summaryrefslogtreecommitdiffstats
path: root/src/VBox/Runtime/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Runtime/common')
-rw-r--r--src/VBox/Runtime/common/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/alloc/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/alloc/alloc.cpp83
-rw-r--r--src/VBox/Runtime/common/alloc/heapoffset.cpp938
-rw-r--r--src/VBox/Runtime/common/alloc/heapsimple.cpp930
-rw-r--r--src/VBox/Runtime/common/alloc/memcache.cpp595
-rw-r--r--src/VBox/Runtime/common/alloc/memtracker.cpp1359
-rw-r--r--src/VBox/Runtime/common/asm/ASMAddFlags.asm80
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm94
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm73
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm88
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm73
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm81
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm66
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm86
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm66
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm66
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm66
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm86
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm80
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm66
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm70
-rw-r--r--src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm80
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitFirstClear-generic.cpp107
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitFirstClear.asm137
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitFirstSet-generic.cpp107
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitFirstSet.asm137
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm103
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm107
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm126
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm101
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm107
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm130
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitNextClear-generic.cpp79
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitNextClear.asm183
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitNextSet-generic.cpp79
-rw-r--r--src/VBox/Runtime/common/asm/ASMBitNextSet.asm183
-rw-r--r--src/VBox/Runtime/common/asm/ASMCpuId.asm121
-rw-r--r--src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm181
-rw-r--r--src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm126
-rw-r--r--src/VBox/Runtime/common/asm/ASMFxRstor.asm74
-rw-r--r--src/VBox/Runtime/common/asm/ASMFxSave.asm74
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetFSBase.asm54
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetFlags.asm53
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetGDTR.asm62
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetGSBase.asm54
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetIDTR.asm62
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm58
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetLDTR.asm53
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetSegAttr.asm71
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetTR.asm53
-rw-r--r--src/VBox/Runtime/common/asm/ASMGetXcr0.asm66
-rw-r--r--src/VBox/Runtime/common/asm/ASMMemFill32-generic.cpp74
-rw-r--r--src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8-generic.cpp55
-rw-r--r--src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm355
-rw-r--r--src/VBox/Runtime/common/asm/ASMMemFirstNonZero-generic.cpp85
-rw-r--r--src/VBox/Runtime/common/asm/ASMMemZero32-generic.cpp51
-rw-r--r--src/VBox/Runtime/common/asm/ASMMemZeroPage-generic.cpp51
-rw-r--r--src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm74
-rw-r--r--src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32-generic.cpp56
-rw-r--r--src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm141
-rw-r--r--src/VBox/Runtime/common/asm/ASMNopPause.asm51
-rw-r--r--src/VBox/Runtime/common/asm/ASMRdMsrEx.asm94
-rw-r--r--src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm59
-rw-r--r--src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm71
-rw-r--r--src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm56
-rw-r--r--src/VBox/Runtime/common/asm/ASMSetFSBase.asm58
-rw-r--r--src/VBox/Runtime/common/asm/ASMSetFlags.asm69
-rw-r--r--src/VBox/Runtime/common/asm/ASMSetGDTR.asm62
-rw-r--r--src/VBox/Runtime/common/asm/ASMSetGSBase.asm58
-rw-r--r--src/VBox/Runtime/common/asm/ASMSetIDTR.asm62
-rw-r--r--src/VBox/Runtime/common/asm/ASMSetXcr0.asm80
-rw-r--r--src/VBox/Runtime/common/asm/ASMWrMsr.asm99
-rw-r--r--src/VBox/Runtime/common/asm/ASMWrMsrEx.asm89
-rw-r--r--src/VBox/Runtime/common/asm/ASMXRstor.asm73
-rw-r--r--src/VBox/Runtime/common/asm/ASMXSave.asm73
-rw-r--r--src/VBox/Runtime/common/asm/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/asm/asm-fake.cpp354
-rw-r--r--src/VBox/Runtime/common/asn1/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-basics.cpp611
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-cursor.cpp678
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-default-allocator.cpp231
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-dump.cpp644
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-efence-allocator.cpp215
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-encode.cpp535
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-safer-allocator.cpp232
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-bitstring-decode.cpp141
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-bitstring-template.h55
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-bitstring.cpp548
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-boolean-decode.cpp93
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-boolean-template.h55
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-boolean.cpp218
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-core-decode.cpp69
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-core-template.h55
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-core.cpp331
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-dyntype-decode.cpp250
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-dyntype.cpp197
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-integer-decode.cpp97
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-integer-template.h55
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-integer.cpp508
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-null-decode.cpp73
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-null.cpp141
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-objid-decode.cpp377
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-objid-template.h64
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-objid.cpp566
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-octetstring-decode.cpp90
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-octetstring-template.h55
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-octetstring.cpp459
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-string-decode.cpp199
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-string-template.h55
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-string-template2.h49
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-string.cpp1855
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-time-decode.cpp415
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-time-template.h55
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-time-template2.h40
-rw-r--r--src/VBox/Runtime/common/asn1/asn1-ut-time.cpp359
-rw-r--r--src/VBox/Runtime/common/asn1/oiddb.cfg351
-rw-r--r--src/VBox/Runtime/common/asn1/oiddb2c.cpp635
-rw-r--r--src/VBox/Runtime/common/checksum/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/checksum/RTSha1Digest.cpp201
-rw-r--r--src/VBox/Runtime/common/checksum/RTSha256Digest.cpp201
-rw-r--r--src/VBox/Runtime/common/checksum/adler32.cpp181
-rw-r--r--src/VBox/Runtime/common/checksum/alt-md2.cpp282
-rw-r--r--src/VBox/Runtime/common/checksum/alt-md4.cpp293
-rw-r--r--src/VBox/Runtime/common/checksum/alt-md5.cpp374
-rw-r--r--src/VBox/Runtime/common/checksum/alt-sha1.cpp535
-rw-r--r--src/VBox/Runtime/common/checksum/alt-sha256.cpp693
-rw-r--r--src/VBox/Runtime/common/checksum/alt-sha3.cpp642
-rw-r--r--src/VBox/Runtime/common/checksum/alt-sha512.cpp805
-rw-r--r--src/VBox/Runtime/common/checksum/crc16ccitt.cpp118
-rw-r--r--src/VBox/Runtime/common/checksum/crc32-zlib.cpp99
-rw-r--r--src/VBox/Runtime/common/checksum/crc32.cpp196
-rw-r--r--src/VBox/Runtime/common/checksum/crc32c.cpp132
-rw-r--r--src/VBox/Runtime/common/checksum/crc64.cpp196
-rw-r--r--src/VBox/Runtime/common/checksum/ipv4.cpp774
-rw-r--r--src/VBox/Runtime/common/checksum/ipv6.cpp136
-rw-r--r--src/VBox/Runtime/common/checksum/manifest-file.cpp94
-rw-r--r--src/VBox/Runtime/common/checksum/manifest.cpp593
-rw-r--r--src/VBox/Runtime/common/checksum/manifest2.cpp1477
-rw-r--r--src/VBox/Runtime/common/checksum/manifest3.cpp667
-rw-r--r--src/VBox/Runtime/common/checksum/md2str.cpp59
-rw-r--r--src/VBox/Runtime/common/checksum/md4str.cpp59
-rw-r--r--src/VBox/Runtime/common/checksum/md5str.cpp59
-rw-r--r--src/VBox/Runtime/common/checksum/openssl-md2.cpp96
-rw-r--r--src/VBox/Runtime/common/checksum/openssl-md4.cpp94
-rw-r--r--src/VBox/Runtime/common/checksum/openssl-md5.cpp84
-rw-r--r--src/VBox/Runtime/common/checksum/openssl-sha1.cpp100
-rw-r--r--src/VBox/Runtime/common/checksum/openssl-sha256.cpp150
-rw-r--r--src/VBox/Runtime/common/checksum/openssl-sha3.cpp270
-rw-r--r--src/VBox/Runtime/common/checksum/openssl-sha512.cpp151
-rw-r--r--src/VBox/Runtime/common/checksum/sha1str.cpp59
-rw-r--r--src/VBox/Runtime/common/checksum/sha224str.cpp59
-rw-r--r--src/VBox/Runtime/common/checksum/sha256str.cpp59
-rw-r--r--src/VBox/Runtime/common/checksum/sha384str.cpp59
-rw-r--r--src/VBox/Runtime/common/checksum/sha512str.cpp59
-rw-r--r--src/VBox/Runtime/common/checksum/sha512t224str.cpp59
-rw-r--r--src/VBox/Runtime/common/checksum/sha512t256str.cpp59
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/atexit-vcc.cpp145
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/except-seh-vcc.cpp243
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/except-vcc.h251
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm358
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/except-x86-vcc.cpp329
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/fltused-vcc.cpp58
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm80
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm108
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/initializers-c-cpp-vcc.cpp158
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/loadcfg-vcc.c154
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/purecall-vcc.cpp63
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/stack-except-seh-vcc.cpp117
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/stack-except-vcc.cpp106
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm157
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm639
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/stacksup-vcc.cpp371
-rw-r--r--src/VBox/Runtime/common/compiler/vcc/tlsdir-vcc.c114
-rw-r--r--src/VBox/Runtime/common/crypto/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/crypto/RTCrPkcs5Pbkdf2Hmac-openssl.cpp86
-rw-r--r--src/VBox/Runtime/common/crypto/RTCrRandBytes-openssl.cpp69
-rw-r--r--src/VBox/Runtime/common/crypto/RTCrStoreCertAddFromJavaKeyStore.cpp320
-rw-r--r--src/VBox/Runtime/common/crypto/RTCrStoreCertAddWantedFromFishingExpedition.cpp256
-rw-r--r--src/VBox/Runtime/common/crypto/RTCrStoreCertExportAsPem.cpp147
-rw-r--r--src/VBox/Runtime/common/crypto/RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp89
-rw-r--r--src/VBox/Runtime/common/crypto/cipher-openssl.cpp596
-rw-r--r--src/VBox/Runtime/common/crypto/digest-builtin.cpp1175
-rw-r--r--src/VBox/Runtime/common/crypto/digest-core.cpp499
-rw-r--r--src/VBox/Runtime/common/crypto/digest-vfs.cpp80
-rw-r--r--src/VBox/Runtime/common/crypto/iprt-openssl.cpp202
-rw-r--r--src/VBox/Runtime/common/crypto/key-create-rsa-openssl.cpp104
-rw-r--r--src/VBox/Runtime/common/crypto/key-file.cpp688
-rw-r--r--src/VBox/Runtime/common/crypto/key-internal.h132
-rw-r--r--src/VBox/Runtime/common/crypto/key-openssl.cpp335
-rw-r--r--src/VBox/Runtime/common/crypto/key.cpp557
-rw-r--r--src/VBox/Runtime/common/crypto/pemfile-read.cpp663
-rw-r--r--src/VBox/Runtime/common/crypto/pemfile-write.cpp266
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs7-asn1-decoder.cpp174
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs7-core.cpp250
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs7-file.cpp119
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs7-init.cpp62
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs7-internal.h47
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs7-sanity.cpp221
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs7-sign.cpp592
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs7-template.h236
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs7-verify.cpp866
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs8-asn1-decoder.cpp53
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs8-core.cpp54
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs8-init.cpp53
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs8-internal.h46
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs8-sanity.cpp53
-rw-r--r--src/VBox/Runtime/common/crypto/pkcs8-template.h69
-rw-r--r--src/VBox/Runtime/common/crypto/pkix-sign.cpp262
-rw-r--r--src/VBox/Runtime/common/crypto/pkix-signature-builtin.cpp153
-rw-r--r--src/VBox/Runtime/common/crypto/pkix-signature-builtin.h51
-rw-r--r--src/VBox/Runtime/common/crypto/pkix-signature-core.cpp294
-rw-r--r--src/VBox/Runtime/common/crypto/pkix-signature-ossl.cpp288
-rw-r--r--src/VBox/Runtime/common/crypto/pkix-signature-rsa.cpp565
-rw-r--r--src/VBox/Runtime/common/crypto/pkix-util.cpp108
-rw-r--r--src/VBox/Runtime/common/crypto/pkix-verify.cpp329
-rw-r--r--src/VBox/Runtime/common/crypto/rc4-openssl.cpp75
-rw-r--r--src/VBox/Runtime/common/crypto/rsa-asn1-decoder.cpp53
-rw-r--r--src/VBox/Runtime/common/crypto/rsa-core.cpp54
-rw-r--r--src/VBox/Runtime/common/crypto/rsa-init.cpp53
-rw-r--r--src/VBox/Runtime/common/crypto/rsa-internal.h50
-rw-r--r--src/VBox/Runtime/common/crypto/rsa-sanity.cpp53
-rw-r--r--src/VBox/Runtime/common/crypto/rsa-template.h118
-rw-r--r--src/VBox/Runtime/common/crypto/spc-asn1-decoder.cpp88
-rw-r--r--src/VBox/Runtime/common/crypto/spc-core.cpp94
-rw-r--r--src/VBox/Runtime/common/crypto/spc-init.cpp55
-rw-r--r--src/VBox/Runtime/common/crypto/spc-internal.h47
-rw-r--r--src/VBox/Runtime/common/crypto/spc-sanity.cpp179
-rw-r--r--src/VBox/Runtime/common/crypto/spc-template.h198
-rw-r--r--src/VBox/Runtime/common/crypto/ssl-openssl.cpp507
-rw-r--r--src/VBox/Runtime/common/crypto/store-cert-add-basic.cpp865
-rw-r--r--src/VBox/Runtime/common/crypto/store-inmem.cpp466
-rw-r--r--src/VBox/Runtime/common/crypto/store-internal.h177
-rw-r--r--src/VBox/Runtime/common/crypto/store.cpp571
-rw-r--r--src/VBox/Runtime/common/crypto/taf-asn1-decoder.cpp54
-rw-r--r--src/VBox/Runtime/common/crypto/taf-core.cpp51
-rw-r--r--src/VBox/Runtime/common/crypto/taf-init.cpp54
-rw-r--r--src/VBox/Runtime/common/crypto/taf-internal.h47
-rw-r--r--src/VBox/Runtime/common/crypto/taf-sanity.cpp52
-rw-r--r--src/VBox/Runtime/common/crypto/taf-template.h104
-rw-r--r--src/VBox/Runtime/common/crypto/tsp-asn1-decoder.cpp51
-rw-r--r--src/VBox/Runtime/common/crypto/tsp-core.cpp51
-rw-r--r--src/VBox/Runtime/common/crypto/tsp-init.cpp51
-rw-r--r--src/VBox/Runtime/common/crypto/tsp-internal.h47
-rw-r--r--src/VBox/Runtime/common/crypto/tsp-sanity.cpp51
-rw-r--r--src/VBox/Runtime/common/crypto/tsp-template.h113
-rw-r--r--src/VBox/Runtime/common/crypto/x509-asn1-decoder.cpp230
-rw-r--r--src/VBox/Runtime/common/crypto/x509-certpaths.cpp3017
-rw-r--r--src/VBox/Runtime/common/crypto/x509-core.cpp1934
-rw-r--r--src/VBox/Runtime/common/crypto/x509-file.cpp174
-rw-r--r--src/VBox/Runtime/common/crypto/x509-init.cpp88
-rw-r--r--src/VBox/Runtime/common/crypto/x509-internal.h47
-rw-r--r--src/VBox/Runtime/common/crypto/x509-sanity.cpp171
-rw-r--r--src/VBox/Runtime/common/crypto/x509-template.h468
-rw-r--r--src/VBox/Runtime/common/crypto/x509-verify.cpp131
-rw-r--r--src/VBox/Runtime/common/dbg/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/dbg/dbg.cpp122
-rw-r--r--src/VBox/Runtime/common/dbg/dbgas.cpp1513
-rw-r--r--src/VBox/Runtime/common/dbg/dbgcfg.cpp2525
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmod.cpp2317
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmodcodeview.cpp3197
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmodcontainer.cpp1050
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmoddbghelp.cpp537
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmoddeferred.cpp730
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmoddwarf.cpp6287
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmodexports.cpp183
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmodghidra.cpp526
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmodldr.cpp286
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmodmapsym.cpp622
-rw-r--r--src/VBox/Runtime/common/dbg/dbgmodnm.cpp581
-rw-r--r--src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm157
-rw-r--r--src/VBox/Runtime/common/dbg/dbgstackdumpself.cpp543
-rw-r--r--src/VBox/Runtime/common/dvm/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/dvm/dvm.cpp940
-rw-r--r--src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp707
-rw-r--r--src/VBox/Runtime/common/dvm/dvmgpt.cpp755
-rw-r--r--src/VBox/Runtime/common/dvm/dvmmbr.cpp1069
-rw-r--r--src/VBox/Runtime/common/dvm/dvmvfs.cpp1467
-rw-r--r--src/VBox/Runtime/common/efi/efiguid.cpp134
-rw-r--r--src/VBox/Runtime/common/efi/efisignaturedb.cpp587
-rw-r--r--src/VBox/Runtime/common/efi/efitime.cpp123
-rw-r--r--src/VBox/Runtime/common/efi/efivarstorevfs.cpp2653
-rw-r--r--src/VBox/Runtime/common/err/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp467
-rw-r--r--src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp465
-rw-r--r--src/VBox/Runtime/common/err/errinfo-alloc.cpp80
-rw-r--r--src/VBox/Runtime/common/err/errinfo.cpp137
-rw-r--r--src/VBox/Runtime/common/err/errinfolog.cpp215
-rw-r--r--src/VBox/Runtime/common/err/errmsg-sorter.cpp483
-rw-r--r--src/VBox/Runtime/common/err/errmsg.cpp338
-rw-r--r--src/VBox/Runtime/common/err/errmsg.sed98
-rw-r--r--src/VBox/Runtime/common/err/errmsgcom.sed76
-rw-r--r--src/VBox/Runtime/common/err/errmsgxpcom.cpp170
-rw-r--r--src/VBox/Runtime/common/err/nocrt-strerror.cpp464
-rw-r--r--src/VBox/Runtime/common/file/nocrt-close.cpp64
-rw-r--r--src/VBox/Runtime/common/file/nocrt-dup.cpp78
-rw-r--r--src/VBox/Runtime/common/file/nocrt-fstat.cpp81
-rw-r--r--src/VBox/Runtime/common/file/nocrt-isatty.cpp72
-rw-r--r--src/VBox/Runtime/common/file/nocrt-open.cpp104
-rw-r--r--src/VBox/Runtime/common/file/nocrt-read.cpp65
-rw-r--r--src/VBox/Runtime/common/fs/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/fs/RTFsCmdLs.cpp1862
-rw-r--r--src/VBox/Runtime/common/fs/extvfs.cpp2860
-rw-r--r--src/VBox/Runtime/common/fs/fatvfs.cpp6374
-rw-r--r--src/VBox/Runtime/common/fs/isomaker.cpp7585
-rw-r--r--src/VBox/Runtime/common/fs/isomakercmd-man.xml590
-rw-r--r--src/VBox/Runtime/common/fs/isomakercmd.cpp3689
-rw-r--r--src/VBox/Runtime/common/fs/isomakerimport.cpp2738
-rw-r--r--src/VBox/Runtime/common/fs/isovfs.cpp7209
-rw-r--r--src/VBox/Runtime/common/fs/ntfsvfs.cpp5698
-rw-r--r--src/VBox/Runtime/common/fs/xfsvfs.cpp2460
-rw-r--r--src/VBox/Runtime/common/fuzz/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/fuzz/fuzz-config.cpp651
-rw-r--r--src/VBox/Runtime/common/fuzz/fuzz-observer.cpp1402
-rw-r--r--src/VBox/Runtime/common/fuzz/fuzz-target-recorder.cpp797
-rw-r--r--src/VBox/Runtime/common/fuzz/fuzz.cpp2322
-rw-r--r--src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp329
-rw-r--r--src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp1877
-rw-r--r--src/VBox/Runtime/common/ioqueue/ioqueue-aiofile-provider.cpp346
-rw-r--r--src/VBox/Runtime/common/ioqueue/ioqueue-stdfile-provider.cpp548
-rw-r--r--src/VBox/Runtime/common/ioqueue/ioqueuebase.cpp297
-rw-r--r--src/VBox/Runtime/common/ldr/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/ldr/ldr.cpp186
-rw-r--r--src/VBox/Runtime/common/ldr/ldrELF.cpp381
-rw-r--r--src/VBox/Runtime/common/ldr/ldrELFRelocatable.cpp.h3163
-rw-r--r--src/VBox/Runtime/common/ldr/ldrEx.cpp773
-rw-r--r--src/VBox/Runtime/common/ldr/ldrFile.cpp317
-rw-r--r--src/VBox/Runtime/common/ldr/ldrLX.cpp3121
-rw-r--r--src/VBox/Runtime/common/ldr/ldrMachO.cpp5734
-rw-r--r--src/VBox/Runtime/common/ldr/ldrMemory.cpp336
-rw-r--r--src/VBox/Runtime/common/ldr/ldrNative.cpp336
-rw-r--r--src/VBox/Runtime/common/ldr/ldrPE.cpp5194
-rw-r--r--src/VBox/Runtime/common/ldr/ldrVfsFile.cpp293
-rw-r--r--src/VBox/Runtime/common/log/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/log/RTLogCreateEx.cpp68
-rw-r--r--src/VBox/Runtime/common/log/RTLogSetR0ThreadNameF.cpp57
-rw-r--r--src/VBox/Runtime/common/log/log-weak-assert.cpp55
-rw-r--r--src/VBox/Runtime/common/log/log-weak-rel.cpp55
-rw-r--r--src/VBox/Runtime/common/log/log-weak.cpp86
-rw-r--r--src/VBox/Runtime/common/log/log.cpp4337
-rw-r--r--src/VBox/Runtime/common/log/logcom.cpp156
-rw-r--r--src/VBox/Runtime/common/log/logellipsis.cpp115
-rw-r--r--src/VBox/Runtime/common/log/logformat.cpp110
-rw-r--r--src/VBox/Runtime/common/log/logrel.cpp106
-rw-r--r--src/VBox/Runtime/common/log/logrelellipsis.cpp88
-rw-r--r--src/VBox/Runtime/common/log/tracebuf.cpp698
-rw-r--r--src/VBox/Runtime/common/log/tracedefault.cpp92
-rw-r--r--src/VBox/Runtime/common/log/tracelogreader.cpp1945
-rw-r--r--src/VBox/Runtime/common/log/tracelogwriter.cpp969
-rw-r--r--src/VBox/Runtime/common/math/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/math/RTUInt128MulByU64.asm91
-rw-r--r--src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm95
-rw-r--r--src/VBox/Runtime/common/math/__fpclassifyd.cpp66
-rw-r--r--src/VBox/Runtime/common/math/__fpclassifyf.cpp66
-rw-r--r--src/VBox/Runtime/common/math/__fpclassifyl.cpp101
-rw-r--r--src/VBox/Runtime/common/math/__isfinite.cpp57
-rw-r--r--src/VBox/Runtime/common/math/__isfinitef.cpp57
-rw-r--r--src/VBox/Runtime/common/math/__isfinitel.cpp63
-rw-r--r--src/VBox/Runtime/common/math/__isinff.cpp57
-rw-r--r--src/VBox/Runtime/common/math/__isinfl.cpp63
-rw-r--r--src/VBox/Runtime/common/math/__isnanl.cpp63
-rw-r--r--src/VBox/Runtime/common/math/__isnormal.cpp57
-rw-r--r--src/VBox/Runtime/common/math/__isnormalf.cpp57
-rw-r--r--src/VBox/Runtime/common/math/__isnormall.cpp63
-rw-r--r--src/VBox/Runtime/common/math/__signbit.cpp57
-rw-r--r--src/VBox/Runtime/common/math/__signbitf.cpp57
-rw-r--r--src/VBox/Runtime/common/math/__signbitl.cpp62
-rw-r--r--src/VBox/Runtime/common/math/atan.asm77
-rw-r--r--src/VBox/Runtime/common/math/atan2.asm72
-rw-r--r--src/VBox/Runtime/common/math/atan2f.asm72
-rw-r--r--src/VBox/Runtime/common/math/atanf.asm77
-rw-r--r--src/VBox/Runtime/common/math/bignum-amd64-x86.asm891
-rw-r--r--src/VBox/Runtime/common/math/bignum.cpp2877
-rw-r--r--src/VBox/Runtime/common/math/ceil.asm79
-rw-r--r--src/VBox/Runtime/common/math/ceilf.asm79
-rw-r--r--src/VBox/Runtime/common/math/ceill.asm70
-rw-r--r--src/VBox/Runtime/common/math/consts.c59
-rw-r--r--src/VBox/Runtime/common/math/copysign.cpp63
-rw-r--r--src/VBox/Runtime/common/math/copysignf.cpp63
-rw-r--r--src/VBox/Runtime/common/math/copysignl.cpp66
-rw-r--r--src/VBox/Runtime/common/math/cos.asm213
-rw-r--r--src/VBox/Runtime/common/math/cosf.asm213
-rw-r--r--src/VBox/Runtime/common/math/cosl.asm72
-rw-r--r--src/VBox/Runtime/common/math/exp.asm151
-rw-r--r--src/VBox/Runtime/common/math/exp2.asm117
-rw-r--r--src/VBox/Runtime/common/math/exp2f.asm117
-rw-r--r--src/VBox/Runtime/common/math/expf.asm151
-rw-r--r--src/VBox/Runtime/common/math/fabs.asm73
-rw-r--r--src/VBox/Runtime/common/math/fabsf.asm72
-rw-r--r--src/VBox/Runtime/common/math/fabsl.asm61
-rw-r--r--src/VBox/Runtime/common/math/feclearexcept.asm121
-rw-r--r--src/VBox/Runtime/common/math/fedisableexcept.asm117
-rw-r--r--src/VBox/Runtime/common/math/feenableexcept.asm121
-rw-r--r--src/VBox/Runtime/common/math/fegetenv.asm90
-rw-r--r--src/VBox/Runtime/common/math/fegetexcept.asm82
-rw-r--r--src/VBox/Runtime/common/math/fegetexceptflag.asm117
-rw-r--r--src/VBox/Runtime/common/math/fegetround.asm79
-rw-r--r--src/VBox/Runtime/common/math/fegetx87precision.asm70
-rw-r--r--src/VBox/Runtime/common/math/feholdexcept.asm99
-rw-r--r--src/VBox/Runtime/common/math/feraiseexcept.asm188
-rw-r--r--src/VBox/Runtime/common/math/fesetenv.asm194
-rw-r--r--src/VBox/Runtime/common/math/fesetexceptflag.asm127
-rw-r--r--src/VBox/Runtime/common/math/fesetround.asm107
-rw-r--r--src/VBox/Runtime/common/math/fesetx87precision.asm88
-rw-r--r--src/VBox/Runtime/common/math/fetestexcept.asm107
-rw-r--r--src/VBox/Runtime/common/math/feupdateenv.asm128
-rw-r--r--src/VBox/Runtime/common/math/floor.asm78
-rw-r--r--src/VBox/Runtime/common/math/floorf.asm78
-rw-r--r--src/VBox/Runtime/common/math/floorl.asm69
-rw-r--r--src/VBox/Runtime/common/math/fma-asm.asm104
-rw-r--r--src/VBox/Runtime/common/math/fma.cpp100
-rw-r--r--src/VBox/Runtime/common/math/fmaf-asm.asm104
-rw-r--r--src/VBox/Runtime/common/math/fmaf.cpp101
-rw-r--r--src/VBox/Runtime/common/math/fmax.cpp64
-rw-r--r--src/VBox/Runtime/common/math/fmaxf.cpp64
-rw-r--r--src/VBox/Runtime/common/math/fmaxl.cpp64
-rw-r--r--src/VBox/Runtime/common/math/fmin.cpp64
-rw-r--r--src/VBox/Runtime/common/math/fminf.cpp64
-rw-r--r--src/VBox/Runtime/common/math/fminl.cpp64
-rw-r--r--src/VBox/Runtime/common/math/frexp.cpp88
-rw-r--r--src/VBox/Runtime/common/math/frexpf.cpp87
-rw-r--r--src/VBox/Runtime/common/math/frexpl.cpp167
-rw-r--r--src/VBox/Runtime/common/math/gcc/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/math/gcc/adddi3.c63
-rw-r--r--src/VBox/Runtime/common/math/gcc/anddi3.c61
-rw-r--r--src/VBox/Runtime/common/math/gcc/ashldi3.c70
-rw-r--r--src/VBox/Runtime/common/math/gcc/ashrdi3.c82
-rw-r--r--src/VBox/Runtime/common/math/gcc/cmpdi2.c62
-rw-r--r--src/VBox/Runtime/common/math/gcc/divdi3.c70
-rw-r--r--src/VBox/Runtime/common/math/gcc/divmoddi4.c84
-rw-r--r--src/VBox/Runtime/common/math/gcc/iordi3.c61
-rw-r--r--src/VBox/Runtime/common/math/gcc/lshldi3.c70
-rw-r--r--src/VBox/Runtime/common/math/gcc/lshrdi3.c69
-rw-r--r--src/VBox/Runtime/common/math/gcc/moddi3.c70
-rw-r--r--src/VBox/Runtime/common/math/gcc/muldi3.c249
-rw-r--r--src/VBox/Runtime/common/math/gcc/negdi2.c60
-rw-r--r--src/VBox/Runtime/common/math/gcc/notdi2.c61
-rw-r--r--src/VBox/Runtime/common/math/gcc/qdivrem.c285
-rw-r--r--src/VBox/Runtime/common/math/gcc/quad.h174
-rw-r--r--src/VBox/Runtime/common/math/gcc/subdi3.c62
-rw-r--r--src/VBox/Runtime/common/math/gcc/ucmpdi2.c61
-rw-r--r--src/VBox/Runtime/common/math/gcc/udivdi3.c56
-rw-r--r--src/VBox/Runtime/common/math/gcc/udivmoddi4.c65
-rw-r--r--src/VBox/Runtime/common/math/gcc/umoddi3.c58
-rw-r--r--src/VBox/Runtime/common/math/gcc/xordi3.c61
-rw-r--r--src/VBox/Runtime/common/math/isinf.cpp57
-rw-r--r--src/VBox/Runtime/common/math/isnan.cpp57
-rw-r--r--src/VBox/Runtime/common/math/isnanf.cpp57
-rw-r--r--src/VBox/Runtime/common/math/ldexp.asm89
-rw-r--r--src/VBox/Runtime/common/math/ldexpf.asm89
-rw-r--r--src/VBox/Runtime/common/math/ldexpl.asm80
-rw-r--r--src/VBox/Runtime/common/math/llrint.asm72
-rw-r--r--src/VBox/Runtime/common/math/llrintf.asm72
-rw-r--r--src/VBox/Runtime/common/math/llrintl.asm70
-rw-r--r--src/VBox/Runtime/common/math/llround.cpp65
-rw-r--r--src/VBox/Runtime/common/math/llroundf.cpp65
-rw-r--r--src/VBox/Runtime/common/math/llroundl.cpp65
-rw-r--r--src/VBox/Runtime/common/math/log.asm97
-rw-r--r--src/VBox/Runtime/common/math/log2.asm229
-rw-r--r--src/VBox/Runtime/common/math/log2f.asm227
-rw-r--r--src/VBox/Runtime/common/math/logf.asm97
-rw-r--r--src/VBox/Runtime/common/math/logl.asm84
-rw-r--r--src/VBox/Runtime/common/math/lrint.asm75
-rw-r--r--src/VBox/Runtime/common/math/lrintf.asm74
-rw-r--r--src/VBox/Runtime/common/math/lrintl.asm77
-rw-r--r--src/VBox/Runtime/common/math/lround.cpp65
-rw-r--r--src/VBox/Runtime/common/math/lroundf.cpp65
-rw-r--r--src/VBox/Runtime/common/math/lroundl.cpp65
-rw-r--r--src/VBox/Runtime/common/math/nocrt-abs.cpp52
-rw-r--r--src/VBox/Runtime/common/math/nocrt-labs.cpp52
-rw-r--r--src/VBox/Runtime/common/math/nocrt-llabs.cpp52
-rw-r--r--src/VBox/Runtime/common/math/pow.asm127
-rw-r--r--src/VBox/Runtime/common/math/powcore.asm633
-rw-r--r--src/VBox/Runtime/common/math/powf.asm127
-rw-r--r--src/VBox/Runtime/common/math/remainder.asm104
-rw-r--r--src/VBox/Runtime/common/math/remainderf.asm104
-rw-r--r--src/VBox/Runtime/common/math/remainderl.asm88
-rw-r--r--src/VBox/Runtime/common/math/rint.asm99
-rw-r--r--src/VBox/Runtime/common/math/rintf.asm99
-rw-r--r--src/VBox/Runtime/common/math/round.cpp69
-rw-r--r--src/VBox/Runtime/common/math/roundf.cpp69
-rw-r--r--src/VBox/Runtime/common/math/roundl.cpp69
-rw-r--r--src/VBox/Runtime/common/math/rtNoCrtHasSse.asm78
-rw-r--r--src/VBox/Runtime/common/math/sin.asm185
-rw-r--r--src/VBox/Runtime/common/math/sincore.asm352
-rw-r--r--src/VBox/Runtime/common/math/sinf.asm185
-rw-r--r--src/VBox/Runtime/common/math/sinl.asm71
-rw-r--r--src/VBox/Runtime/common/math/sqrt.asm65
-rw-r--r--src/VBox/Runtime/common/math/sqrtf.asm65
-rw-r--r--src/VBox/Runtime/common/math/tan.asm119
-rw-r--r--src/VBox/Runtime/common/math/tanf.asm119
-rw-r--r--src/VBox/Runtime/common/math/tanl.asm72
-rw-r--r--src/VBox/Runtime/common/math/trunc.asm108
-rw-r--r--src/VBox/Runtime/common/math/truncf.asm108
-rw-r--r--src/VBox/Runtime/common/math/truncl.asm76
-rw-r--r--src/VBox/Runtime/common/math/watcom/I8D-x86-32.asm108
-rw-r--r--src/VBox/Runtime/common/math/watcom/RTWatcomUInt64Div.c48
-rw-r--r--src/VBox/Runtime/common/math/watcom/U8D-x86-32.asm84
-rw-r--r--src/VBox/Runtime/common/math/watcom/U8LS-x86-32.asm74
-rw-r--r--src/VBox/Runtime/common/math/watcom/U8M-I8M-x86-32.asm87
-rw-r--r--src/VBox/Runtime/common/math/watcom/U8RS-x86-32.asm73
-rw-r--r--src/VBox/Runtime/common/misc/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp52
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2.cpp55
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp55
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp55
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp50
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp55
-rw-r--r--src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp50
-rw-r--r--src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp364
-rw-r--r--src/VBox/Runtime/common/misc/RTFileOpenF.cpp54
-rw-r--r--src/VBox/Runtime/common/misc/RTFileOpenV.cpp58
-rw-r--r--src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp65
-rw-r--r--src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp60
-rw-r--r--src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp58
-rw-r--r--src/VBox/Runtime/common/misc/assert.cpp348
-rw-r--r--src/VBox/Runtime/common/misc/buildconfig.cpp152
-rw-r--r--src/VBox/Runtime/common/misc/cidr.cpp129
-rw-r--r--src/VBox/Runtime/common/misc/circbuf.cpp262
-rw-r--r--src/VBox/Runtime/common/misc/expreval.cpp2740
-rw-r--r--src/VBox/Runtime/common/misc/getopt.cpp922
-rw-r--r--src/VBox/Runtime/common/misc/getoptargv.cpp654
-rw-r--r--src/VBox/Runtime/common/misc/handle.cpp85
-rw-r--r--src/VBox/Runtime/common/misc/handletable.cpp234
-rw-r--r--src/VBox/Runtime/common/misc/handletable.h257
-rw-r--r--src/VBox/Runtime/common/misc/handletablectx.cpp339
-rw-r--r--src/VBox/Runtime/common/misc/handletablesimple.cpp314
-rw-r--r--src/VBox/Runtime/common/misc/inifile.cpp733
-rw-r--r--src/VBox/Runtime/common/misc/json.cpp1914
-rw-r--r--src/VBox/Runtime/common/misc/lockvalidator.cpp4482
-rw-r--r--src/VBox/Runtime/common/misc/message.cpp266
-rw-r--r--src/VBox/Runtime/common/misc/messagerefentry.cpp332
-rw-r--r--src/VBox/Runtime/common/misc/once.cpp450
-rw-r--r--src/VBox/Runtime/common/misc/req.cpp537
-rw-r--r--src/VBox/Runtime/common/misc/reqpool.cpp1299
-rw-r--r--src/VBox/Runtime/common/misc/reqqueue.cpp465
-rw-r--r--src/VBox/Runtime/common/misc/sanity-c.c37
-rw-r--r--src/VBox/Runtime/common/misc/sanity-cpp.cpp38
-rw-r--r--src/VBox/Runtime/common/misc/sanity.h225
-rw-r--r--src/VBox/Runtime/common/misc/semspingpong.cpp218
-rw-r--r--src/VBox/Runtime/common/misc/setjmp.asm148
-rw-r--r--src/VBox/Runtime/common/misc/sg.cpp507
-rw-r--r--src/VBox/Runtime/common/misc/term.cpp252
-rw-r--r--src/VBox/Runtime/common/misc/thread.cpp1446
-rw-r--r--src/VBox/Runtime/common/misc/uri.cpp1181
-rw-r--r--src/VBox/Runtime/common/misc/zero-alt.S118
-rw-r--r--src/VBox/Runtime/common/misc/zero.asm83
-rw-r--r--src/VBox/Runtime/common/misc/zero.cpp52
-rw-r--r--src/VBox/Runtime/common/net/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/net/macstr.cpp123
-rw-r--r--src/VBox/Runtime/common/net/netaddrstr.cpp1233
-rw-r--r--src/VBox/Runtime/common/net/netaddrstr2.cpp737
-rw-r--r--src/VBox/Runtime/common/path/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/path/RTPathAbsDup.cpp49
-rw-r--r--src/VBox/Runtime/common/path/RTPathAbsEx.cpp699
-rw-r--r--src/VBox/Runtime/common/path/RTPathAbsExDup.cpp82
-rw-r--r--src/VBox/Runtime/common/path/RTPathAppend.cpp51
-rw-r--r--src/VBox/Runtime/common/path/RTPathAppendEx.cpp97
-rw-r--r--src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h166
-rw-r--r--src/VBox/Runtime/common/path/RTPathCalcRelative.cpp248
-rw-r--r--src/VBox/Runtime/common/path/RTPathChangeToDosSlashes.cpp74
-rw-r--r--src/VBox/Runtime/common/path/RTPathChangeToUnixSlashes.cpp74
-rw-r--r--src/VBox/Runtime/common/path/RTPathCopyComponents.cpp94
-rw-r--r--src/VBox/Runtime/common/path/RTPathCountComponents.cpp62
-rw-r--r--src/VBox/Runtime/common/path/RTPathEnsureTrailingSeparator.cpp108
-rw-r--r--src/VBox/Runtime/common/path/RTPathExt.cpp79
-rw-r--r--src/VBox/Runtime/common/path/RTPathFilename.cpp109
-rw-r--r--src/VBox/Runtime/common/path/RTPathFilenameUtf16.cpp109
-rw-r--r--src/VBox/Runtime/common/path/RTPathFindCommon.cpp127
-rw-r--r--src/VBox/Runtime/common/path/RTPathFindCommon.cpp.h267
-rw-r--r--src/VBox/Runtime/common/path/RTPathGlob.cpp2177
-rw-r--r--src/VBox/Runtime/common/path/RTPathHasExt.cpp50
-rw-r--r--src/VBox/Runtime/common/path/RTPathHasPath.cpp61
-rw-r--r--src/VBox/Runtime/common/path/RTPathJoin.cpp67
-rw-r--r--src/VBox/Runtime/common/path/RTPathJoinA.cpp83
-rw-r--r--src/VBox/Runtime/common/path/RTPathJoinEx.cpp70
-rw-r--r--src/VBox/Runtime/common/path/RTPathParentLength.cpp95
-rw-r--r--src/VBox/Runtime/common/path/RTPathParentLength.cpp.h68
-rw-r--r--src/VBox/Runtime/common/path/RTPathParse.cpp85
-rw-r--r--src/VBox/Runtime/common/path/RTPathParse.cpp.h256
-rw-r--r--src/VBox/Runtime/common/path/RTPathParseSimple.cpp144
-rw-r--r--src/VBox/Runtime/common/path/RTPathParsedReassemble.cpp161
-rw-r--r--src/VBox/Runtime/common/path/RTPathPurgeFilename.cpp131
-rw-r--r--src/VBox/Runtime/common/path/RTPathRealDup.cpp57
-rw-r--r--src/VBox/Runtime/common/path/RTPathRmCmd.cpp658
-rw-r--r--src/VBox/Runtime/common/path/RTPathSkipRootSpec.cpp51
-rw-r--r--src/VBox/Runtime/common/path/RTPathSplit.cpp143
-rw-r--r--src/VBox/Runtime/common/path/RTPathSplitA.cpp101
-rw-r--r--src/VBox/Runtime/common/path/RTPathSplitReassemble.cpp130
-rw-r--r--src/VBox/Runtime/common/path/RTPathStartsWithRoot.cpp51
-rw-r--r--src/VBox/Runtime/common/path/RTPathStripExt.cpp53
-rw-r--r--src/VBox/Runtime/common/path/RTPathStripFilename.cpp99
-rw-r--r--src/VBox/Runtime/common/path/RTPathStripTrailingSlash.cpp74
-rw-r--r--src/VBox/Runtime/common/path/RTPathTraverseList.cpp90
-rw-r--r--src/VBox/Runtime/common/path/comparepaths.cpp146
-rw-r--r--src/VBox/Runtime/common/path/nocrt-access.cpp78
-rw-r--r--src/VBox/Runtime/common/path/nocrt-unlink.cpp59
-rw-r--r--src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp103
-rw-r--r--src/VBox/Runtime/common/path/rtPathVolumeSpecLen.cpp80
-rw-r--r--src/VBox/Runtime/common/path/rtpath-expand-template.cpp.h92
-rw-r--r--src/VBox/Runtime/common/path/rtpath-root-length-template.cpp.h80
-rw-r--r--src/VBox/Runtime/common/rand/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/rand/nocrt-rand.cpp56
-rw-r--r--src/VBox/Runtime/common/rand/rand.cpp185
-rw-r--r--src/VBox/Runtime/common/rand/randadv.cpp424
-rw-r--r--src/VBox/Runtime/common/rand/randparkmiller.cpp220
-rw-r--r--src/VBox/Runtime/common/rest/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestAnyObject.cpp606
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestArrayBase.cpp496
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestClientApiBase.cpp326
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestClientApiBaseOci.cpp185
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestClientRequestBase.cpp280
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp413
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestJsonPrimaryCursor.cpp120
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestOutputBase.cpp129
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestOutputPrettyBase.cpp134
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestOutputPrettyToString.cpp130
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestOutputToString.cpp130
-rw-r--r--src/VBox/Runtime/common/rest/RTCRestStringMapBase.cpp467
-rw-r--r--src/VBox/Runtime/common/rest/rest-binary.cpp708
-rw-r--r--src/VBox/Runtime/common/rest/rest-primary-object-types.cpp2403
-rw-r--r--src/VBox/Runtime/common/sort/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/sort/RTSortApvIsSorted.cpp58
-rw-r--r--src/VBox/Runtime/common/sort/RTSortIsSorted.cpp60
-rw-r--r--src/VBox/Runtime/common/sort/nocrt-bsearch.cpp80
-rw-r--r--src/VBox/Runtime/common/sort/nocrt-qsort.cpp70
-rw-r--r--src/VBox/Runtime/common/sort/nocrt-qsort_r.cpp56
-rw-r--r--src/VBox/Runtime/common/sort/shellsort.cpp111
-rw-r--r--src/VBox/Runtime/common/string/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/string/RTStrCat.cpp68
-rw-r--r--src/VBox/Runtime/common/string/RTStrCatEx.cpp70
-rw-r--r--src/VBox/Runtime/common/string/RTStrCatP.cpp63
-rw-r--r--src/VBox/Runtime/common/string/RTStrCatPEx.cpp62
-rw-r--r--src/VBox/Runtime/common/string/RTStrCmp.cpp70
-rw-r--r--src/VBox/Runtime/common/string/RTStrCopy.cpp64
-rw-r--r--src/VBox/Runtime/common/string/RTStrCopyEx.cpp66
-rw-r--r--src/VBox/Runtime/common/string/RTStrCopyP.cpp70
-rw-r--r--src/VBox/Runtime/common/string/RTStrCopyPEx.cpp72
-rw-r--r--src/VBox/Runtime/common/string/RTStrEnd.asm100
-rw-r--r--src/VBox/Runtime/common/string/RTStrEnd.cpp57
-rw-r--r--src/VBox/Runtime/common/string/RTStrFormat.cpp59
-rw-r--r--src/VBox/Runtime/common/string/RTStrICmpAscii.cpp88
-rw-r--r--src/VBox/Runtime/common/string/RTStrIStartsWith.cpp56
-rw-r--r--src/VBox/Runtime/common/string/RTStrMemFind32.asm99
-rw-r--r--src/VBox/Runtime/common/string/RTStrMemFind32.cpp60
-rw-r--r--src/VBox/Runtime/common/string/RTStrNCmp.cpp72
-rw-r--r--src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp90
-rw-r--r--src/VBox/Runtime/common/string/RTStrNLen.cpp51
-rw-r--r--src/VBox/Runtime/common/string/RTStrNLenEx.cpp59
-rw-r--r--src/VBox/Runtime/common/string/RTStrPrintHexBytes.cpp95
-rw-r--r--src/VBox/Runtime/common/string/RTStrSplit.cpp133
-rw-r--r--src/VBox/Runtime/common/string/RTStrStartsWith.cpp59
-rw-r--r--src/VBox/Runtime/common/string/RTStrStr.cpp57
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16Cat.cpp54
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16CatAscii.cpp54
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16Chr.cpp62
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16CmpAscii.cpp60
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16Copy.cpp64
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16CopyAscii.cpp82
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16CopyEx.cpp65
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16End.cpp56
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16FindAscii.cpp78
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16ICmpAscii.cpp66
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16NCmp.cpp66
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16NCmpAscii.cpp61
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16NCmpUtf8.cpp74
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16NICmpAscii.cpp71
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16NLen.cpp53
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16NLenEx.cpp59
-rw-r--r--src/VBox/Runtime/common/string/RTUtf16PrintHexBytes.cpp71
-rw-r--r--src/VBox/Runtime/common/string/atoi.cpp64
-rw-r--r--src/VBox/Runtime/common/string/base64-utf16.cpp446
-rw-r--r--src/VBox/Runtime/common/string/base64.cpp539
-rw-r--r--src/VBox/Runtime/common/string/base64.h101
-rw-r--r--src/VBox/Runtime/common/string/bzero.asm137
-rw-r--r--src/VBox/Runtime/common/string/memchr.asm103
-rw-r--r--src/VBox/Runtime/common/string/memchr.cpp75
-rw-r--r--src/VBox/Runtime/common/string/memcmp.asm155
-rw-r--r--src/VBox/Runtime/common/string/memcmp.cpp97
-rw-r--r--src/VBox/Runtime/common/string/memcpy.asm122
-rw-r--r--src/VBox/Runtime/common/string/memcpy.cpp92
-rw-r--r--src/VBox/Runtime/common/string/memmove.asm158
-rw-r--r--src/VBox/Runtime/common/string/mempcpy.asm110
-rw-r--r--src/VBox/Runtime/common/string/mempcpy.cpp55
-rw-r--r--src/VBox/Runtime/common/string/memrchr.asm107
-rw-r--r--src/VBox/Runtime/common/string/memrchr.cpp72
-rw-r--r--src/VBox/Runtime/common/string/memset.asm141
-rw-r--r--src/VBox/Runtime/common/string/memset.cpp86
-rw-r--r--src/VBox/Runtime/common/string/ministring.cpp1210
-rw-r--r--src/VBox/Runtime/common/string/nocrt-atof.cpp52
-rw-r--r--src/VBox/Runtime/common/string/nocrt-scprintf.cpp58
-rw-r--r--src/VBox/Runtime/common/string/nocrt-snprintf.cpp66
-rw-r--r--src/VBox/Runtime/common/string/nocrt-sscanf.cpp57
-rw-r--r--src/VBox/Runtime/common/string/nocrt-strdup.cpp52
-rw-r--r--src/VBox/Runtime/common/string/nocrt-stricmp.cpp52
-rw-r--r--src/VBox/Runtime/common/string/nocrt-strtod.cpp59
-rw-r--r--src/VBox/Runtime/common/string/nocrt-strtok.cpp56
-rw-r--r--src/VBox/Runtime/common/string/nocrt-strtol.cpp72
-rw-r--r--src/VBox/Runtime/common/string/nocrt-strtoll.cpp69
-rw-r--r--src/VBox/Runtime/common/string/nocrt-strtoul.cpp73
-rw-r--r--src/VBox/Runtime/common/string/nocrt-strtoull.cpp70
-rw-r--r--src/VBox/Runtime/common/string/nocrt-vscprintf.cpp65
-rw-r--r--src/VBox/Runtime/common/string/nocrt-vsnprintf.cpp63
-rw-r--r--src/VBox/Runtime/common/string/nocrt-vsscanf.cpp403
-rw-r--r--src/VBox/Runtime/common/string/simplepattern.cpp204
-rw-r--r--src/VBox/Runtime/common/string/straprintf.cpp207
-rw-r--r--src/VBox/Runtime/common/string/strcache.cpp1239
-rw-r--r--src/VBox/Runtime/common/string/strcat.cpp63
-rw-r--r--src/VBox/Runtime/common/string/strchr.asm152
-rw-r--r--src/VBox/Runtime/common/string/strchr.cpp67
-rw-r--r--src/VBox/Runtime/common/string/strcmp.asm111
-rw-r--r--src/VBox/Runtime/common/string/strcpy.asm103
-rw-r--r--src/VBox/Runtime/common/string/strcpy.cpp66
-rw-r--r--src/VBox/Runtime/common/string/strcspn.cpp65
-rw-r--r--src/VBox/Runtime/common/string/strformat.cpp866
-rw-r--r--src/VBox/Runtime/common/string/strformatfloat.cpp353
-rw-r--r--src/VBox/Runtime/common/string/strformatnum.cpp245
-rw-r--r--src/VBox/Runtime/common/string/strformatrt.cpp1722
-rw-r--r--src/VBox/Runtime/common/string/strformattype.cpp487
-rw-r--r--src/VBox/Runtime/common/string/strhash1.cpp82
-rw-r--r--src/VBox/Runtime/common/string/stringalloc.cpp321
-rw-r--r--src/VBox/Runtime/common/string/strlen.asm75
-rw-r--r--src/VBox/Runtime/common/string/strlen.cpp66
-rw-r--r--src/VBox/Runtime/common/string/strncat.cpp69
-rw-r--r--src/VBox/Runtime/common/string/strncmp.asm141
-rw-r--r--src/VBox/Runtime/common/string/strncmp.cpp70
-rw-r--r--src/VBox/Runtime/common/string/strncpy.asm139
-rw-r--r--src/VBox/Runtime/common/string/strnlen.cpp62
-rw-r--r--src/VBox/Runtime/common/string/strpbrk.cpp81
-rw-r--r--src/VBox/Runtime/common/string/strprintf-ellipsis.cpp67
-rw-r--r--src/VBox/Runtime/common/string/strprintf.cpp131
-rw-r--r--src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp67
-rw-r--r--src/VBox/Runtime/common/string/strprintf2.cpp150
-rw-r--r--src/VBox/Runtime/common/string/strrchr.cpp52
-rw-r--r--src/VBox/Runtime/common/string/strspace.cpp195
-rw-r--r--src/VBox/Runtime/common/string/strstr.cpp78
-rw-r--r--src/VBox/Runtime/common/string/strstrip.cpp103
-rw-r--r--src/VBox/Runtime/common/string/strtofloat.cpp1400
-rw-r--r--src/VBox/Runtime/common/string/strtok_r.cpp90
-rw-r--r--src/VBox/Runtime/common/string/strtonum.cpp934
-rw-r--r--src/VBox/Runtime/common/string/strversion.cpp235
-rw-r--r--src/VBox/Runtime/common/string/uni.cpp53
-rw-r--r--src/VBox/Runtime/common/string/unidata-flags.cpp47017
-rw-r--r--src/VBox/Runtime/common/string/unidata-lower.cpp3911
-rw-r--r--src/VBox/Runtime/common/string/unidata-upper.cpp4078
-rw-r--r--src/VBox/Runtime/common/string/uniread.cpp1336
-rw-r--r--src/VBox/Runtime/common/string/utf-16-case.cpp449
-rw-r--r--src/VBox/Runtime/common/string/utf-16-latin-1.cpp491
-rw-r--r--src/VBox/Runtime/common/string/utf-16-printf.cpp243
-rw-r--r--src/VBox/Runtime/common/string/utf-16.cpp1329
-rw-r--r--src/VBox/Runtime/common/string/utf-8-case.cpp318
-rw-r--r--src/VBox/Runtime/common/string/utf-8-case2.cpp128
-rw-r--r--src/VBox/Runtime/common/string/utf-8.cpp2043
-rw-r--r--src/VBox/Runtime/common/string/watcom/bzero.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/memchr.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/memcmp.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/memcpy.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/memmove.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/mempcpy.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/memrchr.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/memset.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/strchr.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/strcmp.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/strcpy.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/strlen.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/strncmp.asm42
-rw-r--r--src/VBox/Runtime/common/string/watcom/strncpy.asm42
-rw-r--r--src/VBox/Runtime/common/string/wcslen.asm75
-rw-r--r--src/VBox/Runtime/common/table/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/table/avl_Base.cpp.h474
-rw-r--r--src/VBox/Runtime/common/table/avl_Destroy.cpp.h120
-rw-r--r--src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h156
-rw-r--r--src/VBox/Runtime/common/table/avl_Enum.cpp.h105
-rw-r--r--src/VBox/Runtime/common/table/avl_Get.cpp.h77
-rw-r--r--src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h113
-rw-r--r--src/VBox/Runtime/common/table/avl_Range.cpp.h94
-rw-r--r--src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h80
-rw-r--r--src/VBox/Runtime/common/table/avl_RemoveNode.cpp.h153
-rw-r--r--src/VBox/Runtime/common/table/avlgcphys.cpp88
-rw-r--r--src/VBox/Runtime/common/table/avlgcptr.cpp88
-rw-r--r--src/VBox/Runtime/common/table/avlhcphys.cpp88
-rw-r--r--src/VBox/Runtime/common/table/avllu32.cpp89
-rw-r--r--src/VBox/Runtime/common/table/avlogcphys.cpp89
-rw-r--r--src/VBox/Runtime/common/table/avlogcptr.cpp90
-rw-r--r--src/VBox/Runtime/common/table/avlohcphys.cpp89
-rw-r--r--src/VBox/Runtime/common/table/avloioport.cpp89
-rw-r--r--src/VBox/Runtime/common/table/avlou32.cpp90
-rw-r--r--src/VBox/Runtime/common/table/avlpv.cpp88
-rw-r--r--src/VBox/Runtime/common/table/avlrfoff.cpp94
-rw-r--r--src/VBox/Runtime/common/table/avlrgcptr.cpp94
-rw-r--r--src/VBox/Runtime/common/table/avlrogcphys.cpp95
-rw-r--r--src/VBox/Runtime/common/table/avlrogcptr.cpp95
-rw-r--r--src/VBox/Runtime/common/table/avlroioport.cpp93
-rw-r--r--src/VBox/Runtime/common/table/avlroogcptr.cpp102
-rw-r--r--src/VBox/Runtime/common/table/avlrpv.cpp93
-rw-r--r--src/VBox/Runtime/common/table/avlru64.cpp93
-rw-r--r--src/VBox/Runtime/common/table/avlruintptr.cpp94
-rw-r--r--src/VBox/Runtime/common/table/avlu32.cpp88
-rw-r--r--src/VBox/Runtime/common/table/avlu64.cpp88
-rw-r--r--src/VBox/Runtime/common/table/avluintptr.cpp88
-rw-r--r--src/VBox/Runtime/common/table/avlul.cpp88
-rw-r--r--src/VBox/Runtime/common/table/table.cpp42
-rw-r--r--src/VBox/Runtime/common/time/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/time/RTTimeFormatDurationEx.cpp267
-rw-r--r--src/VBox/Runtime/common/time/time.cpp1492
-rw-r--r--src/VBox/Runtime/common/time/timeprog.cpp107
-rw-r--r--src/VBox/Runtime/common/time/timesup.cpp467
-rw-r--r--src/VBox/Runtime/common/time/timesupA.asm161
-rw-r--r--src/VBox/Runtime/common/time/timesupA.mac895
-rw-r--r--src/VBox/Runtime/common/time/timesupref.cpp318
-rw-r--r--src/VBox/Runtime/common/time/timesupref.h408
-rw-r--r--src/VBox/Runtime/common/time/timesysalias.cpp67
-rwxr-xr-xsrc/VBox/Runtime/common/time/timezoneinfo-gen.py470
-rw-r--r--src/VBox/Runtime/common/time/timezoneinfo.cpp1171
-rw-r--r--src/VBox/Runtime/common/vfs/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/vfs/vfsbase.cpp4385
-rw-r--r--src/VBox/Runtime/common/vfs/vfschain.cpp1861
-rw-r--r--src/VBox/Runtime/common/vfs/vfsfss2dir.cpp445
-rw-r--r--src/VBox/Runtime/common/vfs/vfsiosmisc.cpp238
-rw-r--r--src/VBox/Runtime/common/vfs/vfsmemory.cpp971
-rw-r--r--src/VBox/Runtime/common/vfs/vfsmisc.cpp99
-rw-r--r--src/VBox/Runtime/common/vfs/vfsmount.cpp584
-rw-r--r--src/VBox/Runtime/common/vfs/vfsmsg.cpp78
-rw-r--r--src/VBox/Runtime/common/vfs/vfsprintf.cpp165
-rw-r--r--src/VBox/Runtime/common/vfs/vfsprogress.cpp554
-rw-r--r--src/VBox/Runtime/common/vfs/vfsreadahead.cpp1013
-rw-r--r--src/VBox/Runtime/common/vfs/vfsstddir.cpp867
-rw-r--r--src/VBox/Runtime/common/vfs/vfsstdfile.cpp669
-rw-r--r--src/VBox/Runtime/common/vfs/vfsstdpipe.cpp324
-rw-r--r--src/VBox/Runtime/common/zip/Makefile.kup0
-rw-r--r--src/VBox/Runtime/common/zip/cpiovfs.cpp1146
-rw-r--r--src/VBox/Runtime/common/zip/cpiovfsreader.h169
-rw-r--r--src/VBox/Runtime/common/zip/gzipcmd.cpp606
-rw-r--r--src/VBox/Runtime/common/zip/gzipvfs.cpp1035
-rw-r--r--src/VBox/Runtime/common/zip/pkzip.cpp259
-rw-r--r--src/VBox/Runtime/common/zip/pkzipvfs.cpp1296
-rw-r--r--src/VBox/Runtime/common/zip/tar.h146
-rw-r--r--src/VBox/Runtime/common/zip/tarcmd.cpp1960
-rw-r--r--src/VBox/Runtime/common/zip/tarvfs.cpp1477
-rw-r--r--src/VBox/Runtime/common/zip/tarvfsreader.h179
-rw-r--r--src/VBox/Runtime/common/zip/tarvfswriter.cpp2363
-rw-r--r--src/VBox/Runtime/common/zip/unzipcmd.cpp480
-rw-r--r--src/VBox/Runtime/common/zip/xarvfs.cpp2156
-rw-r--r--src/VBox/Runtime/common/zip/zip.cpp2016
844 files changed, 344645 insertions, 0 deletions
diff --git a/src/VBox/Runtime/common/Makefile.kup b/src/VBox/Runtime/common/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/Makefile.kup
diff --git a/src/VBox/Runtime/common/alloc/Makefile.kup b/src/VBox/Runtime/common/alloc/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/alloc/Makefile.kup
diff --git a/src/VBox/Runtime/common/alloc/alloc.cpp b/src/VBox/Runtime/common/alloc/alloc.cpp
new file mode 100644
index 00000000..f07f3013
--- /dev/null
+++ b/src/VBox/Runtime/common/alloc/alloc.cpp
@@ -0,0 +1,83 @@
+/* $Id: alloc.cpp $ */
+/** @file
+ * IPRT - Memory Allocation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifndef RTMEM_NO_WRAP_TO_EF_APIS
+# define RTMEM_NO_WRAP_TO_EF_APIS
+#endif
+#include <iprt/mem.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(void *) RTMemDupTag(const void *pvSrc, size_t cb, const char *pszTag) RT_NO_THROW_DEF
+{
+ void *pvDst = RTMemAllocTag(cb, pszTag);
+ if (pvDst)
+ memcpy(pvDst, pvSrc, cb);
+ return pvDst;
+}
+RT_EXPORT_SYMBOL(RTMemDupTag);
+
+
+RTDECL(void *) RTMemDupExTag(const void *pvSrc, size_t cbSrc, size_t cbExtra, const char *pszTag) RT_NO_THROW_DEF
+{
+ void *pvDst = RTMemAllocTag(cbSrc + cbExtra, pszTag);
+ if (pvDst)
+ {
+ memcpy(pvDst, pvSrc, cbSrc);
+ memset((uint8_t *)pvDst + cbSrc, 0, cbExtra);
+ }
+ return pvDst;
+}
+RT_EXPORT_SYMBOL(RTMemDupExTag);
+
+
+RTDECL(void *) RTMemReallocZTag(void *pvOld, size_t cbOld, size_t cbNew, const char *pszTag) RT_NO_THROW_DEF
+{
+ void *pvDst = RTMemReallocTag(pvOld, cbNew, pszTag);
+ if (pvDst && cbNew > cbOld)
+ memset((uint8_t *)pvDst + cbOld, 0, cbNew - cbOld);
+ return pvDst;
+}
+RT_EXPORT_SYMBOL(RTMemReallocZTag);
+
diff --git a/src/VBox/Runtime/common/alloc/heapoffset.cpp b/src/VBox/Runtime/common/alloc/heapoffset.cpp
new file mode 100644
index 00000000..7be6f10a
--- /dev/null
+++ b/src/VBox/Runtime/common/alloc/heapoffset.cpp
@@ -0,0 +1,938 @@
+/* $Id: heapoffset.cpp $ */
+/** @file
+ * IPRT - An Offset Based Heap.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DEFAULT
+#include <iprt/heap.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the heap anchor block. */
+typedef struct RTHEAPOFFSETINTERNAL *PRTHEAPOFFSETINTERNAL;
+/** Pointer to a heap block. */
+typedef struct RTHEAPOFFSETBLOCK *PRTHEAPOFFSETBLOCK;
+/** Pointer to a free heap block. */
+typedef struct RTHEAPOFFSETFREE *PRTHEAPOFFSETFREE;
+
+/**
+ * Structure describing a block in an offset based heap.
+ *
+ * If this block is allocated, it is followed by the user data.
+ * If this block is free, see RTHEAPOFFSETFREE.
+ */
+typedef struct RTHEAPOFFSETBLOCK
+{
+ /** The next block in the global block list. */
+ uint32_t /*PRTHEAPOFFSETBLOCK*/ offNext;
+ /** The previous block in the global block list. */
+ uint32_t /*PRTHEAPOFFSETBLOCK*/ offPrev;
+ /** Offset into the heap of this block. Used to locate the anchor block. */
+ uint32_t /*PRTHEAPOFFSETINTERNAL*/ offSelf;
+ /** Flags + magic. */
+ uint32_t fFlags;
+} RTHEAPOFFSETBLOCK;
+AssertCompileSize(RTHEAPOFFSETBLOCK, 16);
+
+/** The block is free if this flag is set. When cleared it's allocated. */
+#define RTHEAPOFFSETBLOCK_FLAGS_FREE (RT_BIT_32(0))
+/** The magic value. */
+#define RTHEAPOFFSETBLOCK_FLAGS_MAGIC (UINT32_C(0xabcdef00))
+/** The mask that needs to be applied to RTHEAPOFFSETBLOCK::fFlags to obtain the magic value. */
+#define RTHEAPOFFSETBLOCK_FLAGS_MAGIC_MASK (~RT_BIT_32(0))
+
+/**
+ * Checks if the specified block is valid or not.
+ * @returns boolean answer.
+ * @param pBlock Pointer to a RTHEAPOFFSETBLOCK structure.
+ */
+#define RTHEAPOFFSETBLOCK_IS_VALID(pBlock) \
+ ( ((pBlock)->fFlags & RTHEAPOFFSETBLOCK_FLAGS_MAGIC_MASK) == RTHEAPOFFSETBLOCK_FLAGS_MAGIC )
+
+/**
+ * Checks if the specified block is valid and in use.
+ * @returns boolean answer.
+ * @param pBlock Pointer to a RTHEAPOFFSETBLOCK structure.
+ */
+#define RTHEAPOFFSETBLOCK_IS_VALID_USED(pBlock) \
+ ( ((pBlock)->fFlags & (RTHEAPOFFSETBLOCK_FLAGS_MAGIC_MASK | RTHEAPOFFSETBLOCK_FLAGS_FREE)) \
+ == RTHEAPOFFSETBLOCK_FLAGS_MAGIC )
+
+/**
+ * Checks if the specified block is valid and free.
+ * @returns boolean answer.
+ * @param pBlock Pointer to a RTHEAPOFFSETBLOCK structure.
+ */
+#define RTHEAPOFFSETBLOCK_IS_VALID_FREE(pBlock) \
+ ( ((pBlock)->fFlags & (RTHEAPOFFSETBLOCK_FLAGS_MAGIC_MASK | RTHEAPOFFSETBLOCK_FLAGS_FREE)) \
+ == (RTHEAPOFFSETBLOCK_FLAGS_MAGIC | RTHEAPOFFSETBLOCK_FLAGS_FREE) )
+
+/**
+ * Checks if the specified block is free or not.
+ * @returns boolean answer.
+ * @param pBlock Pointer to a valid RTHEAPOFFSETBLOCK structure.
+ */
+#define RTHEAPOFFSETBLOCK_IS_FREE(pBlock) (!!((pBlock)->fFlags & RTHEAPOFFSETBLOCK_FLAGS_FREE))
+
+/**
+ * A free heap block.
+ * This is an extended version of RTHEAPOFFSETBLOCK that takes the unused
+ * user data to store free list pointers and a cached size value.
+ */
+typedef struct RTHEAPOFFSETFREE
+{
+ /** Core stuff. */
+ RTHEAPOFFSETBLOCK Core;
+ /** Pointer to the next free block. */
+ uint32_t /*PRTHEAPOFFSETFREE*/ offNext;
+ /** Pointer to the previous free block. */
+ uint32_t /*PRTHEAPOFFSETFREE*/ offPrev;
+ /** The size of the block (excluding the RTHEAPOFFSETBLOCK part). */
+ uint32_t cb;
+ /** An alignment filler to make it a multiple of 16 bytes. */
+ uint32_t Alignment;
+} RTHEAPOFFSETFREE;
+AssertCompileSize(RTHEAPOFFSETFREE, 16+16);
+
+
+/**
+ * The heap anchor block.
+ * This structure is placed at the head of the memory block specified to RTHeapOffsetInit(),
+ * which means that the first RTHEAPOFFSETBLOCK appears immediately after this structure.
+ */
+typedef struct RTHEAPOFFSETINTERNAL
+{
+ /** The typical magic (RTHEAPOFFSET_MAGIC). */
+ uint32_t u32Magic;
+ /** The heap size. (This structure is included!) */
+ uint32_t cbHeap;
+ /** The amount of free memory in the heap. */
+ uint32_t cbFree;
+ /** Free head pointer. */
+ uint32_t /*PRTHEAPOFFSETFREE*/ offFreeHead;
+ /** Free tail pointer. */
+ uint32_t /*PRTHEAPOFFSETFREE*/ offFreeTail;
+ /** Make the size of this structure 32 bytes. */
+ uint32_t au32Alignment[3];
+} RTHEAPOFFSETINTERNAL;
+AssertCompileSize(RTHEAPOFFSETINTERNAL, 32);
+
+
+/** The minimum allocation size. */
+#define RTHEAPOFFSET_MIN_BLOCK (sizeof(RTHEAPOFFSETBLOCK))
+AssertCompile(RTHEAPOFFSET_MIN_BLOCK >= sizeof(RTHEAPOFFSETBLOCK));
+AssertCompile(RTHEAPOFFSET_MIN_BLOCK >= sizeof(RTHEAPOFFSETFREE) - sizeof(RTHEAPOFFSETBLOCK));
+
+/** The minimum and default alignment. */
+#define RTHEAPOFFSET_ALIGNMENT (sizeof(RTHEAPOFFSETBLOCK))
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef RT_STRICT
+# define RTHEAPOFFSET_STRICT 1
+#endif
+
+/**
+ * Converts RTHEAPOFFSETBLOCK::offSelf into a heap anchor block pointer.
+ *
+ * @returns Pointer of given type.
+ * @param pBlock The block to find the heap anchor block for.
+ */
+#define RTHEAPOFF_GET_ANCHOR(pBlock) ( (PRTHEAPOFFSETINTERNAL)((uint8_t *)(pBlock) - (pBlock)->offSelf ) )
+
+
+/**
+ * Converts an offset to a pointer.
+ *
+ * All offsets are relative to the heap to make life simple.
+ *
+ * @returns Pointer of given type.
+ * @param pHeapInt Pointer to the heap anchor block.
+ * @param off The offset to convert.
+ * @param type The desired type.
+ */
+#ifdef RTHEAPOFFSET_STRICT
+# define RTHEAPOFF_TO_PTR_N(pHeapInt, off, type) ( (type)rtHeapOffCheckedOffToPtr(pHeapInt, off, true /*fNull*/) )
+#else
+# define RTHEAPOFF_TO_PTR_N(pHeapInt, off, type) ( (type)((off) ? (uint8_t *)(pHeapInt) + (off) : NULL) )
+#endif
+
+/**
+ * Converts an offset to a pointer.
+ *
+ * All offsets are relative to the heap to make life simple.
+ *
+ * @returns Pointer of given type.
+ * @param pHeapInt Pointer to the heap anchor block.
+ * @param off The offset to convert.
+ * @param type The desired type.
+ */
+#ifdef RTHEAPOFFSET_STRICT
+# define RTHEAPOFF_TO_PTR(pHeapInt, off, type) ( (type)rtHeapOffCheckedOffToPtr(pHeapInt, off, false /*fNull*/) )
+#else
+# define RTHEAPOFF_TO_PTR(pHeapInt, off, type) ( (type)((uint8_t *)(pHeapInt) + (off)) )
+#endif
+
+/**
+ * Converts a pointer to an offset.
+ *
+ * All offsets are relative to the heap to make life simple.
+ *
+ * @returns Offset into the heap.
+ * @param pHeapInt Pointer to the heap anchor block.
+ * @param ptr The pointer to convert.
+ */
+#ifdef RTHEAPOFFSET_STRICT
+# define RTHEAPOFF_TO_OFF(pHeapInt, ptr) rtHeapOffCheckedPtrToOff(pHeapInt, ptr)
+#else
+# define RTHEAPOFF_TO_OFF(pHeapInt, ptr) ( (uint32_t)((ptr) ? (uintptr_t)(ptr) - (uintptr_t)(pHeapInt) : UINT32_C(0)) )
+#endif
+
+#define ASSERT_L(a, b) AssertMsg((a) < (b), ("a=%08x b=%08x\n", (a), (b)))
+#define ASSERT_LE(a, b) AssertMsg((a) <= (b), ("a=%08x b=%08x\n", (a), (b)))
+#define ASSERT_G(a, b) AssertMsg((a) > (b), ("a=%08x b=%08x\n", (a), (b)))
+#define ASSERT_GE(a, b) AssertMsg((a) >= (b), ("a=%08x b=%08x\n", (a), (b)))
+#define ASSERT_ALIGN(a) AssertMsg(!((uintptr_t)(a) & (RTHEAPOFFSET_ALIGNMENT - 1)), ("a=%p\n", (uintptr_t)(a)))
+
+#define ASSERT_PREV(pHeapInt, pBlock) \
+ do { ASSERT_ALIGN((pBlock)->offPrev); \
+ if ((pBlock)->offPrev) \
+ { \
+ ASSERT_L((pBlock)->offPrev, RTHEAPOFF_TO_OFF(pHeapInt, pBlock)); \
+ ASSERT_GE((pBlock)->offPrev, sizeof(RTHEAPOFFSETINTERNAL)); \
+ } \
+ else \
+ Assert((pBlock) == (PRTHEAPOFFSETBLOCK)((pHeapInt) + 1)); \
+ } while (0)
+
+#define ASSERT_NEXT(pHeap, pBlock) \
+ do { ASSERT_ALIGN((pBlock)->offNext); \
+ if ((pBlock)->offNext) \
+ { \
+ ASSERT_L((pBlock)->offNext, (pHeapInt)->cbHeap); \
+ ASSERT_G((pBlock)->offNext, RTHEAPOFF_TO_OFF(pHeapInt, pBlock)); \
+ } \
+ } while (0)
+
+#define ASSERT_BLOCK(pHeapInt, pBlock) \
+ do { AssertMsg(RTHEAPOFFSETBLOCK_IS_VALID(pBlock), ("%#x\n", (pBlock)->fFlags)); \
+ AssertMsg(RTHEAPOFF_GET_ANCHOR(pBlock) == (pHeapInt), ("%p != %p\n", RTHEAPOFF_GET_ANCHOR(pBlock), (pHeapInt))); \
+ ASSERT_GE(RTHEAPOFF_TO_OFF(pHeapInt, pBlock), sizeof(RTHEAPOFFSETINTERNAL)); \
+ ASSERT_L( RTHEAPOFF_TO_OFF(pHeapInt, pBlock), (pHeapInt)->cbHeap); \
+ ASSERT_NEXT(pHeapInt, pBlock); \
+ ASSERT_PREV(pHeapInt, pBlock); \
+ } while (0)
+
+#define ASSERT_BLOCK_USED(pHeapInt, pBlock) \
+ do { AssertMsg(RTHEAPOFFSETBLOCK_IS_VALID_USED((pBlock)), ("%#x\n", (pBlock)->fFlags)); \
+ AssertMsg(RTHEAPOFF_GET_ANCHOR(pBlock) == (pHeapInt), ("%p != %p\n", RTHEAPOFF_GET_ANCHOR(pBlock), (pHeapInt))); \
+ ASSERT_GE(RTHEAPOFF_TO_OFF(pHeapInt, pBlock), sizeof(RTHEAPOFFSETINTERNAL)); \
+ ASSERT_L( RTHEAPOFF_TO_OFF(pHeapInt, pBlock), (pHeapInt)->cbHeap); \
+ ASSERT_NEXT(pHeapInt, pBlock); \
+ ASSERT_PREV(pHeapInt, pBlock); \
+ } while (0)
+
+#define ASSERT_FREE_PREV(pHeapInt, pBlock) \
+ do { ASSERT_ALIGN((pBlock)->offPrev); \
+ if ((pBlock)->offPrev) \
+ { \
+ ASSERT_GE((pBlock)->offPrev, (pHeapInt)->offFreeHead); \
+ ASSERT_L((pBlock)->offPrev, RTHEAPOFF_TO_OFF(pHeapInt, pBlock)); \
+ ASSERT_LE((pBlock)->offPrev, (pBlock)->Core.offPrev); \
+ } \
+ else \
+ Assert((pBlock) == RTHEAPOFF_TO_PTR(pHeapInt, (pHeapInt)->offFreeHead, PRTHEAPOFFSETFREE) ); \
+ } while (0)
+
+#define ASSERT_FREE_NEXT(pHeapInt, pBlock) \
+ do { ASSERT_ALIGN((pBlock)->offNext); \
+ if ((pBlock)->offNext) \
+ { \
+ ASSERT_LE((pBlock)->offNext, (pHeapInt)->offFreeTail); \
+ ASSERT_G((pBlock)->offNext, RTHEAPOFF_TO_OFF(pHeapInt, pBlock)); \
+ ASSERT_GE((pBlock)->offNext, (pBlock)->Core.offNext); \
+ } \
+ else \
+ Assert((pBlock) == RTHEAPOFF_TO_PTR(pHeapInt, (pHeapInt)->offFreeTail, PRTHEAPOFFSETFREE)); \
+ } while (0)
+
+#ifdef RTHEAPOFFSET_STRICT
+# define ASSERT_FREE_CB(pHeapInt, pBlock) \
+ do { size_t cbCalc = ((pBlock)->Core.offNext ? (pBlock)->Core.offNext : (pHeapInt)->cbHeap) \
+ - RTHEAPOFF_TO_OFF((pHeapInt), (pBlock)) - sizeof(RTHEAPOFFSETBLOCK); \
+ AssertMsg((pBlock)->cb == cbCalc, ("cb=%#zx cbCalc=%#zx\n", (pBlock)->cb, cbCalc)); \
+ } while (0)
+#else
+# define ASSERT_FREE_CB(pHeapInt, pBlock) do {} while (0)
+#endif
+
+/** Asserts that a free block is valid. */
+#define ASSERT_BLOCK_FREE(pHeapInt, pBlock) \
+ do { ASSERT_BLOCK(pHeapInt, &(pBlock)->Core); \
+ Assert(RTHEAPOFFSETBLOCK_IS_VALID_FREE(&(pBlock)->Core)); \
+ ASSERT_GE(RTHEAPOFF_TO_OFF(pHeapInt, pBlock), (pHeapInt)->offFreeHead); \
+ ASSERT_LE(RTHEAPOFF_TO_OFF(pHeapInt, pBlock), (pHeapInt)->offFreeTail); \
+ ASSERT_FREE_NEXT(pHeapInt, pBlock); \
+ ASSERT_FREE_PREV(pHeapInt, pBlock); \
+ ASSERT_FREE_CB(pHeapInt, pBlock); \
+ } while (0)
+
+/** Asserts that the heap anchor block is ok. */
+#define ASSERT_ANCHOR(pHeapInt) \
+ do { AssertPtr(pHeapInt);\
+ Assert((pHeapInt)->u32Magic == RTHEAPOFFSET_MAGIC); \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef RTHEAPOFFSET_STRICT
+static void rtHeapOffsetAssertAll(PRTHEAPOFFSETINTERNAL pHeapInt);
+#endif
+static PRTHEAPOFFSETBLOCK rtHeapOffsetAllocBlock(PRTHEAPOFFSETINTERNAL pHeapInt, size_t cb, size_t uAlignment);
+static void rtHeapOffsetFreeBlock(PRTHEAPOFFSETINTERNAL pHeapInt, PRTHEAPOFFSETBLOCK pBlock);
+
+#ifdef RTHEAPOFFSET_STRICT
+
+/** Checked version of RTHEAPOFF_TO_PTR and RTHEAPOFF_TO_PTR_N. */
+static void *rtHeapOffCheckedOffToPtr(PRTHEAPOFFSETINTERNAL pHeapInt, uint32_t off, bool fNull)
+{
+ Assert(off || fNull);
+ if (!off)
+ return NULL;
+ AssertMsg(off < pHeapInt->cbHeap, ("%#x %#x\n", off, pHeapInt->cbHeap));
+ AssertMsg(off >= sizeof(*pHeapInt), ("%#x %#x\n", off, sizeof(*pHeapInt)));
+ return (uint8_t *)pHeapInt + off;
+}
+
+/** Checked version of RTHEAPOFF_TO_OFF. */
+static uint32_t rtHeapOffCheckedPtrToOff(PRTHEAPOFFSETINTERNAL pHeapInt, void *pv)
+{
+ if (!pv)
+ return 0;
+ uintptr_t off = (uintptr_t)pv - (uintptr_t)pHeapInt;
+ AssertMsg(off < pHeapInt->cbHeap, ("%#x %#x\n", off, pHeapInt->cbHeap));
+ AssertMsg(off >= sizeof(*pHeapInt), ("%#x %#x\n", off, sizeof(*pHeapInt)));
+ return (uint32_t)off;
+}
+
+#endif /* RTHEAPOFFSET_STRICT */
+
+
+
+RTDECL(int) RTHeapOffsetInit(PRTHEAPOFFSET phHeap, void *pvMemory, size_t cbMemory)
+{
+ PRTHEAPOFFSETINTERNAL pHeapInt;
+ PRTHEAPOFFSETFREE pFree;
+ unsigned i;
+
+ /*
+ * Validate input. The imposed minimum heap size is just a convenient value.
+ */
+ AssertReturn(cbMemory >= PAGE_SIZE, VERR_INVALID_PARAMETER);
+ AssertReturn(cbMemory < UINT32_MAX, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvMemory, VERR_INVALID_POINTER);
+ AssertReturn((uintptr_t)pvMemory + (cbMemory - 1) > (uintptr_t)cbMemory, VERR_INVALID_PARAMETER);
+
+ /*
+ * Place the heap anchor block at the start of the heap memory,
+ * enforce 32 byte alignment of it. Also align the heap size correctly.
+ */
+ pHeapInt = (PRTHEAPOFFSETINTERNAL)pvMemory;
+ if ((uintptr_t)pvMemory & 31)
+ {
+ const uintptr_t off = 32 - ((uintptr_t)pvMemory & 31);
+ cbMemory -= off;
+ pHeapInt = (PRTHEAPOFFSETINTERNAL)((uintptr_t)pvMemory + off);
+ }
+ cbMemory &= ~(RTHEAPOFFSET_ALIGNMENT - 1);
+
+
+ /* Init the heap anchor block. */
+ pHeapInt->u32Magic = RTHEAPOFFSET_MAGIC;
+ pHeapInt->cbHeap = (uint32_t)cbMemory;
+ pHeapInt->cbFree = (uint32_t)cbMemory
+ - sizeof(RTHEAPOFFSETBLOCK)
+ - sizeof(RTHEAPOFFSETINTERNAL);
+ pHeapInt->offFreeTail = pHeapInt->offFreeHead = sizeof(*pHeapInt);
+ for (i = 0; i < RT_ELEMENTS(pHeapInt->au32Alignment); i++)
+ pHeapInt->au32Alignment[i] = UINT32_MAX;
+
+ /* Init the single free block. */
+ pFree = RTHEAPOFF_TO_PTR(pHeapInt, pHeapInt->offFreeHead, PRTHEAPOFFSETFREE);
+ pFree->Core.offNext = 0;
+ pFree->Core.offPrev = 0;
+ pFree->Core.offSelf = pHeapInt->offFreeHead;
+ pFree->Core.fFlags = RTHEAPOFFSETBLOCK_FLAGS_MAGIC | RTHEAPOFFSETBLOCK_FLAGS_FREE;
+ pFree->offNext = 0;
+ pFree->offPrev = 0;
+ pFree->cb = pHeapInt->cbFree;
+
+ *phHeap = pHeapInt;
+
+#ifdef RTHEAPOFFSET_STRICT
+ rtHeapOffsetAssertAll(pHeapInt);
+#endif
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTHeapOffsetInit);
+
+
+RTDECL(void *) RTHeapOffsetAlloc(RTHEAPOFFSET hHeap, size_t cb, size_t cbAlignment)
+{
+ PRTHEAPOFFSETINTERNAL pHeapInt = hHeap;
+ PRTHEAPOFFSETBLOCK pBlock;
+
+ /*
+ * Validate and adjust the input.
+ */
+ AssertPtrReturn(pHeapInt, NULL);
+ if (cb < RTHEAPOFFSET_MIN_BLOCK)
+ cb = RTHEAPOFFSET_MIN_BLOCK;
+ else
+ cb = RT_ALIGN_Z(cb, RTHEAPOFFSET_ALIGNMENT);
+ if (!cbAlignment)
+ cbAlignment = RTHEAPOFFSET_ALIGNMENT;
+ else
+ {
+ Assert(!(cbAlignment & (cbAlignment - 1)));
+ Assert((cbAlignment & ~(cbAlignment - 1)) == cbAlignment);
+ if (cbAlignment < RTHEAPOFFSET_ALIGNMENT)
+ cbAlignment = RTHEAPOFFSET_ALIGNMENT;
+ }
+
+ /*
+ * Do the allocation.
+ */
+ pBlock = rtHeapOffsetAllocBlock(pHeapInt, cb, cbAlignment);
+ if (RT_LIKELY(pBlock))
+ {
+ void *pv = pBlock + 1;
+ return pv;
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTHeapOffsetAlloc);
+
+
+RTDECL(void *) RTHeapOffsetAllocZ(RTHEAPOFFSET hHeap, size_t cb, size_t cbAlignment)
+{
+ PRTHEAPOFFSETINTERNAL pHeapInt = hHeap;
+ PRTHEAPOFFSETBLOCK pBlock;
+
+ /*
+ * Validate and adjust the input.
+ */
+ AssertPtrReturn(pHeapInt, NULL);
+ if (cb < RTHEAPOFFSET_MIN_BLOCK)
+ cb = RTHEAPOFFSET_MIN_BLOCK;
+ else
+ cb = RT_ALIGN_Z(cb, RTHEAPOFFSET_ALIGNMENT);
+ if (!cbAlignment)
+ cbAlignment = RTHEAPOFFSET_ALIGNMENT;
+ else
+ {
+ Assert(!(cbAlignment & (cbAlignment - 1)));
+ Assert((cbAlignment & ~(cbAlignment - 1)) == cbAlignment);
+ if (cbAlignment < RTHEAPOFFSET_ALIGNMENT)
+ cbAlignment = RTHEAPOFFSET_ALIGNMENT;
+ }
+
+ /*
+ * Do the allocation.
+ */
+ pBlock = rtHeapOffsetAllocBlock(pHeapInt, cb, cbAlignment);
+ if (RT_LIKELY(pBlock))
+ {
+ void *pv = pBlock + 1;
+ memset(pv, 0, cb);
+ return pv;
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTHeapOffsetAllocZ);
+
+
+/**
+ * Allocates a block of memory from the specified heap.
+ *
+ * No parameter validation or adjustment is performed.
+ *
+ * @returns Pointer to the allocated block.
+ * @returns NULL on failure.
+ *
+ * @param pHeapInt The heap.
+ * @param cb Size of the memory block to allocate.
+ * @param uAlignment The alignment specifications for the allocated block.
+ */
+static PRTHEAPOFFSETBLOCK rtHeapOffsetAllocBlock(PRTHEAPOFFSETINTERNAL pHeapInt, size_t cb, size_t uAlignment)
+{
+ PRTHEAPOFFSETBLOCK pRet = NULL;
+ PRTHEAPOFFSETFREE pFree;
+
+ AssertReturn((pHeapInt)->u32Magic == RTHEAPOFFSET_MAGIC, NULL);
+#ifdef RTHEAPOFFSET_STRICT
+ rtHeapOffsetAssertAll(pHeapInt);
+#endif
+
+ /*
+ * Search for a fitting block from the lower end of the heap.
+ */
+ for (pFree = RTHEAPOFF_TO_PTR_N(pHeapInt, pHeapInt->offFreeHead, PRTHEAPOFFSETFREE);
+ pFree;
+ pFree = RTHEAPOFF_TO_PTR_N(pHeapInt, pFree->offNext, PRTHEAPOFFSETFREE))
+ {
+ uintptr_t offAlign;
+ ASSERT_BLOCK_FREE(pHeapInt, pFree);
+
+ /*
+ * Match for size and alignment.
+ */
+ if (pFree->cb < cb)
+ continue;
+ offAlign = (uintptr_t)(&pFree->Core + 1) & (uAlignment - 1);
+ if (offAlign)
+ {
+ PRTHEAPOFFSETFREE pPrev;
+
+ offAlign = (uintptr_t)(&pFree[1].Core + 1) & (uAlignment - 1);
+ offAlign = uAlignment - offAlign;
+ if (pFree->cb < cb + offAlign + sizeof(RTHEAPOFFSETFREE))
+ continue;
+
+ /*
+ * Split up the free block into two, so that the 2nd is aligned as
+ * per specification.
+ */
+ pPrev = pFree;
+ pFree = (PRTHEAPOFFSETFREE)((uintptr_t)(pFree + 1) + offAlign);
+ pFree->Core.offPrev = pPrev->Core.offSelf;
+ pFree->Core.offNext = pPrev->Core.offNext;
+ pFree->Core.offSelf = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ pFree->Core.fFlags = RTHEAPOFFSETBLOCK_FLAGS_MAGIC | RTHEAPOFFSETBLOCK_FLAGS_FREE;
+ pFree->offPrev = pPrev->Core.offSelf;
+ pFree->offNext = pPrev->offNext;
+ pFree->cb = (pFree->Core.offNext ? pFree->Core.offNext : pHeapInt->cbHeap)
+ - pFree->Core.offSelf - sizeof(RTHEAPOFFSETBLOCK);
+
+ pPrev->Core.offNext = pFree->Core.offSelf;
+ pPrev->offNext = pFree->Core.offSelf;
+ pPrev->cb = pFree->Core.offSelf - pPrev->Core.offSelf - sizeof(RTHEAPOFFSETBLOCK);
+
+ if (pFree->Core.offNext)
+ RTHEAPOFF_TO_PTR(pHeapInt, pFree->Core.offNext, PRTHEAPOFFSETBLOCK)->offPrev = pFree->Core.offSelf;
+ if (pFree->offNext)
+ RTHEAPOFF_TO_PTR(pHeapInt, pFree->Core.offNext, PRTHEAPOFFSETFREE)->offPrev = pFree->Core.offSelf;
+ else
+ pHeapInt->offFreeTail = pFree->Core.offSelf;
+
+ pHeapInt->cbFree -= sizeof(RTHEAPOFFSETBLOCK);
+ ASSERT_BLOCK_FREE(pHeapInt, pPrev);
+ ASSERT_BLOCK_FREE(pHeapInt, pFree);
+ }
+
+ /*
+ * Split off a new FREE block?
+ */
+ if (pFree->cb >= cb + RT_ALIGN_Z(sizeof(RTHEAPOFFSETFREE), RTHEAPOFFSET_ALIGNMENT))
+ {
+ /*
+ * Create a new FREE block at then end of this one.
+ */
+ PRTHEAPOFFSETFREE pNew = (PRTHEAPOFFSETFREE)((uintptr_t)&pFree->Core + cb + sizeof(RTHEAPOFFSETBLOCK));
+
+ pNew->Core.offSelf = RTHEAPOFF_TO_OFF(pHeapInt, pNew);
+ pNew->Core.offNext = pFree->Core.offNext;
+ if (pFree->Core.offNext)
+ RTHEAPOFF_TO_PTR(pHeapInt, pFree->Core.offNext, PRTHEAPOFFSETBLOCK)->offPrev = pNew->Core.offSelf;
+ pNew->Core.offPrev = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ pNew->Core.fFlags = RTHEAPOFFSETBLOCK_FLAGS_MAGIC | RTHEAPOFFSETBLOCK_FLAGS_FREE;
+
+ pNew->offNext = pFree->offNext;
+ if (pNew->offNext)
+ RTHEAPOFF_TO_PTR(pHeapInt, pNew->offNext, PRTHEAPOFFSETFREE)->offPrev = pNew->Core.offSelf;
+ else
+ pHeapInt->offFreeTail = pNew->Core.offSelf;
+ pNew->offPrev = pFree->offPrev;
+ if (pNew->offPrev)
+ RTHEAPOFF_TO_PTR(pHeapInt, pNew->offPrev, PRTHEAPOFFSETFREE)->offNext = pNew->Core.offSelf;
+ else
+ pHeapInt->offFreeHead = pNew->Core.offSelf;
+ pNew->cb = (pNew->Core.offNext ? pNew->Core.offNext : pHeapInt->cbHeap) \
+ - pNew->Core.offSelf - sizeof(RTHEAPOFFSETBLOCK);
+ ASSERT_BLOCK_FREE(pHeapInt, pNew);
+
+ /*
+ * Adjust and convert the old FREE node into a USED node.
+ */
+ pFree->Core.fFlags &= ~RTHEAPOFFSETBLOCK_FLAGS_FREE;
+ pFree->Core.offNext = pNew->Core.offSelf;
+ pHeapInt->cbFree -= pFree->cb;
+ pHeapInt->cbFree += pNew->cb;
+ pRet = &pFree->Core;
+ ASSERT_BLOCK_USED(pHeapInt, pRet);
+ }
+ else
+ {
+ /*
+ * Link it out of the free list.
+ */
+ if (pFree->offNext)
+ RTHEAPOFF_TO_PTR(pHeapInt, pFree->offNext, PRTHEAPOFFSETFREE)->offPrev = pFree->offPrev;
+ else
+ pHeapInt->offFreeTail = pFree->offPrev;
+ if (pFree->offPrev)
+ RTHEAPOFF_TO_PTR(pHeapInt, pFree->offPrev, PRTHEAPOFFSETFREE)->offNext = pFree->offNext;
+ else
+ pHeapInt->offFreeHead = pFree->offNext;
+
+ /*
+ * Convert it to a used block.
+ */
+ pHeapInt->cbFree -= pFree->cb;
+ pFree->Core.fFlags &= ~RTHEAPOFFSETBLOCK_FLAGS_FREE;
+ pRet = &pFree->Core;
+ ASSERT_BLOCK_USED(pHeapInt, pRet);
+ }
+ break;
+ }
+
+#ifdef RTHEAPOFFSET_STRICT
+ rtHeapOffsetAssertAll(pHeapInt);
+#endif
+ return pRet;
+}
+
+
+RTDECL(void) RTHeapOffsetFree(RTHEAPOFFSET hHeap, void *pv)
+{
+ PRTHEAPOFFSETINTERNAL pHeapInt;
+ PRTHEAPOFFSETBLOCK pBlock;
+
+ /*
+ * Validate input.
+ */
+ if (!pv)
+ return;
+ AssertPtr(pv);
+ Assert(RT_ALIGN_P(pv, RTHEAPOFFSET_ALIGNMENT) == pv);
+
+ /*
+ * Get the block and heap. If in strict mode, validate these.
+ */
+ pBlock = (PRTHEAPOFFSETBLOCK)pv - 1;
+ pHeapInt = RTHEAPOFF_GET_ANCHOR(pBlock);
+ ASSERT_BLOCK_USED(pHeapInt, pBlock);
+ ASSERT_ANCHOR(pHeapInt);
+ Assert(pHeapInt == (PRTHEAPOFFSETINTERNAL)hHeap || !hHeap); RT_NOREF_PV(hHeap);
+
+#ifdef RTHEAPOFFSET_FREE_POISON
+ /*
+ * Poison the block.
+ */
+ const size_t cbBlock = (pBlock->pNext ? (uintptr_t)pBlock->pNext : (uintptr_t)pHeapInt->pvEnd)
+ - (uintptr_t)pBlock - sizeof(RTHEAPOFFSETBLOCK);
+ memset(pBlock + 1, RTHEAPOFFSET_FREE_POISON, cbBlock);
+#endif
+
+ /*
+ * Call worker which does the actual job.
+ */
+ rtHeapOffsetFreeBlock(pHeapInt, pBlock);
+}
+RT_EXPORT_SYMBOL(RTHeapOffsetFree);
+
+
+/**
+ * Free a memory block.
+ *
+ * @param pHeapInt The heap.
+ * @param pBlock The memory block to free.
+ */
+static void rtHeapOffsetFreeBlock(PRTHEAPOFFSETINTERNAL pHeapInt, PRTHEAPOFFSETBLOCK pBlock)
+{
+ PRTHEAPOFFSETFREE pFree = (PRTHEAPOFFSETFREE)pBlock;
+ PRTHEAPOFFSETFREE pLeft;
+ PRTHEAPOFFSETFREE pRight;
+
+#ifdef RTHEAPOFFSET_STRICT
+ rtHeapOffsetAssertAll(pHeapInt);
+#endif
+
+ /*
+ * Look for the closest free list blocks by walking the blocks right
+ * of us (both lists are sorted by address).
+ */
+ pLeft = NULL;
+ pRight = NULL;
+ if (pHeapInt->offFreeTail)
+ {
+ pRight = RTHEAPOFF_TO_PTR_N(pHeapInt, pFree->Core.offNext, PRTHEAPOFFSETFREE);
+ while (pRight && !RTHEAPOFFSETBLOCK_IS_FREE(&pRight->Core))
+ {
+ ASSERT_BLOCK(pHeapInt, &pRight->Core);
+ pRight = RTHEAPOFF_TO_PTR_N(pHeapInt, pRight->Core.offNext, PRTHEAPOFFSETFREE);
+ }
+ if (!pRight)
+ pLeft = RTHEAPOFF_TO_PTR_N(pHeapInt, pHeapInt->offFreeTail, PRTHEAPOFFSETFREE);
+ else
+ {
+ ASSERT_BLOCK_FREE(pHeapInt, pRight);
+ pLeft = RTHEAPOFF_TO_PTR_N(pHeapInt, pRight->offPrev, PRTHEAPOFFSETFREE);
+ }
+ if (pLeft)
+ ASSERT_BLOCK_FREE(pHeapInt, pLeft);
+ }
+ AssertMsgReturnVoid(pLeft != pFree, ("Freed twice! pv=%p (pBlock=%p)\n", pBlock + 1, pBlock));
+ ASSERT_L(RTHEAPOFF_TO_OFF(pHeapInt, pLeft), RTHEAPOFF_TO_OFF(pHeapInt, pFree));
+ Assert(!pRight || (uintptr_t)pRight > (uintptr_t)pFree);
+ Assert(!pLeft || RTHEAPOFF_TO_PTR_N(pHeapInt, pLeft->offNext, PRTHEAPOFFSETFREE) == pRight);
+
+ /*
+ * Insert at the head of the free block list?
+ */
+ if (!pLeft)
+ {
+ Assert(pRight == RTHEAPOFF_TO_PTR_N(pHeapInt, pHeapInt->offFreeHead, PRTHEAPOFFSETFREE));
+ pFree->Core.fFlags |= RTHEAPOFFSETBLOCK_FLAGS_FREE;
+ pFree->offPrev = 0;
+ pFree->offNext = RTHEAPOFF_TO_OFF(pHeapInt, pRight);
+ if (pRight)
+ pRight->offPrev = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ else
+ pHeapInt->offFreeTail = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ pHeapInt->offFreeHead = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ }
+ else
+ {
+ /*
+ * Can we merge with left hand free block?
+ */
+ if (pLeft->Core.offNext == RTHEAPOFF_TO_OFF(pHeapInt, pFree))
+ {
+ pLeft->Core.offNext = pFree->Core.offNext;
+ if (pFree->Core.offNext)
+ RTHEAPOFF_TO_PTR(pHeapInt, pFree->Core.offNext, PRTHEAPOFFSETBLOCK)->offPrev = RTHEAPOFF_TO_OFF(pHeapInt, pLeft);
+ pHeapInt->cbFree -= pLeft->cb;
+ pFree = pLeft;
+ }
+ /*
+ * No, just link it into the free list then.
+ */
+ else
+ {
+ pFree->Core.fFlags |= RTHEAPOFFSETBLOCK_FLAGS_FREE;
+ pFree->offNext = RTHEAPOFF_TO_OFF(pHeapInt, pRight);
+ pFree->offPrev = RTHEAPOFF_TO_OFF(pHeapInt, pLeft);
+ pLeft->offNext = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ if (pRight)
+ pRight->offPrev = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ else
+ pHeapInt->offFreeTail = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ }
+ }
+
+ /*
+ * Can we merge with right hand free block?
+ */
+ if ( pRight
+ && pRight->Core.offPrev == RTHEAPOFF_TO_OFF(pHeapInt, pFree))
+ {
+ /* core */
+ pFree->Core.offNext = pRight->Core.offNext;
+ if (pRight->Core.offNext)
+ RTHEAPOFF_TO_PTR(pHeapInt, pRight->Core.offNext, PRTHEAPOFFSETBLOCK)->offPrev = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+
+ /* free */
+ pFree->offNext = pRight->offNext;
+ if (pRight->offNext)
+ RTHEAPOFF_TO_PTR(pHeapInt, pRight->offNext, PRTHEAPOFFSETFREE)->offPrev = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ else
+ pHeapInt->offFreeTail = RTHEAPOFF_TO_OFF(pHeapInt, pFree);
+ pHeapInt->cbFree -= pRight->cb;
+ }
+
+ /*
+ * Calculate the size and update free stats.
+ */
+ pFree->cb = (pFree->Core.offNext ? pFree->Core.offNext : pHeapInt->cbHeap)
+ - RTHEAPOFF_TO_OFF(pHeapInt, pFree) - sizeof(RTHEAPOFFSETBLOCK);
+ pHeapInt->cbFree += pFree->cb;
+ ASSERT_BLOCK_FREE(pHeapInt, pFree);
+
+#ifdef RTHEAPOFFSET_STRICT
+ rtHeapOffsetAssertAll(pHeapInt);
+#endif
+}
+
+
+#ifdef RTHEAPOFFSET_STRICT
+/**
+ * Internal consistency check (relying on assertions).
+ * @param pHeapInt
+ */
+static void rtHeapOffsetAssertAll(PRTHEAPOFFSETINTERNAL pHeapInt)
+{
+ PRTHEAPOFFSETFREE pPrev = NULL;
+ PRTHEAPOFFSETFREE pPrevFree = NULL;
+ PRTHEAPOFFSETFREE pBlock;
+ for (pBlock = (PRTHEAPOFFSETFREE)(pHeapInt + 1);
+ pBlock;
+ pBlock = RTHEAPOFF_TO_PTR_N(pHeapInt, pBlock->Core.offNext, PRTHEAPOFFSETFREE))
+ {
+ if (RTHEAPOFFSETBLOCK_IS_FREE(&pBlock->Core))
+ {
+ ASSERT_BLOCK_FREE(pHeapInt, pBlock);
+ Assert(pBlock->offPrev == RTHEAPOFF_TO_OFF(pHeapInt, pPrevFree));
+ Assert(pPrevFree || pHeapInt->offFreeHead == RTHEAPOFF_TO_OFF(pHeapInt, pBlock));
+ pPrevFree = pBlock;
+ }
+ else
+ ASSERT_BLOCK_USED(pHeapInt, &pBlock->Core);
+ Assert(!pPrev || RTHEAPOFF_TO_OFF(pHeapInt, pPrev) == pBlock->Core.offPrev);
+ pPrev = pBlock;
+ }
+ Assert(pHeapInt->offFreeTail == RTHEAPOFF_TO_OFF(pHeapInt, pPrevFree));
+}
+#endif
+
+
+RTDECL(size_t) RTHeapOffsetSize(RTHEAPOFFSET hHeap, void *pv)
+{
+ PRTHEAPOFFSETINTERNAL pHeapInt;
+ PRTHEAPOFFSETBLOCK pBlock;
+ size_t cbBlock;
+
+ /*
+ * Validate input.
+ */
+ if (!pv)
+ return 0;
+ AssertPtrReturn(pv, 0);
+ AssertReturn(RT_ALIGN_P(pv, RTHEAPOFFSET_ALIGNMENT) == pv, 0);
+
+ /*
+ * Get the block and heap. If in strict mode, validate these.
+ */
+ pBlock = (PRTHEAPOFFSETBLOCK)pv - 1;
+ pHeapInt = RTHEAPOFF_GET_ANCHOR(pBlock);
+ ASSERT_BLOCK_USED(pHeapInt, pBlock);
+ ASSERT_ANCHOR(pHeapInt);
+ Assert(pHeapInt == (PRTHEAPOFFSETINTERNAL)hHeap || !hHeap); RT_NOREF_PV(hHeap);
+
+ /*
+ * Calculate the block size.
+ */
+ cbBlock = (pBlock->offNext ? pBlock->offNext : pHeapInt->cbHeap)
+ - RTHEAPOFF_TO_OFF(pHeapInt, pBlock) - sizeof(RTHEAPOFFSETBLOCK);
+ return cbBlock;
+}
+RT_EXPORT_SYMBOL(RTHeapOffsetSize);
+
+
+RTDECL(size_t) RTHeapOffsetGetHeapSize(RTHEAPOFFSET hHeap)
+{
+ PRTHEAPOFFSETINTERNAL pHeapInt;
+
+ if (hHeap == NIL_RTHEAPOFFSET)
+ return 0;
+
+ pHeapInt = hHeap;
+ AssertPtrReturn(pHeapInt, 0);
+ ASSERT_ANCHOR(pHeapInt);
+ return pHeapInt->cbHeap;
+}
+RT_EXPORT_SYMBOL(RTHeapOffsetGetHeapSize);
+
+
+RTDECL(size_t) RTHeapOffsetGetFreeSize(RTHEAPOFFSET hHeap)
+{
+ PRTHEAPOFFSETINTERNAL pHeapInt;
+
+ if (hHeap == NIL_RTHEAPOFFSET)
+ return 0;
+
+ pHeapInt = hHeap;
+ AssertPtrReturn(pHeapInt, 0);
+ ASSERT_ANCHOR(pHeapInt);
+ return pHeapInt->cbFree;
+}
+RT_EXPORT_SYMBOL(RTHeapOffsetGetFreeSize);
+
+
+RTDECL(void) RTHeapOffsetDump(RTHEAPOFFSET hHeap, PFNRTHEAPOFFSETPRINTF pfnPrintf)
+{
+ PRTHEAPOFFSETINTERNAL pHeapInt = (PRTHEAPOFFSETINTERNAL)hHeap;
+ PRTHEAPOFFSETFREE pBlock;
+
+ pfnPrintf("**** Dumping Heap %p - cbHeap=%x cbFree=%x ****\n",
+ hHeap, pHeapInt->cbHeap, pHeapInt->cbFree);
+
+ for (pBlock = (PRTHEAPOFFSETFREE)(pHeapInt + 1);
+ pBlock;
+ pBlock = RTHEAPOFF_TO_PTR_N(pHeapInt, pBlock->Core.offNext, PRTHEAPOFFSETFREE))
+ {
+ size_t cb = (pBlock->offNext ? pBlock->Core.offNext : pHeapInt->cbHeap)
+ - RTHEAPOFF_TO_OFF(pHeapInt, pBlock) - sizeof(RTHEAPOFFSETBLOCK);
+ if (RTHEAPOFFSETBLOCK_IS_FREE(&pBlock->Core))
+ pfnPrintf("%p %06x FREE offNext=%06x offPrev=%06x fFlags=%#x cb=%#06x : cb=%#06x offNext=%06x offPrev=%06x\n",
+ pBlock, pBlock->Core.offSelf, pBlock->Core.offNext, pBlock->Core.offPrev, pBlock->Core.fFlags, cb,
+ pBlock->cb, pBlock->offNext, pBlock->offPrev);
+ else
+ pfnPrintf("%p %06x USED offNext=%06x offPrev=%06x fFlags=%#x cb=%#06x\n",
+ pBlock, pBlock->Core.offSelf, pBlock->Core.offNext, pBlock->Core.offPrev, pBlock->Core.fFlags, cb);
+ }
+ pfnPrintf("**** Done dumping Heap %p ****\n", hHeap);
+}
+RT_EXPORT_SYMBOL(RTHeapOffsetDump);
+
diff --git a/src/VBox/Runtime/common/alloc/heapsimple.cpp b/src/VBox/Runtime/common/alloc/heapsimple.cpp
new file mode 100644
index 00000000..4dce064a
--- /dev/null
+++ b/src/VBox/Runtime/common/alloc/heapsimple.cpp
@@ -0,0 +1,930 @@
+/* $Id: heapsimple.cpp $ */
+/** @file
+ * IPRT - A Simple Heap.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DEFAULT
+#include <iprt/heap.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/string.h>
+#include <iprt/param.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the heap anchor block. */
+typedef struct RTHEAPSIMPLEINTERNAL *PRTHEAPSIMPLEINTERNAL;
+/** Pointer to a heap block. */
+typedef struct RTHEAPSIMPLEBLOCK *PRTHEAPSIMPLEBLOCK;
+/** Pointer to a free heap block. */
+typedef struct RTHEAPSIMPLEFREE *PRTHEAPSIMPLEFREE;
+
+/**
+ * Structure describing a simple heap block.
+ * If this block is allocated, it is followed by the user data.
+ * If this block is free, see RTHEAPSIMPLEFREE.
+ */
+typedef struct RTHEAPSIMPLEBLOCK
+{
+ /** The next block in the global block list. */
+ PRTHEAPSIMPLEBLOCK pNext;
+ /** The previous block in the global block list. */
+ PRTHEAPSIMPLEBLOCK pPrev;
+ /** Pointer to the heap anchor block. */
+ PRTHEAPSIMPLEINTERNAL pHeap;
+ /** Flags + magic. */
+ uintptr_t fFlags;
+} RTHEAPSIMPLEBLOCK;
+AssertCompileSizeAlignment(RTHEAPSIMPLEBLOCK, 16);
+
+/** The block is free if this flag is set. When cleared it's allocated. */
+#define RTHEAPSIMPLEBLOCK_FLAGS_FREE ((uintptr_t)RT_BIT(0))
+/** The magic value. */
+#define RTHEAPSIMPLEBLOCK_FLAGS_MAGIC ((uintptr_t)0xabcdef00)
+/** The mask that needs to be applied to RTHEAPSIMPLEBLOCK::fFlags to obtain the magic value. */
+#define RTHEAPSIMPLEBLOCK_FLAGS_MAGIC_MASK (~(uintptr_t)RT_BIT(0))
+
+/**
+ * Checks if the specified block is valid or not.
+ * @returns boolean answer.
+ * @param pBlock Pointer to a RTHEAPSIMPLEBLOCK structure.
+ */
+#define RTHEAPSIMPLEBLOCK_IS_VALID(pBlock) \
+ ( ((pBlock)->fFlags & RTHEAPSIMPLEBLOCK_FLAGS_MAGIC_MASK) == RTHEAPSIMPLEBLOCK_FLAGS_MAGIC )
+
+/**
+ * Checks if the specified block is valid and in use.
+ * @returns boolean answer.
+ * @param pBlock Pointer to a RTHEAPSIMPLEBLOCK structure.
+ */
+#define RTHEAPSIMPLEBLOCK_IS_VALID_USED(pBlock) \
+ ( ((pBlock)->fFlags & (RTHEAPSIMPLEBLOCK_FLAGS_MAGIC_MASK | RTHEAPSIMPLEBLOCK_FLAGS_FREE)) \
+ == RTHEAPSIMPLEBLOCK_FLAGS_MAGIC )
+
+/**
+ * Checks if the specified block is valid and free.
+ * @returns boolean answer.
+ * @param pBlock Pointer to a RTHEAPSIMPLEBLOCK structure.
+ */
+#define RTHEAPSIMPLEBLOCK_IS_VALID_FREE(pBlock) \
+ ( ((pBlock)->fFlags & (RTHEAPSIMPLEBLOCK_FLAGS_MAGIC_MASK | RTHEAPSIMPLEBLOCK_FLAGS_FREE)) \
+ == (RTHEAPSIMPLEBLOCK_FLAGS_MAGIC | RTHEAPSIMPLEBLOCK_FLAGS_FREE) )
+
+/**
+ * Checks if the specified block is free or not.
+ * @returns boolean answer.
+ * @param pBlock Pointer to a valid RTHEAPSIMPLEBLOCK structure.
+ */
+#define RTHEAPSIMPLEBLOCK_IS_FREE(pBlock) (!!((pBlock)->fFlags & RTHEAPSIMPLEBLOCK_FLAGS_FREE))
+
+/**
+ * A free heap block.
+ * This is an extended version of RTHEAPSIMPLEBLOCK that takes the unused
+ * user data to store free list pointers and a cached size value.
+ */
+typedef struct RTHEAPSIMPLEFREE
+{
+ /** Core stuff. */
+ RTHEAPSIMPLEBLOCK Core;
+ /** Pointer to the next free block. */
+ PRTHEAPSIMPLEFREE pNext;
+ /** Pointer to the previous free block. */
+ PRTHEAPSIMPLEFREE pPrev;
+ /** The size of the block (excluding the RTHEAPSIMPLEBLOCK part). */
+ size_t cb;
+ /** An alignment filler to make it a multiple of (sizeof(void *) * 2). */
+ size_t Alignment;
+} RTHEAPSIMPLEFREE;
+
+
+/**
+ * The heap anchor block.
+ * This structure is placed at the head of the memory block specified to RTHeapSimpleInit(),
+ * which means that the first RTHEAPSIMPLEBLOCK appears immediately after this structure.
+ */
+typedef struct RTHEAPSIMPLEINTERNAL
+{
+ /** The typical magic (RTHEAPSIMPLE_MAGIC). */
+ size_t uMagic;
+ /** The heap size. (This structure is included!) */
+ size_t cbHeap;
+ /** Pointer to the end of the heap. */
+ void *pvEnd;
+ /** The amount of free memory in the heap. */
+ size_t cbFree;
+ /** Free head pointer. */
+ PRTHEAPSIMPLEFREE pFreeHead;
+ /** Free tail pointer. */
+ PRTHEAPSIMPLEFREE pFreeTail;
+ /** Make the size of this structure is a multiple of 32. */
+ size_t auAlignment[2];
+} RTHEAPSIMPLEINTERNAL;
+AssertCompileSizeAlignment(RTHEAPSIMPLEINTERNAL, 32);
+
+
+/** The minimum allocation size. */
+#define RTHEAPSIMPLE_MIN_BLOCK (sizeof(RTHEAPSIMPLEBLOCK))
+AssertCompile(RTHEAPSIMPLE_MIN_BLOCK >= sizeof(RTHEAPSIMPLEBLOCK));
+AssertCompile(RTHEAPSIMPLE_MIN_BLOCK >= sizeof(RTHEAPSIMPLEFREE) - sizeof(RTHEAPSIMPLEBLOCK));
+
+/** The minimum and default alignment. */
+#define RTHEAPSIMPLE_ALIGNMENT (sizeof(RTHEAPSIMPLEBLOCK))
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef RT_STRICT
+# define RTHEAPSIMPLE_STRICT 1
+#endif
+
+#define ASSERT_L(a, b) AssertMsg((uintptr_t)(a) < (uintptr_t)(b), ("a=%p b=%p\n", (uintptr_t)(a), (uintptr_t)(b)))
+#define ASSERT_LE(a, b) AssertMsg((uintptr_t)(a) <= (uintptr_t)(b), ("a=%p b=%p\n", (uintptr_t)(a), (uintptr_t)(b)))
+#define ASSERT_G(a, b) AssertMsg((uintptr_t)(a) > (uintptr_t)(b), ("a=%p b=%p\n", (uintptr_t)(a), (uintptr_t)(b)))
+#define ASSERT_GE(a, b) AssertMsg((uintptr_t)(a) >= (uintptr_t)(b), ("a=%p b=%p\n", (uintptr_t)(a), (uintptr_t)(b)))
+#define ASSERT_ALIGN(a) AssertMsg(!((uintptr_t)(a) & (RTHEAPSIMPLE_ALIGNMENT - 1)), ("a=%p\n", (uintptr_t)(a)))
+
+#define ASSERT_PREV(pHeapInt, pBlock) \
+ do { ASSERT_ALIGN((pBlock)->pPrev); \
+ if ((pBlock)->pPrev) \
+ { \
+ ASSERT_L((pBlock)->pPrev, (pBlock)); \
+ ASSERT_GE((pBlock)->pPrev, (pHeapInt) + 1); \
+ } \
+ else \
+ Assert((pBlock) == (PRTHEAPSIMPLEBLOCK)((pHeapInt) + 1)); \
+ } while (0)
+
+#define ASSERT_NEXT(pHeap, pBlock) \
+ do { ASSERT_ALIGN((pBlock)->pNext); \
+ if ((pBlock)->pNext) \
+ { \
+ ASSERT_L((pBlock)->pNext, (pHeapInt)->pvEnd); \
+ ASSERT_G((pBlock)->pNext, (pBlock)); \
+ } \
+ } while (0)
+
+#define ASSERT_BLOCK(pHeapInt, pBlock) \
+ do { AssertMsg(RTHEAPSIMPLEBLOCK_IS_VALID(pBlock), ("%#x\n", (pBlock)->fFlags)); \
+ AssertMsg((pBlock)->pHeap == (pHeapInt), ("%p != %p\n", (pBlock)->pHeap, (pHeapInt))); \
+ ASSERT_GE((pBlock), (pHeapInt) + 1); \
+ ASSERT_L((pBlock), (pHeapInt)->pvEnd); \
+ ASSERT_NEXT(pHeapInt, pBlock); \
+ ASSERT_PREV(pHeapInt, pBlock); \
+ } while (0)
+
+#define ASSERT_BLOCK_USED(pHeapInt, pBlock) \
+ do { AssertMsg(RTHEAPSIMPLEBLOCK_IS_VALID_USED((pBlock)), ("%#x\n", (pBlock)->fFlags)); \
+ AssertMsg((pBlock)->pHeap == (pHeapInt), ("%p != %p\n", (pBlock)->pHeap, (pHeapInt))); \
+ ASSERT_GE((pBlock), (pHeapInt) + 1); \
+ ASSERT_L((pBlock), (pHeapInt)->pvEnd); \
+ ASSERT_NEXT(pHeapInt, pBlock); \
+ ASSERT_PREV(pHeapInt, pBlock); \
+ } while (0)
+
+#define ASSERT_FREE_PREV(pHeapInt, pBlock) \
+ do { ASSERT_ALIGN((pBlock)->pPrev); \
+ if ((pBlock)->pPrev) \
+ { \
+ ASSERT_GE((pBlock)->pPrev, (pHeapInt)->pFreeHead); \
+ ASSERT_L((pBlock)->pPrev, (pBlock)); \
+ ASSERT_LE((pBlock)->pPrev, (pBlock)->Core.pPrev); \
+ } \
+ else \
+ Assert((pBlock) == (pHeapInt)->pFreeHead); \
+ } while (0)
+
+#define ASSERT_FREE_NEXT(pHeapInt, pBlock) \
+ do { ASSERT_ALIGN((pBlock)->pNext); \
+ if ((pBlock)->pNext) \
+ { \
+ ASSERT_LE((pBlock)->pNext, (pHeapInt)->pFreeTail); \
+ ASSERT_G((pBlock)->pNext, (pBlock)); \
+ ASSERT_GE((pBlock)->pNext, (pBlock)->Core.pNext); \
+ } \
+ else \
+ Assert((pBlock) == (pHeapInt)->pFreeTail); \
+ } while (0)
+
+#ifdef RTHEAPSIMPLE_STRICT
+# define ASSERT_FREE_CB(pHeapInt, pBlock) \
+ do { size_t cbCalc = ((pBlock)->Core.pNext ? (uintptr_t)(pBlock)->Core.pNext : (uintptr_t)(pHeapInt)->pvEnd) \
+ - (uintptr_t)(pBlock) - sizeof(RTHEAPSIMPLEBLOCK); \
+ AssertMsg((pBlock)->cb == cbCalc, ("cb=%#zx cbCalc=%#zx\n", (pBlock)->cb, cbCalc)); \
+ } while (0)
+#else
+# define ASSERT_FREE_CB(pHeapInt, pBlock) do {} while (0)
+#endif
+
+/** Asserts that a free block is valid. */
+#define ASSERT_BLOCK_FREE(pHeapInt, pBlock) \
+ do { ASSERT_BLOCK(pHeapInt, &(pBlock)->Core); \
+ Assert(RTHEAPSIMPLEBLOCK_IS_VALID_FREE(&(pBlock)->Core)); \
+ ASSERT_GE((pBlock), (pHeapInt)->pFreeHead); \
+ ASSERT_LE((pBlock), (pHeapInt)->pFreeTail); \
+ ASSERT_FREE_NEXT(pHeapInt, pBlock); \
+ ASSERT_FREE_PREV(pHeapInt, pBlock); \
+ ASSERT_FREE_CB(pHeapInt, pBlock); \
+ } while (0)
+
+/** Asserts that the heap anchor block is ok. */
+#define ASSERT_ANCHOR(pHeapInt) \
+ do { AssertPtr(pHeapInt);\
+ Assert((pHeapInt)->uMagic == RTHEAPSIMPLE_MAGIC); \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef RTHEAPSIMPLE_STRICT
+static void rtHeapSimpleAssertAll(PRTHEAPSIMPLEINTERNAL pHeapInt);
+#endif
+static PRTHEAPSIMPLEBLOCK rtHeapSimpleAllocBlock(PRTHEAPSIMPLEINTERNAL pHeapInt, size_t cb, size_t uAlignment);
+static void rtHeapSimpleFreeBlock(PRTHEAPSIMPLEINTERNAL pHeapInt, PRTHEAPSIMPLEBLOCK pBlock);
+
+
+RTDECL(int) RTHeapSimpleInit(PRTHEAPSIMPLE phHeap, void *pvMemory, size_t cbMemory)
+{
+ PRTHEAPSIMPLEINTERNAL pHeapInt;
+ PRTHEAPSIMPLEFREE pFree;
+ unsigned i;
+
+ /*
+ * Validate input. The imposed minimum heap size is just a convenient value.
+ */
+ AssertReturn(cbMemory >= PAGE_SIZE, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvMemory, VERR_INVALID_POINTER);
+ AssertReturn((uintptr_t)pvMemory + (cbMemory - 1) > (uintptr_t)cbMemory, VERR_INVALID_PARAMETER);
+
+ /*
+ * Place the heap anchor block at the start of the heap memory,
+ * enforce 32 byte alignment of it. Also align the heap size correctly.
+ */
+ pHeapInt = (PRTHEAPSIMPLEINTERNAL)pvMemory;
+ if ((uintptr_t)pvMemory & 31)
+ {
+ const uintptr_t off = 32 - ((uintptr_t)pvMemory & 31);
+ cbMemory -= off;
+ pHeapInt = (PRTHEAPSIMPLEINTERNAL)((uintptr_t)pvMemory + off);
+ }
+ cbMemory &= ~(RTHEAPSIMPLE_ALIGNMENT - 1);
+
+
+ /* Init the heap anchor block. */
+ pHeapInt->uMagic = RTHEAPSIMPLE_MAGIC;
+ pHeapInt->pvEnd = (uint8_t *)pHeapInt + cbMemory;
+ pHeapInt->cbHeap = cbMemory;
+ pHeapInt->cbFree = cbMemory
+ - sizeof(RTHEAPSIMPLEBLOCK)
+ - sizeof(RTHEAPSIMPLEINTERNAL);
+ pHeapInt->pFreeTail = pHeapInt->pFreeHead = (PRTHEAPSIMPLEFREE)(pHeapInt + 1);
+ for (i = 0; i < RT_ELEMENTS(pHeapInt->auAlignment); i++)
+ pHeapInt->auAlignment[i] = ~(size_t)0;
+
+ /* Init the single free block. */
+ pFree = pHeapInt->pFreeHead;
+ pFree->Core.pNext = NULL;
+ pFree->Core.pPrev = NULL;
+ pFree->Core.pHeap = pHeapInt;
+ pFree->Core.fFlags = RTHEAPSIMPLEBLOCK_FLAGS_MAGIC | RTHEAPSIMPLEBLOCK_FLAGS_FREE;
+ pFree->pNext = NULL;
+ pFree->pPrev = NULL;
+ pFree->cb = pHeapInt->cbFree;
+
+ *phHeap = pHeapInt;
+
+#ifdef RTHEAPSIMPLE_STRICT
+ rtHeapSimpleAssertAll(pHeapInt);
+#endif
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTHeapSimpleInit);
+
+
+RTDECL(int) RTHeapSimpleRelocate(RTHEAPSIMPLE hHeap, uintptr_t offDelta)
+{
+ PRTHEAPSIMPLEINTERNAL pHeapInt = hHeap;
+ PRTHEAPSIMPLEFREE pCur;
+
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pHeapInt, VERR_INVALID_HANDLE);
+ AssertReturn(pHeapInt->uMagic == RTHEAPSIMPLE_MAGIC, VERR_INVALID_HANDLE);
+ AssertMsgReturn((uintptr_t)pHeapInt - (uintptr_t)pHeapInt->pvEnd + pHeapInt->cbHeap == offDelta,
+ ("offDelta=%p, expected=%p\n", offDelta, (uintptr_t)pHeapInt->pvEnd - pHeapInt->cbHeap - (uintptr_t)pHeapInt),
+ VERR_INVALID_PARAMETER);
+
+ /*
+ * Relocate the heap anchor block.
+ */
+#define RELOCATE_IT(var, type, offDelta) do { if (RT_UNLIKELY((var) != NULL)) { (var) = (type)((uintptr_t)(var) + offDelta); } } while (0)
+ RELOCATE_IT(pHeapInt->pvEnd, void *, offDelta);
+ RELOCATE_IT(pHeapInt->pFreeHead, PRTHEAPSIMPLEFREE, offDelta);
+ RELOCATE_IT(pHeapInt->pFreeTail, PRTHEAPSIMPLEFREE, offDelta);
+
+ /*
+ * Walk the heap blocks.
+ */
+ for (pCur = (PRTHEAPSIMPLEFREE)(pHeapInt + 1);
+ pCur && (uintptr_t)pCur < (uintptr_t)pHeapInt->pvEnd;
+ pCur = (PRTHEAPSIMPLEFREE)pCur->Core.pNext)
+ {
+ RELOCATE_IT(pCur->Core.pNext, PRTHEAPSIMPLEBLOCK, offDelta);
+ RELOCATE_IT(pCur->Core.pPrev, PRTHEAPSIMPLEBLOCK, offDelta);
+ RELOCATE_IT(pCur->Core.pHeap, PRTHEAPSIMPLEINTERNAL, offDelta);
+ if (RTHEAPSIMPLEBLOCK_IS_FREE(&pCur->Core))
+ {
+ RELOCATE_IT(pCur->pNext, PRTHEAPSIMPLEFREE, offDelta);
+ RELOCATE_IT(pCur->pPrev, PRTHEAPSIMPLEFREE, offDelta);
+ }
+ }
+#undef RELOCATE_IT
+
+#ifdef RTHEAPSIMPLE_STRICT
+ /*
+ * Give it a once over before we return.
+ */
+ rtHeapSimpleAssertAll(pHeapInt);
+#endif
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTHeapSimpleRelocate);
+
+
+RTDECL(void *) RTHeapSimpleAlloc(RTHEAPSIMPLE hHeap, size_t cb, size_t cbAlignment)
+{
+ PRTHEAPSIMPLEINTERNAL pHeapInt = hHeap;
+ PRTHEAPSIMPLEBLOCK pBlock;
+
+ /*
+ * Validate and adjust the input.
+ */
+ AssertPtrReturn(pHeapInt, NULL);
+ if (cb < RTHEAPSIMPLE_MIN_BLOCK)
+ cb = RTHEAPSIMPLE_MIN_BLOCK;
+ else
+ cb = RT_ALIGN_Z(cb, RTHEAPSIMPLE_ALIGNMENT);
+ if (!cbAlignment)
+ cbAlignment = RTHEAPSIMPLE_ALIGNMENT;
+ else
+ {
+ Assert(!(cbAlignment & (cbAlignment - 1)));
+ Assert((cbAlignment & ~(cbAlignment - 1)) == cbAlignment);
+ if (cbAlignment < RTHEAPSIMPLE_ALIGNMENT)
+ cbAlignment = RTHEAPSIMPLE_ALIGNMENT;
+ }
+
+ /*
+ * Do the allocation.
+ */
+ pBlock = rtHeapSimpleAllocBlock(pHeapInt, cb, cbAlignment);
+ if (RT_LIKELY(pBlock))
+ {
+ void *pv = pBlock + 1;
+ return pv;
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTHeapSimpleAlloc);
+
+
+RTDECL(void *) RTHeapSimpleAllocZ(RTHEAPSIMPLE hHeap, size_t cb, size_t cbAlignment)
+{
+ PRTHEAPSIMPLEINTERNAL pHeapInt = hHeap;
+ PRTHEAPSIMPLEBLOCK pBlock;
+
+ /*
+ * Validate and adjust the input.
+ */
+ AssertPtrReturn(pHeapInt, NULL);
+ if (cb < RTHEAPSIMPLE_MIN_BLOCK)
+ cb = RTHEAPSIMPLE_MIN_BLOCK;
+ else
+ cb = RT_ALIGN_Z(cb, RTHEAPSIMPLE_ALIGNMENT);
+ if (!cbAlignment)
+ cbAlignment = RTHEAPSIMPLE_ALIGNMENT;
+ else
+ {
+ Assert(!(cbAlignment & (cbAlignment - 1)));
+ Assert((cbAlignment & ~(cbAlignment - 1)) == cbAlignment);
+ if (cbAlignment < RTHEAPSIMPLE_ALIGNMENT)
+ cbAlignment = RTHEAPSIMPLE_ALIGNMENT;
+ }
+
+ /*
+ * Do the allocation.
+ */
+ pBlock = rtHeapSimpleAllocBlock(pHeapInt, cb, cbAlignment);
+ if (RT_LIKELY(pBlock))
+ {
+ void *pv = pBlock + 1;
+ memset(pv, 0, cb);
+ return pv;
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTHeapSimpleAllocZ);
+
+
+/**
+ * Allocates a block of memory from the specified heap.
+ *
+ * No parameter validation or adjustment is performed.
+ *
+ * @returns Pointer to the allocated block.
+ * @returns NULL on failure.
+ *
+ * @param pHeapInt The heap.
+ * @param cb Size of the memory block to allocate.
+ * @param uAlignment The alignment specifications for the allocated block.
+ */
+static PRTHEAPSIMPLEBLOCK rtHeapSimpleAllocBlock(PRTHEAPSIMPLEINTERNAL pHeapInt, size_t cb, size_t uAlignment)
+{
+ PRTHEAPSIMPLEBLOCK pRet = NULL;
+ PRTHEAPSIMPLEFREE pFree;
+
+#ifdef RTHEAPSIMPLE_STRICT
+ rtHeapSimpleAssertAll(pHeapInt);
+#endif
+
+ /*
+ * Search for a fitting block from the lower end of the heap.
+ */
+ for (pFree = pHeapInt->pFreeHead;
+ pFree;
+ pFree = pFree->pNext)
+ {
+ uintptr_t offAlign;
+ ASSERT_BLOCK_FREE(pHeapInt, pFree);
+
+ /*
+ * Match for size and alignment.
+ */
+ if (pFree->cb < cb)
+ continue;
+ offAlign = (uintptr_t)(&pFree->Core + 1) & (uAlignment - 1);
+ if (offAlign)
+ {
+ RTHEAPSIMPLEFREE Free;
+ PRTHEAPSIMPLEBLOCK pPrev;
+
+ offAlign = uAlignment - offAlign;
+ if (pFree->cb - offAlign < cb)
+ continue;
+
+ /*
+ * Make a stack copy of the free block header and adjust the pointer.
+ */
+ Free = *pFree;
+ pFree = (PRTHEAPSIMPLEFREE)((uintptr_t)pFree + offAlign);
+
+ /*
+ * Donate offAlign bytes to the node in front of us.
+ * If we're the head node, we'll have to create a fake node. We'll
+ * mark it USED for simplicity.
+ *
+ * (Should this policy of donating memory to the guy in front of us
+ * cause big 'leaks', we could create a new free node if there is room
+ * for that.)
+ */
+ pPrev = Free.Core.pPrev;
+ if (pPrev)
+ {
+ AssertMsg(!RTHEAPSIMPLEBLOCK_IS_FREE(pPrev), ("Impossible!\n"));
+ pPrev->pNext = &pFree->Core;
+ }
+ else
+ {
+ pPrev = (PRTHEAPSIMPLEBLOCK)(pHeapInt + 1);
+ Assert(pPrev == &pFree->Core);
+ pPrev->pPrev = NULL;
+ pPrev->pNext = &pFree->Core;
+ pPrev->pHeap = pHeapInt;
+ pPrev->fFlags = RTHEAPSIMPLEBLOCK_FLAGS_MAGIC;
+ }
+ pHeapInt->cbFree -= offAlign;
+
+ /*
+ * Recreate pFree in the new position and adjust the neighbors.
+ */
+ *pFree = Free;
+
+ /* the core */
+ if (pFree->Core.pNext)
+ pFree->Core.pNext->pPrev = &pFree->Core;
+ pFree->Core.pPrev = pPrev;
+
+ /* the free part */
+ pFree->cb -= offAlign;
+ if (pFree->pNext)
+ pFree->pNext->pPrev = pFree;
+ else
+ pHeapInt->pFreeTail = pFree;
+ if (pFree->pPrev)
+ pFree->pPrev->pNext = pFree;
+ else
+ pHeapInt->pFreeHead = pFree;
+ ASSERT_BLOCK_FREE(pHeapInt, pFree);
+ ASSERT_BLOCK_USED(pHeapInt, pPrev);
+ }
+
+ /*
+ * Split off a new FREE block?
+ */
+ if (pFree->cb >= cb + RT_ALIGN_Z(sizeof(RTHEAPSIMPLEFREE), RTHEAPSIMPLE_ALIGNMENT))
+ {
+ /*
+ * Move the FREE block up to make room for the new USED block.
+ */
+ PRTHEAPSIMPLEFREE pNew = (PRTHEAPSIMPLEFREE)((uintptr_t)&pFree->Core + cb + sizeof(RTHEAPSIMPLEBLOCK));
+
+ pNew->Core.pNext = pFree->Core.pNext;
+ if (pFree->Core.pNext)
+ pFree->Core.pNext->pPrev = &pNew->Core;
+ pNew->Core.pPrev = &pFree->Core;
+ pNew->Core.pHeap = pHeapInt;
+ pNew->Core.fFlags = RTHEAPSIMPLEBLOCK_FLAGS_MAGIC | RTHEAPSIMPLEBLOCK_FLAGS_FREE;
+
+ pNew->pNext = pFree->pNext;
+ if (pNew->pNext)
+ pNew->pNext->pPrev = pNew;
+ else
+ pHeapInt->pFreeTail = pNew;
+ pNew->pPrev = pFree->pPrev;
+ if (pNew->pPrev)
+ pNew->pPrev->pNext = pNew;
+ else
+ pHeapInt->pFreeHead = pNew;
+ pNew->cb = (pNew->Core.pNext ? (uintptr_t)pNew->Core.pNext : (uintptr_t)pHeapInt->pvEnd) \
+ - (uintptr_t)pNew - sizeof(RTHEAPSIMPLEBLOCK);
+ ASSERT_BLOCK_FREE(pHeapInt, pNew);
+
+ /*
+ * Update the old FREE node making it a USED node.
+ */
+ pFree->Core.fFlags &= ~RTHEAPSIMPLEBLOCK_FLAGS_FREE;
+ pFree->Core.pNext = &pNew->Core;
+ pHeapInt->cbFree -= pFree->cb;
+ pHeapInt->cbFree += pNew->cb;
+ pRet = &pFree->Core;
+ ASSERT_BLOCK_USED(pHeapInt, pRet);
+ }
+ else
+ {
+ /*
+ * Link it out of the free list.
+ */
+ if (pFree->pNext)
+ pFree->pNext->pPrev = pFree->pPrev;
+ else
+ pHeapInt->pFreeTail = pFree->pPrev;
+ if (pFree->pPrev)
+ pFree->pPrev->pNext = pFree->pNext;
+ else
+ pHeapInt->pFreeHead = pFree->pNext;
+
+ /*
+ * Convert it to a used block.
+ */
+ pHeapInt->cbFree -= pFree->cb;
+ pFree->Core.fFlags &= ~RTHEAPSIMPLEBLOCK_FLAGS_FREE;
+ pRet = &pFree->Core;
+ ASSERT_BLOCK_USED(pHeapInt, pRet);
+ }
+ break;
+ }
+
+#ifdef RTHEAPSIMPLE_STRICT
+ rtHeapSimpleAssertAll(pHeapInt);
+#endif
+ return pRet;
+}
+
+
+RTDECL(void) RTHeapSimpleFree(RTHEAPSIMPLE hHeap, void *pv)
+{
+ PRTHEAPSIMPLEINTERNAL pHeapInt;
+ PRTHEAPSIMPLEBLOCK pBlock;
+
+ /*
+ * Validate input.
+ */
+ if (!pv)
+ return;
+ AssertPtr(pv);
+ Assert(RT_ALIGN_P(pv, RTHEAPSIMPLE_ALIGNMENT) == pv);
+
+ /*
+ * Get the block and heap. If in strict mode, validate these.
+ */
+ pBlock = (PRTHEAPSIMPLEBLOCK)pv - 1;
+ pHeapInt = pBlock->pHeap;
+ ASSERT_BLOCK_USED(pHeapInt, pBlock);
+ ASSERT_ANCHOR(pHeapInt);
+ Assert(pHeapInt == (PRTHEAPSIMPLEINTERNAL)hHeap || !hHeap); RT_NOREF_PV(hHeap);
+
+#ifdef RTHEAPSIMPLE_FREE_POISON
+ /*
+ * Poison the block.
+ */
+ const size_t cbBlock = (pBlock->pNext ? (uintptr_t)pBlock->pNext : (uintptr_t)pHeapInt->pvEnd)
+ - (uintptr_t)pBlock - sizeof(RTHEAPSIMPLEBLOCK);
+ memset(pBlock + 1, RTHEAPSIMPLE_FREE_POISON, cbBlock);
+#endif
+
+ /*
+ * Call worker which does the actual job.
+ */
+ rtHeapSimpleFreeBlock(pHeapInt, pBlock);
+}
+RT_EXPORT_SYMBOL(RTHeapSimpleFree);
+
+
+/**
+ * Free a memory block.
+ *
+ * @param pHeapInt The heap.
+ * @param pBlock The memory block to free.
+ */
+static void rtHeapSimpleFreeBlock(PRTHEAPSIMPLEINTERNAL pHeapInt, PRTHEAPSIMPLEBLOCK pBlock)
+{
+ PRTHEAPSIMPLEFREE pFree = (PRTHEAPSIMPLEFREE)pBlock;
+ PRTHEAPSIMPLEFREE pLeft;
+ PRTHEAPSIMPLEFREE pRight;
+
+#ifdef RTHEAPSIMPLE_STRICT
+ rtHeapSimpleAssertAll(pHeapInt);
+#endif
+
+ /*
+ * Look for the closest free list blocks by walking the blocks right
+ * of us (both lists are sorted by address).
+ */
+ pLeft = NULL;
+ pRight = NULL;
+ if (pHeapInt->pFreeTail)
+ {
+ pRight = (PRTHEAPSIMPLEFREE)pFree->Core.pNext;
+ while (pRight && !RTHEAPSIMPLEBLOCK_IS_FREE(&pRight->Core))
+ {
+ ASSERT_BLOCK(pHeapInt, &pRight->Core);
+ pRight = (PRTHEAPSIMPLEFREE)pRight->Core.pNext;
+ }
+ if (!pRight)
+ pLeft = pHeapInt->pFreeTail;
+ else
+ {
+ ASSERT_BLOCK_FREE(pHeapInt, pRight);
+ pLeft = pRight->pPrev;
+ }
+ if (pLeft)
+ ASSERT_BLOCK_FREE(pHeapInt, pLeft);
+ }
+ AssertMsgReturnVoid(pLeft != pFree, ("Freed twice! pv=%p (pBlock=%p)\n", pBlock + 1, pBlock));
+ ASSERT_L(pLeft, pFree);
+ Assert(!pRight || (uintptr_t)pRight > (uintptr_t)pFree);
+ Assert(!pLeft || pLeft->pNext == pRight);
+
+ /*
+ * Insert at the head of the free block list?
+ */
+ if (!pLeft)
+ {
+ Assert(pRight == pHeapInt->pFreeHead);
+ pFree->Core.fFlags |= RTHEAPSIMPLEBLOCK_FLAGS_FREE;
+ pFree->pPrev = NULL;
+ pFree->pNext = pRight;
+ if (pRight)
+ pRight->pPrev = pFree;
+ else
+ pHeapInt->pFreeTail = pFree;
+ pHeapInt->pFreeHead = pFree;
+ }
+ else
+ {
+ /*
+ * Can we merge with left hand free block?
+ */
+ if (pLeft->Core.pNext == &pFree->Core)
+ {
+ pLeft->Core.pNext = pFree->Core.pNext;
+ if (pFree->Core.pNext)
+ pFree->Core.pNext->pPrev = &pLeft->Core;
+ pHeapInt->cbFree -= pLeft->cb;
+ pFree = pLeft;
+ }
+ /*
+ * No, just link it into the free list then.
+ */
+ else
+ {
+ pFree->Core.fFlags |= RTHEAPSIMPLEBLOCK_FLAGS_FREE;
+ pFree->pNext = pRight;
+ pFree->pPrev = pLeft;
+ pLeft->pNext = pFree;
+ if (pRight)
+ pRight->pPrev = pFree;
+ else
+ pHeapInt->pFreeTail = pFree;
+ }
+ }
+
+ /*
+ * Can we merge with right hand free block?
+ */
+ if ( pRight
+ && pRight->Core.pPrev == &pFree->Core)
+ {
+ /* core */
+ pFree->Core.pNext = pRight->Core.pNext;
+ if (pRight->Core.pNext)
+ pRight->Core.pNext->pPrev = &pFree->Core;
+
+ /* free */
+ pFree->pNext = pRight->pNext;
+ if (pRight->pNext)
+ pRight->pNext->pPrev = pFree;
+ else
+ pHeapInt->pFreeTail = pFree;
+ pHeapInt->cbFree -= pRight->cb;
+ }
+
+ /*
+ * Calculate the size and update free stats.
+ */
+ pFree->cb = (pFree->Core.pNext ? (uintptr_t)pFree->Core.pNext : (uintptr_t)pHeapInt->pvEnd)
+ - (uintptr_t)pFree - sizeof(RTHEAPSIMPLEBLOCK);
+ pHeapInt->cbFree += pFree->cb;
+ ASSERT_BLOCK_FREE(pHeapInt, pFree);
+
+#ifdef RTHEAPSIMPLE_STRICT
+ rtHeapSimpleAssertAll(pHeapInt);
+#endif
+}
+
+
+#ifdef RTHEAPSIMPLE_STRICT
+/**
+ * Internal consistency check (relying on assertions).
+ * @param pHeapInt
+ */
+static void rtHeapSimpleAssertAll(PRTHEAPSIMPLEINTERNAL pHeapInt)
+{
+ PRTHEAPSIMPLEFREE pPrev = NULL;
+ PRTHEAPSIMPLEFREE pPrevFree = NULL;
+ PRTHEAPSIMPLEFREE pBlock;
+ for (pBlock = (PRTHEAPSIMPLEFREE)(pHeapInt + 1);
+ pBlock;
+ pBlock = (PRTHEAPSIMPLEFREE)pBlock->Core.pNext)
+ {
+ if (RTHEAPSIMPLEBLOCK_IS_FREE(&pBlock->Core))
+ {
+ ASSERT_BLOCK_FREE(pHeapInt, pBlock);
+ Assert(pBlock->pPrev == pPrevFree);
+ Assert(pPrevFree || pHeapInt->pFreeHead == pBlock);
+ pPrevFree = pBlock;
+ }
+ else
+ ASSERT_BLOCK_USED(pHeapInt, &pBlock->Core);
+ Assert(!pPrev || pPrev == (PRTHEAPSIMPLEFREE)pBlock->Core.pPrev);
+ pPrev = pBlock;
+ }
+ Assert(pHeapInt->pFreeTail == pPrevFree);
+}
+#endif
+
+
+RTDECL(size_t) RTHeapSimpleSize(RTHEAPSIMPLE hHeap, void *pv)
+{
+ PRTHEAPSIMPLEINTERNAL pHeapInt;
+ PRTHEAPSIMPLEBLOCK pBlock;
+ size_t cbBlock;
+
+ /*
+ * Validate input.
+ */
+ if (!pv)
+ return 0;
+ AssertPtrReturn(pv, 0);
+ AssertReturn(RT_ALIGN_P(pv, RTHEAPSIMPLE_ALIGNMENT) == pv, 0);
+
+ /*
+ * Get the block and heap. If in strict mode, validate these.
+ */
+ pBlock = (PRTHEAPSIMPLEBLOCK)pv - 1;
+ pHeapInt = pBlock->pHeap;
+ ASSERT_BLOCK_USED(pHeapInt, pBlock);
+ ASSERT_ANCHOR(pHeapInt);
+ Assert(pHeapInt == (PRTHEAPSIMPLEINTERNAL)hHeap || !hHeap); RT_NOREF_PV(hHeap);
+
+ /*
+ * Calculate the block size.
+ */
+ cbBlock = (pBlock->pNext ? (uintptr_t)pBlock->pNext : (uintptr_t)pHeapInt->pvEnd)
+ - (uintptr_t)pBlock- sizeof(RTHEAPSIMPLEBLOCK);
+ return cbBlock;
+}
+RT_EXPORT_SYMBOL(RTHeapSimpleSize);
+
+
+RTDECL(size_t) RTHeapSimpleGetHeapSize(RTHEAPSIMPLE hHeap)
+{
+ PRTHEAPSIMPLEINTERNAL pHeapInt;
+
+ if (hHeap == NIL_RTHEAPSIMPLE)
+ return 0;
+
+ pHeapInt = hHeap;
+ AssertPtrReturn(pHeapInt, 0);
+ ASSERT_ANCHOR(pHeapInt);
+ return pHeapInt->cbHeap;
+}
+RT_EXPORT_SYMBOL(RTHeapSimpleGetHeapSize);
+
+
+RTDECL(size_t) RTHeapSimpleGetFreeSize(RTHEAPSIMPLE hHeap)
+{
+ PRTHEAPSIMPLEINTERNAL pHeapInt;
+
+ if (hHeap == NIL_RTHEAPSIMPLE)
+ return 0;
+
+ pHeapInt = hHeap;
+ AssertPtrReturn(pHeapInt, 0);
+ ASSERT_ANCHOR(pHeapInt);
+ return pHeapInt->cbFree;
+}
+RT_EXPORT_SYMBOL(RTHeapSimpleGetFreeSize);
+
+
+RTDECL(void) RTHeapSimpleDump(RTHEAPSIMPLE hHeap, PFNRTHEAPSIMPLEPRINTF pfnPrintf)
+{
+ PRTHEAPSIMPLEINTERNAL pHeapInt = (PRTHEAPSIMPLEINTERNAL)hHeap;
+ PRTHEAPSIMPLEFREE pBlock;
+
+ pfnPrintf("**** Dumping Heap %p - cbHeap=%zx cbFree=%zx ****\n",
+ hHeap, pHeapInt->cbHeap, pHeapInt->cbFree);
+
+ for (pBlock = (PRTHEAPSIMPLEFREE)(pHeapInt + 1);
+ pBlock;
+ pBlock = (PRTHEAPSIMPLEFREE)pBlock->Core.pNext)
+ {
+ size_t cb = (pBlock->pNext ? (uintptr_t)pBlock->Core.pNext : (uintptr_t)pHeapInt->pvEnd)
+ - (uintptr_t)pBlock - sizeof(RTHEAPSIMPLEBLOCK);
+ if (RTHEAPSIMPLEBLOCK_IS_FREE(&pBlock->Core))
+ pfnPrintf("%p %06x FREE pNext=%p pPrev=%p fFlags=%#x cb=%#06x : cb=%#06x pNext=%p pPrev=%p\n",
+ pBlock, (uintptr_t)pBlock - (uintptr_t)(pHeapInt + 1), pBlock->Core.pNext, pBlock->Core.pPrev, pBlock->Core.fFlags, cb,
+ pBlock->cb, pBlock->pNext, pBlock->pPrev);
+ else
+ pfnPrintf("%p %06x USED pNext=%p pPrev=%p fFlags=%#x cb=%#06x\n",
+ pBlock, (uintptr_t)pBlock - (uintptr_t)(pHeapInt + 1), pBlock->Core.pNext, pBlock->Core.pPrev, pBlock->Core.fFlags, cb);
+ }
+ pfnPrintf("**** Done dumping Heap %p ****\n", hHeap);
+}
+RT_EXPORT_SYMBOL(RTHeapSimpleDump);
+
diff --git a/src/VBox/Runtime/common/alloc/memcache.cpp b/src/VBox/Runtime/common/alloc/memcache.cpp
new file mode 100644
index 00000000..4d15c294
--- /dev/null
+++ b/src/VBox/Runtime/common/alloc/memcache.cpp
@@ -0,0 +1,595 @@
+/* $Id: memcache.cpp $ */
+/** @file
+ * IPRT - Memory Object Allocation Cache.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/memcache.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a cache instance. */
+typedef struct RTMEMCACHEINT *PRTMEMCACHEINT;
+/** Pointer to a cache page. */
+typedef struct RTMEMCACHEPAGE *PRTMEMCACHEPAGE;
+
+
+
+/**
+ * A free object.
+ *
+ * @remarks This only works if the objects don't have a constructor or
+ * destructor and are big enough.
+ */
+typedef struct RTMEMCACHEFREEOBJ
+{
+ /** Pointer to the next free object */
+ struct RTMEMCACHEFREEOBJ * volatile pNext;
+} RTMEMCACHEFREEOBJ;
+/** Pointer to a free object. */
+typedef RTMEMCACHEFREEOBJ *PRTMEMCACHEFREEOBJ;
+
+
+/**
+ * A cache page.
+ *
+ * This is a page of memory that we split up in to a bunch object sized chunks
+ * and hand out to the cache users. The bitmap is updated in an atomic fashion
+ * so that we don't have to take any locks when freeing or allocating memory.
+ */
+typedef struct RTMEMCACHEPAGE
+{
+ /** Pointer to the cache owning this page.
+ * This is used for validation purposes only. */
+ PRTMEMCACHEINT pCache;
+ /** Pointer to the next page.
+ * This is marked as volatile since we'll be adding new entries to the list
+ * without taking any locks. */
+ PRTMEMCACHEPAGE volatile pNext;
+ /** Bitmap tracking allocated blocks. */
+ void volatile *pbmAlloc;
+ /** Bitmap tracking which blocks that has been thru the constructor. */
+ void volatile *pbmCtor;
+ /** Pointer to the object array. */
+ uint8_t *pbObjects;
+ /** The number of objects on this page. */
+ uint32_t cObjects;
+
+ /** Padding to force cFree into the next cache line. (ASSUMES CL = 64) */
+ uint8_t abPadding[ARCH_BITS == 32 ? 64 - 6*4 : 64 - 5*8 - 4];
+ /** The number of free objects. */
+ int32_t volatile cFree;
+} RTMEMCACHEPAGE;
+AssertCompileMemberOffset(RTMEMCACHEPAGE, cFree, 64);
+
+
+/**
+ * Memory object cache instance.
+ */
+typedef struct RTMEMCACHEINT
+{
+ /** Magic value (RTMEMCACHE_MAGIC). */
+ uint32_t u32Magic;
+ /** The object size. */
+ uint32_t cbObject;
+ /** Object alignment. */
+ uint32_t cbAlignment;
+ /** The per page object count. */
+ uint32_t cPerPage;
+ /** Number of bits in the bitmap.
+ * @remarks This is higher or equal to cPerPage and it is aligned such that
+ * the search operation will be most efficient on x86/AMD64. */
+ uint32_t cBits;
+ /** The maximum number of objects. */
+ uint32_t cMax;
+ /** Whether to the use the free list or not. */
+ bool fUseFreeList;
+ /** Head of the page list. */
+ PRTMEMCACHEPAGE pPageHead;
+ /** Poiner to the insertion point in the page list. */
+ PRTMEMCACHEPAGE volatile *ppPageNext;
+ /** Constructor callback. */
+ PFNMEMCACHECTOR pfnCtor;
+ /** Destructor callback. */
+ PFNMEMCACHEDTOR pfnDtor;
+ /** Callback argument. */
+ void *pvUser;
+ /** Critical section serializing page allocation and similar. */
+ RTCRITSECT CritSect;
+
+ /** The total object count. */
+ uint32_t volatile cTotal;
+ /** The number of free objects. */
+ int32_t volatile cFree;
+ /** This may point to a page with free entries. */
+ PRTMEMCACHEPAGE volatile pPageHint;
+ /** Stack of free items.
+ * These are marked as used in the allocation bitmaps.
+ *
+ * @todo This doesn't scale well when several threads are beating on the
+ * cache. Also, it totally doesn't work when the objects are too
+ * small. */
+ PRTMEMCACHEFREEOBJ volatile pFreeTop;
+} RTMEMCACHEINT;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtMemCacheFreeList(RTMEMCACHEINT *pThis, PRTMEMCACHEFREEOBJ pHead);
+
+
+RTDECL(int) RTMemCacheCreate(PRTMEMCACHE phMemCache, size_t cbObject, size_t cbAlignment, uint32_t cMaxObjects,
+ PFNMEMCACHECTOR pfnCtor, PFNMEMCACHEDTOR pfnDtor, void *pvUser, uint32_t fFlags)
+
+{
+ AssertPtr(phMemCache);
+ AssertPtrNull(pfnCtor);
+ AssertPtrNull(pfnDtor);
+ AssertReturn(!pfnDtor || pfnCtor, VERR_INVALID_PARAMETER);
+ AssertReturn(cbObject > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(cbObject <= PAGE_SIZE / 8, VERR_INVALID_PARAMETER);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+
+ if (cbAlignment == 0)
+ {
+ if (cbObject <= 2)
+ cbAlignment = cbObject;
+ else if (cbObject <= 4)
+ cbAlignment = 4;
+ else if (cbObject <= 8)
+ cbAlignment = 8;
+ else if (cbObject <= 16)
+ cbAlignment = 16;
+ else if (cbObject <= 32)
+ cbAlignment = 32;
+ else
+ cbAlignment = 64;
+ }
+ else
+ {
+ AssertReturn(!((cbAlignment - 1) & cbAlignment), VERR_NOT_POWER_OF_TWO);
+ AssertReturn(cbAlignment <= 64, VERR_OUT_OF_RANGE);
+ }
+
+ /*
+ * Allocate and initialize the instance memory.
+ */
+ RTMEMCACHEINT *pThis = (RTMEMCACHEINT *)RTMemAlloc(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+ int rc = RTCritSectInit(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ pThis->u32Magic = RTMEMCACHE_MAGIC;
+ pThis->cbObject = (uint32_t)RT_ALIGN_Z(cbObject, cbAlignment);
+ pThis->cbAlignment = (uint32_t)cbAlignment;
+ pThis->cPerPage = (uint32_t)((PAGE_SIZE - RT_ALIGN_Z(sizeof(RTMEMCACHEPAGE), cbAlignment)) / pThis->cbObject);
+ while ( RT_ALIGN_Z(sizeof(RTMEMCACHEPAGE), 8)
+ + pThis->cPerPage * pThis->cbObject
+ + RT_ALIGN(pThis->cPerPage, 64) / 8 * 2
+ > PAGE_SIZE)
+ pThis->cPerPage--;
+ pThis->cBits = RT_ALIGN(pThis->cPerPage, 64);
+ pThis->cMax = cMaxObjects;
+ pThis->fUseFreeList = cbObject >= sizeof(RTMEMCACHEFREEOBJ)
+ && !pfnCtor
+ && !pfnDtor;
+ pThis->pPageHead = NULL;
+ pThis->ppPageNext = &pThis->pPageHead;
+ pThis->pfnCtor = pfnCtor;
+ pThis->pfnDtor = pfnDtor;
+ pThis->pvUser = pvUser;
+ pThis->cTotal = 0;
+ pThis->cFree = 0;
+ pThis->pPageHint = NULL;
+ pThis->pFreeTop = NULL;
+
+ *phMemCache = pThis;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTMemCacheDestroy(RTMEMCACHE hMemCache)
+{
+ RTMEMCACHEINT *pThis = hMemCache;
+ if (!pThis)
+ return VINF_SUCCESS;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMEMCACHE_MAGIC, VERR_INVALID_HANDLE);
+
+#if 0 /*def RT_STRICT - don't require eveything to be freed. Caches are very convenient for lazy cleanup. */
+ uint32_t cFree = pThis->cFree;
+ for (PRTMEMCACHEFREEOBJ pFree = pThis->pFreeTop; pFree && cFree < pThis->cTotal + 5; pFree = pFree->pNext)
+ cFree++;
+ AssertMsg(cFree == pThis->cTotal, ("cFree=%u cTotal=%u\n", cFree, pThis->cTotal));
+#endif
+
+ /*
+ * Destroy it.
+ */
+ AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTMEMCACHE_MAGIC_DEAD, RTMEMCACHE_MAGIC), VERR_INVALID_HANDLE);
+ RTCritSectDelete(&pThis->CritSect);
+
+ while (pThis->pPageHead)
+ {
+ PRTMEMCACHEPAGE pPage = pThis->pPageHead;
+ pThis->pPageHead = pPage->pNext;
+ pPage->cFree = 0;
+
+ if (pThis->pfnDtor)
+ {
+ uint32_t iObj = pPage->cObjects;
+ while (iObj-- > 0)
+ if (ASMBitTestAndClear(pPage->pbmCtor, iObj))
+ pThis->pfnDtor(hMemCache, pPage->pbObjects + iObj * pThis->cbObject, pThis->pvUser);
+ }
+
+ RTMemPageFree(pPage, PAGE_SIZE);
+ }
+
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Grows the cache.
+ *
+ * @returns IPRT status code.
+ * @param pThis The memory cache instance.
+ */
+static int rtMemCacheGrow(RTMEMCACHEINT *pThis)
+{
+ /*
+ * Enter the critical section here to avoid allocation races leading to
+ * wasted memory (++) and make it easier to link in the new page.
+ */
+ RTCritSectEnter(&pThis->CritSect);
+ int rc = VINF_SUCCESS;
+ if (pThis->cFree < 0)
+ {
+ /*
+ * Allocate and initialize the new page.
+ *
+ * We put the constructor bitmap at the lower end right after cFree.
+ * We then push the object array to the end of the page and place the
+ * allocation bitmap below it. The hope is to increase the chance that
+ * the allocation bitmap is in a different cache line than cFree since
+ * this increases performance markably when lots of threads are beating
+ * on the cache.
+ */
+ PRTMEMCACHEPAGE pPage = (PRTMEMCACHEPAGE)RTMemPageAlloc(PAGE_SIZE);
+ if (pPage)
+ {
+ uint32_t const cObjects = RT_MIN(pThis->cPerPage, pThis->cMax - pThis->cTotal);
+
+ ASMMemZeroPage(pPage);
+ pPage->pCache = pThis;
+ pPage->pNext = NULL;
+ pPage->cFree = cObjects;
+ pPage->cObjects = cObjects;
+ uint8_t *pb = (uint8_t *)(pPage + 1);
+ pb = RT_ALIGN_PT(pb, 8, uint8_t *);
+ pPage->pbmCtor = pb;
+ pb = (uint8_t *)pPage + PAGE_SIZE - pThis->cbObject * cObjects;
+ pPage->pbObjects = pb; Assert(RT_ALIGN_P(pb, pThis->cbAlignment) == pb);
+ pb -= pThis->cBits / 8;
+ pb = (uint8_t *)((uintptr_t)pb & ~(uintptr_t)7);
+ pPage->pbmAlloc = pb;
+ Assert((uintptr_t)pPage->pbmCtor + pThis->cBits / 8 <= (uintptr_t)pPage->pbmAlloc);
+
+ /* Mark the bitmap padding and any unused objects as allocated. */
+ for (uint32_t iBit = cObjects; iBit < pThis->cBits; iBit++)
+ ASMBitSet(pPage->pbmAlloc, iBit);
+
+ /* Make it the hint. */
+ ASMAtomicWritePtr(&pThis->pPageHint, pPage);
+
+ /* Link the page in at the end of the list. */
+ ASMAtomicWritePtr(pThis->ppPageNext, pPage);
+ pThis->ppPageNext = &pPage->pNext;
+
+ /* Add it to the page counts. */
+ ASMAtomicAddS32(&pThis->cFree, cObjects);
+ ASMAtomicAddU32(&pThis->cTotal, cObjects);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ RTCritSectLeave(&pThis->CritSect);
+ return rc;
+}
+
+
+/**
+ * Grabs a an object in a page.
+ * @returns New cFree value on success (0 or higher), -1 on failure.
+ * @param pPage Pointer to the page.
+ */
+DECL_FORCE_INLINE(int32_t) rtMemCacheGrabObj(PRTMEMCACHEPAGE pPage)
+{
+ if (ASMAtomicUoReadS32(&pPage->cFree) > 0)
+ {
+ int32_t cFreeNew = ASMAtomicDecS32(&pPage->cFree);
+ if (cFreeNew >= 0)
+ return cFreeNew;
+ ASMAtomicIncS32(&pPage->cFree);
+ }
+ return -1;
+}
+
+
+RTDECL(int) RTMemCacheAllocEx(RTMEMCACHE hMemCache, void **ppvObj)
+{
+ RTMEMCACHEINT *pThis = hMemCache;
+ AssertPtrReturn(pThis, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->u32Magic == RTMEMCACHE_MAGIC, VERR_INVALID_PARAMETER);
+
+ /*
+ * Try grab a free object from the stack.
+ */
+ PRTMEMCACHEFREEOBJ pObj = ASMAtomicUoReadPtrT(&pThis->pFreeTop, PRTMEMCACHEFREEOBJ);
+ if (pObj)
+ {
+ pObj = ASMAtomicXchgPtrT(&pThis->pFreeTop, NULL, PRTMEMCACHEFREEOBJ);
+ if (pObj)
+ {
+ if (pObj->pNext)
+ {
+ Assert(pObj->pNext != pObj);
+ PRTMEMCACHEFREEOBJ pAllocRace = ASMAtomicXchgPtrT(&pThis->pFreeTop, pObj->pNext, PRTMEMCACHEFREEOBJ);
+ if (pAllocRace)
+ rtMemCacheFreeList(pThis, pAllocRace);
+ }
+
+ pObj->pNext = NULL;
+ *ppvObj = pObj;
+ return VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Try grab a free object at the cache level.
+ */
+ int32_t cNewFree = ASMAtomicDecS32(&pThis->cFree);
+ if (RT_LIKELY(cNewFree < 0))
+ {
+ uint32_t cTotal = ASMAtomicUoReadU32(&pThis->cTotal);
+ if ( (uint32_t)(cTotal + -cNewFree) > pThis->cMax
+ || (uint32_t)(cTotal + -cNewFree) <= cTotal)
+ {
+ ASMAtomicIncS32(&pThis->cFree);
+ return VERR_MEM_CACHE_MAX_SIZE;
+ }
+
+ int rc = rtMemCacheGrow(pThis);
+ if (RT_FAILURE(rc))
+ {
+ ASMAtomicIncS32(&pThis->cFree);
+ return rc;
+ }
+ }
+
+ /*
+ * Grab a free object at the page level.
+ */
+ PRTMEMCACHEPAGE pPage = ASMAtomicUoReadPtrT(&pThis->pPageHint, PRTMEMCACHEPAGE);
+ int32_t iObj = pPage ? rtMemCacheGrabObj(pPage) : -1;
+ if (iObj < 0)
+ {
+ for (unsigned cLoops = 0; ; cLoops++)
+ {
+ for (pPage = pThis->pPageHead; pPage; pPage = pPage->pNext)
+ {
+ iObj = rtMemCacheGrabObj(pPage);
+ if (iObj >= 0)
+ {
+ if (iObj > 0)
+ ASMAtomicWritePtr(&pThis->pPageHint, pPage);
+ break;
+ }
+ }
+ if (iObj >= 0)
+ break;
+ Assert(cLoops != 2);
+ Assert(cLoops < 10);
+ }
+ }
+ Assert(iObj >= 0);
+ Assert((uint32_t)iObj < pThis->cMax);
+
+ /*
+ * Find a free object in the allocation bitmap. Use the new cFree count
+ * as a hint.
+ */
+ if (ASMAtomicBitTestAndSet(pPage->pbmAlloc, iObj))
+ {
+ for (unsigned cLoops2 = 0;; cLoops2++)
+ {
+ iObj = ASMBitFirstClear(pPage->pbmAlloc, pThis->cBits);
+ if (RT_LIKELY(iObj >= 0))
+ {
+ if (!ASMAtomicBitTestAndSet(pPage->pbmAlloc, iObj))
+ break;
+ }
+ else
+ ASMMemoryFence();
+ Assert(cLoops2 != 40);
+ }
+ Assert(iObj >= 0);
+ }
+ void *pvObj = &pPage->pbObjects[iObj * pThis->cbObject];
+ Assert((uintptr_t)pvObj - (uintptr_t)pPage < PAGE_SIZE);
+
+ /*
+ * Call the constructor?
+ */
+ if ( pThis->pfnCtor
+ && !ASMAtomicBitTestAndSet(pPage->pbmCtor, iObj))
+ {
+ int rc = pThis->pfnCtor(hMemCache, pvObj, pThis->pvUser);
+ if (RT_FAILURE(rc))
+ {
+ ASMAtomicBitClear(pPage->pbmCtor, iObj);
+ RTMemCacheFree(pThis, pvObj);
+ return rc;
+ }
+ }
+
+ *ppvObj = pvObj;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void *) RTMemCacheAlloc(RTMEMCACHE hMemCache)
+{
+ void *pvObj;
+ int rc = RTMemCacheAllocEx(hMemCache, &pvObj);
+ if (RT_SUCCESS(rc))
+ return pvObj;
+ return NULL;
+}
+
+
+
+/**
+ * Really frees one object.
+ *
+ * @param pThis The memory cache.
+ * @param pvObj The memory object to free.
+ */
+static void rtMemCacheFreeOne(RTMEMCACHEINT *pThis, void *pvObj)
+{
+ /* Note: Do *NOT* attempt to poison the object! */
+
+ /*
+ * Find the cache page. The page structure is at the start of the page.
+ */
+ PRTMEMCACHEPAGE pPage = (PRTMEMCACHEPAGE)(((uintptr_t)pvObj) & ~(uintptr_t)PAGE_OFFSET_MASK);
+ Assert(pPage->pCache == pThis);
+ Assert(ASMAtomicUoReadS32(&pPage->cFree) < (int32_t)pThis->cPerPage);
+
+ /*
+ * Clear the bitmap bit and update the two object counter. Order matters!
+ */
+ uintptr_t offObj = (uintptr_t)pvObj - (uintptr_t)pPage->pbObjects;
+ uintptr_t iObj = offObj / pThis->cbObject;
+ Assert(iObj * pThis->cbObject == offObj);
+ Assert(iObj < pThis->cPerPage);
+ AssertReturnVoid(ASMAtomicBitTestAndClear(pPage->pbmAlloc, iObj));
+
+ ASMAtomicIncS32(&pPage->cFree);
+ ASMAtomicIncS32(&pThis->cFree);
+}
+
+
+/**
+ * Really frees a list of 'freed' object.
+ *
+ * @param pThis The memory cache.
+ * @param pHead The head of the list.
+ */
+static void rtMemCacheFreeList(RTMEMCACHEINT *pThis, PRTMEMCACHEFREEOBJ pHead)
+{
+ while (pHead)
+ {
+ PRTMEMCACHEFREEOBJ pFreeMe = pHead;
+ pHead = pHead->pNext;
+ pFreeMe->pNext = NULL;
+ ASMCompilerBarrier();
+ rtMemCacheFreeOne(pThis, pFreeMe);
+ }
+}
+
+
+
+RTDECL(void) RTMemCacheFree(RTMEMCACHE hMemCache, void *pvObj)
+{
+ if (!pvObj)
+ return;
+
+ RTMEMCACHEINT *pThis = hMemCache;
+ AssertPtrReturnVoid(pThis);
+ AssertReturnVoid(pThis->u32Magic == RTMEMCACHE_MAGIC);
+
+ AssertPtr(pvObj);
+ Assert(RT_ALIGN_P(pvObj, pThis->cbAlignment) == pvObj);
+
+ if (!pThis->fUseFreeList)
+ rtMemCacheFreeOne(pThis, pvObj);
+ else
+ {
+# ifdef RT_STRICT
+ /* This is the same as the other branch, except it's not actually freed. */
+ PRTMEMCACHEPAGE pPage = (PRTMEMCACHEPAGE)(((uintptr_t)pvObj) & ~(uintptr_t)PAGE_OFFSET_MASK);
+ Assert(pPage->pCache == pThis);
+ Assert(ASMAtomicUoReadS32(&pPage->cFree) < (int32_t)pThis->cPerPage);
+ uintptr_t offObj = (uintptr_t)pvObj - (uintptr_t)pPage->pbObjects;
+ uintptr_t iObj = offObj / pThis->cbObject;
+ Assert(iObj * pThis->cbObject == offObj);
+ Assert(iObj < pThis->cPerPage);
+ AssertReturnVoid(ASMBitTest(pPage->pbmAlloc, (int32_t)iObj));
+# endif
+
+ /*
+ * Push it onto the free stack.
+ */
+ PRTMEMCACHEFREEOBJ pObj = (PRTMEMCACHEFREEOBJ)pvObj;
+ pObj->pNext = ASMAtomicXchgPtrT(&pThis->pFreeTop, NULL, PRTMEMCACHEFREEOBJ);
+ PRTMEMCACHEFREEOBJ pFreeRace = ASMAtomicXchgPtrT(&pThis->pFreeTop, pObj, PRTMEMCACHEFREEOBJ);
+ if (pFreeRace)
+ rtMemCacheFreeList(pThis, pFreeRace);
+ }
+}
+
diff --git a/src/VBox/Runtime/common/alloc/memtracker.cpp b/src/VBox/Runtime/common/alloc/memtracker.cpp
new file mode 100644
index 00000000..2610eedf
--- /dev/null
+++ b/src/VBox/Runtime/common/alloc/memtracker.cpp
@@ -0,0 +1,1359 @@
+/* $Id: memtracker.cpp $ */
+/** @file
+ * IPRT - Memory Tracker & Leak Detector.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/memtracker.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/critsect.h>
+#ifdef IN_RING3
+# include <iprt/file.h>
+#endif
+#include <iprt/errcore.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#include "internal/file.h"
+#include "internal/magics.h"
+#include "internal/strhash.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a memory tracker instance */
+typedef struct RTMEMTRACKERINT *PRTMEMTRACKERINT;
+
+/**
+ * Memory tracker statistics.
+ */
+typedef struct RTMEMTRACKERSTATS
+{
+ /** Array of method calls. */
+ uint64_t volatile acMethodCalls[RTMEMTRACKERMETHOD_END];
+ /** The number of times this user freed or reallocated a memory block
+ * orignally allocated by someone else. */
+ uint64_t volatile cUserChanges;
+ /** The total number of bytes allocated ever. */
+ uint64_t volatile cbTotalAllocated;
+ /** The total number of blocks allocated ever. */
+ uint64_t volatile cTotalAllocatedBlocks;
+ /** The number of bytes currently allocated. */
+ size_t volatile cbAllocated;
+ /** The number of blocks currently allocated. */
+ size_t volatile cAllocatedBlocks;
+} RTMEMTRACKERSTATS;
+/** Pointer to memory tracker statistics. */
+typedef RTMEMTRACKERSTATS *PRTMEMTRACKERSTATS;
+
+
+/**
+ * Memory tracker user data.
+ */
+typedef struct RTMEMTRACKERUSER
+{
+ /** Entry in the user list (RTMEMTRACKERINT::UserList). */
+ RTLISTNODE ListEntry;
+ /** Pointer to the tracker. */
+ PRTMEMTRACKERINT pTracker;
+ /** Critical section protecting the memory list. */
+ RTCRITSECT CritSect;
+ /** The list of memory allocated by this user (RTMEMTRACKERHDR). */
+ RTLISTANCHOR MemoryList;
+ /** Positive numbers indicates recursion.
+ * Negative numbers are used for the global user since that is shared by
+ * more than one thread. */
+ int32_t volatile cInTracker;
+ /** The user identifier. */
+ uint32_t idUser;
+ /** The statistics for this user. */
+ RTMEMTRACKERSTATS Stats;
+ /** The user (thread) name. */
+ char szName[32];
+} RTMEMTRACKERUSER;
+/** Pointer to memory tracker per user data. */
+typedef RTMEMTRACKERUSER *PRTMEMTRACKERUSER;
+
+
+/**
+ * Memory tracker per tag statistics.
+ */
+typedef struct RTMEMTRACKERTAG
+{
+ /** AVL node core for lookup by hash. */
+ AVLU32NODECORE Core;
+ /** Tag list entry for flat traversal while dumping. */
+ RTLISTNODE ListEntry;
+ /** Pointer to the next tag with the same hash (collisions). */
+ PRTMEMTRACKERTAG pNext;
+ /** The tag statistics. */
+ RTMEMTRACKERSTATS Stats;
+ /** The tag name length. */
+ size_t cchTag;
+ /** The tag string. */
+ char szTag[1];
+} RTMEMTRACKERTAG;
+
+
+/**
+ * The memory tracker instance.
+ */
+typedef struct RTMEMTRACKERINT
+{
+ /** Cross roads semaphore separating dumping and normal operation.
+ * - NS - normal tracking.
+ * - EW - dumping tracking data. */
+ RTSEMXROADS hXRoads;
+
+ /** Critical section protecting the user list and tag database. */
+ RTCRITSECT CritSect;
+ /** List of RTMEMTRACKERUSER records. */
+ RTLISTANCHOR UserList;
+ /** The next user identifier number. */
+ uint32_t idUserNext;
+ /** The TLS index used for the per thread user records. */
+ RTTLS iTls;
+ /** Cross roads semaphore used to protect the tag database.
+ * - NS - lookup.
+ * - EW + critsect - insertion.
+ * @todo Replaced this by a read-write semaphore. */
+ RTSEMXROADS hXRoadsTagDb;
+ /** The root of the tag lookup database. */
+ AVLU32TREE TagDbRoot;
+ /** List of RTMEMTRACKERTAG records. */
+ RTLISTANCHOR TagList;
+#if ARCH_BITS == 32
+ /** Alignment padding. */
+ uint32_t u32Alignment;
+#endif
+ /** The global user record (fallback). */
+ RTMEMTRACKERUSER FallbackUser;
+ /** The global statistics. */
+ RTMEMTRACKERSTATS GlobalStats;
+ /** The number of busy (recursive) allocations. */
+ uint64_t volatile cBusyAllocs;
+ /** The number of busy (recursive) frees. */
+ uint64_t volatile cBusyFrees;
+ /** The number of tags. */
+ uint32_t cTags;
+ /** The number of users. */
+ uint32_t cUsers;
+} RTMEMTRACKERINT;
+AssertCompileMemberAlignment(RTMEMTRACKERINT, FallbackUser, 8);
+
+
+/**
+ * Output callback structure.
+ */
+typedef struct RTMEMTRACKEROUTPUT
+{
+ /** The printf like callback. */
+ DECLCALLBACKMEMBER(void, pfnPrintf,(struct RTMEMTRACKEROUTPUT *pThis, const char *pszFormat, ...));
+
+ /** The data. */
+ union
+ {
+ RTFILE hFile;
+ } uData;
+} RTMEMTRACKEROUTPUT;
+/** Pointer to a memory tracker output callback structure. */
+typedef RTMEMTRACKEROUTPUT *PRTMEMTRACKEROUTPUT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Pointer to the default memory tracker. */
+static PRTMEMTRACKERINT g_pDefaultTracker = NULL;
+
+
+/**
+ * Creates a memory tracker.
+ *
+ * @returns IRPT status code.
+ * @param ppTracker Where to return the tracker instance.
+ */
+static int rtMemTrackerCreate(PRTMEMTRACKERINT *ppTracker)
+{
+ PRTMEMTRACKERINT pTracker = (PRTMEMTRACKERINT)RTMemAllocZ(sizeof(*pTracker));
+ if (!pTracker)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Create locks and stuff.
+ */
+ int rc = RTCritSectInitEx(&pTracker->CritSect,
+ RTCRITSECT_FLAGS_NO_LOCK_VAL | RTCRITSECT_FLAGS_NO_NESTING | RTCRITSECT_FLAGS_BOOTSTRAP_HACK,
+ NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemXRoadsCreate(&pTracker->hXRoads);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemXRoadsCreate(&pTracker->hXRoadsTagDb);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTlsAllocEx(&pTracker->iTls, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInitEx(&pTracker->FallbackUser.CritSect,
+ RTCRITSECT_FLAGS_NO_LOCK_VAL | RTCRITSECT_FLAGS_NO_NESTING | RTCRITSECT_FLAGS_BOOTSTRAP_HACK,
+ NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize the rest of the structure.
+ */
+ RTListInit(&pTracker->UserList);
+ RTListInit(&pTracker->TagList);
+ RTListInit(&pTracker->FallbackUser.ListEntry);
+ RTListInit(&pTracker->FallbackUser.MemoryList);
+ pTracker->FallbackUser.pTracker = pTracker;
+ pTracker->FallbackUser.cInTracker = INT32_MIN / 2;
+ pTracker->FallbackUser.idUser = pTracker->idUserNext++;
+ strcpy(pTracker->FallbackUser.szName, "fallback");
+
+ *ppTracker = pTracker;
+ return VINF_SUCCESS;
+ }
+
+ RTTlsFree(pTracker->iTls);
+ }
+ RTSemXRoadsDestroy(pTracker->hXRoadsTagDb);
+ }
+ RTSemXRoadsDestroy(pTracker->hXRoads);
+ }
+ RTCritSectDelete(&pTracker->CritSect);
+ }
+ return rc;
+}
+
+
+/**
+ * Gets the user record to use.
+ *
+ * @returns Pointer to a user record.
+ * @param pTracker The tracker instance.
+ */
+static PRTMEMTRACKERUSER rtMemTrackerGetUser(PRTMEMTRACKERINT pTracker)
+{
+ /* ASSUMES that RTTlsGet and RTTlsSet will not reenter. */
+ PRTMEMTRACKERUSER pUser = (PRTMEMTRACKERUSER)RTTlsGet(pTracker->iTls);
+ if (RT_UNLIKELY(!pUser))
+ {
+ /*
+ * Is the thread currently initializing or terminating?
+ * If so, don't try add any user record for it as RTThread may barf or
+ * we might not get the thread name.
+ */
+ if (!RTThreadIsSelfAlive())
+ return &pTracker->FallbackUser;
+
+ /*
+ * Allocate and initialize a new user record for this thread.
+ *
+ * We install the fallback user record while doing the allocation and
+ * locking so that we can deal with recursions.
+ */
+ int rc = RTTlsSet(pTracker->iTls, &pTracker->FallbackUser);
+ if (RT_SUCCESS(rc))
+ {
+ pUser = (PRTMEMTRACKERUSER)RTMemAllocZ(sizeof(*pUser));
+ if (pUser)
+ {
+ rc = RTCritSectInitEx(&pUser->CritSect,
+ RTCRITSECT_FLAGS_NO_LOCK_VAL | RTCRITSECT_FLAGS_NO_NESTING | RTCRITSECT_FLAGS_BOOTSTRAP_HACK,
+ NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pUser->ListEntry);
+ RTListInit(&pUser->MemoryList);
+ pUser->pTracker = pTracker;
+ pUser->cInTracker = 1;
+
+ const char *pszName = RTThreadSelfName();
+ if (pszName)
+ RTStrCopy(pUser->szName, sizeof(pUser->szName), pszName);
+
+ /*
+ * Register the new user record.
+ */
+ rc = RTTlsSet(pTracker->iTls, pUser);
+ if (RT_SUCCESS(rc))
+ {
+ RTCritSectEnter(&pTracker->CritSect);
+
+ pUser->idUser = pTracker->idUserNext++;
+ RTListAppend(&pTracker->UserList, &pUser->ListEntry);
+ pTracker->cUsers++;
+
+ RTCritSectLeave(&pTracker->CritSect);
+ return pUser;
+ }
+
+ RTCritSectDelete(&pUser->CritSect);
+ }
+ RTMemFree(pUser);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ /* Failed, user the fallback. */
+ pUser = &pTracker->FallbackUser;
+ }
+
+ ASMAtomicIncS32(&pUser->cInTracker);
+ return pUser;
+}
+
+
+/**
+ * Counterpart to rtMemTrackerGetUser.
+ *
+ * @param pUser The user record to 'put' back.
+ */
+DECLINLINE(void) rtMemTrackerPutUser(PRTMEMTRACKERUSER pUser)
+{
+ ASMAtomicDecS32(&pUser->cInTracker);
+}
+
+
+/**
+ * Get the tag record corresponding to @a pszTag.
+ *
+ * @returns The tag record. This may be NULL if we're out of memory or
+ * if something goes wrong.
+ *
+ * @param pTracker The tracker instance.
+ * @param pUser The user record of the caller. Must NOT be
+ * NULL. This is used to prevent infinite
+ * recursions when allocating a new tag record.
+ * @param pszTag The tag string. Can be NULL.
+ */
+DECLINLINE(PRTMEMTRACKERTAG) rtMemTrackerGetTag(PRTMEMTRACKERINT pTracker, PRTMEMTRACKERUSER pUser, const char *pszTag)
+{
+ AssertPtr(pTracker);
+ AssertPtr(pUser);
+ if (pUser->cInTracker <= 0)
+ return NULL;
+
+ /*
+ * Hash tag string.
+ */
+ size_t cchTag;
+ uint32_t uHash;
+ if (pszTag)
+ uHash = sdbmN(pszTag, 260, &cchTag);
+ else
+ {
+ pszTag = "";
+ cchTag = 0;
+ uHash = 0;
+ }
+
+ /*
+ * Look up the tag.
+ */
+ RTSemXRoadsNSEnter(pTracker->hXRoadsTagDb);
+ PRTMEMTRACKERTAG pTag = (PRTMEMTRACKERTAG)RTAvlU32Get(&pTracker->TagDbRoot, uHash);
+ while ( pTag
+ && ( pTag->cchTag != cchTag
+ || memcmp(pTag->szTag, pszTag, cchTag)) )
+ pTag = pTag->pNext;
+ RTSemXRoadsNSLeave(pTracker->hXRoadsTagDb);
+
+ /*
+ * Create a new tag record if not found.
+ */
+ if (RT_UNLIKELY(!pTag))
+ {
+ pTag = (PRTMEMTRACKERTAG)RTMemAllocZVar(RT_UOFFSETOF_DYN(RTMEMTRACKERTAG, szTag[cchTag + 1]));
+ if (pTag)
+ {
+ pTag->Core.Key = uHash;
+ pTag->cchTag = cchTag;
+ memcpy(pTag->szTag, pszTag, cchTag + 1);
+
+ RTSemXRoadsEWEnter(pTracker->hXRoadsTagDb);
+ RTCritSectEnter(&pTracker->CritSect);
+
+ void *pvFreeMe = NULL;
+ PRTMEMTRACKERTAG pHeadTag = (PRTMEMTRACKERTAG)RTAvlU32Get(&pTracker->TagDbRoot, uHash);
+ if (!pHeadTag)
+ {
+ RTAvlU32Insert(&pTracker->TagDbRoot, &pTag->Core);
+ RTListAppend(&pTracker->TagList, &pTag->ListEntry);
+ pTracker->cTags++;
+ }
+ else
+ {
+ PRTMEMTRACKERTAG pTag2 = pHeadTag;
+ while ( pTag2
+ && ( pTag2->cchTag != cchTag
+ || memcmp(pTag2->szTag, pszTag, cchTag)) )
+ pTag2 = pTag2->pNext;
+ if (RT_LIKELY(!pTag2))
+ {
+ pTag->pNext = pHeadTag->pNext;
+ pHeadTag->pNext = pTag;
+ RTListAppend(&pTracker->TagList, &pTag->ListEntry);
+ pTracker->cTags++;
+ }
+ else
+ {
+ pvFreeMe = pTag;
+ pTag = pTag2;
+ }
+ }
+
+ RTCritSectLeave(&pTracker->CritSect);
+ RTSemXRoadsEWLeave(pTracker->hXRoadsTagDb);
+
+ if (RT_LIKELY(pvFreeMe))
+ RTMemFree(pvFreeMe);
+ }
+ }
+
+ return pTag;
+}
+
+
+/**
+ * Counterpart to rtMemTrackerGetTag.
+ *
+ * @param pTag The tag record to 'put' back.
+ */
+DECLINLINE(void) rtMemTrackerPutTag(PRTMEMTRACKERTAG pTag)
+{
+ NOREF(pTag);
+}
+
+
+/**
+ * Record an allocation call.
+ *
+ * @param pStats The statistics record.
+ * @param cbUser The size of the allocation.
+ * @param enmMethod The allocation method.
+ */
+DECLINLINE(void) rtMemTrackerStateRecordAlloc(PRTMEMTRACKERSTATS pStats, size_t cbUser, RTMEMTRACKERMETHOD enmMethod)
+{
+ ASMAtomicAddU64(&pStats->cbTotalAllocated, cbUser);
+ ASMAtomicIncU64(&pStats->cTotalAllocatedBlocks);
+ ASMAtomicAddZ(&pStats->cbAllocated, cbUser);
+ ASMAtomicIncZ(&pStats->cAllocatedBlocks);
+ ASMAtomicIncU64(&pStats->acMethodCalls[enmMethod]);
+}
+
+
+/**
+ * Record a free call.
+ *
+ * @param pStats The statistics record.
+ * @param cbUser The size of the allocation.
+ * @param enmMethod The free method.
+ */
+DECLINLINE(void) rtMemTrackerStateRecordFree(PRTMEMTRACKERSTATS pStats, size_t cbUser, RTMEMTRACKERMETHOD enmMethod)
+{
+ ASMAtomicSubZ(&pStats->cbAllocated, cbUser);
+ ASMAtomicDecZ(&pStats->cAllocatedBlocks);
+ ASMAtomicIncU64(&pStats->acMethodCalls[enmMethod]);
+}
+
+
+/**
+ * Internal RTMemTrackerHdrAlloc and RTMemTrackerHdrAllocEx worker.
+ *
+ * @returns Pointer to the user data allocation.
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param pv The pointer to the allocated memory. This
+ * includes room for the header.
+ * @param cbUser The size requested by the user.
+ * @param pszTag The tag string.
+ * @param pvCaller The return address.
+ * @param enmMethod The allocation method.
+ */
+static void *rtMemTrackerHdrAllocEx(PRTMEMTRACKERINT pTracker, void *pv, size_t cbUser,
+ const char *pszTag, void *pvCaller, RTMEMTRACKERMETHOD enmMethod)
+{
+ /*
+ * Check input.
+ */
+ if (!pv)
+ return NULL;
+ AssertReturn(enmMethod > RTMEMTRACKERMETHOD_INVALID && enmMethod < RTMEMTRACKERMETHOD_END, NULL);
+
+ /*
+ * Initialize the header.
+ */
+ PRTMEMTRACKERHDR pHdr = (PRTMEMTRACKERHDR)pv;
+
+ pHdr->uMagic = RTMEMTRACKERHDR_MAGIC;
+ pHdr->cbUser = cbUser;
+ RTListInit(&pHdr->ListEntry);
+ pHdr->pUser = NULL;
+ pHdr->pszTag = pszTag;
+ pHdr->pTag = NULL;
+ pHdr->pvCaller = pvCaller;
+ pHdr->pvUser = pHdr + 1;
+ pHdr->uReserved = 0;
+
+ /*
+ * Add it to the tracker if we've got one.
+ */
+ if (pTracker)
+ {
+ PRTMEMTRACKERUSER pUser = rtMemTrackerGetUser(pTracker);
+ if (pUser->cInTracker == 1)
+ {
+ RTSemXRoadsNSEnter(pTracker->hXRoads);
+
+ /* Get the tag and update it's statistics. */
+ PRTMEMTRACKERTAG pTag = rtMemTrackerGetTag(pTracker, pUser, pszTag);
+ if (pTag)
+ {
+ pHdr->pTag = pTag;
+ rtMemTrackerStateRecordAlloc(&pTag->Stats, cbUser, enmMethod);
+ rtMemTrackerPutTag(pTag);
+ }
+
+ /* Link the header and update the user statistics. */
+ RTCritSectEnter(&pUser->CritSect);
+ RTListAppend(&pUser->MemoryList, &pHdr->ListEntry);
+ RTCritSectLeave(&pUser->CritSect);
+
+ pHdr->pUser = pUser;
+ rtMemTrackerStateRecordAlloc(&pUser->Stats, cbUser, enmMethod);
+
+ /* Update the global statistics. */
+ rtMemTrackerStateRecordAlloc(&pTracker->GlobalStats, cbUser, enmMethod);
+
+ RTSemXRoadsNSLeave(pTracker->hXRoads);
+ }
+ else
+ ASMAtomicIncU64(&pTracker->cBusyAllocs);
+ rtMemTrackerPutUser(pUser);
+ }
+
+ return pHdr + 1;
+}
+
+
+/**
+ * Internal worker for rtMemTrackerHdrFreeEx and rtMemTrackerHdrReallocPrep.
+ *
+ * @returns Pointer to the original block.
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param pvUser Pointer to the user memory.
+ * @param cbUser The size of the user memory or 0.
+ * @param pszTag The tag to associate the free with.
+ * @param pvCaller The return address.
+ * @param enmMethod The free method.
+ * @param uDeadMagic The dead magic value to use.
+ */
+static void *rtMemTrackerHdrFreeCommon(PRTMEMTRACKERINT pTracker, void *pvUser, size_t cbUser,
+ const char *pszTag, void *pvCaller, RTMEMTRACKERMETHOD enmMethod,
+ size_t uDeadMagic)
+{
+ PRTMEMTRACKERHDR pHdr = (PRTMEMTRACKERHDR)pvUser - 1;
+ AssertReturn(pHdr->uMagic == RTMEMTRACKERHDR_MAGIC, NULL);
+ Assert(pHdr->cbUser == cbUser || !cbUser); NOREF(cbUser);
+ Assert(pHdr->pvUser == pvUser);
+
+ AssertReturn(enmMethod > RTMEMTRACKERMETHOD_INVALID && enmMethod < RTMEMTRACKERMETHOD_END, NULL);
+
+ /*
+ * First mark it as free.
+ */
+ pHdr->uMagic = uDeadMagic;
+
+ /*
+ * If there is a association with a user, we need to unlink it and update
+ * the statistics.
+ *
+ * A note on the locking here. We don't take the crossroads semaphore when
+ * reentering the memory tracker on the same thread because we may be
+ * holding it in a different direction and would therefore deadlock.
+ */
+ PRTMEMTRACKERUSER pMemUser = pHdr->pUser;
+ if (pMemUser)
+ {
+ Assert(pMemUser->pTracker == pTracker); Assert(pTracker);
+ PRTMEMTRACKERUSER pCallingUser = rtMemTrackerGetUser(pTracker);
+ bool const fTakeXRoadsLock = pCallingUser->cInTracker <= 1;
+ if (fTakeXRoadsLock)
+ RTSemXRoadsNSEnter(pTracker->hXRoads);
+
+ RTCritSectEnter(&pMemUser->CritSect);
+ RTListNodeRemove(&pHdr->ListEntry);
+ RTCritSectLeave(&pMemUser->CritSect);
+
+ if (pCallingUser == pMemUser)
+ rtMemTrackerStateRecordFree(&pCallingUser->Stats, pHdr->cbUser, enmMethod);
+ else
+ {
+ ASMAtomicIncU64(&pCallingUser->Stats.cUserChanges);
+ ASMAtomicIncU64(&pCallingUser->Stats.acMethodCalls[enmMethod]);
+
+ ASMAtomicSubU64(&pMemUser->Stats.cbTotalAllocated, cbUser);
+ ASMAtomicSubZ(&pMemUser->Stats.cbAllocated, cbUser);
+ }
+
+ rtMemTrackerStateRecordFree(&pTracker->GlobalStats, pHdr->cbUser, enmMethod);
+
+ /** @todo we're currently ignoring pszTag, consider how to correctly
+ * attribute the free operation if the tags differ - if it
+ * makes sense at all... */
+ NOREF(pszTag);
+ if (pHdr->pTag)
+ rtMemTrackerStateRecordFree(&pHdr->pTag->Stats, pHdr->cbUser, enmMethod);
+
+
+ if (fTakeXRoadsLock)
+ RTSemXRoadsNSLeave(pTracker->hXRoads);
+ rtMemTrackerPutUser(pCallingUser);
+ }
+ else
+ {
+ /*
+ * No tracked. This may happen even when pTracker != NULL when the same
+ * thread reenters the tracker when allocating tracker structures or memory
+ * in some subroutine like threading and locking.
+ */
+ Assert(!pHdr->pTag);
+ if (pTracker)
+ ASMAtomicIncU64(&pTracker->cBusyFrees);
+ }
+
+ NOREF(pvCaller); /* Intended for We may later do some use-after-free tracking. */
+ return pHdr;
+}
+
+
+/**
+ * Internal worker for RTMemTrackerHdrReallocPrep and
+ * RTMemTrackerHdrReallocPrepEx.
+ *
+ * @returns Pointer to the actual allocation.
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param pvOldUser The user memory.
+ * @param cbOldUser The size of the user memory, 0 if unknown.
+ * @param pszTag The tag string.
+ * @param pvCaller The return address.
+ */
+static void *rtMemTrackerHdrReallocPrepEx(PRTMEMTRACKERINT pTracker, void *pvOldUser, size_t cbOldUser,
+ const char *pszTag, void *pvCaller)
+{
+ if (!pvOldUser)
+ return NULL;
+ return rtMemTrackerHdrFreeCommon(pTracker, pvOldUser, cbOldUser, pszTag, pvCaller,
+ RTMEMTRACKERMETHOD_REALLOC_PREP, RTMEMTRACKERHDR_MAGIC_REALLOC);
+}
+
+
+/**
+ * Internal worker for RTMemTrackerHdrReallocDone and
+ * RTMemTrackerHdrReallocDoneEx.
+ *
+ * @returns Pointer to the actual allocation.
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param pvNew The new memory chunk. Can be NULL.
+ * @param cbNewUser The size of the new memory chunk.
+ * @param pvOldUser Pointer to the old user memory.
+ * @param pszTag The tag string.
+ * @param pvCaller The return address.
+ */
+static void *rtMemTrackerHdrReallocDoneEx(PRTMEMTRACKERINT pTracker, void *pvNew, size_t cbNewUser,
+ void *pvOldUser, const char *pszTag, void *pvCaller)
+{
+ /* Succeeded? */
+ if (pvNew)
+ return rtMemTrackerHdrAllocEx(pTracker, pvNew, cbNewUser, pszTag, pvCaller, RTMEMTRACKERMETHOD_REALLOC_DONE);
+
+ /* Failed or just realloc to zero? */
+ if (cbNewUser)
+ {
+ PRTMEMTRACKERHDR pHdr = (PRTMEMTRACKERHDR)pvOldUser - 1;
+ AssertReturn(pHdr->uMagic == RTMEMTRACKERHDR_MAGIC_REALLOC, NULL);
+
+ return rtMemTrackerHdrAllocEx(pTracker, pHdr, pHdr->cbUser, pszTag, pvCaller, RTMEMTRACKERMETHOD_REALLOC_FAILED);
+ }
+
+ /* Tealloc to zero bytes, i.e. free. */
+ return NULL;
+}
+
+
+/**
+ * Internal worker for RTMemTrackerHdrFree and RTMemTrackerHdrFreeEx.
+ *
+ * @returns Pointer to the actual allocation.
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param pvUser The user memory.
+ * @param cbUser The size of the user memory, 0 if unknown.
+ * @param pszTag The tag string.
+ * @param pvCaller The return address.
+ * @param enmMethod The free method.
+ */
+static void *rtMemTrackerHdrFreeEx(PRTMEMTRACKERINT pTracker, void *pvUser, size_t cbUser,
+ const char *pszTag, void *pvCaller, RTMEMTRACKERMETHOD enmMethod)
+{
+ if (!pvUser)
+ return NULL;
+ return rtMemTrackerHdrFreeCommon(pTracker, pvUser, cbUser, pszTag, pvCaller, enmMethod, RTMEMTRACKERHDR_MAGIC_FREE);
+}
+
+
+/**
+ * Prints a statistics record.
+ *
+ * @param pStats The record.
+ * @param pOutput The output callback table.
+ * @param fVerbose Whether to print in terse or verbose form.
+ */
+DECLINLINE(void) rtMemTrackerDumpOneStatRecord(PRTMEMTRACKERSTATS pStats, PRTMEMTRACKEROUTPUT pOutput, bool fVerbose)
+{
+ if (fVerbose)
+ {
+ pOutput->pfnPrintf(pOutput,
+ " Currently allocated: %7zu blocks, %8zu bytes\n"
+ " Total allocation sum: %7RU64 blocks, %8RU64 bytes\n"
+ ,
+ pStats->cAllocatedBlocks,
+ pStats->cbAllocated,
+ pStats->cTotalAllocatedBlocks,
+ pStats->cbTotalAllocated);
+ pOutput->pfnPrintf(pOutput,
+ " Alloc: %7RU64 AllocZ: %7RU64 Free: %7RU64 User Chg: %7RU64\n"
+ " RPrep: %7RU64 RDone: %7RU64 RFail: %7RU64\n"
+ " New: %7RU64 New[]: %7RU64 Delete: %7RU64 Delete[]: %7RU64\n"
+ ,
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_ALLOC],
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_ALLOCZ],
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_FREE],
+ pStats->cUserChanges,
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_REALLOC_PREP],
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_REALLOC_DONE],
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_REALLOC_FAILED],
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_NEW],
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_NEW_ARRAY],
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_DELETE],
+ pStats->acMethodCalls[RTMEMTRACKERMETHOD_DELETE_ARRAY]);
+ }
+ else
+ {
+ pOutput->pfnPrintf(pOutput, " %zu bytes in %zu blocks\n",
+ pStats->cbAllocated, pStats->cAllocatedBlocks);
+ }
+}
+
+
+/**
+ * Internal worker that dumps all the memory tracking data.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param pOutput The output callback table.
+ */
+static void rtMemTrackerDumpAllWorker(PRTMEMTRACKERINT pTracker, PRTMEMTRACKEROUTPUT pOutput)
+{
+ if (!pTracker)
+ return;
+
+ /*
+ * We use the EW direction to make sure the lists, trees and statistics
+ * does not change while we're working.
+ */
+ PRTMEMTRACKERUSER pUser = rtMemTrackerGetUser(pTracker);
+ RTSemXRoadsEWEnter(pTracker->hXRoads);
+
+ /* Global statistics.*/
+ pOutput->pfnPrintf(pOutput, "*** Global statistics ***\n");
+ rtMemTrackerDumpOneStatRecord(&pTracker->GlobalStats, pOutput, true);
+ pOutput->pfnPrintf(pOutput, " Busy Allocs: %4RU64 Busy Frees: %4RU64 Tags: %3u Users: %3u\n",
+ pTracker->cBusyAllocs, pTracker->cBusyFrees, pTracker->cTags, pTracker->cUsers);
+
+ /* Per tag statistics. */
+ pOutput->pfnPrintf(pOutput, "\n*** Tag statistics ***\n");
+ PRTMEMTRACKERTAG pTag, pNextTag;
+ RTListForEachSafe(&pTracker->TagList, pTag, pNextTag, RTMEMTRACKERTAG, ListEntry)
+ {
+ pOutput->pfnPrintf(pOutput, "Tag: %s\n", pTag->szTag);
+ rtMemTrackerDumpOneStatRecord(&pTag->Stats, pOutput, true);
+ pOutput->pfnPrintf(pOutput, "\n", pTag->szTag);
+ }
+
+ /* Per user statistics & blocks. */
+ pOutput->pfnPrintf(pOutput, "\n*** User statistics ***\n");
+ PRTMEMTRACKERUSER pCurUser, pNextUser;
+ RTListForEachSafe(&pTracker->UserList, pCurUser, pNextUser, RTMEMTRACKERUSER, ListEntry)
+ {
+ pOutput->pfnPrintf(pOutput, "User #%u: %s%s (cInTracker=%d)\n",
+ pCurUser->idUser,
+ pCurUser->szName,
+ pUser == pCurUser ? " (me)" : "",
+ pCurUser->cInTracker);
+ rtMemTrackerDumpOneStatRecord(&pCurUser->Stats, pOutput, true);
+
+ PRTMEMTRACKERHDR pCurHdr, pNextHdr;
+ RTListForEachSafe(&pCurUser->MemoryList, pCurHdr, pNextHdr, RTMEMTRACKERHDR, ListEntry)
+ {
+ if (pCurHdr->pTag)
+ pOutput->pfnPrintf(pOutput,
+ " %zu bytes at %p by %p with tag %s\n"
+ "%.*Rhxd\n"
+ "\n",
+ pCurHdr->cbUser, pCurHdr->pvUser, pCurHdr->pvCaller, pCurHdr->pTag->szTag,
+ RT_MIN(pCurHdr->cbUser, 16*3), pCurHdr->pvUser);
+ else
+ pOutput->pfnPrintf(pOutput,
+ " %zu bytes at %p by %p without a tag\n"
+ "%.*Rhxd\n"
+ "\n",
+ pCurHdr->cbUser, pCurHdr->pvUser, pCurHdr->pvCaller,
+ RT_MIN(pCurHdr->cbUser, 16*3), pCurHdr->pvUser);
+ }
+ pOutput->pfnPrintf(pOutput, "\n", pTag->szTag);
+ }
+
+ /* Repeat the global statistics. */
+ pOutput->pfnPrintf(pOutput, "*** Global statistics (reprise) ***\n");
+ rtMemTrackerDumpOneStatRecord(&pTracker->GlobalStats, pOutput, true);
+ pOutput->pfnPrintf(pOutput, " Busy Allocs: %4RU64 Busy Frees: %4RU64 Tags: %3u Users: %3u\n",
+ pTracker->cBusyAllocs, pTracker->cBusyFrees, pTracker->cTags, pTracker->cUsers);
+
+ RTSemXRoadsEWLeave(pTracker->hXRoads);
+ rtMemTrackerPutUser(pUser);
+}
+
+
+/**
+ * Internal worker that dumps the memory tracking statistics.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param pOutput The output callback table.
+ * @param fVerbose Whether to the verbose or quiet.
+ */
+static void rtMemTrackerDumpStatsWorker(PRTMEMTRACKERINT pTracker, PRTMEMTRACKEROUTPUT pOutput, bool fVerbose)
+{
+ if (!pTracker)
+ return;
+
+ /*
+ * We use the EW direction to make sure the lists, trees and statistics
+ * does not change while we're working.
+ */
+ PRTMEMTRACKERUSER pUser = rtMemTrackerGetUser(pTracker);
+ RTSemXRoadsEWEnter(pTracker->hXRoads);
+
+ /* Global statistics.*/
+ pOutput->pfnPrintf(pOutput, "*** Global statistics ***\n");
+ rtMemTrackerDumpOneStatRecord(&pTracker->GlobalStats, pOutput, fVerbose);
+ if (fVerbose)
+ pOutput->pfnPrintf(pOutput, " Busy Allocs: %4RU64 Busy Frees: %4RU64 Tags: %3u Users: %3u\n",
+ pTracker->cBusyAllocs, pTracker->cBusyFrees, pTracker->cTags, pTracker->cUsers);
+
+ /* Per tag statistics. */
+ pOutput->pfnPrintf(pOutput, "\n*** Tag statistics ***\n");
+ PRTMEMTRACKERTAG pTag, pNextTag;
+ RTListForEachSafe(&pTracker->TagList, pTag, pNextTag, RTMEMTRACKERTAG, ListEntry)
+ {
+ if ( fVerbose
+ || pTag->Stats.cbAllocated)
+ {
+ pOutput->pfnPrintf(pOutput, "Tag: %s\n", pTag->szTag);
+ rtMemTrackerDumpOneStatRecord(&pTag->Stats, pOutput, fVerbose);
+ if (fVerbose)
+ pOutput->pfnPrintf(pOutput, "\n", pTag->szTag);
+ }
+ }
+
+ /* Per user statistics. */
+ pOutput->pfnPrintf(pOutput, "\n*** User statistics ***\n");
+ PRTMEMTRACKERUSER pCurUser, pNextUser;
+ RTListForEachSafe(&pTracker->UserList, pCurUser, pNextUser, RTMEMTRACKERUSER, ListEntry)
+ {
+ if ( fVerbose
+ || pCurUser->Stats.cbAllocated
+ || pCurUser == pUser)
+ {
+ pOutput->pfnPrintf(pOutput, "User #%u: %s%s (cInTracker=%d)\n",
+ pCurUser->idUser,
+ pCurUser->szName,
+ pUser == pCurUser ? " (me)" : "",
+ pCurUser->cInTracker);
+ rtMemTrackerDumpOneStatRecord(&pCurUser->Stats, pOutput, fVerbose);
+ if (fVerbose)
+ pOutput->pfnPrintf(pOutput, "\n", pTag->szTag);
+ }
+ }
+
+ if (fVerbose)
+ {
+ /* Repeat the global statistics. */
+ pOutput->pfnPrintf(pOutput, "*** Global statistics (reprise) ***\n");
+ rtMemTrackerDumpOneStatRecord(&pTracker->GlobalStats, pOutput, fVerbose);
+ pOutput->pfnPrintf(pOutput, " Busy Allocs: %4RU64 Busy Frees: %4RU64 Tags: %3u Users: %3u\n",
+ pTracker->cBusyAllocs, pTracker->cBusyFrees, pTracker->cTags, pTracker->cUsers);
+ }
+
+ RTSemXRoadsEWLeave(pTracker->hXRoads);
+ rtMemTrackerPutUser(pUser);
+}
+
+
+/**
+ * @callback_method_impl{RTMEMTRACKEROUTPUT::pfnPrintf, Outputting to the release log}
+ */
+static DECLCALLBACK(void) rtMemTrackerDumpLogOutput(PRTMEMTRACKEROUTPUT pThis, const char *pszFormat, ...)
+{
+ NOREF(pThis);
+ va_list va;
+ va_start(va, pszFormat);
+ RTLogPrintfV(pszFormat, va);
+ va_end(va);
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpAllToLog and RTMemTrackerDumpAllToLogEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ */
+static void rtMemTrackerDumpAllToLogEx(PRTMEMTRACKERINT pTracker)
+{
+ RTMEMTRACKEROUTPUT Output;
+ Output.pfnPrintf = rtMemTrackerDumpLogOutput;
+ rtMemTrackerDumpAllWorker(pTracker, &Output);
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpStatsToLog and
+ * RTMemTrackerDumpStatsToLogEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param fVerbose Whether to print all the stats or just the ones
+ * relevant to hunting leaks.
+ */
+static void rtMemTrackerDumpStatsToLogEx(PRTMEMTRACKERINT pTracker, bool fVerbose)
+{
+ RTMEMTRACKEROUTPUT Output;
+ Output.pfnPrintf = rtMemTrackerDumpLogOutput;
+ rtMemTrackerDumpStatsWorker(pTracker, &Output, fVerbose);
+}
+
+
+/**
+ * @callback_method_impl{RTMEMTRACKEROUTPUT::pfnPrintf, Outputting to the release log}
+ */
+static DECLCALLBACK(void) rtMemTrackerDumpLogRelOutput(PRTMEMTRACKEROUTPUT pThis, const char *pszFormat, ...)
+{
+ NOREF(pThis);
+ va_list va;
+ va_start(va, pszFormat);
+ RTLogRelPrintfV(pszFormat, va);
+ va_end(va);
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpStatsToLog and
+ * RTMemTrackerDumpStatsToLogEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ */
+static void rtMemTrackerDumpAllToLogRelEx(PRTMEMTRACKERINT pTracker)
+{
+ RTMEMTRACKEROUTPUT Output;
+ Output.pfnPrintf = rtMemTrackerDumpLogRelOutput;
+ rtMemTrackerDumpAllWorker(pTracker, &Output);
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpStatsToLogRel and
+ * RTMemTrackerDumpStatsToLogRelEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param fVerbose Whether to print all the stats or just the ones
+ * relevant to hunting leaks.
+ */
+static void rtMemTrackerDumpStatsToLogRelEx(PRTMEMTRACKERINT pTracker, bool fVerbose)
+{
+ RTMEMTRACKEROUTPUT Output;
+ Output.pfnPrintf = rtMemTrackerDumpLogRelOutput;
+ rtMemTrackerDumpStatsWorker(pTracker, &Output, fVerbose);
+}
+
+#ifdef IN_RING3
+
+/**
+ * @callback_method_impl{RTMEMTRACKEROUTPUT::pfnPrintf, Outputting to file}
+ */
+static DECLCALLBACK(void) rtMemTrackerDumpFileOutput(PRTMEMTRACKEROUTPUT pThis, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ char szOutput[_4K];
+ size_t cchOutput = RTStrPrintfV(szOutput, sizeof(szOutput), pszFormat, va);
+ va_end(va);
+ RTFileWrite(pThis->uData.hFile, szOutput, cchOutput, NULL);
+}
+
+
+/**
+ * Internal work that dumps the memory tracking statistics to a file handle.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param fVerbose Whether to print all the stats or just the ones
+ * relevant to hunting leaks.
+ * @param hFile The file handle. Can be NIL_RTFILE.
+ */
+static void rtMemTrackerDumpStatsToFileHandle(PRTMEMTRACKERINT pTracker, bool fVerbose, RTFILE hFile)
+{
+ if (hFile == NIL_RTFILE)
+ return;
+ RTMEMTRACKEROUTPUT Output;
+ Output.pfnPrintf = rtMemTrackerDumpFileOutput;
+ Output.uData.hFile = hFile;
+ rtMemTrackerDumpStatsWorker(pTracker, &Output, fVerbose);
+}
+
+
+/**
+ * Internal work that dumps all the memory tracking information to a file
+ * handle.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param hFile The file handle. Can be NIL_RTFILE.
+ */
+static void rtMemTrackerDumpAllToFileHandle(PRTMEMTRACKERINT pTracker, RTFILE hFile)
+{
+ if (hFile == NIL_RTFILE)
+ return;
+ RTMEMTRACKEROUTPUT Output;
+ Output.pfnPrintf = rtMemTrackerDumpFileOutput;
+ Output.uData.hFile = hFile;
+ rtMemTrackerDumpAllWorker(pTracker, &Output);
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpStatsToStdOut and
+ * RTMemTrackerDumpStatsToStdOutEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param fVerbose Whether to print all the stats or just the ones
+ * relevant to hunting leaks.
+ */
+static void rtMemTrackerDumpStatsToStdOutEx(PRTMEMTRACKERINT pTracker, bool fVerbose)
+{
+ rtMemTrackerDumpStatsToFileHandle(pTracker, fVerbose, rtFileGetStandard(RTHANDLESTD_OUTPUT));
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpAllToStdOut and
+ * RTMemTrackerDumpAllToStdOutEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ */
+static void rtMemTrackerDumpAllToStdOutEx(PRTMEMTRACKERINT pTracker)
+{
+ rtMemTrackerDumpAllToFileHandle(pTracker, rtFileGetStandard(RTHANDLESTD_OUTPUT));
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpStatsToStdErr and
+ * RTMemTrackerDumpStatsToStdErrEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param fVerbose Whether to print all the stats or just the ones
+ * relevant to hunting leaks.
+ */
+static void rtMemTrackerDumpStatsToStdErrEx(PRTMEMTRACKERINT pTracker, bool fVerbose)
+{
+ rtMemTrackerDumpStatsToFileHandle(pTracker, fVerbose, rtFileGetStandard(RTHANDLESTD_ERROR));
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpAllToStdErr and
+ * RTMemTrackerDumpAllToStdErrEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ */
+static void rtMemTrackerDumpAllToStdErrEx(PRTMEMTRACKERINT pTracker)
+{
+ rtMemTrackerDumpAllToFileHandle(pTracker, rtFileGetStandard(RTHANDLESTD_ERROR));
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpStatsToFile and
+ * RTMemTrackerDumpStatsToFileEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param fVerbose Whether to print all the stats or just the ones
+ * relevant to hunting leaks.
+ * @param pszFilename The name of the output file.
+ */
+static void rtMemTrackerDumpStatsToFileEx(PRTMEMTRACKERINT pTracker, bool fVerbose, const char *pszFilename)
+{
+ if (!pTracker)
+ return;
+
+ /** @todo this is borked. */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFilename,
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE
+ | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
+ if (RT_FAILURE(rc))
+ return;
+ rtMemTrackerDumpStatsToFileHandle(pTracker, fVerbose, hFile);
+ RTFileClose(hFile);
+}
+
+
+/**
+ * Internal worker for RTMemTrackerDumpAllToFile and
+ * RTMemTrackerDumpAllToFileEx.
+ *
+ * @param pTracker The tracker instance. Can be NULL.
+ * @param pszFilename The name of the output file.
+ */
+static void rtMemTrackerDumpAllToFileEx(PRTMEMTRACKERINT pTracker, const char *pszFilename)
+{
+ if (!pTracker)
+ return;
+
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFilename,
+ RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE
+ | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
+ if (RT_FAILURE(rc))
+ return;
+ rtMemTrackerDumpAllToFileHandle(pTracker, hFile);
+ RTFileClose(hFile);
+}
+
+#endif /* IN_RING3 */
+
+
+
+/*
+ *
+ *
+ * Default tracker.
+ * Default tracker.
+ * Default tracker.
+ * Default tracker.
+ * Default tracker.
+ *
+ *
+ */
+
+
+/**
+ * Handles the lazy initialization when g_pDefaultTracker is NULL.
+ *
+ * @returns The newly created default tracker or NULL.
+ */
+static PRTMEMTRACKERINT rtMemTrackerLazyInitDefaultTracker(void)
+{
+ /*
+ * Don't attempt initialize before RTThread has been initialized.
+ */
+ if (!RTThreadIsInitialized())
+ return NULL;
+
+ /*
+ * Only one initialization at a time. For now we'll ASSUME that there
+ * won't be thread ending up here at the same time, only the same
+ * reentering from the allocator when creating the tracker.
+ */
+ static volatile bool s_fInitialized = false;
+ if (ASMAtomicXchgBool(&s_fInitialized, true))
+ return g_pDefaultTracker;
+
+ PRTMEMTRACKERINT pTracker = NULL; /* gcc sucks. */
+ int rc = rtMemTrackerCreate(&pTracker);
+ if (RT_FAILURE(rc))
+ return NULL;
+
+ g_pDefaultTracker = pTracker;
+ return pTracker;
+}
+
+
+
+RTDECL(void *) RTMemTrackerHdrAlloc(void *pv, size_t cb, const char *pszTag, void *pvCaller, RTMEMTRACKERMETHOD enmMethod)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerHdrAllocEx(pTracker, pv, cb, pszTag, pvCaller, enmMethod);
+}
+
+
+RTDECL(void *) RTMemTrackerHdrReallocPrep(void *pvOldUser, size_t cbOldUser, const char *pszTag, void *pvCaller)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerHdrReallocPrepEx(pTracker, pvOldUser, cbOldUser, pszTag, pvCaller);
+}
+
+
+RTDECL(void *) RTMemTrackerHdrReallocDone(void *pvNew, size_t cbNewUser, void *pvOld, const char *pszTag, void *pvCaller)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerHdrReallocDoneEx(pTracker, pvNew, cbNewUser, pvOld, pszTag, pvCaller);
+}
+
+
+RTDECL(void *) RTMemTrackerHdrFree(void *pvUser, size_t cbUser, const char *pszTag, void *pvCaller, RTMEMTRACKERMETHOD enmMethod)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerHdrFreeEx(pTracker, pvUser, cbUser, pszTag, pvCaller, enmMethod);
+}
+
+
+RTDECL(void) RTMemTrackerDumpAllToLog(void)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpAllToLogEx(pTracker);
+}
+
+
+RTDECL(void) RTMemTrackerDumpAllToLogRel(void)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpAllToLogRelEx(pTracker);
+}
+
+
+RTDECL(void) RTMemTrackerDumpAllToStdOut(void)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpAllToStdOutEx(pTracker);
+}
+
+
+RTDECL(void) RTMemTrackerDumpAllToStdErr(void)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpAllToStdErrEx(pTracker);
+}
+
+
+RTDECL(void) RTMemTrackerDumpAllToFile(const char *pszFilename)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpAllToFileEx(pTracker, pszFilename);
+}
+
+
+RTDECL(void) RTMemTrackerDumpStatsToLog(bool fVerbose)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpStatsToLogEx(pTracker, fVerbose);
+}
+
+
+RTDECL(void) RTMemTrackerDumpStatsToLogRel(bool fVerbose)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpStatsToLogRelEx(pTracker, fVerbose);
+}
+
+
+RTDECL(void) RTMemTrackerDumpStatsToStdOut(bool fVerbose)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpStatsToStdOutEx(pTracker, fVerbose);
+}
+
+
+RTDECL(void) RTMemTrackerDumpStatsToStdErr(bool fVerbose)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpStatsToStdErrEx(pTracker, fVerbose);
+}
+
+
+RTDECL(void) RTMemTrackerDumpStatsToFile(bool fVerbose, const char *pszFilename)
+{
+ PRTMEMTRACKERINT pTracker = g_pDefaultTracker;
+ if (RT_UNLIKELY(!pTracker))
+ pTracker = rtMemTrackerLazyInitDefaultTracker();
+ return rtMemTrackerDumpStatsToFileEx(pTracker, fVerbose, pszFilename);
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMAddFlags.asm b/src/VBox/Runtime/common/asm/ASMAddFlags.asm
new file mode 100644
index 00000000..bdecbeb3
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAddFlags.asm
@@ -0,0 +1,80 @@
+; $Id: ASMAddFlags.asm $
+;; @file
+; IPRT - ASMSetFlags().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param rcx/rdi eflags to add
+RT_BEGINPROC ASMAddFlags
+%if ARCH_BITS == 64
+ pushfq
+ mov rax, [rsp]
+ %ifdef ASM_CALL64_GCC
+ or rdi, rax
+ mov [rsp], rdi
+ %else
+ or rcx, rax
+ mov [rsp], rcx
+ %endif
+ popfq
+%elif ARCH_BITS == 32
+ mov ecx, [esp + 4]
+ pushfd
+ mov eax, [esp]
+ or ecx, eax
+ mov [esp], ecx
+ popfd
+%elif ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ pushf
+ pop ax
+ push word [bp + 2 + 2]
+ or [bp - 2], ax
+ popf
+ leave
+%else
+ %error ARCH_BITS
+%endif
+ ret
+ENDPROC ASMAddFlags
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm
new file mode 100644
index 00000000..0cab24b5
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm
@@ -0,0 +1,94 @@
+; $Id: ASMAtomicCmpXchgExU64.asm $
+;; @file
+; IPRT - ASMAtomicCmpXchgExU64().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically Exchange an unsigned 64-bit value, ordered.
+;
+; @param pu64 x86:ebp+8 gcc:rdi msc:rcx
+; @param u64New x86:ebp+c gcc:rsi msc:rdx
+; @param u64Old x86:ebp+14 gcc:rdx msc:r8
+; @param pu64Old x86:ebp+1c gcc:rcx msc:r9
+;
+; @returns bool result: true if successfully exchanged, false if not.
+; x86:al
+;
+RT_BEGINPROC ASMAtomicCmpXchgExU64
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov rax, r8
+ lock cmpxchg [rcx], rdx
+ mov [r9], rax
+ %else
+ mov rax, rdx
+ lock cmpxchg [rdi], rsi
+ mov [rcx], rax
+ %endif
+ setz al
+ movzx eax, al
+ ret
+%endif
+%ifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push ebx
+ push edi
+
+ mov ebx, dword [ebp+0ch]
+ mov ecx, dword [ebp+0ch + 4]
+ mov edi, [ebp+08h]
+ mov eax, dword [ebp+14h]
+ mov edx, dword [ebp+14h + 4]
+ lock cmpxchg8b [edi]
+ mov edi, [ebp + 1ch]
+ mov [edi], eax
+ mov [edi + 4], edx
+ setz al
+ movzx eax, al
+
+ pop edi
+ pop ebx
+ leave
+ ret
+%endif
+ENDPROC ASMAtomicCmpXchgExU64
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm
new file mode 100644
index 00000000..5476de4f
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm
@@ -0,0 +1,73 @@
+; $Id: ASMAtomicCmpXchgU16.asm $
+;; @file
+; IPRT - ASMAtomicCmpXchgU16().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically compares and exchanges an unsigned 8-bit int.
+;
+; @param pu16 x86:esp+4 msc:rcx gcc:rdi
+; @param u16New x86:esp+8 msc:dx gcc:si
+; @param u16Old x86:esp+c msc:r8l gcc:dl
+;
+; @returns bool result: true if successfully exchanged, false if not.
+; x86:al
+;
+RT_BEGINPROC ASMAtomicCmpXchgU16
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov ax, r8w
+ lock cmpxchg [rcx], dx
+ %else
+ mov ax, dx
+ lock cmpxchg [rdi], si
+ %endif
+%else
+ mov ecx, [esp + 04h]
+ mov dx, [esp + 08h]
+ mov ax, [esp + 0ch]
+ lock cmpxchg [ecx], dx
+%endif
+ setz al
+ movzx eax, al
+ ret
+ENDPROC ASMAtomicCmpXchgU16
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm
new file mode 100644
index 00000000..d63b0333
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm
@@ -0,0 +1,88 @@
+; $Id: ASMAtomicCmpXchgU64.asm $
+;; @file
+; IPRT - ASMAtomicCmpXchgU64().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically compares and exchanges an unsigned 64-bit int.
+;
+; @param pu64 x86:ebp+8 gcc:rdi msc:rcx
+; @param u64New x86:ebp+c gcc:rsi msc:rdx
+; @param u64Old x86:ebp+14 gcc:rdx msc:r8
+;
+; @returns bool result: true if successfully exchanged, false if not.
+; x86:al
+;
+RT_BEGINPROC ASMAtomicCmpXchgU64
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov rax, r8
+ lock cmpxchg [rcx], rdx
+ %else
+ mov rax, rdx
+ lock cmpxchg [rdi], rsi
+ %endif
+ setz al
+ movzx eax, al
+ ret
+%endif
+%ifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push ebx
+ push edi
+
+ mov ebx, dword [ebp+0ch]
+ mov ecx, dword [ebp+0ch + 4]
+ mov edi, [ebp+08h]
+ mov eax, dword [ebp+14h]
+ mov edx, dword [ebp+14h + 4]
+ lock cmpxchg8b [edi]
+ setz al
+ movzx eax, al
+
+ pop edi
+ pop ebx
+ leave
+ ret
+%endif
+ENDPROC ASMAtomicCmpXchgU64
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm
new file mode 100644
index 00000000..c4a030ff
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm
@@ -0,0 +1,73 @@
+; $Id: ASMAtomicCmpXchgU8.asm $
+;; @file
+; IPRT - ASMAtomicCmpXchgU8().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically compares and exchanges an unsigned 8-bit int.
+;
+; @param pu8 x86:esp+4 msc:rcx gcc:rdi
+; @param u8New x86:esp+8 msc:dl gcc:sil
+; @param u8Old x86:esp+c msc:r8l gcc:dl
+;
+; @returns bool result: true if successfully exchanged, false if not.
+; x86:al
+;
+RT_BEGINPROC ASMAtomicCmpXchgU8
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov al, r8b
+ lock cmpxchg [rcx], dl
+ %else
+ mov al, dl
+ lock cmpxchg [rdi], sil
+ %endif
+%else
+ mov ecx, [esp + 04h]
+ mov dl, [esp + 08h]
+ mov al, [esp + 0ch]
+ lock cmpxchg [ecx], dl
+%endif
+ setz al
+ movzx eax, al
+ ret
+ENDPROC ASMAtomicCmpXchgU8
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm
new file mode 100644
index 00000000..8214838e
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm
@@ -0,0 +1,81 @@
+; $Id: ASMAtomicReadU64.asm $
+;; @file
+; IPRT - ASMAtomicReadU64().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically reads 64-bit value.
+;
+; @param pu64 x86:ebp+8
+;
+; @returns The current value. (x86:eax+edx)
+;
+;
+RT_BEGINPROC ASMAtomicReadU64
+%ifdef RT_ARCH_AMD64
+ mfence ; ASSUME its present.
+ %ifdef ASM_CALL64_MSC
+ mov rax, [rcx]
+ %else
+ mov rax, [rdi]
+ %endif
+ ret
+%endif
+%ifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push ebx
+ push edi
+
+ xor eax, eax
+ xor edx, edx
+ mov edi, [ebp+08h]
+ xor ecx, ecx
+ xor ebx, ebx
+ lock cmpxchg8b [edi]
+
+ pop edi
+ pop ebx
+ leave
+ ret
+%endif
+ENDPROC ASMAtomicReadU64
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm
new file mode 100644
index 00000000..4bc47b18
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm
@@ -0,0 +1,66 @@
+; $Id: ASMAtomicUoAndU32.asm $
+;; @file
+; IPRT - ASMAtomicUoAndU32().
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically OR an unsigned 32-bit value, unordered.
+;
+; @param pu32 x86:esp+4 gcc:rdi msc:rcx
+; @param u32Or x86:esp+8 gcc:rsi msc:rdx
+;
+; @returns void
+;
+RT_BEGINPROC ASMAtomicUoAndU32
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ and [rcx], edx
+ %else
+ and [rdi], esi
+ %endif
+%elifdef RT_ARCH_X86
+ mov ecx, [esp + 04h]
+ mov edx, [esp + 08h]
+ and [ecx], edx
+%endif
+ ret
+ENDPROC ASMAtomicUoAndU32
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm
new file mode 100644
index 00000000..1266e3bc
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm
@@ -0,0 +1,86 @@
+; $Id: ASMAtomicUoAndU64.asm $
+;; @file
+; IPRT - ASMAtomicUoAndU64().
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically AND an unsigned 64-bit value, unordered.
+;
+; @param pu64 x86:ebp+8 gcc:rdi msc:rcx
+; @param u64Or x86:ebp+c gcc:rsi msc:rdx
+;
+; @returns void
+;
+RT_BEGINPROC ASMAtomicUoAndU64
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ and [rcx], rdx
+ %else
+ and [rdi], rsi
+ %endif
+%elifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push ebx
+ push edi
+
+ mov edi, [ebp + 08h]
+ mov ebx, [ebp + 0ch]
+ mov ecx, [ebp + 0ch + 4]
+ mov eax, ebx
+ mov edx, ecx
+.try_again:
+ cmpxchg8b [edi]
+ jz .done
+ mov ebx, eax
+ and ebx, [ebp + 0ch]
+ mov ecx, edx
+ and ecx, [ebp + 0ch + 4]
+ jmp .try_again
+
+.done:
+ pop edi
+ pop ebx
+ leave
+%endif
+ ret
+ENDPROC ASMAtomicUoAndU64
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm
new file mode 100644
index 00000000..469bc69f
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm
@@ -0,0 +1,66 @@
+; $Id: ASMAtomicUoDecU32.asm $
+;; @file
+; IPRT - ASMAtomicUoDecU32().
+;
+
+;
+; Copyright (C) 2014-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically decrement an unsigned 32-bit value, unordered.
+;
+; @param pu32 x86:esp+4 gcc:rdi msc:rcx
+
+; @returns the new decremented value.
+;
+RT_BEGINPROC ASMAtomicUoDecU32
+ mov eax, -1
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ xadd [rcx], eax
+ %else
+ xadd [rdi], eax
+ %endif
+%elifdef RT_ARCH_X86
+ mov ecx, [esp + 04h]
+ xadd [ecx], eax
+%endif
+ dec eax
+ ret
+ENDPROC ASMAtomicUoDecU32
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm
new file mode 100644
index 00000000..50ce19a0
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm
@@ -0,0 +1,66 @@
+; $Id: ASMAtomicUoIncU32.asm $
+;; @file
+; IPRT - ASMAtomicUoIncU32().
+;
+
+;
+; Copyright (C) 2014-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically increment an unsigned 32-bit value, unordered.
+;
+; @param pu32 x86:esp+4 gcc:rdi msc:rcx
+;
+; @returns the new incremented value.
+;
+RT_BEGINPROC ASMAtomicUoIncU32
+ mov eax, 1
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ xadd [rcx], eax
+ %else
+ xadd [rdi], eax
+ %endif
+%elifdef RT_ARCH_X86
+ mov ecx, [esp + 04h]
+ xadd [ecx], eax
+%endif
+ inc eax
+ ret
+ENDPROC ASMAtomicUoIncU32
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm
new file mode 100644
index 00000000..360e9abd
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm
@@ -0,0 +1,66 @@
+; $Id: ASMAtomicUoOrU32.asm $
+;; @file
+; IPRT - ASMAtomicUoOrU32().
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically OR an unsigned 32-bit value, unordered.
+;
+; @param pu32 x86:esp+4 gcc:rdi msc:rcx
+; @param u32Or x86:esp+8 gcc:rsi msc:rdx
+;
+; @returns void
+;
+RT_BEGINPROC ASMAtomicUoOrU32
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ or [rcx], edx
+ %else
+ or [rdi], esi
+ %endif
+%elifdef RT_ARCH_X86
+ mov ecx, [esp + 04h]
+ mov edx, [esp + 08h]
+ or [ecx], edx
+%endif
+ ret
+ENDPROC ASMAtomicUoOrU32
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm
new file mode 100644
index 00000000..a4a9cc42
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm
@@ -0,0 +1,86 @@
+; $Id: ASMAtomicUoOrU64.asm $
+;; @file
+; IPRT - ASMAtomicUoOrU64().
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically OR an unsigned 64-bit value, unordered.
+;
+; @param pu64 x86:ebp+8 gcc:rdi msc:rcx
+; @param u64Or x86:ebp+c gcc:rsi msc:rdx
+;
+; @returns void
+;
+RT_BEGINPROC ASMAtomicUoOrU64
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ or [rcx], rdx
+ %else
+ or [rdi], rsi
+ %endif
+%elifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push ebx
+ push edi
+
+ mov edi, [ebp + 08h]
+ mov ebx, [ebp + 0ch]
+ mov ecx, [ebp + 0ch + 4]
+ mov eax, ebx
+ mov edx, ecx
+.try_again:
+ cmpxchg8b [edi]
+ jz .done
+ mov ebx, eax
+ or ebx, [ebp + 0ch]
+ mov ecx, edx
+ or ecx, [ebp + 0ch + 4]
+ jmp .try_again
+
+.done:
+ pop edi
+ pop ebx
+ leave
+%endif
+ ret
+ENDPROC ASMAtomicUoOrU64
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm
new file mode 100644
index 00000000..0b789ba5
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm
@@ -0,0 +1,80 @@
+; $Id: ASMAtomicUoReadU64.asm $
+;; @file
+; IPRT - ASMAtomicUoReadU64().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically reads 64-bit value.
+;
+; @param pu64 x86:ebp+8
+;
+; @returns The current value. (x86:eax+edx)
+;
+;
+RT_BEGINPROC ASMAtomicUoReadU64
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov rax, [rcx]
+ %else
+ mov rax, [rdi]
+ %endif
+ ret
+%endif
+%ifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push ebx
+ push edi
+
+ xor eax, eax
+ xor edx, edx
+ mov edi, [ebp+08h]
+ xor ecx, ecx
+ xor ebx, ebx
+ lock cmpxchg8b [edi]
+
+ pop edi
+ pop ebx
+ leave
+ ret
+%endif
+ENDPROC ASMAtomicUoReadU64
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm
new file mode 100644
index 00000000..07da0096
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm
@@ -0,0 +1,66 @@
+; $Id: ASMAtomicUoXorU32.asm $
+;; @file
+; IPRT - ASMAtomicUoXorU32().
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically XOR an unsigned 32-bit value, unordered.
+;
+; @param pu32 x86:esp+4 gcc:rdi msc:rcx
+; @param u32Or x86:esp+8 gcc:rsi msc:rdx
+;
+; @returns void
+;
+RT_BEGINPROC ASMAtomicUoXorU32
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ xor [rcx], edx
+ %else
+ xor [rdi], esi
+ %endif
+%elifdef RT_ARCH_X86
+ mov ecx, [esp + 04h]
+ mov edx, [esp + 08h]
+ xor [ecx], edx
+%endif
+ ret
+ENDPROC ASMAtomicUoXorU32
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm b/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm
new file mode 100644
index 00000000..c1d7fbc0
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm
@@ -0,0 +1,70 @@
+; $Id: ASMAtomicXchgU16.asm $
+;; @file
+; IPRT - ASMAtomicXchgU16().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically Exchange an unsigned 16-bit value, ordered.
+;
+; @param pu16 x86:ebp+8 gcc:rdi msc:rcx
+; @param u16New x86:ebp+c gcc:si msc:dx
+;
+; @returns Current (i.e. old) *pu16 value (AX).
+;
+RT_BEGINPROC ASMAtomicXchgU16
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov ax, dx
+ xchg [rcx], ax
+ %else
+ mov ax, si
+ xchg [rdi], ax
+ %endif
+%elifdef RT_ARCH_X86
+ mov ecx, [esp+04h]
+ mov ax, [esp+08h]
+ xchg [ecx], ax
+%else
+ %error "Unsupport arch."
+%endif
+ ret
+ENDPROC ASMAtomicXchgU16
+
diff --git a/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm
new file mode 100644
index 00000000..a137d351
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm
@@ -0,0 +1,80 @@
+; $Id: ASMAtomicXchgU64.asm $
+;; @file
+; IPRT - ASMAtomicXchgU64().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Atomically Exchange an unsigned 64-bit value, ordered.
+;
+; @param pu64 x86:ebp+8 gcc:rdi msc:rcx
+; @param u64New x86:ebp+c gcc:rsi msc:rdx
+;
+; @returns Current (i.e. old) *pu64 value (x86:eax:edx, 64-bit: rax)
+;
+RT_BEGINPROC ASMAtomicXchgU64
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ xchg [rcx], rdx
+ %else
+ xchg [rdi], rsi
+ %endif
+ ret
+%endif
+%ifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push ebx
+ push edi
+
+.try_again:
+ mov ebx, dword [ebp+0ch]
+ mov ecx, dword [ebp+0ch + 4]
+ mov edi, [ebp+08h]
+ lock cmpxchg8b [edi]
+ jnz .try_again
+
+ pop edi
+ pop ebx
+ leave
+ ret
+%endif
+ENDPROC ASMAtomicXchgU64
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstClear-generic.cpp b/src/VBox/Runtime/common/asm/ASMBitFirstClear-generic.cpp
new file mode 100644
index 00000000..84444616
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitFirstClear-generic.cpp
@@ -0,0 +1,107 @@
+/* $Id: ASMBitFirstClear-generic.cpp $ */
+/** @file
+ * IPRT - ASMBitFirstClear - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+
+
+RTDECL(int32_t) ASMBitFirstClear(const volatile void RT_FAR *pvBitmap, uint32_t cBits) RT_NOTHROW_DEF
+{
+ const volatile size_t RT_FAR *pu = (const volatile size_t RT_FAR *)pvBitmap;
+ Assert(!(cBits & 31));
+ Assert(!((uintptr_t)pvBitmap & 3));
+
+#if ARCH_BITS > 32
+ /* Deal with misaligned bitmaps (happens all the time via ASMBitNextClear()). */
+ if (!((uintptr_t)pvBitmap & 7) && cBits >= 32)
+ {
+ uint32_t u32 = *(const volatile uint32_t RT_FAR *)pu;
+ if (u32 != UINT32_MAX)
+ {
+ size_t const iBaseBit = ((uintptr_t)pu - (uintptr_t)pvBitmap) * 8;
+ return iBaseBit + ASMBitFirstSetU32(~RT_LE2H_U32(u32)) - 1;
+ }
+ pu = (const volatile size_t RT_FAR *)((uintptr_t)pu + sizeof(uint32_t));
+ cBits -= 32;
+ }
+#endif
+
+ /* Main search loop: */
+ while (cBits >= sizeof(size_t) * 8)
+ {
+ size_t u = *pu;
+ if (u == ~(size_t)0)
+ { }
+ else
+ {
+ size_t const iBaseBit = ((uintptr_t)pu - (uintptr_t)pvBitmap) * 8;
+#if ARCH_BITS == 32
+ return iBaseBit + ASMBitFirstSetU32(~RT_LE2H_U32(u)) - 1;
+#elif ARCH_BITS == 64
+ return iBaseBit + ASMBitFirstSetU64(~RT_LE2H_U64(u)) - 1;
+#else
+# error "ARCH_BITS is not supported"
+#endif
+ }
+
+ pu++;
+ cBits -= sizeof(size_t) * 8;
+ }
+
+#if ARCH_BITS > 32
+ /* Final 32-bit item (unlikely)? */
+ if (cBits < 32)
+ { }
+ else
+ {
+ uint32_t u32 = *(const volatile uint32_t RT_FAR *)pu;
+ if (u32 != UINT32_MAX)
+ {
+ size_t const iBaseBit = ((uintptr_t)pu - (uintptr_t)pvBitmap) * 8;
+ return iBaseBit + ASMBitFirstSetU32(~RT_LE2H_U32(u32)) - 1;
+ }
+ }
+#endif
+
+ return -1;
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm b/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm
new file mode 100644
index 00000000..f7b95632
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm
@@ -0,0 +1,137 @@
+; $Id: ASMBitFirstClear.asm $
+;; @file
+; IPRT - ASMBitFirstClear().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the first clear bit in a bitmap.
+;
+; @returns (32/64:eax, 16:ax+dx) Index of the first zero bit.
+; @returns (32/64:eax, 16:ax+dx) -1 if no clear bit was found.
+; @param msc:rcx gcc:rdi pvBitmap Pointer to the bitmap.
+; @param msc:edx gcc:rsi cBits The number of bits in the bitmap. Multiple of 32.
+;
+RT_BEGINPROC ASMBitFirstClear
+ ;
+ ; if (cBits)
+ ; Put cBits in ecx first.
+ ;
+%if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ mov ecx, esi
+ %else
+ xchg rcx, rdx ; rdx=pvDst, ecx=cBits
+ %endif
+%elif ARCH_BITS == 32
+ mov ecx, [esp + 4 + 4]
+%elif ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ mov ecx, [bp + 4 + 4]
+%endif
+ or ecx, ecx
+ jz short .failed
+ ;{
+ push xDI
+
+ ; asm {...}
+%if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ ; rdi = start of scasd - already done
+ %else
+ mov rdi, rdx ; rdi = start of scasd (Note! xchg rdx,rcx above)
+ %endif
+%elif ARCH_BITS == 32
+ mov edi, [esp + 8]
+%elif ARCH_BITS == 16
+ mov ax, [bp + 4 + 2]
+ mov di, [bp + 4]
+ mov es, ax ; es is volatile, no need to save.
+%endif
+ add ecx, 31 ; 32 bit aligned
+ shr ecx, 5 ; number of dwords to scan.
+ mov xDX, xDI ; xDX = saved pvBitmap
+ mov eax, 0ffffffffh
+ repe scasd ; Scan for the first dword with any clear bit.
+ je .failed_restore
+
+ ; find the bit in question
+ sub xDI, 4 ; one step back.
+%if ARCH_BITS == 16
+ movzx edi, di
+ xor eax, [es:xDI] ; eax = NOT [rdi]
+%else
+ xor eax, [xDI] ; eax = NOT [rdi]
+%endif
+ sub xDI, xDX
+ shl edi, 3 ; calc bit offset.
+
+ bsf ecx, eax
+ jz .failed_restore ; race paranoia
+ add ecx, edi
+ mov eax, ecx
+
+ ; return success
+ pop xDI
+%if ARCH_BITS == 16
+ mov edx, eax
+ shr edx, 16
+ leave
+%endif
+ ret
+
+ ; failure
+ ;}
+ ;return -1;
+.failed_restore:
+ pop xDI
+.failed:
+%if ARCH_BITS != 16
+ mov eax, 0ffffffffh
+%else
+ mov ax, 0ffffh
+ mov dx, ax
+ leave
+%endif
+ ret
+ENDPROC ASMBitFirstClear
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSet-generic.cpp b/src/VBox/Runtime/common/asm/ASMBitFirstSet-generic.cpp
new file mode 100644
index 00000000..308f33d7
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitFirstSet-generic.cpp
@@ -0,0 +1,107 @@
+/* $Id: ASMBitFirstSet-generic.cpp $ */
+/** @file
+ * IPRT - ASMBitFirstSet - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+
+
+RTDECL(int32_t) ASMBitFirstSet(const volatile void RT_FAR *pvBitmap, uint32_t cBits) RT_NOTHROW_DEF
+{
+ const volatile size_t RT_FAR *pu = (const volatile size_t RT_FAR *)pvBitmap;
+ Assert(!(cBits & 31));
+ Assert(!((uintptr_t)pvBitmap & 3));
+
+#if ARCH_BITS > 32
+ /* Deal with misaligned bitmaps (happens all the time via ASMBitNextClear()). */
+ if (!((uintptr_t)pvBitmap & 7) && cBits >= 32)
+ {
+ uint32_t u32 = *(const volatile uint32_t RT_FAR *)pu;
+ if (u32 != 0)
+ {
+ size_t const iBaseBit = ((uintptr_t)pu - (uintptr_t)pvBitmap) * 8;
+ return iBaseBit + ASMBitFirstSetU32(RT_LE2H_U32(u32)) - 1;
+ }
+ pu = (const volatile size_t RT_FAR *)((uintptr_t)pu + sizeof(uint32_t));
+ cBits -= 32;
+ }
+#endif
+
+ /* Main search loop: */
+ while (cBits >= sizeof(size_t) * 8)
+ {
+ size_t u = *pu;
+ if (u == 0)
+ { }
+ else
+ {
+ size_t const iBaseBit = ((uintptr_t)pu - (uintptr_t)pvBitmap) * 8;
+#if ARCH_BITS == 32
+ return iBaseBit + ASMBitFirstSetU32(RT_LE2H_U32(u)) - 1;
+#elif ARCH_BITS == 64
+ return iBaseBit + ASMBitFirstSetU64(RT_LE2H_U64(u)) - 1;
+#else
+# error "ARCH_BITS is not supported"
+#endif
+ }
+
+ pu++;
+ cBits -= sizeof(size_t) * 8;
+ }
+
+#if ARCH_BITS > 32
+ /* Final 32-bit item (unlikely)? */
+ if (cBits < 32)
+ { }
+ else
+ {
+ uint32_t u32 = *(const volatile uint32_t RT_FAR *)pu;
+ if (u32 != 0)
+ {
+ size_t const iBaseBit = ((uintptr_t)pu - (uintptr_t)pvBitmap) * 8;
+ return iBaseBit + ASMBitFirstSetU32(RT_LE2H_U32(u32)) - 1;
+ }
+ }
+#endif
+
+ return -1;
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm
new file mode 100644
index 00000000..c72d70af
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm
@@ -0,0 +1,137 @@
+; $Id: ASMBitFirstSet.asm $
+;; @file
+; IPRT - ASMBitFirstSet().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the first set bit in a bitmap.
+;
+; @returns (32/64:eax, 16:ax+dx) Index of the first zero bit.
+; @returns (32/64:eax, 16:ax+dx) -1 if no set bit was found.
+; @param msc:rcx gcc:rdi pvBitmap Pointer to the bitmap.
+; @param msc:edx gcc:rsi cBits The number of bits in the bitmap. Multiple of 32.
+;
+RT_BEGINPROC ASMBitFirstSet
+ ;
+ ; if (cBits)
+ ; Put cBits in ecx first.
+ ;
+%if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ mov ecx, esi
+ %else
+ xchg rcx, rdx ; rdx=pvDst, ecx=cBits
+ %endif
+%elif ARCH_BITS == 32
+ mov ecx, [esp + 4 + 4]
+%elif ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ mov ecx, [bp + 4 + 4]
+%endif
+ or ecx, ecx
+ jz short .failed
+ ;{
+ push xDI
+
+ ; asm {...}
+%if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ ; rdi = start of scasd - already done
+ %else
+ mov rdi, rdx ; rdi = start of scasd (Note! xchg rdx,rcx above)
+ %endif
+%elif ARCH_BITS == 32
+ mov edi, [esp + 4]
+%elif ARCH_BITS == 16
+ mov ax, [bp + 4 + 2]
+ mov di, [bp + 4]
+ mov es, ax ; es is volatile, no need to save.
+%endif
+ add ecx, 31 ; 32 bit aligned
+ shr ecx, 5 ; number of dwords to scan.
+ mov xDX, xDI ; xDX = saved pvBitmap
+ xor eax, eax
+ repe scasd ; Scan for the first dword with any bit set.
+ je .failed_restore
+
+ ; find the bit in question
+ sub xDI, 4 ; one step back.
+%if ARCH_BITS == 16
+ movzx edi, di
+ mov eax, [es:xDI]
+%else
+ mov eax, [xDI]
+%endif
+ sub xDI, xDX
+ shl edi, 3 ; calc bit offset.
+
+ bsf ecx, eax
+ jz .failed_restore ; race paranoia
+ add ecx, edi
+ mov eax, ecx
+
+ ; return success
+ pop xDI
+%if ARCH_BITS == 16
+ mov edx, eax
+ shr edx, 16
+ leave
+%endif
+ ret
+
+ ; failure
+ ;}
+ ;return -1;
+.failed_restore:
+ pop xDI
+.failed:
+%if ARCH_BITS != 16
+ mov eax, 0ffffffffh
+%else
+ mov ax, 0ffffh
+ mov dx, ax
+ leave
+%endif
+ ret
+ENDPROC ASMBitFirstSet
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm
new file mode 100644
index 00000000..d60fa120
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm
@@ -0,0 +1,103 @@
+; $Id: ASMBitFirstSetU16.asm $
+;; @file
+; IPRT - ASMBitFirstSetU16().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the first bit which is set in the given 16-bit integer.
+;
+; Bits are numbered from 1 (least significant) to 16.
+;
+; @returns (xAX) index [1..16] of the first set bit.
+; @returns (xAX) 0 if all bits are cleared.
+; @param msc:cx gcc:di x86:stack u16 Integer to search for set bits.
+;
+; @cproto DECLASM(unsigned) ASMBitFirstSetU16(uint16_t u16);
+;
+RT_BEGINPROC ASMBitFirstSetU16
+%if ARCH_BITS == 16
+ CPU 8086
+ push bp
+ mov bp, sp
+
+ ; 16:0
+ mov ax, 1
+ mov cx, [bp + 2 + 2 + 0]
+ test cx, cx
+ jz .return_zero
+
+ ; find the bit that was set.
+.next_bit:
+ shr cx, 1
+ jc .return
+ inc ax
+ jmp .next_bit
+
+.return_zero:
+ xor ax, ax
+.return:
+ pop bp
+ ret
+
+%else
+ xor eax, eax
+ %if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ bsf ax, si
+ %else
+ bsf ax, cx
+ %endif
+ %elif ARCH_BITS == 32
+ bsf ax, word [esp + 4]
+ %else
+ %error "Missing or invalid ARCH_BITS."
+ %endif
+ jz .return_zero
+ inc eax
+.return:
+ ret
+.return_zero:
+ xor eax, eax
+ ret
+%endif
+ENDPROC ASMBitFirstSetU16
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm
new file mode 100644
index 00000000..f394201d
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm
@@ -0,0 +1,107 @@
+; $Id: ASMBitFirstSetU32.asm $
+;; @file
+; IPRT - ASMBitFirstSetU32().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the first bit which is set in the given 32-bit integer.
+;
+; Bits are numbered from 1 (least significant) to 32.
+;
+; @returns (xAX) index [1..32] of the first set bit.
+; @returns (xAX) 0 if all bits are cleared.
+; @param msc:ecx gcc:edi x86:stack u32 Integer to search for set bits.
+;
+; @cproto DECLASM(unsigned) ASMBitFirstSetU32(uint32_t u32);
+;
+RT_BEGINPROC ASMBitFirstSetU32
+%if ARCH_BITS == 16
+ CPU 8086
+ push bp
+ mov bp, sp
+
+ ; 15:0
+ mov ax, 1
+ mov cx, [bp + 2 + 2 + 0]
+ test cx, cx
+ jnz .next_bit
+
+ ; 31:16
+ mov al, 16
+ or cx, [bp + 2 + 2 + 2]
+ jz .return_zero
+
+ ; find the bit that was set.
+.next_bit:
+ shr cx, 1
+ jc .return
+ inc ax
+ jmp .next_bit
+
+.return_zero:
+ xor ax, ax
+.return:
+ pop bp
+ ret
+
+%else
+ %if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ bsf eax, esi
+ %else
+ bsf eax, ecx
+ %endif
+ %elif ARCH_BITS == 32
+ bsf eax, dword [esp + 4]
+ %else
+ %error "Missing or invalid ARCH_BITS."
+ %endif
+ jz .return_zero
+ inc eax
+.return:
+ ret
+.return_zero:
+ xor eax, eax
+ ret
+%endif
+ENDPROC ASMBitFirstSetU32
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm
new file mode 100644
index 00000000..aee25890
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm
@@ -0,0 +1,126 @@
+; $Id: ASMBitFirstSetU64.asm $
+;; @file
+; IPRT - ASMBitFirstSetU64().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the first bit which is set in the given 64-bit integer.
+;
+; Bits are numbered from 1 (least significant) to 64.
+;
+; @returns (xAX) index [1..64] of the first set bit.
+; @returns (xAX) 0 if all bits are cleared.
+; @param msc:rcx gcc:rdi x86:stack u64 Integer to search for set bits.
+;
+; @cproto DECLASM(unsigned) ASMBitFirstSetU64(uint64_t u64);
+;
+RT_BEGINPROC ASMBitFirstSetU64
+%if ARCH_BITS == 16
+ CPU 8086
+ push bp
+ mov bp, sp
+
+ ; 15:0
+ mov ax, 1
+ mov cx, [bp + 2 + 2 + 0]
+ test cx, cx
+ jnz .next_bit
+
+ ; 31:16
+ mov al, 16
+ or cx, [bp + 2 + 2 + 2]
+ jnz .next_bit
+
+ ; 47:32
+ mov al, 32
+ or cx, [bp + 2 + 2 + 4]
+ jnz .next_bit
+
+ ; 63:48
+ mov al, 48
+ or cx, [bp + 2 + 2 + 6]
+ jz .return_zero
+
+ ; find the bit that was set.
+.next_bit:
+ shr cx, 1
+ jc .return
+ inc ax
+ jmp .next_bit
+
+.return_zero:
+ xor ax, ax
+.return:
+ pop bp
+ ret
+
+%else
+ %if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ bsf rax, rsi
+ %else
+ bsf rax, rcx
+ %endif
+ jz .return_zero
+ inc eax
+ ret
+
+ %elif ARCH_BITS == 32
+ ; Check the first dword then the 2nd one.
+ bsf eax, dword [esp + 4 + 0]
+ jnz .check_2nd_dword
+ inc eax
+ ret
+.check_2nd_dword:
+ bsf eax, dword [esp + 4 + 4]
+ jz .return_zero
+ add eax, 32
+ ret
+ %else
+ %error "Missing or invalid ARCH_BITS."
+ %endif
+.return_zero:
+ xor eax, eax
+ ret
+%endif
+ENDPROC ASMBitFirstSetU64
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm b/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm
new file mode 100644
index 00000000..446e1fb5
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm
@@ -0,0 +1,101 @@
+; $Id: ASMBitLastSetU16.asm $
+;; @file
+; IPRT - ASMBitLastSetU16().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the last bit which is set in the given 16-bit integer.
+;
+; Bits are numbered from 1 (least significant) to 16.
+;
+; @returns (xAX) index [1..16] of the last set bit.
+; @returns (xAX) 0 if all bits are cleared.
+; @param msc:cx gcc:di x86:stack u16 Integer to search for set bits.
+;
+; @cproto DECLASM(unsigned) ASMBitLastSetU16(uint32_t u16);
+;
+RT_BEGINPROC ASMBitLastSetU16
+%if ARCH_BITS == 16
+ CPU 8086
+ push bp
+ mov bp, sp
+
+ mov cx, [bp + 2 + 2]
+ test cx, cx ; check if zero (eliminates checking dec ax result)
+ jz .return_zero
+
+ mov ax, 16
+.next_bit:
+ shl cx, 1
+ jc .return
+ dec ax
+ jmp .next_bit
+
+.return_zero:
+ xor ax, ax
+.return:
+ pop bp
+ ret
+
+%else
+ xor eax, eax
+ %if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ bsr ax, si
+ %else
+ bsr ax, cx
+ %endif
+ %elif ARCH_BITS == 32
+ bsr ax, word [esp + 4]
+ %else
+ %error "Missing or invalid ARCH_BITS."
+ %endif
+ jz .return_zero
+ inc eax
+.return:
+ ret
+.return_zero:
+ xor eax, eax
+ ret
+%endif
+ENDPROC ASMBitLastSetU16
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm b/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm
new file mode 100644
index 00000000..fb3f5ab9
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm
@@ -0,0 +1,107 @@
+; $Id: ASMBitLastSetU32.asm $
+;; @file
+; IPRT - ASMBitLastSetU32().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the last bit which is set in the given 32-bit integer.
+;
+; Bits are numbered from 1 (least significant) to 32.
+;
+; @returns (xAX) index [1..32] of the last set bit.
+; @returns (xAX) 0 if all bits are cleared.
+; @param msc:ecx gcc:edi x86:stack u32 Integer to search for set bits.
+;
+; @cproto DECLASM(unsigned) ASMBitLastSetU32(uint32_t u32);
+;
+RT_BEGINPROC ASMBitLastSetU32
+%if ARCH_BITS == 16
+ CPU 8086
+ push bp
+ mov bp, sp
+
+ ; high word.
+ mov ax, 32
+ mov cx, [bp + 2 + 4]
+ test cx, cx
+ jnz .next_bit
+
+ ; low word.
+ mov al, 16
+ or cx, [bp + 2 + 2]
+ jz .return_zero
+
+ ; find the bit that was set.
+.next_bit:
+ shl cx, 1
+ jc .return
+ dec ax
+ jmp .next_bit
+
+.return_zero:
+ xor ax, ax
+.return:
+ pop bp
+ ret
+
+%else
+ %if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ bsr eax, esi
+ %else
+ bsr eax, ecx
+ %endif
+ %elif ARCH_BITS == 32
+ bsr eax, dword [esp + 4]
+ %else
+ %error "Missing or invalid ARCH_BITS."
+ %endif
+ jz .return_zero
+ inc eax
+.return:
+ ret
+.return_zero:
+ xor eax, eax
+ ret
+%endif
+ENDPROC ASMBitLastSetU32
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm b/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm
new file mode 100644
index 00000000..2d6cb38e
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm
@@ -0,0 +1,130 @@
+; $Id: ASMBitLastSetU64.asm $
+;; @file
+; IPRT - ASMBitLastSetU64().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the last bit which is set in the given 64-bit integer.
+;
+; Bits are numbered from 1 (least significant) to 64.
+;
+; @returns (xAX) index [1..64] of the last set bit.
+; @returns (xAX) 0 if all bits are cleared.
+; @param msc:rcx gcc:rdi x86:stack u64 Integer to search for set bits.
+;
+; @cproto DECLASM(unsigned) ASMBitLastSetU64(uint64_t u64);
+;
+RT_BEGINPROC ASMBitLastSetU64
+%if ARCH_BITS == 16
+ CPU 8086
+ push bp
+ mov bp, sp
+
+ ; 63:48
+ mov ax, 64
+ mov cx, [bp + 2 + 2 + 6]
+ test cx, cx
+ jnz .next_bit
+
+ ; 47:32
+ mov al, 48
+ or cx, [bp + 2 + 2 + 4]
+ jnz .next_bit
+
+ ; 31:16
+ mov al, 32
+ or cx, [bp + 2 + 2 + 2]
+ jnz .next_bit
+
+ ; 16:0
+ mov al, 16
+ or cx, [bp + 2 + 2 + 0]
+ jz .return_zero
+
+ ; find the bit that was set.
+.next_bit:
+ shl cx, 1
+ jc .return
+ dec ax
+ jmp .next_bit
+
+.return_zero:
+ xor ax, ax
+.return:
+ pop bp
+ ret
+
+%else
+ %if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ bsr rax, rsi
+ %else
+ bsr rax, rcx
+ %endif
+ jz .return_zero
+ inc eax
+.return:
+ ret
+
+ %elif ARCH_BITS == 32
+ ; Check the 2nd dword then the first one.
+ bsr eax, dword [esp + 4 + 4]
+ jz .check_1st_dword
+ add eax, 32
+ ret
+
+.check_1st_dword:
+ bsr eax, dword [esp + 4 + 0]
+ jz .return_zero
+ inc eax
+ ret
+
+ %else
+ %error "Missing or invalid ARCH_BITS."
+ %endif
+
+.return_zero:
+ xor eax, eax
+ ret
+%endif
+ENDPROC ASMBitLastSetU64
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitNextClear-generic.cpp b/src/VBox/Runtime/common/asm/ASMBitNextClear-generic.cpp
new file mode 100644
index 00000000..0d3280af
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitNextClear-generic.cpp
@@ -0,0 +1,79 @@
+/* $Id: ASMBitNextClear-generic.cpp $ */
+/** @file
+ * IPRT - ASMBitNextClear - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+
+
+RTDECL(int) ASMBitNextClear(const volatile void RT_FAR *pvBitmap, uint32_t cBits, uint32_t iBitPrev) RT_NOTHROW_DEF
+{
+ const volatile uint32_t RT_FAR *pau32Bitmap = (const volatile uint32_t RT_FAR *)pvBitmap;
+ int iBit = ++iBitPrev & 31;
+ Assert(!(cBits & 31));
+ Assert(!((uintptr_t)pvBitmap & 3));
+ if (iBit)
+ {
+ /*
+ * Inspect the 32-bit word containing the unaligned bit.
+ */
+ uint32_t u32 = ~RT_LE2H_U32(pau32Bitmap[iBitPrev / 32]) >> iBit;
+ if (u32)
+ return iBitPrev + ASMBitFirstSetU32(u32) - 1;
+
+ /*
+ * Skip ahead and see if there is anything left to search.
+ */
+ iBitPrev |= 31;
+ iBitPrev++;
+ if (cBits <= (uint32_t)iBitPrev)
+ return -1;
+ }
+
+ /*
+ * 32-bit aligned search, let ASMBitFirstClear do the dirty work.
+ */
+ iBit = ASMBitFirstClear(&pau32Bitmap[iBitPrev / 32], cBits - iBitPrev);
+ if (iBit >= 0)
+ iBit += iBitPrev;
+ return iBit;
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitNextClear.asm b/src/VBox/Runtime/common/asm/ASMBitNextClear.asm
new file mode 100644
index 00000000..f6dcb3f4
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitNextClear.asm
@@ -0,0 +1,183 @@
+; $Id: ASMBitNextClear.asm $
+;; @file
+; IPRT - ASMBitNextClear().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the next clear bit in a bitmap.
+;
+; @returns (32/64:eax, 16:ax+dx) Index of the first zero bit.
+; @returns (32/64:eax, 16:ax+dx) -1 if no clear bit was found.
+; @param msc:rcx gcc:rdi pvBitmap Pointer to the bitmap.
+; @param msc:edx gcc:rsi cBits The number of bits in the bitmap. Multiple of 32.
+; @param msc:r8d gcc:rcx iBitPrev The previous bit, start searching after it.
+;
+; @remarks Not quite sure how much sense it makes to do this in assembly, but
+; it started out with the ASMBit* API, so that's why we still have it.
+;
+RT_BEGINPROC ASMBitNextClear
+%if ARCH_BITS == 16
+ push bp
+ mov bp, sp
+%endif
+ push xDI
+
+ ;
+ ; Align input registers: rdi=pvBitmap, ecx=iPrevBit
+ ;
+%if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ ; ecx = iBitPrev param, rdi=pvBitmap param.
+ %else
+ mov rdi, rcx ; rdi=pvBits
+ mov ecx, r8d ; ecx=iPrevBit
+ mov r8d, edx ; r8d=cBits (saved for .scan_dwords)
+ %endif
+ mov r9, rdi ; Save rdi for bit calculation.
+%elif ARCH_BITS == 32
+ mov edi, [esp + 8] ; edi=pvBits
+ mov ecx, [esp + 8 + 8] ; ecx=iPrevBit
+%elif ARCH_BITS == 16
+ les di, [bp + 4] ; es:di=pvBits
+ mov ecx, [bp + 4 + 8] ; ecx=iPrevBit
+%endif
+
+ ;
+ ; If iPrevBit and iPrevBit + 1 are in the same dword, inspect it for further bits.
+ ;
+ inc ecx
+ mov eax, ecx
+ shr eax, 5
+ shl eax, 2 ; eax=byte offset into bitmap of current dword.
+ add xDI, xAX ; xDI=current dword address (of iPrevBit + 1).
+ and ecx, 31
+ jz .scan_dwords
+
+%if ARCH_BITS == 16
+ mov edx, [es:di]
+%else
+ mov edx, [xDI]
+%endif
+ not edx ; edx = inverted current dword
+ shr edx, cl ; Shift out bits that we have searched.
+ jz .next_dword ; If zero, nothing to find. Go rep scasd.
+ shl edx, cl ; Shift it back so bsf will return the right index.
+
+ bsf edx, edx ; edx=index of first clear bit
+
+ shl eax, 3 ; Turn eax back into a bit offset of the current dword.
+ add eax, edx ; eax=bit offset
+
+.return:
+ pop xDI
+%if ARCH_BITS == 16
+ mov edx, eax
+ shr edx, 16
+ leave
+%endif
+ ret
+
+ ;
+ ; Do dword scan.
+ ;
+
+ ; Skip empty dword.
+.next_dword:
+ add xDI, 4 ; Skip the empty dword.
+ add eax, 4
+
+.scan_dwords:
+ ; First load and align bit count.
+%if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ mov ecx, esi
+ %else
+ mov ecx, r8d
+ %endif
+%elif ARCH_BITS == 32
+ mov ecx, [esp + 8 + 4]
+%elif ARCH_BITS == 16
+ mov ecx, [bp + 4 + 4]
+%endif
+ add ecx, 31
+ shr ecx, 5 ; ecx=bitmap size in dwords.
+
+ ; Adjust ecx to how many dwords there are left to scan. (eax = current byte offset)
+ shr eax, 2 ; eax=current dword offset.
+ sub ecx, eax
+ jbe .return_failure
+
+ ; Do the scanning.
+ cld
+ mov eax, 0ffffffffh
+ repe scasd
+ je .return_failure
+
+ ; Find the bit in question.
+ sub xDI, 4 ; One step back.
+%if ARCH_BITS == 16
+ movzx edi, di
+ xor eax, [es:xDI]
+%else
+ xor eax, [xDI]
+%endif
+ bsf eax, eax
+ jz .return_failure ; race paranoia
+
+ ; Calc the bit offset.
+%if ARCH_BITS == 16
+ sub di, [bp + 4]
+ movzx edi, di
+%elif ARCH_BITS == 32
+ sub xDI, [esp + 8]
+%elif ARCH_BITS == 64
+ sub xDI, r9
+%endif
+ shl edi, 3 ; edi=bit offset of current dword.
+ add eax, edi
+ jmp .return
+
+.return_failure:
+ mov eax, 0ffffffffh
+ jmp .return
+ENDPROC ASMBitNextClear
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitNextSet-generic.cpp b/src/VBox/Runtime/common/asm/ASMBitNextSet-generic.cpp
new file mode 100644
index 00000000..8233de1b
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitNextSet-generic.cpp
@@ -0,0 +1,79 @@
+/* $Id: ASMBitNextSet-generic.cpp $ */
+/** @file
+ * IPRT - ASMBitNextSet - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+
+
+RTDECL(int) ASMBitNextSet(const volatile void RT_FAR *pvBitmap, uint32_t cBits, uint32_t iBitPrev) RT_NOTHROW_DEF
+{
+ const volatile uint32_t RT_FAR *pau32Bitmap = (const volatile uint32_t RT_FAR *)pvBitmap;
+ int iBit = ++iBitPrev & 31;
+ Assert(!(cBits & 31));
+ Assert(!((uintptr_t)pvBitmap & 3));
+ if (iBit)
+ {
+ /*
+ * Inspect the 32-bit word containing the unaligned bit.
+ */
+ uint32_t u32 = RT_LE2H_U32(pau32Bitmap[iBitPrev / 32]) >> iBit;
+ if (u32)
+ return iBitPrev + ASMBitFirstSetU32(u32) - 1;
+
+ /*
+ * Skip ahead and see if there is anything left to search.
+ */
+ iBitPrev |= 31;
+ iBitPrev++;
+ if (cBits <= (uint32_t)iBitPrev)
+ return -1;
+ }
+
+ /*
+ * 32-bit aligned search, let ASMBitFirstSet do the dirty work.
+ */
+ iBit = ASMBitFirstSet(&pau32Bitmap[iBitPrev / 32], cBits - iBitPrev);
+ if (iBit >= 0)
+ iBit += iBitPrev;
+ return iBit;
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMBitNextSet.asm b/src/VBox/Runtime/common/asm/ASMBitNextSet.asm
new file mode 100644
index 00000000..c2ab4e75
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMBitNextSet.asm
@@ -0,0 +1,183 @@
+; $Id: ASMBitNextSet.asm $
+;; @file
+; IPRT - ASMBitNextSet().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Finds the next set bit in a bitmap.
+;
+; @returns (32/64:eax, 16:ax+dx) Index of the first zero bit.
+; @returns (32/64:eax, 16:ax+dx) -1 if no set bit was found.
+; @param msc:rcx gcc:rdi pvBitmap Pointer to the bitmap.
+; @param msc:edx gcc:rsi cBits The number of bits in the bitmap. Multiple of 32.
+; @param msc:r8d gcc:rcx iBitPrev The previous bit, start searching after it.
+;
+; @remarks Not quite sure how much sense it makes to do this in assembly, but
+; it started out with the ASMBit* API, so that's why we still have it.
+;
+RT_BEGINPROC ASMBitNextSet
+%if ARCH_BITS == 16
+ push bp
+ mov bp, sp
+%endif
+ push xDI
+
+ ;
+ ; Align input registers: rdi=pvBitmap, ecx=iPrevBit
+ ;
+%if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ ; ecx = iBitPrev param, rdi=pvBitmap param.
+ %else
+ mov rdi, rcx ; rdi=pvBits
+ mov ecx, r8d ; ecx=iPrevBit
+ mov r8d, edx ; r8d=cBits (saved for .scan_dwords)
+ %endif
+ mov r9, rdi ; Save rdi for bit calculation.
+%elif ARCH_BITS == 32
+ mov edi, [esp + 8] ; edi=pvBits
+ mov ecx, [esp + 8 + 8] ; edx=iPrevBit
+%elif ARCH_BITS == 16
+ mov ax, [bp + 4 + 2]
+ mov es, ax
+ mov di, [bp + 4] ; es:di=pvBits
+ mov ecx, [bp + 4 + 8] ; edx=iPrevBit
+%endif
+
+ ;
+ ; If iPrevBit and iPrevBit + 1 are in the same dword, inspect it for further bits.
+ ;
+ inc ecx
+ mov eax, ecx
+ shr eax, 5
+ shl eax, 2 ; eax=byte offset into bitmap of current dword.
+ add xDI, xAX ; xDI=current dword address (of iPrevBit + 1).
+ and ecx, 31
+ jz .scan_dwords
+
+%if ARCH_BITS == 16
+ mov edx, [es:di] ; edx = current dword
+%else
+ mov edx, [xDI] ; edx = current dword
+%endif
+ shr edx, cl ; Shift out bits that we have searched.
+ jz .next_dword ; If zero, nothing to find. Go rep scasd.
+ shl edx, cl ; Shift it back so bsf will return the right index.
+
+ bsf edx, edx ; edx=index of first set bit
+
+ shl eax, 3 ; Turn eax back into a bit offset of the current dword.
+ add eax, edx ; eax=bit offset
+
+.return:
+ pop xDI
+%if ARCH_BITS == 16
+ mov edx, eax
+ shr edx, 16
+ leave
+%endif
+ ret
+
+ ;
+ ; Do dword scan.
+ ;
+
+ ; Skip empty dword.
+.next_dword:
+ add xDI, 4 ; Skip the empty dword.
+ add eax, 4
+
+.scan_dwords:
+ ; First load and align bit count.
+%if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ mov ecx, esi
+ %else
+ mov ecx, r8d
+ %endif
+%elif ARCH_BITS == 32
+ mov ecx, [esp + 8 + 4]
+%elif ARCH_BITS == 16
+ mov ecx, [bp + 4 + 4]
+%endif
+ add ecx, 31
+ shr ecx, 5 ; ecx=bitmap size in dwords.
+
+ ; Adjust ecx to how many dwords there are left to scan. (eax = current byte offset)
+ shr eax, 2 ; eax=current dword offset.
+ sub ecx, eax
+ jbe .return_failure
+
+ ; Do the scanning.
+ xor eax, eax
+ repe scasd
+ je .return_failure
+
+ ; Find the bit in question.
+ sub xDI, 4 ; One step back.
+%if ARCH_BITS == 16
+ movzx edi, di
+ mov eax, [es:xDI]
+%else
+ mov eax, [xDI]
+%endif
+ bsf eax, eax
+ jz .return_failure ; race paranoia
+
+ ; Calc the bit offset.
+%if ARCH_BITS == 16
+ sub di, [bp + 4]
+ movzx edi, di
+%elif ARCH_BITS == 32
+ sub xDI, [esp + 4]
+%elif ARCH_BITS == 64
+ sub xDI, r9
+%endif
+ shl edi, 3 ; edi=bit offset of current dword.
+ add eax, edi
+ jmp .return
+
+.return_failure:
+ mov eax, 0ffffffffh
+ jmp .return
+ENDPROC ASMBitNextSet
+
diff --git a/src/VBox/Runtime/common/asm/ASMCpuId.asm b/src/VBox/Runtime/common/asm/ASMCpuId.asm
new file mode 100644
index 00000000..62c31426
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMCpuId.asm
@@ -0,0 +1,121 @@
+; $Id: ASMCpuId.asm $
+;; @file
+; IPRT - ASMCpuIdExSlow().
+;
+
+;
+; Copyright (C) 2012-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; CPUID with EAX input, returning ALL output registers (no NULL checking).
+;
+; @param uOperator 8086:bp+4 x86:ebp+8 gcc:rdi msc:rcx
+; @param pvEAX 8086:bp+8 x86:ebp+0c gcc:rsi msc:rdx
+; @param pvEBX 8086:bp+0c x86:ebp+10 gcc:rdx msc:r8
+; @param pvECX 8086:bp+10 x86:ebp+14 gcc:rcx msc:r9
+; @param pvEDX 8086:bp+14 x86:ebp+18 gcc:r8 msc:rbp+30h
+;
+; DECLASM(void) ASMCpuId(uint32_t uOperator, void *pvEAX, void *pvEBX, void *pvECX, void *pvEDX);
+;
+RT_BEGINPROC ASMCpuId
+ push xBP
+ mov xBP, xSP
+ push xBX
+
+%ifdef ASM_CALL64_MSC
+ %if ARCH_BITS != 64
+ %error ARCH_BITS mismatch?
+ %endif
+ mov eax, ecx
+ mov r10, rdx
+ cpuid
+ mov [r10], eax
+ mov [r8], ebx
+ mov [r9], ecx
+ mov r10, [rbp+30h]
+ mov [r10], edx
+
+%elifdef ASM_CALL64_GCC
+ mov eax, edi
+ mov r10, rdx
+ mov r11, rcx
+ cpuid
+ mov [rsi], eax
+ mov [r10], ebx
+ mov [r11], ecx
+ mov [r8], edx
+
+%elif ARCH_BITS == 32
+ mov eax, [xBP + 08h]
+ cpuid
+ push edx
+ mov edx, [xBP + 0ch]
+ mov [edx], eax
+ mov edx, [xBP + 10h]
+ mov [edx], ebx
+ mov edx, [xBP + 14h]
+ mov [edx], ecx
+ mov edx, [xBP + 18h]
+ pop dword [edx]
+
+%elif ARCH_BITS == 16
+ push es
+ push di
+
+ mov eax, [xBP + 04h]
+ cpuid
+ les di, [xBP + 08h]
+ mov [di], eax
+ les di, [xBP + 0ch]
+ mov [di], ebx
+ les di, [xBP + 10h]
+ mov [di], ecx
+ les di, [xBP + 14h]
+ mov [di], edx
+
+ pop di
+ pop es
+%else
+ %error unsupported arch
+%endif
+
+ pop xBX
+ leave
+ ret
+ENDPROC ASMCpuId
+
diff --git a/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm b/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm
new file mode 100644
index 00000000..aab68727
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm
@@ -0,0 +1,181 @@
+; $Id: ASMCpuIdExSlow.asm $
+;; @file
+; IPRT - ASMCpuIdExSlow().
+;
+
+;
+; Copyright (C) 2012-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; CPUID with EAX and ECX inputs, returning ALL output registers.
+;
+; @param uOperator x86:ebp+8 gcc:rdi msc:rcx
+; @param uInitEBX x86:ebp+c gcc:rsi msc:rdx
+; @param uInitECX x86:ebp+10 gcc:rdx msc:r8
+; @param uInitEDX x86:ebp+14 gcc:rcx msc:r9
+; @param pvEAX x86:ebp+18 gcc:r8 msc:rbp+30h
+; @param pvEBX x86:ebp+1c gcc:r9 msc:rbp+38h
+; @param pvECX x86:ebp+20 gcc:rbp+10h msc:rbp+40h
+; @param pvEDX x86:ebp+24 gcc:rbp+18h msc:rbp+48h
+;
+; @returns EAX
+;
+RT_BEGINPROC ASMCpuIdExSlow
+ push xBP
+ mov xBP, xSP
+ push xBX
+%if ARCH_BITS == 32
+ push edi
+%elif ARCH_BITS == 16
+ push di
+ push es
+%endif
+
+%ifdef ASM_CALL64_MSC
+ %if ARCH_BITS != 64
+ %error ARCH_BITS mismatch?
+ %endif
+ mov eax, ecx
+ mov ebx, edx
+ mov ecx, r8d
+ mov edx, r9d
+ mov r8, [rbp + 30h]
+ mov r9, [rbp + 38h]
+ mov r10, [rbp + 40h]
+ mov r11, [rbp + 48h]
+%elifdef ASM_CALL64_GCC
+ mov eax, edi
+ mov ebx, esi
+ xchg ecx, edx
+ mov r10, [rbp + 10h]
+ mov r11, [rbp + 18h]
+%elif ARCH_BITS == 32
+ mov eax, [xBP + 08h]
+ mov ebx, [xBP + 0ch]
+ mov ecx, [xBP + 10h]
+ mov edx, [xBP + 14h]
+ mov edi, [xBP + 18h]
+%elif ARCH_BITS == 16
+ mov eax, [xBP + 08h - 4]
+ mov ebx, [xBP + 0ch - 4]
+ mov ecx, [xBP + 10h - 4]
+ mov edx, [xBP + 14h - 4]
+%else
+ %error unsupported arch
+%endif
+
+ cpuid
+
+%ifdef RT_ARCH_AMD64
+ test r8, r8
+ jz .store_ebx
+ mov [r8], eax
+%elif ARCH_BITS == 32
+ test edi, edi
+ jz .store_ebx
+ mov [edi], eax
+%else
+ cmp dword [bp + 18h - 4], 0
+ je .store_ebx
+ les di, [bp + 18h - 4]
+ mov [es:di], eax
+%endif
+.store_ebx:
+
+%ifdef RT_ARCH_AMD64
+ test r9, r9
+ jz .store_ecx
+ mov [r9], ebx
+%elif ARCH_BITS == 32
+ mov edi, [ebp + 1ch]
+ test edi, edi
+ jz .store_ecx
+ mov [edi], ebx
+%else
+ cmp dword [bp + 1ch - 4], 0
+ je .store_ecx
+ les di, [bp + 1ch - 4]
+ mov [es:di], ebx
+%endif
+.store_ecx:
+
+%ifdef RT_ARCH_AMD64
+ test r10, r10
+ jz .store_edx
+ mov [r10], ecx
+%elif ARCH_BITS == 32
+ mov edi, [ebp + 20h]
+ test edi, edi
+ jz .store_edx
+ mov [edi], ecx
+%else
+ cmp dword [bp + 20h - 4], 0
+ je .store_edx
+ les di, [bp + 20h - 4]
+ mov [es:di], ecx
+%endif
+.store_edx:
+
+%ifdef RT_ARCH_AMD64
+ test r11, r11
+ jz .done
+ mov [r11], edx
+%elif ARCH_BITS == 32
+ mov edi, [ebp + 24h]
+ test edi, edi
+ jz .done
+ mov [edi], edx
+%else
+ cmp dword [bp + 24h - 4], 0
+ je .done
+ les di, [bp + 24h - 4]
+ mov [es:di], edx
+%endif
+.done:
+
+%if ARCH_BITS == 32
+ pop edi
+%elif ARCH_BITS == 16
+ pop es
+ pop di
+%endif
+ pop xBX
+ leave
+ ret
+ENDPROC ASMCpuIdExSlow
+
diff --git a/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm b/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm
new file mode 100644
index 00000000..5ddb47de
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm
@@ -0,0 +1,126 @@
+; $Id: ASMCpuId_Idx_ECX.asm $
+;; @file
+; IPRT - ASMCpuId_Idx_ECX().
+;
+
+;
+; Copyright (C) 2012-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; CPUID with EAX and ECX inputs, returning ALL output registers.
+;
+; @param uOperator x86:ebp+8 gcc:rdi msc:rcx
+; @param uIdxECX x86:ebp+c gcc:rsi msc:rdx
+; @param pvEAX x86:ebp+10 gcc:rdx msc:r8
+; @param pvEBX x86:ebp+14 gcc:rcx msc:r9
+; @param pvECX x86:ebp+18 gcc:r8 msc:rsp+28h
+; @param pvEDX x86:ebp+1c gcc:r9 msc:rsp+30h
+;
+; @returns void
+;
+RT_BEGINPROC ASMCpuId_Idx_ECX
+%ifdef RT_ARCH_AMD64
+ mov r10, rbx
+
+ %ifdef ASM_CALL64_MSC
+
+ mov eax, ecx
+ mov ecx, edx
+ xor ebx, ebx
+ xor edx, edx
+
+ cpuid
+
+ mov [r8], eax
+ mov [r9], ebx
+ mov rax, [rsp + 28h]
+ mov rbx, [rsp + 30h]
+ mov [rax], ecx
+ mov [rbx], edx
+
+ %else
+ mov rsi, rdx
+ mov r11, rcx
+ mov eax, edi
+ mov ecx, esi
+ xor ebx, ebx
+ xor edx, edx
+
+ cpuid
+
+ mov [rsi], eax
+ mov [r11], ebx
+ mov [r8], ecx
+ mov [r9], edx
+
+ %endif
+
+ mov rbx, r10
+ ret
+
+%elifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push ebx
+ push edi
+
+ xor edx, edx
+ xor ebx, ebx
+ mov eax, [ebp + 08h]
+ mov ecx, [ebp + 0ch]
+
+ cpuid
+
+ mov edi, [ebp + 10h]
+ mov [edi], eax
+ mov edi, [ebp + 14h]
+ mov [edi], ebx
+ mov edi, [ebp + 18h]
+ mov [edi], ecx
+ mov edi, [ebp + 1ch]
+ mov [edi], edx
+
+ pop edi
+ pop ebx
+ leave
+ ret
+%else
+ %error unsupported arch
+%endif
+ENDPROC ASMCpuId_Idx_ECX
+
diff --git a/src/VBox/Runtime/common/asm/ASMFxRstor.asm b/src/VBox/Runtime/common/asm/ASMFxRstor.asm
new file mode 100644
index 00000000..cb492048
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMFxRstor.asm
@@ -0,0 +1,74 @@
+; $Id: ASMFxRstor.asm $
+;; @file
+; IPRT - ASMFxRstor().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Loads extended CPU state.
+; @param pFxState Pointer to the FXRSTOR state area.
+; msc=rcx, gcc=rdi, x86=[esp+4]
+;
+RT_BEGINPROC ASMFxRstor
+SEH64_END_PROLOGUE
+%ifdef ASM_CALL64_MSC
+ o64 fxrstor [rcx]
+%elifdef ASM_CALL64_GCC
+ o64 fxrstor [rdi]
+%elif ARCH_BITS == 32
+ mov ecx, [esp + 4]
+ fxrstor [ecx]
+%elif ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ push es
+ push bx
+ les bx, [bp + 4]
+ fxrstor [es:bx]
+ pop bx
+ pop es
+ pop bp
+%else
+ %error "Undefined arch?"
+%endif
+ ret
+ENDPROC ASMFxRstor
+
diff --git a/src/VBox/Runtime/common/asm/ASMFxSave.asm b/src/VBox/Runtime/common/asm/ASMFxSave.asm
new file mode 100644
index 00000000..e2298ed8
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMFxSave.asm
@@ -0,0 +1,74 @@
+; $Id: ASMFxSave.asm $
+;; @file
+; IPRT - ASMFxSave().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Saves extended CPU state.
+; @param pFxState Pointer to the XSAVE state area.
+; msc=rcx, gcc=rdi, x86=[esp+4]
+;
+RT_BEGINPROC ASMFxSave
+SEH64_END_PROLOGUE
+%ifdef ASM_CALL64_MSC
+ o64 fxsave [rcx]
+%elifdef ASM_CALL64_GCC
+ o64 fxsave [rdi]
+%elif ARCH_BITS == 32
+ mov ecx, [esp + 4]
+ fxsave [ecx]
+%elif ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ push es
+ push bx
+ les bx, [bp + 4]
+ fxsave [es:bx]
+ pop bx
+ pop es
+ pop bp
+%else
+ %error "Undefined arch?"
+%endif
+ ret
+ENDPROC ASMFxSave
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetFSBase.asm b/src/VBox/Runtime/common/asm/ASMGetFSBase.asm
new file mode 100644
index 00000000..e565fc2a
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetFSBase.asm
@@ -0,0 +1,54 @@
+; $Id: ASMGetFSBase.asm $
+;; @file
+; IPRT - ASMGetFSBase().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Get the FS base register.
+; @returns FS base
+;
+RT_BEGINPROC ASMGetFSBase
+ SEH64_END_PROLOGUE
+ rdfsbase rax
+ ret
+ENDPROC ASMGetFSBase
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetFlags.asm b/src/VBox/Runtime/common/asm/ASMGetFlags.asm
new file mode 100644
index 00000000..1db37859
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetFlags.asm
@@ -0,0 +1,53 @@
+; $Id: ASMGetFlags.asm $
+;; @file
+; IPRT - ASMGetFlags().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+RT_BEGINPROC ASMGetFlags
+%if ARCH_BITS == 32
+ pushfd
+%else
+ pushf
+%endif
+ pop xAX
+ ret
+ENDPROC ASMGetFlags
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetGDTR.asm b/src/VBox/Runtime/common/asm/ASMGetGDTR.asm
new file mode 100644
index 00000000..30593f9f
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetGDTR.asm
@@ -0,0 +1,62 @@
+; $Id: ASMGetGDTR.asm $
+;; @file
+; IPRT - ASMGetGDTR().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Gets the content of the GDTR CPU register.
+; @param pGdtr Where to store the GDTR contents.
+; msc=rcx, gcc=rdi, x86=[esp+4]
+;
+RT_BEGINPROC ASMGetGDTR
+%ifdef ASM_CALL64_MSC
+ mov rax, rcx
+%elifdef ASM_CALL64_GCC
+ mov rax, rdi
+%elifdef RT_ARCH_X86
+ mov eax, [esp + 4]
+%else
+ %error "Undefined arch?"
+%endif
+ sgdt [xAX]
+ ret
+ENDPROC ASMGetGDTR
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetGSBase.asm b/src/VBox/Runtime/common/asm/ASMGetGSBase.asm
new file mode 100644
index 00000000..79080c92
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetGSBase.asm
@@ -0,0 +1,54 @@
+; $Id: ASMGetGSBase.asm $
+;; @file
+; IPRT - ASMGetGSBase().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Get the GS base register.
+; @returns GS base
+;
+RT_BEGINPROC ASMGetGSBase
+ SEH64_END_PROLOGUE
+ rdgsbase rax
+ ret
+ENDPROC ASMGetGSBase
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetIDTR.asm b/src/VBox/Runtime/common/asm/ASMGetIDTR.asm
new file mode 100644
index 00000000..0c3618b8
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetIDTR.asm
@@ -0,0 +1,62 @@
+; $Id: ASMGetIDTR.asm $
+;; @file
+; IPRT - ASMGetIDTR().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Gets the content of the IDTR CPU register.
+; @param pIdtr Where to store the IDTR contents.
+; msc=rcx, gcc=rdi, x86=[esp+4]
+;
+RT_BEGINPROC ASMGetIDTR
+%ifdef ASM_CALL64_MSC
+ mov rax, rcx
+%elifdef ASM_CALL64_GCC
+ mov rax, rdi
+%elifdef RT_ARCH_X86
+ mov eax, [esp + 4]
+%else
+ %error "Undefined arch?"
+%endif
+ sidt [xAX]
+ ret
+ENDPROC ASMGetIDTR
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm b/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm
new file mode 100644
index 00000000..7f4fb55e
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm
@@ -0,0 +1,58 @@
+; $Id: ASMGetIdtrLimit.asm $
+;; @file
+; IPRT - ASMGetIdtrLimit().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Gets the content of the IDTR CPU register.
+; @returns IDTR.LIMIT in ax
+;
+RT_BEGINPROC ASMGetIdtrLimit
+ sub xSP, 18h
+ SEH64_ALLOCATE_STACK 18h
+SEH64_END_PROLOGUE
+ sidt [xSP + 6]
+ mov ax, [xSP + 6]
+ add xSP, 18h
+ ret
+ENDPROC ASMGetIdtrLimit
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetLDTR.asm b/src/VBox/Runtime/common/asm/ASMGetLDTR.asm
new file mode 100644
index 00000000..f1dff4f9
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetLDTR.asm
@@ -0,0 +1,53 @@
+; $Id: ASMGetLDTR.asm $
+;; @file
+; IPRT - ASMGetLDTR().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Get the LDTR register.
+; @returns LDTR.
+;
+RT_BEGINPROC ASMGetLDTR
+ sldt ax
+ movzx eax, ax
+ ret
+ENDPROC ASMGetLDTR
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm b/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm
new file mode 100644
index 00000000..bc8dfbef
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm
@@ -0,0 +1,71 @@
+; $Id: ASMGetSegAttr.asm $
+;; @file
+; IPRT - ASMGetSegAttr().
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Get the segment attributes for a selector.
+;
+; @param uSel x86: [ebp + 08h] msc: ecx gcc: edi The selector value.
+; @returns Segment attributes on success or ~0U on failure.
+;
+; @remarks Using ~0U for failure is chosen because valid access rights always
+; have bits 0:7 as 0 (on both Intel & AMD).
+;
+RT_BEGINPROC ASMGetSegAttr
+%ifdef ASM_CALL64_MSC
+ and ecx, 0ffffh
+ lar eax, ecx
+%elifdef ASM_CALL64_GCC
+ and edi, 0ffffh
+ lar eax, edi
+%elifdef RT_ARCH_X86
+ movzx edx, word [esp + 4]
+ lar eax, edx
+%else
+ %error "Which arch is this?"
+%endif
+ jz .return
+ mov eax, 0ffffffffh
+.return:
+ ret
+ENDPROC ASMGetSegAttr
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetTR.asm b/src/VBox/Runtime/common/asm/ASMGetTR.asm
new file mode 100644
index 00000000..e10933f8
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetTR.asm
@@ -0,0 +1,53 @@
+; $Id: ASMGetTR.asm $
+;; @file
+; IPRT - ASMGetTR().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Get the TR register.
+; @returns TR.
+;
+RT_BEGINPROC ASMGetTR
+ str ax
+ movzx eax, ax
+ ret
+ENDPROC ASMGetTR
+
diff --git a/src/VBox/Runtime/common/asm/ASMGetXcr0.asm b/src/VBox/Runtime/common/asm/ASMGetXcr0.asm
new file mode 100644
index 00000000..0da411b9
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMGetXcr0.asm
@@ -0,0 +1,66 @@
+; $Id: ASMGetXcr0.asm $
+;; @file
+; IPRT - ASMGetXcr0().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Gets the content of the XCR0 CPU register.
+; @returns XCR0 value in rax (amd64) / edx:eax (x86).
+;
+RT_BEGINPROC ASMGetXcr0
+SEH64_END_PROLOGUE
+ xor ecx, ecx ; XCR0
+ xgetbv
+%ifdef RT_ARCH_AMD64
+ shl rdx, 32
+ or rax, rdx
+%endif
+%if ARCH_BITS == 16 ; DX:CX:BX:AX - DX=15:0, CX=31:16, BX=47:32, AX=63:48
+ mov ebx, edx
+ mov ecx, eax
+ shr ecx, 16
+ xchg eax, edx
+ shr eax, 16
+%endif
+ ret
+ENDPROC ASMGetXcr0
+
diff --git a/src/VBox/Runtime/common/asm/ASMMemFill32-generic.cpp b/src/VBox/Runtime/common/asm/ASMMemFill32-generic.cpp
new file mode 100644
index 00000000..ecb01ab3
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMMemFill32-generic.cpp
@@ -0,0 +1,74 @@
+/* $Id: ASMMemFill32-generic.cpp $ */
+/** @file
+ * IPRT - ASMMemZeroPage - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2021-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+#include <iprt/string.h>
+#include <iprt/assert.h>
+
+
+RTDECL(void) ASMMemFill32(volatile void RT_FAR *pv, size_t cb, uint32_t u32) RT_NOTHROW_DEF
+{
+ Assert(!(cb & 3));
+ size_t cFills = cb / sizeof(uint32_t);
+ uint32_t *pu32Dst = (uint32_t *)pv;
+
+ while (cFills >= 8)
+ {
+ pu32Dst[0] = u32;
+ pu32Dst[1] = u32;
+ pu32Dst[2] = u32;
+ pu32Dst[3] = u32;
+ pu32Dst[4] = u32;
+ pu32Dst[5] = u32;
+ pu32Dst[6] = u32;
+ pu32Dst[7] = u32;
+ pu32Dst += 8;
+ cFills -= 8;
+ }
+
+ while (cFills > 0)
+ {
+ *pu32Dst++ = u32;
+ cFills -= 1;
+ }
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8-generic.cpp b/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8-generic.cpp
new file mode 100644
index 00000000..d9cfe5e0
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8-generic.cpp
@@ -0,0 +1,55 @@
+/* $Id: ASMMemFirstMismatchingU8-generic.cpp $ */
+/** @file
+ * IPRT - ASMMemFirstMismatchingU8 - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+
+RTDECL(void *) ASMMemFirstMismatchingU8(void const RT_FAR *pv, size_t cb, uint8_t u8) RT_NOTHROW_DEF
+{
+ uint8_t const *pb = (uint8_t const RT_FAR *)pv;
+ for (; cb; cb--, pb++)
+ if (RT_LIKELY(*pb == u8))
+ { /* likely */ }
+ else
+ return (void *)pb;
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm b/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm
new file mode 100644
index 00000000..39db08ce
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm
@@ -0,0 +1,355 @@
+; $Id: ASMMemFirstMismatchingU8.asm $
+;; @file
+; IPRT - ASMMemFirstMismatchingU8().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Variant of ASMMemFirstMismatchingU8 with a fixed @a u8 value.
+; We repeat the prolog and join the generic function.
+;
+RT_BEGINPROC ASMMemFirstNonZero
+ ;
+ ; Prologue.
+ ;
+%if ARCH_BITS != 64
+ push xBP
+ mov xBP, xSP
+ push xDI
+ %if ARCH_BITS == 16
+ push es
+ %endif
+%elifdef ASM_CALL64_MSC
+ mov r9, rdi ; save rdi in r9
+%endif
+SEH64_END_PROLOGUE
+
+ ;
+ ; Normalize input; rdi=pv, rcx=cb, rax=0
+ ;
+ %if ARCH_BITS == 64
+ %ifdef ASM_CALL64_MSC
+ mov rdi, rcx
+ mov rcx, rdx
+ jrcxz RT_CONCAT(NAME(ASMMemFirstMismatchingU8),.return_all_same)
+ xor eax, eax
+ %else
+ mov rcx, rsi
+ jrcxz RT_CONCAT(NAME(ASMMemFirstMismatchingU8),.return_all_same)
+ xor eax, eax
+ %endif
+
+ %elif ARCH_BITS == 32
+ mov ecx, [ebp + 0ch]
+ jecxz RT_CONCAT(NAME(ASMMemFirstMismatchingU8),.return_all_same)
+ mov edi, [ebp + 08h]
+ xor eax, eax
+
+ %elif ARCH_BITS == 16
+ mov cx, [bp + 08h] ; cb
+ jcxz RT_CONCAT(NAME(ASMMemFirstMismatchingU8),.return16_all_same)
+ les di, [bp + 04h] ; pv (far)
+ xor ax, ax
+
+ %else
+ %error "Invalid ARCH_BITS value"
+ %endif
+
+ ;
+ ; Join ASMMemFirstMismatchingU8
+ ;
+ jmp RT_CONCAT(NAME(ASMMemFirstMismatchingU8),.is_all_zero_joining)
+ENDPROC ASMMemFirstNonZero
+
+
+;;
+; Inverted memchr.
+;
+; @returns Pointer to the byte which doesn't equal u8.
+; @returns NULL if all equal to u8.
+;
+; @param msc:rcx gcc:rdi pv Pointer to the memory block.
+; @param msc:rdx gcc:rsi cb Number of bytes in the block. This MUST be aligned on 32-bit!
+; @param msc:r8b gcc:dl u8 The value it's supposed to be filled with.
+;
+; @cproto DECLINLINE(void *) ASMMemFirstMismatchingU8(void const *pv, size_t cb, uint8_t u8)
+;
+RT_BEGINPROC ASMMemFirstMismatchingU8
+ ;
+ ; Prologue.
+ ;
+%if ARCH_BITS != 64
+ push xBP
+ mov xBP, xSP
+ push xDI
+ %if ARCH_BITS == 16
+ push es
+ %endif
+%elifdef ASM_CALL64_MSC
+ mov r9, rdi ; save rdi in r9
+%endif
+SEH64_END_PROLOGUE
+
+%if ARCH_BITS != 16
+ ;
+ ; The 32-bit and 64-bit variant of the code.
+ ;
+
+ ; Normalize input; rdi=pv, rcx=cb, rax=eight-times-u8
+ %if ARCH_BITS == 64
+ %ifdef ASM_CALL64_MSC
+ mov rdi, rcx
+ mov rcx, rdx
+ jrcxz .return_all_same
+ movzx r8d, r8b
+ mov rax, qword 0101010101010101h
+ imul rax, r8
+ %else
+ mov rcx, rsi
+ jrcxz .return_all_same
+ movzx edx, dl
+ mov rax, qword 0101010101010101h
+ imul rax, rdx
+ %endif
+
+ %elif ARCH_BITS == 32
+ mov ecx, [ebp + 0ch]
+ jecxz .return_all_same
+ mov edi, [ebp + 08h]
+ movzx eax, byte [ebp + 10h]
+ mov ah, al
+ movzx edx, ax
+ shl eax, 16
+ or eax, edx
+ %else
+ %error "Invalid ARCH_BITS value"
+ %endif
+
+.is_all_zero_joining:
+ cld
+
+ ; Unaligned pointer? Align it (elsewhere).
+ test edi, xCB - 1
+ jnz .unaligned_pv
+.aligned_pv:
+
+ ; Do the dword/qword scan.
+ mov edx, xCB - 1
+ and edx, ecx ; Remaining bytes for tail scan
+ %if ARCH_BITS == 64
+ shr xCX, 3
+ repe scasq
+ %else
+ shr xCX, 2
+ repe scasd
+ %endif
+ jne .multibyte_mismatch
+
+ ; Prep for tail scan.
+ mov ecx, edx
+
+ ;
+ ; Byte by byte scan.
+ ;
+.byte_by_byte:
+ repe scasb
+ jne .return_xDI
+
+.return_all_same:
+ xor eax, eax
+ %ifdef ASM_CALL64_MSC
+ mov rdi, r9 ; restore rdi
+ %elif ARCH_BITS == 32
+ pop edi
+ leave
+ %endif
+ ret
+
+ ; Return after byte scan mismatch.
+.return_xDI:
+ lea xAX, [xDI - 1]
+ %ifdef ASM_CALL64_MSC
+ mov rdi, r9 ; restore rdi
+ %elif ARCH_BITS == 32
+ pop edi
+ leave
+ %endif
+ ret
+
+ ;
+ ; Multibyte mismatch. We rewind and do a byte scan of the remainder.
+ ; (can't just search the qword as the buffer must be considered volatile).
+ ;
+.multibyte_mismatch:
+ lea xDI, [xDI - xCB]
+ lea xCX, [xCX * xCB + xCB]
+ or ecx, edx
+ jmp .byte_by_byte
+
+ ;
+ ; Unaligned pointer. If it's worth it, align the pointer, but if the
+ ; memory block is too small do the byte scan variant.
+ ;
+.unaligned_pv:
+ cmp xCX, 4*xCB ; 4 steps seems reasonable.
+ jbe .byte_by_byte
+
+ ; Unrolled buffer realignment.
+ %if ARCH_BITS == 64
+ dec xCX
+ scasb
+ jne .return_xDI
+ test edi, xCB - 1
+ jz .aligned_pv
+
+ dec xCX
+ scasb
+ jne .return_xDI
+ test edi, xCB - 1
+ jz .aligned_pv
+
+ dec xCX
+ scasb
+ jne .return_xDI
+ test edi, xCB - 1
+ jz .aligned_pv
+
+ dec xCX
+ scasb
+ jne .return_xDI
+ test edi, xCB - 1
+ jz .aligned_pv
+ %endif
+
+ dec xCX
+ scasb
+ jne .return_xDI
+ test edi, xCB - 1
+ jz .aligned_pv
+
+ dec xCX
+ scasb
+ jne .return_xDI
+ test edi, xCB - 1
+ jz .aligned_pv
+
+ dec xCX
+ scasb
+ jne .return_xDI
+ jmp .aligned_pv
+
+
+%else ; ARCH_BITS == 16
+
+ ;
+ ; The 16-bit variant of the code is a little simpler since we're
+ ; working with two byte words in the 'fast' scan. We also keep
+ ; this separate from the 32-bit/64-bit code because that allows
+ ; avoid a few rex prefixes here and there by using extended
+ ; registers (e??) where we don't care about the whole register.
+ ;
+CPU 8086
+
+ ; Load input parameters.
+ mov cx, [bp + 08h] ; cb
+ jcxz .return16_all_same
+ les di, [bp + 04h] ; pv (far)
+ mov al, [bp + 0ah] ; u8
+ mov ah, al
+
+.is_all_zero_joining:
+ cld
+
+ ; Align the pointer.
+ test di, 1
+ jz .word_scan
+
+ dec cx
+ scasb
+ jne .return16_di
+ jcxz .return16_all_same
+
+ ; Scan word-by-word.
+.word_scan:
+ mov dx, cx
+ shr cx, 1
+ repe scasw
+ jne .word_mismatch
+
+ ; do we have a tail byte?
+ test dl, 1
+ jz .return16_all_same
+ scasb
+ jne .return16_di
+
+.return16_all_same:
+ xor ax, ax
+ xor dx, dx
+.return16:
+ pop es
+ pop di
+ pop bp
+ ret
+
+.word_mismatch:
+ ; back up a word.
+ inc cx
+ sub di, 2
+
+ ; Do byte-by-byte scanning of the rest of the buffer.
+ shl cx, 1
+ mov dl, 1
+ and dl, [bp + 08h] ; cb
+ or cl, dl
+ repe scasb
+ je .return16_all_same
+
+.return16_di:
+ mov ax, di
+ dec ax
+ mov dx, es
+ jmp .return16
+
+%endif ; ARCH_BITS == 16
+ENDPROC ASMMemFirstMismatchingU8
+
diff --git a/src/VBox/Runtime/common/asm/ASMMemFirstNonZero-generic.cpp b/src/VBox/Runtime/common/asm/ASMMemFirstNonZero-generic.cpp
new file mode 100644
index 00000000..6cfd9fef
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMMemFirstNonZero-generic.cpp
@@ -0,0 +1,85 @@
+/* $Id: ASMMemFirstNonZero-generic.cpp $ */
+/** @file
+ * IPRT - ASMMemZeroPage - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+
+RTDECL(void RT_FAR *) ASMMemFirstNonZero(void const RT_FAR *pv, size_t cb) RT_NOTHROW_DEF
+{
+ uint8_t const *pb = (uint8_t const RT_FAR *)pv;
+
+ /*
+ * If we've got a large buffer to search, do it in larger units.
+ */
+ if (cb >= sizeof(size_t) * 2)
+ {
+ /* Align the pointer: */
+ while ((uintptr_t)pb & (sizeof(size_t) - 1))
+ {
+ if (RT_LIKELY(*pb == 0))
+ { /* likely */ }
+ else
+ return (void RT_FAR *)pb;
+ cb--;
+ pb++;
+ }
+
+ /* Scan in size_t sized words: */
+ while ( cb >= sizeof(size_t)
+ && *(size_t *)pb == 0)
+ {
+ pb += sizeof(size_t);
+ cb -= sizeof(size_t);
+ }
+ }
+
+ /*
+ * Search byte by byte.
+ */
+ for (; cb; cb--, pb++)
+ if (RT_LIKELY(*pb == 0))
+ { /* likely */ }
+ else
+ return (void RT_FAR *)pb;
+
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMMemZero32-generic.cpp b/src/VBox/Runtime/common/asm/ASMMemZero32-generic.cpp
new file mode 100644
index 00000000..fa0163da
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMMemZero32-generic.cpp
@@ -0,0 +1,51 @@
+/* $Id: ASMMemZero32-generic.cpp $ */
+/** @file
+ * IPRT - ASMMemZero32 - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2021-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+#include <iprt/string.h>
+
+
+RTDECL(void) ASMMemZero32(volatile void RT_FAR *pv, size_t cb) RT_NOTHROW_DEF
+{
+ memset((void *)pv, 0, cb);
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMMemZeroPage-generic.cpp b/src/VBox/Runtime/common/asm/ASMMemZeroPage-generic.cpp
new file mode 100644
index 00000000..aee19ae7
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMMemZeroPage-generic.cpp
@@ -0,0 +1,51 @@
+/* $Id: ASMMemZeroPage-generic.cpp $ */
+/** @file
+ * IPRT - ASMMemZeroPage - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2021-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+#include <iprt/string.h>
+
+
+RTDECL(void) ASMMemZeroPage(volatile void RT_FAR *pv) RT_NOTHROW_DEF
+{
+ memset((void *)pv, 0, RT_ASM_PAGE_SIZE);
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm b/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm
new file mode 100644
index 00000000..02d86145
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm
@@ -0,0 +1,74 @@
+; $Id: ASMMultU32ByU32DivByU32.asm $
+;; @file
+; IPRT - Assembly Functions, ASMMultU32ByU32DivByU32.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+
+;;
+; Multiple a 32-bit by a 32-bit integer and divide the result by a 32-bit integer
+; using a 64 bit intermediate result.
+;
+; @returns (u32A * u32B) / u32C.
+; @param u32A/ecx/edi The 32-bit value (A).
+; @param u32B/edx/esi The 32-bit value to multiple by A.
+; @param u32C/r8d/edx The 32-bit value to divide A*B by.
+;
+; @cproto DECLASM(uint32_t) ASMMultU32ByU32DivByU32(uint32_t u32A, uint32_t u32B, uint32_t u32C);
+;
+RT_BEGINPROC ASMMultU32ByU32DivByU32
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov eax, ecx
+ mul edx
+ div r8d
+ %else
+ mov eax, edi
+ mov ecx, edx
+ mul esi
+ div ecx
+ %endif
+
+%elifdef RT_ARCH_X86
+ mov eax, [esp + 04h]
+ mul dword [esp + 08h]
+ div dword [esp + 0ch]
+%else
+ %error "Unsupported arch."
+ unsupported arch
+%endif
+
+ ret
+ENDPROC ASMMultU32ByU32DivByU32
diff --git a/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32-generic.cpp b/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32-generic.cpp
new file mode 100644
index 00000000..c4aa23a1
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32-generic.cpp
@@ -0,0 +1,56 @@
+/* $Id: ASMMultU64ByU32DivByU32-generic.cpp $ */
+/** @file
+ * IPRT - ASMMultU64ByU32DivByU32 - generic C implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm-math.h>
+#include "internal/iprt.h"
+
+
+
+RTDECL(uint64_t) ASMMultU64ByU32DivByU32(uint64_t u64A, uint32_t u32B, uint32_t u32C)
+{
+ RTUINT64U u;
+ uint64_t u64Lo = (uint64_t)(u64A & 0xffffffff) * u32B;
+ uint64_t u64Hi = (uint64_t)(u64A >> 32) * u32B;
+ u64Hi += (u64Lo >> 32);
+ u.s.Hi = (uint32_t)(u64Hi / u32C);
+ u.s.Lo = (uint32_t)((((u64Hi % u32C) << 32) + (u64Lo & 0xffffffff)) / u32C);
+ return u.u;
+}
+
diff --git a/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm b/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm
new file mode 100644
index 00000000..bf2f284d
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm
@@ -0,0 +1,141 @@
+; $Id: ASMMultU64ByU32DivByU32.asm $
+;; @file
+; IPRT - Assembly Functions, ASMMultU64ByU32DivByU32.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+
+;;
+; Multiple a 64-bit by a 32-bit integer and divide the result by a 32-bit integer
+; using a 96 bit intermediate result.
+;
+; @returns (u64A * u32B) / u32C.
+; @param u64A/rcx/rdi The 64-bit value.
+; @param u32B/edx/esi The 32-bit value to multiple by A.
+; @param u32C/r8d/edx The 32-bit value to divide A*B by.
+;
+; @cproto DECLASM(uint64_t) ASMMultU64ByU32DivByU32(uint64_t u64A, uint32_t u32B, uint32_t u32C);
+;
+RT_BEGINPROC ASMMultU64ByU32DivByU32
+%if ARCH_BITS == 64
+
+ %ifdef ASM_CALL64_MSC
+ mov rax, rcx ; rax = u64A
+ mov r9d, edx ; should check the specs wrt to the high bits one day...
+ mov r8d, r8d ; be paranoid for the time being.
+ %else
+ mov rax, rdi ; rax = u64A
+ mov r9d, esi ; r9d = u32B
+ mov r8d, edx ; r8d = u32C
+ %endif
+ mul r9
+ div r8
+
+%else ; 16 or 32 bit
+ ;
+ ; This implementation is converted from the GCC inline
+ ; version of the code. Nothing additional has been done
+ ; performance wise.
+ ;
+ %if ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ push eax ; push all return registers to preserve high value (paranoia)
+ push ebx
+ push ecx
+ push edx
+ %endif
+ push esi
+ push edi
+
+ %if ARCH_BITS == 16
+ %define u64A_Lo [bp + 4 + 04h]
+ %define u64A_Hi [bp + 4 + 08h]
+ %define u32B [bp + 4 + 0ch]
+ %define u32C [bp + 4 + 10h]
+ %else
+ %define u64A_Lo [esp + 04h + 08h]
+ %define u64A_Hi [esp + 08h + 08h]
+ %define u32B [esp + 0ch + 08h]
+ %define u32C [esp + 10h + 08h]
+ %endif
+
+ ; Load parameters into registers.
+ mov eax, u64A_Lo
+ mov esi, u64A_Hi
+ mov ecx, u32B
+ mov edi, u32C
+
+ ; The body, just like the in
+ mul ecx ; eax = u64Lo.lo = (u64A.lo * u32B).lo
+ ; edx = u64Lo.hi = (u64A.lo * u32B).hi
+ xchg eax, esi ; esi = u64Lo.lo
+ ; eax = u64A.hi
+ xchg edx, edi ; edi = u64Low.hi
+ ; edx = u32C
+ xchg edx, ecx ; ecx = u32C
+ ; edx = u32B
+ mul edx ; eax = u64Hi.lo = (u64A.hi * u32B).lo
+ ; edx = u64Hi.hi = (u64A.hi * u32B).hi
+ add eax, edi ; u64Hi.lo += u64Lo.hi
+ adc edx, 0 ; u64Hi.hi += carry
+ div ecx ; eax = u64Hi / u32C
+ ; edx = u64Hi % u32C
+ mov edi, eax ; edi = u64Result.hi = u64Hi / u32C
+ mov eax, esi ; eax = u64Lo.lo
+ div ecx ; u64Result.lo
+ mov edx, edi ; u64Result.hi
+
+ ; epilogue
+ pop edi
+ pop esi
+ %if ARCH_BITS == 16
+ ; DX:CX:BX:AX, where DX holds bits 15:0, CX bits 31:16, BX bits 47:32, and AX bits 63:48.
+ mov ax, [bp - 4*4] ; dx = bits 15:0
+ shr eax, 16
+ mov ax, [bp - 3*4] ; cx = bits 31:16
+ mov dx, [bp - 2*4] ; bx = bits 47:32
+ shr edx, 16
+ mov dx, [bp - 1*4] ; ax = bits 63:48
+ pop edx
+ pop ecx
+ pop ebx
+ pop eax
+ leave
+ %endif
+%endif
+ ret
+ENDPROC ASMMultU64ByU32DivByU32
+
diff --git a/src/VBox/Runtime/common/asm/ASMNopPause.asm b/src/VBox/Runtime/common/asm/ASMNopPause.asm
new file mode 100644
index 00000000..f5264f6f
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMNopPause.asm
@@ -0,0 +1,51 @@
+; $Id: ASMNopPause.asm $
+;; @file
+; IPRT - ASMNopPause().
+;
+
+;
+; Copyright (C) 2009-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; The PAUSE variant of NOP for helping hyperthreaded CPUs detecting spin locks.
+;
+RT_BEGINPROC ASMNopPause
+ pause
+ ret
+ENDPROC ASMNopPause
+
diff --git a/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm b/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm
new file mode 100644
index 00000000..441dd21f
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm
@@ -0,0 +1,94 @@
+; $Id: ASMRdMsrEx.asm $
+;; @file
+; IPRT - ASMRdMsrEx().
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Special version of ASMRdMsr that allow specifying the rdi value.
+;
+; @param uMsr msc=rcx, gcc=rdi, x86=[ebp+8] The MSR to read.
+; @param uEdi msc=rdx, gcc=rsi, x86=[ebp+12] The EDI/RDI value.
+; @returns MSR value in rax on amd64 and edx:eax on x86.
+;
+RT_BEGINPROC ASMRdMsrEx
+%ifdef ASM_CALL64_MSC
+proc_frame ASMRdMsrEx_DupWarningHack
+ push rdi
+ [pushreg rdi]
+[endprolog]
+ and ecx, ecx ; serious paranoia
+ mov rdi, rdx
+ xor eax, eax
+ xor edx, edx
+ rdmsr
+ pop rdi
+ and eax, eax ; paranoia
+ shl rdx, 32
+ or rax, rdx
+ ret
+endproc_frame
+%elifdef ASM_CALL64_GCC
+ mov ecx, edi
+ mov rdi, rsi
+ xor eax, eax
+ xor edx, edx
+ rdmsr
+ and eax, eax ; paranoia
+ shl rdx, 32
+ or rax, rdx
+ ret
+%elifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push edi
+ xor eax, eax
+ xor edx, edx
+ mov ecx, [ebp + 8]
+ mov edi, [ebp + 12]
+ rdmsr
+ pop edi
+ leave
+ ret
+%else
+ %error "Undefined arch?"
+%endif
+ENDPROC ASMRdMsrEx
+
diff --git a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm
new file mode 100644
index 00000000..f9825935
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm
@@ -0,0 +1,59 @@
+; $Id: ASMSerializeInstruction-cpuid.asm $
+;; @file
+; IPRT - ASMSerializeInstruction() using cpuid.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+
+;;
+; Executes a serializing instruction.
+;
+; The CPUID instruction isn't fast or slow, but it always triggers a VM EXIT in
+; a virtualized environment, which is prohibitively expensive.
+;
+RT_BEGINPROC ASMSerializeInstructionCpuId
+ push xBX
+ xor eax, eax
+ cpuid
+ pop xBX
+ ret
+ENDPROC ASMSerializeInstructionCpuId
+
diff --git a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm
new file mode 100644
index 00000000..8e1a68ab
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm
@@ -0,0 +1,71 @@
+; $Id: ASMSerializeInstruction-iret.asm $
+;; @file
+; IPRT - ASMSerializeInstruction() using iret.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+
+;;
+; Executes a serializing instruction.
+;
+; The IRET instruction is rather expensive, but unlike the CPUID instruction it
+; will not result in a VM EXIT when running in a virtual machine.
+;
+RT_BEGINPROC ASMSerializeInstructionIRet
+ pop xAX
+%ifdef RT_ARCH_AMD64
+ mov rdx, xSP
+ mov ecx, ss
+ push rcx
+ push rdx
+ pushfq
+ mov ecx, cs
+ push rcx
+ push rax
+ iretq
+%elifdef RT_ARCH_X86
+ pushf
+ push cs
+ push xAX
+ iret
+%endif
+ENDPROC ASMSerializeInstructionIRet
+
diff --git a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm
new file mode 100644
index 00000000..0ff481ab
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm
@@ -0,0 +1,56 @@
+; $Id: ASMSerializeInstruction-rdtscp.asm $
+;; @file
+; IPRT - ASMSerializeInstruction() using rdtscp.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+
+;;
+; Executes a serializing instruction.
+;
+; The RDTSCP instruction is fast, though it may trigger a VM EXIT if the VMM
+; is intercepting TSC reads for some timing reason or in general.
+;
+RT_BEGINPROC ASMSerializeInstructionRdTscp
+ rdtscp
+ ret
+ENDPROC ASMSerializeInstructionRdTscp
+
diff --git a/src/VBox/Runtime/common/asm/ASMSetFSBase.asm b/src/VBox/Runtime/common/asm/ASMSetFSBase.asm
new file mode 100644
index 00000000..ef324132
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMSetFSBase.asm
@@ -0,0 +1,58 @@
+; $Id: ASMSetFSBase.asm $
+;; @file
+; IPRT - ASMSetFSBase().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Set the FS base register.
+; @param uNewBase msc:rcx gcc:rdi New FS base value.
+;
+RT_BEGINPROC ASMSetFSBase
+ SEH64_END_PROLOGUE
+%ifdef ASM_CALL64_MSC
+ wrfsbase rcx
+%else
+ wrfsbase rdi
+%endif
+ ret
+ENDPROC ASMSetFSBase
+
diff --git a/src/VBox/Runtime/common/asm/ASMSetFlags.asm b/src/VBox/Runtime/common/asm/ASMSetFlags.asm
new file mode 100644
index 00000000..112f21ea
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMSetFlags.asm
@@ -0,0 +1,69 @@
+; $Id: ASMSetFlags.asm $
+;; @file
+; IPRT - ASMSetFlags().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param rcx eflags
+RT_BEGINPROC ASMSetFlags
+%if ARCH_BITS == 64
+ %ifdef ASM_CALL64_GCC
+ push rdi
+ %else
+ push rcx
+ %endif
+ popfq
+%elif ARCH_BITS == 32
+ push dword [esp + 4]
+ popfd
+%elif ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ push word [bp + 2 + 2]
+ popf
+ leave
+%else
+ %error ARCH_BITS
+%endif
+ ret
+ENDPROC ASMSetFlags
+
diff --git a/src/VBox/Runtime/common/asm/ASMSetGDTR.asm b/src/VBox/Runtime/common/asm/ASMSetGDTR.asm
new file mode 100644
index 00000000..8817d9c6
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMSetGDTR.asm
@@ -0,0 +1,62 @@
+; $Id: ASMSetGDTR.asm $
+;; @file
+; IPRT - ASMSetGDTR().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Sets the content of the GDTR CPU register.
+; @param pGdtr Where to load the GDTR contents from.
+; msc=rcx, gcc=rdi, x86=[esp+4]
+;
+RT_BEGINPROC ASMSetGDTR
+%ifdef ASM_CALL64_MSC
+ mov rax, rcx
+%elifdef ASM_CALL64_GCC
+ mov rax, rdi
+%elifdef RT_ARCH_X86
+ mov eax, [esp + 4]
+%else
+ %error "Undefined arch?"
+%endif
+ lgdt [xAX]
+ ret
+ENDPROC ASMSetGDTR
+
diff --git a/src/VBox/Runtime/common/asm/ASMSetGSBase.asm b/src/VBox/Runtime/common/asm/ASMSetGSBase.asm
new file mode 100644
index 00000000..cd5a77a7
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMSetGSBase.asm
@@ -0,0 +1,58 @@
+; $Id: ASMSetGSBase.asm $
+;; @file
+; IPRT - ASMSetGSBase().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Set the GS base register.
+; @param uNewBase msc:rcx gcc:rdi New GS base value.
+;
+RT_BEGINPROC ASMSetGSBase
+ SEH64_END_PROLOGUE
+%ifdef ASM_CALL64_MSC
+ wrgsbase rcx
+%else
+ wrgsbase rdi
+%endif
+ ret
+ENDPROC ASMSetGSBase
+
diff --git a/src/VBox/Runtime/common/asm/ASMSetIDTR.asm b/src/VBox/Runtime/common/asm/ASMSetIDTR.asm
new file mode 100644
index 00000000..be334905
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMSetIDTR.asm
@@ -0,0 +1,62 @@
+; $Id: ASMSetIDTR.asm $
+;; @file
+; IPRT - ASMSetIDTR().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Sets the content of the IDTR CPU register.
+; @param pIdtr Where to load the IDTR contents from.
+; msc=rcx, gcc=rdi, x86=[esp+4]
+;
+RT_BEGINPROC ASMSetIDTR
+%ifdef ASM_CALL64_MSC
+ mov rax, rcx
+%elifdef ASM_CALL64_GCC
+ mov rax, rdi
+%elifdef RT_ARCH_X86
+ mov eax, [esp + 4]
+%else
+ %error "Undefined arch?"
+%endif
+ lidt [xAX]
+ ret
+ENDPROC ASMSetIDTR
+
diff --git a/src/VBox/Runtime/common/asm/ASMSetXcr0.asm b/src/VBox/Runtime/common/asm/ASMSetXcr0.asm
new file mode 100644
index 00000000..831cf351
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMSetXcr0.asm
@@ -0,0 +1,80 @@
+; $Id: ASMSetXcr0.asm $
+;; @file
+; IPRT - ASMSetXcr0().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Sets the content of the Xcr0 CPU register.
+; @param uXcr0 The new XCR0 content.
+; msc=rcx, gcc=rdi, x86=[esp+4]
+;
+RT_BEGINPROC ASMSetXcr0
+SEH64_END_PROLOGUE
+%ifdef ASM_CALL64_MSC
+ mov rdx, rcx
+ shr rdx, 32
+ mov eax, ecx
+%elifdef ASM_CALL64_GCC
+ mov rdx, rdi
+ shr rdx, 32
+ mov eax, edi
+%elif ARCH_BITS == 32
+ mov eax, [esp + 4]
+ mov edx, [esp + 8]
+%elif ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ mov eax, [bp + 4]
+ mov edx, [bp + 8]
+%else
+ %error "Undefined arch?"
+%endif
+
+ xor ecx, ecx
+ xsetbv
+
+%if ARCH_BITS == 16
+ leave
+%endif
+ ret
+ENDPROC ASMSetXcr0
+
diff --git a/src/VBox/Runtime/common/asm/ASMWrMsr.asm b/src/VBox/Runtime/common/asm/ASMWrMsr.asm
new file mode 100644
index 00000000..6e144d40
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMWrMsr.asm
@@ -0,0 +1,99 @@
+; $Id: ASMWrMsr.asm $
+;; @file
+; IPRT - ASMWrMsr().
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Special version of ASMRdMsr that allow specifying the rdi value.
+;
+; @param uMsr msc=rcx, gcc=rdi, x86=[ebp+8] The MSR to read.
+; @param uValue msc=rdx, gcc=rsi, x86=[ebp+12] The 64-bit value to write.
+;
+RT_BEGINPROC ASMWrMsr
+%ifdef ASM_CALL64_MSC
+ mov rdi, rdx
+ mov eax, edx
+ shr rdx, 32
+ wrmsr
+ ret
+
+%elifdef ASM_CALL64_GCC
+ mov ecx, edi
+ mov eax, esi
+ mov rdx, rsi
+ shr edx, 32
+ wrmsr
+ ret
+
+%elif ARCH_BITS == 32
+ push ebp
+ mov ebp, esp
+ push edi
+ mov ecx, [ebp + 8]
+ mov eax, [ebp + 12]
+ mov edx, [ebp + 16]
+ wrmsr
+ pop edi
+ leave
+ ret
+
+%elif ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ push eax
+ push ecx
+ push edx
+
+ mov ecx, [bp + 04h]
+ mov eax, [bp + 08h]
+ mov edx, [bp + 0ch]
+ wrmsr
+
+ pop edx
+ pop ecx
+ pop eax
+ leave
+ ret
+%else
+ %error "Undefined arch?"
+%endif
+ENDPROC ASMWrMsr
+
diff --git a/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm b/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm
new file mode 100644
index 00000000..1c14f32f
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm
@@ -0,0 +1,89 @@
+; $Id: ASMWrMsrEx.asm $
+;; @file
+; IPRT - ASMWrMsrEx().
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Special version of ASMRdMsr that allow specifying the rdi value.
+;
+; @param uMsr msc=rcx, gcc=rdi, x86=[ebp+8] The MSR to read.
+; @param uXDI msc=rdx, gcc=rsi, x86=[ebp+12] The EDI/RDI value.
+; @param uValue msc=r8, gcc=rdx, x86=[ebp+16] The 64-bit value to write.
+;
+RT_BEGINPROC ASMWrMsrEx
+%ifdef ASM_CALL64_MSC
+proc_frame ASMWrMsrEx_DupWarningHack
+ push rdi
+ [pushreg rdi]
+[endprolog]
+ and ecx, ecx ; serious paranoia
+ mov rdi, rdx
+ mov eax, r8d
+ mov rdx, r8
+ shr rdx, 32
+ wrmsr
+ pop rdi
+ ret
+endproc_frame
+%elifdef ASM_CALL64_GCC
+ mov ecx, edi
+ mov rdi, rsi
+ mov eax, edx
+ shr edx, 32
+ wrmsr
+ ret
+%elifdef RT_ARCH_X86
+ push ebp
+ mov ebp, esp
+ push edi
+ mov ecx, [ebp + 8]
+ mov edi, [ebp + 12]
+ mov eax, [ebp + 16]
+ mov edx, [ebp + 20]
+ wrmsr
+ pop edi
+ leave
+ ret
+%else
+ %error "Undefined arch?"
+%endif
+ENDPROC ASMWrMsrEx
+
diff --git a/src/VBox/Runtime/common/asm/ASMXRstor.asm b/src/VBox/Runtime/common/asm/ASMXRstor.asm
new file mode 100644
index 00000000..fd4ab564
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMXRstor.asm
@@ -0,0 +1,73 @@
+; $Id: ASMXRstor.asm $
+;; @file
+; IPRT - ASMXRstor().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Loads extended CPU state.
+; @param pXStateArea Pointer to the XRSTOR state area.
+; msc=rcx, gcc=rdi, x86=[esp+4]
+; @param fMask The 64-bit state component mask.
+; msc=rdx, gcc=rsi, x86=[esp+8]
+;
+RT_BEGINPROC ASMXRstor
+SEH64_END_PROLOGUE
+%ifdef ASM_CALL64_MSC
+ mov eax, edx
+ shr rdx, 32
+ xrstor [rcx]
+%elifdef ASM_CALL64_GCC
+ mov rdx, rsi
+ shr rdx, 32
+ mov eax, esi
+ xrstor [rdi]
+%elifdef RT_ARCH_X86
+ mov ecx, [esp + 4]
+ mov eax, [esp + 8]
+ mov edx, [esp + 12]
+ xrstor [ecx]
+%else
+ %error "Undefined arch?"
+%endif
+ ret
+ENDPROC ASMXRstor
+
diff --git a/src/VBox/Runtime/common/asm/ASMXSave.asm b/src/VBox/Runtime/common/asm/ASMXSave.asm
new file mode 100644
index 00000000..ba262cbf
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/ASMXSave.asm
@@ -0,0 +1,73 @@
+; $Id: ASMXSave.asm $
+;; @file
+; IPRT - ASMXSave().
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Saves extended CPU state.
+; @param pXStateArea Pointer to the XSAVE state area.
+; msc=rcx, gcc=rdi, x86=[esp+4]
+; @param fComponents The 64-bit state component mask.
+; msc=rdx, gcc=rsi, x86=[esp+8]
+;
+RT_BEGINPROC ASMXSave
+SEH64_END_PROLOGUE
+%ifdef ASM_CALL64_MSC
+ mov eax, edx
+ shr rdx, 32
+ xsave [rcx]
+%elifdef ASM_CALL64_GCC
+ mov rdx, rsi
+ shr rdx, 32
+ mov eax, esi
+ xsave [rdi]
+%elifdef RT_ARCH_X86
+ mov ecx, [esp + 4]
+ mov eax, [esp + 8]
+ mov edx, [esp + 12]
+ xsave [ecx]
+%else
+ %error "Undefined arch?"
+%endif
+ ret
+ENDPROC ASMXSave
+
diff --git a/src/VBox/Runtime/common/asm/Makefile.kup b/src/VBox/Runtime/common/asm/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/Makefile.kup
diff --git a/src/VBox/Runtime/common/asm/asm-fake.cpp b/src/VBox/Runtime/common/asm/asm-fake.cpp
new file mode 100644
index 00000000..64f1f37e
--- /dev/null
+++ b/src/VBox/Runtime/common/asm/asm-fake.cpp
@@ -0,0 +1,354 @@
+/* $Id: asm-fake.cpp $ */
+/** @file
+ * IPRT - Fake asm.h routines for use early in a new port.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/asm.h>
+#include "internal/iprt.h"
+
+#include <iprt/string.h>
+#include <iprt/param.h>
+
+
+RTDECL(uint8_t) ASMAtomicXchgU8(volatile uint8_t *pu8, uint8_t u8)
+{
+ uint8_t u8Ret = *pu8;
+ *pu8 = u8;
+ return u8Ret;
+}
+
+RTDECL(uint16_t) ASMAtomicXchgU16(volatile uint16_t *pu16, uint16_t u16)
+{
+ uint16_t u16Ret = *pu16;
+ *pu16 = u16;
+ return u16Ret;
+}
+
+RTDECL(uint32_t) ASMAtomicXchgU32(volatile uint32_t *pu32, uint32_t u32)
+{
+ uint32_t u32Ret = *pu32;
+ *pu32 = u32;
+ return u32Ret;
+}
+
+RTDECL(uint64_t) ASMAtomicXchgU64(volatile uint64_t *pu64, uint64_t u64)
+{
+ uint64_t u64Ret = *pu64;
+ *pu64 = u64;
+ return u64Ret;
+}
+
+RTDECL(bool) ASMAtomicCmpXchgU8(volatile uint8_t *pu8, const uint8_t u8New, const uint8_t u8Old)
+{
+ if (*pu8 == u8Old)
+ {
+ *pu8 = u8New;
+ return true;
+ }
+ return false;
+}
+
+RTDECL(bool) ASMAtomicCmpXchgU32(volatile uint32_t *pu32, const uint32_t u32New, const uint32_t u32Old)
+{
+ if (*pu32 == u32Old)
+ {
+ *pu32 = u32New;
+ return true;
+ }
+ return false;
+}
+
+RTDECL(bool) ASMAtomicCmpXchgU64(volatile uint64_t *pu64, const uint64_t u64New, const uint64_t u64Old)
+{
+ if (*pu64 == u64Old)
+ {
+ *pu64 = u64New;
+ return true;
+ }
+ return false;
+}
+
+RTDECL(bool) ASMAtomicCmpXchgExU32(volatile uint32_t *pu32, const uint32_t u32New, const uint32_t u32Old, uint32_t *pu32Old)
+{
+ uint32_t u32Cur = *pu32;
+ if (u32Cur == u32Old)
+ {
+ *pu32 = u32New;
+ *pu32Old = u32Old;
+ return true;
+ }
+ *pu32Old = u32Cur;
+ return false;
+}
+
+RTDECL(bool) ASMAtomicCmpXchgExU64(volatile uint64_t *pu64, const uint64_t u64New, const uint64_t u64Old, uint64_t *pu64Old)
+{
+ uint64_t u64Cur = *pu64;
+ if (u64Cur == u64Old)
+ {
+ *pu64 = u64New;
+ *pu64Old = u64Old;
+ return true;
+ }
+ *pu64Old = u64Cur;
+ return false;
+}
+
+RTDECL(uint32_t) ASMAtomicAddU32(uint32_t volatile *pu32, uint32_t u32)
+{
+ uint32_t u32Old = *pu32;
+ *pu32 = u32Old + u32;
+ return u32Old;
+}
+
+RTDECL(uint64_t) ASMAtomicAddU64(uint64_t volatile *pu64, uint64_t u64)
+{
+ uint64_t u64Old = *pu64;
+ *pu64 = u64Old + u64;
+ return u64Old;
+}
+
+RTDECL(uint32_t) ASMAtomicIncU32(uint32_t volatile *pu32)
+{
+ return *pu32 += 1;
+}
+
+RTDECL(uint32_t) ASMAtomicUoIncU32(uint32_t volatile *pu32)
+{
+ return *pu32 += 1;
+}
+
+RTDECL(uint32_t) ASMAtomicDecU32(uint32_t volatile *pu32)
+{
+ return *pu32 -= 1;
+}
+
+RTDECL(uint32_t) ASMAtomicUoDecU32(uint32_t volatile *pu32)
+{
+ return *pu32 -= 1;
+}
+
+RTDECL(uint64_t) ASMAtomicIncU64(uint64_t volatile *pu64)
+{
+ return *pu64 += 1;
+}
+
+RTDECL(uint64_t) ASMAtomicDecU64(uint64_t volatile *pu64)
+{
+ return *pu64 -= 1;
+}
+
+RTDECL(void) ASMAtomicOrU32(uint32_t volatile *pu32, uint32_t u32)
+{
+ *pu32 |= u32;
+}
+
+RTDECL(void) ASMAtomicUoOrU32(uint32_t volatile *pu32, uint32_t u32)
+{
+ *pu32 |= u32;
+}
+
+RTDECL(void) ASMAtomicAndU32(uint32_t volatile *pu32, uint32_t u32)
+{
+ *pu32 &= u32;
+}
+
+RTDECL(void) ASMAtomicUoAndU32(uint32_t volatile *pu32, uint32_t u32)
+{
+ *pu32 &= u32;
+}
+
+RTDECL(void) ASMAtomicOrU64(uint64_t volatile *pu64, uint64_t u64)
+{
+ *pu64 |= u64;
+}
+
+RTDECL(void) ASMAtomicAndU64(uint64_t volatile *pu64, uint64_t u64)
+{
+ *pu64 &= u64;
+}
+
+RTDECL(void) ASMSerializeInstruction(void)
+{
+
+}
+
+RTDECL(uint64_t) ASMAtomicReadU64(volatile uint64_t *pu64)
+{
+ return *pu64;
+}
+
+RTDECL(uint64_t) ASMAtomicUoReadU64(volatile uint64_t *pu64)
+{
+ return *pu64;
+}
+
+RTDECL(uint8_t) ASMProbeReadByte(const void *pvByte)
+{
+ return *(volatile uint8_t *)pvByte;
+}
+
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+RTDECL(void) ASMNopPause(void)
+{
+}
+#endif
+
+RTDECL(void) ASMBitSet(volatile void *pvBitmap, int32_t iBit)
+{
+ uint8_t volatile *pau8Bitmap = (uint8_t volatile *)pvBitmap;
+ pau8Bitmap[iBit / 8] |= (uint8_t)RT_BIT_32(iBit & 7);
+}
+
+RTDECL(void) ASMAtomicBitSet(volatile void *pvBitmap, int32_t iBit)
+{
+ ASMBitSet(pvBitmap, iBit);
+}
+
+RTDECL(void) ASMBitClear(volatile void *pvBitmap, int32_t iBit)
+{
+ uint8_t volatile *pau8Bitmap = (uint8_t volatile *)pvBitmap;
+ pau8Bitmap[iBit / 8] &= ~((uint8_t)RT_BIT_32(iBit & 7));
+}
+
+RTDECL(void) ASMAtomicBitClear(volatile void *pvBitmap, int32_t iBit)
+{
+ ASMBitClear(pvBitmap, iBit);
+}
+
+RTDECL(void) ASMBitToggle(volatile void *pvBitmap, int32_t iBit)
+{
+ uint8_t volatile *pau8Bitmap = (uint8_t volatile *)pvBitmap;
+ pau8Bitmap[iBit / 8] ^= (uint8_t)RT_BIT_32(iBit & 7);
+}
+
+RTDECL(void) ASMAtomicBitToggle(volatile void *pvBitmap, int32_t iBit)
+{
+ ASMBitToggle(pvBitmap, iBit);
+}
+
+RTDECL(bool) ASMBitTestAndSet(volatile void *pvBitmap, int32_t iBit)
+{
+ if (ASMBitTest(pvBitmap, iBit))
+ return true;
+ ASMBitSet(pvBitmap, iBit);
+ return false;
+}
+
+RTDECL(bool) ASMAtomicBitTestAndSet(volatile void *pvBitmap, int32_t iBit)
+{
+ return ASMBitTestAndSet(pvBitmap, iBit);
+}
+
+RTDECL(bool) ASMBitTestAndClear(volatile void *pvBitmap, int32_t iBit)
+{
+ if (!ASMBitTest(pvBitmap, iBit))
+ return false;
+ ASMBitClear(pvBitmap, iBit);
+ return true;
+}
+
+RTDECL(bool) ASMAtomicBitTestAndClear(volatile void *pvBitmap, int32_t iBit)
+{
+ return ASMBitTestAndClear(pvBitmap, iBit);
+}
+
+RTDECL(bool) ASMBitTestAndToggle(volatile void *pvBitmap, int32_t iBit)
+{
+ bool fRet = ASMBitTest(pvBitmap, iBit);
+ ASMBitToggle(pvBitmap, iBit);
+ return fRet;
+}
+
+RTDECL(bool) ASMAtomicBitTestAndToggle(volatile void *pvBitmap, int32_t iBit)
+{
+ return ASMBitTestAndToggle(pvBitmap, iBit);
+}
+
+RTDECL(bool) ASMBitTest(const volatile void *pvBitmap, int32_t iBit)
+{
+ uint8_t volatile *pau8Bitmap = (uint8_t volatile *)pvBitmap;
+ return pau8Bitmap[iBit / 8] & (uint8_t)RT_BIT_32(iBit & 7) ? true : false;
+}
+
+RTDECL(unsigned) ASMBitFirstSetU32(uint32_t u32)
+{
+ uint32_t iBit;
+ for (iBit = 0; iBit < 32; iBit++)
+ if (u32 & RT_BIT_32(iBit))
+ return iBit + 1;
+ return 0;
+}
+
+RTDECL(unsigned) ASMBitLastSetU32(uint32_t u32)
+{
+ uint32_t iBit = 32;
+ while (iBit-- > 0)
+ if (u32 & RT_BIT_32(iBit))
+ return iBit + 1;
+ return 0;
+}
+
+RTDECL(unsigned) ASMBitFirstSetU64(uint64_t u64)
+{
+ uint32_t iBit;
+ for (iBit = 0; iBit < 64; iBit++)
+ if (u64 & RT_BIT_64(iBit))
+ return iBit + 1;
+ return 0;
+}
+
+RTDECL(unsigned) ASMBitLastSetU64(uint64_t u64)
+{
+ uint32_t iBit = 64;
+ while (iBit-- > 0)
+ if (u64 & RT_BIT_64(iBit))
+ return iBit + 1;
+ return 0;
+}
+
+RTDECL(uint16_t) ASMByteSwapU16(uint16_t u16)
+{
+ return RT_MAKE_U16(RT_HIBYTE(u16), RT_LOBYTE(u16));
+}
+
+RTDECL(uint32_t) ASMByteSwapU32(uint32_t u32)
+{
+ return RT_MAKE_U32_FROM_U8(RT_BYTE4(u32), RT_BYTE3(u32), RT_BYTE2(u32), RT_BYTE1(u32));
+}
+
diff --git a/src/VBox/Runtime/common/asn1/Makefile.kup b/src/VBox/Runtime/common/asn1/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/Makefile.kup
diff --git a/src/VBox/Runtime/common/asn1/asn1-basics.cpp b/src/VBox/Runtime/common/asn1/asn1-basics.cpp
new file mode 100644
index 00000000..05c73f1a
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-basics.cpp
@@ -0,0 +1,611 @@
+/* $Id: asn1-basics.cpp $ */
+/** @file
+ * IPRT - ASN.1, Basic Operations.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/alloca.h>
+#include <iprt/bignum.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * ASN.1 content/value allocation.
+ *
+ * The currently most frequent use of the RTAsn1 module is to decode ASN.1 byte
+ * streams. In that scenario we do not allocate memory for the raw content
+ * bytes, but share it with the byte stream. Also, a great number of RTASN1CORE
+ * structures will never need to have any content bytes allocated with this.
+ *
+ * So, in order to avoid adding an extra 16 (64-bit) or 8 (32-bit) bytes to each
+ * RTASN1CORE structure just to keep track of the occational content allocation,
+ * we put the allocator tracking structure inside the allocation. During
+ * allocator operations it lives temporarily on the stack.
+ */
+typedef struct RTASN1MEMCONTENT
+{
+ /** The allocation tracker. */
+ RTASN1ALLOCATION Allocation;
+#if ARCH_BITS == 32
+ uint32_t Padding; /**< Alignment padding. */
+#endif
+ /** The content bytes, i.e. what RTASN1CORE::uData.pv points to. Use a 64-bit
+ * type here to emphasize that it's 8-byte aligned on all platforms. */
+ uint64_t au64Content[1];
+} RTASN1MEMCONTENT;
+AssertCompileMemberAlignment(RTASN1MEMCONTENT, au64Content, 8);
+/** Pointer to a ASN.1 content allocation. */
+typedef RTASN1MEMCONTENT *PRTASN1MEMCONTENT;
+
+
+
+RTDECL(int) RTAsn1MemResizeArray(PRTASN1ARRAYALLOCATION pAllocation, void ***ppapvArray, uint32_t cCurrent, uint32_t cNew)
+{
+ AssertReturn(pAllocation->pAllocator != NULL, VERR_WRONG_ORDER);
+ AssertReturn(pAllocation->cbEntry > 0, VERR_WRONG_ORDER);
+ AssertReturn(cCurrent <= pAllocation->cEntriesAllocated, VERR_INVALID_PARAMETER);
+ AssertReturn(cCurrent <= pAllocation->cPointersAllocated, VERR_INVALID_PARAMETER);
+ AssertReturn(cNew < _1M, VERR_OUT_OF_RANGE);
+ Assert(pAllocation->cEntriesAllocated <= pAllocation->cPointersAllocated);
+
+ /*
+ * Is there sufficent space allocated already?
+ *
+ * We keep unused entires ZEROed, therefore we must always call the allocator
+ * when shrinking (this also helps with the electric fence allocator).
+ */
+ if (cNew <= pAllocation->cEntriesAllocated)
+ {
+ if (cCurrent <= cNew)
+ return VINF_SUCCESS;
+ pAllocation->pAllocator->pfnShrinkArray(pAllocation->pAllocator, pAllocation, ppapvArray, cCurrent, cNew);
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Must grow (or do initial alloc).
+ */
+ pAllocation->cResizeCalls++;
+ return pAllocation->pAllocator->pfnGrowArray(pAllocation->pAllocator, pAllocation, ppapvArray, cNew);
+}
+
+
+RTDECL(void) RTAsn1MemFreeArray(PRTASN1ARRAYALLOCATION pAllocation, void **papvArray)
+{
+ Assert(pAllocation->pAllocator != NULL);
+ if (papvArray)
+ {
+ pAllocation->pAllocator->pfnFreeArray(pAllocation->pAllocator, pAllocation, papvArray);
+ Assert(pAllocation->cPointersAllocated == 0);
+ Assert(pAllocation->cEntriesAllocated == 0);
+ }
+}
+
+
+RTDECL(int) RTAsn1MemAllocZ(PRTASN1ALLOCATION pAllocation, void **ppvMem, size_t cbMem)
+{
+ AssertReturn(pAllocation->pAllocator != NULL, VERR_WRONG_ORDER);
+ AssertPtr(ppvMem);
+ Assert(cbMem > 0);
+ int rc = pAllocation->pAllocator->pfnAlloc(pAllocation->pAllocator, pAllocation, ppvMem, cbMem);
+ Assert(pAllocation->cbAllocated >= cbMem || RT_FAILURE_NP(rc));
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1MemDup(PRTASN1ALLOCATION pAllocation, void **ppvMem, const void *pvSrc, size_t cbMem)
+{
+ AssertReturn(pAllocation->pAllocator != NULL, VERR_WRONG_ORDER);
+ AssertPtr(ppvMem);
+ AssertPtr(pvSrc);
+ Assert(cbMem > 0);
+ int rc = pAllocation->pAllocator->pfnAlloc(pAllocation->pAllocator, pAllocation, ppvMem, cbMem);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pAllocation->cbAllocated >= cbMem);
+ memcpy(*ppvMem, pvSrc, cbMem);
+ return VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+RTDECL(void) RTAsn1MemFree(PRTASN1ALLOCATION pAllocation, void *pv)
+{
+ Assert(pAllocation->pAllocator != NULL);
+ if (pv)
+ {
+ pAllocation->pAllocator->pfnFree(pAllocation->pAllocator, pAllocation, pv);
+ Assert(pAllocation->cbAllocated == 0);
+ }
+}
+
+
+RTDECL(PRTASN1ALLOCATION) RTAsn1MemInitAllocation(PRTASN1ALLOCATION pAllocation, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ pAllocation->cbAllocated = 0;
+ pAllocation->cReallocs = 0;
+ pAllocation->uReserved0 = 0;
+ pAllocation->pAllocator = pAllocator;
+ return pAllocation;
+}
+
+
+RTDECL(PRTASN1ARRAYALLOCATION) RTAsn1MemInitArrayAllocation(PRTASN1ARRAYALLOCATION pAllocation,
+ PCRTASN1ALLOCATORVTABLE pAllocator, size_t cbEntry)
+{
+ Assert(cbEntry >= sizeof(RTASN1CORE));
+ Assert(cbEntry < _1M);
+ Assert(RT_ALIGN_Z(cbEntry, sizeof(void *)) == cbEntry);
+ pAllocation->cbEntry = (uint32_t)cbEntry;
+ pAllocation->cPointersAllocated = 0;
+ pAllocation->cEntriesAllocated = 0;
+ pAllocation->cResizeCalls = 0;
+ pAllocation->uReserved0 = 0;
+ pAllocation->pAllocator = pAllocator;
+ return pAllocation;
+}
+
+
+RTDECL(int) RTAsn1ContentAllocZ(PRTASN1CORE pAsn1Core, size_t cb, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertReturn(pAllocator != NULL, VERR_WRONG_ORDER);
+ AssertReturn(cb > 0 && cb < _1G, VERR_INVALID_PARAMETER);
+ AssertPtr(pAsn1Core);
+ AssertReturn(!(pAsn1Core->fFlags & RTASN1CORE_F_ALLOCATED_CONTENT), VERR_INVALID_STATE);
+
+ /* Initialize the temporary allocation tracker. */
+ RTASN1ALLOCATION Allocation;
+ Allocation.cbAllocated = 0;
+ Allocation.cReallocs = 0;
+ Allocation.uReserved0 = 0;
+ Allocation.pAllocator = pAllocator;
+
+ /* Make the allocation. */
+ uint32_t cbAlloc = RT_UOFFSETOF(RTASN1MEMCONTENT, au64Content) + (uint32_t)cb;
+ PRTASN1MEMCONTENT pHdr;
+ int rc = pAllocator->pfnAlloc(pAllocator, &Allocation, (void **)&pHdr, cbAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(Allocation.cbAllocated >= cbAlloc);
+ pHdr->Allocation = Allocation;
+ pAsn1Core->cb = (uint32_t)cb;
+ pAsn1Core->uData.pv = &pHdr->au64Content[0];
+ pAsn1Core->fFlags |= RTASN1CORE_F_ALLOCATED_CONTENT;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1ContentDup(PRTASN1CORE pAsn1Core, void const *pvSrc, size_t cbSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ int rc = RTAsn1ContentAllocZ(pAsn1Core, cbSrc, pAllocator);
+ if (RT_SUCCESS(rc))
+ memcpy((void *)pAsn1Core->uData.pv, pvSrc, cbSrc);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1ContentReallocZ(PRTASN1CORE pAsn1Core, size_t cb, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ /* Validate input. */
+ AssertPtr(pAsn1Core);
+ AssertReturn(cb < _1G, VERR_INVALID_PARAMETER);
+
+ if (cb > 0)
+ {
+ /*
+ * Case 1 - Initial allocation.
+ */
+ uint32_t cbNeeded = RT_UOFFSETOF(RTASN1MEMCONTENT, au64Content) + (uint32_t)cb;
+ if (!(pAsn1Core->fFlags & RTASN1CORE_F_ALLOCATED_CONTENT))
+ return RTAsn1ContentAllocZ(pAsn1Core, cb, pAllocator);
+
+ /* Locate the header. */
+ PRTASN1MEMCONTENT pHdr = RT_FROM_MEMBER(pAsn1Core->uData.pv, RTASN1MEMCONTENT, au64Content);
+
+ /*
+ * Case 2 - Reallocation using the same allocator.
+ */
+ if ( pHdr->Allocation.pAllocator == pAllocator
+ || !pAllocator)
+ {
+ pHdr->Allocation.cReallocs++;
+
+ /* Modify the allocation if necessary. */
+ if (pHdr->Allocation.cbAllocated < cbNeeded)
+ {
+ RTASN1ALLOCATION Allocation = pHdr->Allocation;
+ int rc = Allocation.pAllocator->pfnRealloc(Allocation.pAllocator, &Allocation, pHdr, (void **)&pHdr, cbNeeded);
+ if (RT_FAILURE(rc))
+ return rc;
+ Assert(Allocation.cbAllocated >= cbNeeded);
+ pAsn1Core->uData.pv = &pHdr->au64Content[0];
+ pHdr->Allocation = Allocation;
+ }
+
+ /* Clear any additional memory we're letting the user use and
+ update the content size. */
+ if (pAsn1Core->cb < cb)
+ RT_BZERO((uint8_t *)&pAsn1Core->uData.pu8[pAsn1Core->cb], cb - pAsn1Core->cb);
+ pAsn1Core->cb = (uint32_t)cb;
+ }
+ /*
+ * Case 3 - Reallocation using a different allocator.
+ */
+ else
+ {
+ /* Initialize the temporary allocation tracker. */
+ RTASN1ALLOCATION Allocation;
+ Allocation.cbAllocated = 0;
+ Allocation.cReallocs = pHdr->Allocation.cReallocs + 1;
+ Allocation.uReserved0 = 0;
+ Allocation.pAllocator = pAllocator;
+
+ /* Make the allocation. */
+ PRTASN1MEMCONTENT pHdrNew;
+ int rc = pAllocator->pfnAlloc(pAllocator, &Allocation, (void **)&pHdrNew, cbNeeded);
+ if (RT_FAILURE(rc))
+ return rc;
+ Assert(Allocation.cbAllocated >= cbNeeded);
+
+ /* Duplicate the old content and zero any new memory we might've added. */
+ if (pAsn1Core->cb >= cb)
+ memcpy(&pHdrNew->au64Content[0], &pHdr->au64Content[0], cb);
+ else
+ {
+ memcpy(&pHdrNew->au64Content[0], &pHdr->au64Content[0], pAsn1Core->cb);
+ RT_BZERO((uint8_t *)&pHdrNew->au64Content[0] + pAsn1Core->cb, cb - pAsn1Core->cb);
+ }
+
+ /* Update the core. */
+ pHdrNew->Allocation = Allocation;
+ pAsn1Core->uData.pv = &pHdrNew->au64Content[0];
+ pAsn1Core->fFlags |= RTASN1CORE_F_ALLOCATED_CONTENT; /* free cleared it. */
+ pAsn1Core->cb = (uint32_t)cb;
+
+ /* Free the old content. */
+ Allocation = pHdr->Allocation;
+ Allocation.pAllocator->pfnFree(Allocation.pAllocator, &Allocation, pHdr);
+ Assert(Allocation.cbAllocated == 0);
+ }
+ }
+ /*
+ * Case 4 - It's a request to free the memory.
+ */
+ else
+ RTAsn1ContentFree(pAsn1Core);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1ContentFree(PRTASN1CORE pAsn1Core)
+{
+ if (pAsn1Core)
+ {
+ pAsn1Core->cb = 0;
+ if (pAsn1Core->fFlags & RTASN1CORE_F_ALLOCATED_CONTENT)
+ {
+ pAsn1Core->fFlags &= ~RTASN1CORE_F_ALLOCATED_CONTENT;
+ AssertReturnVoid(pAsn1Core->uData.pv);
+
+ PRTASN1MEMCONTENT pHdr = RT_FROM_MEMBER(pAsn1Core->uData.pv, RTASN1MEMCONTENT, au64Content);
+ RTASN1ALLOCATION Allocation = pHdr->Allocation;
+
+ Allocation.pAllocator->pfnFree(Allocation.pAllocator, &Allocation, pHdr);
+ Assert(Allocation.cbAllocated == 0);
+ }
+ pAsn1Core->uData.pv = NULL;
+ }
+}
+
+
+
+/*
+ * Virtual method table based API.
+ */
+
+RTDECL(void) RTAsn1VtDelete(PRTASN1CORE pThisCore)
+{
+ if (pThisCore)
+ {
+ PCRTASN1COREVTABLE pOps = pThisCore->pOps;
+ if (pOps)
+ pOps->pfnDtor(pThisCore);
+ }
+}
+
+
+/**
+ * Context data passed by RTAsn1VtDeepEnum to it's worker callbacks.
+ */
+typedef struct RTASN1DEEPENUMCTX
+{
+ PFNRTASN1ENUMCALLBACK pfnCallback;
+ void *pvUser;
+} RTASN1DEEPENUMCTX;
+
+
+static DECLCALLBACK(int) rtAsn1VtDeepEnumDepthFirst(PRTASN1CORE pThisCore, const char *pszName, uint32_t uDepth, void *pvUser)
+{
+ AssertReturn(pThisCore, VINF_SUCCESS);
+
+ if (pThisCore->pOps && pThisCore->pOps->pfnEnum)
+ {
+ int rc = pThisCore->pOps->pfnEnum(pThisCore, rtAsn1VtDeepEnumDepthFirst, uDepth, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+
+ RTASN1DEEPENUMCTX *pCtx = (RTASN1DEEPENUMCTX *)pvUser;
+ return pCtx->pfnCallback(pThisCore, pszName, uDepth, pCtx->pvUser);
+}
+
+
+static DECLCALLBACK(int) rtAsn1VtDeepEnumDepthLast(PRTASN1CORE pThisCore, const char *pszName, uint32_t uDepth, void *pvUser)
+{
+ AssertReturn(pThisCore, VINF_SUCCESS);
+
+ RTASN1DEEPENUMCTX *pCtx = (RTASN1DEEPENUMCTX *)pvUser;
+ int rc = pCtx->pfnCallback(pThisCore, pszName, uDepth, pCtx->pvUser);
+ if (rc == VINF_SUCCESS)
+ {
+ if (pThisCore->pOps && pThisCore->pOps->pfnEnum)
+ rc = pThisCore->pOps->pfnEnum(pThisCore, rtAsn1VtDeepEnumDepthFirst, uDepth, pvUser);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1VtDeepEnum(PRTASN1CORE pThisCore, bool fDepthFirst, uint32_t uDepth,
+ PFNRTASN1ENUMCALLBACK pfnCallback, void *pvUser)
+{
+ int rc;
+ if (RTAsn1Core_IsPresent(pThisCore))
+ {
+ PCRTASN1COREVTABLE pOps = pThisCore->pOps;
+ if (pOps && pOps->pfnEnum)
+ {
+ RTASN1DEEPENUMCTX Ctx;
+ Ctx.pfnCallback = pfnCallback;
+ Ctx.pvUser = pvUser;
+ rc = pOps->pfnEnum(pThisCore, fDepthFirst ? rtAsn1VtDeepEnumDepthFirst : rtAsn1VtDeepEnumDepthLast, uDepth, &Ctx);
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1VtClone(PRTASN1CORE pThisCore, PRTASN1CORE pSrcCore, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtrReturn(pThisCore, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSrcCore, VERR_INVALID_POINTER);
+ AssertPtrReturn(pAllocator, VERR_INVALID_POINTER);
+
+ if (RTAsn1Core_IsPresent(pSrcCore))
+ {
+ AssertPtrReturn(pSrcCore->pOps, VERR_INVALID_POINTER);
+ AssertPtr(pSrcCore->pOps->pfnClone);
+ return pSrcCore->pOps->pfnClone(pThisCore, pSrcCore, pAllocator);
+ }
+
+ RT_ZERO(*pThisCore);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1VtCompare(PCRTASN1CORE pLeftCore, PCRTASN1CORE pRightCore)
+{
+ int iDiff;
+ if (RTAsn1Core_IsPresent(pLeftCore))
+ {
+ if (RTAsn1Core_IsPresent(pRightCore))
+ {
+ PCRTASN1COREVTABLE pOps = pLeftCore->pOps;
+ if (pOps == pRightCore->pOps)
+ {
+ AssertPtr(pOps->pfnCompare);
+ iDiff = pOps->pfnCompare(pLeftCore, pRightCore);
+ }
+ else
+ iDiff = (uintptr_t)pOps < (uintptr_t)pRightCore->pOps ? -1 : 1;
+ }
+ else
+ iDiff = 1;
+ }
+ else
+ iDiff = 0 - (int)RTAsn1Core_IsPresent(pRightCore);
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1VtCheckSanity(PCRTASN1CORE pThisCore, uint32_t fFlags,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ int rc;
+ if (RTAsn1Core_IsPresent(pThisCore))
+ {
+ PCRTASN1COREVTABLE pOps = pThisCore->pOps;
+ if (pOps && pOps->pfnCheckSanity)
+ rc = pOps->pfnCheckSanity(pThisCore, fFlags, pErrInfo, pszErrorTag);
+ else if (pOps)
+ rc = RTErrInfoSetF(pErrInfo, VERR_ASN1_NO_CHECK_SANITY_METHOD,
+ "%s: Has no pfnCheckSanity function.", pszErrorTag);
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_ASN1_NO_VTABLE, "%s: Has no Vtable function.", pszErrorTag);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Not present.", pszErrorTag);
+ return rc;
+}
+
+
+
+/*
+ * Dummy ASN.1 object.
+ */
+
+RTDECL(int) RTAsn1Dummy_InitEx(PRTASN1DUMMY pThis)
+{
+ return RTAsn1Core_InitEx(&pThis->Asn1Core,
+ UINT32_MAX,
+ ASN1_TAGCLASS_PRIVATE | ASN1_TAGFLAG_CONSTRUCTED,
+ NULL,
+ RTASN1CORE_F_DUMMY);
+}
+
+
+/*
+ * ASN.1 SEQUENCE OF object.
+ */
+
+RTDECL(int) RTAsn1SeqOfCore_Init(PRTASN1SEQOFCORE pThis, PCRTASN1COREVTABLE pVtable)
+{
+ return RTAsn1Core_InitEx(&pThis->Asn1Core,
+ ASN1_TAG_SEQUENCE,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED,
+ pVtable,
+ RTASN1CORE_F_PRESENT);
+}
+
+
+RTDECL(int) RTAsn1SeqOfCore_Clone(PRTASN1SEQOFCORE pThis, PCRTASN1COREVTABLE pVtable, PCRTASN1SEQOFCORE pSrc)
+{
+ AssertReturn(pSrc->Asn1Core.pOps == pVtable, VERR_ASN1_INTERNAL_ERROR_5);
+ return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+}
+
+
+/*
+ * ASN.1 SET OF object.
+ */
+
+RTDECL(int) RTAsn1SetOfCore_Init(PRTASN1SETOFCORE pThis, PCRTASN1COREVTABLE pVtable)
+{
+ return RTAsn1Core_InitEx(&pThis->Asn1Core,
+ ASN1_TAG_SET,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED,
+ pVtable,
+ RTASN1CORE_F_PRESENT);
+}
+
+
+RTDECL(int) RTAsn1SetOfCore_Clone(PRTASN1SETOFCORE pThis, PCRTASN1COREVTABLE pVtable, PCRTASN1SETOFCORE pSrc)
+{
+ AssertReturn(pSrc->Asn1Core.pOps == pVtable, VERR_ASN1_INTERNAL_ERROR_5);
+ return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+}
+
+
+/*
+ * ASN.1 SEQUENCE object.
+ */
+
+RTDECL(int) RTAsn1SequenceCore_Init(PRTASN1SEQUENCECORE pThis, PCRTASN1COREVTABLE pVtable)
+{
+ return RTAsn1Core_InitEx(&pThis->Asn1Core,
+ ASN1_TAG_SEQUENCE,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED,
+ pVtable,
+ RTASN1CORE_F_PRESENT);
+}
+
+
+RTDECL(int) RTAsn1SequenceCore_Clone(PRTASN1SEQUENCECORE pThis, PCRTASN1COREVTABLE pVtable, PCRTASN1SEQUENCECORE pSrc)
+{
+ AssertReturn(pSrc->Asn1Core.pOps == pVtable, VERR_ASN1_INTERNAL_ERROR_5);
+ return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+}
+
+
+/*
+ * ASN.1 SEQUENCE object - only used by SPC, so probably doing something wrong there.
+ */
+
+RTDECL(int) RTAsn1SetCore_Init(PRTASN1SETCORE pThis, PCRTASN1COREVTABLE pVtable)
+{
+ return RTAsn1Core_InitEx(&pThis->Asn1Core,
+ ASN1_TAG_SET,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED,
+ pVtable,
+ RTASN1CORE_F_PRESENT);
+}
+
+
+RTDECL(int) RTAsn1SetCore_Clone(PRTASN1SETCORE pThis, PCRTASN1COREVTABLE pVtable, PCRTASN1SETCORE pSrc)
+{
+ AssertReturn(pSrc->Asn1Core.pOps == pVtable, VERR_ASN1_INTERNAL_ERROR_5);
+ return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+}
+
+
+/*
+ * ASN.1 Context Tag object.
+ */
+
+RTDECL(int) RTAsn1ContextTagN_Init(PRTASN1CONTEXTTAG pThis, uint32_t uTag, PCRTASN1COREVTABLE pVtable)
+{
+ return RTAsn1Core_InitEx(&pThis->Asn1Core,
+ uTag,
+ ASN1_TAGCLASS_CONTEXT | ASN1_TAGFLAG_CONSTRUCTED,
+ pVtable,
+ RTASN1CORE_F_PRESENT);
+}
+
+
+RTDECL(int) RTAsn1ContextTagN_Clone(PRTASN1CONTEXTTAG pThis, PCRTASN1CONTEXTTAG pSrc, uint32_t uTag)
+{
+ Assert(pSrc->Asn1Core.uTag == uTag || !RTASN1CORE_IS_PRESENT(&pSrc->Asn1Core)); RT_NOREF_PV(uTag);
+ return RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+}
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-cursor.cpp b/src/VBox/Runtime/common/asn1/asn1-cursor.cpp
new file mode 100644
index 00000000..3150e67c
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-cursor.cpp
@@ -0,0 +1,678 @@
+/* $Id: asn1-cursor.cpp $ */
+/** @file
+ * IPRT - ASN.1, Basic Operations.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/asm.h>
+#include <iprt/alloca.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def RTASN1_MAX_NESTING
+ * The maximum nesting depth we allow. This limit is enforced to avoid running
+ * out of stack due to malformed ASN.1 input.
+ *
+ * For reference, 'RTSignTool verify-exe RTSignTool.exe', requires a value of 15
+ * to work without hitting the limit for signatures with simple timestamps, and
+ * 23 (amd64/rel = ~3KB) for the new microsoft timestamp counter signatures.
+ */
+#ifdef IN_RING3
+# define RTASN1_MAX_NESTING 64
+#else
+# define RTASN1_MAX_NESTING 32
+#endif
+
+
+
+RTDECL(PRTASN1CURSOR) RTAsn1CursorInitPrimary(PRTASN1CURSORPRIMARY pPrimaryCursor, void const *pvFirst, uint32_t cb,
+ PRTERRINFO pErrInfo, PCRTASN1ALLOCATORVTABLE pAllocator, uint32_t fFlags,
+ const char *pszErrorTag)
+{
+ pPrimaryCursor->Cursor.pbCur = (uint8_t const *)pvFirst;
+ pPrimaryCursor->Cursor.cbLeft = cb;
+ pPrimaryCursor->Cursor.fFlags = (uint8_t)fFlags; Assert(fFlags <= UINT8_MAX);
+ pPrimaryCursor->Cursor.cDepth = 0;
+ pPrimaryCursor->Cursor.abReserved[0] = 0;
+ pPrimaryCursor->Cursor.abReserved[1] = 0;
+ pPrimaryCursor->Cursor.pPrimary = pPrimaryCursor;
+ pPrimaryCursor->Cursor.pUp = NULL;
+ pPrimaryCursor->Cursor.pszErrorTag = pszErrorTag;
+ pPrimaryCursor->pErrInfo = pErrInfo;
+ pPrimaryCursor->pAllocator = pAllocator;
+ pPrimaryCursor->pbFirst = (uint8_t const *)pvFirst;
+ return &pPrimaryCursor->Cursor;
+}
+
+
+RTDECL(int) RTAsn1CursorInitSub(PRTASN1CURSOR pParent, uint32_t cb, PRTASN1CURSOR pChild, const char *pszErrorTag)
+{
+ AssertReturn(pParent->pPrimary, VERR_ASN1_INTERNAL_ERROR_1);
+ AssertReturn(pParent->pbCur, VERR_ASN1_INTERNAL_ERROR_2);
+
+ pChild->pbCur = pParent->pbCur;
+ pChild->cbLeft = cb;
+ pChild->fFlags = pParent->fFlags & ~RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH;
+ pChild->cDepth = pParent->cDepth + 1;
+ AssertReturn(pChild->cDepth < RTASN1_MAX_NESTING, VERR_ASN1_TOO_DEEPLY_NESTED);
+ pChild->abReserved[0] = 0;
+ pChild->abReserved[1] = 0;
+ pChild->pPrimary = pParent->pPrimary;
+ pChild->pUp = pParent;
+ pChild->pszErrorTag = pszErrorTag;
+
+ AssertReturn(pParent->cbLeft >= cb, VERR_ASN1_INTERNAL_ERROR_3);
+ pParent->pbCur += cb;
+ pParent->cbLeft -= cb;
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1CursorInitSubFromCore(PRTASN1CURSOR pParent, PRTASN1CORE pAsn1Core,
+ PRTASN1CURSOR pChild, const char *pszErrorTag)
+{
+ AssertReturn(pParent->pPrimary, VERR_ASN1_INTERNAL_ERROR_1);
+ AssertReturn(pParent->pbCur, VERR_ASN1_INTERNAL_ERROR_2);
+
+ pChild->pbCur = pAsn1Core->uData.pu8;
+ pChild->cbLeft = pAsn1Core->cb;
+ pChild->fFlags = pParent->fFlags & ~RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH;
+ pChild->cDepth = pParent->cDepth + 1;
+ AssertReturn(pChild->cDepth < RTASN1_MAX_NESTING, VERR_ASN1_TOO_DEEPLY_NESTED);
+ pChild->abReserved[0] = 0;
+ pChild->abReserved[1] = 0;
+ pChild->pPrimary = pParent->pPrimary;
+ pChild->pUp = pParent;
+ pChild->pszErrorTag = pszErrorTag;
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1CursorSetInfoV(PRTASN1CURSOR pCursor, int rc, const char *pszMsg, va_list va)
+{
+ PRTERRINFO pErrInfo = pCursor->pPrimary->pErrInfo;
+ if (pErrInfo)
+ {
+ /* Format the message. */
+ RTErrInfoSetV(pErrInfo, rc, pszMsg, va);
+
+ /* Add the prefixes. This isn't the fastest way, but it's the one
+ which eats the least stack. */
+ char *pszBuf = pErrInfo->pszMsg;
+ size_t cbBuf = pErrInfo->cbMsg;
+ if (pszBuf && cbBuf > 32)
+ {
+ size_t cbMove = strlen(pszBuf) + 1;
+
+ /* Make sure there is a ': '. */
+ bool fFirst = false;
+ if (pszMsg[0] != '%' || pszMsg[1] != 's' || pszMsg[2] != ':')
+ {
+ if (cbMove + 2 < cbBuf)
+ {
+ memmove(pszBuf + 2, pszBuf, cbMove);
+ pszBuf[0] = ':';
+ pszBuf[1] = ' ';
+ cbMove += 2;
+ fFirst = true;
+ }
+ }
+
+ /* Add the prefixes from the cursor chain. */
+ while (pCursor)
+ {
+ if (pCursor->pszErrorTag)
+ {
+ size_t cchErrorTag = strlen(pCursor->pszErrorTag);
+ if (cchErrorTag + !fFirst + cbMove > cbBuf)
+ break;
+ memmove(pszBuf + cchErrorTag + !fFirst, pszBuf, cbMove);
+ memcpy(pszBuf, pCursor->pszErrorTag, cchErrorTag);
+ if (!fFirst)
+ pszBuf[cchErrorTag] = '.';
+ cbMove += cchErrorTag + !fFirst;
+ fFirst = false;
+ }
+ pCursor = pCursor->pUp;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1CursorSetInfo(PRTASN1CURSOR pCursor, int rc, const char *pszMsg, ...)
+{
+ va_list va;
+ va_start(va, pszMsg);
+ rc = RTAsn1CursorSetInfoV(pCursor, rc, pszMsg, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(bool) RTAsn1CursorIsEnd(PRTASN1CURSOR pCursor)
+{
+ if (pCursor->cbLeft == 0)
+ return true;
+ if (!(pCursor->fFlags & RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH))
+ return false;
+ return pCursor->cbLeft >= 2
+ && pCursor->pbCur[0] == 0
+ && pCursor->pbCur[1] == 0;
+}
+
+
+RTDECL(int) RTAsn1CursorCheckEnd(PRTASN1CURSOR pCursor)
+{
+ if (!(pCursor->fFlags & RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH))
+ {
+ if (pCursor->cbLeft == 0)
+ return VINF_SUCCESS;
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END,
+ "%u (%#x) bytes left over", pCursor->cbLeft, pCursor->cbLeft);
+ }
+
+ /*
+ * There must be exactly two zero bytes here.
+ */
+ if (pCursor->cbLeft == 2)
+ {
+ if ( pCursor->pbCur[0] == 0
+ && pCursor->pbCur[1] == 0)
+ return VINF_SUCCESS;
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END,
+ "%u (%#x) bytes left over [indef: %.*Rhxs]",
+ pCursor->cbLeft, pCursor->cbLeft, RT_MIN(pCursor->cbLeft, 16), pCursor->pbCur);
+ }
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END,
+ "%u (%#x) byte(s) left over, exepcted exactly two zero bytes [indef len]",
+ pCursor->cbLeft, pCursor->cbLeft);
+}
+
+
+/**
+ * Worker for RTAsn1CursorCheckSeqEnd and RTAsn1CursorCheckSetEnd.
+ */
+static int rtAsn1CursorCheckSeqOrSetEnd(PRTASN1CURSOR pCursor, PRTASN1CORE pAsn1Core)
+{
+ if (!(pAsn1Core->fFlags & RTASN1CORE_F_INDEFINITE_LENGTH))
+ {
+ if (pCursor->cbLeft == 0)
+ return VINF_SUCCESS;
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END,
+ "%u (%#x) bytes left over", pCursor->cbLeft, pCursor->cbLeft);
+ }
+
+ if (pCursor->cbLeft >= 2)
+ {
+ if ( pCursor->pbCur[0] == 0
+ && pCursor->pbCur[1] == 0)
+ {
+ pAsn1Core->cb = (uint32_t)(pCursor->pbCur - pAsn1Core->uData.pu8);
+ pCursor->cbLeft -= 2;
+ pCursor->pbCur += 2;
+
+ PRTASN1CURSOR pParentCursor = pCursor->pUp;
+ if ( pParentCursor
+ && (pParentCursor->fFlags & RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH))
+ {
+ pParentCursor->pbCur -= pCursor->cbLeft;
+ pParentCursor->cbLeft += pCursor->cbLeft;
+ return VINF_SUCCESS;
+ }
+
+ if (pCursor->cbLeft == 0)
+ return VINF_SUCCESS;
+
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END,
+ "%u (%#x) bytes left over (parent not indefinite length)", pCursor->cbLeft, pCursor->cbLeft);
+ }
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END, "%u (%#x) bytes left over [indef: %.*Rhxs]",
+ pCursor->cbLeft, pCursor->cbLeft, RT_MIN(pCursor->cbLeft, 16), pCursor->pbCur);
+ }
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NOT_AT_END,
+ "1 byte left over, expected two for indefinite length end-of-content sequence");
+}
+
+
+RTDECL(int) RTAsn1CursorCheckSeqEnd(PRTASN1CURSOR pCursor, PRTASN1SEQUENCECORE pSeqCore)
+{
+ return rtAsn1CursorCheckSeqOrSetEnd(pCursor, &pSeqCore->Asn1Core);
+}
+
+
+RTDECL(int) RTAsn1CursorCheckSetEnd(PRTASN1CURSOR pCursor, PRTASN1SETCORE pSetCore)
+{
+ return rtAsn1CursorCheckSeqOrSetEnd(pCursor, &pSetCore->Asn1Core);
+}
+
+
+RTDECL(int) RTAsn1CursorCheckOctStrEnd(PRTASN1CURSOR pCursor, PRTASN1OCTETSTRING pOctetString)
+{
+ return rtAsn1CursorCheckSeqOrSetEnd(pCursor, &pOctetString->Asn1Core);
+}
+
+
+RTDECL(PRTASN1ALLOCATION) RTAsn1CursorInitAllocation(PRTASN1CURSOR pCursor, PRTASN1ALLOCATION pAllocation)
+{
+ pAllocation->cbAllocated = 0;
+ pAllocation->cReallocs = 0;
+ pAllocation->uReserved0 = 0;
+ pAllocation->pAllocator = pCursor->pPrimary->pAllocator;
+ return pAllocation;
+}
+
+
+RTDECL(PRTASN1ARRAYALLOCATION) RTAsn1CursorInitArrayAllocation(PRTASN1CURSOR pCursor, PRTASN1ARRAYALLOCATION pAllocation,
+ size_t cbEntry)
+{
+ Assert(cbEntry >= sizeof(RTASN1CORE));
+ Assert(cbEntry < _1M);
+ Assert(RT_ALIGN_Z(cbEntry, sizeof(void *)) == cbEntry);
+ pAllocation->cbEntry = (uint32_t)cbEntry;
+ pAllocation->cPointersAllocated = 0;
+ pAllocation->cEntriesAllocated = 0;
+ pAllocation->cResizeCalls = 0;
+ pAllocation->uReserved0 = 0;
+ pAllocation->pAllocator = pCursor->pPrimary->pAllocator;
+ return pAllocation;
+}
+
+
+RTDECL(int) RTAsn1CursorReadHdr(PRTASN1CURSOR pCursor, PRTASN1CORE pAsn1Core, const char *pszErrorTag)
+{
+ /*
+ * Initialize the return structure in case of failure.
+ */
+ pAsn1Core->uTag = 0;
+ pAsn1Core->fClass = 0;
+ pAsn1Core->uRealTag = 0;
+ pAsn1Core->fRealClass = 0;
+ pAsn1Core->cbHdr = 0;
+ pAsn1Core->cb = 0;
+ pAsn1Core->fFlags = 0;
+ pAsn1Core->uData.pv = NULL;
+ pAsn1Core->pOps = NULL;
+
+ /*
+ * The header has at least two bytes: Type & length.
+ */
+ if (pCursor->cbLeft >= 2)
+ {
+ uint32_t uTag = pCursor->pbCur[0];
+ uint32_t cb = pCursor->pbCur[1];
+ pCursor->cbLeft -= 2;
+ pCursor->pbCur += 2;
+
+ pAsn1Core->uRealTag = pAsn1Core->uTag = uTag & ASN1_TAG_MASK;
+ pAsn1Core->fRealClass = pAsn1Core->fClass = uTag & ~ASN1_TAG_MASK;
+ pAsn1Core->cbHdr = 2;
+ if ((uTag & ASN1_TAG_MASK) == ASN1_TAG_USE_LONG_FORM)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_LONG_TAG,
+ "%s: Implement parsing of tags > 30: %#x (length=%#x)", pszErrorTag, uTag, cb);
+
+ /* Extended length field? */
+ if (cb & RT_BIT(7))
+ {
+ if (cb != RT_BIT(7))
+ {
+ /* Definite form. */
+ uint8_t cbEnc = cb & 0x7f;
+ if (cbEnc > pCursor->cbLeft)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH_ENCODING,
+ "%s: Extended BER length field longer than available data: %#x vs %#x (uTag=%#x)",
+ pszErrorTag, cbEnc, pCursor->cbLeft, uTag);
+ switch (cbEnc)
+ {
+ case 1:
+ cb = pCursor->pbCur[0];
+ break;
+ case 2:
+ cb = RT_MAKE_U16(pCursor->pbCur[1], pCursor->pbCur[0]);
+ break;
+ case 3:
+ cb = RT_MAKE_U32_FROM_U8(pCursor->pbCur[2], pCursor->pbCur[1], pCursor->pbCur[0], 0);
+ break;
+ case 4:
+ cb = RT_MAKE_U32_FROM_U8(pCursor->pbCur[3], pCursor->pbCur[2], pCursor->pbCur[1], pCursor->pbCur[0]);
+ break;
+ default:
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH_ENCODING,
+ "%s: Too long/short extended BER length field: %#x (uTag=%#x)",
+ pszErrorTag, cbEnc, uTag);
+ }
+ pCursor->cbLeft -= cbEnc;
+ pCursor->pbCur += cbEnc;
+ pAsn1Core->cbHdr += cbEnc;
+
+ /* Check the length encoding efficiency (T-REC-X.690-200811 10.1, 9.1). */
+ if (pCursor->fFlags & (RTASN1CURSOR_FLAGS_DER | RTASN1CURSOR_FLAGS_CER))
+ {
+ if (cb <= 0x7f)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH_ENCODING,
+ "%s: Invalid DER/CER length encoding: cbEnc=%u cb=%#x uTag=%#x",
+ pszErrorTag, cbEnc, cb, uTag);
+ uint8_t cbNeeded;
+ if (cb <= 0x000000ff) cbNeeded = 1;
+ else if (cb <= 0x0000ffff) cbNeeded = 2;
+ else if (cb <= 0x00ffffff) cbNeeded = 3;
+ else cbNeeded = 4;
+ if (cbNeeded != cbEnc)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH_ENCODING,
+ "%s: Invalid DER/CER length encoding: cb=%#x uTag=%#x cbEnc=%u cbNeeded=%u",
+ pszErrorTag, cb, uTag, cbEnc, cbNeeded);
+ }
+ }
+ /* Indefinite form. */
+ else if (pCursor->fFlags & RTASN1CURSOR_FLAGS_DER)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_INDEFINITE_LENGTH,
+ "%s: Indefinite length form not allowed in DER mode (uTag=%#x).", pszErrorTag, uTag);
+ else if (!(uTag & ASN1_TAGFLAG_CONSTRUCTED))
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_INDEFINITE_LENGTH,
+ "%s: Indefinite BER/CER encoding is for non-constructed tag (uTag=%#x)", pszErrorTag, uTag);
+ else if ( uTag != (ASN1_TAG_SEQUENCE | ASN1_TAGFLAG_CONSTRUCTED)
+ && uTag != (ASN1_TAG_SET | ASN1_TAGFLAG_CONSTRUCTED)
+ && (uTag & (ASN1_TAGFLAG_CONSTRUCTED | ASN1_TAGCLASS_CONTEXT))
+ != (ASN1_TAGFLAG_CONSTRUCTED | ASN1_TAGCLASS_CONTEXT) )
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_INDEFINITE_LENGTH,
+ "%s: Indefinite BER/CER encoding not supported for this tag (uTag=%#x)", pszErrorTag, uTag);
+ else if (pCursor->fFlags & RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_INDEFINITE_LENGTH,
+ "%s: Nested indefinite BER/CER encoding. (uTag=%#x)", pszErrorTag, uTag);
+ else if (pCursor->cbLeft < 2)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_INDEFINITE_LENGTH,
+ "%s: Too little data left for indefinite BER/CER encoding (uTag=%#x)", pszErrorTag, uTag);
+ else
+ {
+ pCursor->fFlags |= RTASN1CURSOR_FLAGS_INDEFINITE_LENGTH;
+ pAsn1Core->fFlags |= RTASN1CORE_F_INDEFINITE_LENGTH;
+ cb = pCursor->cbLeft; /* Start out with the whole sequence, adjusted later upon reach the end. */
+ }
+ }
+ /* else if (cb == 0 && uTag == 0) { end of content } - callers handle this */
+
+ /* Check if the length makes sense. */
+ if (cb > pCursor->cbLeft)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_BAD_LENGTH,
+ "%s: BER value length out of bounds: %#x (max=%#x uTag=%#x)",
+ pszErrorTag, cb, pCursor->cbLeft, uTag);
+
+ pAsn1Core->fFlags |= RTASN1CORE_F_PRESENT | RTASN1CORE_F_DECODED_CONTENT;
+ pAsn1Core->cb = cb;
+ pAsn1Core->uData.pv = (void *)pCursor->pbCur;
+ return VINF_SUCCESS;
+ }
+
+ if (pCursor->cbLeft)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TOO_LITTLE_DATA_LEFT,
+ "%s: Too little data left to form a valid BER header", pszErrorTag);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_NO_MORE_DATA,
+ "%s: No more data reading BER header", pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorMatchTagClassFlagsEx(PRTASN1CURSOR pCursor, PRTASN1CORE pAsn1Core, uint32_t uTag, uint32_t fClass,
+ bool fString, uint32_t fFlags, const char *pszErrorTag, const char *pszWhat)
+{
+ if (pAsn1Core->uTag == uTag)
+ {
+ if (pAsn1Core->fClass == fClass)
+ return VINF_SUCCESS;
+ if ( fString
+ && pAsn1Core->fClass == (fClass | ASN1_TAGFLAG_CONSTRUCTED))
+ {
+ if (!(pCursor->fFlags & (RTASN1CURSOR_FLAGS_DER | RTASN1CURSOR_FLAGS_CER)))
+ return VINF_SUCCESS;
+ if (pCursor->fFlags & RTASN1CURSOR_FLAGS_CER)
+ {
+ if (pAsn1Core->cb > 1000)
+ return VINF_SUCCESS;
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING,
+ "%s: Constructed %s only allowed for >1000 byte in CER encoding: cb=%#x uTag=%#x fClass=%#x",
+ pszErrorTag, pszWhat, pAsn1Core->cb, pAsn1Core->uTag, pAsn1Core->fClass);
+ }
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING,
+ "%s: DER encoding does not allow constructed %s (cb=%#x uTag=%#x fClass=%#x)",
+ pszErrorTag, pszWhat, pAsn1Core->cb, pAsn1Core->uTag, pAsn1Core->fClass);
+ }
+ }
+
+ if (fFlags & RTASN1CURSOR_GET_F_IMPLICIT)
+ {
+ pAsn1Core->fFlags |= RTASN1CORE_F_TAG_IMPLICIT;
+ pAsn1Core->uRealTag = uTag;
+ pAsn1Core->fRealClass = fClass;
+ return VINF_SUCCESS;
+ }
+
+ return RTAsn1CursorSetInfo(pCursor, pAsn1Core->uTag != uTag ? VERR_ASN1_CURSOR_TAG_MISMATCH : VERR_ASN1_CURSOR_TAG_FLAG_CLASS_MISMATCH,
+ "%s: Unexpected %s type/flags: %#x/%#x (expected %#x/%#x)",
+ pszErrorTag, pszWhat, pAsn1Core->uTag, pAsn1Core->fClass, uTag, fClass);
+}
+
+
+
+static int rtAsn1CursorGetXxxxCursor(PRTASN1CURSOR pCursor, uint32_t fFlags, uint32_t uTag, uint8_t fClass,
+ PRTASN1CORE pAsn1Core, PRTASN1CURSOR pRetCursor,
+ const char *pszErrorTag, const char *pszWhat)
+{
+ int rc = RTAsn1CursorReadHdr(pCursor, pAsn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pAsn1Core->uTag == uTag
+ && pAsn1Core->fClass == fClass)
+ rc = VINF_SUCCESS;
+ else if (fFlags & RTASN1CURSOR_GET_F_IMPLICIT)
+ {
+ pAsn1Core->fFlags |= RTASN1CORE_F_TAG_IMPLICIT;
+ pAsn1Core->uRealTag = uTag;
+ pAsn1Core->fRealClass = fClass;
+ rc = VINF_SUCCESS;
+ }
+ else
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING,
+ "%s: Unexpected %s type/flags: %#x/%#x (expected %#x/%#x)",
+ pszErrorTag, pszWhat, pAsn1Core->uTag, pAsn1Core->fClass, uTag, fClass);
+ rc = RTAsn1CursorInitSub(pCursor, pAsn1Core->cb, pRetCursor, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ pAsn1Core->fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ return VINF_SUCCESS;
+ }
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1CursorGetSequenceCursor(PRTASN1CURSOR pCursor, uint32_t fFlags,
+ PRTASN1SEQUENCECORE pSeqCore, PRTASN1CURSOR pSeqCursor, const char *pszErrorTag)
+{
+ return rtAsn1CursorGetXxxxCursor(pCursor, fFlags, ASN1_TAG_SEQUENCE, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED,
+ &pSeqCore->Asn1Core, pSeqCursor, pszErrorTag, "sequence");
+}
+
+
+RTDECL(int) RTAsn1CursorGetSetCursor(PRTASN1CURSOR pCursor, uint32_t fFlags,
+ PRTASN1SETCORE pSetCore, PRTASN1CURSOR pSetCursor, const char *pszErrorTag)
+{
+ return rtAsn1CursorGetXxxxCursor(pCursor, fFlags, ASN1_TAG_SET, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED,
+ &pSetCore->Asn1Core, pSetCursor, pszErrorTag, "set");
+}
+
+
+RTDECL(int) RTAsn1CursorGetContextTagNCursor(PRTASN1CURSOR pCursor, uint32_t fFlags, uint32_t uExpectedTag,
+ PCRTASN1COREVTABLE pVtable, PRTASN1CONTEXTTAG pCtxTag, PRTASN1CURSOR pCtxTagCursor,
+ const char *pszErrorTag)
+{
+ int rc = rtAsn1CursorGetXxxxCursor(pCursor, fFlags, uExpectedTag, ASN1_TAGCLASS_CONTEXT | ASN1_TAGFLAG_CONSTRUCTED,
+ &pCtxTag->Asn1Core, pCtxTagCursor, pszErrorTag, "ctx tag");
+ pCtxTag->Asn1Core.pOps = pVtable;
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1CursorPeek(PRTASN1CURSOR pCursor, PRTASN1CORE pAsn1Core)
+{
+ uint32_t cbSavedLeft = pCursor->cbLeft;
+ uint8_t const *pbSavedCur = pCursor->pbCur;
+ uint8_t const fSavedFlags = pCursor->fFlags;
+ PRTERRINFO const pErrInfo = pCursor->pPrimary->pErrInfo;
+ pCursor->pPrimary->pErrInfo = NULL;
+
+ int rc = RTAsn1CursorReadHdr(pCursor, pAsn1Core, "peek");
+
+ pCursor->pPrimary->pErrInfo = pErrInfo;
+ pCursor->pbCur = pbSavedCur;
+ pCursor->cbLeft = cbSavedLeft;
+ pCursor->fFlags = fSavedFlags;
+ return rc;
+}
+
+
+RTDECL(bool) RTAsn1CursorIsNextEx(PRTASN1CURSOR pCursor, uint32_t uTag, uint8_t fClass)
+{
+ RTASN1CORE Asn1Core;
+ int rc = RTAsn1CursorPeek(pCursor, &Asn1Core);
+ if (RT_SUCCESS(rc))
+ return uTag == Asn1Core.uTag
+ && fClass == Asn1Core.fClass;
+ return false;
+}
+
+
+/** @name Legacy Interfaces.
+ * @{ */
+RTDECL(int) RTAsn1CursorGetCore(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1CORE pAsn1Core, const char *pszErrorTag)
+{
+ return RTAsn1Core_DecodeAsn1(pCursor, fFlags, pAsn1Core, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetNull(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1NULL pNull, const char *pszErrorTag)
+{
+ return RTAsn1Null_DecodeAsn1(pCursor, fFlags, pNull, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetInteger(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1INTEGER pInteger, const char *pszErrorTag)
+{
+ return RTAsn1Integer_DecodeAsn1(pCursor, fFlags, pInteger, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetBoolean(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1BOOLEAN pBoolean, const char *pszErrorTag)
+{
+ return RTAsn1Boolean_DecodeAsn1(pCursor, fFlags, pBoolean, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetObjId(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1OBJID pObjId, const char *pszErrorTag)
+{
+ return RTAsn1ObjId_DecodeAsn1(pCursor, fFlags, pObjId, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetTime(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1TIME pTime, const char *pszErrorTag)
+{
+ return RTAsn1Time_DecodeAsn1(pCursor, fFlags, pTime, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetBitStringEx(PRTASN1CURSOR pCursor, uint32_t fFlags, uint32_t cMaxBits, PRTASN1BITSTRING pBitString,
+ const char *pszErrorTag)
+{
+ return RTAsn1BitString_DecodeAsn1Ex(pCursor, fFlags, cMaxBits, pBitString, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetBitString(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1BITSTRING pBitString, const char *pszErrorTag)
+{
+ return RTAsn1BitString_DecodeAsn1(pCursor, fFlags, pBitString, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetOctetString(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1OCTETSTRING pOctetString,
+ const char *pszErrorTag)
+{
+ return RTAsn1OctetString_DecodeAsn1(pCursor, fFlags, pOctetString, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetString(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pString, const char *pszErrorTag)
+{
+ return RTAsn1String_DecodeAsn1(pCursor, fFlags, pString, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetIa5String(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pString, const char *pszErrorTag)
+{
+ return RTAsn1Ia5String_DecodeAsn1(pCursor, fFlags, pString, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetUtf8String(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pString, const char *pszErrorTag)
+{
+ return RTAsn1Utf8String_DecodeAsn1(pCursor, fFlags, pString, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetBmpString(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pString, const char *pszErrorTag)
+{
+ return RTAsn1BmpString_DecodeAsn1(pCursor, fFlags, pString, pszErrorTag);
+}
+
+
+RTDECL(int) RTAsn1CursorGetDynType(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1DYNTYPE pDynType, const char *pszErrorTag)
+{
+ return RTAsn1DynType_DecodeAsn1(pCursor, fFlags, pDynType, pszErrorTag);
+}
+/** @} */
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-default-allocator.cpp b/src/VBox/Runtime/common/asn1/asn1-default-allocator.cpp
new file mode 100644
index 00000000..ba04a854
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-default-allocator.cpp
@@ -0,0 +1,231 @@
+/* $Id: asn1-default-allocator.cpp $ */
+/** @file
+ * IPRT - ASN.1, Default Allocator.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/mem.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+/**
+ * Aligns allocation sizes a little.
+ *
+ * @returns Aligned size.
+ * @param cb Requested size.
+ */
+static size_t rtAsn1DefaultAllocator_AlignSize(size_t cb)
+{
+ if (cb >= 64)
+ return RT_ALIGN_Z(cb, 64);
+ if (cb >= 32)
+ return RT_ALIGN_Z(cb, 32);
+ if (cb >= 16)
+ return RT_ALIGN_Z(cb, 16);
+ return cb;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFree} */
+static DECLCALLBACK(void) rtAsn1DefaultAllocator_Free(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, void *pv)
+{
+ RT_NOREF_PV(pThis);
+ RTMemFree(pv);
+ pAllocation->cbAllocated = 0;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnAlloc} */
+static DECLCALLBACK(int) rtAsn1DefaultAllocator_Alloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation,
+ void **ppv, size_t cb)
+{
+ size_t cbAlloc = rtAsn1DefaultAllocator_AlignSize(cb);
+ void *pv = RTMemAllocZ(cbAlloc);
+ if (pv)
+ {
+ *ppv = pv;
+ pAllocation->cbAllocated = (uint32_t)cbAlloc;
+ return VINF_SUCCESS;
+ }
+ RT_NOREF_PV(pThis);
+ return VERR_NO_MEMORY;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnRealloc} */
+static DECLCALLBACK(int) rtAsn1DefaultAllocator_Realloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation,
+ void *pvOld, void **ppvNew, size_t cbNew)
+{
+ Assert(pvOld);
+ Assert(cbNew);
+ size_t cbAlloc = rtAsn1DefaultAllocator_AlignSize(cbNew);
+ void *pv = RTMemRealloc(pvOld, cbAlloc);
+ if (pv)
+ {
+ *ppvNew = pv;
+ pAllocation->cbAllocated = (uint32_t)cbAlloc;
+ return VINF_SUCCESS;
+ }
+ RT_NOREF_PV(pThis);
+ return VERR_NO_MEMORY;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFreeArray} */
+static DECLCALLBACK(void) rtAsn1DefaultAllocator_FreeArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation,
+ void **papvArray)
+{
+ RT_NOREF_PV(pThis);
+ Assert(papvArray);
+ Assert(pAllocation->cbEntry);
+
+ uint32_t i = pAllocation->cEntriesAllocated;
+ while (i-- > 0)
+ RTMemFree(papvArray[i]);
+ RTMemFree(papvArray);
+
+ pAllocation->cEntriesAllocated = 0;
+ pAllocation->cPointersAllocated = 0;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnGrowArray} */
+static DECLCALLBACK(int) rtAsn1DefaultAllocator_GrowArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation,
+ void ***ppapvArray, uint32_t cMinEntries)
+{
+ RT_NOREF_PV(pThis);
+
+ /*
+ * Resize the pointer array. We do chunks of 64 bytes for now.
+ */
+ void **papvArray = *ppapvArray;
+ uint32_t cPointers = RT_ALIGN_32(cMinEntries, 64 / sizeof(void *));
+ if (cPointers > pAllocation->cPointersAllocated)
+ {
+ void *pvPointers = RTMemRealloc(papvArray, cPointers * sizeof(void *));
+ if (pvPointers)
+ { /* likely */ }
+ else if (cMinEntries > pAllocation->cPointersAllocated)
+ {
+ cPointers = cMinEntries;
+ pvPointers = RTMemRealloc(*ppapvArray, cPointers * sizeof(void *));
+ if (!pvPointers)
+ return VERR_NO_MEMORY;
+ }
+ else
+ {
+ cPointers = pAllocation->cPointersAllocated;
+ pvPointers = papvArray;
+ }
+
+ *ppapvArray = papvArray = (void **)pvPointers;
+ RT_BZERO(&papvArray[pAllocation->cPointersAllocated], (cPointers - pAllocation->cPointersAllocated) * sizeof(void *));
+ pAllocation->cPointersAllocated = cPointers;
+ }
+
+ /*
+ * Add more entries. Do multiple as the array grows.
+ *
+ * Note! We could possibly optimize this by allocating slabs of entries and
+ * slice them up. However, keep things as simple as possible for now.
+ */
+ uint32_t cEntries = cMinEntries;
+ if (cEntries > 2)
+ {
+ if (cEntries > 8)
+ cEntries = RT_ALIGN_32(cEntries, 4);
+ else
+ cEntries = RT_ALIGN_32(cEntries, 2);
+ cEntries = RT_MIN(cEntries, cPointers);
+ Assert(cEntries >= cMinEntries);
+ }
+ Assert(cEntries <= pAllocation->cPointersAllocated);
+
+ while (pAllocation->cEntriesAllocated < cEntries)
+ {
+ void *pv;
+ papvArray[pAllocation->cEntriesAllocated] = pv = RTMemAllocZ(pAllocation->cbEntry);
+ if (pv)
+ pAllocation->cEntriesAllocated++;
+ else if (pAllocation->cEntriesAllocated >= cMinEntries)
+ break;
+ else
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnShrinkArray} */
+static DECLCALLBACK(void) rtAsn1DefaultAllocator_ShrinkArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation,
+ void ***ppapvArray, uint32_t cNew, uint32_t cCurrent)
+{
+ RT_NOREF_PV(pThis);
+
+ /*
+ * For now we only zero the entries being removed.
+ */
+ void **papvArray = *ppapvArray;
+ while (cNew < cCurrent)
+ {
+ RT_BZERO(papvArray[cNew], pAllocation->cbEntry);
+ cNew++;
+ }
+}
+
+
+
+/** The default ASN.1 allocator. */
+#if 1 || !defined(IN_RING3) || defined(DOXYGEN_RUNNING)
+RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1DefaultAllocator =
+#else
+RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1DefaultAllocatorDisabled =
+#endif
+{
+ rtAsn1DefaultAllocator_Free,
+ rtAsn1DefaultAllocator_Alloc,
+ rtAsn1DefaultAllocator_Realloc,
+ rtAsn1DefaultAllocator_FreeArray,
+ rtAsn1DefaultAllocator_GrowArray,
+ rtAsn1DefaultAllocator_ShrinkArray
+};
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-dump.cpp b/src/VBox/Runtime/common/asn1/asn1-dump.cpp
new file mode 100644
index 00000000..e75d379e
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-dump.cpp
@@ -0,0 +1,644 @@
+/* $Id: asn1-dump.cpp $ */
+/** @file
+ * IPRT - ASN.1, Structure Dumper.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#ifdef IN_RING3
+# include <iprt/stream.h>
+#endif
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Dump data structure.
+ */
+typedef struct RTASN1DUMPDATA
+{
+ /** RTASN1DUMP_F_XXX. */
+ uint32_t fFlags;
+ /** The printfv like output function. */
+ PFNRTDUMPPRINTFV pfnPrintfV;
+ /** PrintfV user argument. */
+ void *pvUser;
+} RTASN1DUMPDATA;
+/** Pointer to a dump data structure. */
+typedef RTASN1DUMPDATA *PRTASN1DUMPDATA;
+
+
+#ifndef IN_SUP_HARDENED_R3
+
+/*
+ * Since we're the only user of OIDs, this stuff lives here.
+ * Should that ever change, this code needs to move elsewhere and get it's own public API.
+ */
+# include "oiddb.h"
+
+
+/**
+ * Searches a range in the big table for a key.
+ *
+ * @returns Pointer to the matching entry. NULL if not found.
+ * @param iEntry The start of the range.
+ * @param cEntries The number of entries in the range.
+ * @param uKey The key to find.
+ */
+DECLINLINE(PCRTOIDENTRYBIG) rtOidDbLookupBig(uint32_t iEntry, uint32_t cEntries, uint32_t uKey)
+{
+ /* Not worth doing binary search here, too few entries. */
+ while (cEntries-- > 0)
+ {
+ uint32_t const uThisKey = g_aBigOidTable[iEntry].uKey;
+ if (uThisKey >= uKey)
+ {
+ if (uThisKey == uKey)
+ return &g_aBigOidTable[iEntry];
+ break;
+ }
+ iEntry++;
+ }
+ return NULL;
+}
+
+
+/**
+ * Searches a range in the small table for a key.
+ *
+ * @returns Pointer to the matching entry. NULL if not found.
+ * @param iEntry The start of the range.
+ * @param cEntries The number of entries in the range.
+ * @param uKey The key to find.
+ */
+DECLINLINE(PCRTOIDENTRYSMALL) rtOidDbLookupSmall(uint32_t iEntry, uint32_t cEntries, uint32_t uKey)
+{
+ if (cEntries < 6)
+ {
+ /* Linear search for small ranges. */
+ while (cEntries-- > 0)
+ {
+ uint32_t const uThisKey = g_aSmallOidTable[iEntry].uKey;
+ if (uThisKey >= uKey)
+ {
+ if (uThisKey == uKey)
+ return &g_aSmallOidTable[iEntry];
+ break;
+ }
+ iEntry++;
+ }
+ }
+ else
+ {
+ /* Binary search. */
+ uint32_t iEnd = iEntry + cEntries;
+ for (;;)
+ {
+ uint32_t const i = iEntry + (iEnd - iEntry) / 2;
+ uint32_t const uThisKey = g_aSmallOidTable[i].uKey;
+ if (uThisKey < uKey)
+ {
+ iEntry = i + 1;
+ if (iEntry >= iEnd)
+ break;
+ }
+ else if (uThisKey > uKey)
+ {
+ iEnd = i;
+ if (iEnd <= iEntry)
+ break;
+ }
+ else
+ return &g_aSmallOidTable[i];
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Queries the name for an object identifier.
+ *
+ * @returns IPRT status code (VINF_SUCCESS, VERR_NOT_FOUND,
+ * VERR_BUFFER_OVERFLOW)
+ * @param pauComponents The components making up the object ID.
+ * @param cComponents The number of components.
+ * @param pszDst Where to store the name if found.
+ * @param cbDst The size of the destination buffer.
+ */
+static int rtOidDbQueryObjIdName(uint32_t const *pauComponents, uint8_t cComponents, char *pszDst, size_t cbDst)
+{
+ int rc = VERR_NOT_FOUND;
+ if (cComponents > 0)
+ {
+ /*
+ * The top level is always in the small table as the range is restricted to 0,1,2.
+ */
+ bool fBigTable = false;
+ uint32_t cEntries = RT_MIN(RT_ELEMENTS(g_aSmallOidTable), 3);
+ uint32_t iEntry = 0;
+ for (;;)
+ {
+ uint32_t const uKey = *pauComponents++;
+ if (!fBigTable)
+ {
+ PCRTOIDENTRYSMALL pSmallHit = rtOidDbLookupSmall(iEntry, cEntries, uKey);
+ if (pSmallHit)
+ {
+ if (--cComponents == 0)
+ {
+ if (RTBldProgStrTabQueryString(&g_OidDbStrTab, pSmallHit->offString,
+ pSmallHit->cchString, pszDst, cbDst) >= 0)
+ return VINF_SUCCESS;
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cEntries = pSmallHit->cChildren;
+ if (cEntries)
+ {
+ iEntry = pSmallHit->idxChildren;
+ fBigTable = pSmallHit->fBigTable;
+ continue;
+ }
+ }
+ }
+ else
+ {
+ PCRTOIDENTRYBIG pBigHit = rtOidDbLookupBig(iEntry, cEntries, uKey);
+ if (pBigHit)
+ {
+ if (--cComponents == 0)
+ {
+ if (RTBldProgStrTabQueryString(&g_OidDbStrTab, pBigHit->offString,
+ pBigHit->cchString, pszDst, cbDst) >= 0)
+ return VINF_SUCCESS;
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cEntries = pBigHit->cChildren;
+ if (cEntries)
+ {
+ iEntry = pBigHit->idxChildren;
+ fBigTable = pBigHit->fBigTable;
+ continue;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Queries the name for an object identifier.
+ *
+ * This API is simple and more or less requires a
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_NOT_FOUND if not found.
+ * @retval VERR_BUFFER_OVERFLOW if more buffer space is required.
+ *
+ * @param pauComponents The components making up the object ID.
+ * @param cComponents The number of components.
+ * @param pszDst Where to store the name if found.
+ * @param cbDst The size of the destination buffer.
+ */
+RTDECL(int) RTAsn1QueryObjIdName(PCRTASN1OBJID pObjId, char *pszDst, size_t cbDst)
+{
+ return rtOidDbQueryObjIdName(pObjId->pauComponents, pObjId->cComponents, pszDst, cbDst);
+}
+
+#endif /* !IN_SUP_HARDENED_R3 */
+
+
+
+/**
+ * Wrapper around FNRTASN1DUMPPRINTFV.
+ *
+ * @param pData The dump data structure.
+ * @param pszFormat Format string.
+ * @param ... Format arguments.
+ */
+static void rtAsn1DumpPrintf(PRTASN1DUMPDATA pData, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ pData->pfnPrintfV(pData->pvUser, pszFormat, va);
+ va_end(va);
+}
+
+
+/**
+ * Prints indentation.
+ *
+ * @param pData The dump data structure.
+ * @param uDepth The indentation depth.
+ */
+static void rtAsn1DumpPrintIdent(PRTASN1DUMPDATA pData, uint32_t uDepth)
+{
+ uint32_t cchLeft = uDepth * 2;
+ while (cchLeft > 0)
+ {
+ static char const s_szSpaces[] = " ";
+ uint32_t cch = RT_MIN(cchLeft, sizeof(s_szSpaces) - 1);
+ rtAsn1DumpPrintf(pData, &s_szSpaces[sizeof(s_szSpaces) - 1 - cch]);
+ cchLeft -= cch;
+ }
+}
+
+
+/**
+ * Dumps UTC TIME and GENERALIZED TIME
+ *
+ * @param pData The dump data structure.
+ * @param pAsn1Core The ASN.1 core object representation.
+ * @param pszType The time type name.
+ */
+static void rtAsn1DumpTime(PRTASN1DUMPDATA pData, PCRTASN1CORE pAsn1Core, const char *pszType)
+{
+ if ((pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT))
+ {
+ PCRTASN1TIME pTime = (PCRTASN1TIME)pAsn1Core;
+ rtAsn1DumpPrintf(pData, "%s -- %04u-%02u-%02u %02u:%02u:%02.%09Z\n",
+ pszType,
+ pTime->Time.i32Year, pTime->Time.u8Month, pTime->Time.u8MonthDay,
+ pTime->Time.u8Hour, pTime->Time.u8Minute, pTime->Time.u8Second,
+ pTime->Time.u32Nanosecond);
+ }
+ else if (pAsn1Core->cb > 0 && pAsn1Core->cb < 32 && pAsn1Core->uData.pch)
+ rtAsn1DumpPrintf(pData, "%s '%.*s'\n", pszType, (size_t)pAsn1Core->cb, pAsn1Core->uData.pch);
+ else
+ rtAsn1DumpPrintf(pData, "%s -- cb=%u\n", pszType, pAsn1Core->cb);
+}
+
+
+/**
+ * Dumps strings sharing the RTASN1STRING structure.
+ *
+ * @param pData The dump data structure.
+ * @param pAsn1Core The ASN.1 core object representation.
+ * @param pszType The string type name.
+ * @param uDepth The current identation level.
+ */
+static void rtAsn1DumpString(PRTASN1DUMPDATA pData, PCRTASN1CORE pAsn1Core, const char *pszType, uint32_t uDepth)
+{
+ rtAsn1DumpPrintf(pData, "%s", pszType);
+
+ const char *pszPostfix = "'\n";
+ bool fUtf8 = false;
+ const char *pch = pAsn1Core->uData.pch;
+ uint32_t cch = pAsn1Core->cb;
+ PCRTASN1STRING pString = (PCRTASN1STRING)pAsn1Core;
+ if ( (pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT)
+ && pString->pszUtf8
+ && pString->cchUtf8)
+ {
+ fUtf8 = true;
+ pszPostfix = "' -- utf-8\n";
+ }
+
+ if (cch == 0 || !pch)
+ rtAsn1DumpPrintf(pData, "-- cb=%u\n", pszType, pAsn1Core->cb);
+ else
+ {
+ if (cch >= 48)
+ {
+ rtAsn1DumpPrintf(pData, "\n");
+ rtAsn1DumpPrintIdent(pData, uDepth + 1);
+ }
+ rtAsn1DumpPrintf(pData, " '");
+
+ /** @todo Handle BMP and UNIVERSIAL strings specially. */
+ do
+ {
+ const char *pchStart = pch;
+ while ( cch > 0
+ && (uint8_t)*pch >= 0x20
+ && (!fUtf8 ? (uint8_t)*pch < 0x7f : (uint8_t)*pch != 0x7f)
+ && *pch != '\'')
+ cch--, pch++;
+ if (pchStart != pch)
+ rtAsn1DumpPrintf(pData, "%.*s", pch - pchStart, pchStart);
+
+ while ( cch > 0
+ && ( (uint8_t)*pch < 0x20
+ || (!fUtf8 ? (uint8_t)*pch >= 0x7f : (uint8_t)*pch == 0x7f)
+ || (uint8_t)*pch == '\'') )
+ {
+ rtAsn1DumpPrintf(pData, "\\x%02x", *pch);
+ cch--;
+ pch++;
+ }
+ } while (cch > 0);
+
+ rtAsn1DumpPrintf(pData, pszPostfix);
+ }
+}
+
+
+/**
+ * Dumps the type and value of an universal ASN.1 type.
+ *
+ * @returns True if it opens a child, false if not.
+ * @param pData The dumper data.
+ * @param pAsn1Core The ASN.1 object to dump.
+ * @param uDepth The current depth (for indentation).
+ */
+static bool rtAsn1DumpUniversalTypeAndValue(PRTASN1DUMPDATA pData, PCRTASN1CORE pAsn1Core, uint32_t uDepth)
+{
+ const char *pszValuePrefix = "-- value:";
+ const char *pszDefault = "";
+ if (pAsn1Core->fFlags & RTASN1CORE_F_DEFAULT)
+ {
+ pszValuePrefix = "DEFAULT";
+ pszDefault = "DEFAULT ";
+ }
+
+ bool fOpen = false;
+ switch (pAsn1Core->uRealTag)
+ {
+ case ASN1_TAG_BOOLEAN:
+ if (pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT)
+ rtAsn1DumpPrintf(pData, "BOOLEAN %s %RTbool\n", pszValuePrefix, ((PCRTASN1BOOLEAN)pAsn1Core)->fValue);
+ else if (pAsn1Core->cb == 1 && pAsn1Core->uData.pu8)
+ rtAsn1DumpPrintf(pData, "BOOLEAN %s %u\n", pszValuePrefix, *pAsn1Core->uData.pu8);
+ else
+ rtAsn1DumpPrintf(pData, "BOOLEAN -- cb=%u\n", pAsn1Core->cb);
+ break;
+
+ case ASN1_TAG_INTEGER:
+ if ((pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT) && pAsn1Core->cb <= 8)
+ rtAsn1DumpPrintf(pData, "INTEGER %s %llu / %#llx\n", pszValuePrefix,
+ ((PCRTASN1INTEGER)pAsn1Core)->uValue, ((PCRTASN1INTEGER)pAsn1Core)->uValue);
+ else if (pAsn1Core->cb == 0 || pAsn1Core->cb >= 512 || !pAsn1Core->uData.pu8)
+ rtAsn1DumpPrintf(pData, "INTEGER -- cb=%u\n", pAsn1Core->cb);
+ else if (pAsn1Core->cb <= 32)
+ rtAsn1DumpPrintf(pData, "INTEGER %s %.*Rhxs\n", pszValuePrefix, (size_t)pAsn1Core->cb, pAsn1Core->uData.pu8);
+ else
+ rtAsn1DumpPrintf(pData, "INTEGER %s\n%.*Rhxd\n", pszValuePrefix, (size_t)pAsn1Core->cb, pAsn1Core->uData.pu8);
+ break;
+
+ case ASN1_TAG_BIT_STRING:
+ if ((pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT))
+ {
+ PCRTASN1BITSTRING pBitString = (PCRTASN1BITSTRING)pAsn1Core;
+ rtAsn1DumpPrintf(pData, "BIT STRING %s-- cb=%u cBits=%#x cMaxBits=%#x",
+ pszDefault, pBitString->Asn1Core.cb, pBitString->cBits, pBitString->cMaxBits);
+ if (pBitString->cBits <= 64)
+ rtAsn1DumpPrintf(pData, " value=%#llx\n", RTAsn1BitString_GetAsUInt64(pBitString));
+ else
+ rtAsn1DumpPrintf(pData, "\n");
+ }
+ else
+ rtAsn1DumpPrintf(pData, "BIT STRING %s-- cb=%u\n", pszDefault, pAsn1Core->cb);
+ fOpen = pAsn1Core->pOps != NULL;
+ break;
+
+ case ASN1_TAG_OCTET_STRING:
+ rtAsn1DumpPrintf(pData, "OCTET STRING %s-- cb=%u\n", pszDefault, pAsn1Core->cb);
+ fOpen = pAsn1Core->pOps != NULL;
+ break;
+
+ case ASN1_TAG_NULL:
+ rtAsn1DumpPrintf(pData, "NULL\n");
+ break;
+
+ case ASN1_TAG_OID:
+ if ((pAsn1Core->fFlags & RTASN1CORE_F_PRIMITE_TAG_STRUCT))
+ {
+#ifndef IN_SUP_HARDENED_R3
+ PCRTASN1OBJID pObjId = (PCRTASN1OBJID)pAsn1Core;
+ char szName[64];
+ if (rtOidDbQueryObjIdName(pObjId->pauComponents, pObjId->cComponents, szName, sizeof(szName)) == VINF_SUCCESS)
+ rtAsn1DumpPrintf(pData, "OBJECT IDENTIFIER %s%s ('%s')\n",
+ pszDefault, szName, ((PCRTASN1OBJID)pAsn1Core)->szObjId);
+ else
+#endif
+ rtAsn1DumpPrintf(pData, "OBJECT IDENTIFIER %s'%s'\n", pszDefault, ((PCRTASN1OBJID)pAsn1Core)->szObjId);
+ }
+ else
+ rtAsn1DumpPrintf(pData, "OBJECT IDENTIFIER %s -- cb=%u\n", pszDefault, pAsn1Core->cb);
+ break;
+
+ case ASN1_TAG_OBJECT_DESCRIPTOR:
+ rtAsn1DumpPrintf(pData, "OBJECT DESCRIPTOR -- cb=%u TODO\n", pAsn1Core->cb);
+ break;
+
+ case ASN1_TAG_EXTERNAL:
+ rtAsn1DumpPrintf(pData, "EXTERNAL -- cb=%u TODO\n", pAsn1Core->cb);
+ break;
+
+ case ASN1_TAG_REAL:
+ rtAsn1DumpPrintf(pData, "REAL -- cb=%u TODO\n", pAsn1Core->cb);
+ break;
+
+ case ASN1_TAG_ENUMERATED:
+ rtAsn1DumpPrintf(pData, "ENUMERATED -- cb=%u TODO\n", pAsn1Core->cb);
+ break;
+
+ case ASN1_TAG_EMBEDDED_PDV:
+ rtAsn1DumpPrintf(pData, "EMBEDDED PDV -- cb=%u TODO\n", pAsn1Core->cb);
+ break;
+
+ case ASN1_TAG_UTF8_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "UTF8 STRING", uDepth);
+ break;
+
+ case ASN1_TAG_RELATIVE_OID:
+ rtAsn1DumpPrintf(pData, "RELATIVE OBJECT IDENTIFIER -- cb=%u TODO\n", pAsn1Core->cb);
+ break;
+
+ case ASN1_TAG_SEQUENCE:
+ rtAsn1DumpPrintf(pData, "SEQUENCE -- cb=%u\n", pAsn1Core->cb);
+ fOpen = true;
+ break;
+ case ASN1_TAG_SET:
+ rtAsn1DumpPrintf(pData, "SET -- cb=%u\n", pAsn1Core->cb);
+ fOpen = true;
+ break;
+
+ case ASN1_TAG_NUMERIC_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "NUMERIC STRING", uDepth);
+ break;
+
+ case ASN1_TAG_PRINTABLE_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "PRINTABLE STRING", uDepth);
+ break;
+
+ case ASN1_TAG_T61_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "T61 STRING", uDepth);
+ break;
+
+ case ASN1_TAG_VIDEOTEX_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "VIDEOTEX STRING", uDepth);
+ break;
+
+ case ASN1_TAG_IA5_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "IA5 STRING", uDepth);
+ break;
+
+ case ASN1_TAG_GRAPHIC_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "GRAPHIC STRING", uDepth);
+ break;
+
+ case ASN1_TAG_VISIBLE_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "VISIBLE STRING", uDepth);
+ break;
+
+ case ASN1_TAG_GENERAL_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "GENERAL STRING", uDepth);
+ break;
+
+ case ASN1_TAG_UNIVERSAL_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "UNIVERSAL STRING", uDepth);
+ break;
+
+ case ASN1_TAG_BMP_STRING:
+ rtAsn1DumpString(pData, pAsn1Core, "BMP STRING", uDepth);
+ break;
+
+ case ASN1_TAG_UTC_TIME:
+ rtAsn1DumpTime(pData, pAsn1Core, "UTC TIME");
+ break;
+
+ case ASN1_TAG_GENERALIZED_TIME:
+ rtAsn1DumpTime(pData, pAsn1Core, "GENERALIZED TIME");
+ break;
+
+ case ASN1_TAG_CHARACTER_STRING:
+ rtAsn1DumpPrintf(pData, "CHARACTER STRING -- cb=%u TODO\n", pAsn1Core->cb);
+ break;
+
+ default:
+ rtAsn1DumpPrintf(pData, "[UNIVERSAL %u]\n", pAsn1Core->uTag);
+ break;
+ }
+ return fOpen;
+}
+
+
+/** @callback_method_impl{FNRTASN1ENUMCALLBACK} */
+static DECLCALLBACK(int) rtAsn1DumpEnumCallback(PRTASN1CORE pAsn1Core, const char *pszName, uint32_t uDepth, void *pvUser)
+{
+ PRTASN1DUMPDATA pData = (PRTASN1DUMPDATA)pvUser;
+ if (!pAsn1Core->fFlags)
+ return VINF_SUCCESS;
+
+ bool fOpen = false;
+ rtAsn1DumpPrintIdent(pData, uDepth);
+ switch (pAsn1Core->fClass & ASN1_TAGCLASS_MASK)
+ {
+ case ASN1_TAGCLASS_UNIVERSAL:
+ rtAsn1DumpPrintf(pData, "%-16s ", pszName);
+ fOpen = rtAsn1DumpUniversalTypeAndValue(pData, pAsn1Core, uDepth);
+ break;
+
+ case ASN1_TAGCLASS_CONTEXT:
+ if ((pAsn1Core->fRealClass & ASN1_TAGCLASS_MASK) == ASN1_TAGCLASS_UNIVERSAL)
+ {
+ rtAsn1DumpPrintf(pData, "%-16s [%u] ", pszName, pAsn1Core->uTag);
+ fOpen = rtAsn1DumpUniversalTypeAndValue(pData, pAsn1Core, uDepth);
+ }
+ else
+ {
+ rtAsn1DumpPrintf(pData, "%-16s [%u]\n", pszName, pAsn1Core->uTag);
+ fOpen = true;
+ }
+ break;
+
+ case ASN1_TAGCLASS_APPLICATION:
+ if ((pAsn1Core->fRealClass & ASN1_TAGCLASS_MASK) == ASN1_TAGCLASS_UNIVERSAL)
+ {
+ rtAsn1DumpPrintf(pData, "%-16s [APPLICATION %u] ", pszName, pAsn1Core->uTag);
+ fOpen = rtAsn1DumpUniversalTypeAndValue(pData, pAsn1Core, uDepth);
+ }
+ else
+ {
+ rtAsn1DumpPrintf(pData, "%-16s [APPLICATION %u]\n", pszName, pAsn1Core->uTag);
+ fOpen = true;
+ }
+ break;
+
+ case ASN1_TAGCLASS_PRIVATE:
+ if (RTASN1CORE_IS_DUMMY(pAsn1Core))
+ rtAsn1DumpPrintf(pData, "%-16s DUMMY\n", pszName);
+ else
+ {
+ rtAsn1DumpPrintf(pData, "%-16s [PRIVATE %u]\n", pszName, pAsn1Core->uTag);
+ fOpen = true;
+ }
+ break;
+ }
+ /** @todo {} */
+
+ /*
+ * Recurse.
+ */
+ if ( pAsn1Core->pOps
+ && pAsn1Core->pOps->pfnEnum)
+ pAsn1Core->pOps->pfnEnum(pAsn1Core, rtAsn1DumpEnumCallback, uDepth, pData);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Dump(PCRTASN1CORE pAsn1Core, uint32_t fFlags, uint32_t uLevel, PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser)
+{
+ if ( pAsn1Core->pOps
+ && pAsn1Core->pOps->pfnEnum)
+ {
+ RTASN1DUMPDATA Data;
+ Data.fFlags = fFlags;
+ Data.pfnPrintfV = pfnPrintfV;
+ Data.pvUser = pvUser;
+
+ return pAsn1Core->pOps->pfnEnum((PRTASN1CORE)pAsn1Core, rtAsn1DumpEnumCallback, uLevel, &Data);
+ }
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-efence-allocator.cpp b/src/VBox/Runtime/common/asn1/asn1-efence-allocator.cpp
new file mode 100644
index 00000000..ed122378
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-efence-allocator.cpp
@@ -0,0 +1,215 @@
+/* $Id: asn1-efence-allocator.cpp $ */
+/** @file
+ * IPRT - ASN.1, Electric Fense Allocator.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/mem.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFree} */
+static DECLCALLBACK(void) rtAsn1EFenceAllocator_Free(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, void *pv)
+{
+ RT_NOREF_PV(pThis);
+ RTMemEfFreeNP(pv);
+ pAllocation->cbAllocated = 0;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnAlloc} */
+static DECLCALLBACK(int) rtAsn1EFenceAllocator_Alloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation,
+ void **ppv, size_t cb)
+{
+ void *pv = RTMemEfAllocZNP(cb, RTMEM_TAG);
+ if (pv)
+ {
+ *ppv = pv;
+ pAllocation->cbAllocated = (uint32_t)cb;
+ return VINF_SUCCESS;
+ }
+ RT_NOREF_PV(pThis);
+ return VERR_NO_MEMORY;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnRealloc} */
+static DECLCALLBACK(int) rtAsn1EFenceAllocator_Realloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation,
+ void *pvOld, void **ppvNew, size_t cbNew)
+{
+ Assert(pvOld);
+ Assert(cbNew);
+ void *pv = RTMemEfReallocNP(pvOld, cbNew, RTMEM_TAG);
+ if (pv)
+ {
+ *ppvNew = pv;
+ pAllocation->cbAllocated = (uint32_t)cbNew;
+ return VINF_SUCCESS;
+ }
+ RT_NOREF_PV(pThis);
+ return VERR_NO_MEMORY;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFreeArray} */
+static DECLCALLBACK(void) rtAsn1EFenceAllocator_FreeArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation,
+ void **papvArray)
+{
+ RT_NOREF_PV(pThis);
+ Assert(papvArray);
+ Assert(pAllocation->cbEntry);
+ Assert(pAllocation->cEntriesAllocated <= pAllocation->cPointersAllocated);
+
+ uint32_t i = pAllocation->cEntriesAllocated;
+ while (i-- > 0)
+ {
+ RTMemEfFreeNP(papvArray[i]);
+ papvArray[i] = NULL;
+ }
+ RTMemEfFreeNP(papvArray);
+
+ pAllocation->cEntriesAllocated = 0;
+ pAllocation->cPointersAllocated = 0;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnGrowArray} */
+static DECLCALLBACK(int) rtAsn1EFenceAllocator_GrowArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation,
+ void ***ppapvArray, uint32_t cMinEntries)
+{
+ RT_NOREF_PV(pThis);
+ Assert(pAllocation->cbEntry);
+ Assert(pAllocation->cEntriesAllocated <= pAllocation->cPointersAllocated);
+
+ /*
+ * Resize the pointer array.
+ */
+ void **papvArray = *ppapvArray;
+ void *pvPointers = RTMemEfReallocNP(papvArray, cMinEntries * sizeof(void *), RTMEM_TAG);
+ if (pvPointers)
+ {
+ *ppapvArray = papvArray = (void **)pvPointers;
+ if (cMinEntries > pAllocation->cPointersAllocated) /* possible on multiple shrink failures */
+ RT_BZERO(&papvArray[pAllocation->cPointersAllocated],
+ (cMinEntries - pAllocation->cPointersAllocated) * sizeof(void *));
+ else
+ AssertFailed();
+ pAllocation->cPointersAllocated = cMinEntries;
+ }
+ else if (cMinEntries > pAllocation->cPointersAllocated)
+ return VERR_NO_MEMORY;
+ /* else: possible but unlikely */
+
+ /*
+ * Add more entries.
+ */
+ while (pAllocation->cEntriesAllocated < cMinEntries)
+ {
+ void *pv;
+ papvArray[pAllocation->cEntriesAllocated] = pv = RTMemEfAllocZNP(pAllocation->cbEntry, RTMEM_TAG);
+ if (pv)
+ pAllocation->cEntriesAllocated++;
+ else
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnShrinkArray} */
+static DECLCALLBACK(void) rtAsn1EFenceAllocator_ShrinkArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation,
+ void ***ppapvArray, uint32_t cNew, uint32_t cCurrent)
+{
+ RT_NOREF_PV(pThis);
+ Assert(pAllocation->cbEntry);
+ Assert(pAllocation->cEntriesAllocated <= pAllocation->cPointersAllocated);
+
+ /*
+ * We always free and resize.
+ */
+ Assert(pAllocation->cEntriesAllocated == cCurrent);
+ Assert(cNew < cCurrent);
+
+ /* Free entries. */
+ void **papvArray = *ppapvArray;
+ while (cCurrent-- > cNew)
+ {
+ RTMemEfFreeNP(papvArray[cCurrent]);
+ papvArray[cCurrent] = NULL;
+ }
+ pAllocation->cEntriesAllocated = cNew;
+
+ /* Try resize pointer array. Failure here is a genuine possibility since the
+ efence code will try allocate a new block. This causes extra fun in the
+ grow method above. */
+ void *pvPointers = RTMemEfReallocNP(papvArray, cNew * sizeof(void *), RTMEM_TAG);
+ if (pvPointers)
+ {
+ *ppapvArray = (void **)pvPointers;
+ pAllocation->cPointersAllocated = cNew;
+ }
+}
+
+
+/** The Electric Fence ASN.1 allocator. */
+RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1EFenceAllocator =
+{
+ rtAsn1EFenceAllocator_Free,
+ rtAsn1EFenceAllocator_Alloc,
+ rtAsn1EFenceAllocator_Realloc,
+ rtAsn1EFenceAllocator_FreeArray,
+ rtAsn1EFenceAllocator_GrowArray,
+ rtAsn1EFenceAllocator_ShrinkArray
+};
+
+#if 0 && defined(IN_RING3) /* for efence testing */
+RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1DefaultAllocator =
+{
+ rtAsn1EFenceAllocator_Free,
+ rtAsn1EFenceAllocator_Alloc,
+ rtAsn1EFenceAllocator_Realloc,
+ rtAsn1EFenceAllocator_FreeArray,
+ rtAsn1EFenceAllocator_GrowArray,
+ rtAsn1EFenceAllocator_ShrinkArray
+};
+#endif
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-encode.cpp b/src/VBox/Runtime/common/asn1/asn1-encode.cpp
new file mode 100644
index 00000000..d3beb090
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-encode.cpp
@@ -0,0 +1,535 @@
+/* $Id: asn1-encode.cpp $ */
+/** @file
+ * IPRT - ASN.1, Encoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/assert.h>
+#include <iprt/bignum.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Argument package for rtAsn1EncodePrepareCallback passed by RTAsn1EncodePrepare.
+ */
+typedef struct RTASN1ENCODEPREPARGS
+{
+ /** The size at this level. */
+ uint32_t cb;
+ /** RTASN1ENCODE_F_XXX. */
+ uint32_t fFlags;
+ /** Pointer to the error info. (optional) */
+ PRTERRINFO pErrInfo;
+} RTASN1ENCODEPREPARGS;
+
+
+/**
+ * Argument package for rtAsn1EncodeWriteCallback passed by RTAsn1EncodeWrite.
+ */
+typedef struct RTASN1ENCODEWRITEARGS
+{
+ /** RTASN1ENCODE_F_XXX. */
+ uint32_t fFlags;
+ /** Pointer to the writer funtion. */
+ PFNRTASN1ENCODEWRITER pfnWriter;
+ /** User argument to the writer function. */
+ void *pvUser;
+ /** Pointer to the error info. (optional) */
+ PRTERRINFO pErrInfo;
+} RTASN1ENCODEWRITEARGS;
+
+/**
+ * Argument package for rtAsn1EncodeToBufferCallback passed by
+ * RTAsn1EncodeToBuffer.
+ */
+typedef struct RTASN1ENCODETOBUFARGS
+{
+ /** The destination buffer position (incremented while writing). */
+ uint8_t *pbDst;
+ /** The size of the destination buffer left (decremented while writing). */
+ size_t cbDst;
+} RTASN1ENCODETOBUFARGS;
+
+
+RTDECL(int) RTAsn1EncodeRecalcHdrSize(PRTASN1CORE pAsn1Core, uint32_t fFlags, PRTERRINFO pErrInfo)
+{
+ AssertReturn((fFlags & RTASN1ENCODE_F_RULE_MASK) == RTASN1ENCODE_F_DER, VERR_INVALID_FLAGS);
+ int rc = VINF_SUCCESS;
+
+ uint8_t cbHdr;
+ if ((pAsn1Core->fFlags & (RTASN1CORE_F_PRESENT | RTASN1CORE_F_DUMMY | RTASN1CORE_F_DEFAULT)) == RTASN1CORE_F_PRESENT)
+ {
+ /*
+ * The minimum header size is two bytes.
+ */
+ cbHdr = 2;
+
+ /*
+ * Add additional bytes for encoding the tag.
+ */
+ uint32_t uTag = pAsn1Core->uTag;
+ if (uTag >= ASN1_TAG_USE_LONG_FORM)
+ {
+ AssertReturn(pAsn1Core->uTag != UINT32_MAX, RTErrInfoSet(pErrInfo, VERR_ASN1_DUMMY_OBJECT, "uTag=UINT32_MAX"));
+ do
+ {
+ cbHdr++;
+ uTag >>= 7;
+ } while (uTag > 0);
+ }
+
+ /*
+ * Add additional bytes for encoding the content length.
+ */
+ uint32_t cb = pAsn1Core->cb;
+ if (cb >= 0x80)
+ {
+ AssertReturn(cb < _1G, RTErrInfoSetF(pErrInfo, VERR_ASN1_TOO_LONG, "cb=%u (%#x)", cb, cb));
+
+ if (cb <= UINT32_C(0xffff))
+ {
+ if (cb <= UINT32_C(0xff))
+ cbHdr += 1;
+ else
+ cbHdr += 2;
+ }
+ else
+ {
+ if (cb <= UINT32_C(0xffffff))
+ cbHdr += 3;
+ else
+ cbHdr += 4;
+ }
+ }
+ }
+ /*
+ * Not present, dummy or otherwise not encoded.
+ */
+ else
+ {
+ cbHdr = 0;
+ if (pAsn1Core->fFlags & RTASN1CORE_F_DEFAULT)
+ rc = VINF_ASN1_NOT_ENCODED;
+ else
+ {
+ Assert(RTASN1CORE_IS_DUMMY(pAsn1Core));
+ Assert(pAsn1Core->pOps && pAsn1Core->pOps->pfnEnum);
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Update the header length.
+ */
+ pAsn1Core->cbHdr = cbHdr;
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTASN1ENUMCALLBACK}
+ */
+static DECLCALLBACK(int) rtAsn1EncodePrepareCallback(PRTASN1CORE pAsn1Core, const char *pszName, uint32_t uDepth, void *pvUser)
+{
+ RTASN1ENCODEPREPARGS *pArgs = (RTASN1ENCODEPREPARGS *)pvUser;
+ RT_NOREF_PV(pszName);
+ if (RTASN1CORE_IS_PRESENT(pAsn1Core))
+ {
+ /*
+ * Depth first, where relevant.
+ */
+ uint32_t const cbSaved = pArgs->cb;
+ if (pAsn1Core->pOps)
+ {
+ /*
+ * Use the encoding preparation method when available.
+ */
+ int rc;
+ if (pAsn1Core->pOps->pfnEncodePrep)
+ rc = pAsn1Core->pOps->pfnEncodePrep(pAsn1Core, pArgs->fFlags, pArgs->pErrInfo);
+ else if (pAsn1Core->pOps->pfnEnum)
+ {
+ /*
+ * Recurse to prepare the child objects (if any).
+ */
+ rc = pAsn1Core->pOps->pfnEnum(pAsn1Core, rtAsn1EncodePrepareCallback, uDepth + 1, pArgs);
+ if (RT_SUCCESS(rc))
+ pAsn1Core->cb = pArgs->cb - cbSaved;
+ }
+ else
+ {
+ /*
+ * Must be a primitive type if DER.
+ */
+ if ( (pAsn1Core->fClass & ASN1_TAGFLAG_CONSTRUCTED)
+ && (pArgs->fFlags & RTASN1ENCODE_F_DER) )
+ return RTErrInfoSetF(pArgs->pErrInfo, VERR_ASN1_EXPECTED_PRIMITIVE,
+ "Expected primitive ASN.1 object: uTag=%#x fClass=%#x cb=%u",
+ RTASN1CORE_GET_TAG(pAsn1Core), pAsn1Core->fClass, pAsn1Core->cb);
+ rc = VINF_SUCCESS;
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTAsn1EncodeRecalcHdrSize(pAsn1Core, pArgs->fFlags, pArgs->pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ AssertFailed();
+ pAsn1Core->cb = 0;
+ pAsn1Core->cbHdr = 0;
+ }
+
+ /*
+ * Recalculate the output size, thus far. Dummy objects propagates the
+ * content size, but the header size is zero. Other objects with
+ * header size zero are not encoded and should be omitted entirely.
+ */
+ if (pAsn1Core->cbHdr > 0 || RTASN1CORE_IS_DUMMY(pAsn1Core))
+ pArgs->cb = RTASN1CORE_GET_RAW_ASN1_SIZE(pAsn1Core) + cbSaved;
+ else
+ pArgs->cb = cbSaved;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1EncodePrepare(PRTASN1CORE pRoot, uint32_t fFlags, uint32_t *pcbEncoded, PRTERRINFO pErrInfo)
+{
+ AssertReturn((fFlags & RTASN1ENCODE_F_RULE_MASK) == RTASN1ENCODE_F_DER, VERR_INVALID_FLAGS);
+
+ /*
+ * This is implemented as a recursive enumeration of the ASN.1 object structure.
+ */
+ RTASN1ENCODEPREPARGS Args;
+ Args.cb = 0;
+ Args.fFlags = fFlags;
+ Args.pErrInfo = pErrInfo;
+ int rc = rtAsn1EncodePrepareCallback(pRoot, "root", 0, &Args);
+ if (pcbEncoded)
+ *pcbEncoded = RTASN1CORE_GET_RAW_ASN1_SIZE(pRoot);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1EncodeWriteHeader(PCRTASN1CORE pAsn1Core, uint32_t fFlags, FNRTASN1ENCODEWRITER pfnWriter, void *pvUser,
+ PRTERRINFO pErrInfo)
+{
+ AssertReturn((fFlags & RTASN1ENCODE_F_RULE_MASK) == RTASN1ENCODE_F_DER, VERR_INVALID_FLAGS);
+
+ if ((pAsn1Core->fFlags & (RTASN1CORE_F_PRESENT | RTASN1CORE_F_DUMMY | RTASN1CORE_F_DEFAULT)) == RTASN1CORE_F_PRESENT)
+ {
+ uint8_t abHdr[16]; /* 2 + max 5 tag + max 4 length = 11 */
+ uint8_t *pbDst = &abHdr[0];
+
+ /*
+ * Encode the tag.
+ */
+ uint32_t uTag = pAsn1Core->uTag;
+ if (uTag < ASN1_TAG_USE_LONG_FORM)
+ *pbDst++ = (uint8_t)uTag | (pAsn1Core->fClass & ~ASN1_TAG_MASK);
+ else
+ {
+ AssertReturn(pAsn1Core->uTag != UINT32_MAX, RTErrInfoSet(pErrInfo, VERR_ASN1_DUMMY_OBJECT, "uTag=UINT32_MAX"));
+
+ /* In the long form, the tag is encoded MSB style with the 8th bit
+ of each byte indicating the whether there are more byte. */
+ *pbDst++ = ASN1_TAG_USE_LONG_FORM | (pAsn1Core->fClass & ~ASN1_TAG_MASK);
+ if (uTag <= UINT32_C(0x7f))
+ *pbDst++ = uTag;
+ else if (uTag <= UINT32_C(0x3fff)) /* 2**(7*2) = 0x4000 (16384) */
+ {
+ *pbDst++ = (uTag >> 7) | 0x80;
+ *pbDst++ = uTag & 0x7f;
+ }
+ else if (uTag <= UINT32_C(0x1fffff)) /* 2**(7*3) = 0x200000 (2097152) */
+ {
+ *pbDst++ = (uTag >> 14) | 0x80;
+ *pbDst++ = ((uTag >> 7) & 0x7f) | 0x80;
+ *pbDst++ = uTag & 0x7f;
+ }
+ else if (uTag <= UINT32_C(0xfffffff)) /* 2**(7*4) = 0x10000000 (268435456) */
+ {
+ *pbDst++ = (uTag >> 21) | 0x80;
+ *pbDst++ = ((uTag >> 14) & 0x7f) | 0x80;
+ *pbDst++ = ((uTag >> 7) & 0x7f) | 0x80;
+ *pbDst++ = uTag & 0x7f;
+ }
+ else
+ {
+ *pbDst++ = (uTag >> 28) | 0x80;
+ *pbDst++ = ((uTag >> 21) & 0x7f) | 0x80;
+ *pbDst++ = ((uTag >> 14) & 0x7f) | 0x80;
+ *pbDst++ = ((uTag >> 7) & 0x7f) | 0x80;
+ *pbDst++ = uTag & 0x7f;
+ }
+ }
+
+ /*
+ * Encode the length.
+ */
+ uint32_t cb = pAsn1Core->cb;
+ if (cb < 0x80)
+ *pbDst++ = (uint8_t)cb;
+ else
+ {
+ AssertReturn(cb < _1G, RTErrInfoSetF(pErrInfo, VERR_ASN1_TOO_LONG, "cb=%u (%#x)", cb, cb));
+
+ if (cb <= UINT32_C(0xffff))
+ {
+ if (cb <= UINT32_C(0xff))
+ {
+ pbDst[0] = 0x81;
+ pbDst[1] = (uint8_t)cb;
+ pbDst += 2;
+ }
+ else
+ {
+ pbDst[0] = 0x82;
+ pbDst[1] = cb >> 8;
+ pbDst[2] = (uint8_t)cb;
+ pbDst += 3;
+ }
+ }
+ else
+ {
+ if (cb <= UINT32_C(0xffffff))
+ {
+ pbDst[0] = 0x83;
+ pbDst[1] = (uint8_t)(cb >> 16);
+ pbDst[2] = (uint8_t)(cb >> 8);
+ pbDst[3] = (uint8_t)cb;
+ pbDst += 4;
+ }
+ else
+ {
+ pbDst[0] = 0x84;
+ pbDst[1] = (uint8_t)(cb >> 24);
+ pbDst[2] = (uint8_t)(cb >> 16);
+ pbDst[3] = (uint8_t)(cb >> 8);
+ pbDst[4] = (uint8_t)cb;
+ pbDst += 5;
+ }
+ }
+ }
+
+ size_t const cbHdr = pbDst - &abHdr[0];
+ Assert(sizeof(abHdr) >= cbHdr);
+ Assert(pAsn1Core->cbHdr == cbHdr);
+
+ /*
+ * Write it.
+ */
+ return pfnWriter(abHdr, cbHdr, pvUser, pErrInfo);
+ }
+
+ /*
+ * Not present, dummy or otherwise not encoded.
+ */
+ Assert(pAsn1Core->cbHdr == 0);
+ if (pAsn1Core->fFlags & RTASN1CORE_F_DEFAULT)
+ return VINF_ASN1_NOT_ENCODED;
+ Assert(RTASN1CORE_IS_DUMMY(pAsn1Core));
+ Assert(pAsn1Core->pOps && pAsn1Core->pOps->pfnEnum);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNRTASN1ENUMCALLBACK}
+ */
+static DECLCALLBACK(int) rtAsn1EncodeWriteCallback(PRTASN1CORE pAsn1Core, const char *pszName, uint32_t uDepth, void *pvUser)
+{
+ RTASN1ENCODEWRITEARGS *pArgs = (RTASN1ENCODEWRITEARGS *)pvUser;
+ RT_NOREF_PV(pszName);
+ int rc;
+ if (RTASN1CORE_IS_PRESENT(pAsn1Core))
+ {
+ /*
+ * If there is an write method, use it.
+ */
+ if ( pAsn1Core->pOps
+ && pAsn1Core->pOps->pfnEncodeWrite)
+ rc = pAsn1Core->pOps->pfnEncodeWrite(pAsn1Core, pArgs->fFlags, pArgs->pfnWriter, pArgs->pvUser, pArgs->pErrInfo);
+ else
+ {
+ /*
+ * Generic path. Start by writing the header for this object.
+ */
+ rc = RTAsn1EncodeWriteHeader(pAsn1Core, pArgs->fFlags, pArgs->pfnWriter, pArgs->pvUser, pArgs->pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * If there is an enum function, call it to assemble the content.
+ * Otherwise ASSUME the pointer in the header points to the content.
+ */
+ if ( pAsn1Core->pOps
+ && pAsn1Core->pOps->pfnEnum)
+ {
+ if (rc != VINF_ASN1_NOT_ENCODED)
+ rc = pAsn1Core->pOps->pfnEnum(pAsn1Core, rtAsn1EncodeWriteCallback, uDepth + 1, pArgs);
+ }
+ else if (pAsn1Core->cb && rc != VINF_ASN1_NOT_ENCODED)
+ {
+ Assert(!RTASN1CORE_IS_DUMMY(pAsn1Core));
+ AssertPtrReturn(pAsn1Core->uData.pv,
+ RTErrInfoSetF(pArgs->pErrInfo, VERR_ASN1_INVALID_DATA_POINTER,
+ "Invalid uData pointer %p for no pfnEnum object with %#x bytes of content",
+ pAsn1Core->uData.pv, pAsn1Core->cb));
+ rc = pArgs->pfnWriter(pAsn1Core->uData.pv, pAsn1Core->cb, pArgs->pvUser, pArgs->pErrInfo);
+ }
+ }
+ }
+ if (RT_SUCCESS(rc))
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1EncodeWrite(PCRTASN1CORE pRoot, uint32_t fFlags, FNRTASN1ENCODEWRITER pfnWriter, void *pvUser,
+ PRTERRINFO pErrInfo)
+{
+ AssertReturn((fFlags & RTASN1ENCODE_F_RULE_MASK) == RTASN1ENCODE_F_DER, VERR_INVALID_FLAGS);
+
+ /*
+ * This is implemented as a recursive enumeration of the ASN.1 object structure.
+ */
+ RTASN1ENCODEWRITEARGS Args;
+ Args.fFlags = fFlags;
+ Args.pfnWriter = pfnWriter;
+ Args.pvUser = pvUser;
+ Args.pErrInfo = pErrInfo;
+ return rtAsn1EncodeWriteCallback((PRTASN1CORE)pRoot, "root", 0, &Args);
+}
+
+
+static DECLCALLBACK(int) rtAsn1EncodeToBufferCallback(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo)
+{
+ RTASN1ENCODETOBUFARGS *pArgs = (RTASN1ENCODETOBUFARGS *)pvUser;
+ if (RT_LIKELY(pArgs->cbDst >= cbToWrite))
+ {
+ memcpy(pArgs->pbDst, pvBuf, cbToWrite);
+ pArgs->cbDst -= cbToWrite;
+ pArgs->pbDst += cbToWrite;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Overflow.
+ */
+ if (pArgs->cbDst)
+ {
+ memcpy(pArgs->pbDst, pvBuf, pArgs->cbDst);
+ pArgs->pbDst -= pArgs->cbDst;
+ pArgs->cbDst = 0;
+ }
+ RT_NOREF_PV(pErrInfo);
+ return VERR_BUFFER_OVERFLOW;
+}
+
+
+RTDECL(int) RTAsn1EncodeToBuffer(PCRTASN1CORE pRoot, uint32_t fFlags, void *pvBuf, size_t cbBuf, PRTERRINFO pErrInfo)
+{
+ RTASN1ENCODETOBUFARGS Args;
+ Args.pbDst = (uint8_t *)pvBuf;
+ Args.cbDst = cbBuf;
+ return RTAsn1EncodeWrite(pRoot, fFlags, rtAsn1EncodeToBufferCallback, &Args, pErrInfo);
+}
+
+
+RTDECL(int) RTAsn1EncodeQueryRawBits(PRTASN1CORE pRoot, const uint8_t **ppbRaw, uint32_t *pcbRaw,
+ void **ppvFree, PRTERRINFO pErrInfo)
+{
+ /*
+ * ASSUME that if we've got pointers here, they are valid...
+ */
+ if ( pRoot->uData.pv
+ && !(pRoot->fFlags & RTASN1CORE_F_INDEFINITE_LENGTH) /* BER, not DER. */
+ && (pRoot->fFlags & RTASN1CORE_F_DECODED_CONTENT) )
+ {
+ /** @todo Check that it's DER encoding. */
+ *ppbRaw = RTASN1CORE_GET_RAW_ASN1_PTR(pRoot);
+ *pcbRaw = RTASN1CORE_GET_RAW_ASN1_SIZE(pRoot);
+ *ppvFree = NULL;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Encode it into a temporary heap buffer.
+ */
+ uint32_t cbEncoded = 0;
+ int rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, &cbEncoded, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvEncoded = RTMemTmpAllocZ(cbEncoded);
+ if (pvEncoded)
+ {
+ rc = RTAsn1EncodeToBuffer(pRoot, RTASN1ENCODE_F_DER, pvEncoded, cbEncoded, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvFree = pvEncoded;
+ *ppbRaw = (unsigned char *)pvEncoded;
+ *pcbRaw = cbEncoded;
+ return VINF_SUCCESS;
+ }
+ RTMemTmpFree(pvEncoded);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_NO_TMP_MEMORY, "RTMemTmpAllocZ(%u)", cbEncoded);
+ }
+
+ *ppvFree = NULL;
+ *ppbRaw = NULL;
+ *pcbRaw = 0;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-safer-allocator.cpp b/src/VBox/Runtime/common/asn1/asn1-safer-allocator.cpp
new file mode 100644
index 00000000..602b2faa
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-safer-allocator.cpp
@@ -0,0 +1,232 @@
+/* $Id: asn1-safer-allocator.cpp $ */
+/** @file
+ * IPRT - ASN.1, Safer Allocator, for sensitive data.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/memsafer.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+/**
+ * Aligns allocation sizes a little.
+ *
+ * @returns Aligned size.
+ * @param cb Requested size.
+ */
+static size_t rtAsn1SaferAllocator_AlignSize(size_t cb)
+{
+ if (cb >= 64)
+ return RT_ALIGN_Z(cb, 64);
+ if (cb >= 32)
+ return RT_ALIGN_Z(cb, 32);
+ if (cb >= 16)
+ return RT_ALIGN_Z(cb, 16);
+ return cb;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFree} */
+static DECLCALLBACK(void) rtAsn1SaferAllocator_Free(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation, void *pv)
+{
+ RT_NOREF_PV(pThis);
+ RTMemSaferFree(pv, pAllocation->cbAllocated);
+ pAllocation->cbAllocated = 0;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnAlloc} */
+static DECLCALLBACK(int) rtAsn1SaferAllocator_Alloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation,
+ void **ppv, size_t cb)
+{
+ size_t cbAlloc = rtAsn1SaferAllocator_AlignSize(cb);
+ void *pv = RTMemSaferAllocZ(cbAlloc);
+ if (pv)
+ {
+ *ppv = pv;
+ pAllocation->cbAllocated = (uint32_t)cbAlloc;
+ return VINF_SUCCESS;
+ }
+ RT_NOREF_PV(pThis);
+ return VERR_NO_MEMORY;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnRealloc} */
+static DECLCALLBACK(int) rtAsn1SaferAllocator_Realloc(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ALLOCATION pAllocation,
+ void *pvOld, void **ppvNew, size_t cbNew)
+{
+ Assert(pvOld);
+ Assert(cbNew);
+ size_t cbAlloc = rtAsn1SaferAllocator_AlignSize(cbNew);
+ void *pv = RTMemSaferReallocZ(pAllocation->cbAllocated, pvOld, cbAlloc);
+ if (pv)
+ {
+ *ppvNew = pv;
+ pAllocation->cbAllocated = (uint32_t)cbAlloc;
+ return VINF_SUCCESS;
+ }
+ RT_NOREF_PV(pThis);
+ return VERR_NO_MEMORY;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnFreeArray} */
+static DECLCALLBACK(void) rtAsn1SaferAllocator_FreeArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation,
+ void **papvArray)
+{
+ RT_NOREF_PV(pThis);
+ Assert(papvArray);
+ Assert(pAllocation->cbEntry);
+
+ uint32_t i = pAllocation->cEntriesAllocated;
+ while (i-- > 0)
+ RTMemSaferFree(papvArray[i], pAllocation->cbEntry);
+ RTMemFree(papvArray);
+
+ pAllocation->cEntriesAllocated = 0;
+ pAllocation->cPointersAllocated = 0;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnGrowArray} */
+static DECLCALLBACK(int) rtAsn1SaferAllocator_GrowArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation,
+ void ***ppapvArray, uint32_t cMinEntries)
+{
+ RT_NOREF_PV(pThis);
+
+ /*
+ * Resize the pointer array. We do chunks of 64 bytes for now.
+ */
+ void **papvArray = *ppapvArray;
+ uint32_t cPointers = RT_ALIGN_32(cMinEntries, 64 / sizeof(void *));
+ if (cPointers > pAllocation->cPointersAllocated)
+ {
+ void *pvPointers = RTMemRealloc(papvArray, cPointers * sizeof(void *));
+ if (pvPointers)
+ { /* likely */ }
+ else if (cMinEntries > pAllocation->cPointersAllocated)
+ {
+ cPointers = cMinEntries;
+ pvPointers = RTMemRealloc(*ppapvArray, cPointers * sizeof(void *));
+ if (!pvPointers)
+ return VERR_NO_MEMORY;
+ }
+ else
+ {
+ cPointers = pAllocation->cPointersAllocated;
+ pvPointers = papvArray;
+ }
+
+ *ppapvArray = papvArray = (void **)pvPointers;
+ RT_BZERO(&papvArray[pAllocation->cPointersAllocated], (cPointers - pAllocation->cPointersAllocated) * sizeof(void *));
+ pAllocation->cPointersAllocated = cPointers;
+ }
+
+ /*
+ * Add more entries. Do multiple as the array grows.
+ *
+ * Note! We could possibly optimize this by allocating slabs of entries and
+ * slice them up. However, keep things as simple as possible for now.
+ */
+ uint32_t cEntries = cMinEntries;
+ if (cEntries > 2)
+ {
+ if (cEntries > 8)
+ cEntries = RT_ALIGN_32(cEntries, 4);
+ else
+ cEntries = RT_ALIGN_32(cEntries, 2);
+ cEntries = RT_MIN(cEntries, cPointers);
+ Assert(cEntries >= cMinEntries);
+ }
+ Assert(cEntries <= pAllocation->cPointersAllocated);
+
+ while (pAllocation->cEntriesAllocated < cEntries)
+ {
+ void *pv;
+ papvArray[pAllocation->cEntriesAllocated] = pv = RTMemSaferAllocZ(pAllocation->cbEntry);
+ if (pv)
+ pAllocation->cEntriesAllocated++;
+ else if (pAllocation->cEntriesAllocated >= cMinEntries)
+ break;
+ else
+ return VERR_NO_MEMORY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTASN1ALLOCATORVTABLE,pfnShrinkArray} */
+static DECLCALLBACK(void) rtAsn1SaferAllocator_ShrinkArray(PCRTASN1ALLOCATORVTABLE pThis, PRTASN1ARRAYALLOCATION pAllocation,
+ void ***ppapvArray, uint32_t cNew, uint32_t cCurrent)
+{
+ RT_NOREF_PV(pThis);
+
+ /*
+ * For now we only zero the entries being removed.
+ */
+ void **papvArray = *ppapvArray;
+ while (cNew < cCurrent)
+ {
+ RTMemWipeThoroughly(papvArray[cNew], pAllocation->cbEntry, 3);
+ RT_BZERO(papvArray[cNew], pAllocation->cbEntry);
+ cNew++;
+ }
+}
+
+
+
+/** The Safer ASN.1 allocator. */
+#if 1 || !defined(IN_RING3) || defined(DOXYGEN_RUNNING)
+RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1SaferAllocator =
+#else
+RT_DECL_DATA_CONST(RTASN1ALLOCATORVTABLE const) g_RTAsn1SaferAllocatorDisabled =
+#endif
+{
+ rtAsn1SaferAllocator_Free,
+ rtAsn1SaferAllocator_Alloc,
+ rtAsn1SaferAllocator_Realloc,
+ rtAsn1SaferAllocator_FreeArray,
+ rtAsn1SaferAllocator_GrowArray,
+ rtAsn1SaferAllocator_ShrinkArray
+};
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-decode.cpp
new file mode 100644
index 00000000..994173f1
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-decode.cpp
@@ -0,0 +1,141 @@
+/* $Id: asn1-ut-bitstring-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, BIT STRING Type, Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+
+RTDECL(int) RTAsn1BitString_DecodeAsn1Ex(PRTASN1CURSOR pCursor, uint32_t fFlags, uint32_t cMaxBits, PRTASN1BITSTRING pThis,
+ const char *pszErrorTag)
+{
+ pThis->cBits = 0;
+ pThis->cMaxBits = cMaxBits;
+ pThis->uBits.pv = NULL;
+ pThis->pEncapsulated = NULL;
+ RTAsn1CursorInitAllocation(pCursor, &pThis->EncapsulatedAllocation);
+
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1CursorMatchTagClassFlagsString(pCursor, &pThis->Asn1Core, ASN1_TAG_BIT_STRING,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ fFlags, pszErrorTag, "BIT STRING");
+ if (RT_SUCCESS(rc))
+ {
+ if (!(pThis->Asn1Core.fClass & ASN1_TAGFLAG_CONSTRUCTED))
+ {
+ if ( ( cMaxBits == UINT32_MAX
+ || RT_ALIGN(cMaxBits, 8) / 8 + 1 >= pThis->Asn1Core.cb)
+ && pThis->Asn1Core.cb > 0)
+ {
+ uint8_t cUnusedBits = pThis->Asn1Core.cb > 0 ? *pThis->Asn1Core.uData.pu8 : 0;
+ if (pThis->Asn1Core.cb < 2)
+ {
+ /* Not bits present. */
+ if (cUnusedBits == 0)
+ {
+ pThis->cBits = 0;
+ pThis->uBits.pv = NULL;
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.pOps = &g_RTAsn1BitString_Vtable;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ return VINF_SUCCESS;
+ }
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BITSTRING_ENCODING,
+ "%s: Bad unused bit count: %#x (cb=%#x)",
+ pszErrorTag, cUnusedBits, pThis->Asn1Core.cb);
+ }
+ else if (cUnusedBits < 8)
+ {
+ pThis->cBits = (pThis->Asn1Core.cb - 1) * 8;
+ pThis->cBits -= cUnusedBits;
+ pThis->uBits.pu8 = pThis->Asn1Core.uData.pu8 + 1;
+ if ( !(pCursor->fFlags & (RTASN1CURSOR_FLAGS_DER | RTASN1CURSOR_FLAGS_CER))
+ || cUnusedBits == 0
+ || !( pThis->uBits.pu8[pThis->Asn1Core.cb - 2] & (((uint8_t)1 << cUnusedBits) - (uint8_t)1) ) )
+ {
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.pOps = &g_RTAsn1BitString_Vtable;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ return VINF_SUCCESS;
+ }
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BITSTRING_ENCODING,
+ "%s: Unused bits shall be zero in DER/CER mode: last byte=%#x cUnused=%#x",
+ pszErrorTag, pThis->uBits.pu8[pThis->cBits / 8], cUnusedBits);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BITSTRING_ENCODING,
+ "%s: Bad unused bit count: %#x (cb=%#x)",
+ pszErrorTag, cUnusedBits, pThis->Asn1Core.cb);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BITSTRING_ENCODING,
+ "%s: Size mismatch: cb=%#x, expected %#x (cMaxBits=%#x)",
+ pszErrorTag, pThis->Asn1Core.cb, RT_ALIGN(cMaxBits, 8) / 8 + 1, cMaxBits);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CONSTRUCTED_STRING_NOT_IMPL,
+ "%s: Constructed BIT STRING not implemented.", pszErrorTag);
+ }
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1BitString_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1BITSTRING pThis, const char *pszErrorTag)
+{
+ return RTAsn1BitString_DecodeAsn1Ex(pCursor, fFlags, UINT32_MAX, pThis, pszErrorTag);
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-bitstring-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-template.h
new file mode 100644
index 00000000..17375f86
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring-template.h
@@ -0,0 +1,55 @@
+/* $Id: asn1-ut-bitstring-template.h $ */
+/** @file
+ * IPRT - ASN.1, Bit String Type, Collection Type Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+#define RTASN1TMPL_TYPE RTASN1SEQOFBITSTRINGS
+#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfBitStrings
+#define RTASN1TMPL_INT_NAME rtAsn1SeqOfBitStrings
+RTASN1TMPL_SEQ_OF(RTASN1BITSTRING, RTAsn1BitString);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+#define RTASN1TMPL_TYPE RTASN1SETOFBITSTRINGS
+#define RTASN1TMPL_EXT_NAME RTAsn1SetOfBitStrings
+#define RTASN1TMPL_INT_NAME rtAsn1SetOfBitStrings
+RTASN1TMPL_SET_OF(RTASN1BITSTRING, RTAsn1BitString);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-bitstring.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring.cpp
new file mode 100644
index 00000000..80499ab5
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-bitstring.cpp
@@ -0,0 +1,548 @@
+/* $Id: asn1-ut-bitstring.cpp $ */
+/** @file
+ * IPRT - ASN.1, Bit String Type.
+ *
+ * @remarks This file should remain very similar to asn1-ut-octetstring.cpp.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/alloca.h>
+#include <iprt/bignum.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTASN1BITSTRINGWRITERCTX
+{
+ /** Pointer to the output buffer. */
+ uint8_t *pbBuf;
+ /** The current buffer offset. */
+ uint32_t offBuf;
+ /** The size of the buffer. */
+ uint32_t cbBuf;
+} RTASN1BITSTRINGWRITERCTX;
+
+
+/** @callback_method_impl{FNRTASN1ENCODEWRITER,
+ * Used to refresh the content of octet and bit strings. } */
+static DECLCALLBACK(int) rtAsn1BitStringEncodeWriter(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo)
+{
+ RTASN1BITSTRINGWRITERCTX *pCtx = (RTASN1BITSTRINGWRITERCTX *)pvUser;
+ AssertReturn(cbToWrite <= pCtx->cbBuf - pCtx->offBuf,
+ RTErrInfoSetF(pErrInfo, VERR_BUFFER_OVERFLOW,
+ "cbToWrite=%#x offBuf=%#x cbBuf=%#x", cbToWrite, pCtx->cbBuf, pCtx->offBuf));
+ memcpy(&pCtx->pbBuf[pCtx->offBuf], pvBuf, cbToWrite);
+ pCtx->offBuf += (uint32_t)cbToWrite;
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTASN1ENCODEWRITER,
+ * Used to compare the encoded raw content of an octet or bit string with the
+ * encapsulated object. } */
+static DECLCALLBACK(int) rtAsn1BitStringEncodeCompare(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo)
+{
+ RTASN1BITSTRINGWRITERCTX *pCtx = (RTASN1BITSTRINGWRITERCTX *)pvUser;
+ AssertReturn(cbToWrite <= pCtx->cbBuf - pCtx->offBuf, VERR_BUFFER_OVERFLOW);
+ if (memcmp(&pCtx->pbBuf[pCtx->offBuf], pvBuf, cbToWrite) != 0)
+ return VERR_NOT_EQUAL;
+ pCtx->offBuf += (uint32_t)cbToWrite;
+ RT_NOREF_PV(pErrInfo);
+ return VINF_SUCCESS;
+}
+
+
+
+/*
+ * ASN.1 BIT STRING - Special Methods.
+ */
+
+RTDECL(uint64_t) RTAsn1BitString_GetAsUInt64(PCRTASN1BITSTRING pThis)
+{
+ /*
+ * Extract the first 64 bits in host order.
+ */
+ uint8_t const *pb = pThis->uBits.pu8;
+ uint64_t uRet = 0;
+ uint32_t cShift = 0;
+ uint32_t cBits = RT_MIN(pThis->cBits, 64);
+ while (cBits > 0)
+ {
+ uint8_t b = *pb++;
+#if 1 /* We don't have a bit-order constant... */
+ b = ((b & 0x01) << 7)
+ | ((b & 0x02) << 5)
+ | ((b & 0x04) << 3)
+ | ((b & 0x08) << 1)
+ | ((b & 0x10) >> 1)
+ | ((b & 0x20) >> 3)
+ | ((b & 0x40) >> 5)
+ | ((b & 0x80) >> 7);
+#endif
+ if (cBits < 8)
+ {
+ b &= RT_BIT_32(cBits) - 1;
+ uRet |= (uint64_t)b << cShift;
+ break;
+ }
+ uRet |= (uint64_t)b << cShift;
+ cShift += 8;
+ cBits -= 8;
+ }
+
+ return uRet;
+}
+
+
+RTDECL(int) RTAsn1BitString_RefreshContent(PRTASN1BITSTRING pThis, uint32_t fFlags,
+ PCRTASN1ALLOCATORVTABLE pAllocator, PRTERRINFO pErrInfo)
+{
+ AssertReturn(pThis->pEncapsulated, VERR_INVALID_STATE);
+
+ uint32_t cbEncoded;
+ int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Asn1Core.cb = 1 + cbEncoded;
+ pThis->cBits = cbEncoded * 8;
+ AssertReturn(pThis->cBits / 8 == cbEncoded, RTErrInfoSetF(pErrInfo, VERR_TOO_MUCH_DATA, "cbEncoded=%#x", cbEncoded));
+
+ rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, cbEncoded + 1, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->uBits.pu8 = pThis->Asn1Core.uData.pu8 + 1;
+
+ /* Initialize the writer context and write the first byte concerning unused bits. */
+ RTASN1BITSTRINGWRITERCTX Ctx;
+ Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8;
+ Ctx.cbBuf = cbEncoded + 1;
+ Ctx.offBuf = 1;
+ *Ctx.pbBuf = 0;
+
+ rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1BitStringEncodeWriter, &Ctx, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (Ctx.offBuf == cbEncoded + 1)
+ return VINF_SUCCESS;
+
+ rc = RTErrInfoSetF(pErrInfo, rc, "Expected %#x + 1 bytes, got %#x", cbEncoded, Ctx.offBuf);
+ }
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "Error allocating %#x + 1 bytes for storing content\n", cbEncoded);
+ }
+ return rc;
+}
+
+
+RTDECL(bool) RTAsn1BitString_AreContentBitsValid(PCRTASN1BITSTRING pThis, uint32_t fFlags)
+{
+ if (pThis->pEncapsulated)
+ {
+ if (pThis->cBits & 7)
+ return false;
+
+ /* Check the encoded length of the bits. */
+ uint32_t cbEncoded;
+ int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, NULL);
+ if (RT_FAILURE(rc))
+ return false;
+ if (pThis->Asn1Core.cb != 1 + cbEncoded)
+ return false;
+
+ /* Check the encoded bits, if there are any. */
+ if (cbEncoded)
+ {
+ if (!pThis->Asn1Core.uData.pv)
+ return false;
+
+ /* Check the first byte, the unused bit count. */
+ if (*pThis->Asn1Core.uData.pu8 != 0)
+ return false;
+
+ /* Check the other bytes. */
+ RTASN1BITSTRINGWRITERCTX Ctx;
+ Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8;
+ Ctx.cbBuf = cbEncoded + 1;
+ Ctx.offBuf = 1;
+ rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1BitStringEncodeCompare, &Ctx, NULL);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ }
+ return true;
+}
+
+
+
+
+/*
+ * ASN.1 BIT STRING - Standard Methods.
+ */
+
+/** @interface_method_impl{FNRTASN1COREVTENCODEPREP} */
+static DECLCALLBACK(int) RTAsn1BitString_EncodePrep(PRTASN1CORE pThisCore, uint32_t fFlags, PRTERRINFO pErrInfo)
+{
+ PRTASN1BITSTRING pThis = (PRTASN1BITSTRING)pThisCore;
+ if (!pThis->pEncapsulated)
+ {
+ Assert(pThis->cBits == 0 || pThis->Asn1Core.uData.pv);
+ return VINF_SUCCESS;
+ }
+
+ /* Figure out the size of the encapsulated content. */
+ uint32_t cbEncoded;
+ int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /* Free the bytes if they don't match up. */
+ if (pThis->Asn1Core.uData.pv)
+ {
+ bool fMustFree = pThis->Asn1Core.cb != 1 + cbEncoded || (pThis->cBits & 7);
+ if (!fMustFree)
+ {
+ RTASN1BITSTRINGWRITERCTX Ctx;
+ Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8;
+ Ctx.cbBuf = 1 + cbEncoded;
+ Ctx.offBuf = 1;
+ fMustFree = *Ctx.pbBuf != 0;
+ if (!fMustFree)
+ {
+ rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1BitStringEncodeCompare, &Ctx, NULL);
+ fMustFree = RT_FAILURE_NP(rc);
+ }
+ }
+ if (fMustFree)
+ {
+ pThis->uBits.pv = NULL;
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ }
+ }
+ pThis->Asn1Core.cb = 1 + cbEncoded;
+ pThis->cBits = cbEncoded * 8;
+
+ rc = RTAsn1EncodeRecalcHdrSize(&pThis->Asn1Core, fFlags, pErrInfo);
+ }
+ return rc;
+}
+
+
+/** @interface_method_impl{FNRTASN1COREVTENCODEWRITE} */
+static DECLCALLBACK(int) RTAsn1BitString_EncodeWrite(PRTASN1CORE pThisCore, uint32_t fFlags, PFNRTASN1ENCODEWRITER pfnWriter,
+ void *pvUser, PRTERRINFO pErrInfo)
+{
+ PRTASN1BITSTRING pThis = (PRTASN1BITSTRING)pThisCore;
+
+ AssertReturn(RT_ALIGN(pThis->cBits, 8) / 8 + 1 == pThis->Asn1Core.cb, VERR_INTERNAL_ERROR_3);
+
+ /*
+ * First the header.
+ */
+ int rc = RTAsn1EncodeWriteHeader(&pThis->Asn1Core, fFlags, pfnWriter, pvUser, pErrInfo);
+ if (RT_SUCCESS(rc) && rc != VINF_ASN1_NOT_ENCODED)
+ {
+ /*
+ * The content starts with an unused bit count. Calculate it in case we
+ * need to write it out.
+ */
+ uint8_t cUnusedBits = 0;
+ if ((pThis->cBits & 7) != 0)
+ cUnusedBits = 8 - (pThis->cBits & 7);
+
+ /*
+ * If nothing is encapsulated, the core points to the content (if we have any).
+ */
+ if (!pThis->pEncapsulated)
+ {
+ if (pThis->cBits > 0)
+ {
+ Assert(pThis->Asn1Core.uData.pu8[0] == cUnusedBits);
+ rc = pfnWriter(pThis->Asn1Core.uData.pu8, pThis->Asn1Core.cb, pvUser, pErrInfo);
+ }
+ else
+ rc = pfnWriter(&cUnusedBits, sizeof(cUnusedBits), pvUser, pErrInfo);
+ }
+ /*
+ * Write the unused bit count and then call upon the encapsulated
+ * content to serialize itself.
+ */
+ else
+ {
+ rc = pfnWriter(&cUnusedBits, sizeof(cUnusedBits), pvUser, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, pfnWriter, pvUser, pErrInfo);
+ }
+ }
+ return rc;
+}
+
+
+RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1BitString_Vtable =
+{
+ "RTAsn1BitString",
+ sizeof(RTASN1BITSTRING),
+ ASN1_TAG_BIT_STRING,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ 0,
+ (PFNRTASN1COREVTDTOR)RTAsn1BitString_Delete,
+ (PFNRTASN1COREVTENUM)RTAsn1BitString_Enum,
+ (PFNRTASN1COREVTCLONE)RTAsn1BitString_Clone,
+ (PFNRTASN1COREVTCOMPARE)RTAsn1BitString_Compare,
+ (PFNRTASN1COREVTCHECKSANITY)RTAsn1BitString_CheckSanity,
+ RTAsn1BitString_EncodePrep,
+ RTAsn1BitString_EncodeWrite
+};
+
+
+RTDECL(int) RTAsn1BitString_Init(PRTASN1BITSTRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_ZERO(*pThis);
+
+ RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_BIT_STRING, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1BitString_Vtable, RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+ /*pThis->cBits = 0;
+ pThis->cMaxBits = 0;
+ pThis->uBits.pv = NULL;
+ pThis->pEncapsulated = NULL; */
+ RTAsn1MemInitAllocation(&pThis->EncapsulatedAllocation, pAllocator);
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1BitString_InitWithData(PRTASN1BITSTRING pThis, void const *pvSrc, uint32_t cSrcBits,
+ PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RTAsn1BitString_Init(pThis, pAllocator);
+ Assert(pThis->pEncapsulated == NULL);
+
+ uint32_t cbToCopy = (cSrcBits + 7) / 8;
+ int rc = RTAsn1ContentAllocZ(&pThis->Asn1Core, cbToCopy + 1, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cBits = cSrcBits;
+ uint8_t *pbDst = (uint8_t *)pThis->Asn1Core.uData.pu8;
+ pThis->uBits.pv = pbDst + 1;
+ *pbDst = (cSrcBits & 7) != 0 ? 8 - (cSrcBits & 7) : 0; /* unused bits */
+ memcpy(pbDst + 1, pvSrc, cbToCopy);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1BitString_Clone(PRTASN1BITSTRING pThis, PCRTASN1BITSTRING pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator);
+
+ RT_ZERO(*pThis);
+ if (RTAsn1BitString_IsPresent(pSrc))
+ {
+ AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1BitString_Vtable, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ if (!pSrc->pEncapsulated)
+ rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator);
+ else
+ rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ RTAsn1MemInitAllocation(&pThis->EncapsulatedAllocation, pAllocator);
+ pThis->cBits = pSrc->cBits;
+ pThis->cMaxBits = pSrc->cMaxBits;
+ if (!pSrc->pEncapsulated)
+ pThis->uBits.pv = pThis->Asn1Core.uData.pu8 ? pThis->Asn1Core.uData.pu8 + 1 : NULL;
+ else
+ {
+ PCRTASN1COREVTABLE pOps = pSrc->pEncapsulated->pOps;
+ Assert(!pOps || pOps->pfnClone);
+ if (pOps && pOps->pfnClone)
+ {
+ /* We can clone the decoded encapsulated object. */
+ rc = RTAsn1MemAllocZ(&pThis->EncapsulatedAllocation, (void **)&pThis->pEncapsulated, pOps->cbStruct);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pOps->pfnClone(pThis->pEncapsulated, pSrc->pEncapsulated, pAllocator);
+ if (RT_FAILURE(rc))
+ RTAsn1MemFree(&pThis->EncapsulatedAllocation, pThis->pEncapsulated);
+ }
+ }
+ else
+ {
+ /* Borrow the encapsulated pointer and use RTAsn1BitString_RefreshContent
+ to get an accurate copy of the bytes. */
+ pThis->pEncapsulated = pSrc->pEncapsulated;
+ rc = RTAsn1BitString_RefreshContent(pThis, RTASN1ENCODE_F_DER, pAllocator, NULL);
+ pThis->pEncapsulated = NULL;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ RT_ZERO(*pThis);
+ return rc;
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1BitString_Delete(PRTASN1BITSTRING pThis)
+{
+ if ( pThis
+ && RTAsn1BitString_IsPresent(pThis))
+ {
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1BitString_Vtable);
+
+ /* Destroy the encapsulated object. */
+ if (pThis->pEncapsulated)
+ {
+ RTAsn1VtDelete(pThis->pEncapsulated);
+ if (pThis->EncapsulatedAllocation.cbAllocated)
+ RTAsn1MemFree(&pThis->EncapsulatedAllocation, pThis->pEncapsulated);
+ }
+
+ /* Delete content and wipe the content. */
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1BitString_Enum(PRTASN1BITSTRING pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ Assert(pThis && (!RTAsn1BitString_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1BitString_Vtable));
+
+ /* Enumerate the encapsulated object if present. */
+ if (pThis->pEncapsulated)
+ return pfnCallback(pThis->pEncapsulated, "Encapsulated", uDepth + 1, pvUser);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1BitString_Compare(PCRTASN1BITSTRING pLeft, PCRTASN1BITSTRING pRight)
+{
+ Assert(pLeft && (!RTAsn1BitString_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1BitString_Vtable));
+ Assert(pRight && (!RTAsn1BitString_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1BitString_Vtable));
+
+ int iDiff;
+ if (RTAsn1BitString_IsPresent(pLeft))
+ {
+ if (RTAsn1BitString_IsPresent(pRight))
+ {
+ /* Since it's really hard to tell whether encapsulated objects have
+ been modified or not, we might have to refresh both objects
+ while doing this compare. We'll try our best to avoid it though. */
+ if (pLeft->pEncapsulated || pRight->pEncapsulated)
+ {
+ if ( pLeft->pEncapsulated
+ && pRight->pEncapsulated
+ && pLeft->pEncapsulated->pOps == pRight->pEncapsulated->pOps)
+ iDiff = pLeft->pEncapsulated->pOps->pfnCompare(pLeft->pEncapsulated, pRight->pEncapsulated);
+ else
+ {
+ /* No direct comparison of encapsulated objects possible,
+ make sure we've got the rigth bytes then. */
+ if ( pLeft->pEncapsulated
+ && !RTAsn1BitString_AreContentBitsValid(pLeft, RTASN1ENCODE_F_DER))
+ {
+ int rc = RTAsn1BitString_RefreshContent((PRTASN1BITSTRING)pLeft, RTASN1ENCODE_F_DER,
+ pLeft->EncapsulatedAllocation.pAllocator, NULL);
+ AssertRC(rc);
+ }
+
+ if ( pRight->pEncapsulated
+ && !RTAsn1BitString_AreContentBitsValid(pRight, RTASN1ENCODE_F_DER))
+ {
+ int rc = RTAsn1BitString_RefreshContent((PRTASN1BITSTRING)pRight, RTASN1ENCODE_F_DER,
+ pRight->EncapsulatedAllocation.pAllocator, NULL);
+ AssertRC(rc);
+ }
+
+ /* Compare the content bytes. */
+ iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/);
+ }
+ }
+ /*
+ * No encapsulated object, just compare the raw content bytes.
+ */
+ else
+ iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/);
+ }
+ else
+ iDiff = -1;
+ }
+ else
+ iDiff = 0 - (int)RTAsn1BitString_IsPresent(pRight);
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1BitString_CheckSanity(PCRTASN1BITSTRING pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ if (RT_UNLIKELY(!RTAsn1BitString_IsPresent(pThis)))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (BIT STRING).", pszErrorTag);
+
+ if (pThis->cBits > pThis->cMaxBits)
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_BITSTRING_OUT_OF_BOUNDS, "%s: Exceeding max bits: cBits=%u cMaxBits=%u.",
+ pszErrorTag, pThis->cBits, pThis->cMaxBits);
+
+ if (pThis->pEncapsulated)
+ return pThis->pEncapsulated->pOps->pfnCheckSanity(pThis->pEncapsulated, fFlags & RTASN1_CHECK_SANITY_F_COMMON_MASK,
+ pErrInfo, pszErrorTag);
+ return VINF_SUCCESS;
+}
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-bitstring-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-core.h>
+#include <iprt/asn1-generator-init.h>
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-boolean-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-boolean-decode.cpp
new file mode 100644
index 00000000..a8db498f
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-boolean-decode.cpp
@@ -0,0 +1,93 @@
+/* $Id: asn1-ut-boolean-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, BOOLEAN Type, Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+
+RTDECL(int) RTAsn1Boolean_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1BOOLEAN pThis, const char *pszErrorTag)
+{
+ pThis->fValue = 0;
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_BOOLEAN,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, fFlags, pszErrorTag, "BOOLEAN");
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->Asn1Core.cb == 1)
+ {
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ pThis->Asn1Core.pOps = &g_RTAsn1Boolean_Vtable;
+
+ pThis->fValue = *pThis->Asn1Core.uData.pu8 != 0;
+ if ( *pThis->Asn1Core.uData.pu8 == 0
+ || *pThis->Asn1Core.uData.pu8 == 0xff
+ || !(pCursor->fFlags & (RTASN1CURSOR_FLAGS_DER | RTASN1CURSOR_FLAGS_CER)) )
+ return VINF_SUCCESS;
+
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BOOLEAN_ENCODING,
+ "%s: Invalid CER/DER boolean value: %#x, valid: 0, 0xff",
+ pszErrorTag, *pThis->Asn1Core.uData.pu8);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_BOOLEAN_ENCODING, "%s: Invalid boolean length, exepcted 1: %#x",
+ pszErrorTag, pThis->Asn1Core.cb);
+ }
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-boolean-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-boolean-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-boolean-template.h
new file mode 100644
index 00000000..39ef92e5
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-boolean-template.h
@@ -0,0 +1,55 @@
+/* $Id: asn1-ut-boolean-template.h $ */
+/** @file
+ * IPRT - ASN.1, BOOLEAN Type, Collection Type Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+#define RTASN1TMPL_TYPE RTASN1SEQOFBOOLEANS
+#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfBooleans
+#define RTASN1TMPL_INT_NAME rtAsn1SeqOfBooleans
+RTASN1TMPL_SEQ_OF(RTASN1BOOLEAN, RTAsn1Boolean);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+#define RTASN1TMPL_TYPE RTASN1SETOFBOOLEANS
+#define RTASN1TMPL_EXT_NAME RTAsn1SetOfBooleans
+#define RTASN1TMPL_INT_NAME rtAsn1SetOfBooleans
+RTASN1TMPL_SET_OF(RTASN1BOOLEAN, RTAsn1Boolean);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-boolean.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-boolean.cpp
new file mode 100644
index 00000000..7b0485e5
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-boolean.cpp
@@ -0,0 +1,218 @@
+/* $Id: asn1-ut-boolean.cpp $ */
+/** @file
+ * IPRT - ASN.1, BOOLEAN Type.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/bignum.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The false value (DER & CER). */
+static const uint8_t g_bFalse = 0;
+/** The true value (DER & CER). */
+static const uint8_t g_bTrue = 0xff;
+
+
+/*
+ * ASN.1 BOOLEAN - Special Methods.
+ */
+
+RTDECL(int) RTAsn1Boolean_InitDefault(PRTASN1BOOLEAN pThis, bool fValue, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_NOREF_PV(pAllocator);
+ RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_BOOLEAN, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1Boolean_Vtable, RTASN1CORE_F_DEFAULT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+ pThis->fValue = fValue;
+ pThis->Asn1Core.uData.pv = (void *)(fValue ? &g_bTrue : &g_bFalse);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Boolean_Set(PRTASN1BOOLEAN pThis, bool fValue)
+{
+ /* Since we don't need an allocator, let's automatically initialize the struct. */
+ if (!RTAsn1Boolean_IsPresent(pThis))
+ RTAsn1Boolean_Init(pThis, NULL);
+ else
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ pThis->fValue = fValue;
+ pThis->Asn1Core.uData.pv = (void *)(fValue ? &g_bTrue : &g_bFalse);
+ pThis->Asn1Core.cb = 1;
+ pThis->Asn1Core.fFlags &= ~RTASN1CORE_F_DEFAULT;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRESENT;
+ return VINF_SUCCESS;
+}
+
+
+
+/*
+ * ASN.1 BOOLEAN - Standard Methods.
+ */
+
+RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Boolean_Vtable =
+{
+ "RTAsn1Boolean",
+ sizeof(RTASN1BOOLEAN),
+ ASN1_TAG_BOOLEAN,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ 0,
+ (PFNRTASN1COREVTDTOR)RTAsn1Boolean_Delete,
+ NULL,
+ (PFNRTASN1COREVTCLONE)RTAsn1Boolean_Clone,
+ (PFNRTASN1COREVTCOMPARE)RTAsn1Boolean_Compare,
+ (PFNRTASN1COREVTCHECKSANITY)RTAsn1Boolean_CheckSanity,
+ NULL,
+ NULL
+};
+
+
+RTDECL(int) RTAsn1Boolean_Init(PRTASN1BOOLEAN pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_NOREF_PV(pAllocator);
+ RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_BOOLEAN, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1Boolean_Vtable, RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+ pThis->fValue = true;
+ pThis->Asn1Core.cb = 1;
+ pThis->Asn1Core.uData.pv = (void *)&g_bTrue;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Boolean_Clone(PRTASN1BOOLEAN pThis, PCRTASN1BOOLEAN pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator);
+ RT_ZERO(*pThis);
+ if (RTAsn1Boolean_IsPresent(pSrc))
+ {
+ AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable, VERR_INTERNAL_ERROR_3);
+ AssertReturn(pSrc->Asn1Core.cb <= 1, VERR_INTERNAL_ERROR_4);
+
+ int rc;
+ if ( pSrc->Asn1Core.cb == 1
+ && pSrc->Asn1Core.uData.pu8[0] != 0x00
+ && pSrc->Asn1Core.uData.pu8[0] != 0xff)
+ {
+ /* DER/CER incompatible value must be copied as-is. */
+ rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ /* No value or one of the standard values. */
+ rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->Asn1Core.uData.pv = (void *)(pSrc->fValue ? &g_bTrue : &g_bFalse);
+ }
+ pThis->fValue = pSrc->fValue;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1Boolean_Delete(PRTASN1BOOLEAN pThis)
+{
+ if ( pThis
+ && RTAsn1Boolean_IsPresent(pThis))
+ {
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable);
+ Assert(pThis->Asn1Core.cb <= 1);
+
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1Boolean_Enum(PRTASN1BOOLEAN pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ Assert(pThis && (!RTAsn1Boolean_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable));
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser);
+
+ /* No children to enumerate. */
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Boolean_Compare(PCRTASN1BOOLEAN pLeft, PCRTASN1BOOLEAN pRight)
+{
+ Assert(pLeft && (!RTAsn1Boolean_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable));
+ Assert(pRight && (!RTAsn1Boolean_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1Boolean_Vtable));
+
+ int iDiff;
+ if (RTAsn1Boolean_IsPresent(pLeft))
+ {
+ if (RTAsn1Boolean_IsPresent(pRight))
+ iDiff = (int)pLeft->fValue - (int)pRight->fValue;
+ else
+ iDiff = -1;
+ }
+ else
+ iDiff = 0 - (int)RTAsn1Boolean_IsPresent(pRight);
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1Boolean_CheckSanity(PCRTASN1BOOLEAN pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ if (RT_UNLIKELY(!RTAsn1Boolean_IsPresent(pThis)))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (BOOLEAN).", pszErrorTag);
+ RT_NOREF_PV(fFlags);
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-boolean-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-core.h>
+#include <iprt/asn1-generator-init.h>
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-core-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-core-decode.cpp
new file mode 100644
index 00000000..3015ab89
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-core-decode.cpp
@@ -0,0 +1,69 @@
+/* $Id: asn1-ut-core-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, Generic Core Type, Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTAsn1Core_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1CORE pThis, const char *pszErrorTag)
+{
+ int rc = RTAsn1CursorReadHdr(pCursor, pThis, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ RTAsn1CursorSkip(pCursor, pThis->cb);
+ pThis->pOps = &g_RTAsn1Core_Vtable;
+ return VINF_SUCCESS;
+ }
+ RT_NOREF_PV(fFlags);
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-core-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-core-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-core-template.h
new file mode 100644
index 00000000..10a9b995
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-core-template.h
@@ -0,0 +1,55 @@
+/* $Id: asn1-ut-core-template.h $ */
+/** @file
+ * IPRT - ASN.1, Generic Core Type, Collection Type Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+#define RTASN1TMPL_TYPE RTASN1SEQOFCORES
+#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfCores
+#define RTASN1TMPL_INT_NAME rtAsn1SeqOfCores
+RTASN1TMPL_SEQ_OF(RTASN1CORE, RTAsn1Core);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+#define RTASN1TMPL_TYPE RTASN1SETOFCORES
+#define RTASN1TMPL_EXT_NAME RTAsn1SetOfCores
+#define RTASN1TMPL_INT_NAME rtAsn1SetOfCores
+RTASN1TMPL_SET_OF(RTASN1CORE, RTAsn1Core);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-core.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-core.cpp
new file mode 100644
index 00000000..b9ff385f
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-core.cpp
@@ -0,0 +1,331 @@
+/* $Id: asn1-ut-core.cpp $ */
+/** @file
+ * IPRT - ASN.1, Generic Core Type.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/alloca.h>
+#include <iprt/bignum.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*
+ * ASN.1 Core - Special methods (for all applications of RTASN1CORE).
+ */
+
+RTDECL(int) RTAsn1Core_SetTagAndFlags(PRTASN1CORE pAsn1Core, uint32_t uTag, uint8_t fClass)
+{
+ if (!(pAsn1Core->fFlags & RTASN1CORE_F_TAG_IMPLICIT))
+ {
+ pAsn1Core->fRealClass = pAsn1Core->fClass;
+ pAsn1Core->uRealTag = pAsn1Core->uTag;
+ Assert(pAsn1Core->uRealTag == pAsn1Core->uTag);
+ pAsn1Core->fFlags |= RTASN1CORE_F_TAG_IMPLICIT;
+ }
+ pAsn1Core->uTag = uTag;
+ pAsn1Core->fClass = fClass;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Core_ChangeTag(PRTASN1CORE pAsn1Core, uint32_t uTag)
+{
+ if (!(pAsn1Core->fFlags & RTASN1CORE_F_TAG_IMPLICIT))
+ pAsn1Core->uTag = uTag;
+ pAsn1Core->uRealTag = uTag;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1Core_ResetImplict(PRTASN1CORE pThis)
+{
+ AssertPtr(pThis);
+ if (pThis->fFlags & RTASN1CORE_F_TAG_IMPLICIT)
+ {
+ pThis->fFlags &= ~RTASN1CORE_F_TAG_IMPLICIT;
+ pThis->uTag = pThis->uRealTag;
+ pThis->fClass = pThis->fRealClass;
+ }
+}
+
+
+RTDECL(int) RTAsn1Core_InitEx(PRTASN1CORE pAsn1Core, uint32_t uTag, uint8_t fClass, PCRTASN1COREVTABLE pOps, uint32_t fFlags)
+{
+ pAsn1Core->uTag = uTag;
+ pAsn1Core->fClass = fClass;
+ pAsn1Core->uRealTag = uTag;
+ pAsn1Core->fRealClass = fClass;
+ pAsn1Core->cbHdr = 0;
+ pAsn1Core->cb = 0;
+ pAsn1Core->fFlags = fFlags;
+ pAsn1Core->uData.pv = NULL;
+ pAsn1Core->pOps = pOps;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Core_InitDefault(PRTASN1CORE pAsn1Core, uint32_t uTag, uint8_t fClass)
+{
+ return RTAsn1Core_InitEx(pAsn1Core, uTag, fClass, NULL, RTASN1CORE_F_DEFAULT);
+}
+
+
+static int rtAsn1Core_CloneEx(PRTASN1CORE pThis, PCRTASN1CORE pSrc, PCRTASN1ALLOCATORVTABLE pAllocator, bool fCopyContent)
+{
+ Assert(RTASN1CORE_IS_PRESENT(pSrc));
+ pThis->uTag = pSrc->uTag;
+ pThis->fClass = pSrc->fClass;
+ pThis->uRealTag = pSrc->uRealTag;
+ pThis->fRealClass = pSrc->fRealClass;
+ pThis->cbHdr = pSrc->cbHdr;
+ pThis->fFlags = pSrc->fFlags & ~(RTASN1CORE_F_ALLOCATED_CONTENT | RTASN1CORE_F_DECODED_CONTENT);
+ pThis->pOps = pSrc->pOps;
+ pThis->cb = 0;
+ pThis->uData.pv = NULL;
+ if (pSrc->cb)
+ {
+ if (!fCopyContent)
+ pThis->cb = pSrc->cb;
+ else
+ {
+ int rc = RTAsn1ContentDup(pThis, pSrc->uData.pv, pSrc->cb, pAllocator);
+ if (RT_FAILURE(rc))
+ {
+ RT_ZERO(*pThis);
+ return rc;
+ }
+ Assert(pThis->cb == pSrc->cb);
+ AssertPtr(pThis->uData.pv);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Core_CloneContent(PRTASN1CORE pThis, PCRTASN1CORE pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ return rtAsn1Core_CloneEx(pThis, pSrc, pAllocator, true /*fCopyContent*/);
+}
+
+
+RTDECL(int) RTAsn1Core_CloneNoContent(PRTASN1CORE pThis, PCRTASN1CORE pSrc)
+{
+ return rtAsn1Core_CloneEx(pThis, pSrc, NULL, false /*fCopyContent*/);
+}
+
+
+RTDECL(int) RTAsn1Core_CompareEx(PCRTASN1CORE pLeft, PCRTASN1CORE pRight, bool fIgnoreTagAndClass)
+{
+ int iDiff;
+ if (RTASN1CORE_IS_PRESENT(pLeft))
+ {
+ if (RTASN1CORE_IS_PRESENT(pRight))
+ {
+ iDiff = memcmp(pLeft->uData.pv, pRight->uData.pv, RT_MIN(pLeft->cb, pRight->cb));
+ if (!iDiff)
+ {
+ if (pLeft->cb != pRight->cb)
+ iDiff = pLeft->cb < pRight->cb ? -1 : 1;
+ else if (!fIgnoreTagAndClass)
+ {
+ if (pLeft->uTag != pRight->uTag)
+ iDiff = pLeft->uTag < pRight->uTag ? -1 : 1;
+ else if (pLeft->fClass != pRight->fClass)
+ iDiff = pLeft->fClass < pRight->fClass ? -1 : 1;
+ }
+ }
+ else
+ iDiff = iDiff < 0 ? -1 : 1;
+ }
+ else
+ iDiff = -1;
+ }
+ else
+ iDiff = 0 - (int)RTASN1CORE_IS_PRESENT(pRight);
+ return iDiff;
+}
+
+
+/**
+ * @interface_method_impl{RTASN1COREVTABLE,pfnEncodePrep,
+ * This is for not dropping the unparsed content of a 'core' structure when
+ * re-encoding it. }
+ */
+static DECLCALLBACK(int) rtAsn1Core_EncodePrep(PRTASN1CORE pThisCore, uint32_t fFlags, PRTERRINFO pErrInfo)
+{
+ /* We don't update anything here. */
+ RT_NOREF(pThisCore, fFlags, pErrInfo);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTASN1COREVTABLE,pfnEncodeWrite,
+ * This is for not dropping the unparsed content of a 'core' structure when
+ * re-encoding it. }
+ */
+static DECLCALLBACK(int) rtAsn1Core_EncodeWrite(PRTASN1CORE pThisCore, uint32_t fFlags, PFNRTASN1ENCODEWRITER pfnWriter,
+ void *pvUser, PRTERRINFO pErrInfo)
+{
+ int rc = RTAsn1EncodeWriteHeader(pThisCore, fFlags, pfnWriter, pvUser, pErrInfo);
+ if ( RT_SUCCESS(rc)
+ && rc != VINF_ASN1_NOT_ENCODED)
+ {
+ Assert(!RTASN1CORE_IS_DUMMY(pThisCore));
+ if (pThisCore->cb)
+ {
+ AssertPtrReturn(pThisCore->uData.pv,
+ RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_DATA_POINTER,
+ "Invalid uData pointer %p for lone ASN.1 core with %#x bytes of content",
+ pThisCore->uData.pv, pThisCore->cb));
+ rc = pfnWriter(pThisCore->uData.pv, pThisCore->cb, pvUser, pErrInfo);
+ }
+ }
+ return rc;
+}
+
+
+
+/*
+ * ASN.1 Core - Standard Methods.
+ *
+ * @note Children of the ASN.1 Core doesn't normally call these, they are for
+ * when RTASN1CORE is used as a member type.
+ */
+
+RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Core_Vtable =
+{
+ "RTAsn1Core",
+ sizeof(RTASN1CORE),
+ UINT8_MAX,
+ UINT8_MAX,
+ 0,
+ RTAsn1Core_Delete,
+ RTAsn1Core_Enum,
+ (PFNRTASN1COREVTCLONE)RTAsn1Core_Clone,
+ (PFNRTASN1COREVTCOMPARE)RTAsn1Core_Compare,
+ (PFNRTASN1COREVTCHECKSANITY)RTAsn1Core_CheckSanity,
+ rtAsn1Core_EncodePrep,
+ rtAsn1Core_EncodeWrite
+};
+
+
+RTDECL(int) RTAsn1Core_Init(PRTASN1CORE pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_NOREF_PV(pAllocator);
+ return RTAsn1Core_InitEx(pThis, 0, ASN1_TAGCLASS_CONTEXT | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1Core_Vtable, RTASN1CORE_F_PRESENT);
+}
+
+
+RTDECL(int) RTAsn1Core_Clone(PRTASN1CORE pThis, PCRTASN1CORE pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ int rc;
+ RT_ZERO(*pThis);
+ if (RTASN1CORE_IS_PRESENT(pSrc))
+ {
+ Assert(pSrc->pOps == &g_RTAsn1Core_Vtable);
+
+ rc = RTAsn1Core_CloneContent(pThis, pSrc, pAllocator);
+ }
+ else
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+RTDECL(void) RTAsn1Core_Delete(PRTASN1CORE pThis)
+{
+ if (pThis && RTASN1CORE_IS_PRESENT(pThis))
+ {
+ Assert(pThis->pOps == &g_RTAsn1Core_Vtable);
+
+ RTAsn1ContentFree(pThis);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1Core_Enum(PRTASN1CORE pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ /* We have no children to enumerate. */
+ Assert(pThis && (!RTASN1CORE_IS_PRESENT(pThis) || pThis->pOps == &g_RTAsn1Core_Vtable));
+ NOREF(pThis);
+ NOREF(pfnCallback);
+ NOREF(uDepth);
+ NOREF(pvUser);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Core_Compare(PCRTASN1CORE pLeft, PCRTASN1CORE pRight)
+{
+ Assert(pLeft && (!RTASN1CORE_IS_PRESENT(pLeft) || pLeft->pOps == &g_RTAsn1Core_Vtable));
+ Assert(pRight && (!RTASN1CORE_IS_PRESENT(pRight) || pRight->pOps == &g_RTAsn1Core_Vtable));
+
+ return RTAsn1Core_CompareEx(pLeft, pRight, false /*fIgnoreTagAndClass*/);
+}
+
+
+RTDECL(int) RTAsn1Core_CheckSanity(PCRTASN1CORE pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+
+ /* We can only check that it's present. */
+ if (!RTAsn1Core_IsPresent(pThis))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (RTASN1CORE).", pszErrorTag);
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-core-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-core.h>
+#include <iprt/asn1-generator-init.h>
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-dyntype-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-dyntype-decode.cpp
new file mode 100644
index 00000000..bc48688a
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-dyntype-decode.cpp
@@ -0,0 +1,250 @@
+/* $Id: asn1-ut-dyntype-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, Dynamic Type, Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+
+RTDECL(int) RTAsn1DynType_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1DYNTYPE pDynType, const char *pszErrorTag)
+{
+ RT_ZERO(*pDynType);
+
+ Assert(!(fFlags & RTASN1CURSOR_GET_F_IMPLICIT)); RT_NOREF_PV(fFlags);
+ uint32_t cbSavedLeft = pCursor->cbLeft;
+ uint8_t const *pbSavedCur = pCursor->pbCur;
+
+ int rc = RTAsn1CursorReadHdr(pCursor, &pDynType->u.Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ pDynType->enmType = RTASN1TYPE_CORE;
+
+ if (pDynType->u.Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE))
+ {
+ switch (pDynType->u.Core.uTag)
+ {
+ case ASN1_TAG_BOOLEAN:
+ pDynType->enmType = RTASN1TYPE_BOOLEAN;
+ break;
+ case ASN1_TAG_INTEGER:
+ pDynType->enmType = RTASN1TYPE_INTEGER;
+ break;
+ //case ASN1_TAG_ENUMERATED:
+ // pDynType->enmType = RTASN1TYPE_ENUMERATED;
+ // break;
+ //case ASN1_TAG_REAL:
+ // pDynType->enmType = RTASN1TYPE_REAL;
+ // break;
+ case ASN1_TAG_BIT_STRING:
+ pDynType->enmType = RTASN1TYPE_BIT_STRING;
+ break;
+ case ASN1_TAG_OCTET_STRING:
+ pDynType->enmType = RTASN1TYPE_OCTET_STRING;
+ break;
+ case ASN1_TAG_NULL:
+ pDynType->enmType = RTASN1TYPE_NULL;
+ break;
+ case ASN1_TAG_SEQUENCE:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 SEQUENCE shall be constructed.");
+ case ASN1_TAG_SET:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 SET shall be constructed.");
+ case ASN1_TAG_OID:
+ pDynType->enmType = RTASN1TYPE_OBJID;
+ break;
+ //case ASN1_TAG_RELATIVE_OID:
+ // pDynType->enmType = RTASN1TYPE_RELATIVE_OBJID;
+ // break;
+ case ASN1_TAG_UTC_TIME:
+ case ASN1_TAG_GENERALIZED_TIME:
+ pDynType->enmType = RTASN1TYPE_TIME;
+ break;
+ case ASN1_TAG_UTF8_STRING:
+ case ASN1_TAG_NUMERIC_STRING:
+ case ASN1_TAG_PRINTABLE_STRING:
+ case ASN1_TAG_T61_STRING:
+ case ASN1_TAG_VIDEOTEX_STRING:
+ case ASN1_TAG_IA5_STRING:
+ case ASN1_TAG_GRAPHIC_STRING:
+ case ASN1_TAG_VISIBLE_STRING:
+ case ASN1_TAG_UNIVERSAL_STRING:
+ case ASN1_TAG_GENERAL_STRING:
+ case ASN1_TAG_BMP_STRING:
+ pDynType->enmType = RTASN1TYPE_STRING;
+ break;
+ //case ASN1_TAG_CHARACTER_STRING:
+ // pDynType->enmType = RTASN1TYPE_CHARACTER_STRING;
+ // break;
+
+ default:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_TAG_NOT_IMPL,
+ "Primitive tag %u (%#x) not implemented.",
+ pDynType->u.Core.uTag, pDynType->u.Core.uTag);
+ }
+ }
+ else if (pDynType->u.Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED))
+ switch (pDynType->u.Core.uTag)
+ {
+ case ASN1_TAG_BOOLEAN:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 BOOLEAN shall be primitive.");
+ case ASN1_TAG_INTEGER:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 BOOLEAN shall be primitive.");
+ case ASN1_TAG_ENUMERATED:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 ENUMERATED shall be primitive.");
+ case ASN1_TAG_REAL:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 REAL shall be primitive.");
+ case ASN1_TAG_BIT_STRING:
+ pDynType->enmType = RTASN1TYPE_BIT_STRING;
+ break;
+ case ASN1_TAG_OCTET_STRING:
+ pDynType->enmType = RTASN1TYPE_OCTET_STRING;
+ break;
+ case ASN1_TAG_NULL:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 NULL shall be primitive.");
+ case ASN1_TAG_SEQUENCE:
+#if 0
+ pDynType->enmType = RTASN1TYPE_SEQUENCE_CORE;
+ pDynType->u.SeqCore.Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ RTAsn1CursorSkip(pCursor, pDynType->u.Core.cb);
+ return VINF_SUCCESS;
+#else
+ pDynType->enmType = RTASN1TYPE_CORE;
+#endif
+ break;
+ case ASN1_TAG_SET:
+#if 0
+ pDynType->enmType = RTASN1TYPE_SET_CORE;
+ pDynType->u.SeqCore.Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ RTAsn1CursorSkip(pCursor, pDynType->u.Core.cb);
+ return VINF_SUCCESS;
+#else
+ pDynType->enmType = RTASN1TYPE_CORE;
+#endif
+ break;
+ case ASN1_TAG_OID:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 OBJECT ID shall be primitive.");
+ case ASN1_TAG_RELATIVE_OID:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_BAD_TAG, "ASN.1 RELATIVE OID shall be primitive.");
+
+ case ASN1_TAG_UTF8_STRING:
+ case ASN1_TAG_NUMERIC_STRING:
+ case ASN1_TAG_PRINTABLE_STRING:
+ case ASN1_TAG_T61_STRING:
+ case ASN1_TAG_VIDEOTEX_STRING:
+ case ASN1_TAG_IA5_STRING:
+ case ASN1_TAG_GRAPHIC_STRING:
+ case ASN1_TAG_VISIBLE_STRING:
+ case ASN1_TAG_UNIVERSAL_STRING:
+ case ASN1_TAG_GENERAL_STRING:
+ case ASN1_TAG_BMP_STRING:
+ pDynType->enmType = RTASN1TYPE_STRING;
+ break;
+ //case ASN1_TAG_CHARACTER_STRING:
+ // pDynType->enmType = RTASN1TYPE_CHARACTER_STRING;
+ // break;
+
+ default:
+ RT_ZERO(*pDynType);
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_DYNTYPE_TAG_NOT_IMPL,
+ "Constructed tag %u (%#x) not implemented.",
+ pDynType->u.Core.uTag, pDynType->u.Core.uTag);
+ }
+ else
+ Assert(pDynType->enmType == RTASN1TYPE_CORE);
+
+ /*
+ * Restore the cursor and redo with specific type.
+ */
+ pCursor->pbCur = pbSavedCur;
+ pCursor->cbLeft = cbSavedLeft;
+
+ switch (pDynType->enmType)
+ {
+ case RTASN1TYPE_INTEGER:
+ rc = RTAsn1Integer_DecodeAsn1(pCursor, 0, &pDynType->u.Integer, pszErrorTag);
+ break;
+ case RTASN1TYPE_BOOLEAN:
+ rc = RTAsn1Boolean_DecodeAsn1(pCursor, 0, &pDynType->u.Boolean, pszErrorTag);
+ break;
+ case RTASN1TYPE_OBJID:
+ rc = RTAsn1ObjId_DecodeAsn1(pCursor, 0, &pDynType->u.ObjId, pszErrorTag);
+ break;
+ case RTASN1TYPE_BIT_STRING:
+ rc = RTAsn1BitString_DecodeAsn1(pCursor, 0, &pDynType->u.BitString, pszErrorTag);
+ break;
+ case RTASN1TYPE_OCTET_STRING:
+ rc = RTAsn1OctetString_DecodeAsn1(pCursor, 0, &pDynType->u.OctetString, pszErrorTag);
+ break;
+ case RTASN1TYPE_NULL:
+ rc = RTAsn1Null_DecodeAsn1(pCursor, 0, &pDynType->u.Asn1Null, pszErrorTag);
+ break;
+ case RTASN1TYPE_TIME:
+ rc = RTAsn1Time_DecodeAsn1(pCursor, 0, &pDynType->u.Time, pszErrorTag);
+ break;
+ case RTASN1TYPE_STRING:
+ rc = RTAsn1String_DecodeAsn1(pCursor, 0, &pDynType->u.String, pszErrorTag);
+ break;
+ case RTASN1TYPE_CORE:
+ rc = RTAsn1Core_DecodeAsn1(pCursor, 0, &pDynType->u.Core, pszErrorTag);
+ break;
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_4);
+ }
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ RT_ZERO(*pDynType);
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-dyntype.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-dyntype.cpp
new file mode 100644
index 00000000..1708c71a
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-dyntype.cpp
@@ -0,0 +1,197 @@
+/* $Id: asn1-ut-dyntype.cpp $ */
+/** @file
+ * IPRT - ASN.1, Basic Operations.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*
+ * ASN.1 dynamic type union - Standard Methods.
+ */
+
+
+RTDECL(int) RTAsn1DynType_Init(PRTASN1DYNTYPE pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_NOREF_PV(pAllocator);
+ RT_ZERO(*pThis);
+ pThis->enmType = RTASN1TYPE_NOT_PRESENT;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1DynType_Clone(PRTASN1DYNTYPE pThis, PCRTASN1DYNTYPE pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator);
+ RT_ZERO(*pThis);
+ if (RTAsn1DynType_IsPresent(pSrc))
+ {
+ int rc;
+ switch (pSrc->enmType)
+ {
+ case RTASN1TYPE_CORE: rc = RTAsn1Core_Clone(&pThis->u.Core, &pSrc->u.Core, pAllocator); break;
+ case RTASN1TYPE_NULL: rc = RTAsn1Null_Clone(&pThis->u.Asn1Null, &pSrc->u.Asn1Null, pAllocator); break;
+ case RTASN1TYPE_INTEGER: rc = RTAsn1Integer_Clone(&pThis->u.Integer, &pSrc->u.Integer, pAllocator); break;
+ case RTASN1TYPE_BOOLEAN: rc = RTAsn1Boolean_Clone(&pThis->u.Boolean, &pSrc->u.Boolean, pAllocator); break;
+ case RTASN1TYPE_STRING: rc = RTAsn1String_Clone(&pThis->u.String, &pSrc->u.String, pAllocator); break;
+ case RTASN1TYPE_OCTET_STRING: rc = RTAsn1OctetString_Clone(&pThis->u.OctetString, &pSrc->u.OctetString, pAllocator); break;
+ case RTASN1TYPE_BIT_STRING: rc = RTAsn1BitString_Clone(&pThis->u.BitString, &pSrc->u.BitString, pAllocator); break;
+ case RTASN1TYPE_TIME: rc = RTAsn1Time_Clone(&pThis->u.Time, &pSrc->u.Time, pAllocator); break;
+#if 0
+ case RTASN1TYPE_SEQUENCE_CORE: rc = VERR_NOT_SUPPORTED; //rc = RTAsn1SequenceCore_Clone(&pThis->u.SeqCore, &pSrc->u.SeqCore, pAllocator); break;
+ case RTASN1TYPE_SET_CORE: rc = VERR_NOT_SUPPORTED; //rc = RTAsn1SetCore_Clone(&pThis->u.SetCore, &pSrc->u.SetCore, pAllocator); break;
+#endif
+ case RTASN1TYPE_OBJID: rc = RTAsn1ObjId_Clone(&pThis->u.ObjId, &pSrc->u.ObjId, pAllocator); break;
+ default:
+ AssertFailedReturn(VERR_ASN1_INTERNAL_ERROR_2);
+ }
+ if (RT_FAILURE(rc))
+ {
+ RT_ZERO(*pThis);
+ return rc;
+ }
+ pThis->enmType = pSrc->enmType;
+ }
+ else
+ pThis->enmType = RTASN1TYPE_NOT_PRESENT;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1DynType_Delete(PRTASN1DYNTYPE pThis)
+{
+ if ( pThis
+ && RTAsn1DynType_IsPresent(pThis))
+ {
+ if ( pThis->u.Core.pOps
+ && pThis->u.Core.pOps->pfnDtor)
+ pThis->u.Core.pOps->pfnDtor(&pThis->u.Core);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1DynType_SetToNull(PRTASN1DYNTYPE pThis)
+{
+ RTAsn1DynType_Delete(pThis);
+ pThis->enmType = RTASN1TYPE_NULL;
+ return RTAsn1Null_Init(&pThis->u.Asn1Null, NULL /*pAllocator*/);
+}
+
+
+RTDECL(int) RTAsn1DynType_SetToObjId(PRTASN1DYNTYPE pThis, PCRTASN1OBJID pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RTAsn1DynType_Delete(pThis);
+ pThis->enmType = RTASN1TYPE_OBJID;
+ return RTAsn1ObjId_Clone(&pThis->u.ObjId, pSrc, pAllocator);
+}
+
+
+RTDECL(int) RTAsn1DynType_Enum(PRTASN1DYNTYPE pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ if ( pThis
+ && RTAsn1DynType_IsPresent(pThis))
+ {
+ if ( pThis->u.Core.pOps
+ && pThis->u.Core.pOps->pfnEnum)
+ return pThis->u.Core.pOps->pfnEnum(&pThis->u.Core, pfnCallback, uDepth, pvUser);
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1DynType_Compare(PCRTASN1DYNTYPE pLeft, PCRTASN1DYNTYPE pRight)
+{
+ if (RTAsn1DynType_IsPresent(pLeft) && RTAsn1DynType_IsPresent(pRight))
+ {
+ if (pLeft->enmType != pRight->enmType)
+ return pLeft->enmType < pRight->enmType ? -1 : 1;
+
+ switch (pLeft->enmType)
+ {
+ case RTASN1TYPE_CORE: return RTAsn1Core_Compare(&pLeft->u.Core, &pRight->u.Core); break;
+ case RTASN1TYPE_NULL: return RTAsn1Null_Compare(&pLeft->u.Asn1Null, &pRight->u.Asn1Null);
+ case RTASN1TYPE_INTEGER: return RTAsn1Integer_Compare(&pLeft->u.Integer, &pRight->u.Integer);
+ case RTASN1TYPE_BOOLEAN: return RTAsn1Boolean_Compare(&pLeft->u.Boolean, &pRight->u.Boolean);
+ case RTASN1TYPE_STRING: return RTAsn1String_Compare(&pLeft->u.String, &pRight->u.String);
+ case RTASN1TYPE_OCTET_STRING: return RTAsn1OctetString_Compare(&pLeft->u.OctetString, &pRight->u.OctetString);
+ case RTASN1TYPE_BIT_STRING: return RTAsn1BitString_Compare(&pLeft->u.BitString, &pRight->u.BitString);
+ case RTASN1TYPE_TIME: return RTAsn1Time_Compare(&pLeft->u.Time, &pRight->u.Time);
+ case RTASN1TYPE_OBJID: return RTAsn1ObjId_Compare(&pLeft->u.ObjId, &pRight->u.ObjId);
+ default: AssertFailedReturn(-1);
+ }
+ }
+ else
+ return (int)RTAsn1DynType_IsPresent(pLeft) - (int)RTAsn1DynType_IsPresent(pRight);
+}
+
+
+RTDECL(int) RTAsn1DynType_CheckSanity(PCRTASN1DYNTYPE pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ if (RT_UNLIKELY(!RTAsn1DynType_IsPresent(pThis)))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (DYNTYPE).", pszErrorTag);
+
+ int rc;
+ switch (pThis->enmType)
+ {
+ case RTASN1TYPE_CORE: rc = RTAsn1Core_CheckSanity(&pThis->u.Core, fFlags, pErrInfo, pszErrorTag); break;
+ case RTASN1TYPE_NULL: rc = RTAsn1Null_CheckSanity(&pThis->u.Asn1Null, fFlags, pErrInfo, pszErrorTag); break;
+ case RTASN1TYPE_INTEGER: rc = RTAsn1Integer_CheckSanity(&pThis->u.Integer, fFlags, pErrInfo, pszErrorTag); break;
+ case RTASN1TYPE_BOOLEAN: rc = RTAsn1Boolean_CheckSanity(&pThis->u.Boolean, fFlags, pErrInfo, pszErrorTag); break;
+ case RTASN1TYPE_STRING: rc = RTAsn1String_CheckSanity(&pThis->u.String, fFlags, pErrInfo, pszErrorTag); break;
+ case RTASN1TYPE_OCTET_STRING: rc = RTAsn1OctetString_CheckSanity(&pThis->u.OctetString, fFlags, pErrInfo, pszErrorTag); break;
+ case RTASN1TYPE_BIT_STRING: rc = RTAsn1BitString_CheckSanity(&pThis->u.BitString, fFlags, pErrInfo, pszErrorTag); break;
+ case RTASN1TYPE_TIME: rc = RTAsn1Time_CheckSanity(&pThis->u.Time, fFlags, pErrInfo, pszErrorTag); break;
+#if 0
+ case RTASN1TYPE_SEQUENCE_CORE: rc = VINF_SUCCESS; //rc = RTAsn1SequenceCore_CheckSanity(&pThis->u.SeqCore, fFlags, pErrInfo, pszErrorTag); break;
+ case RTASN1TYPE_SET_CORE: rc = VINF_SUCCESS; //rc = RTAsn1SetCore_CheckSanity(&pThis->u.SetCore, fFlags, pErrInfo, pszErrorTag); break;
+#endif
+ case RTASN1TYPE_OBJID: rc = RTAsn1ObjId_CheckSanity(&pThis->u.ObjId, fFlags, pErrInfo, pszErrorTag); break;
+ default:
+ AssertFailedReturn(VERR_ASN1_INTERNAL_ERROR_2);
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-integer-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-integer-decode.cpp
new file mode 100644
index 00000000..f753d453
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-integer-decode.cpp
@@ -0,0 +1,97 @@
+/* $Id: asn1-ut-integer-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, INTEGER Type, Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+RTDECL(int) RTAsn1Integer_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1INTEGER pThis, const char *pszErrorTag)
+{
+ pThis->uValue.u = 0;
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_INTEGER,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ fFlags, pszErrorTag, "INTEGER");
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->Asn1Core.cb > 0)
+ {
+ uint32_t offLast = pThis->Asn1Core.cb - 1;
+ switch (pThis->Asn1Core.cb)
+ {
+ default:
+ case 8: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 7] << 56; RT_FALL_THRU();
+ case 7: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 6] << 48; RT_FALL_THRU();
+ case 6: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 5] << 40; RT_FALL_THRU();
+ case 5: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 4] << 32; RT_FALL_THRU();
+ case 4: pThis->uValue.u |= (uint32_t)pThis->Asn1Core.uData.pu8[offLast - 3] << 24; RT_FALL_THRU();
+ case 3: pThis->uValue.u |= (uint32_t)pThis->Asn1Core.uData.pu8[offLast - 2] << 16; RT_FALL_THRU();
+ case 2: pThis->uValue.u |= (uint16_t)pThis->Asn1Core.uData.pu8[offLast - 1] << 8; RT_FALL_THRU();
+ case 1: pThis->uValue.u |= pThis->Asn1Core.uData.pu8[offLast];
+ }
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ pThis->Asn1Core.pOps = &g_RTAsn1Integer_Vtable;
+ return VINF_SUCCESS;
+ }
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_INTEGER_ENCODING,
+ "%s: Invalid integer length, exepcted more than 0: %#x",
+ pszErrorTag, pThis->Asn1Core.cb);
+ }
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-integer-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-integer-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-integer-template.h
new file mode 100644
index 00000000..1dfbfa4e
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-integer-template.h
@@ -0,0 +1,55 @@
+/* $Id: asn1-ut-integer-template.h $ */
+/** @file
+ * IPRT - ASN.1, INTEGER Type, Collection Type Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+#define RTASN1TMPL_TYPE RTASN1SEQOFINTEGERS
+#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfIntegers
+#define RTASN1TMPL_INT_NAME rtAsn1SeqOfIntegers
+RTASN1TMPL_SEQ_OF(RTASN1INTEGER, RTAsn1Integer);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+#define RTASN1TMPL_TYPE RTASN1SETOFINTEGERS
+#define RTASN1TMPL_EXT_NAME RTAsn1SetOfIntegers
+#define RTASN1TMPL_INT_NAME rtAsn1SetOfIntegers
+RTASN1TMPL_SET_OF(RTASN1INTEGER, RTAsn1Integer);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-integer.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-integer.cpp
new file mode 100644
index 00000000..79df5c49
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-integer.cpp
@@ -0,0 +1,508 @@
+/* $Id: asn1-ut-integer.cpp $ */
+/** @file
+ * IPRT - ASN.1, INTEGER Type.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/bignum.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Fixed on-byte constants for small numbers.
+ * Good for structure version values and such. */
+static const uint8_t g_abSmall[] =
+{
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+};
+
+
+
+/*
+ * ASN.1 INTEGER - Special Methods.
+ */
+
+
+/**
+ * Updates the native value we keep in RTASN1INTEGER::uValue.
+ *
+ * @param pThis The integer.
+ */
+static void rtAsn1Integer_UpdateNativeValue(PRTASN1INTEGER pThis)
+{
+ uint32_t offLast = pThis->Asn1Core.cb - 1;
+ switch (pThis->Asn1Core.cb)
+ {
+ default: AssertBreak(pThis->Asn1Core.cb > 8); /* paranoia */ RT_FALL_THRU();
+ case 8: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 7] << 56; RT_FALL_THRU();
+ case 7: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 6] << 48; RT_FALL_THRU();
+ case 6: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 5] << 40; RT_FALL_THRU();
+ case 5: pThis->uValue.u |= (uint64_t)pThis->Asn1Core.uData.pu8[offLast - 4] << 32; RT_FALL_THRU();
+ case 4: pThis->uValue.u |= (uint32_t)pThis->Asn1Core.uData.pu8[offLast - 3] << 24; RT_FALL_THRU();
+ case 3: pThis->uValue.u |= (uint32_t)pThis->Asn1Core.uData.pu8[offLast - 2] << 16; RT_FALL_THRU();
+ case 2: pThis->uValue.u |= (uint16_t)pThis->Asn1Core.uData.pu8[offLast - 1] << 8; RT_FALL_THRU();
+ case 1: pThis->uValue.u |= pThis->Asn1Core.uData.pu8[offLast];
+ }
+}
+
+
+RTDECL(int) RTAsn1Integer_InitU64(PRTASN1INTEGER pThis, uint64_t uValue, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ /*
+ * Initialize the core and the native value.
+ */
+ RTAsn1Core_InitEx(&pThis->Asn1Core,
+ ASN1_TAG_INTEGER,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1Integer_Vtable,
+ RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+ pThis->uValue.u = uValue;
+
+ /*
+ * Use one of the constants if possible.
+ */
+ if (uValue < RT_ELEMENTS(g_abSmall))
+ {
+ pThis->Asn1Core.cb = 1;
+ pThis->Asn1Core.uData.pv = (void *)&g_abSmall[0];
+ }
+ else
+ {
+ /*
+ * Need to turn uValue into a big endian number without any
+ * unnecessary leading zero bytes.
+ */
+ /* Figure the size. */
+ uint32_t cb = 0;
+ if (uValue <= UINT32_MAX)
+ {
+ if (uValue <= UINT16_MAX)
+ {
+ if (uValue <= UINT8_MAX)
+ cb = 1;
+ else
+ cb = 2;
+ }
+ else
+ {
+ if (uValue <= UINT32_C(0xffffff))
+ cb = 3;
+ else
+ cb = 4;
+ }
+ }
+ else
+ {
+ if (uValue <= UINT64_C(0x0000FfffFfffFfff))
+ {
+ if (uValue <= UINT64_C(0x000000ffFfffFfff))
+ cb = 5;
+ else
+ cb = 6;
+ }
+ else
+ {
+ if (uValue <= UINT64_C(0x00ffFfffFfffFfff))
+ cb = 7;
+ else
+ cb = 8;
+ }
+ }
+
+ /* Allocate space. */
+ int rc = RTAsn1ContentAllocZ(&pThis->Asn1Core, cb, pAllocator);
+ if (RT_FAILURE(rc))
+ {
+ RT_ZERO(*pThis);
+ return rc;
+ }
+
+ /* Serialize the number in MSB order. */
+ uint8_t *pb = (uint8_t *)pThis->Asn1Core.uData.pu8;
+ while (cb-- > 0)
+ {
+ pb[cb] = (uint8_t)uValue;
+ uValue >>= 8;
+ }
+ Assert(uValue == 0);
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Integer_InitDefault(PRTASN1INTEGER pThis, uint64_t uValue, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ int rc = RTAsn1Integer_InitU64(pThis, uValue, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Asn1Core.fFlags &= ~RTASN1CORE_F_PRESENT;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_DEFAULT;
+ }
+ return rc;
+}
+
+
+RTDECL(int32_t) RTAsn1Integer_UnsignedLastBit(PCRTASN1INTEGER pThis)
+{
+ AssertReturn(pThis->Asn1Core.fFlags, -1);
+ uint8_t const *pb = pThis->Asn1Core.uData.pu8;
+ AssertReturn(pb, -1);
+ uint32_t cb = pThis->Asn1Core.cb;
+ AssertReturn(pThis->Asn1Core.cb < (uint32_t)INT32_MAX / 8, -1);
+
+ while (cb-- > 0)
+ {
+ uint8_t b = *pb++;
+ if (b)
+ {
+ int32_t iRet = cb * 8;
+ if (b & 0x80) iRet += 7;
+ else if (b & 0x40) iRet += 6;
+ else if (b & 0x20) iRet += 5;
+ else if (b & 0x10) iRet += 4;
+ else if (b & 0x08) iRet += 3;
+ else if (b & 0x04) iRet += 2;
+ else if (b & 0x02) iRet += 1;
+ else Assert(b == 0x01);
+ return iRet;
+ }
+ }
+ return -1;
+}
+
+
+RTDECL(int) RTAsn1Integer_UnsignedCompare(PCRTASN1INTEGER pLeft, PCRTASN1INTEGER pRight)
+{
+ Assert(pLeft && (!RTAsn1Integer_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1Integer_Vtable));
+ Assert(pRight && (!RTAsn1Integer_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1Integer_Vtable));
+
+ int iDiff;
+ if (RTAsn1Integer_IsPresent(pLeft))
+ {
+ if (RTAsn1Integer_IsPresent(pRight))
+ {
+ if ( pLeft->Asn1Core.cb > 8
+ || pRight->Asn1Core.cb > 8)
+ {
+ uint32_t iLeft = RTAsn1Integer_UnsignedLastBit(pLeft);
+ uint32_t iRight = RTAsn1Integer_UnsignedLastBit(pRight);
+ if (iLeft != iRight)
+ return iLeft < iRight ? -1 : 1;
+ if ((int32_t)iLeft < 0)
+ return 0; /* Both are all zeros. */
+
+ uint32_t i = iLeft / 8;
+ if (i > 8)
+ {
+ uint8_t const *pbLeft = &pLeft->Asn1Core.uData.pu8[pLeft->Asn1Core.cb - i - 1];
+ uint8_t const *pbRight = &pRight->Asn1Core.uData.pu8[pRight->Asn1Core.cb - i - 1];
+ for (;;)
+ {
+ if (*pbLeft != *pbRight)
+ return *pbLeft < *pbRight ? -1 : 1;
+ if (--i <= 8)
+ break;
+ pbLeft++;
+ pbRight++;
+ }
+ }
+ }
+
+ if (pLeft->uValue.u == pRight->uValue.u)
+ iDiff = 0;
+ else
+ iDiff = pLeft->uValue.u < pRight->uValue.u ? -1 : 1;
+ }
+ else
+ iDiff = -1;
+ }
+ else
+ iDiff = 0 - (int)RTAsn1Integer_IsPresent(pRight);
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1Integer_UnsignedCompareWithU64(PCRTASN1INTEGER pThis, uint64_t u64Const)
+{
+ int iDiff;
+ if (RTAsn1Integer_IsPresent(pThis))
+ {
+ if (pThis->Asn1Core.cb > 8)
+ {
+ int32_t iLast = RTAsn1Integer_UnsignedLastBit(pThis);
+ if (iLast >= 64)
+ return 1;
+ }
+
+ if (pThis->uValue.u == u64Const)
+ iDiff = 0;
+ else
+ iDiff = pThis->uValue.u < u64Const ? -1 : 1;
+ }
+ else
+ iDiff = 1;
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1Integer_UnsignedCompareWithU32(PCRTASN1INTEGER pThis, uint32_t u32Const)
+{
+ int iDiff;
+ if (RTAsn1Integer_IsPresent(pThis))
+ {
+ if (pThis->Asn1Core.cb > 8)
+ {
+ int32_t iLast = RTAsn1Integer_UnsignedLastBit(pThis);
+ if (iLast >= 32)
+ return 1;
+ }
+
+ if (pThis->uValue.u == u32Const)
+ iDiff = 0;
+ else
+ iDiff = pThis->uValue.u < u32Const ? -1 : 1;
+ }
+ else
+ iDiff = 1;
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1Integer_ToBigNum(PCRTASN1INTEGER pThis, PRTBIGNUM pBigNum, uint32_t fBigNumInit)
+{
+ AssertReturn(!(fBigNumInit & ~( RTBIGNUMINIT_F_SENSITIVE | RTBIGNUMINIT_F_UNSIGNED | RTBIGNUMINIT_F_SIGNED
+ | RTBIGNUMINIT_F_ENDIAN_LITTLE | RTBIGNUMINIT_F_ENDIAN_BIG)),
+ VERR_INVALID_PARAMETER);
+ AssertReturn(RTAsn1Integer_IsPresent(pThis), VERR_INVALID_PARAMETER);
+
+ if (!(fBigNumInit & (RTBIGNUMINIT_F_UNSIGNED | RTBIGNUMINIT_F_SIGNED)))
+ fBigNumInit |= RTBIGNUMINIT_F_SIGNED;
+
+ if (!(fBigNumInit & (RTBIGNUMINIT_F_ENDIAN_BIG | RTBIGNUMINIT_F_ENDIAN_LITTLE)))
+ fBigNumInit |= RTBIGNUMINIT_F_ENDIAN_BIG;
+
+ return RTBigNumInit(pBigNum, fBigNumInit, pThis->Asn1Core.uData.pv, pThis->Asn1Core.cb);
+}
+
+
+RTDECL(int) RTAsn1Integer_FromBigNum(PRTASN1INTEGER pThis, PCRTBIGNUM pBigNum, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pThis); AssertPtr(pBigNum); AssertPtr(pAllocator);
+
+ /* Be nice and auto init the object. */
+ if (!RTAsn1Integer_IsPresent(pThis))
+ RTAsn1Integer_Init(pThis, NULL);
+
+ uint32_t cb = RTBigNumByteWidth(pBigNum); Assert(cb > 0);
+ int rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, cb, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cb == pThis->Asn1Core.cb);
+ rc = RTBigNumToBytesBigEndian(pBigNum, (void *)pThis->Asn1Core.uData.pv, cb);
+ if (RT_SUCCESS(rc))
+ rtAsn1Integer_UpdateNativeValue(pThis);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1Integer_ToString(PCRTASN1INTEGER pThis, char *pszBuf, size_t cbBuf, uint32_t fFlags, size_t *pcbActual)
+{
+ AssertReturn(RTAsn1Integer_IsPresent(pThis), VERR_INVALID_PARAMETER);
+ AssertReturn(fFlags == 0, VERR_INVALID_FLAGS);
+
+ /*
+ * We only do hex conversions via this API.
+ * Currently we consider all numbers to be unsigned.
+ */
+ /** @todo Signed ASN.1 INTEGER. */
+ int rc;
+ size_t cbActual;
+ if (pThis->Asn1Core.cb <= 8)
+ {
+ cbActual = 2 + pThis->Asn1Core.cb*2 + 1;
+ if (cbActual <= cbBuf)
+ {
+ ssize_t cchFormat = RTStrFormatU64(pszBuf, cbBuf, pThis->uValue.u, 16, (int)cbActual - 1 /*cchWidth*/, 0,
+ RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD);
+ rc = VINF_SUCCESS;
+ AssertStmt(cchFormat == (ssize_t)cbActual - 1, rc = VERR_INTERNAL_ERROR_3);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ {
+ cbActual = pThis->Asn1Core.cb * 3 - 1 /* save one separator */ + 1 /* terminator */;
+ if (cbActual <= cbBuf)
+ {
+ rc = RTStrPrintHexBytes(pszBuf, cbBuf, pThis->Asn1Core.uData.pv, pThis->Asn1Core.cb, RTSTRPRINTHEXBYTES_F_SEP_SPACE);
+ Assert(rc == VINF_SUCCESS);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ if (pcbActual)
+ *pcbActual = cbActual;
+ return rc;
+}
+
+
+/*
+ * ASN.1 INTEGER - Standard Methods.
+ */
+
+RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Integer_Vtable =
+{
+ "RTAsn1Integer",
+ sizeof(RTASN1INTEGER),
+ ASN1_TAG_INTEGER,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ 0,
+ (PFNRTASN1COREVTDTOR)RTAsn1Integer_Delete,
+ NULL,
+ (PFNRTASN1COREVTCLONE)RTAsn1Integer_Clone,
+ (PFNRTASN1COREVTCOMPARE)RTAsn1Integer_Compare,
+ (PFNRTASN1COREVTCHECKSANITY)RTAsn1Integer_CheckSanity,
+ NULL,
+ NULL
+};
+
+
+RTDECL(int) RTAsn1Integer_Init(PRTASN1INTEGER pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_NOREF_PV(pAllocator);
+ RTAsn1Core_InitEx(&pThis->Asn1Core,
+ ASN1_TAG_INTEGER,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1Integer_Vtable,
+ RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+ pThis->uValue.u = 1;
+ pThis->Asn1Core.cb = 1;
+ pThis->Asn1Core.uData.pv = (void *)&g_abSmall[0];
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Integer_Clone(PRTASN1INTEGER pThis, PCRTASN1INTEGER pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator);
+ RT_ZERO(*pThis);
+ if (RTAsn1Integer_IsPresent(pSrc))
+ {
+ AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1Integer_Vtable, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ if ( pSrc->Asn1Core.cb != 1
+ || pSrc->uValue.u >= RT_ELEMENTS(g_abSmall))
+ {
+ /* Value is too large, copy it. */
+ rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ /* Use one of the const values. */
+ rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+ if (RT_FAILURE(rc))
+ return rc;
+ Assert(g_abSmall[pSrc->uValue.u] == pSrc->uValue.u);
+ pThis->Asn1Core.uData.pv = (void *)&g_abSmall[pSrc->uValue.u];
+ }
+ pThis->uValue.u = pSrc->uValue.u;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1Integer_Delete(PRTASN1INTEGER pThis)
+{
+ if ( pThis
+ && RTAsn1Integer_IsPresent(pThis))
+ {
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1Integer_Vtable);
+
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1Integer_Enum(PRTASN1INTEGER pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser);
+ Assert(pThis && (!RTAsn1Integer_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1Integer_Vtable));
+
+ /* No children to enumerate. */
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Integer_Compare(PCRTASN1INTEGER pLeft, PCRTASN1INTEGER pRight)
+{
+ return RTAsn1Integer_UnsignedCompare(pLeft, pRight);
+}
+
+
+RTDECL(int) RTAsn1Integer_CheckSanity(PCRTASN1INTEGER pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+ if (RT_UNLIKELY(!RTAsn1Integer_IsPresent(pThis)))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (INTEGER).", pszErrorTag);
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-integer-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-core.h>
+#include <iprt/asn1-generator-init.h>
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-null-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-null-decode.cpp
new file mode 100644
index 00000000..5c43df1b
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-null-decode.cpp
@@ -0,0 +1,73 @@
+/* $Id: asn1-ut-null-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, NULL type, Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+RTDECL(int) RTAsn1Null_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1NULL pThis, const char *pszErrorTag)
+{
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_NULL,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, fFlags, pszErrorTag, "NULL");
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->Asn1Core.cb == 0)
+ {
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ pThis->Asn1Core.pOps = &g_RTAsn1Null_Vtable;
+ return VINF_SUCCESS;
+ }
+
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_NULL_ENCODING,
+ "%s: Expected NULL object to have zero length: %#x", pszErrorTag, pThis->Asn1Core.cb);
+ }
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-null.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-null.cpp
new file mode 100644
index 00000000..9828dc51
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-null.cpp
@@ -0,0 +1,141 @@
+/* $Id: asn1-ut-null.cpp $ */
+/** @file
+ * IPRT - ASN.1, NULL type.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*
+ * ASN.1 NULL - Special Methods.
+ */
+
+
+/*
+ * ASN.1 NULL - Standard Methods.
+ */
+
+
+RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Null_Vtable =
+{
+ "RTAsn1Null",
+ sizeof(RTASN1NULL),
+ ASN1_TAG_NULL,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ 0,
+ (PFNRTASN1COREVTDTOR)RTAsn1Null_Delete,
+ (PFNRTASN1COREVTENUM)RTAsn1Null_Enum,
+ (PFNRTASN1COREVTCLONE)RTAsn1Null_Clone,
+ (PFNRTASN1COREVTCOMPARE)RTAsn1Null_Compare,
+ (PFNRTASN1COREVTCHECKSANITY)RTAsn1Null_CheckSanity,
+ NULL,
+ NULL
+};
+
+
+RTDECL(int) RTAsn1Null_Init(PRTASN1NULL pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_NOREF_PV(pAllocator);
+ return RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_NULL, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1Null_Vtable, RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+}
+
+
+RTDECL(int) RTAsn1Null_Clone(PRTASN1NULL pThis, PCRTASN1NULL pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator); RT_NOREF_PV(pAllocator);
+ RT_ZERO(*pThis);
+ if (RTAsn1Null_IsPresent(pSrc))
+ {
+ AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1Null_Vtable, VERR_INTERNAL_ERROR_3);
+ AssertReturn(pSrc->Asn1Core.cb == 0, VERR_INTERNAL_ERROR_4);
+
+ int rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1Null_Delete(PRTASN1NULL pThis)
+{
+ if ( pThis
+ && RTAsn1Null_IsPresent(pThis))
+ {
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1Null_Vtable);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1Null_Enum(PRTASN1NULL pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser);
+ Assert(pThis && (!RTAsn1Null_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1Null_Vtable));
+
+ /* No children to enumerate. */
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Null_Compare(PCRTASN1NULL pLeft, PCRTASN1NULL pRight)
+{
+ Assert(pLeft && (!RTAsn1Null_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1Null_Vtable));
+ Assert(pRight && (!RTAsn1Null_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1Null_Vtable));
+
+ return (int)RTAsn1Null_IsPresent(pLeft) - (int)RTAsn1Null_IsPresent(pRight);
+}
+
+
+RTDECL(int) RTAsn1Null_CheckSanity(PCRTASN1NULL pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+ if (RT_UNLIKELY(!RTAsn1Null_IsPresent(pThis)))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (NULL).", pszErrorTag);
+ return VINF_SUCCESS;
+}
+
+/* No NULL object collections. */
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-objid-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-objid-decode.cpp
new file mode 100644
index 00000000..6d0e66a0
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-objid-decode.cpp
@@ -0,0 +1,377 @@
+/* $Id: asn1-ut-objid-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, OBJECT IDENTIFIER Type, Decoder.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/alloca.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static char const g_achDigits[11] = "0123456789";
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+DECLHIDDEN(int) rtAsn1ObjId_InternalFormatComponent(uint32_t uValue, char **ppszObjId, size_t *pcbObjId); /* asn1-ut-objid.cpp */
+
+
+/**
+ * Internal worker for RTAsn1ObjId_DecodeAsn1 that formats a component, with a
+ * leading dot.
+ *
+ * @returns VBox status code (caller complains on failure).
+ * @param uValue The component ID value.
+ * @param ppszObjId Pointer to the output buffer pointer.
+ * @param pcbObjId Pointer to the remaining size of the output buffer.
+ */
+DECLHIDDEN(int) rtAsn1ObjId_InternalFormatComponent(uint32_t uValue, char **ppszObjId, size_t *pcbObjId)
+{
+ /*
+ * Format the number backwards.
+ */
+ char szTmp[32];
+ char *psz = &szTmp[sizeof(szTmp) - 1];
+ *psz = '\0';
+
+ do
+ {
+ *--psz = g_achDigits[uValue % 10];
+ uValue /= 10;
+ } while (uValue > 0);
+
+ /*
+ * Do we have enough space?
+ * We add a dot and save space for the terminator.
+ */
+ size_t cchNeeded = &szTmp[sizeof(szTmp) - 1] - psz;
+ if (1 + cchNeeded < *pcbObjId)
+ {
+ *pcbObjId -= cchNeeded + 1;
+ char *pszObjId = *ppszObjId;
+ *ppszObjId = pszObjId + cchNeeded + 1;
+
+ *pszObjId = '.';
+ memcpy(pszObjId + 1, psz, cchNeeded);
+ return VINF_SUCCESS;
+ }
+
+ AssertFailed();
+ return VERR_ASN1_OBJID_TOO_LONG_STRING_FORM;
+}
+
+
+/**
+ * Reads one object ID component, returning it's value and encoded length.
+ *
+ * @returns The encoded length (positive) on success, negative IPRT status code
+ * on failure.
+ * @param pbContent The start of the component to parse.
+ * @param cbContent The number of content bytes left.
+ * @param puValue Where to return the value.
+ */
+static int rtAsn1ObjId_ReadComponent(uint8_t const *pbContent, uint32_t cbContent, uint32_t *puValue)
+{
+ if (cbContent >= 1)
+ {
+ /* The first byte. */
+ uint8_t b = *pbContent;
+ if (!(b & 0x80))
+ {
+ *puValue = b;
+ return 1;
+ }
+
+ /* Encoded as more than one byte. Make sure that it's efficently
+ encoded as 8.19.2 indicates it must. */
+ if (b != 0x80)
+ {
+ uint32_t off = 1;
+ uint32_t uValue = b & 0x7f;
+ while (off < cbContent)
+ {
+ b = pbContent[off++];
+ uValue <<= 7;
+ uValue |= b & 0x7f;
+ if (!(b & 0x80))
+ {
+ *puValue = uValue;
+ return (int)off;
+ }
+
+ if (RT_UNLIKELY(uValue & UINT32_C(0x0e000000)))
+ return VERR_ASN1_OBJID_COMPONENT_TOO_BIG;
+ }
+ }
+ return VERR_ASN1_INVALID_OBJID_ENCODING;
+ }
+ return VERR_NO_DATA;
+}
+
+
+/**
+ * This function parses the binary content of an OBJECT IDENTIFIER, check the
+ * encoding as well as calculating the storage requirements.
+ *
+ * @returns IPRT status code
+ * @param pbContent Pointer to the content.
+ * @param cbContent The length of the content.
+ * @param pCursor The cursor (for error reporting).
+ * @param pszErrorTag The error tag.
+ * @param pcComponents Where to return the component count.
+ * @param pcchObjId Where to return the length of the dotted string
+ * representation.
+ */
+static int rtAsn1ObjId_PreParse(uint8_t const *pbContent, uint32_t cbContent, PRTASN1CURSOR pCursor, const char *pszErrorTag,
+ uint8_t *pcComponents, uint8_t *pcchObjId)
+{
+ int rc;
+ if (cbContent >= 1 && cbContent < _1K)
+ {
+ /*
+ * Decode the first two numbers. Monkey business: X*40 + Y
+ * Where X is the first number, X in {0,1,2}, and Y is the second
+ * one. The range of Y is {0,...,39} for X in {0,1}, but has a
+ * free range for X = 2.
+ */
+ uint32_t cComponents = 1;
+ uint32_t uValue;
+ rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue);
+ if (rc > 0)
+ {
+ uint32_t cchObjId = 1;
+ uValue = uValue < 2*40 ? uValue % 40 : uValue - 2*40; /* Y */
+ do
+ {
+ cComponents++;
+
+ /* Figure the encoded string length, binary search fashion. */
+ if (uValue < 10000)
+ {
+ if (uValue < 100)
+ {
+ if (uValue < 10)
+ cchObjId += 1 + 1;
+ else
+ cchObjId += 1 + 2;
+ }
+ else
+ {
+ if (uValue < 1000)
+ cchObjId += 1 + 3;
+ else
+ cchObjId += 1 + 4;
+ }
+ }
+ else
+ {
+ if (uValue < 1000000)
+ {
+ if (uValue < 100000)
+ cchObjId += 1 + 5;
+ else
+ cchObjId += 1 + 6;
+ }
+ else
+ {
+ if (uValue < 10000000)
+ cchObjId += 1 + 7;
+ else if (uValue < 100000000)
+ cchObjId += 1 + 8;
+ else
+ cchObjId += 1 + 9;
+ }
+ }
+
+ /* advance. */
+ pbContent += rc;
+ cbContent -= rc;
+ if (!cbContent)
+ {
+ if (cComponents < 128)
+ {
+ if (cchObjId < RT_SIZEOFMEMB(RTASN1OBJID, szObjId))
+ {
+ *pcComponents = cComponents;
+ *pcchObjId = cchObjId;
+ return VINF_SUCCESS;
+ }
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_OBJID_TOO_LONG_STRING_FORM,
+ "%s: Object ID has a too long string form: %#x (max %#x)",
+ pszErrorTag, cchObjId, RT_SIZEOFMEMB(RTASN1OBJID, szObjId));
+ }
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_OBJID_TOO_MANY_COMPONENTS,
+ "%s: Object ID has too many components: %#x (max 127)", pszErrorTag, cComponents);
+ }
+
+ /* next */
+ rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue);
+ } while (rc > 0);
+ }
+ rc = RTAsn1CursorSetInfo(pCursor, rc, "%s: Bad object ID component #%u encoding: %.*Rhxs",
+ pszErrorTag, cComponents, cbContent, pbContent);
+ }
+ else if (cbContent)
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_OBJID_ENCODING, "%s: Object ID content is loo long: %#x",
+ pszErrorTag, cbContent);
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_OBJID_ENCODING, "%s: Zero length object ID content", pszErrorTag);
+ return rc;
+}
+
+
+
+RTDECL(int) RTAsn1ObjId_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1OBJID pThis, const char *pszErrorTag)
+{
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_OID,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE, fFlags, pszErrorTag, "OID");
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Validate and count things first.
+ */
+ uint8_t cComponents = 0; /* gcc maybe-crap */
+ uint8_t cchObjId = 0; /* ditto */
+ rc = rtAsn1ObjId_PreParse(pCursor->pbCur, pThis->Asn1Core.cb, pCursor, pszErrorTag, &cComponents, &cchObjId);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate memory for the components array, either out of the
+ * string buffer or off the heap.
+ */
+ pThis->cComponents = cComponents;
+ RTAsn1CursorInitAllocation(pCursor, &pThis->Allocation);
+#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */
+ if (cComponents * sizeof(uint32_t) <= sizeof(pThis->szObjId) - cchObjId - 1)
+ pThis->pauComponents = (uint32_t *)&pThis->szObjId[sizeof(pThis->szObjId) - cComponents * sizeof(uint32_t)];
+ else
+#endif
+ rc = RTAsn1MemAllocZ(&pThis->Allocation, (void **)&pThis->pauComponents,
+ cComponents * sizeof(pThis->pauComponents[0]));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t *pauComponents = (uint32_t *)pThis->pauComponents;
+
+ /*
+ * Deal with the two first components first since they are
+ * encoded in a weird way to save a byte.
+ */
+ uint8_t const *pbContent = pCursor->pbCur;
+ uint32_t cbContent = pThis->Asn1Core.cb;
+ uint32_t uValue;
+ rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ pbContent += rc;
+ cbContent -= rc;
+
+ if (uValue < 80)
+ {
+ pauComponents[0] = uValue / 40;
+ pauComponents[1] = uValue % 40;
+ }
+ else
+ {
+ pauComponents[0] = 2;
+ pauComponents[1] = uValue - 2*40;
+ }
+
+ char *pszObjId = &pThis->szObjId[0];
+ *pszObjId++ = g_achDigits[pauComponents[0]];
+ size_t cbObjIdLeft = cchObjId + 1 - 1;
+
+ rc = rtAsn1ObjId_InternalFormatComponent(pauComponents[1], &pszObjId, &cbObjIdLeft); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * The other components are encoded in less complicated manner.
+ */
+ for (uint32_t i = 2; i < cComponents; i++)
+ {
+ rc = rtAsn1ObjId_ReadComponent(pbContent, cbContent, &uValue);
+ AssertRCBreak(rc);
+ pbContent += rc;
+ cbContent -= rc;
+ pauComponents[i] = uValue;
+ rc = rtAsn1ObjId_InternalFormatComponent(uValue, &pszObjId, &cbObjIdLeft);
+ AssertRCBreak(rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbObjIdLeft == 1);
+ *pszObjId = '\0';
+
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ pThis->Asn1Core.pOps = &g_RTAsn1ObjId_Vtable;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ RTAsn1MemFree(&pThis->Allocation, (void *)pThis->pauComponents);
+ pThis->pauComponents = NULL;
+ }
+ }
+ }
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-objid-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-objid-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-objid-template.h
new file mode 100644
index 00000000..102aae15
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-objid-template.h
@@ -0,0 +1,64 @@
+/* $Id: asn1-ut-objid-template.h $ */
+/** @file
+ * IPRT - ASN.1, OBJECT IDENTIFIER Type, Collection Type Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+#define RTASN1TMPL_TYPE RTASN1SEQOFOBJIDS
+#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfObjIds
+#define RTASN1TMPL_INT_NAME rtAsn1SeqOfObjIds
+RTASN1TMPL_SEQ_OF(RTASN1OBJID, RTAsn1ObjId);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+#define RTASN1TMPL_TYPE RTASN1SETOFOBJIDS
+#define RTASN1TMPL_EXT_NAME RTAsn1SetOfObjIds
+#define RTASN1TMPL_INT_NAME rtAsn1SetOfObjIds
+RTASN1TMPL_SET_OF(RTASN1OBJID, RTAsn1ObjId);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+#define RTASN1TMPL_TYPE RTASN1SETOFOBJIDSEQS
+#define RTASN1TMPL_EXT_NAME RTAsn1SetOfObjIdSeqs
+#define RTASN1TMPL_INT_NAME rtAsn1SetOfObjIdSeqs
+RTASN1TMPL_SET_OF(RTASN1SEQOFOBJIDS, RTAsn1SeqOfObjIds);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-objid.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-objid.cpp
new file mode 100644
index 00000000..d1212437
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-objid.cpp
@@ -0,0 +1,566 @@
+/* $Id: asn1-ut-objid.cpp $ */
+/** @file
+ * IPRT - ASN.1, OBJECT IDENTIFIER Type.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/alloca.h>
+#include <iprt/bignum.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static char const g_szDefault[] = "2.16.840.1.113894";
+static uint32_t const g_auDefault[] = { 2, 16, 840, 1, 113894 };
+static uint8_t const g_abDefault[] =
+{
+ 2*40 + 16, 0x80 | (840 >> 7), 840 & 0x7f, 1, 0x80 | (113894 >> 14), 0x80 | ((113894 >> 7) & 0x7f), 113894 & 0x7f
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+DECLHIDDEN(int) rtAsn1ObjId_InternalFormatComponent(uint32_t uValue, char **ppszObjId, size_t *pcbObjId); /* asn1-ut-objid.cpp */
+/** @todo check if we really need this. */
+
+
+
+/*
+ * ASN.1 OBJECT IDENTIFIER - Special Methods.
+ */
+
+/**
+ * Encodes the ASN.1 byte sequence for a set of components.
+ *
+ * @returns IPRT status code.
+ * @param cComponents The number of components. Must be at least two.
+ * @param pauComponents The components array.
+ * @param pbEncoded The output buffer.
+ * @param pcbEncoded On input, this holds the size of the output buffer.
+ * On successful return it's the encoded size in bytes.
+ */
+static int rtAsn1ObjId_EncodeComponents(uint32_t cComponents, uint32_t const *pauComponents,
+ uint8_t *pbEncoded, uint32_t *pcbEncoded)
+{
+ uint8_t *pbCur = pbEncoded;
+ uint32_t cbLeft = *pcbEncoded;
+
+ /* The first two componets are encoded together to save a byte, so the loop
+ organization is a little special. */
+ AssertReturn(cComponents >= 2, VERR_ASN1_INTERNAL_ERROR_1);
+ AssertReturn(pauComponents[0] <= 2, VERR_ASN1_INTERNAL_ERROR_1);
+ AssertReturn(pauComponents[1] <= (pauComponents[0] < 2 ? 39 : UINT32_MAX - 80), VERR_ASN1_INTERNAL_ERROR_1);
+ uint32_t i = 1;
+ uint32_t uValue = pauComponents[0] * 40 + pauComponents[1];
+
+ for (;;)
+ {
+ if (uValue < 0x80)
+ {
+ if (RT_UNLIKELY(cbLeft < 1))
+ return VERR_BUFFER_OVERFLOW;
+ cbLeft -= 1;
+ *pbCur++ = (uint8_t)uValue;
+ }
+ else if (uValue < 0x4000)
+ {
+ if (RT_UNLIKELY(cbLeft < 2))
+ return VERR_BUFFER_OVERFLOW;
+ cbLeft -= 2;
+ pbCur[0] = (uValue >> 7) | 0x80;
+ pbCur[1] = uValue & 0x7f;
+ pbCur += 2;
+ }
+ else if (uValue < 0x200000)
+ {
+ if (RT_UNLIKELY(cbLeft < 3))
+ return VERR_BUFFER_OVERFLOW;
+ cbLeft -= 3;
+ pbCur[0] = (uValue >> 14) | 0x80;
+ pbCur[1] = ((uValue >> 7) & 0x7f) | 0x80;
+ pbCur[2] = uValue & 0x7f;
+ pbCur += 3;
+ }
+ else if (uValue < 0x10000000)
+ {
+ if (RT_UNLIKELY(cbLeft < 4))
+ return VERR_BUFFER_OVERFLOW;
+ cbLeft -= 4;
+ pbCur[0] = (uValue >> 21) | 0x80;
+ pbCur[1] = ((uValue >> 14) & 0x7f) | 0x80;
+ pbCur[2] = ((uValue >> 7) & 0x7f) | 0x80;
+ pbCur[3] = uValue & 0x7f;
+ pbCur += 4;
+ }
+ else
+ {
+ if (RT_UNLIKELY(cbLeft < 5))
+ return VERR_BUFFER_OVERFLOW;
+ cbLeft -= 5;
+ pbCur[0] = (uValue >> 28) | 0x80;
+ pbCur[1] = ((uValue >> 21) & 0x7f) | 0x80;
+ pbCur[2] = ((uValue >> 14) & 0x7f) | 0x80;
+ pbCur[3] = ((uValue >> 7) & 0x7f) | 0x80;
+ pbCur[4] = uValue & 0x7f;
+ pbCur += 5;
+ }
+
+ /* Advance / return. */
+ i++;
+ if (i >= cComponents)
+ {
+ *pcbEncoded = (uint32_t)(pbCur - pbEncoded);
+ return VINF_SUCCESS;
+ }
+ uValue = pauComponents[i];
+ }
+}
+
+
+RTDECL(int) RTAsn1ObjId_InitFromString(PRTASN1OBJID pThis, const char *pszObjId, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_ZERO(*pThis);
+
+ /*
+ * Check the string, counting the number of components and checking their validity.
+ */
+ size_t cbObjId = strlen(pszObjId) + 1;
+ AssertReturn(cbObjId < sizeof(pThis->szObjId), VERR_ASN1_OBJID_TOO_LONG_STRING_FORM);
+
+ const char *psz = pszObjId;
+
+ /* Special checking of the first component. It has only three valid values: 0,1,2. */
+ char ch = *psz++;
+ if (RT_UNLIKELY(ch < '0' || ch > '2'))
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+ char const chFirst = ch;
+ ch = *psz++;
+ if (RT_UNLIKELY(ch != '.'))
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+
+ /* The 2nd component. It the first is 0 or 1, it has a max of 39. */
+ uint32_t cComponents = 1;
+ if (chFirst < '2')
+ {
+ ch = *psz++;
+ if (*psz == '.')
+ {
+ if (RT_UNLIKELY(!RT_C_IS_DIGIT(ch)))
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+ }
+ else
+ {
+ if (RT_UNLIKELY(ch < '0' || ch > '3'))
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+ ch = *psz++;
+ if (RT_UNLIKELY(!RT_C_IS_DIGIT(ch)))
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+ if (*psz != '.')
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+ }
+ cComponents++;
+ }
+ else
+ psz--;
+
+ /* Subsequent components have max values of UINT32_MAX - 80. */
+ while ((ch = *psz++) != '\0')
+ {
+ if (RT_UNLIKELY(ch != '.'))
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+ const char *pszStart = psz;
+
+ /* Special treatment of the first digit. Need to make sure it isn't an
+ unnecessary leading 0. */
+ ch = *psz++;
+ if (RT_UNLIKELY(!RT_C_IS_DIGIT(ch)))
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+ if (RT_UNLIKELY(ch == '0' && RT_C_IS_DIGIT(*psz)))
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+
+ /* The rest of the digits. */
+ while ((ch = *psz) != '.' && ch != '\0')
+ {
+ if (RT_UNLIKELY(!RT_C_IS_DIGIT(ch)))
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+ psz++;
+ }
+
+ /* Check the value range. */
+ if (RT_UNLIKELY(psz - pszStart >= 9))
+ if ( psz - pszStart > 9
+ || strncmp(pszStart, "4294967216", 9) >= 0) /* 2^32 - 80 */
+ return VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+
+ cComponents++;
+ }
+
+ if (RT_UNLIKELY(cComponents >= 128))
+ return VERR_ASN1_OBJID_TOO_MANY_COMPONENTS;
+ pThis->cComponents = (uint8_t)cComponents;
+
+ /*
+ * Find space for the component array, either at the unused end of szObjId
+ * or on the heap.
+ */
+ int rc;
+ RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator);
+#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */
+ size_t cbLeft = sizeof(pThis->szObjId) - cbObjId;
+ if (cbLeft >= cComponents * sizeof(uint32_t))
+ {
+ pThis->pauComponents = (uint32_t *)&pThis->szObjId[sizeof(pThis->szObjId) - cComponents * sizeof(uint32_t)];
+ cbLeft -= cComponents * sizeof(uint32_t);
+ rc = VINF_SUCCESS;
+ }
+ else
+#endif
+ rc = RTAsn1MemAllocZ(&pThis->Allocation, (void **)&pThis->pauComponents, cComponents * sizeof(uint32_t));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Fill the elements array.
+ */
+ uint32_t *pauComponents = (uint32_t *)pThis->pauComponents;
+ rc = VINF_SUCCESS;
+ psz = pszObjId;
+ for (uint32_t i = 0; i < cComponents; i++)
+ {
+ uint32_t uValue = 0;
+ rc = RTStrToUInt32Ex(psz, (char **)&psz, 10, &uValue);
+ if (rc == VWRN_TRAILING_CHARS)
+ {
+ pauComponents[i] = uValue;
+ AssertBreakStmt(*psz == '.', rc = VERR_TRAILING_CHARS);
+ psz++;
+ }
+ else if (rc == VINF_SUCCESS)
+ {
+ pauComponents[i] = uValue;
+ Assert(*psz == '\0');
+ }
+ else if (RT_FAILURE(rc))
+ break;
+ else
+ {
+ rc = -rc;
+ break;
+ }
+ }
+ if (rc == VINF_SUCCESS && *psz == '\0')
+ {
+ /*
+ * Initialize the core structure before we start on the encoded bytes.
+ */
+ RTAsn1Core_InitEx(&pThis->Asn1Core,
+ ASN1_TAG_OID,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1ObjId_Vtable,
+ RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+
+ /*
+ * Encode the value into the string buffer. This will NOT overflow
+ * because the string representation is much less efficient than the
+ * binary ASN.1 representation (base-10 + separators vs. base-128).
+ */
+ pThis->Asn1Core.cb = (uint32_t)cbObjId;
+ rc = rtAsn1ObjId_EncodeComponents(cComponents, pThis->pauComponents,
+ (uint8_t *)&pThis->szObjId[0], &pThis->Asn1Core.cb);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Now, find a place for the encoded bytes. There might be
+ * enough room left in the szObjId for it if we're lucky.
+ */
+#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */
+ if (pThis->Asn1Core.cb >= cbLeft)
+ pThis->Asn1Core.uData.pv = memmove(&pThis->szObjId[cbObjId], &pThis->szObjId[0], pThis->Asn1Core.cb);
+ else
+#endif
+ rc = RTAsn1ContentDup(&pThis->Asn1Core, pThis->szObjId, pThis->Asn1Core.cb, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Finally, copy the dotted string.
+ */
+ memcpy(pThis->szObjId, pszObjId, cbObjId);
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ AssertMsgFailed(("%Rrc\n", rc));
+ rc = VERR_ASN1_INTERNAL_ERROR_3;
+ }
+ }
+ else
+ rc = VERR_ASN1_OBJID_INVALID_DOTTED_STRING;
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1ObjId_SetFromString(PRTASN1OBJID pThis, const char *pszObjId, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RTAsn1ObjId_Delete(pThis);
+ int rc = RTAsn1ObjId_InitFromString(pThis, pszObjId, pAllocator);
+ if (RT_FAILURE(rc))
+ RTAsn1ObjId_Init(pThis, pAllocator);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1ObjId_CompareWithString(PCRTASN1OBJID pThis, const char *pszRight)
+{
+ return strcmp(pThis->szObjId, pszRight);
+}
+
+
+RTDECL(bool) RTAsn1ObjId_StartsWith(PCRTASN1OBJID pThis, const char *pszStartsWith)
+{
+ size_t cchStartsWith = strlen(pszStartsWith);
+ return !strncmp(pThis->szObjId, pszStartsWith, cchStartsWith)
+ && ( pszStartsWith[cchStartsWith] == '.'
+ || pszStartsWith[cchStartsWith] == '\0');
+}
+
+
+RTDECL(uint8_t) RTAsn1ObjIdCountComponents(PCRTASN1OBJID pThis)
+{
+ return pThis->cComponents;
+}
+
+
+RTDECL(uint32_t) RTAsn1ObjIdGetComponentsAsUInt32(PCRTASN1OBJID pThis, uint8_t iComponent)
+{
+ if (iComponent < pThis->cComponents)
+ return pThis->pauComponents[iComponent];
+ return UINT32_MAX;
+}
+
+
+RTDECL(uint32_t) RTAsn1ObjIdGetLastComponentsAsUInt32(PCRTASN1OBJID pThis)
+{
+ return pThis->pauComponents[pThis->cComponents - 1];
+}
+
+
+/*
+ * ASN.1 OBJECT IDENTIFIER - Standard Methods.
+ */
+
+RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1ObjId_Vtable =
+{
+ "RTAsn1ObjId",
+ sizeof(RTASN1OBJID),
+ ASN1_TAG_OID,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ 0,
+ (PFNRTASN1COREVTDTOR)RTAsn1ObjId_Delete,
+ NULL,
+ (PFNRTASN1COREVTCLONE)RTAsn1ObjId_Clone,
+ (PFNRTASN1COREVTCOMPARE)RTAsn1ObjId_Compare,
+ (PFNRTASN1COREVTCHECKSANITY)RTAsn1ObjId_CheckSanity,
+ NULL,
+ NULL
+};
+
+
+RTDECL(int) RTAsn1ObjId_Init(PRTASN1OBJID pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_NOREF_PV(pAllocator);
+ RTAsn1Core_InitEx(&pThis->Asn1Core,
+ ASN1_TAG_OID,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1ObjId_Vtable,
+ RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+ pThis->Asn1Core.cb = sizeof(g_abDefault);
+ pThis->Asn1Core.uData.pv = (void *)&g_abDefault[0];
+ pThis->cComponents = RT_ELEMENTS(g_auDefault);
+ pThis->pauComponents = g_auDefault;
+ AssertCompile(sizeof(g_szDefault) <= sizeof(pThis->szObjId));
+ memcpy(pThis->szObjId, g_szDefault, sizeof(g_szDefault));
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1ObjId_Clone(PRTASN1OBJID pThis, PCRTASN1OBJID pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator);
+ RT_ZERO(*pThis);
+ if (RTAsn1ObjId_IsPresent(pSrc))
+ {
+ AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1ObjId_Vtable, VERR_INTERNAL_ERROR_3);
+
+ /* Copy the dotted string representation. */
+ size_t cbObjId = strlen(pSrc->szObjId) + 1;
+ AssertReturn(cbObjId <= sizeof(pThis->szObjId), VERR_INTERNAL_ERROR_5);
+ memcpy(pThis->szObjId, pSrc->szObjId, cbObjId);
+
+ /* Copy the integer component array. Try fit it in the unused space of
+ the dotted object string buffer. We place it at the end of the
+ buffer as that is simple alignment wise and avoid wasting bytes that
+ could be used to sequueze in the content bytes (see below). */
+ int rc;
+ RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator);
+ pThis->cComponents = pSrc->cComponents;
+#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */
+ size_t cbLeft = sizeof(pThis->szObjId);
+ if (pSrc->cComponents * sizeof(uint32_t) <= cbLeft)
+ {
+ pThis->pauComponents = (uint32_t *)&pThis->szObjId[sizeof(pThis->szObjId) - pSrc->cComponents * sizeof(uint32_t)];
+ memcpy((uint32_t *)pThis->pauComponents, pSrc->pauComponents, pSrc->cComponents * sizeof(uint32_t));
+ cbLeft -= pSrc->cComponents * sizeof(uint32_t);
+ rc = VINF_SUCCESS;
+ }
+ else
+#endif
+ {
+ rc = RTAsn1MemDup(&pThis->Allocation, (void **)&pThis->pauComponents, pSrc->pauComponents,
+ pSrc->cComponents * sizeof(uint32_t));
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* See if we can fit the content value into the szObjId as well.
+ It will follow immediately after the string as the component
+ array is the end of the string buffer, when present. */
+#if 0 /** @todo breaks with arrays of ObjIds or structs containing them. They get resized and repositioned in memory, thus invalidating the pointer. Add recall-pointers callback, or just waste memory? Or maybe make all arrays pointer-arrays? */
+ uint32_t cbContent = pSrc->Asn1Core.cb;
+ if (cbContent <= cbLeft)
+ {
+ rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Asn1Core.uData.pv = memcpy(&pThis->szObjId[cbObjId], pSrc->Asn1Core.uData.pv, cbContent);
+ return VINF_SUCCESS;
+ }
+ }
+ else
+#endif
+ {
+ rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ }
+
+ /* failed, clean up. */
+ if (pThis->Allocation.cbAllocated)
+ RTAsn1MemFree(&pThis->Allocation, (uint32_t *)pThis->pauComponents);
+ RT_ZERO(*pThis);
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1ObjId_Delete(PRTASN1OBJID pThis)
+{
+ if ( pThis
+ && RTAsn1ObjId_IsPresent(pThis))
+ {
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1ObjId_Vtable);
+
+ if (pThis->Allocation.cbAllocated)
+ RTAsn1MemFree(&pThis->Allocation, (uint32_t *)pThis->pauComponents);
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1ObjId_Enum(PRTASN1OBJID pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser);
+ Assert(pThis && (!RTAsn1ObjId_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1ObjId_Vtable));
+
+ /* No children to enumerate. */
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1ObjId_Compare(PCRTASN1OBJID pLeft, PCRTASN1OBJID pRight)
+{
+ if (RTAsn1ObjId_IsPresent(pLeft))
+ {
+ if (RTAsn1ObjId_IsPresent(pRight))
+ {
+ uint8_t cComponents = RT_MIN(pLeft->cComponents, pRight->cComponents);
+ for (uint32_t i = 0; i < cComponents; i++)
+ if (pLeft->pauComponents[i] != pRight->pauComponents[i])
+ return pLeft->pauComponents[i] < pRight->pauComponents[i] ? -1 : 1;
+
+ if (pLeft->cComponents == pRight->cComponents)
+ return 0;
+ return pLeft->cComponents < pRight->cComponents ? -1 : 1;
+ }
+ return 1;
+ }
+ return 0 - (int)RTAsn1ObjId_IsPresent(pRight);
+}
+
+
+RTDECL(int) RTAsn1ObjId_CheckSanity(PCRTASN1OBJID pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+ if (RT_UNLIKELY(!RTAsn1ObjId_IsPresent(pThis)))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (OBJID).", pszErrorTag);
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-objid-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-core.h>
+#include <iprt/asn1-generator-init.h>
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-decode.cpp
new file mode 100644
index 00000000..fbe8aaf5
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-decode.cpp
@@ -0,0 +1,90 @@
+/* $Id: asn1-ut-octetstring-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, OCTET STRING Type, Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/asn1.h>
+
+
+RTDECL(int) RTAsn1OctetString_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1OCTETSTRING pThis,
+ const char *pszErrorTag)
+{
+ pThis->pEncapsulated = NULL;
+ RTAsn1CursorInitAllocation(pCursor, &pThis->EncapsulatedAllocation);
+
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1CursorMatchTagClassFlagsString(pCursor, &pThis->Asn1Core, ASN1_TAG_OCTET_STRING,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ fFlags, pszErrorTag, "OCTET STRING");
+ if (RT_SUCCESS(rc))
+ {
+ if ( !(pThis->Asn1Core.fClass & ASN1_TAGFLAG_CONSTRUCTED)
+ || (fFlags & RTASN1CURSOR_GET_F_IMPLICIT) ) /* PKCS #7 ContentInfo tweak. */
+ {
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.pOps = &g_RTAsn1OctetString_Vtable;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ return VINF_SUCCESS;
+ }
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CONSTRUCTED_STRING_NOT_IMPL,
+ "%s: Constructed OCTET STRING not implemented.", pszErrorTag);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, rc, "%s: Not OCTET STRING: fClass=%#x / uTag=%#x",
+ pszErrorTag, pThis->Asn1Core.fClass, pThis->Asn1Core.uTag);
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-octetstring-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-template.h
new file mode 100644
index 00000000..b3668922
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring-template.h
@@ -0,0 +1,55 @@
+/* $Id: asn1-ut-octetstring-template.h $ */
+/** @file
+ * IPRT - ASN.1, Octet String Type, Collection Type Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+#define RTASN1TMPL_TYPE RTASN1SEQOFOCTETSTRINGS
+#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfOctetStrings
+#define RTASN1TMPL_INT_NAME rtAsn1SeqOfOctetStrings
+RTASN1TMPL_SEQ_OF(RTASN1OCTETSTRING, RTAsn1OctetString);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+#define RTASN1TMPL_TYPE RTASN1SETOFOCTETSTRINGS
+#define RTASN1TMPL_EXT_NAME RTAsn1SetOfOctetStrings
+#define RTASN1TMPL_INT_NAME rtAsn1SetOfOctetStrings
+RTASN1TMPL_SET_OF(RTASN1OCTETSTRING, RTAsn1OctetString);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-octetstring.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring.cpp
new file mode 100644
index 00000000..32d6f40e
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-octetstring.cpp
@@ -0,0 +1,459 @@
+/* $Id: asn1-ut-octetstring.cpp $ */
+/** @file
+ * IPRT - ASN.1, Octet String.
+ *
+ * @remarks This file should remain very similar to asn1-ut-bitstring.cpp.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/alloca.h>
+#include <iprt/bignum.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTASN1OCTETSTRINGWRITERCTX
+{
+ /** Pointer to the output buffer. */
+ uint8_t *pbBuf;
+ /** The current buffer offset. */
+ uint32_t offBuf;
+ /** The size of the buffer. */
+ uint32_t cbBuf;
+} RTASN1OCTETSTRINGWRITERCTX;
+
+
+/** @callback_method_impl{FNRTASN1ENCODEWRITER,
+ * Used to refresh the content of octet and bit strings. } */
+static DECLCALLBACK(int) rtAsn1OctetStringEncodeWriter(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo)
+{
+ RTASN1OCTETSTRINGWRITERCTX *pCtx = (RTASN1OCTETSTRINGWRITERCTX *)pvUser;
+ AssertReturn(cbToWrite <= pCtx->cbBuf - pCtx->offBuf,
+ RTErrInfoSetF(pErrInfo, VERR_BUFFER_OVERFLOW,
+ "cbToWrite=%#x offBuf=%#x cbBuf=%#x", cbToWrite, pCtx->cbBuf, pCtx->offBuf));
+ memcpy(&pCtx->pbBuf[pCtx->offBuf], pvBuf, cbToWrite);
+ pCtx->offBuf += (uint32_t)cbToWrite;
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTASN1ENCODEWRITER,
+ * Used to compare the encoded raw content of an octet or bit string with the
+ * encapsulated object. } */
+static DECLCALLBACK(int) rtAsn1OctetStringEncodeCompare(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo)
+{
+ RTASN1OCTETSTRINGWRITERCTX *pCtx = (RTASN1OCTETSTRINGWRITERCTX *)pvUser;
+ RT_NOREF_PV(pErrInfo);
+ AssertReturn(cbToWrite <= pCtx->cbBuf - pCtx->offBuf, VERR_BUFFER_OVERFLOW);
+ if (memcmp(&pCtx->pbBuf[pCtx->offBuf], pvBuf, cbToWrite) != 0)
+ return VERR_NOT_EQUAL;
+ pCtx->offBuf += (uint32_t)cbToWrite;
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * ASN.1 OCTET STRING - Specific Methods
+ */
+
+RTDECL(int) RTAsn1OctetString_RefreshContent(PRTASN1OCTETSTRING pThis, uint32_t fFlags,
+ PCRTASN1ALLOCATORVTABLE pAllocator, PRTERRINFO pErrInfo)
+{
+ AssertReturn(pThis->pEncapsulated, VERR_INVALID_STATE);
+
+ uint32_t cbEncoded;
+ int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Asn1Core.cb = cbEncoded;
+
+ rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, cbEncoded, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Initialize the writer context. */
+ RTASN1OCTETSTRINGWRITERCTX Ctx;
+ Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8;
+ Ctx.cbBuf = cbEncoded;
+ Ctx.offBuf = 0;
+
+ rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1OctetStringEncodeWriter, &Ctx, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (Ctx.offBuf == cbEncoded)
+ return VINF_SUCCESS;
+
+ rc = RTErrInfoSetF(pErrInfo, rc, "Expected %#x bytes, got %#x", cbEncoded, Ctx.offBuf);
+ }
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "Error allocating %#x bytes for storing content\n", cbEncoded);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1OctetString_AllocContent(PRTASN1OCTETSTRING pThis, void const *pvSrc, size_t cb,
+ PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertReturn(!pThis->pEncapsulated, VERR_INVALID_STATE);
+ int rc;
+ if (pvSrc)
+ rc = RTAsn1ContentDup(&pThis->Asn1Core, pvSrc, cb, pAllocator);
+ else
+ rc = RTAsn1ContentAllocZ(&pThis->Asn1Core, cb, pAllocator);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1OctetString_SetContent(PRTASN1OCTETSTRING pThis, void const *pvSrc, size_t cbSrc,
+ PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtrReturn(pvSrc, VERR_INVALID_POINTER);
+ return RTAsn1OctetString_AllocContent(pThis, pvSrc, cbSrc, pAllocator);
+}
+
+
+RTDECL(bool) RTAsn1OctetString_AreContentBytesValid(PCRTASN1OCTETSTRING pThis, uint32_t fFlags)
+{
+ if (pThis->pEncapsulated)
+ {
+ /* Check the encoded length of the octets. */
+ uint32_t cbEncoded;
+ int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, NULL);
+ if (RT_FAILURE(rc))
+ return false;
+ if (pThis->Asn1Core.cb != cbEncoded)
+ return false;
+
+ /* Check the encoded bytes, if there are any. */
+ if (cbEncoded)
+ {
+ if (!pThis->Asn1Core.uData.pv)
+ return false;
+
+ /* Check the other bytes. */
+ RTASN1OCTETSTRINGWRITERCTX Ctx;
+ Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8;
+ Ctx.cbBuf = cbEncoded;
+ Ctx.offBuf = 0;
+ rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1OctetStringEncodeCompare, &Ctx, NULL);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/*
+ * ASN.1 OCTET STRING - Standard Methods.
+ */
+
+/** @interface_method_impl{FNRTASN1COREVTENCODEPREP} */
+static DECLCALLBACK(int) RTAsn1OctetString_EncodePrep(PRTASN1CORE pThisCore, uint32_t fFlags, PRTERRINFO pErrInfo)
+{
+ PRTASN1OCTETSTRING pThis = (PRTASN1OCTETSTRING)pThisCore;
+ if (!pThis->pEncapsulated)
+ {
+ Assert(pThis->Asn1Core.cb == 0 || pThis->Asn1Core.uData.pv);
+ return VINF_SUCCESS;
+ }
+
+ /* Figure out the size of the encapsulated content. */
+ uint32_t cbEncoded;
+ int rc = RTAsn1EncodePrepare(pThis->pEncapsulated, fFlags, &cbEncoded, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /* Free the bytes if they don't match up. */
+ if (pThis->Asn1Core.uData.pv)
+ {
+ bool fMustFree = pThis->Asn1Core.cb != cbEncoded;
+ if (!fMustFree)
+ {
+ RTASN1OCTETSTRINGWRITERCTX Ctx;
+ Ctx.pbBuf = (uint8_t *)pThis->Asn1Core.uData.pu8;
+ Ctx.cbBuf = cbEncoded;
+ Ctx.offBuf = 0;
+ rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, rtAsn1OctetStringEncodeCompare, &Ctx, NULL);
+ fMustFree = RT_FAILURE_NP(rc);
+ }
+ if (fMustFree)
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ }
+
+ pThis->Asn1Core.cb = cbEncoded;
+ rc = RTAsn1EncodeRecalcHdrSize(&pThis->Asn1Core, fFlags, pErrInfo);
+ }
+ return rc;
+}
+
+
+/** @interface_method_impl{FNRTASN1COREVTENCODEWRITE} */
+static DECLCALLBACK(int) RTAsn1OctetString_EncodeWrite(PRTASN1CORE pThisCore, uint32_t fFlags, PFNRTASN1ENCODEWRITER pfnWriter,
+ void *pvUser, PRTERRINFO pErrInfo)
+{
+ PRTASN1OCTETSTRING pThis = (PRTASN1OCTETSTRING)pThisCore;
+
+ /*
+ * First the header.
+ */
+ int rc = RTAsn1EncodeWriteHeader(&pThis->Asn1Core, fFlags, pfnWriter, pvUser, pErrInfo);
+ if (RT_SUCCESS(rc) && rc != VINF_ASN1_NOT_ENCODED)
+ {
+ /*
+ * If nothing is encapsulated, the core points to the content (if we have any).
+ */
+ if (!pThis->pEncapsulated)
+ {
+ if (pThis->Asn1Core.cb > 0)
+ rc = pfnWriter(pThis->Asn1Core.uData.pu8, pThis->Asn1Core.cb, pvUser, pErrInfo);
+ }
+ /*
+ * Call upon the encapsulated content to serialize itself.
+ */
+ else
+ rc = RTAsn1EncodeWrite(pThis->pEncapsulated, fFlags, pfnWriter, pvUser, pErrInfo);
+ }
+ return rc;
+}
+
+
+RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1OctetString_Vtable =
+{
+ "OctetString",
+ sizeof(RTASN1OCTETSTRING),
+ ASN1_TAG_OCTET_STRING,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ 0,
+ (PFNRTASN1COREVTDTOR)RTAsn1OctetString_Delete,
+ (PFNRTASN1COREVTENUM)RTAsn1OctetString_Enum,
+ (PFNRTASN1COREVTCLONE)RTAsn1OctetString_Clone,
+ (PFNRTASN1COREVTCOMPARE)RTAsn1OctetString_Compare,
+ (PFNRTASN1COREVTCHECKSANITY)RTAsn1OctetString_CheckSanity,
+ RTAsn1OctetString_EncodePrep,
+ RTAsn1OctetString_EncodeWrite
+};
+
+
+RTDECL(int) RTAsn1OctetString_Init(PRTASN1OCTETSTRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_ZERO(*pThis);
+
+ RTAsn1Core_InitEx(&pThis->Asn1Core, ASN1_TAG_OCTET_STRING, ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1OctetString_Vtable, RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+ /*pThis->pEncapsulated = NULL;*/
+ RTAsn1MemInitAllocation(&pThis->EncapsulatedAllocation, pAllocator);
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1OctetString_Clone(PRTASN1OCTETSTRING pThis, PCRTASN1OCTETSTRING pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator);
+
+ RT_ZERO(*pThis);
+ if (RTAsn1OctetString_IsPresent(pSrc))
+ {
+ AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ if (!pSrc->pEncapsulated)
+ rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator);
+ else
+ rc = RTAsn1Core_CloneNoContent(&pThis->Asn1Core, &pSrc->Asn1Core);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ RTAsn1MemInitAllocation(&pThis->EncapsulatedAllocation, pAllocator);
+ if (pSrc->pEncapsulated)
+ {
+ PCRTASN1COREVTABLE pOps = pSrc->pEncapsulated->pOps;
+ Assert(!pOps || pOps->pfnClone);
+ if (pOps && pOps->pfnClone)
+ {
+ /* We can clone the decoded encapsulated object. */
+ rc = RTAsn1MemAllocZ(&pThis->EncapsulatedAllocation, (void **)&pThis->pEncapsulated, pOps->cbStruct);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pOps->pfnClone(pThis->pEncapsulated, pSrc->pEncapsulated, pAllocator);
+ if (RT_FAILURE(rc))
+ RTAsn1MemFree(&pThis->EncapsulatedAllocation, pThis->pEncapsulated);
+ }
+ }
+ else
+ {
+ /* Borrow the encapsulated pointer and use RTAsn1OctetString_RefreshContent
+ to get an accurate copy of the bytes. */
+ pThis->pEncapsulated = pSrc->pEncapsulated;
+ rc = RTAsn1OctetString_RefreshContent(pThis, RTASN1ENCODE_F_DER, pAllocator, NULL);
+ pThis->pEncapsulated = NULL;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ RT_ZERO(*pThis);
+ return rc;
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1OctetString_Delete(PRTASN1OCTETSTRING pThis)
+{
+ if ( pThis
+ && RTAsn1OctetString_IsPresent(pThis))
+ {
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable);
+
+ /* Destroy the encapsulated object. */
+ if (pThis->pEncapsulated)
+ {
+ RTAsn1VtDelete(pThis->pEncapsulated);
+ if (pThis->EncapsulatedAllocation.cbAllocated)
+ RTAsn1MemFree(&pThis->EncapsulatedAllocation, pThis->pEncapsulated);
+ }
+
+ /* Delete content and wipe the content. */
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1OctetString_Enum(PRTASN1OCTETSTRING pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ Assert(pThis && (!RTAsn1OctetString_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable));
+
+ /* Enumerate the encapsulated object if present. */
+ if (pThis->pEncapsulated)
+ return pfnCallback(pThis->pEncapsulated, "Encapsulated", uDepth + 1, pvUser);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1OctetString_Compare(PCRTASN1OCTETSTRING pLeft, PCRTASN1OCTETSTRING pRight)
+{
+ Assert(pLeft && (!RTAsn1OctetString_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable));
+ Assert(pRight && (!RTAsn1OctetString_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1OctetString_Vtable));
+
+ int iDiff;
+ if (RTAsn1OctetString_IsPresent(pLeft))
+ {
+ if (RTAsn1OctetString_IsPresent(pRight))
+ {
+ /* Since it's really hard to tell whether encapsulated objects have
+ been modified or not, we might have to refresh both objects
+ while doing this compare. We'll try our best to avoid it though. */
+ if (pLeft->pEncapsulated || pRight->pEncapsulated)
+ {
+ if ( pLeft->pEncapsulated
+ && pRight->pEncapsulated
+ && pLeft->pEncapsulated->pOps == pRight->pEncapsulated->pOps)
+ iDiff = pLeft->pEncapsulated->pOps->pfnCompare(pLeft->pEncapsulated, pRight->pEncapsulated);
+ else
+ {
+ /* No direct comparison of encapsulated objects possible,
+ make sure we've got the rigth bytes then. */
+ if ( pLeft->pEncapsulated
+ && !RTAsn1OctetString_AreContentBytesValid(pLeft, RTASN1ENCODE_F_DER))
+ {
+ int rc = RTAsn1OctetString_RefreshContent((PRTASN1OCTETSTRING)pLeft, RTASN1ENCODE_F_DER,
+ pLeft->EncapsulatedAllocation.pAllocator, NULL);
+ AssertRC(rc);
+ }
+
+ if ( pRight->pEncapsulated
+ && !RTAsn1OctetString_AreContentBytesValid(pRight, RTASN1ENCODE_F_DER))
+ {
+ int rc = RTAsn1OctetString_RefreshContent((PRTASN1OCTETSTRING)pRight, RTASN1ENCODE_F_DER,
+ pRight->EncapsulatedAllocation.pAllocator, NULL);
+ AssertRC(rc);
+ }
+
+ /* Compare the content bytes. */
+ iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/);
+ }
+ }
+ /*
+ * No encapsulated object, just compare the raw content bytes.
+ */
+ else
+ iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/);
+ }
+ else
+ iDiff = -1;
+ }
+ else
+ iDiff = 0 - (int)RTAsn1OctetString_IsPresent(pRight);
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1OctetString_CheckSanity(PCRTASN1OCTETSTRING pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ if (RT_UNLIKELY(!RTAsn1OctetString_IsPresent(pThis)))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (OCTET STRING).", pszErrorTag);
+
+ if (pThis->pEncapsulated)
+ return pThis->pEncapsulated->pOps->pfnCheckSanity(pThis->pEncapsulated, fFlags & RTASN1_CHECK_SANITY_F_COMMON_MASK,
+ pErrInfo, pszErrorTag);
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-octetstring-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-core.h>
+#include <iprt/asn1-generator-init.h>
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-string-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-string-decode.cpp
new file mode 100644
index 00000000..d9444d1c
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-string-decode.cpp
@@ -0,0 +1,199 @@
+/* $Id: asn1-ut-string-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, XXX STRING Types, Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/alloca.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+
+#include <iprt/formats/asn1.h>
+
+
+RTDECL(int) RTAsn1String_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1STRING pThis, const char *pszErrorTag)
+{
+ RT_ZERO(*pThis);
+ AssertReturn(!(fFlags & RTASN1CURSOR_GET_F_IMPLICIT), VERR_INVALID_PARAMETER);
+
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do tag matching.
+ */
+ switch (pThis->Asn1Core.uTag)
+ {
+ case ASN1_TAG_UTF8_STRING:
+ case ASN1_TAG_NUMERIC_STRING:
+ case ASN1_TAG_PRINTABLE_STRING:
+ case ASN1_TAG_T61_STRING:
+ case ASN1_TAG_VIDEOTEX_STRING:
+ case ASN1_TAG_IA5_STRING:
+ case ASN1_TAG_GENERALIZED_TIME:
+ case ASN1_TAG_GRAPHIC_STRING:
+ case ASN1_TAG_VISIBLE_STRING:
+ case ASN1_TAG_GENERAL_STRING:
+ case ASN1_TAG_UNIVERSAL_STRING:
+ case ASN1_TAG_BMP_STRING:
+ rc = VINF_SUCCESS;
+ break;
+ default:
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TAG_MISMATCH,
+ "%s: Not a string object: fClass=%#x / uTag=%#x",
+ pszErrorTag, pThis->Asn1Core.fClass, pThis->Asn1Core.uTag);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Match flags. CER/DER makes it complicated.
+ */
+ if (pThis->Asn1Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE))
+ {
+ /*
+ * Primitive strings are simple.
+ */
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.pOps = &g_RTAsn1String_Vtable;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ RTAsn1CursorInitAllocation(pCursor, &pThis->Allocation);
+
+ /* UTF-8 conversion is done lazily, upon request. */
+ return VINF_SUCCESS;
+ }
+
+ if (pThis->Asn1Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED))
+ {
+ /*
+ * Constructed strings are not yet fully implemented.
+ */
+ if (pCursor->fFlags & RTASN1CURSOR_FLAGS_DER)
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING,
+ "%s: DER encoding does not allow constructed strings (cb=%#x uTag=%#x fClass=%#x)",
+ pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core.uTag, pThis->Asn1Core.fClass);
+ else if (pCursor->fFlags & RTASN1CURSOR_FLAGS_CER)
+ {
+ if (pThis->Asn1Core.cb > 1000)
+ rc = VINF_SUCCESS;
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_ILLEGAL_CONSTRUCTED_STRING,
+ "%s: Constructed strings only allowed for >1000 byte in CER encoding: cb=%#x uTag=%#x fClass=%#x",
+ pszErrorTag, pThis->Asn1Core.cb,
+ pThis->Asn1Core.uTag, pThis->Asn1Core.fClass);
+ }
+ /** @todo implement constructed strings. */
+ if (RT_SUCCESS(rc))
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CONSTRUCTED_STRING_NOT_IMPL,
+ "%s: Support for constructed strings is not implemented", pszErrorTag);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TAG_FLAG_CLASS_MISMATCH,
+ "%s: Not a valid string object: fClass=%#x / uTag=%#x",
+ pszErrorTag, pThis->Asn1Core.fClass, pThis->Asn1Core.uTag);
+ }
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+/**
+ * Common worker for the specific string type getters.
+ *
+ * @returns IPRT status code
+ * @param pCursor The cursor.
+ * @param fFlags The RTASN1CURSOR_GET_F_XXX flags.
+ * @param uTag The string tag.
+ * @param pThis The output object.
+ * @param pszErrorTag The error tag.
+ * @param pszWhat The string type name.
+ */
+static int rtAsn1XxxString_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, uint8_t uTag, PRTASN1STRING pThis,
+ const char *pszErrorTag, const char *pszWhat)
+{
+ pThis->cchUtf8 = 0;
+ pThis->pszUtf8 = NULL;
+
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1CursorMatchTagClassFlagsString(pCursor, &pThis->Asn1Core, uTag,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ fFlags, pszErrorTag, pszWhat);
+ if (RT_SUCCESS(rc))
+ {
+ if (!(pThis->Asn1Core.fClass & ASN1_TAGFLAG_CONSTRUCTED))
+ {
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.pOps = &g_RTAsn1String_Vtable;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ RTAsn1CursorInitAllocation(pCursor, &pThis->Allocation);
+ /* UTF-8 conversion is done lazily, upon request. */
+ return VINF_SUCCESS;
+ }
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CONSTRUCTED_STRING_NOT_IMPL,
+ "%s: Constructed %s not implemented.", pszErrorTag, pszWhat);
+ }
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+/*
+ * Generate code for the tag specific decoders.
+ */
+#define RTASN1STRING_IMPL(a_uTag, a_szTag, a_Api) \
+ RTDECL(int) RT_CONCAT(a_Api,_DecodeAsn1)(PRTASN1CURSOR pCursor, uint32_t fFlags, \
+ PRTASN1STRING pThis, const char *pszErrorTag) \
+ { \
+ return rtAsn1XxxString_DecodeAsn1(pCursor, fFlags, a_uTag, pThis, pszErrorTag, a_szTag); \
+ }
+#include "asn1-ut-string-template2.h"
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-string-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-string-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-string-template.h
new file mode 100644
index 00000000..cb1f98b8
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-string-template.h
@@ -0,0 +1,55 @@
+/* $Id: asn1-ut-string-template.h $ */
+/** @file
+ * IPRT - ASN.1, XXX STRING Types, Collection Type Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+#define RTASN1TMPL_TYPE RTASN1SEQOFSTRINGS
+#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfStrings
+#define RTASN1TMPL_INT_NAME rtAsn1SeqOfStrings
+RTASN1TMPL_SEQ_OF(RTASN1STRING, RTAsn1String);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+#define RTASN1TMPL_TYPE RTASN1SETOFSTRINGS
+#define RTASN1TMPL_EXT_NAME RTAsn1SetOfStrings
+#define RTASN1TMPL_INT_NAME rtAsn1SetOfStrings
+RTASN1TMPL_SET_OF(RTASN1STRING, RTAsn1String);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-string-template2.h b/src/VBox/Runtime/common/asn1/asn1-ut-string-template2.h
new file mode 100644
index 00000000..217fe3d8
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-string-template2.h
@@ -0,0 +1,49 @@
+/* $Id: asn1-ut-string-template2.h $ */
+/** @file
+ * IPRT - ASN.1, XXX STRING Types, Template for type specific wrappers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+RTASN1STRING_IMPL(ASN1_TAG_NUMERIC_STRING, "NUMERIC STRING", RTAsn1NumericString)
+RTASN1STRING_IMPL(ASN1_TAG_PRINTABLE_STRING, "PRINTABLE STRING", RTAsn1PrintableString)
+RTASN1STRING_IMPL(ASN1_TAG_T61_STRING, "T61 STRING", RTAsn1T61String)
+RTASN1STRING_IMPL(ASN1_TAG_VIDEOTEX_STRING, "VIDEOTEX STRING", RTAsn1VideotexString)
+RTASN1STRING_IMPL(ASN1_TAG_VISIBLE_STRING, "VISIBLE STRING", RTAsn1VisibleString)
+RTASN1STRING_IMPL(ASN1_TAG_IA5_STRING, "IA5 STRING", RTAsn1Ia5String)
+RTASN1STRING_IMPL(ASN1_TAG_GRAPHIC_STRING, "GRAPHIC STRING", RTAsn1GraphicString)
+RTASN1STRING_IMPL(ASN1_TAG_GENERAL_STRING, "GENERAL STRING", RTAsn1GeneralString)
+RTASN1STRING_IMPL(ASN1_TAG_UTF8_STRING, "UTF8 STRING", RTAsn1Utf8String)
+RTASN1STRING_IMPL(ASN1_TAG_BMP_STRING, "BMP STRING", RTAsn1BmpString)
+RTASN1STRING_IMPL(ASN1_TAG_UNIVERSAL_STRING, "UNIVERSAL STRING", RTAsn1UniversalString)
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-string.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-string.cpp
new file mode 100644
index 00000000..edec7957
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-string.cpp
@@ -0,0 +1,1855 @@
+/* $Id: asn1-ut-string.cpp $ */
+/** @file
+ * IPRT - ASN.1, XXX STRING Types.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static uint8_t const g_acbStringTags[] =
+{
+ /* [ASN1_TAG_EOC] = */ 0,
+ /* [ASN1_TAG_BOOLEAN] = */ 0,
+ /* [ASN1_TAG_INTEGER] = */ 0,
+ /* [ASN1_TAG_BIT_STRING] = */ 0,
+ /* [ASN1_TAG_OCTET_STRING] = */ 0,
+ /* [ASN1_TAG_NULL] = */ 0,
+ /* [ASN1_TAG_OID] = */ 0,
+ /* [ASN1_TAG_OBJECT_DESCRIPTOR] = */ 0,
+ /* [ASN1_TAG_EXTERNAL] = */ 0,
+ /* [ASN1_TAG_REAL] = */ 0,
+ /* [ASN1_TAG_ENUMERATED] = */ 0,
+ /* [ASN1_TAG_EMBEDDED_PDV] = */ 0,
+ /* [ASN1_TAG_UTF8_STRING] = */ 1,
+ /* [ASN1_TAG_RELATIVE_OID] = */ 0,
+ /* [ASN1_TAG_RESERVED_14] = */ 0,
+ /* [ASN1_TAG_RESERVED_15] = */ 0,
+ /* [ASN1_TAG_SEQUENCE] = */ 0,
+ /* [ASN1_TAG_SET] = */ 0,
+ /* [ASN1_TAG_NUMERIC_STRING] = */ 1,
+ /* [ASN1_TAG_PRINTABLE_STRING] = */ 1,
+ /* [ASN1_TAG_T61_STRING] = */ 1,
+ /* [ASN1_TAG_VIDEOTEX_STRING] = */ 1,
+ /* [ASN1_TAG_IA5_STRING] = */ 1,
+ /* [ASN1_TAG_UTC_TIME] = */ 0,
+ /* [ASN1_TAG_GENERALIZED_TIME] = */ 0,
+ /* [ASN1_TAG_GRAPHIC_STRING] = */ 1,
+ /* [ASN1_TAG_VISIBLE_STRING] = */ 1,
+ /* [ASN1_TAG_GENERAL_STRING] = */ 1,
+ /* [ASN1_TAG_UNIVERSAL_STRING] = */ 4,
+ /* [ASN1_TAG_CHARACTER_STRING] = */ 1,
+ /* [ASN1_TAG_BMP_STRING] = */ 2,
+};
+
+
+
+
+/*
+ * ISO/IEC-2022 + TeletexString mess.
+ */
+
+/**
+ * ISO-2022 codepoint mappings.
+ */
+typedef struct RTISO2022MAP
+{
+ /** The number of bytes per character. */
+ uint8_t cb;
+ /** The registration number. */
+ uint16_t uRegistration;
+ /** The size of the pauToUni table. */
+ uint16_t cToUni;
+ /** Pointer to the convertion table from ISO-2022 to Unicode.
+ * ASSUMES that unicode chars above 0xffff won't be required. */
+ uint16_t const *pauToUni;
+
+ /** Escape sequence for loading into G0 or C0 or C1 depending on the type (sans
+ * ESC). */
+ uint8_t abEscLoadXX[6];
+ /** Escape sequence for loading into G1 (sans ESC). */
+ uint8_t abEscLoadG1[6];
+ /** Escape sequence for loading into G2 (sans ESC). */
+ uint8_t abEscLoadG2[6];
+ /** Escape sequence for loading into G3 (sans ESC). */
+ uint8_t abEscLoadG3[6];
+} RTISO2022MAP;
+/** Pointer to const ISO-2022 mappings. */
+typedef RTISO2022MAP const *PCRTISO2022MAP;
+
+/** Unused codepoint value. */
+#define RTISO2022_UNUSED UINT16_C(0xffff)
+
+
+/** Dummy mappings to avoid dealing with NULL pointers in the decoder
+ * registers. */
+static const RTISO2022MAP g_DummyMap =
+{
+ 1, UINT16_MAX, 0, NULL,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* No escape into G0 */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* No escape into G1 */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* No escape into G2 */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* No escape into G3 */
+};
+
+
+/** GL mappings for ISO-IR-168 (Japanese, update of #87), with space and
+ * delete. */
+static const RTISO2022MAP g_IsoIr168Map =
+{
+ //2, 168, RT_ELEMENTS(g_awcIsoIr168Decode), g_awcIsoIr168Decode,
+ 2, 168, 0, NULL,
+ { 0x26, 0x40, 0x2b, 0x24, 0x42, 0xff } /* Esc into G0 */,
+ { 0x26, 0x40, 0x2b, 0x24, 0x29, 0x42 } /* Esc into G1 */,
+ { 0x26, 0x40, 0x2b, 0x24, 0x2a, 0x42 } /* Esc into G2 */,
+ { 0x26, 0x40, 0x2b, 0x24, 0x2b, 0x42 } /* Esc into G3 */,
+};
+
+
+/** GL mappings for ISO-IR-165 (Chinese), with space and delete. */
+static const RTISO2022MAP g_IsoIr165Map =
+{
+ //2, 165, RT_ELEMENTS(g_awcIsoIr165Decode), g_awcIsoIr165Decode,
+ 2, 165, 0, NULL,
+ { 0x24, 0x28, 0x45, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x24, 0x29, 0x45, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x24, 0x2a, 0x45, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x24, 0x2b, 0x45, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+/** GL mappings for ISO-IR-150 (Greek), with space and delete. */
+static const RTISO2022MAP g_IsoIr150Map =
+{
+ //1, 150, RT_ELEMENTS(g_awcIsoIr150Decode), g_awcIsoIr150Decode,
+ 1, 150, 0, NULL,
+ { 0x28, 0x21, 0x40, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x29, 0x21, 0x40, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x2a, 0x21, 0x40, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x2b, 0x21, 0x40, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+/** GL mappings for ISO-IR-103 (Teletex supplementary), with space and
+ * delete. */
+static const RTISO2022MAP g_IsoIr103Map =
+{
+ //1, 103, RT_ELEMENTS(g_awcIsoIr103Decode), g_awcIsoIr103Decode,
+ 1, 103, 0, NULL,
+ { 0x28, 0x76, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x29, 0x76, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x2a, 0x76, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x2b, 0x76, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+/**
+ * GL mapping from ISO-IR-102 (Teletex primary) to unicode, with space and
+ * delete.
+ *
+ * Mostly 1:1, except that (a) what would be dollar is currency sign, (b)
+ * positions 0x5c, 0x5e, 0x7b, 0x7d and 0x7e are defined not to be used.
+ */
+static uint16_t const g_awcIsoIr102Decode[0x60] =
+{
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x00A4, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0xffff, 0x005d, 0xffff, 0x005f,
+ 0xffff, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0xffff, 0x007c, 0xffff, 0xffff, 0x007f,
+};
+
+/** GL mappings for ISO-IR-102, with space and delete. */
+static const RTISO2022MAP g_IsoIr102Map =
+{
+ 1, 102, RT_ELEMENTS(g_awcIsoIr102Decode), g_awcIsoIr102Decode,
+ { 0x28, 0x75, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x29, 0x75, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x2a, 0x75, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x2b, 0x75, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+#if 0 /* unused */
+/** GL mappings for ISO-IR-87 (Japanese), with space and delete. */
+static const RTISO2022MAP g_IsoIr87Map =
+{
+ //1, 87, RT_ELEMENTS(g_awcIsoIr87Decode), g_awcIsoIr97Decode,
+ 1, 87, 0, NULL,
+ { 0x24, 0x42, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x24, 0x29, 0x42, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x24, 0x2a, 0x42, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x24, 0x2b, 0x42, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+#endif
+
+
+/**
+ * GL mapping from ISO-IR-6 (ASCII) to unicode, with space and delete.
+ *
+ * Completely 1:1.
+ */
+static uint16_t const g_awcIsoIr6Decode[0x60] =
+{
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x006b, 0x007c, 0x007d, 0x007e, 0x007f,
+};
+
+/** GL mappings for ISO-IR-6 (ASCII), with space and delete. */
+static const RTISO2022MAP g_IsoIr6Map =
+{
+ 1, 6, RT_ELEMENTS(g_awcIsoIr6Decode), g_awcIsoIr6Decode,
+ { 0x28, 0x42, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x29, 0x42, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+/** GL maps. */
+static PCRTISO2022MAP g_paGLMaps[] =
+{
+ &g_IsoIr6Map,
+ &g_IsoIr102Map,
+ &g_IsoIr103Map,
+ &g_IsoIr150Map,
+ &g_IsoIr165Map,
+ &g_IsoIr168Map,
+};
+
+
+
+/** GR mappings for ISO-IR-164 (Hebrew supplementary). */
+static const RTISO2022MAP g_IsoIr164Map =
+{
+ //1, 164, RT_ELEMENTS(g_awcIsoIr164Decode), g_awcIsoIr164Decode
+ 1, 164, 0, NULL,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x2d, 0x53, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x2e, 0x53, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x2f, 0x53, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+/** GR mappings for ISO-IR-156 (Supplementary for ASCII (#6)). */
+static const RTISO2022MAP g_IsoIr156Map =
+{
+ //1, 156, RT_ELEMENTS(g_awcIsoIr156Decode), g_awcIsoIr156Decode
+ 1, 156, 0, NULL,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x2d, 0x52, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x2e, 0x52, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x2f, 0x52, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+/** GR mappings for ISO-IR-153 (Basic Cyrillic). */
+static const RTISO2022MAP g_IsoIr153Map =
+{
+ //1, 153, RT_ELEMENTS(g_awcIsoIr153Decode), g_awcIsoIr153Decode
+ 1, 153, 0, NULL,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x2d, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x2e, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x2f, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+/** GR mappings for ISO-IR-144 (Cryllic part of Latin/Cyrillic). */
+static const RTISO2022MAP g_IsoIr144Map =
+{
+ //1, 144, RT_ELEMENTS(g_awcIsoIr144Decode), g_awcIsoIr144Decode
+ 1, 144, 0, NULL,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x2d, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x2e, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x2f, 0x4f, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+/** GR mappings for ISO-IR-126 (Latin/Greek). */
+static const RTISO2022MAP g_IsoIr126Map =
+{
+ //1, 126, RT_ELEMENTS(g_awcIsoIr126Decode), g_awcIsoIr126Decode
+ 1, 126, 0, NULL,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* Esc into G0 */,
+ { 0x2d, 0x46, 0xff, 0xff, 0xff, 0xff } /* Esc into G1 */,
+ { 0x2e, 0x46, 0xff, 0xff, 0xff, 0xff } /* Esc into G2 */,
+ { 0x2f, 0x46, 0xff, 0xff, 0xff, 0xff } /* Esc into G3 */,
+};
+
+
+/** GR maps. */
+static PCRTISO2022MAP g_paGRMaps[] =
+{
+ &g_IsoIr126Map,
+ &g_IsoIr144Map,
+ &g_IsoIr153Map,
+ &g_IsoIr156Map,
+ &g_IsoIr164Map,
+};
+
+
+
+/** C0 mapping from ISO-IR-106 to unicode. */
+static uint16_t g_awcIsoIr106Decode[0x20] =
+{
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0008, 0xffff, 0x000a, 0xffff, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x008e, 0x000a, 0x001b, 0xffff, 0x008f, 0xffff, 0xffff,
+};
+
+/** C0 mappings for ISO-IR-106. */
+static const RTISO2022MAP g_IsoIr106Map =
+{
+ 1, 106, RT_ELEMENTS(g_awcIsoIr106Decode), g_awcIsoIr106Decode,
+ { 0x21, 0x45, 0xff, 0xff, 0xff, 0xff } /* Esc into C0 */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */,
+};
+
+/** C0 maps. */
+static PCRTISO2022MAP g_paC0Maps[] =
+{
+ &g_IsoIr106Map,
+};
+
+
+
+/** C1 mapping from ISO-IR-107 to unicode. */
+static uint16_t g_awcIsoIr107Decode[0x20] =
+{
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x008b, 0x008c, 0xffff, 0xffff, 0xffff,
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x009b, 0xffff, 0xffff, 0xffff, 0xffff,
+};
+
+/** C1 mappings for ISO-IR-107. */
+static const RTISO2022MAP g_IsoIr107Map =
+{
+ 1, 107, RT_ELEMENTS(g_awcIsoIr107Decode), g_awcIsoIr107Decode,
+ { 0x22, 0x48, 0xff, 0xff, 0xff, 0xff } /* Esc into C1 */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */,
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } /* N/A */,
+};
+
+/** C1 maps. */
+static PCRTISO2022MAP g_paC1Maps[] =
+{
+ &g_IsoIr107Map,
+};
+
+
+static int rtIso2022Decoder_LookupAndSet(PCRTISO2022MAP *ppMapRet, uint16_t uRegistration, PCRTISO2022MAP *papMaps, uint32_t cMaps)
+{
+ uint32_t i = cMaps;
+ while (i-- > 0)
+ if (papMaps[i]->uRegistration == uRegistration)
+ {
+ /** @todo skip non-Teletex codesets if we ever add more than we need for it. */
+ *ppMapRet = papMaps[i];
+ return VINF_SUCCESS;
+ }
+ return VERR_ASN1_INVALID_T61_STRING_ENCODING;
+}
+
+
+/**
+ * ISO-2022 decoder state.
+ */
+typedef struct RTISO2022DECODERSTATE
+{
+ /** Pointer to the string */
+ uint8_t const *pabString;
+ /** The string size. */
+ uint32_t cbString;
+ /** The current string position. */
+ uint32_t offString;
+
+ /** The GL mapping. */
+ PCRTISO2022MAP pMapGL;
+ /** The GR mapping. */
+ PCRTISO2022MAP pMapGR;
+ /** The lower control set (C0) mapping. */
+ PCRTISO2022MAP pMapC0;
+ /** The higher control set (C1) mapping. */
+ PCRTISO2022MAP pMapC1;
+ /** The G0, G1, G2, and G3 mappings. */
+ PCRTISO2022MAP apMapGn[4];
+ /** Used by SS2 & SS3 to store the orignal GL value that is to be restored. */
+ PCRTISO2022MAP pRestoreGL;
+ /** Pointer to extended error info buffer, optional. */
+ PRTERRINFO pErrInfo;
+} RTISO2022DECODERSTATE;
+/** Pointer to a const ISO-2022 decoder state. */
+typedef RTISO2022DECODERSTATE *PRTISO2022DECODERSTATE;
+
+
+static int rtIso2022Decoder_SetGL(PRTISO2022DECODERSTATE pThis, PCRTISO2022MAP pNewMap)
+{
+ pThis->pMapGL = pNewMap;
+ return VINF_SUCCESS;
+}
+
+
+static int rtIso2022Decoder_SetGR(PRTISO2022DECODERSTATE pThis, PCRTISO2022MAP pNewMap)
+{
+ pThis->pMapGR = pNewMap;
+ return VINF_SUCCESS;
+}
+
+
+static int rtIso2022Decoder_SetGLForOneChar(PRTISO2022DECODERSTATE pThis, PCRTISO2022MAP pTmpMap)
+{
+ pThis->pRestoreGL = pThis->pMapGL;
+ pThis->pMapGL = pTmpMap;
+ return VINF_SUCCESS;
+}
+
+
+static int rtIso2022Decoder_SetC0(PRTISO2022DECODERSTATE pThis, uint16_t uRegistration)
+{
+ return rtIso2022Decoder_LookupAndSet(&pThis->pMapC0, uRegistration, g_paC0Maps, RT_ELEMENTS(g_paC0Maps));
+}
+
+
+static int rtIso2022Decoder_SetC1(PRTISO2022DECODERSTATE pThis, uint16_t uRegistration)
+{
+ return rtIso2022Decoder_LookupAndSet(&pThis->pMapC1, uRegistration, g_paC1Maps, RT_ELEMENTS(g_paC1Maps));
+}
+
+
+/**
+ * Worker for rtIso2022Decoder_FindEscAndSet.
+ *
+ * @returns true if match, false if not.
+ * @param pabLeft Pointer to the first string byte after the ESC.
+ * @param cbLeft The number of bytes left in the string.
+ * @param pabRight Pointer to the abEscLoad* byte array to match with.
+ * @param cbRight Size of the mapping sequence (fixed).
+ * @param pcchMatch Where to return the length of the escape sequence (sans
+ * ESC) on success.
+ */
+static bool rtIso2022Decoder_MatchEscSeqFrom2ndByte(uint8_t const *pabLeft, uint32_t cbLeft,
+ uint8_t const *pabRight, uint32_t cbRight,
+ uint32_t *pcchMatch)
+{
+ Assert(cbRight == 6);
+ uint32_t i = 1;
+ while (i < cbRight)
+ {
+ if (pabRight[i] == 0xff)
+ break;
+ if (cbLeft <= i || pabLeft[i] != pabRight[i])
+ return false;
+ i++;
+ }
+ *pcchMatch = i;
+ return true;
+}
+
+
+/**
+ * Finds a the set with a matching abEscLoad* escape sequence and loads it into
+ * the designated register.
+ *
+ * @returns The length of the sequence on success, negative error status code on
+ * failure.
+ * @param pThis The decoder instance.
+ * @param ppMapRet Used to specify C0 or C1 maps when processing
+ * escape sequences for loading these. Only the
+ * abEscLoadXX arrays will be searched if this is
+ * not NULL. For loading {G0,...,G3} pass NULL.
+ * @param pb Pointer to the start of the escape sequence.
+ * @param cb The number of bytes remaining in the string.
+ * @param papMaps The maps to search.
+ * @param cMaps The number of maps @a papMaps points to.
+ */
+static int rtIso2022Decoder_FindEscAndSet(PRTISO2022DECODERSTATE pThis,
+ PCRTISO2022MAP *ppMapRet, PCRTISO2022MAP *papMaps, uint32_t cMaps)
+{
+ /* Skip the ESC.*/
+ uint8_t const *pb = &pThis->pabString[pThis->offString + 1];
+ uint32_t cb = pThis->cbString - (pThis->offString + 1);
+
+ /* Cache the first char. */
+ uint8_t const b0 = pb[0];
+
+ /* Scan the array of maps for matching sequences. */
+ uint32_t i = cMaps;
+ while (i-- > 0)
+ {
+ uint32_t cchMatch = 0; /* (MSC maybe used uninitialized) */
+ PCRTISO2022MAP pMap = papMaps[i];
+ /** @todo skip non-Teletex codesets if we ever add more than we need for it. */
+ if ( pMap->abEscLoadXX[0] == b0
+ && rtIso2022Decoder_MatchEscSeqFrom2ndByte(pb, cb, pMap->abEscLoadXX, sizeof(pMap->abEscLoadXX), &cchMatch) )
+ {
+ if (ppMapRet)
+ *ppMapRet = pMap;
+ else
+ pThis->apMapGn[0] = pMap;
+ return cchMatch + 1;
+ }
+
+ if (!ppMapRet) /* ppMapRet is NULL if Gn. */
+ {
+ uint32_t iGn;
+ if ( pMap->abEscLoadG1[0] == b0
+ && rtIso2022Decoder_MatchEscSeqFrom2ndByte(pb, cb, pMap->abEscLoadG1, sizeof(pMap->abEscLoadG1), &cchMatch))
+ iGn = 1;
+ else if ( pMap->abEscLoadG2[0] == b0
+ && rtIso2022Decoder_MatchEscSeqFrom2ndByte(pb, cb, pMap->abEscLoadG2, sizeof(pMap->abEscLoadG2), &cchMatch))
+ iGn = 2;
+ else if ( pMap->abEscLoadG3[0] == b0
+ && rtIso2022Decoder_MatchEscSeqFrom2ndByte(pb, cb, pMap->abEscLoadG3, sizeof(pMap->abEscLoadG3), &cchMatch))
+ iGn = 3;
+ else
+ iGn = UINT32_MAX;
+ if (iGn != UINT32_MAX)
+ {
+ pThis->apMapGn[iGn] = pMap;
+ return cchMatch + 1;
+ }
+ }
+ }
+ return VERR_ASN1_TELETEX_UNSUPPORTED_CHARSET;
+}
+
+
+/**
+ * Interprets an escape sequence.
+ *
+ * @returns The length of the sequence on success, negative error status code on
+ * failure.
+ * @param pThis The decoder instance. The offString must be
+ * pointing to the escape byte.
+ */
+static int rtIso2022Decoder_InterpretEsc(PRTISO2022DECODERSTATE pThis)
+{
+ /* the first escape byte. */
+ uint32_t offString = pThis->offString;
+ if (offString + 1 >= pThis->cbString)
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: Unexpected EOS parsing ESC...", offString);
+ int rc;
+ switch (pThis->pabString[offString + 1])
+ {
+ /*
+ * GL selection:
+ */
+ case 0x6e: /* Lock shift two: G2 -> GL */
+ rc = rtIso2022Decoder_SetGL(pThis, pThis->apMapGn[2]);
+ break;
+ case 0x6f: /* Lock shift three: G3 -> GL */
+ rc = rtIso2022Decoder_SetGL(pThis, pThis->apMapGn[3]);
+ break;
+ case 0x4e: /* Single shift two: G2 -> GL for one char. */
+ rc = rtIso2022Decoder_SetGLForOneChar(pThis, pThis->apMapGn[2]);
+ break;
+ case 0x4f: /* Single shift three: G3 -> GL for one char. */
+ rc = rtIso2022Decoder_SetGLForOneChar(pThis, pThis->apMapGn[3]);
+ break;
+
+ /*
+ * GR selection:
+ */
+ case 0x7e: /* Locking shift one right: G1 -> GR. */
+ rc = rtIso2022Decoder_SetGR(pThis, pThis->apMapGn[1]);
+ break;
+ case 0x7d: /* Locking shift two right: G2 -> GR. */
+ rc = rtIso2022Decoder_SetGR(pThis, pThis->apMapGn[2]);
+ break;
+ case 0x7c: /* Locking shift three right: G3 -> GR. */
+ rc = rtIso2022Decoder_SetGR(pThis, pThis->apMapGn[3]);
+ break;
+
+ /*
+ * Cx selection:
+ */
+ case 0x21: /* C0-designate */
+ return rtIso2022Decoder_FindEscAndSet(pThis, &pThis->pMapC0, g_paC0Maps, RT_ELEMENTS(g_paC0Maps));
+ case 0x22: /* C1-designate */
+ return rtIso2022Decoder_FindEscAndSet(pThis, &pThis->pMapC1, g_paC1Maps, RT_ELEMENTS(g_paC1Maps));
+
+ /*
+ * Single-byte character set selection.
+ */
+ case 0x28: /* G0-designate, 94 chars. */
+ case 0x29: /* G1-designate, 94 chars. */
+ case 0x2a: /* G2-designate, 94 chars. */
+ case 0x2b: /* G3-designate, 94 chars. */
+ return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGLMaps, RT_ELEMENTS(g_paGLMaps));
+
+ case 0x2c: /* G0-designate, 96 chars. */
+ case 0x2d: /* G1-designate, 96 chars. */
+ case 0x2e: /* G2-designate, 96 chars. */
+ case 0x2f: /* G3-designate, 96 chars. */
+ return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGRMaps, RT_ELEMENTS(g_paGRMaps));
+
+ /*
+ * Multibyte character set selection.
+ */
+ case 0x24:
+ if (offString + 2 >= pThis->cbString)
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: Unexpected EOS parsing ESC %#x...", offString, pThis->pabString[offString + 1]);
+ switch (pThis->pabString[offString + 2])
+ {
+ case 0x28: /* G0-designate, 94^n chars. */
+ case 0x29: /* G1-designate, 94^n chars. */
+ case 0x2a: /* G2-designate, 94^n chars. */
+ case 0x2b: /* G3-designate, 94^n chars. */
+ default: /* G0-designate that skips the 0x28? (See japanese ones.) */
+ return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGLMaps, RT_ELEMENTS(g_paGLMaps));
+
+ case 0x2c: /* G0-designate, 96^n chars. */
+ case 0x2d: /* G1-designate, 96^n chars. */
+ case 0x2e: /* G2-designate, 96^n chars. */
+ case 0x2f: /* G3-designate, 96^n chars. */
+ return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGRMaps, RT_ELEMENTS(g_paGRMaps));
+ } \
+ break;
+
+ case 0x26: /* Special escape prefix for #168. */
+ return rtIso2022Decoder_FindEscAndSet(pThis, NULL, g_paGLMaps, RT_ELEMENTS(g_paGLMaps));
+
+ /*
+ * Unknown/unsupported/unimplemented.
+ */
+ case 0x25: /* Designate other coding system. */
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_TELETEX_UNSUPPORTED_ESC_SEQ,
+ "@%u: ESC DOCS not supported\n", offString);
+ default:
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_TELETEX_UNKNOWN_ESC_SEQ,
+ "@%u: Unknown escape sequence: ESC %#x...\n", offString, pThis->pabString[offString + 1]);
+ }
+
+ /* Only single byte escapes sequences for shifting ends up here. */
+ if (RT_SUCCESS(rc))
+ return 1;
+ return rc;
+}
+
+
+static int rtIso2022Decoder_ControlCharHook(PRTISO2022DECODERSTATE pThis, uint16_t wcControl)
+{
+ int rc;
+ switch (wcControl)
+ {
+ case 0x000e: /* Locking shift zero: G0 -> GL. */
+ rc = rtIso2022Decoder_SetGL(pThis, pThis->apMapGn[0]);
+ break;
+
+ case 0x000f: /* Locking shift one: G1 -> GL. */
+ rc = rtIso2022Decoder_SetGL(pThis, pThis->apMapGn[1]);
+ break;
+
+ case 0x008e: /* Single shift two: G2 -> GL for one char. */
+ rc = rtIso2022Decoder_SetGLForOneChar(pThis, pThis->apMapGn[2]);
+ break;
+
+ case 0x008f: /* Single shift three: G3 -> GL for one char. */
+ rc = rtIso2022Decoder_SetGLForOneChar(pThis, pThis->apMapGn[3]);
+ break;
+
+ case 0x002b: /* Escape should be handled by the caller. */
+ rc = rtIso2022Decoder_InterpretEsc(pThis);
+ break;
+
+ default:
+ return 0;
+ }
+
+ return RT_SUCCESS(rc) ? 1 : rc;
+}
+
+
+static int rtIso2022Decoder_Init(PRTISO2022DECODERSTATE pThis, const char *pchString, uint32_t cchString,
+ uint32_t uGL, uint32_t uC0, uint32_t uC1, uint32_t uG0,
+ PRTERRINFO pErrInfo)
+{
+ pThis->pabString = (uint8_t const *)pchString;
+ pThis->cbString = cchString;
+ pThis->offString = 0;
+
+ pThis->pMapGL = &g_DummyMap;
+ pThis->pMapGR = &g_DummyMap;
+ pThis->pMapC0 = &g_DummyMap;
+ pThis->pMapC1 = &g_DummyMap;
+ pThis->pRestoreGL = NULL;
+ pThis->apMapGn[0] = &g_DummyMap;
+ pThis->apMapGn[1] = &g_DummyMap;
+ pThis->apMapGn[2] = &g_DummyMap;
+ pThis->apMapGn[3] = &g_DummyMap;
+ pThis->pErrInfo = pErrInfo;
+
+ int rc = VINF_SUCCESS;
+ if (uGL != UINT32_MAX)
+ rc = rtIso2022Decoder_LookupAndSet(&pThis->pMapGL, uGL, g_paGLMaps, RT_ELEMENTS(g_paGLMaps));
+ if (RT_SUCCESS(rc) && uG0 != UINT32_MAX)
+ rc = rtIso2022Decoder_LookupAndSet(&pThis->apMapGn[0], uG0, g_paGLMaps, RT_ELEMENTS(g_paGLMaps));
+ if (RT_SUCCESS(rc) && uC0 != UINT32_MAX)
+ rc = rtIso2022Decoder_SetC0(pThis, uC0);
+ if (RT_SUCCESS(rc) && uC1 != UINT32_MAX)
+ rc = rtIso2022Decoder_SetC1(pThis, uC1);
+ return rc;
+}
+
+
+static int rtIso2022Decoder_GetNextUniCpSlow(PRTISO2022DECODERSTATE pThis, PRTUNICP pUniCp)
+{
+ while (pThis->offString < pThis->cbString)
+ {
+ uint8_t b = pThis->pabString[pThis->offString];
+ if (!(b & 0x80))
+ {
+ if (b >= 0x20)
+ {
+ /*
+ * GL range.
+ */
+ b -= 0x20;
+ PCRTISO2022MAP pMap = pThis->pMapGL;
+
+ /* Single byte character map. */
+ if (pMap->cb == 1)
+ {
+ if (RT_LIKELY(b < pMap->cToUni))
+ {
+ uint16_t wc = pMap->pauToUni[b];
+ if (RT_LIKELY(wc != RTISO2022_UNUSED))
+ {
+ *pUniCp = wc;
+ pThis->offString += 1;
+ return VINF_SUCCESS;
+ }
+ *pUniCp = RTUNICP_INVALID;
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: GL b=%#x is marked unused in map #%u range %u.",
+ pThis->offString, b + 0x20, pMap->uRegistration, pMap->cToUni);
+ }
+ *pUniCp = RTUNICP_INVALID;
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: GL b=%#x is outside map #%u range %u.",
+ pThis->offString, b + 0x20, pMap->uRegistration, pMap->cToUni);
+ }
+
+ /* Double byte character set. */
+ Assert(pMap->cb == 2);
+ if (pThis->offString + 1 < pThis->cbString)
+ {
+ uint8_t b2 = pThis->pabString[pThis->offString + 1];
+ b2 -= 0x20;
+ if (RT_LIKELY(b2 < 0x60))
+ {
+ uint16_t u16 = ((uint16_t)b << 8) | b2;
+ if (RT_LIKELY(u16 < pMap->cToUni))
+ {
+ uint16_t wc = pMap->pauToUni[b];
+ if (RT_LIKELY(wc != RTISO2022_UNUSED))
+ {
+ *pUniCp = wc;
+ pThis->offString += 2;
+ return VINF_SUCCESS;
+ }
+ *pUniCp = RTUNICP_INVALID;
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: GL b=%#x is marked unused in map #%u.",
+ pThis->offString, b + 0x20, pMap->uRegistration);
+ }
+ if (u16 >= 0x7f00)
+ {
+ *pUniCp = 0x7f; /* delete */
+ pThis->offString += 2;
+ return VINF_SUCCESS;
+ }
+ *pUniCp = RTUNICP_INVALID;
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: GL u16=%#x (b0=%#x b1=%#x) is outside map #%u range %u.",
+ pThis->offString, u16, b + 0x20, b2 + 0x20, pMap->uRegistration, pMap->cToUni);
+ }
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: 2nd GL byte outside GL range: b0=%#x b1=%#x (map #%u)",
+ pThis->offString, b + 0x20, b2 + 0x20, pMap->uRegistration);
+
+ }
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: EOS reading 2nd byte for GL b=%#x (map #%u).",
+ pThis->offString, b + 0x20, pMap->uRegistration);
+ }
+ else
+ {
+ /*
+ * C0 range.
+ */
+ Assert(pThis->pMapC0->cb == 0x20);
+ uint16_t wc = pThis->pMapC0->pauToUni[b];
+ if (wc != RTISO2022_UNUSED)
+ {
+ int rc;
+ if (b == 0x1b || wc == 0x1b) /* ESC is hardcoded, or so they say. */
+ rc = rtIso2022Decoder_InterpretEsc(pThis);
+ else
+ rc = rtIso2022Decoder_ControlCharHook(pThis, wc);
+ if (RT_SUCCESS(rc))
+ {
+ if (rc == 0)
+ {
+ pThis->offString += 1;
+ *pUniCp = wc;
+ return VINF_SUCCESS;
+ }
+ pThis->offString += rc;
+ }
+ else
+ return rc;
+ }
+ else
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: C0 b=%#x is marked unused in map #%u.",
+ pThis->offString, b, pThis->pMapC0->uRegistration);
+ }
+ }
+ else
+ {
+ if (b >= 0xa0)
+ {
+ /*
+ * GR range.
+ */
+ b -= 0xa0;
+ PCRTISO2022MAP pMap = pThis->pMapGR;
+
+ /* Single byte character map. */
+ if (pMap->cb == 1)
+ {
+ /** @todo 0xa0 = SPACE and 0xff = DELETE if it's a 94 charater map... */
+ if (RT_LIKELY(b < pMap->cToUni))
+ {
+ uint16_t wc = pMap->pauToUni[b];
+ if (RT_LIKELY(wc != RTISO2022_UNUSED))
+ {
+ *pUniCp = wc;
+ pThis->offString += 1;
+ return VINF_SUCCESS;
+ }
+
+ *pUniCp = RTUNICP_INVALID;
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: GR b=%#x is marked unused in map #%u.",
+ pThis->offString, b + 0xa0, pMap->uRegistration);
+ }
+ *pUniCp = RTUNICP_INVALID;
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: GR b=%#x is outside map #%u range %u",
+ pThis->offString, b + 0xa0, pMap->uRegistration, pMap->cToUni);
+ }
+
+ /* Double byte character set. */
+ Assert(pMap->cb == 2);
+ if (pThis->offString + 1 < pThis->cbString)
+ {
+ uint8_t b2 = pThis->pabString[pThis->offString + 1];
+ b2 -= 0xa0;
+ if (RT_LIKELY(b2 < 0x60))
+ {
+ uint16_t u16 = ((uint16_t)b << 8) | b2;
+ if (RT_LIKELY(u16 < pMap->cToUni))
+ {
+ uint16_t wc = pMap->pauToUni[b];
+ if (RT_LIKELY(wc != RTISO2022_UNUSED))
+ {
+ *pUniCp = wc;
+ pThis->offString += 2;
+ return VINF_SUCCESS;
+ }
+
+ *pUniCp = RTUNICP_INVALID;
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: GR b=%#x is marked unused in map #%u.",
+ pThis->offString, b + 0xa0, pMap->uRegistration);
+ }
+ *pUniCp = RTUNICP_INVALID;
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: GR u16=%#x (b0=%#x b1=%#x) is outside map #%u range %u.",
+ pThis->offString, u16, b + 0xa0, b2 + 0xa0, pMap->uRegistration, pMap->cToUni);
+ }
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: 2nd GR byte outside GR range: b0=%#x b1=%#x (map #%u).",
+ pThis->offString, b + 0xa0, b2 + 0xa0, pMap->uRegistration);
+
+ }
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: EOS reading 2nd byte for GR b=%#x (map #%u).",
+ pThis->offString, b + 0xa0, pMap->uRegistration);
+ }
+ else
+ {
+ /*
+ * C2 range.
+ */
+ Assert(pThis->pMapC1->cb == 0x20);
+ b -= 0x80;
+ uint16_t wc = pThis->pMapC1->pauToUni[b];
+ if (wc != RTISO2022_UNUSED)
+ {
+ int rc = rtIso2022Decoder_ControlCharHook(pThis, wc);
+ if (RT_SUCCESS(rc))
+ {
+ if (rc == 0)
+ {
+ pThis->offString += 1;
+ *pUniCp = wc;
+ return VINF_SUCCESS;
+ }
+ pThis->offString += rc;
+ }
+ else
+ return rc;
+ }
+ else
+ return RTErrInfoSetF(pThis->pErrInfo, VERR_ASN1_INVALID_T61_STRING_ENCODING,
+ "@%u: C1 b=%#x is marked unused in map #%u.",
+ pThis->offString, b + 0x80, pThis->pMapC1->uRegistration);
+ }
+ }
+ }
+
+ /* End of string. */
+ *pUniCp = RTUNICP_INVALID;
+ return VERR_END_OF_STRING;
+}
+
+DECLINLINE(int) rtIso2022Decoder_GetNextUniCp(PRTISO2022DECODERSTATE pThis, PRTUNICP pUniCp)
+{
+ /*
+ * Deal with single byte GL.
+ */
+ uint32_t const offString = pThis->offString;
+ if (pThis->offString < pThis->cbString)
+ {
+ PCRTISO2022MAP const pMapGL = pThis->pMapGL;
+ if (pMapGL->cb == 1)
+ {
+ uint8_t const b = pThis->pabString[offString] - (uint8_t)0x20;
+ if (b < pMapGL->cToUni)
+ {
+ uint16_t wc = pMapGL->pauToUni[b];
+ if (wc != RTISO2022_UNUSED)
+ {
+ pThis->offString = offString + 1;
+ *pUniCp = wc;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+
+ /*
+ * Deal with complications in the non-inline function.
+ */
+ return rtIso2022Decoder_GetNextUniCpSlow(pThis, pUniCp);
+ }
+
+ *pUniCp = RTUNICP_INVALID;
+ return VERR_END_OF_STRING;
+}
+
+
+static int rtIso2022ValidateString(uint32_t uProfile, const char *pch, uint32_t cch, size_t *pcchUtf8, PRTERRINFO pErrInfo)
+{
+ AssertReturn(uProfile == ASN1_TAG_T61_STRING, VERR_INVALID_PARAMETER); /* just a place holder for now. */
+
+ RTISO2022DECODERSTATE Decoder;
+ int rc = rtIso2022Decoder_Init(&Decoder, pch, cch, 102, 106, 107, 102, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchUtf8 = 0;
+ for (;;)
+ {
+ RTUNICP uc;
+ rc = rtIso2022Decoder_GetNextUniCp(&Decoder, &uc);
+ if (RT_SUCCESS(rc))
+ cchUtf8 += RTStrCpSize(uc);
+ else
+ {
+ if (RT_LIKELY(rc == VERR_END_OF_STRING))
+ {
+ *pcchUtf8 = cchUtf8;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ }
+ }
+ return rc;
+}
+
+
+static int rtIso2022RecodeAsUtf8(uint32_t uProfile, const char *pchSrc, uint32_t cchSrc, char *pszDst, size_t cbDst)
+{
+ AssertReturn(uProfile == ASN1_TAG_T61_STRING, VERR_INVALID_PARAMETER); /* just a place holder for now. */
+ AssertReturn(cbDst > 0, VERR_INVALID_PARAMETER);
+
+ RTISO2022DECODERSTATE Decoder;
+ int rc = rtIso2022Decoder_Init(&Decoder, pchSrc, cchSrc, 102, 106, 107, 102, NULL /*pErrInfo*/);
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ RTUNICP uc;
+ rc = rtIso2022Decoder_GetNextUniCp(&Decoder, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc < 0x80 && cbDst > 1)
+ {
+ *pszDst++ = (char)uc;
+ cbDst--;
+ }
+ else
+ {
+ size_t cchUniCp = RTStrCpSize(uc);
+ if (cbDst > cchUniCp)
+ {
+ cbDst -= cchUniCp;
+ pszDst = RTStrPutCp(pszDst, uc);
+ }
+ else
+ {
+ *pszDst = '\0';
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ else if (RT_LIKELY(rc == VERR_END_OF_STRING))
+ {
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+ }
+ else
+ return rc;
+ }
+ }
+ return rc;
+}
+
+
+
+/** The unicode mapping of the C1 area of windows codepage 1252.
+ * The rest of the code page is 1:1 with unicode. */
+static uint16_t g_awcWin1252_C1[0x20] =
+{
+ 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
+ 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178,
+};
+
+
+static size_t rtWin1252CalcUtf8Length(const char *pch, uint32_t cch)
+{
+ size_t cchUtf8 = 0;
+ while (cch-- > 0)
+ {
+ uint8_t const b = *pch++;
+ if (b < 0x80)
+ cchUtf8 += 1;
+ else if (b >= 0xa0)
+ cchUtf8 += 2;
+ else
+ {
+ uint16_t const wc = g_awcWin1252_C1[b - 0x80];
+ cchUtf8 += RTStrCpSize(wc);
+ }
+ }
+ return cchUtf8;
+}
+
+
+static int rtWin1252RecodeAsUtf8(const char *pchSrc, uint32_t cchSrc, char *pszDst, size_t cbDst)
+{
+ while (cchSrc-- > 0)
+ {
+ uint8_t b = *pchSrc++;
+ if (b < 0x80)
+ {
+ if (cbDst <= 1)
+ return VERR_BUFFER_OVERFLOW;
+ *pszDst++ = (char)b;
+ }
+ else
+ {
+ uint16_t const wc = b >= 0xa0 ? b : g_awcWin1252_C1[b - 0x80];
+ size_t cchCp = RTStrCpSize(wc);
+ if (cbDst <= cchCp)
+ return VERR_BUFFER_OVERFLOW;
+ pszDst = RTStrPutCp(pszDst, wc);
+ }
+ }
+
+ if (!cbDst)
+ return VERR_BUFFER_OVERFLOW;
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+}
+
+
+
+/*
+ * ASN.1 STRING - Specific Methods.
+ */
+
+/** rtAsn1String_IsTeletexLatin1 results. */
+typedef enum RTASN1TELETEXVARIANT
+{
+ /** Couldn't find hard evidence of either. */
+ RTASN1TELETEXVARIANT_UNDECIDED = 1,
+ /** Pretty certain that it's real teletex. */
+ RTASN1TELETEXVARIANT_TELETEX,
+ /** Pretty sure it's latin-1 or Windows-1252. */
+ RTASN1TELETEXVARIANT_LATIN1,
+ /** Pretty sure it's Windows-1252. */
+ RTASN1TELETEXVARIANT_WIN_1252
+} RTASN1TELETEXVARIANT;
+
+/**
+ * Takes a guess as whether TELETEX STRING (T61 STRING) is actually is Latin-1
+ * or the real thing.
+ *
+ * According to RFC-2459, section 4.1.2.4, various libraries, certificate
+ * authorities and others have perverted the TeletexString/T61String tag by
+ * ISO-8859-1 (aka latin-1) strings (more probably these are actually Windows
+ * CP-1252 rather than latin-1). We'll try detect incompatible latin-1
+ * perversions by:
+ * - The use of GR (0xf0-0xff) chars.
+ * - The lack of ESC sequences and shifts (LS0,LS1,SS2,SS3)
+ *
+ * An ASSUMTION here is that GR is not loaded with anything at the start of a
+ * teletex string, as per table 3 in section 8.23.5.2 in T-REC-X.590.200811.
+ *
+ * @retval @c true if chances are good that it's LATIN-1.
+ * @retval @c false if changes are very good that it's real teletex.
+ * @param pch The first char in the string.
+ * @param cch The string length.
+ *
+ * @remarks Useful info on Teletex and ISO/IEC-2022:
+ * https://www.mail-archive.com/asn1@asn1.org/msg00460.html
+ * http://en.wikipedia.org/wiki/ISO/IEC_2022
+ * http://www.open-std.org/cen/tc304/guide/GCONCEPT.HTM
+ * http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-035.pdf
+ */
+static RTASN1TELETEXVARIANT rtAsn1String_IsTeletexLatin1(const char *pch, uint32_t cch)
+{
+ RTASN1TELETEXVARIANT enmVariant = RTASN1TELETEXVARIANT_UNDECIDED;
+ while (cch-- > 0)
+ {
+ uint8_t const b = *pch;
+ if (b >= 0x20 && b <= 0x7f)
+ {
+ if (g_awcIsoIr102Decode[b - 0x20] == RTISO2022_UNUSED)
+ enmVariant = RTASN1TELETEXVARIANT_LATIN1;
+ }
+ else
+ {
+ if ( b == 0x1b /* ESC */
+ || b == 0x0e /* LS0 / SI */
+ || b == 0x0f /* LS1 / SO */
+ || b == 0x19 /* SS2 */
+ || b == 0x1d /* SS3 */ )
+ return RTASN1TELETEXVARIANT_TELETEX;
+
+ if (b >= 0xa0)
+ enmVariant = RTASN1TELETEXVARIANT_LATIN1;
+ else if (b >= 0x80 && b <= 0x9f)
+ {
+ /* Any use of C1 characters defined by windows cp-1252 will
+ lead us to believe it's the windows code rather than the
+ ISO/IEC standard that is being used. (Not that it makes
+ much of a difference, because we're gonna treat it as the
+ windows codepage, anyways.) */
+ if ( b != 0x81
+ && b != 0x8d
+ && b != 0x8f
+ && b != 0x90
+ && b != 0x9d)
+ return RTASN1TELETEXVARIANT_WIN_1252;
+ }
+ }
+ }
+ return RTASN1TELETEXVARIANT_UNDECIDED;
+}
+
+
+/**
+ * Checks the encoding of an ASN.1 string according to it's tag.
+ *
+ * @returns IPRT status code.
+ * @param pThis The string to santity check.
+ * @param pErrInfo Where to store extra error info. Optional.
+ * @param pcchUtf8 Where to return the UTF-8 string length. Optional.
+ */
+static int rtAsn1String_CheckSanity(PCRTASN1STRING pThis, PRTERRINFO pErrInfo, const char *pszErrorTag, size_t *pcchUtf8)
+{
+ int rc;
+ uint32_t cch = pThis->Asn1Core.cb;
+ size_t cchUtf8 = cch;
+ const char *pch = pThis->Asn1Core.uData.pch;
+ uint32_t uTag = RTASN1CORE_GET_TAG(&pThis->Asn1Core);
+ switch (uTag)
+ {
+ case ASN1_TAG_UTF8_STRING:
+ rc = RTStrValidateEncodingEx(pch, cch, 0);
+ if (RT_SUCCESS(rc))
+ break;
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_UTF8_STRING_ENCODING, "%s: Bad UTF-8 encoding (%Rrc, %.*Rhxs)",
+ pszErrorTag, rc, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+
+ case ASN1_TAG_NUMERIC_STRING:
+ while (cch-- > 0)
+ {
+ char ch = *pch++;
+ if ( !RT_C_IS_DIGIT(ch)
+ && ch != ' ')
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_NUMERIC_STRING_ENCODING,
+ "%s: Bad numeric string: ch=%#x (pos %u in %.*Rhxs)", pszErrorTag, ch,
+ pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ }
+ break;
+
+ case ASN1_TAG_PRINTABLE_STRING:
+ while (cch-- > 0)
+ {
+ char ch = *pch++;
+ if ( !RT_C_IS_ALNUM(ch)
+ && ch != ' '
+ && ch != '\''
+ && ch != '('
+ && ch != ')'
+ && ch != '+'
+ && ch != ','
+ && ch != '-'
+ && ch != '.'
+ && ch != '/'
+ && ch != ':'
+ && ch != '='
+ && ch != '?'
+ )
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_PRINTABLE_STRING_ENCODING,
+ "%s: Bad printable string: ch=%#x (pos %u in %.*Rhxs)", pszErrorTag, ch,
+ pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ }
+ break;
+
+ case ASN1_TAG_IA5_STRING: /* ASCII */
+ while (cch-- > 0)
+ {
+ unsigned char ch = *pch++;
+ if (ch == 0 || ch >= 0x80)
+ {
+ /* Ignore C-style zero terminator as the "Microsoft ECC Product Root Certificate Authority 2018"
+ for instance, has a policy qualifier string "http://www.microsoft.com/pkiops/Docs/Repository.htm\0" */
+ /** @todo should '\0' really be excluded above? */
+ if (ch != 0 || cch != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_IA5_STRING_ENCODING,
+ "%s: Bad IA5 string: ch=%#x (pos %u in %.*Rhxs)", pszErrorTag, ch,
+ pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ break;
+ }
+ }
+ break;
+
+ case ASN1_TAG_T61_STRING:
+ switch (rtAsn1String_IsTeletexLatin1(pch, cch))
+ {
+ default:
+ rc = rtIso2022ValidateString(ASN1_TAG_T61_STRING, pch, cch, &cchUtf8, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ break;
+ case RTASN1TELETEXVARIANT_UNDECIDED:
+ case RTASN1TELETEXVARIANT_LATIN1:
+ case RTASN1TELETEXVARIANT_WIN_1252:
+ cchUtf8 = rtWin1252CalcUtf8Length(pch, cch);
+ break;
+ }
+ break;
+
+ case ASN1_TAG_VIDEOTEX_STRING:
+ case ASN1_TAG_GRAPHIC_STRING:
+ return VERR_ASN1_STRING_TYPE_NOT_IMPLEMENTED;
+
+ case ASN1_TAG_VISIBLE_STRING:
+ while (cch-- > 0)
+ {
+ unsigned char ch = *pch++;
+ if (ch < 0x20 || ch >= 0x7f)
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_VISIBLE_STRING_ENCODING,
+ "%s: Bad visible string: ch=%#x (pos %u in %.*Rhxs)", pszErrorTag, ch,
+ pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ }
+ break;
+
+ case ASN1_TAG_GENERAL_STRING:
+ return VERR_ASN1_STRING_TYPE_NOT_IMPLEMENTED;
+
+ case ASN1_TAG_UNIVERSAL_STRING:
+ if (!(cch & 3))
+ {
+ uint8_t const *pb = (uint8_t const *)pch;
+ cchUtf8 = 0;
+ while (cch > 0)
+ {
+ RTUNICP uc = RT_MAKE_U32_FROM_U8(pb[3], pb[2], pb[1], pb[0]); /* big endian */
+ if (!RTUniCpIsValid(uc))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_UNIVERSAL_STRING_ENCODING,
+ "%s: Bad universal string: uc=%#x (pos %u in %.*Rhxs)", pszErrorTag, uc,
+ pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ cchUtf8 += RTUniCpCalcUtf8Len(uc);
+
+ /* next */
+ pb += 4;
+ cch -= 4;
+ }
+ break;
+ }
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_UNIVERSAL_STRING_ENCODING,
+ "%s: Bad universal string: size not a multiple of 4: cch=%#x (%.*Rhxs)",
+ pszErrorTag, cch, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+
+ case ASN1_TAG_BMP_STRING:
+ if (!(cch & 1))
+ {
+ uint8_t const *pb = (uint8_t const *)pch;
+ cchUtf8 = 0;
+ while (cch > 0)
+ {
+ RTUNICP uc = RT_MAKE_U32_FROM_U8(pb[1], pb[0], 0, 0); /* big endian */
+ if (!RTUniCpIsValid(uc))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_BMP_STRING_ENCODING,
+ "%s: Bad BMP string: uc=%#x (pos %u in %.*Rhxs)", pszErrorTag, uc,
+ pThis->Asn1Core.cb - cch + 1, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ cchUtf8 += RTUniCpCalcUtf8Len(uc);
+
+ /* next */
+ pb += 2;
+ cch -= 2;
+ }
+ break;
+ }
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_INVALID_BMP_STRING_ENCODING,
+ "%s: Bad BMP string: odd number of bytes cch=%#x (%.*Rhxs)",
+ pszErrorTag, cch, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+
+ default:
+ AssertMsgFailedReturn(("uTag=%#x\n", uTag), VERR_INTERNAL_ERROR_3);
+ }
+
+ if (pcchUtf8)
+ *pcchUtf8 = cchUtf8;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1String_CompareValues(PCRTASN1STRING pLeft, PCRTASN1STRING pRight)
+{
+ return RTAsn1String_CompareEx(pLeft, pRight, false /*fTypeToo*/);
+}
+
+
+RTDECL(int) RTAsn1String_CompareEx(PCRTASN1STRING pLeft, PCRTASN1STRING pRight, bool fTypeToo)
+{
+ Assert(pLeft && (!RTAsn1String_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1String_Vtable));
+ Assert(pRight && (!RTAsn1String_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1String_Vtable));
+
+ int iDiff;
+ if (RTAsn1String_IsPresent(pLeft))
+ {
+ if (RTAsn1String_IsPresent(pRight))
+ {
+ if (!fTypeToo || RTASN1CORE_GET_TAG(&pLeft->Asn1Core) == RTASN1CORE_GET_TAG(&pRight->Asn1Core))
+ iDiff = RTAsn1Core_CompareEx(&pLeft->Asn1Core, &pRight->Asn1Core, true /*fIgnoreTagAndClass*/);
+ else
+ iDiff = RTASN1CORE_GET_TAG(&pLeft->Asn1Core) < RTASN1CORE_GET_TAG(&pRight->Asn1Core) ? -1 : 1;
+ }
+ else
+ iDiff = 1;
+ }
+ else
+ iDiff = 0 - RTAsn1String_IsPresent(pRight);
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1String_CompareWithString(PCRTASN1STRING pThis, const char *pszString, size_t cchString)
+{
+ Assert(pThis && (!RTAsn1String_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable));
+ AssertPtr(pszString);
+
+ int iDiff;
+ if (RTAsn1String_IsPresent(pThis))
+ {
+ if (cchString == RTSTR_MAX)
+ cchString = strlen(pszString);
+
+ /*
+ * If there is a UTF-8 conversion available already, use it.
+ */
+ if (pThis->pszUtf8)
+ {
+ iDiff = strncmp(pThis->pszUtf8, pszString, cchString);
+ if (!iDiff && pThis->cchUtf8 != cchString)
+ iDiff = pThis->cchUtf8 < cchString ? -1 : 1;
+ }
+ else
+ {
+ /*
+ * Some types are UTF-8 compatible, so try do the compare without
+ * RTAsn1String_QueryUtf8.
+ */
+ uint32_t cch = pThis->Asn1Core.cb;
+ const char *pch = pThis->Asn1Core.uData.pch;
+ switch (RTASN1CORE_GET_TAG(&pThis->Asn1Core))
+ {
+ case ASN1_TAG_UTF8_STRING:
+ case ASN1_TAG_NUMERIC_STRING:
+ case ASN1_TAG_IA5_STRING:
+ case ASN1_TAG_PRINTABLE_STRING:
+ iDiff = strncmp(pch, pszString, RT_MIN(cch, cchString));
+ if (iDiff && cch != cchString)
+ iDiff = cch < cchString ? - 1 : 1;
+ break;
+
+ /** @todo Implement comparing ASN1_TAG_BMP_STRING, ASN1_TAG_UNIVERSAL_STRING and
+ * ASN1_TAG_T61_STRING with UTF-8 strings without conversion. */
+
+ default:
+ {
+ int rc = RTAsn1String_QueryUtf8(pThis, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ iDiff = strncmp(pThis->pszUtf8, pszString, cchString);
+ if (!iDiff && pThis->cchUtf8 != cchString)
+ iDiff = pThis->cchUtf8 < cchString ? -1 : 1;
+ }
+ else
+ iDiff = -1;
+ break;
+ }
+ }
+ }
+
+ /* Reduce the strcmp return value. */
+ if (iDiff != 0)
+ iDiff = iDiff < 0 ? -1 : 1;
+ }
+ else
+ iDiff = -1;
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1String_QueryUtf8(PCRTASN1STRING pThis, const char **ppsz, size_t *pcch)
+{
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable);
+
+ char *psz = (char *)pThis->pszUtf8;
+ size_t cch = pThis->cchUtf8;
+ if (!psz)
+ {
+
+ /*
+ * Convert the first time around. Start by validating the encoding and
+ * calculating the length.
+ */
+ int rc = rtAsn1String_CheckSanity(pThis, NULL, NULL, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ PRTASN1STRING pThisNC = (PRTASN1STRING)pThis;
+ rc = RTAsn1MemAllocZ(&pThisNC->Allocation, (void **)&psz, cch + 1);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Got memory, now do the actual convertion to UTF-8 / copying.
+ */
+ switch (RTASN1CORE_GET_TAG(&pThis->Asn1Core))
+ {
+ case ASN1_TAG_UTF8_STRING:
+ case ASN1_TAG_NUMERIC_STRING:
+ case ASN1_TAG_PRINTABLE_STRING:
+ case ASN1_TAG_IA5_STRING:
+ case ASN1_TAG_VISIBLE_STRING:
+ Assert(cch == pThis->Asn1Core.cb);
+ memcpy(psz, pThis->Asn1Core.uData.pch, cch);
+ psz[cch] = '\0';
+ break;
+
+ case ASN1_TAG_T61_STRING:
+ switch (rtAsn1String_IsTeletexLatin1(pThis->Asn1Core.uData.pch, pThis->Asn1Core.cb))
+ {
+ default:
+ rc = rtIso2022RecodeAsUtf8(ASN1_TAG_T61_STRING, pThis->Asn1Core.uData.pch, pThis->Asn1Core.cb,
+ psz, cch + 1);
+ break;
+ case RTASN1TELETEXVARIANT_UNDECIDED:
+ case RTASN1TELETEXVARIANT_LATIN1:
+ case RTASN1TELETEXVARIANT_WIN_1252:
+ rc = rtWin1252RecodeAsUtf8(pThis->Asn1Core.uData.pch, pThis->Asn1Core.cb, psz, cch + 1);
+ break;
+ }
+ AssertReturnStmt(RT_SUCCESS(rc), RTAsn1MemFree(&pThisNC->Allocation, psz), VERR_INTERNAL_ERROR_3);
+ break;
+
+ /* case ASN1_TAG_VIDEOTEX_STRING: */
+ /* case ASN1_TAG_GRAPHIC_STRING: */
+ /* case ASN1_TAG_GENERAL_STRING: */
+
+ case ASN1_TAG_UNIVERSAL_STRING:
+ {
+ char *pszDst = psz;
+ size_t cchSrc = pThis->Asn1Core.cb;
+ uint8_t const *pbSrc = pThis->Asn1Core.uData.pu8;
+ while (cchSrc > 0)
+ {
+ RTUNICP uc = RT_MAKE_U32_FROM_U8(pbSrc[3], pbSrc[2], pbSrc[1], pbSrc[0]); /* big endian */
+ AssertReturnStmt(RTUniCpIsValid(uc), RTAsn1MemFree(&pThisNC->Allocation, psz), VERR_INTERNAL_ERROR_2);
+ pszDst = RTStrPutCp(pszDst, uc);
+
+ /* next */
+ pbSrc += 4;
+ cchSrc -= 4;
+ }
+ Assert((size_t)(pszDst - psz) == cch);
+ break;
+ }
+
+ case ASN1_TAG_BMP_STRING:
+ {
+ char *pszDst = psz;
+ size_t cchSrc = pThis->Asn1Core.cb;
+ uint8_t const *pbSrc = pThis->Asn1Core.uData.pu8;
+ while (cchSrc > 0)
+ {
+ RTUNICP uc = RT_MAKE_U32_FROM_U8(pbSrc[1], pbSrc[0], 0, 0); /* big endian */
+ AssertReturnStmt(RTUniCpIsValid(uc), RTAsn1MemFree(&pThisNC->Allocation, psz), VERR_INTERNAL_ERROR_2);
+ pszDst = RTStrPutCp(pszDst, uc);
+
+ /* next */
+ pbSrc += 2;
+ cchSrc -= 2;
+ }
+ Assert((size_t)(pszDst - psz) == cch);
+ break;
+ }
+
+ default:
+ RTAsn1MemFree(&pThisNC->Allocation, psz);
+ AssertMsgFailedReturn(("uTag=%#x\n", RTASN1CORE_GET_TAG(&pThis->Asn1Core)), VERR_INTERNAL_ERROR_3);
+ }
+
+ /*
+ * Successfully produced UTF-8. Save it in the object.
+ */
+ pThisNC->pszUtf8 = psz;
+ pThisNC->cchUtf8 = (uint32_t)cch;
+ }
+ else
+ return rc;
+ }
+ else
+ return rc;
+ }
+
+ /*
+ * Success.
+ */
+ if (ppsz)
+ *ppsz = psz;
+ if (pcch)
+ *pcch = cch;
+ return VINF_SUCCESS;
+}
+
+
+
+RTDECL(int) RTAsn1String_QueryUtf8Len(PCRTASN1STRING pThis, size_t *pcch)
+{
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable);
+
+ size_t cch = pThis->cchUtf8;
+ if (!cch && !pThis->pszUtf8)
+ {
+ int rc = rtAsn1String_CheckSanity(pThis, NULL, NULL, &cch);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ *pcch = cch;
+ return VINF_SUCCESS;
+}
+
+
+
+
+RTDECL(int) RTAsn1String_InitEx(PRTASN1STRING pThis, uint32_t uTag, void const *pvValue, size_t cbValue,
+ PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_ZERO(*pThis);
+ AssertMsgReturn(uTag < RT_ELEMENTS(g_acbStringTags) && g_acbStringTags[uTag] > 0, ("uTag=%#x\n", uTag),
+ VERR_INVALID_PARAMETER);
+
+ RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator);
+ RTAsn1Core_InitEx(&pThis->Asn1Core,
+ uTag,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1String_Vtable,
+ RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+
+ if (cbValue > 0)
+ {
+ int rc = RTAsn1ContentDup(&pThis->Asn1Core, pvValue, cbValue, pAllocator);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1String_InitWithValue(PRTASN1STRING pThis, const char *pszUtf8Value, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ Assert(RTStrValidateEncoding(pszUtf8Value));
+ return RTAsn1String_InitEx(pThis, ASN1_TAG_UTF8_STRING, pszUtf8Value, strlen(pszUtf8Value), pAllocator);
+}
+
+
+RTDECL(int) RTAsn1String_RecodeAsUtf8(PRTASN1STRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ /*
+ * Query the UTF-8 string. Do this even if it's already an
+ * ASN1_TAG_UTF8_STRING object as it makes sure we've got a valid UTF-8
+ * string upon successful return.
+ */
+ int rc = RTAsn1String_QueryUtf8(pThis, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTASN1CORE_GET_TAG(&pThis->Asn1Core) != ASN1_TAG_UTF8_STRING)
+ {
+ /*
+ * Resize the content, copy the UTF-8 bytes in there, and change
+ * the tag.
+ */
+ rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, pThis->cchUtf8, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy((void *)pThis->Asn1Core.uData.pv, pThis->pszUtf8, pThis->cchUtf8);
+ rc = RTAsn1Core_ChangeTag(&pThis->Asn1Core, ASN1_TAG_UTF8_STRING);
+ }
+ }
+ }
+ return rc;
+}
+
+
+
+/*
+ * ASN.1 STRING - Standard Methods.
+ */
+
+RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1String_Vtable =
+{
+ "RTAsn1String",
+ sizeof(RTASN1STRING),
+ UINT8_MAX,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ 0,
+ (PFNRTASN1COREVTDTOR)RTAsn1String_Delete,
+ NULL,
+ (PFNRTASN1COREVTCLONE)RTAsn1String_Clone,
+ (PFNRTASN1COREVTCOMPARE)RTAsn1String_Compare,
+ (PFNRTASN1COREVTCHECKSANITY)RTAsn1String_CheckSanity,
+ NULL,
+ NULL
+};
+
+
+RTDECL(int) RTAsn1String_Init(PRTASN1STRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ return RTAsn1String_InitEx(pThis, ASN1_TAG_UTF8_STRING, NULL /*pvValue*/, 0 /*cbValue*/, pAllocator);
+}
+
+
+RTDECL(int) RTAsn1String_Clone(PRTASN1STRING pThis, PCRTASN1STRING pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator);
+ RT_ZERO(*pThis);
+ if (RTAsn1String_IsPresent(pSrc))
+ {
+ AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1String_Vtable, VERR_INTERNAL_ERROR_3);
+ int rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Don't copy the UTF-8 representation, decode it when queried. */
+ RTAsn1MemInitAllocation(&pThis->Allocation, pAllocator);
+ return VINF_SUCCESS;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1String_Delete(PRTASN1STRING pThis)
+{
+ if ( pThis
+ && RTAsn1String_IsPresent(pThis))
+ {
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable);
+
+ if (pThis->Allocation.cbAllocated)
+ RTAsn1MemFree(&pThis->Allocation, (char *)pThis->pszUtf8);
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1String_Enum(PRTASN1STRING pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser);
+ Assert(pThis && (!RTAsn1String_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable));
+
+ /* No children to enumerate. */
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1String_Compare(PCRTASN1STRING pLeft, PCRTASN1STRING pRight)
+{
+ /* Compare tag and binary value. */
+ return RTAsn1String_CompareEx(pLeft, pRight, true /*fTypeToo*/);
+}
+
+
+RTDECL(int) RTAsn1String_CheckSanity(PCRTASN1STRING pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+ if (RT_UNLIKELY(!RTAsn1String_IsPresent(pThis)))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (STRING).", pszErrorTag);
+ return rtAsn1String_CheckSanity(pThis, pErrInfo, pszErrorTag, NULL /*pcchUtf8*/);
+}
+
+
+/*
+ * Generate code for the tag specific methods.
+ * Note! This is very similar to what we're doing in asn1-ut-time.cpp.
+ */
+#define RTASN1STRING_IMPL(a_uTag, a_szTag, a_Api) \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_Init)(PRTASN1STRING pThis, PCRTASN1ALLOCATORVTABLE pAllocator) \
+ { \
+ return RTAsn1String_InitEx(pThis, a_uTag, NULL /*pvValue*/, 0 /*cbValue*/, pAllocator); \
+ } \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_Clone)(PRTASN1STRING pThis, PCRTASN1STRING pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) \
+ { \
+ AssertReturn(RTASN1CORE_GET_TAG(&pSrc->Asn1Core) == a_uTag || !RTAsn1String_IsPresent(pSrc), \
+ VERR_ASN1_STRING_TAG_MISMATCH); \
+ return RTAsn1String_Clone(pThis, pSrc, pAllocator); \
+ } \
+ \
+ RTDECL(void) RT_CONCAT(a_Api,_Delete)(PRTASN1STRING pThis) \
+ { \
+ Assert( !pThis \
+ || !RTAsn1String_IsPresent(pThis) \
+ || ( pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable \
+ && RTASN1CORE_GET_TAG(&pThis->Asn1Core) == a_uTag) ); \
+ RTAsn1String_Delete(pThis); \
+ } \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_Enum)(PRTASN1STRING pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) \
+ { \
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); \
+ Assert( pThis \
+ && ( !RTAsn1String_IsPresent(pThis) \
+ || ( pThis->Asn1Core.pOps == &g_RTAsn1String_Vtable \
+ && RTASN1CORE_GET_TAG(&pThis->Asn1Core) == a_uTag) ) ); \
+ /* No children to enumerate. */ \
+ return VINF_SUCCESS; \
+ } \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_Compare)(PCRTASN1STRING pLeft, PCRTASN1STRING pRight) \
+ { \
+ int iDiff = RTAsn1String_CompareEx(pLeft, pRight, true /*fTypeToo*/); \
+ if (!iDiff && RTASN1CORE_GET_TAG(&pLeft->Asn1Core) != a_uTag && RTAsn1String_IsPresent(pLeft)) \
+ iDiff = RTASN1CORE_GET_TAG(&pLeft->Asn1Core) < a_uTag ? -1 : 1; \
+ return iDiff; \
+ } \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_CheckSanity)(PCRTASN1STRING pThis, uint32_t fFlags, \
+ PRTERRINFO pErrInfo, const char *pszErrorTag) \
+ { \
+ if (RTASN1CORE_GET_TAG(&pThis->Asn1Core) != a_uTag && RTAsn1String_IsPresent(pThis)) \
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_STRING_TAG_MISMATCH, "%s: uTag=%#x, expected %#x (%s)", \
+ pszErrorTag, RTASN1CORE_GET_TAG(&pThis->Asn1Core), a_uTag, a_szTag); \
+ return RTAsn1String_CheckSanity(pThis, fFlags, pErrInfo, pszErrorTag); \
+ }
+
+#include "asn1-ut-string-template2.h"
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-string-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-core.h>
+#include <iprt/asn1-generator-init.h>
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-time-decode.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-time-decode.cpp
new file mode 100644
index 00000000..5df4b73d
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-time-decode.cpp
@@ -0,0 +1,415 @@
+/* $Id: asn1-ut-time-decode.cpp $ */
+/** @file
+ * IPRT - ASN.1, UTC TIME and GENERALIZED TIME Types, Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/alloca.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/**
+ * Common code for UTCTime and GeneralizedTime converters that normalizes the
+ * converted time and checks that the input values doesn't change.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor to use when reporting an error.
+ * @param pThis The time to normalize and check.
+ * @param pszType The type name.
+ * @param pszErrorTag The error tag.
+ */
+static int rtAsn1Time_NormalizeTime(PRTASN1CURSOR pCursor, PRTASN1TIME pThis, const char *pszType, const char *pszErrorTag)
+{
+ int rc;
+ if ( pThis->Time.u8Month > 0
+ && pThis->Time.u8Month <= 12
+ && pThis->Time.u8Hour < 24
+ && pThis->Time.u8Minute < 60
+ && pThis->Time.u8Second <= 60)
+ {
+ /* Work around clever rounding error in DER_CFDateToUTCTime() on OS X. This also
+ supresses any attempt at feeding us leap seconds. If we pass 60 to the
+ normalization code will move on to the next min/hour/day, which is wrong both
+ for the OS X issue and for unwanted leap seconds. Leap seconds are not valid
+ ASN.1 by the by according to the specs available to us. */
+ if (pThis->Time.u8Second < 60)
+ { /* likely */ }
+ else
+ pThis->Time.u8Second = 59;
+
+ /* Normalize and move on. */
+ RTTIME const TimeCopy = pThis->Time;
+ if (RTTimeNormalize(&pThis->Time))
+ {
+ if ( TimeCopy.u8MonthDay == pThis->Time.u8MonthDay
+ && TimeCopy.u8Month == pThis->Time.u8Month
+ && TimeCopy.i32Year == pThis->Time.i32Year
+ && TimeCopy.u8Hour == pThis->Time.u8Hour
+ && TimeCopy.u8Minute == pThis->Time.u8Minute
+ && TimeCopy.u8Second == pThis->Time.u8Second)
+ return VINF_SUCCESS;
+
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_TIME_NORMALIZE_MISMATCH,
+ "%s: Normalized result not the same as %s: '%.*s' / %04u-%02u-%02uT%02u:%02u:%02u vs %04u-%02u-%02uT%02u:%02u:%02u",
+ pszErrorTag, pszType, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch,
+ TimeCopy.i32Year, TimeCopy.u8Month, TimeCopy.u8MonthDay,
+ TimeCopy.u8Hour, TimeCopy.u8Minute, TimeCopy.u8Second,
+ pThis->Time.i32Year, pThis->Time.u8Month, pThis->Time.u8MonthDay,
+ pThis->Time.u8Hour, pThis->Time.u8Minute, pThis->Time.u8Second);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_TIME_NORMALIZE_ERROR,
+ "%s: RTTimeNormalize failed on %s: '%.*s'",
+ pszErrorTag, pszType, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_TIME_BAD_NORMALIZE_INPUT,
+ "%s: Bad %s values: '%.*s'; mth=%u h=%u min=%u sec=%u",
+ pszErrorTag, pszType, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch,
+ pThis->Time.u8Month, pThis->Time.u8Hour, pThis->Time.u8Minute, pThis->Time.u8Second);
+ return rc;
+}
+
+
+/**
+ * Converts the UTCTime string into an the RTTIME member of RTASN1TIME.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor to use when reporting an error.
+ * @param pThis The time to parse.
+ * @param pszErrorTag The error tag.
+ */
+static int rtAsn1Time_ConvertUTCTime(PRTASN1CURSOR pCursor, PRTASN1TIME pThis, const char *pszErrorTag)
+{
+ /*
+ * While the current spec says the seconds field is not optional, this
+ * restriction was added later on. So, when parsing UTCTime we must deal
+ * with it being absent.
+ */
+ int rc;
+ bool fHaveSeconds = pThis->Asn1Core.cb == sizeof("YYMMDDHHMMSSZ") - 1;
+ if (fHaveSeconds || pThis->Asn1Core.cb == sizeof("YYMMDDHHMMZ") - 1)
+ {
+ const char *pachTime = pThis->Asn1Core.uData.pch;
+
+ /* Basic encoding validation. */
+ if ( RT_C_IS_DIGIT(pachTime[0]) /* Y */
+ && RT_C_IS_DIGIT(pachTime[1]) /* Y */
+ && RT_C_IS_DIGIT(pachTime[2]) /* M */
+ && RT_C_IS_DIGIT(pachTime[3]) /* M */
+ && RT_C_IS_DIGIT(pachTime[4]) /* D */
+ && RT_C_IS_DIGIT(pachTime[5]) /* D */
+ && RT_C_IS_DIGIT(pachTime[6]) /* H */
+ && RT_C_IS_DIGIT(pachTime[7]) /* H */
+ && RT_C_IS_DIGIT(pachTime[8]) /* M */
+ && RT_C_IS_DIGIT(pachTime[9]) /* M */
+ && ( !fHaveSeconds
+ || ( RT_C_IS_DIGIT(pachTime[10]) /* S */
+ && RT_C_IS_DIGIT(pachTime[11]) /* S */ ) )
+ && pachTime[fHaveSeconds ? 12 : 10] == 'Z'
+ )
+ {
+ /* Basic conversion. */
+ pThis->Time.i32Year = (pachTime[0] - '0') * 10 + (pachTime[1] - '0');
+ pThis->Time.i32Year += pThis->Time.i32Year < 50 ? 2000 : 1900;
+ pThis->Time.u8Month = (pachTime[2] - '0') * 10 + (pachTime[3] - '0');
+ pThis->Time.u8WeekDay = 0;
+ pThis->Time.u16YearDay = 0;
+ pThis->Time.u8MonthDay = (pachTime[4] - '0') * 10 + (pachTime[5] - '0');
+ pThis->Time.u8Hour = (pachTime[6] - '0') * 10 + (pachTime[7] - '0');
+ pThis->Time.u8Minute = (pachTime[8] - '0') * 10 + (pachTime[9] - '0');
+ if (fHaveSeconds)
+ pThis->Time.u8Second = (pachTime[10] - '0') * 10 + (pachTime[11] - '0');
+ else
+ pThis->Time.u8Second = 0;
+ pThis->Time.u32Nanosecond = 0;
+ pThis->Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ pThis->Time.offUTC = 0;
+
+ /* Check the convered data and normalize the time structure. */
+ rc = rtAsn1Time_NormalizeTime(pCursor, pThis, "UTCTime", pszErrorTag);
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_UTC_TIME_ENCODING, "%s: Bad UTCTime encoding: '%.*s'",
+ pszErrorTag, pThis->Asn1Core.cb, pachTime);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_UTC_TIME_ENCODING, "%s: Bad UTCTime length: %#x",
+ pszErrorTag, pThis->Asn1Core.cb);
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+/**
+ * Converts the fraction part of a generalized time into nanoseconds.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor to use when reporting an error.
+ * @param pchFraction Pointer to the start of the fraction (dot).
+ * @param cchFraction The length of the fraction.
+ * @param pThis The time object we're working on,
+ * Time.u32Nanoseconds will be update.
+ * @param pszErrorTag The error tag.
+ */
+static int rtAsn1Time_ConvertGeneralizedTimeFraction(PRTASN1CURSOR pCursor, const char *pchFraction, uint32_t cchFraction,
+ PRTASN1TIME pThis, const char *pszErrorTag)
+{
+ pThis->Time.u32Nanosecond = 0;
+
+ /*
+ * Check the dot.
+ */
+ if (*pchFraction != '.')
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING,
+ "%s: Expected GeneralizedTime fraction dot, found: '%c' ('%.*s')",
+ pszErrorTag, *pchFraction, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ pchFraction++;
+ cchFraction--;
+ if (!cchFraction)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING,
+ "%s: No digit following GeneralizedTime fraction dot: '%.*s'",
+ pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core);
+
+ /*
+ * Do the conversion.
+ */
+ char chLastDigit;
+ uint32_t uMult = 100000000;
+ do
+ {
+ char chDigit = chLastDigit = *pchFraction;
+ if (!RT_C_IS_DIGIT(chDigit))
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING,
+ "%s: Bad GeneralizedTime fraction digit: '%.*s'",
+ pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ pThis->Time.u32Nanosecond += uMult * (uint32_t)(chDigit - '0');
+
+ /* Advance */
+ cchFraction--;
+ pchFraction++;
+ uMult /= 10;
+ } while (cchFraction > 0 && uMult > 0);
+
+ /*
+ * Lazy bird: For now, we don't permit higher resolution than we can
+ * internally represent. Deal with this if it ever becomes an issue.
+ */
+ if (cchFraction > 0)
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING,
+ "%s: Bad GeneralizedTime fraction too long: '%.*s'",
+ pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ if (chLastDigit == '0')
+ return RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING,
+ "%s: Trailing zeros not allowed for GeneralizedTime: '%.*s'",
+ pszErrorTag, pThis->Asn1Core.cb, pThis->Asn1Core.uData.pch);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts the GeneralizedTime string into an the RTTIME member of RTASN1TIME.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor to use when reporting an error.
+ * @param pThis The time to parse.
+ * @param pszErrorTag The error tag.
+ */
+static int rtAsn1Time_ConvertGeneralizedTime(PRTASN1CURSOR pCursor, PRTASN1TIME pThis, const char *pszErrorTag)
+{
+ int rc;
+ if (pThis->Asn1Core.cb >= sizeof("YYYYMMDDHHMMSSZ") - 1)
+ {
+ const char *pachTime = pThis->Asn1Core.uData.pch;
+
+ /* Basic encoding validation. */
+ if ( RT_C_IS_DIGIT(pachTime[0]) /* Y */
+ && RT_C_IS_DIGIT(pachTime[1]) /* Y */
+ && RT_C_IS_DIGIT(pachTime[2]) /* Y */
+ && RT_C_IS_DIGIT(pachTime[3]) /* Y */
+ && RT_C_IS_DIGIT(pachTime[4]) /* M */
+ && RT_C_IS_DIGIT(pachTime[5]) /* M */
+ && RT_C_IS_DIGIT(pachTime[6]) /* D */
+ && RT_C_IS_DIGIT(pachTime[7]) /* D */
+ && RT_C_IS_DIGIT(pachTime[8]) /* H */
+ && RT_C_IS_DIGIT(pachTime[9]) /* H */
+ && RT_C_IS_DIGIT(pachTime[10]) /* M */
+ && RT_C_IS_DIGIT(pachTime[11]) /* M */
+ && RT_C_IS_DIGIT(pachTime[12]) /* S */ /** @todo was this once optional? */
+ && RT_C_IS_DIGIT(pachTime[13]) /* S */
+ && pachTime[pThis->Asn1Core.cb - 1] == 'Z'
+ )
+ {
+ /* Basic conversion. */
+ pThis->Time.i32Year = 1000 * (pachTime[0] - '0')
+ + 100 * (pachTime[1] - '0')
+ + 10 * (pachTime[2] - '0')
+ + (pachTime[3] - '0');
+ pThis->Time.u8Month = (pachTime[4] - '0') * 10 + (pachTime[5] - '0');
+ pThis->Time.u8WeekDay = 0;
+ pThis->Time.u16YearDay = 0;
+ pThis->Time.u8MonthDay = (pachTime[6] - '0') * 10 + (pachTime[7] - '0');
+ pThis->Time.u8Hour = (pachTime[8] - '0') * 10 + (pachTime[9] - '0');
+ pThis->Time.u8Minute = (pachTime[10] - '0') * 10 + (pachTime[11] - '0');
+ pThis->Time.u8Second = (pachTime[12] - '0') * 10 + (pachTime[13] - '0');
+ pThis->Time.u32Nanosecond = 0;
+ pThis->Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ pThis->Time.offUTC = 0;
+
+ /* Optional fraction part. */
+ rc = VINF_SUCCESS;
+ uint32_t cchLeft = pThis->Asn1Core.cb - 14 - 1;
+ if (cchLeft > 0)
+ rc = rtAsn1Time_ConvertGeneralizedTimeFraction(pCursor, pachTime + 14, cchLeft, pThis, pszErrorTag);
+
+ /* Check the convered data and normalize the time structure. */
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtAsn1Time_NormalizeTime(pCursor, pThis, "GeneralizedTime", pszErrorTag);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING,
+ "%s: Bad GeneralizedTime encoding: '%.*s'",
+ pszErrorTag, pThis->Asn1Core.cb, pachTime);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_INVALID_GENERALIZED_TIME_ENCODING,
+ "%s: Bad GeneralizedTime length: %#x",
+ pszErrorTag, pThis->Asn1Core.cb);
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1Time_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1TIME pThis, const char *pszErrorTag)
+{
+ Assert(!(fFlags & RTASN1CURSOR_GET_F_IMPLICIT)); RT_NOREF_PV(fFlags);
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->Asn1Core.fClass == (ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE) )
+ {
+ if (pThis->Asn1Core.uTag == ASN1_TAG_UTC_TIME)
+ {
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.pOps = &g_RTAsn1Time_Vtable;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ return rtAsn1Time_ConvertUTCTime(pCursor, pThis, pszErrorTag);
+ }
+
+ if (pThis->Asn1Core.uTag == ASN1_TAG_GENERALIZED_TIME)
+ {
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.pOps = &g_RTAsn1Time_Vtable;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ return rtAsn1Time_ConvertGeneralizedTime(pCursor, pThis, pszErrorTag);
+ }
+
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TAG_MISMATCH, "%s: Not UTCTime nor GeneralizedTime: uTag=%#x",
+ pszErrorTag, pThis->Asn1Core.uTag);
+ }
+ else
+ rc = RTAsn1CursorSetInfo(pCursor, VERR_ASN1_CURSOR_TAG_FLAG_CLASS_MISMATCH,
+ "%s: Not UTCTime nor GeneralizedTime: fClass=%#x / uTag=%#x",
+ pszErrorTag, pThis->Asn1Core.fClass, pThis->Asn1Core.uTag);
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1UtcTime_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1TIME pThis, const char *pszErrorTag)
+{
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_UTC_TIME,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ fFlags, pszErrorTag, "UTC TIME");
+ if (RT_SUCCESS(rc))
+ {
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.pOps = &g_RTAsn1Time_Vtable;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ return rtAsn1Time_ConvertUTCTime(pCursor, pThis, pszErrorTag);
+ }
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1GeneralizedTime_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTASN1TIME pThis, const char *pszErrorTag)
+{
+ int rc = RTAsn1CursorReadHdr(pCursor, &pThis->Asn1Core, pszErrorTag);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1CursorMatchTagClassFlags(pCursor, &pThis->Asn1Core, ASN1_TAG_GENERALIZED_TIME,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ fFlags, pszErrorTag, "GENERALIZED TIME");
+ if (RT_SUCCESS(rc))
+ {
+ RTAsn1CursorSkip(pCursor, pThis->Asn1Core.cb);
+ pThis->Asn1Core.pOps = &g_RTAsn1Time_Vtable;
+ pThis->Asn1Core.fFlags |= RTASN1CORE_F_PRIMITE_TAG_STRUCT;
+ return rtAsn1Time_ConvertGeneralizedTime(pCursor, pThis, pszErrorTag);
+ }
+ }
+ RT_ZERO(*pThis);
+ return rc;
+}
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-time-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-time-template.h b/src/VBox/Runtime/common/asn1/asn1-ut-time-template.h
new file mode 100644
index 00000000..06ba84aa
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-time-template.h
@@ -0,0 +1,55 @@
+/* $Id: asn1-ut-time-template.h $ */
+/** @file
+ * IPRT - ASN.1, UTC TIME and GENERALIZED TIME Types, Collection Type Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+#define RTASN1TMPL_TYPE RTASN1SEQOFTIMES
+#define RTASN1TMPL_EXT_NAME RTAsn1SeqOfTimes
+#define RTASN1TMPL_INT_NAME rtAsn1SeqOfTimes
+RTASN1TMPL_SEQ_OF(RTASN1TIME, RTAsn1Time);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+#define RTASN1TMPL_TYPE RTASN1SETOFTIMES
+#define RTASN1TMPL_EXT_NAME RTAsn1SetOfTimes
+#define RTASN1TMPL_INT_NAME rtAsn1SetOfTimes
+RTASN1TMPL_SET_OF(RTASN1TIME, RTAsn1Time);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-time-template2.h b/src/VBox/Runtime/common/asn1/asn1-ut-time-template2.h
new file mode 100644
index 00000000..1c341bdc
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-time-template2.h
@@ -0,0 +1,40 @@
+/* $Id: asn1-ut-time-template2.h $ */
+/** @file
+ * IPRT - ASN.1, UTC TIME and GENERALIZED TIME Types, Template for type specific wrappers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+RTASN1TIME_IMPL(ASN1_TAG_UTC_TIME, "UTC TIME", RTAsn1UtcTime)
+RTASN1TIME_IMPL(ASN1_TAG_GENERALIZED_TIME, "GENERALIZED TIME", RTAsn1GeneralizedTime)
+
diff --git a/src/VBox/Runtime/common/asn1/asn1-ut-time.cpp b/src/VBox/Runtime/common/asn1/asn1-ut-time.cpp
new file mode 100644
index 00000000..d7176e91
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/asn1-ut-time.cpp
@@ -0,0 +1,359 @@
+/* $Id: asn1-ut-time.cpp $ */
+/** @file
+ * IPRT - ASN.1, UTC TIME and GENERALIZED TIME Types.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/asn1.h>
+
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#include <iprt/formats/asn1.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The UTC TIME encoding of the IPRT epoch time. */
+static const char g_szEpochUtc[] = "700101000000Z";
+/** The GENERALIZED TIME encoding of the IPRT epoch time. */
+static const char g_szEpochGeneralized[] = "19700101000000Z";
+
+
+/*
+ * ASN.1 TIME - Special Methods.
+ */
+
+RTDECL(int) RTAsn1Time_InitEx(PRTASN1TIME pThis, uint32_t uTag, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ RT_NOREF_PV(pAllocator);
+ AssertReturn(uTag == ASN1_TAG_UTC_TIME || uTag == ASN1_TAG_GENERALIZED_TIME, VERR_INVALID_PARAMETER);
+ RTAsn1Core_InitEx(&pThis->Asn1Core,
+ uTag,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ &g_RTAsn1Time_Vtable,
+ RTASN1CORE_F_PRESENT | RTASN1CORE_F_PRIMITE_TAG_STRUCT);
+ if (uTag == ASN1_TAG_UTC_TIME)
+ {
+ pThis->Asn1Core.cb = sizeof(g_szEpochUtc) - 1;
+ pThis->Asn1Core.uData.pv = &g_szEpochUtc[0];
+ }
+ else
+ {
+ pThis->Asn1Core.cb = sizeof(g_szEpochGeneralized) - 1;
+ pThis->Asn1Core.uData.pv = &g_szEpochGeneralized[0];
+ }
+
+ RTTIMESPEC EpochTimeSpec;
+ RTTimeExplode(&pThis->Time, RTTimeSpecSetSeconds(&EpochTimeSpec, 0));
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Time_InitWithTime(PRTASN1TIME pThis, uint32_t uTag, PCRTASN1ALLOCATORVTABLE pAllocator, PCRTTIME pTime)
+{
+ int rc = RTAsn1Time_InitEx(pThis, uTag, pAllocator); /* this doens't leave any state needing deletion */
+ if (RT_SUCCESS(rc) && pTime)
+ rc = RTAsn1Time_SetTime(pThis, pAllocator, pTime);
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1Time_SetTime(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator, PCRTTIME pTime)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(RTAsn1Time_IsPresent(pThis), VERR_INVALID_STATE); /* Use RTAsn1Time_InitWithTime. */
+
+ RTTIMESPEC TmpTimeSpec;
+ AssertReturn(RTTimeImplode(&TmpTimeSpec, pTime), VERR_INVALID_PARAMETER);
+ RTTIME NormalizedTime;
+ RTTimeExplode(&NormalizedTime, &TmpTimeSpec);
+
+ uint32_t const uTag = RTASN1CORE_GET_TAG(&pThis->Asn1Core);
+ if (uTag == ASN1_TAG_UTC_TIME)
+ {
+ AssertReturn(NormalizedTime.i32Year >= 1950, VERR_INVALID_PARAMETER);
+ AssertReturn(NormalizedTime.i32Year < 2050, VERR_INVALID_PARAMETER);
+ }
+ else
+ {
+ AssertReturn(uTag == ASN1_TAG_GENERAL_STRING, VERR_INVALID_STATE);
+ AssertReturn(NormalizedTime.i32Year >= 0, VERR_INVALID_PARAMETER);
+ AssertReturn(NormalizedTime.i32Year < 9999, VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Format the string to a temporary buffer, since the ASN.1 content isn't
+ * zero terminated and we cannot use RTStrPrintf directly on it.
+ */
+ char szTmp[64];
+ uint32_t cchTime;
+ if (uTag == ASN1_TAG_UTC_TIME)
+ cchTime = (uint32_t)RTStrPrintf(szTmp, sizeof(szTmp), "%02u%02u%02u%02u%02u%02uZ",
+ NormalizedTime.i32Year % 100,
+ NormalizedTime.u8Month,
+ NormalizedTime.u8MonthDay,
+ NormalizedTime.u8Hour,
+ NormalizedTime.u8Minute,
+ NormalizedTime.u8Second);
+ else
+ cchTime = (uint32_t)RTStrPrintf(szTmp, sizeof(szTmp), "%04u%02u%02u%02u%02u%02uZ",
+ NormalizedTime.i32Year,
+ NormalizedTime.u8Month,
+ NormalizedTime.u8MonthDay,
+ NormalizedTime.u8Hour,
+ NormalizedTime.u8Minute,
+ NormalizedTime.u8Second);
+ AssertReturn(cchTime == (uTag == ASN1_TAG_UTC_TIME ? sizeof(g_szEpochUtc) - 1 : sizeof(g_szEpochGeneralized) - 1),
+ VERR_INTERNAL_ERROR_3);
+
+ /*
+ * (Re-)Allocate content buffer, copy over the formatted timestamp and
+ * set the exploded time member to the new time.
+ */
+ int rc = RTAsn1ContentReallocZ(&pThis->Asn1Core, cchTime, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy((void *)pThis->Asn1Core.uData.pv, szTmp, cchTime);
+ pThis->Time = NormalizedTime;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTAsn1Time_SetTimeSpec(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator, PCRTTIMESPEC pTimeSpec)
+{
+ RTTIME Time;
+ return RTAsn1Time_SetTime(pThis, pAllocator, RTTimeExplode(&Time, pTimeSpec));
+}
+
+
+RTDECL(int) RTAsn1Time_CompareWithTimeSpec(PCRTASN1TIME pLeft, PCRTTIMESPEC pTsRight)
+{
+ int iDiff = RTAsn1Time_IsPresent(pLeft) ? 0 : -1;
+ if (!iDiff)
+ {
+ RTTIME RightTime;
+ iDiff = RTTimeCompare(&pLeft->Time, RTTimeExplode(&RightTime, pTsRight));
+ }
+
+ return iDiff;
+}
+
+
+/*
+ * ASN.1 TIME - Standard Methods.
+ */
+
+RT_DECL_DATA_CONST(RTASN1COREVTABLE const) g_RTAsn1Time_Vtable =
+{
+ "RTAsn1Time",
+ sizeof(RTASN1TIME),
+ UINT8_MAX,
+ ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_PRIMITIVE,
+ 0,
+ (PFNRTASN1COREVTDTOR)RTAsn1Time_Delete,
+ NULL,
+ (PFNRTASN1COREVTCLONE)RTAsn1Time_Clone,
+ (PFNRTASN1COREVTCOMPARE)RTAsn1Time_Compare,
+ (PFNRTASN1COREVTCHECKSANITY)RTAsn1Time_CheckSanity,
+ NULL,
+ NULL
+};
+
+
+RTDECL(int) RTAsn1Time_Init(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ /* Using UTC TIME since epoch would be encoded using UTC TIME following
+ X.509 Validity / Whatever time tag guidelines. */
+ return RTAsn1Time_InitEx(pThis, ASN1_TAG_UTC_TIME, pAllocator);
+}
+
+
+RTDECL(int) RTAsn1Time_Clone(PRTASN1TIME pThis, PCRTASN1TIME pSrc, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ AssertPtr(pSrc); AssertPtr(pThis); AssertPtr(pAllocator);
+ RT_ZERO(*pThis);
+ if (RTAsn1Time_IsPresent(pSrc))
+ {
+ AssertReturn(pSrc->Asn1Core.pOps == &g_RTAsn1Time_Vtable, VERR_INTERNAL_ERROR_3);
+
+ int rc = RTAsn1Core_CloneContent(&pThis->Asn1Core, &pSrc->Asn1Core, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Time = pSrc->Time;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTAsn1Time_Delete(PRTASN1TIME pThis)
+{
+ if ( pThis
+ && RTAsn1Time_IsPresent(pThis))
+ {
+ Assert(pThis->Asn1Core.pOps == &g_RTAsn1Time_Vtable);
+
+ RTAsn1ContentFree(&pThis->Asn1Core);
+ RT_ZERO(*pThis);
+ }
+}
+
+
+RTDECL(int) RTAsn1Time_Enum(PRTASN1TIME pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser)
+{
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser);
+ Assert(pThis && (!RTAsn1Time_IsPresent(pThis) || pThis->Asn1Core.pOps == &g_RTAsn1Time_Vtable));
+
+ /* No children to enumerate. */
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTAsn1Time_Compare(PCRTASN1TIME pLeft, PCRTASN1TIME pRight)
+{
+ Assert(pLeft && (!RTAsn1Time_IsPresent(pLeft) || pLeft->Asn1Core.pOps == &g_RTAsn1Time_Vtable));
+ Assert(pRight && (!RTAsn1Time_IsPresent(pRight) || pRight->Asn1Core.pOps == &g_RTAsn1Time_Vtable));
+
+ int iDiff;
+ if (RTAsn1Time_IsPresent(pLeft))
+ {
+ if (RTAsn1Time_IsPresent(pRight))
+ iDiff = RTTimeCompare(&pLeft->Time, &pRight->Time);
+ else
+ iDiff = -1;
+ }
+ else
+ iDiff = 0 - (int)RTAsn1Time_IsPresent(pRight);
+ return iDiff;
+}
+
+
+RTDECL(int) RTAsn1Time_CheckSanity(PCRTASN1TIME pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+ if (RT_UNLIKELY(!RTAsn1Time_IsPresent(pThis)))
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_NOT_PRESENT, "%s: Missing (TIME).", pszErrorTag);
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate code for the tag specific methods.
+ * Note! This is very similar to what we're doing in asn1-ut-string.cpp.
+ */
+#define RTASN1TIME_IMPL(a_uTag, a_szTag, a_Api) \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_Init)(PRTASN1TIME pThis, PCRTASN1ALLOCATORVTABLE pAllocator) \
+ { \
+ return RTAsn1Time_InitEx(pThis, a_uTag, pAllocator); \
+ } \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_Clone)(PRTASN1TIME pThis, PCRTASN1TIME pSrc, PCRTASN1ALLOCATORVTABLE pAllocator) \
+ { \
+ AssertReturn(RTASN1CORE_GET_TAG(&pSrc->Asn1Core) == a_uTag || !RTAsn1Time_IsPresent(pSrc), \
+ VERR_ASN1_TIME_TAG_MISMATCH); \
+ return RTAsn1Time_Clone(pThis, pSrc, pAllocator); \
+ } \
+ \
+ RTDECL(void) RT_CONCAT(a_Api,_Delete)(PRTASN1TIME pThis) \
+ { \
+ Assert( !pThis \
+ || !RTAsn1Time_IsPresent(pThis) \
+ || ( pThis->Asn1Core.pOps == &g_RTAsn1Time_Vtable \
+ && RTASN1CORE_GET_TAG(&pThis->Asn1Core) == a_uTag) ); \
+ RTAsn1Time_Delete(pThis); \
+ } \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_Enum)(PRTASN1TIME pThis, PFNRTASN1ENUMCALLBACK pfnCallback, uint32_t uDepth, void *pvUser) \
+ { \
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(uDepth); RT_NOREF_PV(pvUser); \
+ Assert( pThis \
+ && ( !RTAsn1Time_IsPresent(pThis) \
+ || ( pThis->Asn1Core.pOps == &g_RTAsn1Time_Vtable \
+ && RTASN1CORE_GET_TAG(&pThis->Asn1Core) == a_uTag) ) ); \
+ /* No children to enumerate. */ \
+ return VINF_SUCCESS; \
+ } \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_Compare)(PCRTASN1TIME pLeft, PCRTASN1TIME pRight) \
+ { \
+ int iDiff = RTAsn1Time_Compare(pLeft, pRight); \
+ if (!iDiff && RTAsn1Time_IsPresent(pLeft)) \
+ { \
+ if (RTASN1CORE_GET_TAG(&pLeft->Asn1Core) == RTASN1CORE_GET_TAG(&pRight->Asn1Core)) \
+ { \
+ if (RTASN1CORE_GET_TAG(&pLeft->Asn1Core) != a_uTag) \
+ iDiff = RTASN1CORE_GET_TAG(&pLeft->Asn1Core) < a_uTag ? -1 : 1; \
+ } \
+ else \
+ iDiff = RTASN1CORE_GET_TAG(&pLeft->Asn1Core) < RTASN1CORE_GET_TAG(&pRight->Asn1Core) ? -1 : 1; \
+ } \
+ return iDiff; \
+ } \
+ \
+ RTDECL(int) RT_CONCAT(a_Api,_CheckSanity)(PCRTASN1TIME pThis, uint32_t fFlags, \
+ PRTERRINFO pErrInfo, const char *pszErrorTag) \
+ { \
+ if (RTASN1CORE_GET_TAG(&pThis->Asn1Core) != a_uTag && RTAsn1Time_IsPresent(pThis)) \
+ return RTErrInfoSetF(pErrInfo, VERR_ASN1_TIME_TAG_MISMATCH, "%s: uTag=%#x, expected %#x (%s)", \
+ pszErrorTag, RTASN1CORE_GET_TAG(&pThis->Asn1Core), a_uTag, a_szTag); \
+ return RTAsn1Time_CheckSanity(pThis, fFlags, pErrInfo, pszErrorTag); \
+ }
+
+#include "asn1-ut-time-template2.h"
+
+
+/*
+ * Generate code for the associated collection types.
+ */
+#define RTASN1TMPL_TEMPLATE_FILE "../common/asn1/asn1-ut-time-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+#include <iprt/asn1-generator-core.h>
+#include <iprt/asn1-generator-init.h>
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/asn1/oiddb.cfg b/src/VBox/Runtime/common/asn1/oiddb.cfg
new file mode 100644
index 00000000..000bc47d
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/oiddb.cfg
@@ -0,0 +1,351 @@
+# $Id: oiddb.cfg $
+## @file
+# OID names.
+#
+# This is processed by oid2c.cpp and turned into a database which
+# the ASN.1 dumper code is using.
+#
+# Two formats here:
+# 1. Simple:
+# <ODI> = <name>
+# 2. The dumpasn1.cfg one:
+# ODI = <ODI>
+# <other stuff> = <ignored>
+# Description = <Description>
+#
+# The file doesn't need to be sorted, but we keep it sorted to make it
+# simpler to insert new OIDs and spot bugs.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program 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
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+
+1.2.840 = usa
+1.2.840.10045 = ansi-x962
+1.2.840.10045.2 = ansi-x962-keyType
+1.2.840.10045.2.1 = ansi-x962-ecPublicKey
+1.2.840.10045.4 = ansi-x962-signatures
+1.2.840.10045.4.1 = ecdsa-with-sha1
+1.2.840.10045.4.2 = ecdsa-with-recommended
+1.2.840.10045.4.3 = ecdsa-with-sha2
+1.2.840.10045.4.3.1 = ecdsa-with-sha224
+1.2.840.10045.4.3.2 = ecdsa-with-sha256
+1.2.840.10045.4.3.3 = ecdsa-with-sha384
+1.2.840.10045.4.3.4 = ecdsa-with-sha512
+1.2.840.113549 = rsadsi
+1.2.840.113549.1 = pkcs
+1.2.840.113549.1.1 = pkcs1
+1.2.840.113549.1.1.1 = pkcs1-RsaEncryption
+1.2.840.113549.1.1.2 = pkcs1-Md2WithRsaEncryption
+1.2.840.113549.1.1.3 = pkcs1-Md4WithRsaEncryption
+1.2.840.113549.1.1.4 = pkcs1-Md5WithRsaEncryption
+1.2.840.113549.1.1.5 = pkcs1-Sha1WithRsaEncryption
+1.2.840.113549.1.1.10 = pkcs1-RsaPss
+1.2.840.113549.1.1.11 = pkcs1-Sha256WithRsaEncryption
+1.2.840.113549.1.1.12 = pkcs1-Sha384WithRsaEncryption
+1.2.840.113549.1.1.13 = pkcs1-Sha512WithRsaEncryption
+1.2.840.113549.1.1.14 = pkcs1-Sha224WithRsaEncryption
+1.2.840.113549.1.1.15 = pkcs1-Sha512-224WithRsaEncryption
+1.2.840.113549.1.1.16 = pkcs1-Sha512-256WithRsaEncryption
+1.2.840.113549.1.7 = pkcs7
+1.2.840.113549.1.7.1 = pkcs7-data
+1.2.840.113549.1.7.2 = pkcs7-signedData
+1.2.840.113549.1.7.3 = pkcs7-envelopedData
+1.2.840.113549.1.7.4 = pkcs7-signedAndEnvelopedData
+1.2.840.113549.1.7.5 = pkcs7-digestedData
+1.2.840.113549.1.7.6 = pkcs7-encryptedData
+1.2.840.113549.1.9 = pkcs9
+1.2.840.113549.1.9.1 = pkcs9-EMailAddress
+1.2.840.113549.1.9.2 = pkcs9-UntrustedName
+1.2.840.113549.1.9.3 = pkcs9-ContentType
+1.2.840.113549.1.9.4 = pkcs9-MessageDigest
+1.2.840.113549.1.9.5 = pkcs9-SigningTime
+1.2.840.113549.1.9.6 = pkcs9-CounterSignature
+1.2.840.113549.1.9.7 = pkcs9-challengePassword
+1.2.840.113549.1.9.8 = pkcs9-UnstructuredAddress
+1.2.840.113549.1.9.9 = pkcs9-ExtendedCertificateAttributes
+1.2.840.113549.1.9.13 = pkcs9-SigningDescription
+1.2.840.113549.1.9.14 = pkcs9-ExtensionRequest
+1.2.840.113549.1.9.15 = pkcs9-SMimeCapabilities
+1.2.840.113549.1.9.16 = pkcs9-SMime
+1.2.840.113549.1.9.16.1 = pkcs9-SMime-ct
+1.2.840.113549.1.9.16.1.2 = pkcs9-ct-AuthData
+1.2.840.113549.1.9.16.1.4 = pkcs9-ct-TimestampTokenInfo
+1.2.840.113549.1.9.16.1.9 = pkcs9-ct-CompressedData
+1.2.840.113549.1.9.16.1.23 = pkcs9-ct-AuthEnvelopedData
+1.2.840.113549.1.9.16.1.31 = pkcs9-ct-TimestampedData
+1.2.840.113549.1.9.16.2 = pkcs9-SMime-id-aa
+1.2.840.113549.1.9.16.2.12 = pkcs9-id-aa-SigningCertificate
+1.2.840.113549.1.9.16.2.14 = pkcs9-id-aa-Attributes
+1.2.840.113549.1.9.16.2.47 = pkcs9-id-aa-signingCertificateV2
+1.2.840.113549.1.9.25 = pkcs9-SMime-at
+1.2.840.113549.1.9.25.1 = pkcs9-at-Pkcs15Token
+1.2.840.113549.1.9.25.2 = pkcs9-at-EncryptedPrivateKeyInfo
+1.2.840.113549.1.9.25.3 = pkcs9-at-RandomNonce
+1.2.840.113549.1.9.25.4 = pkcs9-at-SequenceNumber
+1.2.840.113549.1.9.25.5 = pkcs9-at-Pkcs7PDU
+1.2.840.113635.100.6.1.13 = apple-cs-ext-DevId-Application
+1.2.840.113635.100.6.1.14 = apple-cs-ext-DevId-Installer
+1.2.840.113635.100.6.1.18 = apple-cs-ext-DevId-KernelExt
+1.2.840.113635.100.5.1 = apple-cert-policy
+1.2.840.113635.100.4.13 = apple-eku-packageSign
+#1.2.840.113635.100.9.1 = apple-???
+1.3.6 = dod
+1.3.6.1 = dod-Internet
+1.3.6.1.4.1.311 = microsoft
+1.3.6.1.4.1.311.2 = ms-authenticode
+1.3.6.1.4.1.311.2.1 = ms-spc
+1.3.6.1.4.1.311.2.1.1 = Ms-??-2.1
+1.3.6.1.4.1.311.2.1.4 = Ms-SpcIndirectDataContext
+1.3.6.1.4.1.311.2.1.10 = Ms-SpcAgencyInfo
+1.3.6.1.4.1.311.2.1.11 = Ms-SpcStatementType
+1.3.6.1.4.1.311.2.1.12 = Ms-SpcOpusInfo
+1.3.6.1.4.1.311.2.1.14 = Ms-CertReqExtensions
+1.3.6.1.4.1.311.2.1.15 = Ms-SpcPeImageData
+1.3.6.1.4.1.311.2.1.18 = Ms-SpcRawFileData
+1.3.6.1.4.1.311.2.1.19 = Ms-SpcStructuredStorageData
+1.3.6.1.4.1.311.2.1.20 = Ms-SpcJavaClassDataType1
+1.3.6.1.4.1.311.2.1.21 = Ms-SpcIndividualCodeSigning
+1.3.6.1.4.1.311.2.1.22 = Ms-SpcCommericalSigning
+1.3.6.1.4.1.311.2.1.25 = Ms-SpcLinkType2-Aka-CabData
+1.3.6.1.4.1.311.2.1.26 = Ms-SpcMinimalCriterialInfo
+1.3.6.1.4.1.311.2.1.27 = Ms-SpcFinacialCriterialInfo
+1.3.6.1.4.1.311.2.1.28 = Ms-SpcLinkType3
+1.3.6.1.4.1.311.2.1.29 = Ms-SpcHashInfo
+1.3.6.1.4.1.311.2.1.30 = Ms-SpcSipInfo
+1.3.6.1.4.1.311.2.2 = Ms-Ctl-CAs
+1.3.6.1.4.1.311.2.2.1 = Ms-TrustedCodeSigningCAList
+1.3.6.1.4.1.311.2.2.2 = Ms-TrustedClientAuthCAList
+1.3.6.1.4.1.311.2.2.3 = Ms-TrustedServerAuthCAList
+1.3.6.1.4.1.311.2.3.1 = Ms-SpcPeImagePageHashesV1
+1.3.6.1.4.1.311.2.3.2 = Ms-SpcPeImagePageHashesV2
+1.3.6.1.4.1.311.2.4 = Ms-??-.2.4
+1.3.6.1.4.1.311.2.4.1 = Ms-SpcNestedSignature
+1.3.6.1.4.1.311.3 = ms-timestamping
+1.3.6.1.4.1.311.3.2.1 = Ms-SpcTimeStampRequest
+1.3.6.1.4.1.311.3.3.1 = Ms-CounterSign
+1.3.6.1.4.1.311.3.3.2 = Ms-?-3.2
+1.3.6.1.4.1.311.10.1 = Ms-CertTrustList
+1.3.6.1.4.1.311.10.1.1 = Ms-SortedCertTrustList
+1.3.6.1.4.1.311.10.2 = Ms-NextUpdateLocation
+1.3.6.1.4.1.311.10.3 = Ms-EKU
+1.3.6.1.4.1.311.10.3.1 = Ms-kp-CertTrustListSigning
+1.3.6.1.4.1.311.10.3.2 = Ms-kp-TimeStampSigning
+1.3.6.1.4.1.311.10.3.3 = Ms-ServerGatedCrypto
+1.3.6.1.4.1.311.10.3.3.1 = Ms-Serialized
+1.3.6.1.4.1.311.10.3.4 = Ms-EncryptedFileSystem
+1.3.6.1.4.1.311.10.3.4.1 = Ms-EncryptedFileSystem-Recovery
+1.3.6.1.4.1.311.10.3.5 = Ms-WhqlCrypto
+1.3.6.1.4.1.311.10.3.6 = Ms-Nt5Crypto
+1.3.6.1.4.1.311.10.3.7 = Ms-OemWhqlCrypto
+1.3.6.1.4.1.311.10.3.8 = Ms-EmbeddedNtCrypto
+1.3.6.1.4.1.311.10.3.9 = Ms-RootListSigner
+1.3.6.1.4.1.311.10.3.10 = Ms-QualifiedSubordination
+1.3.6.1.4.1.311.10.3.11 = Ms-KeyRecovery
+1.3.6.1.4.1.311.10.3.12 = Ms-DocumentSigning
+1.3.6.1.4.1.311.10.3.13 = Ms-LifetimeSigning
+1.3.6.1.4.1.311.10.5.1 = Ms-Drm
+1.3.6.1.4.1.311.10.5.2 = Ms-DrmIndividualization
+1.3.6.1.4.1.311.10.9.1 = Ms-CrossCertDistPoints
+1.3.6.1.4.1.311.12 = Ms-Catalog
+1.3.6.1.4.1.311.12.1 = Ms-Catalog-dot-1
+1.3.6.1.4.1.311.12.1.1 = Ms-CatalogList
+1.3.6.1.4.1.311.12.1.2 = Ms-CatalogListMember
+1.3.6.1.4.1.311.12.2 = Ms-Catalog-dot-2
+1.3.6.1.4.1.311.12.2.1 = Ms-CatNameValue
+1.3.6.1.4.1.311.12.2.2 = Ms-CatMemberInfo
+1.3.6.1.4.1.311.15 = Ms-Java
+1.3.6.1.4.1.311.16 = Ms-Outlook-Exchange
+1.3.6.1.4.1.311.17 = Ms-PKCS12-attribute
+1.3.6.1.4.1.311.17.1 = Ms-PKCS12-LocalMachineKeyset
+1.3.6.1.4.1.311.20.1 = Ms-AutoEnrollCtlUsage
+1.3.6.1.4.1.311.20.2 = Ms-EnrollCerttypeExtension
+1.3.6.1.4.1.311.21 = Ms-CertSrv-Infrastructure
+1.3.6.1.4.1.311.21.1 = Ms-CaKeyCertIndexPair
+1.3.6.1.4.1.311.21.2 = Ms-CertSrvPreviousCertHash
+1.3.6.1.4.1.311.21.3 = Ms-CrlVirtualBase
+1.3.6.1.4.1.311.21.4 = Ms-CrlNextPublish
+1.3.6.1.4.1.311.21.6 = Ms-KeyRecovery
+1.3.6.1.4.1.311.21.7 = Ms-CertificateTemplate
+1.3.6.1.4.1.311.21.9 = Ms-DummySigner
+1.3.6.1.4.1.311.40 = Ms-Fonts
+1.3.6.1.4.1.311.41 = Ms-License-and-Registration
+1.3.6.1.4.1.311.42 = Ms-CorpPKI
+1.3.6.1.4.1.311.60 = Ms-ev
+1.3.6.1.4.1.311.60.1 = Ms-ev-rootProgram
+1.3.6.1.4.1.311.60.1.1 = Ms-ev-rootProgramFlags
+1.3.6.1.4.1.311.60.2 = Ms-ev-2
+1.3.6.1.4.1.311.60.2.1 = Ms-ev-2-1
+1.3.6.1.4.1.311.60.2.1.1 = Ms-jurisdictionOfIncorporationLocalityName
+1.3.6.1.4.1.311.60.2.1.2 = Ms-jurisdictionOfIncorporationStateOrProvinceNm
+1.3.6.1.4.1.311.60.2.1.3 = Ms-jurisdictionOfIncorporationCountryName
+1.3.6.1.4.1.311.88 = Ms-CapiCom
+1.3.6.1.5 = security
+1.3.6.1.5.5 = security-Mechanisms
+1.3.6.1.5.5.7 = public-key-infrastructure
+1.3.6.1.5.5.7.1.1 = pkix-AuthorityInfoAccess
+1.3.6.1.5.5.7.1.12 = pkix-LogoType
+1.3.6.1.5.5.7.2.1 = id-qt-CPS
+1.3.6.1.5.5.7.2.2 = id-qt-UNotice
+1.3.6.1.5.5.7.2.3 = id-qt-TextNotice
+1.3.6.1.5.5.7.2.4 = id-qt-ACPS
+1.3.6.1.5.5.7.2.5 = id-qt-ACUNotice
+1.3.6.1.5.5.7.3.1 = id-kp-ServerAuth
+1.3.6.1.5.5.7.3.2 = id-kp-ClientAuth
+1.3.6.1.5.5.7.3.3 = id-kp-CodeSigning
+1.3.6.1.5.5.7.3.4 = id-kp-EmailProtection
+1.3.6.1.5.5.7.3.5 = id-kp-IPSecEndSystem
+1.3.6.1.5.5.7.3.6 = id-kp-IPSecTunnel
+1.3.6.1.5.5.7.3.7 = id-kp-IPSecUser
+1.3.6.1.5.5.7.3.8 = id-kp-Timestamping
+1.3.6.1.5.5.7.3.9 = id-kp-OCSPSigning
+1.3.6.1.5.5.7.3.10 = id-kp-DVCS
+1.3.6.1.5.5.7.3.11 = id-kp-SBGPCertAAServiceAuth
+1.3.6.1.5.5.7.3.13 = id-kp-EAPOverPPP
+1.3.6.1.5.5.7.3.14 = id-kp-EAPOverLAN
+1.3.14.3 = oiw-ssig
+1.3.14.3.2 = oiw-ssig-algorithms
+1.3.14.3.2.2 = oiw-ssig-md4WithRsa
+1.3.14.3.2.3 = oiw-ssig-md5WithRsa
+1.3.14.3.2.4 = oiw-ssig-md4WithRsaEncryption
+1.3.14.3.2.14 = oiw-ssig-mdc2WithRsaSignature
+1.3.14.3.2.15 = oiw-ssig-sha0WithRsaSignature
+1.3.14.3.2.18 = oiw-ssig-sha0
+1.3.14.3.2.19 = oiw-ssig-mdc2
+1.3.14.3.2.21 = oiw-ssig-dsaCommonWithSha0
+1.3.14.3.2.24 = oiw-ssig-md2WithRsaSignature
+1.3.14.3.2.25 = oiw-ssig-md5WithRsaSignature
+1.3.14.3.2.26 = oiw-ssig-sha1
+1.3.14.3.2.29 = oiw-ssig-sha1WithRsaEncryption
+2 = joint-iso/itu-t
+2.1 = asn.1
+2.5 = x500
+2.5.4 = x500-attribute-types
+2.5.4.3 = x500-CommonName
+2.5.4.6 = x500-CountryName
+2.5.4.7 = x500-LocalityName
+2.5.4.8 = x500-StatOrProvinceName
+2.5.4.10 = x500-OrganizationName
+2.5.4.11 = x500-OrganizationUnitName
+2.5.29 = id-ce
+2.5.29.1 = id-ce-AuthorityKeyIdentifier-Deprecated
+2.5.29.2 = id-ce-KeyAttributes-Deprecated
+2.5.29.3 = id-ce-CertificatePolicies-Deprecated
+2.5.29.4 = id-ce-KeyUsageRestriction-Deprecated
+2.5.29.7 = id-ce-SubjectAltName-Deprecated
+2.5.29.8 = id-ce-IssuerAltName-Deprecated
+2.5.29.14 = id-ce-SubjectKeyIdentifier
+2.5.29.15 = id-ce-KeyUsage
+2.5.29.16 = id-ce-PrivateKeyUsagePeriod
+2.5.29.17 = id-ce-SubjectAltName
+2.5.29.18 = id-ce-issuerAltName
+2.5.29.19 = id-ce-BasicConstraints
+2.5.29.25 = id-ce-CrlDistributionPoints
+2.5.29.29 = id-ce-CertificateIssuer
+2.5.29.30 = id-ce-NameConstraints
+2.5.29.31 = id-ce-CrlDistributionPoints
+2.5.29.32 = id-ce-CertificatePolicies
+2.5.29.32.0 = id-ce-cp-anyPolicy
+2.5.29.35 = id-ce-AuthorityKeyIdentifier
+2.5.29.36 = id-ce-PolicyConstraints
+2.5.29.37 = id-ce-ExtKeyUsage
+2.16 = by-country
+2.16.840 = usa
+2.16.840.1 = usa-company-arc
+2.16.840.1.101 = usa-goverment
+2.16.840.1.101.3 = usa-computer-security-register
+2.16.840.1.101.3.4 = nist-algorithms
+2.16.840.1.101.3.4.1 = nist-aes-algorithms
+2.16.840.1.101.3.4.2 = nist-hash-algorithms
+2.16.840.1.101.3.4.2.1 = nist-sha256
+2.16.840.1.101.3.4.2.2 = nist-sha384
+2.16.840.1.101.3.4.2.3 = nist-sha512
+2.16.840.1.101.3.4.2.4 = nist-sha224
+2.16.840.1.101.3.4.2.5 = nist-sha512-224
+2.16.840.1.101.3.4.2.6 = nist-sha512-256
+2.16.840.1.101.3.4.2.7 = nist-sha3-224
+2.16.840.1.101.3.4.2.8 = nist-sha3-256
+2.16.840.1.101.3.4.2.9 = nist-sha3-384
+2.16.840.1.101.3.4.2.10 = nist-sha3-512
+2.16.840.1.101.3.4.2.11 = nist-shake128
+2.16.840.1.101.3.4.2.12 = nist-shake256
+2.16.840.1.101.3.4.3 = nist-sigAlgs
+2.16.840.1.101.3.4.3.1 = nist-dsa-with-sha224
+2.16.840.1.101.3.4.3.2 = nist-dsa-with-sha256
+2.16.840.1.101.3.4.3.3 = nist-dsa-with-sha384
+2.16.840.1.101.3.4.3.4 = nist-dsa-with-sha512
+2.16.840.1.101.3.4.3.5 = nist-id-dsa-with-sha224
+2.16.840.1.101.3.4.3.6 = nist-id-dsa-with-sha256
+2.16.840.1.101.3.4.3.7 = nist-id-dsa-with-sha384
+2.16.840.1.101.3.4.3.8 = nist-id-dsa-with-sha512
+2.16.840.1.101.3.4.3.9 = nist-id-ecdsa-with-sha3-224
+2.16.840.1.101.3.4.3.10 = nist-id-ecdsa-with-sha3-256
+2.16.840.1.101.3.4.3.11 = nist-id-ecdsa-with-sha3-384
+2.16.840.1.101.3.4.3.12 = nist-id-ecdsa-with-sha3-512
+2.16.840.1.101.3.4.3.13 = nist-id-rsassa-pkcs1-v1-5-with-sha3-224
+2.16.840.1.101.3.4.3.14 = nist-id-rsassa-pkcs1-v1-5-with-sha3-256
+2.16.840.1.101.3.4.3.15 = nist-id-rsassa-pkcs1-v1-5-with-sha3-384
+2.16.840.1.101.3.4.3.16 = nist-id-rsassa-pkcs1-v1-5-with-sha3-512
+2.16.840.1.113730 = netscape
+2.16.840.1.113730.1.1 = netscape-cert-type
+2.16.840.1.113730.1.2 = netscape-base-url
+2.16.840.1.113730.1.3 = netscape-revocation-url
+2.16.840.1.113730.1.4 = netscape-ca-revocation-url
+2.16.840.1.113730.1.7 = netscape-cert-renewal-url
+2.16.840.1.113730.1.8 = netscape-ca-policy-url
+2.16.840.1.113730.1.9 = netscape-HomePage-url
+2.16.840.1.113730.1.10 = netscape-EntityLogo
+2.16.840.1.113730.1.11 = netscape-UserPicture
+2.16.840.1.113730.1.12 = netscape-ssl-server-name
+2.16.840.1.113730.1.13 = netscape-comment
+2.16.840.1.113730.4.1 = netscape-eku-serverGatedCrypto
+2.16.840.1.113733 = verisign
+2.16.840.1.113733.1 = verisign-pki
+2.16.840.1.113733.1.6 = verisign-pki-extensions
+2.16.840.1.113733.1.6.7 = verisign-pki-ext-RolloverID
+2.16.840.1.113733.1.7 = verisign-pki-policies
+2.16.840.1.113733.1.7.9 = verisign-pki-policy-9
+2.16.840.1.113733.1.7.21 = verisign-pki-policy-21
+2.16.840.1.113733.1.7.23 = verisign-pki-policy-vtn-cp
+2.16.840.1.113733.1.7.23.1 = verisign-pki-policy-vtn-cp-class1
+2.16.840.1.113733.1.7.23.2 = verisign-pki-policy-vtn-cp-class2
+2.16.840.1.113733.1.7.23.3 = verisign-pki-policy-vtn-cp-class3
+2.16.840.1.113733.1.7.23.4 = verisign-pki-policy-vtn-cp-class4
+2.16.840.1.113733.1.7.23.6 = verisign-pki-policy-vtn-cp-6
+2.16.840.1.113733.1.7.46 = verisign-pki-policy-cis
+2.16.840.1.113733.1.7.46.1 = verisign-pki-policy-cis-type1
+2.16.840.1.113733.1.7.46.2 = verisign-pki-policy-cis-type2
+2.16.840.1.113733.1.7.48 = verisign-pki-policy-thawte
+2.16.840.1.113733.1.7.48.1 = verisign-pki-policy-thawte-cps-1
+# check the following one...
+2.16.840.1.113733.1.8.1 = verisign-pki-eku-IssStrongCrypto
+
diff --git a/src/VBox/Runtime/common/asn1/oiddb2c.cpp b/src/VBox/Runtime/common/asn1/oiddb2c.cpp
new file mode 100644
index 00000000..4fcb92cf
--- /dev/null
+++ b/src/VBox/Runtime/common/asn1/oiddb2c.cpp
@@ -0,0 +1,635 @@
+/* $Id: oiddb2c.cpp $ */
+/** @file
+ * IPRT - OID text database to C converter.
+ *
+ * The output is used by asn1-dump.cpp.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/types.h>
+#include <iprt/ctype.h>
+#include <iprt/stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/*
+ * Include the string table code.
+ */
+#define BLDPROG_STRTAB_MAX_STRLEN 48
+#define BLDPROG_STRTAB_WITH_COMPRESSION
+#define BLDPROG_STRTAB_PURE_ASCII
+#define BLDPROG_STRTAB_WITH_CAMEL_WORDS
+#include <iprt/bldprog-strtab-template.cpp.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define OID2C_MAX_COMP_VALUE _1G
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Raw OID tree node.
+ *
+ * This is what we produce while loading OID input files.
+ */
+typedef struct RAWOIDNODE
+{
+ /** The component value. */
+ uint32_t uKey;
+ /** Number of children. */
+ uint32_t cChildren;
+ /** Pointer to the children pointers (sorted by key). */
+ struct RAWOIDNODE **papChildren;
+ /** Pointer to the parent. */
+ struct RAWOIDNODE *pParent;
+ /** The string table entry for this node. */
+ BLDPROGSTRING StrTabEntry;
+ /** The table index of the children. */
+ uint32_t idxChildren;
+ /** Set if we've got one or more children with large keys. */
+ bool fChildrenInBigTable;
+} RAWOIDNODE;
+/** Pointer to a raw OID node. */
+typedef RAWOIDNODE *PRAWOIDNODE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** What to prefix errors with. */
+static const char *g_pszProgName = "oiddb2c";
+
+/** The OID tree. */
+static PRAWOIDNODE g_pOidRoot = NULL;
+/** Number of nodes in the OID tree. */
+static uint32_t g_cOidNodes = 0;
+/** Number of nodes in the OID tree that has strings (for the string table). */
+static uint32_t g_cOidNodesWithStrings = 0;
+/** Max number of children of a node in the OID tree. */
+static uint32_t g_cMaxOidChildren = 0;
+/** Number of nodes which key fits within 6-bits. */
+static uint32_t g_cOidNodiesWith6bitKeys = 0;
+
+
+static RTEXITCODE error(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ fprintf(stderr, "%s: error: ", g_pszProgName);
+ vfprintf(stderr, pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_FAILURE;
+}
+
+static RTEXITCODE warning(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ fprintf(stderr, "%s: warning: ", g_pszProgName);
+ vfprintf(stderr, pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_FAILURE;
+}
+
+
+static void writeDottedOidForNode(PRAWOIDNODE pCurNode, FILE *pOut)
+{
+ if (pCurNode->pParent)
+ {
+ writeDottedOidForNode(pCurNode->pParent, pOut);
+ fprintf(pOut, ".%u", pCurNode->uKey);
+ }
+ else
+ fprintf(pOut, "%u", pCurNode->uKey);
+}
+
+
+static void writeOidTree(PRAWOIDNODE pCurNode, FILE *pOut, bool fBigTable, PBLDPROGSTRTAB pStrTab)
+{
+ /*
+ * First we produce the entries for our children.
+ */
+ if (pCurNode->fChildrenInBigTable == fBigTable)
+ {
+ for (unsigned i = 0; i < pCurNode->cChildren; i++)
+ {
+ PRAWOIDNODE pChild = pCurNode->papChildren[i];
+ fprintf(pOut, " { %*u, %2u, %u, %2u, %4u, %#06x }, /* ",
+ fBigTable ? 7 : 2,
+ pChild->uKey,
+ (unsigned)pChild->StrTabEntry.cchString,
+ pChild->fChildrenInBigTable,
+ pChild->cChildren,
+ pChild->idxChildren,
+ pChild->StrTabEntry.offStrTab);
+ writeDottedOidForNode(pChild, pOut);
+ if (pChild->StrTabEntry.pszString)
+ {
+ fputs(" = \"", pOut);
+ BldProgStrTab_PrintCStringLitteral(pStrTab, &pChild->StrTabEntry, pOut);
+ fputs("\" */\n", pOut);
+ }
+ else
+ fputs(" */\n", pOut);
+ }
+ }
+
+ /*
+ * Then we decend and let our children do the same.
+ */
+ for (unsigned i = 0; i < pCurNode->cChildren; i++)
+ writeOidTree(pCurNode->papChildren[i], pOut, fBigTable, pStrTab);
+}
+
+
+static uint32_t prepareOidTreeForWriting(PRAWOIDNODE pCurNode, uint32_t idxCur, bool fBigTable)
+{
+ if (pCurNode->fChildrenInBigTable == fBigTable)
+ {
+ pCurNode->idxChildren = pCurNode->cChildren ? idxCur : 0;
+ idxCur += pCurNode->cChildren;
+ }
+
+ for (unsigned i = 0; i < pCurNode->cChildren; i++)
+ idxCur = prepareOidTreeForWriting(pCurNode->papChildren[i], idxCur, fBigTable);
+
+ return idxCur;
+}
+
+
+static void addStringFromOidTree(PRAWOIDNODE pCurNode, PBLDPROGSTRTAB pStrTab)
+{
+ /* Do self. */
+ if (pCurNode->StrTabEntry.pszString)
+ BldProgStrTab_AddString(pStrTab, &pCurNode->StrTabEntry);
+
+ /* Recurse into children. */
+ unsigned i = pCurNode->cChildren;
+ while (i-- > 0)
+ addStringFromOidTree(pCurNode->papChildren[i], pStrTab);
+}
+
+
+static bool isNiceAsciiString(const char *psz)
+{
+ unsigned uch;
+ while ((uch = *psz) != '\0')
+ if ( !(uch & 0x80)
+ && ( uch >= 0x20
+ || uch == '\t') )
+ psz++;
+ else
+ return false;
+ return true;
+}
+
+
+static RTEXITCODE addOidToTree(uint32_t const *pauComponents, unsigned cComponents, const char *pszName,
+ const char *pszFile, unsigned iLineNo)
+{
+ /*
+ * Check preconditions.
+ */
+ size_t cchName = strlen(pszName);
+ if (cchName == '\0')
+ return warning("%s(%d): Empty OID name!\n", pszFile, iLineNo);
+ if (cchName >= BLDPROG_STRTAB_MAX_STRLEN)
+ return warning("%s(%d): OID name is too long (%u)!\n", pszFile, iLineNo, (unsigned)cchName);
+ if (cComponents == 0)
+ return warning("%s(%d): 'Description' without valid OID preceeding it!\n", pszFile, iLineNo);
+ if (!isNiceAsciiString(pszName))
+ return warning("%s(%d): Contains unwanted characters!\n", pszFile, iLineNo);
+
+ /*
+ * Make sure we've got a root node (it has no actual OID componet value,
+ * it's just a place to put the top level children).
+ */
+ if (!g_pOidRoot)
+ {
+ g_pOidRoot = (PRAWOIDNODE)calloc(sizeof(*g_pOidRoot), 1);
+ if (!g_pOidRoot)
+ return error("Out of memory!\n");
+ }
+
+ /*
+ * Decend into the tree, adding any missing nodes as we go along.
+ * We'll end up with the node which is being named.
+ */
+ PRAWOIDNODE pCur = g_pOidRoot;
+ while (cComponents-- > 0)
+ {
+ uint32_t const uKey = *pauComponents++;
+ uint32_t i = pCur->cChildren;
+ while ( i > 0
+ && pCur->papChildren[i - 1]->uKey >= uKey)
+ i--;
+ if ( i < pCur->cChildren
+ && pCur->papChildren[i]->uKey == uKey)
+ pCur = pCur->papChildren[i];
+ else
+ {
+ /* Resize the child pointer array? */
+ if ((pCur->cChildren % 16) == 0)
+ {
+ void *pvNew = realloc(pCur->papChildren, sizeof(pCur->papChildren[0]) * (pCur->cChildren + 16));
+ if (!pvNew)
+ return error("Out of memory!\n");
+ pCur->papChildren = (PRAWOIDNODE *)pvNew;
+ }
+
+ /* Allocate and initialize the node. */
+ PRAWOIDNODE pNew = (PRAWOIDNODE)malloc(sizeof(*pNew));
+ if (!pNew)
+ return error("Out of memory!\n");
+ pNew->uKey = uKey;
+ pNew->pParent = pCur;
+ pNew->papChildren = NULL;
+ pNew->cChildren = 0;
+ pNew->fChildrenInBigTable = false;
+ memset(&pNew->StrTabEntry, 0, sizeof(pNew->StrTabEntry));
+
+ /* Insert it. */
+ if (i < pCur->cChildren)
+ memmove(&pCur->papChildren[i + 1], &pCur->papChildren[i], (pCur->cChildren - i) * sizeof(pCur->papChildren[0]));
+ pCur->papChildren[i] = pNew;
+ pCur->cChildren++;
+
+ if (pCur->cChildren > g_cMaxOidChildren)
+ g_cMaxOidChildren = pCur->cChildren;
+ g_cOidNodes++;
+ if (uKey < 64)
+ g_cOidNodiesWith6bitKeys++;
+ else
+ {
+ pCur->fChildrenInBigTable = true;
+ if (!pCur->pParent)
+ return error("Invalid OID! Top level componet value is out of range: %u (max 2)\n", uKey);
+ }
+
+ /* Decend (could optimize insertion of the remaining nodes, but
+ too much work for very little gain). */
+ pCur = pNew;
+ }
+ }
+
+ /*
+ * Update the node.
+ */
+ if (!pCur->StrTabEntry.pszString)
+ {
+ pCur->StrTabEntry.pszString = (char *)malloc(cchName + 1);
+ if (pCur->StrTabEntry.pszString)
+ memcpy(pCur->StrTabEntry.pszString, pszName, cchName + 1);
+ else
+ return error("Out of memory!\n");
+ pCur->StrTabEntry.cchString = cchName;
+ if (cchName >= 64)
+ pCur->fChildrenInBigTable = true;
+ g_cOidNodesWithStrings++;
+ }
+ /* Ignore duplicates, but warn if different name. */
+ else if ( pCur->StrTabEntry.cchString != cchName
+ || strcmp(pszName, pCur->StrTabEntry.pszString) != 0)
+ warning("%s(%d): Duplicate OID, name differs: '%s' vs '%s'\n", pszFile, iLineNo, pCur->StrTabEntry.pszString, pszName);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+static RTEXITCODE parseOid(uint32_t *pauComponents, unsigned *pcComponents, unsigned cMaxComponents, char const *pszOid,
+ const char *pszFile, unsigned iLine)
+{
+ const char *pszInput = pszOid;
+ unsigned i = 0;
+ char ch;
+ for (;;)
+ {
+ /*
+ * Parse the value.
+ */
+ unsigned uValue = 0;
+ if (RT_C_IS_DIGIT((ch = *pszOid)))
+ {
+ do
+ {
+ uValue *= 10;
+ uValue += ch - '0';
+ if (uValue < OID2C_MAX_COMP_VALUE)
+ pszOid++;
+ else
+ return warning("%s(%d): Component %u in OID attribute value '%s' is out side the supported!\n",
+ pszFile, iLine, i, pszInput);
+ } while (RT_C_IS_DIGIT((ch = *pszOid)));
+ if ( ch == '\0'
+ || ch == '.'
+ || RT_C_IS_BLANK(ch))
+ {
+ if (i < cMaxComponents)
+ {
+ pauComponents[i] = uValue;
+ i++;
+ if (ch != '\0')
+ pszOid++;
+ else
+ {
+ *pcComponents = i;
+ return RTEXITCODE_SUCCESS;
+ }
+ }
+ else
+ return warning("%s(%d): Too many OID components in '%s'!\n", pszFile, iLine, pszInput);
+ }
+ else
+ return warning("%s(%d): Invalid OID attribute value '%s' (ch=%c)!\n", pszFile, iLine, pszInput, ch);
+ }
+ else
+ return warning("%s(%d): Invalid OID attribute value '%s' (ch=%c)!\n", pszFile, iLine, pszInput, ch);
+ }
+}
+
+
+static RTEXITCODE loadOidFile(FILE *pIn, const char *pszFile)
+{
+ /*
+ * We share the format used by dumpasn1.cfg, except that we accept
+ * dotted OIDs.
+ *
+ * An OID entry starts with a 'OID = <space or dot separated OID>'.
+ * It is usually followed by an 'Comment = ', which we ignore, and a
+ * 'Description = <name>' which we keep. We save the entry once we
+ * see the description attribute.
+ */
+ unsigned cOidComponents = 0;
+ uint32_t auOidComponents[16];
+ unsigned iLineNo = 0;
+ char szLine[16384];
+ char *pszLine;
+ szLine[sizeof(szLine) - 1] = '\0';
+ while ((pszLine = fgets(szLine, sizeof(szLine) - 1, pIn)) != NULL)
+ {
+ iLineNo++;
+
+ /* Strip leading spaces.*/
+ char ch;
+ while (RT_C_IS_SPACE((ch = *pszLine)) )
+ pszLine++;
+
+ /* We only care about lines starting with 'OID =', 'Description =' or
+ a numbered OID. */
+ if ( ch == 'O' || ch == 'o'
+ || ch == 'D' || ch == 'd'
+ || ch == '0' || ch == '1' || ch == '2')
+ {
+ /* Right strip the line. */
+ size_t cchLine = strlen(pszLine);
+ while (cchLine > 0 && RT_C_IS_SPACE(pszLine[cchLine - 1]))
+ cchLine--;
+ pszLine[cchLine] = '\0';
+
+ /* Separate the attribute name from the value. */
+ char *pszValue = (char *)memchr(pszLine, '=', cchLine);
+ if (pszValue)
+ {
+ size_t cchName = pszValue - pszLine;
+
+ /* Right strip the name. */
+ while (cchName > 0 && RT_C_IS_SPACE(pszLine[cchName - 1]))
+ cchName--;
+ pszLine[cchName] = '\0';
+
+ /* Left strip the value. */
+ do
+ pszValue++;
+ while (RT_C_IS_SPACE(*pszValue));
+
+ /* Attribute switch */
+ if ( cchName == 3
+ && (pszLine[0] == 'O' || pszLine[0] == 'o')
+ && (pszLine[1] == 'I' || pszLine[1] == 'i')
+ && (pszLine[2] == 'D' || pszLine[2] == 'd'))
+ {
+ cOidComponents = 0;
+ parseOid(auOidComponents, &cOidComponents, RT_ELEMENTS(auOidComponents), pszValue, pszFile, iLineNo);
+ }
+ else if ( cchName == 11
+ && (pszLine[0] == 'D' || pszLine[0] == 'd')
+ && (pszLine[1] == 'e' || pszLine[1] == 'E')
+ && (pszLine[2] == 's' || pszLine[2] == 'S')
+ && (pszLine[3] == 'c' || pszLine[3] == 'C')
+ && (pszLine[4] == 'r' || pszLine[4] == 'R')
+ && (pszLine[5] == 'i' || pszLine[5] == 'I')
+ && (pszLine[6] == 'p' || pszLine[6] == 'P')
+ && (pszLine[7] == 't' || pszLine[7] == 'T')
+ && (pszLine[8] == 'i' || pszLine[8] == 'I')
+ && (pszLine[9] == 'o' || pszLine[9] == 'O')
+ && (pszLine[10] == 'n' || pszLine[10] == 'N'))
+ {
+ if ( addOidToTree(auOidComponents, cOidComponents, pszValue, pszFile, iLineNo)
+ != RTEXITCODE_SUCCESS)
+ return RTEXITCODE_FAILURE;
+ cOidComponents = 0;
+ }
+ else
+ {
+ /* <OID> = <Value> */
+ cOidComponents = 0;
+ if ( parseOid(auOidComponents, &cOidComponents, RT_ELEMENTS(auOidComponents), pszLine, pszLine, iLineNo)
+ == RTEXITCODE_SUCCESS)
+ {
+ if ( addOidToTree(auOidComponents, cOidComponents, pszValue, pszFile, iLineNo)
+ != RTEXITCODE_SUCCESS)
+ return RTEXITCODE_FAILURE;
+ }
+ cOidComponents = 0;
+ }
+ }
+ }
+
+ }
+ if (feof(pIn))
+ return RTEXITCODE_SUCCESS;
+ return error("error or something reading '%s'.\n", pszFile);
+}
+
+
+
+static RTEXITCODE usage(FILE *pOut, const char *argv0, RTEXITCODE rcExit)
+{
+ fprintf(pOut, "usage: %s <out-file.c> <oid-file> [oid-file2 [...]]\n", argv0);
+ return rcExit;
+}
+
+int main(int argc, char **argv)
+{
+ /*
+ * Process arguments and input files.
+ */
+ bool fVerbose = false;
+ unsigned cInFiles = 0;
+ const char *pszOutFile = NULL;
+ for (int i = 1; i < argc; i++)
+ {
+ const char *pszFile = NULL;
+ if (argv[i][0] != '-')
+ pszFile = argv[i];
+ else if (!strcmp(argv[i], "-"))
+ pszFile = argv[i];
+ else
+ return usage(stderr, argv[0], RTEXITCODE_SYNTAX);
+
+ if (!pszOutFile)
+ pszOutFile = pszFile;
+ else
+ {
+ cInFiles++;
+ FILE *pInFile = fopen(pszFile, "r");
+ if (!pInFile)
+ return error("opening '%s' for reading.\n", pszFile);
+ RTEXITCODE rcExit = loadOidFile(pInFile, pszFile);
+ fclose(pInFile);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+ }
+ }
+
+ /*
+ * Check that the user specified at least one input and an output file.
+ */
+ if (!pszOutFile)
+ return error("No output file specified specified!\n");
+ if (!cInFiles)
+ return error("No input files specified!\n");
+ if (!g_cOidNodes)
+ return error("No OID found!\n");
+ if (fVerbose)
+ printf("debug: %u nodes with strings; %u nodes without strings; %u nodes in total;\n"
+ "debug: max %u children; %u nodes with 6-bit keys (%u others)\n",
+ g_cOidNodesWithStrings, g_cOidNodes - g_cOidNodesWithStrings, g_cOidNodes,
+ g_cMaxOidChildren, g_cOidNodiesWith6bitKeys, g_cOidNodes - g_cOidNodiesWith6bitKeys);
+
+ /*
+ * Compile the string table.
+ */
+ BLDPROGSTRTAB StrTab;
+ if (!BldProgStrTab_Init(&StrTab, g_cOidNodesWithStrings))
+ return error("Out of memory!\n");
+
+ addStringFromOidTree(g_pOidRoot, &StrTab);
+
+ if (!BldProgStrTab_CompileIt(&StrTab, fVerbose))
+ return error("BldProgStrTab_CompileIt failed!\n");
+
+ /*
+ * Open the output file and write out the stuff.
+ */
+ FILE *pOut;
+ if (!strcmp(pszOutFile, "-"))
+ pOut = stdout;
+ else
+ pOut = fopen(pszOutFile, "w");
+ if (!pOut)
+ return error("opening '%s' for writing.\n", pszOutFile);
+
+ /* Write the string table. */
+ BldProgStrTab_WriteStringTable(&StrTab, pOut, "static ", "g_", "OidDbStrTab");
+
+ prepareOidTreeForWriting(g_pOidRoot, 0, false /*fBigTable*/);
+ prepareOidTreeForWriting(g_pOidRoot, 0, true /*fBigTable*/);
+
+ fprintf(pOut,
+ "\n"
+ "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n"
+ "# pragma pack(2)\n"
+ "#endif\n"
+ "typedef struct RTOIDENTRYSMALL\n"
+ "{\n"
+ " uint32_t uKey : 6;\n"
+ " uint32_t cchString : 6;\n"
+ " uint32_t fBigTable : 1;\n"
+ " uint32_t cChildren : 7;\n"
+ " uint32_t idxChildren : 12;\n"
+ " uint16_t offString;\n"
+ "} RTOIDENTRYSMALL;\n"
+ "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n"
+ "# pragma pack()\n"
+ "AssertCompileSize(RTOIDENTRYSMALL, 6);\n"
+ "#endif\n"
+ "typedef RTOIDENTRYSMALL const *PCRTOIDENTRYSMALL;\n"
+ "\n"
+ "static const RTOIDENTRYSMALL g_aSmallOidTable[] = \n{\n");
+ writeOidTree(g_pOidRoot, pOut, false /*fBigTable*/, &StrTab);
+ fprintf(pOut, "};\n");
+
+ fprintf(pOut,
+ "\n"
+ "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n"
+ "# pragma pack(2)\n"
+ "#endif\n"
+ "typedef struct RTOIDENTRYBIG\n"
+ "{\n"
+ " uint32_t uKey;\n"
+ " uint8_t cchString;\n"
+ " uint8_t fBigTable : 1;\n"
+ " uint8_t cChildren : 7;\n"
+ " uint16_t idxChildren;\n"
+ " uint16_t offString;\n"
+ "} RTOIDENTRYBIG;\n"
+ "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n"
+ "# pragma pack()\n"
+ "AssertCompileSize(RTOIDENTRYBIG, 10);\n"
+ "#endif\n"
+ "typedef RTOIDENTRYBIG const *PCRTOIDENTRYBIG;\n"
+ "\n"
+ "static const RTOIDENTRYBIG g_aBigOidTable[] = \n{\n");
+ writeOidTree(g_pOidRoot, pOut, true /*fBigTable*/, &StrTab);
+ fprintf(pOut, "};\n");
+
+ /* Carefully close the output file. */
+ if (ferror(pOut))
+ return error("problem writing '%s'!\n", pszOutFile);
+ if (fclose(pOut) != 0)
+ return error("closing '%s' after writing it.\n", pszOutFile);
+
+ return RTEXITCODE_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/checksum/Makefile.kup b/src/VBox/Runtime/common/checksum/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/Makefile.kup
diff --git a/src/VBox/Runtime/common/checksum/RTSha1Digest.cpp b/src/VBox/Runtime/common/checksum/RTSha1Digest.cpp
new file mode 100644
index 00000000..ad05f1b0
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/RTSha1Digest.cpp
@@ -0,0 +1,201 @@
+/* $Id: RTSha1Digest.cpp $ */
+/** @file
+ * IPRT - SHA1 digest creation
+ *
+ * @todo Replace this with generic RTCrDigest based implementation. Too much
+ * stupid code duplication.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/sha.h>
+
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/file.h>
+
+
+RTR3DECL(int) RTSha1Digest(void* pvBuf, size_t cbBuf, char **ppszDigest, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
+{
+ /* Validate input */
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszDigest, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ *ppszDigest = NULL;
+
+ /* Initialize the hash context. */
+ RTSHA1CONTEXT Ctx;
+ RTSha1Init(&Ctx);
+
+ /* Buffer size for progress callback */
+ double rdMulti = 100.0 / (cbBuf ? (double)cbBuf : 1.0);
+
+ /* Working buffer */
+ char *pvTmp = (char*)pvBuf;
+
+ /* Process the memory in blocks */
+ size_t cbReadTotal = 0;
+ for (;;)
+ {
+ size_t cbRead = RT_MIN(cbBuf - cbReadTotal, _1M);
+ RTSha1Update(&Ctx, pvTmp, cbRead);
+ cbReadTotal += cbRead;
+ pvTmp += cbRead;
+
+ /* Call the progress callback if one is defined */
+ if (pfnProgressCallback)
+ {
+ rc = pfnProgressCallback((unsigned)((double)cbReadTotal * rdMulti), pvUser);
+ if (RT_FAILURE(rc))
+ break; /* canceled */
+ }
+ /* Finished? */
+ if (cbReadTotal == cbBuf)
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* Finally calculate & format the SHA1 sum */
+ uint8_t abHash[RTSHA1_HASH_SIZE];
+ RTSha1Final(&Ctx, abHash);
+
+ char *pszDigest;
+ rc = RTStrAllocEx(&pszDigest, RTSHA1_DIGEST_LEN + 1);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSha1ToString(abHash, pszDigest, RTSHA1_DIGEST_LEN + 1);
+ if (RT_SUCCESS(rc))
+ *ppszDigest = pszDigest;
+ else
+ RTStrFree(pszDigest);
+ }
+ }
+
+ return rc;
+}
+
+RTR3DECL(int) RTSha1DigestFromFile(const char *pszFile, char **ppszDigest, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
+{
+ /* Validate input */
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszDigest, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_PARAMETER);
+
+ *ppszDigest = NULL;
+
+ /* Open the file to calculate a SHA1 sum of */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Fetch the file size. Only needed if there is a progress callback. */
+ double rdMulti = 0.0;
+ if (pfnProgressCallback)
+ {
+ uint64_t cbFile;
+ rc = RTFileQuerySize(hFile, &cbFile);
+ if (RT_FAILURE(rc))
+ {
+ RTFileClose(hFile);
+ return rc;
+ }
+ rdMulti = 100.0 / (cbFile ? (double)cbFile : 1.0);
+ }
+
+ /* Allocate a reasonably large buffer, fall back on a tiny one. */
+ void *pvBufFree;
+ size_t cbBuf = _1M;
+ void *pvBuf = pvBufFree = RTMemTmpAlloc(cbBuf);
+ if (!pvBuf)
+ {
+ cbBuf = 0x1000;
+ pvBuf = alloca(cbBuf);
+ }
+
+ /* Initialize the hash context. */
+ RTSHA1CONTEXT Ctx;
+ RTSha1Init(&Ctx);
+
+ /* Read that file in blocks */
+ size_t cbReadTotal = 0;
+ for (;;)
+ {
+ size_t cbRead;
+ rc = RTFileRead(hFile, pvBuf, cbBuf, &cbRead);
+ if (RT_FAILURE(rc) || !cbRead)
+ break;
+ RTSha1Update(&Ctx, pvBuf, cbRead);
+ cbReadTotal += cbRead;
+
+ /* Call the progress callback if one is defined */
+ if (pfnProgressCallback)
+ {
+ rc = pfnProgressCallback((unsigned)((double)cbReadTotal * rdMulti), pvUser);
+ if (RT_FAILURE(rc))
+ break; /* canceled */
+ }
+ }
+ RTMemTmpFree(pvBufFree);
+ RTFileClose(hFile);
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Finally calculate & format the SHA1 sum */
+ uint8_t abHash[RTSHA1_HASH_SIZE];
+ RTSha1Final(&Ctx, abHash);
+
+ char *pszDigest;
+ rc = RTStrAllocEx(&pszDigest, RTSHA1_DIGEST_LEN + 1);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSha1ToString(abHash, pszDigest, RTSHA1_DIGEST_LEN + 1);
+ if (RT_SUCCESS(rc))
+ *ppszDigest = pszDigest;
+ else
+ RTStrFree(pszDigest);
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/checksum/RTSha256Digest.cpp b/src/VBox/Runtime/common/checksum/RTSha256Digest.cpp
new file mode 100644
index 00000000..c6da7e9f
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/RTSha256Digest.cpp
@@ -0,0 +1,201 @@
+/* $Id: RTSha256Digest.cpp $ */
+/** @file
+ * IPRT - SHA256 digest creation
+ *
+ * @todo Replace this with generic RTCrDigest based implementation. Too much
+ * stupid code duplication.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/sha.h>
+
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/file.h>
+
+
+RTR3DECL(int) RTSha256Digest(void* pvBuf, size_t cbBuf, char **ppszDigest, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
+{
+ /* Validate input */
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszDigest, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ *ppszDigest = NULL;
+
+ /* Initialize the hash context. */
+ RTSHA256CONTEXT Ctx;
+ RTSha256Init(&Ctx);
+
+ /* Buffer size for progress callback */
+ double rdMulti = 100.0 / (cbBuf ? (double)cbBuf : 1);
+
+ /* Working buffer */
+ char *pvTmp = (char*)pvBuf;
+
+ /* Process the memory in blocks */
+ size_t cbReadTotal = 0;
+ for (;;)
+ {
+ size_t cbRead = RT_MIN(cbBuf - cbReadTotal, _1M);
+ RTSha256Update(&Ctx, pvTmp, cbRead);
+ cbReadTotal += cbRead;
+ pvTmp += cbRead;
+
+ /* Call the progress callback if one is defined */
+ if (pfnProgressCallback)
+ {
+ rc = pfnProgressCallback((unsigned)((double)cbReadTotal * rdMulti), pvUser);
+ if (RT_FAILURE(rc))
+ break; /* canceled */
+ }
+ /* Finished? */
+ if (cbReadTotal == cbBuf)
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* Finally calculate & format the SHA256 sum */
+ uint8_t abHash[RTSHA256_HASH_SIZE];
+ RTSha256Final(&Ctx, abHash);
+
+ char *pszDigest;
+ rc = RTStrAllocEx(&pszDigest, RTSHA256_DIGEST_LEN + 1);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSha256ToString(abHash, pszDigest, RTSHA256_DIGEST_LEN + 1);
+ if (RT_SUCCESS(rc))
+ *ppszDigest = pszDigest;
+ else
+ RTStrFree(pszDigest);
+ }
+ }
+
+ return rc;
+}
+
+RTR3DECL(int) RTSha256DigestFromFile(const char *pszFile, char **ppszDigest, PFNRTPROGRESS pfnProgressCallback, void *pvUser)
+{
+ /* Validate input */
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszDigest, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_PARAMETER);
+
+ *ppszDigest = NULL;
+
+ /* Initialize the hash context. */
+ RTSHA256CONTEXT Ctx;
+ RTSha256Init(&Ctx);
+
+ /* Open the file to calculate a SHA256 sum of */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFile, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_WRITE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Fetch the file size. Only needed if there is a progress callback. */
+ double rdMulti = 0.0;
+ if (pfnProgressCallback)
+ {
+ uint64_t cbFile;
+ rc = RTFileQuerySize(hFile, &cbFile);
+ if (RT_FAILURE(rc))
+ {
+ RTFileClose(hFile);
+ return rc;
+ }
+ rdMulti = 100.0 / (cbFile ? (double)cbFile : 1.0);
+ }
+
+ /* Allocate a reasonably large buffer, fall back on a tiny one. */
+ void *pvBufFree;
+ size_t cbBuf = _1M;
+ void *pvBuf = pvBufFree = RTMemTmpAlloc(cbBuf);
+ if (!pvBuf)
+ {
+ cbBuf = 0x1000;
+ pvBuf = alloca(cbBuf);
+ }
+
+ /* Read that file in blocks */
+ size_t cbReadTotal = 0;
+ for (;;)
+ {
+ size_t cbRead;
+ rc = RTFileRead(hFile, pvBuf, cbBuf, &cbRead);
+ if (RT_FAILURE(rc) || !cbRead)
+ break;
+ RTSha256Update(&Ctx, pvBuf, cbRead);
+ cbReadTotal += cbRead;
+
+ /* Call the progress callback if one is defined */
+ if (pfnProgressCallback)
+ {
+ rc = pfnProgressCallback((unsigned)((double)cbReadTotal * rdMulti), pvUser);
+ if (RT_FAILURE(rc))
+ break; /* canceled */
+ }
+ }
+ RTMemTmpFree(pvBufFree);
+ RTFileClose(hFile);
+
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Finally calculate & format the SHA256 sum */
+ uint8_t abHash[RTSHA256_HASH_SIZE];
+ RTSha256Final(&Ctx, abHash);
+
+ char *pszDigest;
+ rc = RTStrAllocEx(&pszDigest, RTSHA256_DIGEST_LEN + 1);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSha256ToString(abHash, pszDigest, RTSHA256_DIGEST_LEN + 1);
+ if (RT_SUCCESS(rc))
+ *ppszDigest = pszDigest;
+ else
+ RTStrFree(pszDigest);
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/checksum/adler32.cpp b/src/VBox/Runtime/common/checksum/adler32.cpp
new file mode 100644
index 00000000..1e6bbd1e
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/adler32.cpp
@@ -0,0 +1,181 @@
+/* $Id: adler32.cpp $ */
+/** @file
+ * IPRT - Adler-32
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/crc.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTCRC_ADLER_32_NUMBER 65521
+
+
+RTDECL(uint32_t) RTCrcAdler32(void const *pv, size_t cb)
+{
+ /* Don't want to do the unrolling twice. */
+ return RTCrcAdler32Process(RTCrcAdler32Start(), pv, cb);
+}
+
+
+RTDECL(uint32_t) RTCrcAdler32Start(void)
+{
+ return 1;
+}
+
+
+RTDECL(uint32_t) RTCrcAdler32Process(uint32_t u32Crc, void const *pv, size_t cb)
+{
+ uint8_t const *pbSrc = (uint8_t const *)pv;
+ uint32_t a = u32Crc & 0xffff;
+ uint32_t b = u32Crc >> 16;
+ if (cb < 64 /* randomly selected number */)
+ {
+ while (cb-- > 0)
+ {
+ a += *pbSrc++;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+ }
+ }
+ else
+ {
+ switch (((uintptr_t)pbSrc & 0x3))
+ {
+ case 0:
+ break;
+
+ case 1:
+ a += *pbSrc++;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+ cb--;
+ RT_FALL_THRU();
+
+ case 2:
+ a += *pbSrc++;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+ cb--;
+ RT_FALL_THRU();
+
+ case 3:
+ a += *pbSrc++;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+ cb--;
+ break;
+ }
+
+ while (cb >= 4)
+ {
+ uint32_t u32 = *(uint32_t const *)pbSrc;
+ pbSrc += 4;
+
+ a += u32 & 0xff;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+
+ a += (u32 >> 8) & 0xff;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+
+ a += (u32 >> 16) & 0xff;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+
+ a += (u32 >> 24) & 0xff;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+
+ cb -= 4;
+ }
+
+ switch (cb)
+ {
+ case 0:
+ break;
+
+ case 3:
+ a += *pbSrc++;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+ cb--;
+ RT_FALL_THRU();
+
+ case 2:
+ a += *pbSrc++;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+ cb--;
+ RT_FALL_THRU();
+
+ case 1:
+ a += *pbSrc++;
+ a %= RTCRC_ADLER_32_NUMBER;
+ b += a;
+ b %= RTCRC_ADLER_32_NUMBER;
+ cb--;
+ break;
+ }
+ }
+
+ return a | (b << 16);
+}
+
+
+RTDECL(uint32_t) RTCrcAdler32Finish(uint32_t u32Crc)
+{
+ return u32Crc;
+}
+
diff --git a/src/VBox/Runtime/common/checksum/alt-md2.cpp b/src/VBox/Runtime/common/checksum/alt-md2.cpp
new file mode 100644
index 00000000..258cf510
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/alt-md2.cpp
@@ -0,0 +1,282 @@
+/* $Id: alt-md2.cpp $ */
+/** @file
+ * IPRT - Message-Digest Algorithm 2, Alternative Implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The MD2 block size. */
+#define RTMD2_BLOCK_SIZE 16
+/** The offset of the buffer into RTMD2ALTPRIVATECTX::abStateX. */
+#define RTMD2_BUF_OFF RTMD2_BLOCK_SIZE
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/types.h>
+
+
+/** Our private context structure. */
+typedef struct RTMD2ALTPRIVATECTX
+{
+ /** The state (X).
+ * The staging buffer starts byte 16. */
+ uint8_t abStateX[RTMD2_BLOCK_SIZE * 3];
+ /** The checksum. */
+ uint8_t abChecksum[RTMD2_BLOCK_SIZE];
+ /** The number of buffered bytes. */
+ uint8_t cbBuffer;
+} RTMD2ALTPRIVATECTX;
+
+#define RT_MD2_PRIVATE_ALT_CONTEXT
+#include <iprt/md2.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+AssertCompile(RT_SIZEOFMEMB(RTMD2CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTMD2CONTEXT, AltPrivate));
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** PI substitation used by MD2. */
+static uint8_t const g_PiSubst[256] =
+{
+ 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6, 19,
+ 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188, 76, 130, 202,
+ 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24, 138, 23, 229, 18,
+ 190, 78, 196, 214, 218, 158, 222, 73, 160, 251, 245, 142, 187, 47, 238, 122,
+ 169, 104, 121, 145, 21, 178, 7, 63, 148, 194, 16, 137, 11, 34, 95, 33,
+ 128, 127, 93, 154, 90, 144, 50, 39, 53, 62, 204, 231, 191, 247, 151, 3,
+ 255, 25, 48, 179, 72, 165, 181, 209, 215, 94, 146, 42, 172, 86, 170, 198,
+ 79, 184, 56, 210, 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241,
+ 69, 157, 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2,
+ 27, 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15,
+ 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197, 234, 38,
+ 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65, 129, 77, 82,
+ 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123, 8, 12, 189, 177, 74,
+ 120, 136, 149, 139, 227, 99, 232, 109, 233, 203, 213, 254, 59, 0, 29, 57,
+ 242, 239, 183, 14, 102, 88, 208, 228, 166, 119, 114, 248, 235, 117, 75, 10,
+ 49, 68, 80, 180, 143, 237, 31, 26, 219, 153, 141, 51, 159, 17, 131, 20,
+};
+
+
+RTDECL(void) RTMd2Init(PRTMD2CONTEXT pCtx)
+{
+ pCtx->AltPrivate.cbBuffer = 0;
+ RT_ZERO(pCtx->AltPrivate.abStateX);
+ RT_ZERO(pCtx->AltPrivate.abChecksum);
+}
+RT_EXPORT_SYMBOL(RTMd2Init);
+
+
+
+/**
+ * Initializes the processing of a whole block directly from the input buffer.
+ *
+ * This will update the checksum as well as initializing abStateX.
+ *
+ * @param pCtx The MD2 context.
+ * @param pbBlock The block.
+ */
+DECLINLINE(void) rtMd2BlockInit(PRTMD2CONTEXT pCtx, const uint8_t *pbBlock)
+{
+ uint8_t bL = pCtx->AltPrivate.abChecksum[15];
+ for (unsigned j = 0; j < RTMD2_BLOCK_SIZE; j++)
+ {
+ uint8_t bIn = pbBlock[j];
+ pCtx->AltPrivate.abStateX[j + RTMD2_BLOCK_SIZE] = bIn;
+ pCtx->AltPrivate.abStateX[j + RTMD2_BLOCK_SIZE * 2] = bIn ^ pCtx->AltPrivate.abStateX[j];
+ bL = pCtx->AltPrivate.abChecksum[j] ^= g_PiSubst[bIn ^ bL];
+ }
+}
+
+
+/**
+ * Special version of rtMd2BlockInit that does not update the checksum.
+ *
+ * This is used in the final round when adding the checksum to the calculation.
+ *
+ * @param pCtx The MD2 context.
+ * @param pbBlock The block (i.e. the checksum).
+ */
+DECLINLINE(void) rtMd2BlockInitNoChecksum(PRTMD2CONTEXT pCtx, const uint8_t *pbBlock)
+{
+ for (unsigned j = 0; j < RTMD2_BLOCK_SIZE; j++)
+ {
+ uint8_t bIn = pbBlock[j];
+ pCtx->AltPrivate.abStateX[j + RTMD2_BLOCK_SIZE] = bIn;
+ pCtx->AltPrivate.abStateX[j + RTMD2_BLOCK_SIZE * 2] = bIn ^ pCtx->AltPrivate.abStateX[j];
+ }
+}
+
+
+/**
+ * Initalizes the abStateX from a full buffer and update the checksum.
+ *
+ * The buffer is part of the abStateX structure (bytes 16 thru 31), so this
+ * is a somewhat reduced version of rtMd2BlockInit.
+ *
+ * @param pCtx The MD2 context.
+ */
+DECLINLINE(void) rtMd2BlockInitBuffered(PRTMD2CONTEXT pCtx)
+{
+ uint8_t bL = pCtx->AltPrivate.abChecksum[15];
+ for (unsigned j = 0; j < RTMD2_BLOCK_SIZE; j++)
+ {
+ uint8_t bIn = pCtx->AltPrivate.abStateX[j + RTMD2_BLOCK_SIZE];
+ pCtx->AltPrivate.abStateX[j + RTMD2_BLOCK_SIZE * 2] = bIn ^ pCtx->AltPrivate.abStateX[j];
+ bL = pCtx->AltPrivate.abChecksum[j] ^= g_PiSubst[bIn ^ bL];
+ }
+}
+
+
+/**
+ * Process the current block.
+ *
+ * Requires one of the rtMd2BlockInit functions to be called first.
+ *
+ * @param pCtx The MD2 context.
+ */
+DECLINLINE(void) rtMd2BlockProcess(PRTMD2CONTEXT pCtx)
+{
+ uint8_t bT = 0;
+ for (unsigned j = 0; j < 18; j++) /* 18 rounds */
+ {
+ for (unsigned k = 0; k < RTMD2_BLOCK_SIZE * 3; k++)
+ pCtx->AltPrivate.abStateX[k] = bT = pCtx->AltPrivate.abStateX[k] ^ g_PiSubst[bT];
+ bT += (uint8_t)j;
+ }
+}
+
+
+RTDECL(void) RTMd2Update(PRTMD2CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ Assert(pCtx->AltPrivate.cbBuffer < RTMD2_BLOCK_SIZE);
+ uint8_t const *pbBuf = (uint8_t const *)pvBuf;
+
+ /*
+ * Deal with buffered bytes first.
+ */
+ if (pCtx->AltPrivate.cbBuffer)
+ {
+ uint8_t cbMissing = RTMD2_BLOCK_SIZE - pCtx->AltPrivate.cbBuffer;
+ if (cbBuf >= cbMissing)
+ {
+ memcpy(&pCtx->AltPrivate.abStateX[RTMD2_BUF_OFF + pCtx->AltPrivate.cbBuffer], pbBuf, cbMissing);
+ pbBuf += cbMissing;
+ cbBuf -= cbMissing;
+
+ rtMd2BlockInitBuffered(pCtx);
+ rtMd2BlockProcess(pCtx);
+
+ pCtx->AltPrivate.cbBuffer = 0;
+ }
+ else
+ {
+ memcpy(&pCtx->AltPrivate.abStateX[RTMD2_BUF_OFF + pCtx->AltPrivate.cbBuffer], pbBuf, cbBuf);
+ pCtx->AltPrivate.cbBuffer += (uint8_t)cbBuf;
+ return;
+ }
+ }
+
+ /*
+ * Process full blocks directly from the input buffer.
+ */
+ while (cbBuf >= RTMD2_BLOCK_SIZE)
+ {
+ rtMd2BlockInit(pCtx, pbBuf);
+ rtMd2BlockProcess(pCtx);
+
+ pbBuf += RTMD2_BLOCK_SIZE;
+ cbBuf -= RTMD2_BLOCK_SIZE;
+ }
+
+ /*
+ * Stash any remaining bytes into the context buffer.
+ */
+ if (cbBuf > 0)
+ {
+ memcpy(&pCtx->AltPrivate.abStateX[RTMD2_BUF_OFF], pbBuf, cbBuf);
+ pCtx->AltPrivate.cbBuffer = (uint8_t)cbBuf;
+ }
+}
+RT_EXPORT_SYMBOL(RTMd2Update);
+
+
+RTDECL(void) RTMd2Final(PRTMD2CONTEXT pCtx, uint8_t pabDigest[RTMD2_HASH_SIZE])
+{
+ Assert(pCtx->AltPrivate.cbBuffer < RTMD2_BLOCK_SIZE);
+
+ /*
+ * Pad the message to a multiple of 16 bytes. This is done even if the
+ * message already is a multiple of 16.
+ */
+ unsigned cbPad = RTMD2_BLOCK_SIZE - pCtx->AltPrivate.cbBuffer;
+ memset(&pCtx->AltPrivate.abStateX[RTMD2_BUF_OFF + pCtx->AltPrivate.cbBuffer], cbPad, cbPad);
+ rtMd2BlockInitBuffered(pCtx);
+ rtMd2BlockProcess(pCtx);
+ pCtx->AltPrivate.cbBuffer = 0;
+
+ /*
+ * Add the checksum.
+ */
+ rtMd2BlockInitNoChecksum(pCtx, pCtx->AltPrivate.abChecksum);
+ rtMd2BlockProcess(pCtx);
+
+ /*
+ * Done. Just copy out the digest.
+ */
+ memcpy(pabDigest, pCtx->AltPrivate.abStateX, RTMD2_HASH_SIZE);
+
+ RT_ZERO(pCtx->AltPrivate);
+ pCtx->AltPrivate.cbBuffer = UINT8_MAX;
+}
+RT_EXPORT_SYMBOL(RTMd2Final);
+
+
+RTDECL(void) RTMd2(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTMD2_HASH_SIZE])
+{
+ RTMD2CONTEXT Ctx;
+ RTMd2Init(&Ctx);
+ RTMd2Update(&Ctx, pvBuf, cbBuf);
+ RTMd2Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTMd2);
+
diff --git a/src/VBox/Runtime/common/checksum/alt-md4.cpp b/src/VBox/Runtime/common/checksum/alt-md4.cpp
new file mode 100644
index 00000000..36ee8227
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/alt-md4.cpp
@@ -0,0 +1,293 @@
+/* $Id: alt-md4.cpp $ */
+/** @file
+ * IPRT - Message-Digest Algorithm 4, Alternative Implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The MD4 block size in bytes. */
+#define RTMD4_BLOCK_SIZE 64
+/** The MD4 block size in bits. */
+#define RTMD4_BLOCK_SIZE_IN_BITS (RTMD4_BLOCK_SIZE * 8)
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/types.h>
+
+
+/** Our private context structure. */
+typedef struct RTMD4ALTPRIVATECTX
+{
+ uint32_t uA;
+ uint32_t uB;
+ uint32_t uC;
+ uint32_t uD;
+ /** Message length in bits. */
+ uint64_t cTotalBits;
+ /** Input buffer. cTotalBits indicates how much is present. */
+ union
+ {
+ uint8_t abBuffer[RTMD4_BLOCK_SIZE];
+ uint32_t aX[RTMD4_BLOCK_SIZE / 4];
+ } u;
+} RTMD4ALTPRIVATECTX;
+
+
+#define RT_MD4_PRIVATE_ALT_CONTEXT
+#include <iprt/md4.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+AssertCompile(RT_SIZEOFMEMB(RTMD4CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTMD4CONTEXT, AltPrivate));
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** MD4 padding. */
+static const uint8_t g_abMd4Padding[RTMD4_BLOCK_SIZE] =
+{
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+
+
+
+RTDECL(void) RTMd4Init(PRTMD4CONTEXT pCtx)
+{
+ pCtx->AltPrivate.uA = UINT32_C(0x67452301);
+ pCtx->AltPrivate.uB = UINT32_C(0xefcdab89);
+ pCtx->AltPrivate.uC = UINT32_C(0x98badcfe);
+ pCtx->AltPrivate.uD = UINT32_C(0x10325476);
+ pCtx->AltPrivate.cTotalBits = 0;
+ RT_ZERO(pCtx->AltPrivate.u);
+}
+RT_EXPORT_SYMBOL(RTMd4Init);
+
+
+DECLINLINE(uint32_t) rtMd4FuncF(uint32_t uX, uint32_t uY, uint32_t uZ)
+{
+ return (uX & uY) | (~uX & uZ);
+}
+
+DECLINLINE(uint32_t) rtMd4FuncG(uint32_t uX, uint32_t uY, uint32_t uZ)
+{
+ return (uX & (uY | uZ)) | (uY & uZ);
+}
+
+DECLINLINE(uint32_t) rtMd4FuncH(uint32_t uX, uint32_t uY, uint32_t uZ)
+{
+ return uX ^ uY ^ uZ;
+}
+
+
+/**
+ * Process the current block.
+ *
+ * Requires one of the rtMD4BlockInit functions to be called first.
+ *
+ * @param pCtx The MD4 context.
+ */
+DECLINLINE(void) rtMD4BlockProcess(PRTMD4CONTEXT pCtx)
+{
+#ifdef RT_BIG_ENDIAN
+ /* Convert the X array to little endian. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(pCtx->AltPrivate.u.aX); i++)
+ pCtx->AltPrivate.u.aX[i] = RT_BSWAP_U32(pCtx->AltPrivate.u.aX[i]);
+#endif
+
+ /* Instead of saving A, B, C, D we copy them into variables and work on those. */
+ uint32_t A = pCtx->AltPrivate.uA;
+ uint32_t B = pCtx->AltPrivate.uB;
+ uint32_t C = pCtx->AltPrivate.uC;
+ uint32_t D = pCtx->AltPrivate.uD;
+
+ /* Round #1: */
+#define ABCD_K_S(a,b,c,d, k, s) ASMRotateLeftU32(a + rtMd4FuncF(b, c, d) + pCtx->AltPrivate.u.aX[k], s)
+ A = ABCD_K_S(A,B,C,D, 0, 3); D = ABCD_K_S(D,A,B,C, 1, 7); C = ABCD_K_S(C,D,A,B, 2, 11); B = ABCD_K_S(B,C,D,A, 3, 19);
+ A = ABCD_K_S(A,B,C,D, 4, 3); D = ABCD_K_S(D,A,B,C, 5, 7); C = ABCD_K_S(C,D,A,B, 6, 11); B = ABCD_K_S(B,C,D,A, 7, 19);
+ A = ABCD_K_S(A,B,C,D, 8, 3); D = ABCD_K_S(D,A,B,C, 9, 7); C = ABCD_K_S(C,D,A,B, 10, 11); B = ABCD_K_S(B,C,D,A, 11, 19);
+ A = ABCD_K_S(A,B,C,D, 12, 3); D = ABCD_K_S(D,A,B,C, 13, 7); C = ABCD_K_S(C,D,A,B, 14, 11); B = ABCD_K_S(B,C,D,A, 15, 19);
+#undef ABCD_K_S
+
+ /* Round #2: */
+#define ABCD_K_S(a,b,c,d, k, s) ASMRotateLeftU32(a + rtMd4FuncG(b, c, d) + pCtx->AltPrivate.u.aX[k] + UINT32_C(0x5a827999), s)
+ A = ABCD_K_S(A,B,C,D, 0, 3); D = ABCD_K_S(D,A,B,C, 4, 5); C = ABCD_K_S(C,D,A,B, 8, 9); B = ABCD_K_S(B,C,D,A, 12, 13);
+ A = ABCD_K_S(A,B,C,D, 1, 3); D = ABCD_K_S(D,A,B,C, 5, 5); C = ABCD_K_S(C,D,A,B, 9, 9); B = ABCD_K_S(B,C,D,A, 13, 13);
+ A = ABCD_K_S(A,B,C,D, 2, 3); D = ABCD_K_S(D,A,B,C, 6, 5); C = ABCD_K_S(C,D,A,B, 10, 9); B = ABCD_K_S(B,C,D,A, 14, 13);
+ A = ABCD_K_S(A,B,C,D, 3, 3); D = ABCD_K_S(D,A,B,C, 7, 5); C = ABCD_K_S(C,D,A,B, 11, 9); B = ABCD_K_S(B,C,D,A, 15, 13);
+#undef ABCD_K_S
+
+ /* Round #3: */
+#define ABCD_K_S(a,b,c,d, k, s) ASMRotateLeftU32(a + rtMd4FuncH(b, c, d) + pCtx->AltPrivate.u.aX[k] + UINT32_C(0x6ed9eba1), s)
+ A = ABCD_K_S(A,B,C,D, 0, 3); D = ABCD_K_S(D,A,B,C, 8, 9); C = ABCD_K_S(C,D,A,B, 4, 11); B = ABCD_K_S(B,C,D,A, 12, 15);
+ A = ABCD_K_S(A,B,C,D, 2, 3); D = ABCD_K_S(D,A,B,C, 10, 9); C = ABCD_K_S(C,D,A,B, 6, 11); B = ABCD_K_S(B,C,D,A, 14, 15);
+ A = ABCD_K_S(A,B,C,D, 1, 3); D = ABCD_K_S(D,A,B,C, 9, 9); C = ABCD_K_S(C,D,A,B, 5, 11); B = ABCD_K_S(B,C,D,A, 13, 15);
+ A = ABCD_K_S(A,B,C,D, 3, 3); D = ABCD_K_S(D,A,B,C, 11, 9); C = ABCD_K_S(C,D,A,B, 7, 11); B = ABCD_K_S(B,C,D,A, 15, 15);
+#undef ABCD_K_S
+
+ /* Perform the additions. */
+ pCtx->AltPrivate.uA += A;
+ pCtx->AltPrivate.uB += B;
+ pCtx->AltPrivate.uC += C;
+ pCtx->AltPrivate.uD += D;
+}
+
+
+RTDECL(void) RTMd4Update(PRTMD4CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ uint8_t const *pbBuf = (uint8_t const *)pvBuf;
+
+ /*
+ * Deal with buffered bytes first.
+ */
+ if (pCtx->AltPrivate.cTotalBits & (RTMD4_BLOCK_SIZE_IN_BITS - 1))
+ {
+ uint8_t cbBuffered = (pCtx->AltPrivate.cTotalBits >> 3) & (RTMD4_BLOCK_SIZE - 1);
+ uint8_t cbMissing = RTMD4_BLOCK_SIZE - cbBuffered;
+ if (cbBuf >= cbMissing)
+ {
+ memcpy(&pCtx->AltPrivate.u.abBuffer[cbBuffered], pbBuf, cbMissing);
+ pCtx->AltPrivate.cTotalBits += cbMissing << 3;
+ pbBuf += cbMissing;
+ cbBuf -= cbMissing;
+
+ rtMD4BlockProcess(pCtx);
+ }
+ else
+ {
+ memcpy(&pCtx->AltPrivate.u.abBuffer[cbBuffered], pbBuf, cbBuf);
+ pCtx->AltPrivate.cTotalBits += cbBuf << 3;
+ return;
+ }
+ }
+
+ /*
+ * Process full blocks directly from the input buffer.
+ */
+ while (cbBuf >= RTMD4_BLOCK_SIZE)
+ {
+ memcpy(&pCtx->AltPrivate.u.abBuffer[0], pbBuf, RTMD4_BLOCK_SIZE);
+ rtMD4BlockProcess(pCtx);
+
+ pbBuf += RTMD4_BLOCK_SIZE;
+ cbBuf -= RTMD4_BLOCK_SIZE;
+ pCtx->AltPrivate.cTotalBits += RTMD4_BLOCK_SIZE_IN_BITS;
+ }
+
+ /*
+ * Stash any remaining bytes into the context buffer.
+ */
+ if (cbBuf > 0)
+ {
+ memcpy(&pCtx->AltPrivate.u.abBuffer[0], pbBuf, cbBuf);
+ pCtx->AltPrivate.cTotalBits += cbBuf << 3;
+ }
+}
+RT_EXPORT_SYMBOL(RTMd4Update);
+
+
+RTDECL(void) RTMd4Final(PRTMD4CONTEXT pCtx, uint8_t pabDigest[RTMD4_HASH_SIZE])
+{
+ uint64_t const cTotalBits = pCtx->AltPrivate.cTotalBits;
+
+ /*
+ * Pad input to block size minus sizeof(cTotalBits).
+ */
+ uint8_t cbMissing = RTMD4_BLOCK_SIZE - ((cTotalBits >> 3) & (RTMD4_BLOCK_SIZE - 1));
+ uint8_t cbPadding = cbMissing + (cbMissing > 8 ? 0 : RTMD4_BLOCK_SIZE) - 8;
+ Assert(cbPadding > 0 && cbPadding <= sizeof(g_abMd4Padding));
+ RTMd4Update(pCtx, g_abMd4Padding, cbPadding);
+ Assert(((pCtx->AltPrivate.cTotalBits >> 3) & (RTMD4_BLOCK_SIZE - 1)) == RTMD4_BLOCK_SIZE - 8);
+
+ /*
+ * Encode the total bitcount at the end of the buffer and do the final round.
+ */
+ pCtx->AltPrivate.u.abBuffer[RTMD4_BLOCK_SIZE - 8] = (uint8_t)(cTotalBits );
+ pCtx->AltPrivate.u.abBuffer[RTMD4_BLOCK_SIZE - 7] = (uint8_t)(cTotalBits >> 8);
+ pCtx->AltPrivate.u.abBuffer[RTMD4_BLOCK_SIZE - 6] = (uint8_t)(cTotalBits >> 16);
+ pCtx->AltPrivate.u.abBuffer[RTMD4_BLOCK_SIZE - 5] = (uint8_t)(cTotalBits >> 24);
+ pCtx->AltPrivate.u.abBuffer[RTMD4_BLOCK_SIZE - 4] = (uint8_t)(cTotalBits >> 32);
+ pCtx->AltPrivate.u.abBuffer[RTMD4_BLOCK_SIZE - 3] = (uint8_t)(cTotalBits >> 40);
+ pCtx->AltPrivate.u.abBuffer[RTMD4_BLOCK_SIZE - 2] = (uint8_t)(cTotalBits >> 48);
+ pCtx->AltPrivate.u.abBuffer[RTMD4_BLOCK_SIZE - 1] = (uint8_t)(cTotalBits >> 56);
+ rtMD4BlockProcess(pCtx);
+
+ /*
+ * Done. Just encode the digest.
+ */
+ pabDigest[ 0] = (uint8_t)(pCtx->AltPrivate.uA );
+ pabDigest[ 1] = (uint8_t)(pCtx->AltPrivate.uA >> 8);
+ pabDigest[ 2] = (uint8_t)(pCtx->AltPrivate.uA >> 16);
+ pabDigest[ 3] = (uint8_t)(pCtx->AltPrivate.uA >> 24);
+ pabDigest[ 4] = (uint8_t)(pCtx->AltPrivate.uB );
+ pabDigest[ 5] = (uint8_t)(pCtx->AltPrivate.uB >> 8);
+ pabDigest[ 6] = (uint8_t)(pCtx->AltPrivate.uB >> 16);
+ pabDigest[ 7] = (uint8_t)(pCtx->AltPrivate.uB >> 24);
+ pabDigest[ 8] = (uint8_t)(pCtx->AltPrivate.uC );
+ pabDigest[ 9] = (uint8_t)(pCtx->AltPrivate.uC >> 8);
+ pabDigest[10] = (uint8_t)(pCtx->AltPrivate.uC >> 16);
+ pabDigest[11] = (uint8_t)(pCtx->AltPrivate.uC >> 24);
+ pabDigest[12] = (uint8_t)(pCtx->AltPrivate.uD );
+ pabDigest[13] = (uint8_t)(pCtx->AltPrivate.uD >> 8);
+ pabDigest[14] = (uint8_t)(pCtx->AltPrivate.uD >> 16);
+ pabDigest[15] = (uint8_t)(pCtx->AltPrivate.uD >> 24);
+
+ /*
+ * Nuke the state.
+ */
+ RT_ZERO(pCtx->AltPrivate);
+}
+RT_EXPORT_SYMBOL(RTMd4Final);
+
+
+RTDECL(void) RTMd4(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTMD4_HASH_SIZE])
+{
+ RTMD4CONTEXT Ctx;
+ RTMd4Init(&Ctx);
+ RTMd4Update(&Ctx, pvBuf, cbBuf);
+ RTMd4Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTMd4);
+
diff --git a/src/VBox/Runtime/common/checksum/alt-md5.cpp b/src/VBox/Runtime/common/checksum/alt-md5.cpp
new file mode 100644
index 00000000..b37ed13f
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/alt-md5.cpp
@@ -0,0 +1,374 @@
+/* $Id: alt-md5.cpp $ */
+/** @file
+ * IPRT - MD5 message digest functions, alternative implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+/* The code is virtually unchanged from the original version (see copyright
+ * notice below). Most changes are related to the function names and data
+ * types - in order to fit the code in the IPRT naming style. */
+
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * RTMD5CONTEXT structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/md5.h>
+#include "internal/iprt.h"
+
+#include <iprt/string.h> /* for memcpy() */
+#if defined(RT_BIG_ENDIAN)
+# include <iprt/asm.h> /* RT_LE2H_U32 uses ASMByteSwapU32. */
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/* The four core functions - F1 is optimized somewhat */
+#if 1
+/* #define F1(x, y, z) (x & y | ~x & z) */
+# define F1(x, y, z) (z ^ (x & (y ^ z)))
+# define F2(x, y, z) F1(z, x, y)
+# define F3(x, y, z) (x ^ y ^ z)
+# define F4(x, y, z) (y ^ (x | ~z))
+#else /* gcc 4.0.1 (x86) benefits from the explicitness of F1() here. */
+DECL_FORCE_INLINE(uint32_t) F1(uint32_t x, uint32_t y, uint32_t z)
+{
+ register uint32_t r = y ^ z;
+ r &= x;
+ r ^= z;
+ return r;
+}
+# define F2(x, y, z) F1(z, x, y)
+DECL_FORCE_INLINE(uint32_t) F3(uint32_t x, uint32_t y, uint32_t z)
+{
+ register uint32_t r = x ^ y;
+ r ^= z;
+ return r;
+}
+DECL_FORCE_INLINE(uint32_t) F4(uint32_t x, uint32_t y, uint32_t z)
+{
+ register uint32_t r = ~z;
+ r |= x;
+ r ^= y;
+ return r;
+}
+#endif
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+
+/**
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to reflect
+ * the addition of 16 longwords of new data. RTMd5Update blocks the data and
+ * converts bytes into longwords for this routine.
+ */
+static void rtMd5Transform(uint32_t buf[4], uint32_t const in[16])
+{
+ uint32_t a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ /* fn, w, x, y, z, data, s) */
+ MD5STEP(F1, a, b, c, d, in[ 0] + 0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[ 1] + 0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[ 2] + 0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[ 3] + 0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[ 4] + 0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[ 5] + 0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[ 6] + 0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[ 7] + 0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[ 8] + 0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[ 9] + 0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[ 1] + 0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[ 6] + 0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[ 0] + 0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[ 5] + 0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[ 4] + 0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[ 9] + 0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[ 3] + 0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[ 8] + 0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[ 2] + 0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[ 7] + 0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[ 5] + 0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[ 8] + 0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[ 1] + 0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[ 4] + 0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[ 7] + 0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[ 0] + 0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[ 3] + 0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[ 6] + 0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[ 9] + 0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[ 2] + 0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[ 0] + 0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[ 7] + 0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[ 5] + 0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[ 3] + 0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[ 1] + 0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 8] + 0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[ 6] + 0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 4] + 0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[ 2] + 0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[ 9] + 0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+
+
+#ifdef RT_BIG_ENDIAN
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void rtMd5ByteReverse(uint32_t *buf, unsigned int longs)
+{
+ uint32_t t;
+ do
+ {
+ t = *buf;
+ t = RT_LE2H_U32(t);
+ *buf = t;
+ buf++;
+ } while (--longs);
+}
+#else /* little endian - do nothing */
+# define rtMd5ByteReverse(buf, len) do { /* Nothing */ } while (0)
+#endif
+
+
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+RTDECL(void) RTMd5Init(PRTMD5CONTEXT pCtx)
+{
+ pCtx->AltPrivate.buf[0] = 0x67452301;
+ pCtx->AltPrivate.buf[1] = 0xefcdab89;
+ pCtx->AltPrivate.buf[2] = 0x98badcfe;
+ pCtx->AltPrivate.buf[3] = 0x10325476;
+
+ pCtx->AltPrivate.bits[0] = 0;
+ pCtx->AltPrivate.bits[1] = 0;
+}
+RT_EXPORT_SYMBOL(RTMd5Init);
+
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+RTDECL(void) RTMd5Update(PRTMD5CONTEXT pCtx, const void *pvBuf, size_t len)
+{
+ const uint8_t *buf = (const uint8_t *)pvBuf;
+ uint32_t t;
+
+ /* Update bitcount */
+ t = pCtx->AltPrivate.bits[0];
+ if ((pCtx->AltPrivate.bits[0] = t + ((uint32_t) len << 3)) < t)
+ pCtx->AltPrivate.bits[1]++; /* Carry from low to high */
+ pCtx->AltPrivate.bits[1] += (uint32_t)(len >> 29);
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+ if (t)
+ {
+ uint8_t *p = (uint8_t *) pCtx->AltPrivate.in + t;
+
+ t = 64 - t;
+ if (len < t)
+ {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ rtMd5ByteReverse(pCtx->AltPrivate.in, 16);
+ rtMd5Transform(pCtx->AltPrivate.buf, pCtx->AltPrivate.in);
+ buf += t;
+ len -= t;
+ }
+
+ /* Process data in 64-byte chunks */
+#ifndef RT_BIG_ENDIAN
+ if (!((uintptr_t)buf & 0x3))
+ {
+ while (len >= 64) {
+ rtMd5Transform(pCtx->AltPrivate.buf, (uint32_t const *)buf);
+ buf += 64;
+ len -= 64;
+ }
+ }
+ else
+#endif
+ {
+ while (len >= 64) {
+ memcpy(pCtx->AltPrivate.in, buf, 64);
+ rtMd5ByteReverse(pCtx->AltPrivate.in, 16);
+ rtMd5Transform(pCtx->AltPrivate.buf, pCtx->AltPrivate.in);
+ buf += 64;
+ len -= 64;
+ }
+ }
+
+ /* Handle any remaining bytes of data */
+ memcpy(pCtx->AltPrivate.in, buf, len);
+}
+RT_EXPORT_SYMBOL(RTMd5Update);
+
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+RTDECL(void) RTMd5Final(uint8_t digest[16], PRTMD5CONTEXT pCtx)
+{
+ unsigned int count;
+ uint8_t *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (pCtx->AltPrivate.bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = (uint8_t *)pCtx->AltPrivate.in + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8)
+ {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ rtMd5ByteReverse(pCtx->AltPrivate.in, 16);
+ rtMd5Transform(pCtx->AltPrivate.buf, pCtx->AltPrivate.in);
+
+ /* Now fill the next block with 56 bytes */
+ memset(pCtx->AltPrivate.in, 0, 56);
+ }
+ else
+ {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count - 8);
+ }
+ rtMd5ByteReverse(pCtx->AltPrivate.in, 14);
+
+ /* Append length in bits and transform */
+ pCtx->AltPrivate.in[14] = pCtx->AltPrivate.bits[0];
+ pCtx->AltPrivate.in[15] = pCtx->AltPrivate.bits[1];
+
+ rtMd5Transform(pCtx->AltPrivate.buf, pCtx->AltPrivate.in);
+ rtMd5ByteReverse(pCtx->AltPrivate.buf, 4);
+ memcpy(digest, pCtx->AltPrivate.buf, 16);
+ memset(pCtx, 0, sizeof(*pCtx)); /* In case it's sensitive */
+}
+RT_EXPORT_SYMBOL(RTMd5Final);
+
+
+RTDECL(void) RTMd5(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTMD5HASHSIZE])
+{
+#if 0
+ RTMD5CONTEXT Ctx[2];
+ PRTMD5CONTEXT const pCtx = RT_ALIGN_PT(&Ctx[0], 64, PRTMD5CONTEXT);
+#else
+ RTMD5CONTEXT Ctx;
+ PRTMD5CONTEXT const pCtx = &Ctx;
+#endif
+
+ RTMd5Init(pCtx);
+ for (;;)
+ {
+ uint32_t cb = (uint32_t)RT_MIN(cbBuf, _2M);
+ RTMd5Update(pCtx, pvBuf, cb);
+ if (cb == cbBuf)
+ break;
+ cbBuf -= cb;
+ pvBuf = (uint8_t const *)pvBuf + cb;
+ }
+ RTMd5Final(pabDigest, pCtx);
+}
+RT_EXPORT_SYMBOL(RTMd5);
+
diff --git a/src/VBox/Runtime/common/checksum/alt-sha1.cpp b/src/VBox/Runtime/common/checksum/alt-sha1.cpp
new file mode 100644
index 00000000..ff1fda3f
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/alt-sha1.cpp
@@ -0,0 +1,535 @@
+/* $Id: alt-sha1.cpp $ */
+/** @file
+ * IPRT - SHA-1 hash functions, Alternative Implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The SHA-1 block size (in bytes). */
+#define RTSHA1_BLOCK_SIZE 64U
+
+/** Enables the unrolled code. */
+#define RTSHA1_UNROLLED 1
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+
+
+/** Our private context structure. */
+typedef struct RTSHA1ALTPRIVATECTX
+{
+ /** The W array.
+ * Buffering happens in the first 16 words, converted from big endian to host
+ * endian immediately before processing. The amount of buffered data is kept
+ * in the 6 least significant bits of cbMessage. */
+ uint32_t auW[80];
+ /** The message length (in bytes). */
+ uint64_t cbMessage;
+
+ /** The 5 hash values. */
+ uint32_t auH[5];
+} RTSHA1ALTPRIVATECTX;
+
+#define RT_SHA1_PRIVATE_ALT_CONTEXT
+#include <iprt/sha.h>
+
+
+AssertCompile(RT_SIZEOFMEMB(RTSHA1CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTSHA1CONTEXT, AltPrivate));
+AssertCompileMemberSize(RTSHA1ALTPRIVATECTX, auH, RTSHA1_HASH_SIZE);
+
+
+
+
+RTDECL(void) RTSha1Init(PRTSHA1CONTEXT pCtx)
+{
+ pCtx->AltPrivate.cbMessage = 0;
+ pCtx->AltPrivate.auH[0] = UINT32_C(0x67452301);
+ pCtx->AltPrivate.auH[1] = UINT32_C(0xefcdab89);
+ pCtx->AltPrivate.auH[2] = UINT32_C(0x98badcfe);
+ pCtx->AltPrivate.auH[3] = UINT32_C(0x10325476);
+ pCtx->AltPrivate.auH[4] = UINT32_C(0xc3d2e1f0);
+}
+RT_EXPORT_SYMBOL(RTSha1Init);
+
+
+/**
+ * Initializes the auW array from the specfied input block.
+ *
+ * @param pCtx The SHA1 context.
+ * @param pbBlock The block. Must be 32-bit aligned.
+ */
+DECLINLINE(void) rtSha1BlockInit(PRTSHA1CONTEXT pCtx, uint8_t const *pbBlock)
+{
+#ifdef RTSHA1_UNROLLED
+ uint32_t const *puSrc = (uint32_t const *)pbBlock;
+ uint32_t *puW = &pCtx->AltPrivate.auW[0];
+ Assert(!((uintptr_t)puSrc & 3));
+ Assert(!((uintptr_t)puW & 3));
+
+ /* Copy and byte-swap the block. Initializing the rest of the Ws are done
+ in the processing loop. */
+# ifdef RT_LITTLE_ENDIAN
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+# else
+ memcpy(puW, puSrc, RTSHA1_BLOCK_SIZE);
+# endif
+
+#else /* !RTSHA1_UNROLLED */
+ uint32_t const *pu32Block = (uint32_t const *)pbBlock;
+ Assert(!((uintptr_t)pu32Block & 3));
+
+ unsigned iWord;
+ for (iWord = 0; iWord < 16; iWord++)
+ pCtx->AltPrivate.auW[iWord] = RT_BE2H_U32(pu32Block[iWord]);
+
+ for (; iWord < RT_ELEMENTS(pCtx->AltPrivate.auW); iWord++)
+ {
+ uint32_t u32 = pCtx->AltPrivate.auW[iWord - 16];
+ u32 ^= pCtx->AltPrivate.auW[iWord - 14];
+ u32 ^= pCtx->AltPrivate.auW[iWord - 8];
+ u32 ^= pCtx->AltPrivate.auW[iWord - 3];
+ pCtx->AltPrivate.auW[iWord] = ASMRotateLeftU32(u32, 1);
+ }
+#endif /* !RTSHA1_UNROLLED */
+}
+
+
+/**
+ * Initializes the auW array from data buffered in the first part of the array.
+ *
+ * @param pCtx The SHA1 context.
+ */
+DECLINLINE(void) rtSha1BlockInitBuffered(PRTSHA1CONTEXT pCtx)
+{
+#ifdef RTSHA1_UNROLLED
+ uint32_t *puW = &pCtx->AltPrivate.auW[0];
+ Assert(!((uintptr_t)puW & 3));
+
+ /* Do the byte swap if necessary. Initializing the rest of the Ws are done
+ in the processing loop. */
+# ifdef RT_LITTLE_ENDIAN
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+# endif
+
+#else /* !RTSHA1_UNROLLED_INIT */
+ unsigned iWord;
+ for (iWord = 0; iWord < 16; iWord++)
+ pCtx->AltPrivate.auW[iWord] = RT_BE2H_U32(pCtx->AltPrivate.auW[iWord]);
+
+ for (; iWord < RT_ELEMENTS(pCtx->AltPrivate.auW); iWord++)
+ {
+ uint32_t u32 = pCtx->AltPrivate.auW[iWord - 16];
+ u32 ^= pCtx->AltPrivate.auW[iWord - 14];
+ u32 ^= pCtx->AltPrivate.auW[iWord - 8];
+ u32 ^= pCtx->AltPrivate.auW[iWord - 3];
+ pCtx->AltPrivate.auW[iWord] = ASMRotateLeftU32(u32, 1);
+ }
+#endif /* !RTSHA1_UNROLLED_INIT */
+}
+
+
+/** Function 4.1, Ch(x,y,z). */
+DECL_FORCE_INLINE(uint32_t) rtSha1Ch(uint32_t uX, uint32_t uY, uint32_t uZ)
+{
+#if 1
+ /* Optimization that saves one operation and probably a temporary variable. */
+ uint32_t uResult = uY;
+ uResult ^= uZ;
+ uResult &= uX;
+ uResult ^= uZ;
+ return uResult;
+#else
+ /* The original. */
+ uint32_t uResult = uX & uY;
+ uResult ^= ~uX & uZ;
+ return uResult;
+#endif
+}
+
+
+/** Function 4.1, Parity(x,y,z). */
+DECL_FORCE_INLINE(uint32_t) rtSha1Parity(uint32_t uX, uint32_t uY, uint32_t uZ)
+{
+ uint32_t uResult = uX;
+ uResult ^= uY;
+ uResult ^= uZ;
+ return uResult;
+}
+
+
+/** Function 4.1, Maj(x,y,z). */
+DECL_FORCE_INLINE(uint32_t) rtSha1Maj(uint32_t uX, uint32_t uY, uint32_t uZ)
+{
+#if 1
+ /* Optimization that save one operation and probably a temporary variable. */
+ uint32_t uResult = uY;
+ uResult ^= uZ;
+ uResult &= uX;
+ uResult ^= uY & uZ;
+ return uResult;
+#else
+ /* The original. */
+ uint32_t uResult = (uX & uY);
+ uResult |= (uX & uZ);
+ uResult |= (uY & uZ);
+ return uResult;
+#endif
+}
+
+
+/**
+ * Process the current block.
+ *
+ * Requires one of the rtSha1BlockInit functions to be called first.
+ *
+ * @param pCtx The SHA1 context.
+ */
+static void rtSha1BlockProcess(PRTSHA1CONTEXT pCtx)
+{
+ uint32_t uA = pCtx->AltPrivate.auH[0];
+ uint32_t uB = pCtx->AltPrivate.auH[1];
+ uint32_t uC = pCtx->AltPrivate.auH[2];
+ uint32_t uD = pCtx->AltPrivate.auH[3];
+ uint32_t uE = pCtx->AltPrivate.auH[4];
+
+#ifdef RTSHA1_UNROLLED
+ /* This fully unrolled version will avoid the variable rotation by
+ embedding it into the loop unrolling. */
+ uint32_t *puW = &pCtx->AltPrivate.auW[0];
+# define SHA1_BODY(a_iWord, a_uK, a_fnFt, a_uA, a_uB, a_uC, a_uD, a_uE) \
+ do { \
+ if (a_iWord < 16) \
+ a_uE += *puW++; \
+ else \
+ { \
+ uint32_t u32 = puW[-16]; \
+ u32 ^= puW[-14]; \
+ u32 ^= puW[-8]; \
+ u32 ^= puW[-3]; \
+ u32 = ASMRotateLeftU32(u32, 1); \
+ *puW++ = u32; \
+ a_uE += u32; \
+ } \
+ a_uE += (a_uK); \
+ a_uE += ASMRotateLeftU32(a_uA, 5); \
+ a_uE += a_fnFt(a_uB, a_uC, a_uD); \
+ a_uB = ASMRotateLeftU32(a_uB, 30); \
+ } while (0)
+# define FIVE_ITERATIONS(a_iFirst, a_uK, a_fnFt) \
+ do { \
+ SHA1_BODY(a_iFirst + 0, a_uK, a_fnFt, uA, uB, uC, uD, uE); \
+ SHA1_BODY(a_iFirst + 1, a_uK, a_fnFt, uE, uA, uB, uC, uD); \
+ SHA1_BODY(a_iFirst + 2, a_uK, a_fnFt, uD, uE, uA, uB, uC); \
+ SHA1_BODY(a_iFirst + 3, a_uK, a_fnFt, uC, uD, uE, uA, uB); \
+ SHA1_BODY(a_iFirst + 4, a_uK, a_fnFt, uB, uC, uD, uE, uA); \
+ } while (0)
+# define TWENTY_ITERATIONS(a_iStart, a_uK, a_fnFt) \
+ do { \
+ FIVE_ITERATIONS(a_iStart + 0, a_uK, a_fnFt); \
+ FIVE_ITERATIONS(a_iStart + 5, a_uK, a_fnFt); \
+ FIVE_ITERATIONS(a_iStart + 10, a_uK, a_fnFt); \
+ FIVE_ITERATIONS(a_iStart + 15, a_uK, a_fnFt); \
+ } while (0)
+
+ TWENTY_ITERATIONS( 0, UINT32_C(0x5a827999), rtSha1Ch);
+ TWENTY_ITERATIONS(20, UINT32_C(0x6ed9eba1), rtSha1Parity);
+ TWENTY_ITERATIONS(40, UINT32_C(0x8f1bbcdc), rtSha1Maj);
+ TWENTY_ITERATIONS(60, UINT32_C(0xca62c1d6), rtSha1Parity);
+
+#elif 1 /* Version avoiding the constant selection. */
+ unsigned iWord = 0;
+# define TWENTY_ITERATIONS(a_iWordStop, a_uK, a_uExprBCD) \
+ for (; iWord < a_iWordStop; iWord++) \
+ { \
+ uint32_t uTemp = ASMRotateLeftU32(uA, 5); \
+ uTemp += (a_uExprBCD); \
+ uTemp += uE; \
+ uTemp += pCtx->AltPrivate.auW[iWord]; \
+ uTemp += (a_uK); \
+ \
+ uE = uD; \
+ uD = uC; \
+ uC = ASMRotateLeftU32(uB, 30); \
+ uB = uA; \
+ uA = uTemp; \
+ } do { } while (0)
+ TWENTY_ITERATIONS(20, UINT32_C(0x5a827999), rtSha1Ch(uB, uC, uD));
+ TWENTY_ITERATIONS(40, UINT32_C(0x6ed9eba1), rtSha1Parity(uB, uC, uD));
+ TWENTY_ITERATIONS(60, UINT32_C(0x8f1bbcdc), rtSha1Maj(uB, uC, uD));
+ TWENTY_ITERATIONS(80, UINT32_C(0xca62c1d6), rtSha1Parity(uB, uC, uD));
+
+#else /* Dead simple implementation. */
+ for (unsigned iWord = 0; iWord < RT_ELEMENTS(pCtx->AltPrivate.auW); iWord++)
+ {
+ uint32_t uTemp = ASMRotateLeftU32(uA, 5);
+ uTemp += uE;
+ uTemp += pCtx->AltPrivate.auW[iWord];
+ if (iWord <= 19)
+ {
+ uTemp += (uB & uC) | (~uB & uD);
+ uTemp += UINT32_C(0x5a827999);
+ }
+ else if (iWord <= 39)
+ {
+ uTemp += uB ^ uC ^ uD;
+ uTemp += UINT32_C(0x6ed9eba1);
+ }
+ else if (iWord <= 59)
+ {
+ uTemp += (uB & uC) | (uB & uD) | (uC & uD);
+ uTemp += UINT32_C(0x8f1bbcdc);
+ }
+ else
+ {
+ uTemp += uB ^ uC ^ uD;
+ uTemp += UINT32_C(0xca62c1d6);
+ }
+
+ uE = uD;
+ uD = uC;
+ uC = ASMRotateLeftU32(uB, 30);
+ uB = uA;
+ uA = uTemp;
+ }
+#endif
+
+ pCtx->AltPrivate.auH[0] += uA;
+ pCtx->AltPrivate.auH[1] += uB;
+ pCtx->AltPrivate.auH[2] += uC;
+ pCtx->AltPrivate.auH[3] += uD;
+ pCtx->AltPrivate.auH[4] += uE;
+}
+
+
+RTDECL(void) RTSha1Update(PRTSHA1CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ Assert(pCtx->AltPrivate.cbMessage < UINT64_MAX / 2);
+ uint8_t const *pbBuf = (uint8_t const *)pvBuf;
+
+ /*
+ * Deal with buffered bytes first.
+ */
+ size_t cbBuffered = (size_t)pCtx->AltPrivate.cbMessage & (RTSHA1_BLOCK_SIZE - 1U);
+ if (cbBuffered)
+ {
+ size_t cbMissing = RTSHA1_BLOCK_SIZE - cbBuffered;
+ if (cbBuf >= cbMissing)
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0] + cbBuffered, pbBuf, cbMissing);
+ pCtx->AltPrivate.cbMessage += cbMissing;
+ pbBuf += cbMissing;
+ cbBuf -= cbMissing;
+
+ rtSha1BlockInitBuffered(pCtx);
+ rtSha1BlockProcess(pCtx);
+ }
+ else
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0] + cbBuffered, pbBuf, cbBuf);
+ pCtx->AltPrivate.cbMessage += cbBuf;
+ return;
+ }
+ }
+
+ if (!((uintptr_t)pbBuf & 3))
+ {
+ /*
+ * Process full blocks directly from the input buffer.
+ */
+ while (cbBuf >= RTSHA1_BLOCK_SIZE)
+ {
+ rtSha1BlockInit(pCtx, pbBuf);
+ rtSha1BlockProcess(pCtx);
+
+ pCtx->AltPrivate.cbMessage += RTSHA1_BLOCK_SIZE;
+ pbBuf += RTSHA1_BLOCK_SIZE;
+ cbBuf -= RTSHA1_BLOCK_SIZE;
+ }
+ }
+ else
+ {
+ /*
+ * Unaligned input, so buffer it.
+ */
+ while (cbBuf >= RTSHA1_BLOCK_SIZE)
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0], pbBuf, RTSHA1_BLOCK_SIZE);
+ rtSha1BlockInitBuffered(pCtx);
+ rtSha1BlockProcess(pCtx);
+
+ pCtx->AltPrivate.cbMessage += RTSHA1_BLOCK_SIZE;
+ pbBuf += RTSHA1_BLOCK_SIZE;
+ cbBuf -= RTSHA1_BLOCK_SIZE;
+ }
+ }
+
+ /*
+ * Stash any remaining bytes into the context buffer.
+ */
+ if (cbBuf > 0)
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0], pbBuf, cbBuf);
+ pCtx->AltPrivate.cbMessage += cbBuf;
+ }
+}
+RT_EXPORT_SYMBOL(RTSha1Update);
+
+
+static void rtSha1FinalInternal(PRTSHA1CONTEXT pCtx)
+{
+ Assert(pCtx->AltPrivate.cbMessage < UINT64_MAX / 2);
+
+ /*
+ * Complete the message by adding a single bit (0x80), padding till
+ * the next 448-bit boundrary, the add the message length.
+ */
+ uint64_t const cMessageBits = pCtx->AltPrivate.cbMessage * 8;
+
+ unsigned cbMissing = RTSHA1_BLOCK_SIZE - ((unsigned)pCtx->AltPrivate.cbMessage & (RTSHA1_BLOCK_SIZE - 1U));
+ static uint8_t const s_abSingleBitAndSomePadding[12] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+ if (cbMissing < 1U + 8U)
+ /* Less than 64+8 bits left in the current block, force a new block. */
+ RTSha1Update(pCtx, &s_abSingleBitAndSomePadding, sizeof(s_abSingleBitAndSomePadding));
+ else
+ RTSha1Update(pCtx, &s_abSingleBitAndSomePadding, 1);
+
+ unsigned cbBuffered = (unsigned)pCtx->AltPrivate.cbMessage & (RTSHA1_BLOCK_SIZE - 1U);
+ cbMissing = RTSHA1_BLOCK_SIZE - cbBuffered;
+ Assert(cbMissing >= 8);
+ memset((uint8_t *)&pCtx->AltPrivate.auW[0] + cbBuffered, 0, cbMissing - 8);
+
+ *(uint64_t *)&pCtx->AltPrivate.auW[14] = RT_H2BE_U64(cMessageBits);
+
+ /*
+ * Process the last buffered block constructed/completed above.
+ */
+ rtSha1BlockInitBuffered(pCtx);
+ rtSha1BlockProcess(pCtx);
+
+ /*
+ * Convert the byte order of the hash words and we're done.
+ */
+ pCtx->AltPrivate.auH[0] = RT_H2BE_U32(pCtx->AltPrivate.auH[0]);
+ pCtx->AltPrivate.auH[1] = RT_H2BE_U32(pCtx->AltPrivate.auH[1]);
+ pCtx->AltPrivate.auH[2] = RT_H2BE_U32(pCtx->AltPrivate.auH[2]);
+ pCtx->AltPrivate.auH[3] = RT_H2BE_U32(pCtx->AltPrivate.auH[3]);
+ pCtx->AltPrivate.auH[4] = RT_H2BE_U32(pCtx->AltPrivate.auH[4]);
+}
+
+
+DECLINLINE(void) rtSha1WipeCtx(PRTSHA1CONTEXT pCtx)
+{
+ RT_ZERO(pCtx->AltPrivate);
+ pCtx->AltPrivate.cbMessage = UINT64_MAX;
+}
+
+
+RTDECL(void) RTSha1Final(PRTSHA1CONTEXT pCtx, uint8_t pabDigest[RTSHA1_HASH_SIZE])
+{
+ rtSha1FinalInternal(pCtx);
+ memcpy(pabDigest, &pCtx->AltPrivate.auH[0], RTSHA1_HASH_SIZE);
+ rtSha1WipeCtx(pCtx);
+}
+RT_EXPORT_SYMBOL(RTSha1Final);
+
+
+RTDECL(void) RTSha1(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA1_HASH_SIZE])
+{
+ RTSHA1CONTEXT Ctx;
+ RTSha1Init(&Ctx);
+ RTSha1Update(&Ctx, pvBuf, cbBuf);
+ RTSha1Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha1);
+
+
+RTDECL(bool) RTSha1Check(const void *pvBuf, size_t cbBuf, uint8_t const pabHash[RTSHA1_HASH_SIZE])
+{
+ RTSHA1CONTEXT Ctx;
+ RTSha1Init(&Ctx);
+ RTSha1Update(&Ctx, pvBuf, cbBuf);
+ rtSha1FinalInternal(&Ctx);
+
+ bool fRet = memcmp(pabHash, &Ctx.AltPrivate.auH[0], RTSHA1_HASH_SIZE) == 0;
+
+ rtSha1WipeCtx(&Ctx);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha1Check);
+
diff --git a/src/VBox/Runtime/common/checksum/alt-sha256.cpp b/src/VBox/Runtime/common/checksum/alt-sha256.cpp
new file mode 100644
index 00000000..28acca96
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/alt-sha256.cpp
@@ -0,0 +1,693 @@
+/* $Id: alt-sha256.cpp $ */
+/** @file
+ * IPRT - SHA-256 and SHA-224 hash functions, Alternative Implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The SHA-256 block size (in bytes). */
+#define RTSHA256_BLOCK_SIZE 64U
+
+/** Enables the unrolled code. */
+#define RTSHA256_UNROLLED 1
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+
+
+/** Our private context structure. */
+typedef struct RTSHA256ALTPRIVATECTX
+{
+ /** The W array.
+ * Buffering happens in the first 16 words, converted from big endian to host
+ * endian immediately before processing. The amount of buffered data is kept
+ * in the 6 least significant bits of cbMessage. */
+ uint32_t auW[64];
+ /** The message length (in bytes). */
+ uint64_t cbMessage;
+ /** The 8 hash values. */
+ uint32_t auH[8];
+} RTSHA256ALTPRIVATECTX;
+
+#define RT_SHA256_PRIVATE_ALT_CONTEXT
+#include <iprt/sha.h>
+
+
+AssertCompile(RT_SIZEOFMEMB(RTSHA256CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTSHA256CONTEXT, AltPrivate));
+AssertCompileMemberSize(RTSHA256ALTPRIVATECTX, auH, RTSHA256_HASH_SIZE);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifndef RTSHA256_UNROLLED
+/** The K constants */
+static uint32_t const g_auKs[] =
+{
+ UINT32_C(0x428a2f98), UINT32_C(0x71374491), UINT32_C(0xb5c0fbcf), UINT32_C(0xe9b5dba5),
+ UINT32_C(0x3956c25b), UINT32_C(0x59f111f1), UINT32_C(0x923f82a4), UINT32_C(0xab1c5ed5),
+ UINT32_C(0xd807aa98), UINT32_C(0x12835b01), UINT32_C(0x243185be), UINT32_C(0x550c7dc3),
+ UINT32_C(0x72be5d74), UINT32_C(0x80deb1fe), UINT32_C(0x9bdc06a7), UINT32_C(0xc19bf174),
+ UINT32_C(0xe49b69c1), UINT32_C(0xefbe4786), UINT32_C(0x0fc19dc6), UINT32_C(0x240ca1cc),
+ UINT32_C(0x2de92c6f), UINT32_C(0x4a7484aa), UINT32_C(0x5cb0a9dc), UINT32_C(0x76f988da),
+ UINT32_C(0x983e5152), UINT32_C(0xa831c66d), UINT32_C(0xb00327c8), UINT32_C(0xbf597fc7),
+ UINT32_C(0xc6e00bf3), UINT32_C(0xd5a79147), UINT32_C(0x06ca6351), UINT32_C(0x14292967),
+ UINT32_C(0x27b70a85), UINT32_C(0x2e1b2138), UINT32_C(0x4d2c6dfc), UINT32_C(0x53380d13),
+ UINT32_C(0x650a7354), UINT32_C(0x766a0abb), UINT32_C(0x81c2c92e), UINT32_C(0x92722c85),
+ UINT32_C(0xa2bfe8a1), UINT32_C(0xa81a664b), UINT32_C(0xc24b8b70), UINT32_C(0xc76c51a3),
+ UINT32_C(0xd192e819), UINT32_C(0xd6990624), UINT32_C(0xf40e3585), UINT32_C(0x106aa070),
+ UINT32_C(0x19a4c116), UINT32_C(0x1e376c08), UINT32_C(0x2748774c), UINT32_C(0x34b0bcb5),
+ UINT32_C(0x391c0cb3), UINT32_C(0x4ed8aa4a), UINT32_C(0x5b9cca4f), UINT32_C(0x682e6ff3),
+ UINT32_C(0x748f82ee), UINT32_C(0x78a5636f), UINT32_C(0x84c87814), UINT32_C(0x8cc70208),
+ UINT32_C(0x90befffa), UINT32_C(0xa4506ceb), UINT32_C(0xbef9a3f7), UINT32_C(0xc67178f2),
+};
+#endif /* !RTSHA256_UNROLLED */
+
+
+
+RTDECL(void) RTSha256Init(PRTSHA256CONTEXT pCtx)
+{
+ pCtx->AltPrivate.cbMessage = 0;
+ pCtx->AltPrivate.auH[0] = UINT32_C(0x6a09e667);
+ pCtx->AltPrivate.auH[1] = UINT32_C(0xbb67ae85);
+ pCtx->AltPrivate.auH[2] = UINT32_C(0x3c6ef372);
+ pCtx->AltPrivate.auH[3] = UINT32_C(0xa54ff53a);
+ pCtx->AltPrivate.auH[4] = UINT32_C(0x510e527f);
+ pCtx->AltPrivate.auH[5] = UINT32_C(0x9b05688c);
+ pCtx->AltPrivate.auH[6] = UINT32_C(0x1f83d9ab);
+ pCtx->AltPrivate.auH[7] = UINT32_C(0x5be0cd19);
+}
+RT_EXPORT_SYMBOL(RTSha256Init);
+
+
+/** Function 4.2. */
+DECL_FORCE_INLINE(uint32_t) rtSha256Ch(uint32_t uX, uint32_t uY, uint32_t uZ)
+{
+#if 1
+ /* Optimization that saves one operation and probably a temporary variable. */
+ uint32_t uResult = uY;
+ uResult ^= uZ;
+ uResult &= uX;
+ uResult ^= uZ;
+ return uResult;
+#else
+ /* The original. */
+ uint32_t uResult = uX & uY;
+ uResult ^= ~uX & uZ;
+ return uResult;
+#endif
+}
+
+
+/** Function 4.3. */
+DECL_FORCE_INLINE(uint32_t) rtSha256Maj(uint32_t uX, uint32_t uY, uint32_t uZ)
+{
+#if 1
+ /* Optimization that save one operation and probably a temporary variable. */
+ uint32_t uResult = uY;
+ uResult ^= uZ;
+ uResult &= uX;
+ uResult ^= uY & uZ;
+ return uResult;
+#else
+ /* The original. */
+ uint32_t uResult = uX & uY;
+ uResult ^= uX & uZ;
+ uResult ^= uY & uZ;
+ return uResult;
+#endif
+}
+
+
+/** Function 4.4. */
+DECL_FORCE_INLINE(uint32_t) rtSha256CapitalSigma0(uint32_t uX)
+{
+ uint32_t uResult = uX = ASMRotateRightU32(uX, 2);
+ uX = ASMRotateRightU32(uX, 13 - 2);
+ uResult ^= uX;
+ uX = ASMRotateRightU32(uX, 22 - 13);
+ uResult ^= uX;
+ return uResult;
+}
+
+
+/** Function 4.5. */
+DECL_FORCE_INLINE(uint32_t) rtSha256CapitalSigma1(uint32_t uX)
+{
+ uint32_t uResult = uX = ASMRotateRightU32(uX, 6);
+ uX = ASMRotateRightU32(uX, 11 - 6);
+ uResult ^= uX;
+ uX = ASMRotateRightU32(uX, 25 - 11);
+ uResult ^= uX;
+ return uResult;
+}
+
+
+/** Function 4.6. */
+DECL_FORCE_INLINE(uint32_t) rtSha256SmallSigma0(uint32_t uX)
+{
+ uint32_t uResult = uX >> 3;
+ uX = ASMRotateRightU32(uX, 7);
+ uResult ^= uX;
+ uX = ASMRotateRightU32(uX, 18 - 7);
+ uResult ^= uX;
+ return uResult;
+}
+
+
+/** Function 4.7. */
+DECL_FORCE_INLINE(uint32_t) rtSha256SmallSigma1(uint32_t uX)
+{
+ uint32_t uResult = uX >> 10;
+ uX = ASMRotateRightU32(uX, 17);
+ uResult ^= uX;
+ uX = ASMRotateRightU32(uX, 19 - 17);
+ uResult ^= uX;
+ return uResult;
+}
+
+
+/**
+ * Initializes the auW array from the specfied input block.
+ *
+ * @param pCtx The SHA-256 context.
+ * @param pbBlock The block. Must be arch-bit-width aligned.
+ */
+DECLINLINE(void) rtSha256BlockInit(PRTSHA256CONTEXT pCtx, uint8_t const *pbBlock)
+{
+#ifdef RTSHA256_UNROLLED
+ /* Copy and byte-swap the block. Initializing the rest of the Ws are done
+ in the processing loop. */
+# ifdef RT_LITTLE_ENDIAN
+# if 0 /* Just an idea... very little gain as this isn't the expensive code. */
+ __m128i const uBSwapConst = { 3, 2, 1, 0, 7, 6, 5, 4, 11, 10, 9, 8, 15, 14, 13, 12 };
+ __m128i const *puSrc = (__m128i const *)pbBlock;
+ __m128i *puDst = (__m128i *)&pCtx->AltPrivate.auW[0];
+
+ _mm_storeu_si128(puDst, _mm_shuffle_epi8(_mm_loadu_si128(puSrc), uBSwapConst)); puDst++; puSrc++;
+ _mm_storeu_si128(puDst, _mm_shuffle_epi8(_mm_loadu_si128(puSrc), uBSwapConst)); puDst++; puSrc++;
+ _mm_storeu_si128(puDst, _mm_shuffle_epi8(_mm_loadu_si128(puSrc), uBSwapConst)); puDst++; puSrc++;
+ _mm_storeu_si128(puDst, _mm_shuffle_epi8(_mm_loadu_si128(puSrc), uBSwapConst)); puDst++; puSrc++;
+
+# elif ARCH_BITS == 64
+ uint64_t const *puSrc = (uint64_t const *)pbBlock;
+ uint64_t *puW = (uint64_t *)&pCtx->AltPrivate.auW[0];
+ Assert(!((uintptr_t)puSrc & 7));
+ Assert(!((uintptr_t)puW & 7));
+
+ /* b0 b1 b2 b3 b4 b5 b6 b7 --bwap--> b7 b6 b5 b4 b3 b2 b1 b0 --ror--> b3 b2 b1 b0 b7 b6 b5 b4; */
+ *puW++ = ASMRotateRightU64(ASMByteSwapU64(*puSrc++), 32);
+ *puW++ = ASMRotateRightU64(ASMByteSwapU64(*puSrc++), 32);
+ *puW++ = ASMRotateRightU64(ASMByteSwapU64(*puSrc++), 32);
+ *puW++ = ASMRotateRightU64(ASMByteSwapU64(*puSrc++), 32);
+
+ *puW++ = ASMRotateRightU64(ASMByteSwapU64(*puSrc++), 32);
+ *puW++ = ASMRotateRightU64(ASMByteSwapU64(*puSrc++), 32);
+ *puW++ = ASMRotateRightU64(ASMByteSwapU64(*puSrc++), 32);
+ *puW++ = ASMRotateRightU64(ASMByteSwapU64(*puSrc++), 32);
+
+# else
+ uint32_t const *puSrc = (uint32_t const *)pbBlock;
+ uint32_t *puW = &pCtx->AltPrivate.auW[0];
+ Assert(!((uintptr_t)puSrc & 3));
+ Assert(!((uintptr_t)puW & 3));
+
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+ *puW++ = ASMByteSwapU32(*puSrc++);
+# endif
+# else /* RT_BIG_ENDIAN */
+ memcpy(&pCtx->AltPrivate.auW[0], pbBlock, RTSHA256_BLOCK_SIZE);
+# endif /* RT_BIG_ENDIAN */
+
+#else /* !RTSHA256_UNROLLED */
+ uint32_t const *pu32Block = (uint32_t const *)pbBlock;
+ Assert(!((uintptr_t)pu32Block & 3));
+
+ unsigned iWord;
+ for (iWord = 0; iWord < 16; iWord++)
+ pCtx->AltPrivate.auW[iWord] = RT_BE2H_U32(pu32Block[iWord]);
+
+ for (; iWord < RT_ELEMENTS(pCtx->AltPrivate.auW); iWord++)
+ {
+ uint32_t u32 = rtSha256SmallSigma1(pCtx->AltPrivate.auW[iWord - 2]);
+ u32 += rtSha256SmallSigma0(pCtx->AltPrivate.auW[iWord - 15]);
+ u32 += pCtx->AltPrivate.auW[iWord - 7];
+ u32 += pCtx->AltPrivate.auW[iWord - 16];
+ pCtx->AltPrivate.auW[iWord] = u32;
+ }
+#endif /* !RTSHA256_UNROLLED */
+}
+
+
+/**
+ * Initializes the auW array from data buffered in the first part of the array.
+ *
+ * @param pCtx The SHA-256 context.
+ */
+DECLINLINE(void) rtSha256BlockInitBuffered(PRTSHA256CONTEXT pCtx)
+{
+#ifdef RTSHA256_UNROLLED
+ /* Do the byte swap if necessary. Initializing the rest of the Ws are done
+ in the processing loop. */
+# ifdef RT_LITTLE_ENDIAN
+# if ARCH_BITS == 64
+ uint64_t *puW = (uint64_t *)&pCtx->AltPrivate.auW[0];
+ Assert(!((uintptr_t)puW & 7));
+ /* b0 b1 b2 b3 b4 b5 b6 b7 --bwap--> b7 b6 b5 b4 b3 b2 b1 b0 --ror--> b3 b2 b1 b0 b7 b6 b5 b4; */
+ *puW = ASMRotateRightU64(ASMByteSwapU64(*puW), 32); puW++;
+ *puW = ASMRotateRightU64(ASMByteSwapU64(*puW), 32); puW++;
+ *puW = ASMRotateRightU64(ASMByteSwapU64(*puW), 32); puW++;
+ *puW = ASMRotateRightU64(ASMByteSwapU64(*puW), 32); puW++;
+
+ *puW = ASMRotateRightU64(ASMByteSwapU64(*puW), 32); puW++;
+ *puW = ASMRotateRightU64(ASMByteSwapU64(*puW), 32); puW++;
+ *puW = ASMRotateRightU64(ASMByteSwapU64(*puW), 32); puW++;
+ *puW = ASMRotateRightU64(ASMByteSwapU64(*puW), 32); puW++;
+
+# else
+ uint32_t *puW = &pCtx->AltPrivate.auW[0];
+ Assert(!((uintptr_t)puW & 3));
+
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+ *puW = ASMByteSwapU32(*puW); puW++;
+# endif
+# endif
+
+#else /* !RTSHA256_UNROLLED */
+ unsigned iWord;
+ for (iWord = 0; iWord < 16; iWord++)
+ pCtx->AltPrivate.auW[iWord] = RT_BE2H_U32(pCtx->AltPrivate.auW[iWord]);
+
+ for (; iWord < RT_ELEMENTS(pCtx->AltPrivate.auW); iWord++)
+ {
+ uint32_t u32 = rtSha256SmallSigma1(pCtx->AltPrivate.auW[iWord - 2]);
+ u32 += rtSha256SmallSigma0(pCtx->AltPrivate.auW[iWord - 15]);
+ u32 += pCtx->AltPrivate.auW[iWord - 7];
+ u32 += pCtx->AltPrivate.auW[iWord - 16];
+ pCtx->AltPrivate.auW[iWord] = u32;
+ }
+#endif /* !RTSHA256_UNROLLED */
+}
+
+
+/**
+ * Process the current block.
+ *
+ * Requires one of the rtSha256BlockInit functions to be called first.
+ *
+ * @param pCtx The SHA-256 context.
+ */
+static void rtSha256BlockProcess(PRTSHA256CONTEXT pCtx)
+{
+ uint32_t uA = pCtx->AltPrivate.auH[0];
+ uint32_t uB = pCtx->AltPrivate.auH[1];
+ uint32_t uC = pCtx->AltPrivate.auH[2];
+ uint32_t uD = pCtx->AltPrivate.auH[3];
+ uint32_t uE = pCtx->AltPrivate.auH[4];
+ uint32_t uF = pCtx->AltPrivate.auH[5];
+ uint32_t uG = pCtx->AltPrivate.auH[6];
+ uint32_t uH = pCtx->AltPrivate.auH[7];
+
+#ifdef RTSHA256_UNROLLED
+ uint32_t *puW = &pCtx->AltPrivate.auW[0];
+# define RTSHA256_BODY(a_iWord, a_uK, a_uA, a_uB, a_uC, a_uD, a_uE, a_uF, a_uG, a_uH) \
+ do { \
+ if ((a_iWord) < 16) \
+ a_uH += *puW++; \
+ else \
+ { \
+ uint32_t u32 = puW[-16]; \
+ u32 += rtSha256SmallSigma0(puW[-15]); \
+ u32 += puW[-7]; \
+ u32 += rtSha256SmallSigma1(puW[-2]); \
+ if (a_iWord < 64-2) *puW++ = u32; else puW++; \
+ a_uH += u32; \
+ } \
+ \
+ a_uH += rtSha256CapitalSigma1(a_uE); \
+ a_uH += a_uK; \
+ a_uH += rtSha256Ch(a_uE, a_uF, a_uG); \
+ a_uD += a_uH; \
+ \
+ a_uH += rtSha256CapitalSigma0(a_uA); \
+ a_uH += rtSha256Maj(a_uA, a_uB, a_uC); \
+ } while (0)
+# define RTSHA256_EIGHT(a_uK0, a_uK1, a_uK2, a_uK3, a_uK4, a_uK5, a_uK6, a_uK7, a_iFirst) \
+ do { \
+ RTSHA256_BODY(a_iFirst + 0, a_uK0, uA, uB, uC, uD, uE, uF, uG, uH); \
+ RTSHA256_BODY(a_iFirst + 1, a_uK1, uH, uA, uB, uC, uD, uE, uF, uG); \
+ RTSHA256_BODY(a_iFirst + 2, a_uK2, uG, uH, uA, uB, uC, uD, uE, uF); \
+ RTSHA256_BODY(a_iFirst + 3, a_uK3, uF, uG, uH, uA, uB, uC, uD, uE); \
+ RTSHA256_BODY(a_iFirst + 4, a_uK4, uE, uF, uG, uH, uA, uB, uC, uD); \
+ RTSHA256_BODY(a_iFirst + 5, a_uK5, uD, uE, uF, uG, uH, uA, uB, uC); \
+ RTSHA256_BODY(a_iFirst + 6, a_uK6, uC, uD, uE, uF, uG, uH, uA, uB); \
+ RTSHA256_BODY(a_iFirst + 7, a_uK7, uB, uC, uD, uE, uF, uG, uH, uA); \
+ } while (0)
+ RTSHA256_EIGHT(UINT32_C(0x428a2f98), UINT32_C(0x71374491), UINT32_C(0xb5c0fbcf), UINT32_C(0xe9b5dba5),
+ UINT32_C(0x3956c25b), UINT32_C(0x59f111f1), UINT32_C(0x923f82a4), UINT32_C(0xab1c5ed5), 0);
+ RTSHA256_EIGHT(UINT32_C(0xd807aa98), UINT32_C(0x12835b01), UINT32_C(0x243185be), UINT32_C(0x550c7dc3),
+ UINT32_C(0x72be5d74), UINT32_C(0x80deb1fe), UINT32_C(0x9bdc06a7), UINT32_C(0xc19bf174), 8);
+ RTSHA256_EIGHT(UINT32_C(0xe49b69c1), UINT32_C(0xefbe4786), UINT32_C(0x0fc19dc6), UINT32_C(0x240ca1cc),
+ UINT32_C(0x2de92c6f), UINT32_C(0x4a7484aa), UINT32_C(0x5cb0a9dc), UINT32_C(0x76f988da), 16);
+ RTSHA256_EIGHT(UINT32_C(0x983e5152), UINT32_C(0xa831c66d), UINT32_C(0xb00327c8), UINT32_C(0xbf597fc7),
+ UINT32_C(0xc6e00bf3), UINT32_C(0xd5a79147), UINT32_C(0x06ca6351), UINT32_C(0x14292967), 24);
+ RTSHA256_EIGHT(UINT32_C(0x27b70a85), UINT32_C(0x2e1b2138), UINT32_C(0x4d2c6dfc), UINT32_C(0x53380d13),
+ UINT32_C(0x650a7354), UINT32_C(0x766a0abb), UINT32_C(0x81c2c92e), UINT32_C(0x92722c85), 32);
+ RTSHA256_EIGHT(UINT32_C(0xa2bfe8a1), UINT32_C(0xa81a664b), UINT32_C(0xc24b8b70), UINT32_C(0xc76c51a3),
+ UINT32_C(0xd192e819), UINT32_C(0xd6990624), UINT32_C(0xf40e3585), UINT32_C(0x106aa070), 40);
+ RTSHA256_EIGHT(UINT32_C(0x19a4c116), UINT32_C(0x1e376c08), UINT32_C(0x2748774c), UINT32_C(0x34b0bcb5),
+ UINT32_C(0x391c0cb3), UINT32_C(0x4ed8aa4a), UINT32_C(0x5b9cca4f), UINT32_C(0x682e6ff3), 48);
+ RTSHA256_EIGHT(UINT32_C(0x748f82ee), UINT32_C(0x78a5636f), UINT32_C(0x84c87814), UINT32_C(0x8cc70208),
+ UINT32_C(0x90befffa), UINT32_C(0xa4506ceb), UINT32_C(0xbef9a3f7), UINT32_C(0xc67178f2), 56);
+
+#else /* !RTSHA256_UNROLLED */
+ for (unsigned iWord = 0; iWord < RT_ELEMENTS(pCtx->AltPrivate.auW); iWord++)
+ {
+ uint32_t uT1 = uH;
+ uT1 += rtSha256CapitalSigma1(uE);
+ uT1 += rtSha256Ch(uE, uF, uG);
+ uT1 += g_auKs[iWord];
+ uT1 += pCtx->AltPrivate.auW[iWord];
+
+ uint32_t uT2 = rtSha256CapitalSigma0(uA);
+ uT2 += rtSha256Maj(uA, uB, uC);
+
+ uH = uG;
+ uG = uF;
+ uF = uE;
+ uE = uD + uT1;
+ uD = uC;
+ uC = uB;
+ uB = uA;
+ uA = uT1 + uT2;
+ }
+#endif /* !RTSHA256_UNROLLED */
+
+ pCtx->AltPrivate.auH[0] += uA;
+ pCtx->AltPrivate.auH[1] += uB;
+ pCtx->AltPrivate.auH[2] += uC;
+ pCtx->AltPrivate.auH[3] += uD;
+ pCtx->AltPrivate.auH[4] += uE;
+ pCtx->AltPrivate.auH[5] += uF;
+ pCtx->AltPrivate.auH[6] += uG;
+ pCtx->AltPrivate.auH[7] += uH;
+}
+
+
+RTDECL(void) RTSha256Update(PRTSHA256CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ Assert(pCtx->AltPrivate.cbMessage < UINT64_MAX / 8);
+ uint8_t const *pbBuf = (uint8_t const *)pvBuf;
+
+ /*
+ * Deal with buffered bytes first.
+ */
+ size_t cbBuffered = (size_t)pCtx->AltPrivate.cbMessage & (RTSHA256_BLOCK_SIZE - 1U);
+ if (cbBuffered)
+ {
+ size_t cbMissing = RTSHA256_BLOCK_SIZE - cbBuffered;
+ if (cbBuf >= cbMissing)
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0] + cbBuffered, pbBuf, cbMissing);
+ pCtx->AltPrivate.cbMessage += cbMissing;
+ pbBuf += cbMissing;
+ cbBuf -= cbMissing;
+
+ rtSha256BlockInitBuffered(pCtx);
+ rtSha256BlockProcess(pCtx);
+ }
+ else
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0] + cbBuffered, pbBuf, cbBuf);
+ pCtx->AltPrivate.cbMessage += cbBuf;
+ return;
+ }
+ }
+
+ if (!((uintptr_t)pbBuf & (sizeof(void *) - 1)))
+ {
+ /*
+ * Process full blocks directly from the input buffer.
+ */
+ while (cbBuf >= RTSHA256_BLOCK_SIZE)
+ {
+ rtSha256BlockInit(pCtx, pbBuf);
+ rtSha256BlockProcess(pCtx);
+
+ pCtx->AltPrivate.cbMessage += RTSHA256_BLOCK_SIZE;
+ pbBuf += RTSHA256_BLOCK_SIZE;
+ cbBuf -= RTSHA256_BLOCK_SIZE;
+ }
+ }
+ else
+ {
+ /*
+ * Unaligned input, so buffer it.
+ */
+ while (cbBuf >= RTSHA256_BLOCK_SIZE)
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0], pbBuf, RTSHA256_BLOCK_SIZE);
+ rtSha256BlockInitBuffered(pCtx);
+ rtSha256BlockProcess(pCtx);
+
+ pCtx->AltPrivate.cbMessage += RTSHA256_BLOCK_SIZE;
+ pbBuf += RTSHA256_BLOCK_SIZE;
+ cbBuf -= RTSHA256_BLOCK_SIZE;
+ }
+ }
+
+ /*
+ * Stash any remaining bytes into the context buffer.
+ */
+ if (cbBuf > 0)
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0], pbBuf, cbBuf);
+ pCtx->AltPrivate.cbMessage += cbBuf;
+ }
+}
+RT_EXPORT_SYMBOL(RTSha256Update);
+
+
+/**
+ * Internal worker for RTSha256Final and RTSha224Final that finalizes the
+ * computation but does not copy out the hash value.
+ *
+ * @param pCtx The SHA-256 context.
+ */
+static void rtSha256FinalInternal(PRTSHA256CONTEXT pCtx)
+{
+ Assert(pCtx->AltPrivate.cbMessage < UINT64_MAX / 8);
+
+ /*
+ * Complete the message by adding a single bit (0x80), padding till
+ * the next 448-bit boundrary, the add the message length.
+ */
+ uint64_t const cMessageBits = pCtx->AltPrivate.cbMessage * 8;
+
+ unsigned cbMissing = RTSHA256_BLOCK_SIZE - ((unsigned)pCtx->AltPrivate.cbMessage & (RTSHA256_BLOCK_SIZE - 1U));
+ static uint8_t const s_abSingleBitAndSomePadding[12] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+ if (cbMissing < 1U + 8U)
+ /* Less than 64+8 bits left in the current block, force a new block. */
+ RTSha256Update(pCtx, &s_abSingleBitAndSomePadding, sizeof(s_abSingleBitAndSomePadding));
+ else
+ RTSha256Update(pCtx, &s_abSingleBitAndSomePadding, 1);
+
+ unsigned cbBuffered = (unsigned)pCtx->AltPrivate.cbMessage & (RTSHA256_BLOCK_SIZE - 1U);
+ cbMissing = RTSHA256_BLOCK_SIZE - cbBuffered;
+ Assert(cbMissing >= 8);
+ memset((uint8_t *)&pCtx->AltPrivate.auW[0] + cbBuffered, 0, cbMissing - 8);
+
+ *(uint64_t *)&pCtx->AltPrivate.auW[14] = RT_H2BE_U64(cMessageBits);
+
+ /*
+ * Process the last buffered block constructed/completed above.
+ */
+ rtSha256BlockInitBuffered(pCtx);
+ rtSha256BlockProcess(pCtx);
+
+ /*
+ * Convert the byte order of the hash words and we're done.
+ */
+ pCtx->AltPrivate.auH[0] = RT_H2BE_U32(pCtx->AltPrivate.auH[0]);
+ pCtx->AltPrivate.auH[1] = RT_H2BE_U32(pCtx->AltPrivate.auH[1]);
+ pCtx->AltPrivate.auH[2] = RT_H2BE_U32(pCtx->AltPrivate.auH[2]);
+ pCtx->AltPrivate.auH[3] = RT_H2BE_U32(pCtx->AltPrivate.auH[3]);
+ pCtx->AltPrivate.auH[4] = RT_H2BE_U32(pCtx->AltPrivate.auH[4]);
+ pCtx->AltPrivate.auH[5] = RT_H2BE_U32(pCtx->AltPrivate.auH[5]);
+ pCtx->AltPrivate.auH[6] = RT_H2BE_U32(pCtx->AltPrivate.auH[6]);
+ pCtx->AltPrivate.auH[7] = RT_H2BE_U32(pCtx->AltPrivate.auH[7]);
+
+ RT_ZERO(pCtx->AltPrivate.auW);
+ pCtx->AltPrivate.cbMessage = UINT64_MAX;
+}
+RT_EXPORT_SYMBOL(RTSha256Final);
+
+
+RTDECL(void) RTSha256Final(PRTSHA256CONTEXT pCtx, uint8_t pabDigest[RTSHA256_HASH_SIZE])
+{
+ rtSha256FinalInternal(pCtx);
+ memcpy(pabDigest, &pCtx->AltPrivate.auH[0], RTSHA256_HASH_SIZE);
+ RT_ZERO(pCtx->AltPrivate.auH);
+}
+RT_EXPORT_SYMBOL(RTSha256Final);
+
+
+RTDECL(void) RTSha256(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA256_HASH_SIZE])
+{
+ RTSHA256CONTEXT Ctx;
+ RTSha256Init(&Ctx);
+ RTSha256Update(&Ctx, pvBuf, cbBuf);
+ RTSha256Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha256);
+
+
+RTDECL(bool) RTSha256Check(const void *pvBuf, size_t cbBuf, uint8_t const pabHash[RTSHA256_HASH_SIZE])
+{
+ RTSHA256CONTEXT Ctx;
+ RTSha256Init(&Ctx);
+ RTSha256Update(&Ctx, pvBuf, cbBuf);
+ rtSha256FinalInternal(&Ctx);
+
+ bool fRet = memcmp(pabHash, &Ctx.AltPrivate.auH[0], RTSHA256_HASH_SIZE) == 0;
+
+ RT_ZERO(Ctx.AltPrivate.auH);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha256Check);
+
+
+
+/*
+ * SHA-224 is just SHA-256 with different initial values an a truncated result.
+ */
+
+RTDECL(void) RTSha224Init(PRTSHA224CONTEXT pCtx)
+{
+ pCtx->AltPrivate.cbMessage = 0;
+ pCtx->AltPrivate.auH[0] = UINT32_C(0xc1059ed8);
+ pCtx->AltPrivate.auH[1] = UINT32_C(0x367cd507);
+ pCtx->AltPrivate.auH[2] = UINT32_C(0x3070dd17);
+ pCtx->AltPrivate.auH[3] = UINT32_C(0xf70e5939);
+ pCtx->AltPrivate.auH[4] = UINT32_C(0xffc00b31);
+ pCtx->AltPrivate.auH[5] = UINT32_C(0x68581511);
+ pCtx->AltPrivate.auH[6] = UINT32_C(0x64f98fa7);
+ pCtx->AltPrivate.auH[7] = UINT32_C(0xbefa4fa4);
+}
+RT_EXPORT_SYMBOL(RTSha224Init);
+
+
+RTDECL(void) RTSha224Update(PRTSHA224CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ RTSha256Update(pCtx, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTSha224Update);
+
+
+RTDECL(void) RTSha224Final(PRTSHA224CONTEXT pCtx, uint8_t pabDigest[RTSHA224_HASH_SIZE])
+{
+ rtSha256FinalInternal(pCtx);
+ memcpy(pabDigest, &pCtx->AltPrivate.auH[0], RTSHA224_HASH_SIZE);
+ RT_ZERO(pCtx->AltPrivate.auH);
+}
+RT_EXPORT_SYMBOL(RTSha224Final);
+
+
+RTDECL(void) RTSha224(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA224_HASH_SIZE])
+{
+ RTSHA224CONTEXT Ctx;
+ RTSha224Init(&Ctx);
+ RTSha224Update(&Ctx, pvBuf, cbBuf);
+ RTSha224Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha224);
+
+
+RTDECL(bool) RTSha224Check(const void *pvBuf, size_t cbBuf, uint8_t const pabHash[RTSHA224_HASH_SIZE])
+{
+ RTSHA224CONTEXT Ctx;
+ RTSha224Init(&Ctx);
+ RTSha224Update(&Ctx, pvBuf, cbBuf);
+ rtSha256FinalInternal(&Ctx);
+
+ bool fRet = memcmp(pabHash, &Ctx.AltPrivate.auH[0], RTSHA224_HASH_SIZE) == 0;
+
+ RT_ZERO(Ctx.AltPrivate.auH);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha224Check);
+
diff --git a/src/VBox/Runtime/common/checksum/alt-sha3.cpp b/src/VBox/Runtime/common/checksum/alt-sha3.cpp
new file mode 100644
index 00000000..ef7c4959
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/alt-sha3.cpp
@@ -0,0 +1,642 @@
+/* $Id: alt-sha3.cpp $ */
+/** @file
+ * IPRT - SHA-3 hash functions, Alternative Implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Number of rounds [3.4]. */
+#define RTSHA3_ROUNDS 24
+
+/** @def RTSHA3_FULL_UNROLL
+ * Do full loop unrolling.
+ *
+ * With gcc 10.2.1 on a recent Intel system (10890XE), this results SHA3-512
+ * throughput (tstRTDigest-2) increasing from 83532 KiB/s to 194942 KiB/s
+ * against a text size jump from 5913 to 6929 bytes, i.e. +1016 bytes.
+ *
+ * With VS2019 on a half decent AMD system (3990X), this results in SHA3-512
+ * speedup from 147676 KiB/s to about 192770 KiB/s. The text cost is +612 bytes
+ * (4496 to 5108). When disabling the unrolling of Rho+Pi we get a little
+ * increase 196591 KiB/s (+3821) for some reason, saving 22 bytes of code.
+ *
+ * For comparison, openssl 1.1.1g assembly code (AMD64) achives 264915 KiB/s,
+ * which is only 36% more. Performance is more or less exactly the same as
+ * KECCAK_2X without ROL optimizations (they improve it to 203493 KiB/s).
+ */
+#if !defined(IN_SUP_HARDENED_R3) || defined(DOXYGEN_RUNNING)
+# define RTSHA3_FULL_UNROLL
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/assert.h>
+#include <iprt/assertcompile.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTSHA3ALTPRIVATECTX
+{
+ /** The KECCAK state (W=1600). */
+ union
+ {
+ uint64_t au64[/*1600/64 =*/ 25];
+ uint8_t ab[/*1600/8 =*/ 200];
+ };
+
+ /** Current input position. */
+ uint8_t offInput;
+ /** The number of bytes to xor into the state before doing KECCAK. */
+ uint8_t cbInput;
+ /** The digest size in bytes. */
+ uint8_t cbDigest;
+ /** Padding the size up to 208 bytes. */
+ uint8_t abPadding[4];
+ /** Set if we've finalized the digest. */
+ bool fFinal;
+} RTSHA3ALTPRIVATECTX;
+
+#define RT_SHA3_PRIVATE_ALT_CONTEXT
+#include <iprt/sha.h>
+
+
+
+static void rtSha3Keccak(RTSHA3ALTPRIVATECTX *pState)
+{
+#ifdef RT_BIG_ENDIAN
+ /* This sucks a performance wise on big endian systems, sorry. We just
+ needed something simple that works on AMD64 and x86. */
+ for (size_t i = 0; i < RT_ELEMENTS(pState->au64); i++)
+ pState->au64[i] = RT_LE2H_U64(pState->au64[i]);
+#endif
+
+ /*
+ * Rounds: Rnd(A,idxRound) = Iota(Chi(Pi(Rho(Theta(A)))), idxRount) [3.3]
+ */
+ for (uint32_t idxRound = 0; idxRound < RTSHA3_ROUNDS; idxRound++)
+ {
+ /*
+ * 3.2.1 Theta
+ */
+ {
+ /* Step 1: */
+ const uint64_t au64C[5] =
+ {
+ pState->au64[0] ^ pState->au64[5] ^ pState->au64[10] ^ pState->au64[15] ^ pState->au64[20],
+ pState->au64[1] ^ pState->au64[6] ^ pState->au64[11] ^ pState->au64[16] ^ pState->au64[21],
+ pState->au64[2] ^ pState->au64[7] ^ pState->au64[12] ^ pState->au64[17] ^ pState->au64[22],
+ pState->au64[3] ^ pState->au64[8] ^ pState->au64[13] ^ pState->au64[18] ^ pState->au64[23],
+ pState->au64[4] ^ pState->au64[9] ^ pState->au64[14] ^ pState->au64[19] ^ pState->au64[24],
+ };
+
+ /* Step 2 & 3: */
+#ifndef RTSHA3_FULL_UNROLL
+ for (size_t i = 0; i < RT_ELEMENTS(au64C); i++)
+ {
+ uint64_t const u64D = au64C[(i + 4) % RT_ELEMENTS(au64C)]
+ ^ ASMRotateLeftU64(au64C[(i + 1) % RT_ELEMENTS(au64C)], 1);
+ pState->au64[ 0 + i] ^= u64D;
+ pState->au64[ 5 + i] ^= u64D;
+ pState->au64[10 + i] ^= u64D;
+ pState->au64[15 + i] ^= u64D;
+ pState->au64[20 + i] ^= u64D;
+ }
+#else /* RTSHA3_FULL_UNROLL */
+# define THETA_STEP_2_3(a_i, a_idxCLeft, a_idxCRight) do { \
+ uint64_t const u64D = au64C[a_idxCLeft] ^ ASMRotateLeftU64(au64C[a_idxCRight], 1); \
+ pState->au64[ 0 + a_i] ^= u64D; \
+ pState->au64[ 5 + a_i] ^= u64D; \
+ pState->au64[10 + a_i] ^= u64D; \
+ pState->au64[15 + a_i] ^= u64D; \
+ pState->au64[20 + a_i] ^= u64D; \
+ } while (0)
+ THETA_STEP_2_3(0, 4, 1);
+ THETA_STEP_2_3(1, 0, 2);
+ THETA_STEP_2_3(2, 1, 3);
+ THETA_STEP_2_3(3, 2, 4);
+ THETA_STEP_2_3(4, 3, 0);
+#endif /* RTSHA3_FULL_UNROLL */
+ }
+
+ /*
+ * 3.2.2 Rho + 3.2.3 Pi
+ */
+ {
+#if !defined(RTSHA3_FULL_UNROLL) || defined(_MSC_VER) /* VS2019 is slightly slow with this section unrolled. go figure */
+ static uint8_t const s_aidxState[] = {10,7,11,17,18, 3, 5,16, 8,21, 24, 4,15,23,19, 13,12, 2,20,14, 22, 9, 6, 1};
+ static uint8_t const s_acRotate[] = { 1,3, 6,10,15, 21,28,36,45,55, 2,14,27,41,56, 8,25,43,62,18, 39,61,20,44};
+ AssertCompile(RT_ELEMENTS(s_aidxState) == 24); AssertCompile(RT_ELEMENTS(s_acRotate) == 24);
+ uint64_t u64 = pState->au64[1 /*s_aidxState[RT_ELEMENTS(s_aidxState) - 1]*/];
+# if !defined(_MSC_VER) /* This is slower with VS2019 but slightly faster with g++ (10.2.1). */
+ for (size_t i = 0; i <= 23 - 1; i++) /*i=t*/
+ {
+ uint64_t const u64Result = ASMRotateLeftU64(u64, s_acRotate[i]);
+ size_t const idxState = s_aidxState[i];
+ u64 = pState->au64[idxState];
+ pState->au64[idxState] = u64Result;
+ }
+ pState->au64[1 /*s_aidxState[23]*/] = ASMRotateLeftU64(u64, 44 /*s_acRotate[23]*/);
+# else
+ for (size_t i = 0; i <= 23; i++) /*i=t*/
+ {
+ uint64_t const u64Result = ASMRotateLeftU64(u64, s_acRotate[i]);
+ size_t const idxState = s_aidxState[i];
+ u64 = pState->au64[idxState];
+ pState->au64[idxState] = u64Result;
+ }
+# endif
+#else /* RTSHA3_FULL_UNROLL */
+# define RHO_AND_PI(a_idxState, a_cRotate) do { \
+ uint64_t const u64Result = ASMRotateLeftU64(u64, a_cRotate); \
+ u64 = pState->au64[a_idxState]; \
+ pState->au64[a_idxState] = u64Result; \
+ } while (0)
+
+ uint64_t u64 = pState->au64[1 /*s_aidxState[RT_ELEMENTS(s_aidxState) - 1]*/];
+ RHO_AND_PI(10, 1);
+ RHO_AND_PI( 7, 3);
+ RHO_AND_PI(11, 6);
+ RHO_AND_PI(17, 10);
+ RHO_AND_PI(18, 15);
+ RHO_AND_PI( 3, 21);
+ RHO_AND_PI( 5, 28);
+ RHO_AND_PI(16, 36);
+ RHO_AND_PI( 8, 45);
+ RHO_AND_PI(21, 55);
+ RHO_AND_PI(24, 2);
+ RHO_AND_PI( 4, 14);
+ RHO_AND_PI(15, 27);
+ RHO_AND_PI(23, 41);
+ RHO_AND_PI(19, 56);
+ RHO_AND_PI(13, 8);
+ RHO_AND_PI(12, 25);
+ RHO_AND_PI( 2, 43);
+ RHO_AND_PI(20, 62);
+ RHO_AND_PI(14, 18);
+ RHO_AND_PI(22, 39);
+ RHO_AND_PI( 9, 61);
+ RHO_AND_PI( 6, 20);
+ pState->au64[1 /*s_aidxState[23]*/] = ASMRotateLeftU64(u64, 44 /*s_acRotate[23]*/);
+
+#endif /* RTSHA3_FULL_UNROLL */
+ }
+
+ /*
+ * 3.2.4 Chi & 3.2.5 Iota.
+ */
+ /* Iota values xor constants (indexed by round). */
+ static uint64_t const s_au64RC[] =
+ {
+ UINT64_C(0x0000000000000001), UINT64_C(0x0000000000008082), UINT64_C(0x800000000000808a), UINT64_C(0x8000000080008000),
+ UINT64_C(0x000000000000808b), UINT64_C(0x0000000080000001), UINT64_C(0x8000000080008081), UINT64_C(0x8000000000008009),
+ UINT64_C(0x000000000000008a), UINT64_C(0x0000000000000088), UINT64_C(0x0000000080008009), UINT64_C(0x000000008000000a),
+ UINT64_C(0x000000008000808b), UINT64_C(0x800000000000008b), UINT64_C(0x8000000000008089), UINT64_C(0x8000000000008003),
+ UINT64_C(0x8000000000008002), UINT64_C(0x8000000000000080), UINT64_C(0x000000000000800a), UINT64_C(0x800000008000000a),
+ UINT64_C(0x8000000080008081), UINT64_C(0x8000000000008080), UINT64_C(0x0000000080000001), UINT64_C(0x8000000080008008),
+ };
+ AssertCompile(RT_ELEMENTS(s_au64RC) == RTSHA3_ROUNDS);
+#ifndef RTSHA3_FULL_UNROLL
+ /* Chi */
+ for (size_t i = 0; i < 25; i += 5)
+ {
+# ifndef _MSC_VER /* This is typically slower with VS2019 - go figure. Makes not difference with g++. */
+ uint64_t const u0 = pState->au64[i + 0];
+ uint64_t const u1 = pState->au64[i + 1];
+ uint64_t const u2 = pState->au64[i + 2];
+ pState->au64[i + 0] = u0 ^ (~u1 & u2);
+ uint64_t const u3 = pState->au64[i + 3];
+ pState->au64[i + 1] = u1 ^ (~u2 & u3);
+ uint64_t const u4 = pState->au64[i + 4];
+ pState->au64[i + 2] = u2 ^ (~u3 & u4);
+ pState->au64[i + 3] = u3 ^ (~u4 & u0);
+ pState->au64[i + 4] = u4 ^ (~u0 & u1);
+# else
+ uint64_t const au64Tmp[] = { pState->au64[i + 0], pState->au64[i + 1], pState->au64[i + 2],
+ pState->au64[i + 3], pState->au64[i + 4] };
+ pState->au64[i + 0] ^= ~au64Tmp[1] & au64Tmp[2];
+ pState->au64[i + 1] ^= ~au64Tmp[2] & au64Tmp[3];
+ pState->au64[i + 2] ^= ~au64Tmp[3] & au64Tmp[4];
+ pState->au64[i + 3] ^= ~au64Tmp[4] & au64Tmp[0];
+ pState->au64[i + 4] ^= ~au64Tmp[0] & au64Tmp[1];
+# endif
+ }
+
+ /* Iota. */
+ pState->au64[0] ^= s_au64RC[idxRound];
+
+#else /* RTSHA3_FULL_UNROLL */
+# define CHI_AND_IOTA(a_i, a_IotaExpr) do { \
+ uint64_t const u0 = pState->au64[a_i + 0]; \
+ uint64_t const u1 = pState->au64[a_i + 1]; \
+ uint64_t const u2 = pState->au64[a_i + 2]; \
+ pState->au64[a_i + 0] = u0 ^ (~u1 & u2) a_IotaExpr; \
+ uint64_t const u3 = pState->au64[a_i + 3]; \
+ pState->au64[a_i + 1] = u1 ^ (~u2 & u3); \
+ uint64_t const u4 = pState->au64[a_i + 4]; \
+ pState->au64[a_i + 2] = u2 ^ (~u3 & u4); \
+ pState->au64[a_i + 3] = u3 ^ (~u4 & u0); \
+ pState->au64[a_i + 4] = u4 ^ (~u0 & u1); \
+ } while (0)
+ CHI_AND_IOTA( 0, ^ s_au64RC[idxRound]);
+ CHI_AND_IOTA( 5, RT_NOTHING);
+ CHI_AND_IOTA(10, RT_NOTHING);
+ CHI_AND_IOTA(15, RT_NOTHING);
+ CHI_AND_IOTA(20, RT_NOTHING);
+#endif /* RTSHA3_FULL_UNROLL */
+ }
+
+#ifdef RT_BIG_ENDIAN
+ for (size_t i = 0; i < RT_ELEMENTS(pState->au64); i++)
+ pState->au64[i] = RT_H2LE_U64(pState->au64[i]);
+#endif
+}
+
+
+static int rtSha3Init(RTSHA3ALTPRIVATECTX *pCtx, unsigned cBitsDigest)
+{
+ RT_ZERO(pCtx->au64);
+ pCtx->offInput = 0;
+ pCtx->cbInput = (uint8_t)(sizeof(pCtx->ab) - (2 * cBitsDigest / 8));
+ pCtx->cbDigest = cBitsDigest / 8;
+ pCtx->fFinal = false;
+ return VINF_SUCCESS;
+}
+
+
+static int rtSha3Update(RTSHA3ALTPRIVATECTX *pCtx, uint8_t const *pbData, size_t cbData)
+{
+ Assert(!pCtx->fFinal);
+ size_t const cbInput = pCtx->cbInput;
+ size_t offState = pCtx->offInput;
+ Assert(!(cbInput & 7));
+#if 1
+ if ( ((uintptr_t)pbData & 7) == 0
+ && (offState & 7) == 0
+ && (cbData & 7) == 0)
+ {
+ uint64_t const cQwordsInput = cbInput / sizeof(uint64_t);
+ uint64_t const *pu64Data = (uint64_t const *)pbData;
+ size_t cQwordsData = cbData / sizeof(uint64_t);
+ size_t offData = 0;
+ offState /= sizeof(uint64_t);
+
+ /*
+ * Any catching up to do?
+ */
+ if (offState == 0 || cQwordsData >= cQwordsInput - offState)
+ {
+ if (offState > 0)
+ {
+ while (offState < cQwordsInput)
+ pCtx->au64[offState++] ^= pu64Data[offData++];
+ rtSha3Keccak(pCtx);
+ offState = 0;
+ }
+ if (offData < cQwordsData)
+ {
+ /*
+ * Do full chunks.
+ */
+# if 1
+ switch (cQwordsInput)
+ {
+ case 18: /* ( 200 - (2 * 224/8) = 0x90 (144) ) / 8 = 0x12 (18) */
+ {
+ size_t cFullChunks = (cQwordsData - offData) / 18;
+ while (cFullChunks-- > 0)
+ {
+ pCtx->au64[ 0] ^= pu64Data[offData + 0];
+ pCtx->au64[ 1] ^= pu64Data[offData + 1];
+ pCtx->au64[ 2] ^= pu64Data[offData + 2];
+ pCtx->au64[ 3] ^= pu64Data[offData + 3];
+ pCtx->au64[ 4] ^= pu64Data[offData + 4];
+ pCtx->au64[ 5] ^= pu64Data[offData + 5];
+ pCtx->au64[ 6] ^= pu64Data[offData + 6];
+ pCtx->au64[ 7] ^= pu64Data[offData + 7];
+ pCtx->au64[ 8] ^= pu64Data[offData + 8];
+ pCtx->au64[ 9] ^= pu64Data[offData + 9];
+ pCtx->au64[10] ^= pu64Data[offData + 10];
+ pCtx->au64[11] ^= pu64Data[offData + 11];
+ pCtx->au64[12] ^= pu64Data[offData + 12];
+ pCtx->au64[13] ^= pu64Data[offData + 13];
+ pCtx->au64[14] ^= pu64Data[offData + 14];
+ pCtx->au64[15] ^= pu64Data[offData + 15];
+ pCtx->au64[16] ^= pu64Data[offData + 16];
+ pCtx->au64[17] ^= pu64Data[offData + 17];
+ offData += 18;
+ rtSha3Keccak(pCtx);
+ }
+ break;
+ }
+
+ case 17: /* ( 200 - (2 * 256/8) = 0x88 (136) ) / 8 = 0x11 (17) */
+ {
+ size_t cFullChunks = (cQwordsData - offData) / 17;
+ while (cFullChunks-- > 0)
+ {
+ pCtx->au64[ 0] ^= pu64Data[offData + 0];
+ pCtx->au64[ 1] ^= pu64Data[offData + 1];
+ pCtx->au64[ 2] ^= pu64Data[offData + 2];
+ pCtx->au64[ 3] ^= pu64Data[offData + 3];
+ pCtx->au64[ 4] ^= pu64Data[offData + 4];
+ pCtx->au64[ 5] ^= pu64Data[offData + 5];
+ pCtx->au64[ 6] ^= pu64Data[offData + 6];
+ pCtx->au64[ 7] ^= pu64Data[offData + 7];
+ pCtx->au64[ 8] ^= pu64Data[offData + 8];
+ pCtx->au64[ 9] ^= pu64Data[offData + 9];
+ pCtx->au64[10] ^= pu64Data[offData + 10];
+ pCtx->au64[11] ^= pu64Data[offData + 11];
+ pCtx->au64[12] ^= pu64Data[offData + 12];
+ pCtx->au64[13] ^= pu64Data[offData + 13];
+ pCtx->au64[14] ^= pu64Data[offData + 14];
+ pCtx->au64[15] ^= pu64Data[offData + 15];
+ pCtx->au64[16] ^= pu64Data[offData + 16];
+ offData += 17;
+ rtSha3Keccak(pCtx);
+ }
+ break;
+ }
+
+ case 13: /* ( 200 - (2 * 384/8) = 0x68 (104) ) / 8 = 0x0d (13) */
+ {
+ size_t cFullChunks = (cQwordsData - offData) / 13;
+ while (cFullChunks-- > 0)
+ {
+ pCtx->au64[ 0] ^= pu64Data[offData + 0];
+ pCtx->au64[ 1] ^= pu64Data[offData + 1];
+ pCtx->au64[ 2] ^= pu64Data[offData + 2];
+ pCtx->au64[ 3] ^= pu64Data[offData + 3];
+ pCtx->au64[ 4] ^= pu64Data[offData + 4];
+ pCtx->au64[ 5] ^= pu64Data[offData + 5];
+ pCtx->au64[ 6] ^= pu64Data[offData + 6];
+ pCtx->au64[ 7] ^= pu64Data[offData + 7];
+ pCtx->au64[ 8] ^= pu64Data[offData + 8];
+ pCtx->au64[ 9] ^= pu64Data[offData + 9];
+ pCtx->au64[10] ^= pu64Data[offData + 10];
+ pCtx->au64[11] ^= pu64Data[offData + 11];
+ pCtx->au64[12] ^= pu64Data[offData + 12];
+ offData += 13;
+ rtSha3Keccak(pCtx);
+ }
+ break;
+ }
+
+ case 9: /* ( 200 - (2 * 512/8) = 0x48 (72) ) / 8 = 0x09 (9) */
+ {
+ size_t cFullChunks = (cQwordsData - offData) / 9;
+ while (cFullChunks-- > 0)
+ {
+ pCtx->au64[ 0] ^= pu64Data[offData + 0];
+ pCtx->au64[ 1] ^= pu64Data[offData + 1];
+ pCtx->au64[ 2] ^= pu64Data[offData + 2];
+ pCtx->au64[ 3] ^= pu64Data[offData + 3];
+ pCtx->au64[ 4] ^= pu64Data[offData + 4];
+ pCtx->au64[ 5] ^= pu64Data[offData + 5];
+ pCtx->au64[ 6] ^= pu64Data[offData + 6];
+ pCtx->au64[ 7] ^= pu64Data[offData + 7];
+ pCtx->au64[ 8] ^= pu64Data[offData + 8];
+ offData += 9;
+ rtSha3Keccak(pCtx);
+ }
+ break;
+ }
+
+ default:
+ {
+ AssertFailed();
+# endif
+ size_t cFullChunks = (cQwordsData - offData) / cQwordsInput;
+ while (cFullChunks-- > 0)
+ {
+ offState = cQwordsInput;
+ while (offState-- > 0)
+ pCtx->au64[offState] ^= pu64Data[offData + offState];
+ offData += cQwordsInput;
+ rtSha3Keccak(pCtx);
+ }
+# if 1
+ break;
+ }
+ }
+# endif
+ offState = 0;
+
+ /*
+ * Partial last chunk?
+ */
+ if (offData < cQwordsData)
+ {
+ Assert(cQwordsData - offData < cQwordsInput);
+ while (offData < cQwordsData)
+ pCtx->au64[offState++] ^= pu64Data[offData++];
+ offState *= sizeof(uint64_t);
+ }
+ }
+ }
+ else
+ {
+ while (offData < cQwordsData)
+ pCtx->au64[offState++] ^= pu64Data[offData++];
+ offState *= sizeof(uint64_t);
+ }
+ Assert(offData == cQwordsData);
+ }
+ else
+#endif
+ {
+ /*
+ * Misaligned input/state, so just do simpe byte by byte processing.
+ */
+ for (size_t offData = 0; offData < cbData; offData++)
+ {
+ pCtx->ab[offState] ^= pbData[offData];
+ offState++;
+ if (offState < cbInput)
+ { /* likely */ }
+ else
+ {
+ rtSha3Keccak(pCtx);
+ offState = 0;
+ }
+ }
+ }
+ pCtx->offInput = (uint8_t)offState;
+ return VINF_SUCCESS;
+}
+
+
+static void rtSha3FinalInternal(RTSHA3ALTPRIVATECTX *pCtx)
+{
+ Assert(!pCtx->fFinal);
+
+ pCtx->ab[pCtx->offInput] ^= 0x06;
+ pCtx->ab[pCtx->cbInput - 1] ^= 0x80;
+ rtSha3Keccak(pCtx);
+}
+
+
+static int rtSha3Final(RTSHA3ALTPRIVATECTX *pCtx, uint8_t *pbDigest)
+{
+ Assert(!pCtx->fFinal);
+
+ rtSha3FinalInternal(pCtx);
+
+ memcpy(pbDigest, pCtx->ab, pCtx->cbDigest);
+
+ /* Wipe non-hash state. */
+ RT_BZERO(&pCtx->ab[pCtx->cbDigest], sizeof(pCtx->ab) - pCtx->cbDigest);
+ pCtx->fFinal = true;
+ return VINF_SUCCESS;
+}
+
+
+static int rtSha3(const void *pvData, size_t cbData, unsigned cBitsDigest, uint8_t *pabHash)
+{
+ RTSHA3ALTPRIVATECTX Ctx;
+ rtSha3Init(&Ctx, cBitsDigest);
+ rtSha3Update(&Ctx, (uint8_t const *)pvData, cbData);
+ rtSha3Final(&Ctx, pabHash);
+ return VINF_SUCCESS;
+}
+
+
+static bool rtSha3Check(const void *pvData, size_t cbData, unsigned cBitsDigest, const uint8_t *pabHash)
+{
+ RTSHA3ALTPRIVATECTX Ctx;
+ rtSha3Init(&Ctx, cBitsDigest);
+ rtSha3Update(&Ctx, (uint8_t const *)pvData, cbData);
+ rtSha3FinalInternal(&Ctx);
+ bool fRet = memcmp(pabHash, &Ctx.ab, cBitsDigest / 8) == 0;
+ RT_ZERO(Ctx);
+ return fRet;
+}
+
+
+/** Macro for declaring the interface for a SHA3 variation.
+ * @internal */
+#define RTSHA3_DEFINE_VARIANT(a_cBits) \
+AssertCompile((a_cBits / 8) == RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)); \
+AssertCompile(sizeof(RT_CONCAT3(RTSHA3T,a_cBits,CONTEXT)) >= sizeof(RTSHA3ALTPRIVATECTX)); \
+\
+RTDECL(int) RT_CONCAT(RTSha3t,a_cBits)(const void *pvBuf, size_t cbBuf, uint8_t pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)]) \
+{ \
+ return rtSha3(pvBuf, cbBuf, a_cBits, pabHash); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT(RTSha3t,a_cBits)); \
+\
+\
+RTDECL(bool) RT_CONCAT3(RTSha3t,a_cBits,Check)(const void *pvBuf, size_t cbBuf, \
+ uint8_t const pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)]) \
+{ \
+ return rtSha3Check(pvBuf, cbBuf, a_cBits, pabHash); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Check)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Init)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx) \
+{ \
+ AssertCompile(sizeof(pCtx->Sha3.a64Padding) >= sizeof(pCtx->Sha3.AltPrivate)); \
+ AssertCompile(sizeof(pCtx->Sha3.a64Padding) == sizeof(pCtx->Sha3.abPadding)); \
+ return rtSha3Init(&pCtx->Sha3.AltPrivate, a_cBits); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Init)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Update)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx, const void *pvBuf, size_t cbBuf) \
+{ \
+ Assert(pCtx->Sha3.AltPrivate.cbDigest == (a_cBits) / 8); \
+ return rtSha3Update(&pCtx->Sha3.AltPrivate, (uint8_t const *)pvBuf, cbBuf); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Update)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Final)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx, \
+ uint8_t pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)]) \
+{ \
+ Assert(pCtx->Sha3.AltPrivate.cbDigest == (a_cBits) / 8); \
+ return rtSha3Final(&pCtx->Sha3.AltPrivate, pabHash); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Final)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Cleanup)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx) \
+{ \
+ if (pCtx) \
+ { \
+ Assert(pCtx->Sha3.AltPrivate.cbDigest == (a_cBits) / 8); \
+ RT_ZERO(*pCtx); \
+ } \
+ return VINF_SUCCESS; \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Cleanup)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Clone)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx, \
+ RT_CONCAT3(RTSHA3T,a_cBits,CONTEXT) const *pCtxSrc) \
+{ \
+ memcpy(pCtx, pCtxSrc, sizeof(*pCtx)); \
+ return VINF_SUCCESS; \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Clone)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,ToString)(uint8_t const pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)], \
+ char *pszDigest, size_t cchDigest) \
+{ \
+ return RTStrPrintHexBytes(pszDigest, cchDigest, pabHash, (a_cBits) / 8, 0 /*fFlags*/); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,ToString)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,FromString)(char const *pszDigest, uint8_t pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)]) \
+{ \
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabHash[0], (a_cBits) / 8, 0 /*fFlags*/); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,FromString))
+
+
+RTSHA3_DEFINE_VARIANT(224);
+RTSHA3_DEFINE_VARIANT(256);
+RTSHA3_DEFINE_VARIANT(384);
+RTSHA3_DEFINE_VARIANT(512);
+
diff --git a/src/VBox/Runtime/common/checksum/alt-sha512.cpp b/src/VBox/Runtime/common/checksum/alt-sha512.cpp
new file mode 100644
index 00000000..18d91d6a
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/alt-sha512.cpp
@@ -0,0 +1,805 @@
+/* $Id: alt-sha512.cpp $ */
+/** @file
+ * IPRT - SHA-512 and SHA-384 hash functions, Alternative Implementation.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The SHA-512 block size (in bytes). */
+#define RTSHA512_BLOCK_SIZE 128U
+
+/** Enables the unrolled code. */
+#define RTSHA512_UNROLLED 1
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+
+
+/** Our private context structure. */
+typedef struct RTSHA512ALTPRIVATECTX
+{
+ /** The W array.
+ * Buffering happens in the first 16 words, converted from big endian to host
+ * endian immediately before processing. The amount of buffered data is kept
+ * in the 6 least significant bits of cbMessage. */
+ uint64_t auW[80];
+ /** The message length (in bytes). */
+ RTUINT128U cbMessage;
+ /** The 8 hash values. */
+ uint64_t auH[8];
+} RTSHA512ALTPRIVATECTX;
+
+#define RT_SHA512_PRIVATE_ALT_CONTEXT
+#include <iprt/sha.h>
+
+
+AssertCompile(RT_SIZEOFMEMB(RTSHA512CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTSHA512CONTEXT, AltPrivate));
+AssertCompileMemberSize(RTSHA512ALTPRIVATECTX, auH, RTSHA512_HASH_SIZE);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifndef RTSHA512_UNROLLED
+/** The K constants. */
+static uint64_t const g_auKs[] =
+{
+ UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd), UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc),
+ UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019), UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118),
+ UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe), UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2),
+ UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1), UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694),
+ UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3), UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65),
+ UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483), UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5),
+ UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210), UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4),
+ UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725), UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70),
+ UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926), UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df),
+ UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8), UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b),
+ UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001), UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30),
+ UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910), UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8),
+ UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53), UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8),
+ UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb), UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3),
+ UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60), UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec),
+ UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9), UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b),
+ UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207), UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178),
+ UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6), UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b),
+ UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493), UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c),
+ UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a), UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817),
+};
+#endif /* !RTSHA512_UNROLLED */
+
+
+
+RTDECL(void) RTSha512Init(PRTSHA512CONTEXT pCtx)
+{
+ pCtx->AltPrivate.cbMessage.s.Lo = 0;
+ pCtx->AltPrivate.cbMessage.s.Hi = 0;
+ pCtx->AltPrivate.auH[0] = UINT64_C(0x6a09e667f3bcc908);
+ pCtx->AltPrivate.auH[1] = UINT64_C(0xbb67ae8584caa73b);
+ pCtx->AltPrivate.auH[2] = UINT64_C(0x3c6ef372fe94f82b);
+ pCtx->AltPrivate.auH[3] = UINT64_C(0xa54ff53a5f1d36f1);
+ pCtx->AltPrivate.auH[4] = UINT64_C(0x510e527fade682d1);
+ pCtx->AltPrivate.auH[5] = UINT64_C(0x9b05688c2b3e6c1f);
+ pCtx->AltPrivate.auH[6] = UINT64_C(0x1f83d9abfb41bd6b);
+ pCtx->AltPrivate.auH[7] = UINT64_C(0x5be0cd19137e2179);
+}
+RT_EXPORT_SYMBOL(RTSha512Init);
+
+
+/** Function 4.8. */
+DECL_FORCE_INLINE(uint64_t) rtSha512Ch(uint64_t uX, uint64_t uY, uint64_t uZ)
+{
+#if 1
+ /* Optimization that saves one operation and probably a temporary variable. */
+ uint64_t uResult = uY;
+ uResult ^= uZ;
+ uResult &= uX;
+ uResult ^= uZ;
+ return uResult;
+#else
+ /* The original. */
+ uint64_t uResult = uX & uY;
+ uResult ^= ~uX & uZ;
+ return uResult;
+#endif
+}
+
+
+/** Function 4.9. */
+DECL_FORCE_INLINE(uint64_t) rtSha512Maj(uint64_t uX, uint64_t uY, uint64_t uZ)
+{
+#if 1
+ /* Optimization that save one operation and probably a temporary variable. */
+ uint64_t uResult = uY;
+ uResult ^= uZ;
+ uResult &= uX;
+ uResult ^= uY & uZ;
+ return uResult;
+#else
+ /* The original. */
+ uint64_t uResult = uX & uY;
+ uResult ^= uX & uZ;
+ uResult ^= uY & uZ;
+ return uResult;
+#endif
+}
+
+
+/** Function 4.10. */
+DECL_FORCE_INLINE(uint64_t) rtSha512CapitalSigma0(uint64_t uX)
+{
+ uint64_t uResult = uX = ASMRotateRightU64(uX, 28);
+ uX = ASMRotateRightU64(uX, 34 - 28);
+ uResult ^= uX;
+ uX = ASMRotateRightU64(uX, 39 - 34);
+ uResult ^= uX;
+ return uResult;
+}
+
+
+/** Function 4.11. */
+DECL_FORCE_INLINE(uint64_t) rtSha512CapitalSigma1(uint64_t uX)
+{
+ uint64_t uResult = uX = ASMRotateRightU64(uX, 14);
+ uX = ASMRotateRightU64(uX, 18 - 14);
+ uResult ^= uX;
+ uX = ASMRotateRightU64(uX, 41 - 18);
+ uResult ^= uX;
+ return uResult;
+}
+
+
+/** Function 4.12. */
+DECL_FORCE_INLINE(uint64_t) rtSha512SmallSigma0(uint64_t uX)
+{
+ uint64_t uResult = uX >> 7;
+ uX = ASMRotateRightU64(uX, 1);
+ uResult ^= uX;
+ uX = ASMRotateRightU64(uX, 8 - 1);
+ uResult ^= uX;
+ return uResult;
+}
+
+
+/** Function 4.13. */
+DECL_FORCE_INLINE(uint64_t) rtSha512SmallSigma1(uint64_t uX)
+{
+ uint64_t uResult = uX >> 6;
+ uX = ASMRotateRightU64(uX, 19);
+ uResult ^= uX;
+ uX = ASMRotateRightU64(uX, 61 - 19);
+ uResult ^= uX;
+ return uResult;
+}
+
+
+/**
+ * Initializes the auW array from the specfied input block.
+ *
+ * @param pCtx The SHA-512 context.
+ * @param pbBlock The block. Must be 64-bit aligned.
+ */
+DECLINLINE(void) rtSha512BlockInit(PRTSHA512CONTEXT pCtx, uint8_t const *pbBlock)
+{
+#ifdef RTSHA512_UNROLLED
+ uint64_t const *puSrc = (uint64_t const *)pbBlock;
+ uint64_t *puW = &pCtx->AltPrivate.auW[0];
+ Assert(!((uintptr_t)puSrc & 7));
+ Assert(!((uintptr_t)puW & 7));
+
+ /* Copy and byte-swap the block. Initializing the rest of the Ws are done
+ in the processing loop. */
+# ifdef RT_LITTLE_ENDIAN
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+ *puW++ = ASMByteSwapU64(*puSrc++);
+# else
+ memcpy(puW, puSrc, RTSHA512_BLOCK_SIZE);
+# endif
+
+#else /* !RTSHA512_UNROLLED */
+
+ uint64_t const *pu32Block = (uint64_t const *)pbBlock;
+ Assert(!((uintptr_t)pu32Block & 3));
+
+ unsigned iWord;
+ for (iWord = 0; iWord < 16; iWord++)
+ pCtx->AltPrivate.auW[iWord] = RT_BE2H_U64(pu32Block[iWord]);
+
+ for (; iWord < RT_ELEMENTS(pCtx->AltPrivate.auW); iWord++)
+ {
+ uint64_t u64 = rtSha512SmallSigma1(pCtx->AltPrivate.auW[iWord - 2]);
+ u64 += rtSha512SmallSigma0(pCtx->AltPrivate.auW[iWord - 15]);
+ u64 += pCtx->AltPrivate.auW[iWord - 7];
+ u64 += pCtx->AltPrivate.auW[iWord - 16];
+ pCtx->AltPrivate.auW[iWord] = u64;
+ }
+#endif /* !RTSHA512_UNROLLED */
+}
+
+
+/**
+ * Initializes the auW array from data buffered in the first part of the array.
+ *
+ * @param pCtx The SHA-512 context.
+ */
+DECLINLINE(void) rtSha512BlockInitBuffered(PRTSHA512CONTEXT pCtx)
+{
+#ifdef RTSHA512_UNROLLED
+ uint64_t *puW = &pCtx->AltPrivate.auW[0];
+ Assert(!((uintptr_t)puW & 7));
+
+ /* Do the byte swap if necessary. Initializing the rest of the Ws are done
+ in the processing loop. */
+# ifdef RT_LITTLE_ENDIAN
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+ *puW = ASMByteSwapU64(*puW); puW++;
+# endif
+
+#else /* !RTSHA512_UNROLLED */
+
+ unsigned iWord;
+ for (iWord = 0; iWord < 16; iWord++)
+ pCtx->AltPrivate.auW[iWord] = RT_BE2H_U64(pCtx->AltPrivate.auW[iWord]);
+
+ for (; iWord < RT_ELEMENTS(pCtx->AltPrivate.auW); iWord++)
+ {
+ uint64_t u64 = rtSha512SmallSigma1(pCtx->AltPrivate.auW[iWord - 2]);
+ u64 += rtSha512SmallSigma0(pCtx->AltPrivate.auW[iWord - 15]);
+ u64 += pCtx->AltPrivate.auW[iWord - 7];
+ u64 += pCtx->AltPrivate.auW[iWord - 16];
+ pCtx->AltPrivate.auW[iWord] = u64;
+ }
+#endif /* !RTSHA512_UNROLLED */
+}
+
+
+/**
+ * Process the current block.
+ *
+ * Requires one of the rtSha512BlockInit functions to be called first.
+ *
+ * @param pCtx The SHA-512 context.
+ */
+static void rtSha512BlockProcess(PRTSHA512CONTEXT pCtx)
+{
+ uint64_t uA = pCtx->AltPrivate.auH[0];
+ uint64_t uB = pCtx->AltPrivate.auH[1];
+ uint64_t uC = pCtx->AltPrivate.auH[2];
+ uint64_t uD = pCtx->AltPrivate.auH[3];
+ uint64_t uE = pCtx->AltPrivate.auH[4];
+ uint64_t uF = pCtx->AltPrivate.auH[5];
+ uint64_t uG = pCtx->AltPrivate.auH[6];
+ uint64_t uH = pCtx->AltPrivate.auH[7];
+
+#ifdef RTSHA512_UNROLLED
+ uint64_t *puW = &pCtx->AltPrivate.auW[0];
+# define RTSHA512_BODY(a_iWord, a_uK, a_uA, a_uB, a_uC, a_uD, a_uE, a_uF, a_uG, a_uH) \
+ do { \
+ if ((a_iWord) < 16) \
+ a_uH += *puW++; \
+ else \
+ { \
+ uint64_t u64 = puW[-16]; \
+ u64 += rtSha512SmallSigma0(puW[-15]); \
+ u64 += puW[-7]; \
+ u64 += rtSha512SmallSigma1(puW[-2]); \
+ if (a_iWord < 80-2) *puW++ = u64; else puW++; \
+ a_uH += u64; \
+ } \
+ \
+ a_uH += rtSha512CapitalSigma1(a_uE); \
+ a_uH += a_uK; \
+ a_uH += rtSha512Ch(a_uE, a_uF, a_uG); \
+ a_uD += a_uH; \
+ \
+ a_uH += rtSha512CapitalSigma0(a_uA); \
+ a_uH += rtSha512Maj(a_uA, a_uB, a_uC); \
+ } while (0)
+# define RTSHA512_EIGHT(a_uK0, a_uK1, a_uK2, a_uK3, a_uK4, a_uK5, a_uK6, a_uK7, a_iFirst) \
+ do { \
+ RTSHA512_BODY(a_iFirst + 0, a_uK0, uA, uB, uC, uD, uE, uF, uG, uH); \
+ RTSHA512_BODY(a_iFirst + 1, a_uK1, uH, uA, uB, uC, uD, uE, uF, uG); \
+ RTSHA512_BODY(a_iFirst + 2, a_uK2, uG, uH, uA, uB, uC, uD, uE, uF); \
+ RTSHA512_BODY(a_iFirst + 3, a_uK3, uF, uG, uH, uA, uB, uC, uD, uE); \
+ RTSHA512_BODY(a_iFirst + 4, a_uK4, uE, uF, uG, uH, uA, uB, uC, uD); \
+ RTSHA512_BODY(a_iFirst + 5, a_uK5, uD, uE, uF, uG, uH, uA, uB, uC); \
+ RTSHA512_BODY(a_iFirst + 6, a_uK6, uC, uD, uE, uF, uG, uH, uA, uB); \
+ RTSHA512_BODY(a_iFirst + 7, a_uK7, uB, uC, uD, uE, uF, uG, uH, uA); \
+ } while (0)
+ RTSHA512_EIGHT(UINT64_C(0x428a2f98d728ae22), UINT64_C(0x7137449123ef65cd), UINT64_C(0xb5c0fbcfec4d3b2f), UINT64_C(0xe9b5dba58189dbbc),
+ UINT64_C(0x3956c25bf348b538), UINT64_C(0x59f111f1b605d019), UINT64_C(0x923f82a4af194f9b), UINT64_C(0xab1c5ed5da6d8118),
+ 0);
+ RTSHA512_EIGHT(UINT64_C(0xd807aa98a3030242), UINT64_C(0x12835b0145706fbe), UINT64_C(0x243185be4ee4b28c), UINT64_C(0x550c7dc3d5ffb4e2),
+ UINT64_C(0x72be5d74f27b896f), UINT64_C(0x80deb1fe3b1696b1), UINT64_C(0x9bdc06a725c71235), UINT64_C(0xc19bf174cf692694),
+ 8);
+ RTSHA512_EIGHT(UINT64_C(0xe49b69c19ef14ad2), UINT64_C(0xefbe4786384f25e3), UINT64_C(0x0fc19dc68b8cd5b5), UINT64_C(0x240ca1cc77ac9c65),
+ UINT64_C(0x2de92c6f592b0275), UINT64_C(0x4a7484aa6ea6e483), UINT64_C(0x5cb0a9dcbd41fbd4), UINT64_C(0x76f988da831153b5),
+ 16);
+ RTSHA512_EIGHT(UINT64_C(0x983e5152ee66dfab), UINT64_C(0xa831c66d2db43210), UINT64_C(0xb00327c898fb213f), UINT64_C(0xbf597fc7beef0ee4),
+ UINT64_C(0xc6e00bf33da88fc2), UINT64_C(0xd5a79147930aa725), UINT64_C(0x06ca6351e003826f), UINT64_C(0x142929670a0e6e70),
+ 24);
+ RTSHA512_EIGHT(UINT64_C(0x27b70a8546d22ffc), UINT64_C(0x2e1b21385c26c926), UINT64_C(0x4d2c6dfc5ac42aed), UINT64_C(0x53380d139d95b3df),
+ UINT64_C(0x650a73548baf63de), UINT64_C(0x766a0abb3c77b2a8), UINT64_C(0x81c2c92e47edaee6), UINT64_C(0x92722c851482353b),
+ 32);
+ RTSHA512_EIGHT(UINT64_C(0xa2bfe8a14cf10364), UINT64_C(0xa81a664bbc423001), UINT64_C(0xc24b8b70d0f89791), UINT64_C(0xc76c51a30654be30),
+ UINT64_C(0xd192e819d6ef5218), UINT64_C(0xd69906245565a910), UINT64_C(0xf40e35855771202a), UINT64_C(0x106aa07032bbd1b8),
+ 40);
+ RTSHA512_EIGHT(UINT64_C(0x19a4c116b8d2d0c8), UINT64_C(0x1e376c085141ab53), UINT64_C(0x2748774cdf8eeb99), UINT64_C(0x34b0bcb5e19b48a8),
+ UINT64_C(0x391c0cb3c5c95a63), UINT64_C(0x4ed8aa4ae3418acb), UINT64_C(0x5b9cca4f7763e373), UINT64_C(0x682e6ff3d6b2b8a3),
+ 48);
+ RTSHA512_EIGHT(UINT64_C(0x748f82ee5defb2fc), UINT64_C(0x78a5636f43172f60), UINT64_C(0x84c87814a1f0ab72), UINT64_C(0x8cc702081a6439ec),
+ UINT64_C(0x90befffa23631e28), UINT64_C(0xa4506cebde82bde9), UINT64_C(0xbef9a3f7b2c67915), UINT64_C(0xc67178f2e372532b),
+ 56);
+ RTSHA512_EIGHT(UINT64_C(0xca273eceea26619c), UINT64_C(0xd186b8c721c0c207), UINT64_C(0xeada7dd6cde0eb1e), UINT64_C(0xf57d4f7fee6ed178),
+ UINT64_C(0x06f067aa72176fba), UINT64_C(0x0a637dc5a2c898a6), UINT64_C(0x113f9804bef90dae), UINT64_C(0x1b710b35131c471b),
+ 64);
+ RTSHA512_EIGHT(UINT64_C(0x28db77f523047d84), UINT64_C(0x32caab7b40c72493), UINT64_C(0x3c9ebe0a15c9bebc), UINT64_C(0x431d67c49c100d4c),
+ UINT64_C(0x4cc5d4becb3e42b6), UINT64_C(0x597f299cfc657e2a), UINT64_C(0x5fcb6fab3ad6faec), UINT64_C(0x6c44198c4a475817),
+ 72);
+#else
+ for (unsigned iWord = 0; iWord < RT_ELEMENTS(pCtx->AltPrivate.auW); iWord++)
+ {
+ uint64_t uT1 = uH;
+ uT1 += rtSha512CapitalSigma1(uE);
+ uT1 += rtSha512Ch(uE, uF, uG);
+ uT1 += g_auKs[iWord];
+ uT1 += pCtx->AltPrivate.auW[iWord];
+
+ uint64_t uT2 = rtSha512CapitalSigma0(uA);
+ uT2 += rtSha512Maj(uA, uB, uC);
+
+ uH = uG;
+ uG = uF;
+ uF = uE;
+ uE = uD + uT1;
+ uD = uC;
+ uC = uB;
+ uB = uA;
+ uA = uT1 + uT2;
+ }
+#endif
+
+ pCtx->AltPrivate.auH[0] += uA;
+ pCtx->AltPrivate.auH[1] += uB;
+ pCtx->AltPrivate.auH[2] += uC;
+ pCtx->AltPrivate.auH[3] += uD;
+ pCtx->AltPrivate.auH[4] += uE;
+ pCtx->AltPrivate.auH[5] += uF;
+ pCtx->AltPrivate.auH[6] += uG;
+ pCtx->AltPrivate.auH[7] += uH;
+}
+
+
+RTDECL(void) RTSha512Update(PRTSHA512CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ Assert(pCtx->AltPrivate.cbMessage.s.Hi < UINT64_MAX / 8);
+ uint8_t const *pbBuf = (uint8_t const *)pvBuf;
+
+ /*
+ * Deal with buffered bytes first.
+ */
+ size_t cbBuffered = (size_t)pCtx->AltPrivate.cbMessage.s.Lo & (RTSHA512_BLOCK_SIZE - 1U);
+ if (cbBuffered)
+ {
+ size_t cbMissing = RTSHA512_BLOCK_SIZE - cbBuffered;
+ if (cbBuf >= cbMissing)
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0] + cbBuffered, pbBuf, cbMissing);
+ pCtx->AltPrivate.cbMessage.s.Lo += cbMissing;
+ if (!pCtx->AltPrivate.cbMessage.s.Lo)
+ pCtx->AltPrivate.cbMessage.s.Hi++;
+ pbBuf += cbMissing;
+ cbBuf -= cbMissing;
+
+ rtSha512BlockInitBuffered(pCtx);
+ rtSha512BlockProcess(pCtx);
+ }
+ else
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0] + cbBuffered, pbBuf, cbBuf);
+ pCtx->AltPrivate.cbMessage.s.Lo += cbBuf;
+ return;
+ }
+ }
+
+ if (!((uintptr_t)pbBuf & 7))
+ {
+ /*
+ * Process full blocks directly from the input buffer.
+ */
+ while (cbBuf >= RTSHA512_BLOCK_SIZE)
+ {
+ rtSha512BlockInit(pCtx, pbBuf);
+ rtSha512BlockProcess(pCtx);
+
+ pCtx->AltPrivate.cbMessage.s.Lo += RTSHA512_BLOCK_SIZE;
+ if (!pCtx->AltPrivate.cbMessage.s.Lo)
+ pCtx->AltPrivate.cbMessage.s.Hi++;
+ pbBuf += RTSHA512_BLOCK_SIZE;
+ cbBuf -= RTSHA512_BLOCK_SIZE;
+ }
+ }
+ else
+ {
+ /*
+ * Unaligned input, so buffer it.
+ */
+ while (cbBuf >= RTSHA512_BLOCK_SIZE)
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0], pbBuf, RTSHA512_BLOCK_SIZE);
+ rtSha512BlockInitBuffered(pCtx);
+ rtSha512BlockProcess(pCtx);
+
+ pCtx->AltPrivate.cbMessage.s.Lo += RTSHA512_BLOCK_SIZE;
+ if (!pCtx->AltPrivate.cbMessage.s.Lo)
+ pCtx->AltPrivate.cbMessage.s.Hi++;
+ pbBuf += RTSHA512_BLOCK_SIZE;
+ cbBuf -= RTSHA512_BLOCK_SIZE;
+ }
+ }
+
+ /*
+ * Stash any remaining bytes into the context buffer.
+ */
+ if (cbBuf > 0)
+ {
+ memcpy((uint8_t *)&pCtx->AltPrivate.auW[0], pbBuf, cbBuf);
+ pCtx->AltPrivate.cbMessage.s.Lo += cbBuf;
+ if (!pCtx->AltPrivate.cbMessage.s.Lo)
+ pCtx->AltPrivate.cbMessage.s.Hi++;
+ }
+}
+RT_EXPORT_SYMBOL(RTSha512Update);
+
+
+/**
+ * Internal worker for RTSha512Final and RTSha384Final that finalizes the
+ * computation but does not copy out the hash value.
+ *
+ * @param pCtx The SHA-512 context.
+ */
+static void rtSha512FinalInternal(PRTSHA512CONTEXT pCtx)
+{
+ Assert(pCtx->AltPrivate.cbMessage.s.Hi < UINT64_MAX / 8);
+
+ /*
+ * Complete the message by adding a single bit (0x80), padding till
+ * the next 448-bit boundrary, the add the message length.
+ */
+ RTUINT128U cMessageBits = pCtx->AltPrivate.cbMessage;
+ cMessageBits.s.Hi <<= 3;
+ cMessageBits.s.Hi |= cMessageBits.s.Lo >> 61;
+ cMessageBits.s.Lo <<= 3;
+
+ unsigned cbMissing = RTSHA512_BLOCK_SIZE - ((unsigned)pCtx->AltPrivate.cbMessage.s.Lo & (RTSHA512_BLOCK_SIZE - 1U));
+ static uint8_t const s_abSingleBitAndSomePadding[20] =
+ { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
+ if (cbMissing < 1U + 16U)
+ /* Less than 64+16 bits left in the current block, force a new block. */
+ RTSha512Update(pCtx, &s_abSingleBitAndSomePadding, sizeof(s_abSingleBitAndSomePadding));
+ else
+ RTSha512Update(pCtx, &s_abSingleBitAndSomePadding, 1);
+
+ unsigned cbBuffered = (unsigned)pCtx->AltPrivate.cbMessage.s.Lo & (RTSHA512_BLOCK_SIZE - 1U);
+ cbMissing = RTSHA512_BLOCK_SIZE - cbBuffered;
+ Assert(cbMissing >= 16);
+ memset((uint8_t *)&pCtx->AltPrivate.auW[0] + cbBuffered, 0, cbMissing - 16);
+
+ pCtx->AltPrivate.auW[14] = RT_H2BE_U64(cMessageBits.s.Hi);
+ pCtx->AltPrivate.auW[15] = RT_H2BE_U64(cMessageBits.s.Lo);
+
+ /*
+ * Process the last buffered block constructed/completed above.
+ */
+ rtSha512BlockInitBuffered(pCtx);
+ rtSha512BlockProcess(pCtx);
+
+ /*
+ * Convert the byte order of the hash words and we're done.
+ */
+ pCtx->AltPrivate.auH[0] = RT_H2BE_U64(pCtx->AltPrivate.auH[0]);
+ pCtx->AltPrivate.auH[1] = RT_H2BE_U64(pCtx->AltPrivate.auH[1]);
+ pCtx->AltPrivate.auH[2] = RT_H2BE_U64(pCtx->AltPrivate.auH[2]);
+ pCtx->AltPrivate.auH[3] = RT_H2BE_U64(pCtx->AltPrivate.auH[3]);
+ pCtx->AltPrivate.auH[4] = RT_H2BE_U64(pCtx->AltPrivate.auH[4]);
+ pCtx->AltPrivate.auH[5] = RT_H2BE_U64(pCtx->AltPrivate.auH[5]);
+ pCtx->AltPrivate.auH[6] = RT_H2BE_U64(pCtx->AltPrivate.auH[6]);
+ pCtx->AltPrivate.auH[7] = RT_H2BE_U64(pCtx->AltPrivate.auH[7]);
+
+ RT_ZERO(pCtx->AltPrivate.auW);
+ pCtx->AltPrivate.cbMessage.s.Lo = UINT64_MAX;
+ pCtx->AltPrivate.cbMessage.s.Hi = UINT64_MAX;
+}
+RT_EXPORT_SYMBOL(RTSha512Final);
+
+
+RTDECL(void) RTSha512Final(PRTSHA512CONTEXT pCtx, uint8_t pabDigest[RTSHA512_HASH_SIZE])
+{
+ rtSha512FinalInternal(pCtx);
+ memcpy(pabDigest, &pCtx->AltPrivate.auH[0], RTSHA512_HASH_SIZE);
+ RT_ZERO(pCtx->AltPrivate.auH);
+}
+RT_EXPORT_SYMBOL(RTSha512Final);
+
+
+RTDECL(void) RTSha512(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA512_HASH_SIZE])
+{
+ RTSHA512CONTEXT Ctx;
+ RTSha512Init(&Ctx);
+ RTSha512Update(&Ctx, pvBuf, cbBuf);
+ RTSha512Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha512);
+
+
+RTDECL(bool) RTSha512Check(const void *pvBuf, size_t cbBuf, uint8_t const pabHash[RTSHA512_HASH_SIZE])
+{
+ RTSHA512CONTEXT Ctx;
+ RTSha512Init(&Ctx);
+ RTSha512Update(&Ctx, pvBuf, cbBuf);
+ rtSha512FinalInternal(&Ctx);
+
+ bool fRet = memcmp(pabHash, &Ctx.AltPrivate.auH[0], RTSHA512_HASH_SIZE) == 0;
+
+ RT_ZERO(Ctx.AltPrivate.auH);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha512Check);
+
+
+
+/*
+ * SHA-384 is just SHA-512 with different initial values an a truncated result.
+ */
+
+RTDECL(void) RTSha384Init(PRTSHA384CONTEXT pCtx)
+{
+ pCtx->AltPrivate.cbMessage.s.Lo = 0;
+ pCtx->AltPrivate.cbMessage.s.Hi = 0;
+ pCtx->AltPrivate.auH[0] = UINT64_C(0xcbbb9d5dc1059ed8);
+ pCtx->AltPrivate.auH[1] = UINT64_C(0x629a292a367cd507);
+ pCtx->AltPrivate.auH[2] = UINT64_C(0x9159015a3070dd17);
+ pCtx->AltPrivate.auH[3] = UINT64_C(0x152fecd8f70e5939);
+ pCtx->AltPrivate.auH[4] = UINT64_C(0x67332667ffc00b31);
+ pCtx->AltPrivate.auH[5] = UINT64_C(0x8eb44a8768581511);
+ pCtx->AltPrivate.auH[6] = UINT64_C(0xdb0c2e0d64f98fa7);
+ pCtx->AltPrivate.auH[7] = UINT64_C(0x47b5481dbefa4fa4);
+}
+RT_EXPORT_SYMBOL(RTSha384Init);
+
+
+RTDECL(void) RTSha384Update(PRTSHA384CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ RTSha512Update(pCtx, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTSha384Update);
+
+
+RTDECL(void) RTSha384Final(PRTSHA384CONTEXT pCtx, uint8_t pabDigest[RTSHA384_HASH_SIZE])
+{
+ rtSha512FinalInternal(pCtx);
+ memcpy(pabDigest, &pCtx->AltPrivate.auH[0], RTSHA384_HASH_SIZE);
+ RT_ZERO(pCtx->AltPrivate.auH);
+}
+RT_EXPORT_SYMBOL(RTSha384Final);
+
+
+RTDECL(void) RTSha384(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA384_HASH_SIZE])
+{
+ RTSHA384CONTEXT Ctx;
+ RTSha384Init(&Ctx);
+ RTSha384Update(&Ctx, pvBuf, cbBuf);
+ RTSha384Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha384);
+
+
+RTDECL(bool) RTSha384Check(const void *pvBuf, size_t cbBuf, uint8_t const pabHash[RTSHA384_HASH_SIZE])
+{
+ RTSHA384CONTEXT Ctx;
+ RTSha384Init(&Ctx);
+ RTSha384Update(&Ctx, pvBuf, cbBuf);
+ rtSha512FinalInternal(&Ctx);
+
+ bool fRet = memcmp(pabHash, &Ctx.AltPrivate.auH[0], RTSHA384_HASH_SIZE) == 0;
+
+ RT_ZERO(Ctx.AltPrivate.auH);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha384Check);
+
+
+/*
+ * SHA-512/224 is just SHA-512 with different initial values an a truncated result.
+ */
+
+RTDECL(void) RTSha512t224Init(PRTSHA512T224CONTEXT pCtx)
+{
+ pCtx->AltPrivate.cbMessage.s.Lo = 0;
+ pCtx->AltPrivate.cbMessage.s.Hi = 0;
+ pCtx->AltPrivate.auH[0] = UINT64_C(0x8c3d37c819544da2);
+ pCtx->AltPrivate.auH[1] = UINT64_C(0x73e1996689dcd4d6);
+ pCtx->AltPrivate.auH[2] = UINT64_C(0x1dfab7ae32ff9c82);
+ pCtx->AltPrivate.auH[3] = UINT64_C(0x679dd514582f9fcf);
+ pCtx->AltPrivate.auH[4] = UINT64_C(0x0f6d2b697bd44da8);
+ pCtx->AltPrivate.auH[5] = UINT64_C(0x77e36f7304c48942);
+ pCtx->AltPrivate.auH[6] = UINT64_C(0x3f9d85a86a1d36c8);
+ pCtx->AltPrivate.auH[7] = UINT64_C(0x1112e6ad91d692a1);
+}
+RT_EXPORT_SYMBOL(RTSha512t224Init);
+
+
+RTDECL(void) RTSha512t224Update(PRTSHA512T224CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ RTSha512Update(pCtx, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTSha512t224Update);
+
+
+RTDECL(void) RTSha512t224Final(PRTSHA512T224CONTEXT pCtx, uint8_t pabDigest[RTSHA512T224_HASH_SIZE])
+{
+ rtSha512FinalInternal(pCtx);
+ memcpy(pabDigest, &pCtx->AltPrivate.auH[0], RTSHA512T224_HASH_SIZE);
+ RT_ZERO(pCtx->AltPrivate.auH);
+}
+RT_EXPORT_SYMBOL(RTSha512t224Final);
+
+
+RTDECL(void) RTSha512t224(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA512T224_HASH_SIZE])
+{
+ RTSHA512T224CONTEXT Ctx;
+ RTSha512t224Init(&Ctx);
+ RTSha512t224Update(&Ctx, pvBuf, cbBuf);
+ RTSha512t224Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha512t224);
+
+
+RTDECL(bool) RTSha512t224Check(const void *pvBuf, size_t cbBuf, uint8_t const pabHash[RTSHA512T224_HASH_SIZE])
+{
+ RTSHA512T224CONTEXT Ctx;
+ RTSha512t224Init(&Ctx);
+ RTSha512t224Update(&Ctx, pvBuf, cbBuf);
+ rtSha512FinalInternal(&Ctx);
+
+ bool fRet = memcmp(pabHash, &Ctx.AltPrivate.auH[0], RTSHA512T224_HASH_SIZE) == 0;
+
+ RT_ZERO(Ctx.AltPrivate.auH);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha512t224Check);
+
+
+/*
+ * SHA-512/256 is just SHA-512 with different initial values an a truncated result.
+ */
+
+RTDECL(void) RTSha512t256Init(PRTSHA512T256CONTEXT pCtx)
+{
+ pCtx->AltPrivate.cbMessage.s.Lo = 0;
+ pCtx->AltPrivate.cbMessage.s.Hi = 0;
+ pCtx->AltPrivate.auH[0] = UINT64_C(0x22312194fc2bf72c);
+ pCtx->AltPrivate.auH[1] = UINT64_C(0x9f555fa3c84c64c2);
+ pCtx->AltPrivate.auH[2] = UINT64_C(0x2393b86b6f53b151);
+ pCtx->AltPrivate.auH[3] = UINT64_C(0x963877195940eabd);
+ pCtx->AltPrivate.auH[4] = UINT64_C(0x96283ee2a88effe3);
+ pCtx->AltPrivate.auH[5] = UINT64_C(0xbe5e1e2553863992);
+ pCtx->AltPrivate.auH[6] = UINT64_C(0x2b0199fc2c85b8aa);
+ pCtx->AltPrivate.auH[7] = UINT64_C(0x0eb72ddc81c52ca2);
+}
+RT_EXPORT_SYMBOL(RTSha512t256Init);
+
+
+RTDECL(void) RTSha512t256Update(PRTSHA512T256CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ RTSha512Update(pCtx, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTSha512t256Update);
+
+
+RTDECL(void) RTSha512t256Final(PRTSHA512T256CONTEXT pCtx, uint8_t pabDigest[RTSHA512T256_HASH_SIZE])
+{
+ rtSha512FinalInternal(pCtx);
+ memcpy(pabDigest, &pCtx->AltPrivate.auH[0], RTSHA512T256_HASH_SIZE);
+ RT_ZERO(pCtx->AltPrivate.auH);
+}
+RT_EXPORT_SYMBOL(RTSha512t256Final);
+
+
+RTDECL(void) RTSha512t256(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA512T256_HASH_SIZE])
+{
+ RTSHA512T256CONTEXT Ctx;
+ RTSha512t256Init(&Ctx);
+ RTSha512t256Update(&Ctx, pvBuf, cbBuf);
+ RTSha512t256Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha512t256);
+
+
+RTDECL(bool) RTSha512t256Check(const void *pvBuf, size_t cbBuf, uint8_t const pabHash[RTSHA512T256_HASH_SIZE])
+{
+ RTSHA512T256CONTEXT Ctx;
+ RTSha512t256Init(&Ctx);
+ RTSha512t256Update(&Ctx, pvBuf, cbBuf);
+ rtSha512FinalInternal(&Ctx);
+
+ bool fRet = memcmp(pabHash, &Ctx.AltPrivate.auH[0], RTSHA512T256_HASH_SIZE) == 0;
+
+ RT_ZERO(Ctx.AltPrivate.auH);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha512t256Check);
+
diff --git a/src/VBox/Runtime/common/checksum/crc16ccitt.cpp b/src/VBox/Runtime/common/checksum/crc16ccitt.cpp
new file mode 100644
index 00000000..6ca03ed0
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/crc16ccitt.cpp
@@ -0,0 +1,118 @@
+/* $Id: crc16ccitt.cpp $ */
+/** @file
+ * IPRT - CRC-16-CCITT.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/crc.h>
+#include "internal/iprt.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+/**
+ * CRC-16-CCTII / CRC-CCTII / CRC 010041 table.
+ */
+static uint16_t const g_au16Crc16Cctii[0x100] =
+{
+ UINT16_C(0x0000), UINT16_C(0x1021), UINT16_C(0x2042), UINT16_C(0x3063), UINT16_C(0x4084), UINT16_C(0x50a5), UINT16_C(0x60c6), UINT16_C(0x70e7),
+ UINT16_C(0x8108), UINT16_C(0x9129), UINT16_C(0xa14a), UINT16_C(0xb16b), UINT16_C(0xc18c), UINT16_C(0xd1ad), UINT16_C(0xe1ce), UINT16_C(0xf1ef),
+ UINT16_C(0x1231), UINT16_C(0x0210), UINT16_C(0x3273), UINT16_C(0x2252), UINT16_C(0x52b5), UINT16_C(0x4294), UINT16_C(0x72f7), UINT16_C(0x62d6),
+ UINT16_C(0x9339), UINT16_C(0x8318), UINT16_C(0xb37b), UINT16_C(0xa35a), UINT16_C(0xd3bd), UINT16_C(0xc39c), UINT16_C(0xf3ff), UINT16_C(0xe3de),
+ UINT16_C(0x2462), UINT16_C(0x3443), UINT16_C(0x0420), UINT16_C(0x1401), UINT16_C(0x64e6), UINT16_C(0x74c7), UINT16_C(0x44a4), UINT16_C(0x5485),
+ UINT16_C(0xa56a), UINT16_C(0xb54b), UINT16_C(0x8528), UINT16_C(0x9509), UINT16_C(0xe5ee), UINT16_C(0xf5cf), UINT16_C(0xc5ac), UINT16_C(0xd58d),
+ UINT16_C(0x3653), UINT16_C(0x2672), UINT16_C(0x1611), UINT16_C(0x0630), UINT16_C(0x76d7), UINT16_C(0x66f6), UINT16_C(0x5695), UINT16_C(0x46b4),
+ UINT16_C(0xb75b), UINT16_C(0xa77a), UINT16_C(0x9719), UINT16_C(0x8738), UINT16_C(0xf7df), UINT16_C(0xe7fe), UINT16_C(0xd79d), UINT16_C(0xc7bc),
+ UINT16_C(0x48c4), UINT16_C(0x58e5), UINT16_C(0x6886), UINT16_C(0x78a7), UINT16_C(0x0840), UINT16_C(0x1861), UINT16_C(0x2802), UINT16_C(0x3823),
+ UINT16_C(0xc9cc), UINT16_C(0xd9ed), UINT16_C(0xe98e), UINT16_C(0xf9af), UINT16_C(0x8948), UINT16_C(0x9969), UINT16_C(0xa90a), UINT16_C(0xb92b),
+ UINT16_C(0x5af5), UINT16_C(0x4ad4), UINT16_C(0x7ab7), UINT16_C(0x6a96), UINT16_C(0x1a71), UINT16_C(0x0a50), UINT16_C(0x3a33), UINT16_C(0x2a12),
+ UINT16_C(0xdbfd), UINT16_C(0xcbdc), UINT16_C(0xfbbf), UINT16_C(0xeb9e), UINT16_C(0x9b79), UINT16_C(0x8b58), UINT16_C(0xbb3b), UINT16_C(0xab1a),
+ UINT16_C(0x6ca6), UINT16_C(0x7c87), UINT16_C(0x4ce4), UINT16_C(0x5cc5), UINT16_C(0x2c22), UINT16_C(0x3c03), UINT16_C(0x0c60), UINT16_C(0x1c41),
+ UINT16_C(0xedae), UINT16_C(0xfd8f), UINT16_C(0xcdec), UINT16_C(0xddcd), UINT16_C(0xad2a), UINT16_C(0xbd0b), UINT16_C(0x8d68), UINT16_C(0x9d49),
+ UINT16_C(0x7e97), UINT16_C(0x6eb6), UINT16_C(0x5ed5), UINT16_C(0x4ef4), UINT16_C(0x3e13), UINT16_C(0x2e32), UINT16_C(0x1e51), UINT16_C(0x0e70),
+ UINT16_C(0xff9f), UINT16_C(0xefbe), UINT16_C(0xdfdd), UINT16_C(0xcffc), UINT16_C(0xbf1b), UINT16_C(0xaf3a), UINT16_C(0x9f59), UINT16_C(0x8f78),
+ UINT16_C(0x9188), UINT16_C(0x81a9), UINT16_C(0xb1ca), UINT16_C(0xa1eb), UINT16_C(0xd10c), UINT16_C(0xc12d), UINT16_C(0xf14e), UINT16_C(0xe16f),
+ UINT16_C(0x1080), UINT16_C(0x00a1), UINT16_C(0x30c2), UINT16_C(0x20e3), UINT16_C(0x5004), UINT16_C(0x4025), UINT16_C(0x7046), UINT16_C(0x6067),
+ UINT16_C(0x83b9), UINT16_C(0x9398), UINT16_C(0xa3fb), UINT16_C(0xb3da), UINT16_C(0xc33d), UINT16_C(0xd31c), UINT16_C(0xe37f), UINT16_C(0xf35e),
+ UINT16_C(0x02b1), UINT16_C(0x1290), UINT16_C(0x22f3), UINT16_C(0x32d2), UINT16_C(0x4235), UINT16_C(0x5214), UINT16_C(0x6277), UINT16_C(0x7256),
+ UINT16_C(0xb5ea), UINT16_C(0xa5cb), UINT16_C(0x95a8), UINT16_C(0x8589), UINT16_C(0xf56e), UINT16_C(0xe54f), UINT16_C(0xd52c), UINT16_C(0xc50d),
+ UINT16_C(0x34e2), UINT16_C(0x24c3), UINT16_C(0x14a0), UINT16_C(0x0481), UINT16_C(0x7466), UINT16_C(0x6447), UINT16_C(0x5424), UINT16_C(0x4405),
+ UINT16_C(0xa7db), UINT16_C(0xb7fa), UINT16_C(0x8799), UINT16_C(0x97b8), UINT16_C(0xe75f), UINT16_C(0xf77e), UINT16_C(0xc71d), UINT16_C(0xd73c),
+ UINT16_C(0x26d3), UINT16_C(0x36f2), UINT16_C(0x0691), UINT16_C(0x16b0), UINT16_C(0x6657), UINT16_C(0x7676), UINT16_C(0x4615), UINT16_C(0x5634),
+ UINT16_C(0xd94c), UINT16_C(0xc96d), UINT16_C(0xf90e), UINT16_C(0xe92f), UINT16_C(0x99c8), UINT16_C(0x89e9), UINT16_C(0xb98a), UINT16_C(0xa9ab),
+ UINT16_C(0x5844), UINT16_C(0x4865), UINT16_C(0x7806), UINT16_C(0x6827), UINT16_C(0x18c0), UINT16_C(0x08e1), UINT16_C(0x3882), UINT16_C(0x28a3),
+ UINT16_C(0xcb7d), UINT16_C(0xdb5c), UINT16_C(0xeb3f), UINT16_C(0xfb1e), UINT16_C(0x8bf9), UINT16_C(0x9bd8), UINT16_C(0xabbb), UINT16_C(0xbb9a),
+ UINT16_C(0x4a75), UINT16_C(0x5a54), UINT16_C(0x6a37), UINT16_C(0x7a16), UINT16_C(0x0af1), UINT16_C(0x1ad0), UINT16_C(0x2ab3), UINT16_C(0x3a92),
+ UINT16_C(0xfd2e), UINT16_C(0xed0f), UINT16_C(0xdd6c), UINT16_C(0xcd4d), UINT16_C(0xbdaa), UINT16_C(0xad8b), UINT16_C(0x9de8), UINT16_C(0x8dc9),
+ UINT16_C(0x7c26), UINT16_C(0x6c07), UINT16_C(0x5c64), UINT16_C(0x4c45), UINT16_C(0x3ca2), UINT16_C(0x2c83), UINT16_C(0x1ce0), UINT16_C(0x0cc1),
+ UINT16_C(0xef1f), UINT16_C(0xff3e), UINT16_C(0xcf5d), UINT16_C(0xdf7c), UINT16_C(0xaf9b), UINT16_C(0xbfba), UINT16_C(0x8fd9), UINT16_C(0x9ff8),
+ UINT16_C(0x6e17), UINT16_C(0x7e36), UINT16_C(0x4e55), UINT16_C(0x5e74), UINT16_C(0x2e93), UINT16_C(0x3eb2), UINT16_C(0x0ed1), UINT16_C(0x1ef0),
+};
+
+
+
+RTDECL(uint16_t) RTCrc16Ccitt(const void *pv, size_t cb)
+{
+ uint16_t uCrc = 0;
+ const uint8_t *pb = (const uint8_t *)pv;
+ while (cb-- > 0)
+ uCrc = g_au16Crc16Cctii[(uint8_t)(uCrc >> 8) ^ *pb++] ^ (uCrc << 8);
+ return uCrc;
+}
+
+
+RTDECL(uint16_t) RTCrc16CcittStart(void)
+{
+ return 0;
+}
+
+
+RTDECL(uint16_t) RTCrc16CcittProcess(uint16_t uCrc, const void *pv, size_t cb)
+{
+ const uint8_t *pb = (const uint8_t *)pv;
+ while (cb-- > 0)
+ uCrc = g_au16Crc16Cctii[(uint8_t)(uCrc >> 8) ^ *pb++] ^ (uCrc << 8);
+ return uCrc;
+}
+
+
+RTDECL(uint16_t) RTCrc16CcittFinish(uint16_t uCrc)
+{
+ return uCrc;
+}
+
diff --git a/src/VBox/Runtime/common/checksum/crc32-zlib.cpp b/src/VBox/Runtime/common/checksum/crc32-zlib.cpp
new file mode 100644
index 00000000..0b41b28d
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/crc32-zlib.cpp
@@ -0,0 +1,99 @@
+/* $Id: crc32-zlib.cpp $ */
+/** @file
+ * IPRT - CRC-32 on top of zlib (very fast).
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crc.h>
+
+#include <zlib.h>
+
+/** @todo Check if we can't just use the zlib code directly here. */
+
+/**
+ * Deal with blocks that are too big for the uInt type.
+ */
+static uint32_t rtCrc32ProcessTooBig(uint32_t uCRC32, const void *pv, size_t cb)
+{
+ const Bytef *pb = (const Bytef *)pv;
+ do
+ {
+ uInt const cbChunk = cb <= ~(uInt)0 ? (uInt)cb : ~(uInt)0;
+ uCRC32 = crc32(uCRC32, pb, cbChunk);
+ pb += cbChunk;
+ cb -= cbChunk;
+ } while (!cb);
+ return uCRC32;
+}
+
+RTDECL(uint32_t) RTCrc32(const void *pv, size_t cb)
+{
+ uint32_t uCrc = crc32(0, NULL, 0);
+ if (RT_UNLIKELY((uInt)cb == cb))
+ uCrc = crc32(uCrc, (const Bytef *)pv, (uInt)cb);
+ else
+ uCrc = rtCrc32ProcessTooBig(uCrc, pv, cb);
+ return uCrc;
+}
+RT_EXPORT_SYMBOL(RTCrc32);
+
+
+RTDECL(uint32_t) RTCrc32Start(void)
+{
+ return crc32(0, NULL, 0);
+}
+RT_EXPORT_SYMBOL(RTCrc32Start);
+
+
+RTDECL(uint32_t) RTCrc32Process(uint32_t uCRC32, const void *pv, size_t cb)
+{
+ if (RT_UNLIKELY((uInt)cb == cb))
+ uCRC32 = crc32(uCRC32, (const Bytef *)pv, (uInt)cb);
+ else
+ uCRC32 = rtCrc32ProcessTooBig(uCRC32, pv, cb);
+ return uCRC32;
+}
+RT_EXPORT_SYMBOL(RTCrc32Process);
+
+
+RTDECL(uint32_t) RTCrc32Finish(uint32_t uCRC32)
+{
+ return uCRC32;
+}
+RT_EXPORT_SYMBOL(RTCrc32Finish);
+
diff --git a/src/VBox/Runtime/common/checksum/crc32.cpp b/src/VBox/Runtime/common/checksum/crc32.cpp
new file mode 100644
index 00000000..4d29782f
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/crc32.cpp
@@ -0,0 +1,196 @@
+/* $Id: crc32.cpp $ */
+/** @file
+ * IPRT - CRC32.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ * --------------------------------------------------------------------
+ *
+ * This code is based on:
+ *
+ * CRC32 code derived from work by Gary S. Brown.
+ *
+ * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or
+ * code or tables extracted from it, as desired without restriction.
+ *
+ * First, the polynomial itself and its table of feedback terms. The
+ * polynomial is
+ * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ * Note that we take it "backwards" and put the highest-order term in
+ * the lowest-order bit. The X^32 term is "implied"; the LSB is the
+ * X^31 term, etc. The X^0 term (usually shown as "+1") results in
+ * the MSB being 1
+ *
+ * Note that the usual hardware shift register implementation, which
+ * is what we're using (we're merely optimizing it by doing eight-bit
+ * chunks at a time) shifts bits into the lowest-order term. In our
+ * implementation, that means shifting towards the right. Why do we
+ * do it this way? Because the calculated CRC must be transmitted in
+ * order from highest-order term to lowest-order term. UARTs transmit
+ * characters in order from LSB to MSB. By storing the CRC this way
+ * we hand it to the UART in the order low-byte to high-byte; the UART
+ * sends each low-bit to height-bit; and the result is transmission bit
+ * by bit from highest- to lowest-order term without requiring any bit
+ * shuffling on our part. Reception works similarly
+ *
+ * The feedback terms table consists of 256, 32-bit entries. Notes
+ *
+ * The table can be generated at runtime if desired; code to do so
+ * is shown later. It might not be obvious, but the feedback
+ * terms simply represent the results of eight shift/xor opera
+ * tions for all combinations of data and CRC register values
+ *
+ * The values must be right-shifted by eight bits by the "updcrc
+ * logic; the shift must be unsigned (bring in zeroes). On some
+ * hardware you could probably optimize the shift in assembler by
+ * using byte-swap instructions
+ * polynomial $edb88320
+ *
+ */
+
+#if 0
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/sys/libkern/crc32.c,v 1.2 2003/06/11 05:23:04 obrien Exp $");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#else
+# include <iprt/crc.h>
+# include "internal/iprt.h"
+#endif
+
+#if 0
+uint32_t crc32_tab[] = {
+#else
+/** CRC32 feedback table. */
+static uint32_t const g_au32CRC32[] =
+{
+#endif
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+ 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+ 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+ 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+ 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+ 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+ 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+ 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+ 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+ 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+ 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+ 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+ 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+ 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+ 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+ 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+ 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+ 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+#if 0
+uint32_t
+crc32(const void *buf, size_t size)
+{
+ const uint8_t *p;
+ uint32_t crc;
+
+ p = buf;
+ crc = ~0U;
+
+ while (size--)
+ crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+
+ return crc ^ ~0U;
+}
+#endif
+
+
+
+
+RTDECL(uint32_t) RTCrc32(const void *pv, size_t cb)
+{
+ const uint8_t *pu8 = (const uint8_t *)pv;
+ uint32_t uCRC32 = ~0U;
+ while (cb--)
+ uCRC32 = g_au32CRC32[(uCRC32 ^ *pu8++) & 0xff] ^ (uCRC32 >> 8);
+ return uCRC32 ^ ~0U;
+}
+RT_EXPORT_SYMBOL(RTCrc32);
+
+
+RTDECL(uint32_t) RTCrc32Start(void)
+{
+ return ~0U;
+}
+RT_EXPORT_SYMBOL(RTCrc32Start);
+
+
+RTDECL(uint32_t) RTCrc32Process(uint32_t uCRC32, const void *pv, size_t cb)
+{
+ const uint8_t *pu8 = (const uint8_t *)pv;
+ while (cb--)
+ uCRC32 = g_au32CRC32[(uCRC32 ^ *pu8++) & 0xff] ^ (uCRC32 >> 8);
+ return uCRC32;
+}
+RT_EXPORT_SYMBOL(RTCrc32Process);
+
+
+RTDECL(uint32_t) RTCrc32Finish(uint32_t uCRC32)
+{
+ return uCRC32 ^ ~0U;
+}
+RT_EXPORT_SYMBOL(RTCrc32Finish);
+
diff --git a/src/VBox/Runtime/common/checksum/crc32c.cpp b/src/VBox/Runtime/common/checksum/crc32c.cpp
new file mode 100644
index 00000000..184c8166
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/crc32c.cpp
@@ -0,0 +1,132 @@
+/* $Id: crc32c.cpp $ */
+/** @file
+ * IPRT - CRC32C.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <iprt/crc.h>
+#include "internal/iprt.h"
+
+/**
+ * Generated using the pycrc tool using model crc-32c.
+ */
+static uint32_t const g_au32Crc32C[] =
+{
+ 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c,
+ 0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b,
+ 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c,
+ 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,
+ 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc,
+ 0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a,
+ 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512,
+ 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,
+ 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad,
+ 0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a,
+ 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 0x417b1dbc, 0xb3109ebf,
+ 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,
+ 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f,
+ 0xed03a29b, 0x1f682198, 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927,
+ 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f,
+ 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,
+ 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e,
+ 0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859,
+ 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e,
+ 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,
+ 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de,
+ 0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c,
+ 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 0x082f63b7, 0xfa44e0b4,
+ 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,
+ 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b,
+ 0xb4091bff, 0x466298fc, 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c,
+ 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5,
+ 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,
+ 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975,
+ 0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d,
+ 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905,
+ 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,
+ 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8,
+ 0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff,
+ 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8,
+ 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,
+ 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78,
+ 0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee,
+ 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6,
+ 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,
+ 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69,
+ 0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,
+ 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351
+};
+
+
+DECLINLINE(uint32_t) rtCrc32CProcessWithTable(const uint32_t *pau32Crc32,
+ uint32_t uCrc32, const void *pv, size_t cb)
+{
+ const uint8_t *pu8 = (const uint8_t *)pv;
+
+ while (cb--)
+ uCrc32 = pau32Crc32[(uCrc32 ^ *pu8++) & 0xff] ^ (uCrc32 >> 8);
+
+ return uCrc32;
+}
+
+
+RTDECL(uint32_t) RTCrc32CStart(void)
+{
+ return ~0U;
+}
+RT_EXPORT_SYMBOL(RTCrc32CStart);
+
+
+RTDECL(uint32_t) RTCrc32CFinish(uint32_t uCRC32)
+{
+ return uCRC32 ^ ~0U;
+}
+RT_EXPORT_SYMBOL(RTCrc32CFinish);
+
+
+RTDECL(uint32_t) RTCrc32C(const void *pv, size_t cb)
+{
+ uint32_t uCrc32C = RTCrc32CStart();
+
+ uCrc32C = rtCrc32CProcessWithTable(g_au32Crc32C, uCrc32C, pv, cb);
+ return RTCrc32CFinish(uCrc32C);
+}
+RT_EXPORT_SYMBOL(RTCrc32C);
+
+
+RTDECL(uint32_t) RTCrc32CProcess(uint32_t uCrc32C, const void *pv, size_t cb)
+{
+ return rtCrc32CProcessWithTable(g_au32Crc32C, uCrc32C, pv, cb);;
+}
+RT_EXPORT_SYMBOL(RTCrc32CProcess);
+
diff --git a/src/VBox/Runtime/common/checksum/crc64.cpp b/src/VBox/Runtime/common/checksum/crc64.cpp
new file mode 100644
index 00000000..008bdd1d
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/crc64.cpp
@@ -0,0 +1,196 @@
+/* $Id: crc64.cpp $ */
+/** @file
+ * IPRT - CRC64.
+ *
+ * The method to compute the CRC64 is referred to as CRC-64-ISO:
+ * http://en.wikipedia.org/wiki/Cyclic_redundancy_check
+ * The generator polynomial is x^64 + x^4 + x^3 + x + 1.
+ * Reverse polynom: 0xd800000000000000ULL
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/crc.h>
+#include "internal/iprt.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Lookup table (precomputed CRC64 values for each 8 bit string) computation
+ * takes into account the fact that the reverse polynom has zeros in lower 8 bits:
+ *
+ * @code
+ * for (i = 0; i < 256; i++)
+ * {
+ * shiftRegister = i;
+ * for (j = 0; j < 8; j++)
+ * {
+ * if (shiftRegister & 1)
+ * shiftRegister = (shiftRegister >> 1) ^ Reverse_polynom;
+ * else
+ * shiftRegister >>= 1;
+ * }
+ * CRCTable[i] = shiftRegister;
+ * }
+ * @endcode
+ *
+ * Generic code would look as follows:
+ *
+ * @code
+ * for (i = 0; i < 256; i++)
+ * {
+ * shiftRegister = 0;
+ * bitString = i;
+ * for (j = 0; j < 8; j++)
+ * {
+ * if ((shiftRegister ^ (bitString >> j)) & 1)
+ * shiftRegister = (shiftRegister >> 1) ^ Reverse_polynom;
+ * else
+ * shiftRegister >>= 1;
+ * }
+ * CRCTable[i] = shiftRegister;
+ * }
+ * @endcode
+ *
+ * @remark Since the lookup table elements have 0 in the lower 32 bit word,
+ * the 32 bit assembler implementation of CRC64Process can be optimized,
+ * avoiding at least one 'xor' operation.
+ */
+static const uint64_t g_au64CRC64[] =
+{
+ 0x0000000000000000ULL, 0x01B0000000000000ULL, 0x0360000000000000ULL, 0x02D0000000000000ULL,
+ 0x06C0000000000000ULL, 0x0770000000000000ULL, 0x05A0000000000000ULL, 0x0410000000000000ULL,
+ 0x0D80000000000000ULL, 0x0C30000000000000ULL, 0x0EE0000000000000ULL, 0x0F50000000000000ULL,
+ 0x0B40000000000000ULL, 0x0AF0000000000000ULL, 0x0820000000000000ULL, 0x0990000000000000ULL,
+ 0x1B00000000000000ULL, 0x1AB0000000000000ULL, 0x1860000000000000ULL, 0x19D0000000000000ULL,
+ 0x1DC0000000000000ULL, 0x1C70000000000000ULL, 0x1EA0000000000000ULL, 0x1F10000000000000ULL,
+ 0x1680000000000000ULL, 0x1730000000000000ULL, 0x15E0000000000000ULL, 0x1450000000000000ULL,
+ 0x1040000000000000ULL, 0x11F0000000000000ULL, 0x1320000000000000ULL, 0x1290000000000000ULL,
+ 0x3600000000000000ULL, 0x37B0000000000000ULL, 0x3560000000000000ULL, 0x34D0000000000000ULL,
+ 0x30C0000000000000ULL, 0x3170000000000000ULL, 0x33A0000000000000ULL, 0x3210000000000000ULL,
+ 0x3B80000000000000ULL, 0x3A30000000000000ULL, 0x38E0000000000000ULL, 0x3950000000000000ULL,
+ 0x3D40000000000000ULL, 0x3CF0000000000000ULL, 0x3E20000000000000ULL, 0x3F90000000000000ULL,
+ 0x2D00000000000000ULL, 0x2CB0000000000000ULL, 0x2E60000000000000ULL, 0x2FD0000000000000ULL,
+ 0x2BC0000000000000ULL, 0x2A70000000000000ULL, 0x28A0000000000000ULL, 0x2910000000000000ULL,
+ 0x2080000000000000ULL, 0x2130000000000000ULL, 0x23E0000000000000ULL, 0x2250000000000000ULL,
+ 0x2640000000000000ULL, 0x27F0000000000000ULL, 0x2520000000000000ULL, 0x2490000000000000ULL,
+ 0x6C00000000000000ULL, 0x6DB0000000000000ULL, 0x6F60000000000000ULL, 0x6ED0000000000000ULL,
+ 0x6AC0000000000000ULL, 0x6B70000000000000ULL, 0x69A0000000000000ULL, 0x6810000000000000ULL,
+ 0x6180000000000000ULL, 0x6030000000000000ULL, 0x62E0000000000000ULL, 0x6350000000000000ULL,
+ 0x6740000000000000ULL, 0x66F0000000000000ULL, 0x6420000000000000ULL, 0x6590000000000000ULL,
+ 0x7700000000000000ULL, 0x76B0000000000000ULL, 0x7460000000000000ULL, 0x75D0000000000000ULL,
+ 0x71C0000000000000ULL, 0x7070000000000000ULL, 0x72A0000000000000ULL, 0x7310000000000000ULL,
+ 0x7A80000000000000ULL, 0x7B30000000000000ULL, 0x79E0000000000000ULL, 0x7850000000000000ULL,
+ 0x7C40000000000000ULL, 0x7DF0000000000000ULL, 0x7F20000000000000ULL, 0x7E90000000000000ULL,
+ 0x5A00000000000000ULL, 0x5BB0000000000000ULL, 0x5960000000000000ULL, 0x58D0000000000000ULL,
+ 0x5CC0000000000000ULL, 0x5D70000000000000ULL, 0x5FA0000000000000ULL, 0x5E10000000000000ULL,
+ 0x5780000000000000ULL, 0x5630000000000000ULL, 0x54E0000000000000ULL, 0x5550000000000000ULL,
+ 0x5140000000000000ULL, 0x50F0000000000000ULL, 0x5220000000000000ULL, 0x5390000000000000ULL,
+ 0x4100000000000000ULL, 0x40B0000000000000ULL, 0x4260000000000000ULL, 0x43D0000000000000ULL,
+ 0x47C0000000000000ULL, 0x4670000000000000ULL, 0x44A0000000000000ULL, 0x4510000000000000ULL,
+ 0x4C80000000000000ULL, 0x4D30000000000000ULL, 0x4FE0000000000000ULL, 0x4E50000000000000ULL,
+ 0x4A40000000000000ULL, 0x4BF0000000000000ULL, 0x4920000000000000ULL, 0x4890000000000000ULL,
+ 0xD800000000000000ULL, 0xD9B0000000000000ULL, 0xDB60000000000000ULL, 0xDAD0000000000000ULL,
+ 0xDEC0000000000000ULL, 0xDF70000000000000ULL, 0xDDA0000000000000ULL, 0xDC10000000000000ULL,
+ 0xD580000000000000ULL, 0xD430000000000000ULL, 0xD6E0000000000000ULL, 0xD750000000000000ULL,
+ 0xD340000000000000ULL, 0xD2F0000000000000ULL, 0xD020000000000000ULL, 0xD190000000000000ULL,
+ 0xC300000000000000ULL, 0xC2B0000000000000ULL, 0xC060000000000000ULL, 0xC1D0000000000000ULL,
+ 0xC5C0000000000000ULL, 0xC470000000000000ULL, 0xC6A0000000000000ULL, 0xC710000000000000ULL,
+ 0xCE80000000000000ULL, 0xCF30000000000000ULL, 0xCDE0000000000000ULL, 0xCC50000000000000ULL,
+ 0xC840000000000000ULL, 0xC9F0000000000000ULL, 0xCB20000000000000ULL, 0xCA90000000000000ULL,
+ 0xEE00000000000000ULL, 0xEFB0000000000000ULL, 0xED60000000000000ULL, 0xECD0000000000000ULL,
+ 0xE8C0000000000000ULL, 0xE970000000000000ULL, 0xEBA0000000000000ULL, 0xEA10000000000000ULL,
+ 0xE380000000000000ULL, 0xE230000000000000ULL, 0xE0E0000000000000ULL, 0xE150000000000000ULL,
+ 0xE540000000000000ULL, 0xE4F0000000000000ULL, 0xE620000000000000ULL, 0xE790000000000000ULL,
+ 0xF500000000000000ULL, 0xF4B0000000000000ULL, 0xF660000000000000ULL, 0xF7D0000000000000ULL,
+ 0xF3C0000000000000ULL, 0xF270000000000000ULL, 0xF0A0000000000000ULL, 0xF110000000000000ULL,
+ 0xF880000000000000ULL, 0xF930000000000000ULL, 0xFBE0000000000000ULL, 0xFA50000000000000ULL,
+ 0xFE40000000000000ULL, 0xFFF0000000000000ULL, 0xFD20000000000000ULL, 0xFC90000000000000ULL,
+ 0xB400000000000000ULL, 0xB5B0000000000000ULL, 0xB760000000000000ULL, 0xB6D0000000000000ULL,
+ 0xB2C0000000000000ULL, 0xB370000000000000ULL, 0xB1A0000000000000ULL, 0xB010000000000000ULL,
+ 0xB980000000000000ULL, 0xB830000000000000ULL, 0xBAE0000000000000ULL, 0xBB50000000000000ULL,
+ 0xBF40000000000000ULL, 0xBEF0000000000000ULL, 0xBC20000000000000ULL, 0xBD90000000000000ULL,
+ 0xAF00000000000000ULL, 0xAEB0000000000000ULL, 0xAC60000000000000ULL, 0xADD0000000000000ULL,
+ 0xA9C0000000000000ULL, 0xA870000000000000ULL, 0xAAA0000000000000ULL, 0xAB10000000000000ULL,
+ 0xA280000000000000ULL, 0xA330000000000000ULL, 0xA1E0000000000000ULL, 0xA050000000000000ULL,
+ 0xA440000000000000ULL, 0xA5F0000000000000ULL, 0xA720000000000000ULL, 0xA690000000000000ULL,
+ 0x8200000000000000ULL, 0x83B0000000000000ULL, 0x8160000000000000ULL, 0x80D0000000000000ULL,
+ 0x84C0000000000000ULL, 0x8570000000000000ULL, 0x87A0000000000000ULL, 0x8610000000000000ULL,
+ 0x8F80000000000000ULL, 0x8E30000000000000ULL, 0x8CE0000000000000ULL, 0x8D50000000000000ULL,
+ 0x8940000000000000ULL, 0x88F0000000000000ULL, 0x8A20000000000000ULL, 0x8B90000000000000ULL,
+ 0x9900000000000000ULL, 0x98B0000000000000ULL, 0x9A60000000000000ULL, 0x9BD0000000000000ULL,
+ 0x9FC0000000000000ULL, 0x9E70000000000000ULL, 0x9CA0000000000000ULL, 0x9D10000000000000ULL,
+ 0x9480000000000000ULL, 0x9530000000000000ULL, 0x97E0000000000000ULL, 0x9650000000000000ULL,
+ 0x9240000000000000ULL, 0x93F0000000000000ULL, 0x9120000000000000ULL, 0x9090000000000000ULL
+};
+
+
+RTDECL(uint64_t) RTCrc64(const void *pv, size_t cb)
+{
+ const uint8_t *pu8 = (const uint8_t *)pv;
+ uint64_t uCRC64 = 0ULL;
+ while (cb--)
+ uCRC64 = g_au64CRC64[(uCRC64 ^ *pu8++) & 0xff] ^ (uCRC64 >> 8);
+ return uCRC64;
+}
+RT_EXPORT_SYMBOL(RTCrc64);
+
+
+RTDECL(uint64_t) RTCrc64Start(void)
+{
+ return 0ULL;
+}
+RT_EXPORT_SYMBOL(RTCrc64Start);
+
+
+RTDECL(uint64_t) RTCrc64Process(uint64_t uCRC64, const void *pv, size_t cb)
+{
+ const uint8_t *pu8 = (const uint8_t *)pv;
+ while (cb--)
+ uCRC64 = g_au64CRC64[(uCRC64 ^ *pu8++) & 0xff] ^ (uCRC64 >> 8);
+ return uCRC64;
+}
+RT_EXPORT_SYMBOL(RTCrc64Process);
+
+
+RTDECL(uint64_t) RTCrc64Finish(uint64_t uCRC64)
+{
+ return uCRC64;
+}
+RT_EXPORT_SYMBOL(RTCrc64Finish);
+
diff --git a/src/VBox/Runtime/common/checksum/ipv4.cpp b/src/VBox/Runtime/common/checksum/ipv4.cpp
new file mode 100644
index 00000000..2d75b989
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/ipv4.cpp
@@ -0,0 +1,774 @@
+/* $Id: ipv4.cpp $ */
+/** @file
+ * IPRT - IPv4 Checksum calculation and validation.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/net.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+
+
+/**
+ * Calculates the checksum of the IPv4 header.
+ *
+ * @returns Checksum (network endian).
+ * @param pIpHdr Pointer to the IPv4 header to checksum, network endian (big).
+ * Assumes the caller already checked the minimum size requirement.
+ */
+RTDECL(uint16_t) RTNetIPv4HdrChecksum(PCRTNETIPV4 pIpHdr)
+{
+ uint16_t const *paw = (uint16_t const *)pIpHdr;
+ uint32_t u32Sum = paw[0] /* ip_hl */
+ + paw[1] /* ip_len */
+ + paw[2] /* ip_id */
+ + paw[3] /* ip_off */
+ + paw[4] /* ip_ttl */
+ /*+ paw[5] == 0 */ /* ip_sum */
+ + paw[6] /* ip_src */
+ + paw[7] /* ip_src:16 */
+ + paw[8] /* ip_dst */
+ + paw[9]; /* ip_dst:16 */
+ /* any options */
+ if (pIpHdr->ip_hl > 20 / 4)
+ {
+ /* this is a bit insane... (identical to the TCP header) */
+ switch (pIpHdr->ip_hl)
+ {
+ case 6: u32Sum += paw[10] + paw[11]; break;
+ case 7: u32Sum += paw[10] + paw[11] + paw[12] + paw[13]; break;
+ case 8: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15]; break;
+ case 9: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17]; break;
+ case 10: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19]; break;
+ case 11: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21]; break;
+ case 12: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21] + paw[22] + paw[23]; break;
+ case 13: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21] + paw[22] + paw[23] + paw[24] + paw[25]; break;
+ case 14: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21] + paw[22] + paw[23] + paw[24] + paw[25] + paw[26] + paw[27]; break;
+ case 15: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21] + paw[22] + paw[23] + paw[24] + paw[25] + paw[26] + paw[27] + paw[28] + paw[29]; break;
+ default:
+ AssertFailed();
+ }
+ }
+
+ /* 16-bit one complement fun */
+ u32Sum = (u32Sum >> 16) + (u32Sum & 0xffff); /* hi + low words */
+ u32Sum += u32Sum >> 16; /* carry */
+ return (uint16_t)~u32Sum;
+}
+RT_EXPORT_SYMBOL(RTNetIPv4HdrChecksum);
+
+
+/**
+ * Verifies the header version, header size, packet size, and header checksum
+ * of the specified IPv4 header.
+ *
+ * @returns true if valid, false if invalid.
+ * @param pIpHdr Pointer to the IPv4 header to validate. Network endian (big).
+ * @param cbHdrMax The max header size, or the max size of what pIpHdr points
+ * to if you like. Note that an IPv4 header can be up to 60 bytes.
+ * @param cbPktMax The max IP packet size, IP header and payload. This doesn't have
+ * to be mapped following pIpHdr.
+ * @param fChecksum Whether to validate the checksum (GSO).
+ */
+RTDECL(bool) RTNetIPv4IsHdrValid(PCRTNETIPV4 pIpHdr, size_t cbHdrMax, size_t cbPktMax, bool fChecksum)
+{
+ /*
+ * The header fields.
+ */
+ Assert(cbPktMax >= cbHdrMax);
+ if (RT_UNLIKELY(cbHdrMax < RTNETIPV4_MIN_LEN))
+ return false;
+ if (RT_UNLIKELY(pIpHdr->ip_hl * 4 < RTNETIPV4_MIN_LEN))
+ return false;
+ if (RT_UNLIKELY((size_t)pIpHdr->ip_hl * 4 > cbHdrMax))
+ {
+ Assert((size_t)pIpHdr->ip_hl * 4 > cbPktMax); /* You'll hit this if you mapped/copy too little of the header! */
+ return false;
+ }
+ if (RT_UNLIKELY(pIpHdr->ip_v != 4))
+ return false;
+ if (RT_UNLIKELY(RT_BE2H_U16(pIpHdr->ip_len) > cbPktMax))
+ return false;
+
+ /*
+ * The header checksum if requested.
+ */
+ if (fChecksum)
+ {
+ uint16_t u16Sum = RTNetIPv4HdrChecksum(pIpHdr);
+ if (RT_UNLIKELY(pIpHdr->ip_sum != u16Sum))
+ return false;
+ }
+ return true;
+}
+RT_EXPORT_SYMBOL(RTNetIPv4IsHdrValid);
+
+
+/**
+ * Calculates the checksum of a pseudo header given an IPv4 header [inlined].
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pIpHdr The IP header (network endian (big)).
+ */
+DECLINLINE(uint32_t) rtNetIPv4PseudoChecksum(PCRTNETIPV4 pIpHdr)
+{
+ uint16_t cbPayload = RT_BE2H_U16(pIpHdr->ip_len) - pIpHdr->ip_hl * 4;
+ uint32_t u32Sum = pIpHdr->ip_src.au16[0]
+ + pIpHdr->ip_src.au16[1]
+ + pIpHdr->ip_dst.au16[0]
+ + pIpHdr->ip_dst.au16[1]
+#ifdef RT_BIG_ENDIAN
+ + pIpHdr->ip_p
+#else
+ + ((uint32_t)pIpHdr->ip_p << 8)
+#endif
+ + RT_H2BE_U16(cbPayload);
+ return u32Sum;
+}
+
+
+/**
+ * Calculates the checksum of a pseudo header given an IPv4 header.
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pIpHdr The IP header (network endian (big)).
+ */
+RTDECL(uint32_t) RTNetIPv4PseudoChecksum(PCRTNETIPV4 pIpHdr)
+{
+ return rtNetIPv4PseudoChecksum(pIpHdr);
+}
+RT_EXPORT_SYMBOL(RTNetIPv4PseudoChecksum);
+
+
+/**
+ * Calculates the checksum of a pseudo header given the individual components.
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param SrcAddr The source address in host endian.
+ * @param DstAddr The destination address in host endian.
+ * @param bProtocol The protocol number.
+ * @param cbPkt The packet size (host endian of course) (no IPv4 header).
+ */
+RTDECL(uint32_t) RTNetIPv4PseudoChecksumBits(RTNETADDRIPV4 SrcAddr, RTNETADDRIPV4 DstAddr, uint8_t bProtocol, uint16_t cbPkt)
+{
+ uint32_t u32Sum = RT_H2BE_U16(SrcAddr.au16[0])
+ + RT_H2BE_U16(SrcAddr.au16[1])
+ + RT_H2BE_U16(DstAddr.au16[0])
+ + RT_H2BE_U16(DstAddr.au16[1])
+#ifdef RT_BIG_ENDIAN
+ + bProtocol
+#else
+ + ((uint32_t)bProtocol << 8)
+#endif
+ + RT_H2BE_U16(cbPkt);
+ return u32Sum;
+}
+RT_EXPORT_SYMBOL(RTNetIPv4PseudoChecksumBits);
+
+
+/**
+ * Adds the checksum of the UDP header to the intermediate checksum value [inlined].
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pUdpHdr Pointer to the UDP header to checksum, network endian (big).
+ * @param u32Sum The 32-bit intermediate checksum value.
+ */
+DECLINLINE(uint32_t) rtNetIPv4AddUDPChecksum(PCRTNETUDP pUdpHdr, uint32_t u32Sum)
+{
+ u32Sum += pUdpHdr->uh_sport
+ + pUdpHdr->uh_dport
+ /*+ pUdpHdr->uh_sum = 0 */
+ + pUdpHdr->uh_ulen;
+ return u32Sum;
+}
+
+
+/**
+ * Adds the checksum of the UDP header to the intermediate checksum value.
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pUdpHdr Pointer to the UDP header to checksum, network endian (big).
+ * @param u32Sum The 32-bit intermediate checksum value.
+ */
+RTDECL(uint32_t) RTNetIPv4AddUDPChecksum(PCRTNETUDP pUdpHdr, uint32_t u32Sum)
+{
+ return rtNetIPv4AddUDPChecksum(pUdpHdr, u32Sum);
+}
+RT_EXPORT_SYMBOL(RTNetIPv4AddUDPChecksum);
+
+
+/**
+ * Adds the checksum of the TCP header to the intermediate checksum value [inlined].
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pTcpHdr Pointer to the TCP header to checksum, network
+ * endian (big). Assumes the caller has already validate
+ * it and made sure the entire header is present.
+ * @param u32Sum The 32-bit intermediate checksum value.
+ */
+DECLINLINE(uint32_t) rtNetIPv4AddTCPChecksum(PCRTNETTCP pTcpHdr, uint32_t u32Sum)
+{
+ uint16_t const *paw = (uint16_t const *)pTcpHdr;
+ u32Sum += paw[0] /* th_sport */
+ + paw[1] /* th_dport */
+ + paw[2] /* th_seq */
+ + paw[3] /* th_seq:16 */
+ + paw[4] /* th_ack */
+ + paw[5] /* th_ack:16 */
+ + paw[6] /* th_off, th_x2, th_flags */
+ + paw[7] /* th_win */
+ /*+ paw[8] == 0 */ /* th_sum */
+ + paw[9]; /* th_urp */
+ if (pTcpHdr->th_off > RTNETTCP_MIN_LEN / 4)
+ {
+ /* this is a bit insane... (identical to the IPv4 header) */
+ switch (pTcpHdr->th_off)
+ {
+ case 6: u32Sum += paw[10] + paw[11]; break;
+ case 7: u32Sum += paw[10] + paw[11] + paw[12] + paw[13]; break;
+ case 8: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15]; break;
+ case 9: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17]; break;
+ case 10: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19]; break;
+ case 11: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21]; break;
+ case 12: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21] + paw[22] + paw[23]; break;
+ case 13: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21] + paw[22] + paw[23] + paw[24] + paw[25]; break;
+ case 14: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21] + paw[22] + paw[23] + paw[24] + paw[25] + paw[26] + paw[27]; break;
+ case 15: u32Sum += paw[10] + paw[11] + paw[12] + paw[13] + paw[14] + paw[15] + paw[16] + paw[17] + paw[18] + paw[19] + paw[20] + paw[21] + paw[22] + paw[23] + paw[24] + paw[25] + paw[26] + paw[27] + paw[28] + paw[29]; break;
+ default:
+ AssertFailed();
+ }
+ }
+
+ return u32Sum;
+}
+
+
+/**
+ * Adds the checksum of the TCP header to the intermediate checksum value.
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pTcpHdr Pointer to the TCP header to checksum, network
+ * endian (big). Assumes the caller has already validate
+ * it and made sure the entire header is present.
+ * @param u32Sum The 32-bit intermediate checksum value.
+ */
+RTDECL(uint32_t) RTNetIPv4AddTCPChecksum(PCRTNETTCP pTcpHdr, uint32_t u32Sum)
+{
+ return rtNetIPv4AddTCPChecksum(pTcpHdr, u32Sum);
+}
+RT_EXPORT_SYMBOL(RTNetIPv4AddTCPChecksum);
+
+
+/**
+ * Adds the checksum of the specified data segment to the intermediate checksum value [inlined].
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pvData Pointer to the data that should be checksummed.
+ * @param cbData The number of bytes to checksum.
+ * @param u32Sum The 32-bit intermediate checksum value.
+ * @param pfOdd This is used to keep track of odd bits, initialize to false
+ * when starting to checksum the data (aka text) after a TCP
+ * or UDP header (data never start at an odd offset).
+ */
+DECLINLINE(uint32_t) rtNetIPv4AddDataChecksum(void const *pvData, size_t cbData, uint32_t u32Sum, bool *pfOdd)
+{
+ if (*pfOdd)
+ {
+#ifdef RT_BIG_ENDIAN
+ /* there was an odd byte in the previous chunk, add the lower byte. */
+ u32Sum += *(uint8_t *)pvData;
+#else
+ /* there was an odd byte in the previous chunk, add the upper byte. */
+ u32Sum += (uint32_t)*(uint8_t *)pvData << 8;
+#endif
+ /* skip the byte. */
+ cbData--;
+ if (!cbData)
+ return u32Sum;
+ pvData = (uint8_t const *)pvData + 1;
+ }
+
+ /* iterate the data. */
+ uint16_t const *pw = (uint16_t const *)pvData;
+ while (cbData > 1)
+ {
+ u32Sum += *pw;
+ pw++;
+ cbData -= 2;
+ }
+
+ /* handle odd byte. */
+ if (cbData)
+ {
+#ifdef RT_BIG_ENDIAN
+ u32Sum += (uint32_t)*(uint8_t *)pw << 8;
+#else
+ u32Sum += *(uint8_t *)pw;
+#endif
+ *pfOdd = true;
+ }
+ else
+ *pfOdd = false;
+ return u32Sum;
+}
+
+/**
+ * Adds the checksum of the specified data segment to the intermediate checksum value.
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pvData The data bits to checksum.
+ * @param cbData The number of bytes to checksum.
+ * @param u32Sum The 32-bit intermediate checksum value.
+ * @param pfOdd This is used to keep track of odd bits, initialize to false
+ * when starting to checksum the data (aka text) after a TCP
+ * or UDP header (data never start at an odd offset).
+ */
+RTDECL(uint32_t) RTNetIPv4AddDataChecksum(void const *pvData, size_t cbData, uint32_t u32Sum, bool *pfOdd)
+{
+ return rtNetIPv4AddDataChecksum(pvData, cbData, u32Sum, pfOdd);
+}
+RT_EXPORT_SYMBOL(RTNetIPv4AddDataChecksum);
+
+
+/**
+ * Finalizes a IPv4 checksum [inlined].
+ *
+ * @returns The checksum (network endian).
+ * @param u32Sum The 32-bit intermediate checksum value.
+ */
+DECLINLINE(uint16_t) rtNetIPv4FinalizeChecksum(uint32_t u32Sum)
+{
+ /* 16-bit one complement fun */
+ u32Sum = (u32Sum >> 16) + (u32Sum & 0xffff); /* hi + low words */
+ u32Sum += u32Sum >> 16; /* carry */
+ return (uint16_t)~u32Sum;
+}
+
+
+/**
+ * Finalizes a IPv4 checksum.
+ *
+ * @returns The checksum (network endian).
+ * @param u32Sum The 32-bit intermediate checksum value.
+ */
+RTDECL(uint16_t) RTNetIPv4FinalizeChecksum(uint32_t u32Sum)
+{
+ return rtNetIPv4FinalizeChecksum(u32Sum);
+}
+RT_EXPORT_SYMBOL(RTNetIPv4FinalizeChecksum);
+
+
+/**
+ * Calculates the checksum for the UDP header given the UDP header w/ payload
+ * and the checksum of the pseudo header.
+ *
+ * @returns The checksum (network endian).
+ * @param u32Sum The checksum of the pseudo header. See
+ * RTNetIPv4PseudoChecksum and RTNetIPv6PseudoChecksum.
+ * @param pUdpHdr Pointer to the UDP header and the payload, in
+ * network endian (big). We use the uh_ulen field to
+ * figure out how much to checksum.
+ */
+RTDECL(uint16_t) RTNetUDPChecksum(uint32_t u32Sum, PCRTNETUDP pUdpHdr)
+{
+ bool fOdd;
+ u32Sum = rtNetIPv4AddUDPChecksum(pUdpHdr, u32Sum);
+ fOdd = false;
+ u32Sum = rtNetIPv4AddDataChecksum(pUdpHdr + 1, RT_BE2H_U16(pUdpHdr->uh_ulen) - sizeof(*pUdpHdr), u32Sum, &fOdd);
+ return rtNetIPv4FinalizeChecksum(u32Sum);
+}
+RT_EXPORT_SYMBOL(RTNetUDPChecksum);
+
+
+/**
+ * Calculates the checksum for the UDP header given the IP header,
+ * UDP header and payload.
+ *
+ * @returns The checksum (network endian).
+ * @param pIpHdr Pointer to the IPv4 header, in network endian (big).
+ * @param pUdpHdr Pointer to the UDP header, in network endian (big).
+ * @param pvData Pointer to the UDP payload. The size is taken from the
+ * UDP header and the caller is supposed to have validated
+ * this before calling.
+ */
+RTDECL(uint16_t) RTNetIPv4UDPChecksum(PCRTNETIPV4 pIpHdr, PCRTNETUDP pUdpHdr, void const *pvData)
+{
+ bool fOdd;
+ uint32_t u32Sum = rtNetIPv4PseudoChecksum(pIpHdr);
+ u32Sum = rtNetIPv4AddUDPChecksum(pUdpHdr, u32Sum);
+ fOdd = false;
+ u32Sum = rtNetIPv4AddDataChecksum(pvData, RT_BE2H_U16(pUdpHdr->uh_ulen) - sizeof(*pUdpHdr), u32Sum, &fOdd);
+ return rtNetIPv4FinalizeChecksum(u32Sum);
+}
+RT_EXPORT_SYMBOL(RTNetIPv4UDPChecksum);
+
+
+/**
+ * Simple verification of an UDP packet size.
+ *
+ * @returns true if valid, false if invalid.
+ * @param pIpHdr Pointer to the IPv4 header, in network endian (big).
+ * This is assumed to be valid and the minimum size being mapped.
+ * @param pUdpHdr Pointer to the UDP header, in network endian (big).
+ * @param cbPktMax The max UDP packet size, UDP header and payload (data).
+ */
+DECLINLINE(bool) rtNetIPv4IsUDPSizeValid(PCRTNETIPV4 pIpHdr, PCRTNETUDP pUdpHdr, size_t cbPktMax)
+{
+ /*
+ * Size validation.
+ */
+ size_t cb;
+ if (RT_UNLIKELY(cbPktMax < RTNETUDP_MIN_LEN))
+ return false;
+ cb = RT_BE2H_U16(pUdpHdr->uh_ulen);
+ if (RT_UNLIKELY(cb > cbPktMax))
+ return false;
+ if (RT_UNLIKELY(cb > (size_t)(RT_BE2H_U16(pIpHdr->ip_len) - pIpHdr->ip_hl * 4)))
+ return false;
+ return true;
+}
+
+
+/**
+ * Simple verification of an UDP packet size.
+ *
+ * @returns true if valid, false if invalid.
+ * @param pIpHdr Pointer to the IPv4 header, in network endian (big).
+ * This is assumed to be valid and the minimum size being mapped.
+ * @param pUdpHdr Pointer to the UDP header, in network endian (big).
+ * @param cbPktMax The max UDP packet size, UDP header and payload (data).
+ */
+RTDECL(bool) RTNetIPv4IsUDPSizeValid(PCRTNETIPV4 pIpHdr, PCRTNETUDP pUdpHdr, size_t cbPktMax)
+{
+ return rtNetIPv4IsUDPSizeValid(pIpHdr, pUdpHdr, cbPktMax);
+}
+RT_EXPORT_SYMBOL(RTNetIPv4IsUDPSizeValid);
+
+
+/**
+ * Simple verification of an UDP packet (size + checksum).
+ *
+ * @returns true if valid, false if invalid.
+ * @param pIpHdr Pointer to the IPv4 header, in network endian (big).
+ * This is assumed to be valid and the minimum size being mapped.
+ * @param pUdpHdr Pointer to the UDP header, in network endian (big).
+ * @param pvData Pointer to the data, assuming it's one single segment
+ * and that cbPktMax - sizeof(RTNETUDP) is mapped here.
+ * @param cbPktMax The max UDP packet size, UDP header and payload (data).
+ * @param fChecksum Whether to validate the checksum (GSO).
+ */
+RTDECL(bool) RTNetIPv4IsUDPValid(PCRTNETIPV4 pIpHdr, PCRTNETUDP pUdpHdr, void const *pvData, size_t cbPktMax, bool fChecksum)
+{
+ if (RT_UNLIKELY(!rtNetIPv4IsUDPSizeValid(pIpHdr, pUdpHdr, cbPktMax)))
+ return false;
+ if (fChecksum && pUdpHdr->uh_sum)
+ {
+ uint16_t u16Sum = RTNetIPv4UDPChecksum(pIpHdr, pUdpHdr, pvData);
+ if (RT_UNLIKELY(pUdpHdr->uh_sum != u16Sum))
+ return false;
+ }
+ return true;
+}
+RT_EXPORT_SYMBOL(RTNetIPv4IsUDPValid);
+
+
+/**
+ * Calculates the checksum for the TCP header given the IP header,
+ * TCP header and payload.
+ *
+ * @returns The checksum (network endian).
+ * @param pIpHdr Pointer to the IPv4 header, in network endian (big).
+ * @param pTcpHdr Pointer to the TCP header, in network endian (big).
+ * @param pvData Pointer to the TCP payload. The size is derived from
+ * the two headers and the caller is supposed to have
+ * validated this before calling. If NULL, we assume
+ * the data follows immediately after the TCP header.
+ */
+RTDECL(uint16_t) RTNetIPv4TCPChecksum(PCRTNETIPV4 pIpHdr, PCRTNETTCP pTcpHdr, void const *pvData)
+{
+ bool fOdd;
+ size_t cbData;
+ uint32_t u32Sum = rtNetIPv4PseudoChecksum(pIpHdr);
+ u32Sum = rtNetIPv4AddTCPChecksum(pTcpHdr, u32Sum);
+ fOdd = false;
+ cbData = RT_BE2H_U16(pIpHdr->ip_len) - pIpHdr->ip_hl * 4 - pTcpHdr->th_off * 4;
+ u32Sum = rtNetIPv4AddDataChecksum(pvData ? pvData : (uint8_t const *)pTcpHdr + pTcpHdr->th_off * 4,
+ cbData, u32Sum, &fOdd);
+ return rtNetIPv4FinalizeChecksum(u32Sum);
+}
+RT_EXPORT_SYMBOL(RTNetIPv4TCPChecksum);
+
+
+/**
+ * Calculates the checksum for the TCP header given the TCP header, payload and
+ * the checksum of the pseudo header.
+ *
+ * This is not specific to IPv4.
+ *
+ * @returns The checksum (network endian).
+ * @param u32Sum The checksum of the pseudo header. See
+ * RTNetIPv4PseudoChecksum and RTNetIPv6PseudoChecksum.
+ * @param pTcpHdr Pointer to the TCP header, in network endian (big).
+ * @param pvData Pointer to the TCP payload.
+ * @param cbData The size of the TCP payload.
+ */
+RTDECL(uint16_t) RTNetTCPChecksum(uint32_t u32Sum, PCRTNETTCP pTcpHdr, void const *pvData, size_t cbData)
+{
+ bool fOdd;
+ u32Sum = rtNetIPv4AddTCPChecksum(pTcpHdr, u32Sum);
+ fOdd = false;
+ u32Sum = rtNetIPv4AddDataChecksum(pvData, cbData, u32Sum, &fOdd);
+ return rtNetIPv4FinalizeChecksum(u32Sum);
+}
+RT_EXPORT_SYMBOL(RTNetTCPChecksum);
+
+
+/**
+ * Verification of a TCP header.
+ *
+ * @returns true if valid, false if invalid.
+ * @param pIpHdr Pointer to the IPv4 header, in network endian (big).
+ * This is assumed to be valid and the minimum size being mapped.
+ * @param pTcpHdr Pointer to the TCP header, in network endian (big).
+ * @param cbHdrMax The max TCP header size (what pTcpHdr points to).
+ * @param cbPktMax The max TCP packet size, TCP header and payload (data).
+ */
+DECLINLINE(bool) rtNetIPv4IsTCPSizeValid(PCRTNETIPV4 pIpHdr, PCRTNETTCP pTcpHdr, size_t cbHdrMax, size_t cbPktMax)
+{
+ size_t cbTcpHdr;
+ size_t cbTcp;
+
+ Assert(cbPktMax >= cbHdrMax);
+
+ /*
+ * Size validations.
+ */
+ if (RT_UNLIKELY(cbPktMax < RTNETTCP_MIN_LEN))
+ return false;
+ cbTcpHdr = pTcpHdr->th_off * 4;
+ if (RT_UNLIKELY(cbTcpHdr > cbHdrMax))
+ return false;
+ cbTcp = RT_BE2H_U16(pIpHdr->ip_len) - pIpHdr->ip_hl * 4;
+ if (RT_UNLIKELY(cbTcp > cbPktMax))
+ return false;
+ return true;
+}
+
+
+/**
+ * Simple verification of an TCP packet size.
+ *
+ * @returns true if valid, false if invalid.
+ * @param pIpHdr Pointer to the IPv4 header, in network endian (big).
+ * This is assumed to be valid and the minimum size being mapped.
+ * @param pTcpHdr Pointer to the TCP header, in network endian (big).
+ * @param cbHdrMax The max TCP header size (what pTcpHdr points to).
+ * @param cbPktMax The max TCP packet size, TCP header and payload (data).
+ */
+RTDECL(bool) RTNetIPv4IsTCPSizeValid(PCRTNETIPV4 pIpHdr, PCRTNETTCP pTcpHdr, size_t cbHdrMax, size_t cbPktMax)
+{
+ return rtNetIPv4IsTCPSizeValid(pIpHdr, pTcpHdr, cbHdrMax, cbPktMax);
+}
+RT_EXPORT_SYMBOL(RTNetIPv4IsTCPSizeValid);
+
+
+/**
+ * Simple verification of an TCP packet (size + checksum).
+ *
+ * @returns true if valid, false if invalid.
+ * @param pIpHdr Pointer to the IPv4 header, in network endian (big).
+ * This is assumed to be valid and the minimum size being mapped.
+ * @param pTcpHdr Pointer to the TCP header, in network endian (big).
+ * @param cbHdrMax The max TCP header size (what pTcpHdr points to).
+ * @param pvData Pointer to the data, assuming it's one single segment
+ * and that cbPktMax - sizeof(RTNETTCP) is mapped here.
+ * If NULL then we assume the data follows immediately after
+ * the TCP header.
+ * @param cbPktMax The max TCP packet size, TCP header and payload (data).
+ * @param fChecksum Whether to validate the checksum (GSO).
+ */
+RTDECL(bool) RTNetIPv4IsTCPValid(PCRTNETIPV4 pIpHdr, PCRTNETTCP pTcpHdr, size_t cbHdrMax, void const *pvData, size_t cbPktMax,
+ bool fChecksum)
+{
+ if (RT_UNLIKELY(!rtNetIPv4IsTCPSizeValid(pIpHdr, pTcpHdr, cbHdrMax, cbPktMax)))
+ return false;
+ if (fChecksum)
+ {
+ uint16_t u16Sum = RTNetIPv4TCPChecksum(pIpHdr, pTcpHdr, pvData);
+ if (RT_UNLIKELY(pTcpHdr->th_sum != u16Sum))
+ return false;
+ }
+ return true;
+}
+RT_EXPORT_SYMBOL(RTNetIPv4IsTCPValid);
+
+
+/**
+ * Minimal validation of a DHCP packet.
+ *
+ * This will fail on BOOTP packets (if sufficient data is supplied).
+ * It will not verify the source and destination ports, that's the
+ * caller's responsibility.
+ *
+ * This function will ASSUME that the hardware type is ethernet
+ * and use that for htype/hlen validation.
+ *
+ * @returns true if valid, false if invalid.
+ * @param pUdpHdr Pointer to the UDP header, in network endian (big).
+ * This is assumed to be valid and fully mapped.
+ * @param pDhcp Pointer to the DHCP packet.
+ * This might not be the entire thing, see cbDhcp.
+ * @param cbDhcp The number of valid bytes that pDhcp points to.
+ * @param pMsgType Where to store the message type (if found).
+ * This will be set to 0 if not found and on failure.
+ */
+RTDECL(bool) RTNetIPv4IsDHCPValid(PCRTNETUDP pUdpHdr, PCRTNETBOOTP pDhcp, size_t cbDhcp, uint8_t *pMsgType)
+{
+ ssize_t cbLeft;
+ uint8_t MsgType;
+ PCRTNETDHCPOPT pOpt;
+ NOREF(pUdpHdr); /** @todo rainy-day: Why isn't the UDP header used? */
+
+ AssertPtrNull(pMsgType);
+ if (pMsgType)
+ *pMsgType = 0;
+
+ /*
+ * Validate all the header fields we're able to...
+ */
+ if (cbDhcp < RT_UOFFSETOF(RTNETBOOTP, bp_op) + sizeof(pDhcp->bp_op))
+ return true;
+ if (RT_UNLIKELY( pDhcp->bp_op != RTNETBOOTP_OP_REQUEST
+ && pDhcp->bp_op != RTNETBOOTP_OP_REPLY))
+ return false;
+
+ if (cbDhcp < RT_UOFFSETOF(RTNETBOOTP, bp_htype) + sizeof(pDhcp->bp_htype))
+ return true;
+ if (RT_UNLIKELY(pDhcp->bp_htype != RTNET_ARP_ETHER))
+ return false;
+
+ if (cbDhcp < RT_UOFFSETOF(RTNETBOOTP, bp_hlen) + sizeof(pDhcp->bp_hlen))
+ return true;
+ if (RT_UNLIKELY(pDhcp->bp_hlen != sizeof(RTMAC)))
+ return false;
+
+ if (cbDhcp < RT_UOFFSETOF(RTNETBOOTP, bp_flags) + sizeof(pDhcp->bp_flags))
+ return true;
+ if (RT_UNLIKELY(RT_BE2H_U16(pDhcp->bp_flags) & ~(RTNET_DHCP_FLAGS_NO_BROADCAST)))
+ return false;
+
+ /*
+ * Check the DHCP cookie and make sure it isn't followed by an END option
+ * (because that seems to be indicating that it's BOOTP and not DHCP).
+ */
+ cbLeft = (ssize_t)cbDhcp - RT_UOFFSETOF(RTNETBOOTP, bp_vend.Dhcp.dhcp_cookie) + sizeof(pDhcp->bp_vend.Dhcp.dhcp_cookie);
+ if (cbLeft < 0)
+ return true;
+ if (RT_UNLIKELY(RT_BE2H_U32(pDhcp->bp_vend.Dhcp.dhcp_cookie) != RTNET_DHCP_COOKIE))
+ return false;
+ if (cbLeft < 1)
+ return true;
+ pOpt = (PCRTNETDHCPOPT)&pDhcp->bp_vend.Dhcp.dhcp_opts[0];
+ if (pOpt->dhcp_opt == RTNET_DHCP_OPT_END)
+ return false;
+
+ /*
+ * Scan the options until we find the message type or run out of message.
+ *
+ * We're not strict about termination (END) for many reasons, however,
+ * we don't accept END without MSG_TYPE.
+ */
+ MsgType = 0;
+ while (cbLeft > 0)
+ {
+ if (pOpt->dhcp_opt == RTNET_DHCP_OPT_END)
+ {
+ /* Fail if no MSG_TYPE. */
+ if (!MsgType)
+ return false;
+ break;
+ }
+ if (pOpt->dhcp_opt == RTNET_DHCP_OPT_PAD)
+ {
+ pOpt = (PCRTNETDHCPOPT)((uint8_t const *)pOpt + 1);
+ cbLeft--;
+ }
+ else
+ {
+ switch (pOpt->dhcp_opt)
+ {
+ case RTNET_DHCP_OPT_MSG_TYPE:
+ {
+ if (cbLeft < 3)
+ return true;
+ MsgType = *(const uint8_t *)(pOpt + 1);
+ switch (MsgType)
+ {
+ case RTNET_DHCP_MT_DISCOVER:
+ case RTNET_DHCP_MT_OFFER:
+ case RTNET_DHCP_MT_REQUEST:
+ case RTNET_DHCP_MT_DECLINE:
+ case RTNET_DHCP_MT_ACK:
+ case RTNET_DHCP_MT_NAC:
+ case RTNET_DHCP_MT_RELEASE:
+ case RTNET_DHCP_MT_INFORM:
+ break;
+
+ default:
+ /* we don't know this message type, fail. */
+ return false;
+ }
+
+ /* Found a known message type, consider the job done. */
+ if (pMsgType)
+ *pMsgType = MsgType;
+ return true;
+ }
+ }
+
+ /* Skip the option. */
+ cbLeft -= pOpt->dhcp_len + sizeof(*pOpt);
+ pOpt = (PCRTNETDHCPOPT)((uint8_t const *)pOpt + pOpt->dhcp_len + sizeof(*pOpt));
+ }
+ }
+
+ return true;
+}
+RT_EXPORT_SYMBOL(RTNetIPv4IsDHCPValid);
+
diff --git a/src/VBox/Runtime/common/checksum/ipv6.cpp b/src/VBox/Runtime/common/checksum/ipv6.cpp
new file mode 100644
index 00000000..6c248b07
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/ipv6.cpp
@@ -0,0 +1,136 @@
+/* $Id: ipv6.cpp $ */
+/** @file
+ * IPRT - IPv6 Checksum calculation and validation.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/net.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+
+
+/**
+ * @copydoc RTNetIPv6PseudoChecksumBits
+ */
+DECLINLINE(uint32_t) rtNetIPv6PseudoChecksumBits(PCRTNETADDRIPV6 pSrcAddr, PCRTNETADDRIPV6 pDstAddr,
+ uint8_t bProtocol, uint32_t cbPkt)
+{
+ uint32_t u32Sum = pSrcAddr->au16[0]
+ + pSrcAddr->au16[1]
+ + pSrcAddr->au16[2]
+ + pSrcAddr->au16[3]
+ + pSrcAddr->au16[4]
+ + pSrcAddr->au16[5]
+ + pSrcAddr->au16[6]
+ + pSrcAddr->au16[7]
+ + pDstAddr->au16[0]
+ + pDstAddr->au16[1]
+ + pDstAddr->au16[2]
+ + pDstAddr->au16[3]
+ + pDstAddr->au16[4]
+ + pDstAddr->au16[5]
+ + pDstAddr->au16[6]
+ + pDstAddr->au16[7]
+ + RT_H2BE_U16(RT_HIWORD(cbPkt))
+ + RT_H2BE_U16(RT_LOWORD(cbPkt))
+ + 0
+ + RT_H2BE_U16(RT_MAKE_U16(bProtocol, 0));
+ return u32Sum;
+}
+
+
+/**
+ * Calculates the checksum of a pseudo header given an IPv6 header, ASSUMING
+ * that there are no headers between the IPv6 header and the upper layer header.
+ *
+ * Use this method with create care! In most cases you should be using
+ * RTNetIPv6PseudoChecksumEx.
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pIpHdr The IPv6 header (network endian (big)).
+ */
+RTDECL(uint32_t) RTNetIPv6PseudoChecksum(PCRTNETIPV6 pIpHdr)
+{
+ return rtNetIPv6PseudoChecksumBits(&pIpHdr->ip6_src, &pIpHdr->ip6_dst,
+ pIpHdr->ip6_nxt, RT_N2H_U16(pIpHdr->ip6_plen));
+}
+RT_EXPORT_SYMBOL(RTNetIPv6PseudoChecksum);
+
+
+/**
+ * Calculates the checksum of a pseudo header given an IPv6 header.
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pIpHdr The IPv6 header (network endian (big)).
+ * @param bProtocol The protocol number. This can be the same as the
+ * ip6_nxt field, but doesn't need to be.
+ * @param cbPkt The packet size (host endian of course). This can
+ * be the same as the ip6_plen field, but as with @a
+ * bProtocol it won't be when extension headers are
+ * present. For UDP this will be uh_ulen converted to
+ * host endian.
+ */
+RTDECL(uint32_t) RTNetIPv6PseudoChecksumEx(PCRTNETIPV6 pIpHdr, uint8_t bProtocol, uint16_t cbPkt)
+{
+ return rtNetIPv6PseudoChecksumBits(&pIpHdr->ip6_src, &pIpHdr->ip6_dst, bProtocol, cbPkt);
+}
+RT_EXPORT_SYMBOL(RTNetIPv6PseudoChecksumEx);
+
+
+/**
+ * Calculates the checksum of a pseudo header given the individual components.
+ *
+ * @returns 32-bit intermediary checksum value.
+ * @param pSrcAddr Pointer to the source address in network endian.
+ * @param pDstAddr Pointer to the destination address in network endian.
+ * @param bProtocol The protocol number. This can be the same as the
+ * ip6_nxt field, but doesn't need to be.
+ * @param cbPkt The packet size (host endian of course). This can
+ * be the same as the ip6_plen field, but as with @a
+ * bProtocol it won't be when extension headers are
+ * present. For UDP this will be uh_ulen converted to
+ * host endian.
+ */
+RTDECL(uint32_t) RTNetIPv6PseudoChecksumBits(PCRTNETADDRIPV6 pSrcAddr, PCRTNETADDRIPV6 pDstAddr,
+ uint8_t bProtocol, uint16_t cbPkt)
+{
+ return rtNetIPv6PseudoChecksumBits(pSrcAddr, pDstAddr, bProtocol, cbPkt);
+}
+RT_EXPORT_SYMBOL(RTNetIPv6PseudoChecksumBits);
+
diff --git a/src/VBox/Runtime/common/checksum/manifest-file.cpp b/src/VBox/Runtime/common/checksum/manifest-file.cpp
new file mode 100644
index 00000000..2f6c269a
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/manifest-file.cpp
@@ -0,0 +1,94 @@
+/* $Id: manifest-file.cpp $ */
+/** @file
+ * IPRT - Manifest, the bits with file dependencies
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/manifest.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+#include "internal/magics.h"
+
+
+
+RTDECL(int) RTManifestReadStandardFromFile(RTMANIFEST hManifest, const char *pszFilename)
+{
+ RTFILE hFile;
+ uint32_t fFlags = RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN;
+ int rc = RTFileOpen(&hFile, pszFilename, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSIOSTREAM hVfsIos;
+ rc = RTVfsIoStrmFromRTFile(hFile, fFlags, true /*fLeaveOpen*/, &hVfsIos);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTManifestReadStandard(hManifest, hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ RTFileClose(hFile);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTManifestWriteStandardToFile(RTMANIFEST hManifest, const char *pszFilename)
+{
+ RTFILE hFile;
+ uint32_t fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE;
+ int rc = RTFileOpen(&hFile, pszFilename, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSIOSTREAM hVfsIos;
+ rc = RTVfsIoStrmFromRTFile(hFile, fFlags, true /*fLeaveOpen*/, &hVfsIos);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTManifestWriteStandard(hManifest, hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ RTFileClose(hFile);
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/checksum/manifest.cpp b/src/VBox/Runtime/common/checksum/manifest.cpp
new file mode 100644
index 00000000..74bcc05d
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/manifest.cpp
@@ -0,0 +1,593 @@
+/* $Id: manifest.cpp $ */
+/** @file
+ * IPRT - Manifest file handling, old style - deprecated.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/manifest.h>
+
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/sha.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Internal per file structure used by RTManifestVerify
+ */
+typedef struct RTMANIFESTFILEENTRY
+{
+ char *pszManifestFile;
+ char *pszManifestDigest;
+ PRTMANIFESTTEST pTestPattern;
+} RTMANIFESTFILEENTRY;
+typedef RTMANIFESTFILEENTRY* PRTMANIFESTFILEENTRY;
+
+/**
+ * Internal structure used for the progress callback
+ */
+typedef struct RTMANIFESTCALLBACKDATA
+{
+ PFNRTPROGRESS pfnProgressCallback;
+ void *pvUser;
+ size_t cMaxFiles;
+ size_t cCurrentFile;
+} RTMANIFESTCALLBACKDATA;
+typedef RTMANIFESTCALLBACKDATA* PRTMANIFESTCALLBACKDATA;
+
+
+/*******************************************************************************
+* Private functions
+*******************************************************************************/
+
+DECLINLINE(char *) rtManifestPosOfCharInBuf(char const *pv, size_t cb, char c)
+{
+ char *pb = (char *)pv;
+ for (; cb; --cb, ++pb)
+ if (RT_UNLIKELY(*pb == c))
+ return pb;
+ return NULL;
+}
+
+DECLINLINE(size_t) rtManifestIndexOfCharInBuf(char const *pv, size_t cb, char c)
+{
+ char const *pb = (char const *)pv;
+ for (size_t i=0; i < cb; ++i, ++pb)
+ if (RT_UNLIKELY(*pb == c))
+ return i;
+ return cb;
+}
+
+static DECLCALLBACK(int) rtSHAProgressCallback(unsigned uPercent, void *pvUser)
+{
+ PRTMANIFESTCALLBACKDATA pData = (PRTMANIFESTCALLBACKDATA)pvUser;
+ return pData->pfnProgressCallback((unsigned)( (uPercent + (float)pData->cCurrentFile * 100.0)
+ / (float)pData->cMaxFiles),
+ pData->pvUser);
+}
+
+
+/*******************************************************************************
+* Public functions
+*******************************************************************************/
+
+RTR3DECL(int) RTManifestVerify(const char *pszManifestFile, PRTMANIFESTTEST paTests, size_t cTests, size_t *piFailed)
+{
+ /* Validate input */
+ AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER);
+
+ /* Open the manifest file */
+ RTFILE file;
+ int rc = RTFileOpen(&file, pszManifestFile, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ void *pvBuf = 0;
+ do
+ {
+ uint64_t cbSize;
+ rc = RTFileQuerySize(file, &cbSize);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Cast down for the case size_t < uint64_t. This isn't really correct,
+ but we consider manifest files bigger than size_t as not supported
+ by now. */
+ size_t cbToRead = (size_t)cbSize;
+ pvBuf = RTMemAlloc(cbToRead);
+ if (!pvBuf)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ size_t cbRead = 0;
+ rc = RTFileRead(file, pvBuf, cbToRead, &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTManifestVerifyFilesBuf(pvBuf, cbRead, paTests, cTests, piFailed);
+ }while (0);
+
+ /* Cleanup */
+ if (pvBuf)
+ RTMemFree(pvBuf);
+
+ RTFileClose(file);
+
+ return rc;
+}
+
+RTR3DECL(int) RTManifestVerifyFiles(const char *pszManifestFile, const char * const *papszFiles, size_t cFiles, size_t *piFailed,
+ PFNRTPROGRESS pfnProgressCallback, void *pvUser)
+{
+ /* Validate input */
+ AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER);
+ AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ /* Create our compare list */
+ PRTMANIFESTTEST paFiles = (PRTMANIFESTTEST)RTMemTmpAllocZ(sizeof(RTMANIFESTTEST) * cFiles);
+ if (!paFiles)
+ return VERR_NO_MEMORY;
+
+ RTMANIFESTCALLBACKDATA callback = { pfnProgressCallback, pvUser, cFiles, 0 };
+ /* Fill our compare list */
+ for (size_t i = 0; i < cFiles; ++i)
+ {
+ char *pszDigest;
+ if (pfnProgressCallback)
+ {
+ callback.cCurrentFile = i;
+ rc = RTSha1DigestFromFile(papszFiles[i], &pszDigest, rtSHAProgressCallback, &callback);
+ }
+ else
+ rc = RTSha1DigestFromFile(papszFiles[i], &pszDigest, NULL, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ paFiles[i].pszTestFile = (char*)papszFiles[i];
+ paFiles[i].pszTestDigest = pszDigest;
+ }
+
+ /* Do the verification */
+ if (RT_SUCCESS(rc))
+ rc = RTManifestVerify(pszManifestFile, paFiles, cFiles, piFailed);
+
+ /* Cleanup */
+ for (size_t i = 0; i < cFiles; ++i)
+ {
+ if (paFiles[i].pszTestDigest)
+ RTStrFree((char*)paFiles[i].pszTestDigest);
+ }
+ RTMemTmpFree(paFiles);
+
+ return rc;
+}
+
+RTR3DECL(int) RTManifestWriteFiles(const char *pszManifestFile, RTDIGESTTYPE enmDigestType,
+ const char * const *papszFiles, size_t cFiles,
+ PFNRTPROGRESS pfnProgressCallback, void *pvUser)
+{
+ /* Validate input */
+ AssertPtrReturn(pszManifestFile, VERR_INVALID_POINTER);
+ AssertPtrReturn(papszFiles, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnProgressCallback, VERR_INVALID_POINTER);
+
+ RTFILE file;
+ int rc = RTFileOpen(&file, pszManifestFile, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_ALL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PRTMANIFESTTEST paFiles = 0;
+ void *pvBuf = 0;
+ do
+ {
+ paFiles = (PRTMANIFESTTEST)RTMemAllocZ(sizeof(RTMANIFESTTEST) * cFiles);
+ if (!paFiles)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ RTMANIFESTCALLBACKDATA callback = { pfnProgressCallback, pvUser, cFiles, 0 };
+ for (size_t i = 0; i < cFiles; ++i)
+ {
+ paFiles[i].pszTestFile = papszFiles[i];
+ /* Calculate the SHA1 digest of every file */
+ if (pfnProgressCallback)
+ {
+ callback.cCurrentFile = i;
+ rc = RTSha1DigestFromFile(paFiles[i].pszTestFile, (char**)&paFiles[i].pszTestDigest, rtSHAProgressCallback, &callback);
+ }
+ else
+ rc = RTSha1DigestFromFile(paFiles[i].pszTestFile, (char**)&paFiles[i].pszTestDigest, NULL, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbSize = 0;
+ rc = RTManifestWriteFilesBuf(&pvBuf, &cbSize, enmDigestType, paFiles, cFiles);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = RTFileWrite(file, pvBuf, cbSize, 0);
+ }
+ }while (0);
+
+ RTFileClose(file);
+
+ /* Cleanup */
+ if (pvBuf)
+ RTMemFree(pvBuf);
+ if (paFiles)
+ {
+ for (size_t i = 0; i < cFiles; ++i)
+ if (paFiles[i].pszTestDigest)
+ RTStrFree((char*)paFiles[i].pszTestDigest);
+ RTMemFree(paFiles);
+ }
+
+ /* Delete the manifest file on failure */
+ if (RT_FAILURE(rc))
+ RTFileDelete(pszManifestFile);
+
+ return rc;
+}
+
+
+RTR3DECL(int) RTManifestVerifyDigestType(void const *pvBuf, size_t cbSize, RTDIGESTTYPE *penmDigestType)
+{
+ /* Validate input */
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(penmDigestType, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ char const *pcBuf = (char *)pvBuf;
+ size_t cbRead = 0;
+ /* Parse the manifest file line by line */
+ for (;;)
+ {
+ if (cbRead >= cbSize)
+ return VERR_MANIFEST_UNSUPPORTED_DIGEST_TYPE;
+
+ size_t cch = rtManifestIndexOfCharInBuf(pcBuf, cbSize - cbRead, '\n') + 1;
+
+ /* Skip empty lines (UNIX/DOS format) */
+ if ( ( cch == 1
+ && pcBuf[0] == '\n')
+ || ( cch == 2
+ && pcBuf[0] == '\r'
+ && pcBuf[1] == '\n'))
+ {
+ pcBuf += cch;
+ cbRead += cch;
+ continue;
+ }
+
+/** @todo r=bird: Missing space check here. */
+ /* Check for the digest algorithm */
+ if ( pcBuf[0] == 'S'
+ && pcBuf[1] == 'H'
+ && pcBuf[2] == 'A'
+ && pcBuf[3] == '1')
+ {
+ *penmDigestType = RTDIGESTTYPE_SHA1;
+ break;
+ }
+ if ( pcBuf[0] == 'S'
+ && pcBuf[1] == 'H'
+ && pcBuf[2] == 'A'
+ && pcBuf[3] == '2'
+ && pcBuf[4] == '5'
+ && pcBuf[5] == '6')
+ {
+ *penmDigestType = RTDIGESTTYPE_SHA256;
+ break;
+ }
+
+ pcBuf += cch;
+ cbRead += cch;
+ }
+
+ return rc;
+}
+
+
+RTR3DECL(int) RTManifestVerifyFilesBuf(void *pvBuf, size_t cbSize, PRTMANIFESTTEST paTests, size_t cTests, size_t *piFailed)
+{
+ /* Validate input */
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(paTests, VERR_INVALID_POINTER);
+ AssertReturn(cTests > 0, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(piFailed, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+
+ PRTMANIFESTFILEENTRY paFiles = (PRTMANIFESTFILEENTRY)RTMemTmpAllocZ(sizeof(RTMANIFESTFILEENTRY) * cTests);
+ if (!paFiles)
+ return VERR_NO_MEMORY;
+
+ /* Fill our compare list */
+ for (size_t i = 0; i < cTests; ++i)
+ paFiles[i].pTestPattern = &paTests[i];
+
+ char *pcBuf = (char*)pvBuf;
+ size_t cbRead = 0;
+ /* Parse the manifest file line by line */
+ for (;;)
+ {
+ if (cbRead >= cbSize)
+ break;
+
+ size_t cch = rtManifestIndexOfCharInBuf(pcBuf, cbSize - cbRead, '\n') + 1;
+
+ /* Skip empty lines (UNIX/DOS format) */
+ if ( ( cch == 1
+ && pcBuf[0] == '\n')
+ || ( cch == 2
+ && pcBuf[0] == '\r'
+ && pcBuf[1] == '\n'))
+ {
+ pcBuf += cch;
+ cbRead += cch;
+ continue;
+ }
+
+ /** @todo r=bird:
+ * -# Better deal with this EOF line platform dependency
+ * -# The SHA1 and SHA256 tests should probably include a blank space check.
+ * -# If there is a specific order to the elements in the string, it would be
+ * good if the delimiter searching checked for it.
+ * -# Deal with filenames containing delimiter characters.
+ */
+
+ /* Check for the digest algorithm */
+ if ( cch < 4
+ || ( !( pcBuf[0] == 'S'
+ && pcBuf[1] == 'H'
+ && pcBuf[2] == 'A'
+ && pcBuf[3] == '1')
+ &&
+ !( pcBuf[0] == 'S'
+ && pcBuf[1] == 'H'
+ && pcBuf[2] == 'A'
+ && pcBuf[3] == '2'
+ && pcBuf[4] == '5'
+ && pcBuf[5] == '6')
+ )
+ )
+ {
+ /* Digest unsupported */
+ rc = VERR_MANIFEST_UNSUPPORTED_DIGEST_TYPE;
+ break;
+ }
+
+ /* Try to find the filename */
+ char *pszNameStart = rtManifestPosOfCharInBuf(pcBuf, cch, '(');
+ if (!pszNameStart)
+ {
+ rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
+ break;
+ }
+ char *pszNameEnd = rtManifestPosOfCharInBuf(pcBuf, cch, ')');
+ if (!pszNameEnd)
+ {
+ rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
+ break;
+ }
+
+ /* Copy the filename part */
+ size_t cchName = pszNameEnd - pszNameStart - 1;
+ char *pszName = (char *)RTMemTmpAlloc(cchName + 1);
+ if (!pszName)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ memcpy(pszName, pszNameStart + 1, cchName);
+ pszName[cchName] = '\0';
+
+ /* Try to find the digest sum */
+ char *pszDigestStart = rtManifestPosOfCharInBuf(pcBuf, cch, '=') + 1;
+ if (!pszDigestStart)
+ {
+ RTMemTmpFree(pszName);
+ rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
+ break;
+ }
+ char *pszDigestEnd = rtManifestPosOfCharInBuf(pcBuf, cch, '\r');
+ if (!pszDigestEnd)
+ pszDigestEnd = rtManifestPosOfCharInBuf(pcBuf, cch, '\n');
+ if (!pszDigestEnd)
+ {
+ RTMemTmpFree(pszName);
+ rc = VERR_MANIFEST_WRONG_FILE_FORMAT;
+ break;
+ }
+ /* Copy the digest part */
+ size_t cchDigest = pszDigestEnd - pszDigestStart - 1;
+ char *pszDigest = (char *)RTMemTmpAlloc(cchDigest + 1);
+ if (!pszDigest)
+ {
+ RTMemTmpFree(pszName);
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ memcpy(pszDigest, pszDigestStart + 1, cchDigest);
+ pszDigest[cchDigest] = '\0';
+
+ /* Check our file list against the extracted data */
+ bool fFound = false;
+ for (size_t i = 0; i < cTests; ++i)
+ {
+ /** @todo r=bird: Using RTStrStr here looks bogus. */
+ if (RTStrStr(paFiles[i].pTestPattern->pszTestFile, RTStrStrip(pszName)) != NULL)
+ {
+ /* Add the data of the manifest file to the file list */
+ paFiles[i].pszManifestFile = RTStrDup(RTStrStrip(pszName));
+ paFiles[i].pszManifestDigest = RTStrDup(RTStrStrip(pszDigest));
+ fFound = true;
+ break;
+ }
+ }
+ RTMemTmpFree(pszName);
+ RTMemTmpFree(pszDigest);
+ if (!fFound)
+ {
+ /* There have to be an entry in the file list */
+ rc = VERR_MANIFEST_FILE_MISMATCH;
+ break;
+ }
+
+ pcBuf += cch;
+ cbRead += cch;
+ }
+
+ if ( rc == VINF_SUCCESS
+ || rc == VERR_EOF)
+ {
+ rc = VINF_SUCCESS;
+ for (size_t i = 0; i < cTests; ++i)
+ {
+ /* If there is an entry in the file list, which hasn't an
+ * equivalent in the manifest file, its an error. */
+ if ( !paFiles[i].pszManifestFile
+ || !paFiles[i].pszManifestDigest)
+ {
+ rc = VERR_MANIFEST_FILE_MISMATCH;
+ break;
+ }
+
+ /* Do the manifest SHA digest match against the actual digest? */
+ if (RTStrICmp(paFiles[i].pszManifestDigest, paFiles[i].pTestPattern->pszTestDigest))
+ {
+ if (piFailed)
+ *piFailed = i;
+ rc = VERR_MANIFEST_DIGEST_MISMATCH;
+ break;
+ }
+ }
+ }
+
+ /* Cleanup */
+ for (size_t i = 0; i < cTests; ++i)
+ {
+ if (paFiles[i].pszManifestFile)
+ RTStrFree(paFiles[i].pszManifestFile);
+ if (paFiles[i].pszManifestDigest)
+ RTStrFree(paFiles[i].pszManifestDigest);
+ }
+ RTMemTmpFree(paFiles);
+
+ return rc;
+}
+
+RTR3DECL(int) RTManifestWriteFilesBuf(void **ppvBuf, size_t *pcbSize, RTDIGESTTYPE enmDigestType, PRTMANIFESTTEST paFiles, size_t cFiles)
+{
+ /* Validate input */
+ AssertPtrReturn(ppvBuf, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+ AssertPtrReturn(paFiles, VERR_INVALID_POINTER);
+ AssertReturn(cFiles > 0, VERR_INVALID_PARAMETER);
+
+ const char *pcszDigestType;
+ switch (enmDigestType)
+ {
+ case RTDIGESTTYPE_CRC32: pcszDigestType = "CRC32"; break;
+ case RTDIGESTTYPE_CRC64: pcszDigestType = "CRC64"; break;
+ case RTDIGESTTYPE_MD5: pcszDigestType = "MD5"; break;
+ case RTDIGESTTYPE_SHA1: pcszDigestType = "SHA1"; break;
+ case RTDIGESTTYPE_SHA256: pcszDigestType = "SHA256"; break;
+ default: return VERR_INVALID_PARAMETER;
+ }
+
+ /* Calculate the size necessary for the memory buffer. */
+ size_t cbSize = 0;
+ size_t cbMaxSize = 0;
+ for (size_t i = 0; i < cFiles; ++i)
+ {
+ size_t cbTmp = strlen(RTPathFilename(paFiles[i].pszTestFile))
+ + strlen(paFiles[i].pszTestDigest)
+ + strlen(pcszDigestType)
+ + 6;
+ cbMaxSize = RT_MAX(cbMaxSize, cbTmp);
+ cbSize += cbTmp;
+ }
+
+ /* Create the memory buffer */
+ void *pvBuf = RTMemAlloc(cbSize);
+ if (!pvBuf)
+ return VERR_NO_MEMORY;
+
+ /* Allocate a temporary string buffer. */
+ char *pszTmp = RTStrAlloc(cbMaxSize + 1);
+ if (!pszTmp)
+ {
+ RTMemFree(pvBuf);
+ return VERR_NO_MEMORY;
+ }
+ size_t cbPos = 0;
+
+ for (size_t i = 0; i < cFiles; ++i)
+ {
+ size_t cch = RTStrPrintf(pszTmp, cbMaxSize + 1, "%s (%s)= %s\n", pcszDigestType, RTPathFilename(paFiles[i].pszTestFile), paFiles[i].pszTestDigest);
+ memcpy(&((char*)pvBuf)[cbPos], pszTmp, cch);
+ cbPos += cch;
+ }
+ RTStrFree(pszTmp);
+
+ /* Results */
+ *ppvBuf = pvBuf;
+ *pcbSize = cbSize;
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/checksum/manifest2.cpp b/src/VBox/Runtime/common/checksum/manifest2.cpp
new file mode 100644
index 00000000..0e961977
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/manifest2.cpp
@@ -0,0 +1,1477 @@
+/* $Id: manifest2.cpp $ */
+/** @file
+ * IPRT - Manifest, the core.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/manifest.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/md5.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Manifest attribute.
+ *
+ * Used both for entries and manifest attributes.
+ */
+typedef struct RTMANIFESTATTR
+{
+ /** The string space core (szName). */
+ RTSTRSPACECORE StrCore;
+ /** The property value. */
+ char *pszValue;
+ /** The attribute type if applicable, RTMANIFEST_ATTR_UNKNOWN if not. */
+ uint32_t fType;
+ /** Whether it was visited by the equals operation or not. */
+ bool fVisited;
+ /** The normalized property name that StrCore::pszString points at. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} RTMANIFESTATTR;
+/** Pointer to a manifest attribute. */
+typedef RTMANIFESTATTR *PRTMANIFESTATTR;
+
+
+/**
+ * Manifest entry.
+ */
+typedef struct RTMANIFESTENTRY
+{
+ /** The string space core (szName). */
+ RTSTRSPACECORE StrCore;
+ /** The entry attributes (hashes, checksums, size, etc) -
+ * RTMANIFESTATTR. */
+ RTSTRSPACE Attributes;
+ /** The number of attributes. */
+ uint32_t cAttributes;
+ /** Whether it was visited by the equals operation or not. */
+ bool fVisited;
+ /** The normalized entry name that StrCore::pszString points at. */
+ char szName[RT_FLEXIBLE_ARRAY_NESTED];
+} RTMANIFESTENTRY;
+/** Pointer to a manifest entry. */
+typedef RTMANIFESTENTRY *PRTMANIFESTENTRY;
+
+
+/**
+ * Manifest handle data.
+ */
+typedef struct RTMANIFESTINT
+{
+ /** Magic value (RTMANIFEST_MAGIC). */
+ uint32_t u32Magic;
+ /** The number of references to this manifest. */
+ uint32_t volatile cRefs;
+ /** String space of the entries covered by this manifest -
+ * RTMANIFESTENTRY. */
+ RTSTRSPACE Entries;
+ /** The number of entries. */
+ uint32_t cEntries;
+ /** The entry for the manifest itself. */
+ RTMANIFESTENTRY SelfEntry;
+} RTMANIFESTINT;
+
+/** The value of RTMANIFESTINT::u32Magic. */
+#define RTMANIFEST_MAGIC UINT32_C(0x99998866)
+
+/**
+ * Argument package passed to rtManifestWriteStdAttr by rtManifestWriteStdEntry
+ * and RTManifestWriteStandard.
+ */
+typedef struct RTMANIFESTWRITESTDATTR
+{
+ /** The entry name. */
+ const char *pszEntry;
+ /** The output I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+} RTMANIFESTWRITESTDATTR;
+
+
+/**
+ * Argument package used by RTManifestEqualsEx to pass its arguments to the
+ * enumeration callback functions.
+ */
+typedef struct RTMANIFESTEQUALS
+{
+ /** Name of entries to ignore. */
+ const char * const *papszIgnoreEntries;
+ /** Name of attributes to ignore. */
+ const char * const *papszIgnoreAttrs;
+ /** Flags governing the comparision. */
+ uint32_t fFlags;
+ /** Where to return an error message (++) on failure. Can be NULL. */
+ char *pszError;
+ /** The size of the buffer pszError points to. Can be 0. */
+ size_t cbError;
+
+ /** Pointer to the 2nd manifest. */
+ RTMANIFESTINT *pThis2;
+
+ /** The number of ignored entries from the 1st manifest. */
+ uint32_t cIgnoredEntries2;
+ /** The number of entries processed from the 2nd manifest. */
+ uint32_t cEntries2;
+
+ /** The number of ignored attributes from the 1st manifest. */
+ uint32_t cIgnoredAttributes1;
+ /** The number of ignored attributes from the 1st manifest. */
+ uint32_t cIgnoredAttributes2;
+ /** The number of attributes processed from the 2nd manifest. */
+ uint32_t cAttributes2;
+ /** Pointer to the string space to get matching attributes from. */
+ PRTSTRSPACE pAttributes2;
+ /** The name of the current entry.
+ * Points to an empty string it's the manifest attributes. */
+ const char *pszCurEntry;
+} RTMANIFESTEQUALS;
+/** Pointer to an RTManifestEqualEx argument packet. */
+typedef RTMANIFESTEQUALS *PRTMANIFESTEQUALS;
+
+/**
+ * Argument package used by rtManifestQueryAttrWorker to pass its search
+ * criteria to rtManifestQueryAttrEnumCallback and get a result back.
+ */
+typedef struct RTMANIFESTQUERYATTRARGS
+{
+ /** The attribute types we're hunting for. */
+ uint32_t fType;
+ /** What we've found. */
+ PRTMANIFESTATTR pAttr;
+} RTMANIFESTQUERYATTRARGS;
+/** Pointer to a rtManifestQueryAttrEnumCallback argument packet. */
+typedef RTMANIFESTQUERYATTRARGS *PRTMANIFESTQUERYATTRARGS;
+
+
+RTDECL(int) RTManifestCreate(uint32_t fFlags, PRTMANIFEST phManifest)
+{
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+ AssertPtr(phManifest);
+
+ RTMANIFESTINT *pThis = (RTMANIFESTINT *)RTMemAlloc(RT_UOFFSETOF(RTMANIFESTINT, SelfEntry.szName[1]));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ pThis->u32Magic = RTMANIFEST_MAGIC;
+ pThis->cRefs = 1;
+ pThis->Entries = NULL;
+ pThis->cEntries = 0;
+ pThis->SelfEntry.StrCore.pszString = "main";
+ pThis->SelfEntry.StrCore.cchString = 4;
+ pThis->SelfEntry.Attributes = NULL;
+ pThis->SelfEntry.cAttributes = 0;
+ pThis->SelfEntry.fVisited = false;
+ pThis->SelfEntry.szName[0] = '\0';
+
+ *phManifest = pThis;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(uint32_t) RTManifestRetain(RTMANIFEST hManifest)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1 && cRefs < _1M);
+
+ return cRefs;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Destroys RTMANIFESTATTR.}
+ */
+static DECLCALLBACK(int) rtManifestDestroyAttribute(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTATTR pAttr = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);
+ RTStrFree(pAttr->pszValue);
+ pAttr->pszValue = NULL;
+ RTMemFree(pAttr);
+ NOREF(pvUser);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Destroys RTMANIFESTENTRY.}
+ */
+static DECLCALLBACK(int) rtManifestDestroyEntry(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTENTRY pEntry = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);
+ RTStrSpaceDestroy(&pEntry->Attributes, rtManifestDestroyAttribute, pvUser);
+ RTMemFree(pEntry);
+ return 0;
+}
+
+
+RTDECL(uint32_t) RTManifestRelease(RTMANIFEST hManifest)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ if (pThis == NIL_RTMANIFEST)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < _1M);
+ if (!cRefs)
+ {
+ ASMAtomicWriteU32(&pThis->u32Magic, ~RTMANIFEST_MAGIC);
+ RTStrSpaceDestroy(&pThis->Entries, rtManifestDestroyEntry,pThis);
+ RTStrSpaceDestroy(&pThis->SelfEntry.Attributes, rtManifestDestroyAttribute, pThis);
+ RTMemFree(pThis);
+ }
+
+ return cRefs;
+}
+
+
+RTDECL(int) RTManifestDup(RTMANIFEST hManifestSrc, PRTMANIFEST phManifestDst)
+{
+ RTMANIFESTINT *pThis = hManifestSrc;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(phManifestDst);
+
+ RT_NOREF_PV(phManifestDst); /** @todo implement cloning. */
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Prepare equals operation}
+ */
+static DECLCALLBACK(int) rtManifestAttributeClearVisited(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTATTR pAttr = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);
+ pAttr->fVisited = false;
+ NOREF(pvUser);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Prepare equals operation}
+ */
+static DECLCALLBACK(int) rtManifestEntryClearVisited(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTENTRY pEntry = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);
+ RTStrSpaceEnumerate(&pEntry->Attributes, rtManifestAttributeClearVisited, NULL);
+ pEntry->fVisited = false;
+ NOREF(pvUser);
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Finds the first missing}
+ */
+static DECLCALLBACK(int) rtManifestAttributeFindMissing2(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTEQUALS pEquals = (PRTMANIFESTEQUALS)pvUser;
+ PRTMANIFESTATTR pAttr = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);
+
+ /*
+ * Already visited?
+ */
+ if (pAttr->fVisited)
+ return 0;
+
+ /*
+ * Ignore this entry?
+ */
+ char const * const *ppsz = pEquals->papszIgnoreAttrs;
+ if (ppsz)
+ {
+ while (*ppsz)
+ {
+ if (!strcmp(*ppsz, pAttr->szName))
+ return 0;
+ ppsz++;
+ }
+ }
+
+ /*
+ * Gotcha!
+ */
+ if (*pEquals->pszCurEntry)
+ RTStrPrintf(pEquals->pszError, pEquals->cbError,
+ "Attribute '%s' on '%s' was not found in the 1st manifest",
+ pAttr->szName, pEquals->pszCurEntry);
+ else
+ RTStrPrintf(pEquals->pszError, pEquals->cbError, "Attribute '%s' was not found in the 1st manifest", pAttr->szName);
+ return VERR_NOT_EQUAL;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Finds the first missing}
+ */
+static DECLCALLBACK(int) rtManifestEntryFindMissing2(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTEQUALS pEquals = (PRTMANIFESTEQUALS)pvUser;
+ PRTMANIFESTENTRY pEntry = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);
+
+ /*
+ * Already visited?
+ */
+ if (pEntry->fVisited)
+ return 0;
+
+ /*
+ * Ignore this entry?
+ */
+ char const * const *ppsz = pEquals->papszIgnoreEntries;
+ if (ppsz)
+ {
+ while (*ppsz)
+ {
+ if (!strcmp(*ppsz, pEntry->StrCore.pszString))
+ return 0;
+ ppsz++;
+ }
+ }
+
+ /*
+ * Gotcha!
+ */
+ RTStrPrintf(pEquals->pszError, pEquals->cbError, "'%s' was not found in the 1st manifest", pEntry->StrCore.pszString);
+ return VERR_NOT_EQUAL;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Compares attributes}
+ */
+static DECLCALLBACK(int) rtManifestAttributeCompare(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTEQUALS pEquals = (PRTMANIFESTEQUALS)pvUser;
+ PRTMANIFESTATTR pAttr1 = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);
+ PRTMANIFESTATTR pAttr2;
+
+ Assert(!pAttr1->fVisited);
+ pAttr1->fVisited = true;
+
+ /*
+ * Ignore this entry?
+ */
+ char const * const *ppsz = pEquals->papszIgnoreAttrs;
+ if (ppsz)
+ {
+ while (*ppsz)
+ {
+ if (!strcmp(*ppsz, pAttr1->szName))
+ {
+ pAttr2 = (PRTMANIFESTATTR)RTStrSpaceGet(pEquals->pAttributes2, pAttr1->szName);
+ if (pAttr2)
+ {
+ Assert(!pAttr2->fVisited);
+ pAttr2->fVisited = true;
+ pEquals->cIgnoredAttributes2++;
+ }
+ pEquals->cIgnoredAttributes1++;
+ return 0;
+ }
+ ppsz++;
+ }
+ }
+
+ /*
+ * Find the matching attribute.
+ */
+ pAttr2 = (PRTMANIFESTATTR)RTStrSpaceGet(pEquals->pAttributes2, pAttr1->szName);
+ if (!pAttr2)
+ {
+ if (pEquals->fFlags & RTMANIFEST_EQUALS_IGN_MISSING_ATTRS)
+ return 0;
+
+ if (*pEquals->pszCurEntry)
+ RTStrPrintf(pEquals->pszError, pEquals->cbError,
+ "Attribute '%s' on '%s' was not found in the 2nd manifest",
+ pAttr1->szName, pEquals->pszCurEntry);
+ else
+ RTStrPrintf(pEquals->pszError, pEquals->cbError, "Attribute '%s' was not found in the 2nd manifest", pAttr1->szName);
+ return VERR_NOT_EQUAL;
+ }
+
+ Assert(!pAttr2->fVisited);
+ pAttr2->fVisited = true;
+ pEquals->cAttributes2++;
+
+ /*
+ * Compare them.
+ */
+ if (RTStrICmp(pAttr1->pszValue, pAttr2->pszValue))
+ {
+ if (*pEquals->pszCurEntry)
+ RTStrPrintf(pEquals->pszError, pEquals->cbError,
+ "Attribute '%s' on '%s' does not match ('%s' vs. '%s')",
+ pAttr1->szName, pEquals->pszCurEntry, pAttr1->pszValue, pAttr2->pszValue);
+ else
+ RTStrPrintf(pEquals->pszError, pEquals->cbError,
+ "Attribute '%s' does not match ('%s' vs. '%s')",
+ pAttr1->szName, pAttr1->pszValue, pAttr2->pszValue);
+ return VERR_NOT_EQUAL;
+ }
+
+ return 0;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Prepare equals operation}
+ */
+DECLINLINE (int) rtManifestEntryCompare2(PRTMANIFESTEQUALS pEquals, PRTMANIFESTENTRY pEntry1, PRTMANIFESTENTRY pEntry2)
+{
+ /*
+ * Compare the attributes. It's a bit ugly with all this counting, but
+ * how else to efficiently implement RTMANIFEST_EQUALS_IGN_MISSING_ATTRS?
+ */
+ pEquals->cIgnoredAttributes1 = 0;
+ pEquals->cIgnoredAttributes2 = 0;
+ pEquals->cAttributes2 = 0;
+ pEquals->pszCurEntry = &pEntry2->szName[0];
+ pEquals->pAttributes2 = &pEntry2->Attributes;
+ int rc = RTStrSpaceEnumerate(&pEntry1->Attributes, rtManifestAttributeCompare, pEquals);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check that we matched all that is required.
+ */
+ if ( pEquals->cAttributes2 + pEquals->cIgnoredAttributes2 != pEntry2->cAttributes
+ && ( !(pEquals->fFlags & RTMANIFEST_EQUALS_IGN_MISSING_ATTRS)
+ || pEquals->cIgnoredAttributes1 == pEntry1->cAttributes))
+ rc = RTStrSpaceEnumerate(&pEntry2->Attributes, rtManifestAttributeFindMissing2, pEquals);
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Prepare equals operation}
+ */
+static DECLCALLBACK(int) rtManifestEntryCompare(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTEQUALS pEquals = (PRTMANIFESTEQUALS)pvUser;
+ PRTMANIFESTENTRY pEntry1 = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);
+ PRTMANIFESTENTRY pEntry2;
+
+ /*
+ * Ignore this entry?
+ */
+ char const * const *ppsz = pEquals->papszIgnoreEntries;
+ if (ppsz)
+ {
+ while (*ppsz)
+ {
+ if (!strcmp(*ppsz, pStr->pszString))
+ {
+ pEntry2 = (PRTMANIFESTENTRY)RTStrSpaceGet(&pEquals->pThis2->Entries, pStr->pszString);
+ if (pEntry2)
+ {
+ pEntry2->fVisited = true;
+ pEquals->cIgnoredEntries2++;
+ }
+ pEntry1->fVisited = true;
+ return 0;
+ }
+ ppsz++;
+ }
+ }
+
+ /*
+ * Try find the entry in the other manifest.
+ */
+ pEntry2 = (PRTMANIFESTENTRY)RTStrSpaceGet(&pEquals->pThis2->Entries, pEntry1->StrCore.pszString);
+ if (!pEntry2)
+ {
+ if (!(pEquals->fFlags & RTMANIFEST_EQUALS_IGN_MISSING_ENTRIES_2ND))
+ {
+ RTStrPrintf(pEquals->pszError, pEquals->cbError, "'%s' not found in the 2nd manifest", pEntry1->StrCore.pszString);
+ return VERR_NOT_EQUAL;
+ }
+ pEntry1->fVisited = true;
+ return VINF_SUCCESS;
+ }
+
+ Assert(!pEntry1->fVisited);
+ Assert(!pEntry2->fVisited);
+ pEntry1->fVisited = true;
+ pEntry2->fVisited = true;
+ pEquals->cEntries2++;
+
+ return rtManifestEntryCompare2(pEquals, pEntry1, pEntry2);
+}
+
+
+
+RTDECL(int) RTManifestEqualsEx(RTMANIFEST hManifest1, RTMANIFEST hManifest2, const char * const *papszIgnoreEntries,
+ const char * const *papszIgnoreAttrs, uint32_t fFlags, char *pszError, size_t cbError)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrNullReturn(pszError, VERR_INVALID_POINTER);
+ if (pszError && cbError)
+ *pszError = '\0';
+ RTMANIFESTINT *pThis1 = hManifest1;
+ RTMANIFESTINT *pThis2 = hManifest2;
+ if (pThis1 != NIL_RTMANIFEST)
+ {
+ AssertPtrReturn(pThis1, VERR_INVALID_HANDLE);
+ AssertReturn(pThis1->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ }
+ if (pThis2 != NIL_RTMANIFEST)
+ {
+ AssertPtrReturn(pThis2, VERR_INVALID_HANDLE);
+ AssertReturn(pThis2->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ }
+ AssertReturn(!(fFlags & ~RTMANIFEST_EQUALS_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ /*
+ * The simple cases.
+ */
+ if (pThis1 == pThis2)
+ return VINF_SUCCESS;
+ if (pThis1 == NIL_RTMANIFEST || pThis2 == NIL_RTMANIFEST)
+ return VERR_NOT_EQUAL;
+
+ /*
+ * Since we have to use callback style enumeration, we have to mark the
+ * entries and attributes to make sure we've covered them all.
+ */
+ RTStrSpaceEnumerate(&pThis1->Entries, rtManifestEntryClearVisited, NULL);
+ RTStrSpaceEnumerate(&pThis2->Entries, rtManifestEntryClearVisited, NULL);
+ RTStrSpaceEnumerate(&pThis1->SelfEntry.Attributes, rtManifestAttributeClearVisited, NULL);
+ RTStrSpaceEnumerate(&pThis2->SelfEntry.Attributes, rtManifestAttributeClearVisited, NULL);
+
+ RTMANIFESTEQUALS Equals;
+ Equals.pThis2 = pThis2;
+ Equals.fFlags = fFlags;
+ Equals.papszIgnoreEntries = papszIgnoreEntries;
+ Equals.papszIgnoreAttrs = papszIgnoreAttrs;
+ Equals.pszError = pszError;
+ Equals.cbError = cbError;
+
+ Equals.cIgnoredEntries2 = 0;
+ Equals.cEntries2 = 0;
+ Equals.cIgnoredAttributes1 = 0;
+ Equals.cIgnoredAttributes2 = 0;
+ Equals.cAttributes2 = 0;
+ Equals.pAttributes2 = NULL;
+ Equals.pszCurEntry = NULL;
+
+ int rc = rtManifestEntryCompare2(&Equals, &pThis1->SelfEntry, &pThis2->SelfEntry);
+ if (RT_SUCCESS(rc))
+ rc = RTStrSpaceEnumerate(&pThis1->Entries, rtManifestEntryCompare, &Equals);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Did we cover all entries of the 2nd manifest?
+ */
+ if (Equals.cEntries2 + Equals.cIgnoredEntries2 != pThis2->cEntries)
+ rc = RTStrSpaceEnumerate(&pThis1->Entries, rtManifestEntryFindMissing2, &Equals);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTManifestEquals(RTMANIFEST hManifest1, RTMANIFEST hManifest2)
+{
+ return RTManifestEqualsEx(hManifest1, hManifest2,
+ NULL /*papszIgnoreEntries*/, NULL /*papszIgnoreAttrs*/,
+ 0 /*fFlags*/, NULL, 0);
+}
+
+
+/**
+ * Translates a attribyte type to a attribute name.
+ *
+ * @returns Attribute name for fFlags, NULL if not translatable.
+ * @param fType The type flags. Only one bit should be set.
+ */
+static const char *rtManifestTypeToAttrName(uint32_t fType)
+{
+ switch (fType)
+ {
+ case RTMANIFEST_ATTR_SIZE: return "SIZE";
+ case RTMANIFEST_ATTR_MD5: return "MD5";
+ case RTMANIFEST_ATTR_SHA1: return "SHA1";
+ case RTMANIFEST_ATTR_SHA256: return "SHA256";
+ case RTMANIFEST_ATTR_SHA512: return "SHA512";
+ default: return NULL;
+ }
+}
+
+
+/**
+ * Worker common to RTManifestSetAttr and RTManifestEntrySetAttr.
+ *
+ * @returns IPRT status code.
+ * @param pEntry Pointer to the entry.
+ * @param pszAttr The name of the attribute to add.
+ * @param pszValue The value string.
+ * @param fType The attribute type type.
+ */
+static int rtManifestSetAttrWorker(PRTMANIFESTENTRY pEntry, const char *pszAttr, const char *pszValue, uint32_t fType)
+{
+ char *pszValueCopy;
+ int rc = RTStrDupEx(&pszValueCopy, pszValue);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Does the attribute exist already?
+ */
+ AssertCompileMemberOffset(RTMANIFESTATTR, StrCore, 0);
+ PRTMANIFESTATTR pAttr = (PRTMANIFESTATTR)RTStrSpaceGet(&pEntry->Attributes, pszAttr);
+ if (pAttr)
+ {
+ RTStrFree(pAttr->pszValue);
+ pAttr->pszValue = pszValueCopy;
+ pAttr->fType = fType;
+ }
+ else
+ {
+ size_t const cbName = strlen(pszAttr) + 1;
+ pAttr = (PRTMANIFESTATTR)RTMemAllocVar(RT_UOFFSETOF_DYN(RTMANIFESTATTR, szName[cbName]));
+ if (!pAttr)
+ {
+ RTStrFree(pszValueCopy);
+ return VERR_NO_MEMORY;
+ }
+ memcpy(pAttr->szName, pszAttr, cbName);
+ pAttr->StrCore.pszString = pAttr->szName;
+ pAttr->StrCore.cchString = cbName - 1;
+ pAttr->pszValue = pszValueCopy;
+ pAttr->fType = fType;
+ if (RT_UNLIKELY(!RTStrSpaceInsert(&pEntry->Attributes, &pAttr->StrCore)))
+ {
+ AssertFailed();
+ RTStrFree(pszValueCopy);
+ RTMemFree(pAttr);
+ return VERR_INTERNAL_ERROR_4;
+ }
+ pEntry->cAttributes++;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTManifestSetAttr(RTMANIFEST hManifest, const char *pszAttr, const char *pszValue, uint32_t fType)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pszValue);
+ AssertReturn(RT_IS_POWER_OF_TWO(fType) && fType < RTMANIFEST_ATTR_END, VERR_INVALID_PARAMETER);
+ if (!pszAttr)
+ pszAttr = rtManifestTypeToAttrName(fType);
+ AssertPtr(pszAttr);
+
+ return rtManifestSetAttrWorker(&pThis->SelfEntry, pszAttr, pszValue, fType);
+}
+
+
+/**
+ * Worker common to RTManifestUnsetAttr and RTManifestEntryUnsetAttr.
+ *
+ * @returns IPRT status code.
+ * @param pEntry Pointer to the entry.
+ * @param pszAttr The name of the attribute to remove.
+ */
+static int rtManifestUnsetAttrWorker(PRTMANIFESTENTRY pEntry, const char *pszAttr)
+{
+ PRTSTRSPACECORE pStrCore = RTStrSpaceRemove(&pEntry->Attributes, pszAttr);
+ if (!pStrCore)
+ return VWRN_NOT_FOUND;
+ pEntry->cAttributes--;
+ rtManifestDestroyAttribute(pStrCore, NULL);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTManifestUnsetAttr(RTMANIFEST hManifest, const char *pszAttr)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pszAttr);
+
+ return rtManifestUnsetAttrWorker(&pThis->SelfEntry, pszAttr);
+}
+
+
+/**
+ * Callback employed by rtManifestQueryAttrWorker to search by attribute type.
+ *
+ * @returns VINF_SUCCESS or VINF_CALLBACK_RETURN.
+ * @param pStr The attribute string node.
+ * @param pvUser The argument package.
+ */
+static DECLCALLBACK(int) rtManifestQueryAttrEnumCallback(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTATTR pAttr = (PRTMANIFESTATTR)pStr;
+ PRTMANIFESTQUERYATTRARGS pArgs = (PRTMANIFESTQUERYATTRARGS)pvUser;
+
+ if (pAttr->fType & pArgs->fType)
+ {
+ pArgs->pAttr = pAttr;
+ return VINF_CALLBACK_RETURN;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker common to RTManifestQueryAttr and RTManifestEntryQueryAttr.
+ *
+ * @returns IPRT status code.
+ * @param pEntry The entry.
+ * @param pszAttr The attribute name. If NULL, it will be
+ * selected by @a fType alone.
+ * @param fType The attribute types the entry should match. Pass
+ * Pass RTMANIFEST_ATTR_ANY match any. If more
+ * than one is given, the first matching one is
+ * returned.
+ * @param pszValue Where to return value.
+ * @param cbValue The size of the buffer @a pszValue points to.
+ * @param pfType Where to return the attribute type value.
+ */
+static int rtManifestQueryAttrWorker(PRTMANIFESTENTRY pEntry, const char *pszAttr, uint32_t fType,
+ char *pszValue, size_t cbValue, uint32_t *pfType)
+{
+ /*
+ * Find the requested attribute.
+ */
+ PRTMANIFESTATTR pAttr;
+ if (pszAttr)
+ {
+ /* By name. */
+ pAttr = (PRTMANIFESTATTR)RTStrSpaceGet(&pEntry->Attributes, pszAttr);
+ if (!pAttr)
+ return VERR_MANIFEST_ATTR_NOT_FOUND;
+ if (!(pAttr->fType & fType))
+ return VERR_MANIFEST_ATTR_TYPE_MISMATCH;
+ }
+ else
+ {
+ /* By type. */
+ RTMANIFESTQUERYATTRARGS Args;
+ Args.fType = fType;
+ Args.pAttr = NULL;
+ int rc = RTStrSpaceEnumerate(&pEntry->Attributes, rtManifestQueryAttrEnumCallback, &Args);
+ AssertRCReturn(rc, rc);
+ pAttr = Args.pAttr;
+ if (!pAttr)
+ return VERR_MANIFEST_ATTR_TYPE_NOT_FOUND;
+ }
+
+ /*
+ * Set the return values.
+ */
+ if (cbValue || pszValue)
+ {
+ size_t cbNeeded = strlen(pAttr->pszValue) + 1;
+ if (cbNeeded > cbValue)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pszValue, pAttr->pszValue, cbNeeded);
+ }
+
+ if (pfType)
+ *pfType = pAttr->fType;
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTManifestQueryAttr(RTMANIFEST hManifest, const char *pszAttr, uint32_t fType,
+ char *pszValue, size_t cbValue, uint32_t *pfType)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNull(pszAttr);
+ AssertPtr(pszValue);
+
+ return rtManifestQueryAttrWorker(&pThis->SelfEntry, pszAttr, fType, pszValue, cbValue, pfType);
+}
+
+
+/**
+ * Callback employed by RTManifestQueryAllAttrTypes to collect attribute types.
+ *
+ * @returns VINF_SUCCESS.
+ * @param pStr The attribute string node.
+ * @param pvUser Pointer to type flags (uint32_t).
+ */
+static DECLCALLBACK(int) rtManifestQueryAllAttrTypesEnumAttrCallback(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTATTR pAttr = (PRTMANIFESTATTR)pStr;
+ uint32_t *pfTypes = (uint32_t *)pvUser;
+ *pfTypes |= pAttr->fType;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Callback employed by RTManifestQueryAllAttrTypes to collect attribute types
+ * for an entry.
+ *
+ * @returns VINF_SUCCESS.
+ * @param pStr The attribute string node.
+ * @param pvUser Pointer to type flags (uint32_t).
+ */
+static DECLCALLBACK(int) rtManifestQueryAllAttrTypesEnumEntryCallback(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTENTRY pEntry = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);
+ return RTStrSpaceEnumerate(&pEntry->Attributes, rtManifestQueryAllAttrTypesEnumAttrCallback, pvUser);
+}
+
+
+RTDECL(int) RTManifestQueryAllAttrTypes(RTMANIFEST hManifest, bool fEntriesOnly, uint32_t *pfTypes)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pfTypes);
+
+ *pfTypes = 0;
+ int rc = RTStrSpaceEnumerate(&pThis->Entries, rtManifestQueryAllAttrTypesEnumEntryCallback, pfTypes);
+ if (RT_SUCCESS(rc) && fEntriesOnly)
+ rc = rtManifestQueryAllAttrTypesEnumAttrCallback(&pThis->SelfEntry.StrCore, pfTypes);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates the name entry.
+ *
+ * @returns IPRT status code.
+ * @param pszEntry The entry name to validate.
+ * @param pfNeedNormalization Where to return whether it needs normalization
+ * or not. Optional.
+ * @param pcchEntry Where to return the length. Optional.
+ */
+static int rtManifestValidateNameEntry(const char *pszEntry, bool *pfNeedNormalization, size_t *pcchEntry)
+{
+ int rc;
+ bool fNeedNormalization = false;
+ const char *pszCur = pszEntry;
+
+ for (;;)
+ {
+ RTUNICP uc;
+ rc = RTStrGetCpEx(&pszCur, &uc);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!uc)
+ break;
+ if (uc == '\\')
+ fNeedNormalization = true;
+ else if (uc < 32 || uc == ':' || uc == '(' || uc == ')')
+ return VERR_INVALID_NAME;
+ }
+
+ if (pfNeedNormalization)
+ *pfNeedNormalization = fNeedNormalization;
+
+ size_t cchEntry = pszCur - pszEntry - 1;
+ if (!cchEntry)
+ rc = VERR_INVALID_NAME;
+ if (pcchEntry)
+ *pcchEntry = cchEntry;
+
+ return rc;
+}
+
+
+/**
+ * Normalizes a entry name.
+ *
+ * @param pszEntry The entry name to normalize.
+ */
+static void rtManifestNormalizeEntry(char *pszEntry)
+{
+ char ch;
+ while ((ch = *pszEntry))
+ {
+ if (ch == '\\')
+ *pszEntry = '/';
+ pszEntry++;
+ }
+}
+
+
+/**
+ * Gets an entry.
+ *
+ * @returns IPRT status code.
+ * @param pThis The manifest to work with.
+ * @param pszEntry The entry name.
+ * @param fNeedNormalization Whether rtManifestValidateNameEntry said it
+ * needed normalization.
+ * @param cchEntry The length of the name.
+ * @param ppEntry Where to return the entry pointer on success.
+ */
+static int rtManifestGetEntry(RTMANIFESTINT *pThis, const char *pszEntry, bool fNeedNormalization, size_t cchEntry,
+ PRTMANIFESTENTRY *ppEntry)
+{
+ PRTMANIFESTENTRY pEntry;
+
+ AssertCompileMemberOffset(RTMANIFESTATTR, StrCore, 0);
+ if (!fNeedNormalization)
+ pEntry = (PRTMANIFESTENTRY)RTStrSpaceGet(&pThis->Entries, pszEntry);
+ else
+ {
+ char *pszCopy = (char *)RTMemTmpAlloc(cchEntry + 1);
+ if (RT_UNLIKELY(!pszCopy))
+ return VERR_NO_TMP_MEMORY;
+ memcpy(pszCopy, pszEntry, cchEntry + 1);
+ rtManifestNormalizeEntry(pszCopy);
+
+ pEntry = (PRTMANIFESTENTRY)RTStrSpaceGet(&pThis->Entries, pszCopy);
+ RTMemTmpFree(pszCopy);
+ }
+
+ *ppEntry = pEntry;
+ return pEntry ? VINF_SUCCESS : VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTManifestEntrySetAttr(RTMANIFEST hManifest, const char *pszEntry, const char *pszAttr,
+ const char *pszValue, uint32_t fType)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pszEntry);
+ AssertPtr(pszValue);
+ AssertReturn(RT_IS_POWER_OF_TWO(fType) && fType < RTMANIFEST_ATTR_END, VERR_INVALID_PARAMETER);
+ if (!pszAttr)
+ pszAttr = rtManifestTypeToAttrName(fType);
+ AssertPtr(pszAttr);
+
+ bool fNeedNormalization;
+ size_t cchEntry;
+ int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Resolve the entry, adding one if necessary.
+ */
+ PRTMANIFESTENTRY pEntry;
+ rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
+ if (rc == VERR_NOT_FOUND)
+ {
+ pEntry = (PRTMANIFESTENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTMANIFESTENTRY, szName[cchEntry + 1]));
+ if (!pEntry)
+ return VERR_NO_MEMORY;
+
+ pEntry->StrCore.cchString = cchEntry;
+ pEntry->StrCore.pszString = pEntry->szName;
+ pEntry->Attributes = NULL;
+ pEntry->cAttributes = 0;
+ memcpy(pEntry->szName, pszEntry, cchEntry + 1);
+ if (fNeedNormalization)
+ rtManifestNormalizeEntry(pEntry->szName);
+
+ if (!RTStrSpaceInsert(&pThis->Entries, &pEntry->StrCore))
+ {
+ RTMemFree(pEntry);
+ return VERR_INTERNAL_ERROR_4;
+ }
+ pThis->cEntries++;
+ }
+ else if (RT_FAILURE(rc))
+ return rc;
+
+ return rtManifestSetAttrWorker(pEntry, pszAttr, pszValue, fType);
+}
+
+
+RTDECL(int) RTManifestEntryUnsetAttr(RTMANIFEST hManifest, const char *pszEntry, const char *pszAttr)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pszEntry);
+ AssertPtr(pszAttr);
+
+ bool fNeedNormalization;
+ size_t cchEntry;
+ int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Resolve the entry and hand it over to the worker.
+ */
+ PRTMANIFESTENTRY pEntry;
+ rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
+ if (RT_SUCCESS(rc))
+ rc = rtManifestUnsetAttrWorker(pEntry, pszAttr);
+ return rc;
+}
+
+
+RTDECL(int) RTManifestEntryQueryAttr(RTMANIFEST hManifest, const char *pszEntry, const char *pszAttr, uint32_t fType,
+ char *pszValue, size_t cbValue, uint32_t *pfType)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pszEntry);
+ AssertPtrNull(pszAttr);
+ AssertPtr(pszValue);
+
+ /*
+ * Look up the entry.
+ */
+ bool fNeedNormalization;
+ size_t cchEntry;
+ int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
+ AssertRCReturn(rc, rc);
+
+ PRTMANIFESTENTRY pEntry;
+ rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
+ if (RT_SUCCESS(rc))
+ rc = rtManifestQueryAttrWorker(pEntry, pszAttr, fType, pszValue, cbValue, pfType);
+ return rc;
+}
+
+
+RTDECL(int) RTManifestEntryAdd(RTMANIFEST hManifest, const char *pszEntry)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pszEntry);
+
+ bool fNeedNormalization;
+ size_t cchEntry;
+ int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Only add one if it does not already exist.
+ */
+ PRTMANIFESTENTRY pEntry;
+ rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
+ if (rc == VERR_NOT_FOUND)
+ {
+ pEntry = (PRTMANIFESTENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTMANIFESTENTRY, szName[cchEntry + 1]));
+ if (pEntry)
+ {
+ pEntry->StrCore.cchString = cchEntry;
+ pEntry->StrCore.pszString = pEntry->szName;
+ pEntry->Attributes = NULL;
+ pEntry->cAttributes = 0;
+ memcpy(pEntry->szName, pszEntry, cchEntry + 1);
+ if (fNeedNormalization)
+ rtManifestNormalizeEntry(pEntry->szName);
+
+ if (RTStrSpaceInsert(&pThis->Entries, &pEntry->StrCore))
+ {
+ pThis->cEntries++;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ RTMemFree(pEntry);
+ rc = VERR_INTERNAL_ERROR_4;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VWRN_ALREADY_EXISTS;
+
+ return rc;
+}
+
+
+RTDECL(int) RTManifestEntryRemove(RTMANIFEST hManifest, const char *pszEntry)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pszEntry);
+
+ bool fNeedNormalization;
+ size_t cchEntry;
+ int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Look it up before removing it.
+ */
+ PRTMANIFESTENTRY pEntry;
+ rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
+ if (RT_SUCCESS(rc))
+ {
+ PRTSTRSPACECORE pStrCore = RTStrSpaceRemove(&pThis->Entries, pEntry->StrCore.pszString);
+ AssertReturn(pStrCore, VERR_INTERNAL_ERROR_3);
+ pThis->cEntries--;
+ rtManifestDestroyEntry(pStrCore, pThis);
+ }
+
+ return rc;
+}
+
+
+RTDECL(bool) RTManifestEntryExists(RTMANIFEST hManifest, const char *pszEntry)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, false);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, false);
+ AssertPtr(pszEntry);
+
+ bool fNeedNormalization;
+ size_t cchEntry;
+ int rc = rtManifestValidateNameEntry(pszEntry, &fNeedNormalization, &cchEntry);
+ AssertRCReturn(rc, false);
+
+ /*
+ * Check if it exists.
+ */
+ PRTMANIFESTENTRY pEntry;
+ rc = rtManifestGetEntry(pThis, pszEntry, fNeedNormalization, cchEntry, &pEntry);
+ return RT_SUCCESS_NP(rc);
+}
+
+
+/**
+ * Reads a line from a VFS I/O stream.
+ *
+ * @todo Replace this with a buffered I/O stream layer.
+ *
+ * @returns IPRT status code. VERR_EOF when trying to read beyond the stream
+ * end.
+ * @param hVfsIos The I/O stream to read from.
+ * @param pszLine Where to store what we've read.
+ * @param cbLine The number of bytes to read.
+ */
+static int rtManifestReadLine(RTVFSIOSTREAM hVfsIos, char *pszLine, size_t cbLine)
+{
+ /* This is horribly slow right now, but it's not a biggy as the input is
+ usually cached in memory somewhere... */
+ *pszLine = '\0';
+ while (cbLine > 1)
+ {
+ char ch;
+ int rc = RTVfsIoStrmRead(hVfsIos, &ch, 1, true /*fBLocking*/, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* \r\n */
+ if (ch == '\r')
+ {
+ if (cbLine <= 2)
+ {
+ pszLine[0] = ch;
+ pszLine[1] = '\0';
+ return VINF_BUFFER_OVERFLOW;
+ }
+
+ rc = RTVfsIoStrmRead(hVfsIos, &ch, 1, true /*fBLocking*/, NULL);
+ if (RT_SUCCESS(rc) && ch == '\n')
+ return VINF_SUCCESS;
+ pszLine[0] = '\r';
+ pszLine[1] = ch;
+ pszLine[2] = '\0';
+ if (RT_FAILURE(rc))
+ return rc == VERR_EOF ? VINF_EOF : rc;
+ }
+
+ /* \n */
+ if (ch == '\n')
+ return VINF_SUCCESS;
+
+ /* add character. */
+ pszLine[0] = ch;
+ pszLine[1] = '\0';
+
+ /* advance */
+ pszLine++;
+ cbLine--;
+ }
+
+ return VINF_BUFFER_OVERFLOW;
+}
+
+
+RTDECL(int) RTManifestReadStandardEx(RTMANIFEST hManifest, RTVFSIOSTREAM hVfsIos, char *pszErr, size_t cbErr)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrNull(pszErr);
+ if (pszErr && cbErr)
+ *pszErr = '\0';
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Process the stream line by line.
+ */
+ uint32_t iLine = 0;
+ for (;;)
+ {
+ /*
+ * Read a line from the input stream.
+ */
+ iLine++;
+ char szLine[RTPATH_MAX + RTSHA512_DIGEST_LEN + 32];
+ int rc = rtManifestReadLine(hVfsIos, szLine, sizeof(szLine));
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_EOF)
+ return VINF_SUCCESS;
+ RTStrPrintf(pszErr, cbErr, "Error reading line #%u: %Rrc", iLine, rc);
+ return rc;
+ }
+ if (rc != VINF_SUCCESS)
+ {
+ RTStrPrintf(pszErr, cbErr, "Line number %u is too long", iLine);
+ return VERR_OUT_OF_RANGE;
+ }
+
+ /*
+ * Strip it and skip if empty.
+ */
+ char *psz = RTStrStrip(szLine);
+ if (!*psz)
+ continue;
+
+ /*
+ * Read the attribute name.
+ */
+ char ch;
+ const char * const pszAttr = psz;
+ do
+ psz++;
+ while (!RT_C_IS_BLANK((ch = *psz)) && ch && ch != '(');
+ if (ch)
+ *psz++ = '\0';
+
+ /*
+ * The entry name is enclosed in parenthesis and followed by a '='.
+ */
+ if (ch != '(')
+ {
+ psz = RTStrStripL(psz);
+ ch = *psz++;
+ if (ch != '(')
+ {
+ RTStrPrintf(pszErr, cbErr, "Expected '(' after %zu on line %u", psz - szLine - 1, iLine);
+ return VERR_PARSE_ERROR;
+ }
+ }
+ const char * const pszName = psz;
+ while ((ch = *psz) != '\0')
+ {
+ if (ch == ')')
+ {
+ char *psz2 = RTStrStripL(psz + 1);
+ if (*psz2 == '=')
+ {
+ *psz = '\0';
+ psz = psz2;
+ break;
+ }
+ }
+ psz++;
+ }
+
+ if (*psz != '=')
+ {
+ RTStrPrintf(pszErr, cbErr, "Expected ')=' at %zu on line %u", psz - szLine, iLine);
+ return VERR_PARSE_ERROR;
+ }
+
+ /*
+ * The value.
+ */
+ psz = RTStrStrip(psz + 1);
+ const char * const pszValue = psz;
+ if (!*psz)
+ {
+ RTStrPrintf(pszErr, cbErr, "Expected value at %zu on line %u", psz - szLine, iLine);
+ return VERR_PARSE_ERROR;
+ }
+
+ /*
+ * Detect attribute type and sanity check the value.
+ */
+ uint32_t fType = RTMANIFEST_ATTR_UNKNOWN;
+ static const struct
+ {
+ const char *pszAttr;
+ uint32_t fType;
+ unsigned cBits;
+ unsigned uBase;
+ } s_aDecAttrs[] =
+ {
+ { "SIZE", RTMANIFEST_ATTR_SIZE, 64, 10}
+ };
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aDecAttrs); i++)
+ if (!strcmp(s_aDecAttrs[i].pszAttr, pszAttr))
+ {
+ fType = s_aDecAttrs[i].fType;
+ rc = RTStrToUInt64Full(pszValue, s_aDecAttrs[i].uBase, NULL);
+ if (rc != VINF_SUCCESS)
+ {
+ RTStrPrintf(pszErr, cbErr, "Malformed value ('%s') at %zu on line %u: %Rrc", pszValue, psz - szLine, iLine, rc);
+ return VERR_PARSE_ERROR;
+ }
+ break;
+ }
+
+ if (fType == RTMANIFEST_ATTR_UNKNOWN)
+ {
+ static const struct
+ {
+ const char *pszAttr;
+ uint32_t fType;
+ unsigned cchHex;
+ } s_aHexAttrs[] =
+ {
+ { "MD5", RTMANIFEST_ATTR_MD5, RTMD5_DIGEST_LEN },
+ { "SHA1", RTMANIFEST_ATTR_SHA1, RTSHA1_DIGEST_LEN },
+ { "SHA256", RTMANIFEST_ATTR_SHA256, RTSHA256_DIGEST_LEN },
+ { "SHA512", RTMANIFEST_ATTR_SHA512, RTSHA512_DIGEST_LEN }
+ };
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aHexAttrs); i++)
+ if (!strcmp(s_aHexAttrs[i].pszAttr, pszAttr))
+ {
+ fType = s_aHexAttrs[i].fType;
+ for (unsigned off = 0; off < s_aHexAttrs[i].cchHex; off++)
+ if (!RT_C_IS_XDIGIT(pszValue[off]))
+ {
+ RTStrPrintf(pszErr, cbErr, "Expected hex digit at %zu on line %u (value '%s', pos %u)",
+ pszValue - szLine + off, iLine, pszValue, off);
+ return VERR_PARSE_ERROR;
+ }
+ break;
+ }
+ }
+
+ /*
+ * Finally, add it.
+ */
+ rc = RTManifestEntrySetAttr(hManifest, pszName, pszAttr, pszValue, fType);
+ if (RT_FAILURE(rc))
+ {
+ RTStrPrintf(pszErr, cbErr, "RTManifestEntrySetAttr(,'%s','%s', '%s', %#x) failed on line %u: %Rrc",
+ pszName, pszAttr, pszValue, fType, iLine, rc);
+ return rc;
+ }
+ }
+}
+
+RTDECL(int) RTManifestReadStandard(RTMANIFEST hManifest, RTVFSIOSTREAM hVfsIos)
+{
+ return RTManifestReadStandardEx(hManifest, hVfsIos, NULL, 0);
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Writes RTMANIFESTATTR.}
+ */
+static DECLCALLBACK(int) rtManifestWriteStdAttr(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTATTR pAttr = RT_FROM_MEMBER(pStr, RTMANIFESTATTR, StrCore);
+ RTMANIFESTWRITESTDATTR *pArgs = (RTMANIFESTWRITESTDATTR *)pvUser;
+ char szLine[RTPATH_MAX + RTSHA512_DIGEST_LEN + 32];
+ size_t cchLine = RTStrPrintf(szLine, sizeof(szLine), "%s (%s) = %s\n", pAttr->szName, pArgs->pszEntry, pAttr->pszValue);
+ if (cchLine >= sizeof(szLine) - 1)
+ return VERR_BUFFER_OVERFLOW;
+ return RTVfsIoStrmWrite(pArgs->hVfsIos, szLine, cchLine, true /*fBlocking*/, NULL);
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK, Writes RTMANIFESTENTRY.}
+ */
+static DECLCALLBACK(int) rtManifestWriteStdEntry(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ PRTMANIFESTENTRY pEntry = RT_FROM_MEMBER(pStr, RTMANIFESTENTRY, StrCore);
+
+ RTMANIFESTWRITESTDATTR Args;
+ Args.hVfsIos = (RTVFSIOSTREAM)pvUser;
+ Args.pszEntry = pStr->pszString;
+ return RTStrSpaceEnumerate(&pEntry->Attributes, rtManifestWriteStdAttr, &Args);
+}
+
+
+RTDECL(int) RTManifestWriteStandard(RTMANIFEST hManifest, RTVFSIOSTREAM hVfsIos)
+{
+ RTMANIFESTINT *pThis = hManifest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTMANIFEST_MAGIC, VERR_INVALID_HANDLE);
+
+ RTMANIFESTWRITESTDATTR Args;
+ Args.hVfsIos = hVfsIos;
+ Args.pszEntry = "main";
+ int rc = RTStrSpaceEnumerate(&pThis->SelfEntry.Attributes, rtManifestWriteStdAttr, &Args);
+ if (RT_SUCCESS(rc))
+ rc = RTStrSpaceEnumerate(&pThis->Entries, rtManifestWriteStdEntry, hVfsIos);
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/checksum/manifest3.cpp b/src/VBox/Runtime/common/checksum/manifest3.cpp
new file mode 100644
index 00000000..8309d637
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/manifest3.cpp
@@ -0,0 +1,667 @@
+/* $Id: manifest3.cpp $ */
+/** @file
+ * IPRT - Manifest, the bits with the most dependencies.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/manifest.h>
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/md5.h>
+#include <iprt/mem.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/zero.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Hashes data.
+ *
+ * Used when hashing a file, stream or similar.
+ */
+typedef struct RTMANIFESTHASHES
+{
+ /** The desired attribute types.
+ * Only the hashes indicated by this will be calculated. */
+ uint32_t fAttrs;
+ /** The size. */
+ RTFOFF cbStream;
+
+ /** The MD5 context. */
+ RTMD5CONTEXT Md5Ctx;
+ /** The SHA-1 context. */
+ RTSHA1CONTEXT Sha1Ctx;
+ /** The SHA-256 context. */
+ RTSHA256CONTEXT Sha256Ctx;
+ /** The SHA-512 context. */
+ RTSHA512CONTEXT Sha512Ctx;
+
+ /** The MD5 digest. */
+ uint8_t abMd5Digest[RTMD5_HASH_SIZE];
+ /** The SHA-1 digest. */
+ uint8_t abSha1Digest[RTSHA1_HASH_SIZE];
+ /** The SHA-256 digest. */
+ uint8_t abSha256Digest[RTSHA256_HASH_SIZE];
+ /** The SHA-512 digest. */
+ uint8_t abSha512Digest[RTSHA512_HASH_SIZE];
+} RTMANIFESTHASHES;
+/** Pointer to a the hashes for a stream. */
+typedef RTMANIFESTHASHES *PRTMANIFESTHASHES;
+
+
+/**
+ * The internal data of a manifest passthru I/O stream.
+ */
+typedef struct RTMANIFESTPTIOS
+{
+ /** The stream we're reading from or writing to. */
+ RTVFSIOSTREAM hVfsIos;
+ /** The hashes. */
+ PRTMANIFESTHASHES pHashes;
+ /** The current hash position. */
+ RTFOFF offCurPos;
+ /** Whether we're reading or writing. */
+ bool fReadOrWrite;
+ /** Whether we've already added the entry to the manifest. */
+ bool fAddedEntry;
+ /** The entry name. */
+ char *pszEntry;
+ /** The manifest to add the entry to. */
+ RTMANIFEST hManifest;
+} RTMANIFESTPTIOS;
+/** Pointer to a the internal data of a manifest passthru I/O stream. */
+typedef RTMANIFESTPTIOS *PRTMANIFESTPTIOS;
+
+
+
+/**
+ * Creates a hashes structure.
+ *
+ * @returns Pointer to a hashes structure.
+ * @param fAttrs The desired hashes, RTMANIFEST_ATTR_XXX.
+ */
+static PRTMANIFESTHASHES rtManifestHashesCreate(uint32_t fAttrs)
+{
+ PRTMANIFESTHASHES pHashes = (PRTMANIFESTHASHES)RTMemTmpAllocZ(sizeof(*pHashes));
+ if (pHashes)
+ {
+ pHashes->fAttrs = fAttrs;
+ /*pHashes->cbStream = 0;*/
+ if (fAttrs & RTMANIFEST_ATTR_MD5)
+ RTMd5Init(&pHashes->Md5Ctx);
+ if (fAttrs & RTMANIFEST_ATTR_SHA1)
+ RTSha1Init(&pHashes->Sha1Ctx);
+ if (fAttrs & RTMANIFEST_ATTR_SHA256)
+ RTSha256Init(&pHashes->Sha256Ctx);
+ if (fAttrs & RTMANIFEST_ATTR_SHA512)
+ RTSha512Init(&pHashes->Sha512Ctx);
+ }
+ return pHashes;
+}
+
+
+/**
+ * Updates the hashes with a block of data.
+ *
+ * @param pHashes The hashes structure.
+ * @param pvBuf The data block.
+ * @param cbBuf The size of the data block.
+ */
+static void rtManifestHashesUpdate(PRTMANIFESTHASHES pHashes, void const *pvBuf, size_t cbBuf)
+{
+ pHashes->cbStream += cbBuf;
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_MD5)
+ RTMd5Update(&pHashes->Md5Ctx, pvBuf, cbBuf);
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SHA1)
+ RTSha1Update(&pHashes->Sha1Ctx, pvBuf, cbBuf);
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SHA256)
+ RTSha256Update(&pHashes->Sha256Ctx, pvBuf, cbBuf);
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SHA512)
+ RTSha512Update(&pHashes->Sha512Ctx, pvBuf, cbBuf);
+}
+
+
+/**
+ * Finalizes all the hashes.
+ *
+ * @param pHashes The hashes structure.
+ */
+static void rtManifestHashesFinal(PRTMANIFESTHASHES pHashes)
+{
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_MD5)
+ RTMd5Final(pHashes->abMd5Digest, &pHashes->Md5Ctx);
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SHA1)
+ RTSha1Final(&pHashes->Sha1Ctx, pHashes->abSha1Digest);
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SHA256)
+ RTSha256Final(&pHashes->Sha256Ctx, pHashes->abSha256Digest);
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SHA512)
+ RTSha512Final(&pHashes->Sha512Ctx, pHashes->abSha512Digest);
+}
+
+
+/**
+ * Adds the hashes to a manifest entry.
+ *
+ * @returns IPRT status code.
+ * @param pHashes The hashes structure.
+ * @param hManifest The manifest to add them to.
+ * @param pszEntry The entry name.
+ */
+static int rtManifestHashesSetAttrs(PRTMANIFESTHASHES pHashes, RTMANIFEST hManifest, const char *pszEntry)
+{
+ char szValue[RTSHA512_DIGEST_LEN + 8];
+ int rc = VINF_SUCCESS;
+ int rc2;
+
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SIZE)
+ {
+ RTStrPrintf(szValue, sizeof(szValue), "%RU64", (uint64_t)pHashes->cbStream);
+ rc2 = RTManifestEntrySetAttr(hManifest, pszEntry, "SIZE", szValue, RTMANIFEST_ATTR_SIZE);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_MD5)
+ {
+ rc2 = RTMd5ToString(pHashes->abMd5Digest, szValue, sizeof(szValue));
+ if (RT_SUCCESS(rc2))
+ rc2 = RTManifestEntrySetAttr(hManifest, pszEntry, "MD5", szValue, RTMANIFEST_ATTR_MD5);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SHA1)
+ {
+ rc2 = RTSha1ToString(pHashes->abSha1Digest, szValue, sizeof(szValue));
+ if (RT_SUCCESS(rc2))
+ rc2 = RTManifestEntrySetAttr(hManifest, pszEntry, "SHA1", szValue, RTMANIFEST_ATTR_SHA1);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SHA256)
+ {
+ rc2 = RTSha256ToString(pHashes->abSha256Digest, szValue, sizeof(szValue));
+ if (RT_SUCCESS(rc2))
+ rc2 = RTManifestEntrySetAttr(hManifest, pszEntry, "SHA256", szValue, RTMANIFEST_ATTR_SHA256);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ if (pHashes->fAttrs & RTMANIFEST_ATTR_SHA512)
+ {
+ rc2 = RTSha512ToString(pHashes->abSha512Digest, szValue, sizeof(szValue));
+ if (RT_SUCCESS(rc2))
+ rc2 = RTManifestEntrySetAttr(hManifest, pszEntry, "SHA512", szValue, RTMANIFEST_ATTR_SHA512);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Destroys the hashes.
+ *
+ * @param pHashes The hashes structure. NULL is ignored.
+ */
+static void rtManifestHashesDestroy(PRTMANIFESTHASHES pHashes)
+{
+ RTMemTmpFree(pHashes);
+}
+
+
+
+/*
+ *
+ * M a n i f e s t p a s s t h r u I / O s t r e a m
+ * M a n i f e s t p a s s t h r u I / O s t r e a m
+ * M a n i f e s t p a s s t h r u I / O s t r e a m
+ *
+ */
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtManifestPtIos_Close(void *pvThis)
+{
+ PRTMANIFESTPTIOS pThis = (PRTMANIFESTPTIOS)pvThis;
+
+ int rc = VINF_SUCCESS;
+ if (!pThis->fAddedEntry)
+ {
+ rtManifestHashesFinal(pThis->pHashes);
+ rc = rtManifestHashesSetAttrs(pThis->pHashes, pThis->hManifest, pThis->pszEntry);
+ }
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+ rtManifestHashesDestroy(pThis->pHashes);
+ pThis->pHashes = NULL;
+ RTStrFree(pThis->pszEntry);
+ pThis->pszEntry = NULL;
+ RTManifestRelease(pThis->hManifest);
+ pThis->hManifest = NIL_RTMANIFEST;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtManifestPtIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTMANIFESTPTIOS pThis = (PRTMANIFESTPTIOS)pvThis;
+ return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
+}
+
+/**
+ * Updates the hashes with a scather/gather buffer.
+ *
+ * @param pThis The passthru I/O stream instance data.
+ * @param pSgBuf The scather/gather buffer.
+ * @param cbLeft The number of bytes to take from the buffer.
+ */
+static void rtManifestPtIos_UpdateHashes(PRTMANIFESTPTIOS pThis, PCRTSGBUF pSgBuf, size_t cbLeft)
+{
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ {
+ size_t cbSeg = pSgBuf->paSegs[iSeg].cbSeg;
+ if (cbSeg > cbLeft)
+ cbSeg = cbLeft;
+ rtManifestHashesUpdate(pThis->pHashes, pSgBuf->paSegs[iSeg].pvSeg, cbSeg);
+ cbLeft -= cbSeg;
+ if (!cbLeft)
+ break;
+ }
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtManifestPtIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTMANIFESTPTIOS pThis = (PRTMANIFESTPTIOS)pvThis;
+ int rc;
+
+ /*
+ * To make sure we're continuing where we left off, we must have the exact
+ * stream position since a previous read using 'off' may change it.
+ */
+ RTFOFF offActual = off == -1 ? RTVfsIoStrmTell(pThis->hVfsIos) : off;
+ if (offActual == pThis->offCurPos)
+ {
+ rc = RTVfsIoStrmSgRead(pThis->hVfsIos, off, pSgBuf, fBlocking, pcbRead);
+ if (RT_SUCCESS(rc))
+ {
+ rtManifestPtIos_UpdateHashes(pThis, pSgBuf, pcbRead ? *pcbRead : ~(size_t)0);
+ if (!pcbRead)
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ pThis->offCurPos += pSgBuf->paSegs[iSeg].cbSeg;
+ else
+ pThis->offCurPos += *pcbRead;
+ }
+ Assert(RTVfsIoStrmTell(pThis->hVfsIos) == pThis->offCurPos);
+ }
+ else
+ {
+ /*
+ * If we're skipping over stuff, we need to read the gap and hash it.
+ */
+ if (pThis->offCurPos < offActual)
+ {
+ size_t cbBuf = _8K;
+ void *pvBuf = alloca(cbBuf);
+ do
+ {
+ RTFOFF cbGap = off - pThis->offCurPos;
+ size_t cbThisRead = cbGap >= (RTFOFF)cbBuf ? cbBuf : (size_t)cbGap;
+ size_t cbActual;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, pThis->offCurPos, pvBuf, cbThisRead, fBlocking, &cbActual);
+ if (RT_FAILURE(rc) || rc == VINF_TRY_AGAIN)
+ return rc;
+
+ rtManifestHashesUpdate(pThis->pHashes, pvBuf, cbActual);
+ pThis->offCurPos += cbActual;
+
+ if (rc == VINF_EOF)
+ {
+ if (pcbRead)
+ *pcbRead = 0;
+ else
+ rc = VERR_EOF;
+ return rc;
+ }
+ } while (pThis->offCurPos < offActual);
+ Assert(RTVfsIoStrmTell(pThis->hVfsIos) == offActual);
+ }
+
+ /*
+ * At this point we've eliminated any gap and can execute the requested read.
+ */
+ rc = RTVfsIoStrmSgRead(pThis->hVfsIos, off, pSgBuf, fBlocking, pcbRead);
+ if (RT_SUCCESS(rc))
+ {
+ /* See if there is anything to update the hash with. */
+ size_t cbLeft = pcbRead ? *pcbRead : ~(size_t)0;
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ {
+ size_t cbThis = pSgBuf->paSegs[iSeg].cbSeg;
+ if (cbThis > cbLeft)
+ cbThis = cbLeft;
+
+ if ( offActual >= pThis->offCurPos
+ && pThis->offCurPos < offActual + (ssize_t)cbThis)
+ {
+ size_t offSeg = (size_t)(offActual - pThis->offCurPos);
+ rtManifestHashesUpdate(pThis->pHashes, (uint8_t *)pSgBuf->paSegs[iSeg].pvSeg + offSeg, cbThis - offSeg);
+ pThis->offCurPos += cbThis - offSeg;
+ }
+
+ cbLeft -= cbThis;
+ if (!cbLeft)
+ break;
+ offActual += cbThis;
+ }
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtManifestPtIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTMANIFESTPTIOS pThis = (PRTMANIFESTPTIOS)pvThis;
+ Assert(RTVfsIoStrmTell(pThis->hVfsIos) == pThis->offCurPos);
+
+ /*
+ * Validate the offset.
+ */
+ if (off < 0 || off == pThis->offCurPos)
+ { /* likely */ }
+ else
+ {
+ /* We cannot go back and rewrite stuff. Sorry. */
+ AssertReturn(off > pThis->offCurPos, VERR_WRONG_ORDER);
+
+ /*
+ * We've got a gap between the current and new position.
+ * Fill it with zeros and hope for the best.
+ */
+ uint64_t cbZeroGap = off - pThis->offCurPos;
+ do
+ {
+ size_t cbToZero = cbZeroGap >= sizeof(g_abRTZero64K) ? sizeof(g_abRTZero64K) : (size_t)cbZeroGap;
+ size_t cbZeroed = 0;
+ int rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero64K, cbToZero, true /*fBlocking*/, &cbZeroed);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->offCurPos += cbZeroed;
+ rtManifestHashesUpdate(pThis->pHashes, g_abRTZero64K, cbZeroed);
+ cbZeroGap -= cbZeroed;
+ } while (cbZeroGap > 0);
+ Assert(off == pThis->offCurPos);
+ }
+
+ /*
+ * Do the writing.
+ */
+ int rc = RTVfsIoStrmSgWrite(pThis->hVfsIos, -1 /*off*/, pSgBuf, fBlocking, pcbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ rtManifestPtIos_UpdateHashes(pThis, pSgBuf, pcbWritten ? *pcbWritten : ~(size_t)0);
+ if (!pcbWritten)
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ pThis->offCurPos += pSgBuf->paSegs[iSeg].cbSeg;
+ else
+ pThis->offCurPos += *pcbWritten;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtManifestPtIos_Flush(void *pvThis)
+{
+ PRTMANIFESTPTIOS pThis = (PRTMANIFESTPTIOS)pvThis;
+ return RTVfsIoStrmFlush(pThis->hVfsIos);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtManifestPtIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTMANIFESTPTIOS pThis = (PRTMANIFESTPTIOS)pvThis;
+ return RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtManifestPtIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTMANIFESTPTIOS pThis = (PRTMANIFESTPTIOS)pvThis;
+ RTFOFF off = RTVfsIoStrmTell(pThis->hVfsIos);
+ if (off < 0)
+ return (int)off;
+ *poffActual = off;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The manifest passthru I/O stream vtable.
+ */
+static RTVFSIOSTREAMOPS g_rtManifestPassthruIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "manifest passthru I/O stream",
+ rtManifestPtIos_Close,
+ rtManifestPtIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ 0,
+ rtManifestPtIos_Read,
+ rtManifestPtIos_Write,
+ rtManifestPtIos_Flush,
+ rtManifestPtIos_PollOne,
+ rtManifestPtIos_Tell,
+ NULL /* Skip */,
+ NULL /* ZeroFill */,
+ RTVFSIOSTREAMOPS_VERSION,
+};
+
+
+
+RTDECL(int) RTManifestEntryAddPassthruIoStream(RTMANIFEST hManifest, RTVFSIOSTREAM hVfsIos, const char *pszEntry,
+ uint32_t fAttrs, bool fReadOrWrite, PRTVFSIOSTREAM phVfsIosPassthru)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(fAttrs < RTMANIFEST_ATTR_END, VERR_INVALID_PARAMETER);
+ AssertPtr(pszEntry);
+ AssertPtr(phVfsIosPassthru);
+
+ RTFOFF const offCurPos = RTVfsIoStrmTell(hVfsIos);
+ AssertReturn(offCurPos >= 0, (int)offCurPos);
+
+ uint32_t cRefs = RTManifestRetain(hManifest);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ cRefs = RTVfsIoStrmRetain(hVfsIos);
+ AssertReturnStmt(cRefs != UINT32_MAX, RTManifestRelease(hManifest), VERR_INVALID_HANDLE);
+
+ /*
+ * Create an instace of the passthru I/O stream.
+ */
+ PRTMANIFESTPTIOS pThis;
+ RTVFSIOSTREAM hVfsPtIos;
+ int rc = RTVfsNewIoStream(&g_rtManifestPassthruIosOps, sizeof(*pThis), fReadOrWrite ? RTFILE_O_READ : RTFILE_O_WRITE,
+ NIL_RTVFS, NIL_RTVFSLOCK, &hVfsPtIos, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIos;
+ pThis->pHashes = rtManifestHashesCreate(fAttrs);
+ pThis->offCurPos = offCurPos;
+ pThis->hManifest = hManifest;
+ pThis->fReadOrWrite = fReadOrWrite;
+ pThis->fAddedEntry = false;
+ pThis->pszEntry = RTStrDup(pszEntry);
+ if (pThis->pszEntry && pThis->pHashes)
+ {
+ *phVfsIosPassthru = hVfsPtIos;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsPtIos);
+ }
+ else
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ RTManifestRelease(hManifest);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTManifestPtIosAddEntryNow(RTVFSIOSTREAM hVfsPtIos)
+{
+ PRTMANIFESTPTIOS pThis = (PRTMANIFESTPTIOS)RTVfsIoStreamToPrivate(hVfsPtIos, &g_rtManifestPassthruIosOps);
+ AssertReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(!pThis->fAddedEntry, VERR_WRONG_ORDER);
+
+ pThis->fAddedEntry = true;
+ rtManifestHashesFinal(pThis->pHashes);
+ return rtManifestHashesSetAttrs(pThis->pHashes, pThis->hManifest, pThis->pszEntry);
+}
+
+
+RTDECL(bool) RTManifestPtIosIsInstanceOf(RTVFSIOSTREAM hVfsPtIos)
+{
+ if (hVfsPtIos != NIL_RTVFSIOSTREAM)
+ {
+ PRTMANIFESTPTIOS pThis = (PRTMANIFESTPTIOS)RTVfsIoStreamToPrivate(hVfsPtIos, &g_rtManifestPassthruIosOps);
+ if (pThis)
+ return true;
+ }
+ return false;
+}
+
+
+RTDECL(int) RTManifestEntryAddIoStream(RTMANIFEST hManifest, RTVFSIOSTREAM hVfsIos, const char *pszEntry, uint32_t fAttrs)
+{
+ /*
+ * Note! This is a convenicence function, so just use the available public
+ * methods to get the job done.
+ */
+ AssertReturn(fAttrs < RTMANIFEST_ATTR_END, VERR_INVALID_PARAMETER);
+ AssertPtr(pszEntry);
+
+ /*
+ * Allocate and initialize the hash contexts, hash digests and I/O buffer.
+ */
+ PRTMANIFESTHASHES pHashes = rtManifestHashesCreate(fAttrs);
+ if (!pHashes)
+ return VERR_NO_TMP_MEMORY;
+
+ int rc;
+ size_t cbBuf = _1M;
+ void *pvBuf = RTMemTmpAlloc(cbBuf);
+ if (RT_UNLIKELY(!pvBuf))
+ {
+ cbBuf = _4K;
+ pvBuf = RTMemTmpAlloc(cbBuf);
+ }
+ if (RT_LIKELY(pvBuf))
+ {
+ /*
+ * Process the stream data.
+ */
+ for (;;)
+ {
+ size_t cbRead;
+ rc = RTVfsIoStrmRead(hVfsIos, pvBuf, cbBuf, true /*fBlocking*/, &cbRead);
+ if ( (rc == VINF_EOF && cbRead == 0)
+ || RT_FAILURE(rc))
+ break;
+ rtManifestHashesUpdate(pHashes, pvBuf, cbRead);
+ }
+ RTMemTmpFree(pvBuf);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add the entry with the finalized hashes.
+ */
+ rtManifestHashesFinal(pHashes);
+ rc = RTManifestEntryAdd(hManifest, pszEntry);
+ if (RT_SUCCESS(rc))
+ rc = rtManifestHashesSetAttrs(pHashes, hManifest, pszEntry);
+ }
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+
+ rtManifestHashesDestroy(pHashes);
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/checksum/md2str.cpp b/src/VBox/Runtime/common/checksum/md2str.cpp
new file mode 100644
index 00000000..7c1e42d6
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/md2str.cpp
@@ -0,0 +1,59 @@
+/* $Id: md2str.cpp $ */
+/** @file
+ * IPRT - MD2 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/md2.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTMd2ToString(uint8_t const pabDigest[RTMD2_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTMD2_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTMd2FromString(char const *pszDigest, uint8_t pabDigest[RTMD2_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTMD2_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/checksum/md4str.cpp b/src/VBox/Runtime/common/checksum/md4str.cpp
new file mode 100644
index 00000000..615a718b
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/md4str.cpp
@@ -0,0 +1,59 @@
+/* $Id: md4str.cpp $ */
+/** @file
+ * IPRT - MD4 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/md4.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTMd4ToString(uint8_t const pabDigest[RTMD4_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTMD4_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTMd4FromString(char const *pszDigest, uint8_t pabDigest[RTMD4_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTMD4_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/checksum/md5str.cpp b/src/VBox/Runtime/common/checksum/md5str.cpp
new file mode 100644
index 00000000..08068c96
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/md5str.cpp
@@ -0,0 +1,59 @@
+/* $Id: md5str.cpp $ */
+/** @file
+ * IPRT - MD5 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/md5.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTMd5ToString(uint8_t const pabDigest[RTMD5_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTMD5_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTMd5FromString(char const *pszDigest, uint8_t pabDigest[RTMD5_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTMD5_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/checksum/openssl-md2.cpp b/src/VBox/Runtime/common/checksum/openssl-md2.cpp
new file mode 100644
index 00000000..cce35588
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/openssl-md2.cpp
@@ -0,0 +1,96 @@
+/* $Id: openssl-md2.cpp $ */
+/** @file
+ * IPRT - Message-Digest Algorithm 2.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+
+#include "internal/openssl-pre.h"
+#include <openssl/opensslconf.h>
+#ifndef OPENSSL_NO_MD2
+# include <openssl/md2.h>
+#endif
+#include "internal/openssl-post.h"
+
+#ifndef OPENSSL_NO_MD2
+# define RT_MD2_PRIVATE_CONTEXT
+# include <iprt/md2.h>
+
+# include <iprt/assert.h>
+
+AssertCompile(RT_SIZEOFMEMB(RTMD2CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTMD2CONTEXT, Private));
+
+
+RTDECL(void) RTMd2(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTMD2_HASH_SIZE])
+{
+ RTMD2CONTEXT Ctx;
+ RTMd2Init(&Ctx);
+ RTMd2Update(&Ctx, pvBuf, cbBuf);
+ RTMd2Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTMd2);
+
+
+RTDECL(void) RTMd2Init(PRTMD2CONTEXT pCtx)
+{
+ MD2_Init(&pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTMd2Init);
+
+
+RTDECL(void) RTMd2Update(PRTMD2CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ MD2_Update(&pCtx->Private, (const unsigned char *)pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTMd2Update);
+
+
+RTDECL(void) RTMd2Final(PRTMD2CONTEXT pCtx, uint8_t pabDigest[RTMD2_HASH_SIZE])
+{
+ MD2_Final((unsigned char *)&pabDigest[0], &pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTMd2Final);
+
+
+#else /* OPENSSL_NO_MD2 */
+/*
+ * If the OpenSSL build doesn't have MD2, use the IPRT implementation.
+ */
+# include "alt-md2.cpp"
+#endif /* OPENSSL_NO_MD2 */
+
diff --git a/src/VBox/Runtime/common/checksum/openssl-md4.cpp b/src/VBox/Runtime/common/checksum/openssl-md4.cpp
new file mode 100644
index 00000000..f670801e
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/openssl-md4.cpp
@@ -0,0 +1,94 @@
+/* $Id: openssl-md4.cpp $ */
+/** @file
+ * IPRT - Message-Digest Algorithm 4.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+
+#include "internal/openssl-pre.h"
+#include <openssl/opensslconf.h>
+#include "internal/openssl-post.h"
+#if 0 //ndef OPENSSL_NO_MD4
+# include <openssl/md4.h>
+
+# define RT_MD4_PRIVATE_CONTEXT
+# include <iprt/md4.h>
+
+# include <iprt/assert.h>
+
+AssertCompile(RT_SIZEOFMEMB(RTMD4CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTMD4CONTEXT, Private));
+
+
+RTDECL(void) RTMd4(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTMD4_HASH_SIZE])
+{
+ RTMD4CONTEXT Ctx;
+ RTMd4Init(&Ctx);
+ RTMd4Update(&Ctx, pvBuf, cbBuf);
+ RTMd4Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTMd4);
+
+
+RTDECL(void) RTMd4Init(PRTMD4CONTEXT pCtx)
+{
+ MD4_Init(&pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTMd4Init);
+
+
+RTDECL(void) RTMd4Update(PRTMD4CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ MD4_Update(&pCtx->Private, (const unsigned char *)pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTMd4Update);
+
+
+RTDECL(void) RTMd4Final(PRTMD4CONTEXT pCtx, uint8_t pabDigest[RTMD4_HASH_SIZE])
+{
+ MD4_Final((unsigned char *)&pabDigest[0], &pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTMd4Final);
+
+
+#else /* OPENSSL_NO_MD4 */
+/*
+ * If the OpenSSL build doesn't have MD4, use the IPRT implementation.
+ */
+# include "alt-md4.cpp"
+#endif /* OPENSSL_NO_MD4 */
+
diff --git a/src/VBox/Runtime/common/checksum/openssl-md5.cpp b/src/VBox/Runtime/common/checksum/openssl-md5.cpp
new file mode 100644
index 00000000..57f7b5a6
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/openssl-md5.cpp
@@ -0,0 +1,84 @@
+/* $Id: openssl-md5.cpp $ */
+/** @file
+ * IPRT - MD5 message digest functions, implemented using OpenSSL.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+
+#include "internal/openssl-pre.h"
+#include <openssl/md5.h>
+#include "internal/openssl-post.h"
+
+#define RT_MD5_OPENSSL_PRIVATE_CONTEXT
+#include <iprt/md5.h>
+
+#include <iprt/assert.h>
+
+AssertCompile(RT_SIZEOFMEMB(RTMD5CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTMD5CONTEXT, OsslPrivate));
+
+
+RTDECL(void) RTMd5(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTMD5_HASH_SIZE])
+{
+ RTMD5CONTEXT Ctx;
+ RTMd5Init(&Ctx);
+ RTMd5Update(&Ctx, pvBuf, cbBuf);
+ RTMd5Final(pabDigest, &Ctx);
+}
+RT_EXPORT_SYMBOL(RTMd5);
+
+
+RTDECL(void) RTMd5Init(PRTMD5CONTEXT pCtx)
+{
+ MD5_Init(&pCtx->OsslPrivate);
+}
+RT_EXPORT_SYMBOL(RTMd5Init);
+
+
+RTDECL(void) RTMd5Update(PRTMD5CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ MD5_Update(&pCtx->OsslPrivate, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTMd5Update);
+
+
+RTDECL(void) RTMd5Final(uint8_t pabDigest[32], PRTMD5CONTEXT pCtx)
+{
+ MD5_Final((unsigned char *)&pabDigest[0], &pCtx->OsslPrivate);
+}
+RT_EXPORT_SYMBOL(RTMd5Final);
+
diff --git a/src/VBox/Runtime/common/checksum/openssl-sha1.cpp b/src/VBox/Runtime/common/checksum/openssl-sha1.cpp
new file mode 100644
index 00000000..7caad607
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/openssl-sha1.cpp
@@ -0,0 +1,100 @@
+/* $Id: openssl-sha1.cpp $ */
+/** @file
+ * IPRT - SHA-1 hash functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+
+#include "internal/openssl-pre.h"
+#include <openssl/sha.h>
+#include "internal/openssl-post.h"
+
+#define RT_SHA1_PRIVATE_CONTEXT
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+
+AssertCompile(RT_SIZEOFMEMB(RTSHA1CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTSHA1CONTEXT, Private));
+
+
+RTDECL(void) RTSha1(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA1_HASH_SIZE])
+{
+ RTSHA1CONTEXT Ctx;
+ RTSha1Init(&Ctx);
+ RTSha1Update(&Ctx, pvBuf, cbBuf);
+ RTSha1Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha1);
+
+
+RTDECL(bool) RTSha1Check(const void *pvBuf, size_t cbBuf, uint8_t const pabDigest[RTSHA1_HASH_SIZE])
+{
+ RTSHA1CONTEXT Ctx;
+ RTSha1Init(&Ctx);
+ RTSha1Update(&Ctx, pvBuf, cbBuf);
+ uint8_t abActualDigest[RTSHA1_HASH_SIZE];
+ RTSha1Final(&Ctx, abActualDigest);
+ bool fRet = memcmp(pabDigest, abActualDigest, RTSHA1_HASH_SIZE) == 0;
+ RT_ZERO(abActualDigest);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha1Check);
+
+
+RTDECL(void) RTSha1Init(PRTSHA1CONTEXT pCtx)
+{
+ SHA1_Init(&pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha1Init);
+
+
+RTDECL(void) RTSha1Update(PRTSHA1CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ SHA1_Update(&pCtx->Private, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTSha1Update);
+
+
+RTDECL(void) RTSha1Final(PRTSHA1CONTEXT pCtx, uint8_t pabDigest[RTSHA1_HASH_SIZE])
+{
+ SHA1_Final((unsigned char *)&pabDigest[0], &pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha1Final);
+
diff --git a/src/VBox/Runtime/common/checksum/openssl-sha256.cpp b/src/VBox/Runtime/common/checksum/openssl-sha256.cpp
new file mode 100644
index 00000000..81659680
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/openssl-sha256.cpp
@@ -0,0 +1,150 @@
+/* $Id: openssl-sha256.cpp $ */
+/** @file
+ * IPRT - SHA-256 hash functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+
+#include "internal/openssl-pre.h"
+#include <openssl/sha.h>
+#include "internal/openssl-post.h"
+
+#define RT_SHA256_PRIVATE_CONTEXT
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+
+AssertCompile(RT_SIZEOFMEMB(RTSHA256CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTSHA256CONTEXT, Private));
+
+
+RTDECL(void) RTSha256(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA256_HASH_SIZE])
+{
+ RTSHA256CONTEXT Ctx;
+ RTSha256Init(&Ctx);
+ RTSha256Update(&Ctx, pvBuf, cbBuf);
+ RTSha256Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha256);
+
+
+RTDECL(bool) RTSha256Check(const void *pvBuf, size_t cbBuf, uint8_t const pabDigest[RTSHA256_HASH_SIZE])
+{
+ RTSHA256CONTEXT Ctx;
+ RTSha256Init(&Ctx);
+ RTSha256Update(&Ctx, pvBuf, cbBuf);
+ uint8_t abActualDigest[RTSHA256_HASH_SIZE];
+ RTSha256Final(&Ctx, abActualDigest);
+ bool fRet = memcmp(pabDigest, abActualDigest, RTSHA256_HASH_SIZE) == 0;
+ RT_ZERO(abActualDigest);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha256Check);
+
+
+RTDECL(void) RTSha256Init(PRTSHA256CONTEXT pCtx)
+{
+ SHA256_Init(&pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha256Init);
+
+
+RTDECL(void) RTSha256Update(PRTSHA256CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ SHA256_Update(&pCtx->Private, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTSha256Update);
+
+
+RTDECL(void) RTSha256Final(PRTSHA256CONTEXT pCtx, uint8_t pabDigest[32])
+{
+ SHA256_Final((unsigned char *)&pabDigest[0], &pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha256Final);
+
+
+/*
+ * We have to expose the same API as alt-sha256.cpp, so the SHA-224
+ * implementation also lives here. (SHA-224 is just a truncated SHA-256 with
+ * different initial values.)
+ */
+RTDECL(void) RTSha224(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA224_HASH_SIZE])
+{
+ RTSHA224CONTEXT Ctx;
+ RTSha224Init(&Ctx);
+ RTSha224Update(&Ctx, pvBuf, cbBuf);
+ RTSha224Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha224);
+
+
+RTDECL(bool) RTSha224Check(const void *pvBuf, size_t cbBuf, uint8_t const pabDigest[RTSHA224_HASH_SIZE])
+{
+ RTSHA224CONTEXT Ctx;
+ RTSha224Init(&Ctx);
+ RTSha224Update(&Ctx, pvBuf, cbBuf);
+ uint8_t abActualDigest[RTSHA224_HASH_SIZE];
+ RTSha224Final(&Ctx, abActualDigest);
+ bool fRet = memcmp(pabDigest, abActualDigest, RTSHA224_HASH_SIZE) == 0;
+ RT_ZERO(abActualDigest);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha224Check);
+
+
+RTDECL(void) RTSha224Init(PRTSHA224CONTEXT pCtx)
+{
+ SHA224_Init(&pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha224Init);
+
+
+RTDECL(void) RTSha224Update(PRTSHA224CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ SHA224_Update(&pCtx->Private, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTSha224Update);
+
+
+RTDECL(void) RTSha224Final(PRTSHA224CONTEXT pCtx, uint8_t pabDigest[32])
+{
+ SHA224_Final((unsigned char *)&pabDigest[0], &pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha224Final);
+
diff --git a/src/VBox/Runtime/common/checksum/openssl-sha3.cpp b/src/VBox/Runtime/common/checksum/openssl-sha3.cpp
new file mode 100644
index 00000000..9302b53f
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/openssl-sha3.cpp
@@ -0,0 +1,270 @@
+/* $Id: openssl-sha3.cpp $ */
+/** @file
+ * IPRT - SHA-3 hash functions, OpenSSL based implementation.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#if 1 /* For now: */
+# include "alt-sha3.cpp"
+
+#else
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include "internal/openssl-pre.h"
+#include <openssl/evp.h>
+#include "internal/openssl-post.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTSHA3PRIVATECTX_MAGIC UINT64_C(0xb6362d323c56b758)
+#define RTSHA3PRIVATECTX_MAGIC_FINAL UINT64_C(0x40890fe0e474215d)
+#define RTSHA3PRIVATECTX_MAGIC_DEAD UINT64_C(0xdead7a05081cbeef)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/* Internal EVP structure that we fake here to avoid lots of casting. */
+struct evp_md_ctx_st
+{
+ void *apvWhatever[10];
+};
+
+/** The OpenSSL private context structure. */
+typedef struct RTSHA3PRIVATECTX
+{
+ /** RTSHA3PRIVATECTX_MAGIC / RTSHA3PRIVATECTX_MAGIC_FINAL / RTSHA3PRIVATECTX_MAGIC_DEAD */
+ uint64_t u64Magic;
+ /** The OpenSSL context. We cheat to avoid EVP_MD_CTX_new/free. */
+ struct evp_md_ctx_st MdCtx;
+} RTSHA3PRIVATECTX;
+
+#define RT_SHA3_PRIVATE_CONTEXT
+#include <iprt/sha.h>
+AssertCompile(RT_SIZEOFMEMB(RTSHA3CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTSHA3CONTEXT, Private));
+
+
+
+static int rtSha3Init(PRTSHA3CONTEXT pCtx, const EVP_MD *pMdType)
+{
+ RT_ZERO(*pCtx); /* This is what EVP_MD_CTX_new does. */
+ pCtx->Private.u64Magic = RTSHA3PRIVATECTX_MAGIC;
+
+ AssertReturnStmt(EVP_DigestInit_ex(&pCtx->Private.MdCtx, pMdType, NULL /*engine*/),
+ pCtx->Private.u64Magic = RTSHA3PRIVATECTX_MAGIC_DEAD,
+ VERR_CR_DIGEST_OSSL_DIGEST_INIT_ERROR);
+ return VINF_SUCCESS;
+}
+
+
+static int rtSha3Update(PRTSHA3CONTEXT pCtx, uint8_t const *pbData, size_t cbData)
+{
+ AssertMsgReturn(pCtx->Private.u64Magic == RTSHA3PRIVATECTX_MAGIC, ("u64Magic=%RX64\n", pCtx->Private.u64Magic),
+ VERR_INVALID_CONTEXT);
+ AssertReturn(EVP_DigestUpdate(&pCtx->Private.MdCtx, pbData, cbData), VERR_GENERAL_FAILURE);
+ return VINF_SUCCESS;
+}
+
+
+static int rtSha3Final(PRTSHA3CONTEXT pCtx, uint8_t *pbDigest, size_t cbDigest)
+{
+ RT_BZERO(pbDigest, cbDigest);
+ AssertMsgReturn(pCtx->Private.u64Magic == RTSHA3PRIVATECTX_MAGIC, ("u64Magic=%RX64\n", pCtx->Private.u64Magic),
+ VERR_INVALID_CONTEXT);
+ AssertReturn(EVP_DigestFinal_ex(&pCtx->Private.MdCtx, pbDigest, NULL), VERR_GENERAL_FAILURE);
+
+ /* Implicit cleanup. */
+ EVP_MD_CTX_reset(&pCtx->Private.MdCtx);
+ pCtx->Private.u64Magic = RTSHA3PRIVATECTX_MAGIC_FINAL;
+ return VINF_SUCCESS;
+}
+
+
+static int rtSha3Cleanup(PRTSHA3CONTEXT pCtx)
+{
+ if (pCtx)
+ {
+ if (pCtx->Private.u64Magic == RTSHA3PRIVATECTX_MAGIC_FINAL)
+ { /* likely */ }
+ else if (pCtx->Private.u64Magic == RTSHA3PRIVATECTX_MAGIC)
+ EVP_MD_CTX_reset(&pCtx->Private.MdCtx);
+ else
+ AssertMsgFailedReturn(("u64Magic=%RX64\n", pCtx->Private.u64Magic), VERR_INVALID_CONTEXT);
+ pCtx->Private.u64Magic = RTSHA3PRIVATECTX_MAGIC_DEAD;
+ }
+ return VINF_SUCCESS;
+}
+
+
+static int rtSha3Clone(PRTSHA3CONTEXT pCtx, RTSHA3CONTEXT const *pCtxSrc)
+{
+ Assert(pCtx->Private.u64Magic != RTSHA3PRIVATECTX_MAGIC);
+ RT_ZERO(*pCtx); /* This is what EVP_MD_CTX_new does. */
+
+ AssertReturn(pCtxSrc->Private.u64Magic == RTSHA3PRIVATECTX_MAGIC, VERR_INVALID_CONTEXT);
+
+ pCtx->Private.u64Magic = RTSHA3PRIVATECTX_MAGIC;
+ AssertReturnStmt(EVP_MD_CTX_copy_ex(&pCtx->Private.MdCtx, &pCtxSrc->Private.MdCtx),
+ pCtx->Private.u64Magic = RTSHA3PRIVATECTX_MAGIC_DEAD,
+ VERR_CR_DIGEST_OSSL_DIGEST_CTX_COPY_ERROR);
+ return VINF_SUCCESS;
+}
+
+
+static int rtSha3(const void *pvData, size_t cbData, const EVP_MD *pMdType, uint8_t *pabHash, size_t cbHash)
+{
+ RT_BZERO(pabHash, cbHash);
+
+ int rc;
+ EVP_MD_CTX *pCtx = EVP_MD_CTX_new();
+ if (pCtx)
+ {
+ if (EVP_DigestInit_ex(pCtx, pMdType, NULL /*engine*/))
+ {
+ if (EVP_DigestUpdate(pCtx, pvData, cbData))
+ {
+ if (EVP_DigestFinal_ex(pCtx, pabHash, NULL))
+ rc = VINF_SUCCESS;
+ else
+ AssertFailedStmt(rc = VERR_GENERAL_FAILURE);
+ }
+ else
+ AssertFailedStmt(rc = VERR_GENERAL_FAILURE);
+ }
+ else
+ AssertFailedStmt(rc = VERR_CR_DIGEST_OSSL_DIGEST_INIT_ERROR);
+ EVP_MD_CTX_free(pCtx);
+ }
+ else
+ AssertFailedStmt(rc = VERR_NO_MEMORY);
+ return rc;
+}
+
+
+static bool rtSha3Check(const void *pvData, size_t cbData, const EVP_MD *pMdType,
+ const uint8_t *pabHash, uint8_t *pabHashTmp, size_t cbHash)
+{
+ int rc = rtSha3(pvData, cbData, pMdType, pabHashTmp, cbHash);
+ return RT_SUCCESS(rc) && memcmp(pabHash, pabHashTmp, cbHash) == 0;
+}
+
+
+/** Macro for declaring the interface for a SHA3 variation.
+ * @internal */
+#define RTSHA3_DEFINE_VARIANT(a_cBits, a_pMdType) \
+AssertCompile((a_cBits / 8) == RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)); \
+\
+RTDECL(int) RT_CONCAT(RTSha3t,a_cBits)(const void *pvBuf, size_t cbBuf, uint8_t pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)]) \
+{ \
+ return rtSha3(pvBuf, cbBuf, a_pMdType, pabHash, (a_cBits) / 8); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT(RTSha3t,a_cBits)); \
+\
+\
+RTDECL(bool) RT_CONCAT3(RTSha3t,a_cBits,Check)(const void *pvBuf, size_t cbBuf, \
+ uint8_t const pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)]) \
+{ \
+ uint8_t abHashTmp[(a_cBits) / 8]; \
+ return rtSha3Check(pvBuf, cbBuf, a_pMdType, pabHash, abHashTmp, (a_cBits) / 8); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Check)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Init)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx) \
+{ \
+ return rtSha3Init(&pCtx->Sha3, a_pMdType); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Init)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Update)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx, const void *pvBuf, size_t cbBuf) \
+{ \
+ return rtSha3Update(&pCtx->Sha3, (uint8_t const *)pvBuf, cbBuf); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Update)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Final)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx, \
+ uint8_t pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)]) \
+{ \
+ return rtSha3Final(&pCtx->Sha3, pabHash, (a_cBits) / 8); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Final)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Cleanup)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx) \
+{ \
+ return rtSha3Cleanup(&pCtx->Sha3); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Cleanup)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,Clone)(RT_CONCAT3(PRTSHA3T,a_cBits,CONTEXT) pCtx, \
+ RT_CONCAT3(RTSHA3T,a_cBits,CONTEXT) const *pCtxSrc) \
+{ \
+ return rtSha3Clone(&pCtx->Sha3, &pCtxSrc->Sha3); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,Clone)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,ToString)(uint8_t const pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)], \
+ char *pszDigest, size_t cchDigest) \
+{ \
+ return RTStrPrintHexBytes(pszDigest, cchDigest, pabHash, (a_cBits) / 8, 0 /*fFlags*/); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,ToString)); \
+\
+\
+RTDECL(int) RT_CONCAT3(RTSha3t,a_cBits,FromString)(char const *pszDigest, uint8_t pabHash[RT_CONCAT3(RTSHA3_,a_cBits,_HASH_SIZE)]) \
+{ \
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabHash[0], (a_cBits) / 8, 0 /*fFlags*/); \
+} \
+RT_EXPORT_SYMBOL(RT_CONCAT3(RTSha3t,a_cBits,FromString))
+
+
+RTSHA3_DEFINE_VARIANT(224, EVP_sha3_224());
+RTSHA3_DEFINE_VARIANT(256, EVP_sha3_256());
+RTSHA3_DEFINE_VARIANT(384, EVP_sha3_384());
+RTSHA3_DEFINE_VARIANT(512, EVP_sha3_512());
+
+#endif /* !alt-sha3.cpp */
diff --git a/src/VBox/Runtime/common/checksum/openssl-sha512.cpp b/src/VBox/Runtime/common/checksum/openssl-sha512.cpp
new file mode 100644
index 00000000..37a80bec
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/openssl-sha512.cpp
@@ -0,0 +1,151 @@
+/* $Id: openssl-sha512.cpp $ */
+/** @file
+ * IPRT - SHA-512 hash functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+
+#include "internal/openssl-pre.h"
+#include <openssl/sha.h>
+#include "internal/openssl-post.h"
+
+#define RT_SHA512_PRIVATE_CONTEXT
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+
+AssertCompile(RT_SIZEOFMEMB(RTSHA512CONTEXT, abPadding) >= RT_SIZEOFMEMB(RTSHA512CONTEXT, Private));
+
+
+RTDECL(void) RTSha512(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA512_HASH_SIZE])
+{
+ RTSHA512CONTEXT Ctx;
+ RTSha512Init(&Ctx);
+ RTSha512Update(&Ctx, pvBuf, cbBuf);
+ RTSha512Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha512);
+
+
+RTDECL(bool) RTSha512Check(const void *pvBuf, size_t cbBuf, uint8_t const pabDigest[RTSHA512_HASH_SIZE])
+{
+ RTSHA512CONTEXT Ctx;
+ RTSha512Init(&Ctx);
+ RTSha512Update(&Ctx, pvBuf, cbBuf);
+ uint8_t abActualDigest[RTSHA512_HASH_SIZE];
+ RTSha512Final(&Ctx, abActualDigest);
+ bool fRet = memcmp(pabDigest, abActualDigest, RTSHA512_HASH_SIZE) == 0;
+ RT_ZERO(abActualDigest);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha512Check);
+
+
+RTDECL(void) RTSha512Init(PRTSHA512CONTEXT pCtx)
+{
+ SHA512_Init(&pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha512Init);
+
+
+RTDECL(void) RTSha512Update(PRTSHA512CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ SHA512_Update(&pCtx->Private, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTSha512Update);
+
+
+RTDECL(void) RTSha512Final(PRTSHA512CONTEXT pCtx, uint8_t pabDigest[32])
+{
+ SHA512_Final((unsigned char *)&pabDigest[0], &pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha512Final);
+
+
+/*
+ * We have to expose the same API as alt-sha512.cpp, so the SHA-384,
+ * SHA-512/224 and SHA-512/256 implementations also live here. (They are all
+ * just truncted SHA-512 with different initial values.)
+ */
+
+RTDECL(void) RTSha384(const void *pvBuf, size_t cbBuf, uint8_t pabDigest[RTSHA384_HASH_SIZE])
+{
+ RTSHA384CONTEXT Ctx;
+ RTSha384Init(&Ctx);
+ RTSha384Update(&Ctx, pvBuf, cbBuf);
+ RTSha384Final(&Ctx, pabDigest);
+}
+RT_EXPORT_SYMBOL(RTSha384);
+
+
+RTDECL(bool) RTSha384Check(const void *pvBuf, size_t cbBuf, uint8_t const pabDigest[RTSHA384_HASH_SIZE])
+{
+ RTSHA384CONTEXT Ctx;
+ RTSha384Init(&Ctx);
+ RTSha384Update(&Ctx, pvBuf, cbBuf);
+ uint8_t abActualDigest[RTSHA384_HASH_SIZE];
+ RTSha384Final(&Ctx, abActualDigest);
+ bool fRet = memcmp(pabDigest, abActualDigest, RTSHA384_HASH_SIZE) == 0;
+ RT_ZERO(abActualDigest);
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTSha384Check);
+
+
+RTDECL(void) RTSha384Init(PRTSHA384CONTEXT pCtx)
+{
+ SHA384_Init(&pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha384Init);
+
+
+RTDECL(void) RTSha384Update(PRTSHA384CONTEXT pCtx, const void *pvBuf, size_t cbBuf)
+{
+ SHA384_Update(&pCtx->Private, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTSha384Update);
+
+
+RTDECL(void) RTSha384Final(PRTSHA384CONTEXT pCtx, uint8_t pabDigest[32])
+{
+ SHA384_Final((unsigned char *)&pabDigest[0], &pCtx->Private);
+}
+RT_EXPORT_SYMBOL(RTSha384Final);
+
diff --git a/src/VBox/Runtime/common/checksum/sha1str.cpp b/src/VBox/Runtime/common/checksum/sha1str.cpp
new file mode 100644
index 00000000..39574cde
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/sha1str.cpp
@@ -0,0 +1,59 @@
+/* $Id: sha1str.cpp $ */
+/** @file
+ * IPRT - SHA-1 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTSha1ToString(uint8_t const pabDigest[RTSHA1_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTSHA1_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTSha1FromString(char const *pszDigest, uint8_t pabDigest[RTSHA1_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTSHA1_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/checksum/sha224str.cpp b/src/VBox/Runtime/common/checksum/sha224str.cpp
new file mode 100644
index 00000000..0c5bdc04
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/sha224str.cpp
@@ -0,0 +1,59 @@
+/* $Id: sha224str.cpp $ */
+/** @file
+ * IPRT - SHA-224 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTSha224ToString(uint8_t const pabDigest[RTSHA224_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTSHA224_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTSha224FromString(char const *pszDigest, uint8_t pabDigest[RTSHA224_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTSHA224_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/checksum/sha256str.cpp b/src/VBox/Runtime/common/checksum/sha256str.cpp
new file mode 100644
index 00000000..bcd4284d
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/sha256str.cpp
@@ -0,0 +1,59 @@
+/* $Id: sha256str.cpp $ */
+/** @file
+ * IPRT - SHA-256 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTSha256ToString(uint8_t const pabDigest[RTSHA256_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTSHA256_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTSha256FromString(char const *pszDigest, uint8_t pabDigest[RTSHA256_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTSHA256_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/checksum/sha384str.cpp b/src/VBox/Runtime/common/checksum/sha384str.cpp
new file mode 100644
index 00000000..29fe4b5e
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/sha384str.cpp
@@ -0,0 +1,59 @@
+/* $Id: sha384str.cpp $ */
+/** @file
+ * IPRT - SHA-384 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTSha384ToString(uint8_t const pabDigest[RTSHA384_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTSHA384_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTSha384FromString(char const *pszDigest, uint8_t pabDigest[RTSHA384_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTSHA384_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/checksum/sha512str.cpp b/src/VBox/Runtime/common/checksum/sha512str.cpp
new file mode 100644
index 00000000..35b1d5ea
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/sha512str.cpp
@@ -0,0 +1,59 @@
+/* $Id: sha512str.cpp $ */
+/** @file
+ * IPRT - SHA-512 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTSha512ToString(uint8_t const pabDigest[RTSHA512_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTSHA512_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTSha512FromString(char const *pszDigest, uint8_t pabDigest[RTSHA512_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTSHA512_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/checksum/sha512t224str.cpp b/src/VBox/Runtime/common/checksum/sha512t224str.cpp
new file mode 100644
index 00000000..eb237588
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/sha512t224str.cpp
@@ -0,0 +1,59 @@
+/* $Id: sha512t224str.cpp $ */
+/** @file
+ * IPRT - SHA-512/224 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTSha512t224ToString(uint8_t const pabDigest[RTSHA512T224_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTSHA512T224_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTSha512t224FromString(char const *pszDigest, uint8_t pabDigest[RTSHA512T224_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTSHA512T224_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/checksum/sha512t256str.cpp b/src/VBox/Runtime/common/checksum/sha512t256str.cpp
new file mode 100644
index 00000000..c95cf752
--- /dev/null
+++ b/src/VBox/Runtime/common/checksum/sha512t256str.cpp
@@ -0,0 +1,59 @@
+/* $Id: sha512t256str.cpp $ */
+/** @file
+ * IPRT - SHA-512/256 string functions.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/sha.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTSha512t256ToString(uint8_t const pabDigest[RTSHA512T256_HASH_SIZE], char *pszDigest, size_t cchDigest)
+{
+ return RTStrPrintHexBytes(pszDigest, cchDigest, &pabDigest[0], RTSHA512T256_HASH_SIZE, 0 /*fFlags*/);
+}
+
+
+RTDECL(int) RTSha512t256FromString(char const *pszDigest, uint8_t pabDigest[RTSHA512T256_HASH_SIZE])
+{
+ return RTStrConvertHexBytes(RTStrStripL(pszDigest), &pabDigest[0], RTSHA512T256_HASH_SIZE, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/atexit-vcc.cpp b/src/VBox/Runtime/common/compiler/vcc/atexit-vcc.cpp
new file mode 100644
index 00000000..f6fad758
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/atexit-vcc.cpp
@@ -0,0 +1,145 @@
+/* $Id: atexit-vcc.cpp $ */
+/** @file
+ * IPRT - Visual C++ Compiler - Simple atexit implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/nocrt/stdlib.h>
+
+#include "internal/compiler-vcc.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTNOCRTATEXITCHUNK
+{
+ PFNRTNOCRTATEXITCALLBACK apfnCallbacks[256];
+} RTNOCRTATEXITCHUNK;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The first atexit() registration chunk. */
+static RTNOCRTATEXITCHUNK g_aAtExitPrealloc;
+/** Array of atexit() callback chunk pointers. */
+static RTNOCRTATEXITCHUNK *g_apAtExit[8192 / 256] = { &g_aAtExitPrealloc, };
+/** Chunk and callback index in one. */
+static volatile uint32_t g_idxNextAtExit = 0;
+
+
+/* Note! not using atexit here because it'll clash with built-in prototype. */
+extern "C" int nocrt_atexit(PFNRTNOCRTATEXITCALLBACK pfnCallback) RT_NOEXCEPT
+{
+ AssertPtr(pfnCallback);
+
+ /*
+ * Allocate a table index.
+ */
+ uint32_t idx = ASMAtomicIncU32(&g_idxNextAtExit) - 1;
+ AssertReturnStmt(idx < RT_ELEMENTS(g_apAtExit) * RT_ELEMENTS(g_apAtExit[0]->apfnCallbacks),
+ ASMAtomicDecU32(&g_idxNextAtExit), -1);
+
+ /*
+ * Make sure the table chunk is there.
+ */
+ uint32_t idxChunk = idx / RT_ELEMENTS(g_apAtExit[0]->apfnCallbacks);
+ RTNOCRTATEXITCHUNK *pChunk = ASMAtomicReadPtrT(&g_apAtExit[idxChunk], RTNOCRTATEXITCHUNK *);
+ if (!pChunk)
+ {
+ pChunk = (RTNOCRTATEXITCHUNK *)RTMemAllocZ(sizeof(*pChunk)); /* ASSUMES that the allocator works w/o initialization! */
+ AssertReturn(pChunk, -1); /* don't try decrement, someone could be racing us... */
+
+ if (!ASMAtomicCmpXchgPtr(&g_apAtExit[idxChunk], pChunk, NULL))
+ {
+ RTMemFree(pChunk);
+
+ pChunk = ASMAtomicReadPtrT(&g_apAtExit[idxChunk], RTNOCRTATEXITCHUNK *);
+ Assert(pChunk);
+ }
+ }
+
+ /*
+ * Add our callback.
+ */
+ pChunk->apfnCallbacks[idxChunk % RT_ELEMENTS(pChunk->apfnCallbacks)] = pfnCallback;
+ return 0;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(atexit);
+
+
+void rtVccTermRunAtExit(void) RT_NOEXCEPT
+{
+ uint32_t idxAtExit = ASMAtomicReadU32(&g_idxNextAtExit);
+ if (idxAtExit-- > 0)
+ {
+ uint32_t idxChunk = idxAtExit / RT_ELEMENTS(g_apAtExit[0]->apfnCallbacks);
+ uint32_t idxCallback = idxAtExit % RT_ELEMENTS(g_apAtExit[0]->apfnCallbacks);
+ for (;;)
+ {
+ RTNOCRTATEXITCHUNK *pChunk = ASMAtomicReadPtrT(&g_apAtExit[idxChunk], RTNOCRTATEXITCHUNK *);
+ if (pChunk)
+ {
+ do
+ {
+ g_idxNextAtExit = idxAtExit--; /* Make sure we don't try problematic atexit callbacks! */
+
+ PFNRTNOCRTATEXITCALLBACK pfnCallback = pChunk->apfnCallbacks[idxCallback];
+ if (pfnCallback) /* Can be NULL see registration code */
+ {
+ pfnCallback();
+ pChunk->apfnCallbacks[idxCallback] = NULL;
+ }
+ } while (idxCallback-- > 0);
+ }
+ else
+ idxAtExit -= RT_ELEMENTS(g_apAtExit[0]->apfnCallbacks);
+ if (idxChunk == 0)
+ break;
+ idxChunk--;
+ idxCallback = RT_ELEMENTS(g_apAtExit[0]->apfnCallbacks) - 1;
+ }
+
+ g_idxNextAtExit = 0;
+ }
+}
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/except-seh-vcc.cpp b/src/VBox/Runtime/common/compiler/vcc/except-seh-vcc.cpp
new file mode 100644
index 00000000..3a873673
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/except-seh-vcc.cpp
@@ -0,0 +1,243 @@
+/* $Id: except-seh-vcc.cpp $ */
+/** @file
+ * IPRT - Visual C++ Compiler - SEH exception handler (__try/__except/__finally).
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/nocrt.h"
+
+#include "except-vcc.h"
+
+
+#if !defined(RT_ARCH_AMD64)
+# error "This file is for AMD64 (and probably ARM, but needs porting)"
+#endif
+
+
+/**
+ * Calls an exception filter w/o doing any control guard checks.
+ *
+ * Doing this within an inline function to prevent disabling CFG for any other
+ * calls that __C_specific_handler might be doing.
+ *
+ * Presumably, the presumption here is that since the target address here is
+ * taken from tables assumed to be readonly and generated by the compiler, there
+ * is no need to do any CFG checks. Besides, the target isn't a function that
+ * is safe to be called out of context and thus doesn't belong in the CFG tables
+ * in any way.
+ */
+__declspec(guard(ignore))
+DECLINLINE(LONG) CallFilterFunction(PEXCEPTION_FILTER pfnFilter, PEXCEPTION_POINTERS pXcptPtrs,
+ PEXCEPTION_REGISTRATION_RECORD pXcptRegRec)
+{
+ return pfnFilter(pXcptPtrs, pXcptRegRec);
+}
+
+
+/**
+ * Calls an exception finally block w/o doing any control guard checks.
+ *
+ * See CallFilterFunction for details.
+ */
+__declspec(guard(ignore))
+DECLINLINE(void) CallFinallyFunction(PTERMINATION_HANDLER const pfnTermHandler, BOOLEAN fAbend,
+ PEXCEPTION_REGISTRATION_RECORD pXcptRegRec)
+{
+ pfnTermHandler(fAbend, pXcptRegRec);
+}
+
+
+/**
+ * Call exception filters, handlers and unwind code.
+ *
+ * This is called for windows' structured exception handling (SEH), i.e. the
+ * __try/__except/__finally stuff in Visual C++. The compiler generate scope
+ * records for the __try/__except blocks as well as unwind records for __finally
+ * and probably C++ stack object destructors.
+ *
+ * @returns Exception disposition.
+ * @param pXcptRec The exception record.
+ * @param pXcptRegRec The exception registration record, taken to be the frame
+ * address.
+ * @param pCpuCtx The CPU context for the exception.
+ * @param pDispCtx Dispatcher context.
+ */
+EXCEPTION_DISPOSITION __C_specific_handler(PEXCEPTION_RECORD pXcptRec, PEXCEPTION_REGISTRATION_RECORD pXcptRegRec,
+ PCONTEXT pCpuCtx, PDISPATCHER_CONTEXT pDispCtx)
+{
+ /*
+ * This function works the scope table, starting at ScopeIndex
+ * from the dispatcher context.
+ */
+ SCOPE_TABLE const * const pScopeTab = (SCOPE_TABLE const *)pDispCtx->HandlerData;
+ uint32_t const cScopes = pScopeTab->Count;
+ uint32_t idxScope = pDispCtx->ScopeIndex;
+
+ /*
+ * The table addresses are RVAs, so convert the program counter (PC) to an RVA.
+ */
+ uint32_t const uRvaPc = pDispCtx->ControlPc - pDispCtx->ImageBase;
+
+ /*
+ * Before we get any further, there are two types of scope records:
+ * 1. Unwind (aka termination) handler (JumpTarget == 0).
+ * 2. Exception filter & handler (JumpTarget != 0).
+ */
+
+ if (IS_DISPATCHING(pXcptRec->ExceptionFlags))
+ {
+ /*
+ * Call exception filter functions when dispatching.
+ */
+ for (; idxScope < cScopes; idxScope++)
+ {
+ /* Skip unwind entries (exception handler set to zero). */
+ uint32_t const uXcptHandler = pScopeTab->ScopeRecord[idxScope].JumpTarget;
+ if (uXcptHandler != 0)
+ {
+ uint32_t const uBegin = pScopeTab->ScopeRecord[idxScope].BeginAddress;
+ uint32_t const uEnd = pScopeTab->ScopeRecord[idxScope].EndAddress;
+ uint32_t const cbScope = uEnd - uBegin;
+ if ( uRvaPc - uBegin < cbScope
+ && uBegin < uEnd /* paranoia */)
+ {
+ /* The special HandlerAddress value 1 translates to a
+ EXCEPTION_EXECUTE_HANDLER filter return value. */
+ LONG lRet = EXCEPTION_EXECUTE_HANDLER;
+ uint32_t const uFltTermHandler = pScopeTab->ScopeRecord[idxScope].HandlerAddress;
+ if (uFltTermHandler != 1)
+ {
+ PEXCEPTION_FILTER const pfnFilter = (PEXCEPTION_FILTER)(pDispCtx->ImageBase + uFltTermHandler);
+ EXCEPTION_POINTERS XcptPtrs = { pXcptRec, pCpuCtx };
+ lRet = CallFilterFunction(pfnFilter, &XcptPtrs, pXcptRegRec);
+
+ AssertCompile(EXCEPTION_CONTINUE_SEARCH == 0);
+ if (lRet == EXCEPTION_CONTINUE_SEARCH)
+ continue;
+ }
+
+ /* Return if we're supposed to continue execution (the convention
+ it to match negative values rather than the exact defined value): */
+ AssertCompile(EXCEPTION_CONTINUE_EXECUTION == -1);
+ AssertCompile(EXCEPTION_EXECUTE_HANDLER == 1);
+ if (lRet <= EXCEPTION_CONTINUE_EXECUTION)
+ return ExceptionContinueExecution;
+
+ /* Execute the handler (lRet >= EXCEPTION_EXECUTE_HANDLER). */
+ uintptr_t const uPtrXcptHandler = uXcptHandler + pDispCtx->ImageBase;
+ /** @todo shouldn't we do a guard check on this call? */
+
+ /// @todo _NLG_Notify(uPtrXcptHandler, pXcptRegRec, 1); - debugger notification.
+
+ RtlUnwindEx(pXcptRegRec, (void *)uPtrXcptHandler, pXcptRec,
+ (PVOID)(uintptr_t)pXcptRec->ExceptionCode, pCpuCtx, pDispCtx->HistoryTable);
+
+ /// @todo _NLG_Return2(); - debugger notification.
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Do unwinding.
+ */
+
+ /* Convert the target unwind address to an RVA up front for efficiency.
+ (I think target unwinding is what the RtlUnwindEx call above does.) */
+ uint32_t const uTargetPc = pXcptRec->ExceptionFlags & EXCEPTION_TARGET_UNWIND
+ ? pDispCtx->TargetIp - pDispCtx->ImageBase
+ : UINT32_MAX;
+ //RTAssertMsg2("__C_specific_handler: unwind: idxScope=%#x cScopes=%#x uTargetPc=%#x fXcpt=%#x\n", idxScope, cScopes, uTargetPc, pXcptRec->ExceptionFlags);
+
+ for (; idxScope < cScopes; idxScope++)
+ {
+ uint32_t const uBegin = pScopeTab->ScopeRecord[idxScope].BeginAddress;
+ uint32_t const uEnd = pScopeTab->ScopeRecord[idxScope].EndAddress;
+ uint32_t const cbScope = uEnd - uBegin;
+ if ( uRvaPc - uBegin < cbScope
+ && uBegin < uEnd /* paranoia */)
+ {
+ uint32_t const uFltTermHandler = pScopeTab->ScopeRecord[idxScope].HandlerAddress;
+ uint32_t const uXcptHandler = pScopeTab->ScopeRecord[idxScope].JumpTarget;
+
+ /* Target unwind requires us to stop if the target PC is in the same
+ scope as the control PC. Happens for goto out of inner __try scope
+ or when longjmp'ing into a __try scope. */
+ if (pXcptRec->ExceptionFlags & EXCEPTION_TARGET_UNWIND)
+ {
+ /* The scope same-ness is identified by the same handler and jump target rva values. */
+ for (uint32_t idxTgtScope = 0; idxTgtScope < cScopes; idxTgtScope++)
+ if ( pScopeTab->ScopeRecord[idxTgtScope].JumpTarget == uXcptHandler
+ && pScopeTab->ScopeRecord[idxTgtScope].HandlerAddress == uFltTermHandler)
+ {
+ uint32_t const uTgtBegin = pScopeTab->ScopeRecord[idxTgtScope].BeginAddress;
+ uint32_t const uTgtEnd = pScopeTab->ScopeRecord[idxTgtScope].EndAddress;
+ uint32_t const cbTgtScope = uTgtEnd - uTgtBegin;
+ if ( uTargetPc - uTgtBegin < cbTgtScope
+ && uTgtBegin < uTgtEnd /* paranoia */)
+ {
+ //RTAssertMsg2("__C_specific_handler: ExceptionContinueSearch (#1)\n");
+ return ExceptionContinueSearch;
+ }
+ }
+ }
+
+ /* The unwind handlers are what we're here for. */
+ if (uXcptHandler == 0)
+ {
+ PTERMINATION_HANDLER const pfnTermHandler = (PTERMINATION_HANDLER)(pDispCtx->ImageBase + uFltTermHandler);
+ pDispCtx->ScopeIndex = idxScope + 1;
+ //RTAssertMsg2("__C_specific_handler: Calling __finally %p (idxScope=%#x)\n", pfnTermHandler, idxScope);
+ CallFinallyFunction(pfnTermHandler, TRUE /*fAbend*/, pXcptRegRec);
+ }
+ /* Exception filter & handler entries are skipped, unless the exception
+ handler is being targeted by the unwind, in which case we're done
+ unwinding and the caller should transfer control there. */
+ else if ( uXcptHandler == uTargetPc
+ && (pXcptRec->ExceptionFlags & EXCEPTION_TARGET_UNWIND))
+ {
+ //RTAssertMsg2("__C_specific_handler: ExceptionContinueSearch (#2)\n");
+ return ExceptionContinueSearch;
+ }
+ }
+ }
+ }
+
+ return ExceptionContinueSearch;
+}
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/except-vcc.h b/src/VBox/Runtime/common/compiler/vcc/except-vcc.h
new file mode 100644
index 00000000..f611adb6
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/except-vcc.h
@@ -0,0 +1,251 @@
+/* $Id: except-vcc.h $ */
+/** @file
+ * IPRT - Visual C++ Compiler - Exception Management.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_compiler_vcc_except_vcc_h
+#define IPRT_INCLUDED_SRC_common_compiler_vcc_except_vcc_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define __C_specific_handler their___C_specific_handler
+#include <iprt/win/windows.h>
+#undef __C_specific_handler
+
+#include <iprt/types.h>
+#include <iprt/assertcompile.h>
+
+
+RT_C_DECLS_BEGIN
+
+#if 0
+/** This is part of the AMD64 and ARM (?) exception interface, but appear to
+ * live in the runtime headers for some weird reason. */
+typedef enum
+{
+ ExceptionContinueExecution = 0,
+ ExceptionContinueSearch,
+ ExceptionNestedException,
+ ExceptionCollidedUnwind
+} EXCEPTION_DISPOSITION;
+#endif
+
+/* The following two are borrowed from pecoff.h, as we typically want to include
+ winnt.h with this header and the two header cannot co-exist. */
+
+/**
+ * An unwind code for AMD64 and ARM64.
+ *
+ * @note Also known as UNWIND_CODE or _UNWIND_CODE.
+ */
+typedef union IMAGE_UNWIND_CODE
+{
+ struct
+ {
+ /** The prolog offset where the change takes effect.
+ * This means the instruction following the one being described. */
+ uint8_t CodeOffset;
+ /** Unwind opcode.
+ * For AMD64 see IMAGE_AMD64_UNWIND_OP_CODES. */
+ RT_GCC_EXTENSION uint8_t UnwindOp : 4;
+ /** Opcode specific. */
+ RT_GCC_EXTENSION uint8_t OpInfo : 4;
+ } u;
+ uint16_t FrameOffset;
+} IMAGE_UNWIND_CODE;
+AssertCompileSize(IMAGE_UNWIND_CODE, 2);
+
+/**
+ * Unwind information for AMD64 and ARM64.
+ *
+ * Pointed to by IMAGE_RUNTIME_FUNCTION_ENTRY::UnwindInfoAddress,
+ *
+ * @note Also known as UNWIND_INFO or _UNWIND_INFO.
+ */
+typedef struct IMAGE_UNWIND_INFO
+{
+ /** Version, currently 1 or 2. The latter if IMAGE_AMD64_UWOP_EPILOG is used. */
+ RT_GCC_EXTENSION uint8_t Version : 3;
+ /** IMAGE_UNW_FLAG_XXX */
+ RT_GCC_EXTENSION uint8_t Flags : 5;
+ /** Size of function prolog. */
+ uint8_t SizeOfProlog;
+ /** Number of opcodes in aOpcodes. */
+ uint8_t CountOfCodes;
+ /** Initial frame register. */
+ RT_GCC_EXTENSION uint8_t FrameRegister : 4;
+ /** Scaled frame register offset. */
+ RT_GCC_EXTENSION uint8_t FrameOffset : 4;
+ /** Unwind opcodes. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ IMAGE_UNWIND_CODE aOpcodes[RT_FLEXIBLE_ARRAY];
+} IMAGE_UNWIND_INFO;
+AssertCompileMemberOffset(IMAGE_UNWIND_INFO, aOpcodes, 4);
+typedef IMAGE_UNWIND_INFO *PIMAGE_UNWIND_INFO;
+typedef IMAGE_UNWIND_INFO const *PCIMAGE_UNWIND_INFO;
+
+
+
+#ifdef RT_ARCH_AMD64
+/**
+ * The Visual C++ 2019 layout of the GS_HANDLER_DATA data type for AMD64.
+ *
+ * This is pointed to by DISPATCHER_CONTEXT::HandlerData when dispatching
+ * exceptions. The data resides after the unwind info for the function.
+ */
+typedef struct _GS_HANDLER_DATA
+{
+ union
+ {
+ struct
+ {
+ uint32_t fEHandler : 1;
+#define GS_HANDLER_OFF_COOKIE_IS_EHANDLER RT_BIT(0) /**< Handles exceptions. */
+ uint32_t fUHandler : 1;
+#define GS_HANDLER_OFF_COOKIE_IS_UHANDLER RT_BIT(1) /**< Handles unwind. */
+ uint32_t fHasAlignment : 1;
+#define GS_HANDLER_OFF_COOKIE_HAS_ALIGNMENT RT_BIT(2) /**< Has the uAlignmentMask member. */
+ } Bits;
+#define GS_HANDLER_OFF_COOKIE_MASK UINT32_C(0xfffffff8) /**< Mask to apply to offCookie to the the value. */
+ uint32_t offCookie;
+ } u;
+ int32_t offAlignedBase;
+ /** This field is only there when GS_HANDLER_OFF_COOKIE_HAS_ALIGNMENT is set.
+ * it seems. */
+ uint32_t uAlignmentMask;
+} GS_HANDLER_DATA;
+typedef GS_HANDLER_DATA *PGS_HANDLER_DATA;
+typedef GS_HANDLER_DATA const *PCGS_HANDLER_DATA;
+#endif /* RT_ARCH_AMD64 */
+
+#ifdef RT_ARCH_X86
+void __fastcall __security_check_cookie(uintptr_t uCookieToCheck);
+#else
+void __cdecl __security_check_cookie(uintptr_t uCookieToCheck);
+#endif
+
+
+#ifdef RT_ARCH_X86
+
+/**
+ * Exception registration record for _except_handler4 users
+ * (aka EH4_EXCEPTION_REGISTRATION_RECORD).
+ *
+ * This record is emitted immediately following the stack frame setup, i.e.
+ * after doing PUSH EBP and MOV EBP, ESP. So, EBP equals the address following
+ * this structure.
+ */
+typedef struct EH4_XCPT_REG_REC_T
+{
+ /** The saved ESP after setting up the stack frame and before the __try. */
+ uintptr_t uSavedEsp;
+ PEXCEPTION_POINTERS pXctpPtrs;
+ /** The SEH exception registration record (chained). */
+ EXCEPTION_REGISTRATION_RECORD XcptRec;
+ uintptr_t uEncodedScopeTable;
+ uint32_t uTryLevel;
+ /* uintptr_t uSavedCallerEbp; */
+} EH4_XCPT_REG_REC_T;
+AssertCompileSize(EH4_XCPT_REG_REC_T, 24);
+/** Pointer to an exception registration record for _except_handler4 (aka
+ * PEH4_EXCEPTION_REGISTRATION_RECORD). */
+typedef EH4_XCPT_REG_REC_T *PEH4_XCPT_REG_REC_T;
+
+
+/** Exception filter function for _except_handler4 users (aka
+ * PEXCEPTION_FILTER_X86). */
+typedef uint32_t (__cdecl *PFN_EH4_XCPT_FILTER_T)(void);
+/** Exception handler block function for _except_handler4 users (aka
+ * PEXCEPTION_HANDLER_X86). */
+typedef void (__cdecl *PFN_EH4_XCPT_HANDLER_T)(void);
+/** Exception finally block function for _except_handler4 users (aka
+ * PTERMINATION_HANDLER_X86).
+ */
+typedef void (__fastcall *PFN_EH4_FINALLY_T)(BOOL fAbend);
+
+/**
+ * Scope table record describing __try / __except / __finally blocks (aka
+ * EH4_SCOPETABLE_RECORD).
+ */
+typedef struct EH4_SCOPE_TAB_REC_T
+{
+ uint32_t uEnclosingLevel;
+ /** Pointer to the filter sub-function if this is a __try/__except, NULL for
+ * __try/__finally. */
+ PFN_EH4_XCPT_FILTER_T pfnFilter;
+ union
+ {
+ PFN_EH4_XCPT_HANDLER_T pfnHandler;
+ PFN_EH4_FINALLY_T pfnFinally;
+ };
+} EH4_SCOPE_TAB_REC_T;
+AssertCompileSize(EH4_SCOPE_TAB_REC_T, 12);
+/** Pointer to a const _except_handler4 scope table entry. */
+typedef EH4_SCOPE_TAB_REC_T const *PCEH4_SCOPE_TAB_REC_T;
+
+/** Special EH4_SCOPE_TAB_REC_T::uEnclosingLevel used to terminate the chain. */
+#define EH4_TOPMOST_TRY_LEVEL UINT32_C(0xfffffffe)
+
+/**
+ * Scope table used by _except_handler4 (aka EH4_SCOPETABLE).
+ */
+typedef struct EH4_SCOPE_TAB_T
+{
+ uint32_t offGSCookie;
+ uint32_t offGSCookieXor;
+ uint32_t offEHCookie;
+ uint32_t offEHCookieXor;
+ EH4_SCOPE_TAB_REC_T aScopeRecords[RT_FLEXIBLE_ARRAY];
+} EH4_SCOPE_TAB_T;
+AssertCompileMemberOffset(EH4_SCOPE_TAB_T, aScopeRecords, 16);
+/** Pointer to a const _except_handler4 scope table. */
+typedef EH4_SCOPE_TAB_T const *PCEH4_SCOPE_TAB_T;
+
+/** Special EH4_SCOPE_TAB_T::offGSCookie value. */
+# define EH4_NO_GS_COOKIE UINT32_C(0xfffffffe)
+
+#endif /* RT_ARCH_X86 */
+
+
+
+#if defined(RT_ARCH_AMD64)
+EXCEPTION_DISPOSITION __C_specific_handler(PEXCEPTION_RECORD pXcptRec, PEXCEPTION_REGISTRATION_RECORD pXcptRegRec,
+ PCONTEXT pCpuCtx, PDISPATCHER_CONTEXT pDispCtx);
+#endif
+
+RT_C_DECLS_END
+
+#endif /* !IPRT_INCLUDED_SRC_common_compiler_vcc_except_vcc_h */
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm b/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm
new file mode 100644
index 00000000..dfc47415
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm
@@ -0,0 +1,358 @@
+; $Id: except-x86-vcc-asm.asm $
+;; @file
+; IPRT - Visual C++ Compiler - x86 Exception Handler Support Code.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+
+;*********************************************************************************************************************************
+;* Header Files *
+;*********************************************************************************************************************************
+%include "iprt/asmdefs.mac"
+
+
+;*********************************************************************************************************************************
+;* Defined Constants And Macros *
+;*********************************************************************************************************************************
+
+;; @def WITH_NLG_STUFF
+; Enabled NLG debugger hooks/whatever - no idea how it's used.
+;
+; So far vsdebugeng.manimpl.dll in the VS2019 remote debugger directory is the
+; only component with any _NLG_ strings in it. Could not find any references
+; to _NLG_ in windbg.
+%define WITH_NLG_STUFF 1
+
+
+;*********************************************************************************************************************************
+;* Structures and Typedefs *
+;*********************************************************************************************************************************
+
+struc EH4_XCPT_REG_REC_T
+ .uSavedEsp resd 1 ; 0 / 0x00
+ .pXctpPtrs resd 1 ; 4 / 0x04
+ .XcptRec resd 2 ; 8 / 0x08
+ .uEncodedScopeTable resd 1 ; 16 / 0x10
+ .uTryLevel resd 1 ; 20 / 0x14
+endstruc ; 24 / 0x18
+
+
+;;
+; Scope table record describing __try / __except / __finally blocks (aka
+; EH4_SCOPETABLE_RECORD).
+;
+struc EH4_SCOPE_TAB_REC_T
+ .uEnclosingLevel resd 1
+ .pfnFilter resd 1
+ .pfnHandlerOrFinally resd 1
+endstruc
+
+;; Special EH4_SCOPE_TAB_REC_T::uEnclosingLevel used to terminate the chain.
+%define EH4_TOPMOST_TRY_LEVEL 0xfffffffe
+
+;;
+; Scope table used by _except_handler4 (aka EH4_SCOPETABLE).
+;
+struc EH4_SCOPE_TAB_T
+ .offGSCookie resd 1
+ .offGSCookieXor resd 1
+ .offEHCookie resd 1
+ .offEHCookieXor resd 1
+ .aScopeRecords resb 12
+endstruc
+
+
+;*********************************************************************************************************************************
+;* External Symbols *
+;*********************************************************************************************************************************
+BEGINCODE
+extern IMPNAME(RtlUnwind@16)
+extern _rtVccEh4DoLocalUnwindHandler@16
+
+
+;*********************************************************************************************************************************
+;* Global Variables *
+;*********************************************************************************************************************************
+
+;; Delcare rtVccEh4DoLocalUnwindHandler() in except-x86.cpp as a save exception handler.
+; This adds the symbol table number of the exception handler to the special .sxdata section.
+safeseh _rtVccEh4DoLocalUnwindHandler@16
+
+%ifdef WITH_NLG_STUFF
+BEGINDATA
+GLOBALNAME_RAW __NLG_Destination, data, hidden
+ dd 019930520h
+ dd 0
+ dd 0
+ dd 0
+%endif
+
+
+BEGINCODE
+
+%ifdef WITH_NLG_STUFF
+
+;;
+; Some VS debugger interface, I think.
+;
+; @param EDX Notification code.
+; @uses EBP, EAX & ECX are preserved. This differs from other implementation,
+; but simplifies the calling code.
+;
+ALIGNCODE(32)
+GLOBALNAME_RAW __NLG_Notify, function, hidden
+ ;
+ ; Save registers.
+ ;
+ push eax
+ push ecx
+ push ebp
+ push ebx
+
+
+ ;
+ ; ECX = notification code.
+ ; EBX = __NLG_Destination
+ ;
+ mov ecx, edx ; Originally held in ECX, so moving it there for now.
+ mov ebx, __NLG_Destination
+
+__NLG_Go:
+ ;
+ ; Save info to __NLG_Destination and the stack (same layout).
+ ;
+ mov [ebx + 0ch], ebp
+ push ebp
+ mov [ebx + 08h], ecx
+ push ecx
+ mov [ebx + 04h], eax
+ push eax
+
+ ;
+ ; This is presumably the symbol the debugger hooks on to as the string
+ ; "__NLG_Dispatch" was found in vsdebugeng.manimpl.dll in VS2019.
+ ;
+global __NLG_Dispatch
+__NLG_Dispatch:
+
+ ; Drop the info structure from the stack.
+ add esp, 4*3
+
+ ;
+ ; Restore registers and drop the parameter as we return.
+ ;
+ ; Note! This may sabotage attempts by the debugger to modify the state...
+ ; But that can be fixed once we know this is a requirement. Just
+ ; keep things simple for the caller for now.
+ ;
+ pop ebx
+ pop ebp
+ pop ecx
+ pop eax
+ ret
+
+;;
+; NLG call + return2.
+;
+; The "__NLG_Return2" symbol was observed in several vsdebugeng.manimpl.dll
+; strings in VS2019.
+;
+ALIGNCODE(4)
+GLOBALNAME_RAW __NLG_Call, function, hidden
+ call eax
+
+global __NLG_Return2
+__NLG_Return2:
+ ret
+
+%endif
+
+
+;;
+; Calls the filter sub-function for a __finally statement.
+;
+; This sets all GRPs to zero, except for ESP, EBP and EAX.
+;
+; @param pfnFilter [ebp + 08h]
+; @param fAbend [ebp + 0ch]
+; @param pbFrame [ebp + 10h]
+;
+ALIGNCODE(64)
+BEGINPROC rtVccEh4DoFinally
+ push ebp
+ mov ebp, esp
+ push ebx
+ push edi
+ push esi
+
+ xor edi, edi
+ xor esi, esi
+%ifndef WITH_NLG_STUFF
+ xor edx, edx
+%endif
+ xor ebx, ebx
+
+ mov eax, [ebp + 08h] ; pfnFilter
+ movzx ecx, byte [ebp + 0ch] ; fAbend
+ mov ebp, [ebp + 10h] ; pbFrame
+
+%ifdef WITH_NLG_STUFF
+ mov edx, 101h
+ call __NLG_Notify
+ xor edx, edx
+
+ call __NLG_Call
+%else
+ call eax
+%endif
+
+ pop esi
+ pop edi
+ pop ebx
+ pop ebp
+ ret
+ENDPROC rtVccEh4DoFinally
+
+
+;;
+; Calls the filter sub-function for an __except statement.
+;
+; This sets all GRPs to zero, except for ESP, EBP and ECX.
+;
+; @param pfnFilter [ebp + 08h]
+; @param pbFrame [ebp + 0ch]
+;
+ALIGNCODE(32)
+BEGINPROC rtVccEh4DoFiltering
+ push ebp
+ push ebx
+ push edi
+ push esi
+
+ mov ecx, [esp + 5 * 4 + 0] ; pfnFilter
+ mov ebp, [esp + 5 * 4 + 4] ; pbFrame
+
+ xor edi, edi
+ xor esi, esi
+ xor edx, edx
+ xor ebx, ebx
+ xor eax, eax
+
+ call ecx
+
+ pop esi
+ pop edi
+ pop ebx
+ pop ebp
+ ret
+ENDPROC rtVccEh4DoFiltering
+
+
+;;
+; Resumes executing in an __except block (never returns).
+;
+; @param pfnHandler [ebp + 08h]
+; @param pbFrame [ebp + 0ch]
+;
+ALIGNCODE(32)
+BEGINPROC rtVccEh4JumpToHandler
+ ;
+ ; Since we're never returning there is no need to save anything here. So,
+ ; just start by loading parameters into registers.
+ ;
+ mov esi, [esp + 1 * 4 + 0] ; pfnFilter
+ mov ebp, [esp + 1 * 4 + 4] ; pbFrame
+
+%ifdef WITH_NLG_STUFF
+ ;
+ ; Notify VS debugger/whatever.
+ ;
+ mov eax, esi
+ mov edx, 1
+ call __NLG_Notify
+%endif
+
+ ;
+ ; Zero all GPRs except for ESP, EBP and ESI.
+ ;
+ xor edi, edi
+ xor edx, edx
+ xor ecx, ecx
+ xor ebx, ebx
+ xor eax, eax
+
+ jmp esi
+ENDPROC rtVccEh4JumpToHandler
+
+
+;;
+; This does global unwinding via RtlUnwind.
+;
+; The interface kind of requires us to do this from assembly.
+;
+; @param pXcptRec [ebp + 08h]
+; @param pXcptRegRec [ebp + 0ch]
+;
+ALIGNCODE(32)
+BEGINPROC rtVccEh4DoGlobalUnwind
+ push ebp
+ mov ebp, esp
+
+ ; Save all non-volatile registers.
+ push edi
+ push esi
+ push ebx
+
+ ;
+ ; Call unwind function.
+ ;
+ push 0 ; ReturnValue
+ push dword [ebp + 08h] ; ExceptionRecord - pXcptRec
+ push .return ; TargetIp
+ push dword [ebp + 0ch] ; TargetFrame - pXcptRegRec
+ call IMP(RtlUnwind@16)
+
+ ;
+ ; The RtlUnwind code will place us here if it doesn't return the regular way.
+ ;
+.return:
+ ; Restore non-volatile registers.
+ pop ebx
+ pop esi
+ pop edi
+
+ leave
+ ret
+ENDPROC rtVccEh4DoGlobalUnwind
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc.cpp b/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc.cpp
new file mode 100644
index 00000000..17dd3a0d
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc.cpp
@@ -0,0 +1,329 @@
+/* $Id: except-x86-vcc.cpp $ */
+/** @file
+ * IPRT - Visual C++ Compiler - x86 Exception Handler Filter.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/nt/nt-and-windows.h>
+
+#include "internal/compiler-vcc.h"
+#include "except-vcc.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Extended exception registration record used by rtVccEh4DoLocalUnwind
+ * and rtVccEh4DoLocalUnwindHandler.
+ */
+typedef struct EH4_LOCAL_UNWIND_XCPT_REG
+{
+ /** Security cookie. */
+ uintptr_t uEHCookieFront;
+ /** The actual registration record. */
+ EXCEPTION_REGISTRATION_RECORD XcptRegRec;
+ /** @name rtVccEh4DoLocalUnwind parameters
+ * @{ */
+ PEH4_XCPT_REG_REC_T pEh4XcptRegRec;
+ uint32_t uTargetTryLevel;
+ uint8_t const *pbFrame;
+ /** @} */
+ /** Security cookie. */
+ uintptr_t uEHCookieBack;
+} EH4_LOCAL_UNWIND_XCPT_REG;
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+extern "C" uintptr_t __security_cookie;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+DECLASM(LONG) rtVccEh4DoFiltering(PFN_EH4_XCPT_FILTER_T pfnFilter, uint8_t const *pbFrame);
+DECLASM(DECL_NO_RETURN(void)) rtVccEh4JumpToHandler(PFN_EH4_XCPT_HANDLER_T pfnHandler, uint8_t const *pbFrame);
+DECLASM(void) rtVccEh4DoGlobalUnwind(PEXCEPTION_RECORD pXcptRec, PEXCEPTION_REGISTRATION_RECORD pXcptRegRec);
+DECLASM(void) rtVccEh4DoFinally(PFN_EH4_FINALLY_T pfnFinally, bool fAbend, uint8_t const *pbFrame);
+extern "C" EXCEPTION_DISPOSITION __stdcall
+rtVccEh4DoLocalUnwindHandler(PEXCEPTION_RECORD pXcptRec, PVOID pvEstFrame, PCONTEXT pCpuCtx, PVOID pvDispCtx);
+
+
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4733) /* warning C4733: Inline asm assigning to 'FS:0': handler not registered as safe handler */
+#endif
+
+/**
+ * Calls the __finally blocks up to @a uTargetTryLevel is reached, starting with
+ * @a pEh4XcptRegRec->uTryLevel.
+ *
+ * @param pEh4XcptRegRec The EH4 exception registration record.
+ * @param uTargetTryLevel The target __try level to stop unwinding at.
+ * @param pbFrame The frame pointer (EBP).
+ */
+static void rtVccEh4DoLocalUnwind(PEH4_XCPT_REG_REC_T pEh4XcptRegRec, uint32_t uTargetTryLevel, uint8_t const *pbFrame)
+{
+ /*
+ * Manually set up exception handler.
+ */
+ EH4_LOCAL_UNWIND_XCPT_REG RegRec =
+ {
+ __security_cookie ^ (uintptr_t)&RegRec,
+ {
+ (EXCEPTION_REGISTRATION_RECORD *)__readfsdword(RT_UOFFSETOF(NT_TIB, ExceptionList)),
+ rtVccEh4DoLocalUnwindHandler /* safeseh (.sxdata) entry emitted by except-x86-vcc-asm.asm */
+ },
+ pEh4XcptRegRec,
+ uTargetTryLevel,
+ pbFrame,
+ __security_cookie ^ (uintptr_t)&RegRec
+ };
+ __writefsdword(RT_UOFFSETOF(NT_TIB, ExceptionList), (uintptr_t)&RegRec.XcptRegRec);
+
+ /*
+ * Do the unwinding.
+ */
+ uint32_t uCurTryLevel = pEh4XcptRegRec->uTryLevel;
+ while ( uCurTryLevel != EH4_TOPMOST_TRY_LEVEL
+ && ( uCurTryLevel > uTargetTryLevel
+ || uTargetTryLevel == EH4_TOPMOST_TRY_LEVEL /* if we knew what 0xffffffff meant, this could probably be omitted */ ))
+ {
+ PCEH4_SCOPE_TAB_T const pScopeTable = (PCEH4_SCOPE_TAB_T)(pEh4XcptRegRec->uEncodedScopeTable ^ __security_cookie);
+ PCEH4_SCOPE_TAB_REC_T const pEntry = &pScopeTable->aScopeRecords[uCurTryLevel];
+
+ pEh4XcptRegRec->uTryLevel = uCurTryLevel = pEntry->uEnclosingLevel;
+
+ /* __finally scope table entries have no filter sub-function. */
+ if (!pEntry->pfnFilter)
+ {
+ //RTAssertMsg2("rtVccEh4DoLocalUnwind: Calling %p (level %#x)\n", pEntry->pfnFinally, uCurTryLevel);
+ rtVccEh4DoFinally(pEntry->pfnFinally, true /*fAbend*/, pbFrame);
+
+ /* Read the try level again in case it changed... */
+ uCurTryLevel = pEh4XcptRegRec->uTryLevel;
+ }
+ }
+
+ /*
+ * Deregister exception handler.
+ */
+ __writefsdword(RT_UOFFSETOF(NT_TIB, ExceptionList), (uintptr_t)RegRec.XcptRegRec.Next);
+}
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+/**
+ * Exception handler for rtVccEh4DoLocalUnwind.
+ */
+EXCEPTION_DISPOSITION __stdcall
+rtVccEh4DoLocalUnwindHandler(PEXCEPTION_RECORD pXcptRec, PVOID pvEstFrame, PCONTEXT pCpuCtx, PVOID pvDispCtx)
+{
+ EH4_LOCAL_UNWIND_XCPT_REG *pMyRegRec = RT_FROM_MEMBER(pvEstFrame, EH4_LOCAL_UNWIND_XCPT_REG, XcptRegRec);
+ __security_check_cookie(pMyRegRec->uEHCookieFront ^ (uintptr_t)pMyRegRec);
+ __security_check_cookie(pMyRegRec->uEHCookieBack ^ (uintptr_t)pMyRegRec);
+
+ /*
+ * This is a little sketchy as it isn't all that well documented by the OS
+ * vendor, but if invoked while unwinding, we return ExceptionCollidedUnwind
+ * and update the *ppDispCtx value to point to the colliding one.
+ */
+ if (pXcptRec->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND))
+ {
+ rtVccEh4DoLocalUnwind(pMyRegRec->pEh4XcptRegRec, pMyRegRec->uTargetTryLevel, pMyRegRec->pbFrame);
+
+ PEXCEPTION_REGISTRATION_RECORD *ppDispCtx = (PEXCEPTION_REGISTRATION_RECORD *)pvDispCtx;
+ *ppDispCtx = &pMyRegRec->XcptRegRec;
+ return ExceptionCollidedUnwind;
+ }
+
+ /*
+ * In all other cases we do nothing special.
+ */
+ RT_NOREF(pCpuCtx);
+ return ExceptionContinueSearch;
+}
+
+
+/**
+ * This validates the CPU context, may terminate the application if invalid.
+ */
+DECLINLINE(void) rtVccValidateExceptionContextRecord(PCONTEXT pCpuCtx)
+{
+ if (RT_LIKELY( !rtVccIsGuardICallChecksActive()
+ || rtVccIsPointerOnTheStack(pCpuCtx->Esp)))
+ { /* likely */ }
+ else
+ rtVccCheckContextFailed(pCpuCtx);
+}
+
+
+/**
+ * Helper that validates stack cookies.
+ */
+DECLINLINE(void) rtVccEh4ValidateCookies(PCEH4_SCOPE_TAB_T pScopeTable, uint8_t const *pbFrame)
+{
+ if (pScopeTable->offGSCookie != EH4_NO_GS_COOKIE)
+ {
+ uintptr_t uGsCookie = *(uintptr_t const *)&pbFrame[pScopeTable->offGSCookie];
+ uGsCookie ^= (uintptr_t)&pbFrame[pScopeTable->offGSCookieXor];
+ __security_check_cookie(uGsCookie);
+ }
+
+ uintptr_t uEhCookie = *(uintptr_t const *)&pbFrame[pScopeTable->offEHCookie];
+ uEhCookie ^= (uintptr_t)&pbFrame[pScopeTable->offEHCookieXor];
+ __security_check_cookie(uEhCookie);
+}
+
+
+/**
+ * Call exception filters, handlers and unwind code for x86 code.
+ *
+ * This is called for windows' structured exception handling (SEH) in x86 32-bit
+ * code, i.e. the __try/__except/__finally stuff in Visual C++. The compiler
+ * generate scope records for the __try/__except blocks as well as unwind
+ * records for __finally and probably C++ stack object destructors.
+ *
+ * @returns Exception disposition.
+ * @param pXcptRec The exception record.
+ * @param pXcptRegRec The exception registration record, taken to be the frame
+ * address.
+ * @param pCpuCtx The CPU context for the exception.
+ * @param pDispCtx Dispatcher context.
+ */
+extern "C" __declspec(guard(suppress))
+DWORD _except_handler4(PEXCEPTION_RECORD pXcptRec, PEXCEPTION_REGISTRATION_RECORD pXcptRegRec, PCONTEXT pCpuCtx, PVOID pvCtx)
+{
+ /*
+ * The registration record (probably chained on FS:[0] like in the OS/2 days)
+ * points to a larger structure specific to _except_handler4. The structure
+ * is planted right after the saved caller EBP value when establishing the
+ * stack frame, so EBP = pXcptRegRec + 1;
+ */
+ PEH4_XCPT_REG_REC_T const pEh4XcptRegRec = RT_FROM_MEMBER(pXcptRegRec, EH4_XCPT_REG_REC_T, XcptRec);
+ uint8_t * const pbFrame = (uint8_t *)&pEh4XcptRegRec[1];
+ PCEH4_SCOPE_TAB_T const pScopeTable = (PCEH4_SCOPE_TAB_T)(pEh4XcptRegRec->uEncodedScopeTable ^ __security_cookie);
+
+ /*
+ * Validate the stack cookie and exception context.
+ */
+ rtVccEh4ValidateCookies(pScopeTable, pbFrame);
+ rtVccValidateExceptionContextRecord(pCpuCtx);
+
+ /*
+ * If dispatching an exception, call the exception filter functions and jump
+ * to the __except blocks if so directed.
+ */
+ if (IS_DISPATCHING(pXcptRec->ExceptionFlags))
+ {
+ uint32_t uTryLevel = pEh4XcptRegRec->uTryLevel;
+ //RTAssertMsg2("_except_handler4: dispatch: uTryLevel=%#x\n", uTryLevel);
+ while (uTryLevel != EH4_TOPMOST_TRY_LEVEL)
+ {
+ PCEH4_SCOPE_TAB_REC_T const pEntry = &pScopeTable->aScopeRecords[uTryLevel];
+ PFN_EH4_XCPT_FILTER_T const pfnFilter = pEntry->pfnFilter;
+ if (pfnFilter)
+ {
+ /* Call the __except filtering expression: */
+ //RTAssertMsg2("_except_handler4: Calling pfnFilter=%p\n", pfnFilter);
+ EXCEPTION_POINTERS XcptPtrs = { pXcptRec, pCpuCtx };
+ pEh4XcptRegRec->pXctpPtrs = &XcptPtrs;
+ LONG lRet = rtVccEh4DoFiltering(pfnFilter, pbFrame);
+ pEh4XcptRegRec->pXctpPtrs = NULL;
+ //RTAssertMsg2("_except_handler4: pfnFilter=%p -> %ld\n", pfnFilter, lRet);
+ rtVccEh4ValidateCookies(pScopeTable, pbFrame);
+
+ /* Return if we're supposed to continue execution (the convention
+ it to match negative values rather than the exact defined value): */
+ AssertCompile(EXCEPTION_CONTINUE_EXECUTION == -1);
+ if (lRet <= EXCEPTION_CONTINUE_EXECUTION)
+ return ExceptionContinueExecution;
+
+ /* Similarly, the handler is executed for any positive value. */
+ AssertCompile(EXCEPTION_CONTINUE_SEARCH == 0);
+ AssertCompile(EXCEPTION_EXECUTE_HANDLER == 1);
+ if (lRet >= EXCEPTION_EXECUTE_HANDLER)
+ {
+ /* We're about to resume execution in the __except block, so unwind
+ up to it first. */
+ //RTAssertMsg2("_except_handler4: global unwind\n");
+ rtVccEh4DoGlobalUnwind(pXcptRec, &pEh4XcptRegRec->XcptRec);
+ if (pEh4XcptRegRec->uTryLevel != EH4_TOPMOST_TRY_LEVEL)
+ {
+ //RTAssertMsg2("_except_handler4: local unwind\n");
+ rtVccEh4DoLocalUnwind(pEh4XcptRegRec, uTryLevel, pbFrame);
+ }
+ rtVccEh4ValidateCookies(pScopeTable, pbFrame);
+
+ /* Now jump to the __except block. This will _not_ return. */
+ //RTAssertMsg2("_except_handler4: jumping to __except block %p (level %#x)\n", pEntry->pfnHandler, pEntry->uEnclosingLevel);
+ pEh4XcptRegRec->uTryLevel = pEntry->uEnclosingLevel;
+ rtVccEh4ValidateCookies(pScopeTable, pbFrame); /* paranoia^2 */
+
+ rtVccEh4JumpToHandler(pEntry->pfnHandler, pbFrame);
+ /* (not reached) */
+ }
+ }
+
+ /*
+ * Next try level.
+ */
+ uTryLevel = pEntry->uEnclosingLevel;
+ }
+ }
+ /*
+ * If not dispatching we're unwinding, so we call any __finally blocks.
+ */
+ else
+ {
+ //RTAssertMsg2("_except_handler4: unwind: uTryLevel=%#x\n", pEh4XcptRegRec->uTryLevel);
+ if (pEh4XcptRegRec->uTryLevel != EH4_TOPMOST_TRY_LEVEL)
+ {
+ rtVccEh4DoLocalUnwind(pEh4XcptRegRec, EH4_TOPMOST_TRY_LEVEL, pbFrame);
+ rtVccEh4ValidateCookies(pScopeTable, pbFrame);
+ }
+ }
+
+ RT_NOREF(pvCtx);
+ return ExceptionContinueSearch;
+}
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/fltused-vcc.cpp b/src/VBox/Runtime/common/compiler/vcc/fltused-vcc.cpp
new file mode 100644
index 00000000..d26f2b90
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/fltused-vcc.cpp
@@ -0,0 +1,58 @@
+/* $Id: fltused-vcc.cpp $ */
+/** @file
+ * IPRT - No-CRT - Basic allocators, Windows.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include "internal/compiler-vcc.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Very like some remnant from the 8086, 286 and 386 days of DOS, OS/2 and
+ * similar, where you could link with different floating point libs. My guess
+ * would be that this constant indicates to the compiled code which floating
+ * point support the library provides, probably especially as it comes to
+ * printf and scanf conversion but probably also emulation/real hw.
+ *
+ * Found some old 16-bit and 32-bit MSC C libraries (probably around v6.0)
+ * which all seems to define it as 0x9876. They also have a whole bunch of
+ * external dependencies on what seems to be mostly conversion helpers.
+ */
+unsigned _fltused = 0x9875;
diff --git a/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm
new file mode 100644
index 00000000..40bd3fa2
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm
@@ -0,0 +1,80 @@
+; $Id: ftol2-vcc.asm $
+;; @file
+; IPRT - Floating Point to Integer related Visual C++ support routines.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+;;
+; Convert st0 to integer returning it in eax, popping st0.
+;
+; @returns value in eax
+; @param st0 The value to convet. Will be popped.
+; @uses eax, st0, FSW.TOP
+;
+GLOBALNAME_RAW __ftol2_sse_excpt, function, RT_NOTHING
+GLOBALNAME_RAW __ftol2_sse, function, RT_NOTHING ;; @todo kind of expect __ftol2_sse to take input in xmm0 and return in edx:eax.
+BEGINPROC_RAW __ftoi2
+ push ebp
+ mov ebp, esp
+ sub esp, 8h
+ fisttp dword [esp]
+ mov eax, [esp]
+ leave
+ ret
+ENDPROC_RAW __ftoi2
+
+
+;;
+; Convert st0 to integer returning it in edx:eax, popping st0.
+;
+; @returns value in edx:eax
+; @param st0 The value to convet. Will be popped.
+; @uses eax, edx, st0, FSW.TOP
+;
+BEGINPROC_RAW __ftol2
+ push ebp
+ mov ebp, esp
+ sub esp, 8h
+ and esp, 0fffffff8h ; proper alignment.
+ fisttp qword [esp]
+ mov eax, [esp]
+ mov edx, [esp + 4]
+ leave
+ ret
+ENDPROC_RAW __ftol2
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm
new file mode 100644
index 00000000..42e6d5a8
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm
@@ -0,0 +1,108 @@
+; $Id: guard-vcc.asm $
+;; @file
+; IPRT - Control Flow Guard related Visual C++ support routines.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+
+;*********************************************************************************************************************************
+;* Header Files *
+;*********************************************************************************************************************************
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+
+;*********************************************************************************************************************************
+;* Global Variables *
+;*********************************************************************************************************************************
+
+;
+; Points to a NOP icall checker by default.
+; The loader will replace this when we start advertising it in the load config.
+;
+section .0000cfg rdata align=8
+
+GLOBALNAME __guard_check_icall_fptr
+ RTCCPTR_DEF NAME(__guard_check_icall_nop)
+
+%ifdef RT_ARCH_AMD64
+GLOBALNAME __guard_dispatch_icall_fptr
+ RTCCPTR_DEF __guard_dispatch_icall_nop
+
+; xfg stuff (haven't seen it generated or used yet).
+GLOBALNAME __guard_xfg_check_icall_fptr
+ RTCCPTR_DEF __guard_check_icall_nop
+
+GLOBALNAME __guard_xfg_dispatch_icall_fptr
+ RTCCPTR_DEF __guard_xfg_dispatch_icall_nop
+
+GLOBALNAME __guard_xfg_table_dispatch_icall_fptr
+ RTCCPTR_DEF __guard_xfg_dispatch_icall_nop
+
+%endif
+
+
+BEGINCODE
+;;
+; Check that doesn't do anything.
+;
+; This is for older windows versions which doesn't support call flow guard stuff.
+;
+BEGINPROC __guard_check_icall_nop
+ ret
+ENDPROC __guard_check_icall_nop
+
+%ifdef RT_ARCH_AMD64
+;;
+; Target function in RAX
+;
+; This is for older windows versions which doesn't support call flow guard stuff.
+;
+BEGINPROC __guard_dispatch_icall_nop
+ jmp rax
+ENDPROC __guard_dispatch_icall_nop
+%endif
+
+%ifdef RT_ARCH_AMD64
+;;
+; Target function in RAX
+;
+; This is for windows versions which doesn't support extended call flow guard stuff.
+;
+BEGINPROC __guard_xfg_dispatch_icall_nop
+ jmp [__guard_dispatch_icall_nop wrt RIP]
+ENDPROC __guard_xfg_dispatch_icall_nop
+%endif
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/initializers-c-cpp-vcc.cpp b/src/VBox/Runtime/common/compiler/vcc/initializers-c-cpp-vcc.cpp
new file mode 100644
index 00000000..0fc451db
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/initializers-c-cpp-vcc.cpp
@@ -0,0 +1,158 @@
+/* $Id: initializers-c-cpp-vcc.cpp $ */
+/** @file
+ * IPRT - Visual C++ Compiler - C & C++ Initializers and Terminators.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_COMPILER_VCC_WITH_C_INIT_TERM_SECTIONS
+#define IPRT_COMPILER_VCC_WITH_CPP_INIT_SECTIONS
+#include "internal/compiler-vcc.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef void (__cdecl *PFNVCINITTERM)(void);
+typedef int (__cdecl *PFNVCINITTERMRET)(void);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** @name Initializer arrays.
+ *
+ * The important thing here are the section names, the linker wi.
+ *
+ * @{ */
+/** Start of the C initializer array. */
+__declspec(allocate(".CRT$XIA")) PFNVCINITTERMRET g_apfnRTVccInitializers_C_Start = NULL;
+/** End of the C initializer array. */
+__declspec(allocate(".CRT$XIZ")) PFNVCINITTERMRET g_apfnRTVccInitializers_C_End = NULL;
+
+/** Start of the C pre-terminator array. */
+__declspec(allocate(".CRT$XPA")) PFNVCINITTERM g_apfnRTVccEarlyTerminators_C_Start = NULL;
+/** End of the C pre-terminator array. */
+__declspec(allocate(".CRT$XPZ")) PFNVCINITTERM g_apfnRTVccEarlyTerminators_C_End = NULL;
+
+/** Start of the C terminator array. */
+__declspec(allocate(".CRT$XTA")) PFNVCINITTERM g_apfnRTVccTerminators_C_Start = NULL;
+/** End of the C terminator array. */
+__declspec(allocate(".CRT$XTZ")) PFNVCINITTERM g_apfnRTVccTerminators_C_End = NULL;
+
+/** Start of the C++ initializer array. */
+__declspec(allocate(".CRT$XCA")) PFNVCINITTERM g_apfnRTVccInitializers_Cpp_Start = NULL;
+/** End of the C++ initializer array. */
+__declspec(allocate(".CRT$XCZ")) PFNVCINITTERM g_apfnRTVccInitializers_Cpp_End = NULL;
+
+
+/* Tell the linker to merge the .CRT* sections into .rdata */
+#pragma comment(linker, "/merge:.CRT=.rdata ")
+/** @} */
+
+
+/**
+ * Runs the C and C++ initializers.
+ *
+ * @returns 0 on success, non-zero return from C initalizer on failure.
+ */
+int rtVccInitializersRunInit(void)
+{
+ /*
+ * Run the C initializers first.
+ */
+ for (PFNVCINITTERMRET *ppfn = &g_apfnRTVccInitializers_C_Start;
+ (uintptr_t)ppfn < (uintptr_t)&g_apfnRTVccInitializers_C_End;
+ ppfn++)
+ {
+ PFNVCINITTERMRET const pfn = *ppfn;
+ if (pfn)
+ {
+ int const rc = pfn();
+ if (RT_LIKELY(rc == 0))
+ { /* likely */ }
+ else
+ return rc;
+ }
+ }
+
+ /*
+ * Run the C++ initializers.
+ */
+ for (PFNVCINITTERM *ppfn = &g_apfnRTVccInitializers_Cpp_Start;
+ (uintptr_t)ppfn < (uintptr_t)&g_apfnRTVccInitializers_Cpp_End;
+ ppfn++)
+ {
+ PFNVCINITTERM const pfn = *ppfn;
+ if (pfn)
+ pfn();
+ }
+
+ return 0;
+}
+
+
+/**
+ * Runs the C terminator callbacks.
+ */
+void rtVccInitializersRunTerm(void)
+{
+ /*
+ * First the early terminators.
+ */
+ for (PFNVCINITTERM *ppfn = &g_apfnRTVccEarlyTerminators_C_Start;
+ (uintptr_t)ppfn < (uintptr_t)&g_apfnRTVccEarlyTerminators_C_End;
+ ppfn++)
+ {
+ PFNVCINITTERM const pfn = *ppfn;
+ if (pfn)
+ pfn();
+ }
+
+ /*
+ * Then the real terminator list.
+ */
+ for (PFNVCINITTERM *ppfn = &g_apfnRTVccTerminators_C_Start;
+ (uintptr_t)ppfn < (uintptr_t)&g_apfnRTVccTerminators_C_End;
+ ppfn++)
+ {
+ PFNVCINITTERM const pfn = *ppfn;
+ if (pfn)
+ pfn();
+ }
+
+}
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/loadcfg-vcc.c b/src/VBox/Runtime/common/compiler/vcc/loadcfg-vcc.c
new file mode 100644
index 00000000..c213db19
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/loadcfg-vcc.c
@@ -0,0 +1,154 @@
+/* $Id: loadcfg-vcc.c $ */
+/** @file
+ * IPRT - Visual C++ Compiler - PE/Windows Load Configuration.
+ *
+ * @note This is a C file and not C++ because the compiler generates fixups
+ * that upsets the linker (ADDR32 + ADDR32_4 instead of ADDR64).
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/formats/pecoff.h>
+
+#include "internal/compiler-vcc.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+extern uintptr_t __security_cookie; /**< nocrt-stack-win.asm */
+#ifdef RT_ARCH_X86
+extern uintptr_t __safe_se_handler_table[]; /**< Linker generated. */
+extern uint8_t __safe_se_handler_count; /**< Absolute "address" defined by the linker representing the table size. */
+#endif
+#ifdef RT_ARCH_AMD64
+extern uintptr_t __guard_dispatch_icall_fptr;/**< nocrt-guard-win.asm */
+#endif
+extern uintptr_t __guard_fids_table[]; /**< Linker generated. */
+extern uint8_t __guard_fids_count; /** Another absolute "address" generated by the linker for a table size. */
+extern uint8_t __guard_flags; /** Another absolute "address" generated by the linker, flags value this time. */
+extern uintptr_t __guard_iat_table[]; /**< Linker generated. */
+extern uint8_t __guard_iat_count; /** Another absolute "address" generated by the linker for a table size. */
+extern uintptr_t __guard_longjmp_table[]; /**< Linker generated. */
+extern uint8_t __guard_longjmp_count; /** Another absolute "address" generated by the linker for a table size. */
+extern uint8_t __enclave_config; /** Another absolute "address" generated by the linker, flags value this time. */
+extern uintptr_t __guard_eh_cont_table[]; /**< Linker generated. */
+extern uint8_t __guard_eh_cont_count; /** Another absolute "address" generated by the linker for a table size. */
+#ifdef RT_ARCH_AMD64
+extern uintptr_t __guard_xfg_check_icall_fptr; /**< nocrt-guard-win.asm */
+extern uintptr_t __guard_xfg_dispatch_icall_fptr; /**< nocrt-guard-win.asm */
+extern uintptr_t __guard_xfg_table_dispatch_icall_fptr; /**< nocrt-guard-win.asm */
+#endif
+
+
+/**
+ * The load configuration for the PE image.
+ *
+ * The name of this is dictated by the linker, as it looks for a
+ * _load_config_used symbol and puts it's address and (somehow) size in the load
+ * config data dir entry.
+ *
+ * @todo use _MSC_VER to reduce this for older linkers which doesn't support all
+ * the machinactions we include here for the 2019 (14.29.30139.0) linker.
+ */
+RT_CONCAT(IMAGE_LOAD_CONFIG_DIRECTORY, ARCH_BITS) const _load_config_used =
+{
+ /* .Size = */ sizeof(_load_config_used),
+ /* .TimeDateStamp = */ 0,
+ /* .MajorVersion = */ 0,
+ /* .MinorVersion = */ 0,
+ /* .GlobalFlagsClear = */ 0,
+ /* .GlobalFlagsSet = */ 0,
+ /* .CriticalSectionDefaultTimeout = */ 0,
+ /* .DeCommitFreeBlockThreshold = */ 0,
+ /* .DeCommitTotalFreeThreshold = */ 0,
+ /* .LockPrefixTable = */ 0,
+ /* .MaximumAllocationSize = */ 0,
+ /* .VirtualMemoryThreshold = */ 0,
+ /* .ProcessHeapFlags = */ 0,
+ /* .ProcessAffinityMask = */ 0,
+ /* .CSDVersion = */ 0,
+ /* .DependentLoadFlags = */ 0,
+ /* .EditList = */ 0,
+ /* .SecurityCookie = */ (uintptr_t)&__security_cookie,
+#ifdef RT_ARCH_X86
+ /* .SEHandlerTable = */ (uintptr_t)&__safe_se_handler_table[0],
+ /* .SEHandlerCount = */ (uintptr_t)&__safe_se_handler_count, /* Absolute "address", remember. */
+#else
+ /* .SEHandlerTable = */ 0,
+ /* .SEHandlerCount = */ 0,
+#endif
+ /* .GuardCFCCheckFunctionPointer = */ (uintptr_t)&__guard_check_icall_fptr,
+#ifdef RT_ARCH_AMD64
+ /* .GuardCFDispatchFunctionPointer = */ (uintptr_t)&__guard_dispatch_icall_fptr,
+#else
+ /* .GuardCFDispatchFunctionPointer = */ 0,
+#endif
+ /* .GuardCFFunctionTable = */ (uintptr_t)&__guard_fids_table[0],
+ /* .GuardCFFunctionCount = */ (uintptr_t)&__guard_fids_count, /* Absolute "address", remember. */
+ /* .GuardFlags = */ (uint32_t)(uintptr_t)&__guard_flags, /* Ditto. */
+ /* .CodeIntegrity = */ { 0, 0, 0, 0 },
+ /* .GuardAddressTakenIatEntryTable = */ (uintptr_t)__guard_iat_table,
+ /* .GuardAddressTakenIatEntryCount = */ (uintptr_t)&__guard_iat_count, /* The same. */
+ /* .GuardLongJumpTargetTable = */ (uintptr_t)&__guard_longjmp_table[0],
+ /* .GuardLongJumpTargetCount = */ (uintptr_t)&__guard_longjmp_count, /* Another one. */
+ /* .DynamicValueRelocTable = */ 0,
+ /* .CHPEMetadataPointer = */ 0, /** @todo ARM */
+ /* .GuardRFFailureRoutine = */ 0,
+ /* .GuardRFFailureRoutineFunctionPointer= */ 0,
+ /* .DynamicValueRelocTableOffset = */ 0,
+ /* .DynamicValueRelocTableSection = */ 0,
+ /* .Reserved2 = */ 0,
+ /* .GuardRFVerifyStackPointerFunctionPointer=*/ 0,
+ /* .HotPatchTableOffset = */ 0,
+ /* .Reserved3 = */ 0,
+ /* .EnclaveConfigurationPointer = */ (uintptr_t)&__enclave_config, /* And another one. */
+ /* .VolatileMetadataPointer = */ 0,
+ /* .GuardEHContinuationTable = */ (uintptr_t)&__guard_eh_cont_table[0],
+ /* .GuardEHContinuationCount = */ (uintptr_t)&__guard_eh_cont_count, /* Yet another one. */
+#ifdef RT_ARCH_AMD64
+ /* .GuardXFGCheckFunctionPointer = */ (uintptr_t)&__guard_xfg_check_icall_fptr,
+ /* .GuardXFGDispatchFunctionPointer = */ (uintptr_t)&__guard_xfg_dispatch_icall_fptr,
+ /* .GuardXFGTableDispatchFunctionPointer= */ (uintptr_t)&__guard_xfg_table_dispatch_icall_fptr,
+#else
+ /* .GuardXFGCheckFunctionPointer = */ 0,
+ /* .GuardXFGDispatchFunctionPointer = */ 0,
+ /* .GuardXFGTableDispatchFunctionPointer= */ 0,
+#endif
+ /* .CastGuardOsDeterminedFailureMode = */ 0,
+};
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/purecall-vcc.cpp b/src/VBox/Runtime/common/compiler/vcc/purecall-vcc.cpp
new file mode 100644
index 00000000..d76b3b4d
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/purecall-vcc.cpp
@@ -0,0 +1,63 @@
+/* $Id: purecall-vcc.cpp $ */
+/** @file
+ * IPRT - No-CRT - Pure virtual method vtable filler.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/nocrt.h"
+
+#include <iprt/asm.h>
+#ifndef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+# include <iprt/assert.h>
+#endif
+
+#include "internal/compiler-vcc.h"
+
+
+extern "C" int __cdecl _purecall(void)
+{
+#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+ RTAssertMsg2("\n\n!!%p called _purecall!!\n\n", ASMReturnAddress());
+#else
+ rtNoCrtFatalWriteBegin(RT_STR_TUPLE("\r\n\r\n!!_purecall called from "));
+ rtNoCrtFatalWritePtr(ASMReturnAddress());
+ rtNoCrtFatalWriteEnd(RT_STR_TUPLE("!!\r\n\r\n"));
+#endif
+ RT_BREAKPOINT();
+ return 0;
+}
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/stack-except-seh-vcc.cpp b/src/VBox/Runtime/common/compiler/vcc/stack-except-seh-vcc.cpp
new file mode 100644
index 00000000..ff611e47
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/stack-except-seh-vcc.cpp
@@ -0,0 +1,117 @@
+/* $Id: stack-except-seh-vcc.cpp $ */
+/** @file
+ * IPRT - Visual C++ Compiler - Stack Checking, __GSHandlerCheck_SEH.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/nocrt.h"
+
+#include "except-vcc.h"
+
+
+#if !defined(RT_ARCH_AMD64)
+# error "This file is for AMD64 (and probably ARM, but needs porting)"
+#endif
+
+
+
+/**
+ * Check the stack cookie before calling the exception handler.
+ *
+ * This is to prevent attackers from bypassing stack cookie checking by
+ * triggering an exception.
+ *
+ * This is called for windows' structured exception handling (SEH), i.e. the
+ * __try/__except/__finally stuff in Visual C++, for which the compiler
+ * generates somewhat different strctures compared to the plain __GSHanderCheck
+ * scenario.
+ *
+ * @returns Exception disposition.
+ * @param pXcptRec The exception record.
+ * @param pXcptRegRec The exception registration record, taken to be the frame
+ * address.
+ * @param pCpuCtx The CPU context for the exception.
+ * @param pDispCtx Dispatcher context.
+ */
+extern "C" __declspec(guard(suppress))
+EXCEPTION_DISPOSITION __GSHandlerCheck_SEH(PEXCEPTION_RECORD pXcptRec, PEXCEPTION_REGISTRATION_RECORD pXcptRegRec,
+ PCONTEXT pCpuCtx, PDISPATCHER_CONTEXT pDispCtx)
+{
+ /*
+ * The HandlerData points to a scope table, which is then followed by GS_HANDLER_DATA.
+ *
+ * Sample offCookie values: 0521H (tst.cpp), 02caH (installNetLwf), and 0502H (installNetFlt).
+ */
+ SCOPE_TABLE const *pScopeTable = (SCOPE_TABLE const *)pDispCtx->HandlerData;
+ PCGS_HANDLER_DATA pHandlerData = (PCGS_HANDLER_DATA)&pScopeTable->ScopeRecord[pScopeTable->Count];
+
+ /*
+ * Locate the stack cookie and call the regular stack cookie checker routine.
+ * (Same code as in __GSHandlerCheck, fixes applies both places.)
+ */
+ /* Calculate the cookie address and read it. */
+ uintptr_t uPtrFrame = (uintptr_t)pXcptRegRec;
+ uint32_t offCookie = pHandlerData->u.offCookie;
+ if (offCookie & GS_HANDLER_OFF_COOKIE_HAS_ALIGNMENT)
+ {
+ uPtrFrame += pHandlerData->offAlignedBase;
+ uPtrFrame &= ~(uintptr_t)pHandlerData->uAlignmentMask;
+ }
+ uintptr_t uCookie = *(uintptr_t const *)(uPtrFrame + (int32_t)(offCookie & GS_HANDLER_OFF_COOKIE_MASK));
+
+ /* The stored cookie is xor'ed with the frame / registration record address
+ or with the frame pointer register if one is being used. In the latter
+ case, we have to add the frame offset to get the correct address. */
+ uintptr_t uXorAddr = (uintptr_t)pXcptRegRec;
+ PCIMAGE_UNWIND_INFO pUnwindInfo = (PCIMAGE_UNWIND_INFO)(pDispCtx->ImageBase + pDispCtx->FunctionEntry->UnwindInfoAddress);
+ if (pUnwindInfo->FrameRegister != 0)
+ uXorAddr += pUnwindInfo->FrameOffset << 4;
+
+ /* This call will not return on failure. */
+ __security_check_cookie(uCookie ^ uXorAddr);
+
+
+ /*
+ * Now call the handler if the GS handler data indicates that we ought to.
+ */
+ if ( (IS_UNWINDING(pXcptRec->ExceptionFlags) ? GS_HANDLER_OFF_COOKIE_IS_UHANDLER : GS_HANDLER_OFF_COOKIE_IS_EHANDLER)
+ & pHandlerData->u.offCookie)
+ return __C_specific_handler(pXcptRec, pXcptRegRec, pCpuCtx, pDispCtx);
+
+ return ExceptionContinueSearch;
+}
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/stack-except-vcc.cpp b/src/VBox/Runtime/common/compiler/vcc/stack-except-vcc.cpp
new file mode 100644
index 00000000..13ea35b1
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/stack-except-vcc.cpp
@@ -0,0 +1,106 @@
+/* $Id: stack-except-vcc.cpp $ */
+/** @file
+ * IPRT - Visual C++ Compiler - Stack Checking, __GSHandlerCheck.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/nocrt.h"
+
+#include "except-vcc.h"
+
+
+#if !defined(RT_ARCH_AMD64)
+# error "This file is for AMD64 (and probably ARM, but needs porting)"
+#endif
+
+
+
+/**
+ * Check the stack cookie before calling the exception handler.
+ *
+ * This is to prevent attackers from bypassing stack cookie checking by
+ * triggering an exception.
+ *
+ * This does not call any C++ exception handlers, as it's probably (still
+ * figuring this stuff out) only used when C++ exceptions are disabled.
+ *
+ * @returns Exception disposition.
+ * @param pXcptRec The exception record.
+ * @param pXcptRegRec The exception registration record, taken to be the frame
+ * address.
+ * @param pCpuCtx The CPU context for the exception.
+ * @param pDispCtx Dispatcher context.
+ */
+extern "C" __declspec(guard(suppress))
+EXCEPTION_DISPOSITION __GSHandlerCheck(PEXCEPTION_RECORD pXcptRec, PEXCEPTION_REGISTRATION_RECORD pXcptRegRec,
+ PCONTEXT pCpuCtx, PDISPATCHER_CONTEXT pDispCtx)
+{
+ RT_NOREF(pXcptRec, pCpuCtx);
+
+ /*
+ * Only GS handler data here.
+ */
+ PCGS_HANDLER_DATA pHandlerData = (PCGS_HANDLER_DATA)pDispCtx->HandlerData;
+
+ /*
+ * Locate the stack cookie and call the regular stack cookie checker routine.
+ * (Same code as in __GSHandlerCheck_SEH, fixes applies both places.)
+ */
+ /* Calculate the cookie address and read it. */
+ uintptr_t uPtrFrame = (uintptr_t)pXcptRegRec;
+ uint32_t offCookie = pHandlerData->u.offCookie;
+ if (offCookie & GS_HANDLER_OFF_COOKIE_HAS_ALIGNMENT)
+ {
+ uPtrFrame += pHandlerData->offAlignedBase;
+ uPtrFrame &= ~(uintptr_t)pHandlerData->uAlignmentMask;
+ }
+ uintptr_t uCookie = *(uintptr_t const *)(uPtrFrame + (int32_t)(offCookie & GS_HANDLER_OFF_COOKIE_MASK));
+
+ /* The stored cookie is xor'ed with the frame / registration record address
+ or with the frame pointer register if one is being used. In the latter
+ case, we have to add the frame offset to get the correct address. */
+ uintptr_t uXorAddr = (uintptr_t)pXcptRegRec;
+ PCIMAGE_UNWIND_INFO pUnwindInfo = (PCIMAGE_UNWIND_INFO)(pDispCtx->ImageBase + pDispCtx->FunctionEntry->UnwindInfoAddress);
+ if (pUnwindInfo->FrameRegister != 0)
+ uXorAddr += pUnwindInfo->FrameOffset << 4;
+
+ /* This call will not return on failure. */
+ __security_check_cookie(uCookie ^ uXorAddr);
+
+ return ExceptionContinueSearch;
+}
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm
new file mode 100644
index 00000000..da2e2965
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm
@@ -0,0 +1,157 @@
+; $Id: stack-probe-vcc.asm $
+;; @file
+; IPRT - Stack related Visual C++ support routines.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+
+;*********************************************************************************************************************************
+;* Header Files *
+;*********************************************************************************************************************************
+%if 0 ; YASM's builtin SEH64 support doesn't cope well with code alignment, so use our own.
+ %define RT_ASM_WITH_SEH64
+%else
+ %define RT_ASM_WITH_SEH64_ALT
+%endif
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+;;
+; Probe stack to trigger guard faults, and for x86 to allocate stack space.
+;
+; @param xAX Frame size.
+; @uses AMD64: Probably nothing. EAX is certainly not supposed to change.
+; x86: ESP = ESP - EAX; EFLAGS, nothing else
+;
+ALIGNCODE(64)
+GLOBALNAME_RAW __alloca_probe, __alloca_probe, function
+BEGINPROC_RAW __chkstk
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ push xAX
+ SEH64_PUSH_GREG xAX
+ push xBX
+ SEH64_PUSH_GREG xBX
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Adjust eax so we're relative to [xBP - xCB*2].
+ ;
+ sub xAX, xCB * 4
+ jle .touch_loop_done ; jump if rax < xCB*4, very unlikely
+
+ ;
+ ; Subtract what's left of the current page from eax and only engage
+ ; the touch loop if (int)xAX > 0.
+ ;
+ lea ebx, [ebp - xCB * 2]
+ and ebx, PAGE_SIZE - 1
+ sub xAX, xBX
+ jnl .touch_loop ; jump if pages to touch.
+
+.touch_loop_done:
+ pop xBX
+ pop xAX
+ leave
+%ifndef RT_ARCH_X86
+ ret
+%else
+ ;
+ ; Do the stack space allocation and jump to the return location.
+ ;
+ sub esp, eax
+ add esp, 4
+ jmp dword [esp + eax - 4]
+%endif
+
+ ;
+ ; The touch loop.
+ ;
+.touch_loop:
+ sub xBX, PAGE_SIZE
+%if 1
+ mov [xBP + xBX - xCB * 2], bl
+%else
+ or byte [xBP + xBX - xCB * 2], 0 ; non-destructive variant...
+%endif
+ sub xAX, PAGE_SIZE
+ jnl .touch_loop
+ jmp .touch_loop_done
+ENDPROC_RAW __chkstk
+
+
+%ifdef RT_ARCH_X86
+;;
+; 8 and 16 byte aligned alloca w/ probing.
+;
+; This routine adjusts the allocation size so __chkstk will return a
+; correctly aligned allocation.
+;
+; @param xAX Unaligned allocation size.
+;
+%macro __alloc_probe_xxx 1
+ALIGNCODE(16)
+BEGINPROC_RAW __alloca_probe_ %+ %1
+ push ecx
+
+ ;
+ ; Calc the ESP address after the allocation and adjust EAX so that it
+ ; will be aligned as desired.
+ ;
+ lea ecx, [esp + 8]
+ sub ecx, eax
+ and ecx, %1 - 1
+ add eax, ecx
+ jc .bad_alloc_size
+.continue:
+
+ pop ecx
+ jmp __alloca_probe
+
+.bad_alloc_size:
+ %ifdef RT_STRICT
+ int3
+ %endif
+ or eax, 0xfffffff0
+ jmp .continue
+ENDPROC_RAW __alloca_probe_ %+ %1
+%endmacro
+
+__alloc_probe_xxx 16
+__alloc_probe_xxx 8
+%endif ; RT_ARCH_X86
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm
new file mode 100644
index 00000000..edfd5750
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm
@@ -0,0 +1,639 @@
+; $Id: stack-vcc.asm $
+;; @file
+; IPRT - Stack related Visual C++ support routines.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+
+;*********************************************************************************************************************************
+;* Header Files *
+;*********************************************************************************************************************************
+%if 0 ; YASM's builtin SEH64 support doesn't cope well with code alignment, so use our own.
+ %define RT_ASM_WITH_SEH64
+%else
+ %define RT_ASM_WITH_SEH64_ALT
+%endif
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+%ifdef RT_ARCH_AMD64
+ %include "iprt/win/context-amd64.mac"
+%else
+ %include "iprt/win/context-x86.mac"
+%endif
+
+
+;*********************************************************************************************************************************
+;* Structures and Typedefs *
+;*********************************************************************************************************************************
+
+;; Variable descriptor.
+struc RTC_VAR_DESC_T
+ .offFrame resd 1
+ .cbVar resd 1
+ alignb RTCCPTR_CB
+ .pszName RTCCPTR_RES 1
+endstruc
+
+;; Frame descriptor.
+struc RTC_FRAME_DESC_T
+ .cVars resd 1
+ alignb RTCCPTR_CB
+ .paVars RTCCPTR_RES 1 ; Array of RTC_VAR_DESC_T.
+endstruc
+
+;; An alloca allocation.
+struc RTC_ALLOCA_ENTRY_T
+ .uGuard1 resd 1
+ .pNext RTCCPTR_RES 1 ; Misaligned.
+%if ARCH_BITS == 32
+ .pNextPad resd 1
+%endif
+ .cb RTCCPTR_RES 1 ; Misaligned.
+%if ARCH_BITS == 32
+ .cbPad resd 1
+%endif
+ .auGuard2 resd 3
+endstruc
+
+%ifdef RT_ARCH_X86
+ %define FASTCALL_NAME(a_Name, a_cbArgs) $@ %+ a_Name %+ @ %+ a_cbArgs
+%else
+ %define FASTCALL_NAME(a_Name, a_cbArgs) NAME(a_Name)
+%endif
+
+
+;*********************************************************************************************************************************
+;* Defined Constants And Macros *
+;*********************************************************************************************************************************
+%define VARIABLE_MARKER_PRE 0xcccccccc
+%define VARIABLE_MARKER_POST 0xcccccccc
+
+%define ALLOCA_FILLER_BYTE 0xcc
+%define ALLOCA_FILLER_32 0xcccccccc
+
+
+;*********************************************************************************************************************************
+;* Global Variables *
+;*********************************************************************************************************************************
+BEGINDATA
+GLOBALNAME __security_cookie
+ dd 0xdeadbeef
+ dd 0x0c00ffe0
+
+
+;*********************************************************************************************************************************
+;* External Symbols *
+;*********************************************************************************************************************************
+BEGINCODE
+extern NAME(rtVccStackVarCorrupted)
+extern NAME(rtVccSecurityCookieMismatch)
+extern NAME(rtVccRangeCheckFailed)
+%ifdef RT_ARCH_X86
+extern NAME(rtVccCheckEspFailed)
+%endif
+
+
+
+;;
+; This just initializes a global and calls _RTC_SetErrorFuncW to NULL, and
+; since we don't have either of those we have nothing to do here.
+BEGINPROC _RTC_InitBase
+ SEH64_END_PROLOGUE
+ ret
+ENDPROC _RTC_InitBase
+
+
+;;
+; Nothing to do here.
+BEGINPROC _RTC_Shutdown
+ SEH64_END_PROLOGUE
+ ret
+ENDPROC _RTC_Shutdown
+
+
+
+
+;;
+; Checks stack variable markers.
+;
+; This seems to be a regular C function in the CRT, but x86 is conveniently
+; using the fastcall convention which makes it very similar to amd64.
+;
+; We try make this as sleek as possible, leaving all the trouble for when we
+; find a corrupted stack variable and need to call a C function to complain.
+;
+; @param pStackFrame The caller RSP/ESP. [RCX/ECX]
+; @param pFrameDesc Frame descriptor. [RDX/EDX]
+;
+ALIGNCODE(64)
+BEGINPROC_RAW FASTCALL_NAME(_RTC_CheckStackVars, 8)
+ push xBP
+ SEH64_PUSH_xBP
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the variable count into eax and check that it's not zero.
+ ;
+ mov eax, [xDX + RTC_FRAME_DESC_T.cVars]
+ test eax, eax
+ jz .return
+
+ ;
+ ; Make edx/rdx point to the current variable and xBP be the frame pointer.
+ ; The latter frees up xCX for scratch use and incidentally make stack access
+ ; go via SS instead of DS (mostly irrlevant in 64-bit and 32-bit mode).
+ ;
+ mov xDX, [xDX + RTC_FRAME_DESC_T.paVars]
+ mov xBP, xCX
+
+ ;
+ ; Loop thru the variables and check that their markers/fences haven't be
+ ; trampled over.
+ ;
+.next_var:
+ ; Marker before the variable.
+%if ARCH_BITS == 64
+ movsxd rcx, dword [xDX + RTC_VAR_DESC_T.offFrame]
+%else
+ mov xCX, dword [xDX + RTC_VAR_DESC_T.offFrame]
+%endif
+ cmp dword [xBP + xCX - 4], VARIABLE_MARKER_PRE
+ jne rtVccCheckStackVarsFailed
+
+ ; Marker after the variable.
+ add ecx, dword [xDX + RTC_VAR_DESC_T.cbVar]
+%if ARCH_BITS == 64
+ movsxd rcx, ecx
+%endif
+ cmp dword [xBP + xCX], VARIABLE_MARKER_POST
+ jne rtVccCheckStackVarsFailed
+
+ ;
+ ; Advance to the next variable.
+ ;
+.advance:
+ add xDX, RTC_VAR_DESC_T_size
+ dec eax
+ jnz .next_var
+
+ ;
+ ; Return.
+ ;
+.return:
+ pop xBP
+ ret
+ENDPROC_RAW FASTCALL_NAME(_RTC_CheckStackVars, 8)
+
+;
+; Sub-function for _RTC_CheckStackVars, for purposes of SEH64 unwinding.
+;
+; Note! While we consider this fatal and will terminate the application, the
+; compiler guys do not seem to think it is all that horrible and will
+; report failure, maybe do an int3, and then try continue execution.
+;
+BEGINPROC_RAW rtVccCheckStackVarsFailed
+ nop ;push xBP - done in parent function
+ SEH64_PUSH_xBP
+ mov xCX, xBP ; xCX = caller pStackFrame. xBP free to become frame pointer.
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ pushf
+ push xAX
+ SEH64_PUSH_GREG xAX
+ sub xSP, CONTEXT_SIZE + 20h
+ SEH64_ALLOCATE_STACK (CONTEXT_SIZE + 20h)
+ SEH64_END_PROLOGUE
+
+ lea xAX, [xBP - CONTEXT_SIZE]
+ call NAME(rtVccCaptureContext)
+
+ ; rtVccStackVarCorrupted(uint8_t *pbFrame, RTC_VAR_DESC_T const *pVar, PCONTEXT)
+.again:
+%ifdef RT_ARCH_AMD64
+ lea r8, [xBP - CONTEXT_SIZE]
+%else
+ lea xAX, [xBP - CONTEXT_SIZE]
+ mov [xSP + 8], xAX
+ mov [xSP + 4], xDX
+ mov [xSP], xCX
+%endif
+ call NAME(rtVccStackVarCorrupted)
+ jmp .again
+ENDPROC_RAW rtVccCheckStackVarsFailed
+
+
+%ifdef RT_ARCH_X86
+;;
+; Called to follow up on a 'CMP ESP, EBP' kind of instruction,
+; expected to report failure if the compare failed.
+;
+; Note! While we consider this fatal and will terminate the application, the
+; compiler guys do not seem to think it is all that horrible and will
+; report failure, maybe do an int3, and then try continue execution.
+;
+ALIGNCODE(16)
+BEGINPROC _RTC_CheckEsp
+ jne .unexpected_esp
+ ret
+
+.unexpected_esp:
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ pushf
+ push xAX
+ SEH64_PUSH_GREG xAX
+ sub xSP, CONTEXT_SIZE + 20h
+ SEH64_ALLOCATE_STACK (CONTEXT_SIZE + 20h)
+ SEH64_END_PROLOGUE
+
+ lea xAX, [xBP - CONTEXT_SIZE]
+ call NAME(rtVccCaptureContext)
+
+ ; rtVccCheckEspFailed(PCONTEXT)
+.again:
+ lea xAX, [xBP - CONTEXT_SIZE]
+%ifdef RT_ARCH_AMD64
+ mov xCX, xAX
+%else
+ mov [xSP], xAX
+%endif
+ call NAME(rtVccCheckEspFailed)
+ jmp .again
+
+ENDPROC _RTC_CheckEsp
+%endif ; RT_ARCH_X86
+
+
+
+;;
+; Initialize an alloca allocation list entry and add it to it.
+;
+; When this is call, presumably _RTC_CheckStackVars2 is used to verify the frame.
+;
+; @param pNewEntry Pointer to the new entry. [RCX/ECX]
+; @param cbEntry The entry size, including header. [RDX/EDX]
+; @param ppHead Pointer to the list head pointer. [R8/stack]
+;
+ALIGNCODE(64)
+BEGINPROC_RAW FASTCALL_NAME(_RTC_AllocaHelper, 12)
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Check that input isn't NULL or the size isn't zero.
+ ;
+ test xCX, xCX
+ jz .return
+ test xDX, xDX
+ jz .return
+%if ARCH_BITS == 64
+ test r8, r8
+%else
+ cmp dword [xSP + xCB], 0
+%endif
+ jz .return
+
+ ;
+ ; Memset the memory to ALLOCA_FILLER
+ ;
+%if ARCH_BITS == 64
+ mov r10, rdi ; save rdi
+ mov r11, rcx ; save pNewEntry
+%else
+ push xDI
+ push xCX
+ cld ; paranoia
+%endif
+
+ mov al, ALLOCA_FILLER_BYTE
+ mov xDI, xCX ; entry pointer
+ mov xCX, xDX ; entry size (in bytes)
+ rep stosb
+
+%if ARCH_BITS == 64
+ mov rdi, r10
+%else
+ pop xCX
+ pop xDI
+%endif
+
+ ;
+ ; Fill in the entry and link it as onto the head of the chain.
+ ;
+%if ARCH_BITS == 64
+ mov [r11 + RTC_ALLOCA_ENTRY_T.cb], xDX
+ mov xAX, [r8]
+ mov [r11 + RTC_ALLOCA_ENTRY_T.pNext], xAX
+ mov [r8], r11
+%else
+ mov [xCX + RTC_ALLOCA_ENTRY_T.cb], xDX
+ mov xAX, [xSP + xCB] ; ppHead
+ mov xDX, [xAX]
+ mov [xCX + RTC_ALLOCA_ENTRY_T.pNext], xDX
+ mov [xAX], xCX
+%endif
+
+.return:
+%if ARCH_BITS == 64
+ ret
+%else
+ ret 4
+%endif
+ENDPROC_RAW FASTCALL_NAME(_RTC_AllocaHelper, 12)
+
+
+;;
+; Checks if the security cookie ok, complaining and terminating if it isn't.
+;
+ALIGNCODE(16)
+BEGINPROC_RAW FASTCALL_NAME(__security_check_cookie, 4)
+ SEH64_END_PROLOGUE
+ cmp xCX, [NAME(__security_cookie) xWrtRIP]
+ jne rtVccSecurityCookieFailed
+ ;; amd64 version checks if the top 16 bits are zero, we skip that for now.
+ ret
+ENDPROC_RAW FASTCALL_NAME(__security_check_cookie, 4)
+
+; Sub-function for __security_check_cookie, for purposes of SEH64 unwinding.
+BEGINPROC_RAW rtVccSecurityCookieFailed
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ pushf
+ push xAX
+ SEH64_PUSH_GREG xAX
+ sub xSP, CONTEXT_SIZE + 20h
+ SEH64_ALLOCATE_STACK (CONTEXT_SIZE + 20h)
+ SEH64_END_PROLOGUE
+
+ lea xAX, [xBP - CONTEXT_SIZE]
+ call NAME(rtVccCaptureContext)
+
+ ; rtVccSecurityCookieMismatch(uCookie, PCONTEXT)
+.again:
+%ifdef RT_ARCH_AMD64
+ lea xDX, [xBP - CONTEXT_SIZE]
+%else
+ lea xAX, [xBP - CONTEXT_SIZE]
+ mov [xSP + 4], xAX
+ mov [xSP], xCX
+%endif
+ call NAME(rtVccSecurityCookieMismatch)
+ jmp .again
+ENDPROC_RAW rtVccSecurityCookieFailed
+
+
+;;
+; Generated when using /GS - buffer security checks - so, fatal.
+;
+; Doesn't seem to take any parameters.
+;
+BEGINPROC __report_rangecheckfailure
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ pushf
+ push xAX
+ SEH64_PUSH_GREG xAX
+ sub xSP, CONTEXT_SIZE + 20h
+ SEH64_ALLOCATE_STACK (CONTEXT_SIZE + 20h)
+ SEH64_END_PROLOGUE
+
+ lea xAX, [xBP - CONTEXT_SIZE]
+ call NAME(rtVccCaptureContext)
+
+ ; rtVccRangeCheckFailed(PCONTEXT)
+.again:
+ lea xAX, [xBP - CONTEXT_SIZE]
+%ifdef RT_ARCH_AMD64
+ mov xCX, xAX
+%else
+ mov [xSP], xAX
+%endif
+ call NAME(rtVccRangeCheckFailed)
+ jmp .again
+ENDPROC __report_rangecheckfailure
+
+
+%if 0 ; Currently not treating these as completely fatal, just like the
+ ; compiler guys do. I'm sure the compiler only generate these calls
+ ; if it thinks a variable could be used uninitialized, however I'm not
+ ; really sure if there is a runtime check in addition or if it's an
+ ; action that always will be taken in a code path deemed to be bad.
+ ; Judging from the warnings, the compile time analysis leave lots to be
+ ; desired (lots of false positives).
+;;
+; Not entirely sure how and when the compiler generates these.
+; extern "C" void __cdecl _RTC_UninitUse(const char *pszVar)
+BEGINPROC _RTC_UninitUse
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ pushf
+ push xAX
+ SEH64_PUSH_GREG xAX
+ sub xSP, CONTEXT_SIZE + 20h
+ SEH64_ALLOCATE_STACK (CONTEXT_SIZE + 20h)
+ SEH64_END_PROLOGUE
+
+ lea xAX, [xBP - CONTEXT_SIZE]
+ call NAME(rtVccCaptureContext)
+
+ extern NAME(rtVccUninitializedVariableUse)
+ ; rtVccUninitializedVariableUse(const char *pszVar, PCONTEXT)
+.again:
+%ifdef RT_ARCH_AMD64
+ lea xDX, [xBP - CONTEXT_SIZE]
+%else
+ lea xAX, [xBP - CONTEXT_SIZE]
+ mov [xSP + xCB], xAX
+ mov xAX, [xBP + xCB * 2]
+ mov [xSP], xAX
+%endif
+ call NAME(rtVccUninitializedVariableUse)
+ jmp .again
+ENDPROC _RTC_UninitUse
+%endif
+
+;;
+; Internal worker that creates a CONTEXT record for the caller.
+;
+; This expects a old-style stack frame setup, with xBP as base, such that:
+; xBP+xCB*1: Return address -> Rip/Eip
+; xBP+xCB*0: Return xBP -> Rbp/Ebp
+; xBP-xCB*1: EFLAGS -> EFlags
+; xBP-xCB*2: Saved xAX -> Rax/Eax
+;
+; @param pCtx xAX Pointer to a CONTEXT structure.
+;
+BEGINPROC rtVccCaptureContext
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ mov [xAX + CONTEXT.Rcx], rcx
+ mov [xAX + CONTEXT.Rdx], rdx
+ mov rcx, [xBP - xCB*2]
+ mov [xAX + CONTEXT.Rax], ecx
+ mov [xAX + CONTEXT.Rbx], rbx
+ lea rcx, [xBP + xCB*2]
+ mov [xAX + CONTEXT.Rsp], rcx
+ mov rdx, [xBP]
+ mov [xAX + CONTEXT.Rbp], rdx
+ mov [xAX + CONTEXT.Rdi], rdi
+ mov [xAX + CONTEXT.Rsi], rsi
+ mov [xAX + CONTEXT.R8], r8
+ mov [xAX + CONTEXT.R9], r9
+ mov [xAX + CONTEXT.R10], r10
+ mov [xAX + CONTEXT.R11], r11
+ mov [xAX + CONTEXT.R12], r12
+ mov [xAX + CONTEXT.R13], r13
+ mov [xAX + CONTEXT.R14], r14
+ mov [xAX + CONTEXT.R15], r15
+
+ mov rcx, [xBP + xCB*1]
+ mov [xAX + CONTEXT.Rip], rcx
+ mov edx, [xBP - xCB*1]
+ mov [xAX + CONTEXT.EFlags], edx
+
+ mov dx, ss
+ mov [xAX + CONTEXT.SegSs], dx
+ mov cx, cs
+ mov [xAX + CONTEXT.SegCs], cx
+ mov dx, ds
+ mov [xAX + CONTEXT.SegDs], dx
+ mov cx, es
+ mov [xAX + CONTEXT.SegEs], cx
+ mov dx, fs
+ mov [xAX + CONTEXT.SegFs], dx
+ mov cx, gs
+ mov [xAX + CONTEXT.SegGs], cx
+
+ mov dword [xAX + CONTEXT.ContextFlags], CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS
+
+ ; Clear stuff we didn't set.
+ xor edx, edx
+ mov [xAX + CONTEXT.P1Home], rdx
+ mov [xAX + CONTEXT.P2Home], rdx
+ mov [xAX + CONTEXT.P3Home], rdx
+ mov [xAX + CONTEXT.P4Home], rdx
+ mov [xAX + CONTEXT.P5Home], rdx
+ mov [xAX + CONTEXT.P6Home], rdx
+ mov [xAX + CONTEXT.MxCsr], edx
+ mov [xAX + CONTEXT.Dr0], rdx
+ mov [xAX + CONTEXT.Dr1], rdx
+ mov [xAX + CONTEXT.Dr2], rdx
+ mov [xAX + CONTEXT.Dr3], rdx
+ mov [xAX + CONTEXT.Dr6], rdx
+ mov [xAX + CONTEXT.Dr7], rdx
+
+ mov ecx, CONTEXT_size - CONTEXT.FltSave
+ AssertCompile(((CONTEXT_size - CONTEXT.FltSave) % 8) == 0)
+.again:
+ mov [xAX + CONTEXT.FltSave + xCX - 8], rdx
+ sub ecx, 8
+ jnz .again
+
+ ; Restore edx and ecx.
+ mov rcx, [xAX + CONTEXT.Rcx]
+ mov rdx, [xAX + CONTEXT.Rdx]
+
+%elifdef RT_ARCH_X86
+
+ mov [xAX + CONTEXT.Ecx], ecx
+ mov [xAX + CONTEXT.Edx], edx
+ mov ecx, [xBP - xCB*2]
+ mov [xAX + CONTEXT.Eax], ecx
+ mov [xAX + CONTEXT.Ebx], ebx
+ lea ecx, [xBP + xCB*2]
+ mov [xAX + CONTEXT.Esp], ecx
+ mov edx, [xBP]
+ mov [xAX + CONTEXT.Ebp], edx
+ mov [xAX + CONTEXT.Edi], edi
+ mov [xAX + CONTEXT.Esi], esi
+
+ mov ecx, [xBP + xCB]
+ mov [xAX + CONTEXT.Eip], ecx
+ mov ecx, [xBP - xCB*1]
+ mov [xAX + CONTEXT.EFlags], ecx
+
+ mov dx, ss
+ movzx edx, dx
+ mov [xAX + CONTEXT.SegSs], edx
+ mov cx, cs
+ movzx ecx, cx
+ mov [xAX + CONTEXT.SegCs], ecx
+ mov dx, ds
+ movzx edx, dx
+ mov [xAX + CONTEXT.SegDs], edx
+ mov cx, es
+ movzx ecx, cx
+ mov [xAX + CONTEXT.SegEs], ecx
+ mov dx, fs
+ movzx edx, dx
+ mov [xAX + CONTEXT.SegFs], edx
+ mov cx, gs
+ movzx ecx, cx
+ mov [xAX + CONTEXT.SegGs], ecx
+
+ mov dword [xAX + CONTEXT.ContextFlags], CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS
+
+ ; Clear stuff we didn't set.
+ xor edx, edx
+ mov [xAX + CONTEXT.Dr0], edx
+ mov [xAX + CONTEXT.Dr1], edx
+ mov [xAX + CONTEXT.Dr2], edx
+ mov [xAX + CONTEXT.Dr3], edx
+ mov [xAX + CONTEXT.Dr6], edx
+ mov [xAX + CONTEXT.Dr7], edx
+
+ mov ecx, CONTEXT_size - CONTEXT.ExtendedRegisters
+.again:
+ mov [xAX + CONTEXT.ExtendedRegisters + xCX - 4], edx
+ sub ecx, 4
+ jnz .again
+
+ ; Restore edx and ecx.
+ mov ecx, [xAX + CONTEXT.Ecx]
+ mov edx, [xAX + CONTEXT.Edx]
+
+%else
+ %error RT_ARCH
+%endif
+ ret
+ENDPROC rtVccCaptureContext
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/stacksup-vcc.cpp b/src/VBox/Runtime/common/compiler/vcc/stacksup-vcc.cpp
new file mode 100644
index 00000000..8794cd07
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/stacksup-vcc.cpp
@@ -0,0 +1,371 @@
+/* $Id: stacksup-vcc.cpp $ */
+/** @file
+ * IPRT - Visual C++ Compiler - Stack Checking C/C++ Support.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/nocrt.h"
+
+#include <iprt/asm.h>
+#include <iprt/asm-amd64-x86.h>
+#ifndef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+# include <iprt/assert.h>
+#endif
+
+#include "internal/compiler-vcc.h"
+#ifdef IN_RING3
+# include <iprt/win/windows.h>
+# include "../../../r3/win/internal-r3-win.h" /* ugly, but need some windows API function pointers */
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Gets the program counter member of Windows' CONTEXT structure. */
+#if defined(RT_ARCH_AMD64)
+# define MY_GET_PC_FROM_CONTEXT(a_pCtx) ((a_pCtx)->Rip)
+#elif defined(RT_ARCH_X86)
+# define MY_GET_PC_FROM_CONTEXT(a_pCtx) ((a_pCtx)->Eip)
+#else
+# error "Port Me!"
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Variable descriptor. */
+typedef struct RTC_VAR_DESC_T
+{
+ int32_t offFrame;
+ uint32_t cbVar;
+ const char *pszName;
+} RTC_VAR_DESC_T;
+
+/** Frame descriptor. */
+typedef struct RTC_FRAME_DESC_T
+{
+ uint32_t cVars;
+ RTC_VAR_DESC_T const *paVars;
+} RTC_FRAME_DESC_T;
+
+#define VARIABLE_MARKER_PRE 0xcccccccc
+#define VARIABLE_MARKER_POST 0xcccccccc
+
+
+/**
+ * Alloca allocation entry.
+ * @note For whatever reason the pNext and cb members are misaligned on 64-bit
+ * targets. 32-bit targets OTOH adds padding to keep the structure size
+ * and pNext + cb offsets the same.
+ */
+#pragma pack(4)
+typedef struct RTC_ALLOC_ENTRY
+{
+ uint32_t uGuard1;
+ RTC_ALLOC_ENTRY *pNext;
+#if ARCH_BITS == 32
+ uint32_t pNextPad;
+#endif
+ size_t cb;
+#if ARCH_BITS == 32
+ uint32_t cbPad;
+#endif
+ uint32_t auGuard2[3];
+} RTC_ALLOC_ENTRY;
+#pragma pack()
+
+#define ALLOCA_FILLER_BYTE 0xcc
+#define ALLOCA_FILLER_32 0xcccccccc
+
+
+/*********************************************************************************************************************************
+* External Symbols *
+*********************************************************************************************************************************/
+extern "C" void __fastcall _RTC_CheckStackVars(uint8_t *pbFrame, RTC_VAR_DESC_T const *pVar); /* nocrt-stack.asm */
+extern "C" uintptr_t __security_cookie;
+
+
+/**
+ * Initializes the security cookie value.
+ *
+ * This must be called as the first thing by the startup code. We must also no
+ * do anything fancy here.
+ */
+void rtVccInitSecurityCookie(void) RT_NOEXCEPT
+{
+ __security_cookie = (uintptr_t)ASMReadTSC() ^ (uintptr_t)&__security_cookie;
+}
+
+
+/**
+ * Reports a security error.
+ *
+ * @param uFastFailCode The fast fail code.
+ * @param pCpuCtx The CPU context at the failure location.
+ */
+static DECL_NO_RETURN(void) rtVccFatalSecurityErrorWithCtx(uint32_t uFastFailCode, PCONTEXT pCpuCtx)
+{
+#ifdef IN_RING3
+ /*
+ * Use the __fastfail() approach if available, it is more secure than the stuff below:
+ */
+ if (g_pfnIsProcessorFeaturePresent && g_pfnIsProcessorFeaturePresent(PF_FASTFAIL_AVAILABLE))
+ __fastfail(uFastFailCode);
+
+ /*
+ * Fallback for legacy systems.
+ */
+ if (g_pfnIsDebuggerPresent && g_pfnIsDebuggerPresent())
+ __debugbreak();
+
+ /* If we can, clear the unhandled exception filter and report and unhandled exception. */
+ if (g_pfnSetUnhandledExceptionFilter && g_pfnUnhandledExceptionFilter)
+ {
+ g_pfnSetUnhandledExceptionFilter(NULL);
+
+ EXCEPTION_RECORD XcptRec =
+ {
+ /* .ExceptionCode = */ STATUS_STACK_BUFFER_OVERRUN,
+ /* .ExceptionFlags = */ EXCEPTION_NONCONTINUABLE,
+ /* .ExceptionRecord = */ NULL,
+# ifdef RT_ARCH_AMD64
+ /* .ExceptionAddress = */ (void *)pCpuCtx->Rip,
+# elif defined(RT_ARCH_X86)
+ /* .ExceptionAddress = */ (void *)pCpuCtx->Eip,
+# else
+# error "Port me!"
+# endif
+ /* .NumberParameters = */ 1,
+ /* .ExceptionInformation = */ { uFastFailCode, }
+ };
+
+ EXCEPTION_POINTERS XcptPtrs = { &XcptRec, pCpuCtx };
+ g_pfnUnhandledExceptionFilter(&XcptPtrs);
+ }
+
+ for (;;)
+ TerminateProcess(GetCurrentProcess(), STATUS_STACK_BUFFER_OVERRUN);
+
+#else
+# error "Port ME!"
+#endif
+}
+
+
+DECLASM(void) rtVccStackVarCorrupted(uint8_t *pbFrame, RTC_VAR_DESC_T const *pVar, PCONTEXT pCpuCtx)
+{
+#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+ RTAssertMsg2("\n\n!!Stack corruption!!\n\n"
+ "%p LB %#x - %s\n",
+ pbFrame + pVar->offFrame, pVar->cbVar, pVar->pszName);
+#else
+ rtNoCrtFatalWriteBegin(RT_STR_TUPLE("\r\n\r\n!!Stack corruption!!\r\n\r\n"));
+ rtNoCrtFatalWritePtr(pbFrame + pVar->offFrame);
+ rtNoCrtFatalWrite(RT_STR_TUPLE(" LB "));
+ rtNoCrtFatalWriteX32(pVar->cbVar);
+ rtNoCrtFatalWrite(RT_STR_TUPLE(" - "));
+ rtNoCrtFatalWriteStr(pVar->pszName);
+ rtNoCrtFatalWriteEnd(RT_STR_TUPLE("\r\n"));
+#endif
+ rtVccFatalSecurityErrorWithCtx(FAST_FAIL_INCORRECT_STACK, pCpuCtx);
+}
+
+
+DECLASM(void) rtVccSecurityCookieMismatch(uintptr_t uCookie, PCONTEXT pCpuCtx)
+{
+#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+ RTAssertMsg2("\n\n!!Stack cookie corruption!!\n\n"
+ "expected %p, found %p\n",
+ __security_cookie, uCookie);
+#else
+ rtNoCrtFatalWriteBegin(RT_STR_TUPLE("\r\n\r\n!!Stack cookie corruption!!\r\n\r\n"
+ "expected"));
+ rtNoCrtFatalWritePtr((void *)__security_cookie);
+ rtNoCrtFatalWrite(RT_STR_TUPLE(", found "));
+ rtNoCrtFatalWritePtr((void *)uCookie);
+ rtNoCrtFatalWriteEnd(RT_STR_TUPLE("\r\n"));
+#endif
+ rtVccFatalSecurityErrorWithCtx(FAST_FAIL_STACK_COOKIE_CHECK_FAILURE, pCpuCtx);
+}
+
+
+#ifdef RT_ARCH_X86
+DECLASM(void) rtVccCheckEspFailed(PCONTEXT pCpuCtx)
+{
+# ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+ RTAssertMsg2("\n\n!!ESP check failed!!\n\n"
+ "eip=%p esp=%p ebp=%p\n",
+ pCpuCtx->Eip, pCpuCtx->Esp, pCpuCtx->Ebp);
+# else
+ rtNoCrtFatalWriteBegin(RT_STR_TUPLE("\r\n\r\n!!ESP check failed!!\r\n\r\n"
+ "eip="));
+ rtNoCrtFatalWritePtr((void *)pCpuCtx->Eip);
+ rtNoCrtFatalWrite(RT_STR_TUPLE(" esp="));
+ rtNoCrtFatalWritePtr((void *)pCpuCtx->Esp);
+ rtNoCrtFatalWrite(RT_STR_TUPLE(" ebp="));
+ rtNoCrtFatalWritePtr((void *)pCpuCtx->Ebp);
+ rtNoCrtFatalWriteEnd(RT_STR_TUPLE("\r\n"));
+# endif
+ rtVccFatalSecurityErrorWithCtx(FAST_FAIL_INCORRECT_STACK, pCpuCtx);
+}
+#endif
+
+
+/** @todo reimplement in assembly (feeling too lazy right now). */
+extern "C" void __fastcall _RTC_CheckStackVars2(uint8_t *pbFrame, RTC_VAR_DESC_T const *pVar, RTC_ALLOC_ENTRY *pHead)
+{
+ while (pHead)
+ {
+ if ( pHead->uGuard1 == ALLOCA_FILLER_32
+#if 1 && ARCH_BITS == 32
+ && pHead->pNextPad == ALLOCA_FILLER_32
+ && pHead->cbPad == ALLOCA_FILLER_32
+#endif
+ && pHead->auGuard2[0] == ALLOCA_FILLER_32
+ && pHead->auGuard2[1] == ALLOCA_FILLER_32
+ && pHead->auGuard2[2] == ALLOCA_FILLER_32
+ && *(uint32_t const *)((uint8_t const *)pHead + pHead->cb - sizeof(uint32_t)) == ALLOCA_FILLER_32)
+ { /* likely */ }
+ else
+ {
+#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+ RTAssertMsg2("\n\n!!Stack corruption (alloca)!!\n\n"
+ "%p LB %#x\n",
+ pHead, pHead->cb);
+#else
+ rtNoCrtFatalWriteBegin(RT_STR_TUPLE("\r\n\r\n!!Stack corruption (alloca)!!\r\n\r\n"));
+ rtNoCrtFatalWritePtr(pHead);
+ rtNoCrtFatalWrite(RT_STR_TUPLE(" LB "));
+ rtNoCrtFatalWriteX64(pHead->cb);
+ rtNoCrtFatalWriteEnd(RT_STR_TUPLE("\r\n"));
+#endif
+#ifdef IN_RING3
+ if (g_pfnIsDebuggerPresent && g_pfnIsDebuggerPresent())
+#endif
+ RT_BREAKPOINT();
+ }
+ pHead = pHead->pNext;
+ }
+
+ _RTC_CheckStackVars(pbFrame, pVar);
+}
+
+
+DECLASM(void) rtVccRangeCheckFailed(PCONTEXT pCpuCtx)
+{
+# ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+ RTAssertMsg2("\n\n!!Range check failed at %p!!\n\n", MY_GET_PC_FROM_CONTEXT(pCpuCtx));
+# else
+ rtNoCrtFatalWriteBegin(RT_STR_TUPLE("\r\n\r\n!!Range check failed at "));
+ rtNoCrtFatalWritePtr((void *)MY_GET_PC_FROM_CONTEXT(pCpuCtx));
+ rtNoCrtFatalWriteEnd(RT_STR_TUPLE("!!\r\n"));
+# endif
+ rtVccFatalSecurityErrorWithCtx(FAST_FAIL_RANGE_CHECK_FAILURE, pCpuCtx);
+}
+
+
+/** Whether or not this should be a fatal issue remains to be seen. See
+ * explanation in stack-vcc.asm. */
+#if 0
+DECLASM(void) rtVccUninitializedVariableUse(const char *pszVar, PCONTEXT pCpuCtx)
+#else
+extern "C" void __cdecl _RTC_UninitUse(const char *pszVar)
+#endif
+{
+#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+ RTAssertMsg2("\n\n!!Used uninitialized variable %s at %p!!\n\n",
+ pszVar ? pszVar : "", ASMReturnAddress());
+#else
+ rtNoCrtFatalWriteBegin(RT_STR_TUPLE("\r\n\r\n!!Used uninitialized variable "));
+ rtNoCrtFatalWriteStr(pszVar);
+ rtNoCrtFatalWrite(RT_STR_TUPLE(" at "));
+ rtNoCrtFatalWritePtr(ASMReturnAddress());
+ rtNoCrtFatalWriteEnd(RT_STR_TUPLE("!!\r\n\r\n"));
+#endif
+#if 0
+ rtVccFatalSecurityErrorWithCtx(FAST_FAIL_FATAL_APP_EXIT, pCpuCtx);
+#else
+# ifdef IN_RING3
+ if (g_pfnIsDebuggerPresent && g_pfnIsDebuggerPresent())
+# endif
+ RT_BREAKPOINT();
+#endif
+}
+
+
+void rtVccCheckContextFailed(PCONTEXT pCpuCtx)
+{
+#ifdef IPRT_NOCRT_WITHOUT_FATAL_WRITE
+ RTAssertMsg2("\n\n!!Context (stack) check failed!!\n\n"
+ "PC=%p SP=%p BP=%p\n",
+# ifdef RT_ARCH_AMD64
+ pCpuCtx->Rip, pCpuCtx->Rsp, pCpuCtx->Rbp
+# elif defined(RT_ARCH_X86)
+ pCpuCtx->Eip, pCpuCtx->Esp, pCpuCtx->Ebp
+# else
+# error "unsupported arch"
+# endif
+ );
+#else
+ rtNoCrtFatalWriteBegin(RT_STR_TUPLE("\r\n\r\n!!Context (stack) check failed!!\r\n\r\n"
+ "PC="));
+# ifdef RT_ARCH_AMD64
+ rtNoCrtFatalWritePtr((void *)pCpuCtx->Rip);
+# elif defined(RT_ARCH_X86)
+ rtNoCrtFatalWritePtr((void *)pCpuCtx->Eip);
+# else
+# error "unsupported arch"
+# endif
+ rtNoCrtFatalWrite(RT_STR_TUPLE(" SP="));
+# ifdef RT_ARCH_AMD64
+ rtNoCrtFatalWritePtr((void *)pCpuCtx->Rsp);
+# elif defined(RT_ARCH_X86)
+ rtNoCrtFatalWritePtr((void *)pCpuCtx->Esp);
+# endif
+ rtNoCrtFatalWrite(RT_STR_TUPLE(" BP="));
+# ifdef RT_ARCH_AMD64
+ rtNoCrtFatalWritePtr((void *)pCpuCtx->Rbp);
+# elif defined(RT_ARCH_X86)
+ rtNoCrtFatalWritePtr((void *)pCpuCtx->Ebp);
+# endif
+ rtNoCrtFatalWriteEnd(RT_STR_TUPLE("\r\n"));
+#endif
+ rtVccFatalSecurityErrorWithCtx(FAST_FAIL_INVALID_SET_OF_CONTEXT, pCpuCtx);
+}
+
diff --git a/src/VBox/Runtime/common/compiler/vcc/tlsdir-vcc.c b/src/VBox/Runtime/common/compiler/vcc/tlsdir-vcc.c
new file mode 100644
index 00000000..b151bbe5
--- /dev/null
+++ b/src/VBox/Runtime/common/compiler/vcc/tlsdir-vcc.c
@@ -0,0 +1,114 @@
+/* $Id: tlsdir-vcc.c $ */
+/** @file
+ * IPRT - Visual C++ Compiler - PE/Windows TLS Directory.
+ *
+ * @note Doing this as a C file for same reasons as loadcfg-vcc.c.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_COMPILER_VCC_WITH_TLS_CALLBACK_SECTIONS
+#define IPRT_COMPILER_VCC_WITH_TLS_DATA_SECTIONS
+#include "internal/iprt.h"
+#include <iprt/win/windows.h>
+
+#include "internal/compiler-vcc.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** @name TLS callback arrays.
+ *
+ * The important thing here are the section names, the linker is told to merge
+ * and sort all the .CRT* sections into .rdata.
+ *
+ * @{ */
+/** Start of the TLS callback array. */
+__declspec(allocate(".CRT$XLA")) PIMAGE_TLS_CALLBACK g_apfnRTVccTlsCallbacks_Start[] = { NULL, };
+/** End of the TLS callback array (not actually used, but seems to be
+ * traditional). */
+__declspec(allocate(".CRT$XLZ")) PIMAGE_TLS_CALLBACK g_apfnRTVccTlsCallbacks_End[] = { NULL, };
+
+/* Tell the linker to merge the .CRT* sections into .rdata */
+#pragma comment(linker, "/merge:.CRT=.rdata ")
+/** @} */
+
+
+/** @name TLS data arrays.
+ *
+ * @{ */
+#pragma data_seg(".tls")
+
+/** Start of the TLS data.
+ * @note The linker has a reference to name '_tls_start' indicating a possible
+ * required naming convention here.
+ * @note Not sure if the byte here is ignored or not... In assembly we could
+ * optimize it out, I think... */
+extern __declspec(allocate(".tls")) char _tls_start = 0;
+
+/** End of the TLS callback array.
+ * @note The linker has a reference to name '_tls_end' indicating a possible
+ * required naming convention here. */
+extern __declspec(allocate(".tls$ZZZ")) char _tls_end = 0;
+
+#pragma data_seg ()
+/** @} */
+
+
+/** The TLS index for the module we're linked into.
+ * The linker has a reference to the name '_tls_start', so this is probably
+ * fixed in some way. */
+extern ULONG _tls_index = 0;
+
+
+/**
+ * The TLS directory for the PE image.
+ *
+ * The name of this is dictated by the linker, as it looks for a _tls_used
+ * symbol and puts it's address and (somehow) size in the TLS data dir entry.
+ */
+extern __declspec(".rdata$T") /* seems to be tranditional, doubt it is necessary */
+const RT_CONCAT(IMAGE_TLS_DIRECTORY, ARCH_BITS) _tls_used =
+{
+ /* .StartAddressOfRawData = */ (uintptr_t)&_tls_start,
+ /* .EndAddressOfRawData = */ (uintptr_t)&_tls_end,
+ /* .AddressOfIndex = */ (uintptr_t)&_tls_index,
+ /* .AddressOfCallBacks = */ (uintptr_t)&g_apfnRTVccTlsCallbacks_Start[1],
+ /* .SizeOfZeroFill = */ 0,
+ /* .Characteristics = */ 0,
+};
+
diff --git a/src/VBox/Runtime/common/crypto/Makefile.kup b/src/VBox/Runtime/common/crypto/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/Makefile.kup
diff --git a/src/VBox/Runtime/common/crypto/RTCrPkcs5Pbkdf2Hmac-openssl.cpp b/src/VBox/Runtime/common/crypto/RTCrPkcs5Pbkdf2Hmac-openssl.cpp
new file mode 100644
index 00000000..8b1481aa
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/RTCrPkcs5Pbkdf2Hmac-openssl.cpp
@@ -0,0 +1,86 @@
+/* $Id: RTCrPkcs5Pbkdf2Hmac-openssl.cpp $ */
+/** @file
+ * IPRT - Crypto - RTCrPkcs5Pbkdf2Hmac implementation using OpenSSL.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt.h"
+# include <iprt/crypto/misc.h>
+
+# include <iprt/err.h>
+# include <iprt/rand.h>
+# include <iprt/assert.h>
+
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/evp.h>
+# include "internal/openssl-post.h"
+
+
+RTDECL(int) RTCrPkcs5Pbkdf2Hmac(void const *pvInput, size_t cbInput, void const *pvSalt, size_t cbSalt, uint32_t cIterations,
+ RTDIGESTTYPE enmDigestType, size_t cbKeyLen, void *pvOutput)
+{
+ const EVP_MD *pDigest;
+ switch (enmDigestType)
+ {
+ case RTDIGESTTYPE_SHA1:
+ pDigest = EVP_sha1();
+ break;
+ case RTDIGESTTYPE_SHA256:
+ pDigest = EVP_sha256();
+ break;
+ case RTDIGESTTYPE_SHA512:
+ pDigest = EVP_sha512();
+ break;
+ default:
+ AssertFailedReturn(VERR_CR_DIGEST_NOT_SUPPORTED);
+ }
+
+ /* Note! This requires OpenSSL 1.0.0 or higher. */
+ Assert((size_t)(int)cbInput == cbInput);
+ Assert((size_t)(int)cbSalt == cbSalt);
+ Assert((size_t)(int)cbKeyLen == cbKeyLen);
+ int rcOssl = PKCS5_PBKDF2_HMAC((const char *)pvInput, (int)cbInput, (const unsigned char *)pvSalt, (int)cbSalt,
+ (int)cIterations, pDigest, (int)cbKeyLen, (unsigned char *)pvOutput);
+ if (rcOssl)
+ return VINF_SUCCESS;
+ return VERR_CR_PASSWORD_2_KEY_DERIVIATION_FAILED;
+}
+
+#endif /* IPRT_WITH_OPENSSL */
+
diff --git a/src/VBox/Runtime/common/crypto/RTCrRandBytes-openssl.cpp b/src/VBox/Runtime/common/crypto/RTCrRandBytes-openssl.cpp
new file mode 100644
index 00000000..a29a0240
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/RTCrRandBytes-openssl.cpp
@@ -0,0 +1,69 @@
+/* $Id: RTCrRandBytes-openssl.cpp $ */
+/** @file
+ * IPRT - Crypto - RTCrRandBytes implementation using OpenSSL.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt.h"
+# include <iprt/crypto/misc.h>
+
+# include <iprt/rand.h>
+# include <iprt/assert.h>
+# include <iprt/err.h>
+
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/rand.h>
+# include "internal/openssl-post.h"
+
+
+RTDECL(int) RTCrRandBytes(void *pvDst, size_t cbDst)
+{
+ /* Make sure the return buffer is always fully initialized in case the caller
+ doesn't properly check the return value. */
+ RTRandBytes(pvDst, cbDst); /* */
+
+ /* Get cryptographically strong random. */
+ rtCrOpenSslInit();
+ Assert((size_t)(int)cbDst == cbDst);
+ int rcOpenSsl = RAND_bytes((uint8_t *)pvDst, (int)cbDst);
+ return rcOpenSsl > 0 ? VINF_SUCCESS : rcOpenSsl == 0 ? VERR_CR_RANDOM_FAILED : VERR_CR_RANDOM_SETUP_FAILED;
+}
+
+#endif /* IPRT_WITH_OPENSSL */
+
diff --git a/src/VBox/Runtime/common/crypto/RTCrStoreCertAddFromJavaKeyStore.cpp b/src/VBox/Runtime/common/crypto/RTCrStoreCertAddFromJavaKeyStore.cpp
new file mode 100644
index 00000000..3e1194ef
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/RTCrStoreCertAddFromJavaKeyStore.cpp
@@ -0,0 +1,320 @@
+/* $Id: RTCrStoreCertAddFromJavaKeyStore.cpp $ */
+/** @file
+ * IPRT - Cryptographic (Certificate) Store, RTCrStoreCertAddFromJavaKeyStore.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_CRYPTO
+#include "internal/iprt.h"
+#include <iprt/crypto/store.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/log.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The java key store magic number (file endian). */
+#define JKS_MAGIC RT_H2BE_U32_C(UINT32_C(0xfeedfeed))
+/** Java key store format version 2 (file endian). */
+#define JKS_VERSION_2 RT_H2BE_U32_C(2)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Java key store (JKS) header.
+ */
+typedef struct JKSHEADER
+{
+ /** The magic (big endian) - JKS_MAGIC. */
+ uint32_t uMagic;
+ /** Format version number (big endian) - JKS_VERSION_2. */
+ uint32_t uVersion;
+ /** The number of keystore entries (big endian). */
+ uint32_t cEntries;
+} JKSHEADER;
+/** Pointer to a const java key store header. */
+typedef JKSHEADER const *PCJKSHEADER;
+
+
+RTDECL(int) RTCrStoreCertAddFromJavaKeyStoreInMem(RTCRSTORE hStore, uint32_t fFlags, void const *pvContent, size_t cbContent,
+ const char *pszErrorName, PRTERRINFO pErrInfo)
+{
+ uint8_t const *pbContent = (uint8_t const *)pvContent;
+
+ /*
+ * Check the header.
+ */
+ if (cbContent < sizeof(JKSHEADER) + RTSHA1_HASH_SIZE)
+ return RTErrInfoAddF(pErrInfo, VERR_WRONG_TYPE /** @todo better status codes */,
+ " Too small (%zu bytes) for java key store (%s)", cbContent, pszErrorName);
+ PCJKSHEADER pHdr = (PCJKSHEADER)pbContent;
+ if (pHdr->uMagic != JKS_MAGIC)
+ return RTErrInfoAddF(pErrInfo, VERR_WRONG_TYPE /** @todo better status codes */,
+ " Not java key store magic %#x (%s)", RT_BE2H_U32(pHdr->uMagic), pszErrorName);
+ if (pHdr->uVersion != JKS_VERSION_2)
+ return RTErrInfoAddF(pErrInfo, VERR_WRONG_TYPE /** @todo better status codes */,
+ " Unsupported java key store version %#x (%s)", RT_BE2H_U32(pHdr->uVersion), pszErrorName);
+ uint32_t const cEntries = RT_BE2H_U32(pHdr->cEntries);
+ if (cEntries > cbContent / 24) /* 24 = 4 for type, 4+ alias, 8 byte timestamp, 4 byte len, "X.509" or 4 cert count */
+ return RTErrInfoAddF(pErrInfo, VERR_WRONG_TYPE /** @todo better status codes */,
+ " Entry count %u is to high for %zu byte JKS (%s)", cEntries, cbContent, pszErrorName);
+
+ /*
+ * Here we should check the store signature. However, it always includes
+ * some kind of password, and that's somewhere we don't want to go right
+ * now. Later perhaps.
+ *
+ * We subtract it from the content size to make EOF checks simpler.
+ */
+ int rc = VINF_SUCCESS;
+#if 0 /* later */
+ RTSHA1CONTEXT Ctx;
+ RTSha1Init(&Ctx);
+
+ const char *pszCur = pszPassword;
+ for (;;)
+ {
+ RTUNICP Cp;
+ rc = RTStrGetCpEx(&pszCur, &Cp);
+ AssertRCReturn(rc, rc);
+ if (!Cp)
+ break;
+ uint8_t abWChar[2];
+ abWChar[0] = RT_BYTE2(Cp);
+ abWChar[1] = RT_BYTE1(Cp);
+ RTSha1Update(&Ctx, &abWChar, sizeof(abWChar));
+ }
+
+ RTSha1Update(&Ctx, RT_STR_TUPLE("Mighty Aphrodite"));
+
+ RTSha1Update(&Ctx, pbContent, cbContent - RTSHA1_HASH_SIZE);
+
+ uint8_t abSignature[RTSHA1_HASH_SIZE];
+ RTSha1Final(&Ctx, abSignature);
+
+ if (memcmp(&pbContent[cbContent - RTSHA1_HASH_SIZE], abSignature, RTSHA1_HASH_SIZE) != 0)
+ {
+ rc = RTErrInfoAddF(pErrInfo, VERR_MISMATCH, " File SHA-1 signature mismatch, %.*Rhxs instead of %.*Rhxs, for '%s'",
+ RTSHA1_HASH_SIZE, abSignature,
+ RTSHA1_HASH_SIZE, &pbContent[cbContent - RTSHA1_HASH_SIZE],
+ pszErrorName);
+ if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ return rc;
+ }
+#endif
+ cbContent -= RTSHA1_HASH_SIZE;
+
+
+ /*
+ * A bunch of macros to make decoding easier.
+ */
+#define ENSURE_CONTENT_OR_BREAK_EX(a_cbNeeded, a_pszWhat) \
+ do { \
+ if (RT_LIKELY(off + (a_cbNeeded) <= cbContent)) \
+ { /* likely */ } \
+ else \
+ { \
+ rc = RTErrInfoAddF(pErrInfo, VERR_EOF, " Unexpected end of data at %#x need %u bytes for %s (entry #%u in %s)", \
+ off, a_cbNeeded, a_pszWhat, iEntry, pszErrorName); \
+ break; \
+ } \
+ } while (0)
+#define ENSURE_CONTENT_OR_BREAK(a_Var) ENSURE_CONTENT_OR_BREAK_EX(sizeof(a_Var), #a_Var)
+#define GET_BE_U32_OR_BREAK(a_uVar) \
+ do { \
+ ENSURE_CONTENT_OR_BREAK(a_uVar); \
+ AssertCompile(sizeof(a_uVar) == sizeof(uint32_t)); \
+ a_uVar = RT_MAKE_U32_FROM_U8(pbContent[off + 3], pbContent[off + 2], pbContent[off + 1], pbContent[off + 0]); \
+ off += sizeof(uint32_t); \
+ } while (0)
+#define GET_BE_U16_OR_BREAK(a_uVar) \
+ do { \
+ ENSURE_CONTENT_OR_BREAK(a_uVar); \
+ AssertCompile(sizeof(a_uVar) == sizeof(uint16_t)); \
+ a_uVar = RT_MAKE_U16(pbContent[off + 1], pbContent[off + 0]); \
+ off += sizeof(uint16_t); \
+ } while (0)
+#define SKIP_CONTENT_BYTES_OR_BREAK(a_cbToSkip, a_pszWhat) \
+ do { \
+ ENSURE_CONTENT_OR_BREAK_EX(a_cbToSkip, a_pszWhat); \
+ off += a_cbToSkip; \
+ } while (0)
+#define CHECK_OR_BREAK(a_Expr, a_RTErrInfoAddFArgs) \
+ do { \
+ if (RT_LIKELY(a_Expr)) \
+ { /* likely */ } \
+ else \
+ { \
+ rc = RTErrInfoAddF a_RTErrInfoAddFArgs; \
+ break; \
+ } \
+ } while (0)
+
+ /*
+ * Work our way thru the keystore.
+ */
+ Log(("JKS: %u entries - '%s'\n", cEntries, pszErrorName));
+ size_t off = sizeof(JKSHEADER);
+ uint32_t iEntry = 0;
+ for (;;)
+ {
+ size_t const offEntry = off; NOREF(offEntry);
+
+ /* The entry type. */
+ uint32_t uType;
+ GET_BE_U32_OR_BREAK(uType);
+ CHECK_OR_BREAK(uType == 1 || uType == 2,
+ (pErrInfo, VERR_WRONG_TYPE, " uType=%#x (entry #%u in %s)", uType, iEntry, pszErrorName));
+
+ /* Skip the alias string. */
+ uint16_t cbAlias;
+ GET_BE_U16_OR_BREAK(cbAlias);
+ SKIP_CONTENT_BYTES_OR_BREAK(cbAlias, "szAlias");
+
+ /* Skip the creation timestamp. */
+ SKIP_CONTENT_BYTES_OR_BREAK(sizeof(uint64_t), "tsCreated");
+
+ uint32_t cTrustCerts = 0;
+ if (uType == 1)
+ {
+ /*
+ * It is a private key.
+ */
+ Log(("JKS: %#08zx: entry #%u: Private key\n", offEntry, iEntry));
+
+ /* The encoded key. */
+ uint32_t cbKey;
+ GET_BE_U32_OR_BREAK(cbKey);
+ SKIP_CONTENT_BYTES_OR_BREAK(cbKey, "key data");
+
+ /* The number of trust certificates following it. */
+ GET_BE_U32_OR_BREAK(cTrustCerts);
+ }
+ else if (uType == 2)
+ {
+ /*
+ * It is a certificate.
+ */
+ Log(("JKS: %#08zx: entry #%u: Trust certificate\n", offEntry, iEntry));
+ cTrustCerts = 1;
+ }
+ else
+ AssertFailedBreakStmt(rc = VERR_INTERNAL_ERROR_2);
+
+ /*
+ * Decode trust certificates. Keys have 0 or more of these associated with them.
+ */
+ for (uint32_t iCert = 0; iCert < cTrustCerts; iCert++)
+ {
+ /* X.509 signature */
+ static const char a_achCertType[] = { 0, 5, 'X', '.', '5', '0', '9' };
+ ENSURE_CONTENT_OR_BREAK(a_achCertType);
+ CHECK_OR_BREAK(memcmp(&pbContent[off], a_achCertType, sizeof(a_achCertType)) == 0,
+ (pErrInfo, VERR_WRONG_TYPE, " Unsupported certificate type %.7Rhxs (entry #%u in %s)",
+ &pbContent[off], iEntry, pszErrorName));
+ off += sizeof(a_achCertType);
+
+ /* The encoded certificate length. */
+ uint32_t cbEncoded;
+ GET_BE_U32_OR_BREAK(cbEncoded);
+ ENSURE_CONTENT_OR_BREAK_EX(cbEncoded, "certificate data");
+ Log(("JKS: %#08zx: %#x certificate bytes\n", off, cbEncoded));
+
+ /* Try add the certificate. */
+ RTERRINFOSTATIC StaticErrInfo;
+ int rc2 = RTCrStoreCertAddEncoded(hStore,
+ RTCRCERTCTX_F_ENC_X509_DER | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND),
+ &pbContent[off], cbEncoded, RTErrInfoInitStatic(&StaticErrInfo));
+ if (RT_FAILURE(rc2))
+ {
+ if (RTErrInfoIsSet(&StaticErrInfo.Core))
+ rc = RTErrInfoAddF(pErrInfo, rc2, " entry #%u: %s", iEntry, StaticErrInfo.Core.pszMsg);
+ else
+ rc = RTErrInfoAddF(pErrInfo, rc2, " entry #%u: %Rrc adding cert", iEntry, rc2);
+ if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ break;
+ }
+ off += cbEncoded;
+ }
+
+ /*
+ * Advance.
+ */
+ iEntry++;
+ if (iEntry >= cEntries)
+ {
+ if (off != cbContent)
+ rc = RTErrInfoAddF(pErrInfo, VERR_TOO_MUCH_DATA, " %zu tailing bytes (%s)", cbContent - off, pszErrorName);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTCrStoreCertAddFromJavaKeyStore(RTCRSTORE hStore, uint32_t fFlags, const char *pszFilename, PRTERRINFO pErrInfo)
+{
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS);
+
+ /*
+ * Read the whole thing into memory as that's much more convenient to work
+ * with and we don't expect a java key store to take up a lot of space.
+ */
+ size_t cbContent;
+ void *pvContent;
+ int rc = RTFileReadAllEx(pszFilename, 0, 32U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrStoreCertAddFromJavaKeyStoreInMem(hStore, fFlags, pvContent, cbContent, pszFilename, pErrInfo);
+ RTFileReadAllFree(pvContent, cbContent);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertAddFromJavaKeyStore);
+
diff --git a/src/VBox/Runtime/common/crypto/RTCrStoreCertAddWantedFromFishingExpedition.cpp b/src/VBox/Runtime/common/crypto/RTCrStoreCertAddWantedFromFishingExpedition.cpp
new file mode 100644
index 00000000..8c4cef9e
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/RTCrStoreCertAddWantedFromFishingExpedition.cpp
@@ -0,0 +1,256 @@
+/* $Id: RTCrStoreCertAddWantedFromFishingExpedition.cpp $ */
+/** @file
+ * IPRT - Cryptographic (Certificate) Store, RTCrStoreCertAddFromFishingExpedition.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/store.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+
+#include "x509-internal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef RT_OS_WINDOWS
+# define PREFIX_UNIXROOT "${SystemDrive}/cygwin"
+#elif defined(RT_OS_OS2)
+# define PREFIX_UNIXROOT "/@unixroot@"
+#else
+# define PREFIX_UNIXROOT
+#endif
+
+
+/**
+ * Count the number of found certificates.
+ *
+ * @returns Number found.
+ * @param afFound Indicator array.
+ * @param cWanted Number of wanted certificates.
+ */
+DECLINLINE(size_t) rtCrStoreCountFound(bool const *afFound, size_t cWanted)
+{
+ size_t cFound = 0;
+ while (cWanted-- > 0)
+ if (afFound[cWanted])
+ cFound++;
+ return cFound;
+}
+
+
+RTDECL(int) RTCrStoreCertAddWantedFromFishingExpedition(RTCRSTORE hStore, uint32_t fFlags,
+ PCRTCRCERTWANTED paWanted, size_t cWanted,
+ bool *pafFound, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+ int rc2;
+
+ /*
+ * Validate input.
+ */
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS);
+ fFlags |= RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR; /* force these! */
+ AssertReturn(cWanted, VERR_NOT_FOUND);
+ for (uint32_t i = 0; i < cWanted; i++)
+ {
+ AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER);
+ AssertReturn( paWanted[i].pszSubject
+ || paWanted[i].fSha1Fingerprint
+ || paWanted[i].fSha512Fingerprint,
+ VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Make sure we've got a result array.
+ */
+ bool *pafFoundFree = NULL;
+ if (!pafFound)
+ {
+ pafFound = pafFoundFree = (bool *)RTMemTmpAllocZ(sizeof(bool) * cWanted);
+ AssertReturn(pafFound, VERR_NO_TMP_MEMORY);
+ }
+
+ /*
+ * Search the user and system stores first.
+ */
+ bool fAllFound = false;
+ RTCRSTORE hTmpStore;
+ for (int iStoreId = RTCRSTOREID_INVALID + 1; iStoreId < RTCRSTOREID_END; iStoreId++)
+ {
+ rc2 = RTCrStoreCreateSnapshotById(&hTmpStore, (RTCRSTOREID)iStoreId, NULL);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = RTCrStoreCertAddWantedFromStore(hStore, fFlags, hTmpStore, paWanted, cWanted, pafFound);
+ RTCrStoreRelease(hTmpStore);
+ fAllFound = rc2 == VINF_SUCCESS;
+ if (fAllFound)
+ break;
+ }
+ }
+
+ /*
+ * Search alternative file based stores.
+ */
+ if (!fAllFound)
+ {
+ static const char * const s_apszFiles[] =
+ {
+ PREFIX_UNIXROOT "/usr/share/ca-certificates/trust-source/mozilla.neutral-trust.crt",
+ PREFIX_UNIXROOT "/usr/share/ca-certificates/trust-source/mozilla.trust.crt",
+ PREFIX_UNIXROOT "/usr/share/doc/mutt/samples/ca-bundle.crt",
+ PREFIX_UNIXROOT "/usr/jdk/latest/jre/lib/security/cacerts",
+ PREFIX_UNIXROOT "/usr/share/curl/curl-ca-bundle.crt",
+#ifdef RT_OS_DARWIN
+ "/opt/local/share/curl/curl-ca-bundle.crt",
+ "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/security/cacerts",
+ "/System/Library/Java/Support/CoreDeploy.bundle/Contents/Home/lib/security/cacerts",
+ "/System/Library/Java/Support/CoreDeploy.bundle/Contents/JavaAppletPlugin.plugin/Contents/Home/lib/security/cacerts",
+ "/System/Library/Java/Support/Deploy.bundle/Contents/Home/lib/security/cacerts",
+ "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/lib/security/cacerts",
+ "/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/itms/java/lib/security/cacerts",
+ "/Applications/Xcode-beta.app/Contents/Applications/Application Loader.app/Contents/itms/java/lib/security/cacerts",
+ "/System/Library/Java/JavaVirtualMachines/*/Contents/Home/lib/security/cacerts",
+#endif
+#ifdef RT_OS_LINUX
+ PREFIX_UNIXROOT "/etc/ssl/certs/java/cacerts",
+ PREFIX_UNIXROOT "/usr/lib/j*/*/jre/lib/security/cacerts",
+ PREFIX_UNIXROOT "/opt/*/jre/lib/security/cacerts",
+#endif
+#ifdef RT_OS_SOLARIS
+ PREFIX_UNIXROOT "/usr/java/jre/lib/security/cacerts",
+ PREFIX_UNIXROOT "/usr/jdk/instances/*/jre/lib/security/cacerts",
+#endif
+#ifdef RT_OS_WINDOWS
+ "${AllProgramFiles}/Git/bin/curl-ca-bundle.crt",
+ "${AllProgramFiles}/Mercurial/hgrc.d/cacert.pem",
+ "${AllProgramFiles}/Java/jre*/lib/security/cacerts",
+ "${AllProgramFiles}/Java/jdk*/jre/lib/security/cacerts",
+ "${AllProgramFiles}/HexChat/cert.pem",
+ "${SystemDrive}/BitNami/*/git/bin/curl-ca-bundle.crt",
+ "${SystemDrive}/BitNami/*/heroku/data/cacert.pem",
+ "${SystemDrive}/BitNami/*/heroku/vendor/gems/excon*/data/cacert.pem",
+ "${SystemDrive}/BitNami/*/php/PEAR/AWSSDKforPHP/lib/requstcore/cacert.pem",
+#endif
+ };
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_apszFiles) && !fAllFound; i++)
+ {
+ PCRTPATHGLOBENTRY pResultHead;
+ rc2 = RTPathGlob(s_apszFiles[i], RTPATHGLOB_F_NO_DIRS, &pResultHead, NULL);
+ if (RT_SUCCESS(rc2))
+ {
+ for (PCRTPATHGLOBENTRY pCur = pResultHead; pCur; pCur = pCur->pNext)
+ {
+ rc2 = RTCrStoreCertAddWantedFromFile(hStore, fFlags, pCur->szPath, paWanted, cWanted, pafFound, pErrInfo);
+ fAllFound = rc2 == VINF_SUCCESS;
+ if (fAllFound)
+ break;
+ }
+ RTPathGlobFree(pResultHead);
+ }
+ }
+ }
+
+ /*
+ * Search alternative directory based stores.
+ */
+ if (!fAllFound)
+ {
+ static const char * const s_apszFiles[] =
+ {
+ PREFIX_UNIXROOT "/usr/share/ca-certificates/mozilla/",
+#ifdef RT_OS_DARWIN
+ "/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/ssl_certs/",
+#endif
+#ifdef RT_OS_SOLARIS
+ "/etc/certs/",
+ "/etc/crypto/certs/",
+#endif
+#ifdef RT_OS_WINDOWS
+ "${AllProgramFiles}/Git/ssl/certs/",
+ "${AllProgramFiles}/Git/ssl/certs/expired/",
+ "${AllProgramFiles}/Common Files/Apple/Internet Services/security.resources/roots/",
+ "${AllProgramFiles}/Raptr/ca-certs/",
+ "${SystemDrive}/Bitname/*/git/ssl/certs/",
+ "${SystemDrive}/Bitnami/*/git/ssl/certs/expired/",
+#endif
+ };
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_apszFiles) && !fAllFound; i++)
+ {
+ PCRTPATHGLOBENTRY pResultHead;
+ rc2 = RTPathGlob(s_apszFiles[i], RTPATHGLOB_F_ONLY_DIRS, &pResultHead, NULL);
+ if (RT_SUCCESS(rc2))
+ {
+ for (PCRTPATHGLOBENTRY pCur = pResultHead; pCur; pCur = pCur->pNext)
+ {
+ rc2 = RTCrStoreCertAddWantedFromDir(hStore, fFlags, pCur->szPath, NULL /*paSuffixes*/, 0 /*cSuffixes*/,
+ paWanted, cWanted, pafFound, pErrInfo);
+ fAllFound = rc2 == VINF_SUCCESS;
+ if (fAllFound)
+ break;
+ }
+ RTPathGlobFree(pResultHead);
+ }
+ }
+ }
+
+ /*
+ * If all found, return VINF_SUCCESS, otherwise warn that we didn't find everything.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ size_t cFound = rtCrStoreCountFound(pafFound, cWanted);
+ Assert(cFound == cWanted || !fAllFound);
+ if (cFound == cWanted)
+ rc = VINF_SUCCESS;
+ else if (cFound > 0)
+ rc = VWRN_NOT_FOUND;
+ else
+ rc = VERR_NOT_FOUND;
+ }
+
+ if (pafFoundFree)
+ RTMemTmpFree(pafFoundFree);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromFishingExpedition);
+
diff --git a/src/VBox/Runtime/common/crypto/RTCrStoreCertExportAsPem.cpp b/src/VBox/Runtime/common/crypto/RTCrStoreCertExportAsPem.cpp
new file mode 100644
index 00000000..66349025
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/RTCrStoreCertExportAsPem.cpp
@@ -0,0 +1,147 @@
+/* $Id: RTCrStoreCertExportAsPem.cpp $ */
+/** @file
+ * IPRT - Cryptographic (Certificate) Store, RTCrStoreCertExportAsPem.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/store.h>
+
+#include <iprt/assert.h>
+#include <iprt/base64.h>
+#include <iprt/dir.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+
+
+
+RTDECL(int) RTCrStoreCertExportAsPem(RTCRSTORE hStore, uint32_t fFlags, const char *pszFilename)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+
+ /*
+ * Start the enumeration first as this validates the store handle.
+ */
+ RTCRSTORECERTSEARCH Search;
+ int rc = RTCrStoreCertFindAll(hStore, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Open the file for writing.
+ *
+ * Note! We must use text and no binary here, because the base-64 API
+ * below will use host specific EOL markers, not CRLF as PEM
+ * specifies.
+ */
+ PRTSTREAM hStrm;
+ rc = RTStrmOpen(pszFilename, "w", &hStrm);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Enumerate the certificates in the store, writing them out one by one.
+ */
+ size_t cbBase64 = 0;
+ char *pszBase64 = NULL;
+ PCRTCRCERTCTX pCertCtx;
+ while ((pCertCtx = RTCrStoreCertSearchNext(hStore, &Search)) != NULL)
+ {
+ const char *pszMarker;
+ switch (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK)
+ {
+ case RTCRCERTCTX_F_ENC_X509_DER: pszMarker = "CERTIFICATE"; break;
+ case RTCRCERTCTX_F_ENC_TAF_DER: pszMarker = "TRUST ANCHOR"; break;
+ default: pszMarker = NULL; break;
+ }
+ if (pszMarker && pCertCtx->cbEncoded > 0)
+ {
+ /*
+ * Do the base64 conversion first.
+ */
+ size_t cchEncoded = RTBase64EncodedLength(pCertCtx->cbEncoded);
+ if (cchEncoded < cbBase64)
+ { /* likely */ }
+ else
+ {
+ size_t cbNew = RT_ALIGN(cchEncoded + 64, 128);
+ void *pvNew = RTMemRealloc(pszBase64, cbNew);
+ if (!pvNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ cbBase64 = cbNew;
+ pszBase64 = (char *)pvNew;
+ }
+ rc = RTBase64Encode(pCertCtx->pabEncoded, pCertCtx->cbEncoded, pszBase64, cbBase64, &cchEncoded);
+ if (RT_FAILURE(rc))
+ break;
+
+ RTStrmPrintf(hStrm, "-----BEGIN %s-----\n", pszMarker);
+ RTStrmWrite(hStrm, pszBase64, cchEncoded);
+ rc = RTStrmPrintf(hStrm, "\n-----END %s-----\n", pszMarker);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ RTCrCertCtxRelease(pCertCtx);
+ }
+ if (pCertCtx)
+ RTCrCertCtxRelease(pCertCtx);
+ RTMemFree(pszBase64);
+
+ /*
+ * Flush the output file before closing.
+ */
+ int rc2 = RTStrmFlush(hStrm);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ RTStrmClearError(hStrm); /** @todo fix RTStrmClose... */
+ rc2 = RTStrmClose(hStrm);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ int rc2 = RTCrStoreCertSearchDestroy(hStore, &Search); AssertRC(rc2);
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertExportAsPem);
+
diff --git a/src/VBox/Runtime/common/crypto/RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp b/src/VBox/Runtime/common/crypto/RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp
new file mode 100644
index 00000000..ef1ab550
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp
@@ -0,0 +1,89 @@
+/* $Id: RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.cpp $ */
+/** @file
+ * IPRT - Cryptographic (Certificate) Store, RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/store.h>
+
+#include <iprt/errcore.h>
+
+
+
+
+RTR3DECL(int) RTCrStoreCreateSnapshotOfUserAndSystemTrustedCAsAndCerts(PRTCRSTORE phStore, PRTERRINFO pErrInfo)
+{
+ /*
+ * Start by a snapshot of the user store.
+ */
+ RTCRSTORE hDstStore;
+ int rc = RTCrStoreCreateSnapshotById(&hDstStore, RTCRSTOREID_USER_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Snapshot the system store and add that to the user snapshot. Ignore errors.
+ */
+ RTCRSTORE hSrcStore;
+ rc = RTCrStoreCreateSnapshotById(&hSrcStore, RTCRSTOREID_SYSTEM_TRUSTED_CAS_AND_CERTIFICATES, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrStoreCertAddFromStore(hDstStore, RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR,
+ hSrcStore);
+ RTCrStoreRelease(hSrcStore);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *phStore = hDstStore;
+ return rc;
+ }
+
+ /*
+ * If we've got something in the store, return anyway despite of the error condition.
+ */
+ if (rc != VERR_NO_MEMORY && RTCrStoreCertCount(hDstStore) > 0)
+ {
+ *phStore = hDstStore;
+ return -rc;
+ }
+
+ RTCrStoreRelease(hDstStore);
+ }
+ *phStore = NIL_RTCRSTORE;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/cipher-openssl.cpp b/src/VBox/Runtime/common/crypto/cipher-openssl.cpp
new file mode 100644
index 00000000..079af227
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/cipher-openssl.cpp
@@ -0,0 +1,596 @@
+/* $Id: cipher-openssl.cpp $ */
+/** @file
+ * IPRT - Crypto - Symmetric Cipher using OpenSSL.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt.h"
+# include <iprt/crypto/cipher.h>
+
+# include <iprt/asm.h>
+# include <iprt/assert.h>
+# include <iprt/err.h>
+# include <iprt/mem.h>
+# include <iprt/string.h>
+
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/evp.h>
+# include "internal/openssl-post.h"
+
+# include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(EVP_CTRL_AEAD_GET_TAG)
+# define MY_EVP_CTRL_AEAD_GET_TAG EVP_CTRL_AEAD_GET_TAG
+#else
+# define MY_EVP_CTRL_AEAD_GET_TAG EVP_CTRL_GCM_GET_TAG
+#endif
+
+#if defined(EVP_CTRL_AEAD_SET_TAG)
+# define MY_EVP_CTRL_AEAD_SET_TAG EVP_CTRL_AEAD_SET_TAG
+#else
+# define MY_EVP_CTRL_AEAD_SET_TAG EVP_CTRL_GCM_SET_TAG
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * OpenSSL cipher instance data.
+ */
+typedef struct RTCRCIPHERINT
+{
+ /** Magic value (RTCRCIPHERINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference count. */
+ uint32_t volatile cRefs;
+ /** The cihper. */
+ const EVP_CIPHER *pCipher;
+ /** The IPRT cipher type, if we know it. */
+ RTCRCIPHERTYPE enmType;
+} RTCRCIPHERINT;
+
+
+/**
+ * OpenSSL cipher context data
+ */
+typedef struct RTCRCIPHERCTXINT
+{
+ /** Pointer to cipher instance data */
+ RTCRCIPHERINT *phCipher;
+ /** Pointer to cipher context */
+ EVP_CIPHER_CTX *pCipherCtx;
+ /** Is decryption */
+ bool fDecryption;
+} RTCRCIPHERCTXINT;
+
+
+RTDECL(int) RTCrCipherOpenByType(PRTCRCIPHER phCipher, RTCRCIPHERTYPE enmType, uint32_t fFlags)
+{
+ AssertPtrReturn(phCipher, VERR_INVALID_POINTER);
+ *phCipher = NIL_RTCRCIPHER;
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+
+ /*
+ * Translate the IPRT cipher type to EVP cipher.
+ */
+ const EVP_CIPHER *pCipher = NULL;
+ switch (enmType)
+ {
+ case RTCRCIPHERTYPE_XTS_AES_128:
+ pCipher = EVP_aes_128_xts();
+ break;
+ case RTCRCIPHERTYPE_XTS_AES_256:
+ pCipher = EVP_aes_256_xts();
+ break;
+ case RTCRCIPHERTYPE_GCM_AES_128:
+ pCipher = EVP_aes_128_gcm();
+ break;
+ case RTCRCIPHERTYPE_GCM_AES_256:
+ pCipher = EVP_aes_256_gcm();
+ break;
+ case RTCRCIPHERTYPE_CTR_AES_128:
+ pCipher = EVP_aes_128_ctr();
+ break;
+ case RTCRCIPHERTYPE_CTR_AES_256:
+ pCipher = EVP_aes_256_ctr();
+ break;
+
+ /* no default! */
+ case RTCRCIPHERTYPE_INVALID:
+ case RTCRCIPHERTYPE_END:
+ case RTCRCIPHERTYPE_32BIT_HACK:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+ AssertReturn(pCipher, VERR_CR_CIPHER_NOT_SUPPORTED);
+
+ /*
+ * Create the instance.
+ */
+ RTCRCIPHERINT *pThis = (RTCRCIPHERINT *)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->u32Magic = RTCRCIPHERINT_MAGIC;
+ pThis->cRefs = 1;
+ pThis->pCipher = pCipher;
+ pThis->enmType = enmType;
+ *phCipher = pThis;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+RTDECL(uint32_t) RTCrCipherRetain(RTCRCIPHER hCipher)
+{
+ RTCRCIPHERINT *pThis = hCipher;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1 && cRefs < 1024);
+ return cRefs;
+}
+
+
+/**
+ * Destroys the cipher instance.
+ */
+static uint32_t rtCrCipherDestroy(RTCRCIPHER pThis)
+{
+ pThis->u32Magic= ~RTCRCIPHERINT_MAGIC;
+ pThis->pCipher = NULL;
+ RTMemFree(pThis);
+ return 0;
+}
+
+
+RTDECL(uint32_t) RTCrCipherRelease(RTCRCIPHER hCipher)
+{
+ RTCRCIPHERINT *pThis = hCipher;
+ if (pThis == NIL_RTCRCIPHER)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 1024);
+ if (cRefs == 0)
+ return rtCrCipherDestroy(pThis);
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTCrCipherGetKeyLength(RTCRCIPHER hCipher)
+{
+ RTCRCIPHERINT *pThis = hCipher;
+ AssertPtrReturn(pThis, 0);
+ AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, 0);
+
+ return EVP_CIPHER_key_length(pThis->pCipher);
+}
+
+
+RTDECL(uint32_t) RTCrCipherGetInitializationVectorLength(RTCRCIPHER hCipher)
+{
+ RTCRCIPHERINT *pThis = hCipher;
+ AssertPtrReturn(pThis, 0);
+ AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, 0);
+
+ return EVP_CIPHER_iv_length(pThis->pCipher);
+}
+
+
+RTDECL(uint32_t) RTCrCipherGetBlockSize(RTCRCIPHER hCipher)
+{
+ RTCRCIPHERINT *pThis = hCipher;
+ AssertPtrReturn(pThis, 0);
+ AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, 0);
+
+ return EVP_CIPHER_block_size(pThis->pCipher);
+}
+
+
+RTDECL(int) RTCrCipherCtxFree(RTCRCIPHERCTX hCipherCtx)
+{
+ AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER);
+ RTCRCIPHERCTXINT *pCtx = hCipherCtx;
+
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+ EVP_CIPHER_CTX_free(pCtx->pCipherCtx);
+# else
+ EVP_CIPHER_CTX_cleanup(pCtx->pCipherCtx);
+ RTMemFree(pCtx->pCipherCtx);
+# endif
+ RTMemFree(pCtx);
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrCipherCtxEncryptInit(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey,
+ void const *pvInitVector, size_t cbInitVector,
+ void const *pvAuthData, size_t cbAuthData,
+ PRTCRCIPHERCTX phCipherCtx)
+{
+ /*
+ * Validate input.
+ */
+ RTCRCIPHERINT *pThis = hCipher;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertMsgReturn((ssize_t)cbKey == EVP_CIPHER_key_length(pThis->pCipher),
+ ("%zu, expected %d\n", cbKey, EVP_CIPHER_key_length(pThis->pCipher)),
+ VERR_CR_CIPHER_INVALID_KEY_LENGTH);
+ AssertMsgReturn((ssize_t)cbInitVector == EVP_CIPHER_iv_length(pThis->pCipher),
+ ("%zu, expected %d\n", cbInitVector, EVP_CIPHER_iv_length(pThis->pCipher)),
+ VERR_CR_CIPHER_INVALID_INITIALIZATION_VECTOR_LENGTH);
+
+ Assert(EVP_CIPHER_block_size(pThis->pCipher) <= 1); /** @todo more complicated ciphers later */
+
+ /*
+ * Allocate and initialize the cipher context.
+ */
+ int rc = VERR_NO_MEMORY;
+ /*
+ * Create the instance.
+ */
+ RTCRCIPHERCTXINT *pCtx = (RTCRCIPHERCTXINT *)RTMemAlloc(sizeof(RTCRCIPHERCTXINT));
+ if (pCtx)
+ {
+ pCtx->phCipher = hCipher;
+ pCtx->fDecryption = false;
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+ pCtx->pCipherCtx = EVP_CIPHER_CTX_new();
+ if (pCtx->pCipherCtx)
+# else
+ pCtx->pCipherCtx = (EVP_CIPHER_CTX *)RTMemAllocZ(sizeof(EVP_CIPHER_CTX));
+# endif
+ {
+ if (EVP_EncryptInit(pCtx->pCipherCtx, pCtx->phCipher->pCipher, (unsigned char const *)pvKey,
+ (unsigned char const *)pvInitVector))
+ {
+ if (pvAuthData && cbAuthData)
+ {
+ /* Add auth data. */
+ int cbEncryptedAuth = 0;
+ rc = EVP_EncryptUpdate(pCtx->pCipherCtx, NULL, &cbEncryptedAuth,
+ (unsigned char const *)pvAuthData, (int)cbAuthData) ? VINF_SUCCESS
+ : VERR_CR_CIPHER_OSSL_ENCRYPT_UPDATE_FAILED;
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_CR_CIPHER_OSSL_ENCRYPT_INIT_FAILED;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ *phCipherCtx = pCtx;
+ else
+ RTCrCipherCtxFree(pCtx);
+ return rc;
+}
+
+
+RTDECL(int) RTCrCipherCtxEncryptProcess(RTCRCIPHERCTX hCipherCtx, void const *pvPlainText, size_t cbPlainText,
+ void *pvEncrypted, size_t cbEncrypted, size_t *pcbEncrypted)
+{
+ AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER);
+ AssertReturn(cbPlainText > 0, VERR_NO_DATA);
+ AssertReturn((size_t)(int)cbPlainText == cbPlainText && (int)cbPlainText > 0, VERR_OUT_OF_RANGE);
+ AssertReturn(cbEncrypted >= cbPlainText, VERR_BUFFER_OVERFLOW);
+
+ RTCRCIPHERCTXINT *pCtx = hCipherCtx;
+ AssertReturn(!pCtx->fDecryption, VERR_INVALID_STATE);
+ int cbEncrypted1 = 0;
+ int rc = VERR_CR_CIPHER_OSSL_ENCRYPT_UPDATE_FAILED;
+ if (EVP_EncryptUpdate(pCtx->pCipherCtx, (unsigned char *)pvEncrypted, &cbEncrypted1,
+ (unsigned char const *)pvPlainText, (int)cbPlainText))
+ {
+ *pcbEncrypted = cbEncrypted1;
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTCrCipherCtxEncryptFinish(RTCRCIPHERCTX hCipherCtx,
+ void *pvEncrypted, size_t *pcbEncrypted,
+ void *pvTag, size_t cbTag, size_t *pcbTag)
+{
+ AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER);
+ RTCRCIPHERCTXINT *pCtx = hCipherCtx;
+ AssertReturn(!pCtx->fDecryption, VERR_INVALID_STATE);
+ AssertReturn(!pvTag || (pvTag && cbTag == 16), VERR_CR_CIPHER_INVALID_TAG_LENGTH);
+ int cbEncrypted2 = 0;
+ int rc = VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED;
+ if (EVP_EncryptFinal(pCtx->pCipherCtx, (uint8_t *)pvEncrypted, &cbEncrypted2))
+ {
+ if (pvTag && cbTag)
+ {
+ if (EVP_CIPHER_CTX_ctrl(pCtx->pCipherCtx, MY_EVP_CTRL_AEAD_GET_TAG, (int)cbTag, pvTag))
+ {
+ *pcbTag = cbTag;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_CR_CIPHER_OSSL_GET_TAG_FAILED;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc) && pcbEncrypted)
+ *pcbEncrypted = cbEncrypted2;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTCrCipherCtxDecryptInit(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey,
+ void const *pvInitVector, size_t cbInitVector,
+ void const *pvAuthData, size_t cbAuthData,
+ void *pvTag, size_t cbTag, PRTCRCIPHERCTX phCipherCtx)
+{
+ /*
+ * Validate input.
+ */
+ RTCRCIPHERINT *pThis = hCipher;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRCIPHERINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertMsgReturn((ssize_t)cbKey == EVP_CIPHER_key_length(pThis->pCipher),
+ ("%zu, expected %d\n", cbKey, EVP_CIPHER_key_length(pThis->pCipher)),
+ VERR_CR_CIPHER_INVALID_KEY_LENGTH);
+ AssertMsgReturn((ssize_t)cbInitVector == EVP_CIPHER_iv_length(pThis->pCipher),
+ ("%zu, expected %d\n", cbInitVector, EVP_CIPHER_iv_length(pThis->pCipher)),
+ VERR_CR_CIPHER_INVALID_INITIALIZATION_VECTOR_LENGTH);
+ AssertReturn(!pvTag || (pvTag && cbTag == 16), VERR_CR_CIPHER_INVALID_TAG_LENGTH);
+
+ Assert(EVP_CIPHER_block_size(pThis->pCipher) <= 1); /** @todo more complicated ciphers later */
+
+ /*
+ * Allocate and initialize the cipher context.
+ */
+ int rc = VERR_NO_MEMORY;
+ /*
+ * Create the instance.
+ */
+ RTCRCIPHERCTXINT *pCtx = (RTCRCIPHERCTXINT *)RTMemAlloc(sizeof(RTCRCIPHERCTXINT));
+ if (pCtx)
+ {
+ pCtx->phCipher = hCipher;
+ pCtx->fDecryption = true;
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+ pCtx->pCipherCtx = EVP_CIPHER_CTX_new();
+# else
+ pCtx->pCipherCtx = (EVP_CIPHER_CTX *)RTMemAllocZ(sizeof(EVP_CIPHER_CTX));
+# endif
+
+ if (EVP_DecryptInit(pCtx->pCipherCtx, pThis->pCipher, (unsigned char const *)pvKey,
+ (unsigned char const *)pvInitVector))
+ {
+ rc = VINF_SUCCESS;
+ if (pvTag && cbTag && !EVP_CIPHER_CTX_ctrl(pCtx->pCipherCtx, MY_EVP_CTRL_AEAD_SET_TAG, (int)cbTag, pvTag))
+ rc = VERR_CR_CIPHER_OSSL_SET_TAG_FAILED;
+
+ if (RT_SUCCESS(rc) && pvAuthData && cbAuthData)
+ {
+ /* Add auth data. */
+ int cbDecryptedAuth = 0;
+ if (!EVP_DecryptUpdate(pCtx->pCipherCtx, NULL, &cbDecryptedAuth,
+ (unsigned char const *)pvAuthData, (int)cbAuthData))
+ rc = VERR_CR_CIPHER_OSSL_DECRYPT_UPDATE_FAILED;
+ }
+ }
+ else
+ rc = VERR_CR_CIPHER_OSSL_DECRYPT_INIT_FAILED;
+ }
+
+ if (RT_SUCCESS(rc))
+ *phCipherCtx = pCtx;
+ else
+ RTCrCipherCtxFree(pCtx);
+
+ return rc;
+}
+
+
+RTDECL(int) RTCrCipherCtxDecryptProcess(RTCRCIPHERCTX hCipherCtx,
+ void const *pvEncrypted, size_t cbEncrypted,
+ void *pvPlainText, size_t cbPlainText, size_t *pcbPlainText)
+{
+ AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER);
+ AssertReturn(cbEncrypted > 0, VERR_NO_DATA);
+ AssertReturn((size_t)(int)cbEncrypted == cbEncrypted && (int)cbEncrypted > 0, VERR_OUT_OF_RANGE);
+ AssertReturn(cbPlainText >= cbEncrypted, VERR_BUFFER_OVERFLOW);
+
+ RTCRCIPHERCTXINT *pCtx = hCipherCtx;
+ AssertReturn(pCtx->fDecryption, VERR_INVALID_STATE);
+ int rc = VERR_CR_CIPHER_OSSL_DECRYPT_UPDATE_FAILED;
+ int cbDecrypted1 = 0;
+ if (EVP_DecryptUpdate(pCtx->pCipherCtx, (unsigned char *)pvPlainText, &cbDecrypted1,
+ (unsigned char const *)pvEncrypted, (int)cbEncrypted))
+ {
+ *pcbPlainText = cbDecrypted1;
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTCrCipherCtxDecryptFinish(RTCRCIPHERCTX hCipherCtx,
+ void *pvPlainText, size_t *pcbPlainText)
+{
+ AssertReturn(hCipherCtx, VERR_INVALID_PARAMETER);
+ RTCRCIPHERCTXINT *pCtx = hCipherCtx;
+ AssertReturn(pCtx->fDecryption, VERR_INVALID_STATE);
+ int cbDecrypted2 = 0;
+ int rc = VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED;
+ if (EVP_DecryptFinal(pCtx->pCipherCtx, (uint8_t *)pvPlainText, &cbDecrypted2))
+ {
+ rc = VINF_SUCCESS;
+ if (pcbPlainText)
+ *pcbPlainText = cbDecrypted2;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTCrCipherEncrypt(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey,
+ void const *pvInitVector, size_t cbInitVector,
+ void const *pvPlainText, size_t cbPlainText,
+ void *pvEncrypted, size_t cbEncrypted, size_t *pcbEncrypted)
+{
+ return RTCrCipherEncryptEx(hCipher, pvKey, cbKey, pvInitVector, cbInitVector,
+ NULL, 0, pvPlainText, cbPlainText, pvEncrypted, cbEncrypted,
+ pcbEncrypted, NULL, 0, NULL);
+}
+
+
+RTDECL(int) RTCrCipherDecrypt(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey,
+ void const *pvInitVector, size_t cbInitVector,
+ void const *pvEncrypted, size_t cbEncrypted,
+ void *pvPlainText, size_t cbPlainText, size_t *pcbPlainText)
+{
+ return RTCrCipherDecryptEx(hCipher, pvKey, cbKey, pvInitVector, cbInitVector,
+ NULL, 0, NULL, 0, pvEncrypted, cbEncrypted,
+ pvPlainText, cbPlainText, pcbPlainText);
+}
+
+
+RTDECL(int) RTCrCipherEncryptEx(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey,
+ void const *pvInitVector, size_t cbInitVector,
+ void const *pvAuthData, size_t cbAuthData,
+ void const *pvPlainText, size_t cbPlainText,
+ void *pvEncrypted, size_t cbEncrypted, size_t *pcbEncrypted,
+ void *pvTag, size_t cbTag, size_t *pcbTag)
+{
+ size_t const cbNeeded = cbPlainText;
+ if (pcbEncrypted)
+ {
+ *pcbEncrypted = cbNeeded;
+ AssertReturn(cbEncrypted >= cbNeeded, VERR_BUFFER_OVERFLOW);
+ }
+ else
+ AssertReturn(cbEncrypted == cbNeeded, VERR_INVALID_PARAMETER);
+ AssertReturn((size_t)(int)cbPlainText == cbPlainText && (int)cbPlainText > 0, VERR_OUT_OF_RANGE);
+
+ RTCRCIPHERCTXINT *pCtx = NIL_RTCRCIPHERCTX;
+
+ int rc = RTCrCipherCtxEncryptInit(hCipher, pvKey, cbKey, pvInitVector, cbInitVector,
+ pvAuthData, cbAuthData, &pCtx);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbEncrypted1 = 0;
+ rc = RTCrCipherCtxEncryptProcess(pCtx, pvPlainText, cbPlainText, pvEncrypted, cbEncrypted, &cbEncrypted1);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbEncrypted2 = 0;
+ rc = RTCrCipherCtxEncryptFinish(pCtx, (unsigned char *)pvEncrypted + cbEncrypted1,
+ &cbEncrypted2, pvTag, cbTag, pcbTag);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbEncrypted1 + cbEncrypted2 == cbNeeded);
+ if (pcbEncrypted)
+ *pcbEncrypted = cbEncrypted1 + cbEncrypted2;
+ }
+ }
+ }
+
+ if (pCtx != NIL_RTCRCIPHERCTX)
+ RTCrCipherCtxFree(pCtx);
+
+ return rc;
+}
+
+
+RTDECL(int) RTCrCipherDecryptEx(RTCRCIPHER hCipher, void const *pvKey, size_t cbKey,
+ void const *pvInitVector, size_t cbInitVector,
+ void const *pvAuthData, size_t cbAuthData,
+ void *pvTag, size_t cbTag,
+ void const *pvEncrypted, size_t cbEncrypted,
+ void *pvPlainText, size_t cbPlainText, size_t *pcbPlainText)
+{
+ size_t const cbNeeded = cbEncrypted;
+ if (pcbPlainText)
+ {
+ *pcbPlainText = cbNeeded;
+ AssertReturn(cbPlainText >= cbNeeded, VERR_BUFFER_OVERFLOW);
+ }
+ else
+ AssertReturn(cbPlainText == cbNeeded, VERR_INVALID_PARAMETER);
+ AssertReturn((size_t)(int)cbEncrypted == cbEncrypted && (int)cbEncrypted > 0, VERR_OUT_OF_RANGE);
+
+ RTCRCIPHERCTXINT *pCtx = NIL_RTCRCIPHERCTX;
+
+ int rc = RTCrCipherCtxDecryptInit(hCipher, pvKey, cbKey, pvInitVector, cbInitVector,
+ pvAuthData, cbAuthData, pvTag, cbTag, &pCtx);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbDecrypted1 = 0;
+ rc = RTCrCipherCtxDecryptProcess(pCtx, pvEncrypted, cbEncrypted, pvPlainText, cbPlainText, &cbDecrypted1);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbDecrypted2 = 0;
+ rc = RTCrCipherCtxDecryptFinish(pCtx, (unsigned char *)pvPlainText + cbDecrypted1,
+ &cbDecrypted2);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbDecrypted1 + cbDecrypted2 == cbNeeded);
+ if (pcbPlainText)
+ *pcbPlainText = cbDecrypted1 + cbDecrypted2;
+ }
+ }
+ }
+
+ if (pCtx != NIL_RTCRCIPHERCTX)
+ RTCrCipherCtxFree(pCtx);
+
+ return rc;
+}
+
+#endif /* IPRT_WITH_OPENSSL */
+
diff --git a/src/VBox/Runtime/common/crypto/digest-builtin.cpp b/src/VBox/Runtime/common/crypto/digest-builtin.cpp
new file mode 100644
index 00000000..500108e8
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/digest-builtin.cpp
@@ -0,0 +1,1175 @@
+/* $Id: digest-builtin.cpp $ */
+/** @file
+ * IPRT - Crypto - Cryptographic Hash / Message Digest API, Built-in providers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/digest.h>
+
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/md2.h>
+#include <iprt/md4.h>
+#include <iprt/md5.h>
+#include <iprt/sha.h>
+#include <iprt/crypto/pkix.h>
+
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/evp.h>
+# include "internal/openssl-post.h"
+#endif
+
+
+
+/*
+ * MD2
+ */
+#ifndef IPRT_WITHOUT_DIGEST_MD2
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestMd2_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTMd2Update((PRTMD2CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestMd2_Final(void *pvState, uint8_t *pbHash)
+{
+ RTMd2Final((PRTMD2CONTEXT)pvState, pbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestMd2_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(fReInit); RT_NOREF_PV(pvOpaque);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTMd2Init((PRTMD2CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** MD2 alias ODIs. */
+static const char * const g_apszMd2Aliases[] =
+{
+ RTCR_PKCS1_MD2_WITH_RSA_OID,
+ "1.3.14.3.2.24" /* OIW md2WithRSASignature */,
+ NULL
+};
+
+/** MD2 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestMd2Desc =
+{
+ "md2",
+ "1.2.840.113549.2.2",
+ g_apszMd2Aliases,
+ RTDIGESTTYPE_MD2,
+ RTMD2_HASH_SIZE,
+ sizeof(RTMD2CONTEXT),
+ RTCRDIGESTDESC_F_DEPRECATED,
+ NULL,
+ NULL,
+ rtCrDigestMd2_Update,
+ rtCrDigestMd2_Final,
+ rtCrDigestMd2_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+#endif /* !IPRT_WITHOUT_DIGEST_MD2 */
+
+
+/*
+ * MD4
+ */
+#ifndef IPRT_WITHOUT_DIGEST_MD4
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestMd4_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTMd4Update((PRTMD4CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestMd4_Final(void *pvState, uint8_t *pbHash)
+{
+ RTMd4Final((PRTMD4CONTEXT)pvState, pbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestMd4_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(fReInit); RT_NOREF_PV(pvOpaque);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTMd4Init((PRTMD4CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** MD4 alias ODIs. */
+static const char * const g_apszMd4Aliases[] =
+{
+ RTCR_PKCS1_MD4_WITH_RSA_OID,
+ NULL
+};
+
+/** MD4 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestMd4Desc =
+{
+ "md4",
+ "1.2.840.113549.2.4",
+ g_apszMd4Aliases,
+ RTDIGESTTYPE_MD4,
+ RTMD4_HASH_SIZE,
+ sizeof(RTMD4CONTEXT),
+ RTCRDIGESTDESC_F_DEPRECATED | RTCRDIGESTDESC_F_COMPROMISED | RTCRDIGESTDESC_F_SERVERELY_COMPROMISED,
+ NULL,
+ NULL,
+ rtCrDigestMd4_Update,
+ rtCrDigestMd4_Final,
+ rtCrDigestMd4_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+#endif /* !IPRT_WITHOUT_DIGEST_MD4 */
+
+
+/*
+ * MD5
+ */
+#ifndef IPRT_WITHOUT_DIGEST_MD5
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestMd5_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTMd5Update((PRTMD5CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestMd5_Final(void *pvState, uint8_t *pbHash)
+{
+ RTMd5Final(pbHash, (PRTMD5CONTEXT)pvState);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestMd5_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTMd5Init((PRTMD5CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** MD5 alias ODIs. */
+static const char * const g_apszMd5Aliases[] =
+{
+ RTCR_PKCS1_MD5_WITH_RSA_OID,
+ "1.3.14.3.2.25" /* OIW md5WithRSASignature */,
+ NULL
+};
+
+/** MD5 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestMd5Desc =
+{
+ "md5",
+ "1.2.840.113549.2.5",
+ g_apszMd5Aliases,
+ RTDIGESTTYPE_MD5,
+ RTMD5_HASH_SIZE,
+ sizeof(RTMD5CONTEXT),
+ RTCRDIGESTDESC_F_COMPROMISED,
+ NULL,
+ NULL,
+ rtCrDigestMd5_Update,
+ rtCrDigestMd5_Final,
+ rtCrDigestMd5_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+#endif /* !IPRT_WITHOUT_DIGEST_MD5 */
+
+
+/*
+ * SHA-1
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha1_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTSha1Update((PRTSHA1CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha1_Final(void *pvState, uint8_t *pbHash)
+{
+ RTSha1Final((PRTSHA1CONTEXT)pvState, pbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha1_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTSha1Init((PRTSHA1CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** SHA-1 alias ODIs. */
+static const char * const g_apszSha1Aliases[] =
+{
+ RTCR_PKCS1_SHA1_WITH_RSA_OID,
+ "1.3.14.3.2.29" /* OIW sha1WithRSASignature */,
+ RTCR_X962_ECDSA_WITH_SHA1_OID,
+ NULL
+};
+
+/** SHA-1 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha1Desc =
+{
+ "sha-1",
+ "1.3.14.3.2.26",
+ g_apszSha1Aliases,
+ RTDIGESTTYPE_SHA1,
+ RTSHA1_HASH_SIZE,
+ sizeof(RTSHA1CONTEXT),
+ RTCRDIGESTDESC_F_DEPRECATED,
+ NULL,
+ NULL,
+ rtCrDigestSha1_Update,
+ rtCrDigestSha1_Final,
+ rtCrDigestSha1_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+
+/*
+ * SHA-256
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha256_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTSha256Update((PRTSHA256CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha256_Final(void *pvState, uint8_t *pbHash)
+{
+ RTSha256Final((PRTSHA256CONTEXT)pvState, pbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha256_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTSha256Init((PRTSHA256CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** SHA-256 alias ODIs. */
+static const char * const g_apszSha256Aliases[] =
+{
+ RTCR_PKCS1_SHA256_WITH_RSA_OID,
+ RTCR_X962_ECDSA_WITH_SHA256_OID,
+ NULL
+};
+
+/** SHA-256 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha256Desc =
+{
+ "sha-256",
+ "2.16.840.1.101.3.4.2.1",
+ g_apszSha256Aliases,
+ RTDIGESTTYPE_SHA256,
+ RTSHA256_HASH_SIZE,
+ sizeof(RTSHA256CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha256_Update,
+ rtCrDigestSha256_Final,
+ rtCrDigestSha256_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+
+/*
+ * SHA-512
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha512_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTSha512Update((PRTSHA512CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha512_Final(void *pvState, uint8_t *pbHash)
+{
+ RTSha512Final((PRTSHA512CONTEXT)pvState, pbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha512_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTSha512Init((PRTSHA512CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** SHA-512 alias ODIs. */
+static const char * const g_apszSha512Aliases[] =
+{
+ RTCR_PKCS1_SHA512_WITH_RSA_OID,
+ RTCR_X962_ECDSA_WITH_SHA512_OID,
+ NULL
+};
+
+/** SHA-512 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha512Desc =
+{
+ "sha-512",
+ "2.16.840.1.101.3.4.2.3",
+ g_apszSha512Aliases,
+ RTDIGESTTYPE_SHA512,
+ RTSHA512_HASH_SIZE,
+ sizeof(RTSHA512CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha512_Update,
+ rtCrDigestSha512_Final,
+ rtCrDigestSha512_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+
+/*
+ * SHA-224
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha224_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTSha224Update((PRTSHA224CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha224_Final(void *pvState, uint8_t *pbHash)
+{
+ RTSha224Final((PRTSHA224CONTEXT)pvState, pbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha224_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTSha224Init((PRTSHA224CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** SHA-224 alias ODIs. */
+static const char * const g_apszSha224Aliases[] =
+{
+ RTCR_PKCS1_SHA224_WITH_RSA_OID,
+ RTCR_X962_ECDSA_WITH_SHA224_OID,
+ NULL
+};
+
+/** SHA-224 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha224Desc =
+{
+ "sha-224",
+ "2.16.840.1.101.3.4.2.4",
+ g_apszSha224Aliases,
+ RTDIGESTTYPE_SHA224,
+ RTSHA224_HASH_SIZE,
+ sizeof(RTSHA224CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha224_Update,
+ rtCrDigestSha224_Final,
+ rtCrDigestSha224_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+
+/*
+ * SHA-384
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha384_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTSha384Update((PRTSHA384CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha384_Final(void *pvState, uint8_t *pbHash)
+{
+ RTSha384Final((PRTSHA384CONTEXT)pvState, pbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha384_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTSha384Init((PRTSHA384CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** SHA-384 alias ODIs. */
+static const char * const g_apszSha384Aliases[] =
+{
+ RTCR_PKCS1_SHA384_WITH_RSA_OID,
+ RTCR_X962_ECDSA_WITH_SHA384_OID,
+ NULL
+};
+
+/** SHA-384 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha384Desc =
+{
+ "sha-384",
+ "2.16.840.1.101.3.4.2.2",
+ g_apszSha384Aliases,
+ RTDIGESTTYPE_SHA384,
+ RTSHA384_HASH_SIZE,
+ sizeof(RTSHA384CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha384_Update,
+ rtCrDigestSha384_Final,
+ rtCrDigestSha384_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+
+
+#ifndef IPRT_WITHOUT_SHA512T224
+/*
+ * SHA-512/224
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha512t224_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTSha512t224Update((PRTSHA512T224CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha512t224_Final(void *pvState, uint8_t *pbHash)
+{
+ RTSha512t224Final((PRTSHA512T224CONTEXT)pvState, pbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha512t224_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTSha512t224Init((PRTSHA512T224CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** SHA-512/224 alias ODIs. */
+static const char * const g_apszSha512t224Aliases[] =
+{
+ RTCR_PKCS1_SHA512T224_WITH_RSA_OID,
+ NULL
+};
+
+/** SHA-512/224 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha512t224Desc =
+{
+ "sha-512/224",
+ "2.16.840.1.101.3.4.2.5",
+ g_apszSha512t224Aliases,
+ RTDIGESTTYPE_SHA512T224,
+ RTSHA512T224_HASH_SIZE,
+ sizeof(RTSHA512T224CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha512t224_Update,
+ rtCrDigestSha512t224_Final,
+ rtCrDigestSha512t224_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+#endif /* !IPRT_WITHOUT_SHA512T224 */
+
+
+#ifndef IPRT_WITHOUT_SHA512T256
+/*
+ * SHA-512/256
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha512t256_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ RTSha512t256Update((PRTSHA512T256CONTEXT)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha512t256_Final(void *pvState, uint8_t *pbHash)
+{
+ RTSha512t256Final((PRTSHA512T256CONTEXT)pvState, pbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha512t256_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque); RT_NOREF_PV(fReInit);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ RTSha512t256Init((PRTSHA512T256CONTEXT)pvState);
+ return VINF_SUCCESS;
+}
+
+/** SHA-512/256 alias ODIs. */
+static const char * const g_apszSha512t256Aliases[] =
+{
+ RTCR_PKCS1_SHA512T256_WITH_RSA_OID,
+ NULL
+};
+
+/** SHA-512/256 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha512t256Desc =
+{
+ "sha-512/256",
+ "2.16.840.1.101.3.4.2.6",
+ g_apszSha512t256Aliases,
+ RTDIGESTTYPE_SHA512T256,
+ RTSHA512T256_HASH_SIZE,
+ sizeof(RTSHA512T256CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha512t256_Update,
+ rtCrDigestSha512t256_Final,
+ rtCrDigestSha512t256_Init,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+#endif /* !IPRT_WITHOUT_SHA512T256 */
+
+#ifndef IPRT_WITHOUT_SHA3
+
+/*
+ * SHA3-224
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha3t224_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ int rc = RTSha3t224Update((PRTSHA3T224CONTEXT)pvState, pvData, cbData);
+ AssertRC(rc);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha3t224_Final(void *pvState, uint8_t *pbHash)
+{
+ int rc = RTSha3t224Final((PRTSHA3T224CONTEXT)pvState, pbHash);
+ AssertRC(rc);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha3t224_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ if (fReInit)
+ RTSha3t224Cleanup((PRTSHA3T224CONTEXT)pvState);
+ return RTSha3t224Init((PRTSHA3T224CONTEXT)pvState);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */
+static DECLCALLBACK(void) rtCrDigestSha3t224_Delete(void *pvState)
+{
+ RTSha3t224Cleanup((PRTSHA3T224CONTEXT)pvState);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */
+static DECLCALLBACK(int) rtCrDigestSha3t224_Clone(void *pvState, void const *pvSrcState)
+{
+ return RTSha3t224Clone((PRTSHA3T224CONTEXT)pvState, (PRTSHA3T224CONTEXT)pvSrcState);
+}
+
+/** SHA3-224 alias ODIs. */
+static const char * const g_apszSha3t224Aliases[] =
+{
+ "2.16.840.1.101.3.4.3.13",
+ RTCR_NIST_SHA3_224_WITH_ECDSA_OID,
+ NULL
+};
+
+/** SHA3-224 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha3t224Desc =
+{
+ "sha3-224",
+ "2.16.840.1.101.3.4.2.7",
+ g_apszSha3t224Aliases,
+ RTDIGESTTYPE_SHA3_224,
+ RTSHA3_224_HASH_SIZE,
+ sizeof(RTSHA3T224CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha3t224_Update,
+ rtCrDigestSha3t224_Final,
+ rtCrDigestSha3t224_Init,
+ rtCrDigestSha3t224_Delete,
+ rtCrDigestSha3t224_Clone,
+ NULL,
+ NULL,
+};
+
+
+/*
+ * SHA3-256
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha3t256_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ int rc = RTSha3t256Update((PRTSHA3T256CONTEXT)pvState, pvData, cbData);
+ AssertRC(rc);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha3t256_Final(void *pvState, uint8_t *pbHash)
+{
+ int rc = RTSha3t256Final((PRTSHA3T256CONTEXT)pvState, pbHash);
+ AssertRC(rc);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha3t256_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ if (fReInit)
+ RTSha3t256Cleanup((PRTSHA3T256CONTEXT)pvState);
+ return RTSha3t256Init((PRTSHA3T256CONTEXT)pvState);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */
+static DECLCALLBACK(void) rtCrDigestSha3t256_Delete(void *pvState)
+{
+ RTSha3t256Cleanup((PRTSHA3T256CONTEXT)pvState);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */
+static DECLCALLBACK(int) rtCrDigestSha3t256_Clone(void *pvState, void const *pvSrcState)
+{
+ return RTSha3t256Clone((PRTSHA3T256CONTEXT)pvState, (PRTSHA3T256CONTEXT)pvSrcState);
+}
+
+/** SHA3-256 alias ODIs. */
+static const char * const g_apszSha3t256Aliases[] =
+{
+ "2.16.840.1.101.3.4.3.14",
+ RTCR_NIST_SHA3_256_WITH_ECDSA_OID,
+ NULL
+};
+
+/** SHA3-256 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha3t256Desc =
+{
+ "sha3-256",
+ "2.16.840.1.101.3.4.2.8",
+ g_apszSha3t256Aliases,
+ RTDIGESTTYPE_SHA3_256,
+ RTSHA3_256_HASH_SIZE,
+ sizeof(RTSHA3T256CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha3t256_Update,
+ rtCrDigestSha3t256_Final,
+ rtCrDigestSha3t256_Init,
+ rtCrDigestSha3t256_Delete,
+ rtCrDigestSha3t256_Clone,
+ NULL,
+ NULL,
+};
+
+
+/*
+ * SHA3-384
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha3t384_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ int rc = RTSha3t384Update((PRTSHA3T384CONTEXT)pvState, pvData, cbData);
+ AssertRC(rc);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha3t384_Final(void *pvState, uint8_t *pbHash)
+{
+ int rc = RTSha3t384Final((PRTSHA3T384CONTEXT)pvState, pbHash);
+ AssertRC(rc);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha3t384_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ if (fReInit)
+ RTSha3t384Cleanup((PRTSHA3T384CONTEXT)pvState);
+ return RTSha3t384Init((PRTSHA3T384CONTEXT)pvState);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */
+static DECLCALLBACK(void) rtCrDigestSha3t384_Delete(void *pvState)
+{
+ RTSha3t384Cleanup((PRTSHA3T384CONTEXT)pvState);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */
+static DECLCALLBACK(int) rtCrDigestSha3t384_Clone(void *pvState, void const *pvSrcState)
+{
+ return RTSha3t384Clone((PRTSHA3T384CONTEXT)pvState, (PRTSHA3T384CONTEXT)pvSrcState);
+}
+
+/** SHA3-384 alias ODIs. */
+static const char * const g_apszSha3t384Aliases[] =
+{
+ "2.16.840.1.101.3.4.3.15",
+ RTCR_NIST_SHA3_384_WITH_ECDSA_OID,
+ NULL
+};
+
+/** SHA3-384 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha3t384Desc =
+{
+ "sha3-384",
+ "2.16.840.1.101.3.4.2.9",
+ g_apszSha3t384Aliases,
+ RTDIGESTTYPE_SHA3_384,
+ RTSHA3_384_HASH_SIZE,
+ sizeof(RTSHA3T384CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha3t384_Update,
+ rtCrDigestSha3t384_Final,
+ rtCrDigestSha3t384_Init,
+ rtCrDigestSha3t384_Delete,
+ rtCrDigestSha3t384_Clone,
+ NULL,
+ NULL,
+};
+
+
+/*
+ * SHA3-512
+ */
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestSha3t512_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ int rc = RTSha3t512Update((PRTSHA3T512CONTEXT)pvState, pvData, cbData);
+ AssertRC(rc);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestSha3t512_Final(void *pvState, uint8_t *pbHash)
+{
+ int rc = RTSha3t512Final((PRTSHA3T512CONTEXT)pvState, pbHash);
+ AssertRC(rc);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestSha3t512_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ RT_NOREF_PV(pvOpaque);
+ AssertReturn(pvOpaque == NULL, VERR_INVALID_PARAMETER);
+ if (fReInit)
+ RTSha3t512Cleanup((PRTSHA3T512CONTEXT)pvState);
+ return RTSha3t512Init((PRTSHA3T512CONTEXT)pvState);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */
+static DECLCALLBACK(void) rtCrDigestSha3t512_Delete(void *pvState)
+{
+ RTSha3t512Cleanup((PRTSHA3T512CONTEXT)pvState);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnDelete} */
+static DECLCALLBACK(int) rtCrDigestSha3t512_Clone(void *pvState, void const *pvSrcState)
+{
+ return RTSha3t512Clone((PRTSHA3T512CONTEXT)pvState, (PRTSHA3T512CONTEXT)pvSrcState);
+}
+
+/** SHA3-512 alias ODIs. */
+static const char * const g_apszSha3t512Aliases[] =
+{
+ "2.16.840.1.101.3.4.3.16",
+ RTCR_NIST_SHA3_512_WITH_ECDSA_OID,
+ NULL
+};
+
+/** SHA3-512 descriptor. */
+static RTCRDIGESTDESC const g_rtCrDigestSha3t512Desc =
+{
+ "sha3-512",
+ "2.16.840.1.101.3.4.2.10",
+ g_apszSha3t512Aliases,
+ RTDIGESTTYPE_SHA3_512,
+ RTSHA3_512_HASH_SIZE,
+ sizeof(RTSHA3T512CONTEXT),
+ 0,
+ NULL,
+ NULL,
+ rtCrDigestSha3t512_Update,
+ rtCrDigestSha3t512_Final,
+ rtCrDigestSha3t512_Init,
+ rtCrDigestSha3t512_Delete,
+ rtCrDigestSha3t512_Clone,
+ NULL,
+ NULL,
+};
+
+#endif /* !IPRT_WITHOUT_SHA3 */
+
+
+/**
+ * Array of built in message digest vtables.
+ */
+static PCRTCRDIGESTDESC const g_apDigestOps[] =
+{
+#ifndef IPRT_WITHOUT_DIGEST_MD2
+ &g_rtCrDigestMd2Desc,
+#endif
+#ifndef IPRT_WITHOUT_DIGEST_MD4
+ &g_rtCrDigestMd4Desc,
+#endif
+#ifndef IPRT_WITHOUT_DIGEST_MD5
+ &g_rtCrDigestMd5Desc,
+#endif
+ &g_rtCrDigestSha1Desc,
+ &g_rtCrDigestSha256Desc,
+ &g_rtCrDigestSha512Desc,
+ &g_rtCrDigestSha224Desc,
+ &g_rtCrDigestSha384Desc,
+#ifndef IPRT_WITHOUT_SHA512T224
+ &g_rtCrDigestSha512t224Desc,
+#endif
+#ifndef IPRT_WITHOUT_SHA512T256
+ &g_rtCrDigestSha512t256Desc,
+#endif
+#ifndef IPRT_WITHOUT_SHA3
+ &g_rtCrDigestSha3t224Desc,
+ &g_rtCrDigestSha3t256Desc,
+ &g_rtCrDigestSha3t384Desc,
+ &g_rtCrDigestSha3t512Desc,
+#endif
+};
+
+
+#ifdef IPRT_WITH_OPENSSL
+/*
+ * OpenSSL EVP.
+ */
+
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+/** @impl_interface_method{RTCRDIGESTDESC::pfnNew} */
+static DECLCALLBACK(void*) rtCrDigestOsslEvp_New(void)
+{
+ return EVP_MD_CTX_new();
+}
+
+static DECLCALLBACK(void) rtCrDigestOsslEvp_Free(void *pvState)
+{
+ EVP_MD_CTX_free((EVP_MD_CTX*)pvState);
+}
+
+# endif
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnUpdate} */
+static DECLCALLBACK(void) rtCrDigestOsslEvp_Update(void *pvState, const void *pvData, size_t cbData)
+{
+ EVP_DigestUpdate((EVP_MD_CTX *)pvState, pvData, cbData);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnFinal} */
+static DECLCALLBACK(void) rtCrDigestOsslEvp_Final(void *pvState, uint8_t *pbHash)
+{
+ unsigned int cbHash = EVP_MAX_MD_SIZE;
+ EVP_DigestFinal((EVP_MD_CTX *)pvState, (unsigned char *)pbHash, &cbHash);
+}
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnInit} */
+static DECLCALLBACK(int) rtCrDigestOsslEvp_Init(void *pvState, void *pvOpaque, bool fReInit)
+{
+ EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState;
+ EVP_MD const *pEvpType = (EVP_MD const *)pvOpaque;
+
+ if (fReInit)
+ {
+ pEvpType = EVP_MD_CTX_md(pThis);
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+ EVP_MD_CTX_reset(pThis);
+# else
+ EVP_MD_CTX_cleanup(pThis);
+# endif
+ }
+
+ AssertPtrReturn(pEvpType, VERR_INVALID_PARAMETER);
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+ Assert(EVP_MD_block_size(pEvpType));
+# else
+ Assert(pEvpType->md_size);
+# endif
+ if (EVP_DigestInit(pThis, pEvpType))
+ return VINF_SUCCESS;
+ return VERR_CR_DIGEST_OSSL_DIGEST_INIT_ERROR;
+}
+
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfn} */
+static DECLCALLBACK(void) rtCrDigestOsslEvp_Delete(void *pvState)
+{
+ EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState;
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+ EVP_MD_CTX_reset(pThis);
+# else
+ EVP_MD_CTX_cleanup(pThis);
+# endif
+}
+
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnClone} */
+static DECLCALLBACK(int) rtCrDigestOsslEvp_Clone(void *pvState, void const *pvSrcState)
+{
+ EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState;
+ EVP_MD_CTX const *pSrc = (EVP_MD_CTX const *)pvSrcState;
+
+ if (EVP_MD_CTX_copy(pThis, pSrc))
+ return VINF_SUCCESS;
+ return VERR_CR_DIGEST_OSSL_DIGEST_CTX_COPY_ERROR;
+}
+
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnGetHashSize} */
+static DECLCALLBACK(uint32_t) rtCrDigestOsslEvp_GetHashSize(void *pvState)
+{
+ EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState;
+ return EVP_MD_size(EVP_MD_CTX_md(pThis));
+}
+
+
+/** @impl_interface_method{RTCRDIGESTDESC::pfnGetHashSize} */
+static DECLCALLBACK(RTDIGESTTYPE) rtCrDigestOsslEvp_GetDigestType(void *pvState)
+{
+ RT_NOREF_PV(pvState); //EVP_MD_CTX *pThis = (EVP_MD_CTX *)pvState;
+ /** @todo figure which digest algorithm it is! */
+ return RTDIGESTTYPE_UNKNOWN;
+}
+
+
+/** Descriptor for the OpenSSL EVP base message digest provider. */
+static RTCRDIGESTDESC const g_rtCrDigestOpenSslDesc =
+{
+ "OpenSSL EVP",
+ NULL,
+ NULL,
+ RTDIGESTTYPE_UNKNOWN,
+ EVP_MAX_MD_SIZE,
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+ 0,
+# else
+ sizeof(EVP_MD_CTX),
+# endif
+ 0,
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+ rtCrDigestOsslEvp_New,
+ rtCrDigestOsslEvp_Free,
+# else
+ NULL,
+ NULL,
+# endif
+ rtCrDigestOsslEvp_Update,
+ rtCrDigestOsslEvp_Final,
+ rtCrDigestOsslEvp_Init,
+ rtCrDigestOsslEvp_Delete,
+ rtCrDigestOsslEvp_Clone,
+ rtCrDigestOsslEvp_GetHashSize,
+ rtCrDigestOsslEvp_GetDigestType
+};
+
+#endif /* IPRT_WITH_OPENSSL */
+
+
+RTDECL(PCRTCRDIGESTDESC) RTCrDigestFindByObjIdString(const char *pszObjId, void **ppvOpaque)
+{
+ if (ppvOpaque)
+ *ppvOpaque = NULL;
+
+ /*
+ * Primary OIDs.
+ */
+ uint32_t i = RT_ELEMENTS(g_apDigestOps);
+ while (i-- > 0)
+ if (strcmp(g_apDigestOps[i]->pszObjId, pszObjId) == 0)
+ return g_apDigestOps[i];
+
+ /*
+ * Alias OIDs.
+ */
+ i = RT_ELEMENTS(g_apDigestOps);
+ while (i-- > 0)
+ {
+ const char * const *ppszAliases = g_apDigestOps[i]->papszObjIdAliases;
+ if (ppszAliases)
+ for (; *ppszAliases; ppszAliases++)
+ if (strcmp(*ppszAliases, pszObjId) == 0)
+ return g_apDigestOps[i];
+ }
+
+#ifdef IPRT_WITH_OPENSSL
+ /*
+ * Try EVP and see if it knows the algorithm.
+ */
+ if (ppvOpaque)
+ {
+ rtCrOpenSslInit();
+ int iAlgoNid = OBJ_txt2nid(pszObjId);
+ if (iAlgoNid != NID_undef)
+ {
+ const char *pszAlogSn = OBJ_nid2sn(iAlgoNid);
+ const EVP_MD *pEvpMdType = EVP_get_digestbyname(pszAlogSn);
+ if (pEvpMdType)
+ {
+ /*
+ * Return the OpenSSL provider descriptor and the EVP_MD address.
+ */
+# if OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)
+ Assert(EVP_MD_block_size(pEvpMdType));
+# else
+ Assert(pEvpMdType->md_size);
+# endif
+ *ppvOpaque = (void *)pEvpMdType;
+ return &g_rtCrDigestOpenSslDesc;
+ }
+ }
+ }
+#endif
+ return NULL;
+}
+
+
+RTDECL(PCRTCRDIGESTDESC) RTCrDigestFindByObjId(PCRTASN1OBJID pObjId, void **ppvOpaque)
+{
+ return RTCrDigestFindByObjIdString(pObjId->szObjId, ppvOpaque);
+}
+
+
+RTDECL(int) RTCrDigestCreateByObjIdString(PRTCRDIGEST phDigest, const char *pszObjId)
+{
+ void *pvOpaque;
+ PCRTCRDIGESTDESC pDesc = RTCrDigestFindByObjIdString(pszObjId, &pvOpaque);
+ if (pDesc)
+ return RTCrDigestCreate(phDigest, pDesc, pvOpaque);
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTCrDigestCreateByObjId(PRTCRDIGEST phDigest, PCRTASN1OBJID pObjId)
+{
+ void *pvOpaque;
+ PCRTCRDIGESTDESC pDesc = RTCrDigestFindByObjId(pObjId, &pvOpaque);
+ if (pDesc)
+ return RTCrDigestCreate(phDigest, pDesc, pvOpaque);
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(PCRTCRDIGESTDESC) RTCrDigestFindByType(RTDIGESTTYPE enmDigestType)
+{
+ AssertReturn(enmDigestType > RTDIGESTTYPE_INVALID && enmDigestType <= RTDIGESTTYPE_END, NULL);
+
+ uint32_t i = RT_ELEMENTS(g_apDigestOps);
+ while (i-- > 0)
+ if (g_apDigestOps[i]->enmType == enmDigestType)
+ return g_apDigestOps[i];
+ return NULL;
+}
+
+
+RTDECL(int) RTCrDigestCreateByType(PRTCRDIGEST phDigest, RTDIGESTTYPE enmDigestType)
+{
+ PCRTCRDIGESTDESC pDesc = RTCrDigestFindByType(enmDigestType);
+ if (pDesc)
+ return RTCrDigestCreate(phDigest, pDesc, NULL);
+ return VERR_NOT_FOUND;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/digest-core.cpp b/src/VBox/Runtime/common/crypto/digest-core.cpp
new file mode 100644
index 00000000..e4c0c658
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/digest-core.cpp
@@ -0,0 +1,499 @@
+/* $Id: digest-core.cpp $ */
+/** @file
+ * IPRT - Crypto - Cryptographic Hash / Message Digest API
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/digest.h>
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/crypto/x509.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Generic message digest instance.
+ */
+typedef struct RTCRDIGESTINT
+{
+ /** Magic value (RTCRDIGESTINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Pointer to the message digest descriptor. */
+ PCRTCRDIGESTDESC pDesc;
+ /** The offset into abState of the storage space . At
+ * least RTCRDIGESTDESC::cbHash bytes is available at that location. */
+ uint32_t offHash;
+ /** State. */
+ uint32_t uState;
+ /** The number of bytes consumed. */
+ uint64_t cbConsumed;
+ /** Pointer to the data specific to the message digest algorithm. Points
+ * either to &abState[0] or to memory allocated with pDesc->pfnNew. */
+ void *pvState;
+ /** Opaque data specific to the message digest algorithm, size given by
+ * RTCRDIGESTDESC::cbState. This is followed by space for the final hash
+ * at offHash with size RTCRDIGESTDESC::cbHash. The data specific to the
+ * message digest algorithm can also be 0. In this case, pDesc->pfnNew()
+ * and pDesc->pfnFree() must not be NULL. */
+ uint8_t abState[1];
+} RTCRDIGESTINT;
+/** Pointer to a message digest instance. */
+typedef RTCRDIGESTINT *PRTCRDIGESTINT;
+
+/** Magic value for RTCRDIGESTINT::u32Magic (Ralph C. Merkle). */
+#define RTCRDIGESTINT_MAGIC UINT32_C(0x19520202)
+
+/** @name RTCRDIGESTINT::uState values.
+ * @{ */
+/** Ready for more data. */
+#define RTCRDIGEST_STATE_READY UINT32_C(1)
+/** The hash has been finalized and can be found at offHash. */
+#define RTCRDIGEST_STATE_FINAL UINT32_C(2)
+/** Busted state, can happen after re-init. */
+#define RTCRDIGEST_STATE_BUSTED UINT32_C(3)
+/** @} */
+
+
+
+/**
+ * Used for successful returns which wants to hit at digest security.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VINF_CR_DIGEST_DEPRECATED
+ * @retval VINF_CR_DIGEST_COMPROMISED
+ * @retval VINF_CR_DIGEST_SEVERELY_COMPROMISED
+ * @param pDesc The digest descriptor.
+ */
+DECLINLINE(int) rtCrDigestSuccessWithDigestWarnings(PCRTCRDIGESTDESC pDesc)
+{
+ uint32_t const fFlags = pDesc->fFlags
+ & (RTCRDIGESTDESC_F_DEPRECATED | RTCRDIGESTDESC_F_COMPROMISED | RTCRDIGESTDESC_F_SERVERELY_COMPROMISED);
+ if (!fFlags)
+ return VINF_SUCCESS;
+ if (fFlags & RTCRDIGESTDESC_F_SERVERELY_COMPROMISED)
+ return VINF_CR_DIGEST_SEVERELY_COMPROMISED;
+ if (fFlags & RTCRDIGESTDESC_F_COMPROMISED)
+ return VINF_CR_DIGEST_COMPROMISED;
+ return VINF_CR_DIGEST_DEPRECATED;
+}
+
+
+RTDECL(int) RTCrDigestCreate(PRTCRDIGEST phDigest, PCRTCRDIGESTDESC pDesc, void *pvOpaque)
+{
+ AssertPtrReturn(phDigest, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDesc, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ uint32_t const offHash = RT_ALIGN_32(pDesc->cbState, 8);
+ AssertReturn(pDesc->pfnNew || offHash, VERR_INVALID_PARAMETER);
+ AssertReturn(!pDesc->pfnNew || (pDesc->pfnFree && pDesc->pfnInit && pDesc->pfnClone), VERR_INVALID_PARAMETER);
+ PRTCRDIGESTINT pThis = (PRTCRDIGESTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCRDIGESTINT, abState[offHash + pDesc->cbHash]));
+ if (pThis)
+ {
+ if (pDesc->pfnNew)
+ pThis->pvState = pDesc->pfnNew();
+ else
+ pThis->pvState = &pThis->abState[0];
+ if (pThis->pvState)
+ {
+ pThis->u32Magic = RTCRDIGESTINT_MAGIC;
+ pThis->cRefs = 1;
+ pThis->offHash = offHash;
+ pThis->pDesc = pDesc;
+ pThis->uState = RTCRDIGEST_STATE_READY;
+ if (pDesc->pfnInit)
+ rc = pDesc->pfnInit(pThis->pvState, pvOpaque, false /*fReInit*/);
+ if (RT_SUCCESS(rc))
+ {
+ *phDigest = pThis;
+ return rtCrDigestSuccessWithDigestWarnings(pDesc);
+ }
+ if (pDesc->pfnFree)
+ pDesc->pfnFree(pThis->pvState);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ pThis->u32Magic = 0;
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTCrDigestClone(PRTCRDIGEST phDigest, RTCRDIGEST hSrc)
+{
+ AssertPtrReturn(phDigest, VERR_INVALID_POINTER);
+ AssertPtrReturn(hSrc, VERR_INVALID_HANDLE);
+ AssertReturn(hSrc->u32Magic == RTCRDIGESTINT_MAGIC, VERR_INVALID_HANDLE);
+
+ int rc = VINF_SUCCESS;
+ uint32_t const offHash = hSrc->offHash;
+ PRTCRDIGESTINT pThis = (PRTCRDIGESTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCRDIGESTINT, abState[offHash + hSrc->pDesc->cbHash]));
+ if (pThis)
+ {
+ if (hSrc->pDesc->pfnNew)
+ pThis->pvState = hSrc->pDesc->pfnNew();
+ else
+ pThis->pvState = &pThis->abState[0];
+ if (pThis->pvState)
+ {
+ pThis->u32Magic = RTCRDIGESTINT_MAGIC;
+ pThis->cRefs = 1;
+ pThis->offHash = offHash;
+ pThis->pDesc = hSrc->pDesc;
+ if (hSrc->pDesc->pfnClone)
+ rc = hSrc->pDesc->pfnClone(pThis->pvState, hSrc->pvState);
+ else
+ {
+ Assert(!hSrc->pDesc->pfnNew);
+ memcpy(pThis->pvState, hSrc->pvState, offHash);
+ }
+ memcpy(&pThis->abState[offHash], &hSrc->abState[offHash], hSrc->pDesc->cbHash);
+ pThis->uState = hSrc->uState;
+
+ if (RT_SUCCESS(rc))
+ {
+ *phDigest = pThis;
+ return rtCrDigestSuccessWithDigestWarnings(pThis->pDesc);
+ }
+ if (hSrc->pDesc->pfnFree)
+ hSrc->pDesc->pfnFree(pThis->pvState);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ pThis->u32Magic = 0;
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTCrDigestReset(RTCRDIGEST hDigest)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, VERR_INVALID_HANDLE);
+
+ pThis->cbConsumed = 0;
+ pThis->uState = RTCRDIGEST_STATE_READY;
+
+ int rc = VINF_SUCCESS;
+ if (pThis->pDesc->pfnInit)
+ {
+ rc = pThis->pDesc->pfnInit(pThis->pvState, NULL, true /*fReInit*/);
+ if (RT_FAILURE(rc))
+ pThis->uState = RTCRDIGEST_STATE_BUSTED;
+ RT_BZERO(&pThis->abState[pThis->offHash], pThis->pDesc->cbHash);
+ }
+ else
+ {
+ Assert(!pThis->pDesc->pfnNew);
+ RT_BZERO(pThis->pvState, pThis->offHash + pThis->pDesc->cbHash);
+ }
+ return rc;
+}
+
+
+RTDECL(uint32_t) RTCrDigestRetain(RTCRDIGEST hDigest)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs < 64);
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTCrDigestRelease(RTCRDIGEST hDigest)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ if (pThis == NIL_RTCRDIGEST)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ if (!cRefs)
+ {
+ pThis->u32Magic = ~RTCRDIGESTINT_MAGIC;
+ if (pThis->pDesc->pfnDelete)
+ pThis->pDesc->pfnDelete(pThis->pvState);
+ if (pThis->pDesc->pfnFree)
+ pThis->pDesc->pfnFree(pThis->pvState);
+ RTMemFree(pThis);
+ }
+ Assert(cRefs < 64);
+ return cRefs;
+}
+
+
+RTDECL(int) RTCrDigestUpdate(RTCRDIGEST hDigest, void const *pvData, size_t cbData)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uState == RTCRDIGEST_STATE_READY, VERR_INVALID_STATE);
+
+ pThis->pDesc->pfnUpdate(pThis->pvState, pvData, cbData);
+ pThis->cbConsumed += cbData;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrDigestFinal(RTCRDIGEST hDigest, void *pvHash, size_t cbHash)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uState == RTCRDIGEST_STATE_READY || pThis->uState == RTCRDIGEST_STATE_FINAL, VERR_INVALID_STATE);
+ AssertPtrNullReturn(pvHash, VERR_INVALID_POINTER);
+
+ /*
+ * Make sure the hash calculation is final.
+ */
+ if (pThis->uState == RTCRDIGEST_STATE_READY)
+ {
+ pThis->pDesc->pfnFinal(pThis->pvState, &pThis->abState[pThis->offHash]);
+ pThis->uState = RTCRDIGEST_STATE_FINAL;
+ }
+ else
+ AssertReturn(pThis->uState == RTCRDIGEST_STATE_FINAL, VERR_INVALID_STATE);
+
+ /*
+ * Copy out the hash if requested.
+ */
+ if (cbHash > 0)
+ {
+ uint32_t cbNeeded = pThis->pDesc->cbHash;
+ if (pThis->pDesc->pfnGetHashSize)
+ cbNeeded = pThis->pDesc->pfnGetHashSize(pThis->pvState);
+ Assert(cbNeeded > 0);
+
+ if (cbNeeded == cbHash)
+ memcpy(pvHash, &pThis->abState[pThis->offHash], cbNeeded);
+ else if (cbNeeded < cbHash)
+ {
+ memcpy(pvHash, &pThis->abState[pThis->offHash], cbNeeded);
+ memset((uint8_t *)pvHash + cbNeeded, 0, cbHash - cbNeeded);
+ return VINF_BUFFER_UNDERFLOW;
+ }
+ else
+ {
+ memcpy(pvHash, &pThis->abState[pThis->offHash], cbHash);
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+
+ return rtCrDigestSuccessWithDigestWarnings(pThis->pDesc);
+}
+
+
+RTDECL(bool) RTCrDigestMatch(RTCRDIGEST hDigest, void const *pvHash, size_t cbHash)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+
+ int rc = RTCrDigestFinal(pThis, NULL, 0);
+ AssertRCReturn(rc, false);
+
+ AssertPtrReturn(pvHash, false);
+ return pThis->pDesc->cbHash == cbHash
+ && !memcmp(&pThis->abState[pThis->offHash], pvHash, cbHash);
+}
+
+
+RTDECL(uint8_t const *) RTCrDigestGetHash(RTCRDIGEST hDigest)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, NULL);
+
+ int rc = RTCrDigestFinal(pThis, NULL, 0);
+ AssertRCReturn(rc, NULL);
+
+ return &pThis->abState[pThis->offHash];
+}
+
+
+RTDECL(uint32_t) RTCrDigestGetHashSize(RTCRDIGEST hDigest)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, 0);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, 0);
+ if (pThis->pDesc->pfnGetHashSize)
+ {
+ uint32_t cbHash = pThis->pDesc->pfnGetHashSize(pThis->pvState);
+ Assert(cbHash <= pThis->pDesc->cbHash);
+ return cbHash;
+ }
+ return pThis->pDesc->cbHash;
+}
+
+
+RTDECL(uint64_t) RTCrDigestGetConsumedSize(RTCRDIGEST hDigest)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, 0);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, 0);
+ return pThis->cbConsumed;
+}
+
+
+RTDECL(bool) RTCrDigestIsFinalized(RTCRDIGEST hDigest)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, false);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, false);
+ return pThis->uState == RTCRDIGEST_STATE_FINAL;
+}
+
+
+RTDECL(RTDIGESTTYPE) RTCrDigestGetType(RTCRDIGEST hDigest)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, RTDIGESTTYPE_INVALID);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, RTDIGESTTYPE_INVALID);
+
+ RTDIGESTTYPE enmType = pThis->pDesc->enmType;
+ if (pThis->pDesc->pfnGetDigestType)
+ enmType = pThis->pDesc->pfnGetDigestType(pThis->pvState);
+ return enmType;
+}
+
+
+RTDECL(const char *) RTCrDigestGetAlgorithmOid(RTCRDIGEST hDigest)
+{
+ return RTCrDigestTypeToAlgorithmOid(RTCrDigestGetType(hDigest));
+}
+
+
+RTDECL(uint32_t) RTCrDigestGetFlags(RTCRDIGEST hDigest)
+{
+ PRTCRDIGESTINT pThis = hDigest;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRDIGESTINT_MAGIC, UINT32_MAX);
+ return pThis->pDesc->fFlags;
+}
+
+
+RTDECL(const char *) RTCrDigestTypeToAlgorithmOid(RTDIGESTTYPE enmDigestType)
+{
+ switch (enmDigestType)
+ {
+ case RTDIGESTTYPE_MD2: return RTCRX509ALGORITHMIDENTIFIERID_MD2;
+ case RTDIGESTTYPE_MD4: return RTCRX509ALGORITHMIDENTIFIERID_MD4;
+ case RTDIGESTTYPE_MD5: return RTCRX509ALGORITHMIDENTIFIERID_MD5;
+ case RTDIGESTTYPE_SHA1: return RTCRX509ALGORITHMIDENTIFIERID_SHA1;
+ case RTDIGESTTYPE_SHA224: return RTCRX509ALGORITHMIDENTIFIERID_SHA224;
+ case RTDIGESTTYPE_SHA256: return RTCRX509ALGORITHMIDENTIFIERID_SHA256;
+ case RTDIGESTTYPE_SHA384: return RTCRX509ALGORITHMIDENTIFIERID_SHA384;
+ case RTDIGESTTYPE_SHA512: return RTCRX509ALGORITHMIDENTIFIERID_SHA512;
+ case RTDIGESTTYPE_SHA512T224: return RTCRX509ALGORITHMIDENTIFIERID_SHA512T224;
+ case RTDIGESTTYPE_SHA512T256: return RTCRX509ALGORITHMIDENTIFIERID_SHA512T256;
+ case RTDIGESTTYPE_SHA3_224: return RTCRX509ALGORITHMIDENTIFIERID_SHA3_224;
+ case RTDIGESTTYPE_SHA3_256: return RTCRX509ALGORITHMIDENTIFIERID_SHA3_256;
+ case RTDIGESTTYPE_SHA3_384: return RTCRX509ALGORITHMIDENTIFIERID_SHA3_384;
+ case RTDIGESTTYPE_SHA3_512: return RTCRX509ALGORITHMIDENTIFIERID_SHA3_512;
+ default: return NULL;
+ }
+}
+
+
+RTDECL(const char *) RTCrDigestTypeToName(RTDIGESTTYPE enmDigestType)
+{
+ switch (enmDigestType)
+ {
+ case RTDIGESTTYPE_CRC32: return "CRC32";
+ case RTDIGESTTYPE_CRC64: return "CRC64";
+ case RTDIGESTTYPE_MD2: return "MD2";
+ case RTDIGESTTYPE_MD4: return "MD4";
+ case RTDIGESTTYPE_MD5: return "MD5";
+ case RTDIGESTTYPE_SHA1: return "SHA-1";
+ case RTDIGESTTYPE_SHA224: return "SHA-224";
+ case RTDIGESTTYPE_SHA256: return "SHA-256";
+ case RTDIGESTTYPE_SHA384: return "SHA-384";
+ case RTDIGESTTYPE_SHA512: return "SHA-512";
+ case RTDIGESTTYPE_SHA512T224: return "SHA-512/224";
+ case RTDIGESTTYPE_SHA512T256: return "SHA-512/256";
+ case RTDIGESTTYPE_SHA3_224: return "SHA3-224";
+ case RTDIGESTTYPE_SHA3_256: return "SHA3-256";
+ case RTDIGESTTYPE_SHA3_384: return "SHA3-384";
+ case RTDIGESTTYPE_SHA3_512: return "SHA3-512";
+ default: return NULL;
+ }
+}
+
+
+RTDECL(uint32_t) RTCrDigestTypeToHashSize(RTDIGESTTYPE enmDigestType)
+{
+ switch (enmDigestType)
+ {
+ case RTDIGESTTYPE_CRC32: return 32 / 8;
+ case RTDIGESTTYPE_CRC64: return 64 / 8;
+ case RTDIGESTTYPE_MD2: return 128 / 8;
+ case RTDIGESTTYPE_MD4: return 128 / 8;
+ case RTDIGESTTYPE_MD5: return 128 / 8;
+ case RTDIGESTTYPE_SHA1: return 160 / 8;
+ case RTDIGESTTYPE_SHA224: return 224 / 8;
+ case RTDIGESTTYPE_SHA256: return 256 / 8;
+ case RTDIGESTTYPE_SHA384: return 384 / 8;
+ case RTDIGESTTYPE_SHA512: return 512 / 8;
+ case RTDIGESTTYPE_SHA512T224: return 224 / 8;
+ case RTDIGESTTYPE_SHA512T256: return 256 / 8;
+ case RTDIGESTTYPE_SHA3_224: return 224 / 8;
+ case RTDIGESTTYPE_SHA3_256: return 256 / 8;
+ case RTDIGESTTYPE_SHA3_384: return 384 / 8;
+ case RTDIGESTTYPE_SHA3_512: return 512 / 8;
+ default:
+ AssertFailed();
+ return 0;
+ }
+}
+
diff --git a/src/VBox/Runtime/common/crypto/digest-vfs.cpp b/src/VBox/Runtime/common/crypto/digest-vfs.cpp
new file mode 100644
index 00000000..029db793
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/digest-vfs.cpp
@@ -0,0 +1,80 @@
+/* $Id: digest-vfs.cpp $ */
+/** @file
+ * IPRT - Crypto - Cryptographic Hash / Message Digest API, VFS related interfaces
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/digest.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/vfs.h>
+
+
+RTDECL(int) RTCrDigestUpdateFromVfsFile(RTCRDIGEST hDigest, RTVFSFILE hVfsFile, bool fRewindFile)
+{
+ int rc;
+ if (fRewindFile)
+ rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ else
+ rc = VINF_SUCCESS;
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ char abBuf[_16K];
+ size_t cbRead;
+ rc = RTVfsFileRead(hVfsFile, abBuf, sizeof(abBuf), &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ bool const fEof = rc == VINF_EOF;
+ rc = RTCrDigestUpdate(hDigest, abBuf, cbRead);
+ if (fEof || RT_FAILURE(rc))
+ break;
+ }
+ else
+ {
+ Assert(rc != VERR_EOF);
+ break;
+ }
+ }
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/iprt-openssl.cpp b/src/VBox/Runtime/common/crypto/iprt-openssl.cpp
new file mode 100644
index 00000000..3bc2801c
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/iprt-openssl.cpp
@@ -0,0 +1,202 @@
+/* $Id: iprt-openssl.cpp $ */
+/** @file
+ * IPRT - Crypto - OpenSSL Helpers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+
+#ifdef IPRT_WITH_OPENSSL /* Whole file. */
+# include <iprt/err.h>
+# include <iprt/string.h>
+# include <iprt/mem.h>
+# include <iprt/asn1.h>
+# include <iprt/crypto/digest.h>
+# include <iprt/crypto/pkcs7.h>
+# include <iprt/crypto/spc.h>
+
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/x509.h>
+# include <openssl/err.h>
+# include "internal/openssl-post.h"
+
+
+DECLHIDDEN(void) rtCrOpenSslInit(void)
+{
+ static bool s_fOssInitalized;
+ if (!s_fOssInitalized)
+ {
+ OpenSSL_add_all_algorithms();
+ ERR_load_ERR_strings();
+ ERR_load_crypto_strings();
+
+ /* Add some OIDs we might possibly want to use. */
+ static struct { const char *pszOid, *pszDesc; } const s_aOids[] =
+ {
+ { RTCRSPC_PE_IMAGE_HASHES_V1_OID, "Ms-SpcPeImagePageHashesV1" },
+ { RTCRSPC_PE_IMAGE_HASHES_V2_OID, "Ms-SpcPeImagePageHashesV2" },
+ { RTCRSPC_STMT_TYPE_INDIVIDUAL_CODE_SIGNING, "Ms-SpcIndividualCodeSigning" },
+ { RTCRSPCPEIMAGEDATA_OID, "Ms-SpcPeImageData" },
+ { RTCRSPCINDIRECTDATACONTENT_OID, "Ms-SpcIndirectDataContext" },
+ { RTCR_PKCS9_ID_MS_TIMESTAMP, "Ms-CounterSign" },
+ { RTCR_PKCS9_ID_MS_NESTED_SIGNATURE, "Ms-SpcNestedSignature" },
+ { RTCR_PKCS9_ID_MS_STATEMENT_TYPE, "Ms-SpcStatementType" },
+ { RTCR_PKCS9_ID_MS_SP_OPUS_INFO, "Ms-SpcOpusInfo" },
+ { "1.3.6.1.4.1.311.3.2.1", "Ms-SpcTimeStampRequest" }, /** @todo define */
+ { "1.3.6.1.4.1.311.10.1", "Ms-CertTrustList" }, /** @todo define */
+ };
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOids); i++)
+ OBJ_create(s_aOids[i].pszOid, s_aOids[i].pszDesc, s_aOids[i].pszDesc);
+
+ s_fOssInitalized = true;
+ }
+}
+
+
+DECLHIDDEN(int) rtCrOpenSslErrInfoCallback(const char *pach, size_t cch, void *pvUser)
+{
+ PRTERRINFO pErrInfo = (PRTERRINFO)pvUser;
+ size_t cchAlready = pErrInfo->fFlags & RTERRINFO_FLAGS_SET ? strlen(pErrInfo->pszMsg) : 0;
+ if (cchAlready + 1 < pErrInfo->cbMsg)
+ RTStrCopyEx(pErrInfo->pszMsg + cchAlready, pErrInfo->cbMsg - cchAlready, pach, cch);
+ return -1;
+}
+
+
+DECLHIDDEN(int) rtCrOpenSslConvertX509Cert(void **ppvOsslCert, PCRTCRX509CERTIFICATE pCert, PRTERRINFO pErrInfo)
+{
+ const unsigned char *pabEncoded;
+ uint32_t cbEncoded;
+ void *pvFree;
+ int rc = RTAsn1EncodeQueryRawBits(RTCrX509Certificate_GetAsn1Core(pCert),
+ (const uint8_t **)&pabEncoded, &cbEncoded, &pvFree, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ X509 *pOsslCert = NULL;
+ X509 *pOsslCertRet = d2i_X509(&pOsslCert, &pabEncoded, cbEncoded);
+ RTMemTmpFree(pvFree);
+ if (pOsslCert != NULL && pOsslCertRet == pOsslCert)
+ {
+ *ppvOsslCert = pOsslCert;
+ return VINF_SUCCESS;
+ }
+ rc = RTErrInfoSet(pErrInfo, VERR_CR_X509_OSSL_D2I_FAILED, "d2i_X509");
+
+ }
+ *ppvOsslCert = NULL;
+ return rc;
+}
+
+
+DECLHIDDEN(void) rtCrOpenSslFreeConvertedX509Cert(void *pvOsslCert)
+{
+ X509_free((X509 *)pvOsslCert);
+}
+
+
+DECLHIDDEN(int) rtCrOpenSslAddX509CertToStack(void *pvOsslStack, PCRTCRX509CERTIFICATE pCert, PRTERRINFO pErrInfo)
+{
+ X509 *pOsslCert = NULL;
+ int rc = rtCrOpenSslConvertX509Cert((void **)&pOsslCert, pCert, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (sk_X509_push((STACK_OF(X509) *)pvOsslStack, pOsslCert))
+ rc = VINF_SUCCESS;
+ else
+ {
+ rtCrOpenSslFreeConvertedX509Cert(pOsslCert);
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "sk_X509_push");
+ }
+ }
+ return rc;
+}
+
+
+DECLHIDDEN(const void /*EVP_MD*/ *) rtCrOpenSslConvertDigestType(RTDIGESTTYPE enmDigestType, PRTERRINFO pErrInfo)
+{
+ const char *pszAlgoObjId = RTCrDigestTypeToAlgorithmOid(enmDigestType);
+ AssertReturnStmt(pszAlgoObjId, RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Invalid type: %d", enmDigestType), NULL);
+
+ int iAlgoNid = OBJ_txt2nid(pszAlgoObjId);
+ AssertReturnStmt(iAlgoNid != NID_undef,
+ RTErrInfoSetF(pErrInfo, VERR_CR_DIGEST_OSSL_DIGEST_INIT_ERROR,
+ "OpenSSL does not know: %s (%s)", pszAlgoObjId, RTCrDigestTypeToName(enmDigestType)),
+ NULL);
+
+ const char *pszAlgoSn = OBJ_nid2sn(iAlgoNid);
+ const EVP_MD *pEvpMdType = EVP_get_digestbyname(pszAlgoSn);
+ AssertReturnStmt(pEvpMdType,
+ RTErrInfoSetF(pErrInfo, VERR_CR_DIGEST_OSSL_DIGEST_INIT_ERROR, "OpenSSL/EVP does not know: %d (%s; %s; %s)",
+ iAlgoNid, pszAlgoSn, pszAlgoSn, RTCrDigestTypeToName(enmDigestType)),
+ NULL);
+
+ return pEvpMdType;
+}
+
+DECLHIDDEN(int) rtCrOpenSslConvertPkcs7Attribute(void **ppvOsslAttrib, PCRTCRPKCS7ATTRIBUTE pAttrib, PRTERRINFO pErrInfo)
+{
+ const unsigned char *pabEncoded;
+ uint32_t cbEncoded;
+ void *pvFree;
+ int rc = RTAsn1EncodeQueryRawBits(RTCrPkcs7Attribute_GetAsn1Core(pAttrib),
+ (const uint8_t **)&pabEncoded, &cbEncoded, &pvFree, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ X509_ATTRIBUTE *pOsslAttrib = NULL;
+ X509_ATTRIBUTE *pOsslAttribRet = d2i_X509_ATTRIBUTE(&pOsslAttrib, &pabEncoded, cbEncoded);
+ RTMemTmpFree(pvFree);
+ if (pOsslAttrib != NULL && pOsslAttribRet == pOsslAttrib)
+ {
+ *ppvOsslAttrib = pOsslAttrib;
+ return VINF_SUCCESS;
+ }
+ rc = RTErrInfoSet(pErrInfo, VERR_CR_X509_OSSL_D2I_FAILED, "d2i_X509_ATTRIBUTE");
+ }
+ *ppvOsslAttrib = NULL;
+ return rc;
+}
+
+
+DECLHIDDEN(void) rtCrOpenSslFreeConvertedPkcs7Attribute(void *pvOsslAttrib)
+{
+ X509_ATTRIBUTE_free((X509_ATTRIBUTE *)pvOsslAttrib);
+}
+
+
+#endif /* IPRT_WITH_OPENSSL */
+
diff --git a/src/VBox/Runtime/common/crypto/key-create-rsa-openssl.cpp b/src/VBox/Runtime/common/crypto/key-create-rsa-openssl.cpp
new file mode 100644
index 00000000..44f265f6
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/key-create-rsa-openssl.cpp
@@ -0,0 +1,104 @@
+/* $Id: key-create-rsa-openssl.cpp $ */
+/** @file
+ * IPRT - Crypto - RSA Key Creation using OpenSSL.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/key.h>
+
+#ifdef IPRT_WITH_OPENSSL /* Whole file. */
+# include <iprt/err.h>
+# include <iprt/string.h>
+
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/rsa.h>
+# include <openssl/err.h>
+# include "internal/openssl-post.h"
+
+# include "key-internal.h"
+
+
+
+RTDECL(int) RTCrKeyCreateNewRsa(PRTCRKEY phKey, uint32_t cBits, uint32_t uPubExp, uint32_t fFlags)
+{
+ AssertPtrReturn(phKey, VERR_INVALID_POINTER);
+ AssertMsgReturn(cBits >= 128 && cBits <= _64K, ("cBits=%u\n", cBits), VERR_OUT_OF_RANGE);
+ AssertReturn(uPubExp > 0, VERR_OUT_OF_RANGE);
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+
+ rtCrOpenSslInit();
+
+ /*
+ * Do the key generation first.
+ */
+ int rc = VERR_NO_MEMORY;
+ RSA *pRsa = RSA_new();
+ if (pRsa)
+ {
+ BIGNUM *pPubExp = BN_new();
+ if (pPubExp)
+ {
+ if (BN_set_word(pPubExp, uPubExp) != 0)
+ {
+ if (RSA_generate_key_ex(pRsa, cBits, pPubExp, NULL))
+ {
+ /*
+ * Create a IPRT key for it by encoding it as a private key.
+ */
+ unsigned char *pbRsaPrivateKey = NULL;
+ int cbRsaPrivateKey = i2d_RSAPrivateKey(pRsa, &pbRsaPrivateKey);
+ if (cbRsaPrivateKey > 0)
+ {
+ rc = rtCrKeyCreateRsaPrivate(phKey, pbRsaPrivateKey, cbRsaPrivateKey, NULL, NULL);
+ OPENSSL_free(pbRsaPrivateKey);
+ }
+ /* else: VERR_NO_MEMORY */
+ }
+ else
+ rc = VERR_CR_KEY_GEN_FAILED_RSA;
+ }
+ BN_free(pPubExp);
+ }
+ RSA_free(pRsa);
+ }
+ return rc;
+}
+
+#endif /* IPRT_WITH_OPENSSL */
+
diff --git a/src/VBox/Runtime/common/crypto/key-file.cpp b/src/VBox/Runtime/common/crypto/key-file.cpp
new file mode 100644
index 00000000..62ea549f
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/key-file.cpp
@@ -0,0 +1,688 @@
+/* $Id: key-file.cpp $ */
+/** @file
+ * IPRT - Crypto - Cryptographic Keys, File I/O.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_CRYPTO
+#include "internal/iprt.h"
+#include <iprt/crypto/key.h>
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/memsafer.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/crypto/rsa.h>
+#include <iprt/crypto/pkcs8.h>
+#include <iprt/crypto/pkix.h>
+#include <iprt/crypto/x509.h>
+
+#include "internal/magics.h"
+#include "key-internal.h"
+
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/err.h>
+# include <openssl/evp.h>
+# include <openssl/pkcs12.h>
+# include "internal/openssl-post.h"
+# ifndef OPENSSL_VERSION_NUMBER
+# error "Missing OPENSSL_VERSION_NUMBER!"
+# endif
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/** RSA public key marker words. */
+static RTCRPEMMARKERWORD const g_aWords_RsaPublicKey[] =
+{ { RT_STR_TUPLE("RSA") }, { RT_STR_TUPLE("PUBLIC") }, { RT_STR_TUPLE("KEY") } };
+/** Generic public key marker words. */
+static RTCRPEMMARKERWORD const g_aWords_PublicKey[] =
+{ { RT_STR_TUPLE("PUBLIC") }, { RT_STR_TUPLE("KEY") } };
+
+/** Public key markers. */
+RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrKeyPublicMarkers[] =
+{
+ { g_aWords_RsaPublicKey, RT_ELEMENTS(g_aWords_RsaPublicKey) },
+ { g_aWords_PublicKey, RT_ELEMENTS(g_aWords_PublicKey) },
+};
+/** Number of entries in g_aRTCrKeyPublicMarkers. */
+RT_DECL_DATA_CONST(uint32_t const) g_cRTCrKeyPublicMarkers = RT_ELEMENTS(g_aRTCrKeyPublicMarkers);
+
+
+/** RSA private key marker words. */
+static RTCRPEMMARKERWORD const g_aWords_RsaPrivateKey[] =
+{ { RT_STR_TUPLE("RSA") }, { RT_STR_TUPLE("PRIVATE") }, { RT_STR_TUPLE("KEY") } };
+/** Generic encrypted private key marker words. */
+static RTCRPEMMARKERWORD const g_aWords_EncryptedPrivateKey[] =
+{ { RT_STR_TUPLE("ENCRYPTED") }, { RT_STR_TUPLE("PRIVATE") }, { RT_STR_TUPLE("KEY") } };
+/** Generic private key marker words. */
+static RTCRPEMMARKERWORD const g_aWords_PrivateKey[] =
+{ { RT_STR_TUPLE("PRIVATE") }, { RT_STR_TUPLE("KEY") } };
+
+/** Private key markers. */
+RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrKeyPrivateMarkers[] =
+{
+ { g_aWords_RsaPrivateKey, RT_ELEMENTS(g_aWords_RsaPrivateKey) },
+ { g_aWords_EncryptedPrivateKey, RT_ELEMENTS(g_aWords_EncryptedPrivateKey) },
+ { g_aWords_PrivateKey, RT_ELEMENTS(g_aWords_PrivateKey) },
+};
+/** Number of entries in g_aRTCrKeyPrivateMarkers. */
+RT_DECL_DATA_CONST(uint32_t const) g_cRTCrKeyPrivateMarkers = RT_ELEMENTS(g_aRTCrKeyPrivateMarkers);
+
+
+/** Private and public key markers. */
+RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrKeyAllMarkers[] =
+{
+ { g_aWords_RsaPublicKey, RT_ELEMENTS(g_aWords_RsaPublicKey) },
+ { g_aWords_PublicKey, RT_ELEMENTS(g_aWords_PublicKey) },
+ { g_aWords_RsaPrivateKey, RT_ELEMENTS(g_aWords_RsaPrivateKey) },
+ { g_aWords_EncryptedPrivateKey, RT_ELEMENTS(g_aWords_EncryptedPrivateKey) },
+ { g_aWords_PrivateKey, RT_ELEMENTS(g_aWords_PrivateKey) },
+};
+/** Number of entries in g_aRTCrKeyAllMarkers. */
+RT_DECL_DATA_CONST(uint32_t const) g_cRTCrKeyAllMarkers = RT_ELEMENTS(g_aRTCrKeyAllMarkers);
+
+
+/**
+ * Creates a key from a raw PKCS\#8 PrivateKeyInfo structure.
+ *
+ * This is common code to both kKeyFormat_PrivateKeyInfo and
+ * kKeyFormat_EncryptedPrivateKeyInfo.
+ *
+ * @returns IPRT status code.
+ * @param phKey Where to return the key handle on success.
+ * @param pPrimaryCursor Cursor structure to use.
+ * @param pbRaw The raw PrivateKeyInfo bytes.
+ * @param cbRaw Size of the raw PrivateKeyInfo structure.
+ * @param pErrInfo Where to return additional error information.
+ * @param pszErrorTag What to tag the decoding with.
+ */
+static int rtCrKeyCreateFromPrivateKeyInfo(PRTCRKEY phKey, PRTASN1CURSORPRIMARY pPrimaryCursor,
+ uint8_t const *pbRaw, size_t cbRaw, PRTERRINFO pErrInfo, const char *pszErrorTag)
+
+{
+ RTCRPKCS8PRIVATEKEYINFO PrivateKeyInfo;
+ RT_ZERO(PrivateKeyInfo);
+ RTAsn1CursorInitPrimary(pPrimaryCursor, pbRaw, (uint32_t)cbRaw, pErrInfo, &g_RTAsn1DefaultAllocator,
+ RTASN1CURSOR_FLAGS_DER, pszErrorTag);
+ int rc = RTCrPkcs8PrivateKeyInfo_DecodeAsn1(&pPrimaryCursor->Cursor, 0, &PrivateKeyInfo,
+ pszErrorTag ? pszErrorTag : "PrivateKeyInfo");
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Load the private key according to it's algorithm.
+ * We currently only support RSA (pkcs1-RsaEncryption).
+ */
+ if (RTAsn1ObjId_CompareWithString(&PrivateKeyInfo.PrivateKeyAlgorithm.Algorithm, RTCRX509ALGORITHMIDENTIFIERID_RSA) == 0)
+ rc = rtCrKeyCreateRsaPrivate(phKey, PrivateKeyInfo.PrivateKey.Asn1Core.uData.pv,
+ PrivateKeyInfo.PrivateKey.Asn1Core.cb, pErrInfo, pszErrorTag);
+ else
+ rc = RTERRINFO_LOG_SET(pErrInfo, VERR_CR_KEY_FORMAT_NOT_SUPPORTED,
+ "Support for PKCS#8 PrivateKeyInfo for non-RSA keys is not yet implemented");
+ RTCrPkcs8PrivateKeyInfo_Delete(&PrivateKeyInfo);
+ }
+ return rc;
+}
+
+
+/**
+ * Decrypts a PEM message.
+ *
+ * @returns IPRT status code
+ * @param pEncryptedKey The encrypted private key information.
+ * @param pszPassword The password to use to decrypt the key text.
+ * @param ppbDecrypted Where to return the decrypted message. Free using RTMemSaferFree.
+ * @param pcbDecrypted Where to return the length of the decrypted message.
+ * @param pcbDecryptedAlloced Where to return the allocation size.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtCrKeyDecryptPkcs8Info(PRTCRPKCS8ENCRYPTEDPRIVATEKEYINFO pEncryptedKey, const char *pszPassword,
+ uint8_t **ppbDecrypted, size_t *pcbDecrypted, size_t *pcbDecryptedAlloced, PRTERRINFO pErrInfo)
+{
+ /*
+ * Initialize return values.
+ */
+ *ppbDecrypted = NULL;
+ *pcbDecrypted = 0;
+ *pcbDecryptedAlloced = 0;
+
+ /*
+ * This operation requires a password.
+ */
+ if (!pszPassword)
+ return VERR_CR_KEY_ENCRYPTED;
+
+#ifdef IPRT_WITH_OPENSSL /** @todo abstract encryption & decryption. */
+
+ /*
+ * Query the EncryptionAlgorithm bytes so we can construction a X509_ALGOR
+ * for use in PKCS12_pbe_crypt.
+ */
+ void *pvAlgoFree = NULL;
+ const uint8_t *pbAlgoRaw = NULL;
+ uint32_t cbAlgoRaw = 0;
+ int rc = RTAsn1EncodeQueryRawBits(&pEncryptedKey->EncryptionAlgorithm.SeqCore.Asn1Core,
+ &pbAlgoRaw, &cbAlgoRaw, &pvAlgoFree, pErrInfo);
+ AssertRCReturn(rc, rc);
+
+ const unsigned char *puchAlgo = pbAlgoRaw;
+ X509_ALGOR *pOsslAlgoRet = NULL;
+ pOsslAlgoRet = d2i_X509_ALGOR(&pOsslAlgoRet, &puchAlgo, cbAlgoRaw);
+
+ RTMemTmpFree(pvAlgoFree);
+ if (pOsslAlgoRet)
+ {
+ /*
+ * Do the decryption (en_de = 0).
+ */
+ int cbDecrypted = 0;
+ unsigned char *puchDecrypted = NULL;
+ puchDecrypted = PKCS12_pbe_crypt(pOsslAlgoRet, pszPassword, (int)strlen(pszPassword),
+ (unsigned char *)pEncryptedKey->EncryptedData.Asn1Core.uData.puch, /* cast for v1.0.x */
+ (int)pEncryptedKey->EncryptedData.Asn1Core.cb,
+ &puchDecrypted, &cbDecrypted, 0 /*en_de*/);
+ if (puchDecrypted)
+ {
+ /*
+ * Transfer to a safer buffer and carefully wipe the OpenSSL buffer.
+ */
+ uint8_t *pbFinal = (uint8_t *)RTMemSaferAllocZ(cbDecrypted);
+ if (pbFinal)
+ {
+ memcpy(pbFinal, puchDecrypted, cbDecrypted);
+ *ppbDecrypted = pbFinal;
+ *pcbDecrypted = cbDecrypted;
+ *pcbDecryptedAlloced = cbDecrypted;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ RTMemWipeThoroughly(puchDecrypted, cbDecrypted, 3);
+ OPENSSL_free(puchDecrypted);
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_KEY_DECRYPTION_FAILED,
+ "Incorrect password? d2i_X509_ALGOR failed (%u)", ERR_get_error());
+ X509_ALGOR_free(pOsslAlgoRet);
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_D2I_PRIVATE_KEY_FAILED /* close enough */,
+ "d2i_X509_ALGOR failed (%u)", ERR_get_error());
+ return rc;
+
+#else
+ RT_NOREF(pEncryptedKey, pszPassword, pErrInfo);
+ return VERR_CR_KEY_DECRYPTION_NOT_SUPPORTED;
+#endif
+}
+
+
+/**
+ * Decrypts a PEM message.
+ *
+ * @returns IPRT status code
+ * @param pszDekInfo The decryption info. See RFC-1421 section 4.6.1.3
+ * as well as RFC-1423).
+ * @param pszPassword The password to use to decrypt the key text.
+ * @param pbEncrypted The encrypted key text.
+ * @param cbEncrypted The size of the encrypted text.
+ * @param ppbDecrypted Where to return the decrypted message. Free using RTMemSaferFree.
+ * @param pcbDecrypted Where to return the length of the decrypted message.
+ * @param pcbDecryptedAlloced Where to return the allocation size.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtCrKeyDecryptPemMessage(const char *pszDekInfo, const char *pszPassword, uint8_t *pbEncrypted, size_t cbEncrypted,
+ uint8_t **ppbDecrypted, size_t *pcbDecrypted, size_t *pcbDecryptedAlloced, PRTERRINFO pErrInfo)
+{
+ /*
+ * Initialize return values.
+ */
+ *ppbDecrypted = NULL;
+ *pcbDecrypted = 0;
+ *pcbDecryptedAlloced = 0;
+
+ /*
+ * Parse the DEK-Info.
+ */
+ if (!pszDekInfo)
+ return VERR_CR_KEY_NO_DEK_INFO;
+
+ /* Find the end of the algorithm */
+ const char *pszParams = strchr(pszDekInfo, ',');
+ if (!pszParams)
+ pszParams = strchr(pszDekInfo, '\0');
+ size_t cchAlgo = pszParams - pszDekInfo;
+ while (cchAlgo > 0 && RT_C_IS_SPACE(pszDekInfo[cchAlgo - 1]))
+ cchAlgo--;
+
+ /* Copy it out and zero terminating it. */
+ char szAlgo[256];
+ if (cchAlgo >= sizeof(szAlgo))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_DEK_INFO_TOO_LONG, "Algorithms list is too long (%s)", pszDekInfo);
+ memcpy(szAlgo, pszDekInfo, cchAlgo);
+ szAlgo[cchAlgo] = '\0';
+
+ /* Parameters. */
+ pszParams = RTStrStripL(*pszParams == ',' ? pszParams + 1 : pszParams);
+ size_t const cchParams = strlen(pszParams);
+
+ /*
+ * Do we support the cipher?
+ */
+#ifdef IPRT_WITH_OPENSSL /** @todo abstract encryption & decryption. */
+ const EVP_CIPHER *pCipher = EVP_get_cipherbyname(szAlgo);
+ if (!pCipher)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_UNSUPPORTED_CIPHER, "Unknown key cipher: %s (params: %s)", szAlgo, pszParams);
+
+ /* Decode the initialization vector if one is required. */
+ uint8_t *pbInitVector = NULL;
+ int const cbInitVector = EVP_CIPHER_iv_length(pCipher);
+ if (cbInitVector > 0)
+ {
+ if (*pszParams == '\0')
+ return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_MISSING_CIPHER_PARAMS,
+ "Cipher '%s' expected %u bytes initialization vector, none found", cbInitVector, szAlgo);
+ if ((size_t)cbInitVector > cchParams / 2)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_TOO_SHORT_CIPHER_IV,
+ "Too short initialization vector for '%s', expected %u chars found only %u: %s",
+ szAlgo, cbInitVector * 2, cchParams, pszParams);
+ pbInitVector = (uint8_t *)alloca(cbInitVector);
+ int rc = RTStrConvertHexBytes(pszParams, pbInitVector, cbInitVector, 0 /*fFlags*/);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_BUFFER_OVERFLOW /* openssl ignores this condition */)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_MALFORMED_CIPHER_IV,
+ "Malformed initialization vector for '%s': %s (rc=%Rrc)", szAlgo, pszParams, rc);
+ }
+ else if (*pszParams != '\0')
+ return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_UNEXPECTED_CIPHER_PARAMS,
+ "Cipher '%s' expected no parameters, found: %s", szAlgo, pszParams);
+
+ /*
+ * Do we have a password? If so try decrypt the key.
+ */
+ if (!pszPassword)
+ return VERR_CR_KEY_ENCRYPTED;
+
+ unsigned char abKey[EVP_MAX_KEY_LENGTH * 2];
+ int cbKey = EVP_BytesToKey(pCipher, EVP_md5(), pbInitVector, (unsigned char const *)pszPassword, (int)strlen(pszPassword),
+ 1, abKey, NULL);
+ if (!cbKey)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_PASSWORD_ENCODING, "EVP_BytesToKey failed to encode password");
+
+ EVP_CIPHER_CTX *pCipherCtx = EVP_CIPHER_CTX_new();
+ if (!pCipherCtx)
+ return VERR_NO_MEMORY;
+
+ int rc;
+ if (EVP_DecryptInit_ex(pCipherCtx, pCipher, NULL /*pEngine*/, abKey, pbInitVector))
+ {
+ size_t cbDecryptedAlloced = cbEncrypted;
+ int cbDecrypted = (int)cbDecryptedAlloced;
+ uint8_t *pbDecrypted = (uint8_t *)RTMemSaferAllocZ(cbDecryptedAlloced);
+ if (pbDecrypted)
+ {
+ if (EVP_DecryptUpdate(pCipherCtx, pbDecrypted, &cbDecrypted, pbEncrypted, (int)cbEncrypted))
+ {
+ int cbFinal = (int)cbDecryptedAlloced - cbDecrypted;
+ if (EVP_DecryptFinal_ex(pCipherCtx, &pbDecrypted[cbDecrypted], &cbFinal))
+ {
+ cbDecrypted += cbFinal;
+ Assert((size_t)cbDecrypted <= cbDecryptedAlloced);
+
+ /*
+ * Done! Just set the return values.
+ */
+ *pcbDecrypted = cbDecrypted;
+ *pcbDecryptedAlloced = cbDecryptedAlloced;
+ *ppbDecrypted = pbDecrypted;
+ pbDecrypted = NULL;
+ rc = VINF_CR_KEY_WAS_DECRYPTED;
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_KEY_DECRYPTION_FAILED,
+ "Incorrect password? EVP_DecryptFinal_ex failed for %s", pszDekInfo);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_KEY_DECRYPTION_FAILED,
+ "Incorrect password? EVP_DecryptUpdate failed for %s", pszDekInfo);
+ if (pbDecrypted)
+ RTMemSaferFree(pbDecrypted, cbDecryptedAlloced);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_KEY_OSSL_DECRYPT_INIT_ERROR, "EVP_DecryptInit_ex failed for %s", pszDekInfo);
+ EVP_CIPHER_CTX_free(pCipherCtx);
+ return rc;
+#else
+ RT_NOREF(pbEncrypted, cbEncrypted, pszPassword, pErrInfo, cchParams);
+ return VERR_CR_KEY_DECRYPTION_NOT_SUPPORTED;
+#endif
+}
+
+
+RTDECL(int) RTCrKeyCreateFromPemSection(PRTCRKEY phKey, PCRTCRPEMSECTION pSection, uint32_t fFlags, const char *pszPassword,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ AssertReturn(!(fFlags & (~RTCRKEYFROM_F_VALID_MASK | RTCRKEYFROM_F_ONLY_PEM)), VERR_INVALID_FLAGS);
+
+ AssertPtrReturn(phKey, VERR_INVALID_POINTER);
+ *phKey = NIL_RTCRKEY;
+ AssertPtrReturn(pSection, VERR_INVALID_POINTER);
+ NOREF(pszPassword);
+
+ /*
+ * If the source is PEM section, try identify the format from the markers.
+ */
+ enum
+ {
+ kKeyFormat_Unknown = 0,
+ kKeyFormat_RsaPrivateKey,
+ kKeyFormat_RsaEncryptedPrivateKey,
+ kKeyFormat_RsaPublicKey,
+ kKeyFormat_SubjectPublicKeyInfo,
+ kKeyFormat_PrivateKeyInfo,
+ kKeyFormat_EncryptedPrivateKeyInfo
+ } enmFormat = kKeyFormat_Unknown;
+ const char *pszDekInfo = NULL;
+ PCRTCRPEMMARKER pMarker = pSection->pMarker;
+ if (pMarker)
+ {
+ if ( pMarker->cWords == 3
+ && strcmp(pMarker->paWords[0].pszWord, "RSA") == 0
+ && strcmp(pMarker->paWords[2].pszWord, "KEY") == 0)
+ {
+ if (strcmp(pMarker->paWords[1].pszWord, "PUBLIC") == 0)
+ enmFormat = kKeyFormat_RsaPublicKey;
+ else if (strcmp(pMarker->paWords[1].pszWord, "PRIVATE") == 0)
+ {
+ enmFormat = kKeyFormat_RsaPrivateKey;
+
+ /* RSA PRIVATE KEY encryption is advertised thru PEM header fields.
+ We need the DEK field to decrypt the message (see RFC-1421 4.6.1.3). */
+ for (PCRTCRPEMFIELD pField = pSection->pFieldHead; pField; pField = pField->pNext)
+ {
+ if ( pField->cchName == sizeof("Proc-Type") - 1
+ && pField->cchValue >= sizeof("4,ENCRYPTED") - 1
+ && memcmp(pField->szName, RT_STR_TUPLE("Proc-Type")) == 0)
+ {
+ const char *pszValue = pField->pszValue;
+ if (*pszValue == '4')
+ {
+ do
+ pszValue++;
+ while (RT_C_IS_SPACE(*pszValue) || RT_C_IS_PUNCT(*pszValue));
+ if (strcmp(pszValue, "ENCRYPTED") == 0)
+ enmFormat = kKeyFormat_RsaEncryptedPrivateKey;
+ }
+ }
+ else if ( pField->cchName == sizeof("DEK-Info") - 1
+ && pField->cchValue > 0
+ && !pszDekInfo)
+ pszDekInfo = pField->pszValue;
+ }
+ }
+ else
+ AssertFailed();
+ }
+ else if ( pMarker->cWords == 2
+ && strcmp(pMarker->paWords[1].pszWord, "KEY") == 0)
+ {
+ if (strcmp(pMarker->paWords[0].pszWord, "PUBLIC") == 0)
+ enmFormat = kKeyFormat_SubjectPublicKeyInfo;
+ else if (strcmp(pMarker->paWords[0].pszWord, "PRIVATE") == 0)
+ enmFormat = kKeyFormat_PrivateKeyInfo;
+ else
+ AssertFailed();
+ }
+ else if ( pMarker->cWords == 3
+ && strcmp(pMarker->paWords[0].pszWord, "ENCRYPTED") == 0
+ && strcmp(pMarker->paWords[1].pszWord, "PRIVATE") == 0
+ && strcmp(pMarker->paWords[2].pszWord, "KEY") == 0)
+ enmFormat = kKeyFormat_EncryptedPrivateKeyInfo;
+ else
+ AssertFailed();
+ }
+
+ /*
+ * Try guess the format from the binary data if needed.
+ */
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ if ( enmFormat == kKeyFormat_Unknown
+ && pSection->cbData > 10)
+ {
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pSection->pbData, (uint32_t)pSection->cbData,
+ pErrInfo, &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "probing/0");
+
+ /*
+ * First the must be a sequence.
+ */
+ RTASN1CORE Tag;
+ int rc = RTAsn1CursorReadHdr(&PrimaryCursor.Cursor, &Tag, "#1");
+ if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_SEQUENCE)
+ {
+ RTASN1CURSOR Cursor2;
+ RTAsn1CursorInitSubFromCore(&PrimaryCursor.Cursor, &Tag, &Cursor2, "probing/1");
+ rc = RTAsn1CursorReadHdr(&Cursor2, &Tag, "#2");
+
+ /*
+ * SEQUENCE SubjectPublicKeyInfo.Algorithm?
+ */
+ if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_SEQUENCE)
+ {
+ RTASN1CURSOR Cursor3;
+ RTAsn1CursorInitSubFromCore(&Cursor2, &Tag, &Cursor3, "probing/2");
+ rc = RTAsn1CursorReadHdr(&Cursor3, &Tag, "#3");
+
+ /* SEQUENCE SubjectPublicKeyInfo.Algorithm.Algorithm? */
+ if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_OID)
+ enmFormat = kKeyFormat_SubjectPublicKeyInfo;
+ }
+ /*
+ * INTEGER PrivateKeyInfo.Version?
+ * INTEGER RsaPublicKey.Modulus?
+ * INTEGER RsaPrivateKey.Version?
+ */
+ else if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_INTEGER)
+ {
+ rc = RTAsn1CursorReadHdr(RTAsn1CursorSkip(&Cursor2, Tag.cb), &Tag, "#4");
+
+ /* OBJECT PrivateKeyInfo.privateKeyAlgorithm? */
+ if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_OID)
+ enmFormat = kKeyFormat_PrivateKeyInfo;
+ /* INTEGER RsaPublicKey.PublicExponent?
+ INTEGER RsaPrivateKey.Modulus? */
+ else if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_INTEGER)
+ {
+ /* RsaPublicKey.PublicExponent is at the end. */
+ if (RTAsn1CursorIsEnd(&Cursor2))
+ enmFormat = kKeyFormat_RsaPublicKey;
+ else
+ {
+ /* Check for INTEGER RsaPrivateKey.PublicExponent nad PrivateExponent before concluding. */
+ rc = RTAsn1CursorReadHdr(RTAsn1CursorSkip(&Cursor2, Tag.cb), &Tag, "#5");
+ if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_INTEGER)
+ {
+ rc = RTAsn1CursorReadHdr(RTAsn1CursorSkip(&Cursor2, Tag.cb), &Tag, "#6");
+ if (RT_SUCCESS(rc) && Tag.uTag == ASN1_TAG_INTEGER)
+ enmFormat = kKeyFormat_RsaPrivateKey;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (enmFormat == kKeyFormat_Unknown)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_KEY_UNKNOWN_TYPE,
+ "Unable to identify the key format (%.*Rhxs)", RT_MIN(16, pSection->cbData), pSection->pbData);
+
+ /*
+ * Do the reading.
+ */
+ int rc;
+ switch (enmFormat)
+ {
+ case kKeyFormat_RsaPublicKey:
+ rc = rtCrKeyCreateRsaPublic(phKey, pSection->pbData, (uint32_t)pSection->cbData, pErrInfo, pszErrorTag);
+ break;
+
+ case kKeyFormat_RsaPrivateKey:
+ rc = rtCrKeyCreateRsaPrivate(phKey, pSection->pbData, (uint32_t)pSection->cbData, pErrInfo, pszErrorTag);
+ break;
+
+ case kKeyFormat_RsaEncryptedPrivateKey:
+ {
+ uint8_t *pbDecrypted = NULL;
+ size_t cbDecrypted = 0;
+ size_t cbDecryptedAlloced = 0;
+ rc = rtCrKeyDecryptPemMessage(pszDekInfo, pszPassword, pSection->pbData, pSection->cbData,
+ &pbDecrypted, &cbDecrypted, &cbDecryptedAlloced, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ int rc2 = rtCrKeyCreateRsaPrivate(phKey, pbDecrypted, (uint32_t)cbDecrypted, pErrInfo, pszErrorTag);
+ if (rc2 != VINF_SUCCESS)
+ rc = rc2;
+ RTMemSaferFree(pbDecrypted, cbDecryptedAlloced);
+ }
+ break;
+ }
+
+ case kKeyFormat_SubjectPublicKeyInfo:
+ {
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pSection->pbData, (uint32_t)pSection->cbData,
+ pErrInfo, &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, pszErrorTag);
+ RTCRX509SUBJECTPUBLICKEYINFO SubjectPubKeyInfo;
+ RT_ZERO(SubjectPubKeyInfo);
+ rc = RTCrX509SubjectPublicKeyInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &SubjectPubKeyInfo, "SubjectPubKeyInfo");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrKeyCreateFromSubjectPublicKeyInfo(phKey, &SubjectPubKeyInfo, pErrInfo, pszErrorTag);
+ RTCrX509SubjectPublicKeyInfo_Delete(&SubjectPubKeyInfo);
+ }
+ break;
+ }
+
+ case kKeyFormat_PrivateKeyInfo:
+ rc = rtCrKeyCreateFromPrivateKeyInfo(phKey, &PrimaryCursor, pSection->pbData, pSection->cbData, pErrInfo, pszErrorTag);
+ break;
+
+ case kKeyFormat_EncryptedPrivateKeyInfo:
+ {
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pSection->pbData, (uint32_t)pSection->cbData,
+ pErrInfo, &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, pszErrorTag);
+ RTCRPKCS8ENCRYPTEDPRIVATEKEYINFO EncryptedPrivateKeyInfo;
+ RT_ZERO(EncryptedPrivateKeyInfo);
+ rc = RTCrPkcs8EncryptedPrivateKeyInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &EncryptedPrivateKeyInfo,
+ pszErrorTag ? pszErrorTag : "EncryptedPrivateKeyInfo");
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t *pbDecrypted = NULL;
+ size_t cbDecrypted = 0;
+ size_t cbDecryptedAlloced = 0;
+ rc = rtCrKeyDecryptPkcs8Info(&EncryptedPrivateKeyInfo, pszPassword,
+ &pbDecrypted, &cbDecrypted, &cbDecryptedAlloced, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtCrKeyCreateFromPrivateKeyInfo(phKey, &PrimaryCursor, pbDecrypted, cbDecrypted, pErrInfo, pszErrorTag);
+
+ RTMemSaferFree(pbDecrypted, cbDecryptedAlloced);
+ }
+ RTCrPkcs8EncryptedPrivateKeyInfo_Delete(&EncryptedPrivateKeyInfo);
+ }
+ break;
+ }
+
+ default:
+ AssertFailedStmt(rc = VERR_INTERNAL_ERROR_4);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTCrKeyCreateFromBuffer(PRTCRKEY phKey, uint32_t fFlags, void const *pvSrc, size_t cbSrc, const char *pszPassword,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ AssertReturn(!(fFlags & ~RTCRKEYFROM_F_VALID_MASK), VERR_INVALID_FLAGS);
+ PCRTCRPEMSECTION pSectionHead;
+ int rc = RTCrPemParseContent(pvSrc, cbSrc, fFlags, g_aRTCrKeyAllMarkers, g_cRTCrKeyAllMarkers, &pSectionHead, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (pSectionHead)
+ {
+ rc = RTCrKeyCreateFromPemSection(phKey, pSectionHead, fFlags & ~RTCRKEYFROM_F_ONLY_PEM, pszPassword,
+ pErrInfo, pszErrorTag);
+ RTCrPemFreeSections(pSectionHead);
+ }
+ else
+ rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTCrKeyCreateFromFile(PRTCRKEY phKey, uint32_t fFlags, const char *pszFilename,
+ const char *pszPassword, PRTERRINFO pErrInfo)
+{
+ AssertReturn(!(fFlags & ~RTCRKEYFROM_F_VALID_MASK), VERR_INVALID_FLAGS);
+ PCRTCRPEMSECTION pSectionHead;
+ int rc = RTCrPemReadFile(pszFilename, fFlags, g_aRTCrKeyAllMarkers, g_cRTCrKeyAllMarkers, &pSectionHead, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (pSectionHead)
+ {
+ rc = RTCrKeyCreateFromPemSection(phKey, pSectionHead, fFlags & ~RTCRKEYFROM_F_ONLY_PEM, pszPassword,
+ pErrInfo, RTPathFilename(pszFilename));
+ RTCrPemFreeSections(pSectionHead);
+ }
+ else
+ rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2;
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/key-internal.h b/src/VBox/Runtime/common/crypto/key-internal.h
new file mode 100644
index 00000000..88455fd7
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/key-internal.h
@@ -0,0 +1,132 @@
+/* $Id: key-internal.h $ */
+/** @file
+ * IPRT - Crypto - Cryptographic Keys, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_key_internal_h
+#define IPRT_INCLUDED_SRC_common_crypto_key_internal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/crypto/key.h>
+#include <iprt/bignum.h>
+
+
+/**
+ * Cryptographic key - core bits.
+ */
+typedef struct RTCRKEYINT
+{
+ /** Magic value (RTCRKEYINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The key type. */
+ RTCRKEYTYPE enmType;
+ /** Flags, RTCRKEYINT_F_XXX. */
+ uint32_t fFlags;
+ /** Number of bits in the key. */
+ uint32_t cBits;
+
+ /** Type specific data. */
+ union
+ {
+ /** RTCRKEYTYPE_RSA_PRIVATE. */
+ struct
+ {
+ /** The modulus. */
+ RTBIGNUM Modulus;
+ /** The private exponent. */
+ RTBIGNUM PrivateExponent;
+ /** The public exponent. */
+ RTBIGNUM PublicExponent;
+ /** @todo add more bits as needed. */
+ } RsaPrivate;
+
+ /** RTCRKEYTYPE_RSA_PUBLIC. */
+ struct
+ {
+ /** The modulus. */
+ RTBIGNUM Modulus;
+ /** The exponent. */
+ RTBIGNUM Exponent;
+ } RsaPublic;
+
+ /** RTCRKEYTYPE_ECDSA_PUBLIC. */
+ struct
+ {
+ /** The named curve. */
+ RTASN1OBJID NamedCurve;
+ /** @todo ECPoint. */
+ } EcdsaPublic;
+ } u;
+
+#if defined(IPRT_WITH_OPENSSL)
+ /** Size of raw key copy. */
+ uint32_t cbEncoded;
+ /** Raw copy of the key, for openssl and such.
+ * If sensitive, this is a safer allocation, otherwise it follows the structure. */
+ uint8_t *pbEncoded;
+#endif
+} RTCRKEYINT;
+/** Pointer to a crypographic key. */
+typedef RTCRKEYINT *PRTCRKEYINT;
+/** Pointer to a const crypographic key. */
+typedef RTCRKEYINT const *PCRTCRKEYINT;
+
+
+
+/** @name RTCRKEYINT_F_XXX.
+ * @{ */
+/** Key contains sensitive information, so no unnecessary copies. */
+#define RTCRKEYINT_F_SENSITIVE UINT32_C(0x00000001)
+/** Set if private key bits are present. */
+#define RTCRKEYINT_F_PRIVATE UINT32_C(0x00000002)
+/** Set if public key bits are present. */
+#define RTCRKEYINT_F_PUBLIC UINT32_C(0x00000004)
+/** Set if the cbEncoded/pbEncoded members are present. */
+#define RTCRKEYINT_F_INCLUDE_ENCODED UINT32_C(0x00000008)
+/** @} */
+
+DECLHIDDEN(int) rtCrKeyCreateWorker(PRTCRKEYINT *ppThis, RTCRKEYTYPE enmType, uint32_t fFlags,
+ void const *pvEncoded, uint32_t cbEncoded);
+DECLHIDDEN(int) rtCrKeyCreateRsaPublic(PRTCRKEY phKey, const void *pvKeyBits, uint32_t cbKeyBits,
+ PRTERRINFO pErrInfo, const char *pszErrorTag);
+DECLHIDDEN(int) rtCrKeyCreateRsaPrivate(PRTCRKEY phKey, const void *pvKeyBits, uint32_t cbKeyBits,
+ PRTERRINFO pErrInfo, const char *pszErrorTag);
+DECLHIDDEN(int) rtCrKeyCreateEcdsaPublic(PRTCRKEY phKey, PCRTASN1DYNTYPE pParameters,
+ const void *pvKeyBits, uint32_t cbKeyBits, PRTERRINFO pErrInfo, const char *pszErrorTag);
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_key_internal_h */
diff --git a/src/VBox/Runtime/common/crypto/key-openssl.cpp b/src/VBox/Runtime/common/crypto/key-openssl.cpp
new file mode 100644
index 00000000..c9f73335
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/key-openssl.cpp
@@ -0,0 +1,335 @@
+/* $Id: key-openssl.cpp $ */
+/** @file
+ * IPRT - Crypto - Cryptographic Keys, OpenSSL glue.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_CRYPTO
+#include "internal/iprt.h"
+#include <iprt/crypto/key.h>
+
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/crypto/digest.h>
+
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt-openssl.h"
+# include "internal/magics.h"
+# include "internal/openssl-pre.h"
+# include <openssl/evp.h>
+# include "internal/openssl-post.h"
+# ifndef OPENSSL_VERSION_NUMBER
+# error "Missing OPENSSL_VERSION_NUMBER!"
+# endif
+# if OPENSSL_VERSION_NUMBER < 0x30000000 || defined(LIBRESSL_VERSION_NUMBER)
+# include "openssl/x509.h"
+# include <iprt/crypto/x509.h>
+# endif
+
+# include "key-internal.h"
+
+
+/**
+ * Helper that loads key parameters and the actual key bits if present.
+ */
+static int rtCrKeyToOpenSslKeyLoad(RTCRKEY hKey, int idKeyType, EVP_PKEY **ppEvpNewKey, bool fNeedPublic, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+ if ( hKey->enmType == RTCRKEYTYPE_ECDSA_PUBLIC
+ || hKey->enmType == RTCRKEYTYPE_ECDSA_PRIVATE)
+ {
+# if OPENSSL_VERSION_NUMBER >= 0x30000000 && !defined(LIBRESSL_VERSION_NUMBER)
+ void *pvFree = NULL;
+ const uint8_t *pbRaw = NULL;
+ uint32_t cbRaw = 0;
+ if (hKey->enmType == RTCRKEYTYPE_ECDSA_PUBLIC)
+ rc = RTAsn1EncodeQueryRawBits(&hKey->u.EcdsaPublic.NamedCurve.Asn1Core, &pbRaw, &cbRaw, &pvFree, pErrInfo);
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+ if (RT_SUCCESS(rc))
+ {
+ const unsigned char *puchParams = pbRaw;
+ EVP_PKEY *pRet = d2i_KeyParams(idKeyType, ppEvpNewKey, &puchParams, cbRaw);
+ if (pRet != NULL && pRet == *ppEvpNewKey)
+ rc = VINF_SUCCESS;
+ else
+ rc = RTERRINFO_LOG_SET(pErrInfo, VERR_CR_PKIX_OSSL_D2I_KEY_PARAMS_FAILED, "d2i_KeyParams failed");
+
+ RTMemTmpFree(pvFree);
+ }
+#else
+ /*
+ * Cannot find any real suitable alternative to d2i_KeyParams in pre-3.0.x
+ * OpenSSL, so decided to use d2i_PUBKEY instead. This means we need to
+ * encode the stuff a X.509 SubjectPublicKeyInfo ASN.1 sequence first.
+ */
+ if (hKey->enmType == RTCRKEYTYPE_ECDSA_PUBLIC)
+ {
+ RTCRX509SUBJECTPUBLICKEYINFO PubKeyInfo;
+ rc = RTCrX509SubjectPublicKeyInfo_Init(&PubKeyInfo, &g_RTAsn1DefaultAllocator);
+ AssertRCReturn(rc, rc);
+
+ rc = RTAsn1ObjId_SetFromString(&PubKeyInfo.Algorithm.Algorithm, RTCRX509ALGORITHMIDENTIFIERID_ECDSA,
+ &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ rc = RTAsn1DynType_SetToObjId(&PubKeyInfo.Algorithm.Parameters, &hKey->u.EcdsaPublic.NamedCurve,
+ &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ RTAsn1BitString_Delete(&PubKeyInfo.SubjectPublicKey);
+ rc = RTAsn1BitString_InitWithData(&PubKeyInfo.SubjectPublicKey, hKey->pbEncoded, hKey->cbEncoded * 8,
+ &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Encode the whole shebang. */
+ void *pvFree = NULL;
+ const uint8_t *pbRaw = NULL;
+ uint32_t cbRaw = 0;
+ rc = RTAsn1EncodeQueryRawBits(&PubKeyInfo.SeqCore.Asn1Core, &pbRaw, &cbRaw, &pvFree, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+
+ const unsigned char *puchPubKey = pbRaw;
+ EVP_PKEY *pRet = d2i_PUBKEY(ppEvpNewKey, &puchPubKey, cbRaw);
+ if (pRet != NULL && pRet == *ppEvpNewKey)
+ rc = VINF_SUCCESS;
+ else
+ rc = RTERRINFO_LOG_SET(pErrInfo, VERR_CR_PKIX_OSSL_D2I_KEY_PARAMS_FAILED, "d2i_KeyParams failed");
+ RTMemTmpFree(pvFree);
+ }
+ }
+ }
+ AssertRC(rc);
+ RTCrX509SubjectPublicKeyInfo_Delete(&PubKeyInfo);
+ return rc;
+ }
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_OPENSSL_VERSION_TOO_OLD,
+ "OpenSSL version %#x is too old for IPRTs ECDSA code", OPENSSL_VERSION_NUMBER);
+ RT_NOREF(idKeyType, ppEvpNewKey);
+#endif
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Load the key into the structure.
+ */
+ const unsigned char *puchPublicKey = hKey->pbEncoded;
+ EVP_PKEY *pRet;
+ if (fNeedPublic)
+ pRet = d2i_PublicKey(idKeyType, ppEvpNewKey, &puchPublicKey, hKey->cbEncoded);
+ else
+ pRet = d2i_PrivateKey(idKeyType, ppEvpNewKey, &puchPublicKey, hKey->cbEncoded);
+ if (pRet != NULL && pRet == *ppEvpNewKey)
+ return VINF_SUCCESS;
+
+ /* Bail out: */
+ if (fNeedPublic)
+ rc = RTERRINFO_LOG_SET(pErrInfo, VERR_CR_PKIX_OSSL_D2I_PUBLIC_KEY_FAILED, "d2i_PublicKey failed");
+ else
+ rc = RTERRINFO_LOG_SET(pErrInfo, VERR_CR_PKIX_OSSL_D2I_PRIVATE_KEY_FAILED, "d2i_PrivateKey failed");
+ }
+ return rc;
+}
+
+
+/**
+ * Creates an OpenSSL key for the given IPRT one, returning the message digest
+ * algorithm if desired.
+ *
+ * @returns IRPT status code.
+ * @param hKey The key to convert to an OpenSSL key.
+ * @param fNeedPublic Set if we need the public side of the key.
+ * @param pszAlgoObjId Alogrithm stuff we currently need.
+ * @param ppEvpKey Where to return the pointer to the key structure.
+ * @param ppEvpMdType Where to optionally return the message digest type.
+ * @param pErrInfo Where to optionally return more error details.
+ */
+DECLHIDDEN(int) rtCrKeyToOpenSslKey(RTCRKEY hKey, bool fNeedPublic, void /*EVP_PKEY*/ **ppEvpKey, PRTERRINFO pErrInfo)
+{
+ *ppEvpKey = NULL;
+ AssertReturn(hKey->u32Magic == RTCRKEYINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(fNeedPublic == !(hKey->fFlags & RTCRKEYINT_F_PRIVATE), VERR_WRONG_TYPE);
+ AssertReturn(hKey->fFlags & RTCRKEYINT_F_INCLUDE_ENCODED, VERR_WRONG_TYPE); /* build misconfig */
+
+ rtCrOpenSslInit();
+
+ /*
+ * Translate the key type from IPRT to EVP speak.
+ */
+ int idKeyType;
+ switch (hKey->enmType)
+ {
+ case RTCRKEYTYPE_RSA_PRIVATE:
+ case RTCRKEYTYPE_RSA_PUBLIC:
+ idKeyType = EVP_PKEY_RSA;
+ break;
+
+ case RTCRKEYTYPE_ECDSA_PUBLIC:
+ case RTCRKEYTYPE_ECDSA_PRIVATE:
+ idKeyType = EVP_PKEY_EC;
+ break;
+
+ default:
+ return RTErrInfoSetF(pErrInfo, VERR_NOT_SUPPORTED, "Unsupported key type: %d", hKey->enmType);
+ }
+
+ /*
+ * Allocate a new key structure and set its type.
+ */
+ EVP_PKEY *pEvpNewKey = EVP_PKEY_new();
+ if (!pEvpNewKey)
+ return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "EVP_PKEY_new/%d failed", idKeyType);
+
+ /*
+ * Load key parameters and the key into the EVP structure.
+ */
+ int rc = rtCrKeyToOpenSslKeyLoad(hKey, idKeyType, &pEvpNewKey, fNeedPublic, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *ppEvpKey = pEvpNewKey;
+ return rc;
+ }
+ EVP_PKEY_free(pEvpNewKey);
+ return rc;
+}
+
+
+/**
+ * Creates an OpenSSL key for the given IPRT one, returning the message digest
+ * algorithm if desired.
+ *
+ * @returns IRPT status code.
+ * @param hKey The key to convert to an OpenSSL key.
+ * @param fNeedPublic Set if we need the public side of the key.
+ * @param pszAlgoObjId Alogrithm stuff we currently need.
+ * @param ppEvpKey Where to return the pointer to the key structure.
+ * @param ppEvpMdType Where to optionally return the message digest type.
+ * @param pErrInfo Where to optionally return more error details.
+ */
+DECLHIDDEN(int) rtCrKeyToOpenSslKeyEx(RTCRKEY hKey, bool fNeedPublic, const char *pszAlgoObjId,
+ void /*EVP_PKEY*/ **ppEvpKey, const void /*EVP_MD*/ **ppEvpMdType, PRTERRINFO pErrInfo)
+{
+ *ppEvpKey = NULL;
+ if (ppEvpMdType)
+ *ppEvpMdType = NULL;
+ AssertReturn(hKey->u32Magic == RTCRKEYINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(fNeedPublic == !(hKey->fFlags & RTCRKEYINT_F_PRIVATE), VERR_WRONG_TYPE);
+ AssertReturn(hKey->fFlags & RTCRKEYINT_F_INCLUDE_ENCODED, VERR_WRONG_TYPE); /* build misconfig */
+
+ rtCrOpenSslInit();
+
+ /*
+ * Translate algorithm object ID into stuff that OpenSSL wants.
+ */
+ int iAlgoNid = OBJ_txt2nid(pszAlgoObjId);
+ if (iAlgoNid == NID_undef)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN,
+ "Unknown public key algorithm [OpenSSL]: %s", pszAlgoObjId);
+ const char *pszAlgoSn = OBJ_nid2sn(iAlgoNid);
+
+# if OPENSSL_VERSION_NUMBER >= 0x10001000 && !defined(LIBRESSL_VERSION_NUMBER)
+ int idAlgoPkey = 0;
+ int idAlgoMd = 0;
+ if (!OBJ_find_sigid_algs(iAlgoNid, &idAlgoMd, &idAlgoPkey))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP,
+ "OBJ_find_sigid_algs failed on %u (%s, %s)", iAlgoNid, pszAlgoSn, pszAlgoObjId);
+ if (ppEvpMdType)
+ {
+ const EVP_MD *pEvpMdType = EVP_get_digestbynid(idAlgoMd);
+ if (!pEvpMdType)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP,
+ "EVP_get_digestbynid failed on %d (%s, %s)", idAlgoMd, pszAlgoSn, pszAlgoObjId);
+ *ppEvpMdType = pEvpMdType;
+ }
+# else
+ const EVP_MD *pEvpMdType = EVP_get_digestbyname(pszAlgoSn);
+ if (!pEvpMdType)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP,
+ "EVP_get_digestbyname failed on %s (%s)", pszAlgoSn, pszAlgoObjId);
+ if (ppEvpMdType)
+ *ppEvpMdType = pEvpMdType;
+# endif
+
+ /*
+ * Allocate a new key structure and set its type.
+ */
+ EVP_PKEY *pEvpNewKey = EVP_PKEY_new();
+ if (!pEvpNewKey)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_NO_MEMORY, "EVP_PKEY_new(%d) failed", iAlgoNid);
+
+ int rc;
+# if OPENSSL_VERSION_NUMBER >= 0x10001000 && !defined(LIBRESSL_VERSION_NUMBER)
+ if (EVP_PKEY_set_type(pEvpNewKey, idAlgoPkey))
+ {
+ int idKeyType = EVP_PKEY_base_id(pEvpNewKey);
+# else
+ int idKeyType = pEvpNewKey->type = EVP_PKEY_type(pEvpMdType->required_pkey_type[0]);
+# endif
+ if (idKeyType != NID_undef)
+
+ {
+ /*
+ * Load key parameters and the key into the EVP structure.
+ */
+ rc = rtCrKeyToOpenSslKeyLoad(hKey, idKeyType, &pEvpNewKey, fNeedPublic, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *ppEvpKey = pEvpNewKey;
+ return rc;
+ }
+ }
+ else
+# if OPENSSL_VERSION_NUMBER < 0x10001000 || defined(LIBRESSL_VERSION_NUMBER)
+ rc = RTERRINFO_LOG_SET(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, "EVP_PKEY_type() failed");
+# else
+ rc = RTERRINFO_LOG_SET(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, "EVP_PKEY_base_id() failed");
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR,
+ "EVP_PKEY_set_type(%u) failed (sig algo %s)", idAlgoPkey, pszAlgoSn);
+# endif
+
+ EVP_PKEY_free(pEvpNewKey);
+ *ppEvpKey = NULL;
+ return rc;
+}
+
+#endif /* IPRT_WITH_OPENSSL */
+
diff --git a/src/VBox/Runtime/common/crypto/key.cpp b/src/VBox/Runtime/common/crypto/key.cpp
new file mode 100644
index 00000000..e3c68e75
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/key.cpp
@@ -0,0 +1,557 @@
+/* $Id: key.cpp $ */
+/** @file
+ * IPRT - Crypto - Cryptographic Keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_CRYPTO
+#include "internal/iprt.h"
+#include <iprt/crypto/key.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/memsafer.h>
+#include <iprt/string.h>
+#include <iprt/crypto/rsa.h>
+#include <iprt/crypto/pkix.h>
+
+#include "internal/magics.h"
+#include "key-internal.h"
+
+
+/**
+ * Internal crypto key instance creator.
+ *
+ * This does most of the common work, caller does the 'u' and cBits jobs.
+ *
+ * @returns IPRT status code.
+ * @param ppThis Where to return the key instance.
+ * @param enmType The key type.
+ * @param fFlags The key flags.
+ * @param pvEncoded The encoded key bits.
+ * @param cbEncoded The size of the encoded key bits (in bytes).
+ */
+DECLHIDDEN(int) rtCrKeyCreateWorker(PRTCRKEYINT *ppThis, RTCRKEYTYPE enmType, uint32_t fFlags,
+ void const *pvEncoded, uint32_t cbEncoded)
+{
+ PRTCRKEYINT pThis = (PRTCRKEYINT)RTMemAllocZ(sizeof(*pThis) + (fFlags & RTCRKEYINT_F_SENSITIVE ? 0 : cbEncoded));
+ if (pThis)
+ {
+ pThis->enmType = enmType;
+ pThis->fFlags = fFlags;
+#if defined(IPRT_WITH_OPENSSL)
+ pThis->fFlags |= RTCRKEYINT_F_INCLUDE_ENCODED;
+ pThis->cbEncoded = cbEncoded;
+ if (!(fFlags & RTCRKEYINT_F_SENSITIVE))
+ pThis->pbEncoded = (uint8_t *)(pThis + 1);
+ else
+ {
+ pThis->pbEncoded = (uint8_t *)RTMemSaferAllocZ(cbEncoded);
+ if (!pThis->pbEncoded)
+ {
+ RTMemFree(pThis);
+ return VERR_NO_MEMORY;
+ }
+ }
+ memcpy(pThis->pbEncoded, pvEncoded, cbEncoded);
+#else
+ RT_NOREF(pvEncoded, cbEncoded);
+#endif
+ pThis->cRefs = 1;
+ pThis->u32Magic = RTCRKEYINT_MAGIC;
+ *ppThis = pThis;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Creates an RSA public key from a DER encoded RTCRRSAPUBLICKEY blob.
+ *
+ * @returns IPRT status code.
+ * @param phKey Where to return the key handle.
+ * @param pvKeyBits The DER encoded RTCRRSAPUBLICKEY blob.
+ * @param cbKeyBits The size of the blob.
+ * @param pErrInfo Where to supply addition error details. Optional.
+ * @param pszErrorTag Error tag. Optional.
+ */
+DECLHIDDEN(int) rtCrKeyCreateRsaPublic(PRTCRKEY phKey, const void *pvKeyBits, uint32_t cbKeyBits,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ /*
+ * Decode the key data first since that's what's most likely to fail here.
+ */
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pvKeyBits, cbKeyBits, pErrInfo, &g_RTAsn1DefaultAllocator,
+ RTASN1CURSOR_FLAGS_DER, pszErrorTag ? pszErrorTag : "rsa");
+ RTCRRSAPUBLICKEY PublicKey;
+ RT_ZERO(PublicKey);
+ int rc = RTCrRsaPublicKey_DecodeAsn1(&PrimaryCursor.Cursor, 0, &PublicKey, pszErrorTag ? pszErrorTag : "PublicKey");
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a key instance for it.
+ */
+ PRTCRKEYINT pThis;
+ rc = rtCrKeyCreateWorker(&pThis, RTCRKEYTYPE_RSA_PUBLIC, RTCRKEYINT_F_PUBLIC, pvKeyBits, cbKeyBits);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1Integer_ToBigNum(&PublicKey.Modulus, &pThis->u.RsaPublic.Modulus, 0);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cBits = RTBigNumBitWidth(&pThis->u.RsaPublic.Modulus);
+ rc = RTAsn1Integer_ToBigNum(&PublicKey.PublicExponent, &pThis->u.RsaPublic.Exponent, 0);
+ if (RT_SUCCESS(rc))
+ {
+
+ /* Done. */
+ RTAsn1VtDelete(&PublicKey.SeqCore.Asn1Core);
+ *phKey = pThis;
+ return VINF_SUCCESS;
+ }
+ }
+ RTCrKeyRelease(pThis);
+ }
+ RTAsn1VtDelete(&PublicKey.SeqCore.Asn1Core);
+ }
+ *phKey = NIL_RTCRKEY;
+ return rc;
+}
+
+
+/**
+ * Creates an EC (ECDSA) public key from a DER encoded RTCRX50-PUBLICKEY blob.
+ *
+ * @returns IPRT status code.
+ * @param phKey Where to return the key handle.
+ * @param pParameters The algorithm parameters, typically namedCurve OID.
+ * @param pvKeyBits The DER encoded RTCRRSAPUBLICKEY blob.
+ * @param cbKeyBits The size of the blob.
+ * @param pErrInfo Where to supply addition error details. Optional.
+ * @param pszErrorTag Error tag. Optional.
+ */
+DECLHIDDEN(int) rtCrKeyCreateEcdsaPublic(PRTCRKEY phKey, PCRTASN1DYNTYPE pParameters, const void *pvKeyBits, uint32_t cbKeyBits,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+#if 0
+ /*
+ * Decode the key data first since that's what's most likely to fail here.
+ */
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pvKeyBits, cbKeyBits, pErrInfo, &g_RTAsn1DefaultAllocator,
+ RTASN1CURSOR_FLAGS_DER, pszErrorTag ? pszErrorTag : "rsa");
+ RTCRRSAPUBLICKEY PublicKey;
+ RT_ZERO(PublicKey);
+ int rc = RTCrRsaPublicKey_DecodeAsn1(&PrimaryCursor.Cursor, 0, &PublicKey, pszErrorTag ? pszErrorTag : "PublicKey");
+#else
+ RT_NOREF(pszErrorTag);
+ int rc = VINF_SUCCESS;
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check the parameter (see RTC-5480, section 2.1.1).
+ */
+ if ( pParameters
+ && pParameters->enmType == RTASN1TYPE_OBJID
+ && RTAsn1ObjId_IsPresent(&pParameters->u.ObjId) /* paranoia */)
+ {
+ /*
+ * Create a key instance for it.
+ */
+ PRTCRKEYINT pThis;
+ rc = rtCrKeyCreateWorker(&pThis, RTCRKEYTYPE_ECDSA_PUBLIC, RTCRKEYINT_F_PUBLIC, pvKeyBits, cbKeyBits);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1ObjId_Clone(&pThis->u.EcdsaPublic.NamedCurve, &pParameters->u.ObjId, &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ /* Done. */
+#if 0
+ RTAsn1VtDelete(&PublicKey.SeqCore.Asn1Core);
+#endif
+ *phKey = pThis;
+ return VINF_SUCCESS;
+ }
+ RTCrKeyRelease(pThis);
+ }
+ }
+ else if (!pParameters || pParameters->enmType == RTASN1TYPE_NOT_PRESENT)
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_KEY_ALGO_PARAMS_MISSING,
+ "%s: ECDSA public key expected a namedCurve parameter", pszErrorTag);
+ else if (pParameters->enmType == RTASN1TYPE_NULL)
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_KEY_ALGO_PARAMS_UNKNOWN,
+ "%s: ECDSA public key expected a namedCurve parameter, found implicitCurve (NULL) instead",
+ pszErrorTag);
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_KEY_ALGO_PARAMS_UNKNOWN,
+ "%s: ECDSA public key expected namedCurve parameter, found %d",
+ pszErrorTag, pParameters->enmType);
+#if 0
+ RTAsn1VtDelete(&PublicKey.SeqCore.Asn1Core);
+#endif
+ }
+ *phKey = NIL_RTCRKEY;
+ return rc;
+}
+
+
+RTDECL(int) RTCrKeyCreateFromPublicAlgorithmAndBits(PRTCRKEY phKey, PCRTASN1OBJID pAlgorithm, PCRTASN1DYNTYPE pParameters,
+ PCRTASN1BITSTRING pPublicKey, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(phKey, VERR_INVALID_POINTER);
+ *phKey = NIL_RTCRKEY;
+
+ AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(pPublicKey, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1BitString_IsPresent(pPublicKey), VERR_INVALID_PARAMETER);
+
+ /*
+ * Identify the key type from the algorithm ID
+ */
+ const char * const pszEncryptionOid = RTCrX509AlgorithmIdentifier_GetEncryptionOidFromOid(pAlgorithm->szObjId,
+ false /*fMustIncludeHash*/);
+ if (pszEncryptionOid)
+ {
+ if (strcmp(pszEncryptionOid, RTCRX509ALGORITHMIDENTIFIERID_RSA) == 0)
+ return rtCrKeyCreateRsaPublic(phKey,
+ RTASN1BITSTRING_GET_BIT0_PTR(pPublicKey),
+ RTASN1BITSTRING_GET_BYTE_SIZE(pPublicKey),
+ pErrInfo, pszErrorTag);
+ if (strcmp(pszEncryptionOid, RTCRX509ALGORITHMIDENTIFIERID_ECDSA) == 0)
+ return rtCrKeyCreateEcdsaPublic(phKey,
+ pParameters,
+ RTASN1BITSTRING_GET_BIT0_PTR(pPublicKey),
+ RTASN1BITSTRING_GET_BYTE_SIZE(pPublicKey),
+ pErrInfo, pszErrorTag);
+ }
+ Assert(pszEncryptionOid == NULL);
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN, "oid=%s", pAlgorithm->szObjId);
+}
+
+
+RTDECL(int) RTCrKeyCreateFromSubjectPublicKeyInfo(PRTCRKEY phKey, struct RTCRX509SUBJECTPUBLICKEYINFO const *pSrc,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ AssertPtrReturn(pSrc, VERR_INVALID_POINTER);
+ AssertReturn(RTCrX509SubjectPublicKeyInfo_IsPresent(pSrc), VERR_INVALID_PARAMETER);
+ return RTCrKeyCreateFromPublicAlgorithmAndBits(phKey, &pSrc->Algorithm.Algorithm, &pSrc->Algorithm.Parameters,
+ &pSrc->SubjectPublicKey, pErrInfo, pszErrorTag);
+}
+
+
+/**
+ * Creates an RSA private key from a DER encoded RTCRRSAPRIVATEKEY blob.
+ *
+ * @returns IPRT status code.
+ * @param phKey Where to return the key handle.
+ * @param pvKeyBits The DER encoded RTCRRSAPRIVATEKEY blob.
+ * @param cbKeyBits The size of the blob.
+ * @param pErrInfo Where to supply addition error details. Optional.
+ * @param pszErrorTag Error tag. Optional.
+ */
+DECLHIDDEN(int) rtCrKeyCreateRsaPrivate(PRTCRKEY phKey, const void *pvKeyBits, uint32_t cbKeyBits,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ /*
+ * Decode the key data first since that's what's most likely to fail here.
+ */
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pvKeyBits, cbKeyBits, pErrInfo, &g_RTAsn1SaferAllocator,
+ RTASN1CURSOR_FLAGS_DER, pszErrorTag ? pszErrorTag : "rsa");
+ RTCRRSAPRIVATEKEY PrivateKey;
+ RT_ZERO(PrivateKey);
+ int rc = RTCrRsaPrivateKey_DecodeAsn1(&PrimaryCursor.Cursor, 0, &PrivateKey, pszErrorTag ? pszErrorTag : "PrivateKey");
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a key instance for it.
+ */
+ PRTCRKEYINT pThis;
+ rc = rtCrKeyCreateWorker(&pThis, RTCRKEYTYPE_RSA_PRIVATE, RTCRKEYINT_F_PRIVATE | RTCRKEYINT_F_SENSITIVE,
+ pvKeyBits, cbKeyBits);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1Integer_ToBigNum(&PrivateKey.Modulus, &pThis->u.RsaPrivate.Modulus, 0);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cBits = RTBigNumBitWidth(&pThis->u.RsaPrivate.Modulus);
+ rc = RTAsn1Integer_ToBigNum(&PrivateKey.PrivateExponent, &pThis->u.RsaPrivate.PrivateExponent, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTAsn1Integer_ToBigNum(&PrivateKey.PublicExponent, &pThis->u.RsaPrivate.PublicExponent, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /* Done. */
+ RTAsn1VtDelete(&PrivateKey.SeqCore.Asn1Core);
+ RTMemWipeThoroughly(&PrivateKey, sizeof(PrivateKey), 3);
+ *phKey = pThis;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ RTCrKeyRelease(pThis);
+ }
+ RTAsn1VtDelete(&PrivateKey.SeqCore.Asn1Core);
+ RTMemWipeThoroughly(&PrivateKey, sizeof(PrivateKey), 3);
+ }
+ *phKey = NIL_RTCRKEY;
+ return rc;
+}
+
+
+RTDECL(uint32_t) RTCrKeyRetain(RTCRKEY hKey)
+{
+ PRTCRKEYINT pThis = hKey;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < 1024, ("%#x\n", cRefs));
+ return cRefs;
+}
+
+
+/**
+ * Destructor.
+ *
+ * @returns 0
+ * @param pThis The key to destroy.
+ */
+static int rtCrKeyDestroy(PRTCRKEYINT pThis)
+{
+ /* Invalidate the object. */
+ pThis->u32Magic = ~RTCRKEYINT_MAGIC;
+
+ /* Type specific cleanup. */
+ switch (pThis->enmType)
+ {
+ case RTCRKEYTYPE_RSA_PUBLIC:
+ RTBigNumDestroy(&pThis->u.RsaPublic.Modulus);
+ RTBigNumDestroy(&pThis->u.RsaPublic.Exponent);
+ break;
+
+ case RTCRKEYTYPE_RSA_PRIVATE:
+ RTBigNumDestroy(&pThis->u.RsaPrivate.Modulus);
+ RTBigNumDestroy(&pThis->u.RsaPrivate.PrivateExponent);
+ RTBigNumDestroy(&pThis->u.RsaPrivate.PublicExponent);
+ break;
+
+ case RTCRKEYTYPE_ECDSA_PUBLIC:
+ RTAsn1ObjId_Delete(&pThis->u.EcdsaPublic.NamedCurve);
+ break;
+
+ case RTCRKEYTYPE_ECDSA_PRIVATE: /* not yet implemented */
+ case RTCRKEYTYPE_INVALID:
+ case RTCRKEYTYPE_END:
+ case RTCRKEYTYPE_32BIT_HACK:
+ AssertFailed();
+ }
+ pThis->enmType = RTCRKEYTYPE_INVALID;
+
+#if defined(IPRT_WITH_OPENSSL)
+ /* Free the encoded form if sensitive (otherwise it follows pThis). */
+ if (pThis->pbEncoded)
+ {
+ if (pThis->fFlags & RTCRKEYINT_F_SENSITIVE)
+ RTMemSaferFree((uint8_t *)pThis->pbEncoded, pThis->cbEncoded);
+ else
+ Assert(pThis->pbEncoded == (uint8_t *)(pThis + 1));
+ pThis->pbEncoded = NULL;
+ }
+#endif
+
+ /* Finally, free the key object itself. */
+ RTMemFree(pThis);
+ return 0;
+}
+
+
+RTDECL(uint32_t) RTCrKeyRelease(RTCRKEY hKey)
+{
+ if (hKey == NIL_RTCRKEY)
+ return 0;
+ PRTCRKEYINT pThis = hKey;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < 1024, ("%#x\n", cRefs));
+ if (cRefs != 0)
+ return cRefs;
+ return rtCrKeyDestroy(pThis);
+}
+
+
+RTDECL(RTCRKEYTYPE) RTCrKeyGetType(RTCRKEY hKey)
+{
+ PRTCRKEYINT pThis = hKey;
+ AssertPtrReturn(pThis, RTCRKEYTYPE_INVALID);
+ AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, RTCRKEYTYPE_INVALID);
+ return pThis->enmType;
+}
+
+
+RTDECL(bool) RTCrKeyHasPrivatePart(RTCRKEY hKey)
+{
+ PRTCRKEYINT pThis = hKey;
+ AssertPtrReturn(pThis, false);
+ AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, false);
+ return RT_BOOL(pThis->fFlags & RTCRKEYINT_F_PRIVATE);
+}
+
+
+RTDECL(bool) RTCrKeyHasPublicPart(RTCRKEY hKey)
+{
+ PRTCRKEYINT pThis = hKey;
+ AssertPtrReturn(pThis, false);
+ AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, false);
+ return RT_BOOL(pThis->fFlags & RTCRKEYINT_F_PUBLIC);
+}
+
+
+RTDECL(uint32_t) RTCrKeyGetBitCount(RTCRKEY hKey)
+{
+ PRTCRKEYINT pThis = hKey;
+ AssertPtrReturn(pThis, 0);
+ AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, 0);
+ return pThis->cBits;
+}
+
+
+RTDECL(int) RTCrKeyQueryRsaModulus(RTCRKEY hKey, PRTBIGNUM pModulus)
+{
+ PRTCRKEYINT pThis = hKey;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->enmType == RTCRKEYTYPE_RSA_PRIVATE || pThis->enmType == RTCRKEYTYPE_RSA_PUBLIC, VERR_WRONG_TYPE);
+ AssertPtrReturn(pModulus, VERR_INVALID_POINTER);
+
+ if (pThis->enmType == RTCRKEYTYPE_RSA_PRIVATE)
+ return RTBigNumAssign(pModulus, &pThis->u.RsaPrivate.Modulus);
+ return RTBigNumAssign(pModulus, &pThis->u.RsaPublic.Modulus);
+}
+
+
+RTDECL(int) RTCrKeyQueryRsaPrivateExponent(RTCRKEY hKey, PRTBIGNUM pPrivateExponent)
+{
+ PRTCRKEYINT pThis = hKey;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->enmType == RTCRKEYTYPE_RSA_PRIVATE, VERR_WRONG_TYPE);
+ AssertPtrReturn(pPrivateExponent, VERR_INVALID_POINTER);
+
+ return RTBigNumAssign(pPrivateExponent, &pThis->u.RsaPrivate.PrivateExponent);
+}
+
+
+RTDECL(int) RTCrKeyVerifyParameterCompatibility(RTCRKEY hKey, PCRTASN1DYNTYPE pParameters, bool fForSignature,
+ PCRTASN1OBJID pAlgorithm, PRTERRINFO pErrInfo)
+{
+ PRTCRKEYINT pThis = hKey;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRKEYINT_MAGIC, VERR_INVALID_HANDLE);
+ RT_NOREF(pAlgorithm);
+
+ switch (pThis->enmType)
+ {
+ /*
+ * No parameters. Ignore NULL.
+ */
+ case RTCRKEYTYPE_RSA_PRIVATE:
+ case RTCRKEYTYPE_RSA_PUBLIC:
+ if ( !pParameters
+ || pParameters->enmType == RTASN1TYPE_NOT_PRESENT
+ || pParameters->enmType == RTASN1TYPE_NULL)
+ return VINF_SUCCESS;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_KEY_ALGO_PARAMS_UNEXPECTED,
+ "RSA keys does not generally take parameters (enmType=%d)", pParameters->enmType);
+
+ /*
+ * ECDSA requires a parameter. Currently only the named curve choice is supported.
+ * RFC-3279 section 2.2.3 states that for ecdsa-with-SHA1 the parameter MUST be
+ * omitted. ASSUMING the same applies to the other ecdsa-with-xxxx variants.
+ */
+ case RTCRKEYTYPE_ECDSA_PUBLIC:
+ if (!fForSignature)
+ {
+ /* Key rules: Parameters required. */
+ if (pParameters)
+ {
+ if (pParameters->enmType == RTASN1TYPE_OBJID)
+ {
+ if (RTAsn1ObjId_Compare(&pParameters->u.ObjId, &pThis->u.EcdsaPublic.NamedCurve) == 0)
+ return VINF_SUCCESS;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_KEY_ALGO_PARAMS_MISMATCH,
+ "ECDSA NamedCurve difference: %s, key uses %s",
+ pParameters->u.ObjId.szObjId, pThis->u.EcdsaPublic.NamedCurve.szObjId);
+ }
+ /* We don't implement the other variants. */
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_KEY_ALGO_PARAMS_UNKNOWN,
+ "Unexpected ECDSA parameter: enmType=%d", pParameters->enmType);
+ }
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_CR_KEY_ALGO_PARAMS_MISSING, "ECDSA keys requires parameter(s)");
+ }
+
+ /* Hash+ecdsa parameter rules: No parameters */
+ if ( !pParameters
+ || pParameters->enmType == RTASN1TYPE_NOT_PRESENT
+ || pParameters->enmType == RTASN1TYPE_NULL)
+ return VINF_SUCCESS;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_KEY_ALGO_PARAMS_UNEXPECTED,
+ "ECDSA signature should have no parameters (enmType=%d)", pParameters->enmType);
+
+ case RTCRKEYTYPE_ECDSA_PRIVATE:
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+
+ case RTCRKEYTYPE_INVALID:
+ case RTCRKEYTYPE_END:
+ case RTCRKEYTYPE_32BIT_HACK:
+ break;
+ }
+ AssertFailedReturn(VERR_INTERNAL_ERROR_5);
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pemfile-read.cpp b/src/VBox/Runtime/common/crypto/pemfile-read.cpp
new file mode 100644
index 00000000..646fff84
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pemfile-read.cpp
@@ -0,0 +1,663 @@
+/* $Id: pemfile-read.cpp $ */
+/** @file
+ * IPRT - Crypto - PEM file reader.
+ *
+ * See RFC-1341 for the original ideas for the format, but keep in mind
+ * that the format was hijacked and put to different uses. We're aiming at
+ * dealing with the different uses rather than anything email related here.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pem.h>
+
+#include <iprt/asm.h>
+#include <iprt/base64.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/memsafer.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+
+
+
+/**
+ * Looks for a PEM-like marker.
+ *
+ * @returns true if found, false if not.
+ * @param pbContent Start of the content to search thru.
+ * @param cbContent The size of the content to search.
+ * @param offStart The offset into pbContent to start searching.
+ * @param pszLeadWord The lead word (BEGIN/END).
+ * @param cchLeadWord The length of the lead word.
+ * @param paMarkers Pointer to an array of markers.
+ * @param cMarkers Number of markers in the array.
+ * @param ppMatch Where to return the pointer to the matching
+ * marker. Optional.
+ * @param poffBegin Where to return the start offset of the marker.
+ * Optional.
+ * @param poffEnd Where to return the end offset of the marker
+ * (trailing whitespace and newlines will be
+ * skipped). Optional.
+ */
+static bool rtCrPemFindMarker(uint8_t const *pbContent, size_t cbContent, size_t offStart,
+ const char *pszLeadWord, size_t cchLeadWord, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
+ PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd)
+{
+ /* Remember the start of the content for the purpose of calculating offsets. */
+ uint8_t const * const pbStart = pbContent;
+
+ /* Skip adhead by offStart */
+ if (offStart >= cbContent)
+ return false;
+ pbContent += offStart;
+ cbContent -= offStart;
+
+ /*
+ * Search the content.
+ */
+ while (cbContent > 6)
+ {
+ /*
+ * Look for dashes.
+ */
+ uint8_t const *pbStartSearch = pbContent;
+ pbContent = (uint8_t const *)memchr(pbContent, '-', cbContent);
+ if (!pbContent)
+ break;
+
+ cbContent -= pbContent - pbStartSearch;
+ if (cbContent < 6)
+ break;
+
+ /*
+ * There must be at least three to interest us.
+ */
+ if ( pbContent[1] == '-'
+ && pbContent[2] == '-')
+ {
+ unsigned cDashes = 3;
+ while (cDashes < cbContent && pbContent[cDashes] == '-')
+ cDashes++;
+
+ if (poffBegin)
+ *poffBegin = pbContent - pbStart;
+ cbContent -= cDashes;
+ pbContent += cDashes;
+
+ /*
+ * Match lead word.
+ */
+ if ( cbContent > cchLeadWord
+ && memcmp(pbContent, pszLeadWord, cchLeadWord) == 0
+ && RT_C_IS_BLANK(pbContent[cchLeadWord]) )
+ {
+ pbContent += cchLeadWord;
+ cbContent -= cchLeadWord;
+ while (cbContent > 0 && RT_C_IS_BLANK(*pbContent))
+ {
+ pbContent++;
+ cbContent--;
+ }
+
+ /*
+ * Match one of the specified markers.
+ */
+ uint8_t const *pbSavedContent = pbContent;
+ size_t const cbSavedContent = cbContent;
+ for (uint32_t iMarker = 0; iMarker < cMarkers; iMarker++)
+ {
+ pbContent = pbSavedContent;
+ cbContent = cbSavedContent;
+
+ uint32_t cWords = paMarkers[iMarker].cWords;
+ PCRTCRPEMMARKERWORD pWord = paMarkers[iMarker].paWords;
+ while (cWords > 0)
+ {
+ uint32_t const cchWord = pWord->cchWord;
+ if (cbContent <= cchWord)
+ break;
+ if (memcmp(pbContent, pWord->pszWord, cchWord))
+ break;
+ pbContent += cchWord;
+ cbContent -= cchWord;
+
+ if (!cbContent)
+ break;
+ if (RT_C_IS_BLANK(*pbContent))
+ do
+ {
+ pbContent++;
+ cbContent--;
+ } while (cbContent > 0 && RT_C_IS_BLANK(*pbContent));
+ else if (cWords > 1 || pbContent[0] != '-')
+ break;
+
+ cWords--;
+ if (cWords == 0)
+ {
+ /*
+ * If there are three or more dashes following now, we've got a hit.
+ */
+ if ( cbContent > 3
+ && pbContent[0] == '-'
+ && pbContent[1] == '-'
+ && pbContent[2] == '-')
+ {
+ cDashes = 3;
+ while (cDashes < cbContent && pbContent[cDashes] == '-')
+ cDashes++;
+ cbContent -= cDashes;
+ pbContent += cDashes;
+
+ /*
+ * Skip spaces and newline.
+ */
+ while (cbContent > 0 && RT_C_IS_SPACE(*pbContent))
+ pbContent++, cbContent--;
+ if (poffEnd)
+ *poffEnd = pbContent - pbStart;
+ if (ppMatch)
+ *ppMatch = &paMarkers[iMarker];
+ return true;
+ }
+ break;
+ }
+ pWord++;
+ } /* for each word in marker. */
+ } /* for each marker. */
+ }
+ }
+ else
+ {
+ pbContent++;
+ cbContent--;
+ }
+ }
+
+ return false;
+}
+
+
+static bool rtCrPemFindMarkerSection(uint8_t const *pbContent, size_t cbContent, size_t offStart,
+ PCRTCRPEMMARKER paMarkers, size_t cMarkers,
+ PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd, size_t *poffResume)
+{
+ /** @todo Detect BEGIN / END mismatch. */
+ PCRTCRPEMMARKER pMatch;
+ if (rtCrPemFindMarker(pbContent, cbContent, offStart, "BEGIN", 5, paMarkers, cMarkers,
+ &pMatch, NULL /*poffStart*/, poffBegin))
+ {
+ if (rtCrPemFindMarker(pbContent, cbContent, *poffBegin, "END", 3, pMatch, 1,
+ NULL /*ppMatch*/, poffEnd, poffResume))
+ {
+ *ppMatch = pMatch;
+ return true;
+ }
+ }
+ *ppMatch = NULL;
+ return false;
+}
+
+
+/**
+ * Parses any fields the message may contain.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_NO_MEMORY
+ * @retval VERR_CR_MALFORMED_PEM_HEADER
+ *
+ * @param pSection The current section, where we will attach a list of
+ * fields to the pFieldHead member.
+ * @param pbContent The content of the PEM message being parsed.
+ * @param cbContent The length of the PEM message.
+ * @param pcbFields Where to return the length of the header fields we found.
+ */
+static int rtCrPemProcessFields(PRTCRPEMSECTION pSection, uint8_t const *pbContent, size_t cbContent, size_t *pcbFields)
+{
+ uint8_t const * const pbContentStart = pbContent;
+
+ /*
+ * Work the encapulated header protion field by field.
+ *
+ * This is optional, so currently we don't throw errors here but leave that
+ * to when we work the text portion with the base64 decoder. Also, as a reader
+ * we don't go all pedanic on confirming to specification (RFC-1421), especially
+ * given that it's used for crypto certificates, keys and the like not email. :-)
+ */
+ PCRTCRPEMFIELD *ppNext = &pSection->pFieldHead;
+ while (cbContent > 0)
+ {
+ /* Just look for a colon first. */
+ const uint8_t *pbColon = (const uint8_t *)memchr(pbContent, ':', cbContent);
+ if (!pbColon)
+ break;
+ size_t offColon = pbColon - pbContent;
+
+ /* Check that the colon is within the first line. */
+ if (!memchr(pbContent, '\n', cbContent - offColon))
+ return VERR_CR_MALFORMED_PEM_HEADER;
+
+ /* Skip leading spaces (there shouldn't be any, but just in case). */
+ while (RT_C_IS_BLANK(*pbContent) && /*paranoia:*/ offColon > 0)
+ {
+ offColon--;
+ cbContent--;
+ pbContent++;
+ }
+
+ /* There shouldn't be any spaces before the colon, but just in case */
+ size_t cchName = offColon;
+ while (cchName > 0 && RT_C_IS_BLANK(pbContent[cchName - 1]))
+ cchName--;
+
+ /* Skip leading value spaces (there typically is at least one). */
+ size_t offValue = offColon + 1;
+ while (offValue < cbContent && RT_C_IS_BLANK(pbContent[offValue]))
+ offValue++;
+
+ /* Find the newline the field value ends with and where the next iteration should start later on. */
+ size_t cbLeft;
+ uint8_t const *pbNext = (uint8_t const *)memchr(&pbContent[offValue], '\n', cbContent - offValue);
+ while ( pbNext
+ && (cbLeft = pbNext - pbContent) < cbContent
+ && RT_C_IS_BLANK(pbNext[1]) /* next line must start with a space or tab */)
+ pbNext = (uint8_t const *)memchr(&pbNext[1], '\n', cbLeft - 1);
+
+ size_t cchValue;
+ if (pbNext)
+ {
+ cchValue = pbNext - &pbContent[offValue];
+ if (cchValue > 0 && pbNext[-1] == '\r')
+ cchValue--;
+ pbNext++;
+ }
+ else
+ {
+ cchValue = cbContent - offValue;
+ pbNext = &pbContent[cbContent];
+ }
+
+ /* Strip trailing spaces. */
+ while (cchValue > 0 && RT_C_IS_BLANK(pbContent[offValue + cchValue - 1]))
+ cchValue--;
+
+ /*
+ * Allocate a field instance.
+ *
+ * Note! We don't consider field data sensitive at the moment. This
+ * mainly because the fields are chiefly used to indicate the
+ * encryption parameters to the body.
+ */
+ PRTCRPEMFIELD pNewField = (PRTCRPEMFIELD)RTMemAllocZVar(sizeof(*pNewField) + cchName + 1 + cchValue + 1);
+ if (!pNewField)
+ return VERR_NO_MEMORY;
+ pNewField->cchName = cchName;
+ pNewField->cchValue = cchValue;
+ memcpy(pNewField->szName, pbContent, cchName);
+ pNewField->szName[cchName] = '\0';
+ char *pszDst = (char *)memcpy(&pNewField->szName[cchName + 1], &pbContent[offValue], cchValue);
+ pNewField->pszValue = pszDst;
+ pszDst[cchValue] = '\0';
+ pNewField->pNext = NULL;
+
+ *ppNext = pNewField;
+ ppNext = &pNewField->pNext;
+
+ /*
+ * Advance past the field.
+ */
+ cbContent -= pbNext - pbContent;
+ pbContent = pbNext;
+ }
+
+ /*
+ * Skip blank line(s) before the body.
+ */
+ while (cbContent >= 1)
+ {
+ size_t cbSkip;
+ if (pbContent[0] == '\n')
+ cbSkip = 1;
+ else if ( pbContent[0] == '\r'
+ && cbContent >= 2
+ && pbContent[1] == '\n')
+ cbSkip = 2;
+ else
+ break;
+ pbContent += cbSkip;
+ cbContent -= cbSkip;
+ }
+
+ *pcbFields = pbContent - pbContentStart;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Does the decoding of a PEM-like data blob after it has been located.
+ *
+ * @returns IPRT status ocde
+ * @param pbContent The start of the PEM-like content (text).
+ * @param cbContent The max size of the PEM-like content.
+ * @param fSensitive Set if the safer allocator should be used.
+ * @param ppvDecoded Where to return a heap block containing the
+ * decoded content.
+ * @param pcbDecoded Where to return the size of the decoded content.
+ */
+static int rtCrPemDecodeBase64(uint8_t const *pbContent, size_t cbContent, bool fSensitive,
+ void **ppvDecoded, size_t *pcbDecoded)
+{
+ ssize_t cbDecoded = RTBase64DecodedSizeEx((const char *)pbContent, cbContent, NULL);
+ if (cbDecoded < 0)
+ return VERR_INVALID_BASE64_ENCODING;
+
+ *pcbDecoded = cbDecoded;
+ void *pvDecoded = !fSensitive ? RTMemAlloc(cbDecoded) : RTMemSaferAllocZ(cbDecoded);
+ if (!pvDecoded)
+ return VERR_NO_MEMORY;
+
+ size_t cbActual;
+ int rc = RTBase64DecodeEx((const char *)pbContent, cbContent, pvDecoded, cbDecoded, &cbActual, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbActual == (size_t)cbDecoded)
+ {
+ *ppvDecoded = pvDecoded;
+ return VINF_SUCCESS;
+ }
+
+ rc = VERR_INTERNAL_ERROR_3;
+ }
+ if (!fSensitive)
+ RTMemFree(pvDecoded);
+ else
+ RTMemSaferFree(pvDecoded, cbDecoded);
+ return rc;
+}
+
+
+/**
+ * Checks if the content of a file looks to be binary or not.
+ *
+ * @returns true if likely to be binary, false if not binary.
+ * @param pbFile The file bytes to scan.
+ * @param cbFile The number of bytes.
+ * @param fFlags RTCRPEMREADFILE_F_XXX
+ */
+static bool rtCrPemIsBinaryBlob(uint8_t const *pbFile, size_t cbFile, uint32_t fFlags)
+{
+ if (fFlags & RTCRPEMREADFILE_F_ONLY_PEM)
+ return false;
+
+ /*
+ * Well formed PEM files should probably only contain 7-bit ASCII and
+ * restrict thenselfs to the following control characters:
+ * tab, newline, return, form feed
+ *
+ * However, if we want to read PEM files which contains human readable
+ * certificate details before or after each base-64 section, we can't stick
+ * to 7-bit ASCII. We could say it must be UTF-8, but that's probably to
+ * limited as well. So, we'll settle for detecting binary files by control
+ * characters alone (safe enough for DER encoded stuff, I think).
+ */
+ while (cbFile-- > 0)
+ {
+ uint8_t const b = *pbFile++;
+ if (b < 32 && b != '\t' && b != '\n' && b != '\r' && b != '\f')
+ {
+ /* Ignore EOT (4), SUB (26) and NUL (0) at the end of the file. */
+ if ( (b == 4 || b == 26)
+ && ( cbFile == 0
+ || ( cbFile == 1
+ && *pbFile == '\0')))
+ return false;
+
+ if (b == 0 && cbFile == 0)
+ return false;
+
+ return true;
+ }
+ }
+ return false;
+}
+
+
+RTDECL(int) RTCrPemFreeSections(PCRTCRPEMSECTION pSectionHead)
+{
+ while (pSectionHead != NULL)
+ {
+ PRTCRPEMSECTION pFree = (PRTCRPEMSECTION)pSectionHead;
+ pSectionHead = pSectionHead->pNext;
+ ASMCompilerBarrier(); /* paranoia */
+
+ if (pFree->pbData)
+ {
+ if (!pFree->fSensitive)
+ RTMemFree(pFree->pbData);
+ else
+ RTMemSaferFree(pFree->pbData, pFree->cbData);
+ pFree->pbData = NULL;
+ pFree->cbData = 0;
+ }
+
+ PRTCRPEMFIELD pField = (PRTCRPEMFIELD)pFree->pFieldHead;
+ if (pField)
+ {
+ pFree->pFieldHead = NULL;
+ do
+ {
+ PRTCRPEMFIELD pFreeField = pField;
+ pField = (PRTCRPEMFIELD)pField->pNext;
+ ASMCompilerBarrier(); /* paranoia */
+
+ pFreeField->pszValue = NULL;
+ RTMemFree(pFreeField);
+ } while (pField);
+ }
+
+ RTMemFree(pFree);
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrPemParseContent(void const *pvContent, size_t cbContent, uint32_t fFlags,
+ PCRTCRPEMMARKER paMarkers, size_t cMarkers,
+ PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
+{
+ RT_NOREF_PV(pErrInfo);
+
+ /*
+ * Input validation.
+ */
+ AssertPtr(ppSectionHead);
+ *ppSectionHead = NULL;
+ AssertReturn(cbContent, VINF_EOF);
+ AssertPtr(pvContent);
+ AssertPtr(paMarkers);
+ AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ /*
+ * Pre-allocate a section.
+ */
+ int rc = VINF_SUCCESS;
+ PRTCRPEMSECTION pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
+ if (pSection)
+ {
+ bool const fSensitive = RT_BOOL(fFlags & RTCRPEMREADFILE_F_SENSITIVE);
+
+ /*
+ * Try locate the first section.
+ */
+ uint8_t const *pbContent = (uint8_t const *)pvContent;
+ size_t offBegin, offEnd, offResume;
+ PCRTCRPEMMARKER pMatch;
+ if ( !rtCrPemIsBinaryBlob(pbContent, cbContent, fFlags)
+ && rtCrPemFindMarkerSection(pbContent, cbContent, 0 /*offStart*/, paMarkers, cMarkers,
+ &pMatch, &offBegin, &offEnd, &offResume) )
+ {
+ PCRTCRPEMSECTION *ppNext = ppSectionHead;
+ for (;;)
+ {
+ //pSection->pNext = NULL;
+ pSection->pMarker = pMatch;
+ //pSection->pbData = NULL;
+ //pSection->cbData = 0;
+ //pSection->pFieldHead = NULL;
+ pSection->fSensitive = fSensitive;
+
+ *ppNext = pSection;
+ ppNext = &pSection->pNext;
+
+ /*
+ * Decode the section.
+ */
+ size_t cbFields = 0;
+ int rc2 = rtCrPemProcessFields(pSection, pbContent + offBegin, offEnd - offBegin, &cbFields);
+ offBegin += cbFields;
+ if (RT_SUCCESS(rc2))
+ rc2 = rtCrPemDecodeBase64(pbContent + offBegin, offEnd - offBegin, fSensitive,
+ (void **)&pSection->pbData, &pSection->cbData);
+ if (RT_FAILURE(rc2))
+ {
+ pSection->pbData = NULL;
+ pSection->cbData = 0;
+ if ( rc2 == VERR_INVALID_BASE64_ENCODING
+ && (fFlags & RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR))
+ rc = -rc2;
+ else
+ {
+ rc = rc2;
+ break;
+ }
+ }
+
+ /*
+ * More sections?
+ */
+ if ( offResume + 12 >= cbContent
+ || offResume >= cbContent
+ || !rtCrPemFindMarkerSection(pbContent, cbContent, offResume, paMarkers, cMarkers,
+ &pMatch, &offBegin, &offEnd, &offResume) )
+ break; /* No. */
+
+ /* Ok, allocate a new record for it. */
+ pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
+ if (RT_UNLIKELY(!pSection))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ RTCrPemFreeSections(*ppSectionHead);
+ }
+ else
+ {
+ if (!(fFlags & RTCRPEMREADFILE_F_ONLY_PEM))
+ {
+ /*
+ * No PEM section found. Return the whole file as one binary section.
+ */
+ //pSection->pNext = NULL;
+ //pSection->pMarker = NULL;
+ //pSection->pFieldHead = NULL;
+ pSection->cbData = cbContent;
+ pSection->fSensitive = fSensitive;
+ if (!fSensitive)
+ pSection->pbData = (uint8_t *)RTMemDup(pbContent, cbContent);
+ else
+ {
+ pSection->pbData = (uint8_t *)RTMemSaferAllocZ(cbContent);
+ if (pSection->pbData)
+ memcpy(pSection->pbData, pbContent, cbContent);
+ }
+ if (pSection->pbData)
+ {
+ *ppSectionHead = pSection;
+ return VINF_SUCCESS;
+ }
+
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VWRN_NOT_FOUND;
+ RTMemFree(pSection);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ *ppSectionHead = NULL;
+ return rc;
+}
+
+
+RTDECL(int) RTCrPemReadFile(const char *pszFilename, uint32_t fFlags, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
+ PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
+{
+ *ppSectionHead = NULL;
+ AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ size_t cbContent;
+ void *pvContent;
+ int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrPemParseContent(pvContent, cbContent, fFlags, paMarkers, cMarkers, ppSectionHead, pErrInfo);
+ if (fFlags & RTCRPEMREADFILE_F_SENSITIVE)
+ RTMemWipeThoroughly(pvContent, cbContent, 3);
+ RTFileReadAllFree(pvContent, cbContent);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename);
+ return rc;
+}
+
+
+RTDECL(const char *) RTCrPemFindFirstSectionInContent(void const *pvContent, size_t cbContent,
+ PCRTCRPEMMARKER paMarkers, size_t cMarkers)
+{
+ size_t offBegin;
+ if (rtCrPemFindMarker((uint8_t *)pvContent, cbContent, 0, "BEGIN", 5, paMarkers, cMarkers, NULL, &offBegin, NULL))
+ return (const char *)pvContent + offBegin;
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pemfile-write.cpp b/src/VBox/Runtime/common/crypto/pemfile-write.cpp
new file mode 100644
index 00000000..c37b653e
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pemfile-write.cpp
@@ -0,0 +1,266 @@
+/* $Id: pemfile-write.cpp $ */
+/** @file
+ * IPRT - Crypto - PEM file writer.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pem.h>
+
+#include <iprt/asn1.h>
+#include <iprt/base64.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Used by rtCrPemWriteAsn1Callback to buffer data before outputting it as
+ * BASE64.
+ *
+ * An encoded line is 64 characters long plus a newline, covering 48 bytes
+ * of binary data. We want about 4KB of output:
+ * 4096 / 65 = 63.015384615384615384615384615385
+ * 64 * 65 + 1 = 4161 (0x1041)
+ */
+typedef struct PEMOUTPUTASN1
+{
+ size_t cbPending;
+ PFNRTSTROUTPUT pfnOutput;
+ void *pvUser;
+ size_t cchRet;
+ uint8_t abBlock[0x0c00];
+ char szBlock[0x1060];
+} PEMOUTPUTASN1;
+typedef PEMOUTPUTASN1 *PPEMOUTPUTASN1;
+
+
+
+RTDECL(size_t) RTCrPemWriteBlob(PFNRTSTROUTPUT pfnOutput, void *pvUser,
+ const void *pvContent, size_t cbContent, const char *pszMarker)
+{
+ /*
+ * -----BEGIN XXXXX-----
+ */
+ size_t cchRet = pfnOutput(pvUser, RT_STR_TUPLE("-----BEGIN "));
+ size_t const cchMarker = strlen(pszMarker);
+ cchRet += pfnOutput(pvUser, pszMarker, cchMarker);
+ cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n"));
+
+ /*
+ * base64 - in reasonably sized stack blocks.
+ * An encoded line is 64 characters long plus a newline, covering 48 bytes
+ * of binary data. We want about 4KB of output:
+ * 4096 / 65 = 63.015384615384615384615384615385
+ * 64 * 65 + 1 = 4161 (0x1041)
+ */
+ const size_t cbMaxBlock = 64 * 48;
+ while (cbContent > 0)
+ {
+ char szBlock[0x1060];
+ size_t cbBlock = RT_MIN(cbContent, cbMaxBlock);
+ size_t cchBlock = 0;
+ int rc = RTBase64EncodeEx(pvContent, cbBlock, RTBASE64_FLAGS_EOL_LF,
+ szBlock, sizeof(szBlock), &cchBlock);
+ AssertRC(rc);
+ szBlock[cchBlock++] = '\n';
+ szBlock[cchBlock] = '\0';
+
+ cchRet += pfnOutput(pvUser, szBlock, cchBlock);
+
+ pvContent = (uint8_t const *)pvContent + cbBlock;
+ cbContent -= cbBlock;
+ }
+
+ /*
+ * -----END XXXXX-----
+ */
+ cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----END "));
+ cchRet += pfnOutput(pvUser, pszMarker, cchMarker);
+ cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n"));
+
+ /* termination call */
+ cchRet += pfnOutput(pvUser, NULL, 0);
+
+ return cchRet;
+}
+
+
+RTDECL(ssize_t) RTCrPemWriteBlobToVfsIoStrm(RTVFSIOSTREAM hVfsIos, const void *pvContent, size_t cbContent, const char *pszMarker)
+{
+ VFSIOSTRMOUTBUF Buf;
+ VFSIOSTRMOUTBUF_INIT(&Buf, hVfsIos);
+ size_t cchRet = RTCrPemWriteBlob(RTVfsIoStrmStrOutputCallback, &Buf, pvContent, cbContent, pszMarker);
+ Assert(Buf.offBuf == 0);
+ return RT_SUCCESS(Buf.rc) ? (ssize_t)cchRet : Buf.rc;
+}
+
+
+RTDECL(ssize_t) RTCrPemWriteBlobToVfsFile(RTVFSFILE hVfsFile, const void *pvContent, size_t cbContent, const char *pszMarker)
+{
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE);
+ ssize_t cchRet = RTCrPemWriteBlobToVfsIoStrm(hVfsIos, pvContent, cbContent, pszMarker);
+ RTVfsIoStrmRelease(hVfsIos);
+ return cchRet;
+}
+
+
+/** @callback_method_impl{FNRTASN1ENCODEWRITER} */
+static DECLCALLBACK(int) rtCrPemWriteAsn1Callback(const void *pvBuf, size_t cbToWrite, void *pvUser, PRTERRINFO pErrInfo)
+{
+ PPEMOUTPUTASN1 pThis = (PPEMOUTPUTASN1)pvUser;
+ AssertCompile((sizeof(pThis->abBlock) % 48) == 0);
+
+ while (cbToWrite > 0)
+ {
+ size_t offDst = pThis->cbPending;
+ AssertStmt(offDst <= sizeof(pThis->abBlock), offDst = sizeof(pThis->abBlock));
+ size_t cbDst = sizeof(pThis->abBlock) - offDst;
+ if (cbToWrite < cbDst)
+ {
+ /* Buffer not full: Append and return. */
+ memcpy(&pThis->abBlock[offDst], pvBuf, cbToWrite);
+ pThis->cbPending = offDst + cbToWrite;
+ break;
+ }
+
+ /* Fill the buffer and flush it: */
+ memcpy(&pThis->abBlock[offDst], pvBuf, cbDst);
+ Assert(offDst + cbDst == sizeof(pThis->abBlock));
+
+ size_t cchBlock = 0;
+ int rc = RTBase64EncodeEx(pThis->abBlock, sizeof(pThis->abBlock), RTBASE64_FLAGS_EOL_LF,
+ pThis->szBlock, sizeof(pThis->szBlock), &cchBlock);
+ AssertRC(rc);
+ pThis->szBlock[cchBlock++] = '\n';
+ pThis->szBlock[cchBlock] = '\0';
+
+ pThis->cchRet += pThis->pfnOutput(pThis->pvUser, pThis->szBlock, cchBlock);
+ pThis->cbPending = 0;
+
+ /* Advance. */
+ pvBuf = (uint8_t const *)pvBuf + cbDst;
+ cbToWrite -= cbDst;
+ }
+
+ RT_NOREF(pErrInfo);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(ssize_t) RTCrPemWriteAsn1(PFNRTSTROUTPUT pfnOutput, void *pvUser, PRTASN1CORE pRoot,
+ uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo)
+{
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+
+ /*
+ * Prepare the ASN.1 data for DER encoding.
+ */
+ int rc = RTAsn1EncodePrepare(pRoot, RTASN1ENCODE_F_DER, NULL /*pcbEncoded*/, pErrInfo);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * -----BEGIN XXXXX-----
+ */
+ size_t cchRet = pfnOutput(pvUser, RT_STR_TUPLE("-----BEGIN "));
+ size_t const cchMarker = strlen(pszMarker);
+ cchRet += pfnOutput(pvUser, pszMarker, cchMarker);
+ cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n"));
+
+ /*
+ * BASE64
+ */
+ PEMOUTPUTASN1 This;
+ This.pfnOutput = pfnOutput;
+ This.pvUser = pvUser;
+ This.cchRet = 0;
+ This.cbPending = 0;
+ rc = RTAsn1EncodeWrite(pRoot, RTASN1ENCODE_F_DER, rtCrPemWriteAsn1Callback, &This, pErrInfo);
+ AssertRCReturn(rc, rc);
+ cchRet += This.cchRet;
+
+ Assert(This.cbPending <= sizeof(This.abBlock));
+ if (This.cbPending)
+ {
+ size_t cchBlock = 0;
+ rc = RTBase64EncodeEx(This.abBlock, This.cbPending, RTBASE64_FLAGS_EOL_LF,
+ This.szBlock, sizeof(This.szBlock), &cchBlock);
+ AssertRC(rc);
+ This.szBlock[cchBlock++] = '\n';
+ This.szBlock[cchBlock] = '\0';
+
+ cchRet += pfnOutput(pvUser, This.szBlock, cchBlock);
+ }
+
+ /*
+ * -----END XXXXX-----
+ */
+ cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----END "));
+ cchRet += pfnOutput(pvUser, pszMarker, cchMarker);
+ cchRet += pfnOutput(pvUser, RT_STR_TUPLE("-----\n"));
+
+ /* termination call */
+ cchRet += pfnOutput(pvUser, NULL, 0);
+
+ return cchRet;
+}
+
+
+RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsIoStrm(RTVFSIOSTREAM hVfsIos, PRTASN1CORE pRoot,
+ uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo)
+{
+ VFSIOSTRMOUTBUF Buf;
+ VFSIOSTRMOUTBUF_INIT(&Buf, hVfsIos);
+ ssize_t cchRet = RTCrPemWriteAsn1(RTVfsIoStrmStrOutputCallback, &Buf, pRoot, fFlags, pszMarker, pErrInfo);
+ Assert(Buf.offBuf == 0);
+ return RT_SUCCESS(Buf.rc) ? (ssize_t)cchRet : Buf.rc;
+}
+
+
+RTDECL(ssize_t) RTCrPemWriteAsn1ToVfsFile(RTVFSFILE hVfsFile, PRTASN1CORE pRoot,
+ uint32_t fFlags, const char *pszMarker, PRTERRINFO pErrInfo)
+{
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE);
+ ssize_t cchRet = RTCrPemWriteAsn1ToVfsIoStrm(hVfsIos, pRoot, fFlags, pszMarker, pErrInfo);
+ RTVfsIoStrmRelease(hVfsIos);
+ return cchRet;
+}
diff --git a/src/VBox/Runtime/common/crypto/pkcs7-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/pkcs7-asn1-decoder.cpp
new file mode 100644
index 00000000..93690fe8
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs7-asn1-decoder.cpp
@@ -0,0 +1,174 @@
+/* $Id: pkcs7-asn1-decoder.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#7, Decoder for ASN.1.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs7.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/crypto/spc.h>
+#include <iprt/crypto/tsp.h>
+
+#include "pkcs7-internal.h"
+
+
+/*
+ * PKCS #7 ContentInfo
+ */
+typedef enum RTCRPKCS7CONTENTINFOCHOICE
+{
+ RTCRPKCS7CONTENTINFOCHOICE_INVALID = 0,
+ RTCRPKCS7CONTENTINFOCHOICE_UNKNOWN,
+ RTCRPKCS7CONTENTINFOCHOICE_SIGNED_DATA,
+ RTCRPKCS7CONTENTINFOCHOICE_SPC_INDIRECT_DATA_CONTENT,
+ RTCRPKCS7CONTENTINFOCHOICE_TSP_TST_INFO,
+ RTCRPKCS7CONTENTINFOCHOICE_END,
+ RTCRPKCS7CONTENTINFOCHOICE_32BIT_HACK = 0x7fffffff
+} RTCRPKCS7CONTENTINFOCHOICE;
+
+static int rtCrPkcs7ContentInfo_DecodeExtra(PRTASN1CURSOR pCursor, uint32_t fFlags, PRTCRPKCS7CONTENTINFO pThis,
+ const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags); RT_NOREF_PV(pszErrorTag);
+ pThis->u.pCore = NULL;
+
+ /*
+ * Figure the type.
+ */
+ RTCRPKCS7CONTENTINFOCHOICE enmChoice;
+ size_t cbContent = 0;
+ if (RTAsn1ObjId_CompareWithString(&pThis->ContentType, RTCRPKCS7SIGNEDDATA_OID) == 0)
+ {
+ enmChoice = RTCRPKCS7CONTENTINFOCHOICE_SIGNED_DATA;
+ cbContent = sizeof(*pThis->u.pSignedData);
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ContentType, RTCRSPCINDIRECTDATACONTENT_OID) == 0)
+ {
+ enmChoice = RTCRPKCS7CONTENTINFOCHOICE_SPC_INDIRECT_DATA_CONTENT;
+ cbContent = sizeof(*pThis->u.pIndirectDataContent);
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ContentType, RTCRTSPTSTINFO_OID) == 0)
+ {
+ enmChoice = RTCRPKCS7CONTENTINFOCHOICE_TSP_TST_INFO;
+ cbContent = sizeof(*pThis->u.pTstInfo);
+ }
+ else
+ {
+ enmChoice = RTCRPKCS7CONTENTINFOCHOICE_UNKNOWN;
+ cbContent = 0;
+ }
+
+ int rc = VINF_SUCCESS;
+ if (enmChoice != RTCRPKCS7CONTENTINFOCHOICE_UNKNOWN)
+ {
+ /*
+ * Detect CMS octet string format and open the content cursor.
+ *
+ * Current we don't have any octent string content which, they're all
+ * sequences, which make detection so much simpler.
+ */
+ PRTASN1OCTETSTRING pOctetString = &pThis->Content;
+ RTASN1CURSOR ContentCursor;
+ rc = RTAsn1CursorInitSubFromCore(pCursor, &pThis->Content.Asn1Core, &ContentCursor, "Content");
+ if ( RT_SUCCESS(rc)
+ && RTAsn1CursorIsNextEx(&ContentCursor, ASN1_TAG_OCTET_STRING, ASN1_TAGFLAG_PRIMITIVE | ASN1_TAGCLASS_UNIVERSAL))
+ {
+ rc = RTAsn1MemAllocZ(&pThis->Content.EncapsulatedAllocation, (void **)&pThis->Content.pEncapsulated,
+ sizeof(*pOctetString));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pCmsContent = pOctetString = (PRTASN1OCTETSTRING)pThis->Content.pEncapsulated;
+ rc = RTAsn1OctetString_DecodeAsn1(&ContentCursor, 0, pOctetString, "CmsContent");
+ if (RT_SUCCESS(rc))
+ rc = RTAsn1CursorCheckEnd(&ContentCursor);
+ if (RT_SUCCESS(rc))
+ rc = RTAsn1CursorInitSubFromCore(pCursor, &pOctetString->Asn1Core, &ContentCursor, "CmsContent");
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate memory for the decoded content.
+ */
+ rc = RTAsn1MemAllocZ(&pOctetString->EncapsulatedAllocation, (void **)&pOctetString->pEncapsulated, cbContent);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->u.pCore = pOctetString->pEncapsulated;
+
+ /*
+ * Decode it.
+ */
+ switch (enmChoice)
+ {
+ case RTCRPKCS7CONTENTINFOCHOICE_SIGNED_DATA:
+ rc = RTCrPkcs7SignedData_DecodeAsn1(&ContentCursor, 0, pThis->u.pSignedData, "SignedData");
+ break;
+ case RTCRPKCS7CONTENTINFOCHOICE_SPC_INDIRECT_DATA_CONTENT:
+ rc = RTCrSpcIndirectDataContent_DecodeAsn1(&ContentCursor, 0, pThis->u.pIndirectDataContent,
+ "IndirectDataContent");
+ break;
+ case RTCRPKCS7CONTENTINFOCHOICE_TSP_TST_INFO:
+ rc = RTCrTspTstInfo_DecodeAsn1(&ContentCursor, 0, pThis->u.pTstInfo, "TstInfo");
+ break;
+ default:
+ AssertFailed();
+ rc = VERR_IPE_NOT_REACHED_DEFAULT_CASE;
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTAsn1CursorCheckOctStrEnd(&ContentCursor, &pThis->Content);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTAsn1MemFree(&pOctetString->EncapsulatedAllocation, pOctetString->pEncapsulated);
+ pOctetString->pEncapsulated = NULL;
+ pThis->u.pCore = NULL;
+ }
+ }
+ }
+ return rc;
+}
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs7-core.cpp b/src/VBox/Runtime/common/crypto/pkcs7-core.cpp
new file mode 100644
index 00000000..55f1e169
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs7-core.cpp
@@ -0,0 +1,250 @@
+/* $Id: pkcs7-core.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#7, Core APIs.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs7.h>
+
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/tsp.h>
+
+#include "pkcs7-internal.h"
+
+/*
+ * PKCS #7 Attributes
+ */
+
+RTDECL(int) RTCrPkcs7Attributes_HashAttributes(PRTCRPKCS7ATTRIBUTES pAttributes, RTCRDIGEST hDigest, PRTERRINFO pErrInfo)
+{
+ /* ASSUMES that the attributes are encoded according to DER. */
+ uint8_t const *pbData;
+ uint32_t cbData;
+ void *pvFree = NULL;
+ int rc = RTAsn1EncodeQueryRawBits(RTCrPkcs7Attributes_GetAsn1Core(pAttributes),
+ &pbData, &cbData, &pvFree, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t bSetOfTag = ASN1_TAG_SET | ASN1_TAGCLASS_UNIVERSAL | ASN1_TAGFLAG_CONSTRUCTED;
+ rc = RTCrDigestUpdate(hDigest, &bSetOfTag, sizeof(bSetOfTag)); /* Replace the implict tag with a SET-OF tag. */
+ if (RT_SUCCESS(rc))
+ rc = RTCrDigestUpdate(hDigest, pbData + sizeof(bSetOfTag), cbData - sizeof(bSetOfTag)); /* Skip the implicit tag. */
+ if (RT_SUCCESS(rc))
+ rc = RTCrDigestFinal(hDigest, NULL, 0);
+ else
+ RTErrInfoSet(pErrInfo, rc, "RTCrDigestUpdate failed");
+ RTMemTmpFree(pvFree);
+ }
+ return rc;
+}
+
+
+/*
+ * PKCS #7 SignerInfo
+ */
+
+RTDECL(PCRTASN1TIME) RTCrPkcs7SignerInfo_GetSigningTime(PCRTCRPKCS7SIGNERINFO pThis, PCRTCRPKCS7SIGNERINFO *ppSignerInfo)
+{
+ /*
+ * Check the immediate level, unless we're continuing a previous search.
+ * Note! We ASSUME a single signing time attribute, which simplifies the interface.
+ */
+ uint32_t cAttrsLeft;
+ PRTCRPKCS7ATTRIBUTE const *ppAttr;
+ if (!ppSignerInfo || *ppSignerInfo == NULL)
+ {
+ cAttrsLeft = pThis->AuthenticatedAttributes.cItems;
+ ppAttr = pThis->AuthenticatedAttributes.papItems;
+ while (cAttrsLeft-- > 0)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttr = *ppAttr;
+ if ( pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_SIGNING_TIME
+ && pAttr->uValues.pSigningTime->cItems > 0)
+ {
+ if (ppSignerInfo)
+ *ppSignerInfo = pThis;
+ return pAttr->uValues.pSigningTime->papItems[0];
+ }
+ ppAttr++;
+ }
+ }
+ else if (*ppSignerInfo == pThis)
+ *ppSignerInfo = NULL;
+
+ /*
+ * Check counter signatures.
+ */
+ cAttrsLeft = pThis->UnauthenticatedAttributes.cItems;
+ ppAttr = pThis->UnauthenticatedAttributes.papItems;
+ while (cAttrsLeft-- > 0)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttr = *ppAttr;
+ if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES)
+ {
+ uint32_t cSignatures = pAttr->uValues.pCounterSignatures->cItems;
+ PRTCRPKCS7SIGNERINFO *ppSignature = pAttr->uValues.pCounterSignatures->papItems;
+
+ /* Skip past the previous counter signature. */
+ if (ppSignerInfo && *ppSignerInfo != NULL)
+ while (cSignatures > 0)
+ {
+ cSignatures--;
+ if (*ppSignature == *ppSignerInfo)
+ {
+ *ppSignerInfo = NULL;
+ ppSignature++;
+ break;
+ }
+ ppSignature++;
+ }
+
+ /* Search the counter signatures (if any remaining). */
+ while (cSignatures-- > 0)
+ {
+ PCRTCRPKCS7SIGNERINFO pSignature = *ppSignature;
+ uint32_t cCounterAttrsLeft = pSignature->AuthenticatedAttributes.cItems;
+ PRTCRPKCS7ATTRIBUTE const *ppCounterAttr = pSignature->AuthenticatedAttributes.papItems;
+ while (cCounterAttrsLeft-- > 0)
+ {
+ PCRTCRPKCS7ATTRIBUTE pCounterAttr = *ppCounterAttr;
+ if ( pCounterAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_SIGNING_TIME
+ && pCounterAttr->uValues.pSigningTime->cItems > 0)
+ {
+ if (ppSignerInfo)
+ *ppSignerInfo = pSignature;
+ return pCounterAttr->uValues.pSigningTime->papItems[0];
+ }
+ ppCounterAttr++;
+ }
+ ppSignature++;
+ }
+ }
+ ppAttr++;
+ }
+
+ /*
+ * No signing timestamp found.
+ */
+ if (ppSignerInfo)
+ *ppSignerInfo = NULL;
+
+ return NULL;
+}
+
+
+RTDECL(PCRTASN1TIME) RTCrPkcs7SignerInfo_GetMsTimestamp(PCRTCRPKCS7SIGNERINFO pThis, PCRTCRPKCS7CONTENTINFO *ppContentInfoRet)
+{
+ /*
+ * Assume there is only one, so no need to enumerate anything here.
+ */
+ uint32_t cAttrsLeft = pThis->UnauthenticatedAttributes.cItems;
+ PRTCRPKCS7ATTRIBUTE const *ppAttr = pThis->UnauthenticatedAttributes.papItems;
+ while (cAttrsLeft-- > 0)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttr = *ppAttr;
+ if (pAttr->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_TIMESTAMP)
+ {
+ uint32_t cLeft = pAttr->uValues.pContentInfos->cItems;
+ PRTCRPKCS7CONTENTINFO const *ppContentInfo = pAttr->uValues.pContentInfos->papItems;
+ while (cLeft-- > 0)
+ {
+ PCRTCRPKCS7CONTENTINFO pContentInfo = *ppContentInfo;
+ if (RTAsn1ObjId_CompareWithString(&pContentInfo->ContentType, RTCRPKCS7SIGNEDDATA_OID) == 0)
+ {
+ if (RTAsn1ObjId_CompareWithString(&pContentInfo->u.pSignedData->ContentInfo.ContentType,
+ RTCRTSPTSTINFO_OID) == 0)
+ {
+ if (ppContentInfoRet)
+ *ppContentInfoRet = pContentInfo;
+ return &pContentInfo->u.pSignedData->ContentInfo.u.pTstInfo->GenTime;
+ }
+ }
+
+ pContentInfo++;
+ }
+ }
+ ppAttr++;
+ }
+
+ /*
+ * No signature was found.
+ */
+ if (ppContentInfoRet)
+ *ppContentInfoRet = NULL;
+
+ return NULL;
+}
+
+
+/*
+ * PKCS #7 ContentInfo.
+ */
+
+RTDECL(bool) RTCrPkcs7ContentInfo_IsSignedData(PCRTCRPKCS7CONTENTINFO pThis)
+{
+ return RTAsn1ObjId_CompareWithString(&pThis->ContentType, RTCRPKCS7SIGNEDDATA_OID) == 0;
+}
+
+
+/*
+ * Set of some kind of certificate supported by PKCS #7 or CMS.
+ */
+
+RTDECL(PCRTCRX509CERTIFICATE)
+RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(PCRTCRPKCS7SETOFCERTS pCertificates,
+ PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNumber)
+{
+ for (uint32_t i = 0; i < pCertificates->cItems; i++)
+ {
+ PCRTCRPKCS7CERT pCert = pCertificates->papItems[i];
+ if ( pCert->enmChoice == RTCRPKCS7CERTCHOICE_X509
+ && RTCrX509Certificate_MatchIssuerAndSerialNumber(pCert->u.pX509Cert, pIssuer, pSerialNumber))
+ return pCert->u.pX509Cert;
+ }
+ return NULL;
+}
+
+
+/*
+ * Generate the standard core code.
+ */
+#include <iprt/asn1-generator-core.h>
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs7-file.cpp b/src/VBox/Runtime/common/crypto/pkcs7-file.cpp
new file mode 100644
index 00000000..fbb25f01
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs7-file.cpp
@@ -0,0 +1,119 @@
+/* $Id: pkcs7-file.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS\#7/CMS, File related APIs.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs7.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/crypto/pem.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static RTCRPEMMARKERWORD const g_aWords_Cms[] = { { RT_STR_TUPLE("CMS") } };
+static RTCRPEMMARKERWORD const g_aWords_Pkcs7[] = { { RT_STR_TUPLE("PKCS7") } };
+/** X509 Certificate markers. */
+RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrPkcs7Markers[] =
+{
+ { g_aWords_Cms, RT_ELEMENTS(g_aWords_Cms) },
+ { g_aWords_Pkcs7, RT_ELEMENTS(g_aWords_Pkcs7) }
+};
+/** Number of entries in g_aRTCrPkcs7Markers. */
+RT_DECL_DATA_CONST(uint32_t const) g_cRTCrPkcs7Markers = RT_ELEMENTS(g_aRTCrPkcs7Markers);
+
+
+/** @name Flags for RTCrPkcs7ContentInfo_ReadFromBuffer
+ * @{ */
+/** Only allow PEM certificates, not binary ones.
+ * @sa RTCRPEMREADFILE_F_ONLY_PEM */
+#define RTCRPKCS7_READ_F_PEM_ONLY RT_BIT(1)
+/** @} */
+
+
+RTDECL(int) RTCrPkcs7_ReadFromBuffer(PRTCRPKCS7CONTENTINFO pContentInfo, const void *pvBuf, size_t cbBuf,
+ uint32_t fFlags, PCRTASN1ALLOCATORVTABLE pAllocator,
+ bool *pfCmsLabeled, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ if (pfCmsLabeled)
+ *pfCmsLabeled = false;
+ AssertReturn(!(fFlags & ~RTCRX509CERT_READ_F_PEM_ONLY), VERR_INVALID_FLAGS);
+
+ PCRTCRPEMSECTION pSectionHead;
+ int rc = RTCrPemParseContent(pvBuf, cbBuf,
+ fFlags & RTCRX509CERT_READ_F_PEM_ONLY ? RTCRPEMREADFILE_F_ONLY_PEM : 0,
+ g_aRTCrPkcs7Markers, g_cRTCrPkcs7Markers,
+ &pSectionHead, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (pSectionHead)
+ {
+ if (pfCmsLabeled)
+ *pfCmsLabeled = pSectionHead->pMarker == &g_aRTCrPkcs7Markers[0];
+
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pSectionHead->pbData, (uint32_t)RT_MIN(pSectionHead->cbData, UINT32_MAX),
+ pErrInfo, pAllocator, RTASN1CURSOR_FLAGS_DER, pszErrorTag);
+
+ RTCRPKCS7CONTENTINFO TmpContentInfo;
+ rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &TmpContentInfo, "CI");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrPkcs7ContentInfo_CheckSanity(&TmpContentInfo, 0, pErrInfo, "CI");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrPkcs7ContentInfo_Clone(pContentInfo, &TmpContentInfo, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ if (pSectionHead->pNext || PrimaryCursor.Cursor.cbLeft)
+ rc = VINF_ASN1_MORE_DATA;
+ }
+ }
+ RTCrPkcs7ContentInfo_Delete(&TmpContentInfo);
+ }
+ RTCrPemFreeSections(pSectionHead);
+ }
+ else
+ rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2;
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs7-init.cpp b/src/VBox/Runtime/common/crypto/pkcs7-init.cpp
new file mode 100644
index 00000000..f40ad868
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs7-init.cpp
@@ -0,0 +1,62 @@
+/* $Id: pkcs7-init.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#7, Initialization API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs7.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#include "pkcs7-internal.h"
+
+
+static int rtCrPkcs7ContentInfo_CloneExtra(PRTCRPKCS7CONTENTINFO pThis)
+{
+ pThis->u.pCore = pThis->Content.pEncapsulated;
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-init.h>
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs7-internal.h b/src/VBox/Runtime/common/crypto/pkcs7-internal.h
new file mode 100644
index 00000000..78fba4b7
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs7-internal.h
@@ -0,0 +1,47 @@
+/* $Id: pkcs7-internal.h $ */
+/** @file
+ * IPRT - Crypto - PKCS \#7, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_pkcs7_internal_h
+#define IPRT_INCLUDED_SRC_common_crypto_pkcs7_internal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/pkcs7-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_pkcs7_internal_h */
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs7-sanity.cpp b/src/VBox/Runtime/common/crypto/pkcs7-sanity.cpp
new file mode 100644
index 00000000..dad218e9
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs7-sanity.cpp
@@ -0,0 +1,221 @@
+/* $Id: pkcs7-sanity.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#7, Sanity Checkers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs7.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+//#include <iprt/stream.h>
+
+#include "pkcs7-internal.h"
+
+
+static int rtCrPkcs7SignedData_CheckSanityExtra(PCRTCRPKCS7SIGNEDDATA pSignedData, uint32_t fFlags,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ bool const fAuthenticode = RT_BOOL(fFlags & RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE);
+ RT_NOREF_PV(fFlags);
+
+ //RTAsn1Dump(&pSignedData->SeqCore.Asn1Core, 0, 0, RTAsn1DumpStrmPrintfV, g_pStdOut);
+
+ if ( RTAsn1Integer_UnsignedCompareWithU32(&pSignedData->Version, RTCRPKCS7SIGNEDDATA_V1) != 0
+ && RTAsn1Integer_UnsignedCompareWithU32(&pSignedData->Version, RTCRPKCS7SIGNEDDATA_V3) != 0
+ && RTAsn1Integer_UnsignedCompareWithU32(&pSignedData->Version, RTCRPKCS7SIGNEDDATA_V4) != 0
+ && RTAsn1Integer_UnsignedCompareWithU32(&pSignedData->Version, RTCRPKCS7SIGNEDDATA_V5) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNED_DATA_VERSION, "SignedData version is %llu, expected %u",
+ pSignedData->Version.uValue.u, RTCRPKCS7SIGNEDDATA_V1);
+
+ /*
+ * DigestAlgorithms.
+ */
+ if (pSignedData->DigestAlgorithms.cItems == 0) /** @todo this might be too strict */
+ return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_SIGNED_DATA_NO_DIGEST_ALGOS, "SignedData.DigestAlgorithms is empty");
+ if (pSignedData->DigestAlgorithms.cItems != 1 && fAuthenticode)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_NOT_EXACTLY_ONE_DIGEST_ALGO,
+ "%s: SignedData.DigestAlgorithms has more than one algorithm (%u)",
+ pszErrorTag, pSignedData->DigestAlgorithms.cItems);
+
+ if (fFlags & RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH)
+ for (uint32_t i = 0; i < pSignedData->DigestAlgorithms.cItems; i++)
+ {
+ if ( RTCrX509AlgorithmIdentifier_GetDigestType(pSignedData->DigestAlgorithms.papItems[i],
+ true /*fPureDigestsOnly*/)
+ == RTDIGESTTYPE_INVALID)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_UNKNOWN_DIGEST_ALGORITHM,
+ "%s: SignedData.DigestAlgorithms[%i] is not known: %s",
+ pszErrorTag, i, pSignedData->DigestAlgorithms.papItems[i]->Algorithm.szObjId);
+ if ( pSignedData->DigestAlgorithms.papItems[i]->Parameters.enmType != RTASN1TYPE_NULL
+ && pSignedData->DigestAlgorithms.papItems[i]->Parameters.enmType != RTASN1TYPE_NOT_PRESENT)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_PARAMS_NOT_IMPL,
+ "%s: SignedData.DigestAlgorithms[%i] has parameters: tag=%u",
+ pszErrorTag, i, pSignedData->DigestAlgorithms.papItems[i]->Parameters.u.Core.uTag);
+ }
+
+ /*
+ * Certificates.
+ */
+ if ( (fFlags & RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT)
+ && pSignedData->Certificates.cItems == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_NO_CERTIFICATES,
+ "%s: SignedData.Certifcates is empty, expected at least one certificate", pszErrorTag);
+
+ /*
+ * Crls.
+ */
+ if (fAuthenticode && RTAsn1Core_IsPresent(&pSignedData->Crls))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_EXPECTED_NO_CRLS,
+ "%s: SignedData.Crls is not empty as expected for authenticode.", pszErrorTag);
+ /** @todo check Crls when they become important. */
+
+ /*
+ * SignerInfos.
+ */
+ if (pSignedData->SignerInfos.cItems == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_NO_SIGNER_INFOS, "%s: SignedData.SignerInfos is empty?", pszErrorTag);
+ if (fAuthenticode && pSignedData->SignerInfos.cItems != 1)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_EXPECTED_ONE_SIGNER_INFO,
+ "%s: SignedData.SignerInfos should have one entry for authenticode: %u",
+ pszErrorTag, pSignedData->SignerInfos.cItems);
+
+ for (uint32_t i = 0; i < pSignedData->SignerInfos.cItems; i++)
+ {
+ PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[i];
+
+ if (RTAsn1Integer_UnsignedCompareWithU32(&pSignerInfo->Version, RTCRPKCS7SIGNERINFO_V1) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNER_INFO_VERSION,
+ "%s: SignedData.SignerInfos[%u] version is %llu, expected %u",
+ pszErrorTag, i, pSignerInfo->Version.uValue.u, RTCRPKCS7SIGNERINFO_V1);
+
+ /* IssuerAndSerialNumber. */
+ int rc = RTCrX509Name_CheckSanity(&pSignerInfo->IssuerAndSerialNumber.Name, 0, pErrInfo,
+ "SignedData.SignerInfos[#].IssuerAndSerialNumber.Name");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.cb == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNER_INFO_NO_ISSUER_SERIAL_NO,
+ "%s: SignedData.SignerInfos[%u].IssuerAndSerialNumber.SerialNumber is missing (zero length)",
+ pszErrorTag, i);
+
+ PCRTCRX509CERTIFICATE pCert;
+ pCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates,
+ &pSignerInfo->IssuerAndSerialNumber.Name,
+ &pSignerInfo->IssuerAndSerialNumber.SerialNumber);
+ if (!pCert && (fFlags & RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNER_CERT_NOT_SHIPPED,
+ "%s: SignedData.SignerInfos[%u].IssuerAndSerialNumber not found in T0.Certificates",
+ pszErrorTag, i);
+
+ /* DigestAlgorithm */
+ uint32_t j = 0;
+ while ( j < pSignedData->DigestAlgorithms.cItems
+ && RTCrX509AlgorithmIdentifier_Compare(pSignedData->DigestAlgorithms.papItems[j],
+ &pSignerInfo->DigestAlgorithm) != 0)
+ j++;
+ if (j >= pSignedData->DigestAlgorithms.cItems)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_ALGO_NOT_FOUND_IN_LIST,
+ "%s: SignedData.SignerInfos[%u].DigestAlgorithm (%s) not found in SignedData.DigestAlgorithms",
+ pszErrorTag, i, pSignerInfo->DigestAlgorithm.Algorithm.szObjId);
+
+ /* Digest encryption algorithm. */
+#if 0 /** @todo Unimportant: Seen timestamp signatures specifying pkcs1-Sha256WithRsaEncryption in SignerInfo and just RSA in the certificate. Figure out how to compare the two. */
+ if ( pCert
+ && RTCrX509AlgorithmIdentifier_Compare(&pSignerInfo->DigestEncryptionAlgorithm,
+ &pCert->TbsCertificate.SubjectPublicKeyInfo.Algorithm) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNER_INFO_DIGEST_ENCRYPT_MISMATCH,
+ "SignedData.SignerInfos[%u].DigestEncryptionAlgorithm (%s) mismatch with certificate (%s)",
+ i, pSignerInfo->DigestEncryptionAlgorithm.Algorithm.szObjId,
+ pCert->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm.szObjId);
+#endif
+
+ /* Authenticated attributes we know. */
+ if (RTCrPkcs7Attributes_IsPresent(&pSignerInfo->AuthenticatedAttributes))
+ {
+ bool fFoundContentInfo = false;
+ bool fFoundMessageDigest = false;
+ for (j = 0; j < pSignerInfo->AuthenticatedAttributes.cItems; j++)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->AuthenticatedAttributes.papItems[j];
+ if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_CONTENT_TYPE_OID) == 0)
+ {
+ if (fFoundContentInfo)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB,
+ "%s: Multiple authenticated content-type attributes.", pszErrorTag);
+ fFoundContentInfo = true;
+ AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS, VERR_INTERNAL_ERROR_3);
+ if (pAttrib->uValues.pObjIds->cItems != 1)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_BAD_CONTENT_TYPE_ATTRIB,
+ "%s: Expected exactly one value for content-type attrib, found: %u",
+ pszErrorTag, pAttrib->uValues.pObjIds->cItems);
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_MESSAGE_DIGEST_OID) == 0)
+ {
+ if (fFoundMessageDigest)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MISSING_MESSAGE_DIGEST_ATTRIB,
+ "%s: Multiple authenticated message-digest attributes.", pszErrorTag);
+ fFoundMessageDigest = true;
+ AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS, VERR_INTERNAL_ERROR_3);
+ if (pAttrib->uValues.pOctetStrings->cItems != 1)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_BAD_CONTENT_TYPE_ATTRIB,
+ "%s: Expected exactly one value for message-digest attrib, found: %u",
+ pszErrorTag, pAttrib->uValues.pOctetStrings->cItems);
+ }
+ }
+
+ if (!fFoundContentInfo)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB,
+ "%s: Missing authenticated content-type attribute.", pszErrorTag);
+ if (!fFoundMessageDigest)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MISSING_MESSAGE_DIGEST_ATTRIB,
+ "%s: Missing authenticated message-digest attribute.", pszErrorTag);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs7-sign.cpp b/src/VBox/Runtime/common/crypto/pkcs7-sign.cpp
new file mode 100644
index 00000000..c1600d60
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs7-sign.cpp
@@ -0,0 +1,592 @@
+/* $Id: pkcs7-sign.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#7, Signing
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs7.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/key.h>
+#include <iprt/crypto/pkix.h>
+#include <iprt/crypto/store.h>
+#include <iprt/crypto/x509.h>
+
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/asn1t.h>
+# include <openssl/pkcs7.h>
+# include <openssl/cms.h>
+# include <openssl/x509.h>
+# include <openssl/err.h>
+# include "internal/openssl-post.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * PKCS\#7 / CMS signing operation instance.
+ */
+typedef struct RTCRPKCS7SIGNINGJOBINT
+{
+ /** Magic value (RTCRPKCS7SIGNINGJOBINT). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** RTCRPKCS7SIGN_F_XXX. */
+ uint64_t fFlags;
+ /** Set if finalized. */
+ bool fFinallized;
+
+ //....
+} RTCRPKCS7SIGNINGJOBINT;
+
+/** Magic value for RTCRPKCS7SIGNINGJOBINT (Jonathan Lethem). */
+#define RTCRPKCS7SIGNINGJOBINT_MAGIC UINT32_C(0x19640219)
+
+/** Handle to PKCS\#7/CMS signing operation. */
+typedef struct RTCRPKCS7SIGNINGJOBINT *RTCRPKCS7SIGNINGJOB;
+/** Pointer to a PKCS\#7/CMS signing operation handle. */
+typedef RTCRPKCS7SIGNINGJOB *PRTCRPKCS7SIGNINGJOB;
+
+//// CMS_sign
+//RTDECL(int) RTCrPkcs7Sign(PRTCRPKCS7SIGNINGJOB *phJob, uint64_t fFlags, PCRTCRX509CERTIFICATE pSigner, RTCRKEY hPrivateKey,
+// RTCRSTORE hAdditionalCerts,
+//
+
+#ifdef IPRT_WITH_OPENSSL
+
+static int rtCrPkcs7SimpleSignSignedDataDoV1TweakContent(PKCS7 *pOsslPkcs7, const char *pszContentId,
+ const void *pvData, size_t cbData,
+ PRTERRINFO pErrInfo)
+{
+ AssertReturn(pszContentId, RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB,
+ "RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP requires content type in additional attribs"));
+
+ /*
+ * Create a new inner PKCS#7 content container, forcing it to the 'other' type.
+ */
+ PKCS7 *pOsslInnerContent = PKCS7_new();
+ if (!pOsslInnerContent)
+ return RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "PKCS7_new failed");
+
+ /* Set the type. */
+ int rc;
+ pOsslInnerContent->type = OBJ_txt2obj(pszContentId, 1);
+ if (pOsslInnerContent->type)
+ {
+ /* Create a dynamic ASN1 type which we set to a sequence. */
+ ASN1_TYPE *pOsslOther = pOsslInnerContent->d.other = ASN1_TYPE_new();
+ if (pOsslOther)
+ {
+ pOsslOther->type = V_ASN1_SEQUENCE;
+
+ /* Create a string and put the data in it. */
+ ASN1_STRING *pOsslStr = pOsslOther->value.sequence = ASN1_STRING_new();
+ if (pOsslStr)
+ {
+ rc = ASN1_STRING_set(pOsslStr, pvData, (int)cbData); /* copies the buffer content */
+ if (rc > 0)
+ {
+ /*
+ * Set the content in the PKCS#7 signed data we're constructing.
+ * This consumes pOsslInnerContent on success.
+ */
+ rc = PKCS7_set_content(pOsslPkcs7, pOsslInnerContent);
+ if (rc > 0)
+ return VINF_SUCCESS;
+
+ /* failed */
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "PKCS7_set_content");
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "ASN1_STRING_set(,,%#x)", cbData);
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "ASN1_STRING_new");
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "ASN1_TYPE_new");
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "OBJ_txt2obj(%s, 1) failed", pszContentId);
+ PKCS7_free(pOsslInnerContent);
+ return rc;
+}
+
+
+static int rtCrPkcs7SimpleSignSignedDataDoV1TweakedFinal(PKCS7 *pOsslPkcs7, const char *pszContentId,
+ const void *pvData, size_t cbData, PRTERRINFO pErrInfo)
+{
+ AssertReturn(pszContentId, RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB,
+ "RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP requires content type in additional attribs"));
+
+ /*
+ * Prepare a BIO of what should be hashed with all the hashing filters attached.
+ */
+ BIO *pOsslBio = PKCS7_dataInit(pOsslPkcs7, NULL);
+ if (!pOsslBio)
+ return RTErrInfoSet(pErrInfo, VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED, "PKCS7_dataInit failed");
+
+ /*
+ * Now write the data.
+ *
+ * We must skip the outer wrapper here (see RTCrPkcs7VerifySignedData). This
+ * is probably a bit presumptive about what we're working on, so add an extra
+ * flag for this later.
+ */
+ uint8_t const *pbToWrite = (uint8_t const *)pvData;
+ size_t cbToWrite = cbData;
+
+ /** @todo add extra flag for this? */
+ RTASN1CURSORPRIMARY SkipCursor;
+ RTAsn1CursorInitPrimary(&SkipCursor, pvData, (uint32_t)cbData,
+ pErrInfo,&g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "skip");
+ RTASN1CORE SkipAsn1Core = { 0 };
+ int rc = RTAsn1CursorReadHdr(&SkipCursor.Cursor, &SkipAsn1Core, "skip-core");
+ if (RT_SUCCESS(rc))
+ {
+ pbToWrite += SkipAsn1Core.cbHdr;
+ cbToWrite -= SkipAsn1Core.cbHdr;
+
+ rc = BIO_write(pOsslBio, pbToWrite, (int)cbToWrite);
+ if (rc == (ssize_t)cbToWrite)
+ {
+ BIO_flush(pOsslBio); /** @todo error check this */
+ if (true)
+ {
+ /*
+ * Finalize the job - produce the signer info signatures and stuff.
+ */
+ rc = PKCS7_dataFinal(pOsslPkcs7, pOsslBio);
+ if (rc > 0)
+ {
+ /*
+ * Now tweak the content so we get the desired content type and
+ * no extra wrappers and stuff.
+ */
+ rc = rtCrPkcs7SimpleSignSignedDataDoV1TweakContent(pOsslPkcs7, pszContentId, pvData, cbData, pErrInfo);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED, "PKCS7_dataFinal failed: %d", rc);
+ }
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_CIPHER_OSSL_ENCRYPT_FINAL_FAILED,
+ "%zu byte data write failed: %d", cbToWrite, rc);
+ }
+ BIO_free_all(pOsslBio);
+ return rc;
+}
+
+
+static int rtCrPkcs7SimpleSignSignedDataDoV1AttribConversion(PKCS7_SIGNER_INFO *pSignerInfo,
+ PCRTCRPKCS7ATTRIBUTES pAdditionalAuthenticatedAttribs,
+ const char **ppszContentId, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+ *ppszContentId = NULL;
+
+ if (pAdditionalAuthenticatedAttribs)
+ {
+
+ /*
+ * Convert each attribute.
+ */
+ STACK_OF(X509_ATTRIBUTE) *pOsslAttributes = sk_X509_ATTRIBUTE_new_null();
+ for (uint32_t i = 0; i < pAdditionalAuthenticatedAttribs->cItems && RT_SUCCESS(rc); i++)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttrib = pAdditionalAuthenticatedAttribs->papItems[i];
+
+ /* Look out for content type, as we will probably need that for
+ RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP later. */
+ if ( pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS
+ && RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_CONTENT_TYPE_OID) == 0)
+ {
+ AssertBreakStmt(!*ppszContentId, rc = VERR_CR_PKCS7_BAD_CONTENT_TYPE_ATTRIB);
+ AssertBreakStmt(pAttrib->uValues.pObjIds && pAttrib->uValues.pObjIds->cItems == 1,
+ rc = VERR_CR_PKCS7_BAD_CONTENT_TYPE_ATTRIB);
+ *ppszContentId = pAttrib->uValues.pObjIds->papItems[0]->szObjId;
+ }
+
+ /* The conversion (IPRT encode, OpenSSL decode). */
+ X509_ATTRIBUTE *pOsslAttrib;
+ rc = rtCrOpenSslConvertPkcs7Attribute((void **)&pOsslAttrib, pAttrib, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (!sk_X509_ATTRIBUTE_push(pOsslAttributes, pOsslAttrib))
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "sk_X509_ATTRIBUTE_push failed");
+ }
+ }
+
+ /*
+ * If we've successfully converted all the attributes, make a deep copy
+ * (waste of resource, but whatever) into the signer info we're working on.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ rc = PKCS7_set_signed_attributes(pSignerInfo, pOsslAttributes); /* deep copy */
+ if (rc <= 0)
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "PKCS7_set_signed_attributes failed");
+ }
+
+ /*
+ * Free the attributes (they were copied). Cannot use X509_ATTRIBUTE_pop_free as
+ * the callback causes Visual C++ to complain about exceptions on the callback.
+ */
+ for (int i = sk_X509_ATTRIBUTE_num(pOsslAttributes) - 1; i >= 0; i--)
+ X509_ATTRIBUTE_free(sk_X509_ATTRIBUTE_value(pOsslAttributes, i));
+ sk_X509_ATTRIBUTE_free(pOsslAttributes);
+ }
+ return rc;
+}
+
+static int rtCrPkcs7SimpleSignSignedDataDoV1(uint32_t fFlags, X509 *pOsslSigner, EVP_PKEY *pEvpPrivateKey,
+ BIO *pOsslData, const EVP_MD *pEvpMd, STACK_OF(X509) *pOsslAdditionalCerts,
+ PCRTCRPKCS7ATTRIBUTES pAdditionalAuthenticatedAttribs,
+ const void *pvData, size_t cbData,
+ BIO **ppOsslResult, PRTERRINFO pErrInfo)
+{
+ /*
+ * Use PKCS7_sign with PKCS7_PARTIAL to start a extended the signing process.
+ */
+ /* Create a ContentInfo we can modify using CMS_sign w/ CMS_PARTIAL. */
+ unsigned int fOsslSign = PKCS7_BINARY | PKCS7_PARTIAL;
+ if (fFlags & RTCRPKCS7SIGN_SD_F_DEATCHED)
+ fOsslSign |= PKCS7_DETACHED;
+ if (fFlags & RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP)
+ fOsslSign |= PKCS7_NOSMIMECAP;
+ int rc = VINF_SUCCESS;
+ PKCS7 *pCms = PKCS7_sign(NULL, NULL, pOsslAdditionalCerts, NULL, fOsslSign);
+ if (pCms != NULL)
+ {
+ /*
+ * Add a signer.
+ */
+ PKCS7_SIGNER_INFO *pSignerInfo = PKCS7_sign_add_signer(pCms, pOsslSigner, pEvpPrivateKey, pEvpMd, fOsslSign);
+ if (pSignerInfo)
+ {
+ /*
+ * Add additional attributes to the signer.
+ */
+ const char *pszContentId = NULL;
+ rc = rtCrPkcs7SimpleSignSignedDataDoV1AttribConversion(pSignerInfo, pAdditionalAuthenticatedAttribs,
+ &pszContentId, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Finalized and actually sign the data.
+ */
+ bool const fTweaked = (fFlags & (RTCRPKCS7SIGN_SD_F_DEATCHED | RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP))
+ == RTCRPKCS7SIGN_SD_F_NO_DATA_ENCAP;
+ if (fTweaked)
+ rc = rtCrPkcs7SimpleSignSignedDataDoV1TweakedFinal(pCms, pszContentId, pvData, cbData, pErrInfo);
+ else
+ {
+ rc = PKCS7_final(pCms, pOsslData, fOsslSign);
+ if (rc > 0)
+ rc = VINF_SUCCESS;
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "PKCS7_final");
+ /** @todo maybe we want to use rtCrPkcs7SimpleSignSignedDataDoV1TweakContent
+ * for when the content type isn't 'data'... */
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get the output and copy it into the result buffer.
+ */
+ BIO *pOsslResult = BIO_new(BIO_s_mem());
+ if (pOsslResult)
+ {
+ rc = i2d_PKCS7_bio(pOsslResult, pCms);
+ if (rc > 0)
+ {
+ *ppOsslResult = pOsslResult;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "i2d_PKCS7_bio");
+ BIO_free(pOsslResult);
+ }
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "BIO_new/BIO_s_mem");
+ }
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "PKCS7_sign_add_signer");
+ }
+ PKCS7_free(pCms);
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "PKCS7_sign");
+ return rc;
+}
+
+
+static int rtCrPkcs7SimpleSignSignedDataDoDefault(uint32_t fFlags, X509 *pOsslSigner, EVP_PKEY *pEvpPrivateKey,
+ BIO *pOsslData, const EVP_MD *pEvpMd, STACK_OF(X509) *pOsslAdditionalCerts,
+ PCRTCRPKCS7ATTRIBUTES pAdditionalAuthenticatedAttribs,
+ BIO **ppOsslResult, PRTERRINFO pErrInfo)
+
+{
+ /*
+ * Use CMS_sign with CMS_PARTIAL to start a extended the signing process.
+ */
+ /* Create a ContentInfo we can modify using CMS_sign w/ CMS_PARTIAL. */
+ unsigned int fOsslSign = CMS_BINARY | CMS_PARTIAL;
+ if (fFlags & RTCRPKCS7SIGN_SD_F_DEATCHED)
+ fOsslSign |= CMS_DETACHED;
+ if (fFlags & RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP)
+ fOsslSign |= CMS_NOSMIMECAP;
+ int rc = VINF_SUCCESS;
+ CMS_ContentInfo *pCms = CMS_sign(NULL, NULL, pOsslAdditionalCerts, NULL, fOsslSign);
+ if (pCms != NULL)
+ {
+ /*
+ * Set encapsulated content type if present in the auth attribs.
+ */
+ uint32_t iAuthAttrSkip = UINT32_MAX;
+ for (uint32_t i = 0; i < pAdditionalAuthenticatedAttribs->cItems && RT_SUCCESS(rc); i++)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttrib = pAdditionalAuthenticatedAttribs->papItems[i];
+ if ( pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS
+ && RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_CONTENT_TYPE_OID) == 0)
+ {
+ AssertBreakStmt(pAttrib->uValues.pObjIds && pAttrib->uValues.pObjIds->cItems == 1,
+ rc = VERR_INTERNAL_ERROR_3);
+ PCRTASN1OBJID pObjId = pAttrib->uValues.pObjIds->papItems[0];
+ ASN1_OBJECT *pOsslObjId = OBJ_txt2obj(pObjId->szObjId, 0 /*no_name*/);
+ if (pOsslObjId)
+ {
+ rc = CMS_set1_eContentType(pCms, pOsslObjId);
+ ASN1_OBJECT_free(pOsslObjId);
+ if (rc < 0)
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_GENERIC_ERROR,
+ "CMS_set1_eContentType(%s)", pObjId->szObjId);
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "OBJ_txt2obj");
+
+ iAuthAttrSkip = i;
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add a signer.
+ */
+ CMS_SignerInfo *pSignerInfo = CMS_add1_signer(pCms, pOsslSigner, pEvpPrivateKey, pEvpMd, fOsslSign);
+ if (pSignerInfo)
+ {
+ /*
+ * Add additional attributes, skipping the content type if found above.
+ */
+ if (pAdditionalAuthenticatedAttribs)
+ for (uint32_t i = 0; i < pAdditionalAuthenticatedAttribs->cItems && RT_SUCCESS(rc); i++)
+ if (i != iAuthAttrSkip)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttrib = pAdditionalAuthenticatedAttribs->papItems[i];
+ X509_ATTRIBUTE *pOsslAttrib;
+ rc = rtCrOpenSslConvertPkcs7Attribute((void **)&pOsslAttrib, pAttrib, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = CMS_signed_add1_attr(pSignerInfo, pOsslAttrib);
+ rtCrOpenSslFreeConvertedPkcs7Attribute((void **)pOsslAttrib);
+ if (rc <= 0)
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "CMS_signed_add1_attr");
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Finalized and actually sign the data.
+ */
+ rc = CMS_final(pCms, pOsslData, NULL /*dcont*/, fOsslSign);
+ if (rc > 0)
+ {
+ /*
+ * Get the output and copy it into the result buffer.
+ */
+ BIO *pOsslResult = BIO_new(BIO_s_mem());
+ if (pOsslResult)
+ {
+ rc = i2d_CMS_bio(pOsslResult, pCms);
+ if (rc > 0)
+ {
+ *ppOsslResult = pOsslResult;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "i2d_CMS_bio");
+ BIO_free(pOsslResult);
+ }
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "BIO_new/BIO_s_mem");
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "CMS_final");
+ }
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "CMS_add1_signer");
+ }
+ CMS_ContentInfo_free(pCms);
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "CMS_sign");
+ return rc;
+}
+
+#endif /* IPRT_WITH_OPENSSL */
+
+
+
+RTDECL(int) RTCrPkcs7SimpleSignSignedData(uint32_t fFlags, PCRTCRX509CERTIFICATE pSigner, RTCRKEY hPrivateKey,
+ void const *pvData, size_t cbData, RTDIGESTTYPE enmDigestType,
+ RTCRSTORE hAdditionalCerts, PCRTCRPKCS7ATTRIBUTES pAdditionalAuthenticatedAttribs,
+ void *pvResult, size_t *pcbResult, PRTERRINFO pErrInfo)
+{
+ size_t const cbResultBuf = *pcbResult;
+ *pcbResult = 0;
+ AssertReturn(!(fFlags & ~RTCRPKCS7SIGN_SD_F_VALID_MASK), VERR_INVALID_FLAGS);
+#ifdef IPRT_WITH_OPENSSL
+ AssertReturn((int)cbData >= 0 && (unsigned)cbData == cbData, VERR_TOO_MUCH_DATA);
+
+ /*
+ * Resolve the digest type.
+ */
+ const EVP_MD *pEvpMd = NULL;
+ if (enmDigestType != RTDIGESTTYPE_UNKNOWN)
+ {
+ pEvpMd = (const EVP_MD *)rtCrOpenSslConvertDigestType(enmDigestType, pErrInfo);
+ AssertReturn(pEvpMd, pErrInfo ? pErrInfo->rc : VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Convert the private key.
+ */
+ EVP_PKEY *pEvpPrivateKey = NULL;
+ int rc = rtCrKeyToOpenSslKey(hPrivateKey, false /*fNeedPublic*/, (void **)&pEvpPrivateKey, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert the signing certificate.
+ */
+ X509 *pOsslSigner = NULL;
+ rc = rtCrOpenSslConvertX509Cert((void **)&pOsslSigner, pSigner, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert any additional certificates.
+ */
+ STACK_OF(X509) *pOsslAdditionalCerts = NULL;
+ if (hAdditionalCerts != NIL_RTCRSTORE)
+ rc = RTCrStoreConvertToOpenSslCertStack(hAdditionalCerts, 0 /*fFlags*/, (void **)&pOsslAdditionalCerts, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a BIO for the data buffer.
+ */
+ BIO *pOsslData = BIO_new_mem_buf((void *)pvData, (int)cbData);
+ if (pOsslData)
+ {
+ /*
+ * Do the work.
+ */
+ BIO *pOsslResult = NULL;
+ if (!(fFlags & RTCRPKCS7SIGN_SD_F_USE_V1))
+ rc = rtCrPkcs7SimpleSignSignedDataDoDefault(fFlags, pOsslSigner, pEvpPrivateKey, pOsslData, pEvpMd,
+ pOsslAdditionalCerts, pAdditionalAuthenticatedAttribs,
+ &pOsslResult, pErrInfo);
+ else
+ rc = rtCrPkcs7SimpleSignSignedDataDoV1(fFlags, pOsslSigner, pEvpPrivateKey, pOsslData, pEvpMd,
+ pOsslAdditionalCerts, pAdditionalAuthenticatedAttribs,
+ pvData, cbData,
+ &pOsslResult, pErrInfo);
+ BIO_free(pOsslData);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Copy out the result.
+ */
+ BUF_MEM *pBuf = NULL;
+ rc = (int)BIO_get_mem_ptr(pOsslResult, &pBuf);
+ if (rc > 0)
+ {
+ AssertPtr(pBuf);
+ size_t const cbResult = pBuf->length;
+ if ( cbResultBuf >= cbResult
+ && pvResult != NULL)
+ {
+ memcpy(pvResult, pBuf->data, cbResult);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ *pcbResult = cbResult;
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "BIO_get_mem_ptr");
+ BIO_free(pOsslResult);
+ }
+ }
+ }
+ rtCrOpenSslFreeConvertedX509Cert(pOsslSigner);
+ }
+ EVP_PKEY_free(pEvpPrivateKey);
+ }
+ return rc;
+#else
+ RT_NOREF(fFlags, pSigner, hPrivateKey, pvData, cbData, enmDigestType, hAdditionalCerts, pAdditionalAuthenticatedAttribs,
+ pvResult, pErrInfo, cbResultBuf);
+ *pcbResult = 0;
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs7-template.h b/src/VBox/Runtime/common/crypto/pkcs7-template.h
new file mode 100644
index 00000000..a0e64367
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs7-template.h
@@ -0,0 +1,236 @@
+/* $Id: pkcs7-template.h $ */
+/** @file
+ * IPRT - Crypto - PKCS \#7, Core APIs, Code Generator Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+/*
+ * One PCKS #7 IssuerAndSerialNumber.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7ISSUERANDSERIALNUMBER
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7IssuerAndSerialNumber
+#define RTASN1TMPL_INT_NAME rtCrPkcs7IssuerAndSerialNumber
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Name, RTCRX509NAME, RTCrX509Name);
+RTASN1TMPL_MEMBER( SerialNumber, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One PCKS #7 Attribute.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7ATTRIBUTE
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7Attribute
+#define RTASN1TMPL_INT_NAME rtCrPkcs7Attribute
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Type, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER_DYN_BEGIN(Type, RTCRPKCS7ATTRIBUTETYPE, enmType, Allocation);
+RTASN1TMPL_MEMBER_DYN( uValues, pOctetStrings, MessageDigest, RTASN1SETOFOCTETSTRINGS, RTAsn1SetOfOctetStrings, Allocation,
+ Type, enmType, RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS, RTCR_PKCS9_ID_MESSAGE_DIGEST_OID);
+RTASN1TMPL_MEMBER_DYN( uValues, pObjIds, ContentType, RTASN1SETOFOBJIDS, RTAsn1SetOfObjIds, Allocation,
+ Type, enmType, RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS, RTCR_PKCS9_ID_CONTENT_TYPE_OID);
+RTASN1TMPL_MEMBER_DYN( uValues, pCounterSignatures,CounterSignatures, RTCRPKCS7SIGNERINFOS, RTCrPkcs7SignerInfos, Allocation,
+ Type, enmType, RTCRPKCS7ATTRIBUTETYPE_COUNTER_SIGNATURES, RTCR_PKCS9_ID_COUNTER_SIGNATURE_OID);
+RTASN1TMPL_MEMBER_DYN( uValues, pSigningTime, SigningTime, RTASN1SETOFTIMES, RTAsn1SetOfTimes, Allocation,
+ Type, enmType, RTCRPKCS7ATTRIBUTETYPE_SIGNING_TIME, RTCR_PKCS9_ID_SIGNING_TIME_OID);
+RTASN1TMPL_MEMBER_DYN( uValues, pContentInfos, MsTimestamp, RTCRPKCS7SETOFCONTENTINFOS, RTCrPkcs7SetOfContentInfos, Allocation,
+ Type, enmType, RTCRPKCS7ATTRIBUTETYPE_MS_TIMESTAMP, RTCR_PKCS9_ID_MS_TIMESTAMP);
+RTASN1TMPL_MEMBER_DYN( uValues, pContentInfos, MsNestedSignature, RTCRPKCS7SETOFCONTENTINFOS, RTCrPkcs7SetOfContentInfos, Allocation,
+ Type, enmType, RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE, RTCR_PKCS9_ID_MS_NESTED_SIGNATURE);
+RTASN1TMPL_MEMBER_DYN( uValues, pObjIdSeqs, MsStatementType, RTASN1SETOFOBJIDSEQS, RTAsn1SetOfObjIdSeqs, Allocation,
+ Type, enmType, RTCRPKCS7ATTRIBUTETYPE_MS_STATEMENT_TYPE, RTCR_PKCS9_ID_MS_STATEMENT_TYPE);
+RTASN1TMPL_MEMBER_DYN( uValues, pOctetStrings, AppleMultiCdPlist, RTASN1SETOFOCTETSTRINGS, RTAsn1SetOfOctetStrings, Allocation,
+ Type, enmType, RTCRPKCS7ATTRIBUTETYPE_APPLE_MULTI_CD_PLIST, RTCR_PKCS9_ID_APPLE_MULTI_CD_PLIST);
+RTASN1TMPL_MEMBER_DYN_DEFAULT( uValues, pCores, RTASN1SETOFCORES, RTAsn1SetOfCores, Allocation,
+ Type, enmType, RTCRPKCS7ATTRIBUTETYPE_UNKNOWN);
+RTASN1TMPL_MEMBER_DYN_END(Type, RTCRPKCS7ATTRIBUTETYPE, enmType, Allocation);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Set of PCKS #7 Attributes.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7ATTRIBUTES
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7Attributes
+#define RTASN1TMPL_INT_NAME rtCrPkcs7Attributes
+RTASN1TMPL_SET_OF(RTCRPKCS7ATTRIBUTE, RTCrPkcs7Attribute);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One PCKS #7 SignerInfo.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7SIGNERINFO
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7SignerInfo
+#define RTASN1TMPL_INT_NAME rtCrPkcs7SignerInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Version, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( IssuerAndSerialNumber, RTCRPKCS7ISSUERANDSERIALNUMBER, RTCrPkcs7IssuerAndSerialNumber);
+RTASN1TMPL_MEMBER( DigestAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER_OPT_ITAG( AuthenticatedAttributes, RTCRPKCS7ATTRIBUTES, RTCrPkcs7Attributes, 0);
+RTASN1TMPL_MEMBER( DigestEncryptionAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER( EncryptedDigest, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_MEMBER_OPT_ITAG( UnauthenticatedAttributes, RTCRPKCS7ATTRIBUTES, RTCrPkcs7Attributes, 1);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Set of PCKS #7 SignerInfos.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7SIGNERINFOS
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7SignerInfos
+#define RTASN1TMPL_INT_NAME rtCrPkcs7SignerInfos
+RTASN1TMPL_SET_OF(RTCRPKCS7SIGNERINFO, RTCrPkcs7SignerInfo);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * PCKS #7 SignedData.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7SIGNEDDATA
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7SignedData
+#define RTASN1TMPL_INT_NAME rtCrPkcs7SignedData
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Version, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( DigestAlgorithms, RTCRX509ALGORITHMIDENTIFIERS, RTCrX509AlgorithmIdentifiers);
+RTASN1TMPL_MEMBER( ContentInfo, RTCRPKCS7CONTENTINFO, RTCrPkcs7ContentInfo);
+RTASN1TMPL_MEMBER_OPT_ITAG( Certificates, RTCRPKCS7SETOFCERTS, RTCrPkcs7SetOfCerts, 0);
+RTASN1TMPL_MEMBER_OPT_ITAG( Crls, RTASN1CORE, RTAsn1Core, 1);
+RTASN1TMPL_MEMBER( SignerInfos, RTCRPKCS7SIGNERINFOS, RTCrPkcs7SignerInfos);
+RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrPkcs7SignedData_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) ) /* no ; */
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Set of PCKS #7 SignedData.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7SETOFSIGNEDDATA
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7SetOfSignedData
+#define RTASN1TMPL_INT_NAME rtCrPkcs7SetOfSignedData
+RTASN1TMPL_SET_OF(RTCRPKCS7SIGNEDDATA, RTCrPkcs7SignedData);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * PCKS #7 DigestInfo.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7DIGESTINFO
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7DigestInfo
+#define RTASN1TMPL_INT_NAME rtCrPkcs7DigestInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( DigestAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER( Digest, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * PCKS #7 ContentInfo.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7CONTENTINFO
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7ContentInfo
+#define RTASN1TMPL_INT_NAME rtCrPkcs7ContentInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( ContentType, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER_OPT_ITAG( Content, RTASN1OCTETSTRING, RTAsn1OctetString, 0);
+RTASN1TMPL_EXEC_DECODE( rc = rtCrPkcs7ContentInfo_DecodeExtra(pCursor, fFlags, pThis, pszErrorTag)) /* no ; */
+RTASN1TMPL_EXEC_CLONE( rc = rtCrPkcs7ContentInfo_CloneExtra(pThis) ) /* no ; */
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Set of PCKS #7 ContentInfo.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7SETOFCONTENTINFOS
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7SetOfContentInfos
+#define RTASN1TMPL_INT_NAME rtCrPkcs7SetOfContentInfos
+RTASN1TMPL_SET_OF(RTCRPKCS7CONTENTINFO, RTCrPkcs7ContentInfo);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One PKCS #7 ExtendedCertificateOrCertificate or a CMS CertificateChoices (sic).
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7CERT
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7Cert
+#define RTASN1TMPL_INT_NAME rtCrPkcs7Cert
+RTASN1TMPL_BEGIN_PCHOICE();
+RTASN1TMPL_PCHOICE_ITAG_UC( ASN1_TAG_SEQUENCE, RTCRPKCS7CERTCHOICE_X509, u.pX509Cert, X509Cert, RTCRX509CERTIFICATE, RTCrX509Certificate);
+RTASN1TMPL_PCHOICE_ITAG( 0, RTCRPKCS7CERTCHOICE_EXTENDED_PKCS6, u.pExtendedCert, ExtendedCert, RTASN1CORE, RTAsn1Core);
+RTASN1TMPL_PCHOICE_ITAG( 1, RTCRPKCS7CERTCHOICE_AC_V1, u.pAcV1, AcV1, RTASN1CORE, RTAsn1Core);
+RTASN1TMPL_PCHOICE_ITAG( 2, RTCRPKCS7CERTCHOICE_AC_V2, u.pAcV2, AcV2, RTASN1CORE, RTAsn1Core);
+RTASN1TMPL_PCHOICE_ITAG( 3, RTCRPKCS7CERTCHOICE_OTHER, u.pOtherCert, OtherCert, RTASN1CORE, RTAsn1Core);
+RTASN1TMPL_END_PCHOICE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Set of PKCS #7 ExtendedCertificateOrCertificate or a CMS CertificateChoices.
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS7SETOFCERTS
+#define RTASN1TMPL_EXT_NAME RTCrPkcs7SetOfCerts
+#define RTASN1TMPL_INT_NAME rtCrPkcs7SetOfCerts
+RTASN1TMPL_SET_OF(RTCRPKCS7CERT, RTCrPkcs7Cert);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs7-verify.cpp b/src/VBox/Runtime/common/crypto/pkcs7-verify.cpp
new file mode 100644
index 00000000..121ca442
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs7-verify.cpp
@@ -0,0 +1,866 @@
+/* $Id: pkcs7-verify.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#7, Verification
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs7.h>
+
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/key.h>
+#include <iprt/crypto/pkix.h>
+#include <iprt/crypto/store.h>
+#include <iprt/crypto/x509.h>
+
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/pkcs7.h>
+# include <openssl/x509.h>
+# include <openssl/err.h>
+# include "internal/openssl-post.h"
+#endif
+
+
+
+#ifdef IPRT_WITH_OPENSSL
+static int rtCrPkcs7VerifySignedDataUsingOpenSsl(PCRTCRPKCS7CONTENTINFO pContentInfo, uint32_t fFlags,
+ RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts,
+ void const *pvContent, size_t cbContent, PRTERRINFO pErrInfo)
+{
+ RT_NOREF_PV(fFlags);
+
+ /*
+ * Verify using OpenSSL. ERR_PUT_error
+ */
+ unsigned char const *pbRawContent;
+ uint32_t cbRawContent;
+ void *pvFree;
+ int rcOssl = RTAsn1EncodeQueryRawBits(RTCrPkcs7ContentInfo_GetAsn1Core(pContentInfo),
+ (const uint8_t **)&pbRawContent, &cbRawContent, &pvFree, pErrInfo);
+ AssertRCReturn(rcOssl, rcOssl);
+
+ PKCS7 *pOsslPkcs7 = NULL;
+ PKCS7 *pOsslPkcs7Ret = d2i_PKCS7(&pOsslPkcs7, &pbRawContent, cbRawContent);
+
+ RTMemTmpFree(pvFree);
+
+ if (pOsslPkcs7 != NULL && pOsslPkcs7Ret == pOsslPkcs7)
+ {
+ STACK_OF(X509) *pAddCerts = NULL;
+ if (hAdditionalCerts != NIL_RTCRSTORE)
+ rcOssl = RTCrStoreConvertToOpenSslCertStack(hAdditionalCerts, 0, (void **)&pAddCerts, pErrInfo);
+ else
+ {
+ pAddCerts = sk_X509_new_null();
+ rcOssl = RT_LIKELY(pAddCerts != NULL) ? VINF_SUCCESS : VERR_NO_MEMORY;
+ }
+ if (RT_SUCCESS(rcOssl))
+ {
+ PCRTCRPKCS7SETOFCERTS pCerts = &pContentInfo->u.pSignedData->Certificates;
+ for (uint32_t i = 0; i < pCerts->cItems; i++)
+ if (pCerts->papItems[i]->enmChoice == RTCRPKCS7CERTCHOICE_X509)
+ rtCrOpenSslAddX509CertToStack(pAddCerts, pCerts->papItems[i]->u.pX509Cert, NULL);
+
+ X509_STORE *pTrustedCerts = NULL;
+ if (hTrustedCerts != NIL_RTCRSTORE)
+ rcOssl = RTCrStoreConvertToOpenSslCertStore(hTrustedCerts, 0, (void **)&pTrustedCerts, pErrInfo);
+ if (RT_SUCCESS(rcOssl))
+ {
+ rtCrOpenSslInit();
+
+ BIO *pBioContent = BIO_new_mem_buf((void *)pvContent, (int)cbContent);
+ if (pBioContent)
+ {
+ uint32_t fOsslFlags = PKCS7_NOCHAIN;
+ fOsslFlags |= PKCS7_NOVERIFY; // temporary hack.
+ if (PKCS7_verify(pOsslPkcs7, pAddCerts, pTrustedCerts, pBioContent, NULL /*out*/, fOsslFlags))
+ rcOssl = VINF_SUCCESS;
+ else
+ {
+ rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_OSSL_VERIFY_FAILED, "PKCS7_verify failed: ");
+ if (pErrInfo)
+ ERR_print_errors_cb(rtCrOpenSslErrInfoCallback, pErrInfo);
+ }
+ BIO_free(pBioContent);
+ }
+ if (pTrustedCerts)
+ X509_STORE_free(pTrustedCerts);
+ }
+ else
+ rcOssl = RTErrInfoSet(pErrInfo, rcOssl, "RTCrStoreConvertToOpenSslCertStore failed");
+#include "internal/openssl-pre.h" /* Need to disable C5039 warning here. */
+ if (pAddCerts)
+ sk_X509_pop_free(pAddCerts, X509_free);
+#include "internal/openssl-post.h"
+ }
+ else
+ rcOssl = RTErrInfoSet(pErrInfo, rcOssl, "RTCrStoreConvertToOpenSslCertStack failed");
+ PKCS7_free(pOsslPkcs7);
+ }
+ else
+ {
+ rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_OSSL_D2I_FAILED, "d2i_PKCS7 failed");
+ if (pErrInfo)
+ ERR_print_errors_cb(rtCrOpenSslErrInfoCallback, pErrInfo);
+ }
+
+ return rcOssl;
+}
+#endif /* IPRT_WITH_OPENSSL */
+
+
+
+static int rtCrPkcs7VerifyCertUsageTimstamping(PCRTCRX509CERTIFICATE pCert, PRTERRINFO pErrInfo)
+{
+ if (!(pCert->TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_EXT_KEY_USAGE))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "No extended key usage certificate attribute.");
+ if (!(pCert->TbsCertificate.T3.fExtKeyUsage & (RTCRX509CERT_EKU_F_TIMESTAMPING | RTCRX509CERT_EKU_F_MS_TIMESTAMP_SIGNING)))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "fExtKeyUsage=%#x, missing %#x (time stamping)",
+ pCert->TbsCertificate.T3.fExtKeyUsage,
+ RTCRX509CERT_EKU_F_TIMESTAMPING | RTCRX509CERT_EKU_F_MS_TIMESTAMP_SIGNING);
+ return VINF_SUCCESS;
+}
+
+
+static int rtCrPkcs7VerifyCertUsageDigitalSignature(PCRTCRX509CERTIFICATE pCert, PRTERRINFO pErrInfo)
+{
+ if ( (pCert->TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_KEY_USAGE)
+ && !(pCert->TbsCertificate.T3.fKeyUsage & RTCRX509CERT_KEY_USAGE_F_DIGITAL_SIGNATURE))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "fKeyUsage=%#x, missing %#x",
+ pCert->TbsCertificate.T3.fKeyUsage, RTCRX509CERT_KEY_USAGE_F_DIGITAL_SIGNATURE);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{RTCRPKCS7VERIFYCERTCALLBACK,
+ * Default implementation that checks for the DigitalSignature KeyUsage bit.}
+ */
+RTDECL(int) RTCrPkcs7VerifyCertCallbackDefault(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, uint32_t fFlags,
+ void *pvUser, PRTERRINFO pErrInfo)
+{
+ RT_NOREF_PV(hCertPaths); RT_NOREF_PV(pvUser);
+ int rc = VINF_SUCCESS;
+
+ if (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA)
+ rc = rtCrPkcs7VerifyCertUsageDigitalSignature(pCert, pErrInfo);
+
+ if ( (fFlags & RTCRPKCS7VCC_F_TIMESTAMP)
+ && RT_SUCCESS(rc))
+ rc = rtCrPkcs7VerifyCertUsageTimstamping(pCert, pErrInfo);
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{RTCRPKCS7VERIFYCERTCALLBACK,
+ * Standard code signing. Use this for Microsoft SPC.}
+ */
+RTDECL(int) RTCrPkcs7VerifyCertCallbackCodeSigning(PCRTCRX509CERTIFICATE pCert, RTCRX509CERTPATHS hCertPaths, uint32_t fFlags,
+ void *pvUser, PRTERRINFO pErrInfo)
+{
+ RT_NOREF_PV(hCertPaths); RT_NOREF_PV(pvUser);
+ int rc = VINF_SUCCESS;
+ if (fFlags & RTCRPKCS7VCC_F_SIGNED_DATA)
+ {
+ /*
+ * If KeyUsage is present it must include digital signature.
+ */
+ rc = rtCrPkcs7VerifyCertUsageDigitalSignature(pCert, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * The extended usage 'code signing' must be present.
+ */
+ if (!(pCert->TbsCertificate.T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_EXT_KEY_USAGE))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "No extended key usage certificate attribute.");
+ if (!(pCert->TbsCertificate.T3.fExtKeyUsage & RTCRX509CERT_EKU_F_CODE_SIGNING))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_KEY_USAGE_MISMATCH, "fExtKeyUsage=%#RX64, missing CODE_SIGNING (%#RX64)",
+ pCert->TbsCertificate.T3.fExtKeyUsage, RTCRX509CERT_EKU_F_CODE_SIGNING);
+ }
+ }
+
+ /*
+ * Timestamping too?
+ */
+ if ( (fFlags & RTCRPKCS7VCC_F_TIMESTAMP)
+ && RT_SUCCESS(rc))
+ rc = rtCrPkcs7VerifyCertUsageTimstamping(pCert, pErrInfo);
+
+ return rc;
+}
+
+
+/**
+ * Deals with authenticated attributes.
+ *
+ * When authenticated attributes are present (checked by caller) we must:
+ * - fish out the content type and check it against the content inof,
+ * - fish out the message digest among and check it against *phDigest,
+ * - compute the message digest of the authenticated attributes and
+ * replace *phDigest with this for the signature verification.
+ *
+ * @returns IPRT status code.
+ * @param pSignerInfo The signer info being verified.
+ * @param pSignedData The signed data.
+ * @param phDigest On input this is the digest of the content. On
+ * output it will (on success) be a reference to
+ * the message digest of the authenticated
+ * attributes. The input reference is consumed.
+ * The caller shall release the output reference.
+ * @param fFlags Flags.
+ * @param pErrInfo Extended error info, optional.
+ */
+static int rtCrPkcs7VerifySignerInfoAuthAttribs(PCRTCRPKCS7SIGNERINFO pSignerInfo, PCRTCRPKCS7SIGNEDDATA pSignedData,
+ PRTCRDIGEST phDigest, uint32_t fFlags, PRTERRINFO pErrInfo)
+{
+ /*
+ * Scan the attributes and validate the two required attributes
+ * (RFC-2315, chapter 9.2, fourth bullet). Checking that we've got exactly
+ * one of each of them is checked by the santiy checker function, so we'll
+ * just assert that it did it's job here.
+ */
+ uint32_t cContentTypes = 0;
+ uint32_t cMessageDigests = 0;
+ uint32_t i = pSignerInfo->AuthenticatedAttributes.cItems;
+ while (i-- > 0)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->AuthenticatedAttributes.papItems[i];
+
+ if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_CONTENT_TYPE_OID) == 0)
+ {
+ AssertReturn(!cContentTypes, VERR_CR_PKCS7_INTERNAL_ERROR);
+ AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OBJ_IDS, VERR_CR_PKCS7_INTERNAL_ERROR);
+ AssertReturn(pAttrib->uValues.pObjIds->cItems == 1, VERR_CR_PKCS7_INTERNAL_ERROR);
+
+ if ( !(fFlags & RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE) /* See note about microsoft below. */
+ && RTAsn1ObjId_Compare(pAttrib->uValues.pObjIds->papItems[0], &pSignedData->ContentInfo.ContentType) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_CONTENT_TYPE_ATTRIB_MISMATCH,
+ "Expected content-type %s, found %s", pAttrib->uValues.pObjIds->papItems[0]->szObjId,
+ pSignedData->ContentInfo.ContentType.szObjId);
+ cContentTypes++;
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_MESSAGE_DIGEST_OID) == 0)
+ {
+ AssertReturn(!cMessageDigests, VERR_CR_PKCS7_INTERNAL_ERROR);
+ AssertReturn(pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_OCTET_STRINGS, VERR_CR_PKCS7_INTERNAL_ERROR);
+ AssertReturn(pAttrib->uValues.pOctetStrings->cItems == 1, VERR_CR_PKCS7_INTERNAL_ERROR);
+
+ if (!RTCrDigestMatch(*phDigest,
+ pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pv,
+ pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb))
+ {
+ size_t cbHash = RTCrDigestGetHashSize(*phDigest);
+ if (cbHash != pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MESSAGE_DIGEST_ATTRIB_MISMATCH,
+ "Authenticated message-digest attribute mismatch: cbHash=%#zx cbValue=%#x",
+ cbHash, pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb);
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MESSAGE_DIGEST_ATTRIB_MISMATCH,
+ "Authenticated message-digest attribute mismatch (cbHash=%#zx):\n"
+ "signed: %.*Rhxs\n"
+ "our: %.*Rhxs\n",
+ cbHash,
+ cbHash, pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pv,
+ cbHash, RTCrDigestGetHash(*phDigest));
+ }
+ cMessageDigests++;
+ }
+ }
+
+ /*
+ * Full error reporting here as we don't currently extensively santiy check
+ * counter signatures.
+ * Note! Microsoft includes content info in their timestamp counter signatures,
+ * at least for vista, despite the RFC-3852 stating counter signatures
+ * "MUST NOT contain a content-type".
+ */
+ if (RT_UNLIKELY( cContentTypes != 1
+ && !(fFlags & RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE)))
+ return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_CONTENT_TYPE_ATTRIB,
+ "Missing authenticated content-type attribute.");
+ if (RT_UNLIKELY(cMessageDigests != 1))
+ return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_MISSING_MESSAGE_DIGEST_ATTRIB,
+ "Missing authenticated message-digest attribute.");
+
+ /*
+ * Calculate the digest of the authenticated attributes for use in the
+ * signature validation.
+ */
+ if ( pSignerInfo->DigestAlgorithm.Parameters.enmType != RTASN1TYPE_NULL
+ && pSignerInfo->DigestAlgorithm.Parameters.enmType != RTASN1TYPE_NOT_PRESENT)
+ return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_DIGEST_PARAMS_NOT_IMPL, "Digest algorithm has unsupported parameters");
+
+ RTCRDIGEST hDigest;
+ int rc = RTCrDigestCreateByObjId(&hDigest, &pSignerInfo->DigestAlgorithm.Algorithm);
+ if (RT_SUCCESS(rc))
+ {
+ RTCrDigestRelease(*phDigest);
+ *phDigest = hDigest;
+
+ /** @todo The encoding step modifies the data, contradicting the const-ness
+ * of the parameter. */
+ rc = RTCrPkcs7Attributes_HashAttributes((PRTCRPKCS7ATTRIBUTES)&pSignerInfo->AuthenticatedAttributes, hDigest, pErrInfo);
+ }
+ return rc;
+}
+
+
+/**
+ * Find the handle to the digest given by the specified SignerInfo.
+ *
+ * @returns IPRT status code
+ * @param phDigest Where to return a referenced digest handle on
+ * success.
+ * @param pSignedData The signed data structure.
+ * @param pSignerInfo The signer info.
+ * @param pahDigests Array of content digests that runs parallel to
+ * pSignedData->DigestAlgorithms.
+ * @param pErrInfo Where to store additional error details,
+ * optional.
+ */
+static int rtCrPkcs7VerifyFindDigest(PRTCRDIGEST phDigest, PCRTCRPKCS7SIGNEDDATA pSignedData,
+ PCRTCRPKCS7SIGNERINFO pSignerInfo, PRTCRDIGEST pahDigests, PRTERRINFO pErrInfo)
+{
+ uint32_t iDigest = pSignedData->DigestAlgorithms.cItems;
+ while (iDigest-- > 0)
+ if (RTCrX509AlgorithmIdentifier_Compare(pSignedData->DigestAlgorithms.papItems[iDigest],
+ &pSignerInfo->DigestAlgorithm) == 0)
+ {
+ RTCRDIGEST hDigest = pahDigests[iDigest];
+ uint32_t cRefs = RTCrDigestRetain(hDigest);
+ AssertReturn(cRefs != UINT32_MAX, VERR_CR_PKCS7_INTERNAL_ERROR);
+ *phDigest = hDigest;
+ return VINF_SUCCESS;
+ }
+ *phDigest = NIL_RTCRDIGEST; /* Make gcc happy. */
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_ALGO_NOT_FOUND_IN_LIST,
+ "SignerInfo.DigestAlgorithm %s not found.",
+ pSignerInfo->DigestAlgorithm.Algorithm.szObjId);
+}
+
+
+/**
+ * Verifies one signature on a PKCS \#7 SignedData.
+ *
+ * @returns IPRT status code.
+ * @param pSignerInfo The signature.
+ * @param pSignedData The SignedData.
+ * @param hDigests The digest corresponding to
+ * pSignerInfo->DigestAlgorithm.
+ * @param fFlags Verification flags.
+ * @param hAdditionalCerts Store containing optional certificates,
+ * optional.
+ * @param hTrustedCerts Store containing trusted certificates, required.
+ * @param pValidationTime The time we're supposed to validate the
+ * certificates chains at.
+ * @param pfnVerifyCert Signing certificate verification callback.
+ * @param fVccFlags Signing certificate verification callback flags.
+ * @param pvUser Callback parameter.
+ * @param pErrInfo Where to store additional error details,
+ * optional.
+ */
+static int rtCrPkcs7VerifySignerInfo(PCRTCRPKCS7SIGNERINFO pSignerInfo, PCRTCRPKCS7SIGNEDDATA pSignedData,
+ RTCRDIGEST hDigest, uint32_t fFlags, RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts,
+ PCRTTIMESPEC pValidationTime, PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert,
+ uint32_t fVccFlags, void *pvUser, PRTERRINFO pErrInfo)
+{
+ /*
+ * Locate the certificate used for signing.
+ */
+ PCRTCRCERTCTX pSignerCertCtx = NULL;
+ PCRTCRX509CERTIFICATE pSignerCert = NULL;
+ if (hTrustedCerts != NIL_RTCRSTORE)
+ pSignerCertCtx = RTCrStoreCertByIssuerAndSerialNo(hTrustedCerts, &pSignerInfo->IssuerAndSerialNumber.Name,
+ &pSignerInfo->IssuerAndSerialNumber.SerialNumber);
+ if (!pSignerCertCtx && hAdditionalCerts != NIL_RTCRSTORE)
+ pSignerCertCtx = RTCrStoreCertByIssuerAndSerialNo(hAdditionalCerts, &pSignerInfo->IssuerAndSerialNumber.Name,
+ &pSignerInfo->IssuerAndSerialNumber.SerialNumber);
+ if (pSignerCertCtx)
+ pSignerCert = pSignerCertCtx->pCert;
+ else
+ {
+ pSignerCert = RTCrPkcs7SetOfCerts_FindX509ByIssuerAndSerialNumber(&pSignedData->Certificates,
+ &pSignerInfo->IssuerAndSerialNumber.Name,
+ &pSignerInfo->IssuerAndSerialNumber.SerialNumber);
+ if (!pSignerCert)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNED_DATA_CERT_NOT_FOUND,
+ "Certificate not found: serial=%.*Rhxs",
+ pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.cb,
+ pSignerInfo->IssuerAndSerialNumber.SerialNumber.Asn1Core.uData.pv);
+ }
+
+ /*
+ * Unless caller requesed all certificates to be trusted fully, we always
+ * pass it on to the certificate path builder so it can do the requested
+ * checks on trust anchors. (We didn't used to do this as the path
+ * builder could handle trusted targets. A benefit here is that
+ * pfnVerifyCert can assume a hCertPaths now, and get the validation time
+ * from it if it wants it.)
+ *
+ * If no valid paths are found, this step will fail.
+ */
+ int rc;
+ if (!(fFlags & RTCRPKCS7VERIFY_SD_F_TRUST_ALL_CERTS))
+ {
+ RTCRX509CERTPATHS hCertPaths;
+ rc = RTCrX509CertPathsCreate(&hCertPaths, pSignerCert);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509CertPathsSetValidTimeSpec(hCertPaths, pValidationTime);
+ if (hTrustedCerts != NIL_RTCRSTORE && RT_SUCCESS(rc))
+ rc = RTCrX509CertPathsSetTrustedStore(hCertPaths, hTrustedCerts);
+ if (hAdditionalCerts != NIL_RTCRSTORE && RT_SUCCESS(rc))
+ rc = RTCrX509CertPathsSetUntrustedStore(hCertPaths, hAdditionalCerts);
+ if (pSignedData->Certificates.cItems > 0 && RT_SUCCESS(rc))
+ rc = RTCrX509CertPathsSetUntrustedSet(hCertPaths, &pSignedData->Certificates);
+ if ((fFlags & RTCRPKCS7VERIFY_SD_F_CHECK_TRUST_ANCHORS) && RT_SUCCESS(rc))
+ rc = RTCrX509CertPathsSetTrustAnchorChecks(hCertPaths, true /*fEnable*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509CertPathsBuild(hCertPaths, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = RTCrX509CertPathsValidateAll(hCertPaths, NULL, pErrInfo);
+
+ /*
+ * Check that the certificate purpose and whatnot matches what
+ * is being signed.
+ */
+ if (RT_SUCCESS(rc))
+ rc = pfnVerifyCert(pSignerCert, hCertPaths, fVccFlags, pvUser, pErrInfo);
+ }
+ else
+ RTErrInfoSetF(pErrInfo, rc, "Error configuring path builder: %Rrc", rc);
+ RTCrX509CertPathsRelease(hCertPaths);
+ }
+ }
+ /*
+ * Check that the certificate purpose matches what is signed.
+ */
+ else
+ rc = pfnVerifyCert(pSignerCert, NIL_RTCRX509CERTPATHS, fVccFlags, pvUser, pErrInfo);
+
+ /*
+ * Reference the digest so we can safely replace with one on the
+ * authenticated attributes below.
+ */
+ if ( RT_SUCCESS(rc)
+ && RTCrDigestRetain(hDigest) != UINT32_MAX)
+ {
+ /*
+ * If there are authenticated attributes, we've got more work before we
+ * can verify the signature.
+ */
+ if ( RT_SUCCESS(rc)
+ && RTCrPkcs7Attributes_IsPresent(&pSignerInfo->AuthenticatedAttributes))
+ rc = rtCrPkcs7VerifySignerInfoAuthAttribs(pSignerInfo, pSignedData, &hDigest, fFlags, pErrInfo);
+
+ /*
+ * Verify the signature.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ RTCRKEY hKey;
+ rc = RTCrKeyCreateFromSubjectPublicKeyInfo(&hKey, &pSignerCert->TbsCertificate.SubjectPublicKeyInfo,
+ pErrInfo, "pkcs7");
+ if (RT_SUCCESS(rc))
+ {
+ RTCRPKIXSIGNATURE hSignature;
+ rc = RTCrPkixSignatureCreateByObjId(&hSignature, &pSignerInfo->DigestEncryptionAlgorithm.Algorithm,
+ hKey, &pSignerInfo->DigestEncryptionAlgorithm.Parameters, false /*fSigning*/);
+ RTCrKeyRelease(hKey);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Check that DigestEncryptionAlgorithm is compatible with hSignature
+ * (this is not vital). */
+ rc = RTCrPkixSignatureVerifyOctetString(hSignature, hDigest, &pSignerInfo->EncryptedDigest);
+ if (RT_FAILURE(rc))
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_SIGNATURE_VERIFICATION_FAILED,
+ "Signature verification failed: %Rrc", rc);
+ RTCrPkixSignatureRelease(hSignature);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "Failure to instantiate public key algorithm [IPRT]: %s (%s)",
+ pSignerCert->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm.szObjId,
+ pSignerInfo->DigestEncryptionAlgorithm.Algorithm.szObjId);
+ }
+ }
+
+ RTCrDigestRelease(hDigest);
+ }
+ else if (RT_SUCCESS(rc))
+ rc = VERR_CR_PKCS7_INTERNAL_ERROR;
+ RTCrCertCtxRelease(pSignerCertCtx);
+ return rc;
+}
+
+
+/**
+ * Verifies a counter signature.
+ *
+ * @returns IPRT status code.
+ * @param pCounterSignerInfo The counter signature.
+ * @param pPrimarySignerInfo The primary signature (can be a counter
+ * signature too if nested).
+ * @param pSignedData The SignedData.
+ * @param fFlags Verification flags.
+ * @param hAdditionalCerts Store containing optional certificates,
+ * optional.
+ * @param hTrustedCerts Store containing trusted certificates, required.
+ * @param pValidationTime The time we're supposed to validate the
+ * certificates chains at.
+ * @param pfnVerifyCert Signing certificate verification callback.
+ * @param fVccFlags Signing certificate verification callback flags.
+ * @param pvUser Callback parameter.
+ * @param pErrInfo Where to store additional error details,
+ * optional.
+ */
+static int rtCrPkcs7VerifyCounterSignerInfo(PCRTCRPKCS7SIGNERINFO pCounterSignerInfo, PCRTCRPKCS7SIGNERINFO pPrimarySignerInfo,
+ PCRTCRPKCS7SIGNEDDATA pSignedData, uint32_t fFlags,
+ RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts, PCRTTIMESPEC pValidationTime,
+ PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, uint32_t fVccFlags,
+ void *pvUser, PRTERRINFO pErrInfo)
+{
+ /*
+ * Calculate the digest we need to verify.
+ */
+ RTCRDIGEST hDigest;
+ int rc = RTCrDigestCreateByObjId(&hDigest, &pCounterSignerInfo->DigestAlgorithm.Algorithm);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrDigestUpdate(hDigest,
+ pPrimarySignerInfo->EncryptedDigest.Asn1Core.uData.pv,
+ pPrimarySignerInfo->EncryptedDigest.Asn1Core.cb);
+ if (RT_SUCCESS(rc))
+ rc = RTCrDigestFinal(hDigest, NULL, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Pass it on to the common SignerInfo verifier function.
+ */
+ rc = rtCrPkcs7VerifySignerInfo(pCounterSignerInfo, pSignedData, hDigest,
+ fFlags | RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE,
+ hAdditionalCerts, hTrustedCerts, pValidationTime,
+ pfnVerifyCert, fVccFlags, pvUser, pErrInfo);
+
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CALC_ERROR,
+ "Hashing for counter signature failed unexpectedly: %Rrc", rc);
+ RTCrDigestRelease(hDigest);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CREATE_ERROR, "Error creating digest for '%s': %Rrc",
+ pCounterSignerInfo->DigestAlgorithm.Algorithm.szObjId, rc);
+
+ return rc;
+}
+
+
+/**
+ * Worker.
+ */
+static int rtCrPkcs7VerifySignedDataEx(PCRTCRPKCS7CONTENTINFO pContentInfo, uint32_t fFlags,
+ RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts,
+ PCRTTIMESPEC pValidationTime,
+ PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, void *pvUser,
+ void const *pvContent, size_t cbContent, PRTERRINFO pErrInfo)
+{
+ /*
+ * Check and adjust the input.
+ */
+ if (pfnVerifyCert)
+ AssertPtrReturn(pfnVerifyCert, VERR_INVALID_POINTER);
+ else
+ pfnVerifyCert = RTCrPkcs7VerifyCertCallbackDefault;
+
+ if (!RTCrPkcs7ContentInfo_IsSignedData(pContentInfo))
+ return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_NOT_SIGNED_DATA, "Not PKCS #7 SignedData.");
+ PCRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData;
+ int rc = RTCrPkcs7SignedData_CheckSanity(pSignedData, 0, pErrInfo, "");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Hash the content info.
+ */
+ /* Check that there aren't too many or too few hash algorithms for our
+ implementation and purposes. */
+ RTCRDIGEST ahDigests[2];
+ uint32_t const cDigests = pSignedData->DigestAlgorithms.cItems;
+ if (!cDigests) /** @todo we might have to support this... */
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_NO_DIGEST_ALGORITHMS, "No digest algorithms");
+
+ if (cDigests > RT_ELEMENTS(ahDigests))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_TOO_MANY_DIGEST_ALGORITHMS,
+ "Too many digest algorithm: cAlgorithms=%u", cDigests);
+
+ /* Create the message digest calculators. */
+ rc = VERR_CR_PKCS7_NO_DIGEST_ALGORITHMS;
+ uint32_t i;
+ for (i = 0; i < cDigests; i++)
+ {
+ rc = RTCrDigestCreateByObjId(&ahDigests[i], &pSignedData->DigestAlgorithms.papItems[i]->Algorithm);
+ if (RT_FAILURE(rc))
+ {
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CREATE_ERROR, "Error creating digest for '%s': %Rrc",
+ pSignedData->DigestAlgorithms.papItems[i]->Algorithm.szObjId, rc);
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* Hash the content. */
+ for (i = 0; i < cDigests && RT_SUCCESS(rc); i++)
+ {
+ rc = RTCrDigestUpdate(ahDigests[i], pvContent, cbContent);
+ if (RT_SUCCESS(rc))
+ rc = RTCrDigestFinal(ahDigests[i], NULL, 0);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Validate the signed infos. The flags may select one particular entry.
+ */
+ RTTIMESPEC const GivenValidationTime = *pValidationTime;
+ uint32_t fPrimaryVccFlags = !(fFlags & RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING)
+ ? RTCRPKCS7VCC_F_SIGNED_DATA : RTCRPKCS7VCC_F_TIMESTAMP;
+ uint32_t cItems = pSignedData->SignerInfos.cItems;
+ i = 0;
+ if (fFlags & RTCRPKCS7VERIFY_SD_F_HAS_SIGNER_INDEX)
+ {
+ i = (fFlags & RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX_MASK) >> RTCRPKCS7VERIFY_SD_F_SIGNER_INDEX_SHIFT;
+ cItems = RT_MIN(cItems, i + 1);
+ }
+ rc = VERR_CR_PKCS7_NO_SIGNER_INFOS;
+ for (; i < cItems; i++)
+ {
+ PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[i];
+ RTCRDIGEST hThisDigest = NIL_RTCRDIGEST; /* (gcc maybe incredible stupid.) */
+ rc = rtCrPkcs7VerifyFindDigest(&hThisDigest, pSignedData, pSignerInfo, ahDigests, pErrInfo);
+ if (RT_FAILURE(rc))
+ break;
+
+ /*
+ * See if we can find a trusted signing time.
+ * (Note that while it would make sense splitting up this function,
+ * we need to carry a lot of arguments around, so better not.)
+ */
+ bool fDone = false;
+ PCRTCRPKCS7SIGNERINFO pSigningTimeSigner = NULL;
+ PCRTASN1TIME pSignedTime;
+ while ( !fDone
+ && (pSignedTime = RTCrPkcs7SignerInfo_GetSigningTime(pSignerInfo, &pSigningTimeSigner)) != NULL)
+ {
+ RTTIMESPEC ThisValidationTime;
+ if (RT_LIKELY(RTTimeImplode(&ThisValidationTime, &pSignedTime->Time)))
+ {
+ if (pSigningTimeSigner == pSignerInfo)
+ {
+ if (fFlags & RTCRPKCS7VERIFY_SD_F_COUNTER_SIGNATURE_SIGNING_TIME_ONLY)
+ continue;
+ rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags,
+ hAdditionalCerts, hTrustedCerts, &ThisValidationTime,
+ pfnVerifyCert, fPrimaryVccFlags | RTCRPKCS7VCC_F_TIMESTAMP,
+ pvUser, pErrInfo);
+ }
+ else
+ {
+ rc = VINF_SUCCESS;
+ if (!(fFlags & RTCRPKCS7VERIFY_SD_F_USE_SIGNING_TIME_UNVERIFIED))
+ rc = rtCrPkcs7VerifyCounterSignerInfo(pSigningTimeSigner, pSignerInfo, pSignedData,
+ fFlags & ~RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME,
+ hAdditionalCerts, hTrustedCerts, &ThisValidationTime,
+ pfnVerifyCert, RTCRPKCS7VCC_F_TIMESTAMP, pvUser, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts,
+ hTrustedCerts, &ThisValidationTime,
+ pfnVerifyCert, fPrimaryVccFlags, pvUser, pErrInfo);
+ }
+ fDone = RT_SUCCESS(rc)
+ || (fFlags & RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_SIGNING_TIME_IF_PRESENT);
+ if ((fFlags & RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME) && fDone)
+ *(PRTTIMESPEC)pValidationTime = ThisValidationTime;
+ }
+ else
+ {
+ rc = RTErrInfoSet(pErrInfo, VERR_INTERNAL_ERROR_3, "RTTimeImplode failed");
+ fDone = true;
+ }
+ }
+
+ /*
+ * If not luck, check for microsoft timestamp counter signatures.
+ */
+ if (!fDone && !(fFlags & RTCRPKCS7VERIFY_SD_F_IGNORE_MS_TIMESTAMP))
+ {
+ PCRTCRPKCS7CONTENTINFO pSignedTimestamp = NULL;
+ pSignedTime = RTCrPkcs7SignerInfo_GetMsTimestamp(pSignerInfo, &pSignedTimestamp);
+ if (pSignedTime)
+ {
+ RTTIMESPEC ThisValidationTime;
+ if (RT_LIKELY(RTTimeImplode(&ThisValidationTime, &pSignedTime->Time)))
+ {
+ rc = VINF_SUCCESS;
+ if (!(fFlags & RTCRPKCS7VERIFY_SD_F_USE_MS_TIMESTAMP_UNVERIFIED))
+ rc = RTCrPkcs7VerifySignedData(pSignedTimestamp,
+ fFlags | RTCRPKCS7VERIFY_SD_F_IGNORE_MS_TIMESTAMP
+ | RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING,
+ hAdditionalCerts, hTrustedCerts, &ThisValidationTime,
+ pfnVerifyCert, pvUser, pErrInfo);
+
+ if (RT_SUCCESS(rc))
+ rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts,
+ hTrustedCerts, &ThisValidationTime,
+ pfnVerifyCert, fPrimaryVccFlags, pvUser, pErrInfo);
+ fDone = RT_SUCCESS(rc)
+ || (fFlags & RTCRPKCS7VERIFY_SD_F_ALWAYS_USE_MS_TIMESTAMP_IF_PRESENT);
+ if ((fFlags & RTCRPKCS7VERIFY_SD_F_UPDATE_VALIDATION_TIME) && fDone)
+ *(PRTTIMESPEC)pValidationTime = ThisValidationTime;
+ }
+ else
+ {
+ rc = RTErrInfoSet(pErrInfo, VERR_INTERNAL_ERROR_3, "RTTimeImplode failed");
+ fDone = true;
+ }
+
+ }
+ }
+
+ /*
+ * No valid signing time found, use the one specified instead.
+ */
+ if (!fDone)
+ rc = rtCrPkcs7VerifySignerInfo(pSignerInfo, pSignedData, hThisDigest, fFlags, hAdditionalCerts, hTrustedCerts,
+ &GivenValidationTime, pfnVerifyCert, fPrimaryVccFlags, pvUser, pErrInfo);
+ RTCrDigestRelease(hThisDigest);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_DIGEST_CALC_ERROR,
+ "Hashing content failed unexpectedly (i=%u): %Rrc", i, rc);
+
+ /* Clean up digests. */
+ i = cDigests;
+ }
+ while (i-- > 0)
+ {
+ int rc2 = RTCrDigestRelease(ahDigests[i]);
+ AssertRC(rc2);
+ }
+
+
+#ifdef IPRT_WITH_OPENSSL
+ /*
+ * Verify using OpenSSL and combine the results (should be identical).
+ */
+ /** @todo figure out how to verify MS timstamp signatures using OpenSSL. */
+ if (fFlags & RTCRPKCS7VERIFY_SD_F_USAGE_TIMESTAMPING)
+ return rc;
+ /** @todo figure out if we can verify just one signer info item using OpenSSL. */
+ if (!(fFlags & RTCRPKCS7VERIFY_SD_F_HAS_SIGNER_INDEX) && pSignedData->SignerInfos.cItems > 1)
+ return rc;
+
+ int rcOssl = rtCrPkcs7VerifySignedDataUsingOpenSsl(pContentInfo, fFlags, hAdditionalCerts, hTrustedCerts,
+ pvContent, cbContent, RT_SUCCESS(rc) ? pErrInfo : NULL);
+ if (RT_SUCCESS(rcOssl) && RT_SUCCESS(rc))
+ return rc;
+// AssertMsg(RT_FAILURE_NP(rcOssl) && RT_FAILURE_NP(rc), ("%Rrc, %Rrc\n", rcOssl, rc));
+ if (RT_FAILURE(rc))
+ return rc;
+ return rcOssl;
+#else
+ return rc;
+#endif
+}
+
+
+RTDECL(int) RTCrPkcs7VerifySignedData(PCRTCRPKCS7CONTENTINFO pContentInfo, uint32_t fFlags,
+ RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts,
+ PCRTTIMESPEC pValidationTime, PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, void *pvUser,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * Find the content and pass it on to common worker.
+ */
+ if (!RTCrPkcs7ContentInfo_IsSignedData(pContentInfo))
+ return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_NOT_SIGNED_DATA, "Not PKCS #7 SignedData.");
+
+ /* Exactly what the content is, is for some stupid reason unnecessarily complicated. */
+ PCRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData;
+ void const *pvContent = pSignedData->ContentInfo.Content.Asn1Core.uData.pv;
+ uint32_t cbContent = pSignedData->ContentInfo.Content.Asn1Core.cb;
+ if (pSignedData->ContentInfo.Content.pEncapsulated)
+ {
+ pvContent = pSignedData->ContentInfo.Content.pEncapsulated->uData.pv;
+ cbContent = pSignedData->ContentInfo.Content.pEncapsulated->cb;
+ }
+
+ return rtCrPkcs7VerifySignedDataEx(pContentInfo, fFlags, hAdditionalCerts, hTrustedCerts, pValidationTime,
+ pfnVerifyCert, pvUser, pvContent, cbContent, pErrInfo);
+}
+
+
+RTDECL(int) RTCrPkcs7VerifySignedDataWithExternalData(PCRTCRPKCS7CONTENTINFO pContentInfo, uint32_t fFlags,
+ RTCRSTORE hAdditionalCerts, RTCRSTORE hTrustedCerts,
+ PCRTTIMESPEC pValidationTime,
+ PFNRTCRPKCS7VERIFYCERTCALLBACK pfnVerifyCert, void *pvUser,
+ void const *pvData, size_t cbData, PRTERRINFO pErrInfo)
+{
+ /*
+ * Require 'data' as inner content type.
+ */
+ if (!RTCrPkcs7ContentInfo_IsSignedData(pContentInfo))
+ return RTErrInfoSet(pErrInfo, VERR_CR_PKCS7_NOT_SIGNED_DATA, "Not PKCS #7 SignedData.");
+ PCRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData;
+
+ if (RTAsn1ObjId_CompareWithString(&pSignedData->ContentInfo.ContentType, RTCR_PKCS7_DATA_OID) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_NOT_DATA,
+ "The signedData content type is %s, expected 'data' (%s)",
+ pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID);
+
+ return rtCrPkcs7VerifySignedDataEx(pContentInfo, fFlags, hAdditionalCerts, hTrustedCerts, pValidationTime,
+ pfnVerifyCert, pvUser, pvData, cbData, pErrInfo);
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs8-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/pkcs8-asn1-decoder.cpp
new file mode 100644
index 00000000..e6e8c400
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs8-asn1-decoder.cpp
@@ -0,0 +1,53 @@
+/* $Id: pkcs8-asn1-decoder.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#8, Decoder for ASN.1.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs8.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#include "pkcs8-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs8-core.cpp b/src/VBox/Runtime/common/crypto/pkcs8-core.cpp
new file mode 100644
index 00000000..bdf183c9
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs8-core.cpp
@@ -0,0 +1,54 @@
+/* $Id: pkcs8-core.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#8, Core APIs.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs8.h>
+
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "pkcs8-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-core.h>
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs8-init.cpp b/src/VBox/Runtime/common/crypto/pkcs8-init.cpp
new file mode 100644
index 00000000..a6c5cd1a
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs8-init.cpp
@@ -0,0 +1,53 @@
+/* $Id: pkcs8-init.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#8, Initialization API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs8.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#include "pkcs8-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-init.h>
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs8-internal.h b/src/VBox/Runtime/common/crypto/pkcs8-internal.h
new file mode 100644
index 00000000..e956e7f4
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs8-internal.h
@@ -0,0 +1,46 @@
+/* $Id: pkcs8-internal.h $ */
+/** @file
+ * IPRT - Crypto - PKCS \#8, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_pkcs8_internal_h
+#define IPRT_INCLUDED_SRC_common_crypto_pkcs8_internal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/pkcs8-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_pkcs8_internal_h */
diff --git a/src/VBox/Runtime/common/crypto/pkcs8-sanity.cpp b/src/VBox/Runtime/common/crypto/pkcs8-sanity.cpp
new file mode 100644
index 00000000..78ca611c
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs8-sanity.cpp
@@ -0,0 +1,53 @@
+/* $Id: pkcs8-sanity.cpp $ */
+/** @file
+ * IPRT - Crypto - PKCS \#8, Sanity Checkers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkcs8.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#include "pkcs8-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/crypto/pkcs8-template.h b/src/VBox/Runtime/common/crypto/pkcs8-template.h
new file mode 100644
index 00000000..95d5ecc1
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkcs8-template.h
@@ -0,0 +1,69 @@
+/* $Id: pkcs8-template.h $ */
+/** @file
+ * IPRT - Crypto - PKCS \#8, Code Generator Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+/*
+ * PKCS\#8 Private key info
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS8PRIVATEKEYINFO
+#define RTASN1TMPL_EXT_NAME RTCrPkcs8PrivateKeyInfo
+#define RTASN1TMPL_INT_NAME RTCrPkcs8PrivateKeyInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Version, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( PrivateKeyAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER( PrivateKey, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_MEMBER_OPT_ITAG( Attributes, RTCRPKCS7ATTRIBUTES, RTCrPkcs7Attributes, 0);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Encrypted private key info
+ */
+#define RTASN1TMPL_TYPE RTCRPKCS8ENCRYPTEDPRIVATEKEYINFO
+#define RTASN1TMPL_EXT_NAME RTCrPkcs8EncryptedPrivateKeyInfo
+#define RTASN1TMPL_INT_NAME rtCrPkcs8EncryptedPrivateKeyInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( EncryptionAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER( EncryptedData, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/crypto/pkix-sign.cpp b/src/VBox/Runtime/common/crypto/pkix-sign.cpp
new file mode 100644
index 00000000..8024b8cf
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkix-sign.cpp
@@ -0,0 +1,262 @@
+/* $Id: pkix-sign.cpp $ */
+/** @file
+ * IPRT - Crypto - Public Key Infrastructure API, Verification.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkix.h>
+
+#include <iprt/alloca.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/key.h>
+
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/evp.h>
+# include <openssl/rsa.h>
+# include "internal/openssl-post.h"
+# ifndef OPENSSL_VERSION_NUMBER
+# error "Missing OPENSSL_VERSION_NUMBER!"
+# endif
+#endif
+
+
+#if 0
+RTDECL(int) RTCrPkixPubKeySignData(PCRTASN1OBJID pAlgorithm, PCRTASN1DYNTYPE pParameters, PCRTASN1BITSTRING pPrivateKey,
+ PCRTASN1BITSTRING pSignatureValue, const void *pvData, size_t cbData, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate the digest related inputs.
+ */
+ AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData > 0, VERR_INVALID_PARAMETER);
+
+ /*
+ * Digest the data and call the other API.
+ */
+ RTCRDIGEST hDigest;
+ int rc = RTCrDigestCreateByObjId(&hDigest, pAlgorithm);
+ if (RT_SUCCESS(rcIprt))
+ {
+ rc = RTCrDigestUpdate(hDigest, pvData, cbData);
+ if (RT_SUCCESS(rcIprt))
+ rc = RTCrPkixPubKeySignDigest(pAlgorithm, pParameters, pPrivateKey, pvSignedDigest, cbSignedDigest, hDigest, pErrInfo);
+ else
+ RTErrInfoSet(pErrInfo, rcIprt, "RTCrDigestUpdate failed");
+ RTCrDigestRelease(hDigest);
+ }
+ else
+ RTErrInfoSetF(pErrInfo, rcIprt, "Unknown digest algorithm [IPRT]: %s", pAlgorithm->szObjId);
+ return rc;
+}
+#endif
+
+
+RTDECL(int) RTCrPkixPubKeySignDigest(PCRTASN1OBJID pAlgorithm, RTCRKEY hPrivateKey, PCRTASN1DYNTYPE pParameters,
+ RTCRDIGEST hDigest, uint32_t fFlags,
+ void *pvSignature, size_t *pcbSignature, PRTERRINFO pErrInfo)
+{
+ /*
+ * Valid input.
+ */
+ AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER);
+
+ if (pParameters)
+ {
+ AssertPtrReturn(pParameters, VERR_INVALID_POINTER);
+ if (pParameters->enmType == RTASN1TYPE_NULL)
+ pParameters = NULL;
+ }
+
+ AssertPtrReturn(hPrivateKey, VERR_INVALID_POINTER);
+ Assert(RTCrKeyHasPrivatePart(hPrivateKey));
+
+ AssertPtrReturn(pcbSignature, VERR_INVALID_PARAMETER);
+ size_t cbSignature = *pcbSignature;
+ if (cbSignature)
+ AssertPtrReturn(pvSignature, VERR_INVALID_POINTER);
+ else
+ pvSignature = NULL;
+
+ AssertPtrReturn(hDigest, VERR_INVALID_HANDLE);
+
+ AssertReturn(fFlags == 0, VERR_INVALID_FLAGS);
+
+ /*
+ * Parameters are not currently supported (openssl code path).
+ */
+ if (pParameters)
+ return RTErrInfoSet(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_PARAMS_NOT_IMPL,
+ "Cipher algorithm parameters are not yet supported.");
+
+ /*
+ * Sign using IPRT.
+ */
+ RTCRPKIXSIGNATURE hSignature;
+ int rcIprt = RTCrPkixSignatureCreateByObjId(&hSignature, pAlgorithm, hPrivateKey, pParameters, true /*fSigning*/);
+ if (RT_FAILURE(rcIprt))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN,
+ "Unknown private key algorithm [IPRT %Rrc]: %s", rcIprt, pAlgorithm->szObjId);
+
+ rcIprt = RTCrPkixSignatureSign(hSignature, hDigest, pvSignature, pcbSignature);
+ if (RT_FAILURE(rcIprt))
+ RTErrInfoSet(pErrInfo, rcIprt, "RTCrPkixSignatureSign failed");
+
+ RTCrPkixSignatureRelease(hSignature);
+
+ /*
+ * Sign using OpenSSL EVP if we can.
+ */
+#if defined(IPRT_WITH_OPENSSL) \
+ && (OPENSSL_VERSION_NUMBER > 0x10000000L) /* 0.9.8 doesn't seem to have EVP_PKEY_CTX_set_signature_md. */
+
+ /* Make sure the algorithm includes the digest and isn't just RSA, ECDSA or similar. */
+ const char *pszAlgObjId = RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(pAlgorithm->szObjId,
+ RTCrDigestGetAlgorithmOid(hDigest));
+ AssertMsgStmt(pszAlgObjId, ("enc=%s hash=%s\n", pAlgorithm->szObjId, RTCrDigestGetAlgorithmOid(hDigest)),
+ pszAlgObjId = RTCrDigestGetAlgorithmOid(hDigest));
+
+ /* Create an EVP private key. */
+ EVP_PKEY *pEvpPrivateKey = NULL;
+ const EVP_MD *pEvpMdType = NULL;
+ int rcOssl = rtCrKeyToOpenSslKeyEx(hPrivateKey, false /*fNeedPublic*/, pszAlgObjId,
+ (void **)&pEvpPrivateKey, (const void **)&pEvpMdType, pErrInfo);
+ if (RT_SUCCESS(rcOssl))
+ {
+ /* Create an EVP Private key context we can use to validate the digest. */
+ EVP_PKEY_CTX *pEvpPKeyCtx = EVP_PKEY_CTX_new(pEvpPrivateKey, NULL);
+ if (pEvpPKeyCtx)
+ {
+ rcOssl = EVP_PKEY_sign_init(pEvpPKeyCtx);
+ if (rcOssl > 0)
+ {
+ rcOssl = EVP_PKEY_CTX_set_rsa_padding(pEvpPKeyCtx, RSA_PKCS1_PADDING);
+ if (rcOssl > 0)
+ {
+ rcOssl = EVP_PKEY_CTX_set_signature_md(pEvpPKeyCtx, pEvpMdType);
+ if (rcOssl > 0)
+ {
+ /* Allocate a signature buffer. */
+ unsigned char *pbOsslSignature = NULL;
+ void *pvOsslSignatureFree = NULL;
+ size_t cbOsslSignature = cbSignature;
+ if (cbOsslSignature > 0)
+ {
+ if (cbOsslSignature < _1K)
+ pbOsslSignature = (unsigned char *)alloca(cbOsslSignature);
+ else
+ {
+ pbOsslSignature = (unsigned char *)RTMemTmpAlloc(cbOsslSignature);
+ pvOsslSignatureFree = pbOsslSignature;
+ }
+ }
+ if (cbOsslSignature == 0 || pbOsslSignature != NULL)
+ {
+ /* Get the digest from hDigest and sign it. */
+ rcOssl = EVP_PKEY_sign(pEvpPKeyCtx,
+ pbOsslSignature,
+ &cbOsslSignature,
+ (const unsigned char *)RTCrDigestGetHash(hDigest),
+ RTCrDigestGetHashSize(hDigest));
+ if (rcOssl > 0)
+ {
+ /* Compare the result. The memcmp assums no random padding bits. */
+ rcOssl = VINF_SUCCESS;
+ AssertMsgStmt(cbOsslSignature == *pcbSignature,
+ ("cbOsslSignature=%#x, iprt %#x\n", cbOsslSignature, *pcbSignature),
+ rcOssl = VERR_CR_PKIX_OSSL_VS_IPRT_SIGNATURE_SIZE);
+ AssertMsgStmt( pbOsslSignature == NULL
+ || rcOssl != VINF_SUCCESS
+ || memcmp(pbOsslSignature, pvSignature, cbOsslSignature) == 0,
+ ("OpenSSL: %.*Rhxs\n"
+ "IPRT: %.*Rhxs\n",
+ cbOsslSignature, pbOsslSignature, *pcbSignature, pvSignature),
+ rcOssl = VERR_CR_PKIX_OSSL_VS_IPRT_SIGNATURE);
+ if (!pbOsslSignature && rcOssl == VINF_SUCCESS)
+ rcOssl = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_SIGN_FINAL_FAILED,
+ "EVP_PKEY_sign failed (%d)", rcOssl);
+ if (pvOsslSignatureFree)
+ RTMemTmpFree(pvOsslSignatureFree);
+ }
+ else
+ rcOssl = VERR_NO_TMP_MEMORY;
+ }
+ else
+ rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR,
+ "EVP_PKEY_CTX_set_signature_md failed (%d)", rcOssl);
+ }
+ else
+ rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_RSA_PAD_ERROR,
+ "EVP_PKEY_CTX_set_rsa_padding failed (%d)", rcOssl);
+ }
+ else
+ rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR,
+ "EVP_PKEY_verify_init failed (%d)", rcOssl);
+ EVP_PKEY_CTX_free(pEvpPKeyCtx);
+ }
+ else
+ rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, "EVP_PKEY_CTX_new failed");
+ EVP_PKEY_free(pEvpPrivateKey);
+ }
+
+ /*
+ * Check the result.
+ */
+ if ( (RT_SUCCESS(rcIprt) && RT_SUCCESS(rcOssl))
+ || (RT_FAILURE_NP(rcIprt) && RT_FAILURE_NP(rcOssl))
+ || (RT_SUCCESS(rcIprt) && rcOssl == VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP) )
+ return rcIprt;
+ AssertMsgFailed(("rcIprt=%Rrc rcOssl=%Rrc\n", rcIprt, rcOssl));
+ if (RT_FAILURE_NP(rcOssl))
+ return rcOssl;
+#endif /* IPRT_WITH_OPENSSL */
+
+ return rcIprt;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pkix-signature-builtin.cpp b/src/VBox/Runtime/common/crypto/pkix-signature-builtin.cpp
new file mode 100644
index 00000000..56c62064
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkix-signature-builtin.cpp
@@ -0,0 +1,153 @@
+/* $Id: pkix-signature-builtin.cpp $ */
+/** @file
+ * IPRT - Crypto - Public Key Signature Schemas, Built-in providers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkix.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/evp.h>
+# include "internal/openssl-post.h"
+#endif
+
+#include "pkix-signature-builtin.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Array of built in message digest vtables.
+ */
+static PCRTCRPKIXSIGNATUREDESC const g_apPkixSignatureDescriptors[] =
+{
+ &g_rtCrPkixSigningHashWithRsaDesc,
+#ifdef IPRT_WITH_OPENSSL
+ &g_rtCrPkixSigningHashWithEcdsaDesc,
+#endif
+};
+
+
+
+PCRTCRPKIXSIGNATUREDESC RTCrPkixSignatureFindByObjIdString(const char *pszObjId, void **ppvOpaque)
+{
+ if (ppvOpaque)
+ *ppvOpaque = NULL;
+
+ /*
+ * Primary OIDs.
+ */
+ uint32_t i = RT_ELEMENTS(g_apPkixSignatureDescriptors);
+ while (i-- > 0)
+ if (strcmp(g_apPkixSignatureDescriptors[i]->pszObjId, pszObjId) == 0)
+ return g_apPkixSignatureDescriptors[i];
+
+ /*
+ * Alias OIDs.
+ */
+ i = RT_ELEMENTS(g_apPkixSignatureDescriptors);
+ while (i-- > 0)
+ {
+ const char * const *ppszAliases = g_apPkixSignatureDescriptors[i]->papszObjIdAliases;
+ if (ppszAliases)
+ for (; *ppszAliases; ppszAliases++)
+ if (strcmp(*ppszAliases, pszObjId) == 0)
+ return g_apPkixSignatureDescriptors[i];
+ }
+
+#if 0//def IPRT_WITH_OPENSSL
+ /*
+ * Try EVP and see if it knows the algorithm.
+ */
+ if (ppvOpaque)
+ {
+ rtCrOpenSslInit();
+ int iAlgoNid = OBJ_txt2nid(pszObjId);
+ if (iAlgoNid != NID_undef)
+ {
+ const char *pszAlogSn = OBJ_nid2sn(iAlgoNid);
+ const EVP_MD *pEvpMdType = EVP_get_digestbyname(pszAlogSn);
+ if (pEvpMdType)
+ {
+ /*
+ * Return the OpenSSL provider descriptor and the EVP_MD address.
+ */
+ Assert(pEvpMdType->md_size);
+ *ppvOpaque = (void *)pEvpMdType;
+ return &g_rtCrPkixSignatureOpenSslDesc;
+ }
+ }
+ }
+#endif
+ return NULL;
+}
+
+
+PCRTCRPKIXSIGNATUREDESC RTCrPkixSignatureFindByObjId(PCRTASN1OBJID pObjId, void **ppvOpaque)
+{
+ return RTCrPkixSignatureFindByObjIdString(pObjId->szObjId, ppvOpaque);
+}
+
+
+RTDECL(int) RTCrPkixSignatureCreateByObjIdString(PRTCRPKIXSIGNATURE phSignature, const char *pszObjId,
+ RTCRKEY hKey, PCRTASN1DYNTYPE pParams, bool fSigning)
+{
+ void *pvOpaque;
+ PCRTCRPKIXSIGNATUREDESC pDesc = RTCrPkixSignatureFindByObjIdString(pszObjId, &pvOpaque);
+ if (pDesc)
+ return RTCrPkixSignatureCreate(phSignature, pDesc, pvOpaque, fSigning, hKey, pParams);
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTCrPkixSignatureCreateByObjId(PRTCRPKIXSIGNATURE phSignature, PCRTASN1OBJID pObjId,
+ RTCRKEY hKey, PCRTASN1DYNTYPE pParams, bool fSigning)
+{
+ void *pvOpaque;
+ PCRTCRPKIXSIGNATUREDESC pDesc = RTCrPkixSignatureFindByObjId(pObjId, &pvOpaque);
+ if (pDesc)
+ return RTCrPkixSignatureCreate(phSignature, pDesc, pvOpaque, fSigning, hKey, pParams);
+ return VERR_NOT_FOUND;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pkix-signature-builtin.h b/src/VBox/Runtime/common/crypto/pkix-signature-builtin.h
new file mode 100644
index 00000000..0d8f297c
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkix-signature-builtin.h
@@ -0,0 +1,51 @@
+/* $Id: pkix-signature-builtin.h $ */
+/** @file
+ * IPRT - Crypto - Public Key Signature Schemas, Built-in providers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_pkix_signature_builtin_h
+#define IPRT_INCLUDED_SRC_common_crypto_pkix_signature_builtin_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/crypto/pkix.h>
+
+extern DECL_HIDDEN_DATA(RTCRPKIXSIGNATUREDESC const) g_rtCrPkixSigningHashWithRsaDesc;
+#ifdef IPRT_WITH_OPENSSL
+extern DECL_HIDDEN_DATA(RTCRPKIXSIGNATUREDESC const) g_rtCrPkixSigningHashWithEcdsaDesc;
+#endif
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_pkix_signature_builtin_h */
+
diff --git a/src/VBox/Runtime/common/crypto/pkix-signature-core.cpp b/src/VBox/Runtime/common/crypto/pkix-signature-core.cpp
new file mode 100644
index 00000000..ab755a17
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkix-signature-core.cpp
@@ -0,0 +1,294 @@
+/* $Id: pkix-signature-core.cpp $ */
+/** @file
+ * IPRT - Crypto - Public Key Signature Schema Algorithm, Core API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkix.h>
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/key.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Generic public key signature scheme instance.
+ */
+typedef struct RTCRPKIXSIGNATUREINT
+{
+ /** Magic value (RTCRPKIXSIGNATUREINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Pointer to the message digest descriptor. */
+ PCRTCRPKIXSIGNATUREDESC pDesc;
+ /** Key being used (referrenced of course). */
+ RTCRKEY hKey;
+ /** The operation this instance is licensed for. */
+ bool fSigning;
+ /** State. */
+ uint32_t uState;
+
+ /** Opaque data specific to the message digest algorithm, size given by
+ * RTCRPKIXSIGNATUREDESC::cbState. */
+ uint8_t abState[1];
+} RTCRPKIXSIGNATUREINT;
+AssertCompileMemberAlignment(RTCRPKIXSIGNATUREINT, abState, 8);
+/** Pointer to a public key algorithm instance. */
+typedef RTCRPKIXSIGNATUREINT *PRTCRPKIXSIGNATUREINT;
+
+/** Magic value for RTCRPKIXSIGNATUREINT::u32Magic (Baley Withfield Diffie). */
+#define RTCRPKIXSIGNATUREINT_MAGIC UINT32_C(0x19440605)
+
+/** @name RTCRPKIXSIGNATUREINT::uState values.
+ * @{ */
+/** Ready to go. */
+#define RTCRPKIXSIGNATURE_STATE_READY UINT32_C(1)
+/** Need reset. */
+#define RTCRPKIXSIGNATURE_STATE_DONE UINT32_C(2)
+/** Busted state, can happen after re-init. */
+#define RTCRPKIXSIGNATURE_STATE_BUSTED UINT32_C(3)
+/** @} */
+
+
+
+RTDECL(int) RTCrPkixSignatureCreate(PRTCRPKIXSIGNATURE phSignature, PCRTCRPKIXSIGNATUREDESC pDesc, void *pvOpaque,
+ bool fSigning, RTCRKEY hKey, PCRTASN1DYNTYPE pParams)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(phSignature, VERR_INVALID_POINTER);
+ AssertPtrReturn(pDesc, VERR_INVALID_POINTER);
+ uint32_t cKeyRefs = RTCrKeyRetain(hKey);
+ AssertReturn(cKeyRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Instantiate the algorithm for the given operation.
+ */
+ int rc = VINF_SUCCESS;
+ PRTCRPKIXSIGNATUREINT pThis = (PRTCRPKIXSIGNATUREINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCRPKIXSIGNATUREINT,
+ abState[pDesc->cbState]));
+ if (pThis)
+ {
+ pThis->u32Magic = RTCRPKIXSIGNATUREINT_MAGIC;
+ pThis->cRefs = 1;
+ pThis->pDesc = pDesc;
+ pThis->fSigning = fSigning;
+ pThis->uState = RTCRPKIXSIGNATURE_STATE_READY;
+ pThis->hKey = hKey;
+ if (pDesc->pfnInit)
+ rc = pDesc->pfnInit(pDesc, pThis->abState, pvOpaque, fSigning, hKey, pParams);
+ else
+ rc = RTCrKeyVerifyParameterCompatibility(hKey, pParams, true /*fForSignature*/,
+ NULL /*pAlgorithm*/, NULL /*pErrInfo*/);
+ if (RT_SUCCESS(rc))
+ {
+ *phSignature = pThis;
+ return VINF_SUCCESS;
+ }
+ pThis->u32Magic = 0;
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ RTCrKeyRelease(hKey);
+ return rc;
+
+}
+
+
+RTDECL(uint32_t) RTCrPkixSignatureRetain(RTCRPKIXSIGNATURE hSignature)
+{
+ PRTCRPKIXSIGNATUREINT pThis = hSignature;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRPKIXSIGNATUREINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs < 64);
+ return cRefs;
+}
+
+
+/**
+ * Destructor worker.
+ */
+static uint32_t rtCrPkixSignatureDestructor(PRTCRPKIXSIGNATUREINT pThis)
+{
+ pThis->u32Magic = ~RTCRPKIXSIGNATUREINT_MAGIC;
+ if (pThis->pDesc->pfnDelete)
+ pThis->pDesc->pfnDelete(pThis->pDesc, pThis->abState, pThis->fSigning);
+
+ RTCrKeyRelease(pThis->hKey);
+ pThis->hKey = NIL_RTCRKEY;
+
+ size_t cbToWipe = RT_UOFFSETOF_DYN(RTCRPKIXSIGNATUREINT, abState[pThis->pDesc->cbState]);
+ RTMemWipeThoroughly(pThis, cbToWipe, 6);
+
+ RTMemFree(pThis);
+ return 0;
+}
+
+
+RTDECL(uint32_t) RTCrPkixSignatureRelease(RTCRPKIXSIGNATURE hSignature)
+{
+ PRTCRPKIXSIGNATUREINT pThis = hSignature;
+ if (pThis == NIL_RTCRPKIXSIGNATURE)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRPKIXSIGNATUREINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 64);
+ if (!cRefs)
+ return rtCrPkixSignatureDestructor(pThis);
+ return cRefs;
+}
+
+
+/**
+ * Resets the signature provider instance prior to a new signing or verification
+ * opartion.
+ *
+ * @returns IPRT status code.
+ * @param pThis The instance to reset.
+ */
+static int rtCrPkxiSignatureReset(PRTCRPKIXSIGNATUREINT pThis)
+{
+ if (pThis->uState == RTCRPKIXSIGNATURE_STATE_DONE)
+ {
+ if (pThis->pDesc->pfnReset)
+ {
+ int rc = pThis->pDesc->pfnReset(pThis->pDesc, pThis->abState, pThis->fSigning);
+ if (RT_FAILURE(rc))
+ {
+ pThis->uState = RTCRPKIXSIGNATURE_STATE_BUSTED;
+ return rc;
+ }
+ }
+ pThis->uState = RTCRPKIXSIGNATURE_STATE_READY;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrPkixSignatureVerify(RTCRPKIXSIGNATURE hSignature, RTCRDIGEST hDigest,
+ void const *pvSignature, size_t cbSignature)
+{
+ PRTCRPKIXSIGNATUREINT pThis = hSignature;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRPKIXSIGNATUREINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!pThis->fSigning, VERR_INVALID_FUNCTION);
+ AssertReturn(pThis->uState == RTCRPKIXSIGNATURE_STATE_READY || pThis->uState == RTCRPKIXSIGNATURE_STATE_DONE, VERR_INVALID_STATE);
+
+ uint32_t cRefs = RTCrDigestRetain(hDigest);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ int rc = rtCrPkxiSignatureReset(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pThis->pDesc->pfnVerify(pThis->pDesc, pThis->abState, pThis->hKey, hDigest, pvSignature, cbSignature);
+ pThis->uState = RTCRPKIXSIGNATURE_STATE_DONE;
+ }
+
+ RTCrDigestRelease(hDigest);
+ return rc;
+}
+
+
+RTDECL(int) RTCrPkixSignatureVerifyBitString(RTCRPKIXSIGNATURE hSignature, RTCRDIGEST hDigest, PCRTASN1BITSTRING pSignature)
+{
+ /*
+ * Just unpack it and pass it on to the lower level API.
+ */
+ AssertPtrReturn(pSignature, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1BitString_IsPresent(pSignature), VERR_INVALID_PARAMETER);
+ uint32_t cbData = RTASN1BITSTRING_GET_BYTE_SIZE(pSignature);
+ void const *pvData = RTASN1BITSTRING_GET_BIT0_PTR(pSignature);
+ AssertPtrReturn(pvData, VERR_INVALID_PARAMETER);
+
+ return RTCrPkixSignatureVerify(hSignature, hDigest, pvData, cbData);
+}
+
+
+RTDECL(int) RTCrPkixSignatureVerifyOctetString(RTCRPKIXSIGNATURE hSignature, RTCRDIGEST hDigest, PCRTASN1OCTETSTRING pSignature)
+{
+ /*
+ * Just unpack it and pass it on to the lower level API.
+ */
+ AssertPtrReturn(pSignature, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1OctetString_IsPresent(pSignature), VERR_INVALID_PARAMETER);
+ uint32_t cbData = pSignature->Asn1Core.cb;
+ void const *pvData = pSignature->Asn1Core.uData.pv;
+ AssertPtrReturn(pvData, VERR_INVALID_PARAMETER);
+
+ return RTCrPkixSignatureVerify(hSignature, hDigest, pvData, cbData);
+}
+
+
+RTDECL(int) RTCrPkixSignatureSign(RTCRPKIXSIGNATURE hSignature, RTCRDIGEST hDigest,
+ void *pvSignature, size_t *pcbSignature)
+{
+ PRTCRPKIXSIGNATUREINT pThis = hSignature;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRPKIXSIGNATUREINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->fSigning, VERR_INVALID_FUNCTION);
+ AssertReturn(pThis->uState == RTCRPKIXSIGNATURE_STATE_READY || pThis->uState == RTCRPKIXSIGNATURE_STATE_DONE, VERR_INVALID_STATE);
+
+ uint32_t cRefs = RTCrDigestRetain(hDigest);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ int rc = rtCrPkxiSignatureReset(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pThis->pDesc->pfnSign(pThis->pDesc, pThis->abState, pThis->hKey, hDigest, pvSignature, pcbSignature);
+ if (rc != VERR_BUFFER_OVERFLOW)
+ pThis->uState = RTCRPKIXSIGNATURE_STATE_DONE;
+ }
+
+ RTCrDigestRelease(hDigest);
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pkix-signature-ossl.cpp b/src/VBox/Runtime/common/crypto/pkix-signature-ossl.cpp
new file mode 100644
index 00000000..17af5a15
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkix-signature-ossl.cpp
@@ -0,0 +1,288 @@
+/* $Id: pkix-signature-ossl.cpp $ */
+/** @file
+ * IPRT - Crypto - Public Key Signature Schema Algorithm, ECDSA Providers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef IPRT_WITH_OPENSSL /* whole file */
+# define LOG_GROUP RTLOGGROUP_CRYPTO
+# include "internal/iprt.h"
+# include <iprt/crypto/rsa.h>
+
+# include <iprt/bignum.h>
+# include <iprt/err.h>
+# include <iprt/log.h>
+# include <iprt/mem.h>
+# include <iprt/string.h>
+# include <iprt/crypto/digest.h>
+# include <iprt/crypto/pkix.h>
+
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/evp.h>
+# include "internal/openssl-post.h"
+# ifndef OPENSSL_VERSION_NUMBER
+# error "Missing OPENSSL_VERSION_NUMBER!"
+# endif
+
+# include "pkix-signature-builtin.h"
+# include "rsa-internal.h"
+# include "key-internal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * OpenSSL EVP signature provider instance.
+ */
+typedef struct RTCRPKIXSIGNATUREOSSLEVP
+{
+ /** Set if we're signing, clear if verifying. */
+ bool fSigning;
+} RTCRPKIXSIGNATUREOSSLEVP;
+/** Pointer to an OpenSSL EVP signature provider instance. */
+typedef RTCRPKIXSIGNATUREOSSLEVP *PRTCRPKIXSIGNATUREOSSLEVP;
+
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnInit} */
+static DECLCALLBACK(int) rtCrPkixSignatureOsslEvp_Init(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, void *pvOpaque,
+ bool fSigning, RTCRKEY hKey, PCRTASN1DYNTYPE pParams)
+{
+ RT_NOREF_PV(pvOpaque);
+
+ RTCRKEYTYPE enmKeyType = RTCrKeyGetType(hKey);
+ if (strcmp(pDesc->pszObjId, RTCR_X962_ECDSA_OID) == 0)
+ {
+ if (fSigning)
+ AssertReturn(enmKeyType == RTCRKEYTYPE_ECDSA_PRIVATE, VERR_CR_PKIX_NOT_ECDSA_PRIVATE_KEY);
+ else
+ AssertReturn(enmKeyType == RTCRKEYTYPE_ECDSA_PUBLIC, VERR_CR_PKIX_NOT_ECDSA_PUBLIC_KEY);
+ }
+ else if (strcmp(pDesc->pszObjId, RTCR_PKCS1_RSA_OID) == 0)
+ {
+ if (fSigning)
+ AssertReturn(enmKeyType == RTCRKEYTYPE_RSA_PRIVATE, VERR_CR_PKIX_NOT_RSA_PRIVATE_KEY);
+ else
+ AssertReturn(enmKeyType == RTCRKEYTYPE_RSA_PUBLIC, VERR_CR_PKIX_NOT_RSA_PUBLIC_KEY);
+ }
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ int rc = RTCrKeyVerifyParameterCompatibility(hKey, pParams, true /*fForSignature*/, NULL /*pAlgorithm*/, NULL /*pErrInfo*/);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Locate the EVP_MD for the algorithm.
+ */
+ rtCrOpenSslInit();
+ int iAlgoNid = OBJ_txt2nid(pDesc->pszObjId);
+ if (iAlgoNid != NID_undef)
+ {
+ PRTCRPKIXSIGNATUREOSSLEVP pThis = (PRTCRPKIXSIGNATUREOSSLEVP)pvState;
+ pThis->fSigning = fSigning;
+ return VINF_SUCCESS;
+ }
+ return VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP;
+}
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnReset} */
+static DECLCALLBACK(int) rtCrPkixSignatureOsslEvp_Reset(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, bool fSigning)
+{
+ PRTCRPKIXSIGNATUREOSSLEVP pThis = (PRTCRPKIXSIGNATUREOSSLEVP)pvState;
+ RT_NOREF_PV(fSigning); RT_NOREF_PV(pDesc);
+ Assert(pThis->fSigning == fSigning); NOREF(pThis);
+ return VINF_SUCCESS;
+}
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnDelete} */
+static DECLCALLBACK(void) rtCrPkixSignatureOsslEvp_Delete(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, bool fSigning)
+{
+ PRTCRPKIXSIGNATUREOSSLEVP pThis = (PRTCRPKIXSIGNATUREOSSLEVP)pvState;
+ RT_NOREF_PV(fSigning); RT_NOREF_PV(pDesc);
+ Assert(pThis->fSigning == fSigning);
+ NOREF(pThis);
+}
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnVerify} */
+static DECLCALLBACK(int) rtCrPkixSignatureOsslEvp_Verify(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, RTCRKEY hKey,
+ RTCRDIGEST hDigest, void const *pvSignature, size_t cbSignature)
+{
+ PRTCRPKIXSIGNATUREOSSLEVP pThis = (PRTCRPKIXSIGNATUREOSSLEVP)pvState;
+ Assert(!pThis->fSigning);
+ RT_NOREF_PV(pThis);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000
+ PRTERRINFO const pErrInfo = NULL;
+
+ /*
+ * Get the hash before we do anything that needs cleaning up.
+ */
+ int rc = RTCrDigestFinal(hDigest, NULL, 0);
+ AssertRCReturn(rc, rc);
+ uint8_t const * const pbDigest = RTCrDigestGetHash(hDigest);
+ AssertReturn(pbDigest, VERR_INTERNAL_ERROR_3);
+
+ uint32_t const cbDigest = RTCrDigestGetHashSize(hDigest);
+ AssertReturn(cbDigest > 0 && cbDigest < _16K, VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Combine the encryption and digest algorithms.
+ */
+ const char * const pszDigestOid = RTCrDigestGetAlgorithmOid(hDigest);
+ const char * const pszEncryptedDigestOid
+ = RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(pDesc->pszObjId, pszDigestOid);
+
+ /*
+ * Create an EVP public key from hKey and pszEncryptedDigestOid.
+ */
+ int const iAlgoNid = OBJ_txt2nid(pszEncryptedDigestOid);
+ if (iAlgoNid == NID_undef)
+ return VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP;
+
+ /* Create an EVP public key. */
+ EVP_PKEY *pEvpPublicKey = NULL;
+ const EVP_MD *pEvpMdType = NULL;
+ rc = rtCrKeyToOpenSslKeyEx(hKey, true /*fNeedPublic*/, pszEncryptedDigestOid,
+ (void **)&pEvpPublicKey, (const void **)&pEvpMdType, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+# if OPENSSL_VERSION_NUMBER >= 0x30000000 && !defined(LIBRESSL_VERSION_NUMBER)
+ EVP_PKEY_CTX * const pEvpPublickKeyCtx = EVP_PKEY_CTX_new_from_pkey(NULL, pEvpPublicKey, NULL);
+# else
+ EVP_PKEY_CTX * const pEvpPublickKeyCtx = EVP_PKEY_CTX_new(pEvpPublicKey, NULL);
+# endif
+ if (pEvpPublickKeyCtx)
+ {
+ rc = EVP_PKEY_verify_init(pEvpPublickKeyCtx);
+ if (rc > 0)
+ {
+ rc = EVP_PKEY_CTX_set_signature_md(pEvpPublickKeyCtx, pEvpMdType);
+ if (rc > 0)
+ {
+ rc = EVP_PKEY_verify(pEvpPublickKeyCtx, (const unsigned char *)pvSignature, cbSignature, pbDigest, cbDigest);
+ if (rc > 0)
+ rc = VINF_SUCCESS;
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_VERIFY_FINAL_FAILED,
+ "EVP_PKEY_verify failed (%d)", rc);
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALOG_INIT_FAILED,
+ "EVP_PKEY_CTX_set_signature_md failed (%d)", rc);
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALOG_INIT_FAILED,
+ "EVP_PKEY_verify_init failed (%d)", rc);
+ EVP_PKEY_CTX_free(pEvpPublickKeyCtx);
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALOG_INIT_FAILED,
+ "EVP_PKEY_CTX_new_from_pkey failed (%d)", rc);
+ EVP_PKEY_free(pEvpPublicKey);
+ }
+ return rc;
+
+#else
+ RT_NOREF(pDesc, pvState, hKey, hDigest, pvSignature, cbSignature);
+ return VERR_CR_OPENSSL_VERSION_TOO_OLD;
+#endif
+}
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnSign} */
+static DECLCALLBACK(int) rtCrPkixSignatureOsslEvp_Sign(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, RTCRKEY hKey,
+ RTCRDIGEST hDigest, void *pvSignature, size_t *pcbSignature)
+{
+ PRTCRPKIXSIGNATUREOSSLEVP pThis = (PRTCRPKIXSIGNATUREOSSLEVP)pvState;
+ Assert(pThis->fSigning);
+ RT_NOREF(pThis, pDesc);
+
+#if 0
+ /*
+ * Get the key bits we need.
+ */
+ Assert(RTCrKeyGetType(hKey) == RTCRKEYTYPE_RSA_PRIVATE);
+ PRTBIGNUM pModulus = &hKey->u.RsaPrivate.Modulus;
+ PRTBIGNUM pExponent = &hKey->u.RsaPrivate.PrivateExponent;
+
+ return rc;
+#else
+ RT_NOREF(pDesc, pvState, hKey, hDigest, pvSignature, pcbSignature);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+/** ECDSA alias ODIs. */
+static const char * const g_apszHashWithEcdsaAliases[] =
+{
+ RTCR_X962_ECDSA_WITH_SHA1_OID,
+ RTCR_X962_ECDSA_WITH_SHA2_OID,
+ RTCR_X962_ECDSA_WITH_SHA224_OID,
+ RTCR_X962_ECDSA_WITH_SHA256_OID,
+ RTCR_X962_ECDSA_WITH_SHA384_OID,
+ RTCR_X962_ECDSA_WITH_SHA512_OID,
+ RTCR_NIST_SHA3_224_WITH_ECDSA_OID,
+ RTCR_NIST_SHA3_256_WITH_ECDSA_OID,
+ RTCR_NIST_SHA3_384_WITH_ECDSA_OID,
+ RTCR_NIST_SHA3_512_WITH_ECDSA_OID,
+ NULL
+};
+
+
+/** ECDSA descriptor. */
+DECL_HIDDEN_CONST(RTCRPKIXSIGNATUREDESC const) g_rtCrPkixSigningHashWithEcdsaDesc =
+{
+ "ECDSA",
+ RTCR_X962_ECDSA_OID,
+ g_apszHashWithEcdsaAliases,
+ sizeof(RTCRPKIXSIGNATUREOSSLEVP),
+ 0,
+ 0,
+ rtCrPkixSignatureOsslEvp_Init,
+ rtCrPkixSignatureOsslEvp_Reset,
+ rtCrPkixSignatureOsslEvp_Delete,
+ rtCrPkixSignatureOsslEvp_Verify,
+ rtCrPkixSignatureOsslEvp_Sign,
+};
+#endif /* IPRT_WITH_OPENSSL */
+
diff --git a/src/VBox/Runtime/common/crypto/pkix-signature-rsa.cpp b/src/VBox/Runtime/common/crypto/pkix-signature-rsa.cpp
new file mode 100644
index 00000000..b0b279fc
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkix-signature-rsa.cpp
@@ -0,0 +1,565 @@
+/* $Id: pkix-signature-rsa.cpp $ */
+/** @file
+ * IPRT - Crypto - Public Key Signature Schema Algorithm, RSA Providers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/rsa.h>
+
+#include <iprt/bignum.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/pkix.h>
+
+#include "rsa-internal.h"
+#include "pkix-signature-builtin.h"
+#include "key-internal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * RSA signature provider instance.
+ */
+typedef struct RTCRPKIXSIGNATURERSA
+{
+ /** Set if we're signing, clear if verifying. */
+ bool fSigning;
+
+ /** Temporary big number for use when signing or verifiying. */
+ RTBIGNUM TmpBigNum1;
+ /** Temporary big number for use when signing or verifiying. */
+ RTBIGNUM TmpBigNum2;
+
+ /** Scratch space for decoding the key. */
+ union
+ {
+ /** Public key. */
+ RTCRRSAPUBLICKEY PublicKey;
+ /** Private key. */
+ RTCRRSAPRIVATEKEY PrivateKey;
+ /** Scratch area where we assemble the signature. */
+ uint8_t abSignature[RTCRRSA_MAX_MODULUS_BITS / 8 * 2];
+ } Scratch;
+} RTCRPKIXSIGNATURERSA;
+/** Pointer to an RSA signature provider instance. */
+typedef RTCRPKIXSIGNATURERSA *PRTCRPKIXSIGNATURERSA;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** @name Pre-encoded DigestInfo DER sequences.
+ * @{ */
+static const uint8_t g_abMd2[] =
+{/* { { 1.2.840.113549.2.2 (MD2), NULL }, hash octet-string } */
+ 0x30,0x20, 0x30,0x0c, 0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x02, 0x05,0x00, 0x04,0x10
+};
+static const uint8_t g_abMd4[] =
+{/* { { 1.2.840.113549.2.4 (MD4), NULL }, hash octet-string } */
+ 0x30,0x20, 0x30,0x0c, 0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x04, 0x05,0x00, 0x04,0x10
+};
+static const uint8_t g_abMd5[] =
+{/* { { 1.2.840.113549.2.5 (MD5), NULL }, hash octet-string } */
+ 0x30,0x20, 0x30,0x0c, 0x06,0x08,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x02,0x05, 0x05,0x00, 0x04,0x10
+};
+static const uint8_t g_abSha1[] =
+{/* { { 1.3.14.3.2.26 (SHA-1), NULL }, hash octet-string } */
+ 0x30,0x21, 0x30,0x09, 0x06,0x05,0x2b,0x0e,0x03,0x02,0x1a, 0x05,0x00, 0x04,0x14
+};
+static const uint8_t g_abSha256[] =
+{/* { { 2.16.840.1.101.3.4.2.1 (SHA-256), NULL }, hash octet-string } */
+ 0x30,0x31, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x01, 0x05,0x00, 0x04,0x20
+};
+static const uint8_t g_abSha384[] =
+{/* { { 2.16.840.1.101.3.4.2.2 (SHA-384), NULL }, hash octet-string } */
+ 0x30,0x41, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x02, 0x05,0x00, 0x04,0x30
+};
+static const uint8_t g_abSha512[] =
+{/* { { 2.16.840.1.101.3.4.2.3 (SHA-512), NULL }, hash octet-string } */
+ 0x30,0x51, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x03, 0x05,0x00, 0x04,0x40
+};
+static const uint8_t g_abSha224[] =
+{/* { { 2.16.840.1.101.3.4.2.4 (SHA-224), NULL }, hash octet-string } */
+ 0x30,0x2d, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x04, 0x05,0x00, 0x04,0x1c
+};
+static const uint8_t g_abSha512t224[] =
+{/* { { 2.16.840.1.101.3.4.2.5 (SHA-512T224), NULL }, hash octet-string } */
+ 0x30,0x2d, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x05, 0x05,0x00, 0x04,0x1c
+};
+static const uint8_t g_abSha512t256[] =
+{/* { { 2.16.840.1.101.3.4.2.6 (SHA-512T256), NULL }, hash octet-string } */
+ 0x30,0x31, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x06, 0x05,0x00, 0x04,0x20
+};
+static const uint8_t g_abSha3t224[] =
+{/* { { 2.16.840.1.101.3.4.2.7 (SHA3-224), NULL }, hash octet-string } */
+ 0x30,0x2d, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x07, 0x05,0x00, 0x04,0x1c
+};
+static const uint8_t g_abSha3t256[] =
+{/* { { 2.16.840.1.101.3.4.2.8 (SHA3-256), NULL }, hash octet-string } */
+ 0x30,0x31, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x08, 0x05,0x00, 0x04,0x20
+};
+static const uint8_t g_abSha3t384[] =
+{/* { { 2.16.840.1.101.3.4.2.9 (SHA3-384), NULL }, hash octet-string } */
+ 0x30,0x41, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x09, 0x05,0x00, 0x04,0x30
+};
+static const uint8_t g_abSha3t512[] =
+{/* { { 2.16.840.1.101.3.4.2.10 (SHA3-512), NULL }, hash octet-string } */
+ 0x30,0x51, 0x30,0x0d, 0x06,0x09,0x60,0x86,0x48,0x01,0x65,0x03,0x04,0x02,0x0a, 0x05,0x00, 0x04,0x40
+};
+/** @} */
+
+/** Lookup array for the pre-encoded DigestInfo DER sequences. */
+static struct
+{
+ RTDIGESTTYPE enmDigest;
+ const uint8_t *pb;
+ size_t cb;
+} const g_aDigestInfos[] =
+{
+ { RTDIGESTTYPE_SHA1, g_abSha1, sizeof(g_abSha1) },
+ { RTDIGESTTYPE_SHA256, g_abSha256, sizeof(g_abSha256) },
+ { RTDIGESTTYPE_SHA512, g_abSha512, sizeof(g_abSha512) },
+ { RTDIGESTTYPE_MD2, g_abMd2, sizeof(g_abMd2) },
+ { RTDIGESTTYPE_MD4, g_abMd4, sizeof(g_abMd4) },
+ { RTDIGESTTYPE_MD5, g_abMd5, sizeof(g_abMd5) },
+ { RTDIGESTTYPE_SHA384, g_abSha384, sizeof(g_abSha384) },
+ { RTDIGESTTYPE_SHA224, g_abSha224, sizeof(g_abSha224) },
+ { RTDIGESTTYPE_SHA512T224, g_abSha512t224, sizeof(g_abSha512t224)},
+ { RTDIGESTTYPE_SHA512T256, g_abSha512t256, sizeof(g_abSha512t256)},
+ { RTDIGESTTYPE_SHA3_224, g_abSha3t224, sizeof(g_abSha3t224) },
+ { RTDIGESTTYPE_SHA3_256, g_abSha3t256, sizeof(g_abSha3t256) },
+ { RTDIGESTTYPE_SHA3_384, g_abSha3t384, sizeof(g_abSha3t384) },
+ { RTDIGESTTYPE_SHA3_512, g_abSha3t512, sizeof(g_abSha3t512) },
+};
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnInit} */
+static DECLCALLBACK(int) rtCrPkixSignatureRsa_Init(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, void *pvOpaque,
+ bool fSigning, RTCRKEY hKey, PCRTASN1DYNTYPE pParams)
+{
+ RT_NOREF_PV(pDesc); RT_NOREF_PV(pvState); RT_NOREF_PV(pvOpaque);
+
+ if ( !pParams
+ || pParams->enmType == RTASN1TYPE_NULL
+ || pParams->enmType == RTASN1TYPE_NOT_PRESENT)
+ { /* likely */ }
+ else
+ return VERR_CR_PKIX_SIGNATURE_TAKES_NO_PARAMETERS;
+
+ RTCRKEYTYPE enmKeyType = RTCrKeyGetType(hKey);
+ if (fSigning)
+ AssertReturn(enmKeyType == RTCRKEYTYPE_RSA_PRIVATE, VERR_CR_PKIX_NOT_RSA_PRIVATE_KEY);
+ else
+ AssertReturn(enmKeyType == RTCRKEYTYPE_RSA_PUBLIC, VERR_CR_PKIX_NOT_RSA_PUBLIC_KEY);
+
+ PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState;
+ pThis->fSigning = fSigning;
+
+ return VINF_SUCCESS;
+}
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnReset} */
+static DECLCALLBACK(int) rtCrPkixSignatureRsa_Reset(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, bool fSigning)
+{
+ PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState;
+ RT_NOREF_PV(fSigning); RT_NOREF_PV(pDesc);
+ Assert(pThis->fSigning == fSigning); NOREF(pThis);
+ return VINF_SUCCESS;
+}
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnDelete} */
+static DECLCALLBACK(void) rtCrPkixSignatureRsa_Delete(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, bool fSigning)
+{
+ PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState;
+ RT_NOREF_PV(fSigning); RT_NOREF_PV(pDesc);
+ Assert(pThis->fSigning == fSigning);
+ NOREF(pThis);
+}
+
+
+/**
+ * Common worker for rtCrPkixSignatureRsa_Verify and
+ * rtCrPkixSignatureRsa_Sign that encodes an EMSA-PKCS1-V1_5 signature in
+ * the scratch area.
+ *
+ * This function is referred to as EMSA-PKCS1-v1_5-ENCODE(M,k) in RFC-3447 and
+ * is described in section 9.2
+ *
+ * @returns IPRT status code.
+ * @param pThis The RSA signature provider instance.
+ * @param hDigest The digest which hash to turn into a signature.
+ * @param cbEncodedMsg The desired encoded message length.
+ * @param fNoDigestInfo If true, skip the DigestInfo and encode the digest
+ * without any prefix like described in v1.5 (RFC-2313)
+ * and observed with RSA+MD5 signed timestamps. If
+ * false, include the prefix like v2.0 (RFC-2437)
+ * describes in step in section 9.2.1
+ * (EMSA-PKCS1-v1_5)
+ *
+ * @remarks Must preserve informational status codes!
+ */
+static int rtCrPkixSignatureRsa_EmsaPkcs1V15Encode(PRTCRPKIXSIGNATURERSA pThis, RTCRDIGEST hDigest, size_t cbEncodedMsg,
+ bool fNoDigestInfo)
+{
+ AssertReturn(cbEncodedMsg * 2 <= sizeof(pThis->Scratch), VERR_CR_PKIX_INTERNAL_ERROR);
+
+ /*
+ * Figure out which hash and select the associate prebaked DigestInfo.
+ */
+ RTDIGESTTYPE const enmDigest = RTCrDigestGetType(hDigest);
+ AssertReturn(enmDigest != RTDIGESTTYPE_INVALID && enmDigest != RTDIGESTTYPE_UNKNOWN, VERR_CR_PKIX_UNKNOWN_DIGEST_TYPE);
+ uint8_t const *pbDigestInfoStart = NULL;
+ size_t cbDigestInfoStart = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aDigestInfos); i++)
+ if (g_aDigestInfos[i].enmDigest == enmDigest)
+ {
+ pbDigestInfoStart = g_aDigestInfos[i].pb;
+ cbDigestInfoStart = g_aDigestInfos[i].cb;
+ break;
+ }
+ if (!pbDigestInfoStart)
+ return VERR_CR_PKIX_UNKNOWN_DIGEST_TYPE;
+
+ /*
+ * Get the hash size and verify that it matches what we've got in the
+ * precooked DigestInfo. ASSUMES less that 256 bytes of hash.
+ */
+ uint32_t const cbHash = RTCrDigestGetHashSize(hDigest);
+ AssertReturn(cbHash > 0 && cbHash < _16K, VERR_OUT_OF_RANGE);
+ AssertReturn(cbHash == pbDigestInfoStart[cbDigestInfoStart - 1], VERR_CR_PKIX_INTERNAL_ERROR);
+
+ if (fNoDigestInfo)
+ cbDigestInfoStart = 0;
+
+ if (cbDigestInfoStart + cbHash + 11 > cbEncodedMsg)
+ return VERR_CR_PKIX_HASH_TOO_LONG_FOR_KEY;
+
+ /*
+ * Encode the message the first part of the scratch area.
+ */
+ uint8_t *pbDst = &pThis->Scratch.abSignature[0];
+ pbDst[0] = 0x00;
+ pbDst[1] = 0x01; /* BT - block type, see RFC-2313. */
+ size_t cbFFs = cbEncodedMsg - cbHash - cbDigestInfoStart - 3;
+ memset(&pbDst[2], 0xff, cbFFs);
+ pbDst += cbFFs + 2;
+ *pbDst++ = 0x00;
+ memcpy(pbDst, pbDigestInfoStart, cbDigestInfoStart);
+ pbDst += cbDigestInfoStart;
+ /* Note! Must preserve informational status codes from this call . */
+ int rc = RTCrDigestFinal(hDigest, pbDst, cbHash);
+ if (RT_SUCCESS(rc))
+ {
+ pbDst += cbHash;
+ Assert((size_t)(pbDst - &pThis->Scratch.abSignature[0]) == cbEncodedMsg);
+ }
+ return rc;
+}
+
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnVerify} */
+static DECLCALLBACK(int) rtCrPkixSignatureRsa_Verify(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, RTCRKEY hKey,
+ RTCRDIGEST hDigest, void const *pvSignature, size_t cbSignature)
+{
+ PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState;
+ RT_NOREF_PV(pDesc);
+ Assert(!pThis->fSigning);
+ if (cbSignature > sizeof(pThis->Scratch) / 2)
+ return VERR_CR_PKIX_SIGNATURE_TOO_LONG;
+
+ /*
+ * Get the key bits we need.
+ */
+ Assert(RTCrKeyGetType(hKey) == RTCRKEYTYPE_RSA_PUBLIC);
+ PRTBIGNUM pModulus = &hKey->u.RsaPublic.Modulus;
+ PRTBIGNUM pExponent = &hKey->u.RsaPublic.Exponent;
+
+ /*
+ * 8.2.2.1 - Length check. (RFC-3447)
+ */
+ if (cbSignature != RTBigNumByteWidth(pModulus))
+ return VERR_CR_PKIX_INVALID_SIGNATURE_LENGTH;
+
+ /*
+ * 8.2.2.2 - RSA verification / Decrypt the signature.
+ */
+ /* a) s = OS2IP(S) -- Convert signature to integer. */
+ int rc = RTBigNumInit(&pThis->TmpBigNum1, RTBIGNUMINIT_F_ENDIAN_BIG | RTBIGNUMINIT_F_UNSIGNED,
+ pvSignature, cbSignature);
+ if (RT_FAILURE(rc))
+ return rc;
+ /* b) RSAVP1 - 5.2.2.2: Range check (0 <= s < n). */
+ if (RTBigNumCompare(&pThis->TmpBigNum1, pModulus) < 0)
+ {
+ if (RTBigNumCompareWithU64(&pThis->TmpBigNum1, 0) >= 0)
+ {
+ /* b) RSAVP1 - 5.2.2.3: s^e mod n */
+ rc = RTBigNumInitZero(&pThis->TmpBigNum2, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTBigNumModExp(&pThis->TmpBigNum2, &pThis->TmpBigNum1, pExponent, pModulus);
+ if (RT_SUCCESS(rc))
+ {
+ /* c) EM' = I2OSP(m, k) -- Convert the result to bytes. */
+ uint32_t cbDecrypted = RTBigNumByteWidth(&pThis->TmpBigNum2) + 1; /* 1 = leading zero byte */
+ if (cbDecrypted <= sizeof(pThis->Scratch) / 2)
+ {
+ uint8_t *pbDecrypted = &pThis->Scratch.abSignature[sizeof(pThis->Scratch) / 2];
+ rc = RTBigNumToBytesBigEndian(&pThis->TmpBigNum2, pbDecrypted, cbDecrypted);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * 8.2.2.3 - Build a hopefully identical signature using hDigest.
+ */
+ rc = rtCrPkixSignatureRsa_EmsaPkcs1V15Encode(pThis, hDigest, cbDecrypted, false /* fNoDigestInfo */);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * 8.2.2.4 - Compare the two.
+ */
+ if (memcmp(&pThis->Scratch.abSignature[0], pbDecrypted, cbDecrypted) == 0)
+ { /* No rc = VINF_SUCCESS here, mustpreserve informational status codes from digest. */ }
+ else
+ {
+ /*
+ * Try again without digestinfo. This style signing has been
+ * observed in Vista timestamp counter signatures (Thawte).
+ */
+ rc = rtCrPkixSignatureRsa_EmsaPkcs1V15Encode(pThis, hDigest, cbDecrypted,
+ true /* fNoDigestInfo */);
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(&pThis->Scratch.abSignature[0], pbDecrypted, cbDecrypted) == 0)
+ { /* No rc = VINF_SUCCESS here, mustpreserve informational status codes from digest. */ }
+ else
+ rc = VERR_CR_PKIX_SIGNATURE_MISMATCH;
+ }
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_CR_PKIX_SIGNATURE_TOO_LONG;
+ }
+ RTBigNumDestroy(&pThis->TmpBigNum2);
+ }
+ }
+ else
+ rc = VERR_CR_PKIX_SIGNATURE_NEGATIVE;
+ }
+ else
+ rc = VERR_CR_PKIX_SIGNATURE_GE_KEY;
+ RTBigNumDestroy(&pThis->TmpBigNum1);
+ return rc;
+}
+
+
+/** @impl_interface_method{RTCRPKIXSIGNATUREDESC,pfnSign} */
+static DECLCALLBACK(int) rtCrPkixSignatureRsa_Sign(PCRTCRPKIXSIGNATUREDESC pDesc, void *pvState, RTCRKEY hKey,
+ RTCRDIGEST hDigest, void *pvSignature, size_t *pcbSignature)
+{
+ PRTCRPKIXSIGNATURERSA pThis = (PRTCRPKIXSIGNATURERSA)pvState;
+ RT_NOREF_PV(pDesc);
+ Assert(pThis->fSigning);
+
+ /*
+ * Get the key bits we need.
+ */
+ Assert(RTCrKeyGetType(hKey) == RTCRKEYTYPE_RSA_PRIVATE);
+ PRTBIGNUM pModulus = &hKey->u.RsaPrivate.Modulus;
+ PRTBIGNUM pExponent = &hKey->u.RsaPrivate.PrivateExponent;
+
+ /*
+ * Calc signature length and return if destination buffer isn't big enough.
+ */
+ size_t const cbDst = *pcbSignature;
+ size_t const cbEncodedMsg = RTBigNumByteWidth(pModulus);
+ *pcbSignature = cbEncodedMsg;
+ if (cbEncodedMsg > sizeof(pThis->Scratch) / 2)
+ return VERR_CR_PKIX_SIGNATURE_TOO_LONG;
+ if (!pvSignature || cbDst < cbEncodedMsg)
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * 8.1.1.1 - EMSA-PSS encoding. (RFC-3447)
+ */
+ int rcRetSuccess;
+ int rc = rcRetSuccess = rtCrPkixSignatureRsa_EmsaPkcs1V15Encode(pThis, hDigest, cbEncodedMsg, false /* fNoDigestInfo */);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * 8.1.1.2 - RSA signature.
+ */
+ /* a) m = OS2IP(EM) -- Convert the encoded message (EM) to integer. */
+ rc = RTBigNumInit(&pThis->TmpBigNum1, RTBIGNUMINIT_F_ENDIAN_BIG | RTBIGNUMINIT_F_UNSIGNED,
+ pThis->Scratch.abSignature, cbEncodedMsg);
+ if (RT_SUCCESS(rc))
+ {
+ /* b) s = RSASP1(K, m = EM) - 5.2.1.1: Range check (0 <= m < n). */
+ if (RTBigNumCompare(&pThis->TmpBigNum1, pModulus) < 0)
+ {
+ /* b) s = RSAVP1(K, m = EM) - 5.2.1.2.a: s = m^d mod n */
+ rc = RTBigNumInitZero(&pThis->TmpBigNum2, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTBigNumModExp(&pThis->TmpBigNum2, &pThis->TmpBigNum1, pExponent, pModulus);
+ if (RT_SUCCESS(rc))
+ {
+ /* c) S = I2OSP(s, k) -- Convert the result to bytes. */
+ rc = RTBigNumToBytesBigEndian(&pThis->TmpBigNum2, pvSignature, cbEncodedMsg);
+ AssertStmt(RT_SUCCESS(rc) || rc != VERR_BUFFER_OVERFLOW, rc = VERR_CR_PKIX_INTERNAL_ERROR);
+
+ /* Make sure we return the informational status code from the digest on success. */
+ if (rc == VINF_SUCCESS && rcRetSuccess != VINF_SUCCESS)
+ rc = rcRetSuccess;
+ }
+ RTBigNumDestroy(&pThis->TmpBigNum2);
+ }
+ }
+ else
+ rc = VERR_CR_PKIX_SIGNATURE_GE_KEY;
+ RTBigNumDestroy(&pThis->TmpBigNum1);
+ }
+ }
+ return rc;
+}
+
+
+
+
+/** RSA alias ODIs. */
+static const char * const g_apszHashWithRsaAliases[] =
+{
+ RTCR_PKCS1_MD2_WITH_RSA_OID,
+ RTCR_PKCS1_MD4_WITH_RSA_OID,
+ RTCR_PKCS1_MD5_WITH_RSA_OID,
+ RTCR_PKCS1_SHA1_WITH_RSA_OID,
+ RTCR_PKCS1_SHA256_WITH_RSA_OID,
+ RTCR_PKCS1_SHA384_WITH_RSA_OID,
+ RTCR_PKCS1_SHA512_WITH_RSA_OID,
+ RTCR_PKCS1_SHA224_WITH_RSA_OID,
+ RTCR_PKCS1_SHA512T224_WITH_RSA_OID,
+ RTCR_PKCS1_SHA512T256_WITH_RSA_OID,
+ RTCR_NIST_SHA3_224_WITH_RSA_OID,
+ RTCR_NIST_SHA3_256_WITH_RSA_OID,
+ RTCR_NIST_SHA3_384_WITH_RSA_OID,
+ RTCR_NIST_SHA3_512_WITH_RSA_OID,
+ /* Note: Note quite sure about these OIW oddballs. */
+ "1.3.14.3.2.11" /* OIW rsaSignature */,
+ "1.3.14.3.2.14" /* OIW mdc2WithRSASignature */,
+ "1.3.14.3.2.15" /* OIW shaWithRSASignature */,
+ "1.3.14.3.2.24" /* OIW md2WithRSASignature */,
+ "1.3.14.3.2.25" /* OIW md5WithRSASignature */,
+ "1.3.14.3.2.29" /* OIW sha1WithRSASignature */,
+ NULL
+};
+
+
+/** RSA descriptor. */
+DECL_HIDDEN_CONST(RTCRPKIXSIGNATUREDESC const) g_rtCrPkixSigningHashWithRsaDesc =
+{
+ "RSASSA-PKCS1-v1_5",
+ RTCR_PKCS1_RSA_OID,
+ g_apszHashWithRsaAliases,
+ sizeof(RTCRPKIXSIGNATURERSA),
+ 0,
+ 0,
+ rtCrPkixSignatureRsa_Init,
+ rtCrPkixSignatureRsa_Reset,
+ rtCrPkixSignatureRsa_Delete,
+ rtCrPkixSignatureRsa_Verify,
+ rtCrPkixSignatureRsa_Sign,
+};
+
+
+/**
+ * Worker for RTCrRsaPublicKey_CanHandleDigestType and
+ * RTCrRsaPrivateKey_CanHandleDigestType.
+ *
+ * We implement these two functions here because we've already got the
+ * DigestInfo sizes nicely lined up here.
+ */
+static bool rtCrRsa_CanHandleDigestType(int32_t cModulusBits, RTDIGESTTYPE enmDigestType, PRTERRINFO pErrInfo)
+{
+ /*
+ * ASSUME EMSA-PKCS1-v1_5 padding scheme (RFC-8017 section 9.2):
+ * - 11 byte padding prefix (00, 01, 8 times ff)
+ * - digest info der sequence for rsaWithXxxxEncryption
+ * - the hash value.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aDigestInfos); i++)
+ if (g_aDigestInfos[i].enmDigest == enmDigestType)
+ {
+ size_t const cbHash = RTCrDigestTypeToHashSize(enmDigestType);
+ AssertBreak(cbHash > 0);
+
+ size_t cbMsg = 11 + g_aDigestInfos[i].cb + cbHash;
+ if ((ssize_t)cbMsg <= cModulusBits / 8)
+ return true;
+ RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_INVALID_SIGNATURE_LENGTH, "cModulusBits=%d cbMsg=%u", cModulusBits, cbMsg);
+ return false;
+ }
+ RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_UNKNOWN_DIGEST_TYPE, "%s", RTCrDigestTypeToName(enmDigestType));
+ return false;
+}
+
+
+RTDECL(bool) RTCrRsaPublicKey_CanHandleDigestType(PCRTCRRSAPUBLICKEY pRsaPublicKey, RTDIGESTTYPE enmDigestType,
+ PRTERRINFO pErrInfo)
+{
+ if (RTCrRsaPublicKey_IsPresent(pRsaPublicKey))
+ return rtCrRsa_CanHandleDigestType(RTAsn1Integer_UnsignedLastBit(&pRsaPublicKey->Modulus) + 1, enmDigestType, pErrInfo);
+ return false;
+}
+
+
+RTDECL(bool) RTCrRsaPrivateKey_CanHandleDigestType(PCRTCRRSAPRIVATEKEY pRsaPrivateKey, RTDIGESTTYPE enmDigestType,
+ PRTERRINFO pErrInfo)
+{
+ if (RTCrRsaPrivateKey_IsPresent(pRsaPrivateKey))
+ return rtCrRsa_CanHandleDigestType(RTAsn1Integer_UnsignedLastBit(&pRsaPrivateKey->Modulus) + 1, enmDigestType, pErrInfo);
+ return false;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pkix-util.cpp b/src/VBox/Runtime/common/crypto/pkix-util.cpp
new file mode 100644
index 00000000..1198d0e0
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkix-util.cpp
@@ -0,0 +1,108 @@
+/* $Id: pkix-util.cpp $ */
+/** @file
+ * IPRT - Crypto - Public Key Infrastructure API, Utilities.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkix.h>
+
+#include <iprt/asn1.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/crypto/rsa.h>
+
+
+RTDECL(const char *) RTCrPkixGetCiperOidFromSignatureAlgorithmOid(const char *pszSignatureOid)
+{
+ return RTCrX509AlgorithmIdentifier_GetEncryptionOidFromOid(pszSignatureOid, true /*fMustIncludeHash*/);
+}
+
+
+RTDECL(const char *) RTCrPkixGetCiperOidFromSignatureAlgorithm(PCRTASN1OBJID pAlgorithm)
+{
+ return RTCrX509AlgorithmIdentifier_GetEncryptionOidFromOid(pAlgorithm->szObjId, true /*fMustIncludeHash*/);
+}
+
+
+RTDECL(bool) RTCrPkixPubKeyCanHandleDigestType(PCRTCRX509SUBJECTPUBLICKEYINFO pPublicKeyInfo, RTDIGESTTYPE enmDigestType,
+ PRTERRINFO pErrInfo)
+{
+ bool fRc = false;
+ if (RTCrX509SubjectPublicKeyInfo_IsPresent(pPublicKeyInfo))
+ {
+ void const * const pvKeyBits = RTASN1BITSTRING_GET_BIT0_PTR(&pPublicKeyInfo->SubjectPublicKey);
+ uint32_t const cbKeyBits = RTASN1BITSTRING_GET_BYTE_SIZE(&pPublicKeyInfo->SubjectPublicKey);
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ union
+ {
+ RTCRRSAPUBLICKEY RsaPublicKey;
+ } u;
+
+ if (RTAsn1ObjId_CompareWithString(&pPublicKeyInfo->Algorithm.Algorithm, RTCR_PKCS1_RSA_OID) == 0)
+ {
+ /*
+ * RSA.
+ */
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pvKeyBits, cbKeyBits, pErrInfo, &g_RTAsn1DefaultAllocator,
+ RTASN1CURSOR_FLAGS_DER, "rsa");
+
+ RT_ZERO(u.RsaPublicKey);
+ int rc = RTCrRsaPublicKey_DecodeAsn1(&PrimaryCursor.Cursor, 0, &u.RsaPublicKey, "PublicKey");
+ if (RT_SUCCESS(rc))
+ fRc = RTCrRsaPublicKey_CanHandleDigestType(&u.RsaPublicKey, enmDigestType, pErrInfo);
+ RTCrRsaPublicKey_Delete(&u.RsaPublicKey);
+ }
+ else
+ {
+ /** @todo ECDSA when adding signing support. */
+ RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN, "%s", pPublicKeyInfo->Algorithm.Algorithm.szObjId);
+ AssertMsgFailed(("unknown key algorithm: %s\n", pPublicKeyInfo->Algorithm.Algorithm.szObjId));
+ fRc = true;
+ }
+ }
+ return fRc;
+}
+
+
+RTDECL(bool) RTCrPkixCanCertHandleDigestType(PCRTCRX509CERTIFICATE pCertificate, RTDIGESTTYPE enmDigestType, PRTERRINFO pErrInfo)
+{
+ if (RTCrX509Certificate_IsPresent(pCertificate))
+ return RTCrPkixPubKeyCanHandleDigestType(&pCertificate->TbsCertificate.SubjectPublicKeyInfo, enmDigestType, pErrInfo);
+ return false;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/pkix-verify.cpp b/src/VBox/Runtime/common/crypto/pkix-verify.cpp
new file mode 100644
index 00000000..10c5bd61
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/pkix-verify.cpp
@@ -0,0 +1,329 @@
+/* $Id: pkix-verify.cpp $ */
+/** @file
+ * IPRT - Crypto - Public Key Infrastructure API, Verification.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/pkix.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/crypto/key.h>
+
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/evp.h>
+# include "internal/openssl-post.h"
+# ifndef OPENSSL_VERSION_NUMBER
+# error "Missing OPENSSL_VERSION_NUMBER!"
+# endif
+#endif
+
+
+
+RTDECL(int) RTCrPkixPubKeyVerifySignature(PCRTASN1OBJID pAlgorithm, RTCRKEY hPublicKey, PCRTASN1DYNTYPE pParameters,
+ PCRTASN1BITSTRING pSignatureValue, const void *pvData, size_t cbData,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * Valid input.
+ */
+ AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER);
+
+ AssertPtrReturn(hPublicKey, VERR_INVALID_POINTER);
+ Assert(RTCrKeyHasPublicPart(hPublicKey));
+ RTCRKEYTYPE const enmKeyType = RTCrKeyGetType(hPublicKey);
+ AssertReturn(enmKeyType != RTCRKEYTYPE_INVALID, VERR_INVALID_HANDLE);
+
+ AssertPtrReturn(pSignatureValue, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1BitString_IsPresent(pSignatureValue), VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+ AssertReturn(cbData > 0, VERR_INVALID_PARAMETER);
+
+ /*
+ * Verify that the parameters are compatible with the key. We ASSUME the
+ * parameters are for a hash+cryption combination, like those found in
+ * RTCRX509TBSCERTIFICATE::Signature. At present, these should NULL (or
+ * absent) for the two key types we support RSA & ECDSA, which is an
+ * ASSUMPTION by the OpenSSL code below.
+ */
+ int rcIprt = RTCrKeyVerifyParameterCompatibility(hPublicKey, pParameters, true /*fForSignature*/, pAlgorithm, pErrInfo);
+ AssertRCReturn(rcIprt, rcIprt);
+
+ /*
+ * Validate using IPRT.
+ */
+ RTCRPKIXSIGNATURE hSignature;
+ rcIprt = RTCrPkixSignatureCreateByObjId(&hSignature, pAlgorithm, hPublicKey, pParameters, false /*fSigning*/);
+ if (RT_FAILURE(rcIprt))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN,
+ "Unknown public key algorithm [IPRT %Rrc]: %s", rcIprt, pAlgorithm->szObjId);
+
+ RTCRDIGEST hDigest;
+ rcIprt = RTCrDigestCreateByObjId(&hDigest, pAlgorithm);
+ if (RT_SUCCESS(rcIprt))
+ {
+ /* Calculate the digest. */
+ rcIprt = RTCrDigestUpdate(hDigest, pvData, cbData);
+ if (RT_SUCCESS(rcIprt))
+ {
+ rcIprt = RTCrPkixSignatureVerifyBitString(hSignature, hDigest, pSignatureValue);
+ if (RT_FAILURE(rcIprt))
+ RTErrInfoSet(pErrInfo, rcIprt, "RTCrPkixSignatureVerifyBitString failed");
+ }
+ else
+ RTErrInfoSet(pErrInfo, rcIprt, "RTCrDigestUpdate failed");
+ RTCrDigestRelease(hDigest);
+ }
+ else
+ RTErrInfoSetF(pErrInfo, rcIprt, "Unknown digest algorithm [IPRT]: %s", pAlgorithm->szObjId);
+ RTCrPkixSignatureRelease(hSignature);
+
+#ifdef IPRT_WITH_OPENSSL
+ /* We don't implement digest+cipher parameters in OpenSSL (or at all),
+ RTCrKeyVerifyParameterCompatibility should ensure we don't get here
+ (ASSUMING only RSA and ECDSA keys). But, just in case, bail out if we do. */
+ AssertReturn( !pParameters
+ || pParameters->enmType == RTASN1TYPE_NULL
+ || pParameters->enmType == RTASN1TYPE_NOT_PRESENT,
+ VERR_CR_PKIX_CIPHER_ALGO_PARAMS_NOT_IMPL);
+
+ /*
+ * Validate using OpenSSL EVP.
+ */
+ /* Create an EVP public key. */
+ EVP_PKEY *pEvpPublicKey = NULL;
+ const EVP_MD *pEvpMdType = NULL;
+ int rcOssl = rtCrKeyToOpenSslKeyEx(hPublicKey, true /*fNeedPublic*/, pAlgorithm->szObjId,
+ (void **)&pEvpPublicKey, (const void **)&pEvpMdType, pErrInfo);
+ if (RT_SUCCESS(rcOssl))
+ {
+ EVP_MD_CTX *pEvpMdCtx = EVP_MD_CTX_create();
+ if (pEvpMdCtx)
+ {
+ if (EVP_VerifyInit_ex(pEvpMdCtx, pEvpMdType, NULL /*engine*/))
+ {
+ /* Digest the data. */
+ EVP_VerifyUpdate(pEvpMdCtx, pvData, cbData);
+
+ /* Verify the signature. */
+ if (EVP_VerifyFinal(pEvpMdCtx,
+ RTASN1BITSTRING_GET_BIT0_PTR(pSignatureValue),
+ RTASN1BITSTRING_GET_BYTE_SIZE(pSignatureValue),
+ pEvpPublicKey) > 0)
+ rcOssl = VINF_SUCCESS;
+ else
+ rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_VERIFY_FINAL_FAILED, "EVP_VerifyFinal failed");
+
+ /* Cleanup and return: */
+ }
+ else
+ rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_CIPHER_ALOG_INIT_FAILED,
+ "EVP_VerifyInit_ex failed (algorithm type is %s)", pAlgorithm->szObjId);
+ EVP_MD_CTX_destroy(pEvpMdCtx);
+ }
+ else
+ rcOssl = RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "EVP_MD_CTX_create failed");
+ EVP_PKEY_free(pEvpPublicKey);
+ }
+
+ /*
+ * Check the result.
+ */
+ if ( (RT_SUCCESS(rcIprt) && RT_SUCCESS(rcOssl))
+ || (RT_FAILURE_NP(rcIprt) && RT_FAILURE_NP(rcOssl))
+ || (RT_SUCCESS(rcIprt) && rcOssl == VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP) )
+ return rcIprt;
+ AssertMsgFailed(("rcIprt=%Rrc rcOssl=%Rrc\n", rcIprt, rcOssl));
+ if (RT_FAILURE_NP(rcOssl))
+ return rcOssl;
+#endif /* IPRT_WITH_OPENSSL */
+
+ return rcIprt;
+}
+
+
+RTDECL(int) RTCrPkixPubKeyVerifySignedDigest(PCRTASN1OBJID pAlgorithm, RTCRKEY hPublicKey, PCRTASN1DYNTYPE pParameters,
+ void const *pvSignedDigest, size_t cbSignedDigest, RTCRDIGEST hDigest,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * Valid input.
+ */
+ AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER);
+
+ AssertPtrReturn(hPublicKey, VERR_INVALID_POINTER);
+ Assert(RTCrKeyHasPublicPart(hPublicKey));
+ RTCRKEYTYPE const enmKeyType = RTCrKeyGetType(hPublicKey);
+ AssertReturn(enmKeyType != RTCRKEYTYPE_INVALID, VERR_INVALID_HANDLE);
+
+ AssertPtrReturn(pvSignedDigest, VERR_INVALID_POINTER);
+ AssertReturn(cbSignedDigest, VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(hDigest, VERR_INVALID_HANDLE);
+
+ /*
+ * Verify that the parameters are compatible with the key. We ASSUME the
+ * parameters are for a hash+cryption combination, like those found in
+ * RTCRX509TBSCERTIFICATE::Signature. At present, these should NULL (or
+ * absent) for the two key types we support RSA & ECDSA, which is an
+ * ASSUMPTION by the OpenSSL code below.
+ */
+ int rcIprt = RTCrKeyVerifyParameterCompatibility(hPublicKey, pParameters, true /*fForSignature*/, pAlgorithm, pErrInfo);
+ AssertRCReturn(rcIprt, rcIprt);
+
+ /*
+ * Validate using IPRT.
+ */
+ RTCRPKIXSIGNATURE hSignature;
+ rcIprt = RTCrPkixSignatureCreateByObjId(&hSignature, pAlgorithm, hPublicKey, pParameters, false /*fSigning*/);
+ if (RT_FAILURE(rcIprt))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_CIPHER_ALGO_NOT_KNOWN,
+ "Unknown public key algorithm [IPRT %Rrc]: %s", rcIprt, pAlgorithm->szObjId);
+
+ rcIprt = RTCrPkixSignatureVerify(hSignature, hDigest, pvSignedDigest, cbSignedDigest);
+ if (RT_FAILURE(rcIprt))
+ RTErrInfoSet(pErrInfo, rcIprt, "RTCrPkixSignatureVerifyBitString failed");
+
+ RTCrPkixSignatureRelease(hSignature);
+
+#if defined(IPRT_WITH_OPENSSL) \
+ && (OPENSSL_VERSION_NUMBER > 0x10000000L) /* 0.9.8 doesn't seem to have EVP_PKEY_CTX_set_signature_md. */
+ /*
+ * Validate using OpenSSL EVP.
+ */
+ /* Make sure the algorithm includes the digest and isn't just RSA, ECDSA or similar. */
+ const char *pszAlgObjId = RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(pAlgorithm->szObjId,
+ RTCrDigestGetAlgorithmOid(hDigest));
+ AssertMsgStmt(pszAlgObjId, ("enc=%s hash=%s\n", pAlgorithm->szObjId, RTCrDigestGetAlgorithmOid(hDigest)),
+ pszAlgObjId = RTCrDigestGetAlgorithmOid(hDigest));
+
+ /* We don't implement digest+cipher parameters in OpenSSL (or at all),
+ RTCrKeyVerifyParameterCompatibility should ensure we don't get here
+ (ASSUMING only RSA and ECDSA keys). But, just in case, bail out if we do. */
+ AssertReturn( !pParameters
+ || pParameters->enmType == RTASN1TYPE_NULL
+ || pParameters->enmType == RTASN1TYPE_NOT_PRESENT,
+ VERR_CR_PKIX_CIPHER_ALGO_PARAMS_NOT_IMPL);
+
+ /* Create an EVP public key. */
+ EVP_PKEY *pEvpPublicKey = NULL;
+ const EVP_MD *pEvpMdType = NULL;
+ int rcOssl = rtCrKeyToOpenSslKeyEx(hPublicKey, true /*fNeedPublic*/, pszAlgObjId,
+ (void **)&pEvpPublicKey, (const void **)&pEvpMdType, pErrInfo);
+ if (RT_SUCCESS(rcOssl))
+ {
+ /* Create an EVP public key context we can use to validate the digest. */
+ EVP_PKEY_CTX *pEvpPKeyCtx = EVP_PKEY_CTX_new(pEvpPublicKey, NULL);
+ if (pEvpPKeyCtx)
+ {
+ rcOssl = EVP_PKEY_verify_init(pEvpPKeyCtx);
+ if (rcOssl > 0)
+ {
+ rcOssl = EVP_PKEY_CTX_set_signature_md(pEvpPKeyCtx, pEvpMdType);
+ if (rcOssl > 0)
+ {
+ /* Get the digest from hDigest and verify it. */
+ rcOssl = EVP_PKEY_verify(pEvpPKeyCtx,
+ (uint8_t const *)pvSignedDigest,
+ cbSignedDigest,
+ RTCrDigestGetHash(hDigest),
+ RTCrDigestGetHashSize(hDigest));
+ if (rcOssl > 0)
+ rcOssl = VINF_SUCCESS;
+ else
+ rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_VERIFY_FINAL_FAILED,
+ "EVP_PKEY_verify failed (%d)", rcOssl);
+ /* Cleanup and return: */
+ }
+ else
+ rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR,
+ "EVP_PKEY_CTX_set_signature_md failed (%d)", rcOssl);
+ }
+ else
+ rcOssl = RTErrInfoSetF(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR,
+ "EVP_PKEY_verify_init failed (%d)", rcOssl);
+ EVP_PKEY_CTX_free(pEvpPKeyCtx);
+ }
+ else
+ rcOssl = RTErrInfoSet(pErrInfo, VERR_CR_PKIX_OSSL_EVP_PKEY_TYPE_ERROR, "EVP_PKEY_CTX_new failed");
+ EVP_PKEY_free(pEvpPublicKey);
+ }
+
+ /*
+ * Check the result.
+ */
+ if ( (RT_SUCCESS(rcIprt) && RT_SUCCESS(rcOssl))
+ || (RT_FAILURE_NP(rcIprt) && RT_FAILURE_NP(rcOssl))
+ || (RT_SUCCESS(rcIprt) && rcOssl == VERR_CR_PKIX_OSSL_CIPHER_ALGO_NOT_KNOWN_EVP) )
+ return rcIprt;
+ AssertMsgFailed(("rcIprt=%Rrc rcOssl=%Rrc\n", rcIprt, rcOssl));
+ if (RT_FAILURE_NP(rcOssl))
+ return rcOssl;
+#endif /* IPRT_WITH_OPENSSL */
+
+ return rcIprt;
+}
+
+
+RTDECL(int) RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(PCRTCRX509SUBJECTPUBLICKEYINFO pCertPubKeyInfo,
+ void const *pvSignedDigest, size_t cbSignedDigest,
+ RTCRDIGEST hDigest, PRTERRINFO pErrInfo)
+{
+ RTCRKEY hPublicKey;
+ int rc = RTCrKeyCreateFromPublicAlgorithmAndBits(&hPublicKey, &pCertPubKeyInfo->Algorithm.Algorithm,
+ &pCertPubKeyInfo->Algorithm.Parameters,
+ &pCertPubKeyInfo->SubjectPublicKey, pErrInfo, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo r=bird (2023-07-06): This ASSUMES no digest+cipher parameters, which
+ * is the case for RSA and ECDSA. */
+ rc = RTCrPkixPubKeyVerifySignedDigest(&pCertPubKeyInfo->Algorithm.Algorithm, hPublicKey, NULL,
+ pvSignedDigest, cbSignedDigest, hDigest, pErrInfo);
+
+ uint32_t cRefs = RTCrKeyRelease(hPublicKey);
+ Assert(cRefs == 0); RT_NOREF(cRefs);
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/rc4-openssl.cpp b/src/VBox/Runtime/common/crypto/rc4-openssl.cpp
new file mode 100644
index 00000000..a32eb155
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/rc4-openssl.cpp
@@ -0,0 +1,75 @@
+/* $Id: rc4-openssl.cpp $ */
+/** @file
+ * IPRT - Crypto - Alleged RC4 via OpenSSL.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/iprt.h"
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/rc4.h>
+# include "internal/openssl-post.h"
+# include <iprt/crypto/rc4.h>
+
+# include <iprt/assert.h>
+# include <iprt/assertcompile.h>
+
+
+
+RTDECL(void) RTCrRc4SetKey(PRTCRRC4KEY pKey, size_t cbData, void const *pvData)
+{
+
+ AssertCompile(sizeof(RC4_INT) == 4 ? sizeof(pKey->au64Padding) == sizeof(pKey->Ossl) : sizeof(pKey->au64Padding) >= sizeof(pKey->Ossl));
+ Assert((int)cbData == (ssize_t)cbData);
+ AssertPtr(pKey);
+
+ RC4_set_key(&pKey->Ossl, (int)cbData, (const unsigned char *)pvData);
+}
+
+
+RTDECL(void) RTCrRc4(PRTCRRC4KEY pKey, size_t cbData, void const *pvDataIn, void *pvDataOut)
+{
+ AssertCompile(sizeof(RC4_INT) == 4 ? sizeof(pKey->au64Padding) == sizeof(pKey->Ossl) : sizeof(pKey->au64Padding) >= sizeof(pKey->Ossl));
+ Assert((int)cbData == (ssize_t)cbData);
+ AssertPtr(pKey);
+
+ RC4(&pKey->Ossl, (int)cbData, (const unsigned char *)pvDataIn, (unsigned char *)pvDataOut);
+}
+
+#endif /* IPRT_WITH_OPENSSL */
+
diff --git a/src/VBox/Runtime/common/crypto/rsa-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/rsa-asn1-decoder.cpp
new file mode 100644
index 00000000..9c127fb2
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/rsa-asn1-decoder.cpp
@@ -0,0 +1,53 @@
+/* $Id: rsa-asn1-decoder.cpp $ */
+/** @file
+ * IPRT - Crypto - RSA, Decoder for ASN.1.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/rsa.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#include "rsa-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/crypto/rsa-core.cpp b/src/VBox/Runtime/common/crypto/rsa-core.cpp
new file mode 100644
index 00000000..c356b48c
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/rsa-core.cpp
@@ -0,0 +1,54 @@
+/* $Id: rsa-core.cpp $ */
+/** @file
+ * IPRT - Crypto - RSA, Core APIs.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/rsa.h>
+
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "rsa-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-core.h>
+
diff --git a/src/VBox/Runtime/common/crypto/rsa-init.cpp b/src/VBox/Runtime/common/crypto/rsa-init.cpp
new file mode 100644
index 00000000..2099b377
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/rsa-init.cpp
@@ -0,0 +1,53 @@
+/* $Id: rsa-init.cpp $ */
+/** @file
+ * IPRT - Crypto - RSA, Initialization API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/rsa.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#include "rsa-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-init.h>
+
diff --git a/src/VBox/Runtime/common/crypto/rsa-internal.h b/src/VBox/Runtime/common/crypto/rsa-internal.h
new file mode 100644
index 00000000..93f43a6f
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/rsa-internal.h
@@ -0,0 +1,50 @@
+/* $Id: rsa-internal.h $ */
+/** @file
+ * IPRT - Crypto - RSA, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_rsa_internal_h
+#define IPRT_INCLUDED_SRC_common_crypto_rsa_internal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/** The max number of bits we support in the modulus. */
+#define RTCRRSA_MAX_MODULUS_BITS 16384
+
+#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/rsa-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_rsa_internal_h */
+
diff --git a/src/VBox/Runtime/common/crypto/rsa-sanity.cpp b/src/VBox/Runtime/common/crypto/rsa-sanity.cpp
new file mode 100644
index 00000000..bf4cffaa
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/rsa-sanity.cpp
@@ -0,0 +1,53 @@
+/* $Id: rsa-sanity.cpp $ */
+/** @file
+ * IPRT - Crypto - RSA, Sanity Checkers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/rsa.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#include "rsa-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/crypto/rsa-template.h b/src/VBox/Runtime/common/crypto/rsa-template.h
new file mode 100644
index 00000000..8fc21fed
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/rsa-template.h
@@ -0,0 +1,118 @@
+/* $Id: rsa-template.h $ */
+/** @file
+ * IPRT - Crypto - RSA, Code Generator Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+/*
+ * RSA public key.
+ */
+#define RTASN1TMPL_TYPE RTCRRSAPUBLICKEY
+#define RTASN1TMPL_EXT_NAME RTCrRsaPublicKey
+#define RTASN1TMPL_INT_NAME rtCrRsaPublicKey
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Modulus, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( PublicExponent, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One RSA other prime info.
+ */
+#define RTASN1TMPL_TYPE RTCRRSAOTHERPRIMEINFO
+#define RTASN1TMPL_EXT_NAME RTCrRsaOtherPrimeInfo
+#define RTASN1TMPL_INT_NAME rtCrRsaOtherPrimeInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Prime, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Exponent, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Coefficient, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Sequence of RSA other prime infos.
+ */
+#define RTASN1TMPL_TYPE RTCRRSAOTHERPRIMEINFOS
+#define RTASN1TMPL_EXT_NAME RTCrRsaOtherPrimeInfos
+#define RTASN1TMPL_INT_NAME rtCrRsaOtherPrimeInfos
+RTASN1TMPL_SEQ_OF(RTCRRSAOTHERPRIMEINFO, RTCrRsaOtherPrimeInfo);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * RSA private key.
+ */
+#define RTASN1TMPL_TYPE RTCRRSAPRIVATEKEY
+#define RTASN1TMPL_EXT_NAME RTCrRsaPrivateKey
+#define RTASN1TMPL_INT_NAME rtCrRsaPrivateKey
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Version, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Modulus, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( PublicExponent, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( PrivateExponent, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Prime1, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Prime2, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Exponent1, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Exponent2, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Coefficient, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER_OPT_ITAG_EX( OtherPrimeInfos, RTCRRSAOTHERPRIMEINFOS, RTCrRsaOtherPrimeInfos, ASN1_TAG_SEQUENCE, RTASN1TMPL_ITAG_F_UC, RT_NOTHING);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * RSA Digest Info.
+ */
+#define RTASN1TMPL_TYPE RTCRRSADIGESTINFO
+#define RTASN1TMPL_EXT_NAME RTCrRsaDigestInfo
+#define RTASN1TMPL_INT_NAME rtCrRsaDigestInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( DigestAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER( Digest, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/crypto/spc-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/spc-asn1-decoder.cpp
new file mode 100644
index 00000000..134dd7cc
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/spc-asn1-decoder.cpp
@@ -0,0 +1,88 @@
+/* $Id: spc-asn1-decoder.cpp $ */
+/** @file
+ * IPRT - Crypto - Microsoft SPC / Authenticode, Decoder for ASN.1.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/spc.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "spc-internal.h"
+
+
+/*
+ * One SPC Serialized Object Attribute.
+ */
+
+/** Decode the content of the octet string value if known. */
+static int rtCrSpcSerializedObject_DecodeMore(PRTASN1CURSOR pCursor, uint32_t fFlags,
+ PRTCRSPCSERIALIZEDOBJECT pThis, const char *pszErrorTag)
+
+{
+ RT_NOREF_PV(fFlags); RT_NOREF_PV(pszErrorTag);
+
+ int rc;
+ RTASN1CURSOR SubCursor;
+ if (RTUuidCompareStr(pThis->Uuid.Asn1Core.uData.pUuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR) == 0)
+ {
+ rc = RTAsn1MemAllocZ(&pThis->SerializedData.EncapsulatedAllocation, (void **)&pThis->u.pData, sizeof(*pThis->u.pData));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->SerializedData.pEncapsulated = (PRTASN1CORE)pThis->u.pData;
+ pThis->enmType = RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES;
+
+ rc = RTAsn1CursorInitSubFromCore(pCursor, &pThis->SerializedData.Asn1Core, &SubCursor, "SerializedData");
+ if (RT_SUCCESS(rc))
+ rc = RTCrSpcSerializedObjectAttributes_DecodeAsn1(&SubCursor, 0, pThis->u.pData, "SD");
+ }
+ }
+ else
+ return VINF_SUCCESS;
+ if (RT_SUCCESS(rc))
+ rc = RTAsn1CursorCheckEnd(&SubCursor);
+ return rc;
+}
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/crypto/spc-core.cpp b/src/VBox/Runtime/common/crypto/spc-core.cpp
new file mode 100644
index 00000000..dc33532d
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/spc-core.cpp
@@ -0,0 +1,94 @@
+/* $Id: spc-core.cpp $ */
+/** @file
+ * IPRT - Crypto - Microsoft SPC / Authenticode, Core APIs.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/spc.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "spc-internal.h"
+
+
+RTDECL(int) RTCrSpcSerializedPageHashes_UpdateDerivedData(PRTCRSPCSERIALIZEDPAGEHASHES pThis)
+{
+ pThis->pData = (PCRTCRSPCPEIMAGEPAGEHASHES)pThis->RawData.Asn1Core.uData.pv;
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * SPC Indirect Data Content.
+ */
+
+RTDECL(PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE)
+RTCrSpcIndirectDataContent_GetPeImageObjAttrib(PCRTCRSPCINDIRECTDATACONTENT pThis,
+ RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE enmType)
+{
+ if (pThis->Data.enmType == RTCRSPCAAOVTYPE_PE_IMAGE_DATA)
+ {
+ Assert(RTAsn1ObjId_CompareWithString(&pThis->Data.Type, RTCRSPCPEIMAGEDATA_OID) == 0);
+
+ if ( pThis->Data.uValue.pPeImage
+ && pThis->Data.uValue.pPeImage->T0.File.enmChoice == RTCRSPCLINKCHOICE_MONIKER
+ && RTCrSpcSerializedObject_IsPresent(pThis->Data.uValue.pPeImage->T0.File.u.pMoniker) )
+ {
+ if (pThis->Data.uValue.pPeImage->T0.File.u.pMoniker->enmType == RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES)
+ {
+ Assert(RTUuidCompareStr(pThis->Data.uValue.pPeImage->T0.File.u.pMoniker->Uuid.Asn1Core.uData.pUuid,
+ RTCRSPCSERIALIZEDOBJECT_UUID_STR) == 0);
+ PCRTCRSPCSERIALIZEDOBJECTATTRIBUTES pData = pThis->Data.uValue.pPeImage->T0.File.u.pMoniker->u.pData;
+ if (pData)
+ for (uint32_t i = 0; i < pData->cItems; i++)
+ if (pData->papItems[i]->enmType == enmType)
+ return pData->papItems[i];
+ }
+ }
+ }
+ return NULL;
+}
+
+
+/*
+ * Generate the standard core code.
+ */
+#include <iprt/asn1-generator-core.h>
+
diff --git a/src/VBox/Runtime/common/crypto/spc-init.cpp b/src/VBox/Runtime/common/crypto/spc-init.cpp
new file mode 100644
index 00000000..1f80151b
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/spc-init.cpp
@@ -0,0 +1,55 @@
+/* $Id: spc-init.cpp $ */
+/** @file
+ * IPRT - Crypto - Microsoft SPC / Authenticode, Initialization API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/spc.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "spc-internal.h"
+
+
+/*
+ * Generate the standard core code.
+ */
+#include <iprt/asn1-generator-init.h>
+
diff --git a/src/VBox/Runtime/common/crypto/spc-internal.h b/src/VBox/Runtime/common/crypto/spc-internal.h
new file mode 100644
index 00000000..08f0f4cc
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/spc-internal.h
@@ -0,0 +1,47 @@
+/* $Id: spc-internal.h $ */
+/** @file
+ * IPRT - Crypto - Microsoft SPC / Authenticode, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_spc_internal_h
+#define IPRT_INCLUDED_SRC_common_crypto_spc_internal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/spc-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_spc_internal_h */
+
diff --git a/src/VBox/Runtime/common/crypto/spc-sanity.cpp b/src/VBox/Runtime/common/crypto/spc-sanity.cpp
new file mode 100644
index 00000000..3c6aadbc
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/spc-sanity.cpp
@@ -0,0 +1,179 @@
+/* $Id: spc-sanity.cpp $ */
+/** @file
+ * IPRT - Crypto - Microsoft SPC / Authenticode, Sanity Checkers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/spc.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/uuid.h>
+
+#include "spc-internal.h"
+
+
+RTDECL(int) RTCrSpcIndirectDataContent_CheckSanityEx(PCRTCRSPCINDIRECTDATACONTENT pIndData,
+ PCRTCRPKCS7SIGNEDDATA pSignedData,
+ uint32_t fFlags,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * Match up the digest algorithms (page 8, v1.0).
+ */
+ if (pSignedData->SignerInfos.cItems != 1)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_NOT_EXACTLY_ONE_SIGNER_INFOS,
+ "SpcIndirectDataContent expects SignedData to have exactly one SignerInfos entries, found: %u",
+ pSignedData->SignerInfos.cItems);
+ if (pSignedData->DigestAlgorithms.cItems != 1)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_NOT_EXACTLY_ONE_DIGEST_ALGO,
+ "SpcIndirectDataContent expects SignedData to have exactly one DigestAlgorithms entries, found: %u",
+ pSignedData->DigestAlgorithms.cItems);
+
+ if (RTCrX509AlgorithmIdentifier_Compare(&pIndData->DigestInfo.DigestAlgorithm, /** @todo not entirely sure about this check... */
+ &pSignedData->SignerInfos.papItems[0]->DigestAlgorithm) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_SIGNED_IND_DATA_DIGEST_ALGO_MISMATCH,
+ "SpcIndirectDataContent DigestInfo and SignerInfos algorithms mismatch: %s vs %s",
+ pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId,
+ pSignedData->SignerInfos.papItems[0]->DigestAlgorithm.Algorithm.szObjId);
+
+ if (RTCrX509AlgorithmIdentifier_Compare(&pIndData->DigestInfo.DigestAlgorithm,
+ pSignedData->DigestAlgorithms.papItems[0]) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_IND_DATA_DIGEST_ALGO_NOT_IN_DIGEST_ALGOS,
+ "SpcIndirectDataContent DigestInfo and SignedData.DigestAlgorithms[0] mismatch: %s vs %s",
+ pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId,
+ pSignedData->DigestAlgorithms.papItems[0]->Algorithm.szObjId);
+
+ if (fFlags & RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH)
+ {
+ if ( RTCrX509AlgorithmIdentifier_GetDigestType(&pIndData->DigestInfo.DigestAlgorithm, true /*fPureDigestsOnly*/)
+ == RTDIGESTTYPE_INVALID)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_UNKNOWN_DIGEST_ALGO,
+ "SpcIndirectDataContent DigestAlgortihm is not known: %s",
+ pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId);
+ }
+
+ uint32_t cbDigest = RTCrX509AlgorithmIdentifier_GetDigestSize(&pIndData->DigestInfo.DigestAlgorithm,
+ true /*fPureDigestsOnly*/);
+ if ( pIndData->DigestInfo.Digest.Asn1Core.cb != cbDigest
+ && (cbDigest != UINT32_MAX || (fFlags & RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH)))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_IND_DATA_DIGEST_SIZE_MISMATCH,
+ "SpcIndirectDataContent Digest size mismatch with algorithm: %u, expected %u (%s)",
+ pIndData->DigestInfo.Digest.Asn1Core.cb, cbDigest,
+ pIndData->DigestInfo.DigestAlgorithm.Algorithm.szObjId);
+
+ /*
+ * Data.
+ */
+ if (fFlags & RTCRSPCINDIRECTDATACONTENT_SANITY_F_PE_IMAGE)
+ {
+ if ( pIndData->Data.enmType != RTCRSPCAAOVTYPE_PE_IMAGE_DATA
+ || RTAsn1ObjId_CompareWithString(&pIndData->Data.Type, RTCRSPCPEIMAGEDATA_OID) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_EXPECTED_PE_IMAGE_DATA,
+ "SpcIndirectDataContent.Data.Type is %s, expected %s (SpcPeImageData) [enmType=%d]",
+ pIndData->Data.Type.szObjId, RTCRSPCPEIMAGEDATA_OID, pIndData->Data.enmType);
+ if ( pIndData->Data.uValue.pPeImage
+ || !RTCrSpcPeImageData_IsPresent(pIndData->Data.uValue.pPeImage) )
+ return RTErrInfoSet(pErrInfo, VERR_CR_SPC_PEIMAGE_DATA_NOT_PRESENT,
+ "SpcIndirectDataContent.Data.uValue/PEImage is missing");
+
+ if ( pIndData->Data.uValue.pPeImage->T0.File.enmChoice == RTCRSPCLINKCHOICE_MONIKER
+ && RTCrSpcSerializedObject_IsPresent(pIndData->Data.uValue.pPeImage->T0.File.u.pMoniker) )
+ {
+ PCRTCRSPCSERIALIZEDOBJECT pObj = pIndData->Data.uValue.pPeImage->T0.File.u.pMoniker;
+
+ if (pObj->Uuid.Asn1Core.cb != sizeof(RTUUID))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_BAD_MONIKER_UUID,
+ "SpcIndirectDataContent...MonikerT1.Uuid incorrect size: %u, expected %u.",
+ pObj->Uuid.Asn1Core.cb, sizeof(RTUUID));
+ if (RTUuidCompareStr(pObj->Uuid.Asn1Core.uData.pUuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_UNKNOWN_MONIKER_UUID,
+ "SpcIndirectDataContent...MonikerT1.Uuid mismatch: %RTuuid, expected %s.",
+ pObj->Uuid.Asn1Core.uData.pUuid, RTCRSPCSERIALIZEDOBJECT_UUID_STR);
+
+ if (pObj->enmType != RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_BAD_MONIKER_CHOICE,
+ "SpcIndirectDataContent...pMoniker->enmType=%d, expected %d.",
+ pObj->enmType, RTCRSPCSERIALIZEDOBJECTTYPE_ATTRIBUTES);
+ if (!pObj->u.pData)
+ return RTErrInfoSet(pErrInfo, VERR_CR_SPC_MONIKER_BAD_DATA,
+ "SpcIndirectDataContent...pMoniker->pData is NULL.");
+
+ uint32_t cPageHashTabs = 0;
+ for (uint32_t i = 0; i < pObj->u.pData->cItems; i++)
+ {
+ PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE pAttr = pObj->u.pData->papItems[i];
+ if ( RTAsn1ObjId_CompareWithString(&pAttr->Type, RTCRSPC_PE_IMAGE_HASHES_V1_OID) == 0
+ || RTAsn1ObjId_CompareWithString(&pAttr->Type, RTCRSPC_PE_IMAGE_HASHES_V2_OID) == 0 )
+ {
+ cPageHashTabs++;
+ AssertPtr(pAttr->u.pPageHashes->pData);
+ }
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_PEIMAGE_UNKNOWN_ATTRIBUTE,
+ "SpcIndirectDataContent...MonikerT1 unknown attribute %u: %s.",
+ i, pAttr->Type.szObjId);
+ }
+ if (cPageHashTabs > 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_SPC_PEIMAGE_MULTIPLE_HASH_TABS,
+ "SpcIndirectDataContent...MonikerT1 multiple page hash attributes (%u).", cPageHashTabs);
+
+ }
+ else if ( pIndData->Data.uValue.pPeImage->T0.File.enmChoice == RTCRSPCLINKCHOICE_FILE
+ && RTCrSpcString_IsPresent(&pIndData->Data.uValue.pPeImage->T0.File.u.pT2->File) )
+ {
+ /* Could check for "<<<Obsolete>>>" here, but it's really irrelevant. */
+ }
+ else if ( pIndData->Data.uValue.pPeImage->T0.File.enmChoice == RTCRSPCLINKCHOICE_URL
+ && RTAsn1String_IsPresent(pIndData->Data.uValue.pPeImage->T0.File.u.pUrl) )
+ return RTErrInfoSet(pErrInfo, VERR_CR_SPC_PEIMAGE_URL_UNEXPECTED,
+ "SpcIndirectDataContent.Data.uValue.pPeImage->File is an URL, expected object Moniker or File.");
+ else
+ return RTErrInfoSet(pErrInfo, VERR_CR_SPC_PEIMAGE_NO_CONTENT,
+ "SpcIndirectDataContent.Data.uValue.pPeImage->File has no content");
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate the standard core code.
+ */
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/crypto/spc-template.h b/src/VBox/Runtime/common/crypto/spc-template.h
new file mode 100644
index 00000000..510b66a6
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/spc-template.h
@@ -0,0 +1,198 @@
+/* $Id: spc-template.h $ */
+/** @file
+ * IPRT - Crypto - Microsoft SPC / Authenticode, Code Generator Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+
+/*
+ * One SPC Serialized Page Hashes V2 Object.
+ */
+#define RTASN1TMPL_TYPE RTCRSPCSERIALIZEDPAGEHASHES
+#define RTASN1TMPL_EXT_NAME RTCrSpcSerializedPageHashes
+#define RTASN1TMPL_INT_NAME rtCrSpcSerializedPageHashes
+RTASN1TMPL_BEGIN_SETCORE();
+RTASN1TMPL_MEMBER( RawData, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_EXEC_DECODE( rc = RTCrSpcSerializedPageHashes_UpdateDerivedData(pThis) ) /* no ; */
+RTASN1TMPL_EXEC_CLONE( rc = RTCrSpcSerializedPageHashes_UpdateDerivedData(pThis) ) /* no ; */
+RTASN1TMPL_END_SETCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One SPC Serialized Object Attribute.
+ */
+#define RTASN1TMPL_TYPE RTCRSPCSERIALIZEDOBJECTATTRIBUTE
+#define RTASN1TMPL_EXT_NAME RTCrSpcSerializedObjectAttribute
+#define RTASN1TMPL_INT_NAME rtCrSpcSerializedObjectAttribute
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Type, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER_DYN_BEGIN( Type, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE, enmType, Allocation);
+RTASN1TMPL_MEMBER_DYN( u, pPageHashes, V1Hashes, RTCRSPCSERIALIZEDPAGEHASHES, RTCrSpcSerializedPageHashes, Allocation,
+ Type, enmType, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1, RTCRSPC_PE_IMAGE_HASHES_V1_OID);
+RTASN1TMPL_MEMBER_DYN( u, pPageHashes, V2Hashes, RTCRSPCSERIALIZEDPAGEHASHES, RTCrSpcSerializedPageHashes, Allocation,
+ Type, enmType, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V2, RTCRSPC_PE_IMAGE_HASHES_V2_OID);
+RTASN1TMPL_MEMBER_DYN_DEFAULT( u, pCore, RTASN1CORE, RTAsn1Core, Allocation,
+ Type, enmType, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_UNKNOWN);
+RTASN1TMPL_MEMBER_DYN_END( Type, RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE, enmType, Allocation);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+/*
+ * Set of SPC Serialized Object Attributes.
+ */
+#define RTASN1TMPL_TYPE RTCRSPCSERIALIZEDOBJECTATTRIBUTES
+#define RTASN1TMPL_EXT_NAME RTCrSpcSerializedObjectAttributes
+#define RTASN1TMPL_INT_NAME rtCrSpcSerializedObjectAttributes
+RTASN1TMPL_SET_OF(RTCRSPCSERIALIZEDOBJECTATTRIBUTE, RTCrSpcSerializedObjectAttribute);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One SPC Serialized Object.
+ */
+#define RTASN1TMPL_TYPE RTCRSPCSERIALIZEDOBJECT
+#define RTASN1TMPL_EXT_NAME RTCrSpcSerializedObject
+#define RTASN1TMPL_INT_NAME rtCrSpcSerializedObject
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER_EX( Uuid, RTASN1OCTETSTRING, RTAsn1OctetString,
+ RTASN1TMPL_MEMBER_CONSTR_MIN_MAX(Uuid, RTASN1OCTETSTRING, RTAsn1OctetString, 16, 16, RT_NOTHING));
+RTASN1TMPL_MEMBER( SerializedData, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_EXEC_DECODE( rc = rtCrSpcSerializedObject_DecodeMore(pCursor, fFlags, pThis, pszErrorTag) ) /* no ; */
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Choosy SPC strings.
+ */
+#define RTASN1TMPL_TYPE RTCRSPCSTRING
+#define RTASN1TMPL_EXT_NAME RTCrSpcString
+#define RTASN1TMPL_INT_NAME rtCrSpcString
+RTASN1TMPL_BEGIN_PCHOICE();
+RTASN1TMPL_PCHOICE_ITAG_CP( 0, RTCRSPCSTRINGCHOICE_UCS2, u.pUcs2, Ucs2, RTASN1STRING, RTAsn1BmpString);
+RTASN1TMPL_PCHOICE_ITAG_CP( 1, RTCRSPCSTRINGCHOICE_ASCII, u.pAscii, Ascii, RTASN1STRING, RTAsn1Ia5String);
+RTASN1TMPL_END_PCHOICE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * SPC Link.
+ */
+#define RTASN1TMPL_TYPE RTCRSPCLINK
+#define RTASN1TMPL_EXT_NAME RTCrSpcLink
+#define RTASN1TMPL_INT_NAME rtCrSpcLink
+RTASN1TMPL_BEGIN_PCHOICE();
+RTASN1TMPL_PCHOICE_ITAG_CP( 0, RTCRSPCLINKCHOICE_URL, u.pUrl, Url, RTASN1STRING, RTAsn1Ia5String);
+RTASN1TMPL_PCHOICE_ITAG( 1, RTCRSPCLINKCHOICE_MONIKER, u.pMoniker, Moniker, RTCRSPCSERIALIZEDOBJECT, RTCrSpcSerializedObject);
+RTASN1TMPL_PCHOICE_XTAG( 2, RTCRSPCLINKCHOICE_FILE, u.pT2, CtxTag2, File, RTCRSPCSTRING, RTCrSpcString);
+RTASN1TMPL_END_PCHOICE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * SPC PE Image Data.
+ *
+ * Note! This is not correctly declared in available specifications. The file
+ * member is tagged. Seeing the '--#public--' comment in the specs,
+ * one can't only guess that there are other alternatives in that part
+ * of the structure that microsoft does not wish to document.
+ */
+#define RTASN1TMPL_TYPE RTCRSPCPEIMAGEDATA
+#define RTASN1TMPL_EXT_NAME RTCrSpcPeImageData
+#define RTASN1TMPL_INT_NAME rtCrSpcPeImageData
+RTASN1TMPL_BEGIN_SEQCORE();
+/** @todo The flags defaults to includeResources. Could be expressed here rather
+ * than left to the user to deal with. */
+RTASN1TMPL_MEMBER_OPT_ITAG_EX( Flags, RTASN1BITSTRING, RTAsn1BitString, ASN1_TAG_BIT_STRING, RTASN1TMPL_ITAG_F_UP,
+ RTASN1TMPL_MEMBER_CONSTR_BITSTRING_MIN_MAX(Flags, 0, 3, RT_NOTHING));
+RTASN1TMPL_MEMBER_OPT_XTAG_EX( T0, CtxTag0, File, RTCRSPCLINK, RTCrSpcLink, 0, \
+ RTASN1TMPL_MEMBER_CONSTR_PRESENT(T0.File, RTCrSpcLink, RT_NOTHING));
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * SPC Attribute Type And Optional Value.
+ *
+ * Note! The value doesn't look very optional in available examples and specs.
+ * The available specs also claim there is an explicit 0 tag around the
+ * data, which isn't there is in signed executables. Gotta love Microsoft...
+ */
+#define RTASN1TMPL_TYPE RTCRSPCATTRIBUTETYPEANDOPTIONALVALUE
+#define RTASN1TMPL_EXT_NAME RTCrSpcAttributeTypeAndOptionalValue
+#define RTASN1TMPL_INT_NAME rtCrSpcAttributeTypeAndOptionalValue
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Type, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER_DYN_BEGIN( Type, RTCRSPCAAOVTYPE, enmType, Allocation);
+RTASN1TMPL_MEMBER_DYN( uValue, pPeImage, PeImage, RTCRSPCPEIMAGEDATA, RTCrSpcPeImageData, Allocation,
+ Type, enmType, RTCRSPCAAOVTYPE_PE_IMAGE_DATA, RTCRSPCPEIMAGEDATA_OID);
+RTASN1TMPL_MEMBER_DYN_DEFAULT( uValue, pCore, RTASN1CORE, RTAsn1Core, Allocation,
+ Type, enmType, RTCRSPCAAOVTYPE_UNKNOWN);
+RTASN1TMPL_MEMBER_DYN_END( Type, RTCRSPCAAOVTYPE, enmType, Allocation);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * SPC Indirect Data Content.
+ */
+#define RTASN1TMPL_TYPE RTCRSPCINDIRECTDATACONTENT
+#define RTASN1TMPL_EXT_NAME RTCrSpcIndirectDataContent
+#define RTASN1TMPL_INT_NAME rtCrSpcIndirectDataContent
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Data, RTCRSPCATTRIBUTETYPEANDOPTIONALVALUE, RTCrSpcAttributeTypeAndOptionalValue);
+RTASN1TMPL_MEMBER( DigestInfo, RTCRPKCS7DIGESTINFO, RTCrPkcs7DigestInfo);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/crypto/ssl-openssl.cpp b/src/VBox/Runtime/common/crypto/ssl-openssl.cpp
new file mode 100644
index 00000000..1efafe74
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/ssl-openssl.cpp
@@ -0,0 +1,507 @@
+/** @file
+ * IPRT - Crypto - Secure Socket Layer (SSL) / Transport Security Layer (TLS).
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+#ifdef IPRT_WITH_OPENSSL /* whole file */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+# include "internal/iprt.h"
+# include <iprt/crypto/ssl.h>
+
+# include <iprt/asm.h>
+# include <iprt/assert.h>
+# include <iprt/err.h>
+# include <iprt/file.h>
+# include <iprt/mem.h>
+# include <iprt/string.h>
+
+# include "internal/magics.h"
+
+# include "internal/iprt-openssl.h"
+# include "internal/openssl-pre.h"
+# include <openssl/ssl.h>
+# include <openssl/tls1.h>
+# include "internal/openssl-post.h"
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/**
+ * SSL instance data for OpenSSL.
+ */
+typedef struct RTCRSSLINT
+{
+ /** Magic value (RTCRSSLINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference count. */
+ uint32_t volatile cRefs;
+ /** The SSL context. */
+ SSL_CTX *pCtx;
+} RTCRSSLINT;
+
+/**
+ * SSL session instance data for OpenSSL.
+ */
+typedef struct RTCRSSLSESSIONINT
+{
+ /** Magic value (RTCRSSLSESSIONINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference count. */
+ uint32_t volatile cRefs;
+ /** RTCRSSLSESSION_F_XXX. */
+ uint32_t fFlags;
+
+ /** The SSL instance. */
+ SSL *pSsl;
+ /** The socket BIO instance. */
+ BIO *pBio;
+} RTCRSSLSESSIONINT;
+
+
+
+RTDECL(int) RTCrSslCreate(PRTCRSSL phSsl, uint32_t fFlags)
+{
+ AssertPtr(phSsl);
+ *phSsl = NIL_RTCRSSL;
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+
+ SSL_library_init();
+
+ /*
+ * We aim at TLSv1 or higher here by default.
+ */
+# if OPENSSL_VERSION_NUMBER >= 0x10100000
+ const SSL_METHOD *pSslMethod = TLS_method();
+# elif OPENSSL_VERSION_NUMBER >= 0x10002000
+ const SSL_METHOD *pSslMethod = SSLv23_method();
+# elif OPENSSL_VERSION_NUMBER >= 0x10000000
+ const SSL_METHOD *pSslMethod = TLSv1_method();
+# else
+ SSL_METHOD *pSslMethod = TLSv1_method();
+# endif
+ if (pSslMethod)
+ {
+ RTCRSSLINT *pThis = (RTCRSSLINT *)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->pCtx = SSL_CTX_new(pSslMethod);
+ if (pThis->pCtx)
+ {
+ /* Help with above aim. */
+# if OPENSSL_VERSION_NUMBER >= 0x10100000
+# ifndef SSL_CTX_get_min_proto_version
+/* Some older OpenSSL 1.1.0 releases lack the getters, officially they were
+ * added with 1.1.1 but someone cherry picked them, just maybe too late. */
+# define SSL_CTX_get_min_proto_version(ctx) (0)
+# endif
+ if (SSL_CTX_get_min_proto_version(pThis->pCtx) < TLS1_VERSION)
+ SSL_CTX_set_min_proto_version(pThis->pCtx, TLS1_VERSION);
+# elif OPENSSL_VERSION_NUMBER >= 0x10002000
+ SSL_CTX_set_options(pThis->pCtx, SSL_OP_NO_SSLv2);
+ SSL_CTX_set_options(pThis->pCtx, SSL_OP_NO_SSLv3);
+# endif
+
+ /*
+ * Complete the instance and return it.
+ */
+ pThis->u32Magic = RTCRSSLINT_MAGIC;
+ pThis->cRefs = 1;
+
+ *phSsl = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pThis);
+ }
+ return VERR_NO_MEMORY;
+ }
+ return VERR_NOT_SUPPORTED;
+}
+
+
+RTDECL(uint32_t) RTCrSslRetain(RTCRSSL hSsl)
+{
+ RTCRSSLINT *pThis = hSsl;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < 1024);
+ return cRefs;
+}
+
+
+/**
+ * Worker for RTCrSslRelease.
+ */
+static int rtCrSslDestroy(RTCRSSLINT *pThis)
+{
+ ASMAtomicWriteU32(&pThis->u32Magic, ~RTCRSSLINT_MAGIC);
+ SSL_CTX_free(pThis->pCtx);
+ pThis->pCtx = NULL;
+ RTMemFree(pThis);
+ return 0;
+}
+
+
+RTDECL(uint32_t) RTCrSslRelease(RTCRSSL hSsl)
+{
+ RTCRSSLINT *pThis = hSsl;
+ if (pThis == NIL_RTCRSSL)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 1024);
+ if (cRefs == 0)
+ return rtCrSslDestroy(pThis);
+ return cRefs;
+}
+
+
+RTDECL(int) RTCrSslSetCertificateFile(RTCRSSL hSsl, const char *pszFile, uint32_t fFlags)
+{
+ RTCRSSLINT *pThis = hSsl;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(fFlags & ~RTCRSSL_FILE_F_ASN1), VERR_INVALID_FLAGS);
+
+ int rcOssl = SSL_CTX_use_certificate_file(pThis->pCtx, pszFile,
+ RTCRSSL_FILE_F_ASN1 & fFlags ? SSL_FILETYPE_ASN1 : SSL_FILETYPE_PEM);
+ if (rcOssl != 0)
+ return VINF_SUCCESS;
+ return !pszFile || !*pszFile || !RTFileExists(pszFile) ? VERR_FILE_NOT_FOUND : VERR_OPEN_FAILED; /** @todo Better status codes */
+}
+
+
+RTDECL(int) RTCrSslSetPrivateKeyFile(RTCRSSL hSsl, const char *pszFile, uint32_t fFlags)
+{
+ RTCRSSLINT *pThis = hSsl;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(fFlags & ~RTCRSSL_FILE_F_ASN1), VERR_INVALID_FLAGS);
+
+ int rcOssl = SSL_CTX_use_PrivateKey_file(pThis->pCtx, pszFile,
+ RTCRSSL_FILE_F_ASN1 & fFlags ? SSL_FILETYPE_ASN1 : SSL_FILETYPE_PEM);
+ if (rcOssl != 0)
+ return VINF_SUCCESS;
+ return !pszFile || !*pszFile || !RTFileExists(pszFile) ? VERR_FILE_NOT_FOUND : VERR_OPEN_FAILED; /** @todo Better status codes */
+}
+
+
+RTDECL(int) RTCrSslLoadTrustedRootCerts(RTCRSSL hSsl, const char *pszFile, const char *pszDir)
+{
+ RTCRSSLINT *pThis = hSsl;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE);
+
+ int rcOssl = SSL_CTX_load_verify_locations(pThis->pCtx, pszFile, pszDir);
+ if (rcOssl != 0)
+ return VINF_SUCCESS;
+
+ if (!pszFile || !*pszFile || !RTFileExists(pszFile))
+ return VERR_FILE_NOT_FOUND;
+ return VERR_OPEN_FAILED; /** @todo Better status codes */
+}
+
+
+RTDECL(int) RTCrSslSetNoPeerVerify(RTCRSSL hSsl)
+{
+ RTCRSSLINT *pThis = hSsl;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE);
+
+ SSL_CTX_set_verify(pThis->pCtx, SSL_VERIFY_NONE, NULL);
+ return VINF_SUCCESS;
+}
+
+
+
+//RTDECL(int) RTCrSslCreateSession(RTCRSSL hSsl, RTSOCKET hSocket, uint32_t fFlags, PRTCRSSLSESSION phSslConn);
+
+RTDECL(int) RTCrSslCreateSessionForNativeSocket(RTCRSSL hSsl, RTHCINTPTR hNativeSocket, uint32_t fFlags,
+ PRTCRSSLSESSION phSslSession)
+{
+ /*
+ * Validate input.
+ */
+ *phSslSession = NIL_RTCRSSLSESSION;
+
+ RTCRSSLINT *pThis = hSsl;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(fFlags & ~RTCRSSLSESSION_F_NON_BLOCKING), VERR_INVALID_FLAGS);
+
+ /*
+ * Create a new session.
+ */
+ int rc = VERR_NO_MEMORY;
+ RTCRSSLSESSIONINT *pSession = (RTCRSSLSESSIONINT *)RTMemAllocZ(sizeof(*pSession));
+ if (pSession)
+ {
+ pSession->pSsl = SSL_new(pThis->pCtx);
+ if (pSession->pSsl)
+ {
+ /* Disable read-ahead if non-blocking socket relying on select/poll. */
+ if (fFlags & RTCRSSLSESSION_F_NON_BLOCKING)
+ SSL_set_read_ahead(pSession->pSsl, 0);
+
+ /* Create a wrapper for the socket handle. */
+ pSession->pBio = BIO_new_socket(hNativeSocket, BIO_NOCLOSE);
+ if (pSession->pBio)
+ {
+# if (OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)) || LIBRESSL_VERSION_NUMBER >= 0x2070000f
+ BIO_up_ref(pSession->pBio); /* our reference. */
+# endif
+ SSL_set_bio(pSession->pSsl, pSession->pBio, pSession->pBio);
+
+ /*
+ * Done.
+ */
+ pSession->cRefs = 1;
+ pSession->u32Magic = RTCRSSLSESSIONINT_MAGIC;
+ *phSslSession = pSession;
+ return VINF_SUCCESS;
+ }
+
+ SSL_free(pSession->pSsl);
+ pSession->pSsl = NULL;
+ }
+ RTMemFree(pSession);
+ }
+ return rc;
+}
+
+
+/*********************************************************************************************************************************
+* Session implementation. *
+*********************************************************************************************************************************/
+
+RTDECL(uint32_t) RTCrSslSessionRetain(RTCRSSLSESSION hSslSession)
+{
+ RTCRSSLSESSIONINT *pThis = hSslSession;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < 1024);
+ return cRefs;
+}
+
+
+/**
+ * Worker for RTCrSslRelease.
+ */
+static int rtCrSslSessionDestroy(RTCRSSLSESSIONINT *pThis)
+{
+ ASMAtomicWriteU32(&pThis->u32Magic, ~RTCRSSLSESSIONINT_MAGIC);
+ SSL_free(pThis->pSsl);
+ pThis->pSsl = NULL;
+# if (OPENSSL_VERSION_NUMBER >= 0x10100000 && !defined(LIBRESSL_VERSION_NUMBER)) || LIBRESSL_VERSION_NUMBER >= 0x2070000f
+ BIO_free(pThis->pBio);
+# endif
+ pThis->pBio = NULL;
+ RTMemFree(pThis);
+ return 0;
+}
+
+
+RTDECL(uint32_t) RTCrSslSessionRelease(RTCRSSLSESSION hSslSession)
+{
+ RTCRSSLSESSIONINT *pThis = hSslSession;
+ if (pThis == NIL_RTCRSSLSESSION)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 1024);
+ if (cRefs == 0)
+ return rtCrSslSessionDestroy(pThis);
+ return cRefs;
+}
+
+
+RTDECL(int) RTCrSslSessionAccept(RTCRSSLSESSION hSslSession, uint32_t fFlags)
+{
+ RTCRSSLSESSIONINT *pThis = hSslSession;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+
+ int rcOssl = SSL_accept(pThis->pSsl);
+ if (rcOssl > 0)
+ return VINF_SUCCESS;
+
+ /** @todo better status codes. */
+ if (BIO_should_retry(pThis->pBio))
+ return VERR_TRY_AGAIN;
+ return VERR_NOT_SUPPORTED;
+}
+
+
+RTDECL(int) RTCrSslSessionConnect(RTCRSSLSESSION hSslSession, uint32_t fFlags)
+{
+ RTCRSSLSESSIONINT *pThis = hSslSession;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+
+ int rcOssl = SSL_connect(pThis->pSsl);
+ if (rcOssl > 0)
+ return VINF_SUCCESS;
+
+ /** @todo better status codes. */
+ if (BIO_should_retry(pThis->pBio))
+ return VERR_TRY_AGAIN;
+ return VERR_NOT_SUPPORTED;
+}
+
+
+RTDECL(const char *) RTCrSslSessionGetVersion(RTCRSSLSESSION hSslSession)
+{
+ RTCRSSLSESSIONINT *pThis = hSslSession;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, NULL);
+
+ return SSL_get_version(pThis->pSsl);
+}
+
+
+RTDECL(int) RTCrSslSessionGetCertIssuerNameAsString(RTCRSSLSESSION hSslSession, char *pszBuf, size_t cbBuf, size_t *pcbActual)
+{
+ RTCRSSLSESSIONINT *pThis = hSslSession;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNull(pszBuf);
+ AssertPtrNull(pcbActual);
+ if (pcbActual)
+ *pcbActual = 0;
+
+ /*
+ * Get and format the certificate issuer name.
+ */
+ int rc = VERR_NOT_AVAILABLE;
+ X509 *pCert = SSL_get_certificate(pThis->pSsl);
+ if (pCert)
+ {
+ X509_NAME *pIssuer = X509_get_issuer_name(pCert);
+ if (pIssuer)
+ {
+ char *pszSrc = X509_NAME_oneline(pIssuer, NULL, 0);
+ if (pszSrc)
+ {
+ /*
+ * Copy out the result and free it.
+ */
+ size_t cbNeeded = strlen(pszSrc) + 1;
+ if (pcbActual)
+ *pcbActual = cbNeeded;
+
+ if (pszBuf != NULL && cbBuf > 0)
+ {
+ if (cbBuf >= cbNeeded)
+ {
+ memcpy(pszBuf, pszSrc, cbNeeded);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ memcpy(pszBuf, pszSrc, cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ OPENSSL_free(pszSrc);
+ }
+ }
+ }
+ return rc;
+}
+
+
+RTDECL(bool) RTCrSslSessionPending(RTCRSSLSESSION hSslSession)
+{
+ RTCRSSLSESSIONINT *pThis = hSslSession;
+ AssertPtrReturn(pThis, true);
+ AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, true);
+
+ return SSL_pending(pThis->pSsl) != 0;
+}
+
+
+RTDECL(ssize_t) RTCrSslSessionRead(RTCRSSLSESSION hSslSession, void *pvBuf, size_t cbToRead)
+{
+ RTCRSSLSESSIONINT *pThis = hSslSession;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE);
+
+ Assert((size_t)(int)cbToRead == cbToRead);
+
+ int cbActual = SSL_read(pThis->pSsl, pvBuf, (int)cbToRead);
+ if (cbActual > 0)
+ return cbActual;
+ if (BIO_should_retry(pThis->pBio))
+ return VERR_TRY_AGAIN;
+ return VERR_READ_ERROR; /** @todo better status codes. */
+}
+
+
+RTDECL(ssize_t) RTCrSslSessionWrite(RTCRSSLSESSION hSslSession, void const *pvBuf, size_t cbToWrite)
+{
+ RTCRSSLSESSIONINT *pThis = hSslSession;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSSLSESSIONINT_MAGIC, VERR_INVALID_HANDLE);
+
+ Assert((size_t)(int)cbToWrite == cbToWrite);
+ Assert(cbToWrite != 0 /* undefined behavior if zero */);
+
+ int cbActual = SSL_write(pThis->pSsl, pvBuf, (int)cbToWrite);
+ if (cbActual > 0)
+ return cbActual;
+ if (BIO_should_retry(pThis->pBio))
+ return VERR_TRY_AGAIN;
+ return VERR_WRITE_ERROR; /** @todo better status codes. */
+}
+
+#endif /* IPRT_WITH_OPENSSL */
+
diff --git a/src/VBox/Runtime/common/crypto/store-cert-add-basic.cpp b/src/VBox/Runtime/common/crypto/store-cert-add-basic.cpp
new file mode 100644
index 00000000..a98f75a0
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/store-cert-add-basic.cpp
@@ -0,0 +1,865 @@
+/* $Id: store-cert-add-basic.cpp $ */
+/** @file
+ * IPRT - Cryptographic (Certificate) Store, RTCrStoreCertAddFromDir.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/store.h>
+
+#include <iprt/assert.h>
+#include <iprt/crypto/pem.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+
+#include "x509-internal.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** BEGIN CERTIFICATE / END CERTIFICATE. */
+static RTCRPEMMARKERWORD const g_aWords_Certificate[] =
+{
+ { RT_STR_TUPLE("CERTIFICATE") }
+};
+
+/** BEGIN TRUSTED CERTIFICATE / END TRUSTED CERTIFICATE. */
+static RTCRPEMMARKERWORD const g_aWords_TrustedCertificate[] =
+{
+ { RT_STR_TUPLE("TRUSTED") },
+ { RT_STR_TUPLE("CERTIFICATE") }
+};
+
+/** BEGIN X509 CERTIFICATE / END X509 CERTIFICATE. (old) */
+static RTCRPEMMARKERWORD const g_aWords_X509Certificate[] =
+{
+ { RT_STR_TUPLE("X509") },
+ { RT_STR_TUPLE("CERTIFICATE") }
+};
+
+/**
+ * X509 Certificate markers.
+ *
+ * @remark See crypto/pem/pem.h in OpenSSL for a matching list.
+ */
+static RTCRPEMMARKER const g_aX509CertificateMarkers[3] =
+{
+ { g_aWords_Certificate, RT_ELEMENTS(g_aWords_Certificate) },
+ { g_aWords_TrustedCertificate, RT_ELEMENTS(g_aWords_TrustedCertificate) },
+ { g_aWords_X509Certificate, RT_ELEMENTS(g_aWords_X509Certificate) }
+};
+
+
+
+#ifdef RT_STRICT
+/**
+ * Checks if we've found all the certificates already.
+ *
+ * @returns true if all found, false if not.
+ * @param afFound Indicator array.
+ * @param cWanted Number of wanted certificates.
+ */
+DECLINLINE(bool) rtCrStoreAllDone(bool const *afFound, size_t cWanted)
+{
+ while (cWanted-- > 0)
+ if (!afFound[cWanted])
+ return false;
+ return true;
+}
+#endif /* RT_STRICT */
+
+
+/**
+ * Checks if the given certificate specs matches the given wanted poster.
+ *
+ * @returns true if match, false if not.
+ * @param pWanted The certificate wanted poster.
+ * @param cbEncoded The candidate certificate encoded size.
+ * @param paSha1 The candidate certificate SHA-1 fingerprint.
+ * @param paSha512 The candidate certificate SHA-512 fingerprint.
+ * @param pCert The decoded candidate certificate, optional. If not
+ * given the result will be uncertain.
+ */
+DECLINLINE(bool) rtCrStoreIsCertEqualToWanted(PCRTCRCERTWANTED pWanted,
+ size_t cbEncoded,
+ uint8_t const pabSha1[RTSHA1_HASH_SIZE],
+ uint8_t const pabSha512[RTSHA512_HASH_SIZE],
+ PCRTCRX509CERTIFICATE pCert)
+{
+ if ( pWanted->cbEncoded != cbEncoded
+ && pWanted->cbEncoded != 0)
+ return false;
+
+ if ( pWanted->fSha1Fingerprint
+ && memcmp(pWanted->abSha1, pabSha1, RTSHA1_HASH_SIZE) != 0)
+ return false;
+
+ if ( pWanted->fSha512Fingerprint
+ && memcmp(pWanted->abSha512, pabSha512, RTSHA512_HASH_SIZE) != 0)
+ return false;
+
+ if ( pWanted->pszSubject
+ && pCert
+ && !RTCrX509Name_MatchWithString(&pCert->TbsCertificate.Subject, pWanted->pszSubject))
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Checks if a certificate is wanted.
+ *
+ * @returns true if match, false if not.
+ * @param paWanted The certificate wanted posters.
+ * @param cWanted The number of wanted posters.
+ * @param apfFound Found initicators running paralell to @a paWanted.
+ * @param cbEncoded The candidate certificate encoded size.
+ * @param paSha1 The candidate certificate SHA-1 fingerprint.
+ * @param paSha512 The candidate certificate SHA-512 fingerprint.
+ * @param pCert The decoded candidate certificate, optional. If not
+ * given the result will be uncertain.
+ */
+DECLINLINE(bool) rtCrStoreIsCertWanted(PCRTCRCERTWANTED paWanted, size_t cWanted, bool const *pafFound, size_t cbEncoded,
+ uint8_t const pabSha1[RTSHA1_HASH_SIZE], uint8_t const pabSha512[RTSHA512_HASH_SIZE],
+ PCRTCRX509CERTIFICATE pCert)
+{
+ for (size_t iCert = 0; iCert < cWanted; iCert++)
+ if (!pafFound[iCert])
+ if (rtCrStoreIsCertEqualToWanted(&paWanted[iCert], cbEncoded, pabSha1, pabSha512, pCert))
+ return true;
+ return false;
+}
+
+
+/**
+ * Marks a certificate as found after it has been added to the store.
+ *
+ * May actually mark several certificates as found if there are duplicates or
+ * ambiguities in the wanted list.
+ *
+ * @returns true if all have been found, false if more to search for.
+ *
+ * @param apfFound Found initicators running paralell to @a paWanted.
+ * This is what this function updates.
+ * @param paWanted The certificate wanted posters.
+ * @param cWanted The number of wanted posters.
+ * @param cbEncoded The candidate certificate encoded size.
+ * @param paSha1 The candidate certificate SHA-1 fingerprint.
+ * @param paSha512 The candidate certificate SHA-512 fingerprint.
+ * @param pCert The decoded candidate certificate, optional. If not
+ * given the result will be uncertain.
+ */
+static bool rtCrStoreMarkCertFound(bool *pafFound, PCRTCRCERTWANTED paWanted, size_t cWanted, size_t cbEncoded,
+ uint8_t const pabSha1[RTSHA1_HASH_SIZE], uint8_t const pabSha512[RTSHA512_HASH_SIZE],
+ PCRTCRX509CERTIFICATE pCert)
+{
+ size_t cFound = 0;
+ for (size_t iCert = 0; iCert < cWanted; iCert++)
+ if (pafFound[iCert])
+ cFound++;
+ else if (rtCrStoreIsCertEqualToWanted(&paWanted[iCert], cbEncoded, pabSha1, pabSha512, pCert))
+ {
+ pafFound[iCert] = true;
+ cFound++;
+ }
+ return cFound == cWanted;
+}
+
+
+RTDECL(int) RTCrStoreCertAddFromStore(RTCRSTORE hStore, uint32_t fFlags, RTCRSTORE hStoreSrc)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS);
+
+ /*
+ * Enumerate all the certificates in the source store, adding them to the destination.
+ */
+ RTCRSTORECERTSEARCH Search;
+ int rc = RTCrStoreCertFindAll(hStoreSrc, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTCRCERTCTX pCertCtx;
+ while ((pCertCtx = RTCrStoreCertSearchNext(hStoreSrc, &Search)) != NULL)
+ {
+ int rc2 = RTCrStoreCertAddEncoded(hStore, pCertCtx->fFlags | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND),
+ pCertCtx->pabEncoded, pCertCtx->cbEncoded, NULL);
+ if (RT_FAILURE(rc2))
+ {
+ rc = rc2;
+ if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ break;
+ }
+ RTCrCertCtxRelease(pCertCtx);
+ }
+
+ int rc2 = RTCrStoreCertSearchDestroy(hStoreSrc, &Search); AssertRC(rc2);
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertAddFromStore);
+
+
+RTDECL(int) RTCrStoreCertAddWantedFromStore(RTCRSTORE hStore, uint32_t fFlags, RTCRSTORE hSrcStore,
+ PCRTCRCERTWANTED paWanted, size_t cWanted, bool *pafFound)
+{
+ /*
+ * Validate input a little.
+ */
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS);
+ fFlags |= RTCRCERTCTX_F_ADD_IF_NOT_FOUND; /* forced */
+
+ AssertReturn(cWanted, VERR_NOT_FOUND);
+ for (uint32_t i = 0; i < cWanted; i++)
+ {
+ AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER);
+ AssertReturn( paWanted[i].pszSubject
+ || paWanted[i].fSha1Fingerprint
+ || paWanted[i].fSha512Fingerprint,
+ VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Make sure we've got a result array.
+ */
+ bool *pafFoundFree = NULL;
+ if (!pafFound)
+ {
+ pafFound = pafFoundFree = (bool *)RTMemTmpAllocZ(sizeof(bool) * cWanted);
+ AssertReturn(pafFound, VERR_NO_TMP_MEMORY);
+ }
+
+ /*
+ * Enumerate the store entries.
+ */
+ RTCRSTORECERTSEARCH Search;
+ int rc = RTCrStoreCertFindAll(hSrcStore, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VWRN_NOT_FOUND;
+ PCRTCRCERTCTX pCertCtx;
+ while ((pCertCtx = RTCrStoreCertSearchNext(hSrcStore, &Search)) != NULL)
+ {
+ if ( (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER
+ && pCertCtx->cbEncoded > 0
+ && pCertCtx->pCert)
+ {
+ /*
+ * If the certificate is wanted, try add it to the store.
+ */
+ uint8_t abSha1[RTSHA1_HASH_SIZE];
+ RTSha1(pCertCtx->pabEncoded, pCertCtx->cbEncoded, abSha1);
+ uint8_t abSha512[RTSHA512_HASH_SIZE];
+ RTSha512(pCertCtx->pabEncoded, pCertCtx->cbEncoded, abSha512);
+ if (rtCrStoreIsCertWanted(paWanted, cWanted, pafFound, pCertCtx->cbEncoded, abSha1, abSha512, pCertCtx->pCert))
+ {
+ int rc2 = RTCrStoreCertAddEncoded(hStore,
+ RTCRCERTCTX_F_ENC_X509_DER | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND),
+ pCertCtx->pabEncoded, pCertCtx->cbEncoded, NULL /*pErrInfo*/);
+ if (RT_SUCCESS(rc2))
+ {
+ /*
+ * Mark it as found, stop if we've found all.
+ */
+ if (rtCrStoreMarkCertFound(pafFound, paWanted, cWanted,
+ pCertCtx->cbEncoded, abSha1, abSha512, pCertCtx->pCert))
+ {
+ if (RT_SUCCESS(rc))
+ rc = VINF_SUCCESS;
+ RTCrCertCtxRelease(pCertCtx);
+ break;
+ }
+ }
+ else
+ {
+ /*
+ * Some error adding the certificate. Since it cannot be anything with
+ * the encoding, it must be something with the store or resources, so
+ * always return the error status.
+ */
+ rc = rc2;
+ if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ {
+ RTCrCertCtxRelease(pCertCtx);
+ break;
+ }
+ }
+ }
+ }
+ RTCrCertCtxRelease(pCertCtx);
+ }
+ int rc2 = RTCrStoreCertSearchDestroy(hSrcStore, &Search);
+ AssertRC(rc2);
+ }
+
+ if (pafFoundFree)
+ RTMemTmpFree(pafFoundFree);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromStore);
+
+
+RTDECL(int) RTCrStoreCertCheckWanted(RTCRSTORE hStore, PCRTCRCERTWANTED paWanted, size_t cWanted, bool *pafFound)
+{
+ /*
+ * Validate input a little.
+ */
+ AssertReturn(cWanted, VERR_NOT_FOUND);
+ for (uint32_t i = 0; i < cWanted; i++)
+ {
+ AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER);
+ AssertReturn( paWanted[i].pszSubject
+ || paWanted[i].fSha1Fingerprint
+ || paWanted[i].fSha512Fingerprint,
+ VERR_INVALID_PARAMETER);
+ }
+ AssertPtrReturn(pafFound, VERR_INVALID_POINTER);
+
+ /*
+ * Clear the found array.
+ */
+ for (uint32_t iCert = 0; iCert < cWanted; iCert++)
+ pafFound[iCert] = false;
+
+ /*
+ * Enumerate the store entries.
+ */
+ RTCRSTORECERTSEARCH Search;
+ int rc = RTCrStoreCertFindAll(hStore, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VWRN_NOT_FOUND;
+ PCRTCRCERTCTX pCertCtx;
+ while ((pCertCtx = RTCrStoreCertSearchNext(hStore, &Search)) != NULL)
+ {
+ if ( (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER
+ && pCertCtx->cbEncoded > 0
+ && pCertCtx->pCert)
+ {
+ /*
+ * Hash it and check if it's wanted. Stop when we've found all.
+ */
+ uint8_t abSha1[RTSHA1_HASH_SIZE];
+ RTSha1(pCertCtx->pabEncoded, pCertCtx->cbEncoded, abSha1);
+ uint8_t abSha512[RTSHA512_HASH_SIZE];
+ RTSha512(pCertCtx->pabEncoded, pCertCtx->cbEncoded, abSha512);
+ if (rtCrStoreMarkCertFound(pafFound, paWanted, cWanted, pCertCtx->cbEncoded, abSha1, abSha512, pCertCtx->pCert))
+ {
+ rc = VINF_SUCCESS;
+ RTCrCertCtxRelease(pCertCtx);
+ break;
+ }
+ }
+ RTCrCertCtxRelease(pCertCtx);
+ }
+ int rc2 = RTCrStoreCertSearchDestroy(hStore, &Search);
+ AssertRC(rc2);
+ }
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromStore);
+
+
+RTDECL(int) RTCrStoreCertAddFromFile(RTCRSTORE hStore, uint32_t fFlags, const char *pszFilename, PRTERRINFO pErrInfo)
+{
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS);
+
+ size_t cbContent;
+ void *pvContent;
+ int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Is it a java key store file?
+ */
+ if ( cbContent > 32
+ && ((uint32_t const *)pvContent)[0] == RT_H2BE_U32_C(UINT32_C(0xfeedfeed)) /* magic */
+ && ((uint32_t const *)pvContent)[1] == RT_H2BE_U32_C(UINT32_C(0x00000002)) /* version */ )
+ rc = RTCrStoreCertAddFromJavaKeyStoreInMem(hStore, fFlags, pvContent, cbContent, pszFilename, pErrInfo);
+ /*
+ * No assume PEM or DER encoded binary certificate.
+ */
+ else if (cbContent)
+ {
+ PCRTCRPEMSECTION pSectionHead;
+ rc = RTCrPemParseContent(pvContent, cbContent,
+ (fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)
+ ? RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR : 0,
+ g_aX509CertificateMarkers, RT_ELEMENTS(g_aX509CertificateMarkers),
+ &pSectionHead, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTCRPEMSECTION pCurSec = pSectionHead;
+ while (pCurSec)
+ {
+ int rc2 = RTCrStoreCertAddEncoded(hStore,
+ RTCRCERTCTX_F_ENC_X509_DER | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND),
+ pCurSec->pbData, pCurSec->cbData,
+ !RTErrInfoIsSet(pErrInfo) ? pErrInfo : NULL);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ {
+ rc = rc2;
+ if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ break;
+ }
+ pCurSec = pCurSec->pNext;
+ }
+
+ RTCrPemFreeSections(pSectionHead);
+ }
+ }
+ else /* Will happen if proxy not set / no connection available. */
+ rc = RTErrInfoSetF(pErrInfo, VERR_EOF, "Certificate '%s' is empty", pszFilename);
+ RTFileReadAllFree(pvContent, cbContent);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertAddFromFile);
+
+
+RTDECL(int) RTCrStoreCertAddWantedFromFile(RTCRSTORE hStore, uint32_t fFlags, const char *pszFilename,
+ PCRTCRCERTWANTED paWanted, size_t cWanted, bool *pafFound, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate input a little.
+ */
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS);
+ fFlags |= RTCRCERTCTX_F_ADD_IF_NOT_FOUND; /* forced */
+
+ AssertReturn(cWanted, VERR_NOT_FOUND);
+ for (uint32_t i = 0; i < cWanted; i++)
+ {
+ AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER);
+ AssertReturn( paWanted[i].pszSubject
+ || paWanted[i].fSha1Fingerprint
+ || paWanted[i].fSha512Fingerprint,
+ VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Make sure we've got a result array.
+ */
+ bool *pafFoundFree = NULL;
+ if (!pafFound)
+ {
+ pafFound = pafFoundFree = (bool *)RTMemTmpAllocZ(sizeof(bool) * cWanted);
+ AssertReturn(pafFound, VERR_NO_TMP_MEMORY);
+ }
+
+ size_t cbContent;
+ void *pvContent;
+ int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Is it a java key store file? If so, load it into a tmp store
+ * which we can search. Don't want to duplicate the JKS reader code.
+ */
+ if ( cbContent > 32
+ && ((uint32_t const *)pvContent)[0] == RT_H2BE_U32_C(UINT32_C(0xfeedfeed)) /* magic */
+ && ((uint32_t const *)pvContent)[1] == RT_H2BE_U32_C(UINT32_C(0x00000002)) /* version */ )
+ {
+ RTCRSTORE hTmpStore;
+ rc = RTCrStoreCreateInMem(&hTmpStore, 64);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrStoreCertAddFromJavaKeyStoreInMem(hStore, fFlags, pvContent, cbContent, pszFilename, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = RTCrStoreCertAddWantedFromStore(hStore, fFlags, hTmpStore, paWanted, cWanted, pafFound);
+ RTCrStoreRelease(hTmpStore);
+ }
+ else
+ rc = RTErrInfoSet(pErrInfo, rc, "Error creating temporary crypto store");
+ }
+ /*
+ * No assume PEM or DER encoded binary certificate. Inspect them one by one.
+ */
+ else if (cbContent)
+ {
+ PCRTCRPEMSECTION pSectionHead;
+ rc = RTCrPemParseContent(pvContent, cbContent,
+ (fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)
+ ? RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR : 0,
+ g_aX509CertificateMarkers, RT_ELEMENTS(g_aX509CertificateMarkers),
+ &pSectionHead, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VWRN_NOT_FOUND;
+ for (PCRTCRPEMSECTION pCurSec = pSectionHead; pCurSec; pCurSec = pCurSec->pNext)
+ {
+ if (!pCurSec->cbData)
+ continue;
+
+ /*
+ * See if this is a binary blob we might be interested in.
+ */
+ uint8_t abSha1[RTSHA1_HASH_SIZE];
+ RTSha1(pCurSec->pbData, pCurSec->cbData, abSha1);
+ uint8_t abSha512[RTSHA512_HASH_SIZE];
+ RTSha512(pCurSec->pbData, pCurSec->cbData, abSha512);
+ if (!rtCrStoreIsCertWanted(paWanted, cWanted, pafFound, pCurSec->cbData, abSha1, abSha512, NULL))
+ continue;
+
+ /*
+ * Decode the certificate so we can match the subject string.
+ */
+ RTASN1CURSORPRIMARY Cursor;
+ RTAsn1CursorInitPrimary(&Cursor, pCurSec->pbData, (uint32_t)pCurSec->cbData,
+ !RTErrInfoIsSet(pErrInfo) ? pErrInfo : NULL,
+ &g_RTAsn1DefaultAllocator, RTASN1CURSOR_FLAGS_DER, "InMem");
+ RTCRX509CERTIFICATE X509Cert;
+ int rc2 = RTCrX509Certificate_DecodeAsn1(&Cursor.Cursor, 0, &X509Cert, "Cert");
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = RTCrX509Certificate_CheckSanity(&X509Cert, 0, !RTErrInfoIsSet(pErrInfo) ? pErrInfo : NULL, "Cert");
+ if (RT_SUCCESS(rc2))
+ {
+ if (rtCrStoreIsCertWanted(paWanted, cWanted, pafFound, pCurSec->cbData, abSha1, abSha512, &X509Cert))
+ {
+ /*
+ * The certificate is wanted, now add it to the store.
+ */
+ rc2 = RTCrStoreCertAddEncoded(hStore,
+ RTCRCERTCTX_F_ENC_X509_DER
+ | (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND),
+ pCurSec->pbData, pCurSec->cbData,
+ !RTErrInfoIsSet(pErrInfo) ? pErrInfo : NULL);
+ if (RT_SUCCESS(rc2))
+ {
+ /*
+ * Mark it as found, stop if we've found all.
+ */
+ if (rtCrStoreMarkCertFound(pafFound, paWanted, cWanted,
+ pCurSec->cbData, abSha1, abSha512, &X509Cert))
+ {
+ RTAsn1VtDelete(&X509Cert.SeqCore.Asn1Core);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ }
+ }
+ else
+ Assert(!pErrInfo || RTErrInfoIsSet(pErrInfo));
+ RTAsn1VtDelete(&X509Cert.SeqCore.Asn1Core);
+ }
+ else if (!RTErrInfoIsSet(pErrInfo))
+ RTErrInfoSetF(pErrInfo, rc2, "RTCrX509Certificate_DecodeAsn1 failed");
+
+ /*
+ * Stop on error, if requested. Otherwise, let pErrInfo keep it.
+ */
+ if (RT_FAILURE(rc2) && !(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ {
+ rc = rc2;
+ break;
+ }
+ } /* For each PEM section. */
+
+ RTCrPemFreeSections(pSectionHead);
+ }
+ }
+ else /* Will happen if proxy not set / no connection available. */
+ rc = RTErrInfoSetF(pErrInfo, VERR_EOF, "Certificate '%s' is empty", pszFilename);
+ RTFileReadAllFree(pvContent, cbContent);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename);
+
+ if (pafFoundFree)
+ RTMemTmpFree(pafFoundFree);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromFile);
+
+
+/**
+ * Checks if the directory entry matches the specified suffixes.
+ *
+ * @returns true on match, false on mismatch.
+ * @param pDirEntry The directory to check.
+ * @param paSuffixes The array of suffixes to match against.
+ * @param cSuffixes The number of suffixes in the array.
+ */
+DECLINLINE(bool) rtCrStoreIsSuffixMatch(PCRTDIRENTRY pDirEntry, PCRTSTRTUPLE paSuffixes, size_t cSuffixes)
+{
+ if (cSuffixes == 0)
+ return true;
+
+ size_t const cchName = pDirEntry->cbName;
+ size_t i = cSuffixes;
+ while (i-- > 0)
+ if ( cchName > paSuffixes[i].cch
+ && memcmp(&pDirEntry->szName[cchName - paSuffixes[i].cch], paSuffixes[i].psz, paSuffixes[i].cch) == 0)
+ return true;
+
+ return false;
+}
+
+
+RTDECL(int) RTCrStoreCertAddFromDir(RTCRSTORE hStore, uint32_t fFlags, const char *pszDir,
+ PCRTSTRTUPLE paSuffixes, size_t cSuffixes, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS);
+ size_t i = cSuffixes;
+ while (i-- > 0)
+ {
+ Assert(paSuffixes[i].cch > 0);
+ Assert(strlen(paSuffixes[i].psz) == paSuffixes[i].cch);
+ }
+
+ /*
+ * Prepare for constructing path to the files in the directory, so that we
+ * can open them.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = RTStrCopy(szPath, sizeof(szPath), pszDir);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchPath = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath));
+ if (cchPath > 0)
+ {
+ size_t const cbMaxFilename = sizeof(szPath) - cchPath;
+
+ /*
+ * Enumerate the directory.
+ */
+ RTDIR hDir;
+ rc = RTDirOpen(&hDir, pszDir);
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ /* Read the next entry. */
+ union
+ {
+ RTDIRENTRY DirEntry;
+ uint8_t abPadding[RTPATH_MAX + sizeof(RTDIRENTRY)];
+ } u;
+ size_t cbBuf = sizeof(u);
+ int rc2 = RTDirRead(hDir, &u.DirEntry, &cbBuf);
+ if (RT_SUCCESS(rc2))
+ {
+ if ( ( u.DirEntry.enmType == RTDIRENTRYTYPE_FILE
+ || u.DirEntry.enmType == RTDIRENTRYTYPE_SYMLINK
+ || ( u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN
+ && !RTDirEntryIsStdDotLink(&u.DirEntry)) )
+ && rtCrStoreIsSuffixMatch(&u.DirEntry, paSuffixes, cSuffixes) )
+ {
+ if (u.DirEntry.cbName < cbMaxFilename)
+ {
+ memcpy(&szPath[cchPath], u.DirEntry.szName, u.DirEntry.cbName + 1);
+ rc2 = RTDirQueryUnknownType(szPath, true /*fFollowSymlinks*/, &u.DirEntry.enmType);
+ if ( RT_SUCCESS(rc2)
+ && u.DirEntry.enmType == RTDIRENTRYTYPE_FILE)
+ {
+ /*
+ * Add it.
+ */
+ rc2 = RTCrStoreCertAddFromFile(hStore, fFlags, szPath, pErrInfo);
+ if (RT_FAILURE(rc2))
+ {
+ rc = rc2;
+ if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ break;
+ }
+ }
+ }
+ else
+ {
+ rc = RTErrInfoAddF(pErrInfo, VERR_FILENAME_TOO_LONG,
+ " Too long filename (%u bytes)", u.DirEntry.cbName);
+ if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (rc2 != VERR_NO_MORE_FILES)
+ rc = RTErrInfoAddF(pErrInfo, rc2, " RTDirRead failed: %Rrc", rc2);
+ break;
+ }
+ }
+
+ RTDirClose(hDir);
+ }
+ else
+ rc = RTErrInfoAddF(pErrInfo, rc, " RTDirOpen('%s'): %Rrc", pszDir, rc);
+ }
+ else
+ rc = VERR_FILENAME_TOO_LONG;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertAddFromDir);
+
+
+RTDECL(int) RTCrStoreCertAddWantedFromDir(RTCRSTORE hStore, uint32_t fFlags,
+ const char *pszDir, PCRTSTRTUPLE paSuffixes, size_t cSuffixes,
+ PCRTCRCERTWANTED paWanted, size_t cWanted, bool *pafFound, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate input a little.
+ */
+ AssertReturn(*pszDir, VERR_PATH_ZERO_LENGTH);
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR)), VERR_INVALID_FLAGS);
+ fFlags |= RTCRCERTCTX_F_ADD_IF_NOT_FOUND; /* forced */
+
+ AssertReturn(cWanted, VERR_NOT_FOUND);
+ for (uint32_t i = 0; i < cWanted; i++)
+ {
+ AssertReturn(!paWanted[i].pszSubject || *paWanted[i].pszSubject, VERR_INVALID_PARAMETER);
+ AssertReturn( paWanted[i].pszSubject
+ || paWanted[i].fSha1Fingerprint
+ || paWanted[i].fSha512Fingerprint,
+ VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Prepare for constructing path to the files in the directory, so that we
+ * can open them.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = RTStrCopy(szPath, sizeof(szPath), pszDir);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchPath = RTPathEnsureTrailingSeparator(szPath, sizeof(szPath));
+ if (cchPath > 0)
+ {
+ size_t const cbMaxFilename = sizeof(szPath) - cchPath;
+
+ /*
+ * Enumerate the directory.
+ */
+ RTDIR hDir;
+ rc = RTDirOpen(&hDir, pszDir);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VWRN_NOT_FOUND;
+ for (;;)
+ {
+ /* Read the next entry. */
+ union
+ {
+ RTDIRENTRY DirEntry;
+ uint8_t abPadding[RTPATH_MAX + sizeof(RTDIRENTRY)];
+ } u;
+ size_t cbEntry = sizeof(u);
+ int rc2 = RTDirRead(hDir, &u.DirEntry, &cbEntry);
+ if (RT_SUCCESS(rc2))
+ {
+ if ( ( u.DirEntry.enmType == RTDIRENTRYTYPE_FILE
+ || u.DirEntry.enmType == RTDIRENTRYTYPE_SYMLINK
+ || ( u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN
+ && !RTDirEntryIsStdDotLink(&u.DirEntry)) )
+ && rtCrStoreIsSuffixMatch(&u.DirEntry, paSuffixes, cSuffixes) )
+ {
+ if (u.DirEntry.cbName < cbMaxFilename)
+ {
+ memcpy(&szPath[cchPath], u.DirEntry.szName, u.DirEntry.cbName);
+ szPath[cchPath + u.DirEntry.cbName] = '\0';
+ if (u.DirEntry.enmType != RTDIRENTRYTYPE_FILE)
+ RTDirQueryUnknownType(szPath, true /*fFollowSymlinks*/, &u.DirEntry.enmType);
+ if (u.DirEntry.enmType == RTDIRENTRYTYPE_FILE)
+ {
+ rc2 = RTCrStoreCertAddWantedFromFile(hStore, fFlags, szPath,
+ paWanted, cWanted, pafFound, pErrInfo);
+ if (rc2 == VINF_SUCCESS)
+ {
+ Assert(rtCrStoreAllDone(pafFound, cWanted));
+ if (RT_SUCCESS(rc))
+ rc = VINF_SUCCESS;
+ break;
+ }
+ if (RT_FAILURE(rc2) && !(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ {
+ rc = rc2;
+ break;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * pErrInfo keeps the status code unless it's fatal.
+ */
+ RTErrInfoAddF(pErrInfo, VERR_FILENAME_TOO_LONG,
+ " Too long filename (%u bytes)", u.DirEntry.cbName);
+ if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ {
+ rc = VERR_FILENAME_TOO_LONG;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ if (rc2 != VERR_NO_MORE_FILES)
+ {
+ RTErrInfoAddF(pErrInfo, rc2, "RTDirRead failed: %Rrc", rc2);
+ if (!(fFlags & RTCRCERTCTX_F_ADD_CONTINUE_ON_ERROR))
+ rc = rc2;
+ }
+ break;
+ }
+ }
+ RTDirClose(hDir);
+ }
+ }
+ else
+ rc = VERR_FILENAME_TOO_LONG;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCertAddWantedFromDir);
+
diff --git a/src/VBox/Runtime/common/crypto/store-inmem.cpp b/src/VBox/Runtime/common/crypto/store-inmem.cpp
new file mode 100644
index 00000000..7bc441ae
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/store-inmem.cpp
@@ -0,0 +1,466 @@
+/* $Id: store-inmem.cpp $ */
+/** @file
+ * IPRT - In Memory Cryptographic Certificate Store.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/store.h>
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "store-internal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * A certificate entry in the in-memory store.
+ */
+typedef struct RTCRSTOREINMEMCERT
+{
+ /** The core certificate context. */
+ RTCRCERTCTXINT Core;
+ /** Internal copy of the flag (paranoia). */
+ uint32_t fFlags;
+ /** Decoded data. */
+ union
+ {
+ /** ASN.1 core structure for generic access. */
+ RTASN1CORE Asn1Core;
+ /** The decoded X.509 certificate (RTCRCERTCTX_F_ENC_X509_DER). */
+ RTCRX509CERTIFICATE X509Cert;
+ /** The decoded trust anchor info (RTCRCERTCTX_F_ENC_TAF_DER). */
+ RTCRTAFTRUSTANCHORINFO TaInfo;
+ } u;
+ /** Pointer to the store if still in it (no reference). */
+ struct RTCRSTOREINMEM *pStore;
+ /** The DER encoding of the certificate. */
+ uint8_t abEncoded[1];
+} RTCRSTOREINMEMCERT;
+AssertCompileMembersAtSameOffset(RTCRSTOREINMEMCERT, u.X509Cert.SeqCore.Asn1Core, RTCRSTOREINMEMCERT, u.Asn1Core);
+AssertCompileMembersAtSameOffset(RTCRSTOREINMEMCERT, u.TaInfo.SeqCore.Asn1Core, RTCRSTOREINMEMCERT, u.Asn1Core);
+/** Pointer to an in-memory store certificate entry. */
+typedef RTCRSTOREINMEMCERT *PRTCRSTOREINMEMCERT;
+
+
+/**
+ * The per instance data of a in-memory crypto store.
+ *
+ * Currently we ASSUME we don't need serialization. Add that when needed!
+ */
+typedef struct RTCRSTOREINMEM
+{
+ /** The number of certificates. */
+ uint32_t cCerts;
+ /** The max number of certificates papCerts can store before growing it. */
+ uint32_t cCertsAlloc;
+ /** Array of certificates. */
+ PRTCRSTOREINMEMCERT *papCerts;
+
+ /** Parent store. */
+ RTCRSTORE hParentStore;
+ /** The parent store callback table. */
+ PCRTCRSTOREPROVIDER pParentProvider;
+ /** The parent store provider callback argument. */
+ void *pvParentProvider;
+} RTCRSTOREINMEM;
+/** Pointer to an in-memory crypto store. */
+typedef RTCRSTOREINMEM *PRTCRSTOREINMEM;
+
+
+
+
+static DECLCALLBACK(void) rtCrStoreInMemCertEntry_Dtor(PRTCRCERTCTXINT pCertCtx)
+{
+ PRTCRSTOREINMEMCERT pEntry = (PRTCRSTOREINMEMCERT)pCertCtx;
+ AssertRelease(!pEntry->pStore);
+
+ pEntry->Core.pfnDtor = NULL;
+ RTAsn1VtDelete(&pEntry->u.Asn1Core);
+ RTMemFree(pEntry);
+}
+
+
+/**
+ * Internal method for allocating and initalizing a certificate entry in the
+ * in-memory store.
+ *
+ * @returns IPRT status code.
+ * @param pThis The in-memory store instance.
+ * @param fEnc RTCRCERTCTX_F_ENC_X509_DER or RTCRCERTCTX_F_ENC_TAF_DER.
+ * @param pbSrc The DER encoded X.509 certificate to add.
+ * @param cbSrc The size of the encoded certificate.
+ * @param pErrInfo Where to return extended error info. Optional.
+ * @param ppEntry Where to return the pointer to the new entry.
+ */
+static int rtCrStoreInMemCreateCertEntry(PRTCRSTOREINMEM pThis, uint32_t fEnc, uint8_t const *pbSrc, uint32_t cbSrc,
+ PRTERRINFO pErrInfo, PRTCRSTOREINMEMCERT *ppEntry)
+{
+ int rc;
+ PRTCRSTOREINMEMCERT pEntry = (PRTCRSTOREINMEMCERT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCRSTOREINMEMCERT, abEncoded[cbSrc]));
+ if (pEntry)
+ {
+ memcpy(pEntry->abEncoded, pbSrc, cbSrc);
+ pEntry->Core.u32Magic = RTCRCERTCTXINT_MAGIC;
+ pEntry->Core.cRefs = 1;
+ pEntry->Core.pfnDtor = rtCrStoreInMemCertEntry_Dtor;
+ pEntry->Core.Public.fFlags = fEnc;
+ pEntry->Core.Public.cbEncoded = cbSrc;
+ pEntry->Core.Public.pabEncoded = &pEntry->abEncoded[0];
+ if (fEnc == RTCRCERTCTX_F_ENC_X509_DER)
+ {
+ pEntry->Core.Public.pCert = &pEntry->u.X509Cert;
+ pEntry->Core.Public.pTaInfo = NULL;
+ }
+ else
+ {
+ pEntry->Core.Public.pCert = NULL;
+ pEntry->Core.Public.pTaInfo = &pEntry->u.TaInfo;
+ }
+ pEntry->pStore = pThis;
+
+ RTASN1CURSORPRIMARY Cursor;
+ RTAsn1CursorInitPrimary(&Cursor, &pEntry->abEncoded[0], cbSrc, pErrInfo, &g_RTAsn1DefaultAllocator,
+ RTASN1CURSOR_FLAGS_DER, "InMem");
+ if (fEnc == RTCRCERTCTX_F_ENC_X509_DER)
+ rc = RTCrX509Certificate_DecodeAsn1(&Cursor.Cursor, 0, &pEntry->u.X509Cert, "Cert");
+ else
+ rc = RTCrTafTrustAnchorInfo_DecodeAsn1(&Cursor.Cursor, 0, &pEntry->u.TaInfo, "TaInfo");
+ if (RT_SUCCESS(rc))
+ {
+ if (fEnc == RTCRCERTCTX_F_ENC_X509_DER)
+ rc = RTCrX509Certificate_CheckSanity(&pEntry->u.X509Cert, 0, pErrInfo, "Cert");
+ else
+ rc = RTCrTafTrustAnchorInfo_CheckSanity(&pEntry->u.TaInfo, 0, pErrInfo, "TaInfo");
+ if (RT_SUCCESS(rc))
+ {
+ *ppEntry = pEntry;
+ return VINF_SUCCESS;
+ }
+
+ RTAsn1VtDelete(&pEntry->u.Asn1Core);
+ }
+ RTMemFree(pEntry);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Grows the certificate pointer array to at least @a cMin entries.
+ *
+ * @returns IPRT status code.
+ * @param pThis The in-memory store instance.
+ * @param cMin The new minimum store size.
+ */
+static int rtCrStoreInMemGrow(PRTCRSTOREINMEM pThis, uint32_t cMin)
+{
+ AssertReturn(cMin <= _1M, VERR_OUT_OF_RANGE);
+ AssertReturn(cMin > pThis->cCertsAlloc, VERR_INTERNAL_ERROR_3);
+
+ if (cMin < 64)
+ cMin = RT_ALIGN_32(cMin, 8);
+ else
+ cMin = RT_ALIGN_32(cMin, 32);
+
+ void *pv = RTMemRealloc(pThis->papCerts, cMin * sizeof(pThis->papCerts[0]));
+ if (pv)
+ {
+ pThis->papCerts = (PRTCRSTOREINMEMCERT *)pv;
+ for (uint32_t i = pThis->cCertsAlloc; i < cMin; i++)
+ pThis->papCerts[i] = NULL;
+ pThis->cCertsAlloc = cMin;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+
+/** @interface_method_impl{RTCRSTOREPROVIDER,pfnDestroyStore} */
+static DECLCALLBACK(void) rtCrStoreInMem_DestroyStore(void *pvProvider)
+{
+ PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider;
+
+ while (pThis->cCerts > 0)
+ {
+ uint32_t i = --pThis->cCerts;
+ PRTCRSTOREINMEMCERT pEntry = pThis->papCerts[i];
+ pThis->papCerts[i] = NULL;
+ AssertPtr(pEntry);
+
+ pEntry->pStore = NULL;
+ RTCrCertCtxRelease(&pEntry->Core.Public);
+ }
+
+ RTMemFree(pThis->papCerts);
+ pThis->papCerts = NULL;
+
+ if (pThis->hParentStore != NIL_RTCRSTORE)
+ {
+ RTCrStoreRelease(pThis->hParentStore);
+ pThis->hParentStore = NIL_RTCRSTORE;
+ }
+
+ RTMemFree(pThis);
+}
+
+
+/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertCtxQueryPrivateKey} */
+static DECLCALLBACK(int) rtCrStoreInMem_CertCtxQueryPrivateKey(void *pvProvider, PRTCRCERTCTXINT pCertCtx,
+ uint8_t *pbKey, size_t cbKey, size_t *pcbKeyRet)
+{
+ RT_NOREF_PV(pvProvider); RT_NOREF_PV(pCertCtx); RT_NOREF_PV(pbKey); RT_NOREF_PV(cbKey); RT_NOREF_PV(pcbKeyRet);
+ //PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider;
+ return VERR_NOT_FOUND;
+}
+
+
+/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertFindAll} */
+static DECLCALLBACK(int) rtCrStoreInMem_CertFindAll(void *pvProvider, PRTCRSTORECERTSEARCH pSearch)
+{
+ pSearch->auOpaque[0] = ~(uintptr_t)pvProvider;
+ pSearch->auOpaque[1] = 0;
+ pSearch->auOpaque[2] = ~(uintptr_t)0; /* For the front-end API. */
+ pSearch->auOpaque[3] = ~(uintptr_t)0; /* For the front-end API. */
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertSearchNext} */
+static DECLCALLBACK(PCRTCRCERTCTX) rtCrStoreInMem_CertSearchNext(void *pvProvider, PRTCRSTORECERTSEARCH pSearch)
+{
+ PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider;
+ if (pSearch->auOpaque[0] == ~(uintptr_t)pvProvider)
+ {
+ uintptr_t i = pSearch->auOpaque[1];
+ if (i < pThis->cCerts)
+ {
+ pSearch->auOpaque[1] = i + 1;
+ PRTCRCERTCTXINT pCertCtx = &pThis->papCerts[i]->Core;
+ ASMAtomicIncU32(&pCertCtx->cRefs);
+ return &pCertCtx->Public;
+ }
+
+ /* Do we have a parent store to search? */
+ if (pThis->hParentStore == NIL_RTCRSTORE)
+ return NULL; /* no */
+ if ( !pThis->pParentProvider->pfnCertFindAll
+ || !pThis->pParentProvider->pfnCertSearchNext)
+ return NULL;
+
+ RTCRSTORECERTSEARCH const SavedSearch = *pSearch;
+ int rc = pThis->pParentProvider->pfnCertFindAll(pThis->pvParentProvider, pSearch);
+ AssertRCReturnStmt(rc, *pSearch = SavedSearch, NULL);
+
+ /* Restore the store.cpp specifics: */
+ AssertCompile(RT_ELEMENTS(SavedSearch.auOpaque) == 4);
+ pSearch->auOpaque[2] = SavedSearch.auOpaque[2];
+ pSearch->auOpaque[3] = SavedSearch.auOpaque[3];
+ }
+
+ AssertReturn(pThis->pParentProvider, NULL);
+ AssertReturn(pThis->pParentProvider->pfnCertSearchNext, NULL);
+ return pThis->pParentProvider->pfnCertSearchNext(pThis->pvParentProvider, pSearch);
+}
+
+
+/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertSearchDestroy} */
+static DECLCALLBACK(void) rtCrStoreInMem_CertSearchDestroy(void *pvProvider, PRTCRSTORECERTSEARCH pSearch)
+{
+ PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider;
+ if (pSearch->auOpaque[0] == ~(uintptr_t)pvProvider)
+ {
+ pSearch->auOpaque[0] = 0;
+ pSearch->auOpaque[1] = 0;
+ pSearch->auOpaque[2] = 0;
+ pSearch->auOpaque[3] = 0;
+ }
+ else
+ {
+ AssertReturnVoid(pThis->pParentProvider);
+ AssertReturnVoid(pThis->pParentProvider->pfnCertSearchDestroy);
+ pThis->pParentProvider->pfnCertSearchDestroy(pThis->pvParentProvider, pSearch);
+ }
+}
+
+
+/** @interface_method_impl{RTCRSTOREPROVIDER,pfnCertSearchDestroy} */
+static DECLCALLBACK(int) rtCrStoreInMem_CertAddEncoded(void *pvProvider, uint32_t fFlags,
+ uint8_t const *pbEncoded, uint32_t cbEncoded, PRTERRINFO pErrInfo)
+{
+ PRTCRSTOREINMEM pThis = (PRTCRSTOREINMEM)pvProvider;
+ int rc;
+
+ AssertMsgReturn( (fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER
+ || (fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_TAF_DER
+ , ("Only X.509 and TAF DER are supported: %#x\n", fFlags), VERR_INVALID_FLAGS);
+
+ /*
+ * Check for duplicates if specified.
+ */
+ if (fFlags & RTCRCERTCTX_F_ADD_IF_NOT_FOUND)
+ {
+ uint32_t iCert = pThis->cCerts;
+ while (iCert-- > 0)
+ {
+ PRTCRSTOREINMEMCERT pCert = pThis->papCerts[iCert];
+ if ( pCert->Core.Public.cbEncoded == cbEncoded
+ && pCert->Core.Public.fFlags == (fFlags & RTCRCERTCTX_F_ENC_MASK)
+ && memcmp(pCert->Core.Public.pabEncoded, pbEncoded, cbEncoded) == 0)
+ return VWRN_ALREADY_EXISTS;
+ }
+ }
+
+ /*
+ * Add it.
+ */
+ if (pThis->cCerts + 1 <= pThis->cCertsAlloc)
+ { /* likely */ }
+ else
+ {
+ rc = rtCrStoreInMemGrow(pThis, pThis->cCerts + 1);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ rc = rtCrStoreInMemCreateCertEntry(pThis, fFlags & RTCRCERTCTX_F_ENC_MASK, pbEncoded, cbEncoded,
+ pErrInfo, &pThis->papCerts[pThis->cCerts]);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cCerts++;
+ return VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+/**
+ * In-memory store provider.
+ */
+static RTCRSTOREPROVIDER const g_rtCrStoreInMemProvider =
+{
+ "in-memory",
+ rtCrStoreInMem_DestroyStore,
+ rtCrStoreInMem_CertCtxQueryPrivateKey,
+ rtCrStoreInMem_CertFindAll,
+ rtCrStoreInMem_CertSearchNext,
+ rtCrStoreInMem_CertSearchDestroy,
+ rtCrStoreInMem_CertAddEncoded,
+ NULL,
+ 42
+};
+
+
+/**
+ * Common worker for RTCrStoreCreateInMem and future constructors...
+ *
+ * @returns IPRT status code.
+ * @param ppStore Where to return the store instance.
+ * @param hParentStore Optional parent store. Consums reference on
+ * success.
+ */
+static int rtCrStoreInMemCreateInternal(PRTCRSTOREINMEM *ppStore, RTCRSTORE hParentStore)
+{
+ PRTCRSTOREINMEM pStore = (PRTCRSTOREINMEM)RTMemAlloc(sizeof(*pStore));
+ if (pStore)
+ {
+ pStore->cCerts = 0;
+ pStore->cCertsAlloc = 0;
+ pStore->papCerts = NULL;
+ pStore->hParentStore = hParentStore;
+ pStore->pParentProvider = NULL;
+ pStore->pvParentProvider = NULL;
+ *ppStore = pStore;
+ if (hParentStore == NIL_RTCRSTORE)
+ return VINF_SUCCESS;
+ if (~(uintptr_t)hParentStore != ~(uintptr_t)pStore)
+ {
+ pStore->pParentProvider = rtCrStoreGetProvider(hParentStore, &pStore->pvParentProvider);
+ if (pStore->pParentProvider)
+ return VINF_SUCCESS;
+ AssertFailed();
+ }
+ RTMemFree(pStore);
+ }
+ *ppStore = NULL; /* shut up gcc-maybe-pita warning. */
+ return VERR_NO_MEMORY;
+}
+
+
+RTDECL(int) RTCrStoreCreateInMemEx(PRTCRSTORE phStore, uint32_t cSizeHint, RTCRSTORE hParentStore)
+{
+ if (hParentStore != NIL_RTCRSTORE)
+ {
+ uint32_t cRefs = RTCrStoreRetain(hParentStore);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+ }
+
+ PRTCRSTOREINMEM pStore;
+ int rc = rtCrStoreInMemCreateInternal(&pStore, hParentStore);
+ if (RT_SUCCESS(rc))
+ {
+ if (cSizeHint)
+ rc = rtCrStoreInMemGrow(pStore, RT_MIN(cSizeHint, 512));
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtCrStoreCreate(&g_rtCrStoreInMemProvider, pStore, phStore);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pStore);
+ }
+
+ RTCrStoreRelease(hParentStore);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTCrStoreCreateInMemEx);
+
+
+RTDECL(int) RTCrStoreCreateInMem(PRTCRSTORE phStore, uint32_t cSizeHint)
+{
+ return RTCrStoreCreateInMemEx(phStore, cSizeHint, NIL_RTCRSTORE);
+}
+RT_EXPORT_SYMBOL(RTCrStoreCreateInMem);
+
diff --git a/src/VBox/Runtime/common/crypto/store-internal.h b/src/VBox/Runtime/common/crypto/store-internal.h
new file mode 100644
index 00000000..83628271
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/store-internal.h
@@ -0,0 +1,177 @@
+/* $Id: store-internal.h $ */
+/** @file
+ * IPRT - Cryptographic Store, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_store_internal_h
+#define IPRT_INCLUDED_SRC_common_crypto_store_internal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/**
+ * Internal certificate context.
+ *
+ * In addition to the externally visible structure (RTCRCERTCTX) this has the
+ * reference counter and store reference. (This structure may again be part of
+ * a larger structure internal to the store, depending on the source store.)
+ */
+typedef struct RTCRCERTCTXINT
+{
+ /** Magic number (RTCRCERTCTXINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /**
+ * Destructor that gets called with cRefs reaches zero.
+ * @param pCertCtx The internal certificate context.
+ */
+ DECLCALLBACKMEMBER(void, pfnDtor,(struct RTCRCERTCTXINT *pCertCtx));
+ /** The public store context. */
+ RTCRCERTCTX Public;
+} RTCRCERTCTXINT;
+/** Pointer to an internal certificate context. */
+typedef RTCRCERTCTXINT *PRTCRCERTCTXINT;
+
+/** Magic value for RTCRCERTCTXINT::u32Magic (Alan Mathison Turing). */
+#define RTCRCERTCTXINT_MAGIC UINT32_C(0x19120623)
+/** Dead magic value for RTCRCERTCTXINT::u32Magic. */
+#define RTCRCERTCTXINT_MAGIC_DEAD UINT32_C(0x19540607)
+
+
+/**
+ * IPRT Cryptographic Store Provider.
+ *
+ * @remarks This is a very incomplete sketch.
+ */
+typedef struct RTCRSTOREPROVIDER
+{
+ /** The provider name. */
+ const char *pszName;
+
+ /**
+ * Called to destroy an open store.
+ *
+ * @param pvProvider The provider specific data.
+ */
+ DECLCALLBACKMEMBER(void, pfnDestroyStore,(void *pvProvider));
+
+ /**
+ * Queries the private key.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_NOT_FOUND if not private key.
+ * @retval VERR_ACCESS_DENIED if the private key isn't allowed to leave the
+ * store. One would then have to use the pfnCertCtxSign method.
+ *
+ * @param pvProvider The provider specific data.
+ * @param pCertCtx The internal certificate context.
+ * @param pbKey Where to return the key bytes.
+ * @param cbKey The size of the buffer @a pbKey points to.
+ * @param pcbKeyRet Where to return the size of the returned key.
+ */
+ DECLCALLBACKMEMBER(int, pfnCertCtxQueryPrivateKey,(void *pvProvider, PRTCRCERTCTXINT pCertCtx,
+ uint8_t *pbKey, size_t cbKey, size_t *pcbKeyRet));
+
+ /**
+ * Open an enumeration of all certificates.
+ *
+ * @returns IPRT status code
+ * @param pvProvider The provider specific data.
+ * @param pSearch Pointer to opaque search state structure. The
+ * provider should initalize this on success.
+ */
+ DECLCALLBACKMEMBER(int, pfnCertFindAll,(void *pvProvider, PRTCRSTORECERTSEARCH pSearch));
+
+ /**
+ * Get the next certificate.
+ *
+ * @returns Reference to the next certificate context (must be released by
+ * caller). NULL if no more certificates in the search result.
+ * @param pvProvider The provider specific data.
+ * @param pSearch Pointer to opaque search state structure.
+ */
+ DECLCALLBACKMEMBER(PCRTCRCERTCTX, pfnCertSearchNext,(void *pvProvider, PRTCRSTORECERTSEARCH pSearch));
+
+ /**
+ * Closes a certficate search state.
+ *
+ * @param pvProvider The provider specific data.
+ * @param pSearch Pointer to opaque search state structure to destroy.
+ */
+ DECLCALLBACKMEMBER(void, pfnCertSearchDestroy,(void *pvProvider, PRTCRSTORECERTSEARCH pSearch));
+
+ /**
+ * Adds a certificate to the store.
+ *
+ * @returns IPRT status code.
+ * @retval VWRN_ALREADY_EXISTS if the certificate is already present and
+ * RTCRCERTCTX_F_ADD_IF_NOT_FOUND was specified.
+ * @param pvProvider The provider specific data.
+ * @param fFlags RTCRCERTCTX_F_XXX.
+ * @param pbEncoded The encoded certificate bytes.
+ * @param cbEncoded The size of the encoded certificate.
+ * @param pErrInfo Where to store extended error info. Optional.
+ */
+ DECLCALLBACKMEMBER(int, pfnCertAddEncoded,(void *pvProvider, uint32_t fFlags, uint8_t const *pbEncoded, uint32_t cbEncoded,
+ PRTERRINFO pErrInfo));
+
+
+ /* Optional: */
+
+ /**
+ * Find all certficates matching a given issuer and serial number.
+ *
+ * (Usually only one result.)
+ *
+ * @returns IPRT status code
+ * @param pvProvider The provider specific data.
+ * @param phSearch Pointer to a provider specific search handle.
+ */
+ DECLCALLBACKMEMBER(int, pfnCertFindByIssuerAndSerialNo,(void *pvProvider, PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNo,
+ PRTCRSTORECERTSEARCH phSearch));
+ /** Non-zero end marker. */
+ uintptr_t uEndMarker;
+} RTCRSTOREPROVIDER;
+
+/** Pointer to a store provider call table. */
+typedef RTCRSTOREPROVIDER const *PCRTCRSTOREPROVIDER;
+
+
+DECLHIDDEN(int) rtCrStoreCreate(PCRTCRSTOREPROVIDER pProvider, void *pvProvider, PRTCRSTORE phStore);
+DECLHIDDEN(PCRTCRSTOREPROVIDER) rtCrStoreGetProvider(RTCRSTORE hStore, void **ppvProvider);
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_store_internal_h */
+
diff --git a/src/VBox/Runtime/common/crypto/store.cpp b/src/VBox/Runtime/common/crypto/store.cpp
new file mode 100644
index 00000000..edf80c9c
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/store.cpp
@@ -0,0 +1,571 @@
+/* $Id: store.cpp $ */
+/** @file
+ * IPRT - Cryptographic (Certificate) Store.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/store.h>
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <iprt/crypto/pkcs7.h>
+#include <iprt/crypto/x509.h>
+
+#ifdef IPRT_WITH_OPENSSL
+# include "internal/openssl-pre.h"
+# include <openssl/x509.h>
+# include "internal/openssl-post.h"
+#endif
+
+#include "store-internal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Internal representation of a (certificate,++) store.
+ */
+typedef struct RTCRSTOREINT
+{
+ /** Magic number (RTCRSTOREINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Pointer to the store provider. */
+ PCRTCRSTOREPROVIDER pProvider;
+ /** Provider specific data. */
+ void *pvProvider;
+} RTCRSTOREINT;
+/** Pointer to the internal representation of a store. */
+typedef RTCRSTOREINT *PRTCRSTOREINT;
+
+/** Magic value for RTCRSTOREINT::u32Magic (Alfred Dillwyn "Dilly" Knox). */
+#define RTCRSTOREINT_MAGIC UINT32_C(0x18840723)
+/** Dead magic value for RTCRSTOREINT::u32Magic. */
+#define RTCRSTOREINT_MAGIC_DEAD UINT32_C(0x19430227)
+
+
+
+/**
+ * Internal method a store provider uses to create a store handle.
+ *
+ * @returns IPRT status code
+ * @param pProvider Pointer to the store provider callback table.
+ * @param pvProvider Pointer to the provider specific instance data.
+ * @param phStore Where to return the store handle.
+ */
+DECLHIDDEN(int) rtCrStoreCreate(PCRTCRSTOREPROVIDER pProvider, void *pvProvider, PRTCRSTORE phStore)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)RTMemAlloc(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->pvProvider = pvProvider;
+ pThis->pProvider = pProvider;
+ pThis->cRefs = 1;
+ pThis->u32Magic = RTCRSTOREINT_MAGIC;
+ *phStore = pThis;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * For the parent forwarding of the in-memory store.
+ */
+DECLHIDDEN(PCRTCRSTOREPROVIDER) rtCrStoreGetProvider(RTCRSTORE hStore, void **ppvProvider)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, NULL);
+ *ppvProvider = pThis->pvProvider;
+ return pThis->pProvider;
+}
+
+
+RTDECL(uint32_t) RTCrStoreRetain(RTCRSTORE hStore)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, UINT32_MAX);
+
+ uint32_t cRet = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRet < 8192);
+ return cRet;
+}
+
+
+RTDECL(uint32_t) RTCrStoreRelease(RTCRSTORE hStore)
+{
+ if (hStore == NIL_RTCRSTORE)
+ return 0;
+
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, UINT32_MAX);
+
+ uint32_t cStore = ASMAtomicDecU32(&pThis->cRefs);
+ if (!cStore)
+ {
+ ASMAtomicWriteU32(&pThis->u32Magic, RTCRSTOREINT_MAGIC_DEAD);
+ pThis->pProvider->pfnDestroyStore(pThis->pvProvider);
+ RTMemFree(pThis);
+ }
+ return cStore;
+}
+
+
+RTDECL(PCRTCRCERTCTX) RTCrStoreCertByIssuerAndSerialNo(RTCRSTORE hStore, PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNo)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, NULL);
+ AssertPtrReturn(pIssuer, NULL);
+
+ int rc;
+ RTCRSTORECERTSEARCH Search;
+ if (pThis->pProvider->pfnCertFindByIssuerAndSerialNo)
+ rc = pThis->pProvider->pfnCertFindByIssuerAndSerialNo(pThis->pvProvider, pIssuer, pSerialNo, &Search);
+ else
+ rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, &Search);
+
+ PCRTCRCERTCTX pCertCtx = NULL;
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ pCertCtx = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, &Search);
+ if (!pCertCtx)
+ break;
+
+ if ( pCertCtx->pCert
+ && RTCrX509Certificate_MatchIssuerAndSerialNumber(pCertCtx->pCert, pIssuer, pSerialNo))
+ break;
+ RTCrCertCtxRelease(pCertCtx);
+ }
+
+ pThis->pProvider->pfnCertSearchDestroy(pThis->pvProvider, &Search);
+ }
+ else
+ AssertMsg(rc == VERR_NOT_FOUND, ("%Rrc\n", rc));
+ return pCertCtx;
+}
+
+
+RTDECL(int) RTCrStoreCertAddEncoded(RTCRSTORE hStore, uint32_t fFlags, void const *pvSrc, size_t cbSrc, PRTERRINFO pErrInfo)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pvSrc, VERR_INVALID_POINTER);
+ AssertReturn(cbSrc > 16 && cbSrc < _1M, VERR_OUT_OF_RANGE);
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ENC_MASK)), VERR_INVALID_FLAGS);
+ AssertMsgReturn( (fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER
+ || (fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_TAF_DER
+ , ("Only X.509 and TAF DER supported: %#x\n", fFlags), VERR_INVALID_FLAGS);
+
+ int rc;
+ if (pThis->pProvider->pfnCertAddEncoded)
+ rc = pThis->pProvider->pfnCertAddEncoded(pThis->pvProvider, fFlags, (uint8_t const *)pvSrc, (uint32_t)cbSrc, pErrInfo);
+ else
+ rc = VERR_WRITE_PROTECT;
+
+ return rc;
+}
+
+
+RTDECL(int) RTCrStoreCertAddX509(RTCRSTORE hStore, uint32_t fFlags, PRTCRX509CERTIFICATE pCertificate, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate.
+ */
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE);
+
+ AssertPtrReturn(pCertificate, VERR_INVALID_POINTER);
+ AssertReturn(RTCrX509Certificate_IsPresent(pCertificate), VERR_INVALID_PARAMETER);
+ int rc = RTCrX509Certificate_CheckSanity(pCertificate, 0, pErrInfo, "Cert");
+ AssertRCReturn(rc, rc);
+
+ AssertReturn(!(fFlags & ~(RTCRCERTCTX_F_ADD_IF_NOT_FOUND | RTCRCERTCTX_F_ENC_MASK)), VERR_INVALID_FLAGS);
+ AssertCompile(RTCRCERTCTX_F_ENC_X509_DER == 0);
+ AssertMsgReturn((fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER,
+ ("Invalid encoding: %#x\n", fFlags), VERR_INVALID_FLAGS);
+
+ /*
+ * Encode and add it using pfnCertAddEncoded.
+ */
+ if (pThis->pProvider->pfnCertAddEncoded)
+ {
+ PRTASN1CORE pCore = RTCrX509Certificate_GetAsn1Core(pCertificate);
+ uint32_t cbEncoded = 0;
+ rc = RTAsn1EncodePrepare(pCore, RTASN1ENCODE_F_DER, &cbEncoded, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t * const pbEncoded = (uint8_t *)RTMemTmpAllocZ(cbEncoded);
+ if (pbEncoded)
+ {
+ rc = RTAsn1EncodeToBuffer(pCore, RTASN1ENCODE_F_DER, pbEncoded, cbEncoded, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = pThis->pProvider->pfnCertAddEncoded(pThis->pvProvider, fFlags, pbEncoded, cbEncoded, pErrInfo);
+ RTMemTmpFree(pbEncoded);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+
+ return rc;
+}
+
+
+RTDECL(int) RTCrStoreCertAddPkcs7(RTCRSTORE hStore, uint32_t fFlags, PRTCRPKCS7CERT pCertificate, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(pCertificate, VERR_INVALID_POINTER);
+ AssertReturn(RTCrPkcs7Cert_IsPresent(pCertificate), VERR_INVALID_PARAMETER);
+ switch (pCertificate->enmChoice)
+ {
+ case RTCRPKCS7CERTCHOICE_X509:
+ return RTCrStoreCertAddX509(hStore, fFlags, pCertificate->u.pX509Cert, pErrInfo);
+
+ case RTCRPKCS7CERTCHOICE_EXTENDED_PKCS6:
+ return RTErrInfoSetF(pErrInfo, VERR_NOT_IMPLEMENTED, "RTCrStoreCertAddPkcs7 does not implement EXTENDED_PKCS6");
+ case RTCRPKCS7CERTCHOICE_AC_V1:
+ return RTErrInfoSetF(pErrInfo, VERR_NOT_IMPLEMENTED, "RTCrStoreCertAddPkcs7 does not implement AC_V1");
+ case RTCRPKCS7CERTCHOICE_AC_V2:
+ return RTErrInfoSetF(pErrInfo, VERR_NOT_IMPLEMENTED, "RTCrStoreCertAddPkcs7 does not implement AC_V2");
+ case RTCRPKCS7CERTCHOICE_OTHER:
+ return RTErrInfoSetF(pErrInfo, VERR_NOT_IMPLEMENTED, "RTCrStoreCertAddPkcs7 does not implement OTHER");
+ case RTCRPKCS7CERTCHOICE_END:
+ case RTCRPKCS7CERTCHOICE_INVALID:
+ case RTCRPKCS7CERTCHOICE_32BIT_HACK:
+ break;
+ /* no default */
+ }
+ return RTErrInfoSetF(pErrInfo, VERR_INVALID_PARAMETER, "Invalid RTCRPKCS7CERT enmChoice value: %d", pCertificate->enmChoice);
+}
+
+
+/*
+ * Searching.
+ * Searching.
+ * Searching.
+ */
+
+RTDECL(int) RTCrStoreCertFindAll(RTCRSTORE hStore, PRTCRSTORECERTSEARCH pSearch)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pSearch, VERR_INVALID_POINTER);
+
+ return pThis->pProvider->pfnCertFindAll(pThis->pvProvider, pSearch);
+}
+
+
+/** Indicator for RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280 searches
+ * implemented by this front-end code. */
+#define RTCRSTORECERTSEARCH_BY_SUBECT_OR_ALT_SUBJECT_BY_RFC5280 UINT32_C(0x5be9145d)
+
+RTDECL(int) RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(RTCRSTORE hStore, PCRTCRX509NAME pSubject,
+ PRTCRSTORECERTSEARCH pSearch)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pSearch, VERR_INVALID_POINTER);
+
+ int rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, pSearch);
+ if (RT_SUCCESS(rc))
+ {
+ pSearch->auOpaque[2] = RTCRSTORECERTSEARCH_BY_SUBECT_OR_ALT_SUBJECT_BY_RFC5280;
+ pSearch->auOpaque[3] = (uintptr_t)pSubject;
+ }
+ return rc;
+}
+
+
+RTDECL(PCRTCRCERTCTX) RTCrStoreCertSearchNext(RTCRSTORE hStore, PRTCRSTORECERTSEARCH pSearch)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, NULL);
+ AssertPtrReturn(pSearch, NULL);
+
+ PCRTCRCERTCTX pRet;
+ switch (pSearch->auOpaque[2])
+ {
+ default:
+ pRet = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, pSearch);
+ break;
+
+ case RTCRSTORECERTSEARCH_BY_SUBECT_OR_ALT_SUBJECT_BY_RFC5280:
+ {
+ PCRTCRX509NAME pSubject = (PCRTCRX509NAME)pSearch->auOpaque[3];
+ AssertPtrReturn(pSubject, NULL);
+
+ for (;;)
+ {
+ pRet = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, pSearch);
+ if (!pRet)
+ break;
+ if (pRet->pCert)
+ {
+ if (RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(pRet->pCert, pSubject))
+ break;
+ }
+ else if (pRet->pTaInfo)
+ {
+ if ( RTCrTafCertPathControls_IsPresent(&pRet->pTaInfo->CertPath)
+ && RTCrX509Name_MatchByRfc5280(&pRet->pTaInfo->CertPath.TaName, pSubject))
+ break;
+ }
+ RTCrCertCtxRelease(pRet);
+ }
+ break;
+ }
+ }
+ return pRet;
+}
+
+
+RTDECL(int) RTCrStoreCertSearchDestroy(RTCRSTORE hStore, PRTCRSTORECERTSEARCH pSearch)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE);
+ if (pSearch)
+ {
+ AssertPtrReturn(pSearch, VERR_INVALID_POINTER);
+ pThis->pProvider->pfnCertSearchDestroy(pThis->pvProvider, pSearch);
+ }
+ return VINF_SUCCESS;
+}
+
+
+
+RTDECL(uint32_t) RTCrStoreCertCount(RTCRSTORE hStore)
+{
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, UINT32_MAX);
+
+ RTCRSTORECERTSEARCH Search;
+ int rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, &Search);
+ AssertRCReturn(rc, UINT32_MAX);
+
+
+ uint32_t cCerts = 0;
+ PCRTCRCERTCTX pCur;
+ while ((pCur = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, &Search)) != NULL)
+ {
+ RTCrCertCtxRelease(pCur);
+ cCerts++;
+ }
+
+ return cCerts;
+}
+
+
+#ifdef IPRT_WITH_OPENSSL
+/*
+ * OpenSSL helper.
+ * OpenSSL helper.
+ * OpenSSL helper.
+ */
+
+RTDECL(int) RTCrStoreConvertToOpenSslCertStore(RTCRSTORE hStore, uint32_t fFlags, void **ppvOpenSslStore, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pErrInfo);
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE);
+ RT_NOREF_PV(fFlags);
+
+ /*
+ * Use the pfnCertFindAll method to add all certificates to the store we're returning.
+ */
+ int rc;
+ X509_STORE *pOsslStore = X509_STORE_new();
+ if (pOsslStore)
+ {
+ RTCRSTORECERTSEARCH Search;
+ rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ PCRTCRCERTCTX pCertCtx = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, &Search);
+ if (!pCertCtx)
+ break;
+
+ if ( (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER
+ && pCertCtx->cbEncoded > 0)
+ {
+ X509 *pOsslCert = NULL;
+ const unsigned char *pabEncoded = (const unsigned char *)pCertCtx->pabEncoded;
+ if (d2i_X509(&pOsslCert, &pabEncoded, pCertCtx->cbEncoded) == pOsslCert && pOsslCert != NULL)
+ {
+ if (!X509_STORE_add_cert(pOsslStore, pOsslCert))
+ rc = VERR_NO_MEMORY;
+ X509_free(pOsslCert);
+ }
+ }
+
+ RTCrCertCtxRelease(pCertCtx);
+ } while (RT_SUCCESS(rc));
+
+ pThis->pProvider->pfnCertSearchDestroy(pThis->pvProvider, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvOpenSslStore = pOsslStore;
+ return VINF_SUCCESS;
+ }
+ }
+ X509_STORE_free(pOsslStore);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTCrStoreConvertToOpenSslCertStack(RTCRSTORE hStore, uint32_t fFlags, void **ppvOpenSslStack, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pErrInfo);
+ PRTCRSTOREINT pThis = (PRTCRSTOREINT)hStore;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRSTOREINT_MAGIC, VERR_INVALID_HANDLE);
+ RT_NOREF_PV(fFlags);
+
+ /*
+ * Use the pfnCertFindAll method to add all certificates to the store we're returning.
+ */
+ int rc;
+ STACK_OF(X509) *pOsslStack = sk_X509_new_null();
+ if (pOsslStack)
+ {
+ RTCRSTORECERTSEARCH Search;
+ rc = pThis->pProvider->pfnCertFindAll(pThis->pvProvider, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ PCRTCRCERTCTX pCertCtx = pThis->pProvider->pfnCertSearchNext(pThis->pvProvider, &Search);
+ if (!pCertCtx)
+ break;
+
+ if ( (pCertCtx->fFlags & RTCRCERTCTX_F_ENC_MASK) == RTCRCERTCTX_F_ENC_X509_DER
+ && pCertCtx->cbEncoded > 0)
+ {
+ X509 *pOsslCert = NULL;
+ const unsigned char *pabEncoded = (const unsigned char *)pCertCtx->pabEncoded;
+ if (d2i_X509(&pOsslCert, &pabEncoded, pCertCtx->cbEncoded) == pOsslCert && pOsslCert != NULL)
+ {
+ if (!sk_X509_push(pOsslStack, pOsslCert))
+ {
+ rc = VERR_NO_MEMORY;
+ X509_free(pOsslCert);
+ }
+ }
+ }
+
+ RTCrCertCtxRelease(pCertCtx);
+ } while (RT_SUCCESS(rc));
+
+ pThis->pProvider->pfnCertSearchDestroy(pThis->pvProvider, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvOpenSslStack = pOsslStack;
+ return VINF_SUCCESS;
+ }
+ }
+#include "internal/openssl-pre.h" /* Need to disable C5039 warning here. */
+ sk_X509_pop_free(pOsslStack, X509_free);
+#include "internal/openssl-post.h"
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+#endif /* IPRT_WITH_OPENSSL */
+
+
+/*
+ * Certificate context.
+ * Certificate context.
+ * Certificate context.
+ */
+
+
+RTDECL(uint32_t) RTCrCertCtxRetain(PCRTCRCERTCTX pCertCtx)
+{
+ AssertPtrReturn(pCertCtx, UINT32_MAX);
+ PRTCRCERTCTXINT pThis = RT_FROM_MEMBER(pCertCtx, RTCRCERTCTXINT, Public);
+ AssertReturn(pThis->u32Magic == RTCRCERTCTXINT_MAGIC, UINT32_MAX);
+ uint32_t cRet = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRet < 64);
+ return cRet;
+}
+
+
+RTDECL(uint32_t) RTCrCertCtxRelease(PCRTCRCERTCTX pCertCtx)
+{
+ if (!pCertCtx)
+ return 0;
+
+ AssertPtrReturn(pCertCtx, UINT32_MAX);
+ PRTCRCERTCTXINT pThis = RT_FROM_MEMBER(pCertCtx, RTCRCERTCTXINT, Public);
+ AssertReturn(pThis->u32Magic == RTCRCERTCTXINT_MAGIC, UINT32_MAX);
+ uint32_t cRet = ASMAtomicDecU32(&pThis->cRefs);
+ if (!cRet)
+ {
+ ASMAtomicWriteU32(&pThis->u32Magic, RTCRCERTCTXINT_MAGIC_DEAD);
+ pThis->pfnDtor(pThis);
+ }
+ return cRet;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/taf-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/taf-asn1-decoder.cpp
new file mode 100644
index 00000000..51b2d521
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/taf-asn1-decoder.cpp
@@ -0,0 +1,54 @@
+/* $Id: taf-asn1-decoder.cpp $ */
+/** @file
+ * IPRT - Crypto - Trust Anchor Format (RFC-5914), Decoder for ASN.1.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/taf.h>
+
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include "taf-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/crypto/taf-core.cpp b/src/VBox/Runtime/common/crypto/taf-core.cpp
new file mode 100644
index 00000000..03627c07
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/taf-core.cpp
@@ -0,0 +1,51 @@
+/* $Id: taf-core.cpp $ */
+/** @file
+ * IPRT - Crypto - Trust Anchor Format (RFC-5914), Core API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/taf.h>
+
+#include "taf-internal.h"
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-core.h>
+
diff --git a/src/VBox/Runtime/common/crypto/taf-init.cpp b/src/VBox/Runtime/common/crypto/taf-init.cpp
new file mode 100644
index 00000000..822f5fd6
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/taf-init.cpp
@@ -0,0 +1,54 @@
+/* $Id: taf-init.cpp $ */
+/** @file
+ * IPRT - Crypto - Trust Anchor Format (RFC-5914), Initialization API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/taf.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/crypto/x509.h>
+
+#include "taf-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-init.h>
+
diff --git a/src/VBox/Runtime/common/crypto/taf-internal.h b/src/VBox/Runtime/common/crypto/taf-internal.h
new file mode 100644
index 00000000..084905ed
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/taf-internal.h
@@ -0,0 +1,47 @@
+/* $Id: taf-internal.h $ */
+/** @file
+ * IPRT - Crypto - TAF, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_taf_internal_h
+#define IPRT_INCLUDED_SRC_common_crypto_taf_internal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/taf-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_taf_internal_h */
+
diff --git a/src/VBox/Runtime/common/crypto/taf-sanity.cpp b/src/VBox/Runtime/common/crypto/taf-sanity.cpp
new file mode 100644
index 00000000..f23ce203
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/taf-sanity.cpp
@@ -0,0 +1,52 @@
+/* $Id: taf-sanity.cpp $ */
+/** @file
+ * IPRT - Crypto - Trust Anchor Format (RFC-5914), Sanity Checkers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/taf.h>
+
+#include <iprt/errcore.h>
+
+#include "taf-internal.h"
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/crypto/taf-template.h b/src/VBox/Runtime/common/crypto/taf-template.h
new file mode 100644
index 00000000..3b35aeb3
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/taf-template.h
@@ -0,0 +1,104 @@
+/* $Id: taf-template.h $ */
+/** @file
+ * IPRT - Crypto - Trust Anchor Format (RFC-5914), Code Generator Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+/*
+ * CertPathControls (not sequence-/set-of).
+ */
+#define RTASN1TMPL_TYPE RTCRTAFCERTPATHCONTROLS
+#define RTASN1TMPL_EXT_NAME RTCrTafCertPathControls
+#define RTASN1TMPL_INT_NAME rtCrTafCertPathControls
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( TaName, RTCRX509NAME, RTCrX509Name);
+RTASN1TMPL_MEMBER_OPT_ITAG( Certificate, RTCRX509CERTIFICATE, RTCrX509Certificate, 0);
+RTASN1TMPL_MEMBER_OPT_ITAG( PolicySet, RTCRX509CERTIFICATEPOLICIES, RTCrX509CertificatePolicies, 1);
+RTASN1TMPL_MEMBER_OPT_ITAG_BITSTRING(PolicyFlags, 3 /* max bits */, 2);
+RTASN1TMPL_MEMBER_OPT_ITAG( NameConstr, RTCRX509NAMECONSTRAINTS, RTCrX509NameConstraints, 3);
+RTASN1TMPL_MEMBER_OPT_ITAG_EX( PathLenConstraint, RTASN1INTEGER, RTAsn1Integer, 4, RTASN1TMPL_ITAG_F_CP, RT_NOTHING);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * TrustAnchorInfo.
+ */
+#define RTASN1TMPL_TYPE RTCRTAFTRUSTANCHORINFO
+#define RTASN1TMPL_EXT_NAME RTCrTafTrustAnchorInfo
+#define RTASN1TMPL_INT_NAME rtCrTafTrustAnchorInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER_DEF_ITAG_UP( Version, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER, RTCRTAFTRUSTANCHORINFO_V1);
+RTASN1TMPL_MEMBER( PubKey, RTCRX509SUBJECTPUBLICKEYINFO, RTCrX509SubjectPublicKeyInfo);
+RTASN1TMPL_MEMBER( KeyIdentifier, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_MEMBER_OPT_UTF8_STRING_EX(TaTitle, RTASN1TMPL_MEMBER_CONSTR_MIN_MAX(TaTitleLangTag, RTASN1STRING, RTAsn1String, 1, 64, RT_NOTHING));
+RTASN1TMPL_MEMBER_OPT_ITAG_EX( CertPath, RTCRTAFCERTPATHCONTROLS, RTCrTafCertPathControls, ASN1_TAG_SEQUENCE, RTASN1TMPL_ITAG_F_UC, RT_NOTHING);
+RTASN1TMPL_MEMBER_OPT_XTAG( T1, CtxTag1, Exts, RTCRX509EXTENSIONS, RTCrX509Extensions, 1);
+RTASN1TMPL_MEMBER_OPT_UTF8_STRING_EX(TaTitleLangTag, RTASN1TMPL_MEMBER_CONSTR_MIN_MAX(TaTitleLangTag, RTASN1STRING, RTAsn1String, 2, 4, RT_NOTHING));
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * TrustAnchorChoice.
+ */
+#define RTASN1TMPL_TYPE RTCRTAFTRUSTANCHORCHOICE
+#define RTASN1TMPL_EXT_NAME RTCrTafTrustAnchorChoice
+#define RTASN1TMPL_INT_NAME rtCrTafTrustAnchorChoice
+RTASN1TMPL_BEGIN_PCHOICE();
+RTASN1TMPL_PCHOICE_ITAG(ASN1_TAG_SEQUENCE, RTCRTAFTRUSTANCHORCHOICEVAL_CERTIFICATE, u.pCertificate, Certificate, RTCRX509CERTIFICATE, RTCrX509Certificate);
+RTASN1TMPL_PCHOICE_XTAG(1, RTCRTAFTRUSTANCHORCHOICEVAL_TBS_CERTIFICATE, u.pT1, CtxTag1, TbsCert, RTCRX509TBSCERTIFICATE, RTCrX509TbsCertificate);
+RTASN1TMPL_PCHOICE_XTAG(2, RTCRTAFTRUSTANCHORCHOICEVAL_TRUST_ANCHOR_INFO, u.pT2, CtxTag2, TaInfo, RTCRTAFTRUSTANCHORINFO, RTCrTafTrustAnchorInfo);
+RTASN1TMPL_END_PCHOICE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * TrustAnchorList
+ */
+#define RTASN1TMPL_TYPE RTCRTAFTRUSTANCHORLIST
+#define RTASN1TMPL_EXT_NAME RTCrTafTrustAnchorList
+#define RTASN1TMPL_INT_NAME rtCrTafTrustAnchorList
+RTASN1TMPL_SEQ_OF(RTCRTAFTRUSTANCHORCHOICE, RTCrTafTrustAnchorChoice);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/crypto/tsp-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/tsp-asn1-decoder.cpp
new file mode 100644
index 00000000..b0e7a808
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/tsp-asn1-decoder.cpp
@@ -0,0 +1,51 @@
+/* $Id: tsp-asn1-decoder.cpp $ */
+/** @file
+ * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Decoder for ASN.1.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/tsp.h>
+
+#include "tsp-internal.h"
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/crypto/tsp-core.cpp b/src/VBox/Runtime/common/crypto/tsp-core.cpp
new file mode 100644
index 00000000..ee0c5329
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/tsp-core.cpp
@@ -0,0 +1,51 @@
+/* $Id: tsp-core.cpp $ */
+/** @file
+ * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Core API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/tsp.h>
+
+#include "tsp-internal.h"
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-core.h>
+
diff --git a/src/VBox/Runtime/common/crypto/tsp-init.cpp b/src/VBox/Runtime/common/crypto/tsp-init.cpp
new file mode 100644
index 00000000..4d19c09d
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/tsp-init.cpp
@@ -0,0 +1,51 @@
+/* $Id: tsp-init.cpp $ */
+/** @file
+ * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Initialization API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/tsp.h>
+
+#include "tsp-internal.h"
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-init.h>
+
diff --git a/src/VBox/Runtime/common/crypto/tsp-internal.h b/src/VBox/Runtime/common/crypto/tsp-internal.h
new file mode 100644
index 00000000..34797c8b
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/tsp-internal.h
@@ -0,0 +1,47 @@
+/* $Id: tsp-internal.h $ */
+/** @file
+ * IPRT - Crypto - TSP, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_tsp_internal_h
+#define IPRT_INCLUDED_SRC_common_crypto_tsp_internal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/tsp-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_tsp_internal_h */
+
diff --git a/src/VBox/Runtime/common/crypto/tsp-sanity.cpp b/src/VBox/Runtime/common/crypto/tsp-sanity.cpp
new file mode 100644
index 00000000..2444733c
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/tsp-sanity.cpp
@@ -0,0 +1,51 @@
+/* $Id: tsp-sanity.cpp $ */
+/** @file
+ * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Sanity Checkers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/tsp.h>
+
+#include "tsp-internal.h"
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/crypto/tsp-template.h b/src/VBox/Runtime/common/crypto/tsp-template.h
new file mode 100644
index 00000000..ab288873
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/tsp-template.h
@@ -0,0 +1,113 @@
+/* $Id: tsp-template.h $ */
+/** @file
+ * IPRT - Crypto - Time-Stamp Protocol (RFC-3161), Code Generator Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+/*
+ * MessageImprint
+ */
+#define RTASN1TMPL_TYPE RTCRTSPMESSAGEIMPRINT
+#define RTASN1TMPL_EXT_NAME RTCrTspMessageImprint
+#define RTASN1TMPL_INT_NAME rtCrTspMessageImprint
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( HashAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER( HashedMessage, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+/*
+ * TimeStampReq
+ */
+
+/*
+ * PKIStatusInfo
+ */
+
+/*
+ * TimeStampResp
+ */
+
+/*
+ * Accuracy
+ *
+ * Note! Capping second accuracy at an hour to reduce chance exploiting this
+ * field to tinker with a signed structure. The RFC does not specify
+ * any upper limit.
+ *
+ * Note! Allowing a zero value for the 'millis' field because we've seen symantec
+ * return that when 'micros' is present. The RFC seems to want the TSA to
+ * omit the field if its value is zero.
+ */
+#define RTASN1TMPL_TYPE RTCRTSPACCURACY
+#define RTASN1TMPL_EXT_NAME RTCrTspAccuracy
+#define RTASN1TMPL_INT_NAME rtCrTspAccuracy
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER_OPT_ITAG_EX( Seconds, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER, RTASN1TMPL_ITAG_F_UP,
+ RTASN1TMPL_MEMBER_CONSTR_U64_MIN_MAX(Seconds, 0, 3600, RT_NOTHING));
+RTASN1TMPL_MEMBER_OPT_ITAG_EX( Millis, RTASN1INTEGER, RTAsn1Integer, 0, RTASN1TMPL_ITAG_F_CP,
+ RTASN1TMPL_MEMBER_CONSTR_U64_MIN_MAX(Millis, 0, 999, RT_NOTHING));
+RTASN1TMPL_MEMBER_OPT_ITAG_EX( Micros, RTASN1INTEGER, RTAsn1Integer, 1, RTASN1TMPL_ITAG_F_CP,
+ RTASN1TMPL_MEMBER_CONSTR_U64_MIN_MAX(Micros, 1, 999, RT_NOTHING));
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * TSTInfo
+ */
+#define RTASN1TMPL_TYPE RTCRTSPTSTINFO
+#define RTASN1TMPL_EXT_NAME RTCrTspTstInfo
+#define RTASN1TMPL_INT_NAME rtCrTspTstInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Version, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Policy, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER( MessageImprint, RTCRTSPMESSAGEIMPRINT, RTCrTspMessageImprint);
+RTASN1TMPL_MEMBER( SerialNumber, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( GenTime, RTASN1TIME, RTAsn1GeneralizedTime);
+RTASN1TMPL_MEMBER_OPT_ITAG_UC( Accuracy, RTCRTSPACCURACY, RTCrTspAccuracy, ASN1_TAG_SEQUENCE);
+RTASN1TMPL_MEMBER_DEF_ITAG_UP( Ordering, RTASN1BOOLEAN, RTAsn1Boolean, ASN1_TAG_BOOLEAN, 0 /*False*/);
+RTASN1TMPL_MEMBER_OPT_ITAG_UP( Nonce, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER);
+RTASN1TMPL_MEMBER_OPT_XTAG( T0, CtxTag0, Tsa, RTCRX509GENERALNAME, RTCrX509GeneralName, 0);
+RTASN1TMPL_MEMBER_OPT_ITAG( Extensions, RTCRX509EXTENSION, RTCrX509Extension, 1);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/crypto/x509-asn1-decoder.cpp b/src/VBox/Runtime/common/crypto/x509-asn1-decoder.cpp
new file mode 100644
index 00000000..a510e130
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/x509-asn1-decoder.cpp
@@ -0,0 +1,230 @@
+/* $Id: x509-asn1-decoder.cpp $ */
+/** @file
+ * IPRT - Crypto - X.509, Decoder for ASN.1.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/x509.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#include "x509-internal.h"
+
+
+/*
+ * One X.509 Extension.
+ */
+RTDECL(int) RTCrX509Extension_ExtnValue_DecodeAsn1(PRTASN1CURSOR pCursor, uint32_t fFlags,
+ PRTCRX509EXTENSION pThis, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags); RT_NOREF_PV(pszErrorTag);
+
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_UNKNOWN;
+
+ /*
+ * Decode the encapsulated extension bytes if know the format.
+ */
+ RTASN1CURSOR ValueCursor;
+ int rc = RTAsn1CursorInitSubFromCore(pCursor, &pThis->ExtnValue.Asn1Core, &ValueCursor, "ExtnValue");
+ if (RT_FAILURE(rc))
+ return rc;
+ pCursor = &ValueCursor;
+
+ if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_AUTHORITY_KEY_IDENTIFIER_OID) == 0)
+ {
+ /* 4.2.1.1 Authority Key Identifier */
+ PRTCRX509AUTHORITYKEYIDENTIFIER pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_AUTHORITY_KEY_IDENTIFIER;
+ rc = RTCrX509AuthorityKeyIdentifier_DecodeAsn1(&ValueCursor, 0, pThat, "AuthorityKeyIdentifier");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_OLD_AUTHORITY_KEY_IDENTIFIER_OID) == 0)
+ {
+ /* Old and obsolete version of the above, still found in microsoft certificates. */
+ PRTCRX509OLDAUTHORITYKEYIDENTIFIER pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_OLD_AUTHORITY_KEY_IDENTIFIER;
+ rc = RTCrX509OldAuthorityKeyIdentifier_DecodeAsn1(&ValueCursor, 0, pThat, "OldAuthorityKeyIdentifier");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_SUBJECT_KEY_IDENTIFIER_OID) == 0)
+ {
+ /* 4.2.1.2 Subject Key Identifier */
+ PRTASN1OCTETSTRING pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_OCTET_STRING;
+ rc = RTAsn1CursorGetOctetString(&ValueCursor, 0, pThat, "SubjectKeyIdentifier");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_KEY_USAGE_OID) == 0)
+ {
+ /* 4.2.1.3 Key Usage */
+ PRTASN1BITSTRING pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_BIT_STRING;
+ rc = RTAsn1CursorGetBitStringEx(&ValueCursor, 0, 9, pThat, "KeyUsage");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_CERTIFICATE_POLICIES_OID) == 0)
+ {
+ /* 4.2.1.4 Certificate Policies */
+ PRTCRX509CERTIFICATEPOLICIES pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_CERTIFICATE_POLICIES;
+ rc = RTCrX509CertificatePolicies_DecodeAsn1(&ValueCursor, 0, pThat, "CertPolicies");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_POLICY_MAPPINGS_OID) == 0)
+ {
+ /* 4.2.1.5 Policy Mappings */
+ PRTCRX509POLICYMAPPINGS pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_POLICY_MAPPINGS;
+ rc = RTCrX509PolicyMappings_DecodeAsn1(&ValueCursor, 0, pThat, "PolicyMapppings");
+ }
+ }
+ else if ( RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_SUBJECT_ALT_NAME_OID) == 0
+ || RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_ISSUER_ALT_NAME_OID) == 0)
+ {
+ /* 4.2.1.6 Subject Alternative Name / 4.2.1.7 Issuer Alternative Name */
+ PRTCRX509GENERALNAMES pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_GENERAL_NAMES;
+ rc = RTCrX509GeneralNames_DecodeAsn1(&ValueCursor, 0, pThat, "AltName");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_BASIC_CONSTRAINTS_OID) == 0)
+ {
+ /* 4.2.1.9 Basic Constraints */
+ PRTCRX509BASICCONSTRAINTS pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_BASIC_CONSTRAINTS;
+ rc = RTCrX509BasicConstraints_DecodeAsn1(&ValueCursor, 0, pThat, "BasicConstraints");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_NAME_CONSTRAINTS_OID) == 0)
+ {
+ /* 4.2.1.10 Name Constraints */
+ PRTCRX509NAMECONSTRAINTS pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_NAME_CONSTRAINTS;
+ rc = RTCrX509NameConstraints_DecodeAsn1(&ValueCursor, 0, pThat, "NameConstraints");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_POLICY_CONSTRAINTS_OID) == 0)
+ {
+ /* 4.2.1.11 Policy Constraints */
+ PRTCRX509POLICYCONSTRAINTS pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_POLICY_CONSTRAINTS;
+ rc = RTCrX509PolicyConstraints_DecodeAsn1(&ValueCursor, 0, pThat, "PolicyConstraints");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_EXT_KEY_USAGE_OID) == 0)
+ {
+ /* 4.2.1.12 Extended Key Usage */
+ PRTASN1SEQOFOBJIDS pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->SeqCore.Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_SEQ_OF_OBJ_IDS;
+ rc = RTAsn1SeqOfObjIds_DecodeAsn1(&ValueCursor, 0, pThat, "ExKeyUsage");
+ }
+ }
+ else if (RTAsn1ObjId_CompareWithString(&pThis->ExtnId, RTCRX509_ID_CE_EXT_KEY_USAGE_OID) == 0)
+ {
+ /* 4.2.1.14 Inhibit anyPolicy */
+ PRTASN1INTEGER pThat;
+ rc = RTAsn1MemAllocZ(&pThis->ExtnValue.EncapsulatedAllocation, (void **)&pThat, sizeof(*pThat));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->ExtnValue.pEncapsulated = &pThat->Asn1Core;
+ pThis->enmValue = RTCRX509EXTENSIONVALUE_INTEGER;
+ rc = RTAsn1CursorGetInteger(&ValueCursor, 0, pThat, "InhibitAnyPolicy");
+ }
+ }
+ else
+ return VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ rc = RTAsn1CursorCheckEnd(&ValueCursor);
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rc;
+}
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-asn1-decoder.h>
+
diff --git a/src/VBox/Runtime/common/crypto/x509-certpaths.cpp b/src/VBox/Runtime/common/crypto/x509-certpaths.cpp
new file mode 100644
index 00000000..1ef8cc5c
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/x509-certpaths.cpp
@@ -0,0 +1,3017 @@
+/* $Id: x509-certpaths.cpp $ */
+/** @file
+ * IPRT - Crypto - X.509, Simple Certificate Path Builder & Validator.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_CRYPTO
+#include "internal/iprt.h"
+#include <iprt/crypto/x509.h>
+
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/time.h>
+#include <iprt/crypto/applecodesign.h> /* critical extension OIDs */
+#include <iprt/crypto/pkcs7.h> /* PCRTCRPKCS7SETOFCERTS */
+#include <iprt/crypto/store.h>
+
+#include "x509-internal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * X.509 certificate path node.
+ */
+typedef struct RTCRX509CERTPATHNODE
+{
+ /** Sibling list entry. */
+ RTLISTNODE SiblingEntry;
+ /** List of children or leaf list entry. */
+ RTLISTANCHOR ChildListOrLeafEntry;
+ /** Pointer to the parent node. NULL for root. */
+ struct RTCRX509CERTPATHNODE *pParent;
+
+ /** The distance between this node and the target. */
+ uint32_t uDepth : 8;
+ /** Indicates the source of this certificate. */
+ uint32_t uSrc : 3;
+ /** Set if this is a leaf node. */
+ uint32_t fLeaf : 1;
+ /** Makes sure it's a 32-bit bitfield. */
+ uint32_t uReserved : 20;
+
+ /** Leaf only: The result of the last path vertification. */
+ int rcVerify;
+
+ /** Pointer to the certificate. This can be NULL only for trust anchors. */
+ PCRTCRX509CERTIFICATE pCert;
+
+ /** If the certificate or trust anchor was obtained from a store, this is the
+ * associated certificate context (referenced of course). This is used to
+ * access the trust anchor information, if present.
+ *
+ * (If this is NULL it's from a certificate array or some such given directly to
+ * the path building code. It's assumed the caller doesn't free these until the
+ * path validation/whatever is done with and the paths destroyed.) */
+ PCRTCRCERTCTX pCertCtx;
+} RTCRX509CERTPATHNODE;
+/** Pointer to a X.509 path node. */
+typedef RTCRX509CERTPATHNODE *PRTCRX509CERTPATHNODE;
+
+/** @name RTCRX509CERTPATHNODE::uSrc values.
+ * The trusted and untrusted sources ordered in priority order, where higher
+ * number means high priority in case of duplicates.
+ * @{ */
+#define RTCRX509CERTPATHNODE_SRC_NONE 0
+#define RTCRX509CERTPATHNODE_SRC_TARGET 1
+#define RTCRX509CERTPATHNODE_SRC_UNTRUSTED_SET 2
+#define RTCRX509CERTPATHNODE_SRC_UNTRUSTED_ARRAY 3
+#define RTCRX509CERTPATHNODE_SRC_UNTRUSTED_STORE 4
+#define RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE 5
+#define RTCRX509CERTPATHNODE_SRC_TRUSTED_CERT 6
+#define RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(uSrc) ((uSrc) >= RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE)
+/** @} */
+
+
+/**
+ * Policy tree node.
+ */
+typedef struct RTCRX509CERTPATHSPOLICYNODE
+{
+ /** Sibling list entry. */
+ RTLISTNODE SiblingEntry;
+ /** Tree depth list entry. */
+ RTLISTNODE DepthEntry;
+ /** List of children or leaf list entry. */
+ RTLISTANCHOR ChildList;
+ /** Pointer to the parent. */
+ struct RTCRX509CERTPATHSPOLICYNODE *pParent;
+
+ /** The policy object ID. */
+ PCRTASN1OBJID pValidPolicy;
+
+ /** Optional sequence of policy qualifiers. */
+ PCRTCRX509POLICYQUALIFIERINFOS pPolicyQualifiers;
+
+ /** The first policy ID in the exepcted policy set. */
+ PCRTASN1OBJID pExpectedPolicyFirst;
+ /** Set if we've already mapped pExpectedPolicyFirst. */
+ bool fAlreadyMapped;
+ /** Number of additional items in the expected policy set. */
+ uint32_t cMoreExpectedPolicySet;
+ /** Additional items in the expected policy set. */
+ PCRTASN1OBJID *papMoreExpectedPolicySet;
+} RTCRX509CERTPATHSPOLICYNODE;
+/** Pointer to a policy tree node. */
+typedef RTCRX509CERTPATHSPOLICYNODE *PRTCRX509CERTPATHSPOLICYNODE;
+
+
+/**
+ * Path builder and validator instance.
+ *
+ * The path builder creates a tree of certificates by forward searching from the
+ * end-entity towards a trusted source. The leaf nodes are inserted into list
+ * ordered by the source of the leaf certificate and the path length (i.e. tree
+ * depth).
+ *
+ * The path validator works the tree from the leaf end and validates each
+ * potential path found by the builder. It is generally happy with one working
+ * path, but may be told to verify all of them.
+ */
+typedef struct RTCRX509CERTPATHSINT
+{
+ /** Magic number. */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+
+ /** @name Input
+ * @{ */
+ /** The target certificate (end entity) to build a trusted path for. */
+ PCRTCRX509CERTIFICATE pTarget;
+
+ /** Lone trusted certificate. */
+ PCRTCRX509CERTIFICATE pTrustedCert;
+ /** Store of trusted certificates. */
+ RTCRSTORE hTrustedStore;
+
+ /** Store of untrusted certificates. */
+ RTCRSTORE hUntrustedStore;
+ /** Array of untrusted certificates, typically from the protocol. */
+ PCRTCRX509CERTIFICATE paUntrustedCerts;
+ /** Number of entries in paUntrusted. */
+ uint32_t cUntrustedCerts;
+ /** Set of untrusted PKCS \#7 / CMS certificatess. */
+ PCRTCRPKCS7SETOFCERTS pUntrustedCertsSet;
+
+ /** UTC time we're going to validate the path at, requires
+ * RTCRX509CERTPATHSINT_F_VALID_TIME to be set. */
+ RTTIMESPEC ValidTime;
+ /** Number of policy OIDs in the user initial policy set, 0 means anyPolicy. */
+ uint32_t cInitialUserPolicySet;
+ /** The user initial policy set. As with all other user provided data, we
+ * assume it's immutable and remains valid for the usage period of the path
+ * builder & validator. */
+ PCRTASN1OBJID *papInitialUserPolicySet;
+ /** Number of certificates before the user wants an explicit policy result.
+ * Set to UINT32_MAX no explicit policy restriction required by the user. */
+ uint32_t cInitialExplicitPolicy;
+ /** Number of certificates before the user wants policy mapping to be
+ * inhibited. Set to UINT32_MAX if no initial policy mapping inhibition
+ * desired by the user. */
+ uint32_t cInitialPolicyMappingInhibit;
+ /** Number of certificates before the user wants the anyPolicy to be rejected.
+ * Set to UINT32_MAX no explicit policy restriction required by the user. */
+ uint32_t cInitialInhibitAnyPolicy;
+ /** Initial name restriction: Permitted subtrees. */
+ PCRTCRX509GENERALSUBTREES pInitialPermittedSubtrees;
+ /** Initial name restriction: Excluded subtrees. */
+ PCRTCRX509GENERALSUBTREES pInitialExcludedSubtrees;
+
+ /** Flags RTCRX509CERTPATHSINT_F_XXX. */
+ uint32_t fFlags;
+ /** @} */
+
+ /** Sticky status for remembering allocation errors and the like. */
+ int32_t rc;
+ /** Where to store extended error info (optional). */
+ PRTERRINFO pErrInfo;
+
+ /** @name Path Builder Output
+ * @{ */
+ /** Pointer to the root of the tree. This will always be non-NULL after path
+ * building and thus can be reliably used to tell if path building has taken
+ * place or not. */
+ PRTCRX509CERTPATHNODE pRoot;
+ /** List of working leaf tree nodes. */
+ RTLISTANCHOR LeafList;
+ /** The number of paths (leafs). */
+ uint32_t cPaths;
+ /** @} */
+
+ /** Path Validator State. */
+ struct
+ {
+ /** Number of nodes in the certificate path we're validating (aka 'n'). */
+ uint32_t cNodes;
+ /** The current node (0 being the trust anchor). */
+ uint32_t iNode;
+
+ /** The root node of the valid policy tree. */
+ PRTCRX509CERTPATHSPOLICYNODE pValidPolicyTree;
+ /** An array of length cNodes + 1 which tracks all nodes at the given (index)
+ * tree depth via the RTCRX509CERTPATHSPOLICYNODE::DepthEntry member. */
+ PRTLISTANCHOR paValidPolicyDepthLists;
+
+ /** Number of entries in paPermittedSubtrees (name constraints).
+ * If zero, no permitted name constrains currently in effect. */
+ uint32_t cPermittedSubtrees;
+ /** The allocated size of papExcludedSubtrees */
+ uint32_t cPermittedSubtreesAlloc;
+ /** Array of permitted subtrees we've collected so far (name constraints). */
+ PCRTCRX509GENERALSUBTREE *papPermittedSubtrees;
+ /** Set if we end up with an empty set after calculating a name constraints
+ * union. */
+ bool fNoPermittedSubtrees;
+
+ /** Number of entries in paExcludedSubtrees (name constraints).
+ * If zero, no excluded name constrains currently in effect. */
+ uint32_t cExcludedSubtrees;
+ /** Array of excluded subtrees we've collected so far (name constraints). */
+ PCRTCRX509GENERALSUBTREES *papExcludedSubtrees;
+
+ /** Number of non-self-issued certificates to be processed before a non-NULL
+ * paValidPolicyTree is required. */
+ uint32_t cExplicitPolicy;
+ /** Number of non-self-issued certificates to be processed we stop processing
+ * policy mapping extensions. */
+ uint32_t cInhibitPolicyMapping;
+ /** Number of non-self-issued certificates to be processed before a the
+ * anyPolicy is rejected. */
+ uint32_t cInhibitAnyPolicy;
+ /** Number of non-self-issued certificates we're allowed to process. */
+ uint32_t cMaxPathLength;
+
+ /** The working issuer name. */
+ PCRTCRX509NAME pWorkingIssuer;
+ /** The working public key algorithm ID. */
+ PCRTASN1OBJID pWorkingPublicKeyAlgorithm;
+ /** The working public key algorithm parameters. */
+ PCRTASN1DYNTYPE pWorkingPublicKeyParameters;
+ /** A bit string containing the public key. */
+ PCRTASN1BITSTRING pWorkingPublicKey;
+ } v;
+
+ /** An object identifier initialized to anyPolicy. */
+ RTASN1OBJID AnyPolicyObjId;
+
+ /** Temporary scratch space. */
+ char szTmp[1024];
+} RTCRX509CERTPATHSINT;
+typedef RTCRX509CERTPATHSINT *PRTCRX509CERTPATHSINT;
+
+/** Magic value for RTCRX509CERTPATHSINT::u32Magic (Bruce Schneier). */
+#define RTCRX509CERTPATHSINT_MAGIC UINT32_C(0x19630115)
+
+/** @name RTCRX509CERTPATHSINT_F_XXX - Certificate path build flags.
+ * @{ */
+#define RTCRX509CERTPATHSINT_F_VALID_TIME RT_BIT_32(0)
+#define RTCRX509CERTPATHSINT_F_ELIMINATE_UNTRUSTED_PATHS RT_BIT_32(1)
+/** Whether checking the trust anchor signature (if self signed) and
+ * that it is valid at the verification time, also require it to be a CA if not
+ * leaf node. */
+#define RTCRX509CERTPATHSINT_F_CHECK_TRUST_ANCHOR RT_BIT_32(2)
+#define RTCRX509CERTPATHSINT_F_VALID_MASK UINT32_C(0x00000007)
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtCrX509CertPathsDestroyTree(PRTCRX509CERTPATHSINT pThis);
+static void rtCrX509CpvCleanup(PRTCRX509CERTPATHSINT pThis);
+
+
+/** @name Path Builder and Validator Config APIs
+ * @{
+ */
+
+RTDECL(int) RTCrX509CertPathsCreate(PRTCRX509CERTPATHS phCertPaths, PCRTCRX509CERTIFICATE pTarget)
+{
+ AssertPtrReturn(phCertPaths, VERR_INVALID_POINTER);
+
+ PRTCRX509CERTPATHSINT pThis = (PRTCRX509CERTPATHSINT)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ int rc = RTAsn1ObjId_InitFromString(&pThis->AnyPolicyObjId, RTCRX509_ID_CE_CP_ANY_POLICY_OID, &g_RTAsn1DefaultAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->u32Magic = RTCRX509CERTPATHSINT_MAGIC;
+ pThis->cRefs = 1;
+ pThis->pTarget = pTarget;
+ pThis->hTrustedStore = NIL_RTCRSTORE;
+ pThis->hUntrustedStore = NIL_RTCRSTORE;
+ pThis->cInitialExplicitPolicy = UINT32_MAX;
+ pThis->cInitialPolicyMappingInhibit = UINT32_MAX;
+ pThis->cInitialInhibitAnyPolicy = UINT32_MAX;
+ pThis->rc = VINF_SUCCESS;
+ RTListInit(&pThis->LeafList);
+ *phCertPaths = pThis;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+RTDECL(uint32_t) RTCrX509CertPathsRetain(RTCRX509CERTPATHS hCertPaths)
+{
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 0 && cRefs < 64);
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTCrX509CertPathsRelease(RTCRX509CERTPATHS hCertPaths)
+{
+ uint32_t cRefs;
+ if (hCertPaths != NIL_RTCRX509CERTPATHS)
+ {
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, UINT32_MAX);
+
+ cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 64);
+ if (!cRefs)
+ {
+ /*
+ * No more references, destroy the whole thing.
+ */
+ ASMAtomicWriteU32(&pThis->u32Magic, ~RTCRX509CERTPATHSINT_MAGIC);
+
+ /* config */
+ pThis->pTarget = NULL; /* Referencing user memory. */
+ pThis->pTrustedCert = NULL; /* Referencing user memory. */
+ RTCrStoreRelease(pThis->hTrustedStore);
+ pThis->hTrustedStore = NIL_RTCRSTORE;
+ RTCrStoreRelease(pThis->hUntrustedStore);
+ pThis->hUntrustedStore = NIL_RTCRSTORE;
+ pThis->paUntrustedCerts = NULL; /* Referencing user memory. */
+ pThis->pUntrustedCertsSet = NULL; /* Referencing user memory. */
+ pThis->papInitialUserPolicySet = NULL; /* Referencing user memory. */
+ pThis->pInitialPermittedSubtrees = NULL; /* Referencing user memory. */
+ pThis->pInitialExcludedSubtrees = NULL; /* Referencing user memory. */
+
+ /* builder */
+ rtCrX509CertPathsDestroyTree(pThis);
+
+ /* validator */
+ rtCrX509CpvCleanup(pThis);
+
+ /* misc */
+ RTAsn1VtDelete(&pThis->AnyPolicyObjId.Asn1Core);
+
+ /* Finally, the instance itself. */
+ RTMemFree(pThis);
+ }
+ }
+ else
+ cRefs = 0;
+ return cRefs;
+}
+
+
+
+RTDECL(int) RTCrX509CertPathsSetTrustedStore(RTCRX509CERTPATHS hCertPaths, RTCRSTORE hTrustedStore)
+{
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->pRoot == NULL, VERR_WRONG_ORDER);
+
+ if (pThis->hTrustedStore != NIL_RTCRSTORE)
+ {
+ RTCrStoreRelease(pThis->hTrustedStore);
+ pThis->hTrustedStore = NIL_RTCRSTORE;
+ }
+ if (hTrustedStore != NIL_RTCRSTORE)
+ {
+ AssertReturn(RTCrStoreRetain(hTrustedStore) != UINT32_MAX, VERR_INVALID_HANDLE);
+ pThis->hTrustedStore = hTrustedStore;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrX509CertPathsSetUntrustedStore(RTCRX509CERTPATHS hCertPaths, RTCRSTORE hUntrustedStore)
+{
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->pRoot == NULL, VERR_WRONG_ORDER);
+
+ if (pThis->hUntrustedStore != NIL_RTCRSTORE)
+ {
+ RTCrStoreRelease(pThis->hUntrustedStore);
+ pThis->hUntrustedStore = NIL_RTCRSTORE;
+ }
+ if (hUntrustedStore != NIL_RTCRSTORE)
+ {
+ AssertReturn(RTCrStoreRetain(hUntrustedStore) != UINT32_MAX, VERR_INVALID_HANDLE);
+ pThis->hUntrustedStore = hUntrustedStore;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrX509CertPathsSetUntrustedArray(RTCRX509CERTPATHS hCertPaths, PCRTCRX509CERTIFICATE paCerts, uint32_t cCerts)
+{
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+
+ pThis->paUntrustedCerts = paCerts;
+ pThis->cUntrustedCerts = cCerts;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrX509CertPathsSetUntrustedSet(RTCRX509CERTPATHS hCertPaths, PCRTCRPKCS7SETOFCERTS pSetOfCerts)
+{
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+
+ pThis->pUntrustedCertsSet = pSetOfCerts;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrX509CertPathsSetValidTime(RTCRX509CERTPATHS hCertPaths, PCRTTIME pTime)
+{
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+
+ /* Allow this after building paths, as it's only used during verification. */
+
+ if (pTime)
+ {
+ if (RTTimeImplode(&pThis->ValidTime, pTime))
+ return VERR_INVALID_PARAMETER;
+ pThis->fFlags |= RTCRX509CERTPATHSINT_F_VALID_TIME;
+ }
+ else
+ pThis->fFlags &= ~RTCRX509CERTPATHSINT_F_VALID_TIME;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrX509CertPathsSetValidTimeSpec(RTCRX509CERTPATHS hCertPaths, PCRTTIMESPEC pTimeSpec)
+{
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+
+ /* Allow this after building paths, as it's only used during verification. */
+
+ if (pTimeSpec)
+ {
+ pThis->ValidTime = *pTimeSpec;
+ pThis->fFlags |= RTCRX509CERTPATHSINT_F_VALID_TIME;
+ }
+ else
+ pThis->fFlags &= ~RTCRX509CERTPATHSINT_F_VALID_TIME;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrX509CertPathsSetTrustAnchorChecks(RTCRX509CERTPATHS hCertPaths, bool fEnable)
+{
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+
+ if (fEnable)
+ pThis->fFlags |= RTCRX509CERTPATHSINT_F_CHECK_TRUST_ANCHOR;
+ else
+ pThis->fFlags &= ~RTCRX509CERTPATHSINT_F_CHECK_TRUST_ANCHOR;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrX509CertPathsCreateEx(PRTCRX509CERTPATHS phCertPaths, PCRTCRX509CERTIFICATE pTarget, RTCRSTORE hTrustedStore,
+ RTCRSTORE hUntrustedStore, PCRTCRX509CERTIFICATE paUntrustedCerts, uint32_t cUntrustedCerts,
+ PCRTTIMESPEC pValidTime)
+{
+ int rc = RTCrX509CertPathsCreate(phCertPaths, pTarget);
+ if (RT_SUCCESS(rc))
+ {
+ PRTCRX509CERTPATHSINT pThis = *phCertPaths;
+
+ rc = RTCrX509CertPathsSetTrustedStore(pThis, hTrustedStore);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509CertPathsSetUntrustedStore(pThis, hUntrustedStore);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509CertPathsSetUntrustedArray(pThis, paUntrustedCerts, cUntrustedCerts);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509CertPathsSetValidTimeSpec(pThis, pValidTime);
+ if (RT_SUCCESS(rc))
+ {
+ return VINF_SUCCESS;
+ }
+ }
+ RTCrStoreRelease(pThis->hUntrustedStore);
+ }
+ RTCrStoreRelease(pThis->hTrustedStore);
+ }
+ RTMemFree(pThis);
+ *phCertPaths = NIL_RTCRX509CERTPATHS;
+ }
+ return rc;
+}
+
+/** @} */
+
+
+
+/** @name Path Builder and Validator Common Utility Functions.
+ * @{
+ */
+
+/**
+ * Checks if the certificate is self-issued.
+ *
+ * @returns true / false.
+ * @param pNode The path node to check..
+ */
+static bool rtCrX509CertPathsIsSelfIssued(PRTCRX509CERTPATHNODE pNode)
+{
+ return pNode->pCert
+ && RTCrX509Name_MatchByRfc5280(&pNode->pCert->TbsCertificate.Subject, &pNode->pCert->TbsCertificate.Issuer);
+}
+
+/**
+ * Helper for checking whether a certificate is in the trusted store or not.
+ */
+static bool rtCrX509CertPathsIsCertInStore(PRTCRX509CERTPATHNODE pNode, RTCRSTORE hStore)
+{
+ bool fRc = false;
+ PCRTCRCERTCTX pCertCtx = RTCrStoreCertByIssuerAndSerialNo(hStore, &pNode->pCert->TbsCertificate.Issuer,
+ &pNode->pCert->TbsCertificate.SerialNumber);
+ if (pCertCtx)
+ {
+ if (pCertCtx->pCert)
+ fRc = RTCrX509Certificate_Compare(pCertCtx->pCert, pNode->pCert) == 0;
+ RTCrCertCtxRelease(pCertCtx);
+ }
+ return fRc;
+}
+
+/** @} */
+
+
+
+/** @name Path Builder Functions.
+ * @{
+ */
+
+static PRTCRX509CERTPATHNODE rtCrX509CertPathsNewNode(PRTCRX509CERTPATHSINT pThis)
+{
+ PRTCRX509CERTPATHNODE pNode = (PRTCRX509CERTPATHNODE)RTMemAllocZ(sizeof(*pNode));
+ if (RT_LIKELY(pNode))
+ {
+ RTListInit(&pNode->SiblingEntry);
+ RTListInit(&pNode->ChildListOrLeafEntry);
+ pNode->rcVerify = VERR_CR_X509_NOT_VERIFIED;
+
+ return pNode;
+ }
+
+ pThis->rc = RTErrInfoSet(pThis->pErrInfo, VERR_NO_MEMORY, "No memory for path node");
+ return NULL;
+}
+
+
+static void rtCrX509CertPathsDestroyNode(PRTCRX509CERTPATHNODE pNode)
+{
+ if (pNode->pCertCtx)
+ {
+ RTCrCertCtxRelease(pNode->pCertCtx);
+ pNode->pCertCtx = NULL;
+ }
+ RT_ZERO(*pNode);
+ RTMemFree(pNode);
+}
+
+
+static void rtCrX509CertPathsAddIssuer(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pParent,
+ PCRTCRX509CERTIFICATE pCert, PCRTCRCERTCTX pCertCtx, uint8_t uSrc)
+{
+ /*
+ * Check if we've seen this certificate already in the current path or
+ * among the already gathered issuers.
+ */
+ if (pCert)
+ {
+ /* No duplicate certificates in the path. */
+ PRTCRX509CERTPATHNODE pTmpNode = pParent;
+ while (pTmpNode)
+ {
+ Assert(pTmpNode->pCert);
+ if ( pTmpNode->pCert == pCert
+ || RTCrX509Certificate_Compare(pTmpNode->pCert, pCert) == 0)
+ {
+ /* If target and the source it trusted, upgrade the source so we can successfully verify single node 'paths'. */
+ if ( RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(uSrc)
+ && pTmpNode == pParent
+ && pTmpNode->uSrc == RTCRX509CERTPATHNODE_SRC_TARGET)
+ {
+ AssertReturnVoid(!pTmpNode->pParent);
+ pTmpNode->uSrc = uSrc;
+ }
+ return;
+ }
+ pTmpNode = pTmpNode->pParent;
+ }
+
+ /* No duplicate tree branches. */
+ RTListForEach(&pParent->ChildListOrLeafEntry, pTmpNode, RTCRX509CERTPATHNODE, SiblingEntry)
+ {
+ if (RTCrX509Certificate_Compare(pTmpNode->pCert, pCert) == 0)
+ return;
+ }
+ }
+ else
+ Assert(pCertCtx);
+
+ /*
+ * Reference the context core before making the allocation.
+ */
+ if (pCertCtx)
+ AssertReturnVoidStmt(RTCrCertCtxRetain(pCertCtx) != UINT32_MAX,
+ pThis->rc = RTErrInfoSetF(pThis->pErrInfo, VERR_CR_X509_CPB_BAD_CERT_CTX,
+ "Bad pCertCtx=%p", pCertCtx));
+
+ /*
+ * We haven't see it, append it as a child.
+ */
+ PRTCRX509CERTPATHNODE pNew = rtCrX509CertPathsNewNode(pThis);
+ if (pNew)
+ {
+ pNew->pParent = pParent;
+ pNew->pCert = pCert;
+ pNew->pCertCtx = pCertCtx;
+ pNew->uSrc = uSrc;
+ pNew->uDepth = pParent->uDepth + 1;
+ RTListAppend(&pParent->ChildListOrLeafEntry, &pNew->SiblingEntry);
+ Log2Func(("pNew=%p uSrc=%u uDepth=%u\n", pNew, uSrc, pNew->uDepth));
+ }
+ else
+ RTCrCertCtxRelease(pCertCtx);
+}
+
+
+static void rtCrX509CertPathsGetIssuersFromStore(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode,
+ PCRTCRX509NAME pIssuer, RTCRSTORE hStore, uint8_t uSrc)
+{
+ RTCRSTORECERTSEARCH Search;
+ int rc = RTCrStoreCertFindBySubjectOrAltSubjectByRfc5280(hStore, pIssuer, &Search);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTCRCERTCTX pCertCtx;
+ while ((pCertCtx = RTCrStoreCertSearchNext(hStore, &Search)) != NULL)
+ {
+ if ( pCertCtx->pCert
+ || ( RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(uSrc)
+ && pCertCtx->pTaInfo) )
+ rtCrX509CertPathsAddIssuer(pThis, pNode, pCertCtx->pCert, pCertCtx, uSrc);
+ RTCrCertCtxRelease(pCertCtx);
+ }
+ RTCrStoreCertSearchDestroy(hStore, &Search);
+ }
+}
+
+
+static void rtCrX509CertPathsGetIssuers(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode)
+{
+ Assert(RTListIsEmpty(&pNode->ChildListOrLeafEntry));
+ Assert(!pNode->fLeaf);
+ Assert(pNode->pCert);
+
+ /*
+ * Don't recurse infintely.
+ */
+ if (RT_UNLIKELY(pNode->uDepth >= 50))
+ return;
+
+ PCRTCRX509NAME const pIssuer = &pNode->pCert->TbsCertificate.Issuer;
+#if defined(LOG_ENABLED) && defined(IN_RING3)
+ if (LogIs2Enabled())
+ {
+ char szIssuer[128] = {0};
+ RTCrX509Name_FormatAsString(pIssuer, szIssuer, sizeof(szIssuer), NULL);
+ char szSubject[128] = {0};
+ RTCrX509Name_FormatAsString(&pNode->pCert->TbsCertificate.Subject, szSubject, sizeof(szSubject), NULL);
+ Log2Func(("pNode=%p uSrc=%u uDepth=%u Issuer='%s' (Subject='%s')\n", pNode, pNode->uSrc, pNode->uDepth, szIssuer, szSubject));
+ }
+#endif
+
+ /*
+ * Trusted certificate.
+ */
+ if ( pThis->pTrustedCert
+ && RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(pThis->pTrustedCert, pIssuer))
+ rtCrX509CertPathsAddIssuer(pThis, pNode, pThis->pTrustedCert, NULL, RTCRX509CERTPATHNODE_SRC_TRUSTED_CERT);
+
+ /*
+ * Trusted certificate store.
+ */
+ if (pThis->hTrustedStore != NIL_RTCRSTORE)
+ rtCrX509CertPathsGetIssuersFromStore(pThis, pNode, pIssuer, pThis->hTrustedStore,
+ RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE);
+
+ /*
+ * Untrusted store.
+ */
+ if (pThis->hUntrustedStore != NIL_RTCRSTORE)
+ rtCrX509CertPathsGetIssuersFromStore(pThis, pNode, pIssuer, pThis->hTrustedStore,
+ RTCRX509CERTPATHNODE_SRC_UNTRUSTED_STORE);
+
+ /*
+ * Untrusted array.
+ */
+ if (pThis->paUntrustedCerts)
+ for (uint32_t i = 0; i < pThis->cUntrustedCerts; i++)
+ if (RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(&pThis->paUntrustedCerts[i], pIssuer))
+ rtCrX509CertPathsAddIssuer(pThis, pNode, &pThis->paUntrustedCerts[i], NULL,
+ RTCRX509CERTPATHNODE_SRC_UNTRUSTED_ARRAY);
+
+ /** @todo Rainy day: Should abstract the untrusted array and set so we don't get
+ * unnecessary PKCS7/CMS header dependencies. */
+
+ /*
+ * Untrusted set.
+ */
+ if (pThis->pUntrustedCertsSet)
+ {
+ uint32_t const cCerts = pThis->pUntrustedCertsSet->cItems;
+ PRTCRPKCS7CERT const *papCerts = pThis->pUntrustedCertsSet->papItems;
+ for (uint32_t i = 0; i < cCerts; i++)
+ {
+ PCRTCRPKCS7CERT pCert = papCerts[i];
+ if ( pCert->enmChoice == RTCRPKCS7CERTCHOICE_X509
+ && RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(pCert->u.pX509Cert, pIssuer))
+ rtCrX509CertPathsAddIssuer(pThis, pNode, pCert->u.pX509Cert, NULL, RTCRX509CERTPATHNODE_SRC_UNTRUSTED_SET);
+ }
+ }
+}
+
+
+static PRTCRX509CERTPATHNODE rtCrX509CertPathsGetNextRightUp(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode)
+{
+ for (;;)
+ {
+ /* The root node has no siblings. */
+ PRTCRX509CERTPATHNODE pParent = pNode->pParent;
+ if (!pNode->pParent)
+ return NULL;
+
+ /* Try go to the right. */
+ PRTCRX509CERTPATHNODE pNext = RTListGetNext(&pParent->ChildListOrLeafEntry, pNode, RTCRX509CERTPATHNODE, SiblingEntry);
+ if (pNext)
+ return pNext;
+
+ /* Up. */
+ pNode = pParent;
+ }
+
+ RT_NOREF_PV(pThis);
+}
+
+
+static PRTCRX509CERTPATHNODE rtCrX509CertPathsEliminatePath(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode)
+{
+ for (;;)
+ {
+ Assert(RTListIsEmpty(&pNode->ChildListOrLeafEntry));
+
+ /* Don't remove the root node. */
+ PRTCRX509CERTPATHNODE pParent = pNode->pParent;
+ if (!pParent)
+ return NULL;
+
+ /* Before removing and deleting the node check if there is sibling
+ right to it that we should continue processing from. */
+ PRTCRX509CERTPATHNODE pNext = RTListGetNext(&pParent->ChildListOrLeafEntry, pNode, RTCRX509CERTPATHNODE, SiblingEntry);
+ RTListNodeRemove(&pNode->SiblingEntry);
+ rtCrX509CertPathsDestroyNode(pNode);
+
+ if (pNext)
+ return pNext;
+
+ /* If the parent node cannot be removed, do a normal get-next-rigth-up
+ to find the continuation point for the tree loop. */
+ if (!RTListIsEmpty(&pParent->ChildListOrLeafEntry))
+ return rtCrX509CertPathsGetNextRightUp(pThis, pParent);
+
+ pNode = pParent;
+ }
+}
+
+
+/**
+ * Destroys the whole path tree.
+ *
+ * @param pThis The path builder and verifier instance.
+ */
+static void rtCrX509CertPathsDestroyTree(PRTCRX509CERTPATHSINT pThis)
+{
+ PRTCRX509CERTPATHNODE pNode, pNextLeaf;
+ RTListForEachSafe(&pThis->LeafList, pNode, pNextLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry)
+ {
+ RTListNodeRemove(&pNode->ChildListOrLeafEntry);
+ RTListInit(&pNode->ChildListOrLeafEntry);
+
+ for (;;)
+ {
+ PRTCRX509CERTPATHNODE pParent = pNode->pParent;
+
+ RTListNodeRemove(&pNode->SiblingEntry);
+ rtCrX509CertPathsDestroyNode(pNode);
+
+ if (!pParent)
+ {
+ pThis->pRoot = NULL;
+ break;
+ }
+
+ if (!RTListIsEmpty(&pParent->ChildListOrLeafEntry))
+ break;
+
+ pNode = pParent;
+ }
+ }
+ Assert(!pThis->pRoot);
+}
+
+
+/**
+ * Adds a leaf node.
+ *
+ * This should normally be a trusted certificate, but the caller can also
+ * request the incomplete paths, in which case this will be an untrusted
+ * certificate.
+ *
+ * @returns Pointer to the next node in the tree to process.
+ * @param pThis The path builder instance.
+ * @param pNode The leaf node.
+ */
+static PRTCRX509CERTPATHNODE rtCrX509CertPathsAddLeaf(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode)
+{
+ pNode->fLeaf = true;
+
+ /*
+ * Priority insert by source and depth.
+ */
+ PRTCRX509CERTPATHNODE pCurLeaf;
+ RTListForEach(&pThis->LeafList, pCurLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry)
+ {
+ if ( pNode->uSrc > pCurLeaf->uSrc
+ || ( pNode->uSrc == pCurLeaf->uSrc
+ && pNode->uDepth < pCurLeaf->uDepth) )
+ {
+ RTListNodeInsertBefore(&pCurLeaf->ChildListOrLeafEntry, &pNode->ChildListOrLeafEntry);
+ pThis->cPaths++;
+ return rtCrX509CertPathsGetNextRightUp(pThis, pNode);
+ }
+ }
+
+ RTListAppend(&pThis->LeafList, &pNode->ChildListOrLeafEntry);
+ pThis->cPaths++;
+ return rtCrX509CertPathsGetNextRightUp(pThis, pNode);
+}
+
+
+
+RTDECL(int) RTCrX509CertPathsBuild(RTCRX509CERTPATHS hCertPaths, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(pThis->fFlags & ~RTCRX509CERTPATHSINT_F_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertReturn( (pThis->paUntrustedCerts == NULL && pThis->cUntrustedCerts == 0)
+ || (pThis->paUntrustedCerts != NULL && pThis->cUntrustedCerts > 0),
+ VERR_INVALID_PARAMETER);
+ AssertReturn(RTListIsEmpty(&pThis->LeafList), VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->pRoot == NULL, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->rc == VINF_SUCCESS, pThis->rc);
+ AssertPtrReturn(pThis->pTarget, VERR_INVALID_PARAMETER);
+ Assert(RT_SUCCESS(RTCrX509Certificate_CheckSanity(pThis->pTarget, 0, NULL, NULL)));
+
+ /*
+ * Set up the target.
+ */
+ PRTCRX509CERTPATHNODE pCur;
+ pThis->pRoot = pCur = rtCrX509CertPathsNewNode(pThis);
+ if (pThis->pRoot)
+ {
+ pCur->pCert = pThis->pTarget;
+ pCur->uDepth = 0;
+ pCur->uSrc = RTCRX509CERTPATHNODE_SRC_TARGET;
+
+ /* Check if the target is trusted and do the upgrade (this is outside the RFC,
+ but this simplifies the path validator usage a lot (less work for the caller)). */
+ if ( pThis->pTrustedCert
+ && RTCrX509Certificate_Compare(pThis->pTrustedCert, pCur->pCert) == 0)
+ pCur->uSrc = RTCRX509CERTPATHNODE_SRC_TRUSTED_CERT;
+ else if ( pThis->hTrustedStore != NIL_RTCRSTORE
+ && rtCrX509CertPathsIsCertInStore(pCur, pThis->hTrustedStore))
+ pCur->uSrc = RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE;
+
+ pThis->pErrInfo = pErrInfo;
+
+ /*
+ * The tree construction loop.
+ * Walks down, up, and right as the tree is constructed.
+ */
+ do
+ {
+ /*
+ * Check for the two leaf cases first.
+ */
+ if (RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pCur->uSrc))
+ pCur = rtCrX509CertPathsAddLeaf(pThis, pCur);
+#if 0 /* This isn't right.*/
+ else if (rtCrX509CertPathsIsSelfIssued(pCur))
+ {
+ if (pThis->fFlags & RTCRX509CERTPATHSINT_F_ELIMINATE_UNTRUSTED_PATHS)
+ pCur = rtCrX509CertPathsEliminatePath(pThis, pCur);
+ else
+ pCur = rtCrX509CertPathsAddLeaf(pThis, pCur);
+ }
+#endif
+ /*
+ * Not a leaf, find all potential issuers and decend into these.
+ */
+ else
+ {
+ rtCrX509CertPathsGetIssuers(pThis, pCur);
+ if (RT_FAILURE(pThis->rc))
+ break;
+
+ if (!RTListIsEmpty(&pCur->ChildListOrLeafEntry))
+ pCur = RTListGetFirst(&pCur->ChildListOrLeafEntry, RTCRX509CERTPATHNODE, SiblingEntry);
+ else if (pThis->fFlags & RTCRX509CERTPATHSINT_F_ELIMINATE_UNTRUSTED_PATHS)
+ pCur = rtCrX509CertPathsEliminatePath(pThis, pCur);
+ else
+ pCur = rtCrX509CertPathsAddLeaf(pThis, pCur);
+ }
+ if (pCur)
+ Log2(("RTCrX509CertPathsBuild: pCur=%p fLeaf=%d pParent=%p pNext=%p pPrev=%p\n",
+ pCur, pCur->fLeaf, pCur->pParent,
+ pCur->pParent ? RTListGetNext(&pCur->pParent->ChildListOrLeafEntry, pCur, RTCRX509CERTPATHNODE, SiblingEntry) : NULL,
+ pCur->pParent ? RTListGetPrev(&pCur->pParent->ChildListOrLeafEntry, pCur, RTCRX509CERTPATHNODE, SiblingEntry) : NULL));
+ } while (pCur);
+
+ pThis->pErrInfo = NULL;
+ if (RT_SUCCESS(pThis->rc))
+ return VINF_SUCCESS;
+ }
+ else
+ Assert(RT_FAILURE_NP(pThis->rc));
+ return pThis->rc;
+}
+
+
+/**
+ * Looks up path by leaf/path index.
+ *
+ * @returns Pointer to the leaf node of the path.
+ * @param pThis The path builder & validator instance.
+ * @param iPath The oridnal of the path to get.
+ */
+static PRTCRX509CERTPATHNODE rtCrX509CertPathsGetLeafByIndex(PRTCRX509CERTPATHSINT pThis, uint32_t iPath)
+{
+ Assert(iPath < pThis->cPaths);
+
+ uint32_t iCurPath = 0;
+ PRTCRX509CERTPATHNODE pCurLeaf;
+ RTListForEach(&pThis->LeafList, pCurLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry)
+ {
+ if (iCurPath == iPath)
+ return pCurLeaf;
+ iCurPath++;
+ }
+
+ AssertFailedReturn(NULL);
+}
+
+
+static void rtDumpPrintf(PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ pfnPrintfV(pvUser, pszFormat, va);
+ va_end(va);
+}
+
+
+static void rtDumpIndent(PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser, uint32_t cchSpaces, const char *pszFormat, ...)
+{
+ static const char s_szSpaces[] = " ";
+ while (cchSpaces > 0)
+ {
+ uint32_t cchBurst = RT_MIN(sizeof(s_szSpaces) - 1, cchSpaces);
+ rtDumpPrintf(pfnPrintfV, pvUser, &s_szSpaces[sizeof(s_szSpaces) - cchBurst - 1]);
+ cchSpaces -= cchBurst;
+ }
+
+ va_list va;
+ va_start(va, pszFormat);
+ pfnPrintfV(pvUser, pszFormat, va);
+ va_end(va);
+}
+
+/** @name X.500 attribute types
+ * See RFC-4519 among others.
+ * @{ */
+#define RTCRX500_ID_AT_OBJECT_CLASS_OID "2.5.4.0"
+#define RTCRX500_ID_AT_ALIASED_ENTRY_NAME_OID "2.5.4.1"
+#define RTCRX500_ID_AT_KNOWLDGEINFORMATION_OID "2.5.4.2"
+#define RTCRX500_ID_AT_COMMON_NAME_OID "2.5.4.3"
+#define RTCRX500_ID_AT_SURNAME_OID "2.5.4.4"
+#define RTCRX500_ID_AT_SERIAL_NUMBER_OID "2.5.4.5"
+#define RTCRX500_ID_AT_COUNTRY_NAME_OID "2.5.4.6"
+#define RTCRX500_ID_AT_LOCALITY_NAME_OID "2.5.4.7"
+#define RTCRX500_ID_AT_STATE_OR_PROVINCE_NAME_OID "2.5.4.8"
+#define RTCRX500_ID_AT_STREET_ADDRESS_OID "2.5.4.9"
+#define RTCRX500_ID_AT_ORGANIZATION_NAME_OID "2.5.4.10"
+#define RTCRX500_ID_AT_ORGANIZATION_UNIT_NAME_OID "2.5.4.11"
+#define RTCRX500_ID_AT_TITLE_OID "2.5.4.12"
+#define RTCRX500_ID_AT_DESCRIPTION_OID "2.5.4.13"
+#define RTCRX500_ID_AT_SEARCH_GUIDE_OID "2.5.4.14"
+#define RTCRX500_ID_AT_BUSINESS_CATEGORY_OID "2.5.4.15"
+#define RTCRX500_ID_AT_POSTAL_ADDRESS_OID "2.5.4.16"
+#define RTCRX500_ID_AT_POSTAL_CODE_OID "2.5.4.17"
+#define RTCRX500_ID_AT_POST_OFFICE_BOX_OID "2.5.4.18"
+#define RTCRX500_ID_AT_PHYSICAL_DELIVERY_OFFICE_NAME_OID "2.5.4.19"
+#define RTCRX500_ID_AT_TELEPHONE_NUMBER_OID "2.5.4.20"
+#define RTCRX500_ID_AT_TELEX_NUMBER_OID "2.5.4.21"
+#define RTCRX500_ID_AT_TELETEX_TERMINAL_IDENTIFIER_OID "2.5.4.22"
+#define RTCRX500_ID_AT_FACIMILE_TELEPHONE_NUMBER_OID "2.5.4.23"
+#define RTCRX500_ID_AT_X121_ADDRESS_OID "2.5.4.24"
+#define RTCRX500_ID_AT_INTERNATIONAL_ISDN_NUMBER_OID "2.5.4.25"
+#define RTCRX500_ID_AT_REGISTERED_ADDRESS_OID "2.5.4.26"
+#define RTCRX500_ID_AT_DESTINATION_INDICATOR_OID "2.5.4.27"
+#define RTCRX500_ID_AT_PREFERRED_DELIVERY_METHOD_OID "2.5.4.28"
+#define RTCRX500_ID_AT_PRESENTATION_ADDRESS_OID "2.5.4.29"
+#define RTCRX500_ID_AT_SUPPORTED_APPLICATION_CONTEXT_OID "2.5.4.30"
+#define RTCRX500_ID_AT_MEMBER_OID "2.5.4.31"
+#define RTCRX500_ID_AT_OWNER_OID "2.5.4.32"
+#define RTCRX500_ID_AT_ROLE_OCCUPANT_OID "2.5.4.33"
+#define RTCRX500_ID_AT_SEE_ALSO_OID "2.5.4.34"
+#define RTCRX500_ID_AT_USER_PASSWORD_OID "2.5.4.35"
+#define RTCRX500_ID_AT_USER_CERTIFICATE_OID "2.5.4.36"
+#define RTCRX500_ID_AT_CA_CERTIFICATE_OID "2.5.4.37"
+#define RTCRX500_ID_AT_AUTHORITY_REVOCATION_LIST_OID "2.5.4.38"
+#define RTCRX500_ID_AT_CERTIFICATE_REVOCATION_LIST_OID "2.5.4.39"
+#define RTCRX500_ID_AT_CROSS_CERTIFICATE_PAIR_OID "2.5.4.40"
+#define RTCRX500_ID_AT_NAME_OID "2.5.4.41"
+#define RTCRX500_ID_AT_GIVEN_NAME_OID "2.5.4.42"
+#define RTCRX500_ID_AT_INITIALS_OID "2.5.4.43"
+#define RTCRX500_ID_AT_GENERATION_QUALIFIER_OID "2.5.4.44"
+#define RTCRX500_ID_AT_UNIQUE_IDENTIFIER_OID "2.5.4.45"
+#define RTCRX500_ID_AT_DN_QUALIFIER_OID "2.5.4.46"
+#define RTCRX500_ID_AT_ENHANCHED_SEARCH_GUIDE_OID "2.5.4.47"
+#define RTCRX500_ID_AT_PROTOCOL_INFORMATION_OID "2.5.4.48"
+#define RTCRX500_ID_AT_DISTINGUISHED_NAME_OID "2.5.4.49"
+#define RTCRX500_ID_AT_UNIQUE_MEMBER_OID "2.5.4.50"
+#define RTCRX500_ID_AT_HOUSE_IDENTIFIER_OID "2.5.4.51"
+#define RTCRX500_ID_AT_SUPPORTED_ALGORITHMS_OID "2.5.4.52"
+#define RTCRX500_ID_AT_DELTA_REVOCATION_LIST_OID "2.5.4.53"
+#define RTCRX500_ID_AT_ATTRIBUTE_CERTIFICATE_OID "2.5.4.58"
+#define RTCRX500_ID_AT_PSEUDONYM_OID "2.5.4.65"
+/** @} */
+
+
+static void rtCrX509NameDump(PCRTCRX509NAME pName, PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser)
+{
+ for (uint32_t i = 0; i < pName->cItems; i++)
+ {
+ PCRTCRX509RELATIVEDISTINGUISHEDNAME const pRdn = pName->papItems[i];
+ for (uint32_t j = 0; j < pRdn->cItems; j++)
+ {
+ PRTCRX509ATTRIBUTETYPEANDVALUE pAttrib = pRdn->papItems[j];
+
+ const char *pszType = RTCrX509Name_GetShortRdn(&pAttrib->Type);
+ if (!pszType)
+ pszType = pAttrib->Type.szObjId;
+ rtDumpPrintf(pfnPrintfV, pvUser, "/%s=", pszType);
+ if (pAttrib->Value.enmType == RTASN1TYPE_STRING)
+ {
+ if (pAttrib->Value.u.String.pszUtf8)
+ rtDumpPrintf(pfnPrintfV, pvUser, "%s", pAttrib->Value.u.String.pszUtf8);
+ else
+ {
+ const char *pch = pAttrib->Value.u.String.Asn1Core.uData.pch;
+ uint32_t cch = pAttrib->Value.u.String.Asn1Core.cb;
+ int rc = RTStrValidateEncodingEx(pch, cch, 0);
+ if (RT_SUCCESS(rc) && cch)
+ rtDumpPrintf(pfnPrintfV, pvUser, "%.*s", (size_t)cch, pch);
+ else
+ while (cch > 0)
+ {
+ if (RT_C_IS_PRINT(*pch))
+ rtDumpPrintf(pfnPrintfV, pvUser, "%c", *pch);
+ else
+ rtDumpPrintf(pfnPrintfV, pvUser, "\\x%02x", *pch);
+ cch--;
+ pch++;
+ }
+ }
+ }
+ else
+ rtDumpPrintf(pfnPrintfV, pvUser, "<not-string: uTag=%#x>", pAttrib->Value.u.Core.uTag);
+ }
+ }
+}
+
+
+static const char *rtCrX509CertPathsNodeGetSourceName(PRTCRX509CERTPATHNODE pNode)
+{
+ switch (pNode->uSrc)
+ {
+ case RTCRX509CERTPATHNODE_SRC_TARGET: return "target";
+ case RTCRX509CERTPATHNODE_SRC_UNTRUSTED_SET: return "untrusted_set";
+ case RTCRX509CERTPATHNODE_SRC_UNTRUSTED_ARRAY: return "untrusted_array";
+ case RTCRX509CERTPATHNODE_SRC_UNTRUSTED_STORE: return "untrusted_store";
+ case RTCRX509CERTPATHNODE_SRC_TRUSTED_STORE: return "trusted_store";
+ case RTCRX509CERTPATHNODE_SRC_TRUSTED_CERT: return "trusted_cert";
+ default: return "invalid";
+ }
+}
+
+
+static void rtCrX509CertPathsDumpOneWorker(PRTCRX509CERTPATHSINT pThis, uint32_t iPath, PRTCRX509CERTPATHNODE pCurLeaf,
+ uint32_t uVerbosity, PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser)
+{
+ RT_NOREF_PV(pThis);
+ rtDumpPrintf(pfnPrintfV, pvUser, "Path #%u: %s, %u deep, rcVerify=%Rrc\n",
+ iPath, RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pCurLeaf->uSrc) ? "trusted" : "untrusted", pCurLeaf->uDepth,
+ pCurLeaf->rcVerify);
+
+ for (uint32_t iIndent = 2; pCurLeaf; iIndent += 2, pCurLeaf = pCurLeaf->pParent)
+ {
+ if (pCurLeaf->pCert)
+ {
+ rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Issuer : ");
+ rtCrX509NameDump(&pCurLeaf->pCert->TbsCertificate.Issuer, pfnPrintfV, pvUser);
+ rtDumpPrintf(pfnPrintfV, pvUser, "\n");
+
+ rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Subject: ");
+ rtCrX509NameDump(&pCurLeaf->pCert->TbsCertificate.Subject, pfnPrintfV, pvUser);
+ rtDumpPrintf(pfnPrintfV, pvUser, "\n");
+
+ if (uVerbosity >= 4)
+ RTAsn1Dump(&pCurLeaf->pCert->SeqCore.Asn1Core, 0, iIndent, pfnPrintfV, pvUser);
+ else if (uVerbosity >= 3)
+ RTAsn1Dump(&pCurLeaf->pCert->TbsCertificate.T3.Extensions.SeqCore.Asn1Core, 0, iIndent, pfnPrintfV, pvUser);
+
+ rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Valid : %s thru %s\n",
+ RTTimeToString(&pCurLeaf->pCert->TbsCertificate.Validity.NotBefore.Time,
+ pThis->szTmp, sizeof(pThis->szTmp) / 2),
+ RTTimeToString(&pCurLeaf->pCert->TbsCertificate.Validity.NotAfter.Time,
+ &pThis->szTmp[sizeof(pThis->szTmp) / 2], sizeof(pThis->szTmp) / 2) );
+ }
+ else
+ {
+ Assert(pCurLeaf->pCertCtx); Assert(pCurLeaf->pCertCtx->pTaInfo);
+ rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Subject: ");
+ rtCrX509NameDump(&pCurLeaf->pCertCtx->pTaInfo->CertPath.TaName, pfnPrintfV, pvUser);
+
+ if (uVerbosity >= 4)
+ RTAsn1Dump(&pCurLeaf->pCertCtx->pTaInfo->SeqCore.Asn1Core, 0, iIndent, pfnPrintfV, pvUser);
+ }
+
+ const char *pszSrc = rtCrX509CertPathsNodeGetSourceName(pCurLeaf);
+ rtDumpIndent(pfnPrintfV, pvUser, iIndent, "Source : %s\n", pszSrc);
+ }
+}
+
+
+RTDECL(int) RTCrX509CertPathsDumpOne(RTCRX509CERTPATHS hCertPaths, uint32_t iPath, uint32_t uVerbosity,
+ PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pfnPrintfV, VERR_INVALID_POINTER);
+ int rc;
+ if (iPath < pThis->cPaths)
+ {
+ PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath);
+ if (pLeaf)
+ {
+ rtCrX509CertPathsDumpOneWorker(pThis, iPath, pLeaf, uVerbosity, pfnPrintfV, pvUser);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_CR_X509_CERTPATHS_INTERNAL_ERROR;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ return rc;
+}
+
+
+RTDECL(int) RTCrX509CertPathsDumpAll(RTCRX509CERTPATHS hCertPaths, uint32_t uVerbosity, PFNRTDUMPPRINTFV pfnPrintfV, void *pvUser)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pfnPrintfV, VERR_INVALID_POINTER);
+
+ /*
+ * Dump all the paths.
+ */
+ rtDumpPrintf(pfnPrintfV, pvUser, "%u paths, rc=%Rrc\n", pThis->cPaths, pThis->rc);
+ uint32_t iPath = 0;
+ PRTCRX509CERTPATHNODE pCurLeaf, pNextLeaf;
+ RTListForEachSafe(&pThis->LeafList, pCurLeaf, pNextLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry)
+ {
+ rtCrX509CertPathsDumpOneWorker(pThis, iPath, pCurLeaf, uVerbosity, pfnPrintfV, pvUser);
+ iPath++;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @} */
+
+
+/** @name Path Validator Functions.
+ * @{
+ */
+
+
+static void *rtCrX509CpvAllocZ(PRTCRX509CERTPATHSINT pThis, size_t cb, const char *pszWhat)
+{
+ void *pv = RTMemAllocZ(cb);
+ if (!pv)
+ pThis->rc = RTErrInfoSetF(pThis->pErrInfo, VERR_NO_MEMORY, "Failed to allocate %zu bytes for %s", cb, pszWhat);
+ return pv;
+}
+
+
+DECL_NO_INLINE(static, bool) rtCrX509CpvFailed(PRTCRX509CERTPATHSINT pThis, int rc, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ pThis->rc = RTErrInfoSetV(pThis->pErrInfo, rc, pszFormat, va);
+ va_end(va);
+ return false;
+}
+
+
+/**
+ * Adds a sequence of excluded sub-trees.
+ *
+ * Don't waste time optimizing the output if this is supposed to be a union.
+ * Unless the path is very long, it's a lot more work to optimize and the result
+ * will be the same anyway.
+ *
+ * @returns success indicator.
+ * @param pThis The validator instance.
+ * @param pSubtrees The sequence of sub-trees to add.
+ */
+static bool rtCrX509CpvAddExcludedSubtrees(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALSUBTREES pSubtrees)
+{
+ if (((pThis->v.cExcludedSubtrees + 1) & 0xf) == 0)
+ {
+ void *pvNew = RTMemRealloc(pThis->v.papExcludedSubtrees,
+ (pThis->v.cExcludedSubtrees + 16) * sizeof(pThis->v.papExcludedSubtrees[0]));
+ if (RT_UNLIKELY(!pvNew))
+ return rtCrX509CpvFailed(pThis, VERR_NO_MEMORY, "Error growing subtrees pointer array to %u elements",
+ pThis->v.cExcludedSubtrees + 16);
+ pThis->v.papExcludedSubtrees = (PCRTCRX509GENERALSUBTREES *)pvNew;
+ }
+ pThis->v.papExcludedSubtrees[pThis->v.cExcludedSubtrees] = pSubtrees;
+ pThis->v.cExcludedSubtrees++;
+ return true;
+}
+
+
+/**
+ * Checks if a sub-tree is according to RFC-5280.
+ *
+ * @returns Success indiciator.
+ * @param pThis The validator instance.
+ * @param pSubtree The subtree to check.
+ */
+static bool rtCrX509CpvCheckSubtreeValidity(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALSUBTREE pSubtree)
+{
+ if ( pSubtree->Base.enmChoice <= RTCRX509GENERALNAMECHOICE_INVALID
+ || pSubtree->Base.enmChoice >= RTCRX509GENERALNAMECHOICE_END)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_UNEXP_GENERAL_SUBTREE_CHOICE,
+ "Unexpected GeneralSubtree choice %#x", pSubtree->Base.enmChoice);
+
+ if (RTAsn1Integer_UnsignedCompareWithU32(&pSubtree->Minimum, 0) != 0)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_UNEXP_GENERAL_SUBTREE_MIN,
+ "Unexpected GeneralSubtree Minimum value: %#llx",
+ pSubtree->Minimum.uValue);
+
+ if (RTAsn1Integer_IsPresent(&pSubtree->Maximum))
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_UNEXP_GENERAL_SUBTREE_MAX,
+ "Unexpected GeneralSubtree Maximum value: %#llx",
+ pSubtree->Maximum.uValue);
+
+ return true;
+}
+
+
+/**
+ * Grows the array of permitted sub-trees.
+ *
+ * @returns success indiciator.
+ * @param pThis The validator instance.
+ * @param cAdding The number of subtrees we should grow by
+ * (relative to the current number of valid
+ * entries).
+ */
+static bool rtCrX509CpvGrowPermittedSubtrees(PRTCRX509CERTPATHSINT pThis, uint32_t cAdding)
+{
+ uint32_t cNew = RT_ALIGN_32(pThis->v.cPermittedSubtrees + cAdding, 16);
+ if (cNew > pThis->v.cPermittedSubtreesAlloc)
+ {
+ if (cNew >= _4K)
+ return rtCrX509CpvFailed(pThis, VERR_NO_MEMORY, "Too many permitted subtrees: %u (cur %u)",
+ cNew, pThis->v.cPermittedSubtrees);
+ void *pvNew = RTMemRealloc(pThis->v.papPermittedSubtrees, cNew * sizeof(pThis->v.papPermittedSubtrees[0]));
+ if (RT_UNLIKELY(!pvNew))
+ return rtCrX509CpvFailed(pThis, VERR_NO_MEMORY, "Error growing subtrees pointer array from %u to %u elements",
+ pThis->v.cPermittedSubtreesAlloc, cNew);
+ pThis->v.papPermittedSubtrees = (PCRTCRX509GENERALSUBTREE *)pvNew;
+ }
+ return true;
+}
+
+
+/**
+ * Adds a sequence of permitted sub-trees.
+ *
+ * We store reference to each individual sub-tree because we must support
+ * intersection calculation.
+ *
+ * @returns success indiciator.
+ * @param pThis The validator instance.
+ * @param cSubtrees The number of sub-trees to add.
+ * @param papSubtrees Array of sub-trees to add.
+ */
+static bool rtCrX509CpvAddPermittedSubtrees(PRTCRX509CERTPATHSINT pThis, uint32_t cSubtrees,
+ PRTCRX509GENERALSUBTREE const *papSubtrees)
+{
+ /*
+ * If the array is empty, assume no permitted names.
+ */
+ if (!cSubtrees)
+ {
+ pThis->v.fNoPermittedSubtrees = true;
+ return true;
+ }
+
+ /*
+ * Grow the array if necessary.
+ */
+ if (!rtCrX509CpvGrowPermittedSubtrees(pThis, cSubtrees))
+ return false;
+
+ /*
+ * Append each subtree to the array.
+ */
+ uint32_t iDst = pThis->v.cPermittedSubtrees;
+ for (uint32_t iSrc = 0; iSrc < cSubtrees; iSrc++)
+ {
+ if (!rtCrX509CpvCheckSubtreeValidity(pThis, papSubtrees[iSrc]))
+ return false;
+ pThis->v.papPermittedSubtrees[iDst] = papSubtrees[iSrc];
+ iDst++;
+ }
+ pThis->v.cPermittedSubtrees = iDst;
+
+ return true;
+}
+
+
+/**
+ * Adds a one permitted sub-tree.
+ *
+ * We store reference to each individual sub-tree because we must support
+ * intersection calculation.
+ *
+ * @returns success indiciator.
+ * @param pThis The validator instance.
+ * @param pSubtree Array of sub-trees to add.
+ */
+static bool rtCrX509CpvAddPermittedSubtree(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALSUBTREE pSubtree)
+{
+ return rtCrX509CpvAddPermittedSubtrees(pThis, 1, (PRTCRX509GENERALSUBTREE const *)&pSubtree);
+}
+
+
+/**
+ * Calculates the intersection between @a pSubtrees and the current permitted
+ * sub-trees.
+ *
+ * @returns Success indicator.
+ * @param pThis The validator instance.
+ * @param pSubtrees The sub-tree sequence to intersect with.
+ */
+static bool rtCrX509CpvIntersectionPermittedSubtrees(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALSUBTREES pSubtrees)
+{
+ /*
+ * Deal with special cases first.
+ */
+ if (pThis->v.fNoPermittedSubtrees)
+ {
+ Assert(pThis->v.cPermittedSubtrees == 0);
+ return true;
+ }
+
+ uint32_t cRight = pSubtrees->cItems;
+ PRTCRX509GENERALSUBTREE const *papRight = pSubtrees->papItems;
+ if (cRight == 0)
+ {
+ pThis->v.cPermittedSubtrees = 0;
+ pThis->v.fNoPermittedSubtrees = true;
+ return true;
+ }
+
+ uint32_t cLeft = pThis->v.cPermittedSubtrees;
+ PCRTCRX509GENERALSUBTREE *papLeft = pThis->v.papPermittedSubtrees;
+ if (!cLeft) /* first name constraint, no initial constraint */
+ return rtCrX509CpvAddPermittedSubtrees(pThis, cRight, papRight);
+
+ /*
+ * Create a new array with the intersection, freeing the old (left) array
+ * once we're done.
+ */
+ bool afRightTags[RTCRX509GENERALNAMECHOICE_END] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ pThis->v.cPermittedSubtrees = 0;
+ pThis->v.cPermittedSubtreesAlloc = 0;
+ pThis->v.papPermittedSubtrees = NULL;
+
+ for (uint32_t iRight = 0; iRight < cRight; iRight++)
+ {
+ if (!rtCrX509CpvCheckSubtreeValidity(pThis, papRight[iRight]))
+ return false;
+
+ RTCRX509GENERALNAMECHOICE const enmRightChoice = papRight[iRight]->Base.enmChoice;
+ afRightTags[enmRightChoice] = true;
+
+ bool fHaveRight = false;
+ for (uint32_t iLeft = 0; iLeft < cLeft; iLeft++)
+ if (papLeft[iLeft]->Base.enmChoice == enmRightChoice)
+ {
+ if (RTCrX509GeneralSubtree_Compare(papLeft[iLeft], papRight[iRight]) == 0)
+ {
+ if (!fHaveRight)
+ {
+ fHaveRight = true;
+ rtCrX509CpvAddPermittedSubtree(pThis, papLeft[iLeft]);
+ }
+ }
+ else if (RTCrX509GeneralSubtree_ConstraintMatch(papLeft[iLeft], papRight[iRight]))
+ {
+ if (!fHaveRight)
+ {
+ fHaveRight = true;
+ rtCrX509CpvAddPermittedSubtree(pThis, papRight[iRight]);
+ }
+ }
+ else if (RTCrX509GeneralSubtree_ConstraintMatch(papRight[iRight], papLeft[iLeft]))
+ rtCrX509CpvAddPermittedSubtree(pThis, papLeft[iLeft]);
+ }
+ }
+
+ /*
+ * Add missing types not specified in the right set.
+ */
+ for (uint32_t iLeft = 0; iLeft < cLeft; iLeft++)
+ if (!afRightTags[papLeft[iLeft]->Base.enmChoice])
+ rtCrX509CpvAddPermittedSubtree(pThis, papLeft[iLeft]);
+
+ /*
+ * If we ended up with an empty set, no names are permitted any more.
+ */
+ if (pThis->v.cPermittedSubtrees == 0)
+ pThis->v.fNoPermittedSubtrees = true;
+
+ RTMemFree(papLeft);
+ return RT_SUCCESS(pThis->rc);
+}
+
+
+/**
+ * Check if the given X.509 name is permitted by current name constraints.
+ *
+ * @returns true is permitteded, false if not (caller set error info).
+ * @param pThis The validator instance.
+ * @param pName The name to match.
+ */
+static bool rtCrX509CpvIsNamePermitted(PRTCRX509CERTPATHSINT pThis, PCRTCRX509NAME pName)
+{
+ uint32_t i = pThis->v.cPermittedSubtrees;
+ if (i == 0)
+ return !pThis->v.fNoPermittedSubtrees;
+
+ while (i-- > 0)
+ {
+ PCRTCRX509GENERALSUBTREE pConstraint = pThis->v.papPermittedSubtrees[i];
+ if ( RTCRX509GENERALNAME_IS_DIRECTORY_NAME(&pConstraint->Base)
+ && RTCrX509Name_ConstraintMatch(&pConstraint->Base.u.pT4->DirectoryName, pName))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Check if the given X.509 general name is permitted by current name
+ * constraints.
+ *
+ * @returns true is permitteded, false if not (caller sets error info).
+ * @param pThis The validator instance.
+ * @param pGeneralName The name to match.
+ */
+static bool rtCrX509CpvIsGeneralNamePermitted(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALNAME pGeneralName)
+{
+ uint32_t i = pThis->v.cPermittedSubtrees;
+ if (i == 0)
+ return !pThis->v.fNoPermittedSubtrees;
+
+ while (i-- > 0)
+ if (RTCrX509GeneralName_ConstraintMatch(&pThis->v.papPermittedSubtrees[i]->Base, pGeneralName))
+ return true;
+ return false;
+}
+
+
+/**
+ * Check if the given X.509 name is excluded by current name constraints.
+ *
+ * @returns true if excluded (caller sets error info), false if not explicitly
+ * excluded.
+ * @param pThis The validator instance.
+ * @param pName The name to match.
+ */
+static bool rtCrX509CpvIsNameExcluded(PRTCRX509CERTPATHSINT pThis, PCRTCRX509NAME pName)
+{
+ uint32_t i = pThis->v.cExcludedSubtrees;
+ while (i-- > 0)
+ {
+ PCRTCRX509GENERALSUBTREES pSubTrees = pThis->v.papExcludedSubtrees[i];
+ uint32_t j = pSubTrees->cItems;
+ while (j-- > 0)
+ {
+ PCRTCRX509GENERALSUBTREE const pSubTree = pSubTrees->papItems[j];
+ if ( RTCRX509GENERALNAME_IS_DIRECTORY_NAME(&pSubTree->Base)
+ && RTCrX509Name_ConstraintMatch(&pSubTree->Base.u.pT4->DirectoryName, pName))
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Check if the given X.509 general name is excluded by current name
+ * constraints.
+ *
+ * @returns true if excluded (caller sets error info), false if not explicitly
+ * excluded.
+ * @param pThis The validator instance.
+ * @param pGeneralName The name to match.
+ */
+static bool rtCrX509CpvIsGeneralNameExcluded(PRTCRX509CERTPATHSINT pThis, PCRTCRX509GENERALNAME pGeneralName)
+{
+ uint32_t i = pThis->v.cExcludedSubtrees;
+ while (i-- > 0)
+ {
+ PCRTCRX509GENERALSUBTREES pSubTrees = pThis->v.papExcludedSubtrees[i];
+ uint32_t j = pSubTrees->cItems;
+ while (j-- > 0)
+ if (RTCrX509GeneralName_ConstraintMatch(&pSubTrees->papItems[j]->Base, pGeneralName))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Creates a new node and inserts it.
+ *
+ * @param pThis The path builder & validator instance.
+ * @param pParent The parent node. NULL for the root node.
+ * @param iDepth The tree depth to insert at.
+ * @param pValidPolicy The valid policy of the new node.
+ * @param pQualifiers The qualifiers of the new node.
+ * @param pExpectedPolicy The (first) expected polcy of the new node.
+ */
+static bool rtCrX509CpvPolicyTreeInsertNew(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHSPOLICYNODE pParent, uint32_t iDepth,
+ PCRTASN1OBJID pValidPolicy, PCRTCRX509POLICYQUALIFIERINFOS pQualifiers,
+ PCRTASN1OBJID pExpectedPolicy)
+{
+ Assert(iDepth <= pThis->v.cNodes);
+
+ PRTCRX509CERTPATHSPOLICYNODE pNode;
+ pNode = (PRTCRX509CERTPATHSPOLICYNODE)rtCrX509CpvAllocZ(pThis, sizeof(*pNode), "policy tree node");
+ if (pNode)
+ {
+ pNode->pParent = pParent;
+ if (pParent)
+ RTListAppend(&pParent->ChildList, &pNode->SiblingEntry);
+ else
+ {
+ Assert(pThis->v.pValidPolicyTree == NULL);
+ pThis->v.pValidPolicyTree = pNode;
+ RTListInit(&pNode->SiblingEntry);
+ }
+ RTListInit(&pNode->ChildList);
+ RTListAppend(&pThis->v.paValidPolicyDepthLists[iDepth], &pNode->DepthEntry);
+
+ pNode->pValidPolicy = pValidPolicy;
+ pNode->pPolicyQualifiers = pQualifiers;
+ pNode->pExpectedPolicyFirst = pExpectedPolicy;
+ pNode->cMoreExpectedPolicySet = 0;
+ pNode->papMoreExpectedPolicySet = NULL;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Unlinks and frees a node in the valid policy tree.
+ *
+ * @param pThis The path builder & validator instance.
+ * @param pNode The node to destroy.
+ */
+static void rtCrX509CpvPolicyTreeDestroyNode(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHSPOLICYNODE pNode)
+{
+ Assert(RTListIsEmpty(&pNode->ChildList));
+ if (pNode->pParent)
+ RTListNodeRemove(&pNode->SiblingEntry);
+ else
+ pThis->v.pValidPolicyTree = NULL;
+ RTListNodeRemove(&pNode->DepthEntry);
+ pNode->pParent = NULL;
+
+ if (pNode->papMoreExpectedPolicySet)
+ {
+ RTMemFree(pNode->papMoreExpectedPolicySet);
+ pNode->papMoreExpectedPolicySet = NULL;
+ }
+ RTMemFree(pNode);
+}
+
+
+/**
+ * Unlinks and frees a sub-tree in the valid policy tree.
+ *
+ * @param pThis The path builder & validator instance.
+ * @param pNode The node that is the root of the subtree.
+ */
+static void rtCrX509CpvPolicyTreeDestroySubtree(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHSPOLICYNODE pNode)
+{
+ if (!RTListIsEmpty(&pNode->ChildList))
+ {
+ PRTCRX509CERTPATHSPOLICYNODE pCur = pNode;
+ do
+ {
+ Assert(!RTListIsEmpty(&pCur->ChildList));
+
+ /* Decend until we find a leaf. */
+ do
+ pCur = RTListGetFirst(&pCur->ChildList, RTCRX509CERTPATHSPOLICYNODE, SiblingEntry);
+ while (!RTListIsEmpty(&pCur->ChildList));
+
+ /* Remove it and all leafy siblings. */
+ PRTCRX509CERTPATHSPOLICYNODE pParent = pCur->pParent;
+ do
+ {
+ Assert(pCur != pNode);
+ rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur);
+ pCur = RTListGetFirst(&pParent->ChildList, RTCRX509CERTPATHSPOLICYNODE, SiblingEntry);
+ if (!pCur)
+ {
+ pCur = pParent;
+ pParent = pParent->pParent;
+ }
+ } while (RTListIsEmpty(&pCur->ChildList) && pCur != pNode);
+ } while (pCur != pNode);
+ }
+
+ rtCrX509CpvPolicyTreeDestroyNode(pThis, pNode);
+}
+
+
+
+/**
+ * Destroys the entire policy tree.
+ *
+ * @param pThis The path builder & validator instance.
+ */
+static void rtCrX509CpvPolicyTreeDestroy(PRTCRX509CERTPATHSINT pThis)
+{
+ uint32_t i = pThis->v.cNodes + 1;
+ while (i-- > 0)
+ {
+ PRTCRX509CERTPATHSPOLICYNODE pCur, pNext;
+ RTListForEachSafe(&pThis->v.paValidPolicyDepthLists[i], pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur);
+ }
+ }
+}
+
+
+/**
+ * Removes all leaf nodes at level @a iDepth and above.
+ *
+ * @param pThis The path builder & validator instance.
+ * @param iDepth The depth to start pruning at.
+ */
+static void rtCrX509CpvPolicyTreePrune(PRTCRX509CERTPATHSINT pThis, uint32_t iDepth)
+{
+ do
+ {
+ PRTLISTANCHOR pList = &pThis->v.paValidPolicyDepthLists[iDepth];
+ PRTCRX509CERTPATHSPOLICYNODE pCur, pNext;
+ RTListForEachSafe(pList, pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ if (RTListIsEmpty(&pCur->ChildList))
+ rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur);
+ }
+
+ } while (iDepth-- > 0);
+}
+
+
+/**
+ * Checks if @a pPolicy is the valid policy of a child of @a pNode.
+ *
+ * @returns true if in child node, false if not.
+ * @param pNode The node which children to check.
+ * @param pPolicy The valid policy to look for among the children.
+ */
+static bool rtCrX509CpvPolicyTreeIsChild(PRTCRX509CERTPATHSPOLICYNODE pNode, PCRTASN1OBJID pPolicy)
+{
+ PRTCRX509CERTPATHSPOLICYNODE pChild;
+ RTListForEach(&pNode->ChildList, pChild, RTCRX509CERTPATHSPOLICYNODE, SiblingEntry)
+ {
+ if (RTAsn1ObjId_Compare(pChild->pValidPolicy, pPolicy) == 0)
+ return true;
+ }
+ return true;
+}
+
+
+/**
+ * Prunes the valid policy tree according to the specified user policy set.
+ *
+ * @returns Pointer to the policy object from @a papPolicies if found, NULL if
+ * no match.
+ * @param pObjId The object ID to locate at match in the set.
+ * @param cPolicies The number of policies in @a papPolicies.
+ * @param papPolicies The policy set to search.
+ */
+static PCRTASN1OBJID rtCrX509CpvFindObjIdInPolicySet(PCRTASN1OBJID pObjId, uint32_t cPolicies, PCRTASN1OBJID *papPolicies)
+{
+ uint32_t i = cPolicies;
+ while (i-- > 0)
+ if (RTAsn1ObjId_Compare(pObjId, papPolicies[i]) == 0)
+ return papPolicies[i];
+ return NULL;
+}
+
+
+/**
+ * Prunes the valid policy tree according to the specified user policy set.
+ *
+ * @returns success indicator (allocates memory)
+ * @param pThis The path builder & validator instance.
+ * @param cPolicies The number of policies in @a papPolicies.
+ * @param papPolicies The user initial policies.
+ */
+static bool rtCrX509CpvPolicyTreeIntersect(PRTCRX509CERTPATHSINT pThis, uint32_t cPolicies, PCRTASN1OBJID *papPolicies)
+{
+ /*
+ * 4.1.6.g.i - NULL tree remains NULL.
+ */
+ if (!pThis->v.pValidPolicyTree)
+ return true;
+
+ /*
+ * 4.1.6.g.ii - If the user set includes anyPolicy, the whole tree is the
+ * result of the intersection.
+ */
+ uint32_t i = cPolicies;
+ while (i-- > 0)
+ if (RTAsn1ObjId_CompareWithString(papPolicies[i], RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0)
+ return true;
+
+ /*
+ * 4.1.6.g.iii - Complicated.
+ */
+ PRTCRX509CERTPATHSPOLICYNODE pCur, pNext;
+ PRTLISTANCHOR pList;
+
+ /* 1 & 2: Delete nodes which parent has valid policy == anyPolicy and which
+ valid policy is neither anyPolicy nor a member of papszPolicies.
+ While doing so, construct a set of unused user policies that
+ we'll replace anyPolicy nodes with in step 3. */
+ uint32_t cPoliciesLeft = 0;
+ PCRTASN1OBJID *papPoliciesLeft = NULL;
+ if (cPolicies)
+ {
+ papPoliciesLeft = (PCRTASN1OBJID *)rtCrX509CpvAllocZ(pThis, cPolicies * sizeof(papPoliciesLeft[0]), "papPoliciesLeft");
+ if (!papPoliciesLeft)
+ return false;
+ for (i = 0; i < cPolicies; i++)
+ papPoliciesLeft[i] = papPolicies[i];
+ }
+
+ for (uint32_t iDepth = 1; iDepth <= pThis->v.cNodes; iDepth++)
+ {
+ pList = &pThis->v.paValidPolicyDepthLists[iDepth];
+ RTListForEachSafe(pList, pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ Assert(pCur->pParent);
+ if ( RTAsn1ObjId_CompareWithString(pCur->pParent->pValidPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0
+ && RTAsn1ObjId_CompareWithString(pCur->pValidPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) != 0)
+ {
+ PCRTASN1OBJID pFound = rtCrX509CpvFindObjIdInPolicySet(pCur->pValidPolicy, cPolicies, papPolicies);
+ if (!pFound)
+ rtCrX509CpvPolicyTreeDestroySubtree(pThis, pCur);
+ else
+ for (i = 0; i < cPoliciesLeft; i++)
+ if (papPoliciesLeft[i] == pFound)
+ {
+ cPoliciesLeft--;
+ if (i < cPoliciesLeft)
+ papPoliciesLeft[i] = papPoliciesLeft[cPoliciesLeft];
+ papPoliciesLeft[cPoliciesLeft] = NULL;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * 4.1.5.g.iii.3 - Replace anyPolicy nodes on the final tree depth with
+ * the policies in papPoliciesLeft.
+ */
+ pList = &pThis->v.paValidPolicyDepthLists[pThis->v.cNodes];
+ RTListForEachSafe(pList, pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ if (RTAsn1ObjId_CompareWithString(pCur->pValidPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0)
+ {
+ for (i = 0; i < cPoliciesLeft; i++)
+ rtCrX509CpvPolicyTreeInsertNew(pThis, pCur->pParent, pThis->v.cNodes - 1,
+ papPoliciesLeft[i], pCur->pPolicyQualifiers, papPoliciesLeft[i]);
+ rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur);
+ }
+ }
+
+ RTMemFree(papPoliciesLeft);
+
+ /*
+ * 4.1.5.g.iii.4 - Prune the tree
+ */
+ rtCrX509CpvPolicyTreePrune(pThis, pThis->v.cNodes - 1);
+
+ return RT_SUCCESS(pThis->rc);
+}
+
+
+
+/**
+ * Frees the path validator state.
+ *
+ * @param pThis The path builder & validator instance.
+ */
+static void rtCrX509CpvCleanup(PRTCRX509CERTPATHSINT pThis)
+{
+ /*
+ * Destroy the policy tree and all its nodes. We do this from the bottom
+ * up via the depth lists, saving annoying tree traversal.
+ */
+ if (pThis->v.paValidPolicyDepthLists)
+ {
+ rtCrX509CpvPolicyTreeDestroy(pThis);
+
+ RTMemFree(pThis->v.paValidPolicyDepthLists);
+ pThis->v.paValidPolicyDepthLists = NULL;
+ }
+
+ Assert(pThis->v.pValidPolicyTree == NULL);
+ pThis->v.pValidPolicyTree = NULL;
+
+ /*
+ * Destroy the name constraint arrays.
+ */
+ if (pThis->v.papPermittedSubtrees)
+ {
+ RTMemFree(pThis->v.papPermittedSubtrees);
+ pThis->v.papPermittedSubtrees = NULL;
+ }
+ pThis->v.cPermittedSubtrees = 0;
+ pThis->v.cPermittedSubtreesAlloc = 0;
+ pThis->v.fNoPermittedSubtrees = false;
+
+ if (pThis->v.papExcludedSubtrees)
+ {
+ RTMemFree(pThis->v.papExcludedSubtrees);
+ pThis->v.papExcludedSubtrees = NULL;
+ }
+ pThis->v.cExcludedSubtrees = 0;
+
+ /*
+ * Clear other pointers.
+ */
+ pThis->v.pWorkingIssuer = NULL;
+ pThis->v.pWorkingPublicKey = NULL;
+ pThis->v.pWorkingPublicKeyAlgorithm = NULL;
+ pThis->v.pWorkingPublicKeyParameters = NULL;
+}
+
+
+/**
+ * Initializes the state.
+ *
+ * Caller must check pThis->rc.
+ *
+ * @param pThis The path builder & validator instance.
+ * @param pTrustAnchor The trust anchor node for the path that we're about
+ * to validate.
+ */
+static void rtCrX509CpvInit(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pTrustAnchor)
+{
+ rtCrX509CpvCleanup(pThis);
+
+ /*
+ * The node count does not include the trust anchor.
+ */
+ pThis->v.cNodes = pTrustAnchor->uDepth;
+
+ /*
+ * Valid policy tree starts with an anyPolicy node.
+ */
+ uint32_t i = pThis->v.cNodes + 1;
+ pThis->v.paValidPolicyDepthLists = (PRTLISTANCHOR)rtCrX509CpvAllocZ(pThis, i * sizeof(RTLISTANCHOR),
+ "paValidPolicyDepthLists");
+ if (RT_UNLIKELY(!pThis->v.paValidPolicyDepthLists))
+ return;
+ while (i-- > 0)
+ RTListInit(&pThis->v.paValidPolicyDepthLists[i]);
+
+ if (!rtCrX509CpvPolicyTreeInsertNew(pThis, NULL, 0 /* iDepth*/, &pThis->AnyPolicyObjId, NULL, &pThis->AnyPolicyObjId))
+ return;
+ Assert(!RTListIsEmpty(&pThis->v.paValidPolicyDepthLists[0])); Assert(pThis->v.pValidPolicyTree);
+
+ /*
+ * Name constrains.
+ */
+ if (pThis->pInitialPermittedSubtrees)
+ rtCrX509CpvAddPermittedSubtrees(pThis, pThis->pInitialPermittedSubtrees->cItems,
+ pThis->pInitialPermittedSubtrees->papItems);
+ if (pThis->pInitialExcludedSubtrees)
+ rtCrX509CpvAddExcludedSubtrees(pThis, pThis->pInitialExcludedSubtrees);
+
+ /*
+ * Counters.
+ */
+ pThis->v.cExplicitPolicy = pThis->cInitialExplicitPolicy;
+ pThis->v.cInhibitPolicyMapping = pThis->cInitialPolicyMappingInhibit;
+ pThis->v.cInhibitAnyPolicy = pThis->cInitialInhibitAnyPolicy;
+ pThis->v.cMaxPathLength = pThis->v.cNodes;
+
+ /*
+ * Certificate info from the trust anchor.
+ */
+ if (pTrustAnchor->pCert)
+ {
+ PCRTCRX509TBSCERTIFICATE const pTbsCert = &pTrustAnchor->pCert->TbsCertificate;
+ pThis->v.pWorkingIssuer = &pTbsCert->Subject;
+ pThis->v.pWorkingPublicKey = &pTbsCert->SubjectPublicKeyInfo.SubjectPublicKey;
+ pThis->v.pWorkingPublicKeyAlgorithm = &pTbsCert->SubjectPublicKeyInfo.Algorithm.Algorithm;
+ pThis->v.pWorkingPublicKeyParameters = &pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters;
+ }
+ else
+ {
+ Assert(pTrustAnchor->pCertCtx); Assert(pTrustAnchor->pCertCtx->pTaInfo);
+
+ PCRTCRTAFTRUSTANCHORINFO const pTaInfo = pTrustAnchor->pCertCtx->pTaInfo;
+ pThis->v.pWorkingIssuer = &pTaInfo->CertPath.TaName;
+ pThis->v.pWorkingPublicKey = &pTaInfo->PubKey.SubjectPublicKey;
+ pThis->v.pWorkingPublicKeyAlgorithm = &pTaInfo->PubKey.Algorithm.Algorithm;
+ pThis->v.pWorkingPublicKeyParameters = &pTaInfo->PubKey.Algorithm.Parameters;
+ }
+ if ( !RTASN1CORE_IS_PRESENT(&pThis->v.pWorkingPublicKeyParameters->u.Core)
+ || pThis->v.pWorkingPublicKeyParameters->enmType == RTASN1TYPE_NULL)
+ pThis->v.pWorkingPublicKeyParameters = NULL;
+}
+
+
+/**
+ * This does basic trust anchor checks (similar to 6.1.3.a) before starting on
+ * the RFC-5280 algorithm.
+ */
+static bool rtCrX509CpvMaybeCheckTrustAnchor(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pTrustAnchor)
+{
+ /*
+ * This is optional (not part of RFC-5280) and we need a full certificate
+ * structure to do it.
+ */
+ if (!(pThis->fFlags & RTCRX509CERTPATHSINT_F_CHECK_TRUST_ANCHOR))
+ return true;
+
+ PCRTCRX509CERTIFICATE const pCert = pTrustAnchor->pCert;
+ if (!pCert)
+ return true;
+
+ /*
+ * Verify the certificate signature if self-signed.
+ */
+ if (RTCrX509Certificate_IsSelfSigned(pCert))
+ {
+ int rc = RTCrX509Certificate_VerifySignature(pCert, pThis->v.pWorkingPublicKeyAlgorithm,
+ pThis->v.pWorkingPublicKeyParameters, pThis->v.pWorkingPublicKey,
+ pThis->pErrInfo);
+ if (RT_FAILURE(rc))
+ {
+ pThis->rc = rc;
+ return false;
+ }
+ }
+
+ /*
+ * Verify that the certificate is valid at the specified time.
+ */
+ AssertCompile(sizeof(pThis->szTmp) >= 36 * 3);
+ if ( (pThis->fFlags & RTCRX509CERTPATHSINT_F_VALID_TIME)
+ && !RTCrX509Validity_IsValidAtTimeSpec(&pCert->TbsCertificate.Validity, &pThis->ValidTime))
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_VALID_AT_TIME,
+ "Certificate is not valid (ValidTime=%s Validity=[%s...%s])",
+ RTTimeSpecToString(&pThis->ValidTime, &pThis->szTmp[0], 36),
+ RTTimeToString(&pCert->TbsCertificate.Validity.NotBefore.Time, &pThis->szTmp[36], 36),
+ RTTimeToString(&pCert->TbsCertificate.Validity.NotAfter.Time, &pThis->szTmp[2*36], 36) );
+
+ /*
+ * Verified that the certficiate is not revoked.
+ */
+ /** @todo rainy day. */
+
+ /*
+ * If non-leaf certificate CA must be set, if basic constraints are present.
+ */
+ if (pTrustAnchor->pParent)
+ {
+ if (RTAsn1Integer_UnsignedCompareWithU32(&pTrustAnchor->pCert->TbsCertificate.T0.Version, RTCRX509TBSCERTIFICATE_V3) != 0)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_V3_CERT,
+ "Only version 3 TA certificates are supported (Version=%llu)",
+ pTrustAnchor->pCert->TbsCertificate.T0.Version.uValue);
+ PCRTCRX509BASICCONSTRAINTS pBasicConstraints = pTrustAnchor->pCert->TbsCertificate.T3.pBasicConstraints;
+ if (pBasicConstraints && !pBasicConstraints->CA.fValue)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_CA_CERT,
+ "Trust anchor certificate is not marked as a CA");
+ }
+
+ return true;
+}
+
+
+/**
+ * Step 6.1.3.a.
+ */
+static bool rtCrX509CpvCheckBasicCertInfo(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode)
+{
+ /*
+ * 6.1.3.a.1 - Verify the certificate signature.
+ */
+ int rc = RTCrX509Certificate_VerifySignature(pNode->pCert, pThis->v.pWorkingPublicKeyAlgorithm,
+ pThis->v.pWorkingPublicKeyParameters, pThis->v.pWorkingPublicKey,
+ pThis->pErrInfo);
+ if (RT_FAILURE(rc))
+ {
+ pThis->rc = rc;
+ return false;
+ }
+
+ /*
+ * 6.1.3.a.2 - Verify that the certificate is valid at the specified time.
+ */
+ AssertCompile(sizeof(pThis->szTmp) >= 36 * 3);
+ if ( (pThis->fFlags & RTCRX509CERTPATHSINT_F_VALID_TIME)
+ && !RTCrX509Validity_IsValidAtTimeSpec(&pNode->pCert->TbsCertificate.Validity, &pThis->ValidTime))
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_VALID_AT_TIME,
+ "Certificate is not valid (ValidTime=%s Validity=[%s...%s])",
+ RTTimeSpecToString(&pThis->ValidTime, &pThis->szTmp[0], 36),
+ RTTimeToString(&pNode->pCert->TbsCertificate.Validity.NotBefore.Time, &pThis->szTmp[36], 36),
+ RTTimeToString(&pNode->pCert->TbsCertificate.Validity.NotAfter.Time, &pThis->szTmp[2*36], 36) );
+
+ /*
+ * 6.1.3.a.3 - Verified that the certficiate is not revoked.
+ */
+ /** @todo rainy day. */
+
+ /*
+ * 6.1.3.a.4 - Check the issuer name.
+ */
+ if (!RTCrX509Name_MatchByRfc5280(&pNode->pCert->TbsCertificate.Issuer, pThis->v.pWorkingIssuer))
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_ISSUER_MISMATCH, "Issuer mismatch");
+
+ return true;
+}
+
+
+/**
+ * Step 6.1.3.b-c.
+ */
+static bool rtCrX509CpvCheckNameConstraints(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode)
+{
+ if (pThis->v.fNoPermittedSubtrees)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NO_PERMITTED_NAMES, "No permitted subtrees");
+
+ if ( pNode->pCert->TbsCertificate.Subject.cItems > 0
+ && ( !rtCrX509CpvIsNamePermitted(pThis, &pNode->pCert->TbsCertificate.Subject)
+ || rtCrX509CpvIsNameExcluded(pThis, &pNode->pCert->TbsCertificate.Subject)) )
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NAME_NOT_PERMITTED,
+ "Subject name is not permitted by current name constraints");
+
+ PCRTCRX509GENERALNAMES pAltSubjectName = pNode->pCert->TbsCertificate.T3.pAltSubjectName;
+ if (pAltSubjectName)
+ {
+ uint32_t i = pAltSubjectName->cItems;
+ while (i-- > 0)
+ if ( !rtCrX509CpvIsGeneralNamePermitted(pThis, pAltSubjectName->papItems[i])
+ || rtCrX509CpvIsGeneralNameExcluded(pThis, pAltSubjectName->papItems[i]))
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_ALT_NAME_NOT_PERMITTED,
+ "Alternative name #%u is is not permitted by current name constraints", i);
+ }
+
+ return true;
+}
+
+
+/**
+ * Step 6.1.3.d-f.
+ */
+static bool rtCrX509CpvWorkValidPolicyTree(PRTCRX509CERTPATHSINT pThis, uint32_t iDepth, PRTCRX509CERTPATHNODE pNode,
+ bool fSelfIssued)
+{
+ PCRTCRX509CERTIFICATEPOLICIES pPolicies = pNode->pCert->TbsCertificate.T3.pCertificatePolicies;
+ if (pPolicies)
+ {
+ /*
+ * 6.1.3.d.1 - Work the certiciate policies into the tree.
+ */
+ PRTCRX509CERTPATHSPOLICYNODE pCur;
+ PRTLISTANCHOR pListAbove = &pThis->v.paValidPolicyDepthLists[iDepth - 1];
+ uint32_t iAnyPolicy = UINT32_MAX;
+ uint32_t i = pPolicies->cItems;
+ while (i-- > 0)
+ {
+ PCRTCRX509POLICYQUALIFIERINFOS const pQualifiers = &pPolicies->papItems[i]->PolicyQualifiers;
+ PCRTASN1OBJID const pIdP = &pPolicies->papItems[i]->PolicyIdentifier;
+ if (RTAsn1ObjId_CompareWithString(pIdP, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0)
+ {
+ iAnyPolicy++;
+ continue;
+ }
+
+ /*
+ * 6.1.3.d.1.i - Create children for matching policies.
+ */
+ uint32_t cMatches = 0;
+ RTListForEach(pListAbove, pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ bool fMatch = RTAsn1ObjId_Compare(pCur->pExpectedPolicyFirst, pIdP) == 0;
+ if (!fMatch && pCur->cMoreExpectedPolicySet)
+ for (uint32_t j = 0; !fMatch && j < pCur->cMoreExpectedPolicySet; j++)
+ fMatch = RTAsn1ObjId_Compare(pCur->papMoreExpectedPolicySet[j], pIdP) == 0;
+ if (fMatch)
+ {
+ if (!rtCrX509CpvPolicyTreeInsertNew(pThis, pCur, iDepth, pIdP, pQualifiers, pIdP))
+ return false;
+ cMatches++;
+ }
+ }
+
+ /*
+ * 6.1.3.d.1.ii - If no matches above do the same for anyPolicy
+ * nodes, only match with valid policy this time.
+ */
+ if (cMatches == 0)
+ {
+ RTListForEach(pListAbove, pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ if (RTAsn1ObjId_CompareWithString(pCur->pExpectedPolicyFirst, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0)
+ {
+ if (!rtCrX509CpvPolicyTreeInsertNew(pThis, pCur, iDepth, pIdP, pQualifiers, pIdP))
+ return false;
+ }
+ }
+ }
+ }
+
+ /*
+ * 6.1.3.d.2 - If anyPolicy present, make sure all expected policies
+ * are propagated to the current depth.
+ */
+ if ( iAnyPolicy < pPolicies->cItems
+ && ( pThis->v.cInhibitAnyPolicy > 0
+ || (pNode->pParent && fSelfIssued) ) )
+ {
+ PCRTCRX509POLICYQUALIFIERINFOS pApQ = &pPolicies->papItems[iAnyPolicy]->PolicyQualifiers;
+ RTListForEach(pListAbove, pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ if (!rtCrX509CpvPolicyTreeIsChild(pCur, pCur->pExpectedPolicyFirst))
+ rtCrX509CpvPolicyTreeInsertNew(pThis, pCur, iDepth, pCur->pExpectedPolicyFirst, pApQ,
+ pCur->pExpectedPolicyFirst);
+ for (uint32_t j = 0; j < pCur->cMoreExpectedPolicySet; j++)
+ if (!rtCrX509CpvPolicyTreeIsChild(pCur, pCur->papMoreExpectedPolicySet[j]))
+ rtCrX509CpvPolicyTreeInsertNew(pThis, pCur, iDepth, pCur->papMoreExpectedPolicySet[j], pApQ,
+ pCur->papMoreExpectedPolicySet[j]);
+ }
+ }
+ /*
+ * 6.1.3.d.3 - Prune the tree.
+ */
+ else
+ rtCrX509CpvPolicyTreePrune(pThis, iDepth - 1);
+ }
+ else
+ {
+ /*
+ * 6.1.3.e - No policy extension present, set tree to NULL.
+ */
+ rtCrX509CpvPolicyTreeDestroy(pThis);
+ }
+
+ /*
+ * 6.1.3.f - NULL tree check.
+ */
+ if ( pThis->v.pValidPolicyTree == NULL
+ && pThis->v.cExplicitPolicy == 0)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NO_VALID_POLICY,
+ "An explicit policy is called for but the valid policy tree is NULL.");
+ return RT_SUCCESS(pThis->rc);
+}
+
+
+/**
+ * Step 6.1.4.a-b.
+ */
+static bool rtCrX509CpvSoakUpPolicyMappings(PRTCRX509CERTPATHSINT pThis, uint32_t iDepth,
+ PCRTCRX509POLICYMAPPINGS pPolicyMappings)
+{
+ /*
+ * 6.1.4.a - The anyPolicy is not allowed in policy mappings as it would
+ * allow an evil intermediate certificate to expand the policy
+ * scope of a certiciate chain without regard to upstream.
+ */
+ uint32_t i = pPolicyMappings->cItems;
+ while (i-- > 0)
+ {
+ PCRTCRX509POLICYMAPPING const pOne = pPolicyMappings->papItems[i];
+ if (RTAsn1ObjId_CompareWithString(&pOne->IssuerDomainPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_INVALID_POLICY_MAPPING,
+ "Invalid policy mapping %#u: IssuerDomainPolicy is anyPolicy.", i);
+
+ if (RTAsn1ObjId_CompareWithString(&pOne->SubjectDomainPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_INVALID_POLICY_MAPPING,
+ "Invalid policy mapping %#u: SubjectDomainPolicy is anyPolicy.", i);
+ }
+
+ PRTCRX509CERTPATHSPOLICYNODE pCur, pNext;
+ if (pThis->v.cInhibitPolicyMapping > 0)
+ {
+ /*
+ * 6.1.4.b.1 - Do the policy mapping.
+ */
+ i = pPolicyMappings->cItems;
+ while (i-- > 0)
+ {
+ PCRTCRX509POLICYMAPPING const pOne = pPolicyMappings->papItems[i];
+
+ uint32_t cFound = 0;
+ RTListForEach(&pThis->v.paValidPolicyDepthLists[iDepth], pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ if (RTAsn1ObjId_Compare(pCur->pValidPolicy, &pOne->IssuerDomainPolicy))
+ {
+ if (!pCur->fAlreadyMapped)
+ {
+ pCur->fAlreadyMapped = true;
+ pCur->pExpectedPolicyFirst = &pOne->SubjectDomainPolicy;
+ }
+ else
+ {
+ uint32_t iExpected = pCur->cMoreExpectedPolicySet;
+ void *pvNew = RTMemRealloc(pCur->papMoreExpectedPolicySet,
+ sizeof(pCur->papMoreExpectedPolicySet[0]) * (iExpected + 1));
+ if (!pvNew)
+ return rtCrX509CpvFailed(pThis, VERR_NO_MEMORY,
+ "Error growing papMoreExpectedPolicySet array (cur %u, depth %u)",
+ pCur->cMoreExpectedPolicySet, iDepth);
+ pCur->papMoreExpectedPolicySet = (PCRTASN1OBJID *)pvNew;
+ pCur->papMoreExpectedPolicySet[iExpected] = &pOne->SubjectDomainPolicy;
+ pCur->cMoreExpectedPolicySet = iExpected + 1;
+ }
+ cFound++;
+ }
+ }
+
+ /*
+ * If no mapping took place, look for an anyPolicy node.
+ */
+ if (!cFound)
+ {
+ RTListForEach(&pThis->v.paValidPolicyDepthLists[iDepth], pCur, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ if (RTAsn1ObjId_CompareWithString(pCur->pValidPolicy, RTCRX509_ID_CE_CP_ANY_POLICY_OID) == 0)
+ {
+ if (!rtCrX509CpvPolicyTreeInsertNew(pThis, pCur->pParent, iDepth,
+ &pOne->IssuerDomainPolicy,
+ pCur->pPolicyQualifiers,
+ &pOne->SubjectDomainPolicy))
+ return false;
+ break;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ * 6.1.4.b.2 - Remove matching policies from the tree if mapping is
+ * inhibited and prune the tree.
+ */
+ uint32_t cRemoved = 0;
+ i = pPolicyMappings->cItems;
+ while (i-- > 0)
+ {
+ PCRTCRX509POLICYMAPPING const pOne = pPolicyMappings->papItems[i];
+ RTListForEachSafe(&pThis->v.paValidPolicyDepthLists[iDepth], pCur, pNext, RTCRX509CERTPATHSPOLICYNODE, DepthEntry)
+ {
+ if (RTAsn1ObjId_Compare(pCur->pValidPolicy, &pOne->IssuerDomainPolicy))
+ {
+ rtCrX509CpvPolicyTreeDestroyNode(pThis, pCur);
+ cRemoved++;
+ }
+ }
+ }
+ if (cRemoved)
+ rtCrX509CpvPolicyTreePrune(pThis, iDepth - 1);
+ }
+
+ return true;
+}
+
+
+/**
+ * Step 6.1.4.d-f & 6.1.5.c-e.
+ */
+static void rtCrX509CpvSetWorkingPublicKeyInfo(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode)
+{
+ PCRTCRX509TBSCERTIFICATE const pTbsCert = &pNode->pCert->TbsCertificate;
+
+ /*
+ * 6.1.4.d - The public key.
+ */
+ pThis->v.pWorkingPublicKey = &pTbsCert->SubjectPublicKeyInfo.SubjectPublicKey;
+
+ /*
+ * 6.1.4.e - The public key parameters. Use new ones if present, keep old
+ * if the algorithm remains the same.
+ */
+ if ( RTASN1CORE_IS_PRESENT(&pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters.u.Core)
+ && pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters.enmType != RTASN1TYPE_NULL)
+ pThis->v.pWorkingPublicKeyParameters = &pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters;
+ else if ( pThis->v.pWorkingPublicKeyParameters
+ && RTAsn1ObjId_Compare(pThis->v.pWorkingPublicKeyAlgorithm, &pTbsCert->SubjectPublicKeyInfo.Algorithm.Algorithm) != 0)
+ pThis->v.pWorkingPublicKeyParameters = NULL;
+
+ /*
+ * 6.1.4.f - The public algorithm.
+ */
+ pThis->v.pWorkingPublicKeyAlgorithm = &pTbsCert->SubjectPublicKeyInfo.Algorithm.Algorithm;
+}
+
+
+/**
+ * Step 6.1.4.g.
+ */
+static bool rtCrX509CpvSoakUpNameConstraints(PRTCRX509CERTPATHSINT pThis, PCRTCRX509NAMECONSTRAINTS pNameConstraints)
+{
+ if (pNameConstraints->T0.PermittedSubtrees.cItems > 0)
+ if (!rtCrX509CpvIntersectionPermittedSubtrees(pThis, &pNameConstraints->T0.PermittedSubtrees))
+ return false;
+
+ if (pNameConstraints->T1.ExcludedSubtrees.cItems > 0)
+ if (!rtCrX509CpvAddExcludedSubtrees(pThis, &pNameConstraints->T1.ExcludedSubtrees))
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Step 6.1.4.i.
+ */
+static bool rtCrX509CpvSoakUpPolicyConstraints(PRTCRX509CERTPATHSINT pThis, PCRTCRX509POLICYCONSTRAINTS pPolicyConstraints)
+{
+ if (RTAsn1Integer_IsPresent(&pPolicyConstraints->RequireExplicitPolicy))
+ {
+ if (RTAsn1Integer_UnsignedCompareWithU32(&pPolicyConstraints->RequireExplicitPolicy, pThis->v.cExplicitPolicy) < 0)
+ pThis->v.cExplicitPolicy = pPolicyConstraints->RequireExplicitPolicy.uValue.s.Lo;
+ }
+
+ if (RTAsn1Integer_IsPresent(&pPolicyConstraints->InhibitPolicyMapping))
+ {
+ if (RTAsn1Integer_UnsignedCompareWithU32(&pPolicyConstraints->InhibitPolicyMapping, pThis->v.cInhibitPolicyMapping) < 0)
+ pThis->v.cInhibitPolicyMapping = pPolicyConstraints->InhibitPolicyMapping.uValue.s.Lo;
+ }
+ return true;
+}
+
+
+/**
+ * Step 6.1.4.j.
+ */
+static bool rtCrX509CpvSoakUpInhibitAnyPolicy(PRTCRX509CERTPATHSINT pThis, PCRTASN1INTEGER pInhibitAnyPolicy)
+{
+ if (RTAsn1Integer_UnsignedCompareWithU32(pInhibitAnyPolicy, pThis->v.cInhibitAnyPolicy) < 0)
+ pThis->v.cInhibitAnyPolicy = pInhibitAnyPolicy->uValue.s.Lo;
+ return true;
+}
+
+
+/**
+ * Steps 6.1.4.k, 6.1.4.l, 6.1.4.m, and 6.1.4.n.
+ */
+static bool rtCrX509CpvCheckAndSoakUpBasicConstraintsAndKeyUsage(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode,
+ bool fSelfIssued)
+{
+ /* 6.1.4.k - If basic constraints present, CA must be set. */
+ if (RTAsn1Integer_UnsignedCompareWithU32(&pNode->pCert->TbsCertificate.T0.Version, RTCRX509TBSCERTIFICATE_V3) != 0)
+ {
+ /* Note! Add flags if support for older certificates is needed later. */
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_V3_CERT,
+ "Only version 3 certificates are supported (Version=%llu)",
+ pNode->pCert->TbsCertificate.T0.Version.uValue);
+ }
+ PCRTCRX509BASICCONSTRAINTS pBasicConstraints = pNode->pCert->TbsCertificate.T3.pBasicConstraints;
+ if (pBasicConstraints)
+ {
+ if (!pBasicConstraints->CA.fValue)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NOT_CA_CERT,
+ "Intermediate certificate (#%u) is not marked as a CA", pThis->v.iNode);
+ }
+
+ /* 6.1.4.l - Work cMaxPathLength. */
+ if (!fSelfIssued)
+ {
+ if (pThis->v.cMaxPathLength > 0)
+ pThis->v.cMaxPathLength--;
+ else
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_MAX_PATH_LENGTH,
+ "Hit max path length at node #%u", pThis->v.iNode);
+ }
+
+ /* 6.1.4.m - Update cMaxPathLength if basic constrain field is present and smaller. */
+ if (pBasicConstraints)
+ {
+ if (RTAsn1Integer_IsPresent(&pBasicConstraints->PathLenConstraint))
+ if (RTAsn1Integer_UnsignedCompareWithU32(&pBasicConstraints->PathLenConstraint, pThis->v.cMaxPathLength) < 0)
+ pThis->v.cMaxPathLength = pBasicConstraints->PathLenConstraint.uValue.s.Lo;
+ }
+
+ /* 6.1.4.n - Require keyCertSign in key usage if the extension is present. */
+ PCRTCRX509TBSCERTIFICATE const pTbsCert = &pNode->pCert->TbsCertificate;
+ if ( (pTbsCert->T3.fFlags & RTCRX509TBSCERTIFICATE_F_PRESENT_KEY_USAGE)
+ && !(pTbsCert->T3.fKeyUsage & RTCRX509CERT_KEY_USAGE_F_KEY_CERT_SIGN))
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_MISSING_KEY_CERT_SIGN,
+ "Node #%u does not have KeyCertSign set (keyUsage=%#x)",
+ pThis->v.iNode, pTbsCert->T3.fKeyUsage);
+
+ return true;
+}
+
+
+/**
+ * Step 6.1.4.o - check out critical extensions.
+ */
+static bool rtCrX509CpvCheckCriticalExtensions(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode)
+{
+ uint32_t cLeft = pNode->pCert->TbsCertificate.T3.Extensions.cItems;
+ PRTCRX509EXTENSION const *ppCur = pNode->pCert->TbsCertificate.T3.Extensions.papItems;
+ while (cLeft-- > 0)
+ {
+ PCRTCRX509EXTENSION const pCur = *ppCur;
+ if (pCur->Critical.fValue)
+ {
+ if ( RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_KEY_USAGE_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_SUBJECT_ALT_NAME_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_ISSUER_ALT_NAME_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_BASIC_CONSTRAINTS_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_NAME_CONSTRAINTS_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_CERTIFICATE_POLICIES_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_POLICY_MAPPINGS_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_POLICY_CONSTRAINTS_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_EXT_KEY_USAGE_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_INHIBIT_ANY_POLICY_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_APPLICATION_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_INSTALLER_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_KEXT_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_IPHONE_SW_DEV_OID) != 0
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCR_APPLE_CS_DEVID_MAC_SW_DEV_OID) != 0
+ )
+ {
+ /* @bugref{10130}: An IntelGraphicsPE2021 cert issued by iKG_AZSKGFDCS has a critical subjectKeyIdentifier
+ which we quietly ignore here. RFC-5280 conforming CAs should not mark this as critical.
+ On an end entity this extension can have relevance to path construction. */
+ if ( pNode->uSrc == RTCRX509CERTPATHNODE_SRC_TARGET
+ && RTAsn1ObjId_CompareWithString(&pCur->ExtnId, RTCRX509_ID_CE_SUBJECT_KEY_IDENTIFIER_OID) == 0)
+ LogFunc(("Ignoring non-standard subjectKeyIdentifier on target certificate.\n"));
+ else
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_UNKNOWN_CRITICAL_EXTENSION,
+ "Node #%u has an unknown critical extension: %s",
+ pThis->v.iNode, pCur->ExtnId.szObjId);
+ }
+ }
+
+ ppCur++;
+ }
+
+ return true;
+}
+
+
+/**
+ * Step 6.1.5 - The wrapping up.
+ */
+static bool rtCrX509CpvWrapUp(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pNode)
+{
+ Assert(!pNode->pParent); Assert(pThis->pTarget == pNode->pCert);
+
+ /*
+ * 6.1.5.a - Decrement explicit policy.
+ */
+ if (pThis->v.cExplicitPolicy > 0)
+ pThis->v.cExplicitPolicy--;
+
+ /*
+ * 6.1.5.b - Policy constraints and explicit policy.
+ */
+ PCRTCRX509POLICYCONSTRAINTS pPolicyConstraints = pNode->pCert->TbsCertificate.T3.pPolicyConstraints;
+ if ( pPolicyConstraints
+ && RTAsn1Integer_IsPresent(&pPolicyConstraints->RequireExplicitPolicy)
+ && RTAsn1Integer_UnsignedCompareWithU32(&pPolicyConstraints->RequireExplicitPolicy, 0) == 0)
+ pThis->v.cExplicitPolicy = 0;
+
+ /*
+ * 6.1.5.c-e - Update working public key info.
+ */
+ rtCrX509CpvSetWorkingPublicKeyInfo(pThis, pNode);
+
+ /*
+ * 6.1.5.f - Critical extensions.
+ */
+ if (!rtCrX509CpvCheckCriticalExtensions(pThis, pNode))
+ return false;
+
+ /*
+ * 6.1.5.g - Calculate the intersection between the user initial policy set
+ * and the valid policy tree.
+ */
+ rtCrX509CpvPolicyTreeIntersect(pThis, pThis->cInitialUserPolicySet, pThis->papInitialUserPolicySet);
+
+ if ( pThis->v.cExplicitPolicy == 0
+ && pThis->v.pValidPolicyTree == NULL)
+ return rtCrX509CpvFailed(pThis, VERR_CR_X509_CPV_NO_VALID_POLICY, "No valid policy (wrap-up).");
+
+ return true;
+}
+
+
+/**
+ * Worker that validates one path.
+ *
+ * This implements the algorithm in RFC-5280, section 6.1, with exception of
+ * the CRL checks in 6.1.3.a.3.
+ *
+ * @returns success indicator.
+ * @param pThis The path builder & validator instance.
+ * @param pTrustAnchor The trust anchor node.
+ */
+static bool rtCrX509CpvOneWorker(PRTCRX509CERTPATHSINT pThis, PRTCRX509CERTPATHNODE pTrustAnchor)
+{
+ /*
+ * Init.
+ */
+ rtCrX509CpvInit(pThis, pTrustAnchor);
+ if (RT_SUCCESS(pThis->rc))
+ {
+ /*
+ * Maybe do some trust anchor checks.
+ */
+ if (!rtCrX509CpvMaybeCheckTrustAnchor(pThis, pTrustAnchor))
+ {
+ AssertStmt(RT_FAILURE_NP(pThis->rc), pThis->rc = VERR_CR_X509_CERTPATHS_INTERNAL_ERROR);
+ return false;
+ }
+
+ /*
+ * Special case, target certificate is trusted.
+ */
+ if (!pTrustAnchor->pParent)
+ return true; /* rtCrX509CpvWrapUp should not be needed here. */
+
+ /*
+ * Normal processing.
+ */
+ PRTCRX509CERTPATHNODE pNode = pTrustAnchor->pParent;
+ uint32_t iNode = pThis->v.iNode = 1; /* We count to cNode (inclusive). Same a validation tree depth. */
+ while (pNode && RT_SUCCESS(pThis->rc))
+ {
+ /*
+ * Basic certificate processing.
+ */
+ if (!rtCrX509CpvCheckBasicCertInfo(pThis, pNode)) /* Step 6.1.3.a */
+ break;
+
+ bool const fSelfIssued = rtCrX509CertPathsIsSelfIssued(pNode);
+ if (!fSelfIssued || !pNode->pParent) /* Step 6.1.3.b-c */
+ if (!rtCrX509CpvCheckNameConstraints(pThis, pNode))
+ break;
+
+ if (!rtCrX509CpvWorkValidPolicyTree(pThis, iNode, pNode, fSelfIssued)) /* Step 6.1.3.d-f */
+ break;
+
+ /*
+ * If it's the last certificate in the path, do wrap-ups.
+ */
+ if (!pNode->pParent) /* Step 6.1.5 */
+ {
+ Assert(iNode == pThis->v.cNodes);
+ if (!rtCrX509CpvWrapUp(pThis, pNode))
+ break;
+ AssertRCBreak(pThis->rc);
+ return true;
+ }
+
+ /*
+ * Preparations for the next certificate.
+ */
+ PCRTCRX509TBSCERTIFICATE const pTbsCert = &pNode->pCert->TbsCertificate;
+ if ( pTbsCert->T3.pPolicyMappings
+ && !rtCrX509CpvSoakUpPolicyMappings(pThis, iNode, pTbsCert->T3.pPolicyMappings)) /* Step 6.1.4.a-b */
+ break;
+
+ pThis->v.pWorkingIssuer = &pTbsCert->Subject; /* Step 6.1.4.c */
+
+ rtCrX509CpvSetWorkingPublicKeyInfo(pThis, pNode); /* Step 6.1.4.d-f */
+
+ if ( pTbsCert->T3.pNameConstraints /* Step 6.1.4.g */
+ && !rtCrX509CpvSoakUpNameConstraints(pThis, pTbsCert->T3.pNameConstraints))
+ break;
+
+ if (!fSelfIssued) /* Step 6.1.4.h */
+ {
+ if (pThis->v.cExplicitPolicy > 0)
+ pThis->v.cExplicitPolicy--;
+ if (pThis->v.cInhibitPolicyMapping > 0)
+ pThis->v.cInhibitPolicyMapping--;
+ if (pThis->v.cInhibitAnyPolicy > 0)
+ pThis->v.cInhibitAnyPolicy--;
+ }
+
+ if ( pTbsCert->T3.pPolicyConstraints /* Step 6.1.4.j */
+ && !rtCrX509CpvSoakUpPolicyConstraints(pThis, pTbsCert->T3.pPolicyConstraints))
+ break;
+
+ if ( pTbsCert->T3.pInhibitAnyPolicy /* Step 6.1.4.j */
+ && !rtCrX509CpvSoakUpInhibitAnyPolicy(pThis, pTbsCert->T3.pInhibitAnyPolicy))
+ break;
+
+ if (!rtCrX509CpvCheckAndSoakUpBasicConstraintsAndKeyUsage(pThis, pNode, fSelfIssued)) /* Step 6.1.4.k-n */
+ break;
+
+ if (!rtCrX509CpvCheckCriticalExtensions(pThis, pNode)) /* Step 6.1.4.o */
+ break;
+
+ /*
+ * Advance to the next certificate.
+ */
+ pNode = pNode->pParent;
+ pThis->v.iNode = ++iNode;
+ }
+ AssertStmt(RT_FAILURE_NP(pThis->rc), pThis->rc = VERR_CR_X509_CERTPATHS_INTERNAL_ERROR);
+ }
+ return false;
+}
+
+
+RTDECL(int) RTCrX509CertPathsValidateOne(RTCRX509CERTPATHS hCertPaths, uint32_t iPath, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(pThis->fFlags & ~RTCRX509CERTPATHSINT_F_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pThis->pTarget, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pThis->pRoot, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->rc == VINF_SUCCESS, VERR_INVALID_PARAMETER);
+
+ /*
+ * Locate the path and validate it.
+ */
+ int rc;
+ if (iPath < pThis->cPaths)
+ {
+ PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath);
+ if (pLeaf)
+ {
+ if (RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pLeaf->uSrc))
+ {
+ pThis->pErrInfo = pErrInfo;
+ rtCrX509CpvOneWorker(pThis, pLeaf);
+ pThis->pErrInfo = NULL;
+ rc = pThis->rc;
+ pThis->rc = VINF_SUCCESS;
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_X509_NO_TRUST_ANCHOR, "Path #%u is does not have a trust anchor: uSrc=%s",
+ iPath, rtCrX509CertPathsNodeGetSourceName(pLeaf));
+ pLeaf->rcVerify = rc;
+ }
+ else
+ rc = VERR_CR_X509_CERTPATHS_INTERNAL_ERROR;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ return rc;
+}
+
+
+RTDECL(int) RTCrX509CertPathsValidateAll(RTCRX509CERTPATHS hCertPaths, uint32_t *pcValidPaths, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(pThis->fFlags & ~RTCRX509CERTPATHSINT_F_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pThis->pTarget, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pThis->pRoot, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->rc == VINF_SUCCESS, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pcValidPaths, VERR_INVALID_POINTER);
+
+ /*
+ * Validate the paths.
+ */
+ pThis->pErrInfo = pErrInfo;
+
+ int rcLastFailure = VINF_SUCCESS;
+ uint32_t cValidPaths = 0;
+ PRTCRX509CERTPATHNODE pCurLeaf;
+ RTListForEach(&pThis->LeafList, pCurLeaf, RTCRX509CERTPATHNODE, ChildListOrLeafEntry)
+ {
+ if (RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pCurLeaf->uSrc))
+ {
+ rtCrX509CpvOneWorker(hCertPaths, pCurLeaf);
+ if (RT_SUCCESS(pThis->rc))
+ cValidPaths++;
+ else
+ rcLastFailure = pThis->rc;
+ pCurLeaf->rcVerify = pThis->rc;
+ pThis->rc = VINF_SUCCESS;
+ }
+ else
+ pCurLeaf->rcVerify = VERR_CR_X509_NO_TRUST_ANCHOR;
+ }
+
+ pThis->pErrInfo = NULL;
+
+ if (pcValidPaths)
+ *pcValidPaths = cValidPaths;
+ if (cValidPaths > 0)
+ return VINF_SUCCESS;
+ if (RT_SUCCESS_NP(rcLastFailure))
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_CPV_NO_TRUSTED_PATHS,
+ "None of the %u path(s) have a trust anchor.", pThis->cPaths);
+ return rcLastFailure;
+}
+
+
+RTDECL(uint32_t) RTCrX509CertPathsGetPathCount(RTCRX509CERTPATHS hCertPaths)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, UINT32_MAX);
+ AssertPtrReturn(pThis->pRoot, UINT32_MAX);
+
+ /*
+ * Return data.
+ */
+ return pThis->cPaths;
+}
+
+
+RTDECL(int) RTCrX509CertPathsQueryPathInfo(RTCRX509CERTPATHS hCertPaths, uint32_t iPath,
+ bool *pfTrusted, uint32_t *pcNodes, PCRTCRX509NAME *ppSubject,
+ PCRTCRX509SUBJECTPUBLICKEYINFO *ppPublicKeyInfo,
+ PCRTCRX509CERTIFICATE *ppCert, PCRTCRCERTCTX *ppCertCtx,
+ int *prcVerify)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pThis->pRoot, VERR_WRONG_ORDER);
+ AssertReturn(iPath < pThis->cPaths, VERR_NOT_FOUND);
+
+ /*
+ * Get the data.
+ */
+ PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath);
+ AssertReturn(pLeaf, VERR_CR_X509_INTERNAL_ERROR);
+
+ if (pfTrusted)
+ *pfTrusted = RTCRX509CERTPATHNODE_SRC_IS_TRUSTED(pLeaf->uSrc);
+
+ if (pcNodes)
+ *pcNodes = pLeaf->uDepth + 1; /* Includes both trust anchor and target. */
+
+ if (ppSubject)
+ *ppSubject = pLeaf->pCert ? &pLeaf->pCert->TbsCertificate.Subject : &pLeaf->pCertCtx->pTaInfo->CertPath.TaName;
+
+ if (ppPublicKeyInfo)
+ *ppPublicKeyInfo = pLeaf->pCert ? &pLeaf->pCert->TbsCertificate.SubjectPublicKeyInfo : &pLeaf->pCertCtx->pTaInfo->PubKey;
+
+ if (ppCert)
+ *ppCert = pLeaf->pCert;
+
+ if (ppCertCtx)
+ {
+ if (pLeaf->pCertCtx)
+ {
+ uint32_t cRefs = RTCrCertCtxRetain(pLeaf->pCertCtx);
+ AssertReturn(cRefs != UINT32_MAX, VERR_CR_X509_INTERNAL_ERROR);
+ }
+ *ppCertCtx = pLeaf->pCertCtx;
+ }
+
+ if (prcVerify)
+ *prcVerify = pLeaf->rcVerify;
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(uint32_t) RTCrX509CertPathsGetPathLength(RTCRX509CERTPATHS hCertPaths, uint32_t iPath)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, UINT32_MAX);
+ AssertPtrReturn(pThis->pRoot, UINT32_MAX);
+ AssertReturn(iPath < pThis->cPaths, UINT32_MAX);
+
+ /*
+ * Get the data.
+ */
+ PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath);
+ AssertReturn(pLeaf, UINT32_MAX);
+ return pLeaf->uDepth + 1;
+}
+
+
+RTDECL(int) RTCrX509CertPathsGetPathVerifyResult(RTCRX509CERTPATHS hCertPaths, uint32_t iPath)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pThis->pRoot, VERR_WRONG_ORDER);
+ AssertReturn(iPath < pThis->cPaths, VERR_NOT_FOUND);
+
+ /*
+ * Get the data.
+ */
+ PRTCRX509CERTPATHNODE pLeaf = rtCrX509CertPathsGetLeafByIndex(pThis, iPath);
+ AssertReturn(pLeaf, VERR_CR_X509_INTERNAL_ERROR);
+
+ return pLeaf->rcVerify;
+}
+
+
+static PRTCRX509CERTPATHNODE rtCrX509CertPathsGetPathNodeByIndexes(PRTCRX509CERTPATHSINT pThis, uint32_t iPath, uint32_t iNode)
+{
+ PRTCRX509CERTPATHNODE pNode = rtCrX509CertPathsGetLeafByIndex(pThis, iPath);
+ Assert(pNode);
+ if (pNode)
+ {
+ if (iNode <= pNode->uDepth)
+ {
+ uint32_t uCertDepth = pNode->uDepth - iNode;
+ while (pNode->uDepth > uCertDepth)
+ pNode = pNode->pParent;
+ Assert(pNode);
+ Assert(pNode && pNode->uDepth == uCertDepth);
+ return pNode;
+ }
+ }
+
+ return NULL;
+}
+
+
+RTDECL(PCRTCRX509CERTIFICATE) RTCrX509CertPathsGetPathNodeCert(RTCRX509CERTPATHS hCertPaths, uint32_t iPath, uint32_t iNode)
+{
+ /*
+ * Validate the input.
+ */
+ PRTCRX509CERTPATHSINT pThis = hCertPaths;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTCRX509CERTPATHSINT_MAGIC, NULL);
+ AssertPtrReturn(pThis->pRoot, NULL);
+ AssertReturn(iPath < pThis->cPaths, NULL);
+
+ /*
+ * Get the data.
+ */
+ PRTCRX509CERTPATHNODE pNode = rtCrX509CertPathsGetPathNodeByIndexes(pThis, iPath, iNode);
+ if (pNode)
+ return pNode->pCert;
+ return NULL;
+}
+
+
+/** @} */
+
diff --git a/src/VBox/Runtime/common/crypto/x509-core.cpp b/src/VBox/Runtime/common/crypto/x509-core.cpp
new file mode 100644
index 00000000..7dfcfc14
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/x509-core.cpp
@@ -0,0 +1,1934 @@
+/* $Id: x509-core.cpp $ */
+/** @file
+ * IPRT - Crypto - X.509, Core APIs.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/x509.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+#include <iprt/crypto/pkix.h>
+#ifdef RT_STRICT
+# include <iprt/crypto/digest.h>
+#endif
+
+#include "x509-internal.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef RT_STRICT
+static void rtCrX509AlgorithmIdentifier_AssertTableSanityAndMore(void);
+#endif
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-core.h>
+
+
+/*
+ * X.509 Validity.
+ */
+
+RTDECL(bool) RTCrX509Validity_IsValidAtTimeSpec(PCRTCRX509VALIDITY pThis, PCRTTIMESPEC pTimeSpec)
+{
+ if (RTAsn1Time_CompareWithTimeSpec(&pThis->NotBefore, pTimeSpec) > 0)
+ return false;
+ if (RTAsn1Time_CompareWithTimeSpec(&pThis->NotAfter, pTimeSpec) < 0)
+ return false;
+ return true;
+}
+
+
+/*
+ * One X.509 Algorithm Identifier.
+ */
+
+/**
+ * String table with the encryption OIDs (used by g_aSignatureOidInfo).
+ */
+static const char * const g_apszEncryptionOids[] =
+{
+ NULL,
+#define IDX_ENCRYPTION_NIL 0
+ RTCR_X962_ECDSA_OID,
+#define IDX_ENCRYPTION_ECDSA 1
+ RTCR_PKCS1_RSA_OID,
+#define IDX_ENCRYPTION_RSA 2
+};
+
+/**
+ * Information about an algorithm identifier.
+ */
+typedef struct RTCRX509ALGORITHIDENTIFIERINTERNALINFO
+{
+ /** The signature OID. */
+ const char *pszSignatureOid;
+ /** Index into g_apszEncryptionOids of the encryption ODI.
+ * This is IDX_ENCRYPTION_NIL for hashes. */
+ uint8_t idxEncryption;
+ /** The message digest type specified by the OID.
+ * This is set to RTDIGESTTYPE_INVALID in two cases:
+ * 1. Pure encryption algorithm OID (cBitsDigest also zero).
+ * 2. The hash is so esoteric that IPRT doesn't support it. */
+ uint8_t enmDigestType;
+ /** The digest size in bits.
+ * This is ZERO if the OID does not include an hash. */
+ uint16_t cBitsDigest;
+} RTCRX509ALGORITHIDENTIFIERINTERNALINFO;
+typedef RTCRX509ALGORITHIDENTIFIERINTERNALINFO const *PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO;
+
+/**
+ * Signature to encryption OID.
+ *
+ * @note This is sorted to allow binary searching.
+ * @note This origins in pkix-utils.cpp, which is why it uses the other set of
+ * OID defines.
+ */
+static RTCRX509ALGORITHIDENTIFIERINTERNALINFO const g_aSignatureOidInfo[] =
+{
+ { /*1.0.10118.3.0.55*/ RTCRX509ALGORITHMIDENTIFIERID_WHIRLPOOL, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_INVALID, 512, },
+
+ { /*1.2.840.10045.2.1 */ RTCR_X962_ECDSA_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_INVALID, 0, },
+ { /*1.2.840.10045.4.1 */ RTCR_X962_ECDSA_WITH_SHA1_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA1, 160, },
+ { /*1.2.840.10045.4.3.1 */ RTCR_X962_ECDSA_WITH_SHA224_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA224, 224, },
+ { /*1.2.840.10045.4.3.2 */ RTCR_X962_ECDSA_WITH_SHA256_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA256, 256, },
+ { /*1.2.840.10045.4.3.3 */ RTCR_X962_ECDSA_WITH_SHA384_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA384, 384, },
+ { /*1.2.840.10045.4.3.4 */ RTCR_X962_ECDSA_WITH_SHA512_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA512, 512, },
+
+ { /*1.2.840.113549.1.1.1 */ RTCR_PKCS1_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_INVALID, 0, },
+ { /*1.2.840.113549.1.1.11*/ RTCR_PKCS1_SHA256_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA256, 256, },
+ { /*1.2.840.113549.1.1.12*/ RTCR_PKCS1_SHA384_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA384, 384, },
+ { /*1.2.840.113549.1.1.13*/ RTCR_PKCS1_SHA512_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA512, 512, },
+ { /*1.2.840.113549.1.1.14*/ RTCR_PKCS1_SHA224_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA224, 224, },
+ { /*1.2.840.113549.1.1.15*/ RTCR_PKCS1_SHA512T224_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA512T224, 224, },
+ { /*1.2.840.113549.1.1.16*/ RTCR_PKCS1_SHA512T256_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA512T256, 256, },
+ { /*1.2.840.113549.1.1.2*/ RTCR_PKCS1_MD2_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_MD2, 128, },
+ { /*1.2.840.113549.1.1.3*/ RTCR_PKCS1_MD4_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_MD4, 128, },
+ { /*1.2.840.113549.1.1.4*/ RTCR_PKCS1_MD5_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_MD5, 128, },
+ { /*1.2.840.113549.1.1.5*/ RTCR_PKCS1_SHA1_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA1, 160, },
+
+ { /*1.2.840.113549.2.2*/ RTCRX509ALGORITHMIDENTIFIERID_MD2, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_MD2, 128, },
+ { /*1.2.840.113549.2.4*/ RTCRX509ALGORITHMIDENTIFIERID_MD4, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_MD4, 128, },
+ { /*1.2.840.113549.2.5*/ RTCRX509ALGORITHMIDENTIFIERID_MD5, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_MD5, 128, },
+
+ /* oddballs for which we don't support the padding (skip?): */
+ //{ "1.3.14.3.2.11" /*rsaSignature*/, IDX_ENCRYPTION_RSA/*?*/, RTDIGESTTYPE_INVALID, 0, },
+ { "1.3.14.3.2.14" /*mdc2WithRSASignature w/ 9796-2 padding*/, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_INVALID, 0, },
+ //{ "1.3.14.3.2.15" /*sha0WithRSASignature w/ 9796-2 padding*/, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_INVALID, 160, },
+ { "1.3.14.3.2.24" /*md2WithRSASignature w/ 9796-2 padding*/, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_MD2, 128, },
+ { "1.3.14.3.2.25" /*md5WithRSASignature w/ 9796-2 padding*/, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_MD5, 128, },
+ { /*1.3.14.3.2.26*/ RTCRX509ALGORITHMIDENTIFIERID_SHA1, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA1, 160, },
+ { "1.3.14.3.2.29" /*sha1WithRSAEncryption (obsolete?)*/, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA1, 160, },
+
+ { /*2.16.840.1.101.3.4.2.1*/ RTCRX509ALGORITHMIDENTIFIERID_SHA256, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA256, 256, },
+ { /*2.16.840.1.101.3.4.2.10*/ RTCRX509ALGORITHMIDENTIFIERID_SHA3_512, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA3_512, 512, },
+ { /*2.16.840.1.101.3.4.2.2*/ RTCRX509ALGORITHMIDENTIFIERID_SHA384, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA384, 384, },
+ { /*2.16.840.1.101.3.4.2.3*/ RTCRX509ALGORITHMIDENTIFIERID_SHA512, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA512, 512, },
+ { /*2.16.840.1.101.3.4.2.4*/ RTCRX509ALGORITHMIDENTIFIERID_SHA224, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA224, 224, },
+ { /*2.16.840.1.101.3.4.2.5*/ RTCRX509ALGORITHMIDENTIFIERID_SHA512T224,IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA512T224, 224, },
+ { /*2.16.840.1.101.3.4.2.6*/ RTCRX509ALGORITHMIDENTIFIERID_SHA512T256,IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA512T256, 256, },
+ { /*2.16.840.1.101.3.4.2.7*/ RTCRX509ALGORITHMIDENTIFIERID_SHA3_224, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA3_224, 224, },
+ { /*2.16.840.1.101.3.4.2.8*/ RTCRX509ALGORITHMIDENTIFIERID_SHA3_256, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA3_256, 256, },
+ { /*2.16.840.1.101.3.4.2.9*/ RTCRX509ALGORITHMIDENTIFIERID_SHA3_384, IDX_ENCRYPTION_NIL, RTDIGESTTYPE_SHA3_384, 384, },
+
+ { /*2.16.840.1.101.3.4.3.10*/ RTCR_NIST_SHA3_256_WITH_ECDSA_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA3_256, 256, },
+ { /*2.16.840.1.101.3.4.3.11*/ RTCR_NIST_SHA3_384_WITH_ECDSA_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA3_384, 384, },
+ { /*2.16.840.1.101.3.4.3.12*/ RTCR_NIST_SHA3_512_WITH_ECDSA_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA3_512, 512, },
+ { /*2.16.840.1.101.3.4.3.13*/ RTCR_NIST_SHA3_224_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA3_224, 224, },
+ { /*2.16.840.1.101.3.4.3.14*/ RTCR_NIST_SHA3_256_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA3_256, 256, },
+ { /*2.16.840.1.101.3.4.3.15*/ RTCR_NIST_SHA3_384_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA3_384, 384, },
+ { /*2.16.840.1.101.3.4.3.16*/ RTCR_NIST_SHA3_512_WITH_RSA_OID, IDX_ENCRYPTION_RSA, RTDIGESTTYPE_SHA3_512, 512, },
+ { /*2.16.840.1.101.3.4.3.9*/ RTCR_NIST_SHA3_224_WITH_ECDSA_OID, IDX_ENCRYPTION_ECDSA, RTDIGESTTYPE_SHA3_224, 224, },
+};
+
+/**
+ * Encryption and digest combining.
+ *
+ * This is a subset of g_aSignatureOidInfo.
+ * @todo Organize this more efficiently...
+ */
+typedef struct RTCRX509ALGORITHIDENTIFIERCOMBINING
+{
+ const char *pszDigestOid;
+ const char *pszEncryptedDigestOid;
+} RTCRX509ALGORITHIDENTIFIERCOMBINING;
+typedef RTCRX509ALGORITHIDENTIFIERCOMBINING const *PCRTCRX509ALGORITHIDENTIFIERCOMBINING;
+
+#undef MY_COMBINE
+#define MY_COMBINE(a_Encrypt, a_Digest) \
+ { RTCRX509ALGORITHMIDENTIFIERID_ ## a_Digest, \
+ RTCRX509ALGORITHMIDENTIFIERID_ ## a_Digest ## _WITH_ ## a_Encrypt }
+
+/** Digest and encryption combinations for ECDSA. */
+static RTCRX509ALGORITHIDENTIFIERCOMBINING const g_aDigestAndEncryptionEcdsa[] =
+{
+ MY_COMBINE(ECDSA, SHA1),
+ MY_COMBINE(ECDSA, SHA224),
+ MY_COMBINE(ECDSA, SHA256),
+ MY_COMBINE(ECDSA, SHA384),
+ MY_COMBINE(ECDSA, SHA512),
+ MY_COMBINE(ECDSA, SHA3_224),
+ MY_COMBINE(ECDSA, SHA3_256),
+ MY_COMBINE(ECDSA, SHA3_384),
+ MY_COMBINE(ECDSA, SHA3_512),
+};
+
+/** Digest and encryption combinations for RSA. */
+static RTCRX509ALGORITHIDENTIFIERCOMBINING const g_aDigestAndEncryptionRsa[] =
+{
+ MY_COMBINE(RSA, SHA1),
+ MY_COMBINE(RSA, SHA256),
+ MY_COMBINE(RSA, SHA512),
+ MY_COMBINE(RSA, SHA384),
+ MY_COMBINE(RSA, MD5),
+ MY_COMBINE(RSA, MD2),
+ MY_COMBINE(RSA, MD4),
+ MY_COMBINE(RSA, SHA224),
+ MY_COMBINE(RSA, SHA512T224),
+ MY_COMBINE(RSA, SHA512T256),
+ MY_COMBINE(RSA, SHA3_224),
+ MY_COMBINE(RSA, SHA3_256),
+ MY_COMBINE(RSA, SHA3_384),
+ MY_COMBINE(RSA, SHA3_512),
+};
+
+#undef MY_COMBINE
+
+/**
+ * Table running parallel to g_apszEncryptionOids.
+ */
+static struct
+{
+ PCRTCRX509ALGORITHIDENTIFIERCOMBINING paCombinations;
+ size_t cCombinations;
+} const g_aDigestAndEncryption[] =
+{
+ /* [IDX_ENCRYPTION_NIL] = */ { NULL, 0 },
+ /* [IDX_ENCRYPTION_ECDSA] = */ { &g_aDigestAndEncryptionEcdsa[0], RT_ELEMENTS(g_aDigestAndEncryptionEcdsa) },
+ /* [IDX_ENCRYPTION_RSA] = */ { &g_aDigestAndEncryptionRsa[0], RT_ELEMENTS(g_aDigestAndEncryptionRsa) },
+};
+AssertCompile(IDX_ENCRYPTION_NIL == 0 && IDX_ENCRYPTION_ECDSA == 1 && IDX_ENCRYPTION_RSA == 2);
+
+
+/**
+ * Looks up info we've got on a algorithm identifier.
+ */
+static PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO rtCrX509AlgorithmIdentifier_LookupInfoByOid(const char *pszSignatureOid)
+{
+#ifdef RT_STRICT
+ /*
+ * Do internal santiy checking on first call.
+ */
+ static bool volatile s_fChecked = false;
+ if (RT_LIKELY(s_fChecked))
+ { /* likely */ }
+ else
+ {
+ s_fChecked = true; /* Must be set before the call, as the callee will calls us again. */
+ rtCrX509AlgorithmIdentifier_AssertTableSanityAndMore();
+ }
+#endif
+
+ /*
+ * Do a binary search of g_aSignatureOidInfo.
+ */
+ size_t iFirst = 0;
+ size_t iEnd = RT_ELEMENTS(g_aSignatureOidInfo);
+ for (;;)
+ {
+ size_t const i = iFirst + (iEnd - iFirst) / 2;
+ int const iDiff = strcmp(pszSignatureOid, g_aSignatureOidInfo[i].pszSignatureOid);
+ if (iDiff < 0)
+ {
+ if (i > iFirst)
+ iEnd = i;
+ else
+ return NULL;
+ }
+ else if (iDiff > 0)
+ {
+ if (i + 1 < iEnd)
+ iFirst = i + 1;
+ else
+ return NULL;
+ }
+ else
+ return &g_aSignatureOidInfo[i];
+ }
+}
+
+#ifdef RT_STRICT
+/**
+ * Check that the g_aSignatureOidInfo and g_aDigestAndEncryption makes sense and
+ * matches up with one another and other IPRT information sources.
+ */
+static void rtCrX509AlgorithmIdentifier_AssertTableSanityAndMore(void)
+{
+ /* Check that binary searching work and that digest info matches up: */
+ for (size_t i = 1; i < RT_ELEMENTS(g_aSignatureOidInfo); i++)
+ Assert(strcmp(g_aSignatureOidInfo[i].pszSignatureOid, g_aSignatureOidInfo[i - 1].pszSignatureOid) > 0);
+ for (size_t i = 0; i < RT_ELEMENTS(g_aSignatureOidInfo); i++)
+ {
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo
+ = rtCrX509AlgorithmIdentifier_LookupInfoByOid(g_aSignatureOidInfo[i].pszSignatureOid);
+ Assert(pInfo && pInfo->pszSignatureOid == g_aSignatureOidInfo[i].pszSignatureOid);
+
+ /* If the digest type is RTDIGESTTYPE_INVALID, we must have an pure encryption entry or an obscure hash function. */
+ if (g_aSignatureOidInfo[i].enmDigestType != RTDIGESTTYPE_INVALID)
+ Assert( RTCrDigestTypeToHashSize((RTDIGESTTYPE)g_aSignatureOidInfo[i].enmDigestType) * 8
+ == g_aSignatureOidInfo[i].cBitsDigest);
+ else
+ Assert(g_aSignatureOidInfo[i].cBitsDigest == 0 || g_aSignatureOidInfo[i].idxEncryption == IDX_ENCRYPTION_NIL);
+
+# ifdef IN_RING3
+ /* Check with the RTCrDigestFindByObjIdString API: */
+ RTDIGESTTYPE enmDigestType2 = (RTDIGESTTYPE)g_aSignatureOidInfo[i].enmDigestType;
+# if defined(IPRT_WITHOUT_DIGEST_MD2) || defined(IPRT_WITHOUT_DIGEST_MD4) || defined(IPRT_WITHOUT_DIGEST_MD5) \
+|| defined(IPRT_WITHOUT_SHA512T224) || defined(IPRT_WITHOUT_SHA512T256) || defined(IPRT_WITHOUT_SHA3)
+ switch (enmDigestType2)
+ {
+ default: break;
+# ifdef IPRT_WITHOUT_DIGEST_MD2
+ case RTDIGESTTYPE_MD2:
+# endif
+# ifdef IPRT_WITHOUT_DIGEST_MD4
+ case RTDIGESTTYPE_MD4:
+# endif
+# ifdef IPRT_WITHOUT_DIGEST_MD5
+ case RTDIGESTTYPE_MD5:
+# endif
+# ifdef IPRT_WITHOUT_SHA512T224
+ case RTDIGESTTYPE_SHA512T224:
+# endif
+# ifdef IPRT_WITHOUT_SHA512T256
+ case RTDIGESTTYPE_SHA512T256:
+# endif
+# ifdef IPRT_WITHOUT_SHA3
+ case RTDIGESTTYPE_SHA3_224:
+ case RTDIGESTTYPE_SHA3_256:
+ case RTDIGESTTYPE_SHA3_384:
+ case RTDIGESTTYPE_SHA3_512:
+# endif
+ enmDigestType2 = RTDIGESTTYPE_INVALID;
+ break;
+ }
+# endif
+ PCRTCRDIGESTDESC const pDigestDesc = RTCrDigestFindByObjIdString(g_aSignatureOidInfo[i].pszSignatureOid,
+ NULL /*ppvOpaque*/);
+ if (pDigestDesc)
+ {
+ AssertMsg(pDigestDesc->enmType == enmDigestType2,
+ ("%s pDigestDesc=%s enmDigestType2=%s\n", g_aSignatureOidInfo[i].pszSignatureOid,
+ RTCrDigestTypeToName(pDigestDesc->enmType), RTCrDigestTypeToName(enmDigestType2)));
+ Assert(pDigestDesc->cbHash * 8 == g_aSignatureOidInfo[i].cBitsDigest);
+ }
+ else
+ AssertMsg(enmDigestType2 == RTDIGESTTYPE_INVALID,
+ ("%s enmDigestType2=%s\n", g_aSignatureOidInfo[i].pszSignatureOid, RTCrDigestTypeToName(enmDigestType2)));
+# endif /* IN_RING3 */
+
+
+# ifdef IN_RING3
+ /* Look it up the encryption descriptor. */
+ const char * const pszCheckEncryptId = g_apszEncryptionOids[g_aSignatureOidInfo[i].idxEncryption];
+ PCRTCRPKIXSIGNATUREDESC const pSigDesc = RTCrPkixSignatureFindByObjIdString(g_aSignatureOidInfo[i].pszSignatureOid,
+ NULL /*ppvOpaque*/);
+ if (pSigDesc)
+ Assert(pszCheckEncryptId && strcmp(pSigDesc->pszObjId, pszCheckEncryptId) == 0);
+# ifdef IPRT_WITH_OPENSSL /* No ECDSA implementation w/o OpenSSL at the moment. */
+ else
+ AssertMsg(!pSigDesc && pInfo->idxEncryption == IDX_ENCRYPTION_NIL, ("%s\n", g_aSignatureOidInfo[i].pszSignatureOid));
+# endif
+# endif /* IN_RING3 */
+ }
+
+ /*
+ * Check that everything in g_aDigestAndEncryption is resolvable here and that the info matches up.
+ */
+ for (size_t idxEncryption = IDX_ENCRYPTION_NIL; idxEncryption < RT_ELEMENTS(g_aDigestAndEncryption); idxEncryption++)
+ {
+ PCRTCRX509ALGORITHIDENTIFIERCOMBINING const paCombinations = g_aDigestAndEncryption[idxEncryption].paCombinations;
+ size_t const cCombinations = g_aDigestAndEncryption[idxEncryption].cCombinations;
+ for (size_t i = 0; i < cCombinations; i++)
+ {
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo
+ = rtCrX509AlgorithmIdentifier_LookupInfoByOid(paCombinations[i].pszEncryptedDigestOid);
+ AssertContinue(pInfo);
+ Assert(strcmp(paCombinations[i].pszEncryptedDigestOid, pInfo->pszSignatureOid) == 0);
+ Assert(pInfo->idxEncryption == idxEncryption);
+ Assert(strcmp(paCombinations[i].pszDigestOid,
+ RTCrDigestTypeToAlgorithmOid((RTDIGESTTYPE)pInfo->enmDigestType)) == 0);
+ }
+ }
+}
+#endif /* RT_STRICT */
+
+
+RTDECL(RTDIGESTTYPE) RTCrX509AlgorithmIdentifier_GetDigestType(PCRTCRX509ALGORITHMIDENTIFIER pThis, bool fPureDigestsOnly)
+{
+ AssertPtrReturn(pThis, RTDIGESTTYPE_INVALID);
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pThis->Algorithm.szObjId);
+ return pInfo && (!fPureDigestsOnly || pInfo->idxEncryption == IDX_ENCRYPTION_NIL)
+ ? (RTDIGESTTYPE)pInfo->enmDigestType : RTDIGESTTYPE_INVALID;
+}
+
+
+RTDECL(uint32_t) RTCrX509AlgorithmIdentifier_GetDigestSize(PCRTCRX509ALGORITHMIDENTIFIER pThis, bool fPureDigestsOnly)
+{
+ AssertPtrReturn(pThis, UINT32_MAX);
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pThis->Algorithm.szObjId);
+ return pInfo && (!fPureDigestsOnly || pInfo->idxEncryption == IDX_ENCRYPTION_NIL)
+ ? pInfo->cBitsDigest / 8 : UINT32_MAX;
+}
+
+
+RTDECL(int) RTCrX509AlgorithmIdentifier_CompareWithString(PCRTCRX509ALGORITHMIDENTIFIER pThis, const char *pszObjId)
+{
+ return strcmp(pThis->Algorithm.szObjId, pszObjId);
+}
+
+
+RTDECL(int) RTCrX509AlgorithmIdentifier_CompareDigestOidAndEncryptedDigestOid(const char *pszDigestOid,
+ const char *pszEncryptedDigestOid)
+{
+ /*
+ * Lookup the digest and encrypted digest OIDs.
+ */
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pDigest = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszDigestOid);
+ AssertMsgReturn(pDigest, ("pszDigestOid=%s\n", pszDigestOid), -1);
+ AssertMsgReturn(pDigest->idxEncryption == IDX_ENCRYPTION_NIL, ("pszDigestOid=%s\n", pszDigestOid), -1);
+ AssertMsgReturn(pDigest->cBitsDigest != 0, ("pszDigestOid=%s\n", pszDigestOid), -1);
+
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pEncrypt = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszEncryptedDigestOid);
+ AssertMsgReturn(pEncrypt, ("pszEncryptedDigestOid=%s\n", pszEncryptedDigestOid), 1);
+ AssertMsgReturn(pEncrypt->idxEncryption != IDX_ENCRYPTION_NIL, ("pszEncryptedDigestOid=%s\n", pszEncryptedDigestOid), 1);
+ AssertMsgReturn(pEncrypt->enmDigestType != RTDIGESTTYPE_INVALID, ("pszEncryptedDigestOid=%s\n", pszEncryptedDigestOid), 1);
+
+ return pDigest->enmDigestType == pEncrypt->enmDigestType ? 0 : 1;
+}
+
+RTDECL(int) RTCrX509AlgorithmIdentifier_CompareDigestAndEncryptedDigest(PCRTCRX509ALGORITHMIDENTIFIER pDigest,
+ PCRTCRX509ALGORITHMIDENTIFIER pEncryptedDigest)
+{
+ return RTCrX509AlgorithmIdentifier_CompareDigestOidAndEncryptedDigestOid(pDigest->Algorithm.szObjId,
+ pEncryptedDigest->Algorithm.szObjId);
+}
+
+
+RTDECL(const char *) RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(const char *pszEncryptionOid,
+ const char *pszDigestOid)
+{
+ /*
+ * We can look up the two OIDs and see what they actually are.
+ */
+ /* The digest OID should be a pure hash algorithm, however we also accept
+ the already combined algorithm. */
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pDigest = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszDigestOid);
+ AssertReturn(pDigest, NULL);
+ AssertReturn(pDigest->enmDigestType != RTDIGESTTYPE_INVALID, NULL);
+
+ /* The encryption OID should be a pure encryption algorithm, however we
+ also accept the already combined algorithm. */
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pEncrypt = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszEncryptionOid);
+ AssertReturn(pEncrypt, NULL);
+ uint8_t const idxEncryption = pEncrypt->idxEncryption;
+ AssertReturn(idxEncryption != IDX_ENCRYPTION_NIL, NULL);
+ Assert(idxEncryption < RT_ELEMENTS(g_aDigestAndEncryption));
+
+ /* Is the encryption OID purely encryption? */
+ if (pEncrypt->cBitsDigest == 0)
+ {
+ Assert(pEncrypt->enmDigestType == RTDIGESTTYPE_INVALID);
+
+ /* Identify the slice of the table related to this encryption OID: */
+ PCRTCRX509ALGORITHIDENTIFIERCOMBINING const paCombinations = g_aDigestAndEncryption[idxEncryption].paCombinations;
+ size_t const cCombinations = g_aDigestAndEncryption[idxEncryption].cCombinations;
+
+ /* Is the digest OID purely a digest? */
+ if (pDigest->idxEncryption == IDX_ENCRYPTION_NIL)
+ {
+ for (size_t i = 0; i < cCombinations; i++)
+ if (!strcmp(pszDigestOid, paCombinations[i].pszDigestOid))
+ return paCombinations[i].pszEncryptedDigestOid;
+ AssertMsgFailed(("enc=%s hash=%s\n", pszEncryptionOid, pszDigestOid));
+ }
+ else
+ {
+ /* No, it's a combined one. */
+ for (size_t i = 0; i < cCombinations; i++)
+ if (!strcmp(pszDigestOid, paCombinations[i].pszEncryptedDigestOid))
+ return paCombinations[i].pszEncryptedDigestOid;
+ AssertMsgFailed(("enc=%s hash+enc=%s\n", pszEncryptionOid, pszDigestOid));
+ }
+ }
+ /* The digest OID purely a digest? */
+ else if (pDigest->idxEncryption == IDX_ENCRYPTION_NIL)
+ {
+ /* Check that it's for the same hash before returning it. */
+ Assert(pEncrypt->enmDigestType != RTDIGESTTYPE_INVALID);
+ if (pEncrypt->enmDigestType == pDigest->enmDigestType)
+ return pEncrypt->pszSignatureOid;
+ AssertMsgFailed(("enc+hash=%s hash=%s\n", pszEncryptionOid, pszDigestOid));
+ }
+ /* Both the digest and encryption OIDs are combined ones, so they have to
+ be the same entry then or they cannot be combined. */
+ else if (pDigest == pEncrypt)
+ return pEncrypt->pszSignatureOid;
+ else
+ AssertMsgFailed(("enc+hash=%s hash+enc=%s\n", pszEncryptionOid, pszDigestOid));
+
+ return NULL;
+}
+
+
+RTDECL(const char *) RTCrX509AlgorithmIdentifier_CombineEncryptionAndDigest(PCRTCRX509ALGORITHMIDENTIFIER pEncryption,
+ PCRTCRX509ALGORITHMIDENTIFIER pDigest)
+{
+ return RTCrX509AlgorithmIdentifier_CombineEncryptionOidAndDigestOid(pEncryption->Algorithm.szObjId,
+ pDigest->Algorithm.szObjId);
+}
+
+
+RTDECL(const char *) RTCrX509AlgorithmIdentifier_GetEncryptionOid(PCRTCRX509ALGORITHMIDENTIFIER pThis, bool fMustIncludeHash)
+{
+ AssertPtrReturn(pThis, NULL);
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pThis->Algorithm.szObjId);
+ if (pInfo && (!fMustIncludeHash || pInfo->enmDigestType != RTDIGESTTYPE_INVALID))
+ return g_apszEncryptionOids[pInfo->idxEncryption];
+ return NULL;
+}
+
+
+RTDECL(const char *) RTCrX509AlgorithmIdentifier_GetEncryptionOidFromOid(const char *pszAlgorithmOid, bool fMustIncludeHash)
+{
+ AssertPtrReturn(pszAlgorithmOid, NULL);
+ PCRTCRX509ALGORITHIDENTIFIERINTERNALINFO const pInfo = rtCrX509AlgorithmIdentifier_LookupInfoByOid(pszAlgorithmOid);
+ if (pInfo && (!fMustIncludeHash || pInfo->enmDigestType != RTDIGESTTYPE_INVALID))
+ return g_apszEncryptionOids[pInfo->idxEncryption];
+ return NULL;
+}
+
+
+/*
+ * Set of X.509 Algorithm Identifiers.
+ */
+
+
+/*
+ * One X.509 AttributeTypeAndValue.
+ */
+
+
+/*
+ * Set of X.509 AttributeTypeAndValues / X.509 RelativeDistinguishedName.
+ */
+
+/**
+ * Slow code path of rtCrX509CanNameIsNothing.
+ *
+ * @returns true if @uc maps to nothing, false if not.
+ * @param uc The unicode code point.
+ */
+static bool rtCrX509CanNameIsNothingSlow(RTUNICP uc)
+{
+ switch (uc)
+ {
+ /* 2.2 Map - Paragraph 1: */
+ case 0x00ad:
+ case 0x1806:
+ case 0x034f:
+ case 0x180b: case 0x180c: case 0x180d:
+
+ case 0xfe00: case 0xfe01: case 0xfe02: case 0xfe03:
+ case 0xfe04: case 0xfe05: case 0xfe06: case 0xfe07:
+ case 0xfe08: case 0xfe09: case 0xfe0a: case 0xfe0b:
+ case 0xfe0c: case 0xfe0d: case 0xfe0e: case 0xfe0f:
+
+ case 0xfffc:
+
+ /* 2.2 Map - Paragraph 3 (control code/function): */
+ case 0x0000: case 0x0001: case 0x0002: case 0x0003:
+ case 0x0004: case 0x0005: case 0x0006: case 0x0007:
+ case 0x0008:
+
+ case 0x000e: case 0x000f:
+ case 0x0010: case 0x0011: case 0x0012: case 0x0013:
+ case 0x0014: case 0x0015: case 0x0016: case 0x0017:
+ case 0x0018: case 0x0019: case 0x001a: case 0x001b:
+ case 0x001c: case 0x001d: case 0x001e: case 0x001f:
+
+ case 0x007f:
+ case 0x0080: case 0x0081: case 0x0082: case 0x0083:
+ case 0x0084: /*case 0x0085:*/ case 0x0086: case 0x0087:
+ case 0x0088: case 0x0089: case 0x008a: case 0x008b:
+ case 0x008c: case 0x008d: case 0x008e: case 0x008f:
+ case 0x0090: case 0x0091: case 0x0092: case 0x0093:
+ case 0x0094: case 0x0095: case 0x0096: case 0x0097:
+ case 0x0098: case 0x0099: case 0x009a: case 0x009b:
+ case 0x009c: case 0x009d: case 0x009e: case 0x009f:
+
+ case 0x06dd:
+ case 0x070f:
+ case 0x180e:
+ case 0x200c: case 0x200d: case 0x200e: case 0x200f:
+ case 0x202a: case 0x202b: case 0x202c: case 0x202d: case 0x202e:
+ case 0x2060: case 0x2061: case 0x2062: case 0x2063:
+ case 0x206a: case 0x206b: case 0x206c: case 0x206d: case 0x206e: case 0x206f:
+ case 0xfeff:
+ case 0xfff9: case 0xfffa: case 0xfffb:
+ case 0x1d173: case 0x1d174: case 0x1d175: case 0x1d176: case 0x1d177: case 0x1d178: case 0x1d179: case 0x1d17a:
+ case 0xe0001:
+ case 0xe0020: case 0xe0021: case 0xe0022: case 0xe0023:
+ case 0xe0024: case 0xe0025: case 0xe0026: case 0xe0027:
+ case 0xe0028: case 0xe0029: case 0xe002a: case 0xe002b:
+ case 0xe002c: case 0xe002d: case 0xe002e: case 0xe002f:
+ case 0xe0030: case 0xe0031: case 0xe0032: case 0xe0033:
+ case 0xe0034: case 0xe0035: case 0xe0036: case 0xe0037:
+ case 0xe0038: case 0xe0039: case 0xe003a: case 0xe003b:
+ case 0xe003c: case 0xe003d: case 0xe003e: case 0xe003f:
+ case 0xe0040: case 0xe0041: case 0xe0042: case 0xe0043:
+ case 0xe0044: case 0xe0045: case 0xe0046: case 0xe0047:
+ case 0xe0048: case 0xe0049: case 0xe004a: case 0xe004b:
+ case 0xe004c: case 0xe004d: case 0xe004e: case 0xe004f:
+ case 0xe0050: case 0xe0051: case 0xe0052: case 0xe0053:
+ case 0xe0054: case 0xe0055: case 0xe0056: case 0xe0057:
+ case 0xe0058: case 0xe0059: case 0xe005a: case 0xe005b:
+ case 0xe005c: case 0xe005d: case 0xe005e: case 0xe005f:
+ case 0xe0060: case 0xe0061: case 0xe0062: case 0xe0063:
+ case 0xe0064: case 0xe0065: case 0xe0066: case 0xe0067:
+ case 0xe0068: case 0xe0069: case 0xe006a: case 0xe006b:
+ case 0xe006c: case 0xe006d: case 0xe006e: case 0xe006f:
+ case 0xe0070: case 0xe0071: case 0xe0072: case 0xe0073:
+ case 0xe0074: case 0xe0075: case 0xe0076: case 0xe0077:
+ case 0xe0078: case 0xe0079: case 0xe007a: case 0xe007b:
+ case 0xe007c: case 0xe007d: case 0xe007e: case 0xe007f:
+
+ /* 2.2 Map - Paragraph 4. */
+ case 0x200b:
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Checks if @a uc maps to nothing according to mapping rules of RFC-5280 and
+ * RFC-4518.
+ *
+ * @returns true if @uc maps to nothing, false if not.
+ * @param uc The unicode code point.
+ */
+DECLINLINE(bool) rtCrX509CanNameIsNothing(RTUNICP uc)
+{
+ if (uc > 0x001f && uc < 0x00ad)
+ return false;
+ return rtCrX509CanNameIsNothingSlow(uc);
+}
+
+
+/**
+ * Slow code path of rtCrX509CanNameIsSpace.
+ *
+ * @returns true if space, false if not.
+ * @param uc The unicode code point.
+ */
+static bool rtCrX509CanNameIsSpaceSlow(RTUNICP uc)
+{
+ switch (uc)
+ {
+ /* 2.2 Map - Paragraph 2. */
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ case 0x20:
+ case 0x0085:
+ case 0x00a0:
+ case 0x1680:
+ case 0x2000: case 0x2001: case 0x2002: case 0x2003:
+ case 0x2004: case 0x2005: case 0x2006: case 0x2007:
+ case 0x2008: case 0x2009: case 0x200a:
+ case 0x2028: case 0x2029:
+ case 0x202f:
+ case 0x205f:
+ case 0x3000:
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Checks if @a uc is a space character according to the mapping rules of
+ * RFC-5280 and RFC-4518.
+ *
+ * @returns true if space, false if not.
+ * @param uc The unicode code point.
+ */
+DECLINLINE(bool) rtCrX509CanNameIsSpace(RTUNICP uc)
+{
+ if (uc < 0x0085)
+ {
+ if (uc > 0x0020)
+ return false;
+ if (uc == 0x0020) /* space */
+ return true;
+ }
+ return rtCrX509CanNameIsSpaceSlow(uc);
+}
+
+
+static const char *rtCrX509CanNameStripLeft(const char *psz, size_t *pcch)
+{
+ /*
+ * Return space when we've encountered the first non-space-non-nothing code point.
+ */
+ const char * const pszStart = psz;
+ const char *pszPrev;
+ for (;;)
+ {
+ pszPrev = psz;
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&psz, &uc);
+ AssertRCBreak(rc);
+ if (!uc)
+ {
+ if ((uintptr_t)(pszPrev - pszStart) >= *pcch)
+ break;
+ /* NUL inside the string, maps to nothing => ignore it. */
+ }
+ else if (!rtCrX509CanNameIsSpace(uc) && !rtCrX509CanNameIsNothing(uc))
+ break;
+ }
+ *pcch -= (size_t)(pszPrev - pszStart);
+ return pszPrev;
+}
+
+
+static RTUNICP rtCrX509CanNameGetNextCpWithMappingSlowSpace(const char **ppsz, size_t *pcch)
+{
+ /*
+ * Return space when we've encountered the first non-space-non-nothing code point.
+ */
+ RTUNICP uc;
+ const char *psz = *ppsz;
+ const char * const pszStart = psz;
+ const char *pszPrev;
+ for (;;)
+ {
+ pszPrev = psz;
+ int rc = RTStrGetCpEx(&psz, &uc);
+ AssertRCBreakStmt(rc, uc = 0x20);
+ if (!uc)
+ {
+ if ((uintptr_t)(pszPrev - pszStart) >= *pcch)
+ {
+ uc = 0; /* End of string: Ignore trailing spaces. */
+ break;
+ }
+ /* NUL inside the string, maps to nothing => ignore it. */
+ }
+ else if (!rtCrX509CanNameIsSpace(uc) && !rtCrX509CanNameIsNothing(uc))
+ {
+ uc = 0x20; /* Return space before current char. */
+ break;
+ }
+ }
+
+ *ppsz = pszPrev;
+ *pcch -= (size_t)(pszPrev - pszStart);
+ return uc;
+}
+
+
+DECLINLINE(RTUNICP) rtCrX509CanNameGetNextCpIgnoreNul(const char **ppsz, size_t *pcch)
+{
+ while (*pcch > 0)
+ {
+ const char *psz = *ppsz;
+ RTUNICP uc = (RTUNICP)*psz;
+ if (uc < 0x80)
+ {
+ *pcch -= 1;
+ *ppsz = psz + 1;
+ }
+ else
+ {
+ int rc = RTStrGetCpEx(ppsz, &uc);
+ AssertRCReturn(rc, uc);
+ size_t cchCp = (size_t)(*ppsz - psz);
+ AssertReturn(cchCp <= *pcch, 0);
+ *pcch -= cchCp;
+ }
+ if (uc != 0)
+ return uc;
+ }
+ return 0;
+}
+
+
+static RTUNICP rtCrX509CanNameGetNextCpWithMappingSlowNothing(const char **ppsz, size_t *pcch)
+{
+ /*
+ * Return first code point which doesn't map to nothing. If we encounter
+ * a space, we defer to the mapping-after-space routine above.
+ */
+ for (;;)
+ {
+ RTUNICP uc = rtCrX509CanNameGetNextCpIgnoreNul(ppsz, pcch);
+ if (rtCrX509CanNameIsSpace(uc))
+ return rtCrX509CanNameGetNextCpWithMappingSlowSpace(ppsz, pcch);
+ if (!rtCrX509CanNameIsNothing(uc) || uc == 0)
+ return uc;
+ }
+}
+
+
+DECLINLINE(RTUNICP) rtCrX509CanNameGetNextCpWithMapping(const char **ppsz, size_t *pcch)
+{
+ RTUNICP uc = rtCrX509CanNameGetNextCpIgnoreNul(ppsz, pcch);
+ if (uc)
+ {
+ if (!rtCrX509CanNameIsSpace(uc))
+ {
+ if (!rtCrX509CanNameIsNothing(uc))
+ return uc;
+ return rtCrX509CanNameGetNextCpWithMappingSlowNothing(ppsz, pcch);
+ }
+ return rtCrX509CanNameGetNextCpWithMappingSlowSpace(ppsz, pcch);
+ }
+ return uc;
+}
+
+
+RTDECL(bool) RTCrX509AttributeTypeAndValue_MatchAsRdnByRfc5280(PCRTCRX509ATTRIBUTETYPEANDVALUE pLeft,
+ PCRTCRX509ATTRIBUTETYPEANDVALUE pRight)
+{
+ if (RTAsn1ObjId_Compare(&pLeft->Type, &pRight->Type) == 0)
+ {
+ /*
+ * Try for perfect match in case we get luck.
+ */
+#ifdef DEBUG_bird /* Want to test the complicated code path first */
+ if (pLeft->Value.enmType != RTASN1TYPE_STRING || pRight->Value.enmType != RTASN1TYPE_STRING)
+#endif
+ if (RTAsn1DynType_Compare(&pLeft->Value, &pRight->Value) == 0)
+ return true;
+
+ /*
+ * If both are string types, we can compare them according to RFC-5280.
+ */
+ if ( pLeft->Value.enmType == RTASN1TYPE_STRING
+ && pRight->Value.enmType == RTASN1TYPE_STRING)
+ {
+ size_t cchLeft;
+ const char *pszLeft;
+ int rc = RTAsn1String_QueryUtf8(&pLeft->Value.u.String, &pszLeft, &cchLeft);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchRight;
+ const char *pszRight;
+ rc = RTAsn1String_QueryUtf8(&pRight->Value.u.String, &pszRight, &cchRight);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Perform a simplified RFC-5280 comparsion.
+ * The algorithm as be relaxed on the following counts:
+ * 1. No unicode normalization.
+ * 2. Prohibited characters not checked for.
+ * 3. Bidirectional characters are not ignored.
+ */
+ pszLeft = rtCrX509CanNameStripLeft(pszLeft, &cchLeft);
+ pszRight = rtCrX509CanNameStripLeft(pszRight, &cchRight);
+ while (*pszLeft && *pszRight)
+ {
+ RTUNICP ucLeft = rtCrX509CanNameGetNextCpWithMapping(&pszLeft, &cchLeft);
+ RTUNICP ucRight = rtCrX509CanNameGetNextCpWithMapping(&pszRight, &cchRight);
+ if (ucLeft != ucRight)
+ {
+ ucLeft = RTUniCpToLower(ucLeft);
+ ucRight = RTUniCpToLower(ucRight);
+ if (ucLeft != ucRight)
+ return false;
+ }
+ }
+
+ return cchRight == 0 && cchLeft == 0;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+
+RTDECL(bool) RTCrX509RelativeDistinguishedName_MatchByRfc5280(PCRTCRX509RELATIVEDISTINGUISHEDNAME pLeft,
+ PCRTCRX509RELATIVEDISTINGUISHEDNAME pRight)
+{
+ /*
+ * No match if the attribute count differs.
+ */
+ uint32_t const cItems = pLeft->cItems;
+ if (cItems == pRight->cItems)
+ {
+ /*
+ * Compare each attribute, but don't insist on the same order nor
+ * bother checking for duplicates (too complicated).
+ */
+ for (uint32_t iLeft = 0; iLeft < cItems; iLeft++)
+ {
+ PCRTCRX509ATTRIBUTETYPEANDVALUE pLeftAttr = pLeft->papItems[iLeft];
+ bool fFound = false;
+ for (uint32_t iRight = 0; iRight < cItems; iRight++)
+ if (RTCrX509AttributeTypeAndValue_MatchAsRdnByRfc5280(pLeftAttr, pRight->papItems[iRight]))
+ {
+ fFound = true;
+ break;
+ }
+ if (!fFound)
+ return false;
+ }
+ return true;
+ }
+ return false;
+
+}
+
+
+/*
+ * X.509 Name.
+ */
+
+RTDECL(bool) RTCrX509Name_MatchByRfc5280(PCRTCRX509NAME pLeft, PCRTCRX509NAME pRight)
+{
+ uint32_t const cItems = pLeft->cItems;
+ if (cItems == pRight->cItems)
+ {
+ /* Require exact order. */
+ for (uint32_t iRdn = 0; iRdn < cItems; iRdn++)
+ if (!RTCrX509RelativeDistinguishedName_MatchByRfc5280(pLeft->papItems[iRdn], pRight->papItems[iRdn]))
+ return false;
+ return true;
+ }
+ return false;
+}
+
+
+RTDECL(bool) RTCrX509Name_ConstraintMatch(PCRTCRX509NAME pConstraint, PCRTCRX509NAME pName)
+{
+ /*
+ * Check that the constraint is a prefix of the name. This means that
+ * the name must have at least as many components and the constraint.
+ */
+ if (pName->cItems >= pConstraint->cItems)
+ {
+ /*
+ * Parallel crawl of the two RDNs arrays.
+ */
+ for (uint32_t i = 0; pConstraint->cItems; i++)
+ {
+ PCRTCRX509RELATIVEDISTINGUISHEDNAME pConstrRdns = pConstraint->papItems[i];
+ PCRTCRX509RELATIVEDISTINGUISHEDNAME pNameRdns = pName->papItems[i];
+
+ /*
+ * Walk the constraint attribute & value array.
+ */
+ for (uint32_t iConstrAttrib = 0; iConstrAttrib < pConstrRdns->cItems; iConstrAttrib++)
+ {
+ PCRTCRX509ATTRIBUTETYPEANDVALUE pConstrAttrib = pConstrRdns->papItems[iConstrAttrib];
+
+ /*
+ * Find matching attribute & value in the name.
+ */
+ bool fFound = false;
+ for (uint32_t iNameAttrib = 0; iNameAttrib < pNameRdns->cItems; iNameAttrib++)
+ if (RTCrX509AttributeTypeAndValue_MatchAsRdnByRfc5280(pConstrAttrib, pNameRdns->papItems[iNameAttrib]))
+ {
+ fFound = true;
+ break;
+ }
+ if (fFound)
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Mapping between X.500 object IDs and short and long names.
+ *
+ * See RFC-1327, RFC-4519 ...
+ */
+static struct
+{
+ const char *pszOid;
+ const char *pszShortNm;
+ size_t cchShortNm;
+ const char *pszLongNm;
+} const g_aRdnMap[] =
+{
+ { "0.9.2342.19200300.100.1.1", RT_STR_TUPLE("uid"), "userid" },
+ { "0.9.2342.19200300.100.1.3", RT_STR_TUPLE("Mail"), "Rfc822Mailbox" },
+ { "0.9.2342.19200300.100.1.25", RT_STR_TUPLE("DC"), "DomainComponent" },
+ { "1.2.840.113549.1.9.1", RT_STR_TUPLE("Email") /*nonstandard*/,"EmailAddress" },
+ { "1.3.6.1.4.1.311.60.2.1.1", RT_STR_TUPLE("JdxIncL") /*nonstd*/, "JdxOfIncLocalityName" },
+ { "1.3.6.1.4.1.311.60.2.1.2", RT_STR_TUPLE("JdxIncST") /*nonstd*/, "JdxOfIncStateOrProvinceName" },
+ { "1.3.6.1.4.1.311.60.2.1.3", RT_STR_TUPLE("JdxIncC") /*nonstd*/, "JdxOfIncCountryName" },
+ { "2.5.4.3", RT_STR_TUPLE("CN"), "CommonName" },
+ { "2.5.4.4", RT_STR_TUPLE("SN"), "Surname" },
+ { "2.5.4.5", RT_STR_TUPLE("SRN") /*nonstandard*/, "SerialNumber" },
+ { "2.5.4.6", RT_STR_TUPLE("C"), "CountryName" },
+ { "2.5.4.7", RT_STR_TUPLE("L"), "LocalityName" },
+ { "2.5.4.8", RT_STR_TUPLE("ST"), "StateOrProviceName" },
+ { "2.5.4.9", RT_STR_TUPLE("street"), "Street" },
+ { "2.5.4.10", RT_STR_TUPLE("O"), "OrganizationName" },
+ { "2.5.4.11", RT_STR_TUPLE("OU"), "OrganizationalUnitName" },
+ { "2.5.4.12", RT_STR_TUPLE("title"), "Title" },
+ { "2.5.4.13", RT_STR_TUPLE("desc"), "Description" },
+ { "2.5.4.15", RT_STR_TUPLE("BC") /*nonstandard*/, "BusinessCategory" },
+ { "2.5.4.17", RT_STR_TUPLE("ZIP") /*nonstandard*/, "PostalCode" },
+ { "2.5.4.18", RT_STR_TUPLE("POBox") /*nonstandard*/,"PostOfficeBox" },
+ { "2.5.4.20", RT_STR_TUPLE("PN") /*nonstandard*/, "TelephoneNumber" },
+ { "2.5.4.33", RT_STR_TUPLE("RO") /*nonstandard*/, "RoleOccupant" },
+ { "2.5.4.34", RT_STR_TUPLE("SA") /*nonstandard*/, "StreetAddress" },
+ { "2.5.4.41", RT_STR_TUPLE("N") /*nonstandard*/, "Name" },
+ { "2.5.4.42", RT_STR_TUPLE("GN"), "GivenName" },
+ { "2.5.4.43", RT_STR_TUPLE("I") /*nonstandard*/, "Initials" },
+ { "2.5.4.44", RT_STR_TUPLE("GQ") /*nonstandard*/, "GenerationQualifier" },
+ { "2.5.4.46", RT_STR_TUPLE("DNQ") /*nonstandard*/, "DNQualifier" },
+ { "2.5.4.51", RT_STR_TUPLE("HID") /*nonstandard*/, "HouseIdentifier" },
+};
+
+
+RTDECL(const char *) RTCrX509Name_GetShortRdn(PCRTASN1OBJID pRdnId)
+{
+ uint32_t iName = RT_ELEMENTS(g_aRdnMap);
+ while (iName-- > 0)
+ if (RTAsn1ObjId_CompareWithString(pRdnId, g_aRdnMap[iName].pszOid) == 0)
+ return g_aRdnMap[iName].pszShortNm;
+ return NULL;
+}
+
+
+RTDECL(bool) RTCrX509Name_MatchWithString(PCRTCRX509NAME pThis, const char *pszString)
+{
+ /* Keep track of the string length. */
+ size_t cchString = strlen(pszString);
+
+ /*
+ * The usual double loop for walking the components.
+ */
+ for (uint32_t i = 0; i < pThis->cItems; i++)
+ {
+ PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = pThis->papItems[i];
+ for (uint32_t j = 0; j < pRdn->cItems; j++)
+ {
+ PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j];
+
+ /*
+ * Must be a string.
+ */
+ if (pComponent->Value.enmType != RTASN1TYPE_STRING)
+ return false;
+
+ /*
+ * Look up the component name prefix and check whether it's also in the string.
+ */
+ uint32_t iName = RT_ELEMENTS(g_aRdnMap);
+ while (iName-- > 0)
+ if (RTAsn1ObjId_CompareWithString(&pComponent->Type, g_aRdnMap[iName].pszOid) == 0)
+ break;
+ AssertMsgReturn(iName != UINT32_MAX, ("Please extend g_aRdnMap with '%s'.\n", pComponent->Type.szObjId), false);
+
+ if ( strncmp(pszString, g_aRdnMap[iName].pszShortNm, g_aRdnMap[iName].cchShortNm) != 0
+ || pszString[g_aRdnMap[iName].cchShortNm] != '=')
+ return false;
+
+ pszString += g_aRdnMap[iName].cchShortNm + 1;
+ cchString -= g_aRdnMap[iName].cchShortNm + 1;
+
+ /*
+ * Compare the component string.
+ */
+ size_t cchComponent;
+ int rc = RTAsn1String_QueryUtf8Len(&pComponent->Value.u.String, &cchComponent);
+ AssertRCReturn(rc, false);
+
+ if (cchComponent > cchString)
+ return false;
+ if (RTAsn1String_CompareWithString(&pComponent->Value.u.String, pszString, cchComponent) != 0)
+ return false;
+
+ cchString -= cchComponent;
+ pszString += cchComponent;
+
+ /*
+ * Check separator comma + space and skip extra spaces before the next component.
+ */
+ if (cchString)
+ {
+ if (pszString[0] != ',')
+ return false;
+ if (pszString[1] != ' ' && pszString[1] != '\t')
+ return false;
+ pszString += 2;
+ cchString -= 2;
+
+ while (*pszString == ' ' || *pszString == '\t')
+ {
+ pszString++;
+ cchString--;
+ }
+ }
+ }
+ }
+
+ /*
+ * If we got thru the whole name and the whole string, we're good.
+ */
+ return *pszString == '\0';
+}
+
+
+RTDECL(int) RTCrX509Name_FormatAsString(PCRTCRX509NAME pThis, char *pszBuf, size_t cbBuf, size_t *pcbActual)
+{
+ /*
+ * The usual double loop for walking the components.
+ */
+ size_t off = 0;
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < pThis->cItems; i++)
+ {
+ PCRTCRX509RELATIVEDISTINGUISHEDNAME pRdn = pThis->papItems[i];
+ for (uint32_t j = 0; j < pRdn->cItems; j++)
+ {
+ PCRTCRX509ATTRIBUTETYPEANDVALUE pComponent = pRdn->papItems[j];
+
+ /*
+ * Must be a string.
+ */
+ if (pComponent->Value.enmType != RTASN1TYPE_STRING)
+ return VERR_CR_X509_NAME_NOT_STRING;
+
+ /*
+ * Look up the component name prefix.
+ */
+ uint32_t iName = RT_ELEMENTS(g_aRdnMap);
+ while (iName-- > 0)
+ if (RTAsn1ObjId_CompareWithString(&pComponent->Type, g_aRdnMap[iName].pszOid) == 0)
+ break;
+ AssertMsgReturn(iName != UINT32_MAX, ("Please extend g_aRdnMap with '%s'.\n", pComponent->Type.szObjId),
+ VERR_CR_X509_NAME_MISSING_RDN_MAP_ENTRY);
+
+ /*
+ * Append the prefix.
+ */
+ if (off)
+ {
+ if (off + 2 < cbBuf)
+ {
+ pszBuf[off] = ',';
+ pszBuf[off + 1] = ' ';
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ off += 2;
+ }
+
+ if (off + g_aRdnMap[iName].cchShortNm + 1 < cbBuf)
+ {
+ memcpy(&pszBuf[off], g_aRdnMap[iName].pszShortNm, g_aRdnMap[iName].cchShortNm);
+ pszBuf[off + g_aRdnMap[iName].cchShortNm] = '=';
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ off += g_aRdnMap[iName].cchShortNm + 1;
+
+ /*
+ * Add the component string.
+ */
+ const char *pszUtf8;
+ size_t cchUtf8;
+ int rc2 = RTAsn1String_QueryUtf8(&pComponent->Value.u.String, &pszUtf8, &cchUtf8);
+ AssertRCReturn(rc2, rc2);
+ if (off + cchUtf8 < cbBuf)
+ memcpy(&pszBuf[off], pszUtf8, cchUtf8);
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ off += cchUtf8;
+ }
+ }
+
+ if (pcbActual)
+ *pcbActual = off + 1;
+ if (off < cbBuf)
+ pszBuf[off] = '\0';
+ return rc;
+}
+
+
+
+/*
+ * One X.509 GeneralName.
+ */
+
+/**
+ * Name constraint matching (RFC-5280): DNS Name.
+ *
+ * @returns true on match, false on mismatch.
+ * @param pConstraint The constraint name.
+ * @param pName The name to match against the constraint.
+ */
+static bool rtCrX509GeneralName_ConstraintMatchDnsName(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName)
+{
+ /*
+ * Empty constraint string is taken to match everything.
+ */
+ if (pConstraint->u.pT2_DnsName->Asn1Core.cb == 0)
+ return true;
+
+ /*
+ * Get the UTF-8 strings for the two.
+ */
+ size_t cchConstraint;
+ char const *pszConstraint;
+ int rc = RTAsn1String_QueryUtf8(pConstraint->u.pT2_DnsName, &pszConstraint, &cchConstraint);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchFull;
+ char const *pszFull;
+ rc = RTAsn1String_QueryUtf8(pName->u.pT2_DnsName, &pszFull, &cchFull);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * No match if the constraint is longer.
+ */
+ if (cchConstraint > cchFull)
+ return false;
+
+ /*
+ * No match if the constraint and name tail doesn't match
+ * in a case-insensitive compare.
+ */
+ size_t offFull = cchFull - cchConstraint;
+ if (RTStrICmp(&pszFull[offFull], pszConstraint) != 0)
+ return false;
+ if (!offFull)
+ return true;
+
+ /*
+ * The matching constraint must be delimited by a dot in the full
+ * name. There seems to be some discussion whether ".oracle.com"
+ * should match "www..oracle.com". This implementation does choose
+ * to not succeed in that case.
+ */
+ if ((pszFull[offFull - 1] == '.') ^ (pszFull[offFull] == '.'))
+ return true;
+
+ return false;
+ }
+ }
+
+ /* fall back. */
+ return RTCrX509GeneralName_Compare(pConstraint, pName) == 0;
+}
+
+
+/**
+ * Name constraint matching (RFC-5280): RFC-822 (email).
+ *
+ * @returns true on match, false on mismatch.
+ * @param pConstraint The constraint name.
+ * @param pName The name to match against the constraint.
+ */
+static bool rtCrX509GeneralName_ConstraintMatchRfc822Name(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName)
+{
+ /*
+ * Empty constraint string is taken to match everything.
+ */
+ if (pConstraint->u.pT1_Rfc822->Asn1Core.cb == 0)
+ return true;
+
+ /*
+ * Get the UTF-8 strings for the two.
+ */
+ size_t cchConstraint;
+ char const *pszConstraint;
+ int rc = RTAsn1String_QueryUtf8(pConstraint->u.pT1_Rfc822, &pszConstraint, &cchConstraint);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchFull;
+ char const *pszFull;
+ rc = RTAsn1String_QueryUtf8(pName->u.pT1_Rfc822, &pszFull, &cchFull);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * No match if the constraint is longer.
+ */
+ if (cchConstraint > cchFull)
+ return false;
+
+ /*
+ * A lone dot matches everything.
+ */
+ if (cchConstraint == 1 && *pszConstraint == '.')
+ return true;
+
+ /*
+ * If there is a '@' in the constraint, the entire address must match.
+ */
+ const char *pszConstraintAt = (const char *)memchr(pszConstraint, '@', cchConstraint);
+ if (pszConstraintAt)
+ return cchConstraint == cchFull && RTStrICmp(pszConstraint, pszFull) == 0;
+
+ /*
+ * No match if the constraint and name tail doesn't match
+ * in a case-insensitive compare.
+ */
+ size_t offFull = cchFull - cchConstraint;
+ if (RTStrICmp(&pszFull[offFull], pszConstraint) != 0)
+ return false;
+
+ /*
+ * If the constraint starts with a dot, we're supposed to be
+ * satisfied with a tail match.
+ */
+ /** @todo Check if this should match even if offFull == 0. */
+ if (*pszConstraint == '.')
+ return true;
+
+ /*
+ * Otherwise, we require a hostname match and thus expect an '@'
+ * immediatly preceding the constraint match.
+ */
+ if (pszFull[offFull - 1] == '@')
+ return true;
+
+ return false;
+ }
+ }
+
+ /* fall back. */
+ return RTCrX509GeneralName_Compare(pConstraint, pName) == 0;
+}
+
+
+/**
+ * Extracts the hostname from an URI.
+ *
+ * @returns true if successfully extract, false if no hostname present.
+ * @param pszUri The URI.
+ * @param pchHostName .
+ * @param pcchHostName .
+ */
+static bool rtCrX509GeneralName_ExtractHostName(const char *pszUri, const char **pchHostName, size_t *pcchHostName)
+{
+ /*
+ * Skip the schema name.
+ */
+ const char *pszStart = strchr(pszUri, ':');
+ while (pszStart && (pszStart[1] != '/' || pszStart[2] != '/'))
+ pszStart = strchr(pszStart + 1, ':');
+ if (pszStart)
+ {
+ pszStart += 3;
+
+ /*
+ * The name ends with the first slash or ":port".
+ */
+ const char *pszEnd = strchr(pszStart, '/');
+ if (!pszEnd)
+ pszEnd = strchr(pszStart, '\0');
+ if (memchr(pszStart, ':', (size_t)(pszEnd - pszStart)))
+ do
+ pszEnd--;
+ while (*pszEnd != ':');
+ if (pszEnd != pszStart)
+ {
+ /*
+ * Drop access credentials at the front of the string if present.
+ */
+ const char *pszAt = (const char *)memchr(pszStart, '@', (size_t)(pszEnd - pszStart));
+ if (pszAt)
+ pszStart = pszAt + 1;
+
+ /*
+ * If there is still some string left, that's the host name.
+ */
+ if (pszEnd != pszStart)
+ {
+ *pcchHostName = (size_t)(pszEnd - pszStart);
+ *pchHostName = pszStart;
+ return true;
+ }
+ }
+ }
+
+ *pcchHostName = 0;
+ *pchHostName = NULL;
+ return false;
+}
+
+
+/**
+ * Name constraint matching (RFC-5280): URI.
+ *
+ * @returns true on match, false on mismatch.
+ * @param pConstraint The constraint name.
+ * @param pName The name to match against the constraint.
+ */
+static bool rtCrX509GeneralName_ConstraintMatchUri(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName)
+{
+ /*
+ * Empty constraint string is taken to match everything.
+ */
+ if (pConstraint->u.pT6_Uri->Asn1Core.cb == 0)
+ return true;
+
+ /*
+ * Get the UTF-8 strings for the two.
+ */
+ size_t cchConstraint;
+ char const *pszConstraint;
+ int rc = RTAsn1String_QueryUtf8(pConstraint->u.pT6_Uri, &pszConstraint, &cchConstraint);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cchFull;
+ char const *pszFull;
+ rc = RTAsn1String_QueryUtf8(pName->u.pT6_Uri, &pszFull, &cchFull);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Isolate the hostname in the name.
+ */
+ size_t cchHostName;
+ const char *pchHostName;
+ if (rtCrX509GeneralName_ExtractHostName(pszFull, &pchHostName, &cchHostName))
+ {
+ /*
+ * Domain constraint.
+ */
+ if (*pszConstraint == '.')
+ {
+ if (cchHostName >= cchConstraint)
+ {
+ size_t offHostName = cchHostName - cchConstraint;
+ if (RTStrICmp(&pchHostName[offHostName], pszConstraint) == 0)
+ {
+ /* "http://www..oracle.com" does not match ".oracle.com".
+ It's debatable whether "http://.oracle.com/" should match. */
+ if ( !offHostName
+ || pchHostName[offHostName - 1] != '.')
+ return true;
+ }
+ }
+ }
+ /*
+ * Host name constraint. Full match required.
+ */
+ else if ( cchHostName == cchConstraint
+ && RTStrNICmp(pchHostName, pszConstraint, cchHostName) == 0)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /* fall back. */
+ return RTCrX509GeneralName_Compare(pConstraint, pName) == 0;
+}
+
+
+/**
+ * Name constraint matching (RFC-5280): IP address.
+ *
+ * @returns true on match, false on mismatch.
+ * @param pConstraint The constraint name.
+ * @param pName The name to match against the constraint.
+ */
+static bool rtCrX509GeneralName_ConstraintMatchIpAddress(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName)
+{
+ uint8_t const *pbConstraint = pConstraint->u.pT7_IpAddress->Asn1Core.uData.pu8;
+ uint8_t const *pbFull = pName->u.pT7_IpAddress->Asn1Core.uData.pu8;
+
+ /*
+ * IPv4.
+ */
+ if ( pConstraint->u.pT7_IpAddress->Asn1Core.cb == 8 /* ip+netmask*/
+ && pName->u.pT7_IpAddress->Asn1Core.cb == 4) /* ip */
+ return ((pbFull[0] ^ pbConstraint[0]) & pbConstraint[4]) == 0
+ && ((pbFull[1] ^ pbConstraint[1]) & pbConstraint[5]) == 0
+ && ((pbFull[2] ^ pbConstraint[2]) & pbConstraint[6]) == 0
+ && ((pbFull[3] ^ pbConstraint[3]) & pbConstraint[7]) == 0;
+
+ /*
+ * IPv6.
+ */
+ if ( pConstraint->u.pT7_IpAddress->Asn1Core.cb == 32 /* ip+netmask*/
+ && pName->u.pT7_IpAddress->Asn1Core.cb == 16) /* ip */
+ {
+ for (uint32_t i = 0; i < 16; i++)
+ if (((pbFull[i] ^ pbConstraint[i]) & pbConstraint[i + 16]) != 0)
+ return false;
+ return true;
+ }
+
+ return RTCrX509GeneralName_Compare(pConstraint, pName) == 0;
+}
+
+
+RTDECL(bool) RTCrX509GeneralName_ConstraintMatch(PCRTCRX509GENERALNAME pConstraint, PCRTCRX509GENERALNAME pName)
+{
+ if (pConstraint->enmChoice == pName->enmChoice)
+ {
+ if (RTCRX509GENERALNAME_IS_DIRECTORY_NAME(pConstraint))
+ return RTCrX509Name_ConstraintMatch(&pConstraint->u.pT4->DirectoryName, &pName->u.pT4->DirectoryName);
+
+ if (RTCRX509GENERALNAME_IS_DNS_NAME(pConstraint))
+ return rtCrX509GeneralName_ConstraintMatchDnsName(pConstraint, pName);
+
+ if (RTCRX509GENERALNAME_IS_RFC822_NAME(pConstraint))
+ return rtCrX509GeneralName_ConstraintMatchRfc822Name(pConstraint, pName);
+
+ if (RTCRX509GENERALNAME_IS_URI(pConstraint))
+ return rtCrX509GeneralName_ConstraintMatchUri(pConstraint, pName);
+
+ if (RTCRX509GENERALNAME_IS_IP_ADDRESS(pConstraint))
+ return rtCrX509GeneralName_ConstraintMatchIpAddress(pConstraint, pName);
+
+ AssertFailed();
+ return RTCrX509GeneralName_Compare(pConstraint, pName) == 0;
+ }
+ return false;
+}
+
+
+/*
+ * Sequence of X.509 GeneralNames.
+ */
+
+
+/*
+ * X.509 UniqueIdentifier.
+ */
+
+
+/*
+ * X.509 SubjectPublicKeyInfo.
+ */
+
+
+/*
+ * X.509 AuthorityKeyIdentifier (IPRT representation).
+ */
+
+
+/*
+ * One X.509 PolicyQualifierInfo.
+ */
+
+
+/*
+ * Sequence of X.509 PolicyQualifierInfo.
+ */
+
+
+/*
+ * One X.509 PolicyInformation.
+ */
+
+
+/*
+ * Sequence of X.509 CertificatePolicies.
+ */
+
+
+/*
+ * One X.509 PolicyMapping (IPRT representation).
+ */
+
+
+/*
+ * Sequence of X.509 PolicyMappings (IPRT representation).
+ */
+
+
+/*
+ * X.509 BasicConstraints (IPRT representation).
+ */
+
+
+/*
+ * X.509 GeneralSubtree (IPRT representation).
+ */
+
+
+RTDECL(bool) RTCrX509GeneralSubtree_ConstraintMatch(PCRTCRX509GENERALSUBTREE pConstraint, PCRTCRX509GENERALSUBTREE pName)
+{
+ return RTCrX509GeneralName_ConstraintMatch(&pConstraint->Base, &pName->Base);
+}
+
+
+/*
+ * Sequence of X.509 GeneralSubtrees (IPRT representation).
+ */
+
+
+/*
+ * X.509 NameConstraints (IPRT representation).
+ */
+
+
+/*
+ * X.509 PolicyConstraints (IPRT representation).
+ */
+
+
+/*
+ * One X.509 Extension.
+ */
+
+
+/*
+ * Sequence of X.509 Extensions.
+ */
+
+
+/*
+ * X.509 TbsCertificate.
+ */
+
+static void rtCrx509TbsCertificate_AddKeyUsageFlags(PRTCRX509TBSCERTIFICATE pThis, PCRTCRX509EXTENSION pExtension)
+{
+ AssertReturnVoid(pExtension->enmValue == RTCRX509EXTENSIONVALUE_BIT_STRING);
+ /* 3 = 1 byte for unused bit count, followed by one or two bytes containing actual bits. RFC-5280 defines bits 0 thru 8. */
+ AssertReturnVoid(pExtension->ExtnValue.pEncapsulated->cb <= 3);
+ pThis->T3.fKeyUsage |= (uint32_t)RTAsn1BitString_GetAsUInt64((PCRTASN1BITSTRING)pExtension->ExtnValue.pEncapsulated);
+}
+
+
+static void rtCrx509TbsCertificate_AddExtKeyUsageFlags(PRTCRX509TBSCERTIFICATE pThis, PCRTCRX509EXTENSION pExtension)
+{
+ AssertReturnVoid(pExtension->enmValue == RTCRX509EXTENSIONVALUE_SEQ_OF_OBJ_IDS);
+ PCRTASN1SEQOFOBJIDS pObjIds = (PCRTASN1SEQOFOBJIDS)pExtension->ExtnValue.pEncapsulated;
+ uint32_t i = pObjIds->cItems;
+ while (i-- > 0)
+ {
+
+ if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_ANY_EXTENDED_KEY_USAGE_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_ANY;
+ else if (RTAsn1ObjId_StartsWith(pObjIds->papItems[i], RTCRX509_ID_KP_OID))
+ {
+ if (RTAsn1ObjIdCountComponents(pObjIds->papItems[i]) == 9)
+ switch (RTAsn1ObjIdGetLastComponentsAsUInt32(pObjIds->papItems[i]))
+ {
+ case 1: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_SERVER_AUTH; break;
+ case 2: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_CLIENT_AUTH; break;
+ case 3: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_CODE_SIGNING; break;
+ case 4: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_EMAIL_PROTECTION; break;
+ case 5: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_IPSEC_END_SYSTEM; break;
+ case 6: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_IPSEC_TUNNEL; break;
+ case 7: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_IPSEC_USER; break;
+ case 8: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_TIMESTAMPING; break;
+ case 9: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OCSP_SIGNING; break;
+ case 10: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_DVCS; break;
+ case 11: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_SBGP_CERT_AA_SERVICE_AUTH; break;
+ case 13: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_EAP_OVER_PPP; break;
+ case 14: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_EAP_OVER_LAN; break;
+ default: pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER; break;
+ }
+ else
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER;
+ }
+ else if (RTAsn1ObjId_StartsWith(pObjIds->papItems[i], RTCRX509_APPLE_EKU_APPLE_EXTENDED_KEY_USAGE_OID))
+ {
+ if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_CODE_SIGNING_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_CODE_SIGNING;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_CODE_SIGNING_DEVELOPMENT_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_CODE_SIGNING_DEVELOPMENT;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_SOFTWARE_UPDATE_SIGNING_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_SOFTWARE_UPDATE_SIGNING;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_CODE_SIGNING_THRID_PARTY_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_CODE_SIGNING_THIRD_PARTY;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_RESOURCE_SIGNING_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_RESOURCE_SIGNING;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_APPLE_EKU_SYSTEM_IDENTITY_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_APPLE_SYSTEM_IDENTITY;
+ else
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER;
+ }
+ else if (RTAsn1ObjId_StartsWith(pObjIds->papItems[i], "1.3.6.1.4.1.311"))
+ {
+ if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_TIMESTAMP_SIGNING_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_TIMESTAMP_SIGNING;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_WHQL_CRYPTO_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_WHQL_CRYPTO;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_ATTEST_WHQL_CRYPTO_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_ATTEST_WHQL_CRYPTO;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_NT5_CRYPTO_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_NT5_CRYPTO;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_OEM_WHQL_CRYPTO_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_OEM_WHQL_CRYPTO;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_EMBEDDED_NT_CRYPTO_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_EMBEDDED_NT_CRYPTO;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_KERNEL_MODE_CODE_SIGNING_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_KERNEL_MODE_CODE_SIGNING;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_LIFETIME_SIGNING_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_LIFETIME_SIGNING;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_DRM_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_DRM;
+ else if (RTAsn1ObjId_CompareWithString(pObjIds->papItems[i], RTCRX509_MS_EKU_DRM_INDIVIDUALIZATION_OID) == 0)
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_MS_DRM_INDIVIDUALIZATION;
+ else
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER;
+ }
+ else
+ pThis->T3.fExtKeyUsage |= RTCRX509CERT_EKU_F_OTHER;
+ }
+}
+
+
+/**
+ * (Re-)Process the certificate extensions.
+ *
+ * Will fail if duplicate extensions are encountered.
+ *
+ * @returns IPRT status code.
+ * @param pThis The to-be-signed certificate part.
+ * @param pErrInfo Where to return extended error details,
+ * optional.
+ */
+RTDECL(int) RTCrX509TbsCertificate_ReprocessExtensions(PRTCRX509TBSCERTIFICATE pThis, PRTERRINFO pErrInfo)
+{
+ /*
+ * Clear all variables we will set.
+ */
+ pThis->T3.fFlags = 0;
+ pThis->T3.fKeyUsage = 0;
+ pThis->T3.fExtKeyUsage = 0;
+ pThis->T3.pAuthorityKeyIdentifier = NULL;
+ pThis->T3.pSubjectKeyIdentifier = NULL;
+ pThis->T3.pAltSubjectName = NULL;
+ pThis->T3.pAltIssuerName = NULL;
+ pThis->T3.pCertificatePolicies = NULL;
+ pThis->T3.pPolicyMappings = NULL;
+ pThis->T3.pBasicConstraints = NULL;
+ pThis->T3.pNameConstraints = NULL;
+ pThis->T3.pPolicyConstraints = NULL;
+ pThis->T3.pInhibitAnyPolicy = NULL;
+
+#define CHECK_SET_PRESENT_RET_ON_DUP(a_pThis, a_pErrInfo, a_fPresentFlag) \
+ do { \
+ if ((a_pThis)->T3.fFlags & (a_fPresentFlag)) \
+ return RTErrInfoSet(a_pErrInfo, VERR_CR_X509_TBSCERT_DUPLICATE_EXTENSION, \
+ "Duplicate extension " #a_fPresentFlag); \
+ (a_pThis)->T3.fFlags |= (a_fPresentFlag); \
+ } while (0)
+
+ /*
+ * Process all the extensions.
+ */
+ for (uint32_t i = 0; i < pThis->T3.Extensions.cItems; i++)
+ {
+ PCRTASN1OBJID pExtnId = &pThis->T3.Extensions.papItems[i]->ExtnId;
+ PCRTASN1OCTETSTRING pExtValue = &pThis->T3.Extensions.papItems[i]->ExtnValue;
+ if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_KEY_USAGE_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_KEY_USAGE);
+ rtCrx509TbsCertificate_AddKeyUsageFlags(pThis, pThis->T3.Extensions.papItems[i]);
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_BIT_STRING);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_EXT_KEY_USAGE_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_EXT_KEY_USAGE);
+ rtCrx509TbsCertificate_AddExtKeyUsageFlags(pThis, pThis->T3.Extensions.papItems[i]);
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_SEQ_OF_OBJ_IDS);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_AUTHORITY_KEY_IDENTIFIER_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_AUTHORITY_KEY_IDENTIFIER);
+ pThis->T3.pAuthorityKeyIdentifier = (PCRTCRX509AUTHORITYKEYIDENTIFIER)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_AUTHORITY_KEY_IDENTIFIER);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_OLD_AUTHORITY_KEY_IDENTIFIER_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_OLD_AUTHORITY_KEY_IDENTIFIER);
+ pThis->T3.pOldAuthorityKeyIdentifier = (PCRTCRX509OLDAUTHORITYKEYIDENTIFIER)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_OLD_AUTHORITY_KEY_IDENTIFIER);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_SUBJECT_KEY_IDENTIFIER_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_SUBJECT_KEY_IDENTIFIER);
+ pThis->T3.pSubjectKeyIdentifier = (PCRTASN1OCTETSTRING)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_OCTET_STRING);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_SUBJECT_ALT_NAME_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_SUBJECT_ALT_NAME);
+ pThis->T3.pAltSubjectName = (PCRTCRX509GENERALNAMES)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_GENERAL_NAMES);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_ISSUER_ALT_NAME_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_ISSUER_ALT_NAME);
+ pThis->T3.pAltIssuerName = (PCRTCRX509GENERALNAMES)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_GENERAL_NAMES);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_CERTIFICATE_POLICIES_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_CERTIFICATE_POLICIES);
+ pThis->T3.pCertificatePolicies = (PCRTCRX509CERTIFICATEPOLICIES)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_CERTIFICATE_POLICIES);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_POLICY_MAPPINGS_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_POLICY_MAPPINGS);
+ pThis->T3.pPolicyMappings = (PCRTCRX509POLICYMAPPINGS)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_POLICY_MAPPINGS);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_BASIC_CONSTRAINTS_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_BASIC_CONSTRAINTS);
+ pThis->T3.pBasicConstraints = (PCRTCRX509BASICCONSTRAINTS)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_BASIC_CONSTRAINTS);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_NAME_CONSTRAINTS_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_NAME_CONSTRAINTS);
+ pThis->T3.pNameConstraints = (PCRTCRX509NAMECONSTRAINTS)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_NAME_CONSTRAINTS);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_POLICY_CONSTRAINTS_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_POLICY_CONSTRAINTS);
+ pThis->T3.pPolicyConstraints = (PCRTCRX509POLICYCONSTRAINTS)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_POLICY_CONSTRAINTS);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_INHIBIT_ANY_POLICY_OID) == 0)
+ {
+ CHECK_SET_PRESENT_RET_ON_DUP(pThis, pErrInfo, RTCRX509TBSCERTIFICATE_F_PRESENT_INHIBIT_ANY_POLICY);
+ pThis->T3.pInhibitAnyPolicy = (PCRTASN1INTEGER)pExtValue->pEncapsulated;
+ Assert(pThis->T3.Extensions.papItems[i]->enmValue == RTCRX509EXTENSIONVALUE_INTEGER);
+ }
+ else if (RTAsn1ObjId_CompareWithString(pExtnId, RTCRX509_ID_CE_ACCEPTABLE_CERT_POLICIES_OID) == 0)
+ pThis->T3.fFlags |= RTCRX509TBSCERTIFICATE_F_PRESENT_ACCEPTABLE_CERT_POLICIES;
+ else
+ pThis->T3.fFlags |= RTCRX509TBSCERTIFICATE_F_PRESENT_OTHER;
+ }
+
+ if (!pThis->T3.fFlags)
+ pThis->T3.fFlags |= RTCRX509TBSCERTIFICATE_F_PRESENT_NONE;
+
+#undef CHECK_SET_PRESENT_RET_ON_DUP
+ return VINF_SUCCESS;
+}
+
+
+
+/*
+ * One X.509 Certificate.
+ */
+
+RTDECL(bool) RTCrX509Certificate_MatchIssuerAndSerialNumber(PCRTCRX509CERTIFICATE pCertificate,
+ PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNumber)
+{
+ if ( RTAsn1Integer_UnsignedCompare(&pCertificate->TbsCertificate.SerialNumber, pSerialNumber) == 0
+ && RTCrX509Name_Compare(&pCertificate->TbsCertificate.Issuer, pIssuer) == 0)
+ return true;
+ return false;
+}
+
+
+RTDECL(bool) RTCrX509Certificate_MatchSubjectOrAltSubjectByRfc5280(PCRTCRX509CERTIFICATE pThis, PCRTCRX509NAME pName)
+{
+ if (RTCrX509Name_MatchByRfc5280(&pThis->TbsCertificate.Subject, pName))
+ return true;
+
+ if (RTCrX509Extensions_IsPresent(&pThis->TbsCertificate.T3.Extensions))
+ for (uint32_t i = 0; i < pThis->TbsCertificate.T3.Extensions.cItems; i++)
+ {
+ PCRTCRX509EXTENSION pExt = pThis->TbsCertificate.T3.Extensions.papItems[i];
+ if ( pExt->enmValue == RTCRX509EXTENSIONVALUE_GENERAL_NAMES
+ && RTAsn1ObjId_CompareWithString(&pExt->ExtnId, RTCRX509_ID_CE_SUBJECT_ALT_NAME_OID))
+ {
+ PCRTCRX509GENERALNAMES pGeneralNames = (PCRTCRX509GENERALNAMES)pExt->ExtnValue.pEncapsulated;
+ for (uint32_t j = 0; j < pGeneralNames->cItems; j++)
+ if ( RTCRX509GENERALNAME_IS_DIRECTORY_NAME(pGeneralNames->papItems[j])
+ && RTCrX509Name_MatchByRfc5280(&pGeneralNames->papItems[j]->u.pT4->DirectoryName, pName))
+ return true;
+ }
+ }
+ return false;
+}
+
+
+RTDECL(bool) RTCrX509Certificate_IsSelfSigned(PCRTCRX509CERTIFICATE pCertificate)
+{
+ if (RTCrX509Certificate_IsPresent(pCertificate))
+ {
+ return RTCrX509Name_MatchByRfc5280(&pCertificate->TbsCertificate.Subject,
+ &pCertificate->TbsCertificate.Issuer);
+ }
+ return false;
+}
+
+
+/*
+ * Set of X.509 Certificates.
+ */
+
+RTDECL(PCRTCRX509CERTIFICATE)
+RTCrX509Certificates_FindByIssuerAndSerialNumber(PCRTCRX509CERTIFICATES pCertificates,
+ PCRTCRX509NAME pIssuer, PCRTASN1INTEGER pSerialNumber)
+{
+ for (uint32_t i = 0; i < pCertificates->cItems; i++)
+ if (RTCrX509Certificate_MatchIssuerAndSerialNumber(pCertificates->papItems[i], pIssuer, pSerialNumber))
+ return pCertificates->papItems[i];
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/crypto/x509-file.cpp b/src/VBox/Runtime/common/crypto/x509-file.cpp
new file mode 100644
index 00000000..71863f73
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/x509-file.cpp
@@ -0,0 +1,174 @@
+/* $Id: x509-file.cpp $ */
+/** @file
+ * IPRT - Crypto - X.509, File related APIs.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/x509.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/crypto/pem.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static RTCRPEMMARKERWORD const g_aWords_Certificate[] = { { RT_STR_TUPLE("CERTIFICATE") } };
+/** X509 Certificate markers. */
+RT_DECL_DATA_CONST(RTCRPEMMARKER const) g_aRTCrX509CertificateMarkers[] =
+{
+ { g_aWords_Certificate, RT_ELEMENTS(g_aWords_Certificate) }
+};
+/** Number of entries in g_aRTCrX509CertificateMarkers. */
+RT_DECL_DATA_CONST(uint32_t const) g_cRTCrX509CertificateMarkers = RT_ELEMENTS(g_aRTCrX509CertificateMarkers);
+
+
+RTDECL(int) RTCrX509Certificate_ReadFromFile(PRTCRX509CERTIFICATE pCertificate, const char *pszFilename, uint32_t fFlags,
+ PCRTASN1ALLOCATORVTABLE pAllocator, PRTERRINFO pErrInfo)
+{
+ AssertReturn(!(fFlags & ~RTCRX509CERT_READ_F_PEM_ONLY), VERR_INVALID_FLAGS);
+ PCRTCRPEMSECTION pSectionHead;
+ int rc = RTCrPemReadFile(pszFilename,
+ fFlags & RTCRX509CERT_READ_F_PEM_ONLY ? RTCRPEMREADFILE_F_ONLY_PEM : 0,
+ g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers,
+ &pSectionHead, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (pSectionHead)
+ {
+ RTCRX509CERTIFICATE TmpCert;
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pSectionHead->pbData, (uint32_t)RT_MIN(pSectionHead->cbData, UINT32_MAX),
+ pErrInfo, pAllocator, RTASN1CURSOR_FLAGS_DER, RTPathFilename(pszFilename));
+ rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &TmpCert, "Cert");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509Certificate_CheckSanity(&TmpCert, 0, pErrInfo, "Cert");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509Certificate_Clone(pCertificate, &TmpCert, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ if (pSectionHead->pNext || PrimaryCursor.Cursor.cbLeft)
+ rc = VINF_ASN1_MORE_DATA;
+ }
+ }
+ RTCrX509Certificate_Delete(&TmpCert);
+ }
+ RTCrPemFreeSections(pSectionHead);
+ }
+ else
+ rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2;
+
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTCrX509Certificate_ReadFromBuffer(PRTCRX509CERTIFICATE pCertificate, const void *pvBuf, size_t cbBuf,
+ uint32_t fFlags, PCRTASN1ALLOCATORVTABLE pAllocator,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ AssertReturn(!(fFlags & ~RTCRX509CERT_READ_F_PEM_ONLY), VERR_INVALID_FLAGS);
+ PCRTCRPEMSECTION pSectionHead;
+ int rc = RTCrPemParseContent(pvBuf, cbBuf,
+ fFlags & RTCRX509CERT_READ_F_PEM_ONLY ? RTCRPEMREADFILE_F_ONLY_PEM : 0,
+ g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers,
+ &pSectionHead, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (pSectionHead)
+ {
+ RTCRX509CERTIFICATE TmpCert;
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pSectionHead->pbData, (uint32_t)RT_MIN(pSectionHead->cbData, UINT32_MAX),
+ pErrInfo, pAllocator, RTASN1CURSOR_FLAGS_DER, pszErrorTag);
+ rc = RTCrX509Certificate_DecodeAsn1(&PrimaryCursor.Cursor, 0, &TmpCert, "Cert");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509Certificate_CheckSanity(&TmpCert, 0, pErrInfo, "Cert");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrX509Certificate_Clone(pCertificate, &TmpCert, pAllocator);
+ if (RT_SUCCESS(rc))
+ {
+ if (pSectionHead->pNext || PrimaryCursor.Cursor.cbLeft)
+ rc = VINF_ASN1_MORE_DATA;
+ }
+ }
+ RTCrX509Certificate_Delete(&TmpCert);
+ }
+ RTCrPemFreeSections(pSectionHead);
+ }
+ else
+ rc = rc != VINF_SUCCESS ? -rc : VERR_INTERNAL_ERROR_2;
+ }
+ return rc;
+}
+
+
+
+#if 0
+RTDECL(int) RTCrX509Certificates_ReadFromFile(const char *pszFilename, uint32_t fFlags,
+ PRTCRX509CERTIFICATES pCertificates, PRTERRINFO pErrInfo)
+{
+ AssertReturn(!(fFlags & ~RTCRX509CERT_READ_F_PEM_ONLY), VERR_INVALID_FLAGS);
+ PCRTCRPEMSECTION pSectionHead;
+ int rc = RTCrPemReadFile(pszFilename,
+ fFlags & RTCRX509CERT_READ_F_PEM_ONLY ? RTCRPEMREADFILE_F_ONLY_PEM : 0,
+ g_aRTCrX509CertificateMarkers, g_cRTCrX509CertificateMarkers,
+ &pSectionHead, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pCertificates->Allocation
+
+ PCRTCRPEMSECTION pCurSec = pSectionHead;
+ while (pCurSec)
+ {
+
+ pCurSec = pCurSec->pNext;
+ }
+
+ RTCrPemFreeSections(pSectionHead);
+ }
+ return rc;
+}
+#endif
+
diff --git a/src/VBox/Runtime/common/crypto/x509-init.cpp b/src/VBox/Runtime/common/crypto/x509-init.cpp
new file mode 100644
index 00000000..b30a1945
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/x509-init.cpp
@@ -0,0 +1,88 @@
+/* $Id: x509-init.cpp $ */
+/** @file
+ * IPRT - Crypto - X.509, Initialization API.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/x509.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#include "x509-internal.h"
+
+
+static int rtCrX509Extension_ExtnValue_Clone(PRTCRX509EXTENSION pThis, PCRTCRX509EXTENSION pSrc)
+{
+ pThis->enmValue = pSrc->enmValue;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTCrX509Name_RecodeAsUtf8(PRTCRX509NAME pThis, PCRTASN1ALLOCATORVTABLE pAllocator)
+{
+ uint32_t cRdns = pThis->cItems;
+ PRTCRX509RELATIVEDISTINGUISHEDNAME *ppRdn = pThis->papItems;
+ while (cRdns-- > 0)
+ {
+ PRTCRX509RELATIVEDISTINGUISHEDNAME const pRdn = *ppRdn;
+ uint32_t cAttribs = pRdn->cItems;
+ PRTCRX509ATTRIBUTETYPEANDVALUE *ppAttrib = pRdn->papItems;
+ while (cAttribs-- > 0)
+ {
+ PRTCRX509ATTRIBUTETYPEANDVALUE const pAttrib = *ppAttrib;
+ if (pAttrib->Value.enmType == RTASN1TYPE_STRING)
+ {
+ int rc = RTAsn1String_RecodeAsUtf8(&pAttrib->Value.u.String, pAllocator);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ ppAttrib++;
+ }
+ ppRdn++;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-init.h>
+
diff --git a/src/VBox/Runtime/common/crypto/x509-internal.h b/src/VBox/Runtime/common/crypto/x509-internal.h
new file mode 100644
index 00000000..550892ad
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/x509-internal.h
@@ -0,0 +1,47 @@
+/* $Id: x509-internal.h $ */
+/** @file
+ * IPRT - Crypto - X.509, Internal Header.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_crypto_x509_internal_h
+#define IPRT_INCLUDED_SRC_common_crypto_x509_internal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#define RTASN1TMPL_TEMPLATE_FILE "../common/crypto/x509-template.h"
+#include <iprt/asn1-generator-internal-header.h>
+
+#endif /* !IPRT_INCLUDED_SRC_common_crypto_x509_internal_h */
+
diff --git a/src/VBox/Runtime/common/crypto/x509-sanity.cpp b/src/VBox/Runtime/common/crypto/x509-sanity.cpp
new file mode 100644
index 00000000..843ac996
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/x509-sanity.cpp
@@ -0,0 +1,171 @@
+/* $Id: x509-sanity.cpp $ */
+/** @file
+ * IPRT - Crypto - X.509, Sanity Checkers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/x509.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#include "x509-internal.h"
+
+
+
+static int rtCrX509Validity_CheckSanityExtra(PCRTCRX509VALIDITY pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+
+ if (RTAsn1Time_Compare(&pThis->NotBefore, &pThis->NotAfter) > 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_VALIDITY_SWAPPED, "%s: NotBefore is after NotAfter", pszErrorTag);
+ /** @todo check tag constraints? */
+ return VINF_SUCCESS;
+}
+
+
+static int rtCrX509Name_CheckSanityExtra(PCRTCRX509NAME pThis, uint32_t fFlags, PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+
+ if (pThis->cItems == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_NAME_EMPTY_SET, "%s: Has no components.", pszErrorTag);
+
+ for (uint32_t i = 0; i < pThis->cItems; i++)
+ {
+ PCRTCRX509RELATIVEDISTINGUISHEDNAME const pRdn = pThis->papItems[i];
+ if (pRdn->cItems == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_NAME_EMPTY_SUB_SET,
+ "%s: Items[%u] has no sub components.", pszErrorTag, i);
+
+ for (uint32_t j = 0; j < pRdn->cItems; j++)
+ {
+ PCRTCRX509ATTRIBUTETYPEANDVALUE const pAttr = pRdn->papItems[j];
+
+ if (pAttr->Value.enmType != RTASN1TYPE_STRING)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_NAME_NOT_STRING,
+ "%s: Items[%u].paItems[%u].enmType is %d instead of string (%d).",
+ pszErrorTag, i, j, pAttr->Value.enmType, RTASN1TYPE_STRING);
+ if (pAttr->Value.u.String.Asn1Core.cb == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_NAME_EMPTY_STRING,
+ "%s: Items[%u].paItems[%u] is an empty string", pszErrorTag, i, j);
+ switch (pAttr->Value.u.String.Asn1Core.uTag)
+ {
+ case ASN1_TAG_PRINTABLE_STRING:
+ case ASN1_TAG_UTF8_STRING:
+ break;
+ case ASN1_TAG_T61_STRING:
+ case ASN1_TAG_UNIVERSAL_STRING:
+ case ASN1_TAG_BMP_STRING:
+ break;
+ case ASN1_TAG_IA5_STRING: /* Used by "Microsoft Root Certificate Authority" in the "com" part of the Issuer. */
+ break;
+ default:
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_INVALID_NAME_STRING_TAG,
+ "%s: Items[%u].paItems[%u] invalid string type: %u", pszErrorTag, i, j,
+ pAttr->Value.u.String.Asn1Core.uTag);
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int rtCrX509SubjectPublicKeyInfo_CheckSanityExtra(PCRTCRX509SUBJECTPUBLICKEYINFO pThis, uint32_t fFlags,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+ if (pThis->SubjectPublicKey.cBits <= 32)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_PUBLIC_KEY_TOO_SMALL,
+ "%s: SubjectPublicKey is too small, only %u bits", pszErrorTag, pThis->SubjectPublicKey.cBits);
+ return VINF_SUCCESS;
+}
+
+
+static int rtCrX509TbsCertificate_CheckSanityExtra(PCRTCRX509TBSCERTIFICATE pThis, uint32_t fFlags,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+
+ if ( RTAsn1Integer_IsPresent(&pThis->T0.Version)
+ && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V1) != 0
+ && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V2) != 0
+ && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V3) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_TBSCERT_UNSUPPORTED_VERSION,
+ "%s: Unknown Version number: %llu",
+ pszErrorTag, pThis->T0.Version.uValue.u);
+
+ if ( pThis->SerialNumber.Asn1Core.cb < 1
+ || pThis->SerialNumber.Asn1Core.cb > 1024)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_TBSCERT_SERIAL_NUMBER_OUT_OF_BOUNDS,
+ "%s: Bad SerialNumber length: %u", pszErrorTag, pThis->SerialNumber.Asn1Core.cb);
+
+ if ( ( RTAsn1BitString_IsPresent(&pThis->T1.IssuerUniqueId)
+ || RTAsn1BitString_IsPresent(&pThis->T2.SubjectUniqueId))
+ && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V2) < 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_TBSCERT_UNIQUE_IDS_REQ_V2,
+ "%s: IssuerUniqueId and SubjectUniqueId requires version 2", pszErrorTag);
+
+ if ( RTCrX509Extensions_IsPresent(&pThis->T3.Extensions)
+ && RTAsn1Integer_UnsignedCompareWithU32(&pThis->T0.Version, RTCRX509TBSCERTIFICATE_V3) < 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_TBSCERT_EXTS_REQ_V3, "%s: Extensions requires version 3", pszErrorTag);
+
+ return VINF_SUCCESS;
+}
+
+
+static int rtCrX509Certificate_CheckSanityExtra(PCRTCRX509CERTIFICATE pThis, uint32_t fFlags,
+ PRTERRINFO pErrInfo, const char *pszErrorTag)
+{
+ RT_NOREF_PV(fFlags);
+
+ if (RTCrX509AlgorithmIdentifier_Compare(&pThis->SignatureAlgorithm, &pThis->TbsCertificate.Signature) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_CERT_TBS_SIGN_ALGO_MISMATCH,
+ "%s: SignatureAlgorithm (%s) does not match TbsCertificate.Signature (%s).", pszErrorTag,
+ pThis->SignatureAlgorithm.Algorithm.szObjId,
+ pThis->TbsCertificate.Signature.Algorithm.szObjId);
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Generate the code.
+ */
+#include <iprt/asn1-generator-sanity.h>
+
diff --git a/src/VBox/Runtime/common/crypto/x509-template.h b/src/VBox/Runtime/common/crypto/x509-template.h
new file mode 100644
index 00000000..9ab3fa51
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/x509-template.h
@@ -0,0 +1,468 @@
+/* $Id: x509-template.h $ */
+/** @file
+ * IPRT - Crypto - X.509, Code Generator Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#define RTASN1TMPL_DECL RTDECL
+
+/*
+ * X.509 Validity.
+ */
+#define RTASN1TMPL_TYPE RTCRX509VALIDITY
+#define RTASN1TMPL_EXT_NAME RTCrX509Validity
+#define RTASN1TMPL_INT_NAME rtCrX509Validity
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( NotBefore, RTASN1TIME, RTAsn1Time);
+RTASN1TMPL_MEMBER( NotAfter, RTASN1TIME, RTAsn1Time);
+RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrX509Validity_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) )
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One X.509 Algorithm Identifier.
+ */
+#define RTASN1TMPL_TYPE RTCRX509ALGORITHMIDENTIFIER
+#define RTASN1TMPL_EXT_NAME RTCrX509AlgorithmIdentifier
+#define RTASN1TMPL_INT_NAME rtCrX509AlgorithmIdentifier
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Algorithm, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER_OPT_ANY( Parameters, RTASN1DYNTYPE, RTAsn1DynType);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Set of X.509 Algorithm Identifiers.
+ */
+#define RTASN1TMPL_TYPE RTCRX509ALGORITHMIDENTIFIERS
+#define RTASN1TMPL_EXT_NAME RTCrX509AlgorithmIdentifiers
+#define RTASN1TMPL_INT_NAME rtCrX509AlgorithmIdentifiers
+RTASN1TMPL_SET_OF(RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One X.509 AttributeTypeAndValue.
+ */
+#define RTASN1TMPL_TYPE RTCRX509ATTRIBUTETYPEANDVALUE
+#define RTASN1TMPL_EXT_NAME RTCrX509AttributeTypeAndValue
+#define RTASN1TMPL_INT_NAME rtCrX509AttributeTypeAndValue
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Type, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER( Value, RTASN1DYNTYPE, RTAsn1DynType);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Set of X.509 AttributeTypeAndValues / X.509 RelativeDistinguishedName.
+ */
+#define RTASN1TMPL_TYPE RTCRX509ATTRIBUTETYPEANDVALUES
+#define RTASN1TMPL_EXT_NAME RTCrX509AttributeTypeAndValues
+#define RTASN1TMPL_INT_NAME rtCrX509AttributeTypeAndValues
+RTASN1TMPL_SET_OF(RTCRX509ATTRIBUTETYPEANDVALUE, RTCrX509AttributeTypeAndValue);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+/*
+ * X.509 Name.
+ */
+#define RTASN1TMPL_TYPE RTCRX509NAME
+#define RTASN1TMPL_EXT_NAME RTCrX509Name
+#define RTASN1TMPL_INT_NAME rtCrX509Name
+#undef RTASN1TMPL_SET_SEQ_EXEC_CHECK_SANITY
+#define RTASN1TMPL_SET_SEQ_EXEC_CHECK_SANITY() rc = rtCrX509Name_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag)
+RTASN1TMPL_SEQ_OF(RTCRX509RELATIVEDISTINGUISHEDNAME, RTCrX509RelativeDistinguishedName);
+#undef RTASN1TMPL_SET_SEQ_EXEC_CHECK_SANITY
+#define RTASN1TMPL_SET_SEQ_EXEC_CHECK_SANITY() do { } while (0)
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+/*
+ * One X.509 OtherName.
+ * Note! This is simplified and might not work correctly for all types with
+ * non-DER compatible encodings.
+ */
+#define RTASN1TMPL_TYPE RTCRX509OTHERNAME
+#define RTASN1TMPL_EXT_NAME RTCrX509OtherName
+#define RTASN1TMPL_INT_NAME rtCrX509OtherName
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( TypeId, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER( Value, RTASN1DYNTYPE, RTAsn1DynType);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One X.509 GeneralName.
+ * Note! This is simplified and might not work correctly for all types with
+ * non-DER compatible encodings.
+ */
+#define RTASN1TMPL_TYPE RTCRX509GENERALNAME
+#define RTASN1TMPL_EXT_NAME RTCrX509GeneralName
+#define RTASN1TMPL_INT_NAME rtCrX509GeneralName
+RTASN1TMPL_BEGIN_PCHOICE();
+RTASN1TMPL_PCHOICE_ITAG( 0, RTCRX509GENERALNAMECHOICE_OTHER_NAME, u.pT0_OtherName, OtherName, RTCRX509OTHERNAME, RTCrX509OtherName);
+RTASN1TMPL_PCHOICE_ITAG_CP( 1, RTCRX509GENERALNAMECHOICE_RFC822_NAME, u.pT1_Rfc822, Rfc822, RTASN1STRING, RTAsn1Ia5String);
+RTASN1TMPL_PCHOICE_ITAG_CP( 2, RTCRX509GENERALNAMECHOICE_DNS_NAME, u.pT2_DnsName, DnsType, RTASN1STRING, RTAsn1Ia5String);
+RTASN1TMPL_PCHOICE_XTAG( 3, RTCRX509GENERALNAMECHOICE_X400_ADDRESS, u.pT3, CtxTag3, X400Address, RTASN1DYNTYPE, RTAsn1DynType); /** @todo */
+RTASN1TMPL_PCHOICE_XTAG( 4, RTCRX509GENERALNAMECHOICE_DIRECTORY_NAME, u.pT4, CtxTag4, DirectoryName, RTCRX509NAME, RTCrX509Name);
+RTASN1TMPL_PCHOICE_XTAG( 5, RTCRX509GENERALNAMECHOICE_EDI_PARTY_NAME, u.pT5, CtxTag5, EdiPartyName, RTASN1DYNTYPE, RTAsn1DynType); /** @todo */
+RTASN1TMPL_PCHOICE_ITAG_CP( 6, RTCRX509GENERALNAMECHOICE_URI, u.pT6_Uri, Uri, RTASN1STRING, RTAsn1Ia5String);
+RTASN1TMPL_PCHOICE_ITAG_CP( 7, RTCRX509GENERALNAMECHOICE_IP_ADDRESS, u.pT7_IpAddress, IpAddress, RTASN1OCTETSTRING, RTAsn1OctetString); /** @todo Constraints */
+RTASN1TMPL_PCHOICE_ITAG_CP( 8, RTCRX509GENERALNAMECHOICE_REGISTERED_ID, u.pT8_RegisteredId,RegisteredId,RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_END_PCHOICE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Sequence of X.509 GeneralNames.
+ */
+#define RTASN1TMPL_TYPE RTCRX509GENERALNAMES
+#define RTASN1TMPL_EXT_NAME RTCrX509GeneralNames
+#define RTASN1TMPL_INT_NAME rtCrX509GeneralNames
+RTASN1TMPL_SEQ_OF(RTCRX509GENERALNAME, RTCrX509GeneralName);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * X.509 UniqueIdentifier - RTASN1BITSTRING alias.
+ */
+
+
+/*
+ * X.509 SubjectPublicKeyInfo.
+ */
+#define RTASN1TMPL_TYPE RTCRX509SUBJECTPUBLICKEYINFO
+#define RTASN1TMPL_EXT_NAME RTCrX509SubjectPublicKeyInfo
+#define RTASN1TMPL_INT_NAME rtCrX509SubjectPublicKeyInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Algorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER( SubjectPublicKey, RTASN1BITSTRING, RTAsn1BitString);
+RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrX509SubjectPublicKeyInfo_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) )
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * X.509 AuthorityKeyIdentifier (IPRT representation).
+ */
+#define RTASN1TMPL_TYPE RTCRX509AUTHORITYKEYIDENTIFIER
+#define RTASN1TMPL_EXT_NAME RTCrX509AuthorityKeyIdentifier
+#define RTASN1TMPL_INT_NAME rtCrX509AuthorityKeyIdentifier
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER_OPT_ITAG_CP( KeyIdentifier, RTASN1OCTETSTRING, RTAsn1OctetString, 0);
+RTASN1TMPL_MEMBER_OPT_ITAG( AuthorityCertIssuer, RTCRX509GENERALNAMES, RTCrX509GeneralNames, 1);
+RTASN1TMPL_MEMBER_OPT_ITAG_CP( AuthorityCertSerialNumber, RTASN1INTEGER, RTAsn1Integer, 2);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * X.509 OldAuthorityKeyIdentifier (IPRT representation).
+ */
+#define RTASN1TMPL_TYPE RTCRX509OLDAUTHORITYKEYIDENTIFIER
+#define RTASN1TMPL_EXT_NAME RTCrX509OldAuthorityKeyIdentifier
+#define RTASN1TMPL_INT_NAME rtCrX509OldAuthorityKeyIdentifier
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER_OPT_ITAG_CP( KeyIdentifier, RTASN1OCTETSTRING, RTAsn1OctetString, 0);
+RTASN1TMPL_MEMBER_OPT_XTAG( T1, CtxTag1, AuthorityCertIssuer, RTCRX509NAME, RTCrX509Name, 1);
+RTASN1TMPL_MEMBER_OPT_ITAG_CP( AuthorityCertSerialNumber, RTASN1INTEGER, RTAsn1Integer, 2);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One X.509 PolicyQualifierInfo.
+ */
+#define RTASN1TMPL_TYPE RTCRX509POLICYQUALIFIERINFO
+#define RTASN1TMPL_EXT_NAME RTCrX509PolicyQualifierInfo
+#define RTASN1TMPL_INT_NAME rtCrX509PolicyQualifierInfo
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( PolicyQualifierId, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER( Qualifier, RTASN1DYNTYPE, RTAsn1DynType);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Sequence of X.509 PolicyQualifierInfo.
+ */
+#define RTASN1TMPL_TYPE RTCRX509POLICYQUALIFIERINFOS
+#define RTASN1TMPL_EXT_NAME RTCrX509PolicyQualifierInfos
+#define RTASN1TMPL_INT_NAME rtCrX509PolicyQualifierInfos
+RTASN1TMPL_SEQ_OF(RTCRX509POLICYQUALIFIERINFO, RTCrX509PolicyQualifierInfo);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One X.509 PolicyInformation.
+ */
+#define RTASN1TMPL_TYPE RTCRX509POLICYINFORMATION
+#define RTASN1TMPL_EXT_NAME RTCrX509PolicyInformation
+#define RTASN1TMPL_INT_NAME rtCrX509PolicyInformation
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( PolicyIdentifier, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER_OPT_ITAG_UC( PolicyQualifiers, RTCRX509POLICYQUALIFIERINFOS, RTCrX509PolicyQualifierInfos, ASN1_TAG_SEQUENCE);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Sequence of X.509 CertificatePolicies.
+ */
+#define RTASN1TMPL_TYPE RTCRX509CERTIFICATEPOLICIES
+#define RTASN1TMPL_EXT_NAME RTCrX509CertificatePolicies
+#define RTASN1TMPL_INT_NAME rtCrX509CertificatePolicies
+RTASN1TMPL_SEQ_OF(RTCRX509POLICYINFORMATION, RTCrX509PolicyInformation);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One X.509 PolicyMapping (IPRT representation).
+ */
+#define RTASN1TMPL_TYPE RTCRX509POLICYMAPPING
+#define RTASN1TMPL_EXT_NAME RTCrX509PolicyMapping
+#define RTASN1TMPL_INT_NAME rtCrX509PolicyMapping
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( IssuerDomainPolicy, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER( SubjectDomainPolicy, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Sequence of X.509 PolicyMappings (IPRT representation).
+ */
+#define RTASN1TMPL_TYPE RTCRX509POLICYMAPPINGS
+#define RTASN1TMPL_EXT_NAME RTCrX509PolicyMappings
+#define RTASN1TMPL_INT_NAME rtCrX509PolicyMappings
+RTASN1TMPL_SEQ_OF(RTCRX509POLICYMAPPING, RTCrX509PolicyMapping);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * X.509 BasicConstraints (IPRT representation).
+ */
+#define RTASN1TMPL_TYPE RTCRX509BASICCONSTRAINTS
+#define RTASN1TMPL_EXT_NAME RTCrX509BasicConstraints
+#define RTASN1TMPL_INT_NAME rtCrX509BasicConstraints
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER_DEF_ITAG_UP( CA, RTASN1BOOLEAN, RTAsn1Boolean, ASN1_TAG_BOOLEAN, false);
+RTASN1TMPL_MEMBER_OPT_ITAG_UP( PathLenConstraint, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * X.509 GeneralSubtree (IPRT representation).
+ */
+#define RTASN1TMPL_TYPE RTCRX509GENERALSUBTREE
+#define RTASN1TMPL_EXT_NAME RTCrX509GeneralSubtree
+#define RTASN1TMPL_INT_NAME rtCrX509GeneralSubtree
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( Base, RTCRX509GENERALNAME, RTCrX509GeneralName);
+RTASN1TMPL_MEMBER_DEF_ITAG_UP( Minimum, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER, 0);
+RTASN1TMPL_MEMBER_OPT_ITAG_UP( Maximum, RTASN1INTEGER, RTAsn1Integer, ASN1_TAG_INTEGER);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+
+/*
+ * Sequence of X.509 GeneralSubtrees (IPRT representation).
+ */
+#define RTASN1TMPL_TYPE RTCRX509GENERALSUBTREES
+#define RTASN1TMPL_EXT_NAME RTCrX509GeneralSubtrees
+#define RTASN1TMPL_INT_NAME rtCrX509GeneralSubtrees
+RTASN1TMPL_SEQ_OF(RTCRX509GENERALSUBTREE, RTCrX509GeneralSubtree);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * X.509 NameConstraints (IPRT representation).
+ */
+#define RTASN1TMPL_TYPE RTCRX509NAMECONSTRAINTS
+#define RTASN1TMPL_EXT_NAME RTCrX509NameConstraints
+#define RTASN1TMPL_INT_NAME rtCrX509NameConstraints
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER_OPT_XTAG( T0, CtxTag0, PermittedSubtrees, RTCRX509GENERALSUBTREES, RTCrX509GeneralSubtrees, 0);
+RTASN1TMPL_MEMBER_OPT_XTAG( T1, CtxTag1, ExcludedSubtrees, RTCRX509GENERALSUBTREES, RTCrX509GeneralSubtrees, 1);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * X.509 PolicyConstraints (IPRT representation).
+ */
+#define RTASN1TMPL_TYPE RTCRX509POLICYCONSTRAINTS
+#define RTASN1TMPL_EXT_NAME RTCrX509PolicyConstraints
+#define RTASN1TMPL_INT_NAME rtCrX509PolicyConstraints
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER_OPT_ITAG_CP( RequireExplicitPolicy, RTASN1INTEGER, RTAsn1Integer, 0);
+RTASN1TMPL_MEMBER_OPT_ITAG_CP( InhibitPolicyMapping, RTASN1INTEGER, RTAsn1Integer, 1);
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One X.509 Extension.
+ */
+#define RTASN1TMPL_TYPE RTCRX509EXTENSION
+#define RTASN1TMPL_EXT_NAME RTCrX509Extension
+#define RTASN1TMPL_INT_NAME rtCrX509Extension
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( ExtnId, RTASN1OBJID, RTAsn1ObjId);
+RTASN1TMPL_MEMBER_DEF_ITAG_UP( Critical, RTASN1BOOLEAN, RTAsn1Boolean, ASN1_TAG_BOOLEAN, false);
+RTASN1TMPL_MEMBER( ExtnValue, RTASN1OCTETSTRING, RTAsn1OctetString);
+RTASN1TMPL_EXEC_DECODE(rc = RTCrX509Extension_ExtnValue_DecodeAsn1(pCursor, fFlags, pThis, "ExtnValue"))
+RTASN1TMPL_EXEC_CLONE( rc = rtCrX509Extension_ExtnValue_Clone(pThis, pSrc))
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Sequence of X.509 Extensions.
+ */
+#define RTASN1TMPL_TYPE RTCRX509EXTENSIONS
+#define RTASN1TMPL_EXT_NAME RTCrX509Extensions
+#define RTASN1TMPL_INT_NAME rtCrX509Extensions
+RTASN1TMPL_SEQ_OF(RTCRX509EXTENSION, RTCrX509Extension);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * X.509 TbsCertificate.
+ */
+#define RTASN1TMPL_TYPE RTCRX509TBSCERTIFICATE
+#define RTASN1TMPL_EXT_NAME RTCrX509TbsCertificate
+#define RTASN1TMPL_INT_NAME rtCrX509TbsCertificate
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER_OPT_XTAG( T0, CtxTag0, Version, RTASN1INTEGER, RTAsn1Integer, 0);
+RTASN1TMPL_MEMBER( SerialNumber, RTASN1INTEGER, RTAsn1Integer);
+RTASN1TMPL_MEMBER( Signature, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER( Issuer, RTCRX509NAME, RTCrX509Name);
+RTASN1TMPL_MEMBER( Validity, RTCRX509VALIDITY, RTCrX509Validity);
+RTASN1TMPL_MEMBER( Subject, RTCRX509NAME, RTCrX509Name);
+RTASN1TMPL_MEMBER( SubjectPublicKeyInfo, RTCRX509SUBJECTPUBLICKEYINFO, RTCrX509SubjectPublicKeyInfo);
+RTASN1TMPL_MEMBER_OPT_XTAG( T1, CtxTag1, IssuerUniqueId, RTCRX509UNIQUEIDENTIFIER, RTCrX509UniqueIdentifier, 1);
+RTASN1TMPL_MEMBER_OPT_XTAG( T2, CtxTag2, SubjectUniqueId, RTCRX509UNIQUEIDENTIFIER, RTCrX509UniqueIdentifier, 2);
+RTASN1TMPL_MEMBER_OPT_XTAG( T3, CtxTag3, Extensions, RTCRX509EXTENSIONS, RTCrX509Extensions, 3);
+RTASN1TMPL_EXEC_DECODE( rc = RTCrX509TbsCertificate_ReprocessExtensions(pThis, pCursor->pPrimary->pErrInfo) )
+RTASN1TMPL_EXEC_CLONE( rc = RTCrX509TbsCertificate_ReprocessExtensions(pThis, NULL) )
+RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrX509TbsCertificate_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) )
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * One X.509 Certificate.
+ */
+#define RTASN1TMPL_TYPE RTCRX509CERTIFICATE
+#define RTASN1TMPL_EXT_NAME RTCrX509Certificate
+#define RTASN1TMPL_INT_NAME rtCrX509Certificate
+RTASN1TMPL_BEGIN_SEQCORE();
+RTASN1TMPL_MEMBER( TbsCertificate, RTCRX509TBSCERTIFICATE, RTCrX509TbsCertificate);
+RTASN1TMPL_MEMBER( SignatureAlgorithm, RTCRX509ALGORITHMIDENTIFIER, RTCrX509AlgorithmIdentifier);
+RTASN1TMPL_MEMBER( SignatureValue, RTASN1BITSTRING, RTAsn1BitString);
+RTASN1TMPL_EXEC_CHECK_SANITY( rc = rtCrX509Certificate_CheckSanityExtra(pThis, fFlags, pErrInfo, pszErrorTag) )
+RTASN1TMPL_END_SEQCORE();
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
+
+/*
+ * Set of X.509 Certificates.
+ */
+/** @todo Microsoft Hacks. ExtendedCertificates. */
+#define RTASN1TMPL_TYPE RTCRX509CERTIFICATES
+#define RTASN1TMPL_EXT_NAME RTCrX509Certificates
+#define RTASN1TMPL_INT_NAME rtCrX509Certificates
+RTASN1TMPL_SET_OF(RTCRX509CERTIFICATE, RTCrX509Certificate);
+#undef RTASN1TMPL_TYPE
+#undef RTASN1TMPL_EXT_NAME
+#undef RTASN1TMPL_INT_NAME
+
diff --git a/src/VBox/Runtime/common/crypto/x509-verify.cpp b/src/VBox/Runtime/common/crypto/x509-verify.cpp
new file mode 100644
index 00000000..e2b42c08
--- /dev/null
+++ b/src/VBox/Runtime/common/crypto/x509-verify.cpp
@@ -0,0 +1,131 @@
+/* $Id: x509-verify.cpp $ */
+/** @file
+ * IPRT - Crypto - X.509, Signature verification.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/crypto/x509.h>
+#include <iprt/crypto/pkix.h>
+#include <iprt/crypto/key.h>
+
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTCrX509Certificate_VerifySignature(PCRTCRX509CERTIFICATE pThis, PCRTASN1OBJID pAlgorithm,
+ PCRTASN1DYNTYPE pParameters, PCRTASN1BITSTRING pPublicKey,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate the input a little.
+ */
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertReturn(RTCrX509Certificate_IsPresent(pThis), VERR_INVALID_PARAMETER);
+
+ AssertPtrReturn(pAlgorithm, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1ObjId_IsPresent(pAlgorithm), VERR_INVALID_POINTER);
+
+ AssertPtrReturn(pPublicKey, VERR_INVALID_POINTER);
+ AssertReturn(RTAsn1BitString_IsPresent(pPublicKey), VERR_INVALID_POINTER);
+
+ /*
+ * Check if the algorithm matches.
+ */
+ const char * const pszCipherOid = RTCrX509AlgorithmIdentifier_GetEncryptionOid(&pThis->SignatureAlgorithm,
+ true /*fMustIncludeHash*/);
+ if (!pszCipherOid)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_UNKNOWN_CERT_SIGN_ALGO,
+ "Certificate signature algorithm not known: %s",
+ pThis->SignatureAlgorithm.Algorithm.szObjId);
+
+ if (RTAsn1ObjId_CompareWithString(pAlgorithm, pszCipherOid) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_CR_X509_CERT_SIGN_ALGO_MISMATCH,
+ "Certificate signature cipher algorithm mismatch: cert uses %s (%s) while key uses %s",
+ pszCipherOid, pThis->SignatureAlgorithm.Algorithm.szObjId, pAlgorithm->szObjId);
+
+ /*
+ * Wrap up the public key.
+ */
+ RTCRKEY hPubKey;
+ int rc = RTCrKeyCreateFromPublicAlgorithmAndBits(&hPubKey, pAlgorithm, pParameters, pPublicKey, pErrInfo, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Here we should recode the to-be-signed part as DER, but we'll ASSUME
+ * that it's already in DER encoding and only does this if there the
+ * encoded bits are missing.
+ */
+ const uint8_t *pbRaw;
+ uint32_t cbRaw;
+ void *pvFree = NULL;
+ rc = RTAsn1EncodeQueryRawBits(RTCrX509TbsCertificate_GetAsn1Core(&pThis->TbsCertificate), &pbRaw, &cbRaw, &pvFree, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrPkixPubKeyVerifySignature(&pThis->SignatureAlgorithm.Algorithm, hPubKey, &pThis->SignatureAlgorithm.Parameters,
+ &pThis->SignatureValue, pbRaw, cbRaw, pErrInfo);
+ RTMemTmpFree(pvFree);
+ }
+
+ /* Free the public key. */
+ uint32_t cRefs = RTCrKeyRelease(hPubKey);
+ Assert(cRefs == 0); NOREF(cRefs);
+
+ return rc;
+}
+
+
+RTDECL(int) RTCrX509Certificate_VerifySignatureSelfSigned(PCRTCRX509CERTIFICATE pThis, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate the input a little.
+ */
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertReturn(RTCrX509Certificate_IsPresent(pThis), VERR_INVALID_PARAMETER);
+
+ /*
+ * Call generic verification function.
+ */
+ PCRTCRX509TBSCERTIFICATE const pTbsCert = &pThis->TbsCertificate;
+ return RTCrX509Certificate_VerifySignature(pThis,
+ &pTbsCert->SubjectPublicKeyInfo.Algorithm.Algorithm,
+ &pTbsCert->SubjectPublicKeyInfo.Algorithm.Parameters,
+ &pTbsCert->SubjectPublicKeyInfo.SubjectPublicKey, pErrInfo);
+}
+
diff --git a/src/VBox/Runtime/common/dbg/Makefile.kup b/src/VBox/Runtime/common/dbg/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/Makefile.kup
diff --git a/src/VBox/Runtime/common/dbg/dbg.cpp b/src/VBox/Runtime/common/dbg/dbg.cpp
new file mode 100644
index 00000000..95b95353
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbg.cpp
@@ -0,0 +1,122 @@
+/* $Id: dbg.cpp $ */
+/** @file
+ * IPRT - Debug Misc.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/mem.h>
+
+
+
+/**
+ * Allocate a new symbol structure.
+ *
+ * @returns Pointer to a new structure on success, NULL on failure.
+ */
+RTDECL(PRTDBGSYMBOL) RTDbgSymbolAlloc(void)
+{
+ return (PRTDBGSYMBOL)RTMemAllocZ(sizeof(RTDBGSYMBOL));
+}
+RT_EXPORT_SYMBOL(RTDbgSymbolAlloc);
+
+
+/**
+ * Duplicates a symbol structure.
+ *
+ * @returns Pointer to duplicate on success, NULL on failure.
+ *
+ * @param pSymInfo The symbol info to duplicate.
+ */
+RTDECL(PRTDBGSYMBOL) RTDbgSymbolDup(PCRTDBGSYMBOL pSymInfo)
+{
+ return (PRTDBGSYMBOL)RTMemDup(pSymInfo, sizeof(*pSymInfo));
+}
+RT_EXPORT_SYMBOL(RTDbgSymbolDup);
+
+
+/**
+ * Free a symbol structure previously allocated by a RTDbg method.
+ *
+ * @param pSymInfo The symbol info to free. NULL is ignored.
+ */
+RTDECL(void) RTDbgSymbolFree(PRTDBGSYMBOL pSymInfo)
+{
+ RTMemFree(pSymInfo);
+}
+RT_EXPORT_SYMBOL(RTDbgSymbolFree);
+
+
+/**
+ * Allocate a new line number structure.
+ *
+ * @returns Pointer to a new structure on success, NULL on failure.
+ */
+RTDECL(PRTDBGLINE) RTDbgLineAlloc(void)
+{
+ return (PRTDBGLINE)RTMemAllocZ(sizeof(RTDBGLINE));
+}
+RT_EXPORT_SYMBOL(RTDbgLineAlloc);
+
+
+/**
+ * Duplicates a line number structure.
+ *
+ * @returns Pointer to duplicate on success, NULL on failure.
+ *
+ * @param pLine The line number to duplicate.
+ */
+RTDECL(PRTDBGLINE) RTDbgLineDup(PCRTDBGLINE pLine)
+{
+ return (PRTDBGLINE)RTMemDup(pLine, sizeof(*pLine));
+}
+RT_EXPORT_SYMBOL(RTDbgLineDup);
+
+
+/**
+ * Free a line number structure previously allocated by a RTDbg method.
+ *
+ * @param pLine The line number to free. NULL is ignored.
+ */
+RTDECL(void) RTDbgLineFree(PRTDBGLINE pLine)
+{
+ RTMemFree(pLine);
+}
+RT_EXPORT_SYMBOL(RTDbgLineFree);
+
diff --git a/src/VBox/Runtime/common/dbg/dbgas.cpp b/src/VBox/Runtime/common/dbg/dbgas.cpp
new file mode 100644
index 00000000..cd45a676
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgas.cpp
@@ -0,0 +1,1513 @@
+/* $Id: dbgas.cpp $ */
+/** @file
+ * IPRT - Debug Address Space.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a module table entry. */
+typedef struct RTDBGASMOD *PRTDBGASMOD;
+/** Pointer to an address space mapping node. */
+typedef struct RTDBGASMAP *PRTDBGASMAP;
+/** Pointer to a name head. */
+typedef struct RTDBGASNAME *PRTDBGASNAME;
+
+/**
+ * Module entry.
+ */
+typedef struct RTDBGASMOD
+{
+ /** Node core, the module handle is the key. */
+ AVLPVNODECORE Core;
+ /** Pointer to the first mapping of the module or a segment within it. */
+ PRTDBGASMAP pMapHead;
+ /** Pointer to the next module with an identical name. */
+ PRTDBGASMOD pNextName;
+ /** The index into RTDBGASINT::papModules. */
+ uint32_t iOrdinal;
+} RTDBGASMOD;
+
+/**
+ * An address space mapping, either of a full module or a segment.
+ */
+typedef struct RTDBGASMAP
+{
+ /** The AVL node core. Contains the address range. */
+ AVLRUINTPTRNODECORE Core;
+ /** Pointer to the next mapping of the module. */
+ PRTDBGASMAP pNext;
+ /** Pointer to the module. */
+ PRTDBGASMOD pMod;
+ /** Which segment in the module.
+ * This is NIL_RTDBGSEGIDX when the entire module is mapped. */
+ RTDBGSEGIDX iSeg;
+} RTDBGASMAP;
+
+/**
+ * Name in the address space.
+ */
+typedef struct RTDBGASNAME
+{
+ /** The string space node core.*/
+ RTSTRSPACECORE StrCore;
+ /** The list of nodes */
+ PRTDBGASMOD pHead;
+} RTDBGASNAME;
+
+/**
+ * Debug address space instance.
+ */
+typedef struct RTDBGASINT
+{
+ /** Magic value (RTDBGAS_MAGIC). */
+ uint32_t u32Magic;
+ /** The number of reference to this address space. */
+ uint32_t volatile cRefs;
+ /** Handle of the read-write lock. */
+ RTSEMRW hLock;
+ /** Number of modules in the module address space. */
+ uint32_t cModules;
+ /** Pointer to the module table.
+ * The valid array length is given by cModules. */
+ PRTDBGASMOD *papModules;
+ /** AVL tree translating module handles to module entries. */
+ AVLPVTREE ModTree;
+ /** AVL tree mapping addresses to modules. */
+ AVLRUINTPTRTREE MapTree;
+ /** Names of the modules in the name space. */
+ RTSTRSPACE NameSpace;
+ /** The first address the AS. */
+ RTUINTPTR FirstAddr;
+ /** The last address in the AS. */
+ RTUINTPTR LastAddr;
+ /** The name of the address space. (variable length) */
+ char szName[1];
+} RTDBGASINT;
+/** Pointer to an a debug address space instance. */
+typedef RTDBGASINT *PRTDBGASINT;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Validates an address space handle and returns rc if not valid. */
+#define RTDBGAS_VALID_RETURN_RC(pDbgAs, rc) \
+ do { \
+ AssertPtrReturn((pDbgAs), (rc)); \
+ AssertReturn((pDbgAs)->u32Magic == RTDBGAS_MAGIC, (rc)); \
+ AssertReturn((pDbgAs)->cRefs > 0, (rc)); \
+ } while (0)
+
+/** Locks the address space for reading. */
+#define RTDBGAS_LOCK_READ(pDbgAs) \
+ do { \
+ int rcLock = RTSemRWRequestRead((pDbgAs)->hLock, RT_INDEFINITE_WAIT); \
+ AssertRC(rcLock); \
+ } while (0)
+
+/** Unlocks the address space after reading. */
+#define RTDBGAS_UNLOCK_READ(pDbgAs) \
+ do { \
+ int rcLock = RTSemRWReleaseRead((pDbgAs)->hLock); \
+ AssertRC(rcLock); \
+ } while (0)
+
+/** Locks the address space for writing. */
+#define RTDBGAS_LOCK_WRITE(pDbgAs) \
+ do { \
+ int rcLock = RTSemRWRequestWrite((pDbgAs)->hLock, RT_INDEFINITE_WAIT); \
+ AssertRC(rcLock); \
+ } while (0)
+
+/** Unlocks the address space after writing. */
+#define RTDBGAS_UNLOCK_WRITE(pDbgAs) \
+ do { \
+ int rcLock = RTSemRWReleaseWrite((pDbgAs)->hLock); \
+ AssertRC(rcLock); \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtDbgAsModuleUnlinkMod(PRTDBGASINT pDbgAs, PRTDBGASMOD pMod);
+static void rtDbgAsModuleUnlinkByMap(PRTDBGASINT pDbgAs, PRTDBGASMAP pMap);
+
+
+RTDECL(int) RTDbgAsCreate(PRTDBGAS phDbgAs, RTUINTPTR FirstAddr, RTUINTPTR LastAddr, const char *pszName)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phDbgAs, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertReturn(FirstAddr < LastAddr, VERR_INVALID_PARAMETER);
+
+ /*
+ * Allocate memory for the instance data.
+ */
+ size_t cchName = strlen(pszName);
+ PRTDBGASINT pDbgAs = (PRTDBGASINT)RTMemAllocVar(RT_UOFFSETOF_DYN(RTDBGASINT, szName[cchName + 1]));
+ if (!pDbgAs)
+ return VERR_NO_MEMORY;
+
+ /* initialize it. */
+ pDbgAs->u32Magic = RTDBGAS_MAGIC;
+ pDbgAs->cRefs = 1;
+ pDbgAs->hLock = NIL_RTSEMRW;
+ pDbgAs->cModules = 0;
+ pDbgAs->papModules = NULL;
+ pDbgAs->ModTree = NULL;
+ pDbgAs->MapTree = NULL;
+ pDbgAs->NameSpace = NULL;
+ pDbgAs->FirstAddr = FirstAddr;
+ pDbgAs->LastAddr = LastAddr;
+ memcpy(pDbgAs->szName, pszName, cchName + 1);
+ int rc = RTSemRWCreate(&pDbgAs->hLock);
+ if (RT_SUCCESS(rc))
+ {
+ *phDbgAs = pDbgAs;
+ return VINF_SUCCESS;
+ }
+
+ pDbgAs->u32Magic = 0;
+ RTMemFree(pDbgAs);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsCreate);
+
+
+RTDECL(int) RTDbgAsCreateV(PRTDBGAS phDbgAs, RTUINTPTR FirstAddr, RTUINTPTR LastAddr, const char *pszNameFmt, va_list va)
+{
+ AssertPtrReturn(pszNameFmt, VERR_INVALID_POINTER);
+
+ char *pszName;
+ RTStrAPrintfV(&pszName, pszNameFmt, va);
+ if (!pszName)
+ return VERR_NO_MEMORY;
+
+ int rc = RTDbgAsCreate(phDbgAs, FirstAddr, LastAddr, pszName);
+
+ RTStrFree(pszName);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsCreateV);
+
+
+RTDECL(int) RTDbgAsCreateF(PRTDBGAS phDbgAs, RTUINTPTR FirstAddr, RTUINTPTR LastAddr, const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTDbgAsCreateV(phDbgAs, FirstAddr, LastAddr, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsCreateF);
+
+
+/**
+ * Callback used by RTDbgAsDestroy to free all mapping nodes.
+ *
+ * @returns 0
+ * @param pNode The map node.
+ * @param pvUser NULL.
+ */
+static DECLCALLBACK(int) rtDbgAsDestroyMapCallback(PAVLRUINTPTRNODECORE pNode, void *pvUser)
+{
+ RTMemFree(pNode);
+ NOREF(pvUser);
+ return 0;
+}
+
+
+/**
+ * Callback used by RTDbgAsDestroy to free all name space nodes.
+ *
+ * @returns 0
+ * @param pStr The name node.
+ * @param pvUser NULL.
+ */
+static DECLCALLBACK(int) rtDbgAsDestroyNameCallback(PRTSTRSPACECORE pStr, void *pvUser)
+{
+ RTMemFree(pStr);
+ NOREF(pvUser);
+ return 0;
+}
+
+
+/**
+ * Destroys the address space.
+ *
+ * This means unlinking all the modules it currently contains, potentially
+ * causing some or all of them to be destroyed as they are managed by
+ * reference counting.
+ *
+ * @param pDbgAs The address space instance to be destroyed.
+ */
+static void rtDbgAsDestroy(PRTDBGASINT pDbgAs)
+{
+ /*
+ * Mark the address space invalid and release all the modules.
+ */
+ ASMAtomicWriteU32(&pDbgAs->u32Magic, ~RTDBGAS_MAGIC);
+
+ RTAvlrUIntPtrDestroy(&pDbgAs->MapTree, rtDbgAsDestroyMapCallback, NULL);
+ RTStrSpaceDestroy(&pDbgAs->NameSpace, rtDbgAsDestroyNameCallback, NULL);
+
+ uint32_t i = pDbgAs->cModules;
+ while (i-- > 0)
+ {
+ PRTDBGASMOD pMod = pDbgAs->papModules[i];
+ AssertPtr(pMod);
+ if (RT_VALID_PTR(pMod))
+ {
+ Assert(pMod->iOrdinal == i);
+ RTDbgModRelease((RTDBGMOD)pMod->Core.Key);
+ pMod->Core.Key = NIL_RTDBGMOD;
+ pMod->iOrdinal = UINT32_MAX;
+ RTMemFree(pMod);
+ }
+ pDbgAs->papModules[i] = NULL;
+ }
+ RTSemRWDestroy(pDbgAs->hLock);
+ pDbgAs->hLock = NIL_RTSEMRW;
+ RTMemFree(pDbgAs->papModules);
+ pDbgAs->papModules = NULL;
+
+ RTMemFree(pDbgAs);
+}
+
+
+RTDECL(uint32_t) RTDbgAsRetain(RTDBGAS hDbgAs)
+{
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, UINT32_MAX);
+ return ASMAtomicIncU32(&pDbgAs->cRefs);
+}
+RT_EXPORT_SYMBOL(RTDbgAsRetain);
+
+
+RTDECL(uint32_t) RTDbgAsRelease(RTDBGAS hDbgAs)
+{
+ if (hDbgAs == NIL_RTDBGAS)
+ return 0;
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pDbgAs->cRefs);
+ if (!cRefs)
+ rtDbgAsDestroy(pDbgAs);
+ return cRefs;
+}
+RT_EXPORT_SYMBOL(RTDbgAsRelease);
+
+
+RTDECL(int) RTDbgAsLockExcl(RTDBGAS hDbgAs)
+{
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ RTDBGAS_LOCK_WRITE(pDbgAs);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTDbgAsLockExcl);
+
+
+RTDECL(int) RTDbgAsUnlockExcl(RTDBGAS hDbgAs)
+{
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ RTDBGAS_UNLOCK_WRITE(pDbgAs);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTDbgAsUnlockExcl);
+
+
+RTDECL(const char *) RTDbgAsName(RTDBGAS hDbgAs)
+{
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, NULL);
+ return pDbgAs->szName;
+}
+RT_EXPORT_SYMBOL(RTDbgAsName);
+
+
+RTDECL(RTUINTPTR) RTDbgAsFirstAddr(RTDBGAS hDbgAs)
+{
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, 0);
+ return pDbgAs->FirstAddr;
+}
+RT_EXPORT_SYMBOL(RTDbgAsFirstAddr);
+
+
+RTDECL(RTUINTPTR) RTDbgAsLastAddr(RTDBGAS hDbgAs)
+{
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, 0);
+ return pDbgAs->LastAddr;
+}
+RT_EXPORT_SYMBOL(RTDbgAsLastAddr);
+
+
+RTDECL(uint32_t) RTDbgAsModuleCount(RTDBGAS hDbgAs)
+{
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, 0);
+ return pDbgAs->cModules;
+}
+RT_EXPORT_SYMBOL(RTDbgAsModuleCount);
+
+
+/**
+ * Common worker for RTDbgAsModuleLink and RTDbgAsModuleLinkSeg.
+ *
+ * @returns IPRT status code.
+ * @param pDbgAs Pointer to the address space instance data.
+ * @param hDbgMod The module to link.
+ * @param iSeg The segment to link or NIL if all.
+ * @param Addr The address we're linking it at.
+ * @param cb The size of what we're linking.
+ * @param pszName The name of the module.
+ * @param fFlags See RTDBGASLINK_FLAGS_*.
+ *
+ * @remarks The caller must have locked the address space for writing.
+ */
+int rtDbgAsModuleLinkCommon(PRTDBGASINT pDbgAs, RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg,
+ RTUINTPTR Addr, RTUINTPTR cb, const char *pszName, uint32_t fFlags)
+{
+ /*
+ * Check that the requested space is undisputed.
+ */
+ for (;;)
+ {
+ PRTDBGASMAP pAdjMod = (PRTDBGASMAP)RTAvlrUIntPtrGetBestFit(&pDbgAs->MapTree, Addr, false /* fAbove */);
+ if ( pAdjMod
+ && pAdjMod->Core.KeyLast >= Addr)
+ {
+ if (!(fFlags & RTDBGASLINK_FLAGS_REPLACE))
+ return VERR_ADDRESS_CONFLICT;
+ rtDbgAsModuleUnlinkByMap(pDbgAs, pAdjMod);
+ continue;
+ }
+ pAdjMod = (PRTDBGASMAP)RTAvlrUIntPtrGetBestFit(&pDbgAs->MapTree, Addr, true /* fAbove */);
+ if ( pAdjMod
+ && pAdjMod->Core.Key <= Addr + cb - 1)
+ {
+ if (!(fFlags & RTDBGASLINK_FLAGS_REPLACE))
+ return VERR_ADDRESS_CONFLICT;
+ rtDbgAsModuleUnlinkByMap(pDbgAs, pAdjMod);
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * First, create or find the module table entry.
+ */
+ PRTDBGASMOD pMod = (PRTDBGASMOD)RTAvlPVGet(&pDbgAs->ModTree, hDbgMod);
+ if (!pMod)
+ {
+ /*
+ * Ok, we need a new entry. Grow the table if necessary.
+ */
+ if (!(pDbgAs->cModules % 32))
+ {
+ void *pvNew = RTMemRealloc(pDbgAs->papModules, sizeof(pDbgAs->papModules[0]) * (pDbgAs->cModules + 32));
+ if (!pvNew)
+ return VERR_NO_MEMORY;
+ pDbgAs->papModules = (PRTDBGASMOD *)pvNew;
+ }
+ pMod = (PRTDBGASMOD)RTMemAlloc(sizeof(*pMod));
+ if (!pMod)
+ return VERR_NO_MEMORY;
+ pMod->Core.Key = hDbgMod;
+ pMod->pMapHead = NULL;
+ pMod->pNextName = NULL;
+ if (RT_UNLIKELY(!RTAvlPVInsert(&pDbgAs->ModTree, &pMod->Core)))
+ {
+ AssertFailed();
+ pDbgAs->cModules--;
+ RTMemFree(pMod);
+ return VERR_INTERNAL_ERROR;
+ }
+ pMod->iOrdinal = pDbgAs->cModules;
+ pDbgAs->papModules[pDbgAs->cModules] = pMod;
+ pDbgAs->cModules++;
+ RTDbgModRetain(hDbgMod);
+
+ /*
+ * Add it to the name space.
+ */
+ PRTDBGASNAME pName = (PRTDBGASNAME)RTStrSpaceGet(&pDbgAs->NameSpace, pszName);
+ if (!pName)
+ {
+ size_t cchName = strlen(pszName);
+ pName = (PRTDBGASNAME)RTMemAlloc(sizeof(*pName) + cchName + 1);
+ if (!pName)
+ {
+ RTDbgModRelease(hDbgMod);
+ pDbgAs->cModules--;
+ RTAvlPVRemove(&pDbgAs->ModTree, hDbgMod);
+ RTMemFree(pMod);
+ return VERR_NO_MEMORY;
+ }
+ pName->StrCore.cchString = cchName;
+ pName->StrCore.pszString = (char *)memcpy(pName + 1, pszName, cchName + 1);
+ pName->pHead = pMod;
+ if (!RTStrSpaceInsert(&pDbgAs->NameSpace, &pName->StrCore))
+ AssertFailed();
+ }
+ else
+ {
+ /* quick, but unfair. */
+ pMod->pNextName = pName->pHead;
+ pName->pHead = pMod;
+ }
+ }
+
+ /*
+ * Create a mapping node.
+ */
+ int rc;
+ PRTDBGASMAP pMap = (PRTDBGASMAP)RTMemAlloc(sizeof(*pMap));
+ if (pMap)
+ {
+ pMap->Core.Key = Addr;
+ pMap->Core.KeyLast = Addr + cb - 1;
+ pMap->pMod = pMod;
+ pMap->iSeg = iSeg;
+ if (RTAvlrUIntPtrInsert(&pDbgAs->MapTree, &pMap->Core))
+ {
+ PRTDBGASMAP *pp = &pMod->pMapHead;
+ while (*pp && (*pp)->Core.Key < Addr)
+ pp = &(*pp)->pNext;
+ pMap->pNext = *pp;
+ *pp = pMap;
+ return VINF_SUCCESS;
+ }
+
+ AssertFailed();
+ RTMemFree(pMap);
+ rc = VERR_ADDRESS_CONFLICT;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /*
+ * Unlink the module if this was the only mapping.
+ */
+ if (!pMod->pMapHead)
+ rtDbgAsModuleUnlinkMod(pDbgAs, pMod);
+ return rc;
+}
+
+
+RTDECL(int) RTDbgAsModuleLink(RTDBGAS hDbgAs, RTDBGMOD hDbgMod, RTUINTPTR ImageAddr, uint32_t fFlags)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ const char *pszName = RTDbgModName(hDbgMod);
+ if (!pszName)
+ return VERR_INVALID_HANDLE;
+ RTUINTPTR cb = RTDbgModImageSize(hDbgMod);
+ if (!cb)
+ return VERR_OUT_OF_RANGE;
+ if ( ImageAddr < pDbgAs->FirstAddr
+ || ImageAddr > pDbgAs->LastAddr
+ || ImageAddr + cb - 1 < pDbgAs->FirstAddr
+ || ImageAddr + cb - 1 > pDbgAs->LastAddr
+ || ImageAddr + cb - 1 < ImageAddr)
+ return VERR_OUT_OF_RANGE;
+ AssertReturn(!(fFlags & ~RTDBGASLINK_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ /*
+ * Invoke worker common with RTDbgAsModuleLinkSeg.
+ */
+ RTDBGAS_LOCK_WRITE(pDbgAs);
+ int rc = rtDbgAsModuleLinkCommon(pDbgAs, hDbgMod, NIL_RTDBGSEGIDX, ImageAddr, cb, pszName, fFlags);
+ RTDBGAS_UNLOCK_WRITE(pDbgAs);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsModuleLink);
+
+
+RTDECL(int) RTDbgAsModuleLinkSeg(RTDBGAS hDbgAs, RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg, RTUINTPTR SegAddr, uint32_t fFlags)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ const char *pszName = RTDbgModName(hDbgMod);
+ if (!pszName)
+ return VERR_INVALID_HANDLE;
+ RTUINTPTR cb = RTDbgModSegmentSize(hDbgMod, iSeg);
+ if (!cb)
+ return VERR_OUT_OF_RANGE;
+ if ( SegAddr < pDbgAs->FirstAddr
+ || SegAddr > pDbgAs->LastAddr
+ || SegAddr + cb - 1 < pDbgAs->FirstAddr
+ || SegAddr + cb - 1 > pDbgAs->LastAddr
+ || SegAddr + cb - 1 < SegAddr)
+ return VERR_OUT_OF_RANGE;
+ AssertReturn(!(fFlags & ~RTDBGASLINK_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ /*
+ * Invoke worker common with RTDbgAsModuleLinkSeg.
+ */
+ RTDBGAS_LOCK_WRITE(pDbgAs);
+ int rc = rtDbgAsModuleLinkCommon(pDbgAs, hDbgMod, iSeg, SegAddr, cb, pszName, fFlags);
+ RTDBGAS_UNLOCK_WRITE(pDbgAs);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsModuleLinkSeg);
+
+
+/**
+ * Worker for RTDbgAsModuleUnlink, RTDbgAsModuleUnlinkByAddr and rtDbgAsModuleLinkCommon.
+ *
+ * @param pDbgAs Pointer to the address space instance data.
+ * @param pMod The module to unlink.
+ *
+ * @remarks The caller must have locked the address space for writing.
+ */
+static void rtDbgAsModuleUnlinkMod(PRTDBGASINT pDbgAs, PRTDBGASMOD pMod)
+{
+ Assert(!pMod->pMapHead);
+
+ /*
+ * Unlink it from the name.
+ */
+ const char *pszName = RTDbgModName((RTDBGMOD)pMod->Core.Key);
+ PRTDBGASNAME pName = (PRTDBGASNAME)RTStrSpaceGet(&pDbgAs->NameSpace, pszName);
+ AssertReturnVoid(pName);
+
+ if (pName->pHead == pMod)
+ pName->pHead = pMod->pNextName;
+ else
+ for (PRTDBGASMOD pCur = pName->pHead; pCur; pCur = pCur->pNextName)
+ if (pCur->pNextName == pMod)
+ {
+ pCur->pNextName = pMod->pNextName;
+ break;
+ }
+ pMod->pNextName = NULL;
+
+ /*
+ * Free the name if this was the last reference to it.
+ */
+ if (!pName->pHead)
+ {
+ pName = (PRTDBGASNAME)RTStrSpaceRemove(&pDbgAs->NameSpace, pName->StrCore.pszString);
+ Assert(pName);
+ RTMemFree(pName);
+ }
+
+ /*
+ * Remove it from the module handle tree.
+ */
+ PAVLPVNODECORE pNode = RTAvlPVRemove(&pDbgAs->ModTree, pMod->Core.Key);
+ Assert(pNode == &pMod->Core); NOREF(pNode);
+
+ /*
+ * Remove it from the module table by replacing it by the last entry.
+ */
+ pDbgAs->cModules--;
+ uint32_t iMod = pMod->iOrdinal;
+ Assert(iMod <= pDbgAs->cModules);
+ if (iMod != pDbgAs->cModules)
+ {
+ PRTDBGASMOD pTailMod = pDbgAs->papModules[pDbgAs->cModules];
+ pTailMod->iOrdinal = iMod;
+ pDbgAs->papModules[iMod] = pTailMod;
+ }
+ pMod->iOrdinal = UINT32_MAX;
+
+ /*
+ * Free it.
+ */
+ RTMemFree(pMod);
+}
+
+
+/**
+ * Worker for RTDbgAsModuleUnlink and RTDbgAsModuleUnlinkByAddr.
+ *
+ * @param pDbgAs Pointer to the address space instance data.
+ * @param pMap The map to unlink and free.
+ *
+ * @remarks The caller must have locked the address space for writing.
+ */
+static void rtDbgAsModuleUnlinkMap(PRTDBGASINT pDbgAs, PRTDBGASMAP pMap)
+{
+ /* remove from the tree */
+ PAVLRUINTPTRNODECORE pNode = RTAvlrUIntPtrRemove(&pDbgAs->MapTree, pMap->Core.Key);
+ Assert(pNode == &pMap->Core); NOREF(pNode);
+
+ /* unlink */
+ PRTDBGASMOD pMod = pMap->pMod;
+ if (pMod->pMapHead == pMap)
+ pMod->pMapHead = pMap->pNext;
+ else
+ {
+ bool fFound = false;
+ for (PRTDBGASMAP pCur = pMod->pMapHead; pCur; pCur = pCur->pNext)
+ if (pCur->pNext == pMap)
+ {
+ pCur->pNext = pMap->pNext;
+ fFound = true;
+ break;
+ }
+ Assert(fFound);
+ }
+
+ /* free it */
+ pMap->Core.Key = pMap->Core.KeyLast = 0;
+ pMap->pNext = NULL;
+ pMap->pMod = NULL;
+ RTMemFree(pMap);
+}
+
+
+/**
+ * Worker for RTDbgAsModuleUnlinkByAddr and rtDbgAsModuleLinkCommon that
+ * unlinks a single mapping and releases the module if it's the last one.
+ *
+ * @param pDbgAs The address space instance.
+ * @param pMap The mapping to unlink.
+ *
+ * @remarks The caller must have locked the address space for writing.
+ */
+static void rtDbgAsModuleUnlinkByMap(PRTDBGASINT pDbgAs, PRTDBGASMAP pMap)
+{
+ /*
+ * Unlink it from the address space.
+ * Unlink the module as well if it's the last mapping it has.
+ */
+ PRTDBGASMOD pMod = pMap->pMod;
+ rtDbgAsModuleUnlinkMap(pDbgAs, pMap);
+ if (!pMod->pMapHead)
+ rtDbgAsModuleUnlinkMod(pDbgAs, pMod);
+}
+
+
+RTDECL(int) RTDbgAsModuleUnlink(RTDBGAS hDbgAs, RTDBGMOD hDbgMod)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ if (hDbgMod == NIL_RTDBGMOD)
+ return VINF_SUCCESS;
+
+ RTDBGAS_LOCK_WRITE(pDbgAs);
+ PRTDBGASMOD pMod = (PRTDBGASMOD)RTAvlPVGet(&pDbgAs->ModTree, hDbgMod);
+ if (!pMod)
+ {
+ RTDBGAS_UNLOCK_WRITE(pDbgAs);
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Unmap all everything and release the module.
+ */
+ while (pMod->pMapHead)
+ rtDbgAsModuleUnlinkMap(pDbgAs, pMod->pMapHead);
+ rtDbgAsModuleUnlinkMod(pDbgAs, pMod);
+
+ RTDBGAS_UNLOCK_WRITE(pDbgAs);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTDbgAsModuleUnlink);
+
+
+RTDECL(int) RTDbgAsModuleUnlinkByAddr(RTDBGAS hDbgAs, RTUINTPTR Addr)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+
+ RTDBGAS_LOCK_WRITE(pDbgAs);
+ PRTDBGASMAP pMap = (PRTDBGASMAP)RTAvlrUIntPtrRangeGet(&pDbgAs->MapTree, Addr);
+ if (!pMap)
+ {
+ RTDBGAS_UNLOCK_WRITE(pDbgAs);
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Hand it to
+ */
+ rtDbgAsModuleUnlinkByMap(pDbgAs, pMap);
+
+ RTDBGAS_UNLOCK_WRITE(pDbgAs);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTDbgAsModuleUnlinkByAddr);
+
+
+RTDECL(RTDBGMOD) RTDbgAsModuleByIndex(RTDBGAS hDbgAs, uint32_t iModule)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, NIL_RTDBGMOD);
+
+ RTDBGAS_LOCK_READ(pDbgAs);
+ if (iModule >= pDbgAs->cModules)
+ {
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return NIL_RTDBGMOD;
+ }
+
+ /*
+ * Get, retain and return it.
+ */
+ RTDBGMOD hMod = (RTDBGMOD)pDbgAs->papModules[iModule]->Core.Key;
+ RTDbgModRetain(hMod);
+
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return hMod;
+}
+RT_EXPORT_SYMBOL(RTDbgAsModuleByIndex);
+
+
+RTDECL(int) RTDbgAsModuleByAddr(RTDBGAS hDbgAs, RTUINTPTR Addr, PRTDBGMOD phMod, PRTUINTPTR pAddr, PRTDBGSEGIDX piSeg)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+
+ RTDBGAS_LOCK_READ(pDbgAs);
+ PRTDBGASMAP pMap = (PRTDBGASMAP)RTAvlrUIntPtrRangeGet(&pDbgAs->MapTree, Addr);
+ if (!pMap)
+ {
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Set up the return values.
+ */
+ if (phMod)
+ {
+ RTDBGMOD hMod = (RTDBGMOD)pMap->pMod->Core.Key;
+ RTDbgModRetain(hMod);
+ *phMod = hMod;
+ }
+ if (pAddr)
+ *pAddr = pMap->Core.Key;
+ if (piSeg)
+ *piSeg = pMap->iSeg;
+
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTDbgAsModuleByAddr);
+
+
+RTDECL(int) RTDbgAsModuleByName(RTDBGAS hDbgAs, const char *pszName, uint32_t iName, PRTDBGMOD phMod)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phMod, VERR_INVALID_POINTER);
+
+ RTDBGAS_LOCK_READ(pDbgAs);
+ PRTDBGASNAME pName = (PRTDBGASNAME)RTStrSpaceGet(&pDbgAs->NameSpace, pszName);
+ if (!pName)
+ {
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return VERR_NOT_FOUND;
+ }
+
+ PRTDBGASMOD pMod = pName->pHead;
+ while (iName-- > 0)
+ {
+ pMod = pMod->pNextName;
+ if (!pMod)
+ {
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return VERR_OUT_OF_RANGE;
+ }
+ }
+
+ /*
+ * Get, retain and return it.
+ */
+ RTDBGMOD hMod = (RTDBGMOD)pMod->Core.Key;
+ RTDbgModRetain(hMod);
+ *phMod = hMod;
+
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTDbgAsModuleByName);
+
+
+RTDECL(int) RTDbgAsModuleQueryMapByIndex(RTDBGAS hDbgAs, uint32_t iModule, PRTDBGASMAPINFO paMappings, uint32_t *pcMappings, uint32_t fFlags)
+{
+ /*
+ * Validate input.
+ */
+ uint32_t const cMappings = *pcMappings;
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+
+ RTDBGAS_LOCK_READ(pDbgAs);
+ if (iModule >= pDbgAs->cModules)
+ {
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return VERR_OUT_OF_RANGE;
+ }
+
+ /*
+ * Copy the mapping information about the module.
+ */
+ int rc = VINF_SUCCESS;
+ PRTDBGASMAP pMap = pDbgAs->papModules[iModule]->pMapHead;
+ uint32_t cMaps = 0;
+ while (pMap)
+ {
+ if (cMaps >= cMappings)
+ {
+ rc = VINF_BUFFER_OVERFLOW;
+ break;
+ }
+ paMappings[cMaps].Address = pMap->Core.Key;
+ paMappings[cMaps].iSeg = pMap->iSeg;
+ cMaps++;
+ pMap = pMap->pNext;
+ }
+
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ *pcMappings = cMaps;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsModuleQueryMapByIndex);
+
+
+/**
+ * Internal worker that looks up and retains a module.
+ *
+ * @returns Module handle, NIL_RTDBGMOD if not found.
+ * @param pDbgAs The address space instance data.
+ * @param Addr Address within the module.
+ * @param piSeg where to return the segment index.
+ * @param poffSeg Where to return the segment offset.
+ * @param pMapAddr The mapping address (RTDBGASMAP::Core.Key).
+ */
+DECLINLINE(RTDBGMOD) rtDbgAsModuleByAddr(PRTDBGASINT pDbgAs, RTUINTPTR Addr, PRTDBGSEGIDX piSeg, PRTUINTPTR poffSeg, PRTUINTPTR pMapAddr)
+{
+ RTDBGMOD hMod = NIL_RTDBGMOD;
+
+ RTDBGAS_LOCK_READ(pDbgAs);
+ PRTDBGASMAP pMap = (PRTDBGASMAP)RTAvlrUIntPtrRangeGet(&pDbgAs->MapTree, Addr);
+ if (pMap)
+ {
+ hMod = (RTDBGMOD)pMap->pMod->Core.Key;
+ RTDbgModRetain(hMod);
+ *piSeg = pMap->iSeg != NIL_RTDBGSEGIDX ? pMap->iSeg : RTDBGSEGIDX_RVA;
+ *poffSeg = Addr - pMap->Core.Key;
+ if (pMapAddr)
+ *pMapAddr = pMap->Core.Key;
+ }
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+
+ return hMod;
+}
+
+
+/**
+ * Adjusts the address to correspond to the mapping of the module/segment.
+ *
+ * @param pAddr The address to adjust (in/out).
+ * @param iSeg The related segment.
+ * @param hDbgMod The module handle.
+ * @param MapAddr The mapping address.
+ * @param iMapSeg The segment that's mapped, NIL_RTDBGSEGIDX or
+ * RTDBGSEGIDX_RVA if the whole module is mapped here.
+ */
+DECLINLINE(void) rtDbgAsAdjustAddressByMapping(PRTUINTPTR pAddr, RTDBGSEGIDX iSeg,
+ RTDBGMOD hDbgMod, RTUINTPTR MapAddr, RTDBGSEGIDX iMapSeg)
+{
+ if (iSeg == RTDBGSEGIDX_ABS)
+ return;
+
+ if (iSeg == RTDBGSEGIDX_RVA)
+ {
+ if ( iMapSeg == RTDBGSEGIDX_RVA
+ || iMapSeg == NIL_RTDBGSEGIDX)
+ *pAddr += MapAddr;
+ else
+ {
+ RTUINTPTR SegRva = RTDbgModSegmentRva(hDbgMod, iMapSeg);
+ AssertReturnVoid(SegRva != RTUINTPTR_MAX);
+ AssertMsg(SegRva <= *pAddr, ("SegRva=%RTptr *pAddr=%RTptr\n", SegRva, *pAddr));
+ *pAddr += MapAddr - SegRva;
+ }
+ }
+ else
+ {
+ if ( iMapSeg != RTDBGSEGIDX_RVA
+ && iMapSeg != NIL_RTDBGSEGIDX)
+ {
+ Assert(iMapSeg == iSeg);
+ *pAddr += MapAddr;
+ }
+ else
+ {
+ RTUINTPTR SegRva = RTDbgModSegmentRva(hDbgMod, iSeg);
+ AssertReturnVoid(SegRva != RTUINTPTR_MAX);
+ *pAddr += MapAddr + SegRva;
+ }
+ }
+}
+
+
+/**
+ * Adjusts the symbol value to correspond to the mapping of the module/segment.
+ *
+ * @param pSymbol The returned symbol info.
+ * @param hDbgMod The module handle.
+ * @param MapAddr The mapping address.
+ * @param iMapSeg The segment that's mapped, NIL_RTDBGSEGIDX if the
+ * whole module is mapped here.
+ */
+DECLINLINE(void) rtDbgAsAdjustSymbolValue(PRTDBGSYMBOL pSymbol, RTDBGMOD hDbgMod, RTUINTPTR MapAddr, RTDBGSEGIDX iMapSeg)
+{
+ Assert(pSymbol->iSeg != NIL_RTDBGSEGIDX);
+ Assert(pSymbol->offSeg == pSymbol->Value);
+ rtDbgAsAdjustAddressByMapping(&pSymbol->Value, pSymbol->iSeg, hDbgMod, MapAddr, iMapSeg);
+}
+
+
+/**
+ * Adjusts the line number address to correspond to the mapping of the module/segment.
+ *
+ * @param pLine The returned line number info.
+ * @param hDbgMod The module handle.
+ * @param MapAddr The mapping address.
+ * @param iMapSeg The segment that's mapped, NIL_RTDBGSEGIDX if the
+ * whole module is mapped here.
+ */
+DECLINLINE(void) rtDbgAsAdjustLineAddress(PRTDBGLINE pLine, RTDBGMOD hDbgMod, RTUINTPTR MapAddr, RTDBGSEGIDX iMapSeg)
+{
+ Assert(pLine->iSeg != NIL_RTDBGSEGIDX);
+ Assert(pLine->offSeg == pLine->Address);
+ rtDbgAsAdjustAddressByMapping(&pLine->Address, pLine->iSeg, hDbgMod, MapAddr, iMapSeg);
+}
+
+
+RTDECL(int) RTDbgAsSymbolAdd(RTDBGAS hDbgAs, const char *pszSymbol, RTUINTPTR Addr, RTUINTPTR cb, uint32_t fFlags, uint32_t *piOrdinal)
+{
+ /*
+ * Validate input and resolve the address.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+
+ RTDBGSEGIDX iSeg = NIL_RTDBGSEGIDX; /* shut up gcc */
+ RTUINTPTR offSeg = 0;
+ RTDBGMOD hMod = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, NULL);
+ if (hMod == NIL_RTDBGMOD)
+ return VERR_NOT_FOUND;
+
+ /*
+ * Forward the call.
+ */
+ int rc = RTDbgModSymbolAdd(hMod, pszSymbol, iSeg, offSeg, cb, fFlags, piOrdinal);
+ RTDbgModRelease(hMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsSymbolAdd);
+
+
+/**
+ * Creates a snapshot of the module table on the temporary heap.
+ *
+ * The caller must release all the module handles before freeing the table
+ * using RTMemTmpFree.
+ *
+ * @returns Module table snaphot.
+ * @param pDbgAs The address space instance data.
+ * @param pcModules Where to return the number of modules.
+ */
+static PRTDBGMOD rtDbgAsSnapshotModuleTable(PRTDBGASINT pDbgAs, uint32_t *pcModules)
+{
+ RTDBGAS_LOCK_READ(pDbgAs);
+
+ uint32_t iMod = *pcModules = pDbgAs->cModules;
+ PRTDBGMOD pahModules = (PRTDBGMOD)RTMemTmpAlloc(sizeof(pahModules[0]) * RT_MAX(iMod, 1));
+ if (pahModules)
+ {
+ while (iMod-- > 0)
+ {
+ RTDBGMOD hMod = (RTDBGMOD)pDbgAs->papModules[iMod]->Core.Key;
+ pahModules[iMod] = hMod;
+ RTDbgModRetain(hMod);
+ }
+ }
+
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return pahModules;
+}
+
+
+RTDECL(int) RTDbgAsSymbolByAddr(RTDBGAS hDbgAs, RTUINTPTR Addr, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymbol, PRTDBGMOD phMod)
+{
+ /*
+ * Validate input and resolve the address.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ if (phMod)
+ *phMod = NIL_RTDBGMOD;
+
+ RTDBGSEGIDX iSeg = NIL_RTDBGSEGIDX; /* shut up gcc */
+ RTUINTPTR offSeg = 0;
+ RTUINTPTR MapAddr = 0;
+ RTDBGMOD hMod = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, &MapAddr);
+ if (hMod == NIL_RTDBGMOD)
+ {
+ /*
+ * Check for absolute symbols. Requires iterating all modules.
+ */
+ if (fFlags & RTDBGSYMADDR_FLAGS_SKIP_ABS)
+ return VERR_NOT_FOUND;
+
+ uint32_t cModules;
+ PRTDBGMOD pahModules = rtDbgAsSnapshotModuleTable(pDbgAs, &cModules);
+ if (!pahModules)
+ return VERR_NO_TMP_MEMORY;
+
+ int rc;
+ RTINTPTR offBestDisp = RTINTPTR_MAX;
+ uint32_t iBest = UINT32_MAX;
+ for (uint32_t i = 0; i < cModules; i++)
+ {
+ RTINTPTR offDisp;
+ rc = RTDbgModSymbolByAddr(pahModules[i], RTDBGSEGIDX_ABS, Addr, fFlags, &offDisp, pSymbol);
+ if (RT_SUCCESS(rc) && RT_ABS(offDisp) < offBestDisp)
+ {
+ offBestDisp = RT_ABS(offDisp);
+ iBest = i;
+ }
+ }
+
+ if (iBest == UINT32_MAX)
+ rc = VERR_NOT_FOUND;
+ else
+ {
+ hMod = pahModules[iBest];
+ rc = RTDbgModSymbolByAddr(hMod, RTDBGSEGIDX_ABS, Addr, fFlags, poffDisp, pSymbol);
+ if (RT_SUCCESS(rc))
+ {
+ rtDbgAsAdjustSymbolValue(pSymbol, hMod, MapAddr, iSeg);
+ if (phMod)
+ RTDbgModRetain(*phMod = hMod);
+ }
+ }
+
+ for (uint32_t i = 0; i < cModules; i++)
+ RTDbgModRelease(pahModules[i]);
+ RTMemTmpFree(pahModules);
+ return rc;
+ }
+
+ /*
+ * Forward the call.
+ */
+ int rc = RTDbgModSymbolByAddr(hMod, iSeg, offSeg, fFlags, poffDisp, pSymbol);
+ if (RT_SUCCESS(rc))
+ rtDbgAsAdjustSymbolValue(pSymbol, hMod, MapAddr, iSeg);
+ if (phMod)
+ *phMod = hMod;
+ else
+ RTDbgModRelease(hMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsSymbolByAddr);
+
+
+RTDECL(int) RTDbgAsSymbolByAddrA(RTDBGAS hDbgAs, RTUINTPTR Addr, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL *ppSymInfo, PRTDBGMOD phMod)
+{
+ /*
+ * Validate input and resolve the address.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+
+ RTDBGSEGIDX iSeg = NIL_RTDBGSEGIDX;
+ RTUINTPTR offSeg = 0;
+ RTUINTPTR MapAddr = 0;
+ RTDBGMOD hMod = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, &MapAddr);
+ if (hMod == NIL_RTDBGMOD)
+ {
+ if (phMod)
+ *phMod = NIL_RTDBGMOD;
+ return VERR_NOT_FOUND;
+ }
+
+ /*
+ * Forward the call.
+ */
+ int rc = RTDbgModSymbolByAddrA(hMod, iSeg, offSeg, fFlags, poffDisp, ppSymInfo);
+ if (RT_SUCCESS(rc))
+ rtDbgAsAdjustSymbolValue(*ppSymInfo, hMod, MapAddr, iSeg);
+ if (phMod)
+ *phMod = hMod;
+ else
+ RTDbgModRelease(hMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsSymbolByAddrA);
+
+
+/**
+ * Attempts to find a mapping of the specified symbol/module and
+ * adjust it's Value field accordingly.
+ *
+ * @returns true / false success indicator.
+ * @param pDbgAs The address space.
+ * @param hDbgMod The module handle.
+ * @param pSymbol The symbol info.
+ */
+static bool rtDbgAsFindMappingAndAdjustSymbolValue(PRTDBGASINT pDbgAs, RTDBGMOD hDbgMod, PRTDBGSYMBOL pSymbol)
+{
+ /*
+ * Absolute segments needs no fixing.
+ */
+ RTDBGSEGIDX const iSeg = pSymbol->iSeg;
+ if (iSeg == RTDBGSEGIDX_ABS)
+ return true;
+
+ RTDBGAS_LOCK_READ(pDbgAs);
+
+ /*
+ * Lookup up the module by it's handle and iterate the mappings looking for one
+ * that either encompasses the entire module or the segment in question.
+ */
+ PRTDBGASMOD pMod = (PRTDBGASMOD)RTAvlPVGet(&pDbgAs->ModTree, hDbgMod);
+ if (pMod)
+ {
+ for (PRTDBGASMAP pMap = pMod->pMapHead; pMap; pMap = pMap->pNext)
+ {
+ /* Exact segment match or full-mapping. */
+ if ( iSeg == pMap->iSeg
+ || pMap->iSeg == NIL_RTDBGSEGIDX)
+ {
+ RTUINTPTR MapAddr = pMap->Core.Key;
+ RTDBGSEGIDX iMapSeg = pMap->iSeg;
+
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ rtDbgAsAdjustSymbolValue(pSymbol, hDbgMod, MapAddr, iMapSeg);
+ return true;
+ }
+
+ /* Symbol uses RVA and the mapping doesn't, see if it's in the mapped segment. */
+ if (iSeg == RTDBGSEGIDX_RVA)
+ {
+ Assert(pMap->iSeg != NIL_RTDBGSEGIDX);
+ RTUINTPTR SegRva = RTDbgModSegmentRva(hDbgMod, pMap->iSeg);
+ Assert(SegRva != RTUINTPTR_MAX);
+ RTUINTPTR cbSeg = RTDbgModSegmentSize(hDbgMod, pMap->iSeg);
+ if (SegRva - pSymbol->Value < cbSeg)
+ {
+ RTUINTPTR MapAddr = pMap->Core.Key;
+ RTDBGSEGIDX iMapSeg = pMap->iSeg;
+
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ rtDbgAsAdjustSymbolValue(pSymbol, hDbgMod, MapAddr, iMapSeg);
+ return true;
+ }
+ }
+ }
+ }
+ /* else: Unmapped while we were searching. */
+
+ RTDBGAS_UNLOCK_READ(pDbgAs);
+ return false;
+}
+
+
+RTDECL(int) RTDbgAsSymbolByName(RTDBGAS hDbgAs, const char *pszSymbol, PRTDBGSYMBOL pSymbol, PRTDBGMOD phMod)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszSymbol, VERR_INVALID_POINTER);
+ AssertPtrReturn(pSymbol, VERR_INVALID_POINTER);
+
+ /*
+ * Look for module pattern.
+ */
+ const char *pachModPat = NULL;
+ size_t cchModPat = 0;
+ const char *pszBang = strchr(pszSymbol, '!');
+ if (pszBang)
+ {
+ pachModPat = pszSymbol;
+ cchModPat = pszBang - pszSymbol;
+ pszSymbol = pszBang + 1;
+ if (!*pszSymbol)
+ return VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE;
+ /* Note! Zero length module -> no pattern -> escape for symbol with '!'. */
+ }
+
+ /*
+ * Iterate the modules, looking for the symbol.
+ */
+ uint32_t cModules;
+ PRTDBGMOD pahModules = rtDbgAsSnapshotModuleTable(pDbgAs, &cModules);
+ if (!pahModules)
+ return VERR_NO_TMP_MEMORY;
+
+ for (uint32_t i = 0; i < cModules; i++)
+ {
+ if ( cchModPat == 0
+ || RTStrSimplePatternNMatch(pachModPat, cchModPat, RTDbgModName(pahModules[i]), RTSTR_MAX))
+ {
+ int rc = RTDbgModSymbolByName(pahModules[i], pszSymbol, pSymbol);
+ if (RT_SUCCESS(rc))
+ {
+ if (rtDbgAsFindMappingAndAdjustSymbolValue(pDbgAs, pahModules[i], pSymbol))
+ {
+ if (phMod)
+ RTDbgModRetain(*phMod = pahModules[i]);
+ for (; i < cModules; i++)
+ RTDbgModRelease(pahModules[i]);
+ RTMemTmpFree(pahModules);
+ return rc;
+ }
+ }
+ }
+ RTDbgModRelease(pahModules[i]);
+ }
+
+ RTMemTmpFree(pahModules);
+ return VERR_SYMBOL_NOT_FOUND;
+}
+RT_EXPORT_SYMBOL(RTDbgAsSymbolByName);
+
+
+RTDECL(int) RTDbgAsSymbolByNameA(RTDBGAS hDbgAs, const char *pszSymbol, PRTDBGSYMBOL *ppSymbol, PRTDBGMOD phMod)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(ppSymbol, VERR_INVALID_POINTER);
+ *ppSymbol = NULL;
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszSymbol, VERR_INVALID_POINTER);
+
+ /*
+ * Look for module pattern.
+ */
+ const char *pachModPat = NULL;
+ size_t cchModPat = 0;
+ const char *pszBang = strchr(pszSymbol, '!');
+ if (pszBang)
+ {
+ pachModPat = pszSymbol;
+ cchModPat = pszBang - pszSymbol;
+ pszSymbol = pszBang + 1;
+ if (!*pszSymbol)
+ return VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE;
+ /* Note! Zero length module -> no pattern -> escape for symbol with '!'. */
+ }
+
+ /*
+ * Iterate the modules, looking for the symbol.
+ */
+ uint32_t cModules;
+ PRTDBGMOD pahModules = rtDbgAsSnapshotModuleTable(pDbgAs, &cModules);
+ if (!pahModules)
+ return VERR_NO_TMP_MEMORY;
+
+ for (uint32_t i = 0; i < cModules; i++)
+ {
+ if ( cchModPat == 0
+ || RTStrSimplePatternNMatch(pachModPat, cchModPat, RTDbgModName(pahModules[i]), RTSTR_MAX))
+ {
+ int rc = RTDbgModSymbolByNameA(pahModules[i], pszSymbol, ppSymbol);
+ if (RT_SUCCESS(rc))
+ {
+ if (rtDbgAsFindMappingAndAdjustSymbolValue(pDbgAs, pahModules[i], *ppSymbol))
+ {
+ if (phMod)
+ RTDbgModRetain(*phMod = pahModules[i]);
+ for (; i < cModules; i++)
+ RTDbgModRelease(pahModules[i]);
+ RTMemTmpFree(pahModules);
+ return rc;
+ }
+ }
+ }
+ RTDbgModRelease(pahModules[i]);
+ }
+
+ RTMemTmpFree(pahModules);
+ return VERR_SYMBOL_NOT_FOUND;
+}
+RT_EXPORT_SYMBOL(RTDbgAsSymbolByNameA);
+
+
+RTDECL(int) RTDbgAsLineAdd(RTDBGAS hDbgAs, const char *pszFile, uint32_t uLineNo, RTUINTPTR Addr, uint32_t *piOrdinal)
+{
+ /*
+ * Validate input and resolve the address.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+
+ RTDBGSEGIDX iSeg = NIL_RTDBGSEGIDX; /* shut up gcc */
+ RTUINTPTR offSeg = 0; /* ditto */
+ RTDBGMOD hMod = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, NULL);
+ if (hMod == NIL_RTDBGMOD)
+ return VERR_NOT_FOUND;
+
+ /*
+ * Forward the call.
+ */
+ int rc = RTDbgModLineAdd(hMod, pszFile, uLineNo, iSeg, offSeg, piOrdinal);
+ RTDbgModRelease(hMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsLineAdd);
+
+
+RTDECL(int) RTDbgAsLineByAddr(RTDBGAS hDbgAs, RTUINTPTR Addr, PRTINTPTR poffDisp, PRTDBGLINE pLine, PRTDBGMOD phMod)
+{
+ /*
+ * Validate input and resolve the address.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+
+ RTDBGSEGIDX iSeg = NIL_RTDBGSEGIDX; /* shut up gcc */
+ RTUINTPTR offSeg = 0;
+ RTUINTPTR MapAddr = 0;
+ RTDBGMOD hMod = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, &MapAddr);
+ if (hMod == NIL_RTDBGMOD)
+ return VERR_NOT_FOUND;
+
+ /*
+ * Forward the call.
+ */
+ int rc = RTDbgModLineByAddr(hMod, iSeg, offSeg, poffDisp, pLine);
+ if (RT_SUCCESS(rc))
+ {
+ rtDbgAsAdjustLineAddress(pLine, hMod, MapAddr, iSeg);
+ if (phMod)
+ *phMod = hMod;
+ else
+ RTDbgModRelease(hMod);
+ }
+ else
+ RTDbgModRelease(hMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsLineByAddr);
+
+
+RTDECL(int) RTDbgAsLineByAddrA(RTDBGAS hDbgAs, RTUINTPTR Addr, PRTINTPTR poffDisp, PRTDBGLINE *ppLine, PRTDBGMOD phMod)
+{
+ /*
+ * Validate input and resolve the address.
+ */
+ PRTDBGASINT pDbgAs = hDbgAs;
+ RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
+
+ RTDBGSEGIDX iSeg = NIL_RTDBGSEGIDX; /* shut up gcc */
+ RTUINTPTR offSeg = 0;
+ RTUINTPTR MapAddr = 0;
+ RTDBGMOD hMod = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, &MapAddr);
+ if (hMod == NIL_RTDBGMOD)
+ return VERR_NOT_FOUND;
+
+ /*
+ * Forward the call.
+ */
+ int rc = RTDbgModLineByAddrA(hMod, iSeg, offSeg, poffDisp, ppLine);
+ if (RT_SUCCESS(rc))
+ {
+ rtDbgAsAdjustLineAddress(*ppLine, hMod, MapAddr, iSeg);
+ if (phMod)
+ *phMod = hMod;
+ else
+ RTDbgModRelease(hMod);
+ }
+ else
+ RTDbgModRelease(hMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgAsLineByAddrA);
+
diff --git a/src/VBox/Runtime/common/dbg/dbgcfg.cpp b/src/VBox/Runtime/common/dbg/dbgcfg.cpp
new file mode 100644
index 00000000..f680f123
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgcfg.cpp
@@ -0,0 +1,2525 @@
+/* $Id: dbgcfg.cpp $ */
+/** @file
+ * IPRT - Debugging Configuration.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DBG
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#ifdef IPRT_WITH_HTTP
+# include <iprt/http.h>
+#endif
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * String list entry.
+ */
+typedef struct RTDBGCFGSTR
+{
+ /** List entry. */
+ RTLISTNODE ListEntry;
+ /** Domain specific flags. */
+ uint16_t fFlags;
+ /** The length of the string. */
+ uint16_t cch;
+ /** The string. */
+ char sz[1];
+} RTDBGCFGSTR;
+/** Pointer to a string list entry. */
+typedef RTDBGCFGSTR *PRTDBGCFGSTR;
+
+
+/**
+ * Configuration instance.
+ */
+typedef struct RTDBGCFGINT
+{
+ /** The magic value (RTDBGCFG_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Flags, see RTDBGCFG_FLAGS_XXX. */
+ uint64_t fFlags;
+
+ /** List of paths to search for debug files and executable images. */
+ RTLISTANCHOR PathList;
+ /** List of debug file suffixes. */
+ RTLISTANCHOR SuffixList;
+ /** List of paths to search for source files. */
+ RTLISTANCHOR SrcPathList;
+
+#ifdef RT_OS_WINDOWS
+ /** The _NT_ALT_SYMBOL_PATH and _NT_SYMBOL_PATH combined. */
+ RTLISTANCHOR NtSymbolPathList;
+ /** The _NT_EXECUTABLE_PATH. */
+ RTLISTANCHOR NtExecutablePathList;
+ /** The _NT_SOURCE_PATH. */
+ RTLISTANCHOR NtSourcePath;
+#endif
+
+ /** Log callback function. */
+ PFNRTDBGCFGLOG pfnLogCallback;
+ /** User argument to pass to the log callback. */
+ void *pvLogUser;
+
+ /** Critical section protecting the instance data. */
+ RTCRITSECTRW CritSect;
+} *PRTDBGCFGINT;
+
+/**
+ * Mnemonics map entry for a 64-bit unsigned property value.
+ */
+typedef struct RTDBGCFGU64MNEMONIC
+{
+ /** The flags to set or clear. */
+ uint64_t fFlags;
+ /** The mnemonic. */
+ const char *pszMnemonic;
+ /** The length of the mnemonic. */
+ uint8_t cchMnemonic;
+ /** If @c true, the bits in fFlags will be set, if @c false they will be
+ * cleared. */
+ bool fSet;
+} RTDBGCFGU64MNEMONIC;
+/** Pointer to a read only mnemonic map entry for a uint64_t property. */
+typedef RTDBGCFGU64MNEMONIC const *PCRTDBGCFGU64MNEMONIC;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Validates a debug module handle and returns rc if not valid. */
+#define RTDBGCFG_VALID_RETURN_RC(pThis, rc) \
+ do { \
+ AssertPtrReturn((pThis), (rc)); \
+ AssertReturn((pThis)->u32Magic == RTDBGCFG_MAGIC, (rc)); \
+ AssertReturn((pThis)->cRefs > 0, (rc)); \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Mnemonics map for RTDBGCFGPROP_FLAGS. */
+static const RTDBGCFGU64MNEMONIC g_aDbgCfgFlags[] =
+{
+ { RTDBGCFG_FLAGS_DEFERRED, RT_STR_TUPLE("deferred"), true },
+ { RTDBGCFG_FLAGS_DEFERRED, RT_STR_TUPLE("nodeferred"), false },
+ { RTDBGCFG_FLAGS_NO_SYM_SRV, RT_STR_TUPLE("symsrv"), false },
+ { RTDBGCFG_FLAGS_NO_SYM_SRV, RT_STR_TUPLE("nosymsrv"), true },
+ { RTDBGCFG_FLAGS_NO_SYSTEM_PATHS, RT_STR_TUPLE("syspaths"), false },
+ { RTDBGCFG_FLAGS_NO_SYSTEM_PATHS, RT_STR_TUPLE("nosyspaths"), true },
+ { RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH, RT_STR_TUPLE("rec"), false },
+ { RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH, RT_STR_TUPLE("norec"), true },
+ { RTDBGCFG_FLAGS_NO_RECURSIV_SRC_SEARCH, RT_STR_TUPLE("recsrc"), false },
+ { RTDBGCFG_FLAGS_NO_RECURSIV_SRC_SEARCH, RT_STR_TUPLE("norecsrc"), true },
+ { 0, NULL, 0, false }
+};
+
+
+/** Interesting bundle suffixes. */
+static const char * const g_apszBundleSuffixes[] =
+{
+ ".kext",
+ ".app",
+ ".framework",
+ ".component",
+ ".action",
+ ".caction",
+ ".bundle",
+ ".sourcebundle",
+ ".menu",
+ ".plugin",
+ ".ppp",
+ ".monitorpanel",
+ ".scripting",
+ ".prefPane",
+ ".qlgenerator",
+ ".brailledriver",
+ ".saver",
+ ".SpeechVoice",
+ ".SpeechRecognizer",
+ ".SpeechSynthesizer",
+ ".mdimporter",
+ ".spreporter",
+ ".xpc",
+ NULL
+};
+
+/** Debug bundle suffixes. (Same as above + .dSYM) */
+static const char * const g_apszDSymBundleSuffixes[] =
+{
+ ".dSYM",
+ ".kext.dSYM",
+ ".app.dSYM",
+ ".framework.dSYM",
+ ".component.dSYM",
+ ".action.dSYM",
+ ".caction.dSYM",
+ ".bundle.dSYM",
+ ".sourcebundle.dSYM",
+ ".menu.dSYM",
+ ".plugin.dSYM",
+ ".ppp.dSYM",
+ ".monitorpanel.dSYM",
+ ".scripting.dSYM",
+ ".prefPane.dSYM",
+ ".qlgenerator.dSYM",
+ ".brailledriver.dSYM",
+ ".saver.dSYM",
+ ".SpeechVoice.dSYM",
+ ".SpeechRecognizer.dSYM",
+ ".SpeechSynthesizer.dSYM",
+ ".mdimporter.dSYM",
+ ".spreporter.dSYM",
+ ".xpc.dSYM",
+ NULL
+};
+
+
+
+/**
+ * Runtime logging, level 1.
+ *
+ * @param pThis The debug config instance data.
+ * @param pszFormat The message format string.
+ * @param ... Arguments references in the format string.
+ */
+static void rtDbgCfgLog1(PRTDBGCFGINT pThis, const char *pszFormat, ...)
+{
+ if (LogIsEnabled() || (pThis && pThis->pfnLogCallback))
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ char *pszMsg = RTStrAPrintf2V(pszFormat, va);
+ va_end(va);
+
+ Log(("RTDbgCfg: %s", pszMsg));
+ if (pThis && pThis->pfnLogCallback)
+ pThis->pfnLogCallback(pThis, 1, pszMsg, pThis->pvLogUser);
+ RTStrFree(pszMsg);
+ }
+}
+
+
+/**
+ * Runtime logging, level 2.
+ *
+ * @param pThis The debug config instance data.
+ * @param pszFormat The message format string.
+ * @param ... Arguments references in the format string.
+ */
+static void rtDbgCfgLog2(PRTDBGCFGINT pThis, const char *pszFormat, ...)
+{
+ if (LogIs2Enabled() || (pThis && pThis->pfnLogCallback))
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ char *pszMsg = RTStrAPrintf2V(pszFormat, va);
+ va_end(va);
+
+ Log(("RTDbgCfg: %s", pszMsg));
+ if (pThis && pThis->pfnLogCallback)
+ pThis->pfnLogCallback(pThis, 2, pszMsg, pThis->pvLogUser);
+ RTStrFree(pszMsg);
+ }
+}
+
+
+/**
+ * Checks if the file system at the given path is case insensitive or not.
+ *
+ * @returns true / false
+ * @param pszPath The path to query about.
+ */
+static int rtDbgCfgIsFsCaseInsensitive(const char *pszPath)
+{
+ RTFSPROPERTIES Props;
+ int rc = RTFsQueryProperties(pszPath, &Props);
+ if (RT_FAILURE(rc))
+ return RT_OPSYS == RT_OPSYS_DARWIN
+ || RT_OPSYS == RT_OPSYS_DOS
+ || RT_OPSYS == RT_OPSYS_OS2
+ || RT_OPSYS == RT_OPSYS_NT
+ || RT_OPSYS == RT_OPSYS_WINDOWS;
+ return !Props.fCaseSensitive;
+}
+
+
+/**
+ * Worker that does case sensitive file/dir searching.
+ *
+ * @returns true / false.
+ * @param pszPath The path buffer containing an existing directory and
+ * at @a offLastComp the name we're looking for.
+ * RTPATH_MAX in size. On success, this last component
+ * will have the correct case. On failure, the last
+ * component is stripped off.
+ * @param offLastComp The offset of the last component (for chopping it
+ * off).
+ * @param enmType What kind of thing we're looking for.
+ */
+static bool rtDbgCfgIsXxxxAndFixCaseWorker(char *pszPath, size_t offLastComp, RTDIRENTRYTYPE enmType)
+{
+ /** @todo IPRT should generalize this so we can use host specific tricks to
+ * speed it up. */
+
+ char *pszName = &pszPath[offLastComp];
+
+ /* Return straight away if the name isn't case foldable. */
+ if (!RTStrIsCaseFoldable(pszName))
+ {
+ *pszName = '\0';
+ return false;
+ }
+
+ /*
+ * Try some simple case folding games.
+ */
+ RTStrToLower(pszName);
+ if (RTFileExists(pszPath))
+ return true;
+
+ RTStrToUpper(pszName);
+ if (RTFileExists(pszPath))
+ return true;
+
+ /*
+ * Open the directory and check each entry in it.
+ */
+ char chSaved = *pszName;
+ *pszName = '\0';
+
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, pszPath);
+ if (RT_FAILURE(rc))
+ return false;
+
+ *pszName = chSaved;
+
+ for (;;)
+ {
+ /* Read the next entry. */
+ union
+ {
+ RTDIRENTRY Entry;
+ uint8_t ab[_4K];
+ } u;
+ size_t cbBuf = sizeof(u);
+ rc = RTDirRead(hDir, &u.Entry, &cbBuf);
+ if (RT_FAILURE(rc))
+ break;
+
+ if ( !RTStrICmp(pszName, u.Entry.szName)
+ && ( u.Entry.enmType == enmType
+ || u.Entry.enmType == RTDIRENTRYTYPE_UNKNOWN
+ || u.Entry.enmType == RTDIRENTRYTYPE_SYMLINK) )
+ {
+ strcpy(pszName, u.Entry.szName);
+ if (u.Entry.enmType != enmType)
+ RTDirQueryUnknownType(pszPath, true /*fFollowSymlinks*/, &u.Entry.enmType);
+ if (u.Entry.enmType == enmType)
+ {
+ RTDirClose(hDir);
+ return true;
+ }
+ }
+ }
+
+ RTDirClose(hDir);
+ *pszName = '\0';
+
+ return false;
+}
+
+
+/**
+ * Appends @a pszSubDir to @a pszPath and check whether it exists and is a
+ * directory.
+ *
+ * If @a fCaseInsensitive is set, we will do a case insensitive search for a
+ * matching sub directory.
+ *
+ * @returns true / false
+ * @param pszPath The path buffer containing an existing
+ * directory. RTPATH_MAX in size.
+ * @param pszSubDir The sub directory to append.
+ * @param fCaseInsensitive Whether case insensitive searching is required.
+ */
+static bool rtDbgCfgIsDirAndFixCase(char *pszPath, const char *pszSubDir, bool fCaseInsensitive)
+{
+ /* Save the length of the input path so we can restore it in the case
+ insensitive branch further down. */
+ size_t const cchPath = strlen(pszPath);
+
+ /*
+ * Append the sub directory and check if we got a hit.
+ */
+ int rc = RTPathAppend(pszPath, RTPATH_MAX, pszSubDir);
+ if (RT_FAILURE(rc))
+ return false;
+
+ if (RTDirExists(pszPath))
+ return true;
+
+ /*
+ * Do case insensitive lookup if requested.
+ */
+ if (fCaseInsensitive)
+ return rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, RTDIRENTRYTYPE_DIRECTORY);
+
+ pszPath[cchPath] = '\0';
+ return false;
+}
+
+
+/**
+ * Appends @a pszSubDir1 and @a pszSuffix to @a pszPath and check whether it
+ * exists and is a directory.
+ *
+ * If @a fCaseInsensitive is set, we will do a case insensitive search for a
+ * matching sub directory.
+ *
+ * @returns true / false
+ * @param pszPath The path buffer containing an existing
+ * directory. RTPATH_MAX in size.
+ * @param pszSubDir The sub directory to append.
+ * @param pszSuffix The suffix to append.
+ * @param fCaseInsensitive Whether case insensitive searching is required.
+ */
+static bool rtDbgCfgIsDirAndFixCase2(char *pszPath, const char *pszSubDir, const char *pszSuffix, bool fCaseInsensitive)
+{
+ Assert(!strpbrk(pszSuffix, ":/\\"));
+
+ /* Save the length of the input path so we can restore it in the case
+ insensitive branch further down. */
+ size_t const cchPath = strlen(pszPath);
+
+ /*
+ * Append the subdirectory and suffix, then check if we got a hit.
+ */
+ int rc = RTPathAppend(pszPath, RTPATH_MAX, pszSubDir);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCat(pszPath, RTPATH_MAX, pszSuffix);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTDirExists(pszPath))
+ return true;
+
+ /*
+ * Do case insensitive lookup if requested.
+ */
+ if (fCaseInsensitive)
+ return rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, RTDIRENTRYTYPE_DIRECTORY);
+ }
+ }
+
+ pszPath[cchPath] = '\0';
+ return false;
+}
+
+
+/**
+ * Appends @a pszFilename to @a pszPath and check whether it exists and is a
+ * directory.
+ *
+ * If @a fCaseInsensitive is set, we will do a case insensitive search for a
+ * matching filename.
+ *
+ * @returns true / false
+ * @param pszPath The path buffer containing an existing
+ * directory. RTPATH_MAX in size.
+ * @param pszFilename The filename to append.
+ * @param pszSuffix Optional filename suffix to append.
+ * @param fCaseInsensitive Whether case insensitive searching is required.
+ * @param fMsCompressed Whether to look for the MS compressed file name
+ * variant.
+ * @param pfProbablyCompressed This is set to true if a MS compressed
+ * filename variant is returned. Optional.
+ */
+static bool rtDbgCfgIsFileAndFixCase(char *pszPath, const char *pszFilename, const char *pszSuffix, bool fCaseInsensitive,
+ bool fMsCompressed, bool *pfProbablyCompressed)
+{
+ /* Save the length of the input path so we can restore it in the case
+ insensitive branch further down. */
+ size_t cchPath = strlen(pszPath);
+ if (pfProbablyCompressed)
+ *pfProbablyCompressed = false;
+
+ /*
+ * Append the filename and optionally suffix, then check if we got a hit.
+ */
+ int rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
+ if (RT_FAILURE(rc))
+ return false;
+ if (pszSuffix)
+ {
+ Assert(!fMsCompressed);
+ rc = RTStrCat(pszPath, RTPATH_MAX, pszSuffix);
+ if (RT_FAILURE(rc))
+ return false;
+ }
+
+ if (RTFileExists(pszPath))
+ return true;
+
+ /*
+ * Do case insensitive file lookup if requested.
+ */
+ if (fCaseInsensitive)
+ {
+ if (rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, RTDIRENTRYTYPE_FILE))
+ return true;
+ }
+
+ /*
+ * Look for MS compressed file if requested.
+ */
+ if ( fMsCompressed
+ && (unsigned char)pszFilename[strlen(pszFilename) - 1] < 0x7f)
+ {
+ pszPath[cchPath] = '\0';
+ rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
+ AssertRCReturn(rc, false);
+ pszPath[strlen(pszPath) - 1] = '_';
+
+ if (pfProbablyCompressed)
+ *pfProbablyCompressed = true;
+
+ if ( RTFileExists(pszPath)
+ || ( fCaseInsensitive
+ && rtDbgCfgIsXxxxAndFixCaseWorker(pszPath, cchPath, RTDIRENTRYTYPE_FILE) ))
+ return true;
+
+ if (pfProbablyCompressed)
+ *pfProbablyCompressed = false;
+ }
+
+ pszPath[cchPath] = '\0';
+ return false;
+}
+
+
+static int rtDbgCfgTryOpenDir(PRTDBGCFGINT pThis, char *pszPath, PRTPATHSPLIT pSplitFn, uint32_t fFlags,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ int rcRet = VWRN_NOT_FOUND;
+ int rc2;
+
+ /* If the directory doesn't exist, just quit immediately.
+ Note! Our case insensitivity doesn't extend to the search dirs themselfs,
+ only to the bits under neath them. */
+ if (!RTDirExists(pszPath))
+ {
+ rtDbgCfgLog2(pThis, "Dir does not exist: '%s'\n", pszPath);
+ return rcRet;
+ }
+
+ /* Figure out whether we have to do a case sensitive search or not.
+ Note! As a simplification, we don't ask for case settings in each
+ directory under the user specified path, we assume the file
+ systems that mounted there have compatible settings. Faster
+ that way. */
+ bool const fCaseInsensitive = (fFlags & RTDBGCFG_O_CASE_INSENSITIVE)
+ && !rtDbgCfgIsFsCaseInsensitive(pszPath);
+
+ size_t const cchPath = strlen(pszPath);
+
+ /*
+ * Look for the file with less and less of the original path given.
+ */
+ for (unsigned i = RTPATH_PROP_HAS_ROOT_SPEC(pSplitFn->fProps); i < pSplitFn->cComps; i++)
+ {
+ pszPath[cchPath] = '\0';
+
+ rc2 = VINF_SUCCESS;
+ for (unsigned j = i; j < pSplitFn->cComps - 1U && RT_SUCCESS(rc2); j++)
+ if (!rtDbgCfgIsDirAndFixCase(pszPath, pSplitFn->apszComps[i], fCaseInsensitive))
+ rc2 = VERR_FILE_NOT_FOUND;
+
+ if (RT_SUCCESS(rc2))
+ {
+ if (rtDbgCfgIsFileAndFixCase(pszPath, pSplitFn->apszComps[pSplitFn->cComps - 1], NULL /*pszSuffix*/,
+ fCaseInsensitive, false, NULL))
+ {
+ rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
+ rc2 = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ {
+ if (rc2 == VINF_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
+ else
+ rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
+ return rc2;
+ }
+ rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, pszPath);
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+ }
+ }
+ }
+
+ /*
+ * Do a recursive search if requested.
+ */
+ if ( (fFlags & RTDBGCFG_O_RECURSIVE)
+ && pThis
+ && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH) )
+ {
+ /** @todo Recursive searching will be done later. */
+ }
+
+ return rcRet;
+}
+
+static int rtDbgCfgUnpackMsCacheFile(PRTDBGCFGINT pThis, char *pszPath, const char *pszFilename)
+{
+ rtDbgCfgLog2(pThis, "Unpacking '%s'...\n", pszPath);
+
+ /*
+ * Duplicate the source file path, just for simplicity and restore the
+ * final character in the orignal. We cheerfully ignorining any
+ * possibility of multibyte UTF-8 sequences just like the caller did when
+ * setting it to '_'.
+ */
+ char *pszSrcArchive = RTStrDup(pszPath);
+ if (!pszSrcArchive)
+ return VERR_NO_STR_MEMORY;
+
+ pszPath[strlen(pszPath) - 1] = RT_C_TO_LOWER(pszFilename[strlen(pszFilename) - 1]);
+
+
+ /*
+ * Figuring out the argument list for the platform specific unpack util.
+ */
+#ifdef RT_OS_WINDOWS
+ RTPathChangeToDosSlashes(pszSrcArchive, false /*fForce*/);
+ RTPathChangeToDosSlashes(pszPath, false /*fForce*/);
+ const char *papszArgs[] =
+ {
+ "expand.exe",
+ pszSrcArchive,
+ pszPath,
+ NULL
+ };
+
+#else
+ char szExtractDir[RTPATH_MAX];
+ strcpy(szExtractDir, pszPath);
+ RTPathStripFilename(szExtractDir);
+
+ const char *papszArgs[] =
+ {
+ "cabextract",
+ "-L", /* Lower case extracted files. */
+ "-d", szExtractDir, /* Extraction path */
+ pszSrcArchive,
+ NULL
+ };
+#endif
+
+ /*
+ * Do the unpacking.
+ */
+ RTPROCESS hChild;
+ int rc = RTProcCreate(papszArgs[0], papszArgs, RTENV_DEFAULT,
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ RTPROC_FLAGS_NO_WINDOW | RTPROC_FLAGS_HIDDEN | RTPROC_FLAGS_SEARCH_PATH,
+#else
+ RTPROC_FLAGS_SEARCH_PATH,
+#endif
+ &hChild);
+ if (RT_SUCCESS(rc))
+ {
+ RTPROCSTATUS ProcStatus;
+ rc = RTProcWait(hChild, RTPROCWAIT_FLAGS_BLOCK, &ProcStatus);
+ if (RT_SUCCESS(rc))
+ {
+ if ( ProcStatus.enmReason == RTPROCEXITREASON_NORMAL
+ && ProcStatus.iStatus == 0)
+ {
+ if (RTPathExists(pszPath))
+ {
+ rtDbgCfgLog1(pThis, "Successfully unpacked '%s' to '%s'.\n", pszSrcArchive, pszPath);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rtDbgCfgLog1(pThis, "Successfully ran unpacker on '%s', but '%s' is missing!\n", pszSrcArchive, pszPath);
+ rc = VERR_ZIP_ERROR;
+ }
+ }
+ else
+ {
+ rtDbgCfgLog2(pThis, "Unpacking '%s' failed: iStatus=%d enmReason=%d\n",
+ pszSrcArchive, ProcStatus.iStatus, ProcStatus.enmReason);
+ rc = VERR_ZIP_CORRUPTED;
+ }
+ }
+ else
+ rtDbgCfgLog1(pThis, "Error waiting for process: %Rrc\n", rc);
+
+ }
+ else
+ rtDbgCfgLog1(pThis, "Error starting unpack process '%s': %Rrc\n", papszArgs[0], rc);
+
+ return rc;
+}
+
+
+static int rtDbgCfgTryDownloadAndOpen(PRTDBGCFGINT pThis, const char *pszServer, char *pszPath,
+ const char *pszCacheSubDir, const char *pszUuidMappingSubDir,
+ PRTPATHSPLIT pSplitFn, const char *pszCacheSuffix, uint32_t fFlags,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ RT_NOREF_PV(pszUuidMappingSubDir); /** @todo do we bother trying pszUuidMappingSubDir? */
+ RT_NOREF_PV(pszCacheSuffix); /** @todo do we bother trying pszUuidMappingSubDir? */
+ RT_NOREF_PV(fFlags);
+
+ if (pThis->fFlags & RTDBGCFG_FLAGS_NO_SYM_SRV)
+ return VWRN_NOT_FOUND;
+ if (!pszCacheSubDir || !*pszCacheSubDir)
+ return VWRN_NOT_FOUND;
+ if ( !(fFlags & RTDBGCFG_O_SYMSRV)
+ && !(fFlags & RTDBGCFG_O_DEBUGINFOD))
+ return VWRN_NOT_FOUND;
+
+ /*
+ * Create the path.
+ */
+ size_t cchTmp = strlen(pszPath);
+
+ int rc = RTDirCreateFullPath(pszPath, 0766);
+ if (!RTDirExists(pszPath))
+ {
+ Log(("Error creating cache dir '%s': %Rrc\n", pszPath, rc));
+ return rc;
+ }
+
+ const char *pszFilename = pSplitFn->apszComps[pSplitFn->cComps - 1];
+ rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+ RTStrToLower(&pszPath[cchTmp]);
+ if (!RTDirExists(pszPath))
+ {
+ rc = RTDirCreate(pszPath, 0766, 0);
+ if (RT_FAILURE(rc))
+ {
+ Log(("RTDirCreate(%s) -> %Rrc\n", pszPath, rc));
+ }
+ }
+
+ rc = RTPathAppend(pszPath, RTPATH_MAX, pszCacheSubDir);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!RTDirExists(pszPath))
+ {
+ rc = RTDirCreate(pszPath, 0766, 0);
+ if (RT_FAILURE(rc))
+ {
+ Log(("RTDirCreate(%s) -> %Rrc\n", pszPath, rc));
+ }
+ }
+
+ /* Prepare the destination file name while we're here. */
+ cchTmp = strlen(pszPath);
+ RTStrToLower(&pszPath[cchTmp]);
+ rc = RTPathAppend(pszPath, RTPATH_MAX, pszFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Download/copy the file.
+ */
+ char szUrl[_2K];
+ /* Download URL? */
+ if ( RTStrIStartsWith(pszServer, "http://")
+ || RTStrIStartsWith(pszServer, "https://")
+ || RTStrIStartsWith(pszServer, "ftp://") )
+ {
+#ifdef IPRT_WITH_HTTP
+ RTHTTP hHttp;
+ rc = RTHttpCreate(&hHttp);
+ if (RT_SUCCESS(rc))
+ {
+ RTHttpUseSystemProxySettings(hHttp);
+ RTHttpSetFollowRedirects(hHttp, 8);
+
+ static const char * const s_apszHeadersMsSymSrv[] =
+ {
+ "User-Agent: Microsoft-Symbol-Server/6.6.0999.9",
+ "Pragma: no-cache",
+ };
+
+ static const char * const s_apszHeadersDebuginfod[] =
+ {
+ "User-Agent: IPRT DbgCfg 1.0",
+ "Pragma: no-cache",
+ };
+
+ if (fFlags & RTDBGCFG_O_SYMSRV)
+ rc = RTHttpSetHeaders(hHttp, RT_ELEMENTS(s_apszHeadersMsSymSrv), s_apszHeadersMsSymSrv);
+ else /* Must be debuginfod. */
+ rc = RTHttpSetHeaders(hHttp, RT_ELEMENTS(s_apszHeadersDebuginfod), s_apszHeadersDebuginfod);
+ if (RT_SUCCESS(rc))
+ {
+ if (fFlags & RTDBGCFG_O_SYMSRV)
+ RTStrPrintf(szUrl, sizeof(szUrl), "%s/%s/%s/%s", pszServer, pszFilename, pszCacheSubDir, pszFilename);
+ else
+ RTStrPrintf(szUrl, sizeof(szUrl), "%s/buildid/%s/debuginfo", pszServer, pszCacheSubDir);
+
+ /** @todo Use some temporary file name and rename it after the operation
+ * since not all systems support read-deny file sharing
+ * settings. */
+ rtDbgCfgLog2(pThis, "Downloading '%s' to '%s'...\n", szUrl, pszPath);
+ rc = RTHttpGetFile(hHttp, szUrl, pszPath);
+ if (RT_FAILURE(rc))
+ {
+ RTFileDelete(pszPath);
+ rtDbgCfgLog1(pThis, "%Rrc on URL '%s'\n", rc, szUrl);
+ }
+ if ( rc == VERR_HTTP_NOT_FOUND
+ && (fFlags & RTDBGCFG_O_SYMSRV))
+ {
+ /* Try the compressed version of the file. */
+ pszPath[strlen(pszPath) - 1] = '_';
+ szUrl[strlen(szUrl) - 1] = '_';
+ rtDbgCfgLog2(pThis, "Downloading '%s' to '%s'...\n", szUrl, pszPath);
+ rc = RTHttpGetFile(hHttp, szUrl, pszPath);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgCfgUnpackMsCacheFile(pThis, pszPath, pszFilename);
+ else
+ {
+ rtDbgCfgLog1(pThis, "%Rrc on URL '%s'\n", rc, pszPath);
+ RTFileDelete(pszPath);
+ }
+ }
+ }
+
+ RTHttpDestroy(hHttp);
+ }
+#else
+ rc = VWRN_NOT_FOUND;
+#endif
+ }
+ /* No download, assume dir on server share. */
+ else
+ {
+ if (RTStrIStartsWith(pszServer, "file:///"))
+ pszServer += 4 + 1 + 3 - 1;
+
+ /* Compose the path to the uncompressed file on the server. */
+ rc = RTPathJoin(szUrl, sizeof(szUrl), pszServer, pszFilename);
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szUrl, sizeof(szUrl), pszCacheSubDir);
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(szUrl, sizeof(szUrl), pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ rtDbgCfgLog2(pThis, "Copying '%s' to '%s'...\n", szUrl, pszPath);
+ rc = RTFileCopy(szUrl, pszPath);
+ if (RT_FAILURE(rc))
+ {
+ RTFileDelete(pszPath);
+ rtDbgCfgLog1(pThis, "%Rrc on '%s'\n", rc, szUrl);
+
+ /* Try the compressed version. */
+ pszPath[strlen(pszPath) - 1] = '_';
+ szUrl[strlen(szUrl) - 1] = '_';
+ rtDbgCfgLog2(pThis, "Copying '%s' to '%s'...\n", szUrl, pszPath);
+ rc = RTFileCopy(szUrl, pszPath);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgCfgUnpackMsCacheFile(pThis, pszPath, pszFilename);
+ else
+ {
+ rtDbgCfgLog1(pThis, "%Rrc on '%s'\n", rc, pszPath);
+ RTFileDelete(pszPath);
+ }
+ }
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Succeeded in downloading it. Add UUID mapping?
+ */
+ if (pszUuidMappingSubDir)
+ {
+ /** @todo UUID mapping when downloading. */
+ }
+
+ /*
+ * Give the file a try.
+ */
+ Assert(RTFileExists(pszPath));
+ rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
+ rc = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
+ if (rc == VINF_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
+ else if (rc == VERR_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
+ else
+ rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc, pszPath);
+ }
+
+ return rc;
+}
+
+
+static int rtDbgCfgCopyFileToCache(PRTDBGCFGINT pThis, char const *pszSrc, const char *pchCache, size_t cchCache,
+ const char *pszCacheSubDir, const char *pszUuidMappingSubDir, PRTPATHSPLIT pSplitFn)
+{
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pszSrc); RT_NOREF_PV(pchCache); RT_NOREF_PV(cchCache);
+ RT_NOREF_PV(pszUuidMappingSubDir); RT_NOREF_PV(pSplitFn);
+
+ if (!pszCacheSubDir || !*pszCacheSubDir)
+ return VINF_SUCCESS;
+
+ /** @todo copy to cache */
+ return VINF_SUCCESS;
+}
+
+
+static int rtDbgCfgTryOpenCache(PRTDBGCFGINT pThis, char *pszPath, size_t cchCachePath,
+ const char *pszCacheSubDir, const char *pszUuidMappingSubDir,
+ PCRTPATHSPLIT pSplitFn, const char *pszCacheSuffix, uint32_t fFlags,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ Assert(pszPath[cchCachePath] == '\0');
+
+ /*
+ * If the cache doesn't exist, fail right away.
+ */
+ if (!pszCacheSubDir || !*pszCacheSubDir)
+ return VWRN_NOT_FOUND;
+ if (!RTDirExists(pszPath))
+ {
+ rtDbgCfgLog2(pThis, "Cache does not exist: '%s'\n", pszPath);
+ return VWRN_NOT_FOUND;
+ }
+
+ /*
+ * If we got a UUID mapping option, try it first as we can hopefully
+ * dispense with case folding.
+ */
+ if (pszUuidMappingSubDir)
+ {
+ int rc = RTPathAppend(pszPath, RTPATH_MAX, pszUuidMappingSubDir);
+ if ( RT_SUCCESS(rc)
+ && RTFileExists(pszPath))
+ {
+ /* Try resolve the path before presenting it to the client, a
+ 12 digit filename is of little worth. */
+ char szBackup[RTPATH_MAX];
+ strcpy(szBackup, pszPath);
+ rc = RTPathAbs(szBackup, pszPath, RTPATH_MAX);
+ if (RT_FAILURE(rc))
+ strcpy(pszPath, szBackup);
+
+ /* Do the callback thing. */
+ rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
+ int rc2 = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Found '%s' via uuid mapping.\n", pszPath);
+ else if (rc2 == VERR_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
+ else
+ rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, pszPath);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ return rc2;
+
+ /* Failed, restore the cache path. */
+ memcpy(pszPath, szBackup, cchCachePath);
+ }
+ pszPath[cchCachePath] = '\0';
+ }
+
+ /*
+ * Carefully construct the cache path with case insensitivity in mind.
+ */
+ bool const fCaseInsensitive = (fFlags & RTDBGCFG_O_CASE_INSENSITIVE)
+ && !rtDbgCfgIsFsCaseInsensitive(pszPath);
+ const char *pszFilename = pSplitFn->apszComps[pSplitFn->cComps - 1];
+
+ if (!rtDbgCfgIsDirAndFixCase(pszPath, pszFilename, fCaseInsensitive))
+ return VWRN_NOT_FOUND;
+
+ if (!rtDbgCfgIsDirAndFixCase(pszPath, pszCacheSubDir, fCaseInsensitive))
+ return VWRN_NOT_FOUND;
+
+ bool fProbablyCompressed = false;
+ if (!rtDbgCfgIsFileAndFixCase(pszPath, pszFilename, pszCacheSuffix, fCaseInsensitive,
+ RT_BOOL(fFlags & RTDBGCFG_O_MAYBE_COMPRESSED_MS), &fProbablyCompressed))
+ return VWRN_NOT_FOUND;
+ if (fProbablyCompressed)
+ {
+ int rc = rtDbgCfgUnpackMsCacheFile(pThis, pszPath, pszFilename);
+ if (RT_FAILURE(rc))
+ return VWRN_NOT_FOUND;
+ }
+
+ rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
+ int rc2 = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
+ else if (rc2 == VERR_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
+ else
+ rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, pszPath);
+ return rc2;
+}
+
+
+static int rtDbgCfgTryOpenList(PRTDBGCFGINT pThis, PRTLISTANCHOR pList, PRTPATHSPLIT pSplitFn, const char *pszCacheSubDir,
+ const char *pszUuidMappingSubDir, uint32_t fFlags, char *pszPath,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ int rcRet = VWRN_NOT_FOUND;
+ int rc2 = VINF_SUCCESS;
+
+ const char *pchCache = NULL;
+ size_t cchCache = 0;
+ int rcCache = VWRN_NOT_FOUND;
+
+ PRTDBGCFGSTR pCur;
+ RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
+ {
+ size_t cchDir = pCur->cch;
+ const char *pszDir = pCur->sz;
+ rtDbgCfgLog2(pThis, "Path list entry: '%s'\n", pszDir);
+
+ /* This is very simplistic, but we have a unreasonably large path
+ buffer, so it'll work just fine and simplify things greatly below. */
+ if (cchDir >= RTPATH_MAX - 8U)
+ {
+ if (RT_SUCCESS_NP(rcRet))
+ rcRet = VERR_FILENAME_TOO_LONG;
+ continue;
+ }
+
+ /*
+ * Process the path according to it's type.
+ */
+ if (!RTStrNICmp(pszDir, RT_STR_TUPLE("srv*")))
+ {
+ /*
+ * Symbol server.
+ */
+ pszDir += sizeof("srv*") - 1;
+ cchDir -= sizeof("srv*") - 1;
+ bool fSearchCache = false;
+ const char *pszServer = (const char *)memchr(pszDir, '*', cchDir);
+ if (!pszServer)
+ pszServer = pszDir;
+ else if (pszServer == pszDir)
+ continue;
+ else
+ {
+ fSearchCache = true;
+ pchCache = pszDir;
+ cchCache = pszServer - pszDir;
+ pszServer++;
+ }
+
+ /* We don't have any default cache directory, so skip if the cache is missing. */
+ if (cchCache == 0)
+ continue;
+
+ /* Search the cache first (if we haven't already done so). */
+ if (fSearchCache)
+ {
+ memcpy(pszPath, pchCache, cchCache);
+ pszPath[cchCache] = '\0';
+ RTPathChangeToUnixSlashes(pszPath, false);
+
+ rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, cchCache, pszCacheSubDir, pszUuidMappingSubDir,
+ pSplitFn, NULL /*pszCacheSuffix*/, fFlags, pfnCallback, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ return rc2;
+ }
+
+ /* Try downloading the file. */
+ if (rcCache == VWRN_NOT_FOUND)
+ {
+ memcpy(pszPath, pchCache, cchCache);
+ pszPath[cchCache] = '\0';
+ RTPathChangeToUnixSlashes(pszPath, false);
+
+ rc2 = rtDbgCfgTryDownloadAndOpen(pThis, pszServer, pszPath, pszCacheSubDir, pszUuidMappingSubDir,
+ pSplitFn, NULL /*pszCacheSuffix*/, fFlags, pfnCallback, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ return rc2;
+ }
+ }
+ else if (!RTStrNICmp(pszDir, RT_STR_TUPLE("cache*")))
+ {
+ /*
+ * Cache directory.
+ */
+ pszDir += sizeof("cache*") - 1;
+ cchDir -= sizeof("cache*") - 1;
+ if (!cchDir)
+ continue;
+ pchCache = pszDir;
+ cchCache = cchDir;
+
+ memcpy(pszPath, pchCache, cchCache);
+ pszPath[cchCache] = '\0';
+ RTPathChangeToUnixSlashes(pszPath, false);
+
+ rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, cchCache, pszCacheSubDir, pszUuidMappingSubDir,
+ pSplitFn, NULL /*pszCacheSuffix*/, fFlags, pfnCallback, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ return rc2;
+ }
+ else
+ {
+ /*
+ * Normal directory. Check for our own 'rec*' and 'norec*' prefix
+ * flags governing recursive searching.
+ */
+ uint32_t fFlagsDir = fFlags;
+ if (!RTStrNICmp(pszDir, RT_STR_TUPLE("rec*")))
+ {
+ pszDir += sizeof("rec*") - 1;
+ cchDir -= sizeof("rec*") - 1;
+ fFlagsDir |= RTDBGCFG_O_RECURSIVE;
+ }
+ else if (!RTStrNICmp(pszDir, RT_STR_TUPLE("norec*")))
+ {
+ pszDir += sizeof("norec*") - 1;
+ cchDir -= sizeof("norec*") - 1;
+ fFlagsDir &= ~RTDBGCFG_O_RECURSIVE;
+ }
+
+ /* Copy the path into the buffer and do the searching. */
+ memcpy(pszPath, pszDir, cchDir);
+ pszPath[cchDir] = '\0';
+ RTPathChangeToUnixSlashes(pszPath, false);
+
+ rc2 = rtDbgCfgTryOpenDir(pThis, pszPath, pSplitFn, fFlagsDir, pfnCallback, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ {
+ if ( rc2 == VINF_CALLBACK_RETURN
+ && cchCache > 0)
+ rtDbgCfgCopyFileToCache(pThis, pszPath, pchCache, cchCache,
+ pszCacheSubDir, pszUuidMappingSubDir, pSplitFn);
+ return rc2;
+ }
+ }
+
+ /* Propagate errors. */
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+ }
+
+ return rcRet;
+}
+
+
+/**
+ * Common worker routine for Image and debug info opening.
+ *
+ * This will not search using for suffixes.
+ *
+ * @returns IPRT status code.
+ * @param hDbgCfg The debugging configuration handle.
+ * NIL_RTDBGCFG is accepted, but the result is
+ * that no paths will be searched beyond the
+ * given and the current directory.
+ * @param pszFilename The filename to search for. This may or may
+ * not include a full or partial path.
+ * @param pszCacheSubDir The cache subdirectory to look in.
+ * @param pszUuidMappingSubDir UUID mapping subdirectory to check, NULL if
+ * no mapping wanted.
+ * @param fFlags Flags and hints.
+ * @param pfnCallback The open callback routine.
+ * @param pvUser1 User parameter 1.
+ * @param pvUser2 User parameter 2.
+ */
+static int rtDbgCfgOpenWithSubDir(RTDBGCFG hDbgCfg, const char *pszFilename, const char *pszCacheSubDir,
+ const char *pszUuidMappingSubDir, uint32_t fFlags,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ int rcRet = VINF_SUCCESS;
+ int rc2;
+
+ /*
+ * Do a little validating first.
+ */
+ PRTDBGCFGINT pThis = hDbgCfg;
+ if (pThis != NIL_RTDBGCFG)
+ RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
+ else
+ pThis = NULL;
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszCacheSubDir, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTDBGCFG_O_VALID_MASK), VERR_INVALID_FLAGS);
+
+ /*
+ * Do some guessing as to the way we should parse the filename and whether
+ * it's case exact or not.
+ */
+ bool fDosPath = RT_OPSYS_USES_DOS_PATHS(fFlags & RTDBGCFG_O_OPSYS_MASK)
+ || (fFlags & RTDBGCFG_O_CASE_INSENSITIVE)
+ || strchr(pszFilename, ':') != NULL
+ || strchr(pszFilename, '\\') != NULL;
+ if (fDosPath)
+ fFlags |= RTDBGCFG_O_CASE_INSENSITIVE;
+
+ rtDbgCfgLog2(pThis, "Looking for '%s' w/ cache subdir '%s' and %#x flags...\n", pszFilename, pszCacheSubDir, fFlags);
+
+ PRTPATHSPLIT pSplitFn;
+ rc2 = RTPathSplitA(pszFilename, &pSplitFn, fDosPath ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
+ if (RT_FAILURE(rc2))
+ return rc2;
+ AssertReturnStmt(pSplitFn->fProps & RTPATH_PROP_FILENAME, RTPathSplitFree(pSplitFn), VERR_IS_A_DIRECTORY);
+
+ /*
+ * Try the stored file name first if it has a kind of absolute path.
+ */
+ char szPath[RTPATH_MAX];
+ if (RTPATH_PROP_HAS_ROOT_SPEC(pSplitFn->fProps))
+ {
+ rc2 = RTPathSplitReassemble(pSplitFn, RTPATH_STR_F_STYLE_HOST, szPath, sizeof(szPath));
+ if (RT_SUCCESS(rc2) && RTFileExists(szPath))
+ {
+ RTPathChangeToUnixSlashes(szPath, false);
+ rtDbgCfgLog1(pThis, "Trying '%s'...\n", szPath);
+ rc2 = pfnCallback(pThis, szPath, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Found '%s'.\n", szPath);
+ else if (rc2 == VERR_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Error opening '%s'.\n", szPath);
+ else
+ rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, szPath);
+ }
+ }
+ if ( rc2 != VINF_CALLBACK_RETURN
+ && rc2 != VERR_CALLBACK_RETURN)
+ {
+ /*
+ * Try the current directory (will take cover relative paths
+ * skipped above).
+ */
+ rc2 = RTPathGetCurrent(szPath, sizeof(szPath));
+ if (RT_FAILURE(rc2))
+ strcpy(szPath, ".");
+ RTPathChangeToUnixSlashes(szPath, false);
+
+ rc2 = rtDbgCfgTryOpenDir(pThis, szPath, pSplitFn, fFlags, pfnCallback, pvUser1, pvUser2);
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+
+ if ( rc2 != VINF_CALLBACK_RETURN
+ && rc2 != VERR_CALLBACK_RETURN
+ && pThis)
+ {
+ rc2 = RTCritSectRwEnterShared(&pThis->CritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ /*
+ * Run the applicable lists.
+ */
+ rc2 = rtDbgCfgTryOpenList(pThis, &pThis->PathList, pSplitFn, pszCacheSubDir,
+ pszUuidMappingSubDir, fFlags, szPath, pfnCallback, pvUser1, pvUser2);
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+
+#ifdef RT_OS_WINDOWS
+ if ( rc2 != VINF_CALLBACK_RETURN
+ && rc2 != VERR_CALLBACK_RETURN
+ && (fFlags & RTDBGCFG_O_EXECUTABLE_IMAGE)
+ && !(fFlags & RTDBGCFG_O_NO_SYSTEM_PATHS)
+ && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_SYSTEM_PATHS) )
+ {
+ rc2 = rtDbgCfgTryOpenList(pThis, &pThis->NtExecutablePathList, pSplitFn, pszCacheSubDir,
+ pszUuidMappingSubDir, fFlags, szPath, pfnCallback, pvUser1, pvUser2);
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+ }
+
+ if ( rc2 != VINF_CALLBACK_RETURN
+ && rc2 != VERR_CALLBACK_RETURN
+ && !(fFlags & RTDBGCFG_O_NO_SYSTEM_PATHS)
+ && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_SYSTEM_PATHS) )
+ {
+ rc2 = rtDbgCfgTryOpenList(pThis, &pThis->NtSymbolPathList, pSplitFn, pszCacheSubDir,
+ pszUuidMappingSubDir, fFlags, szPath, pfnCallback, pvUser1, pvUser2);
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+ }
+#endif
+ RTCritSectRwLeaveShared(&pThis->CritSect);
+ }
+ else if (RT_SUCCESS(rcRet))
+ rcRet = rc2;
+ }
+ }
+
+ RTPathSplitFree(pSplitFn);
+ if ( rc2 == VINF_CALLBACK_RETURN
+ || rc2 == VERR_CALLBACK_RETURN)
+ rcRet = rc2;
+ else if (RT_SUCCESS(rcRet))
+ rcRet = VERR_NOT_FOUND;
+ return rcRet;
+}
+
+
+RTDECL(int) RTDbgCfgOpenEx(RTDBGCFG hDbgCfg, const char *pszFilename, const char *pszCacheSubDir,
+ const char *pszUuidMappingSubDir, uint32_t fFlags,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, pszCacheSubDir, pszUuidMappingSubDir, fFlags,
+ pfnCallback, pvUser1, pvUser2);
+}
+
+
+
+
+RTDECL(int) RTDbgCfgOpenPeImage(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t cbImage, uint32_t uTimestamp,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ char szSubDir[32];
+ RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage);
+ return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir, NULL,
+ RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
+ | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXECUTABLE_IMAGE,
+ pfnCallback, pvUser1, pvUser2);
+}
+
+
+RTDECL(int) RTDbgCfgOpenPdb70(RTDBGCFG hDbgCfg, const char *pszFilename, PCRTUUID pUuid, uint32_t uAge,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ char szSubDir[64];
+ if (!pUuid)
+ szSubDir[0] = '\0';
+ else
+ {
+ /* Stringify the UUID and remove the dashes. */
+ int rc2 = RTUuidToStr(pUuid, szSubDir, sizeof(szSubDir));
+ AssertRCReturn(rc2, rc2);
+
+ char *pszSrc = szSubDir;
+ char *pszDst = szSubDir;
+ char ch;
+ while ((ch = *pszSrc++))
+ if (ch != '-')
+ *pszDst++ = RT_C_TO_UPPER(ch);
+
+ RTStrPrintf(pszDst, &szSubDir[sizeof(szSubDir)] - pszDst, "%X", uAge);
+ }
+
+ return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir, NULL,
+ RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
+ | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXT_DEBUG_FILE,
+ pfnCallback, pvUser1, pvUser2);
+}
+
+
+RTDECL(int) RTDbgCfgOpenPdb20(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t cbImage, uint32_t uTimestamp, uint32_t uAge,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ RT_NOREF_PV(cbImage);
+ /** @todo test this! */
+ char szSubDir[32];
+ RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, uAge);
+ return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir, NULL,
+ RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
+ | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXT_DEBUG_FILE,
+ pfnCallback, pvUser1, pvUser2);
+}
+
+
+RTDECL(int) RTDbgCfgOpenDbg(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t cbImage, uint32_t uTimestamp,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ char szSubDir[32];
+ RTStrPrintf(szSubDir, sizeof(szSubDir), "%08X%x", uTimestamp, cbImage);
+ return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir, NULL,
+ RT_OPSYS_WINDOWS /* approx */ | RTDBGCFG_O_SYMSRV | RTDBGCFG_O_CASE_INSENSITIVE
+ | RTDBGCFG_O_MAYBE_COMPRESSED_MS | RTDBGCFG_O_EXT_DEBUG_FILE,
+ pfnCallback, pvUser1, pvUser2);
+}
+
+
+RTDECL(int) RTDbgCfgOpenDwo(RTDBGCFG hDbgCfg, const char *pszFilename, uint32_t uCrc32,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ char szSubDir[32];
+ RTStrPrintf(szSubDir, sizeof(szSubDir), "%08x", uCrc32);
+ return rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, szSubDir, NULL,
+ RT_OPSYS_UNKNOWN | RTDBGCFG_O_EXT_DEBUG_FILE,
+ pfnCallback, pvUser1, pvUser2);
+}
+
+
+RTDECL(int) RTDbgCfgOpenDwoBuildId(RTDBGCFG hDbgCfg, const char *pszFilename, const uint8_t *pbBuildId,
+ size_t cbBuildId, PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ char *pszSubDir = NULL;
+ int rc = RTStrAPrintf(&pszSubDir, "%#.*Rhxs", cbBuildId, pbBuildId);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtDbgCfgOpenWithSubDir(hDbgCfg, pszFilename, pszSubDir, NULL,
+ RTDBGCFG_O_DEBUGINFOD | RT_OPSYS_UNKNOWN | RTDBGCFG_O_EXT_DEBUG_FILE,
+ pfnCallback, pvUser1, pvUser2);
+ RTStrFree(pszSubDir);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * D a r w i n . d S Y M b u n d l e s
+ * D a r w i n . d S Y M b u n d l e s
+ * D a r w i n . d S Y M b u n d l e s
+ *
+ */
+
+/**
+ * Very similar to rtDbgCfgTryOpenDir.
+ */
+static int rtDbgCfgTryOpenDsymBundleInDir(PRTDBGCFGINT pThis, char *pszPath, PRTPATHSPLIT pSplitFn,
+ const char * const *papszSuffixes, uint32_t fFlags,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ int rcRet = VWRN_NOT_FOUND;
+ int rc2;
+
+ /* If the directory doesn't exist, just quit immediately.
+ Note! Our case insensitivity doesn't extend to the search dirs themselfs,
+ only to the bits under neath them. */
+ if (!RTDirExists(pszPath))
+ {
+ rtDbgCfgLog2(pThis, "Dir does not exist: '%s'\n", pszPath);
+ return rcRet;
+ }
+
+ /* Figure out whether we have to do a case sensitive search or not.
+ Note! As a simplification, we don't ask for case settings in each
+ directory under the user specified path, we assume the file
+ systems that mounted there have compatible settings. Faster
+ that way. */
+ bool const fCaseInsensitive = (fFlags & RTDBGCFG_O_CASE_INSENSITIVE)
+ && !rtDbgCfgIsFsCaseInsensitive(pszPath);
+
+ size_t const cchPath = strlen(pszPath);
+
+ /*
+ * Look for the file with less and less of the original path given.
+ * Also try out typical bundle extension variations.
+ */
+ const char *pszName = pSplitFn->apszComps[pSplitFn->cComps - 1];
+ for (unsigned i = RTPATH_PROP_HAS_ROOT_SPEC(pSplitFn->fProps); i < pSplitFn->cComps; i++)
+ {
+ pszPath[cchPath] = '\0';
+
+ rc2 = VINF_SUCCESS;
+ for (unsigned j = i; j < pSplitFn->cComps - 1U && RT_SUCCESS(rc2); j++)
+ if (!rtDbgCfgIsDirAndFixCase(pszPath, pSplitFn->apszComps[i], fCaseInsensitive))
+ rc2 = VERR_FILE_NOT_FOUND;
+ if (RT_SUCCESS(rc2))
+ {
+ for (uint32_t iSuffix = 0; papszSuffixes[iSuffix]; iSuffix++)
+ {
+ if ( !rtDbgCfgIsDirAndFixCase2(pszPath, pszName, papszSuffixes[iSuffix], fCaseInsensitive)
+ && !rtDbgCfgIsDirAndFixCase(pszPath, "Contents", fCaseInsensitive)
+ && !rtDbgCfgIsDirAndFixCase(pszPath, "Resources", fCaseInsensitive)
+ && !rtDbgCfgIsDirAndFixCase(pszPath, "DWARF", fCaseInsensitive))
+ {
+ if (rtDbgCfgIsFileAndFixCase(pszPath, pszName, NULL /*pszSuffix*/, fCaseInsensitive, false, NULL))
+ {
+ rtDbgCfgLog1(pThis, "Trying '%s'...\n", pszPath);
+ rc2 = pfnCallback(pThis, pszPath, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ {
+ if (rc2 == VINF_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Found '%s'.\n", pszPath);
+ else
+ rtDbgCfgLog1(pThis, "Error opening '%s'.\n", pszPath);
+ return rc2;
+ }
+ rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, pszPath);
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+ }
+ }
+ }
+ }
+ rc2 = VERR_FILE_NOT_FOUND;
+ }
+
+ /*
+ * Do a recursive search if requested.
+ */
+ if ( (fFlags & RTDBGCFG_O_RECURSIVE)
+ && !(pThis->fFlags & RTDBGCFG_FLAGS_NO_RECURSIV_SEARCH) )
+ {
+ /** @todo Recursive searching will be done later. */
+ }
+
+ return rcRet;
+}
+
+
+/**
+ * Very similar to rtDbgCfgTryOpenList.
+ */
+static int rtDbgCfgTryOpenBundleInList(PRTDBGCFGINT pThis, PRTLISTANCHOR pList, PRTPATHSPLIT pSplitFn,
+ const char * const *papszSuffixes, const char *pszCacheSubDir,
+ const char *pszCacheSuffix, const char *pszUuidMappingSubDir,
+ uint32_t fFlags, char *pszPath,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ int rcRet = VWRN_NOT_FOUND;
+ int rc2;
+
+ const char *pchCache = NULL;
+ size_t cchCache = 0;
+ int rcCache = VWRN_NOT_FOUND;
+
+ PRTDBGCFGSTR pCur;
+ RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
+ {
+ size_t cchDir = pCur->cch;
+ const char *pszDir = pCur->sz;
+ rtDbgCfgLog2(pThis, "Path list entry: '%s'\n", pszDir);
+
+ /* This is very simplistic, but we have a unreasonably large path
+ buffer, so it'll work just fine and simplify things greatly below. */
+ if (cchDir >= RTPATH_MAX - 8U)
+ {
+ if (RT_SUCCESS_NP(rcRet))
+ rcRet = VERR_FILENAME_TOO_LONG;
+ continue;
+ }
+
+ /*
+ * Process the path according to it's type.
+ */
+ rc2 = VINF_SUCCESS;
+ if (!RTStrNICmp(pszDir, RT_STR_TUPLE("srv*")))
+ {
+ /*
+ * Symbol server.
+ */
+ pszDir += sizeof("srv*") - 1;
+ cchDir -= sizeof("srv*") - 1;
+ bool fSearchCache = false;
+ const char *pszServer = (const char *)memchr(pszDir, '*', cchDir);
+ if (!pszServer)
+ pszServer = pszDir;
+ else if (pszServer == pszDir)
+ continue;
+ else
+ {
+ fSearchCache = true;
+ pchCache = pszDir;
+ cchCache = pszServer - pszDir;
+ pszServer++;
+ }
+
+ /* We don't have any default cache directory, so skip if the cache is missing. */
+ if (cchCache == 0)
+ continue;
+
+ /* Search the cache first (if we haven't already done so). */
+ if (fSearchCache)
+ {
+ memcpy(pszPath, pchCache, cchCache);
+ pszPath[cchCache] = '\0';
+ RTPathChangeToUnixSlashes(pszPath, false);
+
+ rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, cchCache, pszCacheSubDir, pszUuidMappingSubDir,
+ pSplitFn, pszCacheSuffix, fFlags, pfnCallback, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ return rc2;
+ }
+
+ /* Try downloading the file. */
+ if (rcCache == VWRN_NOT_FOUND)
+ {
+ memcpy(pszPath, pchCache, cchCache);
+ pszPath[cchCache] = '\0';
+ RTPathChangeToUnixSlashes(pszPath, false);
+
+ rc2 = rtDbgCfgTryDownloadAndOpen(pThis, pszServer, pszPath, pszCacheSubDir, pszUuidMappingSubDir,
+ pSplitFn, pszCacheSuffix, fFlags, pfnCallback, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ return rc2;
+ }
+ }
+ else if (!RTStrNICmp(pszDir, RT_STR_TUPLE("cache*")))
+ {
+ /*
+ * Cache directory.
+ */
+ pszDir += sizeof("cache*") - 1;
+ cchDir -= sizeof("cache*") - 1;
+ if (!cchDir)
+ continue;
+ pchCache = pszDir;
+ cchCache = cchDir;
+
+ memcpy(pszPath, pchCache, cchCache);
+ pszPath[cchCache] = '\0';
+ RTPathChangeToUnixSlashes(pszPath, false);
+
+ rcCache = rc2 = rtDbgCfgTryOpenCache(pThis, pszPath, cchCache, pszCacheSubDir, pszUuidMappingSubDir,
+ pSplitFn, pszCacheSuffix, fFlags, pfnCallback, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ return rc2;
+ }
+ else
+ {
+ /*
+ * Normal directory. Check for our own 'rec*' and 'norec*' prefix
+ * flags governing recursive searching.
+ */
+ uint32_t fFlagsDir = fFlags;
+ if (!RTStrNICmp(pszDir, RT_STR_TUPLE("rec*")))
+ {
+ pszDir += sizeof("rec*") - 1;
+ cchDir -= sizeof("rec*") - 1;
+ fFlagsDir |= RTDBGCFG_O_RECURSIVE;
+ }
+ else if (!RTStrNICmp(pszDir, RT_STR_TUPLE("norec*")))
+ {
+ pszDir += sizeof("norec*") - 1;
+ cchDir -= sizeof("norec*") - 1;
+ fFlagsDir &= ~RTDBGCFG_O_RECURSIVE;
+ }
+
+ /* Copy the path into the buffer and do the searching. */
+ memcpy(pszPath, pszDir, cchDir);
+ pszPath[cchDir] = '\0';
+ RTPathChangeToUnixSlashes(pszPath, false);
+
+ rc2 = rtDbgCfgTryOpenDsymBundleInDir(pThis, pszPath, pSplitFn, papszSuffixes, fFlagsDir,
+ pfnCallback, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN || rc2 == VERR_CALLBACK_RETURN)
+ {
+ if ( rc2 == VINF_CALLBACK_RETURN
+ && cchCache > 0)
+ rtDbgCfgCopyFileToCache(pThis, pszPath, pchCache, cchCache,
+ pszCacheSubDir, pszUuidMappingSubDir, pSplitFn);
+ return rc2;
+ }
+ }
+
+ /* Propagate errors. */
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+ }
+
+ return rcRet;
+}
+
+
+/**
+ * Creating a UUID mapping subdirectory path for use in caches.
+ *
+ * @returns IPRT status code.
+ * @param pszSubDir The output buffer.
+ * @param cbSubDir The size of the output buffer. (Top dir length +
+ * slash + UUID string len + extra dash.)
+ * @param pszTopDir The top level cache directory name. No slashes
+ * or other directory separators, please.
+ * @param pUuid The UUID.
+ */
+static int rtDbgCfgConstructUuidMappingSubDir(char *pszSubDir, size_t cbSubDir, const char *pszTopDir, PCRTUUID pUuid)
+{
+ Assert(!strpbrk(pszTopDir, ":/\\"));
+
+ size_t cchTopDir = strlen(pszTopDir);
+ if (cchTopDir + 1 + 1 + RTUUID_STR_LENGTH + 1 > cbSubDir)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pszSubDir, pszTopDir, cchTopDir);
+
+ pszSubDir += cchTopDir;
+ *pszSubDir++ = RTPATH_SLASH;
+ cbSubDir -= cchTopDir + 1;
+
+ /* ed5a8336-35c2-4892-9122-21d5572924a3 -> ED5A/8336/35C2/4892/9122/21D5572924A3 */
+ int rc = RTUuidToStr(pUuid, pszSubDir + 1, cbSubDir - 1); AssertRCReturn(rc, rc);
+ RTStrToUpper(pszSubDir + 1);
+ memmove(pszSubDir, pszSubDir + 1, 4);
+ pszSubDir += 4;
+ *pszSubDir = RTPATH_SLASH;
+ pszSubDir += 5;
+ *pszSubDir = RTPATH_SLASH;
+ pszSubDir += 5;
+ *pszSubDir = RTPATH_SLASH;
+ pszSubDir += 5;
+ *pszSubDir = RTPATH_SLASH;
+ pszSubDir += 5;
+ *pszSubDir = RTPATH_SLASH;
+
+ return VINF_SUCCESS;
+}
+
+
+static int rtDbgCfgOpenBundleFile(RTDBGCFG hDbgCfg, const char *pszImage, const char * const *papszSuffixes,
+ const char *pszBundleSubDir, PCRTUUID pUuid, const char *pszUuidMapDirName,
+ const char *pszCacheSuffix, bool fOpenImage,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ /*
+ * Bundles are directories, means we can forget about sharing code much
+ * with the other RTDbgCfgOpenXXX methods. Thus we're duplicating a lot of
+ * code from rtDbgCfgOpenWithSubDir with .dSYM/.kext/.dylib/.app/.* related
+ * adjustments, so, a bug found here or there probably means the other
+ * version needs updating.
+ */
+ int rcRet = VINF_SUCCESS;
+ int rc2;
+
+ /*
+ * Do a little validating first.
+ */
+ PRTDBGCFGINT pThis = hDbgCfg;
+ if (pThis != NIL_RTDBGCFG)
+ RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
+ else
+ pThis = NULL;
+ AssertPtrReturn(pszImage, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);
+
+ /*
+ * Set up rtDbgCfgOpenWithSubDir and uuid map parameters.
+ */
+ uint32_t fFlags = RTDBGCFG_O_EXT_DEBUG_FILE | RT_OPSYS_DARWIN;
+ const char *pszCacheSubDir = NULL;
+ char szCacheSubDir[RTUUID_STR_LENGTH];
+ const char *pszUuidMappingSubDir = NULL;
+ char szUuidMappingSubDir[RTUUID_STR_LENGTH + 16];
+ if (pUuid)
+ {
+ /* Since Mac debuggers uses UUID mappings, we just the slashing default
+ UUID string representation instead of stripping dashes like for PDB. */
+ RTUuidToStr(pUuid, szCacheSubDir, sizeof(szCacheSubDir));
+ pszCacheSubDir = szCacheSubDir;
+
+ rc2 = rtDbgCfgConstructUuidMappingSubDir(szUuidMappingSubDir, sizeof(szUuidMappingSubDir), pszUuidMapDirName, pUuid);
+ AssertRCReturn(rc2, rc2);
+ pszUuidMappingSubDir = szUuidMappingSubDir;
+ }
+
+ /*
+ * Do some guessing as to the way we should parse the filename and whether
+ * it's case exact or not.
+ */
+ bool fDosPath = strchr(pszImage, ':') != NULL
+ || strchr(pszImage, '\\') != NULL
+ || RT_OPSYS_USES_DOS_PATHS(fFlags & RTDBGCFG_O_OPSYS_MASK)
+ || (fFlags & RTDBGCFG_O_CASE_INSENSITIVE);
+ if (fDosPath)
+ fFlags |= RTDBGCFG_O_CASE_INSENSITIVE;
+
+ rtDbgCfgLog2(pThis, "Looking for '%s' with %#x flags...\n", pszImage, fFlags);
+
+ PRTPATHSPLIT pSplitFn;
+ rc2 = RTPathSplitA(pszImage, &pSplitFn, fDosPath ? RTPATH_STR_F_STYLE_DOS : RTPATH_STR_F_STYLE_UNIX);
+ if (RT_FAILURE(rc2))
+ return rc2;
+ AssertReturnStmt(pSplitFn->fProps & RTPATH_PROP_FILENAME, RTPathSplitFree(pSplitFn), VERR_IS_A_DIRECTORY);
+
+ /*
+ * Try the image directory first.
+ */
+ char szPath[RTPATH_MAX];
+ if (pSplitFn->cComps > 0)
+ {
+ rc2 = RTPathSplitReassemble(pSplitFn, RTPATH_STR_F_STYLE_HOST, szPath, sizeof(szPath));
+ if (fOpenImage && RT_SUCCESS(rc2))
+ {
+ rc2 = RTStrCat(szPath, sizeof(szPath), papszSuffixes[0]);
+ if (RT_SUCCESS(rc2))
+ rc2 = RTStrCat(szPath, sizeof(szPath), pszBundleSubDir);
+ if (RT_SUCCESS(rc2))
+ rc2 = RTPathAppend(szPath, sizeof(szPath), pSplitFn->apszComps[pSplitFn->cComps - 1]);
+ }
+ if (RT_SUCCESS(rc2) && RTPathExists(szPath))
+ {
+ RTPathChangeToUnixSlashes(szPath, false);
+ rtDbgCfgLog1(pThis, "Trying '%s'...\n", szPath);
+ rc2 = pfnCallback(pThis, szPath, pvUser1, pvUser2);
+ if (rc2 == VINF_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Found '%s'.\n", szPath);
+ else if (rc2 == VERR_CALLBACK_RETURN)
+ rtDbgCfgLog1(pThis, "Error opening '%s'.\n", szPath);
+ else
+ rtDbgCfgLog1(pThis, "Error %Rrc opening '%s'.\n", rc2, szPath);
+ }
+ }
+ if ( rc2 != VINF_CALLBACK_RETURN
+ && rc2 != VERR_CALLBACK_RETURN)
+ {
+ /*
+ * Try the current directory (will take cover relative paths
+ * skipped above).
+ */
+ rc2 = RTPathGetCurrent(szPath, sizeof(szPath));
+ if (RT_FAILURE(rc2))
+ strcpy(szPath, ".");
+ RTPathChangeToUnixSlashes(szPath, false);
+
+ rc2 = rtDbgCfgTryOpenDsymBundleInDir(pThis, szPath, pSplitFn, g_apszDSymBundleSuffixes,
+ fFlags, pfnCallback, pvUser1, pvUser2);
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+
+ if ( rc2 != VINF_CALLBACK_RETURN
+ && rc2 != VERR_CALLBACK_RETURN
+ && pThis)
+ {
+ rc2 = RTCritSectRwEnterShared(&pThis->CritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ /*
+ * Run the applicable lists.
+ */
+ rc2 = rtDbgCfgTryOpenBundleInList(pThis, &pThis->PathList, pSplitFn, g_apszDSymBundleSuffixes,
+ pszCacheSubDir, pszCacheSuffix,
+ pszUuidMappingSubDir, fFlags, szPath,
+ pfnCallback, pvUser1, pvUser2);
+ if (RT_FAILURE(rc2) && RT_SUCCESS_NP(rcRet))
+ rcRet = rc2;
+
+ RTCritSectRwLeaveShared(&pThis->CritSect);
+ }
+ else if (RT_SUCCESS(rcRet))
+ rcRet = rc2;
+ }
+ }
+
+ RTPathSplitFree(pSplitFn);
+ if ( rc2 == VINF_CALLBACK_RETURN
+ || rc2 == VERR_CALLBACK_RETURN)
+ rcRet = rc2;
+ else if (RT_SUCCESS(rcRet))
+ rcRet = VERR_NOT_FOUND;
+ return rcRet;
+}
+
+
+RTDECL(int) RTDbgCfgOpenDsymBundle(RTDBGCFG hDbgCfg, const char *pszImage, PCRTUUID pUuid,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ return rtDbgCfgOpenBundleFile(hDbgCfg, pszImage, g_apszDSymBundleSuffixes,
+ "Contents" RTPATH_SLASH_STR "Resources" RTPATH_SLASH_STR "DWARF",
+ pUuid, RTDBG_CACHE_UUID_MAP_DIR_DSYMS, RTDBG_CACHE_DSYM_FILE_SUFFIX, false /* fOpenImage */,
+ pfnCallback, pvUser1, pvUser2);
+}
+
+
+RTDECL(int) RTDbgCfgOpenMachOImage(RTDBGCFG hDbgCfg, const char *pszImage, PCRTUUID pUuid,
+ PFNRTDBGCFGOPEN pfnCallback, void *pvUser1, void *pvUser2)
+{
+ return rtDbgCfgOpenBundleFile(hDbgCfg, pszImage, g_apszBundleSuffixes,
+ "Contents" RTPATH_SLASH_STR "MacOS",
+ pUuid, RTDBG_CACHE_UUID_MAP_DIR_IMAGES, NULL /*pszCacheSuffix*/, true /* fOpenImage */,
+ pfnCallback, pvUser1, pvUser2);
+}
+
+
+
+RTDECL(int) RTDbgCfgSetLogCallback(RTDBGCFG hDbgCfg, PFNRTDBGCFGLOG pfnCallback, void *pvUser)
+{
+ PRTDBGCFGINT pThis = hDbgCfg;
+ RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pfnCallback, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pThis->pfnLogCallback == NULL
+ || pfnCallback == NULL
+ || pfnCallback == pThis->pfnLogCallback)
+ {
+ pThis->pfnLogCallback = NULL;
+ pThis->pvLogUser = NULL;
+ ASMCompilerBarrier(); /* paranoia */
+ pThis->pvLogUser = pvUser;
+ pThis->pfnLogCallback = pfnCallback;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+ RTCritSectRwLeaveExcl(&pThis->CritSect);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Frees a string list.
+ *
+ * @param pList The list to free.
+ */
+static void rtDbgCfgFreeStrList(PRTLISTANCHOR pList)
+{
+ PRTDBGCFGSTR pCur;
+ PRTDBGCFGSTR pNext;
+ RTListForEachSafe(pList, pCur, pNext, RTDBGCFGSTR, ListEntry)
+ {
+ RTListNodeRemove(&pCur->ListEntry);
+ RTMemFree(pCur);
+ }
+}
+
+
+/**
+ * Make changes to a string list, given a semicolon separated input string.
+ *
+ * @returns VINF_SUCCESS, VERR_FILENAME_TOO_LONG, VERR_NO_MEMORY
+ * @param pThis The config instance.
+ * @param enmOp The change operation.
+ * @param pszValue The input strings separated by semicolon.
+ * @param fPaths Indicates that this is a path list and that we
+ * should look for srv and cache prefixes.
+ * @param pList The string list anchor.
+ */
+static int rtDbgCfgChangeStringList(PRTDBGCFGINT pThis, RTDBGCFGOP enmOp, const char *pszValue, bool fPaths,
+ PRTLISTANCHOR pList)
+{
+ RT_NOREF_PV(pThis); RT_NOREF_PV(fPaths);
+
+ if (enmOp == RTDBGCFGOP_SET)
+ rtDbgCfgFreeStrList(pList);
+
+ PRTLISTNODE pPrependTo = pList;
+ while (*pszValue)
+ {
+ /* Skip separators. */
+ while (*pszValue == ';')
+ pszValue++;
+ if (!*pszValue)
+ break;
+
+ /* Find the end of this path. */
+ const char *pchPath = pszValue++;
+ char ch;
+ while ((ch = *pszValue) && ch != ';')
+ pszValue++;
+ size_t cchPath = pszValue - pchPath;
+ if (cchPath >= UINT16_MAX)
+ return VERR_FILENAME_TOO_LONG;
+
+ if (enmOp == RTDBGCFGOP_REMOVE)
+ {
+ /*
+ * Remove all occurences.
+ */
+ PRTDBGCFGSTR pCur;
+ PRTDBGCFGSTR pNext;
+ RTListForEachSafe(pList, pCur, pNext, RTDBGCFGSTR, ListEntry)
+ {
+ if ( pCur->cch == cchPath
+ && !memcmp(pCur->sz, pchPath, cchPath))
+ {
+ RTListNodeRemove(&pCur->ListEntry);
+ RTMemFree(pCur);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * We're adding a new one.
+ */
+ PRTDBGCFGSTR pNew = (PRTDBGCFGSTR)RTMemAlloc(RT_UOFFSETOF_DYN(RTDBGCFGSTR, sz[cchPath + 1]));
+ if (!pNew)
+ return VERR_NO_MEMORY;
+ pNew->cch = (uint16_t)cchPath;
+ pNew->fFlags = 0;
+ memcpy(pNew->sz, pchPath, cchPath);
+ pNew->sz[cchPath] = '\0';
+
+ if (enmOp == RTDBGCFGOP_PREPEND)
+ {
+ RTListNodeInsertAfter(pPrependTo, &pNew->ListEntry);
+ pPrependTo = &pNew->ListEntry;
+ }
+ else
+ RTListAppend(pList, &pNew->ListEntry);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Make changes to a 64-bit value
+ *
+ * @returns VINF_SUCCESS, VERR_DBG_CFG_INVALID_VALUE.
+ * @param pThis The config instance.
+ * @param enmOp The change operation.
+ * @param pszValue The input value.
+ * @param paMnemonics The mnemonics map for this value.
+ * @param puValue The value to change.
+ */
+static int rtDbgCfgChangeStringU64(PRTDBGCFGINT pThis, RTDBGCFGOP enmOp, const char *pszValue,
+ PCRTDBGCFGU64MNEMONIC paMnemonics, uint64_t *puValue)
+{
+ RT_NOREF_PV(pThis);
+
+ uint64_t uNew = enmOp == RTDBGCFGOP_SET ? 0 : *puValue;
+ char ch;
+ while ((ch = *pszValue))
+ {
+ /* skip whitespace and separators */
+ while (RT_C_IS_SPACE(ch) || RT_C_IS_CNTRL(ch) || ch == ';' || ch == ':')
+ ch = *++pszValue;
+ if (!ch)
+ break;
+
+ if (RT_C_IS_DIGIT(ch))
+ {
+ uint64_t uTmp;
+ int rc = RTStrToUInt64Ex(pszValue, (char **)&pszValue, 0, &uTmp);
+ if (RT_FAILURE(rc) || rc == VWRN_NUMBER_TOO_BIG)
+ return VERR_DBG_CFG_INVALID_VALUE;
+
+ if (enmOp != RTDBGCFGOP_REMOVE)
+ uNew |= uTmp;
+ else
+ uNew &= ~uTmp;
+ }
+ else
+ {
+ /* A mnemonic, find the end of it. */
+ const char *pszMnemonic = pszValue - 1;
+ do
+ ch = *++pszValue;
+ while (ch && !RT_C_IS_SPACE(ch) && !RT_C_IS_CNTRL(ch) && ch != ';' && ch != ':');
+ size_t cchMnemonic = pszValue - pszMnemonic;
+
+ /* Look it up in the map and apply it. */
+ unsigned i = 0;
+ while (paMnemonics[i].pszMnemonic)
+ {
+ if ( cchMnemonic == paMnemonics[i].cchMnemonic
+ && !memcmp(pszMnemonic, paMnemonics[i].pszMnemonic, cchMnemonic))
+ {
+ if (paMnemonics[i].fSet ? enmOp != RTDBGCFGOP_REMOVE : enmOp == RTDBGCFGOP_REMOVE)
+ uNew |= paMnemonics[i].fFlags;
+ else
+ uNew &= ~paMnemonics[i].fFlags;
+ break;
+ }
+ i++;
+ }
+
+ if (!paMnemonics[i].pszMnemonic)
+ return VERR_DBG_CFG_INVALID_VALUE;
+ }
+ }
+
+ *puValue = uNew;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTDbgCfgChangeString(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, RTDBGCFGOP enmOp, const char *pszValue)
+{
+ PRTDBGCFGINT pThis = hDbgCfg;
+ RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
+ AssertReturn(enmOp > RTDBGCFGOP_INVALID && enmOp < RTDBGCFGOP_END, VERR_INVALID_PARAMETER);
+ if (!pszValue)
+ pszValue = "";
+ else
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmProp)
+ {
+ case RTDBGCFGPROP_FLAGS:
+ rc = rtDbgCfgChangeStringU64(pThis, enmOp, pszValue, g_aDbgCfgFlags, &pThis->fFlags);
+ break;
+ case RTDBGCFGPROP_PATH:
+ rc = rtDbgCfgChangeStringList(pThis, enmOp, pszValue, true, &pThis->PathList);
+ break;
+ case RTDBGCFGPROP_SUFFIXES:
+ rc = rtDbgCfgChangeStringList(pThis, enmOp, pszValue, false, &pThis->SuffixList);
+ break;
+ case RTDBGCFGPROP_SRC_PATH:
+ rc = rtDbgCfgChangeStringList(pThis, enmOp, pszValue, true, &pThis->SrcPathList);
+ break;
+ default:
+ AssertFailed();
+ rc = VERR_INTERNAL_ERROR_3;
+ }
+
+ RTCritSectRwLeaveExcl(&pThis->CritSect);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTDbgCfgChangeUInt(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, RTDBGCFGOP enmOp, uint64_t uValue)
+{
+ PRTDBGCFGINT pThis = hDbgCfg;
+ RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
+ AssertReturn(enmOp > RTDBGCFGOP_INVALID && enmOp < RTDBGCFGOP_END, VERR_INVALID_PARAMETER);
+
+ int rc = RTCritSectRwEnterExcl(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t *puValue = NULL;
+ switch (enmProp)
+ {
+ case RTDBGCFGPROP_FLAGS:
+ puValue = &pThis->fFlags;
+ break;
+ default:
+ rc = VERR_DBG_CFG_NOT_UINT_PROP;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmOp)
+ {
+ case RTDBGCFGOP_SET:
+ *puValue = uValue;
+ break;
+ case RTDBGCFGOP_APPEND:
+ case RTDBGCFGOP_PREPEND:
+ *puValue |= uValue;
+ break;
+ case RTDBGCFGOP_REMOVE:
+ *puValue &= ~uValue;
+ break;
+ default:
+ AssertFailed();
+ rc = VERR_INTERNAL_ERROR_2;
+ }
+ }
+
+ RTCritSectRwLeaveExcl(&pThis->CritSect);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Querys a string list as a single string (semicolon separators).
+ *
+ * @returns VINF_SUCCESS, VERR_BUFFER_OVERFLOW.
+ * @param hDbgCfg The config instance handle.
+ * @param pList The string list anchor.
+ * @param pszValue The output buffer.
+ * @param cbValue The size of the output buffer.
+ */
+static int rtDbgCfgQueryStringList(RTDBGCFG hDbgCfg, PRTLISTANCHOR pList,
+ char *pszValue, size_t cbValue)
+{
+ RT_NOREF_PV(hDbgCfg);
+
+ /*
+ * Check the length first.
+ */
+ size_t cbReq = 1;
+ PRTDBGCFGSTR pCur;
+ RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
+ cbReq += pCur->cch + 1;
+ if (cbReq > cbValue)
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Construct the string list in the buffer.
+ */
+ char *psz = pszValue;
+ RTListForEach(pList, pCur, RTDBGCFGSTR, ListEntry)
+ {
+ if (psz != pszValue)
+ *psz++ = ';';
+ memcpy(psz, pCur->sz, pCur->cch);
+ psz += pCur->cch;
+ }
+ *psz = '\0';
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Querys the string value of a 64-bit unsigned int.
+ *
+ * @returns VINF_SUCCESS, VERR_BUFFER_OVERFLOW.
+ * @param hDbgCfg The config instance handle.
+ * @param uValue The value to query.
+ * @param paMnemonics The mnemonics map for this value.
+ * @param pszValue The output buffer.
+ * @param cbValue The size of the output buffer.
+ */
+static int rtDbgCfgQueryStringU64(RTDBGCFG hDbgCfg, uint64_t uValue, PCRTDBGCFGU64MNEMONIC paMnemonics,
+ char *pszValue, size_t cbValue)
+{
+ RT_NOREF_PV(hDbgCfg);
+
+ /*
+ * If no mnemonics, just return the hex value.
+ */
+ if (!paMnemonics || paMnemonics[0].pszMnemonic)
+ {
+ char szTmp[64];
+ size_t cch = RTStrPrintf(szTmp, sizeof(szTmp), "%#x", uValue);
+ if (cch + 1 > cbValue)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pszValue, szTmp, cbValue);
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Check that there is sufficient buffer space first.
+ */
+ size_t cbReq = 1;
+ for (unsigned i = 0; paMnemonics[i].pszMnemonic; i++)
+ if ( paMnemonics[i].fSet
+ ? (paMnemonics[i].fFlags & uValue)
+ : !(paMnemonics[i].fFlags & uValue))
+ cbReq += (cbReq != 1) + paMnemonics[i].cchMnemonic;
+ if (cbReq > cbValue)
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Construct the string.
+ */
+ char *psz = pszValue;
+ for (unsigned i = 0; paMnemonics[i].pszMnemonic; i++)
+ if ( paMnemonics[i].fSet
+ ? (paMnemonics[i].fFlags & uValue)
+ : !(paMnemonics[i].fFlags & uValue))
+ {
+ if (psz != pszValue)
+ *psz++ = ' ';
+ memcpy(psz, paMnemonics[i].pszMnemonic, paMnemonics[i].cchMnemonic);
+ psz += paMnemonics[i].cchMnemonic;
+ }
+ *psz = '\0';
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTDbgCfgQueryString(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, char *pszValue, size_t cbValue)
+{
+ PRTDBGCFGINT pThis = hDbgCfg;
+ RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmProp)
+ {
+ case RTDBGCFGPROP_FLAGS:
+ rc = rtDbgCfgQueryStringU64(pThis, pThis->fFlags, g_aDbgCfgFlags, pszValue, cbValue);
+ break;
+ case RTDBGCFGPROP_PATH:
+ rc = rtDbgCfgQueryStringList(pThis, &pThis->PathList, pszValue, cbValue);
+ break;
+ case RTDBGCFGPROP_SUFFIXES:
+ rc = rtDbgCfgQueryStringList(pThis, &pThis->SuffixList, pszValue, cbValue);
+ break;
+ case RTDBGCFGPROP_SRC_PATH:
+ rc = rtDbgCfgQueryStringList(pThis, &pThis->SrcPathList, pszValue, cbValue);
+ break;
+ default:
+ AssertFailed();
+ rc = VERR_INTERNAL_ERROR_3;
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSect);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTDbgCfgQueryUInt(RTDBGCFG hDbgCfg, RTDBGCFGPROP enmProp, uint64_t *puValue)
+{
+ PRTDBGCFGINT pThis = hDbgCfg;
+ RTDBGCFG_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(enmProp > RTDBGCFGPROP_INVALID && enmProp < RTDBGCFGPROP_END, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(puValue, VERR_INVALID_POINTER);
+
+ int rc = RTCritSectRwEnterShared(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmProp)
+ {
+ case RTDBGCFGPROP_FLAGS:
+ *puValue = pThis->fFlags;
+ break;
+ default:
+ rc = VERR_DBG_CFG_NOT_UINT_PROP;
+ }
+
+ RTCritSectRwLeaveShared(&pThis->CritSect);
+ }
+
+ return rc;
+}
+
+RTDECL(uint32_t) RTDbgCfgRetain(RTDBGCFG hDbgCfg)
+{
+ PRTDBGCFGINT pThis = hDbgCfg;
+ RTDBGCFG_VALID_RETURN_RC(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs < UINT32_MAX / 2);
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTDbgCfgRelease(RTDBGCFG hDbgCfg)
+{
+ if (hDbgCfg == NIL_RTDBGCFG)
+ return 0;
+
+ PRTDBGCFGINT pThis = hDbgCfg;
+ RTDBGCFG_VALID_RETURN_RC(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ if (!cRefs)
+ {
+ /*
+ * Last reference - free all memory.
+ */
+ ASMAtomicWriteU32(&pThis->u32Magic, ~RTDBGCFG_MAGIC);
+ rtDbgCfgFreeStrList(&pThis->PathList);
+ rtDbgCfgFreeStrList(&pThis->SuffixList);
+ rtDbgCfgFreeStrList(&pThis->SrcPathList);
+#ifdef RT_OS_WINDOWS
+ rtDbgCfgFreeStrList(&pThis->NtSymbolPathList);
+ rtDbgCfgFreeStrList(&pThis->NtExecutablePathList);
+ rtDbgCfgFreeStrList(&pThis->NtSourcePath);
+#endif
+ RTCritSectRwDelete(&pThis->CritSect);
+ RTMemFree(pThis);
+ }
+ else
+ Assert(cRefs < UINT32_MAX / 2);
+ return cRefs;
+}
+
+
+RTDECL(int) RTDbgCfgCreate(PRTDBGCFG phDbgCfg, const char *pszEnvVarPrefix, bool fNativePaths)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(phDbgCfg, VERR_INVALID_POINTER);
+ if (pszEnvVarPrefix)
+ {
+ AssertPtrReturn(pszEnvVarPrefix, VERR_INVALID_POINTER);
+ AssertReturn(*pszEnvVarPrefix, VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Allocate and initialize a new instance.
+ */
+ PRTDBGCFGINT pThis = (PRTDBGCFGINT)RTMemAllocZ(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ pThis->u32Magic = RTDBGCFG_MAGIC;
+ pThis->cRefs = 1;
+ RTListInit(&pThis->PathList);
+ RTListInit(&pThis->SuffixList);
+ RTListInit(&pThis->SrcPathList);
+#ifdef RT_OS_WINDOWS
+ RTListInit(&pThis->NtSymbolPathList);
+ RTListInit(&pThis->NtExecutablePathList);
+ RTListInit(&pThis->NtSourcePath);
+#endif
+
+ int rc = RTCritSectRwInit(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ /*
+ * Read configurtion from the environment if requested to do so.
+ */
+ if (pszEnvVarPrefix || fNativePaths)
+ {
+ const size_t cbEnvVar = 256;
+ const size_t cbEnvVal = 65536 - cbEnvVar;
+ char *pszEnvVar = (char *)RTMemTmpAlloc(cbEnvVar + cbEnvVal);
+ if (pszEnvVar)
+ {
+ char *pszEnvVal = pszEnvVar + cbEnvVar;
+
+ if (pszEnvVarPrefix)
+ {
+ static struct
+ {
+ RTDBGCFGPROP enmProp;
+ const char *pszVar;
+ } const s_aProps[] =
+ {
+ { RTDBGCFGPROP_FLAGS, "FLAGS" },
+ { RTDBGCFGPROP_PATH, "PATH" },
+ { RTDBGCFGPROP_SUFFIXES, "SUFFIXES" },
+ { RTDBGCFGPROP_SRC_PATH, "SRC_PATH" },
+ };
+
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aProps); i++)
+ {
+ size_t cchEnvVar = RTStrPrintf(pszEnvVar, cbEnvVar, "%s_%s", pszEnvVarPrefix, s_aProps[i].pszVar);
+ if (cchEnvVar >= cbEnvVar - 1)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ rc = RTEnvGetEx(RTENV_DEFAULT, pszEnvVar, pszEnvVal, cbEnvVal, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDbgCfgChangeString(pThis, s_aProps[i].enmProp, RTDBGCFGOP_SET, pszEnvVal);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (rc != VERR_ENV_VAR_NOT_FOUND)
+ break;
+ else
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Pick up system specific search paths.
+ */
+ if (RT_SUCCESS(rc) && fNativePaths)
+ {
+ struct
+ {
+ PRTLISTANCHOR pList;
+ const char *pszVar;
+ char chSep;
+ } aNativePaths[] =
+ {
+#ifdef RT_OS_WINDOWS
+ { &pThis->NtExecutablePathList, "_NT_EXECUTABLE_PATH", ';' },
+ { &pThis->NtSymbolPathList, "_NT_ALT_SYMBOL_PATH", ';' },
+ { &pThis->NtSymbolPathList, "_NT_SYMBOL_PATH", ';' },
+ { &pThis->NtSourcePath, "_NT_SOURCE_PATH", ';' },
+#endif
+ { NULL, NULL, 0 }
+ };
+ for (unsigned i = 0; aNativePaths[i].pList; i++)
+ {
+ Assert(aNativePaths[i].chSep == ';'); /* fix when needed */
+ rc = RTEnvGetEx(RTENV_DEFAULT, aNativePaths[i].pszVar, pszEnvVal, cbEnvVal, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtDbgCfgChangeStringList(pThis, RTDBGCFGOP_APPEND, pszEnvVal, true, aNativePaths[i].pList);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (rc != VERR_ENV_VAR_NOT_FOUND)
+ break;
+ else
+ rc = VINF_SUCCESS;
+ }
+ }
+ RTMemTmpFree(pszEnvVar);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * Error, bail out.
+ */
+ RTDbgCfgRelease(pThis);
+ return rc;
+ }
+ }
+
+ /*
+ * Returns successfully.
+ */
+ *phDbgCfg = pThis;
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmod.cpp b/src/VBox/Runtime/common/dbg/dbgmod.cpp
new file mode 100644
index 00000000..ac08f297
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmod.cpp
@@ -0,0 +1,2317 @@
+/* $Id: dbgmod.cpp $ */
+/** @file
+ * IPRT - Debug Module Interpreter.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DBG
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/strcache.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include "internal/dbgmod.h"
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Debug info interpreter registration record. */
+typedef struct RTDBGMODREGDBG
+{
+ /** Pointer to the next record. */
+ struct RTDBGMODREGDBG *pNext;
+ /** Pointer to the virtual function table for the interpreter. */
+ PCRTDBGMODVTDBG pVt;
+ /** Usage counter. */
+ uint32_t volatile cUsers;
+} RTDBGMODREGDBG;
+typedef RTDBGMODREGDBG *PRTDBGMODREGDBG;
+
+/** Image interpreter registration record. */
+typedef struct RTDBGMODREGIMG
+{
+ /** Pointer to the next record. */
+ struct RTDBGMODREGIMG *pNext;
+ /** Pointer to the virtual function table for the interpreter. */
+ PCRTDBGMODVTIMG pVt;
+ /** Usage counter. */
+ uint32_t volatile cUsers;
+} RTDBGMODREGIMG;
+typedef RTDBGMODREGIMG *PRTDBGMODREGIMG;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Validates a debug module handle and returns rc if not valid. */
+#define RTDBGMOD_VALID_RETURN_RC(pDbgMod, rc) \
+ do { \
+ AssertPtrReturn((pDbgMod), (rc)); \
+ AssertReturn((pDbgMod)->u32Magic == RTDBGMOD_MAGIC, (rc)); \
+ AssertReturn((pDbgMod)->cRefs > 0, (rc)); \
+ } while (0)
+
+/** Locks the debug module. */
+#define RTDBGMOD_LOCK(pDbgMod) \
+ do { \
+ int rcLock = RTCritSectEnter(&(pDbgMod)->CritSect); \
+ AssertRC(rcLock); \
+ } while (0)
+
+/** Unlocks the debug module. */
+#define RTDBGMOD_UNLOCK(pDbgMod) \
+ do { \
+ int rcLock = RTCritSectLeave(&(pDbgMod)->CritSect); \
+ AssertRC(rcLock); \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Init once object for lazy registration of the built-in image and debug
+ * info interpreters. */
+static RTONCE g_rtDbgModOnce = RTONCE_INITIALIZER;
+/** Read/Write semaphore protecting the list of registered interpreters. */
+static RTSEMRW g_hDbgModRWSem = NIL_RTSEMRW;
+/** List of registered image interpreters. */
+static PRTDBGMODREGIMG g_pImgHead;
+/** List of registered debug infor interpreters. */
+static PRTDBGMODREGDBG g_pDbgHead;
+/** String cache for the debug info interpreters.
+ * RTSTRCACHE is thread safe. */
+DECL_HIDDEN_DATA(RTSTRCACHE) g_hDbgModStrCache = NIL_RTSTRCACHE;
+
+
+
+
+
+/**
+ * Cleanup debug info interpreter globals.
+ *
+ * @param enmReason The cause of the termination.
+ * @param iStatus The meaning of this depends on enmReason.
+ * @param pvUser User argument, unused.
+ */
+static DECLCALLBACK(void) rtDbgModTermCallback(RTTERMREASON enmReason, int32_t iStatus, void *pvUser)
+{
+ NOREF(iStatus); NOREF(pvUser);
+ if (enmReason == RTTERMREASON_UNLOAD)
+ {
+ RTSemRWDestroy(g_hDbgModRWSem);
+ g_hDbgModRWSem = NIL_RTSEMRW;
+
+ RTStrCacheDestroy(g_hDbgModStrCache);
+ g_hDbgModStrCache = NIL_RTSTRCACHE;
+
+ PRTDBGMODREGDBG pDbg = g_pDbgHead;
+ g_pDbgHead = NULL;
+ while (pDbg)
+ {
+ PRTDBGMODREGDBG pNext = pDbg->pNext;
+ AssertMsg(pDbg->cUsers == 0, ("%#x %s\n", pDbg->cUsers, pDbg->pVt->pszName));
+ RTMemFree(pDbg);
+ pDbg = pNext;
+ }
+
+ PRTDBGMODREGIMG pImg = g_pImgHead;
+ g_pImgHead = NULL;
+ while (pImg)
+ {
+ PRTDBGMODREGIMG pNext = pImg->pNext;
+ AssertMsg(pImg->cUsers == 0, ("%#x %s\n", pImg->cUsers, pImg->pVt->pszName));
+ RTMemFree(pImg);
+ pImg = pNext;
+ }
+ }
+}
+
+
+/**
+ * Internal worker for register a debug interpreter.
+ *
+ * Called while owning the write lock or when locking isn't required.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_NO_MEMORY
+ * @retval VERR_ALREADY_EXISTS
+ *
+ * @param pVt The virtual function table of the debug
+ * module interpreter.
+ */
+static int rtDbgModDebugInterpreterRegister(PCRTDBGMODVTDBG pVt)
+{
+ /*
+ * Search or duplicate registration.
+ */
+ PRTDBGMODREGDBG pPrev = NULL;
+ for (PRTDBGMODREGDBG pCur = g_pDbgHead; pCur; pCur = pCur->pNext)
+ {
+ if (pCur->pVt == pVt)
+ return VERR_ALREADY_EXISTS;
+ if (!strcmp(pCur->pVt->pszName, pVt->pszName))
+ return VERR_ALREADY_EXISTS;
+ pPrev = pCur;
+ }
+
+ /*
+ * Create a new record and add it to the end of the list.
+ */
+ PRTDBGMODREGDBG pReg = (PRTDBGMODREGDBG)RTMemAlloc(sizeof(*pReg));
+ if (!pReg)
+ return VERR_NO_MEMORY;
+ pReg->pVt = pVt;
+ pReg->cUsers = 0;
+ pReg->pNext = NULL;
+ if (pPrev)
+ pPrev->pNext = pReg;
+ else
+ g_pDbgHead = pReg;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Internal worker for register a image interpreter.
+ *
+ * Called while owning the write lock or when locking isn't required.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_NO_MEMORY
+ * @retval VERR_ALREADY_EXISTS
+ *
+ * @param pVt The virtual function table of the image
+ * interpreter.
+ */
+static int rtDbgModImageInterpreterRegister(PCRTDBGMODVTIMG pVt)
+{
+ /*
+ * Search or duplicate registration.
+ */
+ PRTDBGMODREGIMG pPrev = NULL;
+ for (PRTDBGMODREGIMG pCur = g_pImgHead; pCur; pCur = pCur->pNext)
+ {
+ if (pCur->pVt == pVt)
+ return VERR_ALREADY_EXISTS;
+ if (!strcmp(pCur->pVt->pszName, pVt->pszName))
+ return VERR_ALREADY_EXISTS;
+ pPrev = pCur;
+ }
+
+ /*
+ * Create a new record and add it to the end of the list.
+ */
+ PRTDBGMODREGIMG pReg = (PRTDBGMODREGIMG)RTMemAlloc(sizeof(*pReg));
+ if (!pReg)
+ return VERR_NO_MEMORY;
+ pReg->pVt = pVt;
+ pReg->cUsers = 0;
+ pReg->pNext = NULL;
+ if (pPrev)
+ pPrev->pNext = pReg;
+ else
+ g_pImgHead = pReg;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Do-once callback that initializes the read/write semaphore and registers
+ * the built-in interpreters.
+ *
+ * @returns IPRT status code.
+ * @param pvUser NULL.
+ */
+static DECLCALLBACK(int) rtDbgModInitOnce(void *pvUser)
+{
+ NOREF(pvUser);
+
+ /*
+ * Create the semaphore and string cache.
+ */
+ int rc = RTSemRWCreate(&g_hDbgModRWSem);
+ AssertRCReturn(rc, rc);
+
+ rc = RTStrCacheCreate(&g_hDbgModStrCache, "RTDBGMOD");
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Register the interpreters.
+ */
+ rc = rtDbgModDebugInterpreterRegister(&g_rtDbgModVtDbgNm);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModDebugInterpreterRegister(&g_rtDbgModVtDbgMapSym);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModDebugInterpreterRegister(&g_rtDbgModVtDbgDwarf);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModDebugInterpreterRegister(&g_rtDbgModVtDbgCodeView);
+#ifdef IPRT_WITH_GHIDRA_DBG_MOD
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModDebugInterpreterRegister(&g_rtDbgModVtDbgGhidra);
+#endif
+#ifdef RT_OS_WINDOWS
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModDebugInterpreterRegister(&g_rtDbgModVtDbgDbgHelp);
+#endif
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModImageInterpreterRegister(&g_rtDbgModVtImgLdr);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Finally, register the IPRT cleanup callback.
+ */
+ rc = RTTermRegisterCallback(rtDbgModTermCallback, NULL);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ /* bail out: use the termination callback. */
+ }
+ }
+ else
+ g_hDbgModStrCache = NIL_RTSTRCACHE;
+ rtDbgModTermCallback(RTTERMREASON_UNLOAD, 0, NULL);
+ return rc;
+}
+
+
+/**
+ * Performs lazy init of our global variables.
+ * @returns IPRT status code.
+ */
+DECLINLINE(int) rtDbgModLazyInit(void)
+{
+ return RTOnce(&g_rtDbgModOnce, rtDbgModInitOnce, NULL);
+}
+
+
+RTDECL(int) RTDbgModCreate(PRTDBGMOD phDbgMod, const char *pszName, RTUINTPTR cbSeg, uint32_t fFlags)
+{
+ /*
+ * Input validation and lazy initialization.
+ */
+ AssertPtrReturn(phDbgMod, VERR_INVALID_POINTER);
+ *phDbgMod = NIL_RTDBGMOD;
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertReturn(*pszName, VERR_INVALID_PARAMETER);
+ AssertReturn(fFlags == 0 || fFlags == RTDBGMOD_F_NOT_DEFERRED, VERR_INVALID_FLAGS);
+
+ int rc = rtDbgModLazyInit();
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Allocate a new module instance.
+ */
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)RTMemAllocZ(sizeof(*pDbgMod));
+ if (!pDbgMod)
+ return VERR_NO_MEMORY;
+ pDbgMod->u32Magic = RTDBGMOD_MAGIC;
+ pDbgMod->cRefs = 1;
+ rc = RTCritSectInit(&pDbgMod->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pDbgMod->pszImgFileSpecified = RTStrCacheEnter(g_hDbgModStrCache, pszName);
+ pDbgMod->pszName = RTStrCacheEnterLower(g_hDbgModStrCache, RTPathFilenameEx(pszName, RTPATH_STR_F_STYLE_DOS));
+ if (pDbgMod->pszName)
+ {
+ rc = rtDbgModContainerCreate(pDbgMod, cbSeg);
+ if (RT_SUCCESS(rc))
+ {
+ *phDbgMod = pDbgMod;
+ return rc;
+ }
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFile);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszName);
+ }
+ RTCritSectDelete(&pDbgMod->CritSect);
+ }
+
+ RTMemFree(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModCreate);
+
+
+RTDECL(int) RTDbgModCreateFromMap(PRTDBGMOD phDbgMod, const char *pszFilename, const char *pszName,
+ RTUINTPTR uSubtrahend, RTDBGCFG hDbgCfg)
+{
+ RT_NOREF_PV(hDbgCfg);
+
+ /*
+ * Input validation and lazy initialization.
+ */
+ AssertPtrReturn(phDbgMod, VERR_INVALID_POINTER);
+ *phDbgMod = NIL_RTDBGMOD;
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pszName, VERR_INVALID_POINTER);
+ AssertReturn(uSubtrahend == 0, VERR_NOT_IMPLEMENTED); /** @todo implement uSubtrahend. */
+
+ int rc = rtDbgModLazyInit();
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (!pszName)
+ pszName = RTPathFilenameEx(pszFilename, RTPATH_STR_F_STYLE_DOS);
+
+ /*
+ * Allocate a new module instance.
+ */
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)RTMemAllocZ(sizeof(*pDbgMod));
+ if (!pDbgMod)
+ return VERR_NO_MEMORY;
+ pDbgMod->u32Magic = RTDBGMOD_MAGIC;
+ pDbgMod->cRefs = 1;
+ rc = RTCritSectInit(&pDbgMod->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pDbgMod->pszName = RTStrCacheEnterLower(g_hDbgModStrCache, pszName);
+ if (pDbgMod->pszName)
+ {
+ pDbgMod->pszDbgFile = RTStrCacheEnter(g_hDbgModStrCache, pszFilename);
+ if (pDbgMod->pszDbgFile)
+ {
+ /*
+ * Try the map file readers.
+ */
+ rc = RTSemRWRequestRead(g_hDbgModRWSem, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ for (PRTDBGMODREGDBG pCur = g_pDbgHead; pCur; pCur = pCur->pNext)
+ {
+ if (pCur->pVt->fSupports & RT_DBGTYPE_MAP)
+ {
+ pDbgMod->pDbgVt = pCur->pVt;
+ pDbgMod->pvDbgPriv = NULL;
+ rc = pCur->pVt->pfnTryOpen(pDbgMod, RTLDRARCH_WHATEVER);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicIncU32(&pCur->cUsers);
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+
+ *phDbgMod = pDbgMod;
+ return rc;
+ }
+ }
+ }
+
+ /* bail out */
+ rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ }
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszName);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszDbgFile);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTCritSectDelete(&pDbgMod->CritSect);
+ }
+
+ RTMemFree(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModCreateFromMap);
+
+
+
+/*
+ *
+ * E x e c u t a b l e I m a g e F i l e s
+ * E x e c u t a b l e I m a g e F i l e s
+ * E x e c u t a b l e I m a g e F i l e s
+ *
+ */
+
+
+/**
+ * Opens debug information for an image.
+ *
+ * @returns IPRT status code
+ * @param pDbgMod The debug module structure.
+ *
+ * @note This will generally not look for debug info stored in external
+ * files. rtDbgModFromPeImageExtDbgInfoCallback can help with that.
+ */
+static int rtDbgModOpenDebugInfoInsideImage(PRTDBGMODINT pDbgMod)
+{
+ AssertReturn(!pDbgMod->pDbgVt, VERR_DBG_MOD_IPE);
+ AssertReturn(pDbgMod->pImgVt, VERR_DBG_MOD_IPE);
+
+ int rc = RTSemRWRequestRead(g_hDbgModRWSem, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ for (PRTDBGMODREGDBG pDbg = g_pDbgHead; pDbg; pDbg = pDbg->pNext)
+ {
+ pDbgMod->pDbgVt = pDbg->pVt;
+ pDbgMod->pvDbgPriv = NULL;
+ rc = pDbg->pVt->pfnTryOpen(pDbgMod, pDbgMod->pImgVt->pfnGetArch(pDbgMod));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * That's it!
+ */
+ ASMAtomicIncU32(&pDbg->cUsers);
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ return VINF_SUCCESS;
+ }
+
+ pDbgMod->pDbgVt = NULL;
+ Assert(pDbgMod->pvDbgPriv == NULL);
+ }
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ }
+
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+}
+
+
+/** @callback_method_impl{FNRTDBGCFGOPEN} */
+static DECLCALLBACK(int) rtDbgModExtDbgInfoOpenCallback(RTDBGCFG hDbgCfg, const char *pszFilename, void *pvUser1, void *pvUser2)
+{
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)pvUser1;
+ PCRTLDRDBGINFO pDbgInfo = (PCRTLDRDBGINFO)pvUser2;
+ RT_NOREF_PV(pDbgInfo); /** @todo consider a more direct search for a interpreter. */
+ RT_NOREF_PV(hDbgCfg);
+
+ Assert(!pDbgMod->pDbgVt);
+ Assert(!pDbgMod->pvDbgPriv);
+ Assert(!pDbgMod->pszDbgFile);
+ Assert(pDbgMod->pImgVt);
+
+ /*
+ * Set the debug file name and try possible interpreters.
+ */
+ pDbgMod->pszDbgFile = RTStrCacheEnter(g_hDbgModStrCache, pszFilename);
+
+ int rc = RTSemRWRequestRead(g_hDbgModRWSem, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ for (PRTDBGMODREGDBG pDbg = g_pDbgHead; pDbg; pDbg = pDbg->pNext)
+ {
+ pDbgMod->pDbgVt = pDbg->pVt;
+ pDbgMod->pvDbgPriv = NULL;
+ rc = pDbg->pVt->pfnTryOpen(pDbgMod, pDbgMod->pImgVt->pfnGetArch(pDbgMod));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Got it!
+ */
+ ASMAtomicIncU32(&pDbg->cUsers);
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ return VINF_CALLBACK_RETURN;
+ }
+
+ pDbgMod->pDbgVt = NULL;
+ Assert(pDbgMod->pvDbgPriv == NULL);
+ }
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ }
+
+ /* No joy. */
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszDbgFile);
+ pDbgMod->pszDbgFile = NULL;
+ return rc;
+}
+
+
+/**
+ * Argument package used by rtDbgModOpenDebugInfoExternalToImage.
+ */
+typedef struct RTDBGMODOPENDIETI
+{
+ PRTDBGMODINT pDbgMod;
+ RTDBGCFG hDbgCfg;
+} RTDBGMODOPENDIETI;
+
+
+/** @callback_method_impl{FNRTLDRENUMDBG} */
+static DECLCALLBACK(int)
+rtDbgModOpenDebugInfoExternalToImageCallback(RTLDRMOD hLdrMod, PCRTLDRDBGINFO pDbgInfo, void *pvUser)
+{
+ RTDBGMODOPENDIETI *pArgs = (RTDBGMODOPENDIETI *)pvUser;
+ RT_NOREF_PV(hLdrMod);
+
+ Assert(pDbgInfo->enmType > RTLDRDBGINFOTYPE_INVALID && pDbgInfo->enmType < RTLDRDBGINFOTYPE_END);
+ const char *pszExtFile = pDbgInfo->pszExtFile;
+ if (!pszExtFile)
+ {
+ /*
+ * If a external debug type comes without a file name, calculate a
+ * likely debug filename for it. (Hack for NT4 drivers.)
+ */
+ const char *pszExt = NULL;
+ if (pDbgInfo->enmType == RTLDRDBGINFOTYPE_CODEVIEW_DBG)
+ pszExt = ".dbg";
+ else if ( pDbgInfo->enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB20
+ || pDbgInfo->enmType == RTLDRDBGINFOTYPE_CODEVIEW_PDB70)
+ pszExt = ".pdb";
+ if (pszExt && pArgs->pDbgMod->pszName)
+ {
+ size_t cchName = strlen(pArgs->pDbgMod->pszName);
+ char *psz = (char *)alloca(cchName + strlen(pszExt) + 1);
+ if (psz)
+ {
+ memcpy(psz, pArgs->pDbgMod->pszName, cchName + 1);
+ RTPathStripSuffix(psz);
+ pszExtFile = strcat(psz, pszExt);
+ }
+ }
+
+ if (!pszExtFile)
+ {
+ Log2(("rtDbgModOpenDebugInfoExternalToImageCallback: enmType=%d\n", pDbgInfo->enmType));
+ return VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Switch on type and call the appropriate search function.
+ */
+ int rc;
+ switch (pDbgInfo->enmType)
+ {
+ case RTLDRDBGINFOTYPE_CODEVIEW_PDB70:
+ rc = RTDbgCfgOpenPdb70(pArgs->hDbgCfg, pszExtFile,
+ &pDbgInfo->u.Pdb70.Uuid,
+ pDbgInfo->u.Pdb70.uAge,
+ rtDbgModExtDbgInfoOpenCallback, pArgs->pDbgMod, (void *)pDbgInfo);
+ break;
+
+ case RTLDRDBGINFOTYPE_CODEVIEW_PDB20:
+ rc = RTDbgCfgOpenPdb20(pArgs->hDbgCfg, pszExtFile,
+ pDbgInfo->u.Pdb20.cbImage,
+ pDbgInfo->u.Pdb20.uTimestamp,
+ pDbgInfo->u.Pdb20.uAge,
+ rtDbgModExtDbgInfoOpenCallback, pArgs->pDbgMod, (void *)pDbgInfo);
+ break;
+
+ case RTLDRDBGINFOTYPE_CODEVIEW_DBG:
+ rc = RTDbgCfgOpenDbg(pArgs->hDbgCfg, pszExtFile,
+ pDbgInfo->u.Dbg.cbImage,
+ pDbgInfo->u.Dbg.uTimestamp,
+ rtDbgModExtDbgInfoOpenCallback, pArgs->pDbgMod, (void *)pDbgInfo);
+ break;
+
+ case RTLDRDBGINFOTYPE_DWARF_DWO:
+ rc = RTDbgCfgOpenDwo(pArgs->hDbgCfg, pszExtFile,
+ pDbgInfo->u.Dwo.uCrc32,
+ rtDbgModExtDbgInfoOpenCallback, pArgs->pDbgMod, (void *)pDbgInfo);
+ break;
+
+ default:
+ Log(("rtDbgModOpenDebugInfoExternalToImageCallback: Don't know how to handle enmType=%d and pszFileExt=%s\n",
+ pDbgInfo->enmType, pszExtFile));
+ return VERR_DBG_TODO;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("RTDbgMod: Successfully opened external debug info '%s' for '%s'\n",
+ pArgs->pDbgMod->pszDbgFile, pArgs->pDbgMod->pszImgFile));
+ return VINF_CALLBACK_RETURN;
+ }
+ Log(("rtDbgModOpenDebugInfoExternalToImageCallback: '%s' (enmType=%d) for '%s' -> %Rrc\n",
+ pszExtFile, pDbgInfo->enmType, pArgs->pDbgMod->pszImgFile, rc));
+ return rc;
+}
+
+
+/**
+ * Opens debug info listed in the image that is stored in a separate file.
+ *
+ * @returns IPRT status code
+ * @param pDbgMod The debug module.
+ * @param hDbgCfg The debug config. Can be NIL.
+ */
+static int rtDbgModOpenDebugInfoExternalToImage(PRTDBGMODINT pDbgMod, RTDBGCFG hDbgCfg)
+{
+ Assert(!pDbgMod->pDbgVt);
+
+ RTDBGMODOPENDIETI Args;
+ Args.pDbgMod = pDbgMod;
+ Args.hDbgCfg = hDbgCfg;
+ int rc = pDbgMod->pImgVt->pfnEnumDbgInfo(pDbgMod, rtDbgModOpenDebugInfoExternalToImageCallback, &Args);
+ if (RT_SUCCESS(rc) && pDbgMod->pDbgVt)
+ return VINF_SUCCESS;
+
+ LogFlow(("rtDbgModOpenDebugInfoExternalToImage: rc=%Rrc\n", rc));
+ return VERR_NOT_FOUND;
+}
+
+
+/** @callback_method_impl{FNRTDBGCFGOPEN} */
+static DECLCALLBACK(int) rtDbgModExtDbgInfoOpenCallback2(RTDBGCFG hDbgCfg, const char *pszFilename, void *pvUser1, void *pvUser2)
+{
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)pvUser1;
+ RT_NOREF_PV(pvUser2); /** @todo image matching string or smth. */
+ RT_NOREF_PV(hDbgCfg);
+
+ Assert(!pDbgMod->pDbgVt);
+ Assert(!pDbgMod->pvDbgPriv);
+ Assert(!pDbgMod->pszDbgFile);
+ Assert(pDbgMod->pImgVt);
+
+ /*
+ * Set the debug file name and try possible interpreters.
+ */
+ pDbgMod->pszDbgFile = RTStrCacheEnter(g_hDbgModStrCache, pszFilename);
+
+ int rc = RTSemRWRequestRead(g_hDbgModRWSem, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ for (PRTDBGMODREGDBG pDbg = g_pDbgHead; pDbg; pDbg = pDbg->pNext)
+ {
+ pDbgMod->pDbgVt = pDbg->pVt;
+ pDbgMod->pvDbgPriv = NULL;
+ rc = pDbg->pVt->pfnTryOpen(pDbgMod, pDbgMod->pImgVt->pfnGetArch(pDbgMod));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Got it!
+ */
+ ASMAtomicIncU32(&pDbg->cUsers);
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ return VINF_CALLBACK_RETURN;
+ }
+ pDbgMod->pDbgVt = NULL;
+ Assert(pDbgMod->pvDbgPriv == NULL);
+ }
+ }
+
+ /* No joy. */
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszDbgFile);
+ pDbgMod->pszDbgFile = NULL;
+ return rc;
+}
+
+
+/**
+ * Opens external debug info that is not listed in the image.
+ *
+ * @returns IPRT status code
+ * @param pDbgMod The debug module.
+ * @param hDbgCfg The debug config. Can be NIL.
+ */
+static int rtDbgModOpenDebugInfoExternalToImage2(PRTDBGMODINT pDbgMod, RTDBGCFG hDbgCfg)
+{
+ int rc;
+ Assert(!pDbgMod->pDbgVt);
+ Assert(pDbgMod->pImgVt);
+
+ /*
+ * Figure out what to search for based on the image format.
+ */
+ const char *pszzExts = NULL;
+ RTLDRFMT enmFmt = pDbgMod->pImgVt->pfnGetFormat(pDbgMod);
+ switch (enmFmt)
+ {
+ case RTLDRFMT_MACHO:
+ {
+ RTUUID Uuid;
+ PRTUUID pUuid = &Uuid;
+ rc = pDbgMod->pImgVt->pfnQueryProp(pDbgMod, RTLDRPROP_UUID, &Uuid, sizeof(Uuid), NULL);
+ if (RT_FAILURE(rc))
+ pUuid = NULL;
+
+ rc = RTDbgCfgOpenDsymBundle(hDbgCfg, pDbgMod->pszImgFile, pUuid,
+ rtDbgModExtDbgInfoOpenCallback2, pDbgMod, NULL /*pvUser2*/);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ break;
+ }
+
+#if 0 /* Will be links in the image if these apply. .map readers for PE or ELF we don't have. */
+ case RTLDRFMT_ELF:
+ pszzExts = ".debug\0.dwo\0";
+ break;
+ case RTLDRFMT_PE:
+ pszzExts = ".map\0";
+ break;
+#endif
+#if 0 /* Haven't implemented .sym or .map file readers for OS/2 yet. */
+ case RTLDRFMT_LX:
+ pszzExts = ".sym\0.map\0";
+ break;
+#endif
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+
+ NOREF(pszzExts);
+#if 0 /* Later */
+ if (pszzExts)
+ {
+
+ }
+#endif
+
+ LogFlow(("rtDbgModOpenDebugInfoExternalToImage2: rc=%Rrc\n", rc));
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTDbgModCreateFromImage(PRTDBGMOD phDbgMod, const char *pszFilename, const char *pszName,
+ RTLDRARCH enmArch, RTDBGCFG hDbgCfg)
+{
+ /*
+ * Input validation and lazy initialization.
+ */
+ AssertPtrReturn(phDbgMod, VERR_INVALID_POINTER);
+ *phDbgMod = NIL_RTDBGMOD;
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pszName, VERR_INVALID_POINTER);
+ AssertReturn(enmArch > RTLDRARCH_INVALID && enmArch < RTLDRARCH_END, VERR_INVALID_PARAMETER);
+
+ int rc = rtDbgModLazyInit();
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (!pszName)
+ pszName = RTPathFilenameEx(pszFilename, RTPATH_STR_F_STYLE_DOS);
+
+ /*
+ * Allocate a new module instance.
+ */
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)RTMemAllocZ(sizeof(*pDbgMod));
+ if (!pDbgMod)
+ return VERR_NO_MEMORY;
+ pDbgMod->u32Magic = RTDBGMOD_MAGIC;
+ pDbgMod->cRefs = 1;
+ rc = RTCritSectInit(&pDbgMod->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pDbgMod->pszName = RTStrCacheEnterLower(g_hDbgModStrCache, pszName);
+ if (pDbgMod->pszName)
+ {
+ pDbgMod->pszImgFile = RTStrCacheEnter(g_hDbgModStrCache, pszFilename);
+ if (pDbgMod->pszImgFile)
+ {
+ RTStrCacheRetain(pDbgMod->pszImgFile);
+ pDbgMod->pszImgFileSpecified = pDbgMod->pszImgFile;
+
+ /*
+ * Find an image reader which groks the file.
+ */
+ rc = RTSemRWRequestRead(g_hDbgModRWSem, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ PRTDBGMODREGIMG pImg;
+ for (pImg = g_pImgHead; pImg; pImg = pImg->pNext)
+ {
+ pDbgMod->pImgVt = pImg->pVt;
+ pDbgMod->pvImgPriv = NULL;
+ /** @todo need to specify some arch stuff here. */
+ rc = pImg->pVt->pfnTryOpen(pDbgMod, enmArch, 0 /*fLdrFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Image detected, but found no debug info we were
+ * able to understand.
+ */
+ /** @todo some generic way of matching image and debug info, flexible signature
+ * of some kind. Apple uses UUIDs, microsoft uses a UUID+age or a
+ * size+timestamp, and GNU a CRC32 (last time I checked). */
+ rc = rtDbgModOpenDebugInfoExternalToImage(pDbgMod, hDbgCfg);
+ if (RT_FAILURE(rc))
+ rc = rtDbgModOpenDebugInfoInsideImage(pDbgMod);
+ if (RT_FAILURE(rc))
+ rc = rtDbgModOpenDebugInfoExternalToImage2(pDbgMod, hDbgCfg);
+ if (RT_FAILURE(rc))
+ rc = rtDbgModCreateForExports(pDbgMod);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * We're done!
+ */
+ ASMAtomicIncU32(&pImg->cUsers);
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+
+ *phDbgMod = pDbgMod;
+ return VINF_SUCCESS;
+ }
+
+ /* Failed, close up the shop. */
+ pDbgMod->pImgVt->pfnClose(pDbgMod);
+ pDbgMod->pImgVt = NULL;
+ pDbgMod->pvImgPriv = NULL;
+ break;
+ }
+ }
+
+ /*
+ * Could it be a file containing raw debug info?
+ */
+ if (!pImg)
+ {
+ pDbgMod->pImgVt = NULL;
+ pDbgMod->pvImgPriv = NULL;
+ pDbgMod->pszDbgFile = pDbgMod->pszImgFile;
+ pDbgMod->pszImgFile = NULL;
+
+ for (PRTDBGMODREGDBG pDbg = g_pDbgHead; pDbg; pDbg = pDbg->pNext)
+ {
+ pDbgMod->pDbgVt = pDbg->pVt;
+ pDbgMod->pvDbgPriv = NULL;
+ rc = pDbg->pVt->pfnTryOpen(pDbgMod, enmArch);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * That's it!
+ */
+ ASMAtomicIncU32(&pDbg->cUsers);
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+
+ *phDbgMod = pDbgMod;
+ return rc;
+ }
+ }
+
+ pDbgMod->pszImgFile = pDbgMod->pszDbgFile;
+ pDbgMod->pszDbgFile = NULL;
+ }
+
+ /* bail out */
+ rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ }
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFileSpecified);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFile);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszName);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTCritSectDelete(&pDbgMod->CritSect);
+ }
+
+ RTMemFree(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModCreateFromImage);
+
+
+
+
+
+/*
+ *
+ * P E I M A G E
+ * P E I M A G E
+ * P E I M A G E
+ *
+ */
+
+
+
+/** @callback_method_impl{FNRTDBGCFGOPEN} */
+static DECLCALLBACK(int) rtDbgModFromPeImageOpenCallback(RTDBGCFG hDbgCfg, const char *pszFilename, void *pvUser1, void *pvUser2)
+{
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)pvUser1;
+ PRTDBGMODDEFERRED pDeferred = (PRTDBGMODDEFERRED)pvUser2;
+ LogFlow(("rtDbgModFromPeImageOpenCallback: %s\n", pszFilename));
+ RT_NOREF_PV(hDbgCfg);
+
+ Assert(pDbgMod->pImgVt == NULL);
+ Assert(pDbgMod->pvImgPriv == NULL);
+ Assert(pDbgMod->pDbgVt == NULL);
+ Assert(pDbgMod->pvDbgPriv == NULL);
+
+ /*
+ * Replace the image file name while probing it.
+ */
+ const char *pszNewImgFile = RTStrCacheEnter(g_hDbgModStrCache, pszFilename);
+ if (!pszNewImgFile)
+ return VERR_NO_STR_MEMORY;
+ const char *pszOldImgFile = pDbgMod->pszImgFile;
+ pDbgMod->pszImgFile = pszNewImgFile;
+
+ /*
+ * Find an image reader which groks the file.
+ */
+ int rc = RTSemRWRequestRead(g_hDbgModRWSem, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ PRTDBGMODREGIMG pImg;
+ for (pImg = g_pImgHead; pImg; pImg = pImg->pNext)
+ {
+ pDbgMod->pImgVt = pImg->pVt;
+ pDbgMod->pvImgPriv = NULL;
+ int rc2 = pImg->pVt->pfnTryOpen(pDbgMod, RTLDRARCH_WHATEVER, 0 /*fLdrFlags*/);
+ if (RT_SUCCESS(rc2))
+ {
+ rc = rc2;
+ break;
+ }
+ pDbgMod->pImgVt = NULL;
+ Assert(pDbgMod->pvImgPriv == NULL);
+ }
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check the deferred info.
+ */
+ RTUINTPTR cbImage = pDbgMod->pImgVt->pfnImageSize(pDbgMod);
+ if ( pDeferred->cbImage == 0
+ || pDeferred->cbImage == cbImage)
+ {
+ uint32_t uTimestamp = pDeferred->u.PeImage.uTimestamp; /** @todo add method for getting the timestamp. */
+ if ( pDeferred->u.PeImage.uTimestamp == 0
+ || pDeferred->u.PeImage.uTimestamp == uTimestamp)
+ {
+ Log(("RTDbgMod: Found matching PE image '%s'\n", pszFilename));
+
+ /*
+ * We found the executable image we need, now go find any
+ * debug info associated with it. For PE images, this is
+ * generally found in an external file, so we do a sweep
+ * for that first.
+ *
+ * Then try open debug inside the module, and finally
+ * falling back on exports.
+ */
+ rc = rtDbgModOpenDebugInfoExternalToImage(pDbgMod, pDeferred->hDbgCfg);
+ if (RT_FAILURE(rc))
+ rc = rtDbgModOpenDebugInfoInsideImage(pDbgMod);
+ if (RT_FAILURE(rc))
+ rc = rtDbgModCreateForExports(pDbgMod);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrCacheRelease(g_hDbgModStrCache, pszOldImgFile);
+ return VINF_CALLBACK_RETURN;
+ }
+
+ /* Something bad happened, just give up. */
+ Log(("rtDbgModFromPeImageOpenCallback: rtDbgModCreateForExports failed: %Rrc\n", rc));
+ }
+ else
+ {
+ LogFlow(("rtDbgModFromPeImageOpenCallback: uTimestamp mismatch (found %#x, expected %#x) - %s\n",
+ uTimestamp, pDeferred->u.PeImage.uTimestamp, pszFilename));
+ rc = VERR_DBG_FILE_MISMATCH;
+ }
+ }
+ else
+ {
+ LogFlow(("rtDbgModFromPeImageOpenCallback: cbImage mismatch (found %#x, expected %#x) - %s\n",
+ cbImage, pDeferred->cbImage, pszFilename));
+ rc = VERR_DBG_FILE_MISMATCH;
+ }
+
+ pDbgMod->pImgVt->pfnClose(pDbgMod);
+ pDbgMod->pImgVt = NULL;
+ pDbgMod->pvImgPriv = NULL;
+ }
+ else
+ LogFlow(("rtDbgModFromPeImageOpenCallback: Failed %Rrc - %s\n", rc, pszFilename));
+ }
+
+ /* Restore image name. */
+ pDbgMod->pszImgFile = pszOldImgFile;
+ RTStrCacheRelease(g_hDbgModStrCache, pszNewImgFile);
+ return rc;
+}
+
+
+/** @callback_method_impl{FNRTDBGMODDEFERRED} */
+static DECLCALLBACK(int) rtDbgModFromPeImageDeferredCallback(PRTDBGMODINT pDbgMod, PRTDBGMODDEFERRED pDeferred)
+{
+ int rc;
+
+ Assert(pDbgMod->pszImgFile);
+ if (!pDbgMod->pImgVt)
+ rc = RTDbgCfgOpenPeImage(pDeferred->hDbgCfg, pDbgMod->pszImgFile,
+ pDeferred->cbImage, pDeferred->u.PeImage.uTimestamp,
+ rtDbgModFromPeImageOpenCallback, pDbgMod, pDeferred);
+ else
+ {
+ rc = rtDbgModOpenDebugInfoExternalToImage(pDbgMod, pDeferred->hDbgCfg);
+ if (RT_FAILURE(rc))
+ rc = rtDbgModOpenDebugInfoInsideImage(pDbgMod);
+ if (RT_FAILURE(rc))
+ rc = rtDbgModCreateForExports(pDbgMod);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTDbgModCreateFromPeImage(PRTDBGMOD phDbgMod, const char *pszFilename, const char *pszName,
+ PRTLDRMOD phLdrMod, uint32_t cbImage, uint32_t uTimestamp, RTDBGCFG hDbgCfg)
+{
+ /*
+ * Input validation and lazy initialization.
+ */
+ AssertPtrReturn(phDbgMod, VERR_INVALID_POINTER);
+ *phDbgMod = NIL_RTDBGMOD;
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename, VERR_INVALID_PARAMETER);
+ if (!pszName)
+ pszName = RTPathFilenameEx(pszFilename, RTPATH_STR_F_STYLE_DOS);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(phLdrMod, VERR_INVALID_POINTER);
+ RTLDRMOD hLdrMod = phLdrMod ? *phLdrMod : NIL_RTLDRMOD;
+ AssertReturn(hLdrMod == NIL_RTLDRMOD || RTLdrSize(hLdrMod) != ~(size_t)0, VERR_INVALID_HANDLE);
+
+ int rc = rtDbgModLazyInit();
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint64_t fDbgCfg = 0;
+ if (hDbgCfg)
+ {
+ rc = RTDbgCfgQueryUInt(hDbgCfg, RTDBGCFGPROP_FLAGS, &fDbgCfg);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Allocate a new module instance.
+ */
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)RTMemAllocZ(sizeof(*pDbgMod));
+ if (!pDbgMod)
+ return VERR_NO_MEMORY;
+ pDbgMod->u32Magic = RTDBGMOD_MAGIC;
+ pDbgMod->cRefs = 1;
+ rc = RTCritSectInit(&pDbgMod->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pDbgMod->pszName = RTStrCacheEnterLower(g_hDbgModStrCache, pszName);
+ if (pDbgMod->pszName)
+ {
+ pDbgMod->pszImgFile = RTStrCacheEnter(g_hDbgModStrCache, pszFilename);
+ if (pDbgMod->pszImgFile)
+ {
+ RTStrCacheRetain(pDbgMod->pszImgFile);
+ pDbgMod->pszImgFileSpecified = pDbgMod->pszImgFile;
+
+ /*
+ * If we have a loader module, we must instantiate the loader
+ * side of things regardless of the deferred setting.
+ */
+ if (hLdrMod != NIL_RTLDRMOD)
+ {
+ if (!cbImage)
+ cbImage = (uint32_t)RTLdrSize(hLdrMod);
+ pDbgMod->pImgVt = &g_rtDbgModVtImgLdr;
+
+ rc = rtDbgModLdrOpenFromHandle(pDbgMod, hLdrMod);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* We now own the loader handle, so clear the caller variable. */
+ if (phLdrMod)
+ *phLdrMod = NIL_RTLDRMOD;
+
+ /*
+ * Do it now or procrastinate?
+ */
+ if (!(fDbgCfg & RTDBGCFG_FLAGS_DEFERRED) || !cbImage)
+ {
+ RTDBGMODDEFERRED Deferred;
+ Deferred.cbImage = cbImage;
+ Deferred.hDbgCfg = hDbgCfg;
+ Deferred.u.PeImage.uTimestamp = uTimestamp;
+ rc = rtDbgModFromPeImageDeferredCallback(pDbgMod, &Deferred);
+ }
+ else
+ {
+ PRTDBGMODDEFERRED pDeferred;
+ rc = rtDbgModDeferredCreate(pDbgMod, rtDbgModFromPeImageDeferredCallback, cbImage, hDbgCfg,
+ 0 /*cbDeferred*/, 0 /*fFlags*/, &pDeferred);
+ if (RT_SUCCESS(rc))
+ pDeferred->u.PeImage.uTimestamp = uTimestamp;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *phDbgMod = pDbgMod;
+ return VINF_SUCCESS;
+ }
+
+ /* Failed, bail out. */
+ if (hLdrMod != NIL_RTLDRMOD)
+ {
+ Assert(pDbgMod->pImgVt);
+ pDbgMod->pImgVt->pfnClose(pDbgMod);
+ }
+ }
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszName);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFileSpecified);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFile);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTCritSectDelete(&pDbgMod->CritSect);
+ }
+
+ RTMemFree(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModCreateFromPeImage);
+
+
+
+
+/*
+ *
+ * M a c h - O I M A G E
+ * M a c h - O I M A G E
+ * M a c h - O I M A G E
+ *
+ */
+
+
+/**
+ * Argument package used when opening Mach-O images and .dSYMs files.
+ */
+typedef struct RTDBGMODMACHOARGS
+{
+ /** For use more internal use in file locator callbacks. */
+ RTLDRARCH enmArch;
+ /** For use more internal use in file locator callbacks. */
+ PCRTUUID pUuid;
+ /** For use more internal use in file locator callbacks. */
+ bool fOpenImage;
+ /** RTDBGMOD_F_XXX. */
+ uint32_t fFlags;
+} RTDBGMODMACHOARGS;
+/** Pointer to a const segment package. */
+typedef RTDBGMODMACHOARGS const *PCRTDBGMODMACHOARGS;
+
+
+
+/** @callback_method_impl{FNRTDBGCFGOPEN} */
+static DECLCALLBACK(int)
+rtDbgModFromMachOImageOpenDsymMachOCallback(RTDBGCFG hDbgCfg, const char *pszFilename, void *pvUser1, void *pvUser2)
+{
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)pvUser1;
+ PCRTDBGMODMACHOARGS pArgs = (PCRTDBGMODMACHOARGS)pvUser2;
+ RT_NOREF_PV(hDbgCfg);
+
+ Assert(!pDbgMod->pDbgVt);
+ Assert(!pDbgMod->pvDbgPriv);
+ Assert(!pDbgMod->pszDbgFile);
+ Assert(!pDbgMod->pImgVt);
+ Assert(!pDbgMod->pvDbgPriv);
+ Assert(pDbgMod->pszImgFile);
+ Assert(pDbgMod->pszImgFileSpecified);
+
+ const char *pszImgFileOrg = pDbgMod->pszImgFile;
+ pDbgMod->pszImgFile = RTStrCacheEnter(g_hDbgModStrCache, pszFilename);
+ if (!pDbgMod->pszImgFile)
+ return VERR_NO_STR_MEMORY;
+ RTStrCacheRetain(pDbgMod->pszImgFile);
+ pDbgMod->pszDbgFile = pDbgMod->pszImgFile;
+
+ /*
+ * Try image interpreters as the dwarf file inside the dSYM bundle is a
+ * Mach-O file with dwarf debug sections insides it and no code or data.
+ */
+ int rc = RTSemRWRequestRead(g_hDbgModRWSem, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ PRTDBGMODREGIMG pImg;
+ for (pImg = g_pImgHead; pImg; pImg = pImg->pNext)
+ {
+ pDbgMod->pImgVt = pImg->pVt;
+ pDbgMod->pvImgPriv = NULL;
+ int rc2 = pImg->pVt->pfnTryOpen(pDbgMod, pArgs->enmArch,
+ pArgs->fFlags & RTDBGMOD_F_MACHO_LOAD_LINKEDIT ? RTLDR_O_MACHO_LOAD_LINKEDIT : 0);
+ if (RT_SUCCESS(rc2))
+ {
+ rc = rc2;
+ break;
+ }
+ pDbgMod->pImgVt = NULL;
+ Assert(pDbgMod->pvImgPriv == NULL);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check the UUID if one was given.
+ */
+ if (pArgs->pUuid)
+ {
+ RTUUID UuidOpened;
+ rc = pDbgMod->pImgVt->pfnQueryProp(pDbgMod, RTLDRPROP_UUID, &UuidOpened, sizeof(UuidOpened), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTUuidCompare(&UuidOpened, pArgs->pUuid) != 0)
+ rc = VERR_DBG_FILE_MISMATCH;
+ }
+ else if (rc == VERR_NOT_FOUND || rc == VERR_NOT_IMPLEMENTED)
+ rc = VERR_DBG_FILE_MISMATCH;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Pass it to the DWARF reader(s). Careful to restrict this or
+ * the dbghelp wrapper may end up being overly helpful.
+ */
+ for (PRTDBGMODREGDBG pDbg = g_pDbgHead; pDbg; pDbg = pDbg->pNext)
+ {
+ if (pDbg->pVt->fSupports & (RT_DBGTYPE_DWARF | RT_DBGTYPE_STABS | RT_DBGTYPE_WATCOM))
+
+ {
+ pDbgMod->pDbgVt = pDbg->pVt;
+ pDbgMod->pvDbgPriv = NULL;
+ rc = pDbg->pVt->pfnTryOpen(pDbgMod, pDbgMod->pImgVt->pfnGetArch(pDbgMod));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Got it!
+ */
+ ASMAtomicIncU32(&pDbg->cUsers);
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ RTStrCacheRelease(g_hDbgModStrCache, pszImgFileOrg);
+ return VINF_CALLBACK_RETURN;
+ }
+ pDbgMod->pDbgVt = NULL;
+ Assert(pDbgMod->pvDbgPriv == NULL);
+ }
+ }
+
+ /*
+ * Likely fallback for when opening image.
+ */
+ if (pArgs->fOpenImage)
+ {
+ rc = rtDbgModCreateForExports(pDbgMod);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Done.
+ */
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ RTStrCacheRelease(g_hDbgModStrCache, pszImgFileOrg);
+ return VINF_CALLBACK_RETURN;
+ }
+ }
+ }
+
+ pDbgMod->pImgVt->pfnClose(pDbgMod);
+ pDbgMod->pImgVt = NULL;
+ pDbgMod->pvImgPriv = NULL;
+ }
+ }
+
+ /* No joy. */
+ RTSemRWReleaseRead(g_hDbgModRWSem);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFile);
+ pDbgMod->pszImgFile = pszImgFileOrg;
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszDbgFile);
+ pDbgMod->pszDbgFile = NULL;
+ return rc;
+}
+
+
+static int rtDbgModFromMachOImageWorker(PRTDBGMODINT pDbgMod, RTLDRARCH enmArch, uint32_t cbImage,
+ uint32_t cSegs, PCRTDBGSEGMENT paSegs, PCRTUUID pUuid, RTDBGCFG hDbgCfg, uint32_t fFlags)
+{
+ RT_NOREF_PV(cbImage); RT_NOREF_PV(cSegs); RT_NOREF_PV(paSegs);
+
+ RTDBGMODMACHOARGS Args;
+ Args.enmArch = enmArch;
+ Args.pUuid = pUuid && RTUuidIsNull(pUuid) ? pUuid : NULL;
+ Args.fOpenImage = false;
+ Args.fFlags = fFlags;
+
+ /*
+ * Search for the .dSYM bundle first, since that's generally all we need.
+ */
+ int rc = RTDbgCfgOpenDsymBundle(hDbgCfg, pDbgMod->pszImgFile, pUuid,
+ rtDbgModFromMachOImageOpenDsymMachOCallback, pDbgMod, &Args);
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * If we cannot get at the .dSYM, try the executable image.
+ */
+ Args.fOpenImage = true;
+ rc = RTDbgCfgOpenMachOImage(hDbgCfg, pDbgMod->pszImgFile, pUuid,
+ rtDbgModFromMachOImageOpenDsymMachOCallback, pDbgMod, &Args);
+ }
+ return rc;
+}
+
+
+/** @callback_method_impl{FNRTDBGMODDEFERRED} */
+static DECLCALLBACK(int) rtDbgModFromMachOImageDeferredCallback(PRTDBGMODINT pDbgMod, PRTDBGMODDEFERRED pDeferred)
+{
+ return rtDbgModFromMachOImageWorker(pDbgMod, pDeferred->u.MachO.enmArch, pDeferred->cbImage,
+ pDeferred->u.MachO.cSegs, pDeferred->u.MachO.aSegs,
+ &pDeferred->u.MachO.Uuid, pDeferred->hDbgCfg, pDeferred->fFlags);
+}
+
+
+RTDECL(int) RTDbgModCreateFromMachOImage(PRTDBGMOD phDbgMod, const char *pszFilename, const char *pszName, RTLDRARCH enmArch,
+ PRTLDRMOD phLdrModIn, uint32_t cbImage, uint32_t cSegs, PCRTDBGSEGMENT paSegs,
+ PCRTUUID pUuid, RTDBGCFG hDbgCfg, uint32_t fFlags)
+{
+ /*
+ * Input validation and lazy initialization.
+ */
+ AssertPtrReturn(phDbgMod, VERR_INVALID_POINTER);
+ *phDbgMod = NIL_RTDBGMOD;
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(*pszFilename, VERR_INVALID_PARAMETER);
+ if (!pszName)
+ pszName = RTPathFilenameEx(pszFilename, RTPATH_STR_F_STYLE_HOST);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ if (cSegs)
+ {
+ AssertReturn(cSegs < 1024, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(paSegs, VERR_INVALID_POINTER);
+ AssertReturn(!cbImage, VERR_INVALID_PARAMETER);
+ }
+ AssertPtrNullReturn(pUuid, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTDBGMOD_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ AssertPtrNullReturn(phLdrModIn, VERR_INVALID_POINTER);
+ RTLDRMOD hLdrModIn = phLdrModIn ? *phLdrModIn : NIL_RTLDRMOD;
+ AssertReturn(hLdrModIn == NIL_RTLDRMOD || RTLdrSize(hLdrModIn) != ~(size_t)0, VERR_INVALID_HANDLE);
+
+ AssertReturn(cbImage || cSegs || hLdrModIn != NIL_RTLDRMOD, VERR_INVALID_PARAMETER);
+
+ int rc = rtDbgModLazyInit();
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint64_t fDbgCfg = 0;
+ if (hDbgCfg)
+ {
+ rc = RTDbgCfgQueryUInt(hDbgCfg, RTDBGCFGPROP_FLAGS, &fDbgCfg);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * If we got no UUID but the caller passed in a module handle, try
+ * query the UUID from it.
+ */
+ RTUUID UuidFromImage = RTUUID_INITIALIZE_NULL;
+ if ((!pUuid || RTUuidIsNull(pUuid)) && hLdrModIn != NIL_RTLDRMOD)
+ {
+ rc = RTLdrQueryProp(hLdrModIn, RTLDRPROP_UUID, &UuidFromImage, sizeof(UuidFromImage));
+ if (RT_SUCCESS(rc))
+ pUuid = &UuidFromImage;
+ }
+
+ /*
+ * Allocate a new module instance.
+ */
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)RTMemAllocZ(sizeof(*pDbgMod));
+ if (!pDbgMod)
+ return VERR_NO_MEMORY;
+ pDbgMod->u32Magic = RTDBGMOD_MAGIC;
+ pDbgMod->cRefs = 1;
+ rc = RTCritSectInit(&pDbgMod->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pDbgMod->pszName = RTStrCacheEnterLower(g_hDbgModStrCache, pszName);
+ if (pDbgMod->pszName)
+ {
+ pDbgMod->pszImgFile = RTStrCacheEnter(g_hDbgModStrCache, pszFilename);
+ if (pDbgMod->pszImgFile)
+ {
+ RTStrCacheRetain(pDbgMod->pszImgFile);
+ pDbgMod->pszImgFileSpecified = pDbgMod->pszImgFile;
+
+ /*
+ * Load it immediately?
+ */
+ if ( !(fDbgCfg & RTDBGCFG_FLAGS_DEFERRED)
+ || cSegs /* for the time being. */
+ || (!cbImage && !cSegs)
+ || (fFlags & RTDBGMOD_F_NOT_DEFERRED)
+ || hLdrModIn != NIL_RTLDRMOD)
+ {
+ rc = rtDbgModFromMachOImageWorker(pDbgMod, enmArch, cbImage, cSegs, paSegs, pUuid, hDbgCfg, fFlags);
+ if (RT_FAILURE(rc) && hLdrModIn != NIL_RTLDRMOD)
+ {
+ /*
+ * Create module based on exports from hLdrModIn.
+ */
+ if (!cbImage)
+ cbImage = (uint32_t)RTLdrSize(hLdrModIn);
+ pDbgMod->pImgVt = &g_rtDbgModVtImgLdr;
+
+ rc = rtDbgModLdrOpenFromHandle(pDbgMod, hLdrModIn);
+ if (RT_SUCCESS(rc))
+ {
+ /* We now own the loader handle, so clear the caller variable. */
+ if (phLdrModIn)
+ *phLdrModIn = NIL_RTLDRMOD;
+
+ /** @todo delayed exports stuff */
+ rc = rtDbgModCreateForExports(pDbgMod);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Procrastinate. Need image size atm.
+ */
+ PRTDBGMODDEFERRED pDeferred;
+ rc = rtDbgModDeferredCreate(pDbgMod, rtDbgModFromMachOImageDeferredCallback, cbImage, hDbgCfg,
+ RT_UOFFSETOF_DYN(RTDBGMODDEFERRED, u.MachO.aSegs[cSegs]),
+ 0 /*fFlags*/, &pDeferred);
+ if (RT_SUCCESS(rc))
+ {
+ pDeferred->u.MachO.Uuid = *pUuid;
+ pDeferred->u.MachO.enmArch = enmArch;
+ pDeferred->u.MachO.cSegs = cSegs;
+ if (cSegs)
+ memcpy(&pDeferred->u.MachO.aSegs, paSegs, cSegs * sizeof(paSegs[0]));
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *phDbgMod = pDbgMod;
+ return VINF_SUCCESS;
+ }
+
+ /* Failed, bail out. */
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszName);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFileSpecified);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFile);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTCritSectDelete(&pDbgMod->CritSect);
+ }
+
+ RTMemFree(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModCreateFromMachOImage);
+
+
+
+/**
+ * Destroys an module after the reference count has reached zero.
+ *
+ * @param pDbgMod The module instance.
+ */
+static void rtDbgModDestroy(PRTDBGMODINT pDbgMod)
+{
+ /*
+ * Close the debug info interpreter first, then the image interpret.
+ */
+ RTCritSectEnter(&pDbgMod->CritSect); /* paranoia */
+
+ if (pDbgMod->pDbgVt)
+ {
+ pDbgMod->pDbgVt->pfnClose(pDbgMod);
+ pDbgMod->pDbgVt = NULL;
+ pDbgMod->pvDbgPriv = NULL;
+ }
+
+ if (pDbgMod->pImgVt)
+ {
+ pDbgMod->pImgVt->pfnClose(pDbgMod);
+ pDbgMod->pImgVt = NULL;
+ pDbgMod->pvImgPriv = NULL;
+ }
+
+ /*
+ * Free the resources.
+ */
+ ASMAtomicWriteU32(&pDbgMod->u32Magic, ~RTDBGMOD_MAGIC);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszName);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFile);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszImgFileSpecified);
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgMod->pszDbgFile);
+ RTCritSectLeave(&pDbgMod->CritSect); /* paranoia */
+ RTCritSectDelete(&pDbgMod->CritSect);
+ RTMemFree(pDbgMod);
+}
+
+
+RTDECL(uint32_t) RTDbgModRetain(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, UINT32_MAX);
+ return ASMAtomicIncU32(&pDbgMod->cRefs);
+}
+RT_EXPORT_SYMBOL(RTDbgModRetain);
+
+
+RTDECL(uint32_t) RTDbgModRelease(RTDBGMOD hDbgMod)
+{
+ if (hDbgMod == NIL_RTDBGMOD)
+ return 0;
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pDbgMod->cRefs);
+ if (!cRefs)
+ rtDbgModDestroy(pDbgMod);
+ return cRefs;
+}
+RT_EXPORT_SYMBOL(RTDbgModRelease);
+
+
+RTDECL(const char *) RTDbgModName(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, NULL);
+ return pDbgMod->pszName;
+}
+RT_EXPORT_SYMBOL(RTDbgModName);
+
+
+RTDECL(const char *) RTDbgModDebugFile(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, NULL);
+ if (pDbgMod->fDeferred || pDbgMod->fExports)
+ return NULL;
+ return pDbgMod->pszDbgFile;
+}
+RT_EXPORT_SYMBOL(RTDbgModDebugFile);
+
+
+RTDECL(const char *) RTDbgModImageFile(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, NULL);
+ return pDbgMod->pszImgFileSpecified;
+}
+RT_EXPORT_SYMBOL(RTDbgModImageFile);
+
+
+RTDECL(const char *) RTDbgModImageFileUsed(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, NULL);
+ return pDbgMod->pszImgFile == pDbgMod->pszImgFileSpecified ? NULL : pDbgMod->pszImgFile;
+}
+RT_EXPORT_SYMBOL(RTDbgModImageFileUsed);
+
+
+RTDECL(bool) RTDbgModIsDeferred(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, false);
+ return pDbgMod->fDeferred;
+}
+
+
+RTDECL(bool) RTDbgModIsExports(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, false);
+ return pDbgMod->fExports;
+}
+
+
+RTDECL(int) RTDbgModRemoveAll(RTDBGMOD hDbgMod, bool fLeaveSegments)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+
+ RTDBGMOD_LOCK(pDbgMod);
+
+ /* Only possible on container modules. */
+ int rc = VINF_SUCCESS;
+ if (pDbgMod->pDbgVt != &g_rtDbgModVtDbgContainer)
+ {
+ if (fLeaveSegments)
+ {
+ rc = rtDbgModContainer_LineRemoveAll(pDbgMod);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModContainer_SymbolRemoveAll(pDbgMod);
+ }
+ else
+ rc = rtDbgModContainer_RemoveAll(pDbgMod);
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+}
+
+
+RTDECL(RTDBGSEGIDX) RTDbgModRvaToSegOff(RTDBGMOD hDbgMod, RTUINTPTR uRva, PRTUINTPTR poffSeg)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, NIL_RTDBGSEGIDX);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ RTDBGSEGIDX iSeg = pDbgMod->pDbgVt->pfnRvaToSegOff(pDbgMod, uRva, poffSeg);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return iSeg;
+}
+RT_EXPORT_SYMBOL(RTDbgModRvaToSegOff);
+
+
+RTDECL(uint64_t) RTDbgModGetTag(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, 0);
+ return pDbgMod->uTag;
+}
+RT_EXPORT_SYMBOL(RTDbgModGetTag);
+
+
+RTDECL(int) RTDbgModSetTag(RTDBGMOD hDbgMod, uint64_t uTag)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ pDbgMod->uTag = uTag;
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTDbgModSetTag);
+
+
+RTDECL(RTUINTPTR) RTDbgModImageSize(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, RTUINTPTR_MAX);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ RTUINTPTR cbImage = pDbgMod->pDbgVt->pfnImageSize(pDbgMod);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return cbImage;
+}
+RT_EXPORT_SYMBOL(RTDbgModImageSize);
+
+
+RTDECL(RTLDRFMT) RTDbgModImageGetFormat(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, RTLDRFMT_INVALID);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ RTLDRFMT enmFmt;
+ if ( pDbgMod->pImgVt
+ && pDbgMod->pImgVt->pfnGetFormat)
+ enmFmt = pDbgMod->pImgVt->pfnGetFormat(pDbgMod);
+ else
+ enmFmt = RTLDRFMT_INVALID;
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return enmFmt;
+}
+RT_EXPORT_SYMBOL(RTDbgModImageGetFormat);
+
+
+RTDECL(RTLDRARCH) RTDbgModImageGetArch(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, RTLDRARCH_INVALID);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ RTLDRARCH enmArch;
+ if ( pDbgMod->pImgVt
+ && pDbgMod->pImgVt->pfnGetArch)
+ enmArch = pDbgMod->pImgVt->pfnGetArch(pDbgMod);
+ else
+ enmArch = RTLDRARCH_WHATEVER;
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return enmArch;
+}
+RT_EXPORT_SYMBOL(RTDbgModImageGetArch);
+
+
+RTDECL(int) RTDbgModImageQueryProp(RTDBGMOD hDbgMod, RTLDRPROP enmProp, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pcbRet, VERR_INVALID_POINTER);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ int rc;
+ if ( pDbgMod->pImgVt
+ && pDbgMod->pImgVt->pfnQueryProp)
+ rc = pDbgMod->pImgVt->pfnQueryProp(pDbgMod, enmProp, pvBuf, cbBuf, pcbRet);
+ else
+ rc = VERR_NOT_FOUND;
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModImageQueryProp);
+
+
+RTDECL(int) RTDbgModSegmentAdd(RTDBGMOD hDbgMod, RTUINTPTR uRva, RTUINTPTR cb, const char *pszName,
+ uint32_t fFlags, PRTDBGSEGIDX piSeg)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ AssertMsgReturn(uRva + cb >= uRva, ("uRva=%RTptr cb=%RTptr\n", uRva, cb), VERR_DBG_ADDRESS_WRAP);
+ Assert(*pszName);
+ size_t cchName = strlen(pszName);
+ AssertReturn(cchName > 0, VERR_DBG_SEGMENT_NAME_OUT_OF_RANGE);
+ AssertReturn(cchName < RTDBG_SEGMENT_NAME_LENGTH, VERR_DBG_SEGMENT_NAME_OUT_OF_RANGE);
+ AssertMsgReturn(!fFlags, ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
+ AssertPtrNull(piSeg);
+ AssertMsgReturn(!piSeg || *piSeg == NIL_RTDBGSEGIDX || *piSeg <= RTDBGSEGIDX_LAST, ("%#x\n", *piSeg), VERR_DBG_SPECIAL_SEGMENT);
+
+ /*
+ * Do the deed.
+ */
+ RTDBGMOD_LOCK(pDbgMod);
+ int rc = pDbgMod->pDbgVt->pfnSegmentAdd(pDbgMod, uRva, cb, pszName, cchName, fFlags, piSeg);
+ RTDBGMOD_UNLOCK(pDbgMod);
+
+ return rc;
+
+}
+RT_EXPORT_SYMBOL(RTDbgModSegmentAdd);
+
+
+RTDECL(RTDBGSEGIDX) RTDbgModSegmentCount(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, NIL_RTDBGSEGIDX);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ RTDBGSEGIDX cSegs = pDbgMod->pDbgVt->pfnSegmentCount(pDbgMod);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return cSegs;
+}
+RT_EXPORT_SYMBOL(RTDbgModSegmentCount);
+
+
+RTDECL(int) RTDbgModSegmentByIndex(RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg, PRTDBGSEGMENT pSegInfo)
+{
+ AssertMsgReturn(iSeg <= RTDBGSEGIDX_LAST, ("%#x\n", iSeg), VERR_DBG_SPECIAL_SEGMENT);
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ int rc = pDbgMod->pDbgVt->pfnSegmentByIndex(pDbgMod, iSeg, pSegInfo);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModSegmentByIndex);
+
+
+RTDECL(RTUINTPTR) RTDbgModSegmentSize(RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg)
+{
+ if (iSeg == RTDBGSEGIDX_RVA)
+ return RTDbgModImageSize(hDbgMod);
+ RTDBGSEGMENT SegInfo;
+ int rc = RTDbgModSegmentByIndex(hDbgMod, iSeg, &SegInfo);
+ return RT_SUCCESS(rc) ? SegInfo.cb : RTUINTPTR_MAX;
+}
+RT_EXPORT_SYMBOL(RTDbgModSegmentSize);
+
+
+RTDECL(RTUINTPTR) RTDbgModSegmentRva(RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg)
+{
+ RTDBGSEGMENT SegInfo;
+ int rc = RTDbgModSegmentByIndex(hDbgMod, iSeg, &SegInfo);
+ return RT_SUCCESS(rc) ? SegInfo.uRva : RTUINTPTR_MAX;
+}
+RT_EXPORT_SYMBOL(RTDbgModSegmentRva);
+
+
+RTDECL(int) RTDbgModSymbolAdd(RTDBGMOD hDbgMod, const char *pszSymbol, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ RTUINTPTR cb, uint32_t fFlags, uint32_t *piOrdinal)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszSymbol, VERR_INVALID_POINTER);
+ size_t cchSymbol = strlen(pszSymbol);
+ AssertReturn(cchSymbol, VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE);
+ AssertReturn(cchSymbol < RTDBG_SYMBOL_NAME_LENGTH, VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE);
+ AssertMsgReturn( iSeg <= RTDBGSEGIDX_LAST
+ || ( iSeg >= RTDBGSEGIDX_SPECIAL_FIRST
+ && iSeg <= RTDBGSEGIDX_SPECIAL_LAST),
+ ("%#x\n", iSeg),
+ VERR_DBG_INVALID_SEGMENT_INDEX);
+ AssertMsgReturn(off + cb >= off, ("off=%RTptr cb=%RTptr\n", off, cb), VERR_DBG_ADDRESS_WRAP);
+ AssertReturn(!(fFlags & ~RTDBGSYMBOLADD_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ RTDBGMOD_LOCK(pDbgMod);
+
+ /*
+ * Convert RVAs.
+ */
+ if (iSeg == RTDBGSEGIDX_RVA)
+ {
+ iSeg = pDbgMod->pDbgVt->pfnRvaToSegOff(pDbgMod, off, &off);
+ if (iSeg == NIL_RTDBGSEGIDX)
+ {
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return VERR_DBG_INVALID_RVA;
+ }
+ }
+
+ /*
+ * Get down to business.
+ */
+ int rc = pDbgMod->pDbgVt->pfnSymbolAdd(pDbgMod, pszSymbol, cchSymbol, iSeg, off, cb, fFlags, piOrdinal);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModSymbolAdd);
+
+
+RTDECL(uint32_t) RTDbgModSymbolCount(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, UINT32_MAX);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ uint32_t cSymbols = pDbgMod->pDbgVt->pfnSymbolCount(pDbgMod);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return cSymbols;
+}
+RT_EXPORT_SYMBOL(RTDbgModSymbolCount);
+
+
+RTDECL(int) RTDbgModSymbolByOrdinal(RTDBGMOD hDbgMod, uint32_t iOrdinal, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ int rc = pDbgMod->pDbgVt->pfnSymbolByOrdinal(pDbgMod, iOrdinal, pSymInfo);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModSymbolByOrdinal);
+
+
+RTDECL(int) RTDbgModSymbolByOrdinalA(RTDBGMOD hDbgMod, uint32_t iOrdinal, PRTDBGSYMBOL *ppSymInfo)
+{
+ AssertPtr(ppSymInfo);
+ *ppSymInfo = NULL;
+
+ PRTDBGSYMBOL pSymInfo = RTDbgSymbolAlloc();
+ if (!pSymInfo)
+ return VERR_NO_MEMORY;
+
+ int rc = RTDbgModSymbolByOrdinal(hDbgMod, iOrdinal, pSymInfo);
+
+ if (RT_SUCCESS(rc))
+ *ppSymInfo = pSymInfo;
+ else
+ RTDbgSymbolFree(pSymInfo);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModSymbolByOrdinalA);
+
+
+/**
+ * Return a segment number/name as symbol if we couldn't find any
+ * valid symbols within the segment.
+ */
+DECL_NO_INLINE(static, int)
+rtDbgModSymbolByAddrTrySegments(PRTDBGMODINT pDbgMod, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ Assert(iSeg <= RTDBGSEGIDX_LAST);
+ RTDBGSEGMENT SegInfo;
+ int rc = pDbgMod->pDbgVt->pfnSegmentByIndex(pDbgMod, iSeg, &SegInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pSymInfo->Value = 0;
+ pSymInfo->cb = SegInfo.cb;
+ pSymInfo->offSeg = 0;
+ pSymInfo->iSeg = iSeg;
+ pSymInfo->fFlags = 0;
+ if (SegInfo.szName[0])
+ RTStrPrintf(pSymInfo->szName, sizeof(pSymInfo->szName), "start_seg%u_%s", SegInfo.iSeg, SegInfo.szName);
+ else
+ RTStrPrintf(pSymInfo->szName, sizeof(pSymInfo->szName), "start_seg%u", SegInfo.iSeg);
+ if (poffDisp)
+ *poffDisp = off;
+ return VINF_SUCCESS;
+ }
+ return VERR_SYMBOL_NOT_FOUND;
+}
+
+
+RTDECL(int) RTDbgModSymbolByAddr(RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ AssertPtrNull(poffDisp);
+ AssertPtr(pSymInfo);
+ AssertReturn(!(fFlags & ~RTDBGSYMADDR_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ RTDBGMOD_LOCK(pDbgMod);
+
+ /*
+ * Convert RVAs.
+ */
+ if (iSeg == RTDBGSEGIDX_RVA)
+ {
+ iSeg = pDbgMod->pDbgVt->pfnRvaToSegOff(pDbgMod, off, &off);
+ if (iSeg == NIL_RTDBGSEGIDX)
+ {
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return VERR_DBG_INVALID_RVA;
+ }
+ }
+
+ /*
+ * Get down to business.
+ */
+ int rc = pDbgMod->pDbgVt->pfnSymbolByAddr(pDbgMod, iSeg, off, fFlags, poffDisp, pSymInfo);
+
+ /* If we failed to locate a symbol, try use the specified segment as a reference. */
+ if ( rc == VERR_SYMBOL_NOT_FOUND
+ && iSeg <= RTDBGSEGIDX_LAST
+ && !(fFlags & RTDBGSYMADDR_FLAGS_GREATER_OR_EQUAL))
+ rc = rtDbgModSymbolByAddrTrySegments(pDbgMod, iSeg, off, poffDisp, pSymInfo);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModSymbolByAddr);
+
+
+RTDECL(int) RTDbgModSymbolByAddrA(RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL *ppSymInfo)
+{
+ AssertPtr(ppSymInfo);
+ *ppSymInfo = NULL;
+
+ PRTDBGSYMBOL pSymInfo = RTDbgSymbolAlloc();
+ if (!pSymInfo)
+ return VERR_NO_MEMORY;
+
+ int rc = RTDbgModSymbolByAddr(hDbgMod, iSeg, off, fFlags, poffDisp, pSymInfo);
+
+ if (RT_SUCCESS(rc))
+ *ppSymInfo = pSymInfo;
+ else
+ RTDbgSymbolFree(pSymInfo);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModSymbolByAddrA);
+
+
+RTDECL(int) RTDbgModSymbolByName(RTDBGMOD hDbgMod, const char *pszSymbol, PRTDBGSYMBOL pSymInfo)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ AssertPtr(pszSymbol);
+ size_t cchSymbol = strlen(pszSymbol);
+ AssertReturn(cchSymbol, VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE);
+ AssertReturn(cchSymbol < RTDBG_SYMBOL_NAME_LENGTH, VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE);
+ AssertPtr(pSymInfo);
+
+ /*
+ * Make the query.
+ */
+ RTDBGMOD_LOCK(pDbgMod);
+ int rc = pDbgMod->pDbgVt->pfnSymbolByName(pDbgMod, pszSymbol, cchSymbol, pSymInfo);
+ RTDBGMOD_UNLOCK(pDbgMod);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModSymbolByName);
+
+
+RTDECL(int) RTDbgModSymbolByNameA(RTDBGMOD hDbgMod, const char *pszSymbol, PRTDBGSYMBOL *ppSymInfo)
+{
+ AssertPtr(ppSymInfo);
+ *ppSymInfo = NULL;
+
+ PRTDBGSYMBOL pSymInfo = RTDbgSymbolAlloc();
+ if (!pSymInfo)
+ return VERR_NO_MEMORY;
+
+ int rc = RTDbgModSymbolByName(hDbgMod, pszSymbol, pSymInfo);
+
+ if (RT_SUCCESS(rc))
+ *ppSymInfo = pSymInfo;
+ else
+ RTDbgSymbolFree(pSymInfo);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModSymbolByNameA);
+
+
+RTDECL(int) RTDbgModLineAdd(RTDBGMOD hDbgMod, const char *pszFile, uint32_t uLineNo,
+ RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t *piOrdinal)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ AssertPtr(pszFile);
+ size_t cchFile = strlen(pszFile);
+ AssertReturn(cchFile, VERR_DBG_FILE_NAME_OUT_OF_RANGE);
+ AssertReturn(cchFile < RTDBG_FILE_NAME_LENGTH, VERR_DBG_FILE_NAME_OUT_OF_RANGE);
+ AssertMsgReturn( iSeg <= RTDBGSEGIDX_LAST
+ || iSeg == RTDBGSEGIDX_RVA,
+ ("%#x\n", iSeg),
+ VERR_DBG_INVALID_SEGMENT_INDEX);
+ AssertReturn(uLineNo > 0 && uLineNo < UINT32_MAX, VERR_INVALID_PARAMETER);
+
+ RTDBGMOD_LOCK(pDbgMod);
+
+ /*
+ * Convert RVAs.
+ */
+ if (iSeg == RTDBGSEGIDX_RVA)
+ {
+ iSeg = pDbgMod->pDbgVt->pfnRvaToSegOff(pDbgMod, off, &off);
+ if (iSeg == NIL_RTDBGSEGIDX)
+ {
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return VERR_DBG_INVALID_RVA;
+ }
+ }
+
+ /*
+ * Get down to business.
+ */
+ int rc = pDbgMod->pDbgVt->pfnLineAdd(pDbgMod, pszFile, cchFile, uLineNo, iSeg, off, piOrdinal);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModLineAdd);
+
+
+RTDECL(uint32_t) RTDbgModLineCount(RTDBGMOD hDbgMod)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, UINT32_MAX);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ uint32_t cLineNumbers = pDbgMod->pDbgVt->pfnLineCount(pDbgMod);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return cLineNumbers;
+}
+RT_EXPORT_SYMBOL(RTDbgModLineCount);
+
+
+RTDECL(int) RTDbgModLineByOrdinal(RTDBGMOD hDbgMod, uint32_t iOrdinal, PRTDBGLINE pLineInfo)
+{
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ RTDBGMOD_LOCK(pDbgMod);
+
+ int rc = pDbgMod->pDbgVt->pfnLineByOrdinal(pDbgMod, iOrdinal, pLineInfo);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModLineByOrdinal);
+
+
+RTDECL(int) RTDbgModLineByOrdinalA(RTDBGMOD hDbgMod, uint32_t iOrdinal, PRTDBGLINE *ppLineInfo)
+{
+ AssertPtr(ppLineInfo);
+ *ppLineInfo = NULL;
+
+ PRTDBGLINE pLineInfo = RTDbgLineAlloc();
+ if (!pLineInfo)
+ return VERR_NO_MEMORY;
+
+ int rc = RTDbgModLineByOrdinal(hDbgMod, iOrdinal, pLineInfo);
+
+ if (RT_SUCCESS(rc))
+ *ppLineInfo = pLineInfo;
+ else
+ RTDbgLineFree(pLineInfo);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModLineByOrdinalA);
+
+
+RTDECL(int) RTDbgModLineByAddr(RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTINTPTR poffDisp, PRTDBGLINE pLineInfo)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ AssertPtrNull(poffDisp);
+ AssertPtr(pLineInfo);
+
+ RTDBGMOD_LOCK(pDbgMod);
+
+ /*
+ * Convert RVAs.
+ */
+ if (iSeg == RTDBGSEGIDX_RVA)
+ {
+ iSeg = pDbgMod->pDbgVt->pfnRvaToSegOff(pDbgMod, off, &off);
+ if (iSeg == NIL_RTDBGSEGIDX)
+ {
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return VERR_DBG_INVALID_RVA;
+ }
+ }
+
+ int rc = pDbgMod->pDbgVt->pfnLineByAddr(pDbgMod, iSeg, off, poffDisp, pLineInfo);
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModLineByAddr);
+
+
+RTDECL(int) RTDbgModLineByAddrA(RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTINTPTR poffDisp, PRTDBGLINE *ppLineInfo)
+{
+ AssertPtr(ppLineInfo);
+ *ppLineInfo = NULL;
+
+ PRTDBGLINE pLineInfo = RTDbgLineAlloc();
+ if (!pLineInfo)
+ return VERR_NO_MEMORY;
+
+ int rc = RTDbgModLineByAddr(hDbgMod, iSeg, off, poffDisp, pLineInfo);
+
+ if (RT_SUCCESS(rc))
+ *ppLineInfo = pLineInfo;
+ else
+ RTDbgLineFree(pLineInfo);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTDbgModLineByAddrA);
+
+
+RTDECL(int) RTDbgModUnwindFrame(RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ /*
+ * Validate input.
+ */
+ PRTDBGMODINT pDbgMod = hDbgMod;
+ RTDBGMOD_VALID_RETURN_RC(pDbgMod, VERR_INVALID_HANDLE);
+ AssertPtr(pState);
+ AssertReturn(pState->u32Magic == RTDBGUNWINDSTATE_MAGIC, VERR_INVALID_MAGIC);
+
+ RTDBGMOD_LOCK(pDbgMod);
+
+ /*
+ * Convert RVAs.
+ */
+ if (iSeg == RTDBGSEGIDX_RVA)
+ {
+ iSeg = pDbgMod->pDbgVt->pfnRvaToSegOff(pDbgMod, off, &off);
+ if (iSeg == NIL_RTDBGSEGIDX)
+ {
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return VERR_DBG_INVALID_RVA;
+ }
+ }
+
+ /*
+ * Try the debug module first, then the image.
+ */
+ int rc = VERR_DBG_NO_UNWIND_INFO;
+ if (pDbgMod->pDbgVt->pfnUnwindFrame)
+ rc = pDbgMod->pDbgVt->pfnUnwindFrame(pDbgMod, iSeg, off, pState);
+ if ( ( rc == VERR_DBG_NO_UNWIND_INFO
+ || rc == VERR_DBG_UNWIND_INFO_NOT_FOUND)
+ && pDbgMod->pImgVt
+ && pDbgMod->pImgVt->pfnUnwindFrame)
+ {
+ if (rc == VERR_DBG_NO_UNWIND_INFO)
+ rc = pDbgMod->pImgVt->pfnUnwindFrame(pDbgMod, iSeg, off, pState);
+ else
+ {
+ rc = pDbgMod->pImgVt->pfnUnwindFrame(pDbgMod, iSeg, off, pState);
+ if (rc == VERR_DBG_NO_UNWIND_INFO)
+ rc = VERR_DBG_UNWIND_INFO_NOT_FOUND;
+ }
+ }
+
+ RTDBGMOD_UNLOCK(pDbgMod);
+ return rc;
+
+}
+RT_EXPORT_SYMBOL(RTDbgModUnwindFrame);
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmodcodeview.cpp b/src/VBox/Runtime/common/dbg/dbgmodcodeview.cpp
new file mode 100644
index 00000000..5ce270d0
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmodcodeview.cpp
@@ -0,0 +1,3197 @@
+/* $Id: dbgmodcodeview.cpp $ */
+/** @file
+ * IPRT - Debug Module Reader For Microsoft CodeView and COFF.
+ *
+ * Based on the following documentation (plus guess work and googling):
+ *
+ * - "Tools Interface Standard (TIS) Formats Specification for Windows",
+ * dated February 1993, version 1.0.
+ *
+ * - "Visual C++ 5.0 Symbolic Debug Information Specification" chapter of
+ * SPECS.CHM from MSDN Library October 2001.
+ *
+ * - "High Level Languages Debug Table Documentation", aka HLLDBG.HTML, aka
+ * IBMHLL.HTML, last changed 1996-07-08.
+ *
+ * Testcases using RTLdrFlt:
+ * - VBoxPcBios.sym at 0xf0000.
+ * - NT4 kernel PE image (coff syms).
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DBG
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/latin1.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/sort.h>
+#include <iprt/string.h>
+#include <iprt/strcache.h>
+#include "internal/dbgmod.h"
+#include "internal/magics.h"
+
+#include <iprt/formats/codeview.h>
+#include <iprt/formats/pecoff.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * File type.
+ */
+typedef enum RTCVFILETYPE
+{
+ RTCVFILETYPE_INVALID = 0,
+ /** Executable image. */
+ RTCVFILETYPE_IMAGE,
+ /** A DBG-file with a IMAGE_SEPARATE_DEBUG_HEADER. */
+ RTCVFILETYPE_DBG,
+ /** A PDB file. */
+ RTCVFILETYPE_PDB,
+ /** Some other kind of file with CV at the end. */
+ RTCVFILETYPE_OTHER_AT_END,
+ /** The end of the valid values. */
+ RTCVFILETYPE_END,
+ /** Type blowup. */
+ RTCVFILETYPE_32BIT_HACK = 0x7fffffff
+} RTCVFILETYPE;
+
+
+/**
+ * CodeView debug info reader instance.
+ */
+typedef struct RTDBGMODCV
+{
+ /** Using a container for managing the debug info. */
+ RTDBGMOD hCnt;
+
+ /** @name Codeview details
+ * @{ */
+ /** The code view magic (used as format indicator). */
+ uint32_t u32CvMagic;
+ /** The offset of the CV debug info in the file. */
+ uint32_t offBase;
+ /** The size of the CV debug info. */
+ uint32_t cbDbgInfo;
+ /** The offset of the subsection directory (relative to offBase). */
+ uint32_t offDir;
+ /** @} */
+
+ /** @name COFF details.
+ * @{ */
+ /** Offset of the COFF header. */
+ uint32_t offCoffDbgInfo;
+ /** The size of the COFF debug info. */
+ uint32_t cbCoffDbgInfo;
+ /** The COFF debug info header. */
+ IMAGE_COFF_SYMBOLS_HEADER CoffHdr;
+ /** @} */
+
+ /** The file type. */
+ RTCVFILETYPE enmType;
+ /** The file handle (if external). */
+ RTFILE hFile;
+ /** Pointer to the module (no reference retained). */
+ PRTDBGMODINT pMod;
+
+ /** The image size, if we know it. This is 0 if we don't know it. */
+ uint32_t cbImage;
+
+ /** Indicates that we've loaded segments intot he container already. */
+ bool fHaveLoadedSegments;
+ /** Alternative address translation method for DOS frames. */
+ bool fHaveDosFrames;
+
+ /** @name Codeview Parsing state.
+ * @{ */
+ /** Number of directory entries. */
+ uint32_t cDirEnts;
+ /** The directory (converted to 32-bit). */
+ PRTCVDIRENT32 paDirEnts;
+ /** Current debugging style when parsing modules. */
+ uint16_t uCurStyle;
+ /** Current debugging style version (HLL only). */
+ uint16_t uCurStyleVer;
+
+ /** The segment map (if present). */
+ PRTCVSEGMAP pSegMap;
+ /** Segment names. */
+ char *pszzSegNames;
+ /** The size of the segment names. */
+ uint32_t cbSegNames;
+
+ /** Size of the block pchSrcStrings points to. */
+ size_t cbSrcStrings;
+ /** Buffer space allocated for the source string table. */
+ size_t cbSrcStringsAlloc;
+ /** Copy of the last CV8 source string table. */
+ char *pchSrcStrings;
+
+ /** The size of the current source information table. */
+ size_t cbSrcInfo;
+ /** Buffer space allocated for the source information table. */
+ size_t cbSrcInfoAlloc;
+ /** Copy of the last CV8 source information table. */
+ uint8_t *pbSrcInfo;
+
+ /** @} */
+
+} RTDBGMODCV;
+/** Pointer to a codeview debug info reader instance. */
+typedef RTDBGMODCV *PRTDBGMODCV;
+/** Pointer to a const codeview debug info reader instance. */
+typedef RTDBGMODCV *PCRTDBGMODCV;
+
+
+
+/**
+ * Subsection callback.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CodeView debug info reader instance.
+ * @param pvSubSect Pointer to the subsection data.
+ * @param cbSubSect The size of the subsection data.
+ * @param pDirEnt The directory entry.
+ */
+typedef DECLCALLBACKTYPE(int, FNDBGMODCVSUBSECTCALLBACK,(PRTDBGMODCV pThis, void const *pvSubSect, size_t cbSubSect,
+ PCRTCVDIRENT32 pDirEnt));
+/** Pointer to a subsection callback. */
+typedef FNDBGMODCVSUBSECTCALLBACK *PFNDBGMODCVSUBSECTCALLBACK;
+
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Light weight assert + return w/ fixed status code. */
+#define RTDBGMODCV_CHECK_RET_BF(a_Expr, a_LogArgs) \
+ do { \
+ if (!(a_Expr)) \
+ { \
+ Log(("RTDbgCv: Check failed on line %d: " #a_Expr "\n", __LINE__)); \
+ Log(a_LogArgs); \
+ /*AssertFailed();*/ \
+ return VERR_CV_BAD_FORMAT; \
+ } \
+ } while (0)
+
+
+/** Light weight assert + return w/ fixed status code. */
+#define RTDBGMODCV_CHECK_NOMSG_RET_BF(a_Expr) \
+ do { \
+ if (!(a_Expr)) \
+ { \
+ Log(("RTDbgCv: Check failed on line %d: " #a_Expr "\n", __LINE__)); \
+ /*AssertFailed();*/ \
+ return VERR_CV_BAD_FORMAT; \
+ } \
+ } while (0)
+
+
+
+
+
+/**
+ * Reads CodeView information.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CodeView reader instance.
+ * @param off The offset to start reading at, relative to the
+ * CodeView base header.
+ * @param pvBuf The buffer to read into.
+ * @param cb How many bytes to read.
+ */
+static int rtDbgModCvReadAt(PRTDBGMODCV pThis, uint32_t off, void *pvBuf, size_t cb)
+{
+ int rc;
+ if (pThis->hFile == NIL_RTFILE)
+ rc = pThis->pMod->pImgVt->pfnReadAt(pThis->pMod, UINT32_MAX, off + pThis->offBase, pvBuf, cb);
+ else
+ rc = RTFileReadAt(pThis->hFile, off + pThis->offBase, pvBuf, cb, NULL);
+ return rc;
+}
+
+
+/**
+ * Reads CodeView information into an allocated buffer.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CodeView reader instance.
+ * @param off The offset to start reading at, relative to the
+ * CodeView base header.
+ * @param ppvBuf Where to return the allocated buffer on success.
+ * @param cb How many bytes to read.
+ */
+static int rtDbgModCvReadAtAlloc(PRTDBGMODCV pThis, uint32_t off, void **ppvBuf, size_t cb)
+{
+ int rc;
+ void *pvBuf = *ppvBuf = RTMemAlloc(cb);
+ if (pvBuf)
+ {
+ if (pThis->hFile == NIL_RTFILE)
+ rc = pThis->pMod->pImgVt->pfnReadAt(pThis->pMod, UINT32_MAX, off + pThis->offBase, pvBuf, cb);
+ else
+ rc = RTFileReadAt(pThis->hFile, off + pThis->offBase, pvBuf, cb, NULL);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTMemFree(pvBuf);
+ *ppvBuf = NULL;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Gets a name string for a subsection type.
+ *
+ * @returns Section name (read only).
+ * @param uSubSectType The subsection type.
+ */
+static const char *rtDbgModCvGetSubSectionName(uint16_t uSubSectType)
+{
+ switch (uSubSectType)
+ {
+ case kCvSst_OldModule: return "sstOldModule";
+ case kCvSst_OldPublic: return "sstOldPublic";
+ case kCvSst_OldTypes: return "sstOldTypes";
+ case kCvSst_OldSymbols: return "sstOldSymbols";
+ case kCvSst_OldSrcLines: return "sstOldSrcLines";
+ case kCvSst_OldLibraries: return "sstOldLibraries";
+ case kCvSst_OldImports: return "sstOldImports";
+ case kCvSst_OldCompacted: return "sstOldCompacted";
+ case kCvSst_OldSrcLnSeg: return "sstOldSrcLnSeg";
+ case kCvSst_OldSrcLines3: return "sstOldSrcLines3";
+
+ case kCvSst_Module: return "sstModule";
+ case kCvSst_Types: return "sstTypes";
+ case kCvSst_Public: return "sstPublic";
+ case kCvSst_PublicSym: return "sstPublicSym";
+ case kCvSst_Symbols: return "sstSymbols";
+ case kCvSst_AlignSym: return "sstAlignSym";
+ case kCvSst_SrcLnSeg: return "sstSrcLnSeg";
+ case kCvSst_SrcModule: return "sstSrcModule";
+ case kCvSst_Libraries: return "sstLibraries";
+ case kCvSst_GlobalSym: return "sstGlobalSym";
+ case kCvSst_GlobalPub: return "sstGlobalPub";
+ case kCvSst_GlobalTypes: return "sstGlobalTypes";
+ case kCvSst_MPC: return "sstMPC";
+ case kCvSst_SegMap: return "sstSegMap";
+ case kCvSst_SegName: return "sstSegName";
+ case kCvSst_PreComp: return "sstPreComp";
+ case kCvSst_PreCompMap: return "sstPreCompMap";
+ case kCvSst_OffsetMap16: return "sstOffsetMap16";
+ case kCvSst_OffsetMap32: return "sstOffsetMap32";
+ case kCvSst_FileIndex: return "sstFileIndex";
+ case kCvSst_StaticSym: return "sstStaticSym";
+ }
+ static char s_sz[32];
+ RTStrPrintf(s_sz, sizeof(s_sz), "Unknown%#x", uSubSectType);
+ return s_sz;
+}
+#endif /* LOG_ENABLED */
+
+
+#ifdef LOG_ENABLED
+/**
+ * Gets a name string for a symbol type.
+ *
+ * @returns symbol type name (read only).
+ * @param enmSymType The symbol type to name.
+ */
+static const char *rtDbgModCvSsSymTypeName(RTCVSYMTYPE enmSymType)
+{
+ switch (enmSymType)
+ {
+# define CASE_RET_STR(Name) case kCvSymType_##Name: return #Name;
+ CASE_RET_STR(Compile);
+ CASE_RET_STR(Register);
+ CASE_RET_STR(Constant);
+ CASE_RET_STR(UDT);
+ CASE_RET_STR(SSearch);
+ CASE_RET_STR(End);
+ CASE_RET_STR(Skip);
+ CASE_RET_STR(CVReserve);
+ CASE_RET_STR(ObjName);
+ CASE_RET_STR(EndArg);
+ CASE_RET_STR(CobolUDT);
+ CASE_RET_STR(ManyReg);
+ CASE_RET_STR(Return);
+ CASE_RET_STR(EntryThis);
+ CASE_RET_STR(BpRel16);
+ CASE_RET_STR(LData16);
+ CASE_RET_STR(GData16);
+ CASE_RET_STR(Pub16);
+ CASE_RET_STR(LProc16);
+ CASE_RET_STR(GProc16);
+ CASE_RET_STR(Thunk16);
+ CASE_RET_STR(BLock16);
+ CASE_RET_STR(With16);
+ CASE_RET_STR(Label16);
+ CASE_RET_STR(CExModel16);
+ CASE_RET_STR(VftPath16);
+ CASE_RET_STR(RegRel16);
+ CASE_RET_STR(BpRel32);
+ CASE_RET_STR(LData32);
+ CASE_RET_STR(GData32);
+ CASE_RET_STR(Pub32);
+ CASE_RET_STR(LProc32);
+ CASE_RET_STR(GProc32);
+ CASE_RET_STR(Thunk32);
+ CASE_RET_STR(Block32);
+ CASE_RET_STR(With32);
+ CASE_RET_STR(Label32);
+ CASE_RET_STR(CExModel32);
+ CASE_RET_STR(VftPath32);
+ CASE_RET_STR(RegRel32);
+ CASE_RET_STR(LThread32);
+ CASE_RET_STR(GThread32);
+ CASE_RET_STR(LProcMips);
+ CASE_RET_STR(GProcMips);
+ CASE_RET_STR(ProcRef);
+ CASE_RET_STR(DataRef);
+ CASE_RET_STR(Align);
+ CASE_RET_STR(LProcRef);
+ CASE_RET_STR(V2_Register);
+ CASE_RET_STR(V2_Constant);
+ CASE_RET_STR(V2_Udt);
+ CASE_RET_STR(V2_CobolUdt);
+ CASE_RET_STR(V2_ManyReg);
+ CASE_RET_STR(V2_BpRel);
+ CASE_RET_STR(V2_LData);
+ CASE_RET_STR(V2_GData);
+ CASE_RET_STR(V2_Pub);
+ CASE_RET_STR(V2_LProc);
+ CASE_RET_STR(V2_GProc);
+ CASE_RET_STR(V2_VftTable);
+ CASE_RET_STR(V2_RegRel);
+ CASE_RET_STR(V2_LThread);
+ CASE_RET_STR(V2_GThread);
+ CASE_RET_STR(V2_Unknown_1010);
+ CASE_RET_STR(V2_Unknown_1011);
+ CASE_RET_STR(V2_FrameInfo);
+ CASE_RET_STR(V2_Compliand);
+ CASE_RET_STR(V3_Compliand);
+ CASE_RET_STR(V3_Thunk);
+ CASE_RET_STR(V3_Block);
+ CASE_RET_STR(V3_Unknown_1104);
+ CASE_RET_STR(V3_Label);
+ CASE_RET_STR(V3_Register);
+ CASE_RET_STR(V3_Constant);
+ CASE_RET_STR(V3_Udt);
+ CASE_RET_STR(V3_Unknown_1109);
+ CASE_RET_STR(V3_Unknown_110a);
+ CASE_RET_STR(V3_BpRel);
+ CASE_RET_STR(V3_LData);
+ CASE_RET_STR(V3_GData);
+ CASE_RET_STR(V3_Pub);
+ CASE_RET_STR(V3_LProc);
+ CASE_RET_STR(V3_GProc);
+ CASE_RET_STR(V3_RegRel);
+ CASE_RET_STR(V3_LThread);
+ CASE_RET_STR(V3_GThread);
+ CASE_RET_STR(V3_Unknown_1114);
+ CASE_RET_STR(V3_Unknown_1115);
+ CASE_RET_STR(V3_MSTool);
+ CASE_RET_STR(V3_PubFunc1);
+ CASE_RET_STR(V3_PubFunc2);
+ CASE_RET_STR(V3_SectInfo);
+ CASE_RET_STR(V3_SubSectInfo);
+ CASE_RET_STR(V3_Entrypoint);
+ CASE_RET_STR(V3_Unknown_1139);
+ CASE_RET_STR(V3_SecuCookie);
+ CASE_RET_STR(V3_Unknown_113b);
+ CASE_RET_STR(V3_MsToolInfo);
+ CASE_RET_STR(V3_MsToolEnv);
+ CASE_RET_STR(VS2013_Local);
+ CASE_RET_STR(VS2013_FpOff);
+ CASE_RET_STR(VS2013_LProc32);
+ CASE_RET_STR(VS2013_GProc32);
+#undef CASE_RET_STR
+ case kCvSymType_EndOfValues: break;
+ }
+ return "<unknown type>";
+}
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Adds a string to the g_hDbgModStrCache after sanitizing it.
+ *
+ * IPRT only deals with UTF-8 strings, so the string will be forced to UTF-8
+ * encoding. Also, codeview generally have length prefixed
+ *
+ * @returns String cached copy of the string.
+ * @param pch The string to copy to the cache.
+ * @param cch The length of the string. RTSTR_MAX if zero
+ * terminated.
+ */
+static const char *rtDbgModCvAddSanitizedStringToCache(const char *pch, size_t cch)
+{
+ /*
+ * If the string is valid UTF-8 and or the right length, we're good.
+ * This is usually the case.
+ */
+ const char *pszRet;
+ int rc;
+ if (cch != RTSTR_MAX)
+ rc = RTStrValidateEncodingEx(pch, cch, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ else
+ rc = RTStrValidateEncodingEx(pch, cch, 0);
+ if (RT_SUCCESS(rc))
+ pszRet = RTStrCacheEnterN(g_hDbgModStrCache, pch, cch);
+ else
+ {
+ /*
+ * Need to sanitize the string, so make a copy of it.
+ */
+ char *pszCopy = (char *)RTMemDupEx(pch, cch, 1);
+ AssertPtrReturn(pszCopy, NULL);
+
+ /* Deal with anyembedded zero chars. */
+ char *psz = RTStrEnd(pszCopy, cch);
+ while (psz)
+ {
+ *psz = '_';
+ psz = RTStrEnd(psz, cch - (psz - pszCopy));
+ }
+
+ /* Force valid UTF-8 encoding. */
+ RTStrPurgeEncoding(pszCopy);
+ Assert(strlen(pszCopy) == cch);
+
+ /* Enter it into the cache and free the temp copy. */
+ pszRet = RTStrCacheEnterN(g_hDbgModStrCache, pszCopy, cch);
+ RTMemFree(pszCopy);
+ }
+ return pszRet;
+}
+
+
+/**
+ * Translates a codeview segment and offset into our segment layout.
+ *
+ * @returns
+ * @param pThis .
+ * @param piSeg .
+ * @param poff .
+ */
+DECLINLINE(int) rtDbgModCvAdjustSegAndOffset(PRTDBGMODCV pThis, uint32_t *piSeg, uint64_t *poff)
+{
+ uint32_t iSeg = *piSeg;
+ if (iSeg == 0)
+ iSeg = RTDBGSEGIDX_ABS;
+ else if (pThis->pSegMap)
+ {
+ if (pThis->fHaveDosFrames)
+ {
+ if ( iSeg > pThis->pSegMap->Hdr.cSegs
+ || iSeg == 0)
+ return VERR_CV_BAD_FORMAT;
+ if (*poff <= pThis->pSegMap->aDescs[iSeg - 1].cb + pThis->pSegMap->aDescs[iSeg - 1].off)
+ *poff -= pThis->pSegMap->aDescs[iSeg - 1].off;
+ else
+ {
+ /* Workaround for VGABIOS where _DATA symbols like vgafont8 are
+ reported in the VGAROM segment. */
+ uint64_t uAddrSym = *poff + ((uint32_t)pThis->pSegMap->aDescs[iSeg - 1].iFrame << 4);
+ uint16_t j = pThis->pSegMap->Hdr.cSegs;
+ while (j-- > 0)
+ {
+ uint64_t uAddrFirst = (uint64_t)pThis->pSegMap->aDescs[j].off
+ + ((uint32_t)pThis->pSegMap->aDescs[j].iFrame << 4);
+ if (uAddrSym - uAddrFirst < pThis->pSegMap->aDescs[j].cb)
+ {
+ Log(("CV addr fix: %04x:%08x -> %04x:%08x\n", iSeg, *poff, j + 1, uAddrSym - uAddrFirst));
+ *poff = uAddrSym - uAddrFirst;
+ iSeg = j + 1;
+ break;
+ }
+ }
+ if (j == UINT16_MAX)
+ return VERR_CV_BAD_FORMAT;
+ }
+ }
+ else
+ {
+ if ( iSeg > pThis->pSegMap->Hdr.cSegs
+ || iSeg == 0
+ || *poff > pThis->pSegMap->aDescs[iSeg - 1].cb)
+ return VERR_CV_BAD_FORMAT;
+ *poff += pThis->pSegMap->aDescs[iSeg - 1].off;
+ }
+ if (pThis->pSegMap->aDescs[iSeg - 1].fFlags & RTCVSEGMAPDESC_F_ABS)
+ iSeg = RTDBGSEGIDX_ABS;
+ else
+ iSeg = pThis->pSegMap->aDescs[iSeg - 1].iGroup;
+ }
+ *piSeg = iSeg;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a symbol to the container.
+ *
+ * @returns IPRT status code
+ * @param pThis The CodeView debug info reader instance.
+ * @param iSeg Segment number.
+ * @param off Offset into the segment
+ * @param pchName The symbol name (not necessarily terminated).
+ * @param cchName The symbol name length.
+ * @param fFlags Flags reserved for future exploits, MBZ.
+ * @param cbSym Symbol size, 0 if not available.
+ */
+static int rtDbgModCvAddSymbol(PRTDBGMODCV pThis, uint32_t iSeg, uint64_t off, const char *pchName,
+ uint32_t cchName, uint32_t fFlags, uint32_t cbSym)
+{
+ RT_NOREF_PV(fFlags);
+ const char *pszName = rtDbgModCvAddSanitizedStringToCache(pchName, cchName);
+ int rc;
+ if (pszName)
+ {
+#if 1
+ Log2(("CV Sym: %04x:%08x %.*s\n", iSeg, off, cchName, pchName));
+ rc = rtDbgModCvAdjustSegAndOffset(pThis, &iSeg, &off);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDbgModSymbolAdd(pThis->hCnt, pszName, iSeg, off, cbSym, RTDBGSYMBOLADD_F_ADJUST_SIZES_ON_CONFLICT, NULL);
+
+ /* Simple duplicate symbol mangling, just to get more details. */
+ if (rc == VERR_DBG_DUPLICATE_SYMBOL && cchName < _2K)
+ {
+ char szTmpName[_2K + 96];
+ memcpy(szTmpName, pszName, cchName);
+ szTmpName[cchName] = '_';
+ for (uint32_t i = 1; i < 32; i++)
+ {
+ RTStrFormatU32(&szTmpName[cchName + 1], 80, i, 10, 0, 0, 0);
+ rc = RTDbgModSymbolAdd(pThis->hCnt, szTmpName, iSeg, off, cbSym, 0 /*fFlags*/, NULL);
+ if (rc != VERR_DBG_DUPLICATE_SYMBOL)
+ break;
+ }
+
+ }
+ else if (rc == VERR_DBG_ADDRESS_CONFLICT && cbSym)
+ rc = RTDbgModSymbolAdd(pThis->hCnt, pszName, iSeg, off, cbSym,
+ RTDBGSYMBOLADD_F_REPLACE_SAME_ADDR | RTDBGSYMBOLADD_F_ADJUST_SIZES_ON_CONFLICT, NULL);
+
+ Log(("Symbol: %04x:%08x %.*s [%Rrc]\n", iSeg, off, cchName, pchName, rc));
+ if (rc == VERR_DBG_ADDRESS_CONFLICT || rc == VERR_DBG_DUPLICATE_SYMBOL)
+ rc = VINF_SUCCESS;
+ }
+ else
+ Log(("Invalid segment index/offset %#06x:%08x for symbol %.*s\n", iSeg, off, cchName, pchName));
+
+#else
+ Log(("Symbol: %04x:%08x %.*s\n", iSeg, off, cchName, pchName));
+ rc = VINF_SUCCESS;
+#endif
+ RTStrCacheRelease(g_hDbgModStrCache, pszName);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Validates the a zero terminated string.
+ *
+ * @returns String length if valid, UINT16_MAX if invalid.
+ * @param pszString The string to validate.
+ * @param pvRec The pointer to the record containing the string.
+ * @param cbRec The record length.
+ */
+static uint16_t rtDbgModCvValidateZeroString(const char *pszString, void const *pvRec, uint16_t cbRec)
+{
+ size_t offStrMember = (uintptr_t)pszString - (uintptr_t)pvRec;
+ AssertReturn(offStrMember < _1K, UINT16_MAX);
+ AssertReturn(offStrMember <= cbRec, UINT16_MAX);
+ cbRec -= (uint16_t)offStrMember;
+
+ const char *pchEnd = RTStrEnd(pszString, cbRec);
+ AssertReturn(pchEnd, UINT16_MAX);
+
+ int rc = RTStrValidateEncoding(pszString);
+ AssertRCReturn(rc, UINT16_MAX);
+
+ return (uint16_t)(pchEnd - pszString);
+}
+
+
+/**
+ * Parses a CV4 symbol table, adding symbols to the container.
+ *
+ * @returns IPRT status code
+ * @param pThis The CodeView debug info reader instance.
+ * @param pvSymTab The symbol table.
+ * @param cbSymTab The size of the symbol table.
+ * @param fFlags Flags reserved for future exploits, MBZ.
+ */
+static int rtDbgModCvSsProcessV4PlusSymTab(PRTDBGMODCV pThis, void const *pvSymTab, size_t cbSymTab, uint32_t fFlags)
+{
+ int rc = VINF_SUCCESS;
+ RTCPTRUNION uCursor;
+ uCursor.pv = pvSymTab;
+
+ RT_NOREF_PV(fFlags);
+
+ while (cbSymTab > 0 && RT_SUCCESS(rc))
+ {
+ uint8_t const * const pbRecStart = uCursor.pu8;
+ uint16_t cbRec = *uCursor.pu16++;
+ if (cbRec >= 2)
+ {
+ uint16_t uSymType = *uCursor.pu16++;
+
+ Log3((" %p: uSymType=%#06x LB %#x %s\n",
+ pbRecStart - (uint8_t *)pvSymTab, uSymType, cbRec, rtDbgModCvSsSymTypeName((RTCVSYMTYPE)uSymType)));
+ RTDBGMODCV_CHECK_RET_BF(cbRec >= 2 && cbRec <= cbSymTab, ("cbRec=%#x cbSymTab=%#x\n", cbRec, cbSymTab));
+
+ switch (uSymType)
+ {
+ case kCvSymType_LData16:
+ case kCvSymType_GData16:
+ case kCvSymType_Pub16:
+ {
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec > 2 + 2+2+2+1);
+ uint16_t off = *uCursor.pu16++;
+ uint16_t iSeg = *uCursor.pu16++;
+ /*uint16_t iType =*/ *uCursor.pu16++;
+ uint8_t cchName = *uCursor.pu8++;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cchName > 0);
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec >= 2 + 2+2+2+1 + cchName);
+
+ rc = rtDbgModCvAddSymbol(pThis, iSeg, off, uCursor.pch, cchName, 0, 0);
+ break;
+ }
+
+ case kCvSymType_LData32:
+ case kCvSymType_GData32:
+ case kCvSymType_Pub32:
+ {
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec > 2 + 4+2+2+1);
+ uint32_t off = *uCursor.pu32++;
+ uint16_t iSeg = *uCursor.pu16++;
+ /*uint16_t iType =*/ *uCursor.pu16++;
+ uint8_t cchName = *uCursor.pu8++;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cchName > 0);
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec >= 2 + 4+2+2+1 + cchName);
+
+ rc = rtDbgModCvAddSymbol(pThis, iSeg, off, uCursor.pch, cchName, 0, 0);
+ break;
+ }
+
+ case kCvSymType_LProc16:
+ case kCvSymType_GProc16:
+ {
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec > 2 + 4+4+4+2+2+2+2+2+2+1+1);
+ /*uint32_t uParent =*/ *uCursor.pu32++;
+ /*uint32_t uEnd =*/ *uCursor.pu32++;
+ /*uint32_t uNext =*/ *uCursor.pu32++;
+ uint16_t cbProc = *uCursor.pu16++;
+ /*uint16_t offDebugStart =*/ *uCursor.pu16++;
+ /*uint16_t offDebugEnd =*/ *uCursor.pu16++;
+ uint16_t off = *uCursor.pu16++;
+ uint16_t iSeg = *uCursor.pu16++;
+ /*uint16_t iProcType =*/ *uCursor.pu16++;
+ /*uint8_t fbType =*/ *uCursor.pu8++;
+ uint8_t cchName = *uCursor.pu8++;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cchName > 0);
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec >= 2 + 4+4+4+2+2+2+2+2+2+1+1 + cchName);
+
+ rc = rtDbgModCvAddSymbol(pThis, iSeg, off, uCursor.pch, cchName, 0, cbProc);
+ break;
+ }
+
+ case kCvSymType_LProc32:
+ case kCvSymType_GProc32:
+ {
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec > 2 + 4+4+4+4+4+4+4+2+2+1+1);
+ /*uint32_t uParent =*/ *uCursor.pu32++;
+ /*uint32_t uEnd =*/ *uCursor.pu32++;
+ /*uint32_t uNext =*/ *uCursor.pu32++;
+ /*uint32_t cbProc =*/ *uCursor.pu32++;
+ /*uint32_t offDebugStart =*/ *uCursor.pu32++;
+ /*uint32_t offDebugEnd =*/ *uCursor.pu32++;
+ uint32_t off = *uCursor.pu32++;
+ uint16_t iSeg = *uCursor.pu16++;
+ /*uint16_t iProcType =*/ *uCursor.pu16++;
+ /*uint8_t fbType =*/ *uCursor.pu8++;
+ uint8_t cchName = *uCursor.pu8++;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cchName > 0);
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec >= 2 + 4+4+4+4+4+4+4+2+2+1+1 + cchName);
+
+ rc = rtDbgModCvAddSymbol(pThis, iSeg, off, uCursor.pch, cchName, 0, 0);
+ break;
+ }
+
+ case kCvSymType_V3_Label:
+ {
+ PCRTCVSYMV3LABEL pLabel = (PCRTCVSYMV3LABEL)uCursor.pv;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec >= sizeof(*pLabel));
+ uint16_t cchName = rtDbgModCvValidateZeroString(pLabel->szName, pLabel, cbRec);
+ if (cchName != UINT16_MAX && cchName > 0)
+ rc = rtDbgModCvAddSymbol(pThis, pLabel->iSection, pLabel->offSection, pLabel->szName, cchName, 0, 0);
+ else
+ Log3((" cchName=%#x sec:off=%#x:%#x %.*Rhxs\n",
+ cchName, pLabel->iSection, pLabel->offSection, cbRec, pLabel));
+ break;
+ }
+
+ case kCvSymType_V3_LData:
+ case kCvSymType_V3_GData:
+ case kCvSymType_V3_Pub:
+ {
+ PCRTCVSYMV3TYPEDNAME pData = (PCRTCVSYMV3TYPEDNAME)uCursor.pv;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec >= sizeof(*pData));
+ uint16_t cchName = rtDbgModCvValidateZeroString(pData->szName, pData, cbRec);
+ if (cchName != UINT16_MAX && cchName > 0)
+ rc = rtDbgModCvAddSymbol(pThis, pData->iSection, pData->offSection, pData->szName, cchName, 0, 0);
+ else
+ Log3((" cchName=%#x sec:off=%#x:%#x idType=%#x %.*Rhxs\n",
+ cchName, pData->iSection, pData->offSection, pData->idType, cbRec, pData));
+ break;
+ }
+
+ case kCvSymType_V3_LProc:
+ case kCvSymType_V3_GProc:
+ {
+ PCRTCVSYMV3PROC pProc = (PCRTCVSYMV3PROC)uCursor.pv;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbRec >= sizeof(*pProc));
+ uint16_t cchName = rtDbgModCvValidateZeroString(pProc->szName, pProc, cbRec);
+ if (cchName != UINT16_MAX && cchName > 0)
+ rc = rtDbgModCvAddSymbol(pThis, pProc->iSection, pProc->offSection, pProc->szName, cchName,
+ 0, pProc->cbProc);
+ else
+ Log3((" cchName=%#x sec:off=%#x:%#x LB %#x\n",
+ cchName, pProc->iSection, pProc->offSection, pProc->cbProc));
+ break;
+ }
+
+ }
+ }
+ /*else: shorter records can be used for alignment, I guess. */
+
+ /* next */
+ uCursor.pu8 = pbRecStart + cbRec + 2;
+ cbSymTab -= cbRec + 2;
+ }
+ return rc;
+}
+
+
+/**
+ * Makes a copy of the CV8 source string table.
+ *
+ * It will be references in a subsequent source information table, and again by
+ * line number tables thru that.
+ *
+ * @returns IPRT status code
+ * @param pThis The CodeView debug info reader instance.
+ * @param pvSrcStrings The source string table.
+ * @param cbSrcStrings The size of the source strings.
+ * @param fFlags Flags reserved for future exploits, MBZ.
+ */
+static int rtDbgModCvSsProcessV8SrcStrings(PRTDBGMODCV pThis, void const *pvSrcStrings, size_t cbSrcStrings, uint32_t fFlags)
+{
+ RT_NOREF_PV(fFlags);
+
+ if (pThis->cbSrcStrings)
+ Log(("\n!!More than one source file string table for this module!!\n\n"));
+
+ if (cbSrcStrings >= pThis->cbSrcStringsAlloc)
+ {
+ void *pvNew = RTMemRealloc(pThis->pchSrcStrings, cbSrcStrings + 1);
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pThis->pchSrcStrings = (char *)pvNew;
+ pThis->cbSrcStringsAlloc = cbSrcStrings + 1;
+ }
+ memcpy(pThis->pchSrcStrings, pvSrcStrings, cbSrcStrings);
+ pThis->pchSrcStrings[cbSrcStrings] = '\0';
+ pThis->cbSrcStrings = cbSrcStrings;
+ Log2((" saved %#x bytes of CV8 source strings\n", cbSrcStrings));
+
+ if (LogIs3Enabled())
+ {
+ size_t iFile = 0;
+ size_t off = pThis->pchSrcStrings[0] != '\0' ? 0 : 1;
+ while (off < cbSrcStrings)
+ {
+ size_t cch = strlen(&pThis->pchSrcStrings[off]);
+ Log3((" %010zx #%03zu: %s\n", off, iFile, &pThis->pchSrcStrings[off]));
+ off += cch + 1;
+ iFile++;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Makes a copy of the CV8 source information table.
+ *
+ * It will be references in subsequent line number tables.
+ *
+ * @returns IPRT status code
+ * @param pThis The CodeView debug info reader instance.
+ * @param pvSrcInfo The source information table.
+ * @param cbSrcInfo The size of the source information table (bytes).
+ * @param fFlags Flags reserved for future exploits, MBZ.
+ */
+static int rtDbgModCvSsProcessV8SrcInfo(PRTDBGMODCV pThis, void const *pvSrcInfo, size_t cbSrcInfo, uint32_t fFlags)
+{
+ RT_NOREF_PV(fFlags);
+
+ if (pThis->cbSrcInfo)
+ Log(("\n!!More than one source file info table for this module!!\n\n"));
+
+ if (cbSrcInfo + sizeof(RTCV8SRCINFO) > pThis->cbSrcInfoAlloc)
+ {
+ void *pvNew = RTMemRealloc(pThis->pbSrcInfo, cbSrcInfo + sizeof(RTCV8SRCINFO));
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pThis->pbSrcInfo = (uint8_t *)pvNew;
+ pThis->cbSrcInfoAlloc = cbSrcInfo + sizeof(RTCV8SRCINFO);
+ }
+ memcpy(pThis->pbSrcInfo, pvSrcInfo, cbSrcInfo);
+ memset(&pThis->pbSrcInfo[cbSrcInfo], 0, sizeof(RTCV8SRCINFO));
+ pThis->cbSrcInfo = cbSrcInfo;
+ Log2((" saved %#x bytes of CV8 source file info\n", cbSrcInfo));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Makes a copy of the CV8 source string table.
+ *
+ * It will be references in subsequent line number tables.
+ *
+ * @returns IPRT status code
+ * @param pThis The CodeView debug info reader instance.
+ * @param pvSectLines The section source line table.
+ * @param cbSectLines The size of the section source line table.
+ * @param fFlags Flags reserved for future exploits, MBZ.
+ */
+static int rtDbgModCvSsProcessV8SectLines(PRTDBGMODCV pThis, void const *pvSectLines, size_t cbSectLines, uint32_t fFlags)
+{
+ RT_NOREF_PV(fFlags);
+
+ /*
+ * Starts with header.
+ */
+ PCRTCV8LINESHDR pHdr = (PCRTCV8LINESHDR)pvSectLines;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbSectLines >= sizeof(*pHdr));
+ cbSectLines -= sizeof(*pHdr);
+ Log2(("RTDbgModCv: seg #%u, off %#x LB %#x \n", pHdr->iSection, pHdr->offSection, pHdr->cbSectionCovered));
+
+ RTCPTRUNION uCursor;
+ uCursor.pv = pHdr + 1;
+ while (cbSectLines > 0)
+ {
+ /* Source file header. */
+ PCRTCV8LINESSRCMAP pSrcHdr = (PCRTCV8LINESSRCMAP)uCursor.pv;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbSectLines >= sizeof(*pSrcHdr));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(pSrcHdr->cb == pSrcHdr->cLines * sizeof(RTCV8LINEPAIR) + sizeof(RTCV8LINESSRCMAP));
+ RTDBGMODCV_CHECK_RET_BF(!(pSrcHdr->offSourceInfo & 3), ("offSourceInfo=%#x\n", pSrcHdr->offSourceInfo));
+ if (pSrcHdr->offSourceInfo + sizeof(uint32_t) <= pThis->cbSrcInfo)
+ {
+ PCRTCV8SRCINFO pSrcInfo = (PCRTCV8SRCINFO)&pThis->pbSrcInfo[pSrcHdr->offSourceInfo];
+ const char *pszName = pSrcInfo->offSourceName < pThis->cbSrcStrings
+ ? &pThis->pchSrcStrings[pSrcInfo->offSourceName] : "unknown.c";
+ pszName = rtDbgModCvAddSanitizedStringToCache(pszName, RTSTR_MAX);
+ Log2(("RTDbgModCv: #%u lines, %#x bytes, %#x=%s\n", pSrcHdr->cLines, pSrcHdr->cb, pSrcInfo->offSourceName, pszName));
+
+ if (pszName)
+ {
+ /* Process the line/offset pairs. */
+ uint32_t cLeft = pSrcHdr->cLines;
+ PCRTCV8LINEPAIR pPair = (PCRTCV8LINEPAIR)(pSrcHdr + 1);
+ while (cLeft-- > 0)
+ {
+ uint32_t idxSeg = pHdr->iSection;
+ uint64_t off = pPair->offSection + pHdr->offSection;
+ int rc = rtDbgModCvAdjustSegAndOffset(pThis, &idxSeg, &off);
+ if (RT_SUCCESS(rc))
+ rc = RTDbgModLineAdd(pThis->hCnt, pszName, pPair->uLineNumber, idxSeg, off, NULL);
+ if (RT_SUCCESS(rc))
+ Log3(("RTDbgModCv: %#x:%#010llx %0u\n", idxSeg, off, pPair->uLineNumber));
+ else
+ Log(( "RTDbgModCv: %#x:%#010llx %0u - rc=%Rrc!! (org: idxSeg=%#x off=%#x)\n",
+ idxSeg, off, pPair->uLineNumber, rc, pHdr->iSection, pPair->offSection));
+
+ /* next */
+ pPair++;
+ }
+ Assert((uintptr_t)pPair - (uintptr_t)pSrcHdr == pSrcHdr->cb);
+ }
+ }
+ else
+ Log(("RTDbgModCv: offSourceInfo=%#x cbSrcInfo=%#x!\n", pSrcHdr->offSourceInfo, pThis->cbSrcInfo));
+
+ /* next */
+ cbSectLines -= pSrcHdr->cb;
+ uCursor.pu8 += pSrcHdr->cb;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Parses a CV8 symbol table, adding symbols to the container.
+ *
+ * @returns IPRT status code
+ * @param pThis The CodeView debug info reader instance.
+ * @param pvSymTab The symbol table.
+ * @param cbSymTab The size of the symbol table.
+ * @param fFlags Flags reserved for future exploits, MBZ.
+ */
+static int rtDbgModCvSsProcessV8SymTab(PRTDBGMODCV pThis, void const *pvSymTab, size_t cbSymTab, uint32_t fFlags)
+{
+ size_t const cbSymTabSaved = cbSymTab;
+ int rc = VINF_SUCCESS;
+
+ /*
+ * First pass looks for source information and source strings tables.
+ * Microsoft puts the 0xf3 and 0xf4 last, usually with 0xf4 first.
+ *
+ * We ASSUME one string and one info table per module!
+ */
+ RTCPTRUNION uCursor;
+ uCursor.pv = pvSymTab;
+ for (;;)
+ {
+ RTDBGMODCV_CHECK_RET_BF(cbSymTab > sizeof(RTCV8SYMBOLSBLOCK), ("cbSymTab=%zu\n", cbSymTab));
+ PCRTCV8SYMBOLSBLOCK pBlockHdr = (PCRTCV8SYMBOLSBLOCK)uCursor.pv;
+ Log3((" %p: pass #1 uType=%#04x LB %#x\n", (uint8_t *)pBlockHdr - (uint8_t *)pvSymTab, pBlockHdr->uType, pBlockHdr->cb));
+ RTDBGMODCV_CHECK_RET_BF(pBlockHdr->cb <= cbSymTab - sizeof(RTCV8SYMBOLSBLOCK),
+ ("cb=%#u cbSymTab=%zu\n", pBlockHdr->cb, cbSymTab));
+
+ switch (pBlockHdr->uType)
+ {
+ case RTCV8SYMBLOCK_TYPE_SRC_STR:
+ rc = rtDbgModCvSsProcessV8SrcStrings(pThis, pBlockHdr + 1, pBlockHdr->cb, fFlags);
+ break;
+
+ case RTCV8SYMBLOCK_TYPE_SRC_INFO:
+ rc = rtDbgModCvSsProcessV8SrcInfo(pThis, pBlockHdr + 1, pBlockHdr->cb, fFlags);
+ break;
+
+ case RTCV8SYMBLOCK_TYPE_SECT_LINES:
+ case RTCV8SYMBLOCK_TYPE_SYMBOLS:
+ break;
+ default:
+ Log(("rtDbgModCvSsProcessV8SymTab: Unknown block type %#x (LB %#x)\n", pBlockHdr->uType, pBlockHdr->cb));
+ break;
+ }
+ uint32_t cbAligned = RT_ALIGN_32(sizeof(*pBlockHdr) + pBlockHdr->cb, 4);
+ if (RT_SUCCESS(rc) && cbSymTab > cbAligned)
+ {
+ uCursor.pu8 += cbAligned;
+ cbSymTab -= cbAligned;
+ }
+ else
+ break;
+ }
+
+ /*
+ * Log the source info now that we've gathered both it and the strings.
+ */
+ if (LogIs3Enabled() && pThis->cbSrcInfo)
+ {
+ Log3((" Source file info table:\n"));
+ size_t iFile = 0;
+ size_t off = 0;
+ while (off + 4 <= pThis->cbSrcInfo)
+ {
+ PCRTCV8SRCINFO pSrcInfo = (PCRTCV8SRCINFO)&pThis->pbSrcInfo[off];
+#ifdef LOG_ENABLED
+ const char *pszName = pSrcInfo->offSourceName < pThis->cbSrcStrings
+ ? &pThis->pchSrcStrings[pSrcInfo->offSourceName] : "out-of-bounds.c!";
+ if (pSrcInfo->uDigestType == RTCV8SRCINFO_DIGEST_TYPE_MD5)
+ Log3((" %010zx #%03zu: %RTuuid %#x=%s\n", off, iFile, &pSrcInfo->Digest.md5, pSrcInfo->offSourceName, pszName));
+ else if (pSrcInfo->uDigestType == RTCV8SRCINFO_DIGEST_TYPE_NONE)
+ Log3((" %010zx #%03zu: <none> %#x=%s\n", off, iFile, pSrcInfo->offSourceName, pszName));
+ else
+ Log3((" %010zx #%03zu: !%#x! %#x=%s\n", off, iFile, pSrcInfo->uDigestType, pSrcInfo->offSourceName, pszName));
+#endif
+ off += pSrcInfo->uDigestType == RTCV8SRCINFO_DIGEST_TYPE_MD5 ? sizeof(*pSrcInfo) : 8;
+ iFile++;
+ }
+ }
+
+ /*
+ * Second pass, process symbols and line numbers.
+ */
+ uCursor.pv = pvSymTab;
+ cbSymTab = cbSymTabSaved;
+ for (;;)
+ {
+ RTDBGMODCV_CHECK_RET_BF(cbSymTab > sizeof(RTCV8SYMBOLSBLOCK), ("cbSymTab=%zu\n", cbSymTab));
+ PCRTCV8SYMBOLSBLOCK pBlockHdr = (PCRTCV8SYMBOLSBLOCK)uCursor.pv;
+ Log3((" %p: pass #2 uType=%#04x LB %#x\n", (uint8_t *)pBlockHdr - (uint8_t *)pvSymTab, pBlockHdr->uType, pBlockHdr->cb));
+ RTDBGMODCV_CHECK_RET_BF(pBlockHdr->cb <= cbSymTab - sizeof(RTCV8SYMBOLSBLOCK),
+ ("cb=%#u cbSymTab=%zu\n", pBlockHdr->cb, cbSymTab));
+
+ switch (pBlockHdr->uType)
+ {
+ case RTCV8SYMBLOCK_TYPE_SYMBOLS:
+ rc = rtDbgModCvSsProcessV4PlusSymTab(pThis, pBlockHdr + 1, pBlockHdr->cb, fFlags);
+ break;
+
+ case RTCV8SYMBLOCK_TYPE_SECT_LINES:
+ rc = rtDbgModCvSsProcessV8SectLines(pThis, pBlockHdr + 1, pBlockHdr->cb, fFlags);
+ break;
+
+ case RTCV8SYMBLOCK_TYPE_SRC_INFO:
+ case RTCV8SYMBLOCK_TYPE_SRC_STR:
+ break;
+ default:
+ Log(("rtDbgModCvSsProcessV8SymTab: Unknown block type %#x (LB %#x)\n", pBlockHdr->uType, pBlockHdr->cb));
+ break;
+ }
+ uint32_t cbAligned = RT_ALIGN_32(sizeof(*pBlockHdr) + pBlockHdr->cb, 4);
+ if (RT_SUCCESS(rc) && cbSymTab > cbAligned)
+ {
+ uCursor.pu8 += cbAligned;
+ cbSymTab -= cbAligned;
+ }
+ else
+ break;
+ }
+ return rc;
+}
+
+
+/** @callback_method_impl{FNDBGMODCVSUBSECTCALLBACK,
+ * Parses kCvSst_GlobalPub\, kCvSst_GlobalSym and kCvSst_StaticSym subsections\,
+ * adding symbols it finds to the container.} */
+static DECLCALLBACK(int)
+rtDbgModCvSs_GlobalPub_GlobalSym_StaticSym(PRTDBGMODCV pThis, void const *pvSubSect, size_t cbSubSect, PCRTCVDIRENT32 pDirEnt)
+{
+ PCRTCVGLOBALSYMTABHDR pHdr = (PCRTCVGLOBALSYMTABHDR)pvSubSect;
+ RT_NOREF_PV(pDirEnt);
+
+ /*
+ * Quick data validation.
+ */
+ Log2(("RTDbgModCv: %s: uSymHash=%#x uAddrHash=%#x cbSymbols=%#x cbSymHash=%#x cbAddrHash=%#x\n",
+ rtDbgModCvGetSubSectionName(pDirEnt->uSubSectType), pHdr->uSymHash,
+ pHdr->uAddrHash, pHdr->cbSymbols, pHdr->cbSymHash, pHdr->cbAddrHash));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbSubSect >= sizeof(RTCVGLOBALSYMTABHDR));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF((uint64_t)pHdr->cbSymbols + pHdr->cbSymHash + pHdr->cbAddrHash <= cbSubSect - sizeof(*pHdr));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(pHdr->uSymHash < 0x20);
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(pHdr->uAddrHash < 0x20);
+ if (!pHdr->cbSymbols)
+ return VINF_SUCCESS;
+
+ /*
+ * Parse the symbols.
+ */
+ return rtDbgModCvSsProcessV4PlusSymTab(pThis, pHdr + 1, pHdr->cbSymbols, 0);
+}
+
+
+/** @callback_method_impl{FNDBGMODCVSUBSECTCALLBACK,
+ * Parses kCvSst_Module subsection\, storing the debugging style in pThis.} */
+static DECLCALLBACK(int)
+rtDbgModCvSs_Module(PRTDBGMODCV pThis, void const *pvSubSect, size_t cbSubSect, PCRTCVDIRENT32 pDirEnt)
+{
+ RT_NOREF_PV(pDirEnt);
+
+ RTCPTRUNION uCursor;
+ uCursor.pv = pvSubSect;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbSubSect >= 2 + 2 + 2 + 2 + 0 + 1);
+ uint16_t iOverlay = *uCursor.pu16++; NOREF(iOverlay);
+ uint16_t iLib = *uCursor.pu16++; NOREF(iLib);
+ uint16_t cSegs = *uCursor.pu16++;
+ pThis->uCurStyle = *uCursor.pu16++;
+ if (pThis->uCurStyle == 0)
+ pThis->uCurStyle = RT_MAKE_U16('C', 'V');
+ pThis->uCurStyleVer = 0;
+ pThis->cbSrcInfo = 0;
+ pThis->cbSrcStrings = 0;
+ uint8_t cchName = uCursor.pu8[cSegs * 12];
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbSubSect >= 2 + 2 + 2 + 2 + cSegs * 12U + 1 + cchName);
+
+#ifdef LOG_ENABLED
+ const char *pchName = (const char *)&uCursor.pu8[cSegs * 12 + 1];
+ Log2(("RTDbgModCv: Module: iOverlay=%#x iLib=%#x cSegs=%#x Style=%c%c (%#x) %.*s\n", iOverlay, iLib, cSegs,
+ RT_BYTE1(pThis->uCurStyle), RT_BYTE2(pThis->uCurStyle), pThis->uCurStyle, cchName, pchName));
+#endif
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(pThis->uCurStyle == RT_MAKE_U16('C', 'V'));
+
+#ifdef LOG_ENABLED
+ PCRTCVMODSEGINFO32 paSegs = (PCRTCVMODSEGINFO32)uCursor.pv;
+ for (uint16_t iSeg = 0; iSeg < cSegs; iSeg++)
+ Log2((" #%02u: %04x:%08x LB %08x\n", iSeg, paSegs[iSeg].iSeg, paSegs[iSeg].off, paSegs[iSeg].cb));
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNDBGMODCVSUBSECTCALLBACK,
+ * Parses kCvSst_Symbols\, kCvSst_PublicSym and kCvSst_AlignSym subsections\,
+ * adding symbols it finds to the container.} */
+static DECLCALLBACK(int)
+rtDbgModCvSs_Symbols_PublicSym_AlignSym(PRTDBGMODCV pThis, void const *pvSubSect, size_t cbSubSect, PCRTCVDIRENT32 pDirEnt)
+{
+ RT_NOREF_PV(pDirEnt);
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(pThis->uCurStyle == RT_MAKE_U16('C', 'V'));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(cbSubSect >= 8);
+
+ uint32_t u32Signature = *(uint32_t const *)pvSubSect;
+ RTDBGMODCV_CHECK_RET_BF(u32Signature == RTCVSYMBOLS_SIGNATURE_CV4 || u32Signature == RTCVSYMBOLS_SIGNATURE_CV8,
+ ("%#x, expected %#x\n", u32Signature, RTCVSYMBOLS_SIGNATURE_CV4));
+ if (u32Signature == RTCVSYMBOLS_SIGNATURE_CV8)
+ return rtDbgModCvSsProcessV8SymTab(pThis, (uint8_t const *)pvSubSect + 4, cbSubSect - 4, 0);
+ return rtDbgModCvSsProcessV4PlusSymTab(pThis, (uint8_t const *)pvSubSect + 4, cbSubSect - 4, 0);
+}
+
+
+/** @callback_method_impl{FNDBGMODCVSUBSECTCALLBACK,
+ * Parses kCvSst_SrcModule adding line numbers it finds to the container.}
+ */
+static DECLCALLBACK(int)
+rtDbgModCvSs_SrcModule(PRTDBGMODCV pThis, void const *pvSubSect, size_t cbSubSect, PCRTCVDIRENT32 pDirEnt)
+{
+ RT_NOREF_PV(pDirEnt);
+ Log(("rtDbgModCvSs_SrcModule: uCurStyle=%#x\n%.*Rhxd\n", pThis->uCurStyle, cbSubSect, pvSubSect));
+
+ /* Check the header. */
+ PCRTCVSRCMODULE pHdr = (PCRTCVSRCMODULE)pvSubSect;
+ AssertReturn(cbSubSect >= RT_UOFFSETOF(RTCVSRCMODULE, aoffSrcFiles), VERR_CV_BAD_FORMAT);
+ size_t cbHdr = sizeof(RTCVSRCMODULE)
+ + pHdr->cFiles * sizeof(uint32_t)
+ + pHdr->cSegs * sizeof(uint32_t) * 2
+ + pHdr->cSegs * sizeof(uint16_t);
+ Log2(("RTDbgModCv: SrcModule: cFiles=%u cSegs=%u\n", pHdr->cFiles, pHdr->cFiles));
+ RTDBGMODCV_CHECK_RET_BF(cbSubSect >= cbHdr, ("cbSubSect=%#x cbHdr=%zx\n", cbSubSect, cbHdr));
+#ifdef LOG_ENABLED
+ if (LogIs2Enabled())
+ {
+ for (uint32_t i = 0; i < pHdr->cFiles; i++)
+ Log2(("RTDbgModCv: source file #%u: %#x\n", i, pHdr->aoffSrcFiles[i]));
+ PCRTCVSRCRANGE paSegRanges = (PCRTCVSRCRANGE)&pHdr->aoffSrcFiles[pHdr->cFiles];
+ uint16_t const *paidxSegs = (uint16_t const *)&paSegRanges[pHdr->cSegs];
+ for (uint32_t i = 0; i < pHdr->cSegs; i++)
+ Log2(("RTDbgModCv: seg #%u: %#010x-%#010x\n", paidxSegs[i], paSegRanges[i].offStart, paSegRanges[i].offEnd));
+ }
+#endif
+
+ /*
+ * Work over the source files.
+ */
+ for (uint32_t i = 0; i < pHdr->cFiles; i++)
+ {
+ uint32_t const offSrcFile = pHdr->aoffSrcFiles[i];
+ RTDBGMODCV_CHECK_RET_BF(cbSubSect - RT_UOFFSETOF(RTCVSRCFILE, aoffSrcLines) >= offSrcFile,
+ ("cbSubSect=%#x (- %#x) aoffSrcFiles[%u]=%#x\n",
+ cbSubSect, RT_UOFFSETOF(RTCVSRCFILE, aoffSrcLines), i, offSrcFile));
+ PCRTCVSRCFILE pSrcFile = (PCRTCVSRCFILE)((uint8_t const *)pvSubSect + offSrcFile);
+ size_t cbSrcFileHdr = RT_UOFFSETOF_DYN(RTCVSRCFILE, aoffSrcLines[pSrcFile->cSegs])
+ + sizeof(RTCVSRCRANGE) * pSrcFile->cSegs
+ + sizeof(uint8_t);
+ RTDBGMODCV_CHECK_RET_BF(cbSubSect >= offSrcFile + cbSrcFileHdr && cbSubSect > cbSrcFileHdr,
+ ("cbSubSect=%#x aoffSrcFiles[%u]=%#x cbSrcFileHdr=%#x\n", cbSubSect, offSrcFile, i, cbSrcFileHdr));
+ PCRTCVSRCRANGE paSegRanges = (PCRTCVSRCRANGE)&pSrcFile->aoffSrcLines[pSrcFile->cSegs];
+ uint8_t const *pcchName = (uint8_t const *)&paSegRanges[pSrcFile->cSegs]; /** @todo TIS NB09 docs say 16-bit length... */
+ const char *pchName = (const char *)(pcchName + 1);
+ RTDBGMODCV_CHECK_RET_BF(cbSubSect >= offSrcFile + cbSrcFileHdr + *pcchName,
+ ("cbSubSect=%#x offSrcFile=%#x cbSubSect=%#x *pcchName=%#x\n",
+ cbSubSect, offSrcFile, cbSubSect, *pcchName));
+ Log2(("RTDbgModCv: source file #%u/%#x: cSegs=%#x '%.*s'\n", i, offSrcFile, pSrcFile->cSegs, *pcchName, pchName));
+ const char *pszName = rtDbgModCvAddSanitizedStringToCache(pchName, *pcchName);
+
+ /*
+ * Work the segments this source file contributes code to.
+ */
+ for (uint32_t iSeg = 0; iSeg < pSrcFile->cSegs; iSeg++)
+ {
+ uint32_t const offSrcLine = pSrcFile->aoffSrcLines[iSeg];
+ RTDBGMODCV_CHECK_RET_BF(cbSubSect - RT_UOFFSETOF(RTCVSRCLINE, aoffLines) >= offSrcLine,
+ ("cbSubSect=%#x (- %#x) aoffSrcFiles[%u]=%#x\n",
+ cbSubSect, RT_UOFFSETOF(RTCVSRCLINE, aoffLines), iSeg, offSrcLine));
+ PCRTCVSRCLINE pSrcLine = (PCRTCVSRCLINE)((uint8_t const *)pvSubSect + offSrcLine);
+ size_t cbSrcLine = RT_UOFFSETOF_DYN(RTCVSRCLINE, aoffLines[pSrcLine->cPairs])
+ + pSrcLine->cPairs * sizeof(uint16_t);
+ RTDBGMODCV_CHECK_RET_BF(cbSubSect >= offSrcLine + cbSrcLine,
+ ("cbSubSect=%#x aoffSrcFiles[%u]=%#x cbSrcLine=%#x\n",
+ cbSubSect, iSeg, offSrcLine, cbSrcLine));
+ uint16_t const *paiLines = (uint16_t const *)&pSrcLine->aoffLines[pSrcLine->cPairs];
+ Log2(("RTDbgModCv: seg #%u, %u pairs (off %#x)\n", pSrcLine->idxSeg, pSrcLine->cPairs, offSrcLine));
+ for (uint32_t iPair = 0; iPair < pSrcLine->cPairs; iPair++)
+ {
+
+ uint32_t idxSeg = pSrcLine->idxSeg;
+ uint64_t off = pSrcLine->aoffLines[iPair];
+ int rc = rtDbgModCvAdjustSegAndOffset(pThis, &idxSeg, &off);
+ if (RT_SUCCESS(rc))
+ rc = RTDbgModLineAdd(pThis->hCnt, pszName, paiLines[iPair], idxSeg, off, NULL);
+ if (RT_SUCCESS(rc))
+ Log3(("RTDbgModCv: %#x:%#010llx %0u\n", idxSeg, off, paiLines[iPair]));
+ /* Note! Wlink produces the sstSrcModule subsections from LINNUM records, however the
+ CVGenLines() function assumes there is only one segment contributing to the
+ line numbers. So, when we do assembly that jumps between segments, it emits
+ the wrong addresses for some line numbers and we end up here, typically with
+ VERR_DBG_ADDRESS_CONFLICT. */
+ else
+ Log(( "RTDbgModCv: %#x:%#010llx %0u - rc=%Rrc!! (org: idxSeg=%#x off=%#x)\n",
+ idxSeg, off, paiLines[iPair], rc, pSrcLine->idxSeg, pSrcLine->aoffLines[iPair]));
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int rtDbgModCvLoadSegmentMap(PRTDBGMODCV pThis)
+{
+ /*
+ * Search for the segment map and segment names. They will be at the end of the directory.
+ */
+ uint32_t iSegMap = UINT32_MAX;
+ uint32_t iSegNames = UINT32_MAX;
+ uint32_t i = pThis->cDirEnts;
+ while (i-- > 0)
+ {
+ if ( pThis->paDirEnts[i].iMod != 0xffff
+ && pThis->paDirEnts[i].iMod != 0x0000)
+ break;
+ if (pThis->paDirEnts[i].uSubSectType == kCvSst_SegMap)
+ iSegMap = i;
+ else if (pThis->paDirEnts[i].uSubSectType == kCvSst_SegName)
+ iSegNames = i;
+ }
+ if (iSegMap == UINT32_MAX)
+ {
+ Log(("RTDbgModCv: No segment map present, using segment indexes as is then...\n"));
+ return VINF_SUCCESS;
+ }
+ RTDBGMODCV_CHECK_RET_BF(pThis->paDirEnts[iSegMap].cb >= sizeof(RTCVSEGMAPHDR),
+ ("Bad sstSegMap entry: cb=%#x\n", pThis->paDirEnts[iSegMap].cb));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(iSegNames == UINT32_MAX || pThis->paDirEnts[iSegNames].cb > 0);
+
+ /*
+ * Read them into memory.
+ */
+ int rc = rtDbgModCvReadAtAlloc(pThis, pThis->paDirEnts[iSegMap].off, (void **)&pThis->pSegMap,
+ pThis->paDirEnts[iSegMap].cb);
+ if (iSegNames != UINT32_MAX && RT_SUCCESS(rc))
+ {
+ pThis->cbSegNames = pThis->paDirEnts[iSegNames].cb;
+ rc = rtDbgModCvReadAtAlloc(pThis, pThis->paDirEnts[iSegNames].off, (void **)&pThis->pszzSegNames,
+ pThis->paDirEnts[iSegNames].cb);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(!pThis->pszzSegNames || !pThis->pszzSegNames[pThis->cbSegNames - 1]); /* must be terminated */
+
+ /* Use local pointers to avoid lots of indirection and typing. */
+ PCRTCVSEGMAPHDR pHdr = &pThis->pSegMap->Hdr;
+ PRTCVSEGMAPDESC paDescs = &pThis->pSegMap->aDescs[0];
+
+ /*
+ * If there are only logical segments, assume a direct mapping.
+ * PE images, like the NT4 kernel, does it like this.
+ */
+ bool const fNoGroups = pHdr->cSegs == pHdr->cLogSegs;
+
+ /*
+ * The PE image has an extra section/segment for the headers, the others
+ * doesn't. PE images doesn't have DOS frames. So, figure the image type now.
+ */
+ RTLDRFMT enmImgFmt = RTLDRFMT_INVALID;
+ if (pThis->pMod->pImgVt)
+ enmImgFmt = pThis->pMod->pImgVt->pfnGetFormat(pThis->pMod);
+
+ /*
+ * Validate and display it all.
+ */
+ Log2(("RTDbgModCv: SegMap: cSegs=%#x cLogSegs=%#x (cbSegNames=%#x)\n", pHdr->cSegs, pHdr->cLogSegs, pThis->cbSegNames));
+ RTDBGMODCV_CHECK_RET_BF(pThis->paDirEnts[iSegMap].cb >= sizeof(*pHdr) + pHdr->cSegs * sizeof(paDescs[0]),
+ ("SegMap is out of bounds: cbSubSect=%#x cSegs=%#x\n", pThis->paDirEnts[iSegMap].cb, pHdr->cSegs));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(pHdr->cSegs >= pHdr->cLogSegs);
+
+ Log2(("Logical segment descriptors: %u\n", pHdr->cLogSegs));
+
+ bool fHaveDosFrames = false;
+ for (i = 0; i < pHdr->cSegs; i++)
+ {
+ if (i == pHdr->cLogSegs)
+ Log2(("Group/Physical descriptors: %u\n", pHdr->cSegs - pHdr->cLogSegs));
+ char szFlags[16];
+ memset(szFlags, '-', sizeof(szFlags));
+ if (paDescs[i].fFlags & RTCVSEGMAPDESC_F_READ)
+ szFlags[0] = 'R';
+ if (paDescs[i].fFlags & RTCVSEGMAPDESC_F_WRITE)
+ szFlags[1] = 'W';
+ if (paDescs[i].fFlags & RTCVSEGMAPDESC_F_EXECUTE)
+ szFlags[2] = 'X';
+ if (paDescs[i].fFlags & RTCVSEGMAPDESC_F_32BIT)
+ szFlags[3] = '3', szFlags[4] = '2';
+ if (paDescs[i].fFlags & RTCVSEGMAPDESC_F_SEL)
+ szFlags[5] = 'S';
+ if (paDescs[i].fFlags & RTCVSEGMAPDESC_F_ABS)
+ szFlags[6] = 'A';
+ if (paDescs[i].fFlags & RTCVSEGMAPDESC_F_GROUP)
+ szFlags[7] = 'G';
+ szFlags[8] = '\0';
+ if (paDescs[i].fFlags & RTCVSEGMAPDESC_F_RESERVED)
+ szFlags[8] = '!', szFlags[9] = '\0';
+ Log2((" #%02u: %#010x LB %#010x flags=%#06x ovl=%#06x group=%#06x frame=%#06x iSegName=%#06x iClassName=%#06x %s\n",
+ i < pHdr->cLogSegs ? i : i - pHdr->cLogSegs, paDescs[i].off, paDescs[i].cb, paDescs[i].fFlags, paDescs[i].iOverlay,
+ paDescs[i].iGroup, paDescs[i].iFrame, paDescs[i].offSegName, paDescs[i].offClassName, szFlags));
+
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(paDescs[i].offSegName == UINT16_MAX || paDescs[i].offSegName < pThis->cbSegNames);
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(paDescs[i].offClassName == UINT16_MAX || paDescs[i].offClassName < pThis->cbSegNames);
+ const char *pszName = paDescs[i].offSegName != UINT16_MAX
+ ? pThis->pszzSegNames + paDescs[i].offSegName
+ : NULL;
+ const char *pszClass = paDescs[i].offClassName != UINT16_MAX
+ ? pThis->pszzSegNames + paDescs[i].offClassName
+ : NULL;
+ if (pszName || pszClass)
+ Log2((" pszName=%s pszClass=%s\n", pszName, pszClass));
+
+ /* Validate the group link. */
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(paDescs[i].iGroup == 0 || !(paDescs[i].fFlags & RTCVSEGMAPDESC_F_GROUP));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF( paDescs[i].iGroup == 0
+ || ( paDescs[i].iGroup >= pHdr->cLogSegs
+ && paDescs[i].iGroup < pHdr->cSegs));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF( paDescs[i].iGroup == 0
+ || (paDescs[paDescs[i].iGroup].fFlags & RTCVSEGMAPDESC_F_GROUP));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(!(paDescs[i].fFlags & RTCVSEGMAPDESC_F_GROUP) || paDescs[i].off == 0); /* assumed below */
+
+ if (fNoGroups)
+ {
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(paDescs[i].iGroup == 0);
+ if ( !fHaveDosFrames
+ && paDescs[i].iFrame != 0
+ && (paDescs[i].fFlags & (RTCVSEGMAPDESC_F_SEL | RTCVSEGMAPDESC_F_ABS))
+ && paDescs[i].iOverlay == 0
+ && enmImgFmt != RTLDRFMT_PE
+ && pThis->enmType != RTCVFILETYPE_DBG)
+ fHaveDosFrames = true; /* BIOS, only groups with frames. */
+ }
+ }
+
+ /*
+ * Further valiations based on fHaveDosFrames or not.
+ */
+ if (fNoGroups)
+ {
+ if (fHaveDosFrames)
+ for (i = 0; i < pHdr->cSegs; i++)
+ {
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(paDescs[i].iOverlay == 0);
+ RTDBGMODCV_CHECK_NOMSG_RET_BF( (paDescs[i].fFlags & (RTCVSEGMAPDESC_F_SEL | RTCVSEGMAPDESC_F_ABS))
+ == RTCVSEGMAPDESC_F_SEL
+ || (paDescs[i].fFlags & (RTCVSEGMAPDESC_F_SEL | RTCVSEGMAPDESC_F_ABS))
+ == RTCVSEGMAPDESC_F_ABS);
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(!(paDescs[i].fFlags & RTCVSEGMAPDESC_F_ABS));
+ }
+ else
+ for (i = 0; i < pHdr->cSegs; i++)
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(paDescs[i].off == 0);
+ }
+
+ /*
+ * Modify the groups index to be the loader segment index instead, also
+ * add the segments to the container if we haven't done that already.
+ */
+
+ /* Guess work: Group can be implicit if used. Observed Visual C++ v1.5,
+ omitting the CODE group. */
+ const char *pszGroup0 = NULL;
+ uint64_t cbGroup0 = 0;
+ if (!fNoGroups && !fHaveDosFrames)
+ {
+ for (i = 0; i < pHdr->cSegs; i++)
+ if ( !(paDescs[i].fFlags & (RTCVSEGMAPDESC_F_GROUP | RTCVSEGMAPDESC_F_ABS))
+ && paDescs[i].iGroup == 0)
+ {
+ if (pszGroup0 == NULL && paDescs[i].offClassName != UINT16_MAX)
+ pszGroup0 = pThis->pszzSegNames + paDescs[i].offClassName;
+ uint64_t offEnd = (uint64_t)paDescs[i].off + paDescs[i].cb;
+ if (offEnd > cbGroup0)
+ cbGroup0 = offEnd;
+ }
+ }
+
+ /* Add the segments.
+ Note! The RVAs derived from this exercise are all wrong. :-/
+ Note! We don't have an image loader, so we cannot add any fake sections. */
+ /** @todo Try see if we can figure something out from the frame value later. */
+ if (!pThis->fHaveLoadedSegments)
+ {
+ uint16_t iSeg = 0;
+ if (!fHaveDosFrames)
+ {
+ Assert(!pThis->pMod->pImgVt); Assert(pThis->enmType != RTCVFILETYPE_DBG);
+ uint64_t uRva = 0;
+ if (cbGroup0 && !fNoGroups)
+ {
+ rc = RTDbgModSegmentAdd(pThis->hCnt, 0, cbGroup0, pszGroup0 ? pszGroup0 : "Seg00", 0 /*fFlags*/, NULL);
+ uRva += cbGroup0;
+ iSeg++;
+ }
+
+ for (i = 0; RT_SUCCESS(rc) && i < pHdr->cSegs; i++)
+ if ((paDescs[i].fFlags & RTCVSEGMAPDESC_F_GROUP) || fNoGroups)
+ {
+ char szName[16];
+ char *pszName = szName;
+ if (paDescs[i].offSegName != UINT16_MAX)
+ pszName = pThis->pszzSegNames + paDescs[i].offSegName;
+ else
+ RTStrPrintf(szName, sizeof(szName), "Seg%02u", iSeg);
+ rc = RTDbgModSegmentAdd(pThis->hCnt, uRva, paDescs[i].cb, pszName, 0 /*fFlags*/, NULL);
+ uRva += paDescs[i].cb;
+ iSeg++;
+ }
+ }
+ else
+ {
+ /* The map is not sorted by RVA, very annoying, but I'm countering
+ by being lazy and slow about it. :-) Btw. this is the BIOS case. */
+ Assert(fNoGroups);
+#if 1 /** @todo need more inputs */
+
+ /* Figure image base address. */
+ uint64_t uImageBase = UINT64_MAX;
+ for (i = 0; RT_SUCCESS(rc) && i < pHdr->cSegs; i++)
+ {
+ uint64_t uAddr = (uint64_t)paDescs[i].off + ((uint32_t)paDescs[i].iFrame << 4);
+ if (uAddr < uImageBase)
+ uImageBase = uAddr;
+ }
+
+ /* Add the segments. */
+ uint64_t uMinAddr = uImageBase;
+ for (i = 0; RT_SUCCESS(rc) && i < pHdr->cSegs; i++)
+ {
+ /* Figure out the next one. */
+ uint16_t cOverlaps = 0;
+ uint16_t iBest = UINT16_MAX;
+ uint64_t uBestAddr = UINT64_MAX;
+ for (uint16_t j = 0; j < pHdr->cSegs; j++)
+ {
+ uint64_t uAddr = (uint64_t)paDescs[j].off + ((uint32_t)paDescs[j].iFrame << 4);
+ if (uAddr >= uMinAddr && uAddr < uBestAddr)
+ {
+ uBestAddr = uAddr;
+ iBest = j;
+ }
+ else if (uAddr == uBestAddr)
+ {
+ cOverlaps++;
+ if (paDescs[j].cb > paDescs[iBest].cb)
+ {
+ uBestAddr = uAddr;
+ iBest = j;
+ }
+ }
+ }
+ if (iBest == UINT16_MAX && RT_SUCCESS(rc))
+ {
+ rc = VERR_CV_IPE;
+ break;
+ }
+
+ /* Add it. */
+ char szName[16];
+ char *pszName = szName;
+ if (paDescs[iBest].offSegName != UINT16_MAX)
+ pszName = pThis->pszzSegNames + paDescs[iBest].offSegName;
+ else
+ RTStrPrintf(szName, sizeof(szName), "Seg%02u", iSeg);
+ RTDBGSEGIDX idxDbgSeg = NIL_RTDBGSEGIDX;
+ rc = RTDbgModSegmentAdd(pThis->hCnt, uBestAddr - uImageBase, paDescs[iBest].cb, pszName, 0 /*fFlags*/, &idxDbgSeg);
+ Log(("CV: %#010x LB %#010x %s uRVA=%#010x iBest=%u cOverlaps=%u [idxDbgSeg=%#x iSeg=%#x]\n",
+ uBestAddr, paDescs[iBest].cb, szName, uBestAddr - uImageBase, iBest, cOverlaps, idxDbgSeg, idxDbgSeg));
+
+ /* Update translations. */
+ paDescs[iBest].iGroup = iSeg;
+ if (cOverlaps > 0)
+ {
+ for (uint16_t j = 0; j < pHdr->cSegs; j++)
+ if ((uint64_t)paDescs[j].off + ((uint32_t)paDescs[j].iFrame << 4) == uBestAddr)
+ paDescs[iBest].iGroup = iSeg;
+ i += cOverlaps;
+ }
+
+ /* Advance. */
+ uMinAddr = uBestAddr + 1;
+ iSeg++;
+ }
+
+ pThis->fHaveDosFrames = true;
+#else
+ uint32_t iFrameFirst = UINT32_MAX;
+ uint16_t iSeg = 0;
+ uint32_t iFrameMin = 0;
+ do
+ {
+ /* Find next frame. */
+ uint32_t iFrame = UINT32_MAX;
+ for (uint16_t j = 0; j < pHdr->cSegs; j++)
+ if (paDescs[j].iFrame >= iFrameMin && paDescs[j].iFrame < iFrame)
+ iFrame = paDescs[j].iFrame;
+ if (iFrame == UINT32_MAX)
+ break;
+
+ /* Figure the frame span. */
+ uint32_t offFirst = UINT32_MAX;
+ uint64_t offEnd = 0;
+ for (uint16_t j = 0; j < pHdr->cSegs; j++)
+ if (paDescs[j].iFrame == iFrame)
+ {
+ uint64_t offThisEnd = paDescs[j].off + paDescs[j].cb;
+ if (offThisEnd > offEnd)
+ offEnd = offThisEnd;
+ if (paDescs[j].off < offFirst)
+ offFirst = paDescs[j].off;
+ }
+
+ if (offFirst < offEnd)
+ {
+ /* Add it. */
+ char szName[16];
+ RTStrPrintf(szName, sizeof(szName), "Frame_%04x", iFrame);
+ Log(("CV: %s offEnd=%#x offFirst=%#x\n", szName, offEnd, offFirst));
+ if (iFrameFirst == UINT32_MAX)
+ iFrameFirst = iFrame;
+ rc = RTDbgModSegmentAdd(pThis->hCnt, (iFrame - iFrameFirst) << 4, offEnd, szName, 0 /*fFlags*/, NULL);
+
+ /* Translation updates. */
+ for (uint16_t j = 0; j < pHdr->cSegs; j++)
+ if (paDescs[j].iFrame == iFrame)
+ {
+ paDescs[j].iGroup = iSeg;
+ paDescs[j].off = 0;
+ paDescs[j].cb = offEnd > UINT32_MAX ? UINT32_MAX : (uint32_t)offEnd;
+ }
+
+ iSeg++;
+ }
+
+ iFrameMin = iFrame + 1;
+ } while (RT_SUCCESS(rc));
+#endif
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ Log(("RTDbgModCv: %Rrc while adding segments from SegMap\n", rc));
+ return rc;
+ }
+
+ pThis->fHaveLoadedSegments = true;
+
+ /* Skip the stuff below if we have DOS frames since we did it all above. */
+ if (fHaveDosFrames)
+ return VINF_SUCCESS;
+ }
+
+ /* Pass one: Fixate the group segment indexes. */
+ uint16_t iSeg0 = enmImgFmt == RTLDRFMT_PE || pThis->enmType == RTCVFILETYPE_DBG ? 1 : 0;
+ uint16_t iSeg = iSeg0 + (cbGroup0 > 0); /** @todo probably wrong... */
+ for (i = 0; i < pHdr->cSegs; i++)
+ if (paDescs[i].fFlags & RTCVSEGMAPDESC_F_ABS)
+ paDescs[i].iGroup = (uint16_t)(RTDBGSEGIDX_ABS & UINT16_MAX);
+ else if ((paDescs[i].fFlags & RTCVSEGMAPDESC_F_GROUP) || fNoGroups)
+ paDescs[i].iGroup = iSeg++;
+
+ /* Pass two: Resolve group references in to segment indexes. */
+ Log2(("Mapped segments (both kinds):\n"));
+ for (i = 0; i < pHdr->cSegs; i++)
+ {
+ if (!fNoGroups && !(paDescs[i].fFlags & (RTCVSEGMAPDESC_F_GROUP | RTCVSEGMAPDESC_F_ABS)))
+ paDescs[i].iGroup = paDescs[i].iGroup == 0 ? iSeg0 : paDescs[paDescs[i].iGroup].iGroup;
+
+ Log2((" #%02u: %#010x LB %#010x -> %#06x (flags=%#06x ovl=%#06x frame=%#06x)\n",
+ i, paDescs[i].off, paDescs[i].cb, paDescs[i].iGroup,
+ paDescs[i].fFlags, paDescs[i].iOverlay, paDescs[i].iFrame));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{PFNRTSORTCMP,
+ * Used by rtDbgModCvLoadDirectory to sort the directory.}
+ */
+static DECLCALLBACK(int) rtDbgModCvDirEntCmp(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCVDIRENT32 pEntry1 = (PRTCVDIRENT32)pvElement1;
+ PRTCVDIRENT32 pEntry2 = (PRTCVDIRENT32)pvElement2;
+ if (pEntry1->iMod < pEntry2->iMod)
+ return -1;
+ if (pEntry1->iMod > pEntry2->iMod)
+ return 1;
+ if (pEntry1->uSubSectType < pEntry2->uSubSectType)
+ return -1;
+ if (pEntry1->uSubSectType > pEntry2->uSubSectType)
+ return 1;
+
+ RT_NOREF_PV(pvUser);
+ return 0;
+}
+
+
+/**
+ * Loads the directory into memory (RTDBGMODCV::paDirEnts and
+ * RTDBGMODCV::cDirEnts).
+ *
+ * Converting old format version into the newer format to simplifying the code
+ * using the directory.
+ *
+ *
+ * @returns IPRT status code. (May leave with paDirEnts allocated on failure.)
+ * @param pThis The CV reader instance.
+ */
+static int rtDbgModCvLoadDirectory(PRTDBGMODCV pThis)
+{
+ /*
+ * Read in the CV directory.
+ */
+ int rc;
+ if ( pThis->u32CvMagic == RTCVHDR_MAGIC_NB00
+ || pThis->u32CvMagic == RTCVHDR_MAGIC_NB02)
+ {
+ /*
+ * 16-bit type.
+ */
+ RTCVDIRHDR16 DirHdr;
+ rc = rtDbgModCvReadAt(pThis, pThis->offDir, &DirHdr, sizeof(DirHdr));
+ if (RT_SUCCESS(rc))
+ {
+ if (DirHdr.cEntries > 2 && DirHdr.cEntries < _64K - 32U)
+ {
+ pThis->cDirEnts = DirHdr.cEntries;
+ pThis->paDirEnts = (PRTCVDIRENT32)RTMemAlloc(DirHdr.cEntries * sizeof(pThis->paDirEnts[0]));
+ if (pThis->paDirEnts)
+ {
+ rc = rtDbgModCvReadAt(pThis, pThis->offDir + sizeof(DirHdr),
+ pThis->paDirEnts, DirHdr.cEntries * sizeof(RTCVDIRENT16));
+ if (RT_SUCCESS(rc))
+ {
+ /* Convert the entries (from the end). */
+ uint32_t cLeft = DirHdr.cEntries;
+ RTCVDIRENT32 volatile *pDst = pThis->paDirEnts + cLeft;
+ RTCVDIRENT16 volatile *pSrc = (RTCVDIRENT16 volatile *)pThis->paDirEnts + cLeft;
+ while (cLeft--)
+ {
+ pDst--;
+ pSrc--;
+
+ pDst->cb = pSrc->cb;
+ pDst->off = RT_MAKE_U32(pSrc->offLow, pSrc->offHigh);
+ pDst->iMod = pSrc->iMod;
+ pDst->uSubSectType = pSrc->uSubSectType;
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ Log(("Old CV directory count is out of considered valid range: %#x\n", DirHdr.cEntries));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * 32-bit type (reading too much for NB04 is no problem).
+ *
+ * Note! The watcom linker (v1.9) seems to overwrite the directory
+ * header and more under some conditions. So, if this code fails
+ * you might be so lucky as to have reproduce that issue...
+ */
+ RTCVDIRHDR32EX DirHdr;
+ rc = rtDbgModCvReadAt(pThis, pThis->offDir, &DirHdr, sizeof(DirHdr));
+ if (RT_SUCCESS(rc))
+ {
+ if ( DirHdr.Core.cbHdr != sizeof(DirHdr.Core)
+ && DirHdr.Core.cbHdr != sizeof(DirHdr))
+ {
+ Log(("Unexpected CV directory size: %#x [wlink screwup?]\n", DirHdr.Core.cbHdr));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if ( DirHdr.Core.cbHdr == sizeof(DirHdr)
+ && ( DirHdr.offNextDir != 0
+ || DirHdr.fFlags != 0) )
+ {
+ Log(("Extended CV directory headers fields are not zero: fFlags=%#x offNextDir=%#x [wlink screwup?]\n",
+ DirHdr.fFlags, DirHdr.offNextDir));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if (DirHdr.Core.cbEntry != sizeof(RTCVDIRENT32))
+ {
+ Log(("Unexpected CV directory entry size: %#x (expected %#x) [wlink screwup?]\n", DirHdr.Core.cbEntry, sizeof(RTCVDIRENT32)));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if (DirHdr.Core.cEntries < 2 || DirHdr.Core.cEntries >= _512K)
+ {
+ Log(("CV directory count is out of considered valid range: %#x [wlink screwup?]\n", DirHdr.Core.cEntries));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cDirEnts = DirHdr.Core.cEntries;
+ pThis->paDirEnts = (PRTCVDIRENT32)RTMemAlloc(DirHdr.Core.cEntries * sizeof(pThis->paDirEnts[0]));
+ if (pThis->paDirEnts)
+ rc = rtDbgModCvReadAt(pThis, pThis->offDir + DirHdr.Core.cbHdr,
+ pThis->paDirEnts, DirHdr.Core.cEntries * sizeof(RTCVDIRENT32));
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t const cbDbgInfo = pThis->cbDbgInfo;
+ uint32_t const cDirEnts = pThis->cDirEnts;
+
+ /*
+ * Just sort the directory in a way we like, no need to make
+ * complicated demands on the linker output.
+ */
+ RTSortShell(pThis->paDirEnts, cDirEnts, sizeof(pThis->paDirEnts[0]), rtDbgModCvDirEntCmp, NULL);
+
+ /*
+ * Basic info validation.
+ */
+ uint16_t cGlobalMods = 0;
+ uint16_t cNormalMods = 0;
+ uint16_t iModLast = 0;
+ Log2(("RTDbgModCv: %u (%#x) directory entries:\n", cDirEnts, cDirEnts));
+ for (uint32_t i = 0; i < cDirEnts; i++)
+ {
+ PCRTCVDIRENT32 pDirEnt = &pThis->paDirEnts[i];
+ Log2((" #%04u mod=%#06x sst=%#06x at %#010x LB %#07x %s\n",
+ i, pDirEnt->iMod, pDirEnt->uSubSectType, pDirEnt->off, pDirEnt->cb,
+ rtDbgModCvGetSubSectionName(pDirEnt->uSubSectType)));
+
+ if ( pDirEnt->off >= cbDbgInfo
+ || pDirEnt->cb >= cbDbgInfo
+ || pDirEnt->off + pDirEnt->cb > cbDbgInfo)
+ {
+ Log(("CV directory entry #%u is out of bounds: %#x LB %#x, max %#x\n", i, pDirEnt->off, pDirEnt->cb, cbDbgInfo));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if ( pDirEnt->iMod == 0
+ && pThis->u32CvMagic != RTCVHDR_MAGIC_NB04
+ && pThis->u32CvMagic != RTCVHDR_MAGIC_NB02
+ && pThis->u32CvMagic != RTCVHDR_MAGIC_NB00)
+ {
+ Log(("CV directory entry #%u uses module index 0 (uSubSectType=%#x)\n", i, pDirEnt->uSubSectType));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if (pDirEnt->iMod == 0 || pDirEnt->iMod == 0xffff)
+ cGlobalMods++;
+ else
+ {
+ if (pDirEnt->iMod > iModLast)
+ {
+ if ( pDirEnt->uSubSectType != kCvSst_Module
+ && pDirEnt->uSubSectType != kCvSst_OldModule)
+ {
+ Log(("CV directory entry #%u: expected module subsection first, found %s (%#x)\n",
+ i, rtDbgModCvGetSubSectionName(pDirEnt->uSubSectType), pDirEnt->uSubSectType));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if (pDirEnt->iMod != iModLast + 1)
+ {
+ Log(("CV directory entry #%u: skips from mod %#x to %#x modules\n", i, iModLast, pDirEnt->iMod));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ iModLast = pDirEnt->iMod;
+ }
+ cNormalMods++;
+ }
+ }
+ if (cGlobalMods == 0)
+ {
+ Log(("CV directory contains no global modules\n"));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ Log(("CV dir stats: %u total, %u normal, %u special, iModLast=%#x (%u)\n",
+ cDirEnts, cNormalMods, cGlobalMods, iModLast, iModLast));
+
+#if 0 /* skip this stuff */
+ /*
+ * Validate the directory ordering.
+ */
+ uint16_t i = 0;
+
+ /* Normal modules. */
+ if (pThis->enmDirOrder != RTCVDIRORDER_BY_SST_MOD)
+ {
+ uint16_t iEndNormalMods = cNormalMods + (pThis->enmDirOrder == RTCVDIRORDER_BY_MOD_0 ? cGlobalMods : 0);
+ while (i < iEndNormalMods)
+ {
+ if (pThis->paDirEnts[i].iMod == 0 || pThis->paDirEnts[i].iMod == 0xffff)
+ {
+ Log(("CV directory entry #%u: Unexpected global module entry.\n", i));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ i++;
+ }
+ }
+ else
+ {
+ uint32_t fSeen = RT_BIT_32(kCvSst_Module - kCvSst_Module)
+ | RT_BIT_32(kCvSst_Libraries - kCvSst_Module)
+ | RT_BIT_32(kCvSst_GlobalSym - kCvSst_Module)
+ | RT_BIT_32(kCvSst_GlobalPub - kCvSst_Module)
+ | RT_BIT_32(kCvSst_GlobalTypes - kCvSst_Module)
+ | RT_BIT_32(kCvSst_SegName - kCvSst_Module)
+ | RT_BIT_32(kCvSst_SegMap - kCvSst_Module)
+ | RT_BIT_32(kCvSst_StaticSym - kCvSst_Module)
+ | RT_BIT_32(kCvSst_FileIndex - kCvSst_Module)
+ | RT_BIT_32(kCvSst_MPC - kCvSst_Module);
+ uint16_t iMod = 0;
+ uint16_t uSst = kCvSst_Module;
+ while (i < cNormalMods)
+ {
+ PCRTCVDIRENT32 pDirEnt = &pThis->paDirEnts[i];
+ if ( pDirEnt->iMod > iMod
+ || pDirEnt->iMod == iMod) /* wlink subjected to MSVC 2010 /Z7 files with multiple .debug$S. */
+ {
+ if (pDirEnt->uSubSectType != uSst)
+ {
+ Log(("CV directory entry #%u: Expected %s (%#x), found %s (%#x).\n",
+ i, rtDbgModCvGetSubSectionName(uSst), uSst,
+ rtDbgModCvGetSubSectionName(pDirEnt->uSubSectType), pDirEnt->uSubSectType));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ }
+ else
+ {
+ uint32_t iBit = pDirEnt->uSubSectType - kCvSst_Module;
+ if (iBit >= 32U || (fSeen & RT_BIT_32(iBit)))
+ {
+ Log(("CV directory entry #%u: SST %s (%#x) has already been seen or is for globals.\n",
+ i, rtDbgModCvGetSubSectionName(pDirEnt->uSubSectType), pDirEnt->uSubSectType));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ fSeen |= RT_BIT_32(iBit);
+ }
+
+ uSst = pDirEnt->uSubSectType;
+ iMod = pDirEnt->iMod;
+ i++;
+ }
+ }
+
+ /* New style with special modules at the end. */
+ if (pThis->enmDirOrder != RTCVDIRORDER_BY_MOD_0)
+ while (i < cDirEnts)
+ {
+ if (pThis->paDirEnts[i].iMod != 0 && pThis->paDirEnts[i].iMod != 0xffff)
+ {
+ Log(("CV directory entry #%u: Expected global module entry, not %#x.\n", i,
+ pThis->paDirEnts[i].iMod));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ i++;
+ }
+#endif
+ }
+ }
+
+ return rc;
+}
+
+
+static int rtDbgModCvLoadCodeViewInfo(PRTDBGMODCV pThis)
+{
+ /*
+ * Load the directory, the segment map (if any) and then scan for segments
+ * if necessary.
+ */
+ int rc = rtDbgModCvLoadDirectory(pThis);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModCvLoadSegmentMap(pThis);
+ if (RT_SUCCESS(rc) && !pThis->fHaveLoadedSegments)
+ {
+ rc = VERR_CV_TODO; /** @todo Scan anything containing address, in particular sstSegMap and sstModule,
+ * and reconstruct the segments from that information. */
+ pThis->cbImage = 0x1000;
+ rc = VINF_SUCCESS;
+ }
+
+ /*
+ * Process the directory.
+ */
+ for (uint32_t i = 0; RT_SUCCESS(rc) && i < pThis->cDirEnts; i++)
+ {
+ PCRTCVDIRENT32 pDirEnt = &pThis->paDirEnts[i];
+ Log3(("Processing module %#06x subsection #%04u %s\n", pDirEnt->iMod, i, rtDbgModCvGetSubSectionName(pDirEnt->uSubSectType)));
+ PFNDBGMODCVSUBSECTCALLBACK pfnCallback = NULL;
+ switch (pDirEnt->uSubSectType)
+ {
+ case kCvSst_GlobalPub:
+ case kCvSst_GlobalSym:
+ case kCvSst_StaticSym:
+ pfnCallback = rtDbgModCvSs_GlobalPub_GlobalSym_StaticSym;
+ break;
+ case kCvSst_Module:
+ pfnCallback = rtDbgModCvSs_Module;
+ break;
+ case kCvSst_PublicSym:
+ case kCvSst_Symbols:
+ case kCvSst_AlignSym:
+ pfnCallback = rtDbgModCvSs_Symbols_PublicSym_AlignSym;
+ break;
+
+ case kCvSst_OldModule:
+ case kCvSst_OldPublic:
+ case kCvSst_OldTypes:
+ case kCvSst_OldSymbols:
+ case kCvSst_OldSrcLines:
+ case kCvSst_OldLibraries:
+ case kCvSst_OldImports:
+ case kCvSst_OldCompacted:
+ case kCvSst_OldSrcLnSeg:
+ case kCvSst_OldSrcLines3:
+ /** @todo implement more. */
+ break;
+
+ case kCvSst_Types:
+ case kCvSst_Public:
+ case kCvSst_SrcLnSeg:
+ /** @todo implement more. */
+ break;
+ case kCvSst_SrcModule:
+ pfnCallback = rtDbgModCvSs_SrcModule;
+ break;
+ case kCvSst_Libraries:
+ case kCvSst_GlobalTypes:
+ case kCvSst_MPC:
+ case kCvSst_PreComp:
+ case kCvSst_PreCompMap:
+ case kCvSst_OffsetMap16:
+ case kCvSst_OffsetMap32:
+ case kCvSst_FileIndex:
+
+ default:
+ /** @todo implement more. */
+ break;
+
+ /* Skip because we've already processed them: */
+ case kCvSst_SegMap:
+ case kCvSst_SegName:
+ pfnCallback = NULL;
+ break;
+ }
+
+ if (pfnCallback)
+ {
+ void *pvSubSect;
+ rc = rtDbgModCvReadAtAlloc(pThis, pDirEnt->off, &pvSubSect, pDirEnt->cb);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pfnCallback(pThis, pvSubSect, pDirEnt->cb, pDirEnt);
+ RTMemFree(pvSubSect);
+ }
+ }
+ }
+
+ /*
+ * Free temporary parsing objects.
+ */
+ if (pThis->pbSrcInfo)
+ {
+ RTMemFree(pThis->pbSrcInfo);
+ pThis->pbSrcInfo = NULL;
+ pThis->cbSrcInfo = 0;
+ pThis->cbSrcInfoAlloc = 0;
+ }
+ if (pThis->pchSrcStrings)
+ {
+ RTMemFree(pThis->pchSrcStrings);
+ pThis->pchSrcStrings = NULL;
+ pThis->cbSrcStrings = 0;
+ pThis->cbSrcStringsAlloc = 0;
+ }
+
+ return rc;
+}
+
+
+/*
+ *
+ * COFF Debug Info Parsing.
+ * COFF Debug Info Parsing.
+ * COFF Debug Info Parsing.
+ *
+ */
+
+#ifdef LOG_ENABLED
+static const char *rtDbgModCvGetCoffStorageClassName(uint8_t bStorageClass)
+{
+ switch (bStorageClass)
+ {
+ case IMAGE_SYM_CLASS_END_OF_FUNCTION: return "END_OF_FUNCTION";
+ case IMAGE_SYM_CLASS_NULL: return "NULL";
+ case IMAGE_SYM_CLASS_AUTOMATIC: return "AUTOMATIC";
+ case IMAGE_SYM_CLASS_EXTERNAL: return "EXTERNAL";
+ case IMAGE_SYM_CLASS_STATIC: return "STATIC";
+ case IMAGE_SYM_CLASS_REGISTER: return "REGISTER";
+ case IMAGE_SYM_CLASS_EXTERNAL_DEF: return "EXTERNAL_DEF";
+ case IMAGE_SYM_CLASS_LABEL: return "LABEL";
+ case IMAGE_SYM_CLASS_UNDEFINED_LABEL: return "UNDEFINED_LABEL";
+ case IMAGE_SYM_CLASS_MEMBER_OF_STRUCT: return "MEMBER_OF_STRUCT";
+ case IMAGE_SYM_CLASS_ARGUMENT: return "ARGUMENT";
+ case IMAGE_SYM_CLASS_STRUCT_TAG: return "STRUCT_TAG";
+ case IMAGE_SYM_CLASS_MEMBER_OF_UNION: return "MEMBER_OF_UNION";
+ case IMAGE_SYM_CLASS_UNION_TAG: return "UNION_TAG";
+ case IMAGE_SYM_CLASS_TYPE_DEFINITION: return "TYPE_DEFINITION";
+ case IMAGE_SYM_CLASS_UNDEFINED_STATIC: return "UNDEFINED_STATIC";
+ case IMAGE_SYM_CLASS_ENUM_TAG: return "ENUM_TAG";
+ case IMAGE_SYM_CLASS_MEMBER_OF_ENUM: return "MEMBER_OF_ENUM";
+ case IMAGE_SYM_CLASS_REGISTER_PARAM: return "REGISTER_PARAM";
+ case IMAGE_SYM_CLASS_BIT_FIELD: return "BIT_FIELD";
+ case IMAGE_SYM_CLASS_FAR_EXTERNAL: return "FAR_EXTERNAL";
+ case IMAGE_SYM_CLASS_BLOCK: return "BLOCK";
+ case IMAGE_SYM_CLASS_FUNCTION: return "FUNCTION";
+ case IMAGE_SYM_CLASS_END_OF_STRUCT: return "END_OF_STRUCT";
+ case IMAGE_SYM_CLASS_FILE: return "FILE";
+ case IMAGE_SYM_CLASS_SECTION: return "SECTION";
+ case IMAGE_SYM_CLASS_WEAK_EXTERNAL: return "WEAK_EXTERNAL";
+ case IMAGE_SYM_CLASS_CLR_TOKEN: return "CLR_TOKEN";
+ }
+
+ static char s_szName[32];
+ RTStrPrintf(s_szName, sizeof(s_szName), "Unknown%#04x", bStorageClass);
+ return s_szName;
+}
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Adds a chunk of COFF line numbers.
+ *
+ * @param pThis The COFF/CodeView reader instance.
+ * @param pszFile The source file name.
+ * @param iSection The section number.
+ * @param paLines Pointer to the first line number table entry.
+ * @param cLines The number of line number table entries to add.
+ */
+static void rtDbgModCvAddCoffLineNumbers(PRTDBGMODCV pThis, const char *pszFile, uint32_t iSection,
+ PCIMAGE_LINENUMBER paLines, uint32_t cLines)
+{
+ RT_NOREF_PV(iSection);
+ Log4(("Adding %u line numbers in section #%u for %s\n", cLines, iSection, pszFile));
+ PCIMAGE_LINENUMBER pCur = paLines;
+ while (cLines-- > 0)
+ {
+ if (pCur->Linenumber)
+ {
+ int rc = RTDbgModLineAdd(pThis->hCnt, pszFile, pCur->Linenumber, RTDBGSEGIDX_RVA, pCur->Type.VirtualAddress, NULL);
+ Log4((" %#010x: %u [%Rrc]\n", pCur->Type.VirtualAddress, pCur->Linenumber, rc)); NOREF(rc);
+ }
+ pCur++;
+ }
+}
+
+
+/**
+ * Adds a COFF symbol.
+ *
+ * @returns IPRT status (ignored)
+ * @param pThis The COFF/CodeView reader instance.
+ * @param idxSeg IPRT RVA or ABS segment index indicator.
+ * @param uValue The symbol value.
+ * @param pszName The symbol name.
+ */
+static int rtDbgModCvAddCoffSymbol(PRTDBGMODCV pThis, uint32_t idxSeg, uint32_t uValue, const char *pszName)
+{
+ int rc = RTDbgModSymbolAdd(pThis->hCnt, pszName, idxSeg, uValue, 0, 0 /*fFlags*/, NULL);
+ Log(("Symbol: %s:%08x %s [%Rrc]\n", idxSeg == RTDBGSEGIDX_RVA ? "rva" : "abs", uValue, pszName, rc));
+ if (rc == VERR_DBG_ADDRESS_CONFLICT || rc == VERR_DBG_DUPLICATE_SYMBOL)
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+/**
+ * Processes the COFF symbol table.
+ *
+ * @returns IPRT status code
+ * @param pThis The COFF/CodeView reader instance.
+ * @param paSymbols Pointer to the symbol table.
+ * @param cSymbols The number of entries in the symbol table.
+ * @param paLines Pointer to the line number table.
+ * @param cLines The number of entires in the line number table.
+ * @param pszzStrTab Pointer to the string table.
+ * @param cbStrTab Size of the string table.
+ */
+static int rtDbgModCvProcessCoffSymbolTable(PRTDBGMODCV pThis,
+ PCIMAGE_SYMBOL paSymbols, uint32_t cSymbols,
+ PCIMAGE_LINENUMBER paLines, uint32_t cLines,
+ const char *pszzStrTab, uint32_t cbStrTab)
+{
+ Log3(("Processing COFF symbol table with %#x symbols\n", cSymbols));
+
+ /*
+ * Making some bold assumption that the line numbers for the section in
+ * the file are allocated sequentially, we do multiple passes until we've
+ * gathered them all.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cSections = 1;
+ uint32_t iLineSect = 1;
+ uint32_t iLine = 0;
+ do
+ {
+ /*
+ * Process the symbols.
+ */
+ char szShort[9];
+ char szFile[RTPATH_MAX];
+ uint32_t iSymbol = 0;
+ szFile[0] = '\0';
+ szShort[8] = '\0'; /* avoid having to terminate it all the time. */
+
+ while (iSymbol < cSymbols && RT_SUCCESS(rc))
+ {
+ /* Copy the symbol in and hope it works around the misalignment
+ issues everywhere. */
+ IMAGE_SYMBOL Sym;
+ memcpy(&Sym, &paSymbols[iSymbol], sizeof(Sym));
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(Sym.NumberOfAuxSymbols < cSymbols);
+
+ /* Calc a zero terminated symbol name. */
+ const char *pszName;
+ if (Sym.N.Name.Short)
+ pszName = (const char *)memcpy(szShort, &Sym.N, 8);
+ else
+ {
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(Sym.N.Name.Long < cbStrTab);
+ pszName = pszzStrTab + Sym.N.Name.Long;
+ }
+
+ /* Only log stuff and count sections the in the first pass.*/
+ if (iLineSect == 1)
+ {
+ Log3(("%04x: s=%#06x v=%#010x t=%#06x a=%#04x c=%#04x (%s) name='%s'\n",
+ iSymbol, Sym.SectionNumber, Sym.Value, Sym.Type, Sym.NumberOfAuxSymbols,
+ Sym.StorageClass, rtDbgModCvGetCoffStorageClassName(Sym.StorageClass), pszName));
+ if ((int16_t)cSections <= Sym.SectionNumber && Sym.SectionNumber > 0)
+ cSections = Sym.SectionNumber + 1;
+ }
+
+ /*
+ * Use storage class to pick what we need (which isn't much because,
+ * MS only provides a very restricted set of symbols).
+ */
+ IMAGE_AUX_SYMBOL Aux;
+ switch (Sym.StorageClass)
+ {
+ case IMAGE_SYM_CLASS_NULL:
+ /* a NOP */
+ break;
+
+ case IMAGE_SYM_CLASS_FILE:
+ {
+ /* Change the current file name (for line numbers). Pretend
+ ANSI and ISO-8859-1 are similar enough for out purposes... */
+ RTDBGMODCV_CHECK_NOMSG_RET_BF(Sym.NumberOfAuxSymbols > 0);
+ const char *pszFile = (const char *)&paSymbols[iSymbol + 1];
+ char *pszDst = szFile;
+ rc = RTLatin1ToUtf8Ex(pszFile, Sym.NumberOfAuxSymbols * sizeof(IMAGE_SYMBOL), &pszDst, sizeof(szFile), NULL);
+ if (RT_FAILURE(rc))
+ Log(("Error converting COFF filename: %Rrc\n", rc));
+ else if (iLineSect == 1)
+ Log3((" filename='%s'\n", szFile));
+ break;
+ }
+
+ case IMAGE_SYM_CLASS_STATIC:
+ if ( Sym.NumberOfAuxSymbols == 1
+ && ( iLineSect == 1
+ || Sym.SectionNumber == (int32_t)iLineSect) )
+ {
+ memcpy(&Aux, &paSymbols[iSymbol + 1], sizeof(Aux));
+ if (iLineSect == 1)
+ Log3((" section: cb=%#010x #relocs=%#06x #lines=%#06x csum=%#x num=%#x sel=%x rvd=%u\n",
+ Aux.Section.Length, Aux.Section.NumberOfRelocations,
+ Aux.Section.NumberOfLinenumbers,
+ Aux.Section.CheckSum,
+ RT_MAKE_U32(Aux.Section.Number, Aux.Section.HighNumber),
+ Aux.Section.Selection,
+ Aux.Section.bReserved));
+ if ( Sym.SectionNumber == (int32_t)iLineSect
+ && Aux.Section.NumberOfLinenumbers > 0)
+ {
+ uint32_t cLinesToAdd = RT_MIN(Aux.Section.NumberOfLinenumbers, cLines - iLine);
+ if (iLine < cLines && szFile[0])
+ rtDbgModCvAddCoffLineNumbers(pThis, szFile, iLineSect, &paLines[iLine], cLinesToAdd);
+ iLine += cLinesToAdd;
+ }
+ }
+ /* Not so sure about the quality here, but might be useful. */
+ else if ( iLineSect == 1
+ && Sym.NumberOfAuxSymbols == 0
+ && Sym.SectionNumber != IMAGE_SYM_UNDEFINED
+ && Sym.SectionNumber != IMAGE_SYM_ABSOLUTE
+ && Sym.SectionNumber != IMAGE_SYM_DEBUG
+ && Sym.Value > 0
+ && *pszName)
+ rtDbgModCvAddCoffSymbol(pThis, RTDBGSEGIDX_RVA, Sym.Value, pszName);
+ break;
+
+ case IMAGE_SYM_CLASS_EXTERNAL:
+ /* Add functions (first pass only). */
+ if ( iLineSect == 1
+ && (ISFCN(Sym.Type) || Sym.Type == 0)
+ && Sym.NumberOfAuxSymbols == 0
+ && *pszName )
+ {
+ if (Sym.SectionNumber == IMAGE_SYM_ABSOLUTE)
+ rtDbgModCvAddCoffSymbol(pThis, RTDBGSEGIDX_ABS, Sym.Value, pszName);
+ else if ( Sym.SectionNumber != IMAGE_SYM_UNDEFINED
+ && Sym.SectionNumber != IMAGE_SYM_DEBUG)
+ rtDbgModCvAddCoffSymbol(pThis, RTDBGSEGIDX_RVA, Sym.Value, pszName);
+ }
+ break;
+
+ case IMAGE_SYM_CLASS_FUNCTION:
+ /* Not sure this is really used. */
+ break;
+
+ case IMAGE_SYM_CLASS_END_OF_FUNCTION:
+ case IMAGE_SYM_CLASS_AUTOMATIC:
+ case IMAGE_SYM_CLASS_REGISTER:
+ case IMAGE_SYM_CLASS_EXTERNAL_DEF:
+ case IMAGE_SYM_CLASS_LABEL:
+ case IMAGE_SYM_CLASS_UNDEFINED_LABEL:
+ case IMAGE_SYM_CLASS_MEMBER_OF_STRUCT:
+ case IMAGE_SYM_CLASS_ARGUMENT:
+ case IMAGE_SYM_CLASS_STRUCT_TAG:
+ case IMAGE_SYM_CLASS_MEMBER_OF_UNION:
+ case IMAGE_SYM_CLASS_UNION_TAG:
+ case IMAGE_SYM_CLASS_TYPE_DEFINITION:
+ case IMAGE_SYM_CLASS_UNDEFINED_STATIC:
+ case IMAGE_SYM_CLASS_ENUM_TAG:
+ case IMAGE_SYM_CLASS_MEMBER_OF_ENUM:
+ case IMAGE_SYM_CLASS_REGISTER_PARAM:
+ case IMAGE_SYM_CLASS_BIT_FIELD:
+ case IMAGE_SYM_CLASS_FAR_EXTERNAL:
+ case IMAGE_SYM_CLASS_BLOCK:
+ case IMAGE_SYM_CLASS_END_OF_STRUCT:
+ case IMAGE_SYM_CLASS_SECTION:
+ case IMAGE_SYM_CLASS_WEAK_EXTERNAL:
+ case IMAGE_SYM_CLASS_CLR_TOKEN:
+ /* Not used by MS, I think. */
+ break;
+
+ default:
+ Log(("RTDbgCv: Unexpected COFF storage class %#x (%u)\n", Sym.StorageClass, Sym.StorageClass));
+ break;
+ }
+
+ /* next symbol */
+ iSymbol += 1 + Sym.NumberOfAuxSymbols;
+ }
+
+ /* Next section with line numbers. */
+ iLineSect++;
+ } while (iLine < cLines && iLineSect < cSections && RT_SUCCESS(rc));
+
+ return rc;
+}
+
+
+/**
+ * Loads COFF debug information into the container.
+ *
+ * @returns IPRT status code.
+ * @param pThis The COFF/CodeView debug reader instance.
+ */
+static int rtDbgModCvLoadCoffInfo(PRTDBGMODCV pThis)
+{
+ /*
+ * Read the whole section into memory.
+ * Note! Cannot use rtDbgModCvReadAt or rtDbgModCvReadAtAlloc here.
+ */
+ int rc;
+ uint8_t *pbDbgSect = (uint8_t *)RTMemAlloc(pThis->cbCoffDbgInfo);
+ if (pbDbgSect)
+ {
+ if (pThis->hFile == NIL_RTFILE)
+ rc = pThis->pMod->pImgVt->pfnReadAt(pThis->pMod, UINT32_MAX, pThis->offCoffDbgInfo, pbDbgSect, pThis->cbCoffDbgInfo);
+ else
+ rc = RTFileReadAt(pThis->hFile, pThis->offCoffDbgInfo, pbDbgSect, pThis->cbCoffDbgInfo, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* The string table follows after the symbol table. */
+ const char *pszzStrTab = (const char *)( pbDbgSect
+ + pThis->CoffHdr.LvaToFirstSymbol
+ + pThis->CoffHdr.NumberOfSymbols * sizeof(IMAGE_SYMBOL));
+ uint32_t cbStrTab = (uint32_t)((uintptr_t)(pbDbgSect + pThis->cbCoffDbgInfo) - (uintptr_t)pszzStrTab);
+ /** @todo The symbol table starts with a size. Read it and checking. Also verify
+ * that the symtab ends with a terminator character. */
+
+ rc = rtDbgModCvProcessCoffSymbolTable(pThis,
+ (PCIMAGE_SYMBOL)(pbDbgSect + pThis->CoffHdr.LvaToFirstSymbol),
+ pThis->CoffHdr.NumberOfSymbols,
+ (PCIMAGE_LINENUMBER)(pbDbgSect + pThis->CoffHdr.LvaToFirstLinenumber),
+ pThis->CoffHdr.NumberOfLinenumbers,
+ pszzStrTab, cbStrTab);
+ }
+ RTMemFree(pbDbgSect);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+
+
+
+
+/*
+ *
+ * CodeView Debug module implementation.
+ * CodeView Debug module implementation.
+ * CodeView Debug module implementation.
+ *
+ */
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnUnwindFrame} */
+static DECLCALLBACK(int) rtDbgModCv_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ RT_NOREF(pMod, iSeg, off, pState);
+ return VERR_DBG_NO_UNWIND_INFO;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByAddr} */
+static DECLCALLBACK(int) rtDbgModCv_LineByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ PRTINTPTR poffDisp, PRTDBGLINE pLineInfo)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ return RTDbgModLineByAddr(pThis->hCnt, iSeg, off, poffDisp, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByOrdinal} */
+static DECLCALLBACK(int) rtDbgModCv_LineByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGLINE pLineInfo)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ return RTDbgModLineByOrdinal(pThis->hCnt, iOrdinal, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineCount} */
+static DECLCALLBACK(uint32_t) rtDbgModCv_LineCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ return RTDbgModLineCount(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineAdd} */
+static DECLCALLBACK(int) rtDbgModCv_LineAdd(PRTDBGMODINT pMod, const char *pszFile, size_t cchFile, uint32_t uLineNo,
+ uint32_t iSeg, RTUINTPTR off, uint32_t *piOrdinal)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ Assert(!pszFile[cchFile]); NOREF(cchFile);
+ return RTDbgModLineAdd(pThis->hCnt, pszFile, uLineNo, iSeg, off, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByAddr} */
+static DECLCALLBACK(int) rtDbgModCv_SymbolByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ return RTDbgModSymbolByAddr(pThis->hCnt, iSeg, off, fFlags, poffDisp, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByName} */
+static DECLCALLBACK(int) rtDbgModCv_SymbolByName(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); RT_NOREF_PV(cchSymbol);
+ return RTDbgModSymbolByName(pThis->hCnt, pszSymbol/*, cchSymbol*/, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByOrdinal} */
+static DECLCALLBACK(int) rtDbgModCv_SymbolByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ return RTDbgModSymbolByOrdinal(pThis->hCnt, iOrdinal, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolCount} */
+static DECLCALLBACK(uint32_t) rtDbgModCv_SymbolCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ return RTDbgModSymbolCount(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolAdd} */
+static DECLCALLBACK(int) rtDbgModCv_SymbolAdd(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ RTDBGSEGIDX iSeg, RTUINTPTR off, RTUINTPTR cb, uint32_t fFlags,
+ uint32_t *piOrdinal)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); NOREF(cchSymbol);
+ return RTDbgModSymbolAdd(pThis->hCnt, pszSymbol, iSeg, off, cb, fFlags, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentByIndex} */
+static DECLCALLBACK(int) rtDbgModCv_SegmentByIndex(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, PRTDBGSEGMENT pSegInfo)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ return RTDbgModSegmentByIndex(pThis->hCnt, iSeg, pSegInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentCount} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModCv_SegmentCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ return RTDbgModSegmentCount(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentAdd} */
+static DECLCALLBACK(int) rtDbgModCv_SegmentAdd(PRTDBGMODINT pMod, RTUINTPTR uRva, RTUINTPTR cb, const char *pszName, size_t cchName,
+ uint32_t fFlags, PRTDBGSEGIDX piSeg)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ Assert(!pszName[cchName]); NOREF(cchName);
+ return RTDbgModSegmentAdd(pThis->hCnt, uRva, cb, pszName, fFlags, piSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnImageSize} */
+static DECLCALLBACK(RTUINTPTR) rtDbgModCv_ImageSize(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ if (pThis->cbImage)
+ return pThis->cbImage;
+ return RTDbgModImageSize(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnRvaToSegOff} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModCv_RvaToSegOff(PRTDBGMODINT pMod, RTUINTPTR uRva, PRTUINTPTR poffSeg)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ return RTDbgModRvaToSegOff(pThis->hCnt, uRva, poffSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnClose} */
+static DECLCALLBACK(int) rtDbgModCv_Close(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+
+ RTDbgModRelease(pThis->hCnt);
+ if (pThis->hFile != NIL_RTFILE)
+ RTFileClose(pThis->hFile);
+ RTMemFree(pThis->paDirEnts);
+ RTMemFree(pThis);
+
+ pMod->pvDbgPriv = NULL; /* for internal use */
+ return VINF_SUCCESS;
+}
+
+
+/*
+ *
+ * Probing code used by rtDbgModCv_TryOpen.
+ * Probing code used by rtDbgModCv_TryOpen.
+ *
+ */
+
+
+
+/**
+ * @callback_method_impl{FNRTLDRENUMSEGS, Used to add segments from the image}
+ */
+static DECLCALLBACK(int) rtDbgModCvAddSegmentsCallback(RTLDRMOD hLdrMod, PCRTLDRSEG pSeg, void *pvUser)
+{
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pvUser;
+ Log(("Segment %s: LinkAddress=%#llx RVA=%#llx cb=%#llx\n",
+ pSeg->pszName, (uint64_t)pSeg->LinkAddress, (uint64_t)pSeg->RVA, pSeg->cb));
+ NOREF(hLdrMod);
+
+ /* If the segment doesn't have a mapping, just add a dummy so the indexing
+ works out correctly (same as for the image). */
+ if (pSeg->RVA == NIL_RTLDRADDR)
+ return RTDbgModSegmentAdd(pThis->hCnt, 0, 0, pSeg->pszName, 0 /*fFlags*/, NULL);
+
+ RTLDRADDR cb = RT_MAX(pSeg->cb, pSeg->cbMapped);
+ return RTDbgModSegmentAdd(pThis->hCnt, pSeg->RVA, cb, pSeg->pszName, 0 /*fFlags*/, NULL);
+}
+
+
+/**
+ * Copies the sections over from the DBG file.
+ *
+ * Called if we don't have an associated executable image.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CV module instance.
+ * @param pDbgHdr The DBG file header.
+ * @param pszFilename The filename (for logging).
+ */
+static int rtDbgModCvAddSegmentsFromDbg(PRTDBGMODCV pThis, PCIMAGE_SEPARATE_DEBUG_HEADER pDbgHdr, const char *pszFilename)
+{
+ RT_NOREF_PV(pszFilename);
+
+ /*
+ * Validate the header fields a little.
+ */
+ if ( pDbgHdr->NumberOfSections < 1
+ || pDbgHdr->NumberOfSections > 4096)
+ {
+ Log(("RTDbgModCv: Bad NumberOfSections: %d\n", pDbgHdr->NumberOfSections));
+ return VERR_CV_BAD_FORMAT;
+ }
+ if (!RT_IS_POWER_OF_TWO(pDbgHdr->SectionAlignment))
+ {
+ Log(("RTDbgModCv: Bad SectionAlignment: %#x\n", pDbgHdr->SectionAlignment));
+ return VERR_CV_BAD_FORMAT;
+ }
+
+ /*
+ * Read the section table.
+ */
+ size_t cbShs = pDbgHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER);
+ PIMAGE_SECTION_HEADER paShs = (PIMAGE_SECTION_HEADER)RTMemAlloc(cbShs);
+ if (!paShs)
+ return VERR_NO_MEMORY;
+ int rc = RTFileReadAt(pThis->hFile, sizeof(*pDbgHdr), paShs, cbShs, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do some basic validation.
+ */
+ uint32_t cbHeaders = 0;
+ uint32_t uRvaPrev = 0;
+ for (uint32_t i = 0; i < pDbgHdr->NumberOfSections; i++)
+ {
+ Log3(("RTDbgModCv: Section #%02u %#010x LB %#010x %.*s\n",
+ i, paShs[i].VirtualAddress, paShs[i].Misc.VirtualSize, sizeof(paShs[i].Name), paShs[i].Name));
+
+ if (paShs[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD)
+ continue;
+
+ if (paShs[i].VirtualAddress < uRvaPrev)
+ {
+ Log(("RTDbgModCv: %s: Overlap or soring error, VirtualAddress=%#x uRvaPrev=%#x - section #%d '%.*s'!!!\n",
+ pszFilename, paShs[i].VirtualAddress, uRvaPrev, i, sizeof(paShs[i].Name), paShs[i].Name));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ else if ( paShs[i].VirtualAddress > pDbgHdr->SizeOfImage
+ || paShs[i].Misc.VirtualSize > pDbgHdr->SizeOfImage
+ || paShs[i].VirtualAddress + paShs[i].Misc.VirtualSize > pDbgHdr->SizeOfImage)
+ {
+ Log(("RTDbgModCv: %s: VirtualAddress=%#x VirtualSize=%#x (total %x) - beyond image size (%#x) - section #%d '%.*s'!!!\n",
+ pszFilename, paShs[i].VirtualAddress, paShs[i].Misc.VirtualSize,
+ paShs[i].VirtualAddress + paShs[i].Misc.VirtualSize,
+ pThis->cbImage, i, sizeof(paShs[i].Name), paShs[i].Name));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ else if (paShs[i].VirtualAddress & (pDbgHdr->SectionAlignment - 1))
+ {
+ Log(("RTDbgModCv: %s: VirtualAddress=%#x misaligned (%#x) - section #%d '%.*s'!!!\n",
+ pszFilename, paShs[i].VirtualAddress, pDbgHdr->SectionAlignment, i, sizeof(paShs[i].Name), paShs[i].Name));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ else
+ {
+ if (uRvaPrev == 0)
+ cbHeaders = paShs[i].VirtualAddress;
+ uRvaPrev = paShs[i].VirtualAddress + paShs[i].Misc.VirtualSize;
+ continue;
+ }
+ }
+ if (RT_SUCCESS(rc) && uRvaPrev == 0)
+ {
+ Log(("RTDbgModCv: %s: No loadable sections.\n", pszFilename));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if (RT_SUCCESS(rc) && cbHeaders == 0)
+ {
+ Log(("RTDbgModCv: %s: No space for PE headers.\n", pszFilename));
+ rc = VERR_CV_BAD_FORMAT;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add sections.
+ */
+ rc = RTDbgModSegmentAdd(pThis->hCnt, 0, cbHeaders, "NtHdrs", 0 /*fFlags*/, NULL);
+ for (uint32_t i = 0; RT_SUCCESS(rc) && i < pDbgHdr->NumberOfSections; i++)
+ {
+ char szName[sizeof(paShs[i].Name) + 1];
+ memcpy(szName, paShs[i].Name, sizeof(paShs[i].Name));
+ szName[sizeof(szName) - 1] = '\0';
+
+ if (paShs[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD)
+ rc = RTDbgModSegmentAdd(pThis->hCnt, 0, 0, szName, 0 /*fFlags*/, NULL);
+ else
+ rc = RTDbgModSegmentAdd(pThis->hCnt, paShs[i].VirtualAddress, paShs[i].Misc.VirtualSize, szName,
+ 0 /*fFlags*/, NULL);
+ }
+ if (RT_SUCCESS(rc))
+ pThis->fHaveLoadedSegments = true;
+ }
+ }
+
+ RTMemFree(paShs);
+ return rc;
+}
+
+
+/**
+ * Instantiates the CV/COFF reader.
+ *
+ * @returns IPRT status code
+ * @param pDbgMod The debug module instance.
+ * @param enmFileType The type of input file.
+ * @param hFile The file handle, NIL_RTFILE of image.
+ * @param ppThis Where to return the reader instance.
+ */
+static int rtDbgModCvCreateInstance(PRTDBGMODINT pDbgMod, RTCVFILETYPE enmFileType, RTFILE hFile, PRTDBGMODCV *ppThis)
+{
+ /*
+ * Do we already have an instance? Happens if we find multiple debug
+ * formats we support.
+ */
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pDbgMod->pvDbgPriv;
+ if (pThis)
+ {
+ Assert(pThis->enmType == enmFileType);
+ Assert(pThis->hFile == hFile);
+ Assert(pThis->pMod == pDbgMod);
+ *ppThis = pThis;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Create a new instance.
+ */
+ pThis = (PRTDBGMODCV)RTMemAllocZ(sizeof(RTDBGMODCV));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+ int rc = RTDbgModCreate(&pThis->hCnt, pDbgMod->pszName, 0 /*cbSeg*/, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ pDbgMod->pvDbgPriv = pThis;
+ pThis->enmType = enmFileType;
+ pThis->hFile = hFile;
+ pThis->pMod = pDbgMod;
+ pThis->offBase = UINT32_MAX;
+ pThis->offCoffDbgInfo = UINT32_MAX;
+ *ppThis = pThis;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pThis);
+ return rc;
+}
+
+
+/**
+ * Common part of the COFF probing.
+ *
+ * @returns status code.
+ * @param pDbgMod The debug module instance. On success pvDbgPriv
+ * will point to a valid RTDBGMODCV.
+ * @param enmFileType The kind of file this is we're probing.
+ * @param hFile The file with debug info in it.
+ * @param off The offset where to expect CV debug info.
+ * @param cb The number of bytes of debug info.
+ * @param pszFilename The path to the file (for logging).
+ */
+static int rtDbgModCvProbeCoff(PRTDBGMODINT pDbgMod, RTCVFILETYPE enmFileType, RTFILE hFile,
+ uint32_t off, uint32_t cb, const char *pszFilename)
+{
+ RT_NOREF_PV(pszFilename);
+
+ /*
+ * Check that there is sufficient data for a header, then read it.
+ */
+ if (cb < sizeof(IMAGE_COFF_SYMBOLS_HEADER))
+ {
+ Log(("RTDbgModCv: Not enough room for COFF header.\n"));
+ return VERR_BAD_EXE_FORMAT;
+ }
+ if (cb >= UINT32_C(128) * _1M)
+ {
+ Log(("RTDbgModCv: COFF debug information is to large (%'u bytes), max is 128MB\n", cb));
+ return VERR_BAD_EXE_FORMAT;
+ }
+
+ int rc;
+ IMAGE_COFF_SYMBOLS_HEADER Hdr;
+ if (hFile == NIL_RTFILE)
+ rc = pDbgMod->pImgVt->pfnReadAt(pDbgMod, UINT32_MAX, off, &Hdr, sizeof(Hdr));
+ else
+ rc = RTFileReadAt(hFile, off, &Hdr, sizeof(Hdr), NULL);
+ if (RT_FAILURE(rc))
+ {
+ Log(("RTDbgModCv: Error reading COFF header: %Rrc\n", rc));
+ return rc;
+ }
+
+ Log2(("RTDbgModCv: Found COFF debug info header at %#x (LB %#x) in %s\n", off, cb, pszFilename));
+ Log2((" NumberOfSymbols = %#010x\n", Hdr.NumberOfSymbols));
+ Log2((" LvaToFirstSymbol = %#010x\n", Hdr.LvaToFirstSymbol));
+ Log2((" NumberOfLinenumbers = %#010x\n", Hdr.NumberOfLinenumbers));
+ Log2((" LvaToFirstLinenumber = %#010x\n", Hdr.LvaToFirstLinenumber));
+ Log2((" RvaToFirstByteOfCode = %#010x\n", Hdr.RvaToFirstByteOfCode));
+ Log2((" RvaToLastByteOfCode = %#010x\n", Hdr.RvaToLastByteOfCode));
+ Log2((" RvaToFirstByteOfData = %#010x\n", Hdr.RvaToFirstByteOfData));
+ Log2((" RvaToLastByteOfData = %#010x\n", Hdr.RvaToLastByteOfData));
+
+ /*
+ * Validate the COFF header.
+ */
+ if ( (uint64_t)Hdr.LvaToFirstSymbol + (uint64_t)Hdr.NumberOfSymbols * sizeof(IMAGE_SYMBOL) > cb
+ || (Hdr.LvaToFirstSymbol < sizeof(Hdr) && Hdr.NumberOfSymbols > 0))
+ {
+ Log(("RTDbgModCv: Bad COFF symbol count or/and offset: LvaToFirstSymbol=%#x, NumberOfSymbols=%#x cbCoff=%#x\n",
+ Hdr.LvaToFirstSymbol, Hdr.NumberOfSymbols, cb));
+ return VERR_BAD_EXE_FORMAT;
+ }
+ if ( (uint64_t)Hdr.LvaToFirstLinenumber + (uint64_t)Hdr.NumberOfLinenumbers * sizeof(IMAGE_LINENUMBER) > cb
+ || (Hdr.LvaToFirstLinenumber < sizeof(Hdr) && Hdr.NumberOfLinenumbers > 0))
+ {
+ Log(("RTDbgModCv: Bad COFF symbol count or/and offset: LvaToFirstSymbol=%#x, NumberOfSymbols=%#x cbCoff=%#x\n",
+ Hdr.LvaToFirstSymbol, Hdr.NumberOfSymbols, cb));
+ return VERR_BAD_EXE_FORMAT;
+ }
+ if (Hdr.NumberOfSymbols < 2)
+ {
+ Log(("RTDbgModCv: The COFF symbol table is too short to be of any worth... (%u syms)\n", Hdr.NumberOfSymbols));
+ return VERR_NO_DATA;
+ }
+
+ /*
+ * What we care about looks fine, use it.
+ */
+ PRTDBGMODCV pThis;
+ rc = rtDbgModCvCreateInstance(pDbgMod, enmFileType, hFile, &pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offCoffDbgInfo = off;
+ pThis->cbCoffDbgInfo = cb;
+ pThis->CoffHdr = Hdr;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Common part of the CodeView probing.
+ *
+ * @returns status code.
+ * @param pDbgMod The debug module instance. On success pvDbgPriv
+ * will point to a valid RTDBGMODCV.
+ * @param pCvHdr The CodeView base header.
+ * @param enmFileType The kind of file this is we're probing.
+ * @param hFile The file with debug info in it.
+ * @param off The offset where to expect CV debug info.
+ * @param cb The number of bytes of debug info.
+ * @param enmArch The desired image architecture.
+ * @param pszFilename The path to the file (for logging).
+ */
+static int rtDbgModCvProbeCommon(PRTDBGMODINT pDbgMod, PRTCVHDR pCvHdr, RTCVFILETYPE enmFileType, RTFILE hFile,
+ uint32_t off, uint32_t cb, RTLDRARCH enmArch, const char *pszFilename)
+{
+ int rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ RT_NOREF_PV(enmArch); RT_NOREF_PV(pszFilename);
+
+ /* Is a codeview format we (wish to) support? */
+ if ( pCvHdr->u32Magic == RTCVHDR_MAGIC_NB11
+ || pCvHdr->u32Magic == RTCVHDR_MAGIC_NB09
+ || pCvHdr->u32Magic == RTCVHDR_MAGIC_NB08
+ || pCvHdr->u32Magic == RTCVHDR_MAGIC_NB05
+ || pCvHdr->u32Magic == RTCVHDR_MAGIC_NB04
+ || pCvHdr->u32Magic == RTCVHDR_MAGIC_NB02
+ || pCvHdr->u32Magic == RTCVHDR_MAGIC_NB00
+ )
+ {
+ /* We're assuming it's a base header, so the offset must be within
+ the area defined by the debug info we got from the loader. */
+ if (pCvHdr->off < cb && pCvHdr->off >= sizeof(*pCvHdr))
+ {
+ Log(("RTDbgModCv: Found %c%c%c%c at %#x - size %#x, directory at %#x. file type %d\n",
+ RT_BYTE1(pCvHdr->u32Magic), RT_BYTE2(pCvHdr->u32Magic), RT_BYTE3(pCvHdr->u32Magic), RT_BYTE4(pCvHdr->u32Magic),
+ off, cb, pCvHdr->off, enmFileType));
+
+ /*
+ * Create a module instance, if not already done.
+ */
+ PRTDBGMODCV pThis;
+ rc = rtDbgModCvCreateInstance(pDbgMod, enmFileType, hFile, &pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->u32CvMagic = pCvHdr->u32Magic;
+ pThis->offBase = off;
+ pThis->cbDbgInfo = cb;
+ pThis->offDir = pCvHdr->off;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/** @callback_method_impl{FNRTLDRENUMDBG} */
+static DECLCALLBACK(int) rtDbgModCvEnumCallback(RTLDRMOD hLdrMod, PCRTLDRDBGINFO pDbgInfo, void *pvUser)
+{
+ PRTDBGMODINT pDbgMod = (PRTDBGMODINT)pvUser;
+ Assert(!pDbgMod->pvDbgPriv);
+ RT_NOREF_PV(hLdrMod);
+
+ /* Skip external files, RTDbgMod will deal with those
+ via RTDBGMODINT::pszDbgFile. */
+ if (pDbgInfo->pszExtFile)
+ return VINF_SUCCESS;
+
+ /* We only handle the codeview sections. */
+ if (pDbgInfo->enmType == RTLDRDBGINFOTYPE_CODEVIEW)
+ {
+ /* Read the specified header and check if we like it. */
+ RTCVHDR CvHdr;
+ int rc = pDbgMod->pImgVt->pfnReadAt(pDbgMod, pDbgInfo->iDbgInfo, pDbgInfo->offFile, &CvHdr, sizeof(CvHdr));
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModCvProbeCommon(pDbgMod, &CvHdr, RTCVFILETYPE_IMAGE, NIL_RTFILE, pDbgInfo->offFile, pDbgInfo->cb,
+ pDbgMod->pImgVt->pfnGetArch(pDbgMod), pDbgMod->pszImgFile);
+ }
+ else if (pDbgInfo->enmType == RTLDRDBGINFOTYPE_COFF)
+ {
+ /* Join paths with the DBG code. */
+ rtDbgModCvProbeCoff(pDbgMod, RTCVFILETYPE_IMAGE, NIL_RTFILE, pDbgInfo->offFile, pDbgInfo->cb, pDbgMod->pszImgFile);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Part two of the external file probing.
+ *
+ * @returns status code.
+ * @param pThis The debug module instance. On success pvDbgPriv
+ * will point to a valid RTDBGMODCV.
+ * @param enmFileType The kind of file this is we're probing.
+ * @param hFile The file with debug info in it.
+ * @param off The offset where to expect CV debug info.
+ * @param cb The number of bytes of debug info.
+ * @param enmArch The desired image architecture.
+ * @param pszFilename The path to the file (for logging).
+ */
+static int rtDbgModCvProbeFile2(PRTDBGMODINT pThis, RTCVFILETYPE enmFileType, RTFILE hFile, uint32_t off, uint32_t cb,
+ RTLDRARCH enmArch, const char *pszFilename)
+{
+ RTCVHDR CvHdr;
+ int rc = RTFileReadAt(hFile, off, &CvHdr, sizeof(CvHdr), NULL);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModCvProbeCommon(pThis, &CvHdr, enmFileType, hFile, off, cb, enmArch, pszFilename);
+ return rc;
+}
+
+
+/**
+ * Probes an external file for CodeView information.
+ *
+ * @returns status code.
+ * @param pDbgMod The debug module instance. On success pvDbgPriv
+ * will point to a valid RTDBGMODCV.
+ * @param pszFilename The path to the file to probe.
+ * @param enmArch The desired image architecture.
+ */
+static int rtDbgModCvProbeFile(PRTDBGMODINT pDbgMod, const char *pszFilename, RTLDRARCH enmArch)
+{
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Check for .DBG file
+ */
+ IMAGE_SEPARATE_DEBUG_HEADER DbgHdr;
+ rc = RTFileReadAt(hFile, 0, &DbgHdr, sizeof(DbgHdr), NULL);
+ if ( RT_SUCCESS(rc)
+ && DbgHdr.Signature == IMAGE_SEPARATE_DEBUG_SIGNATURE)
+ {
+ Log2(("RTDbgModCv: Found separate debug header in %s:\n", pszFilename));
+ Log2((" Flags = %#x\n", DbgHdr.Flags));
+ Log2((" Machine = %#x\n", DbgHdr.Machine));
+ Log2((" Characteristics = %#x\n", DbgHdr.Characteristics));
+ Log2((" TimeDateStamp = %#x\n", DbgHdr.TimeDateStamp));
+ Log2((" CheckSum = %#x\n", DbgHdr.CheckSum));
+ Log2((" ImageBase = %#x\n", DbgHdr.ImageBase));
+ Log2((" SizeOfImage = %#x\n", DbgHdr.SizeOfImage));
+ Log2((" NumberOfSections = %#x\n", DbgHdr.NumberOfSections));
+ Log2((" ExportedNamesSize = %#x\n", DbgHdr.ExportedNamesSize));
+ Log2((" DebugDirectorySize = %#x\n", DbgHdr.DebugDirectorySize));
+ Log2((" SectionAlignment = %#x\n", DbgHdr.SectionAlignment));
+
+ /*
+ * Match up the architecture if specified.
+ */
+ switch (enmArch)
+ {
+ case RTLDRARCH_X86_32:
+ if (DbgHdr.Machine != IMAGE_FILE_MACHINE_I386)
+ rc = VERR_LDR_ARCH_MISMATCH;
+ break;
+ case RTLDRARCH_AMD64:
+ if (DbgHdr.Machine != IMAGE_FILE_MACHINE_AMD64)
+ rc = VERR_LDR_ARCH_MISMATCH;
+ break;
+
+ default:
+ case RTLDRARCH_HOST:
+ AssertFailed();
+ case RTLDRARCH_WHATEVER:
+ break;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTFileClose(hFile);
+ return rc;
+ }
+
+ /*
+ * Probe for readable debug info in the debug directory.
+ */
+ uint32_t offDbgDir = sizeof(DbgHdr)
+ + DbgHdr.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)
+ + DbgHdr.ExportedNamesSize;
+
+ uint32_t cEntries = DbgHdr.DebugDirectorySize / sizeof(IMAGE_DEBUG_DIRECTORY);
+ for (uint32_t i = 0; i < cEntries; i++, offDbgDir += sizeof(IMAGE_DEBUG_DIRECTORY))
+ {
+ IMAGE_DEBUG_DIRECTORY DbgDir;
+ rc = RTFileReadAt(hFile, offDbgDir, &DbgDir, sizeof(DbgDir), NULL);
+ if (RT_FAILURE(rc))
+ break;
+ if (DbgDir.Type == IMAGE_DEBUG_TYPE_CODEVIEW)
+ rc = rtDbgModCvProbeFile2(pDbgMod, RTCVFILETYPE_DBG, hFile,
+ DbgDir.PointerToRawData, DbgDir.SizeOfData,
+ enmArch, pszFilename);
+ else if (DbgDir.Type == IMAGE_DEBUG_TYPE_COFF)
+ rc = rtDbgModCvProbeCoff(pDbgMod, RTCVFILETYPE_DBG, hFile,
+ DbgDir.PointerToRawData, DbgDir.SizeOfData, pszFilename);
+ }
+
+ /*
+ * If we get down here with an instance, it prooves that we've found
+ * something, regardless of any errors. Add the sections and such.
+ */
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pDbgMod->pvDbgPriv;
+ if (pThis)
+ {
+ pThis->cbImage = DbgHdr.SizeOfImage;
+ if (pDbgMod->pImgVt)
+ rc = VINF_SUCCESS;
+ else
+ {
+ rc = rtDbgModCvAddSegmentsFromDbg(pThis, &DbgHdr, pszFilename);
+ if (RT_FAILURE(rc))
+ rtDbgModCv_Close(pDbgMod);
+ }
+ return rc;
+ }
+
+ /* Failed to find CV or smth, look at the end of the file just to be sure... */
+ }
+
+ /*
+ * Look for CV tail header.
+ */
+ uint64_t cbFile;
+ rc = RTFileSeek(hFile, -(RTFOFF)sizeof(RTCVHDR), RTFILE_SEEK_END, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ cbFile += sizeof(RTCVHDR);
+ RTCVHDR CvHdr;
+ rc = RTFileRead(hFile, &CvHdr, sizeof(CvHdr), NULL);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModCvProbeFile2(pDbgMod, RTCVFILETYPE_OTHER_AT_END, hFile,
+ cbFile - CvHdr.off, CvHdr.off, enmArch, pszFilename);
+ }
+
+ if (RT_FAILURE(rc))
+ RTFileClose(hFile);
+ return rc;
+}
+
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnTryOpen} */
+static DECLCALLBACK(int) rtDbgModCv_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch)
+{
+ /*
+ * Look for debug info.
+ */
+ int rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ if (pMod->pszDbgFile)
+ rc = rtDbgModCvProbeFile(pMod, pMod->pszDbgFile, enmArch);
+
+ if (!pMod->pvDbgPriv && pMod->pImgVt)
+ {
+ int rc2 = pMod->pImgVt->pfnEnumDbgInfo(pMod, rtDbgModCvEnumCallback, pMod);
+ if (RT_FAILURE(rc2))
+ rc = rc2;
+
+ if (!pMod->pvDbgPriv)
+ {
+ /* Try the executable in case it has a NBxx tail header. */
+ rc2 = rtDbgModCvProbeFile(pMod, pMod->pszImgFile, enmArch);
+ if (RT_FAILURE(rc2) && (RT_SUCCESS(rc) || rc == VERR_DBG_NO_MATCHING_INTERPRETER))
+ rc = rc2;
+ }
+ }
+
+ PRTDBGMODCV pThis = (PRTDBGMODCV)pMod->pvDbgPriv;
+ if (!pThis)
+ return RT_SUCCESS_NP(rc) ? VERR_DBG_NO_MATCHING_INTERPRETER : rc;
+ Assert(pThis->offBase != UINT32_MAX || pThis->offCoffDbgInfo != UINT32_MAX);
+
+ /*
+ * Load the debug info.
+ */
+ if (pMod->pImgVt)
+ {
+ rc = pMod->pImgVt->pfnEnumSegments(pMod, rtDbgModCvAddSegmentsCallback, pThis);
+ pThis->fHaveLoadedSegments = true;
+ }
+ if (RT_SUCCESS(rc) && pThis->offBase != UINT32_MAX)
+ rc = rtDbgModCvLoadCodeViewInfo(pThis);
+ if (RT_SUCCESS(rc) && pThis->offCoffDbgInfo != UINT32_MAX)
+ rc = rtDbgModCvLoadCoffInfo(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ Log(("RTDbgCv: Successfully loaded debug info\n"));
+ return VINF_SUCCESS;
+ }
+
+ Log(("RTDbgCv: Debug info load error %Rrc\n", rc));
+ rtDbgModCv_Close(pMod);
+ return rc;
+}
+
+
+
+
+
+/** Virtual function table for the CodeView debug info reader. */
+DECL_HIDDEN_CONST(RTDBGMODVTDBG) const g_rtDbgModVtDbgCodeView =
+{
+ /*.u32Magic = */ RTDBGMODVTDBG_MAGIC,
+ /*.fSupports = */ RT_DBGTYPE_CODEVIEW,
+ /*.pszName = */ "codeview",
+ /*.pfnTryOpen = */ rtDbgModCv_TryOpen,
+ /*.pfnClose = */ rtDbgModCv_Close,
+
+ /*.pfnRvaToSegOff = */ rtDbgModCv_RvaToSegOff,
+ /*.pfnImageSize = */ rtDbgModCv_ImageSize,
+
+ /*.pfnSegmentAdd = */ rtDbgModCv_SegmentAdd,
+ /*.pfnSegmentCount = */ rtDbgModCv_SegmentCount,
+ /*.pfnSegmentByIndex = */ rtDbgModCv_SegmentByIndex,
+
+ /*.pfnSymbolAdd = */ rtDbgModCv_SymbolAdd,
+ /*.pfnSymbolCount = */ rtDbgModCv_SymbolCount,
+ /*.pfnSymbolByOrdinal = */ rtDbgModCv_SymbolByOrdinal,
+ /*.pfnSymbolByName = */ rtDbgModCv_SymbolByName,
+ /*.pfnSymbolByAddr = */ rtDbgModCv_SymbolByAddr,
+
+ /*.pfnLineAdd = */ rtDbgModCv_LineAdd,
+ /*.pfnLineCount = */ rtDbgModCv_LineCount,
+ /*.pfnLineByOrdinal = */ rtDbgModCv_LineByOrdinal,
+ /*.pfnLineByAddr = */ rtDbgModCv_LineByAddr,
+
+ /*.pfnUnwindFrame = */ rtDbgModCv_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTDBG_MAGIC
+};
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmodcontainer.cpp b/src/VBox/Runtime/common/dbg/dbgmodcontainer.cpp
new file mode 100644
index 00000000..cf09ce75
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmodcontainer.cpp
@@ -0,0 +1,1050 @@
+/* $Id: dbgmodcontainer.cpp $ */
+/** @file
+ * IPRT - Debug Info Container.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DBG
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/avl.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#define RTDBGMODCNT_WITH_MEM_CACHE
+#ifdef RTDBGMODCNT_WITH_MEM_CACHE
+# include <iprt/memcache.h>
+#endif
+#include <iprt/string.h>
+#include <iprt/strcache.h>
+#include "internal/dbgmod.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Symbol entry.
+ */
+typedef struct RTDBGMODCTNSYMBOL
+{
+ /** The address core. */
+ AVLRUINTPTRNODECORE AddrCore;
+ /** The name space core. */
+ RTSTRSPACECORE NameCore;
+ /** The ordinal number core. */
+ AVLU32NODECORE OrdinalCore;
+ /** The segment index. */
+ RTDBGSEGIDX iSeg;
+ /** The symbol flags. */
+ uint32_t fFlags;
+ /** The symbol size.
+ * This may be zero while the range in AddrCore indicates 0. */
+ RTUINTPTR cb;
+} RTDBGMODCTNSYMBOL;
+/** Pointer to a symbol entry in the debug info container. */
+typedef RTDBGMODCTNSYMBOL *PRTDBGMODCTNSYMBOL;
+/** Pointer to a const symbol entry in the debug info container. */
+typedef RTDBGMODCTNSYMBOL const *PCRTDBGMODCTNSYMBOL;
+
+/**
+ * Line number entry.
+ */
+typedef struct RTDBGMODCTNLINE
+{
+ /** The address core.
+ * The Key is the address of the line number. */
+ AVLUINTPTRNODECORE AddrCore;
+ /** The ordinal number core. */
+ AVLU32NODECORE OrdinalCore;
+ /** Pointer to the file name (in string cache). */
+ const char *pszFile;
+ /** The line number. */
+ uint32_t uLineNo;
+ /** The segment index. */
+ RTDBGSEGIDX iSeg;
+} RTDBGMODCTNLINE;
+/** Pointer to a line number entry. */
+typedef RTDBGMODCTNLINE *PRTDBGMODCTNLINE;
+/** Pointer to const a line number entry. */
+typedef RTDBGMODCTNLINE const *PCRTDBGMODCTNLINE;
+
+/**
+ * Segment entry.
+ */
+typedef struct RTDBGMODCTNSEGMENT
+{
+ /** The symbol address space tree. */
+ AVLRUINTPTRTREE SymAddrTree;
+ /** The line number address space tree. */
+ AVLUINTPTRTREE LineAddrTree;
+ /** The segment offset. */
+ RTUINTPTR off;
+ /** The segment size. */
+ RTUINTPTR cb;
+ /** The segment flags. */
+ uint32_t fFlags;
+ /** The segment name. */
+ const char *pszName;
+} RTDBGMODCTNSEGMENT;
+/** Pointer to a segment entry in the debug info container. */
+typedef RTDBGMODCTNSEGMENT *PRTDBGMODCTNSEGMENT;
+/** Pointer to a const segment entry in the debug info container. */
+typedef RTDBGMODCTNSEGMENT const *PCRTDBGMODCTNSEGMENT;
+
+/**
+ * Instance data.
+ */
+typedef struct RTDBGMODCTN
+{
+ /** The name space. */
+ RTSTRSPACE Names;
+ /** Tree containing any absolute addresses. */
+ AVLRUINTPTRTREE AbsAddrTree;
+ /** Tree organizing the symbols by ordinal number. */
+ AVLU32TREE SymbolOrdinalTree;
+ /** Tree organizing the line numbers by ordinal number. */
+ AVLU32TREE LineOrdinalTree;
+ /** Segment table. */
+ PRTDBGMODCTNSEGMENT paSegs;
+ /** The number of segments in the segment table. */
+ RTDBGSEGIDX cSegs;
+ /** The image size. 0 means unlimited. */
+ RTUINTPTR cb;
+ /** The next symbol ordinal. */
+ uint32_t iNextSymbolOrdinal;
+ /** The next line number ordinal. */
+ uint32_t iNextLineOrdinal;
+#ifdef RTDBGMODCNT_WITH_MEM_CACHE
+ /** Line number allocator.
+ * Using a cache is a bit overkill since we normally won't free them, but
+ * it's a construct that exists and does the job relatively efficiently. */
+ RTMEMCACHE hLineNumAllocator;
+#endif
+} RTDBGMODCTN;
+/** Pointer to instance data for the debug info container. */
+typedef RTDBGMODCTN *PRTDBGMODCTN;
+
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnUnwindFrame} */
+static DECLCALLBACK(int)
+rtDbgModContainer_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ RT_NOREF(pMod, iSeg, off, pState);
+ return VERR_DBG_NO_UNWIND_INFO;
+}
+
+
+/**
+ * Fills in a RTDBGSYMBOL structure.
+ *
+ * @returns VINF_SUCCESS.
+ * @param pMySym Our internal symbol representation.
+ * @param pExtSym The external symbol representation.
+ */
+DECLINLINE(int) rtDbgModContainerReturnSymbol(PCRTDBGMODCTNSYMBOL pMySym, PRTDBGSYMBOL pExtSym)
+{
+ pExtSym->Value = pMySym->AddrCore.Key;
+ pExtSym->offSeg = pMySym->AddrCore.Key;
+ pExtSym->iSeg = pMySym->iSeg;
+ pExtSym->fFlags = pMySym->fFlags;
+ pExtSym->cb = pMySym->cb;
+ pExtSym->iOrdinal = pMySym->OrdinalCore.Key;
+ Assert(pMySym->NameCore.cchString < sizeof(pExtSym->szName));
+ memcpy(pExtSym->szName, pMySym->NameCore.pszString, pMySym->NameCore.cchString + 1);
+ return VINF_SUCCESS;
+}
+
+
+
+/** @copydoc RTDBGMODVTDBG::pfnLineByAddr */
+static DECLCALLBACK(int) rtDbgModContainer_LineByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ PRTINTPTR poffDisp, PRTDBGLINE pLineInfo)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /*
+ * Validate the input address.
+ */
+ AssertMsgReturn(iSeg < pThis->cSegs,
+ ("iSeg=%#x cSegs=%#x\n", iSeg, pThis->cSegs),
+ VERR_DBG_INVALID_SEGMENT_INDEX);
+ AssertMsgReturn(off < pThis->paSegs[iSeg].cb,
+ ("off=%RTptr cbSeg=%RTptr\n", off, pThis->paSegs[iSeg].cb),
+ VERR_DBG_INVALID_SEGMENT_OFFSET);
+
+ /*
+ * Lookup the nearest line number with an address less or equal to the specified address.
+ */
+ PAVLUINTPTRNODECORE pAvlCore = RTAvlUIntPtrGetBestFit(&pThis->paSegs[iSeg].LineAddrTree, off, false /*fAbove*/);
+ if (!pAvlCore)
+ return pThis->iNextLineOrdinal
+ ? VERR_DBG_LINE_NOT_FOUND
+ : VERR_DBG_NO_LINE_NUMBERS;
+ PCRTDBGMODCTNLINE pMyLine = RT_FROM_MEMBER(pAvlCore, RTDBGMODCTNLINE const, AddrCore);
+ pLineInfo->Address = pMyLine->AddrCore.Key;
+ pLineInfo->offSeg = pMyLine->AddrCore.Key;
+ pLineInfo->iSeg = iSeg;
+ pLineInfo->uLineNo = pMyLine->uLineNo;
+ pLineInfo->iOrdinal = pMyLine->OrdinalCore.Key;
+ strcpy(pLineInfo->szFilename, pMyLine->pszFile);
+ if (poffDisp)
+ *poffDisp = off - pMyLine->AddrCore.Key;
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnLineByOrdinal */
+static DECLCALLBACK(int) rtDbgModContainer_LineByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGLINE pLineInfo)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /*
+ * Look it up.
+ */
+ if (iOrdinal >= pThis->iNextLineOrdinal)
+ return pThis->iNextLineOrdinal
+ ? VERR_DBG_LINE_NOT_FOUND
+ : VERR_DBG_NO_LINE_NUMBERS;
+ PAVLU32NODECORE pAvlCore = RTAvlU32Get(&pThis->LineOrdinalTree, iOrdinal);
+ AssertReturn(pAvlCore, VERR_DBG_LINE_NOT_FOUND);
+ PCRTDBGMODCTNLINE pMyLine = RT_FROM_MEMBER(pAvlCore, RTDBGMODCTNLINE const, OrdinalCore);
+ pLineInfo->Address = pMyLine->AddrCore.Key;
+ pLineInfo->offSeg = pMyLine->AddrCore.Key;
+ pLineInfo->iSeg = pMyLine->iSeg;
+ pLineInfo->uLineNo = pMyLine->uLineNo;
+ pLineInfo->iOrdinal = pMyLine->OrdinalCore.Key;
+ strcpy(pLineInfo->szFilename, pMyLine->pszFile);
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnLineCount */
+static DECLCALLBACK(uint32_t) rtDbgModContainer_LineCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /* Note! The ordinal numbers are 0-based. */
+ return pThis->iNextLineOrdinal;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnLineAdd */
+static DECLCALLBACK(int) rtDbgModContainer_LineAdd(PRTDBGMODINT pMod, const char *pszFile, size_t cchFile, uint32_t uLineNo,
+ uint32_t iSeg, RTUINTPTR off, uint32_t *piOrdinal)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /*
+ * Validate the input address.
+ */
+ AssertMsgReturn(iSeg < pThis->cSegs, ("iSeg=%#x cSegs=%#x\n", iSeg, pThis->cSegs),
+ VERR_DBG_INVALID_SEGMENT_INDEX);
+ AssertMsgReturn(off <= pThis->paSegs[iSeg].cb, ("off=%RTptr cbSeg=%RTptr\n", off, pThis->paSegs[iSeg].cb),
+ VERR_DBG_INVALID_SEGMENT_OFFSET);
+
+ /*
+ * Create a new entry.
+ */
+#ifdef RTDBGMODCNT_WITH_MEM_CACHE
+ PRTDBGMODCTNLINE pLine = (PRTDBGMODCTNLINE)RTMemCacheAlloc(pThis->hLineNumAllocator);
+#else
+ PRTDBGMODCTNLINE pLine = (PRTDBGMODCTNLINE)RTMemAllocZ(sizeof(*pLine));
+#endif
+ if (!pLine)
+ return VERR_NO_MEMORY;
+ pLine->AddrCore.Key = off;
+ pLine->OrdinalCore.Key = pThis->iNextLineOrdinal;
+ pLine->uLineNo = uLineNo;
+ pLine->iSeg = iSeg;
+ pLine->pszFile = RTStrCacheEnterN(g_hDbgModStrCache, pszFile, cchFile);
+ int rc;
+ if (pLine->pszFile)
+ {
+ if (RTAvlUIntPtrInsert(&pThis->paSegs[iSeg].LineAddrTree, &pLine->AddrCore))
+ {
+ if (RTAvlU32Insert(&pThis->LineOrdinalTree, &pLine->OrdinalCore))
+ {
+ if (piOrdinal)
+ *piOrdinal = pThis->iNextLineOrdinal;
+ pThis->iNextLineOrdinal++;
+ return VINF_SUCCESS;
+ }
+
+ rc = VERR_INTERNAL_ERROR_5;
+ RTAvlUIntPtrRemove(&pThis->paSegs[iSeg].LineAddrTree, pLine->AddrCore.Key);
+ }
+
+ /* bail out */
+ rc = VERR_DBG_ADDRESS_CONFLICT;
+ RTStrCacheRelease(g_hDbgModStrCache, pLine->pszFile);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+#ifdef RTDBGMODCNT_WITH_MEM_CACHE
+ RTMemCacheFree(pThis->hLineNumAllocator, pLine);
+#else
+ RTMemFree(pLine);
+#endif
+ return rc;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnSymbolByAddr */
+static DECLCALLBACK(int) rtDbgModContainer_SymbolByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /*
+ * Validate the input address.
+ */
+ AssertMsgReturn( iSeg == RTDBGSEGIDX_ABS
+ || iSeg < pThis->cSegs,
+ ("iSeg=%#x cSegs=%#x\n", iSeg, pThis->cSegs),
+ VERR_DBG_INVALID_SEGMENT_INDEX);
+ AssertMsgReturn( iSeg >= RTDBGSEGIDX_SPECIAL_FIRST
+ || off <= pThis->paSegs[iSeg].cb,
+ ("off=%RTptr cbSeg=%RTptr\n", off, pThis->paSegs[iSeg].cb),
+ VERR_DBG_INVALID_SEGMENT_OFFSET);
+
+ /*
+ * Lookup the nearest symbol with an address less or equal to the specified address.
+ */
+ PAVLRUINTPTRNODECORE pAvlCore = RTAvlrUIntPtrGetBestFit( iSeg == RTDBGSEGIDX_ABS
+ ? &pThis->AbsAddrTree
+ : &pThis->paSegs[iSeg].SymAddrTree,
+ off,
+ fFlags == RTDBGSYMADDR_FLAGS_GREATER_OR_EQUAL /*fAbove*/);
+ if (!pAvlCore)
+ return VERR_SYMBOL_NOT_FOUND;
+ PCRTDBGMODCTNSYMBOL pMySym = RT_FROM_MEMBER(pAvlCore, RTDBGMODCTNSYMBOL const, AddrCore);
+ if (poffDisp)
+ *poffDisp = off - pMySym->AddrCore.Key;
+ return rtDbgModContainerReturnSymbol(pMySym, pSymInfo);
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnSymbolByName */
+static DECLCALLBACK(int) rtDbgModContainer_SymbolByName(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+ NOREF(cchSymbol);
+
+ /*
+ * Look it up in the name space.
+ */
+ PRTSTRSPACECORE pStrCore = RTStrSpaceGet(&pThis->Names, pszSymbol);
+ if (!pStrCore)
+ return VERR_SYMBOL_NOT_FOUND;
+ PCRTDBGMODCTNSYMBOL pMySym = RT_FROM_MEMBER(pStrCore, RTDBGMODCTNSYMBOL const, NameCore);
+ return rtDbgModContainerReturnSymbol(pMySym, pSymInfo);
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnSymbolByOrdinal */
+static DECLCALLBACK(int) rtDbgModContainer_SymbolByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /*
+ * Look it up in the ordinal tree.
+ */
+ if (iOrdinal >= pThis->iNextSymbolOrdinal)
+ return pThis->iNextSymbolOrdinal
+ ? VERR_DBG_NO_SYMBOLS
+ : VERR_SYMBOL_NOT_FOUND;
+ PAVLU32NODECORE pAvlCore = RTAvlU32Get(&pThis->SymbolOrdinalTree, iOrdinal);
+ AssertReturn(pAvlCore, VERR_SYMBOL_NOT_FOUND);
+ PCRTDBGMODCTNSYMBOL pMySym = RT_FROM_MEMBER(pAvlCore, RTDBGMODCTNSYMBOL const, OrdinalCore);
+ return rtDbgModContainerReturnSymbol(pMySym, pSymInfo);
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnSymbolCount */
+static DECLCALLBACK(uint32_t) rtDbgModContainer_SymbolCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /* Note! The ordinal numbers are 0-based. */
+ return pThis->iNextSymbolOrdinal;
+}
+
+
+/**
+ * Worker for rtDbgModContainer_SymbolAdd that removes a symbol to resolve
+ * address conflicts.
+ *
+ * We don't shift ordinals up as that could be very expensive, instead we move
+ * the last one up to take the place of the one we're removing. Caller must
+ * take this into account.
+ *
+ * @param pThis The container.
+ * @param pAddrTree The address tree to remove from.
+ * @param pToRemove The conflicting symbol to be removed.
+ */
+static void rtDbgModContainer_SymbolReplace(PRTDBGMODCTN pThis, PAVLRUINTPTRTREE pAddrTree, PRTDBGMODCTNSYMBOL pToRemove)
+{
+ Log(("rtDbgModContainer_SymbolReplace: pToRemove=%p ordinal=%u %04x:%08RX64 %s\n",
+ pToRemove, pToRemove->OrdinalCore.Key, pToRemove->iSeg, pToRemove->AddrCore.Key, pToRemove->NameCore.pszString));
+
+ /* Unlink it. */
+ PRTSTRSPACECORE pRemovedName = RTStrSpaceRemove(&pThis->Names, pToRemove->NameCore.pszString);
+ Assert(pRemovedName); RT_NOREF_PV(pRemovedName);
+ pToRemove->NameCore.pszString = NULL;
+
+ PAVLRUINTPTRNODECORE pRemovedAddr = RTAvlrUIntPtrRemove(pAddrTree, pToRemove->AddrCore.Key);
+ Assert(pRemovedAddr); RT_NOREF_PV(pRemovedAddr);
+ pToRemove->AddrCore.Key = 0;
+ pToRemove->AddrCore.KeyLast = 0;
+
+ uint32_t const iOrdinal = pToRemove->OrdinalCore.Key;
+ PAVLU32NODECORE pRemovedOrdinal = RTAvlU32Remove(&pThis->SymbolOrdinalTree, iOrdinal);
+ Assert(pRemovedOrdinal); RT_NOREF_PV(pRemovedOrdinal);
+
+ RTMemFree(pToRemove);
+
+ /* Jump the last symbol ordinal to take its place, unless pToRemove is the last one. */
+ if (iOrdinal >= pThis->iNextSymbolOrdinal - 1)
+ pThis->iNextSymbolOrdinal -= 1;
+ else
+ {
+ PAVLU32NODECORE pLastOrdinal = RTAvlU32Remove(&pThis->SymbolOrdinalTree, pThis->iNextSymbolOrdinal - 1);
+ AssertReturnVoid(pLastOrdinal);
+
+ pThis->iNextSymbolOrdinal -= 1;
+ pLastOrdinal->Key = iOrdinal;
+ bool fInsert = RTAvlU32Insert(&pThis->SymbolOrdinalTree, pLastOrdinal);
+ Assert(fInsert); RT_NOREF_PV(fInsert);
+ }
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnSymbolAdd */
+static DECLCALLBACK(int) rtDbgModContainer_SymbolAdd(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ RTDBGSEGIDX iSeg, RTUINTPTR off, RTUINTPTR cb, uint32_t fFlags,
+ uint32_t *piOrdinal)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /*
+ * Address validation. The other arguments have already been validated.
+ */
+ AssertMsgReturn( iSeg == RTDBGSEGIDX_ABS
+ || iSeg < pThis->cSegs,
+ ("iSeg=%#x cSegs=%#x\n", iSeg, pThis->cSegs),
+ VERR_DBG_INVALID_SEGMENT_INDEX);
+ AssertMsgReturn( iSeg >= RTDBGSEGIDX_SPECIAL_FIRST
+ || off <= pThis->paSegs[iSeg].cb,
+ ("off=%RTptr cb=%RTptr cbSeg=%RTptr\n", off, cb, pThis->paSegs[iSeg].cb),
+ VERR_DBG_INVALID_SEGMENT_OFFSET);
+
+ /* Be a little relaxed wrt to the symbol size. */
+ int rc = VINF_SUCCESS;
+ if (iSeg != RTDBGSEGIDX_ABS && off + cb > pThis->paSegs[iSeg].cb)
+ {
+ cb = pThis->paSegs[iSeg].cb - off;
+ rc = VINF_DBG_ADJUSTED_SYM_SIZE;
+ }
+
+ /*
+ * Create a new entry.
+ */
+ PRTDBGMODCTNSYMBOL pSymbol = (PRTDBGMODCTNSYMBOL)RTMemAllocZ(sizeof(*pSymbol));
+ if (!pSymbol)
+ return VERR_NO_MEMORY;
+
+ pSymbol->AddrCore.Key = off;
+ pSymbol->AddrCore.KeyLast = off + (cb ? cb - 1 : 0);
+ pSymbol->OrdinalCore.Key = pThis->iNextSymbolOrdinal;
+ pSymbol->iSeg = iSeg;
+ pSymbol->cb = cb;
+ pSymbol->fFlags = fFlags;
+ pSymbol->NameCore.pszString = RTStrCacheEnterN(g_hDbgModStrCache, pszSymbol, cchSymbol);
+ if (pSymbol->NameCore.pszString)
+ {
+ if (RTStrSpaceInsert(&pThis->Names, &pSymbol->NameCore))
+ {
+ PAVLRUINTPTRTREE pAddrTree = iSeg == RTDBGSEGIDX_ABS
+ ? &pThis->AbsAddrTree
+ : &pThis->paSegs[iSeg].SymAddrTree;
+ if (RTAvlrUIntPtrInsert(pAddrTree, &pSymbol->AddrCore))
+ {
+ if (RTAvlU32Insert(&pThis->SymbolOrdinalTree, &pSymbol->OrdinalCore))
+ {
+ /*
+ * Success.
+ */
+ if (piOrdinal)
+ *piOrdinal = pThis->iNextSymbolOrdinal;
+ Log12(("rtDbgModContainer_SymbolAdd: ordinal=%u %04x:%08RX64 LB %#RX64 %s\n",
+ pThis->iNextSymbolOrdinal, iSeg, off, cb, pSymbol->NameCore.pszString));
+ pThis->iNextSymbolOrdinal++;
+ return rc;
+ }
+
+ /* bail out */
+ rc = VERR_INTERNAL_ERROR_5;
+ RTAvlrUIntPtrRemove(pAddrTree, pSymbol->AddrCore.Key);
+ }
+ /*
+ * Did the caller specify a conflict resolution method?
+ */
+ else if (fFlags & ( RTDBGSYMBOLADD_F_REPLACE_SAME_ADDR
+ | RTDBGSYMBOLADD_F_REPLACE_ANY
+ | RTDBGSYMBOLADD_F_ADJUST_SIZES_ON_CONFLICT))
+ {
+ /*
+ * Handle anything at or before the start address first:
+ */
+ AssertCompileMemberOffset(RTDBGMODCTNSYMBOL, AddrCore, 0);
+ PRTDBGMODCTNSYMBOL pConflict = (PRTDBGMODCTNSYMBOL)RTAvlrUIntPtrRangeGet(pAddrTree, pSymbol->AddrCore.Key);
+ if (pConflict)
+ {
+ if (pConflict->AddrCore.Key == pSymbol->AddrCore.Key)
+ {
+ /* Same address, only option is replacing it. */
+ if (fFlags & (RTDBGSYMBOLADD_F_REPLACE_SAME_ADDR | RTDBGSYMBOLADD_F_REPLACE_ANY))
+ rtDbgModContainer_SymbolReplace(pThis, pAddrTree, pConflict);
+ else
+ rc = VERR_DBG_ADDRESS_CONFLICT;
+ }
+ else if (fFlags & RTDBGSYMBOLADD_F_ADJUST_SIZES_ON_CONFLICT)
+ {
+ /* Reduce the size of the symbol before us, adopting the size if we've got none. */
+ Assert(pConflict->AddrCore.Key < pSymbol->AddrCore.Key);
+ if (!pSymbol->cb)
+ {
+ pSymbol->AddrCore.KeyLast = pSymbol->AddrCore.KeyLast;
+ pSymbol->cb = pSymbol->AddrCore.KeyLast - pConflict->AddrCore.Key + 1;
+ rc = VINF_DBG_ADJUSTED_SYM_SIZE;
+ }
+ pConflict->AddrCore.KeyLast = pSymbol->AddrCore.Key - 1;
+ pConflict->cb = pSymbol->AddrCore.Key - pConflict->AddrCore.Key;
+ }
+ else if (fFlags & RTDBGSYMBOLADD_F_REPLACE_ANY)
+ rtDbgModContainer_SymbolReplace(pThis, pAddrTree, pConflict);
+ else
+ rc = VERR_DBG_ADDRESS_CONFLICT;
+ }
+
+ /*
+ * Try insert again and deal with symbols in the range.
+ */
+ while (RT_SUCCESS(rc))
+ {
+ if (RTAvlrUIntPtrInsert(pAddrTree, &pSymbol->AddrCore))
+ {
+ pSymbol->OrdinalCore.Key = pThis->iNextSymbolOrdinal;
+ if (RTAvlU32Insert(&pThis->SymbolOrdinalTree, &pSymbol->OrdinalCore))
+ {
+ /*
+ * Success.
+ */
+ if (piOrdinal)
+ *piOrdinal = pThis->iNextSymbolOrdinal;
+ pThis->iNextSymbolOrdinal++;
+ Log12(("rtDbgModContainer_SymbolAdd: ordinal=%u %04x:%08RX64 LB %#RX64 %s [replace codepath]\n",
+ pThis->iNextSymbolOrdinal, iSeg, off, cb, pSymbol->NameCore.pszString));
+ return rc;
+ }
+
+ rc = VERR_INTERNAL_ERROR_5;
+ RTAvlrUIntPtrRemove(pAddrTree, pSymbol->AddrCore.Key);
+ break;
+ }
+
+ /* Get the first symbol above us and see if we can do anything about it (or ourselves). */
+ AssertCompileMemberOffset(RTDBGMODCTNSYMBOL, AddrCore, 0);
+ pConflict = (PRTDBGMODCTNSYMBOL)RTAvlrUIntPtrGetBestFit(pAddrTree, pSymbol->AddrCore.Key, true /*fAbove*/);
+ AssertBreakStmt(pConflict, rc = VERR_DBG_ADDRESS_CONFLICT);
+ Assert(pSymbol->AddrCore.Key != pConflict->AddrCore.Key);
+ Assert(pSymbol->AddrCore.KeyLast >= pConflict->AddrCore.Key);
+
+ if (fFlags & RTDBGSYMBOLADD_F_ADJUST_SIZES_ON_CONFLICT)
+ {
+ Assert(pSymbol->cb > 0);
+ pSymbol->AddrCore.Key = pConflict->AddrCore.Key - 1;
+ pSymbol->cb = pConflict->AddrCore.Key - pSymbol->AddrCore.Key;
+ rc = VINF_DBG_ADJUSTED_SYM_SIZE;
+ }
+ else if (fFlags & RTDBGSYMBOLADD_F_REPLACE_ANY)
+ rtDbgModContainer_SymbolReplace(pThis, pAddrTree, pConflict);
+ else
+ rc = VERR_DBG_ADDRESS_CONFLICT;
+ }
+ }
+ else
+ rc = VERR_DBG_ADDRESS_CONFLICT;
+ RTStrSpaceRemove(&pThis->Names, pSymbol->NameCore.pszString);
+ }
+ else
+ rc = VERR_DBG_DUPLICATE_SYMBOL;
+ RTStrCacheRelease(g_hDbgModStrCache, pSymbol->NameCore.pszString);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ RTMemFree(pSymbol);
+ return rc;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnSegmentByIndex */
+static DECLCALLBACK(int) rtDbgModContainer_SegmentByIndex(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, PRTDBGSEGMENT pSegInfo)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+ if (iSeg >= pThis->cSegs)
+ return VERR_DBG_INVALID_SEGMENT_INDEX;
+ pSegInfo->Address = RTUINTPTR_MAX;
+ pSegInfo->uRva = pThis->paSegs[iSeg].off;
+ pSegInfo->cb = pThis->paSegs[iSeg].cb;
+ pSegInfo->fFlags = pThis->paSegs[iSeg].fFlags;
+ pSegInfo->iSeg = iSeg;
+ strcpy(pSegInfo->szName, pThis->paSegs[iSeg].pszName);
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnSegmentCount */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModContainer_SegmentCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+ return pThis->cSegs;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnSegmentAdd */
+static DECLCALLBACK(int) rtDbgModContainer_SegmentAdd(PRTDBGMODINT pMod, RTUINTPTR uRva, RTUINTPTR cb, const char *pszName, size_t cchName,
+ uint32_t fFlags, PRTDBGSEGIDX piSeg)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /*
+ * Input validation (the bits the caller cannot do).
+ */
+ /* Overlapping segments are not yet supported. Will use flags to deal with it if it becomes necessary. */
+ RTUINTPTR uRvaLast = uRva + RT_MAX(cb, 1) - 1;
+ RTUINTPTR uRvaLastMax = uRvaLast;
+ RTDBGSEGIDX iSeg = pThis->cSegs;
+ while (iSeg-- > 0)
+ {
+ RTUINTPTR uCurRva = pThis->paSegs[iSeg].off;
+ RTUINTPTR uCurRvaLast = uCurRva + RT_MAX(pThis->paSegs[iSeg].cb, 1) - 1;
+ if ( uRva <= uCurRvaLast
+ && uRvaLast >= uCurRva
+ && ( /* HACK ALERT! Allow empty segments to share space (bios/watcom, elf). */
+ (cb != 0 && pThis->paSegs[iSeg].cb != 0)
+ || ( cb == 0
+ && uRva != uCurRva
+ && uRva != uCurRvaLast)
+ || ( pThis->paSegs[iSeg].cb == 0
+ && uCurRva != uRva
+ && uCurRva != uRvaLast)
+ )
+ )
+ AssertMsgFailedReturn(("uRva=%RTptr uRvaLast=%RTptr (cb=%RTptr) \"%s\";\n"
+ "uRva=%RTptr uRvaLast=%RTptr (cb=%RTptr) \"%s\" iSeg=%#x\n",
+ uRva, uRvaLast, cb, pszName,
+ uCurRva, uCurRvaLast, pThis->paSegs[iSeg].cb, pThis->paSegs[iSeg].pszName, iSeg),
+ VERR_DBG_SEGMENT_INDEX_CONFLICT);
+ if (uRvaLastMax < uCurRvaLast)
+ uRvaLastMax = uCurRvaLast;
+ }
+ /* Strict ordered segment addition at the moment. */
+ iSeg = pThis->cSegs;
+ AssertMsgReturn(!piSeg || *piSeg == NIL_RTDBGSEGIDX || *piSeg == iSeg,
+ ("iSeg=%#x *piSeg=%#x\n", iSeg, *piSeg),
+ VERR_DBG_INVALID_SEGMENT_INDEX);
+
+ /*
+ * Add an entry to the segment table, extending it if necessary.
+ */
+ if (!(iSeg % 8))
+ {
+ void *pvSegs = RTMemRealloc(pThis->paSegs, sizeof(RTDBGMODCTNSEGMENT) * (iSeg + 8));
+ if (!pvSegs)
+ return VERR_NO_MEMORY;
+ pThis->paSegs = (PRTDBGMODCTNSEGMENT)pvSegs;
+ }
+
+ pThis->paSegs[iSeg].SymAddrTree = NULL;
+ pThis->paSegs[iSeg].LineAddrTree = NULL;
+ pThis->paSegs[iSeg].off = uRva;
+ pThis->paSegs[iSeg].cb = cb;
+ pThis->paSegs[iSeg].fFlags = fFlags;
+ pThis->paSegs[iSeg].pszName = RTStrCacheEnterN(g_hDbgModStrCache, pszName, cchName);
+ if (pThis->paSegs[iSeg].pszName)
+ {
+ if (piSeg)
+ *piSeg = iSeg;
+ pThis->cSegs++;
+ pThis->cb = uRvaLastMax + 1;
+ if (!pThis->cb)
+ pThis->cb = RTUINTPTR_MAX;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnImageSize */
+static DECLCALLBACK(RTUINTPTR) rtDbgModContainer_ImageSize(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+ return pThis->cb;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnRvaToSegOff */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModContainer_RvaToSegOff(PRTDBGMODINT pMod, RTUINTPTR uRva, PRTUINTPTR poffSeg)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+ PCRTDBGMODCTNSEGMENT paSeg = pThis->paSegs;
+ uint32_t const cSegs = pThis->cSegs;
+#if 0
+ if (cSegs <= 7)
+#endif
+ {
+ /*
+ * Linear search.
+ */
+ for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
+ {
+ RTUINTPTR offSeg = uRva - paSeg[iSeg].off;
+ if (offSeg < paSeg[iSeg].cb)
+ {
+ if (poffSeg)
+ *poffSeg = offSeg;
+ return iSeg;
+ }
+ }
+ }
+#if 0 /** @todo binary search doesn't work if we've got empty segments in the list */
+ else
+ {
+ /*
+ * Binary search.
+ */
+ uint32_t iFirst = 0;
+ uint32_t iLast = cSegs - 1;
+ for (;;)
+ {
+ uint32_t iSeg = iFirst + (iLast - iFirst) / 2;
+ RTUINTPTR offSeg = uRva - paSeg[iSeg].off;
+ if (offSeg < paSeg[iSeg].cb)
+ {
+#if 0 /* Enable if we change the above test. */
+ if (offSeg == 0 && paSeg[iSeg].cb == 0)
+ while ( iSeg > 0
+ && paSeg[iSeg - 1].cb == 0
+ && paSeg[iSeg - 1].off == uRva)
+ iSeg--;
+#endif
+
+ if (poffSeg)
+ *poffSeg = offSeg;
+ return iSeg;
+ }
+
+ /* advance */
+ if (uRva < paSeg[iSeg].off)
+ {
+ /* between iFirst and iSeg. */
+ if (iSeg == iFirst)
+ break;
+ iLast = iSeg - 1;
+ }
+ else
+ {
+ /* between iSeg and iLast. paSeg[iSeg].cb == 0 ends up here too. */
+ if (iSeg == iLast)
+ break;
+ iFirst = iSeg + 1;
+ }
+ }
+ }
+#endif
+
+ /* Invalid. */
+ return NIL_RTDBGSEGIDX;
+}
+
+
+/** Destroy a line number node. */
+static DECLCALLBACK(int) rtDbgModContainer_DestroyTreeLineNode(PAVLU32NODECORE pNode, void *pvUser)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pvUser;
+ PRTDBGMODCTNLINE pLine = RT_FROM_MEMBER(pNode, RTDBGMODCTNLINE, OrdinalCore);
+ RTStrCacheRelease(g_hDbgModStrCache, pLine->pszFile);
+ pLine->pszFile = NULL;
+#ifdef RTDBGMODCNT_WITH_MEM_CACHE
+ RTMemCacheFree(pThis->hLineNumAllocator, pLine);
+#else
+ RTMemFree(pLine); NOREF(pThis);
+#endif
+ return 0;
+}
+
+
+/** Destroy a symbol node. */
+static DECLCALLBACK(int) rtDbgModContainer_DestroyTreeNode(PAVLRUINTPTRNODECORE pNode, void *pvUser)
+{
+ PRTDBGMODCTNSYMBOL pSym = RT_FROM_MEMBER(pNode, RTDBGMODCTNSYMBOL, AddrCore);
+ RTStrCacheRelease(g_hDbgModStrCache, pSym->NameCore.pszString);
+ pSym->NameCore.pszString = NULL;
+
+#if 0
+ //PRTDBGMODCTN pThis = (PRTDBGMODCTN)pvUser;
+ //PAVLU32NODECORE pRemoved = RTAvlU32Remove(&pThis->SymbolOrdinalTree, pSym->OrdinalCore.Key);
+ //Assert(pRemoved == &pSym->OrdinalCore); RT_NOREF_PV(pRemoved);
+#else
+ RT_NOREF_PV(pvUser);
+#endif
+
+ RTMemFree(pSym);
+ return 0;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnClose */
+static DECLCALLBACK(int) rtDbgModContainer_Close(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ /*
+ * Destroy the symbols and instance data.
+ */
+ for (uint32_t iSeg = 0; iSeg < pThis->cSegs; iSeg++)
+ {
+ RTAvlrUIntPtrDestroy(&pThis->paSegs[iSeg].SymAddrTree, rtDbgModContainer_DestroyTreeNode, pThis);
+ RTStrCacheRelease(g_hDbgModStrCache, pThis->paSegs[iSeg].pszName);
+ pThis->paSegs[iSeg].pszName = NULL;
+ }
+
+ RTAvlrUIntPtrDestroy(&pThis->AbsAddrTree, rtDbgModContainer_DestroyTreeNode, pThis);
+
+ pThis->Names = NULL;
+
+#ifdef RTDBGMODCNT_WITH_MEM_CACHE
+ RTMemCacheDestroy(pThis->hLineNumAllocator);
+ pThis->hLineNumAllocator = NIL_RTMEMCACHE;
+#else
+ RTAvlU32Destroy(&pThis->LineOrdinalTree, rtDbgModContainer_DestroyTreeLineNode, pThis);
+#endif
+
+ RTMemFree(pThis->paSegs);
+ pThis->paSegs = NULL;
+
+ RTMemFree(pThis);
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTDBGMODVTDBG::pfnTryOpen */
+static DECLCALLBACK(int) rtDbgModContainer_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch)
+{
+ NOREF(pMod); NOREF(enmArch);
+ return VERR_INTERNAL_ERROR_5;
+}
+
+
+
+/** Virtual function table for the debug info container. */
+DECL_HIDDEN_CONST(RTDBGMODVTDBG) const g_rtDbgModVtDbgContainer =
+{
+ /*.u32Magic = */ RTDBGMODVTDBG_MAGIC,
+ /*.fSupports = */ 0, /* (Don't call my TryOpen, please.) */
+ /*.pszName = */ "container",
+ /*.pfnTryOpen = */ rtDbgModContainer_TryOpen,
+ /*.pfnClose = */ rtDbgModContainer_Close,
+
+ /*.pfnRvaToSegOff = */ rtDbgModContainer_RvaToSegOff,
+ /*.pfnImageSize = */ rtDbgModContainer_ImageSize,
+
+ /*.pfnSegmentAdd = */ rtDbgModContainer_SegmentAdd,
+ /*.pfnSegmentCount = */ rtDbgModContainer_SegmentCount,
+ /*.pfnSegmentByIndex = */ rtDbgModContainer_SegmentByIndex,
+
+ /*.pfnSymbolAdd = */ rtDbgModContainer_SymbolAdd,
+ /*.pfnSymbolCount = */ rtDbgModContainer_SymbolCount,
+ /*.pfnSymbolByOrdinal = */ rtDbgModContainer_SymbolByOrdinal,
+ /*.pfnSymbolByName = */ rtDbgModContainer_SymbolByName,
+ /*.pfnSymbolByAddr = */ rtDbgModContainer_SymbolByAddr,
+
+ /*.pfnLineAdd = */ rtDbgModContainer_LineAdd,
+ /*.pfnLineCount = */ rtDbgModContainer_LineCount,
+ /*.pfnLineByOrdinal = */ rtDbgModContainer_LineByOrdinal,
+ /*.pfnLineByAddr = */ rtDbgModContainer_LineByAddr,
+
+ /*.pfnUnwindFrame = */ rtDbgModContainer_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTDBG_MAGIC
+};
+
+
+
+/**
+ * Special container operation for removing all symbols.
+ *
+ * @returns IPRT status code.
+ * @param pMod The module instance.
+ */
+DECLHIDDEN(int) rtDbgModContainer_SymbolRemoveAll(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ for (uint32_t iSeg = 0; iSeg < pThis->cSegs; iSeg++)
+ {
+ RTAvlrUIntPtrDestroy(&pThis->paSegs[iSeg].SymAddrTree, rtDbgModContainer_DestroyTreeNode, NULL);
+ Assert(pThis->paSegs[iSeg].SymAddrTree == NULL);
+ }
+
+ RTAvlrUIntPtrDestroy(&pThis->AbsAddrTree, rtDbgModContainer_DestroyTreeNode, NULL);
+ Assert(pThis->AbsAddrTree == NULL);
+
+ pThis->Names = NULL;
+ pThis->iNextSymbolOrdinal = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Special container operation for removing all line numbers.
+ *
+ * @returns IPRT status code.
+ * @param pMod The module instance.
+ */
+DECLHIDDEN(int) rtDbgModContainer_LineRemoveAll(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ for (uint32_t iSeg = 0; iSeg < pThis->cSegs; iSeg++)
+ pThis->paSegs[iSeg].LineAddrTree = NULL;
+
+ RTAvlU32Destroy(&pThis->LineOrdinalTree, rtDbgModContainer_DestroyTreeLineNode, pThis);
+ Assert(pThis->LineOrdinalTree == NULL);
+
+ pThis->iNextLineOrdinal = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Special container operation for removing everything.
+ *
+ * @returns IPRT status code.
+ * @param pMod The module instance.
+ */
+DECLHIDDEN(int) rtDbgModContainer_RemoveAll(PRTDBGMODINT pMod)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)pMod->pvDbgPriv;
+
+ rtDbgModContainer_LineRemoveAll(pMod);
+ rtDbgModContainer_SymbolRemoveAll(pMod);
+
+ for (uint32_t iSeg = 0; iSeg < pThis->cSegs; iSeg++)
+ {
+ RTStrCacheRelease(g_hDbgModStrCache, pThis->paSegs[iSeg].pszName);
+ pThis->paSegs[iSeg].pszName = NULL;
+ }
+
+ pThis->cSegs = 0;
+ pThis->cb = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Creates a generic debug info container and associates it with the module.
+ *
+ * @returns IPRT status code.
+ * @param pMod The module instance.
+ * @param cbSeg The size of the initial segment. 0 if segments are to be
+ * created manually later on.
+ */
+DECLHIDDEN(int) rtDbgModContainerCreate(PRTDBGMODINT pMod, RTUINTPTR cbSeg)
+{
+ PRTDBGMODCTN pThis = (PRTDBGMODCTN)RTMemAlloc(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ pThis->Names = NULL;
+ pThis->AbsAddrTree = NULL;
+ pThis->SymbolOrdinalTree = NULL;
+ pThis->LineOrdinalTree = NULL;
+ pThis->paSegs = NULL;
+ pThis->cSegs = 0;
+ pThis->cb = 0;
+ pThis->iNextSymbolOrdinal = 0;
+ pThis->iNextLineOrdinal = 0;
+
+ pMod->pDbgVt = &g_rtDbgModVtDbgContainer;
+ pMod->pvDbgPriv = pThis;
+
+#ifdef RTDBGMODCNT_WITH_MEM_CACHE
+ int rc = RTMemCacheCreate(&pThis->hLineNumAllocator, sizeof(RTDBGMODCTNLINE), sizeof(void *), UINT32_MAX,
+ NULL /*pfnCtor*/, NULL /*pfnDtor*/, NULL /*pvUser*/, 0 /*fFlags*/);
+#else
+ int rc = VINF_SUCCESS;
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add the initial segment.
+ */
+ if (cbSeg)
+ rc = rtDbgModContainer_SegmentAdd(pMod, 0, cbSeg, "default", sizeof("default") - 1, 0, NULL);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+#ifdef RTDBGMODCNT_WITH_MEM_CACHE
+ RTMemCacheDestroy(pThis->hLineNumAllocator);
+#endif
+ }
+
+ RTMemFree(pThis);
+ pMod->pDbgVt = NULL;
+ pMod->pvDbgPriv = NULL;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmoddbghelp.cpp b/src/VBox/Runtime/common/dbg/dbgmoddbghelp.cpp
new file mode 100644
index 00000000..831bce8c
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmoddbghelp.cpp
@@ -0,0 +1,537 @@
+/* $Id: dbgmoddbghelp.cpp $ */
+/** @file
+ * IPRT - Debug Info Reader Using DbgHelp.dll if Present.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DBG
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include "internal/dbgmod.h"
+
+#include <iprt/win/windows.h>
+#include <iprt/win/dbghelp.h>
+#include <iprt/win/lazy-dbghelp.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** For passing arguments to DbgHelp.dll callback. */
+typedef struct RTDBGMODBGHELPARGS
+{
+ RTDBGMOD hCnt;
+ PRTDBGMODINT pMod;
+ uint64_t uModAddr;
+ RTLDRADDR uNextRva;
+
+ /** UTF-8 version of the previous file name. */
+ char *pszPrev;
+ /** Copy of the previous file name. */
+ PRTUTF16 pwszPrev;
+ /** Number of bytes pwszPrev points to. */
+ size_t cbPrevUtf16Alloc;
+} RTDBGMODBGHELPARGS;
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnUnwindFrame} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ RT_NOREF(pMod, iSeg, off, pState);
+ return VERR_DBG_NO_UNWIND_INFO;
+}
+
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByAddr} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_LineByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ PRTINTPTR poffDisp, PRTDBGLINE pLineInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModLineByAddr(hCnt, iSeg, off, poffDisp, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByOrdinal} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_LineByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGLINE pLineInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModLineByOrdinal(hCnt, iOrdinal, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineCount} */
+static DECLCALLBACK(uint32_t) rtDbgModDbgHelp_LineCount(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModLineCount(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineAdd} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_LineAdd(PRTDBGMODINT pMod, const char *pszFile, size_t cchFile, uint32_t uLineNo,
+ uint32_t iSeg, RTUINTPTR off, uint32_t *piOrdinal)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszFile[cchFile]); NOREF(cchFile);
+ return RTDbgModLineAdd(hCnt, pszFile, uLineNo, iSeg, off, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByAddr} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_SymbolByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSymbolByAddr(hCnt, iSeg, off, fFlags, poffDisp, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByName} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_SymbolByName(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ PRTDBGSYMBOL pSymInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); RT_NOREF_PV(cchSymbol);
+ return RTDbgModSymbolByName(hCnt, pszSymbol/*, cchSymbol*/, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByOrdinal} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_SymbolByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGSYMBOL pSymInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSymbolByOrdinal(hCnt, iOrdinal, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolCount} */
+static DECLCALLBACK(uint32_t) rtDbgModDbgHelp_SymbolCount(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSymbolCount(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolAdd} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_SymbolAdd(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ RTDBGSEGIDX iSeg, RTUINTPTR off, RTUINTPTR cb, uint32_t fFlags,
+ uint32_t *piOrdinal)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); NOREF(cchSymbol);
+ return RTDbgModSymbolAdd(hCnt, pszSymbol, iSeg, off, cb, fFlags, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentByIndex} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_SegmentByIndex(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, PRTDBGSEGMENT pSegInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSegmentByIndex(hCnt, iSeg, pSegInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentCount} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModDbgHelp_SegmentCount(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSegmentCount(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentAdd} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_SegmentAdd(PRTDBGMODINT pMod, RTUINTPTR uRva, RTUINTPTR cb, const char *pszName, size_t cchName,
+ uint32_t fFlags, PRTDBGSEGIDX piSeg)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszName[cchName]); NOREF(cchName);
+ return RTDbgModSegmentAdd(hCnt, uRva, cb, pszName, fFlags, piSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnImageSize} */
+static DECLCALLBACK(RTUINTPTR) rtDbgModDbgHelp_ImageSize(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ RTUINTPTR cb1 = RTDbgModImageSize(hCnt);
+ RTUINTPTR cb2 = pMod->pImgVt->pfnImageSize(pMod);
+ return RT_MAX(cb1, cb2);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnRvaToSegOff} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModDbgHelp_RvaToSegOff(PRTDBGMODINT pMod, RTUINTPTR uRva, PRTUINTPTR poffSeg)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModRvaToSegOff(hCnt, uRva, poffSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnClose} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_Close(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;;
+ RTDbgModRelease(hCnt);
+ pMod->pvDbgPriv = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * SymEnumLinesW callback that adds a line number to the container.
+ *
+ * @returns TRUE, FALSE if we're out of memory.
+ * @param pLineInfo Line number information.
+ * @param pvUser Pointer to a RTDBGMODBGHELPARGS structure.
+ */
+static BOOL CALLBACK rtDbgModDbgHelpCopyLineNumberCallback(PSRCCODEINFOW pLineInfo, PVOID pvUser)
+{
+ RTDBGMODBGHELPARGS *pArgs = (RTDBGMODBGHELPARGS *)pvUser;
+
+ if (pLineInfo->Address < pArgs->uModAddr)
+ {
+ Log((" %#018RX64 %05u %s [SKIPPED - INVALID ADDRESS!]\n", pLineInfo->Address, pLineInfo->LineNumber));
+ return TRUE;
+ }
+
+ /*
+ * To save having to call RTUtf16ToUtf8 every time, we keep a copy of the
+ * previous file name both as UTF-8 and UTF-16.
+ */
+ /** @todo we could combine RTUtf16Len and memcmp... */
+ size_t cbLen = (RTUtf16Len(pLineInfo->FileName) + 1) * sizeof(RTUTF16);
+ if ( !pArgs->pwszPrev
+ || pArgs->cbPrevUtf16Alloc < cbLen
+ || memcmp(pArgs->pwszPrev, pLineInfo->FileName, cbLen) )
+ {
+ if (pArgs->cbPrevUtf16Alloc >= cbLen)
+ memcpy(pArgs->pwszPrev, pLineInfo->FileName, cbLen);
+ else
+ {
+ RTMemFree(pArgs->pwszPrev);
+ pArgs->cbPrevUtf16Alloc = cbLen;
+ pArgs->pwszPrev = (PRTUTF16)RTMemDupEx(pLineInfo->FileName, cbLen, pArgs->cbPrevUtf16Alloc - cbLen);
+ if (!pArgs->pwszPrev)
+ pArgs->cbPrevUtf16Alloc = 0;
+ }
+
+ RTStrFree(pArgs->pszPrev);
+ pArgs->pszPrev = NULL;
+ int rc = RTUtf16ToUtf8(pLineInfo->FileName, &pArgs->pszPrev);
+ if (RT_FAILURE(rc))
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ Log(("rtDbgModDbgHelpCopyLineNumberCallback: Out of memory\n"));
+ return FALSE;
+ }
+ }
+
+ /*
+ * Add the line number to the container.
+ */
+ int rc = RTDbgModLineAdd(pArgs->hCnt, pArgs->pszPrev, pLineInfo->LineNumber,
+ RTDBGSEGIDX_RVA, pLineInfo->Address - pArgs->uModAddr, NULL);
+ Log((" %#018RX64 %05u %s [%Rrc]\n", pLineInfo->Address, pLineInfo->LineNumber, pArgs->pszPrev, rc));
+ NOREF(rc);
+
+ return TRUE;
+}
+
+
+/**
+ * Copies the line numbers into the container.
+ *
+ * @returns IPRT status code.
+ * @param pMod The debug module.
+ * @param hCnt The container that will keep the symbols.
+ * @param hFake The fake process handle.
+ * @param uModAddr The module load address.
+ */
+static int rtDbgModDbgHelpCopyLineNumbers(PRTDBGMODINT pMod, RTDBGMOD hCnt, HANDLE hFake, uint64_t uModAddr)
+{
+ RTDBGMODBGHELPARGS Args;
+ Args.hCnt = hCnt;
+ Args.pMod = pMod;
+ Args.uModAddr = uModAddr;
+ Args.pszPrev = NULL;
+ Args.pwszPrev = NULL;
+ Args.cbPrevUtf16Alloc = 0;
+
+ int rc;
+ if (SymEnumLinesW(hFake, uModAddr, NULL /*pszObj*/, NULL /*pszFile*/, rtDbgModDbgHelpCopyLineNumberCallback, &Args))
+ rc = VINF_SUCCESS;
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ Log(("Line number enum: %Rrc (%u)\n", rc, GetLastError()));
+ if (rc == VERR_NOT_SUPPORTED)
+ rc = VINF_SUCCESS;
+ }
+
+ RTStrFree(Args.pszPrev);
+ RTMemFree(Args.pwszPrev);
+ return rc;
+}
+
+
+/**
+ * SymEnumSymbols callback that adds a symbol to the container.
+ *
+ * @returns TRUE
+ * @param pSymInfo The symbol information.
+ * @param cbSymbol The symbol size (estimated).
+ * @param pvUser Pointer to a RTDBGMODBGHELPARGS structure.
+ */
+static BOOL CALLBACK rtDbgModDbgHelpCopySymbolsCallback(PSYMBOL_INFO pSymInfo, ULONG cbSymbol, PVOID pvUser)
+{
+ RTDBGMODBGHELPARGS *pArgs = (RTDBGMODBGHELPARGS *)pvUser;
+ if (pSymInfo->Address < pArgs->uModAddr) /* NT4 SP1 ntfs.dbg */
+ {
+ Log((" %#018RX64 LB %#07x %s [SKIPPED - INVALID ADDRESS!]\n", pSymInfo->Address, cbSymbol, pSymInfo->Name));
+ return TRUE;
+ }
+ if (pSymInfo->NameLen >= RTDBG_SYMBOL_NAME_LENGTH)
+ {
+ Log((" %#018RX64 LB %#07x %s [SKIPPED - TOO LONG (%u > %u)!]\n", pSymInfo->Address, cbSymbol, pSymInfo->Name,
+ pSymInfo->NameLen, RTDBG_SYMBOL_NAME_LENGTH));
+ return TRUE;
+ }
+
+ /* ASSUMES the symbol name is ASCII. */
+ int rc = RTDbgModSymbolAdd(pArgs->hCnt, pSymInfo->Name, RTDBGSEGIDX_RVA,
+ pSymInfo->Address - pArgs->uModAddr, cbSymbol, 0, NULL);
+ Log((" %#018RX64 LB %#07x %s [%Rrc]\n", pSymInfo->Address, cbSymbol, pSymInfo->Name, rc));
+ NOREF(rc);
+
+ return TRUE;
+}
+
+
+/**
+ * Copies the symbols into the container.
+ *
+ * @returns IPRT status code.
+ * @param pMod The debug module.
+ * @param hCnt The container that will keep the symbols.
+ * @param hFake The fake process handle.
+ * @param uModAddr The module load address.
+ */
+static int rtDbgModDbgHelpCopySymbols(PRTDBGMODINT pMod, RTDBGMOD hCnt, HANDLE hFake, uint64_t uModAddr)
+{
+ RTDBGMODBGHELPARGS Args;
+ Args.hCnt = hCnt;
+ Args.pMod = pMod;
+ Args.uModAddr = uModAddr;
+ int rc;
+ if (SymEnumSymbols(hFake, uModAddr, NULL, rtDbgModDbgHelpCopySymbolsCallback, &Args))
+ rc = VINF_SUCCESS;
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ Log(("SymEnumSymbols: %Rrc (%u)\n", rc, GetLastError()));
+ }
+ return rc;
+}
+
+
+/** @callback_method_impl{FNRTLDRENUMSEGS,
+ * Copies the PE segments over into the container.} */
+static DECLCALLBACK(int) rtDbgModDbgHelpAddSegmentsCallback(RTLDRMOD hLdrMod, PCRTLDRSEG pSeg, void *pvUser)
+{
+ RTDBGMODBGHELPARGS *pArgs = (RTDBGMODBGHELPARGS *)pvUser;
+ RT_NOREF_PV(hLdrMod);
+
+ Log(("Segment %.*s: LinkAddress=%#llx RVA=%#llx cb=%#llx\n",
+ pSeg->cchName, pSeg->pszName, (uint64_t)pSeg->LinkAddress, (uint64_t)pSeg->RVA, pSeg->cb));
+
+ Assert(pSeg->cchName > 0);
+ Assert(!pSeg->pszName[pSeg->cchName]);
+
+ RTLDRADDR cb = RT_MAX(pSeg->cb, pSeg->cbMapped);
+ RTLDRADDR uRva = pSeg->RVA;
+ if (!uRva)
+ pArgs->uModAddr = pSeg->LinkAddress;
+ else if (uRva == NIL_RTLDRADDR)
+ {
+ cb = 0;
+ uRva = pArgs->uNextRva;
+ }
+ pArgs->uNextRva = uRva + cb;
+
+ return RTDbgModSegmentAdd(pArgs->hCnt, uRva, cb, pSeg->pszName, 0 /*fFlags*/, NULL);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnTryOpen} */
+static DECLCALLBACK(int) rtDbgModDbgHelp_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch)
+{
+ NOREF(enmArch);
+
+ /*
+ * Currently only support external files with a executable already present.
+ */
+ if (!pMod->pszDbgFile)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+ if (!pMod->pImgVt)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+
+ /*
+ * Create a container for copying the information into. We do this early
+ * so we can determine the image base address.
+ */
+ RTDBGMOD hCnt;
+ int rc = RTDbgModCreate(&hCnt, pMod->pszName, 0 /*cbSeg*/, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ RTDBGMODBGHELPARGS Args;
+ RT_ZERO(Args);
+ Args.hCnt = hCnt;
+ rc = pMod->pImgVt->pfnEnumSegments(pMod, rtDbgModDbgHelpAddSegmentsCallback, &Args);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbImage = pMod->pImgVt->pfnImageSize(pMod);
+ uint64_t uImageBase = Args.uModAddr ? Args.uModAddr : 0x4000000;
+
+ /*
+ * Try load the module into an empty address space.
+ */
+ static uint32_t volatile s_uFakeHandle = 0x3940000;
+ HANDLE hFake;
+ do
+ hFake = (HANDLE)(uintptr_t)ASMAtomicIncU32(&s_uFakeHandle);
+ while (hFake == NULL || hFake == INVALID_HANDLE_VALUE);
+
+ LogFlow(("rtDbgModDbgHelp_TryOpen: \n"));
+ if (SymInitialize(hFake, NULL /*SearchPath*/, FALSE /*fInvalidProcess*/))
+ {
+ SymSetOptions(SYMOPT_LOAD_LINES | SymGetOptions());
+
+ PRTUTF16 pwszDbgFile;
+ rc = RTStrToUtf16(pMod->pszDbgFile, &pwszDbgFile);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t uModAddr = SymLoadModuleExW(hFake, NULL /*hFile*/, pwszDbgFile, NULL /*pszModName*/,
+ uImageBase, cbImage, NULL /*pModData*/, 0 /*fFlags*/);
+ if (uModAddr != 0)
+ {
+ rc = rtDbgModDbgHelpCopySymbols(pMod, hCnt, hFake, uModAddr);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModDbgHelpCopyLineNumbers(pMod, hCnt, hFake, uModAddr);
+ if (RT_SUCCESS(rc))
+ {
+ pMod->pvDbgPriv = hCnt;
+ pMod->pDbgVt = &g_rtDbgModVtDbgDbgHelp;
+ hCnt = NIL_RTDBGMOD;
+ LogFlow(("rtDbgModDbgHelp_TryOpen: Successfully loaded '%s' at %#llx\n",
+ pMod->pszDbgFile, (uint64_t)uImageBase));
+ }
+
+ SymUnloadModule64(hFake, uModAddr);
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ if (RT_SUCCESS_NP(rc))
+ rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ LogFlow(("rtDbgModDbgHelp_TryOpen: Error loading the module '%s' at %#llx: %Rrc (%u)\n",
+ pMod->pszDbgFile, (uint64_t)uImageBase, rc, GetLastError()));
+ }
+ RTUtf16Free(pwszDbgFile);
+ }
+ else
+ LogFlow(("rtDbgModDbgHelp_TryOpen: Unicode version issue: %Rrc\n", rc));
+
+ BOOL fRc2 = SymCleanup(hFake); Assert(fRc2); NOREF(fRc2);
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ if (RT_SUCCESS_NP(rc))
+ rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ LogFlow(("rtDbgModDbgHelp_TryOpen: SymInitialize failed: %Rrc (%u)\n", rc, GetLastError()));
+ }
+ }
+ RTDbgModRelease(hCnt);
+ }
+ return rc;
+}
+
+
+
+/** Virtual function table for the DBGHELP debug info reader. */
+DECL_HIDDEN_CONST(RTDBGMODVTDBG) const g_rtDbgModVtDbgDbgHelp =
+{
+ /*.u32Magic = */ RTDBGMODVTDBG_MAGIC,
+ /*.fSupports = */ RT_DBGTYPE_CODEVIEW,
+ /*.pszName = */ "dbghelp",
+ /*.pfnTryOpen = */ rtDbgModDbgHelp_TryOpen,
+ /*.pfnClose = */ rtDbgModDbgHelp_Close,
+
+ /*.pfnRvaToSegOff = */ rtDbgModDbgHelp_RvaToSegOff,
+ /*.pfnImageSize = */ rtDbgModDbgHelp_ImageSize,
+
+ /*.pfnSegmentAdd = */ rtDbgModDbgHelp_SegmentAdd,
+ /*.pfnSegmentCount = */ rtDbgModDbgHelp_SegmentCount,
+ /*.pfnSegmentByIndex = */ rtDbgModDbgHelp_SegmentByIndex,
+
+ /*.pfnSymbolAdd = */ rtDbgModDbgHelp_SymbolAdd,
+ /*.pfnSymbolCount = */ rtDbgModDbgHelp_SymbolCount,
+ /*.pfnSymbolByOrdinal = */ rtDbgModDbgHelp_SymbolByOrdinal,
+ /*.pfnSymbolByName = */ rtDbgModDbgHelp_SymbolByName,
+ /*.pfnSymbolByAddr = */ rtDbgModDbgHelp_SymbolByAddr,
+
+ /*.pfnLineAdd = */ rtDbgModDbgHelp_LineAdd,
+ /*.pfnLineCount = */ rtDbgModDbgHelp_LineCount,
+ /*.pfnLineByOrdinal = */ rtDbgModDbgHelp_LineByOrdinal,
+ /*.pfnLineByAddr = */ rtDbgModDbgHelp_LineByAddr,
+
+ /*.pfnUnwindFrame = */ rtDbgModDbgHelp_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTDBG_MAGIC
+};
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmoddeferred.cpp b/src/VBox/Runtime/common/dbg/dbgmoddeferred.cpp
new file mode 100644
index 00000000..5dbdc5e2
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmoddeferred.cpp
@@ -0,0 +1,730 @@
+/* $Id: dbgmoddeferred.cpp $ */
+/** @file
+ * IPRT - Debug Module Deferred Loading Stub.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include "internal/dbgmod.h"
+#include "internal/magics.h"
+
+
+
+/**
+ * Releases the instance data.
+ *
+ * @param pThis The instance data.
+ */
+static void rtDbgModDeferredReleaseInstanceData(PRTDBGMODDEFERRED pThis)
+{
+ AssertPtr(pThis);
+ Assert(pThis->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); Assert(cRefs < 8);
+ if (!cRefs)
+ {
+ RTDbgCfgRelease(pThis->hDbgCfg);
+ pThis->hDbgCfg = NIL_RTDBGCFG;
+ pThis->u32Magic = RTDBGMODDEFERRED_MAGIC_DEAD;
+ RTMemFree(pThis);
+ }
+}
+
+
+/**
+ * Does the deferred loading of the real data (image and/or debug info).
+ *
+ * @returns VINF_SUCCESS or VERR_DBG_DEFERRED_LOAD_FAILED.
+ * @param pMod The generic module instance data.
+ * @param fForcedRetry Whether it's a forced retry by one of the
+ * pfnTryOpen methods.
+ */
+static int rtDbgModDeferredDoIt(PRTDBGMODINT pMod, bool fForcedRetry)
+{
+ RTCritSectEnter(&pMod->CritSect);
+
+ int rc;
+ if (!pMod->fDeferredFailed || fForcedRetry)
+ {
+ bool const fDbgVt = pMod->pDbgVt == &g_rtDbgModVtDbgDeferred;
+ bool const fImgVt = pMod->pImgVt == &g_rtDbgModVtImgDeferred;
+ AssertReturnStmt(fDbgVt || fImgVt, RTCritSectLeave(&pMod->CritSect), VERR_INTERNAL_ERROR_5);
+
+ PRTDBGMODDEFERRED pThis = (PRTDBGMODDEFERRED)(fDbgVt ? pMod->pvDbgPriv : pMod->pvImgPriv);
+
+ /* Reset the method tables and private data pointes so the deferred loading
+ procedure can figure out what to do and won't get confused. */
+ if (fDbgVt)
+ {
+ pMod->pvDbgPriv = NULL;
+ pMod->pDbgVt = NULL;
+ }
+
+ if (fImgVt)
+ {
+ pMod->pvImgPriv = NULL;
+ pMod->pImgVt = NULL;
+ }
+
+ /* Do the deferred loading. */
+ rc = pThis->pfnDeferred(pMod, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(!fDbgVt || pMod->pDbgVt != NULL);
+ Assert(!fImgVt || pMod->pImgVt != NULL);
+
+ pMod->fDeferred = false;
+ pMod->fDeferredFailed = false;
+
+ rtDbgModDeferredReleaseInstanceData(pThis);
+ if (fImgVt && fDbgVt)
+ rtDbgModDeferredReleaseInstanceData(pThis);
+ }
+ else
+ {
+ /* Failed, bail out and restore the deferred setup. */
+ pMod->fDeferredFailed = true;
+
+ if (fDbgVt)
+ {
+ Assert(!pMod->pDbgVt);
+ pMod->pDbgVt = &g_rtDbgModVtDbgDeferred;
+ pMod->pvDbgPriv = pThis;
+ }
+
+ if (fImgVt)
+ {
+ Assert(!pMod->pImgVt);
+ pMod->pImgVt = &g_rtDbgModVtImgDeferred;
+ pMod->pvImgPriv = pThis;
+ }
+ }
+ }
+ else
+ rc = VERR_DBG_DEFERRED_LOAD_FAILED;
+
+ RTCritSectLeave(&pMod->CritSect);
+ return rc;
+}
+
+
+
+
+/*
+ *
+ * D e b u g I n f o M e t h o d s
+ * D e b u g I n f o M e t h o d s
+ * D e b u g I n f o M e t h o d s
+ *
+ */
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnUnwindFrame} */
+static DECLCALLBACK(int)
+rtDbgModDeferredDbg_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnUnwindFrame(pMod, iSeg, off, pState);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByAddr} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_LineByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ PRTINTPTR poffDisp, PRTDBGLINE pLineInfo)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnLineByAddr(pMod, iSeg, off, poffDisp, pLineInfo);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByOrdinal} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_LineByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGLINE pLineInfo)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnLineByOrdinal(pMod, iOrdinal, pLineInfo);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineCount} */
+static DECLCALLBACK(uint32_t) rtDbgModDeferredDbg_LineCount(PRTDBGMODINT pMod)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ return pMod->pDbgVt->pfnLineCount(pMod);
+ return 0;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineAdd} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_LineAdd(PRTDBGMODINT pMod, const char *pszFile, size_t cchFile, uint32_t uLineNo,
+ uint32_t iSeg, RTUINTPTR off, uint32_t *piOrdinal)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnLineAdd(pMod, pszFile, cchFile, uLineNo, iSeg, off, piOrdinal);
+ return rc;
+}
+
+
+/**
+ * Fill in symbol info for the fake start symbol.
+ *
+ * @returns VINF_SUCCESS
+ * @param pThis The deferred load data.
+ * @param pSymInfo The output structure.
+ */
+static int rtDbgModDeferredDbgSymInfo_Start(PRTDBGMODDEFERRED pThis, PRTDBGSYMBOL pSymInfo)
+{
+ pSymInfo->Value = 0;
+ pSymInfo->cb = pThis->cbImage;
+ pSymInfo->offSeg = 0;
+ pSymInfo->iSeg = 0;
+ pSymInfo->fFlags = 0;
+ pSymInfo->iOrdinal = 0;
+ strcpy(pSymInfo->szName, "DeferredStart");
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Fill in symbol info for the fake last symbol.
+ *
+ * @returns VINF_SUCCESS
+ * @param pThis The deferred load data.
+ * @param pSymInfo The output structure.
+ */
+static int rtDbgModDeferredDbgSymInfo_Last(PRTDBGMODDEFERRED pThis, PRTDBGSYMBOL pSymInfo)
+{
+ pSymInfo->Value = pThis->cbImage - 1;
+ pSymInfo->cb = 0;
+ pSymInfo->offSeg = pThis->cbImage - 1;
+ pSymInfo->iSeg = 0;
+ pSymInfo->fFlags = 0;
+ pSymInfo->iOrdinal = 1;
+ strcpy(pSymInfo->szName, "DeferredLast");
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByAddr} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_SymbolByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ if ( (fFlags & RTDBGSYMADDR_FLAGS_SKIP_ABS_IN_DEFERRED)
+ && iSeg == RTDBGSEGIDX_ABS)
+ return VERR_SYMBOL_NOT_FOUND;
+
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnSymbolByAddr(pMod, iSeg, off, fFlags, poffDisp, pSymInfo);
+ else
+ {
+ PRTDBGMODDEFERRED pThis = (PRTDBGMODDEFERRED)pMod->pvDbgPriv;
+ if (off == 0)
+ rc = rtDbgModDeferredDbgSymInfo_Start(pThis, pSymInfo);
+ else if (off >= pThis->cbImage - 1 || (fFlags & RTDBGSYMADDR_FLAGS_GREATER_OR_EQUAL))
+ rc = rtDbgModDeferredDbgSymInfo_Last(pThis, pSymInfo);
+ else
+ rc = rtDbgModDeferredDbgSymInfo_Start(pThis, pSymInfo);
+ if (poffDisp)
+ *poffDisp = off - pSymInfo->offSeg;
+ }
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByName} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_SymbolByName(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ PRTDBGSYMBOL pSymInfo)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnSymbolByName(pMod, pszSymbol, cchSymbol, pSymInfo);
+ else
+ {
+ PRTDBGMODDEFERRED pThis = (PRTDBGMODDEFERRED)pMod->pvDbgPriv;
+ if ( cchSymbol == sizeof("DeferredStart") - 1
+ && !memcmp(pszSymbol, RT_STR_TUPLE("DeferredStart")))
+ rc = rtDbgModDeferredDbgSymInfo_Start(pThis, pSymInfo);
+ else if ( cchSymbol == sizeof("DeferredLast") - 1
+ && !memcmp(pszSymbol, RT_STR_TUPLE("DeferredLast")))
+ rc = rtDbgModDeferredDbgSymInfo_Last(pThis, pSymInfo);
+ else
+ rc = VERR_SYMBOL_NOT_FOUND;
+ }
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByOrdinal} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_SymbolByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGSYMBOL pSymInfo)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnSymbolByOrdinal(pMod, iOrdinal, pSymInfo);
+ else
+ {
+ PRTDBGMODDEFERRED pThis = (PRTDBGMODDEFERRED)pMod->pvDbgPriv;
+ if (iOrdinal == 0)
+ rc = rtDbgModDeferredDbgSymInfo_Start(pThis, pSymInfo);
+ else if (iOrdinal == 1)
+ rc = rtDbgModDeferredDbgSymInfo_Last(pThis, pSymInfo);
+ else
+ rc = VERR_SYMBOL_NOT_FOUND;
+ }
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolCount} */
+static DECLCALLBACK(uint32_t) rtDbgModDeferredDbg_SymbolCount(PRTDBGMODINT pMod)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ return pMod->pDbgVt->pfnSymbolCount(pMod);
+ return 2;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolAdd} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_SymbolAdd(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ RTDBGSEGIDX iSeg, RTUINTPTR off, RTUINTPTR cb, uint32_t fFlags,
+ uint32_t *piOrdinal)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnSymbolAdd(pMod, pszSymbol, cchSymbol, iSeg, off, cb, fFlags, piOrdinal);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentByIndex} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_SegmentByIndex(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, PRTDBGSEGMENT pSegInfo)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnSegmentByIndex(pMod, iSeg, pSegInfo);
+ else if (iSeg == 0)
+ {
+ PRTDBGMODDEFERRED pThis = (PRTDBGMODDEFERRED)pMod->pvDbgPriv;
+ pSegInfo->Address = 0;
+ pSegInfo->uRva = 0;
+ pSegInfo->cb = pThis->cbImage;
+ pSegInfo->fFlags = 0;
+ pSegInfo->iSeg = 0;
+ memcpy(pSegInfo->szName, RT_STR_TUPLE("LATER"));
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_DBG_INVALID_SEGMENT_INDEX;
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentCount} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModDeferredDbg_SegmentCount(PRTDBGMODINT pMod)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ return pMod->pDbgVt->pfnSegmentCount(pMod);
+ return 1;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentAdd} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_SegmentAdd(PRTDBGMODINT pMod, RTUINTPTR uRva, RTUINTPTR cb, const char *pszName,
+ size_t cchName, uint32_t fFlags, PRTDBGSEGIDX piSeg)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pDbgVt->pfnSegmentAdd(pMod, uRva, cb, pszName, cchName, fFlags, piSeg);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnImageSize} */
+static DECLCALLBACK(RTUINTPTR) rtDbgModDeferredDbg_ImageSize(PRTDBGMODINT pMod)
+{
+ PRTDBGMODDEFERRED pThis = (PRTDBGMODDEFERRED)pMod->pvDbgPriv;
+ Assert(pThis->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ return pThis->cbImage;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnRvaToSegOff} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModDeferredDbg_RvaToSegOff(PRTDBGMODINT pMod, RTUINTPTR uRva, PRTUINTPTR poffSeg)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvDbgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ return pMod->pDbgVt->pfnRvaToSegOff(pMod, uRva, poffSeg);
+ return 0;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnClose} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_Close(PRTDBGMODINT pMod)
+{
+ rtDbgModDeferredReleaseInstanceData((PRTDBGMODDEFERRED)pMod->pvDbgPriv);
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnTryOpen} */
+static DECLCALLBACK(int) rtDbgModDeferredDbg_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch)
+{
+ NOREF(enmArch);
+ return rtDbgModDeferredDoIt(pMod, true /*fForceRetry*/);
+}
+
+
+
+/** Virtual function table for the deferred debug info reader. */
+DECL_HIDDEN_CONST(RTDBGMODVTDBG) const g_rtDbgModVtDbgDeferred =
+{
+ /*.u32Magic = */ RTDBGMODVTDBG_MAGIC,
+ /*.fSupports = */ RT_DBGTYPE_MAP,
+ /*.pszName = */ "deferred",
+ /*.pfnTryOpen = */ rtDbgModDeferredDbg_TryOpen,
+ /*.pfnClose = */ rtDbgModDeferredDbg_Close,
+
+ /*.pfnRvaToSegOff = */ rtDbgModDeferredDbg_RvaToSegOff,
+ /*.pfnImageSize = */ rtDbgModDeferredDbg_ImageSize,
+
+ /*.pfnSegmentAdd = */ rtDbgModDeferredDbg_SegmentAdd,
+ /*.pfnSegmentCount = */ rtDbgModDeferredDbg_SegmentCount,
+ /*.pfnSegmentByIndex = */ rtDbgModDeferredDbg_SegmentByIndex,
+
+ /*.pfnSymbolAdd = */ rtDbgModDeferredDbg_SymbolAdd,
+ /*.pfnSymbolCount = */ rtDbgModDeferredDbg_SymbolCount,
+ /*.pfnSymbolByOrdinal = */ rtDbgModDeferredDbg_SymbolByOrdinal,
+ /*.pfnSymbolByName = */ rtDbgModDeferredDbg_SymbolByName,
+ /*.pfnSymbolByAddr = */ rtDbgModDeferredDbg_SymbolByAddr,
+
+ /*.pfnLineAdd = */ rtDbgModDeferredDbg_LineAdd,
+ /*.pfnLineCount = */ rtDbgModDeferredDbg_LineCount,
+ /*.pfnLineByOrdinal = */ rtDbgModDeferredDbg_LineByOrdinal,
+ /*.pfnLineByAddr = */ rtDbgModDeferredDbg_LineByAddr,
+
+ /*.pfnUnwindFrame = */ rtDbgModDeferredDbg_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTDBG_MAGIC
+};
+
+
+
+
+/*
+ *
+ * I m a g e M e t h o d s
+ * I m a g e M e t h o d s
+ * I m a g e M e t h o d s
+ *
+ */
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnUnwindFrame} */
+static DECLCALLBACK(int)
+rtDbgModDeferredImg_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnUnwindFrame(pMod, iSeg, off, pState);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnQueryProp} */
+static DECLCALLBACK(int)
+rtDbgModDeferredImg_QueryProp(PRTDBGMODINT pMod, RTLDRPROP enmProp, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnQueryProp(pMod, enmProp, pvBuf, cbBuf, pcbRet);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnGetArch} */
+static DECLCALLBACK(RTLDRARCH) rtDbgModDeferredImg_GetArch(PRTDBGMODINT pMod)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+
+ RTLDRARCH enmArch;
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ enmArch = pMod->pImgVt->pfnGetArch(pMod);
+ else
+ enmArch = RTLDRARCH_WHATEVER;
+ return enmArch;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnGetFormat} */
+static DECLCALLBACK(RTLDRFMT) rtDbgModDeferredImg_GetFormat(PRTDBGMODINT pMod)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+
+ RTLDRFMT enmFmt;
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ enmFmt = pMod->pImgVt->pfnGetFormat(pMod);
+ else
+ enmFmt = RTLDRFMT_INVALID;
+ return enmFmt;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnReadAt} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_ReadAt(PRTDBGMODINT pMod, uint32_t iDbgInfoHint, RTFOFF off, void *pvBuf, size_t cb)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnReadAt(pMod, iDbgInfoHint, off, pvBuf, cb);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnUnmapPart} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_UnmapPart(PRTDBGMODINT pMod, size_t cb, void const **ppvMap)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnUnmapPart(pMod, cb, ppvMap);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnMapPart} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_MapPart(PRTDBGMODINT pMod, uint32_t iDbgInfo, RTFOFF off, size_t cb, void const **ppvMap)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnMapPart(pMod, iDbgInfo, off, cb, ppvMap);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnImageSize} */
+static DECLCALLBACK(RTUINTPTR) rtDbgModDeferredImg_ImageSize(PRTDBGMODINT pMod)
+{
+ PRTDBGMODDEFERRED pThis = (PRTDBGMODDEFERRED)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ return pThis->cbImage;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnRvaToSegOffset} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_RvaToSegOffset(PRTDBGMODINT pMod, RTLDRADDR Rva,
+ PRTDBGSEGIDX piSeg, PRTLDRADDR poffSeg)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnRvaToSegOffset(pMod, Rva, piSeg, poffSeg);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnLinkAddressToSegOffset} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_LinkAddressToSegOffset(PRTDBGMODINT pMod, RTLDRADDR LinkAddress,
+ PRTDBGSEGIDX piSeg, PRTLDRADDR poffSeg)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnLinkAddressToSegOffset(pMod, LinkAddress, piSeg, poffSeg);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnEnumSymbols} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_EnumSymbols(PRTDBGMODINT pMod, uint32_t fFlags, RTLDRADDR BaseAddress,
+ PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnEnumSymbols(pMod, fFlags, BaseAddress, pfnCallback, pvUser);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnEnumSegments} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_EnumSegments(PRTDBGMODINT pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnEnumSegments(pMod, pfnCallback, pvUser);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnEnumDbgInfo} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_EnumDbgInfo(PRTDBGMODINT pMod, PFNRTLDRENUMDBG pfnCallback, void *pvUser)
+{
+ Assert(((PRTDBGMODDEFERRED)pMod->pvImgPriv)->u32Magic == RTDBGMODDEFERRED_MAGIC);
+ int rc = rtDbgModDeferredDoIt(pMod, false /*fForceRetry*/);
+ if (RT_SUCCESS(rc))
+ rc = pMod->pImgVt->pfnEnumDbgInfo(pMod, pfnCallback, pvUser);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnClose} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_Close(PRTDBGMODINT pMod)
+{
+ rtDbgModDeferredReleaseInstanceData((PRTDBGMODDEFERRED)pMod->pvImgPriv);
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnTryOpen} */
+static DECLCALLBACK(int) rtDbgModDeferredImg_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch, uint32_t fLdrFlags)
+{
+ RT_NOREF(enmArch, fLdrFlags);
+ return rtDbgModDeferredDoIt(pMod, true /*fForceRetry*/);
+}
+
+
+/** Virtual function table for the RTLdr based image reader. */
+DECL_HIDDEN_CONST(RTDBGMODVTIMG) const g_rtDbgModVtImgDeferred =
+{
+ /*.u32Magic = */ RTDBGMODVTIMG_MAGIC,
+ /*.fReserved = */ 0,
+ /*.pszName = */ "deferred",
+ /*.pfnTryOpen = */ rtDbgModDeferredImg_TryOpen,
+ /*.pfnClose = */ rtDbgModDeferredImg_Close,
+ /*.pfnEnumDbgInfo = */ rtDbgModDeferredImg_EnumDbgInfo,
+ /*.pfnEnumSegments = */ rtDbgModDeferredImg_EnumSegments,
+ /*.pfnEnumSymbols = */ rtDbgModDeferredImg_EnumSymbols,
+ /*.pfnGetLoadedSize = */ rtDbgModDeferredImg_ImageSize,
+ /*.pfnLinkAddressToSegOffset = */ rtDbgModDeferredImg_LinkAddressToSegOffset,
+ /*.pfnRvaToSegOffset = */ rtDbgModDeferredImg_RvaToSegOffset,
+ /*.pfnMapPart = */ rtDbgModDeferredImg_MapPart,
+ /*.pfnUnmapPart = */ rtDbgModDeferredImg_UnmapPart,
+ /*.pfnReadAt = */ rtDbgModDeferredImg_ReadAt,
+ /*.pfnGetFormat = */ rtDbgModDeferredImg_GetFormat,
+ /*.pfnGetArch = */ rtDbgModDeferredImg_GetArch,
+ /*.pfnQueryProp = */ rtDbgModDeferredImg_QueryProp,
+ /*.pfnUnwindFrame = */ rtDbgModDeferredImg_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTIMG_MAGIC
+};
+
+
+/**
+ * Creates a deferred loading stub for both image and debug info.
+ *
+ * @returns IPRT status code.
+ * @param pDbgMod The debug module.
+ * @param pfnDeferred The callback that will try load the image and
+ * debug info.
+ * @param cbImage The size of the image.
+ * @param hDbgCfg The debug config handle. Can be NIL. A
+ * reference will be retained.
+ * @param cbDeferred The size of the deferred instance data, 0 if the
+ * default structure is good enough.
+ * @param fFlags RTDBGMOD_F_XXX.
+ * @param ppDeferred Where to return the instance data. Can be NULL.
+ */
+DECLHIDDEN(int) rtDbgModDeferredCreate(PRTDBGMODINT pDbgMod, PFNRTDBGMODDEFERRED pfnDeferred, RTUINTPTR cbImage,
+ RTDBGCFG hDbgCfg, size_t cbDeferred, uint32_t fFlags, PRTDBGMODDEFERRED *ppDeferred)
+{
+ AssertReturn(!pDbgMod->pDbgVt, VERR_DBG_MOD_IPE);
+
+ if (cbDeferred < sizeof(RTDBGMODDEFERRED))
+ cbDeferred = sizeof(RTDBGMODDEFERRED);
+ PRTDBGMODDEFERRED pDeferred = (PRTDBGMODDEFERRED)RTMemAllocZ(cbDeferred);
+ if (!pDeferred)
+ return VERR_NO_MEMORY;
+
+ pDeferred->u32Magic = RTDBGMODDEFERRED_MAGIC;
+ pDeferred->cRefs = 1 + (pDbgMod->pImgVt == NULL);
+ pDeferred->cbImage = cbImage;
+ if (hDbgCfg != NIL_RTDBGCFG)
+ RTDbgCfgRetain(hDbgCfg);
+ pDeferred->hDbgCfg = hDbgCfg;
+ pDeferred->pfnDeferred = pfnDeferred;
+ pDeferred->fFlags = fFlags;
+
+ pDbgMod->pDbgVt = &g_rtDbgModVtDbgDeferred;
+ pDbgMod->pvDbgPriv = pDeferred;
+ if (!pDbgMod->pImgVt)
+ {
+ pDbgMod->pImgVt = &g_rtDbgModVtImgDeferred;
+ pDbgMod->pvImgPriv = pDeferred;
+ }
+ pDbgMod->fDeferred = true;
+ pDbgMod->fDeferredFailed = false;
+
+ if (ppDeferred)
+ *ppDeferred = pDeferred;
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmoddwarf.cpp b/src/VBox/Runtime/common/dbg/dbgmoddwarf.cpp
new file mode 100644
index 00000000..48645bab
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmoddwarf.cpp
@@ -0,0 +1,6287 @@
+/* $Id: dbgmoddwarf.cpp $ */
+/** @file
+ * IPRT - Debug Info Reader For DWARF.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DBG_DWARF
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#define RTDBGMODDWARF_WITH_MEM_CACHE
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+# include <iprt/memcache.h>
+#endif
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/strcache.h>
+#include <iprt/x86.h>
+#include <iprt/formats/dwarf.h>
+#include "internal/dbgmod.h"
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a DWARF section reader. */
+typedef struct RTDWARFCURSOR *PRTDWARFCURSOR;
+/** Pointer to an attribute descriptor. */
+typedef struct RTDWARFATTRDESC const *PCRTDWARFATTRDESC;
+/** Pointer to a DIE. */
+typedef struct RTDWARFDIE *PRTDWARFDIE;
+/** Pointer to a const DIE. */
+typedef struct RTDWARFDIE const *PCRTDWARFDIE;
+
+/**
+ * DWARF sections.
+ */
+typedef enum krtDbgModDwarfSect
+{
+ krtDbgModDwarfSect_abbrev = 0,
+ krtDbgModDwarfSect_aranges,
+ krtDbgModDwarfSect_frame,
+ krtDbgModDwarfSect_info,
+ krtDbgModDwarfSect_inlined,
+ krtDbgModDwarfSect_line,
+ krtDbgModDwarfSect_loc,
+ krtDbgModDwarfSect_macinfo,
+ krtDbgModDwarfSect_pubnames,
+ krtDbgModDwarfSect_pubtypes,
+ krtDbgModDwarfSect_ranges,
+ krtDbgModDwarfSect_str,
+ krtDbgModDwarfSect_types,
+ /** End of valid parts (exclusive). */
+ krtDbgModDwarfSect_End
+} krtDbgModDwarfSect;
+
+/**
+ * Abbreviation cache entry.
+ */
+typedef struct RTDWARFABBREV
+{
+ /** Whether there are children or not. */
+ bool fChildren;
+#ifdef LOG_ENABLED
+ uint8_t cbHdr; /**< For calcing ABGOFF matching dwarfdump. */
+#endif
+ /** The tag. */
+ uint16_t uTag;
+ /** Offset into the abbrev section of the specification pairs. */
+ uint32_t offSpec;
+ /** The abbreviation table offset this is entry is valid for.
+ * UINT32_MAX if not valid. */
+ uint32_t offAbbrev;
+} RTDWARFABBREV;
+/** Pointer to an abbreviation cache entry. */
+typedef RTDWARFABBREV *PRTDWARFABBREV;
+/** Pointer to a const abbreviation cache entry. */
+typedef RTDWARFABBREV const *PCRTDWARFABBREV;
+
+/**
+ * Structure for gathering segment info.
+ */
+typedef struct RTDBGDWARFSEG
+{
+ /** The highest offset in the segment. */
+ uint64_t offHighest;
+ /** Calculated base address. */
+ uint64_t uBaseAddr;
+ /** Estimated The segment size. */
+ uint64_t cbSegment;
+ /** Segment number (RTLDRSEG::Sel16bit). */
+ RTSEL uSegment;
+} RTDBGDWARFSEG;
+/** Pointer to segment info. */
+typedef RTDBGDWARFSEG *PRTDBGDWARFSEG;
+
+
+/**
+ * The instance data of the DWARF reader.
+ */
+typedef struct RTDBGMODDWARF
+{
+ /** The debug container containing doing the real work. */
+ RTDBGMOD hCnt;
+ /** The image module (no reference). */
+ PRTDBGMODINT pImgMod;
+ /** The debug info module (no reference). */
+ PRTDBGMODINT pDbgInfoMod;
+ /** Nested image module (with reference ofc). */
+ PRTDBGMODINT pNestedMod;
+
+ /** DWARF debug info sections. */
+ struct
+ {
+ /** The file offset of the part. */
+ RTFOFF offFile;
+ /** The size of the part. */
+ size_t cb;
+ /** The memory mapping of the part. */
+ void const *pv;
+ /** Set if present. */
+ bool fPresent;
+ /** The debug info ordinal number in the image file. */
+ uint32_t iDbgInfo;
+ } aSections[krtDbgModDwarfSect_End];
+
+ /** The offset into the abbreviation section of the current cache. */
+ uint32_t offCachedAbbrev;
+ /** The number of cached abbreviations we've allocated space for. */
+ uint32_t cCachedAbbrevsAlloced;
+ /** Array of cached abbreviations, indexed by code. */
+ PRTDWARFABBREV paCachedAbbrevs;
+ /** Used by rtDwarfAbbrev_Lookup when the result is uncachable. */
+ RTDWARFABBREV LookupAbbrev;
+
+ /** The list of compilation units (RTDWARFDIE). */
+ RTLISTANCHOR CompileUnitList;
+
+ /** Set if we have to use link addresses because the module does not have
+ * fixups (mach_kernel). */
+ bool fUseLinkAddress;
+ /** This is set to -1 if we're doing everything in one pass.
+ * Otherwise it's 1 or 2:
+ * - In pass 1, we collect segment info.
+ * - In pass 2, we add debug info to the container.
+ * The two pass parsing is necessary for watcom generated symbol files as
+ * these contains no information about the code and data segments in the
+ * image. So we have to figure out some approximate stuff based on the
+ * segments and offsets we encounter in the debug info. */
+ int8_t iWatcomPass;
+ /** Segment index hint. */
+ uint16_t iSegHint;
+ /** The number of segments in paSegs.
+ * (During segment copying, this is abused to count useful segments.) */
+ uint32_t cSegs;
+ /** Pointer to segments if iWatcomPass isn't -1. */
+ PRTDBGDWARFSEG paSegs;
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+ /** DIE allocators. */
+ struct
+ {
+ RTMEMCACHE hMemCache;
+ uint32_t cbMax;
+ } aDieAllocators[2];
+#endif
+} RTDBGMODDWARF;
+/** Pointer to instance data of the DWARF reader. */
+typedef RTDBGMODDWARF *PRTDBGMODDWARF;
+
+/**
+ * DWARF cursor for reading byte data.
+ */
+typedef struct RTDWARFCURSOR
+{
+ /** The current position. */
+ uint8_t const *pb;
+ /** The number of bytes left to read. */
+ size_t cbLeft;
+ /** The number of bytes left to read in the current unit. */
+ size_t cbUnitLeft;
+ /** The DWARF debug info reader instance. (Can be NULL for eh_frame.) */
+ PRTDBGMODDWARF pDwarfMod;
+ /** Set if this is 64-bit DWARF, clear if 32-bit. */
+ bool f64bitDwarf;
+ /** Set if the format endian is native, clear if endian needs to be
+ * inverted. */
+ bool fNativEndian;
+ /** The size of a native address. */
+ uint8_t cbNativeAddr;
+ /** The cursor status code. This is VINF_SUCCESS until some error
+ * occurs. */
+ int rc;
+ /** The start of the area covered by the cursor.
+ * Used for repositioning the cursor relative to the start of a section. */
+ uint8_t const *pbStart;
+ /** The section. */
+ krtDbgModDwarfSect enmSect;
+} RTDWARFCURSOR;
+
+
+/**
+ * DWARF line number program state.
+ */
+typedef struct RTDWARFLINESTATE
+{
+ /** @name Virtual Line Number Machine Registers.
+ * @{ */
+ struct
+ {
+ uint64_t uAddress;
+ uint64_t idxOp;
+ uint32_t iFile;
+ uint32_t uLine;
+ uint32_t uColumn;
+ bool fIsStatement;
+ bool fBasicBlock;
+ bool fEndSequence;
+ bool fPrologueEnd;
+ bool fEpilogueBegin;
+ uint32_t uIsa;
+ uint32_t uDiscriminator;
+ RTSEL uSegment;
+ } Regs;
+ /** @} */
+
+ /** Header. */
+ struct
+ {
+ uint32_t uVer;
+ uint64_t offFirstOpcode;
+ uint8_t cbMinInstr;
+ uint8_t cMaxOpsPerInstr;
+ uint8_t u8DefIsStmt;
+ int8_t s8LineBase;
+ uint8_t u8LineRange;
+ uint8_t u8OpcodeBase;
+ uint8_t const *pacStdOperands;
+ } Hdr;
+
+ /** @name Include Path Table (0-based)
+ * @{ */
+ const char **papszIncPaths;
+ uint32_t cIncPaths;
+ /** @} */
+
+ /** @name File Name Table (0-based, dummy zero entry)
+ * @{ */
+ char **papszFileNames;
+ uint32_t cFileNames;
+ /** @} */
+
+ /** The DWARF debug info reader instance. */
+ PRTDBGMODDWARF pDwarfMod;
+} RTDWARFLINESTATE;
+/** Pointer to a DWARF line number program state. */
+typedef RTDWARFLINESTATE *PRTDWARFLINESTATE;
+
+
+/**
+ * Decodes an attribute and stores it in the specified DIE member field.
+ *
+ * @returns IPRT status code.
+ * @param pDie Pointer to the DIE structure.
+ * @param pbMember Pointer to the first byte in the member.
+ * @param pDesc The attribute descriptor.
+ * @param uForm The data form.
+ * @param pCursor The cursor to read data from.
+ */
+typedef DECLCALLBACKTYPE(int, FNRTDWARFATTRDECODER,(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor));
+/** Pointer to an attribute decoder callback. */
+typedef FNRTDWARFATTRDECODER *PFNRTDWARFATTRDECODER;
+
+/**
+ * Attribute descriptor.
+ */
+typedef struct RTDWARFATTRDESC
+{
+ /** The attribute. */
+ uint16_t uAttr;
+ /** The data member offset. */
+ uint16_t off;
+ /** The data member size and initialization method. */
+ uint8_t cbInit;
+ uint8_t bPadding[3]; /**< Alignment padding. */
+ /** The decoder function. */
+ PFNRTDWARFATTRDECODER pfnDecoder;
+} RTDWARFATTRDESC;
+
+/** Define a attribute entry. */
+#define ATTR_ENTRY(a_uAttr, a_Struct, a_Member, a_Init, a_pfnDecoder) \
+ { \
+ a_uAttr, \
+ (uint16_t)RT_OFFSETOF(a_Struct, a_Member), \
+ a_Init | ((uint8_t)RT_SIZEOFMEMB(a_Struct, a_Member) & ATTR_SIZE_MASK), \
+ { 0, 0, 0 }, \
+ a_pfnDecoder\
+ }
+
+/** @name Attribute size and init methods.
+ * @{ */
+#define ATTR_INIT_ZERO UINT8_C(0x00)
+#define ATTR_INIT_FFFS UINT8_C(0x80)
+#define ATTR_INIT_MASK UINT8_C(0x80)
+#define ATTR_SIZE_MASK UINT8_C(0x3f)
+#define ATTR_GET_SIZE(a_pAttrDesc) ((a_pAttrDesc)->cbInit & ATTR_SIZE_MASK)
+/** @} */
+
+
+/**
+ * DIE descriptor.
+ */
+typedef struct RTDWARFDIEDESC
+{
+ /** The size of the DIE. */
+ size_t cbDie;
+ /** The number of attributes. */
+ size_t cAttributes;
+ /** Pointer to the array of attributes. */
+ PCRTDWARFATTRDESC paAttributes;
+} RTDWARFDIEDESC;
+typedef struct RTDWARFDIEDESC const *PCRTDWARFDIEDESC;
+/** DIE descriptor initializer. */
+#define DIE_DESC_INIT(a_Type, a_aAttrs) { sizeof(a_Type), RT_ELEMENTS(a_aAttrs), &a_aAttrs[0] }
+
+
+/**
+ * DIE core structure, all inherits (starts with) this.
+ */
+typedef struct RTDWARFDIE
+{
+ /** Pointer to the parent node. NULL if root unit. */
+ struct RTDWARFDIE *pParent;
+ /** Our node in the sibling list. */
+ RTLISTNODE SiblingNode;
+ /** List of children. */
+ RTLISTNODE ChildList;
+ /** The number of attributes successfully decoded. */
+ uint8_t cDecodedAttrs;
+ /** The number of unknown or otherwise unhandled attributes. */
+ uint8_t cUnhandledAttrs;
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+ /** The allocator index. */
+ uint8_t iAllocator;
+#endif
+ /** The die tag, indicating which union structure to use. */
+ uint16_t uTag;
+ /** Offset of the abbreviation specification (within debug_abbrev). */
+ uint32_t offSpec;
+} RTDWARFDIE;
+
+
+/**
+ * DWARF address structure.
+ */
+typedef struct RTDWARFADDR
+{
+ /** The address. */
+ uint64_t uAddress;
+} RTDWARFADDR;
+typedef RTDWARFADDR *PRTDWARFADDR;
+typedef RTDWARFADDR const *PCRTDWARFADDR;
+
+
+/**
+ * DWARF address range.
+ */
+typedef struct RTDWARFADDRRANGE
+{
+ uint64_t uLowAddress;
+ uint64_t uHighAddress;
+ uint8_t const *pbRanges; /* ?? */
+ uint8_t cAttrs : 2;
+ uint8_t fHaveLowAddress : 1;
+ uint8_t fHaveHighAddress : 1;
+ uint8_t fHaveHighIsAddress : 1;
+ uint8_t fHaveRanges : 1;
+} RTDWARFADDRRANGE;
+typedef RTDWARFADDRRANGE *PRTDWARFADDRRANGE;
+typedef RTDWARFADDRRANGE const *PCRTDWARFADDRRANGE;
+
+/** What a RTDWARFREF is relative to. */
+typedef enum krtDwarfRef
+{
+ krtDwarfRef_NotSet,
+ krtDwarfRef_LineSection,
+ krtDwarfRef_LocSection,
+ krtDwarfRef_RangesSection,
+ krtDwarfRef_InfoSection,
+ krtDwarfRef_SameUnit,
+ krtDwarfRef_TypeId64
+} krtDwarfRef;
+
+/**
+ * DWARF reference.
+ */
+typedef struct RTDWARFREF
+{
+ /** The offset. */
+ uint64_t off;
+ /** What the offset is relative to. */
+ krtDwarfRef enmWrt;
+} RTDWARFREF;
+typedef RTDWARFREF *PRTDWARFREF;
+typedef RTDWARFREF const *PCRTDWARFREF;
+
+
+/**
+ * DWARF Location state.
+ */
+typedef struct RTDWARFLOCST
+{
+ /** The input cursor. */
+ RTDWARFCURSOR Cursor;
+ /** Points to the current top of the stack. Initial value -1. */
+ int32_t iTop;
+ /** The value stack. */
+ uint64_t auStack[64];
+} RTDWARFLOCST;
+/** Pointer to location state. */
+typedef RTDWARFLOCST *PRTDWARFLOCST;
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static FNRTDWARFATTRDECODER rtDwarfDecode_Address;
+static FNRTDWARFATTRDECODER rtDwarfDecode_Bool;
+static FNRTDWARFATTRDECODER rtDwarfDecode_LowHighPc;
+static FNRTDWARFATTRDECODER rtDwarfDecode_Ranges;
+static FNRTDWARFATTRDECODER rtDwarfDecode_Reference;
+static FNRTDWARFATTRDECODER rtDwarfDecode_SectOff;
+static FNRTDWARFATTRDECODER rtDwarfDecode_String;
+static FNRTDWARFATTRDECODER rtDwarfDecode_UnsignedInt;
+static FNRTDWARFATTRDECODER rtDwarfDecode_SegmentLoc;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** RTDWARFDIE description. */
+static const RTDWARFDIEDESC g_CoreDieDesc = { sizeof(RTDWARFDIE), 0, NULL };
+
+
+/**
+ * DW_TAG_compile_unit & DW_TAG_partial_unit.
+ */
+typedef struct RTDWARFDIECOMPILEUNIT
+{
+ /** The DIE core structure. */
+ RTDWARFDIE Core;
+ /** The unit name. */
+ const char *pszName;
+ /** The address range of the code belonging to this unit. */
+ RTDWARFADDRRANGE PcRange;
+ /** The language name. */
+ uint16_t uLanguage;
+ /** The identifier case. */
+ uint8_t uIdentifierCase;
+ /** String are UTF-8 encoded. If not set, the encoding is
+ * unknown. */
+ bool fUseUtf8;
+ /** The unit contains main() or equivalent. */
+ bool fMainFunction;
+ /** The line numbers for this unit. */
+ RTDWARFREF StmtListRef;
+ /** The macro information for this unit. */
+ RTDWARFREF MacroInfoRef;
+ /** Reference to the base types. */
+ RTDWARFREF BaseTypesRef;
+ /** Working directory for the unit. */
+ const char *pszCurDir;
+ /** The name of the compiler or whatever that produced this unit. */
+ const char *pszProducer;
+
+ /** @name From the unit header.
+ * @{ */
+ /** The offset into debug_info of this unit (for references). */
+ uint64_t offUnit;
+ /** The length of this unit. */
+ uint64_t cbUnit;
+ /** The offset into debug_abbrev of the abbreviation for this unit. */
+ uint64_t offAbbrev;
+ /** The native address size. */
+ uint8_t cbNativeAddr;
+ /** The DWARF version. */
+ uint8_t uDwarfVer;
+ /** @} */
+} RTDWARFDIECOMPILEUNIT;
+typedef RTDWARFDIECOMPILEUNIT *PRTDWARFDIECOMPILEUNIT;
+
+
+/** RTDWARFDIECOMPILEUNIT attributes. */
+static const RTDWARFATTRDESC g_aCompileUnitAttrs[] =
+{
+ ATTR_ENTRY(DW_AT_name, RTDWARFDIECOMPILEUNIT, pszName, ATTR_INIT_ZERO, rtDwarfDecode_String),
+ ATTR_ENTRY(DW_AT_low_pc, RTDWARFDIECOMPILEUNIT, PcRange, ATTR_INIT_ZERO, rtDwarfDecode_LowHighPc),
+ ATTR_ENTRY(DW_AT_high_pc, RTDWARFDIECOMPILEUNIT, PcRange, ATTR_INIT_ZERO, rtDwarfDecode_LowHighPc),
+ ATTR_ENTRY(DW_AT_ranges, RTDWARFDIECOMPILEUNIT, PcRange, ATTR_INIT_ZERO, rtDwarfDecode_Ranges),
+ ATTR_ENTRY(DW_AT_language, RTDWARFDIECOMPILEUNIT, uLanguage, ATTR_INIT_ZERO, rtDwarfDecode_UnsignedInt),
+ ATTR_ENTRY(DW_AT_macro_info, RTDWARFDIECOMPILEUNIT, MacroInfoRef, ATTR_INIT_ZERO, rtDwarfDecode_SectOff),
+ ATTR_ENTRY(DW_AT_stmt_list, RTDWARFDIECOMPILEUNIT, StmtListRef, ATTR_INIT_ZERO, rtDwarfDecode_SectOff),
+ ATTR_ENTRY(DW_AT_comp_dir, RTDWARFDIECOMPILEUNIT, pszCurDir, ATTR_INIT_ZERO, rtDwarfDecode_String),
+ ATTR_ENTRY(DW_AT_producer, RTDWARFDIECOMPILEUNIT, pszProducer, ATTR_INIT_ZERO, rtDwarfDecode_String),
+ ATTR_ENTRY(DW_AT_identifier_case, RTDWARFDIECOMPILEUNIT, uIdentifierCase,ATTR_INIT_ZERO, rtDwarfDecode_UnsignedInt),
+ ATTR_ENTRY(DW_AT_base_types, RTDWARFDIECOMPILEUNIT, BaseTypesRef, ATTR_INIT_ZERO, rtDwarfDecode_Reference),
+ ATTR_ENTRY(DW_AT_use_UTF8, RTDWARFDIECOMPILEUNIT, fUseUtf8, ATTR_INIT_ZERO, rtDwarfDecode_Bool),
+ ATTR_ENTRY(DW_AT_main_subprogram, RTDWARFDIECOMPILEUNIT, fMainFunction, ATTR_INIT_ZERO, rtDwarfDecode_Bool)
+};
+
+/** RTDWARFDIECOMPILEUNIT description. */
+static const RTDWARFDIEDESC g_CompileUnitDesc = DIE_DESC_INIT(RTDWARFDIECOMPILEUNIT, g_aCompileUnitAttrs);
+
+
+/**
+ * DW_TAG_subprogram.
+ */
+typedef struct RTDWARFDIESUBPROGRAM
+{
+ /** The DIE core structure. */
+ RTDWARFDIE Core;
+ /** The name. */
+ const char *pszName;
+ /** The linkage name. */
+ const char *pszLinkageName;
+ /** The address range of the code belonging to this unit. */
+ RTDWARFADDRRANGE PcRange;
+ /** The first instruction in the function. */
+ RTDWARFADDR EntryPc;
+ /** Segment number (watcom). */
+ RTSEL uSegment;
+ /** Reference to the specification. */
+ RTDWARFREF SpecRef;
+} RTDWARFDIESUBPROGRAM;
+/** Pointer to a DW_TAG_subprogram DIE. */
+typedef RTDWARFDIESUBPROGRAM *PRTDWARFDIESUBPROGRAM;
+/** Pointer to a const DW_TAG_subprogram DIE. */
+typedef RTDWARFDIESUBPROGRAM const *PCRTDWARFDIESUBPROGRAM;
+
+
+/** RTDWARFDIESUBPROGRAM attributes. */
+static const RTDWARFATTRDESC g_aSubProgramAttrs[] =
+{
+ ATTR_ENTRY(DW_AT_name, RTDWARFDIESUBPROGRAM, pszName, ATTR_INIT_ZERO, rtDwarfDecode_String),
+ ATTR_ENTRY(DW_AT_linkage_name, RTDWARFDIESUBPROGRAM, pszLinkageName, ATTR_INIT_ZERO, rtDwarfDecode_String),
+ ATTR_ENTRY(DW_AT_MIPS_linkage_name, RTDWARFDIESUBPROGRAM, pszLinkageName, ATTR_INIT_ZERO, rtDwarfDecode_String),
+ ATTR_ENTRY(DW_AT_low_pc, RTDWARFDIESUBPROGRAM, PcRange, ATTR_INIT_ZERO, rtDwarfDecode_LowHighPc),
+ ATTR_ENTRY(DW_AT_high_pc, RTDWARFDIESUBPROGRAM, PcRange, ATTR_INIT_ZERO, rtDwarfDecode_LowHighPc),
+ ATTR_ENTRY(DW_AT_ranges, RTDWARFDIESUBPROGRAM, PcRange, ATTR_INIT_ZERO, rtDwarfDecode_Ranges),
+ ATTR_ENTRY(DW_AT_entry_pc, RTDWARFDIESUBPROGRAM, EntryPc, ATTR_INIT_ZERO, rtDwarfDecode_Address),
+ ATTR_ENTRY(DW_AT_segment, RTDWARFDIESUBPROGRAM, uSegment, ATTR_INIT_ZERO, rtDwarfDecode_SegmentLoc),
+ ATTR_ENTRY(DW_AT_specification, RTDWARFDIESUBPROGRAM, SpecRef, ATTR_INIT_ZERO, rtDwarfDecode_Reference)
+};
+
+/** RTDWARFDIESUBPROGRAM description. */
+static const RTDWARFDIEDESC g_SubProgramDesc = DIE_DESC_INIT(RTDWARFDIESUBPROGRAM, g_aSubProgramAttrs);
+
+
+/** RTDWARFDIESUBPROGRAM attributes for the specification hack. */
+static const RTDWARFATTRDESC g_aSubProgramSpecHackAttrs[] =
+{
+ ATTR_ENTRY(DW_AT_name, RTDWARFDIESUBPROGRAM, pszName, ATTR_INIT_ZERO, rtDwarfDecode_String),
+ ATTR_ENTRY(DW_AT_linkage_name, RTDWARFDIESUBPROGRAM, pszLinkageName, ATTR_INIT_ZERO, rtDwarfDecode_String),
+ ATTR_ENTRY(DW_AT_MIPS_linkage_name, RTDWARFDIESUBPROGRAM, pszLinkageName, ATTR_INIT_ZERO, rtDwarfDecode_String),
+};
+
+/** RTDWARFDIESUBPROGRAM description for the specification hack. */
+static const RTDWARFDIEDESC g_SubProgramSpecHackDesc = DIE_DESC_INIT(RTDWARFDIESUBPROGRAM, g_aSubProgramSpecHackAttrs);
+
+
+/**
+ * DW_TAG_label.
+ */
+typedef struct RTDWARFDIELABEL
+{
+ /** The DIE core structure. */
+ RTDWARFDIE Core;
+ /** The name. */
+ const char *pszName;
+ /** The address of the first instruction. */
+ RTDWARFADDR Address;
+ /** Segment number (watcom). */
+ RTSEL uSegment;
+ /** Externally visible? */
+ bool fExternal;
+} RTDWARFDIELABEL;
+/** Pointer to a DW_TAG_label DIE. */
+typedef RTDWARFDIELABEL *PRTDWARFDIELABEL;
+/** Pointer to a const DW_TAG_label DIE. */
+typedef RTDWARFDIELABEL const *PCRTDWARFDIELABEL;
+
+
+/** RTDWARFDIESUBPROGRAM attributes. */
+static const RTDWARFATTRDESC g_aLabelAttrs[] =
+{
+ ATTR_ENTRY(DW_AT_name, RTDWARFDIELABEL, pszName, ATTR_INIT_ZERO, rtDwarfDecode_String),
+ ATTR_ENTRY(DW_AT_low_pc, RTDWARFDIELABEL, Address, ATTR_INIT_ZERO, rtDwarfDecode_Address),
+ ATTR_ENTRY(DW_AT_segment, RTDWARFDIELABEL, uSegment, ATTR_INIT_ZERO, rtDwarfDecode_SegmentLoc),
+ ATTR_ENTRY(DW_AT_external, RTDWARFDIELABEL, fExternal, ATTR_INIT_ZERO, rtDwarfDecode_Bool)
+};
+
+/** RTDWARFDIESUBPROGRAM description. */
+static const RTDWARFDIEDESC g_LabelDesc = DIE_DESC_INIT(RTDWARFDIELABEL, g_aLabelAttrs);
+
+
+/**
+ * Tag names and descriptors.
+ */
+static const struct RTDWARFTAGDESC
+{
+ /** The tag value. */
+ uint16_t uTag;
+ /** The tag name as string. */
+ const char *pszName;
+ /** The DIE descriptor to use. */
+ PCRTDWARFDIEDESC pDesc;
+} g_aTagDescs[] =
+{
+#define TAGDESC(a_Name, a_pDesc) { DW_ ## a_Name, #a_Name, a_pDesc }
+#define TAGDESC_EMPTY() { 0, NULL, &g_CoreDieDesc }
+#define TAGDESC_CORE(a_Name) TAGDESC(a_Name, &g_CoreDieDesc)
+ TAGDESC_EMPTY(), /* 0x00 */
+ TAGDESC_CORE(TAG_array_type),
+ TAGDESC_CORE(TAG_class_type),
+ TAGDESC_CORE(TAG_entry_point),
+ TAGDESC_CORE(TAG_enumeration_type), /* 0x04 */
+ TAGDESC_CORE(TAG_formal_parameter),
+ TAGDESC_EMPTY(),
+ TAGDESC_EMPTY(),
+ TAGDESC_CORE(TAG_imported_declaration), /* 0x08 */
+ TAGDESC_EMPTY(),
+ TAGDESC(TAG_label, &g_LabelDesc),
+ TAGDESC_CORE(TAG_lexical_block),
+ TAGDESC_EMPTY(), /* 0x0c */
+ TAGDESC_CORE(TAG_member),
+ TAGDESC_EMPTY(),
+ TAGDESC_CORE(TAG_pointer_type),
+ TAGDESC_CORE(TAG_reference_type), /* 0x10 */
+ TAGDESC_CORE(TAG_compile_unit),
+ TAGDESC_CORE(TAG_string_type),
+ TAGDESC_CORE(TAG_structure_type),
+ TAGDESC_EMPTY(), /* 0x14 */
+ TAGDESC_CORE(TAG_subroutine_type),
+ TAGDESC_CORE(TAG_typedef),
+ TAGDESC_CORE(TAG_union_type),
+ TAGDESC_CORE(TAG_unspecified_parameters), /* 0x18 */
+ TAGDESC_CORE(TAG_variant),
+ TAGDESC_CORE(TAG_common_block),
+ TAGDESC_CORE(TAG_common_inclusion),
+ TAGDESC_CORE(TAG_inheritance), /* 0x1c */
+ TAGDESC_CORE(TAG_inlined_subroutine),
+ TAGDESC_CORE(TAG_module),
+ TAGDESC_CORE(TAG_ptr_to_member_type),
+ TAGDESC_CORE(TAG_set_type), /* 0x20 */
+ TAGDESC_CORE(TAG_subrange_type),
+ TAGDESC_CORE(TAG_with_stmt),
+ TAGDESC_CORE(TAG_access_declaration),
+ TAGDESC_CORE(TAG_base_type), /* 0x24 */
+ TAGDESC_CORE(TAG_catch_block),
+ TAGDESC_CORE(TAG_const_type),
+ TAGDESC_CORE(TAG_constant),
+ TAGDESC_CORE(TAG_enumerator), /* 0x28 */
+ TAGDESC_CORE(TAG_file_type),
+ TAGDESC_CORE(TAG_friend),
+ TAGDESC_CORE(TAG_namelist),
+ TAGDESC_CORE(TAG_namelist_item), /* 0x2c */
+ TAGDESC_CORE(TAG_packed_type),
+ TAGDESC(TAG_subprogram, &g_SubProgramDesc),
+ TAGDESC_CORE(TAG_template_type_parameter),
+ TAGDESC_CORE(TAG_template_value_parameter), /* 0x30 */
+ TAGDESC_CORE(TAG_thrown_type),
+ TAGDESC_CORE(TAG_try_block),
+ TAGDESC_CORE(TAG_variant_part),
+ TAGDESC_CORE(TAG_variable), /* 0x34 */
+ TAGDESC_CORE(TAG_volatile_type),
+ TAGDESC_CORE(TAG_dwarf_procedure),
+ TAGDESC_CORE(TAG_restrict_type),
+ TAGDESC_CORE(TAG_interface_type), /* 0x38 */
+ TAGDESC_CORE(TAG_namespace),
+ TAGDESC_CORE(TAG_imported_module),
+ TAGDESC_CORE(TAG_unspecified_type),
+ TAGDESC_CORE(TAG_partial_unit), /* 0x3c */
+ TAGDESC_CORE(TAG_imported_unit),
+ TAGDESC_EMPTY(),
+ TAGDESC_CORE(TAG_condition),
+ TAGDESC_CORE(TAG_shared_type), /* 0x40 */
+ TAGDESC_CORE(TAG_type_unit),
+ TAGDESC_CORE(TAG_rvalue_reference_type),
+ TAGDESC_CORE(TAG_template_alias)
+#undef TAGDESC
+#undef TAGDESC_EMPTY
+#undef TAGDESC_CORE
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtDwarfInfo_ParseDie(PRTDBGMODDWARF pThis, PRTDWARFDIE pDie, PCRTDWARFDIEDESC pDieDesc,
+ PRTDWARFCURSOR pCursor, PCRTDWARFABBREV pAbbrev, bool fInitDie);
+
+
+
+#if defined(LOG_ENABLED) || defined(RT_STRICT)
+
+# if 0 /* unused */
+/**
+ * Turns a tag value into a string for logging purposes.
+ *
+ * @returns String name.
+ * @param uTag The tag.
+ */
+static const char *rtDwarfLog_GetTagName(uint32_t uTag)
+{
+ if (uTag < RT_ELEMENTS(g_aTagDescs))
+ {
+ const char *pszTag = g_aTagDescs[uTag].pszName;
+ if (pszTag)
+ return pszTag;
+ }
+
+ static char s_szStatic[32];
+ RTStrPrintf(s_szStatic, sizeof(s_szStatic),"DW_TAG_%#x", uTag);
+ return s_szStatic;
+}
+# endif
+
+
+/**
+ * Turns an attributevalue into a string for logging purposes.
+ *
+ * @returns String name.
+ * @param uAttr The attribute.
+ */
+static const char *rtDwarfLog_AttrName(uint32_t uAttr)
+{
+ switch (uAttr)
+ {
+ RT_CASE_RET_STR(DW_AT_sibling);
+ RT_CASE_RET_STR(DW_AT_location);
+ RT_CASE_RET_STR(DW_AT_name);
+ RT_CASE_RET_STR(DW_AT_ordering);
+ RT_CASE_RET_STR(DW_AT_byte_size);
+ RT_CASE_RET_STR(DW_AT_bit_offset);
+ RT_CASE_RET_STR(DW_AT_bit_size);
+ RT_CASE_RET_STR(DW_AT_stmt_list);
+ RT_CASE_RET_STR(DW_AT_low_pc);
+ RT_CASE_RET_STR(DW_AT_high_pc);
+ RT_CASE_RET_STR(DW_AT_language);
+ RT_CASE_RET_STR(DW_AT_discr);
+ RT_CASE_RET_STR(DW_AT_discr_value);
+ RT_CASE_RET_STR(DW_AT_visibility);
+ RT_CASE_RET_STR(DW_AT_import);
+ RT_CASE_RET_STR(DW_AT_string_length);
+ RT_CASE_RET_STR(DW_AT_common_reference);
+ RT_CASE_RET_STR(DW_AT_comp_dir);
+ RT_CASE_RET_STR(DW_AT_const_value);
+ RT_CASE_RET_STR(DW_AT_containing_type);
+ RT_CASE_RET_STR(DW_AT_default_value);
+ RT_CASE_RET_STR(DW_AT_inline);
+ RT_CASE_RET_STR(DW_AT_is_optional);
+ RT_CASE_RET_STR(DW_AT_lower_bound);
+ RT_CASE_RET_STR(DW_AT_producer);
+ RT_CASE_RET_STR(DW_AT_prototyped);
+ RT_CASE_RET_STR(DW_AT_return_addr);
+ RT_CASE_RET_STR(DW_AT_start_scope);
+ RT_CASE_RET_STR(DW_AT_bit_stride);
+ RT_CASE_RET_STR(DW_AT_upper_bound);
+ RT_CASE_RET_STR(DW_AT_abstract_origin);
+ RT_CASE_RET_STR(DW_AT_accessibility);
+ RT_CASE_RET_STR(DW_AT_address_class);
+ RT_CASE_RET_STR(DW_AT_artificial);
+ RT_CASE_RET_STR(DW_AT_base_types);
+ RT_CASE_RET_STR(DW_AT_calling_convention);
+ RT_CASE_RET_STR(DW_AT_count);
+ RT_CASE_RET_STR(DW_AT_data_member_location);
+ RT_CASE_RET_STR(DW_AT_decl_column);
+ RT_CASE_RET_STR(DW_AT_decl_file);
+ RT_CASE_RET_STR(DW_AT_decl_line);
+ RT_CASE_RET_STR(DW_AT_declaration);
+ RT_CASE_RET_STR(DW_AT_discr_list);
+ RT_CASE_RET_STR(DW_AT_encoding);
+ RT_CASE_RET_STR(DW_AT_external);
+ RT_CASE_RET_STR(DW_AT_frame_base);
+ RT_CASE_RET_STR(DW_AT_friend);
+ RT_CASE_RET_STR(DW_AT_identifier_case);
+ RT_CASE_RET_STR(DW_AT_macro_info);
+ RT_CASE_RET_STR(DW_AT_namelist_item);
+ RT_CASE_RET_STR(DW_AT_priority);
+ RT_CASE_RET_STR(DW_AT_segment);
+ RT_CASE_RET_STR(DW_AT_specification);
+ RT_CASE_RET_STR(DW_AT_static_link);
+ RT_CASE_RET_STR(DW_AT_type);
+ RT_CASE_RET_STR(DW_AT_use_location);
+ RT_CASE_RET_STR(DW_AT_variable_parameter);
+ RT_CASE_RET_STR(DW_AT_virtuality);
+ RT_CASE_RET_STR(DW_AT_vtable_elem_location);
+ RT_CASE_RET_STR(DW_AT_allocated);
+ RT_CASE_RET_STR(DW_AT_associated);
+ RT_CASE_RET_STR(DW_AT_data_location);
+ RT_CASE_RET_STR(DW_AT_byte_stride);
+ RT_CASE_RET_STR(DW_AT_entry_pc);
+ RT_CASE_RET_STR(DW_AT_use_UTF8);
+ RT_CASE_RET_STR(DW_AT_extension);
+ RT_CASE_RET_STR(DW_AT_ranges);
+ RT_CASE_RET_STR(DW_AT_trampoline);
+ RT_CASE_RET_STR(DW_AT_call_column);
+ RT_CASE_RET_STR(DW_AT_call_file);
+ RT_CASE_RET_STR(DW_AT_call_line);
+ RT_CASE_RET_STR(DW_AT_description);
+ RT_CASE_RET_STR(DW_AT_binary_scale);
+ RT_CASE_RET_STR(DW_AT_decimal_scale);
+ RT_CASE_RET_STR(DW_AT_small);
+ RT_CASE_RET_STR(DW_AT_decimal_sign);
+ RT_CASE_RET_STR(DW_AT_digit_count);
+ RT_CASE_RET_STR(DW_AT_picture_string);
+ RT_CASE_RET_STR(DW_AT_mutable);
+ RT_CASE_RET_STR(DW_AT_threads_scaled);
+ RT_CASE_RET_STR(DW_AT_explicit);
+ RT_CASE_RET_STR(DW_AT_object_pointer);
+ RT_CASE_RET_STR(DW_AT_endianity);
+ RT_CASE_RET_STR(DW_AT_elemental);
+ RT_CASE_RET_STR(DW_AT_pure);
+ RT_CASE_RET_STR(DW_AT_recursive);
+ RT_CASE_RET_STR(DW_AT_signature);
+ RT_CASE_RET_STR(DW_AT_main_subprogram);
+ RT_CASE_RET_STR(DW_AT_data_bit_offset);
+ RT_CASE_RET_STR(DW_AT_const_expr);
+ RT_CASE_RET_STR(DW_AT_enum_class);
+ RT_CASE_RET_STR(DW_AT_linkage_name);
+ RT_CASE_RET_STR(DW_AT_MIPS_linkage_name);
+ RT_CASE_RET_STR(DW_AT_WATCOM_memory_model);
+ RT_CASE_RET_STR(DW_AT_WATCOM_references_start);
+ RT_CASE_RET_STR(DW_AT_WATCOM_parm_entry);
+ }
+ static char s_szStatic[32];
+ RTStrPrintf(s_szStatic, sizeof(s_szStatic),"DW_AT_%#x", uAttr);
+ return s_szStatic;
+}
+
+
+/**
+ * Turns a form value into a string for logging purposes.
+ *
+ * @returns String name.
+ * @param uForm The form.
+ */
+static const char *rtDwarfLog_FormName(uint32_t uForm)
+{
+ switch (uForm)
+ {
+ RT_CASE_RET_STR(DW_FORM_addr);
+ RT_CASE_RET_STR(DW_FORM_block2);
+ RT_CASE_RET_STR(DW_FORM_block4);
+ RT_CASE_RET_STR(DW_FORM_data2);
+ RT_CASE_RET_STR(DW_FORM_data4);
+ RT_CASE_RET_STR(DW_FORM_data8);
+ RT_CASE_RET_STR(DW_FORM_string);
+ RT_CASE_RET_STR(DW_FORM_block);
+ RT_CASE_RET_STR(DW_FORM_block1);
+ RT_CASE_RET_STR(DW_FORM_data1);
+ RT_CASE_RET_STR(DW_FORM_flag);
+ RT_CASE_RET_STR(DW_FORM_sdata);
+ RT_CASE_RET_STR(DW_FORM_strp);
+ RT_CASE_RET_STR(DW_FORM_udata);
+ RT_CASE_RET_STR(DW_FORM_ref_addr);
+ RT_CASE_RET_STR(DW_FORM_ref1);
+ RT_CASE_RET_STR(DW_FORM_ref2);
+ RT_CASE_RET_STR(DW_FORM_ref4);
+ RT_CASE_RET_STR(DW_FORM_ref8);
+ RT_CASE_RET_STR(DW_FORM_ref_udata);
+ RT_CASE_RET_STR(DW_FORM_indirect);
+ RT_CASE_RET_STR(DW_FORM_sec_offset);
+ RT_CASE_RET_STR(DW_FORM_exprloc);
+ RT_CASE_RET_STR(DW_FORM_flag_present);
+ RT_CASE_RET_STR(DW_FORM_ref_sig8);
+ }
+ static char s_szStatic[32];
+ RTStrPrintf(s_szStatic, sizeof(s_szStatic),"DW_FORM_%#x", uForm);
+ return s_szStatic;
+}
+
+#endif /* LOG_ENABLED || RT_STRICT */
+
+
+
+/** @callback_method_impl{FNRTLDRENUMSEGS} */
+static DECLCALLBACK(int) rtDbgModDwarfScanSegmentsCallback(RTLDRMOD hLdrMod, PCRTLDRSEG pSeg, void *pvUser)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pvUser;
+ Log(("Segment %.*s: LinkAddress=%#llx RVA=%#llx cb=%#llx\n",
+ pSeg->cchName, pSeg->pszName, (uint64_t)pSeg->LinkAddress, (uint64_t)pSeg->RVA, pSeg->cb));
+ NOREF(hLdrMod);
+
+ /* Count relevant segments. */
+ if (pSeg->RVA != NIL_RTLDRADDR)
+ pThis->cSegs++;
+
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTLDRENUMSEGS} */
+static DECLCALLBACK(int) rtDbgModDwarfAddSegmentsCallback(RTLDRMOD hLdrMod, PCRTLDRSEG pSeg, void *pvUser)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pvUser;
+ Log(("Segment %.*s: LinkAddress=%#llx RVA=%#llx cb=%#llx cbMapped=%#llx\n",
+ pSeg->cchName, pSeg->pszName, (uint64_t)pSeg->LinkAddress, (uint64_t)pSeg->RVA, pSeg->cb, pSeg->cbMapped));
+ NOREF(hLdrMod);
+ Assert(pSeg->cchName > 0);
+ Assert(!pSeg->pszName[pSeg->cchName]);
+
+ /* If the segment doesn't have a mapping, just add a dummy so the indexing
+ works out correctly (same as for the image). */
+ if (pSeg->RVA == NIL_RTLDRADDR)
+ return RTDbgModSegmentAdd(pThis->hCnt, 0, 0, pSeg->pszName, 0 /*fFlags*/, NULL);
+
+ /* The link address is 0 for all segments in a relocatable ELF image. */
+ RTLDRADDR cb = pSeg->cb;
+ if ( cb < pSeg->cbMapped
+ && RTLdrGetFormat(hLdrMod) != RTLDRFMT_LX /* for debugging our drivers; 64KB section align by linker, 4KB by loader. */
+ )
+ cb = pSeg->cbMapped;
+ return RTDbgModSegmentAdd(pThis->hCnt, pSeg->RVA, cb, pSeg->pszName, 0 /*fFlags*/, NULL);
+}
+
+
+/**
+ * Calls rtDbgModDwarfAddSegmentsCallback for each segment in the executable
+ * image.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ */
+static int rtDbgModDwarfAddSegmentsFromImage(PRTDBGMODDWARF pThis)
+{
+ AssertReturn(pThis->pImgMod && pThis->pImgMod->pImgVt, VERR_INTERNAL_ERROR_2);
+ Assert(!pThis->cSegs);
+ int rc = pThis->pImgMod->pImgVt->pfnEnumSegments(pThis->pImgMod, rtDbgModDwarfScanSegmentsCallback, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->cSegs == 0)
+ pThis->iWatcomPass = 1;
+ else
+ {
+ pThis->cSegs = 0;
+ pThis->iWatcomPass = -1;
+ rc = pThis->pImgMod->pImgVt->pfnEnumSegments(pThis->pImgMod, rtDbgModDwarfAddSegmentsCallback, pThis);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Looks up a segment.
+ *
+ * @returns Pointer to the segment on success, NULL if not found.
+ * @param pThis The DWARF instance.
+ * @param uSeg The segment number / selector.
+ */
+static PRTDBGDWARFSEG rtDbgModDwarfFindSegment(PRTDBGMODDWARF pThis, RTSEL uSeg)
+{
+ uint32_t cSegs = pThis->cSegs;
+ uint32_t iSeg = pThis->iSegHint;
+ PRTDBGDWARFSEG paSegs = pThis->paSegs;
+ if ( iSeg < cSegs
+ && paSegs[iSeg].uSegment == uSeg)
+ return &paSegs[iSeg];
+
+ for (iSeg = 0; iSeg < cSegs; iSeg++)
+ if (uSeg == paSegs[iSeg].uSegment)
+ {
+ pThis->iSegHint = iSeg;
+ return &paSegs[iSeg];
+ }
+
+ AssertFailed();
+ return NULL;
+}
+
+
+/**
+ * Record a segment:offset during pass 1.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param uSeg The segment number / selector.
+ * @param offSeg The segment offset.
+ */
+static int rtDbgModDwarfRecordSegOffset(PRTDBGMODDWARF pThis, RTSEL uSeg, uint64_t offSeg)
+{
+ /* Look up the segment. */
+ uint32_t cSegs = pThis->cSegs;
+ uint32_t iSeg = pThis->iSegHint;
+ PRTDBGDWARFSEG paSegs = pThis->paSegs;
+ if ( iSeg >= cSegs
+ || paSegs[iSeg].uSegment != uSeg)
+ {
+ for (iSeg = 0; iSeg < cSegs; iSeg++)
+ if (uSeg <= paSegs[iSeg].uSegment)
+ break;
+ if ( iSeg >= cSegs
+ || paSegs[iSeg].uSegment != uSeg)
+ {
+ /* Add */
+ void *pvNew = RTMemRealloc(paSegs, (pThis->cSegs + 1) * sizeof(paSegs[0]));
+ if (!pvNew)
+ return VERR_NO_MEMORY;
+ pThis->paSegs = paSegs = (PRTDBGDWARFSEG)pvNew;
+ if (iSeg != cSegs)
+ memmove(&paSegs[iSeg + 1], &paSegs[iSeg], (cSegs - iSeg) * sizeof(paSegs[0]));
+ paSegs[iSeg].offHighest = offSeg;
+ paSegs[iSeg].uBaseAddr = 0;
+ paSegs[iSeg].cbSegment = 0;
+ paSegs[iSeg].uSegment = uSeg;
+ pThis->cSegs++;
+ }
+
+ pThis->iSegHint = iSeg;
+ }
+
+ /* Increase it's range? */
+ if (paSegs[iSeg].offHighest < offSeg)
+ {
+ Log3(("rtDbgModDwarfRecordSegOffset: iSeg=%d uSeg=%#06x offSeg=%#llx\n", iSeg, uSeg, offSeg));
+ paSegs[iSeg].offHighest = offSeg;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Calls pfnSegmentAdd for each segment in the executable image.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ */
+static int rtDbgModDwarfAddSegmentsFromPass1(PRTDBGMODDWARF pThis)
+{
+ AssertReturn(pThis->cSegs, VERR_DWARF_BAD_INFO);
+ uint32_t const cSegs = pThis->cSegs;
+ PRTDBGDWARFSEG paSegs = pThis->paSegs;
+
+ /*
+ * Are the segments assigned more or less in numerical order?
+ */
+ if ( paSegs[0].uSegment < 16U
+ && paSegs[cSegs - 1].uSegment - paSegs[0].uSegment + 1U <= cSegs + 16U)
+ {
+ /** @todo heuristics, plase. */
+ AssertFailedReturn(VERR_DWARF_TODO);
+
+ }
+ /*
+ * Assume DOS segmentation.
+ */
+ else
+ {
+ for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
+ paSegs[iSeg].uBaseAddr = (uint32_t)paSegs[iSeg].uSegment << 16;
+ for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
+ paSegs[iSeg].cbSegment = paSegs[iSeg].offHighest;
+ }
+
+ /*
+ * Add them.
+ */
+ for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
+ {
+ Log3(("rtDbgModDwarfAddSegmentsFromPass1: Seg#%u: %#010llx LB %#llx uSegment=%#x\n",
+ iSeg, paSegs[iSeg].uBaseAddr, paSegs[iSeg].cbSegment, paSegs[iSeg].uSegment));
+ char szName[32];
+ RTStrPrintf(szName, sizeof(szName), "seg-%#04xh", paSegs[iSeg].uSegment);
+ int rc = RTDbgModSegmentAdd(pThis->hCnt, paSegs[iSeg].uBaseAddr, paSegs[iSeg].cbSegment,
+ szName, 0 /*fFlags*/, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads a DWARF section from the image file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param enmSect The section to load.
+ */
+static int rtDbgModDwarfLoadSection(PRTDBGMODDWARF pThis, krtDbgModDwarfSect enmSect)
+{
+ /*
+ * Don't load stuff twice.
+ */
+ if (pThis->aSections[enmSect].pv)
+ return VINF_SUCCESS;
+
+ /*
+ * Sections that are not present cannot be loaded, treat them like they
+ * are empty
+ */
+ if (!pThis->aSections[enmSect].fPresent)
+ {
+ Assert(pThis->aSections[enmSect].cb);
+ return VINF_SUCCESS;
+ }
+ if (!pThis->aSections[enmSect].cb)
+ return VINF_SUCCESS;
+
+ /*
+ * Sections must be readable with the current image interface.
+ */
+ if (pThis->aSections[enmSect].offFile < 0)
+ return VERR_OUT_OF_RANGE;
+
+ /*
+ * Do the job.
+ */
+ return pThis->pDbgInfoMod->pImgVt->pfnMapPart(pThis->pDbgInfoMod,
+ pThis->aSections[enmSect].iDbgInfo,
+ pThis->aSections[enmSect].offFile,
+ pThis->aSections[enmSect].cb,
+ &pThis->aSections[enmSect].pv);
+}
+
+
+#ifdef SOME_UNUSED_FUNCTION
+/**
+ * Unloads a DWARF section previously mapped by rtDbgModDwarfLoadSection.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param enmSect The section to unload.
+ */
+static int rtDbgModDwarfUnloadSection(PRTDBGMODDWARF pThis, krtDbgModDwarfSect enmSect)
+{
+ if (!pThis->aSections[enmSect].pv)
+ return VINF_SUCCESS;
+
+ int rc = pThis->pDbgInfoMod->pImgVt->pfnUnmapPart(pThis->pDbgInfoMod, pThis->aSections[enmSect].cb, &pThis->aSections[enmSect].pv);
+ AssertRC(rc);
+ return rc;
+}
+#endif
+
+
+/**
+ * Converts to UTF-8 or otherwise makes sure it's valid UTF-8.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param ppsz Pointer to the string pointer. May be
+ * reallocated (RTStr*).
+ */
+static int rtDbgModDwarfStringToUtf8(PRTDBGMODDWARF pThis, char **ppsz)
+{
+ /** @todo DWARF & UTF-8. */
+ NOREF(pThis);
+ RTStrPurgeEncoding(*ppsz);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Convers a link address into a segment+offset or RVA.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param uSegment The segment, 0 if not applicable.
+ * @param LinkAddress The address to convert..
+ * @param piSeg The segment index.
+ * @param poffSeg Where to return the segment offset.
+ */
+static int rtDbgModDwarfLinkAddressToSegOffset(PRTDBGMODDWARF pThis, RTSEL uSegment, uint64_t LinkAddress,
+ PRTDBGSEGIDX piSeg, PRTLDRADDR poffSeg)
+{
+ if (pThis->paSegs)
+ {
+ PRTDBGDWARFSEG pSeg = rtDbgModDwarfFindSegment(pThis, uSegment);
+ if (pSeg)
+ {
+ *piSeg = pSeg - pThis->paSegs;
+ *poffSeg = LinkAddress;
+ return VINF_SUCCESS;
+ }
+ }
+
+ if (pThis->fUseLinkAddress)
+ return pThis->pImgMod->pImgVt->pfnLinkAddressToSegOffset(pThis->pImgMod, LinkAddress, piSeg, poffSeg);
+
+ /* If we have a non-zero segment number, assume it's correct for now.
+ This helps loading watcom linked LX drivers. */
+ if (uSegment > 0)
+ {
+ *piSeg = uSegment - 1;
+ *poffSeg = LinkAddress;
+ return VINF_SUCCESS;
+ }
+
+ return pThis->pImgMod->pImgVt->pfnRvaToSegOffset(pThis->pImgMod, LinkAddress, piSeg, poffSeg);
+}
+
+
+/**
+ * Converts a segment+offset address into an RVA.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param idxSegment The segment index.
+ * @param offSegment The segment offset.
+ * @param puRva Where to return the calculated RVA.
+ */
+static int rtDbgModDwarfSegOffsetToRva(PRTDBGMODDWARF pThis, RTDBGSEGIDX idxSegment, uint64_t offSegment, PRTUINTPTR puRva)
+{
+ if (pThis->paSegs)
+ {
+ PRTDBGDWARFSEG pSeg = rtDbgModDwarfFindSegment(pThis, idxSegment);
+ if (pSeg)
+ {
+ *puRva = pSeg->uBaseAddr + offSegment;
+ return VINF_SUCCESS;
+ }
+ }
+
+ RTUINTPTR uRva = RTDbgModSegmentRva(pThis->pImgMod, idxSegment);
+ if (uRva != RTUINTPTR_MAX)
+ {
+ *puRva = uRva + offSegment;
+ return VINF_SUCCESS;
+ }
+ return VERR_INVALID_POINTER;
+}
+
+/**
+ * Converts a segment+offset address into an RVA.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param uRva The RVA to convert.
+ * @param pidxSegment Where to return the segment index.
+ * @param poffSegment Where to return the segment offset.
+ */
+static int rtDbgModDwarfRvaToSegOffset(PRTDBGMODDWARF pThis, RTUINTPTR uRva, RTDBGSEGIDX *pidxSegment, uint64_t *poffSegment)
+{
+ RTUINTPTR offSeg = 0;
+ RTDBGSEGIDX idxSeg = RTDbgModRvaToSegOff(pThis->pImgMod, uRva, &offSeg);
+ if (idxSeg != NIL_RTDBGSEGIDX)
+ {
+ *pidxSegment = idxSeg;
+ *poffSegment = offSeg;
+ return VINF_SUCCESS;
+ }
+ return VERR_INVALID_POINTER;
+}
+
+
+
+/*
+ *
+ * DWARF Cursor.
+ * DWARF Cursor.
+ * DWARF Cursor.
+ *
+ */
+
+
+/**
+ * Reads a 8-bit unsigned integer and advances the cursor.
+ *
+ * @returns 8-bit unsigned integer. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue What to return on read error.
+ */
+static uint8_t rtDwarfCursor_GetU8(PRTDWARFCURSOR pCursor, uint8_t uErrValue)
+{
+ if (pCursor->cbUnitLeft < 1)
+ {
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ return uErrValue;
+ }
+
+ uint8_t u8 = pCursor->pb[0];
+ pCursor->pb += 1;
+ pCursor->cbUnitLeft -= 1;
+ pCursor->cbLeft -= 1;
+ return u8;
+}
+
+
+/**
+ * Reads a 16-bit unsigned integer and advances the cursor.
+ *
+ * @returns 16-bit unsigned integer. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue What to return on read error.
+ */
+static uint16_t rtDwarfCursor_GetU16(PRTDWARFCURSOR pCursor, uint16_t uErrValue)
+{
+ if (pCursor->cbUnitLeft < 2)
+ {
+ pCursor->pb += pCursor->cbUnitLeft;
+ pCursor->cbLeft -= pCursor->cbUnitLeft;
+ pCursor->cbUnitLeft = 0;
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ return uErrValue;
+ }
+
+ uint16_t u16 = RT_MAKE_U16(pCursor->pb[0], pCursor->pb[1]);
+ pCursor->pb += 2;
+ pCursor->cbUnitLeft -= 2;
+ pCursor->cbLeft -= 2;
+ if (!pCursor->fNativEndian)
+ u16 = RT_BSWAP_U16(u16);
+ return u16;
+}
+
+
+/**
+ * Reads a 32-bit unsigned integer and advances the cursor.
+ *
+ * @returns 32-bit unsigned integer. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue What to return on read error.
+ */
+static uint32_t rtDwarfCursor_GetU32(PRTDWARFCURSOR pCursor, uint32_t uErrValue)
+{
+ if (pCursor->cbUnitLeft < 4)
+ {
+ pCursor->pb += pCursor->cbUnitLeft;
+ pCursor->cbLeft -= pCursor->cbUnitLeft;
+ pCursor->cbUnitLeft = 0;
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ return uErrValue;
+ }
+
+ uint32_t u32 = RT_MAKE_U32_FROM_U8(pCursor->pb[0], pCursor->pb[1], pCursor->pb[2], pCursor->pb[3]);
+ pCursor->pb += 4;
+ pCursor->cbUnitLeft -= 4;
+ pCursor->cbLeft -= 4;
+ if (!pCursor->fNativEndian)
+ u32 = RT_BSWAP_U32(u32);
+ return u32;
+}
+
+
+/**
+ * Reads a 64-bit unsigned integer and advances the cursor.
+ *
+ * @returns 64-bit unsigned integer. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue What to return on read error.
+ */
+static uint64_t rtDwarfCursor_GetU64(PRTDWARFCURSOR pCursor, uint64_t uErrValue)
+{
+ if (pCursor->cbUnitLeft < 8)
+ {
+ pCursor->pb += pCursor->cbUnitLeft;
+ pCursor->cbLeft -= pCursor->cbUnitLeft;
+ pCursor->cbUnitLeft = 0;
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ return uErrValue;
+ }
+
+ uint64_t u64 = RT_MAKE_U64_FROM_U8(pCursor->pb[0], pCursor->pb[1], pCursor->pb[2], pCursor->pb[3],
+ pCursor->pb[4], pCursor->pb[5], pCursor->pb[6], pCursor->pb[7]);
+ pCursor->pb += 8;
+ pCursor->cbUnitLeft -= 8;
+ pCursor->cbLeft -= 8;
+ if (!pCursor->fNativEndian)
+ u64 = RT_BSWAP_U64(u64);
+ return u64;
+}
+
+
+/**
+ * Reads an unsigned LEB128 encoded number.
+ *
+ * @returns unsigned 64-bit number. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue The value to return on error.
+ */
+static uint64_t rtDwarfCursor_GetULeb128(PRTDWARFCURSOR pCursor, uint64_t uErrValue)
+{
+ if (pCursor->cbUnitLeft < 1)
+ {
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ return uErrValue;
+ }
+
+ /*
+ * Special case - single byte.
+ */
+ uint8_t b = pCursor->pb[0];
+ if (!(b & 0x80))
+ {
+ pCursor->pb += 1;
+ pCursor->cbUnitLeft -= 1;
+ pCursor->cbLeft -= 1;
+ return b;
+ }
+
+ /*
+ * Generic case.
+ */
+ /* Decode. */
+ uint32_t off = 1;
+ uint64_t u64Ret = b & 0x7f;
+ do
+ {
+ if (off == pCursor->cbUnitLeft)
+ {
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ u64Ret = uErrValue;
+ break;
+ }
+ b = pCursor->pb[off];
+ u64Ret |= (b & 0x7f) << off * 7;
+ off++;
+ } while (b & 0x80);
+
+ /* Update the cursor. */
+ pCursor->pb += off;
+ pCursor->cbUnitLeft -= off;
+ pCursor->cbLeft -= off;
+
+ /* Check the range. */
+ uint32_t cBits = off * 7;
+ if (cBits > 64)
+ {
+ pCursor->rc = VERR_DWARF_LEB_OVERFLOW;
+ u64Ret = uErrValue;
+ }
+
+ return u64Ret;
+}
+
+
+/**
+ * Reads a signed LEB128 encoded number.
+ *
+ * @returns signed 64-bit number. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param sErrValue The value to return on error.
+ */
+static int64_t rtDwarfCursor_GetSLeb128(PRTDWARFCURSOR pCursor, int64_t sErrValue)
+{
+ if (pCursor->cbUnitLeft < 1)
+ {
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ return sErrValue;
+ }
+
+ /*
+ * Special case - single byte.
+ */
+ uint8_t b = pCursor->pb[0];
+ if (!(b & 0x80))
+ {
+ pCursor->pb += 1;
+ pCursor->cbUnitLeft -= 1;
+ pCursor->cbLeft -= 1;
+ if (b & 0x40)
+ b |= 0x80;
+ return (int8_t)b;
+ }
+
+ /*
+ * Generic case.
+ */
+ /* Decode it. */
+ uint32_t off = 1;
+ uint64_t u64Ret = b & 0x7f;
+ do
+ {
+ if (off == pCursor->cbUnitLeft)
+ {
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ u64Ret = (uint64_t)sErrValue;
+ break;
+ }
+ b = pCursor->pb[off];
+ u64Ret |= (b & 0x7f) << off * 7;
+ off++;
+ } while (b & 0x80);
+
+ /* Update cursor. */
+ pCursor->pb += off;
+ pCursor->cbUnitLeft -= off;
+ pCursor->cbLeft -= off;
+
+ /* Check the range. */
+ uint32_t cBits = off * 7;
+ if (cBits > 64)
+ {
+ pCursor->rc = VERR_DWARF_LEB_OVERFLOW;
+ u64Ret = (uint64_t)sErrValue;
+ }
+ /* Sign extend the value. */
+ else if (u64Ret & RT_BIT_64(cBits - 1))
+ u64Ret |= ~(RT_BIT_64(cBits - 1) - 1);
+
+ return (int64_t)u64Ret;
+}
+
+
+/**
+ * Reads an unsigned LEB128 encoded number, max 32-bit width.
+ *
+ * @returns unsigned 32-bit number. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue The value to return on error.
+ */
+static uint32_t rtDwarfCursor_GetULeb128AsU32(PRTDWARFCURSOR pCursor, uint32_t uErrValue)
+{
+ uint64_t u64 = rtDwarfCursor_GetULeb128(pCursor, uErrValue);
+ if (u64 > UINT32_MAX)
+ {
+ pCursor->rc = VERR_DWARF_LEB_OVERFLOW;
+ return uErrValue;
+ }
+ return (uint32_t)u64;
+}
+
+
+/**
+ * Reads a signed LEB128 encoded number, max 32-bit width.
+ *
+ * @returns signed 32-bit number. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param sErrValue The value to return on error.
+ */
+static int32_t rtDwarfCursor_GetSLeb128AsS32(PRTDWARFCURSOR pCursor, int32_t sErrValue)
+{
+ int64_t s64 = rtDwarfCursor_GetSLeb128(pCursor, sErrValue);
+ if (s64 > INT32_MAX || s64 < INT32_MIN)
+ {
+ pCursor->rc = VERR_DWARF_LEB_OVERFLOW;
+ return sErrValue;
+ }
+ return (int32_t)s64;
+}
+
+
+/**
+ * Skips a LEB128 encoded number.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ */
+static int rtDwarfCursor_SkipLeb128(PRTDWARFCURSOR pCursor)
+{
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+
+ if (pCursor->cbUnitLeft < 1)
+ return pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+
+ uint32_t offSkip = 1;
+ if (pCursor->pb[0] & 0x80)
+ do
+ {
+ if (offSkip == pCursor->cbUnitLeft)
+ {
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ break;
+ }
+ } while (pCursor->pb[offSkip++] & 0x80);
+
+ pCursor->pb += offSkip;
+ pCursor->cbUnitLeft -= offSkip;
+ pCursor->cbLeft -= offSkip;
+ return pCursor->rc;
+}
+
+
+/**
+ * Advances the cursor a given number of bytes.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param offSkip The number of bytes to advance.
+ */
+static int rtDwarfCursor_SkipBytes(PRTDWARFCURSOR pCursor, uint64_t offSkip)
+{
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+ if (pCursor->cbUnitLeft < offSkip)
+ return pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+
+ size_t const offSkipSizeT = (size_t)offSkip;
+ pCursor->cbUnitLeft -= offSkipSizeT;
+ pCursor->cbLeft -= offSkipSizeT;
+ pCursor->pb += offSkipSizeT;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads a zero terminated string, advancing the cursor beyond the terminator.
+ *
+ * @returns Pointer to the string.
+ * @param pCursor The cursor.
+ * @param pszErrValue What to return if the string isn't terminated
+ * before the end of the unit.
+ */
+static const char *rtDwarfCursor_GetSZ(PRTDWARFCURSOR pCursor, const char *pszErrValue)
+{
+ const char *pszRet = (const char *)pCursor->pb;
+ for (;;)
+ {
+ if (!pCursor->cbUnitLeft)
+ {
+ pCursor->rc = VERR_DWARF_BAD_STRING;
+ return pszErrValue;
+ }
+ pCursor->cbUnitLeft--;
+ pCursor->cbLeft--;
+ if (!*pCursor->pb++)
+ break;
+ }
+ return pszRet;
+}
+
+
+/**
+ * Reads a 1, 2, 4 or 8 byte unsigned value.
+ *
+ * @returns 64-bit unsigned value.
+ * @param pCursor The cursor.
+ * @param cbValue The value size.
+ * @param uErrValue The error value.
+ */
+static uint64_t rtDwarfCursor_GetVarSizedU(PRTDWARFCURSOR pCursor, size_t cbValue, uint64_t uErrValue)
+{
+ uint64_t u64Ret;
+ switch (cbValue)
+ {
+ case 1: u64Ret = rtDwarfCursor_GetU8( pCursor, UINT8_MAX); break;
+ case 2: u64Ret = rtDwarfCursor_GetU16(pCursor, UINT16_MAX); break;
+ case 4: u64Ret = rtDwarfCursor_GetU32(pCursor, UINT32_MAX); break;
+ case 8: u64Ret = rtDwarfCursor_GetU64(pCursor, UINT64_MAX); break;
+ default:
+ pCursor->rc = VERR_DWARF_BAD_INFO;
+ return uErrValue;
+ }
+ if (RT_FAILURE(pCursor->rc))
+ return uErrValue;
+ return u64Ret;
+}
+
+
+#if 0 /* unused */
+/**
+ * Gets the pointer to a variable size block and advances the cursor.
+ *
+ * @returns Pointer to the block at the current cursor location. On error
+ * RTDWARFCURSOR::rc is set and NULL returned.
+ * @param pCursor The cursor.
+ * @param cbBlock The block size.
+ */
+static const uint8_t *rtDwarfCursor_GetBlock(PRTDWARFCURSOR pCursor, uint32_t cbBlock)
+{
+ if (cbBlock > pCursor->cbUnitLeft)
+ {
+ pCursor->rc = VERR_DWARF_UNEXPECTED_END;
+ return NULL;
+ }
+
+ uint8_t const *pb = &pCursor->pb[0];
+ pCursor->pb += cbBlock;
+ pCursor->cbUnitLeft -= cbBlock;
+ pCursor->cbLeft -= cbBlock;
+ return pb;
+}
+#endif
+
+
+/**
+ * Reads an unsigned DWARF half number.
+ *
+ * @returns The number. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue What to return on error.
+ */
+static uint16_t rtDwarfCursor_GetUHalf(PRTDWARFCURSOR pCursor, uint16_t uErrValue)
+{
+ return rtDwarfCursor_GetU16(pCursor, uErrValue);
+}
+
+
+/**
+ * Reads an unsigned DWARF byte number.
+ *
+ * @returns The number. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue What to return on error.
+ */
+static uint8_t rtDwarfCursor_GetUByte(PRTDWARFCURSOR pCursor, uint8_t uErrValue)
+{
+ return rtDwarfCursor_GetU8(pCursor, uErrValue);
+}
+
+
+/**
+ * Reads a signed DWARF byte number.
+ *
+ * @returns The number. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param iErrValue What to return on error.
+ */
+static int8_t rtDwarfCursor_GetSByte(PRTDWARFCURSOR pCursor, int8_t iErrValue)
+{
+ return (int8_t)rtDwarfCursor_GetU8(pCursor, (uint8_t)iErrValue);
+}
+
+
+/**
+ * Reads a unsigned DWARF offset value.
+ *
+ * @returns The value. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue What to return on error.
+ */
+static uint64_t rtDwarfCursor_GetUOff(PRTDWARFCURSOR pCursor, uint64_t uErrValue)
+{
+ if (pCursor->f64bitDwarf)
+ return rtDwarfCursor_GetU64(pCursor, uErrValue);
+ return rtDwarfCursor_GetU32(pCursor, (uint32_t)uErrValue);
+}
+
+
+/**
+ * Reads a unsigned DWARF native offset value.
+ *
+ * @returns The value. On error RTDWARFCURSOR::rc is set and @a
+ * uErrValue is returned.
+ * @param pCursor The cursor.
+ * @param uErrValue What to return on error.
+ */
+static uint64_t rtDwarfCursor_GetNativeUOff(PRTDWARFCURSOR pCursor, uint64_t uErrValue)
+{
+ switch (pCursor->cbNativeAddr)
+ {
+ case 1: return rtDwarfCursor_GetU8(pCursor, (uint8_t )uErrValue);
+ case 2: return rtDwarfCursor_GetU16(pCursor, (uint16_t)uErrValue);
+ case 4: return rtDwarfCursor_GetU32(pCursor, (uint32_t)uErrValue);
+ case 8: return rtDwarfCursor_GetU64(pCursor, uErrValue);
+ default:
+ pCursor->rc = VERR_INTERNAL_ERROR_2;
+ return uErrValue;
+ }
+}
+
+
+/**
+ * Reads a 1, 2, 4 or 8 byte unsigned value.
+ *
+ * @returns 64-bit unsigned value.
+ * @param pCursor The cursor.
+ * @param bPtrEnc The pointer encoding.
+ * @param uErrValue The error value.
+ */
+static uint64_t rtDwarfCursor_GetPtrEnc(PRTDWARFCURSOR pCursor, uint8_t bPtrEnc, uint64_t uErrValue)
+{
+ uint64_t u64Ret;
+ switch (bPtrEnc & DW_EH_PE_FORMAT_MASK)
+ {
+ case DW_EH_PE_ptr:
+ u64Ret = rtDwarfCursor_GetNativeUOff(pCursor, uErrValue);
+ break;
+ case DW_EH_PE_uleb128:
+ u64Ret = rtDwarfCursor_GetULeb128(pCursor, uErrValue);
+ break;
+ case DW_EH_PE_udata2:
+ u64Ret = rtDwarfCursor_GetU16(pCursor, UINT16_MAX);
+ break;
+ case DW_EH_PE_udata4:
+ u64Ret = rtDwarfCursor_GetU32(pCursor, UINT32_MAX);
+ break;
+ case DW_EH_PE_udata8:
+ u64Ret = rtDwarfCursor_GetU64(pCursor, UINT64_MAX);
+ break;
+ case DW_EH_PE_sleb128:
+ u64Ret = rtDwarfCursor_GetSLeb128(pCursor, uErrValue);
+ break;
+ case DW_EH_PE_sdata2:
+ u64Ret = (int64_t)(int16_t)rtDwarfCursor_GetU16(pCursor, UINT16_MAX);
+ break;
+ case DW_EH_PE_sdata4:
+ u64Ret = (int64_t)(int32_t)rtDwarfCursor_GetU32(pCursor, UINT32_MAX);
+ break;
+ case DW_EH_PE_sdata8:
+ u64Ret = rtDwarfCursor_GetU64(pCursor, UINT64_MAX);
+ break;
+ default:
+ pCursor->rc = VERR_DWARF_BAD_INFO;
+ return uErrValue;
+ }
+ if (RT_FAILURE(pCursor->rc))
+ return uErrValue;
+ return u64Ret;
+}
+
+
+/**
+ * Gets the unit length, updating the unit length member and DWARF bitness
+ * members of the cursor.
+ *
+ * @returns The unit length.
+ * @param pCursor The cursor.
+ */
+static uint64_t rtDwarfCursor_GetInitialLength(PRTDWARFCURSOR pCursor)
+{
+ /*
+ * Read the initial length.
+ */
+ pCursor->cbUnitLeft = pCursor->cbLeft;
+ uint64_t cbUnit = rtDwarfCursor_GetU32(pCursor, 0);
+ if (cbUnit != UINT32_C(0xffffffff))
+ pCursor->f64bitDwarf = false;
+ else
+ {
+ pCursor->f64bitDwarf = true;
+ cbUnit = rtDwarfCursor_GetU64(pCursor, 0);
+ }
+
+
+ /*
+ * Set the unit length, quitely fixing bad lengths.
+ */
+ pCursor->cbUnitLeft = (size_t)cbUnit;
+ if ( pCursor->cbUnitLeft > pCursor->cbLeft
+ || pCursor->cbUnitLeft != cbUnit)
+ pCursor->cbUnitLeft = pCursor->cbLeft;
+
+ return cbUnit;
+}
+
+
+/**
+ * Calculates the section offset corresponding to the current cursor position.
+ *
+ * @returns 32-bit section offset. If out of range, RTDWARFCURSOR::rc will be
+ * set and UINT32_MAX returned.
+ * @param pCursor The cursor.
+ */
+static uint32_t rtDwarfCursor_CalcSectOffsetU32(PRTDWARFCURSOR pCursor)
+{
+ size_t off = pCursor->pb - pCursor->pbStart;
+ uint32_t offRet = (uint32_t)off;
+ if (offRet != off)
+ {
+ AssertFailed();
+ pCursor->rc = VERR_OUT_OF_RANGE;
+ offRet = UINT32_MAX;
+ }
+ return offRet;
+}
+
+
+/**
+ * Calculates an absolute cursor position from one relative to the current
+ * cursor position.
+ *
+ * @returns The absolute cursor position.
+ * @param pCursor The cursor.
+ * @param offRelative The relative position. Must be a positive
+ * offset.
+ */
+static uint8_t const *rtDwarfCursor_CalcPos(PRTDWARFCURSOR pCursor, size_t offRelative)
+{
+ if (offRelative > pCursor->cbUnitLeft)
+ {
+ Log(("rtDwarfCursor_CalcPos: bad position %#zx, cbUnitLeft=%#zu\n", offRelative, pCursor->cbUnitLeft));
+ pCursor->rc = VERR_DWARF_BAD_POS;
+ return NULL;
+ }
+ return pCursor->pb + offRelative;
+}
+
+
+/**
+ * Advances the cursor to the given position.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param pbNewPos The new position - returned by
+ * rtDwarfCursor_CalcPos().
+ */
+static int rtDwarfCursor_AdvanceToPos(PRTDWARFCURSOR pCursor, uint8_t const *pbNewPos)
+{
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+ AssertPtr(pbNewPos);
+ if ((uintptr_t)pbNewPos < (uintptr_t)pCursor->pb)
+ {
+ Log(("rtDwarfCursor_AdvanceToPos: bad position %p, current %p\n", pbNewPos, pCursor->pb));
+ return pCursor->rc = VERR_DWARF_BAD_POS;
+ }
+
+ uintptr_t cbAdj = (uintptr_t)pbNewPos - (uintptr_t)pCursor->pb;
+ if (RT_UNLIKELY(cbAdj > pCursor->cbUnitLeft))
+ {
+ AssertFailed();
+ pCursor->rc = VERR_DWARF_BAD_POS;
+ cbAdj = pCursor->cbUnitLeft;
+ }
+
+ pCursor->cbUnitLeft -= cbAdj;
+ pCursor->cbLeft -= cbAdj;
+ pCursor->pb += cbAdj;
+ return pCursor->rc;
+}
+
+
+/**
+ * Check if the cursor is at the end of the current DWARF unit.
+ *
+ * @retval true if at the end or a cursor error is pending.
+ * @retval false if not.
+ * @param pCursor The cursor.
+ */
+static bool rtDwarfCursor_IsAtEndOfUnit(PRTDWARFCURSOR pCursor)
+{
+ return !pCursor->cbUnitLeft || RT_FAILURE(pCursor->rc);
+}
+
+
+/**
+ * Skips to the end of the current unit.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ */
+static int rtDwarfCursor_SkipUnit(PRTDWARFCURSOR pCursor)
+{
+ pCursor->pb += pCursor->cbUnitLeft;
+ pCursor->cbLeft -= pCursor->cbUnitLeft;
+ pCursor->cbUnitLeft = 0;
+ return pCursor->rc;
+}
+
+
+/**
+ * Check if the cursor is at the end of the section (or whatever the cursor is
+ * processing).
+ *
+ * @retval true if at the end or a cursor error is pending.
+ * @retval false if not.
+ * @param pCursor The cursor.
+ */
+static bool rtDwarfCursor_IsAtEnd(PRTDWARFCURSOR pCursor)
+{
+ return !pCursor->cbLeft || RT_FAILURE(pCursor->rc);
+}
+
+
+/**
+ * Initialize a section reader cursor.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param pThis The dwarf module.
+ * @param enmSect The name of the section to read.
+ */
+static int rtDwarfCursor_Init(PRTDWARFCURSOR pCursor, PRTDBGMODDWARF pThis, krtDbgModDwarfSect enmSect)
+{
+ int rc = rtDbgModDwarfLoadSection(pThis, enmSect);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pCursor->enmSect = enmSect;
+ pCursor->pbStart = (uint8_t const *)pThis->aSections[enmSect].pv;
+ pCursor->pb = pCursor->pbStart;
+ pCursor->cbLeft = pThis->aSections[enmSect].cb;
+ pCursor->cbUnitLeft = pCursor->cbLeft;
+ pCursor->pDwarfMod = pThis;
+ pCursor->f64bitDwarf = false;
+ /** @todo ask the image about the endian used as well as the address
+ * width. */
+ pCursor->fNativEndian = true;
+ pCursor->cbNativeAddr = 4;
+ pCursor->rc = VINF_SUCCESS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize a section reader cursor with a skip offset.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param pThis The dwarf module.
+ * @param enmSect The name of the section to read.
+ * @param offSect The offset to skip into the section.
+ */
+static int rtDwarfCursor_InitWithOffset(PRTDWARFCURSOR pCursor, PRTDBGMODDWARF pThis,
+ krtDbgModDwarfSect enmSect, uint32_t offSect)
+{
+ if (offSect > pThis->aSections[enmSect].cb)
+ {
+ Log(("rtDwarfCursor_InitWithOffset: offSect=%#x cb=%#x enmSect=%d\n", offSect, pThis->aSections[enmSect].cb, enmSect));
+ return VERR_DWARF_BAD_POS;
+ }
+
+ int rc = rtDwarfCursor_Init(pCursor, pThis, enmSect);
+ if (RT_SUCCESS(rc))
+ {
+ /* pCursor->pbStart += offSect; - we're skipping, offsets are relative to start of section... */
+ pCursor->pb += offSect;
+ pCursor->cbLeft -= offSect;
+ pCursor->cbUnitLeft -= offSect;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Initialize a cursor for a block (subsection) retrieved from the given cursor.
+ *
+ * The parent cursor will be advanced past the block.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param pParent The parent cursor. Will be moved by @a cbBlock.
+ * @param cbBlock The size of the block the new cursor should
+ * cover.
+ */
+static int rtDwarfCursor_InitForBlock(PRTDWARFCURSOR pCursor, PRTDWARFCURSOR pParent, uint32_t cbBlock)
+{
+ if (RT_FAILURE(pParent->rc))
+ return pParent->rc;
+ if (pParent->cbUnitLeft < cbBlock)
+ {
+ Log(("rtDwarfCursor_InitForBlock: cbUnitLeft=%#x < cbBlock=%#x \n", pParent->cbUnitLeft, cbBlock));
+ return VERR_DWARF_BAD_POS;
+ }
+
+ *pCursor = *pParent;
+ pCursor->cbLeft = cbBlock;
+ pCursor->cbUnitLeft = cbBlock;
+
+ pParent->pb += cbBlock;
+ pParent->cbLeft -= cbBlock;
+ pParent->cbUnitLeft -= cbBlock;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize a reader cursor for a memory block (eh_frame).
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param pvMem The memory block.
+ * @param cbMem The size of the memory block.
+ */
+static int rtDwarfCursor_InitForMem(PRTDWARFCURSOR pCursor, void const *pvMem, size_t cbMem)
+{
+ pCursor->enmSect = krtDbgModDwarfSect_End;
+ pCursor->pbStart = (uint8_t const *)pvMem;
+ pCursor->pb = (uint8_t const *)pvMem;
+ pCursor->cbLeft = cbMem;
+ pCursor->cbUnitLeft = cbMem;
+ pCursor->pDwarfMod = NULL;
+ pCursor->f64bitDwarf = false;
+ /** @todo ask the image about the endian used as well as the address
+ * width. */
+ pCursor->fNativEndian = true;
+ pCursor->cbNativeAddr = 4;
+ pCursor->rc = VINF_SUCCESS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deletes a section reader initialized by rtDwarfCursor_Init.
+ *
+ * @returns @a rcOther or RTDWARCURSOR::rc.
+ * @param pCursor The section reader.
+ * @param rcOther Other error code to be returned if it indicates
+ * error or if the cursor status is OK.
+ */
+static int rtDwarfCursor_Delete(PRTDWARFCURSOR pCursor, int rcOther)
+{
+ /* ... and a drop of poison. */
+ pCursor->pb = NULL;
+ pCursor->cbLeft = ~(size_t)0;
+ pCursor->cbUnitLeft = ~(size_t)0;
+ pCursor->pDwarfMod = NULL;
+ if (RT_FAILURE(pCursor->rc) && RT_SUCCESS(rcOther))
+ rcOther = pCursor->rc;
+ pCursor->rc = VERR_INTERNAL_ERROR_4;
+ return rcOther;
+}
+
+
+/*
+ *
+ * DWARF Frame Unwind Information.
+ * DWARF Frame Unwind Information.
+ * DWARF Frame Unwind Information.
+ *
+ */
+
+/**
+ * Common information entry (CIE) information.
+ */
+typedef struct RTDWARFCIEINFO
+{
+ /** The segment location of the CIE. */
+ uint64_t offCie;
+ /** The DWARF version. */
+ uint8_t uDwarfVer;
+ /** The address pointer encoding. */
+ uint8_t bAddressPtrEnc;
+ /** The segment size (v4). */
+ uint8_t cbSegment;
+ /** The return register column. UINT8_MAX if default register. */
+ uint8_t bRetReg;
+ /** The LSDA pointer encoding. */
+ uint8_t bLsdaPtrEnc;
+
+ /** Set if the EH data field is present ('eh'). */
+ bool fHasEhData : 1;
+ /** Set if there is an augmentation data size ('z'). */
+ bool fHasAugmentationSize : 1;
+ /** Set if the augmentation data contains a LSDA (pointer size byte in CIE,
+ * pointer in FDA) ('L'). */
+ bool fHasLanguageSpecificDataArea : 1;
+ /** Set if the augmentation data contains a personality routine
+ * (pointer size + pointer) ('P'). */
+ bool fHasPersonalityRoutine : 1;
+ /** Set if the augmentation data contains the address encoding . */
+ bool fHasAddressEnc : 1;
+ /** Set if signal frame. */
+ bool fIsSignalFrame : 1;
+ /** Set if we've encountered unknown augmentation data. This
+ * means the CIE is incomplete and cannot be used. */
+ bool fHasUnknowAugmentation : 1;
+
+ /** Copy of the augmentation string. */
+ const char *pszAugmentation;
+
+ /** Code alignment factor for the instruction. */
+ uint64_t uCodeAlignFactor;
+ /** Data alignment factor for the instructions. */
+ int64_t iDataAlignFactor;
+
+ /** Pointer to the instruction sequence. */
+ uint8_t const *pbInstructions;
+ /** The length of the instruction sequence. */
+ size_t cbInstructions;
+} RTDWARFCIEINFO;
+/** Pointer to CIE info. */
+typedef RTDWARFCIEINFO *PRTDWARFCIEINFO;
+/** Pointer to const CIE info. */
+typedef RTDWARFCIEINFO const *PCRTDWARFCIEINFO;
+
+
+/** Number of registers we care about.
+ * @note We're currently not expecting to be decoding ppc, arm, ia64 or such,
+ * only x86 and x86_64. We can easily increase the column count. */
+#define RTDWARFCF_MAX_REGISTERS 96
+
+
+/**
+ * Call frame state row.
+ */
+typedef struct RTDWARFCFROW
+{
+ /** Stack worked by DW_CFA_remember_state and DW_CFA_restore_state. */
+ struct RTDWARFCFROW *pNextOnStack;
+
+ /** @name CFA - Canonical frame address expression.
+ * Since there are partial CFA instructions, we cannot be lazy like with the
+ * register but keep register+offset around. For DW_CFA_def_cfa_expression
+ * we just take down the program location, though.
+ * @{ */
+ /** Pointer to DW_CFA_def_cfa_expression instruction, NULL if reg+offset. */
+ uint8_t const *pbCfaExprInstr;
+ /** The CFA register offset. */
+ int64_t offCfaReg;
+ /** The CFA base register number. */
+ uint16_t uCfaBaseReg;
+ /** Set if we've got a valid CFA definition. */
+ bool fCfaDefined : 1;
+ /** @} */
+
+ /** Set if on the heap and needs freeing. */
+ bool fOnHeap : 1;
+ /** Pointer to the instructions bytes defining registers.
+ * NULL means */
+ uint8_t const *apbRegInstrs[RTDWARFCF_MAX_REGISTERS];
+} RTDWARFCFROW;
+typedef RTDWARFCFROW *PRTDWARFCFROW;
+typedef RTDWARFCFROW const *PCRTDWARFCFROW;
+
+/** Row program execution state. */
+typedef struct RTDWARFCFEXEC
+{
+ PRTDWARFCFROW pRow;
+ /** Number of PC bytes left to advance before we get a hit. */
+ uint64_t cbLeftToAdvance;
+ /** Number of pushed rows. */
+ uint32_t cPushes;
+ /** Set if little endian, clear if big endian. */
+ bool fLittleEndian;
+ /** The CIE. */
+ PCRTDWARFCIEINFO pCie;
+ /** The program counter value for the FDE. Subjected to segment.
+ * Needed for DW_CFA_set_loc. */
+ uint64_t uPcBegin;
+ /** The offset relative to uPcBegin for which we're searching for a row.
+ * Needed for DW_CFA_set_loc. */
+ uint64_t offInRange;
+} RTDWARFCFEXEC;
+typedef RTDWARFCFEXEC *PRTDWARFCFEXEC;
+
+
+/* Set of macros for getting and skipping operands. */
+#define SKIP_ULEB128_OR_LEB128() \
+ do \
+ { \
+ AssertReturn(offInstr < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO); \
+ } while (pbInstr[offInstr++] & 0x80)
+
+#define GET_ULEB128_AS_U14(a_uDst) \
+ do \
+ { \
+ AssertReturn(offInstr < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO); \
+ uint8_t b = pbInstr[offInstr++]; \
+ (a_uDst) = b & 0x7f; \
+ if (b & 0x80) \
+ { \
+ AssertReturn(offInstr < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO); \
+ b = pbInstr[offInstr++]; \
+ AssertReturn(!(b & 0x80), VERR_DBG_MALFORMED_UNWIND_INFO); \
+ (a_uDst) |= (uint16_t)b << 7; \
+ } \
+ } while (0)
+#define GET_ULEB128_AS_U63(a_uDst) \
+ do \
+ { \
+ AssertReturn(offInstr < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO); \
+ uint8_t b = pbInstr[offInstr++]; \
+ (a_uDst) = b & 0x7f; \
+ if (b & 0x80) \
+ { \
+ unsigned cShift = 7; \
+ do \
+ { \
+ AssertReturn(offInstr < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO); \
+ AssertReturn(cShift < 63, VERR_DWARF_LEB_OVERFLOW); \
+ b = pbInstr[offInstr++]; \
+ (a_uDst) |= (uint16_t)(b & 0x7f) << cShift; \
+ cShift += 7; \
+ } while (b & 0x80); \
+ } \
+ } while (0)
+#define GET_LEB128_AS_I63(a_uDst) \
+ do \
+ { \
+ AssertReturn(offInstr < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO); \
+ uint8_t b = pbInstr[offInstr++]; \
+ if (!(b & 0x80)) \
+ (a_uDst) = !(b & 0x40) ? b : (int64_t)(int8_t)(b | 0x80); \
+ else \
+ { \
+ /* Read value into unsigned variable: */ \
+ unsigned cShift = 7; \
+ uint64_t uTmp = b & 0x7f; \
+ do \
+ { \
+ AssertReturn(offInstr < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO); \
+ AssertReturn(cShift < 63, VERR_DWARF_LEB_OVERFLOW); \
+ b = pbInstr[offInstr++]; \
+ uTmp |= (uint16_t)(b & 0x7f) << cShift; \
+ cShift += 7; \
+ } while (b & 0x80); \
+ /* Sign extend before setting the destination value: */ \
+ cShift -= 7 + 1; \
+ if (uTmp & RT_BIT_64(cShift)) \
+ uTmp |= ~(RT_BIT_64(cShift) - 1); \
+ (a_uDst) = (int64_t)uTmp; \
+ } \
+ } while (0)
+
+#define SKIP_BLOCK() \
+ do \
+ { \
+ uint16_t cbBlock; \
+ GET_ULEB128_AS_U14(cbBlock); \
+ AssertReturn(offInstr + cbBlock <= cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO); \
+ offInstr += cbBlock; \
+ } while (0)
+
+
+static int rtDwarfUnwind_Execute(PRTDWARFCFEXEC pExecState, uint8_t const *pbInstr, uint32_t cbInstr)
+{
+ PRTDWARFCFROW pRow = pExecState->pRow;
+ for (uint32_t offInstr = 0; offInstr < cbInstr;)
+ {
+ /*
+ * Instruction switches.
+ */
+ uint8_t const bInstr = pbInstr[offInstr++];
+ switch (bInstr & DW_CFA_high_bit_mask)
+ {
+ case DW_CFA_advance_loc:
+ {
+ uint8_t const cbAdvance = bInstr & ~DW_CFA_high_bit_mask;
+ if (cbAdvance > pExecState->cbLeftToAdvance)
+ return VINF_SUCCESS;
+ pExecState->cbLeftToAdvance -= cbAdvance;
+ break;
+ }
+
+ case DW_CFA_offset:
+ {
+ uint8_t iReg = bInstr & ~DW_CFA_high_bit_mask;
+ if (iReg < RT_ELEMENTS(pRow->apbRegInstrs))
+ pRow->apbRegInstrs[iReg] = &pbInstr[offInstr - 1];
+ SKIP_ULEB128_OR_LEB128();
+ break;
+ }
+
+ case 0:
+ switch (bInstr)
+ {
+ case DW_CFA_nop:
+ break;
+
+ /*
+ * Register instructions.
+ */
+ case DW_CFA_register:
+ case DW_CFA_offset_extended:
+ case DW_CFA_offset_extended_sf:
+ case DW_CFA_val_offset:
+ case DW_CFA_val_offset_sf:
+ {
+ uint8_t const * const pbCurInstr = &pbInstr[offInstr - 1];
+ uint16_t iReg;
+ GET_ULEB128_AS_U14(iReg);
+ if (iReg < RT_ELEMENTS(pRow->apbRegInstrs))
+ pRow->apbRegInstrs[iReg] = pbCurInstr;
+ SKIP_ULEB128_OR_LEB128();
+ break;
+ }
+
+ case DW_CFA_expression:
+ case DW_CFA_val_expression:
+ {
+ uint8_t const * const pbCurInstr = &pbInstr[offInstr - 1];
+ uint16_t iReg;
+ GET_ULEB128_AS_U14(iReg);
+ if (iReg < RT_ELEMENTS(pRow->apbRegInstrs))
+ pRow->apbRegInstrs[iReg] = pbCurInstr;
+ SKIP_BLOCK();
+ break;
+ }
+
+ case DW_CFA_restore_extended:
+ {
+ uint8_t const * const pbCurInstr = &pbInstr[offInstr - 1];
+ uint16_t iReg;
+ GET_ULEB128_AS_U14(iReg);
+ if (iReg < RT_ELEMENTS(pRow->apbRegInstrs))
+ pRow->apbRegInstrs[iReg] = pbCurInstr;
+ break;
+ }
+
+ case DW_CFA_undefined:
+ {
+ uint16_t iReg;
+ GET_ULEB128_AS_U14(iReg);
+ if (iReg < RT_ELEMENTS(pRow->apbRegInstrs))
+ pRow->apbRegInstrs[iReg] = NULL;
+ break;
+ }
+
+ case DW_CFA_same_value:
+ {
+ uint8_t const * const pbCurInstr = &pbInstr[offInstr - 1];
+ uint16_t iReg;
+ GET_ULEB128_AS_U14(iReg);
+ if (iReg < RT_ELEMENTS(pRow->apbRegInstrs))
+ pRow->apbRegInstrs[iReg] = pbCurInstr;
+ break;
+ }
+
+
+ /*
+ * CFA instructions.
+ */
+ case DW_CFA_def_cfa:
+ {
+ GET_ULEB128_AS_U14(pRow->uCfaBaseReg);
+ uint64_t offCfaReg;
+ GET_ULEB128_AS_U63(offCfaReg);
+ pRow->offCfaReg = offCfaReg;
+ pRow->pbCfaExprInstr = NULL;
+ pRow->fCfaDefined = true;
+ break;
+ }
+
+ case DW_CFA_def_cfa_register:
+ {
+ GET_ULEB128_AS_U14(pRow->uCfaBaseReg);
+ pRow->pbCfaExprInstr = NULL;
+ pRow->fCfaDefined = true;
+ /* Leaves offCfaReg as is. */
+ break;
+ }
+
+ case DW_CFA_def_cfa_offset:
+ {
+ uint64_t offCfaReg;
+ GET_ULEB128_AS_U63(offCfaReg);
+ pRow->offCfaReg = offCfaReg;
+ pRow->pbCfaExprInstr = NULL;
+ pRow->fCfaDefined = true;
+ /* Leaves uCfaBaseReg as is. */
+ break;
+ }
+
+ case DW_CFA_def_cfa_sf:
+ GET_ULEB128_AS_U14(pRow->uCfaBaseReg);
+ GET_LEB128_AS_I63(pRow->offCfaReg);
+ pRow->pbCfaExprInstr = NULL;
+ pRow->fCfaDefined = true;
+ break;
+
+ case DW_CFA_def_cfa_offset_sf:
+ GET_LEB128_AS_I63(pRow->offCfaReg);
+ pRow->pbCfaExprInstr = NULL;
+ pRow->fCfaDefined = true;
+ /* Leaves uCfaBaseReg as is. */
+ break;
+
+ case DW_CFA_def_cfa_expression:
+ pRow->pbCfaExprInstr = &pbInstr[offInstr - 1];
+ pRow->fCfaDefined = true;
+ SKIP_BLOCK();
+ break;
+
+ /*
+ * Less likely instructions:
+ */
+ case DW_CFA_advance_loc1:
+ {
+ AssertReturn(offInstr < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO);
+ uint8_t const cbAdvance = pbInstr[offInstr++];
+ if (cbAdvance > pExecState->cbLeftToAdvance)
+ return VINF_SUCCESS;
+ pExecState->cbLeftToAdvance -= cbAdvance;
+ break;
+ }
+
+ case DW_CFA_advance_loc2:
+ {
+ AssertReturn(offInstr + 1 < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO);
+ uint16_t const cbAdvance = pExecState->fLittleEndian
+ ? RT_MAKE_U16(pbInstr[offInstr], pbInstr[offInstr + 1])
+ : RT_MAKE_U16(pbInstr[offInstr + 1], pbInstr[offInstr]);
+ if (cbAdvance > pExecState->cbLeftToAdvance)
+ return VINF_SUCCESS;
+ pExecState->cbLeftToAdvance -= cbAdvance;
+ offInstr += 2;
+ break;
+ }
+
+ case DW_CFA_advance_loc4:
+ {
+ AssertReturn(offInstr + 3 < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO);
+ uint32_t const cbAdvance = pExecState->fLittleEndian
+ ? RT_MAKE_U32_FROM_U8(pbInstr[offInstr + 0], pbInstr[offInstr + 1],
+ pbInstr[offInstr + 2], pbInstr[offInstr + 3])
+ : RT_MAKE_U32_FROM_U8(pbInstr[offInstr + 3], pbInstr[offInstr + 2],
+ pbInstr[offInstr + 1], pbInstr[offInstr + 0]);
+ if (cbAdvance > pExecState->cbLeftToAdvance)
+ return VINF_SUCCESS;
+ pExecState->cbLeftToAdvance -= cbAdvance;
+ offInstr += 4;
+ break;
+ }
+
+ /*
+ * This bugger is really annoying and probably never used.
+ */
+ case DW_CFA_set_loc:
+ {
+ /* Ignore the segment number. */
+ if (pExecState->pCie->cbSegment)
+ {
+ offInstr += pExecState->pCie->cbSegment;
+ AssertReturn(offInstr < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO);
+ }
+
+ /* Retrieve the address. sigh. */
+ uint64_t uAddress;
+ switch (pExecState->pCie->bAddressPtrEnc & (DW_EH_PE_FORMAT_MASK | DW_EH_PE_indirect))
+ {
+ case DW_EH_PE_udata2:
+ AssertReturn(offInstr + 1 < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO);
+ if (pExecState->fLittleEndian)
+ uAddress = RT_MAKE_U16(pbInstr[offInstr], pbInstr[offInstr + 1]);
+ else
+ uAddress = RT_MAKE_U16(pbInstr[offInstr + 1], pbInstr[offInstr]);
+ offInstr += 2;
+ break;
+ case DW_EH_PE_sdata2:
+ AssertReturn(offInstr + 1 < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO);
+ if (pExecState->fLittleEndian)
+ uAddress = (int64_t)(int16_t)RT_MAKE_U16(pbInstr[offInstr], pbInstr[offInstr + 1]);
+ else
+ uAddress = (int64_t)(int16_t)RT_MAKE_U16(pbInstr[offInstr + 1], pbInstr[offInstr]);
+ offInstr += 2;
+ break;
+ case DW_EH_PE_udata4:
+ AssertReturn(offInstr + 3 < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO);
+ if (pExecState->fLittleEndian)
+ uAddress = RT_MAKE_U32_FROM_U8(pbInstr[offInstr + 0], pbInstr[offInstr + 1],
+ pbInstr[offInstr + 2], pbInstr[offInstr + 3]);
+ else
+ uAddress = RT_MAKE_U32_FROM_U8(pbInstr[offInstr + 3], pbInstr[offInstr + 2],
+ pbInstr[offInstr + 1], pbInstr[offInstr + 0]);
+
+ offInstr += 4;
+ break;
+ case DW_EH_PE_sdata4:
+ AssertReturn(offInstr + 3 < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO);
+ if (pExecState->fLittleEndian)
+ uAddress = (int64_t)(int32_t)RT_MAKE_U32_FROM_U8(pbInstr[offInstr + 0], pbInstr[offInstr + 1],
+ pbInstr[offInstr + 2], pbInstr[offInstr + 3]);
+ else
+ uAddress = (int64_t)(int32_t)RT_MAKE_U32_FROM_U8(pbInstr[offInstr + 3], pbInstr[offInstr + 2],
+ pbInstr[offInstr + 1], pbInstr[offInstr + 0]);
+ offInstr += 4;
+ break;
+ case DW_EH_PE_udata8:
+ case DW_EH_PE_sdata8:
+ AssertReturn(offInstr + 7 < cbInstr, VERR_DBG_MALFORMED_UNWIND_INFO);
+ if (pExecState->fLittleEndian)
+ uAddress = RT_MAKE_U64_FROM_U8(pbInstr[offInstr + 0], pbInstr[offInstr + 1],
+ pbInstr[offInstr + 2], pbInstr[offInstr + 3],
+ pbInstr[offInstr + 4], pbInstr[offInstr + 5],
+ pbInstr[offInstr + 6], pbInstr[offInstr + 7]);
+ else
+ uAddress = RT_MAKE_U64_FROM_U8(pbInstr[offInstr + 7], pbInstr[offInstr + 6],
+ pbInstr[offInstr + 5], pbInstr[offInstr + 4],
+ pbInstr[offInstr + 3], pbInstr[offInstr + 2],
+ pbInstr[offInstr + 1], pbInstr[offInstr + 0]);
+ offInstr += 8;
+ break;
+ case DW_EH_PE_sleb128:
+ case DW_EH_PE_uleb128:
+ default:
+ AssertMsgFailedReturn(("%#x\n", pExecState->pCie->bAddressPtrEnc), VERR_DWARF_TODO);
+ }
+ AssertReturn(uAddress >= pExecState->uPcBegin, VERR_DBG_MALFORMED_UNWIND_INFO);
+
+ /* Did we advance past the desire address already? */
+ if (uAddress > pExecState->uPcBegin + pExecState->offInRange)
+ return VINF_SUCCESS;
+ pExecState->cbLeftToAdvance = pExecState->uPcBegin + pExecState->offInRange - uAddress;
+ break;
+
+
+ /*
+ * Row state push/pop instructions.
+ */
+
+ case DW_CFA_remember_state:
+ {
+ AssertReturn(pExecState->cPushes < 10, VERR_DBG_MALFORMED_UNWIND_INFO);
+ PRTDWARFCFROW pNewRow = (PRTDWARFCFROW)RTMemTmpAlloc(sizeof(*pNewRow));
+ AssertReturn(pNewRow, VERR_NO_TMP_MEMORY);
+ memcpy(pNewRow, pRow, sizeof(*pNewRow));
+ pNewRow->pNextOnStack = pRow;
+ pNewRow->fOnHeap = true;
+ pExecState->pRow = pNewRow;
+ pExecState->cPushes += 1;
+ pRow = pNewRow;
+ break;
+ }
+
+ case DW_CFA_restore_state:
+ AssertReturn(pRow->pNextOnStack, VERR_DBG_MALFORMED_UNWIND_INFO);
+ Assert(pRow->fOnHeap);
+ Assert(pExecState->cPushes > 0);
+ pExecState->cPushes -= 1;
+ pExecState->pRow = pRow->pNextOnStack;
+ RTMemTmpFree(pRow);
+ pRow = pExecState->pRow;
+ break;
+ }
+ }
+ break;
+
+ case DW_CFA_restore:
+ {
+ uint8_t const * const pbCurInstr = &pbInstr[offInstr - 1];
+ uint8_t const iReg = bInstr & ~DW_CFA_high_bit_mask;
+ if (iReg < RT_ELEMENTS(pRow->apbRegInstrs))
+ pRow->apbRegInstrs[iReg] = pbCurInstr;
+ break;
+ }
+ }
+ }
+ return VINF_TRY_AGAIN;
+}
+
+
+/**
+ * Register getter for AMD64.
+ *
+ * @returns true if found, false if not.
+ * @param pState The unwind state to get the register from.
+ * @param iReg The dwarf register number.
+ * @param puValue Where to store the register value.
+ */
+static bool rtDwarfUnwind_Amd64GetRegFromState(PCRTDBGUNWINDSTATE pState, uint16_t iReg, uint64_t *puValue)
+{
+ switch (iReg)
+ {
+ case DWREG_AMD64_RAX: *puValue = pState->u.x86.auRegs[X86_GREG_xAX]; return true;
+ case DWREG_AMD64_RDX: *puValue = pState->u.x86.auRegs[X86_GREG_xDX]; return true;
+ case DWREG_AMD64_RCX: *puValue = pState->u.x86.auRegs[X86_GREG_xCX]; return true;
+ case DWREG_AMD64_RBX: *puValue = pState->u.x86.auRegs[X86_GREG_xBX]; return true;
+ case DWREG_AMD64_RSI: *puValue = pState->u.x86.auRegs[X86_GREG_xSI]; return true;
+ case DWREG_AMD64_RDI: *puValue = pState->u.x86.auRegs[X86_GREG_xDI]; return true;
+ case DWREG_AMD64_RBP: *puValue = pState->u.x86.auRegs[X86_GREG_xBP]; return true;
+ case DWREG_AMD64_RSP: *puValue = pState->u.x86.auRegs[X86_GREG_xSP]; return true;
+ case DWREG_AMD64_R8: *puValue = pState->u.x86.auRegs[X86_GREG_x8]; return true;
+ case DWREG_AMD64_R9: *puValue = pState->u.x86.auRegs[X86_GREG_x9]; return true;
+ case DWREG_AMD64_R10: *puValue = pState->u.x86.auRegs[X86_GREG_x10]; return true;
+ case DWREG_AMD64_R11: *puValue = pState->u.x86.auRegs[X86_GREG_x11]; return true;
+ case DWREG_AMD64_R12: *puValue = pState->u.x86.auRegs[X86_GREG_x12]; return true;
+ case DWREG_AMD64_R13: *puValue = pState->u.x86.auRegs[X86_GREG_x13]; return true;
+ case DWREG_AMD64_R14: *puValue = pState->u.x86.auRegs[X86_GREG_x14]; return true;
+ case DWREG_AMD64_R15: *puValue = pState->u.x86.auRegs[X86_GREG_x15]; return true;
+ case DWREG_AMD64_RFLAGS: *puValue = pState->u.x86.uRFlags; return true;
+ case DWREG_AMD64_ES: *puValue = pState->u.x86.auSegs[X86_SREG_ES]; return true;
+ case DWREG_AMD64_CS: *puValue = pState->u.x86.auSegs[X86_SREG_CS]; return true;
+ case DWREG_AMD64_SS: *puValue = pState->u.x86.auSegs[X86_SREG_SS]; return true;
+ case DWREG_AMD64_DS: *puValue = pState->u.x86.auSegs[X86_SREG_DS]; return true;
+ case DWREG_AMD64_FS: *puValue = pState->u.x86.auSegs[X86_SREG_FS]; return true;
+ case DWREG_AMD64_GS: *puValue = pState->u.x86.auSegs[X86_SREG_GS]; return true;
+ }
+ return false;
+}
+
+
+/**
+ * Register getter for 386+.
+ *
+ * @returns true if found, false if not.
+ * @param pState The unwind state to get the register from.
+ * @param iReg The dwarf register number.
+ * @param puValue Where to store the register value.
+ */
+static bool rtDwarfUnwind_X86GetRegFromState(PCRTDBGUNWINDSTATE pState, uint16_t iReg, uint64_t *puValue)
+{
+ switch (iReg)
+ {
+ case DWREG_X86_EAX: *puValue = pState->u.x86.auRegs[X86_GREG_xAX]; return true;
+ case DWREG_X86_ECX: *puValue = pState->u.x86.auRegs[X86_GREG_xCX]; return true;
+ case DWREG_X86_EDX: *puValue = pState->u.x86.auRegs[X86_GREG_xDX]; return true;
+ case DWREG_X86_EBX: *puValue = pState->u.x86.auRegs[X86_GREG_xBX]; return true;
+ case DWREG_X86_ESP: *puValue = pState->u.x86.auRegs[X86_GREG_xSP]; return true;
+ case DWREG_X86_EBP: *puValue = pState->u.x86.auRegs[X86_GREG_xBP]; return true;
+ case DWREG_X86_ESI: *puValue = pState->u.x86.auRegs[X86_GREG_xSI]; return true;
+ case DWREG_X86_EDI: *puValue = pState->u.x86.auRegs[X86_GREG_xDI]; return true;
+ case DWREG_X86_EFLAGS: *puValue = pState->u.x86.uRFlags; return true;
+ case DWREG_X86_ES: *puValue = pState->u.x86.auSegs[X86_SREG_ES]; return true;
+ case DWREG_X86_CS: *puValue = pState->u.x86.auSegs[X86_SREG_CS]; return true;
+ case DWREG_X86_SS: *puValue = pState->u.x86.auSegs[X86_SREG_SS]; return true;
+ case DWREG_X86_DS: *puValue = pState->u.x86.auSegs[X86_SREG_DS]; return true;
+ case DWREG_X86_FS: *puValue = pState->u.x86.auSegs[X86_SREG_FS]; return true;
+ case DWREG_X86_GS: *puValue = pState->u.x86.auSegs[X86_SREG_GS]; return true;
+ }
+ return false;
+}
+
+/** Register getter. */
+typedef bool FNDWARFUNWINDGEREGFROMSTATE(PCRTDBGUNWINDSTATE pState, uint16_t iReg, uint64_t *puValue);
+/** Pointer to a register getter. */
+typedef FNDWARFUNWINDGEREGFROMSTATE *PFNDWARFUNWINDGEREGFROMSTATE;
+
+
+
+/**
+ * Does the heavy work for figuring out the return value of a register.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_NOT_FOUND if register is undefined.
+ *
+ * @param pRow The DWARF unwind table "row" to use.
+ * @param uReg The DWARF register number.
+ * @param pCie The corresponding CIE.
+ * @param uCfa The canonical frame address to use.
+ * @param pState The unwind to use when reading stack.
+ * @param pOldState The unwind state to get register values from.
+ * @param pfnGetReg The register value getter.
+ * @param puValue Where to store the return value.
+ * @param cbValue The size this register would have on the stack.
+ */
+static int rtDwarfUnwind_CalcRegisterValue(PRTDWARFCFROW pRow, unsigned uReg, PCRTDWARFCIEINFO pCie, uint64_t uCfa,
+ PRTDBGUNWINDSTATE pState, PCRTDBGUNWINDSTATE pOldState,
+ PFNDWARFUNWINDGEREGFROMSTATE pfnGetReg, uint64_t *puValue, uint8_t cbValue)
+{
+ Assert(uReg < RT_ELEMENTS(pRow->apbRegInstrs));
+ uint8_t const *pbInstr = pRow->apbRegInstrs[uReg];
+ if (!pbInstr)
+ return VERR_NOT_FOUND;
+
+ uint32_t cbInstr = UINT32_MAX / 2;
+ uint32_t offInstr = 1;
+ uint8_t const bInstr = *pbInstr;
+ switch (bInstr)
+ {
+ default:
+ if ((bInstr & DW_CFA_high_bit_mask) == DW_CFA_offset)
+ {
+ uint64_t offCfa;
+ GET_ULEB128_AS_U63(offCfa);
+ int rc = pState->pfnReadStack(pState, uCfa + (int64_t)offCfa * pCie->iDataAlignFactor, cbValue, puValue);
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_offset %#RX64: %Rrc, %#RX64\n", uReg, uCfa + (int64_t)offCfa * pCie->iDataAlignFactor, rc, *puValue));
+ return rc;
+ }
+ AssertReturn((bInstr & DW_CFA_high_bit_mask) == DW_CFA_restore, VERR_INTERNAL_ERROR);
+ RT_FALL_THRU();
+ case DW_CFA_restore_extended:
+ /* Need to search the CIE for the rule. */
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_restore/extended:\n", uReg));
+ AssertFailedReturn(VERR_DWARF_TODO);
+
+ case DW_CFA_offset_extended:
+ {
+ SKIP_ULEB128_OR_LEB128();
+ uint64_t offCfa;
+ GET_ULEB128_AS_U63(offCfa);
+ int rc = pState->pfnReadStack(pState, uCfa + (int64_t)offCfa * pCie->iDataAlignFactor, cbValue, puValue);
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_offset_extended %#RX64: %Rrc, %#RX64\n", uReg, uCfa + (int64_t)offCfa * pCie->iDataAlignFactor, rc, *puValue));
+ return rc;
+ }
+
+ case DW_CFA_offset_extended_sf:
+ {
+ SKIP_ULEB128_OR_LEB128();
+ int64_t offCfa;
+ GET_LEB128_AS_I63(offCfa);
+ int rc = pState->pfnReadStack(pState, uCfa + offCfa * pCie->iDataAlignFactor, cbValue, puValue);
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_offset_extended_sf %#RX64: %Rrc, %#RX64\n", uReg, uCfa + offCfa * pCie->iDataAlignFactor, rc, *puValue));
+ return rc;
+ }
+
+ case DW_CFA_val_offset:
+ {
+ SKIP_ULEB128_OR_LEB128();
+ uint64_t offCfa;
+ GET_ULEB128_AS_U63(offCfa);
+ *puValue = uCfa + (int64_t)offCfa * pCie->iDataAlignFactor;
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_val_offset: %#RX64\n", uReg, *puValue));
+ return VINF_SUCCESS;
+ }
+
+ case DW_CFA_val_offset_sf:
+ {
+ SKIP_ULEB128_OR_LEB128();
+ int64_t offCfa;
+ GET_LEB128_AS_I63(offCfa);
+ *puValue = uCfa + offCfa * pCie->iDataAlignFactor;
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_val_offset_sf: %#RX64\n", uReg, *puValue));
+ return VINF_SUCCESS;
+ }
+
+ case DW_CFA_register:
+ {
+ SKIP_ULEB128_OR_LEB128();
+ uint16_t iSrcReg;
+ GET_ULEB128_AS_U14(iSrcReg);
+ if (pfnGetReg(pOldState, uReg, puValue))
+ {
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_register: %#RX64\n", uReg, *puValue));
+ return VINF_SUCCESS;
+ }
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_register: VERR_NOT_FOUND\n", uReg));
+ return VERR_NOT_FOUND;
+ }
+
+ case DW_CFA_expression:
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_expression: TODO\n", uReg));
+ AssertFailedReturn(VERR_DWARF_TODO);
+
+ case DW_CFA_val_expression:
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_val_expression: TODO\n", uReg));
+ AssertFailedReturn(VERR_DWARF_TODO);
+
+ case DW_CFA_undefined:
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_undefined\n", uReg));
+ return VERR_NOT_FOUND;
+
+ case DW_CFA_same_value:
+ if (pfnGetReg(pOldState, uReg, puValue))
+ {
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_same_value: %#RX64\n", uReg, *puValue));
+ return VINF_SUCCESS;
+ }
+ Log8(("rtDwarfUnwind_CalcRegisterValue(%#x): DW_CFA_same_value: VERR_NOT_FOUND\n", uReg));
+ return VERR_NOT_FOUND;
+ }
+}
+
+
+DECLINLINE(void) rtDwarfUnwind_UpdateX86GRegFromRow(PRTDBGUNWINDSTATE pState, PCRTDBGUNWINDSTATE pOldState, unsigned idxGReg,
+ PRTDWARFCFROW pRow, unsigned idxDwReg, PCRTDWARFCIEINFO pCie,
+ uint64_t uCfa, PFNDWARFUNWINDGEREGFROMSTATE pfnGetReg, uint8_t cbGReg)
+{
+ int rc = rtDwarfUnwind_CalcRegisterValue(pRow, idxDwReg, pCie, uCfa, pState, pOldState, pfnGetReg,
+ &pState->u.x86.auRegs[idxGReg], cbGReg);
+ if (RT_SUCCESS(rc))
+ pState->u.x86.Loaded.s.fRegs |= RT_BIT_32(idxGReg);
+}
+
+
+DECLINLINE(void) rtDwarfUnwind_UpdateX86SRegFromRow(PRTDBGUNWINDSTATE pState, PCRTDBGUNWINDSTATE pOldState, unsigned idxSReg,
+ PRTDWARFCFROW pRow, unsigned idxDwReg, PCRTDWARFCIEINFO pCie,
+ uint64_t uCfa, PFNDWARFUNWINDGEREGFROMSTATE pfnGetReg)
+{
+ uint64_t uValue = pState->u.x86.auSegs[idxSReg];
+ int rc = rtDwarfUnwind_CalcRegisterValue(pRow, idxDwReg, pCie, uCfa, pState, pOldState, pfnGetReg, &uValue, sizeof(uint16_t));
+ if (RT_SUCCESS(rc))
+ {
+ pState->u.x86.auSegs[idxSReg] = (uint16_t)uValue;
+ pState->u.x86.Loaded.s.fSegs |= RT_BIT_32(idxSReg);
+ }
+}
+
+
+DECLINLINE(void) rtDwarfUnwind_UpdateX86RFlagsFromRow(PRTDBGUNWINDSTATE pState, PCRTDBGUNWINDSTATE pOldState,
+ PRTDWARFCFROW pRow, unsigned idxDwReg, PCRTDWARFCIEINFO pCie,
+ uint64_t uCfa, PFNDWARFUNWINDGEREGFROMSTATE pfnGetReg)
+{
+ int rc = rtDwarfUnwind_CalcRegisterValue(pRow, idxDwReg, pCie, uCfa, pState, pOldState, pfnGetReg,
+ &pState->u.x86.uRFlags, sizeof(uint32_t));
+ if (RT_SUCCESS(rc))
+ pState->u.x86.Loaded.s.fRFlags = 1;
+}
+
+
+DECLINLINE(void) rtDwarfUnwind_UpdatePCFromRow(PRTDBGUNWINDSTATE pState, PCRTDBGUNWINDSTATE pOldState,
+ PRTDWARFCFROW pRow, unsigned idxDwReg, PCRTDWARFCIEINFO pCie,
+ uint64_t uCfa, PFNDWARFUNWINDGEREGFROMSTATE pfnGetReg, uint8_t cbPc)
+{
+ if (pCie->bRetReg != UINT8_MAX)
+ idxDwReg = pCie->bRetReg;
+ int rc = rtDwarfUnwind_CalcRegisterValue(pRow, idxDwReg, pCie, uCfa, pState, pOldState, pfnGetReg, &pState->uPc, cbPc);
+ if (RT_SUCCESS(rc))
+ pState->u.x86.Loaded.s.fPc = 1;
+ else
+ {
+ rc = pState->pfnReadStack(pState, uCfa - cbPc, cbPc, &pState->uPc);
+ if (RT_SUCCESS(rc))
+ pState->u.x86.Loaded.s.fPc = 1;
+ }
+}
+
+
+
+/**
+ * Updates @a pState with the rules found in @a pRow.
+ *
+ * @returns IPRT status code.
+ * @param pState The unwind state to update.
+ * @param pRow The "row" in the dwarf unwind table.
+ * @param pCie The CIE structure for the row.
+ * @param enmImageArch The image architecture.
+ */
+static int rtDwarfUnwind_UpdateStateFromRow(PRTDBGUNWINDSTATE pState, PRTDWARFCFROW pRow,
+ PCRTDWARFCIEINFO pCie, RTLDRARCH enmImageArch)
+{
+ /*
+ * We need to make a copy of the current state so we can get at the
+ * current register values while calculating the ones of the next frame.
+ */
+ RTDBGUNWINDSTATE const Old = *pState;
+
+ /*
+ * Get the register state getter.
+ */
+ PFNDWARFUNWINDGEREGFROMSTATE pfnGetReg;
+ switch (enmImageArch)
+ {
+ case RTLDRARCH_AMD64:
+ pfnGetReg = rtDwarfUnwind_Amd64GetRegFromState;
+ break;
+ case RTLDRARCH_X86_32:
+ case RTLDRARCH_X86_16:
+ pfnGetReg = rtDwarfUnwind_X86GetRegFromState;
+ break;
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Calc the canonical frame address for the current row.
+ */
+ AssertReturn(pRow->fCfaDefined, VERR_DBG_MALFORMED_UNWIND_INFO);
+ uint64_t uCfa = 0;
+ if (!pRow->pbCfaExprInstr)
+ {
+ pfnGetReg(&Old, pRow->uCfaBaseReg, &uCfa);
+ uCfa += pRow->offCfaReg;
+ }
+ else
+ {
+ AssertFailed();
+ return VERR_DWARF_TODO;
+ }
+ Log8(("rtDwarfUnwind_UpdateStateFromRow: uCfa=%RX64\n", uCfa));
+
+ /*
+ * Do the architecture specific register updating.
+ */
+ switch (enmImageArch)
+ {
+ case RTLDRARCH_AMD64:
+ pState->enmRetType = RTDBGRETURNTYPE_NEAR64;
+ pState->u.x86.FrameAddr.off = uCfa - 8*2;
+ pState->u.x86.Loaded.fAll = 0;
+ pState->u.x86.Loaded.s.fFrameAddr = 1;
+ rtDwarfUnwind_UpdatePCFromRow(pState, &Old, pRow, DWREG_AMD64_RA, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86RFlagsFromRow(pState, &Old, pRow, DWREG_AMD64_RFLAGS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xAX, pRow, DWREG_AMD64_RAX, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xCX, pRow, DWREG_AMD64_RCX, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xDX, pRow, DWREG_AMD64_RDX, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xBX, pRow, DWREG_AMD64_RBX, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xSP, pRow, DWREG_AMD64_RSP, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xBP, pRow, DWREG_AMD64_RBP, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xSI, pRow, DWREG_AMD64_RSI, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xDI, pRow, DWREG_AMD64_RDI, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_x8, pRow, DWREG_AMD64_R8, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_x9, pRow, DWREG_AMD64_R9, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_x10, pRow, DWREG_AMD64_R10, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_x11, pRow, DWREG_AMD64_R11, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_x12, pRow, DWREG_AMD64_R12, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_x13, pRow, DWREG_AMD64_R13, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_x14, pRow, DWREG_AMD64_R14, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_x15, pRow, DWREG_AMD64_R15, pCie, uCfa, pfnGetReg, sizeof(uint64_t));
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_ES, pRow, DWREG_AMD64_ES, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_CS, pRow, DWREG_AMD64_CS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_SS, pRow, DWREG_AMD64_SS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_DS, pRow, DWREG_AMD64_DS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_FS, pRow, DWREG_AMD64_FS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_GS, pRow, DWREG_AMD64_GS, pCie, uCfa, pfnGetReg);
+ break;
+
+ case RTLDRARCH_X86_32:
+ case RTLDRARCH_X86_16:
+ pState->enmRetType = RTDBGRETURNTYPE_NEAR32;
+ pState->u.x86.FrameAddr.off = uCfa - 4*2;
+ pState->u.x86.Loaded.fAll = 0;
+ pState->u.x86.Loaded.s.fFrameAddr = 1;
+ rtDwarfUnwind_UpdatePCFromRow(pState, &Old, pRow, DWREG_X86_RA, pCie, uCfa, pfnGetReg, sizeof(uint32_t));
+ rtDwarfUnwind_UpdateX86RFlagsFromRow(pState, &Old, pRow, DWREG_X86_EFLAGS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xAX, pRow, DWREG_X86_EAX, pCie, uCfa, pfnGetReg, sizeof(uint32_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xCX, pRow, DWREG_X86_ECX, pCie, uCfa, pfnGetReg, sizeof(uint32_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xDX, pRow, DWREG_X86_EDX, pCie, uCfa, pfnGetReg, sizeof(uint32_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xBX, pRow, DWREG_X86_EBX, pCie, uCfa, pfnGetReg, sizeof(uint32_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xSP, pRow, DWREG_X86_ESP, pCie, uCfa, pfnGetReg, sizeof(uint32_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xBP, pRow, DWREG_X86_EBP, pCie, uCfa, pfnGetReg, sizeof(uint32_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xSI, pRow, DWREG_X86_ESI, pCie, uCfa, pfnGetReg, sizeof(uint32_t));
+ rtDwarfUnwind_UpdateX86GRegFromRow(pState, &Old, X86_GREG_xDI, pRow, DWREG_X86_EDI, pCie, uCfa, pfnGetReg, sizeof(uint32_t));
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_ES, pRow, DWREG_X86_ES, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_CS, pRow, DWREG_X86_CS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_SS, pRow, DWREG_X86_SS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_DS, pRow, DWREG_X86_DS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_FS, pRow, DWREG_X86_FS, pCie, uCfa, pfnGetReg);
+ rtDwarfUnwind_UpdateX86SRegFromRow(pState, &Old, X86_SREG_GS, pRow, DWREG_X86_GS, pCie, uCfa, pfnGetReg);
+ if (pState->u.x86.Loaded.s.fRegs & RT_BIT_32(X86_GREG_xSP))
+ pState->u.x86.FrameAddr.off = pState->u.x86.auRegs[X86_GREG_xSP] - 8;
+ else
+ pState->u.x86.FrameAddr.off = uCfa - 8;
+ pState->u.x86.FrameAddr.sel = pState->u.x86.auSegs[X86_SREG_SS];
+ if (pState->u.x86.Loaded.s.fSegs & RT_BIT_32(X86_SREG_CS))
+ {
+ if ((pState->uPc >> 16) == pState->u.x86.auSegs[X86_SREG_CS])
+ {
+ pState->enmRetType = RTDBGRETURNTYPE_FAR16;
+ pState->uPc &= UINT16_MAX;
+ Log8(("rtDwarfUnwind_UpdateStateFromRow: Detected FAR16 return to %04x:%04RX64\n", pState->u.x86.auSegs[X86_SREG_CS], pState->uPc));
+ }
+ else
+ {
+ pState->enmRetType = RTDBGRETURNTYPE_FAR32;
+ Log8(("rtDwarfUnwind_UpdateStateFromRow: CS loaded, assume far return.\n"));
+ }
+ }
+ break;
+
+ default:
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes a FDE, taking over after the PC range field.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param pCie Information about the corresponding CIE.
+ * @param uPcBegin The PC begin field value (sans segment).
+ * @param cbPcRange The PC range from @a uPcBegin.
+ * @param offInRange The offset into the range corresponding to
+ * pState->uPc.
+ * @param enmImageArch The image architecture.
+ * @param pState The unwind state to work.
+ */
+static int rtDwarfUnwind_ProcessFde(PRTDWARFCURSOR pCursor, PCRTDWARFCIEINFO pCie, uint64_t uPcBegin,
+ uint64_t cbPcRange, uint64_t offInRange, RTLDRARCH enmImageArch, PRTDBGUNWINDSTATE pState)
+{
+ /*
+ * Deal with augmented data fields.
+ */
+ /* The size. */
+ size_t cbInstr = ~(size_t)0;
+ if (pCie->fHasAugmentationSize)
+ {
+ uint64_t cbAugData = rtDwarfCursor_GetULeb128(pCursor, UINT64_MAX);
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+ if (cbAugData > pCursor->cbUnitLeft)
+ return VERR_DBG_MALFORMED_UNWIND_INFO;
+ cbInstr = pCursor->cbUnitLeft - cbAugData;
+ }
+ else if (pCie->fHasUnknowAugmentation)
+ return VERR_DBG_MALFORMED_UNWIND_INFO;
+
+ /* Parse the string and fetch FDE fields. */
+ if (!pCie->fHasEhData)
+ for (const char *pszAug = pCie->pszAugmentation; *pszAug != '\0'; pszAug++)
+ switch (*pszAug)
+ {
+ case 'L':
+ if (pCie->bLsdaPtrEnc != DW_EH_PE_omit)
+ rtDwarfCursor_GetPtrEnc(pCursor, pCie->bLsdaPtrEnc, 0);
+ break;
+ }
+
+ /* Skip unconsumed bytes. */
+ if ( cbInstr != ~(size_t)0
+ && pCursor->cbUnitLeft > cbInstr)
+ rtDwarfCursor_SkipBytes(pCursor, pCursor->cbUnitLeft - cbInstr);
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+
+ /*
+ * Now "execute" the programs till we've constructed the desired row.
+ */
+ RTDWARFCFROW Row;
+ RTDWARFCFEXEC ExecState = { &Row, offInRange, 0, true /** @todo byte-order*/, pCie, uPcBegin, offInRange };
+ RT_ZERO(Row);
+
+ int rc = rtDwarfUnwind_Execute(&ExecState, pCie->pbInstructions, (uint32_t)pCie->cbInstructions);
+ if (rc == VINF_TRY_AGAIN)
+ rc = rtDwarfUnwind_Execute(&ExecState, pCursor->pb, (uint32_t)pCursor->cbUnitLeft);
+
+ /* On success, extract whatever state we've got. */
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfUnwind_UpdateStateFromRow(pState, &Row, pCie, enmImageArch);
+
+ /*
+ * Clean up allocations in case of pushes.
+ */
+ if (ExecState.pRow == &Row)
+ Assert(!ExecState.pRow->fOnHeap);
+ else
+ do
+ {
+ PRTDWARFCFROW pPopped = ExecState.pRow;
+ ExecState.pRow = ExecState.pRow->pNextOnStack;
+ Assert(pPopped->fOnHeap);
+ RTMemTmpFree(pPopped);
+ } while (ExecState.pRow && ExecState.pRow != &Row);
+
+ RT_NOREF(pState, uPcBegin, cbPcRange, offInRange);
+ return rc;
+}
+
+
+/**
+ * Load the information we need from a CIE.
+ *
+ * This starts after the initial length and CIE_pointer fields has
+ * been processed.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param pNewCie The structure to populate with parsed CIE info.
+ * @param offUnit The unit offset.
+ * @param bDefaultPtrEnc The default pointer encoding.
+ */
+static int rtDwarfUnwind_LoadCie(PRTDWARFCURSOR pCursor, PRTDWARFCIEINFO pNewCie, uint64_t offUnit, uint8_t bDefaultPtrEnc)
+{
+ /*
+ * Initialize the CIE record and get the version.
+ */
+ RT_ZERO(*pNewCie);
+ pNewCie->offCie = offUnit;
+ pNewCie->bLsdaPtrEnc = DW_EH_PE_omit;
+ pNewCie->bAddressPtrEnc = DW_EH_PE_omit; /* set later */
+ pNewCie->uDwarfVer = rtDwarfCursor_GetUByte(pCursor, 0);
+ if ( pNewCie->uDwarfVer >= 1 /* Note! Some GCC versions may emit v1 here. */
+ && pNewCie->uDwarfVer <= 5)
+ { /* likely */ }
+ else
+ {
+ Log(("rtDwarfUnwind_LoadCie(%RX64): uDwarfVer=%u: VERR_VERSION_MISMATCH\n", offUnit, pNewCie->uDwarfVer));
+ return VERR_VERSION_MISMATCH;
+ }
+
+ /*
+ * The augmentation string.
+ *
+ * First deal with special "eh" string from oldish GCC (dwarf2out.c about 1997), specified in LSB:
+ * https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/ehframechpt.html
+ */
+ pNewCie->pszAugmentation = rtDwarfCursor_GetSZ(pCursor, "");
+ if ( pNewCie->pszAugmentation[0] == 'e'
+ && pNewCie->pszAugmentation[1] == 'h'
+ && pNewCie->pszAugmentation[2] == '\0')
+ {
+ pNewCie->fHasEhData = true;
+ rtDwarfCursor_GetPtrEnc(pCursor, bDefaultPtrEnc, 0);
+ }
+ else
+ {
+ /* Regular augmentation string. */
+ for (const char *pszAug = pNewCie->pszAugmentation; *pszAug != '\0'; pszAug++)
+ switch (*pszAug)
+ {
+ case 'z':
+ pNewCie->fHasAugmentationSize = true;
+ break;
+ case 'L':
+ pNewCie->fHasLanguageSpecificDataArea = true;
+ break;
+ case 'P':
+ pNewCie->fHasPersonalityRoutine = true;
+ break;
+ case 'R':
+ pNewCie->fHasAddressEnc = true;
+ break;
+ case 'S':
+ pNewCie->fIsSignalFrame = true;
+ break;
+ default:
+ pNewCie->fHasUnknowAugmentation = true;
+ break;
+ }
+ }
+
+ /*
+ * More standard fields
+ */
+ uint8_t cbAddress = 0;
+ if (pNewCie->uDwarfVer >= 4)
+ {
+ cbAddress = rtDwarfCursor_GetU8(pCursor, bDefaultPtrEnc == DW_EH_PE_udata8 ? 8 : 4);
+ pNewCie->cbSegment = rtDwarfCursor_GetU8(pCursor, 0);
+ }
+ pNewCie->uCodeAlignFactor = rtDwarfCursor_GetULeb128(pCursor, 1);
+ pNewCie->iDataAlignFactor = rtDwarfCursor_GetSLeb128(pCursor, 1);
+ pNewCie->bRetReg = rtDwarfCursor_GetU8(pCursor, UINT8_MAX);
+
+ /*
+ * Augmentation data.
+ */
+ if (!pNewCie->fHasEhData)
+ {
+ /* The size. */
+ size_t cbInstr = ~(size_t)0;
+ if (pNewCie->fHasAugmentationSize)
+ {
+ uint64_t cbAugData = rtDwarfCursor_GetULeb128(pCursor, UINT64_MAX);
+ if (RT_FAILURE(pCursor->rc))
+ {
+ Log(("rtDwarfUnwind_LoadCie(%#RX64): rtDwarfCursor_GetULeb128 -> %Rrc!\n", offUnit, pCursor->rc));
+ return pCursor->rc;
+ }
+ if (cbAugData > pCursor->cbUnitLeft)
+ {
+ Log(("rtDwarfUnwind_LoadCie(%#RX64): cbAugData=%#x pCursor->cbUnitLeft=%#x -> VERR_DBG_MALFORMED_UNWIND_INFO!\n", offUnit, cbAugData, pCursor->cbUnitLeft));
+ return VERR_DBG_MALFORMED_UNWIND_INFO;
+ }
+ cbInstr = pCursor->cbUnitLeft - cbAugData;
+ }
+ else if (pNewCie->fHasUnknowAugmentation)
+ {
+ Log(("rtDwarfUnwind_LoadCie(%#RX64): fHasUnknowAugmentation=1 -> VERR_DBG_MALFORMED_UNWIND_INFO!\n", offUnit));
+ return VERR_DBG_MALFORMED_UNWIND_INFO;
+ }
+
+ /* Parse the string. */
+ for (const char *pszAug = pNewCie->pszAugmentation; *pszAug != '\0'; pszAug++)
+ switch (*pszAug)
+ {
+ case 'L':
+ pNewCie->bLsdaPtrEnc = rtDwarfCursor_GetU8(pCursor, DW_EH_PE_omit);
+ break;
+ case 'P':
+ rtDwarfCursor_GetPtrEnc(pCursor, rtDwarfCursor_GetU8(pCursor, DW_EH_PE_omit), 0);
+ break;
+ case 'R':
+ pNewCie->bAddressPtrEnc = rtDwarfCursor_GetU8(pCursor, DW_EH_PE_omit);
+ break;
+ }
+
+ /* Skip unconsumed bytes. */
+ if ( cbInstr != ~(size_t)0
+ && pCursor->cbUnitLeft > cbInstr)
+ rtDwarfCursor_SkipBytes(pCursor, pCursor->cbUnitLeft - cbInstr);
+ }
+
+ /*
+ * Note down where the instructions are.
+ */
+ pNewCie->pbInstructions = pCursor->pb;
+ pNewCie->cbInstructions = pCursor->cbUnitLeft;
+
+ /*
+ * Determine the target address encoding. Make sure we resolve DW_EH_PE_ptr.
+ */
+ if (pNewCie->bAddressPtrEnc == DW_EH_PE_omit)
+ switch (cbAddress)
+ {
+ case 2: pNewCie->bAddressPtrEnc = DW_EH_PE_udata2; break;
+ case 4: pNewCie->bAddressPtrEnc = DW_EH_PE_udata4; break;
+ case 8: pNewCie->bAddressPtrEnc = DW_EH_PE_udata8; break;
+ default: pNewCie->bAddressPtrEnc = bDefaultPtrEnc; break;
+ }
+ else if ((pNewCie->bAddressPtrEnc & DW_EH_PE_FORMAT_MASK) == DW_EH_PE_ptr)
+ pNewCie->bAddressPtrEnc = bDefaultPtrEnc;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Does a slow unwind of a '.debug_frame' or '.eh_frame' section.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param uRvaCursor The RVA corrsponding to the cursor start location.
+ * @param idxSeg The segment of the PC location.
+ * @param offSeg The segment offset of the PC location.
+ * @param uRva The RVA of the PC location.
+ * @param pState The unwind state to work.
+ * @param bDefaultPtrEnc The default pointer encoding.
+ * @param fIsEhFrame Set if this is a '.eh_frame'. GCC generate these
+ * with different CIE_pointer values.
+ * @param enmImageArch The image architecture.
+ */
+DECLHIDDEN(int) rtDwarfUnwind_Slow(PRTDWARFCURSOR pCursor, RTUINTPTR uRvaCursor,
+ RTDBGSEGIDX idxSeg, RTUINTPTR offSeg, RTUINTPTR uRva,
+ PRTDBGUNWINDSTATE pState, uint8_t bDefaultPtrEnc, bool fIsEhFrame, RTLDRARCH enmImageArch)
+{
+ Log8(("rtDwarfUnwind_Slow: idxSeg=%#x offSeg=%RTptr uRva=%RTptr enmArch=%d PC=%#RX64\n", idxSeg, offSeg, uRva, pState->enmArch, pState->uPc));
+
+ /*
+ * CIE info we collect.
+ */
+ PRTDWARFCIEINFO paCies = NULL;
+ uint32_t cCies = 0;
+ PRTDWARFCIEINFO pCieHint = NULL;
+
+ /*
+ * Do the scanning.
+ */
+ uint64_t const offCieOffset = pCursor->f64bitDwarf ? UINT64_MAX : UINT32_MAX;
+ int rc = VERR_DBG_UNWIND_INFO_NOT_FOUND;
+ while (!rtDwarfCursor_IsAtEnd(pCursor))
+ {
+ uint64_t const offUnit = rtDwarfCursor_CalcSectOffsetU32(pCursor);
+ if (rtDwarfCursor_GetInitialLength(pCursor) == 0)
+ break;
+
+ uint64_t const offRelCie = rtDwarfCursor_GetUOff(pCursor, offCieOffset);
+ if (offRelCie != offCieOffset)
+ {
+ /*
+ * Frame descriptor entry (FDE).
+ */
+ /* Locate the corresponding CIE. The CIE pointer is self relative
+ in .eh_frame and section relative in .debug_frame. */
+ PRTDWARFCIEINFO pCieForFde;
+ uint64_t offCie = fIsEhFrame ? offUnit + 4 - offRelCie : offRelCie;
+ if (pCieHint && pCieHint->offCie == offCie)
+ pCieForFde = pCieHint;
+ else
+ {
+ pCieForFde = NULL;
+ uint32_t i = cCies;
+ while (i-- > 0)
+ if (paCies[i].offCie == offCie)
+ {
+ pCieHint = pCieForFde = &paCies[i];
+ break;
+ }
+ }
+ if (pCieForFde)
+ {
+ /* Read the PC range covered by this FDE (the fields are also known as initial_location). */
+ RTDBGSEGIDX idxFdeSeg = RTDBGSEGIDX_RVA;
+ if (pCieForFde->cbSegment)
+ idxFdeSeg = rtDwarfCursor_GetVarSizedU(pCursor, pCieForFde->cbSegment, RTDBGSEGIDX_RVA);
+ uint64_t uPcBegin;
+ switch (pCieForFde->bAddressPtrEnc & DW_EH_PE_APPL_MASK)
+ {
+ default: AssertFailed();
+ RT_FALL_THRU();
+ case DW_EH_PE_absptr:
+ uPcBegin = rtDwarfCursor_GetPtrEnc(pCursor, pCieForFde->bAddressPtrEnc, 0);
+ break;
+ case DW_EH_PE_pcrel:
+ {
+ uPcBegin = rtDwarfCursor_CalcSectOffsetU32(pCursor) + uRvaCursor;
+ uPcBegin += rtDwarfCursor_GetPtrEnc(pCursor, pCieForFde->bAddressPtrEnc, 0);
+ break;
+ }
+ }
+ uint64_t cbPcRange = rtDwarfCursor_GetPtrEnc(pCursor, pCieForFde->bAddressPtrEnc, 0);
+
+ /* Match it with what we're looking for. */
+ bool fMatch = idxFdeSeg == RTDBGSEGIDX_RVA
+ ? uRva - uPcBegin < cbPcRange
+ : idxSeg == idxFdeSeg && offSeg - uPcBegin < cbPcRange;
+ Log8(("%#08RX64: FDE pCie=%p idxFdeSeg=%#x uPcBegin=%#RX64 cbPcRange=%#x fMatch=%d\n",
+ offUnit, pCieForFde, idxFdeSeg, uPcBegin, cbPcRange, fMatch));
+ if (fMatch)
+ {
+ rc = rtDwarfUnwind_ProcessFde(pCursor, pCieForFde, uPcBegin, cbPcRange,
+ idxFdeSeg == RTDBGSEGIDX_RVA ? uRva - uPcBegin : offSeg - uPcBegin,
+ enmImageArch, pState);
+ break;
+ }
+ }
+ else
+ Log8(("%#08RX64: FDE - pCie=NULL!! offCie=%#RX64 offRelCie=%#RX64 fIsEhFrame=%d\n", offUnit, offCie, offRelCie, fIsEhFrame));
+ }
+ else
+ {
+ /*
+ * Common information entry (CIE). Record the info we need about it.
+ */
+ if ((cCies % 8) == 0)
+ {
+ void *pvNew = RTMemRealloc(paCies, sizeof(paCies[0]) * (cCies + 8));
+ if (pvNew)
+ {
+ paCies = (PRTDWARFCIEINFO)pvNew;
+ pCieHint = NULL;
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ Log8(("%#08RX64: CIE\n", offUnit));
+ int rc2 = rtDwarfUnwind_LoadCie(pCursor, &paCies[cCies], offUnit, bDefaultPtrEnc);
+ if (RT_SUCCESS(rc2))
+ {
+ Log8(("%#08RX64: CIE #%u: offCie=%#RX64\n", offUnit, cCies, paCies[cCies].offCie));
+ cCies++;
+ }
+ }
+ rtDwarfCursor_SkipUnit(pCursor);
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (paCies)
+ RTMemFree(paCies);
+ Log8(("rtDwarfUnwind_Slow: returns %Rrc PC=%#RX64\n", rc, pState->uPc));
+ return rc;
+}
+
+
+/**
+ * Helper for translating a loader architecture value to a pointe encoding.
+ *
+ * @returns Pointer encoding.
+ * @param enmLdrArch The loader architecture value to convert.
+ */
+static uint8_t rtDwarfUnwind_ArchToPtrEnc(RTLDRARCH enmLdrArch)
+{
+ switch (enmLdrArch)
+ {
+ case RTLDRARCH_AMD64:
+ case RTLDRARCH_ARM64:
+ return DW_EH_PE_udata8;
+ case RTLDRARCH_X86_16:
+ case RTLDRARCH_X86_32:
+ case RTLDRARCH_ARM32:
+ return DW_EH_PE_udata4;
+ case RTLDRARCH_HOST:
+ case RTLDRARCH_WHATEVER:
+ case RTLDRARCH_INVALID:
+ case RTLDRARCH_END:
+ case RTLDRARCH_32BIT_HACK:
+ break;
+ }
+ AssertFailed();
+ return DW_EH_PE_udata4;
+}
+
+
+/**
+ * Interface for the loader code.
+ *
+ * @returns IPRT status.
+ * @param pvSection The '.eh_frame' section data.
+ * @param cbSection The size of the '.eh_frame' section data.
+ * @param uRvaSection The RVA of the '.eh_frame' section.
+ * @param idxSeg The segment of the PC location.
+ * @param offSeg The segment offset of the PC location.
+ * @param uRva The RVA of the PC location.
+ * @param pState The unwind state to work.
+ * @param enmArch The image architecture.
+ */
+DECLHIDDEN(int) rtDwarfUnwind_EhData(void const *pvSection, size_t cbSection, RTUINTPTR uRvaSection,
+ RTDBGSEGIDX idxSeg, RTUINTPTR offSeg, RTUINTPTR uRva,
+ PRTDBGUNWINDSTATE pState, RTLDRARCH enmArch)
+{
+ RTDWARFCURSOR Cursor;
+ rtDwarfCursor_InitForMem(&Cursor, pvSection, cbSection);
+ int rc = rtDwarfUnwind_Slow(&Cursor, uRvaSection, idxSeg, offSeg, uRva, pState,
+ rtDwarfUnwind_ArchToPtrEnc(enmArch), true /*fIsEhFrame*/, enmArch);
+ LogFlow(("rtDwarfUnwind_EhData: rtDwarfUnwind_Slow -> %Rrc\n", rc));
+ rc = rtDwarfCursor_Delete(&Cursor, rc);
+ LogFlow(("rtDwarfUnwind_EhData: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/*
+ *
+ * DWARF Line Numbers.
+ * DWARF Line Numbers.
+ * DWARF Line Numbers.
+ *
+ */
+
+
+/**
+ * Defines a file name.
+ *
+ * @returns IPRT status code.
+ * @param pLnState The line number program state.
+ * @param pszFilename The name of the file.
+ * @param idxInc The include path index.
+ */
+static int rtDwarfLine_DefineFileName(PRTDWARFLINESTATE pLnState, const char *pszFilename, uint64_t idxInc)
+{
+ /*
+ * Resize the array if necessary.
+ */
+ uint32_t iFileName = pLnState->cFileNames;
+ if ((iFileName % 2) == 0)
+ {
+ void *pv = RTMemRealloc(pLnState->papszFileNames, sizeof(pLnState->papszFileNames[0]) * (iFileName + 2));
+ if (!pv)
+ return VERR_NO_MEMORY;
+ pLnState->papszFileNames = (char **)pv;
+ }
+
+ /*
+ * Add the file name.
+ */
+ if ( pszFilename[0] == '/'
+ || pszFilename[0] == '\\'
+ || (RT_C_IS_ALPHA(pszFilename[0]) && pszFilename[1] == ':') )
+ pLnState->papszFileNames[iFileName] = RTStrDup(pszFilename);
+ else if (idxInc < pLnState->cIncPaths)
+ pLnState->papszFileNames[iFileName] = RTPathJoinA(pLnState->papszIncPaths[idxInc], pszFilename);
+ else
+ return VERR_DWARF_BAD_LINE_NUMBER_HEADER;
+ if (!pLnState->papszFileNames[iFileName])
+ return VERR_NO_STR_MEMORY;
+ pLnState->cFileNames = iFileName + 1;
+
+ /*
+ * Sanitize the name.
+ */
+ int rc = rtDbgModDwarfStringToUtf8(pLnState->pDwarfMod, &pLnState->papszFileNames[iFileName]);
+ Log((" File #%02u = '%s'\n", iFileName, pLnState->papszFileNames[iFileName]));
+ return rc;
+}
+
+
+/**
+ * Adds a line to the table and resets parts of the state (DW_LNS_copy).
+ *
+ * @returns IPRT status code
+ * @param pLnState The line number program state.
+ * @param offOpCode The opcode offset (for logging
+ * purposes).
+ */
+static int rtDwarfLine_AddLine(PRTDWARFLINESTATE pLnState, uint32_t offOpCode)
+{
+ PRTDBGMODDWARF pThis = pLnState->pDwarfMod;
+ int rc;
+ if (pThis->iWatcomPass == 1)
+ rc = rtDbgModDwarfRecordSegOffset(pThis, pLnState->Regs.uSegment, pLnState->Regs.uAddress + 1);
+ else
+ {
+ const char *pszFile = pLnState->Regs.iFile < pLnState->cFileNames
+ ? pLnState->papszFileNames[pLnState->Regs.iFile]
+ : "<bad file name index>";
+ NOREF(offOpCode);
+
+ RTDBGSEGIDX iSeg;
+ RTUINTPTR offSeg;
+ rc = rtDbgModDwarfLinkAddressToSegOffset(pLnState->pDwarfMod, pLnState->Regs.uSegment, pLnState->Regs.uAddress,
+ &iSeg, &offSeg); /*AssertRC(rc);*/
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("rtDwarfLine_AddLine: %x:%08llx (%#llx) %s(%d) [offOpCode=%08x]\n", iSeg, offSeg, pLnState->Regs.uAddress, pszFile, pLnState->Regs.uLine, offOpCode));
+ rc = RTDbgModLineAdd(pLnState->pDwarfMod->hCnt, pszFile, pLnState->Regs.uLine, iSeg, offSeg, NULL);
+
+ /* Ignore address conflicts for now. */
+ if (rc == VERR_DBG_ADDRESS_CONFLICT)
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VINF_SUCCESS; /* ignore failure */
+ }
+
+ pLnState->Regs.fBasicBlock = false;
+ pLnState->Regs.fPrologueEnd = false;
+ pLnState->Regs.fEpilogueBegin = false;
+ pLnState->Regs.uDiscriminator = 0;
+ return rc;
+}
+
+
+/**
+ * Reset the program to the start-of-sequence state.
+ *
+ * @param pLnState The line number program state.
+ */
+static void rtDwarfLine_ResetState(PRTDWARFLINESTATE pLnState)
+{
+ pLnState->Regs.uAddress = 0;
+ pLnState->Regs.idxOp = 0;
+ pLnState->Regs.iFile = 1;
+ pLnState->Regs.uLine = 1;
+ pLnState->Regs.uColumn = 0;
+ pLnState->Regs.fIsStatement = RT_BOOL(pLnState->Hdr.u8DefIsStmt);
+ pLnState->Regs.fBasicBlock = false;
+ pLnState->Regs.fEndSequence = false;
+ pLnState->Regs.fPrologueEnd = false;
+ pLnState->Regs.fEpilogueBegin = false;
+ pLnState->Regs.uIsa = 0;
+ pLnState->Regs.uDiscriminator = 0;
+ pLnState->Regs.uSegment = 0;
+}
+
+
+/**
+ * Runs the line number program.
+ *
+ * @returns IPRT status code.
+ * @param pLnState The line number program state.
+ * @param pCursor The cursor.
+ */
+static int rtDwarfLine_RunProgram(PRTDWARFLINESTATE pLnState, PRTDWARFCURSOR pCursor)
+{
+ LogFlow(("rtDwarfLine_RunProgram: cbUnitLeft=%zu\n", pCursor->cbUnitLeft));
+
+ int rc = VINF_SUCCESS;
+ rtDwarfLine_ResetState(pLnState);
+
+ while (!rtDwarfCursor_IsAtEndOfUnit(pCursor))
+ {
+#ifdef LOG_ENABLED
+ uint32_t const offOpCode = rtDwarfCursor_CalcSectOffsetU32(pCursor);
+#else
+ uint32_t const offOpCode = 0;
+#endif
+ uint8_t bOpCode = rtDwarfCursor_GetUByte(pCursor, DW_LNS_extended);
+ if (bOpCode >= pLnState->Hdr.u8OpcodeBase)
+ {
+ /*
+ * Special opcode.
+ */
+ uint8_t const bLogOpCode = bOpCode; NOREF(bLogOpCode);
+ bOpCode -= pLnState->Hdr.u8OpcodeBase;
+
+ int32_t const cLineDelta = bOpCode % pLnState->Hdr.u8LineRange + (int32_t)pLnState->Hdr.s8LineBase;
+ bOpCode /= pLnState->Hdr.u8LineRange;
+
+ uint64_t uTmp = bOpCode + pLnState->Regs.idxOp;
+ uint64_t const cAddressDelta = uTmp / pLnState->Hdr.cMaxOpsPerInstr * pLnState->Hdr.cbMinInstr;
+ uint64_t const cOpIndexDelta = uTmp % pLnState->Hdr.cMaxOpsPerInstr;
+
+ pLnState->Regs.uLine += cLineDelta;
+ pLnState->Regs.uAddress += cAddressDelta;
+ pLnState->Regs.idxOp += cOpIndexDelta;
+ Log2(("%08x: DW Special Opcode %#04x: uLine + %d => %u; uAddress + %#llx => %#llx; idxOp + %#llx => %#llx\n",
+ offOpCode, bLogOpCode, cLineDelta, pLnState->Regs.uLine, cAddressDelta, pLnState->Regs.uAddress,
+ cOpIndexDelta, pLnState->Regs.idxOp));
+
+ /*
+ * LLVM emits debug info for global constructors (_GLOBAL__I_a) which are not part of source
+ * code but are inserted by the compiler: The resulting line number will be 0
+ * because they are not part of the source file obviously (see https://reviews.llvm.org/rL205999),
+ * so skip adding them when they are encountered.
+ */
+ if (pLnState->Regs.uLine)
+ rc = rtDwarfLine_AddLine(pLnState, offOpCode);
+ }
+ else
+ {
+ switch (bOpCode)
+ {
+ /*
+ * Standard opcode.
+ */
+ case DW_LNS_copy:
+ Log2(("%08x: DW_LNS_copy\n", offOpCode));
+ /* See the comment about LLVM above. */
+ if (pLnState->Regs.uLine)
+ rc = rtDwarfLine_AddLine(pLnState, offOpCode);
+ break;
+
+ case DW_LNS_advance_pc:
+ {
+ uint64_t u64Adv = rtDwarfCursor_GetULeb128(pCursor, 0);
+ pLnState->Regs.uAddress += (pLnState->Regs.idxOp + u64Adv) / pLnState->Hdr.cMaxOpsPerInstr
+ * pLnState->Hdr.cbMinInstr;
+ pLnState->Regs.idxOp += (pLnState->Regs.idxOp + u64Adv) % pLnState->Hdr.cMaxOpsPerInstr;
+ Log2(("%08x: DW_LNS_advance_pc: u64Adv=%#llx (%lld) )\n", offOpCode, u64Adv, u64Adv));
+ break;
+ }
+
+ case DW_LNS_advance_line:
+ {
+ int32_t cLineDelta = rtDwarfCursor_GetSLeb128AsS32(pCursor, 0);
+ pLnState->Regs.uLine += cLineDelta;
+ Log2(("%08x: DW_LNS_advance_line: uLine + %d => %u\n", offOpCode, cLineDelta, pLnState->Regs.uLine));
+ break;
+ }
+
+ case DW_LNS_set_file:
+ pLnState->Regs.iFile = rtDwarfCursor_GetULeb128AsU32(pCursor, 0);
+ Log2(("%08x: DW_LNS_set_file: iFile=%u\n", offOpCode, pLnState->Regs.iFile));
+ break;
+
+ case DW_LNS_set_column:
+ pLnState->Regs.uColumn = rtDwarfCursor_GetULeb128AsU32(pCursor, 0);
+ Log2(("%08x: DW_LNS_set_column\n", offOpCode));
+ break;
+
+ case DW_LNS_negate_stmt:
+ pLnState->Regs.fIsStatement = !pLnState->Regs.fIsStatement;
+ Log2(("%08x: DW_LNS_negate_stmt\n", offOpCode));
+ break;
+
+ case DW_LNS_set_basic_block:
+ pLnState->Regs.fBasicBlock = true;
+ Log2(("%08x: DW_LNS_set_basic_block\n", offOpCode));
+ break;
+
+ case DW_LNS_const_add_pc:
+ {
+ uint8_t u8Adv = (255 - pLnState->Hdr.u8OpcodeBase) / pLnState->Hdr.u8LineRange;
+ if (pLnState->Hdr.cMaxOpsPerInstr <= 1)
+ pLnState->Regs.uAddress += (uint32_t)pLnState->Hdr.cbMinInstr * u8Adv;
+ else
+ {
+ pLnState->Regs.uAddress += (pLnState->Regs.idxOp + u8Adv) / pLnState->Hdr.cMaxOpsPerInstr
+ * pLnState->Hdr.cbMinInstr;
+ pLnState->Regs.idxOp = (pLnState->Regs.idxOp + u8Adv) % pLnState->Hdr.cMaxOpsPerInstr;
+ }
+ Log2(("%08x: DW_LNS_const_add_pc\n", offOpCode));
+ break;
+ }
+ case DW_LNS_fixed_advance_pc:
+ pLnState->Regs.uAddress += rtDwarfCursor_GetUHalf(pCursor, 0);
+ pLnState->Regs.idxOp = 0;
+ Log2(("%08x: DW_LNS_fixed_advance_pc\n", offOpCode));
+ break;
+
+ case DW_LNS_set_prologue_end:
+ pLnState->Regs.fPrologueEnd = true;
+ Log2(("%08x: DW_LNS_set_prologue_end\n", offOpCode));
+ break;
+
+ case DW_LNS_set_epilogue_begin:
+ pLnState->Regs.fEpilogueBegin = true;
+ Log2(("%08x: DW_LNS_set_epilogue_begin\n", offOpCode));
+ break;
+
+ case DW_LNS_set_isa:
+ pLnState->Regs.uIsa = rtDwarfCursor_GetULeb128AsU32(pCursor, 0);
+ Log2(("%08x: DW_LNS_set_isa %#x\n", offOpCode, pLnState->Regs.uIsa));
+ break;
+
+ default:
+ {
+ unsigned cOpsToSkip = pLnState->Hdr.pacStdOperands[bOpCode - 1];
+ Log(("rtDwarfLine_RunProgram: Unknown standard opcode %#x, %#x operands, at %08x.\n", bOpCode, cOpsToSkip, offOpCode));
+ while (cOpsToSkip-- > 0)
+ rc = rtDwarfCursor_SkipLeb128(pCursor);
+ break;
+ }
+
+ /*
+ * Extended opcode.
+ */
+ case DW_LNS_extended:
+ {
+ /* The instruction has a length prefix. */
+ uint64_t cbInstr = rtDwarfCursor_GetULeb128(pCursor, UINT64_MAX);
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+ if (cbInstr > pCursor->cbUnitLeft)
+ return VERR_DWARF_BAD_LNE;
+ uint8_t const * const pbEndOfInstr = rtDwarfCursor_CalcPos(pCursor, cbInstr);
+
+ /* Get the opcode and deal with it if we know it. */
+ bOpCode = rtDwarfCursor_GetUByte(pCursor, 0);
+ switch (bOpCode)
+ {
+ case DW_LNE_end_sequence:
+#if 0 /* No need for this, I think. */
+ pLnState->Regs.fEndSequence = true;
+ rc = rtDwarfLine_AddLine(pLnState, offOpCode);
+#endif
+ rtDwarfLine_ResetState(pLnState);
+ Log2(("%08x: DW_LNE_end_sequence\n", offOpCode));
+ break;
+
+ case DW_LNE_set_address:
+ pLnState->Regs.uAddress = rtDwarfCursor_GetVarSizedU(pCursor, cbInstr - 1, UINT64_MAX);
+ pLnState->Regs.idxOp = 0;
+ Log2(("%08x: DW_LNE_set_address: %#llx\n", offOpCode, pLnState->Regs.uAddress));
+ break;
+
+ case DW_LNE_define_file:
+ {
+ const char *pszFilename = rtDwarfCursor_GetSZ(pCursor, NULL);
+ uint32_t idxInc = rtDwarfCursor_GetULeb128AsU32(pCursor, UINT32_MAX);
+ rtDwarfCursor_SkipLeb128(pCursor); /* st_mtime */
+ rtDwarfCursor_SkipLeb128(pCursor); /* st_size */
+ Log2(("%08x: DW_LNE_define_file: {%d}/%s\n", offOpCode, idxInc, pszFilename));
+
+ rc = rtDwarfCursor_AdvanceToPos(pCursor, pbEndOfInstr);
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfLine_DefineFileName(pLnState, pszFilename, idxInc);
+ break;
+ }
+
+ /*
+ * Note! Was defined in DWARF 4. But... Watcom used it for setting the
+ * segment in DWARF 2, creating an incompatibility with the newer
+ * standard. And gcc 10 uses v3 for these.
+ */
+ case DW_LNE_set_descriminator:
+ if (pLnState->Hdr.uVer != 2)
+ {
+ Assert(pLnState->Hdr.uVer >= 3);
+ pLnState->Regs.uDiscriminator = rtDwarfCursor_GetULeb128AsU32(pCursor, UINT32_MAX);
+ Log2(("%08x: DW_LNE_set_descriminator: %u\n", offOpCode, pLnState->Regs.uDiscriminator));
+ }
+ else
+ {
+ uint64_t uSeg = rtDwarfCursor_GetVarSizedU(pCursor, cbInstr - 1, UINT64_MAX);
+ Log2(("%08x: DW_LNE_set_segment: %#llx, cbInstr=%#x - Watcom Extension\n", offOpCode, uSeg, cbInstr));
+ pLnState->Regs.uSegment = (RTSEL)uSeg;
+ AssertStmt(pLnState->Regs.uSegment == uSeg, rc = VERR_DWARF_BAD_INFO);
+ }
+ break;
+
+ default:
+ Log(("rtDwarfLine_RunProgram: Unknown extended opcode %#x, length %#x at %08x\n", bOpCode, cbInstr, offOpCode));
+ break;
+ }
+
+ /* Advance the cursor to the end of the instruction . */
+ rtDwarfCursor_AdvanceToPos(pCursor, pbEndOfInstr);
+ break;
+ }
+ }
+ }
+
+ /*
+ * Check the status before looping.
+ */
+ if (RT_FAILURE(rc))
+ return rc;
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Reads the include directories for a line number unit.
+ *
+ * @returns IPRT status code
+ * @param pLnState The line number program state.
+ * @param pCursor The cursor.
+ */
+static int rtDwarfLine_ReadFileNames(PRTDWARFLINESTATE pLnState, PRTDWARFCURSOR pCursor)
+{
+ int rc = rtDwarfLine_DefineFileName(pLnState, "/<bad-zero-file-name-entry>", 0);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ for (;;)
+ {
+ const char *psz = rtDwarfCursor_GetSZ(pCursor, NULL);
+ if (!*psz)
+ break;
+
+ uint64_t idxInc = rtDwarfCursor_GetULeb128(pCursor, UINT64_MAX);
+ rtDwarfCursor_SkipLeb128(pCursor); /* st_mtime */
+ rtDwarfCursor_SkipLeb128(pCursor); /* st_size */
+
+ rc = rtDwarfLine_DefineFileName(pLnState, psz, idxInc);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return pCursor->rc;
+}
+
+
+/**
+ * Reads the include directories for a line number unit.
+ *
+ * @returns IPRT status code
+ * @param pLnState The line number program state.
+ * @param pCursor The cursor.
+ */
+static int rtDwarfLine_ReadIncludePaths(PRTDWARFLINESTATE pLnState, PRTDWARFCURSOR pCursor)
+{
+ const char *psz = ""; /* The zeroth is the unit dir. */
+ for (;;)
+ {
+ if ((pLnState->cIncPaths % 2) == 0)
+ {
+ void *pv = RTMemRealloc(pLnState->papszIncPaths, sizeof(pLnState->papszIncPaths[0]) * (pLnState->cIncPaths + 2));
+ if (!pv)
+ return VERR_NO_MEMORY;
+ pLnState->papszIncPaths = (const char **)pv;
+ }
+ Log((" Path #%02u = '%s'\n", pLnState->cIncPaths, psz));
+ pLnState->papszIncPaths[pLnState->cIncPaths] = psz;
+ pLnState->cIncPaths++;
+
+ psz = rtDwarfCursor_GetSZ(pCursor, NULL);
+ if (!*psz)
+ break;
+ }
+
+ return pCursor->rc;
+}
+
+
+/**
+ * Explodes the line number table for a compilation unit.
+ *
+ * @returns IPRT status code
+ * @param pThis The DWARF instance.
+ * @param pCursor The cursor to read the line number information
+ * via.
+ */
+static int rtDwarfLine_ExplodeUnit(PRTDBGMODDWARF pThis, PRTDWARFCURSOR pCursor)
+{
+ RTDWARFLINESTATE LnState;
+ RT_ZERO(LnState);
+ LnState.pDwarfMod = pThis;
+
+ /*
+ * Parse the header.
+ */
+ rtDwarfCursor_GetInitialLength(pCursor);
+ LnState.Hdr.uVer = rtDwarfCursor_GetUHalf(pCursor, 0);
+ if ( LnState.Hdr.uVer < 2
+ || LnState.Hdr.uVer > 4)
+ return rtDwarfCursor_SkipUnit(pCursor);
+
+ LnState.Hdr.offFirstOpcode = rtDwarfCursor_GetUOff(pCursor, 0);
+ uint8_t const * const pbFirstOpcode = rtDwarfCursor_CalcPos(pCursor, LnState.Hdr.offFirstOpcode);
+
+ LnState.Hdr.cbMinInstr = rtDwarfCursor_GetUByte(pCursor, 0);
+ if (LnState.Hdr.uVer >= 4)
+ LnState.Hdr.cMaxOpsPerInstr = rtDwarfCursor_GetUByte(pCursor, 0);
+ else
+ LnState.Hdr.cMaxOpsPerInstr = 1;
+ LnState.Hdr.u8DefIsStmt = rtDwarfCursor_GetUByte(pCursor, 0);
+ LnState.Hdr.s8LineBase = rtDwarfCursor_GetSByte(pCursor, 0);
+ LnState.Hdr.u8LineRange = rtDwarfCursor_GetUByte(pCursor, 0);
+ LnState.Hdr.u8OpcodeBase = rtDwarfCursor_GetUByte(pCursor, 0);
+
+ if ( !LnState.Hdr.u8OpcodeBase
+ || !LnState.Hdr.cMaxOpsPerInstr
+ || !LnState.Hdr.u8LineRange
+ || LnState.Hdr.u8DefIsStmt > 1)
+ return VERR_DWARF_BAD_LINE_NUMBER_HEADER;
+ Log2(("DWARF Line number header:\n"
+ " uVer %d\n"
+ " offFirstOpcode %#llx\n"
+ " cbMinInstr %u\n"
+ " cMaxOpsPerInstr %u\n"
+ " u8DefIsStmt %u\n"
+ " s8LineBase %d\n"
+ " u8LineRange %u\n"
+ " u8OpcodeBase %u\n",
+ LnState.Hdr.uVer, LnState.Hdr.offFirstOpcode, LnState.Hdr.cbMinInstr, LnState.Hdr.cMaxOpsPerInstr,
+ LnState.Hdr.u8DefIsStmt, LnState.Hdr.s8LineBase, LnState.Hdr.u8LineRange, LnState.Hdr.u8OpcodeBase));
+
+ LnState.Hdr.pacStdOperands = pCursor->pb;
+ for (uint8_t iStdOpcode = 1; iStdOpcode < LnState.Hdr.u8OpcodeBase; iStdOpcode++)
+ rtDwarfCursor_GetUByte(pCursor, 0);
+
+ int rc = pCursor->rc;
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfLine_ReadIncludePaths(&LnState, pCursor);
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfLine_ReadFileNames(&LnState, pCursor);
+
+ /*
+ * Run the program....
+ */
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfCursor_AdvanceToPos(pCursor, pbFirstOpcode);
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfLine_RunProgram(&LnState, pCursor);
+
+ /*
+ * Clean up.
+ */
+ size_t i = LnState.cFileNames;
+ while (i-- > 0)
+ RTStrFree(LnState.papszFileNames[i]);
+ RTMemFree(LnState.papszFileNames);
+ RTMemFree(LnState.papszIncPaths);
+
+ Assert(rtDwarfCursor_IsAtEndOfUnit(pCursor) || RT_FAILURE(rc));
+ return rc;
+}
+
+
+/**
+ * Explodes the line number table.
+ *
+ * The line numbers are insered into the debug info container.
+ *
+ * @returns IPRT status code
+ * @param pThis The DWARF instance.
+ */
+static int rtDwarfLine_ExplodeAll(PRTDBGMODDWARF pThis)
+{
+ if (!pThis->aSections[krtDbgModDwarfSect_line].fPresent)
+ return VINF_SUCCESS;
+
+ RTDWARFCURSOR Cursor;
+ int rc = rtDwarfCursor_Init(&Cursor, pThis, krtDbgModDwarfSect_line);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ while ( !rtDwarfCursor_IsAtEnd(&Cursor)
+ && RT_SUCCESS(rc))
+ rc = rtDwarfLine_ExplodeUnit(pThis, &Cursor);
+
+ return rtDwarfCursor_Delete(&Cursor, rc);
+}
+
+
+/*
+ *
+ * DWARF Abbreviations.
+ * DWARF Abbreviations.
+ * DWARF Abbreviations.
+ *
+ */
+
+/**
+ * Deals with a cache miss in rtDwarfAbbrev_Lookup.
+ *
+ * @returns Pointer to abbreviation cache entry (read only). May be rendered
+ * invalid by subsequent calls to this function.
+ * @param pThis The DWARF instance.
+ * @param uCode The abbreviation code to lookup.
+ */
+static PCRTDWARFABBREV rtDwarfAbbrev_LookupMiss(PRTDBGMODDWARF pThis, uint32_t uCode)
+{
+ /*
+ * There is no entry with code zero.
+ */
+ if (!uCode)
+ return NULL;
+
+ /*
+ * Resize the cache array if the code is considered cachable.
+ */
+ bool fFillCache = true;
+ if (pThis->cCachedAbbrevsAlloced < uCode)
+ {
+ if (uCode >= _64K)
+ fFillCache = false;
+ else
+ {
+ uint32_t cNew = RT_ALIGN(uCode, 64);
+ void *pv = RTMemRealloc(pThis->paCachedAbbrevs, sizeof(pThis->paCachedAbbrevs[0]) * cNew);
+ if (!pv)
+ fFillCache = false;
+ else
+ {
+ Log(("rtDwarfAbbrev_LookupMiss: Growing from %u to %u...\n", pThis->cCachedAbbrevsAlloced, cNew));
+ pThis->paCachedAbbrevs = (PRTDWARFABBREV)pv;
+ for (uint32_t i = pThis->cCachedAbbrevsAlloced; i < cNew; i++)
+ pThis->paCachedAbbrevs[i].offAbbrev = UINT32_MAX;
+ pThis->cCachedAbbrevsAlloced = cNew;
+ }
+ }
+ }
+
+ /*
+ * Walk the abbreviations till we find the desired code.
+ */
+ RTDWARFCURSOR Cursor;
+ int rc = rtDwarfCursor_InitWithOffset(&Cursor, pThis, krtDbgModDwarfSect_abbrev, pThis->offCachedAbbrev);
+ if (RT_FAILURE(rc))
+ return NULL;
+
+ PRTDWARFABBREV pRet = NULL;
+ if (fFillCache)
+ {
+ /*
+ * Search for the entry and fill the cache while doing so.
+ * We assume that abbreviation codes for a unit will stop when we see
+ * zero code or when the code value drops.
+ */
+ uint32_t uPrevCode = 0;
+ for (;;)
+ {
+ /* Read the 'header'. Skipping zero code bytes. */
+#ifdef LOG_ENABLED
+ uint32_t const offStart = rtDwarfCursor_CalcSectOffsetU32(&Cursor);
+#endif
+ uint32_t const uCurCode = rtDwarfCursor_GetULeb128AsU32(&Cursor, 0);
+ if (pRet && (uCurCode == 0 || uCurCode < uPrevCode))
+ break; /* probably end of unit. */
+ if (uCurCode != 0)
+ {
+ uint32_t const uCurTag = rtDwarfCursor_GetULeb128AsU32(&Cursor, 0);
+ uint8_t const uChildren = rtDwarfCursor_GetU8(&Cursor, 0);
+ if (RT_FAILURE(Cursor.rc))
+ break;
+ if ( uCurTag > 0xffff
+ || uChildren > 1)
+ {
+ Cursor.rc = VERR_DWARF_BAD_ABBREV;
+ break;
+ }
+
+ /* Cache it? */
+ if (uCurCode <= pThis->cCachedAbbrevsAlloced)
+ {
+ PRTDWARFABBREV pEntry = &pThis->paCachedAbbrevs[uCurCode - 1];
+ if (pEntry->offAbbrev != pThis->offCachedAbbrev)
+ {
+ pEntry->offAbbrev = pThis->offCachedAbbrev;
+ pEntry->fChildren = RT_BOOL(uChildren);
+ pEntry->uTag = uCurTag;
+ pEntry->offSpec = rtDwarfCursor_CalcSectOffsetU32(&Cursor);
+#ifdef LOG_ENABLED
+ pEntry->cbHdr = (uint8_t)(pEntry->offSpec - offStart);
+ Log7(("rtDwarfAbbrev_LookupMiss(%#x): fill: %#x: uTag=%#x offAbbrev=%#x%s\n",
+ uCode, offStart, pEntry->uTag, pEntry->offAbbrev, pEntry->fChildren ? " has-children" : ""));
+#endif
+ if (uCurCode == uCode)
+ {
+ Assert(!pRet);
+ pRet = pEntry;
+ if (uCurCode == pThis->cCachedAbbrevsAlloced)
+ break;
+ }
+ }
+ else if (pRet)
+ break; /* Next unit, don't cache more. */
+ /* else: We're growing the cache and re-reading old data. */
+ }
+
+ /* Skip the specification. */
+ uint32_t uAttr, uForm;
+ do
+ {
+ uAttr = rtDwarfCursor_GetULeb128AsU32(&Cursor, 0);
+ uForm = rtDwarfCursor_GetULeb128AsU32(&Cursor, 0);
+ } while (uAttr != 0);
+ }
+ if (RT_FAILURE(Cursor.rc))
+ break;
+
+ /* Done? (Maximize cache filling.) */
+ if ( pRet != NULL
+ && uCurCode >= pThis->cCachedAbbrevsAlloced)
+ break;
+ uPrevCode = uCurCode;
+ }
+ if (pRet)
+ Log6(("rtDwarfAbbrev_LookupMiss(%#x): uTag=%#x offSpec=%#x offAbbrev=%#x [fill]\n",
+ uCode, pRet->uTag, pRet->offSpec, pRet->offAbbrev));
+ else
+ Log6(("rtDwarfAbbrev_LookupMiss(%#x): failed [fill]\n", uCode));
+ }
+ else
+ {
+ /*
+ * Search for the entry with the desired code, no cache filling.
+ */
+ for (;;)
+ {
+ /* Read the 'header'. */
+#ifdef LOG_ENABLED
+ uint32_t const offStart = rtDwarfCursor_CalcSectOffsetU32(&Cursor);
+#endif
+ uint32_t const uCurCode = rtDwarfCursor_GetULeb128AsU32(&Cursor, 0);
+ uint32_t const uCurTag = rtDwarfCursor_GetULeb128AsU32(&Cursor, 0);
+ uint8_t const uChildren = rtDwarfCursor_GetU8(&Cursor, 0);
+ if (RT_FAILURE(Cursor.rc))
+ break;
+ if ( uCurTag > 0xffff
+ || uChildren > 1)
+ {
+ Cursor.rc = VERR_DWARF_BAD_ABBREV;
+ break;
+ }
+
+ /* Do we have a match? */
+ if (uCurCode == uCode)
+ {
+ pRet = &pThis->LookupAbbrev;
+ pRet->fChildren = RT_BOOL(uChildren);
+ pRet->uTag = uCurTag;
+ pRet->offSpec = rtDwarfCursor_CalcSectOffsetU32(&Cursor);
+ pRet->offAbbrev = pThis->offCachedAbbrev;
+#ifdef LOG_ENABLED
+ pRet->cbHdr = (uint8_t)(pRet->offSpec - offStart);
+#endif
+ break;
+ }
+
+ /* Skip the specification. */
+ uint32_t uAttr, uForm;
+ do
+ {
+ uAttr = rtDwarfCursor_GetULeb128AsU32(&Cursor, 0);
+ uForm = rtDwarfCursor_GetULeb128AsU32(&Cursor, 0);
+ } while (uAttr != 0);
+ if (RT_FAILURE(Cursor.rc))
+ break;
+ }
+ if (pRet)
+ Log6(("rtDwarfAbbrev_LookupMiss(%#x): uTag=%#x offSpec=%#x offAbbrev=%#x [no-fill]\n",
+ uCode, pRet->uTag, pRet->offSpec, pRet->offAbbrev));
+ else
+ Log6(("rtDwarfAbbrev_LookupMiss(%#x): failed [no-fill]\n", uCode));
+ }
+
+ rtDwarfCursor_Delete(&Cursor, VINF_SUCCESS);
+ return pRet;
+}
+
+
+/**
+ * Looks up an abbreviation.
+ *
+ * @returns Pointer to abbreviation cache entry (read only). May be rendered
+ * invalid by subsequent calls to this function.
+ * @param pThis The DWARF instance.
+ * @param uCode The abbreviation code to lookup.
+ */
+static PCRTDWARFABBREV rtDwarfAbbrev_Lookup(PRTDBGMODDWARF pThis, uint32_t uCode)
+{
+ uCode -= 1;
+ if (uCode < pThis->cCachedAbbrevsAlloced)
+ {
+ if (pThis->paCachedAbbrevs[uCode].offAbbrev == pThis->offCachedAbbrev)
+ return &pThis->paCachedAbbrevs[uCode];
+ }
+ return rtDwarfAbbrev_LookupMiss(pThis, uCode + 1);
+}
+
+
+/**
+ * Sets the abbreviation offset of the current unit.
+ *
+ * @param pThis The DWARF instance.
+ * @param offAbbrev The offset into the abbreviation section.
+ */
+static void rtDwarfAbbrev_SetUnitOffset(PRTDBGMODDWARF pThis, uint32_t offAbbrev)
+{
+ pThis->offCachedAbbrev = offAbbrev;
+}
+
+
+
+/*
+ *
+ * DIE Attribute Parsers.
+ * DIE Attribute Parsers.
+ * DIE Attribute Parsers.
+ *
+ */
+
+/**
+ * Gets the compilation unit a DIE belongs to.
+ *
+ * @returns The compilation unit DIE.
+ * @param pDie Some DIE in the unit.
+ */
+static PRTDWARFDIECOMPILEUNIT rtDwarfDie_GetCompileUnit(PRTDWARFDIE pDie)
+{
+ while (pDie->pParent)
+ pDie = pDie->pParent;
+ AssertReturn( pDie->uTag == DW_TAG_compile_unit
+ || pDie->uTag == DW_TAG_partial_unit,
+ NULL);
+ return (PRTDWARFDIECOMPILEUNIT)pDie;
+}
+
+
+/**
+ * Resolves a string section (debug_str) reference.
+ *
+ * @returns Pointer to the string (inside the string section).
+ * @param pThis The DWARF instance.
+ * @param pCursor The cursor.
+ * @param pszErrValue What to return on failure (@a
+ * pCursor->rc is set).
+ */
+static const char *rtDwarfDecodeHlp_GetStrp(PRTDBGMODDWARF pThis, PRTDWARFCURSOR pCursor, const char *pszErrValue)
+{
+ uint64_t offDebugStr = rtDwarfCursor_GetUOff(pCursor, UINT64_MAX);
+ if (RT_FAILURE(pCursor->rc))
+ return pszErrValue;
+
+ if (offDebugStr >= pThis->aSections[krtDbgModDwarfSect_str].cb)
+ {
+ /* Ugly: Exploit the cursor status field for reporting errors. */
+ pCursor->rc = VERR_DWARF_BAD_INFO;
+ return pszErrValue;
+ }
+
+ if (!pThis->aSections[krtDbgModDwarfSect_str].pv)
+ {
+ int rc = rtDbgModDwarfLoadSection(pThis, krtDbgModDwarfSect_str);
+ if (RT_FAILURE(rc))
+ {
+ /* Ugly: Exploit the cursor status field for reporting errors. */
+ pCursor->rc = rc;
+ return pszErrValue;
+ }
+ }
+
+ return (const char *)pThis->aSections[krtDbgModDwarfSect_str].pv + (size_t)offDebugStr;
+}
+
+
+/** @callback_method_impl{FNRTDWARFATTRDECODER} */
+static DECLCALLBACK(int) rtDwarfDecode_Address(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor)
+{
+ AssertReturn(ATTR_GET_SIZE(pDesc) == sizeof(RTDWARFADDR), VERR_INTERNAL_ERROR_3);
+ NOREF(pDie);
+
+ uint64_t uAddr;
+ switch (uForm)
+ {
+ case DW_FORM_addr: uAddr = rtDwarfCursor_GetNativeUOff(pCursor, 0); break;
+ case DW_FORM_data1: uAddr = rtDwarfCursor_GetU8(pCursor, 0); break;
+ case DW_FORM_data2: uAddr = rtDwarfCursor_GetU16(pCursor, 0); break;
+ case DW_FORM_data4: uAddr = rtDwarfCursor_GetU32(pCursor, 0); break;
+ case DW_FORM_data8: uAddr = rtDwarfCursor_GetU64(pCursor, 0); break;
+ case DW_FORM_udata: uAddr = rtDwarfCursor_GetULeb128(pCursor, 0); break;
+ default:
+ AssertMsgFailedReturn(("%#x (%s)\n", uForm, rtDwarfLog_FormName(uForm)), VERR_DWARF_UNEXPECTED_FORM);
+ }
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+
+ PRTDWARFADDR pAddr = (PRTDWARFADDR)pbMember;
+ pAddr->uAddress = uAddr;
+
+ Log4((" %-20s %#010llx [%s]\n", rtDwarfLog_AttrName(pDesc->uAttr), uAddr, rtDwarfLog_FormName(uForm)));
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTDWARFATTRDECODER} */
+static DECLCALLBACK(int) rtDwarfDecode_Bool(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor)
+{
+ AssertReturn(ATTR_GET_SIZE(pDesc) == sizeof(bool), VERR_INTERNAL_ERROR_3);
+ NOREF(pDie);
+
+ bool *pfMember = (bool *)pbMember;
+ switch (uForm)
+ {
+ case DW_FORM_flag:
+ {
+ uint8_t b = rtDwarfCursor_GetU8(pCursor, UINT8_MAX);
+ if (b > 1)
+ {
+ Log(("Unexpected boolean value %#x\n", b));
+ return RT_FAILURE(pCursor->rc) ? pCursor->rc : pCursor->rc = VERR_DWARF_BAD_INFO;
+ }
+ *pfMember = RT_BOOL(b);
+ break;
+ }
+
+ case DW_FORM_flag_present:
+ *pfMember = true;
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%#x\n", uForm), VERR_DWARF_UNEXPECTED_FORM);
+ }
+
+ Log4((" %-20s %RTbool [%s]\n", rtDwarfLog_AttrName(pDesc->uAttr), *pfMember, rtDwarfLog_FormName(uForm)));
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTDWARFATTRDECODER} */
+static DECLCALLBACK(int) rtDwarfDecode_LowHighPc(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor)
+{
+ AssertReturn(ATTR_GET_SIZE(pDesc) == sizeof(RTDWARFADDRRANGE), VERR_INTERNAL_ERROR_3);
+ AssertReturn(pDesc->uAttr == DW_AT_low_pc || pDesc->uAttr == DW_AT_high_pc, VERR_INTERNAL_ERROR_3);
+ NOREF(pDie);
+
+ uint64_t uAddr;
+ switch (uForm)
+ {
+ case DW_FORM_addr: uAddr = rtDwarfCursor_GetNativeUOff(pCursor, 0); break;
+ case DW_FORM_data1: uAddr = rtDwarfCursor_GetU8(pCursor, 0); break;
+ case DW_FORM_data2: uAddr = rtDwarfCursor_GetU16(pCursor, 0); break;
+ case DW_FORM_data4: uAddr = rtDwarfCursor_GetU32(pCursor, 0); break;
+ case DW_FORM_data8: uAddr = rtDwarfCursor_GetU64(pCursor, 0); break;
+ case DW_FORM_udata: uAddr = rtDwarfCursor_GetULeb128(pCursor, 0); break;
+ default:
+ AssertMsgFailedReturn(("%#x\n", uForm), VERR_DWARF_UNEXPECTED_FORM);
+ }
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+
+ PRTDWARFADDRRANGE pRange = (PRTDWARFADDRRANGE)pbMember;
+ if (pDesc->uAttr == DW_AT_low_pc)
+ {
+ if (pRange->fHaveLowAddress)
+ {
+ Log(("rtDwarfDecode_LowHighPc: Duplicate DW_AT_low_pc\n"));
+ return pCursor->rc = VERR_DWARF_BAD_INFO;
+ }
+ pRange->fHaveLowAddress = true;
+ pRange->uLowAddress = uAddr;
+ }
+ else
+ {
+ if (pRange->fHaveHighAddress)
+ {
+ Log(("rtDwarfDecode_LowHighPc: Duplicate DW_AT_high_pc\n"));
+ return pCursor->rc = VERR_DWARF_BAD_INFO;
+ }
+ pRange->fHaveHighAddress = true;
+ pRange->fHaveHighIsAddress = uForm == DW_FORM_addr;
+ if (!pRange->fHaveHighIsAddress && pRange->fHaveLowAddress)
+ {
+ pRange->fHaveHighIsAddress = true;
+ pRange->uHighAddress = uAddr + pRange->uLowAddress;
+ }
+ else
+ pRange->uHighAddress = uAddr;
+
+ }
+ pRange->cAttrs++;
+
+ Log4((" %-20s %#010llx [%s]\n", rtDwarfLog_AttrName(pDesc->uAttr), uAddr, rtDwarfLog_FormName(uForm)));
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTDWARFATTRDECODER} */
+static DECLCALLBACK(int) rtDwarfDecode_Ranges(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor)
+{
+ AssertReturn(ATTR_GET_SIZE(pDesc) == sizeof(RTDWARFADDRRANGE), VERR_INTERNAL_ERROR_3);
+ AssertReturn(pDesc->uAttr == DW_AT_ranges, VERR_INTERNAL_ERROR_3);
+ NOREF(pDie);
+
+ /* Decode it. */
+ uint64_t off;
+ switch (uForm)
+ {
+ case DW_FORM_addr: off = rtDwarfCursor_GetNativeUOff(pCursor, 0); break;
+ case DW_FORM_data4: off = rtDwarfCursor_GetU32(pCursor, 0); break;
+ case DW_FORM_data8: off = rtDwarfCursor_GetU64(pCursor, 0); break;
+ case DW_FORM_sec_offset: off = rtDwarfCursor_GetUOff(pCursor, 0); break;
+ default:
+ AssertMsgFailedReturn(("%#x\n", uForm), VERR_DWARF_UNEXPECTED_FORM);
+ }
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+
+ /* Validate the offset and load the ranges. */
+ PRTDBGMODDWARF pThis = pCursor->pDwarfMod;
+ if (off >= pThis->aSections[krtDbgModDwarfSect_ranges].cb)
+ {
+ Log(("rtDwarfDecode_Ranges: bad ranges off=%#llx\n", off));
+ return pCursor->rc = VERR_DWARF_BAD_POS;
+ }
+
+ if (!pThis->aSections[krtDbgModDwarfSect_ranges].pv)
+ {
+ int rc = rtDbgModDwarfLoadSection(pThis, krtDbgModDwarfSect_ranges);
+ if (RT_FAILURE(rc))
+ return pCursor->rc = rc;
+ }
+
+ /* Store the result. */
+ PRTDWARFADDRRANGE pRange = (PRTDWARFADDRRANGE)pbMember;
+ if (pRange->fHaveRanges)
+ {
+ Log(("rtDwarfDecode_Ranges: Duplicate DW_AT_ranges\n"));
+ return pCursor->rc = VERR_DWARF_BAD_INFO;
+ }
+ pRange->fHaveRanges = true;
+ pRange->cAttrs++;
+ pRange->pbRanges = (uint8_t const *)pThis->aSections[krtDbgModDwarfSect_ranges].pv + (size_t)off;
+
+ Log4((" %-20s TODO [%s]\n", rtDwarfLog_AttrName(pDesc->uAttr), rtDwarfLog_FormName(uForm)));
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTDWARFATTRDECODER} */
+static DECLCALLBACK(int) rtDwarfDecode_Reference(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor)
+{
+ AssertReturn(ATTR_GET_SIZE(pDesc) == sizeof(RTDWARFREF), VERR_INTERNAL_ERROR_3);
+
+ /* Decode it. */
+ uint64_t off;
+ krtDwarfRef enmWrt = krtDwarfRef_SameUnit;
+ switch (uForm)
+ {
+ case DW_FORM_ref1: off = rtDwarfCursor_GetU8(pCursor, 0); break;
+ case DW_FORM_ref2: off = rtDwarfCursor_GetU16(pCursor, 0); break;
+ case DW_FORM_ref4: off = rtDwarfCursor_GetU32(pCursor, 0); break;
+ case DW_FORM_ref8: off = rtDwarfCursor_GetU64(pCursor, 0); break;
+ case DW_FORM_ref_udata: off = rtDwarfCursor_GetULeb128(pCursor, 0); break;
+
+ case DW_FORM_ref_addr:
+ enmWrt = krtDwarfRef_InfoSection;
+ off = rtDwarfCursor_GetUOff(pCursor, 0);
+ break;
+
+ case DW_FORM_ref_sig8:
+ enmWrt = krtDwarfRef_TypeId64;
+ off = rtDwarfCursor_GetU64(pCursor, 0);
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%#x\n", uForm), VERR_DWARF_UNEXPECTED_FORM);
+ }
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+
+ /* Validate the offset and convert to debug_info relative offsets. */
+ if (enmWrt == krtDwarfRef_InfoSection)
+ {
+ if (off >= pCursor->pDwarfMod->aSections[krtDbgModDwarfSect_info].cb)
+ {
+ Log(("rtDwarfDecode_Reference: bad info off=%#llx\n", off));
+ return pCursor->rc = VERR_DWARF_BAD_POS;
+ }
+ }
+ else if (enmWrt == krtDwarfRef_SameUnit)
+ {
+ PRTDWARFDIECOMPILEUNIT pUnit = rtDwarfDie_GetCompileUnit(pDie);
+ if (off >= pUnit->cbUnit)
+ {
+ Log(("rtDwarfDecode_Reference: bad unit off=%#llx\n", off));
+ return pCursor->rc = VERR_DWARF_BAD_POS;
+ }
+ off += pUnit->offUnit;
+ enmWrt = krtDwarfRef_InfoSection;
+ }
+ /* else: not bother verifying/resolving the indirect type reference yet. */
+
+ /* Store it */
+ PRTDWARFREF pRef = (PRTDWARFREF)pbMember;
+ pRef->enmWrt = enmWrt;
+ pRef->off = off;
+
+ Log4((" %-20s %d:%#010llx [%s]\n", rtDwarfLog_AttrName(pDesc->uAttr), enmWrt, off, rtDwarfLog_FormName(uForm)));
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTDWARFATTRDECODER} */
+static DECLCALLBACK(int) rtDwarfDecode_SectOff(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor)
+{
+ AssertReturn(ATTR_GET_SIZE(pDesc) == sizeof(RTDWARFREF), VERR_INTERNAL_ERROR_3);
+ NOREF(pDie);
+
+ uint64_t off;
+ switch (uForm)
+ {
+ case DW_FORM_data4: off = rtDwarfCursor_GetU32(pCursor, 0); break;
+ case DW_FORM_data8: off = rtDwarfCursor_GetU64(pCursor, 0); break;
+ case DW_FORM_sec_offset: off = rtDwarfCursor_GetUOff(pCursor, 0); break;
+ default:
+ AssertMsgFailedReturn(("%#x (%s)\n", uForm, rtDwarfLog_FormName(uForm)), VERR_DWARF_UNEXPECTED_FORM);
+ }
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+
+ krtDbgModDwarfSect enmSect;
+ krtDwarfRef enmWrt;
+ switch (pDesc->uAttr)
+ {
+ case DW_AT_stmt_list: enmSect = krtDbgModDwarfSect_line; enmWrt = krtDwarfRef_LineSection; break;
+ case DW_AT_macro_info: enmSect = krtDbgModDwarfSect_loc; enmWrt = krtDwarfRef_LocSection; break;
+ case DW_AT_ranges: enmSect = krtDbgModDwarfSect_ranges; enmWrt = krtDwarfRef_RangesSection; break;
+ default:
+ AssertMsgFailedReturn(("%u (%s)\n", pDesc->uAttr, rtDwarfLog_AttrName(pDesc->uAttr)), VERR_INTERNAL_ERROR_4);
+ }
+ size_t cbSect = pCursor->pDwarfMod->aSections[enmSect].cb;
+ if (off >= cbSect)
+ {
+ /* Watcom generates offset past the end of the section, increasing the
+ offset by one for each compile unit. So, just fudge it. */
+ Log(("rtDwarfDecode_SectOff: bad off=%#llx, attr %#x (%s), enmSect=%d cb=%#llx; Assuming watcom/gcc.\n", off,
+ pDesc->uAttr, rtDwarfLog_AttrName(pDesc->uAttr), enmSect, cbSect));
+ off = cbSect;
+ }
+
+ PRTDWARFREF pRef = (PRTDWARFREF)pbMember;
+ pRef->enmWrt = enmWrt;
+ pRef->off = off;
+
+ Log4((" %-20s %d:%#010llx [%s]\n", rtDwarfLog_AttrName(pDesc->uAttr), enmWrt, off, rtDwarfLog_FormName(uForm)));
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTDWARFATTRDECODER} */
+static DECLCALLBACK(int) rtDwarfDecode_String(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor)
+{
+ AssertReturn(ATTR_GET_SIZE(pDesc) == sizeof(const char *), VERR_INTERNAL_ERROR_3);
+ NOREF(pDie);
+
+ const char *psz;
+ switch (uForm)
+ {
+ case DW_FORM_string:
+ psz = rtDwarfCursor_GetSZ(pCursor, NULL);
+ break;
+
+ case DW_FORM_strp:
+ psz = rtDwarfDecodeHlp_GetStrp(pCursor->pDwarfMod, pCursor, NULL);
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%#x\n", uForm), VERR_DWARF_UNEXPECTED_FORM);
+ }
+
+ *(const char **)pbMember = psz;
+ Log4((" %-20s '%s' [%s]\n", rtDwarfLog_AttrName(pDesc->uAttr), psz, rtDwarfLog_FormName(uForm)));
+ return pCursor->rc;
+}
+
+
+/** @callback_method_impl{FNRTDWARFATTRDECODER} */
+static DECLCALLBACK(int) rtDwarfDecode_UnsignedInt(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor)
+{
+ NOREF(pDie);
+ uint64_t u64Val;
+ switch (uForm)
+ {
+ case DW_FORM_udata: u64Val = rtDwarfCursor_GetULeb128(pCursor, 0); break;
+ case DW_FORM_data1: u64Val = rtDwarfCursor_GetU8(pCursor, 0); break;
+ case DW_FORM_data2: u64Val = rtDwarfCursor_GetU16(pCursor, 0); break;
+ case DW_FORM_data4: u64Val = rtDwarfCursor_GetU32(pCursor, 0); break;
+ case DW_FORM_data8: u64Val = rtDwarfCursor_GetU64(pCursor, 0); break;
+ default:
+ AssertMsgFailedReturn(("%#x\n", uForm), VERR_DWARF_UNEXPECTED_FORM);
+ }
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+
+ switch (ATTR_GET_SIZE(pDesc))
+ {
+ case 1:
+ *pbMember = (uint8_t)u64Val;
+ if (*pbMember != u64Val)
+ {
+ AssertFailed();
+ return VERR_OUT_OF_RANGE;
+ }
+ break;
+
+ case 2:
+ *(uint16_t *)pbMember = (uint16_t)u64Val;
+ if (*(uint16_t *)pbMember != u64Val)
+ {
+ AssertFailed();
+ return VERR_OUT_OF_RANGE;
+ }
+ break;
+
+ case 4:
+ *(uint32_t *)pbMember = (uint32_t)u64Val;
+ if (*(uint32_t *)pbMember != u64Val)
+ {
+ AssertFailed();
+ return VERR_OUT_OF_RANGE;
+ }
+ break;
+
+ case 8:
+ *(uint64_t *)pbMember = (uint64_t)u64Val;
+ if (*(uint64_t *)pbMember != u64Val)
+ {
+ AssertFailed();
+ return VERR_OUT_OF_RANGE;
+ }
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%#x\n", ATTR_GET_SIZE(pDesc)), VERR_INTERNAL_ERROR_2);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize location interpreter state from cursor & form.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_NOT_FOUND if no location information (i.e. there is source but
+ * it resulted in no byte code).
+ * @param pLoc The location state structure to initialize.
+ * @param pCursor The cursor to read from.
+ * @param uForm The attribute form.
+ */
+static int rtDwarfLoc_Init(PRTDWARFLOCST pLoc, PRTDWARFCURSOR pCursor, uint32_t uForm)
+{
+ uint32_t cbBlock;
+ switch (uForm)
+ {
+ case DW_FORM_block1:
+ cbBlock = rtDwarfCursor_GetU8(pCursor, 0);
+ break;
+
+ case DW_FORM_block2:
+ cbBlock = rtDwarfCursor_GetU16(pCursor, 0);
+ break;
+
+ case DW_FORM_block4:
+ cbBlock = rtDwarfCursor_GetU32(pCursor, 0);
+ break;
+
+ case DW_FORM_block:
+ cbBlock = rtDwarfCursor_GetULeb128(pCursor, 0);
+ break;
+
+ default:
+ AssertMsgFailedReturn(("uForm=%#x\n", uForm), VERR_DWARF_UNEXPECTED_FORM);
+ }
+ if (!cbBlock)
+ return VERR_NOT_FOUND;
+
+ int rc = rtDwarfCursor_InitForBlock(&pLoc->Cursor, pCursor, cbBlock);
+ if (RT_FAILURE(rc))
+ return rc;
+ pLoc->iTop = -1;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Pushes a value onto the stack.
+ *
+ * @returns VINF_SUCCESS or VERR_DWARF_STACK_OVERFLOW.
+ * @param pLoc The state.
+ * @param uValue The value to push.
+ */
+static int rtDwarfLoc_Push(PRTDWARFLOCST pLoc, uint64_t uValue)
+{
+ int iTop = pLoc->iTop + 1;
+ AssertReturn((unsigned)iTop < RT_ELEMENTS(pLoc->auStack), VERR_DWARF_STACK_OVERFLOW);
+ pLoc->auStack[iTop] = uValue;
+ pLoc->iTop = iTop;
+ return VINF_SUCCESS;
+}
+
+
+static int rtDwarfLoc_Evaluate(PRTDWARFLOCST pLoc, void *pvLater, void *pvUser)
+{
+ RT_NOREF_PV(pvLater); RT_NOREF_PV(pvUser);
+
+ while (!rtDwarfCursor_IsAtEndOfUnit(&pLoc->Cursor))
+ {
+ /* Read the next opcode.*/
+ uint8_t const bOpcode = rtDwarfCursor_GetU8(&pLoc->Cursor, 0);
+
+ /* Get its operands. */
+ uint64_t uOperand1 = 0;
+ uint64_t uOperand2 = 0;
+ switch (bOpcode)
+ {
+ case DW_OP_addr:
+ uOperand1 = rtDwarfCursor_GetNativeUOff(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_pick:
+ case DW_OP_const1u:
+ case DW_OP_deref_size:
+ case DW_OP_xderef_size:
+ uOperand1 = rtDwarfCursor_GetU8(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_const1s:
+ uOperand1 = (int8_t)rtDwarfCursor_GetU8(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_const2u:
+ uOperand1 = rtDwarfCursor_GetU16(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_skip:
+ case DW_OP_bra:
+ case DW_OP_const2s:
+ uOperand1 = (int16_t)rtDwarfCursor_GetU16(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_const4u:
+ uOperand1 = rtDwarfCursor_GetU32(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_const4s:
+ uOperand1 = (int32_t)rtDwarfCursor_GetU32(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_const8u:
+ uOperand1 = rtDwarfCursor_GetU64(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_const8s:
+ uOperand1 = rtDwarfCursor_GetU64(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_regx:
+ case DW_OP_piece:
+ case DW_OP_plus_uconst:
+ case DW_OP_constu:
+ uOperand1 = rtDwarfCursor_GetULeb128(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_consts:
+ case DW_OP_fbreg:
+ case DW_OP_breg0+0: case DW_OP_breg0+1: case DW_OP_breg0+2: case DW_OP_breg0+3:
+ case DW_OP_breg0+4: case DW_OP_breg0+5: case DW_OP_breg0+6: case DW_OP_breg0+7:
+ case DW_OP_breg0+8: case DW_OP_breg0+9: case DW_OP_breg0+10: case DW_OP_breg0+11:
+ case DW_OP_breg0+12: case DW_OP_breg0+13: case DW_OP_breg0+14: case DW_OP_breg0+15:
+ case DW_OP_breg0+16: case DW_OP_breg0+17: case DW_OP_breg0+18: case DW_OP_breg0+19:
+ case DW_OP_breg0+20: case DW_OP_breg0+21: case DW_OP_breg0+22: case DW_OP_breg0+23:
+ case DW_OP_breg0+24: case DW_OP_breg0+25: case DW_OP_breg0+26: case DW_OP_breg0+27:
+ case DW_OP_breg0+28: case DW_OP_breg0+29: case DW_OP_breg0+30: case DW_OP_breg0+31:
+ uOperand1 = rtDwarfCursor_GetSLeb128(&pLoc->Cursor, 0);
+ break;
+ case DW_OP_bregx:
+ uOperand1 = rtDwarfCursor_GetULeb128(&pLoc->Cursor, 0);
+ uOperand2 = rtDwarfCursor_GetSLeb128(&pLoc->Cursor, 0);
+ break;
+ }
+ if (RT_FAILURE(pLoc->Cursor.rc))
+ break;
+
+ /* Interpret the opcode. */
+ int rc;
+ switch (bOpcode)
+ {
+ case DW_OP_const1u:
+ case DW_OP_const1s:
+ case DW_OP_const2u:
+ case DW_OP_const2s:
+ case DW_OP_const4u:
+ case DW_OP_const4s:
+ case DW_OP_const8u:
+ case DW_OP_const8s:
+ case DW_OP_constu:
+ case DW_OP_consts:
+ case DW_OP_addr:
+ rc = rtDwarfLoc_Push(pLoc, uOperand1);
+ break;
+ case DW_OP_lit0 + 0: case DW_OP_lit0 + 1: case DW_OP_lit0 + 2: case DW_OP_lit0 + 3:
+ case DW_OP_lit0 + 4: case DW_OP_lit0 + 5: case DW_OP_lit0 + 6: case DW_OP_lit0 + 7:
+ case DW_OP_lit0 + 8: case DW_OP_lit0 + 9: case DW_OP_lit0 + 10: case DW_OP_lit0 + 11:
+ case DW_OP_lit0 + 12: case DW_OP_lit0 + 13: case DW_OP_lit0 + 14: case DW_OP_lit0 + 15:
+ case DW_OP_lit0 + 16: case DW_OP_lit0 + 17: case DW_OP_lit0 + 18: case DW_OP_lit0 + 19:
+ case DW_OP_lit0 + 20: case DW_OP_lit0 + 21: case DW_OP_lit0 + 22: case DW_OP_lit0 + 23:
+ case DW_OP_lit0 + 24: case DW_OP_lit0 + 25: case DW_OP_lit0 + 26: case DW_OP_lit0 + 27:
+ case DW_OP_lit0 + 28: case DW_OP_lit0 + 29: case DW_OP_lit0 + 30: case DW_OP_lit0 + 31:
+ rc = rtDwarfLoc_Push(pLoc, bOpcode - DW_OP_lit0);
+ break;
+ case DW_OP_nop:
+ break;
+ case DW_OP_dup: /** @todo 0 operands. */
+ case DW_OP_drop: /** @todo 0 operands. */
+ case DW_OP_over: /** @todo 0 operands. */
+ case DW_OP_pick: /** @todo 1 operands, a 1-byte stack index. */
+ case DW_OP_swap: /** @todo 0 operands. */
+ case DW_OP_rot: /** @todo 0 operands. */
+ case DW_OP_abs: /** @todo 0 operands. */
+ case DW_OP_and: /** @todo 0 operands. */
+ case DW_OP_div: /** @todo 0 operands. */
+ case DW_OP_minus: /** @todo 0 operands. */
+ case DW_OP_mod: /** @todo 0 operands. */
+ case DW_OP_mul: /** @todo 0 operands. */
+ case DW_OP_neg: /** @todo 0 operands. */
+ case DW_OP_not: /** @todo 0 operands. */
+ case DW_OP_or: /** @todo 0 operands. */
+ case DW_OP_plus: /** @todo 0 operands. */
+ case DW_OP_plus_uconst: /** @todo 1 operands, a ULEB128 addend. */
+ case DW_OP_shl: /** @todo 0 operands. */
+ case DW_OP_shr: /** @todo 0 operands. */
+ case DW_OP_shra: /** @todo 0 operands. */
+ case DW_OP_xor: /** @todo 0 operands. */
+ case DW_OP_skip: /** @todo 1 signed 2-byte constant. */
+ case DW_OP_bra: /** @todo 1 signed 2-byte constant. */
+ case DW_OP_eq: /** @todo 0 operands. */
+ case DW_OP_ge: /** @todo 0 operands. */
+ case DW_OP_gt: /** @todo 0 operands. */
+ case DW_OP_le: /** @todo 0 operands. */
+ case DW_OP_lt: /** @todo 0 operands. */
+ case DW_OP_ne: /** @todo 0 operands. */
+ case DW_OP_reg0 + 0: case DW_OP_reg0 + 1: case DW_OP_reg0 + 2: case DW_OP_reg0 + 3: /** @todo 0 operands - reg 0..31. */
+ case DW_OP_reg0 + 4: case DW_OP_reg0 + 5: case DW_OP_reg0 + 6: case DW_OP_reg0 + 7:
+ case DW_OP_reg0 + 8: case DW_OP_reg0 + 9: case DW_OP_reg0 + 10: case DW_OP_reg0 + 11:
+ case DW_OP_reg0 + 12: case DW_OP_reg0 + 13: case DW_OP_reg0 + 14: case DW_OP_reg0 + 15:
+ case DW_OP_reg0 + 16: case DW_OP_reg0 + 17: case DW_OP_reg0 + 18: case DW_OP_reg0 + 19:
+ case DW_OP_reg0 + 20: case DW_OP_reg0 + 21: case DW_OP_reg0 + 22: case DW_OP_reg0 + 23:
+ case DW_OP_reg0 + 24: case DW_OP_reg0 + 25: case DW_OP_reg0 + 26: case DW_OP_reg0 + 27:
+ case DW_OP_reg0 + 28: case DW_OP_reg0 + 29: case DW_OP_reg0 + 30: case DW_OP_reg0 + 31:
+ case DW_OP_breg0+ 0: case DW_OP_breg0+ 1: case DW_OP_breg0+ 2: case DW_OP_breg0+ 3: /** @todo 1 operand, a SLEB128 offset. */
+ case DW_OP_breg0+ 4: case DW_OP_breg0+ 5: case DW_OP_breg0+ 6: case DW_OP_breg0+ 7:
+ case DW_OP_breg0+ 8: case DW_OP_breg0+ 9: case DW_OP_breg0+ 10: case DW_OP_breg0+ 11:
+ case DW_OP_breg0+ 12: case DW_OP_breg0+ 13: case DW_OP_breg0+ 14: case DW_OP_breg0+ 15:
+ case DW_OP_breg0+ 16: case DW_OP_breg0+ 17: case DW_OP_breg0+ 18: case DW_OP_breg0+ 19:
+ case DW_OP_breg0+ 20: case DW_OP_breg0+ 21: case DW_OP_breg0+ 22: case DW_OP_breg0+ 23:
+ case DW_OP_breg0+ 24: case DW_OP_breg0+ 25: case DW_OP_breg0+ 26: case DW_OP_breg0+ 27:
+ case DW_OP_breg0+ 28: case DW_OP_breg0+ 29: case DW_OP_breg0+ 30: case DW_OP_breg0+ 31:
+ case DW_OP_piece: /** @todo 1 operand, a ULEB128 size of piece addressed. */
+ case DW_OP_regx: /** @todo 1 operand, a ULEB128 register. */
+ case DW_OP_fbreg: /** @todo 1 operand, a SLEB128 offset. */
+ case DW_OP_bregx: /** @todo 2 operands, a ULEB128 register followed by a SLEB128 offset. */
+ case DW_OP_deref: /** @todo 0 operands. */
+ case DW_OP_deref_size: /** @todo 1 operand, a 1-byte size of data retrieved. */
+ case DW_OP_xderef: /** @todo 0 operands. */
+ case DW_OP_xderef_size: /** @todo 1 operand, a 1-byte size of data retrieved. */
+ AssertMsgFailedReturn(("bOpcode=%#x\n", bOpcode), VERR_DWARF_TODO);
+ default:
+ AssertMsgFailedReturn(("bOpcode=%#x\n", bOpcode), VERR_DWARF_UNKNOWN_LOC_OPCODE);
+ }
+ }
+
+ return pLoc->Cursor.rc;
+}
+
+
+/** @callback_method_impl{FNRTDWARFATTRDECODER} */
+static DECLCALLBACK(int) rtDwarfDecode_SegmentLoc(PRTDWARFDIE pDie, uint8_t *pbMember, PCRTDWARFATTRDESC pDesc,
+ uint32_t uForm, PRTDWARFCURSOR pCursor)
+{
+ NOREF(pDie);
+ AssertReturn(ATTR_GET_SIZE(pDesc) == 2, VERR_DWARF_IPE);
+
+ int rc;
+ if ( uForm == DW_FORM_block
+ || uForm == DW_FORM_block1
+ || uForm == DW_FORM_block2
+ || uForm == DW_FORM_block4)
+ {
+ RTDWARFLOCST LocSt;
+ rc = rtDwarfLoc_Init(&LocSt, pCursor, uForm);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtDwarfLoc_Evaluate(&LocSt, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (LocSt.iTop >= 0)
+ {
+ *(uint16_t *)pbMember = LocSt.auStack[LocSt.iTop];
+ Log4((" %-20s %#06llx [%s]\n", rtDwarfLog_AttrName(pDesc->uAttr),
+ LocSt.auStack[LocSt.iTop], rtDwarfLog_FormName(uForm)));
+ return VINF_SUCCESS;
+ }
+ rc = VERR_DWARF_STACK_UNDERFLOW;
+ }
+ }
+ }
+ else
+ rc = rtDwarfDecode_UnsignedInt(pDie, pbMember, pDesc, uForm, pCursor);
+ return rc;
+}
+
+/*
+ *
+ * DWARF debug_info parser
+ * DWARF debug_info parser
+ * DWARF debug_info parser
+ *
+ */
+
+
+/**
+ * Special hack to get the name and/or linkage name for a subprogram via a
+ * specification reference.
+ *
+ * Since this is a hack, we ignore failure.
+ *
+ * If we want to really make use of DWARF info, we'll have to create some kind
+ * of lookup tree for handling this. But currently we don't, so a hack will
+ * suffice.
+ *
+ * @param pThis The DWARF instance.
+ * @param pSubProgram The subprogram which is short on names.
+ */
+static void rtDwarfInfo_TryGetSubProgramNameFromSpecRef(PRTDBGMODDWARF pThis, PRTDWARFDIESUBPROGRAM pSubProgram)
+{
+ /*
+ * Must have a spec ref, and it must be in the info section.
+ */
+ if (pSubProgram->SpecRef.enmWrt != krtDwarfRef_InfoSection)
+ return;
+
+ /*
+ * Create a cursor for reading the info and then the abbrivation code
+ * starting the off the DIE.
+ */
+ RTDWARFCURSOR InfoCursor;
+ int rc = rtDwarfCursor_InitWithOffset(&InfoCursor, pThis, krtDbgModDwarfSect_info, pSubProgram->SpecRef.off);
+ if (RT_FAILURE(rc))
+ return;
+
+ uint32_t uAbbrCode = rtDwarfCursor_GetULeb128AsU32(&InfoCursor, UINT32_MAX);
+ if (uAbbrCode)
+ {
+ /* Only references to subprogram tags are interesting here. */
+ PCRTDWARFABBREV pAbbrev = rtDwarfAbbrev_Lookup(pThis, uAbbrCode);
+ if ( pAbbrev
+ && pAbbrev->uTag == DW_TAG_subprogram)
+ {
+ /*
+ * Use rtDwarfInfo_ParseDie to do the parsing, but with a different
+ * attribute spec than usual.
+ */
+ rtDwarfInfo_ParseDie(pThis, &pSubProgram->Core, &g_SubProgramSpecHackDesc, &InfoCursor,
+ pAbbrev, false /*fInitDie*/);
+ }
+ }
+
+ rtDwarfCursor_Delete(&InfoCursor, VINF_SUCCESS);
+}
+
+
+/**
+ * Select which name to use.
+ *
+ * @returns One of the names.
+ * @param pszName The DWARF name, may exclude namespace and class.
+ * Can also be NULL.
+ * @param pszLinkageName The linkage name. Can be NULL.
+ */
+static const char *rtDwarfInfo_SelectName(const char *pszName, const char *pszLinkageName)
+{
+ if (!pszName || !pszLinkageName)
+ return pszName ? pszName : pszLinkageName;
+
+ /*
+ * Some heuristics for selecting the link name if the normal name is missing
+ * namespace or class prefixes.
+ */
+ size_t cchName = strlen(pszName);
+ size_t cchLinkageName = strlen(pszLinkageName);
+ if (cchLinkageName <= cchName + 1)
+ return pszName;
+
+ const char *psz = strstr(pszLinkageName, pszName);
+ if (!psz || psz - pszLinkageName < 4)
+ return pszName;
+
+ return pszLinkageName;
+}
+
+
+/**
+ * Parse the attributes of a DIE.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param pDie The internal DIE structure to fill.
+ */
+static int rtDwarfInfo_SnoopSymbols(PRTDBGMODDWARF pThis, PRTDWARFDIE pDie)
+{
+ int rc = VINF_SUCCESS;
+ switch (pDie->uTag)
+ {
+ case DW_TAG_subprogram:
+ {
+ PRTDWARFDIESUBPROGRAM pSubProgram = (PRTDWARFDIESUBPROGRAM)pDie;
+
+ /* Obtain referenced specification there is only partial info. */
+ if ( pSubProgram->PcRange.cAttrs
+ && !pSubProgram->pszName)
+ rtDwarfInfo_TryGetSubProgramNameFromSpecRef(pThis, pSubProgram);
+
+ if (pSubProgram->PcRange.cAttrs)
+ {
+ if (pSubProgram->PcRange.fHaveRanges)
+ Log5(("subprogram %s (%s) <implement ranges>\n", pSubProgram->pszName, pSubProgram->pszLinkageName));
+ else
+ {
+ Log5(("subprogram %s (%s) %#llx-%#llx%s\n", pSubProgram->pszName, pSubProgram->pszLinkageName,
+ pSubProgram->PcRange.uLowAddress, pSubProgram->PcRange.uHighAddress,
+ pSubProgram->PcRange.cAttrs == 2 ? "" : " !bad!"));
+ if ( ( pSubProgram->pszName || pSubProgram->pszLinkageName)
+ && pSubProgram->PcRange.cAttrs == 2)
+ {
+ if (pThis->iWatcomPass == 1)
+ rc = rtDbgModDwarfRecordSegOffset(pThis, pSubProgram->uSegment, pSubProgram->PcRange.uHighAddress);
+ else
+ {
+ RTDBGSEGIDX iSeg;
+ RTUINTPTR offSeg;
+ rc = rtDbgModDwarfLinkAddressToSegOffset(pThis, pSubProgram->uSegment,
+ pSubProgram->PcRange.uLowAddress,
+ &iSeg, &offSeg);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cb;
+ if (pSubProgram->PcRange.uHighAddress >= pSubProgram->PcRange.uLowAddress)
+ cb = pSubProgram->PcRange.uHighAddress - pSubProgram->PcRange.uLowAddress;
+ else
+ cb = 1;
+ rc = RTDbgModSymbolAdd(pThis->hCnt,
+ rtDwarfInfo_SelectName(pSubProgram->pszName, pSubProgram->pszLinkageName),
+ iSeg, offSeg, cb, 0 /*fFlags*/, NULL /*piOrdinal*/);
+ if (RT_FAILURE(rc))
+ {
+ if ( rc == VERR_DBG_DUPLICATE_SYMBOL
+ || rc == VERR_DBG_ADDRESS_CONFLICT /** @todo figure why this happens with 10.6.8 mach_kernel, 32-bit. */
+ )
+ rc = VINF_SUCCESS;
+ else
+ AssertMsgFailed(("%Rrc\n", rc));
+ }
+ }
+ else if ( pSubProgram->PcRange.uLowAddress == 0 /* see with vmlinux */
+ && pSubProgram->PcRange.uHighAddress == 0)
+ {
+ Log5(("rtDbgModDwarfLinkAddressToSegOffset: Ignoring empty range.\n"));
+ rc = VINF_SUCCESS; /* ignore */
+ }
+ else
+ {
+ AssertRC(rc);
+ Log5(("rtDbgModDwarfLinkAddressToSegOffset failed: %Rrc\n", rc));
+ }
+ }
+ }
+ }
+ }
+ else
+ Log5(("subprogram %s (%s) external\n", pSubProgram->pszName, pSubProgram->pszLinkageName));
+ break;
+ }
+
+ case DW_TAG_label:
+ {
+ PCRTDWARFDIELABEL pLabel = (PCRTDWARFDIELABEL)pDie;
+ //if (pLabel->fExternal)
+ {
+ Log5(("label %s %#x:%#llx\n", pLabel->pszName, pLabel->uSegment, pLabel->Address.uAddress));
+ if (pThis->iWatcomPass == 1)
+ rc = rtDbgModDwarfRecordSegOffset(pThis, pLabel->uSegment, pLabel->Address.uAddress);
+ else if (pLabel->pszName && *pLabel->pszName != '\0') /* Seen empty labels with isolinux. */
+ {
+ RTDBGSEGIDX iSeg;
+ RTUINTPTR offSeg;
+ rc = rtDbgModDwarfLinkAddressToSegOffset(pThis, pLabel->uSegment, pLabel->Address.uAddress,
+ &iSeg, &offSeg);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDbgModSymbolAdd(pThis->hCnt, pLabel->pszName, iSeg, offSeg, 0 /*cb*/,
+ 0 /*fFlags*/, NULL /*piOrdinal*/);
+ AssertMsg(RT_SUCCESS(rc) || rc == VERR_DBG_ADDRESS_CONFLICT,
+ ("%Rrc %s %x:%x\n", rc, pLabel->pszName, iSeg, offSeg));
+ }
+ else
+ Log5(("rtDbgModDwarfLinkAddressToSegOffset failed: %Rrc\n", rc));
+
+ /* Ignore errors regarding local labels. */
+ if (RT_FAILURE(rc) && !pLabel->fExternal)
+ rc = -rc;
+ }
+
+ }
+ break;
+ }
+
+ }
+ return rc;
+}
+
+
+/**
+ * Initializes the non-core fields of an internal DIE structure.
+ *
+ * @param pDie The DIE structure.
+ * @param pDieDesc The DIE descriptor.
+ */
+static void rtDwarfInfo_InitDie(PRTDWARFDIE pDie, PCRTDWARFDIEDESC pDieDesc)
+{
+ size_t i = pDieDesc->cAttributes;
+ while (i-- > 0)
+ {
+ switch (pDieDesc->paAttributes[i].cbInit & ATTR_INIT_MASK)
+ {
+ case ATTR_INIT_ZERO:
+ /* Nothing to do (RTMemAllocZ). */
+ break;
+
+ case ATTR_INIT_FFFS:
+ switch (pDieDesc->paAttributes[i].cbInit & ATTR_SIZE_MASK)
+ {
+ case 1:
+ *(uint8_t *)((uintptr_t)pDie + pDieDesc->paAttributes[i].off) = UINT8_MAX;
+ break;
+ case 2:
+ *(uint16_t *)((uintptr_t)pDie + pDieDesc->paAttributes[i].off) = UINT16_MAX;
+ break;
+ case 4:
+ *(uint32_t *)((uintptr_t)pDie + pDieDesc->paAttributes[i].off) = UINT32_MAX;
+ break;
+ case 8:
+ *(uint64_t *)((uintptr_t)pDie + pDieDesc->paAttributes[i].off) = UINT64_MAX;
+ break;
+ default:
+ AssertFailed();
+ memset((uint8_t *)pDie + pDieDesc->paAttributes[i].off, 0xff,
+ pDieDesc->paAttributes[i].cbInit & ATTR_SIZE_MASK);
+ break;
+ }
+ break;
+
+ default:
+ AssertFailed();
+ }
+ }
+}
+
+
+/**
+ * Creates a new internal DIE structure and links it up.
+ *
+ * @returns Pointer to the new DIE structure.
+ * @param pThis The DWARF instance.
+ * @param pDieDesc The DIE descriptor (for size and init).
+ * @param pAbbrev The abbreviation cache entry.
+ * @param pParent The parent DIE (NULL if unit).
+ */
+static PRTDWARFDIE rtDwarfInfo_NewDie(PRTDBGMODDWARF pThis, PCRTDWARFDIEDESC pDieDesc,
+ PCRTDWARFABBREV pAbbrev, PRTDWARFDIE pParent)
+{
+ NOREF(pThis);
+ Assert(pDieDesc->cbDie >= sizeof(RTDWARFDIE));
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+ uint32_t iAllocator = pDieDesc->cbDie > pThis->aDieAllocators[0].cbMax;
+ Assert(pDieDesc->cbDie <= pThis->aDieAllocators[iAllocator].cbMax);
+ PRTDWARFDIE pDie = (PRTDWARFDIE)RTMemCacheAlloc(pThis->aDieAllocators[iAllocator].hMemCache);
+#else
+ PRTDWARFDIE pDie = (PRTDWARFDIE)RTMemAllocZ(pDieDesc->cbDie);
+#endif
+ if (pDie)
+ {
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+ RT_BZERO(pDie, pDieDesc->cbDie);
+ pDie->iAllocator = iAllocator;
+#endif
+ rtDwarfInfo_InitDie(pDie, pDieDesc);
+
+ pDie->uTag = pAbbrev->uTag;
+ pDie->offSpec = pAbbrev->offSpec;
+ pDie->pParent = pParent;
+ if (pParent)
+ RTListAppend(&pParent->ChildList, &pDie->SiblingNode);
+ else
+ RTListInit(&pDie->SiblingNode);
+ RTListInit(&pDie->ChildList);
+
+ }
+ return pDie;
+}
+
+
+/**
+ * Free all children of a DIE.
+ *
+ * @param pThis The DWARF instance.
+ * @param pParentDie The parent DIE.
+ */
+static void rtDwarfInfo_FreeChildren(PRTDBGMODDWARF pThis, PRTDWARFDIE pParentDie)
+{
+ PRTDWARFDIE pChild, pNextChild;
+ RTListForEachSafe(&pParentDie->ChildList, pChild, pNextChild, RTDWARFDIE, SiblingNode)
+ {
+ if (!RTListIsEmpty(&pChild->ChildList))
+ rtDwarfInfo_FreeChildren(pThis, pChild);
+ RTListNodeRemove(&pChild->SiblingNode);
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+ RTMemCacheFree(pThis->aDieAllocators[pChild->iAllocator].hMemCache, pChild);
+#else
+ RTMemFree(pChild);
+#endif
+ }
+}
+
+
+/**
+ * Free a DIE an all its children.
+ *
+ * @param pThis The DWARF instance.
+ * @param pDie The DIE to free.
+ */
+static void rtDwarfInfo_FreeDie(PRTDBGMODDWARF pThis, PRTDWARFDIE pDie)
+{
+ rtDwarfInfo_FreeChildren(pThis, pDie);
+ RTListNodeRemove(&pDie->SiblingNode);
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+ RTMemCacheFree(pThis->aDieAllocators[pDie->iAllocator].hMemCache, pDie);
+#else
+ RTMemFree(pChild);
+#endif
+}
+
+
+/**
+ * Skips a form.
+ * @returns IPRT status code
+ * @param pCursor The cursor.
+ * @param uForm The form to skip.
+ */
+static int rtDwarfInfo_SkipForm(PRTDWARFCURSOR pCursor, uint32_t uForm)
+{
+ switch (uForm)
+ {
+ case DW_FORM_addr:
+ return rtDwarfCursor_SkipBytes(pCursor, pCursor->cbNativeAddr);
+
+ case DW_FORM_block:
+ case DW_FORM_exprloc:
+ return rtDwarfCursor_SkipBytes(pCursor, rtDwarfCursor_GetULeb128(pCursor, 0));
+
+ case DW_FORM_block1:
+ return rtDwarfCursor_SkipBytes(pCursor, rtDwarfCursor_GetU8(pCursor, 0));
+
+ case DW_FORM_block2:
+ return rtDwarfCursor_SkipBytes(pCursor, rtDwarfCursor_GetU16(pCursor, 0));
+
+ case DW_FORM_block4:
+ return rtDwarfCursor_SkipBytes(pCursor, rtDwarfCursor_GetU32(pCursor, 0));
+
+ case DW_FORM_data1:
+ case DW_FORM_ref1:
+ case DW_FORM_flag:
+ return rtDwarfCursor_SkipBytes(pCursor, 1);
+
+ case DW_FORM_data2:
+ case DW_FORM_ref2:
+ return rtDwarfCursor_SkipBytes(pCursor, 2);
+
+ case DW_FORM_data4:
+ case DW_FORM_ref4:
+ return rtDwarfCursor_SkipBytes(pCursor, 4);
+
+ case DW_FORM_data8:
+ case DW_FORM_ref8:
+ case DW_FORM_ref_sig8:
+ return rtDwarfCursor_SkipBytes(pCursor, 8);
+
+ case DW_FORM_udata:
+ case DW_FORM_sdata:
+ case DW_FORM_ref_udata:
+ return rtDwarfCursor_SkipLeb128(pCursor);
+
+ case DW_FORM_string:
+ rtDwarfCursor_GetSZ(pCursor, NULL);
+ return pCursor->rc;
+
+ case DW_FORM_indirect:
+ return rtDwarfInfo_SkipForm(pCursor, rtDwarfCursor_GetULeb128AsU32(pCursor, UINT32_MAX));
+
+ case DW_FORM_strp:
+ case DW_FORM_ref_addr:
+ case DW_FORM_sec_offset:
+ return rtDwarfCursor_SkipBytes(pCursor, pCursor->f64bitDwarf ? 8 : 4);
+
+ case DW_FORM_flag_present:
+ return pCursor->rc; /* no data */
+
+ default:
+ Log(("rtDwarfInfo_SkipForm: Unknown form %#x\n", uForm));
+ return VERR_DWARF_UNKNOWN_FORM;
+ }
+}
+
+
+
+#ifdef SOME_UNUSED_FUNCTION
+/**
+ * Skips a DIE.
+ *
+ * @returns IPRT status code.
+ * @param pCursor The cursor.
+ * @param pAbbrevCursor The abbreviation cursor.
+ */
+static int rtDwarfInfo_SkipDie(PRTDWARFCURSOR pCursor, PRTDWARFCURSOR pAbbrevCursor)
+{
+ for (;;)
+ {
+ uint32_t uAttr = rtDwarfCursor_GetULeb128AsU32(pAbbrevCursor, 0);
+ uint32_t uForm = rtDwarfCursor_GetULeb128AsU32(pAbbrevCursor, 0);
+ if (uAttr == 0 && uForm == 0)
+ break;
+
+ int rc = rtDwarfInfo_SkipForm(pCursor, uForm);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return RT_FAILURE(pCursor->rc) ? pCursor->rc : pAbbrevCursor->rc;
+}
+#endif
+
+
+/**
+ * Parse the attributes of a DIE.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param pDie The internal DIE structure to fill.
+ * @param pDieDesc The DIE descriptor.
+ * @param pCursor The debug_info cursor.
+ * @param pAbbrev The abbreviation cache entry.
+ * @param fInitDie Whether to initialize the DIE first. If not (@c
+ * false) it's safe to assume we're following a
+ * DW_AT_specification or DW_AT_abstract_origin,
+ * and that we shouldn't be snooping any symbols.
+ */
+static int rtDwarfInfo_ParseDie(PRTDBGMODDWARF pThis, PRTDWARFDIE pDie, PCRTDWARFDIEDESC pDieDesc,
+ PRTDWARFCURSOR pCursor, PCRTDWARFABBREV pAbbrev, bool fInitDie)
+{
+ RTDWARFCURSOR AbbrevCursor;
+ int rc = rtDwarfCursor_InitWithOffset(&AbbrevCursor, pThis, krtDbgModDwarfSect_abbrev, pAbbrev->offSpec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (fInitDie)
+ rtDwarfInfo_InitDie(pDie, pDieDesc);
+ for (;;)
+ {
+#ifdef LOG_ENABLED
+ uint32_t const off = (uint32_t)(AbbrevCursor.pb - AbbrevCursor.pbStart);
+#endif
+ uint32_t uAttr = rtDwarfCursor_GetULeb128AsU32(&AbbrevCursor, 0);
+ uint32_t uForm = rtDwarfCursor_GetULeb128AsU32(&AbbrevCursor, 0);
+ Log4((" %04x: %-23s [%s]\n", off, rtDwarfLog_AttrName(uAttr), rtDwarfLog_FormName(uForm)));
+ if (uAttr == 0)
+ break;
+ if (uForm == DW_FORM_indirect)
+ uForm = rtDwarfCursor_GetULeb128AsU32(pCursor, 0);
+
+ /* Look up the attribute in the descriptor and invoke the decoder. */
+ PCRTDWARFATTRDESC pAttr = NULL;
+ size_t i = pDieDesc->cAttributes;
+ while (i-- > 0)
+ if (pDieDesc->paAttributes[i].uAttr == uAttr)
+ {
+ pAttr = &pDieDesc->paAttributes[i];
+ rc = pAttr->pfnDecoder(pDie, (uint8_t *)pDie + pAttr->off, pAttr, uForm, pCursor);
+ break;
+ }
+
+ /* Some house keeping. */
+ if (pAttr)
+ pDie->cDecodedAttrs++;
+ else
+ {
+ pDie->cUnhandledAttrs++;
+ rc = rtDwarfInfo_SkipForm(pCursor, uForm);
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ rc = rtDwarfCursor_Delete(&AbbrevCursor, rc);
+ if (RT_SUCCESS(rc))
+ rc = pCursor->rc;
+
+ /*
+ * Snoop up symbols on the way out.
+ */
+ if (RT_SUCCESS(rc) && fInitDie)
+ {
+ rc = rtDwarfInfo_SnoopSymbols(pThis, pDie);
+ /* Ignore duplicates, get work done instead. */
+ /** @todo clean up global/static symbol mess. */
+ if (rc == VERR_DBG_DUPLICATE_SYMBOL || rc == VERR_DBG_ADDRESS_CONFLICT)
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Load the debug information of a unit.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DWARF instance.
+ * @param pCursor The debug_info cursor.
+ * @param fKeepDies Whether to keep the DIEs or discard them as soon
+ * as possible.
+ */
+static int rtDwarfInfo_LoadUnit(PRTDBGMODDWARF pThis, PRTDWARFCURSOR pCursor, bool fKeepDies)
+{
+ Log(("rtDwarfInfo_LoadUnit: %#x\n", rtDwarfCursor_CalcSectOffsetU32(pCursor)));
+
+ /*
+ * Read the compilation unit header.
+ */
+ uint64_t offUnit = rtDwarfCursor_CalcSectOffsetU32(pCursor);
+ uint64_t cbUnit = rtDwarfCursor_GetInitialLength(pCursor);
+ cbUnit += rtDwarfCursor_CalcSectOffsetU32(pCursor) - offUnit;
+ uint16_t const uVer = rtDwarfCursor_GetUHalf(pCursor, 0);
+ if ( uVer < 2
+ || uVer > 4)
+ return rtDwarfCursor_SkipUnit(pCursor);
+ uint64_t const offAbbrev = rtDwarfCursor_GetUOff(pCursor, UINT64_MAX);
+ uint8_t const cbNativeAddr = rtDwarfCursor_GetU8(pCursor, UINT8_MAX);
+ if (RT_FAILURE(pCursor->rc))
+ return pCursor->rc;
+ Log((" uVer=%d offAbbrev=%#llx cbNativeAddr=%d\n", uVer, offAbbrev, cbNativeAddr));
+
+ /*
+ * Set up the abbreviation cache and store the native address size in the cursor.
+ */
+ if (offAbbrev > UINT32_MAX)
+ {
+ Log(("Unexpected abbrviation code offset of %#llx\n", offAbbrev));
+ return VERR_DWARF_BAD_INFO;
+ }
+ rtDwarfAbbrev_SetUnitOffset(pThis, (uint32_t)offAbbrev);
+ pCursor->cbNativeAddr = cbNativeAddr;
+
+ /*
+ * The first DIE is a compile or partial unit, parse it here.
+ */
+ uint32_t uAbbrCode = rtDwarfCursor_GetULeb128AsU32(pCursor, UINT32_MAX);
+ if (!uAbbrCode)
+ {
+ Log(("Unexpected abbrviation code of zero\n"));
+ return VERR_DWARF_BAD_INFO;
+ }
+ PCRTDWARFABBREV pAbbrev = rtDwarfAbbrev_Lookup(pThis, uAbbrCode);
+ if (!pAbbrev)
+ return VERR_DWARF_ABBREV_NOT_FOUND;
+ if ( pAbbrev->uTag != DW_TAG_compile_unit
+ && pAbbrev->uTag != DW_TAG_partial_unit)
+ {
+ Log(("Unexpected compile/partial unit tag %#x\n", pAbbrev->uTag));
+ return VERR_DWARF_BAD_INFO;
+ }
+
+ PRTDWARFDIECOMPILEUNIT pUnit;
+ pUnit = (PRTDWARFDIECOMPILEUNIT)rtDwarfInfo_NewDie(pThis, &g_CompileUnitDesc, pAbbrev, NULL /*pParent*/);
+ if (!pUnit)
+ return VERR_NO_MEMORY;
+ pUnit->offUnit = offUnit;
+ pUnit->cbUnit = cbUnit;
+ pUnit->offAbbrev = offAbbrev;
+ pUnit->cbNativeAddr = cbNativeAddr;
+ pUnit->uDwarfVer = (uint8_t)uVer;
+ RTListAppend(&pThis->CompileUnitList, &pUnit->Core.SiblingNode);
+
+ int rc = rtDwarfInfo_ParseDie(pThis, &pUnit->Core, &g_CompileUnitDesc, pCursor, pAbbrev, true /*fInitDie*/);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Parse DIEs.
+ */
+ uint32_t cDepth = 0;
+ PRTDWARFDIE pParentDie = &pUnit->Core;
+ while (!rtDwarfCursor_IsAtEndOfUnit(pCursor))
+ {
+#ifdef LOG_ENABLED
+ uint32_t offLog = rtDwarfCursor_CalcSectOffsetU32(pCursor);
+#endif
+ uAbbrCode = rtDwarfCursor_GetULeb128AsU32(pCursor, UINT32_MAX);
+ if (!uAbbrCode)
+ {
+ /* End of siblings, up one level. (Is this correct?) */
+ if (pParentDie->pParent)
+ {
+ pParentDie = pParentDie->pParent;
+ cDepth--;
+ if (!fKeepDies && pParentDie->pParent)
+ rtDwarfInfo_FreeChildren(pThis, pParentDie);
+ }
+ }
+ else
+ {
+ /*
+ * Look up the abbreviation and match the tag up with a descriptor.
+ */
+ pAbbrev = rtDwarfAbbrev_Lookup(pThis, uAbbrCode);
+ if (!pAbbrev)
+ return VERR_DWARF_ABBREV_NOT_FOUND;
+
+ PCRTDWARFDIEDESC pDieDesc;
+ const char *pszName;
+ if (pAbbrev->uTag < RT_ELEMENTS(g_aTagDescs))
+ {
+ Assert(g_aTagDescs[pAbbrev->uTag].uTag == pAbbrev->uTag || g_aTagDescs[pAbbrev->uTag].uTag == 0);
+ pszName = g_aTagDescs[pAbbrev->uTag].pszName;
+ pDieDesc = g_aTagDescs[pAbbrev->uTag].pDesc;
+ }
+ else
+ {
+ pszName = "<unknown>";
+ pDieDesc = &g_CoreDieDesc;
+ }
+ Log4(("%08x: %*stag=%s (%#x, abbrev %u @ %#x)%s\n", offLog, cDepth * 2, "", pszName,
+ pAbbrev->uTag, uAbbrCode, pAbbrev->offSpec - pAbbrev->cbHdr, pAbbrev->fChildren ? " has children" : ""));
+
+ /*
+ * Create a new internal DIE structure and parse the
+ * attributes.
+ */
+ PRTDWARFDIE pNewDie = rtDwarfInfo_NewDie(pThis, pDieDesc, pAbbrev, pParentDie);
+ if (!pNewDie)
+ return VERR_NO_MEMORY;
+
+ if (pAbbrev->fChildren)
+ {
+ pParentDie = pNewDie;
+ cDepth++;
+ }
+
+ rc = rtDwarfInfo_ParseDie(pThis, pNewDie, pDieDesc, pCursor, pAbbrev, true /*fInitDie*/);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (!fKeepDies && !pAbbrev->fChildren)
+ rtDwarfInfo_FreeDie(pThis, pNewDie);
+ }
+ } /* while more DIEs */
+
+
+ /* Unlink and free child DIEs if told to do so. */
+ if (!fKeepDies)
+ rtDwarfInfo_FreeChildren(pThis, &pUnit->Core);
+
+ return RT_SUCCESS(rc) ? pCursor->rc : rc;
+}
+
+
+/**
+ * Extracts the symbols.
+ *
+ * The symbols are insered into the debug info container.
+ *
+ * @returns IPRT status code
+ * @param pThis The DWARF instance.
+ */
+static int rtDwarfInfo_LoadAll(PRTDBGMODDWARF pThis)
+{
+ RTDWARFCURSOR Cursor;
+ int rc = rtDwarfCursor_Init(&Cursor, pThis, krtDbgModDwarfSect_info);
+ if (RT_SUCCESS(rc))
+ {
+ while ( !rtDwarfCursor_IsAtEnd(&Cursor)
+ && RT_SUCCESS(rc))
+ rc = rtDwarfInfo_LoadUnit(pThis, &Cursor, false /* fKeepDies */);
+
+ rc = rtDwarfCursor_Delete(&Cursor, rc);
+ }
+ return rc;
+}
+
+
+
+/*
+ *
+ * Public and image level symbol handling.
+ * Public and image level symbol handling.
+ * Public and image level symbol handling.
+ * Public and image level symbol handling.
+ *
+ *
+ */
+
+#define RTDBGDWARF_SYM_ENUM_BASE_ADDRESS UINT32_C(0x200000)
+
+/** @callback_method_impl{FNRTLDRENUMSYMS,
+ * Adds missing symbols from the image symbol table.} */
+static DECLCALLBACK(int) rtDwarfSyms_EnumSymbolsCallback(RTLDRMOD hLdrMod, const char *pszSymbol, unsigned uSymbol,
+ RTLDRADDR Value, void *pvUser)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pvUser;
+ RT_NOREF_PV(hLdrMod); RT_NOREF_PV(uSymbol);
+ Assert(pThis->iWatcomPass != 1);
+
+ RTLDRADDR uRva = Value - RTDBGDWARF_SYM_ENUM_BASE_ADDRESS;
+ if ( Value >= RTDBGDWARF_SYM_ENUM_BASE_ADDRESS
+ && uRva < _1G)
+ {
+ RTDBGSYMBOL SymInfo;
+ RTINTPTR offDisp;
+ int rc = RTDbgModSymbolByAddr(pThis->hCnt, RTDBGSEGIDX_RVA, uRva, RTDBGSYMADDR_FLAGS_LESS_OR_EQUAL, &offDisp, &SymInfo);
+ if ( RT_FAILURE(rc)
+ || offDisp != 0)
+ {
+ rc = RTDbgModSymbolAdd(pThis->hCnt, pszSymbol, RTDBGSEGIDX_RVA, uRva, 1, 0 /*fFlags*/, NULL /*piOrdinal*/);
+ Log(("Dwarf: Symbol #%05u %#018RTptr %s [%Rrc]\n", uSymbol, Value, pszSymbol, rc)); NOREF(rc);
+ }
+ }
+ else
+ Log(("Dwarf: Symbol #%05u %#018RTptr '%s' [SKIPPED - INVALID ADDRESS]\n", uSymbol, Value, pszSymbol));
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Loads additional symbols from the pubnames section and the executable image.
+ *
+ * The symbols are insered into the debug info container.
+ *
+ * @returns IPRT status code
+ * @param pThis The DWARF instance.
+ */
+static int rtDwarfSyms_LoadAll(PRTDBGMODDWARF pThis)
+{
+ /*
+ * pubnames.
+ */
+ int rc = VINF_SUCCESS;
+ if (pThis->aSections[krtDbgModDwarfSect_pubnames].fPresent)
+ {
+// RTDWARFCURSOR Cursor;
+// int rc = rtDwarfCursor_Init(&Cursor, pThis, krtDbgModDwarfSect_info);
+// if (RT_SUCCESS(rc))
+// {
+// while ( !rtDwarfCursor_IsAtEnd(&Cursor)
+// && RT_SUCCESS(rc))
+// rc = rtDwarfInfo_LoadUnit(pThis, &Cursor, false /* fKeepDies */);
+//
+// rc = rtDwarfCursor_Delete(&Cursor, rc);
+// }
+// return rc;
+ }
+
+ /*
+ * The executable image.
+ */
+ if ( pThis->pImgMod
+ && pThis->pImgMod->pImgVt->pfnEnumSymbols
+ && pThis->iWatcomPass != 1
+ && RT_SUCCESS(rc))
+ {
+ rc = pThis->pImgMod->pImgVt->pfnEnumSymbols(pThis->pImgMod,
+ RTLDR_ENUM_SYMBOL_FLAGS_ALL | RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD,
+ RTDBGDWARF_SYM_ENUM_BASE_ADDRESS,
+ rtDwarfSyms_EnumSymbolsCallback,
+ pThis);
+ }
+
+ return rc;
+}
+
+
+
+
+/*
+ *
+ * DWARF Debug module implementation.
+ * DWARF Debug module implementation.
+ * DWARF Debug module implementation.
+ *
+ */
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnUnwindFrame} */
+static DECLCALLBACK(int) rtDbgModDwarf_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+
+ /*
+ * Unwinding info is stored in the '.debug_frame' section, or altertively
+ * in the '.eh_frame' one in the image. In the latter case the dbgmodldr.cpp
+ * part of the operation will take care of it. Since the sections contain the
+ * same data, we just create a cursor and call a common function to do the job.
+ */
+ if (pThis->aSections[krtDbgModDwarfSect_frame].fPresent)
+ {
+ RTDWARFCURSOR Cursor;
+ int rc = rtDwarfCursor_Init(&Cursor, pThis, krtDbgModDwarfSect_frame);
+ if (RT_SUCCESS(rc))
+ {
+ /* Figure default pointer encoding from image arch. */
+ uint8_t bPtrEnc = rtDwarfUnwind_ArchToPtrEnc(pMod->pImgVt->pfnGetArch(pMod));
+
+ /* Make sure we've got both seg:off and rva for the input address. */
+ RTUINTPTR uRva = off;
+ if (iSeg == RTDBGSEGIDX_RVA)
+ rtDbgModDwarfRvaToSegOffset(pThis, uRva, &iSeg, &off);
+ else
+ rtDbgModDwarfSegOffsetToRva(pThis, iSeg, off, &uRva);
+
+ /* Do the work */
+ rc = rtDwarfUnwind_Slow(&Cursor, 0 /** @todo .debug_frame RVA*/, iSeg, off, uRva,
+ pState, bPtrEnc, false /*fIsEhFrame*/, pMod->pImgVt->pfnGetArch(pMod));
+
+ rc = rtDwarfCursor_Delete(&Cursor, rc);
+ }
+ return rc;
+ }
+ return VERR_DBG_NO_UNWIND_INFO;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByAddr} */
+static DECLCALLBACK(int) rtDbgModDwarf_LineByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ PRTINTPTR poffDisp, PRTDBGLINE pLineInfo)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ return RTDbgModLineByAddr(pThis->hCnt, iSeg, off, poffDisp, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByOrdinal} */
+static DECLCALLBACK(int) rtDbgModDwarf_LineByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGLINE pLineInfo)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ return RTDbgModLineByOrdinal(pThis->hCnt, iOrdinal, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineCount} */
+static DECLCALLBACK(uint32_t) rtDbgModDwarf_LineCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ return RTDbgModLineCount(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineAdd} */
+static DECLCALLBACK(int) rtDbgModDwarf_LineAdd(PRTDBGMODINT pMod, const char *pszFile, size_t cchFile, uint32_t uLineNo,
+ uint32_t iSeg, RTUINTPTR off, uint32_t *piOrdinal)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ Assert(!pszFile[cchFile]); NOREF(cchFile);
+ return RTDbgModLineAdd(pThis->hCnt, pszFile, uLineNo, iSeg, off, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByAddr} */
+static DECLCALLBACK(int) rtDbgModDwarf_SymbolByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ return RTDbgModSymbolByAddr(pThis->hCnt, iSeg, off, fFlags, poffDisp, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByName} */
+static DECLCALLBACK(int) rtDbgModDwarf_SymbolByName(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); RT_NOREF_PV(cchSymbol);
+ return RTDbgModSymbolByName(pThis->hCnt, pszSymbol/*, cchSymbol*/, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByOrdinal} */
+static DECLCALLBACK(int) rtDbgModDwarf_SymbolByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ return RTDbgModSymbolByOrdinal(pThis->hCnt, iOrdinal, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolCount} */
+static DECLCALLBACK(uint32_t) rtDbgModDwarf_SymbolCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ return RTDbgModSymbolCount(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolAdd} */
+static DECLCALLBACK(int) rtDbgModDwarf_SymbolAdd(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ RTDBGSEGIDX iSeg, RTUINTPTR off, RTUINTPTR cb, uint32_t fFlags,
+ uint32_t *piOrdinal)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); NOREF(cchSymbol);
+ return RTDbgModSymbolAdd(pThis->hCnt, pszSymbol, iSeg, off, cb, fFlags, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentByIndex} */
+static DECLCALLBACK(int) rtDbgModDwarf_SegmentByIndex(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, PRTDBGSEGMENT pSegInfo)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ return RTDbgModSegmentByIndex(pThis->hCnt, iSeg, pSegInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentCount} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModDwarf_SegmentCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ return RTDbgModSegmentCount(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentAdd} */
+static DECLCALLBACK(int) rtDbgModDwarf_SegmentAdd(PRTDBGMODINT pMod, RTUINTPTR uRva, RTUINTPTR cb, const char *pszName, size_t cchName,
+ uint32_t fFlags, PRTDBGSEGIDX piSeg)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ Assert(!pszName[cchName]); NOREF(cchName);
+ return RTDbgModSegmentAdd(pThis->hCnt, uRva, cb, pszName, fFlags, piSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnImageSize} */
+static DECLCALLBACK(RTUINTPTR) rtDbgModDwarf_ImageSize(PRTDBGMODINT pMod)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ RTUINTPTR cb1 = RTDbgModImageSize(pThis->hCnt);
+ RTUINTPTR cb2 = pThis->pImgMod->pImgVt->pfnImageSize(pMod);
+ return RT_MAX(cb1, cb2);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnRvaToSegOff} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModDwarf_RvaToSegOff(PRTDBGMODINT pMod, RTUINTPTR uRva, PRTUINTPTR poffSeg)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+ return RTDbgModRvaToSegOff(pThis->hCnt, uRva, poffSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnClose} */
+static DECLCALLBACK(int) rtDbgModDwarf_Close(PRTDBGMODINT pMod)
+{
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pMod->pvDbgPriv;
+
+ for (unsigned iSect = 0; iSect < RT_ELEMENTS(pThis->aSections); iSect++)
+ if (pThis->aSections[iSect].pv)
+ pThis->pDbgInfoMod->pImgVt->pfnUnmapPart(pThis->pDbgInfoMod, pThis->aSections[iSect].cb, &pThis->aSections[iSect].pv);
+
+ RTDbgModRelease(pThis->hCnt);
+ RTMemFree(pThis->paCachedAbbrevs);
+ if (pThis->pNestedMod)
+ {
+ pThis->pNestedMod->pImgVt->pfnClose(pThis->pNestedMod);
+ RTStrCacheRelease(g_hDbgModStrCache, pThis->pNestedMod->pszName);
+ RTStrCacheRelease(g_hDbgModStrCache, pThis->pNestedMod->pszDbgFile);
+ RTMemFree(pThis->pNestedMod);
+ pThis->pNestedMod = NULL;
+ }
+
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+ uint32_t i = RT_ELEMENTS(pThis->aDieAllocators);
+ while (i-- > 0)
+ {
+ RTMemCacheDestroy(pThis->aDieAllocators[i].hMemCache);
+ pThis->aDieAllocators[i].hMemCache = NIL_RTMEMCACHE;
+ }
+#endif
+
+ RTMemFree(pThis);
+
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTLDRENUMDBG} */
+static DECLCALLBACK(int) rtDbgModDwarfEnumCallback(RTLDRMOD hLdrMod, PCRTLDRDBGINFO pDbgInfo, void *pvUser)
+{
+ RT_NOREF_PV(hLdrMod);
+
+ /*
+ * Skip stuff we can't handle.
+ */
+ if (pDbgInfo->enmType != RTLDRDBGINFOTYPE_DWARF)
+ return VINF_SUCCESS;
+ const char *pszSection = pDbgInfo->u.Dwarf.pszSection;
+ if (!pszSection || !*pszSection)
+ return VINF_SUCCESS;
+ Assert(!pDbgInfo->pszExtFile);
+
+ /*
+ * Must have a part name starting with debug_ and possibly prefixed by dots
+ * or underscores.
+ */
+ if (!strncmp(pszSection, RT_STR_TUPLE(".debug_"))) /* ELF */
+ pszSection += sizeof(".debug_") - 1;
+ else if (!strncmp(pszSection, RT_STR_TUPLE("__debug_"))) /* Mach-O */
+ pszSection += sizeof("__debug_") - 1;
+ else if (!strcmp(pszSection, ".WATCOM_references"))
+ return VINF_SUCCESS; /* Ignore special watcom section for now.*/
+ else if ( !strcmp(pszSection, "__apple_types")
+ || !strcmp(pszSection, "__apple_namespac")
+ || !strcmp(pszSection, "__apple_objc")
+ || !strcmp(pszSection, "__apple_names"))
+ return VINF_SUCCESS; /* Ignore special apple sections for now. */
+ else
+ AssertMsgFailedReturn(("%s\n", pszSection), VINF_SUCCESS /*ignore*/);
+
+ /*
+ * Figure out which part we're talking about.
+ */
+ krtDbgModDwarfSect enmSect;
+ if (0) { /* dummy */ }
+#define ELSE_IF_STRCMP_SET(a_Name) else if (!strcmp(pszSection, #a_Name)) enmSect = krtDbgModDwarfSect_ ## a_Name
+ ELSE_IF_STRCMP_SET(abbrev);
+ ELSE_IF_STRCMP_SET(aranges);
+ ELSE_IF_STRCMP_SET(frame);
+ ELSE_IF_STRCMP_SET(info);
+ ELSE_IF_STRCMP_SET(inlined);
+ ELSE_IF_STRCMP_SET(line);
+ ELSE_IF_STRCMP_SET(loc);
+ ELSE_IF_STRCMP_SET(macinfo);
+ ELSE_IF_STRCMP_SET(pubnames);
+ ELSE_IF_STRCMP_SET(pubtypes);
+ ELSE_IF_STRCMP_SET(ranges);
+ ELSE_IF_STRCMP_SET(str);
+ ELSE_IF_STRCMP_SET(types);
+#undef ELSE_IF_STRCMP_SET
+ else
+ {
+ AssertMsgFailed(("%s\n", pszSection));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Record the section.
+ */
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)pvUser;
+ AssertMsgReturn(!pThis->aSections[enmSect].fPresent, ("duplicate %s\n", pszSection), VINF_SUCCESS /*ignore*/);
+
+ pThis->aSections[enmSect].fPresent = true;
+ pThis->aSections[enmSect].offFile = pDbgInfo->offFile;
+ pThis->aSections[enmSect].pv = NULL;
+ pThis->aSections[enmSect].cb = (size_t)pDbgInfo->cb;
+ pThis->aSections[enmSect].iDbgInfo = pDbgInfo->iDbgInfo;
+ if (pThis->aSections[enmSect].cb != pDbgInfo->cb)
+ pThis->aSections[enmSect].cb = ~(size_t)0;
+
+ return VINF_SUCCESS;
+}
+
+
+static int rtDbgModDwarfTryOpenDbgFile(PRTDBGMODINT pDbgMod, PRTDBGMODDWARF pThis, RTLDRARCH enmArch)
+{
+ if ( !pDbgMod->pszDbgFile
+ || RTPathIsSame(pDbgMod->pszDbgFile, pDbgMod->pszImgFile) == (int)true /* returns VERR too */)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+
+ /*
+ * Only open the image.
+ */
+ PRTDBGMODINT pDbgInfoMod = (PRTDBGMODINT)RTMemAllocZ(sizeof(*pDbgInfoMod));
+ if (!pDbgInfoMod)
+ return VERR_NO_MEMORY;
+
+ int rc;
+ pDbgInfoMod->u32Magic = RTDBGMOD_MAGIC;
+ pDbgInfoMod->cRefs = 1;
+ if (RTStrCacheRetain(pDbgMod->pszDbgFile) != UINT32_MAX)
+ {
+ pDbgInfoMod->pszImgFile = pDbgMod->pszDbgFile;
+ if (RTStrCacheRetain(pDbgMod->pszName) != UINT32_MAX)
+ {
+ pDbgInfoMod->pszName = pDbgMod->pszName;
+ pDbgInfoMod->pImgVt = &g_rtDbgModVtImgLdr;
+ rc = pDbgInfoMod->pImgVt->pfnTryOpen(pDbgInfoMod, enmArch, 0 /*fLdrFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pDbgInfoMod = pDbgInfoMod;
+ pThis->pNestedMod = pDbgInfoMod;
+ return VINF_SUCCESS;
+ }
+
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgInfoMod->pszName);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTStrCacheRelease(g_hDbgModStrCache, pDbgInfoMod->pszImgFile);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ RTMemFree(pDbgInfoMod);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnTryOpen} */
+static DECLCALLBACK(int) rtDbgModDwarf_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch)
+{
+ /*
+ * DWARF is only supported when part of an image.
+ */
+ if (!pMod->pImgVt)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+
+ /*
+ * Create the module instance data.
+ */
+ PRTDBGMODDWARF pThis = (PRTDBGMODDWARF)RTMemAllocZ(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+ pThis->pDbgInfoMod = pMod;
+ pThis->pImgMod = pMod;
+ RTListInit(&pThis->CompileUnitList);
+
+ /** @todo better fUseLinkAddress heuristics! */
+ /* mach_kernel: */
+ if ( (pMod->pszDbgFile && strstr(pMod->pszDbgFile, "mach_kernel"))
+ || (pMod->pszImgFile && strstr(pMod->pszImgFile, "mach_kernel"))
+ || (pMod->pszImgFileSpecified && strstr(pMod->pszImgFileSpecified, "mach_kernel")) )
+ pThis->fUseLinkAddress = true;
+
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+ AssertCompile(RT_ELEMENTS(pThis->aDieAllocators) == 2);
+ pThis->aDieAllocators[0].cbMax = sizeof(RTDWARFDIE);
+ pThis->aDieAllocators[1].cbMax = sizeof(RTDWARFDIECOMPILEUNIT);
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aTagDescs); i++)
+ if (g_aTagDescs[i].pDesc && g_aTagDescs[i].pDesc->cbDie > pThis->aDieAllocators[1].cbMax)
+ pThis->aDieAllocators[1].cbMax = (uint32_t)g_aTagDescs[i].pDesc->cbDie;
+ pThis->aDieAllocators[1].cbMax = RT_ALIGN_32(pThis->aDieAllocators[1].cbMax, sizeof(uint64_t));
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aDieAllocators); i++)
+ {
+ int rc = RTMemCacheCreate(&pThis->aDieAllocators[i].hMemCache, pThis->aDieAllocators[i].cbMax, sizeof(uint64_t),
+ UINT32_MAX, NULL /*pfnCtor*/, NULL /*pfnDtor*/, NULL /*pvUser*/, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ {
+ while (i-- > 0)
+ RTMemCacheDestroy(pThis->aDieAllocators[i].hMemCache);
+ RTMemFree(pThis);
+ return rc;
+ }
+ }
+#endif
+
+ /*
+ * If the debug file name is set, let's see if it's an ELF image with DWARF
+ * inside it. In that case we'll have to deal with two image modules, one
+ * for segments and address translation and one for the debug information.
+ */
+ if (pMod->pszDbgFile != NULL)
+ rtDbgModDwarfTryOpenDbgFile(pMod, pThis, enmArch);
+
+ /*
+ * Enumerate the debug info in the module, looking for DWARF bits.
+ */
+ int rc = pThis->pDbgInfoMod->pImgVt->pfnEnumDbgInfo(pThis->pDbgInfoMod, rtDbgModDwarfEnumCallback, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->aSections[krtDbgModDwarfSect_info].fPresent)
+ {
+ /*
+ * Extract / explode the data we want (symbols and line numbers)
+ * storing them in a container module.
+ */
+ rc = RTDbgModCreate(&pThis->hCnt, pMod->pszName, 0 /*cbSeg*/, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ pMod->pvDbgPriv = pThis;
+
+ rc = rtDbgModDwarfAddSegmentsFromImage(pThis);
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfInfo_LoadAll(pThis);
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfSyms_LoadAll(pThis);
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfLine_ExplodeAll(pThis);
+ if (RT_SUCCESS(rc) && pThis->iWatcomPass == 1)
+ {
+ rc = rtDbgModDwarfAddSegmentsFromPass1(pThis);
+ pThis->iWatcomPass = 2;
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfInfo_LoadAll(pThis);
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfSyms_LoadAll(pThis);
+ if (RT_SUCCESS(rc))
+ rc = rtDwarfLine_ExplodeAll(pThis);
+ }
+
+ /*
+ * Free the cached abbreviations and unload all sections.
+ */
+ pThis->cCachedAbbrevsAlloced = 0;
+ RTMemFree(pThis->paCachedAbbrevs);
+ pThis->paCachedAbbrevs = NULL;
+
+ for (unsigned iSect = 0; iSect < RT_ELEMENTS(pThis->aSections); iSect++)
+ if (pThis->aSections[iSect].pv)
+ pThis->pDbgInfoMod->pImgVt->pfnUnmapPart(pThis->pDbgInfoMod, pThis->aSections[iSect].cb,
+ &pThis->aSections[iSect].pv);
+
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Kill pThis->CompileUnitList and the alloc caches. */
+ return VINF_SUCCESS;
+ }
+
+ /* bail out. */
+ RTDbgModRelease(pThis->hCnt);
+ pMod->pvDbgPriv = NULL;
+ }
+ }
+ else
+ rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ }
+
+ if (pThis->paCachedAbbrevs)
+ RTMemFree(pThis->paCachedAbbrevs);
+ pThis->paCachedAbbrevs = NULL;
+
+ for (unsigned iSect = 0; iSect < RT_ELEMENTS(pThis->aSections); iSect++)
+ if (pThis->aSections[iSect].pv)
+ pThis->pDbgInfoMod->pImgVt->pfnUnmapPart(pThis->pDbgInfoMod, pThis->aSections[iSect].cb,
+ &pThis->aSections[iSect].pv);
+
+#ifdef RTDBGMODDWARF_WITH_MEM_CACHE
+ uint32_t i = RT_ELEMENTS(pThis->aDieAllocators);
+ while (i-- > 0)
+ {
+ RTMemCacheDestroy(pThis->aDieAllocators[i].hMemCache);
+ pThis->aDieAllocators[i].hMemCache = NIL_RTMEMCACHE;
+ }
+#endif
+
+ RTMemFree(pThis);
+
+ return rc;
+}
+
+
+
+/** Virtual function table for the DWARF debug info reader. */
+DECL_HIDDEN_CONST(RTDBGMODVTDBG) const g_rtDbgModVtDbgDwarf =
+{
+ /*.u32Magic = */ RTDBGMODVTDBG_MAGIC,
+ /*.fSupports = */ RT_DBGTYPE_DWARF,
+ /*.pszName = */ "dwarf",
+ /*.pfnTryOpen = */ rtDbgModDwarf_TryOpen,
+ /*.pfnClose = */ rtDbgModDwarf_Close,
+
+ /*.pfnRvaToSegOff = */ rtDbgModDwarf_RvaToSegOff,
+ /*.pfnImageSize = */ rtDbgModDwarf_ImageSize,
+
+ /*.pfnSegmentAdd = */ rtDbgModDwarf_SegmentAdd,
+ /*.pfnSegmentCount = */ rtDbgModDwarf_SegmentCount,
+ /*.pfnSegmentByIndex = */ rtDbgModDwarf_SegmentByIndex,
+
+ /*.pfnSymbolAdd = */ rtDbgModDwarf_SymbolAdd,
+ /*.pfnSymbolCount = */ rtDbgModDwarf_SymbolCount,
+ /*.pfnSymbolByOrdinal = */ rtDbgModDwarf_SymbolByOrdinal,
+ /*.pfnSymbolByName = */ rtDbgModDwarf_SymbolByName,
+ /*.pfnSymbolByAddr = */ rtDbgModDwarf_SymbolByAddr,
+
+ /*.pfnLineAdd = */ rtDbgModDwarf_LineAdd,
+ /*.pfnLineCount = */ rtDbgModDwarf_LineCount,
+ /*.pfnLineByOrdinal = */ rtDbgModDwarf_LineByOrdinal,
+ /*.pfnLineByAddr = */ rtDbgModDwarf_LineByAddr,
+
+ /*.pfnUnwindFrame = */ rtDbgModDwarf_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTDBG_MAGIC
+};
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmodexports.cpp b/src/VBox/Runtime/common/dbg/dbgmodexports.cpp
new file mode 100644
index 00000000..716c43e1
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmodexports.cpp
@@ -0,0 +1,183 @@
+/* $Id: dbgmodexports.cpp $ */
+/** @file
+ * IPRT - Debug Module Using Image Exports.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DBG
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/string.h>
+#include "internal/dbgmod.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTDBGMODEXPORTARGS
+{
+ PRTDBGMODINT pDbgMod;
+ RTLDRADDR uImageBase;
+ RTLDRADDR uRvaNext;
+ uint32_t cSegs;
+} RTDBGMODEXPORTARGS;
+/** Pointer to an argument package. */
+typedef RTDBGMODEXPORTARGS *PRTDBGMODEXPORTARGS;
+
+
+/**
+ * @callback_method_impl{FNRTLDRENUMSYMS,
+ * Copies the symbols over into the container} */
+static DECLCALLBACK(int) rtDbgModExportsAddSymbolCallback(RTLDRMOD hLdrMod, const char *pszSymbol, unsigned uSymbol,
+ RTLDRADDR Value, void *pvUser)
+{
+ PRTDBGMODEXPORTARGS pArgs = (PRTDBGMODEXPORTARGS)pvUser;
+ NOREF(hLdrMod);
+
+ if (Value >= pArgs->uImageBase)
+ {
+ char szOrdinalNm[48];
+ if (!pszSymbol || *pszSymbol == '\0')
+ {
+ RTStrPrintf(szOrdinalNm, sizeof(szOrdinalNm), "Ordinal%u", uSymbol);
+ pszSymbol = szOrdinalNm;
+ }
+
+ int rc = RTDbgModSymbolAdd(pArgs->pDbgMod, pszSymbol, RTDBGSEGIDX_RVA, Value - pArgs->uImageBase,
+ 0 /*cb*/, 0 /* fFlags */, NULL /*piOrdinal*/);
+ Log(("Symbol #%05u %#018RTptr %s [%Rrc]\n", uSymbol, Value, pszSymbol, rc)); NOREF(rc);
+ }
+ else
+ Log(("Symbol #%05u %#018RTptr %s [SKIPPED - INVALID ADDRESS]\n", uSymbol, Value, pszSymbol));
+ return VINF_SUCCESS;
+}
+
+
+/** @callback_method_impl{FNRTLDRENUMSEGS,
+ * Copies the segments over into the container} */
+static DECLCALLBACK(int) rtDbgModExportsAddSegmentsCallback(RTLDRMOD hLdrMod, PCRTLDRSEG pSeg, void *pvUser)
+{
+ PRTDBGMODEXPORTARGS pArgs = (PRTDBGMODEXPORTARGS)pvUser;
+ Log(("Segment %.*s: LinkAddress=%#llx RVA=%#llx cb=%#llx\n",
+ pSeg->cchName, pSeg->pszName, (uint64_t)pSeg->LinkAddress, (uint64_t)pSeg->RVA, pSeg->cb));
+ NOREF(hLdrMod);
+
+ pArgs->cSegs++;
+
+ /* Add dummy segments for segments that doesn't get mapped. */
+ if ( pSeg->LinkAddress == NIL_RTLDRADDR
+ || pSeg->RVA == NIL_RTLDRADDR)
+ return RTDbgModSegmentAdd(pArgs->pDbgMod, 0, 0, pSeg->pszName, 0 /*fFlags*/, NULL);
+
+ RTLDRADDR uRva = pSeg->RVA;
+#if 0 /* Kluge for the .data..percpu segment in 64-bit linux kernels and similar. (Moved to ELF.) */
+ if (uRva < pArgs->uRvaNext)
+ uRva = RT_ALIGN_T(pArgs->uRvaNext, pSeg->Alignment, RTLDRADDR);
+#endif
+
+ /* Find the best base address for the module. */
+ if ( ( !pArgs->uImageBase
+ || pArgs->uImageBase > pSeg->LinkAddress)
+ && ( pSeg->LinkAddress != 0 /* .data..percpu again. */
+ || pArgs->cSegs == 1))
+ pArgs->uImageBase = pSeg->LinkAddress;
+
+ /* Add it. */
+ RTLDRADDR cb = RT_MAX(pSeg->cb, pSeg->cbMapped);
+ pArgs->uRvaNext = uRva + cb;
+ return RTDbgModSegmentAdd(pArgs->pDbgMod, uRva, cb, pSeg->pszName, 0 /*fFlags*/, NULL);
+}
+
+
+/**
+ * Creates the debug info side of affairs based on exports and segments found in
+ * the image part.
+ *
+ * The image part must be successfully initialized prior to the call, while the
+ * debug bits must not be present of course.
+ *
+ * @returns IPRT status code
+ * @param pDbgMod The debug module structure.
+ */
+DECLHIDDEN(int) rtDbgModCreateForExports(PRTDBGMODINT pDbgMod)
+{
+ AssertReturn(!pDbgMod->pDbgVt, VERR_DBG_MOD_IPE);
+ AssertReturn(pDbgMod->pImgVt, VERR_DBG_MOD_IPE);
+ RTUINTPTR cbImage = pDbgMod->pImgVt->pfnImageSize(pDbgMod);
+ AssertReturn(cbImage > 0, VERR_DBG_MOD_IPE);
+
+ /*
+ * We simply use a container type for this work.
+ */
+ int rc = rtDbgModContainerCreate(pDbgMod, 0);
+ if (RT_FAILURE(rc))
+ return rc;
+ pDbgMod->fExports = true;
+
+ /*
+ * Copy the segments and symbols.
+ */
+ RTDBGMODEXPORTARGS Args;
+ Args.pDbgMod = pDbgMod;
+ Args.uImageBase = 0;
+ Args.uRvaNext = 0;
+ Args.cSegs = 0;
+ rc = pDbgMod->pImgVt->pfnEnumSegments(pDbgMod, rtDbgModExportsAddSegmentsCallback, &Args);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pDbgMod->pImgVt->pfnEnumSymbols(pDbgMod, RTLDR_ENUM_SYMBOL_FLAGS_ALL | RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD,
+ Args.uImageBase ? Args.uImageBase : 0x10000,
+ rtDbgModExportsAddSymbolCallback, &Args);
+ if (RT_FAILURE(rc))
+ Log(("rtDbgModCreateForExports: Error during symbol enum: %Rrc\n", rc));
+ }
+ else
+ Log(("rtDbgModCreateForExports: Error during segment enum: %Rrc\n", rc));
+
+ /* This won't fail. */
+ if (RT_SUCCESS(rc))
+ rc = VINF_SUCCESS;
+ else
+ rc = -rc; /* Change it into a warning. */
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmodghidra.cpp b/src/VBox/Runtime/common/dbg/dbgmodghidra.cpp
new file mode 100644
index 00000000..d69f4640
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmodghidra.cpp
@@ -0,0 +1,526 @@
+/* $Id: dbgmodghidra.cpp $ */
+/** @file
+ * IPRT - Debug Info Reader for Ghidra XML files created with createPdbXmlFiles.bat/pdb.exe.
+ */
+
+/*
+ * Copyright (C) 2021-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/mem.h>
+#include <iprt/sort.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/cpp/xml.h>
+#include "internal/dbgmod.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Temporary segment data.
+ */
+typedef struct RTDBGMODGHIDRASEG
+{
+ const char *pszNumber;
+ RTUINTPTR uRva;
+} RTDBGMODGHIDRASEG;
+typedef RTDBGMODGHIDRASEG *PRTDBGMODGHIDRASEG;
+typedef const RTDBGMODGHIDRASEG *PCRTDBGMODGHIDRASEG;
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnUnwindFrame} */
+static DECLCALLBACK(int) rtDbgModGhidra_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ RT_NOREF(pMod, iSeg, off, pState);
+ return VERR_DBG_NO_UNWIND_INFO;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByAddr} */
+static DECLCALLBACK(int) rtDbgModGhidra_LineByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ PRTINTPTR poffDisp, PRTDBGLINE pLineInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModLineByAddr(hCnt, iSeg, off, poffDisp, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByOrdinal} */
+static DECLCALLBACK(int) rtDbgModGhidra_LineByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGLINE pLineInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModLineByOrdinal(hCnt, iOrdinal, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineCount} */
+static DECLCALLBACK(uint32_t) rtDbgModGhidra_LineCount(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModLineCount(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineAdd} */
+static DECLCALLBACK(int) rtDbgModGhidra_LineAdd(PRTDBGMODINT pMod, const char *pszFile, size_t cchFile, uint32_t uLineNo,
+ uint32_t iSeg, RTUINTPTR off, uint32_t *piOrdinal)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszFile[cchFile]); NOREF(cchFile);
+ return RTDbgModLineAdd(hCnt, pszFile, uLineNo, iSeg, off, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByAddr} */
+static DECLCALLBACK(int) rtDbgModGhidra_SymbolByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSymbolByAddr(hCnt, iSeg, off, fFlags, poffDisp, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByName} */
+static DECLCALLBACK(int) rtDbgModGhidra_SymbolByName(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ PRTDBGSYMBOL pSymInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); NOREF(cchSymbol);
+ return RTDbgModSymbolByName(hCnt, pszSymbol, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByOrdinal} */
+static DECLCALLBACK(int) rtDbgModGhidra_SymbolByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGSYMBOL pSymInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSymbolByOrdinal(hCnt, iOrdinal, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolCount} */
+static DECLCALLBACK(uint32_t) rtDbgModGhidra_SymbolCount(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSymbolCount(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolAdd} */
+static DECLCALLBACK(int) rtDbgModGhidra_SymbolAdd(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ RTDBGSEGIDX iSeg, RTUINTPTR off, RTUINTPTR cb, uint32_t fFlags,
+ uint32_t *piOrdinal)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); NOREF(cchSymbol);
+ return RTDbgModSymbolAdd(hCnt, pszSymbol, iSeg, off, cb, fFlags, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentByIndex} */
+static DECLCALLBACK(int) rtDbgModGhidra_SegmentByIndex(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, PRTDBGSEGMENT pSegInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSegmentByIndex(hCnt, iSeg, pSegInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentCount} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModGhidra_SegmentCount(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSegmentCount(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentAdd} */
+static DECLCALLBACK(int) rtDbgModGhidra_SegmentAdd(PRTDBGMODINT pMod, RTUINTPTR uRva, RTUINTPTR cb, const char *pszName,
+ size_t cchName, uint32_t fFlags, PRTDBGSEGIDX piSeg)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszName[cchName]); NOREF(cchName);
+ return RTDbgModSegmentAdd(hCnt, uRva, cb, pszName, fFlags, piSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnImageSize} */
+static DECLCALLBACK(RTUINTPTR) rtDbgModGhidra_ImageSize(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModImageSize(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnRvaToSegOff} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModGhidra_RvaToSegOff(PRTDBGMODINT pMod, RTUINTPTR uRva, PRTUINTPTR poffSeg)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModRvaToSegOff(hCnt, uRva, poffSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnClose} */
+static DECLCALLBACK(int) rtDbgModGhidra_Close(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ RTDbgModRelease(hCnt);
+ pMod->pvDbgPriv = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Returns the table with the given name from the given table list.
+ *
+ * @returns Pointer to the XML element node containing the given table or NULL if not found.
+ * @param pelmTables Pointer to the node containing the tables.
+ * @param pszName The table name to look for.
+ */
+static const xml::ElementNode *rtDbgModGhidraGetTableByName(const xml::ElementNode *pelmTables, const char *pszName)
+{
+ xml::NodesLoop nl1(*pelmTables, "table");
+ const xml::ElementNode *pelmTbl;
+ while ((pelmTbl = nl1.forAllNodes()))
+ {
+ const char *pszTblName = NULL;
+
+ if ( pelmTbl->getAttributeValue("name", &pszTblName)
+ && !strcmp(pszTblName, pszName))
+ return pelmTbl;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Adds the symbols from the given \"Symbols\" table.
+ *
+ * @returns IPRT status code.
+ * @param hCnt Debug module container handle.
+ * @param elmTbl Reference to the XML node containing the symbols.
+ */
+static int rtDbgModGhidraXmlParseSymbols(RTDBGMOD hCnt, const xml::ElementNode &elmTbl)
+{
+ xml::NodesLoop nlSym(elmTbl, "symbol");
+ const xml::ElementNode *pelmSym;
+ while ((pelmSym = nlSym.forAllNodes()))
+ {
+ /* Only parse Function and PublicSymbol tags. */
+ const char *pszTag = NULL;
+ if ( pelmSym->getAttributeValue("tag", &pszTag)
+ && ( !strcmp(pszTag, "PublicSymbol")
+ || !strcmp(pszTag, "Function")))
+ {
+ const char *pszSymName = NULL;
+ if ( !pelmSym->getAttributeValue("undecorated", &pszSymName)
+ || *pszSymName == '\0')
+ pelmSym->getAttributeValue("name", &pszSymName);
+
+ if ( pszSymName
+ && strlen(pszSymName) < RTDBG_SYMBOL_NAME_LENGTH)
+ {
+ uint64_t u64Addr = 0;
+ uint64_t u64Length = 0;
+ if ( pelmSym->getAttributeValue("address", &u64Addr)
+ && pelmSym->getAttributeValue("length", &u64Length))
+ {
+ int rc = RTDbgModSymbolAdd(hCnt, pszSymName, RTDBGSEGIDX_RVA, u64Addr, u64Length, 0 /*fFlags*/, NULL);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_DBG_DUPLICATE_SYMBOL
+ && rc != VERR_DBG_ADDRESS_CONFLICT
+ && rc != VERR_DBG_INVALID_RVA) /* (don't be too strict) */
+ return rc;
+ }
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds the symbols from the given \"functions\" table.
+ *
+ * @returns IPRT status code.
+ * @param hCnt Debug module container handle.
+ * @param elmTbl Reference to the XML node containing the symbols.
+ */
+static int rtDbgModGhidraXmlParseFunctions(RTDBGMOD hCnt, const xml::ElementNode &elmTbl)
+{
+ xml::NodesLoop nlFun(elmTbl, "function");
+ const xml::ElementNode *pelmFun;
+ while ((pelmFun = nlFun.forAllNodes()))
+ {
+ xml::NodesLoop nlLn(*pelmFun, "line_number");
+ const xml::ElementNode *pelmLn;
+ while ((pelmLn = nlLn.forAllNodes()))
+ {
+ const char *pszFile = NULL;
+ uint32_t uLineNo = 0;
+ uint64_t off = 0;
+ if ( pelmLn->getAttributeValue("source_file", &pszFile)
+ && pelmLn->getAttributeValue("start", &uLineNo)
+ && pelmLn->getAttributeValue("addr", &off))
+ {
+ int rc = RTDbgModLineAdd(hCnt, pszFile, uLineNo, RTDBGSEGIDX_RVA, off, NULL /*piOrdinal*/);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_DBG_DUPLICATE_SYMBOL
+ && rc != VERR_DBG_ADDRESS_CONFLICT
+ && rc != VERR_DBG_INVALID_RVA) /* (don't be too strict) */
+ return rc;
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc FNRTSORTCMP
+ */
+static DECLCALLBACK(int) rtDbgModGhidraSegmentsSortCmp(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PCRTDBGMODGHIDRASEG pSeg1 = (PCRTDBGMODGHIDRASEG)pvElement1;
+ PCRTDBGMODGHIDRASEG pSeg2 = (PCRTDBGMODGHIDRASEG)pvElement2;
+
+ if (pSeg1->uRva > pSeg2->uRva)
+ return 1;
+ if (pSeg1->uRva < pSeg2->uRva)
+ return -1;
+
+ return 0;
+}
+
+
+/**
+ * Adds the segments in the given \"SegmentMap\" table.
+ *
+ * @returns IPRT status code.
+ * @param hCnt Debug module container handle.
+ * @param elmTblSeg Reference to the XML node containing the segments.
+ */
+static int rtDbgModGhidraSegmentsAdd(RTDBGMOD hCnt, const xml::ElementNode &elmTblSeg)
+{
+ RTDBGMODGHIDRASEG aSegments[32];
+ uint32_t idxSeg = 0;
+
+ xml::NodesLoop nl1(elmTblSeg, "segment");
+ const xml::ElementNode *pelmSeg;
+ while ( (pelmSeg = nl1.forAllNodes())
+ && idxSeg < RT_ELEMENTS(aSegments))
+ {
+ const char *pszNumber = NULL;
+ RTUINTPTR uRva = 0;
+
+ if ( pelmSeg->getAttributeValue("number", &pszNumber)
+ && pelmSeg->getAttributeValue("address", &uRva))
+ {
+ aSegments[idxSeg].pszNumber = pszNumber;
+ aSegments[idxSeg].uRva = uRva;
+ idxSeg++;
+ }
+ }
+
+ /* Sort the segments by RVA so it is possible to deduce segment sizes. */
+ RTSortShell(&aSegments[0], idxSeg, sizeof(aSegments[0]), rtDbgModGhidraSegmentsSortCmp, NULL);
+
+ for (uint32_t i = 0; i < idxSeg - 1; i++)
+ {
+ int rc = RTDbgModSegmentAdd(hCnt, aSegments[i].uRva, aSegments[i + 1].uRva - aSegments[i].uRva,
+ aSegments[i].pszNumber, 0 /*fFlags*/, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /* Last segment for which we assume a size of 0 right now. */
+ int rc = RTDbgModSegmentAdd(hCnt, aSegments[idxSeg - 1].uRva, 0,
+ aSegments[idxSeg - 1].pszNumber, 0 /*fFlags*/, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ return rc;
+}
+
+
+/**
+ * Load the symbols from an XML document.
+ *
+ * @returns IPRT status code.
+ * @param hCnt Debug module container handle.
+ * @param a_pDoc Pointer to the XML document.
+ */
+static int rtDbgModGhidraXmlParse(RTDBGMOD hCnt, xml::Document *a_pDoc)
+{
+ /*
+ * Get the root element and check whether it looks like a valid Ghidra XML.
+ */
+ const xml::ElementNode *pelmRoot = a_pDoc->getRootElement();
+ if ( !pelmRoot
+ || strcmp(pelmRoot->getName(), "pdb") != 0)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+
+ const xml::ElementNode *pelmTables = pelmRoot->findChildElement("tables");
+ if (!pelmTables)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+
+ const xml::ElementNode *pelmTbl = rtDbgModGhidraGetTableByName(pelmTables, "SegmentMap");
+ if (pelmTbl)
+ {
+ int rc = rtDbgModGhidraSegmentsAdd(hCnt, *pelmTbl);
+ if (RT_SUCCESS(rc))
+ {
+ pelmTbl = rtDbgModGhidraGetTableByName(pelmTables, "Symbols");
+ if (pelmTbl)
+ {
+ rc = rtDbgModGhidraXmlParseSymbols(hCnt, *pelmTbl);
+ if (RT_SUCCESS(rc))
+ {
+ pelmTbl = pelmRoot->findChildElement("functions"); /* Might not be there. */
+ if (pelmTbl)
+ rc = rtDbgModGhidraXmlParseFunctions(hCnt, *pelmTbl);
+ return rc;
+ }
+ }
+ }
+ }
+
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnTryOpen} */
+static DECLCALLBACK(int) rtDbgModGhidra_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch)
+{
+ RT_NOREF(enmArch);
+
+ /*
+ * Fend off images.
+ */
+ if (!pMod->pszDbgFile)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+ pMod->pvDbgPriv = NULL;
+
+ /*
+ * Try open the file and create an instance.
+ */
+ xml::Document Doc;
+ {
+ xml::XmlFileParser Parser;
+ try
+ {
+ Parser.read(pMod->pszDbgFile, Doc);
+ }
+ catch (xml::XmlError &rErr)
+ {
+ RT_NOREF(rErr);
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+ }
+ catch (xml::EIPRTFailure &rErr)
+ {
+ return rErr.rc();
+ }
+ }
+
+ RTDBGMOD hCnt;
+ int rc = RTDbgModCreate(&hCnt, pMod->pszName, 0 /*cbSeg*/, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Hand the xml doc over to the common code.
+ */
+ try
+ {
+ rc = rtDbgModGhidraXmlParse(hCnt, &Doc);
+ if (RT_SUCCESS(rc))
+ {
+ pMod->pvDbgPriv = hCnt;
+ return VINF_SUCCESS;
+ }
+ }
+ catch (RTCError &rXcpt) // includes all XML exceptions
+ {
+ RT_NOREF(rXcpt);
+ rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ }
+ RTDbgModRelease(hCnt);
+ }
+
+ return rc;
+}
+
+
+
+/** Virtual function table for the Ghidra XML file reader. */
+DECL_HIDDEN_CONST(RTDBGMODVTDBG) const g_rtDbgModVtDbgGhidra =
+{
+ /*.u32Magic = */ RTDBGMODVTDBG_MAGIC,
+ /*.fSupports = */ RT_DBGTYPE_OTHER | RT_DBGTYPE_MAP,
+ /*.pszName = */ "ghidra",
+ /*.pfnTryOpen = */ rtDbgModGhidra_TryOpen,
+ /*.pfnClose = */ rtDbgModGhidra_Close,
+
+ /*.pfnRvaToSegOff = */ rtDbgModGhidra_RvaToSegOff,
+ /*.pfnImageSize = */ rtDbgModGhidra_ImageSize,
+
+ /*.pfnSegmentAdd = */ rtDbgModGhidra_SegmentAdd,
+ /*.pfnSegmentCount = */ rtDbgModGhidra_SegmentCount,
+ /*.pfnSegmentByIndex = */ rtDbgModGhidra_SegmentByIndex,
+
+ /*.pfnSymbolAdd = */ rtDbgModGhidra_SymbolAdd,
+ /*.pfnSymbolCount = */ rtDbgModGhidra_SymbolCount,
+ /*.pfnSymbolByOrdinal = */ rtDbgModGhidra_SymbolByOrdinal,
+ /*.pfnSymbolByName = */ rtDbgModGhidra_SymbolByName,
+ /*.pfnSymbolByAddr = */ rtDbgModGhidra_SymbolByAddr,
+
+ /*.pfnLineAdd = */ rtDbgModGhidra_LineAdd,
+ /*.pfnLineCount = */ rtDbgModGhidra_LineCount,
+ /*.pfnLineByOrdinal = */ rtDbgModGhidra_LineByOrdinal,
+ /*.pfnLineByAddr = */ rtDbgModGhidra_LineByAddr,
+
+ /*.pfnUnwindFrame = */ rtDbgModGhidra_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTDBG_MAGIC
+};
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmodldr.cpp b/src/VBox/Runtime/common/dbg/dbgmodldr.cpp
new file mode 100644
index 00000000..b81f6d89
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmodldr.cpp
@@ -0,0 +1,286 @@
+/* $Id: dbgmodldr.cpp $ */
+/** @file
+ * IPRT - Debug Module Image Interpretation by RTLdr.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include "internal/dbgmod.h"
+#include "internal/ldr.h"
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * The instance data of the RTLdr based image reader.
+ */
+typedef struct RTDBGMODLDR
+{
+ /** Magic value (RTDBGMODLDR_MAGIC). */
+ uint32_t u32Magic;
+ /** The loader handle. */
+ RTLDRMOD hLdrMod;
+} RTDBGMODLDR;
+/** Pointer to instance data NM map reader. */
+typedef RTDBGMODLDR *PRTDBGMODLDR;
+
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnUnwindFrame} */
+static DECLCALLBACK(int) rtDbgModLdr_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrUnwindFrame(pThis->hLdrMod, NULL, iSeg, off, pState);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnQueryProp} */
+static DECLCALLBACK(int) rtDbgModLdr_QueryProp(PRTDBGMODINT pMod, RTLDRPROP enmProp, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrQueryPropEx(pThis->hLdrMod, enmProp, NULL /*pvBits*/, pvBuf, cbBuf, pcbRet);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnGetArch} */
+static DECLCALLBACK(RTLDRARCH) rtDbgModLdr_GetArch(PRTDBGMODINT pMod)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrGetArch(pThis->hLdrMod);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnGetFormat} */
+static DECLCALLBACK(RTLDRFMT) rtDbgModLdr_GetFormat(PRTDBGMODINT pMod)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrGetFormat(pThis->hLdrMod);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnReadAt} */
+static DECLCALLBACK(int) rtDbgModLdr_ReadAt(PRTDBGMODINT pMod, uint32_t iDbgInfoHint, RTFOFF off, void *pvBuf, size_t cb)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ RT_NOREF_PV(iDbgInfoHint);
+ return rtLdrReadAt(pThis->hLdrMod, pvBuf, UINT32_MAX /** @todo iDbgInfo*/, off, cb);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnUnmapPart} */
+static DECLCALLBACK(int) rtDbgModLdr_UnmapPart(PRTDBGMODINT pMod, size_t cb, void const **ppvMap)
+{
+ Assert(((PRTDBGMODLDR)pMod->pvImgPriv)->u32Magic == RTDBGMODLDR_MAGIC);
+ NOREF(pMod); NOREF(cb);
+ RTMemFree((void *)*ppvMap);
+ *ppvMap = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnMapPart} */
+static DECLCALLBACK(int) rtDbgModLdr_MapPart(PRTDBGMODINT pMod, uint32_t iDbgInfo, RTFOFF off, size_t cb, void const **ppvMap)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+
+ void *pvMap = RTMemAlloc(cb);
+ if (!pvMap)
+ return VERR_NO_MEMORY;
+
+ int rc = rtLdrReadAt(pThis->hLdrMod, pvMap, iDbgInfo, off, cb);
+ if (RT_SUCCESS(rc))
+ *ppvMap = pvMap;
+ else
+ {
+ RTMemFree(pvMap);
+ *ppvMap = NULL;
+ }
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnImageSize} */
+static DECLCALLBACK(RTUINTPTR) rtDbgModLdr_ImageSize(PRTDBGMODINT pMod)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrSize(pThis->hLdrMod);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnRvaToSegOffset} */
+static DECLCALLBACK(int) rtDbgModLdr_RvaToSegOffset(PRTDBGMODINT pMod, RTLDRADDR Rva, PRTDBGSEGIDX piSeg, PRTLDRADDR poffSeg)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrRvaToSegOffset(pThis->hLdrMod, Rva, piSeg, poffSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnLinkAddressToSegOffset} */
+static DECLCALLBACK(int) rtDbgModLdr_LinkAddressToSegOffset(PRTDBGMODINT pMod, RTLDRADDR LinkAddress,
+ PRTDBGSEGIDX piSeg, PRTLDRADDR poffSeg)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrLinkAddressToSegOffset(pThis->hLdrMod, LinkAddress, piSeg, poffSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnEnumSymbols} */
+static DECLCALLBACK(int) rtDbgModLdr_EnumSymbols(PRTDBGMODINT pMod, uint32_t fFlags, RTLDRADDR BaseAddress,
+ PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrEnumSymbols(pThis->hLdrMod, fFlags, NULL /*pvBits*/, BaseAddress, pfnCallback, pvUser);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnEnumSegments} */
+static DECLCALLBACK(int) rtDbgModLdr_EnumSegments(PRTDBGMODINT pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrEnumSegments(pThis->hLdrMod, pfnCallback, pvUser);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnEnumDbgInfo} */
+static DECLCALLBACK(int) rtDbgModLdr_EnumDbgInfo(PRTDBGMODINT pMod, PFNRTLDRENUMDBG pfnCallback, void *pvUser)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+ return RTLdrEnumDbgInfo(pThis->hLdrMod, NULL, pfnCallback, pvUser);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnClose} */
+static DECLCALLBACK(int) rtDbgModLdr_Close(PRTDBGMODINT pMod)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)pMod->pvImgPriv;
+ AssertPtr(pThis);
+ Assert(pThis->u32Magic == RTDBGMODLDR_MAGIC);
+
+ int rc = RTLdrClose(pThis->hLdrMod); AssertRC(rc);
+ pThis->hLdrMod = NIL_RTLDRMOD;
+ pThis->u32Magic = RTDBGMODLDR_MAGIC_DEAD;
+
+ RTMemFree(pThis);
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTIMG,pfnTryOpen} */
+static DECLCALLBACK(int) rtDbgModLdr_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch, uint32_t fLdrFlags)
+{
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrOpen(pMod->pszImgFile, RTLDR_O_FOR_DEBUG | fLdrFlags, enmArch, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtDbgModLdrOpenFromHandle(pMod, hLdrMod);
+ if (RT_FAILURE(rc))
+ RTLdrClose(hLdrMod);
+ }
+ return rc;
+}
+
+
+/** Virtual function table for the RTLdr based image reader. */
+DECL_HIDDEN_CONST(RTDBGMODVTIMG) const g_rtDbgModVtImgLdr =
+{
+ /*.u32Magic = */ RTDBGMODVTIMG_MAGIC,
+ /*.fReserved = */ 0,
+ /*.pszName = */ "RTLdr",
+ /*.pfnTryOpen = */ rtDbgModLdr_TryOpen,
+ /*.pfnClose = */ rtDbgModLdr_Close,
+ /*.pfnEnumDbgInfo = */ rtDbgModLdr_EnumDbgInfo,
+ /*.pfnEnumSegments = */ rtDbgModLdr_EnumSegments,
+ /*.pfnEnumSymbols = */ rtDbgModLdr_EnumSymbols,
+ /*.pfnImageSize = */ rtDbgModLdr_ImageSize,
+ /*.pfnLinkAddressToSegOffset = */ rtDbgModLdr_LinkAddressToSegOffset,
+ /*.pfnRvaToSegOffset= */ rtDbgModLdr_RvaToSegOffset,
+ /*.pfnMapPart = */ rtDbgModLdr_MapPart,
+ /*.pfnUnmapPart = */ rtDbgModLdr_UnmapPart,
+ /*.pfnReadAt = */ rtDbgModLdr_ReadAt,
+ /*.pfnGetFormat = */ rtDbgModLdr_GetFormat,
+ /*.pfnGetArch = */ rtDbgModLdr_GetArch,
+ /*.pfnQueryProp = */ rtDbgModLdr_QueryProp,
+ /*.pfnUnwindFrame = */ rtDbgModLdr_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTIMG_MAGIC
+};
+
+
+/**
+ * Open PE-image trick.
+ *
+ * @returns IPRT status code
+ * @param pDbgMod The debug module instance.
+ * @param hLdrMod The module to open a image debug backend for.
+ */
+DECLHIDDEN(int) rtDbgModLdrOpenFromHandle(PRTDBGMODINT pDbgMod, RTLDRMOD hLdrMod)
+{
+ PRTDBGMODLDR pThis = (PRTDBGMODLDR)RTMemAllocZ(sizeof(RTDBGMODLDR));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ pThis->u32Magic = RTDBGMODLDR_MAGIC;
+ pThis->hLdrMod = hLdrMod;
+ pDbgMod->pvImgPriv = pThis;
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmodmapsym.cpp b/src/VBox/Runtime/common/dbg/dbgmodmapsym.cpp
new file mode 100644
index 00000000..8cbc0447
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmodmapsym.cpp
@@ -0,0 +1,622 @@
+/* $Id: dbgmodmapsym.cpp $ */
+/** @file
+ * IPRT - Debug Map Reader for MAPSYM files (used by SYMDBG from old MASM).
+ *
+ * MAPSYM is was the tool producing these files from linker map files for
+ * use with SYMDBG (which shipped with MASM 3.0 (possibly earlier)), the OS/2
+ * kernel debugger, and other tools. The format is very limited and they had
+ * to strip down the os2krnl.map file in later years to keep MAPSYM happy.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DBG
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include "internal/dbgmod.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** @name MAPSYM structures and constants.
+ * @{ */
+
+/** MAPSYM: Header structure. */
+typedef struct MAPSYMHDR
+{
+ uint16_t off16NextMap; /**< 0x00: Offset of the next map divided by 16. */
+ uint8_t bFlags; /**< 0x02: Who really knows... */
+ uint8_t bReserved; /**< 0x03: Reserved / unknown. */
+ uint16_t uSegEntry; /**< 0x04: Some entrypoint/segment thing we don't care about. */
+ uint16_t cConsts; /**< 0x06: Constants referenced by offConstDef. */
+ uint16_t offConstDef; /**< 0x08: Offset to head of constant chain. Not div 16? */
+ uint16_t cSegs; /**< 0x0a: Number of segments in the map. */
+ uint16_t off16SegDef; /**< 0x0c: Offset of the segment defintions divided by 16. */
+ uint8_t cchMaxSym; /**< 0x0e: Maximum symbol-name length. */
+ uint8_t cchModule; /**< 0x0f: Length of the module name. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char achModule[RT_FLEXIBLE_ARRAY]; /**< 0x10: Module name, length given by cchModule. */
+} MAPSYMHDR;
+
+/** MAPSYM: Tail structure. */
+typedef struct MAPSYMTAIL
+{
+ uint16_t offNextMap; /**< 0x00: Always zero (it's the tail, see). */
+ uint8_t bRelease; /**< 0x02: Minor version number. */
+ uint8_t bVersion; /**< 0x03: Major version number. */
+} MAPSYMTAIL;
+
+/** MAPSYM: Segment defintion. */
+typedef struct MAPSYMSEGDEF
+{
+ uint16_t off16NextSeg; /**< 0x00: Offset of the next segment divided by 16. */
+ uint16_t cSymbols; /**< 0x02: Number of symbol offsets . */
+ uint16_t offSymbolOffsets; /**< 0x04: Offset of the symbol offset table. Each entry is a 16-bit value giving
+ * the offset symbol relative to this structure. */
+ uint16_t au16Reserved0[4]; /**< 0x06: Reserved / unknown.
+ * First byte/word seems to be 1-based segment number. */
+ uint8_t bFlags; /**< 0x0e: MAPSYMSEGDEF_F_32BIT or zero. */
+ uint8_t bReserved1; /**< 0x0f: Reserved / unknown. */
+ uint16_t offLineDef; /**< 0x10: Offset to the line defintions. */
+ uint16_t u16Reserved2; /**< 0x12: Reserved / unknown. Often seen holding 0xff00. */
+ uint8_t cchSegName; /**< 0x14: Segment name length. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char achSegName[RT_FLEXIBLE_ARRAY]; /**< 0x15: Segment name, length given by cchSegName. */
+} MAPSYMSEGDEF;
+
+#define MAPSYMSEGDEF_F_32BIT UINT8_C(0x01) /**< Indicates 32-bit segment rather than 16-bit, relevant for symbols. */
+#define MAPSYMSEGDEF_F_UNKNOWN UINT8_C(0x02) /**< Set on all segments in os2krnlr.sym from ACP2. */
+
+/** MAPSYM: 16-bit symbol */
+typedef struct MAPSYMSYMDEF16
+{
+ uint16_t uValue; /**< 0x00: The symbol value (address). */
+ uint8_t cchName; /**< 0x02: Symbol name length. */
+ char achName[1]; /**< 0x03: The symbol name, length give by cchName. */
+} MAPSYMSYMDEF16;
+
+/** MAPSYM: 16-bit symbol */
+typedef struct MAPSYMSYMDEF32
+{
+ uint32_t uValue; /**< 0x00: The symbol value (address). */
+ uint8_t cchName; /**< 0x04: Symbol name length. */
+ char achName[1]; /**< 0x05: The symbol name, length give by cchName. */
+} MAPSYMSYMDEF32;
+
+/** MAPSYM: Line number defintions. */
+typedef struct MAPSYMLINEDEF
+{
+ uint16_t off16NextLine; /**< 0x00: Offset to the next line defintion divided by 16. */
+ uint16_t uSegment; /**< 0x02: Guessing this must be segment number. */
+ uint16_t offLines; /**< 0x04: Offset to the line number array, relative to this structure. */
+ uint16_t cLines; /**< 0x08: Number of line numbers in the array. */
+ uint8_t cchSrcFile; /**< 0x0a: Length of source filename. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char achSrcFile[RT_FLEXIBLE_ARRAY]; /**< 0x0b: Source filename, length given by cchSrcFile. */
+} MAPSYMLINEDEF;
+
+/** MAPSYM: 16-bit line numbers. */
+typedef struct MAPSYMLINENO16
+{
+ uint16_t offSeg;
+ uint16_t uLineNo;
+} MAPSYMLINENO16;
+
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum number of segments we expect in a MAPSYM file. */
+#define RTDBGMODMAPSYM_MAX_SEGMENTS 256
+
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnUnwindFrame} */
+static DECLCALLBACK(int) rtDbgModMapSym_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ RT_NOREF(pMod, iSeg, off, pState);
+ return VERR_DBG_NO_UNWIND_INFO;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByAddr} */
+static DECLCALLBACK(int) rtDbgModMapSym_LineByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ PRTINTPTR poffDisp, PRTDBGLINE pLineInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModLineByAddr(hCnt, iSeg, off, poffDisp, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByOrdinal} */
+static DECLCALLBACK(int) rtDbgModMapSym_LineByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGLINE pLineInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModLineByOrdinal(hCnt, iOrdinal, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineCount} */
+static DECLCALLBACK(uint32_t) rtDbgModMapSym_LineCount(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModLineCount(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineAdd} */
+static DECLCALLBACK(int) rtDbgModMapSym_LineAdd(PRTDBGMODINT pMod, const char *pszFile, size_t cchFile, uint32_t uLineNo,
+ uint32_t iSeg, RTUINTPTR off, uint32_t *piOrdinal)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszFile[cchFile]); NOREF(cchFile);
+ return RTDbgModLineAdd(hCnt, pszFile, uLineNo, iSeg, off, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByAddr} */
+static DECLCALLBACK(int) rtDbgModMapSym_SymbolByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSymbolByAddr(hCnt, iSeg, off, fFlags, poffDisp, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByName} */
+static DECLCALLBACK(int) rtDbgModMapSym_SymbolByName(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ PRTDBGSYMBOL pSymInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); NOREF(cchSymbol);
+ return RTDbgModSymbolByName(hCnt, pszSymbol, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByOrdinal} */
+static DECLCALLBACK(int) rtDbgModMapSym_SymbolByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGSYMBOL pSymInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSymbolByOrdinal(hCnt, iOrdinal, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolCount} */
+static DECLCALLBACK(uint32_t) rtDbgModMapSym_SymbolCount(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSymbolCount(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolAdd} */
+static DECLCALLBACK(int) rtDbgModMapSym_SymbolAdd(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ RTDBGSEGIDX iSeg, RTUINTPTR off, RTUINTPTR cb, uint32_t fFlags,
+ uint32_t *piOrdinal)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); NOREF(cchSymbol);
+ return RTDbgModSymbolAdd(hCnt, pszSymbol, iSeg, off, cb, fFlags, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentByIndex} */
+static DECLCALLBACK(int) rtDbgModMapSym_SegmentByIndex(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, PRTDBGSEGMENT pSegInfo)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSegmentByIndex(hCnt, iSeg, pSegInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentCount} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModMapSym_SegmentCount(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModSegmentCount(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentAdd} */
+static DECLCALLBACK(int) rtDbgModMapSym_SegmentAdd(PRTDBGMODINT pMod, RTUINTPTR uRva, RTUINTPTR cb, const char *pszName,
+ size_t cchName, uint32_t fFlags, PRTDBGSEGIDX piSeg)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ Assert(!pszName[cchName]); NOREF(cchName);
+ return RTDbgModSegmentAdd(hCnt, uRva, cb, pszName, fFlags, piSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnImageSize} */
+static DECLCALLBACK(RTUINTPTR) rtDbgModMapSym_ImageSize(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModImageSize(hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnRvaToSegOff} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModMapSym_RvaToSegOff(PRTDBGMODINT pMod, RTUINTPTR uRva, PRTUINTPTR poffSeg)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ return RTDbgModRvaToSegOff(hCnt, uRva, poffSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnClose} */
+static DECLCALLBACK(int) rtDbgModMapSym_Close(PRTDBGMODINT pMod)
+{
+ RTDBGMOD hCnt = (RTDBGMOD)pMod->pvDbgPriv;
+ RTDbgModRelease(hCnt);
+ pMod->pvDbgPriv = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validate the module header.
+ *
+ * @returns true if valid, false if not.
+ * @param pHdr The header.
+ * @param cbAvail How much we've actually read.
+ * @param cbFile The file size (relative to module header).
+ */
+static bool rtDbgModMapSymIsValidHeader(MAPSYMHDR const *pHdr, size_t cbAvail, uint64_t cbFile)
+{
+ if (cbAvail <= RT_UOFFSETOF(MAPSYMHDR, achModule))
+ return false;
+
+ if (pHdr->cSegs == 0)
+ return false;
+ if (pHdr->cSegs > RTDBGMODMAPSYM_MAX_SEGMENTS)
+ return false;
+
+ if (pHdr->off16SegDef == 0)
+ return false;
+ if (pHdr->off16SegDef * (uint32_t)16 >= cbFile)
+ return false;
+
+ if (pHdr->cchModule == 0)
+ return false;
+ if (pHdr->cchModule > 128) /* Note must be smaller than abPadding below in caller */
+ return false;
+
+ size_t cchMaxName = cbAvail - RT_UOFFSETOF(MAPSYMHDR, achModule);
+ if (pHdr->cchModule > cchMaxName)
+ return false;
+
+ for (uint32_t i = 0; i < pHdr->cchModule; i++)
+ {
+ unsigned char const uch = pHdr->achModule[i];
+ if ( uch < 0x20
+ || uch >= 0x7f)
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Validate the given segment definition.
+ *
+ * @returns true if valid, false if not.
+ * @param pSegDef The segment definition structure.
+ * @param cbMax Host many bytes are available starting with pSegDef.
+ */
+static bool rtDbgModMapSymIsValidSegDef(MAPSYMSEGDEF const *pSegDef, size_t cbMax)
+{
+ if (RT_UOFFSETOF(MAPSYMSEGDEF, achSegName) > cbMax)
+ return false;
+ if (pSegDef->cSymbols)
+ {
+ if (pSegDef->cSymbols > _32K)
+ {
+ Log(("rtDbgModMapSymIsValidSegDef: Too many symbols: %#x\n", pSegDef->cSymbols));
+ return false;
+ }
+
+ if (pSegDef->offSymbolOffsets + (uint32_t)2 * pSegDef->cSymbols > cbMax)
+ {
+ Log(("rtDbgModMapSymIsValidSegDef: Bad symbol offset/count: %#x/%#x\n", pSegDef->offSymbolOffsets, pSegDef->cSymbols));
+ return false;
+ }
+ }
+
+ size_t cchMaxName = cbMax - RT_UOFFSETOF(MAPSYMHDR, achModule);
+ if (pSegDef->cchSegName > cchMaxName)
+ {
+ Log(("rtDbgModMapSymIsValidSegDef: Bad segment name length\n"));
+ return false;
+ }
+
+ for (uint32_t i = 0; i < pSegDef->cchSegName; i++)
+ {
+ unsigned char uch = pSegDef->achSegName[i];
+ if ( uch < 0x20
+ || uch >= 0x7f)
+ {
+ Log(("rtDbgModMapSymIsValidSegDef: Bad segment name: %.*Rhxs\n", pSegDef->cchSegName, pSegDef->achSegName));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Fills @a hCnt with segments and symbols from the MAPSYM file.
+ *
+ * @note We only support reading the first module, right now.
+ */
+static int rtDbgModMapSymReadIt(RTDBGMOD hCnt, uint8_t const *pbFile, size_t cbFile)
+{
+ /*
+ * Revalidate the header.
+ */
+ MAPSYMHDR const *pHdr = (MAPSYMHDR const *)pbFile;
+ if (!rtDbgModMapSymIsValidHeader(pHdr, cbFile, cbFile))
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+ Log(("rtDbgModMapSymReadIt: szModule='%.*s' cSegs=%u off16NextMap=%#x\n",
+ pHdr->cchModule, pHdr->achModule, pHdr->cSegs, pHdr->off16NextMap));
+
+ /*
+ * Load each segment.
+ */
+ uint32_t uRva = 0;
+ uint32_t const cSegs = pHdr->cSegs;
+ uint32_t offSegment = pHdr->off16SegDef * (uint32_t)16;
+ for (uint32_t iSeg = 0; iSeg < cSegs; iSeg++)
+ {
+ if (offSegment >= cbFile)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+
+ size_t const cbMax = cbFile - offSegment;
+ MAPSYMSEGDEF const *pSegDef = (MAPSYMSEGDEF const *)&pbFile[offSegment];
+ if (!rtDbgModMapSymIsValidSegDef(pSegDef, cbMax))
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+
+ Log(("rtDbgModMapSymReadIt: Segment #%u: flags=%#x name='%.*s' symbols=%#x @ %#x next=%#x lines=@%#x (reserved: %#x %#x %#x %#x %#x %#x)\n",
+ iSeg, pSegDef->bFlags, pSegDef->cchSegName, pSegDef->achSegName, pSegDef->cSymbols, pSegDef->offSymbolOffsets,
+ pSegDef->off16NextSeg, pSegDef->offLineDef, pSegDef->au16Reserved0[0], pSegDef->au16Reserved0[1],
+ pSegDef->au16Reserved0[2], pSegDef->au16Reserved0[3], pSegDef->bReserved1, pSegDef->u16Reserved2));
+
+ /*
+ * First symbol pass finds the largest symbol and uses that as the segment size.
+ */
+ uint32_t cbSegmentEst = 0;
+ uint32_t const cSymbols = pSegDef->cSymbols;
+ uint16_t const * const paoffSymbols = (uint16_t const *)&pbFile[offSegment + pSegDef->offSymbolOffsets];
+ bool const fIs32Bit = RT_BOOL(pSegDef->bFlags & MAPSYMSEGDEF_F_32BIT);
+ uint32_t const cbSymDef = fIs32Bit ? 4 + 1 : 2 + 1;
+ for (uint32_t iSymbol = 0; iSymbol < cSymbols; iSymbol++)
+ {
+ uint32_t off = paoffSymbols[iSymbol] + offSegment;
+ if (off + cbSymDef <= cbFile)
+ {
+ uint32_t uValue = fIs32Bit ? *(uint32_t const *)&pbFile[off] : (uint32_t)*(uint16_t const *)&pbFile[off];
+ if (uValue > cbSegmentEst)
+ cbSegmentEst = uValue;
+ }
+ else
+ Log(("rtDbgModMapSymReadIt: Bad symbol offset %#x\n", off));
+ }
+
+ /*
+ * Add the segment.
+ */
+ char szName[256];
+ memcpy(szName, pSegDef->achSegName, pSegDef->cchSegName);
+ szName[pSegDef->cchSegName] = '\0';
+ if (!pSegDef->cchSegName)
+ RTStrPrintf(szName, sizeof(szName), "seg%02u", iSeg);
+
+ RTDBGSEGIDX idxDbgSeg = iSeg;
+ int rc = RTDbgModSegmentAdd(hCnt, uRva, cbSegmentEst, szName, 0 /*fFlags*/, &idxDbgSeg);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uRva += cbSegmentEst;
+
+ /*
+ * The second symbol pass loads the symbol values and names.
+ */
+ for (uint32_t iSymbol = 0; iSymbol < cSymbols; iSymbol++)
+ {
+ uint32_t off = paoffSymbols[iSymbol] + offSegment;
+ if (off + cbSymDef <= cbFile)
+ {
+ /* Get value: */
+ uint32_t uValue = RT_MAKE_U16(pbFile[off], pbFile[off + 1]);
+ off += 2;
+ if (fIs32Bit)
+ {
+ uValue |= RT_MAKE_U32_FROM_U8(0, 0, pbFile[off], pbFile[off + 1]);
+ off += 2;
+ }
+
+ /* Get name: */
+ uint8_t cchName = pbFile[off++];
+ if (off + cchName <= cbFile)
+ {
+ memcpy(szName, &pbFile[off], cchName);
+ szName[cchName] = '\0';
+ RTStrPurgeEncoding(szName);
+ }
+ else
+ cchName = 0;
+ if (cchName == 0)
+ RTStrPrintf(szName, sizeof(szName), "unknown_%u_%u", iSeg, iSymbol);
+
+ /* Try add it: */
+ rc = RTDbgModSymbolAdd(hCnt, szName, idxDbgSeg, uValue, 0 /*cb*/, 0 /*fFlags*/, NULL /*piOrdinal*/);
+ if (RT_SUCCESS(rc))
+ Log7(("rtDbgModMapSymReadIt: %02x:%06x %s\n", idxDbgSeg, uValue, szName));
+ else if ( rc == VERR_DBG_DUPLICATE_SYMBOL
+ || rc == VERR_DBG_ADDRESS_CONFLICT
+ || rc == VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE)
+ Log(("rtDbgModMapSymReadIt: %02x:%06x %s\n", idxDbgSeg, uValue, szName));
+ else
+ {
+ Log(("rtDbgModMapSymReadIt: Unexpected RTDbgModSymbolAdd failure: %Rrc - %02x:%06x %s\n",
+ rc, idxDbgSeg, uValue, szName));
+ return rc;
+ }
+ }
+ }
+
+ /* Next segment */
+ offSegment = pSegDef->off16NextSeg * (uint32_t)16;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnTryOpen} */
+static DECLCALLBACK(int) rtDbgModMapSym_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch)
+{
+ NOREF(enmArch);
+
+ /*
+ * Fend off images.
+ */
+ if ( !pMod->pszDbgFile
+ || pMod->pImgVt)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+ pMod->pvDbgPriv = NULL;
+
+ /*
+ * Try open the file and check out the first header.
+ */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pMod->pszDbgFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbFile = 0;
+ rc = RTFileQuerySize(hFile, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile < _2M)
+ {
+ union
+ {
+ MAPSYMHDR Hdr;
+ char abPadding[sizeof(MAPSYMHDR) + 257]; /* rtDbgModMapSymIsValidHeader makes size assumptions. */
+ } uBuf;
+ size_t cbToRead = (size_t)RT_MIN(cbFile, sizeof(uBuf));
+ rc = RTFileReadAt(hFile, 0, &uBuf, RT_MIN(cbFile, sizeof(uBuf)), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (rtDbgModMapSymIsValidHeader(&uBuf.Hdr, cbToRead, cbFile))
+ {
+ uBuf.Hdr.achModule[uBuf.Hdr.cchModule] = '\0';
+
+ /*
+ * Read the whole thing into memory, create an
+ * instance/container and load it with symbols.
+ */
+ void *pvFile = NULL;
+ size_t cbFile2 = 0;
+ rc = RTFileReadAllByHandle(hFile, &pvFile, &cbFile2);
+ if (RT_SUCCESS(rc))
+ {
+ RTDBGMOD hCnt;
+ rc = RTDbgModCreate(&hCnt, uBuf.Hdr.achModule, 0 /*cbSeg*/, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtDbgModMapSymReadIt(hCnt, (uint8_t const *)pvFile, cbFile2);
+ if (RT_SUCCESS(rc))
+ pMod->pvDbgPriv = hCnt;
+ else
+ RTDbgModRelease(hCnt);
+ }
+ RTFileReadAllFree(pvFile, cbFile2);
+ }
+ }
+ else
+ rc = VERR_DBG_NO_MATCHING_INTERPRETER;
+ }
+ }
+ RTFileClose(hFile);
+ }
+ Log(("rtDbgModMapSym_TryOpen: %s -> %Rrc, %p\n", pMod->pszDbgFile, rc, pMod->pvDbgPriv));
+ return rc;
+}
+
+
+
+/** Virtual function table for the MAPSYM file reader. */
+DECL_HIDDEN_CONST(RTDBGMODVTDBG) const g_rtDbgModVtDbgMapSym =
+{
+ /*.u32Magic = */ RTDBGMODVTDBG_MAGIC,
+ /*.fSupports = */ RT_DBGTYPE_SYM,
+ /*.pszName = */ "mapsym",
+ /*.pfnTryOpen = */ rtDbgModMapSym_TryOpen,
+ /*.pfnClose = */ rtDbgModMapSym_Close,
+
+ /*.pfnRvaToSegOff = */ rtDbgModMapSym_RvaToSegOff,
+ /*.pfnImageSize = */ rtDbgModMapSym_ImageSize,
+
+ /*.pfnSegmentAdd = */ rtDbgModMapSym_SegmentAdd,
+ /*.pfnSegmentCount = */ rtDbgModMapSym_SegmentCount,
+ /*.pfnSegmentByIndex = */ rtDbgModMapSym_SegmentByIndex,
+
+ /*.pfnSymbolAdd = */ rtDbgModMapSym_SymbolAdd,
+ /*.pfnSymbolCount = */ rtDbgModMapSym_SymbolCount,
+ /*.pfnSymbolByOrdinal = */ rtDbgModMapSym_SymbolByOrdinal,
+ /*.pfnSymbolByName = */ rtDbgModMapSym_SymbolByName,
+ /*.pfnSymbolByAddr = */ rtDbgModMapSym_SymbolByAddr,
+
+ /*.pfnLineAdd = */ rtDbgModMapSym_LineAdd,
+ /*.pfnLineCount = */ rtDbgModMapSym_LineCount,
+ /*.pfnLineByOrdinal = */ rtDbgModMapSym_LineByOrdinal,
+ /*.pfnLineByAddr = */ rtDbgModMapSym_LineByAddr,
+
+ /*.pfnUnwindFrame = */ rtDbgModMapSym_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTDBG_MAGIC
+};
+
diff --git a/src/VBox/Runtime/common/dbg/dbgmodnm.cpp b/src/VBox/Runtime/common/dbg/dbgmodnm.cpp
new file mode 100644
index 00000000..04174c7b
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgmodnm.cpp
@@ -0,0 +1,581 @@
+/* $Id: dbgmodnm.cpp $ */
+/** @file
+ * IPRT - Debug Map Reader For NM Like Mapfiles.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dbg.h>
+#include "internal/iprt.h"
+
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include "internal/dbgmod.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Instance data.
+ */
+typedef struct RTDBGMODNM
+{
+ /** The debug container containing doing the real work. */
+ RTDBGMOD hCnt;
+} RTDBGMODNM;
+/** Pointer to instance data NM map reader. */
+typedef RTDBGMODNM *PRTDBGMODNM;
+
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnUnwindFrame} */
+static DECLCALLBACK(int) rtDbgModNm_UnwindFrame(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ RT_NOREF(pMod, iSeg, off, pState);
+ return VERR_DBG_NO_UNWIND_INFO;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByAddr} */
+static DECLCALLBACK(int) rtDbgModNm_LineByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off,
+ PRTINTPTR poffDisp, PRTDBGLINE pLineInfo)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModLineByAddr(pThis->hCnt, iSeg, off, poffDisp, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineByOrdinal} */
+static DECLCALLBACK(int) rtDbgModNm_LineByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGLINE pLineInfo)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModLineByOrdinal(pThis->hCnt, iOrdinal, pLineInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineCount} */
+static DECLCALLBACK(uint32_t) rtDbgModNm_LineCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModLineCount(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnLineAdd} */
+static DECLCALLBACK(int) rtDbgModNm_LineAdd(PRTDBGMODINT pMod, const char *pszFile, size_t cchFile, uint32_t uLineNo,
+ uint32_t iSeg, RTUINTPTR off, uint32_t *piOrdinal)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ Assert(!pszFile[cchFile]); NOREF(cchFile);
+ return RTDbgModLineAdd(pThis->hCnt, pszFile, uLineNo, iSeg, off, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByAddr} */
+static DECLCALLBACK(int) rtDbgModNm_SymbolByAddr(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, RTUINTPTR off, uint32_t fFlags,
+ PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModSymbolByAddr(pThis->hCnt, iSeg, off, fFlags, poffDisp, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByName} */
+static DECLCALLBACK(int) rtDbgModNm_SymbolByName(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); NOREF(cchSymbol);
+ return RTDbgModSymbolByName(pThis->hCnt, pszSymbol, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolByOrdinal} */
+static DECLCALLBACK(int) rtDbgModNm_SymbolByOrdinal(PRTDBGMODINT pMod, uint32_t iOrdinal, PRTDBGSYMBOL pSymInfo)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModSymbolByOrdinal(pThis->hCnt, iOrdinal, pSymInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolCount} */
+static DECLCALLBACK(uint32_t) rtDbgModNm_SymbolCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModSymbolCount(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSymbolAdd} */
+static DECLCALLBACK(int) rtDbgModNm_SymbolAdd(PRTDBGMODINT pMod, const char *pszSymbol, size_t cchSymbol,
+ RTDBGSEGIDX iSeg, RTUINTPTR off, RTUINTPTR cb, uint32_t fFlags,
+ uint32_t *piOrdinal)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ Assert(!pszSymbol[cchSymbol]); NOREF(cchSymbol);
+ return RTDbgModSymbolAdd(pThis->hCnt, pszSymbol, iSeg, off, cb, fFlags, piOrdinal);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentByIndex} */
+static DECLCALLBACK(int) rtDbgModNm_SegmentByIndex(PRTDBGMODINT pMod, RTDBGSEGIDX iSeg, PRTDBGSEGMENT pSegInfo)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModSegmentByIndex(pThis->hCnt, iSeg, pSegInfo);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentCount} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModNm_SegmentCount(PRTDBGMODINT pMod)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModSegmentCount(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnSegmentAdd} */
+static DECLCALLBACK(int) rtDbgModNm_SegmentAdd(PRTDBGMODINT pMod, RTUINTPTR uRva, RTUINTPTR cb, const char *pszName,
+ size_t cchName, uint32_t fFlags, PRTDBGSEGIDX piSeg)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ Assert(!pszName[cchName]); NOREF(cchName);
+ return RTDbgModSegmentAdd(pThis->hCnt, uRva, cb, pszName, fFlags, piSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnImageSize} */
+static DECLCALLBACK(RTUINTPTR) rtDbgModNm_ImageSize(PRTDBGMODINT pMod)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModImageSize(pThis->hCnt);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnRvaToSegOff} */
+static DECLCALLBACK(RTDBGSEGIDX) rtDbgModNm_RvaToSegOff(PRTDBGMODINT pMod, RTUINTPTR uRva, PRTUINTPTR poffSeg)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ return RTDbgModRvaToSegOff(pThis->hCnt, uRva, poffSeg);
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnClose} */
+static DECLCALLBACK(int) rtDbgModNm_Close(PRTDBGMODINT pMod)
+{
+ PRTDBGMODNM pThis = (PRTDBGMODNM)pMod->pvDbgPriv;
+ RTDbgModRelease(pThis->hCnt);
+ pThis->hCnt = NIL_RTDBGMOD;
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Scans a NM-like map file.
+ *
+ * This implements both passes to avoid code duplication.
+ *
+ * @returns IPRT status code.
+ * @param pThis The instance data.
+ * @param pStrm The stream.
+ * @param fAddSymbols false in the first pass, true in the second.
+ */
+static int rtDbgModNmScanFile(PRTDBGMODNM pThis, PRTSTREAM pStrm, bool fAddSymbols)
+{
+ /*
+ * Try parse the stream.
+ */
+ RTUINTPTR SegZeroRva = fAddSymbols ? RTDbgModSegmentRva(pThis->hCnt, 0/*iSeg*/) : 0;
+ char szSym[RTDBG_SYMBOL_NAME_LENGTH] = "";
+ size_t cchMod = 0;
+ size_t offSym = 0;
+ unsigned cchAddr = 0;
+ uint64_t u64Low = UINT64_MAX;
+ uint64_t u64High = 0;
+ int fWithType = -1;
+ char szLine[512];
+ int rc;
+ while (RT_SUCCESS(rc = RTStrmGetLine(pStrm, szLine, sizeof(szLine))))
+ {
+ char chType;
+ if (RT_C_IS_XDIGIT(szLine[0]))
+ {
+ /*
+ * This is really what C was made for, string parsing.
+ */
+ /* The symbol value (address). */
+ uint64_t u64Addr;
+ char *psz;
+ rc = RTStrToUInt64Ex(szLine, &psz, 16, &u64Addr);
+ if (rc != VWRN_TRAILING_CHARS)
+ return VERR_DBG_NOT_NM_MAP_FILE;
+
+ /* Check the address width. */
+ if (cchAddr == 0)
+ cchAddr = psz == &szLine[8] ? 8 : 16;
+ if (psz != &szLine[cchAddr])
+ return VERR_DBG_NOT_NM_MAP_FILE;
+
+ /* Get the type and check for single space before symbol. */
+ char *pszName;
+ if (fWithType < 0)
+ fWithType = RT_C_IS_BLANK(szLine[cchAddr + 2]) ? 1 : 0; /* have type? Linux 2.4 /proc/ksyms doesn't. */
+ if (fWithType)
+ {
+ chType = szLine[cchAddr + 1];
+ pszName = &szLine[cchAddr + 3];
+ if ( RT_C_IS_BLANK(chType)
+ || !RT_C_IS_BLANK(szLine[cchAddr + 2])
+ || RT_C_IS_BLANK(szLine[cchAddr + 3]))
+ return VERR_DBG_NOT_NM_MAP_FILE;
+ }
+ else
+ {
+ chType = 'T';
+ pszName = &szLine[cchAddr + 1];
+ }
+
+ /* Find the end of the symbol name. */
+ char *pszNameEnd = pszName;
+ char ch;
+ while ((ch = *pszNameEnd) != '\0' && !RT_C_IS_SPACE(ch))
+ pszNameEnd++;
+
+ /* Any module name (linux /proc/kallsyms) following in brackets? */
+ char *pszModName = pszNameEnd;
+ char *pszModNameEnd = pszModName;
+ if (*pszModName)
+ {
+ *pszModName++ = '\0';
+ pszModNameEnd = pszModName = RTStrStripL(pszModName);
+ if (*pszModName != '\0')
+ {
+ if (*pszModName != '[')
+ return VERR_DBG_NOT_LINUX_KALLSYMS;
+ pszModNameEnd = ++pszModName;
+ while ((ch = *pszModNameEnd) != '\0' && ch != ']')
+ pszModNameEnd++;
+ if (ch != ']')
+ return VERR_DBG_NOT_LINUX_KALLSYMS;
+ char *pszEnd = pszModNameEnd + 1;
+ if ((size_t)(pszModNameEnd - pszModName) >= 128) /* lazy bird */
+ return VERR_DBG_NOT_LINUX_KALLSYMS;
+ *pszModNameEnd = '\0';
+ if (*pszEnd)
+ pszEnd = RTStrStripL(pszEnd);
+ if (*pszEnd)
+ return VERR_DBG_NOT_LINUX_KALLSYMS;
+ }
+ }
+
+ /*
+ * Did the module change? Then update the symbol prefix.
+ */
+ if ( cchMod != (size_t)(pszModNameEnd - pszModName)
+ || memcmp(pszModName, szSym, cchMod))
+ {
+ cchMod = pszModNameEnd - pszModName;
+ if (cchMod == 0)
+ offSym = 0;
+ else
+ {
+ memcpy(szSym, pszModName, cchMod);
+ szSym[cchMod] = '.';
+ offSym = cchMod + 1;
+ }
+ szSym[offSym] = '\0';
+ }
+
+ /*
+ * Validate the type and add the symbol if it's a type we care for.
+ */
+ uint32_t fFlags = 0;
+ RTDBGSEGIDX iSegSym = 0;
+ switch (chType)
+ {
+ /* absolute */
+ case 'a':
+ case '?': /* /proc/kallsyms */
+ iSegSym = RTDBGSEGIDX_ABS;
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_LOCAL;
+ break;
+ case 'A':
+ iSegSym = RTDBGSEGIDX_ABS;
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_PUBLIC;
+ break;
+
+ case 'b':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_LOCAL;
+ break;
+ case 'B':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_PUBLIC;
+ break;
+
+ case 'c':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_LOCAL | RTDBG_SYM_FLAGS_COMMON;
+ break;
+ case 'C':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_PUBLIC | RTDBG_SYM_FLAGS_COMMON;
+ break;
+
+ case 'd':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_LOCAL;
+ break;
+ case 'D':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_PUBLIC;
+ break;
+
+ case 'g':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_LOCAL;
+ break;
+ case 'G':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_PUBLIC;
+ break;
+
+ case 'i':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_LOCAL;
+ break;
+ case 'I':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_PUBLIC;
+ break;
+
+ case 'r':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_LOCAL | RTDBG_SYM_FLAGS_CONST;
+ break;
+ case 'R':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_PUBLIC | RTDBG_SYM_FLAGS_CONST;
+ break;
+
+ case 's':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_LOCAL;
+ break;
+ case 'S':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_PUBLIC;
+ break;
+
+ case 't':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_LOCAL | RTDBG_SYM_FLAGS_TEXT;
+ break;
+ case 'T':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_PUBLIC | RTDBG_SYM_FLAGS_TEXT;
+ break;
+
+ case 'w':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_WEAK | RTDBG_SYM_FLAGS_LOCAL; //???
+ break;
+ case 'W':
+ /// @todo fFlags |= RTDBG_SYM_FLAGS_WEAK | RTDBG_SYM_FLAGS_PUBLIC;
+ break;
+
+ case 'N': /* debug */
+ case 'n':
+ case '-': /* stabs */
+ case 'u': /* undefined (/proc/kallsyms) */
+ case 'U':
+ case 'v': /* weakext */
+ case 'V':
+ iSegSym = NIL_RTDBGSEGIDX;
+ break;
+
+ default:
+ return VERR_DBG_NOT_NM_MAP_FILE;
+ }
+
+ if (iSegSym != NIL_RTDBGSEGIDX)
+ {
+ if (fAddSymbols)
+ {
+ size_t cchName = pszNameEnd - pszName;
+ if (cchName >= sizeof(szSym) - offSym)
+ cchName = sizeof(szSym) - offSym - 1;
+ memcpy(&szSym[offSym], pszName, cchName + 1);
+ if (iSegSym == 0)
+ rc = RTDbgModSymbolAdd(pThis->hCnt, szSym, iSegSym, u64Addr - SegZeroRva, 0/*cb*/, fFlags, NULL);
+ else
+ rc = RTDbgModSymbolAdd(pThis->hCnt, szSym, iSegSym, u64Addr, 0/*cb*/, fFlags, NULL);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_DBG_DUPLICATE_SYMBOL
+ && rc != VERR_DBG_ADDRESS_CONFLICT) /* (don't be too strict) */
+ return rc;
+ }
+
+ /* Track segment span. */
+ if (iSegSym == 0)
+ {
+ if (u64Low > u64Addr)
+ u64Low = u64Addr;
+ if (u64High < u64Addr)
+ u64High = u64Addr;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * This is either a blank line or a symbol without an address.
+ */
+ RTStrStripR(szLine);
+ if (szLine[0])
+ {
+ size_t cch = strlen(szLine);
+ if (cchAddr == 0)
+ cchAddr = cch < 16+3 || szLine[8+1] != ' ' ? 8 : 16;
+ if (cch < cchAddr+3+1)
+ return VERR_DBG_NOT_NM_MAP_FILE;
+ chType = szLine[cchAddr + 1];
+ if ( chType != 'U'
+ && chType != 'w')
+ return VERR_DBG_NOT_NM_MAP_FILE;
+ char *pszType = RTStrStripL(szLine);
+ if (pszType != &szLine[cchAddr + 1])
+ return VERR_DBG_NOT_NM_MAP_FILE;
+ if (!RT_C_IS_BLANK(szLine[cchAddr + 2]))
+ return VERR_DBG_NOT_NM_MAP_FILE;
+ }
+ /* else: blank - ignored */
+ }
+ }
+
+ /*
+ * The final segment.
+ */
+ if (rc == VERR_EOF)
+ {
+ if (fAddSymbols)
+ rc = VINF_SUCCESS;
+ else
+ {
+ if ( u64Low != UINT64_MAX
+ || u64High != 0)
+ rc = RTDbgModSegmentAdd(pThis->hCnt, u64Low, u64High - u64Low + 1, "main", 0, NULL);
+ else /* No sensible symbols... throw an error instead? */
+ rc = RTDbgModSegmentAdd(pThis->hCnt, 0, 0, "main", 0, NULL);
+ }
+ }
+
+ return rc;
+}
+
+
+/** @interface_method_impl{RTDBGMODVTDBG,pfnTryOpen} */
+static DECLCALLBACK(int) rtDbgModNm_TryOpen(PRTDBGMODINT pMod, RTLDRARCH enmArch)
+{
+ NOREF(enmArch);
+
+ /*
+ * Fend off images.
+ */
+ if ( !pMod->pszDbgFile
+ || pMod->pImgVt)
+ return VERR_DBG_NO_MATCHING_INTERPRETER;
+
+ /*
+ * Try open the file and create an instance.
+ */
+ PRTSTREAM pStrm;
+ int rc = RTStrmOpen(pMod->pszDbgFile, "r", &pStrm);
+ if (RT_SUCCESS(rc))
+ {
+ PRTDBGMODNM pThis = (PRTDBGMODNM)RTMemAlloc(sizeof(*pThis));
+ if (pThis)
+ {
+ rc = RTDbgModCreate(&pThis->hCnt, pMod->pszName, 0 /*cbSeg*/, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Scan the file twice, first to figure the segment
+ * sizes, then to add the symbol.
+ */
+ rc = rtDbgModNmScanFile(pThis, pStrm, false /*fAddSymbols*/);
+ if (RT_SUCCESS(rc))
+ rc = RTStrmRewind(pStrm);
+ if (RT_SUCCESS(rc))
+ rc = rtDbgModNmScanFile(pThis, pStrm, true /*fAddSymbols*/);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrmClose(pStrm);
+ pMod->pvDbgPriv = pThis;
+ return rc;
+ }
+ }
+ RTDbgModRelease(pThis->hCnt);
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ RTStrmClose(pStrm);
+ }
+ return rc;
+}
+
+
+
+/** Virtual function table for the NM-like map file reader. */
+DECL_HIDDEN_CONST(RTDBGMODVTDBG) const g_rtDbgModVtDbgNm =
+{
+ /*.u32Magic = */ RTDBGMODVTDBG_MAGIC,
+ /*.fSupports = */ RT_DBGTYPE_MAP,
+ /*.pszName = */ "nm",
+ /*.pfnTryOpen = */ rtDbgModNm_TryOpen,
+ /*.pfnClose = */ rtDbgModNm_Close,
+
+ /*.pfnRvaToSegOff = */ rtDbgModNm_RvaToSegOff,
+ /*.pfnImageSize = */ rtDbgModNm_ImageSize,
+
+ /*.pfnSegmentAdd = */ rtDbgModNm_SegmentAdd,
+ /*.pfnSegmentCount = */ rtDbgModNm_SegmentCount,
+ /*.pfnSegmentByIndex = */ rtDbgModNm_SegmentByIndex,
+
+ /*.pfnSymbolAdd = */ rtDbgModNm_SymbolAdd,
+ /*.pfnSymbolCount = */ rtDbgModNm_SymbolCount,
+ /*.pfnSymbolByOrdinal = */ rtDbgModNm_SymbolByOrdinal,
+ /*.pfnSymbolByName = */ rtDbgModNm_SymbolByName,
+ /*.pfnSymbolByAddr = */ rtDbgModNm_SymbolByAddr,
+
+ /*.pfnLineAdd = */ rtDbgModNm_LineAdd,
+ /*.pfnLineCount = */ rtDbgModNm_LineCount,
+ /*.pfnLineByOrdinal = */ rtDbgModNm_LineByOrdinal,
+ /*.pfnLineByAddr = */ rtDbgModNm_LineByAddr,
+
+ /*.pfnUnwindFrame = */ rtDbgModNm_UnwindFrame,
+
+ /*.u32EndMagic = */ RTDBGMODVTDBG_MAGIC
+};
+
diff --git a/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm b/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm
new file mode 100644
index 00000000..9c21438d
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm
@@ -0,0 +1,157 @@
+; $Id: dbgstackdumpself-amd64-x86.asm $
+;; @file
+; IPRT - RTDbgStackDumpSelf assembly wrapper calling rtDbgStackDumpSelfWorker.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*********************************************************************************************************************************
+;* Header Files *
+;*********************************************************************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+;*********************************************************************************************************************************
+;* Extern Symbols *
+;*********************************************************************************************************************************
+extern NAME(rtDbgStackDumpSelfWorker)
+
+
+BEGINCODE
+
+;;
+; Collects register state and calls C worker.
+;
+RT_BEGINPROC RTDbgStackDumpSelf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+
+ ;
+ ; Push all GPRS in reserve order...
+ ;
+%ifdef RT_ARCH_AMD64
+ push r15
+ SEH64_PUSH_GREG r15
+ push r14
+ SEH64_PUSH_GREG r14
+ push r13
+ SEH64_PUSH_GREG r13
+ push r12
+ SEH64_PUSH_GREG r12
+ push r13
+ SEH64_PUSH_GREG r13
+ push r12
+ SEH64_PUSH_GREG r12
+ push r11
+ SEH64_PUSH_GREG r11
+ push r10
+ SEH64_PUSH_GREG r10
+ push r9
+ SEH64_PUSH_GREG r9
+ push r8
+ SEH64_PUSH_GREG r8
+%endif
+ push xDI
+ SEH64_PUSH_GREG xDI
+ push xSI
+ SEH64_PUSH_GREG xSI
+%ifdef RT_ARCH_AMD64
+ mov r10, [xBP] ; Caller RBP.
+ push r10
+ lea r10, [xBP + xCB * 2] ; Caller RSP.
+ push r10
+%else
+ push dword [xBP] ; Caller EBP
+ push xAX
+ lea xAX, [xBP + xCB * 2] ; Caller ESP.
+ xchg xAX, [xSP]
+%endif
+ push xBX
+ SEH64_PUSH_GREG xBX
+ push xDX
+ SEH64_PUSH_GREG xDX
+ push xCX
+ SEH64_PUSH_GREG xCX
+ push xAX
+ SEH64_PUSH_GREG xAX
+
+ ;
+ ; ... preceeded by the EIP/RIP.
+ ;
+%ifdef RT_ARCH_AMD64
+ mov r10, [xBP + xCB]
+ push r10
+%else
+ push dword [xBP + xCB]
+%endif
+
+ ;
+ ; Call the worker function passing the register array as the fourth argument.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov rcx, rsp ; 4th parameter = register array.
+ sub rsp, 8h ; Align stack.
+ SEH64_ALLOCATE_STACK 28h
+%elifdef ASM_CALL64_MSC
+ mov r9, rsp ; 4th parameter = register array.
+ sub rsp, 28h ; Aling stack and allocate spill area.
+ SEH64_ALLOCATE_STACK 28h
+%else
+ mov eax, esp ; Save register array location
+ and xSP, ~15 ; Align the stack.
+%endif
+SEH64_END_PROLOGUE
+
+%ifndef RT_ARCH_AMD64
+ push eax
+ push dword [xBP + xCB * 4]
+ push dword [xBP + xCB * 3]
+ push dword [xBP + xCB * 2]
+%endif
+%ifdef ASM_FORMAT_ELF
+ %ifdef PIC
+ call NAME(rtDbgStackDumpSelfWorker) wrt ..plt
+ %else
+ call NAME(rtDbgStackDumpSelfWorker)
+ %endif
+%else
+ call NAME(rtDbgStackDumpSelfWorker)
+%endif
+
+ leave
+ ret
+ENDPROC RTDbgStackDumpSelf
+
diff --git a/src/VBox/Runtime/common/dbg/dbgstackdumpself.cpp b/src/VBox/Runtime/common/dbg/dbgstackdumpself.cpp
new file mode 100644
index 00000000..32ef86b3
--- /dev/null
+++ b/src/VBox/Runtime/common/dbg/dbgstackdumpself.cpp
@@ -0,0 +1,543 @@
+/* $Id: dbgstackdumpself.cpp $ */
+/** @file
+ * IPRT - Dump current thread stack to buffer.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/dbg.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/ldr.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+# include <iprt/x86.h>
+#else
+# error "PORTME"
+#endif
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/param.h>
+# include <iprt/utf16.h>
+# include <iprt/win/windows.h>
+#elif defined(RT_OS_LINUX) || defined(RT_OS_DARWIN)
+# include <dlfcn.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Cached module.
+ */
+typedef struct RTDBGSTACKSELFMOD
+{
+ /** List entry. */
+ RTLISTNODE ListEntry;
+ /** The mapping address. */
+ uintptr_t uMapping;
+ /** The size of the mapping. */
+ size_t cbMapping;
+ /** The loader module handle. */
+ RTLDRMOD hLdrMod;
+ /** The debug module handle, if available. */
+ RTDBGMOD hDbgMod;
+ /** Offset into szFilename of the name part. */
+ size_t offName;
+ /** the module filename. */
+ char szFilename[RTPATH_MAX];
+} RTDBGSTACKSELFMOD;
+/** Pointer to a cached module. */
+typedef RTDBGSTACKSELFMOD *PRTDBGSTACKSELFMOD;
+
+
+/**
+ * Symbol search state.
+ */
+typedef struct RTDBGSTACKSELFSYMSEARCH
+{
+ /** The address (not RVA) we're searching for a symbol for. */
+ uintptr_t uSearch;
+ /** The distance of the current hit. This is ~(uintptr_t)0 if no hit. */
+ uintptr_t offBestDist;
+ /** Where to return symbol information. */
+ PRTDBGSYMBOL pSymInfo;
+} RTDBGSTACKSELFSYMSEARCH;
+typedef RTDBGSTACKSELFSYMSEARCH *PRTDBGSTACKSELFSYMSEARCH;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+/* Wanted to use DECLHIDDEN(DECLASM(size_t)) here, but early solaris 11 doesn't like it. */
+RT_C_DECLS_BEGIN
+DECL_HIDDEN_CALLBACK(size_t) rtDbgStackDumpSelfWorker(char *pszStack, size_t cbStack, uint32_t fFlags, PCRTCCUINTREG pauRegs);
+RT_C_DECLS_END
+
+
+/**
+ * Worker for stack and module reader callback.
+ *
+ * @returns IPRT status code
+ * @param pvDst Where to put the request memory.
+ * @param cbToRead How much to read.
+ * @param uSrcAddr Where to read the memory from.
+ */
+static int rtDbgStackDumpSelfSafeMemoryReader(void *pvDst, size_t cbToRead, uintptr_t uSrcAddr)
+{
+# ifdef RT_OS_WINDOWS
+# if 1
+ __try
+ {
+ memcpy(pvDst, (void const *)uSrcAddr, cbToRead);
+ }
+ __except(EXCEPTION_EXECUTE_HANDLER)
+ {
+ return VERR_ACCESS_DENIED;
+ }
+# else
+ SIZE_T cbActual = 0;
+ if (ReadProcessMemory(GetCurrentProcess(), (void const *)uSrcAddr, pvDst, cbToRead, &cbActual))
+ {
+ if (cbActual >= cbToRead)
+ return VINF_SUCCESS;
+ memset((uint8_t *)pvDst + cbActual, 0, cbToRead - cbActual);
+ return VINF_SUCCESS;
+ }
+# endif
+# else
+ /** @todo secure this from SIGSEGV. */
+ memcpy(pvDst, (void const *)uSrcAddr, cbToRead);
+# endif
+ return VINF_SUCCESS;
+}
+
+
+#if defined(RT_OS_WINDOWS) && 0
+/**
+ * @callback_method_impl{FNRTLDRRDRMEMREAD}
+ */
+static DECLCALLBACK(int) rtDbgStackDumpSelfModReader(void *pvBuf, size_t cb, size_t off, void *pvUser)
+{
+ PRTDBGSTACKSELFMOD pMod = (PRTDBGSTACKSELFMOD)pvUser;
+ return rtDbgStackDumpSelfSafeMemoryReader(pvBuf, cb, pMod->uMapping + off);
+}
+#endif
+
+
+/**
+ * @interface_method_impl{RTDBGUNWINDSTATE,pfnReadStack}
+ */
+static DECLCALLBACK(int) rtDbgStackDumpSelfReader(PRTDBGUNWINDSTATE pThis, RTUINTPTR uSp, size_t cbToRead, void *pvDst)
+{
+ RT_NOREF(pThis);
+ return rtDbgStackDumpSelfSafeMemoryReader(pvDst, cbToRead, uSp);
+}
+
+
+#ifdef RT_OS_WINDOWS
+/**
+ * Figure the size of a loaded PE image.
+ * @returns The size.
+ * @param uBase The image base address.
+ */
+static size_t rtDbgStackDumpSelfGetPeImageSize(uintptr_t uBase)
+{
+ union
+ {
+ IMAGE_DOS_HEADER DosHdr;
+ IMAGE_NT_HEADERS NtHdrs;
+ } uBuf;
+ int rc = rtDbgStackDumpSelfSafeMemoryReader(&uBuf, sizeof(uBuf.DosHdr), uBase);
+ if (RT_SUCCESS(rc))
+ {
+ if ( uBuf.DosHdr.e_magic == IMAGE_DOS_SIGNATURE
+ && uBuf.DosHdr.e_lfanew < _2M)
+ {
+ rc = rtDbgStackDumpSelfSafeMemoryReader(&uBuf, sizeof(uBuf.NtHdrs), uBase + uBuf.DosHdr.e_lfanew);
+ if (RT_SUCCESS(rc))
+ {
+ if (uBuf.NtHdrs.Signature == IMAGE_NT_SIGNATURE)
+ return uBuf.NtHdrs.OptionalHeader.SizeOfImage;
+ }
+ }
+ }
+ return _64K;
+}
+#endif
+
+
+/**
+ * Works the module cache.
+ *
+ * @returns Pointer to module cache entry on success, NULL otherwise.
+ * @param uPc The PC to locate a module for.
+ * @param pCachedModules The module cache.
+ */
+static PRTDBGSTACKSELFMOD rtDbgStackDumpSelfQueryModForPC(uintptr_t uPc, PRTLISTANCHOR pCachedModules)
+{
+ /*
+ * Search the list first.
+ */
+ PRTDBGSTACKSELFMOD pMod;
+ RTListForEach(pCachedModules, pMod, RTDBGSTACKSELFMOD, ListEntry)
+ {
+ if (uPc - pMod->uMapping < pMod->cbMapping)
+ return pMod;
+ }
+
+ /*
+ * Try figure out the module using the native loader or similar.
+ */
+#ifdef RT_OS_WINDOWS
+ HMODULE hmod = NULL;
+ static bool volatile s_fResolvedSymbols = false;
+ static decltype(GetModuleHandleExW) *g_pfnGetModuleHandleExW = NULL;
+ decltype(GetModuleHandleExW) *pfnGetModuleHandleExW;
+ if (s_fResolvedSymbols)
+ pfnGetModuleHandleExW = g_pfnGetModuleHandleExW;
+ else
+ {
+ pfnGetModuleHandleExW = (decltype(GetModuleHandleExW) *)GetProcAddress(GetModuleHandleW(L"kernel32.dll"),
+ "GetModuleHandleExW");
+ g_pfnGetModuleHandleExW = pfnGetModuleHandleExW;
+ s_fResolvedSymbols = true;
+ }
+ if ( pfnGetModuleHandleExW
+ && pfnGetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ (LPCWSTR)uPc, &hmod))
+ {
+ WCHAR wszFilename[RTPATH_MAX];
+ DWORD cwcFilename = GetModuleFileNameW(hmod, wszFilename, RT_ELEMENTS(wszFilename));
+ if (cwcFilename > 0)
+ {
+ pMod = (PRTDBGSTACKSELFMOD)RTMemAllocZ(sizeof(*pMod));
+ if (pMod)
+ {
+ char *pszDst = pMod->szFilename;
+ int rc = RTUtf16ToUtf8Ex(wszFilename, cwcFilename, &pszDst, sizeof(pMod->szFilename), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszFilename = RTPathFilename(pMod->szFilename);
+ pMod->offName = pszFilename ? pszFilename - &pMod->szFilename[0] : 0;
+ pMod->uMapping = (uintptr_t)hmod & ~(uintptr_t)(PAGE_SIZE - 1);
+ pMod->cbMapping = rtDbgStackDumpSelfGetPeImageSize(pMod->uMapping);
+ pMod->hLdrMod = NIL_RTLDRMOD;
+ pMod->hDbgMod = NIL_RTDBGMOD;
+
+# if 0 /* this ain't reliable, trouble enumerate symbols in VBoxRT. But why bother when we can load it off the disk. */
+ rc = RTLdrOpenInMemory(&pMod->szFilename[pMod->offName], RTLDR_O_FOR_DEBUG, RTLdrGetHostArch(),
+ pMod->cbMapping, rtDbgStackDumpSelfModReader, NULL /*pfnDtor*/, pMod /*pvUser*/,
+ &pMod->hLdrMod, NULL /*pErrInfo*/);
+# else
+ rc = RTLdrOpen(pMod->szFilename, RTLDR_O_FOR_DEBUG, RTLdrGetHostArch(), &pMod->hLdrMod);
+# endif
+ if (RT_SUCCESS(rc))
+ {
+ pMod->cbMapping = RTLdrSize(pMod->hLdrMod);
+
+ /* Try open debug info for the module. */
+ uint32_t uTimeDateStamp = 0;
+ RTLdrQueryProp(pMod->hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimeDateStamp, sizeof(uTimeDateStamp));
+ rc = RTDbgModCreateFromPeImage(&pMod->hDbgMod, pMod->szFilename, &pMod->szFilename[pMod->offName],
+ &pMod->hLdrMod, (uint32_t)pMod->cbMapping, uTimeDateStamp, NIL_RTDBGCFG);
+ RTListPrepend(pCachedModules, &pMod->ListEntry);
+ return pMod;
+ }
+ }
+ RTMemFree(pMod);
+ }
+ }
+ }
+#elif defined(RT_OS_LINUX) || defined(RT_OS_DARWIN)
+ Dl_info Info = { NULL, NULL, NULL, NULL };
+ int rc = dladdr((const void *)uPc, &Info);
+ if (rc != 0 && Info.dli_fname)
+ {
+ pMod = (PRTDBGSTACKSELFMOD)RTMemAllocZ(sizeof(*pMod));
+ if (pMod)
+ {
+ /** @todo better filename translation... */
+ rc = RTStrCopy(pMod->szFilename, sizeof(pMod->szFilename), Info.dli_fname);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrPurgeEncoding(pMod->szFilename);
+
+ const char *pszFilename = RTPathFilename(pMod->szFilename);
+ pMod->offName = pszFilename ? pszFilename - &pMod->szFilename[0] : 0;
+ pMod->uMapping = (uintptr_t)Info.dli_fbase;
+ pMod->cbMapping = 0;
+ pMod->hLdrMod = NIL_RTLDRMOD;
+ pMod->hDbgMod = NIL_RTDBGMOD;
+
+ rc = RTLdrOpen(pMod->szFilename, RTLDR_O_FOR_DEBUG, RTLdrGetHostArch(), &pMod->hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ pMod->cbMapping = RTLdrSize(pMod->hLdrMod);
+
+ /* Try open debug info for the module. */
+ //uint32_t uTimeDateStamp = 0;
+ //RTLdrQueryProp(pMod->hLdrMod, RTLDRPROP_TIMESTAMP_SECONDS, &uTimeDateStamp, sizeof(uTimeDateStamp));
+ //RTDbgModCreateFromImage()??
+ //rc = RTDbgModCreateFromPeImage(&pMod->hDbgMod, pMod->szFilename, &pMod->szFilename[pMod->offName],
+ // &pMod->hLdrMod, (uint32_t)pMod->cbMapping, uTimeDateStamp, NIL_RTDBGCFG);
+
+ RTListPrepend(pCachedModules, &pMod->ListEntry);
+ return pMod;
+ }
+ }
+ RTMemFree(pMod);
+ }
+ }
+#endif
+ return NULL;
+}
+
+
+/**
+ * @callback_method_impl{FNRTLDRENUMSYMS}
+ */
+static DECLCALLBACK(int) rtDbgStackdumpSelfSymbolSearchCallback(RTLDRMOD hLdrMod, const char *pszSymbol,
+ unsigned uSymbol, RTLDRADDR Value, void *pvUser)
+{
+ PRTDBGSTACKSELFSYMSEARCH pSearch = (PRTDBGSTACKSELFSYMSEARCH)pvUser;
+ if (Value >= pSearch->uSearch)
+ {
+ uintptr_t const offDist = (uintptr_t)Value - pSearch->uSearch;
+ if (offDist < pSearch->offBestDist)
+ {
+ pSearch->offBestDist = offDist;
+
+ PRTDBGSYMBOL pSymInfo = pSearch->pSymInfo;
+ pSymInfo->Value = Value;
+ pSymInfo->offSeg = Value;
+ pSymInfo->iSeg = RTDBGSEGIDX_ABS;
+ pSymInfo->iOrdinal = uSymbol;
+ pSymInfo->fFlags = 0;
+ if (pszSymbol)
+ RTStrCopy(pSymInfo->szName, sizeof(pSymInfo->szName), pszSymbol);
+ else
+ RTStrPrintf(pSymInfo->szName, sizeof(pSymInfo->szName), "Ordinal#%u", uSymbol);
+
+ if (offDist < 8)
+ return VINF_CALLBACK_RETURN;
+ }
+ }
+ RT_NOREF(hLdrMod);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Searches for a symbol close to @a uRva.
+ *
+ * @returns true if found, false if not.
+ * @param pMod The module cache entry to search in.
+ * @param uRva The RVA to locate a symbol for.
+ * @param poffDisp Where to return the distance between uRva and the returned symbol.
+ * @param pSymInfo Where to return the symbol information.
+ */
+static bool rtDbgStackDumpSelfQuerySymbol(PRTDBGSTACKSELFMOD pMod, uintptr_t uRva, PRTINTPTR poffDisp, PRTDBGSYMBOL pSymInfo)
+{
+ if (pMod->hDbgMod != NIL_RTDBGMOD)
+ {
+ int rc = RTDbgModSymbolByAddr(pMod->hDbgMod, RTDBGSEGIDX_RVA, uRva, RTDBGSYMADDR_FLAGS_LESS_OR_EQUAL,
+ poffDisp, pSymInfo);
+ if (RT_SUCCESS(rc))
+ return true;
+ }
+
+ if (pMod->hLdrMod != NIL_RTLDRMOD)
+ {
+ RTDBGSTACKSELFSYMSEARCH SearchInfo = { pMod->uMapping + uRva, ~(uintptr_t)0, pSymInfo };
+ int rc = RTLdrEnumSymbols(pMod->hLdrMod, 0, (const void *)pMod->uMapping, pMod->uMapping,
+ rtDbgStackdumpSelfSymbolSearchCallback, &SearchInfo);
+ if (RT_SUCCESS(rc) && SearchInfo.offBestDist != ~(uintptr_t)0)
+ {
+ *poffDisp = SearchInfo.offBestDist;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Does the grunt work for RTDbgStackDumpSelf.
+ *
+ * Called thru an assembly wrapper that collects the necessary register state.
+ *
+ * @returns Length of the string returned in pszStack.
+ * @param pszStack Where to output the stack dump.
+ * @param cbStack The size of the @a pszStack buffer.
+ * @param fFlags Flags, MBZ.
+ * @param pauRegs Register state. For AMD64 and x86 this starts with the
+ * PC and us followed by the general purpose registers.
+ */
+DECL_HIDDEN_CALLBACK(size_t) rtDbgStackDumpSelfWorker(char *pszStack, size_t cbStack, uint32_t fFlags, PCRTCCUINTREG pauRegs)
+{
+ RT_NOREF(fFlags);
+
+ /*
+ * Initialize the unwind state.
+ */
+ RTDBGUNWINDSTATE UnwindState;
+ RT_ZERO(UnwindState);
+
+ UnwindState.u32Magic = RTDBGUNWINDSTATE_MAGIC;
+ UnwindState.pfnReadStack = rtDbgStackDumpSelfReader;
+#ifdef RT_ARCH_AMD64
+ UnwindState.enmArch = RTLDRARCH_AMD64;
+ UnwindState.uPc = pauRegs[0];
+ UnwindState.enmRetType = RTDBGRETURNTYPE_NEAR64;
+ for (unsigned i = 0; i < 16; i++)
+ UnwindState.u.x86.auRegs[i] = pauRegs[i + 1];
+#elif defined(RT_ARCH_X86)
+ UnwindState.enmArch = RTLDRARCH_X86_32;
+ UnwindState.uPc = pauRegs[0];
+ UnwindState.enmRetType = RTDBGRETURNTYPE_NEAR32;
+ for (unsigned i = 0; i < 8; i++)
+ UnwindState.u.x86.auRegs[i] = pauRegs[i + 1];
+#else
+# error "PORTME"
+#endif
+
+ /*
+ * We cache modules.
+ */
+ PRTDBGSTACKSELFMOD pCurModule = NULL;
+ RTLISTANCHOR CachedModules;
+ RTListInit(&CachedModules);
+
+ /*
+ * Work the stack.
+ */
+ size_t offDst = 0;
+ while (offDst + 64 < cbStack)
+ {
+ /* Try locate the module containing the current PC. */
+ if ( !pCurModule
+ || UnwindState.uPc - pCurModule->uMapping >= pCurModule->cbMapping)
+ pCurModule = rtDbgStackDumpSelfQueryModForPC(UnwindState.uPc, &CachedModules);
+ bool fManualUnwind = true;
+ if (!pCurModule)
+ offDst += RTStrPrintf(&pszStack[offDst], cbStack - offDst, "%p\n", UnwindState.uPc);
+ else
+ {
+ uintptr_t const uRva = UnwindState.uPc - pCurModule->uMapping;
+
+ /*
+ * Add a call stack entry with the symbol if we can.
+ */
+ union
+ {
+ RTDBGSYMBOL SymbolInfo;
+ RTDBGLINE LineInfo;
+ } uBuf;
+ RTINTPTR offDisp = 0;
+ if (!rtDbgStackDumpSelfQuerySymbol(pCurModule, uRva, &offDisp, &uBuf.SymbolInfo))
+ offDst += RTStrPrintf(&pszStack[offDst], cbStack - offDst, "%p %s + %#zx\n",
+ UnwindState.uPc, &pCurModule->szFilename[pCurModule->offName], (size_t)uRva);
+ else if (offDisp == 0)
+ offDst += RTStrPrintf(&pszStack[offDst], cbStack - offDst, "%p %s!%s (rva:%#zx)\n", UnwindState.uPc,
+ &pCurModule->szFilename[pCurModule->offName], uBuf.SymbolInfo.szName, (size_t)uRva);
+ else
+ offDst += RTStrPrintf(&pszStack[offDst], cbStack - offDst, "%p %s!%s%c%#zx (rva:%#zx)\n",
+ UnwindState.uPc, &pCurModule->szFilename[pCurModule->offName], uBuf.SymbolInfo.szName,
+ offDisp >= 0 ? '+' : '-', (size_t)RT_ABS(offDisp), (size_t)uRva);
+
+ /*
+ * Try supply the line number.
+ */
+ if (pCurModule->hDbgMod != NIL_RTDBGMOD)
+ {
+ offDisp = 0;
+ int rc = RTDbgModLineByAddr(pCurModule->hDbgMod, RTDBGSEGIDX_RVA, uRva, &offDisp, &uBuf.LineInfo);
+ if (RT_SUCCESS(rc) && offDisp)
+ offDst += RTStrPrintf(&pszStack[offDst], cbStack - offDst, " [%s:%u]\n",
+ uBuf.LineInfo.szFilename, uBuf.LineInfo.uLineNo);
+ else if (RT_SUCCESS(rc))
+ offDst += RTStrPrintf(&pszStack[offDst], cbStack - offDst, " [%s:%u (%c%#zx)]\n", uBuf.LineInfo.szFilename,
+ uBuf.LineInfo.uLineNo, offDisp >= 0 ? '+' : '-', (size_t)RT_ABS(offDisp));
+ }
+
+ /*
+ * Try unwind using the module info.
+ */
+ int rc;
+ if (pCurModule->hDbgMod != NIL_RTDBGMOD)
+ rc = RTDbgModUnwindFrame(pCurModule->hDbgMod, RTDBGSEGIDX_RVA, uRva, &UnwindState);
+ else
+ rc = RTLdrUnwindFrame(pCurModule->hLdrMod, (void const *)pCurModule->uMapping, UINT32_MAX, uRva, &UnwindState);
+ if (RT_SUCCESS(rc))
+ fManualUnwind = false;
+ }
+ if (fManualUnwind)
+ {
+ break;
+ }
+ }
+
+ /*
+ * Destroy the cache.
+ */
+ PRTDBGSTACKSELFMOD pNextModule;
+ RTListForEachSafe(&CachedModules, pCurModule, pNextModule, RTDBGSTACKSELFMOD, ListEntry)
+ {
+ if (pCurModule->hDbgMod != NIL_RTDBGMOD)
+ {
+ RTDbgModRelease(pCurModule->hDbgMod);
+ pCurModule->hDbgMod = NIL_RTDBGMOD;
+ }
+ if (pCurModule->hLdrMod != NIL_RTLDRMOD)
+ {
+ RTLdrClose(pCurModule->hLdrMod);
+ pCurModule->hLdrMod = NIL_RTLDRMOD;
+ }
+ RTMemFree(pCurModule);
+ }
+
+ return offDst;
+}
+
diff --git a/src/VBox/Runtime/common/dvm/Makefile.kup b/src/VBox/Runtime/common/dvm/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/dvm/Makefile.kup
diff --git a/src/VBox/Runtime/common/dvm/dvm.cpp b/src/VBox/Runtime/common/dvm/dvm.cpp
new file mode 100644
index 00000000..591e4562
--- /dev/null
+++ b/src/VBox/Runtime/common/dvm/dvm.cpp
@@ -0,0 +1,940 @@
+/* $Id: dvm.cpp $ */
+/** @file
+ * IPRT Disk Volume Management API (DVM) - generic code.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/dvm.h>
+#include <iprt/err.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/list.h>
+#include "internal/dvm.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * The internal volume manager structure.
+ */
+typedef struct RTDVMINTERNAL
+{
+ /** The DVM magic (RTDVM_MAGIC). */
+ uint32_t u32Magic;
+ /** The disk descriptor. */
+ RTDVMDISK DvmDisk;
+ /** Pointer to the backend operations table after a successful probe. */
+ PCRTDVMFMTOPS pDvmFmtOps;
+ /** The format specific volume manager data. */
+ RTDVMFMT hVolMgrFmt;
+ /** Flags passed on manager creation. */
+ uint32_t fFlags;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** List of recognised volumes (RTDVMVOLUMEINTERNAL). */
+ RTLISTANCHOR VolumeList;
+} RTDVMINTERNAL;
+/** Pointer to an internal volume manager. */
+typedef RTDVMINTERNAL *PRTDVMINTERNAL;
+
+/**
+ * The internal volume structure.
+ */
+typedef struct RTDVMVOLUMEINTERNAL
+{
+ /** The DVM volume magic (RTDVMVOLUME_MAGIC). */
+ uint32_t u32Magic;
+ /** Node for the volume list. */
+ RTLISTNODE VolumeNode;
+ /** Pointer to the owning volume manager. */
+ PRTDVMINTERNAL pVolMgr;
+ /** Format specific volume data. */
+ RTDVMVOLUMEFMT hVolFmt;
+ /** Set block status.callback */
+ PFNDVMVOLUMEQUERYBLOCKSTATUS pfnQueryBlockStatus;
+ /** Opaque user data. */
+ void *pvUser;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+} RTDVMVOLUMEINTERNAL;
+/** Pointer to an internal volume. */
+typedef RTDVMVOLUMEINTERNAL *PRTDVMVOLUMEINTERNAL;
+
+
+/*********************************************************************************************************************************
+* Global variables *
+*********************************************************************************************************************************/
+/**
+ * Supported volume formats.
+ */
+static PCRTDVMFMTOPS const g_aDvmFmts[] =
+{
+ &g_rtDvmFmtMbr,
+ &g_rtDvmFmtGpt,
+ &g_rtDvmFmtBsdLbl
+};
+
+/**
+ * Descriptions of the volume types.
+ *
+ * This is indexed by RTDVMVOLTYPE.
+ */
+static const char * const g_apszDvmVolTypes[] =
+{
+ "Invalid",
+ "Unknown",
+ "NTFS",
+ "FAT12",
+ "FAT16",
+ "FAT32",
+
+ "EFI system partition",
+
+ "Mac OS X HFS or HFS+",
+ "Mac OS X APFS",
+
+ "Linux swap",
+ "Linux native",
+ "Linux LVM",
+ "Linux SoftRaid",
+
+ "FreeBSD",
+ "NetBSD",
+ "OpenBSD",
+ "Solaris",
+
+ "Basic data partition",
+ "Microsoft reserved partition",
+ "Windows LDM metadata",
+ "Windows LDM data",
+ "Windows recovery partition",
+ "Windows storage spaces",
+
+ "IBM GPFS",
+
+ "OS/2",
+};
+AssertCompile(RT_ELEMENTS(g_apszDvmVolTypes) == RTDVMVOLTYPE_END);
+
+
+/**
+ * Read from the disk at the given offset, neither the offset nor the size is
+ * necessary sector aligned.
+ *
+ * @returns IPRT status code.
+ * @param pDisk The disk descriptor to read from.
+ * @param off Start offset.
+ * @param pvBuf Destination buffer.
+ * @param cbRead How much to read.
+ */
+DECLHIDDEN(int) rtDvmDiskReadUnaligned(PCRTDVMDISK pDisk, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ size_t const cbSector = (size_t)pDisk->cbSector;
+ size_t const offDelta = off % cbSector;
+ size_t const cbDelta = cbRead % cbSector;
+ if (!cbDelta && !offDelta)
+ return rtDvmDiskRead(pDisk, off, pvBuf, cbRead);
+
+ int rc;
+ size_t cbExtra = offDelta + (cbDelta ? cbSector - cbDelta: 0);
+ uint8_t *pbTmpBuf = (uint8_t *)RTMemTmpAlloc(cbRead + cbExtra);
+ if (pbTmpBuf)
+ {
+ rc = rtDvmDiskRead(pDisk, off - offDelta, pbTmpBuf, cbRead + cbExtra);
+ if (RT_SUCCESS(rc))
+ memcpy(pvBuf, &pbTmpBuf[offDelta], cbRead);
+ else
+ RT_BZERO(pvBuf, cbRead);
+ RTMemTmpFree(pbTmpBuf);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Creates a new volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The DVM map instance.
+ * @param hVolFmt The format specific volume handle.
+ * @param phVol Where to store the generic volume handle on success.
+ */
+static int rtDvmVolumeCreate(PRTDVMINTERNAL pThis, RTDVMVOLUMEFMT hVolFmt, PRTDVMVOLUME phVol)
+{
+ PRTDVMVOLUMEINTERNAL pVol = (PRTDVMVOLUMEINTERNAL)RTMemAllocZ(sizeof(RTDVMVOLUMEINTERNAL));
+ if (pVol)
+ {
+ pVol->u32Magic = RTDVMVOLUME_MAGIC;
+ pVol->cRefs = 0;
+ pVol->pVolMgr = pThis;
+ pVol->hVolFmt = hVolFmt;
+
+ *phVol = pVol;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+/**
+ * Destroys a volume handle.
+ *
+ * @param pThis The volume manager instance.
+ * @param pVol The volume to destroy.
+ */
+static void rtDvmVolumeDestroy(PRTDVMINTERNAL pThis, PRTDVMVOLUMEINTERNAL pVol)
+{
+ AssertPtr(pThis);
+ AssertPtr(pThis->pDvmFmtOps);
+ Assert(pVol->pVolMgr == pThis);
+
+ /* Close the volume. */
+ pThis->pDvmFmtOps->pfnVolumeClose(pVol->hVolFmt);
+
+ pVol->u32Magic = RTDVMVOLUME_MAGIC_DEAD;
+ pVol->pVolMgr = NULL;
+ pVol->hVolFmt = NIL_RTDVMVOLUMEFMT;
+ RTMemFree(pVol);
+}
+
+
+RTDECL(int) RTDvmCreate(PRTDVM phVolMgr, RTVFSFILE hVfsFile, uint32_t cbSector, uint32_t fFlags)
+{
+ AssertMsgReturn(!(fFlags & ~DVM_FLAGS_VALID_MASK), ("Invalid flags given %#x\n", fFlags), VERR_INVALID_FLAGS);
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ uint64_t cbDisk;
+ int rc = RTVfsFileQuerySize(hVfsFile, &cbDisk);
+ if (RT_SUCCESS(rc))
+ {
+ PRTDVMINTERNAL pThis = (PRTDVMINTERNAL)RTMemAllocZ(sizeof(RTDVMINTERNAL));
+ if (pThis)
+ {
+ pThis->u32Magic = RTDVM_MAGIC;
+ pThis->DvmDisk.cbDisk = cbDisk;
+ pThis->DvmDisk.cbSector = cbSector;
+ pThis->DvmDisk.hVfsFile = hVfsFile;
+
+ pThis->pDvmFmtOps = NULL;
+ pThis->hVolMgrFmt = NIL_RTDVMFMT;
+ pThis->fFlags = fFlags;
+ pThis->cRefs = 1;
+ RTListInit(&pThis->VolumeList);
+
+ *phVolMgr = pThis;
+ return VINF_SUCCESS;
+ }
+ rc = VERR_NO_MEMORY;
+ }
+ RTVfsFileRelease(hVfsFile);
+ return rc;
+}
+
+
+RTDECL(uint32_t) RTDvmRetain(RTDVM hVolMgr)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ return cRefs;
+}
+
+/**
+ * Destroys a volume manager handle.
+ *
+ * @param pThis The volume manager to destroy.
+ */
+static void rtDvmDestroy(PRTDVMINTERNAL pThis)
+{
+ pThis->u32Magic = RTDVM_MAGIC_DEAD;
+
+ if (pThis->hVolMgrFmt != NIL_RTDVMFMT)
+ {
+ AssertPtr(pThis->pDvmFmtOps);
+
+ /* */
+ PRTDVMVOLUMEINTERNAL pItNext, pIt;
+ RTListForEachSafe(&pThis->VolumeList, pIt, pItNext, RTDVMVOLUMEINTERNAL, VolumeNode)
+ {
+ RTListNodeRemove(&pIt->VolumeNode);
+ rtDvmVolumeDestroy(pThis, pIt);
+ }
+
+ /* Let the backend do it's own cleanup first. */
+ pThis->pDvmFmtOps->pfnClose(pThis->hVolMgrFmt);
+ pThis->hVolMgrFmt = NIL_RTDVMFMT;
+ pThis->pDvmFmtOps = NULL;
+ }
+
+ pThis->DvmDisk.cbDisk = 0;
+ pThis->DvmDisk.cbSector = 0;
+ if (pThis->DvmDisk.hVfsFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pThis->DvmDisk.hVfsFile);
+ pThis->DvmDisk.hVfsFile = NIL_RTVFSFILE;
+ }
+
+ RTMemFree(pThis);
+}
+
+RTDECL(uint32_t) RTDvmRelease(RTDVM hVolMgr)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ if (pThis == NIL_RTDVM)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0)
+ rtDvmDestroy(pThis);
+ return cRefs;
+}
+
+RTDECL(int) RTDvmMapOpen(RTDVM hVolMgr)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->hVolMgrFmt == NIL_RTDVMFMT, VERR_WRONG_ORDER);
+
+ Assert(!pThis->pDvmFmtOps);
+
+ /*
+ * Let each format backend have a go at the disk, pick the one which scores the highest.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t uScoreMax = RTDVM_MATCH_SCORE_UNSUPPORTED;
+ PCRTDVMFMTOPS pDvmFmtOpsMatch = NULL;
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aDvmFmts); i++)
+ {
+ uint32_t uScore = 0;
+ PCRTDVMFMTOPS pDvmFmtOps = g_aDvmFmts[i];
+
+ rc = pDvmFmtOps->pfnProbe(&pThis->DvmDisk, &uScore);
+ if (RT_SUCCESS(rc))
+ {
+ if (uScore > uScoreMax)
+ {
+ pDvmFmtOpsMatch = pDvmFmtOps;
+ uScoreMax = uScore;
+ }
+ }
+ else
+ return rc;
+ }
+ if (uScoreMax > RTDVM_MATCH_SCORE_UNSUPPORTED)
+ {
+ AssertPtr(pDvmFmtOpsMatch);
+
+ /*
+ * Open the format.
+ */
+ rc = pDvmFmtOpsMatch->pfnOpen(&pThis->DvmDisk, &pThis->hVolMgrFmt);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pDvmFmtOps = pDvmFmtOpsMatch;
+
+ /*
+ * Construct volume list (we're done if none).
+ */
+ uint32_t cVols = pThis->pDvmFmtOps->pfnGetValidVolumes(pThis->hVolMgrFmt);
+ if (cVols == 0)
+ return VINF_SUCCESS;
+
+ /* First volume. */
+ RTDVMVOLUMEFMT hVolFmt = NIL_RTDVMVOLUMEFMT;
+ rc = pThis->pDvmFmtOps->pfnQueryFirstVolume(pThis->hVolMgrFmt, &hVolFmt);
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ PRTDVMVOLUMEINTERNAL pVol = NULL;
+ rc = rtDvmVolumeCreate(pThis, hVolFmt, &pVol);
+ if (RT_FAILURE(rc))
+ {
+ pThis->pDvmFmtOps->pfnVolumeClose(hVolFmt);
+ break;
+ }
+ RTListAppend(&pThis->VolumeList, &pVol->VolumeNode);
+
+ /* Done?*/
+ cVols--;
+ if (cVols < 1)
+ return VINF_SUCCESS;
+
+ /* Next volume. */
+ rc = pThis->pDvmFmtOps->pfnQueryNextVolume(pThis->hVolMgrFmt, pVol->hVolFmt, &hVolFmt);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* Bail out. */
+ PRTDVMVOLUMEINTERNAL pItNext, pIt;
+ RTListForEachSafe(&pThis->VolumeList, pIt, pItNext, RTDVMVOLUMEINTERNAL, VolumeNode)
+ {
+ RTListNodeRemove(&pIt->VolumeNode);
+ rtDvmVolumeDestroy(pThis, pIt);
+ }
+ }
+
+ pDvmFmtOpsMatch->pfnClose(pThis->hVolMgrFmt);
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ return rc;
+}
+
+RTDECL(int) RTDvmMapInitialize(RTDVM hVolMgr, const char *pszFmt)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszFmt, VERR_INVALID_POINTER);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->hVolMgrFmt == NIL_RTDVMFMT, VERR_WRONG_ORDER);
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aDvmFmts); i++)
+ {
+ PCRTDVMFMTOPS pDvmFmtOps = g_aDvmFmts[i];
+ if (!RTStrCmp(pDvmFmtOps->pszFmt, pszFmt))
+ {
+ int rc = pDvmFmtOps->pfnInitialize(&pThis->DvmDisk, &pThis->hVolMgrFmt);
+ if (RT_SUCCESS(rc))
+ pThis->pDvmFmtOps = pDvmFmtOps;
+ return rc;
+ }
+ }
+ return VERR_NOT_SUPPORTED;
+}
+
+RTDECL(const char *) RTDvmMapGetFormatName(RTDVM hVolMgr)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, NULL);
+ AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, NULL);
+
+ return pThis->pDvmFmtOps->pszFmt;
+}
+
+RTDECL(RTDVMFORMATTYPE) RTDvmMapGetFormatType(RTDVM hVolMgr)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ AssertPtrReturn(pThis, RTDVMFORMATTYPE_INVALID);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, RTDVMFORMATTYPE_INVALID);
+ AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, RTDVMFORMATTYPE_INVALID);
+
+ return pThis->pDvmFmtOps->enmFormat;
+}
+
+RTDECL(int) RTDvmMapQueryDiskUuid(RTDVM hVolMgr, PRTUUID pUuid)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pUuid, VERR_INVALID_POINTER);
+
+ if (pThis->pDvmFmtOps->pfnQueryDiskUuid)
+ return pThis->pDvmFmtOps->pfnQueryDiskUuid(pThis->hVolMgrFmt, pUuid);
+ return VERR_NOT_SUPPORTED;
+}
+
+RTDECL(uint32_t) RTDvmMapGetValidVolumes(RTDVM hVolMgr)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, UINT32_MAX);
+ AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, UINT32_MAX);
+
+ return pThis->pDvmFmtOps->pfnGetValidVolumes(pThis->hVolMgrFmt);
+}
+
+RTDECL(uint32_t) RTDvmMapGetMaxVolumes(RTDVM hVolMgr)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, UINT32_MAX);
+ AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, UINT32_MAX);
+
+ return pThis->pDvmFmtOps->pfnGetMaxVolumes(pThis->hVolMgrFmt);
+}
+
+RTDECL(int) RTDvmMapQueryFirstVolume(RTDVM hVolMgr, PRTDVMVOLUME phVol)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phVol, VERR_INVALID_POINTER);
+
+ int rc = VERR_DVM_MAP_EMPTY;
+ PRTDVMVOLUMEINTERNAL pVol = RTListGetFirst(&pThis->VolumeList, RTDVMVOLUMEINTERNAL, VolumeNode);
+ if (pVol)
+ {
+ rc = VINF_SUCCESS;
+ RTDvmVolumeRetain(pVol);
+ *phVol = pVol;
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTDvmMapQueryNextVolume(RTDVM hVolMgr, RTDVMVOLUME hVol, PRTDVMVOLUME phVolNext)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+ PRTDVMVOLUMEINTERNAL pVol = hVol;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pVol, VERR_INVALID_HANDLE);
+ AssertReturn(pVol->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phVolNext, VERR_INVALID_POINTER);
+
+ int rc = VERR_DVM_MAP_NO_VOLUME;
+ PRTDVMVOLUMEINTERNAL pVolNext = RTListGetNext(&pThis->VolumeList, pVol, RTDVMVOLUMEINTERNAL, VolumeNode);
+ if (pVolNext)
+ {
+ rc = VINF_SUCCESS;
+ RTDvmVolumeRetain(pVolNext);
+ *phVolNext = pVolNext;
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTDvmMapQueryBlockStatus(RTDVM hVolMgr, uint64_t off, uint64_t cb, bool *pfAllocated)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pfAllocated, VERR_INVALID_POINTER);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->hVolMgrFmt != NIL_RTDVMFMT, VERR_WRONG_ORDER);
+ AssertMsgReturn( off <= pThis->DvmDisk.cbDisk
+ || cb <= pThis->DvmDisk.cbDisk
+ || off + cb <= pThis->DvmDisk.cbDisk,
+ ("off=%#RX64 cb=%#RX64 cbDisk=%#RX64\n", off, cb, pThis->DvmDisk.cbDisk),
+ VERR_OUT_OF_RANGE);
+
+ /*
+ * Check whether the range is inuse by the volume manager metadata first.
+ */
+ int rc = pThis->pDvmFmtOps->pfnQueryRangeUse(pThis->hVolMgrFmt, off, cb, pfAllocated);
+ if (RT_FAILURE(rc) || *pfAllocated)
+ return rc;
+
+ /*
+ * Not used by volume manager metadata, so work thru the specified range one
+ * volume / void (free space) at a time. All must be unallocated for us to
+ * reach the end, we return immediately if any portion is allocated.
+ */
+ while (cb > 0)
+ {
+ /*
+ * Search through all volumes.
+ *
+ * It is not possible to get all start sectors and sizes of all volumes
+ * here because volumes can be scattered around the disk for certain formats.
+ * Linux LVM is one example, it extents of logical volumes don't need to be
+ * contiguous on the medium.
+ */
+ bool fVolFound = false;
+ PRTDVMVOLUMEINTERNAL pVol;
+ RTListForEach(&pThis->VolumeList, pVol, RTDVMVOLUMEINTERNAL, VolumeNode)
+ {
+ uint64_t cbIntersect;
+ uint64_t offVol;
+ bool fIntersect = pThis->pDvmFmtOps->pfnVolumeIsRangeIntersecting(pVol->hVolFmt, off, cb, &offVol, &cbIntersect);
+ if (fIntersect)
+ {
+ fVolFound = true;
+ if (pVol->pfnQueryBlockStatus)
+ {
+ bool fVolAllocated = true;
+ rc = pVol->pfnQueryBlockStatus(pVol->pvUser, offVol, cbIntersect, &fVolAllocated);
+ if (RT_FAILURE(rc) || fVolAllocated)
+ {
+ *pfAllocated = true;
+ return rc;
+ }
+ }
+ else if (!(pThis->fFlags & DVM_FLAGS_NO_STATUS_CALLBACK_MARK_AS_UNUSED))
+ {
+ *pfAllocated = true;
+ return VINF_SUCCESS;
+ }
+ /* else, flag is set, continue. */
+
+ cb -= cbIntersect;
+ off += cbIntersect;
+ break;
+ }
+ }
+
+ if (!fVolFound)
+ {
+ if (pThis->fFlags & DVM_FLAGS_UNUSED_SPACE_MARK_AS_USED)
+ {
+ *pfAllocated = true;
+ return VINF_SUCCESS;
+ }
+
+ cb -= pThis->DvmDisk.cbSector;
+ off += pThis->DvmDisk.cbSector;
+ }
+ }
+
+ *pfAllocated = false;
+ return rc;
+}
+
+RTDECL(int) RTDvmMapQueryTableLocations(RTDVM hVolMgr, uint32_t fFlags,
+ PRTDVMTABLELOCATION paLocations, size_t cLocations, size_t *pcActual)
+{
+ PRTDVMINTERNAL pThis = hVolMgr;
+
+ /*
+ * Input validation.
+ */
+ if (cLocations)
+ {
+ AssertPtrReturn(paLocations, VERR_INVALID_POINTER);
+ if (pcActual)
+ {
+ AssertPtrReturn(pcActual, VERR_INVALID_POINTER);
+ *pcActual = 0;
+ }
+ }
+ else
+ {
+ AssertPtrReturn(pcActual, VERR_INVALID_POINTER);
+ *pcActual = 0;
+ }
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(fFlags & ~RTDVMMAPQTABLOC_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ /*
+ * Pass it down to the format backend.
+ */
+ return pThis->pDvmFmtOps->pfnQueryTableLocations(pThis->hVolMgrFmt, fFlags, paLocations, cLocations, pcActual);
+}
+
+RTDECL(uint32_t) RTDvmVolumeRetain(RTDVMVOLUME hVol)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs >= 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 1)
+ RTDvmRetain(pThis->pVolMgr);
+ return cRefs;
+}
+
+RTDECL(uint32_t) RTDvmVolumeRelease(RTDVMVOLUME hVol)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ if (pThis == NIL_RTDVMVOLUME)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0)
+ {
+ /* Release the volume manager. */
+ pThis->pfnQueryBlockStatus = NULL;
+ RTDvmRelease(pThis->pVolMgr);
+ }
+ return cRefs;
+}
+
+RTDECL(void) RTDvmVolumeSetQueryBlockStatusCallback(RTDVMVOLUME hVol,
+ PFNDVMVOLUMEQUERYBLOCKSTATUS pfnQueryBlockStatus,
+ void *pvUser)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturnVoid(pThis);
+ AssertReturnVoid(pThis->u32Magic == RTDVMVOLUME_MAGIC);
+
+ pThis->pfnQueryBlockStatus = pfnQueryBlockStatus;
+ pThis->pvUser = pvUser;
+}
+
+RTDECL(uint64_t) RTDvmVolumeGetSize(RTDVMVOLUME hVol)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, 0);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, 0);
+
+ return pThis->pVolMgr->pDvmFmtOps->pfnVolumeGetSize(pThis->hVolFmt);
+}
+
+RTDECL(int) RTDvmVolumeQueryName(RTDVMVOLUME hVol, char **ppszVolName)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(ppszVolName, VERR_INVALID_POINTER);
+
+ return pThis->pVolMgr->pDvmFmtOps->pfnVolumeQueryName(pThis->hVolFmt, ppszVolName);
+}
+
+RTDECL(RTDVMVOLTYPE) RTDvmVolumeGetType(RTDVMVOLUME hVol)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, RTDVMVOLTYPE_INVALID);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, RTDVMVOLTYPE_INVALID);
+
+ return pThis->pVolMgr->pDvmFmtOps->pfnVolumeGetType(pThis->hVolFmt);
+}
+
+RTDECL(uint64_t) RTDvmVolumeGetFlags(RTDVMVOLUME hVol)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, UINT64_MAX);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, UINT64_MAX);
+
+ return pThis->pVolMgr->pDvmFmtOps->pfnVolumeGetFlags(pThis->hVolFmt);
+}
+
+RTDECL(int) RTDvmVolumeQueryRange(RTDVMVOLUME hVol, uint64_t *poffStart, uint64_t *poffLast)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(poffStart, VERR_INVALID_POINTER);
+ AssertPtrReturn(poffLast, VERR_INVALID_POINTER);
+
+ return pThis->pVolMgr->pDvmFmtOps->pfnVolumeQueryRange(pThis->hVolFmt, poffStart, poffLast);
+}
+
+RTDECL(int) RTDvmVolumeQueryTableLocation(RTDVMVOLUME hVol, uint64_t *poffTable, uint64_t *pcbTable)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(poffTable, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbTable, VERR_INVALID_POINTER);
+
+ return pThis->pVolMgr->pDvmFmtOps->pfnVolumeQueryTableLocation(pThis->hVolFmt, poffTable, pcbTable);
+}
+
+RTDECL(uint32_t) RTDvmVolumeGetIndex(RTDVMVOLUME hVol, RTDVMVOLIDX enmIndex)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, UINT32_MAX);
+ AssertReturn(enmIndex > RTDVMVOLIDX_INVALID && enmIndex < RTDVMVOLIDX_END, UINT32_MAX);
+
+ if (enmIndex == RTDVMVOLIDX_HOST)
+ {
+#ifdef RT_OS_WINDOWS
+ enmIndex = RTDVMVOLIDX_USER_VISIBLE;
+#elif defined(RT_OS_LINUX) \
+ || defined(RT_OS_FREEBSD) \
+ || defined(RT_OS_NETBSD) \
+ || defined(RT_OS_SOLARIS) \
+ || defined(RT_OS_DARWIN) \
+ || defined(RT_OS_OS2) /*whatever*/
+/* Darwing and freebsd matches the linux algo. Solaris matches linux algo partially, at least, in the part we use. */
+ enmIndex = RTDVMVOLIDX_LINUX;
+#else
+# error "PORTME"
+#endif
+ }
+
+ return pThis->pVolMgr->pDvmFmtOps->pfnVolumeGetIndex(pThis->hVolFmt, enmIndex);
+}
+
+/**
+ * Helper for RTDvmVolumeQueryProp.
+ */
+static void rtDvmReturnInteger(void *pvDst, size_t cbDst, PRTUINT64U pSrc, size_t cbSrc)
+{
+ /* Read the source: */
+ uint64_t uSrc;
+ switch (cbSrc)
+ {
+ case sizeof(uint8_t): uSrc = (uint8_t)pSrc->Words.w0; break;
+ case sizeof(uint16_t): uSrc = pSrc->Words.w0; break;
+ case sizeof(uint32_t): uSrc = pSrc->s.Lo; break;
+ default: AssertFailed(); RT_FALL_THROUGH();
+ case sizeof(uint64_t): uSrc = pSrc->u; break;
+ }
+
+ /* Write the destination: */
+ switch (cbDst)
+ {
+ default: AssertFailed(); RT_FALL_THROUGH();
+ case sizeof(uint8_t): *(uint8_t *)pvDst = (uint8_t)uSrc; break;
+ case sizeof(uint16_t): *(uint16_t *)pvDst = (uint16_t)uSrc; break;
+ case sizeof(uint32_t): *(uint32_t *)pvDst = (uint32_t)uSrc; break;
+ case sizeof(uint64_t): *(uint64_t *)pvDst = uSrc; break;
+ }
+}
+
+RTDECL(int) RTDvmVolumeQueryProp(RTDVMVOLUME hVol, RTDVMVOLPROP enmProperty, void *pvBuf, size_t cbBuf, size_t *pcbBuf)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE);
+ size_t cbBufFallback = 0;
+ if (pcbBuf == NULL)
+ pcbBuf = &cbBufFallback;
+ AssertReturnStmt(enmProperty > RTDVMVOLPROP_INVALID && enmProperty < RTDVMVOLPROP_END, *pcbBuf = 0, VERR_INVALID_FUNCTION);
+
+ switch (enmProperty)
+ {
+ /* 8, 16, 32 or 64 bit sized integers: */
+ case RTDVMVOLPROP_MBR_FIRST_HEAD:
+ case RTDVMVOLPROP_MBR_FIRST_SECTOR:
+ case RTDVMVOLPROP_MBR_LAST_HEAD:
+ case RTDVMVOLPROP_MBR_LAST_SECTOR:
+ case RTDVMVOLPROP_MBR_TYPE:
+ {
+ *pcbBuf = sizeof(uint8_t);
+ AssertReturn( cbBuf == sizeof(uint8_t)
+ || cbBuf == sizeof(uint16_t)
+ || cbBuf == sizeof(uint32_t)
+ || cbBuf == sizeof(uint64_t), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+
+ RTUINT64U Union64 = {0};
+ int rc = pThis->pVolMgr->pDvmFmtOps->pfnVolumeQueryProp(pThis->hVolFmt, enmProperty, &Union64, cbBuf, pcbBuf);
+ rtDvmReturnInteger(pvBuf, cbBuf, &Union64, *pcbBuf);
+ return rc;
+ }
+
+ /* 16, 32 or 64 bit sized integers: */
+ case RTDVMVOLPROP_MBR_FIRST_CYLINDER:
+ case RTDVMVOLPROP_MBR_LAST_CYLINDER:
+ {
+ *pcbBuf = sizeof(uint16_t);
+ AssertReturn( cbBuf == sizeof(uint16_t)
+ || cbBuf == sizeof(uint32_t)
+ || cbBuf == sizeof(uint64_t), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+
+ RTUINT64U Union64 = {0};
+ int rc = pThis->pVolMgr->pDvmFmtOps->pfnVolumeQueryProp(pThis->hVolFmt, enmProperty, &Union64, cbBuf, pcbBuf);
+ rtDvmReturnInteger(pvBuf, cbBuf, &Union64, *pcbBuf);
+ return rc;
+ }
+
+ /* RTUUIDs: */
+ case RTDVMVOLPROP_GPT_TYPE:
+ case RTDVMVOLPROP_GPT_UUID:
+ {
+ *pcbBuf = sizeof(RTUUID);
+ AssertReturn(cbBuf == sizeof(RTUUID), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+
+ RTUUID Uuid = RTUUID_INITIALIZE_NULL;
+ int rc = pThis->pVolMgr->pDvmFmtOps->pfnVolumeQueryProp(pThis->hVolFmt, enmProperty, &Uuid, sizeof(RTUUID), pcbBuf);
+ memcpy(pvBuf, &Uuid, sizeof(Uuid));
+ return rc;
+ }
+
+ case RTDVMVOLPROP_INVALID:
+ case RTDVMVOLPROP_END:
+ case RTDVMVOLPROP_32BIT_HACK:
+ break;
+ /* No default case! */
+ }
+ AssertFailed();
+ return VERR_NOT_SUPPORTED;
+}
+
+RTDECL(uint64_t) RTDvmVolumeGetPropU64(RTDVMVOLUME hVol, RTDVMVOLPROP enmProperty, uint64_t uDefault)
+{
+ uint64_t uValue = uDefault;
+ int rc = RTDvmVolumeQueryProp(hVol, enmProperty, &uValue, sizeof(uValue), NULL);
+ if (RT_SUCCESS(rc))
+ return uValue;
+ AssertMsg(rc == VERR_NOT_SUPPORTED || rc == VERR_NOT_FOUND, ("%Rrc enmProperty=%d\n", rc, enmProperty));
+ return uDefault;
+}
+
+RTDECL(int) RTDvmVolumeRead(RTDVMVOLUME hVol, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbRead > 0, VERR_INVALID_PARAMETER);
+
+ return pThis->pVolMgr->pDvmFmtOps->pfnVolumeRead(pThis->hVolFmt, off, pvBuf, cbRead);
+}
+
+RTDECL(int) RTDvmVolumeWrite(RTDVMVOLUME hVol, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ PRTDVMVOLUMEINTERNAL pThis = hVol;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTDVMVOLUME_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbWrite > 0, VERR_INVALID_PARAMETER);
+
+ return pThis->pVolMgr->pDvmFmtOps->pfnVolumeWrite(pThis->hVolFmt, off, pvBuf, cbWrite);
+}
+
+RTDECL(const char *) RTDvmVolumeTypeGetDescr(RTDVMVOLTYPE enmVolType)
+{
+ AssertReturn(enmVolType >= RTDVMVOLTYPE_INVALID && enmVolType < RTDVMVOLTYPE_END, NULL);
+
+ return g_apszDvmVolTypes[enmVolType];
+}
+
diff --git a/src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp b/src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp
new file mode 100644
index 00000000..32b5a740
--- /dev/null
+++ b/src/VBox/Runtime/common/dvm/dvmbsdlabel.cpp
@@ -0,0 +1,707 @@
+/* $Id: dvmbsdlabel.cpp $ */
+/** @file
+ * IPRT Disk Volume Management API (DVM) - BSD disklabel format backend.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/dvm.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include "internal/dvm.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/*
+ * Below are the on disk structures of a bsd disklabel as found in
+ * /usr/include/sys/disklabel.h from a FreeBSD system.
+ *
+ * Everything is stored in little endian on the disk.
+ */
+
+/** BSD disklabel magic. */
+#define RTDVM_BSDLBL_MAGIC UINT32_C(0x82564557)
+/** Maximum number of partitions in the label. */
+#define RTDVM_BSDLBL_MAX_PARTITIONS 8
+
+/**
+ * A BSD disk label partition.
+ */
+typedef struct BsdLabelPartition
+{
+ /** Number of sectors in the partition. */
+ uint32_t cSectors;
+ /** Start sector. */
+ uint32_t offSectorStart;
+ /** Filesystem fragment size. */
+ uint32_t cbFsFragment;
+ /** Filesystem type. */
+ uint8_t bFsType;
+ /** Filesystem fragments per block. */
+ uint8_t cFsFragmentsPerBlock;
+ /** Filesystem cylinders per group. */
+ uint16_t cFsCylPerGroup;
+} BsdLabelPartition;
+AssertCompileSize(BsdLabelPartition, 16);
+/** Pointer to a BSD disklabel partition structure. */
+typedef BsdLabelPartition *PBsdLabelPartition;
+
+/**
+ * On disk BSD label structure.
+ */
+typedef struct BsdLabel
+{
+ /** Magic identifying the BSD disk label. */
+ uint32_t u32Magic;
+ /** Drive type */
+ uint16_t u16DriveType;
+ /** Subtype depending on the drive type above. */
+ uint16_t u16SubType;
+ /** Type name. */
+ uint8_t abTypeName[16];
+ /** Pack identifier. */
+ uint8_t abPackName[16];
+ /** Number of bytes per sector. */
+ uint32_t cbSector;
+ /** Number of sectors per track. */
+ uint32_t cSectorsPerTrack;
+ /** Number of tracks per cylinder. */
+ uint32_t cTracksPerCylinder;
+ /** Number of data cylinders pre unit. */
+ uint32_t cDataCylindersPerUnit;
+ /** Number of data sectors per cylinder. */
+ uint32_t cDataSectorsPerCylinder;
+ /** Number of data sectors per unit (unit as in disk drive?). */
+ uint32_t cSectorsPerUnit;
+ /** Number of spare sectors per track. */
+ uint16_t cSpareSectorsPerTrack;
+ /** Number of spare sectors per cylinder. */
+ uint16_t cSpareSectorsPerCylinder;
+ /** Number of alternate cylinders per unit. */
+ uint32_t cSpareCylindersPerUnit;
+ /** Rotational speed of the disk drive in rotations per minute. */
+ uint16_t cRotationsPerMinute;
+ /** Sector interleave. */
+ uint16_t uSectorInterleave;
+ /** Sector 0 skew, per track. */
+ uint16_t uSectorSkewPerTrack;
+ /** Sector 0 skew, per cylinder. */
+ uint16_t uSectorSkewPerCylinder;
+ /** Head switch time in us. */
+ uint32_t usHeadSwitch;
+ /** Time of a track-to-track seek in us. */
+ uint32_t usTrackSeek;
+ /** Flags. */
+ uint32_t fFlags;
+ /** Drive type sepcific information. */
+ uint32_t au32DriveData[5];
+ /** Reserved. */
+ uint32_t au32Reserved[5];
+ /** The magic number again. */
+ uint32_t u32Magic2;
+ /** Checksum (xor of the whole structure). */
+ uint16_t u16ChkSum;
+ /** Number of partitions in the array. */
+ uint16_t cPartitions;
+ /** Boot area size in bytes. */
+ uint32_t cbBootArea;
+ /** Maximum size of the filesystem super block. */
+ uint32_t cbFsSuperBlock;
+ /** The partition array. */
+ BsdLabelPartition aPartitions[RTDVM_BSDLBL_MAX_PARTITIONS];
+} BsdLabel;
+AssertCompileSize(BsdLabel, 148 + RTDVM_BSDLBL_MAX_PARTITIONS * 16);
+/** Pointer to a BSD disklabel structure. */
+typedef BsdLabel *PBsdLabel;
+
+/**
+ * BSD disk label volume manager data.
+ */
+typedef struct RTDVMFMTINTERNAL
+{
+ /** Pointer to the underlying disk. */
+ PCRTDVMDISK pDisk;
+ /** Number of used partitions. */
+ uint32_t cPartitions;
+ /** Saved BSD disklabel structure. */
+ BsdLabel DiskLabel;
+} RTDVMFMTINTERNAL;
+/** Pointer to the MBR volume manager. */
+typedef RTDVMFMTINTERNAL *PRTDVMFMTINTERNAL;
+
+/**
+ * MBR volume data.
+ */
+typedef struct RTDVMVOLUMEFMTINTERNAL
+{
+ /** Pointer to the volume manager. */
+ PRTDVMFMTINTERNAL pVolMgr;
+ /** Partition table entry index. */
+ uint32_t idxEntry;
+ /** Start offset of the volume. */
+ uint64_t offStart;
+ /** Size of the volume. */
+ uint64_t cbVolume;
+ /** Pointer to the raw partition table entry. */
+ PBsdLabelPartition pBsdPartitionEntry;
+} RTDVMVOLUMEFMTINTERNAL;
+/** Pointer to an MBR volume. */
+typedef RTDVMVOLUMEFMTINTERNAL *PRTDVMVOLUMEFMTINTERNAL;
+
+/** Converts a LBA number to the byte offset. */
+#define RTDVM_BSDLBL_LBA2BYTE(lba, disk) ((lba) * (disk)->cbSector)
+/** Converts a Byte offset to the LBA number. */
+#define RTDVM_BSDLBL_BYTE2LBA(lba, disk) ((lba) / (disk)->cbSector)
+
+/**
+ * Calculates the checksum of the entire bsd disklabel structure.
+ *
+ * @returns The checksum.
+ * @param pBsdLabel BSD disklabel to get the checksum for.
+ */
+static uint16_t rtDvmFmtBsdLblDiskLabelChkSum(PBsdLabel pBsdLabel)
+{
+ uint16_t uChkSum = 0;
+ uint16_t *pCurr = (uint16_t *)pBsdLabel;
+ uint16_t *pEnd = (uint16_t *)&pBsdLabel->aPartitions[pBsdLabel->cPartitions];
+
+ while (pCurr < pEnd)
+ uChkSum ^= *pCurr++;
+
+ return uChkSum;
+}
+
+/**
+ * Converts a partition entry to the host endianness.
+ *
+ * @param pPartition The partition to decode.
+ */
+static void rtDvmFmtBsdLblDiskLabelDecodePartition(PBsdLabelPartition pPartition)
+{
+ pPartition->cSectors = RT_LE2H_U32(pPartition->cSectors);
+ pPartition->offSectorStart = RT_LE2H_U32(pPartition->offSectorStart);
+ pPartition->cbFsFragment = RT_LE2H_U32(pPartition->cbFsFragment);
+ pPartition->cFsCylPerGroup = RT_LE2H_U16(pPartition->cFsCylPerGroup);
+}
+
+/**
+ * Converts the on disk BSD label to the host endianness.
+ *
+ * @returns Whether the given label structure is a valid BSD disklabel.
+ * @param pBsdLabel Pointer to the BSD disklabel to decode.
+ */
+static bool rtDvmFmtBsdLblDiskLabelDecode(PBsdLabel pBsdLabel)
+{
+ pBsdLabel->u32Magic = RT_LE2H_U32(pBsdLabel->u32Magic);
+ pBsdLabel->u16DriveType = RT_LE2H_U16(pBsdLabel->u16DriveType);
+ pBsdLabel->u16SubType = RT_LE2H_U16(pBsdLabel->u16SubType);
+ pBsdLabel->cbSector = RT_LE2H_U32(pBsdLabel->cbSector);
+ pBsdLabel->cSectorsPerTrack = RT_LE2H_U32(pBsdLabel->cSectorsPerTrack);
+ pBsdLabel->cTracksPerCylinder = RT_LE2H_U32(pBsdLabel->cTracksPerCylinder);
+ pBsdLabel->cDataCylindersPerUnit = RT_LE2H_U32(pBsdLabel->cDataCylindersPerUnit);
+ pBsdLabel->cDataSectorsPerCylinder = RT_LE2H_U32(pBsdLabel->cDataSectorsPerCylinder);
+ pBsdLabel->cSectorsPerUnit = RT_LE2H_U32(pBsdLabel->cSectorsPerUnit);
+ pBsdLabel->cSpareSectorsPerTrack = RT_LE2H_U16(pBsdLabel->cSpareSectorsPerTrack);
+ pBsdLabel->cSpareSectorsPerCylinder = RT_LE2H_U16(pBsdLabel->cSpareSectorsPerCylinder);
+ pBsdLabel->cSpareCylindersPerUnit = RT_LE2H_U32(pBsdLabel->cSpareCylindersPerUnit);
+ pBsdLabel->cRotationsPerMinute = RT_LE2H_U16(pBsdLabel->cRotationsPerMinute);
+ pBsdLabel->uSectorInterleave = RT_LE2H_U16(pBsdLabel->uSectorInterleave);
+ pBsdLabel->uSectorSkewPerTrack = RT_LE2H_U16(pBsdLabel->uSectorSkewPerTrack);
+ pBsdLabel->uSectorSkewPerCylinder = RT_LE2H_U16(pBsdLabel->uSectorSkewPerCylinder);
+ pBsdLabel->usHeadSwitch = RT_LE2H_U16(pBsdLabel->usHeadSwitch);
+ pBsdLabel->usTrackSeek = RT_LE2H_U16(pBsdLabel->usTrackSeek);
+ pBsdLabel->fFlags = RT_LE2H_U32(pBsdLabel->fFlags);
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pBsdLabel->au32DriveData); i++)
+ pBsdLabel->au32DriveData[i] = RT_LE2H_U32(pBsdLabel->au32DriveData[i]);
+ for (unsigned i = 0; i < RT_ELEMENTS(pBsdLabel->au32Reserved); i++)
+ pBsdLabel->au32Reserved[i] = RT_LE2H_U32(pBsdLabel->au32Reserved[i]);
+
+ pBsdLabel->u32Magic2 = RT_LE2H_U32(pBsdLabel->u32Magic2);
+ pBsdLabel->u16ChkSum = RT_LE2H_U16(pBsdLabel->u16ChkSum);
+ pBsdLabel->cPartitions = RT_LE2H_U16(pBsdLabel->cPartitions);
+ pBsdLabel->cbBootArea = RT_LE2H_U32(pBsdLabel->cbBootArea);
+ pBsdLabel->cbFsSuperBlock = RT_LE2H_U32(pBsdLabel->cbFsSuperBlock);
+
+ /* Check the magics now. */
+ if ( pBsdLabel->u32Magic != RTDVM_BSDLBL_MAGIC
+ || pBsdLabel->u32Magic2 != RTDVM_BSDLBL_MAGIC
+ || pBsdLabel->cPartitions != RTDVM_BSDLBL_MAX_PARTITIONS)
+ return false;
+
+ /* Convert the partitions array. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pBsdLabel->aPartitions); i++)
+ rtDvmFmtBsdLblDiskLabelDecodePartition(&pBsdLabel->aPartitions[i]);
+
+ /* Check the checksum now. */
+ uint16_t u16ChkSumSaved = pBsdLabel->u16ChkSum;
+
+ pBsdLabel->u16ChkSum = 0;
+ if (u16ChkSumSaved != rtDvmFmtBsdLblDiskLabelChkSum(pBsdLabel))
+ return false;
+
+ pBsdLabel->u16ChkSum = u16ChkSumSaved;
+ return true;
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblProbe(PCRTDVMDISK pDisk, uint32_t *puScore)
+{
+ BsdLabel DiskLabel;
+ int rc = VINF_SUCCESS;
+
+ *puScore = RTDVM_MATCH_SCORE_UNSUPPORTED;
+
+ if (pDisk->cbDisk >= sizeof(BsdLabel))
+ {
+ /* Read from the disk and check for the disk label structure. */
+ rc = rtDvmDiskReadUnaligned(pDisk, RTDVM_BSDLBL_LBA2BYTE(1, pDisk), &DiskLabel, sizeof(BsdLabel));
+ if ( RT_SUCCESS(rc)
+ && rtDvmFmtBsdLblDiskLabelDecode(&DiskLabel))
+ *puScore = RTDVM_MATCH_SCORE_PERFECT;
+ }
+ return rc;
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblOpen(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt)
+{
+ int rc = VINF_SUCCESS;
+ PRTDVMFMTINTERNAL pThis = NULL;
+
+ pThis = (PRTDVMFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMFMTINTERNAL));
+ if (pThis)
+ {
+ pThis->pDisk = pDisk;
+ pThis->cPartitions = 0;
+
+ /* Read from the disk and check for the disk label structure. */
+ rc = rtDvmDiskReadUnaligned(pDisk, RTDVM_BSDLBL_LBA2BYTE(1, pDisk), &pThis->DiskLabel, sizeof(BsdLabel));
+ if ( RT_SUCCESS(rc)
+ && rtDvmFmtBsdLblDiskLabelDecode(&pThis->DiskLabel))
+ {
+ /* Count number of used entries. */
+ for (unsigned i = 0; i < pThis->DiskLabel.cPartitions; i++)
+ if (pThis->DiskLabel.aPartitions[i].cSectors)
+ pThis->cPartitions++;
+
+ *phVolMgrFmt = pThis;
+ }
+ else
+ {
+ RTMemFree(pThis);
+ rc = VERR_INVALID_MAGIC;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblInitialize(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt)
+{
+ NOREF(pDisk); NOREF(phVolMgrFmt);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static DECLCALLBACK(void) rtDvmFmtBsdLblClose(RTDVMFMT hVolMgrFmt)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ pThis->pDisk = NULL;
+ pThis->cPartitions = 0;
+ RT_ZERO(pThis->DiskLabel);
+ RTMemFree(pThis);
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblQueryRangeUse(RTDVMFMT hVolMgrFmt,
+ uint64_t off, uint64_t cbRange,
+ bool *pfUsed)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ NOREF(cbRange);
+
+ if (off <= RTDVM_BSDLBL_LBA2BYTE(1, pThis->pDisk))
+ *pfUsed = true;
+ else
+ *pfUsed = false;
+
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(uint32_t) rtDvmFmtBsdLblGetValidVolumes(RTDVMFMT hVolMgrFmt)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+ return pThis->cPartitions;
+}
+
+static DECLCALLBACK(uint32_t) rtDvmFmtBsdLblGetMaxVolumes(RTDVMFMT hVolMgrFmt)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+ return pThis->DiskLabel.cPartitions;
+}
+
+/**
+ * Creates a new volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The MBR volume manager data.
+ * @param pbBsdLblEntry The raw MBR entry data.
+ * @param idx The index in the partition table.
+ * @param phVolFmt Where to store the volume data on success.
+ */
+static int rtDvmFmtBsdLblVolumeCreate(PRTDVMFMTINTERNAL pThis, PBsdLabelPartition pBsdPartitionEntry,
+ uint32_t idx, PRTDVMVOLUMEFMT phVolFmt)
+{
+ int rc = VINF_SUCCESS;
+ PRTDVMVOLUMEFMTINTERNAL pVol = (PRTDVMVOLUMEFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMVOLUMEFMTINTERNAL));
+
+ if (pVol)
+ {
+ pVol->pVolMgr = pThis;
+ pVol->idxEntry = idx;
+ pVol->pBsdPartitionEntry = pBsdPartitionEntry;
+ pVol->offStart = (uint64_t)pBsdPartitionEntry->offSectorStart * pThis->DiskLabel.cbSector;
+ pVol->cbVolume = (uint64_t)pBsdPartitionEntry->cSectors * pThis->DiskLabel.cbSector;
+
+ *phVolFmt = pVol;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblQueryFirstVolume(RTDVMFMT hVolMgrFmt, PRTDVMVOLUMEFMT phVolFmt)
+{
+ int rc = VINF_SUCCESS;
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ if (pThis->cPartitions != 0)
+ {
+ /* Search for the first non empty entry. */
+ for (unsigned i = 0; i < pThis->DiskLabel.cPartitions; i++)
+ {
+ if (pThis->DiskLabel.aPartitions[i].cSectors)
+ {
+ rc = rtDvmFmtBsdLblVolumeCreate(pThis, &pThis->DiskLabel.aPartitions[i], i, phVolFmt);
+ break;
+ }
+ }
+ }
+ else
+ rc = VERR_DVM_MAP_EMPTY;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblQueryNextVolume(RTDVMFMT hVolMgrFmt, RTDVMVOLUMEFMT hVolFmt, PRTDVMVOLUMEFMT phVolFmtNext)
+{
+ int rc = VERR_DVM_MAP_NO_VOLUME;
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ PBsdLabelPartition pBsdPartitionEntry = pVol->pBsdPartitionEntry + 1;
+
+ for (unsigned i = pVol->idxEntry + 1; i < pThis->DiskLabel.cPartitions; i++)
+ {
+ if (pBsdPartitionEntry->cSectors)
+ {
+ rc = rtDvmFmtBsdLblVolumeCreate(pThis, pBsdPartitionEntry, i, phVolFmtNext);
+ break;
+ }
+ pBsdPartitionEntry++;
+ }
+
+ return rc;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnQueryTableLocations */
+static DECLCALLBACK(int) rtDvmFmtBsdLblQueryTableLocations(RTDVMFMT hVolMgrFmt, uint32_t fFlags, PRTDVMTABLELOCATION paLocations,
+ size_t cLocations, size_t *pcActual)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ /*
+ * The MBR if requested.
+ */
+ int rc = VINF_SUCCESS;
+ size_t iLoc = 0;
+ if (fFlags & RTDVMMAPQTABLOC_F_INCLUDE_LEGACY)
+ {
+ if (cLocations > 0)
+ {
+ paLocations[iLoc].off = 0;
+ paLocations[iLoc].cb = RTDVM_BSDLBL_LBA2BYTE(1, pThis->pDisk);
+ paLocations[iLoc].cbPadding = 0;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ iLoc++;
+ }
+
+ /*
+ * The BSD lable.
+ */
+ if (cLocations > iLoc)
+ {
+ paLocations[iLoc].off = RTDVM_BSDLBL_LBA2BYTE(1, pThis->pDisk);
+ paLocations[iLoc].cb = (sizeof(BsdLabel) + pThis->pDisk->cbSector - 1) / pThis->pDisk->cbSector * pThis->pDisk->cbSector;
+
+ uint32_t offFirstSector = pThis->pDisk->cbDisk / pThis->pDisk->cbSector;
+ for (unsigned i = 0; i < pThis->DiskLabel.cPartitions; i++)
+ if ( pThis->DiskLabel.aPartitions[i].cSectors
+ && pThis->DiskLabel.aPartitions[i].offSectorStart < offFirstSector)
+ offFirstSector = pThis->DiskLabel.aPartitions[i].offSectorStart;
+
+ uint64_t offEnd = paLocations[iLoc].off + paLocations[iLoc].cb;
+ paLocations[iLoc].cbPadding = (uint64_t)offFirstSector * pThis->DiskLabel.cbSector;
+ if (paLocations[iLoc].cbPadding > offEnd)
+ paLocations[iLoc].cbPadding -= offEnd;
+ else
+ AssertFailedStmt(paLocations[iLoc].cbPadding = 0);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ iLoc++;
+
+ /*
+ * Return values.
+ */
+ if (pcActual)
+ *pcActual = iLoc;
+ else if (cLocations != iLoc && RT_SUCCESS(rc))
+ {
+ RT_BZERO(&paLocations[iLoc], (cLocations - iLoc) * sizeof(paLocations[0]));
+ rc = VERR_BUFFER_UNDERFLOW;
+ }
+ return rc;
+}
+
+static DECLCALLBACK(void) rtDvmFmtBsdLblVolumeClose(RTDVMVOLUMEFMT hVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ pVol->pVolMgr = NULL;
+ pVol->offStart = 0;
+ pVol->cbVolume = 0;
+ pVol->pBsdPartitionEntry = NULL;
+
+ RTMemFree(pVol);
+}
+
+static DECLCALLBACK(uint64_t) rtDvmFmtBsdLblVolumeGetSize(RTDVMVOLUMEFMT hVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ return pVol->cbVolume;
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblVolumeQueryName(RTDVMVOLUMEFMT hVolFmt, char **ppszVolName)
+{
+ NOREF(hVolFmt); NOREF(ppszVolName);
+ return VERR_NOT_SUPPORTED;
+}
+
+static DECLCALLBACK(RTDVMVOLTYPE) rtDvmFmtBsdLblVolumeGetType(RTDVMVOLUMEFMT hVolFmt)
+{
+ NOREF(hVolFmt);
+ return RTDVMVOLTYPE_UNKNOWN;
+}
+
+static DECLCALLBACK(uint64_t) rtDvmFmtBsdLblVolumeGetFlags(RTDVMVOLUMEFMT hVolFmt)
+{
+ NOREF(hVolFmt);
+ return DVMVOLUME_F_CONTIGUOUS;
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblVolumeQueryRange(RTDVMVOLUMEFMT hVolFmt, uint64_t *poffStart, uint64_t *poffLast)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ *poffStart = pVol->offStart;
+ *poffLast = pVol->offStart + pVol->cbVolume - 1;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(bool) rtDvmFmtBsdLblVolumeIsRangeIntersecting(RTDVMVOLUMEFMT hVolFmt,
+ uint64_t offStart, size_t cbRange,
+ uint64_t *poffVol,
+ uint64_t *pcbIntersect)
+{
+ bool fIntersect = false;
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ if (RTDVM_RANGE_IS_INTERSECTING(pVol->offStart, pVol->cbVolume, offStart))
+ {
+ fIntersect = true;
+ *poffVol = offStart - pVol->offStart;
+ *pcbIntersect = RT_MIN(cbRange, pVol->offStart + pVol->cbVolume - offStart);
+ }
+
+ return fIntersect;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnVolumeQueryTableLocation */
+static DECLCALLBACK(int) rtDvmFmtBsdLblVolumeQueryTableLocation(RTDVMVOLUMEFMT hVolFmt, uint64_t *poffTable, uint64_t *pcbTable)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ *poffTable = RTDVM_BSDLBL_LBA2BYTE(1, pVol->pVolMgr->pDisk);
+ *pcbTable = RT_ALIGN_Z(sizeof(BsdLabel), pVol->pVolMgr->pDisk->cbSector);
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnVolumeGetIndex */
+static DECLCALLBACK(uint32_t) rtDvmFmtBsdLblVolumeGetIndex(RTDVMVOLUMEFMT hVolFmt, RTDVMVOLIDX enmIndex)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ switch (enmIndex)
+ {
+ case RTDVMVOLIDX_USER_VISIBLE:
+ case RTDVMVOLIDX_ALL:
+ case RTDVMVOLIDX_LINUX:
+ return pVol->idxEntry + 1;
+ case RTDVMVOLIDX_IN_TABLE:
+ return pVol->idxEntry;
+
+ case RTDVMVOLIDX_INVALID:
+ case RTDVMVOLIDX_HOST:
+ case RTDVMVOLIDX_END:
+ case RTDVMVOLIDX_32BIT_HACK:
+ break;
+ /* no default! */
+ }
+ AssertFailed();
+ return UINT32_MAX;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnVolumeQueryProp */
+static DECLCALLBACK(int) rtDvmFmtBsdLblVolumeQueryProp(RTDVMVOLUMEFMT hVolFmt, RTDVMVOLPROP enmProperty,
+ void *pvBuf, size_t cbBuf, size_t *pcbBuf)
+{
+ switch (enmProperty)
+ {
+ case RTDVMVOLPROP_MBR_FIRST_CYLINDER:
+ case RTDVMVOLPROP_MBR_FIRST_HEAD:
+ case RTDVMVOLPROP_MBR_FIRST_SECTOR:
+ case RTDVMVOLPROP_MBR_LAST_CYLINDER:
+ case RTDVMVOLPROP_MBR_LAST_HEAD:
+ case RTDVMVOLPROP_MBR_LAST_SECTOR:
+ case RTDVMVOLPROP_MBR_TYPE:
+ case RTDVMVOLPROP_GPT_TYPE:
+ case RTDVMVOLPROP_GPT_UUID:
+ return VERR_NOT_SUPPORTED;
+
+ case RTDVMVOLPROP_INVALID:
+ case RTDVMVOLPROP_END:
+ case RTDVMVOLPROP_32BIT_HACK:
+ break;
+ /* no default! */
+ }
+ RT_NOREF(hVolFmt, pvBuf, cbBuf, pcbBuf);
+ return VERR_NOT_SUPPORTED;
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblVolumeRead(RTDVMVOLUMEFMT hVolFmt, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ AssertReturn(off + cbRead <= pVol->cbVolume, VERR_INVALID_PARAMETER);
+
+ return rtDvmDiskRead(pVol->pVolMgr->pDisk, pVol->offStart + off, pvBuf, cbRead);
+}
+
+static DECLCALLBACK(int) rtDvmFmtBsdLblVolumeWrite(RTDVMVOLUMEFMT hVolFmt, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ AssertReturn(off + cbWrite <= pVol->cbVolume, VERR_INVALID_PARAMETER);
+
+ return rtDvmDiskWrite(pVol->pVolMgr->pDisk, pVol->offStart + off, pvBuf, cbWrite);
+}
+
+DECL_HIDDEN_CONST(const RTDVMFMTOPS) g_rtDvmFmtBsdLbl =
+{
+ /* pcszFmt */
+ "BsdLabel",
+ /* enmFormat, */
+ RTDVMFORMATTYPE_BSD_LABEL,
+ /* pfnProbe */
+ rtDvmFmtBsdLblProbe,
+ /* pfnOpen */
+ rtDvmFmtBsdLblOpen,
+ /* pfnInitialize */
+ rtDvmFmtBsdLblInitialize,
+ /* pfnClose */
+ rtDvmFmtBsdLblClose,
+ /* pfnQueryRangeUse */
+ rtDvmFmtBsdLblQueryRangeUse,
+ /* pfnQueryDiskUuid */
+ NULL,
+ /* pfnGetValidVolumes */
+ rtDvmFmtBsdLblGetValidVolumes,
+ /* pfnGetMaxVolumes */
+ rtDvmFmtBsdLblGetMaxVolumes,
+ /* pfnQueryFirstVolume */
+ rtDvmFmtBsdLblQueryFirstVolume,
+ /* pfnQueryNextVolume */
+ rtDvmFmtBsdLblQueryNextVolume,
+ /* pfnQueryTableLocations */
+ rtDvmFmtBsdLblQueryTableLocations,
+ /* pfnVolumeClose */
+ rtDvmFmtBsdLblVolumeClose,
+ /* pfnVolumeGetSize */
+ rtDvmFmtBsdLblVolumeGetSize,
+ /* pfnVolumeQueryName */
+ rtDvmFmtBsdLblVolumeQueryName,
+ /* pfnVolumeGetType */
+ rtDvmFmtBsdLblVolumeGetType,
+ /* pfnVolumeGetFlags */
+ rtDvmFmtBsdLblVolumeGetFlags,
+ /* pfnVolumeQueryRange */
+ rtDvmFmtBsdLblVolumeQueryRange,
+ /* pfnVolumeIsRangeIntersecting */
+ rtDvmFmtBsdLblVolumeIsRangeIntersecting,
+ /* pfnVolumeQueryTableLocation */
+ rtDvmFmtBsdLblVolumeQueryTableLocation,
+ /* pfnVolumeGetIndex */
+ rtDvmFmtBsdLblVolumeGetIndex,
+ /* pfnVolumeQueryProp */
+ rtDvmFmtBsdLblVolumeQueryProp,
+ /* pfnVolumeRead */
+ rtDvmFmtBsdLblVolumeRead,
+ /* pfnVolumeWrite */
+ rtDvmFmtBsdLblVolumeWrite
+};
+
diff --git a/src/VBox/Runtime/common/dvm/dvmgpt.cpp b/src/VBox/Runtime/common/dvm/dvmgpt.cpp
new file mode 100644
index 00000000..d4328dd9
--- /dev/null
+++ b/src/VBox/Runtime/common/dvm/dvmgpt.cpp
@@ -0,0 +1,755 @@
+/* $Id: dvmgpt.cpp $ */
+/** @file
+ * IPRT Disk Volume Management API (DVM) - GPT format backend.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/dvm.h>
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/uuid.h>
+#include "internal/dvm.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** The GPT signature. */
+#define RTDVM_GPT_SIGNATURE "EFI PART"
+
+/**
+ * GPT on disk header.
+ */
+typedef struct GPTHDR
+{
+ /** 0x00: Signature ("EFI PART"). */
+ char abSignature[8];
+ /** 0x08: Revision. */
+ uint32_t u32Revision;
+ /** 0x0c: Header size. */
+ uint32_t cbHeader;
+ /** 0x10: CRC of header. */
+ uint32_t u32Crc;
+} GPTHDR;
+/** Pointer to a GPT header. */
+typedef struct GPTHDR *PGPTHDR;
+AssertCompileSize(GPTHDR, 20);
+
+/**
+ * Complete GPT table header for revision 1.0.
+ */
+#pragma pack(1)
+typedef struct GPTHDRREV1
+{
+ /** 0x00: Header. */
+ GPTHDR Hdr;
+ /** 0x14: Reserved. */
+ uint32_t u32Reserved;
+ /** 0x18: Current LBA. */
+ uint64_t u64LbaCurrent;
+ /** 0x20: Backup LBA. */
+ uint64_t u64LbaBackup;
+ /** 0x28:First usable LBA for partitions. */
+ uint64_t u64LbaFirstPartition;
+ /** 0x30: Last usable LBA for partitions. */
+ uint64_t u64LbaLastPartition;
+ /** 0x38: Disk UUID. */
+ RTUUID DiskUuid;
+ /** 0x48: LBA of first partition entry. */
+ uint64_t u64LbaPartitionEntries;
+ /** 0x50: Number of partition entries. */
+ uint32_t cPartitionEntries;
+ /** 0x54: Partition entry size. */
+ uint32_t cbPartitionEntry;
+ /** 0x58: CRC of partition entries. */
+ uint32_t u32CrcPartitionEntries;
+} GPTHDRREV1;
+/** Pointer to a revision 1.0 GPT header. */
+typedef GPTHDRREV1 *PGPTHDRREV1;
+#pragma pack()
+AssertCompileSize(GPTHDRREV1, 92);
+
+/**
+ * GPT partition table entry.
+ */
+typedef struct GPTENTRY
+{
+ /** 0x00: Partition type UUID. */
+ RTUUID UuidType;
+ /** 0x10: Partition UUID. */
+ RTUUID UuidPartition;
+ /** 0x20: First LBA. */
+ uint64_t u64LbaFirst;
+ /** 0x28: Last LBA. */
+ uint64_t u64LbaLast;
+ /** 0x30: Attribute flags. */
+ uint64_t u64Flags;
+ /** 0x38: Partition name (UTF-16LE code units). */
+ RTUTF16 aPartitionName[36];
+} GPTENTRY;
+/** Pointer to a GPT entry. */
+typedef struct GPTENTRY *PGPTENTRY;
+AssertCompileSize(GPTENTRY, 128);
+
+/** Partition flags - System partition. */
+#define RTDVM_GPT_ENTRY_SYSTEM RT_BIT_64(0)
+/** Partition flags - Partition is readonly. */
+#define RTDVM_GPT_ENTRY_READONLY RT_BIT_64(60)
+/** Partition flags - Partition is hidden. */
+#define RTDVM_GPT_ENTRY_HIDDEN RT_BIT_64(62)
+/** Partition flags - Don't automount this partition. */
+#define RTDVM_GPT_ENTRY_NO_AUTOMOUNT RT_BIT_64(63)
+
+/**
+ * GPT volume manager data.
+ */
+typedef struct RTDVMFMTINTERNAL
+{
+ /** Pointer to the underlying disk. */
+ PCRTDVMDISK pDisk;
+ /** GPT header. */
+ GPTHDRREV1 HdrRev1;
+ /** GPT array. */
+ PGPTENTRY paGptEntries;
+ /** Number of occupied partition entries. */
+ uint32_t cPartitions;
+} RTDVMFMTINTERNAL;
+/** Pointer to the MBR volume manager. */
+typedef RTDVMFMTINTERNAL *PRTDVMFMTINTERNAL;
+
+/**
+ * GPT volume data.
+ */
+typedef struct RTDVMVOLUMEFMTINTERNAL
+{
+ /** Pointer to the volume manager. */
+ PRTDVMFMTINTERNAL pVolMgr;
+ /** Partition table entry index. */
+ uint32_t idxEntry;
+ /** Start offset of the volume. */
+ uint64_t offStart;
+ /** Size of the volume. */
+ uint64_t cbVolume;
+ /** Pointer to the GPT entry in the array. */
+ PGPTENTRY pGptEntry;
+} RTDVMVOLUMEFMTINTERNAL;
+/** Pointer to an MBR volume. */
+typedef RTDVMVOLUMEFMTINTERNAL *PRTDVMVOLUMEFMTINTERNAL;
+
+/**
+ * GPT partition type to DVM volume type mapping entry.
+ */
+
+typedef struct RTDVMGPTPARTTYPE2VOLTYPE
+{
+ /** Type UUID. */
+ const char *pcszUuid;
+ /** DVM volume type. */
+ RTDVMVOLTYPE enmVolType;
+} RTDVMGPTPARTTYPE2VOLTYPE;
+/** Pointer to a MBR FS Type to volume type mapping entry. */
+typedef RTDVMGPTPARTTYPE2VOLTYPE *PRTDVMGPTPARTTYPE2VOLTYPE;
+
+/** Converts a LBA number to the byte offset. */
+#define RTDVM_GPT_LBA2BYTE(lba, disk) ((lba) * (disk)->cbSector)
+/** Converts a Byte offset to the LBA number. */
+#define RTDVM_GPT_BYTE2LBA(lba, disk) ((lba) / (disk)->cbSector)
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Mapping of partition types to DVM volume types.
+ *
+ * From http://en.wikipedia.org/wiki/GUID_Partition_Table
+ */
+static const RTDVMGPTPARTTYPE2VOLTYPE g_aPartType2DvmVolTypes[] =
+{
+ { "C12A7328-F81F-11D2-BA4B-00A0C93EC93B", RTDVMVOLTYPE_EFI_SYSTEM },
+
+ { "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7", RTDVMVOLTYPE_WIN_BASIC },
+ { "E3C9E316-0B5C-4DB8-817D-F92DF00215AE", RTDVMVOLTYPE_WIN_MSR },
+ { "5808C8AA-7E8F-42E0-85D2-E1E90434CFB3", RTDVMVOLTYPE_WIN_LDM_META },
+ { "AF9B60A0-1431-4F62-BC68-3311714A69AD", RTDVMVOLTYPE_WIN_LDM_DATA },
+ { "DE94BBA4-06D1-4D40-A16A-BFD50179D6AC", RTDVMVOLTYPE_WIN_RECOVERY },
+ { "E75CAF8F-F680-4CEE-AFA3-B001E56EFC2D", RTDVMVOLTYPE_WIN_STORAGE_SPACES },
+
+ { "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F", RTDVMVOLTYPE_LINUX_SWAP },
+ { "0FC63DAF-8483-4772-8E79-3D69D8477DE4", RTDVMVOLTYPE_LINUX_NATIVE },
+ { "44479540-F297-41B2-9AF7-D131D5F0458A", RTDVMVOLTYPE_LINUX_NATIVE }, /* x86 root */
+ { "4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709", RTDVMVOLTYPE_LINUX_NATIVE }, /* AMD64 root */
+ { "69DAD710-2CE4-4E3C-B16C-21A1D49ABED3", RTDVMVOLTYPE_LINUX_NATIVE }, /* ARM32 root */
+ { "B921B045-1DF0-41C3-AF44-4C6F280D3FAE", RTDVMVOLTYPE_LINUX_NATIVE }, /* ARM64 root */
+ { "E6D6D379-F507-44C2-A23C-238F2A3DF928", RTDVMVOLTYPE_LINUX_LVM },
+ { "A19D880F-05FC-4D3B-A006-743F0F84911E", RTDVMVOLTYPE_LINUX_SOFTRAID },
+
+ { "83BD6B9D-7F41-11DC-BE0B-001560B84F0F", RTDVMVOLTYPE_FREEBSD }, /* Boot */
+ { "516E7CB4-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* Data */
+ { "516E7CB5-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* Swap */
+ { "516E7CB6-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* UFS */
+ { "516E7CB8-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* Vinum */
+ { "516E7CBA-6ECF-11D6-8FF8-00022D09712B", RTDVMVOLTYPE_FREEBSD }, /* ZFS */
+
+ { "49F48D32-B10E-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* Swap */
+ { "49F48D5A-B10E-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* FFS */
+ { "49F48D82-B10E-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* LFS */
+ { "49F48DAA-B10E-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* Raid */
+ { "2DB519C4-B10F-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* Concatenated */
+ { "2DB519EC-B10F-11DC-B99B-0019D1879648", RTDVMVOLTYPE_NETBSD }, /* Encrypted */
+
+ { "48465300-0000-11AA-AA11-00306543ECAC", RTDVMVOLTYPE_DARWIN_HFS },
+ { "7C3457EF-0000-11AA-AA11-00306543ECAC", RTDVMVOLTYPE_DARWIN_APFS },
+
+ { "6A82CB45-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Boot */
+ { "6A85CF4D-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Root */
+ { "6A87C46F-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Swap */
+ { "6A8B642B-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Backup */
+ { "6A898CC3-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* /usr */
+ { "6A8EF2E9-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* /var */
+ { "6A90BA39-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* /home */
+ { "6A9283A5-1DD2-11B2-99A6-080020736631", RTDVMVOLTYPE_SOLARIS }, /* Alternate sector */
+
+ { "37AFFC90-EF7D-4E96-91C3-2D7AE055B174", RTDVMVOLTYPE_IBM_GPFS },
+
+ { "90B6FF38-B98F-4358-A21F-48F35B4A8AD3", RTDVMVOLTYPE_ARCA_OS2 }, /* OS/2 type 1 defined by Arca Noae */
+};
+
+static DECLCALLBACK(int) rtDvmFmtGptProbe(PCRTDVMDISK pDisk, uint32_t *puScore)
+{
+ int rc = VINF_SUCCESS;
+ GPTHDR Hdr;
+
+ *puScore = RTDVM_MATCH_SCORE_UNSUPPORTED;
+
+ if (rtDvmDiskGetSectors(pDisk) >= 2)
+ {
+ /* Read from the disk and check for the signature. */
+ rc = rtDvmDiskReadUnaligned(pDisk, RTDVM_GPT_LBA2BYTE(1, pDisk), &Hdr, sizeof(GPTHDR));
+ if ( RT_SUCCESS(rc)
+ && !strncmp(&Hdr.abSignature[0], RTDVM_GPT_SIGNATURE, RT_ELEMENTS(Hdr.abSignature))
+ && RT_LE2H_U32(Hdr.u32Revision) == 0x00010000
+ && RT_LE2H_U32(Hdr.cbHeader) == sizeof(GPTHDRREV1))
+ *puScore = RTDVM_MATCH_SCORE_PERFECT;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) rtDvmFmtGptOpen(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt)
+{
+ int rc = VINF_SUCCESS;
+ PRTDVMFMTINTERNAL pThis = NULL;
+
+ pThis = (PRTDVMFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMFMTINTERNAL));
+ if (pThis)
+ {
+ pThis->pDisk = pDisk;
+ pThis->cPartitions = 0;
+
+ /* Read the complete GPT header and convert to host endianess. */
+ rc = rtDvmDiskReadUnaligned(pDisk, RTDVM_GPT_LBA2BYTE(1, pDisk), &pThis->HdrRev1, sizeof(pThis->HdrRev1));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->HdrRev1.Hdr.u32Revision = RT_LE2H_U32(pThis->HdrRev1.Hdr.u32Revision);
+ pThis->HdrRev1.Hdr.cbHeader = RT_LE2H_U32(pThis->HdrRev1.Hdr.cbHeader);
+ pThis->HdrRev1.Hdr.u32Crc = RT_LE2H_U32(pThis->HdrRev1.Hdr.u32Crc);
+ pThis->HdrRev1.u64LbaCurrent = RT_LE2H_U64(pThis->HdrRev1.u64LbaCurrent);
+ pThis->HdrRev1.u64LbaBackup = RT_LE2H_U64(pThis->HdrRev1.u64LbaBackup);
+ pThis->HdrRev1.u64LbaFirstPartition = RT_LE2H_U64(pThis->HdrRev1.u64LbaFirstPartition);
+ pThis->HdrRev1.u64LbaLastPartition = RT_LE2H_U64(pThis->HdrRev1.u64LbaLastPartition);
+ /** @todo Disk UUID */
+ pThis->HdrRev1.u64LbaPartitionEntries = RT_LE2H_U64(pThis->HdrRev1.u64LbaPartitionEntries);
+ pThis->HdrRev1.cPartitionEntries = RT_LE2H_U32(pThis->HdrRev1.cPartitionEntries);
+ pThis->HdrRev1.cbPartitionEntry = RT_LE2H_U32(pThis->HdrRev1.cbPartitionEntry);
+ pThis->HdrRev1.u32CrcPartitionEntries = RT_LE2H_U32(pThis->HdrRev1.u32CrcPartitionEntries);
+
+ if (pThis->HdrRev1.cbPartitionEntry == sizeof(GPTENTRY))
+ {
+ size_t cbAlignedGptEntries = RT_ALIGN_Z(pThis->HdrRev1.cPartitionEntries * pThis->HdrRev1.cbPartitionEntry, pDisk->cbSector);
+ pThis->paGptEntries = (PGPTENTRY)RTMemAllocZ(cbAlignedGptEntries);
+ if (pThis->paGptEntries)
+ {
+ rc = rtDvmDiskRead(pDisk, RTDVM_GPT_LBA2BYTE(pThis->HdrRev1.u64LbaPartitionEntries, pDisk),
+ pThis->paGptEntries, cbAlignedGptEntries);
+ if (RT_SUCCESS(rc))
+ {
+ /* Count the occupied entries. */
+ for (unsigned i = 0; i < pThis->HdrRev1.cPartitionEntries; i++)
+ if (!RTUuidIsNull(&pThis->paGptEntries[i].UuidType))
+ {
+ /* Convert to host endianess. */
+ /** @todo Uuids */
+ pThis->paGptEntries[i].u64LbaFirst = RT_LE2H_U64(pThis->paGptEntries[i].u64LbaFirst);
+ pThis->paGptEntries[i].u64LbaLast = RT_LE2H_U64(pThis->paGptEntries[i].u64LbaLast);
+ pThis->paGptEntries[i].u64Flags = RT_LE2H_U64(pThis->paGptEntries[i].u64Flags);
+ for (unsigned cwc = 0; cwc < RT_ELEMENTS(pThis->paGptEntries[i].aPartitionName); cwc++)
+ pThis->paGptEntries[i].aPartitionName[cwc] = RT_LE2H_U16(pThis->paGptEntries[i].aPartitionName[cwc]);
+
+ pThis->cPartitions++;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *phVolMgrFmt = pThis;
+ return rc;
+ }
+ }
+ RTMemFree(pThis->paGptEntries);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) rtDvmFmtGptInitialize(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt)
+{
+ NOREF(pDisk); NOREF(phVolMgrFmt);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static DECLCALLBACK(void) rtDvmFmtGptClose(RTDVMFMT hVolMgrFmt)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ pThis->pDisk = NULL;
+ RT_ZERO(pThis->HdrRev1);
+
+ RTMemFree(pThis->paGptEntries);
+ pThis->paGptEntries = NULL;
+
+ RTMemFree(pThis);
+}
+
+static DECLCALLBACK(int) rtDvmFmtGptQueryRangeUse(RTDVMFMT hVolMgrFmt,
+ uint64_t off, uint64_t cbRange,
+ bool *pfUsed)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ NOREF(cbRange);
+
+ if (off < 33*pThis->pDisk->cbSector)
+ *pfUsed = true;
+ else
+ *pfUsed = false;
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnQueryDiskUuid */
+static DECLCALLBACK(int) rtDvmFmtGptQueryDiskUuid(RTDVMFMT hVolMgrFmt, PRTUUID pUuid)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ *pUuid = pThis->HdrRev1.DiskUuid;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(uint32_t) rtDvmFmtGptGetValidVolumes(RTDVMFMT hVolMgrFmt)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ return pThis->cPartitions;
+}
+
+static DECLCALLBACK(uint32_t) rtDvmFmtGptGetMaxVolumes(RTDVMFMT hVolMgrFmt)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ return pThis->HdrRev1.cPartitionEntries;
+}
+
+/**
+ * Creates a new volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The MBR volume manager data.
+ * @param pGptEntry The GPT entry.
+ * @param idx The index in the partition array.
+ * @param phVolFmt Where to store the volume data on success.
+ */
+static int rtDvmFmtMbrVolumeCreate(PRTDVMFMTINTERNAL pThis, PGPTENTRY pGptEntry,
+ uint32_t idx, PRTDVMVOLUMEFMT phVolFmt)
+{
+ int rc = VINF_SUCCESS;
+ PRTDVMVOLUMEFMTINTERNAL pVol = (PRTDVMVOLUMEFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMVOLUMEFMTINTERNAL));
+
+ if (pVol)
+ {
+ pVol->pVolMgr = pThis;
+ pVol->idxEntry = idx;
+ pVol->pGptEntry = pGptEntry;
+ pVol->offStart = RTDVM_GPT_LBA2BYTE(pGptEntry->u64LbaFirst, pThis->pDisk);
+ pVol->cbVolume = RTDVM_GPT_LBA2BYTE(pGptEntry->u64LbaLast - pGptEntry->u64LbaFirst + 1, pThis->pDisk);
+
+ *phVolFmt = pVol;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) rtDvmFmtGptQueryFirstVolume(RTDVMFMT hVolMgrFmt, PRTDVMVOLUMEFMT phVolFmt)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ if (pThis->cPartitions != 0)
+ {
+ PGPTENTRY pGptEntry = &pThis->paGptEntries[0];
+
+ /* Search for the first non empty entry. */
+ for (unsigned i = 0; i < pThis->HdrRev1.cPartitionEntries; i++)
+ {
+ if (!RTUuidIsNull(&pGptEntry->UuidType))
+ return rtDvmFmtMbrVolumeCreate(pThis, pGptEntry, i, phVolFmt);
+ pGptEntry++;
+ }
+ AssertFailed();
+ }
+ return VERR_DVM_MAP_EMPTY;
+}
+
+static DECLCALLBACK(int) rtDvmFmtGptQueryNextVolume(RTDVMFMT hVolMgrFmt, RTDVMVOLUMEFMT hVolFmt, PRTDVMVOLUMEFMT phVolFmtNext)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ PGPTENTRY pGptEntry = pVol->pGptEntry + 1;
+
+ for (unsigned i = pVol->idxEntry + 1; i < pThis->HdrRev1.cPartitionEntries; i++)
+ {
+ if (!RTUuidIsNull(&pGptEntry->UuidType))
+ return rtDvmFmtMbrVolumeCreate(pThis, pGptEntry, i, phVolFmtNext);
+ pGptEntry++;
+ }
+
+ return VERR_DVM_MAP_NO_VOLUME;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnQueryTableLocations */
+static DECLCALLBACK(int) rtDvmFmtGptQueryTableLocations(RTDVMFMT hVolMgrFmt, uint32_t fFlags, PRTDVMTABLELOCATION paLocations,
+ size_t cLocations, size_t *pcActual)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ /*
+ * The MBR if requested.
+ */
+ int rc = VINF_SUCCESS;
+ size_t iLoc = 0;
+ if (fFlags & RTDVMMAPQTABLOC_F_INCLUDE_LEGACY)
+ {
+ if (cLocations > 0)
+ {
+ paLocations[iLoc].off = 0;
+ paLocations[iLoc].cb = RTDVM_GPT_LBA2BYTE(1, pThis->pDisk);
+ paLocations[iLoc].cbPadding = 0;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ iLoc++;
+ }
+
+ /*
+ * The GPT.
+ */
+ if (cLocations > iLoc)
+ {
+ uint64_t const offEnd = (pThis->HdrRev1.cPartitionEntries * pThis->HdrRev1.cbPartitionEntry + pThis->pDisk->cbSector - 1)
+ / pThis->pDisk->cbSector
+ * pThis->pDisk->cbSector;
+ paLocations[iLoc].off = RTDVM_GPT_LBA2BYTE(1, pThis->pDisk);
+ paLocations[iLoc].cb = offEnd - paLocations[iLoc].off;
+
+ uint64_t uLbaFirstPart = pThis->pDisk->cbDisk / pThis->pDisk->cbSector;
+ for (unsigned i = 0; i < pThis->HdrRev1.cPartitionEntries; i++)
+ if ( pThis->paGptEntries[i].u64LbaFirst < uLbaFirstPart
+ && !RTUuidIsNull(&pThis->paGptEntries[i].UuidType))
+ uLbaFirstPart = pThis->paGptEntries[i].u64LbaFirst;
+
+ paLocations[iLoc].cbPadding = RTDVM_GPT_LBA2BYTE(uLbaFirstPart, pThis->pDisk);
+ if (paLocations[iLoc].cbPadding > offEnd)
+ paLocations[iLoc].cbPadding -= offEnd;
+ else
+ AssertFailedStmt(paLocations[iLoc].cbPadding = 0);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ iLoc++;
+
+ /*
+ * Return values.
+ */
+ if (pcActual)
+ *pcActual = iLoc;
+ else if (cLocations != iLoc && RT_SUCCESS(rc))
+ {
+ RT_BZERO(&paLocations[iLoc], (cLocations - iLoc) * sizeof(paLocations[0]));
+ rc = VERR_BUFFER_UNDERFLOW;
+ }
+ return rc;
+}
+
+static DECLCALLBACK(void) rtDvmFmtGptVolumeClose(RTDVMVOLUMEFMT hVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ pVol->pVolMgr = NULL;
+ pVol->offStart = 0;
+ pVol->cbVolume = 0;
+ pVol->pGptEntry = NULL;
+
+ RTMemFree(pVol);
+}
+
+static DECLCALLBACK(uint64_t) rtDvmFmtGptVolumeGetSize(RTDVMVOLUMEFMT hVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ return pVol->cbVolume;
+}
+
+static DECLCALLBACK(int) rtDvmFmtGptVolumeQueryName(RTDVMVOLUMEFMT hVolFmt, char **ppszVolName)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ *ppszVolName = NULL;
+ return RTUtf16ToUtf8Ex(&pVol->pGptEntry->aPartitionName[0], RT_ELEMENTS(pVol->pGptEntry->aPartitionName),
+ ppszVolName, 0, NULL);
+}
+
+static DECLCALLBACK(RTDVMVOLTYPE) rtDvmFmtGptVolumeGetType(RTDVMVOLUMEFMT hVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aPartType2DvmVolTypes); i++)
+ if (!RTUuidCompareStr(&pVol->pGptEntry->UuidType, g_aPartType2DvmVolTypes[i].pcszUuid))
+ return g_aPartType2DvmVolTypes[i].enmVolType;
+
+ return RTDVMVOLTYPE_UNKNOWN;
+}
+
+static DECLCALLBACK(uint64_t) rtDvmFmtGptVolumeGetFlags(RTDVMVOLUMEFMT hVolFmt)
+{
+ NOREF(hVolFmt);
+ return DVMVOLUME_F_CONTIGUOUS;
+}
+
+static DECLCALLBACK(int) rtDvmFmtGptVolumeQueryRange(RTDVMVOLUMEFMT hVolFmt, uint64_t *poffStart, uint64_t *poffLast)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ *poffStart = pVol->offStart;
+ *poffLast = pVol->offStart + pVol->cbVolume - 1;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(bool) rtDvmFmtGptVolumeIsRangeIntersecting(RTDVMVOLUMEFMT hVolFmt,
+ uint64_t offStart, size_t cbRange,
+ uint64_t *poffVol,
+ uint64_t *pcbIntersect)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ if (RTDVM_RANGE_IS_INTERSECTING(pVol->offStart, pVol->cbVolume, offStart))
+ {
+ *poffVol = offStart - pVol->offStart;
+ *pcbIntersect = RT_MIN(cbRange, pVol->offStart + pVol->cbVolume - offStart);
+ return true;
+ }
+ return false;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnVolumeQueryTableLocation */
+static DECLCALLBACK(int) rtDvmFmtGptVolumeQueryTableLocation(RTDVMVOLUMEFMT hVolFmt, uint64_t *poffTable, uint64_t *pcbTable)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ PRTDVMFMTINTERNAL pVolMgr = pVol->pVolMgr;
+ *poffTable = RTDVM_GPT_LBA2BYTE(1, pVolMgr->pDisk);
+ *pcbTable = RTDVM_GPT_LBA2BYTE(pVolMgr->HdrRev1.u64LbaPartitionEntries, pVolMgr->pDisk)
+ + RT_ALIGN_Z(pVolMgr->HdrRev1.cPartitionEntries * pVolMgr->HdrRev1.cbPartitionEntry, pVolMgr->pDisk->cbSector)
+ - *poffTable;
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnVolumeGetIndex */
+static DECLCALLBACK(uint32_t) rtDvmFmtGptVolumeGetIndex(RTDVMVOLUMEFMT hVolFmt, RTDVMVOLIDX enmIndex)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ switch (enmIndex)
+ {
+ case RTDVMVOLIDX_USER_VISIBLE:
+ case RTDVMVOLIDX_ALL:
+ case RTDVMVOLIDX_LINUX:
+ return pVol->idxEntry + 1;
+
+ case RTDVMVOLIDX_IN_TABLE:
+ return pVol->idxEntry;
+
+ case RTDVMVOLIDX_INVALID:
+ case RTDVMVOLIDX_HOST:
+ case RTDVMVOLIDX_END:
+ case RTDVMVOLIDX_32BIT_HACK:
+ break;
+ /* no default! */
+ }
+ AssertFailed();
+ return UINT32_MAX;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnVolumeQueryProp */
+static DECLCALLBACK(int) rtDvmFmtGptVolumeQueryProp(RTDVMVOLUMEFMT hVolFmt, RTDVMVOLPROP enmProperty,
+ void *pvBuf, size_t cbBuf, size_t *pcbBuf)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ switch (enmProperty)
+ {
+ case RTDVMVOLPROP_MBR_FIRST_CYLINDER:
+ case RTDVMVOLPROP_MBR_FIRST_HEAD:
+ case RTDVMVOLPROP_MBR_FIRST_SECTOR:
+ case RTDVMVOLPROP_MBR_LAST_CYLINDER:
+ case RTDVMVOLPROP_MBR_LAST_HEAD:
+ case RTDVMVOLPROP_MBR_LAST_SECTOR:
+ case RTDVMVOLPROP_MBR_TYPE:
+ return VERR_NOT_SUPPORTED;
+
+ case RTDVMVOLPROP_GPT_TYPE:
+ *pcbBuf = sizeof(RTUUID);
+ Assert(cbBuf >= *pcbBuf);
+ *(PRTUUID)pvBuf = pVol->pGptEntry->UuidType;
+ return VINF_SUCCESS;
+
+ case RTDVMVOLPROP_GPT_UUID:
+ *pcbBuf = sizeof(RTUUID);
+ Assert(cbBuf >= *pcbBuf);
+ *(PRTUUID)pvBuf = pVol->pGptEntry->UuidPartition;
+ return VINF_SUCCESS;
+
+ case RTDVMVOLPROP_INVALID:
+ case RTDVMVOLPROP_END:
+ case RTDVMVOLPROP_32BIT_HACK:
+ break;
+ /* not default! */
+ }
+ AssertFailed();
+ RT_NOREF(cbBuf);
+ return VERR_NOT_SUPPORTED;
+}
+
+static DECLCALLBACK(int) rtDvmFmtGptVolumeRead(RTDVMVOLUMEFMT hVolFmt, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ AssertReturn(off + cbRead <= pVol->cbVolume, VERR_INVALID_PARAMETER);
+
+ return rtDvmDiskRead(pVol->pVolMgr->pDisk, pVol->offStart + off, pvBuf, cbRead);
+}
+
+static DECLCALLBACK(int) rtDvmFmtGptVolumeWrite(RTDVMVOLUMEFMT hVolFmt, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ AssertReturn(off + cbWrite <= pVol->cbVolume, VERR_INVALID_PARAMETER);
+
+ return rtDvmDiskWrite(pVol->pVolMgr->pDisk, pVol->offStart + off, pvBuf, cbWrite);
+}
+
+DECL_HIDDEN_CONST(const RTDVMFMTOPS) g_rtDvmFmtGpt =
+{
+ /* pszFmt */
+ "GPT",
+ /* enmFormat, */
+ RTDVMFORMATTYPE_GPT,
+ /* pfnProbe */
+ rtDvmFmtGptProbe,
+ /* pfnOpen */
+ rtDvmFmtGptOpen,
+ /* pfnInitialize */
+ rtDvmFmtGptInitialize,
+ /* pfnClose */
+ rtDvmFmtGptClose,
+ /* pfnQueryRangeUse */
+ rtDvmFmtGptQueryRangeUse,
+ /* pfnQueryDiskUuid */
+ rtDvmFmtGptQueryDiskUuid,
+ /* pfnGetValidVolumes */
+ rtDvmFmtGptGetValidVolumes,
+ /* pfnGetMaxVolumes */
+ rtDvmFmtGptGetMaxVolumes,
+ /* pfnQueryFirstVolume */
+ rtDvmFmtGptQueryFirstVolume,
+ /* pfnQueryNextVolume */
+ rtDvmFmtGptQueryNextVolume,
+ /* pfnQueryTableLocations */
+ rtDvmFmtGptQueryTableLocations,
+ /* pfnVolumeClose */
+ rtDvmFmtGptVolumeClose,
+ /* pfnVolumeGetSize */
+ rtDvmFmtGptVolumeGetSize,
+ /* pfnVolumeQueryName */
+ rtDvmFmtGptVolumeQueryName,
+ /* pfnVolumeGetType */
+ rtDvmFmtGptVolumeGetType,
+ /* pfnVolumeGetFlags */
+ rtDvmFmtGptVolumeGetFlags,
+ /* pfnVolumeQueryRange */
+ rtDvmFmtGptVolumeQueryRange,
+ /* pfnVolumeIsRangeIntersecting */
+ rtDvmFmtGptVolumeIsRangeIntersecting,
+ /* pfnVolumeQueryTableLocation */
+ rtDvmFmtGptVolumeQueryTableLocation,
+ /* pfnVolumeGetIndex */
+ rtDvmFmtGptVolumeGetIndex,
+ /* pfnVolumeQueryProp */
+ rtDvmFmtGptVolumeQueryProp,
+ /* pfnVolumeRead */
+ rtDvmFmtGptVolumeRead,
+ /* pfnVolumeWrite */
+ rtDvmFmtGptVolumeWrite
+};
+
diff --git a/src/VBox/Runtime/common/dvm/dvmmbr.cpp b/src/VBox/Runtime/common/dvm/dvmmbr.cpp
new file mode 100644
index 00000000..ef9fe659
--- /dev/null
+++ b/src/VBox/Runtime/common/dvm/dvmmbr.cpp
@@ -0,0 +1,1069 @@
+/* $Id: dvmmbr.cpp $ */
+/** @file
+ * IPRT Disk Volume Management API (DVM) - MBR format backend.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/dvm.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include "internal/dvm.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Checks if the partition type is an extended partition container. */
+#define RTDVMMBR_IS_EXTENDED(a_bType) ((a_bType) == 0x05 || (a_bType) == 0x0f)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a MBR sector. */
+typedef struct RTDVMMBRSECTOR *PRTDVMMBRSECTOR;
+
+
+/** The on-disk Cylinder/Head/Sector (CHS) info. */
+typedef struct MBRCHSADDR
+{
+ uint8_t uHead;
+ uint8_t uSector : 6;
+ uint8_t uCylinderH : 2;
+ uint8_t uCylinderL;
+} MBRCHSADDR;
+AssertCompileSize(MBRCHSADDR, 3);
+
+
+/** A decoded cylinder/head/sector address. */
+typedef struct RTDVMMBRCHSADDR
+{
+ uint16_t uCylinder;
+ uint8_t uHead;
+ uint8_t uSector;
+} RTDVMMBRCHSADDR;
+
+
+/**
+ * MBR entry.
+ */
+typedef struct RTDVMMBRENTRY
+{
+ /** Our entry in the in-use partition entry list (RTDVMMBRENTRY). */
+ RTLISTNODE ListEntry;
+ /** Pointer to the MBR sector containing this entry. */
+ PRTDVMMBRSECTOR pSector;
+ /** Pointer to the next sector in the extended partition table chain. */
+ PRTDVMMBRSECTOR pChain;
+ /** The byte offset of the start of the partition (relative to disk). */
+ uint64_t offPart;
+ /** Number of bytes for this partition. */
+ uint64_t cbPart;
+ /** The partition/filesystem type. */
+ uint8_t bType;
+ /** The partition flags. */
+ uint8_t fFlags;
+ /** Bad entry. */
+ bool fBad;
+ /** RTDVMVOLIDX_IN_TABLE - Zero-based index within the table in pSector.
+ * (Also the index into RTDVMMBRSECTOR::aEntries.) */
+ uint8_t idxTable;
+ /** RTDVMVOLIDX_ALL - One-based index. All primary entries are included,
+ * whether they are used or not. In the extended table chain, only USED
+ * entries are counted (but we include RTDVMMBR_IS_EXTENDED entries). */
+ uint8_t idxAll;
+ /** RTDVMVOLIDX_USER_VISIBLE - One-base index. Skips all unused entries
+ * and RTDVMMBR_IS_EXTENDED. */
+ uint8_t idxVisible;
+ /** RTDVMVOLIDX_LINUX - One-based index following the /dev/sdaX scheme. */
+ uint8_t idxLinux;
+ uint8_t bUnused;
+ /** The first CHS address of this partition */
+ RTDVMMBRCHSADDR FirstChs;
+ /** The last CHS address of this partition */
+ RTDVMMBRCHSADDR LastChs;
+} RTDVMMBRENTRY;
+/** Pointer to an MBR entry. */
+typedef RTDVMMBRENTRY *PRTDVMMBRENTRY;
+
+/**
+ * A MBR sector.
+ */
+typedef struct RTDVMMBRSECTOR
+{
+ /** Internal representation of the entries. */
+ RTDVMMBRENTRY aEntries[4];
+ /** The byte offset of this MBR sector (relative to disk).
+ * We keep this for detecting cycles now, but it will be needed if we start
+ * updating the partition table at some point. */
+ uint64_t offOnDisk;
+ /** Pointer to the previous sector if this isn't a primary one. */
+ PRTDVMMBRENTRY pPrevSector;
+ /** Set if this is the primary MBR, cleared if an extended. */
+ bool fIsPrimary;
+ /** Number of used entries. */
+ uint8_t cUsed;
+ /** Number of extended entries. */
+ uint8_t cExtended;
+ /** The extended entry we're following (we only follow one, except when
+ * fIsPrimary is @c true). UINT8_MAX if none. */
+ uint8_t idxExtended;
+#if ARCH_BITS == 64
+ uint32_t uAlignmentPadding;
+#endif
+ /** The raw data. */
+ uint8_t abData[RT_FLEXIBLE_ARRAY_NESTED];
+} RTDVMMBRSECTOR;
+
+/**
+ * MBR volume manager data.
+ */
+typedef struct RTDVMFMTINTERNAL
+{
+ /** Pointer to the underlying disk. */
+ PCRTDVMDISK pDisk;
+ /** Head of the list of in-use RTDVMMBRENTRY structures. This excludes
+ * extended partition table entries. */
+ RTLISTANCHOR PartitionHead;
+ /** The sector size to use when doing address calculation based on partition
+ * table sector addresses and counts. */
+ uint32_t cbSector;
+ /** The total number of partitions, not counting extended ones. */
+ uint32_t cPartitions;
+ /** The actual primary MBR sector. */
+ RTDVMMBRSECTOR Primary;
+} RTDVMFMTINTERNAL;
+/** Pointer to the MBR volume manager. */
+typedef RTDVMFMTINTERNAL *PRTDVMFMTINTERNAL;
+
+/**
+ * MBR volume data.
+ */
+typedef struct RTDVMVOLUMEFMTINTERNAL
+{
+ /** Pointer to the volume manager. */
+ PRTDVMFMTINTERNAL pVolMgr;
+ /** The MBR entry. */
+ PRTDVMMBRENTRY pEntry;
+} RTDVMVOLUMEFMTINTERNAL;
+/** Pointer to an MBR volume. */
+typedef RTDVMVOLUMEFMTINTERNAL *PRTDVMVOLUMEFMTINTERNAL;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Mapping of FS types to DVM volume types.
+ *
+ * @see https://en.wikipedia.org/wiki/Partition_type
+ * @see http://www.win.tue.nl/~aeb/partitions/partition_types-1.html
+ */
+static const struct RTDVMMBRFS2VOLTYPE
+{
+ /** MBR FS Id. */
+ uint8_t bFsId;
+ /** DVM volume type. */
+ RTDVMVOLTYPE enmVolType;
+} g_aFs2DvmVolTypes[] =
+{
+ { 0x01, RTDVMVOLTYPE_FAT12 },
+ { 0x04, RTDVMVOLTYPE_FAT16 },
+ { 0x06, RTDVMVOLTYPE_FAT16 }, /* big FAT16 */
+ { 0x07, RTDVMVOLTYPE_NTFS }, /* Simplification: Used for HPFS, exFAT, ++, too but NTFS is the more common one. */
+ { 0x0b, RTDVMVOLTYPE_FAT32 },
+ { 0x0c, RTDVMVOLTYPE_FAT32 },
+ { 0x0e, RTDVMVOLTYPE_FAT16 },
+
+ /* Hidden variants of the above: */
+ { 0x11, RTDVMVOLTYPE_FAT12 },
+ { 0x14, RTDVMVOLTYPE_FAT16 },
+ { 0x16, RTDVMVOLTYPE_FAT16 },
+ { 0x17, RTDVMVOLTYPE_NTFS },
+ { 0x1b, RTDVMVOLTYPE_FAT32 },
+ { 0x1c, RTDVMVOLTYPE_FAT32 },
+ { 0x1e, RTDVMVOLTYPE_FAT16 },
+
+ { 0x82, RTDVMVOLTYPE_LINUX_SWAP },
+ { 0x83, RTDVMVOLTYPE_LINUX_NATIVE },
+ { 0x8e, RTDVMVOLTYPE_LINUX_LVM },
+ { 0xa5, RTDVMVOLTYPE_FREEBSD },
+ { 0xa9, RTDVMVOLTYPE_NETBSD },
+ { 0xa6, RTDVMVOLTYPE_OPENBSD },
+ { 0xaf, RTDVMVOLTYPE_DARWIN_HFS },
+ { 0xbf, RTDVMVOLTYPE_SOLARIS },
+ { 0xfd, RTDVMVOLTYPE_LINUX_SOFTRAID }
+};
+
+
+static DECLCALLBACK(int) rtDvmFmtMbrProbe(PCRTDVMDISK pDisk, uint32_t *puScore)
+{
+ int rc = VINF_SUCCESS;
+ *puScore = RTDVM_MATCH_SCORE_UNSUPPORTED;
+ if (pDisk->cbDisk > RT_MAX(512, pDisk->cbSector))
+ {
+ /* Read from the disk and check for the 0x55aa signature at the end. */
+ size_t cbAlignedSize = RT_MAX(512, pDisk->cbSector);
+ uint8_t *pbMbr = (uint8_t *)RTMemTmpAllocZ(cbAlignedSize);
+ if (pbMbr)
+ {
+ rc = rtDvmDiskRead(pDisk, 0, pbMbr, cbAlignedSize);
+ if ( RT_SUCCESS(rc)
+ && pbMbr[510] == 0x55
+ && pbMbr[511] == 0xaa)
+ *puScore = RTDVM_MATCH_SCORE_SUPPORTED; /* Not perfect because GPTs have a protective MBR. */
+ /** @todo this could easily confuser a DOS, OS/2 or NT boot sector with a MBR... */
+ RTMemTmpFree(pbMbr);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+
+ return rc;
+}
+
+
+static void rtDvmFmtMbrDestroy(PRTDVMFMTINTERNAL pThis)
+{
+ /*
+ * Delete chains of extended partitions.
+ */
+ for (unsigned i = 0; i < 4; i++)
+ {
+ PRTDVMMBRSECTOR pCur = pThis->Primary.aEntries[i].pChain;
+ while (pCur)
+ {
+ PRTDVMMBRSECTOR pNext = pCur->idxExtended != UINT8_MAX ? pCur->aEntries[pCur->idxExtended].pChain : NULL;
+
+ RT_ZERO(pCur->aEntries);
+ pCur->pPrevSector = NULL;
+ RTMemFree(pCur);
+
+ pCur = pNext;
+ }
+ }
+
+ /*
+ * Now kill this.
+ */
+ pThis->pDisk = NULL;
+ RT_ZERO(pThis->Primary.aEntries);
+ RTMemFree(pThis);
+}
+
+
+/**
+ * Decodes the on-disk cylinder/head/sector info and stores it the
+ * destination structure.
+ */
+DECLINLINE(void) rtDvmFmtMbrDecodeChs(RTDVMMBRCHSADDR *pDst, uint8_t *pbRaw)
+{
+ MBRCHSADDR *pRawChs = (MBRCHSADDR *)pbRaw;
+ pDst->uCylinder = RT_MAKE_U16(pRawChs->uCylinderL, pRawChs->uCylinderH);
+ pDst->uSector = pRawChs->uSector;
+ pDst->uHead = pRawChs->uHead;
+}
+
+
+static int rtDvmFmtMbrReadExtended(PRTDVMFMTINTERNAL pThis, PRTDVMMBRENTRY pPrimaryEntry,
+ uint8_t *pidxAll, uint8_t *pidxVisible, uint8_t *pidxLinux)
+{
+ uint64_t const cbExt = pPrimaryEntry->cbPart;
+ uint64_t const offExtBegin = pPrimaryEntry->offPart;
+
+ uint64_t offCurBegin = offExtBegin;
+ PRTDVMMBRENTRY pCurEntry = pPrimaryEntry;
+ for (unsigned cTables = 1; ; cTables++)
+ {
+ /*
+ * Do some sanity checking.
+ */
+ /* Check the address of the partition table. */
+ if (offCurBegin - offExtBegin >= cbExt)
+ {
+ LogRel(("rtDvmFmtMbrReadExtended: offCurBegin=%#RX64 is outside the extended partition: %#RX64..%#RX64 (LB %#RX64)\n",
+ offCurBegin, offExtBegin, offExtBegin + cbExt - 1, cbExt));
+ pCurEntry->fBad = true;
+ return -VERR_OUT_OF_RANGE;
+ }
+
+ /* Limit the chain length. */
+ if (cTables > 64)
+ {
+ LogRel(("rtDvmFmtMbrReadExtended: offCurBegin=%#RX64 is the %uth table, we stop here.\n", offCurBegin, cTables));
+ pCurEntry->fBad = true;
+ return -VERR_TOO_MANY_SYMLINKS;
+ }
+
+ /* Check for obvious cycles. */
+ for (PRTDVMMBRENTRY pPrev = pCurEntry->pSector->pPrevSector; pPrev != NULL; pPrev = pPrev->pSector->pPrevSector)
+ if (pPrev->offPart == offCurBegin)
+ {
+ LogRel(("rtDvmFmtMbrReadExtended: Cycle! We've seen offCurBegin=%#RX64 before\n", offCurBegin));
+ pCurEntry->fBad = true;
+ return -VERR_TOO_MANY_SYMLINKS;
+ }
+
+ /*
+ * Allocate a new sector entry and read the sector with the table.
+ */
+ size_t const cbMbr = RT_MAX(512, pThis->pDisk->cbSector);
+ PRTDVMMBRSECTOR pNext = (PRTDVMMBRSECTOR)RTMemAllocZVar(RT_UOFFSETOF_DYN(RTDVMMBRSECTOR, abData[cbMbr]));
+ if (!pNext)
+ return VERR_NO_MEMORY;
+ pNext->offOnDisk = offCurBegin;
+ pNext->pPrevSector = pCurEntry;
+ //pNext->fIsPrimary = false;
+ //pNext->cUsed = 0;
+ //pNext->cExtended = 0;
+ pNext->idxExtended = UINT8_MAX;
+
+ uint8_t *pabData = &pNext->abData[0];
+ int rc = rtDvmDiskReadUnaligned(pThis->pDisk, pNext->offOnDisk, pabData, cbMbr);
+ if ( RT_FAILURE(rc)
+ || pabData[510] != 0x55
+ || pabData[511] != 0xaa)
+ {
+ if (RT_FAILURE(rc))
+ LogRel(("rtDvmFmtMbrReadExtended: Error reading extended partition table at sector %#RX64: %Rrc\n", offCurBegin, rc));
+ else
+ LogRel(("rtDvmFmtMbrReadExtended: Extended partition table at sector %#RX64 does not have a valid DOS signature: %#x %#x\n",
+ offCurBegin, pabData[510], pabData[511]));
+ RTMemFree(pNext);
+ pCurEntry->fBad = true;
+ return rc;
+ }
+ pCurEntry->pChain = pNext;
+
+ /*
+ * Process the table, taking down the first forward entry.
+ *
+ * As noted in the caller of this function, we only deal with one extended
+ * partition entry at this level since noone really ever put more than one
+ * here anyway.
+ */
+ PRTDVMMBRENTRY pEntry = &pNext->aEntries[0];
+ uint8_t *pbMbrEntry = &pabData[446];
+ for (unsigned i = 0; i < 4; i++, pEntry++, pbMbrEntry += 16)
+ {
+ pEntry->pSector = pNext;
+ pEntry->idxTable = (uint8_t)i;
+ RTListInit(&pEntry->ListEntry);
+
+ uint8_t const bType = pbMbrEntry[4];
+ if (bType != 0)
+ {
+ pEntry->bType = bType;
+ pEntry->fFlags = pbMbrEntry[0];
+ pEntry->idxAll = *pidxAll;
+ *pidxAll += 1;
+
+ rtDvmFmtMbrDecodeChs(&pEntry->FirstChs, &pbMbrEntry[1]);
+ rtDvmFmtMbrDecodeChs(&pEntry->LastChs, &pbMbrEntry[5]);
+
+ pEntry->offPart = RT_MAKE_U32_FROM_U8(pbMbrEntry[0x08 + 0],
+ pbMbrEntry[0x08 + 1],
+ pbMbrEntry[0x08 + 2],
+ pbMbrEntry[0x08 + 3]);
+ pEntry->offPart *= pThis->cbSector;
+ pEntry->cbPart = RT_MAKE_U32_FROM_U8(pbMbrEntry[0x0c + 0],
+ pbMbrEntry[0x0c + 1],
+ pbMbrEntry[0x0c + 2],
+ pbMbrEntry[0x0c + 3]);
+ pEntry->cbPart *= pThis->cbSector;
+ if (!RTDVMMBR_IS_EXTENDED(bType))
+ {
+ pEntry->offPart += offCurBegin;
+ pEntry->idxVisible = *pidxVisible;
+ *pidxVisible += 1;
+ pEntry->idxLinux = *pidxLinux;
+ *pidxLinux += 1;
+
+ pThis->cPartitions++;
+ RTListAppend(&pThis->PartitionHead, &pEntry->ListEntry);
+ Log2(("rtDvmFmtMbrReadExtended: %#012RX64::%u: vol%u bType=%#04x fFlags=%#04x offPart=%#012RX64 cbPart=%#012RX64\n",
+ offCurBegin, i, pThis->cPartitions - 1, pEntry->bType, pEntry->fFlags, pEntry->offPart, pEntry->cbPart));
+ }
+ else
+ {
+ pEntry->offPart += offExtBegin;
+ pNext->cExtended++;
+ if (pNext->idxExtended == UINT8_MAX)
+ pNext->idxExtended = (uint8_t)i;
+ else
+ {
+ pEntry->fBad = true;
+ LogRel(("rtDvmFmtMbrReadExtended: Warning! Both #%u and #%u are extended partition table entries! Only following the former\n",
+ i, pNext->idxExtended));
+ }
+ Log2(("rtDvmFmtMbrReadExtended: %#012RX64::%u: ext%u bType=%#04x fFlags=%#04x offPart=%#012RX64 cbPart=%#012RX64\n",
+ offCurBegin, i, pNext->cExtended - 1, pEntry->bType, pEntry->fFlags, pEntry->offPart, pEntry->cbPart));
+ }
+ pNext->cUsed++;
+
+ }
+ /* else: unused */
+ }
+
+ /*
+ * We're done if we didn't find any extended partition table entry.
+ * Otherwise, advance to the next one.
+ */
+ if (!pNext->cExtended)
+ return VINF_SUCCESS;
+ pCurEntry = &pNext->aEntries[pNext->idxExtended];
+ offCurBegin = pCurEntry->offPart;
+ }
+}
+
+
+static DECLCALLBACK(int) rtDvmFmtMbrOpen(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt)
+{
+ int rc;
+ size_t const cbMbr = RT_MAX(512, pDisk->cbSector);
+ PRTDVMFMTINTERNAL pThis = (PRTDVMFMTINTERNAL)RTMemAllocZVar(RT_UOFFSETOF_DYN(RTDVMFMTINTERNAL, Primary.abData[cbMbr]));
+ if (pThis)
+ {
+ pThis->pDisk = pDisk;
+ //pThis->cPartitions = 0;
+ RTListInit(&pThis->PartitionHead);
+ //pThis->Primary.offOnDisk = 0;
+ //pThis->Primary.pPrevSector = NULL;
+ pThis->Primary.fIsPrimary = true;
+ //pThis->Primary.cUsed = 0;
+ //pThis->Primary.cExtended = 0;
+ pThis->Primary.idxExtended = UINT8_MAX;
+
+ /* We'll use the sector size reported by the disk.
+
+ Though, giiven that the MBR was hardwired to 512 byte sectors, we probably
+ should do some probing when the sector size differs from 512, but that can
+ wait till there is a real need for it and we've got some semi reliable
+ heuristics for doing that. */
+ pThis->cbSector = (uint32_t)pDisk->cbSector;
+ AssertLogRelMsgStmt( pThis->cbSector >= 512
+ && pThis->cbSector <= _64K,
+ ("cbSector=%#x\n", pThis->cbSector),
+ pThis->cbSector = 512);
+
+ /*
+ * Read the primary MBR.
+ */
+ uint8_t *pabData = &pThis->Primary.abData[0];
+ rc = rtDvmDiskRead(pDisk, 0, pabData, cbMbr);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pabData[510] == 0x55 && pabData[511] == 0xaa);
+
+ /*
+ * Setup basic data for the 4 entries.
+ */
+ PRTDVMMBRENTRY pEntry = &pThis->Primary.aEntries[0];
+ uint8_t *pbMbrEntry = &pabData[446];
+ uint8_t idxVisible = 1;
+ for (unsigned i = 0; i < 4; i++, pEntry++, pbMbrEntry += 16)
+ {
+ pEntry->pSector = &pThis->Primary;
+ pEntry->idxTable = (uint8_t)i;
+ RTListInit(&pEntry->ListEntry);
+
+ uint8_t const bType = pbMbrEntry[4];
+ if (bType != 0)
+ {
+ pEntry->bType = bType;
+ pEntry->fFlags = pbMbrEntry[0];
+ pEntry->idxAll = (uint8_t)(i + 1);
+
+ rtDvmFmtMbrDecodeChs(&pEntry->FirstChs, &pbMbrEntry[1]);
+ rtDvmFmtMbrDecodeChs(&pEntry->LastChs, &pbMbrEntry[5]);
+
+ pEntry->offPart = RT_MAKE_U32_FROM_U8(pbMbrEntry[0x08 + 0],
+ pbMbrEntry[0x08 + 1],
+ pbMbrEntry[0x08 + 2],
+ pbMbrEntry[0x08 + 3]);
+ pEntry->offPart *= pThis->cbSector;
+ pEntry->cbPart = RT_MAKE_U32_FROM_U8(pbMbrEntry[0x0c + 0],
+ pbMbrEntry[0x0c + 1],
+ pbMbrEntry[0x0c + 2],
+ pbMbrEntry[0x0c + 3]);
+ pEntry->cbPart *= pThis->cbSector;
+ if (!RTDVMMBR_IS_EXTENDED(bType))
+ {
+ pEntry->idxVisible = idxVisible++;
+ pEntry->idxLinux = (uint8_t)(i + 1);
+ pThis->cPartitions++;
+ RTListAppend(&pThis->PartitionHead, &pEntry->ListEntry);
+ Log2(("rtDvmFmtMbrOpen: %u: vol%u bType=%#04x fFlags=%#04x offPart=%#012RX64 cbPart=%#012RX64\n",
+ i, pThis->cPartitions - 1, pEntry->bType, pEntry->fFlags, pEntry->offPart, pEntry->cbPart));
+ }
+ else
+ {
+ pThis->Primary.cExtended++;
+ Log2(("rtDvmFmtMbrOpen: %u: ext%u bType=%#04x fFlags=%#04x offPart=%#012RX64 cbPart=%#012RX64\n",
+ i, pThis->Primary.cExtended - 1, pEntry->bType, pEntry->fFlags, pEntry->offPart, pEntry->cbPart));
+ }
+ pThis->Primary.cUsed++;
+ }
+ /* else: unused */
+ }
+
+ /*
+ * Now read any extended partitions. Since it's no big deal for us, we allow
+ * the primary partition table to have more than one extended partition. However
+ * in the extended tables we only allow a single forward link to avoid having to
+ * deal with recursion.
+ */
+ if (pThis->Primary.cExtended > 0)
+ {
+ uint8_t idxAll = 5;
+ uint8_t idxLinux = 5;
+ for (unsigned i = 0; i < 4; i++)
+ if (RTDVMMBR_IS_EXTENDED(pThis->Primary.aEntries[i].bType))
+ {
+ if (pThis->Primary.idxExtended == UINT8_MAX)
+ pThis->Primary.idxExtended = (uint8_t)i;
+ rc = rtDvmFmtMbrReadExtended(pThis, &pThis->Primary.aEntries[i], &idxAll, &idxVisible, &idxLinux);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *phVolMgrFmt = pThis;
+ return rc;
+ }
+ }
+ rtDvmFmtMbrDestroy(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) rtDvmFmtMbrInitialize(PCRTDVMDISK pDisk, PRTDVMFMT phVolMgrFmt)
+{
+ int rc;
+ size_t const cbMbr = RT_MAX(512, pDisk->cbSector);
+ PRTDVMFMTINTERNAL pThis = (PRTDVMFMTINTERNAL)RTMemAllocZVar(RT_UOFFSETOF_DYN(RTDVMFMTINTERNAL, Primary.abData[cbMbr]));
+ if (pThis)
+ {
+ pThis->pDisk = pDisk;
+ //pThis->cPartitions = 0;
+ RTListInit(&pThis->PartitionHead);
+ //pThis->Primary.offOnDisk = 0
+ //pThis->Primary.pPrevSector = NULL;
+ pThis->Primary.fIsPrimary = true;
+ //pThis->Primary.cUsed = 0;
+ //pThis->Primary.cExtended = 0;
+ pThis->Primary.idxExtended = UINT8_MAX;
+
+ /* Setup a new MBR and write it to the disk. */
+ uint8_t *pabData = &pThis->Primary.abData[0];
+ RT_BZERO(pabData, 512);
+ pabData[510] = 0x55;
+ pabData[511] = 0xaa;
+ rc = rtDvmDiskWrite(pDisk, 0, pabData, cbMbr);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pDisk = pDisk;
+ *phVolMgrFmt = pThis;
+ }
+ else
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+static DECLCALLBACK(void) rtDvmFmtMbrClose(RTDVMFMT hVolMgrFmt)
+{
+ rtDvmFmtMbrDestroy(hVolMgrFmt);
+}
+
+static DECLCALLBACK(int) rtDvmFmtMbrQueryRangeUse(RTDVMFMT hVolMgrFmt, uint64_t off, uint64_t cbRange, bool *pfUsed)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ /*
+ * The MBR definitely uses the first 512 bytes, but we consider anything up
+ * to 1MB of alignment padding / cylinder gap to be considered in use too.
+ *
+ * The cylinder gap has been used by several boot managers and boot loaders
+ * to store code and data.
+ */
+ if (off < (uint64_t)_1M)
+ {
+ *pfUsed = true;
+ return VINF_SUCCESS;
+ }
+
+ /* Ditto for any extended partition tables. */
+ for (uint32_t iPrimary = 0; iPrimary < 4; iPrimary++)
+ {
+ PRTDVMMBRSECTOR pCur = pThis->Primary.aEntries[iPrimary].pChain;
+ while (pCur)
+ {
+ if ( off < pCur->offOnDisk + _1M
+ && off + cbRange > pCur->offOnDisk)
+ {
+ *pfUsed = true;
+ return VINF_SUCCESS;
+ }
+
+
+ if (pCur->idxExtended == UINT8_MAX)
+ break;
+ pCur = pCur->aEntries[pCur->idxExtended].pChain;
+ }
+
+ }
+
+ /* Not in use. */
+ *pfUsed = false;
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnQueryDiskUuid */
+static DECLCALLBACK(int) rtDvmFmtMbrQueryDiskUuid(RTDVMFMT hVolMgrFmt, PRTUUID pUuid)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+ uint32_t idDisk = RT_MAKE_U32_FROM_U8(pThis->Primary.abData[440],
+ pThis->Primary.abData[441],
+ pThis->Primary.abData[442],
+ pThis->Primary.abData[443]);
+ if (idDisk != 0)
+ {
+ RTUuidClear(pUuid);
+ pUuid->Gen.u32TimeLow = idDisk;
+ return VINF_NOT_SUPPORTED;
+ }
+ return VERR_NOT_SUPPORTED;
+}
+
+static DECLCALLBACK(uint32_t) rtDvmFmtMbrGetValidVolumes(RTDVMFMT hVolMgrFmt)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+
+ return pThis->cPartitions;
+}
+
+static DECLCALLBACK(uint32_t) rtDvmFmtMbrGetMaxVolumes(RTDVMFMT hVolMgrFmt)
+{
+ NOREF(hVolMgrFmt);
+ return 4; /** @todo Add support for EBR? */
+}
+
+/**
+ * Creates a new volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The MBR volume manager data.
+ * @param pEntry The MBR entry to create a volume handle for.
+ * @param phVolFmt Where to store the volume data on success.
+ */
+static int rtDvmFmtMbrVolumeCreate(PRTDVMFMTINTERNAL pThis, PRTDVMMBRENTRY pEntry, PRTDVMVOLUMEFMT phVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = (PRTDVMVOLUMEFMTINTERNAL)RTMemAllocZ(sizeof(RTDVMVOLUMEFMTINTERNAL));
+ if (pVol)
+ {
+ pVol->pVolMgr = pThis;
+ pVol->pEntry = pEntry;
+ *phVolFmt = pVol;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+static DECLCALLBACK(int) rtDvmFmtMbrQueryFirstVolume(RTDVMFMT hVolMgrFmt, PRTDVMVOLUMEFMT phVolFmt)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+ if (pThis->cPartitions != 0)
+ return rtDvmFmtMbrVolumeCreate(pThis, RTListGetFirst(&pThis->PartitionHead, RTDVMMBRENTRY, ListEntry), phVolFmt);
+ return VERR_DVM_MAP_EMPTY;
+}
+
+static DECLCALLBACK(int) rtDvmFmtMbrQueryNextVolume(RTDVMFMT hVolMgrFmt, RTDVMVOLUMEFMT hVolFmt, PRTDVMVOLUMEFMT phVolFmtNext)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+ PRTDVMVOLUMEFMTINTERNAL pCurVol = hVolFmt;
+ if (pCurVol)
+ {
+ PRTDVMMBRENTRY pNextEntry = RTListGetNext(&pThis->PartitionHead, pCurVol->pEntry, RTDVMMBRENTRY, ListEntry);
+ if (pNextEntry)
+ return rtDvmFmtMbrVolumeCreate(pThis, pNextEntry, phVolFmtNext);
+ return VERR_DVM_MAP_NO_VOLUME;
+ }
+ if (pThis->cPartitions != 0)
+ return rtDvmFmtMbrVolumeCreate(pThis, RTListGetFirst(&pThis->PartitionHead, RTDVMMBRENTRY, ListEntry), phVolFmtNext);
+ return VERR_DVM_MAP_EMPTY;
+}
+
+/**
+ * Helper for rtDvmFmtMbrQueryTableLocations that calculates the padding and/or
+ * free space at @a off.
+ *
+ * Because nothing need to be sorted by start offset, we have to traverse all
+ * partition tables to determine this.
+ */
+static uint64_t rtDvmFmtMbrCalcTablePadding(PRTDVMFMTINTERNAL pThis, uint64_t off)
+{
+ uint64_t offNext = pThis->pDisk->cbDisk;
+ for (unsigned i = 0; i < 4; i++)
+ {
+ /* Check this primary entry */
+ uint64_t offCur = pThis->Primary.aEntries[i].offPart;
+ if (offCur >= off && offCur < offNext && pThis->Primary.aEntries[i].bType != 0)
+ offNext = offCur;
+
+ /* If it's an extended partition, check the chained ones too. */
+ for (PRTDVMMBRSECTOR pCur = pThis->Primary.aEntries[i].pChain;
+ pCur != NULL;
+ pCur = pCur->idxExtended != UINT8_MAX ? pCur->aEntries[pCur->idxExtended].pChain : NULL)
+ {
+ for (unsigned j = 0; j < 4; j++)
+ {
+ offCur = pCur->aEntries[j].offPart;
+ if (offCur >= off && offCur < offNext && pCur->aEntries[j].bType != 0)
+ offNext = offCur;
+ }
+ }
+ }
+ Assert(offNext >= off);
+ return offNext - off;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnQueryTableLocations */
+static DECLCALLBACK(int) rtDvmFmtMbrQueryTableLocations(RTDVMFMT hVolMgrFmt, uint32_t fFlags, PRTDVMTABLELOCATION paLocations,
+ size_t cLocations, size_t *pcActual)
+{
+ PRTDVMFMTINTERNAL pThis = hVolMgrFmt;
+ RT_NOREF(fFlags);
+
+ /*
+ * The MBR.
+ */
+ int rc = VINF_SUCCESS;
+ size_t iLoc = 0;
+ if (cLocations > 0)
+ {
+ paLocations[iLoc].off = pThis->Primary.offOnDisk;
+ paLocations[iLoc].cb = pThis->cbSector;
+ paLocations[iLoc].cbPadding = rtDvmFmtMbrCalcTablePadding(pThis, 0 + pThis->cbSector);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ iLoc++;
+
+ /*
+ * Now do the extended partitions.
+ *
+ * Remember, we only support multiple in the primary MBR, only the first
+ * one is honored in the chained ones.
+ */
+ for (unsigned i = 0; i < 4; i++)
+ {
+ for (PRTDVMMBRSECTOR pCur = pThis->Primary.aEntries[i].pChain;
+ pCur != NULL;
+ pCur = pCur->idxExtended != UINT8_MAX ? pCur->aEntries[pCur->idxExtended].pChain : NULL)
+ {
+ if (cLocations > iLoc)
+ {
+ paLocations[iLoc].off = pCur->offOnDisk;
+ paLocations[iLoc].cb = pThis->cbSector;
+ paLocations[iLoc].cbPadding = rtDvmFmtMbrCalcTablePadding(pThis, pCur->offOnDisk + pThis->cbSector);
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ iLoc++;
+ }
+ }
+
+ /*
+ * Return values.
+ */
+ if (pcActual)
+ *pcActual = iLoc;
+ else if (cLocations != iLoc && RT_SUCCESS(rc))
+ {
+ RT_BZERO(&paLocations[iLoc], (cLocations - iLoc) * sizeof(paLocations[0]));
+ rc = VERR_BUFFER_UNDERFLOW;
+ }
+ return rc;
+}
+
+static DECLCALLBACK(void) rtDvmFmtMbrVolumeClose(RTDVMVOLUMEFMT hVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ pVol->pVolMgr = NULL;
+ pVol->pEntry = NULL;
+
+ RTMemFree(pVol);
+}
+
+static DECLCALLBACK(uint64_t) rtDvmFmtMbrVolumeGetSize(RTDVMVOLUMEFMT hVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ return pVol->pEntry->cbPart;
+}
+
+static DECLCALLBACK(int) rtDvmFmtMbrVolumeQueryName(RTDVMVOLUMEFMT hVolFmt, char **ppszVolName)
+{
+ NOREF(hVolFmt); NOREF(ppszVolName);
+ return VERR_NOT_SUPPORTED;
+}
+
+static DECLCALLBACK(RTDVMVOLTYPE) rtDvmFmtMbrVolumeGetType(RTDVMVOLUMEFMT hVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ uint8_t const bType = pVol->pEntry->bType;
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aFs2DvmVolTypes); i++)
+ if (g_aFs2DvmVolTypes[i].bFsId == bType)
+ return g_aFs2DvmVolTypes[i].enmVolType;
+
+ return RTDVMVOLTYPE_UNKNOWN;
+}
+
+static DECLCALLBACK(uint64_t) rtDvmFmtMbrVolumeGetFlags(RTDVMVOLUMEFMT hVolFmt)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ uint64_t fFlags = DVMVOLUME_F_CONTIGUOUS;
+ if (pVol->pEntry->fFlags & 0x80)
+ fFlags |= DVMVOLUME_FLAGS_BOOTABLE | DVMVOLUME_FLAGS_ACTIVE;
+
+ return fFlags;
+}
+
+static DECLCALLBACK(int) rtDvmFmtMbrVolumeQueryRange(RTDVMVOLUMEFMT hVolFmt, uint64_t *poffStart, uint64_t *poffLast)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ *poffStart = pVol->pEntry->offPart;
+ *poffLast = pVol->pEntry->offPart + pVol->pEntry->cbPart - 1;
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(bool) rtDvmFmtMbrVolumeIsRangeIntersecting(RTDVMVOLUMEFMT hVolFmt, uint64_t offStart, size_t cbRange,
+ uint64_t *poffVol, uint64_t *pcbIntersect)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+
+ if (RTDVM_RANGE_IS_INTERSECTING(pVol->pEntry->offPart, pVol->pEntry->cbPart, offStart))
+ {
+ *poffVol = offStart - pVol->pEntry->offPart;
+ *pcbIntersect = RT_MIN(cbRange, pVol->pEntry->offPart + pVol->pEntry->cbPart - offStart);
+ return true;
+ }
+ return false;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnVolumeQueryTableLocation */
+static DECLCALLBACK(int) rtDvmFmtMbrVolumeQueryTableLocation(RTDVMVOLUMEFMT hVolFmt, uint64_t *poffTable, uint64_t *pcbTable)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ *poffTable = pVol->pEntry->pSector->offOnDisk;
+ *pcbTable = RT_MAX(512, pVol->pVolMgr->pDisk->cbSector);
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnVolumeGetIndex */
+static DECLCALLBACK(uint32_t) rtDvmFmtMbrVolumeGetIndex(RTDVMVOLUMEFMT hVolFmt, RTDVMVOLIDX enmIndex)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ switch (enmIndex)
+ {
+ case RTDVMVOLIDX_USER_VISIBLE:
+ return pVol->pEntry->idxVisible;
+ case RTDVMVOLIDX_ALL:
+ return pVol->pEntry->idxAll;
+ case RTDVMVOLIDX_IN_TABLE:
+ return pVol->pEntry->idxTable;
+ case RTDVMVOLIDX_LINUX:
+ return pVol->pEntry->idxLinux;
+
+ case RTDVMVOLIDX_INVALID:
+ case RTDVMVOLIDX_HOST:
+ case RTDVMVOLIDX_END:
+ case RTDVMVOLIDX_32BIT_HACK:
+ break;
+ /* no default! */
+ }
+ return UINT32_MAX;
+}
+
+/** @copydoc RTDVMFMTOPS::pfnVolumeQueryProp */
+static DECLCALLBACK(int) rtDvmFmtMbrVolumeQueryProp(RTDVMVOLUMEFMT hVolFmt, RTDVMVOLPROP enmProperty,
+ void *pvBuf, size_t cbBuf, size_t *pcbBuf)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ switch (enmProperty)
+ {
+ case RTDVMVOLPROP_MBR_FIRST_CYLINDER:
+ *pcbBuf = sizeof(uint16_t);
+ Assert(cbBuf >= *pcbBuf);
+ *(uint16_t *)pvBuf = pVol->pEntry->FirstChs.uCylinder;
+ return VINF_SUCCESS;
+ case RTDVMVOLPROP_MBR_LAST_CYLINDER:
+ *pcbBuf = sizeof(uint16_t);
+ Assert(cbBuf >= *pcbBuf);
+ *(uint16_t *)pvBuf = pVol->pEntry->LastChs.uCylinder;
+ return VINF_SUCCESS;
+
+ case RTDVMVOLPROP_MBR_FIRST_HEAD:
+ *pcbBuf = sizeof(uint8_t);
+ Assert(cbBuf >= *pcbBuf);
+ *(uint8_t *)pvBuf = pVol->pEntry->FirstChs.uHead;
+ return VINF_SUCCESS;
+ case RTDVMVOLPROP_MBR_LAST_HEAD:
+ *pcbBuf = sizeof(uint8_t);
+ Assert(cbBuf >= *pcbBuf);
+ *(uint8_t *)pvBuf = pVol->pEntry->LastChs.uHead;
+ return VINF_SUCCESS;
+
+ case RTDVMVOLPROP_MBR_FIRST_SECTOR:
+ *pcbBuf = sizeof(uint8_t);
+ Assert(cbBuf >= *pcbBuf);
+ *(uint8_t *)pvBuf = pVol->pEntry->FirstChs.uSector;
+ return VINF_SUCCESS;
+ case RTDVMVOLPROP_MBR_LAST_SECTOR:
+ *pcbBuf = sizeof(uint8_t);
+ Assert(cbBuf >= *pcbBuf);
+ *(uint8_t *)pvBuf = pVol->pEntry->LastChs.uSector;
+ return VINF_SUCCESS;
+
+ case RTDVMVOLPROP_MBR_TYPE:
+ *pcbBuf = sizeof(uint8_t);
+ Assert(cbBuf >= *pcbBuf);
+ *(uint8_t *)pvBuf = pVol->pEntry->bType;
+ return VINF_SUCCESS;
+
+ case RTDVMVOLPROP_GPT_TYPE:
+ case RTDVMVOLPROP_GPT_UUID:
+ return VERR_NOT_SUPPORTED;
+
+ case RTDVMVOLPROP_INVALID:
+ case RTDVMVOLPROP_END:
+ case RTDVMVOLPROP_32BIT_HACK:
+ break;
+ /* not default! */
+ }
+ RT_NOREF(cbBuf);
+ AssertFailed();
+ return VERR_NOT_SUPPORTED;
+}
+
+static DECLCALLBACK(int) rtDvmFmtMbrVolumeRead(RTDVMVOLUMEFMT hVolFmt, uint64_t off, void *pvBuf, size_t cbRead)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ AssertReturn(off + cbRead <= pVol->pEntry->cbPart, VERR_INVALID_PARAMETER);
+
+ return rtDvmDiskRead(pVol->pVolMgr->pDisk, pVol->pEntry->offPart + off, pvBuf, cbRead);
+}
+
+static DECLCALLBACK(int) rtDvmFmtMbrVolumeWrite(RTDVMVOLUMEFMT hVolFmt, uint64_t off, const void *pvBuf, size_t cbWrite)
+{
+ PRTDVMVOLUMEFMTINTERNAL pVol = hVolFmt;
+ AssertReturn(off + cbWrite <= pVol->pEntry->cbPart, VERR_INVALID_PARAMETER);
+
+ return rtDvmDiskWrite(pVol->pVolMgr->pDisk, pVol->pEntry->offPart + off, pvBuf, cbWrite);
+}
+
+DECL_HIDDEN_CONST(const RTDVMFMTOPS) g_rtDvmFmtMbr =
+{
+ /* pszFmt */
+ "MBR",
+ /* enmFormat */
+ RTDVMFORMATTYPE_MBR,
+ /* pfnProbe */
+ rtDvmFmtMbrProbe,
+ /* pfnOpen */
+ rtDvmFmtMbrOpen,
+ /* pfnInitialize */
+ rtDvmFmtMbrInitialize,
+ /* pfnClose */
+ rtDvmFmtMbrClose,
+ /* pfnQueryRangeUse */
+ rtDvmFmtMbrQueryRangeUse,
+ /* pfnQueryDiskUuid */
+ rtDvmFmtMbrQueryDiskUuid,
+ /* pfnGetValidVolumes */
+ rtDvmFmtMbrGetValidVolumes,
+ /* pfnGetMaxVolumes */
+ rtDvmFmtMbrGetMaxVolumes,
+ /* pfnQueryFirstVolume */
+ rtDvmFmtMbrQueryFirstVolume,
+ /* pfnQueryNextVolume */
+ rtDvmFmtMbrQueryNextVolume,
+ /* pfnQueryTableLocations */
+ rtDvmFmtMbrQueryTableLocations,
+ /* pfnVolumeClose */
+ rtDvmFmtMbrVolumeClose,
+ /* pfnVolumeGetSize */
+ rtDvmFmtMbrVolumeGetSize,
+ /* pfnVolumeQueryName */
+ rtDvmFmtMbrVolumeQueryName,
+ /* pfnVolumeGetType */
+ rtDvmFmtMbrVolumeGetType,
+ /* pfnVolumeGetFlags */
+ rtDvmFmtMbrVolumeGetFlags,
+ /* pfnVolumeQueryRange */
+ rtDvmFmtMbrVolumeQueryRange,
+ /* pfnVOlumeIsRangeIntersecting */
+ rtDvmFmtMbrVolumeIsRangeIntersecting,
+ /* pfnVolumeQueryTableLocation */
+ rtDvmFmtMbrVolumeQueryTableLocation,
+ /* pfnVolumeGetIndex */
+ rtDvmFmtMbrVolumeGetIndex,
+ /* pfnVolumeQueryProp */
+ rtDvmFmtMbrVolumeQueryProp,
+ /* pfnVolumeRead */
+ rtDvmFmtMbrVolumeRead,
+ /* pfnVolumeWrite */
+ rtDvmFmtMbrVolumeWrite
+};
+
diff --git a/src/VBox/Runtime/common/dvm/dvmvfs.cpp b/src/VBox/Runtime/common/dvm/dvmvfs.cpp
new file mode 100644
index 00000000..f22f02f9
--- /dev/null
+++ b/src/VBox/Runtime/common/dvm/dvmvfs.cpp
@@ -0,0 +1,1467 @@
+/* $Id: dvmvfs.cpp $ */
+/** @file
+ * IPRT Disk Volume Management API (DVM) - VFS glue.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS /** @todo fix log group */
+#include <iprt/types.h>
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/dvm.h>
+#include <iprt/err.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/file.h>
+#include <iprt/sg.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/poll.h>
+#include <iprt/log.h>
+#include "internal/dvm.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a volume manager VFS. */
+typedef struct RTDVMVFSVOL *PRTDVMVFSVOL;
+
+/**
+ * The internal data of a DVM volume I/O stream.
+ */
+typedef struct RTVFSDVMFILE
+{
+ /** The volume the VFS file belongs to. */
+ RTDVMVOLUME hVol;
+ /** Pointer to the VFS volume. Can be NULL. */
+ PRTDVMVFSVOL pVfsVol;
+ /** Current position. */
+ uint64_t offCurPos;
+ /** Set if readable. */
+ bool fCanRead;
+ /** Set if writable. */
+ bool fCanWrite;
+} RTVFSDVMFILE;
+/** Pointer to a the internal data of a DVM volume file. */
+typedef RTVFSDVMFILE *PRTVFSDVMFILE;
+
+/**
+ * The internal data of a DVM volume symlink.
+ */
+typedef struct RTVFSDVMSYMLINK
+{
+ /** The DVM volume the symlink represent. */
+ RTDVMVOLUME hVol;
+ /** The DVM volume manager @a hVol belongs to. */
+ RTDVM hVolMgr;
+ /** The symlink name. */
+ char *pszSymlink;
+ /** The symlink target (volXX). */
+ char szTarget[16];
+} RTVFSDVMSYMLINK;
+/** Pointer to a the internal data of a DVM volume file. */
+typedef RTVFSDVMSYMLINK *PRTVFSDVMSYMLINK;
+
+/**
+ * The volume manager VFS (root) dir data.
+ */
+typedef struct RTDVMVFSDIR
+{
+ /** Pointer to the VFS volume. */
+ PRTDVMVFSVOL pVfsVol;
+ /** The current directory offset. */
+ uint32_t offDir;
+ /** Set if we need to try return hCurVolume again because of buffer overflow. */
+ bool fReturnCurrent;
+ /** Pointer to name alias string (returned by RTDvmVolumeQueryName, free it). */
+ char *pszNameAlias;
+ /** The current DVM volume. */
+ RTDVMVOLUME hCurVolume;
+} RTDVMVFSDIR;
+/** Pointer to a volume manager VFS (root) dir. */
+typedef RTDVMVFSDIR *PRTDVMVFSDIR;
+
+/**
+ * A volume manager VFS for use in chains (thing pseudo/devfs).
+ */
+typedef struct RTDVMVFSVOL
+{
+ /** The volume manager. */
+ RTDVM hVolMgr;
+ /** Whether to close it on success. */
+ bool fCloseDvm;
+ /** Whether the access is read-only. */
+ bool fReadOnly;
+ /** Number of volumes. */
+ uint32_t cVolumes;
+ /** Self reference. */
+ RTVFS hVfsSelf;
+} RTDVMVFSVOL;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) rtDvmVfsVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir);
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_Close(void *pvThis)
+{
+ PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis;
+
+ RTDvmVolumeRelease(pThis->hVol);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtDvmVfsFile_QueryInfoWorker and rtDvmVfsSym_QueryInfoWorker.
+ */
+static int rtDvmVfsFileSym_QueryAddAttrWorker(RTDVMVOLUME hVolume, RTDVM hVolMgr,
+ PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.u.Unix.uid = (RTUID)RTDvmVolumeGetType(hVolume);
+ pObjInfo->Attr.u.Unix.gid = hVolMgr != NIL_RTDVM ? (RTGID)RTDvmMapGetFormatType(hVolMgr) : NIL_RTGID;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = 0;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ {
+ RTDVMVOLTYPE enmType = RTDvmVolumeGetType(hVolume);
+ pObjInfo->Attr.u.UnixOwner.uid = (RTUID)enmType;
+ RTStrCopy(pObjInfo->Attr.u.UnixOwner.szName, sizeof(pObjInfo->Attr.u.UnixOwner.szName),
+ RTDvmVolumeTypeGetDescr(enmType));
+ break;
+ }
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ if (hVolMgr != NIL_RTDVM)
+ {
+ pObjInfo->Attr.u.UnixGroup.gid = (RTGID)RTDvmMapGetFormatType(hVolMgr);
+ RTStrCopy(pObjInfo->Attr.u.UnixGroup.szName, sizeof(pObjInfo->Attr.u.UnixGroup.szName),
+ RTDvmMapGetFormatName(hVolMgr));
+ }
+ else
+ {
+ pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ }
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtDvmVfsFile_QueryInfo, rtDvmVfsDir_QueryEntryInfo, and
+ * rtDvmVfsDir_ReadDir.
+ */
+static int rtDvmVfsFile_QueryInfoWorker(RTDVMVOLUME hVolume, RTDVM hVolMgr, bool fReadOnly,
+ PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+
+ pObjInfo->cbObject = RTDvmVolumeGetSize(hVolume);
+ pObjInfo->cbAllocated = pObjInfo->cbObject;
+ RTTimeSpecSetNano(&pObjInfo->AccessTime, 0);
+ RTTimeSpecSetNano(&pObjInfo->ModificationTime, 0);
+ RTTimeSpecSetNano(&pObjInfo->ChangeTime, 0);
+ RTTimeSpecSetNano(&pObjInfo->BirthTime, 0);
+ pObjInfo->Attr.fMode = RTFS_TYPE_FILE | RTFS_DOS_NT_NORMAL;
+ if (fReadOnly)
+ pObjInfo->Attr.fMode |= RTFS_DOS_READONLY | 0444;
+ else
+ pObjInfo->Attr.fMode |= 0666;
+
+ return rtDvmVfsFileSym_QueryAddAttrWorker(hVolume, hVolMgr, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis;
+ return rtDvmVfsFile_QueryInfoWorker(pThis->hVol,
+ pThis->pVfsVol ? pThis->pVfsVol->hVolMgr : NIL_RTDVM,
+ pThis->pVfsVol ? pThis->pVfsVol->fReadOnly : !pThis->fCanWrite,
+ pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis;
+ int rc = VINF_SUCCESS;
+
+ Assert(pSgBuf->cSegs == 1);
+ NOREF(fBlocking);
+
+ /*
+ * Find the current position and check if it's within the volume.
+ */
+ uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off;
+ if (offUnsigned >= RTDvmVolumeGetSize(pThis->hVol))
+ {
+ if (pcbRead)
+ {
+ *pcbRead = 0;
+ pThis->offCurPos = offUnsigned;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+
+ size_t cbLeftToRead;
+ if (offUnsigned + pSgBuf->paSegs[0].cbSeg > RTDvmVolumeGetSize(pThis->hVol))
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ *pcbRead = cbLeftToRead = (size_t)(RTDvmVolumeGetSize(pThis->hVol) - offUnsigned);
+ }
+ else
+ {
+ cbLeftToRead = pSgBuf->paSegs[0].cbSeg;
+ if (pcbRead)
+ *pcbRead = cbLeftToRead;
+ }
+
+ /*
+ * Ok, we've got a valid stretch within the file. Do the reading.
+ */
+ if (cbLeftToRead > 0)
+ {
+ rc = RTDvmVolumeRead(pThis->hVol, offUnsigned, pSgBuf->paSegs[0].pvSeg, cbLeftToRead);
+ if (RT_SUCCESS(rc))
+ offUnsigned += cbLeftToRead;
+ }
+
+ pThis->offCurPos = offUnsigned;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis;
+ int rc = VINF_SUCCESS;
+
+ Assert(pSgBuf->cSegs == 1);
+ NOREF(fBlocking);
+
+ /*
+ * Find the current position and check if it's within the volume.
+ * Writing beyond the end of a volume is not supported.
+ */
+ uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off;
+ if (offUnsigned >= RTDvmVolumeGetSize(pThis->hVol))
+ {
+ if (pcbWritten)
+ {
+ *pcbWritten = 0;
+ pThis->offCurPos = offUnsigned;
+ }
+ return VERR_NOT_SUPPORTED;
+ }
+
+ size_t cbLeftToWrite;
+ if (offUnsigned + pSgBuf->paSegs[0].cbSeg > RTDvmVolumeGetSize(pThis->hVol))
+ {
+ if (!pcbWritten)
+ return VERR_EOF;
+ *pcbWritten = cbLeftToWrite = (size_t)(RTDvmVolumeGetSize(pThis->hVol) - offUnsigned);
+ }
+ else
+ {
+ cbLeftToWrite = pSgBuf->paSegs[0].cbSeg;
+ if (pcbWritten)
+ *pcbWritten = cbLeftToWrite;
+ }
+
+ /*
+ * Ok, we've got a valid stretch within the file. Do the reading.
+ */
+ if (cbLeftToWrite > 0)
+ {
+ rc = RTDvmVolumeWrite(pThis->hVol, offUnsigned, pSgBuf->paSegs[0].pvSeg, cbLeftToWrite);
+ if (RT_SUCCESS(rc))
+ offUnsigned += cbLeftToWrite;
+ }
+
+ pThis->offCurPos = offUnsigned;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_Flush(void *pvThis)
+{
+ NOREF(pvThis);
+ return VINF_SUCCESS; /** @todo Implement missing DVM API. */
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis;
+ *poffActual = pThis->offCurPos;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ NOREF(pvThis);
+ NOREF(fMode);
+ NOREF(fMask);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ NOREF(pvThis);
+ NOREF(pAccessTime);
+ NOREF(pModificationTime);
+ NOREF(pChangeTime);
+ NOREF(pBirthTime);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ NOREF(pvThis);
+ NOREF(uid);
+ NOREF(gid);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis;
+
+ /*
+ * Seek relative to which position.
+ */
+ uint64_t offWrt;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offWrt = 0;
+ break;
+
+ case RTFILE_SEEK_CURRENT:
+ offWrt = pThis->offCurPos;
+ break;
+
+ case RTFILE_SEEK_END:
+ offWrt = RTDvmVolumeGetSize(pThis->hVol);
+ break;
+
+ default:
+ return VERR_INTERNAL_ERROR_5;
+ }
+
+ /*
+ * Calc new position, take care to stay within bounds.
+ *
+ * @todo: Setting position beyond the end of the volume does not make sense.
+ */
+ uint64_t offNew;
+ if (offSeek == 0)
+ offNew = offWrt;
+ else if (offSeek > 0)
+ {
+ offNew = offWrt + offSeek;
+ if ( offNew < offWrt
+ || offNew > RTFOFF_MAX)
+ offNew = RTFOFF_MAX;
+ }
+ else if ((uint64_t)-offSeek < offWrt)
+ offNew = offWrt + offSeek;
+ else
+ offNew = 0;
+
+ /*
+ * Update the state and set return value.
+ */
+ pThis->offCurPos = offNew;
+
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtDvmVfsFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTVFSDVMFILE pThis = (PRTVFSDVMFILE)pvThis;
+ *pcbFile = RTDvmVolumeGetSize(pThis->hVol);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Standard file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtDvmVfsStdFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "DvmFile",
+ rtDvmVfsFile_Close,
+ rtDvmVfsFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtDvmVfsFile_Read,
+ rtDvmVfsFile_Write,
+ rtDvmVfsFile_Flush,
+ NULL /*pfnPollOne*/,
+ rtDvmVfsFile_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ /*RTVFSIOFILEOPS_FEAT_NO_AT_OFFSET*/ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtDvmVfsFile_SetMode,
+ rtDvmVfsFile_SetTimes,
+ rtDvmVfsFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtDvmVfsFile_Seek,
+ rtDvmVfsFile_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Internal worker for RTDvmVolumeCreateVfsFile and rtDvmVfsDir_OpenFile.
+ *
+ * @returns IPRT status code.
+ * @param pVfsVol The VFS volume, optional.
+ * @param hVol The volume handle. (Reference not consumed.)
+ * @param fOpen RTFILE_O_XXX (valid).
+ * @param phVfsFileOut Where to return the handle to the file.
+ */
+static int rtDvmVfsCreateFileForVolume(PRTDVMVFSVOL pVfsVol, RTDVMVOLUME hVol, uint64_t fOpen, PRTVFSFILE phVfsFileOut)
+{
+ uint32_t cRefs = RTDvmVolumeRetain(hVol);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create the volume file.
+ */
+ RTVFSFILE hVfsFile;
+ PRTVFSDVMFILE pThis;
+ int rc = RTVfsNewFile(&g_rtDvmVfsStdFileOps, sizeof(*pThis), fOpen, NIL_RTVFS, NIL_RTVFSLOCK, &hVfsFile, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offCurPos = 0;
+ pThis->hVol = hVol;
+ pThis->fCanRead = RT_BOOL(fOpen & RTFILE_O_READ);
+ pThis->fCanWrite = RT_BOOL(fOpen & RTFILE_O_WRITE);
+ pThis->pVfsVol = pVfsVol;
+
+ *phVfsFileOut = hVfsFile;
+ return VINF_SUCCESS;
+ }
+
+ RTDvmVolumeRelease(hVol);
+ return rc;
+}
+
+
+RTDECL(int) RTDvmVolumeCreateVfsFile(RTDVMVOLUME hVol, uint64_t fOpen, PRTVFSFILE phVfsFileOut)
+{
+ AssertPtrReturn(hVol, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phVfsFileOut, VERR_INVALID_POINTER);
+ AssertReturn(fOpen & RTFILE_O_ACCESS_MASK, VERR_INVALID_FLAGS);
+ AssertReturn(!(fOpen & ~RTFILE_O_VALID_MASK), VERR_INVALID_FLAGS);
+ return rtDvmVfsCreateFileForVolume(NULL, hVol, fOpen, phVfsFileOut);
+}
+
+
+/*********************************************************************************************************************************
+* DVM Symbolic Link Objects *
+*********************************************************************************************************************************/
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtDvmVfsSym_Close(void *pvThis)
+{
+ PRTVFSDVMSYMLINK pThis = (PRTVFSDVMSYMLINK)pvThis;
+ if (pThis->pszSymlink)
+ {
+ RTStrFree(pThis->pszSymlink);
+ pThis->pszSymlink = NULL;
+ }
+ if (pThis->hVol != NIL_RTDVMVOLUME)
+ {
+ RTDvmVolumeRelease(pThis->hVol);
+ pThis->hVol = NIL_RTDVMVOLUME;
+ }
+ if (pThis->hVolMgr != NIL_RTDVM)
+ {
+ RTDvmRelease(pThis->hVolMgr);
+ pThis->hVolMgr = NIL_RTDVM;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtDvmVfsSym_QueryInfo and rtDvmVfsDir_Read.
+ *
+ * @returns IPRT status code.
+ * @param hVolume The volume handle.
+ * @param hVolMgr The volume manager handle. Optional.
+ * @param pszTarget The link target.
+ * @param pObjInfo The object info structure to populate.
+ * @param enmAddAttr The additional attributes to supply.
+ */
+static int rtDvmVfsSym_QueryInfoWorker(RTDVMVOLUME hVolume, RTDVM hVolMgr, const char *pszTarget,
+ PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_ZERO(*pObjInfo);
+ pObjInfo->cbObject = pObjInfo->cbAllocated = pszTarget ? strlen(pszTarget) : 0;
+ pObjInfo->Attr.fMode = 0777 | RTFS_TYPE_SYMLINK | RTFS_DOS_NT_REPARSE_POINT;
+
+ return rtDvmVfsFileSym_QueryAddAttrWorker(hVolume, hVolMgr, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtDvmVfsSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSDVMSYMLINK pThis = (PRTVFSDVMSYMLINK)pvThis;
+ return rtDvmVfsSym_QueryInfoWorker(pThis->hVol, pThis->hVolMgr, pThis->szTarget, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSSYMLINKOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtDvmVfsSym_Read(void *pvThis, char *pszTarget, size_t cbTarget)
+{
+ PRTVFSDVMSYMLINK pThis = (PRTVFSDVMSYMLINK)pvThis;
+ return RTStrCopy(pszTarget, cbTarget, pThis->szTarget);
+}
+
+
+/**
+ * DVM symbolic link operations.
+ */
+static const RTVFSSYMLINKOPS g_rtDvmVfsSymOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_SYMLINK,
+ "DvmSymlink",
+ rtDvmVfsSym_Close,
+ rtDvmVfsSym_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSSYMLINKOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj),
+ NULL /*rtDvmVfsSym_SetMode*/,
+ NULL /*rtDvmVfsSym_SetTimes*/,
+ NULL /*rtDvmVfsSym_SetOwner*/,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtDvmVfsSym_Read,
+ RTVFSSYMLINKOPS_VERSION
+};
+
+
+/**
+ * Internal worker for rtDvmVfsDir_OpenFile.
+ *
+ * @returns IPRT status code.
+ * @param hVol The volume handle (not consumed).
+ * @param hVolMgr The volume manager handle (not consumed).
+ * @param iVol The volume number.
+ * @param pszSymlink The volume name. Consumed on success.
+ * @param phVfsSymlinkOut Where to return the handle to the file.
+ */
+static int rtDvmVfsCreateSymlinkForVolume(RTDVMVOLUME hVol, RTDVM hVolMgr, uint32_t iVol, char *pszSymlink,
+ PRTVFSSYMLINK phVfsSymlinkOut)
+{
+ uint32_t cRefs = RTDvmVolumeRetain(hVol);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ cRefs = RTDvmRetain(hVolMgr);
+ AssertReturnStmt(cRefs != UINT32_MAX, RTDvmVolumeRelease(hVol), VERR_INVALID_HANDLE);
+
+ /*
+ * Create the symlink.
+ */
+ RTVFSSYMLINK hVfsSym;
+ PRTVFSDVMSYMLINK pThis;
+ int rc = RTVfsNewSymlink(&g_rtDvmVfsSymOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, &hVfsSym, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVol = hVol;
+ pThis->hVolMgr = hVolMgr;
+ pThis->pszSymlink = pszSymlink;
+ RTStrPrintf(pThis->szTarget, sizeof(pThis->szTarget), "vol%u", iVol);
+
+ *phVfsSymlinkOut = hVfsSym;
+ return VINF_SUCCESS;
+ }
+ RTDvmRelease(hVolMgr);
+ RTDvmVolumeRelease(hVol);
+ return rc;
+}
+
+
+
+/*********************************************************************************************************************************
+* DVM Directory Objects *
+*********************************************************************************************************************************/
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_Close(void *pvThis)
+{
+ PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis;
+
+ if (pThis->hCurVolume != NIL_RTDVMVOLUME)
+ {
+ RTDvmVolumeRelease(pThis->hCurVolume);
+ pThis->hCurVolume = NIL_RTDVMVOLUME;
+ }
+
+ if (pThis->pszNameAlias)
+ {
+ RTStrFree(pThis->pszNameAlias);
+ pThis->pszNameAlias = NULL;
+ }
+
+ pThis->pVfsVol = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis;
+ pObjInfo->cbObject = pThis->pVfsVol->cVolumes;
+ pObjInfo->cbAllocated = pThis->pVfsVol->cVolumes;
+ RTTimeSpecSetNano(&pObjInfo->AccessTime, 0);
+ RTTimeSpecSetNano(&pObjInfo->ModificationTime, 0);
+ RTTimeSpecSetNano(&pObjInfo->ChangeTime, 0);
+ RTTimeSpecSetNano(&pObjInfo->BirthTime, 0);
+ pObjInfo->Attr.fMode = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY;
+ if (pThis->pVfsVol->fReadOnly)
+ pObjInfo->Attr.fMode |= RTFS_DOS_READONLY | 0555;
+ else
+ pObjInfo->Attr.fMode |= 0777;
+
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
+ pObjInfo->Attr.u.Unix.gid = (RTGID)RTDvmMapGetFormatType(pThis->pVfsVol->hVolMgr);
+ pObjInfo->Attr.u.Unix.cHardlinks = pThis->pVfsVol->cVolumes;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = 0;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = (RTGID)RTDvmMapGetFormatType(pThis->pVfsVol->hVolMgr);
+ RTStrCopy(pObjInfo->Attr.u.UnixGroup.szName, sizeof(pObjInfo->Attr.u.UnixGroup.szName),
+ RTDvmMapGetFormatName(pThis->pVfsVol->hVolMgr));
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ NOREF(pvThis); NOREF(fMode); NOREF(fMask);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ NOREF(pvThis); NOREF(pAccessTime); NOREF(pModificationTime); NOREF(pChangeTime); NOREF(pBirthTime);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+static int rtDvmVfsDir_FindEntry(PRTDVMVFSDIR pThis, const char *pszEntry,
+ PRTDVMVOLUME phVolume, uint32_t *piVol, char **ppszSymlink)
+{
+ *phVolume = NIL_RTDVMVOLUME;
+ *ppszSymlink = NULL;
+ *piVol = UINT32_MAX;
+
+ /*
+ * Enumerate the volumes and try match the volume name.
+ */
+ int rc;
+ PRTDVMVFSVOL pVfsVol = pThis->pVfsVol;
+ if (pVfsVol->cVolumes > 0)
+ {
+ /* The first volume. */
+ uint32_t iVol = 0;
+ RTDVMVOLUME hVol;
+ rc = RTDvmMapQueryFirstVolume(pThis->pVfsVol->hVolMgr, &hVol);
+ while (RT_SUCCESS(rc))
+ {
+ /* Match the name. */
+ bool fMatch;
+ char *pszVolName;
+ rc = RTDvmVolumeQueryName(hVol, &pszVolName);
+ if (RT_SUCCESS(rc))
+ {
+ fMatch = RTStrCmp(pszEntry, pszVolName) == 0 && *pszVolName != '\0';
+ if (fMatch)
+ {
+ *phVolume = hVol;
+ *ppszSymlink = pszVolName;
+ *piVol = iVol;
+ return VINF_SUCCESS;
+ }
+ RTStrFree(pszVolName);
+ }
+ else if (rc == VERR_NOT_SUPPORTED)
+ fMatch = false;
+ else
+ {
+ RTDvmVolumeRelease(hVol);
+ break;
+ }
+
+ /* Match the sequential volume number. */
+ if (!fMatch)
+ {
+ char szTmp[16];
+ RTStrPrintf(szTmp, sizeof(szTmp), "vol%u", iVol);
+ fMatch = RTStrCmp(pszEntry, szTmp) == 0;
+ }
+
+ if (fMatch)
+ {
+ *phVolume = hVol;
+ *piVol = iVol;
+ return VINF_SUCCESS;
+ }
+
+ /* More volumes? */
+ iVol++;
+ if (iVol >= pVfsVol->cVolumes)
+ {
+ RTDvmVolumeRelease(hVol);
+ rc = VERR_FILE_NOT_FOUND;
+ break;
+ }
+
+ /* Get the next volume. */
+ RTDVMVOLUME hVolNext;
+ rc = RTDvmMapQueryNextVolume(pThis->pVfsVol->hVolMgr, hVol, &hVolNext);
+ RTDvmVolumeRelease(hVol);
+ hVol = hVolNext;
+ }
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen, uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis;
+
+ /*
+ * Special case: '.' and '..'
+ */
+ if ( pszEntry[0] == '.'
+ && ( pszEntry[1] == '\0'
+ || ( pszEntry[1] == '.'
+ && pszEntry[2] == '\0')))
+ {
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ {
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ RTVFSDIR hVfsDir;
+ int rc = rtDvmVfsVol_OpenRoot(pThis->pVfsVol, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ return rc;
+ }
+ return VERR_IS_A_DIRECTORY;
+ }
+ return VERR_ACCESS_DENIED;
+ }
+
+ /*
+ * Open volume file.
+ */
+ RTDVMVOLUME hVolume = NIL_RTDVMVOLUME;
+ uint32_t iVol = 0;
+ char *pszSymlink = NULL;
+ int rc = rtDvmVfsDir_FindEntry(pThis, pszEntry, &hVolume, &iVol, &pszSymlink);
+ if (RT_SUCCESS(rc))
+ {
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ {
+ if (fFlags & (RTVFSOBJ_F_OPEN_FILE | RTVFSOBJ_F_OPEN_DEV_BLOCK))
+ {
+ if (!pszSymlink)
+ {
+ if ( !(fOpen & RTFILE_O_WRITE)
+ || !pThis->pVfsVol->fReadOnly)
+ {
+ /* Create file object. */
+ RTVFSFILE hVfsFile;
+ rc = rtDvmVfsCreateFileForVolume(pThis->pVfsVol, hVolume, fOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+ }
+ else
+ rc = VERR_IS_A_SYMLINK;
+ }
+ else if (fFlags & RTVFSOBJ_F_OPEN_SYMLINK)
+ {
+ /* Create symlink object */
+ RTVFSSYMLINK hVfsSym = NIL_RTVFSSYMLINK; /* (older gcc maybe used uninitialized) */
+ rc = rtDvmVfsCreateSymlinkForVolume(hVolume, pThis->pVfsVol ? pThis->pVfsVol->hVolMgr : NIL_RTDVM, iVol,
+ pszSymlink, &hVfsSym);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromSymlink(hVfsSym);
+ RTVfsSymlinkRelease(hVfsSym);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ pszSymlink = NULL;
+ }
+ }
+ else
+ rc = VERR_IS_A_FILE;
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+ RTDvmVolumeRelease(hVolume);
+ if (pszSymlink)
+ RTStrFree(pszSymlink);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenFile}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_OpenFile(void *pvThis, const char *pszFilename, uint64_t fOpen, PRTVFSFILE phVfsFile)
+{
+ RTVFSOBJ hVfsObj;
+ int rc = rtDvmVfsDir_Open(pvThis, pszFilename, fOpen, RTVFSOBJ_F_OPEN_FILE, &hVfsObj);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsFile = RTVfsObjToFile(hVfsObj);
+ RTVfsObjRelease(hVfsObj);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_RewindDir(void *pvThis)
+{
+ PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis;
+
+ if (pThis->hCurVolume != NIL_RTDVMVOLUME)
+ {
+ RTDvmVolumeRelease(pThis->hCurVolume);
+ pThis->hCurVolume = NIL_RTDVMVOLUME;
+ }
+ pThis->fReturnCurrent = false;
+ pThis->offDir = 0;
+ if (pThis->pszNameAlias)
+ {
+ RTStrFree(pThis->pszNameAlias);
+ pThis->pszNameAlias = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtDvmVfsDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTDVMVFSDIR pThis = (PRTDVMVFSDIR)pvThis;
+ PRTDVMVFSVOL pVfsVol = pThis->pVfsVol;
+ int rc;
+
+ /*
+ * Format the volume name since we'll be needing it all but the final call.
+ */
+ char szVolNo[16];
+ size_t const cchVolNo = RTStrPrintf(szVolNo, sizeof(szVolNo), "vol%u", pThis->offDir);
+
+ if (!pThis->fReturnCurrent)
+ {
+ /*
+ * Do we have a pending name alias to return?
+ */
+ if (pThis->pszNameAlias)
+ {
+ size_t cchNameAlias = strlen(pThis->pszNameAlias);
+ size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cchNameAlias + 1]);
+ if (cbNeeded <= *pcbDirEntry)
+ {
+ *pcbDirEntry = cbNeeded;
+
+ /* Do the names. */
+ pDirEntry->cbName = (uint16_t)cchNameAlias;
+ memcpy(pDirEntry->szName, pThis->pszNameAlias, cchNameAlias + 1);
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+
+ /* Do the rest. */
+ rc = rtDvmVfsSym_QueryInfoWorker(pThis->hCurVolume, pVfsVol->hVolMgr, szVolNo, &pDirEntry->Info, enmAddAttr);
+ if (RT_SUCCESS(rc))
+ {
+ RTStrFree(pThis->pszNameAlias);
+ pThis->pszNameAlias = NULL;
+ pThis->offDir += 1;
+ }
+ return rc;
+ }
+
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /*
+ * Get the next volume to return info about.
+ */
+ if (pThis->offDir < pVfsVol->cVolumes)
+ {
+ RTDVMVOLUME hNextVolume;
+ if (pThis->offDir == 0)
+ rc = RTDvmMapQueryFirstVolume(pVfsVol->hVolMgr, &hNextVolume);
+ else
+ rc = RTDvmMapQueryNextVolume(pVfsVol->hVolMgr, pThis->hCurVolume, &hNextVolume);
+ if (RT_FAILURE(rc))
+ return rc;
+ RTDvmVolumeRelease(pThis->hCurVolume);
+ pThis->hCurVolume = hNextVolume;
+
+ /* Check if we need to return a name alias later. */
+ rc = RTDvmVolumeQueryName(pThis->hCurVolume, &pThis->pszNameAlias);
+ if (RT_FAILURE(rc))
+ pThis->pszNameAlias = NULL;
+ else if (*pThis->pszNameAlias == '\0')
+ {
+ RTStrFree(pThis->pszNameAlias);
+ pThis->pszNameAlias = NULL;
+ }
+ }
+ else
+ {
+ RTDvmVolumeRelease(pThis->hCurVolume);
+ pThis->hCurVolume = NIL_RTDVMVOLUME;
+ return VERR_NO_MORE_FILES;
+ }
+ }
+
+ /*
+ * Figure out the name length.
+ */
+ size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cchVolNo + 1]);
+ if (cbNeeded <= *pcbDirEntry)
+ {
+ *pcbDirEntry = cbNeeded;
+
+ /* Do the names. */
+ pDirEntry->cbName = (uint16_t)cchVolNo;
+ memcpy(pDirEntry->szName, szVolNo, cchVolNo + 1);
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+ /* Do the rest. */
+ rc = rtDvmVfsFile_QueryInfoWorker(pThis->hCurVolume, pVfsVol->hVolMgr, pVfsVol->fReadOnly, &pDirEntry->Info, enmAddAttr);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->fReturnCurrent = false;
+ if (!pThis->pszNameAlias)
+ pThis->offDir += 1;
+ return rc;
+ }
+ }
+ else
+ {
+ *pcbDirEntry = cbNeeded;
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ pThis->fReturnCurrent = true;
+ return rc;
+}
+
+
+/**
+ * DVM (root) directory operations.
+ */
+static const RTVFSDIROPS g_rtDvmVfsDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "DvmDir",
+ rtDvmVfsDir_Close,
+ rtDvmVfsDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtDvmVfsDir_SetMode,
+ rtDvmVfsDir_SetTimes,
+ rtDvmVfsDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtDvmVfsDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ rtDvmVfsDir_OpenFile,
+ NULL /* pfnOpenDir */,
+ rtDvmVfsDir_CreateDir,
+ rtDvmVfsDir_OpenSymlink,
+ rtDvmVfsDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtDvmVfsDir_UnlinkEntry,
+ rtDvmVfsDir_RenameEntry,
+ rtDvmVfsDir_RewindDir,
+ rtDvmVfsDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtDvmVfsVol_Close(void *pvThis)
+{
+ PRTDVMVFSVOL pThis = (PRTDVMVFSVOL)pvThis;
+ LogFlow(("rtDvmVfsVol_Close(%p)\n", pThis));
+
+ if ( pThis->fCloseDvm
+ && pThis->hVolMgr != NIL_RTDVM )
+ RTDvmRelease(pThis->hVolMgr);
+ pThis->hVolMgr = NIL_RTDVM;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtDvmVfsVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtDvmVfsVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTDVMVFSVOL pThis = (PRTDVMVFSVOL)pvThis;
+
+ PRTDVMVFSDIR pNewDir;
+ int rc = RTVfsNewDir(&g_rtDvmVfsDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf,
+ NIL_RTVFSLOCK /*use volume lock*/, phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ pNewDir->offDir = 0;
+ pNewDir->pVfsVol = pThis;
+ pNewDir->fReturnCurrent = false;
+ pNewDir->pszNameAlias = NULL;
+ pNewDir->hCurVolume = NIL_RTDVMVOLUME;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtDvmVfsVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed)
+{
+ RT_NOREF(pvThis, off, cb, pfUsed);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+DECL_HIDDEN_CONST(const RTVFSOPS) g_rtDvmVfsVolOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_VFS,
+ "DvmVol",
+ rtDvmVfsVol_Close,
+ rtDvmVfsVol_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSOPS_VERSION,
+ 0 /* fFeatures */,
+ rtDvmVfsVol_OpenRoot,
+ rtDvmVfsVol_QueryRangeState,
+ RTVFSOPS_VERSION
+};
+
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtDvmVfsChain_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
+ return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
+ if (pElement->enmType != RTVFSOBJTYPE_VFS)
+ return VERR_VFS_CHAIN_ONLY_VFS;
+
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ /** @todo allow specifying sector size */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if ( !strcmp(psz, "ro")
+ || !strcmp(psz, "r"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtDvmVfsChain_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ /*
+ * Instantiate the volume manager and open the map stuff.
+ */
+ RTVFSFILE hPrevVfsFile = RTVfsObjToFile(hPrevVfsObj);
+ AssertReturn(hPrevVfsFile != NIL_RTVFSFILE, VERR_VFS_CHAIN_CAST_FAILED);
+
+ RTDVM hVolMgr;
+ int rc = RTDvmCreate(&hVolMgr, hPrevVfsFile, 512, 0 /*fFlags*/);
+ RTVfsFileRelease(hPrevVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTDvmMapOpen(hVolMgr);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a VFS instance for the volume manager.
+ */
+ RTVFS hVfs = NIL_RTVFS;
+ PRTDVMVFSVOL pThis = NULL;
+ rc = RTVfsNew(&g_rtDvmVfsVolOps, sizeof(RTDVMVFSVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVolMgr = hVolMgr;
+ pThis->fCloseDvm = true;
+ pThis->fReadOnly = pElement->uProvider == (uint64_t)true;
+ pThis->cVolumes = RTDvmMapGetValidVolumes(hVolMgr);
+ pThis->hVfsSelf = hVfs;
+
+ *phVfsObj = RTVfsObjFromVfs(hVfs);
+ RTVfsRelease(hVfs);
+ return *phVfsObj != NIL_RTVFSOBJ ? VINF_SUCCESS : VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "RTDvmMapOpen failed: %Rrc", rc);
+ RTDvmRelease(hVolMgr);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "RTDvmCreate failed: %Rrc", rc);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtDvmVfsChain_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement);
+ return false;
+}
+
+
+/** VFS chain element 'file'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainIsoFsVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "dvm",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Opens a container image using the VD API.\n"
+ "Optionally takes one parameter 'ro' (read only) or 'rw' (read write).\n",
+ /* pfnValidate = */ rtDvmVfsChain_Validate,
+ /* pfnInstantiate = */ rtDvmVfsChain_Instantiate,
+ /* pfnCanReuseElement = */ rtDvmVfsChain_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainIsoFsVolReg, rtVfsChainIsoFsVolReg);
+
diff --git a/src/VBox/Runtime/common/efi/efiguid.cpp b/src/VBox/Runtime/common/efi/efiguid.cpp
new file mode 100644
index 00000000..31fda39b
--- /dev/null
+++ b/src/VBox/Runtime/common/efi/efiguid.cpp
@@ -0,0 +1,134 @@
+/* $Id: efiguid.cpp $ */
+/** @file
+ * IPRT - EFI GUID conversion helpers.
+ */
+
+/*
+ * Copyright (C) 2021-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DEFAULT
+#include <iprt/efi.h>
+
+#include <iprt/cdefs.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+RTDECL(PRTUUID) RTEfiGuidToUuid(PRTUUID pUuid, PCEFI_GUID pEfiGuid)
+{
+ pUuid->Gen.u32TimeLow = RT_LE2H_U32(pEfiGuid->u32Data1);
+ pUuid->Gen.u16TimeMid = RT_LE2H_U16(pEfiGuid->u16Data2);
+ pUuid->Gen.u16TimeHiAndVersion = RT_LE2H_U16(pEfiGuid->u16Data3);
+ pUuid->Gen.u8ClockSeqHiAndReserved = pEfiGuid->abData4[0];
+ pUuid->Gen.u8ClockSeqLow = pEfiGuid->abData4[1];
+ pUuid->Gen.au8Node[0] = pEfiGuid->abData4[2];
+ pUuid->Gen.au8Node[1] = pEfiGuid->abData4[3];
+ pUuid->Gen.au8Node[2] = pEfiGuid->abData4[4];
+ pUuid->Gen.au8Node[3] = pEfiGuid->abData4[5];
+ pUuid->Gen.au8Node[4] = pEfiGuid->abData4[6];
+ pUuid->Gen.au8Node[5] = pEfiGuid->abData4[7];
+ return pUuid;
+}
+
+
+RTDECL(PEFI_GUID) RTEfiGuidFromUuid(PEFI_GUID pEfiGuid, PCRTUUID pUuid)
+{
+ pEfiGuid->u32Data1 = RT_H2LE_U32(pUuid->Gen.u32TimeLow);
+ pEfiGuid->u16Data2 = RT_H2LE_U16(pUuid->Gen.u16TimeMid);
+ pEfiGuid->u16Data3 = RT_H2LE_U16(pUuid->Gen.u16TimeHiAndVersion);
+ pEfiGuid->abData4[0] = pUuid->Gen.u8ClockSeqHiAndReserved;
+ pEfiGuid->abData4[1] = pUuid->Gen.u8ClockSeqLow;
+ pEfiGuid->abData4[2] = pUuid->Gen.au8Node[0];
+ pEfiGuid->abData4[3] = pUuid->Gen.au8Node[1];
+ pEfiGuid->abData4[4] = pUuid->Gen.au8Node[2];
+ pEfiGuid->abData4[5] = pUuid->Gen.au8Node[3];
+ pEfiGuid->abData4[6] = pUuid->Gen.au8Node[4];
+ pEfiGuid->abData4[7] = pUuid->Gen.au8Node[5];
+ return pEfiGuid;
+}
+
+
+RTDECL(int) RTEfiGuidCompare(PCEFI_GUID pGuid1, PCEFI_GUID pGuid2)
+{
+ /*
+ * Special cases.
+ */
+ if (pGuid1 == pGuid2)
+ return 0;
+ AssertPtrReturn(pGuid1, -1);
+ AssertPtrReturn(pGuid2, 1);
+
+ /*
+ * Standard cases.
+ */
+ if (pGuid1->u32Data1 != pGuid2->u32Data1)
+ return pGuid1->u32Data1 < pGuid2->u32Data1 ? -1 : 1;
+ if (pGuid1->u16Data2 != pGuid2->u16Data2)
+ return pGuid1->u16Data2 < pGuid2->u16Data2 ? -1 : 1;
+ if (pGuid1->u16Data3 != pGuid2->u16Data3)
+ return pGuid1->u16Data3 < pGuid2->u16Data3 ? -1 : 1;
+ if (pGuid1->abData4[0] != pGuid2->abData4[0])
+ return pGuid1->abData4[0] < pGuid2->abData4[0] ? -1 : 1;
+ if (pGuid1->abData4[1] != pGuid2->abData4[1])
+ return pGuid1->abData4[1] < pGuid2->abData4[1] ? -1 : 1;
+ if (pGuid1->abData4[2] != pGuid2->abData4[2])
+ return pGuid1->abData4[2] < pGuid2->abData4[2] ? -1 : 1;
+ if (pGuid1->abData4[3] != pGuid2->abData4[3])
+ return pGuid1->abData4[3] < pGuid2->abData4[3] ? -1 : 1;
+ if (pGuid1->abData4[4] != pGuid2->abData4[4])
+ return pGuid1->abData4[4] < pGuid2->abData4[4] ? -1 : 1;
+ if (pGuid1->abData4[5] != pGuid2->abData4[5])
+ return pGuid1->abData4[5] < pGuid2->abData4[5] ? -1 : 1;
+ if (pGuid1->abData4[6] != pGuid2->abData4[6])
+ return pGuid1->abData4[6] < pGuid2->abData4[6] ? -1 : 1;
+ if (pGuid1->abData4[7] != pGuid2->abData4[7])
+ return pGuid1->abData4[7] < pGuid2->abData4[7] ? -1 : 1;
+ return 0;
+}
+
diff --git a/src/VBox/Runtime/common/efi/efisignaturedb.cpp b/src/VBox/Runtime/common/efi/efisignaturedb.cpp
new file mode 100644
index 00000000..b75445f0
--- /dev/null
+++ b/src/VBox/Runtime/common/efi/efisignaturedb.cpp
@@ -0,0 +1,587 @@
+/* $Id: efisignaturedb.cpp $ */
+/** @file
+ * IPRT - EFI signature database helpers.
+ */
+
+/*
+ * Copyright (C) 2021-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_DEFAULT
+#include <iprt/efi.h>
+
+#include <iprt/cdefs.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/sg.h>
+
+#include <iprt/formats/efi-signature.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * EFI signature entry.
+ */
+typedef struct RTEFISIGNATURE
+{
+ /** List node. */
+ RTLISTNODE NdLst;
+ /** The signature owner. */
+ RTUUID UuidOwner;
+ /** Size of the signature data in bytes. */
+ uint32_t cbSignature;
+ /** The signature data (variable in size). */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ uint8_t abSignature[RT_FLEXIBLE_ARRAY];
+} RTEFISIGNATURE;
+/** Pointer to a EFI signature entry. */
+typedef RTEFISIGNATURE *PRTEFISIGNATURE;
+/** Pointer to a const EFI signature entry. */
+typedef const RTEFISIGNATURE *PCRTEFISIGNATURE;
+
+
+/**
+ * The EFI signature database instance data.
+ */
+typedef struct RTEFISIGDBINT
+{
+ /** List head of the various signature types. */
+ RTLISTANCHOR aLstSigTypes[RTEFISIGTYPE_FIRST_INVALID];
+} RTEFISIGDBINT;
+/** Pointer to the EFI signature database instance data. */
+typedef RTEFISIGDBINT *PRTEFISIGDBINT;
+
+
+/**
+ * Signature type descriptor.
+ */
+typedef struct RTEFISIGDBDESC
+{
+ /** The EFI GUID identifying the signature type. */
+ EFI_GUID GuidSignatureType;
+ /** The additional signature header for this signature type. */
+ uint32_t cbSigHdr;
+ /** Size of the signature data (including EFI_SIGNATURE_DATA),
+ * can be 0 size varies with each signature (X.509 for example). */
+ uint32_t cbSig;
+ /** The internal signature type enum. */
+ RTEFISIGTYPE enmSigType;
+ /** Human readable string of the signature type. */
+ const char *pszName;
+} RTEFISIGDBDESC;
+/** Pointer to a signature type descriptor. */
+typedef RTEFISIGDBDESC *PRTEFISIGDBDESC;
+/** Pointer to a const signature type descriptor. */
+typedef const RTEFISIGDBDESC *PCRTEFISIGDBDESC;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/**
+ * Mapping of EFI signature GUIDs to their IPRT signature type equivalent.
+ */
+static const RTEFISIGDBDESC g_aGuid2SigTypeMapping[] =
+{
+ { EFI_NULL_GUID, 0, 0, RTEFISIGTYPE_INVALID, "INVALID" },
+ { EFI_SIGNATURE_TYPE_GUID_SHA256, 0, EFI_SIGNATURE_TYPE_SZ_SHA256, RTEFISIGTYPE_SHA256, "SHA-256" },
+ { EFI_SIGNATURE_TYPE_GUID_RSA2048, 0, EFI_SIGNATURE_TYPE_SZ_RSA2048, RTEFISIGTYPE_RSA2048, "RSA-2048" },
+ { EFI_SIGNATURE_TYPE_GUID_RSA2048_SHA256, 0, EFI_SIGNATURE_TYPE_SZ_RSA2048_SHA256, RTEFISIGTYPE_RSA2048_SHA256, "RSA-2048/SHA-256" },
+ { EFI_SIGNATURE_TYPE_GUID_SHA1, 0, EFI_SIGNATURE_TYPE_SZ_SHA1, RTEFISIGTYPE_SHA1, "SHA-1" },
+ { EFI_SIGNATURE_TYPE_GUID_RSA2048_SHA1, 0, EFI_SIGNATURE_TYPE_SZ_RSA2048_SHA1, RTEFISIGTYPE_RSA2048_SHA1, "RSA-2048/SHA-1" },
+ { EFI_SIGNATURE_TYPE_GUID_X509, 0, 0, RTEFISIGTYPE_X509, "X.509" }
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Returns the internal siganture type descriptor for the given EFI GUID.
+ *
+ * @returns Pointer to the descriptor if found or NULL if not.
+ * @param pGuid The EFI signature type GUID to look for.
+ */
+static PCRTEFISIGDBDESC rtEfiSigDbGetDescByGuid(PCEFI_GUID pGuid)
+{
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aGuid2SigTypeMapping); i++)
+ if (!RTEfiGuidCompare(&g_aGuid2SigTypeMapping[i].GuidSignatureType, pGuid))
+ return &g_aGuid2SigTypeMapping[i];
+
+ return NULL;
+}
+
+
+/**
+ * Validates the given signature lsit header.
+ *
+ * @returns Flag whether the list header is considered valid.
+ * @param pLstHdr The list header to validate.
+ * @param pDesc The descriptor for the signature type of the given list.
+ */
+static bool rtEfiSigDbSigHdrValidate(PCEFI_SIGNATURE_LIST pLstHdr, PCRTEFISIGDBDESC pDesc)
+{
+ uint32_t cbSigLst = RT_LE2H_U32(pLstHdr->cbSigLst);
+ uint32_t cbSigHdr = RT_LE2H_U32(pLstHdr->cbSigHdr);
+ uint32_t cbSig = RT_LE2H_U32(pLstHdr->cbSig);
+
+ if (cbSigHdr != pDesc->cbSigHdr)
+ return false;
+ if (cbSig < sizeof(EFI_SIGNATURE_DATA))
+ return false;
+ if ( pDesc->cbSig
+ && pLstHdr->cbSig != pDesc->cbSig)
+ return false;
+ if ( cbSigLst <= sizeof(*pLstHdr)
+ || cbSigLst <= cbSigHdr
+ || cbSigLst <= cbSig)
+ return false;
+ if ((cbSigLst - sizeof(*pLstHdr) - cbSigHdr) % cbSig)
+ return false;
+
+ return true;
+}
+
+
+/**
+ * Loads a single signature list into the given signature database from the given file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The signature database instance.
+ * @param hVfsFileIn The file to load the signature list from.
+ * @param pcbConsumed Where to store the number of bytes consumed for this signature list on success.
+ */
+static int rtEfiSigDbLoadSigList(PRTEFISIGDBINT pThis, RTVFSFILE hVfsFileIn, uint64_t *pcbConsumed)
+{
+ EFI_SIGNATURE_LIST LstHdr;
+ int rc = RTVfsFileRead(hVfsFileIn, &LstHdr, sizeof(LstHdr), NULL /*pcbRead*/);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTEFISIGDBDESC pDesc = rtEfiSigDbGetDescByGuid(&LstHdr.GuidSigType);
+ if (pDesc)
+ {
+ if (rtEfiSigDbSigHdrValidate(&LstHdr, pDesc))
+ {
+ RTLISTANCHOR LstTmp;
+ uint32_t cbSig = RT_LE2H_U32(LstHdr.cbSig);
+ uint32_t cbSigData = cbSig - sizeof(EFI_SIGNATURE_DATA);
+ uint32_t cSigs = (RT_LE2H_U32(LstHdr.cbSigLst) - RT_LE2H_U32(LstHdr.cbSigHdr)) / cbSig;
+
+ /** @todo Skip/parse signature header if we have to add a type which has this != 0. */
+ RTListInit(&LstTmp);
+ for (uint32_t i = 0; i < cSigs && RT_SUCCESS(rc); i++)
+ {
+ PRTEFISIGNATURE pSig = (PRTEFISIGNATURE)RTMemAllocZ(RT_UOFFSETOF_DYN(RTEFISIGNATURE, abSignature[cbSigData]));
+ if (pSig)
+ {
+ EFI_SIGNATURE_DATA SigData;
+ rc = RTVfsFileRead(hVfsFileIn, &SigData, sizeof(SigData), NULL /*pcbRead*/);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileRead(hVfsFileIn, &pSig->abSignature[0], cbSigData, NULL /*pcbRead*/);
+ if (RT_SUCCESS(rc))
+ {
+ RTEfiGuidToUuid(&pSig->UuidOwner, &SigData.GuidOwner);
+ pSig->cbSignature = cbSigData;
+ RTListAppend(&LstTmp, &pSig->NdLst);
+ }
+ else
+ RTMemFree(pSig);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Add the signatures to the list. */
+ RTListConcatenate(&pThis->aLstSigTypes[pDesc->enmSigType], &LstTmp);
+ *pcbConsumed = sizeof(LstHdr) + RT_LE2H_U32(LstHdr.cbSigHdr) + cSigs * cbSig;
+ }
+ else
+ {
+ /* Destroy the temporary list. */
+ PRTEFISIGNATURE pIt, pItNext;
+
+ RTListForEachSafe(&LstTmp, pIt, pItNext, RTEFISIGNATURE, NdLst)
+ {
+ RTListNodeRemove(&pIt->NdLst);
+ RTMemFree(pIt);
+ }
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Variant for written a list of signatures where each signature gets its own signature list header
+ * (for types where each signature can differ in size like X.509).
+ *
+ * @returns IPRT status code.
+ * @param pLst The list of signatures to write.
+ * @param pDesc The signature type descriptor.
+ * @param hVfsFileOut The file to write the database to.
+ * @param pcbThisWritten Where to store the number of bytes written for the given signature list.
+ */
+static int rtEfiSigDbWriteListSingle(PRTLISTANCHOR pLst, PCRTEFISIGDBDESC pDesc, RTVFSFILE hVfsFileOut, size_t *pcbThisWritten)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbWritten = 0;
+ PRTEFISIGNATURE pIt;
+
+ RTListForEach(pLst, pIt, RTEFISIGNATURE, NdLst)
+ {
+ EFI_SIGNATURE_LIST LstHdr;
+ EFI_SIGNATURE_DATA SigData;
+ LstHdr.GuidSigType = pDesc->GuidSignatureType;
+ LstHdr.cbSigLst = RT_H2LE_U32(sizeof(LstHdr) + sizeof(SigData) + pDesc->cbSigHdr + pIt->cbSignature);
+ LstHdr.cbSigHdr = RT_H2LE_U32(pDesc->cbSigHdr);
+ LstHdr.cbSig = RT_H2LE_U32(pIt->cbSignature + sizeof(SigData));
+ RTEfiGuidFromUuid(&SigData.GuidOwner, &pIt->UuidOwner);
+
+ RTSGSEG aSegs[3];
+ RTSGBUF SgBuf;
+
+ Assert(!pDesc->cbSigHdr);
+ aSegs[0].pvSeg = &LstHdr;
+ aSegs[0].cbSeg = sizeof(LstHdr);
+ aSegs[1].pvSeg = &SigData;
+ aSegs[1].cbSeg = sizeof(SigData);
+ aSegs[2].pvSeg = &pIt->abSignature[0];
+ aSegs[2].cbSeg = pIt->cbSignature;
+ RTSgBufInit(&SgBuf, &aSegs[0], RT_ELEMENTS(aSegs));
+ rc = RTVfsFileSgWrite(hVfsFileOut, -1, &SgBuf, true /*fBlocking*/, NULL /*pcbWritten*/);
+ if (RT_FAILURE(rc))
+ break;
+
+ cbWritten += sizeof(LstHdr) + sizeof(SigData) + pDesc->cbSigHdr + pIt->cbSignature;
+ }
+
+ if (RT_SUCCESS(rc))
+ *pcbThisWritten = cbWritten;
+
+ return rc;
+}
+
+
+/**
+ * Writes the given signature list to the database in the given file.
+ *
+ * @returns IPRT status code.
+ * @param pLst The list of signatures to write.
+ * @param pDesc The signature type descriptor.
+ * @param hVfsFileOut The file to write the database to.
+ * @param pcbThisWritten Where to store the number of bytes written for the given signature list.
+ */
+static int rtEfiSigDbWriteList(PRTLISTANCHOR pLst, PCRTEFISIGDBDESC pDesc, RTVFSFILE hVfsFileOut, size_t *pcbThisWritten)
+{
+ /*
+ * For signature lists where each signature can have a different size (X.509 for example)
+ * writing a new list for each signature is required which is done by a dedicated method.
+ */
+ if (!pDesc->cbSig)
+ return rtEfiSigDbWriteListSingle(pLst, pDesc, hVfsFileOut, pcbThisWritten);
+
+
+ /* Count the number of signatures first. */
+ uint32_t cSigs = 0;
+ PRTEFISIGNATURE pIt;
+
+ RTListForEach(pLst, pIt, RTEFISIGNATURE, NdLst)
+ {
+ cSigs++;
+ }
+
+ EFI_SIGNATURE_LIST LstHdr;
+ LstHdr.GuidSigType = pDesc->GuidSignatureType;
+ LstHdr.cbSigLst = RT_H2LE_U32(sizeof(LstHdr) + pDesc->cbSigHdr + cSigs * pDesc->cbSig);
+ LstHdr.cbSigHdr = RT_H2LE_U32(pDesc->cbSigHdr);
+ LstHdr.cbSig = RT_H2LE_U32(pDesc->cbSig);
+
+ int rc = RTVfsFileWrite(hVfsFileOut, &LstHdr, sizeof(LstHdr), NULL /*pcbWritten*/);
+ if (RT_SUCCESS(rc))
+ {
+ RTListForEach(pLst, pIt, RTEFISIGNATURE, NdLst)
+ {
+ RTSGSEG aSegs[2];
+ RTSGBUF SgBuf;
+ EFI_SIGNATURE_DATA SigData;
+ RTEfiGuidFromUuid(&SigData.GuidOwner, &pIt->UuidOwner);
+
+ Assert(pDesc->cbSig == pIt->cbSignature);
+ aSegs[0].pvSeg = &SigData;
+ aSegs[0].cbSeg = sizeof(SigData);
+ aSegs[1].pvSeg = &pIt->abSignature[0];
+ aSegs[1].cbSeg = pIt->cbSignature;
+ RTSgBufInit(&SgBuf, &aSegs[0], RT_ELEMENTS(aSegs));
+ rc = RTVfsFileSgWrite(hVfsFileOut, -1, &SgBuf, true /*fBlocking*/, NULL /*pcbWritten*/);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ *pcbThisWritten = sizeof(LstHdr) + pDesc->cbSigHdr + cSigs * pDesc->cbSig;
+
+ return rc;
+}
+
+
+/**
+ * Allocate a new signature of the given size.
+ *
+ * @returns Pointer to the new signature or NULL if out of memory.
+ * @param pUuidOwner The UUID of the signature owner.
+ * @param cbSig Size of the signature data in bytes.
+ */
+static PRTEFISIGNATURE rtEfiSigDbAllocSignature(PCRTUUID pUuidOwner, uint32_t cbSig)
+{
+ PRTEFISIGNATURE pSig = (PRTEFISIGNATURE)RTMemAllocZ(RT_UOFFSETOF_DYN(RTEFISIGNATURE, abSignature[cbSig]));
+ if (pSig)
+ {
+ pSig->UuidOwner = *pUuidOwner;
+ pSig->cbSignature = cbSig;
+ }
+
+ return pSig;
+}
+
+
+RTDECL(int) RTEfiSigDbCreate(PRTEFISIGDB phEfiSigDb)
+{
+ AssertPtrReturn(phEfiSigDb, VERR_INVALID_POINTER);
+
+ PRTEFISIGDBINT pThis = (PRTEFISIGDBINT)RTMemAllocZ(sizeof(*pThis));
+ if (RT_LIKELY(pThis))
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aLstSigTypes); i++)
+ RTListInit(&pThis->aLstSigTypes[i]);
+ *phEfiSigDb = pThis;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NO_MEMORY;
+}
+
+
+RTDECL(int) RTEfiSigDbDestroy(RTEFISIGDB hEfiSigDb)
+{
+ PRTEFISIGDBINT pThis = hEfiSigDb;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aLstSigTypes); i++)
+ {
+ PRTEFISIGNATURE pIt, pItNext;
+
+ RTListForEachSafe(&pThis->aLstSigTypes[i], pIt, pItNext, RTEFISIGNATURE, NdLst)
+ {
+ RTListNodeRemove(&pIt->NdLst);
+ RTMemFree(pIt);
+ }
+ }
+
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTEfiSigDbAddFromExistingDb(RTEFISIGDB hEfiSigDb, RTVFSFILE hVfsFileIn)
+{
+ PRTEFISIGDBINT pThis = hEfiSigDb;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ uint64_t cbFile;
+ int rc = RTVfsFileQuerySize(hVfsFileIn, &cbFile);
+ if ( RT_SUCCESS(rc)
+ && cbFile)
+ {
+ do
+ {
+ uint64_t cbConsumed = 0;
+ rc = rtEfiSigDbLoadSigList(pThis, hVfsFileIn, &cbConsumed);
+ cbFile -= cbConsumed;
+ } while ( RT_SUCCESS(rc)
+ && cbFile);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTEfiSigDbAddSignatureFromFile(RTEFISIGDB hEfiSigDb, RTEFISIGTYPE enmSigType, PCRTUUID pUuidOwner, RTVFSFILE hVfsFileIn)
+{
+ PRTEFISIGDBINT pThis = hEfiSigDb;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(enmSigType >= RTEFISIGTYPE_FIRST_VALID && enmSigType < RTEFISIGTYPE_FIRST_INVALID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pUuidOwner, VERR_INVALID_POINTER);
+
+ PCRTEFISIGDBDESC pDesc = &g_aGuid2SigTypeMapping[enmSigType];
+ uint64_t cbSig = 0;
+ int rc = RTVfsFileQuerySize(hVfsFileIn, &cbSig);
+ if (RT_SUCCESS(rc))
+ {
+ if ( ( !pDesc->cbSig
+ || pDesc->cbSig - sizeof(EFI_SIGNATURE_DATA) == cbSig)
+ && cbSig < UINT32_MAX)
+ {
+ PRTEFISIGNATURE pSig = rtEfiSigDbAllocSignature(pUuidOwner, (uint32_t)cbSig);
+ if (pSig)
+ {
+ rc = RTVfsFileRead(hVfsFileIn, &pSig->abSignature[0], (size_t)cbSig, NULL /*pcbRead*/);
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pThis->aLstSigTypes[enmSigType], &pSig->NdLst);
+ else
+ RTMemFree(pSig);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTEfiSigDbAddSignatureFromBuf(RTEFISIGDB hEfiSigDb, RTEFISIGTYPE enmSigType, PCRTUUID pUuidOwner,
+ const void *pvBuf, size_t cbBuf)
+{
+ PRTEFISIGDBINT pThis = hEfiSigDb;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(enmSigType >= RTEFISIGTYPE_FIRST_VALID && enmSigType < RTEFISIGTYPE_FIRST_INVALID, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pUuidOwner, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf && cbBuf < UINT32_MAX, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PCRTEFISIGDBDESC pDesc = &g_aGuid2SigTypeMapping[enmSigType];
+ if ( !pDesc->cbSig
+ || pDesc->cbSig - sizeof(EFI_SIGNATURE_DATA) == cbBuf)
+ {
+ PRTEFISIGNATURE pSig = rtEfiSigDbAllocSignature(pUuidOwner, (uint32_t)cbBuf);
+ if (pSig)
+ {
+ memcpy(&pSig->abSignature[0], pvBuf, cbBuf);
+ RTListAppend(&pThis->aLstSigTypes[enmSigType], &pSig->NdLst);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ return rc;
+}
+
+
+RTDECL(int) RTEfiSigDbWriteToFile(RTEFISIGDB hEfiSigDb, RTVFSFILE hVfsFileOut)
+{
+ PRTEFISIGDBINT pThis = hEfiSigDb;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ int rc = VINF_SUCCESS;
+ size_t cbSigDb = 0;
+ for (uint32_t i = RTEFISIGTYPE_FIRST_VALID; i < RT_ELEMENTS(pThis->aLstSigTypes) && RT_SUCCESS(rc); i++)
+ {
+ if (!RTListIsEmpty(&pThis->aLstSigTypes[i]))
+ {
+ size_t cbThisWritten = 0;
+ rc = rtEfiSigDbWriteList(&pThis->aLstSigTypes[i], &g_aGuid2SigTypeMapping[i], hVfsFileOut, &cbThisWritten);
+ if (RT_SUCCESS(rc))
+ cbSigDb += cbThisWritten;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileSetSize(hVfsFileOut, cbSigDb, RTVFSFILE_SIZE_F_NORMAL);
+
+ return rc;
+}
+
+
+RTDECL(int) RTEfiSigDbEnum(RTEFISIGDB hEfiSigDb, PFNRTEFISIGDBENUMSIG pfnEnumSig, void *pvUser)
+{
+ PRTEFISIGDBINT pThis = hEfiSigDb;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ for (uint32_t i = RTEFISIGTYPE_FIRST_VALID; i < RT_ELEMENTS(pThis->aLstSigTypes); i++)
+ {
+ PRTEFISIGNATURE pIt;
+
+ RTListForEach(&pThis->aLstSigTypes[i], pIt, RTEFISIGNATURE, NdLst)
+ {
+ int rc = pfnEnumSig(pThis, (RTEFISIGTYPE)i, &pIt->UuidOwner, &pIt->abSignature[0], pIt->cbSignature, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(const char *) RTEfiSigDbTypeStringify(RTEFISIGTYPE enmSigType)
+{
+ AssertReturn(enmSigType < RTEFISIGTYPE_FIRST_INVALID, NULL);
+ return g_aGuid2SigTypeMapping[enmSigType].pszName;
+}
+
+
+RTDECL(PCEFI_GUID) RTEfiSigDbTypeGetGuid(RTEFISIGTYPE enmSigType)
+{
+ AssertReturn(enmSigType < RTEFISIGTYPE_FIRST_INVALID, NULL);
+ return &g_aGuid2SigTypeMapping[enmSigType].GuidSignatureType;
+}
+
diff --git a/src/VBox/Runtime/common/efi/efitime.cpp b/src/VBox/Runtime/common/efi/efitime.cpp
new file mode 100644
index 00000000..a4e82b5a
--- /dev/null
+++ b/src/VBox/Runtime/common/efi/efitime.cpp
@@ -0,0 +1,123 @@
+/* $Id: efitime.cpp $ */
+/** @file
+ * IPRT - EFI time conversion helpers.
+ */
+
+/*
+ * Copyright (C) 2021-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_TIME
+#include <iprt/efi.h>
+
+#include <iprt/cdefs.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+RTDECL(PRTTIMESPEC) RTEfiTimeToTimeSpec(PRTTIMESPEC pTimeSpec, PCEFI_TIME pEfiTime)
+{
+ RTTIME Time; RT_ZERO(Time);
+
+ Time.i32Year = pEfiTime->u16Year;
+ Time.u8Month = pEfiTime->u8Month;
+ Time.u8MonthDay = pEfiTime->u8Day;
+ Time.u8Hour = pEfiTime->u8Hour;
+ Time.u8Minute = pEfiTime->u8Minute;
+ Time.u8Second = pEfiTime->u8Second;
+ Time.u32Nanosecond = pEfiTime->u32Nanosecond;
+ if (pEfiTime->iTimezone != EFI_TIME_TIMEZONE_UNSPECIFIED)
+ Time.offUTC = pEfiTime->iTimezone;
+ Time.fFlags = RTTIME_FLAGS_TYPE_LOCAL;
+ if (RTTimeIsLeapYear(Time.i32Year))
+ Time.fFlags |= RTTIME_FLAGS_LEAP_YEAR;
+ else
+ Time.fFlags |= RTTIME_FLAGS_COMMON_YEAR;
+ if (pEfiTime->u8Daylight & EFI_TIME_DAYLIGHT_ADJUST)
+ {
+ if (pEfiTime->u8Daylight & EFI_TIME_DAYLIGHT_INDST)
+ Time.fFlags |= RTTIME_FLAGS_DST;
+ }
+ else
+ Time.fFlags |= RTTIME_FLAGS_NO_DST_DATA;
+
+ if (!RTTimeLocalNormalize(&Time))
+ return NULL;
+
+ return RTTimeImplode(pTimeSpec, &Time);
+}
+
+
+RTDECL(PEFI_TIME) RTEfiTimeFromTimeSpec(PEFI_TIME pEfiTime, PCRTTIMESPEC pTimeSpec)
+{
+ RTTIME Time; RT_ZERO(Time);
+ if (!RTTimeExplode(&Time, pTimeSpec))
+ return NULL;
+
+ RT_ZERO(*pEfiTime);
+ pEfiTime->u16Year = Time.i32Year < 0
+ ? 0
+ : (uint16_t)Time.i32Year;
+ pEfiTime->u8Month = Time.u8Month;
+ pEfiTime->u8Day = Time.u8MonthDay;
+ pEfiTime->u8Hour = Time.u8Hour;
+ pEfiTime->u8Minute = Time.u8Minute;
+ pEfiTime->u8Second = Time.u8Second;
+ pEfiTime->u32Nanosecond = Time.u32Nanosecond;
+ if ((Time.fFlags & RTTIME_FLAGS_TYPE_MASK) == RTTIME_FLAGS_TYPE_LOCAL)
+ pEfiTime->iTimezone = Time.offUTC;
+ else
+ pEfiTime->iTimezone = EFI_TIME_TIMEZONE_UNSPECIFIED;
+ if (!(Time.fFlags & RTTIME_FLAGS_NO_DST_DATA))
+ {
+ pEfiTime->u8Daylight = EFI_TIME_DAYLIGHT_ADJUST;
+ if (Time.fFlags & RTTIME_FLAGS_DST)
+ pEfiTime->u8Daylight |= EFI_TIME_DAYLIGHT_INDST;
+ }
+ return pEfiTime;
+}
+
diff --git a/src/VBox/Runtime/common/efi/efivarstorevfs.cpp b/src/VBox/Runtime/common/efi/efivarstorevfs.cpp
new file mode 100644
index 00000000..7703577e
--- /dev/null
+++ b/src/VBox/Runtime/common/efi/efivarstorevfs.cpp
@@ -0,0 +1,2653 @@
+/* $Id: efivarstorevfs.cpp $ */
+/** @file
+ * IPRT - Expose a EFI variable store as a Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2021-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include <iprt/efi.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/crc.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/formats/efi-fv.h>
+#include <iprt/formats/efi-varstore.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the varstore filesystem data. */
+typedef struct RTEFIVARSTORE *PRTEFIVARSTORE;
+
+
+/**
+ * EFI variable entry.
+ */
+typedef struct RTEFIVAR
+{
+ /** Pointer to the owning variable store. */
+ PRTEFIVARSTORE pVarStore;
+ /** Offset of the variable data located in the backing image - 0 if not written yet. */
+ uint64_t offVarData;
+ /** Pointer to the in memory data, NULL if not yet read. */
+ void *pvData;
+ /** Monotonic counter value. */
+ uint64_t cMonotonic;
+ /** Size of the variable data in bytes. */
+ uint32_t cbData;
+ /** Index of the assoicated public key. */
+ uint32_t idPubKey;
+ /** Attributes for the variable. */
+ uint32_t fAttr;
+ /** Flag whether the variable was deleted. */
+ bool fDeleted;
+ /** Name of the variable. */
+ char *pszName;
+ /** The raw EFI timestamp as read from the header. */
+ EFI_TIME EfiTimestamp;
+ /** The creation/update time. */
+ RTTIMESPEC Time;
+ /** The vendor UUID of the variable. */
+ RTUUID Uuid;
+} RTEFIVAR;
+/** Pointer to an EFI variable. */
+typedef RTEFIVAR *PRTEFIVAR;
+
+
+/**
+ * EFI GUID entry.
+ */
+typedef struct RTEFIGUID
+{
+ /** The UUID representation of the GUID. */
+ RTUUID Uuid;
+ /** Pointer to the array of indices into RTEFIVARSTORE::paVars. */
+ uint32_t *paidxVars;
+ /** Number of valid indices in the array. */
+ uint32_t cVars;
+ /** Maximum number of indices the array can hold. */
+ uint32_t cVarsMax;
+} RTEFIGUID;
+/** Pointer to an EFI variable. */
+typedef RTEFIGUID *PRTEFIGUID;
+
+
+/**
+ * EFI variable store filesystem volume.
+ */
+typedef struct RTEFIVARSTORE
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the volume has. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+
+ /** RTVFSMNT_F_XXX. */
+ uint32_t fMntFlags;
+ /** RTEFIVARSTOREVFS_F_XXX (currently none defined). */
+ uint32_t fVarStoreFlags;
+
+ /** Size of the variable store (minus the header). */
+ uint64_t cbVarStore;
+ /** Start offset into the backing image where the variable data starts. */
+ uint64_t offStoreData;
+ /** Flag whether the variable store uses authenticated variables. */
+ bool fAuth;
+ /** Number of bytes occupied by existing variables. */
+ uint64_t cbVarData;
+
+ /** Pointer to the array of variables sorted by start offset. */
+ PRTEFIVAR paVars;
+ /** Number of valid variables in the array. */
+ uint32_t cVars;
+ /** Maximum number of variables the array can hold. */
+ uint32_t cVarsMax;
+
+ /** Pointer to the array of vendor GUIDS. */
+ PRTEFIGUID paGuids;
+ /** Number of valid GUIDS in the array. */
+ uint32_t cGuids;
+ /** Maximum number of GUIDS the array can hold. */
+ uint32_t cGuidsMax;
+
+} RTEFIVARSTORE;
+
+
+/**
+ * Variable store directory type.
+ */
+typedef enum RTEFIVARSTOREDIRTYPE
+{
+ /** Invalid directory type. */
+ RTEFIVARSTOREDIRTYPE_INVALID = 0,
+ /** Root directory type. */
+ RTEFIVARSTOREDIRTYPE_ROOT,
+ /** 'by-name' directory. */
+ RTEFIVARSTOREDIRTYPE_BY_NAME,
+ /** 'by-uuid' directory. */
+ RTEFIVARSTOREDIRTYPE_BY_GUID,
+ /** 'raw' directory. */
+ RTEFIVARSTOREDIRTYPE_RAW,
+ /** Specific 'by-uuid/{...}' directory. */
+ RTEFIVARSTOREDIRTYPE_GUID,
+ /** Specific 'raw/{...}' directory. */
+ RTEFIVARSTOREDIRTYPE_RAW_ENTRY,
+ /** 32bit blowup hack. */
+ RTEFIVARSTOREDIRTYPE_32BIT_HACK = 0x7fffffff
+} RTEFIVARSTOREDIRTYPE;
+
+
+/**
+ * EFI variable store directory entry.
+ */
+typedef struct RTEFIVARSTOREDIRENTRY
+{
+ /** Name of the directory if constant. */
+ const char *pszName;
+ /** Size of the name. */
+ size_t cbName;
+ /** Entry type. */
+ RTEFIVARSTOREDIRTYPE enmType;
+ /** Parent entry type. */
+ RTEFIVARSTOREDIRTYPE enmParentType;
+} RTEFIVARSTOREDIRENTRY;
+/** Pointer to a EFI variable store directory entry. */
+typedef RTEFIVARSTOREDIRENTRY *PRTEFIVARSTOREDIRENTRY;
+/** Pointer to a const EFI variable store directory entry. */
+typedef const RTEFIVARSTOREDIRENTRY *PCRTEFIVARSTOREDIRENTRY;
+
+
+/**
+ * Variable store directory.
+ */
+typedef struct RTEFIVARSTOREDIR
+{
+ /* Flag whether we reached the end of directory entries. */
+ bool fNoMoreFiles;
+ /** The index of the next item to read. */
+ uint32_t idxNext;
+ /** Directory entry. */
+ PCRTEFIVARSTOREDIRENTRY pEntry;
+ /** The variable store associated with this directory. */
+ PRTEFIVARSTORE pVarStore;
+ /** Time when the directory was created. */
+ RTTIMESPEC Time;
+ /** Pointer to the GUID entry, only valid for RTEFIVARSTOREDIRTYPE_GUID. */
+ PRTEFIGUID pGuid;
+ /** The variable ID, only valid for RTEFIVARSTOREDIRTYPE_RAW_ENTRY. */
+ uint32_t idVar;
+} RTEFIVARSTOREDIR;
+/** Pointer to an Variable store directory. */
+typedef RTEFIVARSTOREDIR *PRTEFIVARSTOREDIR;
+
+
+/**
+ * File type.
+ */
+typedef enum RTEFIVARSTOREFILETYPE
+{
+ /** Invalid type, do not use. */
+ RTEFIVARSTOREFILETYPE_INVALID = 0,
+ /** File accesses the data portion of the variable. */
+ RTEFIVARSTOREFILETYPE_DATA,
+ /** File accesses the attributes of the variable. */
+ RTEFIVARSTOREFILETYPE_ATTR,
+ /** File accesses the UUID of the variable. */
+ RTEFIVARSTOREFILETYPE_UUID,
+ /** File accesses the public key index of the variable. */
+ RTEFIVARSTOREFILETYPE_PUBKEY,
+ /** File accesses the raw EFI Time of the variable. */
+ RTEFIVARSTOREFILETYPE_TIME,
+ /** The monotonic counter (deprecated). */
+ RTEFIVARSTOREFILETYPE_MONOTONIC,
+ /** 32bit hack. */
+ RTEFIVARSTOREFILETYPE_32BIT_HACK = 0x7fffffff
+} RTEFIVARSTOREFILETYPE;
+
+
+/**
+ * Raw file type entry.
+ */
+typedef struct RTEFIVARSTOREFILERAWENTRY
+{
+ /** Name of the entry. */
+ const char *pszName;
+ /** The associated file type. */
+ RTEFIVARSTOREFILETYPE enmType;
+ /** File size of the object, 0 if dynamic. */
+ size_t cbObject;
+ /** Offset of the item in the variable header. */
+ uint32_t offObject;
+} RTEFIVARSTOREFILERAWENTRY;
+/** Pointer to a raw file type entry. */
+typedef RTEFIVARSTOREFILERAWENTRY *PRTEFIVARSTOREFILERAWENTRY;
+/** Pointer to a const file type entry. */
+typedef const RTEFIVARSTOREFILERAWENTRY *PCRTEFIVARSTOREFILERAWENTRY;
+
+
+/**
+ * Open file instance.
+ */
+typedef struct RTEFIVARFILE
+{
+ /** The file type. */
+ PCRTEFIVARSTOREFILERAWENTRY pEntry;
+ /** Variable store this file belongs to. */
+ PRTEFIVARSTORE pVarStore;
+ /** The underlying variable structure. */
+ PRTEFIVAR pVar;
+ /** Current offset into the file for I/O. */
+ RTFOFF offFile;
+} RTEFIVARFILE;
+/** Pointer to an open file instance. */
+typedef RTEFIVARFILE *PRTEFIVARFILE;
+
+
+/**
+ * Directories.
+ */
+static const RTEFIVARSTOREDIRENTRY g_aDirs[] =
+{
+ { NULL, 0, RTEFIVARSTOREDIRTYPE_ROOT, RTEFIVARSTOREDIRTYPE_ROOT },
+ { RT_STR_TUPLE("by-name"), RTEFIVARSTOREDIRTYPE_BY_NAME, RTEFIVARSTOREDIRTYPE_ROOT },
+ { RT_STR_TUPLE("by-uuid"), RTEFIVARSTOREDIRTYPE_BY_GUID, RTEFIVARSTOREDIRTYPE_ROOT },
+ { RT_STR_TUPLE("raw"), RTEFIVARSTOREDIRTYPE_RAW, RTEFIVARSTOREDIRTYPE_ROOT },
+ { NULL, 0, RTEFIVARSTOREDIRTYPE_GUID, RTEFIVARSTOREDIRTYPE_BY_GUID },
+ { NULL, 0, RTEFIVARSTOREDIRTYPE_RAW_ENTRY, RTEFIVARSTOREDIRTYPE_RAW },
+};
+
+
+/**
+ * Raw files for accessing specific items in the variable header.
+ */
+static const RTEFIVARSTOREFILERAWENTRY g_aRawFiles[] =
+{
+ { "attr", RTEFIVARSTOREFILETYPE_ATTR, sizeof(uint32_t), RT_UOFFSETOF(RTEFIVAR, fAttr) },
+ { "data", RTEFIVARSTOREFILETYPE_DATA, 0, 0 },
+ { "uuid", RTEFIVARSTOREFILETYPE_UUID, sizeof(RTUUID), RT_UOFFSETOF(RTEFIVAR, Uuid) },
+ { "pubkey", RTEFIVARSTOREFILETYPE_PUBKEY, sizeof(uint32_t), RT_UOFFSETOF(RTEFIVAR, idPubKey) },
+ { "time", RTEFIVARSTOREFILETYPE_TIME, sizeof(EFI_TIME), RT_UOFFSETOF(RTEFIVAR, EfiTimestamp) },
+ { "monotonic", RTEFIVARSTOREFILETYPE_MONOTONIC, sizeof(uint64_t), RT_UOFFSETOF(RTEFIVAR, cMonotonic) }
+};
+
+#define RTEFIVARSTORE_FILE_ENTRY_DATA 1
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtEfiVarStore_NewDirByType(PRTEFIVARSTORE pThis, RTEFIVARSTOREDIRTYPE enmDirType,
+ PRTEFIGUID pGuid, uint32_t idVar, PRTVFSOBJ phVfsObj);
+
+
+#ifdef LOG_ENABLED
+
+/**
+ * Logs a firmware volume header.
+ *
+ * @param pFvHdr The firmware volume header.
+ */
+static void rtEfiVarStoreFvHdr_Log(PCEFI_FIRMWARE_VOLUME_HEADER pFvHdr)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("EfiVarStore: Volume Header:\n"));
+ Log2(("EfiVarStore: abZeroVec %#.*Rhxs\n", sizeof(pFvHdr->abZeroVec), &pFvHdr->abZeroVec[0]));
+ Log2(("EfiVarStore: GuidFilesystem %#.*Rhxs\n", sizeof(pFvHdr->GuidFilesystem), &pFvHdr->GuidFilesystem));
+ Log2(("EfiVarStore: cbFv %#RX64\n", RT_LE2H_U64(pFvHdr->cbFv)));
+ Log2(("EfiVarStore: u32Signature %#RX32\n", RT_LE2H_U32(pFvHdr->u32Signature)));
+ Log2(("EfiVarStore: fAttr %#RX32\n", RT_LE2H_U32(pFvHdr->fAttr)));
+ Log2(("EfiVarStore: cbFvHdr %#RX16\n", RT_LE2H_U16(pFvHdr->cbFvHdr)));
+ Log2(("EfiVarStore: u16Chksum %#RX16\n", RT_LE2H_U16(pFvHdr->u16Chksum)));
+ Log2(("EfiVarStore: offExtHdr %#RX16\n", RT_LE2H_U16(pFvHdr->offExtHdr)));
+ Log2(("EfiVarStore: bRsvd %#RX8\n", pFvHdr->bRsvd));
+ Log2(("EfiVarStore: bRevision %#RX8\n", pFvHdr->bRevision));
+ }
+}
+
+
+/**
+ * Logs a variable store header.
+ *
+ * @param pStoreHdr The variable store header.
+ */
+static void rtEfiVarStoreHdr_Log(PCEFI_VARSTORE_HEADER pStoreHdr)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("EfiVarStore: Variable Store Header:\n"));
+ Log2(("EfiVarStore: GuidVarStore %#.*Rhxs\n", sizeof(pStoreHdr->GuidVarStore), &pStoreHdr->GuidVarStore));
+ Log2(("EfiVarStore: cbVarStore %#RX32\n", RT_LE2H_U32(pStoreHdr->cbVarStore)));
+ Log2(("EfiVarStore: bFmt %#RX8\n", pStoreHdr->bFmt));
+ Log2(("EfiVarStore: bState %#RX8\n", pStoreHdr->bState));
+ }
+}
+
+
+/**
+ * Logs a authenticated variable header.
+ *
+ * @param pVarHdr The authenticated variable header.
+ * @param offVar Offset of the authenticated variable header.
+ */
+static void rtEfiVarStoreAuthVarHdr_Log(PCEFI_AUTH_VAR_HEADER pVarHdr, uint64_t offVar)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("EfiVarStore: Authenticated Variable Header at offset %#RU64:\n", offVar));
+ Log2(("EfiVarStore: u16StartId %#RX16\n", RT_LE2H_U16(pVarHdr->u16StartId)));
+ Log2(("EfiVarStore: bState %#RX8\n", pVarHdr->bState));
+ Log2(("EfiVarStore: bRsvd %#RX8\n", pVarHdr->bRsvd));
+ Log2(("EfiVarStore: fAttr %#RX32\n", RT_LE2H_U32(pVarHdr->fAttr)));
+ Log2(("EfiVarStore: cMonotonic %#RX64\n", RT_LE2H_U64(pVarHdr->cMonotonic)));
+ Log2(("EfiVarStore: Timestamp.u16Year %#RX16\n", RT_LE2H_U16(pVarHdr->Timestamp.u16Year)));
+ Log2(("EfiVarStore: Timestamp.u8Month %#RX8\n", pVarHdr->Timestamp.u8Month));
+ Log2(("EfiVarStore: Timestamp.u8Day %#RX8\n", pVarHdr->Timestamp.u8Day));
+ Log2(("EfiVarStore: Timestamp.u8Hour %#RX8\n", pVarHdr->Timestamp.u8Hour));
+ Log2(("EfiVarStore: Timestamp.u8Minute %#RX8\n", pVarHdr->Timestamp.u8Minute));
+ Log2(("EfiVarStore: Timestamp.u8Second %#RX8\n", pVarHdr->Timestamp.u8Second));
+ Log2(("EfiVarStore: Timestamp.bPad0 %#RX8\n", pVarHdr->Timestamp.bPad0));
+ Log2(("EfiVarStore: Timestamp.u32Nanosecond %#RX32\n", RT_LE2H_U32(pVarHdr->Timestamp.u32Nanosecond)));
+ Log2(("EfiVarStore: Timestamp.iTimezone %#RI16\n", RT_LE2H_S16(pVarHdr->Timestamp.iTimezone)));
+ Log2(("EfiVarStore: Timestamp.u8Daylight %#RX8\n", pVarHdr->Timestamp.u8Daylight));
+ Log2(("EfiVarStore: Timestamp.bPad1 %#RX8\n", pVarHdr->Timestamp.bPad1));
+ Log2(("EfiVarStore: idPubKey %#RX32\n", RT_LE2H_U32(pVarHdr->idPubKey)));
+ Log2(("EfiVarStore: cbName %#RX32\n", RT_LE2H_U32(pVarHdr->cbName)));
+ Log2(("EfiVarStore: cbData %#RX32\n", RT_LE2H_U32(pVarHdr->cbData)));
+ Log2(("EfiVarStore: GuidVendor %#.*Rhxs\n", sizeof(pVarHdr->GuidVendor), &pVarHdr->GuidVendor));
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+/**
+ * Worker for rtEfiVarStoreFile_QueryInfo() and rtEfiVarStoreDir_QueryInfo().
+ *
+ * @returns IPRT status code.
+ * @param cbObject Size of the object in bytes.
+ * @param fIsDir Flag whether the object is a directory or file.
+ * @param pTime The time to use.
+ * @param pObjInfo The FS object information structure to fill in.
+ * @param enmAddAttr What to fill in.
+ */
+static int rtEfiVarStore_QueryInfo(uint64_t cbObject, bool fIsDir, PCRTTIMESPEC pTime, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ pObjInfo->cbObject = cbObject;
+ pObjInfo->cbAllocated = cbObject;
+ pObjInfo->AccessTime = *pTime;
+ pObjInfo->ModificationTime = *pTime;
+ pObjInfo->ChangeTime = *pTime;
+ pObjInfo->BirthTime = *pTime;
+ pObjInfo->Attr.fMode = fIsDir
+ ? RTFS_TYPE_DIRECTORY | RTFS_UNIX_ALL_ACCESS_PERMS
+ : RTFS_TYPE_FILE | RTFS_UNIX_IWOTH | RTFS_UNIX_IROTH
+ | RTFS_UNIX_IWGRP | RTFS_UNIX_IRGRP
+ | RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING: RT_FALL_THRU();
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
+ pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = 0;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = 0;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = 0;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tries to find and return the GUID entry for the given UUID.
+ *
+ * @returns Pointer to the GUID entry or NULL if not found.
+ * @param pThis The EFI variable store instance.
+ * @param pUuid The UUID to look for.
+ */
+static PRTEFIGUID rtEfiVarStore_GetGuid(PRTEFIVARSTORE pThis, PCRTUUID pUuid)
+{
+ for (uint32_t i = 0; i < pThis->cGuids; i++)
+ if (!RTUuidCompare(&pThis->paGuids[i].Uuid, pUuid))
+ return &pThis->paGuids[i];
+
+ return NULL;
+}
+
+
+/**
+ * Adds the given UUID to the array of known GUIDs.
+ *
+ * @returns Pointer to the GUID entry or NULL if out of memory.
+ * @param pThis The EFI variable store instance.
+ * @param pUuid The UUID to add.
+ */
+static PRTEFIGUID rtEfiVarStore_AddGuid(PRTEFIVARSTORE pThis, PCRTUUID pUuid)
+{
+ if (pThis->cGuids == pThis->cGuidsMax)
+ {
+ /* Grow the array. */
+ uint32_t cGuidsMaxNew = pThis->cGuidsMax + 10;
+ PRTEFIGUID paGuidsNew = (PRTEFIGUID)RTMemRealloc(pThis->paGuids, cGuidsMaxNew * sizeof(RTEFIGUID));
+ if (!paGuidsNew)
+ return NULL;
+
+ pThis->paGuids = paGuidsNew;
+ pThis->cGuidsMax = cGuidsMaxNew;
+ }
+
+ PRTEFIGUID pGuid = &pThis->paGuids[pThis->cGuids++];
+ pGuid->Uuid = *pUuid;
+ pGuid->paidxVars = NULL;
+ pGuid->cVars = 0;
+ pGuid->cVarsMax = 0;
+ return pGuid;
+}
+
+
+/**
+ * Adds the given variable to the GUID array.
+ *
+ * @returns IPRT status code.
+ * @param pThis The EFI variable store instance.
+ * @param pUuid The UUID of the variable.
+ * @param idVar The variable index into the array.
+ */
+static int rtEfiVarStore_AddVarByGuid(PRTEFIVARSTORE pThis, PCRTUUID pUuid, uint32_t idVar)
+{
+ PRTEFIGUID pGuid = rtEfiVarStore_GetGuid(pThis, pUuid);
+ if (!pGuid)
+ pGuid = rtEfiVarStore_AddGuid(pThis, pUuid);
+
+ if ( pGuid
+ && pGuid->cVars == pGuid->cVarsMax)
+ {
+ /* Grow the array. */
+ uint32_t cVarsMaxNew = pGuid->cVarsMax + 10;
+ uint32_t *paidxVarsNew = (uint32_t *)RTMemRealloc(pGuid->paidxVars, cVarsMaxNew * sizeof(uint32_t));
+ if (!paidxVarsNew)
+ return VERR_NO_MEMORY;
+
+ pGuid->paidxVars = paidxVarsNew;
+ pGuid->cVarsMax = cVarsMaxNew;
+ }
+
+ int rc = VINF_SUCCESS;
+ if (pGuid)
+ pGuid->paidxVars[pGuid->cVars++] = idVar;
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Reads variable data from the given memory area.
+ *
+ * @returns IPRT status code.
+ * @param pThis The EFI variable file instance.
+ * @param pvData Pointer to the start of the data.
+ * @param cbData Size of the variable data in bytes.
+ * @param off Where to start reading relative from the data start offset.
+ * @param pSgBuf Where to store the read data.
+ * @param pcbRead Where to return the number of bytes read, optional.
+ */
+static int rtEfiVarStoreFile_ReadMem(PRTEFIVARFILE pThis, const void *pvData, size_t cbData,
+ RTFOFF off, PCRTSGBUF pSgBuf, size_t *pcbRead)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbRead = pSgBuf->paSegs[0].cbSeg;
+ size_t cbThisRead = RT_MIN(cbData - off, cbRead);
+ const uint8_t *pbData = (const uint8_t *)pvData;
+ if (!pcbRead)
+ {
+ if (cbThisRead == cbRead)
+ memcpy(pSgBuf->paSegs[0].pvSeg, &pbData[off], cbThisRead);
+ else
+ rc = VERR_EOF;
+
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbThisRead;
+ Log6(("rtEfiVarStoreFile_ReadMem: off=%#RX64 cbSeg=%#x -> %Rrc\n", off, pSgBuf->paSegs[0].cbSeg, rc));
+ }
+ else
+ {
+ if ((uint64_t)off >= cbData)
+ {
+ *pcbRead = 0;
+ rc = VINF_EOF;
+ }
+ else
+ {
+ memcpy(pSgBuf->paSegs[0].pvSeg, &pbData[off], cbThisRead);
+ /* Return VINF_EOF if beyond end-of-file. */
+ if (cbThisRead < cbRead)
+ rc = VINF_EOF;
+ pThis->offFile = off + cbThisRead;
+ *pcbRead = cbThisRead;
+ }
+ Log6(("rtEfiVarStoreFile_ReadMem: off=%#RX64 cbSeg=%#x -> %Rrc *pcbRead=%#x\n", off, pSgBuf->paSegs[0].cbSeg, rc, *pcbRead));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Writes variable data from the given memory area.
+ *
+ * @returns IPRT status code.
+ * @param pThis The EFI variable file instance.
+ * @param pvData Pointer to the start of the data.
+ * @param cbData Size of the variable data in bytes.
+ * @param off Where to start writing relative from the data start offset.
+ * @param pSgBuf The data to write.
+ * @param pcbWritten Where to return the number of bytes written, optional.
+ */
+static int rtEfiVarStoreFile_WriteMem(PRTEFIVARFILE pThis, void *pvData, size_t cbData,
+ RTFOFF off, PCRTSGBUF pSgBuf, size_t *pcbWritten)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbWrite = pSgBuf->paSegs[0].cbSeg;
+ size_t cbThisWrite = RT_MIN(cbData - off, cbWrite);
+ uint8_t *pbData = (uint8_t *)pvData;
+ if (!pcbWritten)
+ {
+ if (cbThisWrite == cbWrite)
+ memcpy(&pbData[off], pSgBuf->paSegs[0].pvSeg, cbThisWrite);
+ else
+ rc = VERR_EOF;
+
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbThisWrite;
+ Log6(("rtEfiVarStoreFile_WriteMem: off=%#RX64 cbSeg=%#x -> %Rrc\n", off, pSgBuf->paSegs[0].cbSeg, rc));
+ }
+ else
+ {
+ if ((uint64_t)off >= cbData)
+ {
+ *pcbWritten = 0;
+ rc = VINF_EOF;
+ }
+ else
+ {
+ memcpy(&pbData[off], pSgBuf->paSegs[0].pvSeg, cbThisWrite);
+ /* Return VINF_EOF if beyond end-of-file. */
+ if (cbThisWrite < cbWrite)
+ rc = VINF_EOF;
+ pThis->offFile = off + cbThisWrite;
+ *pcbWritten = cbThisWrite;
+ }
+ Log6(("rtEfiVarStoreFile_WriteMem: off=%#RX64 cbSeg=%#x -> %Rrc *pcbWritten=%#x\n", off, pSgBuf->paSegs[0].cbSeg, rc, *pcbWritten));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Reads variable data from the given range.
+ *
+ * @returns IPRT status code.
+ * @param pThis The EFI variable file instance.
+ * @param offData Where the data starts in the backing storage.
+ * @param cbData Size of the variable data in bytes.
+ * @param off Where to start reading relative from the data start offset.
+ * @param pSgBuf Where to store the read data.
+ * @param pcbRead Where to return the number of bytes read, optional.
+ */
+static int rtEfiVarStoreFile_ReadFile(PRTEFIVARFILE pThis, uint64_t offData, size_t cbData,
+ RTFOFF off, PCRTSGBUF pSgBuf, size_t *pcbRead)
+{
+ int rc;
+ PRTEFIVARSTORE pVarStore = pThis->pVarStore;
+ size_t cbRead = pSgBuf->paSegs[0].cbSeg;
+ size_t cbThisRead = RT_MIN(cbData - off, cbRead);
+ uint64_t offStart = offData + off;
+ if (!pcbRead)
+ {
+ if (cbThisRead == cbRead)
+ rc = RTVfsFileReadAt(pVarStore->hVfsBacking, offStart, pSgBuf->paSegs[0].pvSeg, cbThisRead, NULL);
+ else
+ rc = VERR_EOF;
+
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbThisRead;
+ Log6(("rtFsEfiVarStore_Read: off=%#RX64 cbSeg=%#x -> %Rrc\n", off, pSgBuf->paSegs[0].cbSeg, rc));
+ }
+ else
+ {
+ if ((uint64_t)off >= cbData)
+ {
+ *pcbRead = 0;
+ rc = VINF_EOF;
+ }
+ else
+ {
+ rc = RTVfsFileReadAt(pVarStore->hVfsBacking, offStart, pSgBuf->paSegs[0].pvSeg, cbThisRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Return VINF_EOF if beyond end-of-file. */
+ if (cbThisRead < cbRead)
+ rc = VINF_EOF;
+ pThis->offFile = off + cbThisRead;
+ *pcbRead = cbThisRead;
+ }
+ else
+ *pcbRead = 0;
+ }
+ Log6(("rtFsEfiVarStore_Read: off=%#RX64 cbSeg=%#x -> %Rrc *pcbRead=%#x\n", off, pSgBuf->paSegs[0].cbSeg, rc, *pcbRead));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Ensures that the variable data is available before any modification.
+ *
+ * @returns IPRT status code.
+ * @param pVar The variable instance.
+ */
+static int rtEfiVarStore_VarReadData(PRTEFIVAR pVar)
+{
+ if (RT_LIKELY( !pVar->offVarData
+ || !pVar->cbData))
+ return VINF_SUCCESS;
+
+ Assert(!pVar->pvData);
+ pVar->pvData = RTMemAlloc(pVar->cbData);
+ if (RT_UNLIKELY(!pVar->pvData))
+ return VERR_NO_MEMORY;
+
+ PRTEFIVARSTORE pVarStore = pVar->pVarStore;
+ int rc = RTVfsFileReadAt(pVarStore->hVfsBacking, pVar->offVarData, pVar->pvData, pVar->cbData, NULL);
+ if (RT_SUCCESS(rc))
+ pVar->offVarData = 0; /* Marks the variable data as in memory. */
+ else
+ {
+ RTMemFree(pVar->pvData);
+ pVar->pvData = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Ensures that the given variable has the given data size.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_DISK_FULL if the new size would exceed the variable storage size.
+ * @param pVar The variable instance.
+ * @param cbData New number of bytes of data for the variable.
+ */
+static int rtEfiVarStore_VarEnsureDataSz(PRTEFIVAR pVar, size_t cbData)
+{
+ PRTEFIVARSTORE pVarStore = pVar->pVarStore;
+
+ if (pVar->cbData == cbData)
+ return VINF_SUCCESS;
+
+ if ((uint32_t)cbData != cbData)
+ return VERR_FILE_TOO_BIG;
+
+ int rc = VINF_SUCCESS;
+ if (cbData < pVar->cbData)
+ {
+ /* Shrink. */
+ void *pvNew = RTMemRealloc(pVar->pvData, cbData);
+ if (pvNew)
+ {
+ pVar->pvData = pvNew;
+ pVarStore->cbVarData -= pVar->cbData - cbData;
+ pVar->cbData = (uint32_t)cbData;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (cbData > pVar->cbData)
+ {
+ /* Grow. */
+ if (pVarStore->cbVarStore - pVarStore->cbVarData >= cbData - pVar->cbData)
+ {
+ void *pvNew = RTMemRealloc(pVar->pvData, cbData);
+ if (pvNew)
+ {
+ pVar->pvData = pvNew;
+ pVarStore->cbVarData += cbData - pVar->cbData;
+ pVar->cbData = (uint32_t)cbData;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_DISK_FULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Flush the variable store to the backing storage. This will remove any
+ * deleted variables in the backing storage.
+ *
+ * @returns IPRT status code.
+ * @param pThis The EFI variable store instance.
+ */
+static int rtEfiVarStore_Flush(PRTEFIVARSTORE pThis)
+{
+ int rc = VINF_SUCCESS;
+ uint64_t offCur = pThis->offStoreData;
+
+ for (uint32_t i = 0; i < pThis->cVars && RT_SUCCESS(rc); i++)
+ {
+ PRTUTF16 pwszName = NULL;
+ size_t cwcLen = 0;
+ PRTEFIVAR pVar = &pThis->paVars[i];
+
+ if (!pVar->fDeleted)
+ {
+ rc = RTStrToUtf16Ex(pVar->pszName, RTSTR_MAX, &pwszName, 0, &cwcLen);
+ if (RT_SUCCESS(rc))
+ {
+ cwcLen++; /* Include the terminator. */
+
+ /* Read in the data of the variable if it exists. */
+ rc = rtEfiVarStore_VarReadData(pVar);
+ if (RT_SUCCESS(rc))
+ {
+ /* Write out the variable. */
+ EFI_AUTH_VAR_HEADER VarHdr;
+ size_t cbName = cwcLen * sizeof(RTUTF16);
+
+ VarHdr.u16StartId = RT_H2LE_U16(EFI_AUTH_VAR_HEADER_START);
+ VarHdr.bState = EFI_AUTH_VAR_HEADER_STATE_ADDED;
+ VarHdr.bRsvd = 0;
+ VarHdr.fAttr = RT_H2LE_U32(pVar->fAttr);
+ VarHdr.cMonotonic = RT_H2LE_U64(pVar->cMonotonic);
+ VarHdr.idPubKey = RT_H2LE_U32(pVar->idPubKey);
+ VarHdr.cbName = RT_H2LE_U32((uint32_t)cbName);
+ VarHdr.cbData = RT_H2LE_U32(pVar->cbData);
+ RTEfiGuidFromUuid(&VarHdr.GuidVendor, &pVar->Uuid);
+ memcpy(&VarHdr.Timestamp, &pVar->EfiTimestamp, sizeof(pVar->EfiTimestamp));
+
+ rc = RTVfsFileWriteAt(pThis->hVfsBacking, offCur, &VarHdr, sizeof(VarHdr), NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileWriteAt(pThis->hVfsBacking, offCur + sizeof(VarHdr), pwszName, cbName, NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileWriteAt(pThis->hVfsBacking, offCur + sizeof(VarHdr) + cbName, pVar->pvData, pVar->cbData, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ offCur += sizeof(VarHdr) + cbName + pVar->cbData;
+ uint64_t offCurAligned = RT_ALIGN_64(offCur, sizeof(uint32_t));
+ if (offCurAligned > offCur)
+ {
+ /* Should be at most 3 bytes to align the next variable to a 32bit boundary. */
+ Assert(offCurAligned - offCur <= 3);
+ uint8_t abFill[3] = { 0xff };
+ rc = RTVfsFileWriteAt(pThis->hVfsBacking, offCur, &abFill[0], offCurAligned - offCur, NULL);
+ }
+
+ offCur = offCurAligned;
+ }
+ }
+
+ RTUtf16Free(pwszName);
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Fill the remainder with 0xff as it would be the case for a real NAND flash device. */
+ uint8_t abFF[512];
+ memset(&abFF[0], 0xff, sizeof(abFF));
+
+ uint64_t offStart = offCur;
+ uint64_t offEnd = pThis->offStoreData + pThis->cbVarStore;
+ while ( offStart < offEnd
+ && RT_SUCCESS(rc))
+ {
+ size_t cbThisWrite = RT_MIN(sizeof(abFF), offEnd - offStart);
+ rc = RTVfsFileWriteAt(pThis->hVfsBacking, offStart, &abFF[0], cbThisWrite, NULL);
+ offStart += cbThisWrite;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Tries to find a variable with the given name.
+ *
+ * @returns Pointer to the variable if found or NULL otherwise.
+ * @param pThis The variable store instance.
+ * @param pszName Name of the variable to look for.
+ * @param pidVar Where to store the index of the variable, optional.
+ */
+static PRTEFIVAR rtEfiVarStore_VarGet(PRTEFIVARSTORE pThis, const char *pszName, uint32_t *pidVar)
+{
+ for (uint32_t i = 0; i < pThis->cVars; i++)
+ if ( !pThis->paVars[i].fDeleted
+ && !strcmp(pszName, pThis->paVars[i].pszName))
+ {
+ if (pidVar)
+ *pidVar = i;
+ return &pThis->paVars[i];
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Maybe grows the array of variables to hold more entries.
+ *
+ * @returns IPRT status code.
+ * @param pThis The variable store instance.
+ */
+static int rtEfiVarStore_VarMaybeGrowEntries(PRTEFIVARSTORE pThis)
+{
+ if (pThis->cVars == pThis->cVarsMax)
+ {
+ /* Grow the variable array. */
+ uint32_t cVarsMaxNew = pThis->cVarsMax + 10;
+ PRTEFIVAR paVarsNew = (PRTEFIVAR)RTMemRealloc(pThis->paVars, cVarsMaxNew * sizeof(RTEFIVAR));
+ if (!paVarsNew)
+ return VERR_NO_MEMORY;
+
+ pThis->paVars = paVarsNew;
+ pThis->cVarsMax = cVarsMaxNew;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Add a variable with the given name.
+ *
+ * @returns Pointer to the entry or NULL if out of memory.
+ * @param pThis The variable store instance.
+ * @param pszName Name of the variable to add.
+ * @param pUuid The UUID of the variable owner.
+ * @param pidVar Where to store the variable index on success, optional
+ */
+static PRTEFIVAR rtEfiVarStore_VarAdd(PRTEFIVARSTORE pThis, const char *pszName, PCRTUUID pUuid, uint32_t *pidVar)
+{
+ Assert(!rtEfiVarStore_VarGet(pThis, pszName, NULL));
+
+ int rc = rtEfiVarStore_VarMaybeGrowEntries(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ PRTEFIVAR pVar = &pThis->paVars[pThis->cVars];
+ RT_ZERO(*pVar);
+
+ pVar->pszName = RTStrDup(pszName);
+ if (pVar->pszName)
+ {
+ pVar->pVarStore = pThis;
+ pVar->offVarData = 0;
+ pVar->fDeleted = false;
+ pVar->Uuid = *pUuid;
+ RTTimeNow(&pVar->Time);
+
+ rc = rtEfiVarStore_AddVarByGuid(pThis, pUuid, pThis->cVars);
+ AssertRC(rc); /** @todo */
+
+ if (pidVar)
+ *pidVar = pThis->cVars;
+ pThis->cVars++;
+ return pVar;
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Delete the given variable.
+ *
+ * @returns IPRT status code.
+ * @param pThis The variable store instance.
+ * @param pVar The variable.
+ */
+static int rtEfiVarStore_VarDel(PRTEFIVARSTORE pThis, PRTEFIVAR pVar)
+{
+ pVar->fDeleted = true;
+ if (pVar->pvData)
+ RTMemFree(pVar->pvData);
+ pVar->pvData = NULL;
+ pThis->cbVarData -= sizeof(EFI_AUTH_VAR_HEADER) + pVar->cbData;
+ /** @todo Delete from GUID entry. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Delete the variable with the given index.
+ *
+ * @returns IPRT status code.
+ * @param pThis The variable store instance.
+ * @param idVar The variable index.
+ */
+static int rtEfiVarStore_VarDelById(PRTEFIVARSTORE pThis, uint32_t idVar)
+{
+ return rtEfiVarStore_VarDel(pThis, &pThis->paVars[idVar]);
+}
+
+
+/**
+ * Delete the variable with the given name.
+ *
+ * @returns IPRT status code.
+ * @param pThis The variable store instance.
+ * @param pszName Name of the variable to delete.
+ */
+static int rtEfiVarStore_VarDelByName(PRTEFIVARSTORE pThis, const char *pszName)
+{
+ PRTEFIVAR pVar = rtEfiVarStore_VarGet(pThis, pszName, NULL);
+ if (pVar)
+ return rtEfiVarStore_VarDel(pThis, pVar);
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+/*
+ *
+ * File operations.
+ * File operations.
+ * File operations.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_Close(void *pvThis)
+{
+ PRTEFIVARFILE pThis = (PRTEFIVARFILE)pvThis;
+ LogFlow(("rtEfiVarStoreFile_Close(%p/%p)\n", pThis, pThis->pVar));
+ RT_NOREF(pThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTEFIVARFILE pThis = (PRTEFIVARFILE)pvThis;
+ uint64_t cbObject = pThis->pEntry->cbObject > 0
+ ? pThis->pEntry->cbObject
+ : pThis->pVar->cbData;
+ return rtEfiVarStore_QueryInfo(cbObject, false /*fIsDir*/, &pThis->pVar->Time, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTEFIVARFILE pThis = (PRTEFIVARFILE)pvThis;
+ PRTEFIVAR pVar = pThis->pVar;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ if (pThis->pEntry->cbObject)
+ rc = rtEfiVarStoreFile_ReadMem(pThis, (const uint8_t *)pVar + pThis->pEntry->offObject, pThis->pEntry->cbObject, off, pSgBuf, pcbRead);
+ else
+ {
+ /* Data section. */
+ if (!pVar->offVarData)
+ rc = rtEfiVarStoreFile_ReadMem(pThis, pVar->pvData, pVar->cbData, off, pSgBuf, pcbRead);
+ else
+ rc = rtEfiVarStoreFile_ReadFile(pThis, pVar->offVarData, pVar->cbData, off, pSgBuf, pcbRead);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTEFIVARFILE pThis = (PRTEFIVARFILE)pvThis;
+ PRTEFIVARSTORE pVarStore = pThis->pVarStore;
+ PRTEFIVAR pVar = pThis->pVar;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (pVarStore->fMntFlags & RTVFSMNT_F_READ_ONLY)
+ return VERR_WRITE_PROTECT;
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ if (pThis->pEntry->cbObject) /* These can't grow. */
+ rc = rtEfiVarStoreFile_WriteMem(pThis, (uint8_t *)pVar + pThis->pEntry->offObject, pThis->pEntry->cbObject,
+ off, pSgBuf, pcbWritten);
+ else
+ {
+ /* Writing data section. */
+ rc = rtEfiVarStore_VarReadData(pVar);
+ if (RT_SUCCESS(rc))
+ {
+ if (off + pSgBuf->paSegs[0].cbSeg > pVar->cbData)
+ rc = rtEfiVarStore_VarEnsureDataSz(pVar, off + pSgBuf->paSegs[0].cbSeg);
+ if (RT_SUCCESS(rc))
+ rc = rtEfiVarStoreFile_WriteMem(pThis, pVar->pvData, pVar->cbData, off, pSgBuf, pcbWritten);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTEFIVARFILE pThis = (PRTEFIVARFILE)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTEFIVARFILE pThis = (PRTEFIVARFILE)pvThis;
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = pThis->pVar->cbData + offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNew = (RTFOFF)pThis->offFile + offSeek;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ if (offNew >= 0)
+ {
+ pThis->offFile = offNew;
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+ }
+ return VERR_NEGATIVE_SEEK;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTEFIVARFILE pThis = (PRTEFIVARFILE)pvThis;
+ if (pThis->pEntry->cbObject)
+ *pcbFile = pThis->pEntry->cbObject;
+ else
+ *pcbFile = (uint64_t)pThis->pVar->cbData;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ PRTEFIVARFILE pThis = (PRTEFIVARFILE)pvThis;
+ PRTEFIVAR pVar = pThis->pVar;
+ PRTEFIVARSTORE pVarStore = pThis->pVarStore;
+
+ RT_NOREF(fFlags);
+
+ if (pVarStore->fMntFlags & RTVFSMNT_F_READ_ONLY)
+ return VERR_WRITE_PROTECT;
+
+ int rc = rtEfiVarStore_VarReadData(pVar);
+ if (RT_SUCCESS(rc))
+ rc = rtEfiVarStore_VarEnsureDataSz(pVar, cbFile);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = UINT32_MAX;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * EFI variable store file operations.
+ */
+static const RTVFSFILEOPS g_rtEfiVarStoreFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "EfiVarStore File",
+ rtEfiVarStoreFile_Close,
+ rtEfiVarStoreFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtEfiVarStoreFile_Read,
+ rtEfiVarStoreFile_Write,
+ rtEfiVarStoreFile_Flush,
+ NULL /*PollOne*/,
+ rtEfiVarStoreFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtEfiVarStoreFile_SetMode,
+ rtEfiVarStoreFile_SetTimes,
+ rtEfiVarStoreFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtEfiVarStoreFile_Seek,
+ rtEfiVarStoreFile_QuerySize,
+ rtEfiVarStoreFile_SetSize,
+ rtEfiVarStoreFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Creates a new VFS file from the given regular file inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param fOpen Open flags passed.
+ * @param pVar The variable this file accesses.
+ * @param pEntry File type entry.
+ * @param phVfsFile Where to store the VFS file handle on success.
+ * @param pErrInfo Where to record additional error information on error, optional.
+ */
+static int rtEfiVarStore_NewFile(PRTEFIVARSTORE pThis, uint64_t fOpen, PRTEFIVAR pVar,
+ PCRTEFIVARSTOREFILERAWENTRY pEntry, PRTVFSOBJ phVfsObj)
+{
+ RTVFSFILE hVfsFile;
+ PRTEFIVARFILE pNewFile;
+ int rc = RTVfsNewFile(&g_rtEfiVarStoreFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ &hVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ pNewFile->pEntry = pEntry;
+ pNewFile->pVarStore = pThis;
+ pNewFile->pVar = pVar;
+ pNewFile->offFile = 0;
+
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * Directory instance methods
+ * Directory instance methods
+ * Directory instance methods
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_Close(void *pvThis)
+{
+ PRTEFIVARSTOREDIR pThis = (PRTEFIVARSTOREDIR)pvThis;
+ LogFlowFunc(("pThis=%p\n", pThis));
+ pThis->pVarStore = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTEFIVARSTOREDIR pThis = (PRTEFIVARSTOREDIR)pvThis;
+ LogFlowFunc(("\n"));
+ return rtEfiVarStore_QueryInfo(1, true /*fIsDir*/, &pThis->Time, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ LogFlowFunc(("pszEntry='%s' fOpen=%#RX64 fFlags=%#x\n", pszEntry, fOpen, fFlags));
+ PRTEFIVARSTOREDIR pThis = (PRTEFIVARSTOREDIR)pvThis;
+ PRTEFIVARSTORE pVarStore = pThis->pVarStore;
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Special cases '.' and '.'
+ */
+ if (pszEntry[0] == '.')
+ {
+ RTEFIVARSTOREDIRTYPE enmDirTypeNew = RTEFIVARSTOREDIRTYPE_INVALID;
+ if (pszEntry[1] == '\0')
+ enmDirTypeNew = pThis->pEntry->enmType;
+ else if (pszEntry[1] == '.' && pszEntry[2] == '\0')
+ enmDirTypeNew = pThis->pEntry->enmParentType;
+
+ if (enmDirTypeNew != RTEFIVARSTOREDIRTYPE_INVALID)
+ {
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ rc = rtEfiVarStore_NewDirByType(pVarStore, enmDirTypeNew, NULL /*pGuid*/, 0 /*idVar*/, phVfsObj);
+ else
+ rc = VERR_ACCESS_DENIED;
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ return rc;
+ }
+ }
+
+ /*
+ * We can create or replace in certain directories.
+ */
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ { /* likely */ }
+ else
+ return VERR_WRITE_PROTECT;
+
+ switch (pThis->pEntry->enmType)
+ {
+ case RTEFIVARSTOREDIRTYPE_ROOT:
+ {
+ if (!strcmp(pszEntry, "by-name"))
+ return rtEfiVarStore_NewDirByType(pVarStore, RTEFIVARSTOREDIRTYPE_BY_NAME,
+ NULL /*pGuid*/, 0 /*idVar*/, phVfsObj);
+ else if (!strcmp(pszEntry, "by-uuid"))
+ return rtEfiVarStore_NewDirByType(pVarStore, RTEFIVARSTOREDIRTYPE_BY_GUID,
+ NULL /*pGuid*/, 0 /*idVar*/, phVfsObj);
+ else if (!strcmp(pszEntry, "raw"))
+ return rtEfiVarStore_NewDirByType(pVarStore, RTEFIVARSTOREDIRTYPE_RAW,
+ NULL /*pGuid*/, 0 /*idVar*/, phVfsObj);
+ else
+ rc = VERR_FILE_NOT_FOUND;
+ break;
+ }
+ case RTEFIVARSTOREDIRTYPE_GUID: /** @todo This looks through all variables, not only the ones with the GUID. */
+ case RTEFIVARSTOREDIRTYPE_BY_NAME:
+ case RTEFIVARSTOREDIRTYPE_RAW:
+ {
+ /* Look for the name. */
+ uint32_t idVar = 0;
+ PRTEFIVAR pVar = rtEfiVarStore_VarGet(pVarStore, pszEntry, &idVar);
+ if ( !pVar
+ && ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE))
+ {
+ if (pThis->pEntry->enmType == RTEFIVARSTOREDIRTYPE_GUID)
+ pVar = rtEfiVarStore_VarAdd(pVarStore, pszEntry, &pThis->pGuid->Uuid, &idVar);
+ else
+ {
+ RTUUID UuidNull;
+ RTUuidClear(&UuidNull);
+ pVar = rtEfiVarStore_VarAdd(pVarStore, pszEntry, &UuidNull, &idVar);
+ }
+
+ if (!pVar)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+
+ if (pVar)
+ {
+ if (pThis->pEntry->enmType == RTEFIVARSTOREDIRTYPE_RAW)
+ return rtEfiVarStore_NewDirByType(pVarStore, RTEFIVARSTOREDIRTYPE_RAW_ENTRY,
+ NULL /*pGuid*/, idVar, phVfsObj);
+ else
+ return rtEfiVarStore_NewFile(pVarStore, fOpen, pVar,
+ &g_aRawFiles[RTEFIVARSTORE_FILE_ENTRY_DATA], phVfsObj);
+ }
+
+ rc = VERR_FILE_NOT_FOUND;
+ break;
+ }
+ case RTEFIVARSTOREDIRTYPE_BY_GUID:
+ {
+ /* Look for the name. */
+ for (uint32_t i = 0; i < pVarStore->cGuids; i++)
+ {
+ PRTEFIGUID pGuid = &pVarStore->paGuids[i];
+ char szUuid[RTUUID_STR_LENGTH];
+ rc = RTUuidToStr(&pGuid->Uuid, szUuid, sizeof(szUuid));
+ AssertRC(rc);
+
+ if (!strcmp(pszEntry, szUuid))
+ return rtEfiVarStore_NewDirByType(pVarStore, RTEFIVARSTOREDIRTYPE_GUID,
+ pGuid, 0 /*idVar*/, phVfsObj);
+ }
+
+ rc = VERR_FILE_NOT_FOUND;
+ break;
+ }
+ case RTEFIVARSTOREDIRTYPE_RAW_ENTRY:
+ {
+ /* Look for the name. */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRawFiles); i++)
+ if (!strcmp(pszEntry, g_aRawFiles[i].pszName))
+ return rtEfiVarStore_NewFile(pVarStore, fOpen, &pVarStore->paVars[pThis->idVar],
+ &g_aRawFiles[i], phVfsObj);
+
+ rc = VERR_FILE_NOT_FOUND;
+ break;
+ }
+ case RTEFIVARSTOREDIRTYPE_INVALID:
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+
+ LogFlow(("rtEfiVarStoreDir_Open(%s): returns %Rrc\n", pszEntry, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ PRTEFIVARSTOREDIR pThis = (PRTEFIVARSTOREDIR)pvThis;
+ PRTEFIVARSTORE pVarStore = pThis->pVarStore;
+ LogFlowFunc(("\n"));
+
+ RT_NOREF(fMode, phVfsDir);
+
+ if (pVarStore->fMntFlags & RTVFSMNT_F_READ_ONLY)
+ return VERR_WRITE_PROTECT;
+
+ /* We support creating directories only for GUIDs and RAW variable entries. */
+ int rc = VINF_SUCCESS;
+ if (pThis->pEntry->enmType == RTEFIVARSTOREDIRTYPE_BY_GUID)
+ {
+ RTUUID Uuid;
+ rc = RTUuidFromStr(&Uuid, pszSubDir);
+ if (RT_FAILURE(rc))
+ return VERR_NOT_SUPPORTED;
+
+ PRTEFIGUID pGuid = rtEfiVarStore_GetGuid(pVarStore, &Uuid);
+ if (pGuid)
+ return VERR_ALREADY_EXISTS;
+
+ pGuid = rtEfiVarStore_AddGuid(pVarStore, &Uuid);
+ if (!pGuid)
+ return VERR_NO_MEMORY;
+ }
+ else if (pThis->pEntry->enmType == RTEFIVARSTOREDIRTYPE_RAW)
+ {
+ PRTEFIVAR pVar = rtEfiVarStore_VarGet(pVarStore, pszSubDir, NULL /*pidVar*/);
+ if (!pVar)
+ {
+ if (sizeof(EFI_AUTH_VAR_HEADER) < pVarStore->cbVarStore - pVarStore->cbVarData)
+ {
+ uint32_t idVar = 0;
+ RTUUID UuidNull;
+ RTUuidClear(&UuidNull);
+
+ pVar = rtEfiVarStore_VarAdd(pVarStore, pszSubDir, &UuidNull, &idVar);
+ if (pVar)
+ pVarStore->cbVarData += sizeof(EFI_AUTH_VAR_HEADER);
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_DISK_FULL;
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ PRTEFIVARSTOREDIR pThis = (PRTEFIVARSTOREDIR)pvThis;
+ PRTEFIVARSTORE pVarStore = pThis->pVarStore;
+ LogFlowFunc(("\n"));
+
+ RT_NOREF(fType);
+
+ if (pVarStore->fMntFlags & RTVFSMNT_F_READ_ONLY)
+ return VERR_WRITE_PROTECT;
+
+ if ( pThis->pEntry->enmType == RTEFIVARSTOREDIRTYPE_RAW
+ || pThis->pEntry->enmType == RTEFIVARSTOREDIRTYPE_BY_NAME
+ || pThis->pEntry->enmType == RTEFIVARSTOREDIRTYPE_GUID)
+ return rtEfiVarStore_VarDelByName(pVarStore, pszEntry);
+ else if (pThis->pEntry->enmType == RTEFIVARSTOREDIRTYPE_BY_GUID)
+ {
+ /* Look for the name. */
+ for (uint32_t i = 0; i < pVarStore->cGuids; i++)
+ {
+ PRTEFIGUID pGuid = &pVarStore->paGuids[i];
+ char szUuid[RTUUID_STR_LENGTH];
+ int rc = RTUuidToStr(&pGuid->Uuid, szUuid, sizeof(szUuid));
+ AssertRC(rc); RT_NOREF(rc);
+
+ if (!strcmp(pszEntry, szUuid))
+ {
+ for (uint32_t iVar = 0; iVar < pGuid->cVars; iVar++)
+ rtEfiVarStore_VarDelById(pVarStore, pGuid->paidxVars[iVar]);
+
+ if (pGuid->paidxVars)
+ RTMemFree(pGuid->paidxVars);
+ pGuid->paidxVars = NULL;
+ pGuid->cVars = 0;
+ pGuid->cVarsMax = 0;
+ RTUuidClear(&pGuid->Uuid);
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_FILE_NOT_FOUND;
+ }
+
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_RewindDir(void *pvThis)
+{
+ PRTEFIVARSTOREDIR pThis = (PRTEFIVARSTOREDIR)pvThis;
+ LogFlowFunc(("\n"));
+
+ pThis->idxNext = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtEfiVarStoreDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTEFIVARSTOREDIR pThis = (PRTEFIVARSTOREDIR)pvThis;
+ PRTEFIVARSTORE pVarStore = pThis->pVarStore;
+ LogFlowFunc(("\n"));
+
+ if (pThis->fNoMoreFiles)
+ return VERR_NO_MORE_FILES;
+
+ int rc = VINF_SUCCESS;
+ char aszUuid[RTUUID_STR_LENGTH];
+ const char *pszName = NULL;
+ size_t cbName = 0;
+ uint64_t cbObject = 0;
+ bool fIsDir = false;
+ bool fNoMoreFiles = false;
+ RTTIMESPEC Time;
+ PCRTTIMESPEC pTimeSpec = &Time;
+ RTTimeNow(&Time);
+
+ switch (pThis->pEntry->enmType)
+ {
+ case RTEFIVARSTOREDIRTYPE_ROOT:
+ {
+ if (pThis->idxNext == 0)
+ {
+ pszName = "by-name";
+ cbName = sizeof("by-name");
+ cbObject = 1;
+ fIsDir = true;
+ }
+ else if (pThis->idxNext == 1)
+ {
+ pszName = "by-uuid";
+ cbName = sizeof("by-uuid");
+ cbObject = 1;
+ fIsDir = true;
+ }
+ else if (pThis->idxNext == 2)
+ {
+ pszName = "raw";
+ cbName = sizeof("raw");
+ cbObject = 1;
+ fIsDir = true;
+ fNoMoreFiles = true;
+ }
+ break;
+ }
+ case RTEFIVARSTOREDIRTYPE_BY_NAME:
+ case RTEFIVARSTOREDIRTYPE_RAW:
+ {
+ PRTEFIVAR pVar = &pVarStore->paVars[pThis->idxNext];
+ if (pThis->idxNext + 1 == pVarStore->cVars)
+ fNoMoreFiles = true;
+ pszName = pVar->pszName;
+ cbName = strlen(pszName) + 1;
+ cbObject = pVar->cbData;
+ pTimeSpec = &pVar->Time;
+ if (pThis->pEntry->enmType == RTEFIVARSTOREDIRTYPE_RAW)
+ fIsDir = true;
+ break;
+ }
+ case RTEFIVARSTOREDIRTYPE_BY_GUID:
+ {
+ PRTEFIGUID pGuid = &pVarStore->paGuids[pThis->idxNext];
+ if (pThis->idxNext + 1 == pVarStore->cGuids)
+ fNoMoreFiles = true;
+ pszName = &aszUuid[0];
+ cbName = sizeof(aszUuid);
+ cbObject = 1;
+ rc = RTUuidToStr(&pGuid->Uuid, &aszUuid[0], cbName);
+ AssertRC(rc);
+ break;
+ }
+ case RTEFIVARSTOREDIRTYPE_GUID:
+ {
+ PRTEFIGUID pGuid = pThis->pGuid;
+ uint32_t idVar = pGuid->paidxVars[pThis->idxNext];
+ PRTEFIVAR pVar = &pVarStore->paVars[idVar];
+ if (pThis->idxNext + 1 == pGuid->cVars)
+ fNoMoreFiles = true;
+ pszName = pVar->pszName;
+ cbName = strlen(pszName) + 1;
+ cbObject = pVar->cbData;
+ pTimeSpec = &pVar->Time;
+ break;
+ }
+ case RTEFIVARSTOREDIRTYPE_RAW_ENTRY:
+ {
+ PCRTEFIVARSTOREFILERAWENTRY pEntry = &g_aRawFiles[pThis->idxNext];
+ PRTEFIVAR pVar = &pVarStore->paVars[pThis->idVar];
+
+ if (pThis->idxNext + 1 == RT_ELEMENTS(g_aRawFiles))
+ fNoMoreFiles = true;
+ pszName = pEntry->pszName;
+ cbName = strlen(pszName) + 1;
+ cbObject = pEntry->cbObject;
+ if (!cbObject)
+ cbObject = pVar->cbData;
+ pTimeSpec = &pVar->Time;
+ break;
+ }
+ case RTEFIVARSTOREDIRTYPE_INVALID:
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+
+ if (cbName <= 255)
+ {
+ size_t const cbDirEntry = *pcbDirEntry;
+
+ *pcbDirEntry = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cbName + 2]);
+ if (*pcbDirEntry <= cbDirEntry)
+ {
+ memcpy(&pDirEntry->szName[0], pszName, cbName);
+ pDirEntry->szName[cbName] = '\0';
+ pDirEntry->cbName = (uint16_t)cbName;
+ rc = rtEfiVarStore_QueryInfo(cbObject, fIsDir, &Time, &pDirEntry->Info, enmAddAttr);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->fNoMoreFiles = fNoMoreFiles;
+ pThis->idxNext++;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rc = VERR_FILENAME_TOO_LONG;
+ return rc;
+}
+
+
+/**
+ * EFI variable store directory operations.
+ */
+static const RTVFSDIROPS g_rtEfiVarStoreDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "EfiVarStore Dir",
+ rtEfiVarStoreDir_Close,
+ rtEfiVarStoreDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtEfiVarStoreDir_SetMode,
+ rtEfiVarStoreDir_SetTimes,
+ rtEfiVarStoreDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtEfiVarStoreDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile */,
+ NULL /* pfnOpenDir */,
+ rtEfiVarStoreDir_CreateDir,
+ rtEfiVarStoreDir_OpenSymlink,
+ rtEfiVarStoreDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtEfiVarStoreDir_UnlinkEntry,
+ rtEfiVarStoreDir_RenameEntry,
+ rtEfiVarStoreDir_RewindDir,
+ rtEfiVarStoreDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+static int rtEfiVarStore_NewDirByType(PRTEFIVARSTORE pThis, RTEFIVARSTOREDIRTYPE enmDirType,
+ PRTEFIGUID pGuid, uint32_t idVar, PRTVFSOBJ phVfsObj)
+{
+ RTVFSDIR hVfsDir;
+ PRTEFIVARSTOREDIR pDir;
+ int rc = RTVfsNewDir(&g_rtEfiVarStoreDirOps, sizeof(*pDir), 0 /*fFlags*/, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ &hVfsDir, (void **)&pDir);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTEFIVARSTOREDIRENTRY pEntry = NULL;
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aDirs); i++)
+ if (g_aDirs[i].enmType == enmDirType)
+ {
+ pEntry = &g_aDirs[i];
+ break;
+ }
+
+ AssertPtr(pEntry);
+ pDir->idxNext = 0;
+ pDir->pEntry = pEntry;
+ pDir->pVarStore = pThis;
+ pDir->pGuid = pGuid;
+ pDir->idVar = idVar;
+ RTTimeNow(&pDir->Time);
+
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+
+ return rc;
+}
+
+
+/*
+ *
+ * Volume level code.
+ * Volume level code.
+ * Volume level code.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtEfiVarStore_Close(void *pvThis)
+{
+ PRTEFIVARSTORE pThis = (PRTEFIVARSTORE)pvThis;
+
+ /* Write the variable store if in read/write mode. */
+ if (!(pThis->fMntFlags & RTVFSMNT_F_READ_ONLY))
+ {
+ int rc = rtEfiVarStore_Flush(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Backing file and handles.
+ */
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+ pThis->hVfsSelf = NIL_RTVFS;
+ if (pThis->paVars)
+ {
+ for (uint32_t i = 0; i < pThis->cVars; i++)
+ {
+ RTStrFree(pThis->paVars[i].pszName);
+ if (pThis->paVars[i].pvData)
+ RTMemFree(pThis->paVars[i].pvData);
+ }
+
+ RTMemFree(pThis->paVars);
+ pThis->paVars = NULL;
+ pThis->cVars = 0;
+ pThis->cVarsMax = 0;
+ }
+
+ if (pThis->paGuids)
+ {
+ for (uint32_t i = 0; i < pThis->cGuids; i++)
+ {
+ PRTEFIGUID pGuid = &pThis->paGuids[i];
+
+ if (pGuid->paidxVars)
+ {
+ RTMemFree(pGuid->paidxVars);
+ pGuid->paidxVars = NULL;
+ }
+ }
+
+ RTMemFree(pThis->paGuids);
+ pThis->paGuids = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtEfiVarStore_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtEfiVarStore_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTEFIVARSTORE pThis = (PRTEFIVARSTORE)pvThis;
+ RTVFSOBJ hVfsObj;
+ int rc = rtEfiVarStore_NewDirByType(pThis, RTEFIVARSTOREDIRTYPE_ROOT,
+ NULL /*pGuid*/, 0 /*idVar*/, &hVfsObj);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsDir = RTVfsObjToDir(hVfsObj);
+ RTVfsObjRelease(hVfsObj);
+ }
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+DECL_HIDDEN_CONST(const RTVFSOPS) g_rtEfiVarStoreOps =
+{
+ /* .Obj = */
+ {
+ /* .uVersion = */ RTVFSOBJOPS_VERSION,
+ /* .enmType = */ RTVFSOBJTYPE_VFS,
+ /* .pszName = */ "EfiVarStore",
+ /* .pfnClose = */ rtEfiVarStore_Close,
+ /* .pfnQueryInfo = */ rtEfiVarStore_QueryInfo,
+ /* .pfnQueryInfoEx = */ NULL,
+ /* .uEndMarker = */ RTVFSOBJOPS_VERSION
+ },
+ /* .uVersion = */ RTVFSOPS_VERSION,
+ /* .fFeatures = */ 0,
+ /* .pfnOpenRoot = */ rtEfiVarStore_OpenRoot,
+ /* .pfnQueryRangeState = */ NULL,
+ /* .uEndMarker = */ RTVFSOPS_VERSION
+};
+
+
+/**
+ * Validates the given firmware header.
+ *
+ * @returns true if the given header is considered valid, flse otherwise.
+ * @param pThis The EFI variable store instance.
+ * @param pFvHdr The firmware volume header to validate.
+ * @param poffData The offset into the backing where the data area begins.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtEfiVarStoreFvHdr_Validate(PRTEFIVARSTORE pThis, PCEFI_FIRMWARE_VOLUME_HEADER pFvHdr, uint64_t *poffData,
+ PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ rtEfiVarStoreFvHdr_Log(pFvHdr);
+#endif
+
+ EFI_GUID GuidNvData = EFI_VARSTORE_FILESYSTEM_GUID;
+ if (memcmp(&pFvHdr->GuidFilesystem, &GuidNvData, sizeof(GuidNvData)))
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Filesystem GUID doesn't indicate a variable store");
+ if (RT_LE2H_U64(pFvHdr->cbFv) > pThis->cbBacking)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Firmware volume length exceeds size of backing storage (truncated file?)");
+ /* Signature was already verfied by caller. */
+ /** @todo Check attributes. */
+ if (pFvHdr->bRsvd != 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Reserved field of header is not 0");
+ if (pFvHdr->bRevision != EFI_FIRMWARE_VOLUME_HEADER_REVISION)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Unexpected revision of the firmware volume header");
+ if (RT_LE2H_U16(pFvHdr->offExtHdr) != 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Firmware volume header contains unsupported extended headers");
+
+ /* Start calculating the checksum of the main header. */
+ uint16_t u16Chksum = 0;
+ const uint16_t *pu16 = (const uint16_t *)pFvHdr;
+ while (pu16 < (const uint16_t *)pFvHdr + (sizeof(*pFvHdr) / sizeof(uint16_t)))
+ u16Chksum += RT_LE2H_U16(*pu16++);
+
+ /* Read in the block map and verify it as well. */
+ uint64_t cbFvVol = 0;
+ uint64_t cbFvHdr = sizeof(*pFvHdr);
+ uint64_t offBlockMap = sizeof(*pFvHdr);
+ for (;;)
+ {
+ EFI_FW_BLOCK_MAP BlockMap;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, offBlockMap, &BlockMap, sizeof(BlockMap), NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Reading block map entry from %#RX64 failed", offBlockMap);
+
+ cbFvHdr += sizeof(BlockMap);
+ offBlockMap += sizeof(BlockMap);
+
+ /* A zero entry denotes the end. */
+ if ( !RT_LE2H_U32(BlockMap.cBlocks)
+ && !RT_LE2H_U32(BlockMap.cbBlock))
+ break;
+
+ cbFvVol += RT_LE2H_U32(BlockMap.cBlocks) * RT_LE2H_U32(BlockMap.cbBlock);
+
+ pu16 = (const uint16_t *)&BlockMap;
+ while (pu16 < (const uint16_t *)&BlockMap + (sizeof(BlockMap) / sizeof(uint16_t)))
+ u16Chksum += RT_LE2H_U16(*pu16++);
+ }
+
+ *poffData = offBlockMap;
+
+ if (u16Chksum)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Firmware volume header has incorrect checksum");
+ if (RT_LE2H_U16(pFvHdr->cbFvHdr) != cbFvHdr)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Unexpected firmware volume header size");
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates the given variable store header.
+ *
+ * @returns true if the given header is considered valid, false otherwise.
+ * @param pThis The EFI variable store instance.
+ * @param pHdr The variable store header to validate.
+ * @param pfAuth Where to store whether the variable store uses authenticated variables or not.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtEfiVarStoreHdr_Validate(PRTEFIVARSTORE pThis, PCEFI_VARSTORE_HEADER pHdr, bool *pfAuth, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ rtEfiVarStoreHdr_Log(pHdr);
+#endif
+
+ EFI_GUID GuidVarStoreAuth = EFI_VARSTORE_HEADER_GUID_AUTHENTICATED_VARIABLE;
+ EFI_GUID GuidVarStore = EFI_VARSTORE_HEADER_GUID_VARIABLE;
+ if (!memcmp(&pHdr->GuidVarStore, &GuidVarStoreAuth, sizeof(GuidVarStoreAuth)))
+ *pfAuth = true;
+ else if (!memcmp(&pHdr->GuidVarStore, &GuidVarStore, sizeof(GuidVarStore)))
+ *pfAuth = false;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Variable store GUID doesn't indicate a variable store (%RTuuid)", pHdr->GuidVarStore);
+ if (RT_LE2H_U32(pHdr->cbVarStore) >= pThis->cbBacking)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Variable store length exceeds size of backing storage (truncated file?): %#RX32, max %#RX64",
+ RT_LE2H_U32(pHdr->cbVarStore), pThis->cbBacking);
+ if (pHdr->bFmt != EFI_VARSTORE_HEADER_FMT_FORMATTED)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Variable store is not formatted (%#x)", pHdr->bFmt);
+ if (pHdr->bState != EFI_VARSTORE_HEADER_STATE_HEALTHY)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Variable store is not healthy (%#x)", pHdr->bState);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates the given authenticate variable header.
+ *
+ * @returns true if the given header is considered valid, false otherwise.
+ * @param pThis The EFI variable store instance.
+ * @param pVarHdr The variable header to validate.
+ * @param offVar Offset of the authenticated variable header.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtEfiVarStoreAuthVar_Validate(PRTEFIVARSTORE pThis, PCEFI_AUTH_VAR_HEADER pVarHdr, uint64_t offVar, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ rtEfiVarStoreAuthVarHdr_Log(pVarHdr, offVar);
+#endif
+
+ uint32_t cbName = RT_LE2H_U32(pVarHdr->cbName);
+ uint32_t cbData = RT_LE2H_U32(pVarHdr->cbData);
+ uint64_t cbVarMax = pThis->cbBacking - offVar - sizeof(*pVarHdr);
+ if ( cbVarMax <= cbName
+ || cbVarMax - cbName <= cbData)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Variable exceeds remaining space in store (cbName=%u cbData=%u cbVarMax=%llu)",
+ cbName, cbData, cbVarMax);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads the authenticated variable at the given offset.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_EOF if the end of the store was reached.
+ * @param pThis The EFI variable store instance.
+ * @param offVar Offset of the variable to load.
+ * @param poffVarEnd Where to store the offset pointing to the end of the variable.
+ * @param fIgnoreDelVars Flag whether to ignore deleted variables.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtEfiVarStoreLoadAuthVar(PRTEFIVARSTORE pThis, uint64_t offVar, uint64_t *poffVarEnd,
+ bool fIgnoreDelVars, PRTERRINFO pErrInfo)
+{
+ EFI_AUTH_VAR_HEADER VarHdr;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, offVar, &VarHdr, sizeof(VarHdr), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = rtEfiVarStoreAuthVar_Validate(pThis, &VarHdr, offVar, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (poffVarEnd)
+ *poffVarEnd = offVar + sizeof(VarHdr) + RT_LE2H_U32(VarHdr.cbData) + RT_LE2H_U32(VarHdr.cbName);
+
+ /* Only add complete variables or deleted variables when requested. */
+ if ( ( fIgnoreDelVars
+ && VarHdr.bState != EFI_AUTH_VAR_HEADER_STATE_ADDED)
+ || VarHdr.bState == EFI_AUTH_VAR_HEADER_STATE_HDR_VALID_ONLY)
+ return VINF_SUCCESS;
+
+ pThis->cbVarData += sizeof(VarHdr) + RT_LE2H_U32(VarHdr.cbData) + RT_LE2H_U32(VarHdr.cbName);
+
+ RTUTF16 awchName[128]; RT_ZERO(awchName);
+ if (RT_LE2H_U32(VarHdr.cbName) > sizeof(awchName) - sizeof(RTUTF16))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Variable name is too long (%llu vs. %llu)\n",
+ RT_LE2H_U32(VarHdr.cbName), sizeof(awchName));
+
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offVar + sizeof(VarHdr), &awchName[0], RT_LE2H_U32(VarHdr.cbName), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Log2(("Variable name '%ls'\n", &awchName[0]));
+ rc = rtEfiVarStore_VarMaybeGrowEntries(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PRTEFIVAR pVar = &pThis->paVars[pThis->cVars++];
+ pVar->pVarStore = pThis;
+ if (RT_LE2H_U32(VarHdr.cbData))
+ pVar->offVarData = offVar + sizeof(VarHdr) + RT_LE2H_U32(VarHdr.cbName);
+ else
+ pVar->offVarData = 0;
+ pVar->fAttr = RT_LE2H_U32(VarHdr.fAttr);
+ pVar->cMonotonic = RT_LE2H_U64(VarHdr.cMonotonic);
+ pVar->idPubKey = RT_LE2H_U32(VarHdr.idPubKey);
+ pVar->cbData = RT_LE2H_U32(VarHdr.cbData);
+ pVar->pvData = NULL;
+ pVar->fDeleted = false;
+ memcpy(&pVar->EfiTimestamp, &VarHdr.Timestamp, sizeof(VarHdr.Timestamp));
+
+ if (VarHdr.Timestamp.u8Month)
+ RTEfiTimeToTimeSpec(&pVar->Time, &VarHdr.Timestamp);
+ else
+ RTTimeNow(&pVar->Time);
+
+ RTEfiGuidToUuid(&pVar->Uuid, &VarHdr.GuidVendor);
+
+ rc = RTUtf16ToUtf8(&awchName[0], &pVar->pszName);
+ if (RT_FAILURE(rc))
+ pThis->cVars--;
+
+ rc = rtEfiVarStore_AddVarByGuid(pThis, &pVar->Uuid, pThis->cVars - 1);
+
+ return rc;
+}
+
+
+/**
+ * Looks for the next variable starting at the given offset.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_EOF if the end of the store was reached.
+ * @param pThis The EFI variable store instance.
+ * @param offStart Where in the image to start looking.
+ * @param poffVar Where to store the start of the next variable if found.
+ */
+static int rtEfiVarStoreFindVar(PRTEFIVARSTORE pThis, uint64_t offStart, uint64_t *poffVar)
+{
+ /* Try to find the ID indicating a variable start by loading data in chunks. */
+ uint64_t offEnd = pThis->offStoreData + pThis->cbVarStore;
+ while (offStart < offEnd)
+ {
+ uint16_t au16Tmp[_1K / sizeof(uint16_t)];
+ size_t cbThisRead = RT_MIN(sizeof(au16Tmp), offEnd - offStart);
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, offStart, &au16Tmp[0], sizeof(au16Tmp), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ for (uint32_t i = 0; i < RT_ELEMENTS(au16Tmp); i++)
+ if (RT_LE2H_U16(au16Tmp[i]) == EFI_AUTH_VAR_HEADER_START)
+ {
+ *poffVar = offStart + i * sizeof(uint16_t);
+ return VINF_SUCCESS;
+ }
+
+ offStart += cbThisRead;
+ }
+
+ return VERR_EOF;
+}
+
+
+/**
+ * Loads and parses the superblock of the filesystem.
+ *
+ * @returns IPRT status code.
+ * @param pThis The EFI variable store instance.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtEfiVarStoreLoad(PRTEFIVARSTORE pThis, PRTERRINFO pErrInfo)
+{
+ EFI_FIRMWARE_VOLUME_HEADER FvHdr;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, 0, &FvHdr, sizeof(FvHdr), NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading firmware volume header");
+
+ /* Validate the signature. */
+ if (RT_LE2H_U32(FvHdr.u32Signature) != EFI_FIRMWARE_VOLUME_HEADER_SIGNATURE)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not a EFI variable store - Signature mismatch: %RX32", RT_LE2H_U16(FvHdr.u32Signature));
+
+ uint64_t offData = 0;
+ rc = rtEfiVarStoreFvHdr_Validate(pThis, &FvHdr, &offData, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ EFI_VARSTORE_HEADER StoreHdr;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offData, &StoreHdr, sizeof(StoreHdr), NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading variable store header");
+
+ rc = rtEfiVarStoreHdr_Validate(pThis, &StoreHdr, &pThis->fAuth, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->offStoreData = offData + sizeof(StoreHdr);
+ pThis->cbVarStore = RT_LE2H_U32(StoreHdr.cbVarStore) - sizeof(StoreHdr);
+
+ /* Go over variables and set up the pointers. */
+ offData = pThis->offStoreData;
+ for (;;)
+ {
+ uint64_t offVar = 0;
+
+ rc = rtEfiVarStoreFindVar(pThis, offData, &offVar);
+ if (RT_FAILURE(rc))
+ break;
+
+ rc = rtEfiVarStoreLoadAuthVar(pThis, offVar, &offData, true /* fIgnoreDelVars*/, pErrInfo);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Align to 16bit boundary. */
+ offData = RT_ALIGN_64(offData, 2);
+ }
+
+ if (rc == VERR_EOF) /* Reached end of variable store. */
+ rc = VINF_SUCCESS;
+
+ return rc;
+}
+
+
+/**
+ * Fills the given range with 0xff to match what a real NAND flash device would return for
+ * unwritten storage.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFile The VFS file handle to write to.
+ * @param offStart The start offset to fill.
+ * @param offEnd Offset to fill up to (exclusive).
+ */
+static int rtEfiVarStoreFillWithFF(RTVFSFILE hVfsFile, uint64_t offStart, uint64_t offEnd)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t abFF[512];
+ memset(&abFF[0], 0xff, sizeof(abFF));
+
+ while ( offStart < offEnd
+ && RT_SUCCESS(rc))
+ {
+ size_t cbThisWrite = RT_MIN(sizeof(abFF), offEnd - offStart);
+ rc = RTVfsFileWriteAt(hVfsFile, offStart, &abFF[0], cbThisWrite, NULL);
+ offStart += cbThisWrite;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTEfiVarStoreOpenAsVfs(RTVFSFILE hVfsFileIn, uint32_t fMntFlags, uint32_t fVarStoreFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ AssertReturn(!(fMntFlags & ~RTVFSMNT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!fVarStoreFlags, VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a VFS instance and initialize the data so rtFsExtVol_Close works.
+ */
+ RTVFS hVfs;
+ PRTEFIVARSTORE pThis;
+ int rc = RTVfsNew(&g_rtEfiVarStoreOps, sizeof(*pThis), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsBacking = hVfsFileIn;
+ pThis->hVfsSelf = hVfs;
+ pThis->fMntFlags = fMntFlags;
+ pThis->fVarStoreFlags = fVarStoreFlags;
+
+ rc = RTVfsFileQuerySize(pThis->hVfsBacking, &pThis->cbBacking);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtEfiVarStoreLoad(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfs = hVfs;
+ return VINF_SUCCESS;
+ }
+ }
+
+ RTVfsRelease(hVfs);
+ *phVfs = NIL_RTVFS;
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+
+ return rc;
+}
+
+
+RTDECL(int) RTEfiVarStoreCreate(RTVFSFILE hVfsFile, uint64_t offStore, uint64_t cbStore, uint32_t fFlags, uint32_t cbBlock,
+ PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pErrInfo);
+
+ /*
+ * Validate input.
+ */
+ if (!cbBlock)
+ cbBlock = 4096;
+ else
+ AssertMsgReturn(cbBlock <= 8192 && RT_IS_POWER_OF_TWO(cbBlock),
+ ("cbBlock=%#x\n", cbBlock), VERR_INVALID_PARAMETER);
+ AssertReturn(!(fFlags & ~RTEFIVARSTORE_CREATE_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ if (!cbStore)
+ {
+ uint64_t cbFile;
+ int rc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ AssertRCReturn(rc, rc);
+ AssertMsgReturn(cbFile > offStore, ("cbFile=%#RX64 offStore=%#RX64\n", cbFile, offStore), VERR_INVALID_PARAMETER);
+ cbStore = cbFile - offStore;
+ }
+
+ uint32_t cbFtw = 0;
+ uint32_t offFtw = 0;
+ uint32_t cbVarStore = cbStore;
+ uint32_t cbNvEventLog = 0;
+ uint32_t offNvEventLog = 0;
+ if (!(fFlags & RTEFIVARSTORE_CREATE_F_NO_FTW_WORKING_SPACE))
+ {
+ /* Split the available space in half for the fault tolerant working area. */
+ /** @todo Don't fully understand how these values come together right now but
+ * we want to create NVRAM files matching the default OVMF_VARS.fd for now, see
+ * https://github.com/tianocore/edk2/commit/b24fca05751f8222acf264853709012e0ab7bf49
+ * for the layout.
+ * Probably have toadd more arguments to control the different parameters.
+ */
+ cbNvEventLog = _4K;
+ cbVarStore = cbStore / 2 - cbNvEventLog - _4K;
+ cbFtw = cbVarStore + _4K;
+ offNvEventLog = cbVarStore;
+ offFtw = offNvEventLog + cbNvEventLog;
+ }
+
+ uint32_t const cBlocks = (uint32_t)(cbStore / cbBlock);
+
+ EFI_GUID GuidVarStore = EFI_VARSTORE_FILESYSTEM_GUID;
+ EFI_GUID GuidVarAuth = EFI_VARSTORE_HEADER_GUID_AUTHENTICATED_VARIABLE;
+ EFI_FIRMWARE_VOLUME_HEADER FvHdr; RT_ZERO(FvHdr);
+ EFI_FW_BLOCK_MAP aBlockMap[2]; RT_ZERO(aBlockMap);
+ EFI_VARSTORE_HEADER VarStoreHdr; RT_ZERO(VarStoreHdr);
+
+ /* Firmware volume header. */
+ memcpy(&FvHdr.GuidFilesystem, &GuidVarStore, sizeof(GuidVarStore));
+ FvHdr.cbFv = RT_H2LE_U64(cbStore);
+ FvHdr.u32Signature = RT_H2LE_U32(EFI_FIRMWARE_VOLUME_HEADER_SIGNATURE);
+ FvHdr.fAttr = RT_H2LE_U32(0x4feff); /** @todo */
+ FvHdr.cbFvHdr = RT_H2LE_U16(sizeof(FvHdr) + sizeof(aBlockMap));
+ FvHdr.bRevision = EFI_FIRMWARE_VOLUME_HEADER_REVISION;
+
+ /* Start calculating the checksum of the main header. */
+ uint16_t u16Chksum = 0;
+ const uint16_t *pu16 = (const uint16_t *)&FvHdr;
+ while (pu16 < (const uint16_t *)&FvHdr + (sizeof(FvHdr) / sizeof(uint16_t)))
+ u16Chksum += RT_LE2H_U16(*pu16++);
+
+ /* Block map, the second entry remains 0 as it serves the delimiter. */
+ aBlockMap[0].cbBlock = RT_H2LE_U32(cbBlock);
+ aBlockMap[0].cBlocks = RT_H2LE_U32(cBlocks);
+
+ pu16 = (const uint16_t *)&aBlockMap[0];
+ while (pu16 < (const uint16_t *)&aBlockMap[0] + (sizeof(aBlockMap) / (sizeof(uint16_t))))
+ u16Chksum += RT_LE2H_U16(*pu16++);
+
+ FvHdr.u16Chksum = RT_H2LE_U16(UINT16_MAX - u16Chksum + 1);
+
+ /* Variable store header. */
+ memcpy(&VarStoreHdr.GuidVarStore, &GuidVarAuth, sizeof(GuidVarAuth));
+ VarStoreHdr.cbVarStore = RT_H2LE_U32(cbVarStore - sizeof(FvHdr) - sizeof(aBlockMap));
+ VarStoreHdr.bFmt = EFI_VARSTORE_HEADER_FMT_FORMATTED;
+ VarStoreHdr.bState = EFI_VARSTORE_HEADER_STATE_HEALTHY;
+
+ /* Write everything. */
+ int rc = RTVfsFileWriteAt(hVfsFile, offStore, &FvHdr, sizeof(FvHdr), NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileWriteAt(hVfsFile, offStore + sizeof(FvHdr), &aBlockMap[0], sizeof(aBlockMap), NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileWriteAt(hVfsFile, offStore + sizeof(FvHdr) + sizeof(aBlockMap), &VarStoreHdr, sizeof(VarStoreHdr), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Fill the remainder with 0xff as it would be the case for a real NAND flash device. */
+ uint64_t offStart = offStore + sizeof(FvHdr) + sizeof(aBlockMap) + sizeof(VarStoreHdr);
+ uint64_t offEnd = offStore + cbVarStore;
+
+ rc = rtEfiVarStoreFillWithFF(hVfsFile, offStart, offEnd);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && !(fFlags & RTEFIVARSTORE_CREATE_F_NO_FTW_WORKING_SPACE))
+ {
+ EFI_GUID GuidFtwArea = EFI_WORKING_BLOCK_SIGNATURE_GUID;
+ EFI_FTW_BLOCK_HEADER FtwHdr; RT_ZERO(FtwHdr);
+
+ memcpy(&FtwHdr.GuidSignature, &GuidFtwArea, sizeof(GuidFtwArea));
+ FtwHdr.fWorkingBlockValid = RT_H2LE_U32(0xfffffffe); /** @todo */
+ FtwHdr.cbWriteQueue = RT_H2LE_U64(0xfe0ULL); /* This comes from the default OVMF variable volume. */
+ FtwHdr.u32Chksum = RTCrc32(&FtwHdr, sizeof(FtwHdr));
+
+ /* The area starts with the event log which defaults to 0xff. */
+ rc = rtEfiVarStoreFillWithFF(hVfsFile, offNvEventLog, offNvEventLog + cbNvEventLog);
+ if (RT_SUCCESS(rc))
+ {
+ /* Write the FTW header. */
+ rc = RTVfsFileWriteAt(hVfsFile, offFtw, &FtwHdr, sizeof(FtwHdr), NULL);
+ if (RT_SUCCESS(rc))
+ rc = rtEfiVarStoreFillWithFF(hVfsFile, offFtw + sizeof(FtwHdr), offFtw + cbFtw);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainEfiVarStore_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
+ return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
+ if ( pElement->enmType != RTVFSOBJTYPE_VFS
+ && pElement->enmType != RTVFSOBJTYPE_DIR)
+ return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly ? RTVFSMNT_F_READ_ONLY : 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainEfiVarStore_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError);
+
+ int rc;
+ RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
+ if (hVfsFileIn != NIL_RTVFSFILE)
+ {
+ RTVFS hVfs;
+ rc = RTEfiVarStoreOpenAsVfs(hVfsFileIn, (uint32_t)pElement->uProvider, (uint32_t)(pElement->uProvider >> 32), &hVfs, pErrInfo);
+ RTVfsFileRelease(hVfsFileIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromVfs(hVfs);
+ RTVfsRelease(hVfs);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainEfiVarStore_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider
+ || !pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'efivarstore'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainEfiVarStoreReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "efivarstore",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a EFI variable store, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainEfiVarStore_Validate,
+ /* pfnInstantiate = */ rtVfsChainEfiVarStore_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainEfiVarStore_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainEfiVarStoreReg, rtVfsChainEfiVarStoreReg);
+
diff --git a/src/VBox/Runtime/common/err/Makefile.kup b/src/VBox/Runtime/common/err/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/err/Makefile.kup
diff --git a/src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp b/src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp
new file mode 100644
index 00000000..c79b49be
--- /dev/null
+++ b/src/VBox/Runtime/common/err/RTErrConvertFromErrno.cpp
@@ -0,0 +1,467 @@
+/* $Id: RTErrConvertFromErrno.cpp $ */
+/** @file
+ * IPRT - Convert errno to iprt status codes.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/err.h>
+#include "internal/iprt.h"
+
+#include <iprt/log.h>
+#include <iprt/assert.h>
+#include <iprt/errno.h>
+
+
+RTDECL(int) RTErrConvertFromErrno(int iNativeCode)
+{
+ /* very fast check for no error. */
+ if (iNativeCode == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Process error codes.
+ *
+ * (Use a switch and not a table since the numbers vary among compilers
+ * and OSes. So we let the compiler switch optimizer handle speed issues.)
+ *
+ * This switch is arranged like the Linux i386 errno.h! This switch is mirrored
+ * by RTErrConvertToErrno.
+ */
+ switch (iNativeCode)
+ { /* Linux number */
+#ifdef EPERM
+ case EPERM: return VERR_ACCESS_DENIED; /* 1 */
+#endif
+#ifdef ENOENT
+ case ENOENT: return VERR_FILE_NOT_FOUND;
+#endif
+#ifdef ESRCH
+ case ESRCH: return VERR_PROCESS_NOT_FOUND;
+#endif
+#ifdef EINTR
+ case EINTR: return VERR_INTERRUPTED;
+#endif
+#ifdef EIO
+ case EIO: return VERR_DEV_IO_ERROR;
+#endif
+#ifdef ENXIO
+ case ENXIO: return VERR_DEV_IO_ERROR; /** @todo fix this duplicate error */
+#endif
+#ifdef E2BIG
+ case E2BIG: return VERR_TOO_MUCH_DATA;
+#endif
+#ifdef ENOEXEC
+ case ENOEXEC: return VERR_BAD_EXE_FORMAT;
+#endif
+#ifdef EBADF
+ case EBADF: return VERR_INVALID_HANDLE;
+#endif
+#ifdef ECHILD
+ case ECHILD: return VERR_PROCESS_NOT_FOUND; /* 10 */ /** @todo fix duplicate error */
+#endif
+#ifdef EAGAIN
+ case EAGAIN: return VERR_TRY_AGAIN;
+#endif
+#ifdef ENOMEM
+ case ENOMEM: return VERR_NO_MEMORY;
+#endif
+#ifdef EACCES
+ case EACCES: return VERR_ACCESS_DENIED; /** @todo fix duplicate error */
+#endif
+#ifdef EFAULT
+ case EFAULT: return VERR_INVALID_POINTER;
+#endif
+#ifdef ENOTBLK
+ //case ENOTBLK: return VERR_;
+#endif
+#ifdef EBUSY
+ case EBUSY: return VERR_RESOURCE_BUSY;
+#endif
+#ifdef EEXIST
+ case EEXIST: return VERR_ALREADY_EXISTS;
+#endif
+#ifdef EXDEV
+ case EXDEV: return VERR_NOT_SAME_DEVICE;
+#endif
+#ifdef ENODEV
+ case ENODEV: return VERR_NOT_SUPPORTED; /** @todo fix duplicate error */
+#endif
+#ifdef ENOTDIR
+ case ENOTDIR: return VERR_PATH_NOT_FOUND; /* 20 */
+#endif
+#ifdef EISDIR
+ case EISDIR: return VERR_IS_A_DIRECTORY;
+#endif
+#ifdef EINVAL
+ case EINVAL: return VERR_INVALID_PARAMETER;
+#endif
+#ifdef ENFILE
+ case ENFILE: return VERR_TOO_MANY_OPEN_FILES; /** @todo fix duplicate error */
+#endif
+#ifdef EMFILE
+ case EMFILE: return VERR_TOO_MANY_OPEN_FILES;
+#endif
+#ifdef ENOTTY
+ case ENOTTY: return VERR_INVALID_FUNCTION;
+#endif
+#ifdef ETXTBSY
+ case ETXTBSY: return VERR_SHARING_VIOLATION;
+#endif
+#ifdef EFBIG
+ case EFBIG: return VERR_FILE_TOO_BIG;
+#endif
+#ifdef ENOSPC
+ case ENOSPC: return VERR_DISK_FULL;
+#endif
+#ifdef ESPIPE
+ case ESPIPE: return VERR_SEEK_ON_DEVICE;
+#endif
+#ifdef EROFS
+ case EROFS: return VERR_WRITE_PROTECT; /* 30 */
+#endif
+#ifdef EMLINK
+ //case EMLINK:
+#endif
+#ifdef EPIPE
+ case EPIPE: return VERR_BROKEN_PIPE;
+#endif
+#ifdef EDOM
+ case EDOM: return VERR_INVALID_PARAMETER; /** @todo fix duplicate error */
+#endif
+#ifdef ERANGE
+ case ERANGE: return VERR_OUT_OF_RANGE;
+#endif
+#ifdef EDEADLK
+ case EDEADLK: return VERR_DEADLOCK;
+#endif
+#ifdef ENAMETOOLONG
+ case ENAMETOOLONG: return VERR_FILENAME_TOO_LONG;
+#endif
+#ifdef ENOLCK
+ case ENOLCK: return VERR_FILE_LOCK_FAILED;
+#endif
+#ifdef ENOSYS /** @todo map this differently on solaris. */
+ case ENOSYS: return VERR_NOT_SUPPORTED;
+#endif
+#ifdef ENOTEMPTY
+ case ENOTEMPTY: return VERR_DIR_NOT_EMPTY;
+#endif
+#ifdef ELOOP
+ case ELOOP: return VERR_TOO_MANY_SYMLINKS; /* 40 */
+#endif
+ //41??
+#ifdef ENOMSG
+ //case ENOMSG 42 /* No message of desired type */
+#endif
+#ifdef EIDRM
+ //case EIDRM 43 /* Identifier removed */
+#endif
+#ifdef ECHRNG
+ //case ECHRNG 44 /* Channel number out of range */
+#endif
+#ifdef EL2NSYNC
+ //case EL2NSYNC 45 /* Level 2 not synchronized */
+#endif
+#ifdef EL3HLT
+ //case EL3HLT 46 /* Level 3 halted */
+#endif
+#ifdef EL3RST
+ //case EL3RST 47 /* Level 3 reset */
+#endif
+#ifdef ELNRNG
+ //case ELNRNG 48 /* Link number out of range */
+#endif
+#ifdef EUNATCH
+ //case EUNATCH 49 /* Protocol driver not attached */
+#endif
+#ifdef ENOCSI
+ //case ENOCSI 50 /* No CSI structure available */
+#endif
+#ifdef EL2HLT
+ //case EL2HLT 51 /* Level 2 halted */
+#endif
+#ifdef EBADE
+ //case EBADE 52 /* Invalid exchange */
+#endif
+#ifdef EBADR
+ //case EBADR 53 /* Invalid request descriptor */
+#endif
+#ifdef EXFULL
+ //case EXFULL 54 /* Exchange full */
+#endif
+#ifdef ENOANO
+ //case ENOANO 55 /* No anode */
+#endif
+#ifdef EBADRQC
+ //case EBADRQC 56 /* Invalid request code */
+#endif
+#ifdef EBADSLT
+ //case EBADSLT 57 /* Invalid slot */
+#endif
+ //case 58:
+#ifdef EBFONT
+ //case EBFONT 59 /* Bad font file format */
+#endif
+#ifdef ENOSTR
+ //case ENOSTR 60 /* Device not a stream */
+#endif
+#ifdef ENODATA
+ case ENODATA: return VERR_NO_DATA;
+#endif
+#ifdef ETIME
+ //case ETIME 62 /* Timer expired */
+#endif
+#ifdef ENOSR
+ //case ENOSR 63 /* Out of streams resources */
+#endif
+#ifdef ENONET
+ case ENONET: return VERR_NET_NO_NETWORK;
+#endif
+#ifdef ENOPKG
+ //case ENOPKG 65 /* Package not installed */
+#endif
+#ifdef EREMOTE
+ //case EREMOTE 66 /* Object is remote */
+#endif
+#ifdef ENOLINK
+ //case ENOLINK 67 /* Link has been severed */
+#endif
+#ifdef EADV
+ //case EADV 68 /* Advertise error */
+#endif
+#ifdef ESRMNT
+ //case ESRMNT 69 /* Srmount error */
+#endif
+#ifdef ECOMM
+ //case ECOMM 70 /* Communication error on send */
+#endif
+#ifdef EPROTO
+ case EPROTO: return VERR_NET_PROTOCOL_ERROR;
+#endif
+#ifdef EMULTIHOP
+ //case EMULTIHOP 72 /* Multihop attempted */
+#endif
+#ifdef EDOTDOT
+ //case EDOTDOT 73 /* RFS specific error */
+#endif
+#ifdef EBADMSG
+ //case EBADMSG 74 /* Not a data message */
+#endif
+#ifdef EOVERFLOW
+ case EOVERFLOW: return VERR_TOO_MUCH_DATA; /** @todo fix duplicate error */
+#endif
+#ifdef ENOTUNIQ
+ case ENOTUNIQ: return VERR_NET_NOT_UNIQUE_NAME;
+#endif
+#ifdef EBADFD
+ case EBADFD: return VERR_INVALID_HANDLE; /** @todo fix duplicate error? */
+#endif
+#ifdef EREMCHG
+ //case EREMCHG 78 /* Remote address changed */
+#endif
+#ifdef ELIBACC
+ //case ELIBACC 79 /* Can not access a needed shared library */
+#endif
+#ifdef ELIBBAD
+ //case ELIBBAD 80 /* Accessing a corrupted shared library */
+#endif
+#ifdef ELIBSCN
+ //case ELIBSCN 81 /* .lib section in a.out corrupted */
+#endif
+#ifdef ELIBMAX
+ //case ELIBMAX 82 /* Attempting to link in too many shared libraries */
+#endif
+#ifdef ELIBEXEC
+ //case ELIBEXEC 83 /* Cannot exec a shared library directly */
+#endif
+#ifdef EILSEQ
+ case EILSEQ: return VERR_NO_TRANSLATION;
+#endif
+#ifdef ERESTART
+ case ERESTART: return VERR_INTERRUPTED;/** @todo fix duplicate error?*/
+#endif
+#ifdef ESTRPIPE
+ //case ESTRPIPE 86 /* Streams pipe error */
+#endif
+#ifdef EUSERS
+ //case EUSERS 87 /* Too many users */
+#endif
+#ifdef ENOTSOCK
+ case ENOTSOCK: return VERR_NET_NOT_SOCKET;
+#endif
+#ifdef EDESTADDRREQ
+ case EDESTADDRREQ: return VERR_NET_DEST_ADDRESS_REQUIRED;
+#endif
+#ifdef EMSGSIZE
+ case EMSGSIZE: return VERR_NET_MSG_SIZE;
+#endif
+#ifdef EPROTOTYPE
+ case EPROTOTYPE: return VERR_NET_PROTOCOL_TYPE;
+#endif
+#ifdef ENOPROTOOPT
+ case ENOPROTOOPT: return VERR_NET_PROTOCOL_NOT_AVAILABLE;
+#endif
+#ifdef EPROTONOSUPPORT
+ case EPROTONOSUPPORT: return VERR_NET_PROTOCOL_NOT_SUPPORTED;
+#endif
+#ifdef ESOCKTNOSUPPORT
+ case ESOCKTNOSUPPORT: return VERR_NET_SOCKET_TYPE_NOT_SUPPORTED;
+#endif
+#ifdef EOPNOTSUPP /** @todo map this differently on solaris. */
+ case EOPNOTSUPP: return VERR_NET_OPERATION_NOT_SUPPORTED;
+#endif
+#ifdef EPFNOSUPPORT
+ case EPFNOSUPPORT: return VERR_NET_PROTOCOL_FAMILY_NOT_SUPPORTED;
+#endif
+#ifdef EAFNOSUPPORT
+ case EAFNOSUPPORT: return VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED;
+#endif
+#ifdef EADDRINUSE
+ case EADDRINUSE: return VERR_NET_ADDRESS_IN_USE;
+#endif
+#ifdef EADDRNOTAVAIL
+ case EADDRNOTAVAIL: return VERR_NET_ADDRESS_NOT_AVAILABLE;
+#endif
+#ifdef ENETDOWN
+ case ENETDOWN: return VERR_NET_DOWN;
+#endif
+#ifdef ENETUNREACH
+ case ENETUNREACH: return VERR_NET_UNREACHABLE;
+#endif
+#ifdef ENETRESET
+ case ENETRESET: return VERR_NET_CONNECTION_RESET;
+#endif
+#ifdef ECONNABORTED
+ case ECONNABORTED: return VERR_NET_CONNECTION_ABORTED;
+#endif
+#ifdef ECONNRESET
+ case ECONNRESET: return VERR_NET_CONNECTION_RESET_BY_PEER;
+#endif
+#ifdef ENOBUFS
+ case ENOBUFS: return VERR_NET_NO_BUFFER_SPACE;
+#endif
+#ifdef EISCONN
+ case EISCONN: return VERR_NET_ALREADY_CONNECTED;
+#endif
+#ifdef ENOTCONN
+ case ENOTCONN: return VERR_NET_NOT_CONNECTED;
+#endif
+#ifdef ESHUTDOWN
+ case ESHUTDOWN: return VERR_NET_SHUTDOWN;
+#endif
+#ifdef ETOOMANYREFS
+ case ETOOMANYREFS: return VERR_NET_TOO_MANY_REFERENCES;
+#endif
+#ifdef ETIMEDOUT
+ case ETIMEDOUT: return VERR_TIMEOUT;
+#endif
+#ifdef ECONNREFUSED
+ case ECONNREFUSED: return VERR_NET_CONNECTION_REFUSED;
+#endif
+#ifdef EHOSTDOWN
+ case EHOSTDOWN: return VERR_NET_HOST_DOWN;
+#endif
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH: return VERR_NET_HOST_UNREACHABLE;
+#endif
+#ifdef EALREADY
+# if !defined(ENOLCK) || (EALREADY != ENOLCK)
+ case EALREADY: return VERR_NET_ALREADY_IN_PROGRESS;
+# endif
+#endif
+#ifdef EINPROGRESS
+# if !defined(ENODEV) || (EINPROGRESS != ENODEV)
+ case EINPROGRESS: return VERR_NET_IN_PROGRESS;
+# endif
+#endif
+#ifdef ESTALE
+ case ESTALE: return VERR_STALE_FILE_HANDLE; /* 116: Stale NFS file handle */
+#endif
+#ifdef EUCLEAN
+ //case EUCLEAN 117 /* Structure needs cleaning */
+#endif
+#ifdef ENOTNAM
+ //case ENOTNAM 118 /* Not a XENIX named type file */
+#endif
+#ifdef ENAVAIL
+ //case ENAVAIL 119 /* No XENIX semaphores available */
+#endif
+#ifdef EISNAM
+ //case EISNAM 120 /* Is a named type file */
+#endif
+#ifdef EREMOTEIO
+ //case EREMOTEIO 121 /* Remote I/O error */
+#endif
+#ifdef EDQUOT
+ case EDQUOT: return VERR_DISK_FULL; /** @todo fix duplicate error */
+#endif
+#ifdef ENOMEDIUM
+ case ENOMEDIUM: return VERR_MEDIA_NOT_PRESENT;
+#endif
+#ifdef EMEDIUMTYPE
+ case EMEDIUMTYPE: return VERR_MEDIA_NOT_RECOGNIZED;
+#endif
+#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
+ case EWOULDBLOCK: return VERR_TRY_AGAIN;
+#endif
+
+ /* Non-linux */
+
+#ifdef EPROCLIM
+ case EPROCLIM: return VERR_MAX_PROCS_REACHED;
+#endif
+#ifdef EDOOFUS
+# if EDOOFUS != EINVAL
+ case EDOOFUS: return VERR_INTERNAL_ERROR;
+# endif
+#endif
+#ifdef ENOTSUP
+# ifndef EOPNOTSUPP
+ case ENOTSUP: return VERR_NOT_SUPPORTED;
+# else
+# if ENOTSUP != EOPNOTSUPP
+ case ENOTSUP: return VERR_NOT_SUPPORTED;
+# endif
+# endif
+#endif
+ default:
+ AssertLogRelMsgFailed(("Unhandled error code %d\n", iNativeCode));
+ return VERR_UNRESOLVED_ERROR;
+ }
+}
+RT_EXPORT_SYMBOL(RTErrConvertFromErrno);
+
diff --git a/src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp b/src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp
new file mode 100644
index 00000000..d8e9fbb7
--- /dev/null
+++ b/src/VBox/Runtime/common/err/RTErrConvertToErrno.cpp
@@ -0,0 +1,465 @@
+/* $Id: RTErrConvertToErrno.cpp $ */
+/** @file
+ * IPRT - Convert iprt status codes to errno.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/err.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/errno.h>
+
+
+RTDECL(int) RTErrConvertToErrno(int iErr)
+{
+ /* very fast check for no error. */
+ if (RT_SUCCESS(iErr))
+ return 0;
+
+ /*
+ * Process error codes.
+ *
+ * (Use a switch and not a table since the numbers vary among compilers
+ * and OSes. So we let the compiler switch optimizer handle speed issues.)
+ *
+ * This switch is arranged like the Linux i386 errno.h! It also mirrors the
+ * conversions performed by RTErrConvertFromErrno with a few extra case since
+ * there are far more IPRT status codes than Unix ones.
+ */
+ switch (iErr)
+ {
+#ifdef EPERM
+ case VERR_ACCESS_DENIED: return EPERM;
+#endif
+#ifdef ENOENT
+ case VERR_FILE_NOT_FOUND: return ENOENT;
+#endif
+#ifdef ESRCH
+ case VERR_PROCESS_NOT_FOUND: return ESRCH;
+#endif
+#ifdef EINTR
+ case VERR_INTERRUPTED: return EINTR;
+#endif
+#ifdef EIO
+ case VERR_DEV_IO_ERROR: return EIO;
+#endif
+#ifdef ENXIO
+ //case VERR_DEV_IO_ERROR: return ENXIO;
+#endif
+#ifdef E2BIG
+ case VERR_TOO_MUCH_DATA: return E2BIG;
+#endif
+#ifdef ENOEXEC
+ case VERR_BAD_EXE_FORMAT: return ENOEXEC;
+#endif
+#ifdef EBADF
+ case VERR_INVALID_HANDLE: return EBADF;
+#endif
+#ifdef ECHILD
+ //case VERR_PROCESS_NOT_FOUND: return ECHILD;
+#endif
+#ifdef EAGAIN
+ case VERR_TRY_AGAIN: return EAGAIN;
+#endif
+#ifdef ENOMEM
+ case VERR_NO_MEMORY: return ENOMEM;
+#endif
+#ifdef EACCES
+ //case VERR_ACCESS_DENIED: return EACCES;
+#endif
+#ifdef EFAULT
+ case VERR_INVALID_POINTER: return EFAULT;
+#endif
+#ifdef ENOTBLK
+ //case ENOTBLK: return VERR_;
+#endif
+#ifdef EBUSY
+ case VERR_RESOURCE_BUSY: return EBUSY;
+#endif
+#ifdef EEXIST
+ case VERR_ALREADY_EXISTS: return EEXIST;
+#endif
+#ifdef EXDEV
+ case VERR_NOT_SAME_DEVICE: return EXDEV;
+#endif
+#ifdef ENODEV
+ //case VERR_NOT_SUPPORTED: return ENODEV;
+#endif
+#ifdef ENOTDIR
+ case VERR_NOT_A_DIRECTORY:
+ case VERR_PATH_NOT_FOUND: return ENOTDIR;
+#endif
+#ifdef EISDIR
+ case VERR_IS_A_DIRECTORY: return EISDIR;
+#endif
+#ifdef EINVAL
+ case VERR_INVALID_PARAMETER: return EINVAL;
+#endif
+#ifdef ENFILE
+ case VERR_TOO_MANY_OPEN_FILES: return ENFILE;
+#endif
+#ifdef EMFILE
+ //case VERR_TOO_MANY_OPEN_FILES: return EMFILE;
+#endif
+#ifdef ENOTTY
+ case VERR_INVALID_FUNCTION: return ENOTTY;
+#endif
+#ifdef ETXTBSY
+ case VERR_SHARING_VIOLATION: return ETXTBSY;
+#endif
+#ifdef EFBIG
+ case VERR_FILE_TOO_BIG: return EFBIG;
+#endif
+#ifdef ENOSPC
+ case VERR_DISK_FULL: return ENOSPC;
+#endif
+#ifdef ESPIPE
+ case VERR_SEEK_ON_DEVICE: return ESPIPE;
+#endif
+#ifdef EROFS
+ case VERR_WRITE_PROTECT: return EROFS;
+#endif
+#ifdef EMLINK
+ //case EMLINK:
+#endif
+#ifdef EPIPE
+ case VERR_BROKEN_PIPE: return EPIPE;
+#endif
+#ifdef EDOM
+ //case VERR_INVALID_PARAMETER: return EDOM;
+#endif
+#ifdef ERANGE
+ case VERR_OUT_OF_RANGE: return ERANGE;
+ case VERR_FLOAT_UNDERFLOW: return ERANGE;
+ case VWRN_FLOAT_UNDERFLOW: return ERANGE;
+ case VERR_FLOAT_OVERFLOW: return ERANGE;
+ case VWRN_FLOAT_OVERFLOW: return ERANGE;
+#endif
+#ifdef EDEADLK
+ case VERR_DEADLOCK: return EDEADLK;
+#endif
+#ifdef ENAMETOOLONG
+ case VERR_FILENAME_TOO_LONG: return ENAMETOOLONG;
+#endif
+#ifdef ENOLCK
+ case VERR_FILE_LOCK_FAILED: return ENOLCK;
+#endif
+#ifdef ENOSYS
+ case VERR_NOT_IMPLEMENTED:
+ case VERR_NOT_SUPPORTED: return ENOSYS;
+#endif
+#ifdef ENOTEMPTY
+ case VERR_DIR_NOT_EMPTY: return ENOTEMPTY;
+#endif
+#ifdef ELOOP
+ case VERR_TOO_MANY_SYMLINKS: return ELOOP;
+#endif
+ //41??
+#ifdef ENOMSG
+ //case ENOMSG 42 /* No message of desired type */
+#endif
+#ifdef EIDRM
+ //case EIDRM 43 /* Identifier removed */
+#endif
+#ifdef ECHRNG
+ //case ECHRNG 44 /* Channel number out of range */
+#endif
+#ifdef EL2NSYNC
+ //case EL2NSYNC 45 /* Level 2 not synchronized */
+#endif
+#ifdef EL3HLT
+ //case EL3HLT 46 /* Level 3 halted */
+#endif
+#ifdef EL3RST
+ //case EL3RST 47 /* Level 3 reset */
+#endif
+#ifdef ELNRNG
+ //case ELNRNG 48 /* Link number out of range */
+#endif
+#ifdef EUNATCH
+ //case EUNATCH 49 /* Protocol driver not attached */
+#endif
+#ifdef ENOCSI
+ //case ENOCSI 50 /* No CSI structure available */
+#endif
+#ifdef EL2HLT
+ //case EL2HLT 51 /* Level 2 halted */
+#endif
+#ifdef EBADE
+ //case EBADE 52 /* Invalid exchange */
+#endif
+#ifdef EBADR
+ //case EBADR 53 /* Invalid request descriptor */
+#endif
+#ifdef EXFULL
+ //case EXFULL 54 /* Exchange full */
+#endif
+#ifdef ENOANO
+ //case ENOANO 55 /* No anode */
+#endif
+#ifdef EBADRQC
+ //case EBADRQC 56 /* Invalid request code */
+#endif
+#ifdef EBADSLT
+ //case EBADSLT 57 /* Invalid slot */
+#endif
+ //case 58:
+#ifdef EBFONT
+ //case EBFONT 59 /* Bad font file format */
+#endif
+#ifdef ENOSTR
+ //case ENOSTR 60 /* Device not a stream */
+#endif
+#ifdef ENODATA
+ case VERR_NO_DATA: return ENODATA;
+#endif
+#ifdef ETIME
+ //case ETIME 62 /* Timer expired */
+#endif
+#ifdef ENOSR
+ //case ENOSR 63 /* Out of streams resources */
+#endif
+#ifdef ENONET
+ case VERR_NET_NO_NETWORK: return ENONET;
+#endif
+#ifdef ENOPKG
+ //case ENOPKG 65 /* Package not installed */
+#endif
+#ifdef EREMOTE
+ //case EREMOTE 66 /* Object is remote */
+#endif
+#ifdef ENOLINK
+ //case ENOLINK 67 /* Link has been severed */
+#endif
+#ifdef EADV
+ //case EADV 68 /* Advertise error */
+#endif
+#ifdef ESRMNT
+ //case ESRMNT 69 /* Srmount error */
+#endif
+#ifdef ECOMM
+ //case ECOMM 70 /* Communication error on send */
+#endif
+#ifdef EPROTO
+ //case EPROTO 71 /* Protocol error */
+#endif
+#ifdef EMULTIHOP
+ //case EMULTIHOP 72 /* Multihop attempted */
+#endif
+#ifdef EDOTDOT
+ //case EDOTDOT 73 /* RFS specific error */
+#endif
+#ifdef EBADMSG
+ //case EBADMSG 74 /* Not a data message */
+#endif
+#ifdef EOVERFLOW
+ //case VERR_TOO_MUCH_DATA: return EOVERFLOW;
+#endif
+#ifdef ENOTUNIQ
+ case VERR_NET_NOT_UNIQUE_NAME: return ENOTUNIQ;
+#endif
+#ifdef EBADFD
+ //case VERR_INVALID_HANDLE: return EBADFD;
+#endif
+#ifdef EREMCHG
+ //case EREMCHG 78 /* Remote address changed */
+#endif
+#ifdef ELIBACC
+ //case ELIBACC 79 /* Can not access a needed shared library */
+#endif
+#ifdef ELIBBAD
+ //case ELIBBAD 80 /* Accessing a corrupted shared library */
+#endif
+#ifdef ELIBSCN
+ //case ELIBSCN 81 /* .lib section in a.out corrupted */
+#endif
+#ifdef ELIBMAX
+ //case ELIBMAX 82 /* Attempting to link in too many shared libraries */
+#endif
+#ifdef ELIBEXEC
+ //case ELIBEXEC 83 /* Cannot exec a shared library directly */
+#endif
+#ifdef EILSEQ
+ case VERR_NO_TRANSLATION: return EILSEQ;
+#endif
+#ifdef ERESTART
+ //case VERR_INTERRUPTED: return ERESTART;
+#endif
+#ifdef ESTRPIPE
+ //case ESTRPIPE 86 /* Streams pipe error */
+#endif
+#ifdef EUSERS
+ //case EUSERS 87 /* Too many users */
+#endif
+#ifdef ENOTSOCK
+ case VERR_NET_NOT_SOCKET: return ENOTSOCK;
+#endif
+#ifdef EDESTADDRREQ
+ case VERR_NET_DEST_ADDRESS_REQUIRED: return EDESTADDRREQ;
+#endif
+#ifdef EMSGSIZE
+ case VERR_NET_MSG_SIZE: return EMSGSIZE;
+#endif
+#ifdef EPROTOTYPE
+ case VERR_NET_PROTOCOL_TYPE: return EPROTOTYPE;
+#endif
+#ifdef ENOPROTOOPT
+ case VERR_NET_PROTOCOL_NOT_AVAILABLE: return ENOPROTOOPT;
+#endif
+#ifdef EPROTONOSUPPORT
+ case VERR_NET_PROTOCOL_NOT_SUPPORTED: return EPROTONOSUPPORT;
+#endif
+#ifdef ESOCKTNOSUPPORT
+ case VERR_NET_SOCKET_TYPE_NOT_SUPPORTED: return ESOCKTNOSUPPORT;
+#endif
+#ifdef EOPNOTSUPP
+ case VERR_NET_OPERATION_NOT_SUPPORTED: return EOPNOTSUPP;
+#endif
+#ifdef EPFNOSUPPORT
+ case VERR_NET_PROTOCOL_FAMILY_NOT_SUPPORTED: return EPFNOSUPPORT;
+#endif
+#ifdef EAFNOSUPPORT
+ case VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED: return EAFNOSUPPORT;
+#endif
+#ifdef EADDRINUSE
+ case VERR_NET_ADDRESS_IN_USE: return EADDRINUSE;
+#endif
+#ifdef EADDRNOTAVAIL
+ case VERR_NET_ADDRESS_NOT_AVAILABLE: return EADDRNOTAVAIL;
+#endif
+#ifdef ENETDOWN
+ case VERR_NET_DOWN: return ENETDOWN;
+#endif
+#ifdef ENETUNREACH
+ case VERR_NET_UNREACHABLE: return ENETUNREACH;
+#endif
+#ifdef ENETRESET
+ case VERR_NET_CONNECTION_RESET: return ENETRESET;
+#endif
+#ifdef ECONNABORTED
+ case VERR_NET_CONNECTION_ABORTED: return ECONNABORTED;
+#endif
+#ifdef ECONNRESET
+ case VERR_NET_CONNECTION_RESET_BY_PEER: return ECONNRESET;
+#endif
+#ifdef ENOBUFS
+ case VERR_NET_NO_BUFFER_SPACE: return ENOBUFS;
+#endif
+#ifdef EISCONN
+ case VERR_NET_ALREADY_CONNECTED: return EISCONN;
+#endif
+#ifdef ENOTCONN
+ case VERR_NET_NOT_CONNECTED: return ENOTCONN;
+#endif
+#ifdef ESHUTDOWN
+ case VERR_NET_SHUTDOWN: return ESHUTDOWN;
+#endif
+#ifdef ETOOMANYREFS
+ case VERR_NET_TOO_MANY_REFERENCES: return ETOOMANYREFS;
+#endif
+#ifdef ETIMEDOUT
+ case VERR_TIMEOUT: return ETIMEDOUT;
+#endif
+#ifdef ECONNREFUSED
+ case VERR_NET_CONNECTION_REFUSED: return ECONNREFUSED;
+#endif
+#ifdef EHOSTDOWN
+ case VERR_NET_HOST_DOWN: return EHOSTDOWN;
+#endif
+#ifdef EHOSTUNREACH
+ case VERR_NET_HOST_UNREACHABLE: return EHOSTUNREACH;
+#endif
+#ifdef EALREADY
+ case VERR_NET_ALREADY_IN_PROGRESS: return EALREADY;
+#endif
+#ifdef EINPROGRESS
+ case VERR_NET_IN_PROGRESS: return EINPROGRESS;
+#endif
+#ifdef ESTALE
+ //case ESTALE 116 /* Stale NFS file handle */
+#endif
+#ifdef EUCLEAN
+ //case EUCLEAN 117 /* Structure needs cleaning */
+#endif
+#ifdef ENOTNAM
+ //case ENOTNAM 118 /* Not a XENIX named type file */
+#endif
+#ifdef ENAVAIL
+ //case ENAVAIL 119 /* No XENIX semaphores available */
+#endif
+#ifdef EISNAM
+ //case EISNAM 120 /* Is a named type file */
+#endif
+#ifdef EREMOTEIO
+ //case EREMOTEIO 121 /* Remote I/O error */
+#endif
+#ifdef EDQUOT
+ //case VERR_DISK_FULL: return EDQUOT;
+#endif
+#ifdef ENOMEDIUM
+ case VERR_MEDIA_NOT_PRESENT: return ENOMEDIUM;
+#endif
+#ifdef EMEDIUMTYPE
+ case VERR_MEDIA_NOT_RECOGNIZED: return EMEDIUMTYPE;
+#endif
+
+ /* Non-linux */
+
+#ifdef EPROCLIM
+ case VERR_MAX_PROCS_REACHED: return EPROCLIM;
+#endif
+#ifdef EDOOFUS
+ case VERR_INTERNAL_ERROR:
+ case VERR_INTERNAL_ERROR_2:
+ case VERR_INTERNAL_ERROR_3: return EDOOFUS;
+#endif
+
+ default:
+ /* The idea here is that if you hit this, you will have to
+ translate the status code yourself. */
+ AssertMsgFailed(("Unhandled error code %Rrc\n", iErr));
+#ifdef EPROTO
+ return EPROTO;
+#else
+ return EINVAL;
+#endif
+ }
+}
+RT_EXPORT_SYMBOL(RTErrConvertToErrno);
+
diff --git a/src/VBox/Runtime/common/err/errinfo-alloc.cpp b/src/VBox/Runtime/common/err/errinfo-alloc.cpp
new file mode 100644
index 00000000..9c4f6ed0
--- /dev/null
+++ b/src/VBox/Runtime/common/err/errinfo-alloc.cpp
@@ -0,0 +1,80 @@
+/* $Id: errinfo-alloc.cpp $ */
+/** @file
+ * IPRT - Error Info, Allocators.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/err.h>
+
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(PRTERRINFO) RTErrInfoAlloc(size_t cbMsg)
+{
+ PRTERRINFO pErrInfo;
+ RTErrInfoAllocEx(cbMsg, &pErrInfo);
+ return pErrInfo;
+}
+
+
+RTDECL(int) RTErrInfoAllocEx(size_t cbMsg, PRTERRINFO *ppErrInfo)
+{
+ if (cbMsg == 0)
+ cbMsg = _4K;
+ else
+ cbMsg = RT_ALIGN_Z(cbMsg, 256);
+
+ PRTERRINFO pErrInfo;
+ *ppErrInfo = pErrInfo = (PRTERRINFO)RTMemTmpAlloc(sizeof(*pErrInfo) + cbMsg);
+ if (RT_UNLIKELY(!pErrInfo))
+ return VERR_NO_TMP_MEMORY;
+
+ RTErrInfoInit(pErrInfo, (char *)(pErrInfo + 1), cbMsg);
+ pErrInfo->fFlags = RTERRINFO_FLAGS_T_ALLOC | RTERRINFO_FLAGS_MAGIC;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTErrInfoFree(PRTERRINFO pErrInfo)
+{
+ RTMemTmpFree(pErrInfo);
+}
+
diff --git a/src/VBox/Runtime/common/err/errinfo.cpp b/src/VBox/Runtime/common/err/errinfo.cpp
new file mode 100644
index 00000000..1af02c8f
--- /dev/null
+++ b/src/VBox/Runtime/common/err/errinfo.cpp
@@ -0,0 +1,137 @@
+/* $Id: errinfo.cpp $ */
+/** @file
+ * IPRT - Error Info, Setters.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/errcore.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTErrInfoSet(PRTERRINFO pErrInfo, int rc, const char *pszMsg)
+{
+ if (pErrInfo)
+ {
+ AssertPtr(pErrInfo);
+ Assert((pErrInfo->fFlags & RTERRINFO_FLAGS_MAGIC_MASK) == RTERRINFO_FLAGS_MAGIC);
+
+ RTStrCopy(pErrInfo->pszMsg, pErrInfo->cbMsg, pszMsg);
+ pErrInfo->rc = rc;
+ pErrInfo->fFlags |= RTERRINFO_FLAGS_SET;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoSetF(PRTERRINFO pErrInfo, int rc, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTErrInfoSetV(pErrInfo, rc, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoSetV(PRTERRINFO pErrInfo, int rc, const char *pszFormat, va_list va)
+{
+ if (pErrInfo)
+ {
+ AssertPtr(pErrInfo);
+ Assert((pErrInfo->fFlags & RTERRINFO_FLAGS_MAGIC_MASK) == RTERRINFO_FLAGS_MAGIC);
+
+ RTStrPrintfV(pErrInfo->pszMsg, pErrInfo->cbMsg, pszFormat, va);
+ pErrInfo->rc = rc;
+ pErrInfo->fFlags |= RTERRINFO_FLAGS_SET;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoAdd(PRTERRINFO pErrInfo, int rc, const char *pszMsg)
+{
+ if (pErrInfo)
+ {
+ AssertPtr(pErrInfo);
+ if (pErrInfo->fFlags & RTERRINFO_FLAGS_SET)
+ RTStrCat(pErrInfo->pszMsg, pErrInfo->cbMsg, pszMsg);
+ else
+ {
+ while (*pszMsg == ' ')
+ pszMsg++;
+ return RTErrInfoSet(pErrInfo, rc, pszMsg);
+ }
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoAddF(PRTERRINFO pErrInfo, int rc, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTErrInfoAddV(pErrInfo, rc, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoAddV(PRTERRINFO pErrInfo, int rc, const char *pszFormat, va_list va)
+{
+ if (pErrInfo)
+ {
+ AssertPtr(pErrInfo);
+ Assert((pErrInfo->fFlags & RTERRINFO_FLAGS_MAGIC_MASK) == RTERRINFO_FLAGS_MAGIC);
+ if (pErrInfo->fFlags & RTERRINFO_FLAGS_SET)
+ {
+ char *pszOut = (char *)memchr(pErrInfo->pszMsg, '\0', pErrInfo->cbMsg - 2);
+ if (pszOut)
+ RTStrPrintfV(pszOut, &pErrInfo->pszMsg[pErrInfo->cbMsg] - pszOut, pszFormat, va);
+ }
+ else
+ {
+ while (*pszFormat == ' ')
+ pszFormat++;
+ return RTErrInfoSetV(pErrInfo, rc, pszFormat, va);
+ }
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/err/errinfolog.cpp b/src/VBox/Runtime/common/err/errinfolog.cpp
new file mode 100644
index 00000000..0ed53b75
--- /dev/null
+++ b/src/VBox/Runtime/common/err/errinfolog.cpp
@@ -0,0 +1,215 @@
+/* $Id: errinfolog.cpp $ */
+/** @file
+ * IPRT - Error Info, Setters with logging.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/errcore.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/log.h>
+
+
+RTDECL(int) RTErrInfoLogAndSet(PRTERRINFO pErrInfo, int rc, uint32_t iLogGroup, uint32_t fFlags, const char *pszMsg)
+{
+ /* The logging: */
+ if (fFlags & RTERRINFO_LOG_F_RELEASE)
+ {
+ PRTLOGGER pLogger = RTLogRelGetDefaultInstanceExWeak(RT_MAKE_U32(RTLOGGRPFLAGS_LEVEL_1, iLogGroup));
+ if (pLogger)
+ RTLogLoggerExWeak(pLogger, RTLOGGRPFLAGS_LEVEL_1, iLogGroup, "RTErrInfoSet(%Rrc): %s\n", rc, pszMsg);
+ }
+
+ PRTLOGGER pLogger = RTLogGetDefaultInstanceExWeak(RT_MAKE_U32(RTLOGGRPFLAGS_LEVEL_1, iLogGroup));
+ if (pLogger)
+ RTLogLoggerExWeak(pLogger, RTLOGGRPFLAGS_LEVEL_1, iLogGroup, "RTErrInfoSet(%Rrc): %s\n", rc, pszMsg);
+
+ /* The setting: */
+ if (pErrInfo)
+ {
+ AssertPtr(pErrInfo);
+ Assert((pErrInfo->fFlags & RTERRINFO_FLAGS_MAGIC_MASK) == RTERRINFO_FLAGS_MAGIC);
+
+ RTStrCopy(pErrInfo->pszMsg, pErrInfo->cbMsg, pszMsg);
+ pErrInfo->rc = rc;
+ pErrInfo->fFlags |= RTERRINFO_FLAGS_SET;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoLogAndSetF(PRTERRINFO pErrInfo, int rc, uint32_t iLogGroup, uint32_t fFlags, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTErrInfoLogAndSetV(pErrInfo, rc, iLogGroup, fFlags, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoLogAndSetV(PRTERRINFO pErrInfo, int rc, uint32_t iLogGroup, uint32_t fFlags, const char *pszFormat, va_list va)
+{
+ /* The logging: */
+ if (fFlags & RTERRINFO_LOG_F_RELEASE)
+ {
+ PRTLOGGER pLogger = RTLogRelGetDefaultInstanceExWeak(RT_MAKE_U32(RTLOGGRPFLAGS_LEVEL_1, iLogGroup));
+ if (pLogger)
+ {
+ va_list va2;
+ va_copy(va2, va);
+ RTLogLoggerExWeak(pLogger, RTLOGGRPFLAGS_LEVEL_1, iLogGroup, "RTErrInfoSet(%Rrc): %N\n", rc, pszFormat, &va2);
+ va_end(va2);
+ }
+ }
+
+ PRTLOGGER pLogger = RTLogGetDefaultInstanceExWeak(RT_MAKE_U32(RTLOGGRPFLAGS_LEVEL_1, iLogGroup));
+ if (pLogger)
+ {
+ va_list va2;
+ va_copy(va2, va);
+ RTLogLoggerExWeak(pLogger, RTLOGGRPFLAGS_LEVEL_1, iLogGroup, "RTErrInfoSet(%Rrc): %N\n", rc, pszFormat, &va2);
+ va_end(va2);
+ }
+
+ /* The setting: */
+ if (pErrInfo)
+ {
+ AssertPtr(pErrInfo);
+ Assert((pErrInfo->fFlags & RTERRINFO_FLAGS_MAGIC_MASK) == RTERRINFO_FLAGS_MAGIC);
+
+ RTStrPrintfV(pErrInfo->pszMsg, pErrInfo->cbMsg, pszFormat, va);
+ pErrInfo->rc = rc;
+ pErrInfo->fFlags |= RTERRINFO_FLAGS_SET;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoLogAndAdd(PRTERRINFO pErrInfo, int rc, uint32_t iLogGroup, uint32_t fFlags, const char *pszMsg)
+{
+ if (pErrInfo)
+ {
+ AssertPtr(pErrInfo);
+ if (pErrInfo->fFlags & RTERRINFO_FLAGS_SET)
+ RTStrCat(pErrInfo->pszMsg, pErrInfo->cbMsg, pszMsg);
+ else
+ {
+ while (*pszMsg == ' ')
+ pszMsg++;
+ return RTErrInfoSet(pErrInfo, rc, pszMsg);
+ }
+ }
+
+ /* The logging: */
+ if (fFlags & RTERRINFO_LOG_F_RELEASE)
+ {
+ PRTLOGGER pLogger = RTLogRelGetDefaultInstanceExWeak(RT_MAKE_U32(RTLOGGRPFLAGS_LEVEL_1, iLogGroup));
+ if (pLogger)
+ RTLogLoggerExWeak(pLogger, RTLOGGRPFLAGS_LEVEL_1, iLogGroup, "RTErrInfoAdd(%Rrc): %s\n", rc, pszMsg);
+ }
+
+ PRTLOGGER pLogger = RTLogGetDefaultInstanceExWeak(RT_MAKE_U32(RTLOGGRPFLAGS_LEVEL_1, iLogGroup));
+ if (pLogger)
+ RTLogLoggerExWeak(pLogger, RTLOGGRPFLAGS_LEVEL_1, iLogGroup, "RTErrInfoAdd(%Rrc): %s\n", rc, pszMsg);
+
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoLogAndAddF(PRTERRINFO pErrInfo, int rc, uint32_t iLogGroup, uint32_t fFlags, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTErrInfoLogAndAddV(pErrInfo, rc, iLogGroup, fFlags, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(int) RTErrInfoLogAndAddV(PRTERRINFO pErrInfo, int rc, uint32_t iLogGroup, uint32_t fFlags, const char *pszFormat, va_list va)
+{
+ if (pErrInfo)
+ {
+ AssertPtr(pErrInfo);
+ Assert((pErrInfo->fFlags & RTERRINFO_FLAGS_MAGIC_MASK) == RTERRINFO_FLAGS_MAGIC);
+ if (pErrInfo->fFlags & RTERRINFO_FLAGS_SET)
+ {
+ char *pszOut = (char *)memchr(pErrInfo->pszMsg, '\0', pErrInfo->cbMsg - 2);
+ if (pszOut)
+ {
+ va_list va2;
+ va_copy(va2, va);
+ RTStrPrintfV(pszOut, &pErrInfo->pszMsg[pErrInfo->cbMsg] - pszOut, pszFormat, va2);
+ va_end(va2);
+ }
+ }
+ else
+ {
+ while (*pszFormat == ' ')
+ pszFormat++;
+ return RTErrInfoSetV(pErrInfo, rc, pszFormat, va);
+ }
+ }
+
+ /* The logging: */
+ if (fFlags & RTERRINFO_LOG_F_RELEASE)
+ {
+ PRTLOGGER pLogger = RTLogRelGetDefaultInstanceExWeak(RT_MAKE_U32(RTLOGGRPFLAGS_LEVEL_1, iLogGroup));
+ if (pLogger)
+ {
+ va_list va2;
+ va_copy(va2, va);
+ RTLogLoggerExWeak(pLogger, RTLOGGRPFLAGS_LEVEL_1, iLogGroup, "RTErrInfoAdd(%Rrc): %N\n", rc, pszFormat, &va2);
+ va_end(va2);
+ }
+ }
+
+ PRTLOGGER pLogger = RTLogGetDefaultInstanceExWeak(RT_MAKE_U32(RTLOGGRPFLAGS_LEVEL_1, iLogGroup));
+ if (pLogger)
+ {
+ va_list va2;
+ va_copy(va2, va);
+ RTLogLoggerExWeak(pLogger, RTLOGGRPFLAGS_LEVEL_1, iLogGroup, "RTErrInfoAdd(%Rrc): %N\n", rc, pszFormat, &va2);
+ va_end(va2);
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/err/errmsg-sorter.cpp b/src/VBox/Runtime/common/err/errmsg-sorter.cpp
new file mode 100644
index 00000000..a4c68865
--- /dev/null
+++ b/src/VBox/Runtime/common/err/errmsg-sorter.cpp
@@ -0,0 +1,483 @@
+/* $Id: errmsg-sorter.cpp $ */
+/** @file
+ * IPRT - Status code messages, sorter build program.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/err.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+/*
+ * Include the string table code.
+ */
+#define BLDPROG_STRTAB_MAX_STRLEN 512
+#define BLDPROG_STRTAB_WITH_COMPRESSION
+#define BLDPROG_STRTAB_PURE_ASCII
+#define BLDPROG_STRTAB_WITH_CAMEL_WORDS
+#include <iprt/bldprog-strtab-template.cpp.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Used for raw-input and sorting. */
+typedef struct RTSTATUSMSGINT1
+{
+ /** Pointer to the short message string. */
+ const char *pszMsgShort;
+ /** Pointer to the full message string. */
+ const char *pszMsgFull;
+ /** Pointer to the define string. */
+ const char *pszDefine;
+ /** Status code number. */
+ int iCode;
+ /** Set if duplicate. */
+ bool fDuplicate;
+} RTSTATUSMSGINT1;
+typedef RTSTATUSMSGINT1 *PRTSTATUSMSGINT1;
+
+
+/** This is used when building the string table and printing it. */
+typedef struct RTSTATUSMSGINT2
+{
+ /** The short message string. */
+ BLDPROGSTRING MsgShort;
+ /** The full message string. */
+ BLDPROGSTRING MsgFull;
+ /** The define string. */
+ BLDPROGSTRING Define;
+ /** Pointer to the define string. */
+ const char *pszDefine;
+ /** Status code number. */
+ int iCode;
+ /** Index into the primary table (for multiple passes). */
+ unsigned idx1;
+} RTSTATUSMSGINT2;
+typedef RTSTATUSMSGINT2 *PRTSTATUSMSGINT2;
+
+
+/** This used to determin minimum field sizes. */
+typedef struct RTSTATUSMSGSTATS
+{
+ unsigned offMax;
+ unsigned cchMax;
+ unsigned cBitsOffset;
+ unsigned cBitsLength;
+} RTSTATUSMSGSTATS;
+typedef RTSTATUSMSGSTATS *PRTSTATUSMSGSTATS;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const char *g_pszProgName = "errmsg-sorter";
+static RTSTATUSMSGINT1 g_aStatusMsgs[] =
+{
+#if !defined(IPRT_NO_ERROR_DATA) && !defined(DOXYGEN_RUNNING)
+# include "errmsgdata.h"
+#else
+ { "Success.", "Success.", "VINF_SUCCESS", 0, false },
+#endif
+};
+
+
+static RTEXITCODE error(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ fprintf(stderr, "%s: error: ", g_pszProgName);
+ vfprintf(stderr, pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_FAILURE;
+}
+
+
+/** qsort callback. */
+static int CompareErrMsg(const void *pv1, const void *pv2) RT_NOTHROW_DEF
+{
+ PRTSTATUSMSGINT1 p1 = (PRTSTATUSMSGINT1)pv1;
+ PRTSTATUSMSGINT1 p2 = (PRTSTATUSMSGINT1)pv2;
+ int iDiff;
+ if (p1->iCode < p2->iCode)
+ iDiff = -1;
+ else if (p1->iCode > p2->iCode)
+ iDiff = 1;
+ else
+ iDiff = 0;
+ return iDiff;
+}
+
+
+/**
+ * Checks whether @a pszDefine is a deliberate duplicate define that should be
+ * omitted.
+ */
+static bool IgnoreDuplicateDefine(const char *pszDefine)
+{
+ size_t const cchDefine = strlen(pszDefine);
+
+ static const RTSTRTUPLE s_aTails[] =
+ {
+ { RT_STR_TUPLE("_FIRST") },
+ { RT_STR_TUPLE("_LAST") },
+ { RT_STR_TUPLE("_HIGEST") },
+ { RT_STR_TUPLE("_LOWEST") },
+ };
+ for (size_t i = 0; i < RT_ELEMENTS(s_aTails); i++)
+ if ( cchDefine > s_aTails[i].cch
+ && memcmp(&pszDefine[cchDefine - s_aTails[i].cch], s_aTails[i].psz, s_aTails[i].cch) == 0)
+ return true;
+
+ static const RTSTRTUPLE s_aDeliberateOrSilly[] =
+ {
+ { RT_STR_TUPLE("VERR_VRDP_TIMEOUT") },
+ { RT_STR_TUPLE("VINF_VRDP_SUCCESS") },
+ { RT_STR_TUPLE("VWRN_CONTINUE_RECOMPILE") },
+ { RT_STR_TUPLE("VWRN_PATM_CONTINUE_SEARCH") },
+ };
+ for (size_t i = 0; i < RT_ELEMENTS(s_aDeliberateOrSilly); i++)
+ if ( cchDefine == s_aDeliberateOrSilly[i].cch
+ && memcmp(pszDefine, s_aDeliberateOrSilly[i].psz, cchDefine) == 0)
+ return true;
+
+ return false;
+}
+
+
+DECLINLINE(void) GatherStringStats(PRTSTATUSMSGSTATS pStats, PBLDPROGSTRING pString)
+{
+ if (pStats->offMax < pString->offStrTab)
+ pStats->offMax = pString->offStrTab;
+ if (pStats->cchMax < pString->cchString)
+ pStats->cchMax = (unsigned)pString->cchString;
+}
+
+
+DECLINLINE(unsigned) CalcBitsForValue(size_t uValue)
+{
+ unsigned cBits = 1;
+ while (RT_BIT_64(cBits) < uValue && cBits < 64)
+ cBits++;
+ return cBits;
+}
+
+
+static void CalcBitsForStringStats(PRTSTATUSMSGSTATS pStats)
+{
+ pStats->cBitsOffset = CalcBitsForValue(pStats->offMax);
+ pStats->cBitsLength = CalcBitsForValue(pStats->cchMax);
+}
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * Parse arguments.
+ */
+ enum { kMode_All, kMode_NoFullMsg, kMode_OnlyDefines } enmMode;
+ if (argc == 3 && strcmp(argv[1], "--all") == 0)
+ enmMode = kMode_All;
+ else if (argc == 3 && strcmp(argv[1], "--no-full-msg") == 0)
+ enmMode = kMode_NoFullMsg;
+ else if (argc == 3 && strcmp(argv[1], "--only-defines") == 0)
+ enmMode = kMode_OnlyDefines;
+ else
+ {
+ fprintf(stderr,
+ "syntax error!\n"
+ "Usage: %s <--all|--no-full-msg|--only-defines> <outfile>\n", argv[0]);
+ return RTEXITCODE_SYNTAX;
+ }
+ const char * const pszOutFile = argv[2];
+
+ /*
+ * Sort the table and mark duplicates.
+ */
+ qsort(g_aStatusMsgs, RT_ELEMENTS(g_aStatusMsgs), sizeof(g_aStatusMsgs[0]), CompareErrMsg);
+
+ int rcExit = RTEXITCODE_SUCCESS;
+ int iPrev = INT32_MAX;
+ for (size_t i = 0; i < RT_ELEMENTS(g_aStatusMsgs); i++)
+ {
+ /* Deal with duplicates, trying to eliminate unnecessary *_FIRST, *_LAST,
+ *_LOWEST, and *_HIGHEST entries as well as some deliberate duplicate entries.
+ This means we need to look forward and backwards here. */
+ PRTSTATUSMSGINT1 pMsg = &g_aStatusMsgs[i];
+ if (pMsg->iCode == iPrev && i != 0)
+ {
+ if (IgnoreDuplicateDefine(pMsg->pszDefine))
+ {
+ pMsg->fDuplicate = true;
+ continue;
+ }
+ PRTSTATUSMSGINT1 pPrev = &g_aStatusMsgs[i - 1];
+ rcExit = error("Duplicate value %d - %s and %s\n", iPrev, pMsg->pszDefine, pPrev->pszDefine);
+ }
+ else if (i + 1 < RT_ELEMENTS(g_aStatusMsgs))
+ {
+ PRTSTATUSMSGINT1 pNext = &g_aStatusMsgs[i];
+ if ( pMsg->iCode == pNext->iCode
+ && IgnoreDuplicateDefine(pMsg->pszDefine))
+ {
+ pMsg->fDuplicate = true;
+ continue;
+ }
+ }
+ iPrev = pMsg->iCode;
+ pMsg->fDuplicate = false;
+ }
+
+ /*
+ * Create a string table for it all.
+ */
+ BLDPROGSTRTAB StrTab;
+ if (!BldProgStrTab_Init(&StrTab, RT_ELEMENTS(g_aStatusMsgs) * 3))
+ return error("Out of memory!\n");
+
+ static RTSTATUSMSGINT2 s_aStatusMsgs2[RT_ELEMENTS(g_aStatusMsgs)];
+ unsigned cStatusMsgs = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aStatusMsgs); i++)
+ if (!g_aStatusMsgs[i].fDuplicate)
+ {
+ s_aStatusMsgs2[cStatusMsgs].idx1 = i;
+ s_aStatusMsgs2[cStatusMsgs].iCode = g_aStatusMsgs[i].iCode;
+ s_aStatusMsgs2[cStatusMsgs].pszDefine = g_aStatusMsgs[i].pszDefine;
+ BldProgStrTab_AddStringDup(&StrTab, &s_aStatusMsgs2[cStatusMsgs].Define, g_aStatusMsgs[i].pszDefine);
+ cStatusMsgs++;
+ }
+
+ if (enmMode != kMode_OnlyDefines)
+ for (size_t i = 0; i < cStatusMsgs; i++)
+ BldProgStrTab_AddStringDup(&StrTab, &s_aStatusMsgs2[i].MsgShort, g_aStatusMsgs[s_aStatusMsgs2[i].idx1].pszMsgShort);
+
+ if (enmMode == kMode_All)
+ for (size_t i = 0; i < cStatusMsgs; i++)
+ BldProgStrTab_AddStringDup(&StrTab, &s_aStatusMsgs2[i].MsgFull, g_aStatusMsgs[s_aStatusMsgs2[i].idx1].pszMsgFull);
+
+ if (!BldProgStrTab_CompileIt(&StrTab, true))
+ return error("BldProgStrTab_CompileIt failed!\n");
+
+ /*
+ * Prepare output file.
+ */
+ FILE *pOut = fopen(pszOutFile, "wt");
+ if (pOut)
+ {
+ /*
+ * .
+ */
+ RTSTATUSMSGSTATS Defines = {0, 0, 0, 0};
+ RTSTATUSMSGSTATS MsgShort = {0, 0, 0, 0};
+ RTSTATUSMSGSTATS MsgFull = {0, 0, 0, 0};
+ for (size_t i = 0; i < cStatusMsgs; i++)
+ {
+ GatherStringStats(&Defines, &s_aStatusMsgs2[i].Define);
+ GatherStringStats(&MsgShort, &s_aStatusMsgs2[i].MsgShort);
+ GatherStringStats(&MsgFull, &s_aStatusMsgs2[i].MsgFull);
+ }
+ CalcBitsForStringStats(&Defines);
+ CalcBitsForStringStats(&MsgShort);
+ CalcBitsForStringStats(&MsgFull);
+ printf(" Defines: max offset %#x -> %u bits, max length %#x -> bits %u\n",
+ Defines.offMax, Defines.cBitsOffset, (unsigned)Defines.cchMax, Defines.cBitsLength);
+ if (enmMode != kMode_OnlyDefines)
+ printf("MsgShort: max offset %#x -> %u bits, max length %#x -> bits %u\n",
+ MsgShort.offMax, MsgShort.cBitsOffset, (unsigned)MsgShort.cchMax, MsgShort.cBitsLength);
+ if (enmMode == kMode_All)
+ printf(" MsgFull: max offset %#x -> %u bits, max length %#x -> bits %u\n",
+ MsgFull.offMax, MsgFull.cBitsOffset, (unsigned)MsgFull.cchMax, MsgFull.cBitsLength);
+
+ unsigned cBitsCodePos = CalcBitsForValue((size_t)s_aStatusMsgs2[cStatusMsgs - 1].iCode);
+ unsigned cBitsCodeNeg = CalcBitsForValue((size_t)-s_aStatusMsgs2[0].iCode);
+ unsigned cBitsCode = RT_MAX(cBitsCodePos, cBitsCodeNeg) + 1;
+ printf("Statuses: min %d, max %d -> %u bits\n",
+ s_aStatusMsgs2[0].iCode, s_aStatusMsgs2[cStatusMsgs - 1].iCode, cBitsCode);
+
+ /*
+ * Print the table.
+ */
+ fprintf(pOut,
+ "\n"
+ "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n"
+ "# pragma pack(1)\n"
+ "#endif\n"
+ "typedef struct RTMSGENTRYINT\n"
+ "{\n");
+ /* 16 + 16 + 8 */
+ bool fOptimalLayout = true;
+ if ( enmMode == kMode_OnlyDefines
+ && cBitsCode <= 16
+ && Defines.cBitsOffset <= 16
+ && Defines.cBitsLength <= 8)
+ fprintf(pOut,
+ " uint16_t offDefine; /* need %2u bits, max %#x */\n"
+ " uint8_t cchDefine; /* need %2u bits, max %#x */\n"
+ " int16_t iCode; /* need %2u bits */\n",
+ Defines.cBitsOffset, Defines.offMax, Defines.cBitsLength, Defines.cchMax, cBitsCode);
+ else if ( enmMode == kMode_NoFullMsg
+ && cBitsCode + Defines.cBitsOffset + Defines.cBitsLength + MsgShort.cBitsOffset + MsgShort.cBitsLength <= 64)
+ fprintf(pOut,
+ " uint64_t offDefine : %2u; /* max %#x */\n"
+ " uint64_t cchDefine : %2u; /* max %#x */\n"
+ " uint64_t offMsgShort : %2u; /* max %#x */\n"
+ " uint64_t cchMsgShort : %2u; /* max %#x */\n"
+ " int64_t iCode : %2u;\n",
+ Defines.cBitsOffset, Defines.offMax,
+ Defines.cBitsLength, Defines.cchMax,
+ MsgShort.cBitsOffset, MsgShort.offMax,
+ MsgShort.cBitsLength, MsgShort.cchMax,
+ cBitsCode);
+ else if ( enmMode == kMode_All
+ && Defines.cBitsOffset + Defines.cBitsLength
+ + MsgShort.cBitsOffset + MsgShort.cBitsLength
+ + MsgFull.cBitsOffset + MsgFull.cBitsLength
+ + cBitsCode <= 96
+ && cBitsCode + Defines.cBitsLength + MsgShort.cBitsLength <= 32)
+ fprintf(pOut,
+ " uint64_t offDefine : %2u; /* max %#x */\n"
+ " uint64_t offMsgShort : %2u; /* max %#x */\n"
+ " uint64_t offMsgFull : %2u; /* max %#x */\n"
+ " uint64_t cchMsgFull : %2u; /* max %#x */\n"
+ " int32_t iCode : %2u;\n"
+ " uint32_t cchDefine : %2u; /* max %#x */\n"
+ " uint32_t cchMsgShort : %2u; /* max %#x */\n",
+ Defines.cBitsOffset, Defines.offMax,
+ MsgShort.cBitsOffset, MsgShort.offMax,
+ MsgFull.cBitsOffset, MsgFull.offMax,
+ MsgFull.cBitsLength, MsgFull.cchMax,
+ cBitsCode,
+ Defines.cBitsLength, Defines.cchMax,
+ MsgShort.cBitsLength, MsgShort.cchMax);
+ else
+ {
+ fprintf(stderr, "%s: warning: Optimized structure layouts needs readjusting...\n", g_pszProgName);
+ fOptimalLayout = false;
+ fprintf(pOut,
+ " uint32_t offDefine : 23; /* need %u bits, max %#x */\n"
+ " uint32_t cchDefine : 9; /* need %u bits, max %#x */\n",
+ Defines.cBitsOffset, Defines.offMax, Defines.cBitsLength, Defines.cchMax);
+ if (enmMode != kMode_OnlyDefines)
+ fprintf(pOut,
+ " uint32_t offMsgShort : 23; /* need %u bits, max %#x */\n"
+ " uint32_t cchMsgShort : 9; /* need %u bits, max %#x */\n",
+ MsgShort.cBitsOffset, MsgShort.offMax, MsgShort.cBitsLength, MsgShort.offMax);
+ if (enmMode == kMode_All)
+ fprintf(pOut,
+ " uint32_t offMsgFull : 23; /* need %u bits, max %#x */\n"
+ " uint32_t cchMsgFull : 9; /* need %u bits, max %#x */\n",
+ MsgFull.cBitsOffset, MsgFull.offMax, MsgFull.cBitsLength, MsgFull.cchMax);
+ fprintf(pOut,
+ " int32_t iCode; /* need %u bits */\n", cBitsCode);
+ }
+ fprintf(pOut,
+ "} RTMSGENTRYINT;\n"
+ "typedef RTMSGENTRYINT *PCRTMSGENTRYINT;\n"
+ "#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)\n"
+ "# pragma pack()\n"
+ "#endif\n"
+ "\n"
+ "static const RTMSGENTRYINT g_aStatusMsgs[ /*%lu*/ ] =\n"
+ "{\n"
+ ,
+ (unsigned long)cStatusMsgs);
+
+ if (enmMode == kMode_All && fOptimalLayout)
+ for (size_t i = 0; i < cStatusMsgs; i++)
+ fprintf(pOut, " { %#08x, %#08x, %#08x, %3u, %6d, %3u, %3u }, /* %s */\n",
+ s_aStatusMsgs2[i].Define.offStrTab,
+ s_aStatusMsgs2[i].MsgShort.offStrTab,
+ s_aStatusMsgs2[i].MsgFull.offStrTab,
+ (unsigned)s_aStatusMsgs2[i].MsgFull.cchString,
+ s_aStatusMsgs2[i].iCode,
+ (unsigned)s_aStatusMsgs2[i].Define.cchString,
+ (unsigned)s_aStatusMsgs2[i].MsgShort.cchString,
+ s_aStatusMsgs2[i].pszDefine);
+ else if (enmMode == kMode_All)
+ for (size_t i = 0; i < cStatusMsgs; i++)
+ fprintf(pOut, " { %#08x, %3u, %#08x, %3u, %#08x, %3u, %8d }, /* %s */\n",
+ s_aStatusMsgs2[i].Define.offStrTab,
+ (unsigned)s_aStatusMsgs2[i].Define.cchString,
+ s_aStatusMsgs2[i].MsgShort.offStrTab,
+ (unsigned)s_aStatusMsgs2[i].MsgShort.cchString,
+ s_aStatusMsgs2[i].MsgFull.offStrTab,
+ (unsigned)s_aStatusMsgs2[i].MsgFull.cchString,
+ s_aStatusMsgs2[i].iCode,
+ s_aStatusMsgs2[i].pszDefine);
+ else if (enmMode == kMode_NoFullMsg)
+ for (size_t i = 0; i < cStatusMsgs; i++)
+ fprintf(pOut, " { %#08x, %3u, %#08x, %3u, %8d }, /* %s */\n",
+ s_aStatusMsgs2[i].Define.offStrTab,
+ (unsigned)s_aStatusMsgs2[i].Define.cchString,
+ s_aStatusMsgs2[i].MsgShort.offStrTab,
+ (unsigned)s_aStatusMsgs2[i].MsgShort.cchString,
+ s_aStatusMsgs2[i].iCode,
+ s_aStatusMsgs2[i].pszDefine);
+ else if (enmMode == kMode_OnlyDefines)
+ for (size_t i = 0; i < cStatusMsgs; i++)
+ fprintf(pOut, " { %#08x, %3u, %8d }, /* %s */\n",
+ s_aStatusMsgs2[i].Define.offStrTab,
+ (unsigned)s_aStatusMsgs2[i].Define.cchString,
+ s_aStatusMsgs2[i].iCode,
+ s_aStatusMsgs2[i].pszDefine);
+ else
+ return error("Unsupported message selection (%d)!\n", enmMode);
+ fprintf(pOut,
+ "};\n"
+ "\n");
+
+ BldProgStrTab_WriteStringTable(&StrTab, pOut, "static ", "g_", "StatusMsgStrTab");
+
+ /*
+ * Close the output file and we're done.
+ */
+ fflush(pOut);
+ if (ferror(pOut))
+ rcExit = error("Error writing '%s'!\n", pszOutFile);
+ if (fclose(pOut) != 0)
+ rcExit = error("Failed to close '%s' after writing it!\n", pszOutFile);
+ }
+ else
+ rcExit = error("Failed to open '%s' for writing!\n", pszOutFile);
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/common/err/errmsg.cpp b/src/VBox/Runtime/common/err/errmsg.cpp
new file mode 100644
index 00000000..5e37cba7
--- /dev/null
+++ b/src/VBox/Runtime/common/err/errmsg.cpp
@@ -0,0 +1,338 @@
+/* $Id: errmsg.cpp $ */
+/** @file
+ * IPRT - Status code messages.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/err.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <VBox/err.h>
+
+#include <iprt/bldprog-strtab.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if (defined(IN_RT_STATIC) || defined(IPRT_ERRMSG_DEFINES_ONLY)) && !defined(IPRT_ERRMSG_NO_FULL_MSG)
+/** Skip the full message in static builds to save space.
+ * This is defined when IPRT_ERRMSG_DEFINES_ONLY is defined. */
+# define IPRT_ERRMSG_NO_FULL_MSG
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef IPRT_NO_ERROR_DATA
+/* Cook data just for VINF_SUCCESS so that code below compiles fine. */
+static const char g_achStrTabData[] = { "VINF_SUCCESS" };
+static const RTBLDPROGSTRTAB g_StatusMsgStrTab = { g_achStrTabData, sizeof(g_achStrTabData) - 1, 0, NULL };
+static const struct
+{
+ int16_t iCode;
+ uint8_t offDefine;
+ uint8_t cchDefine;
+ uint8_t offMsgShort;
+ uint8_t cchMsgShort;
+ uint8_t offMsgFull;
+ uint8_t cchMsgFull;
+} g_aStatusMsgs[] =
+{
+ { VINF_SUCCESS, 0, 12, 0, 12, 0, 12, },
+};
+#elif defined(IPRT_ERRMSG_DEFINES_ONLY)
+# include "errmsgdata-only-defines.h"
+#elif defined(IPRT_ERRMSG_NO_FULL_MSG)
+# include "errmsgdata-no-full-msg.h"
+#else
+# include "errmsgdata-all.h"
+#endif
+
+
+/**
+ * Looks up the message table entry for @a rc.
+ *
+ * @returns index into g_aStatusMsgs on success, ~(size_t)0 if not found.
+ * @param rc The status code to locate the entry for.
+ */
+static size_t rtErrLookup(int rc)
+{
+ /*
+ * Perform binary search (duplicate code in rtErrWinLookup).
+ */
+ size_t iStart = 0;
+ size_t iEnd = RT_ELEMENTS(g_aStatusMsgs);
+ for (;;)
+ {
+ size_t i = iStart + (iEnd - iStart) / 2;
+ int const iCode = g_aStatusMsgs[i].iCode;
+ if (rc < iCode)
+ {
+ if (iStart < i)
+ iEnd = i;
+ else
+ break;
+ }
+ else if (rc > iCode)
+ {
+ i++;
+ if (i < iEnd)
+ iStart = i;
+ else
+ break;
+ }
+ else
+ return i;
+ }
+
+#ifdef RT_STRICT
+ for (size_t i = 0; i < RT_ELEMENTS(g_aStatusMsgs); i++)
+ Assert(g_aStatusMsgs[i].iCode != rc);
+#endif
+
+ return ~(size_t)0;
+}
+
+
+RTDECL(bool) RTErrIsKnown(int rc)
+{
+ return rtErrLookup(rc) != ~(size_t)0;
+}
+RT_EXPORT_SYMBOL(RTErrIsKnown);
+
+
+RTDECL(ssize_t) RTErrQueryDefine(int rc, char *pszBuf, size_t cbBuf, bool fFailIfUnknown)
+{
+ size_t idx = rtErrLookup(rc);
+ if (idx != ~(size_t)0)
+ return RTBldProgStrTabQueryString(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offDefine, g_aStatusMsgs[idx].cchDefine,
+ pszBuf, cbBuf);
+ if (fFailIfUnknown)
+ return VERR_NOT_FOUND;
+ return RTStrFormatU32(pszBuf, cbBuf, rc, 10, 0, 0, RTSTR_F_VALSIGNED);
+}
+RT_EXPORT_SYMBOL(RTErrQueryDefine);
+
+
+/**
+ * Helper for rtErrQueryMsgNotFound.
+ */
+DECLINLINE(ssize_t) rtErrQueryCopyHelper(char **ppszBuf, size_t *pcbBuf, const char *pszSrc, size_t cchSrc, ssize_t cchRet)
+{
+ char *pszDst = *ppszBuf;
+ size_t cbDst = *pcbBuf;
+ if (cbDst > cchSrc)
+ {
+ memcpy(pszDst, pszSrc, cchSrc);
+ cbDst -= cchSrc;
+ pszDst += cchSrc;
+ cchRet += cchSrc;
+ *pszDst = '\0';
+ }
+ else
+ {
+ while (cbDst > 1 && cchSrc > 0)
+ {
+ *pszDst++ = *pszSrc++;
+ cchSrc--;
+ cbDst--;
+ }
+ if (cbDst > 0)
+ *pszDst = '\0';
+ cchRet = VERR_BUFFER_OVERFLOW;
+ }
+ *ppszBuf = pszDst;
+ *pcbBuf = cbDst;
+ return cchRet;
+}
+
+
+/**
+ * RTErrQueryMsgShort & RTErrQueryMsgFull helper.
+ */
+DECL_NO_INLINE(static, ssize_t) rtErrQueryMsgNotFound(int rc, char *pszBuf, size_t cbBuf)
+{
+ /* Unknown Status %d (%#x) */
+ ssize_t cchRet = rtErrQueryCopyHelper(&pszBuf, &cbBuf, RT_STR_TUPLE("Unknown Status "), 0);
+ char szValue[64];
+ size_t cchValue = (size_t)RTStrFormatU32(szValue, sizeof(szValue), rc, 10, 0, 0, RTSTR_F_VALSIGNED);
+ cchRet = rtErrQueryCopyHelper(&pszBuf, &cbBuf, szValue, cchValue, cchRet);
+ cchRet = rtErrQueryCopyHelper(&pszBuf, &cbBuf, RT_STR_TUPLE(" ("), cchRet);
+ cchValue = (size_t)RTStrFormatU32(szValue, sizeof(szValue), rc, 16, 0, 0, RTSTR_F_SPECIAL);
+ cchRet = rtErrQueryCopyHelper(&pszBuf, &cbBuf, szValue, cchValue, cchRet);
+ cchRet = rtErrQueryCopyHelper(&pszBuf, &cbBuf, RT_STR_TUPLE(")"), cchRet);
+ return cchRet;
+}
+
+
+RTDECL(ssize_t) RTErrQueryMsgShort(int rc, char *pszBuf, size_t cbBuf, bool fFailIfUnknown)
+{
+ size_t idx = rtErrLookup(rc);
+ if (idx != ~(size_t)0)
+#ifdef IPRT_ERRMSG_DEFINES_ONLY
+ return RTBldProgStrTabQueryString(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offDefine, g_aStatusMsgs[idx].cchDefine,
+ pszBuf, cbBuf);
+#else
+ return RTBldProgStrTabQueryString(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offMsgShort, g_aStatusMsgs[idx].cchMsgShort,
+ pszBuf, cbBuf);
+#endif
+ if (fFailIfUnknown)
+ return VERR_NOT_FOUND;
+ return rtErrQueryMsgNotFound(rc, pszBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTErrQueryMsgShort);
+
+
+RTDECL(ssize_t) RTErrQueryMsgFull(int rc, char *pszBuf, size_t cbBuf, bool fFailIfUnknown)
+{
+#if defined(IPRT_ERRMSG_DEFINES_ONLY) || defined(IPRT_ERRMSG_NO_FULL_MSG)
+ return RTErrQueryMsgShort(rc, pszBuf, cbBuf, fFailIfUnknown);
+#else
+ size_t idx = rtErrLookup(rc);
+ if (idx != ~(size_t)0)
+ return RTBldProgStrTabQueryString(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offMsgFull, g_aStatusMsgs[idx].cchMsgFull,
+ pszBuf, cbBuf);
+ if (fFailIfUnknown)
+ return VERR_NOT_FOUND;
+ return rtErrQueryMsgNotFound(rc, pszBuf, cbBuf);
+#endif
+}
+RT_EXPORT_SYMBOL(RTErrQueryMsgShort);
+
+
+RTDECL(size_t) RTErrFormatDefine(int rc, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, char *pszTmp, size_t cbTmp)
+{
+ size_t idx = rtErrLookup(rc);
+ if (idx != ~(size_t)0)
+ return RTBldProgStrTabQueryOutput(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offDefine, g_aStatusMsgs[idx].cchDefine,
+ pfnOutput, pvArgOutput);
+ size_t cchValue = (size_t)RTStrFormatU32(pszTmp, cbTmp, rc, 10, 0, 0, RTSTR_F_VALSIGNED);
+ return pfnOutput(pvArgOutput, pszTmp, cchValue);
+}
+RT_EXPORT_SYMBOL(RTErrFormatDefine);
+
+
+/**
+ * RTErrFormatMsgShort & RTErrFormatMsgFull helper.
+ */
+static size_t rtErrFormatMsgNotFound(int rc, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, char *pszTmp, size_t cbTmp)
+{
+ size_t cchRet = pfnOutput(pvArgOutput, RT_STR_TUPLE("Unknown Status "));
+ size_t cchValue = (size_t)RTStrFormatU32(pszTmp, cbTmp, rc, 10, 0, 0, RTSTR_F_VALSIGNED);
+ cchRet += pfnOutput(pvArgOutput, pszTmp, cchValue);
+ cchRet += pfnOutput(pvArgOutput, RT_STR_TUPLE(" ("));
+ cchValue = (size_t)RTStrFormatU32(pszTmp, cbTmp, rc, 16, 0, 0, RTSTR_F_SPECIAL);
+ cchRet += pfnOutput(pvArgOutput, pszTmp, cchValue);
+ cchRet += pfnOutput(pvArgOutput, RT_STR_TUPLE(")"));
+ return cchRet;
+}
+
+
+RTDECL(size_t) RTErrFormatMsgShort(int rc, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, char *pszTmp, size_t cbTmp)
+{
+ size_t idx = rtErrLookup(rc);
+ if (idx != ~(size_t)0)
+#ifndef IPRT_ERRMSG_DEFINES_ONLY
+ return RTBldProgStrTabQueryOutput(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offMsgShort, g_aStatusMsgs[idx].cchMsgShort,
+ pfnOutput, pvArgOutput);
+#else
+ return RTBldProgStrTabQueryOutput(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offDefine, g_aStatusMsgs[idx].cchDefine,
+ pfnOutput, pvArgOutput);
+#endif
+ return rtErrFormatMsgNotFound(rc, pfnOutput, pvArgOutput, pszTmp, cbTmp);
+}
+RT_EXPORT_SYMBOL(RTErrFormatMsgShort);
+
+
+RTDECL(size_t) RTErrFormatMsgFull(int rc, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, char *pszTmp, size_t cbTmp)
+{
+#ifdef IPRT_ERRMSG_NO_FULL_MSG
+ return RTErrFormatMsgShort(rc, pfnOutput, pvArgOutput, pszTmp, cbTmp);
+#else
+ size_t idx = rtErrLookup(rc);
+ if (idx != ~(size_t)0)
+ return RTBldProgStrTabQueryOutput(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offMsgFull, g_aStatusMsgs[idx].cchMsgFull,
+ pfnOutput, pvArgOutput);
+ return rtErrFormatMsgNotFound(rc, pfnOutput, pvArgOutput, pszTmp, cbTmp);
+#endif
+}
+RT_EXPORT_SYMBOL(RTErrFormatMsgFull);
+
+
+RTDECL(size_t) RTErrFormatMsgAll(int rc, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, char *pszTmp, size_t cbTmp)
+{
+ size_t idx = rtErrLookup(rc);
+ if (idx != ~(size_t)0)
+ {
+ size_t cchValue;
+ size_t cchRet = RTBldProgStrTabQueryOutput(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offDefine, g_aStatusMsgs[idx].cchDefine,
+ pfnOutput, pvArgOutput);
+ cchRet += pfnOutput(pvArgOutput, RT_STR_TUPLE(" ("));
+ cchValue = (size_t)RTStrFormatU32(pszTmp, cbTmp, rc, 10, 0, 0, RTSTR_F_VALSIGNED);
+ cchRet += pfnOutput(pvArgOutput, pszTmp, cchValue);
+#ifdef IPRT_ERRMSG_DEFINES_ONLY
+ cchRet += pfnOutput(pvArgOutput, RT_STR_TUPLE(")"));
+#elif defined(IPRT_ERRMSG_NO_FULL_MSG)
+ cchRet += pfnOutput(pvArgOutput, RT_STR_TUPLE(") - "));
+ cchRet += RTBldProgStrTabQueryOutput(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offMsgShort, g_aStatusMsgs[idx].cchMsgShort,
+ pfnOutput, pvArgOutput);
+#else
+ cchRet += pfnOutput(pvArgOutput, RT_STR_TUPLE(") - "));
+ cchRet += RTBldProgStrTabQueryOutput(&g_StatusMsgStrTab,
+ g_aStatusMsgs[idx].offMsgFull, g_aStatusMsgs[idx].cchMsgFull,
+ pfnOutput, pvArgOutput);
+#endif
+ return cchRet;
+ }
+ return rtErrFormatMsgNotFound(rc, pfnOutput, pvArgOutput, pszTmp, cbTmp);
+}
+RT_EXPORT_SYMBOL(RTErrFormatMsgAll);
+
diff --git a/src/VBox/Runtime/common/err/errmsg.sed b/src/VBox/Runtime/common/err/errmsg.sed
new file mode 100644
index 00000000..97febba9
--- /dev/null
+++ b/src/VBox/Runtime/common/err/errmsg.sed
@@ -0,0 +1,98 @@
+# $Id: errmsg.sed $
+## @file
+# IPRT - SED script for converting */err.h.
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program 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
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# Handle text inside the markers.
+/SED-START/,/SED-END/{
+
+# if (#define) goto defines
+/^[[:space:]]*#[[:space:]]*define/b defines
+
+# if (/**) goto description
+/\/\*\*/b description
+
+}
+
+# Everything else is deleted!
+d
+b end
+
+
+##
+# Convert the defines
+:defines
+s/^[[:space:]]*#[[:space:]]*define[[:space:]]*\([[:alnum:]_]*\)[[:space:]]*\(.*\)[[:space:]]*$/ "\1",\n \1, false }, /
+b end
+
+##
+# Convert descriptive comments. /** desc */
+:description
+
+# Read all the lines belonging to the comment into the buffer.
+:look-for-end-of-comment
+/\*\//bend-of-comment
+N
+blook-for-end-of-comment
+:end-of-comment
+
+# anything with @{ and @} is skipped
+/@[\{\}]/d
+
+# Fix double spaces
+s/[[:space:]][[:space:]]/ /g
+
+# Fix \# sequences (doxygen needs them, we don't).
+s/\\#/#/g
+
+# insert punctuation.
+s/\([^.[:space:]]\)[[:space:]]*\*\//\1. \*\//
+
+# convert /** short. more
+s/[[:space:]]*\/\*\*[[:space:]]*/ { NULL, \"/
+s/ { NULL, \"\([^.!?"]*[.!?][.!?]*\)/ { \"\1\",\n \"\1/
+
+# terminate the string
+s/[[:space:]]*\*\//\"\,/
+
+# translate empty lines into new-lines (only one, please).
+s/[[:space:]]*[[:space:]]\*[[:space:]][[:space:]]*\*[[:space:]][[:space:]]*/\\n/g
+
+# remove asterics.
+s/[[:space:]]*[[:space:]]\*[[:space:]][[:space:]]*/ /g
+b end
+
+
+# next expression
+:end
diff --git a/src/VBox/Runtime/common/err/errmsgcom.sed b/src/VBox/Runtime/common/err/errmsgcom.sed
new file mode 100644
index 00000000..01d7f7fe
--- /dev/null
+++ b/src/VBox/Runtime/common/err/errmsgcom.sed
@@ -0,0 +1,76 @@
+# $Id: errmsgcom.sed $
+## @file
+# IPRT - SED script for converting COM errors
+#
+
+#
+# Copyright (C) 2006-2023 Oracle and/or its affiliates.
+#
+# This file is part of VirtualBox base platform packages, as
+# available from https://www.virtualbox.org.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, in version 3 of the
+# License.
+#
+# This program 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
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <https://www.gnu.org/licenses>.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+# in the VirtualBox distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+#
+
+# we only care about message definitions
+/No symbolic name defined/b skip_it
+\/\/ MessageId: /b messageid
+d
+b end
+
+
+# Everything else is deleted!
+:skip_it
+d
+b end
+
+
+#
+# A message ID we care about
+#
+:messageid
+# concatenate the next four lines to the string
+N
+N
+N
+N
+{
+ # remove DOS <CR>.
+ s/\r//g
+ # remove the message ID
+ s/\/\/ MessageId: //g
+ # remove the stuff in between
+ s/\/\/\n\/\/ MessageText:\n\/\/\n\/\/ *//g
+ # backslashes have to be escaped
+ s/\\/\\\\/g
+ # double quotes have to be escaped, too
+ s/"/\\"/g
+ # output C array entry
+ s/\([[:alnum:]_]*\)[\t ]*\n\(.*\)[\t ]*$/{ "\2", "\1", \1 }, /
+}
+b end
+
+# next expression
+:end
diff --git a/src/VBox/Runtime/common/err/errmsgxpcom.cpp b/src/VBox/Runtime/common/err/errmsgxpcom.cpp
new file mode 100644
index 00000000..170f7dea
--- /dev/null
+++ b/src/VBox/Runtime/common/err/errmsgxpcom.cpp
@@ -0,0 +1,170 @@
+/* $Id: errmsgxpcom.cpp $ */
+/** @file
+ * IPRT - Status code messages for XPCOM.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/errcore.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/errcore.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+typedef uint32_t VBOXSTATUSTYPE; /* used by errmsgvboxcomdata.h */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Array of messages.
+ * The data is generated by a sed script.
+ */
+static const RTCOMERRMSG g_aStatusMsgs[] =
+{
+#define MY_ERR(_def, _desc, _val) { _desc, _def, _val }
+
+ MY_ERR("NS_OK", "Success", UINT32_C(0x00000000)),
+ MY_ERR("NS_ERROR_NOT_IMPLEMENTED", "Not implemented", UINT32_C(0x80004001)),
+ MY_ERR("NS_ERROR_NO_INTERFACE", "Interface not supported", UINT32_C(0x80004002)),
+ MY_ERR("NS_ERROR_INVALID_POINTER", "Invalid pointer value", UINT32_C(0x80004003)),
+ MY_ERR("NS_ERROR_ABORT", "Operation aborted", UINT32_C(0x80004004)),
+ MY_ERR("NS_ERROR_FAILURE", "Operation failed", UINT32_C(0x80004005)),
+ MY_ERR("NS_ERROR_CALL_FAILED", "Call to remote object failed", UINT32_C(0x800706be)),
+ MY_ERR("NS_ERROR_UNEXPECTED", "Unexpected error", UINT32_C(0x8000ffff)),
+
+ MY_ERR("NS_ERROR_CANNOT_CONVERT_DATA", "Cannot convert data", UINT32_C(0x80010001)),
+ MY_ERR("NS_ERROR_OBJECT_IS_IMMUTABLE", "Object is immutable", UINT32_C(0x80010002)),
+ MY_ERR("NS_ERROR_LOSS_OF_SIGNIFICANT_DATA", "Loss of significant data", UINT32_C(0x80010003)),
+ MY_ERR("NS_ERROR_PROXY_INVALID_IN_PARAMETER", "Cannot proxy an IN parameter", UINT32_C(0x80010010)),
+ MY_ERR("NS_ERROR_PROXY_INVALID_OUT_PARAMETER", "Cannot proxy an OUT parameter", UINT32_C(0x80010011)),
+ MY_ERR("NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA", "Loss of insignificant data", UINT32_C(0x00010001)),
+
+ MY_ERR("E_ACCESSDENIED", "Access denied", UINT32_C(0x80070005)), /* VirtualBox addition */
+ MY_ERR("NS_ERROR_OUT_OF_MEMORY", "Memory allocation failed", UINT32_C(0x8007000e)),
+ MY_ERR("NS_ERROR_INVALID_ARG", "Invalid argument value", UINT32_C(0x80070057)),
+
+ MY_ERR("NS_ERROR_NO_AGGREGATION", "Class does not allow aggregation", UINT32_C(0x80040110)),
+ MY_ERR("NS_ERROR_NOT_AVAILABLE", "Resource not available", UINT32_C(0x80040111)),
+ MY_ERR("NS_ERROR_FACTORY_NOT_REGISTERED", "Class not registered", UINT32_C(0x80040154)),
+ MY_ERR("NS_ERROR_FACTORY_REGISTER_AGAIN", "Cannot be registered, try again later", UINT32_C(0x80040155)),
+ MY_ERR("NS_ERROR_FACTORY_NOT_LOADED", "Dynamically loaded factory cannot be found", UINT32_C(0x800401f8)),
+ MY_ERR("NS_ERROR_FACTORY_EXISTS", "Factory already exists", UINT32_C(0xc1f30100)),
+ MY_ERR("NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT", "Factory does not support signatures", UINT32_C(0xc1f30101)),
+ MY_ERR("NS_ERROR_NOT_INITIALIZED", "Instance not initialized", UINT32_C(0xc1f30001)),
+ MY_ERR("NS_ERROR_ALREADY_INITIALIZED", "Instance already initialized", UINT32_C(0xc1f30002)),
+
+ MY_ERR("NS_BASE_STREAM_CLOSED", "Stream closed", UINT32_C(0x80470002)),
+ MY_ERR("NS_BASE_STREAM_OSERROR", "Operative system stream error", UINT32_C(0x80470003)),
+ MY_ERR("NS_BASE_STREAM_ILLEGAL_ARGS", "Illegal argument to stream method", UINT32_C(0x80470004)),
+ MY_ERR("NS_BASE_STREAM_NO_CONVERTER", "No stream converter", UINT32_C(0x80470005)),
+ MY_ERR("NS_BASE_STREAM_BAD_CONVERSION", "Badstream conversion", UINT32_C(0x80470006)),
+ MY_ERR("NS_BASE_STREAM_WOULD_BLOCK", "Stream operation would block", UINT32_C(0x80470007)),
+
+ MY_ERR("NS_ERROR_FILE_UNRECOGNIZED_PATH", "Unrecognized path", UINT32_C(0x80520001)),
+ MY_ERR("NS_ERROR_FILE_UNRESOLVABLE_SYMLINK", "NS_ERROR_FILE_UNRESOLVABLE_SYMLINK", UINT32_C(0x80520002)),
+ MY_ERR("NS_ERROR_FILE_EXECUTION_FAILED", "NS_ERROR_FILE_EXECUTION_FAILED", UINT32_C(0x80520003)),
+ MY_ERR("NS_ERROR_FILE_UNKNOWN_TYPE", "NS_ERROR_FILE_UNKNOWN_TYPE", UINT32_C(0x80520004)),
+ MY_ERR("NS_ERROR_FILE_DESTINATION_NOT_DIR", "NS_ERROR_FILE_DESTINATION_NOT_DIR", UINT32_C(0x80520005)),
+ MY_ERR("NS_ERROR_FILE_TARGET_DOES_NOT_EXIST", "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST", UINT32_C(0x80520006)),
+ MY_ERR("NS_ERROR_FILE_COPY_OR_MOVE_FAILED", "NS_ERROR_FILE_COPY_OR_MOVE_FAILED", UINT32_C(0x80520007)),
+ MY_ERR("NS_ERROR_FILE_ALREADY_EXISTS", "NS_ERROR_FILE_ALREADY_EXISTS", UINT32_C(0x80520008)),
+ MY_ERR("NS_ERROR_FILE_INVALID_PATH", "NS_ERROR_FILE_INVALID_PATH", UINT32_C(0x80520009)),
+ MY_ERR("NS_ERROR_FILE_DISK_FULL", "NS_ERROR_FILE_DISK_FULL", UINT32_C(0x8052000a)),
+ MY_ERR("NS_ERROR_FILE_CORRUPTED", "NS_ERROR_FILE_CORRUPTED", UINT32_C(0x8052000b)),
+ MY_ERR("NS_ERROR_FILE_NOT_DIRECTORY", "NS_ERROR_FILE_NOT_DIRECTORY", UINT32_C(0x8052000c)),
+ MY_ERR("NS_ERROR_FILE_IS_DIRECTORY", "NS_ERROR_FILE_IS_DIRECTORY", UINT32_C(0x8052000d)),
+ MY_ERR("NS_ERROR_FILE_IS_LOCKED", "NS_ERROR_FILE_IS_LOCKED", UINT32_C(0x8052000e)),
+ MY_ERR("NS_ERROR_FILE_TOO_BIG", "NS_ERROR_FILE_TOO_BIG", UINT32_C(0x8052000f)),
+ MY_ERR("NS_ERROR_FILE_NO_DEVICE_SPACE", "NS_ERROR_FILE_NO_DEVICE_SPACE", UINT32_C(0x80520010)),
+ MY_ERR("NS_ERROR_FILE_NAME_TOO_LONG", "NS_ERROR_FILE_NAME_TOO_LONG", UINT32_C(0x80520011)),
+ MY_ERR("NS_ERROR_FILE_NOT_FOUND", "NS_ERROR_FILE_NOT_FOUND", UINT32_C(0x80520012)),
+ MY_ERR("NS_ERROR_FILE_READ_ONLY", "NS_ERROR_FILE_READ_ONLY", UINT32_C(0x80520013)),
+ MY_ERR("NS_ERROR_FILE_DIR_NOT_EMPTY", "NS_ERROR_FILE_DIR_NOT_EMPTY", UINT32_C(0x80520014)),
+ MY_ERR("NS_ERROR_FILE_ACCESS_DENIED", "NS_ERROR_FILE_ACCESS_DENIED", UINT32_C(0x80520015)),
+ MY_ERR("NS_SUCCESS_FILE_DIRECTORY_EMPTY", "NS_SUCCESS_FILE_DIRECTORY_EMPTY", UINT32_C(0x00520001)),
+ MY_ERR("NS_ERROR_SOCKET_FAIL", "IPC daemon socket error", UINT32_C(0xc1f30200)), /* new XPCOM error code */
+
+#if defined(VBOX) && !defined(IN_GUEST) && !defined(DOXYGEN_RUNNING)
+# include "errmsgvboxcomdata.h"
+#endif
+
+ { NULL, NULL, 0 }
+#undef MY_ERR
+};
+
+
+/** Temporary buffers to format unknown messages in.
+ * @{
+ */
+static char g_aszUnknownStr[8][64];
+static RTCOMERRMSG g_aUnknownMsgs[8] =
+{
+ { &g_aszUnknownStr[0][0], &g_aszUnknownStr[0][0], 0 },
+ { &g_aszUnknownStr[1][0], &g_aszUnknownStr[1][0], 0 },
+ { &g_aszUnknownStr[2][0], &g_aszUnknownStr[2][0], 0 },
+ { &g_aszUnknownStr[3][0], &g_aszUnknownStr[3][0], 0 },
+ { &g_aszUnknownStr[4][0], &g_aszUnknownStr[4][0], 0 },
+ { &g_aszUnknownStr[5][0], &g_aszUnknownStr[5][0], 0 },
+ { &g_aszUnknownStr[6][0], &g_aszUnknownStr[6][0], 0 },
+ { &g_aszUnknownStr[7][0], &g_aszUnknownStr[7][0], 0 }
+};
+/** Last used index in g_aUnknownMsgs. */
+static volatile uint32_t g_iUnknownMsgs;
+/** @} */
+
+
+RTDECL(PCRTCOMERRMSG) RTErrCOMGet(uint32_t rc)
+{
+ unsigned i;
+ for (i = 0; i < RT_ELEMENTS(g_aStatusMsgs) - 1; i++)
+ if (g_aStatusMsgs[i].iCode == rc)
+ return &g_aStatusMsgs[i];
+
+ /*
+ * Need to use the temporary stuff.
+ */
+ int32_t iMsg = (ASMAtomicIncU32(&g_iUnknownMsgs) - 1) % RT_ELEMENTS(g_aUnknownMsgs);
+ RTStrPrintf(&g_aszUnknownStr[iMsg][0], sizeof(g_aszUnknownStr[iMsg]), "Unknown Status 0x%X", rc);
+ return &g_aUnknownMsgs[iMsg];
+}
+RT_EXPORT_SYMBOL(RTErrCOMGet);
+
diff --git a/src/VBox/Runtime/common/err/nocrt-strerror.cpp b/src/VBox/Runtime/common/err/nocrt-strerror.cpp
new file mode 100644
index 00000000..e26136ea
--- /dev/null
+++ b/src/VBox/Runtime/common/err/nocrt-strerror.cpp
@@ -0,0 +1,464 @@
+/* $Id: nocrt-strerror.cpp $ */
+/** @file
+ * IPRT - No-CRT - Convert errno value to string.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/string.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/assert.h>
+#include <iprt/log.h>
+
+
+#undef strerror
+const char *RT_NOCRT(strerror)(int iErrNo)
+{
+ /*
+ * Process error codes.
+ *
+ * (Use a switch and not a table since the numbers vary among compilers
+ * and OSes. So we let the compiler switch optimizer handle speed issues.)
+ *
+ * This switch is arranged like the Linux i386 errno.h! This switch is mirrored
+ * by RTErrConvertToErrno and RTErrConvertFromErrno.
+ */
+ switch (iErrNo)
+ { /* Linux number */
+ case 0: return "no error";
+#ifdef EPERM
+ RT_CASE_RET_STR(EPERM); /* 1 */
+#endif
+#ifdef ENOENT
+ RT_CASE_RET_STR(ENOENT);
+#endif
+#ifdef ESRCH
+ RT_CASE_RET_STR(ESRCH);
+#endif
+#ifdef EINTR
+ RT_CASE_RET_STR(EINTR);
+#endif
+#ifdef EIO
+ RT_CASE_RET_STR(EIO);
+#endif
+#ifdef ENXIO
+ RT_CASE_RET_STR(ENXIO); /** @todo fix this duplicate error */
+#endif
+#ifdef E2BIG
+ RT_CASE_RET_STR(E2BIG);
+#endif
+#ifdef ENOEXEC
+ RT_CASE_RET_STR(ENOEXEC);
+#endif
+#ifdef EBADF
+ RT_CASE_RET_STR(EBADF);
+#endif
+#ifdef ECHILD
+ RT_CASE_RET_STR(ECHILD); /* 10 */ /** @todo fix duplicate error */
+#endif
+#ifdef EAGAIN
+ RT_CASE_RET_STR(EAGAIN);
+#endif
+#ifdef ENOMEM
+ RT_CASE_RET_STR(ENOMEM);
+#endif
+#ifdef EACCES
+ RT_CASE_RET_STR(EACCES); /** @todo fix duplicate error */
+#endif
+#ifdef EFAULT
+ RT_CASE_RET_STR(EFAULT);
+#endif
+#ifdef ENOTBLK
+ RT_CASE_RET_STR(ENOTBLK);
+#endif
+#ifdef EBUSY
+ RT_CASE_RET_STR(EBUSY);
+#endif
+#ifdef EEXIST
+ RT_CASE_RET_STR(EEXIST);
+#endif
+#ifdef EXDEV
+ RT_CASE_RET_STR(EXDEV);
+#endif
+#ifdef ENODEV
+ RT_CASE_RET_STR(ENODEV); /** @todo fix duplicate error */
+#endif
+#ifdef ENOTDIR
+ RT_CASE_RET_STR(ENOTDIR); /* 20 */
+#endif
+#ifdef EISDIR
+ RT_CASE_RET_STR(EISDIR);
+#endif
+#ifdef EINVAL
+ RT_CASE_RET_STR(EINVAL);
+#endif
+#ifdef ENFILE
+ RT_CASE_RET_STR(ENFILE); /** @todo fix duplicate error */
+#endif
+#ifdef EMFILE
+ RT_CASE_RET_STR(EMFILE);
+#endif
+#ifdef ENOTTY
+ RT_CASE_RET_STR(ENOTTY);
+#endif
+#ifdef ETXTBSY
+ RT_CASE_RET_STR(ETXTBSY);
+#endif
+#ifdef EFBIG
+ RT_CASE_RET_STR(EFBIG);
+#endif
+#ifdef ENOSPC
+ RT_CASE_RET_STR(ENOSPC);
+#endif
+#ifdef ESPIPE
+ RT_CASE_RET_STR(ESPIPE);
+#endif
+#ifdef EROFS
+ RT_CASE_RET_STR(EROFS); /* 30 */
+#endif
+#ifdef EMLINK
+ RT_CASE_RET_STR(EMLINK);
+#endif
+#ifdef EPIPE
+ RT_CASE_RET_STR(EPIPE);
+#endif
+#ifdef EDOM
+ RT_CASE_RET_STR(EDOM); /** @todo fix duplicate error */
+#endif
+#ifdef ERANGE
+ RT_CASE_RET_STR(ERANGE); /** @todo fix duplicate error */
+#endif
+#ifdef EDEADLK
+ RT_CASE_RET_STR(EDEADLK);
+#endif
+#ifdef ENAMETOOLONG
+ RT_CASE_RET_STR(ENAMETOOLONG);
+#endif
+#ifdef ENOLCK
+ RT_CASE_RET_STR(ENOLCK);
+#endif
+#ifdef ENOSYS /** @todo map this differently on solaris. */
+ RT_CASE_RET_STR(ENOSYS);
+#endif
+#ifdef ENOTEMPTY
+ RT_CASE_RET_STR(ENOTEMPTY);
+#endif
+#ifdef ELOOP
+ RT_CASE_RET_STR(ELOOP); /* 40 */
+#endif
+ //41??
+#ifdef ENOMSG
+ RT_CASE_RET_STR(ENOMSG);
+#endif
+#ifdef EIDRM
+ RT_CASE_RET_STR(EIDRM);
+#endif
+#ifdef ECHRNG
+ RT_CASE_RET_STR(ECHRNG);
+#endif
+#ifdef EL2NSYNC
+ RT_CASE_RET_STR(EL2NSYNC);
+#endif
+#ifdef EL3HLT
+ RT_CASE_RET_STR(EL3HLT);
+#endif
+#ifdef EL3RST
+ RT_CASE_RET_STR(EL3RST);
+#endif
+#ifdef ELNRNG
+ RT_CASE_RET_STR(ELNRNG);
+#endif
+#ifdef EUNATCH
+ RT_CASE_RET_STR(EUNATCH);
+#endif
+#ifdef ENOCSI
+ RT_CASE_RET_STR(ENOCSI);
+#endif
+#ifdef EL2HLT
+ RT_CASE_RET_STR(EL2HLT);
+#endif
+#ifdef EBADE
+ RT_CASE_RET_STR(EBADE);
+#endif
+#ifdef EBADR
+ RT_CASE_RET_STR(EBADR);
+#endif
+#ifdef EXFULL
+ RT_CASE_RET_STR(EXFULL);
+#endif
+#ifdef ENOANO
+ RT_CASE_RET_STR(ENOANO);
+#endif
+#ifdef EBADRQC
+ RT_CASE_RET_STR(EBADRQC);
+#endif
+#ifdef EBADSLT
+ RT_CASE_RET_STR(EBADSLT);
+#endif
+ //case 58:
+#ifdef EBFONT
+ RT_CASE_RET_STR(EBFONT);
+#endif
+#ifdef ENOSTR
+ RT_CASE_RET_STR(ENOSTR);
+#endif
+#ifdef ENODATA
+ RT_CASE_RET_STR(ENODATA);
+#endif
+#ifdef ETIME
+ RT_CASE_RET_STR(ETIME);
+#endif
+#ifdef ENOSR
+ RT_CASE_RET_STR(ENOSR);
+#endif
+#ifdef ENONET
+ RT_CASE_RET_STR(ENONET);
+#endif
+#ifdef ENOPKG
+ RT_CASE_RET_STR(ENOPKG);
+#endif
+#ifdef EREMOTE
+ RT_CASE_RET_STR(EREMOTE);
+#endif
+#ifdef ENOLINK
+ RT_CASE_RET_STR(ENOLINK);
+#endif
+#ifdef EADV
+ RT_CASE_RET_STR(EADV);
+#endif
+#ifdef ESRMNT
+ RT_CASE_RET_STR(ESRMNT);
+#endif
+#ifdef ECOMM
+ RT_CASE_RET_STR(ECOMM);
+#endif
+#ifdef EPROTO
+ RT_CASE_RET_STR(EPROTO);
+#endif
+#ifdef EMULTIHOP
+ RT_CASE_RET_STR(EMULTIHOP);
+#endif
+#ifdef EDOTDOT
+ RT_CASE_RET_STR(EDOTDOT);
+#endif
+#ifdef EBADMSG
+ RT_CASE_RET_STR(EBADMSG);
+#endif
+#ifdef EOVERFLOW
+ RT_CASE_RET_STR(EOVERFLOW); /** @todo fix duplicate error? */
+#endif
+#ifdef ENOTUNIQ
+ RT_CASE_RET_STR(ENOTUNIQ);
+#endif
+#ifdef EBADFD
+ RT_CASE_RET_STR(EBADFD); /** @todo fix duplicate error? */
+#endif
+#ifdef EREMCHG
+ RT_CASE_RET_STR(EREMCHG);
+#endif
+#ifdef ELIBACC
+ RT_CASE_RET_STR(ELIBACC);
+#endif
+#ifdef ELIBBAD
+ RT_CASE_RET_STR(ELIBBAD);
+#endif
+#ifdef ELIBSCN
+ RT_CASE_RET_STR(ELIBSCN);
+#endif
+#ifdef ELIBMAX
+ RT_CASE_RET_STR(ELIBMAX);
+#endif
+#ifdef ELIBEXEC
+ RT_CASE_RET_STR(ELIBEXEC);
+#endif
+#ifdef EILSEQ
+ RT_CASE_RET_STR(EILSEQ);
+#endif
+#ifdef ERESTART
+ RT_CASE_RET_STR(ERESTART);/** @todo fix duplicate error?*/
+#endif
+#ifdef ESTRPIPE
+ RT_CASE_RET_STR(ESTRPIPE);
+#endif
+#ifdef EUSERS
+ RT_CASE_RET_STR(EUSERS);
+#endif
+#ifdef ENOTSOCK
+ RT_CASE_RET_STR(ENOTSOCK);
+#endif
+#ifdef EDESTADDRREQ
+ RT_CASE_RET_STR(EDESTADDRREQ);
+#endif
+#ifdef EMSGSIZE
+ RT_CASE_RET_STR(EMSGSIZE);
+#endif
+#ifdef EPROTOTYPE
+ RT_CASE_RET_STR(EPROTOTYPE);
+#endif
+#ifdef ENOPROTOOPT
+ RT_CASE_RET_STR(ENOPROTOOPT);
+#endif
+#ifdef EPROTONOSUPPORT
+ RT_CASE_RET_STR(EPROTONOSUPPORT);
+#endif
+#ifdef ESOCKTNOSUPPORT
+ RT_CASE_RET_STR(ESOCKTNOSUPPORT);
+#endif
+#ifdef EOPNOTSUPP /** @todo map this differently on solaris. */
+ RT_CASE_RET_STR(EOPNOTSUPP);
+#endif
+#ifdef EPFNOSUPPORT
+ RT_CASE_RET_STR(EPFNOSUPPORT);
+#endif
+#ifdef EAFNOSUPPORT
+ RT_CASE_RET_STR(EAFNOSUPPORT);
+#endif
+#ifdef EADDRINUSE
+ RT_CASE_RET_STR(EADDRINUSE);
+#endif
+#ifdef EADDRNOTAVAIL
+ RT_CASE_RET_STR(EADDRNOTAVAIL);
+#endif
+#ifdef ENETDOWN
+ RT_CASE_RET_STR(ENETDOWN);
+#endif
+#ifdef ENETUNREACH
+ RT_CASE_RET_STR(ENETUNREACH);
+#endif
+#ifdef ENETRESET
+ RT_CASE_RET_STR(ENETRESET);
+#endif
+#ifdef ECONNABORTED
+ RT_CASE_RET_STR(ECONNABORTED);
+#endif
+#ifdef ECONNRESET
+ RT_CASE_RET_STR(ECONNRESET);
+#endif
+#ifdef ENOBUFS
+ RT_CASE_RET_STR(ENOBUFS);
+#endif
+#ifdef EISCONN
+ RT_CASE_RET_STR(EISCONN);
+#endif
+#ifdef ENOTCONN
+ RT_CASE_RET_STR(ENOTCONN);
+#endif
+#ifdef ESHUTDOWN
+ RT_CASE_RET_STR(ESHUTDOWN);
+#endif
+#ifdef ETOOMANYREFS
+ RT_CASE_RET_STR(ETOOMANYREFS);
+#endif
+#ifdef ETIMEDOUT
+ RT_CASE_RET_STR(ETIMEDOUT);
+#endif
+#ifdef ECONNREFUSED
+ RT_CASE_RET_STR(ECONNREFUSED);
+#endif
+#ifdef EHOSTDOWN
+ RT_CASE_RET_STR(EHOSTDOWN);
+#endif
+#ifdef EHOSTUNREACH
+ RT_CASE_RET_STR(EHOSTUNREACH);
+#endif
+#ifdef EALREADY
+# if !defined(ENOLCK) || (EALREADY != ENOLCK)
+ RT_CASE_RET_STR(EALREADY);
+# endif
+#endif
+#ifdef EINPROGRESS
+# if !defined(ENODEV) || (EINPROGRESS != ENODEV)
+ RT_CASE_RET_STR(EINPROGRESS);
+# endif
+#endif
+#ifdef ESTALE
+ RT_CASE_RET_STR(ESTALE); /* 116: Stale NFS file handle */
+#endif
+#ifdef EUCLEAN
+ RT_CASE_RET_STR(EUCLEAN);
+#endif
+#ifdef ENOTNAM
+ RT_CASE_RET_STR(ENOTNAM);
+#endif
+#ifdef ENAVAIL
+ RT_CASE_RET_STR(ENAVAIL);
+#endif
+#ifdef EISNAM
+ RT_CASE_RET_STR(EISNAM);
+#endif
+#ifdef EREMOTEIO
+ RT_CASE_RET_STR(EREMOTEIO);
+#endif
+#ifdef EDQUOT
+ RT_CASE_RET_STR(EDQUOT); /** @todo fix duplicate error */
+#endif
+#ifdef ENOMEDIUM
+ RT_CASE_RET_STR(ENOMEDIUM);
+#endif
+#ifdef EMEDIUMTYPE
+ RT_CASE_RET_STR(EMEDIUMTYPE);
+#endif
+#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
+ RT_CASE_RET_STR(EWOULDBLOCK);
+#endif
+
+ /* Non-linux */
+
+#ifdef EPROCLIM
+ RT_CASE_RET_STR(EPROCLIM);
+#endif
+#ifdef EDOOFUS
+# if EDOOFUS != EINVAL
+ RT_CASE_RET_STR(EDOOFUS);
+# endif
+#endif
+#ifdef ENOTSUP
+# ifndef EOPNOTSUPP
+ RT_CASE_RET_STR(ENOTSUP);
+# else
+# if ENOTSUP != EOPNOTSUPP
+ RT_CASE_RET_STR(ENOTSUP);
+# endif
+# endif
+#endif
+ default:
+ AssertLogRelMsgFailedReturn(("Unhandled error code %d\n", iErrNo), "unknown-errno-value");
+ }
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strerror);
+
diff --git a/src/VBox/Runtime/common/file/nocrt-close.cpp b/src/VBox/Runtime/common/file/nocrt-close.cpp
new file mode 100644
index 00000000..32aa20ba
--- /dev/null
+++ b/src/VBox/Runtime/common/file/nocrt-close.cpp
@@ -0,0 +1,64 @@
+/* $Id: nocrt-close.cpp $ */
+/** @file
+ * IPRT - No-CRT - close().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/unistd.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+
+
+#undef close
+int RT_NOCRT(close)(int fd) RT_NOEXCEPT
+{
+ RTFILE hFile;
+ int rc = RTFileFromNative(&hFile, fd);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileClose(hFile);
+ if (RT_SUCCESS(rc))
+ return 0;
+ }
+ errno = RTErrConvertToErrno(rc);
+ return -1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(close);
+
diff --git a/src/VBox/Runtime/common/file/nocrt-dup.cpp b/src/VBox/Runtime/common/file/nocrt-dup.cpp
new file mode 100644
index 00000000..36df104b
--- /dev/null
+++ b/src/VBox/Runtime/common/file/nocrt-dup.cpp
@@ -0,0 +1,78 @@
+/* $Id: nocrt-dup.cpp $ */
+/** @file
+ * IPRT - No-CRT - dup().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/unistd.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+
+
+#undef dup
+int RT_NOCRT(dup)(int fdSrc)
+{
+ RTFILE hFileSrc;
+ int rc = RTFileFromNative(&hFileSrc, fdSrc);
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE hFileNew;
+#if defined(RT_OS_WINDOWS) /* default on windows is noinherit */
+ rc = RTFileDup(hFileSrc, 0, &hFileNew);
+#else
+ rc = RTFileDup(hFileSrc, RTFILE_O_INHERIT, &hFileNew);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ intptr_t fdNew = RTFileToNative(hFileNew);
+ AssertMsg((int)fdNew == fdNew, ("%#zx\n", fdNew));
+ if ((int)fdNew == fdNew)
+ return (int)fdNew;
+ RTFileClose(hFileNew);
+ errno = EMFILE;
+ return -1;
+ }
+ }
+ errno = RTErrConvertToErrno(rc);
+ return -1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(dup);
+
diff --git a/src/VBox/Runtime/common/file/nocrt-fstat.cpp b/src/VBox/Runtime/common/file/nocrt-fstat.cpp
new file mode 100644
index 00000000..0e4db609
--- /dev/null
+++ b/src/VBox/Runtime/common/file/nocrt-fstat.cpp
@@ -0,0 +1,81 @@
+/* $Id: nocrt-fstat.cpp $ */
+/** @file
+ * IPRT - No-CRT - fstat().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/sys/stat.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+
+
+#undef fstat
+int RT_NOCRT(fstat)(int fd, struct RT_NOCRT(stat) *pStat)
+{
+ RTFILE hFile;
+ int rc = RTFileFromNative(&hFile, fd);
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO ObjInfo;
+ rc = RTFileQueryInfo(hFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(rc))
+ {
+ pStat->st_ino = ObjInfo.Attr.u.Unix.INodeId;
+ pStat->st_dev = ObjInfo.Attr.u.Unix.INodeIdDevice;
+ pStat->st_rdev = ObjInfo.Attr.u.Unix.Device;
+ pStat->st_mode = ObjInfo.Attr.fMode;
+ pStat->st_link = ObjInfo.Attr.u.Unix.cHardlinks;
+ pStat->st_uid = ObjInfo.Attr.u.Unix.uid;
+ pStat->st_gid = ObjInfo.Attr.u.Unix.gid;
+ pStat->st_size = ObjInfo.cbObject;
+ pStat->st_blocks = (ObjInfo.cbObject + 511) / 512;
+ pStat->st_blksize = 16384; /* whatever */
+ pStat->st_birthtime = RTTimeSpecGetSeconds(&ObjInfo.BirthTime);
+ pStat->st_ctime = RTTimeSpecGetSeconds(&ObjInfo.ChangeTime);
+ pStat->st_mtime = RTTimeSpecGetSeconds(&ObjInfo.ModificationTime);
+ pStat->st_atime = RTTimeSpecGetSeconds(&ObjInfo.AccessTime);
+ return 0;
+ }
+ }
+ errno = RTErrConvertToErrno(rc);
+ return -1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fstat);
+
diff --git a/src/VBox/Runtime/common/file/nocrt-isatty.cpp b/src/VBox/Runtime/common/file/nocrt-isatty.cpp
new file mode 100644
index 00000000..68be35bb
--- /dev/null
+++ b/src/VBox/Runtime/common/file/nocrt-isatty.cpp
@@ -0,0 +1,72 @@
+/* $Id: nocrt-isatty.cpp $ */
+/** @file
+ * IPRT - No-CRT - isatty().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/unistd.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/errcore.h>
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#endif
+
+
+#undef isatty
+int RT_NOCRT(isatty)(int fd) RT_NOEXCEPT
+{
+#ifdef RT_OS_WINDOWS
+ HANDLE hNative = (HANDLE)(uintptr_t)(unsigned)fd;
+ DWORD dwType = GetFileType(hNative);
+ if (dwType == FILE_TYPE_CHAR)
+ return 1;
+
+ if (dwType != FILE_TYPE_UNKNOWN)
+ errno = ENOTTY;
+ else
+ errno = RTErrConvertToErrno(RTErrConvertFromWin32(GetLastError()));
+ return 0;
+
+#else
+# error "port me"
+#endif
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(isatty);
+
diff --git a/src/VBox/Runtime/common/file/nocrt-open.cpp b/src/VBox/Runtime/common/file/nocrt-open.cpp
new file mode 100644
index 00000000..bd5000c8
--- /dev/null
+++ b/src/VBox/Runtime/common/file/nocrt-open.cpp
@@ -0,0 +1,104 @@
+/* $Id: nocrt-open.cpp $ */
+/** @file
+ * IPRT - No-CRT - read().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/fcntl.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/assert.h>
+#include <iprt/assertcompile.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+
+
+#undef open
+int RT_NOCRT(open)(const char *pszFilename, uint64_t fFlags, ... /*RTFMODE fMode*/)
+{
+ /*
+ * Make fFlags into proper RTFILE_O_XXX.
+ */
+ /* Make sure we got exactly one RTFILE_O_ACTION_MASK value: */
+ AssertCompile(O_CREAT == RTFILE_O_OPEN_CREATE); AssertCompile(RT_IS_POWER_OF_TWO(O_CREAT));
+ AssertCompile(O_EXCL == RTFILE_O_CREATE); AssertCompile(RT_IS_POWER_OF_TWO(O_EXCL));
+ if (fFlags & O_CREAT)
+ {
+ if (fFlags & O_EXCL)
+ fFlags &= ~(uint64_t)O_CREAT;
+ va_list va;
+ va_start(va, fFlags);
+ int fMode = va_arg(va, int);
+ va_end(va);
+ fFlags &= ~(uint64_t)RTFILE_O_CREATE_MODE_MASK;
+ fFlags |= ((uint64_t)fMode << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK;
+ }
+ else
+ {
+ if (fFlags & O_EXCL)
+ fFlags &= ~(uint64_t)O_EXCL;
+ fFlags |= RTFILE_O_OPEN;
+ }
+
+ /* Close on exec / inherit flag needs inverting: */
+ AssertCompile(O_CLOEXEC == RTFILE_O_INHERIT);
+ fFlags ^= O_CLOEXEC;
+
+ /* Add deny selection: */
+ fFlags |= RTFILE_O_DENY_NONE;
+
+ /*
+ * Try open it.
+ */
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pszFilename, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ intptr_t fd = RTFileToNative(hFile);
+ if ((int)fd == fd)
+ return (int)fd;
+
+ AssertMsgFailed(("fd=%zd (%p)\n", fd, fd));
+ RTFileClose(hFile);
+ rc = VERR_INTERNAL_ERROR;
+ }
+ errno = RTErrConvertToErrno(rc);
+ return -1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(open);
+
diff --git a/src/VBox/Runtime/common/file/nocrt-read.cpp b/src/VBox/Runtime/common/file/nocrt-read.cpp
new file mode 100644
index 00000000..7d781f1b
--- /dev/null
+++ b/src/VBox/Runtime/common/file/nocrt-read.cpp
@@ -0,0 +1,65 @@
+/* $Id: nocrt-read.cpp $ */
+/** @file
+ * IPRT - No-CRT - read().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/unistd.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+
+
+#undef read
+ssize_t RT_NOCRT(read)(int fd, void *pvBuf, size_t cbToRead) RT_NOEXCEPT
+{
+ RTFILE hFile;
+ int rc = RTFileFromNative(&hFile, fd);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbRead = 0;
+ rc = RTFileRead(hFile, pvBuf, cbToRead, &cbRead);
+ if (RT_SUCCESS(rc))
+ return cbRead;
+ }
+ errno = RTErrConvertToErrno(rc);
+ return -1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(read);
+
diff --git a/src/VBox/Runtime/common/fs/Makefile.kup b/src/VBox/Runtime/common/fs/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/Makefile.kup
diff --git a/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp b/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp
new file mode 100644
index 00000000..6373a8e6
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/RTFsCmdLs.cpp
@@ -0,0 +1,1862 @@
+/* $Id: RTFsCmdLs.cpp $ */
+/** @file
+ * IPRT - /bin/ls like utility for testing the VFS code.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/sort.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Display entry.
+ */
+typedef struct RTCMDLSENTRY
+{
+ /** The information about the entry. */
+ RTFSOBJINFO Info;
+ /** Symbolic link target (allocated after the name). */
+ const char *pszTarget;
+ /** Owner if applicable(allocated after the name). */
+ const char *pszOwner;
+ /** Group if applicable (allocated after the name). */
+ const char *pszGroup;
+ /** The length of szName. */
+ size_t cchName;
+ /** The entry name. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} RTCMDLSENTRY;
+/** Pointer to a ls display entry. */
+typedef RTCMDLSENTRY *PRTCMDLSENTRY;
+/** Pointer to a ls display entry pointer. */
+typedef PRTCMDLSENTRY *PPRTCMDLSENTRY;
+
+
+/**
+ * Collection of display entries.
+ */
+typedef struct RTCMDLSCOLLECTION
+{
+ /** Current size of papEntries. */
+ size_t cEntries;
+ /** Memory allocated for papEntries. */
+ size_t cEntriesAllocated;
+ /** Current entries pending sorting and display. */
+ PPRTCMDLSENTRY papEntries;
+
+ /** Total number of bytes allocated for the above entries. */
+ uint64_t cbTotalAllocated;
+ /** Total number of file content bytes. */
+ uint64_t cbTotalFiles;
+
+ /** The collection name (path). */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} RTCMDLSCOLLECTION;
+/** Pointer to a display entry collection. */
+typedef RTCMDLSCOLLECTION *PRTCMDLSCOLLECTION;
+/** Pointer to a display entry collection pointer. */
+typedef PRTCMDLSCOLLECTION *PPRTCMDLSCOLLECTION;
+
+
+/** Sorting. */
+typedef enum RTCMDLSSORT
+{
+ RTCMDLSSORT_INVALID = 0,
+ RTCMDLSSORT_NONE,
+ RTCMDLSSORT_NAME,
+ RTCMDLSSORT_EXTENSION,
+ RTCMDLSSORT_SIZE,
+ RTCMDLSSORT_TIME,
+ RTCMDLSSORT_VERSION
+} RTCMDLSSORT;
+
+/** Time selection. */
+typedef enum RTCMDLSTIME
+{
+ RTCMDLSTIME_INVALID = 0,
+ RTCMDLSTIME_BTIME,
+ RTCMDLSTIME_CTIME,
+ RTCMDLSTIME_MTIME,
+ RTCMDLSTIME_ATIME
+} RTCMDLSTIME;
+
+/** Time display style. */
+typedef enum RTCMDLSTIMESTYLE
+{
+ RTCMDLSTIMESTYLE_INVALID = 0,
+ RTCMDLSTIMESTYLE_FULL_ISO,
+ RTCMDLSTIMESTYLE_LONG_ISO,
+ RTCMDLSTIMESTYLE_ISO,
+ RTCMDLSTIMESTYLE_LOCALE,
+ RTCMDLSTIMESTYLE_CUSTOM
+} RTCMDLSTIMESTYLE;
+
+/** Coloring selection. */
+typedef enum RTCMDLSCOLOR
+{
+ RTCMDLSCOLOR_INVALID = 0,
+ RTCMDLSCOLOR_NONE
+} RTCMDLSCOLOR;
+
+/** Formatting. */
+typedef enum RTCMDLSFORMAT
+{
+ RTCMDLSFORMAT_INVALID = 0,
+ RTCMDLSFORMAT_COLS_VERTICAL, /**< -C/default */
+ RTCMDLSFORMAT_COLS_HORIZONTAL, /**< -x */
+ RTCMDLSFORMAT_COMMAS, /**< -m */
+ RTCMDLSFORMAT_SINGLE, /**< -1 */
+ RTCMDLSFORMAT_LONG, /**< -l */
+ RTCMDLSFORMAT_MACHINE_READABLE /**< --machine-readable */
+} RTCMDLSFORMAT;
+
+
+/**
+ * LS command options and state.
+ */
+typedef struct RTCMDLSOPTS
+{
+ /** @name Traversal.
+ * @{ */
+ bool fFollowSymlinksInDirs; /**< -L */
+ bool fFollowSymlinkToAnyArgs;
+ bool fFollowSymlinkToDirArgs;
+ bool fFollowDirectoryArgs; /**< Inverse -d/--directory. */
+ bool fRecursive; /**< -R */
+ /** @} */
+
+
+ /** @name Filtering.
+ * @{ */
+ bool fShowHidden; /**< -a/--all or -A/--almost-all */
+ bool fShowDotAndDotDot; /**< -a vs -A */
+ bool fShowBackups; /**< Inverse -B/--ignore-backups (*~). */
+ /** @} */
+
+ /** @name Sorting
+ * @{ */
+ RTCMDLSSORT enmSort; /**< --sort */
+ bool fReverseSort; /**< -r */
+ bool fGroupDirectoriesFirst; /**< fGroupDirectoriesFirst */
+ /** @} */
+
+ /** @name Formatting
+ * @{ */
+ RTCMDLSFORMAT enmFormat; /**< --format */
+
+ bool fEscapeNonGraphicChars; /**< -b, --escape */
+ bool fEscapeControlChars;
+ bool fHideControlChars; /**< -q/--hide-control-chars, --show-control-chars */
+
+ bool fHumanReadableSizes; /**< -h */
+ bool fSiUnits; /**< --si */
+ uint32_t cbBlock; /**< --block-size=N, -k */
+
+ bool fShowOwner;
+ bool fShowGroup;
+ bool fNumericalIds; /**< -n */
+ bool fShowINode;
+ bool fShowAllocatedSize; /**< -s */
+ uint8_t cchTab; /**< -T */
+ uint32_t cchWidth; /**< -w */
+
+ RTCMDLSCOLOR enmColor; /**< --color */
+
+ RTCMDLSTIME enmTime; /**< --time */
+ RTCMDLSTIMESTYLE enmTimeStyle; /**< --time-style, --full-time */
+ const char *pszTimeCustom; /**< --time-style=+xxx */
+ /** @} */
+
+ /** @name State
+ * @{ */
+ /** Current size of papCollections. */
+ size_t cCollections;
+ /** Memory allocated for papCollections. */
+ size_t cCollectionsAllocated;
+ /** Current entry collection pending display, the last may also be pending
+ * sorting. */
+ PPRTCMDLSCOLLECTION papCollections;
+ /** @} */
+} RTCMDLSOPTS;
+/** Pointer to ls options and state. */
+typedef RTCMDLSOPTS *PRTCMDLSOPTS;
+
+
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Unsorted} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstUnsorted(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ return !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpName(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ return RTStrCmp(pEntry1->szName, pEntry2->szName);
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstName(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, extension} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpExtension(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTStrCmp(RTPathSuffix(pEntry1->szName), RTPathSuffix(pEntry2->szName));
+ if (!iDiff)
+ iDiff = RTStrCmp(pEntry1->szName, pEntry2->szName);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Ext + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstExtension(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpExtension(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Allocated size + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpAllocated(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ if (pEntry1->Info.cbAllocated == pEntry2->Info.cbAllocated)
+ return rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return pEntry1->Info.cbAllocated < pEntry2->Info.cbAllocated ? -1 : 1;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Allocated size + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstAllocated(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpAllocated(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Content size + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpSize(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ if (pEntry1->Info.cbObject == pEntry2->Info.cbObject)
+ return rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return pEntry1->Info.cbObject < pEntry2->Info.cbObject ? -1 : 1;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Content size + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstSize(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpSize(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Modification time + name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpMTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTTimeSpecCompare(&pEntry1->Info.ModificationTime, &pEntry2->Info.ModificationTime);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Modification time + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstMTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpMTime(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Birth time + name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpBTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTTimeSpecCompare(&pEntry1->Info.BirthTime, &pEntry2->Info.BirthTime);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Birth time + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstBTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpBTime(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Change time + name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpCTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTTimeSpecCompare(&pEntry1->Info.ChangeTime, &pEntry2->Info.ChangeTime);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Change time + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstCTime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpCTime(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Accessed time + name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpATime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = RTTimeSpecCompare(&pEntry1->Info.AccessTime, &pEntry2->Info.AccessTime);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpName(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Accessed time + Name} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstATime(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpATime(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Name as version} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpVersion(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ return RTStrVersionCompare(pEntry1->szName, pEntry2->szName);
+}
+
+
+/** @callback_method_impl{FNRTSORTCMP, Dirs first + Name as version} */
+static DECLCALLBACK(int) rtCmdLsEntryCmpDirFirstVersion(void const *pvElement1, void const *pvElement2, void *pvUser)
+{
+ RT_NOREF(pvUser);
+ PRTCMDLSENTRY pEntry1 = (PRTCMDLSENTRY)pvElement1;
+ PRTCMDLSENTRY pEntry2 = (PRTCMDLSENTRY)pvElement2;
+ int iDiff = !RTFS_IS_DIRECTORY(pEntry1->Info.Attr.fMode) - !RTFS_IS_DIRECTORY(pEntry2->Info.Attr.fMode);
+ if (!iDiff)
+ iDiff = rtCmdLsEntryCmpVersion(pEntry1, pEntry2, pvUser);
+ return iDiff;
+}
+
+
+/**
+ * Sorts the entries in the collections according the sorting options.
+ *
+ * @param pOpts The options and state.
+ */
+static void rtCmdLsSortCollections(PRTCMDLSOPTS pOpts)
+{
+ /*
+ * Sort the entries in each collection.
+ */
+ PFNRTSORTCMP pfnCmp;
+ switch (pOpts->enmSort)
+ {
+ case RTCMDLSSORT_NONE:
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstUnsorted : NULL;
+ break;
+ default: AssertFailed(); RT_FALL_THRU();
+ case RTCMDLSSORT_NAME:
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstName : rtCmdLsEntryCmpName;
+ break;
+ case RTCMDLSSORT_EXTENSION:
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstExtension : rtCmdLsEntryCmpExtension;
+ break;
+ case RTCMDLSSORT_SIZE:
+ if (pOpts->fShowAllocatedSize)
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstAllocated : rtCmdLsEntryCmpAllocated;
+ else
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstSize : rtCmdLsEntryCmpSize;
+ break;
+ case RTCMDLSSORT_TIME:
+ switch (pOpts->enmTime)
+ {
+ default: AssertFailed(); RT_FALL_THRU();
+ case RTCMDLSTIME_MTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstMTime : rtCmdLsEntryCmpMTime; break;
+ case RTCMDLSTIME_BTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstBTime : rtCmdLsEntryCmpBTime; break;
+ case RTCMDLSTIME_CTIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstCTime : rtCmdLsEntryCmpCTime; break;
+ case RTCMDLSTIME_ATIME: pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstATime : rtCmdLsEntryCmpATime; break;
+ }
+ break;
+ case RTCMDLSSORT_VERSION:
+ pfnCmp = pOpts->fGroupDirectoriesFirst ? rtCmdLsEntryCmpDirFirstVersion : rtCmdLsEntryCmpVersion;
+ break;
+ }
+ if (pfnCmp)
+ {
+ /*
+ * Walk thru the collections and sort their entries.
+ */
+ size_t i = pOpts->cCollections;
+ while (i-- > 0)
+ {
+ PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i];
+ RTSortApvShell((void **)pCollection->papEntries, pCollection->cEntries, pfnCmp, NULL);
+
+ if (pOpts->fReverseSort)
+ {
+ PPRTCMDLSENTRY papEntries = pCollection->papEntries;
+ size_t iHead = 0;
+ size_t iTail = pCollection->cEntries;
+ while (iHead < iTail)
+ {
+ PRTCMDLSENTRY pTmp = papEntries[iHead];
+ papEntries[iHead] = papEntries[iTail];
+ papEntries[iTail] = pTmp;
+ iHead++;
+ iTail--;
+ }
+ }
+ }
+ }
+
+ /** @todo sort the collections too, except for the first one. */
+}
+
+
+/**
+ * Format human readable size.
+ */
+static const char *rtCmdLsFormatSizeHumanReadable(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
+{
+ if (pOpts->fHumanReadableSizes)
+ {
+ if (!pOpts->fSiUnits)
+ {
+ size_t cch = RTStrPrintf(pszDst, cbDst, "%Rhub", cb);
+ if (pszDst[cch - 1] == 'i')
+ pszDst[cch - 1] = '\0'; /* drop the trailing 'i' */
+ }
+ else
+ RTStrPrintf(pszDst, cbDst, "%Rhui", cb);
+ }
+ else if (pOpts->cbBlock)
+ RTStrFormatU64(pszDst, cbDst, (cb + pOpts->cbBlock - 1) / pOpts->cbBlock, 10, 0, 0, 0);
+ else
+ RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Format block count.
+ */
+static const char *rtCmdLsFormatBlocks(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
+{
+ if (pOpts->fHumanReadableSizes)
+ return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst);
+
+ uint32_t cbBlock = pOpts->cbBlock;
+ if (cbBlock == 0)
+ cbBlock = _1K;
+ RTStrFormatU64(pszDst, cbDst, (cb + cbBlock / 2 - 1) / cbBlock, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Format file size.
+ */
+static const char *rtCmdLsFormatSize(PRTCMDLSOPTS pOpts, uint64_t cb, char *pszDst, size_t cbDst)
+{
+ if (pOpts->fHumanReadableSizes)
+ return rtCmdLsFormatSizeHumanReadable(pOpts, cb, pszDst, cbDst);
+ if (pOpts->cbBlock > 0)
+ return rtCmdLsFormatBlocks(pOpts, cb, pszDst, cbDst);
+ RTStrFormatU64(pszDst, cbDst, cb, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Format name, i.e. escape, hide, quote stuff.
+ */
+static const char *rtCmdLsFormatName(PRTCMDLSOPTS pOpts, const char *pszName, char *pszDst, size_t cbDst)
+{
+ if ( !pOpts->fEscapeNonGraphicChars
+ && !pOpts->fEscapeControlChars
+ && !pOpts->fHideControlChars)
+ return pszName;
+ /** @todo implement name formatting. */
+ RT_NOREF(pszDst, cbDst);
+ return pszName;
+}
+
+
+/**
+ * Figures out the length for a 32-bit number when formatted as decimal.
+ * @returns Number of digits.
+ * @param uValue The number.
+ */
+DECLINLINE(size_t) rtCmdLsDecimalFormatLengthU32(uint32_t uValue)
+{
+ if (uValue < 10)
+ return 1;
+ if (uValue < 100)
+ return 2;
+ if (uValue < 1000)
+ return 3;
+ if (uValue < 10000)
+ return 4;
+ if (uValue < 100000)
+ return 5;
+ if (uValue < 1000000)
+ return 6;
+ if (uValue < 10000000)
+ return 7;
+ if (uValue < 100000000)
+ return 8;
+ if (uValue < 1000000000)
+ return 9;
+ return 10;
+}
+
+
+/**
+ * Formats the given group ID according to the specified options.
+ *
+ * @returns pszDst
+ * @param pOpts The options and state.
+ * @param gid The GID to format.
+ * @param pszOwner The owner returned by the FS.
+ * @param pszDst The output buffer.
+ * @param cbDst The output buffer size.
+ */
+static const char *rtCmdLsDecimalFormatGroup(PRTCMDLSOPTS pOpts, RTGID gid, const char *pszGroup, char *pszDst, size_t cbDst)
+{
+ if (!pOpts->fNumericalIds)
+ {
+ if (pszGroup)
+ {
+ RTStrCopy(pszDst, cbDst, pszGroup);
+ return pszDst;
+ }
+ if (gid == NIL_RTGID)
+ return "<Nil>";
+ }
+ RTStrFormatU64(pszDst, cbDst, gid, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Formats the given user ID according to the specified options.
+ *
+ * @returns pszDst
+ * @param pOpts The options and state.
+ * @param uid The UID to format.
+ * @param pszOwner The owner returned by the FS.
+ * @param pszDst The output buffer.
+ * @param cbDst The output buffer size.
+ */
+static const char *rtCmdLsDecimalFormatOwner(PRTCMDLSOPTS pOpts, RTUID uid, const char *pszOwner, char *pszDst, size_t cbDst)
+{
+ if (!pOpts->fNumericalIds)
+ {
+ if (pszOwner)
+ {
+ RTStrCopy(pszDst, cbDst, pszOwner);
+ return pszDst;
+ }
+ if (uid == NIL_RTUID)
+ return "<Nil>";
+ }
+ RTStrFormatU64(pszDst, cbDst, uid, 10, 0, 0, 0);
+ return pszDst;
+}
+
+
+/**
+ * Formats the given timestamp according to the desired --time-style.
+ *
+ * @returns pszDst
+ * @param pOpts The options and state.
+ * @param pTimestamp The timestamp.
+ * @param pszDst The output buffer.
+ * @param cbDst The output buffer size.
+ */
+static const char *rtCmdLsFormatTimestamp(PRTCMDLSOPTS pOpts, PCRTTIMESPEC pTimestamp, char *pszDst, size_t cbDst)
+{
+ /** @todo timestamp formatting according to the given style. */
+ RT_NOREF(pOpts);
+ return RTTimeSpecToString(pTimestamp, pszDst, cbDst);
+}
+
+
+
+/**
+ * RTCMDLSFORMAT_MACHINE_READABLE: --machine-readable
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInMachineReadableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp)
+{
+ RT_NOREF(pOpts, pCollection, pszTmp, cbTmp);
+ RTMsgError("Machine readable format not implemented\n");
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * RTCMDLSFORMAT_COMMAS: -m
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInCvsFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp)
+{
+ RT_NOREF(pOpts, pCollection, pszTmp, cbTmp);
+ RTMsgError("Table output formats not implemented\n");
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * RTCMDLSFORMAT_LONG: -l
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInLongFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
+{
+ /*
+ * Figure the width of the size, the link count, the uid, the gid, and the inode columns.
+ */
+ size_t cchSizeCol = 1;
+ size_t cchLinkCol = 1;
+ size_t cchUidCol = pOpts->fShowOwner ? 1 : 0;
+ size_t cchGidCol = pOpts->fShowGroup ? 1 : 0;
+ size_t cchINodeCol = pOpts->fShowINode ? 1 : 0;
+
+ size_t i = pCollection->cEntries;
+ while (i-- > 0)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+
+ rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp);
+ size_t cchTmp = strlen(pszTmp);
+ if (cchTmp > cchSizeCol)
+ cchSizeCol = cchTmp;
+
+ cchTmp = rtCmdLsDecimalFormatLengthU32(pEntry->Info.Attr.u.Unix.cHardlinks) + 1;
+ if (cchTmp > cchLinkCol)
+ cchLinkCol = cchTmp;
+
+ if (pOpts->fShowOwner)
+ {
+ rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp);
+ cchTmp = strlen(pszTmp);
+ if (cchTmp > cchUidCol)
+ cchUidCol = cchTmp;
+ }
+
+ if (pOpts->fShowGroup)
+ {
+ rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp);
+ cchTmp = strlen(pszTmp);
+ if (cchTmp > cchGidCol)
+ cchGidCol = cchTmp;
+ }
+
+ if (pOpts->fShowINode)
+ {
+ cchTmp = RTStrFormatU64(pszTmp, cchTmp, pEntry->Info.Attr.u.Unix.INodeId, 10, 0, 0, 0);
+ if (cchTmp > cchINodeCol)
+ cchINodeCol = cchTmp;
+ }
+ }
+
+ /*
+ * Determin time member offset.
+ */
+ size_t offTime;
+ switch (pOpts->enmTime)
+ {
+ default: AssertFailed(); RT_FALL_THRU();
+ case RTCMDLSTIME_MTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.ModificationTime); break;
+ case RTCMDLSTIME_BTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.BirthTime); break;
+ case RTCMDLSTIME_CTIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.ChangeTime); break;
+ case RTCMDLSTIME_ATIME: offTime = RT_UOFFSETOF(RTCMDLSENTRY, Info.AccessTime); break;
+ }
+
+ /*
+ * Display the entries.
+ */
+ for (i = 0; i < pCollection->cEntries; i++)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+
+ if (cchINodeCol)
+ RTPrintf("%*RU64 ", cchINodeCol, pEntry->Info.Attr.u.Unix.INodeId);
+ if (cchAllocatedCol)
+ RTPrintf("%*s ", cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp));
+
+ RTFMODE fMode = pEntry->Info.Attr.fMode;
+ switch (fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FIFO: RTPrintf("f"); break;
+ case RTFS_TYPE_DEV_CHAR: RTPrintf("c"); break;
+ case RTFS_TYPE_DIRECTORY: RTPrintf("d"); break;
+ case RTFS_TYPE_DEV_BLOCK: RTPrintf("b"); break;
+ case RTFS_TYPE_FILE: RTPrintf("-"); break;
+ case RTFS_TYPE_SYMLINK: RTPrintf("l"); break;
+ case RTFS_TYPE_SOCKET: RTPrintf("s"); break;
+ case RTFS_TYPE_WHITEOUT: RTPrintf("w"); break;
+ default: RTPrintf("?"); AssertFailed(); break;
+ }
+ /** @todo sticy bits++ */
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IRUSR ? 'r' : '-',
+ fMode & RTFS_UNIX_IWUSR ? 'w' : '-',
+ fMode & RTFS_UNIX_IXUSR ? 'x' : '-');
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IRGRP ? 'r' : '-',
+ fMode & RTFS_UNIX_IWGRP ? 'w' : '-',
+ fMode & RTFS_UNIX_IXGRP ? 'x' : '-');
+ RTPrintf("%c%c%c",
+ fMode & RTFS_UNIX_IROTH ? 'r' : '-',
+ fMode & RTFS_UNIX_IWOTH ? 'w' : '-',
+ fMode & RTFS_UNIX_IXOTH ? 'x' : '-');
+ if (1)
+ {
+ RTPrintf(" %c%c%c%c%c%c%c%c%c%c%c%c%c%c",
+ fMode & RTFS_DOS_READONLY ? 'R' : '-',
+ fMode & RTFS_DOS_HIDDEN ? 'H' : '-',
+ fMode & RTFS_DOS_SYSTEM ? 'S' : '-',
+ fMode & RTFS_DOS_DIRECTORY ? 'D' : '-',
+ fMode & RTFS_DOS_ARCHIVED ? 'A' : '-',
+ fMode & RTFS_DOS_NT_DEVICE ? 'd' : '-',
+ fMode & RTFS_DOS_NT_NORMAL ? 'N' : '-',
+ fMode & RTFS_DOS_NT_TEMPORARY ? 'T' : '-',
+ fMode & RTFS_DOS_NT_SPARSE_FILE ? 'P' : '-',
+ fMode & RTFS_DOS_NT_REPARSE_POINT ? 'J' : '-',
+ fMode & RTFS_DOS_NT_COMPRESSED ? 'C' : '-',
+ fMode & RTFS_DOS_NT_OFFLINE ? 'O' : '-',
+ fMode & RTFS_DOS_NT_NOT_CONTENT_INDEXED ? 'I' : '-',
+ fMode & RTFS_DOS_NT_ENCRYPTED ? 'E' : '-');
+ }
+ RTPrintf(" %*u", cchLinkCol, pEntry->Info.Attr.u.Unix.cHardlinks);
+ if (cchUidCol)
+ RTPrintf(" %*s", cchUidCol,
+ rtCmdLsDecimalFormatOwner(pOpts, pEntry->Info.Attr.u.Unix.uid, pEntry->pszOwner, pszTmp, cbTmp));
+ if (cchGidCol)
+ RTPrintf(" %*s", cchGidCol,
+ rtCmdLsDecimalFormatGroup(pOpts, pEntry->Info.Attr.u.Unix.gid, pEntry->pszGroup, pszTmp, cbTmp));
+ RTPrintf(" %*s", cchSizeCol, rtCmdLsFormatSize(pOpts, pEntry->Info.cbObject, pszTmp, cbTmp));
+
+ PCRTTIMESPEC pTime = (PCRTTIMESPEC)((uintptr_t)pEntry + offTime);
+ RTPrintf(" %s", rtCmdLsFormatTimestamp(pOpts, pTime, pszTmp, cbTmp));
+
+ RTPrintf(" %s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp));
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * RTCMDLSFORMAT_SINGLE: -1
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInSingleFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
+{
+ if (cchAllocatedCol > 0)
+ for (size_t i = 0; i < pCollection->cEntries; i++)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+ RTPrintf("%*s %s\n",
+ cchAllocatedCol, rtCmdLsFormatBlocks(pOpts, pEntry->Info.cbAllocated, pszTmp, cbTmp / 4),
+ rtCmdLsFormatName(pOpts, pEntry->szName, &pszTmp[cbTmp / 4], cbTmp / 4 * 3));
+ }
+ else
+ for (size_t i = 0; i < pCollection->cEntries; i++)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+ RTPrintf("%s\n", rtCmdLsFormatName(pOpts, pEntry->szName, pszTmp, cbTmp));
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * RTCMDLSFORMAT_COLS_VERTICAL: default, -C; RTCMDLSFORMAT_COLS_HORIZONTAL: -x
+ */
+static RTEXITCODE rtCmdLsDisplayCollectionInTableFormat(PRTCMDLSOPTS pOpts, PRTCMDLSCOLLECTION pCollection,
+ char *pszTmp, size_t cbTmp, size_t cchAllocatedCol)
+{
+ RT_NOREF(pOpts, pCollection, pszTmp, cbTmp, cchAllocatedCol);
+ RTMsgError("Table output formats not implemented\n");
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Does the actual displaying of the entry collections.
+ *
+ * @returns Program exit code.
+ * @param pOpts The options and state.
+ */
+static RTEXITCODE rtCmdLsDisplayCollections(PRTCMDLSOPTS pOpts)
+{
+ rtCmdLsSortCollections(pOpts);
+
+ bool const fNeedCollectionName = pOpts->cCollections > 2
+ || ( pOpts->cCollections == 2
+ && pOpts->papCollections[0]->cEntries > 0);
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ for (size_t iCollection = 0; iCollection < pOpts->cCollections; iCollection++)
+ {
+ PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[iCollection];
+ char szTmp[RTPATH_MAX*2];
+
+ /* The header. */
+ if (iCollection != 0)
+ {
+ if ( iCollection > 1
+ || pOpts->papCollections[0]->cEntries > 0)
+ RTPrintf("\n");
+ if (fNeedCollectionName)
+ RTPrintf("%s:\n", rtCmdLsFormatName(pOpts, pCollection->szName, szTmp, sizeof(szTmp)));
+ RTPrintf("total %s\n", rtCmdLsFormatBlocks(pOpts, pCollection->cbTotalAllocated, szTmp, sizeof(szTmp)));
+ }
+
+ /* Format the entries. */
+ RTEXITCODE rcExit2;
+ if (pOpts->enmFormat == RTCMDLSFORMAT_MACHINE_READABLE)
+ rcExit2 = rtCmdLsDisplayCollectionInMachineReadableFormat(pOpts, pCollection, szTmp, sizeof(szTmp));
+ else if (pOpts->enmFormat == RTCMDLSFORMAT_COMMAS)
+ rcExit2 = rtCmdLsDisplayCollectionInCvsFormat(pOpts, pCollection, szTmp, sizeof(szTmp));
+ else
+ {
+ /* If the allocated size is requested, calculate the column width. */
+ size_t cchAllocatedCol = 0;
+ if (pOpts->fShowAllocatedSize)
+ {
+ size_t i = pCollection->cEntries;
+ while (i-- > 0)
+ {
+ rtCmdLsFormatBlocks(pOpts, pCollection->papEntries[i]->Info.cbAllocated, szTmp, sizeof(szTmp));
+ size_t cchTmp = strlen(szTmp);
+ if (cchTmp > cchAllocatedCol)
+ cchAllocatedCol = cchTmp;
+ }
+ }
+
+ /* Do the individual formatting. */
+ if (pOpts->enmFormat == RTCMDLSFORMAT_LONG)
+ rcExit2 = rtCmdLsDisplayCollectionInLongFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
+ else if (pOpts->enmFormat == RTCMDLSFORMAT_SINGLE)
+ rcExit2 = rtCmdLsDisplayCollectionInSingleFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
+ else
+ rcExit2 = rtCmdLsDisplayCollectionInTableFormat(pOpts, pCollection, szTmp, sizeof(szTmp), cchAllocatedCol);
+ }
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ }
+ return rcExit;
+}
+
+
+/**
+ * Frees all collections and their entries.
+ * @param pOpts The options and state.
+ */
+static void rtCmdLsFreeCollections(PRTCMDLSOPTS pOpts)
+{
+ size_t i = pOpts->cCollections;
+ while (i-- > 0)
+ {
+ PRTCMDLSCOLLECTION pCollection = pOpts->papCollections[i];
+ PPRTCMDLSENTRY papEntries = pCollection->papEntries;
+ size_t j = pCollection->cEntries;
+ while (j-- > 0)
+ {
+ RTMemFree(papEntries[j]);
+ papEntries[j] = NULL;
+ }
+ RTMemFree(papEntries);
+ pCollection->papEntries = NULL;
+ pCollection->cEntries = 0;
+ pCollection->cEntriesAllocated = 0;
+ RTMemFree(pCollection);
+ pOpts->papCollections[i] = NULL;
+ }
+
+ RTMemFree(pOpts->papCollections);
+ pOpts->papCollections = NULL;
+ pOpts->cCollections = 0;
+ pOpts->cCollectionsAllocated = 0;
+}
+
+
+/**
+ * Allocates a new collection.
+ *
+ * @returns Pointer to the collection.
+ * @param pOpts The options and state.
+ * @param pszName The collection name. Empty for special first
+ * collection.
+ */
+static PRTCMDLSCOLLECTION rtCmdLsNewCollection(PRTCMDLSOPTS pOpts, const char *pszName)
+{
+ /* Grow the pointer table? */
+ if (pOpts->cCollections >= pOpts->cCollectionsAllocated)
+ {
+ size_t cNew = pOpts->cCollectionsAllocated ? pOpts->cCollectionsAllocated * 2 : 16;
+ void *pvNew = RTMemRealloc(pOpts->papCollections, cNew * sizeof(pOpts->papCollections[0]));
+ if (!pvNew)
+ {
+ RTMsgError("Out of memory! (resize collections)");
+ return NULL;
+ }
+ pOpts->cCollectionsAllocated = cNew;
+ pOpts->papCollections = (PPRTCMDLSCOLLECTION)pvNew;
+
+ /* If this is the first time and pszName isn't empty, add the zero'th
+ entry for the command line stuff (hardcoded first collection). */
+ if ( pOpts->cCollections == 0
+ && *pszName)
+ {
+ PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_UOFFSETOF(RTCMDLSCOLLECTION, szName[1]));
+ if (!pCollection)
+ {
+ RTMsgError("Out of memory! (collection)");
+ return NULL;
+ }
+ pOpts->papCollections[0] = pCollection;
+ pOpts->cCollections = 1;
+ }
+ }
+
+ /* Add new collection. */
+ size_t cbName = strlen(pszName) + 1;
+ PRTCMDLSCOLLECTION pCollection = (PRTCMDLSCOLLECTION)RTMemAllocZ(RT_UOFFSETOF_DYN(RTCMDLSCOLLECTION, szName[cbName]));
+ if (pCollection)
+ {
+ memcpy(pCollection->szName, pszName, cbName);
+ pOpts->papCollections[pOpts->cCollections++] = pCollection;
+ }
+ else
+ RTMsgError("Out of memory! (collection)");
+ return pCollection;
+}
+
+
+/**
+ * Adds one entry to a collection.
+ * @returns Program exit code
+ * @param pCollection The collection.
+ * @param pszEntry The entry name.
+ * @param pInfo The entry info.
+ * @param pszOwner The owner name if available, otherwise NULL.
+ * @param pszGroup The group anme if available, otherwise NULL.
+ * @param pszTarget The symbolic link target if applicable and
+ * available, otherwise NULL.
+ */
+static RTEXITCODE rtCmdLsAddOne(PRTCMDLSCOLLECTION pCollection, const char *pszEntry, PRTFSOBJINFO pInfo,
+ const char *pszOwner, const char *pszGroup, const char *pszTarget)
+{
+
+ /* Make sure there is space in the collection for the new entry. */
+ if (pCollection->cEntries >= pCollection->cEntriesAllocated)
+ {
+ size_t cNew = pCollection->cEntriesAllocated ? pCollection->cEntriesAllocated * 2 : 16;
+ void *pvNew = RTMemRealloc(pCollection->papEntries, cNew * sizeof(pCollection->papEntries[0]));
+ if (!pvNew)
+ return RTMsgErrorExitFailure("Out of memory! (resize entries)");
+ pCollection->papEntries = (PPRTCMDLSENTRY)pvNew;
+ pCollection->cEntriesAllocated = cNew;
+ }
+
+ /* Create and insert a new entry. */
+ size_t const cchEntry = strlen(pszEntry);
+ size_t const cbOwner = pszOwner ? strlen(pszOwner) + 1 : 0;
+ size_t const cbGroup = pszGroup ? strlen(pszGroup) + 1 : 0;
+ size_t const cbTarget = pszTarget ? strlen(pszTarget) + 1 : 0;
+ size_t const cbEntry = RT_UOFFSETOF_DYN(RTCMDLSENTRY, szName[cchEntry + 1 + cbOwner + cbGroup + cbTarget]);
+ PRTCMDLSENTRY pEntry = (PRTCMDLSENTRY)RTMemAlloc(cbEntry);
+ if (pEntry)
+ {
+ pEntry->Info = *pInfo;
+ pEntry->pszTarget = NULL; /** @todo symbolic links. */
+ pEntry->pszOwner = NULL;
+ pEntry->pszGroup = NULL;
+ pEntry->cchName = cchEntry;
+ memcpy(pEntry->szName, pszEntry, cchEntry);
+ pEntry->szName[cchEntry] = '\0';
+
+ char *psz = &pEntry->szName[cchEntry + 1];
+ if (pszTarget)
+ {
+ pEntry->pszTarget = psz;
+ memcpy(psz, pszTarget, cbTarget);
+ psz += cbTarget;
+ }
+ if (pszOwner)
+ {
+ pEntry->pszOwner = psz;
+ memcpy(psz, pszOwner, cbOwner);
+ psz += cbOwner;
+ }
+ if (pszGroup)
+ {
+ pEntry->pszGroup = psz;
+ memcpy(psz, pszGroup, cbGroup);
+ }
+
+ pCollection->papEntries[pCollection->cEntries++] = pEntry;
+ pCollection->cbTotalAllocated += pEntry->Info.cbAllocated;
+ pCollection->cbTotalFiles += pEntry->Info.cbObject;
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTMsgErrorExitFailure("Out of memory! (entry)");
+}
+
+
+/**
+ * Checks if the entry is to be filtered out.
+ *
+ * @returns true if filtered out, false if included.
+ * @param pOpts The options and state.
+ * @param pszEntry The entry name.
+ * @param pInfo The entry info.
+ */
+static bool rtCmdLsIsFilteredOut(PRTCMDLSOPTS pOpts, const char *pszEntry, PCRTFSOBJINFO pInfo)
+{
+ /*
+ * Should we filter out this entry?
+ */
+ if ( !pOpts->fShowHidden
+ && (pInfo->Attr.fMode & RTFS_DOS_HIDDEN))
+ return true;
+
+ size_t const cchEntry = strlen(pszEntry);
+ if ( !pOpts->fShowDotAndDotDot
+ && cchEntry <= 2
+ && pszEntry[0] == '.'
+ && ( cchEntry == 1
+ || pszEntry[1] == '.' ))
+ return true;
+
+ if ( !pOpts->fShowBackups
+ && pszEntry[cchEntry - 1] == '~')
+ return true;
+ return false;
+}
+
+
+/**
+ * Processes a directory, recursing into subdirectories if desired.
+ *
+ * @returns Program exit code.
+ * @param pOpts The options.
+ * @param hVfsDir The directory.
+ * @param pszPath Path buffer, RTPATH_MAX in size.
+ * @param cchPath The length of the current path.
+ * @param pInfo The parent information.
+ */
+static RTEXITCODE rtCmdLsProcessDirectory(PRTCMDLSOPTS pOpts, RTVFSDIR hVfsDir, char *pszPath, size_t cchPath, PCRTFSOBJINFO pInfo)
+{
+ /*
+ * Create a new collection for this directory.
+ */
+ RT_NOREF(pInfo);
+ PRTCMDLSCOLLECTION pCollection = rtCmdLsNewCollection(pOpts, pszPath);
+ if (!pCollection)
+ return RTEXITCODE_FAILURE;
+
+ /*
+ * Process the directory entries.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
+ PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (!pDirEntry)
+ return RTMsgErrorExitFailure("Out of memory! (direntry buffer)");
+
+ for (;;)
+ {
+ /*
+ * Read the next entry.
+ */
+ size_t cbDirEntry = cbDirEntryAlloced;
+ int rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemTmpFree(pDirEntry);
+ cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
+ pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ continue;
+ rcExit = RTMsgErrorExitFailure("Out of memory (direntry buffer)");
+ }
+ else if (rc != VERR_NO_MORE_FILES)
+ rcExit = RTMsgErrorExitFailure("RTVfsDirReadEx failed: %Rrc\n", rc);
+ break;
+ }
+
+ /*
+ * Process the entry.
+ */
+ if (rtCmdLsIsFilteredOut(pOpts, pDirEntry->szName, &pDirEntry->Info))
+ continue;
+
+
+ const char *pszOwner = NULL;
+ RTFSOBJINFO OwnerInfo;
+ if (pDirEntry->Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner)
+ {
+ rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0])
+ pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0];
+ }
+
+ const char *pszGroup = NULL;
+ RTFSOBJINFO GroupInfo;
+ if (pDirEntry->Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup)
+ {
+ rc = RTVfsDirQueryPathInfo(hVfsDir, pDirEntry->szName, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0])
+ pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0];
+ }
+
+ RTEXITCODE rcExit2 = rtCmdLsAddOne(pCollection, pDirEntry->szName, &pDirEntry->Info, pszOwner, pszGroup, NULL);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ }
+
+ RTMemTmpFree(pDirEntry);
+
+ /*
+ * Recurse into subdirectories if requested.
+ */
+ if (pOpts->fRecursive)
+ {
+ for (uint32_t i = 0; i < pCollection->cEntries; i++)
+ {
+ PRTCMDLSENTRY pEntry = pCollection->papEntries[i];
+ if (RTFS_IS_SYMLINK(pEntry->Info.Attr.fMode))
+ {
+ if (!pOpts->fFollowSymlinksInDirs)
+ continue;
+ /** @todo implement following symbolic links in the tree. */
+ continue;
+ }
+ else if ( !RTFS_IS_DIRECTORY(pEntry->Info.Attr.fMode)
+ || ( pEntry->szName[0] == '.'
+ && ( pEntry->szName[1] == '\0'
+ || ( pEntry->szName[1] == '.'
+ && pEntry->szName[2] == '\0'))) )
+ continue;
+
+ /* Open subdirectory and process it. */
+ RTVFSDIR hSubDir;
+ int rc = RTVfsDirOpenDir(hVfsDir, pEntry->szName, 0 /*fFlags*/, &hSubDir);
+ if (RT_SUCCESS(rc))
+ {
+ if (cchPath + 1 + pEntry->cchName + 1 < RTPATH_MAX)
+ {
+ pszPath[cchPath] = RTPATH_SLASH;
+ memcpy(&pszPath[cchPath + 1], pEntry->szName, pEntry->cchName + 1);
+ RTEXITCODE rcExit2 = rtCmdLsProcessDirectory(pOpts, hSubDir, pszPath,
+ cchPath + 1 + pEntry->cchName, &pEntry->Info);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ pszPath[cchPath] = '\0';
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Too deep recursion: %s%c%s", pszPath, RTPATH_SLASH, pEntry->szName);
+ RTVfsDirRelease(hSubDir);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("RTVfsDirOpenDir failed on %s in %s: %Rrc\n", pEntry->szName, pszPath, rc);
+ }
+ }
+ return rcExit;
+}
+
+
+/**
+ * Processes one argument.
+ *
+ * @returns Program exit code.
+ * @param pOpts The options.
+ * @param pszArg The argument.
+ */
+static RTEXITCODE rtCmdLsProcessArgument(PRTCMDLSOPTS pOpts, const char *pszArg)
+{
+ /*
+ * Query info about the object 'pszArg' indicates.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTFSOBJINFO Info;
+ uint32_t fPath = pOpts->fFollowSymlinkToAnyArgs ? RTPATH_F_FOLLOW_LINK : RTPATH_F_ON_LINK;
+ int rc = RTVfsChainQueryInfo(pszArg, &Info, RTFSOBJATTRADD_UNIX, fPath, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainQueryInfo", pszArg, rc, offError, &ErrInfo.Core);
+
+ /* Symbolic links requires special handling of course. */
+ if (RTFS_IS_SYMLINK(Info.Attr.fMode))
+ {
+ if (pOpts->fFollowSymlinkToDirArgs)
+ {
+ RTFSOBJINFO Info2;
+ rc = RTVfsChainQueryInfo(pszArg, &Info2, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc) && !RTFS_IS_DIRECTORY(Info.Attr.fMode))
+ Info = Info2;
+ }
+ }
+
+ /*
+ * If it's not a directory or we've been told to process directories
+ * without going into them, just add it to the default collection.
+ */
+ if ( !pOpts->fFollowDirectoryArgs
+ || !RTFS_IS_DIRECTORY(Info.Attr.fMode))
+ {
+ if ( pOpts->cCollections > 0
+ || rtCmdLsNewCollection(pOpts, "") != NULL)
+ {
+ const char *pszOwner = NULL;
+ RTFSOBJINFO OwnerInfo;
+ if (Info.Attr.u.Unix.uid != NIL_RTUID && pOpts->fShowOwner)
+ {
+ rc = RTVfsChainQueryInfo(pszArg, &OwnerInfo, RTFSOBJATTRADD_UNIX_OWNER, fPath, NULL, NULL);
+ if (RT_SUCCESS(rc) && OwnerInfo.Attr.u.UnixOwner.szName[0])
+ pszOwner = &OwnerInfo.Attr.u.UnixOwner.szName[0];
+ }
+
+ const char *pszGroup = NULL;
+ RTFSOBJINFO GroupInfo;
+ if (Info.Attr.u.Unix.gid != NIL_RTGID && pOpts->fShowGroup)
+ {
+ rc = RTVfsChainQueryInfo(pszArg, &GroupInfo, RTFSOBJATTRADD_UNIX_GROUP, fPath, NULL, NULL);
+ if (RT_SUCCESS(rc) && GroupInfo.Attr.u.UnixGroup.szName[0])
+ pszGroup = &GroupInfo.Attr.u.UnixGroup.szName[0];
+ }
+
+ return rtCmdLsAddOne(pOpts->papCollections[0], pszArg, &Info, pszOwner, pszGroup, NULL);
+ }
+ return RTEXITCODE_FAILURE;
+ }
+
+ /*
+ * Open the directory.
+ */
+ RTVFSDIR hVfsDir;
+ rc = RTVfsChainOpenDir(pszArg, 0 /*fFlags*/, &hVfsDir, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenDir", pszArg, rc, offError, &ErrInfo.Core);
+
+ RTEXITCODE rcExit;
+ char szPath[RTPATH_MAX];
+ size_t cchPath = strlen(pszArg);
+ if (cchPath < sizeof(szPath))
+ {
+ memcpy(szPath, pszArg, cchPath + 1);
+ rcExit = rtCmdLsProcessDirectory(pOpts, hVfsDir, szPath, cchPath, &Info);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("Too long argument: %s", pszArg);
+ RTVfsDirRelease(hVfsDir);
+ return rcExit;
+}
+
+
+/**
+ * A /bin/ls clone.
+ *
+ * @returns Program exit code.
+ *
+ * @param cArgs The number of arguments.
+ * @param papszArgs The argument vector. (Note that this may be
+ * reordered, so the memory must be writable.)
+ */
+RTR3DECL(RTEXITCODE) RTFsCmdLs(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+#define OPT_AUTHOR 1000
+#define OPT_BLOCK_SIZE 1001
+#define OPT_COLOR 1002
+#define OPT_FILE_TYPE 1003
+#define OPT_FORMAT 1004
+#define OPT_FULL_TIME 1005
+#define OPT_GROUP_DIRECTORIES_FIRST 1006
+#define OPT_SI 1007
+#define OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR 1008
+#define OPT_HIDE 1009
+#define OPT_INDICATOR_STYLE 1010
+#define OPT_MACHINE_READABLE 1011
+#define OPT_SHOW_CONTROL_CHARS 1012
+#define OPT_QUOTING_STYLE 1013
+#define OPT_SORT 1014
+#define OPT_TIME 1015
+#define OPT_TIME_STYLE 1016
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--all", 'a', RTGETOPT_REQ_NOTHING },
+ { "--almost-all", 'A', RTGETOPT_REQ_NOTHING },
+ //{ "--author", OPT_AUTHOR, RTGETOPT_REQ_NOTHING },
+ { "--escape", 'b', RTGETOPT_REQ_NOTHING },
+ { "--block-size", OPT_BLOCK_SIZE, RTGETOPT_REQ_UINT32 },
+ { "--ctime", 'c', RTGETOPT_REQ_NOTHING },
+ //{ "--columns", 'C', RTGETOPT_REQ_NOTHING },
+ //{ "--color", OPT_COLOR, RTGETOPT_OPT_STRING },
+ { "--directory", 'd', RTGETOPT_REQ_NOTHING },
+ //{ "--dired", 'D', RTGETOPT_REQ_NOTHING },
+ { "--dash-f", 'f', RTGETOPT_REQ_NOTHING },
+ //{ "--classify", 'F', RTGETOPT_REQ_NOTHING },
+ //{ "--file-type", OPT_FILE_TYPE, RTGETOPT_REQ_NOTHING },
+ { "--format", OPT_FORMAT, RTGETOPT_REQ_STRING },
+ { "--full-time", OPT_FULL_TIME, RTGETOPT_REQ_NOTHING },
+ { "--dash-g", 'g', RTGETOPT_REQ_NOTHING },
+ { "--group-directories-first", OPT_GROUP_DIRECTORIES_FIRST, RTGETOPT_REQ_NOTHING },
+ { "--no-group", 'G', RTGETOPT_REQ_NOTHING },
+ { "--human-readable", 'h', RTGETOPT_REQ_NOTHING },
+ { "--si", OPT_SI, RTGETOPT_REQ_NOTHING },
+ { "--dereference-command-line", 'H', RTGETOPT_REQ_NOTHING },
+ { "--dereference-command-line-symlink-to-dir", OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR, RTGETOPT_REQ_NOTHING },
+ //{ "--hide" OPT_HIDE, RTGETOPT_REQ_STRING },
+ //{ "--indicator-style" OPT_INDICATOR_STYLE, RTGETOPT_REQ_STRING },
+ { "--inode", 'i', RTGETOPT_REQ_NOTHING },
+ { "--block-size-1kib", 'k', RTGETOPT_REQ_NOTHING },
+ { "--long", 'l', RTGETOPT_REQ_NOTHING },
+ { "--dereference", 'L', RTGETOPT_REQ_NOTHING },
+ { "--format-commas", 'm', RTGETOPT_REQ_NOTHING },
+ { "--machinereadable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--machine-readable", OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--numeric-uid-gid", 'n', RTGETOPT_REQ_NOTHING },
+ { "--literal", 'N', RTGETOPT_REQ_NOTHING },
+ { "--long-without-group-info", 'o', RTGETOPT_REQ_NOTHING },
+ //{ "--indicator-style", 'p', RTGETOPT_REQ_STRING },
+ { "--hide-control-chars", 'q', RTGETOPT_REQ_NOTHING },
+ { "--show-control-chars", OPT_SHOW_CONTROL_CHARS, RTGETOPT_REQ_NOTHING },
+ //{ "--quote-name", 'Q', RTGETOPT_REQ_NOTHING },
+ //{ "--quoting-style", OPT_QUOTING_STYLE, RTGETOPT_REQ_STRING },
+ { "--reverse", 'r', RTGETOPT_REQ_NOTHING },
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--size", 's', RTGETOPT_REQ_NOTHING },
+ { "--sort-by-size", 'S', RTGETOPT_REQ_NOTHING },
+ { "--sort", OPT_SORT, RTGETOPT_REQ_STRING },
+ { "--time", OPT_TIME, RTGETOPT_REQ_STRING },
+ { "--time-style", OPT_TIME_STYLE, RTGETOPT_REQ_STRING },
+ { "--sort-by-time", 't', RTGETOPT_REQ_NOTHING },
+ { "--tabsize", 'T', RTGETOPT_REQ_UINT8 },
+ { "--atime", 'u', RTGETOPT_REQ_NOTHING },
+ { "--unsorted", 'U', RTGETOPT_REQ_NOTHING },
+ { "--version-sort", 'v', RTGETOPT_REQ_NOTHING },
+ { "--width", 'w', RTGETOPT_REQ_UINT32 },
+ { "--list-by-line", 'x', RTGETOPT_REQ_NOTHING },
+ { "--sort-by-extension", 'X', RTGETOPT_REQ_NOTHING },
+ { "--one-file-per-line", '1', RTGETOPT_REQ_NOTHING },
+ { "--help", '?', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTCMDLSOPTS Opts;
+ Opts.fFollowSymlinksInDirs = false;
+ Opts.fFollowSymlinkToAnyArgs = false;
+ Opts.fFollowSymlinkToDirArgs = false;
+ Opts.fFollowDirectoryArgs = true;
+ Opts.fRecursive = false;
+ Opts.fShowHidden = false;
+ Opts.fShowDotAndDotDot = false;
+ Opts.fShowBackups = true;
+ Opts.enmSort = RTCMDLSSORT_NAME;
+ Opts.fReverseSort = false;
+ Opts.fGroupDirectoriesFirst = false;
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
+ Opts.fEscapeNonGraphicChars = false;
+ Opts.fEscapeControlChars = true;
+ Opts.fHideControlChars = false;
+ Opts.fHumanReadableSizes = false; /**< -h */
+ Opts.fSiUnits = false;
+ Opts.cbBlock = 0;
+ Opts.fShowOwner = true;
+ Opts.fShowGroup = true;
+ Opts.fNumericalIds = false;
+ Opts.fShowINode = false;
+ Opts.fShowAllocatedSize = false;
+ Opts.cchTab = 8;
+ Opts.cchWidth = 80;
+ Opts.enmColor = RTCMDLSCOLOR_NONE;
+ Opts.enmTime = RTCMDLSTIME_MTIME;
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE;
+ Opts.pszTimeCustom = NULL;
+
+ Opts.cCollections = 0;
+ Opts.cCollectionsAllocated = 0;
+ Opts.papCollections = NULL;
+
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned cProcessed = 0;
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+
+ for (;;)
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ /* When reaching the end of arguments without having processed any
+ files/dirs/whatever yet, we do the current directory. */
+ if (cProcessed > 0)
+ {
+ RTEXITCODE rcExit2 = rtCmdLsDisplayCollections(&Opts);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ rtCmdLsFreeCollections(&Opts);
+ return rcExit;
+ }
+ ValueUnion.psz = ".";
+ RT_FALL_THRU();
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ RTEXITCODE rcExit2 = rtCmdLsProcessArgument(&Opts, ValueUnion.psz);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ cProcessed++;
+ break;
+ }
+
+ case 'a':
+ Opts.fShowHidden = true;
+ Opts.fShowDotAndDotDot = true;
+ break;
+
+ case 'A':
+ Opts.fShowHidden = true;
+ Opts.fShowDotAndDotDot = false;
+ break;
+
+ case 'b':
+ Opts.fEscapeNonGraphicChars = true;
+ break;
+
+ case OPT_BLOCK_SIZE:
+ if (!ValueUnion.u32)
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Invalid block size: %u", ValueUnion.u32);
+ }
+ Opts.cbBlock = ValueUnion.u32;
+ Opts.fHumanReadableSizes = false;
+ Opts.fSiUnits = false;
+ break;
+
+ case 'c':
+ Opts.enmTime = RTCMDLSTIME_CTIME;
+ break;
+
+ case 'C':
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
+ break;
+
+ case 'd':
+ Opts.fFollowDirectoryArgs = false;
+ Opts.fFollowSymlinkToAnyArgs = false;
+ Opts.fFollowSymlinkToDirArgs = false;
+ Opts.fRecursive = false;
+ break;
+
+ case 'f':
+ Opts.fShowHidden = true;
+ Opts.fShowDotAndDotDot = true;
+ if (Opts.enmFormat == RTCMDLSFORMAT_LONG)
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
+ Opts.enmColor = RTCMDLSCOLOR_NONE;
+ Opts.enmSort = RTCMDLSSORT_NONE;
+ break;
+
+ case OPT_FORMAT:
+ if ( strcmp(ValueUnion.psz, "across") == 0
+ || strcmp(ValueUnion.psz, "horizontal") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL;
+ else if (strcmp(ValueUnion.psz, "commas") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_COMMAS;
+ else if ( strcmp(ValueUnion.psz, "long") == 0
+ || strcmp(ValueUnion.psz, "verbose") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ else if (strcmp(ValueUnion.psz, "single-column") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_SINGLE;
+ else if (strcmp(ValueUnion.psz, "vertical") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_VERTICAL;
+ else if (strcmp(ValueUnion.psz, "machine-readable") == 0)
+ Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE;
+ else
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown format: %s", ValueUnion.psz);
+ }
+ break;
+
+ case OPT_FULL_TIME:
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO;
+ break;
+
+ case 'g':
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ Opts.fShowOwner = false;
+ break;
+
+ case OPT_GROUP_DIRECTORIES_FIRST:
+ Opts.fGroupDirectoriesFirst = true;
+ break;
+
+ case 'G':
+ Opts.fShowGroup = false;
+ break;
+
+ case 'h':
+ Opts.fHumanReadableSizes = true;
+ Opts.fSiUnits = false;
+ break;
+
+ case OPT_SI:
+ Opts.fHumanReadableSizes = true;
+ Opts.fSiUnits = true;
+ break;
+
+ case 'H':
+ Opts.fFollowSymlinkToAnyArgs = true;
+ Opts.fFollowSymlinkToDirArgs = true;
+ break;
+
+ case OPT_DEREFERENCE_COMMAND_LINE_SYMLINK_TO_DIR:
+ Opts.fFollowSymlinkToAnyArgs = false;
+ Opts.fFollowSymlinkToDirArgs = true;
+ break;
+
+ case 'i':
+ Opts.fShowINode = true;
+ break;
+
+ case 'k':
+ Opts.cbBlock = _1K;
+ Opts.fHumanReadableSizes = false;
+ Opts.fSiUnits = false;
+ break;
+
+ case 'l':
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ break;
+
+ case 'L':
+ Opts.fFollowSymlinksInDirs = true;
+ Opts.fFollowSymlinkToAnyArgs = true;
+ Opts.fFollowSymlinkToDirArgs = true;
+ break;
+
+ case 'm':
+ Opts.enmFormat = RTCMDLSFORMAT_COMMAS;
+ break;
+
+ case OPT_MACHINE_READABLE:
+ Opts.enmFormat = RTCMDLSFORMAT_MACHINE_READABLE;
+ break;
+
+ case 'n':
+ Opts.fNumericalIds = true;
+ break;
+
+ case 'N':
+ Opts.fEscapeNonGraphicChars = false;
+ Opts.fEscapeControlChars = false;
+ Opts.fHideControlChars = false;
+ break;
+
+ case 'o':
+ Opts.enmFormat = RTCMDLSFORMAT_LONG;
+ Opts.fShowGroup = false;
+ break;
+
+ case 'q':
+ Opts.fHideControlChars = true;
+ break;
+
+ case OPT_SHOW_CONTROL_CHARS:
+ Opts.fHideControlChars = true;
+ break;
+
+ case 'r':
+ Opts.fReverseSort = true;
+ break;
+
+ case 'R':
+ Opts.fRecursive = true;
+ break;
+
+ case 's':
+ Opts.fShowAllocatedSize = true;
+ break;
+
+ case 'S':
+ Opts.enmSort = RTCMDLSSORT_SIZE;
+ break;
+
+ case OPT_SORT:
+ if (strcmp(ValueUnion.psz, "none") == 0)
+ Opts.enmSort = RTCMDLSSORT_NONE;
+ else if (strcmp(ValueUnion.psz, "extension") == 0)
+ Opts.enmSort = RTCMDLSSORT_EXTENSION;
+ else if (strcmp(ValueUnion.psz, "size") == 0)
+ Opts.enmSort = RTCMDLSSORT_SIZE;
+ else if (strcmp(ValueUnion.psz, "time") == 0)
+ Opts.enmSort = RTCMDLSSORT_TIME;
+ else if (strcmp(ValueUnion.psz, "version") == 0)
+ Opts.enmSort = RTCMDLSSORT_VERSION;
+ else
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz);
+ }
+ break;
+
+ case OPT_TIME:
+ if ( strcmp(ValueUnion.psz, "btime") == 0
+ || strcmp(ValueUnion.psz, "birth") == 0)
+ Opts.enmTime = RTCMDLSTIME_BTIME;
+ else if ( strcmp(ValueUnion.psz, "ctime") == 0
+ || strcmp(ValueUnion.psz, "status") == 0)
+ Opts.enmTime = RTCMDLSTIME_CTIME;
+ else if ( strcmp(ValueUnion.psz, "mtime") == 0
+ || strcmp(ValueUnion.psz, "write") == 0
+ || strcmp(ValueUnion.psz, "modify") == 0)
+ Opts.enmTime = RTCMDLSTIME_MTIME;
+ else if ( strcmp(ValueUnion.psz, "atime") == 0
+ || strcmp(ValueUnion.psz, "access") == 0
+ || strcmp(ValueUnion.psz, "use") == 0)
+ Opts.enmTime = RTCMDLSTIME_ATIME;
+ else
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown time attribute: %s", ValueUnion.psz);
+ }
+ break;
+
+ case OPT_TIME_STYLE:
+ if (strcmp(ValueUnion.psz, "full-iso") == 0)
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_FULL_ISO;
+ else if (strcmp(ValueUnion.psz, "long-iso") == 0)
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LONG_ISO;
+ else if (strcmp(ValueUnion.psz, "iso") == 0)
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_ISO;
+ else if (strcmp(ValueUnion.psz, "locale") == 0)
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_LOCALE;
+ else if (*ValueUnion.psz == '+')
+ {
+ Opts.enmTimeStyle = RTCMDLSTIMESTYLE_CUSTOM;
+ Opts.pszTimeCustom = ValueUnion.psz;
+ }
+ else
+ {
+ Assert(!Opts.papCollections);
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown sort by: %s", ValueUnion.psz);
+ }
+ break;
+
+ case 't':
+ Opts.enmSort = RTCMDLSSORT_TIME;
+ break;
+
+ case 'T':
+ Opts.cchTab = ValueUnion.u8;
+ break;
+
+ case 'u':
+ Opts.enmTime = RTCMDLSTIME_ATIME;
+ break;
+
+ case 'U':
+ Opts.enmSort = RTCMDLSSORT_NONE;
+ break;
+
+ case 'v':
+ Opts.enmSort = RTCMDLSSORT_VERSION;
+ break;
+
+ case 'w':
+ Opts.cchWidth = ValueUnion.u32;
+ break;
+
+ case 'x':
+ Opts.enmFormat = RTCMDLSFORMAT_COLS_HORIZONTAL;
+ break;
+
+ case 'X':
+ Opts.enmSort = RTCMDLSSORT_EXTENSION;
+ break;
+
+ case '1':
+ Opts.enmFormat = RTCMDLSFORMAT_SINGLE;
+ break;
+
+ case '?':
+ {
+ RTPrintf("Usage: to be written\n"
+ "Options dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ if (s_aOptions[i].iShort < 127 && s_aOptions[i].iShort >= 0x20)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ else
+ RTPrintf(" %s\n", s_aOptions[i].pszLong);
+#ifdef RT_OS_WINDOWS
+ const char *pszProgNm = RTPathFilename(papszArgs[0]);
+ RTPrintf("\n"
+ "The path prefix '\\\\:iprtnt:\\' can be used to access the NT namespace.\n"
+ "To list devices: %s -la \\\\:iprtnt:\\Device\n"
+ "To list win32 devices: %s -la \\\\:iprtnt:\\GLOBAL??\n"
+ "To list the root (hack/bug): %s -la \\\\:iprtnt:\\\n",
+ pszProgNm, pszProgNm, pszProgNm);
+#endif
+ Assert(!Opts.papCollections);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ Assert(!Opts.papCollections);
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ Assert(!Opts.papCollections);
+ return RTGetOptPrintError(chOpt, &ValueUnion);
+ }
+ }
+}
+
diff --git a/src/VBox/Runtime/common/fs/extvfs.cpp b/src/VBox/Runtime/common/fs/extvfs.cpp
new file mode 100644
index 00000000..9f2d2b01
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/extvfs.cpp
@@ -0,0 +1,2860 @@
+/* $Id: extvfs.cpp $ */
+/** @file
+ * IPRT - Ext2/3/4 Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include <iprt/fsvfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/formats/ext.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The maximum block group cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSEXT_MAX_BLOCK_GROUP_CACHE_SIZE _512K
+#else
+# define RTFSEXT_MAX_BLOCK_GROUP_CACHE_SIZE _128K
+#endif
+/** The maximum inode cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSEXT_MAX_INODE_CACHE_SIZE _512K
+#else
+# define RTFSEXT_MAX_INODE_CACHE_SIZE _128K
+#endif
+/** The maximum extent/block map cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSEXT_MAX_BLOCK_CACHE_SIZE _512K
+#else
+# define RTFSEXT_MAX_BLOCK_CACHE_SIZE _128K
+#endif
+
+/** All supported incompatible features. */
+#define RTFSEXT_INCOMPAT_FEATURES_SUPP ( EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE | EXT_SB_FEAT_INCOMPAT_EXTENTS | EXT_SB_FEAT_INCOMPAT_64BIT \
+ | EXT_SB_FEAT_INCOMPAT_FLEX_BG)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the ext filesystem data. */
+typedef struct RTFSEXTVOL *PRTFSEXTVOL;
+
+
+/**
+ * Cached block group descriptor data.
+ */
+typedef struct RTFSEXTBLKGRP
+{
+ /** AVL tree node, indexed by the block group number. */
+ AVLU32NODECORE Core;
+ /** List node for the LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** Block number where the inode table is store. */
+ uint64_t iBlockInodeTbl;
+ /** Pointer to the inode bitmap. */
+ uint8_t *pabInodeBitmap;
+ /** Block bitmap - variable in size (depends on the block size
+ * and number of blocks per group). */
+ uint8_t abBlockBitmap[1];
+} RTFSEXTBLKGRP;
+/** Pointer to block group descriptor data. */
+typedef RTFSEXTBLKGRP *PRTFSEXTBLKGRP;
+
+
+/**
+ * In-memory inode.
+ */
+typedef struct RTFSEXTINODE
+{
+ /** AVL tree node, indexed by the inode number. */
+ AVLU32NODECORE Core;
+ /** List node for the inode LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** Byte offset in the backing file where the inode is stored.. */
+ uint64_t offInode;
+ /** Inode data. */
+ RTFSOBJINFO ObjInfo;
+ /** Inode flags (copied from the on disk inode). */
+ uint32_t fFlags;
+ /** Copy of the block map/extent tree. */
+ uint32_t aiBlocks[EXT_INODE_BLOCK_ENTRIES];
+} RTFSEXTINODE;
+/** Pointer to an in-memory inode. */
+typedef RTFSEXTINODE *PRTFSEXTINODE;
+
+
+/**
+ * Block cache entry.
+ */
+typedef struct RTFSEXTBLOCKENTRY
+{
+ /** AVL tree node, indexed by the filesystem block number. */
+ AVLU64NODECORE Core;
+ /** List node for the inode LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** The block data. */
+ uint8_t abData[1];
+} RTFSEXTBLOCKENTRY;
+/** Pointer to a block cache entry. */
+typedef RTFSEXTBLOCKENTRY *PRTFSEXTBLOCKENTRY;
+
+
+/**
+ * Open directory instance.
+ */
+typedef struct RTFSEXTDIR
+{
+ /** Volume this directory belongs to. */
+ PRTFSEXTVOL pVol;
+ /** The underlying inode structure. */
+ PRTFSEXTINODE pInode;
+ /** Set if we've reached the end of the directory enumeration. */
+ bool fNoMoreFiles;
+ /** Current offset into the directory where the next entry should be read. */
+ uint64_t offEntry;
+ /** Next entry index (for logging purposes). */
+ uint32_t idxEntry;
+} RTFSEXTDIR;
+/** Pointer to an open directory instance. */
+typedef RTFSEXTDIR *PRTFSEXTDIR;
+
+
+/**
+ * Open file instance.
+ */
+typedef struct RTFSEXTFILE
+{
+ /** Volume this directory belongs to. */
+ PRTFSEXTVOL pVol;
+ /** The underlying inode structure. */
+ PRTFSEXTINODE pInode;
+ /** Current offset into the file for I/O. */
+ RTFOFF offFile;
+} RTFSEXTFILE;
+/** Pointer to an open file instance. */
+typedef RTFSEXTFILE *PRTFSEXTFILE;
+
+
+/**
+ * Ext2/3/4 filesystem volume.
+ */
+typedef struct RTFSEXTVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the ext volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+
+ /** RTVFSMNT_F_XXX. */
+ uint32_t fMntFlags;
+ /** RTFSEXTVFS_F_XXX (currently none defined). */
+ uint32_t fExtFlags;
+
+ /** Flag whether the filesystem is 64bit. */
+ bool f64Bit;
+ /** Size of one block. */
+ size_t cbBlock;
+ /** Number of bits to shift left for fast conversion of block numbers to offsets. */
+ uint32_t cBlockShift;
+ /** Number of blocks in one group. */
+ uint32_t cBlocksPerGroup;
+ /** Number of inodes in each block group. */
+ uint32_t cInodesPerGroup;
+ /** Number of blocks groups in the volume. */
+ uint32_t cBlockGroups;
+ /** Size of the block bitmap. */
+ size_t cbBlockBitmap;
+ /** Size of the inode bitmap. */
+ size_t cbInodeBitmap;
+ /** Size of block group descriptor. */
+ size_t cbBlkGrpDesc;
+ /** Size of an inode. */
+ size_t cbInode;
+
+ /** Incompatible features selected for this filesystem. */
+ uint32_t fFeaturesIncompat;
+
+ /** @name Block group cache.
+ * @{ */
+ /** LRU list anchor. */
+ RTLISTANCHOR LstBlockGroupLru;
+ /** Root of the cached block group tree. */
+ AVLU32TREE BlockGroupRoot;
+ /** Size of the cached block groups. */
+ size_t cbBlockGroups;
+ /** @} */
+
+ /** @name Inode cache.
+ * @{ */
+ /** LRU list anchor for the inode cache. */
+ RTLISTANCHOR LstInodeLru;
+ /** Root of the cached inode tree. */
+ AVLU32TREE InodeRoot;
+ /** Size of the cached inodes. */
+ size_t cbInodes;
+ /** @} */
+
+ /** @name Block cache.
+ * @{ */
+ /** LRU list anchor for the block cache. */
+ RTLISTANCHOR LstBlockLru;
+ /** Root of the cached block tree. */
+ AVLU64TREE BlockRoot;
+ /** Size of cached blocks. */
+ size_t cbBlocks;
+ /** @} */
+} RTFSEXTVOL;
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtFsExtVol_OpenDirByInode(PRTFSEXTVOL pThis, uint32_t iInode, PRTVFSDIR phVfsDir);
+
+#ifdef LOG_ENABLED
+
+/**
+ * Logs the ext filesystem superblock.
+ *
+ * @param pSb Pointer to the superblock.
+ */
+static void rtFsExtSb_Log(PCEXTSUPERBLOCK pSb)
+{
+ if (LogIs2Enabled())
+ {
+ RTTIMESPEC Spec;
+ char sz[80];
+
+ Log2(("EXT: Superblock:\n"));
+ Log2(("EXT: cInodesTotal %RU32\n", RT_LE2H_U32(pSb->cInodesTotal)));
+ Log2(("EXT: cBlocksTotalLow %RU32\n", RT_LE2H_U32(pSb->cBlocksTotalLow)));
+ Log2(("EXT: cBlocksRsvdForSuperUserLow %RU32\n", RT_LE2H_U32(pSb->cBlocksRsvdForSuperUserLow)));
+ Log2(("EXT: cBlocksFreeLow %RU32\n", RT_LE2H_U32(pSb->cBlocksFreeLow)));
+ Log2(("EXT: cInodesFree %RU32\n", RT_LE2H_U32(pSb->cInodesFree)));
+ Log2(("EXT: iBlockOfSuperblock %RU32\n", RT_LE2H_U32(pSb->iBlockOfSuperblock)));
+ Log2(("EXT: cLogBlockSize %RU32\n", RT_LE2H_U32(pSb->cLogBlockSize)));
+ Log2(("EXT: cLogClusterSize %RU32\n", RT_LE2H_U32(pSb->cLogClusterSize)));
+ Log2(("EXT: cBlocksPerGroup %RU32\n", RT_LE2H_U32(pSb->cBlocksPerGroup)));
+ Log2(("EXT: cClustersPerBlockGroup %RU32\n", RT_LE2H_U32(pSb->cClustersPerBlockGroup)));
+ Log2(("EXT: cInodesPerBlockGroup %RU32\n", RT_LE2H_U32(pSb->cInodesPerBlockGroup)));
+ Log2(("EXT: u32LastMountTime %#RX32 %s\n", RT_LE2H_U32(pSb->u32LastMountTime),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pSb->u32LastMountTime)), sz, sizeof(sz))));
+ Log2(("EXT: u32LastWrittenTime %#RX32 %s\n", RT_LE2H_U32(pSb->u32LastWrittenTime),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pSb->u32LastWrittenTime)), sz, sizeof(sz))));
+ Log2(("EXT: cMountsSinceLastCheck %RU16\n", RT_LE2H_U32(pSb->cMountsSinceLastCheck)));
+ Log2(("EXT: cMaxMountsUntilCheck %RU16\n", RT_LE2H_U32(pSb->cMaxMountsUntilCheck)));
+ Log2(("EXT: u16Signature %#RX16\n", RT_LE2H_U32(pSb->u16Signature)));
+ Log2(("EXT: u16FilesystemState %#RX16\n", RT_LE2H_U32(pSb->u16FilesystemState)));
+ Log2(("EXT: u16ActionOnError %#RX16\n", RT_LE2H_U32(pSb->u16ActionOnError)));
+ Log2(("EXT: u16RevLvlMinor %#RX16\n", RT_LE2H_U32(pSb->u16RevLvlMinor)));
+ Log2(("EXT: u32LastCheckTime %#RX32 %s\n", RT_LE2H_U32(pSb->u32LastCheckTime),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pSb->u32LastCheckTime)), sz, sizeof(sz))));
+ Log2(("EXT: u32CheckInterval %RU32\n", RT_LE2H_U32(pSb->u32CheckInterval)));
+ Log2(("EXT: u32OsIdCreator %#RX32\n", RT_LE2H_U32(pSb->u32OsIdCreator)));
+ Log2(("EXT: u32RevLvl %#RX32\n", RT_LE2H_U32(pSb->u32RevLvl)));
+ Log2(("EXT: u16UidReservedBlocks %#RX16\n", RT_LE2H_U32(pSb->u16UidReservedBlocks)));
+ Log2(("EXT: u16GidReservedBlocks %#RX16\n", RT_LE2H_U32(pSb->u16GidReservedBlocks)));
+ if (RT_LE2H_U32(pSb->u32RevLvl) == EXT_SB_REV_V2_DYN_INODE_SZ)
+ {
+ Log2(("EXT: iFirstInodeNonRsvd %#RX32\n", RT_LE2H_U32(pSb->iFirstInodeNonRsvd)));
+ Log2(("EXT: cbInode %#RX16\n", RT_LE2H_U32(pSb->cbInode)));
+ Log2(("EXT: iBlkGrpSb %#RX16\n", RT_LE2H_U32(pSb->iBlkGrpSb)));
+ Log2(("EXT: fFeaturesCompat %#RX32%s%s%s%s%s%s%s%s%s%s\n", RT_LE2H_U32(pSb->fFeaturesCompat),
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_DIR_PREALLOC ? " dir-prealloc" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_IMAGIC_INODES ? " imagic-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_HAS_JOURNAL ? " has-journal" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_EXT_ATTR ? " ext-attrs" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_RESIZE_INODE ? " resize-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_DIR_INDEX ? " dir-index" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_LAZY_BG ? " lazy-bg" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_EXCLUDE_INODE ? " excl-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_EXCLUDE_BITMAP ? " excl-bitmap" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompat) & EXT_SB_FEAT_COMPAT_SPARSE_SUPER2 ? " sparse-super2" : ""));
+ Log2(("EXT: fFeaturesIncompat %#RX32%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", RT_LE2H_U32(pSb->fFeaturesIncompat),
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_COMPRESSION ? " compression" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE ? " dir-filetype" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_RECOVER ? " recovery" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_JOURNAL_DEV ? " journal-dev" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_META_BG ? " meta-bg" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_EXTENTS ? " extents" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_64BIT ? " 64bit" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_MMP ? " mmp" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_FLEX_BG ? " flex-bg" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_EXT_ATTR_INODE ? " extattr-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_DIRDATA ? " dir-data" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_CSUM_SEED ? " csum-seed" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_LARGE_DIR ? " large-dir" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_INLINE_DATA ? " inline-data" : "",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & EXT_SB_FEAT_INCOMPAT_ENCRYPT ? " encrypt" : ""));
+ Log2(("EXT: fFeaturesCompatRo %#RX32%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", RT_LE2H_U32(pSb->fFeaturesCompatRo),
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_SPARSE_SUPER ? " sparse-super" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_LARGE_FILE ? " large-file" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_BTREE_DIR ? " btree-dir" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_HUGE_FILE ? " huge-file" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_GDT_CHSKUM ? " gdt-chksum" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_DIR_NLINK ? " dir-nlink" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_EXTRA_INODE_SZ ? " extra-inode" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_HAS_SNAPSHOTS ? " snapshots" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_QUOTA ? " quota" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_BIGALLOC ? " big-alloc" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_METADATA_CHKSUM ? " meta-chksum" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_REPLICA ? " replica" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_READONLY ? " ro" : "",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo) & EXT_SB_FEAT_COMPAT_RO_PROJECT ? " project" : ""));
+ Log2(("EXT: au8Uuid <todo>\n"));
+ Log2(("EXT: achVolumeName %16s\n", &pSb->achVolumeName[0]));
+ Log2(("EXT: achLastMounted %64s\n", &pSb->achLastMounted[0]));
+ Log2(("EXT: u32AlgoUsageBitmap %#RX32\n", RT_LE2H_U32(pSb->u32AlgoUsageBitmap)));
+ Log2(("EXT: cBlocksPrealloc %RU8\n", pSb->cBlocksPrealloc));
+ Log2(("EXT: cBlocksPreallocDirectory %RU8\n", pSb->cBlocksPreallocDirectory));
+ Log2(("EXT: cGdtEntriesRsvd %RU16\n", pSb->cGdtEntriesRsvd));
+ Log2(("EXT: au8JournalUuid <todo>\n"));
+ Log2(("EXT: iJournalInode %#RX32\n", RT_LE2H_U32(pSb->iJournalInode)));
+ Log2(("EXT: u32JournalDev %#RX32\n", RT_LE2H_U32(pSb->u32JournalDev)));
+ Log2(("EXT: u32LastOrphan %#RX32\n", RT_LE2H_U32(pSb->u32LastOrphan)));
+ Log2(("EXT: au32HashSeedHtree[0] %#RX32\n", RT_LE2H_U32(pSb->au32HashSeedHtree[0])));
+ Log2(("EXT: au32HashSeedHtree[1] %#RX32\n", RT_LE2H_U32(pSb->au32HashSeedHtree[1])));
+ Log2(("EXT: au32HashSeedHtree[2] %#RX32\n", RT_LE2H_U32(pSb->au32HashSeedHtree[2])));
+ Log2(("EXT: au32HashSeedHtree[3] %#RX32\n", RT_LE2H_U32(pSb->au32HashSeedHtree[3])));
+ Log2(("EXT: u8HashVersionDef %#RX8\n", pSb->u8HashVersionDef));
+ Log2(("EXT: u8JnlBackupType %#RX8\n", pSb->u8JnlBackupType));
+ Log2(("EXT: cbGroupDesc %RU16\n", RT_LE2H_U16(pSb->cbGroupDesc)));
+ Log2(("EXT: fMntOptsDef %#RX32\n", RT_LE2H_U32(pSb->fMntOptsDef)));
+ Log2(("EXT: iFirstMetaBg %#RX32\n", RT_LE2H_U32(pSb->iFirstMetaBg)));
+ Log2(("EXT: u32TimeFsCreation %#RX32 %s\n", RT_LE2H_U32(pSb->u32TimeFsCreation),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pSb->u32TimeFsCreation)), sz, sizeof(sz))));
+ for (unsigned i = 0; i < RT_ELEMENTS(pSb->au32JnlBlocks); i++)
+ Log2(("EXT: au32JnlBlocks[%u] %#RX32\n", i, RT_LE2H_U32(pSb->au32JnlBlocks[i])));
+ Log2(("EXT: cBlocksTotalHigh %#RX32\n", RT_LE2H_U32(pSb->cBlocksTotalHigh)));
+ Log2(("EXT: cBlocksRsvdForSuperUserHigh %#RX32\n", RT_LE2H_U32(pSb->cBlocksRsvdForSuperUserHigh)));
+ Log2(("EXT: cBlocksFreeHigh %#RX32\n", RT_LE2H_U32(pSb->cBlocksFreeHigh)));
+ Log2(("EXT: cbInodesExtraMin %#RX16\n", RT_LE2H_U16(pSb->cbInodesExtraMin)));
+ Log2(("EXT: cbNewInodesRsv %#RX16\n", RT_LE2H_U16(pSb->cbInodesExtraMin)));
+ Log2(("EXT: fFlags %#RX32\n", RT_LE2H_U32(pSb->fFlags)));
+ Log2(("EXT: cRaidStride %RU16\n", RT_LE2H_U16(pSb->cRaidStride)));
+ Log2(("EXT: cSecMmpInterval %RU16\n", RT_LE2H_U16(pSb->cSecMmpInterval)));
+ Log2(("EXT: iMmpBlock %#RX64\n", RT_LE2H_U64(pSb->iMmpBlock)));
+ Log2(("EXT: cRaidStrideWidth %#RX32\n", RT_LE2H_U32(pSb->cRaidStrideWidth)));
+ Log2(("EXT: cLogGroupsPerFlex %RU8\n", pSb->cLogGroupsPerFlex));
+ Log2(("EXT: u8ChksumType %RX8\n", pSb->u8ChksumType));
+ Log2(("EXT: cKbWritten %#RX64\n", RT_LE2H_U64(pSb->cKbWritten)));
+ Log2(("EXT: iSnapshotInode %#RX32\n", RT_LE2H_U32(pSb->iSnapshotInode)));
+ Log2(("EXT: iSnapshotId %#RX32\n", RT_LE2H_U32(pSb->iSnapshotId)));
+ Log2(("EXT: cSnapshotRsvdBlocks %#RX64\n", RT_LE2H_U64(pSb->cSnapshotRsvdBlocks)));
+ Log2(("EXT: iSnapshotListInode %#RX32\n", RT_LE2H_U32(pSb->iSnapshotListInode)));
+ Log2(("EXT: cErrorsSeen %#RX32\n", RT_LE2H_U32(pSb->cErrorsSeen)));
+ Log2(("EXT: [...]\n")); /** @todo Missing fields if becoming interesting. */
+ Log2(("EXT: iInodeLostFound %#RX32\n", RT_LE2H_U32(pSb->iInodeLostFound)));
+ Log2(("EXT: iInodeProjQuota %#RX32\n", RT_LE2H_U32(pSb->iInodeProjQuota)));
+ Log2(("EXT: u32ChksumSeed %#RX32\n", RT_LE2H_U32(pSb->u32ChksumSeed)));
+ Log2(("EXT: [...]\n")); /** @todo Missing fields if becoming interesting. */
+ Log2(("EXT: u32Chksum %#RX32\n", RT_LE2H_U32(pSb->u32Chksum)));
+ }
+ }
+}
+
+
+/**
+ * Logs a ext filesystem block group descriptor.
+ *
+ * @param pThis The ext volume instance.
+ * @param iBlockGroup Block group number.
+ * @param pBlockGroup Pointer to the block group.
+ */
+static void rtFsExtBlockGroup_Log(PRTFSEXTVOL pThis, uint32_t iBlockGroup, PCEXTBLOCKGROUPDESC pBlockGroup)
+{
+ if (LogIs2Enabled())
+ {
+ uint64_t iBlockStart = (uint64_t)iBlockGroup * pThis->cBlocksPerGroup;
+ Log2(("EXT: Block group %#RX32 (blocks %#RX64 to %#RX64):\n",
+ iBlockGroup, iBlockStart, iBlockStart + pThis->cBlocksPerGroup - 1));
+ Log2(("EXT: offBlockBitmapLow %#RX32\n", RT_LE2H_U32(pBlockGroup->v32.offBlockBitmapLow)));
+ Log2(("EXT: offInodeBitmapLow %#RX32\n", RT_LE2H_U32(pBlockGroup->v32.offInodeBitmapLow)));
+ Log2(("EXT: offInodeTableLow %#RX32\n", RT_LE2H_U32(pBlockGroup->v32.offInodeTableLow)));
+ Log2(("EXT: cBlocksFreeLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.cBlocksFreeLow)));
+ Log2(("EXT: cInodesFreeLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.cInodesFreeLow)));
+ Log2(("EXT: cDirectoriesLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.cDirectoriesLow)));
+ Log2(("EXT: fFlags %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.fFlags)));
+ Log2(("EXT: offSnapshotExclBitmapLow %#RX32\n", RT_LE2H_U32(pBlockGroup->v32.offSnapshotExclBitmapLow)));
+ Log2(("EXT: u16ChksumBlockBitmapLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.u16ChksumBlockBitmapLow)));
+ Log2(("EXT: u16ChksumInodeBitmapLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.u16ChksumInodeBitmapLow)));
+ Log2(("EXT: cInodeTblUnusedLow %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.cInodeTblUnusedLow)));
+ Log2(("EXT: u16Chksum %#RX16\n", RT_LE2H_U16(pBlockGroup->v32.u16Chksum)));
+ if (pThis->cbBlkGrpDesc == sizeof(EXTBLOCKGROUPDESC64))
+ {
+ Log2(("EXT: offBlockBitmapHigh %#RX32\n", RT_LE2H_U32(pBlockGroup->v64.offBlockBitmapHigh)));
+ Log2(("EXT: offInodeBitmapHigh %#RX32\n", RT_LE2H_U32(pBlockGroup->v64.offInodeBitmapHigh)));
+ Log2(("EXT: offInodeTableHigh %#RX32\n", RT_LE2H_U32(pBlockGroup->v64.offInodeTableHigh)));
+ Log2(("EXT: cBlocksFreeHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.cBlocksFreeHigh)));
+ Log2(("EXT: cInodesFreeHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.cInodesFreeHigh)));
+ Log2(("EXT: cDirectoriesHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.cDirectoriesHigh)));
+ Log2(("EXT: cInodeTblUnusedHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.cInodeTblUnusedHigh)));
+ Log2(("EXT: offSnapshotExclBitmapHigh %#RX32\n", RT_LE2H_U32(pBlockGroup->v64.offSnapshotExclBitmapHigh)));
+ Log2(("EXT: u16ChksumBlockBitmapHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.u16ChksumBlockBitmapHigh)));
+ Log2(("EXT: u16ChksumInodeBitmapHigh %#RX16\n", RT_LE2H_U16(pBlockGroup->v64.u16ChksumInodeBitmapHigh)));
+ }
+ }
+}
+
+
+/**
+ * Logs a ext filesystem inode.
+ *
+ * @param pThis The ext volume instance.
+ * @param iInode Inode number.
+ * @param pInode Pointer to the inode.
+ */
+static void rtFsExtInode_Log(PRTFSEXTVOL pThis, uint32_t iInode, PCEXTINODECOMB pInode)
+{
+ if (LogIs2Enabled())
+ {
+ RTTIMESPEC Spec;
+ char sz[80];
+
+ Log2(("EXT: Inode %#RX32:\n", iInode));
+ Log2(("EXT: fMode %#RX16\n", RT_LE2H_U16(pInode->Core.fMode)));
+ Log2(("EXT: uUidLow %#RX16\n", RT_LE2H_U16(pInode->Core.uUidLow)));
+ Log2(("EXT: cbSizeLow %#RX32\n", RT_LE2H_U32(pInode->Core.cbSizeLow)));
+ Log2(("EXT: u32TimeLastAccess %#RX32 %s\n", RT_LE2H_U32(pInode->Core.u32TimeLastAccess),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Core.u32TimeLastAccess)), sz, sizeof(sz))));
+ Log2(("EXT: u32TimeLastChange %#RX32 %s\n", RT_LE2H_U32(pInode->Core.u32TimeLastChange),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Core.u32TimeLastChange)), sz, sizeof(sz))));
+ Log2(("EXT: u32TimeLastModification %#RX32 %s\n", RT_LE2H_U32(pInode->Core.u32TimeLastModification),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Core.u32TimeLastModification)), sz, sizeof(sz))));
+ Log2(("EXT: u32TimeDeletion %#RX32 %s\n", RT_LE2H_U32(pInode->Core.u32TimeDeletion),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Core.u32TimeDeletion)), sz, sizeof(sz))));
+ Log2(("EXT: uGidLow %#RX16\n", RT_LE2H_U16(pInode->Core.uGidLow)));
+ Log2(("EXT: cHardLinks %#RU16\n", RT_LE2H_U16(pInode->Core.cHardLinks)));
+ Log2(("EXT: cBlocksLow %#RX32\n", RT_LE2H_U32(pInode->Core.cBlocksLow)));
+ Log2(("EXT: fFlags %#RX32\n", RT_LE2H_U32(pInode->Core.fFlags)));
+ Log2(("EXT: Osd1.u32LnxVersion %#RX32\n", RT_LE2H_U32(pInode->Core.Osd1.u32LnxVersion)));
+ for (unsigned i = 0; i < RT_ELEMENTS(pInode->Core.au32Block); i++)
+ Log2(("EXT: au32Block[%u] %#RX32\n", i, RT_LE2H_U32(pInode->Core.au32Block[i])));
+ Log2(("EXT: u32Version %#RX32\n", RT_LE2H_U32(pInode->Core.u32Version)));
+ Log2(("EXT: offExtAttrLow %#RX32\n", RT_LE2H_U32(pInode->Core.offExtAttrLow)));
+ Log2(("EXT: cbSizeHigh %#RX32\n", RT_LE2H_U32(pInode->Core.cbSizeHigh)));
+ Log2(("EXT: u32FragmentAddrObs %#RX32\n", RT_LE2H_U32(pInode->Core.u32FragmentAddrObs)));
+ Log2(("EXT: Osd2.Lnx.cBlocksHigh %#RX32\n", RT_LE2H_U32(pInode->Core.Osd2.Lnx.cBlocksHigh)));
+ Log2(("EXT: Osd2.Lnx.offExtAttrHigh %#RX32\n", RT_LE2H_U32(pInode->Core.Osd2.Lnx.offExtAttrHigh)));
+ Log2(("EXT: Osd2.Lnx.uUidHigh %#RX16\n", RT_LE2H_U16(pInode->Core.Osd2.Lnx.uUidHigh)));
+ Log2(("EXT: Osd2.Lnx.uGidHigh %#RX16\n", RT_LE2H_U16(pInode->Core.Osd2.Lnx.uGidHigh)));
+ Log2(("EXT: Osd2.Lnx.u16ChksumLow %#RX16\n", RT_LE2H_U16(pInode->Core.Osd2.Lnx.u16ChksumLow)));
+
+ if (pThis->cbInode >= sizeof(EXTINODECOMB))
+ {
+ Log2(("EXT: cbInodeExtra %#RU16\n", RT_LE2H_U16(pInode->Extra.cbInodeExtra)));
+ Log2(("EXT: u16ChksumHigh %#RX16\n", RT_LE2H_U16(pInode->Extra.u16ChksumHigh)));
+ Log2(("EXT: u32ExtraTimeLastChange %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ExtraTimeLastChange)));
+ Log2(("EXT: u32ExtraTimeLastModification %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ExtraTimeLastModification)));
+ Log2(("EXT: u32ExtraTimeLastAccess %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ExtraTimeLastAccess)));
+ Log2(("EXT: u32TimeCreation %#RX32 %s\n", RT_LE2H_U32(pInode->Extra.u32TimeCreation),
+ RTTimeSpecToString(RTTimeSpecSetSeconds(&Spec, RT_LE2H_U32(pInode->Extra.u32TimeCreation)), sz, sizeof(sz))));
+ Log2(("EXT: u32ExtraTimeCreation %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ExtraTimeCreation)));
+ Log2(("EXT: u32VersionHigh %#RX32\n", RT_LE2H_U16(pInode->Extra.u32VersionHigh)));
+ Log2(("EXT: u32ProjectId %#RX32\n", RT_LE2H_U16(pInode->Extra.u32ProjectId)));
+ }
+ }
+}
+
+
+/**
+ * Logs a ext filesystem directory entry.
+ *
+ * @param pThis The ext volume instance.
+ * @param idxDirEntry Directory entry index number.
+ * @param pDirEntry The directory entry.
+ */
+static void rtFsExtDirEntry_Log(PRTFSEXTVOL pThis, uint32_t idxDirEntry, PCEXTDIRENTRYEX pDirEntry)
+{
+ if (LogIs2Enabled())
+ {
+ int cbName = 0;
+
+ Log2(("EXT: Directory entry %#RX32:\n", idxDirEntry));
+ Log2(("EXT: iInodeRef %#RX32\n", RT_LE2H_U32(pDirEntry->Core.iInodeRef)));
+ Log2(("EXT: cbRecord %#RX32\n", RT_LE2H_U32(pDirEntry->Core.cbRecord)));
+ if (pThis->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE)
+ {
+ Log2(("EXT: cbName %#RU8\n", pDirEntry->Core.u.v2.cbName));
+ Log2(("EXT: uType %#RX8\n", pDirEntry->Core.u.v2.uType));
+ cbName = pDirEntry->Core.u.v2.cbName;
+ }
+ else
+ {
+ Log2(("EXT: cbName %#RU16\n", RT_LE2H_U16(pDirEntry->Core.u.v1.cbName)));
+ cbName = RT_LE2H_U16(pDirEntry->Core.u.v1.cbName);
+ }
+ Log2(("EXT: achName %*s\n", cbName, &pDirEntry->Core.achName[0]));
+ }
+}
+
+
+/**
+ * Logs an extent header.
+ *
+ * @param pExtentHdr The extent header node.
+ */
+static void rtFsExtExtentHdr_Log(PCEXTEXTENTHDR pExtentHdr)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("EXT: Extent header:\n"));
+ Log2(("EXT: u16Magic %#RX16\n", RT_LE2H_U32(pExtentHdr->u16Magic)));
+ Log2(("EXT: cEntries %#RX16\n", RT_LE2H_U32(pExtentHdr->cEntries)));
+ Log2(("EXT: cMax %#RX16\n", RT_LE2H_U32(pExtentHdr->cMax)));
+ Log2(("EXT: uDepth %#RX16\n", RT_LE2H_U32(pExtentHdr->uDepth)));
+ Log2(("EXT: cGeneration %#RX32\n", RT_LE2H_U32(pExtentHdr->cGeneration)));
+ }
+}
+
+
+/**
+ * Logs an extent index node.
+ *
+ * @param pExtentIdx The extent index node.
+ */
+static void rtFsExtExtentIdx_Log(PCEXTEXTENTIDX pExtentIdx)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("EXT: Extent index node:\n"));
+ Log2(("EXT: iBlock %#RX32\n", RT_LE2H_U32(pExtentIdx->iBlock)));
+ Log2(("EXT: offChildLow %#RX32\n", RT_LE2H_U32(pExtentIdx->offChildLow)));
+ Log2(("EXT: offChildHigh %#RX16\n", RT_LE2H_U16(pExtentIdx->offChildHigh)));
+ }
+}
+
+
+/**
+ * Logs an extent.
+ *
+ * @param pExtent The extent.
+ */
+static void rtFsExtExtent_Log(PCEXTEXTENT pExtent)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("EXT: Extent:\n"));
+ Log2(("EXT: iBlock %#RX32\n", RT_LE2H_U32(pExtent->iBlock)));
+ Log2(("EXT: cBlocks %#RX16\n", RT_LE2H_U16(pExtent->cBlocks)));
+ Log2(("EXT: offStartHigh %#RX16\n", RT_LE2H_U32(pExtent->offStartHigh)));
+ Log2(("EXT: offStartLow %#RX16\n", RT_LE2H_U16(pExtent->offStartLow)));
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+/**
+ * Converts a block number to a byte offset.
+ *
+ * @returns Offset in bytes for the given block number.
+ * @param pThis The ext volume instance.
+ * @param iBlock The block number to convert.
+ */
+DECLINLINE(uint64_t) rtFsExtBlockIdxToDiskOffset(PRTFSEXTVOL pThis, uint64_t iBlock)
+{
+ return iBlock << pThis->cBlockShift;
+}
+
+
+/**
+ * Converts a byte offset to a block number.
+ *
+ * @returns Block number.
+ * @param pThis The ext volume instance.
+ * @param iBlock The offset to convert.
+ */
+DECLINLINE(uint64_t) rtFsExtDiskOffsetToBlockIdx(PRTFSEXTVOL pThis, uint64_t off)
+{
+ return off >> pThis->cBlockShift;
+}
+
+
+/**
+ * Creates the proper block number from the given low and high parts in case a 64bit
+ * filesystem is used.
+ *
+ * @returns 64bit block number.
+ * @param pThis The ext volume instance.
+ * @param uLow The lower 32bit part.
+ * @param uHigh The upper 32bit part.
+ */
+DECLINLINE(uint64_t) rtFsExtBlockFromLowHigh(PRTFSEXTVOL pThis, uint32_t uLow, uint32_t uHigh)
+{
+ return pThis->f64Bit ? RT_MAKE_U64(uLow, uHigh): uLow;
+}
+
+
+/**
+ * Converts the given high and low parts of the block number to a byte offset.
+ *
+ * @returns Offset in bytes for the given block number.
+ * @param uLow The lower 32bit part of the block number.
+ * @param uHigh The upper 32bit part of the block number.
+ */
+DECLINLINE(uint64_t) rtFsExtBlockIdxLowHighToDiskOffset(PRTFSEXTVOL pThis, uint32_t uLow, uint32_t uHigh)
+{
+ uint64_t iBlock = rtFsExtBlockFromLowHigh(pThis, uLow, uHigh);
+ return rtFsExtBlockIdxToDiskOffset(pThis, iBlock);
+}
+
+
+/**
+ * Allocates a new block group.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param cbAlloc How much to allocate.
+ * @param iBlockGroup Block group number.
+ */
+static PRTFSEXTBLOCKENTRY rtFsExtVol_BlockAlloc(PRTFSEXTVOL pThis, size_t cbAlloc, uint64_t iBlock)
+{
+ PRTFSEXTBLOCKENTRY pBlock = (PRTFSEXTBLOCKENTRY)RTMemAllocZ(cbAlloc);
+ if (RT_LIKELY(pBlock))
+ {
+ pBlock->Core.Key = iBlock;
+ pBlock->cRefs = 0;
+ pThis->cbBlocks += cbAlloc;
+ }
+
+ return pBlock;
+}
+
+
+/**
+ * Returns a new block entry utilizing the cache if possible.
+ *
+ * @returns Pointer to the new block entry or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param iBlock Block number.
+ */
+static PRTFSEXTBLOCKENTRY rtFsExtVol_BlockGetNew(PRTFSEXTVOL pThis, uint64_t iBlock)
+{
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ size_t cbAlloc = RT_UOFFSETOF_DYN(RTFSEXTBLOCKENTRY, abData[pThis->cbBlock]);
+ if (pThis->cbBlocks + cbAlloc <= RTFSEXT_MAX_BLOCK_CACHE_SIZE)
+ pBlock = rtFsExtVol_BlockAlloc(pThis, cbAlloc, iBlock);
+ else
+ {
+ pBlock = RTListRemoveLast(&pThis->LstBlockLru, RTFSEXTBLOCKENTRY, NdLru);
+ if (!pBlock)
+ pBlock = rtFsExtVol_BlockAlloc(pThis, cbAlloc, iBlock);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->BlockRoot, pBlock->Core.Key);
+ Assert(pCore == &pBlock->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pBlock->cRefs);
+ pBlock->Core.Key = iBlock;
+ pBlock->cRefs = 1;
+
+ return pBlock;
+}
+
+
+/**
+ * Frees the given block.
+ *
+ * @param pThis The ext volume instance.
+ * @param pBlock The block to free.
+ */
+static void rtFsExtVol_BlockFree(PRTFSEXTVOL pThis, PRTFSEXTBLOCKENTRY pBlock)
+{
+ Assert(!pBlock->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the block group
+ * is freed right away.
+ */
+ if (pThis->cbBlocks <= RTFSEXT_MAX_BLOCK_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstBlockLru, &pBlock->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->BlockRoot, pBlock->Core.Key);
+ Assert(pCore == &pBlock->Core); RT_NOREF(pCore);
+ RTMemFree(pBlock);
+ pThis->cbBlocks -= RT_UOFFSETOF_DYN(RTFSEXTBLOCKENTRY, abData[pThis->cbBlock]);
+ }
+}
+
+
+/**
+ * Gets the specified block data from the volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param iBlock The filesystem block to load.
+ * @param ppBlock Where to return the pointer to the block entry on success.
+ * @param ppvData Where to return the pointer to the block data on success.
+ */
+static int rtFsExtVol_BlockLoad(PRTFSEXTVOL pThis, uint64_t iBlock, PRTFSEXTBLOCKENTRY *ppBlock, void **ppvData)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the block group from the cache first. */
+ PRTFSEXTBLOCKENTRY pBlock = (PRTFSEXTBLOCKENTRY)RTAvlU64Get(&pThis->BlockRoot, iBlock);
+ if (!pBlock)
+ {
+ /* Slow path, load from disk. */
+ pBlock = rtFsExtVol_BlockGetNew(pThis, iBlock);
+ if (RT_LIKELY(pBlock))
+ {
+ uint64_t offRead = rtFsExtBlockIdxToDiskOffset(pThis, iBlock);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pBlock->abData[0], pThis->cbBlock, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ bool fIns = RTAvlU64Insert(&pThis->BlockRoot, &pBlock->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pBlock->cRefs);
+ if (cRefs == 1) /* Blocks get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pBlock->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppBlock = pBlock;
+ *ppvData = &pBlock->abData[0];
+ }
+ else if (pBlock)
+ {
+ ASMAtomicDecU32(&pBlock->cRefs);
+ rtFsExtVol_BlockFree(pThis, pBlock); /* Free the block. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given block.
+ *
+ * @param pThis The ext volume instance.
+ * @param pBlock The block to release.
+ */
+static void rtFsExtVol_BlockRelease(PRTFSEXTVOL pThis, PRTFSEXTBLOCKENTRY pBlock)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pBlock->cRefs);
+ if (!cRefs)
+ rtFsExtVol_BlockFree(pThis, pBlock);
+}
+
+
+/**
+ * Allocates a new block group.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param cbAlloc How much to allocate.
+ * @param iBlockGroup Block group number.
+ */
+static PRTFSEXTBLKGRP rtFsExtBlockGroupAlloc(PRTFSEXTVOL pThis, size_t cbAlloc, uint32_t iBlockGroup)
+{
+ PRTFSEXTBLKGRP pBlockGroup = (PRTFSEXTBLKGRP)RTMemAllocZ(cbAlloc);
+ if (RT_LIKELY(pBlockGroup))
+ {
+ pBlockGroup->Core.Key = iBlockGroup;
+ pBlockGroup->cRefs = 0;
+ pBlockGroup->pabInodeBitmap = &pBlockGroup->abBlockBitmap[pThis->cbBlockBitmap];
+ pThis->cbBlockGroups += cbAlloc;
+ }
+
+ return pBlockGroup;
+}
+
+
+/**
+ * Frees the given block group.
+ *
+ * @param pThis The ext volume instance.
+ * @param pBlockGroup The block group to free.
+ */
+static void rtFsExtBlockGroupFree(PRTFSEXTVOL pThis, PRTFSEXTBLKGRP pBlockGroup)
+{
+ Assert(!pBlockGroup->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the block group
+ * is freed right away.
+ */
+ if (pThis->cbBlockGroups <= RTFSEXT_MAX_BLOCK_GROUP_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstBlockGroupLru, &pBlockGroup->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->BlockGroupRoot, pBlockGroup->Core.Key);
+ Assert(pCore == &pBlockGroup->Core); RT_NOREF(pCore);
+ RTMemFree(pBlockGroup);
+ pThis->cbBlockGroups -= sizeof(RTFSEXTBLKGRP) + pThis->cbBlockBitmap + pThis->cbInodeBitmap;
+ }
+}
+
+
+/**
+ * Returns a new block group utilizing the cache if possible.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param iBlockGroup Block group number.
+ */
+static PRTFSEXTBLKGRP rtFsExtBlockGroupGetNew(PRTFSEXTVOL pThis, uint32_t iBlockGroup)
+{
+ PRTFSEXTBLKGRP pBlockGroup = NULL;
+ size_t cbAlloc = sizeof(RTFSEXTBLKGRP) + pThis->cbBlockBitmap + pThis->cbInodeBitmap;
+ if (pThis->cbBlockGroups + cbAlloc <= RTFSEXT_MAX_BLOCK_GROUP_CACHE_SIZE)
+ pBlockGroup = rtFsExtBlockGroupAlloc(pThis, cbAlloc, iBlockGroup);
+ else
+ {
+ pBlockGroup = RTListRemoveLast(&pThis->LstBlockGroupLru, RTFSEXTBLKGRP, NdLru);
+ if (!pBlockGroup)
+ pBlockGroup = rtFsExtBlockGroupAlloc(pThis, cbAlloc, iBlockGroup);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->BlockGroupRoot, pBlockGroup->Core.Key);
+ Assert(pCore == &pBlockGroup->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pBlockGroup->cRefs);
+ pBlockGroup->Core.Key = iBlockGroup;
+ pBlockGroup->cRefs = 1;
+
+ return pBlockGroup;
+}
+
+
+/**
+ * Loads the given block group number and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param iBlockGroup The block group to load.
+ * @param ppBlockGroup Where to store the block group on success.
+ */
+static int rtFsExtBlockGroupLoad(PRTFSEXTVOL pThis, uint32_t iBlockGroup, PRTFSEXTBLKGRP *ppBlockGroup)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the block group from the cache first. */
+ PRTFSEXTBLKGRP pBlockGroup = (PRTFSEXTBLKGRP)RTAvlU32Get(&pThis->BlockGroupRoot, iBlockGroup);
+ if (!pBlockGroup)
+ {
+ /* Slow path, load from disk. */
+ pBlockGroup = rtFsExtBlockGroupGetNew(pThis, iBlockGroup);
+ if (RT_LIKELY(pBlockGroup))
+ {
+ uint64_t offRead = rtFsExtBlockIdxToDiskOffset(pThis, pThis->cbBlock == _1K ? 2 : 1)
+ + (uint64_t)iBlockGroup * pThis->cbBlkGrpDesc;
+ EXTBLOCKGROUPDESC BlockGroupDesc;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &BlockGroupDesc, pThis->cbBlkGrpDesc, NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtBlockGroup_Log(pThis, iBlockGroup, &BlockGroupDesc);
+#endif
+ pBlockGroup->iBlockInodeTbl = RT_LE2H_U32(BlockGroupDesc.v32.offInodeTableLow)
+ | ((pThis->cbBlkGrpDesc == sizeof(EXTBLOCKGROUPDESC64))
+ ? (uint64_t)RT_LE2H_U32(BlockGroupDesc.v64.offInodeTableHigh) << 32
+ : 0);
+
+ offRead = rtFsExtBlockIdxLowHighToDiskOffset(pThis, RT_LE2H_U32(BlockGroupDesc.v32.offBlockBitmapLow),
+ RT_LE2H_U32(BlockGroupDesc.v64.offBlockBitmapHigh));
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pBlockGroup->abBlockBitmap[0], pThis->cbBlockBitmap, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ offRead = rtFsExtBlockIdxLowHighToDiskOffset(pThis, RT_LE2H_U32(BlockGroupDesc.v32.offInodeBitmapLow),
+ RT_LE2H_U32(BlockGroupDesc.v64.offInodeBitmapHigh));
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pBlockGroup->pabInodeBitmap[0], pThis->cbInodeBitmap, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ bool fIns = RTAvlU32Insert(&pThis->BlockGroupRoot, &pBlockGroup->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pBlockGroup->cRefs);
+ if (cRefs == 1) /* Block groups get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pBlockGroup->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppBlockGroup = pBlockGroup;
+ else if (pBlockGroup)
+ {
+ ASMAtomicDecU32(&pBlockGroup->cRefs);
+ rtFsExtBlockGroupFree(pThis, pBlockGroup); /* Free the block group. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given block group.
+ *
+ * @param pThis The ext volume instance.
+ * @param pBlockGroup The block group to release.
+ */
+static void rtFsExtBlockGroupRelease(PRTFSEXTVOL pThis, PRTFSEXTBLKGRP pBlockGroup)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pBlockGroup->cRefs);
+ if (!cRefs)
+ rtFsExtBlockGroupFree(pThis, pBlockGroup);
+}
+
+
+/**
+ * Allocates a new inode.
+ *
+ * @returns Pointer to the new inode or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param iInode Inode number.
+ */
+static PRTFSEXTINODE rtFsExtInodeAlloc(PRTFSEXTVOL pThis, uint32_t iInode)
+{
+ PRTFSEXTINODE pInode = (PRTFSEXTINODE)RTMemAllocZ(sizeof(RTFSEXTINODE));
+ if (RT_LIKELY(pInode))
+ {
+ pInode->Core.Key = iInode;
+ pInode->cRefs = 0;
+ pThis->cbInodes += sizeof(RTFSEXTINODE);
+ }
+
+ return pInode;
+}
+
+
+/**
+ * Frees the given inode.
+ *
+ * @param pThis The ext volume instance.
+ * @param pInode The inode to free.
+ */
+static void rtFsExtInodeFree(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode)
+{
+ Assert(!pInode->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the inode
+ * is freed right away.
+ */
+ if (pThis->cbInodes <= RTFSEXT_MAX_INODE_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstInodeLru, &pInode->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->InodeRoot, pInode->Core.Key);
+ Assert(pCore == &pInode->Core); RT_NOREF(pCore);
+ RTMemFree(pInode);
+ pThis->cbInodes -= sizeof(RTFSEXTINODE);
+ }
+}
+
+
+/**
+ * Returns a new inodep utilizing the cache if possible.
+ *
+ * @returns Pointer to the new inode or NULL if out of memory.
+ * @param pThis The ext volume instance.
+ * @param iInode Inode number.
+ */
+static PRTFSEXTINODE rtFsExtInodeGetNew(PRTFSEXTVOL pThis, uint32_t iInode)
+{
+ PRTFSEXTINODE pInode = NULL;
+ if (pThis->cbInodes + sizeof(RTFSEXTINODE) <= RTFSEXT_MAX_INODE_CACHE_SIZE)
+ pInode = rtFsExtInodeAlloc(pThis, iInode);
+ else
+ {
+ pInode = RTListRemoveLast(&pThis->LstInodeLru, RTFSEXTINODE, NdLru);
+ if (!pInode)
+ pInode = rtFsExtInodeAlloc(pThis, iInode);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->InodeRoot, pInode->Core.Key);
+ Assert(pCore == &pInode->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pInode->cRefs);
+ pInode->Core.Key = iInode;
+ pInode->cRefs = 1;
+
+ return pInode;
+}
+
+
+/**
+ * Loads the given inode number and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param iInode The inode to load.
+ * @param ppInode Where to store the inode on success.
+ */
+static int rtFsExtInodeLoad(PRTFSEXTVOL pThis, uint32_t iInode, PRTFSEXTINODE *ppInode)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the inode from the cache first. */
+ PRTFSEXTINODE pInode = (PRTFSEXTINODE)RTAvlU32Get(&pThis->InodeRoot, iInode);
+ if (!pInode)
+ {
+ /* Slow path, load from disk. */
+ pInode = rtFsExtInodeGetNew(pThis, iInode);
+ if (RT_LIKELY(pInode))
+ {
+ /* Calculate the block group and load that one first to get at the inode table location. */
+ PRTFSEXTBLKGRP pBlockGroup = NULL;
+ rc = rtFsExtBlockGroupLoad(pThis, (iInode - 1) / pThis->cInodesPerGroup, &pBlockGroup);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxInodeInTbl = (iInode - 1) % pThis->cInodesPerGroup;
+ uint64_t offRead = rtFsExtBlockIdxToDiskOffset(pThis, pBlockGroup->iBlockInodeTbl)
+ + idxInodeInTbl * pThis->cbInode;
+
+ /* Release block group here already as it is not required. */
+ rtFsExtBlockGroupRelease(pThis, pBlockGroup);
+
+ EXTINODECOMB Inode;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &Inode, RT_MIN(sizeof(Inode), pThis->cbInode), NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtInode_Log(pThis, iInode, &Inode);
+#endif
+ pInode->offInode = offRead;
+ pInode->fFlags = RT_LE2H_U32(Inode.Core.fFlags);
+ pInode->ObjInfo.cbObject = (uint64_t)RT_LE2H_U32(Inode.Core.cbSizeHigh) << 32
+ | (uint64_t)RT_LE2H_U32(Inode.Core.cbSizeLow);
+ pInode->ObjInfo.cbAllocated = ( (uint64_t)RT_LE2H_U16(Inode.Core.Osd2.Lnx.cBlocksHigh) << 32
+ | (uint64_t)RT_LE2H_U32(Inode.Core.cBlocksLow)) * pThis->cbBlock;
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.AccessTime, RT_LE2H_U32(Inode.Core.u32TimeLastAccess));
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.ModificationTime, RT_LE2H_U32(Inode.Core.u32TimeLastModification));
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.ChangeTime, RT_LE2H_U32(Inode.Core.u32TimeLastChange));
+ pInode->ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ pInode->ObjInfo.Attr.u.Unix.uid = (uint32_t)RT_LE2H_U16(Inode.Core.Osd2.Lnx.uUidHigh) << 16
+ | (uint32_t)RT_LE2H_U16(Inode.Core.uUidLow);
+ pInode->ObjInfo.Attr.u.Unix.gid = (uint32_t)RT_LE2H_U16(Inode.Core.Osd2.Lnx.uGidHigh) << 16
+ | (uint32_t)RT_LE2H_U16(Inode.Core.uGidLow);
+ pInode->ObjInfo.Attr.u.Unix.cHardlinks = RT_LE2H_U16(Inode.Core.cHardLinks);
+ pInode->ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ pInode->ObjInfo.Attr.u.Unix.INodeId = iInode;
+ pInode->ObjInfo.Attr.u.Unix.fFlags = 0;
+ pInode->ObjInfo.Attr.u.Unix.GenerationId = RT_LE2H_U32(Inode.Core.u32Version);
+ pInode->ObjInfo.Attr.u.Unix.Device = 0;
+ if (pThis->cbInode >= sizeof(EXTINODECOMB))
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.BirthTime, RT_LE2H_U32(Inode.Extra.u32TimeCreation));
+ else
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.BirthTime, RT_LE2H_U32(Inode.Core.u32TimeLastChange));
+ for (unsigned i = 0; i < RT_ELEMENTS(pInode->aiBlocks); i++)
+ pInode->aiBlocks[i] = RT_LE2H_U32(Inode.Core.au32Block[i]);
+
+ /* Fill in the mode. */
+ pInode->ObjInfo.Attr.fMode = 0;
+ uint32_t fInodeMode = RT_LE2H_U32(Inode.Core.fMode);
+ switch (EXT_INODE_MODE_TYPE_GET_TYPE(fInodeMode))
+ {
+ case EXT_INODE_MODE_TYPE_FIFO:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_FIFO;
+ break;
+ case EXT_INODE_MODE_TYPE_CHAR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DEV_CHAR;
+ break;
+ case EXT_INODE_MODE_TYPE_DIR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DIRECTORY;
+ break;
+ case EXT_INODE_MODE_TYPE_BLOCK:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DEV_BLOCK;
+ break;
+ case EXT_INODE_MODE_TYPE_REGULAR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_FILE;
+ break;
+ case EXT_INODE_MODE_TYPE_SYMLINK:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_SYMLINK;
+ break;
+ case EXT_INODE_MODE_TYPE_SOCKET:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_SOCKET;
+ break;
+ default:
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ if (fInodeMode & EXT_INODE_MODE_EXEC_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXOTH;
+ if (fInodeMode & EXT_INODE_MODE_WRITE_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWOTH;
+ if (fInodeMode & EXT_INODE_MODE_READ_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IROTH;
+ if (fInodeMode & EXT_INODE_MODE_EXEC_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXGRP;
+ if (fInodeMode & EXT_INODE_MODE_WRITE_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWGRP;
+ if (fInodeMode & EXT_INODE_MODE_READ_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IRGRP;
+ if (fInodeMode & EXT_INODE_MODE_EXEC_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXUSR;
+ if (fInodeMode & EXT_INODE_MODE_WRITE_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWUSR;
+ if (fInodeMode & EXT_INODE_MODE_READ_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IRUSR;
+ if (fInodeMode & EXT_INODE_MODE_STICKY)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISTXT;
+ if (fInodeMode & EXT_INODE_MODE_SET_GROUP_ID)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISGID;
+ if (fInodeMode & EXT_INODE_MODE_SET_USER_ID)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISUID;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ bool fIns = RTAvlU32Insert(&pThis->InodeRoot, &pInode->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pInode->cRefs);
+ if (cRefs == 1) /* Inodes get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pInode->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppInode = pInode;
+ else if (pInode)
+ {
+ ASMAtomicDecU32(&pInode->cRefs);
+ rtFsExtInodeFree(pThis, pInode); /* Free the inode. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given inode.
+ *
+ * @param pThis The ext volume instance.
+ * @param pInode The inode to release.
+ */
+static void rtFsExtInodeRelease(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pInode->cRefs);
+ if (!cRefs)
+ rtFsExtInodeFree(pThis, pInode);
+}
+
+
+/**
+ * Worker for various QueryInfo methods.
+ *
+ * @returns IPRT status code.
+ * @param pInode The inode structure to return info for.
+ * @param pObjInfo Where to return object info.
+ * @param enmAddAttr What additional info to return.
+ */
+static int rtFsExtInode_QueryInfo(PRTFSEXTINODE pInode, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_ZERO(*pObjInfo);
+
+ pObjInfo->cbObject = pInode->ObjInfo.cbObject;
+ pObjInfo->cbAllocated = pInode->ObjInfo.cbAllocated;
+ pObjInfo->AccessTime = pInode->ObjInfo.AccessTime;
+ pObjInfo->ModificationTime = pInode->ObjInfo.ModificationTime;
+ pObjInfo->ChangeTime = pInode->ObjInfo.ChangeTime;
+ pObjInfo->BirthTime = pInode->ObjInfo.BirthTime;
+ pObjInfo->Attr.fMode = pInode->ObjInfo.Attr.fMode;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_UNIX:
+ memcpy(&pObjInfo->Attr.u.Unix, &pInode->ObjInfo.Attr.u.Unix, sizeof(pInode->ObjInfo.Attr.u.Unix));
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = pInode->ObjInfo.Attr.u.Unix.uid;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = pInode->ObjInfo.Attr.u.Unix.gid;
+ break;
+
+ default:
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates a given extent header.
+ *
+ * @returns Flag whether the extent header appears to be valid.
+ * @param pExtentHdr The extent header to validate.
+ */
+DECLINLINE(bool) rtFsExtInode_ExtentHdrValidate(PCEXTEXTENTHDR pExtentHdr)
+{
+ return RT_LE2H_U16(pExtentHdr->u16Magic) == EXT_EXTENT_HDR_MAGIC
+ && RT_LE2H_U16(pExtentHdr->cEntries) <= RT_LE2H_U16(pExtentHdr->cMax)
+ && RT_LE2H_U16(pExtentHdr->uDepth) <= EXT_EXTENT_HDR_DEPTH_MAX;
+}
+
+
+/**
+ * Parses the given extent, checking whether it intersects with the given block.
+ *
+ * @returns Flag whether the extent maps the given range (at least partly).
+ * @param pExtent The extent to parse.
+ * @param iBlock The starting inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ */
+DECLINLINE(bool) rtFsExtInode_ExtentParse(PCEXTEXTENT pExtent, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+#ifdef LOG_ENABLED
+ rtFsExtExtent_Log(pExtent);
+#endif
+
+ uint32_t iExtentBlock = RT_LE2H_U32(pExtent->iBlock);
+ uint16_t cExtentLength = RT_LE2H_U16(pExtent->cBlocks);
+
+ /* Length over EXT_EXTENT_LENGTH_LIMIT blocks indicate a sparse extent. */
+ if (cExtentLength > EXT_EXTENT_LENGTH_LIMIT)
+ {
+ *pfSparse = true;
+ cExtentLength -= EXT_EXTENT_LENGTH_LIMIT;
+ }
+ else
+ *pfSparse = false;
+
+ if ( iExtentBlock <= iBlock
+ && iExtentBlock + cExtentLength > iBlock)
+ {
+ uint32_t iBlockRel = iBlock - iExtentBlock;
+ *pcBlocks = RT_MIN(cBlocks, cExtentLength - iBlockRel);
+ *piBlockFs = ( ((uint64_t)RT_LE2H_U16(pExtent->offStartHigh)) << 32
+ | ((uint64_t)RT_LE2H_U32(pExtent->offStartLow))) + iBlockRel;
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Locates the location of the next level in the extent tree mapping the given block.
+ *
+ * @returns Filesystem block number where the next level of the extent is stored.
+ * @param paExtentIdx Pointer to the array of extent index nodes.
+ * @param cEntries Number of entries in the extent index node array.
+ * @param iBlock The block to resolve.
+ */
+DECLINLINE(uint64_t) rtFsExtInode_ExtentIndexLocateNextLvl(PCEXTEXTENTIDX paExtentIdx, uint16_t cEntries, uint64_t iBlock)
+{
+ for (uint32_t i = 1; i < cEntries; i++)
+ {
+ PCEXTEXTENTIDX pPrev = &paExtentIdx[i - 1];
+ PCEXTEXTENTIDX pCur = &paExtentIdx[i];
+
+#ifdef LOG_ENABLED
+ rtFsExtExtentIdx_Log(pPrev);
+#endif
+
+ if ( RT_LE2H_U32(pPrev->iBlock) <= iBlock
+ && RT_LE2H_U32(pCur->iBlock) > iBlock)
+ return (uint64_t)RT_LE2H_U16(pPrev->offChildHigh) << 32
+ | (uint64_t)RT_LE2H_U32(pPrev->offChildLow);
+ }
+
+ /* Nothing found so far, the blast extent index must cover the block as the array is sorted. */
+ PCEXTEXTENTIDX pLast = &paExtentIdx[cEntries - 1];
+#ifdef LOG_ENABLED
+ rtFsExtExtentIdx_Log(pLast);
+#endif
+
+ return (uint64_t)RT_LE2H_U16(pLast->offChildHigh) << 32
+ | (uint64_t)RT_LE2H_U32(pLast->offChildLow);
+}
+
+
+/**
+ * Maps the given inode block to the destination filesystem block using the embedded extent tree.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The inode structure to read from.
+ * @param iBlock The starting inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ *
+ * @todo Optimize
+ */
+static int rtFsExtInode_MapBlockToFsViaExtent(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ int rc = VINF_SUCCESS;
+
+ /* The root of the extent tree is located in the block data of the inode. */
+ PCEXTEXTENTHDR pExtentHdr = (PCEXTEXTENTHDR)&pInode->aiBlocks[0];
+
+#ifdef LOG_ENABLED
+ rtFsExtExtentHdr_Log(pExtentHdr);
+#endif
+
+ /*
+ * Some validation, the top level is located inside the inode block data
+ * and has a maxmimum of 4 entries.
+ */
+ if ( rtFsExtInode_ExtentHdrValidate(pExtentHdr)
+ && RT_LE2H_U16(pExtentHdr->cMax) <= 4)
+ {
+ uint16_t uDepthCur = RT_LE2H_U16(pExtentHdr->uDepth);
+ if (!uDepthCur)
+ {
+ PCEXTEXTENT pExtent = (PCEXTEXTENT)(pExtentHdr + 1);
+
+ rc = VERR_VFS_BOGUS_FORMAT;
+ for (uint32_t i = 0; i < RT_LE2H_U16(pExtentHdr->cEntries); i++)
+ {
+ /* Check whether the extent intersects with the block. */
+ if (rtFsExtInode_ExtentParse(pExtent, iBlock, cBlocks, piBlockFs, pcBlocks, pfSparse))
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ pExtent++;
+ }
+ }
+ else
+ {
+ uint8_t *pbExtent = NULL;
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ uint64_t iBlockNext = 0;
+ PCEXTEXTENTIDX paExtentIdx = (PCEXTEXTENTIDX)(pExtentHdr + 1);
+ uint16_t cEntries = RT_LE2H_U16(pExtentHdr->cEntries);
+
+ /* Descend the tree until we reached the leaf nodes. */
+ do
+ {
+ iBlockNext = rtFsExtInode_ExtentIndexLocateNextLvl(paExtentIdx, cEntries, iBlock);
+ /* Read in the full block. */
+ rc = rtFsExtVol_BlockLoad(pThis, iBlockNext, &pBlock, (void **)&pbExtent);
+ if (RT_SUCCESS(rc))
+ {
+ pExtentHdr = (PCEXTEXTENTHDR)pbExtent;
+
+#ifdef LOG_ENABLED
+ rtFsExtExtentHdr_Log(pExtentHdr);
+#endif
+
+ if ( rtFsExtInode_ExtentHdrValidate(pExtentHdr)
+ && RT_LE2H_U16(pExtentHdr->cMax) <= (pThis->cbBlock - sizeof(EXTEXTENTHDR)) / sizeof(EXTEXTENTIDX)
+ && RT_LE2H_U16(pExtentHdr->uDepth) == uDepthCur - 1)
+ {
+ uDepthCur--;
+ cEntries = RT_LE2H_U16(pExtentHdr->cEntries);
+ paExtentIdx = (PCEXTEXTENTIDX)(pExtentHdr + 1);
+ if (uDepthCur)
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ }
+ while ( uDepthCur > 0
+ && RT_SUCCESS(rc));
+
+ if (RT_SUCCESS(rc))
+ {
+ Assert(!uDepthCur);
+
+ /* We reached the leaf nodes. */
+ PCEXTEXTENT pExtent = (PCEXTEXTENT)(pExtentHdr + 1);
+ for (uint32_t i = 0; i < RT_LE2H_U16(pExtentHdr->cEntries); i++)
+ {
+ /* Check whether the extent intersects with the block. */
+ if (rtFsExtInode_ExtentParse(pExtent, iBlock, cBlocks, piBlockFs, pcBlocks, pfSparse))
+ {
+ rc = VINF_SUCCESS;
+ break;
+ }
+ pExtent++;
+ }
+ }
+
+ if (pBlock)
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+
+ return rc;
+}
+
+
+/**
+ * Maps the given inode block to the destination filesystem block using the original block mapping scheme.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The inode structure to read from.
+ * @param iBlock The inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ *
+ * @todo Optimize and handle sparse files.
+ */
+static int rtFsExtInode_MapBlockToFsViaBlockMap(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ int rc = VINF_SUCCESS;
+ RT_NOREF(cBlocks);
+
+ *pfSparse = false;
+ *pcBlocks = 1;
+
+ /* The first 12 inode blocks are directly mapped from the inode. */
+ if (iBlock <= 11)
+ *piBlockFs = pInode->aiBlocks[iBlock];
+ else
+ {
+ uint32_t cEntriesPerBlockMap = (uint32_t)(pThis->cbBlock >> sizeof(uint32_t));
+
+ if (iBlock <= cEntriesPerBlockMap + 11)
+ {
+ /* Indirect block. */
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ uint32_t *paBlockMap = NULL;
+ rc = rtFsExtVol_BlockLoad(pThis, pInode->aiBlocks[12], &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ *piBlockFs = RT_LE2H_U32(paBlockMap[iBlock - 12]);
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ }
+ else if (iBlock <= cEntriesPerBlockMap * cEntriesPerBlockMap + cEntriesPerBlockMap + 11)
+ {
+ /* Double indirect block. */
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ uint32_t *paBlockMap = NULL;
+
+ iBlock -= 12 + cEntriesPerBlockMap;
+ rc = rtFsExtVol_BlockLoad(pThis, pInode->aiBlocks[13], &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxBlockL2 = iBlock / cEntriesPerBlockMap;
+ uint32_t idxBlockL1 = iBlock % cEntriesPerBlockMap;
+ uint32_t iBlockNext = RT_LE2H_U32(paBlockMap[idxBlockL2]);
+
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ rc = rtFsExtVol_BlockLoad(pThis, iBlockNext, &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ *piBlockFs = RT_LE2H_U32(paBlockMap[idxBlockL1]);
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ }
+ }
+ else
+ {
+ /* Triple indirect block. */
+ PRTFSEXTBLOCKENTRY pBlock = NULL;
+ uint32_t *paBlockMap = NULL;
+
+ iBlock -= 12 + cEntriesPerBlockMap * cEntriesPerBlockMap + cEntriesPerBlockMap;
+ rc = rtFsExtVol_BlockLoad(pThis, pInode->aiBlocks[14], &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxBlockL3 = iBlock / (cEntriesPerBlockMap * cEntriesPerBlockMap);
+ uint32_t iBlockNext = RT_LE2H_U32(paBlockMap[idxBlockL3]);
+
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ rc = rtFsExtVol_BlockLoad(pThis, iBlockNext, &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxBlockL2 = (iBlock % (cEntriesPerBlockMap * cEntriesPerBlockMap)) / cEntriesPerBlockMap;
+ uint32_t idxBlockL1 = iBlock % cEntriesPerBlockMap;
+ iBlockNext = RT_LE2H_U32(paBlockMap[idxBlockL2]);
+
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ rc = rtFsExtVol_BlockLoad(pThis, iBlockNext, &pBlock, (void **)&paBlockMap);
+ if (RT_SUCCESS(rc))
+ {
+ *piBlockFs = RT_LE2H_U32(paBlockMap[idxBlockL1]);
+ rtFsExtVol_BlockRelease(pThis, pBlock);
+ }
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Maps the given inode block to the destination filesystem block.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The inode structure to read from.
+ * @param iBlock The inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ *
+ * @todo Optimize
+ */
+static int rtFsExtInode_MapBlockToFs(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ if (pInode->fFlags & EXT_INODE_F_EXTENTS)
+ return rtFsExtInode_MapBlockToFsViaExtent(pThis, pInode, iBlock, cBlocks, piBlockFs, pcBlocks, pfSparse);
+ else
+ return rtFsExtInode_MapBlockToFsViaBlockMap(pThis, pInode, iBlock, cBlocks, piBlockFs, pcBlocks, pfSparse);
+}
+
+
+/**
+ * Reads data from the given inode at the given byte offset.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The inode structure to read from.
+ * @param off The byte offset to start reading from.
+ * @param pvBuf Where to store the read data to.
+ * @param pcbRead Where to return the amount of data read.
+ */
+static int rtFsExtInode_Read(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, uint64_t off, void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pbBuf = (uint8_t *)pvBuf;
+
+ if (((uint64_t)pInode->ObjInfo.cbObject < off + cbRead))
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ else
+ cbRead = (uint64_t)pInode->ObjInfo.cbObject - off;
+ }
+
+ while ( cbRead
+ && RT_SUCCESS(rc))
+ {
+ uint64_t iBlockStart = rtFsExtDiskOffsetToBlockIdx(pThis, off);
+ uint32_t offBlockStart = off % pThis->cbBlock;
+
+ /* Resolve the inode block to the proper filesystem block. */
+ uint64_t iBlockFs = 0;
+ size_t cBlocks = 0;
+ bool fSparse = false;
+ rc = rtFsExtInode_MapBlockToFs(pThis, pInode, iBlockStart, 1, &iBlockFs, &cBlocks, &fSparse);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cBlocks == 1);
+
+ size_t cbThisRead = RT_MIN(cbRead, pThis->cbBlock - offBlockStart);
+
+ if (!fSparse)
+ {
+ uint64_t offRead = rtFsExtBlockIdxToDiskOffset(pThis, iBlockFs);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead + offBlockStart, pbBuf, cbThisRead, NULL);
+ }
+ else
+ memset(pbBuf, 0, cbThisRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ pbBuf += cbThisRead;
+ cbRead -= cbThisRead;
+ off += cbThisRead;
+ if (pcbRead)
+ *pcbRead += cbThisRead;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * File operations.
+ * File operations.
+ * File operations.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Close(void *pvThis)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ LogFlow(("rtFsExtFile_Close(%p/%p)\n", pThis, pThis->pInode));
+
+ rtFsExtInodeRelease(pThis->pVol, pThis->pInode);
+ pThis->pInode = NULL;
+ pThis->pVol = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsExtFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ return rtFsExtInode_QueryInfo(pThis->pInode, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ size_t cbRead = pSgBuf->paSegs[0].cbSeg;
+ if (!pcbRead)
+ {
+ rc = rtFsExtInode_Read(pThis->pVol, pThis->pInode, (uint64_t)off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbRead;
+ Log6(("rtFsExtFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc\n", off, pSgBuf->paSegs[0].cbSeg, rc));
+ }
+ else
+ {
+ PRTFSEXTINODE pInode = pThis->pInode;
+ if (off >= pInode->ObjInfo.cbObject)
+ {
+ *pcbRead = 0;
+ rc = VINF_EOF;
+ }
+ else
+ {
+ if ((uint64_t)off + cbRead <= (uint64_t)pInode->ObjInfo.cbObject)
+ rc = rtFsExtInode_Read(pThis->pVol, pThis->pInode, (uint64_t)off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ else
+ {
+ /* Return VINF_EOF if beyond end-of-file. */
+ cbRead = (size_t)(pInode->ObjInfo.cbObject - off);
+ rc = rtFsExtInode_Read(pThis->pVol, pThis->pInode, off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ rc = VINF_EOF;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offFile = off + cbRead;
+ *pcbRead = cbRead;
+ }
+ else
+ *pcbRead = 0;
+ }
+ Log6(("rtFsExtFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc *pcbRead=%#x\n", off, pSgBuf->paSegs[0].cbSeg, rc, *pcbRead));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ RT_NOREF(pvThis, off, pSgBuf, fBlocking, pcbWritten);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsExtFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsExtFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsExtFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsExtFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = pThis->pInode->ObjInfo.cbObject + offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNew = (RTFOFF)pThis->offFile + offSeek;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ if (offNew >= 0)
+ {
+ pThis->offFile = offNew;
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+ }
+ return VERR_NEGATIVE_SEEK;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtFsExtFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSEXTFILE pThis = (PRTFSEXTFILE)pvThis;
+ *pcbFile = (uint64_t)pThis->pInode->ObjInfo.cbObject;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtFsExtFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ RT_NOREF(pvThis, cbFile, fFlags);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtFsExtFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = INT64_MAX; /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * EXT file operations.
+ */
+static const RTVFSFILEOPS g_rtFsExtFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "EXT File",
+ rtFsExtFile_Close,
+ rtFsExtFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsExtFile_Read,
+ rtFsExtFile_Write,
+ rtFsExtFile_Flush,
+ NULL /*PollOne*/,
+ rtFsExtFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtFsExtFile_SetMode,
+ rtFsExtFile_SetTimes,
+ rtFsExtFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsExtFile_Seek,
+ rtFsExtFile_QuerySize,
+ rtFsExtFile_SetSize,
+ rtFsExtFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Creates a new VFS file from the given regular file inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param fOpen Open flags passed.
+ * @param iInode The inode for the file.
+ * @param phVfsFile Where to store the VFS file handle on success.
+ * @param pErrInfo Where to record additional error information on error, optional.
+ * @param pszWhat Logging prefix.
+ */
+static int rtFsExtVol_NewFile(PRTFSEXTVOL pThis, uint64_t fOpen, uint32_t iInode,
+ PRTVFSFILE phVfsFile, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ /*
+ * Load the inode and check that it really is a file.
+ */
+ PRTFSEXTINODE pInode = NULL;
+ int rc = rtFsExtInodeLoad(pThis, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_FILE(pInode->ObjInfo.Attr.fMode))
+ {
+ PRTFSEXTFILE pNewFile;
+ rc = RTVfsNewFile(&g_rtFsExtFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ pNewFile->pVol = pThis;
+ pNewFile->pInode = pInode;
+ pNewFile->offFile = 0;
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_FILE, "%s: fMode=%#RX32", pszWhat, pInode->ObjInfo.Attr.fMode);
+
+ if (RT_FAILURE(rc))
+ rtFsExtInodeRelease(pThis, pInode);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * EXT directory code.
+ * EXT directory code.
+ * EXT directory code.
+ *
+ */
+
+/**
+ * Looks up an entry in the given directory inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pInode The directory inode structure to.
+ * @param pszEntry The entry to lookup.
+ * @param piInode Where to store the inode number if the entry was found.
+ */
+static int rtFsExtDir_Lookup(PRTFSEXTVOL pThis, PRTFSEXTINODE pInode, const char *pszEntry, uint32_t *piInode)
+{
+ uint64_t offEntry = 0;
+ int rc = VERR_FILE_NOT_FOUND;
+ uint32_t idxDirEntry = 0;
+ size_t cchEntry = strlen(pszEntry);
+
+ if (cchEntry > 255)
+ return VERR_FILENAME_TOO_LONG;
+
+ while (offEntry < (uint64_t)pInode->ObjInfo.cbObject)
+ {
+ EXTDIRENTRYEX DirEntry;
+ size_t cbThis = RT_MIN(sizeof(DirEntry), (uint64_t)pInode->ObjInfo.cbObject - offEntry);
+ int rc2 = rtFsExtInode_Read(pThis, pInode, offEntry, &DirEntry, cbThis, NULL);
+ if (RT_SUCCESS(rc2))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtDirEntry_Log(pThis, idxDirEntry, &DirEntry);
+#endif
+
+ uint16_t cbName = pThis->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE
+ ? DirEntry.Core.u.v2.cbName
+ : RT_LE2H_U16(DirEntry.Core.u.v1.cbName);
+ if ( cchEntry == cbName
+ && !memcmp(pszEntry, &DirEntry.Core.achName[0], cchEntry))
+ {
+ *piInode = RT_LE2H_U32(DirEntry.Core.iInodeRef);
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ offEntry += RT_LE2H_U16(DirEntry.Core.cbRecord);
+ idxDirEntry++;
+ }
+ else
+ {
+ rc = rc2;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * Directory instance methods
+ * Directory instance methods
+ * Directory instance methods
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsExtDir_Close(void *pvThis)
+{
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ LogFlowFunc(("pThis=%p\n", pThis));
+ rtFsExtInodeRelease(pThis->pVol, pThis->pInode);
+ pThis->pInode = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsExtDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ LogFlowFunc(("\n"));
+ return rtFsExtInode_QueryInfo(pThis->pInode, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsExtDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsExtDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsExtDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsExtDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ LogFlowFunc(("pszEntry='%s' fOpen=%#RX64 fFlags=%#x\n", pszEntry, fOpen, fFlags));
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ PRTFSEXTVOL pVol = pThis->pVol;
+ int rc = VINF_SUCCESS;
+
+ RT_NOREF(fFlags);
+
+ /*
+ * We cannot create or replace anything, just open stuff.
+ */
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ { /* likely */ }
+ else
+ return VERR_WRITE_PROTECT;
+
+ /*
+ * Lookup the entry.
+ */
+ uint32_t iInode = 0;
+ rc = rtFsExtDir_Lookup(pVol, pThis->pInode, pszEntry, &iInode);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSEXTINODE pInode = NULL;
+ rc = rtFsExtInodeLoad(pVol, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(pInode->ObjInfo.Attr.fMode))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsExtVol_OpenDirByInode(pVol, iInode, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else if (RTFS_IS_FILE(pInode->ObjInfo.Attr.fMode))
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsExtVol_NewFile(pVol, fOpen, iInode, &hVfsFile, NULL, pszEntry);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ LogFlow(("rtFsExtDir_Open(%s): returns %Rrc\n", pszEntry, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtFsExtDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsExtDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtFsExtDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsExtDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsExtDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsExtDir_RewindDir(void *pvThis)
+{
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ LogFlowFunc(("\n"));
+
+ pThis->fNoMoreFiles = false;
+ pThis->offEntry = 0;
+ pThis->idxEntry = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsExtDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSEXTDIR pThis = (PRTFSEXTDIR)pvThis;
+ PRTFSEXTINODE pInode = pThis->pInode;
+ LogFlowFunc(("\n"));
+
+ if (pThis->fNoMoreFiles)
+ return VERR_NO_MORE_FILES;
+
+ EXTDIRENTRYEX DirEntry;
+ size_t cbThis = RT_MIN(sizeof(DirEntry), (uint64_t)pInode->ObjInfo.cbObject - pThis->offEntry);
+ int rc = rtFsExtInode_Read(pThis->pVol, pInode, pThis->offEntry, &DirEntry, cbThis, NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtDirEntry_Log(pThis->pVol, pThis->idxEntry, &DirEntry);
+#endif
+
+ /* 0 inode entry means unused entry. */
+ /** @todo Can there be unused entries somewhere in the middle? */
+ uint32_t iInodeRef = RT_LE2H_U32(DirEntry.Core.iInodeRef);
+ if (iInodeRef != 0)
+ {
+ uint16_t cbName = pThis->pVol->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE
+ ? DirEntry.Core.u.v2.cbName
+ : RT_LE2H_U16(DirEntry.Core.u.v1.cbName);
+
+ if (cbName <= 255)
+ {
+ size_t const cbDirEntry = *pcbDirEntry;
+
+ *pcbDirEntry = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cbName + 2]);
+ if (*pcbDirEntry <= cbDirEntry)
+ {
+ /* Load the referenced inode. */
+ PRTFSEXTINODE pInodeRef;
+ rc = rtFsExtInodeLoad(pThis->pVol, iInodeRef, &pInodeRef);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pDirEntry->szName[0], &DirEntry.Core.achName[0], cbName);
+ pDirEntry->szName[cbName] = '\0';
+ pDirEntry->cbName = cbName;
+ rc = rtFsExtInode_QueryInfo(pInodeRef, &pDirEntry->Info, enmAddAttr);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offEntry += RT_LE2H_U16(DirEntry.Core.cbRecord);
+ pThis->idxEntry++;
+ rtFsExtInodeRelease(pThis->pVol, pInodeRef);
+ return VINF_SUCCESS;
+ }
+ rtFsExtInodeRelease(pThis->pVol, pInodeRef);
+ }
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rc = VERR_FILENAME_TOO_LONG;
+ }
+ else
+ {
+ rc = VERR_NO_MORE_FILES;
+ LogFlowFunc(("no more files\n"));
+ pThis->fNoMoreFiles = true;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * EXT directory operations.
+ */
+static const RTVFSDIROPS g_rtFsExtDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "EXT Dir",
+ rtFsExtDir_Close,
+ rtFsExtDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtFsExtDir_SetMode,
+ rtFsExtDir_SetTimes,
+ rtFsExtDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsExtDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile */,
+ NULL /* pfnOpenDir */,
+ rtFsExtDir_CreateDir,
+ rtFsExtDir_OpenSymlink,
+ rtFsExtDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsExtDir_UnlinkEntry,
+ rtFsExtDir_RenameEntry,
+ rtFsExtDir_RewindDir,
+ rtFsExtDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+/**
+ * Opens a directory by the given inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param iInode The inode to open.
+ * @param phVfsDir Where to store the handle to the VFS directory on success.
+ */
+static int rtFsExtVol_OpenDirByInode(PRTFSEXTVOL pThis, uint32_t iInode, PRTVFSDIR phVfsDir)
+{
+ PRTFSEXTINODE pInode = NULL;
+ int rc = rtFsExtInodeLoad(pThis, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(pInode->ObjInfo.Attr.fMode))
+ {
+ PRTFSEXTDIR pNewDir;
+ rc = RTVfsNewDir(&g_rtFsExtDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ pNewDir->fNoMoreFiles = false;
+ pNewDir->pVol = pThis;
+ pNewDir->pInode = pInode;
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+
+ if (RT_FAILURE(rc))
+ rtFsExtInodeRelease(pThis, pInode);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * Volume level code.
+ * Volume level code.
+ * Volume level code.
+ *
+ */
+
+/**
+ * Checks whether the block range in the given block group is in use by checking the
+ * block bitmap.
+ *
+ * @returns Flag whether the range is in use.
+ * @param pBlkGrpDesc The block group to check for.
+ * @param iBlockStart The starting block to check relative from the beginning of the block group.
+ * @param cBlocks How many blocks to check.
+ */
+static bool rtFsExtIsBlockRangeInUse(PRTFSEXTBLKGRP pBlkGrpDesc, uint64_t iBlockStart, size_t cBlocks)
+{
+ /** @todo Optimize with ASMBitFirstSet(). */
+ while (cBlocks)
+ {
+ uint32_t idxByte = iBlockStart / 8;
+ uint32_t iBit = iBlockStart % 8;
+
+ if (pBlkGrpDesc->abBlockBitmap[idxByte] & RT_BIT(iBit))
+ return true;
+
+ cBlocks--;
+ iBlockStart++;
+ }
+
+ return false;
+}
+
+
+static DECLCALLBACK(int) rtFsExtVolBlockGroupTreeDestroy(PAVLU32NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSEXTBLKGRP pBlockGroup = (PRTFSEXTBLKGRP)pCore;
+ Assert(!pBlockGroup->cRefs);
+ RTMemFree(pBlockGroup);
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtFsExtVolInodeTreeDestroy(PAVLU32NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSEXTINODE pInode = (PRTFSEXTINODE)pCore;
+ Assert(!pInode->cRefs);
+ RTMemFree(pInode);
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtFsExtVolBlockTreeDestroy(PAVLU64NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSEXTBLOCKENTRY pBlock = (PRTFSEXTBLOCKENTRY)pCore;
+ Assert(!pBlock->cRefs);
+ RTMemFree(pBlock);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsExtVol_Close(void *pvThis)
+{
+ PRTFSEXTVOL pThis = (PRTFSEXTVOL)pvThis;
+
+ /* Destroy the block group tree. */
+ RTAvlU32Destroy(&pThis->BlockGroupRoot, rtFsExtVolBlockGroupTreeDestroy, pThis);
+ pThis->BlockGroupRoot = NULL;
+ RTListInit(&pThis->LstBlockGroupLru);
+
+ /* Destroy the inode tree. */
+ RTAvlU32Destroy(&pThis->InodeRoot, rtFsExtVolInodeTreeDestroy, pThis);
+ pThis->InodeRoot = NULL;
+ RTListInit(&pThis->LstInodeLru);
+
+ /* Destroy the block cache tree. */
+ RTAvlU64Destroy(&pThis->BlockRoot, rtFsExtVolBlockTreeDestroy, pThis);
+ pThis->BlockRoot = NULL;
+ RTListInit(&pThis->LstBlockLru);
+
+ /*
+ * Backing file and handles.
+ */
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+ pThis->hVfsSelf = NIL_RTVFS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsExtVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsExtVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSEXTVOL pThis = (PRTFSEXTVOL)pvThis;
+ int rc = rtFsExtVol_OpenDirByInode(pThis, EXT_INODE_NR_ROOT_DIR, phVfsDir);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsExtVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed)
+{
+ int rc = VINF_SUCCESS;
+ PRTFSEXTVOL pThis = (PRTFSEXTVOL)pvThis;
+
+ *pfUsed = false;
+
+ uint64_t iBlock = rtFsExtDiskOffsetToBlockIdx(pThis, off);
+ uint64_t cBlocks = rtFsExtDiskOffsetToBlockIdx(pThis, cb) + (cb % pThis->cbBlock ? 1 : 0);
+ while (cBlocks > 0)
+ {
+ uint32_t const iBlockGroup = iBlock / pThis->cBlocksPerGroup;
+ uint32_t const iBlockRelStart = iBlock - iBlockGroup * pThis->cBlocksPerGroup;
+ PRTFSEXTBLKGRP pBlockGroup = NULL;
+
+ rc = rtFsExtBlockGroupLoad(pThis, iBlockGroup, &pBlockGroup);
+ if (RT_FAILURE(rc))
+ break;
+
+ uint64_t cBlocksThis = RT_MIN(cBlocks, iBlockRelStart - pThis->cBlocksPerGroup);
+ if (rtFsExtIsBlockRangeInUse(pBlockGroup, iBlockRelStart, cBlocksThis))
+ {
+ *pfUsed = true;
+ break;
+ }
+
+ rtFsExtBlockGroupRelease(pThis, pBlockGroup);
+ cBlocks -= cBlocksThis;
+ iBlock += cBlocksThis;
+ }
+
+ return rc;
+}
+
+
+DECL_HIDDEN_CONST(const RTVFSOPS) g_rtFsExtVolOps =
+{
+ /* .Obj = */
+ {
+ /* .uVersion = */ RTVFSOBJOPS_VERSION,
+ /* .enmType = */ RTVFSOBJTYPE_VFS,
+ /* .pszName = */ "ExtVol",
+ /* .pfnClose = */ rtFsExtVol_Close,
+ /* .pfnQueryInfo = */ rtFsExtVol_QueryInfo,
+ /* .pfnQueryInfoEx = */ NULL,
+ /* .uEndMarker = */ RTVFSOBJOPS_VERSION
+ },
+ /* .uVersion = */ RTVFSOPS_VERSION,
+ /* .fFeatures = */ 0,
+ /* .pfnOpenRoot = */ rtFsExtVol_OpenRoot,
+ /* .pfnQueryRangeState = */ rtFsExtVol_QueryRangeState,
+ /* .uEndMarker = */ RTVFSOPS_VERSION
+};
+
+
+
+/**
+ * Loads the parameters from the given original ext2 format superblock (EXT_SB_REV_ORIG).
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pSb The superblock to load.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsExtVolLoadAndParseSuperBlockV0(PRTFSEXTVOL pThis, PCEXTSUPERBLOCK pSb, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pErrInfo);
+
+ /*
+ * Linux never supported a differing cluster (also called fragment) size for
+ * the original ext2 layout so we reject such filesystems as it is not clear what
+ * the purpose is really.
+ */
+ if (RT_LE2H_U32(pSb->cLogBlockSize) != RT_LE2H_U32(pSb->cLogClusterSize))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "EXT filesystem cluster and block size differ");
+
+ pThis->f64Bit = false;
+ pThis->cBlockShift = 10 + RT_LE2H_U32(pSb->cLogBlockSize);
+ pThis->cbBlock = UINT64_C(1) << pThis->cBlockShift;
+ pThis->cbInode = sizeof(EXTINODE);
+ pThis->cbBlkGrpDesc = sizeof(EXTBLOCKGROUPDESC32);
+ pThis->cBlocksPerGroup = RT_LE2H_U32(pSb->cBlocksPerGroup);
+ pThis->cInodesPerGroup = RT_LE2H_U32(pSb->cInodesPerBlockGroup);
+ pThis->cBlockGroups = RT_LE2H_U32(pSb->cBlocksTotalLow) / pThis->cBlocksPerGroup;
+ pThis->cbBlockBitmap = pThis->cBlocksPerGroup / 8;
+ if (pThis->cBlocksPerGroup % 8)
+ pThis->cbBlockBitmap++;
+ pThis->cbInodeBitmap = pThis->cInodesPerGroup / 8;
+ if (pThis->cInodesPerGroup % 8)
+ pThis->cbInodeBitmap++;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads the parameters from the given ext superblock (EXT_SB_REV_V2_DYN_INODE_SZ).
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pSb The superblock to load.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsExtVolLoadAndParseSuperBlockV1(PRTFSEXTVOL pThis, PCEXTSUPERBLOCK pSb, PRTERRINFO pErrInfo)
+{
+ if ((RT_LE2H_U32(pSb->fFeaturesIncompat) & ~RTFSEXT_INCOMPAT_FEATURES_SUPP) != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "EXT filesystem contains unsupported incompatible features: %RX32",
+ RT_LE2H_U32(pSb->fFeaturesIncompat) & ~RTFSEXT_INCOMPAT_FEATURES_SUPP);
+ if ( RT_LE2H_U32(pSb->fFeaturesCompatRo) != 0
+ && !(pThis->fMntFlags & RTVFSMNT_F_READ_ONLY))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "EXT filesystem contains unsupported readonly features: %RX32",
+ RT_LE2H_U32(pSb->fFeaturesCompatRo));
+
+ pThis->fFeaturesIncompat = RT_LE2H_U32(pSb->fFeaturesIncompat);
+ pThis->f64Bit = RT_BOOL(pThis->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_64BIT);
+ pThis->cBlockShift = 10 + RT_LE2H_U32(pSb->cLogBlockSize);
+ pThis->cbBlock = UINT64_C(1) << pThis->cBlockShift;
+ pThis->cbInode = RT_LE2H_U16(pSb->cbInode);
+ pThis->cbBlkGrpDesc = pThis->f64Bit ? RT_LE2H_U16(pSb->cbGroupDesc) : sizeof(EXTBLOCKGROUPDESC32);
+ pThis->cBlocksPerGroup = RT_LE2H_U32(pSb->cBlocksPerGroup);
+ pThis->cInodesPerGroup = RT_LE2H_U32(pSb->cInodesPerBlockGroup);
+ pThis->cBlockGroups = RT_LE2H_U32(pSb->cBlocksTotalLow) / pThis->cBlocksPerGroup;
+ pThis->cbBlockBitmap = pThis->cBlocksPerGroup / 8;
+ if (pThis->cBlocksPerGroup % 8)
+ pThis->cbBlockBitmap++;
+ pThis->cbInodeBitmap = pThis->cInodesPerGroup / 8;
+ if (pThis->cInodesPerGroup % 8)
+ pThis->cbInodeBitmap++;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads and parses the superblock of the filesystem.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ext volume instance.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsExtVolLoadAndParseSuperblock(PRTFSEXTVOL pThis, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+ EXTSUPERBLOCK Sb;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, EXT_SB_OFFSET, &Sb, sizeof(EXTSUPERBLOCK), NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading super block");
+
+ /* Validate the superblock. */
+ if (RT_LE2H_U16(Sb.u16Signature) != EXT_SB_SIGNATURE)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not EXT - Signature mismatch: %RX16", RT_LE2H_U16(Sb.u16Signature));
+
+#ifdef LOG_ENABLED
+ rtFsExtSb_Log(&Sb);
+#endif
+
+ if (RT_LE2H_U16(Sb.u16FilesystemState) == EXT_SB_STATE_ERRORS)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "EXT filesystem contains errors");
+
+ if (RT_LE2H_U32(Sb.u32RevLvl) == EXT_SB_REV_ORIG)
+ rc = rtFsExtVolLoadAndParseSuperBlockV0(pThis, &Sb, pErrInfo);
+ else
+ rc = rtFsExtVolLoadAndParseSuperBlockV1(pThis, &Sb, pErrInfo);
+
+ return rc;
+}
+
+
+RTDECL(int) RTFsExtVolOpen(RTVFSFILE hVfsFileIn, uint32_t fMntFlags, uint32_t fExtFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ AssertReturn(!(fMntFlags & ~RTVFSMNT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!fExtFlags, VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a VFS instance and initialize the data so rtFsExtVol_Close works.
+ */
+ RTVFS hVfs;
+ PRTFSEXTVOL pThis;
+ int rc = RTVfsNew(&g_rtFsExtVolOps, sizeof(*pThis), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsBacking = hVfsFileIn;
+ pThis->hVfsSelf = hVfs;
+ pThis->fMntFlags = fMntFlags;
+ pThis->fExtFlags = fExtFlags;
+ pThis->BlockGroupRoot = NULL;
+ pThis->InodeRoot = NULL;
+ pThis->BlockRoot = NULL;
+ pThis->cbBlockGroups = 0;
+ pThis->cbInodes = 0;
+ pThis->cbBlocks = 0;
+ RTListInit(&pThis->LstBlockGroupLru);
+ RTListInit(&pThis->LstInodeLru);
+ RTListInit(&pThis->LstBlockLru);
+
+ rc = RTVfsFileQuerySize(pThis->hVfsBacking, &pThis->cbBacking);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsExtVolLoadAndParseSuperblock(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfs = hVfs;
+ return VINF_SUCCESS;
+ }
+ }
+
+ RTVfsRelease(hVfs);
+ *phVfs = NIL_RTVFS;
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainExtVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
+ return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
+ if ( pElement->enmType != RTVFSOBJTYPE_VFS
+ && pElement->enmType != RTVFSOBJTYPE_DIR)
+ return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly ? RTVFSMNT_F_READ_ONLY : 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainExtVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError);
+
+ int rc;
+ RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
+ if (hVfsFileIn != NIL_RTVFSFILE)
+ {
+ RTVFS hVfs;
+ rc = RTFsExtVolOpen(hVfsFileIn, (uint32_t)pElement->uProvider, (uint32_t)(pElement->uProvider >> 32), &hVfs, pErrInfo);
+ RTVfsFileRelease(hVfsFileIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromVfs(hVfs);
+ RTVfsRelease(hVfs);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainExtVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider
+ || !pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'ext'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainExtVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "ext",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a EXT file system, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainExtVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainExtVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainExtVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainExtVolReg, rtVfsChainExtVolReg);
+
diff --git a/src/VBox/Runtime/common/fs/fatvfs.cpp b/src/VBox/Runtime/common/fs/fatvfs.cpp
new file mode 100644
index 00000000..e7cb5b46
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/fatvfs.cpp
@@ -0,0 +1,6374 @@
+/* $Id: fatvfs.cpp $ */
+/** @file
+ * IPRT - FAT Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include "internal/iprt.h"
+#include <iprt/fsvfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/poll.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+#include <iprt/sg.h>
+#include <iprt/thread.h>
+#include <iprt/uni.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/zero.h>
+#include <iprt/formats/fat.h>
+
+#include "internal/fs.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/**
+ * Gets the cluster from a directory entry.
+ *
+ * @param a_pDirEntry Pointer to the directory entry.
+ * @param a_pVol Pointer to the volume.
+ */
+#define RTFSFAT_GET_CLUSTER(a_pDirEntry, a_pVol) \
+ ( (a_pVol)->enmFatType >= RTFSFATTYPE_FAT32 \
+ ? RT_MAKE_U32((a_pDirEntry)->idxCluster, (a_pDirEntry)->u.idxClusterHigh) \
+ : (a_pDirEntry)->idxCluster )
+
+/**
+ * Rotates a unsigned 8-bit value one bit to the right.
+ *
+ * @returns Rotated 8-bit value.
+ * @param a_bValue The value to rotate.
+ */
+#define RTFSFAT_ROT_R1_U8(a_bValue) (((a_bValue) >> 1) | (uint8_t)((a_bValue) << 7))
+
+
+/** Maximum number of characters we will create in a long file name. */
+#define RTFSFAT_MAX_LFN_CHARS 255
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a FAT directory instance. */
+typedef struct RTFSFATDIRSHRD *PRTFSFATDIRSHRD;
+/** Pointer to a FAT volume (VFS instance data). */
+typedef struct RTFSFATVOL *PRTFSFATVOL;
+
+
+/** The number of entire in a chain part. */
+#define RTFSFATCHAINPART_ENTRIES (256U - 4U)
+
+/**
+ * A part of the cluster chain covering up to 252 clusters.
+ */
+typedef struct RTFSFATCHAINPART
+{
+ /** List entry. */
+ RTLISTNODE ListEntry;
+ /** Chain entries. */
+ uint32_t aEntries[RTFSFATCHAINPART_ENTRIES];
+} RTFSFATCHAINPART;
+AssertCompile(sizeof(RTFSFATCHAINPART) <= _1K);
+typedef RTFSFATCHAINPART *PRTFSFATCHAINPART;
+typedef RTFSFATCHAINPART const *PCRTFSFATCHAINPART;
+
+
+/**
+ * A FAT cluster chain.
+ */
+typedef struct RTFSFATCHAIN
+{
+ /** The chain size in bytes. */
+ uint32_t cbChain;
+ /** The chain size in entries. */
+ uint32_t cClusters;
+ /** The cluster size. */
+ uint32_t cbCluster;
+ /** The shift count for converting between clusters and bytes. */
+ uint8_t cClusterByteShift;
+ /** List of chain parts (RTFSFATCHAINPART). */
+ RTLISTANCHOR ListParts;
+} RTFSFATCHAIN;
+/** Pointer to a FAT chain. */
+typedef RTFSFATCHAIN *PRTFSFATCHAIN;
+/** Pointer to a const FAT chain. */
+typedef RTFSFATCHAIN const *PCRTFSFATCHAIN;
+
+
+/**
+ * FAT file system object (common part to files and dirs (shared)).
+ */
+typedef struct RTFSFATOBJ
+{
+ /** The parent directory keeps a list of open objects (RTFSFATOBJ). */
+ RTLISTNODE Entry;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The parent directory (not released till all children are close). */
+ PRTFSFATDIRSHRD pParentDir;
+ /** The byte offset of the directory entry in the parent dir.
+ * This is set to UINT32_MAX for the root directory. */
+ uint32_t offEntryInDir;
+ /** Attributes. */
+ RTFMODE fAttrib;
+ /** The object size. */
+ uint32_t cbObject;
+ /** The access time. */
+ RTTIMESPEC AccessTime;
+ /** The modificaton time. */
+ RTTIMESPEC ModificationTime;
+ /** The birth time. */
+ RTTIMESPEC BirthTime;
+ /** Cluster chain. */
+ RTFSFATCHAIN Clusters;
+ /** Pointer to the volume. */
+ struct RTFSFATVOL *pVol;
+ /** Set if we've maybe dirtied the FAT. */
+ bool fMaybeDirtyFat;
+ /** Set if we've maybe dirtied the directory entry. */
+ bool fMaybeDirtyDirEnt;
+} RTFSFATOBJ;
+/** Poitner to a FAT file system object. */
+typedef RTFSFATOBJ *PRTFSFATOBJ;
+
+/**
+ * Shared FAT file data.
+ */
+typedef struct RTFSFATFILESHRD
+{
+ /** Core FAT object info. */
+ RTFSFATOBJ Core;
+} RTFSFATFILESHRD;
+/** Pointer to shared FAT file data. */
+typedef RTFSFATFILESHRD *PRTFSFATFILESHRD;
+
+
+/**
+ * Per handle data for a FAT file.
+ */
+typedef struct RTFSFATFILE
+{
+ /** Pointer to the shared data. */
+ PRTFSFATFILESHRD pShared;
+ /** The current file offset. */
+ uint32_t offFile;
+} RTFSFATFILE;
+/** Pointer to the per handle data of a FAT file. */
+typedef RTFSFATFILE *PRTFSFATFILE;
+
+
+/**
+ * FAT shared directory structure.
+ *
+ * We work directories in one of two buffering modes. If there are few entries
+ * or if it's the FAT12/16 root directory, we map the whole thing into memory.
+ * If it's too large, we use an inefficient sector buffer for now.
+ *
+ * Directory entry updates happens exclusively via the directory, so any open
+ * files or subdirs have a parent reference for doing that. The parent OTOH,
+ * keeps a list of open children.
+ */
+typedef struct RTFSFATDIRSHRD
+{
+ /** Core FAT object info. */
+ RTFSFATOBJ Core;
+ /** Open child objects (RTFSFATOBJ). */
+ RTLISTNODE OpenChildren;
+
+ /** Number of directory entries. */
+ uint32_t cEntries;
+
+ /** If fully buffered. */
+ bool fFullyBuffered;
+ /** Set if this is a linear root directory. */
+ bool fIsLinearRootDir;
+ /** The size of the memory paEntries points at. */
+ uint32_t cbAllocatedForEntries;
+
+ /** Pointer to the directory buffer.
+ * In fully buffering mode, this is the whole of the directory. Otherwise it's
+ * just a sector worth of buffers. */
+ PFATDIRENTRYUNION paEntries;
+ /** The disk offset corresponding to what paEntries points to.
+ * UINT64_MAX if notthing read into paEntries yet. */
+ uint64_t offEntriesOnDisk;
+ union
+ {
+ /** Data for the full buffered mode.
+ * No need to messing around with clusters here, as we only uses this for
+ * directories with a contiguous mapping on the disk.
+ * So, if we grow a directory in a non-contiguous manner, we have to switch
+ * to sector buffering on the fly. */
+ struct
+ {
+ /** Number of sectors mapped by paEntries and pbDirtySectors. */
+ uint32_t cSectors;
+ /** Number of dirty sectors. */
+ uint32_t cDirtySectors;
+ /** Dirty sector bitmap (one bit per sector). */
+ uint8_t *pbDirtySectors;
+ } Full;
+ /** The simple sector buffering.
+ * This only works for clusters, so no FAT12/16 root directory fun. */
+ struct
+ {
+ /** The directory offset, UINT32_MAX if invalid. */
+ uint32_t offInDir;
+ /** Dirty flag. */
+ bool fDirty;
+ } Simple;
+ } u;
+} RTFSFATDIRSHRD;
+/** Pointer to a shared FAT directory instance. */
+typedef RTFSFATDIRSHRD *PRTFSFATDIRSHRD;
+
+
+/**
+ * The per handle FAT directory data.
+ */
+typedef struct RTFSFATDIR
+{
+ /** Core FAT object info. */
+ PRTFSFATDIRSHRD pShared;
+ /** The current directory offset. */
+ uint32_t offDir;
+} RTFSFATDIR;
+/** Pointer to a per handle FAT directory data. */
+typedef RTFSFATDIR *PRTFSFATDIR;
+
+
+/**
+ * File allocation table cache entry.
+ */
+typedef struct RTFSFATCLUSTERMAPENTRY
+{
+ /** The byte offset into the fat, UINT32_MAX if invalid entry. */
+ uint32_t offFat;
+ /** Pointer to the data. */
+ uint8_t *pbData;
+ /** Dirty bitmap. Indexed by byte offset right shifted by
+ * RTFSFATCLUSTERMAPCACHE::cDirtyShift. */
+ uint64_t bmDirty;
+} RTFSFATCLUSTERMAPENTRY;
+/** Pointer to a file allocation table cache entry. */
+typedef RTFSFATCLUSTERMAPENTRY *PRTFSFATCLUSTERMAPENTRY;
+
+/**
+ * File allocation table cache.
+ */
+typedef struct RTFSFATCLUSTERMAPCACHE
+{
+ /** Number of cache entries (power of two). */
+ uint32_t cEntries;
+ /** This shift count to use in the first step of the index calculation. */
+ uint32_t cEntryIndexShift;
+ /** The AND mask to use in the second step of the index calculation. */
+ uint32_t fEntryIndexMask;
+ /** The max size of data in a cache entry (power of two). */
+ uint32_t cbEntry;
+ /** The AND mask to use to get the entry offset. */
+ uint32_t fEntryOffsetMask;
+ /** Dirty bitmap shift count. */
+ uint32_t cDirtyShift;
+ /** The dirty cache line size (multiple of two). */
+ uint32_t cbDirtyLine;
+ /** The FAT size. */
+ uint32_t cbFat;
+ /** The Number of clusters in the FAT. */
+ uint32_t cClusters;
+ /** Cluster allocation search hint. */
+ uint32_t idxAllocHint;
+ /** Pointer to the volume (for disk access). */
+ PRTFSFATVOL pVol;
+ /** The cache name. */
+ const char *pszName;
+ /** Cache entries. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ RTFSFATCLUSTERMAPENTRY aEntries[RT_FLEXIBLE_ARRAY];
+} RTFSFATCLUSTERMAPCACHE;
+/** Pointer to a FAT linear metadata cache. */
+typedef RTFSFATCLUSTERMAPCACHE *PRTFSFATCLUSTERMAPCACHE;
+
+
+/**
+ * BPB version.
+ */
+typedef enum RTFSFATBPBVER
+{
+ RTFSFATBPBVER_INVALID = 0,
+ RTFSFATBPBVER_NO_BPB,
+ RTFSFATBPBVER_DOS_2_0,
+ //RTFSFATBPBVER_DOS_3_2, - we don't try identify this one.
+ RTFSFATBPBVER_DOS_3_31,
+ RTFSFATBPBVER_EXT_28,
+ RTFSFATBPBVER_EXT_29,
+ RTFSFATBPBVER_FAT32_28,
+ RTFSFATBPBVER_FAT32_29,
+ RTFSFATBPBVER_END
+} RTFSFATBPBVER;
+
+
+/**
+ * A FAT volume.
+ */
+typedef struct RTFSFATVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the FAT volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+ /** Byte offset of the bootsector relative to the start of the file. */
+ uint64_t offBootSector;
+ /** The UTC offset in nanoseconds to use for this file system (FAT traditionally
+ * stores timestamps in local time).
+ * @remarks This may need improving later. */
+ int64_t offNanoUTC;
+ /** The UTC offset in minutes to use for this file system (FAT traditionally
+ * stores timestamps in local time).
+ * @remarks This may need improving later. */
+ int32_t offMinUTC;
+ /** Set if read-only mode. */
+ bool fReadOnly;
+ /** Media byte. */
+ uint8_t bMedia;
+ /** Reserved sectors. */
+ uint32_t cReservedSectors;
+ /** The BPB version. Gives us an idea of the FAT file system version. */
+ RTFSFATBPBVER enmBpbVersion;
+
+ /** Logical sector size. */
+ uint32_t cbSector;
+ /** The shift count for converting between sectors and bytes. */
+ uint8_t cSectorByteShift;
+ /** The shift count for converting between clusters and bytes. */
+ uint8_t cClusterByteShift;
+ /** The cluster size in bytes. */
+ uint32_t cbCluster;
+ /** The number of data clusters, including the two reserved ones. */
+ uint32_t cClusters;
+ /** The offset of the first cluster. */
+ uint64_t offFirstCluster;
+ /** The total size from the BPB, in bytes. */
+ uint64_t cbTotalSize;
+
+ /** The FAT type. */
+ RTFSFATTYPE enmFatType;
+
+ /** Number of FAT entries (clusters). */
+ uint32_t cFatEntries;
+ /** The size of a FAT, in bytes. */
+ uint32_t cbFat;
+ /** Number of FATs. */
+ uint32_t cFats;
+ /** The end of chain marker used by the formatter (FAT entry \#2). */
+ uint32_t idxEndOfChain;
+ /** The maximum last cluster supported by the FAT format. */
+ uint32_t idxMaxLastCluster;
+ /** FAT byte offsets. */
+ uint64_t aoffFats[8];
+ /** Pointer to the FAT (cluster map) cache. */
+ PRTFSFATCLUSTERMAPCACHE pFatCache;
+
+ /** The root directory byte offset. */
+ uint64_t offRootDir;
+ /** Root directory cluster, UINT32_MAX if not FAT32. */
+ uint32_t idxRootDirCluster;
+ /** Number of root directory entries, if fixed. UINT32_MAX for FAT32. */
+ uint32_t cRootDirEntries;
+ /** The size of the root directory, rounded up to the nearest sector size. */
+ uint32_t cbRootDir;
+ /** The root directory data (shared). */
+ PRTFSFATDIRSHRD pRootDir;
+
+ /** Serial number. */
+ uint32_t uSerialNo;
+ /** The stripped volume label, if included in EBPB. */
+ char szLabel[12];
+ /** The file system type from the EBPB (also stripped). */
+ char szType[9];
+ /** Number of FAT32 boot sector copies. */
+ uint8_t cBootSectorCopies;
+ /** FAT32 flags. */
+ uint16_t fFat32Flags;
+ /** Offset of the FAT32 boot sector copies, UINT64_MAX if none. */
+ uint64_t offBootSectorCopies;
+
+ /** The FAT32 info sector byte offset, UINT64_MAX if not present. */
+ uint64_t offFat32InfoSector;
+ /** The FAT32 info sector if offFat32InfoSector isn't UINT64_MAX. */
+ FAT32INFOSECTOR Fat32InfoSector;
+} RTFSFATVOL;
+/** Pointer to a const FAT volume (VFS instance data). */
+typedef RTFSFATVOL const *PCRTFSFATVOL;
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Codepage 437 translation table with invalid 8.3 characters marked as 0xffff or 0xfffe.
+ *
+ * The 0xfffe notation is used for characters that are valid in long file names but not short.
+ *
+ * @remarks The valid first 128 entries are 1:1 with unicode.
+ * @remarks Lower case characters are all marked invalid.
+ */
+static RTUTF16 g_awchFatCp437ValidChars[] =
+{ /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0xffff, 0xfffe, 0xfffe, 0x002d, 0xfffe, 0xffff,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0xffff, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xffff,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0xfffe, 0xffff, 0xfffe, 0x005e, 0x005f,
+ 0x0060, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe,
+ 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xffff, 0xffff, 0xffff, 0x007e, 0xffff,
+ 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+ 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
+ 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
+ 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
+ 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
+ 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
+};
+AssertCompileSize(g_awchFatCp437ValidChars, 256*2);
+
+/**
+ * Codepage 437 translation table without invalid 8.3. character markings.
+ */
+static RTUTF16 g_awchFatCp437Chars[] =
+{ /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */
+ 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
+ 0x25ba, 0x25c4, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
+ 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+ 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
+ 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
+ 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
+ 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
+ 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
+};
+AssertCompileSize(g_awchFatCp437Chars, 256*2);
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static PRTFSFATOBJ rtFsFatDirShrd_LookupShared(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir);
+static void rtFsFatDirShrd_AddOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild);
+static void rtFsFatDirShrd_RemoveOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild);
+static int rtFsFatDirShrd_GetEntryForUpdate(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir,
+ PFATDIRENTRY *ppDirEntry, uint32_t *puWriteLock);
+static int rtFsFatDirShrd_PutEntryAfterUpdate(PRTFSFATDIRSHRD pThis, PFATDIRENTRY pDirEntry, uint32_t uWriteLock);
+static int rtFsFatDirShrd_Flush(PRTFSFATDIRSHRD pThis);
+static int rtFsFatDir_NewWithShared(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pShared, PRTVFSDIR phVfsDir);
+static int rtFsFatDir_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir,
+ uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTVFSDIR phVfsDir);
+
+
+/**
+ * Convers a cluster to a disk offset.
+ *
+ * @returns Disk byte offset, UINT64_MAX on invalid cluster.
+ * @param pThis The FAT volume instance.
+ * @param idxCluster The cluster number.
+ */
+DECLINLINE(uint64_t) rtFsFatClusterToDiskOffset(PRTFSFATVOL pThis, uint32_t idxCluster)
+{
+ AssertReturn(idxCluster >= FAT_FIRST_DATA_CLUSTER, UINT64_MAX);
+ AssertReturn(idxCluster < pThis->cClusters, UINT64_MAX);
+ return (idxCluster - FAT_FIRST_DATA_CLUSTER) * (uint64_t)pThis->cbCluster
+ + pThis->offFirstCluster;
+}
+
+
+#ifdef RT_STRICT
+/**
+ * Assert chain consistency.
+ */
+static bool rtFsFatChain_AssertValid(PCRTFSFATCHAIN pChain)
+{
+ bool fRc = true;
+ uint32_t cParts = 0;
+ PRTFSFATCHAINPART pPart;
+ RTListForEach(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry)
+ cParts++;
+
+ uint32_t cExpected = (pChain->cClusters + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES;
+ AssertMsgStmt(cExpected == cParts, ("cExpected=%#x cParts=%#x\n", cExpected, cParts), fRc = false);
+ AssertMsgStmt(pChain->cbChain == (pChain->cClusters << pChain->cClusterByteShift),
+ ("cExpected=%#x cParts=%#x\n", cExpected, cParts), fRc = false);
+ return fRc;
+}
+#endif /* RT_STRICT */
+
+
+/**
+ * Initializes an empty cluster chain.
+ *
+ * @param pChain The chain.
+ * @param pVol The volume.
+ */
+static void rtFsFatChain_InitEmpty(PRTFSFATCHAIN pChain, PRTFSFATVOL pVol)
+{
+ pChain->cbCluster = pVol->cbCluster;
+ pChain->cClusterByteShift = pVol->cClusterByteShift;
+ pChain->cbChain = 0;
+ pChain->cClusters = 0;
+ RTListInit(&pChain->ListParts);
+}
+
+
+/**
+ * Deletes a chain, freeing it's resources.
+ *
+ * @param pChain The chain.
+ */
+static void rtFsFatChain_Delete(PRTFSFATCHAIN pChain)
+{
+ Assert(RT_IS_POWER_OF_TWO(pChain->cbCluster));
+ Assert(RT_BIT_32(pChain->cClusterByteShift) == pChain->cbCluster);
+
+ PRTFSFATCHAINPART pPart = RTListRemoveLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ while (pPart)
+ {
+ RTMemFree(pPart);
+ pPart = RTListRemoveLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ }
+
+ pChain->cbChain = 0;
+ pChain->cClusters = 0;
+}
+
+
+/**
+ * Appends a cluster to a cluster chain.
+ *
+ * @returns IPRT status code.
+ * @param pChain The chain.
+ * @param idxCluster The cluster to append.
+ */
+static int rtFsFatChain_Append(PRTFSFATCHAIN pChain, uint32_t idxCluster)
+{
+ PRTFSFATCHAINPART pPart;
+ uint32_t idxLast = pChain->cClusters % RTFSFATCHAINPART_ENTRIES;
+ if (idxLast != 0)
+ pPart = RTListGetLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ else
+ {
+ pPart = (PRTFSFATCHAINPART)RTMemAllocZ(sizeof(*pPart));
+ if (!pPart)
+ return VERR_NO_MEMORY;
+ RTListAppend(&pChain->ListParts, &pPart->ListEntry);
+ }
+ pPart->aEntries[idxLast] = idxCluster;
+ pChain->cClusters++;
+ pChain->cbChain += pChain->cbCluster;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reduces the number of clusters in the chain to @a cClusters.
+ *
+ * @param pChain The chain.
+ * @param cClustersNew The new cluster count. Must be equal or smaller to
+ * the current number of clusters.
+ */
+static void rtFsFatChain_Shrink(PRTFSFATCHAIN pChain, uint32_t cClustersNew)
+{
+ uint32_t cOldParts = (pChain->cClusters + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES;
+ uint32_t cNewParts = (cClustersNew + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES;
+ Assert(cOldParts >= cNewParts);
+ while (cOldParts-- > cNewParts)
+ RTMemFree(RTListRemoveLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry));
+ pChain->cClusters = cClustersNew;
+ pChain->cbChain = cClustersNew << pChain->cClusterByteShift;
+ Assert(rtFsFatChain_AssertValid(pChain));
+}
+
+
+
+/**
+ * Converts a file offset to a disk offset.
+ *
+ * The disk offset is only valid until the end of the cluster it is within.
+ *
+ * @returns Disk offset. UINT64_MAX if invalid file offset.
+ * @param pChain The chain.
+ * @param offFile The file offset.
+ * @param pVol The volume.
+ */
+static uint64_t rtFsFatChain_FileOffsetToDiskOff(PCRTFSFATCHAIN pChain, uint32_t offFile, PCRTFSFATVOL pVol)
+{
+ uint32_t idxCluster = offFile >> pChain->cClusterByteShift;
+ if (idxCluster < pChain->cClusters)
+ {
+ PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ while (idxCluster >= RTFSFATCHAINPART_ENTRIES)
+ {
+ idxCluster -= RTFSFATCHAINPART_ENTRIES;
+ pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry);
+ }
+ return pVol->offFirstCluster
+ + ((uint64_t)(pPart->aEntries[idxCluster] - FAT_FIRST_DATA_CLUSTER) << pChain->cClusterByteShift)
+ + (offFile & (pChain->cbCluster - 1));
+ }
+ return UINT64_MAX;
+}
+
+
+/**
+ * Checks if the cluster chain is contiguous on the disk.
+ *
+ * @returns true / false.
+ * @param pChain The chain.
+ */
+static bool rtFsFatChain_IsContiguous(PCRTFSFATCHAIN pChain)
+{
+ if (pChain->cClusters <= 1)
+ return true;
+
+ PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ uint32_t idxNext = pPart->aEntries[0];
+ uint32_t cLeft = pChain->cClusters;
+ for (;;)
+ {
+ uint32_t const cInPart = RT_MIN(cLeft, RTFSFATCHAINPART_ENTRIES);
+ for (uint32_t iPart = 0; iPart < cInPart; iPart++)
+ if (pPart->aEntries[iPart] == idxNext)
+ idxNext++;
+ else
+ return false;
+ cLeft -= cInPart;
+ if (!cLeft)
+ return true;
+ pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry);
+ }
+}
+
+
+/**
+ * Gets a cluster array index.
+ *
+ * This works the chain thing as an indexed array.
+ *
+ * @returns The cluster number, UINT32_MAX if out of bounds.
+ * @param pChain The chain.
+ * @param idx The index.
+ */
+static uint32_t rtFsFatChain_GetClusterByIndex(PCRTFSFATCHAIN pChain, uint32_t idx)
+{
+ if (idx < pChain->cClusters)
+ {
+ /*
+ * In the first part?
+ */
+ PRTFSFATCHAINPART pPart;
+ if (idx < RTFSFATCHAINPART_ENTRIES)
+ {
+ pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ return pPart->aEntries[idx];
+ }
+
+ /*
+ * In the last part?
+ */
+ uint32_t cParts = (pChain->cClusters + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES;
+ uint32_t idxPart = idx / RTFSFATCHAINPART_ENTRIES;
+ uint32_t idxInPart = idx % RTFSFATCHAINPART_ENTRIES;
+ if (idxPart + 1 == cParts)
+ pPart = RTListGetLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ else
+ {
+ /*
+ * No, do linear search from the start, skipping the first part.
+ */
+ pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ while (idxPart-- > 0)
+ pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry);
+ }
+
+ return pPart->aEntries[idxInPart];
+ }
+ return UINT32_MAX;
+}
+
+
+/**
+ * Gets the first cluster.
+ *
+ * @returns The cluster number, UINT32_MAX if empty
+ * @param pChain The chain.
+ */
+static uint32_t rtFsFatChain_GetFirstCluster(PCRTFSFATCHAIN pChain)
+{
+ if (pChain->cClusters > 0)
+ {
+ PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ return pPart->aEntries[0];
+ }
+ return UINT32_MAX;
+}
+
+
+
+/**
+ * Gets the last cluster.
+ *
+ * @returns The cluster number, UINT32_MAX if empty
+ * @param pChain The chain.
+ */
+static uint32_t rtFsFatChain_GetLastCluster(PCRTFSFATCHAIN pChain)
+{
+ if (pChain->cClusters > 0)
+ {
+ PRTFSFATCHAINPART pPart = RTListGetLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
+ return pPart->aEntries[(pChain->cClusters - 1) % RTFSFATCHAINPART_ENTRIES];
+ }
+ return UINT32_MAX;
+}
+
+
+/**
+ * Creates a cache for the file allocation table (cluster map).
+ *
+ * @returns Pointer to the cache.
+ * @param pThis The FAT volume instance.
+ * @param pbFirst512FatBytes The first 512 bytes of the first FAT.
+ */
+static int rtFsFatClusterMap_Create(PRTFSFATVOL pThis, uint8_t const *pbFirst512FatBytes, PRTERRINFO pErrInfo)
+{
+ Assert(RT_ALIGN_32(pThis->cbFat, pThis->cbSector) == pThis->cbFat);
+ Assert(pThis->cbFat != 0);
+
+ /*
+ * Figure the cache size. Keeping it _very_ simple for now as we just need
+ * something that works, not anything the performs like crazy.
+ *
+ * Note! Lowering the max cache size below 128KB will break ASSUMPTIONS in the FAT16
+ * and eventually FAT12 code.
+ */
+ uint32_t cEntries;
+ uint32_t cEntryIndexShift;
+ uint32_t fEntryIndexMask;
+ uint32_t cbEntry = pThis->cbFat;
+ uint32_t fEntryOffsetMask;
+ if (cbEntry <= _512K)
+ {
+ cEntries = 1;
+ cEntryIndexShift = 0;
+ fEntryIndexMask = 0;
+ fEntryOffsetMask = UINT32_MAX;
+ }
+ else
+ {
+ Assert(pThis->cbSector < _512K / 8);
+ cEntries = 8;
+ cEntryIndexShift = 9;
+ fEntryIndexMask = cEntries - 1;
+ AssertReturn(RT_IS_POWER_OF_TWO(cEntries), VERR_INTERNAL_ERROR_4);
+
+ cbEntry = pThis->cbSector;
+ fEntryOffsetMask = pThis->cbSector - 1;
+ AssertReturn(RT_IS_POWER_OF_TWO(cbEntry), VERR_INTERNAL_ERROR_5);
+ }
+
+ /*
+ * Allocate and initialize it all.
+ */
+ PRTFSFATCLUSTERMAPCACHE pFatCache;
+ pFatCache = (PRTFSFATCLUSTERMAPCACHE)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFSFATCLUSTERMAPCACHE, aEntries[cEntries]));
+ pThis->pFatCache = pFatCache;
+ if (!pFatCache)
+ return RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "Failed to allocate FAT cache");
+ pFatCache->cEntries = cEntries;
+ pFatCache->fEntryIndexMask = fEntryIndexMask;
+ pFatCache->cEntryIndexShift = cEntryIndexShift;
+ pFatCache->cbEntry = cbEntry;
+ pFatCache->fEntryOffsetMask = fEntryOffsetMask;
+ pFatCache->pVol = pThis;
+ pFatCache->cbFat = pThis->cbFat;
+ pFatCache->cClusters = pThis->cClusters;
+
+ unsigned i = cEntries;
+ while (i-- > 0)
+ {
+ pFatCache->aEntries[i].pbData = (uint8_t *)RTMemAlloc(cbEntry);
+ if (pFatCache->aEntries[i].pbData == NULL)
+ {
+ for (i++; i < cEntries; i++)
+ RTMemFree(pFatCache->aEntries[i].pbData);
+ RTMemFree(pFatCache);
+ return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "Failed to allocate FAT cache entry (%#x bytes)", cbEntry);
+ }
+
+ pFatCache->aEntries[i].offFat = UINT32_MAX;
+ pFatCache->aEntries[i].bmDirty = 0;
+ }
+ Log3(("rtFsFatClusterMap_Create: cbFat=%#RX32 cEntries=%RU32 cEntryIndexShift=%RU32 fEntryIndexMask=%#RX32\n",
+ pFatCache->cbFat, pFatCache->cEntries, pFatCache->cEntryIndexShift, pFatCache->fEntryIndexMask));
+ Log3(("rtFsFatClusterMap_Create: cbEntries=%#RX32 fEntryOffsetMask=%#RX32\n", pFatCache->cbEntry, pFatCache->fEntryOffsetMask));
+
+ /*
+ * Calc the dirty shift factor.
+ */
+ cbEntry /= 64;
+ if (cbEntry < pThis->cbSector)
+ cbEntry = pThis->cbSector;
+
+ pFatCache->cDirtyShift = 1;
+ pFatCache->cbDirtyLine = 1;
+ while (pFatCache->cbDirtyLine < cbEntry)
+ {
+ pFatCache->cDirtyShift++;
+ pFatCache->cbDirtyLine <<= 1;
+ }
+ Assert(pFatCache->cEntries == 1 || pFatCache->cbDirtyLine == pThis->cbSector);
+ Log3(("rtFsFatClusterMap_Create: cbDirtyLine=%#RX32 cDirtyShift=%u\n", pFatCache->cbDirtyLine, pFatCache->cDirtyShift));
+
+ /*
+ * Fill the cache if single entry or entry size is 512.
+ */
+ if (pFatCache->cEntries == 1 || pFatCache->cbEntry == 512)
+ {
+ memcpy(pFatCache->aEntries[0].pbData, pbFirst512FatBytes, RT_MIN(512, pFatCache->cbEntry));
+ if (pFatCache->cbEntry > 512)
+ {
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->aoffFats[0] + 512,
+ &pFatCache->aEntries[0].pbData[512], pFatCache->cbEntry - 512, NULL);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSet(pErrInfo, rc, "Error reading FAT into memory");
+ }
+ pFatCache->aEntries[0].offFat = 0;
+ pFatCache->aEntries[0].bmDirty = 0;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_Flush and rtFsFatClusterMap_FlushEntry.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pThis The FAT volume instance.
+ * @param iFirstEntry Entry to start flushing at.
+ * @param iLastEntry Last entry to flush.
+ */
+static int rtFsFatClusterMap_FlushWorker(PRTFSFATVOL pThis, uint32_t const iFirstEntry, uint32_t const iLastEntry)
+{
+ PRTFSFATCLUSTERMAPCACHE pFatCache = pThis->pFatCache;
+ Log3(("rtFsFatClusterMap_FlushWorker: %p %#x %#x\n", pThis, iFirstEntry, iLastEntry));
+
+ /*
+ * Walk the cache entries, accumulating segments to flush.
+ */
+ int rc = VINF_SUCCESS;
+ uint64_t off = UINT64_MAX;
+ uint64_t offEdge = UINT64_MAX;
+ RTSGSEG aSgSegs[8];
+ RT_ZERO(aSgSegs); /* Initialization required for GCC >= 11. */
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, aSgSegs, RT_ELEMENTS(aSgSegs));
+ SgBuf.cSegs = 0; /** @todo RTSgBuf API is stupid, make it smarter. */
+
+ for (uint32_t iFatCopy = 0; iFatCopy < pThis->cFats; iFatCopy++)
+ {
+ for (uint32_t iEntry = iFirstEntry; iEntry <= iLastEntry; iEntry++)
+ {
+ uint64_t bmDirty = pFatCache->aEntries[iEntry].bmDirty;
+ if ( bmDirty != 0
+ && pFatCache->aEntries[iEntry].offFat != UINT32_MAX)
+ {
+ uint32_t offEntry = 0;
+ uint64_t iDirtyLine = 1;
+ while (offEntry < pFatCache->cbEntry)
+ {
+ if (pFatCache->aEntries[iEntry].bmDirty & iDirtyLine)
+ {
+ /*
+ * Found dirty cache line.
+ */
+ uint64_t offDirtyLine = pThis->aoffFats[iFatCopy] + pFatCache->aEntries[iEntry].offFat + offEntry;
+
+ /* Can we simply extend the last segment? */
+ if ( offDirtyLine == offEdge
+ && offEntry)
+ {
+ Assert(SgBuf.cSegs > 0);
+ Assert( (uintptr_t)aSgSegs[SgBuf.cSegs - 1].pvSeg + aSgSegs[SgBuf.cSegs - 1].cbSeg
+ == (uintptr_t)&pFatCache->aEntries[iEntry].pbData[offEntry]);
+ aSgSegs[SgBuf.cSegs - 1].cbSeg += pFatCache->cbDirtyLine;
+ offEdge += pFatCache->cbDirtyLine;
+ }
+ else
+ {
+ /* Starting new job? */
+ if (off == UINT64_MAX)
+ {
+ off = offDirtyLine;
+ Assert(SgBuf.cSegs == 0);
+ }
+ /* flush if not adjacent or if we're out of segments. */
+ else if ( offDirtyLine != offEdge
+ || SgBuf.cSegs >= RT_ELEMENTS(aSgSegs))
+ {
+ int rc2 = RTVfsFileSgWrite(pThis->hVfsBacking, off, &SgBuf, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ RTSgBufReset(&SgBuf);
+ SgBuf.cSegs = 0;
+ off = offDirtyLine;
+ }
+
+ /* Append segment. */
+ aSgSegs[SgBuf.cSegs].cbSeg = pFatCache->cbDirtyLine;
+ aSgSegs[SgBuf.cSegs].pvSeg = &pFatCache->aEntries[iEntry].pbData[offEntry];
+ SgBuf.cSegs++;
+ offEdge = offDirtyLine + pFatCache->cbDirtyLine;
+ }
+
+ bmDirty &= ~iDirtyLine;
+ if (!bmDirty)
+ break;
+ }
+ iDirtyLine <<= 1;
+ offEntry += pFatCache->cbDirtyLine;
+ }
+ Assert(!bmDirty);
+ }
+ }
+ }
+
+ /*
+ * Final flush job.
+ */
+ if (SgBuf.cSegs > 0)
+ {
+ int rc2 = RTVfsFileSgWrite(pThis->hVfsBacking, off, &SgBuf, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+ /*
+ * Clear the dirty flags on success.
+ */
+ if (RT_SUCCESS(rc))
+ for (uint32_t iEntry = iFirstEntry; iEntry <= iLastEntry; iEntry++)
+ pFatCache->aEntries[iEntry].bmDirty = 0;
+
+ return rc;
+}
+
+
+/**
+ * Flushes out all dirty lines in the entire file allocation table cache.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pThis The FAT volume instance.
+ */
+static int rtFsFatClusterMap_Flush(PRTFSFATVOL pThis)
+{
+ return rtFsFatClusterMap_FlushWorker(pThis, 0, pThis->pFatCache->cEntries - 1);
+}
+
+
+/**
+ * Flushes out all dirty lines in the file allocation table (cluster map) cache
+ * entry.
+ *
+ * This is typically called prior to reusing the cache entry.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pFatCache The FAT cache
+ * @param iEntry The cache entry to flush.
+ */
+static int rtFsFatClusterMap_FlushEntry(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t iEntry)
+{
+ return rtFsFatClusterMap_FlushWorker(pFatCache->pVol, iEntry, iEntry);
+}
+
+
+/**
+ * Gets a pointer to a FAT entry.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pFatCache The FAT cache.
+ * @param offFat The FAT byte offset to get the entry off.
+ * @param ppbEntry Where to return the pointer to the entry.
+ */
+static int rtFsFatClusterMap_GetEntry(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t offFat, uint8_t **ppbEntry)
+{
+ int rc;
+ if (offFat < pFatCache->cbFat)
+ {
+ uint32_t const iEntry = (offFat >> pFatCache->cEntryIndexShift) & pFatCache->fEntryIndexMask;
+ uint32_t const offInEntry = offFat & pFatCache->fEntryOffsetMask;
+ uint32_t const offFatEntry = offFat - offInEntry;
+
+ *ppbEntry = pFatCache->aEntries[iEntry].pbData + offInEntry;
+
+ /* If it's already ready, return immediately. */
+ if (pFatCache->aEntries[iEntry].offFat == offFatEntry)
+ {
+ Log3(("rtFsFatClusterMap_GetEntry: Hit entry %u for offFat=%#RX32\n", iEntry, offFat));
+ return VINF_SUCCESS;
+ }
+
+ /* Do we need to flush it? */
+ rc = VINF_SUCCESS;
+ if ( pFatCache->aEntries[iEntry].bmDirty != 0
+ && pFatCache->aEntries[iEntry].offFat != UINT32_MAX)
+ {
+ Log3(("rtFsFatClusterMap_GetEntry: Flushing entry %u for offFat=%#RX32\n", iEntry, offFat));
+ rc = rtFsFatClusterMap_FlushEntry(pFatCache, iEntry);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pFatCache->aEntries[iEntry].bmDirty = 0;
+
+ /* Read in the entry from disk */
+ rc = RTVfsFileReadAt(pFatCache->pVol->hVfsBacking, pFatCache->pVol->aoffFats[0] + offFatEntry,
+ pFatCache->aEntries[iEntry].pbData, pFatCache->cbEntry, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("rtFsFatClusterMap_GetEntry: Loaded entry %u for offFat=%#RX32\n", iEntry, offFat));
+ pFatCache->aEntries[iEntry].offFat = offFatEntry;
+ return VINF_SUCCESS;
+ }
+ /** @todo We can try other FAT copies here... */
+ LogRel(("rtFsFatClusterMap_GetEntry: Error loading entry %u for offFat=%#RX32 (%#64RX32 LB %#x): %Rrc\n",
+ iEntry, offFat, pFatCache->pVol->aoffFats[0] + offFatEntry, pFatCache->cbEntry, rc));
+ pFatCache->aEntries[iEntry].offFat = UINT32_MAX;
+ }
+ }
+ else
+ rc = VERR_OUT_OF_RANGE;
+ *ppbEntry = NULL;
+ return rc;
+}
+
+
+/**
+ * Gets a pointer to a FAT entry, extended version.
+ *
+ * @returns IPRT status code. On failure, we're currently kind of screwed.
+ * @param pFatCache The FAT cache.
+ * @param offFat The FAT byte offset to get the entry off.
+ * @param ppbEntry Where to return the pointer to the entry.
+ * @param pidxEntry Where to return the entry index.
+ */
+static int rtFsFatClusterMap_GetEntryEx(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t offFat,
+ uint8_t **ppbEntry, uint32_t *pidxEntry)
+{
+ int rc;
+ if (offFat < pFatCache->cbFat)
+ {
+ uint32_t const iEntry = (offFat >> pFatCache->cEntryIndexShift) & pFatCache->fEntryIndexMask;
+ uint32_t const offInEntry = offFat & pFatCache->fEntryOffsetMask;
+ uint32_t const offFatEntry = offFat - offInEntry;
+
+ *ppbEntry = pFatCache->aEntries[iEntry].pbData + offInEntry;
+ *pidxEntry = iEntry;
+
+ /* If it's already ready, return immediately. */
+ if (pFatCache->aEntries[iEntry].offFat == offFatEntry)
+ {
+ Log3(("rtFsFatClusterMap_GetEntryEx: Hit entry %u for offFat=%#RX32\n", iEntry, offFat));
+ return VINF_SUCCESS;
+ }
+
+ /* Do we need to flush it? */
+ rc = VINF_SUCCESS;
+ if ( pFatCache->aEntries[iEntry].bmDirty != 0
+ && pFatCache->aEntries[iEntry].offFat != UINT32_MAX)
+ {
+ Log3(("rtFsFatClusterMap_GetEntryEx: Flushing entry %u for offFat=%#RX32\n", iEntry, offFat));
+ rc = rtFsFatClusterMap_FlushEntry(pFatCache, iEntry);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pFatCache->aEntries[iEntry].bmDirty = 0;
+
+ /* Read in the entry from disk */
+ rc = RTVfsFileReadAt(pFatCache->pVol->hVfsBacking, pFatCache->pVol->aoffFats[0] + offFatEntry,
+ pFatCache->aEntries[iEntry].pbData, pFatCache->cbEntry, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("rtFsFatClusterMap_GetEntryEx: Loaded entry %u for offFat=%#RX32\n", iEntry, offFat));
+ pFatCache->aEntries[iEntry].offFat = offFatEntry;
+ return VINF_SUCCESS;
+ }
+ /** @todo We can try other FAT copies here... */
+ LogRel(("rtFsFatClusterMap_GetEntryEx: Error loading entry %u for offFat=%#RX32 (%#64RX32 LB %#x): %Rrc\n",
+ iEntry, offFat, pFatCache->pVol->aoffFats[0] + offFatEntry, pFatCache->cbEntry, rc));
+ pFatCache->aEntries[iEntry].offFat = UINT32_MAX;
+ }
+ }
+ else
+ rc = VERR_OUT_OF_RANGE;
+ *ppbEntry = NULL;
+ *pidxEntry = UINT32_MAX;
+ return rc;
+}
+
+
+/**
+ * Destroys the file allcation table cache, first flushing any dirty lines.
+ *
+ * @returns IRPT status code from flush (we've destroyed it regardless of the
+ * status code).
+ * @param pThis The FAT volume instance which cluster map shall be
+ * destroyed.
+ */
+static int rtFsFatClusterMap_Destroy(PRTFSFATVOL pThis)
+{
+ int rc = VINF_SUCCESS;
+ PRTFSFATCLUSTERMAPCACHE pFatCache = pThis->pFatCache;
+ if (pFatCache)
+ {
+ /* flush stuff. */
+ rc = rtFsFatClusterMap_Flush(pThis);
+
+ /* free everything. */
+ uint32_t i = pFatCache->cEntries;
+ while (i-- > 0)
+ {
+ RTMemFree(pFatCache->aEntries[i].pbData);
+ pFatCache->aEntries[i].pbData = NULL;
+ }
+ pFatCache->cEntries = 0;
+ RTMemFree(pFatCache);
+
+ pThis->pFatCache = NULL;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_ReadClusterChain handling FAT12.
+ */
+static int rtFsFatClusterMap_Fat12_ReadClusterChain(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, PRTFSFATCHAIN pChain)
+{
+ /* ASSUME that for FAT12 we cache the whole FAT in a single entry. That
+ way we don't need to deal with entries in different sectors and whatnot. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+
+ /* Special case for empty files. */
+ if (idxCluster == 0)
+ return VINF_SUCCESS;
+
+ /* Work cluster by cluster. */
+ uint8_t const *pbFat = pFatCache->aEntries[0].pbData;
+ for (;;)
+ {
+ /* Validate the cluster, checking for end of file. */
+ if ((uint32_t)(idxCluster - FAT_FIRST_DATA_CLUSTER) >= pFatCache->cClusters)
+ {
+ if (idxCluster >= FAT_FIRST_FAT12_EOC)
+ return VINF_SUCCESS;
+ Log(("Fat/ReadChain12: bogus cluster %#x vs %#x total\n", idxCluster, pFatCache->cClusters));
+ return VERR_VFS_BOGUS_OFFSET;
+ }
+
+ /* Add cluster to chain. */
+ int rc = rtFsFatChain_Append(pChain, idxCluster);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Next cluster. */
+#ifdef LOG_ENABLED
+ const uint32_t idxPrevCluster = idxCluster;
+#endif
+ bool fOdd = idxCluster & 1;
+ uint32_t offFat = idxCluster * 3 / 2;
+ idxCluster = RT_MAKE_U16(pbFat[offFat], pbFat[offFat + 1]);
+ if (fOdd)
+ idxCluster >>= 4;
+ else
+ idxCluster &= 0x0fff;
+ Log4(("Fat/ReadChain12: [%#x] %#x (next: %#x)\n", pChain->cClusters - 1, idxPrevCluster, idxCluster));
+ }
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_ReadClusterChain handling FAT16.
+ */
+static int rtFsFatClusterMap_Fat16_ReadClusterChain(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, PRTFSFATCHAIN pChain)
+{
+ /* ASSUME that for FAT16 we cache the whole FAT in a single entry. That
+ way we don't need to deal with entries in different sectors and whatnot. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+
+ /* Special case for empty files. */
+ if (idxCluster == 0)
+ return VINF_SUCCESS;
+
+ /* Work cluster by cluster. */
+ uint8_t const *pbFat = pFatCache->aEntries[0].pbData;
+ for (;;)
+ {
+ /* Validate the cluster, checking for end of file. */
+ if ((uint32_t)(idxCluster - FAT_FIRST_DATA_CLUSTER) >= pFatCache->cClusters)
+ {
+ if (idxCluster >= FAT_FIRST_FAT16_EOC)
+ return VINF_SUCCESS;
+ Log(("Fat/ReadChain16: bogus cluster %#x vs %#x total\n", idxCluster, pFatCache->cClusters));
+ return VERR_VFS_BOGUS_OFFSET;
+ }
+
+ /* Add cluster to chain. */
+ int rc = rtFsFatChain_Append(pChain, idxCluster);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Next cluster. */
+ idxCluster = RT_MAKE_U16(pbFat[idxCluster * 2], pbFat[idxCluster * 2 + 1]);
+ }
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_ReadClusterChain handling FAT32.
+ */
+static int rtFsFatClusterMap_Fat32_ReadClusterChain(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, PRTFSFATCHAIN pChain)
+{
+ /* Special case for empty files. */
+ if (idxCluster == 0)
+ return VINF_SUCCESS;
+
+ /* Work cluster by cluster. */
+ for (;;)
+ {
+ /* Validate the cluster, checking for end of file. */
+ if ((uint32_t)(idxCluster - FAT_FIRST_DATA_CLUSTER) >= pFatCache->cClusters)
+ {
+ if (idxCluster >= FAT_FIRST_FAT32_EOC)
+ return VINF_SUCCESS;
+ Log(("Fat/ReadChain32: bogus cluster %#x vs %#x total\n", idxCluster, pFatCache->cClusters));
+ return VERR_VFS_BOGUS_OFFSET;
+ }
+
+ /* Add cluster to chain. */
+ int rc = rtFsFatChain_Append(pChain, idxCluster);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Get the next cluster. */
+ uint8_t *pbEntry;
+ rc = rtFsFatClusterMap_GetEntry(pFatCache, idxCluster * 4, &pbEntry);
+ if (RT_SUCCESS(rc))
+ idxCluster = RT_MAKE_U32_FROM_U8(pbEntry[0], pbEntry[1], pbEntry[2], pbEntry[3]);
+ else
+ return rc;
+ }
+}
+
+
+/**
+ * Reads a cluster chain into memory
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param idxFirstCluster The first cluster.
+ * @param pChain The chain element to read into (and thereby
+ * initialize).
+ */
+static int rtFsFatClusterMap_ReadClusterChain(PRTFSFATVOL pThis, uint32_t idxFirstCluster, PRTFSFATCHAIN pChain)
+{
+ pChain->cbCluster = pThis->cbCluster;
+ pChain->cClusterByteShift = pThis->cClusterByteShift;
+ pChain->cClusters = 0;
+ pChain->cbChain = 0;
+ RTListInit(&pChain->ListParts);
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_Fat12_ReadClusterChain(pThis->pFatCache, idxFirstCluster, pChain);
+ case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_Fat16_ReadClusterChain(pThis->pFatCache, idxFirstCluster, pChain);
+ case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_Fat32_ReadClusterChain(pThis->pFatCache, idxFirstCluster, pChain);
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2);
+ }
+}
+
+
+/**
+ * Sets bmDirty for entry @a iEntry.
+ *
+ * @param pFatCache The FAT cache.
+ * @param iEntry The cache entry.
+ * @param offEntry The offset into the cache entry that was dirtied.
+ */
+DECLINLINE(void) rtFsFatClusterMap_SetDirtyByte(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t iEntry, uint32_t offEntry)
+{
+ uint8_t iLine = offEntry / pFatCache->cbDirtyLine;
+ pFatCache->aEntries[iEntry].bmDirty |= RT_BIT_64(iLine);
+}
+
+/**
+ * Sets bmDirty for entry @a iEntry.
+ *
+ * @param pFatCache The FAT cache.
+ * @param iEntry The cache entry.
+ * @param pbIntoEntry Pointer into the cache entry that was dirtied.
+ */
+DECLINLINE(void) rtFsFatClusterMap_SetDirtyByteByPtr(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t iEntry, uint8_t *pbIntoEntry)
+{
+ uintptr_t offEntry = pbIntoEntry - pFatCache->aEntries[iEntry].pbData;
+ Assert(offEntry < pFatCache->cbEntry);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, iEntry, (uint32_t)offEntry);
+}
+
+
+/** Sets a FAT12 cluster value. */
+static int rtFsFatClusterMap_SetCluster12(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, uint32_t uValue)
+{
+ /* ASSUME that for FAT12 we cache the whole FAT in a single entry. That
+ way we don't need to deal with entries in different sectors and whatnot. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+ AssertReturn(uValue < 0x1000, VERR_INTERNAL_ERROR_2);
+
+ /* Make the change. */
+ uint8_t *pbFat = pFatCache->aEntries[0].pbData;
+ uint32_t offFat = idxCluster * 3 / 2;
+ if (idxCluster & 1)
+ {
+ Log3(("Fat/SetCluster12: [%#x]: %#x -> %#x\n", idxCluster, (((pbFat[offFat]) & 0xf0) >> 4) | ((unsigned)pbFat[offFat + 1] << 4), uValue));
+ pbFat[offFat] = ((uint8_t)0x0f & pbFat[offFat]) | ((uint8_t)uValue << 4);
+ pbFat[offFat + 1] = (uint8_t)(uValue >> 4);
+ }
+ else
+ {
+ Log3(("Fat/SetCluster12: [%#x]: %#x -> %#x\n", idxCluster, pbFat[offFat] | ((pbFat[offFat + 1] & 0x0f) << 8), uValue));
+ pbFat[offFat] = (uint8_t)uValue;
+ pbFat[offFat + 1] = ((uint8_t)0xf0 & pbFat[offFat + 1]) | (uint8_t)(uValue >> 8);
+ }
+
+ /* Update the dirty bits. */
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat + 1);
+
+ return VINF_SUCCESS;
+}
+
+
+/** Sets a FAT16 cluster value. */
+static int rtFsFatClusterMap_SetCluster16(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, uint32_t uValue)
+{
+ /* ASSUME that for FAT16 we cache the whole FAT in a single entry. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+ AssertReturn(uValue < 0x10000, VERR_INTERNAL_ERROR_2);
+
+ /* Make the change. */
+ uint8_t *pbFat = pFatCache->aEntries[0].pbData;
+ uint32_t offFat = idxCluster * 2;
+ pbFat[offFat] = (uint8_t)idxCluster;
+ pbFat[offFat + 1] = (uint8_t)(idxCluster >> 8);
+
+ /* Update the dirty bits. */
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat);
+
+ return VINF_SUCCESS;
+}
+
+
+/** Sets a FAT32 cluster value. */
+static int rtFsFatClusterMap_SetCluster32(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, uint32_t uValue)
+{
+ AssertReturn(uValue < 0x10000000, VERR_INTERNAL_ERROR_2);
+
+ /* Get the fat cache entry. */
+ uint8_t *pbEntry;
+ uint32_t idxEntry;
+ int rc = rtFsFatClusterMap_GetEntryEx(pFatCache, idxCluster * 4, &pbEntry, &idxEntry);
+ if (RT_SUCCESS(rc))
+ {
+ /* Make the change. */
+ pbEntry[0] = (uint8_t)idxCluster;
+ pbEntry[1] = (uint8_t)(idxCluster >> 8);
+ pbEntry[2] = (uint8_t)(idxCluster >> 16);
+ pbEntry[3] = (uint8_t)(idxCluster >> 24);
+
+ /* Update the dirty bits. */
+ rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Marks the cluster @a idxCluster as the end of the cluster chain.
+ *
+ * @returns IPRT status code
+ * @param pThis The FAT volume instance.
+ * @param idxCluster The cluster to end the chain with.
+ */
+static int rtFsFatClusterMap_SetEndOfChain(PRTFSFATVOL pThis, uint32_t idxCluster)
+{
+ AssertReturn(idxCluster >= FAT_FIRST_DATA_CLUSTER, VERR_VFS_BOGUS_OFFSET);
+ AssertMsgReturn(idxCluster < pThis->cClusters, ("idxCluster=%#x cClusters=%#x\n", idxCluster, pThis->cClusters),
+ VERR_VFS_BOGUS_OFFSET);
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_SetCluster12(pThis->pFatCache, idxCluster, FAT_FIRST_FAT12_EOC);
+ case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_SetCluster16(pThis->pFatCache, idxCluster, FAT_FIRST_FAT16_EOC);
+ case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_SetCluster32(pThis->pFatCache, idxCluster, FAT_FIRST_FAT32_EOC);
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+}
+
+
+/**
+ * Marks the cluster @a idxCluster as free.
+ * @returns IPRT status code
+ * @param pThis The FAT volume instance.
+ * @param idxCluster The cluster to free.
+ */
+static int rtFsFatClusterMap_FreeCluster(PRTFSFATVOL pThis, uint32_t idxCluster)
+{
+ AssertReturn(idxCluster >= FAT_FIRST_DATA_CLUSTER, VERR_VFS_BOGUS_OFFSET);
+ AssertReturn(idxCluster < pThis->cClusters, VERR_VFS_BOGUS_OFFSET);
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_SetCluster12(pThis->pFatCache, idxCluster, 0);
+ case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_SetCluster16(pThis->pFatCache, idxCluster, 0);
+ case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_SetCluster32(pThis->pFatCache, idxCluster, 0);
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_AllocateCluster that handles FAT12.
+ */
+static int rtFsFatClusterMap_AllocateCluster12(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxPrevCluster, uint32_t *pidxCluster)
+{
+ /* ASSUME that for FAT12 we cache the whole FAT in a single entry. That
+ way we don't need to deal with entries in different sectors and whatnot. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Check that the previous cluster is a valid chain end.
+ */
+ uint8_t *pbFat = pFatCache->aEntries[0].pbData;
+ uint32_t offFatPrev;
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ offFatPrev = idxPrevCluster * 3 / 2;
+ AssertReturn(offFatPrev + 1 < pFatCache->cbFat, VERR_INTERNAL_ERROR_3);
+ uint32_t idxPrevValue;
+ if (idxPrevCluster & 1)
+ idxPrevValue = (pbFat[offFatPrev] >> 4) | ((uint32_t)pbFat[offFatPrev + 1] << 4);
+ else
+ idxPrevValue = pbFat[offFatPrev] | ((uint32_t)(pbFat[offFatPrev + 1] & 0x0f) << 8);
+ AssertReturn(idxPrevValue >= FAT_FIRST_FAT12_EOC, VERR_VFS_BOGUS_OFFSET);
+ }
+ else
+ offFatPrev = UINT32_MAX;
+
+ /*
+ * Search cluster by cluster from the start (it's small, so easy trumps
+ * complicated optimizations).
+ */
+ uint32_t idxCluster = FAT_FIRST_DATA_CLUSTER;
+ uint32_t offFat = 3;
+ while (idxCluster < pFatCache->cClusters)
+ {
+ if (idxCluster & 1)
+ {
+ if ( (pbFat[offFat] & 0xf0) != 0
+ || pbFat[offFat + 1] != 0)
+ {
+ offFat += 2;
+ idxCluster++;
+ continue;
+ }
+
+ /* Set EOC. */
+ pbFat[offFat] |= 0xf0;
+ pbFat[offFat + 1] = 0xff;
+ }
+ else
+ {
+ if ( pbFat[offFat]
+ || pbFat[offFat + 1] & 0x0f)
+ {
+ offFat += 1;
+ idxCluster++;
+ continue;
+ }
+
+ /* Set EOC. */
+ pbFat[offFat] = 0xff;
+ pbFat[offFat + 1] |= 0x0f;
+ }
+
+ /* Update the dirty bits. */
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat + 1);
+
+ /* Chain it onto the previous cluster. */
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ if (idxPrevCluster & 1)
+ {
+ pbFat[offFatPrev] = (pbFat[offFatPrev] & (uint8_t)0x0f) | (uint8_t)(idxCluster << 4);
+ pbFat[offFatPrev + 1] = (uint8_t)(idxCluster >> 4);
+ }
+ else
+ {
+ pbFat[offFatPrev] = (uint8_t)idxCluster;
+ pbFat[offFatPrev + 1] = (pbFat[offFatPrev + 1] & (uint8_t)0xf0) | ((uint8_t)(idxCluster >> 8) & (uint8_t)0x0f);
+ }
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFatPrev);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFatPrev + 1);
+ }
+
+ *pidxCluster = idxCluster;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_AllocateCluster that handles FAT16.
+ */
+static int rtFsFatClusterMap_AllocateCluster16(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxPrevCluster, uint32_t *pidxCluster)
+{
+ /* ASSUME that for FAT16 we cache the whole FAT in a single entry. */
+ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Check that the previous cluster is a valid chain end.
+ */
+ uint8_t *pbFat = pFatCache->aEntries[0].pbData;
+ uint32_t offFatPrev;
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ offFatPrev = idxPrevCluster * 2;
+ AssertReturn(offFatPrev + 1 < pFatCache->cbFat, VERR_INTERNAL_ERROR_3);
+ uint32_t idxPrevValue = RT_MAKE_U16(pbFat[offFatPrev], pbFat[offFatPrev + 1]);
+ AssertReturn(idxPrevValue >= FAT_FIRST_FAT16_EOC, VERR_VFS_BOGUS_OFFSET);
+ }
+ else
+ offFatPrev = UINT32_MAX;
+
+ /*
+ * We start searching at idxAllocHint and continues to the end. The next
+ * iteration starts searching from the start and up to idxAllocHint.
+ */
+ uint32_t idxCluster = RT_MIN(pFatCache->idxAllocHint, FAT_FIRST_DATA_CLUSTER);
+ uint32_t offFat = idxCluster * 2;
+ uint32_t cClusters = pFatCache->cClusters;
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ while (idxCluster < cClusters)
+ {
+ if ( pbFat[offFat + 0] != 0x00
+ || pbFat[offFat + 1] != 0x00)
+ {
+ /* In use - advance to the next one. */
+ offFat += 2;
+ idxCluster++;
+ }
+ else
+ {
+ /*
+ * Found one. Grab it.
+ */
+ /* Set EOC. */
+ pbFat[offFat + 0] = 0xff;
+ pbFat[offFat + 1] = 0xff;
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat);
+
+ /* Chain it onto the previous cluster (if any). */
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ pbFat[offFatPrev + 0] = (uint8_t)idxCluster;
+ pbFat[offFatPrev + 1] = (uint8_t)(idxCluster >> 8);
+ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFatPrev);
+ }
+
+ /* Update the allocation hint. */
+ pFatCache->idxAllocHint = idxCluster + 1;
+
+ /* Done. */
+ *pidxCluster = idxCluster;
+ return VINF_SUCCESS;
+ }
+ }
+
+ /* Wrap around to the start of the map. */
+ cClusters = RT_MIN(pFatCache->idxAllocHint, pFatCache->cClusters);
+ idxCluster = FAT_FIRST_DATA_CLUSTER;
+ offFat = 4;
+ }
+
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Worker for rtFsFatClusterMap_AllocateCluster that handles FAT32.
+ */
+static int rtFsFatClusterMap_AllocateCluster32(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxPrevCluster, uint32_t *pidxCluster)
+{
+ /*
+ * Check that the previous cluster is a valid chain end.
+ */
+ int rc;
+ uint8_t *pbEntry;
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ rc = rtFsFatClusterMap_GetEntry(pFatCache, idxPrevCluster * 4, &pbEntry);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxPrevValue = RT_MAKE_U32_FROM_U8(pbEntry[0], pbEntry[1], pbEntry[2], pbEntry[3]);
+ AssertReturn(idxPrevValue >= FAT_FIRST_FAT32_EOC, VERR_VFS_BOGUS_OFFSET);
+ }
+ else
+ return rc;
+ }
+
+ /*
+ * We start searching at idxAllocHint and continues to the end. The next
+ * iteration starts searching from the start and up to idxAllocHint.
+ */
+ uint32_t idxCluster = RT_MIN(pFatCache->idxAllocHint, FAT_FIRST_DATA_CLUSTER);
+ uint32_t offFat = idxCluster * 4;
+ uint32_t cClusters = pFatCache->cClusters;
+ for (uint32_t i = 0; i < 2; i++)
+ {
+ while (idxCluster < cClusters)
+ {
+ /* Note! This could be done in cache entry chunks. */
+ uint32_t idxEntry;
+ rc = rtFsFatClusterMap_GetEntryEx(pFatCache, offFat, &pbEntry, &idxEntry);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pbEntry[0] != 0x00
+ || pbEntry[1] != 0x00
+ || pbEntry[2] != 0x00
+ || pbEntry[3] != 0x00)
+ {
+ /* In use - advance to the next one. */
+ offFat += 4;
+ idxCluster++;
+ }
+ else
+ {
+ /*
+ * Found one. Grab it.
+ */
+ /* Set EOC. */
+ pbEntry[0] = 0xff;
+ pbEntry[1] = 0xff;
+ pbEntry[2] = 0xff;
+ pbEntry[3] = 0x0f;
+ rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry);
+
+ /* Chain it on the previous cluster (if any). */
+ if (idxPrevCluster != UINT32_MAX)
+ {
+ rc = rtFsFatClusterMap_GetEntryEx(pFatCache, idxPrevCluster * 4, &pbEntry, &idxEntry);
+ if (RT_SUCCESS(rc))
+ {
+ pbEntry[0] = (uint8_t)idxCluster;
+ pbEntry[1] = (uint8_t)(idxCluster >> 8);
+ pbEntry[2] = (uint8_t)(idxCluster >> 16);
+ pbEntry[3] = (uint8_t)(idxCluster >> 24);
+ rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry);
+ }
+ else
+ {
+ /* Try free the cluster. */
+ int rc2 = rtFsFatClusterMap_GetEntryEx(pFatCache, offFat, &pbEntry, &idxEntry);
+ if (RT_SUCCESS(rc2))
+ {
+ pbEntry[0] = 0;
+ pbEntry[1] = 0;
+ pbEntry[2] = 0;
+ pbEntry[3] = 0;
+ rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry);
+ }
+ return rc;
+ }
+ }
+
+ /* Update the allocation hint. */
+ pFatCache->idxAllocHint = idxCluster + 1;
+
+ /* Done. */
+ *pidxCluster = idxCluster;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+
+ /* Wrap around to the start of the map. */
+ cClusters = RT_MIN(pFatCache->idxAllocHint, pFatCache->cClusters);
+ idxCluster = FAT_FIRST_DATA_CLUSTER;
+ offFat = 4;
+ }
+
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Allocates a cluster an appends it to the chain given by @a idxPrevCluster.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_DISK_FULL if no more available clusters.
+ * @param pThis The FAT volume instance.
+ * @param idxPrevCluster The previous cluster, UINT32_MAX if first.
+ * @param pidxCluster Where to return the cluster number on success.
+ */
+static int rtFsFatClusterMap_AllocateCluster(PRTFSFATVOL pThis, uint32_t idxPrevCluster, uint32_t *pidxCluster)
+{
+ AssertReturn(idxPrevCluster == UINT32_MAX || (idxPrevCluster >= FAT_FIRST_DATA_CLUSTER && idxPrevCluster < pThis->cClusters),
+ VERR_INTERNAL_ERROR_5);
+ *pidxCluster = UINT32_MAX;
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_AllocateCluster12(pThis->pFatCache, idxPrevCluster, pidxCluster);
+ case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_AllocateCluster16(pThis->pFatCache, idxPrevCluster, pidxCluster);
+ case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_AllocateCluster32(pThis->pFatCache, idxPrevCluster, pidxCluster);
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+}
+
+
+/**
+ * Allocates clusters.
+ *
+ * Will free the clusters if it fails to allocate all of them.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param pChain The chain.
+ * @param cClusters Number of clusters to add to the chain.
+ */
+static int rtFsFatClusterMap_AllocateMoreClusters(PRTFSFATVOL pThis, PRTFSFATCHAIN pChain, uint32_t cClusters)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t const cOldClustersInChain = pChain->cClusters;
+ uint32_t const idxOldLastCluster = rtFsFatChain_GetLastCluster(pChain);
+ uint32_t idxPrevCluster = idxOldLastCluster;
+ uint32_t iCluster = 0;
+ while (iCluster < cClusters)
+ {
+ uint32_t idxCluster;
+ rc = rtFsFatClusterMap_AllocateCluster(pThis, idxPrevCluster, &idxCluster);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsFatChain_Append(pChain, idxCluster);
+ if (RT_SUCCESS(rc))
+ {
+ /* next */
+ iCluster++;
+ continue;
+ }
+
+ /* Bail out, freeing any clusters we've managed to allocate by now. */
+ rtFsFatClusterMap_FreeCluster(pThis, idxCluster);
+ }
+ if (idxOldLastCluster != UINT32_MAX)
+ rtFsFatClusterMap_SetEndOfChain(pThis, idxOldLastCluster);
+ while (iCluster-- > 0)
+ rtFsFatClusterMap_FreeCluster(pThis, rtFsFatChain_GetClusterByIndex(pChain, cOldClustersInChain + iCluster));
+ rtFsFatChain_Shrink(pChain, iCluster);
+ break;
+ }
+ return rc;
+}
+
+
+
+/**
+ * Converts a FAT timestamp into an IPRT timesspec.
+ *
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param uDate The date part of the FAT timestamp.
+ * @param uTime The time part of the FAT timestamp.
+ * @param cCentiseconds Centiseconds part if applicable (0 otherwise).
+ * @param pVol The volume.
+ */
+static void rtFsFatDateTime2TimeSpec(PRTTIMESPEC pTimeSpec, uint16_t uDate, uint16_t uTime,
+ uint8_t cCentiseconds, PCRTFSFATVOL pVol)
+{
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = 1980 + (uDate >> 9);
+ Time.u8Month = RT_MAX((uDate >> 5) & 0xf, 1);
+ Time.u8MonthDay = RT_MAX(uDate & 0x1f, 1);
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = uTime >> 11;
+ Time.u8Minute = (uTime >> 5) & 0x3f;
+ Time.u8Second = (uTime & 0x1f) << 1;
+ Time.u32Nanosecond = 0;
+ if (cCentiseconds > 0 && cCentiseconds < 200) /* screw complicated stuff for now. */
+ {
+ if (cCentiseconds >= 100)
+ {
+ cCentiseconds -= 100;
+ Time.u8Second++;
+ }
+ Time.u32Nanosecond = cCentiseconds * UINT64_C(100000000);
+ }
+
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+ RTTimeSpecSubNano(pTimeSpec, pVol->offNanoUTC);
+}
+
+
+/**
+ * Converts an IPRT timespec to a FAT timestamp.
+ *
+ * @returns The centiseconds part.
+ * @param pVol The volume.
+ * @param pTimeSpec The IPRT timespec to convert (UTC).
+ * @param puDate Where to return the date part of the FAT timestamp.
+ * @param puTime Where to return the time part of the FAT timestamp.
+ */
+static uint8_t rtFsFatTimeSpec2FatDateTime(PCRTFSFATVOL pVol, PCRTTIMESPEC pTimeSpec, uint16_t *puDate, uint16_t *puTime)
+{
+ RTTIMESPEC TimeSpec = *pTimeSpec;
+ RTTIME Time;
+ RTTimeExplode(&Time, RTTimeSpecSubNano(&TimeSpec, pVol->offNanoUTC));
+
+ if (puDate)
+ *puDate = ((uint16_t)(RT_MAX(Time.i32Year, 1980) - 1980) << 9)
+ | (Time.u8Month << 5)
+ | Time.u8MonthDay;
+ if (puTime)
+ *puTime = ((uint16_t)Time.u8Hour << 11)
+ | (Time.u8Minute << 5)
+ | (Time.u8Second >> 1);
+ return (Time.u8Second & 1) * 100 + Time.u32Nanosecond / 10000000;
+
+}
+
+
+/**
+ * Gets the current FAT timestamp.
+ *
+ * @returns The centiseconds part.
+ * @param pVol The volume.
+ * @param puDate Where to return the date part of the FAT timestamp.
+ * @param puTime Where to return the time part of the FAT timestamp.
+ */
+static uint8_t rtFsFatCurrentFatDateTime(PCRTFSFATVOL pVol, uint16_t *puDate, uint16_t *puTime)
+{
+ RTTIMESPEC TimeSpec;
+ return rtFsFatTimeSpec2FatDateTime(pVol, RTTimeNow(&TimeSpec), puDate, puTime);
+}
+
+
+/**
+ * Initialization of a RTFSFATOBJ structure from a FAT directory entry.
+ *
+ * @note The RTFSFATOBJ::pParentDir and RTFSFATOBJ::Clusters members are
+ * properly initialized elsewhere.
+ *
+ * @param pObj The structure to initialize.
+ * @param pDirEntry The directory entry.
+ * @param offEntryInDir The offset in the parent directory.
+ * @param pVol The volume.
+ */
+static void rtFsFatObj_InitFromDirEntry(PRTFSFATOBJ pObj, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir, PRTFSFATVOL pVol)
+{
+ RTListInit(&pObj->Entry);
+ pObj->cRefs = 1;
+ pObj->pParentDir = NULL;
+ pObj->pVol = pVol;
+ pObj->offEntryInDir = offEntryInDir;
+ pObj->fAttrib = ((RTFMODE)pDirEntry->fAttrib << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_OS2;
+ pObj->fAttrib = rtFsModeFromDos(pObj->fAttrib, (char *)&pDirEntry->achName[0], sizeof(pDirEntry->achName), 0, 0);
+ pObj->cbObject = pDirEntry->cbFile;
+ pObj->fMaybeDirtyFat = false;
+ pObj->fMaybeDirtyDirEnt = false;
+ rtFsFatDateTime2TimeSpec(&pObj->ModificationTime, pDirEntry->uModifyDate, pDirEntry->uModifyTime, 0, pVol);
+ rtFsFatDateTime2TimeSpec(&pObj->BirthTime, pDirEntry->uBirthDate, pDirEntry->uBirthTime, pDirEntry->uBirthCentiseconds, pVol);
+ rtFsFatDateTime2TimeSpec(&pObj->AccessTime, pDirEntry->uAccessDate, 0, 0, pVol);
+}
+
+
+/**
+ * Dummy initialization of a RTFSFATOBJ structure.
+ *
+ * @note The RTFSFATOBJ::pParentDir and RTFSFATOBJ::Clusters members are
+ * properly initialized elsewhere.
+ *
+ * @param pObj The structure to initialize.
+ * @param cbObject The object size.
+ * @param fAttrib The attributes.
+ * @param pVol The volume.
+ */
+static void rtFsFatObj_InitDummy(PRTFSFATOBJ pObj, uint32_t cbObject, RTFMODE fAttrib, PRTFSFATVOL pVol)
+{
+ RTListInit(&pObj->Entry);
+ pObj->cRefs = 1;
+ pObj->pParentDir = NULL;
+ pObj->pVol = pVol;
+ pObj->offEntryInDir = UINT32_MAX;
+ pObj->fAttrib = fAttrib;
+ pObj->cbObject = cbObject;
+ pObj->fMaybeDirtyFat = false;
+ pObj->fMaybeDirtyDirEnt = false;
+ RTTimeSpecSetDosSeconds(&pObj->AccessTime, 0);
+ RTTimeSpecSetDosSeconds(&pObj->ModificationTime, 0);
+ RTTimeSpecSetDosSeconds(&pObj->BirthTime, 0);
+}
+
+
+/**
+ * Flushes FAT object meta data.
+ *
+ * @returns IPRT status code
+ * @param pObj The common object structure.
+ */
+static int rtFsFatObj_FlushMetaData(PRTFSFATOBJ pObj)
+{
+ int rc = VINF_SUCCESS;
+ if (pObj->fMaybeDirtyFat)
+ {
+ rc = rtFsFatClusterMap_Flush(pObj->pVol);
+ if (RT_SUCCESS(rc))
+ pObj->fMaybeDirtyFat = false;
+ }
+ if (pObj->fMaybeDirtyDirEnt)
+ {
+ int rc2 = rtFsFatDirShrd_Flush(pObj->pParentDir);
+ if (RT_SUCCESS(rc2))
+ pObj->fMaybeDirtyDirEnt = false;
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Worker for rtFsFatFile_Close and rtFsFatDir_Close that does common work.
+ *
+ * @returns IPRT status code.
+ * @param pObj The common object structure.
+ */
+static int rtFsFatObj_Close(PRTFSFATOBJ pObj)
+{
+ int rc = rtFsFatObj_FlushMetaData(pObj);
+ if (pObj->pParentDir)
+ rtFsFatDirShrd_RemoveOpenChild(pObj->pParentDir, pObj);
+ rtFsFatChain_Delete(&pObj->Clusters);
+ return rc;
+}
+
+
+/**
+ * Worker for rtFsFatFile_QueryInfo and rtFsFatDir_QueryInfo
+ */
+static int rtFsFatObj_QueryInfo(PRTFSFATOBJ pThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ LogFlow(("rtFsFatObj_QueryInfo: %p fMode=%#x\n", pThis, pThis->fAttrib));
+
+ pObjInfo->cbObject = pThis->cbObject;
+ pObjInfo->cbAllocated = pThis->Clusters.cbChain;
+ pObjInfo->AccessTime = pThis->AccessTime;
+ pObjInfo->ModificationTime = pThis->ModificationTime;
+ pObjInfo->ChangeTime = pThis->ModificationTime;
+ pObjInfo->BirthTime = pThis->BirthTime;
+ pObjInfo->Attr.fMode = pThis->fAttrib;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING: RT_FALL_THRU();
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
+ pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = 0; /* Could probably use the directory entry offset. */
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = 0;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = 0;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsFatFile_SetMode and rtFsFatDir_SetMode.
+ */
+static int rtFsFatObj_SetMode(PRTFSFATOBJ pThis, RTFMODE fMode, RTFMODE fMask)
+{
+#if 0
+ if (fMask != ~RTFS_TYPE_MASK)
+ {
+ fMode |= ~fMask & ObjInfo.Attr.fMode;
+ }
+#else
+ RT_NOREF(pThis, fMode, fMask);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+/**
+ * Worker for rtFsFatFile_SetTimes and rtFsFatDir_SetTimes.
+ */
+static int rtFsFatObj_SetTimes(PRTFSFATOBJ pThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+#if 0
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+#else
+ RT_NOREF(pThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Close(void *pvThis)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ LogFlow(("rtFsFatFile_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+
+ int rc = VINF_SUCCESS;
+ if (pShared)
+ {
+ if (ASMAtomicDecU32(&pShared->Core.cRefs) == 0)
+ {
+ LogFlow(("rtFsFatFile_Close: Destroying shared structure %p\n", pShared));
+ rc = rtFsFatObj_Close(&pShared->Core);
+ RTMemFree(pShared);
+ }
+ else
+ rc = rtFsFatObj_FlushMetaData(&pShared->Core);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsFatFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ return rtFsFatObj_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ AssertReturn(pSgBuf->cSegs != 0, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ /*
+ * Check for EOF.
+ */
+ if (off == -1)
+ off = pThis->offFile;
+ if ((uint64_t)off >= pShared->Core.cbObject)
+ {
+ if (pcbRead)
+ {
+ *pcbRead = 0;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+
+ /*
+ * Do the reading cluster by cluster.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cbFileLeft = pShared->Core.cbObject - (uint32_t)off;
+ uint32_t cbRead = 0;
+ size_t cbLeft = pSgBuf->paSegs[0].cbSeg;
+ uint8_t *pbDst = (uint8_t *)pSgBuf->paSegs[0].pvSeg;
+ while (cbLeft > 0)
+ {
+ if (cbFileLeft > 0)
+ {
+ uint64_t offDisk = rtFsFatChain_FileOffsetToDiskOff(&pShared->Core.Clusters, (uint32_t)off, pShared->Core.pVol);
+ if (offDisk != UINT64_MAX)
+ {
+ uint32_t cbToRead = pShared->Core.Clusters.cbCluster - ((uint32_t)off & (pShared->Core.Clusters.cbCluster - 1));
+ if (cbToRead > cbLeft)
+ cbToRead = (uint32_t)cbLeft;
+ if (cbToRead > cbFileLeft)
+ cbToRead = cbFileLeft;
+ rc = RTVfsFileReadAt(pShared->Core.pVol->hVfsBacking, offDisk, pbDst, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ off += cbToRead;
+ pbDst += cbToRead;
+ cbRead += cbToRead;
+ cbFileLeft -= cbToRead;
+ cbLeft -= cbToRead;
+ continue;
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_OFFSET;
+ }
+ else
+ rc = pcbRead ? VINF_EOF : VERR_EOF;
+ break;
+ }
+
+ /* Update the offset and return. */
+ pThis->offFile = off;
+ if (pcbRead)
+ *pcbRead = cbRead;
+ return rc;
+}
+
+
+/**
+ * Changes the size of a file or directory FAT object.
+ *
+ * @returns IPRT status code
+ * @param pObj The common object.
+ * @param cbFile The new file size.
+ */
+static int rtFsFatObj_SetSize(PRTFSFATOBJ pObj, uint32_t cbFile)
+{
+ AssertReturn( ((pObj->cbObject + pObj->Clusters.cbCluster - 1) >> pObj->Clusters.cClusterByteShift)
+ == pObj->Clusters.cClusters, VERR_INTERNAL_ERROR_3);
+
+ /*
+ * Do nothing if the size didn't change.
+ */
+ if (pObj->cbObject == cbFile)
+ return VINF_SUCCESS;
+
+ /*
+ * Do we need to allocate or free clusters?
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t const cClustersNew = (cbFile + pObj->Clusters.cbCluster - 1) >> pObj->Clusters.cClusterByteShift;
+ AssertReturn(pObj->pParentDir, VERR_INTERNAL_ERROR_2);
+ if (pObj->Clusters.cClusters == cClustersNew)
+ { /* likely when writing small bits at a time. */ }
+ else if (pObj->Clusters.cClusters < cClustersNew)
+ {
+ /* Allocate and append new clusters. */
+ do
+ {
+ uint32_t idxCluster;
+ rc = rtFsFatClusterMap_AllocateCluster(pObj->pVol, rtFsFatChain_GetLastCluster(&pObj->Clusters), &idxCluster);
+ if (RT_SUCCESS(rc))
+ rc = rtFsFatChain_Append(&pObj->Clusters, idxCluster);
+ } while (pObj->Clusters.cClusters < cClustersNew && RT_SUCCESS(rc));
+ pObj->fMaybeDirtyFat = true;
+ }
+ else
+ {
+ /* Free clusters we don't need any more. */
+ if (cClustersNew > 0)
+ rc = rtFsFatClusterMap_SetEndOfChain(pObj->pVol, rtFsFatChain_GetClusterByIndex(&pObj->Clusters, cClustersNew - 1));
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t iClusterToFree = cClustersNew;
+ while (iClusterToFree < pObj->Clusters.cClusters && RT_SUCCESS(rc))
+ {
+ rc = rtFsFatClusterMap_FreeCluster(pObj->pVol, rtFsFatChain_GetClusterByIndex(&pObj->Clusters, iClusterToFree));
+ iClusterToFree++;
+ }
+
+ rtFsFatChain_Shrink(&pObj->Clusters, cClustersNew);
+ }
+ pObj->fMaybeDirtyFat = true;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Update the object size, since we've got the right number of clusters backing it now.
+ */
+ pObj->cbObject = cbFile;
+
+ /*
+ * Update the directory entry.
+ */
+ uint32_t uWriteLock;
+ PFATDIRENTRY pDirEntry;
+ rc = rtFsFatDirShrd_GetEntryForUpdate(pObj->pParentDir, pObj->offEntryInDir, &pDirEntry, &uWriteLock);
+ if (RT_SUCCESS(rc))
+ {
+ pDirEntry->cbFile = cbFile;
+ uint32_t idxFirstCluster;
+ if (cClustersNew == 0)
+ idxFirstCluster = 0; /** @todo figure out if setting the cluster to 0 is the right way to deal with empty files... */
+ else
+ idxFirstCluster = rtFsFatChain_GetFirstCluster(&pObj->Clusters);
+ pDirEntry->idxCluster = (uint16_t)idxFirstCluster;
+ if (pObj->pVol->enmFatType >= RTFSFATTYPE_FAT32)
+ pDirEntry->u.idxClusterHigh = (uint16_t)(idxFirstCluster >> 16);
+
+ rc = rtFsFatDirShrd_PutEntryAfterUpdate(pObj->pParentDir, pDirEntry, uWriteLock);
+ pObj->fMaybeDirtyDirEnt = true;
+ }
+ }
+ Log3(("rtFsFatObj_SetSize: Returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ PRTFSFATVOL pVol = pShared->Core.pVol;
+ AssertReturn(pSgBuf->cSegs != 0, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (pVol->fReadOnly)
+ return VERR_WRITE_PROTECT;
+
+ if (off == -1)
+ off = pThis->offFile;
+
+ /*
+ * Do the reading cluster by cluster.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t cbWritten = 0;
+ size_t cbLeft = pSgBuf->paSegs[0].cbSeg;
+ uint8_t const *pbSrc = (uint8_t const *)pSgBuf->paSegs[0].pvSeg;
+ while (cbLeft > 0)
+ {
+ /* Figure out how much we can write. Checking for max file size and such. */
+ uint32_t cbToWrite = pShared->Core.Clusters.cbCluster - ((uint32_t)off & (pShared->Core.Clusters.cbCluster - 1));
+ if (cbToWrite > cbLeft)
+ cbToWrite = (uint32_t)cbLeft;
+ uint64_t offNew = (uint64_t)off + cbToWrite;
+ if (offNew < _4G)
+ { /*likely*/ }
+ else if ((uint64_t)off < _4G - 1U)
+ cbToWrite = _4G - 1U - off;
+ else
+ {
+ rc = VERR_FILE_TOO_BIG;
+ break;
+ }
+
+ /* Grow the file? */
+ if ((uint32_t)offNew > pShared->Core.cbObject)
+ {
+ rc = rtFsFatObj_SetSize(&pShared->Core, (uint32_t)offNew);
+ if (RT_SUCCESS(rc))
+ { /* likely */}
+ else
+ break;
+ }
+
+ /* Figure the disk offset. */
+ uint64_t offDisk = rtFsFatChain_FileOffsetToDiskOff(&pShared->Core.Clusters, (uint32_t)off, pVol);
+ if (offDisk != UINT64_MAX)
+ {
+ rc = RTVfsFileWriteAt(pVol->hVfsBacking, offDisk, pbSrc, cbToWrite, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ off += cbToWrite;
+ pbSrc += cbToWrite;
+ cbWritten += cbToWrite;
+ cbLeft -= cbToWrite;
+ }
+ else
+ break;
+ }
+ else
+ {
+ rc = VERR_VFS_BOGUS_OFFSET;
+ break;
+ }
+ }
+
+ /* Update the offset and return. */
+ pThis->offFile = off;
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Flush(void *pvThis)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ int rc1 = rtFsFatObj_FlushMetaData(&pShared->Core);
+ int rc2 = RTVfsFileFlush(pShared->Core.pVol->hVfsBacking);
+ return RT_FAILURE(rc1) ? rc1 : rc2;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtFsFatFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ NOREF(pvThis);
+ int rc;
+ if (fEvents != RTPOLL_EVT_ERROR)
+ {
+ *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR;
+ rc = VINF_SUCCESS;
+ }
+ else if (fIntr)
+ rc = RTThreadSleep(cMillies);
+ else
+ {
+ uint64_t uMsStart = RTTimeMilliTS();
+ do
+ rc = RTThreadSleep(cMillies);
+ while ( rc == VERR_INTERRUPTED
+ && !fIntr
+ && RTTimeMilliTS() - uMsStart < cMillies);
+ if (rc == VERR_INTERRUPTED)
+ rc = VERR_TIMEOUT;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsFatFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ return rtFsFatObj_SetMode(&pThis->pShared->Core, fMode, fMask);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsFatFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ return rtFsFatObj_SetTimes(&pThis->pShared->Core, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsFatFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsFatFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = (RTFOFF)pShared->Core.cbObject + offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNew = (RTFOFF)pThis->offFile + offSeek;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ if (offNew >= 0)
+ {
+ if (offNew <= _4G)
+ {
+ pThis->offFile = offNew;
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+ }
+ return VERR_OUT_OF_RANGE;
+ }
+ return VERR_NEGATIVE_SEEK;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtFsFatFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ *pcbFile = pShared->Core.cbObject;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtFsFatFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
+ PRTFSFATFILESHRD pShared = pThis->pShared;
+ AssertReturn(!fFlags, VERR_NOT_SUPPORTED);
+ if (cbFile > UINT32_MAX)
+ return VERR_FILE_TOO_BIG;
+ return rtFsFatObj_SetSize(&pShared->Core, (uint32_t)cbFile);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtFsFatFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = UINT32_MAX;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * FAT file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtFsFatFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "FatFile",
+ rtFsFatFile_Close,
+ rtFsFatFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsFatFile_Read,
+ rtFsFatFile_Write,
+ rtFsFatFile_Flush,
+ rtFsFatFile_PollOne,
+ rtFsFatFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtFsFatFile_SetMode,
+ rtFsFatFile_SetTimes,
+ rtFsFatFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsFatFile_Seek,
+ rtFsFatFile_QuerySize,
+ rtFsFatFile_SetSize,
+ rtFsFatFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Instantiates a new file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param pParentDir The parent directory.
+ * @param pDirEntry The parent directory entry.
+ * @param offEntryInDir The byte offset of the directory entry in the parent
+ * directory.
+ * @param fOpen RTFILE_O_XXX flags.
+ * @param phVfsFile Where to return the file handle.
+ */
+static int rtFsFatFile_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir,
+ uint64_t fOpen, PRTVFSFILE phVfsFile)
+{
+ AssertPtr(pParentDir);
+ Assert(!(offEntryInDir & (sizeof(FATDIRENTRY) - 1)));
+
+ PRTFSFATFILE pNewFile;
+ int rc = RTVfsNewFile(&g_rtFsFatFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ pNewFile->offFile = 0;
+ pNewFile->pShared = NULL;
+
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSFATFILESHRD pShared = (PRTFSFATFILESHRD)rtFsFatDirShrd_LookupShared(pParentDir, offEntryInDir);
+ if (pShared)
+ {
+ LogFlow(("rtFsFatFile_New: cbObject=%#RX32 \n", pShared->Core.cbObject));
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+
+ pShared = (PRTFSFATFILESHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rtFsFatObj_InitFromDirEntry(&pShared->Core, pDirEntry, offEntryInDir, pThis);
+ pNewFile->pShared = pShared;
+
+ rc = rtFsFatClusterMap_ReadClusterChain(pThis, RTFSFAT_GET_CLUSTER(pDirEntry, pThis), &pShared->Core.Clusters);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Link into parent directory so we can use it to update
+ * our directory entry.
+ */
+ rtFsFatDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+
+ /*
+ * Should we truncate the file or anything of that sort?
+ */
+ if ( (fOpen & RTFILE_O_TRUNCATE)
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ {
+ Log3(("rtFsFatFile_New: calling rtFsFatObj_SetSize to zap the file size.\n"));
+ rc = rtFsFatObj_SetSize(&pShared->Core, 0);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("rtFsFatFile_New: cbObject=%#RX32 pShared=%p\n", pShared->Core.cbObject, pShared));
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /* Destroy the file object. */
+ RTVfsFileRelease(*phVfsFile);
+ }
+ *phVfsFile = NIL_RTVFSFILE;
+ return rc;
+}
+
+
+/**
+ * Looks up the shared structure for a child.
+ *
+ * @returns Referenced pointer to the shared structure, NULL if not found.
+ * @param pThis The directory.
+ * @param offEntryInDir The directory record offset of the child.
+ */
+static PRTFSFATOBJ rtFsFatDirShrd_LookupShared(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir)
+{
+ PRTFSFATOBJ pCur;
+ RTListForEach(&pThis->OpenChildren, pCur, RTFSFATOBJ, Entry)
+ {
+ if (pCur->offEntryInDir == offEntryInDir)
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs);
+ Assert(cRefs > 1); RT_NOREF(cRefs);
+ return pCur;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Flush directory changes when having a fully buffered directory.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ */
+static int rtFsFatDirShrd_FlushFullyBuffered(PRTFSFATDIRSHRD pThis)
+{
+ Assert(pThis->fFullyBuffered);
+ uint32_t const cbSector = pThis->Core.pVol->cbSector;
+ RTVFSFILE const hVfsBacking = pThis->Core.pVol->hVfsBacking;
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < pThis->u.Full.cSectors; i++)
+ if (ASMBitTest(pThis->u.Full.pbDirtySectors, i))
+ {
+ int rc2 = RTVfsFileWriteAt(hVfsBacking, pThis->offEntriesOnDisk + i * cbSector,
+ (uint8_t *)pThis->paEntries + i * cbSector, cbSector, NULL);
+ if (RT_SUCCESS(rc2))
+ ASMBitClear(pThis->u.Full.pbDirtySectors, i);
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+/**
+ * Flush directory changes when using simple buffering.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ */
+static int rtFsFatDirShrd_FlushSimple(PRTFSFATDIRSHRD pThis)
+{
+ Assert(!pThis->fFullyBuffered);
+ int rc;
+ if ( !pThis->u.Simple.fDirty
+ || pThis->offEntriesOnDisk != UINT64_MAX)
+ rc = VINF_SUCCESS;
+ else
+ {
+ Assert(pThis->u.Simple.offInDir != UINT32_MAX);
+ rc = RTVfsFileWriteAt(pThis->Core.pVol->hVfsBacking, pThis->offEntriesOnDisk,
+ pThis->paEntries, pThis->Core.pVol->cbSector, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->u.Simple.fDirty = false;
+ }
+ return rc;
+}
+
+
+/**
+ * Flush directory changes.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ */
+static int rtFsFatDirShrd_Flush(PRTFSFATDIRSHRD pThis)
+{
+ if (pThis->fFullyBuffered)
+ return rtFsFatDirShrd_FlushFullyBuffered(pThis);
+ return rtFsFatDirShrd_FlushSimple(pThis);
+}
+
+
+/**
+ * Gets one or more entires at @a offEntryInDir.
+ *
+ * Common worker for rtFsFatDirShrd_GetEntriesAt and rtFsFatDirShrd_GetEntryForUpdate
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param offEntryInDir The directory offset in bytes.
+ * @param fForUpdate Whether it's for updating.
+ * @param ppaEntries Where to return pointer to the entry at
+ * @a offEntryInDir.
+ * @param pcEntries Where to return the number of entries
+ * @a *ppaEntries points to.
+ * @param puBufferReadLock Where to return the buffer read lock handle.
+ * Call rtFsFatDirShrd_ReleaseBufferAfterReading when
+ * done.
+ */
+static int rtFsFatDirShrd_GetEntriesAtCommon(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir, bool fForUpdate,
+ PFATDIRENTRYUNION *ppaEntries, uint32_t *pcEntries, uint32_t *puLock)
+{
+ *puLock = UINT32_MAX;
+
+ int rc;
+ Assert(RT_ALIGN_32(offEntryInDir, sizeof(FATDIRENTRY)) == offEntryInDir);
+ Assert(pThis->Core.cbObject / sizeof(FATDIRENTRY) == pThis->cEntries);
+ uint32_t const idxEntryInDir = offEntryInDir / sizeof(FATDIRENTRY);
+ if (idxEntryInDir < pThis->cEntries)
+ {
+ if (pThis->fFullyBuffered)
+ {
+ /*
+ * Fully buffered: Return pointer to all the entires starting at offEntryInDir.
+ */
+ *ppaEntries = &pThis->paEntries[idxEntryInDir];
+ *pcEntries = pThis->cEntries - idxEntryInDir;
+ *puLock = !fForUpdate ? 1 : UINT32_C(0x80000001);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /*
+ * Simple buffering: If hit, return the number of entries.
+ */
+ PRTFSFATVOL pVol = pThis->Core.pVol;
+ uint32_t off = offEntryInDir - pThis->u.Simple.offInDir;
+ if (off < pVol->cbSector)
+ {
+ *ppaEntries = &pThis->paEntries[off / sizeof(FATDIRENTRY)];
+ *pcEntries = (pVol->cbSector - off) / sizeof(FATDIRENTRY);
+ *puLock = !fForUpdate ? 1 : UINT32_C(0x80000001);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /*
+ * Simple buffering: Miss.
+ * Flush dirty. Read in new sector. Return entries in sector starting
+ * at offEntryInDir.
+ */
+ if (!pThis->u.Simple.fDirty)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtFsFatDirShrd_FlushSimple(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ off = offEntryInDir & (pVol->cbSector - 1);
+ pThis->u.Simple.offInDir = (offEntryInDir & ~(pVol->cbSector - 1));
+ pThis->offEntriesOnDisk = rtFsFatChain_FileOffsetToDiskOff(&pThis->Core.Clusters, pThis->u.Simple.offInDir,
+ pThis->Core.pVol);
+ rc = RTVfsFileReadAt(pThis->Core.pVol->hVfsBacking, pThis->offEntriesOnDisk,
+ pThis->paEntries, pVol->cbSector, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ *ppaEntries = &pThis->paEntries[off / sizeof(FATDIRENTRY)];
+ *pcEntries = (pVol->cbSector - off) / sizeof(FATDIRENTRY);
+ *puLock = !fForUpdate ? 1 : UINT32_C(0x80000001);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ pThis->u.Simple.offInDir = UINT32_MAX;
+ pThis->offEntriesOnDisk = UINT64_MAX;
+ }
+ }
+ }
+ }
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+ return rc;
+}
+
+
+/**
+ * Puts back a directory entry after updating it, releasing the write lock and
+ * marking it dirty.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ * @param pDirEntry The directory entry.
+ * @param uWriteLock The write lock.
+ */
+static int rtFsFatDirShrd_PutEntryAfterUpdate(PRTFSFATDIRSHRD pThis, PFATDIRENTRY pDirEntry, uint32_t uWriteLock)
+{
+ Assert(uWriteLock == UINT32_C(0x80000001));
+ RT_NOREF(uWriteLock);
+ if (pThis->fFullyBuffered)
+ {
+ uint32_t idxSector = ((uintptr_t)pDirEntry - (uintptr_t)pThis->paEntries) / pThis->Core.pVol->cbSector;
+ ASMBitSet(pThis->u.Full.pbDirtySectors, idxSector);
+ }
+ else
+ pThis->u.Simple.fDirty = true;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the pointer to the given directory entry for the purpose of updating it.
+ *
+ * Call rtFsFatDirShrd_PutEntryAfterUpdate afterwards.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param offEntryInDir The byte offset of the directory entry, within the
+ * directory.
+ * @param ppDirEntry Where to return the pointer to the directory entry.
+ * @param puWriteLock Where to return the write lock.
+ */
+static int rtFsFatDirShrd_GetEntryForUpdate(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir, PFATDIRENTRY *ppDirEntry,
+ uint32_t *puWriteLock)
+{
+ uint32_t cEntriesIgn;
+ return rtFsFatDirShrd_GetEntriesAtCommon(pThis, offEntryInDir, true /*fForUpdate*/, (PFATDIRENTRYUNION *)ppDirEntry,
+ &cEntriesIgn, puWriteLock);
+}
+
+
+/**
+ * Release a directory buffer after done reading from it.
+ *
+ * This is currently just a placeholder.
+ *
+ * @param pThis The directory.
+ * @param uBufferReadLock The buffer lock.
+ */
+static void rtFsFatDirShrd_ReleaseBufferAfterReading(PRTFSFATDIRSHRD pThis, uint32_t uBufferReadLock)
+{
+ RT_NOREF(pThis, uBufferReadLock);
+ Assert(uBufferReadLock == 1);
+}
+
+
+/**
+ * Gets one or more entires at @a offEntryInDir.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param offEntryInDir The directory offset in bytes.
+ * @param ppaEntries Where to return pointer to the entry at
+ * @a offEntryInDir.
+ * @param pcEntries Where to return the number of entries
+ * @a *ppaEntries points to.
+ * @param puBufferReadLock Where to return the buffer read lock handle.
+ * Call rtFsFatDirShrd_ReleaseBufferAfterReading when
+ * done.
+ */
+static int rtFsFatDirShrd_GetEntriesAt(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir,
+ PCFATDIRENTRYUNION *ppaEntries, uint32_t *pcEntries, uint32_t *puBufferReadLock)
+{
+ return rtFsFatDirShrd_GetEntriesAtCommon(pThis, offEntryInDir, false /*fForUpdate*/, (PFATDIRENTRYUNION *)ppaEntries,
+ pcEntries, puBufferReadLock);
+}
+
+
+/**
+ * Translates a unicode codepoint to an uppercased CP437 index.
+ *
+ * @returns CP437 index if valie, UINT16_MAX if not.
+ * @param uc The codepoint to convert.
+ */
+static uint16_t rtFsFatUnicodeCodepointToUpperCodepage(RTUNICP uc)
+{
+ /*
+ * The first 128 chars have 1:1 translation for valid FAT chars.
+ */
+ if (uc < 128)
+ {
+ if (g_awchFatCp437ValidChars[uc] == uc)
+ return (uint16_t)uc;
+ if (RT_C_IS_LOWER(uc))
+ return uc - 0x20;
+ return UINT16_MAX;
+ }
+
+ /*
+ * Try for uppercased, settle for lower case if no upper case variant in the table.
+ * This is really expensive, btw.
+ */
+ RTUNICP ucUpper = RTUniCpToUpper(uc);
+ for (unsigned i = 128; i < 256; i++)
+ if (g_awchFatCp437ValidChars[i] == ucUpper)
+ return i;
+ if (ucUpper != uc)
+ for (unsigned i = 128; i < 256; i++)
+ if (g_awchFatCp437ValidChars[i] == uc)
+ return i;
+ return UINT16_MAX;
+}
+
+
+/**
+ * Convert filename string to 8-dot-3 format, doing necessary ASCII uppercasing
+ * and such.
+ *
+ * @returns true if 8.3 formattable name, false if not.
+ * @param pszName8Dot3 Where to return the 8-dot-3 name when returning
+ * @c true. Filled with zero on false. 8+3+1 bytes.
+ * @param pszName The filename to convert.
+ */
+static bool rtFsFatDir_StringTo8Dot3(char *pszName8Dot3, const char *pszName)
+{
+ /*
+ * Don't try convert names with more than 12 unicode chars in them.
+ */
+ size_t const cucName = RTStrUniLen(pszName);
+ if (cucName <= 12 && cucName > 0)
+ {
+ /*
+ * Recode the input string as CP437, uppercasing it, validating the
+ * name, formatting it as a FAT directory entry string.
+ */
+ size_t offDst = 0;
+ bool fExt = false;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszName, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc)
+ {
+ if (offDst < 8+3)
+ {
+ uint16_t idxCp = rtFsFatUnicodeCodepointToUpperCodepage(uc);
+ if (idxCp != UINT16_MAX)
+ {
+ pszName8Dot3[offDst++] = (char)idxCp;
+ Assert(uc != '.');
+ continue;
+ }
+
+ /* Maybe the dot? */
+ if ( uc == '.'
+ && !fExt
+ && offDst <= 8)
+ {
+ fExt = true;
+ while (offDst < 8)
+ pszName8Dot3[offDst++] = ' ';
+ continue;
+ }
+ }
+ }
+ /* String terminator: Check length, pad and convert 0xe5. */
+ else if (offDst <= (size_t)(fExt ? 8 + 3 : 8))
+ {
+ while (offDst < 8 + 3)
+ pszName8Dot3[offDst++] = ' ';
+ Assert(offDst == 8 + 3);
+ pszName8Dot3[offDst] = '\0';
+
+ if ((uint8_t)pszName8Dot3[0] == FATDIRENTRY_CH0_DELETED)
+ pszName8Dot3[0] = FATDIRENTRY_CH0_ESC_E5;
+ return true;
+ }
+ }
+ /* invalid */
+ break;
+ }
+ }
+ memset(&pszName8Dot3[0], 0, 8+3+1);
+ return false;
+}
+
+
+/**
+ * Calculates the checksum of a directory entry.
+ * @returns Checksum.
+ * @param pDirEntry The directory entry to checksum.
+ */
+static uint8_t rtFsFatDir_CalcChecksum(PCFATDIRENTRY pDirEntry)
+{
+ uint8_t bChecksum = pDirEntry->achName[0];
+ for (uint8_t off = 1; off < RT_ELEMENTS(pDirEntry->achName); off++)
+ {
+ bChecksum = RTFSFAT_ROT_R1_U8(bChecksum);
+ bChecksum += pDirEntry->achName[off];
+ }
+ return bChecksum;
+}
+
+
+/**
+ * Locates a directory entry in a directory.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_FILE_NOT_FOUND if not found.
+ * @param pThis The directory to search.
+ * @param pszEntry The entry to look for.
+ * @param poffEntryInDir Where to return the offset of the directory
+ * entry.
+ * @param pfLong Where to return long name indicator.
+ * @param pDirEntry Where to return a copy of the directory entry.
+ */
+static int rtFsFatDirShrd_FindEntry(PRTFSFATDIRSHRD pThis, const char *pszEntry, uint32_t *poffEntryInDir, bool *pfLong,
+ PFATDIRENTRY pDirEntry)
+{
+ /* Set return values. */
+ *pfLong = false;
+ *poffEntryInDir = UINT32_MAX;
+
+ /*
+ * Turn pszEntry into a 8.3 filename, if possible.
+ */
+ char szName8Dot3[8+3+1];
+ bool fIs8Dot3Name = rtFsFatDir_StringTo8Dot3(szName8Dot3, pszEntry);
+
+ /*
+ * Scan the directory buffer by buffer.
+ */
+ RTUTF16 wszName[260+1];
+ uint8_t bChecksum = UINT8_MAX;
+ uint8_t idNextSlot = UINT8_MAX;
+ size_t cwcName = 0;
+ uint32_t offEntryInDir = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ Assert(RT_ALIGN_32(cbDir, sizeof(*pDirEntry)) == cbDir);
+ AssertCompile(FATDIRNAMESLOT_MAX_SLOTS * FATDIRNAMESLOT_CHARS_PER_SLOT < RT_ELEMENTS(wszName));
+ wszName[260] = '\0';
+
+ while (offEntryInDir < cbDir)
+ {
+ /* Get chunk of entries starting at offEntryInDir. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pThis, offEntryInDir, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Now work thru each of the entries.
+ */
+ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY))
+ {
+ switch ((uint8_t)paEntries[iEntry].Entry.achName[0])
+ {
+ default:
+ break;
+ case FATDIRENTRY_CH0_DELETED:
+ cwcName = 0;
+ continue;
+ case FATDIRENTRY_CH0_END_OF_DIR:
+ if (pThis->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0)
+ {
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VERR_FILE_NOT_FOUND;
+ }
+ cwcName = 0;
+ break; /* Technically a valid entry before DOS 2.0, or so some claim. */
+ }
+
+ /*
+ * Check for long filename slot.
+ */
+ if ( paEntries[iEntry].Slot.fAttrib == FAT_ATTR_NAME_SLOT
+ && paEntries[iEntry].Slot.idxZero == 0
+ && paEntries[iEntry].Slot.fZero == 0
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) <= FATDIRNAMESLOT_HIGHEST_SLOT_ID
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) != 0)
+ {
+ /* New slot? */
+ if (paEntries[iEntry].Slot.idSlot & FATDIRNAMESLOT_FIRST_SLOT_FLAG)
+ {
+ idNextSlot = paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG;
+ bChecksum = paEntries[iEntry].Slot.bChecksum;
+ cwcName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT;
+ wszName[cwcName] = '\0';
+ }
+ /* Is valid next entry? */
+ else if ( paEntries[iEntry].Slot.idSlot == idNextSlot
+ && paEntries[iEntry].Slot.bChecksum == bChecksum)
+ { /* likely */ }
+ else
+ cwcName = 0;
+ if (cwcName)
+ {
+ idNextSlot--;
+ size_t offName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT;
+ memcpy(&wszName[offName], paEntries[iEntry].Slot.awcName0, sizeof(paEntries[iEntry].Slot.awcName0));
+ memcpy(&wszName[offName + 5], paEntries[iEntry].Slot.awcName1, sizeof(paEntries[iEntry].Slot.awcName1));
+ memcpy(&wszName[offName + 5 + 6], paEntries[iEntry].Slot.awcName2, sizeof(paEntries[iEntry].Slot.awcName2));
+ }
+ }
+ /*
+ * Regular directory entry. Do the matching, first 8.3 then long name.
+ */
+ else if ( fIs8Dot3Name
+ && !(paEntries[iEntry].Entry.fAttrib & FAT_ATTR_VOLUME)
+ && memcmp(paEntries[iEntry].Entry.achName, szName8Dot3, sizeof(paEntries[iEntry].Entry.achName)) == 0)
+ {
+ *poffEntryInDir = offEntryInDir;
+ *pDirEntry = paEntries[iEntry].Entry;
+ *pfLong = false;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+ else if ( cwcName != 0
+ && idNextSlot == 0
+ && !(paEntries[iEntry].Entry.fAttrib & FAT_ATTR_VOLUME)
+ && rtFsFatDir_CalcChecksum(&paEntries[iEntry].Entry) == bChecksum
+ && RTUtf16ICmpUtf8(wszName, pszEntry) == 0)
+ {
+ *poffEntryInDir = offEntryInDir;
+ *pDirEntry = paEntries[iEntry].Entry;
+ *pfLong = true;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+ else
+ cwcName = 0;
+ }
+
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ }
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+/**
+ * Watered down version of rtFsFatDirShrd_FindEntry that is used by the short name
+ * generator to check for duplicates.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_FILE_NOT_FOUND if not found.
+ * @retval VINF_SUCCESS if found.
+ * @param pThis The directory to search.
+ * @param pszEntry The entry to look for.
+ */
+static int rtFsFatDirShrd_FindEntryShort(PRTFSFATDIRSHRD pThis, const char *pszName8Dot3)
+{
+ Assert(strlen(pszName8Dot3) == 8+3);
+
+ /*
+ * Scan the directory buffer by buffer.
+ */
+ uint32_t offEntryInDir = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ Assert(RT_ALIGN_32(cbDir, sizeof(FATDIRENTRY)) == cbDir);
+
+ while (offEntryInDir < cbDir)
+ {
+ /* Get chunk of entries starting at offEntryInDir. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pThis, offEntryInDir, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Now work thru each of the entries.
+ */
+ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY))
+ {
+ switch ((uint8_t)paEntries[iEntry].Entry.achName[0])
+ {
+ default:
+ break;
+ case FATDIRENTRY_CH0_DELETED:
+ continue;
+ case FATDIRENTRY_CH0_END_OF_DIR:
+ if (pThis->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0)
+ {
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VERR_FILE_NOT_FOUND;
+ }
+ break; /* Technically a valid entry before DOS 2.0, or so some claim. */
+ }
+
+ /*
+ * Skip long filename slots.
+ */
+ if ( paEntries[iEntry].Slot.fAttrib == FAT_ATTR_NAME_SLOT
+ && paEntries[iEntry].Slot.idxZero == 0
+ && paEntries[iEntry].Slot.fZero == 0
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) <= FATDIRNAMESLOT_HIGHEST_SLOT_ID
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) != 0)
+ { /* skipped */ }
+ /*
+ * Regular directory entry. Do the matching, first 8.3 then long name.
+ */
+ else if (memcmp(paEntries[iEntry].Entry.achName, pszName8Dot3, sizeof(paEntries[iEntry].Entry.achName)) == 0)
+ {
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+ }
+
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ }
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+/**
+ * Calculates the FATDIRENTRY::fCase flags for the given name.
+ *
+ * ASSUMES that the name is a 8.3 name.
+ *
+ * @returns Case flag mask.
+ * @param pszName The name.
+ */
+static uint8_t rtFsFatDir_CalcCaseFlags(const char *pszName)
+{
+ uint8_t bRet = FATDIRENTRY_CASE_F_LOWER_BASE | FATDIRENTRY_CASE_F_LOWER_EXT;
+ uint8_t bCurrent = FATDIRENTRY_CASE_F_LOWER_BASE;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszName, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc != 0)
+ {
+ if (uc != '.')
+ {
+ if (RTUniCpIsUpper(uc))
+ {
+ bRet &= ~bCurrent;
+ if (!bRet)
+ return 0;
+ }
+ }
+ else
+ bCurrent = FATDIRENTRY_CASE_F_LOWER_EXT;
+ }
+ else if (bCurrent == FATDIRENTRY_CASE_F_LOWER_BASE)
+ return bRet & ~FATDIRENTRY_CASE_F_LOWER_EXT;
+ else
+ return bRet;
+ }
+ else
+ return 0;
+ }
+}
+
+
+/**
+ * Checks if we need to generate a long name for @a pszEntry.
+ *
+ * @returns true if we need to, false if we don't.
+ * @param pszEntry The UTF-8 directory entry entry name.
+ * @param fIs8Dot3Name Whether we've managed to create a 8-dot-3 name.
+ * @param pDirEntry The directory entry with the 8-dot-3 name when
+ * fIs8Dot3Name is set.
+ */
+static bool rtFsFatDir_NeedLongName(const char *pszEntry, bool fIs8Dot3Name, PCFATDIRENTRY pDirEntry)
+{
+ /*
+ * Check the easy ways out first.
+ */
+
+ /* If we couldn't make a straight 8-dot-3 name out of it, the we
+ must do the long name thing. No question. */
+ if (!fIs8Dot3Name)
+ return true;
+
+ /* If both lower case flags are set, then the whole name must be
+ lowercased, so we won't need a long entry. */
+ if (pDirEntry->fCase == (FATDIRENTRY_CASE_F_LOWER_BASE | FATDIRENTRY_CASE_F_LOWER_EXT))
+ return false;
+
+ /*
+ * Okay, check out the whole string then, part by part. (This is code
+ * similar to rtFsFatDir_CalcCaseFlags.)
+ */
+ uint8_t fCurrent = pDirEntry->fCase & FATDIRENTRY_CASE_F_LOWER_BASE;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszEntry, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc != 0)
+ {
+ if (uc != '.')
+ {
+ if ( fCurrent
+ || !RTUniCpIsLower(uc))
+ { /* okay */ }
+ else
+ return true;
+ }
+ else
+ fCurrent = pDirEntry->fCase & FATDIRENTRY_CASE_F_LOWER_EXT;
+ }
+ /* It checked out to the end, so we don't need a long name. */
+ else
+ return false;
+ }
+ else
+ return true;
+ }
+}
+
+
+/**
+ * Checks if the given long name is valid for a long file name or not.
+ *
+ * Encoding, length and character set limitations are checked.
+ *
+ * @returns IRPT status code.
+ * @param pwszEntry The long filename.
+ * @param cwc The length of the filename in UTF-16 chars.
+ */
+static int rtFsFatDir_ValidateLongName(PCRTUTF16 pwszEntry, size_t cwc)
+{
+ /* Length limitation. */
+ if (cwc <= RTFSFAT_MAX_LFN_CHARS)
+ {
+ /* Character set limitations. */
+ for (size_t off = 0; off < cwc; off++)
+ {
+ RTUTF16 wc = pwszEntry[off];
+ if (wc < 128)
+ {
+ if (g_awchFatCp437ValidChars[wc] <= UINT16_C(0xfffe))
+ { /* likely */ }
+ else
+ return VERR_INVALID_NAME;
+ }
+ }
+
+ /* Name limitations. */
+ if ( cwc == 1
+ && pwszEntry[0] == '.')
+ return VERR_INVALID_NAME;
+ if ( cwc == 2
+ && pwszEntry[0] == '.'
+ && pwszEntry[1] == '.')
+ return VERR_INVALID_NAME;
+
+ /** @todo Check for more invalid names, also in the 8.3 case! */
+ return VINF_SUCCESS;
+ }
+ return VERR_FILENAME_TOO_LONG;
+}
+
+
+/**
+ * Worker for rtFsFatDirShrd_GenerateShortName.
+ */
+static void rtFsFatDir_CopyShortName(char *pszDst, uint32_t cchDst, const char *pszSrc, size_t cchSrc, char chPad)
+{
+ /* Copy from source. */
+ if (cchSrc > 0)
+ {
+ const char *pszSrcEnd = &pszSrc[cchSrc];
+ while (cchDst > 0 && pszSrc != pszSrcEnd)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc < 128)
+ {
+ if (g_awchFatCp437ValidChars[uc] != uc)
+ {
+ if (uc)
+ {
+ uc = RTUniCpToUpper(uc);
+ if (g_awchFatCp437ValidChars[uc] != uc)
+ uc = '_';
+ }
+ else
+ break;
+ }
+ }
+ else
+ uc = '_';
+ }
+ else
+ uc = '_';
+
+ *pszDst++ = (char)uc;
+ cchDst--;
+ }
+ }
+
+ /* Pad the remaining space. */
+ while (cchDst-- > 0)
+ *pszDst++ = chPad;
+}
+
+
+/**
+ * Generates a short filename.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param pszEntry The long name (UTF-8).
+ * @param pDirEntry Where to put the short name.
+ */
+static int rtFsFatDirShrd_GenerateShortName(PRTFSFATDIRSHRD pThis, const char *pszEntry, PFATDIRENTRY pDirEntry)
+{
+ /* Do some input parsing. */
+ const char *pszExt = RTPathSuffix(pszEntry);
+ size_t const cchBasename = pszExt ? pszExt - pszEntry : strlen(pszEntry);
+ size_t const cchExt = pszExt ? strlen(++pszExt) : 0;
+
+ /* Fill in the extension first. It stays the same. */
+ char szShortName[8+3+1];
+ rtFsFatDir_CopyShortName(&szShortName[8], 3, pszExt, cchExt, ' ');
+ szShortName[8+3] = '\0';
+
+ /*
+ * First try single digit 1..9.
+ */
+ rtFsFatDir_CopyShortName(szShortName, 6, pszEntry, cchBasename, '_');
+ szShortName[6] = '~';
+ for (uint32_t iLastDigit = 1; iLastDigit < 10; iLastDigit++)
+ {
+ szShortName[7] = iLastDigit + '0';
+ int rc = rtFsFatDirShrd_FindEntryShort(pThis, szShortName);
+ if (rc == VERR_FILE_NOT_FOUND)
+ {
+ memcpy(pDirEntry->achName, szShortName, sizeof(pDirEntry->achName));
+ return VINF_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * First try two digits 10..99.
+ */
+ szShortName[5] = '~';
+ for (uint32_t iFirstDigit = 1; iFirstDigit < 10; iFirstDigit++)
+ for (uint32_t iLastDigit = 0; iLastDigit < 10; iLastDigit++)
+ {
+ szShortName[6] = iFirstDigit + '0';
+ szShortName[7] = iLastDigit + '0';
+ int rc = rtFsFatDirShrd_FindEntryShort(pThis, szShortName);
+ if (rc == VERR_FILE_NOT_FOUND)
+ {
+ memcpy(pDirEntry->achName, szShortName, sizeof(pDirEntry->achName));
+ return VINF_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Okay, do random numbers then.
+ */
+ szShortName[2] = '~';
+ for (uint32_t i = 0; i < 8192; i++)
+ {
+ char szHex[68];
+ ssize_t cchHex = RTStrFormatU32(szHex, sizeof(szHex), RTRandU32(), 16, 5, 0, RTSTR_F_CAPITAL | RTSTR_F_WIDTH | RTSTR_F_ZEROPAD);
+ AssertReturn(cchHex >= 5, VERR_NET_NOT_UNIQUE_NAME);
+ szShortName[7] = szHex[cchHex - 1];
+ szShortName[6] = szHex[cchHex - 2];
+ szShortName[5] = szHex[cchHex - 3];
+ szShortName[4] = szHex[cchHex - 4];
+ szShortName[3] = szHex[cchHex - 5];
+ int rc = rtFsFatDirShrd_FindEntryShort(pThis, szShortName);
+ if (rc == VERR_FILE_NOT_FOUND)
+ {
+ memcpy(pDirEntry->achName, szShortName, sizeof(pDirEntry->achName));
+ return VINF_SUCCESS;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return VERR_NET_NOT_UNIQUE_NAME;
+}
+
+
+/**
+ * Considers whether we need to create a long name or not.
+ *
+ * If a long name is needed and the name wasn't 8-dot-3 compatible, a 8-dot-3
+ * name will be generated and stored in *pDirEntry.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ * @param pszEntry The name.
+ * @param fIs8Dot3Name Whether we have a 8-dot-3 name already.
+ * @param pDirEntry Where to return the generated 8-dot-3 name.
+ * @param paSlots Where to return the long name entries. The array
+ * can hold at least FATDIRNAMESLOT_MAX_SLOTS entries.
+ * @param pcSlots Where to return the actual number of slots used.
+ */
+static int rtFsFatDirShrd_MaybeCreateLongNameAndShortAlias(PRTFSFATDIRSHRD pThis, const char *pszEntry, bool fIs8Dot3Name,
+ PFATDIRENTRY pDirEntry, PFATDIRNAMESLOT paSlots, uint32_t *pcSlots)
+{
+ RT_NOREF(pThis, pDirEntry, paSlots, pszEntry);
+
+ /*
+ * If we don't need to create a long name, return immediately.
+ */
+ if (!rtFsFatDir_NeedLongName(pszEntry, fIs8Dot3Name, pDirEntry))
+ {
+ *pcSlots = 0;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Convert the name to UTF-16 and figure it's length (this validates the
+ * input encoding). Then do long name validation (length, charset limitation).
+ */
+ RTUTF16 wszEntry[FATDIRNAMESLOT_MAX_SLOTS * FATDIRNAMESLOT_CHARS_PER_SLOT + 4];
+ PRTUTF16 pwszEntry = wszEntry;
+ size_t cwcEntry;
+ int rc = RTStrToUtf16Ex(pszEntry, RTSTR_MAX, &pwszEntry, RT_ELEMENTS(wszEntry), &cwcEntry);
+ if (RT_SUCCESS(rc))
+ rc = rtFsFatDir_ValidateLongName(pwszEntry, cwcEntry);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Generate a short name if we need to.
+ */
+ if (!fIs8Dot3Name)
+ rc = rtFsFatDirShrd_GenerateShortName(pThis, pszEntry, pDirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Fill in the long name slots. First we pad the wszEntry with 0xffff
+ * until it is a multiple of of the slot count. That way we can copy
+ * the name straight into the entry without constaints.
+ */
+ memset(&wszEntry[cwcEntry + 1], 0xff,
+ RT_MIN(sizeof(wszEntry) - (cwcEntry + 1) * sizeof(RTUTF16),
+ FATDIRNAMESLOT_CHARS_PER_SLOT * sizeof(RTUTF16)));
+
+ uint8_t const bChecksum = rtFsFatDir_CalcChecksum(pDirEntry);
+ size_t const cSlots = (cwcEntry + FATDIRNAMESLOT_CHARS_PER_SLOT - 1) / FATDIRNAMESLOT_CHARS_PER_SLOT;
+ size_t iSlot = cSlots;
+ PCRTUTF16 pwszSrc = wszEntry;
+ while (iSlot-- > 0)
+ {
+ memcpy(paSlots[iSlot].awcName0, pwszSrc, sizeof(paSlots[iSlot].awcName0));
+ pwszSrc += RT_ELEMENTS(paSlots[iSlot].awcName0);
+ memcpy(paSlots[iSlot].awcName1, pwszSrc, sizeof(paSlots[iSlot].awcName1));
+ pwszSrc += RT_ELEMENTS(paSlots[iSlot].awcName1);
+ memcpy(paSlots[iSlot].awcName2, pwszSrc, sizeof(paSlots[iSlot].awcName2));
+ pwszSrc += RT_ELEMENTS(paSlots[iSlot].awcName2);
+
+ paSlots[iSlot].idSlot = (uint8_t)(cSlots - iSlot);
+ paSlots[iSlot].fAttrib = FAT_ATTR_NAME_SLOT;
+ paSlots[iSlot].fZero = 0;
+ paSlots[iSlot].idxZero = 0;
+ paSlots[iSlot].bChecksum = bChecksum;
+ }
+ paSlots[0].idSlot |= FATDIRNAMESLOT_FIRST_SLOT_FLAG;
+ *pcSlots = (uint32_t)cSlots;
+ return VINF_SUCCESS;
+ }
+ }
+ *pcSlots = UINT32_MAX;
+ return rc;
+}
+
+
+/**
+ * Searches the directory for a given number of free directory entries.
+ *
+ * The free entries must be consecutive of course.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_DISK_FULL if no space was found, *pcFreeTail set.
+ * @param pThis The directory to search.
+ * @param cEntriesNeeded How many entries we need.
+ * @param poffEntryInDir Where to return the offset of the first entry we
+ * found.
+ * @param pcFreeTail Where to return the number of free entries at the
+ * end of the directory when VERR_DISK_FULL is
+ * returned.
+ */
+static int rtFsFatChain_FindFreeEntries(PRTFSFATDIRSHRD pThis, uint32_t cEntriesNeeded,
+ uint32_t *poffEntryInDir, uint32_t *pcFreeTail)
+{
+ /* First try make gcc happy. */
+ *pcFreeTail = 0;
+ *poffEntryInDir = UINT32_MAX;
+
+ /*
+ * Scan the whole directory, buffer by buffer.
+ */
+ uint32_t offStartFreeEntries = UINT32_MAX;
+ uint32_t cFreeEntries = 0;
+ uint32_t offEntryInDir = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ Assert(RT_ALIGN_32(cbDir, sizeof(FATDIRENTRY)) == cbDir);
+ while (offEntryInDir < cbDir)
+ {
+ /* Get chunk of entries starting at offEntryInDir. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pThis, offEntryInDir, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Now work thru each of the entries.
+ */
+ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY))
+ {
+ uint8_t const bFirst = paEntries[iEntry].Entry.achName[0];
+ if ( bFirst == FATDIRENTRY_CH0_DELETED
+ || bFirst == FATDIRENTRY_CH0_END_OF_DIR)
+ {
+ if (offStartFreeEntries != UINT32_MAX)
+ cFreeEntries++;
+ else
+ {
+ offStartFreeEntries = offEntryInDir;
+ cFreeEntries = 1;
+ }
+ if (cFreeEntries >= cEntriesNeeded)
+ {
+ *pcFreeTail = cEntriesNeeded;
+ *poffEntryInDir = offStartFreeEntries;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+
+ if (bFirst == FATDIRENTRY_CH0_END_OF_DIR)
+ {
+ if (pThis->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0)
+ {
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ *pcFreeTail = cFreeEntries = (cbDir - offStartFreeEntries) / sizeof(FATDIRENTRY);
+ if (cFreeEntries >= cEntriesNeeded)
+ {
+ *poffEntryInDir = offStartFreeEntries;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ return VINF_SUCCESS;
+ }
+ return VERR_DISK_FULL;
+ }
+ }
+ }
+ else if (offStartFreeEntries != UINT32_MAX)
+ {
+ offStartFreeEntries = UINT32_MAX;
+ cFreeEntries = 0;
+ }
+ }
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock);
+ }
+ *pcFreeTail = cFreeEntries;
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Try grow the directory.
+ *
+ * This is not called on the root directory.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_DISK_FULL if we failed to allocated new space.
+ * @param pThis The directory to grow.
+ * @param cMinNewEntries The minimum number of new entries to allocated.
+ */
+static int rtFsFatChain_GrowDirectory(PRTFSFATDIRSHRD pThis, uint32_t cMinNewEntries)
+{
+ RT_NOREF(pThis, cMinNewEntries);
+ return VERR_DISK_FULL;
+}
+
+
+/**
+ * Inserts a directory with zero of more long name slots preceeding it.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory.
+ * @param pDirEntry The directory entry.
+ * @param paSlots The long name slots.
+ * @param cSlots The number of long name slots.
+ * @param poffEntryInDir Where to return the directory offset.
+ */
+static int rtFsFatChain_InsertEntries(PRTFSFATDIRSHRD pThis, PCFATDIRENTRY pDirEntry, PFATDIRNAMESLOT paSlots, uint32_t cSlots,
+ uint32_t *poffEntryInDir)
+{
+ uint32_t const cTotalEntries = cSlots + 1;
+
+ /*
+ * Find somewhere to put the entries. Try extend the directory if we're
+ * not successful at first.
+ */
+ uint32_t cFreeTailEntries;
+ uint32_t offFirstInDir;
+ int rc = rtFsFatChain_FindFreeEntries(pThis, cTotalEntries, &offFirstInDir, &cFreeTailEntries);
+ if (rc == VERR_DISK_FULL)
+ {
+ Assert(cFreeTailEntries < cTotalEntries);
+
+ /* Try grow it and use the newly allocated space. */
+ if ( pThis->Core.pParentDir
+ && pThis->cEntries < _64K /* Don't grow beyond 64K entries */)
+ {
+ offFirstInDir = pThis->Core.cbObject - cFreeTailEntries * sizeof(FATDIRENTRY);
+ rc = rtFsFatChain_GrowDirectory(pThis, cTotalEntries - cFreeTailEntries);
+ }
+
+ if (rc == VERR_DISK_FULL)
+ {
+ /** @todo Try compact the directory if we couldn't grow it. */
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Update the directory.
+ */
+ uint32_t offCurrent = offFirstInDir;
+ for (uint32_t iSrcSlot = 0; iSrcSlot < cTotalEntries; iSrcSlot++, offCurrent += sizeof(FATDIRENTRY))
+ {
+ uint32_t uBufferLock;
+ PFATDIRENTRY pDstEntry;
+ rc = rtFsFatDirShrd_GetEntryForUpdate(pThis, offCurrent, &pDstEntry, &uBufferLock);
+ if (RT_SUCCESS(rc))
+ {
+ if (iSrcSlot < cSlots)
+ memcpy(pDstEntry, &paSlots[iSrcSlot], sizeof(*pDstEntry));
+ else
+ memcpy(pDstEntry, pDirEntry, sizeof(*pDstEntry));
+ rc = rtFsFatDirShrd_PutEntryAfterUpdate(pThis, pDstEntry, uBufferLock);
+ if (RT_SUCCESS(rc))
+ continue;
+
+ /*
+ * Bail out: Try mark any edited entries as deleted.
+ */
+ iSrcSlot++;
+ }
+ while (iSrcSlot-- > 0)
+ {
+ int rc2 = rtFsFatDirShrd_GetEntryForUpdate(pThis, offFirstInDir + iSrcSlot * sizeof(FATDIRENTRY),
+ &pDstEntry, &uBufferLock);
+ if (RT_SUCCESS(rc2))
+ {
+ pDstEntry->achName[0] = FATDIRENTRY_CH0_DELETED;
+ rtFsFatDirShrd_PutEntryAfterUpdate(pThis, pDstEntry, uBufferLock);
+ }
+ }
+ *poffEntryInDir = UINT32_MAX;
+ return rc;
+ }
+ AssertRC(rc);
+
+ /*
+ * Successfully inserted all.
+ */
+ *poffEntryInDir = offFirstInDir + cSlots * sizeof(FATDIRENTRY);
+ return VINF_SUCCESS;
+ }
+
+ *poffEntryInDir = UINT32_MAX;
+ return rc;
+}
+
+
+
+/**
+ * Creates a new directory entry.
+ *
+ * @returns IPRT status code
+ * @param pThis The directory.
+ * @param pszEntry The name of the new entry.
+ * @param fAttrib The attributes.
+ * @param cbInitial The initialize size.
+ * @param poffEntryInDir Where to return the offset of the directory entry.
+ * @param pDirEntry Where to return a copy of the directory entry.
+ *
+ * @remarks ASSUMES caller has already called rtFsFatDirShrd_FindEntry to make sure
+ * the entry doesn't exist.
+ */
+static int rtFsFatDirShrd_CreateEntry(PRTFSFATDIRSHRD pThis, const char *pszEntry, uint8_t fAttrib, uint32_t cbInitial,
+ uint32_t *poffEntryInDir, PFATDIRENTRY pDirEntry)
+{
+ PRTFSFATVOL pVol = pThis->Core.pVol;
+ *poffEntryInDir = UINT32_MAX;
+ if (pVol->fReadOnly)
+ return VERR_WRITE_PROTECT;
+
+ /*
+ * Create the directory entries on the stack.
+ */
+ bool fIs8Dot3Name = rtFsFatDir_StringTo8Dot3((char *)pDirEntry->achName, pszEntry);
+ pDirEntry->fAttrib = fAttrib;
+ pDirEntry->fCase = fIs8Dot3Name ? rtFsFatDir_CalcCaseFlags(pszEntry) : 0;
+ pDirEntry->uBirthCentiseconds = rtFsFatCurrentFatDateTime(pVol, &pDirEntry->uBirthDate, &pDirEntry->uBirthTime);
+ pDirEntry->uAccessDate = pDirEntry->uBirthDate;
+ pDirEntry->uModifyDate = pDirEntry->uBirthDate;
+ pDirEntry->uModifyTime = pDirEntry->uBirthTime;
+ pDirEntry->idxCluster = 0; /* Will fill this in later if cbInitial is non-zero. */
+ pDirEntry->u.idxClusterHigh = 0;
+ pDirEntry->cbFile = cbInitial;
+
+ /*
+ * Create long filename slots if necessary.
+ */
+ uint32_t cSlots = UINT32_MAX;
+ FATDIRNAMESLOT aSlots[FATDIRNAMESLOT_MAX_SLOTS];
+ AssertCompile(RTFSFAT_MAX_LFN_CHARS < RT_ELEMENTS(aSlots) * FATDIRNAMESLOT_CHARS_PER_SLOT);
+ int rc = rtFsFatDirShrd_MaybeCreateLongNameAndShortAlias(pThis, pszEntry, fIs8Dot3Name, pDirEntry, aSlots, &cSlots);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cSlots <= FATDIRNAMESLOT_MAX_SLOTS);
+
+ /*
+ * Allocate initial clusters if requested.
+ */
+ RTFSFATCHAIN Clusters;
+ rtFsFatChain_InitEmpty(&Clusters, pVol);
+ if (cbInitial > 0)
+ {
+ rc = rtFsFatClusterMap_AllocateMoreClusters(pVol, &Clusters,
+ (cbInitial + Clusters.cbCluster - 1) >> Clusters.cClusterByteShift);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxFirstCluster = rtFsFatChain_GetFirstCluster(&Clusters);
+ pDirEntry->idxCluster = (uint16_t)idxFirstCluster;
+ if (pVol->enmFatType >= RTFSFATTYPE_FAT32)
+ pDirEntry->u.idxClusterHigh = (uint16_t)(idxFirstCluster >> 16);
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Insert the directory entry and name slots.
+ */
+ rc = rtFsFatChain_InsertEntries(pThis, pDirEntry, aSlots, cSlots, poffEntryInDir);
+ if (RT_SUCCESS(rc))
+ {
+ rtFsFatChain_Delete(&Clusters);
+ return VINF_SUCCESS;
+ }
+
+ for (uint32_t iClusterToFree = 0; iClusterToFree < Clusters.cClusters; iClusterToFree++)
+ rtFsFatClusterMap_FreeCluster(pVol, rtFsFatChain_GetClusterByIndex(&Clusters, iClusterToFree));
+ rtFsFatChain_Delete(&Clusters);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Releases a reference to a shared directory structure.
+ *
+ * @param pShared The shared directory structure.
+ */
+static int rtFsFatDirShrd_Release(PRTFSFATDIRSHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pShared->Core.cRefs);
+ Assert(cRefs < UINT32_MAX / 2);
+ if (cRefs == 0)
+ {
+ LogFlow(("rtFsFatDirShrd_Release: Destroying shared structure %p\n", pShared));
+ Assert(pShared->Core.cRefs == 0);
+
+ int rc;
+ if (pShared->paEntries)
+ {
+ rc = rtFsFatDirShrd_Flush(pShared);
+ RTMemFree(pShared->paEntries);
+ pShared->paEntries = NULL;
+ }
+ else
+ rc = VINF_SUCCESS;
+
+ if ( pShared->fFullyBuffered
+ && pShared->u.Full.pbDirtySectors)
+ {
+ RTMemFree(pShared->u.Full.pbDirtySectors);
+ pShared->u.Full.pbDirtySectors = NULL;
+ }
+
+ int rc2 = rtFsFatObj_Close(&pShared->Core);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTMemFree(pShared);
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Retains a reference to a shared directory structure.
+ *
+ * @param pShared The shared directory structure.
+ */
+static void rtFsFatDirShrd_Retain(PRTFSFATDIRSHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pShared->Core.cRefs);
+ Assert(cRefs > 1); NOREF(cRefs);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsFatDir_Close(void *pvThis)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ PRTFSFATDIRSHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ return rtFsFatDirShrd_Release(pShared);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsFatDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ return rtFsFatObj_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsFatDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ return rtFsFatObj_SetMode(&pThis->pShared->Core, fMode, fMask);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsFatDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ return rtFsFatObj_SetTimes(&pThis->pShared->Core, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsFatDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsFatDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ PRTFSFATDIRSHRD pShared = pThis->pShared;
+ int rc;
+
+ /*
+ * Special cases '.' and '.'
+ */
+ if (pszEntry[0] == '.')
+ {
+ PRTFSFATDIRSHRD pSharedToOpen;
+ if (pszEntry[1] == '\0')
+ pSharedToOpen = pShared;
+ else if (pszEntry[1] == '.' && pszEntry[2] == '\0')
+ {
+ pSharedToOpen = pShared->Core.pParentDir;
+ if (!pSharedToOpen)
+ pSharedToOpen = pShared;
+ }
+ else
+ pSharedToOpen = NULL;
+ if (pSharedToOpen)
+ {
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ {
+ rtFsFatDirShrd_Retain(pSharedToOpen);
+ RTVFSDIR hVfsDir;
+ rc = rtFsFatDir_NewWithShared(pShared->Core.pVol, pSharedToOpen, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ return rc;
+ }
+ }
+
+ /*
+ * Try open existing file.
+ */
+ uint32_t offEntryInDir;
+ bool fLong;
+ FATDIRENTRY DirEntry;
+ rc = rtFsFatDirShrd_FindEntry(pShared, pszEntry, &offEntryInDir, &fLong, &DirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ switch (DirEntry.fAttrib & (FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME))
+ {
+ case 0:
+ if (fFlags & RTVFSOBJ_F_OPEN_FILE)
+ {
+ if ( !(DirEntry.fAttrib & FAT_ATTR_READONLY)
+ || !(fOpen & RTFILE_O_WRITE))
+ {
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsFatFile_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir, fOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+ }
+ else
+ rc = VERR_IS_A_FILE;
+ break;
+
+ case FAT_ATTR_DIRECTORY:
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ if ( !(DirEntry.fAttrib & FAT_ATTR_READONLY)
+ || !(fOpen & RTFILE_O_WRITE))
+ {
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsFatDir_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir,
+ RTFSFAT_GET_CLUSTER(&DirEntry, pShared->Core.pVol), UINT64_MAX /*offDisk*/,
+ DirEntry.cbFile, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else if ((fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ rc = VERR_INVALID_FUNCTION;
+ else
+ rc = VERR_ALREADY_EXISTS;
+ }
+ else
+ rc = VERR_ACCESS_DENIED;
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ break;
+
+ default:
+ rc = VERR_PATH_NOT_FOUND;
+ break;
+ }
+ }
+ /*
+ * Create a file or directory?
+ */
+ else if (rc == VERR_FILE_NOT_FOUND)
+ {
+ if ( ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ && (fFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_NOTHING)
+ {
+ if ((fFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_FILE)
+ {
+ rc = rtFsFatDirShrd_CreateEntry(pShared, pszEntry, FAT_ATTR_ARCHIVE, 0 /*cbInitial*/, &offEntryInDir, &DirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsFatFile_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir, fOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ }
+ else if ((fFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_DIRECTORY)
+ {
+ rc = rtFsFatDirShrd_CreateEntry(pShared, pszEntry, FAT_ATTR_ARCHIVE | FAT_ATTR_DIRECTORY,
+ pShared->Core.pVol->cbCluster, &offEntryInDir, &DirEntry);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsFatDir_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir,
+ RTFSFAT_GET_CLUSTER(&DirEntry, pShared->Core.pVol), UINT64_MAX /*offDisk*/,
+ DirEntry.cbFile, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ }
+ else
+ rc = VERR_VFS_UNSUPPORTED_CREATE_TYPE;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsFatDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtFsFatDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsFatDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsFatDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsFatDir_RewindDir(void *pvThis)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ pThis->offDir = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Calculates the UTF-8 length of the name in the given directory entry.
+ *
+ * @returns The length in characters (bytes), excluding terminator.
+ * @param pShared The shared directory structure (for codepage).
+ * @param pEntry The directory entry.
+ */
+static size_t rtFsFatDir_CalcUtf8LengthForDirEntry(PRTFSFATDIRSHRD pShared, PCFATDIRENTRY pEntry)
+{
+ RT_NOREF(pShared);
+ PCRTUTF16 g_pawcMap = &g_awchFatCp437Chars[0];
+
+ /* The base name (this won't work with DBCS, but that's not a concern at the moment). */
+ size_t offSrc = 8;
+ while (offSrc > 1 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[offSrc - 1]]))
+ offSrc--;
+
+ size_t cchRet = 0;
+ while (offSrc-- > 0)
+ cchRet += RTStrCpSize(g_pawcMap[pEntry->achName[offSrc]]);
+
+ /* Extension. */
+ offSrc = 11;
+ while (offSrc > 8 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[offSrc - 1]]))
+ offSrc--;
+ if (offSrc > 8)
+ {
+ cchRet += 1; /* '.' */
+ while (offSrc-- > 8)
+ cchRet += RTStrCpSize(g_pawcMap[pEntry->achName[offSrc]]);
+ }
+
+ return cchRet;
+}
+
+
+/**
+ * Copies the name from the directory entry into a UTF-16 buffer.
+ *
+ * @returns Number of UTF-16 items written (excluding terminator).
+ * @param pShared The shared directory structure (for codepage).
+ * @param pEntry The directory entry.
+ * @param pwszDst The destination buffer.
+ * @param cwcDst The destination buffer size.
+ */
+static uint16_t rtFsFatDir_CopyDirEntryToUtf16(PRTFSFATDIRSHRD pShared, PCFATDIRENTRY pEntry, PRTUTF16 pwszDst, size_t cwcDst)
+{
+ Assert(cwcDst > 0);
+
+ RT_NOREF(pShared);
+ PCRTUTF16 g_pawcMap = &g_awchFatCp437Chars[0];
+
+ /* The base name (this won't work with DBCS, but that's not a concern at the moment). */
+ size_t cchSrc = 8;
+ while (cchSrc > 1 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[cchSrc - 1]]))
+ cchSrc--;
+
+ size_t offDst = 0;
+ for (size_t offSrc = 0; offSrc < cchSrc; offSrc++)
+ {
+ AssertReturnStmt(offDst + 1 < cwcDst, pwszDst[cwcDst - 1] = '\0', (uint16_t)cwcDst);
+ pwszDst[offDst++] = g_pawcMap[pEntry->achName[offSrc]];
+ }
+
+ /* Extension. */
+ cchSrc = 3;
+ while (cchSrc > 0 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[8 + cchSrc - 1]]))
+ cchSrc--;
+ if (cchSrc > 0)
+ {
+ AssertReturnStmt(offDst + 1 < cwcDst, pwszDst[cwcDst - 1] = '\0', (uint16_t)cwcDst);
+ pwszDst[offDst++] = '.';
+
+ for (size_t offSrc = 0; offSrc < cchSrc; offSrc++)
+ {
+ AssertReturnStmt(offDst + 1 < cwcDst, pwszDst[cwcDst - 1] = '\0', (uint16_t)cwcDst);
+ pwszDst[offDst++] = g_pawcMap[pEntry->achName[8 + offSrc]];
+ }
+ }
+
+ pwszDst[offDst] = '\0';
+ return (uint16_t)offDst;
+}
+
+
+/**
+ * Copies the name from the directory entry into a UTF-8 buffer.
+ *
+ * @returns Number of UTF-16 items written (excluding terminator).
+ * @param pShared The shared directory structure (for codepage).
+ * @param pEntry The directory entry.
+ * @param pszDst The destination buffer.
+ * @param cbDst The destination buffer size.
+ */
+static uint16_t rtFsFatDir_CopyDirEntryToUtf8(PRTFSFATDIRSHRD pShared, PCFATDIRENTRY pEntry, char *pszDst, size_t cbDst)
+{
+ Assert(cbDst > 0);
+
+ RT_NOREF(pShared);
+ PCRTUTF16 g_pawcMap = &g_awchFatCp437Chars[0];
+
+ /* The base name (this won't work with DBCS, but that's not a concern at the moment). */
+ size_t cchSrc = 8;
+ while (cchSrc > 1 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[cchSrc - 1]]))
+ cchSrc--;
+
+ char * const pszDstEnd = pszDst + cbDst;
+ char *pszCurDst = pszDst;
+ for (size_t offSrc = 0; offSrc < cchSrc; offSrc++)
+ {
+ RTUNICP const uc = g_pawcMap[pEntry->achName[offSrc]];
+ size_t cbCp = RTStrCpSize(uc);
+ AssertReturnStmt(cbCp < (size_t)(pszDstEnd - pszCurDst), *pszCurDst = '\0', (uint16_t)(pszDstEnd - pszCurDst));
+ pszCurDst = RTStrPutCp(pszCurDst, uc);
+ }
+
+ /* Extension. */
+ cchSrc = 3;
+ while (cchSrc > 0 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[8 + cchSrc - 1]]))
+ cchSrc--;
+ if (cchSrc > 0)
+ {
+ AssertReturnStmt(1U < (size_t)(pszDstEnd - pszCurDst), *pszCurDst = '\0', (uint16_t)(pszDstEnd - pszCurDst));
+ *pszCurDst++ = '.';
+
+ for (size_t offSrc = 0; offSrc < cchSrc; offSrc++)
+ {
+ RTUNICP const uc = g_pawcMap[pEntry->achName[8 + offSrc]];
+ size_t cbCp = RTStrCpSize(uc);
+ AssertReturnStmt(cbCp < (size_t)(pszDstEnd - pszCurDst), *pszCurDst = '\0', (uint16_t)(pszDstEnd - pszCurDst));
+ pszCurDst = RTStrPutCp(pszCurDst, uc);
+ }
+ }
+
+ *pszCurDst = '\0';
+ return (uint16_t)(pszDstEnd - pszCurDst);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsFatDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
+ PRTFSFATDIRSHRD pShared = pThis->pShared;
+
+ /*
+ * Fake '.' and '..' entries (required for root, we do it everywhere).
+ */
+ if (pThis->offDir < 2)
+ {
+ size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[pThis->offDir + 2]);
+ if (cbNeeded < *pcbDirEntry)
+ *pcbDirEntry = cbNeeded;
+ else
+ {
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ int rc;
+ if ( pThis->offDir == 0
+ || pShared->Core.pParentDir == NULL)
+ rc = rtFsFatObj_QueryInfo(&pShared->Core, &pDirEntry->Info, enmAddAttr);
+ else
+ rc = rtFsFatObj_QueryInfo(&pShared->Core.pParentDir->Core, &pDirEntry->Info, enmAddAttr);
+
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '.';
+ pDirEntry->szName[++pThis->offDir] = '\0';
+ pDirEntry->cbName = pThis->offDir;
+ return rc;
+ }
+ if ( pThis->offDir == 2
+ && pShared->cEntries >= 2)
+ {
+ /* Skip '.' and '..' entries if present. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pShared, 0, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ( (paEntries[0].Entry.fAttrib & FAT_ATTR_DIRECTORY)
+ && memcmp(paEntries[0].Entry.achName, RT_STR_TUPLE(". ")) == 0)
+ {
+ if ( (paEntries[1].Entry.fAttrib & FAT_ATTR_DIRECTORY)
+ && memcmp(paEntries[1].Entry.achName, RT_STR_TUPLE(".. ")) == 0)
+ pThis->offDir += sizeof(paEntries[0]) * 2;
+ else
+ pThis->offDir += sizeof(paEntries[0]);
+ }
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock);
+ }
+
+ /*
+ * Scan the directory buffer by buffer.
+ */
+ RTUTF16 wszName[260+1];
+ uint8_t bChecksum = UINT8_MAX;
+ uint8_t idNextSlot = UINT8_MAX;
+ size_t cwcName = 0;
+ uint32_t offEntryInDir = pThis->offDir - 2;
+ uint32_t const cbDir = pShared->Core.cbObject;
+ Assert(RT_ALIGN_32(cbDir, sizeof(*pDirEntry)) == cbDir);
+ AssertCompile(FATDIRNAMESLOT_MAX_SLOTS * FATDIRNAMESLOT_CHARS_PER_SLOT < RT_ELEMENTS(wszName));
+ wszName[260] = '\0';
+
+ while (offEntryInDir < cbDir)
+ {
+ /* Get chunk of entries starting at offEntryInDir. */
+ uint32_t uBufferLock = UINT32_MAX;
+ uint32_t cEntries = 0;
+ PCFATDIRENTRYUNION paEntries = NULL;
+ int rc = rtFsFatDirShrd_GetEntriesAt(pShared, offEntryInDir, &paEntries, &cEntries, &uBufferLock);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Now work thru each of the entries.
+ */
+ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY))
+ {
+ switch ((uint8_t)paEntries[iEntry].Entry.achName[0])
+ {
+ default:
+ break;
+ case FATDIRENTRY_CH0_DELETED:
+ cwcName = 0;
+ continue;
+ case FATDIRENTRY_CH0_END_OF_DIR:
+ if (pShared->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0)
+ {
+ pThis->offDir = cbDir + 2;
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock);
+ return VERR_NO_MORE_FILES;
+ }
+ cwcName = 0;
+ break; /* Technically a valid entry before DOS 2.0, or so some claim. */
+ }
+
+ /*
+ * Check for long filename slot.
+ */
+ if ( paEntries[iEntry].Slot.fAttrib == FAT_ATTR_NAME_SLOT
+ && paEntries[iEntry].Slot.idxZero == 0
+ && paEntries[iEntry].Slot.fZero == 0
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) <= FATDIRNAMESLOT_HIGHEST_SLOT_ID
+ && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) != 0)
+ {
+ /* New slot? */
+ if (paEntries[iEntry].Slot.idSlot & FATDIRNAMESLOT_FIRST_SLOT_FLAG)
+ {
+ idNextSlot = paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG;
+ bChecksum = paEntries[iEntry].Slot.bChecksum;
+ cwcName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT;
+ wszName[cwcName] = '\0';
+ }
+ /* Is valid next entry? */
+ else if ( paEntries[iEntry].Slot.idSlot == idNextSlot
+ && paEntries[iEntry].Slot.bChecksum == bChecksum)
+ { /* likely */ }
+ else
+ cwcName = 0;
+ if (cwcName)
+ {
+ idNextSlot--;
+ size_t offName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT;
+ memcpy(&wszName[offName], paEntries[iEntry].Slot.awcName0, sizeof(paEntries[iEntry].Slot.awcName0));
+ memcpy(&wszName[offName + 5], paEntries[iEntry].Slot.awcName1, sizeof(paEntries[iEntry].Slot.awcName1));
+ memcpy(&wszName[offName + 5 + 6], paEntries[iEntry].Slot.awcName2, sizeof(paEntries[iEntry].Slot.awcName2));
+ }
+ }
+ /*
+ * Got a regular directory entry. Try return it to the caller if not volume label.
+ */
+ else if (!(paEntries[iEntry].Entry.fAttrib & FAT_ATTR_VOLUME))
+ {
+ /* Do the length calc and check for overflows. */
+ bool fLongName = false;
+ size_t cchName = 0;
+ if ( cwcName != 0
+ && idNextSlot == 0
+ && rtFsFatDir_CalcChecksum(&paEntries[iEntry].Entry) == bChecksum)
+ {
+ rc = RTUtf16CalcUtf8LenEx(wszName, cwcName, &cchName);
+ if (RT_SUCCESS(rc))
+ fLongName = true;
+ }
+ if (!fLongName)
+ cchName = rtFsFatDir_CalcUtf8LengthForDirEntry(pShared, &paEntries[iEntry].Entry);
+ size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cchName + 1]);
+ if (cbNeeded <= *pcbDirEntry)
+ *pcbDirEntry = cbNeeded;
+ else
+ {
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* To avoid duplicating code in rtFsFatObj_InitFromDirRec and
+ rtFsFatObj_QueryInfo, we create a dummy RTFSFATOBJ on the stack. */
+ RTFSFATOBJ TmpObj;
+ RT_ZERO(TmpObj);
+ rtFsFatObj_InitFromDirEntry(&TmpObj, &paEntries[iEntry].Entry, offEntryInDir, pShared->Core.pVol);
+
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock);
+
+ rc = rtFsFatObj_QueryInfo(&TmpObj, &pDirEntry->Info, enmAddAttr);
+
+ /* Copy out the names. */
+ pDirEntry->cbName = (uint16_t)cchName;
+ if (fLongName)
+ {
+ char *pszDst = &pDirEntry->szName[0];
+ int rc2 = RTUtf16ToUtf8Ex(wszName, cwcName, &pszDst, cchName + 1, NULL);
+ AssertRC(rc2);
+
+ pDirEntry->cwcShortName = rtFsFatDir_CopyDirEntryToUtf16(pShared, &paEntries[iEntry].Entry,
+ pDirEntry->wszShortName,
+ RT_ELEMENTS(pDirEntry->wszShortName));
+ }
+ else
+ {
+ rtFsFatDir_CopyDirEntryToUtf8(pShared, &paEntries[iEntry].Entry, &pDirEntry->szName[0], cchName + 1);
+ pDirEntry->wszShortName[0] = '\0';
+ pDirEntry->cwcShortName = 0;
+ }
+
+ if (RT_SUCCESS(rc))
+ pThis->offDir = offEntryInDir + sizeof(paEntries[iEntry]) + 2;
+ Assert(RTStrValidateEncoding(pDirEntry->szName) == VINF_SUCCESS);
+ return rc;
+ }
+ else
+ cwcName = 0;
+ }
+
+ rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock);
+ }
+
+ pThis->offDir = cbDir + 2;
+ return VERR_NO_MORE_FILES;
+}
+
+
+/**
+ * FAT directory operations.
+ */
+static const RTVFSDIROPS g_rtFsFatDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "FatDir",
+ rtFsFatDir_Close,
+ rtFsFatDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtFsFatDir_SetMode,
+ rtFsFatDir_SetTimes,
+ rtFsFatDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsFatDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile*/,
+ NULL /* pfnOpenDir */,
+ NULL /* pfnCreateDir */,
+ rtFsFatDir_OpenSymlink,
+ rtFsFatDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsFatDir_UnlinkEntry,
+ rtFsFatDir_RenameEntry,
+ rtFsFatDir_RewindDir,
+ rtFsFatDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+
+
+/**
+ * Adds an open child to the parent directory.
+ *
+ * Maintains an additional reference to the parent dir to prevent it from going
+ * away. If @a pDir is the root directory, it also ensures the volume is
+ * referenced and sticks around until the last open object is gone.
+ *
+ * @param pDir The directory.
+ * @param pChild The child being opened.
+ * @sa rtFsFatDirShrd_RemoveOpenChild
+ */
+static void rtFsFatDirShrd_AddOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild)
+{
+ rtFsFatDirShrd_Retain(pDir);
+
+ RTListAppend(&pDir->OpenChildren, &pChild->Entry);
+ pChild->pParentDir = pDir;
+}
+
+
+/**
+ * Removes an open child to the parent directory.
+ *
+ * @param pDir The directory.
+ * @param pChild The child being removed.
+ *
+ * @remarks This is the very last thing you do as it may cause a few other
+ * objects to be released recursively (parent dir and the volume).
+ *
+ * @sa rtFsFatDirShrd_AddOpenChild
+ */
+static void rtFsFatDirShrd_RemoveOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild)
+{
+ AssertReturnVoid(pChild->pParentDir == pDir);
+ RTListNodeRemove(&pChild->Entry);
+ pChild->pParentDir = NULL;
+
+ rtFsFatDirShrd_Release(pDir);
+}
+
+
+/**
+ * Instantiates a new shared directory instance.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pDirEntry The parent directory entry. This is NULL for the
+ * root directory.
+ * @param offEntryInDir The byte offset of the directory entry in the parent
+ * directory. UINT32_MAX if root directory.
+ * @param idxCluster The cluster where the directory content is to be
+ * found. This can be UINT32_MAX if a root FAT12/16
+ * directory.
+ * @param offDisk The disk byte offset of the FAT12/16 root directory.
+ * This is UINT64_MAX if idxCluster is given.
+ * @param cbDir The size of the directory.
+ * @param ppSharedDir Where to return shared FAT directory instance.
+ */
+static int rtFsFatDirShrd_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir,
+ uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTFSFATDIRSHRD *ppSharedDir)
+{
+ Assert((idxCluster == UINT32_MAX) != (offDisk == UINT64_MAX));
+ Assert((pDirEntry == NULL) == (offEntryInDir == UINT32_MAX));
+ *ppSharedDir = NULL;
+
+ int rc = VERR_NO_MEMORY;
+ PRTFSFATDIRSHRD pShared = (PRTFSFATDIRSHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ /*
+ * Initialize it all so rtFsFatDir_Close doesn't trip up in anyway.
+ */
+ RTListInit(&pShared->OpenChildren);
+ if (pDirEntry)
+ rtFsFatObj_InitFromDirEntry(&pShared->Core, pDirEntry, offEntryInDir, pThis);
+ else
+ rtFsFatObj_InitDummy(&pShared->Core, cbDir, RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | RTFS_UNIX_ALL_PERMS, pThis);
+
+ pShared->cEntries = cbDir / sizeof(FATDIRENTRY);
+ pShared->fIsLinearRootDir = idxCluster == UINT32_MAX;
+ pShared->fFullyBuffered = pShared->fIsLinearRootDir;
+ pShared->paEntries = NULL;
+ pShared->offEntriesOnDisk = UINT64_MAX;
+ if (pShared->fFullyBuffered)
+ pShared->cbAllocatedForEntries = RT_ALIGN_32(cbDir, pThis->cbSector);
+ else
+ pShared->cbAllocatedForEntries = pThis->cbSector;
+
+ /*
+ * If clustered backing, read the chain and see if we cannot still do the full buffering.
+ */
+ if (idxCluster != UINT32_MAX)
+ {
+ rc = rtFsFatClusterMap_ReadClusterChain(pThis, idxCluster, &pShared->Core.Clusters);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pShared->Core.Clusters.cClusters >= 1
+ && pShared->Core.Clusters.cbChain <= _64K
+ && rtFsFatChain_IsContiguous(&pShared->Core.Clusters))
+ {
+ Assert(pShared->Core.Clusters.cbChain >= cbDir);
+ pShared->cbAllocatedForEntries = pShared->Core.Clusters.cbChain;
+ pShared->fFullyBuffered = true;
+ }
+
+ /* DOS doesn't set a size on directores, so use the cluster length instead. */
+ if ( cbDir == 0
+ && pShared->Core.Clusters.cbChain > 0)
+ {
+ cbDir = pShared->Core.Clusters.cbChain;
+ pShared->Core.cbObject = cbDir;
+ pShared->cEntries = cbDir / sizeof(FATDIRENTRY);
+ if (pShared->fFullyBuffered)
+ pShared->cbAllocatedForEntries = RT_ALIGN_32(cbDir, pThis->cbSector);
+ }
+ }
+ }
+ else
+ {
+ rtFsFatChain_InitEmpty(&pShared->Core.Clusters, pThis);
+ rc = VINF_SUCCESS;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate and initialize the buffering. Fill the buffer.
+ */
+ pShared->paEntries = (PFATDIRENTRYUNION)RTMemAlloc(pShared->cbAllocatedForEntries);
+ if (!pShared->paEntries)
+ {
+ if (pShared->fFullyBuffered && !pShared->fIsLinearRootDir)
+ {
+ pShared->fFullyBuffered = false;
+ pShared->cbAllocatedForEntries = pThis->cbSector;
+ pShared->paEntries = (PFATDIRENTRYUNION)RTMemAlloc(pShared->cbAllocatedForEntries);
+ }
+ if (!pShared->paEntries)
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pShared->fFullyBuffered)
+ {
+ pShared->u.Full.cDirtySectors = 0;
+ pShared->u.Full.cSectors = pShared->cbAllocatedForEntries / pThis->cbSector;
+ pShared->u.Full.pbDirtySectors = (uint8_t *)RTMemAllocZ((pShared->u.Full.cSectors + 63) / 8);
+ if (pShared->u.Full.pbDirtySectors)
+ pShared->offEntriesOnDisk = offDisk != UINT64_MAX ? offDisk
+ : rtFsFatClusterToDiskOffset(pThis, idxCluster);
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ pShared->offEntriesOnDisk = rtFsFatClusterToDiskOffset(pThis, idxCluster);
+ pShared->u.Simple.offInDir = 0;
+ pShared->u.Simple.fDirty = false;
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, pShared->offEntriesOnDisk,
+ pShared->paEntries, pShared->cbAllocatedForEntries, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Link into parent directory so we can use it to update
+ * our directory entry.
+ */
+ if (pParentDir)
+ rtFsFatDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+ *ppSharedDir = pShared;
+ return VINF_SUCCESS;
+ }
+ }
+
+ /* Free the buffer on failure so rtFsFatDir_Close doesn't try do anything with it. */
+ RTMemFree(pShared->paEntries);
+ pShared->paEntries = NULL;
+ }
+
+ Assert(pShared->Core.cRefs == 1);
+ rtFsFatDirShrd_Release(pShared);
+ }
+ return rc;
+}
+
+
+/**
+ * Instantiates a new directory with a shared structure presupplied.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param pShared Referenced pointer to the shared structure. The
+ * reference is always CONSUMED.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsFatDir_NewWithShared(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pShared, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Create VFS object around the shared structure.
+ */
+ PRTFSFATDIR pNewDir;
+ int rc = RTVfsNewDir(&g_rtFsFatDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf,
+ NIL_RTVFSLOCK /*use volume lock*/, phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ * We CONSUME a reference to pShared here.
+ */
+ pNewDir->offDir = 0;
+ pNewDir->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+
+ rtFsFatDirShrd_Release(pShared);
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+}
+
+
+
+/**
+ * Instantiates a new directory VFS, creating the shared structure as necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pDirEntry The parent directory entry. This is NULL for the
+ * root directory.
+ * @param offEntryInDir The byte offset of the directory entry in the parent
+ * directory. UINT32_MAX if root directory.
+ * @param idxCluster The cluster where the directory content is to be
+ * found. This can be UINT32_MAX if a root FAT12/16
+ * directory.
+ * @param offDisk The disk byte offset of the FAT12/16 root directory.
+ * This is UINT64_MAX if idxCluster is given.
+ * @param cbDir The size of the directory.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsFatDir_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir,
+ uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSFATDIRSHRD pShared = (PRTFSFATDIRSHRD)rtFsFatDirShrd_LookupShared(pParentDir, offEntryInDir);
+ if (!pShared)
+ {
+ int rc = rtFsFatDirShrd_New(pThis, pParentDir, pDirEntry, offEntryInDir, idxCluster, offDisk, cbDir, &pShared);
+ if (RT_FAILURE(rc))
+ {
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+ }
+ }
+ return rtFsFatDir_NewWithShared(pThis, pShared, phVfsDir);
+}
+
+
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsFatVol_Close(void *pvThis)
+{
+ PRTFSFATVOL pThis = (PRTFSFATVOL)pvThis;
+ LogFlow(("rtFsFatVol_Close(%p)\n", pThis));
+
+ int rc = VINF_SUCCESS;
+ if (pThis->pRootDir != NULL)
+ {
+ Assert(RTListIsEmpty(&pThis->pRootDir->OpenChildren));
+ Assert(pThis->pRootDir->Core.cRefs == 1);
+ rc = rtFsFatDirShrd_Release(pThis->pRootDir);
+ pThis->pRootDir = NULL;
+ }
+
+ int rc2 = rtFsFatClusterMap_Destroy(pThis);
+ if (RT_SUCCESS(rc))
+ rc = rc2;
+
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsFatVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsFatVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSFATVOL pThis = (PRTFSFATVOL)pvThis;
+
+ rtFsFatDirShrd_Retain(pThis->pRootDir); /* consumed by the next call */
+ return rtFsFatDir_NewWithShared(pThis, pThis->pRootDir, phVfsDir);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsFatVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed)
+{
+
+
+ RT_NOREF(pvThis, off, cb, pfUsed);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+DECL_HIDDEN_CONST(const RTVFSOPS) g_rtFsFatVolOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_VFS,
+ "FatVol",
+ rtFsFatVol_Close,
+ rtFsFatVol_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSOPS_VERSION,
+ 0 /* fFeatures */,
+ rtFsFatVol_OpenRoot,
+ rtFsFatVol_QueryRangeState,
+ RTVFSOPS_VERSION
+};
+
+
+/**
+ * Tries to detect a DOS 1.x formatted image and fills in the BPB fields.
+ *
+ * There is no BPB here, but fortunately, there isn't much variety.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance, BPB derived fields are filled
+ * in on success.
+ * @param pBootSector The boot sector.
+ * @param pbFatSector Points to the FAT sector, or whatever is 512 bytes after
+ * the boot sector.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtFsFatVolTryInitDos1x(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, uint8_t const *pbFatSector,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * PC-DOS 1.0 does a 2fh byte short jump w/o any NOP following it.
+ * Instead the following are three words and a 9 byte build date
+ * string. The remaining space is zero filled.
+ *
+ * Note! No idea how this would look like for 8" floppies, only got 5"1/4'.
+ *
+ * ASSUME all non-BPB disks are using this format.
+ */
+ if ( pBootSector->abJmp[0] != 0xeb /* jmp rel8 */
+ || pBootSector->abJmp[1] < 0x2f
+ || pBootSector->abJmp[1] >= 0x80
+ || pBootSector->abJmp[2] == 0x90 /* nop */)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "No DOS v1.0 bootsector either - invalid jmp: %.3Rhxs", pBootSector->abJmp);
+ uint32_t const offJump = 2 + pBootSector->abJmp[1];
+ uint32_t const offFirstZero = 2 /*jmp */ + 3 * 2 /* words */ + 9 /* date string */;
+ Assert(offFirstZero >= RT_UOFFSETOF(FATBOOTSECTOR, Bpb));
+ uint32_t const cbZeroPad = RT_MIN(offJump - offFirstZero,
+ sizeof(pBootSector->Bpb.Bpb20) - (offFirstZero - RT_UOFFSETOF(FATBOOTSECTOR, Bpb)));
+
+ if (!ASMMemIsAllU8((uint8_t const *)pBootSector + offFirstZero, cbZeroPad, 0))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "No DOS v1.0 bootsector either - expected zero padding %#x LB %#x: %.*Rhxs",
+ offFirstZero, cbZeroPad, cbZeroPad, (uint8_t const *)pBootSector + offFirstZero);
+
+ /*
+ * Check the FAT ID so we can tell if this is double or single sided,
+ * as well as being a valid FAT12 start.
+ */
+ if ( (pbFatSector[0] != 0xfe && pbFatSector[0] != 0xff)
+ || pbFatSector[1] != 0xff
+ || pbFatSector[2] != 0xff)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "No DOS v1.0 bootsector either - unexpected start of FAT: %.3Rhxs", pbFatSector);
+
+ /*
+ * Fixed DOS 1.0 config.
+ */
+ pThis->enmFatType = RTFSFATTYPE_FAT12;
+ pThis->enmBpbVersion = RTFSFATBPBVER_NO_BPB;
+ pThis->bMedia = pbFatSector[0];
+ pThis->cReservedSectors = 1;
+ pThis->cbSector = 512;
+ pThis->cbCluster = pThis->bMedia == 0xfe ? 1024 : 512;
+ pThis->cFats = 2;
+ pThis->cbFat = 512;
+ pThis->aoffFats[0] = pThis->offBootSector + pThis->cReservedSectors * 512;
+ pThis->aoffFats[1] = pThis->aoffFats[0] + pThis->cbFat;
+ pThis->offRootDir = pThis->aoffFats[1] + pThis->cbFat;
+ pThis->cRootDirEntries = 512;
+ pThis->offFirstCluster = pThis->offRootDir + RT_ALIGN_32(pThis->cRootDirEntries * sizeof(FATDIRENTRY),
+ pThis->cbSector);
+ pThis->cbTotalSize = pThis->bMedia == 0xfe ? 8 * 1 * 40 * 512 : 8 * 2 * 40 * 512;
+ pThis->cClusters = (pThis->cbTotalSize - (pThis->offFirstCluster - pThis->offBootSector)) / pThis->cbCluster;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsFatVolTryInitDos2Plus that handles remaining BPB fields.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance, BPB derived fields are filled
+ * in on success.
+ * @param pBootSector The boot sector.
+ * @param fMaybe331 Set if it could be a DOS v3.31 BPB.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtFsFatVolTryInitDos2PlusBpb(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, bool fMaybe331, PRTERRINFO pErrInfo)
+{
+ pThis->enmBpbVersion = RTFSFATBPBVER_DOS_2_0;
+
+ /*
+ * Figure total sector count. Could both be zero, in which case we have to
+ * fall back on the size of the backing stuff.
+ */
+ if (pBootSector->Bpb.Bpb20.cTotalSectors16 != 0)
+ pThis->cbTotalSize = pBootSector->Bpb.Bpb20.cTotalSectors16 * pThis->cbSector;
+ else if ( pBootSector->Bpb.Bpb331.cTotalSectors32 != 0
+ && fMaybe331)
+ {
+ pThis->enmBpbVersion = RTFSFATBPBVER_DOS_3_31;
+ pThis->cbTotalSize = pBootSector->Bpb.Bpb331.cTotalSectors32 * (uint64_t)pThis->cbSector;
+ }
+ else
+ pThis->cbTotalSize = pThis->cbBacking - pThis->offBootSector;
+ if (pThis->cReservedSectors * pThis->cbSector >= pThis->cbTotalSize)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT12/16 total or reserved sector count: %#x vs %#x",
+ pThis->cReservedSectors, pThis->cbTotalSize / pThis->cbSector);
+
+ /*
+ * The fat size. Complete FAT offsets.
+ */
+ if ( pBootSector->Bpb.Bpb20.cSectorsPerFat == 0
+ || ((uint32_t)pBootSector->Bpb.Bpb20.cSectorsPerFat * pThis->cFats + 1) * pThis->cbSector > pThis->cbTotalSize)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT12/16 sectors per FAT: %#x (total sectors %#RX64)",
+ pBootSector->Bpb.Bpb20.cSectorsPerFat, pThis->cbTotalSize / pThis->cbSector);
+ pThis->cbFat = pBootSector->Bpb.Bpb20.cSectorsPerFat * pThis->cbSector;
+
+ AssertReturn(pThis->cFats < RT_ELEMENTS(pThis->aoffFats), VERR_VFS_BOGUS_FORMAT);
+ for (unsigned iFat = 1; iFat <= pThis->cFats; iFat++)
+ pThis->aoffFats[iFat] = pThis->aoffFats[iFat - 1] + pThis->cbFat;
+
+ /*
+ * Do root directory calculations.
+ */
+ pThis->idxRootDirCluster = UINT32_MAX;
+ pThis->offRootDir = pThis->aoffFats[pThis->cFats];
+ if (pThis->cRootDirEntries == 0)
+ return RTErrInfoSet(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Zero FAT12/16 root directory size");
+ pThis->cbRootDir = pThis->cRootDirEntries * sizeof(FATDIRENTRY);
+ pThis->cbRootDir = RT_ALIGN_32(pThis->cbRootDir, pThis->cbSector);
+
+ /*
+ * First cluster and cluster count checks and calcs. Determin FAT type.
+ */
+ pThis->offFirstCluster = pThis->offRootDir + pThis->cbRootDir;
+ uint64_t cbSystemStuff = pThis->offFirstCluster - pThis->offBootSector;
+ if (cbSystemStuff >= pThis->cbTotalSize)
+ return RTErrInfoSet(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT12/16 total size, root dir, or fat size");
+ pThis->cClusters = (pThis->cbTotalSize - cbSystemStuff) / pThis->cbCluster;
+
+ if (pThis->cClusters >= FAT_MAX_FAT16_DATA_CLUSTERS)
+ {
+ pThis->cClusters = FAT_MAX_FAT16_DATA_CLUSTERS;
+ pThis->enmFatType = RTFSFATTYPE_FAT16;
+ }
+ else if (pThis->cClusters >= FAT_MIN_FAT16_DATA_CLUSTERS)
+ pThis->enmFatType = RTFSFATTYPE_FAT16;
+ else
+ pThis->enmFatType = RTFSFATTYPE_FAT12; /** @todo Not sure if this is entirely the right way to go about it... */
+
+ uint32_t cClustersPerFat;
+ if (pThis->enmFatType == RTFSFATTYPE_FAT16)
+ cClustersPerFat = pThis->cbFat / 2;
+ else
+ cClustersPerFat = pThis->cbFat * 2 / 3;
+ if (pThis->cClusters > cClustersPerFat)
+ pThis->cClusters = cClustersPerFat;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsFatVolTryInitDos2Plus and rtFsFatVolTryInitDos2PlusFat32 that
+ * handles common extended BPBs fields.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance.
+ * @param bExtSignature The extended BPB signature.
+ * @param uSerialNumber The serial number.
+ * @param pachLabel Pointer to the volume label field.
+ * @param pachType Pointer to the file system type field.
+ */
+static void rtFsFatVolInitCommonEbpbBits(PRTFSFATVOL pThis, uint8_t bExtSignature, uint32_t uSerialNumber,
+ char const *pachLabel, char const *pachType)
+{
+ pThis->uSerialNo = uSerialNumber;
+ if (bExtSignature == FATEBPB_SIGNATURE)
+ {
+ memcpy(pThis->szLabel, pachLabel, RT_SIZEOFMEMB(FATEBPB, achLabel));
+ pThis->szLabel[RT_SIZEOFMEMB(FATEBPB, achLabel)] = '\0';
+ RTStrStrip(pThis->szLabel);
+
+ memcpy(pThis->szType, pachType, RT_SIZEOFMEMB(FATEBPB, achType));
+ pThis->szType[RT_SIZEOFMEMB(FATEBPB, achType)] = '\0';
+ RTStrStrip(pThis->szType);
+ }
+ else
+ {
+ pThis->szLabel[0] = '\0';
+ pThis->szType[0] = '\0';
+ }
+}
+
+
+/**
+ * Worker for rtFsFatVolTryInitDos2Plus that deals with FAT32.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance, BPB derived fields are filled
+ * in on success.
+ * @param pBootSector The boot sector.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtFsFatVolTryInitDos2PlusFat32(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, PRTERRINFO pErrInfo)
+{
+ pThis->enmFatType = RTFSFATTYPE_FAT32;
+ pThis->enmBpbVersion = pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE
+ ? RTFSFATBPBVER_FAT32_29 : RTFSFATBPBVER_FAT32_28;
+ pThis->fFat32Flags = pBootSector->Bpb.Fat32Ebpb.fFlags;
+
+ if (pBootSector->Bpb.Fat32Ebpb.uVersion != FAT32EBPB_VERSION_0_0)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Unsupported FAT32 version: %d.%d (%#x)",
+ RT_HI_U8(pBootSector->Bpb.Fat32Ebpb.uVersion), RT_LO_U8(pBootSector->Bpb.Fat32Ebpb.uVersion),
+ pBootSector->Bpb.Fat32Ebpb.uVersion);
+
+ /*
+ * Figure total sector count. We expected it to be filled in.
+ */
+ bool fUsing64BitTotalSectorCount = false;
+ if (pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors16 != 0)
+ pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors16 * pThis->cbSector;
+ else if (pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors32 != 0)
+ pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors32 * (uint64_t)pThis->cbSector;
+ else if ( pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 <= UINT64_MAX / 512
+ && pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 > 3
+ && pBootSector->Bpb.Fat32Ebpb.bExtSignature != FATEBPB_SIGNATURE_OLD)
+ {
+ pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 * pThis->cbSector;
+ fUsing64BitTotalSectorCount = true;
+ }
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "FAT32 total sector count out of range: %#RX64",
+ pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64);
+ if (pThis->cReservedSectors * pThis->cbSector >= pThis->cbTotalSize)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT32 total or reserved sector count: %#x vs %#x",
+ pThis->cReservedSectors, pThis->cbTotalSize / pThis->cbSector);
+
+ /*
+ * Fat size. We check the 16-bit field even if it probably should be zero all the time.
+ */
+ if (pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat != 0)
+ {
+ if ( pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 != 0
+ && pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 != pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Both 16-bit and 32-bit FAT size fields are set: %#RX16 vs %#RX32",
+ pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat, pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32);
+ pThis->cbFat = pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat * pThis->cbSector;
+ }
+ else
+ {
+ uint64_t cbFat = pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 * (uint64_t)pThis->cbSector;
+ if ( cbFat == 0
+ || cbFat >= FAT_MAX_FAT32_TOTAL_CLUSTERS * 4 + pThis->cbSector * 16)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus 32-bit FAT size: %#RX32", pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32);
+ pThis->cbFat = (uint32_t)cbFat;
+ }
+
+ /*
+ * Complete the FAT offsets and first cluster offset, then calculate number
+ * of data clusters.
+ */
+ AssertReturn(pThis->cFats < RT_ELEMENTS(pThis->aoffFats), VERR_VFS_BOGUS_FORMAT);
+ for (unsigned iFat = 1; iFat <= pThis->cFats; iFat++)
+ pThis->aoffFats[iFat] = pThis->aoffFats[iFat - 1] + pThis->cbFat;
+ pThis->offFirstCluster = pThis->aoffFats[pThis->cFats];
+
+ if (pThis->offFirstCluster - pThis->offBootSector >= pThis->cbTotalSize)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus 32-bit FAT size or total sector count: cFats=%d cbFat=%#x cbTotalSize=%#x",
+ pThis->cFats, pThis->cbFat, pThis->cbTotalSize);
+
+ uint64_t cClusters = (pThis->cbTotalSize - (pThis->offFirstCluster - pThis->offBootSector)) / pThis->cbCluster;
+ if (cClusters <= FAT_MAX_FAT32_DATA_CLUSTERS)
+ pThis->cClusters = (uint32_t)cClusters;
+ else
+ pThis->cClusters = FAT_MAX_FAT32_DATA_CLUSTERS;
+ if (pThis->cClusters > (pThis->cbFat / 4 - FAT_FIRST_DATA_CLUSTER))
+ pThis->cClusters = (pThis->cbFat / 4 - FAT_FIRST_DATA_CLUSTER);
+
+ /*
+ * Root dir cluster.
+ */
+ if ( pBootSector->Bpb.Fat32Ebpb.uRootDirCluster < FAT_FIRST_DATA_CLUSTER
+ || pBootSector->Bpb.Fat32Ebpb.uRootDirCluster >= pThis->cClusters)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT32 root directory cluster: %#x", pBootSector->Bpb.Fat32Ebpb.uRootDirCluster);
+ pThis->idxRootDirCluster = pBootSector->Bpb.Fat32Ebpb.uRootDirCluster;
+ pThis->offRootDir = pThis->offFirstCluster
+ + (pBootSector->Bpb.Fat32Ebpb.uRootDirCluster - FAT_FIRST_DATA_CLUSTER) * pThis->cbCluster;
+
+ /*
+ * Info sector.
+ */
+ if ( pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo == 0
+ || pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo == UINT16_MAX)
+ pThis->offFat32InfoSector = UINT64_MAX;
+ else if (pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo >= pThis->cReservedSectors)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT32 info sector number: %#x (reserved sectors %#x)",
+ pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo, pThis->cReservedSectors);
+ else
+ {
+ pThis->offFat32InfoSector = pThis->cbSector * pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo + pThis->offBootSector;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->offFat32InfoSector,
+ &pThis->Fat32InfoSector, sizeof(pThis->Fat32InfoSector), NULL);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, rc, "Failed to read FAT32 info sector at offset %#RX64", pThis->offFat32InfoSector);
+ if ( pThis->Fat32InfoSector.uSignature1 != FAT32INFOSECTOR_SIGNATURE_1
+ || pThis->Fat32InfoSector.uSignature2 != FAT32INFOSECTOR_SIGNATURE_2
+ || pThis->Fat32InfoSector.uSignature3 != FAT32INFOSECTOR_SIGNATURE_3)
+ return RTErrInfoSetF(pErrInfo, rc, "FAT32 info sector signature mismatch: %#x %#x %#x",
+ pThis->Fat32InfoSector.uSignature1, pThis->Fat32InfoSector.uSignature2,
+ pThis->Fat32InfoSector.uSignature3);
+ }
+
+ /*
+ * Boot sector copy.
+ */
+ if ( pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo == 0
+ || pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo == UINT16_MAX)
+ {
+ pThis->cBootSectorCopies = 0;
+ pThis->offBootSectorCopies = UINT64_MAX;
+ }
+ else if (pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo >= pThis->cReservedSectors)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bogus FAT32 info boot sector copy location: %#x (reserved sectors %#x)",
+ pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo, pThis->cReservedSectors);
+ else
+ {
+ /** @todo not sure if cbSector is correct here. */
+ pThis->cBootSectorCopies = 3;
+ if ( (uint32_t)pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo + pThis->cBootSectorCopies
+ > pThis->cReservedSectors)
+ pThis->cBootSectorCopies = (uint8_t)(pThis->cReservedSectors - pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo);
+ pThis->offBootSectorCopies = pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo * pThis->cbSector + pThis->offBootSector;
+ if ( pThis->offFat32InfoSector != UINT64_MAX
+ && pThis->offFat32InfoSector - pThis->offBootSectorCopies < (uint64_t)(pThis->cBootSectorCopies * pThis->cbSector))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "FAT32 info sector and boot sector copies overlap: %#x vs %#x",
+ pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo, pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo);
+ }
+
+ /*
+ * Serial number, label and type.
+ */
+ rtFsFatVolInitCommonEbpbBits(pThis, pBootSector->Bpb.Fat32Ebpb.bExtSignature, pBootSector->Bpb.Fat32Ebpb.uSerialNumber,
+ pBootSector->Bpb.Fat32Ebpb.achLabel,
+ fUsing64BitTotalSectorCount ? pBootSector->achOemName : pBootSector->Bpb.Fat32Ebpb.achLabel);
+ if (pThis->szType[0] == '\0')
+ memcpy(pThis->szType, "FAT32", 6);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tries to detect a DOS 2.0+ formatted image and fills in the BPB fields.
+ *
+ * We ASSUME BPB here, but need to figure out which version of the BPB it is,
+ * which is lots of fun.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT volume instance, BPB derived fields are filled
+ * in on success.
+ * @param pBootSector The boot sector.
+ * @param pbFatSector Points to the FAT sector, or whatever is 512 bytes after
+ * the boot sector. On successful return it will contain
+ * the first FAT sector.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtFsFatVolTryInitDos2Plus(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, uint8_t *pbFatSector, PRTERRINFO pErrInfo)
+{
+ /*
+ * Check if we've got a known jump instruction first, because that will
+ * give us a max (E)BPB size hint.
+ */
+ uint8_t offJmp = UINT8_MAX;
+ if ( pBootSector->abJmp[0] == 0xeb
+ && pBootSector->abJmp[1] <= 0x7f)
+ offJmp = pBootSector->abJmp[1] + 2;
+ else if ( pBootSector->abJmp[0] == 0x90
+ && pBootSector->abJmp[1] == 0xeb
+ && pBootSector->abJmp[2] <= 0x7f)
+ offJmp = pBootSector->abJmp[2] + 3;
+ else if ( pBootSector->abJmp[0] == 0xe9
+ && pBootSector->abJmp[2] <= 0x7f)
+ offJmp = RT_MIN(127, RT_MAKE_U16(pBootSector->abJmp[1], pBootSector->abJmp[2]));
+ uint8_t const cbMaxBpb = offJmp - RT_UOFFSETOF(FATBOOTSECTOR, Bpb);
+
+ /*
+ * Do the basic DOS v2.0 BPB fields.
+ */
+ if (cbMaxBpb < sizeof(FATBPB20))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "DOS signature, but jmp too short for any BPB: %#x (max %#x BPB)", offJmp, cbMaxBpb);
+
+ if (pBootSector->Bpb.Bpb20.cFats == 0)
+ return RTErrInfoSet(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, number of FATs is zero, so not FAT file system");
+ if (pBootSector->Bpb.Bpb20.cFats > 4)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "DOS signature, too many FATs: %#x", pBootSector->Bpb.Bpb20.cFats);
+ pThis->cFats = pBootSector->Bpb.Bpb20.cFats;
+
+ if (!FATBPB_MEDIA_IS_VALID(pBootSector->Bpb.Bpb20.bMedia))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "DOS signature, invalid media byte: %#x", pBootSector->Bpb.Bpb20.bMedia);
+ pThis->bMedia = pBootSector->Bpb.Bpb20.bMedia;
+
+ if (!RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cbSector))
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "DOS signature, sector size not power of two: %#x", pBootSector->Bpb.Bpb20.cbSector);
+ if ( pBootSector->Bpb.Bpb20.cbSector != 512
+ && pBootSector->Bpb.Bpb20.cbSector != 4096
+ && pBootSector->Bpb.Bpb20.cbSector != 1024
+ && pBootSector->Bpb.Bpb20.cbSector != 128)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "DOS signature, unsupported sector size: %#x", pBootSector->Bpb.Bpb20.cbSector);
+ pThis->cbSector = pBootSector->Bpb.Bpb20.cbSector;
+
+ if ( !RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cSectorsPerCluster)
+ || !pBootSector->Bpb.Bpb20.cSectorsPerCluster)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, cluster size not non-zero power of two: %#x",
+ pBootSector->Bpb.Bpb20.cSectorsPerCluster);
+ pThis->cbCluster = pBootSector->Bpb.Bpb20.cSectorsPerCluster * pThis->cbSector;
+
+ uint64_t const cMaxRoot = (pThis->cbBacking - pThis->offBootSector - 512) / sizeof(FATDIRENTRY); /* we'll check again later. */
+ if (pBootSector->Bpb.Bpb20.cMaxRootDirEntries >= cMaxRoot)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "DOS signature, too many root entries: %#x (max %#RX64)",
+ pBootSector->Bpb.Bpb20.cSectorsPerCluster, cMaxRoot);
+ pThis->cRootDirEntries = pBootSector->Bpb.Bpb20.cMaxRootDirEntries;
+
+ if ( pBootSector->Bpb.Bpb20.cReservedSectors == 0
+ || pBootSector->Bpb.Bpb20.cReservedSectors >= _32K)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "DOS signature, bogus reserved sector count: %#x", pBootSector->Bpb.Bpb20.cReservedSectors);
+ pThis->cReservedSectors = pBootSector->Bpb.Bpb20.cReservedSectors;
+ pThis->aoffFats[0] = pThis->offBootSector + pThis->cReservedSectors * pThis->cbSector;
+
+ /*
+ * Jump ahead and check for FAT32 EBPB.
+ * If found, we simply ASSUME it's a FAT32 file system.
+ */
+ int rc;
+ if ( ( sizeof(FAT32EBPB) <= cbMaxBpb
+ && pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE)
+ || ( RT_UOFFSETOF(FAT32EBPB, achLabel) <= cbMaxBpb
+ && pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE_OLD) )
+ {
+ rc = rtFsFatVolTryInitDos2PlusFat32(pThis, pBootSector, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ /*
+ * Check for extended BPB, otherwise we'll have to make qualified guesses
+ * about what kind of BPB we're up against based on jmp offset and zero fields.
+ * ASSUMES either FAT16 or FAT12.
+ */
+ if ( ( sizeof(FATEBPB) <= cbMaxBpb
+ && pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE)
+ || ( RT_UOFFSETOF(FATEBPB, achLabel) <= cbMaxBpb
+ && pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE_OLD) )
+ {
+ rtFsFatVolInitCommonEbpbBits(pThis, pBootSector->Bpb.Ebpb.bExtSignature, pBootSector->Bpb.Ebpb.uSerialNumber,
+ pBootSector->Bpb.Ebpb.achLabel, pBootSector->Bpb.Ebpb.achType);
+ rc = rtFsFatVolTryInitDos2PlusBpb(pThis, pBootSector, true /*fMaybe331*/, pErrInfo);
+ pThis->enmBpbVersion = pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE
+ ? RTFSFATBPBVER_EXT_29 : RTFSFATBPBVER_EXT_28;
+ }
+ else
+ rc = rtFsFatVolTryInitDos2PlusBpb(pThis, pBootSector, cbMaxBpb >= sizeof(FATBPB331), pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (pThis->szType[0] == '\0')
+ memcpy(pThis->szType, pThis->enmFatType == RTFSFATTYPE_FAT12 ? "FAT12" : "FAT16", 6);
+ }
+
+ /*
+ * Check the FAT ID. May have to read a bit of the FAT into the buffer.
+ */
+ if (pThis->aoffFats[0] != pThis->offBootSector + 512)
+ {
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->aoffFats[0], pbFatSector, 512, NULL);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSet(pErrInfo, rc, "error reading first FAT sector");
+ }
+ if (pbFatSector[0] != pThis->bMedia)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Media byte and FAT ID mismatch: %#x vs %#x (%.7Rhxs)", pbFatSector[0], pThis->bMedia, pbFatSector);
+ uint32_t idxOurEndOfChain;
+ switch (pThis->enmFatType)
+ {
+ case RTFSFATTYPE_FAT12:
+ if ((pbFatSector[1] & 0xf) != 0xf)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT12): %.3Rhxs", pbFatSector);
+ pThis->idxMaxLastCluster = FAT_LAST_FAT12_DATA_CLUSTER;
+ pThis->idxEndOfChain = (pbFatSector[1] >> 4) | ((uint32_t)pbFatSector[2] << 4);
+ idxOurEndOfChain = FAT_FIRST_FAT12_EOC | 0xf;
+ break;
+
+ case RTFSFATTYPE_FAT16:
+ if (pbFatSector[1] != 0xff)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT16): %.4Rhxs", pbFatSector);
+ pThis->idxMaxLastCluster = FAT_LAST_FAT16_DATA_CLUSTER;
+ pThis->idxEndOfChain = RT_MAKE_U16(pbFatSector[2], pbFatSector[3]);
+ idxOurEndOfChain = FAT_FIRST_FAT16_EOC | 0xf;
+ break;
+
+ case RTFSFATTYPE_FAT32:
+ if ( pbFatSector[1] != 0xff
+ || pbFatSector[2] != 0xff
+ || (pbFatSector[3] & 0x0f) != 0x0f)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT32): %.8Rhxs", pbFatSector);
+ pThis->idxMaxLastCluster = FAT_LAST_FAT32_DATA_CLUSTER;
+ pThis->idxEndOfChain = RT_MAKE_U32_FROM_U8(pbFatSector[4], pbFatSector[5], pbFatSector[6], pbFatSector[7]);
+ idxOurEndOfChain = FAT_FIRST_FAT32_EOC | 0xf;
+ break;
+
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_2);
+ }
+
+ if (pThis->idxEndOfChain <= pThis->idxMaxLastCluster)
+ {
+ Log(("rtFsFatVolTryInitDos2Plus: Bogus idxEndOfChain=%#x, using %#x instead\n", pThis->idxEndOfChain, idxOurEndOfChain));
+ pThis->idxEndOfChain = idxOurEndOfChain;
+ }
+
+ RT_NOREF(pbFatSector);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Given a power of two value @a cb return exponent value.
+ *
+ * @returns Shift count
+ * @param cb The value.
+ */
+static uint8_t rtFsFatVolCalcByteShiftCount(uint32_t cb)
+{
+ Assert(RT_IS_POWER_OF_TWO(cb));
+ unsigned iBit = ASMBitFirstSetU32(cb);
+ Assert(iBit >= 1);
+ iBit--;
+ return iBit;
+}
+
+
+/**
+ * Worker for RTFsFatVolOpen.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FAT VFS instance to initialize.
+ * @param hVfsSelf The FAT VFS handle (no reference consumed).
+ * @param hVfsBacking The file backing the alleged FAT file system.
+ * Reference is consumed (via rtFsFatVol_Destroy).
+ * @param fReadOnly Readonly or readwrite mount.
+ * @param offBootSector The boot sector offset in bytes.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsFatVolTryInit(PRTFSFATVOL pThis, RTVFS hVfsSelf, RTVFSFILE hVfsBacking,
+ bool fReadOnly, uint64_t offBootSector, PRTERRINFO pErrInfo)
+{
+ /*
+ * First initialize the state so that rtFsFatVol_Destroy won't trip up.
+ */
+ pThis->hVfsSelf = hVfsSelf;
+ pThis->hVfsBacking = hVfsBacking; /* Caller referenced it for us, we consume it; rtFsFatVol_Destroy releases it. */
+ pThis->cbBacking = 0;
+ pThis->offBootSector = offBootSector;
+ pThis->offNanoUTC = RTTimeLocalDeltaNano();
+ pThis->offMinUTC = pThis->offNanoUTC / RT_NS_1MIN;
+ pThis->fReadOnly = fReadOnly;
+ pThis->cReservedSectors = 1;
+
+ pThis->cbSector = 512;
+ pThis->cbCluster = 512;
+ pThis->cClusters = 0;
+ pThis->offFirstCluster = 0;
+ pThis->cbTotalSize = 0;
+
+ pThis->enmFatType = RTFSFATTYPE_INVALID;
+ pThis->cFatEntries = 0;
+ pThis->cFats = 0;
+ pThis->cbFat = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aoffFats); i++)
+ pThis->aoffFats[i] = UINT64_MAX;
+ pThis->pFatCache = NULL;
+
+ pThis->offRootDir = UINT64_MAX;
+ pThis->idxRootDirCluster = UINT32_MAX;
+ pThis->cRootDirEntries = UINT32_MAX;
+ pThis->cbRootDir = 0;
+ pThis->pRootDir = NULL;
+
+ pThis->uSerialNo = 0;
+ pThis->szLabel[0] = '\0';
+ pThis->szType[0] = '\0';
+ pThis->cBootSectorCopies = 0;
+ pThis->fFat32Flags = 0;
+ pThis->offBootSectorCopies = UINT64_MAX;
+ pThis->offFat32InfoSector = UINT64_MAX;
+ RT_ZERO(pThis->Fat32InfoSector);
+
+ /*
+ * Get stuff that may fail.
+ */
+ int rc = RTVfsFileQuerySize(hVfsBacking, &pThis->cbBacking);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->cbTotalSize = pThis->cbBacking - pThis->offBootSector;
+
+ /*
+ * Read the boot sector and the following sector (start of the allocation
+ * table unless it a FAT32 FS). We'll then validate the boot sector and
+ * start of the FAT, expanding the BPB into the instance data.
+ */
+ union
+ {
+ uint8_t ab[512*2];
+ uint16_t au16[512*2 / 2];
+ uint32_t au32[512*2 / 4];
+ FATBOOTSECTOR BootSector;
+ FAT32INFOSECTOR InfoSector;
+ } Buf;
+ RT_ZERO(Buf);
+
+ rc = RTVfsFileReadAt(hVfsBacking, offBootSector, &Buf.BootSector, 512 * 2, NULL);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSet(pErrInfo, rc, "Unable to read bootsect");
+
+ /*
+ * Extract info from the BPB and validate the two special FAT entries.
+ *
+ * Check the DOS signature first. The PC-DOS 1.0 boot floppy does not have
+ * a signature and we ASSUME this is the case for all floppies formated by it.
+ */
+ if (Buf.BootSector.uSignature != FATBOOTSECTOR_SIGNATURE)
+ {
+ if (Buf.BootSector.uSignature != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "No DOS bootsector signature: %#06x", Buf.BootSector.uSignature);
+ rc = rtFsFatVolTryInitDos1x(pThis, &Buf.BootSector, &Buf.ab[512], pErrInfo);
+ }
+ else
+ rc = rtFsFatVolTryInitDos2Plus(pThis, &Buf.BootSector, &Buf.ab[512], pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Calc shift counts.
+ */
+ pThis->cSectorByteShift = rtFsFatVolCalcByteShiftCount(pThis->cbSector);
+ pThis->cClusterByteShift = rtFsFatVolCalcByteShiftCount(pThis->cbCluster);
+
+ /*
+ * Setup the FAT cache.
+ */
+ rc = rtFsFatClusterMap_Create(pThis, &Buf.ab[512], pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Create the root directory fun.
+ */
+ if (pThis->idxRootDirCluster == UINT32_MAX)
+ rc = rtFsFatDirShrd_New(pThis, NULL /*pParentDir*/, NULL /*pDirEntry*/, UINT32_MAX /*offEntryInDir*/,
+ UINT32_MAX, pThis->offRootDir, pThis->cbRootDir, &pThis->pRootDir);
+ else
+ rc = rtFsFatDirShrd_New(pThis, NULL /*pParentDir*/, NULL /*pDirEntry*/, UINT32_MAX /*offEntryInDir*/,
+ pThis->idxRootDirCluster, UINT64_MAX, pThis->cbRootDir, &pThis->pRootDir);
+ return rc;
+}
+
+
+/**
+ * Opens a FAT file system volume.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFileIn The file or device backing the volume.
+ * @param fReadOnly Whether to mount it read-only.
+ * @param offBootSector The offset of the boot sector relative to the start
+ * of @a hVfsFileIn. Pass 0 for floppies.
+ * @param phVfs Where to return the virtual file system handle.
+ * @param pErrInfo Where to return additional error information.
+ */
+RTDECL(int) RTFsFatVolOpen(RTVFSFILE hVfsFileIn, bool fReadOnly, uint64_t offBootSector, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ /*
+ * Quick input validation.
+ */
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ *phVfs = NIL_RTVFS;
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a new FAT VFS instance and try initialize it using the given input file.
+ */
+ RTVFS hVfs = NIL_RTVFS;
+ void *pvThis = NULL;
+ int rc = RTVfsNew(&g_rtFsFatVolOps, sizeof(RTFSFATVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, &pvThis);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsFatVolTryInit((PRTFSFATVOL)pvThis, hVfs, hVfsFileIn, fReadOnly, offBootSector, pErrInfo);
+ if (RT_SUCCESS(rc))
+ *phVfs = hVfs;
+ else
+ RTVfsRelease(hVfs);
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+ return rc;
+}
+
+
+
+
+/**
+ * Fills a range in the file with zeros in the most efficient manner.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFile The file to write to.
+ * @param off Where to start filling with zeros.
+ * @param cbZeros How many zero blocks to write.
+ */
+static int rtFsFatVolWriteZeros(RTVFSFILE hVfsFile, uint64_t off, uint32_t cbZeros)
+{
+ while (cbZeros > 0)
+ {
+ uint32_t cbToWrite = sizeof(g_abRTZero64K);
+ if (cbToWrite > cbZeros)
+ cbToWrite = cbZeros;
+ int rc = RTVfsFileWriteAt(hVfsFile, off, g_abRTZero64K, cbToWrite, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ off += cbToWrite;
+ cbZeros -= cbToWrite;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Formats a FAT volume.
+ *
+ * @returns IRPT status code.
+ * @param hVfsFile The volume file.
+ * @param offVol The offset into @a hVfsFile of the file.
+ * Typically 0.
+ * @param cbVol The size of the volume. Pass 0 if the rest of
+ * hVfsFile should be used.
+ * @param fFlags See RTFSFATVOL_FMT_F_XXX.
+ * @param cbSector The logical sector size. Must be power of two.
+ * Optional, pass zero to use 512.
+ * @param cSectorsPerCluster Number of sectors per cluster. Power of two.
+ * Optional, pass zero to auto detect.
+ * @param enmFatType The FAT type (12, 16, 32) to use.
+ * Optional, pass RTFSFATTYPE_INVALID for default.
+ * @param cHeads The number of heads to report in the BPB.
+ * Optional, pass zero to auto detect.
+ * @param cSectorsPerTrack The number of sectors per track to put in the
+ * BPB. Optional, pass zero to auto detect.
+ * @param bMedia The media byte value and FAT ID to use.
+ * Optional, pass zero to auto detect.
+ * @param cRootDirEntries Number of root directory entries.
+ * Optional, pass zero to auto detect.
+ * @param cHiddenSectors Number of hidden sectors. Pass 0 for
+ * unpartitioned media.
+ * @param pErrInfo Additional error information, maybe. Optional.
+ */
+RTDECL(int) RTFsFatVolFormat(RTVFSFILE hVfsFile, uint64_t offVol, uint64_t cbVol, uint32_t fFlags, uint16_t cbSector,
+ uint16_t cSectorsPerCluster, RTFSFATTYPE enmFatType, uint32_t cHeads, uint32_t cSectorsPerTrack,
+ uint8_t bMedia, uint16_t cRootDirEntries, uint32_t cHiddenSectors, PRTERRINFO pErrInfo)
+{
+ int rc;
+ uint32_t cFats = 2;
+
+ /*
+ * Validate input.
+ */
+ if (!cbSector)
+ cbSector = 512;
+ else
+ AssertMsgReturn( cbSector == 128
+ || cbSector == 512
+ || cbSector == 1024
+ || cbSector == 4096,
+ ("cbSector=%#x\n", cbSector),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(cSectorsPerCluster == 0 || (cSectorsPerCluster <= 128 && RT_IS_POWER_OF_TWO(cSectorsPerCluster)),
+ ("cSectorsPerCluster=%#x\n", cSectorsPerCluster), VERR_INVALID_PARAMETER);
+ if (bMedia != 0)
+ {
+ AssertMsgReturn(FAT_ID_IS_VALID(bMedia), ("bMedia=%#x\n", bMedia), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(FATBPB_MEDIA_IS_VALID(bMedia), ("bMedia=%#x\n", bMedia), VERR_INVALID_PARAMETER);
+ }
+ AssertReturn(!(fFlags & ~RTFSFATVOL_FMT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(enmFatType >= RTFSFATTYPE_INVALID && enmFatType < RTFSFATTYPE_END, VERR_INVALID_PARAMETER);
+
+ if (!cbVol)
+ {
+ uint64_t cbFile;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ AssertRCReturn(rc, rc);
+ AssertMsgReturn(cbFile > offVol, ("cbFile=%#RX64 offVol=%#RX64\n", cbFile, offVol), VERR_INVALID_PARAMETER);
+ cbVol = cbFile - offVol;
+ }
+ uint64_t const cSectorsInVol = cbVol / cbSector;
+
+ /*
+ * Guess defaults if necessary.
+ */
+ if (!cSectorsPerCluster || !cHeads || !cSectorsPerTrack || !bMedia || !cRootDirEntries)
+ {
+ static struct
+ {
+ uint64_t cbVol;
+ uint8_t bMedia;
+ uint8_t cHeads;
+ uint8_t cSectorsPerTrack;
+ uint8_t cSectorsPerCluster;
+ uint16_t cRootDirEntries;
+ } s_aDefaults[] =
+ {
+ /* cbVol, bMedia, cHeads, cSectorsPTrk, cSectorsPClstr, cRootDirEntries */
+ { 163840, 0xfe, /* cyl: 40,*/ 1, 8, 1, 64 },
+ { 184320, 0xfc, /* cyl: 40,*/ 1, 9, 2, 64 },
+ { 327680, 0xff, /* cyl: 40,*/ 2, 8, 2, 112 },
+ { 368640, 0xfd, /* cyl: 40,*/ 2, 9, 2, 112 },
+ { 737280, 0xf9, /* cyl: 80,*/ 2, 9, 2, 112 },
+ { 1228800, 0xf9, /* cyl: 80,*/ 2, 15, 2, 112 },
+ { 1474560, 0xf0, /* cyl: 80,*/ 2, 18, 1, 224 },
+ { 2949120, 0xf0, /* cyl: 80,*/ 2, 36, 2, 224 },
+ { 528482304, 0xf8, /* cyl: 1024,*/ 16, 63, 0, 512 }, // 504MB limit
+ { UINT64_C(7927234560), 0xf8, /* cyl: 1024,*/ 240, 63, 0, 512 }, // 7.3GB limit
+ { UINT64_C(8422686720), 0xf8, /* cyl: 1024,*/ 255, 63, 0, 512 }, // 7.84GB limit
+
+ };
+ uint32_t iDefault = 0;
+ while ( iDefault < RT_ELEMENTS(s_aDefaults) - 1U
+ && cbVol > s_aDefaults[iDefault].cbVol)
+ iDefault++;
+ if (!cHeads)
+ cHeads = s_aDefaults[iDefault].cHeads;
+ if (!cSectorsPerTrack)
+ cSectorsPerTrack = s_aDefaults[iDefault].cSectorsPerTrack;
+ if (!bMedia)
+ bMedia = s_aDefaults[iDefault].bMedia;
+ if (!cRootDirEntries)
+ cRootDirEntries = s_aDefaults[iDefault].cRootDirEntries;
+ if (!cSectorsPerCluster)
+ {
+ cSectorsPerCluster = s_aDefaults[iDefault].cSectorsPerCluster;
+ if (!cSectorsPerCluster)
+ {
+ uint32_t cbFat12Overhead = cbSector /* boot sector */
+ + RT_ALIGN_32(FAT_MAX_FAT12_TOTAL_CLUSTERS * 3 / 2, cbSector) * cFats /* FATs */
+ + RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector) /* root dir */;
+ uint32_t cbFat16Overhead = cbSector /* boot sector */
+ + RT_ALIGN_32(FAT_MAX_FAT16_TOTAL_CLUSTERS * 2, cbSector) * cFats /* FATs */
+ + RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector) /* root dir */;
+
+ if ( enmFatType == RTFSFATTYPE_FAT12
+ || cbVol <= cbFat12Overhead + FAT_MAX_FAT12_DATA_CLUSTERS * 4 * cbSector)
+ {
+ enmFatType = RTFSFATTYPE_FAT12;
+ cSectorsPerCluster = 1;
+ while ( cSectorsPerCluster < 128
+ && cSectorsInVol
+ > cbFat12Overhead / cbSector
+ + (uint32_t)cSectorsPerCluster * FAT_MAX_FAT12_DATA_CLUSTERS
+ + cSectorsPerCluster - 1)
+ cSectorsPerCluster <<= 1;
+ }
+ else if ( enmFatType == RTFSFATTYPE_FAT16
+ || cbVol <= cbFat16Overhead + FAT_MAX_FAT16_DATA_CLUSTERS * 128 * cbSector)
+ {
+ enmFatType = RTFSFATTYPE_FAT16;
+ cSectorsPerCluster = 1;
+ while ( cSectorsPerCluster < 128
+ && cSectorsInVol
+ > cbFat12Overhead / cbSector
+ + (uint32_t)cSectorsPerCluster * FAT_MAX_FAT16_DATA_CLUSTERS
+ + cSectorsPerCluster - 1)
+ cSectorsPerCluster <<= 1;
+ }
+ else
+ {
+ /* The target here is keeping the FAT size below 8MB. Seems windows
+ likes a minimum 4KB cluster size as wells as a max of 32KB (googling). */
+ enmFatType = RTFSFATTYPE_FAT32;
+ uint32_t cbFat32Overhead = cbSector * 32 /* boot sector, info sector, boot sector copies, reserved sectors */
+ + _8M * cFats;
+ if (cbSector >= _4K)
+ cSectorsPerCluster = 1;
+ else
+ cSectorsPerCluster = _4K / cbSector;
+ while ( cSectorsPerCluster < 128
+ && cSectorsPerCluster * cbSector < _32K
+ && cSectorsInVol > cbFat32Overhead / cbSector + (uint64_t)cSectorsPerCluster * _2M)
+ cSectorsPerCluster <<= 1;
+ }
+ }
+ }
+ }
+ Assert(cSectorsPerCluster);
+ Assert(cRootDirEntries);
+ uint32_t cbRootDir = RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector);
+ uint32_t const cbCluster = cSectorsPerCluster * cbSector;
+
+ /*
+ * If we haven't figured out the FAT type yet, do so.
+ * The file system code determins the FAT based on cluster counts,
+ * so we must do so here too.
+ */
+ if (enmFatType == RTFSFATTYPE_INVALID)
+ {
+ uint32_t cbFat12Overhead = cbSector /* boot sector */
+ + RT_ALIGN_32(FAT_MAX_FAT12_TOTAL_CLUSTERS * 3 / 2, cbSector) * cFats /* FATs */
+ + RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector) /* root dir */;
+ if ( cbVol <= cbFat12Overhead + cbCluster
+ || (cbVol - cbFat12Overhead) / cbCluster <= FAT_MAX_FAT12_DATA_CLUSTERS)
+ enmFatType = RTFSFATTYPE_FAT12;
+ else
+ {
+ uint32_t cbFat16Overhead = cbSector /* boot sector */
+ + RT_ALIGN_32(FAT_MAX_FAT16_TOTAL_CLUSTERS * 2, cbSector) * cFats /* FATs */
+ + cbRootDir;
+ if ( cbVol <= cbFat16Overhead + cbCluster
+ || (cbVol - cbFat16Overhead) / cbCluster <= FAT_MAX_FAT16_DATA_CLUSTERS)
+ enmFatType = RTFSFATTYPE_FAT16;
+ else
+ enmFatType = RTFSFATTYPE_FAT32;
+ }
+ }
+ if (enmFatType == RTFSFATTYPE_FAT32)
+ cbRootDir = cbCluster;
+
+ /*
+ * Calculate the FAT size and number of data cluster.
+ *
+ * Since the FAT size depends on how many data clusters there are, we start
+ * with a minimum FAT size and maximum clust count, then recalucate it. The
+ * result isn't necessarily stable, so we will only retry stabalizing the
+ * result a few times.
+ */
+ uint32_t cbReservedFixed = enmFatType == RTFSFATTYPE_FAT32 ? 32 * cbSector : cbSector + cbRootDir;
+ uint32_t cbFat = cbSector;
+ if (cbReservedFixed + cbFat * cFats >= cbVol)
+ return RTErrInfoSetF(pErrInfo, VERR_DISK_FULL, "volume is too small (cbVol=%#RX64 rsvd=%#x cbFat=%#x cFat=%#x)",
+ cbVol, cbReservedFixed, cbFat, cFats);
+ uint32_t cMaxClusters = enmFatType == RTFSFATTYPE_FAT12 ? FAT_MAX_FAT12_DATA_CLUSTERS
+ : enmFatType == RTFSFATTYPE_FAT16 ? FAT_MAX_FAT16_DATA_CLUSTERS
+ : FAT_MAX_FAT12_DATA_CLUSTERS;
+ uint32_t cClusters = (uint32_t)RT_MIN((cbVol - cbReservedFixed - cbFat * cFats) / cbCluster, cMaxClusters);
+ uint32_t cPrevClusters;
+ uint32_t cTries = 4;
+ do
+ {
+ cPrevClusters = cClusters;
+ switch (enmFatType)
+ {
+ case RTFSFATTYPE_FAT12:
+ cbFat = (uint32_t)RT_MIN(FAT_MAX_FAT12_TOTAL_CLUSTERS, cClusters) * 3 / 2;
+ break;
+ case RTFSFATTYPE_FAT16:
+ cbFat = (uint32_t)RT_MIN(FAT_MAX_FAT16_TOTAL_CLUSTERS, cClusters) * 2;
+ break;
+ case RTFSFATTYPE_FAT32:
+ cbFat = (uint32_t)RT_MIN(FAT_MAX_FAT32_TOTAL_CLUSTERS, cClusters) * 4;
+ cbFat = RT_ALIGN_32(cbFat, _4K);
+ break;
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2);
+ }
+ cbFat = RT_ALIGN_32(cbFat, cbSector);
+ if (cbReservedFixed + cbFat * cFats >= cbVol)
+ return RTErrInfoSetF(pErrInfo, VERR_DISK_FULL, "volume is too small (cbVol=%#RX64 rsvd=%#x cbFat=%#x cFat=%#x)",
+ cbVol, cbReservedFixed, cbFat, cFats);
+ cClusters = (uint32_t)RT_MIN((cbVol - cbReservedFixed - cbFat * cFats) / cbCluster, cMaxClusters);
+ } while ( cClusters != cPrevClusters
+ && cTries-- > 0);
+ uint64_t const cTotalSectors = cClusters * (uint64_t)cSectorsPerCluster + (cbReservedFixed + cbFat * cFats) / cbSector;
+
+ /*
+ * Check that the file system type and cluster count matches up. If they
+ * don't the type will be misdetected.
+ *
+ * Note! These assertions could trigger if the above calculations are wrong.
+ */
+ switch (enmFatType)
+ {
+ case RTFSFATTYPE_FAT12:
+ AssertMsgReturn(cClusters >= FAT_MIN_FAT12_DATA_CLUSTERS && cClusters <= FAT_MAX_FAT12_DATA_CLUSTERS,
+ ("cClusters=%#x\n", cClusters), VERR_OUT_OF_RANGE);
+ break;
+ case RTFSFATTYPE_FAT16:
+ AssertMsgReturn(cClusters >= FAT_MIN_FAT16_DATA_CLUSTERS && cClusters <= FAT_MAX_FAT16_DATA_CLUSTERS,
+ ("cClusters=%#x\n", cClusters), VERR_OUT_OF_RANGE);
+ break;
+ case RTFSFATTYPE_FAT32:
+ AssertMsgReturn(cClusters >= FAT_MIN_FAT32_DATA_CLUSTERS && cClusters <= FAT_MAX_FAT32_DATA_CLUSTERS,
+ ("cClusters=%#x\n", cClusters), VERR_OUT_OF_RANGE);
+ RT_FALL_THRU();
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2);
+ }
+
+ /*
+ * Okay, create the boot sector.
+ */
+ size_t cbBuf = RT_MAX(RT_MAX(_64K, cbCluster), cbSector * 2U);
+ uint8_t *pbBuf = (uint8_t *)RTMemTmpAllocZ(cbBuf);
+ AssertReturn(pbBuf, VERR_NO_TMP_MEMORY);
+
+ const char *pszLastOp = "boot sector";
+ PFATBOOTSECTOR pBootSector = (PFATBOOTSECTOR)pbBuf;
+ pBootSector->abJmp[0] = 0xeb;
+ pBootSector->abJmp[1] = RT_UOFFSETOF(FATBOOTSECTOR, Bpb)
+ + (enmFatType == RTFSFATTYPE_FAT32 ? sizeof(FAT32EBPB) : sizeof(FATEBPB)) - 2;
+ pBootSector->abJmp[2] = 0x90;
+ memcpy(pBootSector->achOemName, enmFatType == RTFSFATTYPE_FAT32 ? "FAT32 " : "IPRT 6.2", sizeof(pBootSector->achOemName));
+ pBootSector->Bpb.Bpb331.cbSector = (uint16_t)cbSector;
+ pBootSector->Bpb.Bpb331.cSectorsPerCluster = (uint8_t)cSectorsPerCluster;
+ pBootSector->Bpb.Bpb331.cReservedSectors = enmFatType == RTFSFATTYPE_FAT32 ? cbReservedFixed / cbSector : 1;
+ pBootSector->Bpb.Bpb331.cFats = (uint8_t)cFats;
+ pBootSector->Bpb.Bpb331.cMaxRootDirEntries = enmFatType == RTFSFATTYPE_FAT32 ? 0 : cRootDirEntries;
+ pBootSector->Bpb.Bpb331.cTotalSectors16 = cTotalSectors <= UINT16_MAX ? (uint16_t)cTotalSectors : 0;
+ pBootSector->Bpb.Bpb331.bMedia = bMedia;
+ pBootSector->Bpb.Bpb331.cSectorsPerFat = enmFatType == RTFSFATTYPE_FAT32 ? 0 : cbFat / cbSector;
+ pBootSector->Bpb.Bpb331.cSectorsPerTrack = cSectorsPerTrack;
+ pBootSector->Bpb.Bpb331.cTracksPerCylinder = cHeads;
+ pBootSector->Bpb.Bpb331.cHiddenSectors = cHiddenSectors;
+ /* XP barfs if both cTotalSectors32 and cTotalSectors16 are set */
+ pBootSector->Bpb.Bpb331.cTotalSectors32 = cTotalSectors <= UINT32_MAX && pBootSector->Bpb.Bpb331.cTotalSectors16 == 0
+ ? (uint32_t)cTotalSectors : 0;
+ if (enmFatType != RTFSFATTYPE_FAT32)
+ {
+ pBootSector->Bpb.Ebpb.bInt13Drive = 0;
+ pBootSector->Bpb.Ebpb.bReserved = 0;
+ pBootSector->Bpb.Ebpb.bExtSignature = FATEBPB_SIGNATURE;
+ pBootSector->Bpb.Ebpb.uSerialNumber = RTRandU32();
+ memset(pBootSector->Bpb.Ebpb.achLabel, ' ', sizeof(pBootSector->Bpb.Ebpb.achLabel));
+ memcpy(pBootSector->Bpb.Ebpb.achType, enmFatType == RTFSFATTYPE_FAT12 ? "FAT12 " : "FAT16 ",
+ sizeof(pBootSector->Bpb.Ebpb.achType));
+ }
+ else
+ {
+ pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 = cbFat / cbSector;
+ pBootSector->Bpb.Fat32Ebpb.fFlags = 0;
+ pBootSector->Bpb.Fat32Ebpb.uVersion = FAT32EBPB_VERSION_0_0;
+ pBootSector->Bpb.Fat32Ebpb.uRootDirCluster = FAT_FIRST_DATA_CLUSTER;
+ pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo = 1;
+ pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo = 6;
+ RT_ZERO(pBootSector->Bpb.Fat32Ebpb.abReserved);
+
+ pBootSector->Bpb.Fat32Ebpb.bInt13Drive = 0;
+ pBootSector->Bpb.Fat32Ebpb.bReserved = 0;
+ pBootSector->Bpb.Fat32Ebpb.bExtSignature = FATEBPB_SIGNATURE;
+ pBootSector->Bpb.Fat32Ebpb.uSerialNumber = RTRandU32();
+ memset(pBootSector->Bpb.Fat32Ebpb.achLabel, ' ', sizeof(pBootSector->Bpb.Fat32Ebpb.achLabel));
+ if (cTotalSectors > UINT32_MAX)
+ pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 = cTotalSectors;
+ else
+ memcpy(pBootSector->Bpb.Fat32Ebpb.u.achType, "FAT32 ", sizeof(pBootSector->Bpb.Fat32Ebpb.u.achType));
+ }
+ pbBuf[pBootSector->abJmp[1] + 2 + 0] = 0xcd; /* int 18h */ /** @todo find/implement booting of next boot device. */
+ pbBuf[pBootSector->abJmp[1] + 2 + 1] = 0x18;
+ pbBuf[pBootSector->abJmp[1] + 2 + 2] = 0xcc; /* int3 */
+ pbBuf[pBootSector->abJmp[1] + 2 + 3] = 0xcc;
+
+ pBootSector->uSignature = FATBOOTSECTOR_SIGNATURE;
+ if (cbSector != sizeof(*pBootSector))
+ *(uint16_t *)&pbBuf[cbSector - 2] = FATBOOTSECTOR_SIGNATURE; /** @todo figure out how disks with non-512 byte sectors work! */
+
+ rc = RTVfsFileWriteAt(hVfsFile, offVol, pBootSector, cbSector, NULL);
+ uint32_t const offFirstFat = pBootSector->Bpb.Bpb331.cReservedSectors * cbSector;
+
+ /*
+ * Write the FAT32 info sector, 3 boot sector copies, and zero fill
+ * the other reserved sectors.
+ */
+ if (RT_SUCCESS(rc) && enmFatType == RTFSFATTYPE_FAT32)
+ {
+ pszLastOp = "fat32 info sector";
+ PFAT32INFOSECTOR pInfoSector = (PFAT32INFOSECTOR)&pbBuf[cbSector]; /* preserve the boot sector. */
+ RT_ZERO(*pInfoSector);
+ pInfoSector->uSignature1 = FAT32INFOSECTOR_SIGNATURE_1;
+ pInfoSector->uSignature2 = FAT32INFOSECTOR_SIGNATURE_2;
+ pInfoSector->uSignature3 = FAT32INFOSECTOR_SIGNATURE_3;
+ pInfoSector->cFreeClusters = cClusters - 1; /* ASSUMES 1 cluster for the root dir. */
+ pInfoSector->cLastAllocatedCluster = FAT_FIRST_DATA_CLUSTER;
+ rc = RTVfsFileWriteAt(hVfsFile, offVol + cbSector, pInfoSector, cbSector, NULL);
+
+ uint32_t iSector = 2;
+ if (RT_SUCCESS(rc))
+ {
+ pszLastOp = "fat32 unused reserved sectors";
+ rc = rtFsFatVolWriteZeros(hVfsFile, offVol + iSector * cbSector,
+ (pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo - iSector) * cbSector);
+ iSector = pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pszLastOp = "boot sector copy";
+ for (uint32_t i = 0; i < 3 && RT_SUCCESS(rc); i++, iSector++)
+ rc = RTVfsFileWriteAt(hVfsFile, offVol + iSector * cbSector, pBootSector, cbSector, NULL);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pszLastOp = "fat32 unused reserved sectors";
+ rc = rtFsFatVolWriteZeros(hVfsFile, offVol + iSector * cbSector,
+ (pBootSector->Bpb.Bpb331.cReservedSectors - iSector) * cbSector);
+ }
+ }
+
+ /*
+ * The FATs.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ pszLastOp = "fat";
+ pBootSector = NULL; /* invalid */
+ RT_BZERO(pbBuf, cbSector);
+ switch (enmFatType)
+ {
+ case RTFSFATTYPE_FAT32:
+ pbBuf[11] = 0x0f; /* EOC for root dir*/
+ pbBuf[10] = 0xff;
+ pbBuf[9] = 0xff;
+ pbBuf[8] = 0xff;
+ pbBuf[7] = 0x0f; /* Formatter's EOC, followed by signed extend FAT ID. */
+ pbBuf[6] = 0xff;
+ pbBuf[5] = 0xff;
+ pbBuf[4] = 0xff;
+ RT_FALL_THRU();
+ case RTFSFATTYPE_FAT16:
+ pbBuf[3] = 0xff;
+ RT_FALL_THRU();
+ case RTFSFATTYPE_FAT12:
+ pbBuf[2] = 0xff;
+ pbBuf[1] = 0xff;
+ pbBuf[0] = bMedia; /* FAT ID */
+ break;
+ default: AssertFailed();
+ }
+ for (uint32_t iFatCopy = 0; iFatCopy < cFats && RT_SUCCESS(rc); iFatCopy++)
+ {
+ rc = RTVfsFileWriteAt(hVfsFile, offVol + offFirstFat + cbFat * iFatCopy, pbBuf, cbSector, NULL);
+ if (RT_SUCCESS(rc) && cbFat > cbSector)
+ rc = rtFsFatVolWriteZeros(hVfsFile, offVol + offFirstFat + cbFat * iFatCopy + cbSector, cbFat - cbSector);
+ }
+ }
+
+ /*
+ * The root directory.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo any mandatory directory entries we need to fill in here? */
+ pszLastOp = "root dir";
+ rc = rtFsFatVolWriteZeros(hVfsFile, offVol + offFirstFat + cbFat * cFats, cbRootDir);
+ }
+
+ /*
+ * If long format, fill the rest of the disk with 0xf6.
+ */
+ AssertCompile(RTFSFATVOL_FMT_F_QUICK != 0);
+ if (RT_SUCCESS(rc) && !(fFlags & RTFSFATVOL_FMT_F_QUICK))
+ {
+ pszLastOp = "formatting data clusters";
+ uint64_t offCur = offFirstFat + cbFat * cFats + cbRootDir;
+ uint64_t cbLeft = cTotalSectors * cbSector;
+ if (cbVol - cbLeft <= _256K) /* HACK ALERT! Format to end of volume if it's a cluster rounding thing. */
+ cbLeft = cbVol;
+ if (cbLeft > offCur)
+ {
+ cbLeft -= offCur;
+ offCur += offVol;
+
+ memset(pbBuf, 0xf6, cbBuf);
+ while (cbLeft > 0)
+ {
+ size_t cbToWrite = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft;
+ rc = RTVfsFileWriteAt(hVfsFile, offCur, pbBuf, cbToWrite, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ offCur += cbToWrite;
+ cbLeft -= cbToWrite;
+ }
+ else
+ break;
+ }
+ }
+ }
+
+ /*
+ * Done.
+ */
+ RTMemTmpFree(pbBuf);
+ if (RT_SUCCESS(rc))
+ return rc;
+ return RTErrInfoSet(pErrInfo, rc, pszLastOp);
+}
+
+
+/**
+ * Formats a 1.44MB floppy image.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFile The image.
+ */
+RTDECL(int) RTFsFatVolFormat144(RTVFSFILE hVfsFile, bool fQuick)
+{
+ return RTFsFatVolFormat(hVfsFile, 0 /*offVol*/, 1474560, fQuick ? RTFSFATVOL_FMT_F_QUICK : RTFSFATVOL_FMT_F_FULL,
+ 512 /*cbSector*/, 1 /*cSectorsPerCluster*/, RTFSFATTYPE_FAT12, 2 /*cHeads*/, 18 /*cSectors*/,
+ 0xf0 /*bMedia*/, 224 /*cRootDirEntries*/, 0 /*cHiddenSectors*/, NULL /*pErrInfo*/);
+}
+
+
+/**
+ * Formats a 2.88MB floppy image.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFile The image.
+ */
+RTDECL(int) RTFsFatVolFormat288(RTVFSFILE hVfsFile, bool fQuick)
+{
+ return RTFsFatVolFormat(hVfsFile, 0 /*offVol*/, 2949120, fQuick ? RTFSFATVOL_FMT_F_QUICK : RTFSFATVOL_FMT_F_FULL,
+ 512 /*cbSector*/, 2 /*cSectorsPerCluster*/, RTFSFATTYPE_FAT12, 2 /*cHeads*/, 36 /*cSectors*/,
+ 0xf0 /*bMedia*/, 224 /*cRootDirEntries*/, 0 /*cHiddenSectors*/, NULL /*pErrInfo*/);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainFatVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
+ return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
+ if ( pElement->enmType != RTVFSOBJTYPE_VFS
+ && pElement->enmType != RTVFSOBJTYPE_DIR)
+ return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainFatVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError);
+
+ int rc;
+ RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
+ if (hVfsFileIn != NIL_RTVFSFILE)
+ {
+ RTVFS hVfs;
+ rc = RTFsFatVolOpen(hVfsFileIn, pElement->uProvider != false, 0, &hVfs, pErrInfo);
+ RTVfsFileRelease(hVfsFileIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromVfs(hVfs);
+ RTVfsRelease(hVfs);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainFatVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider
+ || !pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'file'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainFatVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "fat",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a FAT file system, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainFatVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainFatVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainFatVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainFatVolReg, rtVfsChainFatVolReg);
+
diff --git a/src/VBox/Runtime/common/fs/isomaker.cpp b/src/VBox/Runtime/common/fs/isomaker.cpp
new file mode 100644
index 00000000..9a1c4f8d
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isomaker.cpp
@@ -0,0 +1,7585 @@
+/* $Id: isomaker.cpp $ */
+/** @file
+ * IPRT - ISO Image Maker.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include "internal/iprt.h"
+#include <iprt/fsisomaker.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/md5.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/zero.h>
+#include <iprt/formats/iso9660.h>
+
+#include <internal/magics.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Asserts valid handle, returns @a a_rcRet if not. */
+#define RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(a_pThis, a_rcRet) \
+ do { AssertPtrReturn(a_pThis, a_rcRet); \
+ AssertReturn((a_pThis)->uMagic == RTFSISOMAKERINT_MAGIC, a_rcRet); \
+ } while (0)
+
+/** Asserts valid handle, returns VERR_INVALID_HANDLE if not. */
+#define RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(a_pThis) RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(a_pThis, VERR_INVALID_HANDLE)
+
+/** The sector size. */
+#define RTFSISOMAKER_SECTOR_SIZE _2K
+/** The sector offset mask. */
+#define RTFSISOMAKER_SECTOR_OFFSET_MASK (_2K - 1)
+/** Maximum number of objects. */
+#define RTFSISOMAKER_MAX_OBJECTS _16M
+/** Maximum number of objects per directory. */
+#define RTFSISOMAKER_MAX_OBJECTS_PER_DIR _256K /**< @todo check limit */
+
+/** Number of bytes to store per dir record when using multiple extents. */
+#define RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE UINT32_C(0xfffff800)
+
+/** UTF-8 name buffer. */
+#define RTFSISOMAKER_MAX_NAME_BUF 768
+
+/** Max symbolic link target length. */
+#define RTFSISOMAKER_MAX_SYMLINK_TARGET_LEN 260
+
+/** TRANS.TBL left padding length.
+ * We keep the amount of padding low to avoid wasing memory when generating
+ * these long obsolete files. */
+#define RTFSISOMAKER_TRANS_TBL_LEFT_PAD 12
+
+/** Tests if @a a_ch is in the set of d-characters. */
+#define RTFSISOMAKER_IS_IN_D_CHARS(a_ch) (RT_C_IS_UPPER(a_ch) || RT_C_IS_DIGIT(a_ch) || (a_ch) == '_')
+
+/** Tests if @a a_ch is in the set of d-characters when uppercased. */
+#define RTFSISOMAKER_IS_UPPER_IN_D_CHARS(a_ch) (RT_C_IS_ALNUM(a_ch) || (a_ch) == '_')
+
+
+/** Calculates the path table record size given the name length.
+ * @note The root directory length is 1 (name byte is 0x00), we make sure this
+ * is the case in rtFsIsoMakerNormalizeNameForNamespace. */
+#define RTFSISOMAKER_CALC_PATHREC_SIZE(a_cbNameInDirRec) \
+ ( RT_UOFFSETOF_DYN(ISO9660PATHREC, achDirId[(a_cbNameInDirRec) + ((a_cbNameInDirRec) & 1)]) )
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to an ISO maker object name space node. */
+typedef struct RTFSISOMAKERNAME *PRTFSISOMAKERNAME;
+/** Pointer to a const ISO maker object name space node. */
+typedef struct RTFSISOMAKERNAME const *PCRTFSISOMAKERNAME;
+/** Pointer to an ISO maker object name space node pointer. */
+typedef PRTFSISOMAKERNAME *PPRTFSISOMAKERNAME;
+
+/** Pointer to a common ISO image maker file system object. */
+typedef struct RTFSISOMAKEROBJ *PRTFSISOMAKEROBJ;
+/** Pointer to a const common ISO image maker file system object. */
+typedef struct RTFSISOMAKEROBJ const *PCRTFSISOMAKEROBJ;
+
+/** Pointer to a ISO maker file object. */
+typedef struct RTFSISOMAKERFILE *PRTFSISOMAKERFILE;
+/** Pointer to a const ISO maker file object. */
+typedef struct RTFSISOMAKERFILE const *PCRTFSISOMAKERFILE;
+
+/**
+ * Filesystem object type.
+ */
+typedef enum RTFSISOMAKEROBJTYPE
+{
+ RTFSISOMAKEROBJTYPE_INVALID = 0,
+ RTFSISOMAKEROBJTYPE_DIR,
+ RTFSISOMAKEROBJTYPE_FILE,
+ RTFSISOMAKEROBJTYPE_SYMLINK,
+ RTFSISOMAKEROBJTYPE_END
+} RTFSISOMAKEROBJTYPE;
+
+/**
+ * Extra name space information required for directories.
+ */
+typedef struct RTFSISOMAKERNAMEDIR
+{
+ /** The location of the directory data. */
+ uint64_t offDir;
+ /** The size of the directory. */
+ uint32_t cbDir;
+ /** Number of children. */
+ uint32_t cChildren;
+ /** Sorted array of children. */
+ PPRTFSISOMAKERNAME papChildren;
+ /** The translate table file. */
+ PRTFSISOMAKERFILE pTransTblFile;
+
+ /** The offset in the path table (ISO-9660).
+ * This is set when finalizing the image. */
+ uint32_t offPathTable;
+ /** The path table identifier of this directory (ISO-9660).
+ * This is set when finalizing the image. */
+ uint16_t idPathTable;
+ /** The size of the first directory record (0x00 - '.'). */
+ uint8_t cbDirRec00;
+ /** The size of the second directory record (0x01 - '..'). */
+ uint8_t cbDirRec01;
+ /** Pointer to back to the namespace node this belongs to (for the finalized
+ * entry list). */
+ PRTFSISOMAKERNAME pName;
+ /** Entry in the list of finalized directories. */
+ RTLISTNODE FinalizedEntry;
+} RTFSISOMAKERNAMEDIR;
+/** Pointer to directory specfic namespace node info. */
+typedef RTFSISOMAKERNAMEDIR *PRTFSISOMAKERNAMEDIR;
+/** Pointer to const directory specfic namespace node info. */
+typedef const RTFSISOMAKERNAMEDIR *PCRTFSISOMAKERNAMEDIR;
+
+
+/**
+ * ISO maker object namespace node.
+ */
+typedef struct RTFSISOMAKERNAME
+{
+ /** Pointer to the file system object. */
+ PRTFSISOMAKEROBJ pObj;
+ /** Pointer to the partent directory, NULL if root dir. */
+ PRTFSISOMAKERNAME pParent;
+
+ /** Pointer to the directory information if this is a directory, NULL if not a
+ * directory. This is allocated together with this structure, so it doesn't need
+ * freeing. */
+ PRTFSISOMAKERNAMEDIR pDir;
+
+ /** The name specified when creating this namespace node. Helps navigating
+ * the namespace when we mangle or otherwise change the names.
+ * Allocated together with of this structure, no spearate free necessary. */
+ const char *pszSpecNm;
+
+ /** Alternative rock ridge name. */
+ char *pszRockRidgeNm;
+ /** Alternative TRANS.TBL name. */
+ char *pszTransNm;
+ /** Length of pszSpecNm. */
+ uint16_t cchSpecNm;
+ /** Length of pszRockRidgeNm. */
+ uint16_t cchRockRidgeNm;
+ /** Length of pszTransNm. */
+ uint16_t cchTransNm;
+
+ /** The depth in the namespace tree of this name. */
+ uint8_t uDepth;
+ /** Set if pszTransNm is allocated separately. Normally same as pszSpecNm. */
+ bool fRockRidgeNmAlloced : 1;
+ /** Set if pszTransNm is allocated separately. Normally same as pszSpecNm. */
+ bool fTransNmAlloced : 1;
+ /** Set if we need to emit an ER entry (root only). */
+ bool fRockNeedER : 1;
+ /** Set if we need to emit a RR entry in the directory record. */
+ bool fRockNeedRRInDirRec : 1;
+ /** Set if we need to emit a RR entry in the spill file. */
+ bool fRockNeedRRInSpill : 1;
+
+ /** The mode mask.
+ * Starts out as a copy of RTFSISOMAKEROBJ::fMode. */
+ RTFMODE fMode;
+ /** The owner ID.
+ * Starts out as a copy of RTFSISOMAKEROBJ::uid. */
+ RTUID uid;
+ /** The group ID.
+ * Starts out as a copy of RTFSISOMAKEROBJ::gid. */
+ RTGID gid;
+ /** The device number if a character or block device.
+ * This is for Rock Ridge. */
+ RTDEV Device;
+ /** The number of hardlinks to report in the file stats.
+ * This is for Rock Ridge. */
+ uint32_t cHardlinks;
+
+ /** The offset of the directory entry in the parent directory. */
+ uint32_t offDirRec;
+ /** Size of the directory record (ISO-9660).
+ * This is set when the image is being finalized. */
+ uint16_t cbDirRec;
+ /** Number of directory records needed to cover the entire file size. */
+ uint16_t cDirRecs;
+ /** The total directory record size (cbDirRec * cDirRecs), including end of
+ * sector zero padding. */
+ uint16_t cbDirRecTotal;
+
+ /** Rock ridge flags (ISO9660RRIP_RR_F_XXX). */
+ uint8_t fRockEntries;
+ /** Number of rock ridge data bytes in the directory record. Unaligned! */
+ uint8_t cbRockInDirRec;
+ /** Rock ridge spill file data offset, UINT32_MAX if placed in dir record. */
+ uint32_t offRockSpill;
+ /** Size of rock data in spill file. */
+ uint16_t cbRockSpill;
+
+ /** The number of bytes the name requires in the directory record. */
+ uint16_t cbNameInDirRec;
+ /** The name length. */
+ uint16_t cchName;
+ /** The name. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} RTFSISOMAKERNAME;
+
+/**
+ * A ISO maker namespace.
+ */
+typedef struct RTFSISOMAKERNAMESPACE
+{
+ /** The namespace root. */
+ PRTFSISOMAKERNAME pRoot;
+ /** Total number of name nodes in the namespace. */
+ uint32_t cNames;
+ /** Total number of directories in the namespace.
+ * @note Appears to be unused. */
+ uint32_t cDirs;
+ /** The namespace selector (RTFSISOMAKER_NAMESPACE_XXX). */
+ uint32_t fNamespace;
+ /** Offset into RTFSISOMAKERNAMESPACE of the name member. */
+ uint32_t offName;
+ /** The configuration level for this name space.
+ * - For UDF and HFS namespaces this is either @c true or @c false.
+ * - For the primary ISO-9660 namespace this is 1, 2, or 3.
+ * - For the joliet namespace this 0 (joliet disabled), 1, 2, or 3. */
+ uint8_t uLevel;
+ /** The rock ridge level: 1 - enabled; 2 - with ER tag.
+ * Linux behaves a little different when seeing the ER tag. */
+ uint8_t uRockRidgeLevel;
+ /** The TRANS.TBL filename if enabled, NULL if disabled.
+ * When not NULL, this may be pointing to heap or g_szTransTbl. */
+ char *pszTransTbl;
+ /** The system ID (ISO9660PRIMARYVOLDESC::achSystemId). Empty if NULL.
+ * When not NULL, this may be pointing to heap of g_szSystemId. */
+ char *pszSystemId;
+ /** The volume ID / label (ISO9660PRIMARYVOLDESC::achVolumeId).
+ * A string representation of RTFSISOMAKERINT::ImageCreationTime if NULL. */
+ char *pszVolumeId;
+ /** The volume set ID (ISO9660PRIMARYVOLDESC::achVolumeSetId). Empty if NULL. */
+ char *pszVolumeSetId;
+ /** The publisher ID or (root) file reference (ISO9660PRIMARYVOLDESC::achPublisherId). Empty if NULL. */
+ char *pszPublisherId;
+ /* The data preperer ID or (root) file reference (ISO9660PRIMARYVOLDESC::achDataPreparerId). Empty if NULL. */
+ char *pszDataPreparerId;
+ /* The application ID or (root) file reference (ISO9660PRIMARYVOLDESC::achApplicationId).
+ * Defaults to g_szAppIdPrimaryIso or g_szAppIdJoliet. */
+ char *pszApplicationId;
+ /** The copyright (root) file identifier (ISO9660PRIMARYVOLDESC::achCopyrightFileId). None if NULL. */
+ char *pszCopyrightFileId;
+ /** The abstract (root) file identifier (ISO9660PRIMARYVOLDESC::achAbstractFileId). None if NULL. */
+ char *pszAbstractFileId;
+ /** The bibliographic (root) file identifier (ISO9660PRIMARYVOLDESC::achBibliographicFileId). None if NULL. */
+ char *pszBibliographicFileId;
+} RTFSISOMAKERNAMESPACE;
+/** Pointer to a namespace. */
+typedef RTFSISOMAKERNAMESPACE *PRTFSISOMAKERNAMESPACE;
+/** Pointer to a const namespace. */
+typedef RTFSISOMAKERNAMESPACE const *PCRTFSISOMAKERNAMESPACE;
+
+
+/**
+ * Common base structure for the file system objects.
+ *
+ * The times are shared across all namespaces, while the uid, gid and mode are
+ * duplicates in each namespace.
+ */
+typedef struct RTFSISOMAKEROBJ
+{
+ /** The linear list entry of the image content. */
+ RTLISTNODE Entry;
+ /** The object index. */
+ uint32_t idxObj;
+ /** The type of this object. */
+ RTFSISOMAKEROBJTYPE enmType;
+
+ /** The primary ISO-9660 name space name. */
+ PRTFSISOMAKERNAME pPrimaryName;
+ /** The joliet name space name. */
+ PRTFSISOMAKERNAME pJolietName;
+ /** The UDF name space name. */
+ PRTFSISOMAKERNAME pUdfName;
+ /** The HFS name space name. */
+ PRTFSISOMAKERNAME pHfsName;
+
+ /** Birth (creation) time. */
+ RTTIMESPEC BirthTime;
+ /** Attribute change time. */
+ RTTIMESPEC ChangeTime;
+ /** Modification time. */
+ RTTIMESPEC ModificationTime;
+ /** Accessed time. */
+ RTTIMESPEC AccessedTime;
+
+ /** Owner ID. */
+ RTUID uid;
+ /** Group ID. */
+ RTGID gid;
+ /** Attributes (unix permissions bits mainly). */
+ RTFMODE fMode;
+
+ /** Used to make sure things like the boot catalog stays in the image even if
+ * it's not mapped into any of the namespaces. */
+ uint32_t cNotOrphan;
+} RTFSISOMAKEROBJ;
+
+
+/**
+ * File source type.
+ */
+typedef enum RTFSISOMAKERSRCTYPE
+{
+ RTFSISOMAKERSRCTYPE_INVALID = 0,
+ RTFSISOMAKERSRCTYPE_PATH,
+ RTFSISOMAKERSRCTYPE_VFS_FILE,
+ RTFSISOMAKERSRCTYPE_COMMON,
+ RTFSISOMAKERSRCTYPE_TRANS_TBL,
+ RTFSISOMAKERSRCTYPE_RR_SPILL,
+ RTFSISOMAKERSRCTYPE_END
+} RTFSISOMAKERSRCTYPE;
+
+/**
+ * ISO maker file object.
+ */
+typedef struct RTFSISOMAKERFILE
+{
+ /** The common bit. */
+ RTFSISOMAKEROBJ Core;
+ /** The file data size. */
+ uint64_t cbData;
+ /** Byte offset of the data in the image.
+ * UINT64_MAX until the location is finalized. */
+ uint64_t offData;
+
+ /** The type of source object. */
+ RTFSISOMAKERSRCTYPE enmSrcType;
+ /** The source data. */
+ union
+ {
+ /** Path to the source file.
+ * Allocated together with this structure. */
+ const char *pszSrcPath;
+ /** Source VFS file. */
+ RTVFSFILE hVfsFile;
+ /** Source is a part of a common VFS file. */
+ struct
+ {
+ /** The offset into the file */
+ uint64_t offData;
+ /** The index of the common file. */
+ uint32_t idxSrc;
+ } Common;
+ /** The directory the translation table belongs to. */
+ PRTFSISOMAKERNAME pTransTblDir;
+ /** The namespace for a rock ridge spill file.. */
+ PRTFSISOMAKERNAMESPACE pRockSpillNamespace;
+ } u;
+
+ /** Boot info table to patch into the file.
+ * This is calculated during file finalization as it needs the file location. */
+ PISO9660SYSLINUXINFOTABLE pBootInfoTable;
+
+ /** Entry in the list of finalized directories. */
+ RTLISTNODE FinalizedEntry;
+} RTFSISOMAKERFILE;
+
+
+/**
+ * ISO maker directory object.
+ *
+ * Unlike files, the allocation info is name space specific and lives in the
+ * corresponding RTFSISOMAKERNAMEDIR structures.
+ */
+typedef struct RTFSISOMAKERDIR
+{
+ /** The common bit. */
+ RTFSISOMAKEROBJ Core;
+} RTFSISOMAKERDIR;
+/** Pointer to an ISO maker directory object. */
+typedef RTFSISOMAKERDIR *PRTFSISOMAKERDIR;
+
+
+/**
+ * ISO maker symlink object.
+ */
+typedef struct RTFSISOMAKERSYMLINK
+{
+ /** The common bit. */
+ RTFSISOMAKEROBJ Core;
+ /** The size of the rock ridge 'SL' records for this link. */
+ uint16_t cbSlRockRidge;
+ /** The symbolic link target length. */
+ uint16_t cchTarget;
+ /** The symbolic link target. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szTarget[RT_FLEXIBLE_ARRAY];
+} RTFSISOMAKERSYMLINK;
+/** Pointer to an ISO maker directory object. */
+typedef RTFSISOMAKERSYMLINK *PRTFSISOMAKERSYMLINK;
+/** Pointer to a const ISO maker directory object. */
+typedef const RTFSISOMAKERSYMLINK *PCRTFSISOMAKERSYMLINK;
+
+
+
+/**
+ * Instance data for a ISO image maker.
+ */
+typedef struct RTFSISOMAKERINT
+{
+ /** Magic value (RTFSISOMAKERINT_MAGIC). */
+ uint32_t uMagic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+
+ /** Set after we've been fed the first bit of content.
+ * This means that the namespace configuration has been finalized and can no
+ * longer be changed because it's simply too much work to do adjustments
+ * after having started to add files. */
+ bool fSeenContent;
+ /** Set once we've finalized the image structures.
+ * After this no more changes are allowed. */
+ bool fFinalized;
+
+ /** The primary ISO-9660 namespace. */
+ RTFSISOMAKERNAMESPACE PrimaryIso;
+ /** The joliet namespace. */
+ RTFSISOMAKERNAMESPACE Joliet;
+ /** The UDF namespace. */
+ RTFSISOMAKERNAMESPACE Udf;
+ /** The hybrid HFS+ namespace. */
+ RTFSISOMAKERNAMESPACE Hfs;
+
+ /** The list of objects (RTFSISOMAKEROBJ). */
+ RTLISTANCHOR ObjectHead;
+ /** Number of objects in the image (ObjectHead).
+ * This is used to number them, i.e. create RTFSISOMAKEROBJ::idxObj. */
+ uint32_t cObjects;
+
+ /** Amount of file data. */
+ uint64_t cbData;
+ /** Number of volume descriptors. */
+ uint32_t cVolumeDescriptors;
+ /** The image (trail) padding in bytes. */
+ uint32_t cbImagePadding;
+
+ /** The 'now' timestamp we use for the whole image.
+ * This way we'll save lots of RTTimeNow calls and have similar timestamps
+ * over the whole image. */
+ RTTIMESPEC ImageCreationTime;
+ /** Indicates strict or non-strict attribute handling style.
+ * See RTFsIsoMakerSetAttributeStyle() for details. */
+ bool fStrictAttributeStyle;
+ /** The default owner ID. */
+ RTUID uidDefault;
+ /** The default group ID. */
+ RTGID gidDefault;
+ /** The default file mode mask. */
+ RTFMODE fDefaultFileMode;
+ /** The default file mode mask. */
+ RTFMODE fDefaultDirMode;
+
+ /** Forced file mode mask (permissions only). */
+ RTFMODE fForcedFileMode;
+ /** Set if fForcedFileMode is active. */
+ bool fForcedFileModeActive;
+ /** Set if fForcedDirMode is active. */
+ bool fForcedDirModeActive;
+ /** Forced directory mode mask (permissions only). */
+ RTFMODE fForcedDirMode;
+
+ /** Number of common source files. */
+ uint32_t cCommonSources;
+ /** Array of common source file handles. */
+ PRTVFSFILE paCommonSources;
+
+ /** @name Boot related stuff
+ * @{ */
+ /** The boot catalog file. */
+ PRTFSISOMAKERFILE pBootCatFile;
+ /** Per boot catalog entry data needed for updating offsets when finalizing. */
+ struct
+ {
+ /** The type (ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY,
+ * ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER,
+ * ISO9660_ELTORITO_HEADER_ID_FINAL_SECTION_HEADER,
+ * ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE or
+ * ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE). */
+ uint8_t bType;
+ /** Number of entries related to this one. This is zero for unused entries,
+ * 2 for the validation entry, 2+ for section headers, and 1 for images. */
+ uint8_t cEntries;
+ /** The boot file. */
+ PRTFSISOMAKERFILE pBootFile;
+ } aBootCatEntries[64];
+ /** @} */
+
+ /** @name Finalized image stuff
+ * @{ */
+ /** The finalized image size. */
+ uint64_t cbFinalizedImage;
+ /** System area content (sectors 0 thur 15). This is NULL if the system area
+ * are all zeros, which is often the case. Hybrid ISOs have an MBR followed by
+ * a GUID partition table here, helping making the image bootable when
+ * transfered to a USB stick. */
+ uint8_t *pbSysArea;
+ /** Number of non-zero system area bytes pointed to by pbSysArea. */
+ size_t cbSysArea;
+
+ /** Pointer to the buffer holding the volume descriptors. */
+ uint8_t *pbVolDescs;
+ /** Pointer to the primary volume descriptor. */
+ PISO9660PRIMARYVOLDESC pPrimaryVolDesc;
+ /** El Torito volume descriptor. */
+ PISO9660BOOTRECORDELTORITO pElToritoDesc;
+ /** Pointer to the primary volume descriptor. */
+ PISO9660SUPVOLDESC pJolietVolDesc;
+ /** Terminating ISO-9660 volume descriptor. */
+ PISO9660VOLDESCHDR pTerminatorVolDesc;
+
+ /** Finalized ISO-9660 directory structures. */
+ struct RTFSISOMAKERFINALIZEDDIRS
+ {
+ /** The image byte offset of the first directory. */
+ uint64_t offDirs;
+ /** The image byte offset of the little endian path table.
+ * This always follows offDirs. */
+ uint64_t offPathTableL;
+ /** The image byte offset of the big endian path table.
+ * This always follows offPathTableL. */
+ uint64_t offPathTableM;
+ /** The size of the path table. */
+ uint32_t cbPathTable;
+ /** List of finalized directories for this namespace.
+ * The list is in path table order so it can be generated on the fly. The
+ * directories will be ordered in the same way. */
+ RTLISTANCHOR FinalizedDirs;
+ /** Rock ridge spill file. */
+ PRTFSISOMAKERFILE pRRSpillFile;
+ }
+ /** The finalized directory data for the primary ISO-9660 namespace. */
+ PrimaryIsoDirs,
+ /** The finalized directory data for the joliet namespace. */
+ JolietDirs;
+
+ /** The image byte offset of the first file. */
+ uint64_t offFirstFile;
+ /** Finalized file head (RTFSISOMAKERFILE).
+ * The list is ordered by disk location. Files are following the
+ * directories and path tables. */
+ RTLISTANCHOR FinalizedFiles;
+ /** @} */
+
+} RTFSISOMAKERINT;
+/** Pointer to an ISO maker instance. */
+typedef RTFSISOMAKERINT *PRTFSISOMAKERINT;
+
+/** Pointer to the data for finalized ISO-9660 (primary / joliet) dirs. */
+typedef struct RTFSISOMAKERINT::RTFSISOMAKERFINALIZEDDIRS *PRTFSISOMAKERFINALIZEDDIRS;
+
+
+/**
+ * Instance data of an ISO maker output file.
+ */
+typedef struct RTFSISOMAKEROUTPUTFILE
+{
+ /** The ISO maker (owns a reference). */
+ PRTFSISOMAKERINT pIsoMaker;
+ /** The current file position. */
+ uint64_t offCurPos;
+ /** Current file hint. */
+ PRTFSISOMAKERFILE pFileHint;
+ /** Source file corresponding to pFileHint.
+ * This is used when dealing with a RTFSISOMAKERSRCTYPE_VFS_FILE or
+ * RTFSISOMAKERSRCTYPE_TRANS_TBL file. */
+ RTVFSFILE hVfsSrcFile;
+ /** Current directory hint for the primary ISO namespace. */
+ PRTFSISOMAKERNAMEDIR pDirHintPrimaryIso;
+ /** Current directory hint for the joliet namespace. */
+ PRTFSISOMAKERNAMEDIR pDirHintJoliet;
+ /** Joliet directory child index hint. */
+ uint32_t iChildPrimaryIso;
+ /** Joliet directory child index hint. */
+ uint32_t iChildJoliet;
+} RTFSISOMAKEROUTPUTFILE;
+/** Pointer to the instance data of an ISO maker output file. */
+typedef RTFSISOMAKEROUTPUTFILE *PRTFSISOMAKEROUTPUTFILE;
+
+
+/**
+ * Directory entry type.
+ */
+typedef enum RTFSISOMAKERDIRTYPE
+{
+ /** Invalid directory entry. */
+ RTFSISOMAKERDIRTYPE_INVALID = 0,
+ /** Entry for the current directory, aka ".". */
+ RTFSISOMAKERDIRTYPE_CURRENT,
+ /** Entry for the parent directory, aka "..". */
+ RTFSISOMAKERDIRTYPE_PARENT,
+ /** Entry for a regular directory entry. */
+ RTFSISOMAKERDIRTYPE_OTHER
+} RTFSISOMAKERDIRTYPE;
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Help for iterating over namespaces.
+ */
+static const struct
+{
+ /** The RTFSISOMAKER_NAMESPACE_XXX indicator. */
+ uint32_t fNamespace;
+ /** Offset into RTFSISOMAKERINT of the namespace member. */
+ uintptr_t offNamespace;
+ /** Offset into RTFSISOMAKERNAMESPACE of the name member. */
+ uintptr_t offName;
+ /** Namespace name for debugging purposes. */
+ const char *pszName;
+} g_aRTFsIsoNamespaces[] =
+{
+ { RTFSISOMAKER_NAMESPACE_ISO_9660, RT_UOFFSETOF(RTFSISOMAKERINT, PrimaryIso), RT_UOFFSETOF(RTFSISOMAKEROBJ, pPrimaryName), "iso-9660" },
+ { RTFSISOMAKER_NAMESPACE_JOLIET, RT_UOFFSETOF(RTFSISOMAKERINT, Joliet), RT_UOFFSETOF(RTFSISOMAKEROBJ, pJolietName), "joliet" },
+ { RTFSISOMAKER_NAMESPACE_UDF, RT_UOFFSETOF(RTFSISOMAKERINT, Udf), RT_UOFFSETOF(RTFSISOMAKEROBJ, pUdfName), "udf" },
+ { RTFSISOMAKER_NAMESPACE_HFS, RT_UOFFSETOF(RTFSISOMAKERINT, Hfs), RT_UOFFSETOF(RTFSISOMAKEROBJ, pHfsName), "hfs" },
+};
+
+/**
+ * Translates a single namespace flag (RTFSISOMAKER_NAMESPACE_XXX) to an
+ * index into g_aRTFsIsoNamespaces.
+ */
+static const uint8_t g_aidxRTFsIsoNamespaceFlagToIdx[] =
+{
+ /*[0] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[RTFSISOMAKER_NAMESPACE_ISO_9660] = */ 0,
+ /*[RTFSISOMAKER_NAMESPACE_JOLIET] = */ 1,
+ /*[3] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[RTFSISOMAKER_NAMESPACE_UDF] = */ 2,
+ /*[5] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[6] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[7] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[RTFSISOMAKER_NAMESPACE_HFS] = */ 3,
+ /*[9] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[10] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[11] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[12] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[13] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[14] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+ /*[15] = */ RT_ELEMENTS(g_aRTFsIsoNamespaces),
+};
+
+/** The default translation table filename. */
+static const char g_szTransTbl[] = "TRANS.TBL";
+/** The default application ID for the primary ISO-9660 volume descriptor. */
+static char g_szAppIdPrimaryIso[64] = "";
+/** The default application ID for the joliet volume descriptor. */
+static char g_szAppIdJoliet[64] = "";
+/** The default system ID the primary ISO-9660 volume descriptor. */
+static char g_szSystemId[64] = "";
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtFsIsoMakerObjSetName(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKEROBJ pObj,
+ PRTFSISOMAKERNAME pParent, const char *pchSpec, size_t cchSpec, bool fNoNormalize,
+ PPRTFSISOMAKERNAME ppNewName);
+static int rtFsIsoMakerObjUnsetName(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKEROBJ pObj);
+static int rtFsIsoMakerAddUnnamedDirWorker(PRTFSISOMAKERINT pThis, PCRTFSOBJINFO pObjInfo, PRTFSISOMAKERDIR *ppDir);
+static int rtFsIsoMakerAddUnnamedFileWorker(PRTFSISOMAKERINT pThis, PCRTFSOBJINFO pObjInfo, size_t cbExtra,
+ PRTFSISOMAKERFILE *ppFile);
+static int rtFsIsoMakerObjRemoveWorker(PRTFSISOMAKERINT pThis, PRTFSISOMAKEROBJ pObj);
+
+static ssize_t rtFsIsoMakerOutFile_RockRidgeGenSL(const char *pszTarget, uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual);
+
+
+
+/**
+ * Creates an ISO maker instance.
+ *
+ * @returns IPRT status code.
+ * @param phIsoMaker Where to return the handle to the new ISO maker.
+ */
+RTDECL(int) RTFsIsoMakerCreate(PRTFSISOMAKER phIsoMaker)
+{
+ /*
+ * Do some integrity checks first.
+ */
+ AssertReturn(g_aRTFsIsoNamespaces[g_aidxRTFsIsoNamespaceFlagToIdx[RTFSISOMAKER_NAMESPACE_ISO_9660]].fNamespace == RTFSISOMAKER_NAMESPACE_ISO_9660,
+ VERR_ISOMK_IPE_TABLE);
+ AssertReturn(g_aRTFsIsoNamespaces[g_aidxRTFsIsoNamespaceFlagToIdx[RTFSISOMAKER_NAMESPACE_JOLIET]].fNamespace == RTFSISOMAKER_NAMESPACE_JOLIET,
+ VERR_ISOMK_IPE_TABLE);
+ AssertReturn(g_aRTFsIsoNamespaces[g_aidxRTFsIsoNamespaceFlagToIdx[RTFSISOMAKER_NAMESPACE_UDF]].fNamespace == RTFSISOMAKER_NAMESPACE_UDF,
+ VERR_ISOMK_IPE_TABLE);
+ AssertReturn(g_aRTFsIsoNamespaces[g_aidxRTFsIsoNamespaceFlagToIdx[RTFSISOMAKER_NAMESPACE_HFS]].fNamespace == RTFSISOMAKER_NAMESPACE_HFS,
+ VERR_ISOMK_IPE_TABLE);
+
+ if (g_szAppIdPrimaryIso[0] == '\0')
+ RTStrPrintf(g_szAppIdPrimaryIso, sizeof(g_szAppIdPrimaryIso), "IPRT ISO MAKER V%u.%u.%u R%s",
+ RTBldCfgVersionMajor(), RTBldCfgVersionMinor(), RTBldCfgVersionBuild(), RTBldCfgRevisionStr());
+ if (g_szAppIdJoliet[0] == '\0')
+ RTStrPrintf(g_szAppIdJoliet, sizeof(g_szAppIdJoliet),
+ "IPRT ISO Maker v%s r%s", RTBldCfgVersion(), RTBldCfgRevisionStr());
+ if (g_szSystemId[0] == '\0')
+ {
+ RTStrCopy(g_szSystemId, sizeof(g_szSystemId), RTBldCfgTargetDotArch());
+ RTStrToUpper(g_szSystemId);
+ }
+
+ /*
+ * Create the instance with defaults.
+ */
+ int rc;
+ PRTFSISOMAKERINT pThis = (PRTFSISOMAKERINT)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->uMagic = RTFSISOMAKERINT_MAGIC;
+ pThis->cRefs = 1;
+ //pThis->fSeenContent = false;
+ //pThis->fFinalized = false;
+
+ pThis->PrimaryIso.fNamespace = RTFSISOMAKER_NAMESPACE_ISO_9660;
+ pThis->PrimaryIso.offName = RT_UOFFSETOF(RTFSISOMAKEROBJ, pPrimaryName);
+ pThis->PrimaryIso.uLevel = 3; /* 30 char names, large files */
+ pThis->PrimaryIso.uRockRidgeLevel = 1;
+ pThis->PrimaryIso.pszTransTbl = (char *)g_szTransTbl;
+ pThis->PrimaryIso.pszSystemId = g_szSystemId;
+ //pThis->PrimaryIso.pszVolumeId = NULL;
+ //pThis->PrimaryIso.pszSetVolumeId = NULL;
+ //pThis->PrimaryIso.pszPublisherId = NULL;
+ //pThis->PrimaryIso.pszDataPreparerId = NULL;
+ pThis->PrimaryIso.pszApplicationId = g_szAppIdPrimaryIso;
+ //pThis->PrimaryIso.pszCopyrightFileId = NULL;
+ //pThis->PrimaryIso.pszAbstractFileId = NULL;
+ //pThis->PrimaryIso.pszBibliographicFileId = NULL;
+
+ pThis->Joliet.fNamespace = RTFSISOMAKER_NAMESPACE_JOLIET;
+ pThis->Joliet.offName = RT_UOFFSETOF(RTFSISOMAKEROBJ, pJolietName);
+ pThis->Joliet.uLevel = 3;
+ //pThis->Joliet.uRockRidgeLevel = 0;
+ //pThis->Joliet.pszTransTbl = NULL;
+ //pThis->Joliet.pszSystemId = NULL;
+ //pThis->Joliet.pszVolumeId = NULL;
+ //pThis->Joliet.pszSetVolumeId = NULL;
+ //pThis->Joliet.pszPublisherId = NULL;
+ //pThis->Joliet.pszDataPreparerId = NULL;
+ pThis->Joliet.pszApplicationId = g_szAppIdJoliet;
+ //pThis->Joliet.pszCopyrightFileId = NULL;
+ //pThis->Joliet.pszAbstractFileId = NULL;
+ //pThis->Joliet.pszBibliographicFileId = NULL;
+
+ pThis->Udf.fNamespace = RTFSISOMAKER_NAMESPACE_UDF;
+ pThis->Udf.offName = RT_UOFFSETOF(RTFSISOMAKEROBJ, pUdfName);
+ //pThis->Udf.uLevel = 0;
+ //pThis->Udf.uRockRidgeLevel = 0;
+ //pThis->Udf.pszTransTbl = NULL;
+ //pThis->Udf.uRockRidgeLevel = 0;
+ //pThis->Udf.pszTransTbl = NULL;
+ //pThis->Udf.pszSystemId = NULL;
+ //pThis->Udf.pszVolumeId = NULL;
+ //pThis->Udf.pszSetVolumeId = NULL;
+ //pThis->Udf.pszPublisherId = NULL;
+ //pThis->Udf.pszDataPreparerId = NULL;
+ //pThis->Udf.pszApplicationId = NULL;
+ //pThis->Udf.pszCopyrightFileId = NULL;
+ //pThis->Udf.pszAbstractFileId = NULL;
+ //pThis->Udf.pszBibliographicFileId = NULL;
+
+ pThis->Hfs.fNamespace = RTFSISOMAKER_NAMESPACE_HFS;
+ pThis->Hfs.offName = RT_UOFFSETOF(RTFSISOMAKEROBJ, pHfsName);
+ //pThis->Hfs.uLevel = 0;
+ //pThis->Hfs.uRockRidgeLevel = 0;
+ //pThis->Hfs.pszTransTbl = NULL;
+ //pThis->Hfs.pszSystemId = NULL;
+ //pThis->Hfs.pszVolumeId = NULL;
+ //pThis->Hfs.pszSetVolumeId = NULL;
+ //pThis->Hfs.pszPublisherId = NULL;
+ //pThis->Hfs.pszDataPreparerId = NULL;
+ //pThis->Hfs.pszApplicationId = NULL;
+ //pThis->Hfs.pszCopyrightFileId = NULL;
+ //pThis->Hfs.pszAbstractFileId = NULL;
+ //pThis->Hfs.pszBibliographicFileId = NULL;
+
+ RTListInit(&pThis->ObjectHead);
+ //pThis->cObjects = 0;
+ //pThis->cbData = 0;
+
+ pThis->cVolumeDescriptors = 3; /* primary, secondary joliet, terminator. */
+ pThis->cbImagePadding = 150 * RTFSISOMAKER_SECTOR_SIZE;
+
+ //pThis->fStrictAttributeStyle = false;
+ //pThis->uidDefault = 0;
+ //pThis->gidDefault = 0;
+ pThis->fDefaultFileMode = 0444 | RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | RTFS_DOS_READONLY;
+ pThis->fDefaultDirMode = 0555 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | RTFS_DOS_READONLY;
+
+ //pThis->fForcedFileMode = 0;
+ //pThis->fForcedFileModeActive = false;
+ //pThis->fForcedDirModeActive = false;
+ //pThis->fForcedDirMode = 0;
+
+ //pThis->cCommonSources = 0;
+ //pThis->paCommonSources = NULL;
+
+ //pThis->pBootCatFile = NULL;
+
+ pThis->cbFinalizedImage = UINT64_MAX;
+ //pThis->pbSysArea = NULL;
+ //pThis->cbSysArea = 0;
+ //pThis->pbVolDescs = NULL;
+ //pThis->pPrimaryVolDesc = NULL;
+ //pThis->pElToritoDesc = NULL;
+ //pThis->pJolietVolDesc = NULL;
+
+ pThis->PrimaryIsoDirs.offDirs = UINT64_MAX;
+ pThis->PrimaryIsoDirs.offPathTableL = UINT64_MAX;
+ pThis->PrimaryIsoDirs.offPathTableM = UINT64_MAX;
+ pThis->PrimaryIsoDirs.cbPathTable = 0;
+ RTListInit(&pThis->PrimaryIsoDirs.FinalizedDirs);
+ //pThis->PrimaryIsoDirs.pRRSpillFile = NULL;
+
+ pThis->JolietDirs.offDirs = UINT64_MAX;
+ pThis->JolietDirs.offPathTableL = UINT64_MAX;
+ pThis->JolietDirs.offPathTableM = UINT64_MAX;
+ pThis->JolietDirs.cbPathTable = 0;
+ RTListInit(&pThis->JolietDirs.FinalizedDirs);
+ //pThis->JolietDirs.pRRSpillFile = NULL;
+
+ pThis->offFirstFile = UINT64_MAX;
+ RTListInit(&pThis->FinalizedFiles);
+
+ RTTimeNow(&pThis->ImageCreationTime);
+
+ /*
+ * Add the root directory node with idObj == 0.
+ */
+ PRTFSISOMAKERDIR pDirRoot;
+ rc = rtFsIsoMakerAddUnnamedDirWorker(pThis, NULL /*pObjInfo*/, &pDirRoot);
+ if (RT_SUCCESS(rc))
+ {
+ *phIsoMaker = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+/**
+ * Frees an object.
+ *
+ * This is a worker for rtFsIsoMakerDestroy and RTFsIsoMakerObjRemove.
+ *
+ * @param pObj The object to free.
+ */
+DECLINLINE(void) rtFsIsoMakerObjDestroy(PRTFSISOMAKEROBJ pObj)
+{
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ switch (pFile->enmSrcType)
+ {
+ case RTFSISOMAKERSRCTYPE_PATH:
+ pFile->u.pszSrcPath = NULL;
+ break;
+
+ case RTFSISOMAKERSRCTYPE_TRANS_TBL:
+ pFile->u.pTransTblDir = NULL;
+ break;
+
+ case RTFSISOMAKERSRCTYPE_VFS_FILE:
+ RTVfsFileRelease(pFile->u.hVfsFile);
+ pFile->u.hVfsFile = NIL_RTVFSFILE;
+ break;
+
+ case RTFSISOMAKERSRCTYPE_COMMON:
+ case RTFSISOMAKERSRCTYPE_RR_SPILL:
+ break;
+
+ case RTFSISOMAKERSRCTYPE_INVALID:
+ case RTFSISOMAKERSRCTYPE_END:
+ AssertFailed();
+ break;
+
+ /* no default, want warnings */
+ }
+ if (pFile->pBootInfoTable)
+ {
+ RTMemFree(pFile->pBootInfoTable);
+ pFile->pBootInfoTable = NULL;
+ }
+ }
+
+ RTMemFree(pObj);
+}
+
+
+/**
+ * Frees a namespace node.
+ *
+ * This is a worker for rtFsIsoMakerDestroyTree and rtFsIsoMakerObjUnsetName.
+ *
+ * @param pName The node to free.
+ */
+DECLINLINE(void) rtFsIsoMakerDestroyName(PRTFSISOMAKERNAME pName)
+{
+ if (pName->fRockRidgeNmAlloced)
+ {
+ RTMemFree(pName->pszRockRidgeNm);
+ pName->pszRockRidgeNm = NULL;
+ }
+ if (pName->fTransNmAlloced)
+ {
+ RTMemFree(pName->pszTransNm);
+ pName->pszTransNm = NULL;
+ }
+ PRTFSISOMAKERNAMEDIR pDir = pName->pDir;
+ if (pDir != NULL)
+ {
+ Assert(pDir->cChildren == 0);
+ RTMemFree(pDir->papChildren);
+ pDir->papChildren = NULL;
+ }
+ RTMemFree(pName);
+}
+
+
+/**
+ * Destroys a namespace.
+ *
+ * @param pNamespace The namespace to destroy.
+ */
+static void rtFsIsoMakerDestroyTree(PRTFSISOMAKERNAMESPACE pNamespace)
+{
+ /*
+ * Recursively destroy the tree first.
+ */
+ PRTFSISOMAKERNAME pCur = pNamespace->pRoot;
+ if (pCur)
+ {
+ Assert(!pCur->pParent);
+ for (;;)
+ {
+ if ( pCur->pDir
+ && pCur->pDir->cChildren)
+ pCur = pCur->pDir->papChildren[pCur->pDir->cChildren - 1];
+ else
+ {
+ PRTFSISOMAKERNAME pNext = pCur->pParent;
+ rtFsIsoMakerDestroyName(pCur);
+
+ /* Unlink from parent, we're the last entry. */
+ if (pNext)
+ {
+ Assert(pNext->pDir->cChildren > 0);
+ pNext->pDir->cChildren--;
+ Assert(pNext->pDir->papChildren[pNext->pDir->cChildren] == pCur);
+ pNext->pDir->papChildren[pNext->pDir->cChildren] = NULL;
+ pCur = pNext;
+ }
+ else
+ {
+ Assert(pNamespace->pRoot == pCur);
+ break;
+ }
+ }
+ }
+ pNamespace->pRoot = NULL;
+ }
+
+ /*
+ * Free the translation table filename if allocated.
+ */
+ if (pNamespace->pszTransTbl)
+ {
+ if (pNamespace->pszTransTbl != g_szTransTbl)
+ RTStrFree(pNamespace->pszTransTbl);
+ pNamespace->pszTransTbl = NULL;
+ }
+
+ /*
+ * Free string IDs.
+ */
+ if (pNamespace->pszSystemId)
+ {
+ if (pNamespace->pszSystemId != g_szSystemId)
+ RTStrFree(pNamespace->pszSystemId);
+ pNamespace->pszSystemId = NULL;
+ }
+
+ if (pNamespace->pszVolumeId)
+ {
+ RTStrFree(pNamespace->pszVolumeId);
+ pNamespace->pszVolumeId = NULL;
+ }
+
+ if (pNamespace->pszVolumeSetId)
+ {
+ RTStrFree(pNamespace->pszVolumeSetId);
+ pNamespace->pszVolumeSetId = NULL;
+ }
+
+ if (pNamespace->pszPublisherId)
+ {
+ RTStrFree(pNamespace->pszPublisherId);
+ pNamespace->pszPublisherId = NULL;
+ }
+
+ if (pNamespace->pszDataPreparerId)
+ {
+ RTStrFree(pNamespace->pszDataPreparerId);
+ pNamespace->pszDataPreparerId = NULL;
+ }
+
+ if (pNamespace->pszApplicationId)
+ {
+ if ( pNamespace->pszApplicationId != g_szAppIdPrimaryIso
+ && pNamespace->pszApplicationId != g_szAppIdJoliet)
+ RTStrFree(pNamespace->pszApplicationId);
+ pNamespace->pszApplicationId = NULL;
+ }
+
+ if (pNamespace->pszCopyrightFileId)
+ {
+ RTStrFree(pNamespace->pszCopyrightFileId);
+ pNamespace->pszCopyrightFileId = NULL;
+ }
+
+ if (pNamespace->pszAbstractFileId)
+ {
+ RTStrFree(pNamespace->pszAbstractFileId);
+ pNamespace->pszAbstractFileId = NULL;
+ }
+
+ if (pNamespace->pszBibliographicFileId)
+ {
+ RTStrFree(pNamespace->pszBibliographicFileId);
+ pNamespace->pszBibliographicFileId = NULL;
+ }
+}
+
+
+/**
+ * Destroys an ISO maker instance.
+ *
+ * @param pThis The ISO maker instance to destroy.
+ */
+static void rtFsIsoMakerDestroy(PRTFSISOMAKERINT pThis)
+{
+ rtFsIsoMakerDestroyTree(&pThis->PrimaryIso);
+ rtFsIsoMakerDestroyTree(&pThis->Joliet);
+ rtFsIsoMakerDestroyTree(&pThis->Udf);
+ rtFsIsoMakerDestroyTree(&pThis->Hfs);
+
+ PRTFSISOMAKEROBJ pCur;
+ PRTFSISOMAKEROBJ pNext;
+ RTListForEachSafe(&pThis->ObjectHead, pCur, pNext, RTFSISOMAKEROBJ, Entry)
+ {
+ RTListNodeRemove(&pCur->Entry);
+ rtFsIsoMakerObjDestroy(pCur);
+ }
+
+ if (pThis->paCommonSources)
+ {
+ RTMemFree(pThis->paCommonSources);
+ pThis->paCommonSources = NULL;
+ }
+
+ if (pThis->pbVolDescs)
+ {
+ RTMemFree(pThis->pbVolDescs);
+ pThis->pbVolDescs = NULL;
+ }
+
+ if (pThis->pbSysArea)
+ {
+ RTMemFree(pThis->pbSysArea);
+ pThis->pbSysArea = NULL;
+ }
+
+ pThis->uMagic = ~RTFSISOMAKERINT_MAGIC;
+ RTMemFree(pThis);
+}
+
+
+/**
+ * Retains a references to an ISO maker instance.
+ *
+ * @returns New reference count on success, UINT32_MAX if invalid handle.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint32_t) RTFsIsoMakerRetain(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTFSISOMAKERINT_MAGIC, UINT32_MAX);
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < _64K);
+ return cRefs;
+}
+
+
+/**
+ * Releases a references to an ISO maker instance.
+ *
+ * @returns New reference count on success, UINT32_MAX if invalid handle.
+ * @param hIsoMaker The ISO maker handle. NIL is ignored.
+ */
+RTDECL(uint32_t) RTFsIsoMakerRelease(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ uint32_t cRefs;
+ if (pThis == NIL_RTFSISOMAKER)
+ cRefs = 0;
+ else
+ {
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTFSISOMAKERINT_MAGIC, UINT32_MAX);
+ cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < _64K);
+ if (!cRefs)
+ rtFsIsoMakerDestroy(pThis);
+ }
+ return cRefs;
+}
+
+
+/**
+ * Sets the ISO-9660 level.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param uIsoLevel The level, 1-3.
+ */
+RTDECL(int) RTFsIsoMakerSetIso9660Level(RTFSISOMAKER hIsoMaker, uint8_t uIsoLevel)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(uIsoLevel <= 3, VERR_INVALID_PARAMETER);
+ AssertReturn(uIsoLevel > 0, VERR_INVALID_PARAMETER); /* currently not possible to disable this */
+ AssertReturn(!pThis->fSeenContent, VERR_WRONG_ORDER);
+
+ pThis->PrimaryIso.uLevel = uIsoLevel;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the ISO-9660 level.
+ *
+ * @returns The level, UINT8_MAX if invalid handle.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint8_t) RTFsIsoMakerGetIso9660Level(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT8_MAX);
+ return pThis->PrimaryIso.uLevel;
+}
+
+
+/**
+ * Sets the joliet level.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param uJolietLevel The joliet UCS-2 level 1-3, or 0 to disable
+ * joliet.
+ */
+RTDECL(int) RTFsIsoMakerSetJolietUcs2Level(RTFSISOMAKER hIsoMaker, uint8_t uJolietLevel)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(uJolietLevel <= 3, VERR_INVALID_PARAMETER);
+ AssertReturn(!pThis->fSeenContent, VERR_WRONG_ORDER);
+
+ if (pThis->Joliet.uLevel != uJolietLevel)
+ {
+ if (uJolietLevel == 0)
+ pThis->cVolumeDescriptors--;
+ else if (pThis->Joliet.uLevel == 0)
+ pThis->cVolumeDescriptors++;
+ pThis->Joliet.uLevel = uJolietLevel;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the rock ridge support level (on the primary ISO-9660 namespace).
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param uLevel 0 if disabled, 1 to just enable, 2 to enable and
+ * write the ER tag.
+ */
+RTDECL(int) RTFsIsoMakerSetRockRidgeLevel(RTFSISOMAKER hIsoMaker, uint8_t uLevel)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(uLevel <= 2, VERR_INVALID_PARAMETER);
+ AssertReturn( !pThis->fSeenContent
+ || (uLevel >= pThis->PrimaryIso.uRockRidgeLevel && pThis->PrimaryIso.uRockRidgeLevel > 0), VERR_WRONG_ORDER);
+ AssertReturn(!pThis->fSeenContent, VERR_WRONG_ORDER);
+
+ pThis->PrimaryIso.uRockRidgeLevel = uLevel;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the rock ridge support level on the joliet namespace (experimental).
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param uLevel 0 if disabled, 1 to just enable, 2 to enable and
+ * write the ER tag.
+ */
+RTDECL(int) RTFsIsoMakerSetJolietRockRidgeLevel(RTFSISOMAKER hIsoMaker, uint8_t uLevel)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(uLevel <= 2, VERR_INVALID_PARAMETER);
+ AssertReturn( !pThis->fSeenContent
+ || (uLevel >= pThis->Joliet.uRockRidgeLevel && pThis->Joliet.uRockRidgeLevel > 0), VERR_WRONG_ORDER);
+
+ pThis->Joliet.uRockRidgeLevel = uLevel;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the rock ridge support level (on the primary ISO-9660 namespace).
+ *
+ * @returns 0 if disabled, 1 just enabled, 2 if enabled with ER tag, and
+ * UINT8_MAX if the handle is invalid.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint8_t) RTFsIsoMakerGetRockRidgeLevel(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT8_MAX);
+ return pThis->PrimaryIso.uRockRidgeLevel;
+}
+
+
+/**
+ * Gets the rock ridge support level on the joliet namespace (experimental).
+ *
+ * @returns 0 if disabled, 1 just enabled, 2 if enabled with ER tag, and
+ * UINT8_MAX if the handle is invalid.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint8_t) RTFsIsoMakerGetJolietRockRidgeLevel(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT8_MAX);
+ return pThis->Joliet.uRockRidgeLevel;
+}
+
+
+/**
+ * Changes the file attribute (mode, owner, group) inherit style (from source).
+ *
+ * The strict style will use the exact attributes from the source, where as the
+ * non-strict (aka rational and default) style will use 0 for the owner and
+ * group IDs and normalize the mode bits along the lines of 'chmod a=rX',
+ * stripping set-uid/gid bitson files but preserving sticky ones on directories.
+ *
+ * When disabling strict style, the default dir and file modes will be restored
+ * to default values.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fStrict Indicates strict (true) or non-strict (false)
+ * style.
+ */
+RTDECL(int) RTFsIsoMakerSetAttribInheritStyle(RTFSISOMAKER hIsoMaker, bool fStrict)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+
+ pThis->fStrictAttributeStyle = fStrict;
+ if (!fStrict)
+ {
+ pThis->fDefaultFileMode = 0444 | RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | RTFS_DOS_READONLY;
+ pThis->fDefaultDirMode = 0555 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | RTFS_DOS_READONLY;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the default file mode settings.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fMode The default file mode.
+ */
+RTDECL(int) RTFsIsoMakerSetDefaultFileMode(RTFSISOMAKER hIsoMaker, RTFMODE fMode)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ Assert(!(fMode & ~RTFS_UNIX_ALL_PERMS));
+
+ pThis->fDefaultFileMode &= ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDefaultFileMode |= fMode & RTFS_UNIX_ALL_PERMS;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the default dir mode settings.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fMode The default dir mode.
+ */
+RTDECL(int) RTFsIsoMakerSetDefaultDirMode(RTFSISOMAKER hIsoMaker, RTFMODE fMode)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ Assert(!(fMode & ~RTFS_UNIX_ALL_PERMS));
+
+ pThis->fDefaultDirMode &= ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDefaultDirMode |= fMode & RTFS_UNIX_ALL_PERMS;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the forced file mode, if @a fForce is true also the default mode is set.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fMode The file mode.
+ * @param fForce Indicate whether forced mode is active or not.
+ */
+RTDECL(int) RTFsIsoMakerSetForcedFileMode(RTFSISOMAKER hIsoMaker, RTFMODE fMode, bool fForce)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ Assert(!(fMode & ~RTFS_UNIX_ALL_PERMS));
+
+ pThis->fForcedFileMode = fMode & RTFS_UNIX_ALL_PERMS;
+ pThis->fForcedFileModeActive = fForce;
+ if (fForce)
+ {
+ pThis->fDefaultFileMode &= ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDefaultFileMode |= fMode & RTFS_UNIX_ALL_PERMS;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the forced dir mode, if @a fForce is true also the default mode is set.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param fMode The dir mode.
+ * @param fForce Indicate whether forced mode is active or not.
+ */
+RTDECL(int) RTFsIsoMakerSetForcedDirMode(RTFSISOMAKER hIsoMaker, RTFMODE fMode, bool fForce)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ Assert(!(fMode & ~RTFS_UNIX_ALL_PERMS));
+
+ pThis->fForcedDirModeActive = fForce;
+ pThis->fForcedDirMode = fMode & RTFS_UNIX_ALL_PERMS;
+ if (fForce)
+ {
+ pThis->fDefaultDirMode &= ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDefaultDirMode |= fMode & RTFS_UNIX_ALL_PERMS;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets the content of the system area, i.e. the first 32KB of the image.
+ *
+ * This can be used to put generic boot related stuff.
+ *
+ * @note Other settings may overwrite parts of the content (yet to be
+ * determined which).
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pvContent The content to put in the system area.
+ * @param cbContent The size of the content.
+ * @param off The offset into the system area.
+ */
+RTDECL(int) RTFsIsoMakerSetSysAreaContent(RTFSISOMAKER hIsoMaker, void const *pvContent, size_t cbContent, uint32_t off)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+ AssertReturn(cbContent > 0, VERR_OUT_OF_RANGE);
+ AssertReturn(cbContent <= _32K, VERR_OUT_OF_RANGE);
+ AssertReturn(off < _32K, VERR_OUT_OF_RANGE);
+ size_t cbSysArea = off + cbContent;
+ AssertReturn(cbSysArea <= _32K, VERR_OUT_OF_RANGE);
+
+ /*
+ * Adjust the allocation and copy over the new/additional content.
+ */
+ if (pThis->cbSysArea < cbSysArea)
+ {
+ void *pvNew = RTMemRealloc(pThis->pbSysArea, cbSysArea);
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pThis->pbSysArea = (uint8_t *)pvNew;
+ memset(&pThis->pbSysArea[pThis->cbSysArea], 0, cbSysArea - pThis->cbSysArea);
+ }
+
+ memcpy(&pThis->pbSysArea[off], pvContent, cbContent);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Sets a string property in one or more namespaces.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param enmStringProp The string property to set.
+ * @param fNamespaces The namespaces to set it in.
+ * @param pszValue The value to set it to. NULL is treated like an
+ * empty string. The value will be silently truncated
+ * to fit the available space.
+ */
+RTDECL(int) RTFsIsoMakerSetStringProp(RTFSISOMAKER hIsoMaker, RTFSISOMAKERSTRINGPROP enmStringProp,
+ uint32_t fNamespaces, const char *pszValue)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn( enmStringProp > RTFSISOMAKERSTRINGPROP_INVALID
+ && enmStringProp < RTFSISOMAKERSTRINGPROP_END, VERR_INVALID_PARAMETER);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ if (pszValue)
+ {
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+ if (*pszValue == '\0')
+ pszValue = NULL;
+ }
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Work the namespaces.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ /* Get a pointer to the field. */
+ char **ppszValue;
+ switch (enmStringProp)
+ {
+ case RTFSISOMAKERSTRINGPROP_SYSTEM_ID: ppszValue = &pNamespace->pszSystemId; break;
+ case RTFSISOMAKERSTRINGPROP_VOLUME_ID: ppszValue = &pNamespace->pszVolumeId; break;
+ case RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID: ppszValue = &pNamespace->pszVolumeSetId; break;
+ case RTFSISOMAKERSTRINGPROP_PUBLISHER_ID: ppszValue = &pNamespace->pszPublisherId; break;
+ case RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID: ppszValue = &pNamespace->pszDataPreparerId; break;
+ case RTFSISOMAKERSTRINGPROP_APPLICATION_ID: ppszValue = &pNamespace->pszApplicationId; break;
+ case RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID: ppszValue = &pNamespace->pszCopyrightFileId; break;
+ case RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID: ppszValue = &pNamespace->pszAbstractFileId; break;
+ case RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID: ppszValue = &pNamespace->pszBibliographicFileId; break;
+ default: AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ /* Free the old value. */
+ char *pszOld = *ppszValue;
+ if ( pszOld
+ && pszOld != g_szAppIdPrimaryIso
+ && pszOld != g_szAppIdJoliet
+ && pszOld != g_szSystemId)
+ RTStrFree(pszOld);
+
+ /* Set the new value. */
+ if (!pszValue)
+ *ppszValue = NULL;
+ else
+ {
+ *ppszValue = RTStrDup(pszValue);
+ AssertReturn(*ppszValue, VERR_NO_STR_MEMORY);
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Specifies image padding.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param cSectors Number of sectors to pad the image with.
+ */
+RTDECL(int) RTFsIsoMakerSetImagePadding(RTFSISOMAKER hIsoMaker, uint32_t cSectors)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(cSectors <= _64K, VERR_OUT_OF_RANGE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ pThis->cbImagePadding = cSectors * RTFSISOMAKER_SECTOR_SIZE;
+ return VINF_SUCCESS;
+}
+
+
+
+
+
+/*
+ *
+ * Name space related internals.
+ * Name space related internals.
+ * Name space related internals.
+ *
+ */
+
+
+/**
+ * Gets the pointer to the name member for the given namespace.
+ *
+ * @returns Pointer to name member.
+ * @param pObj The object to find a name member in.
+ * @param pNamespace The namespace which name to calculate.
+ */
+DECLINLINE(PPRTFSISOMAKERNAME) rtFsIsoMakerObjGetNameForNamespace(PRTFSISOMAKEROBJ pObj, PCRTFSISOMAKERNAMESPACE pNamespace)
+{
+ return (PPRTFSISOMAKERNAME)((uintptr_t)pObj + pNamespace->offName);
+}
+
+
+/**
+ * Locates a child object by its namespace name.
+ *
+ * @returns Pointer to the child if found, NULL if not.
+ * @param pDirObj The directory object to search.
+ * @param pszEntry The (namespace) entry name.
+ * @param cchEntry The length of the name.
+ */
+static PRTFSISOMAKERNAME rtFsIsoMakerFindObjInDir(PRTFSISOMAKERNAME pDirObj, const char *pszEntry, size_t cchEntry)
+{
+ if (pDirObj)
+ {
+ PRTFSISOMAKERNAMEDIR pDir = pDirObj->pDir;
+ AssertReturn(pDir, NULL);
+
+ uint32_t i = pDir->cChildren;
+ while (i-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[i];
+ if ( pChild->cchName == cchEntry
+ && RTStrNICmp(pChild->szName, pszEntry, cchEntry) == 0)
+ return pChild;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Compares the two names according to ISO-9660 directory sorting rules.
+ *
+ * As long as we don't want to do case insensitive joliet sorting, this works
+ * for joliet names to, I think.
+ *
+ * @returns 0 if equal, -1 if pszName1 comes first, 1 if pszName2 comes first.
+ * @param pszName1 The first name.
+ * @param pszName2 The second name.
+ */
+DECLINLINE(int) rtFsIsoMakerCompareIso9660Names(const char *pszName1, const char *pszName2)
+{
+ for (;;)
+ {
+ char const ch1 = *pszName1++;
+ char const ch2 = *pszName2++;
+ if (ch1 == ch2)
+ {
+ if (ch1)
+ { /* likely */ }
+ else
+ return 0;
+ }
+ else if (ch1 == ';' || ch2 == ';')
+ return ch1 == ';' ? -1 : 1;
+ else if (ch1 == '.' || ch2 == '.')
+ return ch1 == '.' ? -1 : 1;
+ else
+ return (unsigned char)ch1 < (unsigned char)ch2 ? -1 : 1;
+ }
+}
+
+
+/**
+ * Finds the index into papChildren where the given name should be inserted.
+ *
+ * @returns Index of the given name.
+ * @param pNamespace The namspace.
+ * @param pParent The parent namespace node.
+ * @param pszName The name.
+ */
+static uint32_t rtFsIsoMakerFindInsertIndex(PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKERNAME pParent, const char *pszName)
+{
+ uint32_t idxRet = pParent->pDir->cChildren;
+ if (idxRet > 0)
+ {
+ /*
+ * The idea is to do binary search using a namespace specific compare
+ * function. However, it looks like we can get away with using the
+ * same compare function for all namespaces.
+ */
+ uint32_t idxStart = 0;
+ uint32_t idxEnd = idxRet;
+ PPRTFSISOMAKERNAME papChildren = pParent->pDir->papChildren;
+ switch (pNamespace->fNamespace)
+ {
+ case RTFSISOMAKER_NAMESPACE_ISO_9660:
+ case RTFSISOMAKER_NAMESPACE_JOLIET:
+ case RTFSISOMAKER_NAMESPACE_UDF:
+ case RTFSISOMAKER_NAMESPACE_HFS:
+ for (;;)
+ {
+ idxRet = idxStart + (idxEnd - idxStart) / 2;
+ PRTFSISOMAKERNAME pCur = papChildren[idxRet];
+ int iDiff = rtFsIsoMakerCompareIso9660Names(pszName, pCur->szName);
+ if (iDiff < 0)
+ {
+ if (idxRet > idxStart)
+ idxEnd = idxRet;
+ else
+ break;
+ }
+ else
+ {
+ idxRet++;
+ if ( iDiff != 0
+ && idxRet < idxEnd)
+ idxStart = idxRet;
+ else
+ break;
+ }
+ }
+ break;
+
+ default:
+ AssertFailed();
+ break;
+ }
+ }
+ return idxRet;
+}
+
+
+
+/**
+ * Locates a child entry by its specified name.
+ *
+ * @returns Pointer to the child if found, NULL if not.
+ * @param pDirName The directory name to search.
+ * @param pszEntry The (specified) entry name.
+ * @param cchEntry The length of the name.
+ */
+static PRTFSISOMAKERNAME rtFsIsoMakerFindEntryInDirBySpec(PRTFSISOMAKERNAME pDirName, const char *pszEntry, size_t cchEntry)
+{
+ if (pDirName)
+ {
+ PRTFSISOMAKERNAMEDIR pDir = pDirName->pDir;
+ AssertReturn(pDir, NULL);
+
+ uint32_t i = pDir->cChildren;
+ while (i-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[i];
+ if ( pChild->cchSpecNm == cchEntry
+ && RTStrNICmp(pChild->pszSpecNm, pszEntry, cchEntry) == 0)
+ return pChild;
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Locates a subdir object in any namespace by its specified name.
+ *
+ * This is used to avoid having one instance of RTFSISOMAKERDIR in each
+ * namespace for the same directory.
+ *
+ * @returns Pointer to the subdir object if found, NULL if not.
+ * @param pDirObj The directory object to search.
+ * @param pszEntry The (specified) entry name.
+ * @param cchEntry The length of the name.
+ * @param fSkipNamespaces Namespaces to skip.
+ * @sa rtFsIsoMakerFindEntryInDirBySpec
+ */
+static PRTFSISOMAKERDIR rtFsIsoMakerFindSubdirBySpec(PRTFSISOMAKERDIR pDirObj, const char *pszEntry, size_t cchEntry,
+ uint32_t fSkipNamespaces)
+{
+ AssertReturn(pDirObj, NULL);
+ AssertReturn(pDirObj->Core.enmType == RTFSISOMAKEROBJTYPE_DIR, NULL);
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (!(fSkipNamespaces & g_aRTFsIsoNamespaces[i].fNamespace))
+ {
+ PRTFSISOMAKERNAME pDirName = *(PPRTFSISOMAKERNAME)((uintptr_t)pDirObj + g_aRTFsIsoNamespaces[i].offName);
+ if (pDirName)
+ {
+ PRTFSISOMAKERNAMEDIR pDir = pDirName->pDir;
+ AssertStmt(pDir, continue);
+
+ uint32_t iChild = pDir->cChildren;
+ while (iChild-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[iChild];
+ if ( pChild->cchSpecNm == cchEntry
+ && pChild->pDir != NULL
+ && RTStrNICmp(pChild->pszSpecNm, pszEntry, cchEntry) == 0)
+ return (PRTFSISOMAKERDIR)pChild->pObj;
+ }
+ }
+ }
+ return NULL;
+}
+
+
+/**
+ * Walks the given path by specified object names in a namespace.
+ *
+ * @returns IPRT status code.
+ * @param pNamespace The namespace to walk the path in.
+ * @param pszPath The path to walk.
+ * @param ppName Where to return the name node that the path ends with.
+ */
+static int rtFsIsoMakerWalkPathBySpec(PRTFSISOMAKERNAMESPACE pNamespace, const char *pszPath, PPRTFSISOMAKERNAME ppName)
+{
+ *ppName = NULL;
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+
+ /*
+ * Deal with the special case of the root.
+ */
+ while (RTPATH_IS_SLASH(*pszPath))
+ pszPath++;
+
+ PRTFSISOMAKERNAME pCur = pNamespace->pRoot;
+ if (!pCur)
+ return *pszPath ? VERR_PATH_NOT_FOUND : VERR_FILE_NOT_FOUND;
+ if (!*pszPath)
+ {
+ *ppName = pCur;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Now, do the rest of the path.
+ */
+ for (;;)
+ {
+ /*
+ * Find the end of the component.
+ */
+ char ch;
+ size_t cchComponent = 0;
+ while ((ch = pszPath[cchComponent]) != '\0' && !RTPATH_IS_SLASH(ch))
+ cchComponent++;
+ if (!cchComponent)
+ {
+ *ppName = pCur;
+ return VINF_SUCCESS;
+ }
+
+ size_t offNext = cchComponent;
+ while (RTPATH_IS_SLASH(ch))
+ ch = pszPath[++offNext];
+
+ /*
+ * Deal with dot and dot-dot.
+ */
+ if (cchComponent == 1 && pszPath[0] == '.')
+ { /* nothing to do */ }
+ else if (cchComponent == 2 && pszPath[0] == '.' && pszPath[1] == '.')
+ {
+ if (pCur->pParent)
+ pCur = pCur->pParent;
+ }
+ /*
+ * Look up the name.
+ */
+ else
+ {
+ PRTFSISOMAKERNAME pChild = rtFsIsoMakerFindEntryInDirBySpec(pCur, pszPath, cchComponent);
+ if (!pChild)
+ return pszPath[offNext] ? VERR_PATH_NOT_FOUND : VERR_FILE_NOT_FOUND;
+ if ( (offNext > cchComponent)
+ && !pChild->pDir)
+ return VERR_NOT_A_DIRECTORY;
+ pCur = pChild;
+ }
+
+ /*
+ * Skip ahead in the path.
+ */
+ pszPath += offNext;
+ }
+}
+
+
+/**
+ * Copy and convert a name to valid ISO-9660 (d-characters only).
+ *
+ * Worker for rtFsIsoMakerNormalizeNameForNamespace. ASSUMES it deals with
+ * dots.
+ *
+ * @returns Length of the resulting string.
+ * @param pszDst The output buffer.
+ * @param cchDstMax The maximum number of (d-chars) to put in the output
+ * buffer.
+ * @param pchSrc The UTF-8 source string (not neccessarily terminated).
+ * @param cchSrc The maximum number of chars to copy from the source
+ * string.
+ */
+static size_t rtFsIsoMakerCopyIso9660Name(char *pszDst, size_t cchDstMax, const char *pchSrc, size_t cchSrc)
+{
+ const char *pchSrcIn = pchSrc;
+ size_t offDst = 0;
+ while ((size_t)(pchSrc - pchSrcIn) < cchSrc)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pchSrc, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if ( uc < 128
+ && RTFSISOMAKER_IS_UPPER_IN_D_CHARS((char)uc))
+ {
+ pszDst[offDst++] = RT_C_TO_UPPER((char)uc);
+ if (offDst >= cchDstMax)
+ break;
+ }
+ }
+ }
+ pszDst[offDst] = '\0';
+ return offDst;
+}
+
+
+/**
+ * Normalizes a name for the primary ISO-9660 namespace.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pParent The parent directory. NULL if root.
+ * @param pchSrc The specified name to normalize (not necessarily zero
+ * terminated).
+ * @param cchSrc The length of the specified name.
+ * @param fNoNormalize Don't normalize the name very strictly (imported or
+ * such).
+ * @param fIsDir Indicates whether it's a directory or file (like).
+ * @param pszDst The output buffer. Must be at least 32 bytes.
+ * @param cbDst The size of the output buffer.
+ * @param pcchDst Where to return the length of the returned string (i.e.
+ * not counting the terminator).
+ * @param pcbInDirRec Where to return the name size in the directory record.
+ */
+static int rtFsIsoMakerNormalizeNameForPrimaryIso9660(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAME pParent,
+ const char *pchSrc, size_t cchSrc, bool fNoNormalize, bool fIsDir,
+ char *pszDst, size_t cbDst, size_t *pcchDst, size_t *pcbInDirRec)
+{
+ AssertReturn(cbDst > ISO9660_MAX_NAME_LEN + 2, VERR_ISOMK_IPE_BUFFER_SIZE);
+
+ /* Skip leading dots. */
+ while (cchSrc > 0 && *pchSrc == '.')
+ pchSrc++, cchSrc--;
+ if (!cchSrc)
+ {
+ pchSrc = "DOTS";
+ cchSrc = 4;
+ }
+
+ /*
+ * Produce a first name.
+ */
+ uint8_t const uIsoLevel = !fNoNormalize ? pThis->PrimaryIso.uLevel : RT_MAX(pThis->PrimaryIso.uLevel, 3);
+ size_t cchDst;
+ size_t offDstDot;
+ if (fIsDir && !fNoNormalize)
+ offDstDot = cchDst = rtFsIsoMakerCopyIso9660Name(pszDst, uIsoLevel >= 2 ? ISO9660_MAX_NAME_LEN : 8,
+ pchSrc, cchSrc);
+ else
+ {
+ /* Look for the last dot and try preserve the extension when doing the conversion. */
+ size_t offLastDot = cchSrc;
+ for (size_t off = 0; off < cchSrc; off++)
+ if (pchSrc[off] == '.')
+ offLastDot = off;
+
+ if (fNoNormalize)
+ {
+ /* Try preserve the imported name, though, put the foot down if too long. */
+ offDstDot = offLastDot;
+ cchDst = cchSrc;
+ if (cchSrc > ISO9660_MAX_NAME_LEN)
+ {
+ cchDst = ISO9660_MAX_NAME_LEN;
+ if (offDstDot > cchDst)
+ offDstDot = cchDst;
+ }
+ memcpy(pszDst, pchSrc, cchDst);
+ pszDst[cchDst] = '\0';
+ }
+ else if (offLastDot == cchSrc)
+ offDstDot = cchDst = rtFsIsoMakerCopyIso9660Name(pszDst, uIsoLevel >= 2 ? ISO9660_MAX_NAME_LEN : 8,
+ pchSrc, cchSrc);
+ else
+ {
+ const char * const pchSrcExt = &pchSrc[offLastDot + 1];
+ size_t const cchSrcExt = cchSrc - offLastDot - 1;
+ if (uIsoLevel < 2)
+ {
+ cchDst = rtFsIsoMakerCopyIso9660Name(pszDst, 8, pchSrc, cchSrc);
+ offDstDot = cchDst;
+ pszDst[cchDst++] = '.';
+ cchDst += rtFsIsoMakerCopyIso9660Name(&pszDst[cchDst], 3, pchSrcExt, cchSrcExt);
+ }
+ else
+ {
+ size_t cchDstExt = rtFsIsoMakerCopyIso9660Name(pszDst, ISO9660_MAX_NAME_LEN - 2, pchSrcExt, cchSrcExt);
+ if (cchDstExt > 0)
+ {
+ size_t cchBasename = rtFsIsoMakerCopyIso9660Name(pszDst, ISO9660_MAX_NAME_LEN - 2,
+ pchSrc, offLastDot);
+ if (cchBasename + 1 + cchDstExt <= ISO9660_MAX_NAME_LEN)
+ cchDst = cchBasename;
+ else
+ cchDst = ISO9660_MAX_NAME_LEN - 1 - RT_MIN(cchDstExt, 4);
+ offDstDot = cchDst;
+ pszDst[cchDst++] = '.';
+ cchDst += rtFsIsoMakerCopyIso9660Name(&pszDst[cchDst], ISO9660_MAX_NAME_LEN - 1 - cchDst,
+ pchSrcExt, cchSrcExt);
+ }
+ else
+ offDstDot = cchDst = rtFsIsoMakerCopyIso9660Name(pszDst, ISO9660_MAX_NAME_LEN, pchSrc, cchSrc);
+ }
+ }
+ }
+
+ /* Append version if not directory */
+ if (!fIsDir)
+ {
+ pszDst[cchDst++] = ';';
+ pszDst[cchDst++] = '1';
+ pszDst[cchDst] = '\0';
+ }
+
+ /*
+ * Unique name?
+ */
+ if (!rtFsIsoMakerFindObjInDir(pParent, pszDst, cchDst))
+ {
+ *pcchDst = cchDst;
+ *pcbInDirRec = cchDst;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Mangle the name till we've got a unique one.
+ */
+ size_t const cchMaxBasename = (uIsoLevel >= 2 ? ISO9660_MAX_NAME_LEN : 8) - (cchDst - offDstDot);
+ size_t cchInserted = 0;
+ for (uint32_t i = 0; i < _32K; i++)
+ {
+ /* Add a numberic infix. */
+ char szOrd[64];
+ size_t cchOrd = RTStrFormatU32(szOrd, sizeof(szOrd), i + 1, 10, -1, -1, 0 /*fFlags*/);
+ Assert((ssize_t)cchOrd > 0);
+
+ /* Do we need to shuffle the suffix? */
+ if (cchOrd > cchInserted)
+ {
+ if (offDstDot < cchMaxBasename)
+ {
+ memmove(&pszDst[offDstDot + 1], &pszDst[offDstDot], cchDst + 1 - offDstDot);
+ cchDst++;
+ offDstDot++;
+ }
+ cchInserted = cchOrd;
+ }
+
+ /* Insert the new infix and try again. */
+ memcpy(&pszDst[offDstDot - cchOrd], szOrd, cchOrd);
+ if (!rtFsIsoMakerFindObjInDir(pParent, pszDst, cchDst))
+ {
+ *pcchDst = cchDst;
+ *pcbInDirRec = cchDst;
+ return VINF_SUCCESS;
+ }
+ }
+ AssertFailed();
+ return VERR_DUPLICATE;
+}
+
+
+/**
+ * Normalizes a name for the specified name space.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace which rules to normalize it according to.
+ * @param pParent The parent directory. NULL if root.
+ * @param pchSrc The specified name to normalize (not necessarily zero
+ * terminated).
+ * @param cchSrc The length of the specified name.
+ * @param fIsDir Indicates whether it's a directory or file (like).
+ * @param fNoNormalize Don't normalize the name very strictly (imported or
+ * such).
+ * @param pszDst The output buffer. Must be at least 32 bytes.
+ * @param cbDst The size of the output buffer.
+ * @param pcchDst Where to return the length of the returned string (i.e.
+ * not counting the terminator).
+ * @param pcbInDirRec Where to return the name size in the directory record.
+ */
+static int rtFsIsoMakerNormalizeNameForNamespace(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKERNAME pParent, const char *pchSrc, size_t cchSrc,
+ bool fNoNormalize, bool fIsDir,
+ char *pszDst, size_t cbDst, size_t *pcchDst, size_t *pcbInDirRec)
+{
+ if (cchSrc > 0)
+ {
+ /*
+ * Check that the object doesn't already exist.
+ */
+ AssertReturn(!rtFsIsoMakerFindEntryInDirBySpec(pParent, pchSrc, cchSrc), VERR_ALREADY_EXISTS);
+ switch (pNamespace->fNamespace)
+ {
+ /*
+ * This one is a lot of work, so separate function.
+ */
+ case RTFSISOMAKER_NAMESPACE_ISO_9660:
+ return rtFsIsoMakerNormalizeNameForPrimaryIso9660(pThis, pParent, pchSrc, cchSrc, fNoNormalize, fIsDir,
+ pszDst, cbDst, pcchDst, pcbInDirRec);
+
+ /*
+ * At the moment we don't give darn about UCS-2 limitations here...
+ */
+ case RTFSISOMAKER_NAMESPACE_JOLIET:
+ {
+/** @todo Joliet name limit and check for duplicates. */
+ AssertReturn(cbDst > cchSrc, VERR_BUFFER_OVERFLOW);
+ memcpy(pszDst, pchSrc, cchSrc);
+ pszDst[cchSrc] = '\0';
+ *pcchDst = cchSrc;
+ *pcbInDirRec = RTStrCalcUtf16Len(pszDst) * sizeof(RTUTF16);
+ return VINF_SUCCESS;
+ }
+
+ case RTFSISOMAKER_NAMESPACE_UDF:
+ case RTFSISOMAKER_NAMESPACE_HFS:
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+ }
+ else
+ {
+ /*
+ * Root special case.
+ *
+ * For ISO-9660 and joliet, we enter it with a length of 1 byte. The
+ * value byte value is zero. The path tables we generate won't be
+ * accepted by windows unless we do this.
+ */
+ *pszDst = '\0';
+ *pcchDst = 0;
+ *pcbInDirRec = pNamespace->fNamespace & (RTFSISOMAKER_NAMESPACE_ISO_9660 | RTFSISOMAKER_NAMESPACE_JOLIET) ? 1 : 0;
+ AssertReturn(!pParent, VERR_ISOMK_IPE_NAMESPACE_3);
+ return VINF_SUCCESS;
+ }
+}
+
+
+/**
+ * Creates a TRANS.TBL file object for a newly named directory.
+ *
+ * The file is associated with the namespace node for the directory. The file
+ * will be generated on the fly from the directory object.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pDirName The new name space node for the directory.
+ */
+static int rtFsIsoMakerAddTransTblFileToNewDir(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKERNAME pDirName)
+{
+ /*
+ * Create a file object for it.
+ */
+ PRTFSISOMAKERFILE pFile;
+ int rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, NULL, 0, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_TRANS_TBL;
+ pFile->u.pTransTblDir = pDirName;
+ pFile->pBootInfoTable = NULL;
+ pDirName->pDir->pTransTblFile = pFile;
+
+ /*
+ * Add it to the directory.
+ */
+ PRTFSISOMAKERNAME pTransTblNm;
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, &pFile->Core, pDirName, pNamespace->pszTransTbl,
+ strlen(pNamespace->pszTransTbl), false /*fNoNormalize*/, &pTransTblNm);
+ if (RT_SUCCESS(rc))
+ {
+ pTransTblNm->cchTransNm = 0;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Bail.
+ */
+ pDirName->pDir->pTransTblFile = NULL;
+ rtFsIsoMakerObjRemoveWorker(pThis, &pFile->Core);
+ }
+ return rc;
+}
+
+
+/**
+ * Sets the name of an object in a namespace.
+ *
+ * If the object is already named in the name space, it will first be removed
+ * from that namespace. Should we run out of memory or into normalization
+ * issues after removing it, its original state will _not_ be restored.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pObj The object to name.
+ * @param pParent The parent namespace entry
+ * @param pchSpec The specified name (not necessarily terminated).
+ * @param cchSpec The specified name length.
+ * @param fNoNormalize Don't normalize the name (imported or such).
+ * @param ppNewName Where to return the name entry. Optional.
+ */
+static int rtFsIsoMakerObjSetName(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKEROBJ pObj,
+ PRTFSISOMAKERNAME pParent, const char *pchSpec, size_t cchSpec, bool fNoNormalize,
+ PPRTFSISOMAKERNAME ppNewName)
+{
+ Assert(cchSpec < _32K);
+
+ /*
+ * If this is a file, check the size against the ISO level.
+ * This ASSUMES that only files which size we already know will be 4GB+ sized.
+ */
+ if ( (pNamespace->fNamespace & RTFSISOMAKER_NAMESPACE_ISO_9660)
+ && pNamespace->uLevel < 3
+ && pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ if (pFile->cbData >= _4G)
+ return VERR_ISOMK_FILE_TOO_BIG_REQ_ISO_LEVEL_3;
+ }
+
+ /*
+ * If this is a symbolic link, refuse to add it to a namespace that isn't
+ * configured to support symbolic links.
+ */
+ if ( pObj->enmType == RTFSISOMAKEROBJTYPE_SYMLINK
+ && (pNamespace->fNamespace & (RTFSISOMAKER_NAMESPACE_ISO_9660 | RTFSISOMAKER_NAMESPACE_JOLIET))
+ && pNamespace->uRockRidgeLevel == 0)
+ return VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE;
+
+ /*
+ * If the object is already named, unset that name before continuing.
+ */
+ if (*rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace))
+ {
+ int rc = rtFsIsoMakerObjUnsetName(pThis, pNamespace, pObj);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * To avoid need to revert anything, make sure papChildren in the parent is
+ * large enough. If root object, make sure we haven't got a root already.
+ */
+ if (pParent)
+ {
+ AssertReturn(pParent->pDir, VERR_ISOMK_IPE_NAMESPACE_1);
+ uint32_t cChildren = pParent->pDir->cChildren;
+ if (cChildren & 31)
+ { /* likely */ }
+ else
+ {
+ AssertReturn(cChildren < RTFSISOMAKER_MAX_OBJECTS_PER_DIR, VERR_TOO_MUCH_DATA);
+ void *pvNew = RTMemRealloc(pParent->pDir->papChildren, (cChildren + 32) * sizeof(pParent->pDir->papChildren[0]));
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pParent->pDir->papChildren = (PPRTFSISOMAKERNAME)pvNew;
+ }
+ }
+ else
+ AssertReturn(pNamespace->pRoot == NULL, VERR_ISOMK_IPE_NAMESPACE_2);
+
+ /*
+ * Normalize the name for this namespace.
+ */
+ size_t cchName = 0;
+ size_t cbNameInDirRec = 0;
+ char szName[RTFSISOMAKER_MAX_NAME_BUF];
+ int rc = rtFsIsoMakerNormalizeNameForNamespace(pThis, pNamespace, pParent, pchSpec, cchSpec, fNoNormalize,
+ pObj->enmType == RTFSISOMAKEROBJTYPE_DIR,
+ szName, sizeof(szName), &cchName, &cbNameInDirRec);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cbNameInDirRec > 0);
+
+ size_t cbName = sizeof(RTFSISOMAKERNAME)
+ + cchName + 1
+ + cchSpec + 1;
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_DIR)
+ cbName = RT_ALIGN_Z(cbName, 8) + sizeof(RTFSISOMAKERNAMEDIR);
+ PRTFSISOMAKERNAME pName = (PRTFSISOMAKERNAME)RTMemAllocZ(cbName);
+ if (pName)
+ {
+ pName->pObj = pObj;
+ pName->pParent = pParent;
+ pName->cbNameInDirRec = (uint16_t)cbNameInDirRec;
+ pName->cchName = (uint16_t)cchName;
+
+ char *pszDst = &pName->szName[cchName + 1];
+ memcpy(pszDst, pchSpec, cchSpec);
+ pszDst[cchSpec] = '\0';
+ pName->pszSpecNm = pszDst;
+ pName->pszRockRidgeNm = pszDst;
+ pName->pszTransNm = pszDst;
+ pName->cchSpecNm = (uint16_t)cchSpec;
+ pName->cchRockRidgeNm = (uint16_t)cchSpec;
+ pName->cchTransNm = (uint16_t)cchSpec;
+ pName->uDepth = pParent ? pParent->uDepth + 1 : 0;
+ pName->fRockRidgeNmAlloced = false;
+ pName->fTransNmAlloced = false;
+ pName->fRockNeedER = false;
+ pName->fRockNeedRRInDirRec = false;
+ pName->fRockNeedRRInSpill = false;
+
+ pName->fMode = pObj->fMode;
+ pName->uid = pObj->uid;
+ pName->gid = pObj->gid;
+ pName->Device = 0;
+ pName->cHardlinks = 1;
+ pName->offDirRec = UINT32_MAX;
+ pName->cbDirRec = 0;
+ pName->cDirRecs = 1;
+ pName->cbDirRecTotal = 0;
+ pName->fRockEntries = 0;
+ pName->cbRockInDirRec = 0;
+ pName->offRockSpill = UINT32_MAX;
+ pName->cbRockSpill = 0;
+
+ memcpy(pName->szName, szName, cchName);
+ pName->szName[cchName] = '\0';
+
+ if (pObj->enmType != RTFSISOMAKEROBJTYPE_DIR)
+ pName->pDir = NULL;
+ else
+ {
+ size_t offDir = RT_UOFFSETOF(RTFSISOMAKERNAME, szName) + cchName + 1 + cchSpec + 1;
+ offDir = RT_ALIGN_Z(offDir, 8);
+ PRTFSISOMAKERNAMEDIR pDir = (PRTFSISOMAKERNAMEDIR)((uintptr_t)pName + offDir);
+ pDir->offDir = UINT64_MAX;
+ pDir->cbDir = 0;
+ pDir->cChildren = 0;
+ pDir->papChildren = NULL;
+ pDir->pTransTblFile = NULL;
+ pDir->pName = pName;
+ pDir->offPathTable = UINT32_MAX;
+ pDir->idPathTable = UINT16_MAX;
+ pDir->cbDirRec00 = 0;
+ pDir->cbDirRec01 = 0;
+ RTListInit(&pDir->FinalizedEntry);
+ pName->pDir = pDir;
+
+ /* Create the TRANS.TBL file object and enter it into this directory as the first entry. */
+ if (pNamespace->pszTransTbl)
+ {
+ rc = rtFsIsoMakerAddTransTblFileToNewDir(pThis, pNamespace, pName);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pName);
+ return rc;
+ }
+ }
+ }
+
+ /*
+ * Do the linking and stats. We practice insertion sorting.
+ */
+ if (pParent)
+ {
+ uint32_t idxName = rtFsIsoMakerFindInsertIndex(pNamespace, pParent, pName->szName);
+ uint32_t cChildren = pParent->pDir->cChildren;
+ if (idxName < cChildren)
+ memmove(&pParent->pDir->papChildren[idxName + 1], &pParent->pDir->papChildren[idxName],
+ (cChildren - idxName) * sizeof(pParent->pDir->papChildren[0]));
+ pParent->pDir->papChildren[idxName] = pName;
+ pParent->pDir->cChildren++;
+ }
+ else
+ pNamespace->pRoot = pName;
+ *rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace) = pName;
+ pNamespace->cNames++;
+
+ /*
+ * Done.
+ */
+ if (ppNewName)
+ *ppNewName = pName;
+ return VINF_SUCCESS;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Walks the path up to the parent, creating missing directories as needed.
+ *
+ * As usual, we walk the specified names rather than the mangled ones.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace to walk.
+ * @param pszPath The path to walk.
+ * @param ppParent Where to return the pointer to the parent
+ * namespace node.
+ * @param ppszEntry Where to return the pointer to the final name component.
+ * @param pcchEntry Where to return the length of the final name component.
+ */
+static int rtFsIsoMakerCreatePathToParent(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, const char *pszPath,
+ PPRTFSISOMAKERNAME ppParent, const char **ppszEntry, size_t *pcchEntry)
+{
+ *ppParent = NULL; /* shut up gcc */
+ *ppszEntry = NULL; /* shut up gcc */
+ *pcchEntry = 0; /* shut up gcc */
+
+ int rc;
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_ISOMK_IPE_ROOT_SLASH);
+
+ /*
+ * Deal with the special case of the root.
+ */
+ while (RTPATH_IS_SLASH(*pszPath))
+ pszPath++;
+ AssertReturn(*pszPath, VERR_ISOMK_IPE_EMPTY_PATH); /* We should not be called on a root path. */
+
+ PRTFSISOMAKERNAME pParent = pNamespace->pRoot;
+ if (!pParent)
+ {
+ PRTFSISOMAKERDIR pDir = RTListGetFirst(&pThis->ObjectHead, RTFSISOMAKERDIR, Core.Entry);
+#ifdef RT_STRICT
+ Assert(pDir);
+ Assert(pDir->Core.idxObj == 0);
+ Assert(pDir->Core.enmType == RTFSISOMAKEROBJTYPE_DIR);
+ Assert(*rtFsIsoMakerObjGetNameForNamespace(&pDir->Core, pNamespace) == NULL);
+#endif
+
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, &pDir->Core, NULL /*pParent*/, "", 0, false /*fNoNormalize*/, &pParent);
+ AssertRCReturn(rc, rc);
+ pParent = pNamespace->pRoot;
+ AssertReturn(pParent, VERR_ISOMK_IPE_NAMESPACE_4);
+ }
+
+ /*
+ * Now, do the rest of the path.
+ */
+ for (;;)
+ {
+ /*
+ * Find the end of the component and see if its the final one or not.
+ */
+ char ch;
+ size_t cchComponent = 0;
+ while ((ch = pszPath[cchComponent]) != '\0' && !RTPATH_IS_SLASH(ch))
+ cchComponent++;
+ AssertReturn(cchComponent > 0, VERR_ISOMK_IPE_EMPTY_COMPONENT);
+
+ size_t offNext = cchComponent;
+ while (RTPATH_IS_SLASH(ch))
+ ch = pszPath[++offNext];
+
+ if (ch == '\0')
+ {
+ /*
+ * Final component. Make sure it is not dot or dot-dot before returning.
+ */
+ AssertReturn( pszPath[0] != '.'
+ || cchComponent > 2
+ || ( cchComponent == 2
+ && pszPath[1] != '.'),
+ VERR_INVALID_NAME);
+
+ *ppParent = pParent;
+ *ppszEntry = pszPath;
+ *pcchEntry = cchComponent;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Deal with dot and dot-dot.
+ */
+ if (cchComponent == 1 && pszPath[0] == '.')
+ { /* nothing to do */ }
+ else if (cchComponent == 2 && pszPath[0] == '.' && pszPath[1] == '.')
+ {
+ if (pParent->pParent)
+ pParent = pParent->pParent;
+ }
+ /*
+ * Look it up.
+ */
+ else
+ {
+ PRTFSISOMAKERNAME pChild = rtFsIsoMakerFindEntryInDirBySpec(pParent, pszPath, cchComponent);
+ if (pChild)
+ {
+ if (pChild->pDir)
+ pParent = pChild;
+ else
+ return VERR_NOT_A_DIRECTORY;
+ }
+ else
+ {
+ /* Try see if we've got a directory with the same spec name in a different namespace.
+ (We don't want to waste heap by creating a directory instance per namespace.) */
+ PRTFSISOMAKERDIR pChildObj = rtFsIsoMakerFindSubdirBySpec((PRTFSISOMAKERDIR)pParent->pObj,
+ pszPath, cchComponent, pNamespace->fNamespace);
+ if (pChildObj)
+ {
+ PPRTFSISOMAKERNAME ppChildName = rtFsIsoMakerObjGetNameForNamespace(&pChildObj->Core, pNamespace);
+ if (!*ppChildName)
+ {
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, &pChildObj->Core, pParent, pszPath, cchComponent,
+ false /*fNoNormalize*/, &pChild);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(pChild != NULL, VERR_ISOMK_IPE_NAMESPACE_5);
+ }
+ }
+ /* If we didn't have luck in other namespaces, create a new directory. */
+ if (!pChild)
+ {
+ rc = rtFsIsoMakerAddUnnamedDirWorker(pThis, NULL /*pObjInfo*/, &pChildObj);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, &pChildObj->Core, pParent, pszPath, cchComponent,
+ false /*fNoNormalize*/, &pChild);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(pChild != NULL, VERR_ISOMK_IPE_NAMESPACE_5);
+ }
+ pParent = pChild;
+ }
+ }
+
+ /*
+ * Skip ahead in the path.
+ */
+ pszPath += offNext;
+ }
+}
+
+
+/**
+ * Worker for RTFsIsoMakerObjSetPath that operates on a single namespace.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace to name it in.
+ * @param pObj The filesystem object to name.
+ * @param pszPath The path to the entry in the namespace.
+ */
+static int rtFsIsoMakerObjSetPathInOne(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKEROBJ pObj, const char *pszPath)
+{
+ AssertReturn(*rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace) == NULL, VERR_WRONG_ORDER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_ISOMK_IPE_ROOT_SLASH);
+
+ /*
+ * Figure out where the parent is.
+ * This will create missing parent name space entries and directory nodes.
+ */
+ PRTFSISOMAKERNAME pParent;
+ const char *pszEntry;
+ size_t cchEntry;
+ int rc;
+ if (pszPath[1] != '\0')
+ rc = rtFsIsoMakerCreatePathToParent(pThis, pNamespace, pszPath, &pParent, &pszEntry, &cchEntry);
+ else
+ {
+ /*
+ * Special case for the root directory.
+ */
+ Assert(pObj->enmType == RTFSISOMAKEROBJTYPE_DIR);
+ AssertReturn(pNamespace->pRoot == NULL, VERR_WRONG_ORDER);
+ pszEntry = "/";
+ cchEntry = 0;
+ pParent = NULL;
+ rc = VINF_SUCCESS;
+ }
+
+ /*
+ * Do the job on the final path component.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ AssertReturn(!RTPATH_IS_SLASH(pszEntry[cchEntry]) || pObj->enmType == RTFSISOMAKEROBJTYPE_DIR,
+ VERR_NOT_A_DIRECTORY);
+ rc = rtFsIsoMakerObjSetName(pThis, pNamespace, pObj, pParent, pszEntry, cchEntry, false /*fNoNormalize*/, NULL);
+ }
+ return rc;
+}
+
+
+/**
+ * Removes an object from the given namespace.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pObj The object to name.
+ */
+static int rtFsIsoMakerObjUnsetName(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKEROBJ pObj)
+{
+ LogFlow(("rtFsIsoMakerObjUnsetName: idxObj=#%#x\n", pObj->idxObj));
+
+ /*
+ * First check if there is anything to do here at all.
+ */
+ PPRTFSISOMAKERNAME ppName = rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace);
+ PRTFSISOMAKERNAME pName = *ppName;
+ if (!pName)
+ return VINF_SUCCESS;
+
+ /*
+ * We don't support this on the root.
+ */
+ AssertReturn(pName->pParent, VERR_ACCESS_DENIED);
+
+ /*
+ * If this is a directory, we're in for some real fun here as we need to
+ * unset the names of all the children too.
+ */
+ PRTFSISOMAKERNAMEDIR pDir = pName->pDir;
+ if (pDir)
+ {
+ uint32_t iChild = pDir->cChildren;
+ while (iChild-- > 0)
+ {
+ int rc = rtFsIsoMakerObjUnsetName(pThis, pNamespace, pDir->papChildren[iChild]->pObj);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ AssertReturn(pDir->cChildren == 0, VERR_DIR_NOT_EMPTY);
+ }
+
+ /*
+ * Unlink the pName from the parent.
+ */
+ pDir = pName->pParent->pDir;
+ uint32_t iChild = pDir->cChildren;
+ while (iChild-- > 0)
+ if (pDir->papChildren[iChild] == pName)
+ {
+ uint32_t cToMove = pDir->cChildren - iChild - 1;
+ if (cToMove > 0)
+ memmove(&pDir->papChildren[iChild], &pDir->papChildren[iChild + 1], cToMove * sizeof(pDir->papChildren[0]));
+ pDir->cChildren--;
+ pNamespace->cNames--;
+
+ /*
+ * NULL the name member in the object and free the structure.
+ */
+ *ppName = NULL;
+ RTMemFree(pName);
+
+ return VINF_SUCCESS;
+ }
+
+ /* Not found. This can't happen. */
+ AssertFailed();
+ return VERR_ISOMK_IPE_NAMESPACE_6;
+}
+
+
+/**
+ * Gets currently populated namespaces.
+ *
+ * @returns Set of namespaces (RTFSISOMAKER_NAMESPACE_XXX), UINT32_MAX on error.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(uint32_t) RTFsIsoMakerGetPopulatedNamespaces(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT32_MAX);
+
+ uint32_t fRet = 0;
+ if (pThis->PrimaryIso.cNames > 0)
+ fRet |= RTFSISOMAKER_NAMESPACE_ISO_9660;
+ if (pThis->Joliet.cNames > 0)
+ fRet |= RTFSISOMAKER_NAMESPACE_JOLIET;
+ if (pThis->Udf.cNames > 0)
+ fRet |= RTFSISOMAKER_NAMESPACE_UDF;
+ if (pThis->Hfs.cNames > 0)
+ fRet |= RTFSISOMAKER_NAMESPACE_HFS;
+
+ return fRet;
+}
+
+
+
+
+/*
+ *
+ * Object level config
+ * Object level config
+ * Object level config
+ *
+ */
+
+
+/**
+ * Translates an object index number to an object pointer, slow path.
+ *
+ * @returns Pointer to object, NULL if not found.
+ * @param pThis The ISO maker instance.
+ * @param idxObj The object index too resolve.
+ */
+DECL_NO_INLINE(static, PRTFSISOMAKEROBJ) rtFsIsoMakerIndexToObjSlow(PRTFSISOMAKERINT pThis, uint32_t idxObj)
+{
+ PRTFSISOMAKEROBJ pObj;
+ RTListForEachReverse(&pThis->ObjectHead, pObj, RTFSISOMAKEROBJ, Entry)
+ {
+ if (pObj->idxObj == idxObj)
+ return pObj;
+ }
+ return NULL;
+}
+
+
+/**
+ * Translates an object index number to an object pointer.
+ *
+ * @returns Pointer to object, NULL if not found.
+ * @param pThis The ISO maker instance.
+ * @param idxObj The object index too resolve.
+ */
+DECLINLINE(PRTFSISOMAKEROBJ) rtFsIsoMakerIndexToObj(PRTFSISOMAKERINT pThis, uint32_t idxObj)
+{
+ PRTFSISOMAKEROBJ pObj = RTListGetLast(&pThis->ObjectHead, RTFSISOMAKEROBJ, Entry);
+ if (!pObj || RT_LIKELY(pObj->idxObj == idxObj))
+ return pObj;
+ return rtFsIsoMakerIndexToObjSlow(pThis, idxObj);
+}
+
+
+/**
+ * Resolves a path into a object ID.
+ *
+ * This will be doing the looking up using the specified object names rather
+ * than the version adjusted and mangled according to the namespace setup.
+ *
+ * @returns The object ID corresponding to @a pszPath, or UINT32_MAX if not
+ * found or invalid parameters.
+ * @param hIsoMaker The ISO maker instance.
+ * @param fNamespaces The namespace to resolve @a pszPath in. It's
+ * possible to specify multiple namespaces here, of
+ * course, but that's inefficient.
+ * @param pszPath The path to the object.
+ */
+RTDECL(uint32_t) RTFsIsoMakerGetObjIdxForPath(RTFSISOMAKER hIsoMaker, uint32_t fNamespaces, const char *pszPath)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET_EX(pThis, UINT32_MAX);
+
+ /*
+ * Do the searching.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->pRoot)
+ {
+ PRTFSISOMAKERNAME pName;
+ int rc = rtFsIsoMakerWalkPathBySpec(pNamespace, pszPath, &pName);
+ if (RT_SUCCESS(rc))
+ return pName->pObj->idxObj;
+ }
+ }
+
+ return UINT32_MAX;
+}
+
+
+/**
+ * Removes the specified object from the image.
+ *
+ * This is a worker for RTFsIsoMakerObjRemove and
+ * rtFsIsoMakerFinalizeRemoveOrphans.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker instance.
+ * @param pObj The object to remove from the image.
+ */
+static int rtFsIsoMakerObjRemoveWorker(PRTFSISOMAKERINT pThis, PRTFSISOMAKEROBJ pObj)
+{
+ /*
+ * Don't allow removing trans.tbl files and the boot catalog.
+ */
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ if (pFile->enmSrcType == RTFSISOMAKERSRCTYPE_TRANS_TBL)
+ return VWRN_DANGLING_OBJECTS; /* HACK ALERT! AssertReturn(pFile->enmSrcType != RTFSISOMAKERSRCTYPE_TRANS_TBL, VERR_ACCESS_DENIED); */
+ AssertReturn(pFile != pThis->pBootCatFile, VERR_ACCESS_DENIED);
+ }
+
+ /*
+ * Remove the object from all name spaces.
+ */
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ int rc2 = rtFsIsoMakerObjUnsetName(pThis, pNamespace, pObj);
+ if (RT_SUCCESS(rc2) || RT_FAILURE(rc))
+ continue;
+ rc = rc2;
+ }
+
+ /*
+ * If that succeeded, remove the object itself.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pObj->Entry);
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ uint64_t cbData = ((PRTFSISOMAKERFILE)pObj)->cbData;
+ pThis->cbData -= RT_ALIGN_64(cbData, RTFSISOMAKER_SECTOR_SIZE);
+ }
+ pThis->cObjects--;
+ rtFsIsoMakerObjDestroy(pObj);
+ }
+ return rc;
+}
+
+
+/**
+ * Removes the specified object from the image.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker instance.
+ * @param idxObj The index of the object to remove.
+ */
+RTDECL(int) RTFsIsoMakerObjRemove(RTFSISOMAKER hIsoMaker, uint32_t idxObj)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn( pObj->enmType != RTFSISOMAKEROBJTYPE_FILE
+ || ((PRTFSISOMAKERFILE)pObj)->enmSrcType != RTFSISOMAKERSRCTYPE_RR_SPILL, VERR_ACCESS_DENIED);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Call worker.
+ */
+ return rtFsIsoMakerObjRemoveWorker(pThis, pObj);
+}
+
+
+/**
+ * Sets the path (name) of an object in the selected namespaces.
+ *
+ * The name will be transformed as necessary.
+ *
+ * The initial implementation does not allow this function to be called more
+ * than once on an object.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index of to name.
+ * @param fNamespaces The namespaces to apply the path to
+ * (RTFSISOMAKER_NAMESPACE_XXX).
+ * @param pszPath The path.
+ */
+RTDECL(int) RTFsIsoMakerObjSetPath(RTFSISOMAKER hIsoMaker, uint32_t idxObj, uint32_t fNamespaces, const char *pszPath)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Execute requested actions.
+ */
+ uint32_t cAdded = 0;
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ int rc2 = rtFsIsoMakerObjSetPathInOne(pThis, pNamespace, pObj, pszPath);
+ if (RT_SUCCESS(rc2))
+ cAdded++;
+ else if (RT_SUCCESS(rc) || rc == VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE)
+ rc = rc2;
+ }
+ }
+ return rc != VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE || cAdded == 0 ? rc : VINF_ISOMK_SYMLINK_REQ_ROCK_RIDGE;
+}
+
+
+/**
+ * Sets the name of an object in the selected namespaces, placing it under the
+ * given directory.
+ *
+ * The name will be transformed as necessary.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index of to name.
+ * @param idxParentObj The parent directory object.
+ * @param fNamespaces The namespaces to apply the path to
+ * (RTFSISOMAKER_NAMESPACE_XXX).
+ * @param pszName The name.
+ * @param fNoNormalize Don't normalize the name (imported or such).
+ */
+RTDECL(int) RTFsIsoMakerObjSetNameAndParent(RTFSISOMAKER hIsoMaker, uint32_t idxObj, uint32_t idxParentObj,
+ uint32_t fNamespaces, const char *pszName, bool fNoNormalize)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ size_t cchName = strlen(pszName);
+ AssertReturn(cchName > 0, VERR_INVALID_NAME);
+ AssertReturn(memchr(pszName, '/', cchName) == NULL, VERR_INVALID_NAME);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ PRTFSISOMAKEROBJ pParentObj = rtFsIsoMakerIndexToObj(pThis, idxParentObj);
+ AssertReturn(pParentObj, VERR_OUT_OF_RANGE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Execute requested actions.
+ */
+ uint32_t cAdded = 0;
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ PRTFSISOMAKERNAME pParentName = *rtFsIsoMakerObjGetNameForNamespace(pParentObj, pNamespace);
+ if (pParentName)
+ {
+ int rc2 = rtFsIsoMakerObjSetName(pThis, pNamespace, pObj, pParentName, pszName, cchName,
+ fNoNormalize, NULL /*ppNewName*/);
+ if (RT_SUCCESS(rc2))
+ cAdded++;
+ else if (RT_SUCCESS(rc) || rc == VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE)
+ rc = rc2;
+ }
+ }
+ }
+ return rc != VERR_ISOMK_SYMLINK_REQ_ROCK_RIDGE || cAdded == 0 ? rc : VINF_ISOMK_SYMLINK_REQ_ROCK_RIDGE;
+}
+
+
+/**
+ * Changes the rock ridge name for the object in the selected namespaces.
+ *
+ * The object must already be enetered into the namespaces by
+ * RTFsIsoMakerObjSetNameAndParent, RTFsIsoMakerObjSetPath or similar.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index of to name.
+ * @param fNamespaces The namespaces to apply the path to
+ * (RTFSISOMAKER_NAMESPACE_XXX).
+ * @param pszRockName The rock ridge name. Passing NULL will restore
+ * it back to the specified name, while an empty
+ * string will restore it to the namespace name.
+ */
+RTDECL(int) RTFsIsoMakerObjSetRockName(RTFSISOMAKER hIsoMaker, uint32_t idxObj, uint32_t fNamespaces, const char *pszRockName)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ size_t cchRockName;
+ if (pszRockName)
+ {
+ AssertPtrReturn(pszRockName, VERR_INVALID_POINTER);
+ cchRockName = strlen(pszRockName);
+ AssertReturn(cchRockName < _1K, VERR_FILENAME_TOO_LONG);
+ AssertReturn(memchr(pszRockName, '/', cchRockName) == NULL, VERR_INVALID_NAME);
+ }
+ else
+ cchRockName = 0;
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Execute requested actions.
+ */
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if ( pNamespace->uLevel > 0
+ && pNamespace->uRockRidgeLevel > 0)
+ {
+ PRTFSISOMAKERNAME pName = *rtFsIsoMakerObjGetNameForNamespace(pObj, pNamespace);
+ if (pName)
+ {
+ /* Free the old rock ridge name. */
+ if (pName->fRockRidgeNmAlloced)
+ {
+ RTMemFree(pName->pszRockRidgeNm);
+ pName->pszRockRidgeNm = NULL;
+ pName->fRockRidgeNmAlloced = false;
+ }
+
+ /* Set new rock ridge name. */
+ if (cchRockName > 0)
+ {
+ pName->pszRockRidgeNm = (char *)RTMemDup(pszRockName, cchRockName + 1);
+ if (!pName->pszRockRidgeNm)
+ {
+ pName->pszRockRidgeNm = (char *)pName->pszSpecNm;
+ pName->cchRockRidgeNm = pName->cchSpecNm;
+ return VERR_NO_MEMORY;
+ }
+ pName->cchRockRidgeNm = (uint16_t)cchRockName;
+ pName->fRockRidgeNmAlloced = true;
+ }
+ else if (pszRockName == NULL)
+ {
+ pName->pszRockRidgeNm = (char *)pName->pszSpecNm;
+ pName->cchRockRidgeNm = pName->cchSpecNm;
+ }
+ else
+ {
+ pName->pszRockRidgeNm = pName->szName;
+ pName->cchRockRidgeNm = pName->cchName;
+ }
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Enables or disable syslinux boot info table patching of a file.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index.
+ * @param fEnable Whether to enable or disable patching.
+ */
+RTDECL(int) RTFsIsoMakerObjEnableBootInfoTablePatching(RTFSISOMAKER hIsoMaker, uint32_t idxObj, bool fEnable)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn(pObj->enmType == RTFSISOMAKEROBJTYPE_FILE, VERR_WRONG_TYPE);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ AssertReturn( pFile->enmSrcType == RTFSISOMAKERSRCTYPE_PATH
+ || pFile->enmSrcType == RTFSISOMAKERSRCTYPE_VFS_FILE
+ || pFile->enmSrcType == RTFSISOMAKERSRCTYPE_COMMON,
+ VERR_WRONG_TYPE);
+
+ /*
+ * Do the job.
+ */
+ if (fEnable)
+ {
+ if (!pFile->pBootInfoTable)
+ {
+ pFile->pBootInfoTable = (PISO9660SYSLINUXINFOTABLE)RTMemAllocZ(sizeof(*pFile->pBootInfoTable));
+ AssertReturn(pFile->pBootInfoTable, VERR_NO_MEMORY);
+ }
+ }
+ else if (pFile->pBootInfoTable)
+ {
+ RTMemFree(pFile->pBootInfoTable);
+ pFile->pBootInfoTable = NULL;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the data size of an object.
+ *
+ * Currently only supported on file objects.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index.
+ * @param pcbData Where to return the size.
+ */
+RTDECL(int) RTFsIsoMakerObjQueryDataSize(RTFSISOMAKER hIsoMaker, uint32_t idxObj, uint64_t *pcbData)
+{
+ /*
+ * Validate and translate input.
+ */
+ AssertPtrReturn(pcbData, VERR_INVALID_POINTER);
+ *pcbData = UINT64_MAX;
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+
+ /*
+ * Do the job.
+ */
+ if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ if ( pFile->enmSrcType != RTFSISOMAKERSRCTYPE_TRANS_TBL
+ && pFile->enmSrcType != RTFSISOMAKERSRCTYPE_RR_SPILL)
+ {
+ *pcbData = ((PRTFSISOMAKERFILE)pObj)->cbData;
+ return VINF_SUCCESS;
+ }
+ }
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * Initalizes the common part of a file system object and links it into global
+ * chain.
+ *
+ * @returns IPRT status code
+ * @param pThis The ISO maker instance.
+ * @param pObj The common object.
+ * @param enmType The object type.
+ * @param pObjInfo The object information (typically source).
+ * Optional.
+ */
+static int rtFsIsoMakerInitCommonObj(PRTFSISOMAKERINT pThis, PRTFSISOMAKEROBJ pObj,
+ RTFSISOMAKEROBJTYPE enmType, PCRTFSOBJINFO pObjInfo)
+{
+ Assert(!pThis->fFinalized);
+ AssertReturn(pThis->cObjects < RTFSISOMAKER_MAX_OBJECTS, VERR_OUT_OF_RANGE);
+
+ pObj->enmType = enmType;
+ pObj->pPrimaryName = NULL;
+ pObj->pJolietName = NULL;
+ pObj->pUdfName = NULL;
+ pObj->pHfsName = NULL;
+ pObj->idxObj = pThis->cObjects++;
+ pObj->cNotOrphan = 0;
+ if (pObjInfo)
+ {
+ pObj->BirthTime = pObjInfo->BirthTime;
+ pObj->ChangeTime = pObjInfo->ChangeTime;
+ pObj->ModificationTime = pObjInfo->ModificationTime;
+ pObj->AccessedTime = pObjInfo->AccessTime;
+ if (!pThis->fStrictAttributeStyle)
+ {
+ if (enmType == RTFSISOMAKEROBJTYPE_DIR)
+ pObj->fMode = (pObjInfo->Attr.fMode & ~07222) | 0555;
+ else
+ {
+ pObj->fMode = (pObjInfo->Attr.fMode & ~00222) | 0444;
+ if (pObj->fMode & 0111)
+ pObj->fMode |= 0111;
+ }
+ pObj->uid = pThis->uidDefault;
+ pObj->gid = pThis->gidDefault;
+ }
+ else
+ {
+ pObj->fMode = pObjInfo->Attr.fMode;
+ pObj->uid = pObjInfo->Attr.u.Unix.uid != NIL_RTUID ? pObjInfo->Attr.u.Unix.uid : pThis->uidDefault;
+ pObj->gid = pObjInfo->Attr.u.Unix.gid != NIL_RTGID ? pObjInfo->Attr.u.Unix.gid : pThis->gidDefault;
+ }
+ if (enmType == RTFSISOMAKEROBJTYPE_DIR ? pThis->fForcedDirModeActive : pThis->fForcedFileModeActive)
+ pObj->fMode = (pObj->fMode & ~RTFS_UNIX_ALL_PERMS)
+ | (enmType == RTFSISOMAKEROBJTYPE_DIR ? pThis->fForcedDirMode : pThis->fForcedFileMode);
+ }
+ else
+ {
+ pObj->BirthTime = pThis->ImageCreationTime;
+ pObj->ChangeTime = pThis->ImageCreationTime;
+ pObj->ModificationTime = pThis->ImageCreationTime;
+ pObj->AccessedTime = pThis->ImageCreationTime;
+ pObj->fMode = enmType == RTFSISOMAKEROBJTYPE_DIR ? pThis->fDefaultDirMode : pThis->fDefaultFileMode;
+ pObj->uid = pThis->uidDefault;
+ pObj->gid = pThis->gidDefault;
+ }
+
+ RTListAppend(&pThis->ObjectHead, &pObj->Entry);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Internal function for adding an unnamed directory.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO make instance.
+ * @param pObjInfo Pointer to object attributes, must be set to
+ * UNIX. The size and hardlink counts are ignored.
+ * Optional.
+ * @param ppDir Where to return the directory.
+ */
+static int rtFsIsoMakerAddUnnamedDirWorker(PRTFSISOMAKERINT pThis, PCRTFSOBJINFO pObjInfo, PRTFSISOMAKERDIR *ppDir)
+{
+ PRTFSISOMAKERDIR pDir = (PRTFSISOMAKERDIR)RTMemAllocZ(sizeof(*pDir));
+ AssertReturn(pDir, VERR_NO_MEMORY);
+ int rc = rtFsIsoMakerInitCommonObj(pThis, &pDir->Core, RTFSISOMAKEROBJTYPE_DIR, pObjInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *ppDir = pDir;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pDir);
+ return rc;
+
+}
+
+
+/**
+ * Adds an unnamed directory to the image.
+ *
+ * The directory must explictly be entered into the desired namespaces.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pObjInfo Pointer to object attributes, must be set to
+ * UNIX. The size and hardlink counts are ignored.
+ * Optional.
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddDir, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedDir(RTFSISOMAKER hIsoMaker, PCRTFSOBJINFO pObjInfo, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ if (pObjInfo)
+ {
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX, VERR_INVALID_PARAMETER);
+ AssertReturn(RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode), VERR_INVALID_FLAGS);
+ }
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ PRTFSISOMAKERDIR pDir;
+ int rc = rtFsIsoMakerAddUnnamedDirWorker(pThis, pObjInfo, &pDir);
+ *pidxObj = RT_SUCCESS(rc) ? pDir->Core.idxObj : UINT32_MAX;
+ return rc;
+}
+
+
+/**
+ * Adds a directory to the image in all namespaces and default attributes.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszDir The path (UTF-8) to the directory in the ISO.
+ *
+ * @param pidxObj Where to return the configuration index of the
+ * directory. Optional.
+ * @sa RTFsIsoMakerAddUnnamedDir, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddDir(RTFSISOMAKER hIsoMaker, const char *pszDir, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszDir, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszDir), VERR_INVALID_NAME);
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedDir(hIsoMaker, NULL /*pObjInfo*/, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerObjSetPath(hIsoMaker, idxObj, RTFSISOMAKER_NAMESPACE_ALL, pszDir);
+ if (RT_SUCCESS(rc))
+ {
+ if (pidxObj)
+ *pidxObj = idxObj;
+ }
+ else
+ RTFsIsoMakerObjRemove(hIsoMaker, idxObj);
+ }
+ return rc;
+}
+
+
+/**
+ * Internal function for adding an unnamed file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO make instance.
+ * @param pObjInfo Object information. Optional.
+ * @param cbExtra Extra space for additional data (e.g. source
+ * path string copy).
+ * @param ppFile Where to return the file.
+ */
+static int rtFsIsoMakerAddUnnamedFileWorker(PRTFSISOMAKERINT pThis, PCRTFSOBJINFO pObjInfo, size_t cbExtra,
+ PRTFSISOMAKERFILE *ppFile)
+{
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)RTMemAllocZ(sizeof(*pFile) + cbExtra);
+ AssertReturn(pFile, VERR_NO_MEMORY);
+ int rc = rtFsIsoMakerInitCommonObj(pThis, &pFile->Core, RTFSISOMAKEROBJTYPE_FILE, pObjInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->cbData = pObjInfo ? pObjInfo->cbObject : 0;
+ pThis->cbData += RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE);
+ pFile->offData = UINT64_MAX;
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_INVALID;
+ pFile->u.pszSrcPath = NULL;
+ pFile->pBootInfoTable = NULL;
+ RTListInit(&pFile->FinalizedEntry);
+
+ *ppFile = pFile;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pFile);
+ return rc;
+
+}
+
+
+/**
+ * Adds an unnamed file to the image that's backed by a host file.
+ *
+ * The file must explictly be entered into the desired namespaces.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszSrcFile The source file path. VFS chain spec allowed.
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddFile, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedFileWithSrcPath(RTFSISOMAKER hIsoMaker, const char *pszSrcFile, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ *pidxObj = UINT32_MAX;
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Check that the source file exists and is a file.
+ */
+ uint32_t offError = 0;
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsChainQueryInfo(pszSrcFile, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK, &offError, NULL);
+ AssertMsgRCReturn(rc, ("%s -> %Rrc offError=%u\n", pszSrcFile, rc, offError), rc);
+ AssertMsgReturn(RTFS_IS_FILE(ObjInfo.Attr.fMode), ("%#x - %s\n", ObjInfo.Attr.fMode, pszSrcFile), VERR_NOT_A_FILE);
+
+ /*
+ * Create a file object for it.
+ */
+ size_t const cbSrcFile = strlen(pszSrcFile) + 1;
+ PRTFSISOMAKERFILE pFile;
+ rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, &ObjInfo, cbSrcFile, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_PATH;
+ pFile->u.pszSrcPath = (char *)memcpy(pFile + 1, pszSrcFile, cbSrcFile);
+
+ *pidxObj = pFile->Core.idxObj;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds an unnamed file to the image that's backed by a VFS file.
+ *
+ * The file must explictly be entered into the desired namespaces.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param hVfsFileSrc The source file handle.
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddUnnamedFileWithSrcPath, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedFileWithVfsFile(RTFSISOMAKER hIsoMaker, RTVFSFILE hVfsFileSrc, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ *pidxObj = UINT32_MAX;
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Get the VFS file info. This implicitly validates the handle.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsFileQueryInfo(hVfsFileSrc, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ AssertMsgRCReturn(rc, ("RTVfsFileQueryInfo(%p) -> %Rrc\n", hVfsFileSrc, rc), rc);
+
+ /*
+ * Retain a reference to the file.
+ */
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileSrc);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a file object for it.
+ */
+ PRTFSISOMAKERFILE pFile;
+ rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, &ObjInfo, 0, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_VFS_FILE;
+ pFile->u.hVfsFile = hVfsFileSrc;
+
+ *pidxObj = pFile->Core.idxObj;
+ }
+ else
+ RTVfsFileRelease(hVfsFileSrc);
+ return rc;
+}
+
+
+/**
+ * Adds an unnamed file to the image that's backed by a portion of a common
+ * source file.
+ *
+ * The file must explictly be entered into the desired namespaces.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxCommonSrc The common source file index.
+ * @param offData The offset of the data in the source file.
+ * @param cbData The file size.
+ * @param pObjInfo Pointer to file info. Optional.
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddUnnamedFileWithSrcPath, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedFileWithCommonSrc(RTFSISOMAKER hIsoMaker, uint32_t idxCommonSrc,
+ uint64_t offData, uint64_t cbData, PCRTFSOBJINFO pObjInfo, uint32_t *pidxObj)
+{
+ /*
+ * Validate and fake input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ *pidxObj = UINT32_MAX;
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+ AssertReturn(idxCommonSrc < pThis->cCommonSources, VERR_INVALID_PARAMETER);
+ AssertReturn(offData < (uint64_t)RTFOFF_MAX, VERR_OUT_OF_RANGE);
+ AssertReturn(cbData < (uint64_t)RTFOFF_MAX, VERR_OUT_OF_RANGE);
+ AssertReturn(offData + cbData < (uint64_t)RTFOFF_MAX, VERR_OUT_OF_RANGE);
+ RTFSOBJINFO ObjInfo;
+ if (!pObjInfo)
+ {
+ ObjInfo.cbObject = cbData;
+ ObjInfo.cbAllocated = cbData;
+ ObjInfo.BirthTime = pThis->ImageCreationTime;
+ ObjInfo.ChangeTime = pThis->ImageCreationTime;
+ ObjInfo.ModificationTime = pThis->ImageCreationTime;
+ ObjInfo.AccessTime = pThis->ImageCreationTime;
+ ObjInfo.Attr.fMode = pThis->fDefaultFileMode;
+ ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ ObjInfo.Attr.u.Unix.uid = NIL_RTUID;
+ ObjInfo.Attr.u.Unix.gid = NIL_RTGID;
+ ObjInfo.Attr.u.Unix.cHardlinks = 1;
+ ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ ObjInfo.Attr.u.Unix.INodeId = 0;
+ ObjInfo.Attr.u.Unix.fFlags = 0;
+ ObjInfo.Attr.u.Unix.GenerationId = 0;
+ ObjInfo.Attr.u.Unix.Device = 0;
+ pObjInfo = &ObjInfo;
+ }
+ else
+ {
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX, VERR_WRONG_TYPE);
+ AssertReturn((uint64_t)pObjInfo->cbObject == cbData, VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Create a file object for it.
+ */
+ PRTFSISOMAKERFILE pFile;
+ int rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, pObjInfo, 0, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_COMMON;
+ pFile->u.Common.idxSrc = idxCommonSrc;
+ pFile->u.Common.offData = offData;
+
+ *pidxObj = pFile->Core.idxObj;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a common source file.
+ *
+ * Using RTFsIsoMakerAddUnnamedFileWithCommonSrc a sections common source file
+ * can be referenced to make up other files. The typical use case is when
+ * importing data from an existing ISO.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param hVfsFile VFS handle of the common source. (A reference
+ * is added, none consumed.)
+ * @param pidxCommonSrc Where to return the assigned common source
+ * index. This is used to reference the file.
+ * @sa RTFsIsoMakerAddUnnamedFileWithCommonSrc
+ */
+RTDECL(int) RTFsIsoMakerAddCommonSourceFile(RTFSISOMAKER hIsoMaker, RTVFSFILE hVfsFile, uint32_t *pidxCommonSrc)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxCommonSrc, VERR_INVALID_POINTER);
+ *pidxCommonSrc = UINT32_MAX;
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Resize the common source array if necessary.
+ */
+ if ((pThis->cCommonSources & 15) == 0)
+ {
+ void *pvNew = RTMemRealloc(pThis->paCommonSources, (pThis->cCommonSources + 16) * sizeof(pThis->paCommonSources[0]));
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pThis->paCommonSources = (PRTVFSFILE)pvNew;
+ }
+
+ /*
+ * Retain a reference to the source file, thereby validating the handle.
+ * Then add it to the array.
+ */
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ uint32_t idx = pThis->cCommonSources++;
+ pThis->paCommonSources[idx] = hVfsFile;
+
+ *pidxCommonSrc = idx;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a file that's backed by a host file to the image in all namespaces and
+ * with attributes taken from the source file.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszFile The path to the file in the image.
+ * @param pszSrcFile The source file path. VFS chain spec allowed.
+ * @param pidxObj Where to return the configuration index of the file.
+ * Optional
+ * @sa RTFsIsoMakerAddFileWithVfsFile,
+ * RTFsIsoMakerAddUnnamedFileWithSrcPath
+ */
+RTDECL(int) RTFsIsoMakerAddFileWithSrcPath(RTFSISOMAKER hIsoMaker, const char *pszFile, const char *pszSrcFile, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszFile), VERR_INVALID_NAME);
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedFileWithSrcPath(hIsoMaker, pszSrcFile, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerObjSetPath(hIsoMaker, idxObj, RTFSISOMAKER_NAMESPACE_ALL, pszFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (pidxObj)
+ *pidxObj = idxObj;
+ }
+ else
+ RTFsIsoMakerObjRemove(hIsoMaker, idxObj);
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a file that's backed by a VFS file to the image in all namespaces and
+ * with attributes taken from the source file.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszFile The path to the file in the image.
+ * @param hVfsFileSrc The source file handle.
+ * @param pidxObj Where to return the configuration index of the file.
+ * Optional.
+ * @sa RTFsIsoMakerAddUnnamedFileWithVfsFile,
+ * RTFsIsoMakerAddFileWithSrcPath
+ */
+RTDECL(int) RTFsIsoMakerAddFileWithVfsFile(RTFSISOMAKER hIsoMaker, const char *pszFile, RTVFSFILE hVfsFileSrc, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszFile, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszFile), VERR_INVALID_NAME);
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedFileWithVfsFile(hIsoMaker, hVfsFileSrc, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerObjSetPath(hIsoMaker, idxObj, RTFSISOMAKER_NAMESPACE_ALL, pszFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (pidxObj)
+ *pidxObj = idxObj;
+ }
+ else
+ RTFsIsoMakerObjRemove(hIsoMaker, idxObj);
+ }
+ return rc;
+}
+
+
+/**
+ * Adds an unnamed symbolic link to the image.
+ *
+ * The symlink must explictly be entered into the desired namespaces. Please
+ * note that it is not possible to enter a symbolic link into an ISO 9660
+ * namespace where rock ridge extensions are disabled, since symbolic links
+ * depend on rock ridge. For HFS and UDF there is no such requirement.
+ *
+ * Will fail if no namespace is configured that supports symlinks.
+ *
+ * @returns IPRT status code
+ * @retval VERR_ISOMK_SYMLINK_SUPPORT_DISABLED if not supported.
+ * @param hIsoMaker The ISO maker handle.
+ * @param pObjInfo Pointer to object attributes, must be set to
+ * UNIX. The size and hardlink counts are ignored.
+ * Optional.
+ * @param pszTarget The symbolic link target (UTF-8).
+ * @param pidxObj Where to return the configuration index of the
+ * directory.
+ * @sa RTFsIsoMakerAddSymlink, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddUnnamedSymlink(RTFSISOMAKER hIsoMaker, PCRTFSOBJINFO pObjInfo, const char *pszTarget, uint32_t *pidxObj)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ if (pObjInfo)
+ {
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX, VERR_INVALID_PARAMETER);
+ AssertReturn(RTFS_IS_SYMLINK(pObjInfo->Attr.fMode), VERR_INVALID_FLAGS);
+ }
+ AssertPtrReturn(pszTarget, VERR_INVALID_POINTER);
+ size_t cchTarget = strlen(pszTarget);
+ AssertReturn(cchTarget > 0, VERR_INVALID_NAME);
+ AssertReturn(cchTarget < RTFSISOMAKER_MAX_SYMLINK_TARGET_LEN, VERR_FILENAME_TOO_LONG);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Check that symlinks are supported by some namespace.
+ */
+ AssertReturn( (pThis->PrimaryIso.uLevel > 0 && pThis->PrimaryIso.uRockRidgeLevel > 0)
+ || (pThis->Joliet.uLevel > 0 && pThis->Joliet.uRockRidgeLevel > 0)
+ || pThis->Udf.uLevel > 0
+ || pThis->Hfs.uLevel > 0,
+ VERR_ISOMK_SYMLINK_SUPPORT_DISABLED);
+
+ /*
+ * Calculate the size of the SL entries.
+ */
+ uint8_t abTmp[_2K + RTFSISOMAKER_MAX_SYMLINK_TARGET_LEN * 3];
+ ssize_t cbSlRockRidge = rtFsIsoMakerOutFile_RockRidgeGenSL(pszTarget, abTmp, sizeof(abTmp));
+ AssertReturn(cbSlRockRidge > 0, (int)cbSlRockRidge);
+
+ /*
+ * Do the adding.
+ */
+ PRTFSISOMAKERSYMLINK pSymlink = (PRTFSISOMAKERSYMLINK)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFSISOMAKERSYMLINK, szTarget[cchTarget + 1]));
+ AssertReturn(pSymlink, VERR_NO_MEMORY);
+ int rc = rtFsIsoMakerInitCommonObj(pThis, &pSymlink->Core, RTFSISOMAKEROBJTYPE_SYMLINK, pObjInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pSymlink->cchTarget = (uint16_t)cchTarget;
+ pSymlink->cbSlRockRidge = (uint16_t)cbSlRockRidge;
+ memcpy(pSymlink->szTarget, pszTarget, cchTarget);
+ pSymlink->szTarget[cchTarget] = '\0';
+
+ *pidxObj = pSymlink->Core.idxObj;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pSymlink);
+ return rc;
+}
+
+
+/**
+ * Adds a directory to the image in all namespaces and default attributes.
+ *
+ * Will fail if no namespace is configured that supports symlinks.
+ *
+ * @returns IPRT status code
+ * @param hIsoMaker The ISO maker handle.
+ * @param pszSymlink The path (UTF-8) to the symlink in the ISO.
+ * @param pszTarget The symlink target (UTF-8).
+ * @param pidxObj Where to return the configuration index of the
+ * directory. Optional.
+ * @sa RTFsIsoMakerAddUnnamedSymlink, RTFsIsoMakerObjSetPath
+ */
+RTDECL(int) RTFsIsoMakerAddSymlink(RTFSISOMAKER hIsoMaker, const char *pszSymlink, const char *pszTarget, uint32_t *pidxObj)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszSymlink, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszSymlink), VERR_INVALID_NAME);
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedSymlink(hIsoMaker, NULL /*pObjInfo*/, pszTarget, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerObjSetPath(hIsoMaker, idxObj, RTFSISOMAKER_NAMESPACE_ALL, pszSymlink);
+ if (RT_SUCCESS(rc))
+ {
+ if (pidxObj)
+ *pidxObj = idxObj;
+ }
+ else
+ RTFsIsoMakerObjRemove(hIsoMaker, idxObj);
+ }
+ return rc;
+
+}
+
+
+
+/*
+ *
+ * Name space level object config.
+ * Name space level object config.
+ * Name space level object config.
+ *
+ */
+
+
+/**
+ * Modifies the mode mask for a given path in one or more namespaces.
+ *
+ * The mode mask is used by rock ridge, UDF and HFS.
+ *
+ * @returns IPRT status code.
+ * @retval VWRN_NOT_FOUND if the path wasn't found in any of the specified
+ * namespaces.
+ *
+ * @param hIsoMaker The ISO maker handler.
+ * @param pszPath The path which mode mask should be modified.
+ * @param fNamespaces The namespaces to set it in.
+ * @param fSet The mode bits to set.
+ * @param fUnset The mode bits to clear (applied first).
+ * @param fFlags Reserved, MBZ.
+ * @param pcHits Where to return number of paths found. Optional.
+ */
+RTDECL(int) RTFsIsoMakerSetPathMode(RTFSISOMAKER hIsoMaker, const char *pszPath, uint32_t fNamespaces,
+ RTFMODE fSet, RTFMODE fUnset, uint32_t fFlags, uint32_t *pcHits)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!(fSet & ~07777), VERR_INVALID_PARAMETER);
+ AssertReturn(!(fUnset & ~07777), VERR_INVALID_PARAMETER);
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+ AssertPtrNullReturn(pcHits, VERR_INVALID_POINTER);
+
+ /*
+ * Make the changes namespace by namespace.
+ */
+ uint32_t cHits = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ PRTFSISOMAKERNAME pName;
+ int rc = rtFsIsoMakerWalkPathBySpec(pNamespace, pszPath, &pName);
+ if (RT_SUCCESS(rc))
+ {
+ pName->fMode = (pName->fMode & ~fUnset) | fSet;
+ cHits++;
+ }
+ }
+ }
+
+ if (pcHits)
+ *pcHits = cHits;
+ if (cHits > 0)
+ return VINF_SUCCESS;
+ return VWRN_NOT_FOUND;
+}
+
+
+/**
+ * Modifies the owner ID for a given path in one or more namespaces.
+ *
+ * The owner ID is used by rock ridge, UDF and HFS.
+ *
+ * @returns IPRT status code.
+ * @retval VWRN_NOT_FOUND if the path wasn't found in any of the specified
+ * namespaces.
+ *
+ * @param hIsoMaker The ISO maker handler.
+ * @param pszPath The path which mode mask should be modified.
+ * @param fNamespaces The namespaces to set it in.
+ * @param idOwner The new owner ID to set.
+ * @param pcHits Where to return number of paths found. Optional.
+ */
+RTDECL(int) RTFsIsoMakerSetPathOwnerId(RTFSISOMAKER hIsoMaker, const char *pszPath, uint32_t fNamespaces,
+ RTUID idOwner, uint32_t *pcHits)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrNullReturn(pcHits, VERR_INVALID_POINTER);
+
+ /*
+ * Make the changes namespace by namespace.
+ */
+ uint32_t cHits = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ PRTFSISOMAKERNAME pName;
+ int rc = rtFsIsoMakerWalkPathBySpec(pNamespace, pszPath, &pName);
+ if (RT_SUCCESS(rc))
+ {
+ pName->uid = idOwner;
+ cHits++;
+ }
+ }
+ }
+
+ if (pcHits)
+ *pcHits = cHits;
+ if (cHits > 0)
+ return VINF_SUCCESS;
+ return VWRN_NOT_FOUND;
+}
+
+
+/**
+ * Modifies the group ID for a given path in one or more namespaces.
+ *
+ * The group ID is used by rock ridge, UDF and HFS.
+ *
+ * @returns IPRT status code.
+ * @retval VWRN_NOT_FOUND if the path wasn't found in any of the specified
+ * namespaces.
+ *
+ * @param hIsoMaker The ISO maker handler.
+ * @param pszPath The path which mode mask should be modified.
+ * @param fNamespaces The namespaces to set it in.
+ * @param idGroup The new group ID to set.
+ * @param pcHits Where to return number of paths found. Optional.
+ */
+RTDECL(int) RTFsIsoMakerSetPathGroupId(RTFSISOMAKER hIsoMaker, const char *pszPath, uint32_t fNamespaces,
+ RTGID idGroup, uint32_t *pcHits)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(RTPATH_IS_SLASH(*pszPath), VERR_INVALID_NAME);
+ AssertReturn(!(fNamespaces & ~RTFSISOMAKER_NAMESPACE_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrNullReturn(pcHits, VERR_INVALID_POINTER);
+
+ /*
+ * Make the changes namespace by namespace.
+ */
+ uint32_t cHits = 0;
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aRTFsIsoNamespaces); i++)
+ if (fNamespaces & g_aRTFsIsoNamespaces[i].fNamespace)
+ {
+ PRTFSISOMAKERNAMESPACE pNamespace = (PRTFSISOMAKERNAMESPACE)((uintptr_t)pThis + g_aRTFsIsoNamespaces[i].offNamespace);
+ if (pNamespace->uLevel > 0)
+ {
+ PRTFSISOMAKERNAME pName;
+ int rc = rtFsIsoMakerWalkPathBySpec(pNamespace, pszPath, &pName);
+ if (RT_SUCCESS(rc))
+ {
+ pName->gid = idGroup;
+ cHits++;
+ }
+ }
+ }
+
+ if (pcHits)
+ *pcHits = cHits;
+ if (cHits > 0)
+ return VINF_SUCCESS;
+ return VWRN_NOT_FOUND;
+}
+
+
+
+
+
+
+/*
+ *
+ * El Torito Booting.
+ * El Torito Booting.
+ * El Torito Booting.
+ * El Torito Booting.
+ *
+ */
+
+/**
+ * Ensures that we've got a boot catalog file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerEnsureBootCatFile(PRTFSISOMAKERINT pThis)
+{
+ if (pThis->pBootCatFile)
+ return VINF_SUCCESS;
+
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /* Create a VFS memory file for backing up the file. */
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, RTFSISOMAKER_SECTOR_SIZE, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ /* Create an unnamed VFS backed file and mark it as non-orphaned. */
+ PRTFSISOMAKERFILE pFile;
+ rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, NULL, 0, &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_VFS_FILE;
+ pFile->u.hVfsFile = hVfsFile;
+ pFile->Core.cNotOrphan = 1;
+
+ /* Save file pointer and allocate a volume descriptor. */
+ pThis->pBootCatFile = pFile;
+ pThis->cVolumeDescriptors++;
+
+ return VINF_SUCCESS;
+ }
+ RTVfsFileRelease(hVfsFile);
+ }
+ return rc;
+}
+
+
+/**
+ * Queries the configuration index of the boot catalog file object.
+ *
+ * The boot catalog file is created as necessary, thus this have to be a query
+ * rather than a getter since object creation may fail.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param pidxObj Where to return the configuration index.
+ */
+RTDECL(int) RTFsIsoMakerQueryObjIdxForBootCatalog(RTFSISOMAKER hIsoMaker, uint32_t *pidxObj)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pidxObj, VERR_INVALID_POINTER);
+ *pidxObj = UINT32_MAX;
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+
+ /*
+ * Do the job.
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ *pidxObj = pThis->pBootCatFile->Core.idxObj;
+ return rc;
+}
+
+
+/**
+ * Sets the boot catalog backing file.
+ *
+ * The content of the given file will be discarded and replaced with the boot
+ * catalog, the naming and file attributes (other than size) will be retained.
+ *
+ * This API exists mainly to assist when importing ISOs.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxObj The configuration index of the file.
+ */
+RTDECL(int) RTFsIsoMakerBootCatSetFile(RTFSISOMAKER hIsoMaker, uint32_t idxObj)
+{
+ /*
+ * Validate and translate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+
+ PRTFSISOMAKEROBJ pObj = rtFsIsoMakerIndexToObj(pThis, idxObj);
+ AssertReturn(pObj, VERR_OUT_OF_RANGE);
+ AssertReturn(pObj->enmType == RTFSISOMAKEROBJTYPE_FILE, VERR_WRONG_TYPE);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ AssertReturn( pFile->enmSrcType == RTFSISOMAKERSRCTYPE_PATH
+ || pFile->enmSrcType == RTFSISOMAKERSRCTYPE_COMMON
+ || pFile->enmSrcType == RTFSISOMAKERSRCTYPE_VFS_FILE,
+ VERR_WRONG_TYPE);
+
+ /*
+ * To reduce the possible combinations here, make sure there is a boot cat
+ * file that we're "replacing".
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Grab a reference to the boot cat memory VFS so we can destroy it
+ * later using regular destructors.
+ */
+ PRTFSISOMAKERFILE pOldFile = pThis->pBootCatFile;
+ RTVFSFILE hVfsFile = pOldFile->u.hVfsFile;
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ if (cRefs != UINT32_MAX)
+ {
+ /*
+ * Try remove the existing boot file.
+ */
+ pOldFile->Core.cNotOrphan--;
+ pThis->pBootCatFile = NULL;
+ rc = rtFsIsoMakerObjRemoveWorker(pThis, &pOldFile->Core);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Just morph pFile into a boot catalog file.
+ */
+ if (pFile->enmSrcType == RTFSISOMAKERSRCTYPE_VFS_FILE)
+ {
+ RTVfsFileRelease(pFile->u.hVfsFile);
+ pFile->u.hVfsFile = NIL_RTVFSFILE;
+ }
+
+ pThis->cbData -= RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE);
+ pFile->cbData = 0;
+ pFile->Core.cNotOrphan++;
+ pFile->enmSrcType = RTFSISOMAKERSRCTYPE_VFS_FILE;
+ pFile->u.hVfsFile = hVfsFile;
+
+ pThis->pBootCatFile = pFile;
+
+ return VINF_SUCCESS;
+ }
+
+ pThis->pBootCatFile = pOldFile;
+ pOldFile->Core.cNotOrphan++;
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ rc = VERR_ISOMK_IPE_BOOT_CAT_FILE;
+ }
+ return rc;
+}
+
+
+/**
+ * Set the validation entry of the boot catalog (this is the first entry).
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idPlatform The platform ID
+ * (ISO9660_ELTORITO_PLATFORM_ID_XXX).
+ * @param pszString CD/DVD-ROM identifier. Optional.
+ */
+RTDECL(int) RTFsIsoMakerBootCatSetValidationEntry(RTFSISOMAKER hIsoMaker, uint8_t idPlatform, const char *pszString)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ size_t cchString = 0;
+ if (pszString)
+ {
+ cchString = RTStrCalcLatin1Len(pszString);
+ AssertReturn(cchString < RT_SIZEOFMEMB(ISO9660ELTORITOVALIDATIONENTRY, achId), VERR_OUT_OF_RANGE);
+ }
+
+ /*
+ * Make sure we've got a boot file.
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Construct the entry data.
+ */
+ ISO9660ELTORITOVALIDATIONENTRY Entry;
+ Entry.bHeaderId = ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY;
+ Entry.bPlatformId = idPlatform;
+ Entry.u16Reserved = 0;
+ RT_ZERO(Entry.achId);
+ if (cchString)
+ {
+ char *pszTmp = Entry.achId;
+ rc = RTStrToLatin1Ex(pszString, RTSTR_MAX, &pszTmp, sizeof(Entry.achId), NULL);
+ AssertRC(rc);
+ }
+ Entry.u16Checksum = 0;
+ Entry.bKey1 = ISO9660_ELTORITO_KEY_BYTE_1;
+ Entry.bKey2 = ISO9660_ELTORITO_KEY_BYTE_2;
+
+ /* Calc checksum. */
+ uint16_t uSum = 0;
+ uint16_t const *pu16Src = (uint16_t const *)&Entry;
+ uint16_t cLeft = sizeof(Entry) / sizeof(uint16_t);
+ while (cLeft-- > 0)
+ {
+ uSum += RT_LE2H_U16(*pu16Src);
+ pu16Src++;
+ }
+ Entry.u16Checksum = RT_H2LE_U16((uint16_t)0 - uSum);
+
+ /*
+ * Write the entry and update our internal tracker.
+ */
+ rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, 0, &Entry, sizeof(Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->aBootCatEntries[0].bType = ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY;
+ pThis->aBootCatEntries[0].cEntries = 2;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Set the validation entry of the boot catalog (this is the first entry).
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxBootCat The boot catalog entry. Zero and two are
+ * invalid. Must be less than 63.
+ * @param idxImageObj The configuration index of the boot image.
+ * @param bBootMediaType The media type and flag (not for entry 1)
+ * (ISO9660_ELTORITO_BOOT_MEDIA_TYPE_XXX,
+ * ISO9660_ELTORITO_BOOT_MEDIA_F_XXX).
+ * @param bSystemType The partitiona table system ID.
+ * @param fBootable Whether it's a bootable entry or if we just want
+ * the BIOS to setup the emulation without booting
+ * it.
+ * @param uLoadSeg The load address divided by 0x10 (i.e. the real
+ * mode segment number).
+ * @param cSectorsToLoad Number of emulated sectors to load.
+ * @param bSelCritType The selection criteria type, if none pass
+ * ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE.
+ * @param pvSelCritData Pointer to the selection criteria data.
+ * @param cbSelCritData Size of the selection criteria data.
+ */
+RTDECL(int) RTFsIsoMakerBootCatSetSectionEntry(RTFSISOMAKER hIsoMaker, uint32_t idxBootCat, uint32_t idxImageObj,
+ uint8_t bBootMediaType, uint8_t bSystemType, bool fBootable,
+ uint16_t uLoadSeg, uint16_t cSectorsToLoad,
+ uint8_t bSelCritType, void const *pvSelCritData, size_t cbSelCritData)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)rtFsIsoMakerIndexToObj(pThis, idxImageObj);
+ AssertReturn(pFile, VERR_OUT_OF_RANGE);
+ AssertReturn((bBootMediaType & ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK) <= ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK,
+ VERR_INVALID_PARAMETER);
+ AssertReturn(!(bBootMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_MASK) || idxBootCat != 1,
+ VERR_INVALID_PARAMETER);
+
+ AssertReturn(idxBootCat != 0 && idxBootCat != 2 && idxBootCat < RT_ELEMENTS(pThis->aBootCatEntries) - 1U, VERR_OUT_OF_RANGE);
+
+ size_t cExtEntries = 0;
+ if (bSelCritType == ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE)
+ AssertReturn(cbSelCritData == 0, VERR_INVALID_PARAMETER);
+ else
+ {
+ AssertReturn(idxBootCat > 2, VERR_INVALID_PARAMETER);
+ if (cbSelCritData > 0)
+ {
+ AssertPtrReturn(pvSelCritData, VERR_INVALID_POINTER);
+
+ if (cbSelCritData <= RT_SIZEOFMEMB(ISO9660ELTORITOSECTIONENTRY, abSelectionCriteria))
+ cExtEntries = 0;
+ else
+ {
+ cExtEntries = (cbSelCritData - RT_SIZEOFMEMB(ISO9660ELTORITOSECTIONENTRY, abSelectionCriteria)
+ + RT_SIZEOFMEMB(ISO9660ELTORITOSECTIONENTRYEXT, abSelectionCriteria) - 1)
+ / RT_SIZEOFMEMB(ISO9660ELTORITOSECTIONENTRYEXT, abSelectionCriteria);
+ AssertReturn(cExtEntries + 1 < RT_ELEMENTS(pThis->aBootCatEntries) - 1, VERR_TOO_MUCH_DATA);
+ }
+ }
+ }
+
+ /*
+ * Make sure we've got a boot file.
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Construct the entry.
+ */
+ union
+ {
+ ISO9660ELTORITOSECTIONENTRY Entry;
+ ISO9660ELTORITOSECTIONENTRYEXT ExtEntry;
+ } u;
+ u.Entry.bBootIndicator = fBootable ? ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE
+ : ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE;
+ u.Entry.bBootMediaType = bBootMediaType;
+ u.Entry.uLoadSeg = RT_H2LE_U16(uLoadSeg);
+ u.Entry.bSystemType = cExtEntries == 0
+ ? bSystemType & ~ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION
+ : bSystemType | ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION;
+ u.Entry.bUnused = 0;
+ u.Entry.cEmulatedSectorsToLoad = RT_H2LE_U16(cSectorsToLoad);
+ u.Entry.offBootImage = 0;
+ u.Entry.bSelectionCriteriaType = bSelCritType;
+ RT_ZERO(u.Entry.abSelectionCriteria);
+ if (cbSelCritData > 0)
+ memcpy(u.Entry.abSelectionCriteria, pvSelCritData, RT_MIN(cbSelCritData, sizeof(u.Entry.abSelectionCriteria)));
+
+ /*
+ * Write it and update our internal tracker.
+ */
+ rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, ISO9660_ELTORITO_ENTRY_SIZE * idxBootCat,
+ &u.Entry, sizeof(u.Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->aBootCatEntries[idxBootCat].pBootFile != pFile)
+ {
+ if (pThis->aBootCatEntries[idxBootCat].pBootFile)
+ pThis->aBootCatEntries[idxBootCat].pBootFile->Core.cNotOrphan--;
+ pFile->Core.cNotOrphan++;
+ pThis->aBootCatEntries[idxBootCat].pBootFile = pFile;
+ }
+
+ pThis->aBootCatEntries[idxBootCat].bType = u.Entry.bBootIndicator;
+ pThis->aBootCatEntries[idxBootCat].cEntries = 1;
+ }
+
+ /*
+ * Do add further extension entries with selection criteria.
+ */
+ if (cExtEntries)
+ {
+ uint8_t const *pbSrc = (uint8_t const *)pvSelCritData;
+ size_t cbSrc = cbSelCritData;
+ pbSrc += sizeof(u.Entry.abSelectionCriteria);
+ cbSrc -= sizeof(u.Entry.abSelectionCriteria);
+
+ while (cbSrc > 0)
+ {
+ u.ExtEntry.bExtensionId = ISO9660_ELTORITO_SECTION_ENTRY_EXT_ID;
+ if (cbSrc > sizeof(u.ExtEntry.abSelectionCriteria))
+ {
+ u.ExtEntry.fFlags = ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_MORE;
+ memcpy(u.ExtEntry.abSelectionCriteria, pbSrc, sizeof(u.ExtEntry.abSelectionCriteria));
+ pbSrc += sizeof(u.ExtEntry.abSelectionCriteria);
+ cbSrc -= sizeof(u.ExtEntry.abSelectionCriteria);
+ }
+ else
+ {
+ u.ExtEntry.fFlags = 0;
+ RT_ZERO(u.ExtEntry.abSelectionCriteria);
+ memcpy(u.ExtEntry.abSelectionCriteria, pbSrc, cbSrc);
+ cbSrc = 0;
+ }
+
+ idxBootCat++;
+ rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, ISO9660_ELTORITO_ENTRY_SIZE * idxBootCat,
+ &u.Entry, sizeof(u.Entry), NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* update the internal tracker. */
+ if (pThis->aBootCatEntries[idxBootCat].pBootFile)
+ {
+ pThis->aBootCatEntries[idxBootCat].pBootFile->Core.cNotOrphan--;
+ pThis->aBootCatEntries[idxBootCat].pBootFile = NULL;
+ }
+
+ pThis->aBootCatEntries[idxBootCat].bType = ISO9660_ELTORITO_SECTION_ENTRY_EXT_ID;
+ pThis->aBootCatEntries[idxBootCat].cEntries = 1;
+ }
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Set the validation entry of the boot catalog (this is the first entry).
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param idxBootCat The boot catalog entry.
+ * @param cEntries Number of entries in the section.
+ * @param idPlatform The platform ID
+ * (ISO9660_ELTORITO_PLATFORM_ID_XXX).
+ * @param pszString Section identifier or something. Optional.
+ */
+RTDECL(int) RTFsIsoMakerBootCatSetSectionHeaderEntry(RTFSISOMAKER hIsoMaker, uint32_t idxBootCat, uint32_t cEntries,
+ uint8_t idPlatform, const char *pszString)
+{
+ /*
+ * Validate input.
+ */
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+
+ AssertReturn(idxBootCat >= 2 && idxBootCat < RT_ELEMENTS(pThis->aBootCatEntries) - 1U, VERR_OUT_OF_RANGE);
+ AssertReturn(cEntries < RT_ELEMENTS(pThis->aBootCatEntries) - 2U - 1U, VERR_OUT_OF_RANGE);
+ AssertReturn(idxBootCat + cEntries + 1 < RT_ELEMENTS(pThis->aBootCatEntries), VERR_OUT_OF_RANGE);
+
+ size_t cchString = 0;
+ if (pszString)
+ {
+ cchString = RTStrCalcLatin1Len(pszString);
+ AssertReturn(cchString < RT_SIZEOFMEMB(ISO9660ELTORITOVALIDATIONENTRY, achId), VERR_OUT_OF_RANGE);
+ }
+
+ /*
+ * Make sure we've got a boot file.
+ */
+ int rc = rtFsIsoMakerEnsureBootCatFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Construct the entry data.
+ */
+ ISO9660ELTORITOSECTIONHEADER Entry;
+ Entry.bHeaderId = ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER;
+ Entry.bPlatformId = idPlatform;
+ Entry.cEntries = RT_H2LE_U16(cEntries);
+ RT_ZERO(Entry.achSectionId);
+ if (cchString)
+ {
+ char *pszTmp = Entry.achSectionId;
+ rc = RTStrToLatin1Ex(pszString, RTSTR_MAX, &pszTmp, sizeof(Entry.achSectionId), NULL);
+ AssertRC(rc);
+ }
+
+ /*
+ * Write the entry and update our internal tracker.
+ */
+ rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, ISO9660_ELTORITO_ENTRY_SIZE * idxBootCat,
+ &Entry, sizeof(Entry), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->aBootCatEntries[idxBootCat].pBootFile != NULL)
+ {
+ pThis->aBootCatEntries[idxBootCat].pBootFile->Core.cNotOrphan--;
+ pThis->aBootCatEntries[idxBootCat].pBootFile = NULL;
+ }
+
+ pThis->aBootCatEntries[idxBootCat].bType = ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER;
+ pThis->aBootCatEntries[idxBootCat].cEntries = cEntries + 1;
+ }
+ }
+ return rc;
+}
+
+
+
+
+
+/*
+ *
+ * Image finalization.
+ * Image finalization.
+ * Image finalization.
+ *
+ */
+
+
+/**
+ * Remove any orphaned object from the disk.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizeRemoveOrphans(PRTFSISOMAKERINT pThis)
+{
+ for (;;)
+ {
+ uint32_t cRemoved = 0;
+ PRTFSISOMAKEROBJ pCur;
+ PRTFSISOMAKEROBJ pNext;
+ RTListForEachSafe(&pThis->ObjectHead, pCur, pNext, RTFSISOMAKEROBJ, Entry)
+ {
+ if ( pCur->pPrimaryName
+ || pCur->pJolietName
+ || pCur->pUdfName
+ || pCur->pHfsName
+ || pCur->cNotOrphan > 0)
+ { /* likely */ }
+ else
+ {
+ Log4(("rtFsIsoMakerFinalizeRemoveOrphans: %#x cbData=%#RX64\n", pCur->idxObj,
+ pCur->enmType == RTFSISOMAKEROBJTYPE_FILE ? ((PRTFSISOMAKERFILE)(pCur))->cbData : 0));
+ int rc = rtFsIsoMakerObjRemoveWorker(pThis, pCur);
+ if (RT_SUCCESS(rc))
+ {
+ if (rc != VWRN_DANGLING_OBJECTS) /** */
+ cRemoved++;
+ }
+ else
+ return rc;
+ }
+ }
+ if (!cRemoved)
+ return VINF_SUCCESS;
+ }
+}
+
+
+/**
+ * Finalizes the El Torito boot stuff, part 1.
+ *
+ * This includes generating the boot catalog data and fixing the location of all
+ * related image files.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizeBootStuffPart1(PRTFSISOMAKERINT pThis)
+{
+ /*
+ * Anything?
+ */
+ if (!pThis->pBootCatFile)
+ return VINF_SUCCESS;
+
+ /*
+ * Validate the boot catalog file.
+ */
+ AssertReturn(pThis->aBootCatEntries[0].bType == ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY,
+ VERR_ISOMK_BOOT_CAT_NO_VALIDATION_ENTRY);
+ AssertReturn(pThis->aBootCatEntries[1].pBootFile != NULL, VERR_ISOMK_BOOT_CAT_NO_DEFAULT_ENTRY);
+
+ /* Check any sections following the default one. */
+ uint32_t cEntries = 2;
+ while ( cEntries < RT_ELEMENTS(pThis->aBootCatEntries) - 1U
+ && pThis->aBootCatEntries[cEntries].cEntries > 0)
+ {
+ AssertReturn(pThis->aBootCatEntries[cEntries].bType == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER,
+ VERR_ISOMK_BOOT_CAT_EXPECTED_SECTION_HEADER);
+ for (uint32_t i = 1; i < pThis->aBootCatEntries[cEntries].cEntries; i++)
+ AssertReturn(pThis->aBootCatEntries[cEntries].pBootFile != NULL,
+ pThis->aBootCatEntries[cEntries].cEntries == 0
+ ? VERR_ISOMK_BOOT_CAT_EMPTY_ENTRY : VERR_ISOMK_BOOT_CAT_INVALID_SECTION_SIZE);
+ cEntries += pThis->aBootCatEntries[cEntries].cEntries;
+ }
+
+ /* Save for size setting. */
+ uint32_t const cEntriesInFile = cEntries + 1;
+
+ /* Check that the remaining entries are empty. */
+ while (cEntries < RT_ELEMENTS(pThis->aBootCatEntries))
+ {
+ AssertReturn(pThis->aBootCatEntries[cEntries].cEntries == 0, VERR_ISOMK_BOOT_CAT_ERRATIC_ENTRY);
+ cEntries++;
+ }
+
+ /*
+ * Fixate the size of the boot catalog file.
+ */
+ pThis->pBootCatFile->cbData = cEntriesInFile * ISO9660_ELTORITO_ENTRY_SIZE;
+ pThis->cbData += RT_ALIGN_32(cEntriesInFile * ISO9660_ELTORITO_ENTRY_SIZE, RTFSISOMAKER_SECTOR_SIZE);
+
+ /*
+ * Move up the boot images and boot catalog to the start of the image.
+ */
+ for (uint32_t i = RT_ELEMENTS(pThis->aBootCatEntries) - 2; i > 0; i--)
+ if (pThis->aBootCatEntries[i].pBootFile)
+ {
+ RTListNodeRemove(&pThis->aBootCatEntries[i].pBootFile->Core.Entry);
+ RTListPrepend(&pThis->ObjectHead, &pThis->aBootCatEntries[i].pBootFile->Core.Entry);
+ }
+
+ /* The boot catalog comes first. */
+ RTListNodeRemove(&pThis->pBootCatFile->Core.Entry);
+ RTListPrepend(&pThis->ObjectHead, &pThis->pBootCatFile->Core.Entry);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finalizes the El Torito boot stuff, part 1.
+ *
+ * This includes generating the boot catalog data and fixing the location of all
+ * related image files.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizeBootStuffPart2(PRTFSISOMAKERINT pThis)
+{
+ /*
+ * Anything?
+ */
+ if (!pThis->pBootCatFile)
+ return VINF_SUCCESS;
+
+ /*
+ * Fill in the descriptor.
+ */
+ PISO9660BOOTRECORDELTORITO pDesc = pThis->pElToritoDesc;
+ pDesc->Hdr.bDescType = ISO9660VOLDESC_TYPE_BOOT_RECORD;
+ pDesc->Hdr.bDescVersion = ISO9660PRIMARYVOLDESC_VERSION;
+ memcpy(pDesc->Hdr.achStdId, ISO9660VOLDESC_STD_ID, sizeof(pDesc->Hdr.achStdId));
+ memcpy(pDesc->achBootSystemId, RT_STR_TUPLE(ISO9660BOOTRECORDELTORITO_BOOT_SYSTEM_ID));
+ pDesc->offBootCatalog = RT_H2LE_U32((uint32_t)(pThis->pBootCatFile->offData / RTFSISOMAKER_SECTOR_SIZE));
+
+ /*
+ * Update the image file locations.
+ */
+ uint32_t cEntries = 2;
+ for (uint32_t i = 1; i < RT_ELEMENTS(pThis->aBootCatEntries) - 1; i++)
+ if (pThis->aBootCatEntries[i].pBootFile)
+ {
+ uint32_t off = pThis->aBootCatEntries[i].pBootFile->offData / RTFSISOMAKER_SECTOR_SIZE;
+ off = RT_H2LE_U32(off);
+ int rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile,
+ i * ISO9660_ELTORITO_ENTRY_SIZE + RT_UOFFSETOF(ISO9660ELTORITOSECTIONENTRY, offBootImage),
+ &off, sizeof(off), NULL /*pcbWritten*/);
+ AssertRCReturn(rc, rc);
+ if (i == cEntries)
+ cEntries = i + 1;
+ }
+
+ /*
+ * Write end section.
+ */
+ ISO9660ELTORITOSECTIONHEADER Entry;
+ Entry.bHeaderId = ISO9660_ELTORITO_HEADER_ID_FINAL_SECTION_HEADER;
+ Entry.bPlatformId = ISO9660_ELTORITO_PLATFORM_ID_X86;
+ Entry.cEntries = 0;
+ RT_ZERO(Entry.achSectionId);
+ int rc = RTVfsFileWriteAt(pThis->pBootCatFile->u.hVfsFile, cEntries * ISO9660_ELTORITO_ENTRY_SIZE,
+ &Entry, sizeof(Entry), NULL /*pcbWritten*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gathers the dirs for an ISO-9660 namespace (e.g. primary or joliet).
+ *
+ * @param pNamespace The namespace.
+ * @param pFinalizedDirs The finalized directory structure. The
+ * FinalizedDirs will be worked here.
+ */
+static void rtFsIsoMakerFinalizeGatherDirs(PRTFSISOMAKERNAMESPACE pNamespace, PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs)
+{
+ RTListInit(&pFinalizedDirs->FinalizedDirs);
+
+ /*
+ * Enter the root directory (if we got one).
+ */
+ if (!pNamespace->pRoot)
+ return;
+ PRTFSISOMAKERNAMEDIR pCurDir = pNamespace->pRoot->pDir;
+ RTListAppend(&pFinalizedDirs->FinalizedDirs, &pCurDir->FinalizedEntry);
+ do
+ {
+ /*
+ * Scan pCurDir and add directories. We don't need to sort anything
+ * here because the directory is already in path table compatible order.
+ */
+ uint32_t cLeft = pCurDir->cChildren;
+ PPRTFSISOMAKERNAME ppChild = pCurDir->papChildren;
+ while (cLeft-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = *ppChild++;
+ if (pChild->pDir)
+ RTListAppend(&pFinalizedDirs->FinalizedDirs, &pChild->pDir->FinalizedEntry);
+ }
+
+ /*
+ * Advance to the next directory.
+ */
+ pCurDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pCurDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ } while (pCurDir);
+}
+
+
+/**
+ * Allocates space in the rock ridge spill file.
+ *
+ * @returns Spill file offset, UINT32_MAX on failure.
+ * @param pRRSpillFile The spill file.
+ * @param cbRock Number of bytes to allocate.
+ */
+static uint32_t rtFsIsoMakerFinalizeAllocRockRidgeSpill(PRTFSISOMAKERFILE pRRSpillFile, uint32_t cbRock)
+{
+ uint32_t off = pRRSpillFile->cbData;
+ if (ISO9660_SECTOR_SIZE - (pRRSpillFile->cbData & ISO9660_SECTOR_OFFSET_MASK) >= cbRock)
+ { /* likely */ }
+ else
+ {
+ off |= ISO9660_SECTOR_OFFSET_MASK;
+ off++;
+ AssertLogRelReturn(off > 0, UINT32_MAX);
+ pRRSpillFile->cbData = off;
+ }
+ pRRSpillFile->cbData += RT_ALIGN_32(cbRock, 4);
+ return off;
+}
+
+
+/**
+ * Finalizes a directory entry (i.e. namespace node).
+ *
+ * This calculates the directory record size.
+ *
+ * @returns IPRT status code.
+ * @param pFinalizedDirs .
+ * @param pName The directory entry to finalize.
+ * @param offInDir The offset in the directory of this record.
+ * @param uRockRidgeLevel This is the rock ridge level.
+ * @param fIsRoot Set if this is the root.
+ */
+static int rtFsIsoMakerFinalizeIsoDirectoryEntry(PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs, PRTFSISOMAKERNAME pName,
+ uint32_t offInDir, uint8_t uRockRidgeLevel, bool fIsRoot)
+{
+ /* Set directory and translation table offsets. (These are for
+ helping generating data blocks later.) */
+ pName->offDirRec = offInDir;
+
+ /* Calculate the minimal directory record size. */
+ size_t cbDirRec = RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pName->cbNameInDirRec + !(pName->cbNameInDirRec & 1);
+ AssertReturn(cbDirRec <= UINT8_MAX, VERR_FILENAME_TOO_LONG);
+
+ pName->cbDirRec = (uint8_t)cbDirRec;
+ pName->cDirRecs = 1;
+ if (pName->pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pName->pObj;
+ if (pFile->cbData > UINT32_MAX)
+ pName->cDirRecs = (pFile->cbData + RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE - 1) / RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE;
+ }
+
+ /*
+ * Calculate the size of the rock ridge bits we need.
+ */
+ if (uRockRidgeLevel > 0)
+ {
+ uint16_t cbRock = 0;
+ uint8_t fFlags = 0;
+
+ /* Level two starts with a 'RR' entry. */
+ if (uRockRidgeLevel >= 2)
+ cbRock += sizeof(ISO9660RRIPRR);
+
+ /* We always do 'PX' and 'TF' w/ 4 timestamps. */
+ cbRock += sizeof(ISO9660RRIPPX)
+ + RT_UOFFSETOF(ISO9660RRIPTF, abPayload) + 4 * sizeof(ISO9660RECTIMESTAMP);
+ fFlags |= ISO9660RRIP_RR_F_PX | ISO9660RRIP_RR_F_TF;
+
+ /* Devices needs 'PN'. */
+ if ( RTFS_IS_DEV_BLOCK(pName->pObj->fMode)
+ || RTFS_IS_DEV_CHAR(pName->pObj->fMode))
+ {
+ cbRock += sizeof(ISO9660RRIPPN);
+ fFlags |= ISO9660RRIP_RR_F_PN;
+ }
+
+ /* Usually we need a 'NM' entry too. */
+ if ( pName->pszRockRidgeNm != pName->szName
+ && pName->cchRockRidgeNm > 0
+ && ( pName->cbNameInDirRec != 1
+ || (uint8_t)pName->szName[0] > (uint8_t)0x01) ) /** @todo only root dir ever uses an ID byte here? [RR NM ./..] */
+ {
+ uint16_t cchNm = pName->cchRockRidgeNm;
+ while (cchNm > ISO9660RRIPNM_MAX_NAME_LEN)
+ {
+ cbRock += (uint16_t)RT_UOFFSETOF(ISO9660RRIPNM, achName) + ISO9660RRIPNM_MAX_NAME_LEN;
+ cchNm -= ISO9660RRIPNM_MAX_NAME_LEN;
+ }
+ cbRock += (uint16_t)RT_UOFFSETOF(ISO9660RRIPNM, achName) + cchNm;
+ fFlags |= ISO9660RRIP_RR_F_NM;
+ }
+
+ /* Symbolic links needs a 'SL' entry. */
+ if (pName->pObj->enmType == RTFSISOMAKEROBJTYPE_SYMLINK)
+ {
+ PRTFSISOMAKERSYMLINK pSymlink = (PRTFSISOMAKERSYMLINK)pName->pObj;
+ cbRock += pSymlink->cbSlRockRidge;
+ fFlags |= ISO9660RRIP_RR_F_SL;
+ }
+
+ /*
+ * Decide where stuff goes. The '.' record of the root dir is special.
+ */
+ pName->fRockEntries = fFlags;
+ if (!fIsRoot)
+ {
+ if (pName->cbDirRec + cbRock < UINT8_MAX)
+ {
+ pName->cbRockInDirRec = cbRock;
+ pName->cbRockSpill = 0;
+ pName->fRockNeedRRInDirRec = uRockRidgeLevel >= 2;
+ pName->fRockNeedRRInSpill = false;
+ }
+ else if (pName->cbDirRec + sizeof(ISO9660SUSPCE) < UINT8_MAX)
+ {
+ /* Try fit the 'RR' entry in the directory record, but don't bother with anything else. */
+ if (uRockRidgeLevel >= 2 && pName->cbDirRec + sizeof(ISO9660SUSPCE) + sizeof(ISO9660RRIPRR) < UINT8_MAX)
+ {
+ pName->cbRockInDirRec = (uint16_t)(sizeof(ISO9660SUSPCE) + sizeof(ISO9660RRIPRR));
+ cbRock -= sizeof(ISO9660RRIPRR);
+ pName->cbRockSpill = cbRock;
+ pName->fRockNeedRRInDirRec = true;
+ pName->fRockNeedRRInSpill = false;
+ }
+ else
+ {
+ pName->cbRockInDirRec = (uint16_t)sizeof(ISO9660SUSPCE);
+ pName->cbRockSpill = cbRock;
+ pName->fRockNeedRRInDirRec = false;
+ pName->fRockNeedRRInSpill = uRockRidgeLevel >= 2;
+ }
+ pName->offRockSpill = rtFsIsoMakerFinalizeAllocRockRidgeSpill(pFinalizedDirs->pRRSpillFile, cbRock);
+ AssertReturn(pName->offRockSpill != UINT32_MAX, VERR_ISOMK_RR_SPILL_FILE_FULL);
+ }
+ else
+ {
+ LogRel(("RTFsIsoMaker: no space for 'CE' entry: cbDirRec=%#x bytes, name=%s (%#x bytes)\n",
+ pName->cbDirRec, pName->szName, pName->cbNameInDirRec));
+ return VERR_ISOMK_RR_NO_SPACE_FOR_CE;
+ }
+ }
+ else
+ {
+ /* The root starts with a 'SP' record to indicate that SUSP is being used,
+ this is always in the directory record. If we add a 'ER' record (big) too,
+ we put all but 'SP' and 'ER' in the spill file too keep things simple. */
+ if (uRockRidgeLevel < 2)
+ {
+ Assert(!(fFlags & (ISO9660RRIP_RR_F_NM | ISO9660RRIP_RR_F_SL | ISO9660RRIP_RR_F_CL | ISO9660RRIP_RR_F_PL | ISO9660RRIP_RR_F_RE)));
+ cbRock += sizeof(ISO9660SUSPSP);
+ Assert(pName->cbDirRec + cbRock < UINT8_MAX);
+ pName->cbRockInDirRec = cbRock;
+ pName->cbRockSpill = 0;
+ pName->fRockNeedER = false;
+ pName->fRockNeedRRInDirRec = false;
+ pName->fRockNeedRRInSpill = false;
+ }
+ else
+ {
+ pName->cbRockInDirRec = (uint16_t)(sizeof(ISO9660SUSPSP) + sizeof(ISO9660SUSPCE));
+ pName->fRockNeedER = true;
+ pName->fRockNeedRRInSpill = true;
+ pName->fRockNeedRRInDirRec = false;
+ cbRock += ISO9660_RRIP_ER_LEN;
+ pName->cbRockSpill = cbRock;
+ pName->offRockSpill = rtFsIsoMakerFinalizeAllocRockRidgeSpill(pFinalizedDirs->pRRSpillFile, cbRock);
+ }
+ }
+ pName->cbDirRec += pName->cbRockInDirRec + (pName->cbRockInDirRec & 1);
+ Assert(pName->cbDirRec < UINT8_MAX);
+ }
+
+ pName->cbDirRecTotal = pName->cbDirRec * pName->cDirRecs;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finalizes either a primary and secondary ISO namespace.
+ *
+ * @returns IPRT status code
+ * @param pThis The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pFinalizedDirs The finalized directories structure for the
+ * namespace.
+ * @param poffData The data offset. We will allocate blocks for the
+ * directories and the path tables.
+ */
+static int rtFsIsoMakerFinalizeDirectoriesInIsoNamespace(PRTFSISOMAKERINT pThis, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs, uint64_t *poffData)
+{
+ int rc;
+
+ /* The directory data comes first, so take down it's offset. */
+ pFinalizedDirs->offDirs = *poffData;
+
+ /*
+ * Reset the rock ridge spill file (in case we allow finalizing more than once)
+ * and create a new spill file if rock ridge is enabled. The directory entry
+ * finalize function uses this as a clue that rock ridge is enabled.
+ */
+ if (pFinalizedDirs->pRRSpillFile)
+ {
+ pFinalizedDirs->pRRSpillFile->Core.cNotOrphan = 0;
+ rtFsIsoMakerObjRemoveWorker(pThis, &pFinalizedDirs->pRRSpillFile->Core);
+ pFinalizedDirs->pRRSpillFile = NULL;
+ }
+ if (pNamespace->uRockRidgeLevel > 0)
+ {
+ rc = rtFsIsoMakerAddUnnamedFileWorker(pThis, NULL, 0, &pFinalizedDirs->pRRSpillFile);
+ AssertRCReturn(rc, rc);
+ pFinalizedDirs->pRRSpillFile->enmSrcType = RTFSISOMAKERSRCTYPE_RR_SPILL;
+ pFinalizedDirs->pRRSpillFile->u.pRockSpillNamespace = pNamespace;
+ pFinalizedDirs->pRRSpillFile->Core.cNotOrphan = 1;
+ }
+
+ uint16_t idPathTable = 1;
+ uint32_t cbPathTable = 0;
+ if (pNamespace->pRoot)
+ {
+ /*
+ * Precalc the directory record size for the root directory.
+ */
+ rc = rtFsIsoMakerFinalizeIsoDirectoryEntry(pFinalizedDirs, pNamespace->pRoot, 0 /*offInDir*/,
+ pNamespace->uRockRidgeLevel, true /*fIsRoot*/);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Work thru the directories.
+ */
+ PRTFSISOMAKERNAMEDIR pCurDir;
+ RTListForEach(&pFinalizedDirs->FinalizedDirs, pCurDir, RTFSISOMAKERNAMEDIR, FinalizedEntry)
+ {
+ PRTFSISOMAKERNAME pCurName = pCurDir->pName;
+ PRTFSISOMAKERNAME pParentName = pCurName->pParent ? pCurName->pParent : pCurName;
+
+ /* We don't do anything special for the special '.' and '..' directory
+ entries, instead we use the directory entry in the parent directory
+ with a 1 byte name (00 or 01). */
+ /** @todo r=bird: This causes trouble with RR NM records, since we'll be
+ * emitting the real directory name rather than '.' or '..' (or
+ * whatever we should be emitting for these two special dirs).
+ * FreeBSD got confused with this. The RTFSISOMAKERDIRTYPE stuff is a
+ * workaround for this, however it doesn't hold up if we have to use
+ * the spill file. [RR NM ./..] */
+ Assert(pCurName->cbDirRec != 0);
+ Assert(pParentName->cbDirRec != 0);
+ pCurDir->cbDirRec00 = pCurName->cbDirRec - pCurName->cbNameInDirRec - !(pCurName->cbNameInDirRec & 1) + 1;
+ pCurDir->cbDirRec01 = pParentName->cbDirRec - pParentName->cbNameInDirRec - !(pParentName->cbNameInDirRec & 1) + 1;
+
+ uint32_t offInDir = (uint32_t)pCurDir->cbDirRec00 + pCurDir->cbDirRec01;
+
+ /* Finalize the directory entries. */
+ uint32_t cSubDirs = 0;
+ uint32_t cbTransTbl = 0;
+ uint32_t cLeft = pCurDir->cChildren;
+ PPRTFSISOMAKERNAME ppChild = pCurDir->papChildren;
+ while (cLeft-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = *ppChild++;
+ rc = rtFsIsoMakerFinalizeIsoDirectoryEntry(pFinalizedDirs, pChild, offInDir,
+ pNamespace->uRockRidgeLevel, false /*fIsRoot*/);
+ AssertRCReturn(rc, rc);
+
+ if ((RTFSISOMAKER_SECTOR_SIZE - (offInDir & RTFSISOMAKER_SECTOR_OFFSET_MASK)) < pChild->cbDirRecTotal)
+ {
+ Assert(ppChild[-1] == pChild && &ppChild[-1] != pCurDir->papChildren);
+ if ( pChild->cDirRecs == 1
+ || pChild->cDirRecs <= RTFSISOMAKER_SECTOR_SIZE / pChild->cbDirRec)
+ {
+ ppChild[-2]->cbDirRecTotal += RTFSISOMAKER_SECTOR_SIZE - (offInDir & RTFSISOMAKER_SECTOR_OFFSET_MASK);
+ offInDir = (offInDir | RTFSISOMAKER_SECTOR_OFFSET_MASK) + 1; /* doesn't fit, skip to next sector. */
+ Log4(("rtFsIsoMakerFinalizeDirectoriesInIsoNamespace: zero padding dir rec @%#x: %#x -> %#x; offset %#x -> %#x\n",
+ ppChild[-2]->offDirRec, ppChild[-2]->cbDirRec, ppChild[-2]->cbDirRecTotal, pChild->offDirRec, offInDir));
+ pChild->offDirRec = offInDir;
+ }
+ /* else: too complicated and ulikely, so whatever. */
+ }
+
+ offInDir += pChild->cbDirRecTotal;
+ if (pChild->cchTransNm)
+ cbTransTbl += 2 /* type & space*/
+ + RT_MAX(pChild->cchName, RTFSISOMAKER_TRANS_TBL_LEFT_PAD)
+ + 1 /* tab */
+ + pChild->cchTransNm
+ + 1 /* newline */;
+
+ if (RTFS_IS_DIRECTORY(pChild->fMode))
+ cSubDirs++;
+ }
+
+ /* Set the directory size and location, advancing the data offset. */
+ pCurDir->cbDir = offInDir;
+ pCurDir->offDir = *poffData;
+ *poffData += RT_ALIGN_32(offInDir, RTFSISOMAKER_SECTOR_SIZE);
+
+ /* Set the translation table file size. */
+ if (pCurDir->pTransTblFile)
+ {
+ pCurDir->pTransTblFile->cbData = cbTransTbl;
+ pThis->cbData += RT_ALIGN_32(cbTransTbl, RTFSISOMAKER_SECTOR_SIZE);
+ }
+
+ /* Add to the path table size calculation. */
+ pCurDir->offPathTable = cbPathTable;
+ pCurDir->idPathTable = idPathTable++;
+ cbPathTable += RTFSISOMAKER_CALC_PATHREC_SIZE(pCurName->cbNameInDirRec);
+
+ /* Set the hardlink count. */
+ pCurName->cHardlinks = cSubDirs + 2;
+
+ Log4(("rtFsIsoMakerFinalizeDirectoriesInIsoNamespace: idxObj=#%#x cbDir=%#08x cChildren=%#05x %s\n",
+ pCurDir->pName->pObj->idxObj, pCurDir->cbDir, pCurDir->cChildren, pCurDir->pName->szName));
+ }
+ }
+
+ /*
+ * Remove rock ridge spill file if we haven't got any spill.
+ * If we have, round the size up to a whole sector to avoid the slow path
+ * when reading from it.
+ */
+ if (pFinalizedDirs->pRRSpillFile)
+ {
+ if (pFinalizedDirs->pRRSpillFile->cbData > 0)
+ {
+ pFinalizedDirs->pRRSpillFile->cbData = RT_ALIGN_64(pFinalizedDirs->pRRSpillFile->cbData, ISO9660_SECTOR_SIZE);
+ pThis->cbData += pFinalizedDirs->pRRSpillFile->cbData;
+ }
+ else
+ {
+ rc = rtFsIsoMakerObjRemoveWorker(pThis, &pFinalizedDirs->pRRSpillFile->Core);
+ if (RT_SUCCESS(rc))
+ pFinalizedDirs->pRRSpillFile = NULL;
+ }
+ }
+
+ /*
+ * Calculate the path table offsets and move past them.
+ */
+ pFinalizedDirs->cbPathTable = cbPathTable;
+ pFinalizedDirs->offPathTableL = *poffData;
+ *poffData += RT_ALIGN_64(cbPathTable, RTFSISOMAKER_SECTOR_SIZE);
+
+ pFinalizedDirs->offPathTableM = *poffData;
+ *poffData += RT_ALIGN_64(cbPathTable, RTFSISOMAKER_SECTOR_SIZE);
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Finalizes directories and related stuff.
+ *
+ * This will not generate actual directory data, but calculate the size of it
+ * once it's generated. Ditto for the path tables. The exception is the rock
+ * ridge spill file, which will be generated in memory.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param poffData The data offset (in/out).
+ */
+static int rtFsIsoMakerFinalizeDirectories(PRTFSISOMAKERINT pThis, uint64_t *poffData)
+{
+ /*
+ * Locate the directories, width first, inserting them in the finalized lists so
+ * we can process them efficiently.
+ */
+ rtFsIsoMakerFinalizeGatherDirs(&pThis->PrimaryIso, &pThis->PrimaryIsoDirs);
+ rtFsIsoMakerFinalizeGatherDirs(&pThis->Joliet, &pThis->JolietDirs);
+
+ /*
+ * Process the primary ISO and joliet namespaces.
+ */
+ int rc = rtFsIsoMakerFinalizeDirectoriesInIsoNamespace(pThis, &pThis->PrimaryIso, &pThis->PrimaryIsoDirs, poffData);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerFinalizeDirectoriesInIsoNamespace(pThis, &pThis->Joliet, &pThis->JolietDirs, poffData);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Later: UDF, HFS.
+ */
+ }
+ return rc;
+}
+
+
+/**
+ * Finalizes data allocations.
+ *
+ * This will set the RTFSISOMAKERFILE::offData members.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ * @param poffData The data offset (in/out).
+ */
+static int rtFsIsoMakerFinalizeData(PRTFSISOMAKERINT pThis, uint64_t *poffData)
+{
+ pThis->offFirstFile = *poffData;
+
+ /*
+ * We currently does not have any ordering prioritizing implemented, so we
+ * just store files in the order they were added.
+ */
+ PRTFSISOMAKEROBJ pCur;
+ RTListForEach(&pThis->ObjectHead, pCur, RTFSISOMAKEROBJ, Entry)
+ {
+ if (pCur->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pCurFile = (PRTFSISOMAKERFILE)pCur;
+ if (pCurFile->offData == UINT64_MAX)
+ {
+ pCurFile->offData = *poffData;
+ *poffData += RT_ALIGN_64(pCurFile->cbData, RTFSISOMAKER_SECTOR_SIZE);
+ RTListAppend(&pThis->FinalizedFiles, &pCurFile->FinalizedEntry);
+ Log4(("rtFsIsoMakerFinalizeData: %#x @%#RX64 cbData=%#RX64\n", pCurFile->Core.idxObj, pCurFile->offData, pCurFile->cbData));
+ }
+
+ /*
+ * Create the boot info table.
+ */
+ if (pCurFile->pBootInfoTable)
+ {
+ /*
+ * Checksum the file.
+ */
+ int rc;
+ RTVFSFILE hVfsFile;
+ uint64_t offBase;
+ switch (pCurFile->enmSrcType)
+ {
+ case RTFSISOMAKERSRCTYPE_PATH:
+ rc = RTVfsChainOpenFile(pCurFile->u.pszSrcPath, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &hVfsFile, NULL, NULL);
+ AssertMsgRCReturn(rc, ("%s -> %Rrc\n", pCurFile->u.pszSrcPath, rc), rc);
+ offBase = 0;
+ break;
+ case RTFSISOMAKERSRCTYPE_VFS_FILE:
+ hVfsFile = pCurFile->u.hVfsFile;
+ offBase = 0;
+ rc = VINF_SUCCESS;
+ break;
+ case RTFSISOMAKERSRCTYPE_COMMON:
+ hVfsFile = pThis->paCommonSources[pCurFile->u.Common.idxSrc];
+ offBase = pCurFile->u.Common.offData;
+ rc = VINF_SUCCESS;
+ break;
+ default:
+ AssertMsgFailedReturn(("enmSrcType=%d\n", pCurFile->enmSrcType), VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ uint32_t uChecksum = 0;
+ uint32_t off = 64;
+ uint32_t cbLeft = RT_MAX(64, (uint32_t)pCurFile->cbData) - 64;
+ while (cbLeft > 0)
+ {
+ union
+ {
+ uint8_t ab[_16K];
+ uint32_t au32[_16K / sizeof(uint32_t)];
+ } uBuf;
+ uint32_t cbRead = RT_MIN(sizeof(uBuf), cbLeft);
+ if (cbRead & 3)
+ RT_ZERO(uBuf);
+ rc = RTVfsFileReadAt(hVfsFile, offBase + off, &uBuf, cbRead, NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ size_t i = RT_ALIGN_Z(cbRead, sizeof(uint32_t)) / sizeof(uint32_t);
+ while (i-- > 0)
+ uChecksum += RT_LE2H_U32(uBuf.au32[i]);
+
+ off += cbRead;
+ cbLeft -= cbRead;
+ }
+
+ if (pCurFile->enmSrcType == RTFSISOMAKERSRCTYPE_PATH)
+ RTVfsFileRelease(hVfsFile);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Populate the structure.
+ */
+ pCurFile->pBootInfoTable->offPrimaryVolDesc = RT_H2LE_U32(16);
+ pCurFile->pBootInfoTable->offBootFile = RT_H2LE_U32((uint32_t)(pCurFile->offData / RTFSISOMAKER_SECTOR_SIZE));
+ pCurFile->pBootInfoTable->cbBootFile = RT_H2LE_U32((uint32_t)pCurFile->cbData);
+ pCurFile->pBootInfoTable->uChecksum = RT_H2LE_U32(uChecksum);
+ RT_ZERO(pCurFile->pBootInfoTable->auReserved);
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Copies the given string as UTF-16 and pad unused space in the destination
+ * with spaces.
+ *
+ * @param pachDst The destination field. C type is char, but real life
+ * type is UTF-16 / UCS-2.
+ * @param cchDst The size of the destination field.
+ * @param pszSrc The source string. NULL is treated like empty string.
+ */
+static void rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(char *pachDst, size_t cchDst, const char *pszSrc)
+{
+ size_t cwcSrc = 0;
+ if (pszSrc)
+ {
+ RTUTF16 wszSrc[256];
+ PRTUTF16 pwszSrc = wszSrc;
+ int rc = RTStrToUtf16BigEx(pszSrc, RTSTR_MAX, &pwszSrc, RT_ELEMENTS(wszSrc), &cwcSrc);
+ AssertRCStmt(rc, cwcSrc = 0);
+
+ if (cwcSrc > cchDst / sizeof(RTUTF16))
+ cwcSrc = cchDst / sizeof(RTUTF16);
+ memcpy(pachDst, wszSrc, cwcSrc * sizeof(RTUTF16));
+ }
+
+ /* Space padding. Note! cchDst can be an odd number. */
+ size_t cchWritten = cwcSrc * sizeof(RTUTF16);
+ if (cchWritten < cchDst)
+ {
+ while (cchWritten + 2 <= cchDst)
+ {
+ pachDst[cchWritten++] = '\0';
+ pachDst[cchWritten++] = ' ';
+ }
+ if (cchWritten < cchDst)
+ pachDst[cchWritten] = '\0';
+ }
+}
+
+
+/**
+ * Copies the given string and pad unused space in the destination with spaces.
+ *
+ * @param pachDst The destination field.
+ * @param cchDst The size of the destination field.
+ * @param pszSrc The source string. NULL is treated like empty string.
+ */
+static void rtFsIsoMakerFinalizeCopyAndSpacePad(char *pachDst, size_t cchDst, const char *pszSrc)
+{
+ size_t cchSrc;
+ if (!pszSrc)
+ cchSrc = 0;
+ else
+ {
+ cchSrc = strlen(pszSrc);
+ if (cchSrc > cchDst)
+ cchSrc = cchDst;
+ memcpy(pachDst, pszSrc, cchSrc);
+ }
+ if (cchSrc < cchDst)
+ memset(&pachDst[cchSrc], ' ', cchDst - cchSrc);
+}
+
+
+/**
+ * Formats a timespec as an ISO-9660 ascii timestamp.
+ *
+ * @param pTime The timespec to format.
+ * @param pIsoTs The ISO-9660 timestamp destination buffer.
+ */
+static void rtFsIsoMakerTimespecToIso9660Timestamp(PCRTTIMESPEC pTime, PISO9660TIMESTAMP pIsoTs)
+{
+ RTTIME Exploded;
+ RTTimeExplode(&Exploded, pTime);
+
+ char szTmp[64];
+#define FORMAT_FIELD(a_achDst, a_uSrc) \
+ do { \
+ RTStrFormatU32(szTmp, sizeof(szTmp), a_uSrc, 10, sizeof(a_achDst), sizeof(a_achDst), \
+ RTSTR_F_ZEROPAD | RTSTR_F_WIDTH | RTSTR_F_PRECISION); \
+ memcpy(a_achDst, szTmp, sizeof(a_achDst)); \
+ } while (0)
+ FORMAT_FIELD(pIsoTs->achYear, Exploded.i32Year);
+ FORMAT_FIELD(pIsoTs->achMonth, Exploded.u8Month);
+ FORMAT_FIELD(pIsoTs->achDay, Exploded.u8MonthDay);
+ FORMAT_FIELD(pIsoTs->achHour, Exploded.u8Hour);
+ FORMAT_FIELD(pIsoTs->achMinute, Exploded.u8Minute);
+ FORMAT_FIELD(pIsoTs->achSecond, Exploded.u8Second);
+ FORMAT_FIELD(pIsoTs->achCentisecond, Exploded.u32Nanosecond / RT_NS_10MS);
+#undef FORMAT_FIELD
+ pIsoTs->offUtc = 0;
+}
+
+/**
+ * Formats zero ISO-9660 ascii timestamp (treated as not specified).
+ *
+ * @param pIsoTs The ISO-9660 timestamp destination buffer.
+ */
+static void rtFsIsoMakerZero9660Timestamp(PISO9660TIMESTAMP pIsoTs)
+{
+ memset(pIsoTs, '0', RT_UOFFSETOF(ISO9660TIMESTAMP, offUtc));
+ pIsoTs->offUtc = 0;
+}
+
+
+/**
+ * Formats a timespec as an ISO-9660 record timestamp.
+ *
+ * @param pTime The timespec to format.
+ * @param pIsoTs The ISO-9660 timestamp destination buffer.
+ */
+static void rtFsIsoMakerTimespecToIso9660RecTimestamp(PCRTTIMESPEC pTime, PISO9660RECTIMESTAMP pIsoRecTs)
+{
+ RTTIME Exploded;
+ RTTimeExplode(&Exploded, pTime);
+
+ pIsoRecTs->bYear = Exploded.i32Year >= 1900 ? Exploded.i32Year - 1900 : 0;
+ pIsoRecTs->bMonth = Exploded.u8Month;
+ pIsoRecTs->bDay = Exploded.u8MonthDay;
+ pIsoRecTs->bHour = Exploded.u8Hour;
+ pIsoRecTs->bMinute = Exploded.u8Minute;
+ pIsoRecTs->bSecond = Exploded.u8Second;
+ pIsoRecTs->offUtc = 0;
+}
+
+
+/**
+ * Allocate and prepare the volume descriptors.
+ *
+ * What's not done here gets done later by rtFsIsoMakerFinalizeBootStuffPart2,
+ * or at teh very end of the finalization by
+ * rtFsIsoMakerFinalizeVolumeDescriptors.
+ *
+ * @returns IPRT status code
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizePrepVolumeDescriptors(PRTFSISOMAKERINT pThis)
+{
+ /*
+ * Allocate and calc pointers.
+ */
+ RTMemFree(pThis->pbVolDescs);
+ pThis->pbVolDescs = (uint8_t *)RTMemAllocZ(pThis->cVolumeDescriptors * RTFSISOMAKER_SECTOR_SIZE);
+ AssertReturn(pThis->pbVolDescs, VERR_NO_MEMORY);
+
+ uint32_t offVolDescs = 0;
+
+ pThis->pPrimaryVolDesc = (PISO9660PRIMARYVOLDESC)&pThis->pbVolDescs[offVolDescs];
+ offVolDescs += RTFSISOMAKER_SECTOR_SIZE;
+
+ if (!pThis->pBootCatFile)
+ pThis->pElToritoDesc = NULL;
+ else
+ {
+ pThis->pElToritoDesc = (PISO9660BOOTRECORDELTORITO)&pThis->pbVolDescs[offVolDescs];
+ offVolDescs += RTFSISOMAKER_SECTOR_SIZE;
+ }
+
+ if (!pThis->Joliet.uLevel)
+ pThis->pJolietVolDesc = NULL;
+ else
+ {
+ pThis->pJolietVolDesc = (PISO9660SUPVOLDESC)&pThis->pbVolDescs[offVolDescs];
+ offVolDescs += RTFSISOMAKER_SECTOR_SIZE;
+ }
+
+ pThis->pTerminatorVolDesc = (PISO9660VOLDESCHDR)&pThis->pbVolDescs[offVolDescs];
+ offVolDescs += RTFSISOMAKER_SECTOR_SIZE;
+
+ if (pThis->Udf.uLevel > 0)
+ {
+ /** @todo UDF descriptors. */
+ }
+ AssertReturn(offVolDescs == pThis->cVolumeDescriptors * RTFSISOMAKER_SECTOR_SIZE, VERR_ISOMK_IPE_DESC_COUNT);
+
+ /*
+ * This may be needed later.
+ */
+ char szImageCreationTime[42];
+ RTTimeSpecToString(&pThis->ImageCreationTime, szImageCreationTime, sizeof(szImageCreationTime));
+
+ /*
+ * Initialize the primary descriptor.
+ */
+ PISO9660PRIMARYVOLDESC pPrimary = pThis->pPrimaryVolDesc;
+
+ pPrimary->Hdr.bDescType = ISO9660VOLDESC_TYPE_PRIMARY;
+ pPrimary->Hdr.bDescVersion = ISO9660PRIMARYVOLDESC_VERSION;
+ memcpy(pPrimary->Hdr.achStdId, ISO9660VOLDESC_STD_ID, sizeof(pPrimary->Hdr.achStdId));
+ //pPrimary->bPadding8 = 0;
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achSystemId, sizeof(pPrimary->achSystemId), pThis->PrimaryIso.pszSystemId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achVolumeId, sizeof(pPrimary->achVolumeId),
+ pThis->PrimaryIso.pszVolumeId ? pThis->PrimaryIso.pszVolumeId : szImageCreationTime);
+ //pPrimary->Unused73 = {0}
+ //pPrimary->VolumeSpaceSize = later
+ //pPrimary->abUnused89 = {0}
+ pPrimary->cVolumesInSet.be = RT_H2BE_U16_C(1);
+ pPrimary->cVolumesInSet.le = RT_H2LE_U16_C(1);
+ pPrimary->VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pPrimary->VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pPrimary->cbLogicalBlock.be = RT_H2BE_U16_C(RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->cbLogicalBlock.le = RT_H2LE_U16_C(RTFSISOMAKER_SECTOR_SIZE);
+ //pPrimary->cbPathTable = later
+ //pPrimary->offTypeLPathTable = later
+ //pPrimary->offOptionalTypeLPathTable = {0}
+ //pPrimary->offTypeMPathTable = later
+ //pPrimary->offOptionalTypeMPathTable = {0}
+ //pPrimary->RootDir = later
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achVolumeSetId, sizeof(pPrimary->achVolumeSetId),
+ pThis->PrimaryIso.pszVolumeSetId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achPublisherId, sizeof(pPrimary->achPublisherId),
+ pThis->PrimaryIso.pszPublisherId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achDataPreparerId, sizeof(pPrimary->achDataPreparerId),
+ pThis->PrimaryIso.pszDataPreparerId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achApplicationId, sizeof(pPrimary->achApplicationId),
+ pThis->PrimaryIso.pszApplicationId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achCopyrightFileId, sizeof(pPrimary->achCopyrightFileId),
+ pThis->PrimaryIso.pszCopyrightFileId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achAbstractFileId, sizeof(pPrimary->achAbstractFileId),
+ pThis->PrimaryIso.pszAbstractFileId);
+ rtFsIsoMakerFinalizeCopyAndSpacePad(pPrimary->achBibliographicFileId, sizeof(pPrimary->achBibliographicFileId),
+ pThis->PrimaryIso.pszBibliographicFileId);
+ rtFsIsoMakerTimespecToIso9660Timestamp(&pThis->ImageCreationTime, &pPrimary->BirthTime);
+ rtFsIsoMakerTimespecToIso9660Timestamp(&pThis->ImageCreationTime, &pPrimary->ModifyTime);
+ rtFsIsoMakerZero9660Timestamp(&pPrimary->ExpireTime);
+ rtFsIsoMakerZero9660Timestamp(&pPrimary->EffectiveTime);
+ pPrimary->bFileStructureVersion = ISO9660_FILE_STRUCTURE_VERSION;
+ //pPrimary->bReserved883 = 0;
+ //RT_ZERO(pPrimary->abAppUse);
+ //RT_ZERO(pPrimary->abReserved1396);
+
+ /*
+ * Initialize the joliet descriptor if included.
+ */
+ PISO9660SUPVOLDESC pJoliet = pThis->pJolietVolDesc;
+ if (pJoliet)
+ {
+ pJoliet->Hdr.bDescType = ISO9660VOLDESC_TYPE_SUPPLEMENTARY;
+ pJoliet->Hdr.bDescVersion = ISO9660SUPVOLDESC_VERSION;
+ memcpy(pJoliet->Hdr.achStdId, ISO9660VOLDESC_STD_ID, sizeof(pJoliet->Hdr.achStdId));
+ pJoliet->fVolumeFlags = ISO9660SUPVOLDESC_VOL_F_ESC_ONLY_REG;
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achSystemId, sizeof(pJoliet->achSystemId), pThis->Joliet.pszSystemId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achVolumeId, sizeof(pJoliet->achVolumeId),
+ pThis->Joliet.pszVolumeId ? pThis->Joliet.pszVolumeId : szImageCreationTime);
+ //pJoliet->Unused73 = {0}
+ //pJoliet->VolumeSpaceSize = later
+ memset(pJoliet->abEscapeSequences, ' ', sizeof(pJoliet->abEscapeSequences));
+ pJoliet->abEscapeSequences[0] = ISO9660_JOLIET_ESC_SEQ_0;
+ pJoliet->abEscapeSequences[1] = ISO9660_JOLIET_ESC_SEQ_1;
+ pJoliet->abEscapeSequences[2] = pThis->Joliet.uLevel == 1 ? ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1
+ : pThis->Joliet.uLevel == 2 ? ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2
+ : ISO9660_JOLIET_ESC_SEQ_2_LEVEL_3;
+ pJoliet->cVolumesInSet.be = RT_H2BE_U16_C(1);
+ pJoliet->cVolumesInSet.le = RT_H2LE_U16_C(1);
+ pJoliet->VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pJoliet->VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pJoliet->cbLogicalBlock.be = RT_H2BE_U16_C(RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->cbLogicalBlock.le = RT_H2LE_U16_C(RTFSISOMAKER_SECTOR_SIZE);
+ //pJoliet->cbPathTable = later
+ //pJoliet->offTypeLPathTable = later
+ //pJoliet->offOptionalTypeLPathTable = {0}
+ //pJoliet->offTypeMPathTable = later
+ //pJoliet->offOptionalTypeMPathTable = {0}
+ //pJoliet->RootDir = later
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achVolumeSetId, sizeof(pJoliet->achVolumeSetId),
+ pThis->Joliet.pszVolumeSetId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achPublisherId, sizeof(pJoliet->achPublisherId),
+ pThis->Joliet.pszPublisherId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achDataPreparerId, sizeof(pJoliet->achDataPreparerId),
+ pThis->Joliet.pszDataPreparerId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achApplicationId, sizeof(pJoliet->achApplicationId),
+ pThis->Joliet.pszApplicationId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achCopyrightFileId, sizeof(pJoliet->achCopyrightFileId),
+ pThis->Joliet.pszCopyrightFileId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achAbstractFileId, sizeof(pJoliet->achAbstractFileId),
+ pThis->Joliet.pszAbstractFileId);
+ rtFsIsoMakerFinalizeCopyAsUtf16BigAndSpacePad(pJoliet->achBibliographicFileId, sizeof(pJoliet->achBibliographicFileId),
+ pThis->Joliet.pszBibliographicFileId);
+ rtFsIsoMakerTimespecToIso9660Timestamp(&pThis->ImageCreationTime, &pJoliet->BirthTime);
+ rtFsIsoMakerTimespecToIso9660Timestamp(&pThis->ImageCreationTime, &pJoliet->ModifyTime);
+ rtFsIsoMakerZero9660Timestamp(&pJoliet->ExpireTime);
+ rtFsIsoMakerZero9660Timestamp(&pJoliet->EffectiveTime);
+ pJoliet->bFileStructureVersion = ISO9660_FILE_STRUCTURE_VERSION;
+ //pJoliet->bReserved883 = 0;
+ //RT_ZERO(pJoliet->abAppUse);
+ //RT_ZERO(pJoliet->abReserved1396);
+ }
+
+ /*
+ * The ISO-9660 terminator descriptor.
+ */
+ pThis->pTerminatorVolDesc->bDescType = ISO9660VOLDESC_TYPE_TERMINATOR;
+ pThis->pTerminatorVolDesc->bDescVersion = 1;
+ memcpy(pThis->pTerminatorVolDesc->achStdId, ISO9660VOLDESC_STD_ID, sizeof(pThis->pTerminatorVolDesc->achStdId));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finalizes the volume descriptors.
+ *
+ * This will set the RTFSISOMAKERFILE::offData members.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker instance.
+ */
+static int rtFsIsoMakerFinalizeVolumeDescriptors(PRTFSISOMAKERINT pThis)
+{
+ AssertReturn(pThis->pbVolDescs && pThis->pPrimaryVolDesc && pThis->pTerminatorVolDesc, VERR_ISOMK_IPE_FINALIZE_1);
+
+ /*
+ * Primary descriptor.
+ */
+ PISO9660PRIMARYVOLDESC pPrimary = pThis->pPrimaryVolDesc;
+
+ pPrimary->VolumeSpaceSize.be = RT_H2BE_U32(pThis->cbFinalizedImage / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->VolumeSpaceSize.le = RT_H2LE_U32(pThis->cbFinalizedImage / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->cbPathTable.be = RT_H2BE_U32(pThis->PrimaryIsoDirs.cbPathTable);
+ pPrimary->cbPathTable.le = RT_H2LE_U32(pThis->PrimaryIsoDirs.cbPathTable);
+ pPrimary->offTypeLPathTable = RT_H2LE_U32(pThis->PrimaryIsoDirs.offPathTableL / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->offTypeMPathTable = RT_H2BE_U32(pThis->PrimaryIsoDirs.offPathTableM / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->RootDir.DirRec.cbDirRec = sizeof(pPrimary->RootDir);
+ pPrimary->RootDir.DirRec.cExtAttrBlocks = 0;
+ pPrimary->RootDir.DirRec.offExtent.be = RT_H2BE_U32(pThis->PrimaryIso.pRoot->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->RootDir.DirRec.offExtent.le = RT_H2LE_U32(pThis->PrimaryIso.pRoot->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pPrimary->RootDir.DirRec.cbData.be = RT_H2BE_U32(pThis->PrimaryIso.pRoot->pDir->cbDir);
+ pPrimary->RootDir.DirRec.cbData.le = RT_H2LE_U32(pThis->PrimaryIso.pRoot->pDir->cbDir);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pThis->PrimaryIso.pRoot->pObj->BirthTime, &pPrimary->RootDir.DirRec.RecTime);
+ pPrimary->RootDir.DirRec.fFileFlags = ISO9660_FILE_FLAGS_DIRECTORY;
+ pPrimary->RootDir.DirRec.bFileUnitSize = 0;
+ pPrimary->RootDir.DirRec.bInterleaveGapSize = 0;
+ pPrimary->RootDir.DirRec.VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pPrimary->RootDir.DirRec.VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pPrimary->RootDir.DirRec.bFileIdLength = 1;
+ pPrimary->RootDir.DirRec.achFileId[0] = 0x00;
+
+ /*
+ * Initialize the joliet descriptor if included.
+ */
+ PISO9660SUPVOLDESC pJoliet = pThis->pJolietVolDesc;
+ if (pJoliet)
+ {
+ pJoliet->VolumeSpaceSize = pPrimary->VolumeSpaceSize;
+ pJoliet->cbPathTable.be = RT_H2BE_U32(pThis->JolietDirs.cbPathTable);
+ pJoliet->cbPathTable.le = RT_H2LE_U32(pThis->JolietDirs.cbPathTable);
+ pJoliet->offTypeLPathTable = RT_H2LE_U32(pThis->JolietDirs.offPathTableL / RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->offTypeMPathTable = RT_H2BE_U32(pThis->JolietDirs.offPathTableM / RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->RootDir.DirRec.cbDirRec = sizeof(pJoliet->RootDir);
+ pJoliet->RootDir.DirRec.cExtAttrBlocks = 0;
+ pJoliet->RootDir.DirRec.offExtent.be = RT_H2BE_U32(pThis->Joliet.pRoot->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->RootDir.DirRec.offExtent.le = RT_H2LE_U32(pThis->Joliet.pRoot->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pJoliet->RootDir.DirRec.cbData.be = RT_H2BE_U32(pThis->Joliet.pRoot->pDir->cbDir);
+ pJoliet->RootDir.DirRec.cbData.le = RT_H2LE_U32(pThis->Joliet.pRoot->pDir->cbDir);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pThis->Joliet.pRoot->pObj->BirthTime, &pJoliet->RootDir.DirRec.RecTime);
+ pJoliet->RootDir.DirRec.fFileFlags = ISO9660_FILE_FLAGS_DIRECTORY;
+ pJoliet->RootDir.DirRec.bFileUnitSize = 0;
+ pJoliet->RootDir.DirRec.bInterleaveGapSize = 0;
+ pJoliet->RootDir.DirRec.VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pJoliet->RootDir.DirRec.VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pJoliet->RootDir.DirRec.bFileIdLength = 1;
+ pJoliet->RootDir.DirRec.achFileId[0] = 0x00;
+ }
+
+#if 0 /* this doesn't quite fool it. */
+ /*
+ * isomd5sum fake.
+ */
+ if (1)
+ {
+ uint8_t abDigest[RTMD5_HASH_SIZE];
+ if (pThis->cbSysArea == 0)
+ RTMd5(g_abRTZero4K, ISO9660_SECTOR_SIZE, abDigest);
+ else
+ {
+ RTMD5CONTEXT Ctx;
+ RTMd5Init(&Ctx);
+ RTMd5Update(&Ctx, pThis->pbSysArea, RT_MIN(pThis->cbSysArea, ISO9660_SECTOR_SIZE));
+ if (pThis->cbSysArea < ISO9660_SECTOR_SIZE)
+ RTMd5Update(&Ctx, g_abRTZero4K, ISO9660_SECTOR_SIZE - pThis->cbSysArea);
+ RTMd5Final(abDigest, &Ctx);
+ }
+ char szFakeHash[RTMD5_DIGEST_LEN + 1];
+ RTMd5ToString(abDigest, szFakeHash, sizeof(szFakeHash));
+
+ size_t cch = RTStrPrintf((char *)&pPrimary->abAppUse[0], sizeof(pPrimary->abAppUse),
+ "ISO MD5SUM = %s;SKIPSECTORS = %u;RHLISOSTATUS=1;THIS IS JUST A FAKE!",
+ szFakeHash, pThis->cbFinalizedImage / RTFSISOMAKER_SECTOR_SIZE - 1);
+ memset(&pPrimary->abAppUse[cch], ' ', sizeof(pPrimary->abAppUse) - cch);
+ }
+#endif
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finalizes the image.
+ *
+ * @returns IPRT status code.
+ * @param hIsoMaker The ISO maker handle.
+ */
+RTDECL(int) RTFsIsoMakerFinalize(RTFSISOMAKER hIsoMaker)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ /*
+ * Remove orphaned objects and allocate volume descriptors.
+ */
+ int rc = rtFsIsoMakerFinalizeRemoveOrphans(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(pThis->cObjects > 0, VERR_NO_DATA);
+
+ /* The primary ISO-9660 namespace must be explicitly disabled (for now),
+ so we return VERR_NO_DATA if no root dir. */
+ AssertReturn(pThis->PrimaryIso.pRoot || pThis->PrimaryIso.uLevel == 0, VERR_NO_DATA);
+
+ /* Automatically disable the joliet namespace if it is empty (no root dir). */
+ if (!pThis->Joliet.pRoot && pThis->Joliet.uLevel > 0)
+ {
+ pThis->Joliet.uLevel = 0;
+ pThis->cVolumeDescriptors--;
+ }
+
+ rc = rtFsIsoMakerFinalizePrepVolumeDescriptors(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * If there is any boot related stuff to be included, it ends up right after
+ * the descriptors.
+ */
+ uint64_t offData = _32K + pThis->cVolumeDescriptors * RTFSISOMAKER_SECTOR_SIZE;
+ rc = rtFsIsoMakerFinalizeBootStuffPart1(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Directories and path tables comes next.
+ */
+ rc = rtFsIsoMakerFinalizeDirectories(pThis, &offData);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Then we store the file data.
+ */
+ rc = rtFsIsoMakerFinalizeData(pThis, &offData);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbFinalizedImage = offData + pThis->cbImagePadding;
+
+ /*
+ * Do a 2nd pass over the boot stuff to finalize locations.
+ */
+ rc = rtFsIsoMakerFinalizeBootStuffPart2(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Finally, finalize the volume descriptors as they depend on some of the
+ * block allocations done in the previous steps.
+ */
+ rc = rtFsIsoMakerFinalizeVolumeDescriptors(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->fFinalized = true;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ }
+ return rc;
+}
+
+
+
+
+
+/*
+ *
+ * Image I/O.
+ * Image I/O.
+ * Image I/O.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Close(void *pvThis)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+
+ RTFsIsoMakerRelease(pThis->pIsoMaker);
+ pThis->pIsoMaker = NULL;
+
+ if (pThis->hVfsSrcFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pThis->hVfsSrcFile);
+ pThis->hVfsSrcFile = NIL_RTVFSFILE;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+ PRTFSISOMAKERINT pIsoMaker = pThis->pIsoMaker;
+
+
+ pObjInfo->cbObject = pIsoMaker->cbFinalizedImage;
+ pObjInfo->cbAllocated = pIsoMaker->cbFinalizedImage;
+ pObjInfo->AccessTime = pIsoMaker->ImageCreationTime;
+ pObjInfo->ModificationTime = pIsoMaker->ImageCreationTime;
+ pObjInfo->ChangeTime = pIsoMaker->ImageCreationTime;
+ pObjInfo->BirthTime = pIsoMaker->ImageCreationTime;
+ pObjInfo->Attr.fMode = 0444 | RTFS_TYPE_FILE | RTFS_DOS_READONLY;
+
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ enmAddAttr = RTFSOBJATTRADD_UNIX;
+ RT_FALL_THRU();
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
+ pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = 0;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+
+ default:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Generates the 'SL' records for a symbolic link.
+ *
+ * This is used both when generating directories records, spill file data and
+ * when creating the symbolic link.
+ *
+ * @returns Number of bytes produced. Negative IPRT status if buffer overflow.
+ * @param pszTarget The symbolic link target to encode.
+ * @param pbBuf The output buffer.
+ * @param cbBuf The size of the output buffer.
+ */
+static ssize_t rtFsIsoMakerOutFile_RockRidgeGenSL(const char *pszTarget, uint8_t *pbBuf, size_t cbBuf)
+{
+ Assert(*pszTarget != '\0');
+
+ PISO9660RRIPSL pEntry = (PISO9660RRIPSL)pbBuf;
+ pEntry->Hdr.bSig1 = ISO9660RRIPSL_SIG1;
+ pEntry->Hdr.bSig2 = ISO9660RRIPSL_SIG2;
+ pEntry->Hdr.cbEntry = 0; /* set later. */
+ pEntry->Hdr.bVersion = ISO9660RRIPSL_VER;
+ pEntry->fFlags = 0;
+ size_t offEntry = 0;
+ size_t off = RT_UOFFSETOF(ISO9660RRIPSL, abComponents);
+
+ /* Does it start with a root slash? */
+ if (RTPATH_IS_SLASH(*pszTarget))
+ {
+ pbBuf[off++] = ISO9660RRIP_SL_C_ROOT;
+ pbBuf[off++] = 0;
+ pszTarget++;
+ }
+
+ for (;;)
+ {
+ /* Find the end of the component. */
+ size_t cchComponent = 0;
+ char ch;
+ while ((ch = pszTarget[cchComponent]) != '\0' && !RTPATH_IS_SLASH(ch))
+ cchComponent++;
+
+ /* Check for dots and figure out how much space we need. */
+ uint8_t fFlags;
+ size_t cbNeeded;
+ if (cchComponent == 1 && *pszTarget == '.')
+ {
+ fFlags = ISO9660RRIP_SL_C_CURRENT;
+ cbNeeded = 2;
+ }
+ else if (cchComponent == 2 && pszTarget[0] == '.' && pszTarget[1] == '.')
+ {
+ fFlags = ISO9660RRIP_SL_C_PARENT;
+ cbNeeded = 2;
+ }
+ else
+ {
+ fFlags = 0;
+ cbNeeded = 2 + cchComponent;
+ }
+
+ /* Split the SL record if we're out of space. */
+ if ( off - offEntry + cbNeeded < UINT8_MAX
+ && off + cbNeeded <= cbBuf)
+ { /* likely */ }
+ else if (cbNeeded + RT_UOFFSETOF(ISO9660RRIPSL, abComponents) < UINT8_MAX)
+ {
+ AssertReturn(off + cbNeeded + RT_UOFFSETOF(ISO9660RRIPSL, abComponents) <= cbBuf, VERR_BUFFER_OVERFLOW);
+ Assert(off - offEntry < UINT8_MAX);
+ pEntry->Hdr.cbEntry = (uint8_t)(off - offEntry);
+ pEntry->fFlags |= ISO9660RRIP_SL_F_CONTINUE;
+
+ offEntry = off;
+ pEntry = (PISO9660RRIPSL)&pbBuf[off];
+ pEntry->Hdr.bSig1 = ISO9660RRIPSL_SIG1;
+ pEntry->Hdr.bSig2 = ISO9660RRIPSL_SIG2;
+ pEntry->Hdr.cbEntry = 0; /* set later. */
+ pEntry->Hdr.bVersion = ISO9660RRIPSL_VER;
+ pEntry->fFlags = 0;
+ }
+ else
+ {
+ /* Special case: component doesn't fit in a single SL entry. */
+ do
+ {
+ if (off - offEntry + 3 < UINT8_MAX)
+ {
+ size_t cchLeft = UINT8_MAX - 1 - (off - offEntry) - 2;
+ size_t cchToCopy = RT_MIN(cchLeft, cchComponent);
+ AssertReturn(off + 2 + cchToCopy <= cbBuf, VERR_BUFFER_OVERFLOW);
+ pbBuf[off++] = cchToCopy < cchComponent ? ISO9660RRIP_SL_C_CONTINUE : 0;
+ pbBuf[off++] = (uint8_t)cchToCopy;
+ memcpy(&pbBuf[off], pszTarget, cchToCopy);
+ off += cchToCopy;
+ pszTarget += cchToCopy;
+ cchComponent -= cchToCopy;
+ if (!cchComponent)
+ break;
+ }
+
+ Assert(off - offEntry < UINT8_MAX);
+ pEntry->Hdr.cbEntry = (uint8_t)(off - offEntry);
+ pEntry->fFlags |= ISO9660RRIP_SL_F_CONTINUE;
+
+ AssertReturn(off + 2 + cchComponent + RT_UOFFSETOF(ISO9660RRIPSL, abComponents) <= cbBuf, VERR_BUFFER_OVERFLOW);
+ offEntry = off;
+ pEntry = (PISO9660RRIPSL)&pbBuf[off];
+ pEntry->Hdr.bSig1 = ISO9660RRIPSL_SIG1;
+ pEntry->Hdr.bSig2 = ISO9660RRIPSL_SIG2;
+ pEntry->Hdr.cbEntry = 0; /* set later. */
+ pEntry->Hdr.bVersion = ISO9660RRIPSL_VER;
+ pEntry->fFlags = 0;
+ } while (cchComponent > 0);
+ if (ch == '\0')
+ break;
+ pszTarget++;
+ continue;
+ }
+
+ /* Produce the record. */
+ pbBuf[off++] = fFlags;
+ pbBuf[off++] = (uint8_t)(cbNeeded - 2);
+ if (cchComponent > 0)
+ {
+ memcpy(&pbBuf[off], pszTarget, cbNeeded - 2);
+ off += cbNeeded - 2;
+ }
+
+ if (ch == '\0')
+ break;
+ pszTarget += cchComponent + 1;
+ }
+
+ Assert(off - offEntry < UINT8_MAX);
+ pEntry->Hdr.cbEntry = (uint8_t)(off - offEntry);
+ return off;
+}
+
+
+/**
+ * Generates rock ridge data.
+ *
+ * This is used both for the directory record and for the spill file ('CE').
+ *
+ * @param pName The name to generate rock ridge info for.
+ * @param pbSys The output buffer.
+ * @param cbSys The size of the output buffer.
+ * @param fInSpill Indicates whether we're in a spill file (true) or
+ * directory record (false).
+ * @param enmDirType The kind of directory entry this is.
+ */
+static void rtFsIosMakerOutFile_GenerateRockRidge(PRTFSISOMAKERNAME pName, uint8_t *pbSys, size_t cbSys,
+ bool fInSpill, RTFSISOMAKERDIRTYPE enmDirType)
+{
+ /*
+ * Deal with records specific to the root directory '.' entry.
+ */
+ if (pName->pParent != NULL)
+ { /* likely */ }
+ else
+ {
+ if (!fInSpill)
+ {
+ PISO9660SUSPSP pSP = (PISO9660SUSPSP)pbSys;
+ Assert(cbSys >= sizeof(*pSP));
+ pSP->Hdr.bSig1 = ISO9660SUSPSP_SIG1;
+ pSP->Hdr.bSig2 = ISO9660SUSPSP_SIG2;
+ pSP->Hdr.cbEntry = ISO9660SUSPSP_LEN;
+ pSP->Hdr.bVersion = ISO9660SUSPSP_VER;
+ pSP->bCheck1 = ISO9660SUSPSP_CHECK1;
+ pSP->bCheck2 = ISO9660SUSPSP_CHECK2;
+ pSP->cbSkip = 0;
+ pbSys += sizeof(*pSP);
+ cbSys -= sizeof(*pSP);
+ }
+ if (pName->fRockNeedER)
+ {
+ PISO9660SUSPER pER = (PISO9660SUSPER)pbSys;
+ Assert(cbSys >= ISO9660_RRIP_ER_LEN);
+ AssertCompile(ISO9660_RRIP_ER_LEN < UINT8_MAX);
+ pER->Hdr.bSig1 = ISO9660SUSPER_SIG1;
+ pER->Hdr.bSig2 = ISO9660SUSPER_SIG2;
+ pER->Hdr.cbEntry = ISO9660_RRIP_ER_LEN;
+ pER->Hdr.bVersion = ISO9660SUSPER_VER;
+ pER->cchIdentifier = sizeof(ISO9660_RRIP_ID) - 1;
+ pER->cchDescription = sizeof(ISO9660_RRIP_DESC) - 1;
+ pER->cchSource = sizeof(ISO9660_RRIP_SRC) - 1;
+ pER->bVersion = ISO9660_RRIP_VER;
+ char *pchDst = &pER->achPayload[0]; /* we do this to shut up annoying clang. */
+ memcpy(pchDst, RT_STR_TUPLE(ISO9660_RRIP_ID));
+ pchDst += sizeof(ISO9660_RRIP_ID) - 1;
+ memcpy(pchDst, RT_STR_TUPLE(ISO9660_RRIP_DESC));
+ pchDst += sizeof(ISO9660_RRIP_DESC) - 1;
+ memcpy(pchDst, RT_STR_TUPLE(ISO9660_RRIP_SRC));
+ pbSys += ISO9660_RRIP_ER_LEN;
+ cbSys -= ISO9660_RRIP_ER_LEN;
+ }
+ }
+
+ /*
+ * Deal with common stuff.
+ */
+ if (!fInSpill ? pName->fRockNeedRRInDirRec : pName->fRockNeedRRInSpill)
+ {
+ PISO9660RRIPRR pRR = (PISO9660RRIPRR)pbSys;
+ Assert(cbSys >= sizeof(*pRR));
+ pRR->Hdr.bSig1 = ISO9660RRIPRR_SIG1;
+ pRR->Hdr.bSig2 = ISO9660RRIPRR_SIG2;
+ pRR->Hdr.cbEntry = ISO9660RRIPRR_LEN;
+ pRR->Hdr.bVersion = ISO9660RRIPRR_VER;
+ pRR->fFlags = pName->fRockEntries;
+ pbSys += sizeof(*pRR);
+ cbSys -= sizeof(*pRR);
+ }
+
+ /*
+ * The following entries all end up in the spill or fully in
+ * the directory record.
+ */
+ if (fInSpill || pName->cbRockSpill == 0)
+ {
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_PX)
+ {
+ PISO9660RRIPPX pPX = (PISO9660RRIPPX)pbSys;
+ Assert(cbSys >= sizeof(*pPX));
+ pPX->Hdr.bSig1 = ISO9660RRIPPX_SIG1;
+ pPX->Hdr.bSig2 = ISO9660RRIPPX_SIG2;
+ pPX->Hdr.cbEntry = ISO9660RRIPPX_LEN;
+ pPX->Hdr.bVersion = ISO9660RRIPPX_VER;
+ pPX->fMode.be = RT_H2BE_U32((uint32_t)(pName->fMode & RTFS_UNIX_MASK));
+ pPX->fMode.le = RT_H2LE_U32((uint32_t)(pName->fMode & RTFS_UNIX_MASK));
+ pPX->cHardlinks.be = RT_H2BE_U32((uint32_t)pName->cHardlinks);
+ pPX->cHardlinks.le = RT_H2LE_U32((uint32_t)pName->cHardlinks);
+ pPX->uid.be = RT_H2BE_U32((uint32_t)pName->uid);
+ pPX->uid.le = RT_H2LE_U32((uint32_t)pName->uid);
+ pPX->gid.be = RT_H2BE_U32((uint32_t)pName->gid);
+ pPX->gid.le = RT_H2LE_U32((uint32_t)pName->gid);
+#if 0 /* This is confusing solaris. Looks like it has code assuming inode numbers are block numbers and ends up mistaking files for the root dir. Sigh. */
+ pPX->INode.be = RT_H2BE_U32((uint32_t)pName->pObj->idxObj + 1); /* Don't use zero - isoinfo doesn't like it. */
+ pPX->INode.le = RT_H2LE_U32((uint32_t)pName->pObj->idxObj + 1);
+#else
+ pPX->INode.be = 0;
+ pPX->INode.le = 0;
+#endif
+ pbSys += sizeof(*pPX);
+ cbSys -= sizeof(*pPX);
+ }
+
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_TF)
+ {
+ PISO9660RRIPTF pTF = (PISO9660RRIPTF)pbSys;
+ pTF->Hdr.bSig1 = ISO9660RRIPTF_SIG1;
+ pTF->Hdr.bSig2 = ISO9660RRIPTF_SIG2;
+ pTF->Hdr.cbEntry = Iso9660RripTfCalcLength(ISO9660RRIPTF_F_BIRTH | ISO9660RRIPTF_F_MODIFY | ISO9660RRIPTF_F_ACCESS | ISO9660RRIPTF_F_CHANGE);
+ Assert(cbSys >= pTF->Hdr.cbEntry);
+ pTF->Hdr.bVersion = ISO9660RRIPTF_VER;
+ pTF->fFlags = ISO9660RRIPTF_F_BIRTH | ISO9660RRIPTF_F_MODIFY | ISO9660RRIPTF_F_ACCESS | ISO9660RRIPTF_F_CHANGE;
+ PISO9660RECTIMESTAMP paTimestamps = (PISO9660RECTIMESTAMP)&pTF->abPayload[0];
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pName->pObj->BirthTime, &paTimestamps[0]);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pName->pObj->ModificationTime, &paTimestamps[1]);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pName->pObj->AccessedTime, &paTimestamps[2]);
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pName->pObj->ChangeTime, &paTimestamps[3]);
+ cbSys -= pTF->Hdr.cbEntry;
+ pbSys += pTF->Hdr.cbEntry;
+ }
+
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_PN)
+ {
+ PISO9660RRIPPN pPN = (PISO9660RRIPPN)pbSys;
+ Assert(cbSys >= sizeof(*pPN));
+ pPN->Hdr.bSig1 = ISO9660RRIPPN_SIG1;
+ pPN->Hdr.bSig2 = ISO9660RRIPPN_SIG2;
+ pPN->Hdr.cbEntry = ISO9660RRIPPN_LEN;
+ pPN->Hdr.bVersion = ISO9660RRIPPN_VER;
+ pPN->Major.be = RT_H2BE_U32((uint32_t)RTDEV_MAJOR(pName->Device));
+ pPN->Major.le = RT_H2LE_U32((uint32_t)RTDEV_MAJOR(pName->Device));
+ pPN->Minor.be = RT_H2BE_U32((uint32_t)RTDEV_MINOR(pName->Device));
+ pPN->Minor.le = RT_H2LE_U32((uint32_t)RTDEV_MINOR(pName->Device));
+ cbSys -= sizeof(*pPN);
+ pbSys += sizeof(*pPN);
+ }
+
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_NM)
+ {
+ size_t cchSrc = pName->cchRockRidgeNm;
+ const char *pszSrc = pName->pszRockRidgeNm;
+ for (;;)
+ {
+ size_t cchThis = RT_MIN(cchSrc, ISO9660RRIPNM_MAX_NAME_LEN);
+ PISO9660RRIPNM pNM = (PISO9660RRIPNM)pbSys;
+ Assert(cbSys >= RT_UOFFSETOF_DYN(ISO9660RRIPNM, achName[cchThis]));
+ pNM->Hdr.bSig1 = ISO9660RRIPNM_SIG1;
+ pNM->Hdr.bSig2 = ISO9660RRIPNM_SIG2;
+ pNM->Hdr.cbEntry = (uint8_t)(RT_UOFFSETOF(ISO9660RRIPNM, achName) + cchThis);
+ pNM->Hdr.bVersion = ISO9660RRIPNM_VER;
+ pNM->fFlags = cchThis == cchSrc ? 0 : ISO9660RRIP_NM_F_CONTINUE;
+ /** @todo r=bird: This only works when not using the spill file. The spill
+ * file entry will be shared between the original and all the '.' and
+ * '..' entries. FreeBSD gets confused by this w/o the
+ * ISO9660RRIP_NM_F_CURRENT and ISO9660RRIP_NM_F_PARENT flags. */
+ if (enmDirType == RTFSISOMAKERDIRTYPE_CURRENT)
+ pNM->fFlags |= ISO9660RRIP_NM_F_CURRENT;
+ else if (enmDirType == RTFSISOMAKERDIRTYPE_PARENT)
+ pNM->fFlags |= ISO9660RRIP_NM_F_PARENT;
+ memcpy(&pNM->achName[0], pszSrc, cchThis);
+ pbSys += RT_UOFFSETOF(ISO9660RRIPNM, achName) + cchThis;
+ cbSys -= RT_UOFFSETOF(ISO9660RRIPNM, achName) + cchThis;
+ cchSrc -= cchThis;
+ if (!cchSrc)
+ break;
+ }
+ }
+
+ if (pName->fRockEntries & ISO9660RRIP_RR_F_SL)
+ {
+ AssertReturnVoid(pName->pObj->enmType == RTFSISOMAKEROBJTYPE_SYMLINK);
+ PCRTFSISOMAKERSYMLINK pSymlink = (PCRTFSISOMAKERSYMLINK)pName->pObj;
+
+ ssize_t cbSlRockRidge = rtFsIsoMakerOutFile_RockRidgeGenSL(pSymlink->szTarget, pbSys, cbSys);
+ AssertReturnVoid(cbSlRockRidge > 0);
+ Assert(cbSys >= (size_t)cbSlRockRidge);
+ pbSys += (size_t)cbSlRockRidge;
+ cbSys -= (size_t)cbSlRockRidge;
+ }
+ }
+
+ /* finally, zero padding. */
+ if (cbSys & 1)
+ {
+ *pbSys++ = '\0';
+ cbSys--;
+ }
+
+ Assert(!fInSpill ? cbSys == 0 : cbSys < _2G);
+}
+
+
+
+
+/**
+ * Reads one or more sectors from a rock ridge spill file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker output file instance. We use the
+ * directory pointer hints and child index hints
+ * @param pIsoMaker The ISO maker.
+ * @param pFile The rock ridge spill file.
+ * @param offInFile The offset into the spill file. This is sector aligned.
+ * @param pbBuf The output buffer.
+ * @param cbToRead The number of bytes to tread. This is sector aligned.
+ */
+static int rtFsIsoMakerOutFile_RockRidgeSpillReadSectors(PRTFSISOMAKEROUTPUTFILE pThis, PRTFSISOMAKERINT pIsoMaker,
+ PRTFSISOMAKERFILE pFile, uint32_t offInFile, uint8_t *pbBuf,
+ size_t cbToRead)
+{
+ /*
+ * We're only working multiple of ISO 9660 sectors.
+ *
+ * The spill of one directory record will always fit entirely within a
+ * sector, we make sure about that during finalization. There may be
+ * zero padding between spill data sequences, especially on the sector
+ * boundrary.
+ */
+ Assert((offInFile & ISO9660_SECTOR_OFFSET_MASK) == 0);
+ Assert((cbToRead & ISO9660_SECTOR_OFFSET_MASK) == 0);
+ Assert(cbToRead >= ISO9660_SECTOR_SIZE);
+
+ /*
+ * We generate a sector at a time.
+ *
+ * So, we start by locating the first directory/child in the block offInFile
+ * is pointing to.
+ */
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs;
+ PRTFSISOMAKERNAMEDIR *ppDirHint;
+ uint32_t *pidxChildHint;
+ if (pFile->u.pRockSpillNamespace->fNamespace & RTFSISOMAKER_NAMESPACE_ISO_9660)
+ {
+ pFinalizedDirs = &pIsoMaker->PrimaryIsoDirs;
+ ppDirHint = &pThis->pDirHintPrimaryIso;
+ pidxChildHint = &pThis->iChildPrimaryIso;
+ }
+ else
+ {
+ pFinalizedDirs = &pIsoMaker->JolietDirs;
+ ppDirHint = &pThis->pDirHintJoliet;
+ pidxChildHint = &pThis->iChildJoliet;
+ }
+
+ /* Special case: '.' record in root dir */
+ uint32_t idxChild = *pidxChildHint;
+ PRTFSISOMAKERNAMEDIR pDir = *ppDirHint;
+ if ( offInFile == 0
+ && (pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry)) != NULL
+ && pDir->pName->cbRockSpill > 0)
+ {
+ AssertReturn(pDir, VERR_ISOMK_IPE_RR_READ);
+ AssertReturn(pDir->pName->offRockSpill == 0, VERR_ISOMK_IPE_RR_READ);
+ idxChild = 0;
+ }
+ else
+ {
+ /* Establish where to start searching from. */
+ if ( !pDir
+ || idxChild >= pDir->cChildren
+ || pDir->papChildren[idxChild]->cbRockSpill == 0)
+ {
+ idxChild = 0;
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturn(pDir, VERR_ISOMK_IPE_RR_READ);
+ }
+
+ if (pDir->papChildren[idxChild]->offRockSpill == offInFile)
+ { /* hit, no need to search */ }
+ else if (pDir->papChildren[idxChild]->offRockSpill < offInFile)
+ {
+ /* search forwards */
+ for (;;)
+ {
+ idxChild++;
+ while ( idxChild < pDir->cChildren
+ && ( pDir->papChildren[idxChild]->offRockSpill < offInFile
+ || pDir->papChildren[idxChild]->cbRockSpill == 0) )
+ idxChild++;
+ if (idxChild < pDir->cChildren)
+ break;
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturn(pDir, VERR_ISOMK_IPE_RR_READ);
+ }
+ Assert(pDir->papChildren[idxChild]->offRockSpill == offInFile);
+ }
+ else
+ {
+ /* search backwards (no root dir concerns here) */
+ for (;;)
+ {
+ while ( idxChild > 0
+ && ( pDir->papChildren[idxChild - 1]->offRockSpill >= offInFile
+ || pDir->papChildren[idxChild - 1]->cbRockSpill == 0) )
+ idxChild--;
+ if (pDir->papChildren[idxChild]->offRockSpill == offInFile)
+ break;
+ pDir = RTListGetPrev(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturn(pDir, VERR_ISOMK_IPE_RR_READ);
+ }
+ Assert(pDir->papChildren[idxChild]->offRockSpill == offInFile);
+ }
+ }
+
+ /*
+ * Produce data.
+ */
+ while (cbToRead > 0)
+ {
+ PRTFSISOMAKERNAME pChild;
+ if ( offInFile > 0
+ || pDir->pName->cbRockSpill == 0
+ || pDir->pName->pParent != NULL)
+ {
+ pChild = pDir->papChildren[idxChild];
+ AssertReturn(pChild->offRockSpill == offInFile, VERR_ISOMK_IPE_RR_READ);
+ AssertReturn(pChild->cbRockSpill > 0, VERR_ISOMK_IPE_RR_READ);
+ idxChild++;
+ }
+ else
+ { /* root dir special case. */
+ pChild = pDir->pName;
+ Assert(idxChild == 0);
+ Assert(pChild->pParent == NULL);
+ }
+
+ AssertReturn(cbToRead >= pChild->cbRockSpill, VERR_ISOMK_IPE_RR_READ);
+ /** @todo r=bird: using RTFSISOMAKERDIRTYPE_OTHER is correct as we don't seem to
+ * have separate name entries for '.' and '..'. However it means that if
+ * any directory ends up in the spill file we'll end up with the wrong
+ * data for the '.' and '..' entries. [RR NM ./..] */
+ rtFsIosMakerOutFile_GenerateRockRidge(pDir->pName, pbBuf, cbToRead, true /*fInSpill*/, RTFSISOMAKERDIRTYPE_OTHER);
+ cbToRead -= pChild->cbRockSpill;
+ pbBuf += pChild->cbRockSpill;
+ offInFile += pChild->cbRockSpill;
+
+ /* Advance to the next name, if any. */
+ uint32_t offNext = UINT32_MAX;
+ do
+ {
+ while (idxChild < pDir->cChildren)
+ {
+ pChild = pDir->papChildren[idxChild];
+ if (pChild->cbRockSpill == 0)
+ Assert(pChild->offRockSpill == UINT32_MAX);
+ else
+ {
+ offNext = pChild->offRockSpill;
+ AssertReturn(offNext >= offInFile, VERR_ISOMK_IPE_RR_READ);
+ AssertReturn(offNext < pFile->cbData, VERR_ISOMK_IPE_RR_READ);
+ break;
+ }
+ idxChild++;
+ }
+ if (offNext != UINT32_MAX)
+ break;
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ idxChild = 0;
+ } while (pDir != NULL);
+
+ if (offNext != UINT32_MAX)
+ {
+ uint32_t cbToZero = offNext - offInFile;
+ if (cbToRead > cbToZero)
+ RT_BZERO(pbBuf, cbToZero);
+ else
+ {
+ RT_BZERO(pbBuf, cbToRead);
+ *ppDirHint = pDir;
+ *pidxChildHint = idxChild;
+ break;
+ }
+ cbToRead -= cbToZero;
+ pbBuf += cbToZero;
+ offInFile += cbToZero;
+ }
+ else
+ {
+ RT_BZERO(pbBuf, cbToRead);
+ *ppDirHint = NULL;
+ *pidxChildHint = UINT32_MAX;
+ break;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with reads that aren't an exact multiple of sectors.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker output file instance. We use the
+ * directory pointer hints and child index hints
+ * @param pIsoMaker The ISO maker.
+ * @param pFile The rock ridge spill file.
+ * @param offInFile The offset into the spill file.
+ * @param pbBuf The output buffer.
+ * @param cbToRead The number of bytes to tread.
+ */
+static int rtFsIsoMakerOutFile_RockRidgeSpillReadUnaligned(PRTFSISOMAKEROUTPUTFILE pThis, PRTFSISOMAKERINT pIsoMaker,
+ PRTFSISOMAKERFILE pFile, uint32_t offInFile, uint8_t *pbBuf,
+ uint32_t cbToRead)
+{
+ for (;;)
+ {
+ /*
+ * Deal with unnaligned file offsets and sub-sector sized reads.
+ */
+ if ( (offInFile & ISO9660_SECTOR_OFFSET_MASK)
+ || cbToRead < ISO9660_SECTOR_SIZE)
+ {
+ uint8_t abSectorBuf[ISO9660_SECTOR_SIZE];
+ int rc = rtFsIsoMakerOutFile_RockRidgeSpillReadSectors(pThis, pIsoMaker, pFile,
+ offInFile & ~(uint32_t)ISO9660_SECTOR_OFFSET_MASK,
+ abSectorBuf, sizeof(abSectorBuf));
+ if (RT_FAILURE(rc))
+ return rc;
+ uint32_t offSrcBuf = (size_t)offInFile & (size_t)ISO9660_SECTOR_OFFSET_MASK;
+ uint32_t cbToCopy = RT_MIN(ISO9660_SECTOR_SIZE - offSrcBuf, cbToRead);
+ memcpy(pbBuf, &abSectorBuf[offSrcBuf], cbToCopy);
+ if (cbToCopy >= cbToRead)
+ return VINF_SUCCESS;
+ cbToRead -= cbToCopy;
+ offInFile += cbToCopy;
+ pbBuf += cbToCopy;
+ }
+
+ /*
+ * The offset is aligned now, so try read some sectors directly into the buffer.
+ */
+ AssertContinue((offInFile & ISO9660_SECTOR_OFFSET_MASK) == 0);
+ if (cbToRead >= ISO9660_SECTOR_SIZE)
+ {
+ uint32_t cbFullSectors = cbToRead & ~(uint32_t)ISO9660_SECTOR_OFFSET_MASK;
+ int rc = rtFsIsoMakerOutFile_RockRidgeSpillReadSectors(pThis, pIsoMaker, pFile, offInFile, pbBuf, cbFullSectors);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbFullSectors >= cbToRead)
+ return VINF_SUCCESS;
+ cbToRead -= cbFullSectors;
+ offInFile += cbFullSectors;
+ pbBuf += cbFullSectors;
+ }
+ }
+}
+
+
+
+/**
+ * Produces the content of a TRANS.TBL file as a memory file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO maker output file instance. The file is
+ * returned as pThis->hVfsSrcFile.
+ * @param pFile The TRANS.TBL file.
+ */
+static int rtFsIsoMakerOutFile_ProduceTransTbl(PRTFSISOMAKEROUTPUTFILE pThis, PRTFSISOMAKERFILE pFile)
+{
+ /*
+ * Create memory file instance.
+ */
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, pFile->cbData, &hVfsFile);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Produce the file content.
+ */
+ PRTFSISOMAKERNAME *ppChild = pFile->u.pTransTblDir->pDir->papChildren;
+ uint32_t cLeft = pFile->u.pTransTblDir->pDir->cChildren;
+ while (cLeft-- > 0)
+ {
+ PRTFSISOMAKERNAME pChild = *ppChild++;
+ if (pChild->cchTransNm)
+ {
+ /** @todo TRANS.TBL codeset, currently using UTF-8 which is probably not it.
+ * However, nobody uses this stuff any more, so who cares. */
+ char szEntry[RTFSISOMAKER_MAX_NAME_BUF * 2 + 128];
+ size_t cchEntry = RTStrPrintf(szEntry, sizeof(szEntry), "%c %-*s\t%s\n", pChild->pDir ? 'D' : 'F',
+ RTFSISOMAKER_TRANS_TBL_LEFT_PAD, pChild->szName, pChild->pszTransNm);
+ rc = RTVfsFileWrite(hVfsFile, szEntry, cchEntry, NULL);
+ if (RT_FAILURE(rc))
+ {
+ RTVfsFileRelease(hVfsFile);
+ return rc;
+ }
+ }
+ }
+
+ /*
+ * Check that the size matches our estimate.
+ */
+ uint64_t cbResult = 0;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbResult);
+ if (RT_SUCCESS(rc) && cbResult == pFile->cbData)
+ {
+ pThis->hVfsSrcFile = hVfsFile;
+ return VINF_SUCCESS;
+ }
+
+ AssertMsgFailed(("rc=%Rrc, cbResult=%#RX64 cbData=%#RX64\n", rc, cbResult, pFile->cbData));
+ RTVfsFileRelease(hVfsFile);
+ return VERR_ISOMK_IPE_PRODUCE_TRANS_TBL;
+}
+
+
+
+/**
+ * Reads file data.
+ *
+ * @returns IPRT status code
+ * @param pThis The instance data for the VFS file. We use this to
+ * keep hints about where we are and we which source
+ * file we've opened/created.
+ * @param pIsoMaker The ISO maker instance.
+ * @param offUnsigned The ISO image byte offset of the requested data.
+ * @param pbBuf The output buffer.
+ * @param cbBuf How much to read.
+ * @param pcbDone Where to return how much was read.
+ */
+static int rtFsIsoMakerOutFile_ReadFileData(PRTFSISOMAKEROUTPUTFILE pThis, PRTFSISOMAKERINT pIsoMaker, uint64_t offUnsigned,
+ uint8_t *pbBuf, size_t cbBuf, size_t *pcbDone)
+{
+ *pcbDone = 0;
+
+ /*
+ * Figure out which file. We keep a hint in the instance.
+ */
+ uint64_t offInFile;
+ PRTFSISOMAKERFILE pFile = pThis->pFileHint;
+ if (!pFile)
+ {
+ pFile = RTListGetFirst(&pIsoMaker->FinalizedFiles, RTFSISOMAKERFILE, FinalizedEntry);
+ AssertReturn(pFile, VERR_ISOMK_IPE_READ_FILE_DATA_1);
+ }
+ if ((offInFile = offUnsigned - pFile->offData) < RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE))
+ { /* hit */ }
+ else if (offUnsigned >= pFile->offData)
+ {
+ /* Seek forwards. */
+ do
+ {
+ pFile = RTListGetNext(&pIsoMaker->FinalizedFiles, pFile, RTFSISOMAKERFILE, FinalizedEntry);
+ AssertReturn(pFile, VERR_ISOMK_IPE_READ_FILE_DATA_2);
+ } while ((offInFile = offUnsigned - pFile->offData) >= RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE));
+ }
+ else
+ {
+ /* Seek backwards. */
+ do
+ {
+ pFile = RTListGetPrev(&pIsoMaker->FinalizedFiles, pFile, RTFSISOMAKERFILE, FinalizedEntry);
+ AssertReturn(pFile, VERR_ISOMK_IPE_READ_FILE_DATA_3);
+ } while ((offInFile = offUnsigned - pFile->offData) >= RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE));
+ }
+
+ /*
+ * Update the hint/current file.
+ */
+ if (pThis->pFileHint != pFile)
+ {
+ pThis->pFileHint = pFile;
+ if (pThis->hVfsSrcFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pThis->hVfsSrcFile);
+ pThis->hVfsSrcFile = NIL_RTVFSFILE;
+ }
+ }
+
+ /*
+ * Produce data bits according to the source type.
+ */
+ if (offInFile < pFile->cbData)
+ {
+ int rc;
+ size_t cbToRead = RT_MIN(cbBuf, pFile->cbData - offInFile);
+
+ switch (pFile->enmSrcType)
+ {
+ case RTFSISOMAKERSRCTYPE_PATH:
+ if (pThis->hVfsSrcFile == NIL_RTVFSFILE)
+ {
+ rc = RTVfsChainOpenFile(pFile->u.pszSrcPath, RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ &pThis->hVfsSrcFile, NULL, NULL);
+ AssertMsgRCReturn(rc, ("%s -> %Rrc\n", pFile->u.pszSrcPath, rc), rc);
+ }
+ rc = RTVfsFileReadAt(pThis->hVfsSrcFile, offInFile, pbBuf, cbToRead, NULL);
+ AssertRC(rc);
+ break;
+
+ case RTFSISOMAKERSRCTYPE_VFS_FILE:
+ rc = RTVfsFileReadAt(pFile->u.hVfsFile, offInFile, pbBuf, cbToRead, NULL);
+ AssertRC(rc);
+ break;
+
+ case RTFSISOMAKERSRCTYPE_COMMON:
+ rc = RTVfsFileReadAt(pIsoMaker->paCommonSources[pFile->u.Common.idxSrc],
+ pFile->u.Common.offData + offInFile, pbBuf, cbToRead, NULL);
+ AssertRC(rc);
+ break;
+
+ case RTFSISOMAKERSRCTYPE_TRANS_TBL:
+ if (pThis->hVfsSrcFile == NIL_RTVFSFILE)
+ {
+ rc = rtFsIsoMakerOutFile_ProduceTransTbl(pThis, pFile);
+ AssertRCReturn(rc, rc);
+ }
+ rc = RTVfsFileReadAt(pThis->hVfsSrcFile, offInFile, pbBuf, cbToRead, NULL);
+ AssertRC(rc);
+ break;
+
+ case RTFSISOMAKERSRCTYPE_RR_SPILL:
+ Assert(pFile->cbData < UINT32_MAX);
+ if ( !(offInFile & ISO9660_SECTOR_OFFSET_MASK)
+ && !(cbToRead & ISO9660_SECTOR_OFFSET_MASK)
+ && cbToRead > 0)
+ rc = rtFsIsoMakerOutFile_RockRidgeSpillReadSectors(pThis, pIsoMaker, pFile, (uint32_t)offInFile,
+ pbBuf, (uint32_t)cbToRead);
+ else
+ rc = rtFsIsoMakerOutFile_RockRidgeSpillReadUnaligned(pThis, pIsoMaker, pFile, (uint32_t)offInFile,
+ pbBuf, (uint32_t)cbToRead);
+ break;
+
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ *pcbDone = cbToRead;
+
+ /*
+ * Do boot info table patching.
+ */
+ if ( pFile->pBootInfoTable
+ && offInFile < 64
+ && offInFile + cbToRead > 8)
+ {
+ size_t offInBuf = offInFile < 8 ? 8 - (size_t)offInFile : 0;
+ size_t offInTab = offInFile <= 8 ? 0 : (size_t)offInFile - 8;
+ size_t cbToCopy = RT_MIN(sizeof(*pFile->pBootInfoTable) - offInTab, cbToRead - offInBuf);
+ memcpy(&pbBuf[offInBuf], (uint8_t *)pFile->pBootInfoTable + offInTab, cbToCopy);
+ }
+
+ /*
+ * Check if we're into the zero padding at the end of the file now.
+ */
+ if ( cbToRead < cbBuf
+ && (pFile->cbData & RTFSISOMAKER_SECTOR_OFFSET_MASK)
+ && offInFile + cbToRead == pFile->cbData)
+ {
+ cbBuf -= cbToRead;
+ pbBuf += cbToRead;
+ size_t cbZeros = RT_MIN(cbBuf, RTFSISOMAKER_SECTOR_SIZE - (pFile->cbData & RTFSISOMAKER_SECTOR_OFFSET_MASK));
+ memset(pbBuf, 0, cbZeros);
+ *pcbDone += cbZeros;
+ }
+ }
+ else
+ {
+ size_t cbZeros = RT_MIN(cbBuf, RT_ALIGN_64(pFile->cbData, RTFSISOMAKER_SECTOR_SIZE) - offInFile);
+ memset(pbBuf, 0, cbZeros);
+ *pcbDone = cbZeros;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Generates ISO-9660 path table record into the specified buffer.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The directory namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param pbBuf The buffer. This is large enough to hold the path
+ * record (use RTFSISOMAKER_CALC_PATHREC_SIZE) and a zero
+ * RTUTF16 terminator if @a fUnicode is true.
+ */
+static uint32_t rtFsIsoMakerOutFile_GeneratePathRec(PRTFSISOMAKERNAME pName, bool fUnicode, bool fLittleEndian, uint8_t *pbBuf)
+{
+ PISO9660PATHREC pPathRec = (PISO9660PATHREC)pbBuf;
+ pPathRec->cbDirId = pName->cbNameInDirRec;
+ pPathRec->cbExtAttr = 0;
+ if (fLittleEndian)
+ {
+ pPathRec->offExtent = RT_H2LE_U32(pName->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pPathRec->idParentRec = RT_H2LE_U16(pName->pParent ? pName->pParent->pDir->idPathTable : 1);
+ }
+ else
+ {
+ pPathRec->offExtent = RT_H2BE_U32(pName->pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pPathRec->idParentRec = RT_H2BE_U16(pName->pParent ? pName->pParent->pDir->idPathTable : 1);
+ }
+ if (!fUnicode)
+ {
+ memcpy(&pPathRec->achDirId[0], pName->szName, pName->cbNameInDirRec);
+ if (pName->cbNameInDirRec & 1)
+ pPathRec->achDirId[pName->cbNameInDirRec] = '\0';
+ }
+ else
+ {
+ /* Caller made sure there is space for a zero terminator character. */
+ PRTUTF16 pwszTmp = (PRTUTF16)&pPathRec->achDirId[0];
+ size_t cwcResult = 0;
+ int rc = RTStrToUtf16BigEx(pName->szName, RTSTR_MAX, &pwszTmp, pName->cbNameInDirRec / sizeof(RTUTF16) + 1, &cwcResult);
+ AssertRC(rc);
+ Assert( cwcResult * sizeof(RTUTF16) == pName->cbNameInDirRec
+ || (!pName->pParent && cwcResult == 0 && pName->cbNameInDirRec == 1) );
+
+ }
+ return RTFSISOMAKER_CALC_PATHREC_SIZE(pName->cbNameInDirRec);
+}
+
+
+/**
+ * Deals with situations where the destination buffer doesn't cover the whole
+ * path table record.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The directory namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param offInRec The offset into the path table record.
+ * @param pbBuf The buffer.
+ * @param cbBuf The buffer size.
+ */
+static uint32_t rtFsIsoMakerOutFile_GeneratePathRecPartial(PRTFSISOMAKERNAME pName, bool fUnicode, bool fLittleEndian,
+ uint32_t offInRec, uint8_t *pbBuf, size_t cbBuf)
+{
+ uint8_t abTmpRec[256];
+ size_t cbToCopy = rtFsIsoMakerOutFile_GeneratePathRec(pName, fUnicode, fLittleEndian, abTmpRec);
+ cbToCopy = RT_MIN(cbBuf, cbToCopy - offInRec);
+ memcpy(pbBuf, &abTmpRec[offInRec], cbToCopy);
+ return (uint32_t)cbToCopy;
+}
+
+
+/**
+ * Generate path table records.
+ *
+ * This will generate record up to the end of the table. However, it will not
+ * supply the zero padding in the last sector, the caller is expected to take
+ * care of that.
+ *
+ * @returns Number of bytes written to the buffer.
+ * @param ppDirHint Pointer to the directory hint for the namespace.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param fLittleEndian Set if we're generating little endian records, clear
+ * if big endian records.
+ * @param offInTable Offset into the path table.
+ * @param pbBuf The output buffer.
+ * @param cbBuf The buffer size.
+ */
+static size_t rtFsIsoMakerOutFile_ReadPathTable(PRTFSISOMAKERNAMEDIR *ppDirHint, PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs,
+ bool fUnicode, bool fLittleEndian, uint32_t offInTable,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ /*
+ * Figure out which directory to start with. We keep a hint in the instance.
+ */
+ PRTFSISOMAKERNAMEDIR pDir = *ppDirHint;
+ if (!pDir)
+ {
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ }
+ if (offInTable - pDir->offPathTable < RTFSISOMAKER_CALC_PATHREC_SIZE(pDir->pName->cbNameInDirRec))
+ { /* hit */ }
+ /* Seek forwards: */
+ else if (offInTable > pDir->offPathTable)
+ do
+ {
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ } while (offInTable - pDir->offPathTable >= RTFSISOMAKER_CALC_PATHREC_SIZE(pDir->pName->cbNameInDirRec));
+ /* Back to the start: */
+ else if (offInTable == 0)
+ {
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ }
+ /* Seek backwards: */
+ else
+ do
+ {
+ pDir = RTListGetPrev(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ } while (offInTable - pDir->offPathTable >= RTFSISOMAKER_CALC_PATHREC_SIZE(pDir->pName->cbNameInDirRec));
+
+ /*
+ * Generate content.
+ */
+ size_t cbDone = 0;
+ while ( cbBuf > 0
+ && pDir)
+ {
+ PRTFSISOMAKERNAME pName = pDir->pName;
+ uint8_t cbRec = RTFSISOMAKER_CALC_PATHREC_SIZE(pName->cbNameInDirRec);
+ uint32_t cbCopied;
+ if ( offInTable == pDir->offPathTable
+ && cbBuf >= cbRec + fUnicode * 2U)
+ cbCopied = rtFsIsoMakerOutFile_GeneratePathRec(pName, fUnicode, fLittleEndian, pbBuf);
+ else
+ cbCopied = rtFsIsoMakerOutFile_GeneratePathRecPartial(pName, fUnicode, fLittleEndian,
+ offInTable - pDir->offPathTable, pbBuf, cbBuf);
+ cbDone += cbCopied;
+ offInTable += cbCopied;
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ }
+
+ /*
+ * Update the hint.
+ */
+ *ppDirHint = pDir;
+
+ return cbDone;
+}
+
+
+/**
+ * Generates ISO-9660 directory record into the specified buffer.
+ *
+ * The caller must deal with multi-extent copying and end of sector zero
+ * padding.
+ *
+ * @returns Number of bytes copied into the buffer (pName->cbDirRec).
+ * @param pName The namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16BE / UCS-2BE, i.e. we're in the joliet namespace.
+ * @param pbBuf The buffer. This is at least pName->cbDirRec bytes
+ * big (i.e. at most 256 bytes).
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ * @param enmDirType The kind of directory entry this is.
+ */
+static uint32_t rtFsIsoMakerOutFile_GenerateDirRec(PRTFSISOMAKERNAME pName, bool fUnicode, uint8_t *pbBuf,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs, RTFSISOMAKERDIRTYPE enmDirType)
+{
+ /*
+ * Emit a standard ISO-9660 directory record.
+ */
+ PISO9660DIRREC pDirRec = (PISO9660DIRREC)pbBuf;
+ PCRTFSISOMAKEROBJ pObj = pName->pObj;
+ PCRTFSISOMAKERNAMEDIR pDir = pName->pDir;
+ if (pDir)
+ {
+ pDirRec->offExtent.be = RT_H2BE_U32(pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pDirRec->offExtent.le = RT_H2LE_U32(pDir->offDir / RTFSISOMAKER_SECTOR_SIZE);
+ pDirRec->cbData.be = RT_H2BE_U32(pDir->cbDir);
+ pDirRec->cbData.le = RT_H2LE_U32(pDir->cbDir);
+ pDirRec->fFileFlags = ISO9660_FILE_FLAGS_DIRECTORY;
+ }
+ else if (pObj->enmType == RTFSISOMAKEROBJTYPE_FILE)
+ {
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pObj;
+ pDirRec->offExtent.be = RT_H2BE_U32(pFile->offData / RTFSISOMAKER_SECTOR_SIZE);
+ pDirRec->offExtent.le = RT_H2LE_U32(pFile->offData / RTFSISOMAKER_SECTOR_SIZE);
+ pDirRec->cbData.be = RT_H2BE_U32(pFile->cbData);
+ pDirRec->cbData.le = RT_H2LE_U32(pFile->cbData);
+ pDirRec->fFileFlags = 0;
+ }
+ else
+ {
+ pDirRec->offExtent.be = 0;
+ pDirRec->offExtent.le = 0;
+ pDirRec->cbData.be = 0;
+ pDirRec->cbData.le = 0;
+ pDirRec->fFileFlags = 0;
+ }
+ rtFsIsoMakerTimespecToIso9660RecTimestamp(&pObj->BirthTime, &pDirRec->RecTime);
+
+ pDirRec->cbDirRec = pName->cbDirRec;
+ pDirRec->cExtAttrBlocks = 0;
+ pDirRec->bFileUnitSize = 0;
+ pDirRec->bInterleaveGapSize = 0;
+ pDirRec->VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ pDirRec->VolumeSeqNo.le = RT_H2LE_U16_C(1);
+ pDirRec->bFileIdLength = pName->cbNameInDirRec;
+
+ if (!fUnicode)
+ {
+ memcpy(&pDirRec->achFileId[0], pName->szName, pName->cbNameInDirRec);
+ if (!(pName->cbNameInDirRec & 1))
+ pDirRec->achFileId[pName->cbNameInDirRec] = '\0';
+ }
+ else
+ {
+ /* Convert to big endian UTF-16. We're using a separate buffer here
+ because of zero terminator (none in pDirRec) and misalignment. */
+ RTUTF16 wszTmp[128];
+ PRTUTF16 pwszTmp = &wszTmp[0];
+ size_t cwcResult = 0;
+ int rc = RTStrToUtf16BigEx(pName->szName, RTSTR_MAX, &pwszTmp, RT_ELEMENTS(wszTmp), &cwcResult);
+ AssertRC(rc);
+ Assert( cwcResult * sizeof(RTUTF16) == pName->cbNameInDirRec
+ || (!pName->pParent && cwcResult == 0 && pName->cbNameInDirRec == 1) );
+ memcpy(&pDirRec->achFileId[0], pwszTmp, pName->cbNameInDirRec);
+ pDirRec->achFileId[pName->cbNameInDirRec] = '\0';
+ }
+
+ /*
+ * Rock ridge fields if enabled.
+ */
+ if (pName->cbRockInDirRec > 0)
+ {
+ uint8_t *pbSys = (uint8_t *)&pDirRec->achFileId[pName->cbNameInDirRec + !(pName->cbNameInDirRec & 1)];
+ size_t cbSys = &pbBuf[pName->cbDirRec] - pbSys;
+ Assert(cbSys >= pName->cbRockInDirRec);
+ if (cbSys > pName->cbRockInDirRec)
+ RT_BZERO(&pbSys[pName->cbRockInDirRec], cbSys - pName->cbRockInDirRec);
+ if (pName->cbRockSpill == 0)
+ rtFsIosMakerOutFile_GenerateRockRidge(pName, pbSys, cbSys, false /*fInSpill*/, enmDirType);
+ else
+ {
+ /* Maybe emit SP and RR entry, before emitting the CE entry. */
+ if (pName->pParent == NULL)
+ {
+ PISO9660SUSPSP pSP = (PISO9660SUSPSP)pbSys;
+ pSP->Hdr.bSig1 = ISO9660SUSPSP_SIG1;
+ pSP->Hdr.bSig2 = ISO9660SUSPSP_SIG2;
+ pSP->Hdr.cbEntry = ISO9660SUSPSP_LEN;
+ pSP->Hdr.bVersion = ISO9660SUSPSP_VER;
+ pSP->bCheck1 = ISO9660SUSPSP_CHECK1;
+ pSP->bCheck2 = ISO9660SUSPSP_CHECK2;
+ pSP->cbSkip = 0;
+ pbSys += sizeof(*pSP);
+ cbSys -= sizeof(*pSP);
+ }
+ if (pName->fRockNeedRRInDirRec)
+ {
+ PISO9660RRIPRR pRR = (PISO9660RRIPRR)pbSys;
+ pRR->Hdr.bSig1 = ISO9660RRIPRR_SIG1;
+ pRR->Hdr.bSig2 = ISO9660RRIPRR_SIG2;
+ pRR->Hdr.cbEntry = ISO9660RRIPRR_LEN;
+ pRR->Hdr.bVersion = ISO9660RRIPRR_VER;
+ pRR->fFlags = pName->fRockEntries;
+ pbSys += sizeof(*pRR);
+ cbSys -= sizeof(*pRR);
+ }
+ PISO9660SUSPCE pCE = (PISO9660SUSPCE)pbSys;
+ pCE->Hdr.bSig1 = ISO9660SUSPCE_SIG1;
+ pCE->Hdr.bSig2 = ISO9660SUSPCE_SIG2;
+ pCE->Hdr.cbEntry = ISO9660SUSPCE_LEN;
+ pCE->Hdr.bVersion = ISO9660SUSPCE_VER;
+ uint64_t offData = pFinalizedDirs->pRRSpillFile->offData + pName->offRockSpill;
+ pCE->offBlock.be = RT_H2BE_U32((uint32_t)(offData / ISO9660_SECTOR_SIZE));
+ pCE->offBlock.le = RT_H2LE_U32((uint32_t)(offData / ISO9660_SECTOR_SIZE));
+ pCE->offData.be = RT_H2BE_U32((uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK));
+ pCE->offData.le = RT_H2LE_U32((uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK));
+ pCE->cbData.be = RT_H2BE_U32((uint32_t)pName->cbRockSpill);
+ pCE->cbData.le = RT_H2LE_U32((uint32_t)pName->cbRockSpill);
+ Assert(cbSys >= sizeof(*pCE));
+ }
+ }
+
+ return pName->cbDirRec;
+}
+
+
+/**
+ * Generates ISO-9660 directory records into the specified buffer.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16BE / UCS-2BE, i.e. we're in the joliet namespace.
+ * @param pbBuf The buffer. This is at least pName->cbDirRecTotal
+ * bytes big.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ */
+static uint32_t rtFsIsoMakerOutFile_GenerateDirRecDirect(PRTFSISOMAKERNAME pName, bool fUnicode, uint8_t *pbBuf,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs)
+{
+ /*
+ * Normally there is just a single record without any zero padding.
+ */
+ uint32_t cbReturn = rtFsIsoMakerOutFile_GenerateDirRec(pName, fUnicode, pbBuf, pFinalizedDirs, RTFSISOMAKERDIRTYPE_OTHER);
+ if (RT_LIKELY(pName->cbDirRecTotal == cbReturn))
+ return cbReturn;
+ Assert(cbReturn < pName->cbDirRecTotal);
+
+ /*
+ * Deal with multiple records.
+ */
+ if (pName->cDirRecs > 1)
+ {
+ Assert(pName->pObj->enmType == RTFSISOMAKEROBJTYPE_FILE);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pName->pObj;
+
+ /* Set max size and duplicate the first directory record cDirRecs - 1 times. */
+ uint32_t const cbOne = cbReturn;
+ PISO9660DIRREC pDirRec = (PISO9660DIRREC)pbBuf;
+ pDirRec->cbData.be = RT_H2BE_U32_C(RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pDirRec->cbData.le = RT_H2LE_U32_C(RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pDirRec->fFileFlags |= ISO9660_FILE_FLAGS_MULTI_EXTENT;
+
+ PISO9660DIRREC pCurDirRec = pDirRec;
+ uint32_t offExtent = (uint32_t)(pFile->offData / RTFSISOMAKER_SECTOR_SIZE);
+ Assert(offExtent == ISO9660_GET_ENDIAN(&pDirRec->offExtent));
+ for (uint32_t iDirRec = 1; iDirRec < pName->cDirRecs; iDirRec++)
+ {
+ pCurDirRec = (PISO9660DIRREC)memcpy(&pbBuf[cbReturn], pDirRec, cbOne);
+
+ offExtent += RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE / RTFSISOMAKER_SECTOR_SIZE;
+ pCurDirRec->offExtent.le = RT_H2LE_U32(offExtent);
+
+ cbReturn += cbOne;
+ }
+ Assert(cbReturn <= pName->cbDirRecTotal);
+
+ /* Adjust the size in the final record. */
+ uint32_t cbDataLast = (uint32_t)(pFile->cbData % RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pCurDirRec->cbData.be = RT_H2BE_U32(cbDataLast);
+ pCurDirRec->cbData.le = RT_H2LE_U32(cbDataLast);
+ pCurDirRec->fFileFlags &= ~ISO9660_FILE_FLAGS_MULTI_EXTENT;
+ }
+
+ /*
+ * Do end of sector zero padding.
+ */
+ if (cbReturn < pName->cbDirRecTotal)
+ memset(&pbBuf[cbReturn], 0, (uint32_t)pName->cbDirRecTotal - cbReturn);
+
+ return pName->cbDirRecTotal;
+}
+
+
+/**
+ * Deals with situations where the destination buffer doesn't cover the whole
+ * directory record.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param off The offset into the directory record.
+ * @param pbBuf The buffer.
+ * @param cbBuf The buffer size.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ */
+static uint32_t rtFsIsoMakerOutFile_GenerateDirRecPartial(PRTFSISOMAKERNAME pName, bool fUnicode,
+ uint32_t off, uint8_t *pbBuf, size_t cbBuf,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs)
+{
+ Assert(off < pName->cbDirRecTotal);
+
+ /*
+ * This is reasonably simple when there is only one directory record and
+ * without any padding.
+ */
+ uint8_t abTmpBuf[256];
+ Assert(pName->cbDirRec <= sizeof(abTmpBuf));
+ uint32_t const cbOne = rtFsIsoMakerOutFile_GenerateDirRec(pName, fUnicode, abTmpBuf,
+ pFinalizedDirs, RTFSISOMAKERDIRTYPE_OTHER);
+ Assert(cbOne == pName->cbDirRec);
+ if (cbOne == pName->cbDirRecTotal)
+ {
+ uint32_t cbToCopy = RT_MIN((uint32_t)cbBuf, cbOne - off);
+ memcpy(pbBuf, &abTmpBuf[off], cbToCopy);
+ return cbToCopy;
+ }
+ Assert(cbOne < pName->cbDirRecTotal);
+
+ /*
+ * Single record and zero padding?
+ */
+ uint32_t cbCopied = 0;
+ if (pName->cDirRecs == 1)
+ {
+ /* Anything from the record to copy? */
+ if (off < cbOne)
+ {
+ cbCopied = RT_MIN((uint32_t)cbBuf, cbOne - off);
+ memcpy(pbBuf, &abTmpBuf[off], cbCopied);
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ off += cbCopied;
+ }
+
+ /* Anything from the zero padding? */
+ if (off >= cbOne && cbBuf > 0)
+ {
+ uint32_t cbToZero = RT_MIN((uint32_t)cbBuf, (uint32_t)pName->cbDirRecTotal - off);
+ memset(pbBuf, 0, cbToZero);
+ cbCopied += cbToZero;
+ }
+ }
+ /*
+ * Multi-extent stuff. Need to modify the cbData member as we copy.
+ */
+ else
+ {
+ Assert(pName->pObj->enmType == RTFSISOMAKEROBJTYPE_FILE);
+ PRTFSISOMAKERFILE pFile = (PRTFSISOMAKERFILE)pName->pObj;
+
+ /* Max out the size. */
+ PISO9660DIRREC pDirRec = (PISO9660DIRREC)abTmpBuf;
+ pDirRec->cbData.be = RT_H2BE_U32_C(RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pDirRec->cbData.le = RT_H2LE_U32_C(RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE);
+ pDirRec->fFileFlags |= ISO9660_FILE_FLAGS_MULTI_EXTENT;
+
+ /* Copy directory records. */
+ uint32_t offDirRec = pName->offDirRec;
+ uint32_t offExtent = pFile->offData / RTFSISOMAKER_SECTOR_SIZE;
+ for (uint32_t i = 0; i < pName->cDirRecs && cbBuf > 0; i++)
+ {
+ uint32_t const offInRec = off - offDirRec;
+ if (offInRec < cbOne)
+ {
+ /* Update the record. */
+ pDirRec->offExtent.be = RT_H2BE_U32(offExtent);
+ pDirRec->offExtent.le = RT_H2LE_U32(offExtent);
+ if (i + 1 == pName->cDirRecs)
+ {
+ uint32_t cbDataLast = pFile->cbData % RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE;
+ pDirRec->cbData.be = RT_H2BE_U32(cbDataLast);
+ pDirRec->cbData.le = RT_H2LE_U32(cbDataLast);
+ pDirRec->fFileFlags &= ~ISO9660_FILE_FLAGS_MULTI_EXTENT;
+ }
+
+ /* Copy chunk. */
+ uint32_t cbToCopy = RT_MIN((uint32_t)cbBuf, cbOne - offInRec);
+ memcpy(pbBuf, &abTmpBuf[offInRec], cbToCopy);
+ cbCopied += cbToCopy;
+ pbBuf += cbToCopy;
+ cbBuf -= cbToCopy;
+ off += cbToCopy;
+ }
+
+ offDirRec += cbOne;
+ offExtent += RTFSISOMAKER_MAX_ISO9660_EXTENT_SIZE / RTFSISOMAKER_SECTOR_SIZE;
+ }
+
+ /* Anything from the zero padding? */
+ if (off >= offDirRec && cbBuf > 0)
+ {
+ uint32_t cbToZero = RT_MIN((uint32_t)cbBuf, (uint32_t)pName->cbDirRecTotal - offDirRec);
+ memset(pbBuf, 0, cbToZero);
+ cbCopied += cbToZero;
+ }
+ }
+
+ return cbCopied;
+}
+
+
+/**
+ * Generate a '.' or '..' directory record.
+ *
+ * This is the same as rtFsIsoMakerOutFile_GenerateDirRec, but with the filename
+ * reduced to 1 byte.
+ *
+ * @returns Number of bytes copied into the buffer.
+ * @param pName The directory namespace node.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param bDirId The directory ID (0x00 or 0x01).
+ * @param off The offset into the directory record.
+ * @param pbBuf The buffer.
+ * @param cbBuf The buffer size.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ */
+static uint32_t rtFsIsoMakerOutFile_GenerateSpecialDirRec(PRTFSISOMAKERNAME pName, bool fUnicode, uint8_t bDirId,
+ uint32_t off, uint8_t *pbBuf, size_t cbBuf,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs)
+{
+ Assert(off < pName->cbDirRec);
+ Assert(pName->pDir);
+
+ /* Generate a regular directory record. */
+ uint8_t abTmpBuf[256];
+ Assert(off < pName->cbDirRec);
+ size_t cbToCopy = rtFsIsoMakerOutFile_GenerateDirRec(pName, fUnicode, abTmpBuf, pFinalizedDirs,
+ bDirId == 0 ? RTFSISOMAKERDIRTYPE_CURRENT : RTFSISOMAKERDIRTYPE_PARENT);
+ Assert(cbToCopy == pName->cbDirRec);
+
+ /** @todo r=bird: This isn't working quite right as the NM record includes the
+ * full directory name. Spill file stuff is shared with the (grand)parent
+ * directory entry. [RR NM ./..] */
+
+ /* Replace the filename part. */
+ PISO9660DIRREC pDirRec = (PISO9660DIRREC)abTmpBuf;
+ if (pDirRec->bFileIdLength != 1)
+ {
+ uint8_t offSysUse = pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1) + RT_UOFFSETOF(ISO9660DIRREC, achFileId);
+ uint8_t cbSysUse = pDirRec->cbDirRec - offSysUse;
+ if (cbSysUse > 0)
+ memmove(&pDirRec->achFileId[1], &abTmpBuf[offSysUse], cbSysUse);
+ pDirRec->bFileIdLength = 1;
+ cbToCopy = RT_UOFFSETOF(ISO9660DIRREC, achFileId) + 1 + cbSysUse;
+ pDirRec->cbDirRec = (uint8_t)cbToCopy;
+ }
+ pDirRec->achFileId[0] = bDirId;
+
+ /* Do the copying. */
+ cbToCopy = RT_MIN(cbBuf, cbToCopy - off);
+ memcpy(pbBuf, &abTmpBuf[off], cbToCopy);
+ return (uint32_t)cbToCopy;
+}
+
+
+/**
+ * Read directory records.
+ *
+ * This locates the directory at @a offUnsigned and generates directory records
+ * for it. Caller must repeat the call to get directory entries for the next
+ * directory should there be desire for that.
+ *
+ * @returns Number of bytes copied into @a pbBuf.
+ * @param ppDirHint Pointer to the directory hint for the namespace.
+ * @param pIsoMaker The ISO maker instance.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ * @param fUnicode Set if the name should be translated to big endian
+ * UTF-16 / UCS-2, i.e. we're in the joliet namespace.
+ * @param offUnsigned The ISO image byte offset of the requested data.
+ * @param pbBuf The output buffer.
+ * @param cbBuf How much to read.
+ */
+static size_t rtFsIsoMakerOutFile_ReadDirRecords(PRTFSISOMAKERNAMEDIR *ppDirHint, PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs,
+ bool fUnicode, uint64_t offUnsigned, uint8_t *pbBuf, size_t cbBuf)
+{
+ /*
+ * Figure out which directory. We keep a hint in the instance.
+ */
+ uint64_t offInDir64;
+ PRTFSISOMAKERNAMEDIR pDir = *ppDirHint;
+ if (!pDir)
+ {
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ }
+ if ((offInDir64 = offUnsigned - pDir->offDir) < RT_ALIGN_32(pDir->cbDir, RTFSISOMAKER_SECTOR_SIZE))
+ { /* hit */ }
+ /* Seek forwards: */
+ else if (offUnsigned > pDir->offDir)
+ do
+ {
+ pDir = RTListGetNext(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ } while ((offInDir64 = offUnsigned - pDir->offDir) >= RT_ALIGN_32(pDir->cbDir, RTFSISOMAKER_SECTOR_SIZE));
+ /* Back to the start: */
+ else if (pFinalizedDirs->offDirs / RTFSISOMAKER_SECTOR_SIZE == offUnsigned / RTFSISOMAKER_SECTOR_SIZE)
+ {
+ pDir = RTListGetFirst(&pFinalizedDirs->FinalizedDirs, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ offInDir64 = offUnsigned - pDir->offDir;
+ }
+ /* Seek backwards: */
+ else
+ do
+ {
+ pDir = RTListGetPrev(&pFinalizedDirs->FinalizedDirs, pDir, RTFSISOMAKERNAMEDIR, FinalizedEntry);
+ AssertReturnStmt(pDir, *pbBuf = 0xff, 1);
+ } while ((offInDir64 = offUnsigned - pDir->offDir) >= RT_ALIGN_32(pDir->cbDir, RTFSISOMAKER_SECTOR_SIZE));
+
+ /*
+ * Update the hint.
+ */
+ *ppDirHint = pDir;
+
+ /*
+ * Generate content.
+ */
+ size_t cbDone = 0;
+ uint32_t offInDir = (uint32_t)offInDir64;
+ if (offInDir < pDir->cbDir)
+ {
+ PRTFSISOMAKERNAME pDirName = pDir->pName;
+ PRTFSISOMAKERNAME pParentName = pDirName->pParent ? pDirName->pParent : pDirName;
+ uint32_t cbSpecialRecs = (uint32_t)pDir->cbDirRec00 + pDir->cbDirRec01;
+
+ /*
+ * Special '.' and/or '..' entries requested.
+ */
+ uint32_t iChild;
+ if (offInDir < cbSpecialRecs)
+ {
+ /* do '.' */
+ if (offInDir < pDir->cbDirRec00)
+ {
+ uint32_t cbCopied = rtFsIsoMakerOutFile_GenerateSpecialDirRec(pDirName, fUnicode, 0, offInDir,
+ pbBuf, cbBuf, pFinalizedDirs);
+ cbDone += cbCopied;
+ offInDir += cbCopied;
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ }
+
+ /* do '..' */
+ if (cbBuf > 0)
+ {
+ uint32_t cbCopied = rtFsIsoMakerOutFile_GenerateSpecialDirRec(pParentName, fUnicode, 1,
+ offInDir - pDir->cbDirRec00,
+ pbBuf, cbBuf, pFinalizedDirs);
+ cbDone += cbCopied;
+ offInDir += cbCopied;
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ }
+
+ iChild = 0;
+ }
+ /*
+ * Locate the directory entry we should start with. We can do this
+ * using binary searching on offInDir.
+ */
+ else
+ {
+ /** @todo binary search */
+ iChild = 0;
+ while (iChild < pDir->cChildren)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[iChild];
+ if ((offInDir - pChild->offDirRec) < pChild->cbDirRecTotal)
+ break;
+ iChild++;
+ }
+ AssertReturnStmt(iChild < pDir->cChildren, *pbBuf = 0xff, 1);
+ }
+
+ /*
+ * Normal directory entries.
+ */
+ while ( cbBuf > 0
+ && iChild < pDir->cChildren)
+ {
+ PRTFSISOMAKERNAME pChild = pDir->papChildren[iChild];
+ uint32_t cbCopied;
+ if ( offInDir == pChild->offDirRec
+ && cbBuf >= pChild->cbDirRecTotal)
+ cbCopied = rtFsIsoMakerOutFile_GenerateDirRecDirect(pChild, fUnicode, pbBuf, pFinalizedDirs);
+ else
+ cbCopied = rtFsIsoMakerOutFile_GenerateDirRecPartial(pChild, fUnicode, offInDir - pChild->offDirRec,
+ pbBuf, cbBuf, pFinalizedDirs);
+
+ cbDone += cbCopied;
+ offInDir += cbCopied;
+ pbBuf += cbCopied;
+ cbBuf -= cbCopied;
+ iChild++;
+ }
+
+ /*
+ * Check if we're into the zero padding at the end of the directory now.
+ */
+ if ( cbBuf > 0
+ && iChild >= pDir->cChildren)
+ {
+ size_t cbZeros = RT_MIN(cbBuf, RTFSISOMAKER_SECTOR_SIZE - (pDir->cbDir & RTFSISOMAKER_SECTOR_OFFSET_MASK));
+ memset(pbBuf, 0, cbZeros);
+ cbDone += cbZeros;
+ }
+ }
+ else
+ {
+ cbDone = RT_MIN(cbBuf, RT_ALIGN_32(pDir->cbDir, RTFSISOMAKER_SECTOR_SIZE) - offInDir);
+ memset(pbBuf, 0, cbDone);
+ }
+
+ return cbDone;
+}
+
+
+/**
+ * Read directory records or path table records.
+ *
+ * Will not necessarily fill the entire buffer. Caller must call again to get
+ * more.
+ *
+ * @returns Number of bytes copied into @a pbBuf.
+ * @param ppDirHint Pointer to the directory hint for the namespace.
+ * @param pIsoMaker The ISO maker instance.
+ * @param pNamespace The namespace.
+ * @param pFinalizedDirs The finalized directory data for the namespace.
+ * @param offUnsigned The ISO image byte offset of the requested data.
+ * @param pbBuf The output buffer.
+ * @param cbBuf How much to read.
+ */
+static size_t rtFsIsoMakerOutFile_ReadDirStructures(PRTFSISOMAKERNAMEDIR *ppDirHint, PRTFSISOMAKERNAMESPACE pNamespace,
+ PRTFSISOMAKERFINALIZEDDIRS pFinalizedDirs,
+ uint64_t offUnsigned, uint8_t *pbBuf, size_t cbBuf)
+{
+ if (offUnsigned < pFinalizedDirs->offPathTableL)
+ return rtFsIsoMakerOutFile_ReadDirRecords(ppDirHint, pFinalizedDirs,
+ pNamespace->fNamespace == RTFSISOMAKER_NAMESPACE_JOLIET,
+ offUnsigned, pbBuf, cbBuf);
+
+ uint64_t offInTable;
+ if ((offInTable = offUnsigned - pFinalizedDirs->offPathTableL) < pFinalizedDirs->cbPathTable)
+ return rtFsIsoMakerOutFile_ReadPathTable(ppDirHint, pFinalizedDirs,
+ pNamespace->fNamespace == RTFSISOMAKER_NAMESPACE_JOLIET,
+ true /*fLittleEndian*/, (uint32_t)offInTable, pbBuf, cbBuf);
+
+ if ((offInTable = offUnsigned - pFinalizedDirs->offPathTableM) < pFinalizedDirs->cbPathTable)
+ return rtFsIsoMakerOutFile_ReadPathTable(ppDirHint, pFinalizedDirs,
+ pNamespace->fNamespace == RTFSISOMAKER_NAMESPACE_JOLIET,
+ false /*fLittleEndian*/, (uint32_t)offInTable, pbBuf, cbBuf);
+
+ /* ASSUME we're in the zero padding at the end of a path table. */
+ Assert( offUnsigned - pFinalizedDirs->offPathTableL < RT_ALIGN_32(pFinalizedDirs->cbPathTable, RTFSISOMAKER_SECTOR_SIZE)
+ || offUnsigned - pFinalizedDirs->offPathTableM < RT_ALIGN_32(pFinalizedDirs->cbPathTable, RTFSISOMAKER_SECTOR_SIZE));
+ size_t cbZeros = RT_MIN(cbBuf, RTFSISOMAKER_SECTOR_SIZE - ((size_t)offUnsigned & RTFSISOMAKER_SECTOR_OFFSET_MASK));
+ memset(pbBuf, 0, cbZeros);
+ return cbZeros;
+}
+
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+ PRTFSISOMAKERINT pIsoMaker = pThis->pIsoMaker;
+ size_t cbBuf = pSgBuf->paSegs[0].cbSeg;
+ uint8_t *pbBuf = (uint8_t *)pSgBuf->paSegs[0].pvSeg;
+
+ Assert(pSgBuf->cSegs == 1);
+ RT_NOREF(fBlocking);
+
+ /*
+ * Process the offset, checking for end-of-file.
+ */
+ uint64_t offUnsigned;
+ if (off < 0)
+ offUnsigned = pThis->offCurPos;
+ else
+ offUnsigned = (uint64_t)off;
+ if (offUnsigned >= pIsoMaker->cbFinalizedImage)
+ {
+ if (*pcbRead)
+ {
+ *pcbRead = 0;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+ if ( !pcbRead
+ && pIsoMaker->cbFinalizedImage - offUnsigned < cbBuf)
+ return VERR_EOF;
+
+ /*
+ * Produce the bytes.
+ */
+ int rc = VINF_SUCCESS;
+ size_t cbRead = 0;
+ while (cbBuf > 0)
+ {
+ size_t cbDone;
+
+ /* Betting on there being more file data than metadata, thus doing the
+ offset switch in decending order. */
+ if (offUnsigned >= pIsoMaker->offFirstFile)
+ {
+ if (offUnsigned < pIsoMaker->cbFinalizedImage)
+ {
+ if (offUnsigned < pIsoMaker->cbFinalizedImage - pIsoMaker->cbImagePadding)
+ {
+ rc = rtFsIsoMakerOutFile_ReadFileData(pThis, pIsoMaker, offUnsigned, pbBuf, cbBuf, &cbDone);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ {
+ cbDone = pIsoMaker->cbFinalizedImage - offUnsigned;
+ if (cbDone > cbBuf)
+ cbDone = cbBuf;
+ memset(pbBuf, 0, cbDone);
+ }
+ }
+ else
+ {
+ rc = pcbRead ? VINF_EOF : VERR_EOF;
+ break;
+ }
+ }
+ /*
+ * Joliet directory structures.
+ */
+ else if ( offUnsigned >= pIsoMaker->JolietDirs.offDirs
+ && pIsoMaker->JolietDirs.offDirs < pIsoMaker->JolietDirs.offPathTableL)
+ cbDone = rtFsIsoMakerOutFile_ReadDirStructures(&pThis->pDirHintJoliet, &pIsoMaker->Joliet, &pIsoMaker->JolietDirs,
+ offUnsigned, pbBuf, cbBuf);
+ /*
+ * Primary ISO directory structures.
+ */
+ else if (offUnsigned >= pIsoMaker->PrimaryIsoDirs.offDirs)
+ cbDone = rtFsIsoMakerOutFile_ReadDirStructures(&pThis->pDirHintPrimaryIso, &pIsoMaker->PrimaryIso,
+ &pIsoMaker->PrimaryIsoDirs, offUnsigned, pbBuf, cbBuf);
+ /*
+ * Volume descriptors.
+ */
+ else if (offUnsigned >= _32K)
+ {
+ size_t offVolDescs = (size_t)offUnsigned - _32K;
+ cbDone = RT_MIN(cbBuf, (pIsoMaker->cVolumeDescriptors * RTFSISOMAKER_SECTOR_SIZE) - offVolDescs);
+ memcpy(pbBuf, &pIsoMaker->pbVolDescs[offVolDescs], cbDone);
+ }
+ /*
+ * Zeros in the system area.
+ */
+ else if (offUnsigned >= pIsoMaker->cbSysArea)
+ {
+ cbDone = RT_MIN(cbBuf, _32K - (size_t)offUnsigned);
+ memset(pbBuf, 0, cbDone);
+ }
+ /*
+ * Actual data in the system area.
+ */
+ else
+ {
+ cbDone = RT_MIN(cbBuf, pIsoMaker->cbSysArea - (size_t)offUnsigned);
+ memcpy(pbBuf, &pIsoMaker->pbSysArea[(size_t)offUnsigned], cbDone);
+ }
+
+ /*
+ * Common advance.
+ */
+ cbRead += cbDone;
+ offUnsigned += cbDone;
+ pbBuf += cbDone;
+ cbBuf -= cbDone;
+ }
+
+ if (pcbRead)
+ *pcbRead = cbRead;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+ *poffActual = pThis->offCurPos;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnSkip}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Skip(void *pvThis, RTFOFF cb)
+{
+ RTFOFF offIgnored;
+ return rtFsIsoMakerOutFile_Seek(pvThis, cb, RTFILE_SEEK_CURRENT, &offIgnored);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+
+ /*
+ * Seek relative to which position.
+ */
+ uint64_t offWrt;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offWrt = 0;
+ break;
+
+ case RTFILE_SEEK_CURRENT:
+ offWrt = pThis->offCurPos;
+ break;
+
+ case RTFILE_SEEK_END:
+ offWrt = pThis->pIsoMaker->cbFinalizedImage;
+ break;
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Calc new position, take care to stay within RTFOFF type bounds.
+ */
+ uint64_t offNew;
+ if (offSeek == 0)
+ offNew = offWrt;
+ else if (offSeek > 0)
+ {
+ offNew = offWrt + offSeek;
+ if ( offNew < offWrt
+ || offNew > RTFOFF_MAX)
+ offNew = RTFOFF_MAX;
+ }
+ else if ((uint64_t)-offSeek < offWrt)
+ offNew = offWrt + offSeek;
+ else
+ offNew = 0;
+ pThis->offCurPos = offNew;
+
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtFsIsoMakerOutFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSISOMAKEROUTPUTFILE pThis = (PRTFSISOMAKEROUTPUTFILE)pvThis;
+ *pcbFile = pThis->pIsoMaker->cbFinalizedImage;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Standard file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtFsIsoMakerOutputFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "ISO Maker Output File",
+ rtFsIsoMakerOutFile_Close,
+ rtFsIsoMakerOutFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsIsoMakerOutFile_Read,
+ NULL /*Write*/,
+ rtFsIsoMakerOutFile_Flush,
+ NULL /*PollOne*/,
+ rtFsIsoMakerOutFile_Tell,
+ rtFsIsoMakerOutFile_Skip,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ NULL /*SetMode*/,
+ NULL /*SetTimes*/,
+ NULL /*SetOwner*/,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsIsoMakerOutFile_Seek,
+ rtFsIsoMakerOutFile_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION
+};
+
+
+
+/**
+ * Creates a VFS file for a finalized ISO maker instanced.
+ *
+ * The file can be used to access the image. Both sequential and random access
+ * are supported, so that this could in theory be hooked up to a CD/DVD-ROM
+ * drive emulation and used as a virtual ISO image.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param phVfsFile Where to return the handle.
+ */
+RTDECL(int) RTFsIsoMakerCreateVfsOutputFile(RTFSISOMAKER hIsoMaker, PRTVFSFILE phVfsFile)
+{
+ PRTFSISOMAKERINT pThis = hIsoMaker;
+ RTFSISOMAKER_ASSERT_VALID_HANDLE_RET(pThis);
+ AssertReturn(pThis->fFinalized, VERR_WRONG_ORDER);
+ AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER);
+
+ uint32_t cRefs = RTFsIsoMakerRetain(pThis);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ PRTFSISOMAKEROUTPUTFILE pFileData;
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsNewFile(&g_rtFsIsoMakerOutputFileOps, sizeof(*pFileData), RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_CREATE,
+ NIL_RTVFS, NIL_RTVFSLOCK, &hVfsFile, (void **)&pFileData);
+ if (RT_SUCCESS(rc))
+ {
+ pFileData->pIsoMaker = pThis;
+ pFileData->offCurPos = 0;
+ pFileData->pFileHint = NULL;
+ pFileData->hVfsSrcFile = NIL_RTVFSFILE;
+ pFileData->pDirHintPrimaryIso = NULL;
+ pFileData->pDirHintJoliet = NULL;
+ pFileData->iChildPrimaryIso = UINT32_MAX;
+ pFileData->iChildJoliet = UINT32_MAX;
+ *phVfsFile = hVfsFile;
+ return VINF_SUCCESS;
+ }
+
+ RTFsIsoMakerRelease(pThis);
+ *phVfsFile = NIL_RTVFSFILE;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/fs/isomakercmd-man.xml b/src/VBox/Runtime/common/fs/isomakercmd-man.xml
new file mode 100644
index 00000000..277d3806
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isomakercmd-man.xml
@@ -0,0 +1,590 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ manpage for RTIsoMaker/VISO
+-->
+<!--
+ Copyright (C) 2006-2023 Oracle and/or its affiliates.
+
+ This file is part of VirtualBox base platform packages, as
+ available from https://www.virtualbox.org.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License
+ as published by the Free Software Foundation, in version 3 of the
+ License.
+
+ This program 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <https://www.gnu.org/licenses>.
+
+ The contents of this file may alternatively be used under the terms
+ of the Common Development and Distribution License Version 1.0
+ (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ in the VirtualBox distribution, in which case the provisions of the
+ CDDL are applicable instead of those of the GPL.
+
+ You may elect to license modified versions of this file under the
+ terms and conditions of either the GPL or the CDDL or both.
+
+ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+<refentry id="viso" lang="en">
+
+ <refentryinfo>
+ <pubdate>$Date: 2023-01-17 15:15:46 +0100 (Tue, 17 Jan 2023) $</pubdate>
+ <title>VISO file format / RTIsoMaker</title>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>viso</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <!--<refname>VISO</refname>-->
+ <refname>viso</refname>
+ <refpurpose>ISO image maker</refpurpose>
+ <refclass>IPRT</refclass>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis id="synopsis-viso"> <!-- The 'id' is mandatory and must start with 'synopsis-'. -->
+ <command>RTIsoMaker</command>
+ <arg><replaceable>options</replaceable></arg>
+ <arg>@<replaceable>commands.rsp</replaceable></arg>
+ <arg choice="req" rep="repeat"><replaceable>filespec</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+ <para>Construct a virtual ISO 9660 / Joliet / UDF / HFS hybrid image and either write it to a
+ file (RTIsoMaker) or serve it as a virtual image (VISO).</para>
+
+ <refsect2 id="viso-viso">
+ <title>VISO file format</title>
+ <para>A VISO file is a virtual ISO image, i.e. constructed in memory from a bunch of files on
+ the host. A VISO is just the recipe describing how to go about this using a syntax vaguely
+ similar to mkisofs and genisoimage.</para>
+
+ <para>One requirement is that the VISO file must start with one of the
+ <option>--iprt-iso-maker-file-marker</option> options. Which of the options you use will
+ dictate the quoting and escaping rules used when reading the file. The option takes the
+ image UUID as an argument.</para>
+
+ <para>The VISO files are treated as UTF-8 and must not contain any byte order marker (BOM).
+ There is currently no way to comment out lines in a VISO file.</para>
+
+ </refsect2>
+
+ <refsect2 id="viso-filespecs">
+ <title>File specifications and --name-setup</title>
+ <para>All non-options that does not start with '@' are taken to indicate a file, directory,
+ or similar that is should be added to the ISO image. Directories are added recursively and
+ content is subject to filtering options.</para>
+
+ <para>Since there can be up to six different namespaces on an ISO, it is handy to be able to
+ control the names used in each and be able to exclude an object from one or more namespaces.
+ The <option>--name-setup</option> option specifies the file specification format to use
+ forthwith.</para>
+
+ <para>The default setup is:</para>
+ <!-- indent this: -->
+ <para><computeroutput> --name-setup iso+joliet+udf+hfs</computeroutput></para>
+
+ <para>Which means you specify one on-ISO name for all namespaces followed by '=' and the
+ source file system name. Only specifying the source file system will add the
+ file/dir/whatever to the root of the ISO image.</para>
+
+ <para>Lets look at the following two examples:</para>
+
+ <!-- indent these -->
+ <para><computeroutput> /docs/readme.txt=/home/user/Documents/product-x-readme.txt</computeroutput></para>
+ <para><computeroutput> /home/user/Documents/product-x-readme.txt</computeroutput></para>
+
+ <para>In the first case the file <computeroutput>'/home/user/Documents/product-x-readme.txt'</computeroutput>
+ is added to the ISO image as <computeroutput>'/docs/readme.txt'</computeroutput> in all
+ enabled namespaces. In the primary ISO 9660 namespace, the filename will by default be
+ converted to upper case because it's required by the spec.</para>
+
+ <para>In the second case the file is added to the root under the name
+ <computeroutput>'product-x-readme.txt'</computeroutput> in all namespaces. Though, in the
+ primary ISO 9660 namespace the name will be transformed to apply with the current ISO level,
+ probably uppercased, possibly truncated too. </para>
+
+ <para>Given <option>--name-setup iso,joliet,udf</option> you can specify the name individually
+ for each of the three namespace, if you like. If you omit any, they will use last name given.
+ Any names left blank (<computeroutput>==</computeroutput>) will be considered omitted.</para>
+
+ <para>A different name in each namespace:</para>
+ <para><computeroutput> /ISO.TXT=/Joliet.TxT=/UDF.txt=/tmp/iso/real.txt</computeroutput></para>
+ <para>Specific name in the ISO 9660 namespace, same in the rest:</para>
+ <para><computeroutput> /ISO.TXT=/OtherNamespaces.TxT=/tmp/iso/real.txt</computeroutput></para>
+ <para>Omit the file from the ISO 9660 namespace:</para>
+ <para><computeroutput> =/OtherNamespaces.TxT=/tmp/iso/real.txt</computeroutput></para>
+ <para>Omit the file from the joliet namespace:</para>
+ <para><computeroutput> /ISO.TXT==/UDF.TxT=/tmp/iso/real.txt</computeroutput></para>
+ <para>Use the same filename as the source everywhere:</para>
+ <para><computeroutput> /tmp/iso/real.txt</computeroutput></para>
+
+
+ <para>Using for instance <option>--name-setup udf</option> you can add a files/dirs/whatever
+ to select namespace(s) without the more complicated empty name syntax above.</para>
+
+ <para>When adding directories, you can only control the naming and omitting of the directory
+ itself, not any recursively added files and directories below it.</para>
+
+ </refsect2>
+ </refsect1>
+
+ <refsect1 id="viso-options">
+ <title>Options</title>
+
+ <refsect2 id="viso-options-general">
+ <title>General</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-o <replaceable>output-file</replaceable></option></term>
+ <term><option>--output=<replaceable>output-file</replaceable></option></term>
+ <listitem><para>The output filename. This option is not supported in VISO mode.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--name-setup=<replaceable>spec</replaceable></option></term>
+ <listitem><para>Configures active namespaces and how file specifications are to be
+ interpreted. The specification is a comma separated list. Each element in the list is
+ a sub-list separated by space, <computeroutput>'+'</computeroutput> or
+ <computeroutput>'|'</computeroutput> giving the namespaces that elements controls.
+ Namespaces are divied into two major and minor ones, you cannot specifying a minor
+ before the major it belongs to.</para>
+ <para>Major namespaces and aliases in parentheses:</para>
+ <itemizedlist spacing="compact">
+ <listitem><para>iso (primary, iso9660, iso-9660, primary-iso, iso-primary)</para></listitem>
+ <listitem><para>joliet</para></listitem>
+ <listitem><para>udf</para></listitem>
+ <listitem><para>hfs (hfs-plus)</para></listitem>
+ </itemizedlist>
+ <para>Minor namespaces:</para>
+ <itemizedlist spacing="compact">
+ <listitem><para>rock: rock ridge on previous major namespace (iso / joliet)</para></listitem>
+ <listitem><para>iso-rock: rock ridge extensions on primary ISO 9660 namespace</para></listitem>
+ <listitem><para>joliet-rock: rock ridge on joliet namespace (just for fun)</para></listitem>
+ <listitem><para>trans-tbl: translation table file on previous major namespace</para></listitem>
+ <listitem><para>iso-trans-tbl</para></listitem>
+ <listitem><para>joliet-trans-tbl</para></listitem>
+ <listitem><para>udf-trans-tbl</para></listitem>
+ <listitem><para>hfs-trans-tbl</para></listitem>
+ </itemizedlist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--name-setup-from-import</option></term>
+ <listitem><para>This is for use following one or more <option>--import-iso</option>
+ operations and will pick a configuration matching the imported content as best we can.
+ If the imported ISOs only had a iso9660 namespace, the joliet, udf and hfs namespaces
+ will be removed. This is useful when adding additional files to the ISO and will
+ prevent guest from picking a namespace without the imported ISO content when mounting it.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--push-iso=<replaceable>iso-file</replaceable></option></term>
+ <term><option>--push-iso-no-joliet=<replaceable>iso-file</replaceable></option></term>
+ <term><option>--push-iso-no-rock-<replaceable>iso-file</replaceable></option></term>
+ <term><option>--push-iso-no-rock-no-joliet=<replaceable>iso-file</replaceable></option></term>
+ <listitem><para>Open the specified ISO file and use it as source file system until the
+ corresponding <option>--pop</option> options is encountered. The variations are for
+ selecting which namespace on the ISO to (not) access. These options are handy for copying
+ files/directories/stuff from an ISO without having to extract them first or using the
+ <computeroutput>:iprtvfs:</computeroutput> syntax.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--pop</option></term>
+ <listitem><para>Pops a <option>--push-iso</option> of the source file system stack.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--import-iso=<replaceable>iso-file</replaceable></option></term>
+ <listitem><para>Imports everything on the given ISO file, including boot configuration and
+ system area (first 16 sectors) content. You can use <option>--name-setup</option> to omit
+ namespaces.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-namespaces">
+ <title>Namespaces</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--iso-level=<replaceable>0|1|2|3</replaceable></option></term> <!-- FIXME: imperfect markup -->
+ <listitem>
+ <para>Sets the ISO level:</para>
+ <itemizedlist spacing="compact">
+ <listitem><para>0: Disable primary ISO namespace.</para></listitem>
+ <listitem><para>1: ISO level 1: Filenames 8.3 format and limited to 4GB - 1.</para></listitem>
+ <listitem><para>2: ISO level 2: 31 char long names and limited to 4GB - 1.</para></listitem>
+ <listitem><para>3: ISO level 3: 31 char long names and support for >=4GB files. (default)</para></listitem>
+ <listitem><para>4: Fictive level used by other tools. Not yet implemented.</para></listitem>
+ </itemizedlist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--rock-ridge</option></term>
+ <term><option>--limited-rock-ridge</option></term>
+ <term><option>--no-rock-ridge</option></term>
+ <listitem><para>Enables or disables rock ridge support for the primary ISO 9660 namespace.
+ The <option>--limited-rock-ridge</option> option omits a couple of bits in the root
+ directory that would make Linux pick rock ridge over joliet.</para>
+ <para>Default: <option>--limited-rock-ridge</option></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-J</option></term>
+ <term><option>--joliet</option></term>
+ <term><option>--no-joliet</option></term>
+ <listitem><para>Enables or disable the joliet namespace. This option must precede any file
+ specifications.</para>
+ <para>Default: <option>--joliet</option></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--joliet-ucs-level=<replaceable>1|2|3</replaceable></option></term> <!-- FIXME: imperfect markup -->
+ <term><option>--ucs-level=<replaceable>1|2|3</replaceable></option></term>
+ <listitem><para>Set the Joliet UCS support level. This is currently only flagged in the
+ image but not enforced on the actual path names.</para>
+ <para>Default level: 3</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-file-attributes">
+ <title>File Attributes</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--rational-attribs</option></term>
+ <listitem><para>Enables rational file attribute handling (default):</para>
+ <itemizedlist spacing="compact">
+ <listitem><para>Owner ID is set to zero</para></listitem>
+ <listitem><para>Group ID is set to zero</para></listitem>
+ <listitem><para>Mode is set to 0444 for non-executable files.</para></listitem>
+ <listitem><para>Mode is set to 0555 for executable files.</para></listitem>
+ <listitem><para>Mode is set to 0555 for directories, preserving stick bits.</para></listitem>
+ </itemizedlist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--strict-attribs</option></term>
+ <listitem><para>Counters <option>--rational-attribs</option> and causes attributes to be
+ recorded exactly as they appear in the source.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--file-mode=<replaceable>mode</replaceable></option></term>
+ <term><option>--no-file-mode</option></term>
+ <listitem><para>Controls the forced file mode mask for rock ridge, UDF and HFS.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--dir-mode=<replaceable>mode</replaceable></option></term>
+ <term><option>--no-dir-mode</option></term>
+ <listitem><para>Controls the forced directory mode mask for rock ridge, UDF and HFS.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--new-dir-mode=<replaceable>mode</replaceable></option></term>
+ <listitem><para>Controls the default mode mask (rock ridge, UDF, HFS) for directories that
+ are created implicitly. The <option>--dir-mode</option> option overrides this.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--chmod=<replaceable>mode</replaceable>:<replaceable>on-iso-file</replaceable></option></term>
+ <listitem><para>Explictily sets the rock ridge, UDF and HFS file mode for a file/dir/whatever
+ that has already been added to the ISO. The mode can be octal, <computeroutput>ra+x</computeroutput>,
+ <computeroutput>a+r</computeroutput>, or <computeroutput>a+rx</computeroutput>.
+ (Support for more complicated mode specifications may be implemented at a later point.)</para>
+ <para>Note that only namespaces in the current --name-setup are affected.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--chown=<replaceable>owner-id</replaceable>:<replaceable>on-iso-file</replaceable></option></term>
+ <listitem><para>Explictily sets the rock ridge, UDF and HFS file owner ID (numeric) for a
+ file/dir/whatever that has already been added to the ISO.</para>
+ <para>Note that only namespaces in the current --name-setup are affected.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>--chgrp=<replaceable>group-id</replaceable>:<replaceable>on-iso-file</replaceable></term>
+ <listitem><para>Explictily sets the rock ridge, UDF and HFS file group ID (numeric) for a
+ file/dir/whatever that has already been added to the ISO.</para>
+ <para>Note that only namespaces in the current --name-setup are affected.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-booting">
+ <title>Booting</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--eltorito-new-entry</option></term>
+ <term><option>--eltorito-alt-boot</option></term>
+ <listitem><para>Starts a new El Torito boot entry.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--eltorito-add-image=<replaceable>filespec</replaceable></option></term>
+ <listitem><para>File specification of a file that should be added to the image and used as
+ the El Torito boot image of the current boot entry.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-b <replaceable>on-iso-file</replaceable></option></term>
+ <term><option>--eltorito-boot=<replaceable>on-iso-file</replaceable></option></term>
+ <listitem><para>Specifies a file on the ISO as the El Torito boot image for the current boot
+ entry.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--eltorito-floppy-12</option></term>
+ <term><option>--eltorito-floppy-144</option></term>
+ <term><option>--eltorito-floppy-288</option></term>
+ <term><option>--no-emulation-boot</option></term>
+ <term><option>--hard-disk-boot</option></term>
+ <listitem><para>Sets the boot image emulation type of the current El Torito boot entry.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--boot-load-seg=<replaceable>seg</replaceable></option></term>
+ <listitem><para>Specify the image load segment for the current El Torito boot entry.</para>
+ <para>Default: 0x7c0</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--boot-load-size=<replaceable>sectors</replaceable></option></term>
+ <listitem><para>Specify the image load size in emulated sectors for the current El Torito
+ boot entry.</para>
+ <para>Default: 4 (sectors of 512 bytes)</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--no-boot</option></term>
+ <listitem><para>Indicates that the current El Torito boot entry isn't bootable. (The BIOS
+ will allegedly configure the emulation, but not attempt booting.)</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--boot-info-table</option></term>
+ <listitem><para>Write a isolinux/syslinux boot info table into the boot image for the
+ current El Torito boot entry.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--eltorito-platform-id=<replaceable>id</replaceable></option></term>
+ <listitem><para>Set the El Torito platform ID of the current entry, a new entry of the
+ verification entry depending on when it's used. The ID must be one of:
+ <computeroutput>x86</computeroutput>, <computeroutput>PPC</computeroutput>,
+ <computeroutput>Mac</computeroutput>, <computeroutput>efi</computeroutput></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-c <replaceable>namespec</replaceable></option></term>
+ <term><option>--boot-catalog=<replaceable>namespec</replaceable></option></term>
+ <listitem><para>Enters the El Torito boot catalog into the namespaces as a file. The
+ <replaceable>namespec</replaceable> uses the same format as a 'filespec', but omits the
+ final source file system name component.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-G <replaceable>file</replaceable></option></term>
+ <term><option>--generic-boot=<replaceable>file</replaceable></option></term>
+ <listitem><para>Specifies a file that should be loaded at offset 0 in the ISO image. The
+ file must not be larger than 32KB. When creating a hybrid image, parts of this may be
+ regenerated by partition tables and such.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-string-properties">
+ <title>String properties (applied to active namespaces only)</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--abstract=<replaceable>file-id</replaceable></option></term>
+ <listitem><para>The name of the abstract file in the root dir.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-A <replaceable>text|_file-id</replaceable></option></term>
+ <term><option>--application-id=<replaceable>text|_file-id</replaceable></option></term>
+ <listitem><para>Application ID string or root file name. The latter must be prefixed with
+ an underscore.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--biblio=<replaceable>file-id</replaceable></option></term>
+ <listitem><para>The name of the bibliographic file in the root dir.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--copyright=<replaceable>file-id</replaceable></option></term>
+ <listitem><para>The name of the copyright file in the root dir.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-P <replaceable>text|_file-id</replaceable></option></term>
+ <term><option>--publisher=<replaceable>text|_file-id</replaceable></option></term>
+ <listitem><para>Publisher ID string or root file name. The latter must be prefixed with an
+ underscore.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-p <replaceable>text|_file-id</replaceable></option></term>
+ <term><option>--preparer=<replaceable>text|_file-id</replaceable></option></term>
+ <listitem><para>Data preparer ID string or root file name. The latter must be prefixed
+ with an underscore.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--sysid=<replaceable>text</replaceable></option></term>
+ <listitem><para>System ID string.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--volid=<replaceable>text</replaceable></option></term>
+ <term><option>--volume-id=<replaceable>text</replaceable></option></term>
+ <listitem><para>Volume ID string (label). (It is possible to set different labels for
+ primary ISO 9660, joliet, UDF and HFS by changing the active namespaces using the
+ <option>--name-setup</option> option between <option>--volume-id</option> occurences.)</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--volset=<replaceable>text</replaceable></option></term>
+ <listitem><para>Volume set ID string.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ <refsect2 id="viso-options-compatibility">
+ <title>Compatibility:</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--graft-points</option></term>
+ <listitem><para>Alias for --name-setup iso+joliet+udf+hfs.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-l</option></term>
+ <term><option>--long-names</option></term>
+ <listitem><para>Allow 31 charater filenames. Just ensure ISO level >= 2 here.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-R</option></term>
+ <term><option>--rock</option></term>
+ <listitem><para>Same as <option>--rock-ridge</option> and <option>--strict-attribs</option>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-r</option></term>
+ <term><option>--rational-rock</option></term>
+ <listitem><para>Same as <option>--rock-ridge</option> and <option>--rational-attribs</option>.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+
+ <refsect2 id="viso-options-viso-specific">
+ <title>VISO Specific:</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--iprt-iso-maker-file-marker=<replaceable>UUID</replaceable></option></term>
+ <term><option>--iprt-iso-maker-file-marker-bourne=<replaceable>UUID</replaceable></option></term>
+ <term><option>--iprt-iso-maker-file-marker-bourne-sh=<replaceable>UUID</replaceable></option></term>
+ <listitem><para>Used as first option in a VISO file to specify the file UUID and that it is
+ formatted using bourne-shell argument quoting &amp; escaping style.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--iprt-iso-maker-file-marker-ms=<replaceable>UUID</replaceable></option></term>
+ <term><option>--iprt-iso-maker-file-marker-ms-sh=<replaceable>UUID</replaceable></option></term>
+ <listitem><para>Used as first option in a VISO file to specify the file UUID and that it is
+ formatted using microsoft CRT argument quoting &amp; escaping style.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+
+ <refsect2 id="viso-options-testing">
+ <title>Testing (not applicable to VISO):</title>
+ <variablelist>
+
+ <varlistentry>
+ <term><option>--output-buffer-size=<replaceable>bytes</replaceable></option></term>
+ <listitem><para>Selects a specific output buffer size for testing virtual image reads.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--random-output-buffer-size</option></term>
+ <listitem><para>Enables randomized buffer size for each virtual image read, using the
+ current output buffer size (<option>--output-buffer-size</option>) as maximum.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--random-order-verification=<replaceable>size</replaceable></option></term>
+ <listitem><para>Enables verification pass of the image that compares blocks of the given
+ size in random order from the virtual and output images.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect2>
+
+ </refsect1>
+</refentry>
+
diff --git a/src/VBox/Runtime/common/fs/isomakercmd.cpp b/src/VBox/Runtime/common/fs/isomakercmd.cpp
new file mode 100644
index 00000000..271c28a6
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isomakercmd.cpp
@@ -0,0 +1,3689 @@
+/* $Id: isomakercmd.cpp $ */
+/** @file
+ * IPRT - ISO Image Maker Command.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include "internal/iprt.h"
+#include <iprt/fsisomaker.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/fsvfs.h>
+#include <iprt/err.h>
+#include <iprt/getopt.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/formats/iso9660.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum number of name specifiers we allow. */
+#define RTFSISOMAKERCMD_MAX_NAMES 8
+
+/** Maximum directory recursions when adding a directory tree. */
+#define RTFSISOMAKERCMD_MAX_DIR_RECURSIONS 32
+
+/** @name Name specifiers
+ * @{ */
+#define RTFSISOMAKERCMDNAME_PRIMARY_ISO RTFSISOMAKER_NAMESPACE_ISO_9660
+#define RTFSISOMAKERCMDNAME_JOLIET RTFSISOMAKER_NAMESPACE_JOLIET
+#define RTFSISOMAKERCMDNAME_UDF RTFSISOMAKER_NAMESPACE_UDF
+#define RTFSISOMAKERCMDNAME_HFS RTFSISOMAKER_NAMESPACE_HFS
+
+#define RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE RT_BIT_32(16)
+#define RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE RT_BIT_32(17)
+
+#define RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL RT_BIT_32(20)
+#define RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL RT_BIT_32(21)
+#define RTFSISOMAKERCMDNAME_UDF_TRANS_TBL RT_BIT_32(22)
+#define RTFSISOMAKERCMDNAME_HFS_TRANS_TBL RT_BIT_32(23)
+
+#define RTFSISOMAKERCMDNAME_MAJOR_MASK \
+ (RTFSISOMAKERCMDNAME_PRIMARY_ISO | RTFSISOMAKERCMDNAME_JOLIET | RTFSISOMAKERCMDNAME_UDF | RTFSISOMAKERCMDNAME_HFS)
+
+#define RTFSISOMAKERCMDNAME_MINOR_MASK \
+ ( RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE | RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL \
+ | RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE | RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL \
+ | RTFSISOMAKERCMDNAME_UDF_TRANS_TBL \
+ | RTFSISOMAKERCMDNAME_HFS_TRANS_TBL)
+AssertCompile((RTFSISOMAKERCMDNAME_MAJOR_MASK & RTFSISOMAKERCMDNAME_MINOR_MASK) == 0);
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef enum RTFSISOMAKERCMDOPT
+{
+ RTFSISOMAKERCMD_OPT_FIRST = 1000,
+
+ RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER,
+ RTFSISOMAKERCMD_OPT_OUTPUT_BUFFER_SIZE,
+ RTFSISOMAKERCMD_OPT_RANDOM_OUTPUT_BUFFER_SIZE,
+ RTFSISOMAKERCMD_OPT_RANDOM_ORDER_VERIFICATION,
+ RTFSISOMAKERCMD_OPT_NAME_SETUP,
+ RTFSISOMAKERCMD_OPT_NAME_SETUP_FROM_IMPORT,
+
+ RTFSISOMAKERCMD_OPT_ROCK_RIDGE,
+ RTFSISOMAKERCMD_OPT_LIMITED_ROCK_RIDGE,
+ RTFSISOMAKERCMD_OPT_NO_ROCK_RIDGE,
+ RTFSISOMAKERCMD_OPT_NO_JOLIET,
+
+ RTFSISOMAKERCMD_OPT_IMPORT_ISO,
+ RTFSISOMAKERCMD_OPT_PUSH_ISO,
+ RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_JOLIET,
+ RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK,
+ RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK_NO_JOLIET,
+ RTFSISOMAKERCMD_OPT_POP,
+
+ RTFSISOMAKERCMD_OPT_ELTORITO_NEW_ENTRY,
+ RTFSISOMAKERCMD_OPT_ELTORITO_ADD_IMAGE,
+ RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_12,
+ RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_144,
+ RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_288,
+
+ RTFSISOMAKERCMD_OPT_RATIONAL_ATTRIBS,
+ RTFSISOMAKERCMD_OPT_STRICT_ATTRIBS,
+ RTFSISOMAKERCMD_OPT_NO_FILE_MODE,
+ RTFSISOMAKERCMD_OPT_NO_DIR_MODE,
+ RTFSISOMAKERCMD_OPT_CHMOD,
+ RTFSISOMAKERCMD_OPT_CHOWN,
+ RTFSISOMAKERCMD_OPT_CHGRP,
+
+ /*
+ * Compatibility options:
+ */
+ RTFSISOMAKERCMD_OPT_ABSTRACT_FILE_ID,
+ RTFSISOMAKERCMD_OPT_ALLOW_LEADING_DOTS,
+ RTFSISOMAKERCMD_OPT_ALLOW_LIMITED_SIZE,
+ RTFSISOMAKERCMD_OPT_ALLOW_LOWERCASE,
+ RTFSISOMAKERCMD_OPT_ALLOW_MULTI_DOT,
+ RTFSISOMAKERCMD_OPT_ALPHA_BOOT,
+ RTFSISOMAKERCMD_OPT_APPLE,
+ RTFSISOMAKERCMD_OPT_BIBLIOGRAPHIC_FILE_ID,
+ RTFSISOMAKERCMD_OPT_CHECK_OLD_NAMES,
+ RTFSISOMAKERCMD_OPT_CHECK_SESSION,
+ RTFSISOMAKERCMD_OPT_COPYRIGHT_FILE_ID,
+ RTFSISOMAKERCMD_OPT_DETECT_HARDLINKS,
+ RTFSISOMAKERCMD_OPT_DIR_MODE,
+ RTFSISOMAKERCMD_OPT_DVD_VIDEO,
+ RTFSISOMAKERCMD_OPT_ELTORITO_PLATFORM_ID,
+ RTFSISOMAKERCMD_OPT_ELTORITO_HARD_DISK_BOOT,
+ RTFSISOMAKERCMD_OPT_ELTORITO_INFO_TABLE,
+ RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SEG,
+ RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SIZE,
+ RTFSISOMAKERCMD_OPT_ELTORITO_NO_BOOT,
+ RTFSISOMAKERCMD_OPT_ELTORITO_NO_EMULATION_BOOT,
+ RTFSISOMAKERCMD_OPT_EXCLUDE_LIST,
+ RTFSISOMAKERCMD_OPT_FILE_MODE,
+ RTFSISOMAKERCMD_OPT_FORCE_RR,
+ RTFSISOMAKERCMD_OPT_GID,
+ RTFSISOMAKERCMD_OPT_GRAFT_POINTS,
+ RTFSISOMAKERCMD_OPT_GUI,
+ RTFSISOMAKERCMD_OPT_HFS_AUTO,
+ RTFSISOMAKERCMD_OPT_HFS_BLESS,
+ RTFSISOMAKERCMD_OPT_HFS_BOOT_FILE,
+ RTFSISOMAKERCMD_OPT_HFS_CAP,
+ RTFSISOMAKERCMD_OPT_HFS_CHRP_BOOT,
+ RTFSISOMAKERCMD_OPT_HFS_CLUSTER_SIZE,
+ RTFSISOMAKERCMD_OPT_HFS_CREATOR,
+ RTFSISOMAKERCMD_OPT_HFS_DAVE,
+ RTFSISOMAKERCMD_OPT_HFS_DOUBLE,
+ RTFSISOMAKERCMD_OPT_HFS_ENABLE,
+ RTFSISOMAKERCMD_OPT_HFS_ETHERSHARE,
+ RTFSISOMAKERCMD_OPT_HFS_EXCHANGE,
+ RTFSISOMAKERCMD_OPT_HFS_HIDE,
+ RTFSISOMAKERCMD_OPT_HFS_HIDE_LIST,
+ RTFSISOMAKERCMD_OPT_HFS_ICON_POSITION,
+ RTFSISOMAKERCMD_OPT_HFS_INPUT_CHARSET,
+ RTFSISOMAKERCMD_OPT_HFS_MAC_NAME,
+ RTFSISOMAKERCMD_OPT_HFS_MACBIN,
+ RTFSISOMAKERCMD_OPT_HFS_MAGIC,
+ RTFSISOMAKERCMD_OPT_HFS_MAP,
+ RTFSISOMAKERCMD_OPT_HFS_NETATALK,
+ RTFSISOMAKERCMD_OPT_HFS_NO_DESKTOP,
+ RTFSISOMAKERCMD_OPT_HFS_OSX_DOUBLE,
+ RTFSISOMAKERCMD_OPT_HFS_OSX_HFS,
+ RTFSISOMAKERCMD_OPT_HFS_OUTPUT_CHARSET,
+ RTFSISOMAKERCMD_OPT_HFS_PARMS,
+ RTFSISOMAKERCMD_OPT_HFS_PART,
+ RTFSISOMAKERCMD_OPT_HFS_PREP_BOOT,
+ RTFSISOMAKERCMD_OPT_HFS_PROBE,
+ RTFSISOMAKERCMD_OPT_HFS_ROOT_INFO,
+ RTFSISOMAKERCMD_OPT_HFS_SFM,
+ RTFSISOMAKERCMD_OPT_HFS_SGI,
+ RTFSISOMAKERCMD_OPT_HFS_SINGLE,
+ RTFSISOMAKERCMD_OPT_HFS_TYPE,
+ RTFSISOMAKERCMD_OPT_HFS_UNLOCK,
+ RTFSISOMAKERCMD_OPT_HFS_USHARE,
+ RTFSISOMAKERCMD_OPT_HFS_VOL_ID,
+ RTFSISOMAKERCMD_OPT_HFS_XINET,
+ RTFSISOMAKERCMD_OPT_HIDDEN,
+ RTFSISOMAKERCMD_OPT_HIDDEN_LIST,
+ RTFSISOMAKERCMD_OPT_HIDE,
+ RTFSISOMAKERCMD_OPT_HIDE_JOLIET,
+ RTFSISOMAKERCMD_OPT_HIDE_JOLIET_LIST,
+ RTFSISOMAKERCMD_OPT_HIDE_JOLIET_TRANS_TBL,
+ RTFSISOMAKERCMD_OPT_HIDE_LIST,
+ RTFSISOMAKERCMD_OPT_HIDE_RR_MOVED,
+ RTFSISOMAKERCMD_OPT_HPPA_BOOTLOADER,
+ RTFSISOMAKERCMD_OPT_HPPA_CMDLINE,
+ RTFSISOMAKERCMD_OPT_HPPA_KERNEL_32,
+ RTFSISOMAKERCMD_OPT_HPPA_KERNEL_64,
+ RTFSISOMAKERCMD_OPT_HPPA_RAMDISK,
+ RTFSISOMAKERCMD_OPT_INPUT_CHARSET,
+ RTFSISOMAKERCMD_OPT_ISO_LEVEL,
+ RTFSISOMAKERCMD_OPT_JIGDO_COMPRESS,
+ RTFSISOMAKERCMD_OPT_JIGDO_EXCLUDE,
+ RTFSISOMAKERCMD_OPT_JIGDO_FORCE_MD5,
+ RTFSISOMAKERCMD_OPT_JIGDO_JIGDO,
+ RTFSISOMAKERCMD_OPT_JIGDO_MAP,
+ RTFSISOMAKERCMD_OPT_JIGDO_MD5_LIST,
+ RTFSISOMAKERCMD_OPT_JIGDO_MIN_FILE_SIZE,
+ RTFSISOMAKERCMD_OPT_JIGDO_TEMPLATE,
+ RTFSISOMAKERCMD_OPT_JOLIET_CHARSET,
+ RTFSISOMAKERCMD_OPT_JOLIET_LEVEL,
+ RTFSISOMAKERCMD_OPT_JOLIET_LONG,
+ RTFSISOMAKERCMD_OPT_LOG_FILE,
+ RTFSISOMAKERCMD_OPT_MAX_ISO9660_FILENAMES,
+ RTFSISOMAKERCMD_OPT_MIPS_BOOT,
+ RTFSISOMAKERCMD_OPT_MIPSEL_BOOT,
+ RTFSISOMAKERCMD_OPT_NEW_DIR_MODE,
+ RTFSISOMAKERCMD_OPT_NO_BACKUP_FILES,
+ RTFSISOMAKERCMD_OPT_NO_DETECT_HARDLINKS,
+ RTFSISOMAKERCMD_OPT_NO_ISO_TRANSLATE,
+ RTFSISOMAKERCMD_OPT_NO_PAD,
+ RTFSISOMAKERCMD_OPT_NO_RR,
+ RTFSISOMAKERCMD_OPT_NO_SPLIT_SYMLINK_COMPONENTS,
+ RTFSISOMAKERCMD_OPT_NO_SPLIT_SYMLINK_FIELDS,
+ RTFSISOMAKERCMD_OPT_OLD_ROOT,
+ RTFSISOMAKERCMD_OPT_OUTPUT_CHARSET,
+ RTFSISOMAKERCMD_OPT_PAD,
+ RTFSISOMAKERCMD_OPT_PATH_LIST,
+ RTFSISOMAKERCMD_OPT_PRINT_SIZE,
+ RTFSISOMAKERCMD_OPT_QUIET,
+ RTFSISOMAKERCMD_OPT_RELAXED_FILENAMES,
+ RTFSISOMAKERCMD_OPT_ROOT,
+ RTFSISOMAKERCMD_OPT_SORT,
+ RTFSISOMAKERCMD_OPT_SPARC_BOOT,
+ RTFSISOMAKERCMD_OPT_SPARC_LABEL,
+ RTFSISOMAKERCMD_OPT_SPLIT_OUTPUT,
+ RTFSISOMAKERCMD_OPT_STREAM_FILE_NAME,
+ RTFSISOMAKERCMD_OPT_STREAM_MEDIA_SIZE,
+ RTFSISOMAKERCMD_OPT_SUNX86_BOOT,
+ RTFSISOMAKERCMD_OPT_SUNX86_LABEL,
+ RTFSISOMAKERCMD_OPT_SYSTEM_ID,
+ RTFSISOMAKERCMD_OPT_TRANS_TBL_NAME,
+ RTFSISOMAKERCMD_OPT_UDF,
+ RTFSISOMAKERCMD_OPT_UID,
+ RTFSISOMAKERCMD_OPT_USE_FILE_VERSION,
+ RTFSISOMAKERCMD_OPT_VOLUME_ID,
+ RTFSISOMAKERCMD_OPT_VOLUME_SET_ID,
+ RTFSISOMAKERCMD_OPT_VOLUME_SET_SEQ_NO,
+ RTFSISOMAKERCMD_OPT_VOLUME_SET_SIZE,
+ RTFSISOMAKERCMD_OPT_END
+} RTFSISOMAKERCMDOPT;
+
+
+/**
+ * El Torito boot entry.
+ */
+typedef struct RTFSISOMKCMDELTORITOENTRY
+{
+ /** The type of this entry. */
+ enum
+ {
+ kEntryType_Invalid = 0,
+ kEntryType_Validation, /**< Same as kEntryType_SectionHeader, just hardcoded #0. */
+ kEntryType_SectionHeader,
+ kEntryType_Default, /**< Same as kEntryType_Section, just hardcoded #1. */
+ kEntryType_Section
+ } enmType;
+ /** Type specific data. */
+ union
+ {
+ struct
+ {
+ /** The platform ID (ISO9660_ELTORITO_PLATFORM_ID_XXX). */
+ uint8_t idPlatform;
+ /** Some string for the header. */
+ const char *pszString;
+ } Validation,
+ SectionHeader;
+ struct
+ {
+ /** The name of the boot image wihtin the ISO (-b option). */
+ const char *pszImageNameInIso;
+ /** The object ID of the image in the ISO. This is set to UINT32_MAX when
+ * pszImageNameInIso is used (i.e. -b option) and we've delayed everything
+ * boot related till after all files have been added to the image. */
+ uint32_t idxImageObj;
+ /** Whether to insert boot info table into the image. */
+ bool fInsertBootInfoTable;
+ /** Bootble or not. Possible to make BIOS set up emulation w/o booting it. */
+ bool fBootable;
+ /** The media type (ISO9660_ELTORITO_BOOT_MEDIA_TYPE_XXX). */
+ uint8_t bBootMediaType;
+ /** File system / partition type. */
+ uint8_t bSystemType;
+ /** Load address divided by 0x10. */
+ uint16_t uLoadSeg;
+ /** Number of sectors (512) to load. */
+ uint16_t cSectorsToLoad;
+ } Section,
+ Default;
+ } u;
+} RTFSISOMKCMDELTORITOENTRY;
+/** Pointer to an el torito boot entry. */
+typedef RTFSISOMKCMDELTORITOENTRY *PRTFSISOMKCMDELTORITOENTRY;
+
+/**
+ * ISO maker command options & state.
+ */
+typedef struct RTFSISOMAKERCMDOPTS
+{
+ /** The handle to the ISO maker. */
+ RTFSISOMAKER hIsoMaker;
+ /** Set if we're creating a virtual image maker, i.e. producing something
+ * that is going to be read from only and not written to disk. */
+ bool fVirtualImageMaker;
+ /** Extended error info. This is a stderr alternative for the
+ * fVirtualImageMaker case (stdout goes to LogRel). */
+ PRTERRINFO pErrInfo;
+
+ /** The output file.
+ * This is NULL when fVirtualImageMaker is set. */
+ const char *pszOutFile;
+ /** Special buffer size to use for testing the ISO maker code reading. */
+ uint32_t cbOutputReadBuffer;
+ /** Use random output read buffer size. cbOutputReadBuffer works as maximum
+ * when this is enabled. */
+ bool fRandomOutputReadBufferSize;
+ /** Do output verification, but do it in random order if non-zero. The
+ * values gives the block size to use. */
+ uint32_t cbRandomOrderVerifciationBlock;
+
+ /** Index of the top source stack entry, -1 if empty. */
+ int32_t iSrcStack;
+ struct
+ {
+ /** The root VFS dir or the CWD for relative paths. */
+ RTVFSDIR hSrcDir;
+ /** The current source VFS, NIL_RTVFS if the regular file system is used. */
+ RTVFS hSrcVfs;
+ /** The specifier for hSrcVfs (error messages). */
+ const char *pszSrcVfs;
+ /** The option for hSrcVfs.
+ * This is NULL for a CWD passed via the API that shouldn't be popped. */
+ const char *pszSrcVfsOption;
+ } aSrcStack[5];
+
+ /** @name Processing of inputs
+ * @{ */
+ /** The namespaces (RTFSISOMAKER_NAMESPACE_XXX) we're currently adding
+ * input to. */
+ uint32_t fDstNamespaces;
+ /** The number of name specifiers we're currently operating with. */
+ uint32_t cNameSpecifiers;
+ /** Name specifier configurations.
+ * For instance given "name0=name1=name2=name3=source-file" we will add
+ * source-file to the image with name0 as the name in the namespace and
+ * sub-name specified by aNameSpecifiers[0], name1 in aNameSpecifiers[1],
+ * and so on. This allows exact control over which names a file will
+ * have in each namespace (primary-iso, joliet, udf, hfs) and sub-namespace
+ * (rock-ridge, trans.tbl).
+ */
+ uint32_t afNameSpecifiers[RTFSISOMAKERCMD_MAX_NAMES];
+ /** The forced directory mode. */
+ RTFMODE fDirMode;
+ /** Set if fDirMode should be applied. */
+ bool fDirModeActive;
+ /** Set if fFileMode should be applied. */
+ bool fFileModeActive;
+ /** The force file mode. */
+ RTFMODE fFileMode;
+ /** @} */
+
+ /** @name Booting related options and state.
+ * @{ */
+ /** Number of boot catalog entries (aBootCatEntries). */
+ uint32_t cBootCatEntries;
+ /** Boot catalog entries. */
+ RTFSISOMKCMDELTORITOENTRY aBootCatEntries[64];
+ /** @} */
+
+ /** @name Filtering
+ * @{ */
+ /** The trans.tbl filename when enabled. We must not import these files. */
+ const char *pszTransTbl;
+ /** @} */
+
+ /** Number of items (files, directories, images, whatever) we've added. */
+ uint32_t cItemsAdded;
+} RTFSISOMAKERCMDOPTS;
+typedef RTFSISOMAKERCMDOPTS *PRTFSISOMAKERCMDOPTS;
+typedef RTFSISOMAKERCMDOPTS const *PCRTFSISOMAKERCMDOPTS;
+
+
+/**
+ * One parsed name.
+ */
+typedef struct RTFSISOMKCMDPARSEDNAME
+{
+ /** Copy of the corresponding RTFSISOMAKERCMDOPTS::afNameSpecifiers
+ * value. */
+ uint32_t fNameSpecifiers;
+ /** The length of the specified path. */
+ uint32_t cchPath;
+ /** Specified path. */
+ char szPath[RTPATH_MAX];
+} RTFSISOMKCMDPARSEDNAME;
+/** Pointer to a parsed name. */
+typedef RTFSISOMKCMDPARSEDNAME *PRTFSISOMKCMDPARSEDNAME;
+/** Pointer to a const parsed name. */
+typedef RTFSISOMKCMDPARSEDNAME const *PCRTFSISOMKCMDPARSEDNAME;
+
+
+/**
+ * Parsed names.
+ */
+typedef struct RTFSISOMKCMDPARSEDNAMES
+{
+ /** Number of names. */
+ uint32_t cNames;
+ /** Number of names with the source. */
+ uint32_t cNamesWithSrc;
+ /** Special source types.
+ * Used for conveying commands to do on names intead of adding a source.
+ * Only used when adding generic stuff w/o any options involved. */
+ enum
+ {
+ kSrcType_None,
+ kSrcType_Normal,
+ kSrcType_NormalSrcStack,
+ kSrcType_Remove,
+ kSrcType_MustRemove
+ } enmSrcType;
+ /** The parsed names. */
+ RTFSISOMKCMDPARSEDNAME aNames[RTFSISOMAKERCMD_MAX_NAMES + 1];
+} RTFSISOMKCMDPARSEDNAMES;
+/** Pointer to parsed names. */
+typedef RTFSISOMKCMDPARSEDNAMES *PRTFSISOMKCMDPARSEDNAMES;
+/** Pointer to const parsed names. */
+typedef RTFSISOMKCMDPARSEDNAMES *PCRTFSISOMKCMDPARSEDNAMES;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/*
+ * Parse the command line. This is similar to genisoimage and mkisofs,
+ * thus the single dash long name aliases.
+ */
+static const RTGETOPTDEF g_aRtFsIsoMakerOptions[] =
+{
+ /*
+ * Unique IPRT ISO maker options.
+ */
+ { "--name-setup", RTFSISOMAKERCMD_OPT_NAME_SETUP, RTGETOPT_REQ_STRING },
+ { "--name-setup-from-import", RTFSISOMAKERCMD_OPT_NAME_SETUP_FROM_IMPORT, RTGETOPT_REQ_NOTHING },
+ { "--import-iso", RTFSISOMAKERCMD_OPT_IMPORT_ISO, RTGETOPT_REQ_STRING },
+ { "--push-iso", RTFSISOMAKERCMD_OPT_PUSH_ISO, RTGETOPT_REQ_STRING },
+ { "--push-iso-no-joliet", RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_JOLIET, RTGETOPT_REQ_STRING },
+ { "--push-iso-no-rock", RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK, RTGETOPT_REQ_STRING },
+ { "--push-iso-no-rock-no-joliet", RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK_NO_JOLIET, RTGETOPT_REQ_STRING },
+ { "--pop", RTFSISOMAKERCMD_OPT_POP, RTGETOPT_REQ_NOTHING },
+
+ { "--rock-ridge", RTFSISOMAKERCMD_OPT_ROCK_RIDGE, RTGETOPT_REQ_NOTHING },
+ { "--limited-rock-ridge", RTFSISOMAKERCMD_OPT_LIMITED_ROCK_RIDGE, RTGETOPT_REQ_NOTHING },
+ { "--no-rock-ridge", RTFSISOMAKERCMD_OPT_NO_ROCK_RIDGE, RTGETOPT_REQ_NOTHING },
+ { "--no-joliet", RTFSISOMAKERCMD_OPT_NO_JOLIET, RTGETOPT_REQ_NOTHING },
+ { "--joliet-ucs-level", RTFSISOMAKERCMD_OPT_JOLIET_LEVEL, RTGETOPT_REQ_UINT8 },
+
+ { "--rational-attribs", RTFSISOMAKERCMD_OPT_RATIONAL_ATTRIBS, RTGETOPT_REQ_NOTHING },
+ { "--strict-attribs", RTFSISOMAKERCMD_OPT_STRICT_ATTRIBS, RTGETOPT_REQ_NOTHING },
+ { "--no-file-mode", RTFSISOMAKERCMD_OPT_NO_FILE_MODE, RTGETOPT_REQ_NOTHING },
+ { "--no-dir-mode", RTFSISOMAKERCMD_OPT_NO_DIR_MODE, RTGETOPT_REQ_NOTHING },
+ { "--chmod", RTFSISOMAKERCMD_OPT_CHMOD, RTGETOPT_REQ_STRING },
+ { "--chown", RTFSISOMAKERCMD_OPT_CHOWN, RTGETOPT_REQ_STRING },
+ { "--chgrp", RTFSISOMAKERCMD_OPT_CHGRP, RTGETOPT_REQ_STRING },
+
+ { "--eltorito-new-entry", RTFSISOMAKERCMD_OPT_ELTORITO_NEW_ENTRY, RTGETOPT_REQ_NOTHING },
+ { "--eltorito-add-image", RTFSISOMAKERCMD_OPT_ELTORITO_ADD_IMAGE, RTGETOPT_REQ_STRING },
+ { "--eltorito-floppy-12", RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_12, RTGETOPT_REQ_NOTHING },
+ { "--eltorito-floppy-144", RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_144, RTGETOPT_REQ_NOTHING },
+ { "--eltorito-floppy-288", RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_288, RTGETOPT_REQ_NOTHING },
+
+ { "--iprt-iso-maker-file-marker", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+ { "--iprt-iso-maker-file-marker-ms", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+ { "--iprt-iso-maker-file-marker-ms-crt", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+ { "--iprt-iso-maker-file-marker-bourne", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+ { "--iprt-iso-maker-file-marker-bourne-sh", RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER, RTGETOPT_REQ_STRING },
+
+ { "--output-buffer-size", RTFSISOMAKERCMD_OPT_OUTPUT_BUFFER_SIZE, RTGETOPT_REQ_UINT32 },
+ { "--random-output-buffer-size", RTFSISOMAKERCMD_OPT_RANDOM_OUTPUT_BUFFER_SIZE, RTGETOPT_REQ_NOTHING },
+ { "--random-order-verification", RTFSISOMAKERCMD_OPT_RANDOM_ORDER_VERIFICATION, RTGETOPT_REQ_UINT32 },
+
+#define DD(a_szLong, a_chShort, a_fFlags) { a_szLong, a_chShort, a_fFlags }, { "-" a_szLong, a_chShort, a_fFlags }
+
+ /*
+ * genisoimage/mkisofs compatibility options we've implemented:
+ */
+ /* booting: */
+ { "--generic-boot", 'G', RTGETOPT_REQ_STRING },
+ DD("-eltorito-boot", 'b', RTGETOPT_REQ_STRING ),
+ DD("-eltorito-alt-boot", RTFSISOMAKERCMD_OPT_ELTORITO_NEW_ENTRY, RTGETOPT_REQ_NOTHING ),
+ DD("-eltorito-platform-id", RTFSISOMAKERCMD_OPT_ELTORITO_PLATFORM_ID, RTGETOPT_REQ_STRING ),
+ DD("-hard-disk-boot", RTFSISOMAKERCMD_OPT_ELTORITO_HARD_DISK_BOOT, RTGETOPT_REQ_NOTHING ),
+ DD("-no-emulation-boot", RTFSISOMAKERCMD_OPT_ELTORITO_NO_EMULATION_BOOT, RTGETOPT_REQ_NOTHING ),
+ DD("-no-boot", RTFSISOMAKERCMD_OPT_ELTORITO_NO_BOOT, RTGETOPT_REQ_NOTHING ),
+ DD("-boot-load-seg", RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SEG, RTGETOPT_REQ_UINT16 ),
+ DD("-boot-load-size", RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SIZE, RTGETOPT_REQ_UINT16 ),
+ DD("-boot-info-table", RTFSISOMAKERCMD_OPT_ELTORITO_INFO_TABLE, RTGETOPT_REQ_NOTHING ),
+ { "--boot-catalog", 'c', RTGETOPT_REQ_STRING },
+
+ /* String props: */
+ DD("-abstract", RTFSISOMAKERCMD_OPT_ABSTRACT_FILE_ID, RTGETOPT_REQ_STRING ),
+ { "--application-id", 'A', RTGETOPT_REQ_STRING },
+ DD("-biblio", RTFSISOMAKERCMD_OPT_BIBLIOGRAPHIC_FILE_ID, RTGETOPT_REQ_STRING ),
+ DD("-copyright", RTFSISOMAKERCMD_OPT_COPYRIGHT_FILE_ID, RTGETOPT_REQ_STRING ),
+ DD("-publisher", 'P', RTGETOPT_REQ_STRING ),
+ { "--preparer", 'p', RTGETOPT_REQ_STRING },
+ DD("-sysid", RTFSISOMAKERCMD_OPT_SYSTEM_ID, RTGETOPT_REQ_STRING ),
+ { "--volume-id", RTFSISOMAKERCMD_OPT_VOLUME_ID, RTGETOPT_REQ_STRING }, /* should've been '-V' */
+ DD("-volid", RTFSISOMAKERCMD_OPT_VOLUME_ID, RTGETOPT_REQ_STRING ),
+ DD("-volset", RTFSISOMAKERCMD_OPT_VOLUME_SET_ID, RTGETOPT_REQ_STRING ),
+
+ /* Other: */
+ DD("-file-mode", RTFSISOMAKERCMD_OPT_FILE_MODE, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT ),
+ DD("-dir-mode", RTFSISOMAKERCMD_OPT_DIR_MODE, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT ),
+ DD("-new-dir-mode", RTFSISOMAKERCMD_OPT_NEW_DIR_MODE, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT ),
+ DD("-graft-points", RTFSISOMAKERCMD_OPT_GRAFT_POINTS, RTGETOPT_REQ_NOTHING ),
+ DD("--iso-level", RTFSISOMAKERCMD_OPT_ISO_LEVEL, RTGETOPT_REQ_UINT8 ),
+ { "--long-names", 'l', RTGETOPT_REQ_NOTHING },
+ { "--output", 'o', RTGETOPT_REQ_STRING },
+ { "--joliet", 'J', RTGETOPT_REQ_NOTHING },
+ DD("-ucs-level", RTFSISOMAKERCMD_OPT_JOLIET_LEVEL, RTGETOPT_REQ_UINT8 ),
+ DD("-rock", 'R', RTGETOPT_REQ_NOTHING ),
+ DD("-rational-rock", 'r', RTGETOPT_REQ_NOTHING ),
+ DD("-pad", RTFSISOMAKERCMD_OPT_PAD, RTGETOPT_REQ_NOTHING ),
+ DD("-no-pad", RTFSISOMAKERCMD_OPT_NO_PAD, RTGETOPT_REQ_NOTHING ),
+
+ /*
+ * genisoimage/mkisofs compatibility:
+ */
+ DD("-allow-limited-size", RTFSISOMAKERCMD_OPT_ALLOW_LIMITED_SIZE, RTGETOPT_REQ_NOTHING ),
+ DD("-allow-leading-dots", RTFSISOMAKERCMD_OPT_ALLOW_LEADING_DOTS, RTGETOPT_REQ_NOTHING ),
+ DD("-ldots", RTFSISOMAKERCMD_OPT_ALLOW_LEADING_DOTS, RTGETOPT_REQ_NOTHING ),
+ DD("-allow-lowercase", RTFSISOMAKERCMD_OPT_ALLOW_LOWERCASE, RTGETOPT_REQ_NOTHING ),
+ DD("-allow-multidot", RTFSISOMAKERCMD_OPT_ALLOW_MULTI_DOT, RTGETOPT_REQ_NOTHING ),
+ DD("-cache-inodes", RTFSISOMAKERCMD_OPT_DETECT_HARDLINKS, RTGETOPT_REQ_NOTHING ),
+ DD("-no-cache-inodes", RTFSISOMAKERCMD_OPT_NO_DETECT_HARDLINKS, RTGETOPT_REQ_NOTHING ),
+ DD("-alpha-boot", RTFSISOMAKERCMD_OPT_ALPHA_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-hppa-bootloader", RTFSISOMAKERCMD_OPT_HPPA_BOOTLOADER, RTGETOPT_REQ_STRING ),
+ DD("-hppa-cmdline", RTFSISOMAKERCMD_OPT_HPPA_CMDLINE, RTGETOPT_REQ_STRING ),
+ DD("-hppa-kernel-32", RTFSISOMAKERCMD_OPT_HPPA_KERNEL_32, RTGETOPT_REQ_STRING ),
+ DD("-hppa-kernel-64", RTFSISOMAKERCMD_OPT_HPPA_KERNEL_64, RTGETOPT_REQ_STRING ),
+ DD("-hppa-ramdisk", RTFSISOMAKERCMD_OPT_HPPA_RAMDISK, RTGETOPT_REQ_STRING ),
+ DD("-mips-boot", RTFSISOMAKERCMD_OPT_MIPS_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-mipsel-boot", RTFSISOMAKERCMD_OPT_MIPSEL_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-sparc-boot", 'B', RTGETOPT_REQ_STRING ),
+ { "--cd-extra", 'C', RTGETOPT_REQ_STRING },
+ DD("-check-oldnames", RTFSISOMAKERCMD_OPT_CHECK_OLD_NAMES, RTGETOPT_REQ_NOTHING ),
+ DD("-check-session", RTFSISOMAKERCMD_OPT_CHECK_SESSION, RTGETOPT_REQ_STRING ),
+ { "--dont-append-dot", 'd', RTGETOPT_REQ_NOTHING },
+ { "--deep-directories", 'D', RTGETOPT_REQ_NOTHING },
+ DD("-dvd-video", RTFSISOMAKERCMD_OPT_DVD_VIDEO, RTGETOPT_REQ_NOTHING ),
+ DD("-follow-symlinks", 'f', RTGETOPT_REQ_NOTHING ),
+ DD("-gid", RTFSISOMAKERCMD_OPT_GID, RTGETOPT_REQ_UINT32 ),
+ DD("-gui", RTFSISOMAKERCMD_OPT_GUI, RTGETOPT_REQ_NOTHING ),
+ DD("-hide", RTFSISOMAKERCMD_OPT_HIDE, RTGETOPT_REQ_STRING ),
+ DD("-hide-list", RTFSISOMAKERCMD_OPT_HIDE_LIST, RTGETOPT_REQ_STRING ),
+ DD("-hidden", RTFSISOMAKERCMD_OPT_HIDDEN, RTGETOPT_REQ_STRING ),
+ DD("-hidden-list", RTFSISOMAKERCMD_OPT_HIDDEN_LIST, RTGETOPT_REQ_STRING ),
+ DD("-hide-joliet", RTFSISOMAKERCMD_OPT_HIDE_JOLIET, RTGETOPT_REQ_STRING ),
+ DD("-hide-joliet-list", RTFSISOMAKERCMD_OPT_HIDE_JOLIET_LIST, RTGETOPT_REQ_STRING ),
+ DD("-hide-joliet-trans-tbl", RTFSISOMAKERCMD_OPT_HIDE_JOLIET_TRANS_TBL, RTGETOPT_REQ_NOTHING ),
+ DD("-hide-rr-moved", RTFSISOMAKERCMD_OPT_HIDE_RR_MOVED, RTGETOPT_REQ_NOTHING ),
+ DD("-input-charset", RTFSISOMAKERCMD_OPT_INPUT_CHARSET, RTGETOPT_REQ_STRING ),
+ DD("-output-charset", RTFSISOMAKERCMD_OPT_OUTPUT_CHARSET, RTGETOPT_REQ_STRING ),
+ DD("-joliet-long", RTFSISOMAKERCMD_OPT_JOLIET_LONG, RTGETOPT_REQ_NOTHING ),
+ DD("-jcharset", RTFSISOMAKERCMD_OPT_JOLIET_CHARSET, RTGETOPT_REQ_STRING ),
+ { "--leading-dot", 'L', RTGETOPT_REQ_NOTHING },
+ DD("-jigdo-jigdo", RTFSISOMAKERCMD_OPT_JIGDO_JIGDO, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-template", RTFSISOMAKERCMD_OPT_JIGDO_TEMPLATE, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-min-file-size", RTFSISOMAKERCMD_OPT_JIGDO_MIN_FILE_SIZE, RTGETOPT_REQ_UINT64 ),
+ DD("-jigdo-force-md5", RTFSISOMAKERCMD_OPT_JIGDO_FORCE_MD5, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-exclude", RTFSISOMAKERCMD_OPT_JIGDO_EXCLUDE, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-map", RTFSISOMAKERCMD_OPT_JIGDO_MAP, RTGETOPT_REQ_STRING ),
+ DD("-md5-list", RTFSISOMAKERCMD_OPT_JIGDO_MD5_LIST, RTGETOPT_REQ_STRING ),
+ DD("-jigdo-template-compress", RTFSISOMAKERCMD_OPT_JIGDO_COMPRESS, RTGETOPT_REQ_STRING ),
+ DD("-log-file", RTFSISOMAKERCMD_OPT_LOG_FILE, RTGETOPT_REQ_STRING ),
+ { "--exclude", 'm', RTGETOPT_REQ_STRING },
+ { "--exclude", 'x', RTGETOPT_REQ_STRING },
+ DD("-exclude-list", RTFSISOMAKERCMD_OPT_EXCLUDE_LIST, RTGETOPT_REQ_STRING ),
+ DD("-max-iso9660-filenames", RTFSISOMAKERCMD_OPT_MAX_ISO9660_FILENAMES, RTGETOPT_REQ_NOTHING ),
+ { "--merge", 'M', RTGETOPT_REQ_STRING },
+ DD("-dev", 'M', RTGETOPT_REQ_STRING ),
+ { "--omit-version-numbers", 'N', RTGETOPT_REQ_NOTHING },
+ DD("-nobak", RTFSISOMAKERCMD_OPT_NO_BACKUP_FILES, RTGETOPT_REQ_NOTHING ),
+ DD("-no-bak", RTFSISOMAKERCMD_OPT_NO_BACKUP_FILES, RTGETOPT_REQ_NOTHING ),
+ DD("-force-rr", RTFSISOMAKERCMD_OPT_FORCE_RR, RTGETOPT_REQ_NOTHING ),
+ DD("-no-rr", RTFSISOMAKERCMD_OPT_NO_RR, RTGETOPT_REQ_NOTHING ),
+ DD("-no-split-symlink-components", RTFSISOMAKERCMD_OPT_NO_SPLIT_SYMLINK_COMPONENTS, RTGETOPT_REQ_NOTHING ),
+ DD("-no-split-symlink-fields", RTFSISOMAKERCMD_OPT_NO_SPLIT_SYMLINK_FIELDS, RTGETOPT_REQ_NOTHING ),
+ DD("-path-list", RTFSISOMAKERCMD_OPT_PATH_LIST, RTGETOPT_REQ_STRING ),
+ DD("-print-size", RTFSISOMAKERCMD_OPT_PRINT_SIZE, RTGETOPT_REQ_NOTHING ),
+ DD("-quiet", RTFSISOMAKERCMD_OPT_QUIET, RTGETOPT_REQ_NOTHING ),
+ DD("-relaxed-filenames", RTFSISOMAKERCMD_OPT_RELAXED_FILENAMES, RTGETOPT_REQ_NOTHING ),
+ DD("-root", RTFSISOMAKERCMD_OPT_ROOT, RTGETOPT_REQ_STRING ),
+ DD("-old-root", RTFSISOMAKERCMD_OPT_OLD_ROOT, RTGETOPT_REQ_STRING ),
+ DD("-sort", RTFSISOMAKERCMD_OPT_SORT, RTGETOPT_REQ_STRING ),
+ DD("-sparc-boot", RTFSISOMAKERCMD_OPT_SPARC_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-sparc-label", RTFSISOMAKERCMD_OPT_SPARC_LABEL, RTGETOPT_REQ_STRING ),
+ DD("-split-output", RTFSISOMAKERCMD_OPT_SPLIT_OUTPUT, RTGETOPT_REQ_NOTHING ),
+ DD("-stream-media-size", RTFSISOMAKERCMD_OPT_STREAM_MEDIA_SIZE, RTGETOPT_REQ_UINT64 ),
+ DD("-stream-file-name", RTFSISOMAKERCMD_OPT_STREAM_FILE_NAME, RTGETOPT_REQ_STRING ),
+ DD("-sunx86-boot", RTFSISOMAKERCMD_OPT_SUNX86_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-sunx86-label", RTFSISOMAKERCMD_OPT_SUNX86_LABEL, RTGETOPT_REQ_STRING ),
+ { "--trans-tbl", 'T', RTGETOPT_REQ_NOTHING },
+ DD("-table-name", RTFSISOMAKERCMD_OPT_TRANS_TBL_NAME, RTGETOPT_REQ_STRING ),
+ DD("-udf", RTFSISOMAKERCMD_OPT_UDF, RTGETOPT_REQ_NOTHING ),
+ DD("-uid", RTFSISOMAKERCMD_OPT_UID, RTGETOPT_REQ_UINT32 ),
+ DD("-use-fileversion", RTFSISOMAKERCMD_OPT_USE_FILE_VERSION, RTGETOPT_REQ_NOTHING ),
+ { "--untranslated-filenames", 'U', RTGETOPT_REQ_NOTHING },
+ DD("-no-iso-translate", RTFSISOMAKERCMD_OPT_NO_ISO_TRANSLATE, RTGETOPT_REQ_NOTHING ),
+ DD("-volset-size", RTFSISOMAKERCMD_OPT_VOLUME_SET_SIZE, RTGETOPT_REQ_UINT32 ),
+ DD("-volset-seqno", RTFSISOMAKERCMD_OPT_VOLUME_SET_SEQ_NO, RTGETOPT_REQ_UINT32 ),
+ { "--transpared-compression", 'z', RTGETOPT_REQ_NOTHING },
+
+ /* HFS and ISO-9660 apple extensions. */
+ DD("-hfs", RTFSISOMAKERCMD_OPT_HFS_ENABLE, RTGETOPT_REQ_NOTHING ),
+ DD("-apple", RTFSISOMAKERCMD_OPT_APPLE, RTGETOPT_REQ_NOTHING ),
+ DD("-map", RTFSISOMAKERCMD_OPT_HFS_MAP, RTGETOPT_REQ_STRING ),
+ DD("-magic", RTFSISOMAKERCMD_OPT_HFS_MAGIC, RTGETOPT_REQ_STRING ),
+ DD("-hfs-creator", RTFSISOMAKERCMD_OPT_HFS_CREATOR, RTGETOPT_REQ_STRING ),
+ DD("-hfs-type", RTFSISOMAKERCMD_OPT_HFS_TYPE, RTGETOPT_REQ_STRING ),
+ DD("-probe", RTFSISOMAKERCMD_OPT_HFS_PROBE, RTGETOPT_REQ_NOTHING ),
+ DD("-no-desktop", RTFSISOMAKERCMD_OPT_HFS_NO_DESKTOP, RTGETOPT_REQ_NOTHING ),
+ DD("-mac-name", RTFSISOMAKERCMD_OPT_HFS_MAC_NAME, RTGETOPT_REQ_NOTHING ),
+ DD("-boot-hfs-file", RTFSISOMAKERCMD_OPT_HFS_BOOT_FILE, RTGETOPT_REQ_STRING ),
+ DD("-part", RTFSISOMAKERCMD_OPT_HFS_PART, RTGETOPT_REQ_NOTHING ),
+ DD("-auto", RTFSISOMAKERCMD_OPT_HFS_AUTO, RTGETOPT_REQ_STRING ),
+ DD("-cluster-size", RTFSISOMAKERCMD_OPT_HFS_CLUSTER_SIZE, RTGETOPT_REQ_UINT32 ),
+ DD("-hide-hfs", RTFSISOMAKERCMD_OPT_HFS_HIDE, RTGETOPT_REQ_STRING ),
+ DD("-hide-hfs-list", RTFSISOMAKERCMD_OPT_HFS_HIDE_LIST, RTGETOPT_REQ_STRING ),
+ DD("-hfs-volid", RTFSISOMAKERCMD_OPT_HFS_VOL_ID, RTGETOPT_REQ_STRING ),
+ DD("-icon-position", RTFSISOMAKERCMD_OPT_HFS_ICON_POSITION, RTGETOPT_REQ_NOTHING ),
+ DD("-root-info", RTFSISOMAKERCMD_OPT_HFS_ROOT_INFO, RTGETOPT_REQ_STRING ),
+ DD("-prep-boot", RTFSISOMAKERCMD_OPT_HFS_PREP_BOOT, RTGETOPT_REQ_STRING ),
+ DD("-chrp-boot", RTFSISOMAKERCMD_OPT_HFS_CHRP_BOOT, RTGETOPT_REQ_NOTHING ),
+ DD("-input-hfs-charset", RTFSISOMAKERCMD_OPT_HFS_INPUT_CHARSET, RTGETOPT_REQ_STRING ),
+ DD("-output-hfs-charset", RTFSISOMAKERCMD_OPT_HFS_OUTPUT_CHARSET, RTGETOPT_REQ_STRING ),
+ DD("-hfs-unlock", RTFSISOMAKERCMD_OPT_HFS_UNLOCK, RTGETOPT_REQ_NOTHING ),
+ DD("-hfs-bless", RTFSISOMAKERCMD_OPT_HFS_BLESS, RTGETOPT_REQ_STRING ),
+ DD("-hfs-parms", RTFSISOMAKERCMD_OPT_HFS_PARMS, RTGETOPT_REQ_STRING ),
+ { "--cap", RTFSISOMAKERCMD_OPT_HFS_CAP, RTGETOPT_REQ_NOTHING },
+ { "--netatalk", RTFSISOMAKERCMD_OPT_HFS_NETATALK, RTGETOPT_REQ_NOTHING },
+ { "--double", RTFSISOMAKERCMD_OPT_HFS_DOUBLE, RTGETOPT_REQ_NOTHING },
+ { "--ethershare", RTFSISOMAKERCMD_OPT_HFS_ETHERSHARE, RTGETOPT_REQ_NOTHING },
+ { "--ushare", RTFSISOMAKERCMD_OPT_HFS_USHARE, RTGETOPT_REQ_NOTHING },
+ { "--exchange", RTFSISOMAKERCMD_OPT_HFS_EXCHANGE, RTGETOPT_REQ_NOTHING },
+ { "--sgi", RTFSISOMAKERCMD_OPT_HFS_SGI, RTGETOPT_REQ_NOTHING },
+ { "--xinet", RTFSISOMAKERCMD_OPT_HFS_XINET, RTGETOPT_REQ_NOTHING },
+ { "--macbin", RTFSISOMAKERCMD_OPT_HFS_MACBIN, RTGETOPT_REQ_NOTHING },
+ { "--single", RTFSISOMAKERCMD_OPT_HFS_SINGLE, RTGETOPT_REQ_NOTHING },
+ { "--dave", RTFSISOMAKERCMD_OPT_HFS_DAVE, RTGETOPT_REQ_NOTHING },
+ { "--sfm", RTFSISOMAKERCMD_OPT_HFS_SFM, RTGETOPT_REQ_NOTHING },
+ { "--osx-double", RTFSISOMAKERCMD_OPT_HFS_OSX_DOUBLE, RTGETOPT_REQ_NOTHING },
+ { "--osx-hfs", RTFSISOMAKERCMD_OPT_HFS_OSX_HFS, RTGETOPT_REQ_NOTHING },
+#undef DD
+};
+
+#ifndef RT_OS_OS2 /* fixme */
+# include "isomakercmd-man.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtFsIsoMakerCmdParse(PRTFSISOMAKERCMDOPTS pOpts, unsigned cArgs, char **papszArgs, unsigned cDepth);
+
+
+/**
+ * Wrapper around RTErrInfoSetV / RTMsgErrorV.
+ *
+ * @returns @a rc
+ * @param pOpts The ISO maker command instance.
+ * @param rc The return code.
+ * @param pszFormat The message format.
+ * @param ... The message format arguments.
+ */
+static int rtFsIsoMakerCmdErrorRc(PRTFSISOMAKERCMDOPTS pOpts, int rc, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pOpts->pErrInfo)
+ RTErrInfoSetV(pOpts->pErrInfo, rc, pszFormat, va);
+ else
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Wrapper around RTErrInfoSetV / RTMsgErrorV for doing the job of
+ * RTVfsChainMsgError.
+ *
+ * @returns @a rc
+ * @param pOpts The ISO maker command instance.
+ * @param pszFunction The API called.
+ * @param pszSpec The VFS chain specification or file path passed to the.
+ * @param rc The return code.
+ * @param offError The error offset value returned (0 if not captured).
+ * @param pErrInfo Additional error information. Optional.
+ */
+static int rtFsIsoMakerCmdChainError(PRTFSISOMAKERCMDOPTS pOpts, const char *pszFunction, const char *pszSpec, int rc,
+ uint32_t offError, PRTERRINFO pErrInfo)
+{
+ if (RTErrInfoIsSet(pErrInfo))
+ {
+ if (offError > 0)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "%s failed with rc=%Rrc: %s\n"
+ " '%s'\n"
+ " %*s^",
+ pszFunction, rc, pErrInfo->pszMsg, pszSpec, offError, "");
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s failed to open '%s': %Rrc: %s",
+ pszFunction, pszSpec, rc, pErrInfo->pszMsg);
+ }
+ else
+ {
+ if (offError > 0)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "%s failed with rc=%Rrc:\n"
+ " '%s'\n"
+ " %*s^",
+ pszFunction, rc, pszSpec, offError, "");
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s failed to open '%s': %Rrc", pszFunction, pszSpec, rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Wrapper around RTErrInfoSetV / RTMsgErrorV for displaying syntax errors.
+ *
+ * @returns VERR_INVALID_PARAMETER
+ * @param pOpts The ISO maker command instance.
+ * @param pszFormat The message format.
+ * @param ... The message format arguments.
+ */
+static int rtFsIsoMakerCmdSyntaxError(PRTFSISOMAKERCMDOPTS pOpts, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pOpts->pErrInfo)
+ RTErrInfoSetV(pOpts->pErrInfo, VERR_INVALID_PARAMETER, pszFormat, va);
+ else
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return VERR_INVALID_PARAMETER;
+}
+
+
+/**
+ * Wrapper around RTPrintfV / RTLogRelPrintfV.
+ *
+ * @param pOpts The ISO maker command instance.
+ * @param pszFormat The message format.
+ * @param ... The message format arguments.
+ */
+static void rtFsIsoMakerPrintf(PRTFSISOMAKERCMDOPTS pOpts, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pOpts->pErrInfo)
+ RTLogRelPrintfV(pszFormat, va);
+ else
+ RTPrintfV(pszFormat, va);
+ va_end(va);
+}
+
+/**
+ * Deletes the state and returns @a rc.
+ *
+ * @returns @a rc.
+ * @param pOpts The ISO maker command instance to delete.
+ * @param rc The status code to return.
+ */
+static int rtFsIsoMakerCmdDeleteState(PRTFSISOMAKERCMDOPTS pOpts, int rc)
+{
+ if (pOpts->hIsoMaker != NIL_RTFSISOMAKER)
+ {
+ RTFsIsoMakerRelease(pOpts->hIsoMaker);
+ pOpts->hIsoMaker = NIL_RTFSISOMAKER;
+ }
+
+ while (pOpts->iSrcStack >= 0)
+ {
+ RTVfsDirRelease(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir);
+ RTVfsRelease(pOpts->aSrcStack[pOpts->iSrcStack].hSrcVfs);
+ pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir = NIL_RTVFSDIR;
+ pOpts->aSrcStack[pOpts->iSrcStack].hSrcVfs = NIL_RTVFS;
+ pOpts->iSrcStack--;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Print the usage.
+ *
+ * @param pOpts Options for print metho.
+ * @param pszProgName The program name.
+ */
+static void rtFsIsoMakerCmdUsage(PRTFSISOMAKERCMDOPTS pOpts, const char *pszProgName)
+{
+#ifndef RT_OS_OS2 /* fixme */
+ if (!pOpts->pErrInfo)
+ RTMsgRefEntryHelp(g_pStdOut, &g_viso);
+ else
+#endif
+ rtFsIsoMakerPrintf(pOpts, "Usage: %s [options] [@commands.rsp] <filespec...>\n",
+ RTPathFilename(pszProgName));
+}
+
+
+/**
+ * Verifies the image content by reading blocks in random order.
+ *
+ * This is for exercise the virtual ISO code better and test that we get the
+ * same data when reading something twice.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcFile The source file (virtual ISO).
+ * @param hVfsDstFile The destination file (image file on disk).
+ * @param cbImage The size of the ISO.
+ */
+static int rtFsIsoMakerCmdVerifyImageInRandomOrder(PRTFSISOMAKERCMDOPTS pOpts, RTVFSFILE hVfsSrcFile,
+ RTVFSFILE hVfsDstFile, uint64_t cbImage)
+{
+ /*
+ * Figure the buffer (block) size and allocate a bitmap for noting down blocks we've covered.
+ */
+ int rc;
+ size_t cbBuf = RT_MAX(pOpts->cbRandomOrderVerifciationBlock, 1);
+ uint64_t cBlocks64 = (cbImage + cbBuf - 1) / cbBuf;
+ if (cBlocks64 > _512M)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_OUT_OF_RANGE,
+ "verification block count too high: cBlocks=%#RX64 (cbBuf=%#zx), max 512M", cBlocks64, cbBuf);
+ uint32_t cBlocks = (uint32_t)cBlocks64;
+ uint32_t cbBitmap = (cBlocks + 63) / 8;
+ if (cbBitmap > _64M)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_OUT_OF_RANGE,
+ "verification bitmap too big: cbBitmap=%#RX32 (cbBuf=%#zx), max 64MB", cbBitmap, cbBuf);
+ void *pvSrcBuf = RTMemTmpAlloc(cbBuf);
+ void *pvDstBuf = RTMemTmpAlloc(cbBuf);
+ void *pvBitmap = RTMemTmpAllocZ(cbBitmap);
+ if (pvSrcBuf && pvDstBuf && pvBitmap)
+ {
+ /* Must set the unused bits in the top qword. */
+ for (uint32_t i = RT_ALIGN_32(cBlocks, 64) - 1; i >= cBlocks; i--)
+ ASMBitSet(pvBitmap, i);
+
+ /*
+ * Do the verification.
+ */
+ rtFsIsoMakerPrintf(pOpts, "Verifying image in random order using %zu (%#zx) byte blocks: %#RX32 in blocks\n",
+ cbBuf, cbBuf, cBlocks);
+
+ rc = VINF_SUCCESS;
+ uint64_t cLeft = cBlocks;
+ while (cLeft-- > 0)
+ {
+ /*
+ * Figure out which block to check next.
+ */
+ uint32_t iBlock = RTRandU32Ex(0, cBlocks - 1);
+ if (!ASMBitTestAndSet(pvBitmap, iBlock))
+ Assert(iBlock < cBlocks);
+ else
+ {
+ /* try 32 other random numbers. */
+ bool fBitSet;
+ unsigned cTries = 0;
+ do
+ {
+ iBlock = RTRandU32Ex(0, cBlocks - 1);
+ fBitSet = ASMBitTestAndSet(pvBitmap, iBlock);
+ } while (fBitSet && ++cTries < 32);
+ if (fBitSet)
+ {
+ /* Look for the next clear bit after it (with wrap around). */
+ int iHit = ASMBitNextClear(pvBitmap, RT_ALIGN_32(cBlocks, 64), iBlock);
+ Assert(iHit < (int32_t)cBlocks);
+ if (iHit < 0)
+ {
+ iHit = ASMBitFirstClear(pvBitmap, RT_ALIGN_32(iBlock, 64));
+ Assert(iHit < (int32_t)cBlocks);
+ }
+ if (iHit >= 0)
+ {
+ fBitSet = ASMBitTestAndSet(pvBitmap, iHit);
+ if (!fBitSet)
+ iBlock = iHit;
+ else
+ {
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_INTERNAL_ERROR_3,
+ "Bitmap weirdness: iHit=%#x iBlock=%#x cLeft=%#x cBlocks=%#x",
+ iHit, iBlock, cLeft, cBlocks);
+ if (!pOpts->pErrInfo)
+ RTMsgInfo("Bitmap: %#RX32 bytes\n%.*Rhxd", cbBitmap, cbBitmap, pvBitmap);
+ break;
+ }
+ }
+ else
+ {
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_INTERNAL_ERROR_2,
+ "Bitmap weirdness: iBlock=%#x cLeft=%#x cBlocks=%#x",
+ iBlock, cLeft, cBlocks);
+ if (!pOpts->pErrInfo)
+ RTMsgInfo("Bitmap: %#RX32 bytes\n%.*Rhxd", cbBitmap, cbBitmap, pvBitmap);
+ break;
+ }
+ }
+ }
+ Assert(ASMBitTest(pvBitmap, iBlock));
+
+ /*
+ * Figure out how much and where to read (last block fun).
+ */
+ uint64_t offBlock = iBlock * (uint64_t)cbBuf;
+ size_t cbToRead = cbBuf;
+ if (iBlock + 1 < cBlocks)
+ { /* likely */ }
+ else if (cbToRead > cbImage - offBlock)
+ cbToRead = (size_t)(cbImage - offBlock);
+ Assert(offBlock + cbToRead <= cbImage);
+
+ /*
+ * Read the blocks.
+ */
+ //RTPrintf("Reading block #%#x at %#RX64\n", iBlock, offBlock);
+ rc = RTVfsFileReadAt(hVfsDstFile, offBlock, pvDstBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ memset(pvSrcBuf, 0xdd, cbBuf);
+ rc = RTVfsFileReadAt(hVfsSrcFile, offBlock, pvSrcBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(pvDstBuf, pvSrcBuf, cbToRead) == 0)
+ continue;
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_MISMATCH,
+ "Block #%#x differs! offBlock=%#RX64 cbToRead=%#zu\n"
+ "Virtual ISO (source):\n%.*Rhxd\nWritten ISO (destination):\n%.*Rhxd",
+ iBlock, offBlock, cbToRead, cbToRead, pvSrcBuf, cbToRead, pvDstBuf);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "Error reading %#zx bytes source (virtual ISO) block #%#x at %#RX64: %Rrc",
+ cbToRead, iBlock, offBlock, rc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "Error reading %#zx bytes destination (written ISO) block #%#x at %#RX64: %Rrc",
+ cbToRead, iBlock, offBlock, rc);
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ rtFsIsoMakerPrintf(pOpts, "Written image verified fine!\n");
+ }
+ else if (!pvSrcBuf || !pvDstBuf)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "RTMemTmpAlloc(%#zx) failed", cbBuf);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "RTMemTmpAlloc(%#zx) failed", cbBuf);
+ RTMemTmpFree(pvBitmap);
+ RTMemTmpFree(pvDstBuf);
+ RTMemTmpFree(pvSrcBuf);
+ return rc;
+}
+
+
+/**
+ * Writes the image to file, no checking, no special buffering.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcFile The source file from the ISO maker.
+ * @param hVfsDstFile The destination file (image file on disk).
+ * @param cbImage The size of the ISO.
+ * @param ppvBuf Pointer to the buffer pointer. The buffer will
+ * be reallocated, but we want the luxary of the
+ * caller freeing it.
+ */
+static int rtFsIsoMakerCmdWriteImageRandomBufferSize(PRTFSISOMAKERCMDOPTS pOpts, RTVFSFILE hVfsSrcFile, RTVFSFILE hVfsDstFile,
+ uint64_t cbImage, void **ppvBuf)
+{
+ /*
+ * Copy the virtual image bits to the destination file.
+ */
+ void *pvBuf = *ppvBuf;
+ uint32_t cbMaxBuf = pOpts->cbOutputReadBuffer > 0 ? pOpts->cbOutputReadBuffer : _64K;
+ uint64_t offImage = 0;
+ while (offImage < cbImage)
+ {
+ /* Figure out how much to copy this time. */
+ size_t cbToCopy = RTRandU32Ex(1, cbMaxBuf - 1);
+ if (offImage + cbToCopy < cbImage)
+ { /* likely */ }
+ else
+ cbToCopy = (size_t)(cbImage - offImage);
+ RTMemFree(pvBuf);
+ *ppvBuf = pvBuf = RTMemTmpAlloc(cbToCopy);
+ if (pvBuf)
+ {
+ /* Do the copying. */
+ int rc = RTVfsFileReadAt(hVfsSrcFile, offImage, pvBuf, cbToCopy, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWriteAt(hVfsDstFile, offImage, pvBuf, cbToCopy, NULL);
+ if (RT_SUCCESS(rc))
+ offImage += cbToCopy;
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error %Rrc writing %#zx bytes at offset %#RX64 to '%s'",
+ rc, cbToCopy, offImage, pOpts->pszOutFile);
+ }
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error %Rrc read %#zx bytes at offset %#RX64", rc, cbToCopy, offImage);
+ }
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "RTMemTmpAlloc(%#zx) failed", cbToCopy);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Writes the image to file, no checking, no special buffering.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcFile The source file from the ISO maker.
+ * @param hVfsDstFile The destination file (image file on disk).
+ * @param cbImage The size of the ISO.
+ * @param pvBuf Pointer to read buffer.
+ * @param cbBuf The buffer size.
+ */
+static int rtFsIsoMakerCmdWriteImageSimple(PRTFSISOMAKERCMDOPTS pOpts, RTVFSFILE hVfsSrcFile, RTVFSFILE hVfsDstFile,
+ uint64_t cbImage, void *pvBuf, size_t cbBuf)
+{
+ /*
+ * Copy the virtual image bits to the destination file.
+ */
+ uint64_t offImage = 0;
+ while (offImage < cbImage)
+ {
+ /* Figure out how much to copy this time. */
+ size_t cbToCopy = cbBuf;
+ if (offImage + cbToCopy < cbImage)
+ { /* likely */ }
+ else
+ cbToCopy = (size_t)(cbImage - offImage);
+
+ /* Do the copying. */
+ int rc = RTVfsFileReadAt(hVfsSrcFile, offImage, pvBuf, cbToCopy, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWriteAt(hVfsDstFile, offImage, pvBuf, cbToCopy, NULL);
+ if (RT_SUCCESS(rc))
+ offImage += cbToCopy;
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error %Rrc writing %#zx bytes at offset %#RX64 to '%s'",
+ rc, cbToCopy, offImage, pOpts->pszOutFile);
+ }
+ else
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error %Rrc read %#zx bytes at offset %#RX64", rc, cbToCopy, offImage);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Writes the image to file.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcFile The source file from the ISO maker.
+ */
+static int rtFsIsoMakerCmdWriteImage(PRTFSISOMAKERCMDOPTS pOpts, RTVFSFILE hVfsSrcFile)
+{
+ /*
+ * Get the image size and setup the copy buffer.
+ */
+ uint64_t cbImage;
+ int rc = RTVfsFileQuerySize(hVfsSrcFile, &cbImage);
+ if (RT_SUCCESS(rc))
+ {
+ rtFsIsoMakerPrintf(pOpts, "Image size: %'RU64 (%#RX64) bytes\n", cbImage, cbImage);
+
+ uint32_t cbBuf = pOpts->cbOutputReadBuffer == 0 ? _1M : pOpts->cbOutputReadBuffer;
+ void *pvBuf = RTMemTmpAlloc(cbBuf);
+ if (pvBuf)
+ {
+ /*
+ * Open the output file.
+ */
+ RTVFSFILE hVfsDstFile;
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenFile(pOpts->pszOutFile,
+ RTFILE_O_READWRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_WRITE
+ | (0664 << RTFILE_O_CREATE_MODE_SHIFT),
+ &hVfsDstFile, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Apply the desired writing method.
+ */
+ if (!pOpts->fRandomOutputReadBufferSize)
+ rc = rtFsIsoMakerCmdWriteImageRandomBufferSize(pOpts, hVfsSrcFile, hVfsDstFile, cbImage, &pvBuf);
+ else
+ rc = rtFsIsoMakerCmdWriteImageSimple(pOpts, hVfsSrcFile, hVfsDstFile, cbImage, pvBuf, cbBuf);
+ RTMemTmpFree(pvBuf);
+
+ if (RT_SUCCESS(rc) && pOpts->cbRandomOrderVerifciationBlock > 0)
+ rc = rtFsIsoMakerCmdVerifyImageInRandomOrder(pOpts, hVfsSrcFile, hVfsDstFile, cbImage);
+
+ /*
+ * Flush the output file before releasing it.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileFlush(hVfsDstFile);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsFileFlush failed on '%s': %Rrc", pOpts->pszOutFile, rc);
+ }
+
+ RTVfsFileRelease(hVfsDstFile);
+ }
+ else
+ {
+ RTMemTmpFree(pvBuf);
+ rc = rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pOpts->pszOutFile, rc, offError, &ErrInfo.Core);
+ }
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "RTMemTmpAlloc(%zu) failed", cbBuf);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsFileQuerySize failed: %Rrc", rc);
+ return rc;
+}
+
+
+/**
+ * Formats @a fNameSpecifiers into a '+' separated list of names.
+ *
+ * @returns pszDst
+ * @param fNameSpecifiers The name specifiers.
+ * @param pszDst The destination bufer.
+ * @param cbDst The size of the destination buffer.
+ */
+static char *rtFsIsoMakerCmdNameSpecifiersToString(uint32_t fNameSpecifiers, char *pszDst, size_t cbDst)
+{
+ static struct { const char *pszName; uint32_t cchName; uint32_t fSpec; } const s_aSpecs[] =
+ {
+ { RT_STR_TUPLE("primary"), RTFSISOMAKERCMDNAME_PRIMARY_ISO },
+ { RT_STR_TUPLE("primary-rock"), RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE },
+ { RT_STR_TUPLE("primary-trans-tbl"), RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL },
+ { RT_STR_TUPLE("joliet"), RTFSISOMAKERCMDNAME_JOLIET },
+ { RT_STR_TUPLE("joliet-rock"), RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE },
+ { RT_STR_TUPLE("joliet-trans-tbl"), RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL },
+ { RT_STR_TUPLE("udf"), RTFSISOMAKERCMDNAME_UDF },
+ { RT_STR_TUPLE("udf-trans-tbl"), RTFSISOMAKERCMDNAME_UDF_TRANS_TBL },
+ { RT_STR_TUPLE("hfs"), RTFSISOMAKERCMDNAME_HFS },
+ { RT_STR_TUPLE("hfs-trans-tbl"), RTFSISOMAKERCMDNAME_HFS_TRANS_TBL },
+ };
+
+ Assert(cbDst > 0);
+ char *pszRet = pszDst;
+ for (uint32_t i = 0; i < RT_ELEMENTS(s_aSpecs); i++)
+ if (s_aSpecs[i].fSpec & fNameSpecifiers)
+ {
+ if (pszDst != pszRet && cbDst > 1)
+ {
+ *pszDst++ = '+';
+ cbDst--;
+ }
+ if (cbDst > s_aSpecs[i].cchName)
+ {
+ memcpy(pszDst, s_aSpecs[i].pszName, s_aSpecs[i].cchName);
+ cbDst -= s_aSpecs[i].cchName;
+ pszDst += s_aSpecs[i].cchName;
+ }
+ else if (cbDst > 1)
+ {
+ memcpy(pszDst, s_aSpecs[i].pszName, cbDst - 1);
+ pszDst += cbDst - 1;
+ cbDst = 1;
+ }
+
+ fNameSpecifiers &= ~s_aSpecs[i].fSpec;
+ if (!fNameSpecifiers)
+ break;
+ }
+ *pszDst = '\0';
+ return pszRet;
+}
+
+
+/**
+ * Parses the --name-setup option.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The name setup specification.
+ */
+static int rtFsIsoMakerCmdOptNameSetup(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec)
+{
+ /*
+ * Comma separated list of one or more specifiers.
+ */
+ uint32_t fNamespaces = 0;
+ uint32_t fPrevMajor = 0;
+ uint32_t iNameSpecifier = 0;
+ uint32_t offSpec = 0;
+ do
+ {
+ /*
+ * Parse up to the next colon or end of string.
+ */
+ uint32_t fNameSpecifier = 0;
+ char ch;
+ while ( (ch = pszSpec[offSpec]) != '\0'
+ && ch != ',')
+ {
+ if (RT_C_IS_SPACE(ch) || ch == '+' || ch == '|') /* space, '+' and '|' are allowed as name separators. */
+ offSpec++;
+ else
+ {
+ /* Find the end of the name. */
+ uint32_t offEndSpec = offSpec + 1;
+ while ( (ch = pszSpec[offEndSpec]) != '\0'
+ && ch != ','
+ && ch != '+'
+ && ch != '|'
+ && !RT_C_IS_SPACE(ch))
+ offEndSpec++;
+
+#define IS_EQUAL(a_sz) (cchName == sizeof(a_sz) - 1U && strncmp(pchName, a_sz, sizeof(a_sz) - 1U) == 0)
+ const char * const pchName = &pszSpec[offSpec];
+ uint32_t const cchName = offEndSpec - offSpec;
+ /* major namespaces */
+ if ( IS_EQUAL("iso")
+ || IS_EQUAL("primary")
+ || IS_EQUAL("iso9660")
+ || IS_EQUAL("iso-9660")
+ || IS_EQUAL("primary-iso")
+ || IS_EQUAL("iso-primary") )
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO;
+ fNamespaces |= fPrevMajor = RTFSISOMAKER_NAMESPACE_ISO_9660;
+ }
+ else if (IS_EQUAL("joliet"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET;
+ fNamespaces |= fPrevMajor = RTFSISOMAKER_NAMESPACE_JOLIET;
+ }
+ else if (IS_EQUAL("udf"))
+ {
+#if 0
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_UDF;
+ fNamespaces |= fPrevMajor = RTFSISOMAKER_NAMESPACE_UDF;
+#else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "UDF support is currently not implemented");
+#endif
+ }
+ else if (IS_EQUAL("hfs") || IS_EQUAL("hfsplus"))
+ {
+#if 0
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_HFS;
+ fNamespaces |= fPrevMajor = RTFSISOMAKER_NAMESPACE_HFS;
+#else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Hybrid HFS+ support is currently not implemented");
+#endif
+ }
+ /* rock ridge */
+ else if ( IS_EQUAL("rr")
+ || IS_EQUAL("rock")
+ || IS_EQUAL("rock-ridge"))
+ {
+ if (fPrevMajor == RTFSISOMAKERCMDNAME_PRIMARY_ISO)
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE;
+ else if (fPrevMajor == RTFSISOMAKERCMDNAME_JOLIET)
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE;
+ else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "unqualified rock-ridge name specifier");
+ }
+ else if ( IS_EQUAL("iso-rr") || IS_EQUAL("iso-rock") || IS_EQUAL("iso-rock-ridge")
+ || IS_EQUAL("primary-rr") || IS_EQUAL("primary-rock") || IS_EQUAL("primary-rock-ridge")
+ || IS_EQUAL("iso9660-rr") || IS_EQUAL("iso9660-rock") || IS_EQUAL("iso9660-rock-ridge")
+ || IS_EQUAL("iso-9660-rr") || IS_EQUAL("iso-9660-rock") || IS_EQUAL("iso-9660-rock-ridge")
+ || IS_EQUAL("primaryiso-rr") || IS_EQUAL("primaryiso-rock") || IS_EQUAL("primaryiso-rock-ridge")
+ || IS_EQUAL("primary-iso-rr") || IS_EQUAL("primary-iso-rock") || IS_EQUAL("primary-iso-rock-ridge") )
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_PRIMARY_ISO))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "iso-9660-rock-ridge must come after the iso-9660 name specifier");
+ }
+ else if (IS_EQUAL("joliet-rr") || IS_EQUAL("joliet-rock") || IS_EQUAL("joliet-rock-ridge"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_JOLIET))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "joliet-rock-ridge must come after the joliet name specifier");
+ }
+ /* trans.tbl */
+ else if (IS_EQUAL("trans") || IS_EQUAL("trans-tbl"))
+ {
+ if (fPrevMajor == RTFSISOMAKERCMDNAME_PRIMARY_ISO)
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL;
+ else if (fPrevMajor == RTFSISOMAKERCMDNAME_JOLIET)
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL;
+ else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "unqualified trans-tbl name specifier");
+ }
+ else if ( IS_EQUAL("iso-trans") || IS_EQUAL("iso-trans-tbl")
+ || IS_EQUAL("primary-trans") || IS_EQUAL("primary-trans-tbl")
+ || IS_EQUAL("iso9660-trans") || IS_EQUAL("iso9660-trans-tbl")
+ || IS_EQUAL("iso-9660-trans") || IS_EQUAL("iso-9660-trans-tbl")
+ || IS_EQUAL("primaryiso-trans") || IS_EQUAL("primaryiso-trans-tbl")
+ || IS_EQUAL("primary-iso-trans") || IS_EQUAL("primary-iso-trans-tbl") )
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_PRIMARY_ISO))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "iso-9660-trans-tbl must come after the iso-9660 name specifier");
+ }
+ else if (IS_EQUAL("joliet-trans") || IS_EQUAL("joliet-trans-tbl"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_JOLIET_TRANS_TBL;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_JOLIET))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "joliet-trans-tbl must come after the joliet name specifier");
+ }
+ else if (IS_EQUAL("udf-trans") || IS_EQUAL("udf-trans-tbl"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_UDF_TRANS_TBL;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_UDF))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "udf-trans-tbl must come after the udf name specifier");
+ }
+ else if (IS_EQUAL("hfs-trans") || IS_EQUAL("hfs-trans-tbl"))
+ {
+ fNameSpecifier |= RTFSISOMAKERCMDNAME_HFS_TRANS_TBL;
+ if (!(fNamespaces & RTFSISOMAKERCMDNAME_HFS))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "hfs-trans-tbl must come after the hfs name specifier");
+ }
+ else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "unknown name specifier '%.*s'", cchName, pchName);
+#undef IS_EQUAL
+ offSpec = offEndSpec;
+ }
+ } /* while same specifier */
+
+ /*
+ * Check that it wasn't empty.
+ */
+ if (fNameSpecifier == 0)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "name specifier #%u (0-based) is empty ", iNameSpecifier);
+
+ /*
+ * Complain if a major namespace name is duplicated. The rock-ridge and
+ * trans.tbl names are simple to replace, the others affect the two former
+ * names and are therefore not allowed twice in the list.
+ */
+ uint32_t i = iNameSpecifier;
+ while (i-- > 0)
+ {
+ uint32_t fRepeated = (fNameSpecifier & RTFSISOMAKERCMDNAME_MAJOR_MASK)
+ & (pOpts->afNameSpecifiers[i] & RTFSISOMAKERCMDNAME_MAJOR_MASK);
+ if (fRepeated)
+ {
+ char szTmp[128];
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "repeating name specifier%s: %s", RT_IS_POWER_OF_TWO(fRepeated) ? "" : "s",
+ rtFsIsoMakerCmdNameSpecifiersToString(fRepeated, szTmp, sizeof(szTmp)));
+ }
+ }
+
+ /*
+ * Add it.
+ */
+ if (iNameSpecifier >= RT_ELEMENTS(pOpts->afNameSpecifiers))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "too many name specifiers (max %d)", RT_ELEMENTS(pOpts->afNameSpecifiers));
+ pOpts->afNameSpecifiers[iNameSpecifier] = fNameSpecifier;
+ iNameSpecifier++;
+
+ /*
+ * Next, if any.
+ */
+ if (pszSpec[offSpec] == ',')
+ offSpec++;
+ } while (pszSpec[offSpec] != '\0');
+
+ pOpts->cNameSpecifiers = iNameSpecifier;
+ pOpts->fDstNamespaces = fNamespaces;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Handles the --name-setup-from-import option.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptNameSetupFromImport(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ /*
+ * Figure out what's on the ISO.
+ */
+ uint32_t fNamespaces = RTFsIsoMakerGetPopulatedNamespaces(pOpts->hIsoMaker);
+ AssertReturn(fNamespaces != UINT32_MAX, VERR_INVALID_HANDLE);
+ if (fNamespaces != 0)
+ {
+ if ( (fNamespaces & RTFSISOMAKER_NAMESPACE_ISO_9660)
+ && RTFsIsoMakerGetRockRidgeLevel(pOpts->hIsoMaker) > 0)
+ fNamespaces |= RTFSISOMAKERCMDNAME_PRIMARY_ISO_ROCK_RIDGE;
+
+ if ( (fNamespaces & RTFSISOMAKER_NAMESPACE_JOLIET)
+ && RTFsIsoMakerGetJolietRockRidgeLevel(pOpts->hIsoMaker) > 0)
+ fNamespaces |= RTFSISOMAKERCMDNAME_JOLIET_ROCK_RIDGE;
+
+ /*
+ * The TRANS.TBL files cannot be disabled at present and the importer
+ * doesn't check whether they are there or not, so carry them on from
+ * the previous setup.
+ */
+ uint32_t fOld = 0;
+ uint32_t i = pOpts->cNameSpecifiers;
+ while (i-- > 0)
+ fOld |= pOpts->afNameSpecifiers[0];
+ if (fNamespaces & RTFSISOMAKER_NAMESPACE_ISO_9660)
+ fNamespaces |= fOld & RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL;
+ if (fNamespaces & RTFSISOMAKER_NAMESPACE_JOLIET)
+ fNamespaces |= fOld & RTFSISOMAKERCMDNAME_PRIMARY_ISO_TRANS_TBL;
+ if (fNamespaces & RTFSISOMAKER_NAMESPACE_UDF)
+ fNamespaces |= fOld & RTFSISOMAKERCMDNAME_UDF_TRANS_TBL;
+ if (fNamespaces & RTFSISOMAKER_NAMESPACE_HFS)
+ fNamespaces |= fOld & RTFSISOMAKERCMDNAME_HFS_TRANS_TBL;
+
+ /*
+ * Apply the new configuration.
+ */
+ pOpts->cNameSpecifiers = 1;
+ pOpts->afNameSpecifiers[0] = fNamespaces;
+ pOpts->fDstNamespaces = fNamespaces & RTFSISOMAKERCMDNAME_MAJOR_MASK;
+
+ char szTmp[128];
+ rtFsIsoMakerPrintf(pOpts, "info: --name-setup-from-import determined: --name-setup=%s\n",
+ rtFsIsoMakerCmdNameSpecifiersToString(fNamespaces, szTmp, sizeof(szTmp)));
+ return VINF_SUCCESS;
+ }
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_DRIVE_IS_EMPTY, "--name-setup-from-import used on an empty ISO");
+}
+
+
+/**
+ * Checks if we should use the source stack or the regular file system for
+ * opening a source.
+ *
+ * @returns true / false.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The source path under consideration.
+ */
+static bool rtFsIsoMakerCmdUseSrcStack(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSrc)
+{
+ /* Not if there isn't any stack. */
+ if (pOpts->iSrcStack < 0)
+ return false;
+
+ /* Not if we've got a :iprtvfs: incantation. */
+ if (RTVfsChainIsSpec(pszSrc))
+ return false;
+
+ /* If the top entry is a CWD rather than a VFS, we only do it for root-less paths. */
+ if (pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption == NULL)
+ {
+ if (RTPathStartsWithRoot(pszSrc))
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Processes a non-option argument.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The specification of what to add.
+ * @param fWithSrc Whether the specification includes a source path
+ * or not.
+ * @param pParsed Where to return the parsed name specification.
+ */
+static int rtFsIsoMakerCmdParseNameSpec(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec, bool fWithSrc,
+ PRTFSISOMKCMDPARSEDNAMES pParsed)
+{
+ const char * const pszSpecIn = pszSpec;
+ uint32_t const cMaxNames = pOpts->cNameSpecifiers + fWithSrc;
+
+ /*
+ * Split it up by '='.
+ */
+ pParsed->cNames = 0;
+ pParsed->cNamesWithSrc = 0;
+ pParsed->enmSrcType = fWithSrc ? RTFSISOMKCMDPARSEDNAMES::kSrcType_Normal : RTFSISOMKCMDPARSEDNAMES::kSrcType_None;
+ for (;;)
+ {
+ const char *pszEqual = strchr(pszSpec, '=');
+ size_t cchName = pszEqual ? pszEqual - pszSpec : strlen(pszSpec);
+ bool fNeedSlash = (pszEqual || !fWithSrc) && !RTPATH_IS_SLASH(*pszSpec) && cchName > 0;
+ if (cchName + fNeedSlash >= sizeof(pParsed->aNames[pParsed->cNamesWithSrc].szPath))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "name #%u (0-based) is too long: %s", pParsed->cNamesWithSrc, pszSpecIn);
+ if (pParsed->cNamesWithSrc >= cMaxNames)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "too many names specified (max %u%s): %s",
+ pOpts->cNameSpecifiers, fWithSrc ? " + source" : "", pszSpecIn);
+ if (!fNeedSlash)
+ memcpy(pParsed->aNames[pParsed->cNamesWithSrc].szPath, pszSpec, cchName);
+ else
+ {
+ memcpy(&pParsed->aNames[pParsed->cNamesWithSrc].szPath[1], pszSpec, cchName);
+ pParsed->aNames[pParsed->cNamesWithSrc].szPath[0] = RTPATH_SLASH;
+ cchName++;
+ }
+ pParsed->aNames[pParsed->cNamesWithSrc].szPath[cchName] = '\0';
+ pParsed->aNames[pParsed->cNamesWithSrc].cchPath = (uint32_t)cchName;
+ pParsed->cNamesWithSrc++;
+
+ if (!pszEqual)
+ {
+ if (fWithSrc)
+ {
+ if (!cchName)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "empty source file name: %s", pszSpecIn);
+ if (cchName == 8 && strcmp(pszSpec, ":remove:") == 0)
+ pParsed->enmSrcType = RTFSISOMKCMDPARSEDNAMES::kSrcType_Remove;
+ else if (cchName == 13 && strcmp(pszSpec, ":must-remove:") == 0)
+ pParsed->enmSrcType = RTFSISOMKCMDPARSEDNAMES::kSrcType_MustRemove;
+ else if (rtFsIsoMakerCmdUseSrcStack(pOpts, pszSpec))
+ pParsed->enmSrcType = RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack;
+ }
+ break;
+ }
+ pszSpec = pszEqual + 1;
+ }
+
+ /*
+ * If there are too few names specified, move the source and repeat the
+ * last non-source name. If only source, convert source into a name spec.
+ */
+ if (pParsed->cNamesWithSrc < cMaxNames)
+ {
+ uint32_t iSrc;
+ if (!fWithSrc)
+ iSrc = pParsed->cNamesWithSrc - 1;
+ else
+ {
+ pParsed->aNames[pOpts->cNameSpecifiers] = pParsed->aNames[pParsed->cNamesWithSrc - 1];
+ iSrc = pParsed->cNamesWithSrc >= 2 ? pParsed->cNamesWithSrc - 2 : 0;
+ }
+
+ /* If the source is a input file name specifier, reduce it to something that starts with a slash. */
+ if (pParsed->cNamesWithSrc == 1 && fWithSrc)
+ {
+ const char *pszSrc = pParsed->aNames[iSrc].szPath;
+ char *pszFinalPath = NULL;
+ if (RTVfsChainIsSpec(pParsed->aNames[iSrc].szPath))
+ {
+ uint32_t offError;
+ int rc = RTVfsChainQueryFinalPath(pParsed->aNames[iSrc].szPath, &pszFinalPath, &offError);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainQueryFinalPath",
+ pParsed->aNames[iSrc].szPath, rc, offError, NULL);
+ pszSrc = pszFinalPath;
+ }
+
+ /* Find the start of the last component, ignoring trailing slashes. */
+ size_t cchSrc = strlen(pszSrc);
+ size_t offLast = cchSrc;
+ while (offLast > 0 && RTPATH_IS_SLASH(pszSrc[offLast - 1]))
+ offLast--;
+ while (offLast > 0 && !RTPATH_IS_SLASH(pszSrc[offLast - 1]))
+ offLast--;
+
+ /* Move it up front with a leading slash. */
+ if (offLast > 0 || !RTPATH_IS_SLASH(*pszSrc))
+ {
+ pParsed->aNames[iSrc].cchPath = 1 + (uint32_t)(cchSrc - offLast);
+ if (pParsed->aNames[iSrc].cchPath >= sizeof(pParsed->aNames[iSrc].szPath))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "name too long: %s", pszSpecIn);
+
+ memmove(&pParsed->aNames[iSrc].szPath[1], &pszSrc[offLast], pParsed->aNames[iSrc].cchPath);
+ }
+ else
+ pParsed->aNames[iSrc].cchPath = 1;
+ pParsed->aNames[iSrc].szPath[0] = RTPATH_SLASH;
+
+ if (pszFinalPath)
+ RTStrFree(pszFinalPath);
+ }
+
+ for (uint32_t iDst = iSrc + 1; iDst < pOpts->cNameSpecifiers; iDst++)
+ pParsed->aNames[iDst] = pParsed->aNames[iSrc];
+
+ pParsed->cNamesWithSrc = cMaxNames;
+ }
+ pParsed->cNames = pOpts->cNameSpecifiers;
+
+ /*
+ * Copy the specifier flags and check that the paths all starts with slashes.
+ */
+ for (uint32_t i = 0; i < pOpts->cNameSpecifiers; i++)
+ {
+ pParsed->aNames[i].fNameSpecifiers = pOpts->afNameSpecifiers[i];
+ Assert( pParsed->aNames[i].cchPath == 0
+ || RTPATH_IS_SLASH(pParsed->aNames[i].szPath[0]));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Enteres an object into the namespace by full paths.
+ *
+ * This is used by rtFsIsoMakerCmdOptEltoritoSetBootCatalogPath and
+ * rtFsIsoMakerCmdAddFile.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param idxObj The configuration index of the object to be named.
+ * @param pParsed The parsed names.
+ * @param pszSrcOrName Source file or name.
+ */
+static int rtFsIsoMakerCmdSetObjPaths(PRTFSISOMAKERCMDOPTS pOpts, uint32_t idxObj, PCRTFSISOMKCMDPARSEDNAMES pParsed,
+ const char *pszSrcOrName)
+{
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < pParsed->cNames; i++)
+ if (pParsed->aNames[i].cchPath > 0)
+ {
+ if (pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK)
+ {
+ rc = RTFsIsoMakerObjSetPath(pOpts->hIsoMaker, idxObj,
+ pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK,
+ pParsed->aNames[i].szPath);
+ if (RT_FAILURE(rc))
+ {
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error setting name '%s' on '%s': %Rrc",
+ pParsed->aNames[i].szPath, pszSrcOrName, rc);
+ break;
+ }
+ }
+ if (pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MINOR_MASK)
+ {
+ /** @todo add APIs for this. */
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a file.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The path to the source file.
+ * @param pParsed The parsed names.
+ * @param pidxObj Where to return the configuration index for the
+ * added file. Optional.
+ */
+static int rtFsIsoMakerCmdAddFile(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSrc, PCRTFSISOMKCMDPARSEDNAMES pParsed,
+ uint32_t *pidxObj)
+{
+ int rc;
+ uint32_t idxObj = UINT32_MAX;
+ if (pParsed->enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack)
+ {
+ RTVFSFILE hVfsFileSrc;
+ rc = RTVfsDirOpenFile(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszSrc,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileSrc);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening '%s' (%s '%s'): %Rrc",
+ pszSrc, pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+
+ rc = RTFsIsoMakerAddUnnamedFileWithVfsFile(pOpts->hIsoMaker, hVfsFileSrc, &idxObj);
+ RTVfsFileRelease(hVfsFileSrc);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding '%s' (VFS): %Rrc", pszSrc, rc);
+ }
+ else
+ {
+ rc = RTFsIsoMakerAddUnnamedFileWithSrcPath(pOpts->hIsoMaker, pszSrc, &idxObj);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding '%s': %Rrc", pszSrc, rc);
+ }
+
+
+ pOpts->cItemsAdded++;
+ if (pidxObj)
+ *pidxObj = idxObj;
+
+ return rtFsIsoMakerCmdSetObjPaths(pOpts, idxObj, pParsed, pszSrc);
+}
+
+
+/**
+ * Applies filtering rules.
+ *
+ * @returns true if filtered out, false if included.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The source source.
+ * @param pszName The name part (maybe different buffer from pszSrc).
+ * @param fIsDir Set if directory, clear if not.
+ */
+static bool rtFsIsoMakerCmdIsFilteredOut(PRTFSISOMAKERCMDOPTS pOpts, const char *pszDir, const char *pszName, bool fIsDir)
+{
+ /* Ignore trans.tbl files. */
+ if ( !fIsDir
+ && RTStrICmp(pszName, pOpts->pszTransTbl) == 0)
+ return true;
+
+ RT_NOREF(pOpts, pszDir, pszName, fIsDir);
+ return false;
+}
+
+
+/**
+ * Worker for rtFsIsoMakerCmdAddVfsDir that does the recursion.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsDir The directory to process.
+ * @param idxDirObj The configuration index of the directory.
+ * @param pszSrc Pointer to the source path buffer. RTPATH_MAX
+ * in size. Okay to modify beyond @a cchSrc.
+ * @param cchSrc Length of the path corresponding to @a hVfsDir.
+ * @param fNamespaces Which ISO maker namespaces to add the names to.
+ * @param cDepth Number of recursions. Used to deal with loopy
+ * directories.
+ * @param fFilesWithSrcPath Whether to add files using @a pszSrc or to add
+ * as VFS handles (open first). For saving native
+ * file descriptors.
+ */
+static int rtFsIsoMakerCmdAddVfsDirRecursive(PRTFSISOMAKERCMDOPTS pOpts, RTVFSDIR hVfsDir, uint32_t idxDirObj,
+ char *pszSrc, size_t cchSrc, uint32_t fNamespaces, uint8_t cDepth,
+ bool fFilesWithSrcPath)
+{
+ /*
+ * Check that we're not in too deep.
+ */
+ if (cDepth >= RTFSISOMAKERCMD_MAX_DIR_RECURSIONS)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_ISOMK_IMPORT_TOO_DEEP_DIR_TREE,
+ "Recursive (VFS) dir add too deep (depth=%u): %.*s", cDepth, cchSrc, pszSrc);
+ /*
+ * Enumerate the directory.
+ */
+ int rc;
+ size_t cbDirEntryAlloced = sizeof(RTDIRENTRYEX);
+ PRTDIRENTRYEX pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ {
+ for (;;)
+ {
+ /*
+ * Read the next entry.
+ */
+ size_t cbDirEntry = cbDirEntryAlloced;
+ rc = RTVfsDirReadEx(hVfsDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ RTMemTmpFree(pDirEntry);
+ cbDirEntryAlloced = RT_ALIGN_Z(RT_MIN(cbDirEntry, cbDirEntryAlloced) + 64, 64);
+ pDirEntry = (PRTDIRENTRYEX)RTMemTmpAlloc(cbDirEntryAlloced);
+ if (pDirEntry)
+ continue;
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "Out of memory (direntry buffer)");
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsDirReadEx failed on %.*s: %Rrc", cchSrc, pszSrc, rc);
+ break;
+ }
+
+ /* Ignore '.' and '..' entries. */
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ /*
+ * Process the entry.
+ */
+
+ /* Update the name. */
+ if (cchSrc + 1 + pDirEntry->cbName < RTPATH_MAX)
+ {
+ pszSrc[cchSrc] = '/'; /* VFS only groks unix slashes */
+ memcpy(&pszSrc[cchSrc + 1], pDirEntry->szName, pDirEntry->cbName);
+ pszSrc[cchSrc + 1 + pDirEntry->cbName] = '\0';
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_FILENAME_TOO_LONG, "Filename is too long (depth %u): '%.*s/%s'",
+ cDepth, cchSrc, pszSrc, pDirEntry->szName);
+
+ /* Okay? Check name filtering. */
+ if ( RT_SUCCESS(rc)
+ && !rtFsIsoMakerCmdIsFilteredOut(pOpts, pszSrc, pDirEntry->szName, RTFS_IS_DIRECTORY(pDirEntry->Info.Attr.fMode)))
+ {
+ /* Do type specific adding. */
+ uint32_t idxObj = UINT32_MAX;
+ if (RTFS_IS_FILE(pDirEntry->Info.Attr.fMode))
+ {
+ /*
+ * Files are either added with VFS handles or paths to the sources,
+ * depending on what's considered more efficient. We prefer the latter
+ * if hVfsDir maps to native handle and not a virtual one.
+ */
+ if (!fFilesWithSrcPath)
+ {
+ RTVFSFILE hVfsFileSrc;
+ rc = RTVfsDirOpenFile(hVfsDir, pDirEntry->szName,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileSrc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerAddUnnamedFileWithVfsFile(pOpts->hIsoMaker, hVfsFileSrc, &idxObj);
+ RTVfsFileRelease(hVfsFileSrc);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding file '%s' (VFS recursive, handle): %Rrc",
+ pszSrc, rc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening file '%s' (VFS recursive): %Rrc", pszSrc, rc);
+ }
+ else
+ {
+ /* Add file with source path: */
+ rc = RTFsIsoMakerAddUnnamedFileWithSrcPath(pOpts->hIsoMaker, pszSrc, &idxObj);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding file '%s' (VFS recursive, path): %Rrc",
+ pszSrc, rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pOpts->cItemsAdded++;
+ rc = RTFsIsoMakerObjSetNameAndParent(pOpts->hIsoMaker, idxObj, idxDirObj, fNamespaces,
+ pDirEntry->szName, false /*fNoNormalize*/);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error setting parent & name on file '%s' to '%s': %Rrc",
+ pszSrc, pDirEntry->szName, rc);
+ }
+ }
+ else if (RTFS_IS_DIRECTORY(pDirEntry->Info.Attr.fMode))
+ {
+ /*
+ * Open and add the sub-directory.
+ */
+ RTVFSDIR hVfsSubDirSrc;
+ rc = RTVfsDirOpenDir(hVfsDir, pDirEntry->szName, 0 /*fFlags*/, &hVfsSubDirSrc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerAddUnnamedDir(pOpts->hIsoMaker, &pDirEntry->Info, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ pOpts->cItemsAdded++;
+ rc = RTFsIsoMakerObjSetNameAndParent(pOpts->hIsoMaker, idxObj, idxDirObj, fNamespaces,
+ pDirEntry->szName, false /*fNoNormalize*/);
+ if (RT_SUCCESS(rc))
+ /* Recurse into the sub-directory. */
+ rc = rtFsIsoMakerCmdAddVfsDirRecursive(pOpts, hVfsSubDirSrc, idxObj, pszSrc,
+ cchSrc + 1 + pDirEntry->cbName, fNamespaces, cDepth + 1,
+ fFilesWithSrcPath);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "Error setting parent & name on directory '%s' to '%s': %Rrc",
+ pszSrc, pDirEntry->szName, rc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error adding directory '%s' (VFS recursive): %Rrc", pszSrc, rc);
+ RTVfsDirRelease(hVfsSubDirSrc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening directory '%s' (VFS recursive): %Rrc", pszSrc, rc);
+ }
+ else if (RTFS_IS_SYMLINK(pDirEntry->Info.Attr.fMode))
+ {
+ /*
+ * TODO: ISO FS symlink support.
+ */
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED,
+ "Adding symlink '%s' failed: not yet implemented", pszSrc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED,
+ "Adding special file '%s' failed: not implemented", pszSrc);
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ RTMemTmpFree(pDirEntry);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "Out of memory! (direntry buffer)");
+ return rc;
+}
+
+
+/**
+ * Common directory adding worker.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param hVfsSrcDir The directory being added.
+ * @param pszSrc The source directory name.
+ * @param pParsed The parsed names.
+ * @param fFilesWithSrcPath Whether to add files using @a pszSrc
+ * or to add as VFS handles (open first). For
+ * saving native file descriptors.
+ * @param pidxObj Where to return the configuration index for the
+ * added file. Optional.
+ */
+static int rtFsIsoMakerCmdAddVfsDirCommon(PRTFSISOMAKERCMDOPTS pOpts, RTVFSDIR hVfsDirSrc, char *pszSrc,
+ PCRTFSISOMKCMDPARSEDNAMES pParsed, bool fFilesWithSrcPath, PCRTFSOBJINFO pObjInfo)
+{
+ /*
+ * Add the directory if it doesn't exist.
+ */
+ uint32_t idxObj = UINT32_MAX;
+ for (uint32_t i = 0; i < pParsed->cNames; i++)
+ if (pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK)
+ {
+ idxObj = RTFsIsoMakerGetObjIdxForPath(pOpts->hIsoMaker,
+ pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK,
+ pParsed->aNames[i].szPath);
+ if (idxObj != UINT32_MAX)
+ {
+ /** @todo make sure the directory is present in the other namespace. */
+ break;
+ }
+ }
+ int rc = VINF_SUCCESS;
+ if (idxObj == UINT32_MAX)
+ {
+ rc = RTFsIsoMakerAddUnnamedDir(pOpts->hIsoMaker, pObjInfo, &idxObj);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdSetObjPaths(pOpts, idxObj, pParsed, pParsed->aNames[pParsed->cNames - 1].szPath);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerAddUnnamedDir failed: %Rrc", rc);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add the directory content.
+ */
+ uint32_t fNamespaces = 0;
+ for (uint32_t i = 0; i < pParsed->cNames; i++)
+ fNamespaces |= pParsed->aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK;
+ rc = rtFsIsoMakerCmdAddVfsDirRecursive(pOpts, hVfsDirSrc, idxObj, pszSrc,
+ pParsed->aNames[pParsed->cNamesWithSrc - 1].cchPath, fNamespaces, 0 /*cDepth*/,
+ fFilesWithSrcPath);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Adds a directory, from the source VFS.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pParsed The parsed names.
+ * @param pidxObj Where to return the configuration index for the
+ * added file. Optional.
+ */
+static int rtFsIsoMakerCmdAddVfsDir(PRTFSISOMAKERCMDOPTS pOpts, PCRTFSISOMKCMDPARSEDNAMES pParsed, PCRTFSOBJINFO pObjInfo)
+{
+ Assert(pParsed->cNames < pParsed->cNamesWithSrc);
+ char *pszSrc = pParsed->aNames[pParsed->cNamesWithSrc - 1].szPath;
+ RTPathChangeToUnixSlashes(pszSrc, true /*fForce*/); /* VFS currently only understand unix slashes. */
+ RTVFSDIR hVfsDirSrc;
+ int rc = RTVfsDirOpenDir(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszSrc, 0 /*fFlags*/, &hVfsDirSrc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoMakerCmdAddVfsDirCommon(pOpts, hVfsDirSrc, pszSrc, pParsed, false /*fFilesWithSrcPath*/, pObjInfo);
+ RTVfsDirRelease(hVfsDirSrc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening directory '%s' (%s '%s'): %Rrc", pszSrc,
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+ return rc;
+}
+
+
+/**
+ * Adds a directory, from a VFS chain or real file system.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The path to the source directory.
+ * @param pParsed The parsed names.
+ */
+static int rtFsIsoMakerCmdAddDir(PRTFSISOMAKERCMDOPTS pOpts, PCRTFSISOMKCMDPARSEDNAMES pParsed, PCRTFSOBJINFO pObjInfo)
+{
+ Assert(pParsed->cNames < pParsed->cNamesWithSrc);
+ char *pszSrc = pParsed->aNames[pParsed->cNamesWithSrc - 1].szPath;
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTVFSDIR hVfsDirSrc;
+ int rc = RTVfsChainOpenDir(pszSrc, 0 /*fOpen*/, &hVfsDirSrc, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoMakerCmdAddVfsDirCommon(pOpts, hVfsDirSrc, pszSrc, pParsed,
+ RTVfsDirIsStdDir(hVfsDirSrc) /*fFilesWithSrcPath*/, pObjInfo);
+ RTVfsDirRelease(hVfsDirSrc);
+ }
+ else
+ rc = rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenDir", pszSrc, rc, offError, &ErrInfo.Core);
+ return rc;
+}
+
+
+/**
+ * Adds a file after first making sure it's a file.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszSrc The path to the source file.
+ * @param pParsed The parsed names.
+ * @param pidxObj Where to return the configuration index for the
+ * added file. Optional.
+ */
+static int rtFsIsoMakerCmdStatAndAddFile(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSrc, PCRTFSISOMKCMDPARSEDNAMES pParsed,
+ uint32_t *pidxObj)
+{
+ int rc;
+ RTFSOBJINFO ObjInfo;
+ if (pParsed->enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack)
+ {
+ rc = RTVfsDirQueryPathInfo(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszSrc,
+ &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsQueryPathInfo failed on %s (%s %s): %Rrc", pszSrc,
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+ }
+ else
+ {
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainQueryInfo(pszSrc, &ObjInfo, RTFSOBJATTRADD_UNIX,
+ RTPATH_F_FOLLOW_LINK, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainQueryInfo", pszSrc, rc, offError, &ErrInfo.Core);
+ }
+
+ if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ return rtFsIsoMakerCmdAddFile(pOpts, pszSrc, pParsed, pidxObj);
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_A_FILE, "Not a file: %s", pszSrc);
+}
+
+
+/**
+ * Processes a non-option argument.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The specification of what to add.
+ */
+static int rtFsIsoMakerCmdAddSomething(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec)
+{
+ /*
+ * Parse the name spec.
+ */
+ RTFSISOMKCMDPARSEDNAMES Parsed;
+ int rc = rtFsIsoMakerCmdParseNameSpec(pOpts, pszSpec, true /*fWithSrc*/, &Parsed);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Deal with special source filenames used to remove/change stuff.
+ */
+ if ( Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_Remove
+ || Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_MustRemove)
+ {
+ const char *pszFirstNm = NULL;
+ uint32_t cRemoved = 0;
+ for (uint32_t i = 0; i < pOpts->cNameSpecifiers; i++)
+ if ( Parsed.aNames[i].cchPath > 0
+ && (Parsed.aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK))
+ {
+ /* Make sure we remove all objects by this name. */
+ pszFirstNm = Parsed.aNames[i].szPath;
+ for (;;)
+ {
+ uint32_t idxObj = RTFsIsoMakerGetObjIdxForPath(pOpts->hIsoMaker,
+ Parsed.aNames[i].fNameSpecifiers & RTFSISOMAKERCMDNAME_MAJOR_MASK,
+ Parsed.aNames[i].szPath);
+ if (idxObj == UINT32_MAX)
+ break;
+ rc = RTFsIsoMakerObjRemove(pOpts->hIsoMaker, idxObj);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to remove '%s': %Rrc", pszSpec, rc);
+ cRemoved++;
+ }
+ }
+ if ( Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_MustRemove
+ && cRemoved == 0)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_FOUND, "Failed to locate '%s' for removal", pszSpec);
+ }
+ /*
+ * Add regular source.
+ */
+ else
+ {
+ const char *pszSrc = Parsed.aNames[Parsed.cNamesWithSrc - 1].szPath;
+ RTFSOBJINFO ObjInfo;
+ if (Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack)
+ {
+ rc = RTVfsDirQueryPathInfo(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszSrc,
+ &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTVfsQueryPathInfo failed on %s (%s %s): %Rrc", pszSrc,
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+ }
+ else
+ {
+ uint32_t offError;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainQueryInfo(pszSrc, &ObjInfo, RTFSOBJATTRADD_UNIX,
+ RTPATH_F_FOLLOW_LINK, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainQueryInfo", pszSrc, rc, offError, &ErrInfo.Core);
+ }
+
+ /* By type: */
+
+ if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ return rtFsIsoMakerCmdAddFile(pOpts, pszSrc, &Parsed, NULL /*pidxObj*/);
+
+ if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ {
+ if (Parsed.enmSrcType == RTFSISOMKCMDPARSEDNAMES::kSrcType_NormalSrcStack)
+ return rtFsIsoMakerCmdAddVfsDir(pOpts, &Parsed, &ObjInfo);
+ return rtFsIsoMakerCmdAddDir(pOpts, &Parsed, &ObjInfo);
+ }
+
+ if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED, "Adding symlink '%s' failed: not yet implemented", pszSpec);
+
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED, "Adding special file '%s' failed: not implemented", pszSpec);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Opens an ISO and use it for subsequent file system accesses.
+ *
+ * This is handy for duplicating a part of an ISO in the new image.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszIsoSpec The ISO path specifier.
+ * @param pszOption The option we're being called on.
+ * @param fFlags RTFSISO9660_F_XXX
+ */
+static int rtFsIsoMakerCmdOptPushIso(PRTFSISOMAKERCMDOPTS pOpts, const char *pszIsoSpec, const char *pszOption, uint32_t fFlags)
+{
+ int32_t iSrcStack = pOpts->iSrcStack + 1;
+ if ((uint32_t)iSrcStack >= RT_ELEMENTS(pOpts->aSrcStack))
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_IMPLEMENTED,
+ "Too many pushes %s %s (previous: %s %s, %s %s, %s %s, ...)",
+ pszOption, pszIsoSpec,
+ pOpts->aSrcStack[iSrcStack - 1].pszSrcVfsOption, pOpts->aSrcStack[iSrcStack - 1].pszSrcVfs,
+ pOpts->aSrcStack[iSrcStack - 2].pszSrcVfsOption, pOpts->aSrcStack[iSrcStack - 2].pszSrcVfs,
+ pOpts->aSrcStack[iSrcStack - 3].pszSrcVfsOption, pOpts->aSrcStack[iSrcStack - 3].pszSrcVfs);
+
+ /*
+ * Try open the file.
+ */
+ int rc;
+ RTVFSFILE hVfsFileIso = NIL_RTVFSFILE;
+ RTERRINFOSTATIC ErrInfo;
+ if (rtFsIsoMakerCmdUseSrcStack(pOpts, pszIsoSpec))
+ {
+ rc = RTVfsDirOpenFile(pOpts->aSrcStack[iSrcStack - 1].hSrcDir, pszIsoSpec,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFileIso);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error opening '%s' relative to '%s'",
+ pszIsoSpec, pOpts->aSrcStack[iSrcStack - 1].pszSrcVfs);
+ }
+ else
+ {
+ uint32_t offError;
+ rc = RTVfsChainOpenFile(pszIsoSpec, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE,
+ &hVfsFileIso, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pszIsoSpec, rc, offError, &ErrInfo.Core);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTVFS hSrcVfs;
+ rc = RTFsIso9660VolOpen(hVfsFileIso, fFlags, &hSrcVfs, RTErrInfoInitStatic(&ErrInfo));
+ RTVfsFileRelease(hVfsFileIso);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsSrcRootDir;
+ rc = RTVfsOpenRoot(hSrcVfs, &hVfsSrcRootDir);
+ if (RT_SUCCESS(rc))
+ {
+ pOpts->aSrcStack[iSrcStack].hSrcDir = hVfsSrcRootDir;
+ pOpts->aSrcStack[iSrcStack].hSrcVfs = hSrcVfs;
+ pOpts->aSrcStack[iSrcStack].pszSrcVfs = pszIsoSpec;
+ pOpts->aSrcStack[iSrcStack].pszSrcVfsOption = pszOption;
+ pOpts->iSrcStack = iSrcStack;
+ return VINF_SUCCESS;
+ }
+ RTVfsRelease(hSrcVfs);
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to open '%s' as ISO FS: %Rrc - %s",
+ pszIsoSpec, rc, ErrInfo.Core.pszMsg);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to open '%s' as ISO FS: %Rrc", pszIsoSpec, rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Counter part to --push-iso and friends.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptPop(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ int32_t const iSrcStack = pOpts->iSrcStack;
+ if ( iSrcStack >= 0
+ && pOpts->aSrcStack[iSrcStack].pszSrcVfsOption)
+ {
+ RTVfsDirRelease(pOpts->aSrcStack[iSrcStack].hSrcDir);
+ RTVfsRelease(pOpts->aSrcStack[iSrcStack].hSrcVfs);
+ pOpts->aSrcStack[iSrcStack].hSrcDir = NIL_RTVFSDIR;
+ pOpts->aSrcStack[iSrcStack].hSrcVfs = NIL_RTVFS;
+ pOpts->aSrcStack[iSrcStack].pszSrcVfs = NULL;
+ pOpts->aSrcStack[iSrcStack].pszSrcVfsOption = NULL;
+ pOpts->iSrcStack = iSrcStack - 1;
+ return VINF_SUCCESS;
+ }
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_NOT_FOUND, "--pop without --push-xxx");
+}
+
+
+/**
+ * Deals with the --import-iso {iso-file-spec} options.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszIsoSpec The ISO path specifier.
+ */
+static int rtFsIsoMakerCmdOptImportIso(PRTFSISOMAKERCMDOPTS pOpts, const char *pszIsoSpec)
+{
+ /*
+ * Open the input file.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ RTVFSFILE hIsoFile;
+ int rc;
+ if (rtFsIsoMakerCmdUseSrcStack(pOpts, pszIsoSpec))
+ {
+ rc = RTVfsDirOpenFile(pOpts->aSrcStack[pOpts->iSrcStack].hSrcDir, pszIsoSpec,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hIsoFile);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to open '%s' %s %s for importing: %Rrc", pszIsoSpec,
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfsOption ? "inside" : "relative to",
+ pOpts->aSrcStack[pOpts->iSrcStack].pszSrcVfs, rc);
+ }
+ else
+ {
+ uint32_t offError;
+ rc = RTVfsChainOpenFile(pszIsoSpec, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE,
+ &hIsoFile, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pszIsoSpec, rc, offError, &ErrInfo.Core);
+ }
+
+ RTFSISOMAKERIMPORTRESULTS Results;
+ rc = RTFsIsoMakerImport(pOpts->hIsoMaker, hIsoFile, 0 /*fFlags*/, &Results, RTErrInfoInitStatic(&ErrInfo));
+
+ RTVfsFileRelease(hIsoFile);
+
+ pOpts->cItemsAdded += Results.cAddedFiles;
+ pOpts->cItemsAdded += Results.cAddedSymlinks;
+ pOpts->cItemsAdded += Results.cAddedDirs;
+ pOpts->cItemsAdded += Results.cBootCatEntries != UINT32_MAX ? Results.cBootCatEntries : 0;
+ pOpts->cItemsAdded += Results.cbSysArea != 0 ? 1 : 0;
+
+ rtFsIsoMakerPrintf(pOpts, "ISO imported statistics for '%s'\n", pszIsoSpec);
+ rtFsIsoMakerPrintf(pOpts, " cAddedNames: %'14RU32\n", Results.cAddedNames);
+ rtFsIsoMakerPrintf(pOpts, " cAddedDirs: %'14RU32\n", Results.cAddedDirs);
+ rtFsIsoMakerPrintf(pOpts, " cbAddedDataBlocks: %'14RU64 bytes\n", Results.cbAddedDataBlocks);
+ rtFsIsoMakerPrintf(pOpts, " cAddedFiles: %'14RU32\n", Results.cAddedFiles);
+ rtFsIsoMakerPrintf(pOpts, " cAddedSymlinks: %'14RU32\n", Results.cAddedSymlinks);
+ if (Results.cBootCatEntries == UINT32_MAX)
+ rtFsIsoMakerPrintf(pOpts, " cBootCatEntries: none\n");
+ else
+ rtFsIsoMakerPrintf(pOpts, " cBootCatEntries: %'14RU32\n", Results.cBootCatEntries);
+ rtFsIsoMakerPrintf(pOpts, " cbSysArea: %'14RU32\n", Results.cbSysArea);
+ rtFsIsoMakerPrintf(pOpts, " cErrors: %'14RU32\n", Results.cErrors);
+
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (RTErrInfoIsSet(&ErrInfo.Core))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerImport failed: %Rrc - %s", rc, ErrInfo.Core.pszMsg);
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerImport failed: %Rrc", rc);
+}
+
+
+/**
+ * Deals with: --iso-level, -l
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uLevel The new ISO level.
+ */
+static int rtFsIsoMakerCmdOptSetIsoLevel(PRTFSISOMAKERCMDOPTS pOpts, uint8_t uLevel)
+{
+ int rc = RTFsIsoMakerSetIso9660Level(pOpts->hIsoMaker, uLevel);
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (rc == VERR_WRONG_ORDER)
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Cannot change ISO level to %d after having added files!", uLevel);
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set ISO level to %d: %Rrc", uLevel, rc);
+}
+
+
+/**
+ * Deals with: --rock-ridge, --limited-rock-ridge, --no-rock-ridge
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uLevel The new rock ridge level.
+ */
+static int rtFsIsoMakerCmdOptSetPrimaryRockLevel(PRTFSISOMAKERCMDOPTS pOpts, uint8_t uLevel)
+{
+ int rc = RTFsIsoMakerSetRockRidgeLevel(pOpts->hIsoMaker, uLevel);
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (rc == VERR_WRONG_ORDER)
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Cannot change rock ridge level to %d after having added files!", uLevel);
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set rock ridge level to %d: %Rrc", uLevel, rc);
+}
+
+
+/**
+ * Deals with: --joliet, --no-joliet, --joliet-ucs-level, --ucs-level
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uLevel The new rock ridge level.
+ */
+static int rtFsIsoMakerCmdOptSetJolietUcs2Level(PRTFSISOMAKERCMDOPTS pOpts, uint8_t uLevel)
+{
+ int rc = RTFsIsoMakerSetJolietUcs2Level(pOpts->hIsoMaker, uLevel);
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (rc == VERR_WRONG_ORDER)
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Cannot change joliet UCS level to %d after having added files!", uLevel);
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set joliet UCS level to %d: %Rrc", uLevel, rc);
+}
+
+
+/**
+ * Deals with: --rational-attribs, --strict-attribs, -R, -r
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uLevel The new rock ridge level.
+ */
+static int rtFsIsoMakerCmdOptSetAttribInheritStyle(PRTFSISOMAKERCMDOPTS pOpts, bool fStrict)
+{
+ int rc = RTFsIsoMakerSetAttribInheritStyle(pOpts->hIsoMaker, fStrict);
+ if (RT_SUCCESS(rc))
+ return rc;
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to change attributes handling style to %s: %Rrc",
+ fStrict ? "strict" : "rational", rc);
+}
+
+
+/**
+ * Deals with: -G|--generic-boot {file}
+ *
+ * This concers content the first 16 sectors of the image. We start loading the
+ * file at byte 0 in the image and stops at 32KB.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszGenericBootImage The generic boot image source.
+ */
+static int rtFsIsoMakerCmdOptGenericBoot(PRTFSISOMAKERCMDOPTS pOpts, const char *pszGenericBootImage)
+{
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsChainOpenFile(pszGenericBootImage, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFile,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pszGenericBootImage, rc, offError, &ErrInfo.Core);
+
+ uint8_t abBuf[_32K];
+ size_t cbRead;
+ rc = RTVfsFileReadAt(hVfsFile, 0, abBuf, sizeof(abBuf), &cbRead);
+ RTVfsFileRelease(hVfsFile);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Error reading 32KB from generic boot image '%s': %Rrc", pszGenericBootImage, rc);
+
+ rc = RTFsIsoMakerSetSysAreaContent(pOpts->hIsoMaker, abBuf, cbRead, 0);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerSetSysAreaContent failed with a %zu bytes input: %Rrc", cbRead, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Helper that makes sure we've got a validation boot entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static void rtFsIsoMakerCmdOptEltoritoEnsureValidationEntry(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ if (pOpts->cBootCatEntries == 0)
+ {
+ pOpts->aBootCatEntries[0].enmType = RTFSISOMKCMDELTORITOENTRY::kEntryType_Validation;
+ pOpts->aBootCatEntries[0].u.Validation.idPlatform = ISO9660_ELTORITO_PLATFORM_ID_X86;
+ pOpts->aBootCatEntries[0].u.Validation.pszString = NULL;
+ pOpts->cBootCatEntries = 1;
+ }
+}
+
+
+/**
+ * Helper that makes sure we've got a current boot entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param fForceNew Whether to force a new entry.
+ * @param pidxBootCat Where to return the boot catalog index.
+ */
+static int rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(PRTFSISOMAKERCMDOPTS pOpts, bool fForceNew, uint32_t *pidxBootCat)
+{
+ rtFsIsoMakerCmdOptEltoritoEnsureValidationEntry(pOpts);
+
+ uint32_t i = pOpts->cBootCatEntries;
+ if (i == 2 && fForceNew)
+ {
+ pOpts->aBootCatEntries[i].enmType = RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader;
+ pOpts->aBootCatEntries[i].u.SectionHeader.idPlatform = pOpts->aBootCatEntries[0].u.Validation.idPlatform;
+ pOpts->aBootCatEntries[i].u.SectionHeader.pszString = NULL;
+ pOpts->cBootCatEntries = ++i;
+ }
+
+ if ( i == 1
+ || fForceNew
+ || pOpts->aBootCatEntries[i - 1].enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader)
+ {
+ if (i >= RT_ELEMENTS(pOpts->aBootCatEntries))
+ {
+ *pidxBootCat = UINT32_MAX;
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_BUFFER_OVERFLOW, "Too many boot catalog entries");
+ }
+
+ pOpts->aBootCatEntries[i].enmType = i == 1 ? RTFSISOMKCMDELTORITOENTRY::kEntryType_Default
+ : RTFSISOMKCMDELTORITOENTRY::kEntryType_Section;
+ pOpts->aBootCatEntries[i].u.Section.pszImageNameInIso = NULL;
+ pOpts->aBootCatEntries[i].u.Section.idxImageObj = UINT32_MAX;
+ pOpts->aBootCatEntries[i].u.Section.fInsertBootInfoTable = false;
+ pOpts->aBootCatEntries[i].u.Section.fBootable = true;
+ pOpts->aBootCatEntries[i].u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK;
+ pOpts->aBootCatEntries[i].u.Section.bSystemType = 1 /*FAT12*/;
+ pOpts->aBootCatEntries[i].u.Section.uLoadSeg = 0x7c0;
+ pOpts->aBootCatEntries[i].u.Section.cSectorsToLoad = 4;
+ pOpts->cBootCatEntries = ++i;
+ }
+
+ *pidxBootCat = i - 1;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with: --boot-catalog <path-spec>
+ *
+ * This enters the boot catalog into the namespaces of the image. The path-spec
+ * is similar to what rtFsIsoMakerCmdAddSomething processes, only there isn't a
+ * source file part.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszGenericBootImage The generic boot image source.
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetBootCatalogPath(PRTFSISOMAKERCMDOPTS pOpts, const char *pszBootCat)
+{
+ /* Make sure we'll fail later if no other boot options are present. */
+ rtFsIsoMakerCmdOptEltoritoEnsureValidationEntry(pOpts);
+
+ /* Parse the name spec. */
+ RTFSISOMKCMDPARSEDNAMES Parsed;
+ int rc = rtFsIsoMakerCmdParseNameSpec(pOpts, pszBootCat, false /*fWithSrc*/, &Parsed);
+ if (RT_SUCCESS(rc))
+ {
+ /* Query/create the boot catalog and enter it into the name spaces. */
+ uint32_t idxBootCatObj;
+ rc = RTFsIsoMakerQueryObjIdxForBootCatalog(pOpts->hIsoMaker, &idxBootCatObj);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdSetObjPaths(pOpts, idxBootCatObj, &Parsed, "boot catalog");
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerQueryBootCatalogPathObjIdx failed: %Rrc", rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Deals with: --eltorito-add-image {file-spec}
+ *
+ * This differs from -b|--eltorito-boot in that it takes a source file
+ * specification identical to what rtFsIsoMakerCmdAddSomething processes instead
+ * of a reference to a file in the image.
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszGenericBootImage The generic boot image source.
+ */
+static int rtFsIsoMakerCmdOptEltoritoAddImage(PRTFSISOMAKERCMDOPTS pOpts, const char *pszBootImageSpec)
+{
+ /* Parse the name spec. */
+ RTFSISOMKCMDPARSEDNAMES Parsed;
+ int rc = rtFsIsoMakerCmdParseNameSpec(pOpts, pszBootImageSpec, true /*fWithSrc*/, &Parsed);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t idxBootCat;
+ rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pOpts->aBootCatEntries[idxBootCat].u.Section.idxImageObj != UINT32_MAX
+ || pOpts->aBootCatEntries[idxBootCat].u.Section.pszImageNameInIso != NULL)
+ rc = rtFsIsoMakerCmdSyntaxError(pOpts, "boot image already given for current El Torito entry (%#u)", idxBootCat);
+ else
+ {
+ uint32_t idxImageObj;
+ rc = rtFsIsoMakerCmdStatAndAddFile(pOpts, Parsed.aNames[Parsed.cNamesWithSrc - 1].szPath, &Parsed, &idxImageObj);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.idxImageObj = idxImageObj;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Deals with: -b|--eltorito-boot {file-in-iso}
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszGenericBootImage The generic boot image source.
+ */
+static int rtFsIsoMakerCmdOptEltoritoBoot(PRTFSISOMAKERCMDOPTS pOpts, const char *pszBootImage)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pOpts->aBootCatEntries[idxBootCat].u.Section.idxImageObj != UINT32_MAX
+ || pOpts->aBootCatEntries[idxBootCat].u.Section.pszImageNameInIso != NULL)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "boot image already given for current El Torito entry (%#u)", idxBootCat);
+
+ uint32_t idxImageObj = RTFsIsoMakerGetObjIdxForPath(pOpts->hIsoMaker, RTFSISOMAKER_NAMESPACE_ALL, pszBootImage);
+ if (idxImageObj == UINT32_MAX)
+ pOpts->aBootCatEntries[idxBootCat].u.Section.pszImageNameInIso = pszBootImage;
+ pOpts->aBootCatEntries[idxBootCat].u.Section.idxImageObj = idxImageObj;
+ }
+ return rc;
+}
+
+
+/**
+ * Deals with: --eltorito-platform-id {x86|PPC|Mac|efi|number}
+ *
+ * Operates on the validation entry or a section header.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszPlatformId The platform ID.
+ */
+static int rtFsIsoMakerCmdOptEltoritoPlatformId(PRTFSISOMAKERCMDOPTS pOpts, const char *pszPlatformId)
+{
+ /* Decode it. */
+ uint8_t idPlatform;
+ if (strcmp(pszPlatformId, "x86") == 0)
+ idPlatform = ISO9660_ELTORITO_PLATFORM_ID_X86;
+ else if (strcmp(pszPlatformId, "PPC") == 0)
+ idPlatform = ISO9660_ELTORITO_PLATFORM_ID_PPC;
+ else if (strcmp(pszPlatformId, "Mac") == 0)
+ idPlatform = ISO9660_ELTORITO_PLATFORM_ID_MAC;
+ else if (strcmp(pszPlatformId, "efi") == 0)
+ idPlatform = ISO9660_ELTORITO_PLATFORM_ID_EFI;
+ else
+ {
+ int rc = RTStrToUInt8Full(pszPlatformId, 0, &idPlatform);
+ if (rc != VINF_SUCCESS)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "invalid or unknown platform ID: %s", pszPlatformId);
+ }
+
+ /* If this option comes before anything related to the default entry, work
+ on the validation entry. */
+ if (pOpts->cBootCatEntries <= 1)
+ {
+ rtFsIsoMakerCmdOptEltoritoEnsureValidationEntry(pOpts);
+ pOpts->aBootCatEntries[0].u.Validation.idPlatform = idPlatform;
+ }
+ /* Otherwise, work on the current section header, creating a new one if necessary. */
+ else
+ {
+ uint32_t idxBootCat = pOpts->cBootCatEntries - 1;
+ if (pOpts->aBootCatEntries[idxBootCat].enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader)
+ pOpts->aBootCatEntries[idxBootCat].u.SectionHeader.idPlatform = idPlatform;
+ else
+ {
+ idxBootCat++;
+ if (idxBootCat + 2 > RT_ELEMENTS(pOpts->aBootCatEntries))
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_BUFFER_OVERFLOW, "Too many boot catalog entries");
+
+ pOpts->aBootCatEntries[idxBootCat].enmType = RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader;
+ pOpts->aBootCatEntries[idxBootCat].u.SectionHeader.idPlatform = idPlatform;
+ pOpts->aBootCatEntries[idxBootCat].u.SectionHeader.pszString = NULL;
+ pOpts->cBootCatEntries = idxBootCat + 1;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with: -no-boot
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetNotBootable(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.fBootable = false;
+ return rc;
+}
+
+
+/**
+ * Deals with: -hard-disk-boot, -no-emulation-boot, --eltorito-floppy-12,
+ * --eltorito-floppy-144, --eltorito-floppy-288
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param bMediaType The media type.
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetMediaType(PRTFSISOMAKERCMDOPTS pOpts, uint8_t bMediaType)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.bBootMediaType = bMediaType;
+ return rc;
+}
+
+
+/**
+ * Deals with: -boot-load-seg {seg}
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param uSeg The load segment.
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetLoadSegment(PRTFSISOMAKERCMDOPTS pOpts, uint16_t uSeg)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.uLoadSeg = uSeg;
+ return rc;
+}
+
+
+/**
+ * Deals with: -boot-load-size {sectors}
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param cSectors Number of emulated sectors to load
+ */
+static int rtFsIsoMakerCmdOptEltoritoSetLoadSectorCount(PRTFSISOMAKERCMDOPTS pOpts, uint16_t cSectors)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.cSectorsToLoad = cSectors;
+ return rc;
+}
+
+
+/**
+ * Deals with: -boot-info-table
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptEltoritoEnableBootInfoTablePatching(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ uint32_t idxBootCat;
+ int rc = rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, false /*fForceNew*/, &idxBootCat);
+ if (RT_SUCCESS(rc))
+ pOpts->aBootCatEntries[idxBootCat].u.Section.fInsertBootInfoTable = true;
+ return rc;
+}
+
+
+/**
+ * Validates and commits the boot catalog stuff.
+ *
+ * ASSUMING this is called after all options are parsed and there is only this
+ * one call.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptEltoritoCommitBootCatalog(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ if (pOpts->cBootCatEntries == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Locate and configure the boot images first.
+ */
+ int rc;
+ PRTFSISOMKCMDELTORITOENTRY pBootCatEntry = &pOpts->aBootCatEntries[1];
+ for (uint32_t idxBootCat = 1; idxBootCat < pOpts->cBootCatEntries; idxBootCat++, pBootCatEntry++)
+ if ( pBootCatEntry->enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_Default
+ || pBootCatEntry->enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_Section)
+ {
+ /* Make sure we've got a boot image. */
+ uint32_t idxImageObj = pBootCatEntry->u.Section.idxImageObj;
+ if (idxImageObj == UINT32_MAX)
+ {
+ const char *pszBootImage = pBootCatEntry->u.Section.pszImageNameInIso;
+ if (pszBootImage == NULL)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "No image name given for boot catalog entry #%u", idxBootCat);
+
+ idxImageObj = RTFsIsoMakerGetObjIdxForPath(pOpts->hIsoMaker, RTFSISOMAKER_NAMESPACE_ALL, pszBootImage);
+ if (idxImageObj == UINT32_MAX)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Unable to locate image for boot catalog entry #%u: %s",
+ idxBootCat, pszBootImage);
+ pBootCatEntry->u.Section.idxImageObj = idxImageObj;
+ }
+
+ /* Enable patching it? */
+ if (pBootCatEntry->u.Section.fInsertBootInfoTable)
+ {
+ rc = RTFsIsoMakerObjEnableBootInfoTablePatching(pOpts->hIsoMaker, idxImageObj, true);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "RTFsIsoMakerObjEnableBootInfoTablePatching failed on entry #%u: %Rrc",
+ idxBootCat, rc);
+ }
+
+ /* Figure out the floppy type given the object size. */
+ if (pBootCatEntry->u.Section.bBootMediaType == ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK)
+ {
+ uint64_t cbImage;
+ rc = RTFsIsoMakerObjQueryDataSize(pOpts->hIsoMaker, idxImageObj, &cbImage);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerObjGetDataSize failed on entry #%u: %Rrc",
+ idxBootCat, rc);
+ if (cbImage == 1228800)
+ pBootCatEntry->u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_2_MB;
+ else if (cbImage <= 1474560)
+ pBootCatEntry->u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_44_MB;
+ else if (cbImage <= 2949120)
+ pBootCatEntry->u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_2_88_MB;
+ else
+ pBootCatEntry->u.Section.bBootMediaType = ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK;
+ }
+ }
+
+ /*
+ * Add the boot catalog entries.
+ */
+ pBootCatEntry = &pOpts->aBootCatEntries[0];
+ for (uint32_t idxBootCat = 0; idxBootCat < pOpts->cBootCatEntries; idxBootCat++, pBootCatEntry++)
+ switch (pBootCatEntry->enmType)
+ {
+ case RTFSISOMKCMDELTORITOENTRY::kEntryType_Validation:
+ Assert(idxBootCat == 0);
+ rc = RTFsIsoMakerBootCatSetValidationEntry(pOpts->hIsoMaker, pBootCatEntry->u.Validation.idPlatform,
+ pBootCatEntry->u.Validation.pszString);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerBootCatSetValidationEntry failed: %Rrc", rc);
+ break;
+
+ case RTFSISOMKCMDELTORITOENTRY::kEntryType_Default:
+ case RTFSISOMKCMDELTORITOENTRY::kEntryType_Section:
+ Assert(pBootCatEntry->enmType == RTFSISOMKCMDELTORITOENTRY::kEntryType_Default ? idxBootCat == 1 : idxBootCat > 2);
+ rc = RTFsIsoMakerBootCatSetSectionEntry(pOpts->hIsoMaker, idxBootCat,
+ pBootCatEntry->u.Section.idxImageObj,
+ pBootCatEntry->u.Section.bBootMediaType,
+ pBootCatEntry->u.Section.bSystemType,
+ pBootCatEntry->u.Section.fBootable,
+ pBootCatEntry->u.Section.uLoadSeg,
+ pBootCatEntry->u.Section.cSectorsToLoad,
+ ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE, NULL, 0);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerBootCatSetSectionEntry failed on entry #%u: %Rrc",
+ idxBootCat, rc);
+ break;
+
+ case RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader:
+ {
+ uint32_t cEntries = 1;
+ while ( idxBootCat + cEntries < pOpts->cBootCatEntries
+ && pBootCatEntry[cEntries].enmType != RTFSISOMKCMDELTORITOENTRY::kEntryType_SectionHeader)
+ cEntries++;
+ cEntries--;
+
+ Assert(idxBootCat > 1);
+ rc = RTFsIsoMakerBootCatSetSectionHeaderEntry(pOpts->hIsoMaker, idxBootCat, cEntries,
+ pBootCatEntry->u.SectionHeader.idPlatform,
+ pBootCatEntry->u.SectionHeader.pszString);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc,
+ "RTFsIsoMakerBootCatSetSectionHeaderEntry failed on entry #%u: %Rrc",
+ idxBootCat, rc);
+ break;
+ }
+
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with: --eltorito-new-entry, --eltorito-alt-boot
+ *
+ * This operates on the current eltorito boot catalog entry.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ */
+static int rtFsIsoMakerCmdOptEltoritoNewEntry(PRTFSISOMAKERCMDOPTS pOpts)
+{
+ uint32_t idxBootCat;
+ return rtFsIsoMakerCmdOptEltoritoEnsureSectionEntry(pOpts, true /*fForceNew*/, &idxBootCat);
+}
+
+
+/**
+ * Sets a string property in all namespaces.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszValue The new string value.
+ * @param enmStringProp The string property.
+ */
+static int rtFsIsoMakerCmdOptSetStringProp(PRTFSISOMAKERCMDOPTS pOpts, const char *pszValue, RTFSISOMAKERSTRINGPROP enmStringProp)
+{
+ int rc = RTFsIsoMakerSetStringProp(pOpts->hIsoMaker, enmStringProp, pOpts->fDstNamespaces, pszValue);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set string property %d to '%s': %Rrc", enmStringProp, pszValue, rc);
+ return rc;
+}
+
+
+/**
+ * Handles the --dir-mode and --file-mode options.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param fDir True if applies to dir, false if applies to
+ * files.
+ * @param fMode The forced mode.
+ */
+static int rtFsIsoMakerCmdOptSetFileOrDirMode(PRTFSISOMAKERCMDOPTS pOpts, bool fDir, RTFMODE fMode)
+{
+ /* Change the mode masks. */
+ int rc;
+ if (fDir)
+ rc = RTFsIsoMakerSetForcedDirMode(pOpts->hIsoMaker, fMode, true /*fForced*/);
+ else
+ rc = RTFsIsoMakerSetForcedFileMode(pOpts->hIsoMaker, fMode, true /*fForced*/);
+ if (RT_SUCCESS(rc))
+ {
+ /* Then enable rock.*/
+ rc = RTFsIsoMakerSetRockRidgeLevel(pOpts->hIsoMaker, 2);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to enable rock ridge: %Rrc", rc);
+ }
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set %s force & default mode mask to %04o: %Rrc",
+ fMode, fDir ? "directory" : "file", rc);
+}
+
+
+/**
+ * Handles the --no-dir-mode and --no-file-mode options that counters
+ * --dir-mode and --file-mode.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param fDir True if applies to dir, false if applies to
+ * files.
+ */
+static int rtFsIsoMakerCmdOptDisableFileOrDirMode(PRTFSISOMAKERCMDOPTS pOpts, bool fDir)
+{
+ int rc;
+ if (fDir)
+ rc = RTFsIsoMakerSetForcedDirMode(pOpts->hIsoMaker, 0, false /*fForced*/);
+ else
+ rc = RTFsIsoMakerSetForcedFileMode(pOpts->hIsoMaker, 0, false /*fForced*/);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to disable forced %s mode mask: %Rrc", fDir ? "directory" : "file", rc);
+}
+
+
+
+/**
+ * Handles the --new-dir-mode option.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param fMode The forced mode.
+ */
+static int rtFsIsoMakerCmdOptSetNewDirMode(PRTFSISOMAKERCMDOPTS pOpts, RTFMODE fMode)
+{
+ int rc = RTFsIsoMakerSetDefaultDirMode(pOpts->hIsoMaker, fMode);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "Failed to set default dir mode mask to %04o: %Rrc", fMode, rc);
+}
+
+
+/**
+ * Handles the --chmod option.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The option value.
+ */
+static int rtFsIsoMakerCmdOptChmod(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec)
+{
+ /*
+ * Parse the mode part.
+ */
+ int rc;
+ uint32_t fUnset = 07777;
+ uint32_t fSet = 0;
+ const char *pszPath = pszSpec;
+ if (RT_C_IS_DIGIT(*pszPath))
+ {
+ rc = RTStrToUInt32Ex(pszSpec, (char **)&pszPath, 8, &fSet);
+ if (rc != VWRN_TRAILING_CHARS)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, octal mode parse failed: %s (%Rrc)", pszSpec, rc);
+ if (fSet & ~07777)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, invalid mode mask: 0%o, max 07777", fSet);
+ if (*pszPath != ':')
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, expected colon after mode: %s", pszSpec);
+ }
+ else
+ {
+ pszPath = strchr(pszPath, ':');
+ if (pszPath == NULL)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, expected colon after mode: %s", pszSpec);
+ size_t const cchMode = pszPath - pszSpec;
+
+ /* We currently only matches certain patterns. Later this needs to be generalized into a RTFile or RTPath method. */
+ fUnset = 0;
+#define MATCH_MODE_STR(a_szMode) (cchMode == sizeof(a_szMode) - 1U && memcmp(pszSpec, a_szMode, sizeof(a_szMode) - 1) == 0)
+ if (MATCH_MODE_STR("a+x"))
+ fSet = 0111;
+ else if (MATCH_MODE_STR("a+r"))
+ fSet = 0444;
+ else if (MATCH_MODE_STR("a+rx"))
+ fSet = 0555;
+ else
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Sorry, --chmod doesn't understand complicated mode expressions: %s", pszSpec);
+#undef MATCH_MODE_STR
+ }
+
+ /*
+ * Check that the file starts with a slash.
+ */
+ pszPath++;
+ if (!RTPATH_IS_SLASH(*pszPath))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --chmod, path must start with a slash: %s", pszSpec);
+
+ /*
+ * Do the job.
+ */
+ rc = RTFsIsoMakerSetPathMode(pOpts->hIsoMaker, pszPath, pOpts->fDstNamespaces, fSet, fUnset, 0 /*fFlags*/, NULL /*pcHits*/);
+ if (rc == VWRN_NOT_FOUND)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Could not find --chmod path: %s", pszPath);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "RTFsIsoMakerSetPathMode(,%s,%#x,%o,%o,0,) failed: %Rrc",
+ pszPath, pOpts->fDstNamespaces, fSet, fUnset, rc);
+}
+
+
+/**
+ * Handles the --chown and --chgrp options.
+ *
+ * @returns IPRT status code
+ * @param pOpts The ISO maker command instance.
+ * @param pszSpec The option value.
+ * @param fIsChOwn Set if 'chown', clear if 'chgrp'.
+ */
+static int rtFsIsoMakerCmdOptChangeOwnerGroup(PRTFSISOMAKERCMDOPTS pOpts, const char *pszSpec, bool fIsChOwn)
+{
+ const char * const pszOpt = fIsChOwn ? "chown" : "chgrp";
+
+ /*
+ * Parse out the ID and path .
+ */
+ uint32_t idValue;
+ const char *pszPath = pszSpec;
+ int rc = RTStrToUInt32Ex(pszSpec, (char **)&pszPath, 0, &idValue);
+ if (rc != VWRN_TRAILING_CHARS)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --%s, numeric ID parse failed: %s (%Rrc)", pszOpt, pszSpec, rc);
+ if (*pszPath != ':')
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --%s, expected colon after ID: %s", pszOpt, pszSpec);
+ pszPath++;
+ if (!RTPATH_IS_SLASH(*pszPath))
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Malformed --%s, path must start with a slash: %s", pszOpt, pszSpec);
+
+ /*
+ * Do the job.
+ */
+ if (fIsChOwn)
+ rc = RTFsIsoMakerSetPathOwnerId(pOpts->hIsoMaker, pszPath, pOpts->fDstNamespaces, idValue, NULL /*pcHits*/);
+ else
+ rc = RTFsIsoMakerSetPathGroupId(pOpts->hIsoMaker, pszPath, pOpts->fDstNamespaces, idValue, NULL /*pcHits*/);
+ if (rc == VWRN_NOT_FOUND)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "Could not find --%s path: %s", pszOpt, pszPath);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "RTFsIsoMakerSetPath%sId(,%s,%#x,%u,) failed: %Rrc",
+ fIsChOwn ? "Owner" : "Group", pszPath, pOpts->fDstNamespaces, idValue, rc);
+}
+
+
+/**
+ * Loads an argument file (e.g. a .iso-file) and parses it.
+ *
+ * @returns IPRT status code.
+ * @param pOpts The ISO maker command instance.
+ * @param pszFileSpec The file to parse.
+ * @param cDepth The current nesting depth.
+ */
+static int rtFsIsoMakerCmdParseArgumentFile(PRTFSISOMAKERCMDOPTS pOpts, const char *pszFileSpec, unsigned cDepth)
+{
+ if (cDepth > 2)
+ return rtFsIsoMakerCmdErrorRc(pOpts, VERR_INVALID_PARAMETER, "Too many nested argument files!");
+
+ /*
+ * Read the file into memory.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsChainOpenFile(pszFileSpec, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &hVfsFile,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdChainError(pOpts, "RTVfsChainOpenFile", pszFileSpec, rc, offError, &ErrInfo.Core);
+
+ uint64_t cbFile = 0;
+ rc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbFile < _2M)
+ {
+ char *pszContent = (char *)RTMemTmpAllocZ((size_t)cbFile + 1);
+ if (pszContent)
+ {
+ rc = RTVfsFileRead(hVfsFile, pszContent, (size_t)cbFile, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check that it's valid UTF-8 and turn it into an argument vector.
+ */
+ rc = RTStrValidateEncodingEx(pszContent, (size_t)cbFile + 1,
+ RTSTR_VALIDATE_ENCODING_EXACT_LENGTH | RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fGetOpt = strstr(pszContent, "--iprt-iso-maker-file-marker-ms") == NULL
+ ? RTGETOPTARGV_CNV_QUOTE_BOURNE_SH : RTGETOPTARGV_CNV_QUOTE_MS_CRT;
+ fGetOpt |= RTGETOPTARGV_CNV_MODIFY_INPUT;
+ char **papszArgs;
+ int cArgs;
+ rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszContent, fGetOpt, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Parse them.
+ */
+ rc = rtFsIsoMakerCmdParse(pOpts, cArgs, papszArgs, cDepth + 1);
+
+ RTGetOptArgvFreeEx(papszArgs, fGetOpt);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: RTGetOptArgvFromString failed: %Rrc", pszFileSpec, rc);
+
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: invalid encoding", pszFileSpec);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: error to read it into memory: %Rrc", pszFileSpec, rc);
+ RTMemTmpFree(pszContent);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_NO_TMP_MEMORY, "%s: failed to allocte %zu bytes for reading",
+ pszFileSpec, (size_t)cbFile + 1);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_FILE_TOO_BIG, "%s: file is too big: %'RU64 bytes, max 2MB", pszFileSpec, cbFile);
+ }
+ else
+ rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: RTVfsFileQuerySize failed: %Rrc", pszFileSpec, rc);
+ RTVfsFileRelease(hVfsFile);
+ return rc;
+}
+
+
+/**
+ * Parses the given command line options.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN if exit successfully (help, version).
+ * @param pOpts The ISO maker command instance.
+ * @param cArgs Number of arguments in papszArgs.
+ * @param papszArgs The argument vector to parse.
+ */
+static int rtFsIsoMakerCmdParse(PRTFSISOMAKERCMDOPTS pOpts, unsigned cArgs, char **papszArgs, unsigned cDepth)
+{
+ /* Setup option parsing. */
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, g_aRtFsIsoMakerOptions, RT_ELEMENTS(g_aRtFsIsoMakerOptions),
+ cDepth == 0 ? 1 : 0 /*iFirst*/, 0 /*fFlags*/);
+ if (RT_FAILURE(rc))
+ return rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTGetOpt failed: %Rrc", rc);
+
+ /*
+ * Parse parameters. Parameters are position dependent.
+ */
+ RTGETOPTUNION ValueUnion;
+ while ( RT_SUCCESS(rc)
+ && (rc = RTGetOpt(&GetState, &ValueUnion)) != 0)
+ {
+ switch (rc)
+ {
+ /*
+ * Files and directories.
+ */
+ case VINF_GETOPT_NOT_OPTION:
+ if ( *ValueUnion.psz != '@'
+ || strchr(ValueUnion.psz, '='))
+ rc = rtFsIsoMakerCmdAddSomething(pOpts, ValueUnion.psz);
+ else
+ rc = rtFsIsoMakerCmdParseArgumentFile(pOpts, ValueUnion.psz + 1, cDepth);
+ break;
+
+
+ /*
+ * General options
+ */
+ case 'o':
+ if (pOpts->fVirtualImageMaker)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "The --output option is not allowed");
+ if (pOpts->pszOutFile)
+ return rtFsIsoMakerCmdSyntaxError(pOpts, "The --output option is specified more than once");
+ pOpts->pszOutFile = ValueUnion.psz;
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NAME_SETUP:
+ rc = rtFsIsoMakerCmdOptNameSetup(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NAME_SETUP_FROM_IMPORT:
+ rc = rtFsIsoMakerCmdOptNameSetupFromImport(pOpts);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PUSH_ISO:
+ rc = rtFsIsoMakerCmdOptPushIso(pOpts, ValueUnion.psz, "--push-iso", 0);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_JOLIET:
+ rc = rtFsIsoMakerCmdOptPushIso(pOpts, ValueUnion.psz, "--push-iso-no-joliet", RTFSISO9660_F_NO_JOLIET);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK:
+ rc = rtFsIsoMakerCmdOptPushIso(pOpts, ValueUnion.psz, "--push-iso-no-rock", RTFSISO9660_F_NO_ROCK);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PUSH_ISO_NO_ROCK_NO_JOLIET:
+ rc = rtFsIsoMakerCmdOptPushIso(pOpts, ValueUnion.psz, "--push-iso-no-rock-no-joliet",
+ RTFSISO9660_F_NO_ROCK | RTFSISO9660_F_NO_JOLIET);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_POP:
+ rc = rtFsIsoMakerCmdOptPop(pOpts);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_IMPORT_ISO:
+ rc = rtFsIsoMakerCmdOptImportIso(pOpts, ValueUnion.psz);
+ break;
+
+
+ /*
+ * Namespace configuration.
+ */
+ case RTFSISOMAKERCMD_OPT_ISO_LEVEL:
+ rc = rtFsIsoMakerCmdOptSetIsoLevel(pOpts, ValueUnion.u8);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ROCK_RIDGE:
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 2);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_LIMITED_ROCK_RIDGE:
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 1);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_ROCK_RIDGE:
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 0);
+ break;
+
+ case 'J':
+ rc = rtFsIsoMakerCmdOptSetJolietUcs2Level(pOpts, 3);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_JOLIET:
+ rc = rtFsIsoMakerCmdOptSetJolietUcs2Level(pOpts, 0);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_JOLIET_LEVEL:
+ rc = rtFsIsoMakerCmdOptSetJolietUcs2Level(pOpts, ValueUnion.u8);
+ break;
+
+
+ /*
+ * File attributes.
+ */
+ case RTFSISOMAKERCMD_OPT_RATIONAL_ATTRIBS:
+ rc = rtFsIsoMakerCmdOptSetAttribInheritStyle(pOpts, false /*fStrict*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_STRICT_ATTRIBS:
+ rc = rtFsIsoMakerCmdOptSetAttribInheritStyle(pOpts, true /*fStrict*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_FILE_MODE:
+ rc = rtFsIsoMakerCmdOptSetFileOrDirMode(pOpts, false /*fDir*/, ValueUnion.u32);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_FILE_MODE:
+ rc = rtFsIsoMakerCmdOptDisableFileOrDirMode(pOpts, false /*fDir*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_DIR_MODE:
+ rc = rtFsIsoMakerCmdOptSetFileOrDirMode(pOpts, true /*fDir*/, ValueUnion.u32);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_DIR_MODE:
+ rc = rtFsIsoMakerCmdOptDisableFileOrDirMode(pOpts, true /*fDir*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NEW_DIR_MODE:
+ rc = rtFsIsoMakerCmdOptSetNewDirMode(pOpts, ValueUnion.u32);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_CHMOD:
+ rc = rtFsIsoMakerCmdOptChmod(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_CHOWN:
+ rc = rtFsIsoMakerCmdOptChangeOwnerGroup(pOpts, ValueUnion.psz, true /*fIsChOwn*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_CHGRP:
+ rc = rtFsIsoMakerCmdOptChangeOwnerGroup(pOpts, ValueUnion.psz, false /*fIsChOwn*/);
+ break;
+
+
+ /*
+ * Boot related options.
+ */
+ case 'G': /* --generic-boot <file> */
+ rc = rtFsIsoMakerCmdOptGenericBoot(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_ADD_IMAGE:
+ rc = rtFsIsoMakerCmdOptEltoritoAddImage(pOpts, ValueUnion.psz);
+ break;
+
+ case 'b': /* --eltorito-boot <boot.img> */
+ rc = rtFsIsoMakerCmdOptEltoritoBoot(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_NEW_ENTRY:
+ rc = rtFsIsoMakerCmdOptEltoritoNewEntry(pOpts);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_PLATFORM_ID:
+ rc = rtFsIsoMakerCmdOptEltoritoPlatformId(pOpts, ValueUnion.psz);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_NO_BOOT:
+ rc = rtFsIsoMakerCmdOptEltoritoSetNotBootable(pOpts);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_12:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_2_MB);
+ break;
+ case RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_144:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_44_MB);
+ break;
+ case RTFSISOMAKERCMD_OPT_ELTORITO_FLOPPY_288:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_2_88_MB);
+ break;
+ case RTFSISOMAKERCMD_OPT_ELTORITO_HARD_DISK_BOOT:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK);
+ break;
+ case RTFSISOMAKERCMD_OPT_ELTORITO_NO_EMULATION_BOOT:
+ rc = rtFsIsoMakerCmdOptEltoritoSetMediaType(pOpts, ISO9660_ELTORITO_BOOT_MEDIA_TYPE_NO_EMULATION);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SEG:
+ rc = rtFsIsoMakerCmdOptEltoritoSetLoadSegment(pOpts, ValueUnion.u16);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_LOAD_SIZE:
+ rc = rtFsIsoMakerCmdOptEltoritoSetLoadSectorCount(pOpts, ValueUnion.u16);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_ELTORITO_INFO_TABLE:
+ rc = rtFsIsoMakerCmdOptEltoritoEnableBootInfoTablePatching(pOpts);
+ break;
+
+ case 'c': /* --boot-catalog <cd-path> */
+ rc = rtFsIsoMakerCmdOptEltoritoSetBootCatalogPath(pOpts, ValueUnion.psz);
+ break;
+
+
+ /*
+ * Image/namespace property related options.
+ */
+ case RTFSISOMAKERCMD_OPT_ABSTRACT_FILE_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID);
+ break;
+
+ case 'A': /* --application-id */
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_APPLICATION_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_BIBLIOGRAPHIC_FILE_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_COPYRIGHT_FILE_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID);
+ break;
+
+ case 'P': /* -publisher */
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_PUBLISHER_ID);
+ break;
+
+ case 'p': /* --preparer*/
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_SYSTEM_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_SYSTEM_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_VOLUME_ID: /* (should've been '-V') */
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_VOLUME_ID);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_VOLUME_SET_ID:
+ rc = rtFsIsoMakerCmdOptSetStringProp(pOpts, ValueUnion.psz, RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID);
+ break;
+
+
+ /*
+ * Compatibility.
+ */
+ case RTFSISOMAKERCMD_OPT_GRAFT_POINTS:
+ rc = rtFsIsoMakerCmdOptNameSetup(pOpts, "iso+joliet+udf+hfs");
+ break;
+
+ case 'l':
+ if (RTFsIsoMakerGetIso9660Level(pOpts->hIsoMaker) >= 2)
+ rc = rtFsIsoMakerCmdOptSetIsoLevel(pOpts, 2);
+ break;
+
+ case 'R':
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 2);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdOptSetAttribInheritStyle(pOpts, true /*fStrict*/);
+ break;
+
+ case 'r':
+ rc = rtFsIsoMakerCmdOptSetPrimaryRockLevel(pOpts, 2);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdOptSetAttribInheritStyle(pOpts, false /*fStrict*/);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_PAD:
+ rc = RTFsIsoMakerSetImagePadding(pOpts->hIsoMaker, 150);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerSetImagePadding failed: %Rrc", rc);
+ break;
+
+ case RTFSISOMAKERCMD_OPT_NO_PAD:
+ rc = RTFsIsoMakerSetImagePadding(pOpts->hIsoMaker, 0);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "RTFsIsoMakerSetImagePadding failed: %Rrc", rc);
+ break;
+
+
+ /*
+ * VISO specific
+ */
+ case RTFSISOMAKERCMD_OPT_IPRT_ISO_MAKER_FILE_MARKER:
+ /* ignored */
+ break;
+
+
+ /*
+ * Testing.
+ */
+ case RTFSISOMAKERCMD_OPT_OUTPUT_BUFFER_SIZE: /* --output-buffer-size {cb} */
+ pOpts->cbOutputReadBuffer = ValueUnion.u32;
+ break;
+
+ case RTFSISOMAKERCMD_OPT_RANDOM_OUTPUT_BUFFER_SIZE: /* --random-output-buffer-size */
+ pOpts->fRandomOutputReadBufferSize = true;
+ break;
+
+ case RTFSISOMAKERCMD_OPT_RANDOM_ORDER_VERIFICATION: /* --random-order-verification {cb} */
+ pOpts->cbRandomOrderVerifciationBlock = ValueUnion.u32;
+ break;
+
+
+ /*
+ * Standard bits.
+ */
+ case 'h':
+ rtFsIsoMakerCmdUsage(pOpts, papszArgs[0]);
+ return pOpts->fVirtualImageMaker ? VERR_NOT_FOUND : VINF_CALLBACK_RETURN;
+
+ case 'V':
+ rtFsIsoMakerPrintf(pOpts, "%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return pOpts->fVirtualImageMaker ? VERR_NOT_FOUND : VINF_CALLBACK_RETURN;
+
+ default:
+ if (rc > 0 && RT_C_IS_GRAPH(rc))
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_GETOPT_UNKNOWN_OPTION, "Unhandled option: -%c", rc);
+ else if (rc > 0)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, VERR_GETOPT_UNKNOWN_OPTION, "Unhandled option: %i (%#x)", rc, rc);
+ else if (rc == VERR_GETOPT_UNKNOWN_OPTION)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "Unknown option: '%s'", ValueUnion.psz);
+ else if (ValueUnion.pDef)
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%s: %Rrs", ValueUnion.pDef->pszLong, rc);
+ else
+ rc = rtFsIsoMakerCmdErrorRc(pOpts, rc, "%Rrs", rc);
+ return rc;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Extended ISO maker command.
+ *
+ * This can be used as a ISO maker command that produces a image file, or
+ * alternatively for setting up a virtual ISO in memory.
+ *
+ * @returns IPRT status code
+ * @param cArgs Number of arguments.
+ * @param papszArgs Pointer to argument array.
+ * @param hVfsCwd The current working directory to assume when processing
+ * relative file/dir references. Pass NIL_RTVFSDIR to use
+ * the current CWD of the process.
+ * @param pszCwd Path to @a hVfsCwdDir. Use for error reporting and
+ * optimizing the open file count if possible.
+ * @param phVfsFile Where to return the virtual ISO. Pass NULL to for
+ * normal operation (creates file on disk).
+ * @param pErrInfo Where to return extended error information in the
+ * virtual ISO mode.
+ */
+RTDECL(int) RTFsIsoMakerCmdEx(unsigned cArgs, char **papszArgs, RTVFSDIR hVfsCwd, const char *pszCwd,
+ PRTVFSFILE phVfsFile, PRTERRINFO pErrInfo)
+{
+ if (phVfsFile)
+ *phVfsFile = NIL_RTVFSFILE;
+
+ /*
+ * Create instance.
+ */
+ RTFSISOMAKERCMDOPTS Opts;
+ RT_ZERO(Opts);
+ Opts.hIsoMaker = NIL_RTFSISOMAKER;
+ Opts.pErrInfo = pErrInfo;
+ Opts.fVirtualImageMaker = phVfsFile != NULL;
+ Opts.cNameSpecifiers = 1;
+ Opts.afNameSpecifiers[0] = RTFSISOMAKERCMDNAME_MAJOR_MASK;
+ Opts.fDstNamespaces = RTFSISOMAKERCMDNAME_MAJOR_MASK;
+ Opts.pszTransTbl = "TRANS.TBL"; /** @todo query this below */
+ for (uint32_t i = 0; i < RT_ELEMENTS(Opts.aBootCatEntries); i++)
+ Opts.aBootCatEntries[i].u.Section.idxImageObj = UINT32_MAX;
+
+ /* Initialize the source stack with NILs (to be on the safe size). */
+ Opts.iSrcStack = -1;
+ for (uint32_t i = 0; i < RT_ELEMENTS(Opts.aSrcStack); i++)
+ {
+ Opts.aSrcStack[i].hSrcDir = NIL_RTVFSDIR;
+ Opts.aSrcStack[i].hSrcVfs = NIL_RTVFS;
+ }
+
+ /* Push the CWD if present. */
+ if (hVfsCwd != NIL_RTVFSDIR)
+ {
+ AssertReturn(pszCwd, VERR_INVALID_PARAMETER);
+ uint32_t cRefs = RTVfsDirRetain(hVfsCwd);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ Opts.aSrcStack[0].hSrcDir = hVfsCwd;
+ Opts.aSrcStack[0].pszSrcVfs = pszCwd;
+ Opts.iSrcStack = 0;
+ }
+
+ /* Create the ISO creator instance. */
+ int rc = RTFsIsoMakerCreate(&Opts.hIsoMaker);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Parse the command line and check for mandatory options.
+ */
+ rc = rtFsIsoMakerCmdParse(&Opts, cArgs, papszArgs, 0);
+ if (RT_SUCCESS(rc) && rc != VINF_CALLBACK_RETURN)
+ {
+ if (!Opts.cItemsAdded)
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, VERR_NO_DATA, "Cowardly refuses to create empty ISO image");
+ else if (!Opts.pszOutFile && !Opts.fVirtualImageMaker)
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, VERR_INVALID_PARAMETER, "No output file specified (--output <file>)");
+
+ /*
+ * Final actions.
+ */
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoMakerCmdOptEltoritoCommitBootCatalog(&Opts);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Finalize the image and get the virtual file.
+ */
+ rc = RTFsIsoMakerFinalize(Opts.hIsoMaker);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTFsIsoMakerCreateVfsOutputFile(Opts.hIsoMaker, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * We're done now if we're only setting up a virtual image.
+ */
+ if (Opts.fVirtualImageMaker)
+ *phVfsFile = hVfsFile;
+ else
+ {
+ rc = rtFsIsoMakerCmdWriteImage(&Opts, hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, rc, "RTFsIsoMakerCreateVfsOutputFile failed: %Rrc", rc);
+ }
+ else
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, rc, "RTFsIsoMakerFinalize failed: %Rrc", rc);
+ }
+ }
+ }
+ else
+ {
+ rc = rtFsIsoMakerCmdErrorRc(&Opts, rc, "RTFsIsoMakerCreate failed: %Rrc", rc);
+ Opts.hIsoMaker = NIL_RTFSISOMAKER;
+ }
+
+ return rtFsIsoMakerCmdDeleteState(&Opts, rc);
+}
+
+
+/**
+ * ISO maker command (creates image file on disk).
+ *
+ * @returns IPRT status code
+ * @param cArgs Number of arguments.
+ * @param papszArgs Pointer to argument array.
+ */
+RTDECL(RTEXITCODE) RTFsIsoMakerCmd(unsigned cArgs, char **papszArgs)
+{
+ int rc = RTFsIsoMakerCmdEx(cArgs, papszArgs, NIL_RTVFSDIR, NULL, NULL, NULL);
+ return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
+}
+
diff --git a/src/VBox/Runtime/common/fs/isomakerimport.cpp b/src/VBox/Runtime/common/fs/isomakerimport.cpp
new file mode 100644
index 00000000..41a72ec5
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isomakerimport.cpp
@@ -0,0 +1,2738 @@
+/* $Id: isomakerimport.cpp $ */
+/** @file
+ * IPRT - ISO Image Maker, Import Existing Image.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include "internal/iprt.h"
+#include <iprt/fsisomaker.h>
+
+#include <iprt/avl.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+#include <iprt/formats/iso9660.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Max directory depth. */
+#define RTFSISOMK_IMPORT_MAX_DEPTH 32
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Block to file translation node.
+ */
+typedef struct RTFSISOMKIMPBLOCK2FILE
+{
+ /** AVL tree node containing the first block number of the file.
+ * Block number is relative to the start of the import image. */
+ AVLU32NODECORE Core;
+ /** The configuration index of the file. */
+ uint32_t idxObj;
+ /** Namespaces the file has been seen in already (RTFSISOMAKER_NAMESPACE_XXX). */
+ uint32_t fNamespaces;
+ /** Pointer to the next file with the same block number. */
+ struct RTFSISOMKIMPBLOCK2FILE *pNext;
+} RTFSISOMKIMPBLOCK2FILE;
+/** Pointer to a block-2-file translation node. */
+typedef RTFSISOMKIMPBLOCK2FILE *PRTFSISOMKIMPBLOCK2FILE;
+
+
+/**
+ * Directory todo list entry.
+ */
+typedef struct RTFSISOMKIMPDIR
+{
+ /** List stuff. */
+ RTLISTNODE Entry;
+ /** The directory configuration index with hIsoMaker. */
+ uint32_t idxObj;
+ /** The directory data block number. */
+ uint32_t offDirBlock;
+ /** The directory size (in bytes). */
+ uint32_t cbDir;
+ /** The depth of this directory. */
+ uint8_t cDepth;
+} RTFSISOMKIMPDIR;
+/** Pointer to a directory todo list entry. */
+typedef RTFSISOMKIMPDIR *PRTFSISOMKIMPDIR;
+
+
+/**
+ * ISO maker ISO importer state.
+ */
+typedef struct RTFSISOMKIMPORTER
+{
+ /** The destination ISO maker. */
+ RTFSISOMAKER hIsoMaker;
+ /** RTFSISOMK_IMPORT_F_XXX. */
+ uint32_t fFlags;
+ /** The status code of the whole import.
+ * This notes down the first error status. */
+ int rc;
+ /** Pointer to error info return structure. */
+ PRTERRINFO pErrInfo;
+
+ /** The source file. */
+ RTVFSFILE hSrcFile;
+ /** The size of the source file. */
+ uint64_t cbSrcFile;
+ /** The number of 2KB blocks in the source file. */
+ uint64_t cBlocksInSrcFile;
+ /** The import source index of hSrcFile in hIsoMaker. UINT32_MAX till adding
+ * the first file. */
+ uint32_t idxSrcFile;
+
+ /** The root of the tree for converting data block numbers to files
+ * (PRTFSISOMKIMPBLOCK2FILE). This is essential when importing boot files and
+ * the 2nd namespace (joliet, udf, hfs) so that we avoid duplicating data. */
+ AVLU32TREE Block2FileRoot;
+
+ /** The block offset of the primary volume descriptor. */
+ uint32_t offPrimaryVolDesc;
+ /** The primary volume space size in blocks. */
+ uint32_t cBlocksInPrimaryVolumeSpace;
+ /** The primary volume space size in bytes. */
+ uint64_t cbPrimaryVolumeSpace;
+ /** The number of volumes in the set. */
+ uint32_t cVolumesInSet;
+ /** The primary volume sequence ID. */
+ uint32_t idPrimaryVol;
+
+ /** Set if we've already seen a joliet volume descriptor. */
+ bool fSeenJoliet;
+
+ /** The name of the TRANS.TBL in the import media (must ignore). */
+ const char *pszTransTbl;
+
+ /** Pointer to the import results structure (output). */
+ PRTFSISOMAKERIMPORTRESULTS pResults;
+
+ /** Sector buffer for volume descriptors and such. */
+ union
+ {
+ uint8_t ab[ISO9660_SECTOR_SIZE];
+ ISO9660VOLDESCHDR VolDescHdr;
+ ISO9660PRIMARYVOLDESC PrimVolDesc;
+ ISO9660SUPVOLDESC SupVolDesc;
+ ISO9660BOOTRECORDELTORITO ElToritoDesc;
+ } uSectorBuf;
+
+ /** Name buffer. */
+ char szNameBuf[_2K];
+
+ /** A somewhat larger buffer. */
+ uint8_t abBuf[_64K];
+
+ /** @name Rock Ridge stuff
+ * @{ */
+ /** Set if we've see the SP entry. */
+ bool fSuspSeenSP;
+ /** Set if we've seen the last 'NM' entry. */
+ bool fSeenLastNM;
+ /** Set if we've seen the last 'SL' entry. */
+ bool fSeenLastSL;
+ /** The SUSP skip into system area offset. */
+ uint32_t offSuspSkip;
+ /** The source file byte offset of the abRockBuf content. */
+ uint64_t offRockBuf;
+ /** Name buffer for rock ridge. */
+ char szRockNameBuf[_2K];
+ /** Symlink target name buffer for rock ridge. */
+ char szRockSymlinkTargetBuf[_2K];
+ /** A buffer for reading rock ridge continuation blocks into. */
+ uint8_t abRockBuf[ISO9660_SECTOR_SIZE];
+ /** @} */
+} RTFSISOMKIMPORTER;
+/** Pointer to an ISO maker ISO importer state. */
+typedef RTFSISOMKIMPORTER *PRTFSISOMKIMPORTER;
+
+
+/*
+ * The following is also found in iso9660vfs.cpp:
+ * The following is also found in iso9660vfs.cpp:
+ * The following is also found in iso9660vfs.cpp:
+ */
+
+/**
+ * Converts a ISO 9660 binary timestamp into an IPRT timesspec.
+ *
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param pIso9660 The ISO 9660 binary timestamp.
+ */
+static void rtFsIsoImpIso9660RecDateTime2TimeSpec(PRTTIMESPEC pTimeSpec, PCISO9660RECTIMESTAMP pIso9660)
+{
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = pIso9660->bYear + 1900;
+ Time.u8Month = RT_MIN(RT_MAX(pIso9660->bMonth, 1), 12);
+ Time.u8MonthDay = RT_MIN(RT_MAX(pIso9660->bDay, 1), 31);
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = RT_MIN(pIso9660->bHour, 23);
+ Time.u8Minute = RT_MIN(pIso9660->bMinute, 59);
+ Time.u8Second = RT_MIN(pIso9660->bSecond, 59);
+ Time.u32Nanosecond = 0;
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+
+ /* Only apply the UTC offset if it's within reasons. */
+ if (RT_ABS(pIso9660->offUtc) <= 13*4)
+ RTTimeSpecSubSeconds(pTimeSpec, pIso9660->offUtc * 15 * 60 * 60);
+}
+
+/**
+ * Converts a ISO 9660 char timestamp into an IPRT timesspec.
+ *
+ * @returns true if valid, false if not.
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param pIso9660 The ISO 9660 char timestamp.
+ */
+static bool rtFsIsoImpIso9660DateTime2TimeSpecIfValid(PRTTIMESPEC pTimeSpec, PCISO9660TIMESTAMP pIso9660)
+{
+ if ( RT_C_IS_DIGIT(pIso9660->achYear[0])
+ && RT_C_IS_DIGIT(pIso9660->achYear[1])
+ && RT_C_IS_DIGIT(pIso9660->achYear[2])
+ && RT_C_IS_DIGIT(pIso9660->achYear[3])
+ && RT_C_IS_DIGIT(pIso9660->achMonth[0])
+ && RT_C_IS_DIGIT(pIso9660->achMonth[1])
+ && RT_C_IS_DIGIT(pIso9660->achDay[0])
+ && RT_C_IS_DIGIT(pIso9660->achDay[1])
+ && RT_C_IS_DIGIT(pIso9660->achHour[0])
+ && RT_C_IS_DIGIT(pIso9660->achHour[1])
+ && RT_C_IS_DIGIT(pIso9660->achMinute[0])
+ && RT_C_IS_DIGIT(pIso9660->achMinute[1])
+ && RT_C_IS_DIGIT(pIso9660->achSecond[0])
+ && RT_C_IS_DIGIT(pIso9660->achSecond[1])
+ && RT_C_IS_DIGIT(pIso9660->achCentisecond[0])
+ && RT_C_IS_DIGIT(pIso9660->achCentisecond[1]))
+ {
+
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = (pIso9660->achYear[0] - '0') * 1000
+ + (pIso9660->achYear[1] - '0') * 100
+ + (pIso9660->achYear[2] - '0') * 10
+ + (pIso9660->achYear[3] - '0');
+ Time.u8Month = (pIso9660->achMonth[0] - '0') * 10
+ + (pIso9660->achMonth[1] - '0');
+ Time.u8MonthDay = (pIso9660->achDay[0] - '0') * 10
+ + (pIso9660->achDay[1] - '0');
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = (pIso9660->achHour[0] - '0') * 10
+ + (pIso9660->achHour[1] - '0');
+ Time.u8Minute = (pIso9660->achMinute[0] - '0') * 10
+ + (pIso9660->achMinute[1] - '0');
+ Time.u8Second = (pIso9660->achSecond[0] - '0') * 10
+ + (pIso9660->achSecond[1] - '0');
+ Time.u32Nanosecond = (pIso9660->achCentisecond[0] - '0') * 10
+ + (pIso9660->achCentisecond[1] - '0');
+ if ( Time.u8Month > 1 && Time.u8Month <= 12
+ && Time.u8MonthDay > 1 && Time.u8MonthDay <= 31
+ && Time.u8Hour < 60
+ && Time.u8Minute < 60
+ && Time.u8Second < 60
+ && Time.u32Nanosecond < 100)
+ {
+ if (Time.i32Year <= 1677)
+ Time.i32Year = 1677;
+ else if (Time.i32Year <= 2261)
+ Time.i32Year = 2261;
+
+ Time.u32Nanosecond *= RT_NS_10MS;
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+
+ /* Only apply the UTC offset if it's within reasons. */
+ if (RT_ABS(pIso9660->offUtc) <= 13*4)
+ RTTimeSpecSubSeconds(pTimeSpec, pIso9660->offUtc * 15 * 60 * 60);
+ return true;
+ }
+ }
+ return false;
+}
+
+/* end of duplicated static functions. */
+
+
+/**
+ * Wrapper around RTErrInfoSetV.
+ *
+ * @returns rc
+ * @param pThis The importer instance.
+ * @param rc The status code to set.
+ * @param pszFormat The format string detailing the error.
+ * @param va Argument to the format string.
+ */
+static int rtFsIsoImpErrorV(PRTFSISOMKIMPORTER pThis, int rc, const char *pszFormat, va_list va)
+{
+ va_list vaCopy;
+ va_copy(vaCopy, va);
+ LogRel(("RTFsIsoMkImport error %Rrc: %N\n", rc, pszFormat, &vaCopy));
+ va_end(vaCopy);
+
+ if (RT_SUCCESS(pThis->rc))
+ {
+ pThis->rc = rc;
+ rc = RTErrInfoSetV(pThis->pErrInfo, rc, pszFormat, va);
+ }
+
+ pThis->pResults->cErrors++;
+ return rc;
+}
+
+
+/**
+ * Wrapper around RTErrInfoSetF.
+ *
+ * @returns rc
+ * @param pThis The importer instance.
+ * @param rc The status code to set.
+ * @param pszFormat The format string detailing the error.
+ * @param ... Argument to the format string.
+ */
+static int rtFsIsoImpError(PRTFSISOMKIMPORTER pThis, int rc, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ rc = rtFsIsoImpErrorV(pThis, rc, pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Callback for destroying a RTFSISOMKIMPBLOCK2FILE node.
+ *
+ * @returns VINF_SUCCESS
+ * @param pNode The node to destroy.
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int) rtFsIsoMakerImportDestroyData2File(PAVLU32NODECORE pNode, void *pvUser)
+{
+ PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)pNode;
+ if (pBlock2File)
+ {
+ PRTFSISOMKIMPBLOCK2FILE pNext;
+ while ((pNext = pBlock2File->pNext) != NULL)
+ {
+ pBlock2File->pNext = pNext->pNext;
+ pNext->pNext = NULL;
+ RTMemFree(pNext);
+ }
+ RTMemFree(pNode);
+ }
+
+ RT_NOREF(pvUser);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a symbolic link and names it given its ISO-9660 directory record and
+ * parent.
+ *
+ * @returns IPRT status code (safe to ignore).
+ * @param pThis The importer instance.
+ * @param pDirRec The directory record.
+ * @param pObjInfo Object information.
+ * @param fNamespace The namespace flag.
+ * @param idxParent Parent directory.
+ * @param pszName The name.
+ * @param pszRockName The rock ridge name. Empty if not present.
+ * @param pszTarget The symbolic link target.
+ */
+static int rtFsIsoImportProcessIso9660AddAndNameSymlink(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, PRTFSOBJINFO pObjInfo,
+ uint32_t fNamespace, uint32_t idxParent,
+ const char *pszName, const char *pszRockName, const char *pszTarget)
+{
+ NOREF(pDirRec);
+ Assert(!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY));
+ Assert(RTFS_IS_SYMLINK(pObjInfo->Attr.fMode));
+
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedSymlink(pThis->hIsoMaker, pObjInfo, pszTarget, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ Log3((" --> added symlink #%#x (-> %s)\n", idxObj, pszTarget));
+ pThis->pResults->cAddedSymlinks++;
+
+ /*
+ * Enter the object into the namespace.
+ */
+ rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName, true /*fNoNormalize*/);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pResults->cAddedNames++;
+
+ if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0)
+ {
+ rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for symlink '%s' to '%s'", pszName, pszRockName);
+ }
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error naming symlink '%s' (-> %s): %Rrc", pszName, pszTarget, rc);
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error adding symbolic link '%s' (-> %s): %Rrc", pszName, pszTarget, rc);
+ return rc;
+}
+
+
+
+/**
+ * Adds a directory and names it given its ISO-9660 directory record and parent.
+ *
+ * @returns IPRT status code (safe to ignore).
+ * @param pThis The importer instance.
+ * @param pDirRec The directory record.
+ * @param pObjInfo Object information.
+ * @param cbData The actual directory data size. (Always same as in the
+ * directory record, but this what we do for files below.)
+ * @param fNamespace The namespace flag.
+ * @param idxParent Parent directory.
+ * @param pszName The name.
+ * @param pszRockName The rock ridge name. Empty if not present.
+ * @param cDepth The depth to add it with.
+ * @param pTodoList The todo list (for directories).
+ */
+static int rtFsIsoImportProcessIso9660AddAndNameDirectory(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec,
+ PCRTFSOBJINFO pObjInfo, uint64_t cbData,
+ uint32_t fNamespace, uint32_t idxParent, const char *pszName,
+ const char *pszRockName, uint8_t cDepth, PRTLISTANCHOR pTodoList)
+{
+ Assert(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY);
+ uint32_t idxObj;
+ int rc = RTFsIsoMakerAddUnnamedDir(pThis->hIsoMaker, pObjInfo, &idxObj);
+ if (RT_SUCCESS(rc))
+ {
+ Log3((" --> added directory #%#x\n", idxObj));
+ pThis->pResults->cAddedDirs++;
+
+ /*
+ * Enter the object into the namespace.
+ */
+ rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName, true /*fNoNormalize*/);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pResults->cAddedNames++;
+
+ if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0)
+ rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Push it onto the traversal stack.
+ */
+ PRTFSISOMKIMPDIR pImpDir = (PRTFSISOMKIMPDIR)RTMemAlloc(sizeof(*pImpDir));
+ if (pImpDir)
+ {
+ Assert((uint32_t)cbData == cbData /* no multi-extents for dirs makes it this far */);
+ pImpDir->cbDir = (uint32_t)cbData;
+ pImpDir->offDirBlock = ISO9660_GET_ENDIAN(&pDirRec->offExtent);
+ pImpDir->idxObj = idxObj;
+ pImpDir->cDepth = cDepth;
+ RTListAppend(pTodoList, &pImpDir->Entry);
+ }
+ else
+ rc = rtFsIsoImpError(pThis, VERR_NO_MEMORY, "Could not allocate RTFSISOMKIMPDIR");
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for directory '%s' to '%s'", pszName, pszRockName);
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error naming directory '%s': %Rrc", pszName, rc);
+ }
+ else
+ rc = rtFsIsoImpError(pThis, rc, "Error adding directory '%s': %Rrc", pszName, rc);
+ return rc;
+}
+
+
+/**
+ * Adds a file and names it given its ISO-9660 directory record and parent.
+ *
+ * @returns IPRT status code (safe to ignore).
+ * @param pThis The importer instance.
+ * @param pDirRec The directory record.
+ * @param pObjInfo Object information.
+ * @param cbData The actual file data size.
+ * @param fNamespace The namespace flag.
+ * @param idxParent Parent directory.
+ * @param pszName The name.
+ * @param pszRockName The rock ridge name. Empty if not present.
+ */
+static int rtFsIsoImportProcessIso9660AddAndNameFile(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, PRTFSOBJINFO pObjInfo,
+ uint64_t cbData, uint32_t fNamespace, uint32_t idxParent,
+ const char *pszName, const char *pszRockName)
+{
+ int rc;
+
+ /*
+ * First we must make sure the common source file has been added.
+ */
+ if (pThis->idxSrcFile != UINT32_MAX)
+ { /* likely */ }
+ else
+ {
+ rc = RTFsIsoMakerAddCommonSourceFile(pThis->hIsoMaker, pThis->hSrcFile, &pThis->idxSrcFile);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddCommonSourceFile failed: %Rrc", rc);
+ Assert(pThis->idxSrcFile != UINT32_MAX);
+ }
+
+ /*
+ * Lookup the data block if the file has a non-zero length. The aim is to
+ * find files across namespaces while bearing in mind that files in the same
+ * namespace may share data storage, i.e. what in a traditional unix file
+ * system would be called hardlinked. Problem is that the core engine doesn't
+ * do hardlinking yet and assume each file has exactly one name per namespace.
+ */
+ uint32_t idxObj = UINT32_MAX;
+ PRTFSISOMKIMPBLOCK2FILE pBlock2File = NULL;
+ PRTFSISOMKIMPBLOCK2FILE pBlock2FilePrev = NULL;
+ if (cbData > 0) /* no data tracking for zero byte files */
+ {
+ pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, ISO9660_GET_ENDIAN(&pDirRec->offExtent));
+ if (pBlock2File)
+ {
+ if (!(pBlock2File->fNamespaces & fNamespace))
+ {
+ pBlock2File->fNamespaces |= fNamespace;
+ idxObj = pBlock2File->idxObj;
+ }
+ else
+ {
+ do
+ {
+ pBlock2FilePrev = pBlock2File;
+ pBlock2File = pBlock2File->pNext;
+ } while (pBlock2File && (pBlock2File->fNamespaces & fNamespace));
+ if (pBlock2File)
+ {
+ pBlock2File->fNamespaces |= fNamespace;
+ idxObj = pBlock2File->idxObj;
+ }
+ }
+ }
+ }
+
+ /*
+ * If the above lookup didn't succeed, add a new file with a lookup record.
+ */
+ if (idxObj == UINT32_MAX)
+ {
+ pObjInfo->cbObject = pObjInfo->cbAllocated = cbData;
+ rc = RTFsIsoMakerAddUnnamedFileWithCommonSrc(pThis->hIsoMaker, pThis->idxSrcFile,
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent) * (uint64_t)ISO9660_SECTOR_SIZE,
+ cbData, pObjInfo, &idxObj);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "Error adding file '%s': %Rrc", pszName, rc);
+ Assert(idxObj != UINT32_MAX);
+
+ /* Update statistics. */
+ pThis->pResults->cAddedFiles++;
+ if (cbData > 0)
+ {
+ pThis->pResults->cbAddedDataBlocks += RT_ALIGN_64(cbData, ISO9660_SECTOR_SIZE);
+
+ /* Lookup record. */
+ pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTMemAlloc(sizeof(*pBlock2File));
+ AssertReturn(pBlock2File, rtFsIsoImpError(pThis, VERR_NO_MEMORY, "Could not allocate RTFSISOMKIMPBLOCK2FILE"));
+
+ pBlock2File->idxObj = idxObj;
+ pBlock2File->Core.Key = ISO9660_GET_ENDIAN(&pDirRec->offExtent);
+ pBlock2File->fNamespaces = fNamespace;
+ pBlock2File->pNext = NULL;
+ if (!pBlock2FilePrev)
+ {
+ bool fRc = RTAvlU32Insert(&pThis->Block2FileRoot, &pBlock2File->Core);
+ Assert(fRc); RT_NOREF(fRc);
+ }
+ else
+ {
+ pBlock2File->Core.pLeft = NULL;
+ pBlock2File->Core.pRight = NULL;
+ pBlock2FilePrev->pNext = pBlock2File;
+ }
+ }
+ }
+
+ /*
+ * Enter the object into the namespace.
+ */
+ rc = RTFsIsoMakerObjSetNameAndParent(pThis->hIsoMaker, idxObj, idxParent, fNamespace, pszName, true /*fNoNormalize*/);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pResults->cAddedNames++;
+
+ if (*pszRockName != '\0' && strcmp(pszName, pszRockName) != 0)
+ {
+ rc = RTFsIsoMakerObjSetRockName(pThis->hIsoMaker, idxObj, fNamespace, pszRockName);
+ if (RT_FAILURE(rc))
+ rc = rtFsIsoImpError(pThis, rc, "Error setting rock ridge name for file '%s' to '%s'", pszName, pszRockName);
+ }
+ }
+ else
+ return rtFsIsoImpError(pThis, rc, "Error naming file '%s': %Rrc", pszName, rc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses rock ridge information if present in the directory entry.
+ *
+ * @param pThis The importer instance.
+ * @param pObjInfo The object information to improve upon.
+ * @param pbSys The system area of the directory record.
+ * @param cbSys The number of bytes present in the sys area.
+ * @param fUnicode Indicates which namespace we're working on.
+ * @param fIsFirstDirRec Set if this is the '.' directory entry in the
+ * root directory. (Some entries applies only to
+ * it.)
+ * @param fContinuationRecord Set if we're processing a continuation record in
+ * living in the abRockBuf.
+ */
+static void rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(PRTFSISOMKIMPORTER pThis, PRTFSOBJINFO pObjInfo,
+ uint8_t const *pbSys, size_t cbSys, bool fUnicode,
+ bool fIsFirstDirRec, bool fContinuationRecord)
+{
+ RT_NOREF(pObjInfo);
+
+ while (cbSys >= 4)
+ {
+ /*
+ * Check header length and advance the sys variables.
+ */
+ PCISO9660SUSPUNION pUnion = (PCISO9660SUSPUNION)pbSys;
+ if ( pUnion->Hdr.cbEntry > cbSys
+ && pUnion->Hdr.cbEntry < sizeof(pUnion->Hdr))
+ {
+ LogRel(("rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge: cbEntry=%#x cbSys=%#x (%#x %#x)\n",
+ pUnion->Hdr.cbEntry, cbSys, pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ return;
+ }
+ pbSys += pUnion->Hdr.cbEntry;
+ cbSys -= pUnion->Hdr.cbEntry;
+
+ /*
+ * Process fields.
+ */
+#define MAKE_SIG(a_bSig1, a_bSig2) \
+ ( ((uint16_t)(a_bSig1) & 0x1f) \
+ | (((uint16_t)(a_bSig2) ^ 0x40) << 5) \
+ | ((((uint16_t)(a_bSig1) ^ 0x40) & 0xe0) << (5 + 8)) )
+
+ uint16_t const uSig = MAKE_SIG(pUnion->Hdr.bSig1, pUnion->Hdr.bSig2);
+ switch (uSig)
+ {
+ /*
+ * System use sharing protocol entries.
+ */
+ case MAKE_SIG(ISO9660SUSPCE_SIG1, ISO9660SUSPCE_SIG2):
+ {
+ if (RT_BE2H_U32(pUnion->CE.offBlock.be) != RT_LE2H_U32(pUnion->CE.offBlock.le))
+ LogRel(("rtFsIsoImport/Rock: Invalid CE offBlock field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.offBlock.be), RT_LE2H_U32(pUnion->CE.offBlock.le)));
+ else if (RT_BE2H_U32(pUnion->CE.cbData.be) != RT_LE2H_U32(pUnion->CE.cbData.le))
+ LogRel(("rtFsIsoImport/Rock: Invalid CE cbData field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.cbData.be), RT_LE2H_U32(pUnion->CE.cbData.le)));
+ else if (RT_BE2H_U32(pUnion->CE.offData.be) != RT_LE2H_U32(pUnion->CE.offData.le))
+ LogRel(("rtFsIsoImport/Rock: Invalid CE offData field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.offData.be), RT_LE2H_U32(pUnion->CE.offData.le)));
+ else if (!fContinuationRecord)
+ {
+ uint64_t offData = ISO9660_GET_ENDIAN(&pUnion->CE.offBlock) * (uint64_t)ISO9660_SECTOR_SIZE;
+ offData += ISO9660_GET_ENDIAN(&pUnion->CE.offData);
+ uint32_t cbData = ISO9660_GET_ENDIAN(&pUnion->CE.cbData);
+ if (cbData <= sizeof(pThis->abRockBuf) - (uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK))
+ {
+ AssertCompile(sizeof(pThis->abRockBuf) == ISO9660_SECTOR_SIZE);
+ uint64_t offDataBlock = offData & ~(uint64_t)ISO9660_SECTOR_OFFSET_MASK;
+ if (pThis->offRockBuf == offDataBlock)
+ rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, pObjInfo,
+ &pThis->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, fUnicode, fIsFirstDirRec,
+ true /*fContinuationRecord*/);
+ else
+ {
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, offDataBlock, pThis->abRockBuf, sizeof(pThis->abRockBuf), NULL);
+ if (RT_SUCCESS(rc))
+ rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, pObjInfo,
+ &pThis->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, fUnicode, fIsFirstDirRec,
+ true /*fContinuationRecord*/);
+ else
+ LogRel(("rtFsIsoImport/Rock: Error reading continuation record at %#RX64: %Rrc\n",
+ offDataBlock, rc));
+ }
+ }
+ else
+ LogRel(("rtFsIsoImport/Rock: continuation record isn't within a sector! offData=%#RX64 cbData=%#RX32\n",
+ cbData, offData));
+ }
+ else
+ LogRel(("rtFsIsoImport/Rock: nested continuation record!\n"));
+ break;
+ }
+
+ case MAKE_SIG(ISO9660SUSPSP_SIG1, ISO9660SUSPSP_SIG2): /* SP */
+ if ( pUnion->Hdr.cbEntry != ISO9660SUSPSP_LEN
+ || pUnion->Hdr.bVersion != ISO9660SUSPSP_VER
+ || pUnion->SP.bCheck1 != ISO9660SUSPSP_CHECK1
+ || pUnion->SP.bCheck2 != ISO9660SUSPSP_CHECK2
+ || pUnion->SP.cbSkip > UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]))
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SP' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x), bCheck1=%#x (vs %#x), bCheck2=%#x (vs %#x), cbSkip=%#x (vs max %#x)\n",
+ pUnion->Hdr.cbEntry, ISO9660SUSPSP_LEN, pUnion->Hdr.bVersion, ISO9660SUSPSP_VER,
+ pUnion->SP.bCheck1, ISO9660SUSPSP_CHECK1, pUnion->SP.bCheck2, ISO9660SUSPSP_CHECK2,
+ pUnion->SP.cbSkip, UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]) ));
+ else if (!fIsFirstDirRec)
+ LogRel(("rtFsIsoImport/Rock: Ignorining 'SP' entry in non-root directory record\n"));
+ else if (pThis->fSuspSeenSP)
+ LogRel(("rtFsIsoImport/Rock: Ignorining additional 'SP' entry\n"));
+ else
+ {
+ pThis->offSuspSkip = pUnion->SP.cbSkip;
+ if (pUnion->SP.cbSkip != 0)
+ LogRel(("rtFsIsoImport/Rock: SP: cbSkip=%#x\n", pUnion->SP.cbSkip));
+ }
+ break;
+
+ case MAKE_SIG(ISO9660SUSPER_SIG1, ISO9660SUSPER_SIG2): /* ER */
+ if ( pUnion->Hdr.cbEntry > RT_UOFFSETOF(ISO9660SUSPER, achPayload) + (uint32_t)pUnion->ER.cchIdentifier
+ + (uint32_t)pUnion->ER.cchDescription + (uint32_t)pUnion->ER.cchSource
+ || pUnion->Hdr.bVersion != ISO9660SUSPER_VER)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'ER' entry: cbEntry=%#x bVersion=%#x (vs %#x) cchIdentifier=%#x cchDescription=%#x cchSource=%#x\n",
+ pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion, ISO9660SUSPER_VER, pUnion->ER.cchIdentifier,
+ pUnion->ER.cchDescription, pUnion->ER.cchSource));
+ else if (!fIsFirstDirRec)
+ LogRel(("rtFsIsoImport/Rock: Ignorining 'ER' entry in non-root directory record\n"));
+ else if ( pUnion->ER.bVersion == 1 /* RRIP detection */
+ && ( (pUnion->ER.cchIdentifier >= 4 && strncmp(pUnion->ER.achPayload, ISO9660_RRIP_ID, 4 /*RRIP*/) == 0)
+ || (pUnion->ER.cchIdentifier >= 10 && strncmp(pUnion->ER.achPayload, RT_STR_TUPLE(ISO9660_RRIP_1_12_ID)) == 0) ))
+ {
+ LogRel(("rtFsIsoImport/Rock: Rock Ridge 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n",
+ pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload,
+ pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier],
+ pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription]));
+ if (!fUnicode)
+ {
+ int rc = RTFsIsoMakerSetRockRidgeLevel(pThis->hIsoMaker, 2);
+ if (RT_FAILURE(rc))
+ LogRel(("rtFsIsoImport/Rock: RTFsIsoMakerSetRockRidgeLevel(,2) failed: %Rrc\n", rc));
+ }
+ else
+ {
+ int rc = RTFsIsoMakerSetJolietRockRidgeLevel(pThis->hIsoMaker, 2);
+ if (RT_FAILURE(rc))
+ LogRel(("rtFsIsoImport/Rock: RTFsIsoMakerSetJolietRockRidgeLevel(,2) failed: %Rrc\n", rc));
+ }
+ }
+ else
+ LogRel(("rtFsIsoImport/Rock: Unknown extension in 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n",
+ pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload,
+ pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier],
+ pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription]));
+ break;
+
+ case MAKE_SIG(ISO9660SUSPPD_SIG1, ISO9660SUSPPD_SIG2): /* PD - ignored */
+ case MAKE_SIG(ISO9660SUSPST_SIG1, ISO9660SUSPST_SIG2): /* ST - ignore for now */
+ case MAKE_SIG(ISO9660SUSPES_SIG1, ISO9660SUSPES_SIG2): /* ES - ignore for now */
+ break;
+
+ /*
+ * Rock ridge interchange protocol entries.
+ */
+ case MAKE_SIG(ISO9660RRIPRR_SIG1, ISO9660RRIPRR_SIG2): /* RR */
+ if ( pUnion->RR.Hdr.cbEntry != ISO9660RRIPRR_LEN
+ || pUnion->RR.Hdr.bVersion != ISO9660RRIPRR_VER)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'RR' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n",
+ pUnion->RR.Hdr.cbEntry, ISO9660RRIPRR_LEN, pUnion->RR.Hdr.bVersion, ISO9660RRIPRR_VER, pUnion->RR.fFlags));
+ /* else: ignore it */
+ break;
+
+ case MAKE_SIG(ISO9660RRIPPX_SIG1, ISO9660RRIPPX_SIG2): /* PX */
+ if ( ( pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN
+ && pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN_NO_INODE)
+ || pUnion->PX.Hdr.bVersion != ISO9660RRIPPX_VER
+ || RT_BE2H_U32(pUnion->PX.fMode.be) != RT_LE2H_U32(pUnion->PX.fMode.le)
+ || RT_BE2H_U32(pUnion->PX.cHardlinks.be) != RT_LE2H_U32(pUnion->PX.cHardlinks.le)
+ || RT_BE2H_U32(pUnion->PX.uid.be) != RT_LE2H_U32(pUnion->PX.uid.le)
+ || RT_BE2H_U32(pUnion->PX.gid.be) != RT_LE2H_U32(pUnion->PX.gid.le)
+ || ( pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN
+ && RT_BE2H_U32(pUnion->PX.INode.be) != RT_LE2H_U32(pUnion->PX.INode.le)) )
+ LogRel(("rtFsIsoImport/Rock: Malformed 'PX' entry: cbEntry=%#x (vs %#x or %#x), bVersion=%#x (vs %#x) fMode=%#x/%#x cHardlinks=%#x/%#x uid=%#x/%#x gid=%#x/%#x inode=%#x/%#x\n",
+ pUnion->PX.Hdr.cbEntry, ISO9660RRIPPX_LEN, ISO9660RRIPPX_LEN_NO_INODE,
+ pUnion->PX.Hdr.bVersion, ISO9660RRIPPX_VER,
+ RT_BE2H_U32(pUnion->PX.fMode.be), RT_LE2H_U32(pUnion->PX.fMode.le),
+ RT_BE2H_U32(pUnion->PX.cHardlinks.be), RT_LE2H_U32(pUnion->PX.cHardlinks.le),
+ RT_BE2H_U32(pUnion->PX.uid.be), RT_LE2H_U32(pUnion->PX.uid.le),
+ RT_BE2H_U32(pUnion->PX.gid.be), RT_LE2H_U32(pUnion->PX.gid.le),
+ pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_BE2H_U32(pUnion->PX.INode.be) : 0,
+ pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_LE2H_U32(pUnion->PX.INode.le) : 0 ));
+ else
+ {
+ if (RTFS_IS_DIRECTORY(ISO9660_GET_ENDIAN(&pUnion->PX.fMode)) == RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ pObjInfo->Attr.fMode = ISO9660_GET_ENDIAN(&pUnion->PX.fMode);
+ else
+ LogRel(("rtFsIsoImport/Rock: 'PX' entry changes directory-ness: fMode=%#x, existing %#x; ignored\n",
+ ISO9660_GET_ENDIAN(&pUnion->PX.fMode), pObjInfo->Attr.fMode));
+ pObjInfo->Attr.u.Unix.cHardlinks = ISO9660_GET_ENDIAN(&pUnion->PX.cHardlinks);
+ pObjInfo->Attr.u.Unix.uid = ISO9660_GET_ENDIAN(&pUnion->PX.uid);
+ pObjInfo->Attr.u.Unix.gid = ISO9660_GET_ENDIAN(&pUnion->PX.gid);
+ /* ignore inode */
+ }
+ break;
+
+ case MAKE_SIG(ISO9660RRIPPN_SIG1, ISO9660RRIPPN_SIG2): /* PN */
+ if ( pUnion->PN.Hdr.cbEntry != ISO9660RRIPPN_LEN
+ || pUnion->PN.Hdr.bVersion != ISO9660RRIPPN_VER
+ || RT_BE2H_U32(pUnion->PN.Major.be) != RT_LE2H_U32(pUnion->PN.Major.le)
+ || RT_BE2H_U32(pUnion->PN.Minor.be) != RT_LE2H_U32(pUnion->PN.Minor.le))
+ LogRel(("rtFsIsoImport/Rock: Malformed 'PN' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) Major=%#x/%#x Minor=%#x/%#x\n",
+ pUnion->PN.Hdr.cbEntry, ISO9660RRIPPN_LEN, pUnion->PN.Hdr.bVersion, ISO9660RRIPPN_VER,
+ RT_BE2H_U32(pUnion->PN.Major.be), RT_LE2H_U32(pUnion->PN.Major.le),
+ RT_BE2H_U32(pUnion->PN.Minor.be), RT_LE2H_U32(pUnion->PN.Minor.le) ));
+ else if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ LogRel(("rtFsIsoImport/Rock: Ignoring 'PN' entry for directory (%#x/%#x)\n",
+ ISO9660_GET_ENDIAN(&pUnion->PN.Major), ISO9660_GET_ENDIAN(&pUnion->PN.Minor) ));
+ else
+ pObjInfo->Attr.u.Unix.Device = RTDEV_MAKE(ISO9660_GET_ENDIAN(&pUnion->PN.Major),
+ ISO9660_GET_ENDIAN(&pUnion->PN.Minor));
+ break;
+
+ case MAKE_SIG(ISO9660RRIPTF_SIG1, ISO9660RRIPTF_SIG2): /* TF */
+ if ( pUnion->TF.Hdr.bVersion != ISO9660RRIPTF_VER
+ || pUnion->TF.Hdr.cbEntry < Iso9660RripTfCalcLength(pUnion->TF.fFlags))
+ LogRel(("rtFsIsoImport/Rock: Malformed 'TF' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n",
+ pUnion->TF.Hdr.cbEntry, Iso9660RripTfCalcLength(pUnion->TF.fFlags),
+ pUnion->TF.Hdr.bVersion, ISO9660RRIPTF_VER, RT_BE2H_U32(pUnion->TF.fFlags) ));
+ else if (!(pUnion->TF.fFlags & ISO9660RRIPTF_F_LONG_FORM))
+ {
+ PCISO9660RECTIMESTAMP pTimestamp = (PCISO9660RECTIMESTAMP)&pUnion->TF.abPayload[0];
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH)
+ {
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->BirthTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY)
+ {
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->ModificationTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS)
+ {
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->AccessTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE)
+ {
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&pObjInfo->ChangeTime, pTimestamp);
+ pTimestamp++;
+ }
+ }
+ else
+ {
+ PCISO9660TIMESTAMP pTimestamp = (PCISO9660TIMESTAMP)&pUnion->TF.abPayload[0];
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH)
+ {
+ rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->BirthTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY)
+ {
+ rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->ModificationTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS)
+ {
+ rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->AccessTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE)
+ {
+ rtFsIsoImpIso9660DateTime2TimeSpecIfValid(&pObjInfo->ChangeTime, pTimestamp);
+ pTimestamp++;
+ }
+ }
+ break;
+
+ case MAKE_SIG(ISO9660RRIPSF_SIG1, ISO9660RRIPSF_SIG2): /* SF */
+ LogRel(("rtFsIsoImport/Rock: Sparse file support not yet implemented!\n"));
+ break;
+
+ case MAKE_SIG(ISO9660RRIPSL_SIG1, ISO9660RRIPSL_SIG2): /* SL */
+ if ( pUnion->SL.Hdr.bVersion != ISO9660RRIPSL_VER
+ || pUnion->SL.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2])
+ || (pUnion->SL.fFlags & ~ISO9660RRIP_SL_F_CONTINUE)
+ || (pUnion->SL.abComponents[0] & ISO9660RRIP_SL_C_RESERVED_MASK) )
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x comp[0].fFlags=%#x\n",
+ pUnion->SL.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2]),
+ pUnion->SL.Hdr.bVersion, ISO9660RRIPSL_VER, pUnion->SL.fFlags, pUnion->SL.abComponents[0]));
+ else if (pThis->fSeenLastSL)
+ LogRel(("rtFsIsoImport/Rock: Unexpected 'SL!' entry\n"));
+ else
+ {
+ pThis->fSeenLastSL = !(pUnion->SL.fFlags & ISO9660RRIP_SL_F_CONTINUE); /* used in loop */
+
+ size_t offDst = strlen(pThis->szRockSymlinkTargetBuf);
+ uint8_t const *pbSrc = &pUnion->SL.abComponents[0];
+ uint8_t cbSrcLeft = pUnion->SL.Hdr.cbEntry - RT_UOFFSETOF(ISO9660RRIPSL, abComponents);
+ while (cbSrcLeft >= 2)
+ {
+ uint8_t const fFlags = pbSrc[0];
+ uint8_t cchCopy = pbSrc[1];
+ uint8_t const cbSkip = cchCopy + 2;
+ if (cbSkip > cbSrcLeft)
+ {
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: component flags=%#x, component length+2=%#x vs %#x left\n",
+ fFlags, cbSkip, cbSrcLeft));
+ break;
+ }
+
+ const char *pszCopy;
+ switch (fFlags & ~ISO9660RRIP_SL_C_CONTINUE)
+ {
+ case 0:
+ pszCopy = (const char *)&pbSrc[2];
+ break;
+
+ case ISO9660RRIP_SL_C_CURRENT:
+ if (cchCopy != 0)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: CURRENT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = ".";
+ cchCopy = 1;
+ break;
+
+ case ISO9660RRIP_SL_C_PARENT:
+ if (cchCopy != 0)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: PARENT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = "..";
+ cchCopy = 2;
+ break;
+
+ case ISO9660RRIP_SL_C_ROOT:
+ if (cchCopy != 0)
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: ROOT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = "/";
+ cchCopy = 1;
+ break;
+
+ default:
+ LogRel(("rtFsIsoImport/Rock: Malformed 'SL' component: component flags=%#x (bad), component length=%#x vs %#x left\n",
+ fFlags, cchCopy, cbSrcLeft));
+ pszCopy = NULL;
+ cchCopy = 0;
+ break;
+ }
+
+ if (offDst + cchCopy < sizeof(pThis->szRockSymlinkTargetBuf))
+ {
+ memcpy(&pThis->szRockSymlinkTargetBuf[offDst], pszCopy, cchCopy);
+ offDst += cchCopy;
+ }
+ else
+ {
+ LogRel(("rtFsIsoImport/Rock: 'SL' constructs a too long target! '%.*s%.*s'\n",
+ offDst, pThis->szRockSymlinkTargetBuf, cchCopy, pszCopy));
+ memcpy(&pThis->szRockSymlinkTargetBuf[offDst], pszCopy,
+ sizeof(pThis->szRockSymlinkTargetBuf) - offDst - 1);
+ offDst = sizeof(pThis->szRockSymlinkTargetBuf) - 1;
+ break;
+ }
+
+ /* Advance */
+ pbSrc += cbSkip;
+ cbSrcLeft -= cbSkip;
+
+ /* Append slash if appropriate. */
+ if ( !(fFlags & ISO9660RRIP_SL_C_CONTINUE)
+ && (cbSrcLeft >= 2 || !pThis->fSeenLastSL) )
+ {
+ if (offDst + 1 < sizeof(pThis->szRockSymlinkTargetBuf))
+ pThis->szRockSymlinkTargetBuf[offDst++] = '/';
+ else
+ {
+ LogRel(("rtFsIsoImport/Rock: 'SL' constructs a too long target! '%.*s/'\n",
+ offDst, pThis->szRockSymlinkTargetBuf));
+ break;
+ }
+ }
+ }
+ pThis->szRockSymlinkTargetBuf[offDst] = '\0';
+
+ /* Purge the encoding as we don't want invalid UTF-8 floating around. */
+ /** @todo do this afterwards as needed. */
+ RTStrPurgeEncoding(pThis->szRockSymlinkTargetBuf);
+ }
+ break;
+
+ case MAKE_SIG(ISO9660RRIPNM_SIG1, ISO9660RRIPNM_SIG2): /* NM */
+ if ( pUnion->NM.Hdr.bVersion != ISO9660RRIPNM_VER
+ || pUnion->NM.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPNM, achName)
+ || (pUnion->NM.fFlags & ISO9660RRIP_NM_F_RESERVED_MASK) )
+ LogRel(("rtFsIsoImport/Rock: Malformed 'NM' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x %.*Rhxs\n",
+ pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName),
+ pUnion->NM.Hdr.bVersion, ISO9660RRIPNM_VER, pUnion->NM.fFlags,
+ pUnion->NM.Hdr.cbEntry - RT_MIN(pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName)),
+ &pUnion->NM.achName[0] ));
+ else if (pThis->fSeenLastNM)
+ LogRel(("rtFsIsoImport/Rock: Unexpected 'NM' entry!\n"));
+ else
+ {
+ pThis->fSeenLastNM = !(pUnion->NM.fFlags & ISO9660RRIP_NM_F_CONTINUE);
+
+ uint8_t const cchName = pUnion->NM.Hdr.cbEntry - (uint8_t)RT_UOFFSETOF(ISO9660RRIPNM, achName);
+ if (pUnion->NM.fFlags & (ISO9660RRIP_NM_F_CURRENT | ISO9660RRIP_NM_F_PARENT))
+ {
+ if (cchName == 0)
+ Log(("rtFsIsoImport/Rock: Ignoring 'NM' entry for '.' and '..'\n"));
+ else
+ LogRel(("rtFsIsoImport/Rock: Ignoring malformed 'NM' using '.' or '..': fFlags=%#x cchName=%#x %.*Rhxs; szRockNameBuf='%s'\n",
+ pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName, pThis->szRockNameBuf));
+ pThis->szRockNameBuf[0] = '\0';
+ pThis->fSeenLastNM = true;
+ }
+ else
+ {
+ size_t offDst = strlen(pThis->szRockNameBuf);
+ if (offDst + cchName < sizeof(pThis->szRockNameBuf))
+ {
+ memcpy(&pThis->szRockNameBuf[offDst], pUnion->NM.achName, cchName);
+ pThis->szRockNameBuf[offDst + cchName] = '\0';
+
+ /* Purge the encoding as we don't want invalid UTF-8 floating around. */
+ /** @todo do this afterwards as needed. */
+ RTStrPurgeEncoding(pThis->szRockNameBuf);
+ }
+ else
+ {
+ LogRel(("rtFsIsoImport/Rock: 'NM' constructs a too long name, ignoring it all: '%s%.*s'\n",
+ pThis->szRockNameBuf, cchName, pUnion->NM.achName));
+ pThis->szRockNameBuf[0] = '\0';
+ pThis->fSeenLastNM = true;
+ }
+ }
+ }
+ break;
+
+ case MAKE_SIG(ISO9660RRIPCL_SIG1, ISO9660RRIPCL_SIG2): /* CL - just warn for now. */
+ case MAKE_SIG(ISO9660RRIPPL_SIG1, ISO9660RRIPPL_SIG2): /* PL - just warn for now. */
+ case MAKE_SIG(ISO9660RRIPRE_SIG1, ISO9660RRIPRE_SIG2): /* RE - just warn for now. */
+ LogRel(("rtFsIsoImport/Rock: Ignoring directory relocation entry '%c%c'!\n", pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ break;
+
+ default:
+ LogRel(("rtFsIsoImport/Rock: Unknown SUSP entry: %#x %#x, %#x bytes, v%u\n",
+ pUnion->Hdr.bSig1, pUnion->Hdr.bSig2, pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion));
+ break;
+#undef MAKE_SIG
+ }
+ }
+}
+
+
+/**
+ * Deals with the special '.' entry in the root directory.
+ *
+ * @returns IPRT status code.
+ * @param pThis The import instance.
+ * @param pDirRec The root directory record.
+ * @param fUnicode Indicates which namespace we're working on.
+ */
+static int rtFsIsoImportProcessIso9660TreeWorkerDoRockForRoot(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, bool fUnicode)
+{
+ uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId)
+ - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1);
+ uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)];
+ if (cbSys > 4)
+ {
+ RTFSOBJINFO ObjInfo;
+ ObjInfo.cbObject = 0;
+ ObjInfo.cbAllocated = 0;
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&ObjInfo.AccessTime, &pDirRec->RecTime);
+ ObjInfo.ModificationTime = ObjInfo.AccessTime;
+ ObjInfo.ChangeTime = ObjInfo.AccessTime;
+ ObjInfo.BirthTime = ObjInfo.AccessTime;
+ ObjInfo.Attr.fMode = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555;
+ ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ ObjInfo.Attr.u.Unix.uid = NIL_RTUID;
+ ObjInfo.Attr.u.Unix.gid = NIL_RTGID;
+ ObjInfo.Attr.u.Unix.cHardlinks = 2;
+ ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ ObjInfo.Attr.u.Unix.INodeId = 0;
+ ObjInfo.Attr.u.Unix.fFlags = 0;
+ ObjInfo.Attr.u.Unix.GenerationId = 0;
+ ObjInfo.Attr.u.Unix.Device = 0;
+
+ rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, &ObjInfo, pbSys, cbSys, fUnicode, true /*fIsFirstDirRec*/,
+ false /*fContinuationRecord*/);
+ /** @todo Update root dir attribs. Need API. */
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates a directory record.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pDirRec The directory record to validate.
+ * @param cbMax The maximum size.
+ */
+static int rtFsIsoImportValidateDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, uint32_t cbMax)
+{
+ /*
+ * Validate dual fields.
+ */
+ if (RT_LE2H_U32(pDirRec->cbData.le) != RT_BE2H_U32(pDirRec->cbData.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC,
+ "Invalid dir rec size field: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pDirRec->cbData.be), RT_LE2H_U32(pDirRec->cbData.le));
+
+ if (RT_LE2H_U32(pDirRec->offExtent.le) != RT_BE2H_U32(pDirRec->offExtent.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC,
+ "Invalid dir rec extent field: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pDirRec->offExtent.be), RT_LE2H_U32(pDirRec->offExtent.le));
+
+ if (RT_LE2H_U16(pDirRec->VolumeSeqNo.le) != RT_BE2H_U16(pDirRec->VolumeSeqNo.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC,
+ "Invalid dir rec volume sequence ID field: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pDirRec->VolumeSeqNo.be), RT_LE2H_U16(pDirRec->VolumeSeqNo.le));
+
+ /*
+ * Check values.
+ */
+ if (ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo) != pThis->idPrimaryVol)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_REC_VOLUME_SEQ_NO,
+ "Expected dir rec to have same volume sequence number as primary volume: %#x, expected %#x",
+ ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo), pThis->idPrimaryVol);
+
+ if (ISO9660_GET_ENDIAN(&pDirRec->offExtent) >= pThis->cBlocksInPrimaryVolumeSpace)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_REC_EXTENT_OUT_OF_BOUNDS,
+ "Invalid dir rec extent: %#RX32, max %#RX32",
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent), pThis->cBlocksInPrimaryVolumeSpace);
+
+ if (pDirRec->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pDirRec->bFileIdLength)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC_LENGTH,
+ "Dir record size is too small: %#x (min %#x)",
+ pDirRec->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId) + pDirRec->bFileIdLength);
+ if (pDirRec->cbDirRec > cbMax)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_DIR_REC_LENGTH,
+ "Dir record size is too big: %#x (max %#x)", pDirRec->cbDirRec, cbMax);
+
+ if ( (pDirRec->fFileFlags & (ISO9660_FILE_FLAGS_MULTI_EXTENT | ISO9660_FILE_FLAGS_DIRECTORY))
+ == (ISO9660_FILE_FLAGS_MULTI_EXTENT | ISO9660_FILE_FLAGS_DIRECTORY))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DIR_WITH_MORE_EXTENTS,
+ "Multi-extent directories are not supported (cbData=%#RX32 offExtent=%#RX32)",
+ ISO9660_GET_ENDIAN(&pDirRec->cbData), ISO9660_GET_ENDIAN(&pDirRec->offExtent));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates a dot or dot-dot directory record.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pDirRec The dot directory record to validate.
+ * @param cbMax The maximum size.
+ * @param bName The name byte (0x00: '.', 0x01: '..').
+ */
+static int rtFsIsoImportValidateDotDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec, uint32_t cbMax, uint8_t bName)
+{
+ int rc = rtFsIsoImportValidateDirRec(pThis, pDirRec, cbMax);
+ if (RT_SUCCESS(rc))
+ {
+ if (pDirRec->bFileIdLength != 1)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DOT_DIR_REC_BAD_NAME_LENGTH,
+ "Invalid dot dir rec file id length: %u", pDirRec->bFileIdLength);
+ if ((uint8_t)pDirRec->achFileId[0] != bName)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_DOT_DIR_REC_BAD_NAME,
+ "Invalid dot dir rec file id: %#x, expected %#x", pDirRec->achFileId[0], bName);
+ }
+ return rc;
+}
+
+
+/**
+ * rtFsIsoImportProcessIso9660TreeWorker helper that reads more data.
+ *
+ * @returns IPRT status code.
+ * @param pThis The importer instance.
+ * @param ppDirRec Pointer to the directory record pointer (in/out).
+ * @param pcbChunk Pointer to the cbChunk variable (in/out).
+ * @param pcbDir Pointer to the cbDir variable (in/out). This indicates
+ * how much we've left to read from the directory.
+ * @param poffNext Pointer to the offNext variable (in/out). This
+ * indicates where the next chunk of directory data is in
+ * the input file.
+ */
+static int rtFsIsoImportProcessIso9660TreeWorkerReadMore(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC *ppDirRec,
+ uint32_t *pcbChunk, uint32_t *pcbDir, uint64_t *poffNext)
+{
+ uint32_t cbChunk = *pcbChunk;
+ *ppDirRec = (PCISO9660DIRREC)memmove(&pThis->abBuf[ISO9660_SECTOR_SIZE - cbChunk], *ppDirRec, cbChunk);
+
+ Assert(!(*poffNext & (ISO9660_SECTOR_SIZE - 1)));
+ uint32_t cbToRead = RT_MIN(*pcbDir, sizeof(pThis->abBuf) - ISO9660_SECTOR_SIZE);
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, *poffNext, &pThis->abBuf[ISO9660_SECTOR_SIZE], cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: Read %#zx more bytes @%#RX64, now got @%#RX64 LB %#RX32\n",
+ cbToRead, *poffNext, *poffNext - cbChunk, cbChunk + cbToRead));
+ *poffNext += cbToRead;
+ *pcbDir -= cbToRead;
+ *pcbChunk = cbChunk + cbToRead;
+ return VINF_SUCCESS;
+ }
+ return rtFsIsoImpError(pThis, rc, "Error reading %#RX32 bytes at %#RX64 (dir): %Rrc", *poffNext, cbToRead);
+}
+
+
+/**
+ * rtFsIsoImportProcessIso9660TreeWorker helper that deals with skipping to the
+ * next sector when cbDirRec is zero.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_NO_MORE_FILES when we reaches the end of the directory.
+ * @param pThis The importer instance.
+ * @param ppDirRec Pointer to the directory record pointer (in/out).
+ * @param pcbChunk Pointer to the cbChunk variable (in/out). Indicates how
+ * much we've left to process starting and pDirRec.
+ * @param pcbDir Pointer to the cbDir variable (in/out). This indicates
+ * how much we've left to read from the directory.
+ * @param poffNext Pointer to the offNext variable (in/out). This
+ * indicates where the next chunk of directory data is in
+ * the input file.
+ */
+static int rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC *ppDirRec,
+ uint32_t *pcbChunk, uint32_t *pcbDir, uint64_t *poffNext)
+{
+ uint32_t cbChunk = *pcbChunk;
+ uint64_t offChunk = *poffNext - cbChunk;
+ uint32_t cbSkip = ISO9660_SECTOR_SIZE - ((uint32_t)offChunk & (ISO9660_SECTOR_SIZE - 1));
+ if (cbSkip < cbChunk)
+ {
+ *ppDirRec = (PCISO9660DIRREC)((uintptr_t)*ppDirRec + cbSkip);
+ *pcbChunk = cbChunk -= cbSkip;
+ if ( cbChunk > UINT8_MAX
+ || *pcbDir == 0)
+ {
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> jumped %#RX32 to @%#RX64 LB %#RX32\n",
+ cbSkip, *poffNext - cbChunk, cbChunk));
+ return VINF_SUCCESS;
+ }
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> jumped %#RX32 to @%#RX64 LB %#RX32, but needs to read more\n",
+ cbSkip, *poffNext - cbChunk, cbChunk));
+ return rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, ppDirRec, pcbChunk, pcbDir, poffNext);
+ }
+
+ /* ASSUMES we're working in multiples of sectors! */
+ if (*pcbDir == 0)
+ {
+ *pcbChunk = 0;
+ return VERR_NO_MORE_FILES;
+ }
+
+ /* End of chunk, read the next sectors. */
+ Assert(!(*poffNext & (ISO9660_SECTOR_SIZE - 1)));
+ uint32_t cbToRead = RT_MIN(*pcbDir, sizeof(pThis->abBuf));
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, *poffNext, pThis->abBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: cbDirRec=0 --> Read %#zx more bytes @%#RX64, now got @%#RX64 LB %#RX32\n",
+ cbToRead, *poffNext, *poffNext - cbChunk, cbChunk + cbToRead));
+ *poffNext += cbToRead;
+ *pcbDir -= cbToRead;
+ *pcbChunk = cbChunk + cbToRead;
+ *ppDirRec = (PCISO9660DIRREC)&pThis->abBuf[0];
+ return VINF_SUCCESS;
+ }
+ return rtFsIsoImpError(pThis, rc, "Error reading %#RX32 bytes at %#RX64 (dir): %Rrc", *poffNext, cbToRead);
+}
+
+
+/**
+ * Deals with a single directory.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param idxDir The configuration index for the directory.
+ * @param offDirBlock The offset of the directory data.
+ * @param cbDir The size of the directory data.
+ * @param cDepth The depth of the directory.
+ * @param fUnicode Set if it's a unicode (UTF-16BE) encoded
+ * directory.
+ * @param pTodoList The todo-list to add sub-directories to.
+ */
+static int rtFsIsoImportProcessIso9660TreeWorker(PRTFSISOMKIMPORTER pThis, uint32_t idxDir,
+ uint32_t offDirBlock, uint32_t cbDir, uint8_t cDepth, bool fUnicode,
+ PRTLISTANCHOR pTodoList)
+{
+ /*
+ * Restrict the depth to try avoid loops.
+ */
+ if (cDepth > RTFSISOMK_IMPORT_MAX_DEPTH)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_TOO_DEEP_DIR_TREE, "Dir at %#x LB %#x is too deep", offDirBlock, cbDir);
+
+ /*
+ * Read the first chunk into the big buffer.
+ */
+ uint32_t cbChunk = RT_MIN(cbDir, sizeof(pThis->abBuf));
+ uint64_t offNext = (uint64_t)offDirBlock * ISO9660_SECTOR_SIZE;
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, offNext, pThis->abBuf, cbChunk, NULL);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "Error reading directory at %#RX64 (%#RX32 / %#RX32): %Rrc", offNext, cbChunk, cbDir);
+
+ cbDir -= cbChunk;
+ offNext += cbChunk;
+
+ /*
+ * Skip the current and parent directory entries.
+ */
+ PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pThis->abBuf[0];
+ rc = rtFsIsoImportValidateDotDirRec(pThis, pDirRec, cbChunk, 0x00);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ( cDepth == 0
+ && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ROCK_RIDGE)
+ && pDirRec->cbDirRec > RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]))
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerDoRockForRoot(pThis, pDirRec, fUnicode);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ cbChunk -= pDirRec->cbDirRec;
+ pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec);
+ rc = rtFsIsoImportValidateDotDirRec(pThis, pDirRec, cbChunk, 0x01);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ cbChunk -= pDirRec->cbDirRec;
+ pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec);
+
+ /*
+ * Work our way thru all the directory records.
+ */
+ Log3(("rtFsIsoImportProcessIso9660TreeWorker: Starting at @%#RX64 LB %#RX32 (out of %#RX32) in %#x\n",
+ offNext - cbChunk, cbChunk, cbChunk + cbDir, idxDir));
+ const uint32_t fNamespace = fUnicode ? RTFSISOMAKER_NAMESPACE_JOLIET : RTFSISOMAKER_NAMESPACE_ISO_9660;
+ while ( cbChunk > 0
+ || cbDir > 0)
+ {
+ /*
+ * Do we need to read some more?
+ */
+ if ( cbChunk > UINT8_MAX
+ || cbDir == 0)
+ { /* No, we don't. */ }
+ else
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, &pDirRec, &cbChunk, &cbDir, &offNext);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /* If null length, skip to the next sector. May have to read some then. */
+ if (pDirRec->cbDirRec != 0)
+ { /* likely */ }
+ else
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(pThis, &pDirRec, &cbChunk, &cbDir, &offNext);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ break;
+ return rc;
+ }
+ if (pDirRec->cbDirRec == 0)
+ continue;
+ }
+
+ /*
+ * Validate the directory record. Give up if not valid since we're
+ * likely to get error with subsequent record too.
+ */
+ uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId)
+ - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1);
+ uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)];
+ Log3(("pDirRec=&abBuf[%#07zx]: @%#010RX64 cb=%#04x ff=%#04x off=%#010RX32 cb=%#010RX32 cbSys=%#x id=%.*Rhxs\n",
+ (uintptr_t)pDirRec - (uintptr_t)&pThis->abBuf[0], offNext - cbChunk, pDirRec->cbDirRec, pDirRec->fFileFlags,
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent), ISO9660_GET_ENDIAN(&pDirRec->cbData), cbSys,
+ pDirRec->bFileIdLength, pDirRec->achFileId));
+ rc = rtFsIsoImportValidateDirRec(pThis, pDirRec, cbChunk);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* This early calculation of the next record is due to multi-extent
+ handling further down. */
+ uint32_t cbChunkNew = cbChunk - pDirRec->cbDirRec;
+ PCISO9660DIRREC pDirRecNext = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec);
+
+ /* Start Collecting object info. */
+ RTFSOBJINFO ObjInfo;
+ ObjInfo.cbObject = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ ObjInfo.cbAllocated = ObjInfo.cbObject;
+ rtFsIsoImpIso9660RecDateTime2TimeSpec(&ObjInfo.AccessTime, &pDirRec->RecTime);
+ ObjInfo.ModificationTime = ObjInfo.AccessTime;
+ ObjInfo.ChangeTime = ObjInfo.AccessTime;
+ ObjInfo.BirthTime = ObjInfo.AccessTime;
+ ObjInfo.Attr.fMode = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY
+ ? RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555
+ : RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | 0444;
+ ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ ObjInfo.Attr.u.Unix.uid = NIL_RTUID;
+ ObjInfo.Attr.u.Unix.gid = NIL_RTGID;
+ ObjInfo.Attr.u.Unix.cHardlinks = 1;
+ ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ ObjInfo.Attr.u.Unix.INodeId = 0;
+ ObjInfo.Attr.u.Unix.fFlags = 0;
+ ObjInfo.Attr.u.Unix.GenerationId = 0;
+ ObjInfo.Attr.u.Unix.Device = 0;
+
+ /*
+ * Convert the name into the name buffer (szNameBuf).
+ */
+ if (!fUnicode)
+ {
+ memcpy(pThis->szNameBuf, pDirRec->achFileId, pDirRec->bFileIdLength);
+ pThis->szNameBuf[pDirRec->bFileIdLength] = '\0';
+ rc = RTStrValidateEncoding(pThis->szNameBuf);
+ }
+ else
+ {
+ char *pszDst = pThis->szNameBuf;
+ rc = RTUtf16BigToUtf8Ex((PRTUTF16)pDirRec->achFileId, pDirRec->bFileIdLength / sizeof(RTUTF16),
+ &pszDst, sizeof(pThis->szNameBuf), NULL);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* Drop the version from the name. */
+ size_t cchName = strlen(pThis->szNameBuf);
+ if ( !(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ && cchName > 2
+ && RT_C_IS_DIGIT(pThis->szNameBuf[cchName - 1]))
+ {
+ uint32_t offName = 2;
+ while ( offName <= 5
+ && offName + 1 < cchName
+ && RT_C_IS_DIGIT(pThis->szNameBuf[cchName - offName]))
+ offName++;
+ if ( offName + 1 < cchName
+ && pThis->szNameBuf[cchName - offName] == ';')
+ {
+ RTStrToUInt32Full(&pThis->szNameBuf[cchName - offName + 1], 10, &ObjInfo.Attr.u.Unix.GenerationId);
+ pThis->szNameBuf[cchName - offName] = '\0';
+ }
+ }
+ Log3((" --> name='%s'\n", pThis->szNameBuf));
+
+ pThis->szRockNameBuf[0] = '\0';
+ pThis->szRockSymlinkTargetBuf[0] = '\0';
+ if ( cbSys > pThis->offSuspSkip
+ && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ROCK_RIDGE))
+ {
+ pThis->fSeenLastNM = false;
+ pThis->fSeenLastSL = false;
+ pThis->szRockNameBuf[0] = '\0';
+ pThis->szRockSymlinkTargetBuf[0] = '\0';
+ rtFsIsoImportProcessIso9660TreeWorkerParseRockRidge(pThis, &ObjInfo, &pbSys[pThis->offSuspSkip],
+ cbSys - pThis->offSuspSkip, fUnicode,
+ false /*fContinuationRecord*/, false /*fIsFirstDirRec*/);
+ }
+
+ /*
+ * Deal with multi-extent files (usually large ones). We currently only
+ * handle files where the data is in single continuous chunk and only split
+ * up into multiple directory records because of data type limitations.
+ */
+ uint8_t abDirRecCopy[256];
+ uint64_t cbData = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ { /* likely */ }
+ else
+ {
+ if (cbData & (ISO9660_SECTOR_SIZE - 1))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISALIGNED_MULTI_EXTENT,
+ "The size of non-final multi-extent record #0x0 isn't block aligned: %#RX64", cbData);
+
+ /* Make a copy of the first directory record so we don't overwrite
+ it when reading in more records below. */
+ pDirRec = (PCISO9660DIRREC)memcpy(abDirRecCopy, pDirRec, pDirRec->cbDirRec);
+
+ /* Process extent records. */
+ uint32_t cDirRecs = 1;
+ uint32_t offNextBlock = ISO9660_GET_ENDIAN(&pDirRec->offExtent)
+ + ISO9660_GET_ENDIAN(&pDirRec->cbData) / ISO9660_SECTOR_SIZE;
+ while ( cbChunkNew > 0
+ || cbDir > 0)
+ {
+ /* Read more? Skip? */
+ if ( cbChunkNew <= UINT8_MAX
+ && cbDir != 0)
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerReadMore(pThis, &pDirRecNext, &cbChunkNew, &cbDir, &offNext);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ if (pDirRecNext->cbDirRec == 0)
+ {
+ rc = rtFsIsoImportProcessIso9660TreeWorkerHandleZeroSizedDirRec(pThis, &pDirRecNext, &cbChunkNew,
+ &cbDir, &offNext);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_NO_MORE_FILES)
+ break;
+ return rc;
+ }
+ if (pDirRecNext->cbDirRec == 0)
+ continue;
+ }
+
+ /* Check the next record. */
+ rc = rtFsIsoImportValidateDirRec(pThis, pDirRecNext, cbChunkNew);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (pDirRecNext->bFileIdLength != pDirRec->bFileIdLength)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC,
+ "Multi-extent record #%#x differs from the first: bFileIdLength is %#x, expected %#x",
+ cDirRecs, pDirRecNext->bFileIdLength, pDirRec->bFileIdLength);
+ if (memcmp(pDirRecNext->achFileId, pDirRec->achFileId, pDirRec->bFileIdLength) != 0)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC,
+ "Multi-extent record #%#x differs from the first: achFileId is %.*Rhxs, expected %.*Rhxs",
+ cDirRecs, pDirRecNext->bFileIdLength, pDirRecNext->achFileId,
+ pDirRec->bFileIdLength, pDirRec->achFileId);
+ if (ISO9660_GET_ENDIAN(&pDirRecNext->VolumeSeqNo) != ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISMATCHING_MULTI_EXTENT_REC,
+ "Multi-extent record #%#x differs from the first: VolumeSeqNo is %#x, expected %#x",
+ cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->VolumeSeqNo),
+ ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo));
+ if ( (pDirRecNext->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT)
+ && (ISO9660_GET_ENDIAN(&pDirRecNext->cbData) & (ISO9660_SECTOR_SIZE - 1)) )
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MISALIGNED_MULTI_EXTENT,
+ "The size of non-final multi-extent record #%#x isn't block aligned: %#RX32",
+ cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->cbData));
+
+ /* Check that the data is contiguous, then add the data. */
+ if (ISO9660_GET_ENDIAN(&pDirRecNext->offExtent) == offNextBlock)
+ cbData += ISO9660_GET_ENDIAN(&pDirRecNext->cbData);
+ else
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_NON_CONTIGUOUS_MULTI_EXTENT,
+ "Multi-extent record #%#x isn't contiguous: offExtent=%#RX32, expected %#RX32",
+ cDirRecs, ISO9660_GET_ENDIAN(&pDirRecNext->offExtent), offNextBlock);
+
+ /* Advance. */
+ cDirRecs++;
+ bool fDone = !(pDirRecNext->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT);
+ offNext += ISO9660_GET_ENDIAN(&pDirRecNext->cbData) / ISO9660_SECTOR_SIZE;
+ cbChunkNew -= pDirRecNext->cbDirRec;
+ pDirRecNext = (PCISO9660DIRREC)((uintptr_t)pDirRecNext + pDirRecNext->cbDirRec);
+ if (fDone)
+ break;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Add the object.
+ */
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ rtFsIsoImportProcessIso9660AddAndNameDirectory(pThis, pDirRec, &ObjInfo, cbData, fNamespace, idxDir,
+ pThis->szNameBuf, pThis->szRockNameBuf, cDepth + 1, pTodoList);
+ else if (pThis->szRockSymlinkTargetBuf[0] == '\0')
+ {
+ if (strcmp(pThis->szNameBuf, pThis->pszTransTbl) != 0)
+ rtFsIsoImportProcessIso9660AddAndNameFile(pThis, pDirRec, &ObjInfo, cbData, fNamespace, idxDir,
+ pThis->szNameBuf, pThis->szRockNameBuf);
+ }
+ else
+ rtFsIsoImportProcessIso9660AddAndNameSymlink(pThis, pDirRec, &ObjInfo, fNamespace, idxDir, pThis->szNameBuf,
+ pThis->szRockNameBuf, pThis->szRockSymlinkTargetBuf);
+ }
+ }
+ else
+ rtFsIsoImpError(pThis, rc, "Invalid name at %#RX64: %.Rhxs",
+ offNext - cbChunk, pDirRec->bFileIdLength, pDirRec->achFileId);
+
+ /*
+ * Advance to the next directory record.
+ */
+ cbChunk = cbChunkNew;
+ pDirRec = pDirRecNext;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deals with a directory tree.
+ *
+ * This is implemented by tracking directories that needs to be processed in a
+ * todo list, so no recursive calls, however it uses a bit of heap.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param offDirBlock The offset of the root directory data.
+ * @param cbDir The size of the root directory data.
+ * @param fUnicode Set if it's a unicode (UTF-16BE) encoded
+ * directory.
+ */
+static int rtFsIsoImportProcessIso9660Tree(PRTFSISOMKIMPORTER pThis, uint32_t offDirBlock, uint32_t cbDir, bool fUnicode)
+{
+ /*
+ * Reset some parsing state.
+ */
+ pThis->offSuspSkip = 0;
+ pThis->fSuspSeenSP = false;
+ pThis->pszTransTbl = "TRANS.TBL"; /** @todo query this from the iso maker! */
+
+ /*
+ * Make sure we've got a root in the namespace.
+ */
+ uint32_t idxDir = RTFsIsoMakerGetObjIdxForPath(pThis->hIsoMaker,
+ !fUnicode ? RTFSISOMAKER_NAMESPACE_ISO_9660 : RTFSISOMAKER_NAMESPACE_JOLIET,
+ "/");
+ if (idxDir == UINT32_MAX)
+ {
+ idxDir = RTFSISOMAKER_CFG_IDX_ROOT;
+ int rc = RTFsIsoMakerObjSetPath(pThis->hIsoMaker, RTFSISOMAKER_CFG_IDX_ROOT,
+ !fUnicode ? RTFSISOMAKER_NAMESPACE_ISO_9660 : RTFSISOMAKER_NAMESPACE_JOLIET, "/");
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerObjSetPath failed on root dir: %Rrc", rc);
+ }
+ Assert(idxDir == RTFSISOMAKER_CFG_IDX_ROOT);
+
+ /*
+ * Directories.
+ */
+ int rc = VINF_SUCCESS;
+ uint8_t cDepth = 0;
+ RTLISTANCHOR TodoList;
+ RTListInit(&TodoList);
+ for (;;)
+ {
+ int rc2 = rtFsIsoImportProcessIso9660TreeWorker(pThis, idxDir, offDirBlock, cbDir, cDepth, fUnicode, &TodoList);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+
+ /*
+ * Pop the next directory.
+ */
+ PRTFSISOMKIMPDIR pNext = RTListRemoveLast(&TodoList, RTFSISOMKIMPDIR, Entry);
+ if (!pNext)
+ break;
+ idxDir = pNext->idxObj;
+ offDirBlock = pNext->offDirBlock;
+ cbDir = pNext->cbDir;
+ cDepth = pNext->cDepth;
+ RTMemFree(pNext);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Imports a UTF-16BE string property from the joliet volume descriptor.
+ *
+ * The fields are normally space filled and padded, but we also consider zero
+ * bytes are fillers. If the field only contains padding, the string property
+ * will remain unchanged.
+ *
+ * @returns IPRT status code (ignorable).
+ * @param pThis The importer instance.
+ * @param pachField Pointer to the field. The structure type
+ * is 'char' for hysterical raisins, while the
+ * real type is 'RTUTF16'.
+ * @param cchField The field length.
+ * @param enmStringProp The corresponding string property.
+ *
+ * @note Clobbers pThis->pbBuf!
+ */
+static int rtFsIsoImportUtf16BigStringField(PRTFSISOMKIMPORTER pThis, const char *pachField, size_t cchField,
+ RTFSISOMAKERSTRINGPROP enmStringProp)
+{
+ /*
+ * Scan the field from the end as this way we know the result length if we find anything.
+ */
+ PCRTUTF16 pwcField = (PCRTUTF16)pachField;
+ size_t cwcField = cchField / sizeof(RTUTF16); /* ignores any odd field byte */
+ size_t off = cwcField;
+ while (off-- > 0)
+ {
+ RTUTF16 wc = RT_BE2H_U16(pwcField[off]);
+ if (wc == ' ' || wc == '\0')
+ { /* likely */ }
+ else
+ {
+ /*
+ * Convert to UTF-16.
+ */
+ char *pszCopy = (char *)pThis->abBuf;
+ int rc = RTUtf16BigToUtf8Ex(pwcField, off + 1, &pszCopy, sizeof(pThis->abBuf), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFsIsoMakerSetStringProp(pThis->hIsoMaker, enmStringProp, RTFSISOMAKER_NAMESPACE_JOLIET, pszCopy);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetStringProp failed setting field %d to '%s': %Rrc",
+ enmStringProp, pszCopy, rc);
+ }
+ return rtFsIsoImpError(pThis, rc, "RTUtf16BigToUtf8Ex failed converting field %d to UTF-8: %Rrc - %.*Rhxs",
+ enmStringProp, rc, off * sizeof(RTUTF16), pwcField);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Imports a string property from the primary volume descriptor.
+ *
+ * The fields are normally space filled and padded, but we also consider zero
+ * bytes are fillers. If the field only contains padding, the string property
+ * will remain unchanged.
+ *
+ * @returns IPRT status code (ignorable).
+ * @param pThis The importer instance.
+ * @param pachField Pointer to the field.
+ * @param cchField The field length.
+ * @param enmStringProp The corresponding string property.
+ *
+ * @note Clobbers pThis->pbBuf!
+ */
+static int rtFsIsoImportAsciiStringField(PRTFSISOMKIMPORTER pThis, const char *pachField, size_t cchField,
+ RTFSISOMAKERSTRINGPROP enmStringProp)
+{
+ /*
+ * Scan the field from the end as this way we know the result length if we find anything.
+ */
+ size_t off = cchField;
+ while (off-- > 0)
+ {
+ char ch = pachField[off];
+ if (ch == ' ' || ch == '\0')
+ { /* likely */ }
+ else
+ {
+ /*
+ * Make a copy of the string in abBuf, purge the encoding.
+ */
+ off++;
+ char *pszCopy = (char *)pThis->abBuf;
+ memcpy(pszCopy, pachField, off);
+ pszCopy[off] = '\0';
+ RTStrPurgeEncoding(pszCopy);
+
+ int rc = RTFsIsoMakerSetStringProp(pThis->hIsoMaker, enmStringProp, RTFSISOMAKER_NAMESPACE_ISO_9660, pszCopy);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetStringProp failed setting field %d to '%s': %Rrc",
+ enmStringProp, pszCopy, rc);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates a root directory record.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pDirRec The root directory record to validate.
+ */
+static int rtFsIsoImportValidateRootDirRec(PRTFSISOMKIMPORTER pThis, PCISO9660DIRREC pDirRec)
+{
+ /*
+ * Validate dual fields.
+ */
+ if (RT_LE2H_U32(pDirRec->cbData.le) != RT_BE2H_U32(pDirRec->cbData.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC,
+ "Invalid root dir size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pDirRec->cbData.be), RT_LE2H_U32(pDirRec->cbData.le));
+
+ if (RT_LE2H_U32(pDirRec->offExtent.le) != RT_BE2H_U32(pDirRec->offExtent.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC,
+ "Invalid root dir extent: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pDirRec->offExtent.be), RT_LE2H_U32(pDirRec->offExtent.le));
+
+ if (RT_LE2H_U16(pDirRec->VolumeSeqNo.le) != RT_BE2H_U16(pDirRec->VolumeSeqNo.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC,
+ "Invalid root dir volume sequence ID: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pDirRec->VolumeSeqNo.be), RT_LE2H_U16(pDirRec->VolumeSeqNo.le));
+
+ /*
+ * Check values.
+ */
+ if (ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo) != pThis->idPrimaryVol)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_VOLUME_SEQ_NO,
+ "Expected root dir to have same volume sequence number as primary volume: %#x, expected %#x",
+ ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo), pThis->idPrimaryVol);
+
+ if (ISO9660_GET_ENDIAN(&pDirRec->cbData) == 0)
+ return RTErrInfoSet(pThis->pErrInfo, VERR_ISOMK_IMPORT_ZERO_SIZED_ROOT_DIR, "Zero sized root dir");
+
+ if (ISO9660_GET_ENDIAN(&pDirRec->offExtent) >= pThis->cBlocksInPrimaryVolumeSpace)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_EXTENT_OUT_OF_BOUNDS,
+ "Invalid root dir extent: %#RX32, max %#RX32",
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent), pThis->cBlocksInPrimaryVolumeSpace);
+
+ if (pDirRec->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_ROOT_DIR_REC_LENGTH,
+ "Root dir record size is too small: %#x (min %#x)",
+ pDirRec->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId));
+
+ if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_WITHOUT_DIR_FLAG,
+ "Root dir is not flagged as directory: %#x", pDirRec->fFileFlags);
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_ROOT_DIR_IS_MULTI_EXTENT,
+ "Root dir is cannot be multi-extent: %#x", pDirRec->fFileFlags);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes a primary volume descriptor, importing all files and stuff.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pVolDesc The primary volume descriptor.
+ */
+static int rtFsIsoImportProcessPrimaryDesc(PRTFSISOMKIMPORTER pThis, PISO9660PRIMARYVOLDESC pVolDesc)
+{
+ /*
+ * Validate dual fields first.
+ */
+ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION)
+ return rtFsIsoImpError(pThis, VERR_IOSMK_IMPORT_PRIMARY_VOL_DESC_VER,
+ "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion);
+
+ if (RT_LE2H_U16(pVolDesc->cbLogicalBlock.le) != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching logical block size: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le));
+ if (RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le) != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching volume space size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le));
+ if (RT_LE2H_U16(pVolDesc->cVolumesInSet.le) != RT_BE2H_U16(pVolDesc->cVolumesInSet.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching volumes in set: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le));
+ if (RT_LE2H_U16(pVolDesc->VolumeSeqNo.le) != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be))
+ {
+ /* Hack alert! An Windows NT 3.1 ISO was found to not have the big endian bit set here, so work around it. */
+ if ( pVolDesc->VolumeSeqNo.be == 0
+ && pVolDesc->VolumeSeqNo.le == RT_H2LE_U16_C(1))
+ pVolDesc->VolumeSeqNo.be = RT_H2BE_U16_C(1);
+ else
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching volume sequence no.: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le));
+ }
+ if (RT_LE2H_U32(pVolDesc->cbPathTable.le) != RT_BE2H_U32(pVolDesc->cbPathTable.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_PRIMARY_VOL_DESC,
+ "Mismatching path table size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le));
+
+ /*
+ * Validate field values against our expectations.
+ */
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != ISO9660_SECTOR_SIZE)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_LOGICAL_BLOCK_SIZE_NOT_2KB,
+ "Unsupported block size: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock));
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != 1)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MORE_THAN_ONE_VOLUME_IN_SET,
+ "Volumes in set: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet));
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != 1)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO,
+ "Unexpected volume sequence number: %#x", ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo));
+
+ /*
+ * Gather info we need.
+ */
+ pThis->cBlocksInPrimaryVolumeSpace = ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize);
+ pThis->cbPrimaryVolumeSpace = pThis->cBlocksInPrimaryVolumeSpace * (uint64_t)ISO9660_SECTOR_SIZE;
+ pThis->cVolumesInSet = ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet);
+ pThis->idPrimaryVol = ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo);
+
+ /*
+ * Validate the root directory record.
+ */
+ int rc = rtFsIsoImportValidateRootDirRec(pThis, &pVolDesc->RootDir.DirRec);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Import stuff if present and not opted out.
+ */
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_SYSTEM_ID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId),
+ RTFSISOMAKERSTRINGPROP_SYSTEM_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_VOLUME_ID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId),
+ RTFSISOMAKERSTRINGPROP_VOLUME_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_VOLUME_SET_ID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId),
+ RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_PUBLISHER_ID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId),
+ RTFSISOMAKERSTRINGPROP_PUBLISHER_ID);
+ if (pThis->fFlags & RTFSISOMK_IMPORT_F_DATA_PREPARER_ID)
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId),
+ RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID);
+ if (pThis->fFlags & RTFSISOMK_IMPORT_F_APPLICATION_ID)
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId),
+ RTFSISOMAKERSTRINGPROP_APPLICATION_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_COPYRIGHT_FID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId),
+ RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_ABSTRACT_FID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId),
+ RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_BIBLIO_FID))
+ rtFsIsoImportAsciiStringField(pThis, pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId),
+ RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID);
+
+ /*
+ * Process the directory tree.
+ */
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_PRIMARY_ISO))
+ rc = rtFsIsoImportProcessIso9660Tree(pThis, ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.offExtent),
+ ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.cbData), false /*fUnicode*/);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes a secondary volume descriptor, if it is joliet we'll importing all
+ * the files and stuff.
+ *
+ * @returns IPRT status code (safe to ignore, see pThis->rc).
+ * @param pThis The importer instance.
+ * @param pVolDesc The primary volume descriptor.
+ */
+static int rtFsIsoImportProcessSupplementaryDesc(PRTFSISOMKIMPORTER pThis, PISO9660SUPVOLDESC pVolDesc)
+{
+ /*
+ * Validate dual fields first.
+ */
+ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION)
+ return rtFsIsoImpError(pThis, VERR_IOSMK_IMPORT_SUP_VOL_DESC_VER,
+ "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion);
+
+ if (RT_LE2H_U16(pVolDesc->cbLogicalBlock.le) != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching logical block size: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le));
+ if (RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le) != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching volume space size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le));
+ if (RT_LE2H_U16(pVolDesc->cVolumesInSet.le) != RT_BE2H_U16(pVolDesc->cVolumesInSet.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching volumes in set: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le));
+ if (RT_LE2H_U16(pVolDesc->VolumeSeqNo.le) != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching volume sequence no.: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le));
+ if (RT_LE2H_U32(pVolDesc->cbPathTable.le) != RT_BE2H_U32(pVolDesc->cbPathTable.be))
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BAD_SUP_VOL_DESC,
+ "Mismatching path table size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le));
+
+ /*
+ * Validate field values against our expectations.
+ */
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != ISO9660_SECTOR_SIZE)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_LOGICAL_BLOCK_SIZE_NOT_2KB,
+ "Unsupported block size: %#x", ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock));
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != pThis->cVolumesInSet)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_VOLUME_IN_SET_MISMATCH, "Volumes in set: %#x, expected %#x",
+ ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet), pThis->cVolumesInSet);
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != pThis->idPrimaryVol)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOLUMNE_SEQ_NO,
+ "Unexpected volume sequence number: %#x (expected %#x)",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo), pThis->idPrimaryVol);
+
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize) != pThis->cBlocksInPrimaryVolumeSpace)
+ {
+ /* ubuntu-21.10-desktop-amd64.iso has 0x172f4e blocks (3 111 809 024 bytes) here
+ and 0x173838 blocks (3 116 482 560 bytes) in the primary, a difference of
+ -2282 blocks (-4 673 536 bytes). Guess something was omitted from the joliet
+ edition, not immediately obvious what though.
+
+ For now we'll just let it pass as long as the primary size is the larger.
+ (Not quite sure how the code will handle a supplementary volume spanning
+ more space, as I suspect it only uses the primary volume size for
+ validating block addresses and such.) */
+ LogRel(("rtFsIsoImportProcessSupplementaryDesc: Volume space size differs between primary and supplementary descriptors: %#x, primary %#x",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize), pThis->cBlocksInPrimaryVolumeSpace));
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize) > pThis->cBlocksInPrimaryVolumeSpace)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_VOLUME_SPACE_SIZE_MISMATCH,
+ "Volume space given in the supplementary descriptor is larger than in the primary: %#x, primary %#x",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize), pThis->cBlocksInPrimaryVolumeSpace);
+ }
+
+ /*
+ * Validate the root directory record.
+ */
+ int rc = rtFsIsoImportValidateRootDirRec(pThis, &pVolDesc->RootDir.DirRec);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Is this a joliet descriptor? Ignore if not.
+ */
+ uint8_t uJolietLevel = 0;
+ if ( pVolDesc->abEscapeSequences[0] == ISO9660_JOLIET_ESC_SEQ_0
+ && pVolDesc->abEscapeSequences[1] == ISO9660_JOLIET_ESC_SEQ_1)
+ switch (pVolDesc->abEscapeSequences[2])
+ {
+ case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1: uJolietLevel = 1; break;
+ case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2: uJolietLevel = 2; break;
+ case ISO9660_JOLIET_ESC_SEQ_2_LEVEL_3: uJolietLevel = 3; break;
+ default: Log(("rtFsIsoImportProcessSupplementaryDesc: last joliet escape sequence byte doesn't match: %#x\n",
+ pVolDesc->abEscapeSequences[2]));
+ }
+ if (uJolietLevel == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Only one joliet descriptor.
+ */
+ if (pThis->fSeenJoliet)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_JOLIET_VOL_DESCS,
+ "More than one Joliet volume descriptor is not supported");
+ pThis->fSeenJoliet = true;
+
+ /*
+ * Import stuff if present and not opted out.
+ */
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_SYSTEM_ID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId),
+ RTFSISOMAKERSTRINGPROP_SYSTEM_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_VOLUME_ID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId),
+ RTFSISOMAKERSTRINGPROP_VOLUME_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_VOLUME_SET_ID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId),
+ RTFSISOMAKERSTRINGPROP_VOLUME_SET_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_PUBLISHER_ID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId),
+ RTFSISOMAKERSTRINGPROP_PUBLISHER_ID);
+ if (pThis->fFlags & RTFSISOMK_IMPORT_F_J_DATA_PREPARER_ID)
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId),
+ RTFSISOMAKERSTRINGPROP_DATA_PREPARER_ID);
+ if (pThis->fFlags & RTFSISOMK_IMPORT_F_J_APPLICATION_ID)
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId),
+ RTFSISOMAKERSTRINGPROP_APPLICATION_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_COPYRIGHT_FID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId),
+ RTFSISOMAKERSTRINGPROP_COPYRIGHT_FILE_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_ABSTRACT_FID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId),
+ RTFSISOMAKERSTRINGPROP_ABSTRACT_FILE_ID);
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_J_BIBLIO_FID))
+ rtFsIsoImportUtf16BigStringField(pThis, pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId),
+ RTFSISOMAKERSTRINGPROP_BIBLIOGRAPHIC_FILE_ID);
+
+ /*
+ * Process the directory tree.
+ */
+ if (!(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_JOLIET))
+ return rtFsIsoImportProcessIso9660Tree(pThis, ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.offExtent),
+ ISO9660_GET_ENDIAN(&pVolDesc->RootDir.DirRec.cbData), true /*fUnicode*/);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Checks out an El Torito boot image to see if it requires info table patching.
+ *
+ * @returns IPRT status code (ignored).
+ * @param pThis The ISO importer instance.
+ * @param idxImageObj The configuration index of the image.
+ * @param offBootImage The block offset of the image.
+ */
+static int rtFsIsoImportProcessElToritoImage(PRTFSISOMKIMPORTER pThis, uint32_t idxImageObj, uint32_t offBootImage)
+{
+ ISO9660SYSLINUXINFOTABLE InfoTable;
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, offBootImage * (uint64_t)ISO9660_SECTOR_SIZE + ISO9660SYSLINUXINFOTABLE_OFFSET,
+ &InfoTable, sizeof(InfoTable), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if ( RT_LE2H_U32(InfoTable.offBootFile) == offBootImage
+ && RT_LE2H_U32(InfoTable.offPrimaryVolDesc) == pThis->offPrimaryVolDesc
+ && ASMMemIsAllU8(&InfoTable.auReserved[0], sizeof(InfoTable.auReserved), 0) )
+ {
+ rc = RTFsIsoMakerObjEnableBootInfoTablePatching(pThis->hIsoMaker, idxImageObj, true /*fEnable*/);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerObjEnableBootInfoTablePatching failed: %Rrc", rc);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes a boot catalog default or section entry.
+ *
+ * @returns IPRT status code (ignored).
+ * @param pThis The ISO importer instance.
+ * @param iEntry The boot catalog entry number. This is 1 for
+ * the default entry, and 3+ for section entries.
+ * @param cMaxEntries Maximum number of entries.
+ * @param pEntry The entry to process.
+ * @param pcSkip Where to return the number of extension entries to skip.
+ */
+static int rtFsIsoImportProcessElToritoSectionEntry(PRTFSISOMKIMPORTER pThis, uint32_t iEntry, uint32_t cMaxEntries,
+ PCISO9660ELTORITOSECTIONENTRY pEntry, uint32_t *pcSkip)
+{
+ *pcSkip = 0;
+
+ /*
+ * Check the boot indicator type for entry 1.
+ */
+ if ( pEntry->bBootIndicator != ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE
+ && pEntry->bBootIndicator != ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_DEF_ENTRY_INVALID_BOOT_IND,
+ "Default boot catalog entry has an invalid boot indicator: %#x", pEntry->bBootIndicator);
+
+ /*
+ * Check the media type and flags.
+ */
+ uint32_t cbDefaultSize;
+ uint8_t bMediaType = pEntry->bBootMediaType;
+ switch (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_TYPE_MASK)
+ {
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_2_MB:
+ cbDefaultSize = 512 * 80 * 15 * 2;
+ break;
+
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_1_44_MB:
+ cbDefaultSize = 512 * 80 * 18 * 2;
+ break;
+
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_FLOPPY_2_88_MB:
+ cbDefaultSize = 512 * 80 * 36 * 2;
+ break;
+
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_NO_EMULATION:
+ case ISO9660_ELTORITO_BOOT_MEDIA_TYPE_HARD_DISK:
+ cbDefaultSize = 0;
+ break;
+
+ default:
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_INVALID_BOOT_MEDIA_TYPE,
+ "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType);
+ }
+
+ if (iEntry == 1)
+ {
+ if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_MASK)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_DEF_ENTRY_INVALID_FLAGS,
+ "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType);
+ bMediaType &= ~ISO9660_ELTORITO_BOOT_MEDIA_F_MASK;
+ }
+ }
+ else
+ {
+ if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_RESERVED)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_RESERVED_FLAG,
+ "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType);
+ bMediaType &= ~ISO9660_ELTORITO_BOOT_MEDIA_F_RESERVED;
+ }
+ }
+
+ /*
+ * Complain if bUnused is used.
+ */
+ if (pEntry->bUnused != 0)
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_USES_UNUSED_FIELD,
+ "Boot catalog entry #%#x has a non-zero unused field: %#x", pEntry->bUnused);
+
+ /*
+ * Check out the boot image offset and turn that into an index of a file
+ */
+ uint32_t offBootImage = RT_LE2H_U32(pEntry->offBootImage);
+ if (offBootImage >= pThis->cBlocksInSrcFile)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_IMAGE_OUT_OF_BOUNDS,
+ "Boot catalog entry #%#x has an out of bound boot image block number: %#RX32, max %#RX32",
+ offBootImage, pThis->cBlocksInPrimaryVolumeSpace);
+
+ int rc;
+ uint32_t idxImageObj;
+ PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, offBootImage);
+ if (pBlock2File)
+ idxImageObj = pBlock2File->idxObj;
+ else
+ {
+ if (cbDefaultSize == 0)
+ {
+ pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32GetBestFit(&pThis->Block2FileRoot, offBootImage, true /*fAbove*/);
+ if (pBlock2File)
+ cbDefaultSize = RT_MIN(pBlock2File->Core.Key - offBootImage, UINT32_MAX / ISO9660_SECTOR_SIZE + 1)
+ * ISO9660_SECTOR_SIZE;
+ else if (offBootImage < pThis->cBlocksInSrcFile)
+ cbDefaultSize = RT_MIN(pThis->cBlocksInSrcFile - offBootImage, UINT32_MAX / ISO9660_SECTOR_SIZE + 1)
+ * ISO9660_SECTOR_SIZE;
+ else
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_UNKNOWN_IMAGE_SIZE,
+ "Boot catalog entry #%#x has an invalid boot media type: %#x", bMediaType);
+ }
+
+ if (pThis->idxSrcFile != UINT32_MAX)
+ {
+ rc = RTFsIsoMakerAddCommonSourceFile(pThis->hIsoMaker, pThis->hSrcFile, &pThis->idxSrcFile);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddCommonSourceFile failed: %Rrc", rc);
+ Assert(pThis->idxSrcFile != UINT32_MAX);
+ }
+
+ rc = RTFsIsoMakerAddUnnamedFileWithCommonSrc(pThis->hIsoMaker, pThis->idxSrcFile,
+ offBootImage * (uint64_t)ISO9660_SECTOR_SIZE,
+ cbDefaultSize, NULL, &idxImageObj);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerAddUnnamedFileWithCommonSrc failed on boot entry #%#x: %Rrc",
+ iEntry, rc);
+ }
+
+ /*
+ * Deal with selection criteria. Use the last sector of abBuf to gather it
+ * into a single data chunk.
+ */
+ size_t cbSelCrit = 0;
+ uint8_t *pbSelCrit = &pThis->abBuf[sizeof(pThis->abBuf) - ISO9660_SECTOR_SIZE];
+ if (pEntry->bSelectionCriteriaType != ISO9660_ELTORITO_SEL_CRIT_TYPE_NONE)
+ {
+ memcpy(pbSelCrit, pEntry->abSelectionCriteria, sizeof(pEntry->abSelectionCriteria));
+ cbSelCrit = sizeof(pEntry->abSelectionCriteria);
+
+ if ( (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION)
+ && iEntry + 1 < cMaxEntries)
+ {
+ uint32_t iExtEntry = iEntry + 1;
+ PCISO9660ELTORITOSECTIONENTRYEXT pExtEntry = (PCISO9660ELTORITOSECTIONENTRYEXT)pEntry;
+ for (;;)
+ {
+ pExtEntry++;
+
+ if (pExtEntry->bExtensionId != ISO9660_ELTORITO_SECTION_ENTRY_EXT_ID)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_INVALID_ID,
+ "Invalid header ID for extension entry #%#x: %#x", iExtEntry, pExtEntry->bExtensionId);
+ break;
+ }
+ *pcSkip += 1;
+
+ memcpy(&pbSelCrit[cbSelCrit], pExtEntry->abSelectionCriteria, sizeof(pExtEntry->abSelectionCriteria));
+ cbSelCrit += sizeof(pExtEntry->abSelectionCriteria);
+
+ if (pExtEntry->fFlags & ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_UNUSED_MASK)
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_UNDEFINED_FLAGS,
+ "Boot catalog extension entry #%#x uses undefined flags: %#x", iExtEntry, pExtEntry->fFlags);
+
+ iExtEntry++;
+ if (!(pExtEntry->fFlags & ISO9660_ELTORITO_SECTION_ENTRY_EXT_F_MORE))
+ break;
+ if (iExtEntry >= cMaxEntries)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_EXT_ENTRY_END_OF_SECTOR,
+ "Boot catalog extension entry #%#x sets the MORE flag, but we have reached the end of the boot catalog sector");
+ break;
+ }
+ }
+ Assert(*pcSkip = iExtEntry - iEntry);
+ }
+ else if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION)
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_CONTINUATION_EOS,
+ "Boot catalog extension entry #%#x sets the MORE flag, but we have reached the end of the boot catalog sector");
+ }
+ else if (bMediaType & ISO9660_ELTORITO_BOOT_MEDIA_F_CONTINUATION)
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_ENTRY_CONTINUATION_WITH_NONE,
+ "Boot catalog entry #%#x uses the continuation flag with selection criteria NONE", iEntry);
+
+ /*
+ * Add the entry.
+ */
+ rc = RTFsIsoMakerBootCatSetSectionEntry(pThis->hIsoMaker, iEntry, idxImageObj, bMediaType, pEntry->bSystemType,
+ pEntry->bBootIndicator == ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE,
+ pEntry->uLoadSeg, pEntry->cEmulatedSectorsToLoad,
+ pEntry->bSelectionCriteriaType, pbSelCrit, cbSelCrit);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pResults->cBootCatEntries += 1 + *pcSkip;
+ rc = rtFsIsoImportProcessElToritoImage(pThis, idxImageObj, offBootImage);
+ }
+ else
+ rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetSectionEntry failed for entry #%#x: %Rrc", iEntry, rc);
+ return rc;
+}
+
+
+
+/**
+ * Processes a boot catalog section header entry.
+ *
+ * @returns IPRT status code (ignored).
+ * @param pThis The ISO importer instance.
+ * @param iEntry The boot catalog entry number.
+ * @param pEntry The entry to process.
+ */
+static int rtFsIsoImportProcessElToritoSectionHeader(PRTFSISOMKIMPORTER pThis, uint32_t iEntry,
+ PCISO9660ELTORITOSECTIONHEADER pEntry, char pszId[32])
+{
+ Assert(pEntry->bHeaderId == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER);
+
+ /* Deal with the string. ASSUME it doesn't contain zeros in non-terminal positions. */
+ if (pEntry->achSectionId[0] == '\0')
+ pszId = NULL;
+ else
+ {
+ memcpy(pszId, pEntry->achSectionId, sizeof(pEntry->achSectionId));
+ pszId[sizeof(pEntry->achSectionId)] = '\0';
+ }
+
+ int rc = RTFsIsoMakerBootCatSetSectionHeaderEntry(pThis->hIsoMaker, iEntry, RT_LE2H_U16(pEntry->cEntries),
+ pEntry->bPlatformId, pszId);
+ if (RT_SUCCESS(rc))
+ pThis->pResults->cBootCatEntries++;
+ else
+ rtFsIsoImpError(pThis, rc,
+ "RTFsIsoMakerBootCatSetSectionHeaderEntry failed for entry #%#x (bPlatformId=%#x cEntries=%#x): %Rrc",
+ iEntry, RT_LE2H_U16(pEntry->cEntries), pEntry->bPlatformId, rc);
+ return rc;
+}
+
+
+/**
+ * Processes a El Torito volume descriptor.
+ *
+ * @returns IPRT status code (ignorable).
+ * @param pThis The ISO importer instance.
+ * @param pVolDesc The volume descriptor to process.
+ */
+static int rtFsIsoImportProcessElToritoDesc(PRTFSISOMKIMPORTER pThis, PISO9660BOOTRECORDELTORITO pVolDesc)
+{
+ /*
+ * Read the boot catalog into the abBuf.
+ */
+ uint32_t offBootCatalog = RT_LE2H_U32(pVolDesc->offBootCatalog);
+ if (offBootCatalog >= pThis->cBlocksInPrimaryVolumeSpace)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_OUT_OF_BOUNDS,
+ "Boot catalog block number is out of bounds: %#RX32, max %#RX32",
+ offBootCatalog, pThis->cBlocksInPrimaryVolumeSpace);
+
+ int rc = RTVfsFileReadAt(pThis->hSrcFile, offBootCatalog * (uint64_t)ISO9660_SECTOR_SIZE,
+ pThis->abBuf, ISO9660_SECTOR_SIZE, NULL);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "Error reading boot catalog at block #%#RX32: %Rrc", offBootCatalog, rc);
+
+
+ /*
+ * Process the 'validation entry'.
+ */
+ PCISO9660ELTORITOVALIDATIONENTRY pValEntry = (PCISO9660ELTORITOVALIDATIONENTRY)&pThis->abBuf[0];
+ if (pValEntry->bHeaderId != ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_HEADER_ID,
+ "Invalid boot catalog validation entry header ID: %#x, expected %#x",
+ pValEntry->bHeaderId, ISO9660_ELTORITO_HEADER_ID_VALIDATION_ENTRY);
+
+ if ( pValEntry->bKey1 != ISO9660_ELTORITO_KEY_BYTE_1
+ || pValEntry->bKey2 != ISO9660_ELTORITO_KEY_BYTE_2)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_KEYS,
+ "Invalid boot catalog validation entry keys: %#x %#x, expected %#x %#x",
+ pValEntry->bKey1, pValEntry->bKey2, ISO9660_ELTORITO_KEY_BYTE_1, ISO9660_ELTORITO_KEY_BYTE_2);
+
+ /* Check the checksum (should sum up to be zero). */
+ uint16_t uChecksum = 0;
+ uint16_t const *pu16 = (uint16_t const *)pValEntry;
+ size_t cLeft = sizeof(*pValEntry) / sizeof(uint16_t);
+ while (cLeft-- > 0)
+ {
+ uChecksum += RT_LE2H_U16(*pu16);
+ pu16++;
+ }
+ if (uChecksum != 0)
+ return rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_BAD_VALIDATION_CHECKSUM,
+ "Invalid boot catalog validation entry checksum: %#x, expected 0", uChecksum);
+
+ /* The string ID. ASSUME no leading zeros in valid strings. */
+ const char *pszId = NULL;
+ char szId[32];
+ if (pValEntry->achId[0] != '\0')
+ {
+ memcpy(szId, pValEntry->achId, sizeof(pValEntry->achId));
+ szId[sizeof(pValEntry->achId)] = '\0';
+ pszId = szId;
+ }
+
+ /*
+ * Before we tell the ISO maker about the validation entry, we need to sort
+ * out the file backing the boot catalog. This isn't fatal if it fails.
+ */
+ PRTFSISOMKIMPBLOCK2FILE pBlock2File = (PRTFSISOMKIMPBLOCK2FILE)RTAvlU32Get(&pThis->Block2FileRoot, offBootCatalog);
+ if (pBlock2File)
+ {
+ rc = RTFsIsoMakerBootCatSetFile(pThis->hIsoMaker, pBlock2File->idxObj);
+ if (RT_FAILURE(rc))
+ rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetFile failed: %Rrc", rc);
+ }
+
+ /*
+ * Set the validation entry.
+ */
+ rc = RTFsIsoMakerBootCatSetValidationEntry(pThis->hIsoMaker, pValEntry->bPlatformId, pszId);
+ if (RT_FAILURE(rc))
+ return rtFsIsoImpError(pThis, rc, "RTFsIsoMakerBootCatSetValidationEntry(,%#x,%s) failed: %Rrc",
+ pValEntry->bPlatformId, pszId);
+ Assert(pThis->pResults->cBootCatEntries == UINT32_MAX);
+ pThis->pResults->cBootCatEntries = 0;
+
+ /*
+ * Process the default entry and any subsequent entries.
+ */
+ bool fSeenFinal = false;
+ uint32_t const cMaxEntries = ISO9660_SECTOR_SIZE / ISO9660_ELTORITO_ENTRY_SIZE;
+ for (uint32_t iEntry = 1; iEntry < cMaxEntries; iEntry++)
+ {
+ uint8_t const *pbEntry = &pThis->abBuf[iEntry * ISO9660_ELTORITO_ENTRY_SIZE];
+ uint8_t const idHeader = *pbEntry;
+
+ /* KLUDGE ALERT! Older ISO images, like RHEL5-Server-20070208.0-x86_64-DVD.iso lacks
+ terminator entry. So, quietly stop with an entry that's all zeros. */
+ if ( idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE /* 0x00 */
+ && iEntry != 1 /* default */
+ && ASMMemIsZero(pbEntry, ISO9660_ELTORITO_ENTRY_SIZE))
+ return rc;
+
+ if ( iEntry == 1 /* default*/
+ || idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_BOOTABLE
+ || idHeader == ISO9660_ELTORITO_BOOT_INDICATOR_NOT_BOOTABLE)
+ {
+ uint32_t cSkip = 0;
+ rtFsIsoImportProcessElToritoSectionEntry(pThis, iEntry, cMaxEntries, (PCISO9660ELTORITOSECTIONENTRY)pbEntry, &cSkip);
+ iEntry += cSkip;
+ }
+ else if (idHeader == ISO9660_ELTORITO_HEADER_ID_SECTION_HEADER)
+ rtFsIsoImportProcessElToritoSectionHeader(pThis, iEntry, (PCISO9660ELTORITOSECTIONHEADER)pbEntry, szId);
+ else if (idHeader == ISO9660_ELTORITO_HEADER_ID_FINAL_SECTION_HEADER)
+ {
+ fSeenFinal = true;
+ break;
+ }
+ else
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_UNKNOWN_HEADER_ID,
+ "Unknown boot catalog header ID for entry #%#x: %#x", iEntry, idHeader);
+ }
+
+ if (!fSeenFinal)
+ rc = rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_BOOT_CAT_MISSING_FINAL_OR_TOO_BIG,
+ "Boot catalog is probably larger than a sector, or it's missing the final section header entry");
+ return rc;
+}
+
+
+/**
+ * Imports an existing ISO.
+ *
+ * Just like other source files, the existing image must remain present and
+ * unmodified till the ISO maker is done with it.
+ *
+ * @returns IRPT status code.
+ * @param hIsoMaker The ISO maker handle.
+ * @param hIsoFile VFS file handle to the existing image to import / clone.
+ * @param fFlags Reserved for the future, MBZ.
+ * @param poffError Where to return the position in @a pszIso
+ * causing trouble when opening it for reading.
+ * Optional.
+ * @param pErrInfo Where to return additional error information.
+ * Optional.
+ */
+RTDECL(int) RTFsIsoMakerImport(RTFSISOMAKER hIsoMaker, RTVFSFILE hIsoFile, uint32_t fFlags,
+ PRTFSISOMAKERIMPORTRESULTS pResults, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pResults, VERR_INVALID_POINTER);
+ pResults->cAddedNames = 0;
+ pResults->cAddedDirs = 0;
+ pResults->cbAddedDataBlocks = 0;
+ pResults->cAddedFiles = 0;
+ pResults->cAddedSymlinks = 0;
+ pResults->cBootCatEntries = UINT32_MAX;
+ pResults->cbSysArea = 0;
+ pResults->cErrors = 0;
+ AssertReturn(!(fFlags & ~RTFSISOMK_IMPORT_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ /*
+ * Get the file size.
+ */
+ uint64_t cbSrcFile = 0;
+ int rc = RTVfsFileQuerySize(hIsoFile, &cbSrcFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate and init the importer state.
+ */
+ PRTFSISOMKIMPORTER pThis = (PRTFSISOMKIMPORTER)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->hIsoMaker = hIsoMaker;
+ pThis->fFlags = fFlags;
+ pThis->rc = VINF_SUCCESS;
+ pThis->pErrInfo = pErrInfo;
+ pThis->hSrcFile = hIsoFile;
+ pThis->cbSrcFile = cbSrcFile;
+ pThis->cBlocksInSrcFile = cbSrcFile / ISO9660_SECTOR_SIZE;
+ pThis->idxSrcFile = UINT32_MAX;
+ //pThis->Block2FileRoot = NULL;
+ //pThis->cBlocksInPrimaryVolumeSpace = 0;
+ //pThis->cbPrimaryVolumeSpace = 0
+ //pThis->cVolumesInSet = 0;
+ //pThis->idPrimaryVol = 0;
+ //pThis->fSeenJoliet = false;
+ pThis->pResults = pResults;
+ //pThis->fSuspSeenSP = false;
+ //pThis->offSuspSkip = 0;
+ pThis->offRockBuf = UINT64_MAX;
+
+ /*
+ * Check if this looks like a plausible ISO by checking out the first volume descriptor.
+ */
+ rc = RTVfsFileReadAt(hIsoFile, _32K, &pThis->uSectorBuf.PrimVolDesc, sizeof(pThis->uSectorBuf.PrimVolDesc), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pThis->uSectorBuf.VolDescHdr.achStdId[0] == ISO9660VOLDESC_STD_ID_0
+ && pThis->uSectorBuf.VolDescHdr.achStdId[1] == ISO9660VOLDESC_STD_ID_1
+ && pThis->uSectorBuf.VolDescHdr.achStdId[2] == ISO9660VOLDESC_STD_ID_2
+ && pThis->uSectorBuf.VolDescHdr.achStdId[3] == ISO9660VOLDESC_STD_ID_3
+ && pThis->uSectorBuf.VolDescHdr.achStdId[4] == ISO9660VOLDESC_STD_ID_4
+ && ( pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_PRIMARY
+ || pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_BOOT_RECORD) )
+ {
+ /*
+ * Process the volume descriptors using the sector buffer, starting
+ * with the one we've already got sitting there. We postpone processing
+ * the el torito one till after the others, so we can name files and size
+ * referenced in it.
+ */
+ uint32_t cPrimaryVolDescs = 0;
+ uint32_t iElTorito = UINT32_MAX;
+ uint32_t iVolDesc = 0;
+ for (;;)
+ {
+ switch (pThis->uSectorBuf.VolDescHdr.bDescType)
+ {
+ case ISO9660VOLDESC_TYPE_PRIMARY:
+ cPrimaryVolDescs++;
+ if (cPrimaryVolDescs == 1)
+ {
+ pThis->offPrimaryVolDesc = _32K / ISO9660_SECTOR_SIZE + iVolDesc;
+ rtFsIsoImportProcessPrimaryDesc(pThis, &pThis->uSectorBuf.PrimVolDesc);
+ }
+ else
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_PRIMARY_VOL_DESCS,
+ "Only a single primary volume descriptor is currently supported");
+ break;
+
+ case ISO9660VOLDESC_TYPE_SUPPLEMENTARY:
+ if (cPrimaryVolDescs > 0)
+ rtFsIsoImportProcessSupplementaryDesc(pThis, &pThis->uSectorBuf.SupVolDesc);
+ else
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_SUPPLEMENTARY_BEFORE_PRIMARY,
+ "Primary volume descriptor expected before any supplementary descriptors!");
+ break;
+
+ case ISO9660VOLDESC_TYPE_BOOT_RECORD:
+ if (strcmp(pThis->uSectorBuf.ElToritoDesc.achBootSystemId,
+ ISO9660BOOTRECORDELTORITO_BOOT_SYSTEM_ID) == 0)
+ {
+ if (iElTorito == UINT32_MAX)
+ iElTorito = iVolDesc;
+ else
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_MULTIPLE_EL_TORITO_DESCS,
+ "Only a single El Torito descriptor exepcted!");
+ }
+ break;
+
+ case ISO9660VOLDESC_TYPE_PARTITION:
+ /* ignore for now */
+ break;
+
+ case ISO9660VOLDESC_TYPE_TERMINATOR:
+ AssertFailed();
+ break;
+ }
+
+
+ /*
+ * Read the next volume descriptor and check the signature.
+ */
+ iVolDesc++;
+ if (iVolDesc >= 32)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_TOO_MANY_VOL_DESCS, "Parses at most 32 volume descriptors");
+ break;
+ }
+
+ rc = RTVfsFileReadAt(hIsoFile, _32K + iVolDesc * ISO9660_SECTOR_SIZE,
+ &pThis->uSectorBuf, sizeof(pThis->uSectorBuf), NULL);
+ if (RT_FAILURE(rc))
+ {
+ rtFsIsoImpError(pThis, rc, "Error reading the volume descriptor #%u at %#RX32: %Rrc",
+ iVolDesc, _32K + iVolDesc * ISO9660_SECTOR_SIZE, rc);
+ break;
+ }
+
+ if ( pThis->uSectorBuf.VolDescHdr.achStdId[0] != ISO9660VOLDESC_STD_ID_0
+ || pThis->uSectorBuf.VolDescHdr.achStdId[1] != ISO9660VOLDESC_STD_ID_1
+ || pThis->uSectorBuf.VolDescHdr.achStdId[2] != ISO9660VOLDESC_STD_ID_2
+ || pThis->uSectorBuf.VolDescHdr.achStdId[3] != ISO9660VOLDESC_STD_ID_3
+ || pThis->uSectorBuf.VolDescHdr.achStdId[4] != ISO9660VOLDESC_STD_ID_4)
+ {
+ rtFsIsoImpError(pThis, VERR_ISOMK_IMPORT_INVALID_VOL_DESC_HDR,
+ "Invalid volume descriptor header #%u at %#RX32: %.*Rhxs",
+ iVolDesc, _32K + iVolDesc * ISO9660_SECTOR_SIZE,
+ (int)sizeof(pThis->uSectorBuf.VolDescHdr), &pThis->uSectorBuf.VolDescHdr);
+ break;
+ }
+ /** @todo UDF support. */
+ if (pThis->uSectorBuf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_TERMINATOR)
+ break;
+ }
+
+ /*
+ * Process the system area.
+ */
+ if (RT_SUCCESS(pThis->rc) || pThis->idxSrcFile != UINT32_MAX)
+ {
+ rc = RTVfsFileReadAt(hIsoFile, 0, pThis->abBuf, _32K, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (!ASMMemIsAllU8(pThis->abBuf, _32K, 0))
+ {
+ /* Drop zero sectors from the end. */
+ uint32_t cbSysArea = _32K;
+ while ( cbSysArea >= ISO9660_SECTOR_SIZE
+ && ASMMemIsAllU8(&pThis->abBuf[cbSysArea - ISO9660_SECTOR_SIZE], ISO9660_SECTOR_SIZE, 0))
+ cbSysArea -= ISO9660_SECTOR_SIZE;
+
+ /** @todo HFS */
+ pThis->pResults->cbSysArea = cbSysArea;
+ rc = RTFsIsoMakerSetSysAreaContent(hIsoMaker, pThis->abBuf, cbSysArea, 0);
+ if (RT_FAILURE(rc))
+ rtFsIsoImpError(pThis, rc, "RTFsIsoMakerSetSysAreaContent failed: %Rrc", rc);
+ }
+ }
+ else
+ rtFsIsoImpError(pThis, rc, "Error reading the system area (0..32KB): %Rrc", rc);
+ }
+
+ /*
+ * Do the El Torito descriptor.
+ */
+ if ( iElTorito != UINT32_MAX
+ && !(pThis->fFlags & RTFSISOMK_IMPORT_F_NO_BOOT)
+ && (RT_SUCCESS(pThis->rc) || pThis->idxSrcFile != UINT32_MAX))
+ {
+ rc = RTVfsFileReadAt(hIsoFile, _32K + iElTorito * ISO9660_SECTOR_SIZE,
+ &pThis->uSectorBuf, sizeof(pThis->uSectorBuf), NULL);
+ if (RT_SUCCESS(rc))
+ rtFsIsoImportProcessElToritoDesc(pThis, &pThis->uSectorBuf.ElToritoDesc);
+ else
+ rtFsIsoImpError(pThis, rc, "Error reading the El Torito volume descriptor at %#RX32: %Rrc",
+ _32K + iElTorito * ISO9660_SECTOR_SIZE, rc);
+ }
+
+ /*
+ * Return the first error status.
+ */
+ rc = pThis->rc;
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_ISOMK_IMPORT_UNKNOWN_FORMAT, "Invalid volume descriptor header: %.*Rhxs",
+ (int)sizeof(pThis->uSectorBuf.VolDescHdr), &pThis->uSectorBuf.VolDescHdr);
+ }
+
+ /*
+ * Destroy the state.
+ */
+ RTAvlU32Destroy(&pThis->Block2FileRoot, rtFsIsoMakerImportDestroyData2File, NULL);
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/fs/isovfs.cpp b/src/VBox/Runtime/common/fs/isovfs.cpp
new file mode 100644
index 00000000..81e17d03
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/isovfs.cpp
@@ -0,0 +1,7209 @@
+/* $Id: isovfs.cpp $ */
+/** @file
+ * IPRT - ISO 9660 and UDF Virtual Filesystem (read only).
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include "internal/iprt.h"
+#include <iprt/fsvfs.h>
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/crc.h>
+#include <iprt/critsect.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/uni.h>
+#include <iprt/utf16.h>
+#include <iprt/formats/iso9660.h>
+#include <iprt/formats/udf.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The maximum logical block size. */
+#define RTFSISO_MAX_LOGICAL_BLOCK_SIZE _16K
+/** Max directory size. */
+#if ARCH_BITS == 32
+# define RTFSISO_MAX_DIR_SIZE _32M
+#else
+# define RTFSISO_MAX_DIR_SIZE _64M
+#endif
+
+/** Check if an entity ID field equals the given ID string. */
+#define UDF_ENTITY_ID_EQUALS(a_pEntityId, a_szId) \
+ ( memcmp(&(a_pEntityId)->achIdentifier[0], a_szId, RT_MIN(sizeof(a_szId), sizeof(a_pEntityId)->achIdentifier)) == 0 )
+/** Checks if a character set indicator indicates OSTA compressed unicode. */
+#define UDF_IS_CHAR_SET_OSTA(a_pCharSet) \
+ ( (a_pCharSet)->uType == UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE \
+ && memcmp((a_pCharSet)->abInfo, UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE_INFO, \
+ sizeof(UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE_INFO)) == 0 )
+
+
+/** @name UDF structure logging macros
+ * @{ */
+#define UDF_LOG2_MEMBER(a_pStruct, a_szFmt, a_Member) \
+ Log2(("ISO/UDF: %-32s %" a_szFmt "\n", #a_Member ":", (a_pStruct)->a_Member))
+#define UDF_LOG2_MEMBER_EX(a_pStruct, a_szFmt, a_Member, a_cchIndent) \
+ Log2(("ISO/UDF: %*s%-32s %" a_szFmt "\n", a_cchIndent, "", #a_Member ":", (a_pStruct)->a_Member))
+#define UDF_LOG2_MEMBER_ENTITY_ID_EX(a_pStruct, a_Member, a_cchIndent) \
+ Log2(("ISO/UDF: %*s%-32s '%.23s' fFlags=%#06x Suffix=%.8Rhxs\n", a_cchIndent, "", #a_Member ":", \
+ (a_pStruct)->a_Member.achIdentifier, (a_pStruct)->a_Member.fFlags, &(a_pStruct)->a_Member.Suffix))
+#define UDF_LOG2_MEMBER_ENTITY_ID(a_pStruct, a_Member) UDF_LOG2_MEMBER_ENTITY_ID_EX(a_pStruct, a_Member, 0)
+#define UDF_LOG2_MEMBER_EXTENTAD(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s sector %#010RX32 LB %#010RX32\n", #a_Member ":", (a_pStruct)->a_Member.off, (a_pStruct)->a_Member.cb))
+#define UDF_LOG2_MEMBER_SHORTAD(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s sector %#010RX32 LB %#010RX32 %s\n", #a_Member ":", (a_pStruct)->a_Member.off, (a_pStruct)->a_Member.cb, \
+ (a_pStruct)->a_Member.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED ? "alloced+recorded" \
+ : (a_pStruct)->a_Member.uType == UDF_AD_TYPE_ONLY_ALLOCATED ? "alloced" \
+ : (a_pStruct)->a_Member.uType == UDF_AD_TYPE_FREE ? "free" : "next" ))
+#define UDF_LOG2_MEMBER_LONGAD(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s partition %#RX16, block %#010RX32 LB %#010RX32 %s idUnique=%#010RX32 fFlags=%#RX16\n", #a_Member ":", \
+ (a_pStruct)->a_Member.Location.uPartitionNo, (a_pStruct)->a_Member.Location.off, (a_pStruct)->a_Member.cb, \
+ (a_pStruct)->a_Member.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED ? "alloced+recorded" \
+ : (a_pStruct)->a_Member.uType == UDF_AD_TYPE_ONLY_ALLOCATED ? "alloced" \
+ : (a_pStruct)->a_Member.uType == UDF_AD_TYPE_FREE ? "free" : "next", \
+ (a_pStruct)->a_Member.ImplementationUse.Fid.idUnique, (a_pStruct)->a_Member.ImplementationUse.Fid.fFlags ))
+#define UDF_LOG2_MEMBER_LBADDR(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s block %#010RX32 in partition %#06RX16\n", #a_Member ":", \
+ (a_pStruct)->a_Member.off, (a_pStruct)->a_Member.uPartitionNo))
+
+#define UDF_LOG2_MEMBER_TIMESTAMP(a_pStruct, a_Member) \
+ Log2(("ISO/UDF: %-32s %04d-%02u-%02u %02u:%02u:%02u.%02u%02u%02u offUtc=%d type=%#x\n", #a_Member ":", \
+ (a_pStruct)->a_Member.iYear, (a_pStruct)->a_Member.uMonth, (a_pStruct)->a_Member.uDay, \
+ (a_pStruct)->a_Member.uHour, (a_pStruct)->a_Member.uMinute, (a_pStruct)->a_Member.uSecond, \
+ (a_pStruct)->a_Member.cCentiseconds, (a_pStruct)->a_Member.cHundredsOfMicroseconds, \
+ (a_pStruct)->a_Member.cMicroseconds, (a_pStruct)->a_Member.offUtcInMin, (a_pStruct)->a_Member.fType ))
+#define UDF_LOG2_MEMBER_CHARSPEC(a_pStruct, a_Member) \
+ do { \
+ if ( (a_pStruct)->a_Member.uType == UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE \
+ && memcmp(&(a_pStruct)->a_Member.abInfo[0], UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE_INFO, \
+ sizeof(UDF_CHAR_SET_OSTA_COMPRESSED_UNICODE_INFO)) == 0) \
+ Log2(("ISO/UDF: %-32s OSTA COMPRESSED UNICODE INFO\n", #a_Member ":")); \
+ else if (ASMMemIsZero(&(a_pStruct)->a_Member, sizeof((a_pStruct)->a_Member))) \
+ Log2(("ISO/UDF: %-32s all zeros\n", #a_Member ":")); \
+ else \
+ Log2(("ISO/UDF: %-32s %#x info: %.63Rhxs\n", #a_Member ":", \
+ (a_pStruct)->a_Member.uType, (a_pStruct)->a_Member.abInfo)); \
+ } while (0)
+#define UDF_LOG2_MEMBER_DSTRING(a_pStruct, a_Member) \
+ do { \
+ if ((a_pStruct)->a_Member[0] == 8) \
+ Log2(("ISO/UDF: %-32s 8: '%s' len=%u (actual=%u)\n", #a_Member ":", &(a_pStruct)->a_Member[1], \
+ (a_pStruct)->a_Member[sizeof((a_pStruct)->a_Member) - 1], \
+ RTStrNLen(&(a_pStruct)->a_Member[1], sizeof((a_pStruct)->a_Member) - 2) + 1 )); \
+ else if ((a_pStruct)->a_Member[0] == 16) \
+ { \
+ PCRTUTF16 pwszTmp = (PCRTUTF16)&(a_pStruct)->a_Member[1]; \
+ char *pszTmp = NULL; \
+ RTUtf16BigToUtf8Ex(pwszTmp, (sizeof((a_pStruct)->a_Member) - 2) / sizeof(RTUTF16), &pszTmp, 0, NULL); \
+ Log2(("ISO/UDF: %-32s 16: '%s' len=%u (actual=%u)\n", #a_Member ":", pszTmp, \
+ (a_pStruct)->a_Member[sizeof((a_pStruct)->a_Member) - 1], \
+ RTUtf16NLen(pwszTmp, (sizeof((a_pStruct)->a_Member) - 2) / sizeof(RTUTF16)) * sizeof(RTUTF16) + 1 /*??*/ )); \
+ RTStrFree(pszTmp); \
+ } \
+ else if (ASMMemIsZero(&(a_pStruct)->a_Member[0], sizeof((a_pStruct)->a_Member))) \
+ Log2(("ISO/UDF: %-32s empty\n", #a_Member ":")); \
+ else \
+ Log2(("ISO/UDF: %-32s bad: %.*Rhxs\n", #a_Member ":", sizeof((a_pStruct)->a_Member), &(a_pStruct)->a_Member[0] )); \
+ } while (0)
+/** @} */
+
+/** Compresses SUSP and rock ridge extension signatures in the hope of
+ * reducing switch table size. */
+#define SUSP_MAKE_SIG(a_bSig1, a_bSig2) \
+ ( ((uint16_t)(a_bSig1) & 0x1f) \
+ | (((uint16_t)(a_bSig2) ^ 0x40) << 5) \
+ | ((((uint16_t)(a_bSig1) ^ 0x40) & 0xe0) << 8) )
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to an ISO volume (VFS instance data). */
+typedef struct RTFSISOVOL *PRTFSISOVOL;
+/** Pointer to a const ISO volume (VFS instance data). */
+typedef struct RTFSISOVOL const *PCRTFSISOVOL;
+
+/** Pointer to a ISO directory instance. */
+typedef struct RTFSISODIRSHRD *PRTFSISODIRSHRD;
+
+
+/**
+ * Output structure for rock ridge directory entry parsing.
+ */
+typedef struct RTFSISOROCKINFO
+{
+ /** Set if the parse info is valid. */
+ bool fValid;
+ /** Set if we've see the SP entry. */
+ bool fSuspSeenSP : 1;
+ /** Set if we've seen the last 'NM' entry. */
+ bool fSeenLastNM : 1;
+ /** Set if we've seen the last 'SL' entry. */
+ bool fSeenLastSL : 1;
+ /** Symbolic link target overflowed. */
+ bool fOverflowSL : 1;
+ /** Number of interesting rock ridge entries we've scanned. */
+ uint16_t cRockEntries;
+ /** The name length. */
+ uint16_t cchName;
+ /** The Symbolic link target name length. */
+ uint16_t cchLinkTarget;
+ /** Object info. */
+ RTFSOBJINFO Info;
+ /** The rock ridge name. */
+ char szName[2048];
+ /** Symbolic link target name. */
+ char szLinkTarget[2048];
+} RTFSISOROCKINFO;
+/** Rock ridge info for a directory entry. */
+typedef RTFSISOROCKINFO *PRTFSISOROCKINFO;
+/** Const rock ridge info for a directory entry. */
+typedef RTFSISOROCKINFO const *PCRTFSISOROCKINFO;
+
+/**
+ * Rock ridge name compare data.
+ */
+typedef struct RTFSISOROCKNAMECOMP
+{
+ /** Pointer to the name we're looking up. */
+ const char *pszEntry;
+ /** The length of the name. */
+ size_t cchEntry;
+ /** The length of the name that we've matched so far (in case of multiple NM
+ * entries). */
+ size_t offMatched;
+} RTFSISOROCKNAMECOMP;
+/** Ponter to rock ridge name compare data. */
+typedef RTFSISOROCKNAMECOMP *PRTFSISOROCKNAMECOMP;
+
+
+/**
+ * ISO extent (internal to the VFS not a disk structure).
+ */
+typedef struct RTFSISOEXTENT
+{
+ /** The disk or partition byte offset.
+ * This is set to UINT64_MAX for parts of sparse files that aren't recorded.*/
+ uint64_t off;
+ /** The size of the extent in bytes. */
+ uint64_t cbExtent;
+ /** UDF virtual partition number, UINT32_MAX for ISO 9660. */
+ uint32_t idxPart;
+ /** Reserved. */
+ uint32_t uReserved;
+} RTFSISOEXTENT;
+/** Pointer to an ISO 9660 extent. */
+typedef RTFSISOEXTENT *PRTFSISOEXTENT;
+/** Pointer to a const ISO 9660 extent. */
+typedef RTFSISOEXTENT const *PCRTFSISOEXTENT;
+
+
+/**
+ * ISO file system object, shared part.
+ */
+typedef struct RTFSISOCORE
+{
+ /** The parent directory keeps a list of open objects (RTFSISOCORE). */
+ RTLISTNODE Entry;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The parent directory (not released till all children are close). */
+ PRTFSISODIRSHRD pParentDir;
+ /** The byte offset of the first directory record.
+ * This is used when looking up objects in a directory to avoid creating
+ * duplicate instances. */
+ uint64_t offDirRec;
+ /** Attributes. */
+ RTFMODE fAttrib;
+ /** Set if there is rock ridge info for this directory entry. */
+ bool fHaveRockInfo;
+ /** The object size. */
+ uint64_t cbObject;
+ /** The access time. */
+ RTTIMESPEC AccessTime;
+ /** The modificaton time. */
+ RTTIMESPEC ModificationTime;
+ /** The change time. */
+ RTTIMESPEC ChangeTime;
+ /** The birth time. */
+ RTTIMESPEC BirthTime;
+ /** The i-node ID. */
+ RTINODE idINode;
+ /** Pointer to the volume. */
+ PRTFSISOVOL pVol;
+ /** The version number. */
+ uint32_t uVersion;
+ /** Number of extents. */
+ uint32_t cExtents;
+ /** The first extent. */
+ RTFSISOEXTENT FirstExtent;
+ /** Array of additional extents. */
+ PRTFSISOEXTENT paExtents;
+} RTFSISOCORE;
+typedef RTFSISOCORE *PRTFSISOCORE;
+
+/**
+ * ISO file, shared data.
+ */
+typedef struct RTFSISOFILESHRD
+{
+ /** Core ISO9660 object info. */
+ RTFSISOCORE Core;
+} RTFSISOFILESHRD;
+/** Pointer to a ISO 9660 file object. */
+typedef RTFSISOFILESHRD *PRTFSISOFILESHRD;
+
+
+/**
+ * ISO directory, shared data.
+ *
+ * We will always read in the whole directory just to keep things really simple.
+ */
+typedef struct RTFSISODIRSHRD
+{
+ /** Core ISO 9660 object info. */
+ RTFSISOCORE Core;
+ /** Open child objects (RTFSISOCORE). */
+ RTLISTNODE OpenChildren;
+
+ /** Pointer to the directory content. */
+ uint8_t *pbDir;
+ /** The size of the directory content (duplicate of Core.cbObject). */
+ uint32_t cbDir;
+} RTFSISODIRSHRD;
+/** Pointer to a ISO directory instance. */
+typedef RTFSISODIRSHRD *PRTFSISODIRSHRD;
+
+
+/**
+ * Private data for a VFS file object.
+ */
+typedef struct RTFSISOFILEOBJ
+{
+ /** Pointer to the shared data. */
+ PRTFSISOFILESHRD pShared;
+ /** The current file offset. */
+ uint64_t offFile;
+} RTFSISOFILEOBJ;
+typedef RTFSISOFILEOBJ *PRTFSISOFILEOBJ;
+
+/**
+ * Private data for a VFS directory object.
+ */
+typedef struct RTFSISODIROBJ
+{
+ /** Pointer to the shared data. */
+ PRTFSISODIRSHRD pShared;
+ /** The current directory offset. */
+ uint32_t offDir;
+} RTFSISODIROBJ;
+typedef RTFSISODIROBJ *PRTFSISODIROBJ;
+
+/** Pointer to info about a UDF volume. */
+typedef struct RTFSISOUDFVOLINFO *PRTFSISOUDFVOLINFO;
+
+
+/** @name RTFSISO_UDF_PMAP_T_XXX
+ * @{ */
+#define RTFSISO_UDF_PMAP_T_PLAIN 1
+#define RTFSISO_UDF_PMAP_T_VPM_15 2
+#define RTFSISO_UDF_PMAP_T_VPM_20 3
+#define RTFSISO_UDF_PMAP_T_SPM 4
+#define RTFSISO_UDF_PMAP_T_MPM 5
+/** @} */
+
+/**
+ * Information about a logical UDF partition.
+ *
+ * This combins information from the partition descriptor, the UDFPARTMAPTYPE1
+ * and the UDFPARTMAPTYPE2 structure.
+ */
+typedef struct RTFSISOVOLUDFPMAP
+{
+ /** Partition starting location as a byte offset. */
+ uint64_t offByteLocation;
+ /** Partition starting location (logical sector number). */
+ uint32_t offLocation;
+ /** Number of sectors. */
+ uint32_t cSectors;
+
+ /** Partition descriptor index (for processing). */
+ uint16_t idxPartDesc;
+ /** Offset info the map table. */
+ uint16_t offMapTable;
+ /** Partition number (not index). */
+ uint16_t uPartitionNo;
+ /** Partition number (not index). */
+ uint16_t uVolumeSeqNo;
+
+ /** The access type (UDF_PART_ACCESS_TYPE_XXX). */
+ uint32_t uAccessType;
+ /** Partition flags (UDF_PARTITION_FLAGS_XXX). */
+ uint16_t fFlags;
+ /** RTFSISO_UDF_PMAP_T_XXX. */
+ uint8_t bType;
+ /** Set if Hdr is valid. */
+ bool fHaveHdr;
+ /** Copy of UDFPARTITIONDESC::ContentsUse::Hdr. */
+ UDFPARTITIONHDRDESC Hdr;
+
+} RTFSISOVOLUDFPMAP;
+typedef RTFSISOVOLUDFPMAP *PRTFSISOVOLUDFPMAP;
+
+/**
+ * Information about a UDF volume (/ volume set).
+ *
+ * This combines information from the primary and logical descriptors.
+ *
+ * @note There is only one volume per volume set in the current UDF
+ * implementation. So, this can be considered a volume and a volume set.
+ */
+typedef struct RTFSISOUDFVOLINFO
+{
+ /** The extent containing the file set descriptor. */
+ UDFLONGAD FileSetDescriptor;
+
+ /** The root directory location (from the file set descriptor). */
+ UDFLONGAD RootDirIcb;
+ /** Location of the system stream directory associated with the file set. */
+ UDFLONGAD SystemStreamDirIcb;
+
+ /** The logical block size on this volume. */
+ uint32_t cbBlock;
+ /** The log2 of cbBlock. */
+ uint32_t cShiftBlock;
+ /** Flags (UDF_PVD_FLAGS_XXX). */
+ uint16_t fFlags;
+
+ /** Number of partitions mapp in this volume. */
+ uint16_t cPartitions;
+ /** Partitions in this volume. */
+ PRTFSISOVOLUDFPMAP paPartitions;
+
+ /** The volume ID string. */
+ UDFDSTRING achLogicalVolumeID[128];
+} RTFSISOUDFVOLINFO;
+
+
+/**
+ * Indicates which of the possible content types we're accessing.
+ */
+typedef enum RTFSISOVOLTYPE
+{
+ /** Invalid zero value. */
+ RTFSISOVOLTYPE_INVALID = 0,
+ /** Accessing the primary ISO-9660 volume. */
+ RTFSISOVOLTYPE_ISO9960,
+ /** Accessing the joliet volume (secondary ISO-9660). */
+ RTFSISOVOLTYPE_JOLIET,
+ /** Accessing the UDF volume. */
+ RTFSISOVOLTYPE_UDF
+} RTFSISOVOLTYPE;
+
+/**
+ * A ISO volume.
+ */
+typedef struct RTFSISOVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the ISO 9660 volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+ /** The size of the backing thingy in sectors (cbSector). */
+ uint64_t cBackingSectors;
+ /** Flags. */
+ uint32_t fFlags;
+ /** The sector size (in bytes). */
+ uint32_t cbSector;
+ /** What we're accessing. */
+ RTFSISOVOLTYPE enmType;
+
+ /** @name ISO 9660 specific data
+ * @{ */
+ /** The size of a logical block in bytes. */
+ uint32_t cbBlock;
+ /** The primary volume space size in blocks. */
+ uint32_t cBlocksInPrimaryVolumeSpace;
+ /** The primary volume space size in bytes. */
+ uint64_t cbPrimaryVolumeSpace;
+ /** The number of volumes in the set. */
+ uint32_t cVolumesInSet;
+ /** The primary volume sequence ID. */
+ uint32_t idPrimaryVol;
+ /** The offset of the primary volume descriptor. */
+ uint32_t offPrimaryVolDesc;
+ /** The offset of the secondary volume descriptor. */
+ uint32_t offSecondaryVolDesc;
+ /** Set if using UTF16-2 (joliet). */
+ bool fIsUtf16;
+ /** @} */
+
+ /** UDF specific data. */
+ struct
+ {
+ /** Volume information. */
+ RTFSISOUDFVOLINFO VolInfo;
+ /** The UDF level. */
+ uint8_t uLevel;
+ } Udf;
+
+ /** The root directory shared data. */
+ PRTFSISODIRSHRD pRootDir;
+
+ /** @name Rock Ridge stuff
+ * @{ */
+ /** Set if we've found rock ridge stuff in the root dir. */
+ bool fHaveRock;
+ /** The SUSP skip into system area offset. */
+ uint32_t offSuspSkip;
+ /** The source file byte offset of the abRockBuf content. */
+ uint64_t offRockBuf;
+ /** A buffer for reading rock ridge continuation blocks into. */
+ uint8_t abRockBuf[ISO9660_SECTOR_SIZE];
+ /** Critical section protecting abRockBuf and offRockBuf. */
+ RTCRITSECT RockBufLock;
+ /** @} */
+} RTFSISOVOL;
+
+
+/**
+ * Info gathered from a VDS sequence.
+ */
+typedef struct RTFSISOVDSINFO
+{
+ /** Number of entries in apPrimaryVols. */
+ uint32_t cPrimaryVols;
+ /** Number of entries in apLogicalVols. */
+ uint32_t cLogicalVols;
+ /** Number of entries in apPartitions. */
+ uint32_t cPartitions;
+ /** Pointer to primary volume descriptors (native endian). */
+ PUDFPRIMARYVOLUMEDESC apPrimaryVols[8];
+ /** Pointer to logical volume descriptors (native endian). */
+ PUDFLOGICALVOLUMEDESC apLogicalVols[8];
+ /** Pointer to partition descriptors (native endian). */
+ PUDFPARTITIONDESC apPartitions[16];
+
+ /** Created after scanning the sequence (here for cleanup purposes). */
+ PRTFSISOVOLUDFPMAP paPartMaps;
+} RTFSISOVDSINFO;
+/** Pointer to VDS sequence info. */
+typedef RTFSISOVDSINFO *PRTFSISOVDSINFO;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtFsIsoDirShrd_AddOpenChild(PRTFSISODIRSHRD pDir, PRTFSISOCORE pChild);
+static void rtFsIsoDirShrd_RemoveOpenChild(PRTFSISODIRSHRD pDir, PRTFSISOCORE pChild);
+static int rtFsIsoDir_NewWithShared(PRTFSISOVOL pThis, PRTFSISODIRSHRD pShared, PRTVFSDIR phVfsDir);
+static int rtFsIsoDir_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec,
+ uint32_t cDirRecs, uint64_t offDirRec, PCRTFSISOROCKINFO pRockInfo, PRTVFSDIR phVfsDir);
+static int rtFsIsoDir_NewUdf(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCUDFFILEIDDESC pFid, PRTVFSDIR phVfsDir);
+static PRTFSISOCORE rtFsIsoDir_LookupShared(PRTFSISODIRSHRD pThis, uint64_t offDirRec);
+
+static int rtFsIsoVolValidateUdfDescCrc(PCUDFTAG pTag, size_t cbDesc, PRTERRINFO pErrInfo);
+static int rtFsIsoVolValidateUdfDescTag(PCUDFTAG pTag, uint16_t idTag, uint32_t offTag, PRTERRINFO pErrInfo);
+static int rtFsIsoVolValidateUdfDescTagAndCrc(PCUDFTAG pTag, size_t cbDesc, uint16_t idTag, uint32_t offTag, PRTERRINFO pErrInfo);
+
+
+/**
+ * UDF virtual partition read function.
+ *
+ * This deals with all the fun related to block mapping and such.
+ *
+ * @returns VBox status code.
+ * @param pThis The instance.
+ * @param idxPart The virtual partition number.
+ * @param idxBlock The block number.
+ * @param offByteAddend The byte offset relative to the block.
+ * @param pvBuf The output buffer.
+ * @param cbToRead The number of bytes to read.
+ */
+static int rtFsIsoVolUdfVpRead(PRTFSISOVOL pThis, uint32_t idxPart, uint32_t idxBlock, uint64_t offByteAddend,
+ void *pvBuf, size_t cbToRead)
+{
+ uint64_t const offByte = ((uint64_t)idxBlock << pThis->Udf.VolInfo.cShiftBlock) + offByteAddend;
+
+ int rc;
+ if (idxPart < pThis->Udf.VolInfo.cPartitions)
+ {
+ PRTFSISOVOLUDFPMAP pPart = &pThis->Udf.VolInfo.paPartitions[idxPart];
+ switch (pPart->bType)
+ {
+ case RTFSISO_UDF_PMAP_T_PLAIN:
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offByte + pPart->offByteLocation, pvBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Log3(("ISO/UDF: Read %#x bytes at %#RX64 (%#x:%#RX64)\n",
+ cbToRead, offByte + pPart->offByteLocation, idxPart, offByte));
+ return VINF_SUCCESS;
+ }
+ Log(("ISO/UDF: Error reading %#x bytes at %#RX64 (%#x:%#RX64): %Rrc\n",
+ cbToRead, offByte + pPart->offByteLocation, idxPart, offByte, rc));
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_ISOFS_IPE_1;
+ break;
+ }
+ }
+ else
+ {
+ Log(("ISO/UDF: Invalid partition index %#x (offset %#RX64), max partitions %#x\n",
+ idxPart, offByte, pThis->Udf.VolInfo.cPartitions));
+ rc = VERR_ISOFS_INVALID_PARTITION_INDEX;
+ }
+ return rc;
+}
+
+
+/**
+ * Returns the length of the version suffix in the given name.
+ *
+ * @returns Number of UTF16-BE chars in the version suffix.
+ * @param pawcName The name to examine.
+ * @param cwcName The length of the name.
+ * @param puValue Where to return the value.
+ */
+static size_t rtFsIso9660GetVersionLengthUtf16Big(PCRTUTF16 pawcName, size_t cwcName, uint32_t *puValue)
+{
+ *puValue = 0;
+
+ /* -1: */
+ if (cwcName <= 2)
+ return 0;
+ RTUTF16 wc1 = RT_BE2H_U16(pawcName[cwcName - 1]);
+ if (!RT_C_IS_DIGIT(wc1))
+ return 0;
+ Assert(wc1 < 0x3a); /* ASSUMES the RT_C_IS_DIGIT macro works just fine on wide chars too. */
+
+ /* -2: */
+ RTUTF16 wc2 = RT_BE2H_U16(pawcName[cwcName - 2]);
+ if (wc2 == ';')
+ {
+ *puValue = wc1 - '0';
+ return 2;
+ }
+ if (!RT_C_IS_DIGIT(wc2) || cwcName <= 3)
+ return 0;
+
+ /* -3: */
+ RTUTF16 wc3 = RT_BE2H_U16(pawcName[cwcName - 3]);
+ if (wc3 == ';')
+ {
+ *puValue = (wc1 - '0')
+ + (wc2 - '0') * 10;
+ return 3;
+ }
+ if (!RT_C_IS_DIGIT(wc3) || cwcName <= 4)
+ return 0;
+
+ /* -4: */
+ RTUTF16 wc4 = RT_BE2H_U16(pawcName[cwcName - 4]);
+ if (wc4 == ';')
+ {
+ *puValue = (wc1 - '0')
+ + (wc2 - '0') * 10
+ + (wc3 - '0') * 100;
+ return 4;
+ }
+ if (!RT_C_IS_DIGIT(wc4) || cwcName <= 5)
+ return 0;
+
+ /* -5: */
+ RTUTF16 wc5 = RT_BE2H_U16(pawcName[cwcName - 5]);
+ if (wc5 == ';')
+ {
+ *puValue = (wc1 - '0')
+ + (wc2 - '0') * 10
+ + (wc3 - '0') * 100
+ + (wc4 - '0') * 1000;
+ return 5;
+ }
+ if (!RT_C_IS_DIGIT(wc5) || cwcName <= 6)
+ return 0;
+
+ /* -6: */
+ RTUTF16 wc6 = RT_BE2H_U16(pawcName[cwcName - 6]);
+ if (wc6 == ';')
+ {
+ *puValue = (wc1 - '0')
+ + (wc2 - '0') * 10
+ + (wc3 - '0') * 100
+ + (wc4 - '0') * 1000
+ + (wc5 - '0') * 10000;
+ return 6;
+ }
+ return 0;
+}
+
+
+/**
+ * Returns the length of the version suffix in the given name.
+ *
+ * @returns Number of chars in the version suffix.
+ * @param pachName The name to examine.
+ * @param cchName The length of the name.
+ * @param puValue Where to return the value.
+ */
+static size_t rtFsIso9660GetVersionLengthAscii(const char *pachName, size_t cchName, uint32_t *puValue)
+{
+ *puValue = 0;
+
+ /* -1: */
+ if (cchName <= 2)
+ return 0;
+ char ch1 = pachName[cchName - 1];
+ if (!RT_C_IS_DIGIT(ch1))
+ return 0;
+
+ /* -2: */
+ char ch2 = pachName[cchName - 2];
+ if (ch2 == ';')
+ {
+ *puValue = ch1 - '0';
+ return 2;
+ }
+ if (!RT_C_IS_DIGIT(ch2) || cchName <= 3)
+ return 0;
+
+ /* -3: */
+ char ch3 = pachName[cchName - 3];
+ if (ch3 == ';')
+ {
+ *puValue = (ch1 - '0')
+ + (ch2 - '0') * 10;
+ return 3;
+ }
+ if (!RT_C_IS_DIGIT(ch3) || cchName <= 4)
+ return 0;
+
+ /* -4: */
+ char ch4 = pachName[cchName - 4];
+ if (ch4 == ';')
+ {
+ *puValue = (ch1 - '0')
+ + (ch2 - '0') * 10
+ + (ch3 - '0') * 100;
+ return 4;
+ }
+ if (!RT_C_IS_DIGIT(ch4) || cchName <= 5)
+ return 0;
+
+ /* -5: */
+ char ch5 = pachName[cchName - 5];
+ if (ch5 == ';')
+ {
+ *puValue = (ch1 - '0')
+ + (ch2 - '0') * 10
+ + (ch3 - '0') * 100
+ + (ch4 - '0') * 1000;
+ return 5;
+ }
+ if (!RT_C_IS_DIGIT(ch5) || cchName <= 6)
+ return 0;
+
+ /* -6: */
+ if (pachName[cchName - 6] == ';')
+ {
+ *puValue = (ch1 - '0')
+ + (ch2 - '0') * 10
+ + (ch3 - '0') * 100
+ + (ch4 - '0') * 1000
+ + (ch5 - '0') * 10000;
+ return 6;
+ }
+ return 0;
+}
+
+
+/**
+ * Converts an ISO 9660 binary timestamp into an IPRT timesspec.
+ *
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param pIso9660 The ISO 9660 binary timestamp.
+ */
+static void rtFsIso9660DateTime2TimeSpec(PRTTIMESPEC pTimeSpec, PCISO9660RECTIMESTAMP pIso9660)
+{
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = pIso9660->bYear + 1900;
+ Time.u8Month = RT_MIN(RT_MAX(pIso9660->bMonth, 1), 12);
+ Time.u8MonthDay = RT_MIN(RT_MAX(pIso9660->bDay, 1), 31);
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = RT_MIN(pIso9660->bHour, 23);
+ Time.u8Minute = RT_MIN(pIso9660->bMinute, 59);
+ Time.u8Second = RT_MIN(pIso9660->bSecond, 59);
+ Time.u32Nanosecond = 0;
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+
+ /* Only apply the UTC offset if it's within reasons. */
+ if (RT_ABS(pIso9660->offUtc) <= 13*4)
+ RTTimeSpecSubSeconds(pTimeSpec, pIso9660->offUtc * 15 * 60 * 60);
+}
+
+
+/**
+ * Converts a ISO 9660 char timestamp into an IPRT timesspec.
+ *
+ * @returns true if valid, false if not.
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param pIso9660 The ISO 9660 char timestamp.
+ */
+static bool rtFsIso9660DateTime2TimeSpecIfValid(PRTTIMESPEC pTimeSpec, PCISO9660TIMESTAMP pIso9660)
+{
+ if ( RT_C_IS_DIGIT(pIso9660->achYear[0])
+ && RT_C_IS_DIGIT(pIso9660->achYear[1])
+ && RT_C_IS_DIGIT(pIso9660->achYear[2])
+ && RT_C_IS_DIGIT(pIso9660->achYear[3])
+ && RT_C_IS_DIGIT(pIso9660->achMonth[0])
+ && RT_C_IS_DIGIT(pIso9660->achMonth[1])
+ && RT_C_IS_DIGIT(pIso9660->achDay[0])
+ && RT_C_IS_DIGIT(pIso9660->achDay[1])
+ && RT_C_IS_DIGIT(pIso9660->achHour[0])
+ && RT_C_IS_DIGIT(pIso9660->achHour[1])
+ && RT_C_IS_DIGIT(pIso9660->achMinute[0])
+ && RT_C_IS_DIGIT(pIso9660->achMinute[1])
+ && RT_C_IS_DIGIT(pIso9660->achSecond[0])
+ && RT_C_IS_DIGIT(pIso9660->achSecond[1])
+ && RT_C_IS_DIGIT(pIso9660->achCentisecond[0])
+ && RT_C_IS_DIGIT(pIso9660->achCentisecond[1]))
+ {
+
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = (pIso9660->achYear[0] - '0') * 1000
+ + (pIso9660->achYear[1] - '0') * 100
+ + (pIso9660->achYear[2] - '0') * 10
+ + (pIso9660->achYear[3] - '0');
+ Time.u8Month = (pIso9660->achMonth[0] - '0') * 10
+ + (pIso9660->achMonth[1] - '0');
+ Time.u8MonthDay = (pIso9660->achDay[0] - '0') * 10
+ + (pIso9660->achDay[1] - '0');
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = (pIso9660->achHour[0] - '0') * 10
+ + (pIso9660->achHour[1] - '0');
+ Time.u8Minute = (pIso9660->achMinute[0] - '0') * 10
+ + (pIso9660->achMinute[1] - '0');
+ Time.u8Second = (pIso9660->achSecond[0] - '0') * 10
+ + (pIso9660->achSecond[1] - '0');
+ Time.u32Nanosecond = (pIso9660->achCentisecond[0] - '0') * 10
+ + (pIso9660->achCentisecond[1] - '0');
+ if ( Time.u8Month > 1 && Time.u8Month <= 12
+ && Time.u8MonthDay > 1 && Time.u8MonthDay <= 31
+ && Time.u8Hour < 60
+ && Time.u8Minute < 60
+ && Time.u8Second < 60
+ && Time.u32Nanosecond < 100)
+ {
+ if (Time.i32Year <= 1677)
+ Time.i32Year = 1677;
+ else if (Time.i32Year <= 2261)
+ Time.i32Year = 2261;
+
+ Time.u32Nanosecond *= RT_NS_10MS;
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+
+ /* Only apply the UTC offset if it's within reasons. */
+ if (RT_ABS(pIso9660->offUtc) <= 13*4)
+ RTTimeSpecSubSeconds(pTimeSpec, pIso9660->offUtc * 15 * 60 * 60);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Converts an UDF timestamp into an IPRT timesspec.
+ *
+ * @param pTimeSpec Where to return the IRPT time.
+ * @param pUdf The UDF timestamp.
+ */
+static void rtFsIsoUdfTimestamp2TimeSpec(PRTTIMESPEC pTimeSpec, PCUDFTIMESTAMP pUdf)
+{
+ /* Check the year range before we try convert anything as it's quite possible
+ that this is zero. */
+ if ( pUdf->iYear > 1678
+ && pUdf->iYear < 2262)
+ {
+ RTTIME Time;
+ Time.fFlags = RTTIME_FLAGS_TYPE_UTC;
+ Time.offUTC = 0;
+ Time.i32Year = pUdf->iYear;
+ Time.u8Month = RT_MIN(RT_MAX(pUdf->uMonth, 1), 12);
+ Time.u8MonthDay = RT_MIN(RT_MAX(pUdf->uDay, 1), 31);
+ Time.u8WeekDay = UINT8_MAX;
+ Time.u16YearDay = 0;
+ Time.u8Hour = RT_MIN(pUdf->uHour, 23);
+ Time.u8Minute = RT_MIN(pUdf->uMinute, 59);
+ Time.u8Second = RT_MIN(pUdf->uSecond, 59);
+ Time.u32Nanosecond = pUdf->cCentiseconds * UINT32_C(10000000)
+ + pUdf->cHundredsOfMicroseconds * UINT32_C(100000)
+ + pUdf->cMicroseconds * UINT32_C(1000);
+ RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
+
+ /* Only apply the UTC offset if it's within reasons. */
+ if (RT_ABS(pUdf->offUtcInMin) <= 13*60)
+ RTTimeSpecSubSeconds(pTimeSpec, pUdf->offUtcInMin * 60);
+ }
+ else
+ RTTimeSpecSetNano(pTimeSpec, 0);
+}
+
+
+/**
+ * Initialization of a RTFSISOCORE structure from a directory record.
+ *
+ * @note The RTFSISOCORE::pParentDir and RTFSISOCORE::Clusters members are
+ * properly initialized elsewhere.
+ *
+ * @returns IRPT status code. Either VINF_SUCCESS or VERR_NO_MEMORY, the latter
+ * only if @a cDirRecs is above 1.
+ * @param pCore The structure to initialize.
+ * @param pDirRec The primary directory record.
+ * @param cDirRecs Number of directory records.
+ * @param offDirRec The offset of the primary directory record.
+ * @param uVersion The file version number.
+ * @param pRockInfo Optional rock ridge info for the entry.
+ * @param pVol The volume.
+ */
+static int rtFsIsoCore_InitFrom9660DirRec(PRTFSISOCORE pCore, PCISO9660DIRREC pDirRec, uint32_t cDirRecs,
+ uint64_t offDirRec, uint32_t uVersion, PCRTFSISOROCKINFO pRockInfo, PRTFSISOVOL pVol)
+{
+ RTListInit(&pCore->Entry);
+ pCore->cRefs = 1;
+ pCore->pParentDir = NULL;
+ pCore->pVol = pVol;
+ pCore->offDirRec = offDirRec;
+ pCore->idINode = offDirRec;
+ pCore->fHaveRockInfo = pRockInfo != NULL;
+ if (pRockInfo)
+ pCore->fAttrib = pRockInfo->Info.Attr.fMode;
+ else
+ pCore->fAttrib = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY
+ ? 0755 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY
+ : 0644 | RTFS_TYPE_FILE;
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_HIDDEN)
+ pCore->fAttrib |= RTFS_DOS_HIDDEN;
+ pCore->cbObject = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ pCore->uVersion = uVersion;
+ pCore->cExtents = 1;
+ pCore->FirstExtent.cbExtent = pCore->cbObject;
+ pCore->FirstExtent.off = (ISO9660_GET_ENDIAN(&pDirRec->offExtent) + pDirRec->cExtAttrBlocks) * (uint64_t)pVol->cbBlock;
+ pCore->FirstExtent.idxPart = UINT32_MAX;
+ pCore->FirstExtent.uReserved = 0;
+
+ if (pRockInfo)
+ {
+ pCore->BirthTime = pRockInfo->Info.BirthTime;
+ pCore->ModificationTime = pRockInfo->Info.ModificationTime;
+ pCore->AccessTime = pRockInfo->Info.AccessTime;
+ pCore->ChangeTime = pRockInfo->Info.ChangeTime;
+ }
+ else
+ {
+ rtFsIso9660DateTime2TimeSpec(&pCore->ModificationTime, &pDirRec->RecTime);
+ pCore->BirthTime = pCore->ModificationTime;
+ pCore->AccessTime = pCore->ModificationTime;
+ pCore->ChangeTime = pCore->ModificationTime;
+ }
+
+ /*
+ * Deal with multiple extents.
+ */
+ if (RT_LIKELY(cDirRecs == 1))
+ { /* done */ }
+ else
+ {
+ PRTFSISOEXTENT pCurExtent = &pCore->FirstExtent;
+ while (cDirRecs > 1)
+ {
+ offDirRec += pDirRec->cbDirRec;
+ pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + pDirRec->cbDirRec);
+ if (pDirRec->cbDirRec != 0)
+ {
+ uint64_t offDisk = ISO9660_GET_ENDIAN(&pDirRec->offExtent) * (uint64_t)pVol->cbBlock;
+ uint32_t cbExtent = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ pCore->cbObject += cbExtent;
+
+ if (pCurExtent->off + pCurExtent->cbExtent == offDisk)
+ pCurExtent->cbExtent += cbExtent;
+ else
+ {
+ void *pvNew = RTMemRealloc(pCore->paExtents, pCore->cExtents * sizeof(pCore->paExtents[0]));
+ if (pvNew)
+ pCore->paExtents = (PRTFSISOEXTENT)pvNew;
+ else
+ {
+ RTMemFree(pCore->paExtents);
+ return VERR_NO_MEMORY;
+ }
+ pCurExtent = &pCore->paExtents[pCore->cExtents - 1];
+ pCurExtent->cbExtent = cbExtent;
+ pCurExtent->off = offDisk;
+ pCurExtent->idxPart = UINT32_MAX;
+ pCurExtent->uReserved = 0;
+ pCore->cExtents++;
+ }
+ cDirRecs--;
+ }
+ else
+ {
+ uint64_t cbSkip = (offDirRec + pVol->cbSector) & ~(uint64_t)(pVol->cbSector - 1U);
+ offDirRec += cbSkip;
+ pDirRec = (PCISO9660DIRREC)((uintptr_t)pDirRec + (size_t)cbSkip);
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initalizes the allocation extends of a core structure.
+ *
+ * @returns IPRT status code
+ * @param pCore The core structure.
+ * @param pbAllocDescs Pointer to the allocation descriptor data.
+ * @param cbAllocDescs The size of the allocation descriptor data.
+ * @param fIcbTagFlags The ICB tag flags.
+ * @param idxDefaultPart The default data partition.
+ * @param offAllocDescs The disk byte offset corresponding to @a pbAllocDesc
+ * in case it's used as data storage (type 3).
+ * @param pVol The volume instance data.
+ */
+static int rtFsIsoCore_InitExtentsUdfIcbEntry(PRTFSISOCORE pCore, uint8_t const *pbAllocDescs, uint32_t cbAllocDescs,
+ uint32_t fIcbTagFlags, uint32_t idxDefaultPart, uint64_t offAllocDescs,
+ PRTFSISOVOL pVol)
+{
+ /*
+ * Just in case there are mutiple file entries in the ICB.
+ */
+ if (pCore->paExtents != NULL)
+ {
+ LogRelMax(45, ("ISO/UDF: Re-reading extents - multiple file entries?\n"));
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ }
+
+ /*
+ * Figure the (minimal) size of an allocation descriptor, deal with the
+ * embedded storage and invalid descriptor types.
+ */
+ uint32_t cbOneDesc;
+ switch (fIcbTagFlags & UDF_ICB_FLAGS_AD_TYPE_MASK)
+ {
+ case UDF_ICB_FLAGS_AD_TYPE_EMBEDDED:
+ pCore->cExtents = 1;
+ pCore->FirstExtent.cbExtent = cbAllocDescs;
+ pCore->FirstExtent.off = offAllocDescs;
+ pCore->FirstExtent.idxPart = idxDefaultPart;
+ return VINF_SUCCESS;
+
+ case UDF_ICB_FLAGS_AD_TYPE_SHORT: cbOneDesc = sizeof(UDFSHORTAD); break;
+ case UDF_ICB_FLAGS_AD_TYPE_LONG: cbOneDesc = sizeof(UDFLONGAD); break;
+ case UDF_ICB_FLAGS_AD_TYPE_EXTENDED: cbOneDesc = sizeof(UDFEXTAD); break;
+
+ default:
+ LogRelMax(45, ("ISO/UDF: Unknown allocation descriptor type %#x\n", fIcbTagFlags));
+ return VERR_ISO_FS_UNKNOWN_AD_TYPE;
+ }
+ if (cbAllocDescs >= cbOneDesc)
+ {
+ /*
+ * Loop thru the allocation descriptors.
+ */
+ PRTFSISOEXTENT pCurExtent = NULL;
+ union
+ {
+ uint8_t const *pb;
+ PCUDFSHORTAD pShort;
+ PCUDFLONGAD pLong;
+ PCUDFEXTAD pExt;
+ } uPtr;
+ uPtr.pb = pbAllocDescs;
+ do
+ {
+ /* Extract the information we need from the descriptor. */
+ uint32_t idxBlock;
+ uint32_t idxPart;
+ uint32_t cb;
+ uint8_t uType;
+ switch (fIcbTagFlags & UDF_ICB_FLAGS_AD_TYPE_MASK)
+ {
+ case UDF_ICB_FLAGS_AD_TYPE_SHORT:
+ uType = uPtr.pShort->uType;
+ cb = uPtr.pShort->cb;
+ idxBlock = uPtr.pShort->off;
+ idxPart = idxDefaultPart;
+ cbAllocDescs -= sizeof(*uPtr.pShort);
+ uPtr.pShort++;
+ break;
+ case UDF_ICB_FLAGS_AD_TYPE_LONG:
+ uType = uPtr.pLong->uType;
+ cb = uPtr.pLong->cb;
+ idxBlock = uPtr.pLong->Location.off;
+ idxPart = uPtr.pLong->Location.uPartitionNo;
+ cbAllocDescs -= sizeof(*uPtr.pLong);
+ uPtr.pLong++;
+ break;
+ case UDF_ICB_FLAGS_AD_TYPE_EXTENDED:
+ if ( uPtr.pExt->cbInformation > cbAllocDescs
+ || uPtr.pExt->cbInformation < sizeof(*uPtr.pExt))
+ return VERR_ISOFS_BAD_EXTAD;
+ uType = uPtr.pExt->uType;
+ cb = uPtr.pExt->cb;
+ idxBlock = uPtr.pExt->Location.off;
+ idxPart = uPtr.pExt->Location.uPartitionNo;
+ cbAllocDescs -= uPtr.pExt->cbInformation;
+ uPtr.pb += uPtr.pExt->cbInformation;
+ break;
+ default:
+ AssertFailedReturn(VERR_IPE_NOT_REACHED_DEFAULT_CASE);
+ }
+
+ /* Check if we can extend the current extent. This is useful since
+ the descriptors can typically only cover 1GB. */
+ uint64_t const off = (uint64_t)idxBlock << pVol->Udf.VolInfo.cShiftBlock;
+ if ( pCurExtent != NULL
+ && ( pCurExtent->off != UINT64_MAX
+ ? uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED
+ && pCurExtent->off + pCurExtent->cbExtent == off
+ && pCurExtent->idxPart == idxPart
+ : uType != UDF_AD_TYPE_RECORDED_AND_ALLOCATED) )
+ pCurExtent->cbExtent += cb;
+ else
+ {
+ /* Allocate a new descriptor. */
+ if (pCore->cExtents == 0)
+ {
+ pCore->cExtents = 1;
+ pCurExtent = &pCore->FirstExtent;
+ }
+ else
+ {
+ void *pvNew = RTMemRealloc(pCore->paExtents, pCore->cExtents * sizeof(pCore->paExtents[0]));
+ if (pvNew)
+ pCore->paExtents = (PRTFSISOEXTENT)pvNew;
+ else
+ {
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ pCore->cExtents = 0;
+ return VERR_NO_MEMORY;
+ }
+ pCurExtent = &pCore->paExtents[pCore->cExtents - 1];
+ pCore->cExtents++;
+ }
+
+ /* Initialize it. */
+ if (uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ {
+ pCurExtent->off = off;
+ pCurExtent->idxPart = idxPart;
+ }
+ else
+ {
+ pCurExtent->off = UINT64_MAX;
+ pCurExtent->idxPart = UINT32_MAX;
+ }
+ pCurExtent->cbExtent = cb;
+ pCurExtent->uReserved = 0;
+ }
+ } while (cbAllocDescs >= cbOneDesc);
+
+ if (cbAllocDescs > 0)
+ LogRelMax(45,("ISO/UDF: Warning! %u bytes left in allocation descriptor: %.*Rhxs\n", cbAllocDescs, cbAllocDescs, uPtr.pb));
+ }
+ else
+ {
+ /*
+ * Zero descriptors
+ */
+ pCore->cExtents = 0;
+ pCore->FirstExtent.off = UINT64_MAX;
+ pCore->FirstExtent.cbExtent = 0;
+ pCore->FirstExtent.idxPart = UINT32_MAX;
+
+ if (cbAllocDescs > 0)
+ LogRelMax(45, ("ISO/UDF: Warning! Allocation descriptor area is shorted than one descriptor: %#u vs %#u: %.*Rhxs\n",
+ cbAllocDescs, cbOneDesc, cbAllocDescs, pbAllocDescs));
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts ICB flags, ICB file type and file entry permissions to an IPRT file
+ * mode mask.
+ *
+ * @returns IPRT status ocde
+ * @param fIcbTagFlags The ICB flags.
+ * @param bFileType The ICB file type.
+ * @param fPermission The file entry permission mask.
+ * @param pfAttrib Where to return the IRPT file mode mask.
+ */
+static int rtFsIsoCore_UdfStuffToFileMode(uint32_t fIcbTagFlags, uint8_t bFileType, uint32_t fPermission, PRTFMODE pfAttrib)
+{
+ /*
+ * Type:
+ */
+ RTFMODE fAttrib;
+ switch (bFileType)
+ {
+ case UDF_FILE_TYPE_DIRECTORY:
+ fAttrib = RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY;
+ break;
+
+ case UDF_FILE_TYPE_REGULAR_FILE:
+ case UDF_FILE_TYPE_REAL_TIME_FILE:
+ fAttrib = RTFS_TYPE_FILE;
+ break;
+
+ case UDF_FILE_TYPE_SYMBOLIC_LINK:
+ fAttrib = RTFS_TYPE_SYMLINK;
+ break;
+
+ case UDF_FILE_TYPE_BLOCK_DEVICE:
+ fAttrib = RTFS_TYPE_DEV_BLOCK;
+ break;
+ case UDF_FILE_TYPE_CHARACTER_DEVICE:
+ fAttrib = RTFS_TYPE_DEV_CHAR;
+ break;
+
+ case UDF_FILE_TYPE_FIFO:
+ fAttrib = RTFS_TYPE_FIFO;
+ break;
+
+ case UDF_FILE_TYPE_SOCKET:
+ fAttrib = RTFS_TYPE_SOCKET;
+ break;
+
+ case UDF_FILE_TYPE_STREAM_DIRECTORY:
+ case UDF_FILE_TYPE_EXTENDED_ATTRIBUTES:
+ case UDF_FILE_TYPE_TERMINAL_ENTRY:
+ case UDF_FILE_TYPE_VAT:
+ case UDF_FILE_TYPE_METADATA_FILE:
+ case UDF_FILE_TYPE_METADATA_MIRROR_FILE:
+ case UDF_FILE_TYPE_METADATA_BITMAP_FILE:
+ case UDF_FILE_TYPE_NOT_SPECIFIED:
+ case UDF_FILE_TYPE_INDIRECT_ENTRY:
+ case UDF_FILE_TYPE_UNALLOCATED_SPACE_ENTRY:
+ case UDF_FILE_TYPE_PARTITION_INTEGRITY_ENTRY:
+ LogRelMax(45, ("ISO/UDF: Warning! Wrong file type: %#x\n", bFileType));
+ return VERR_ISOFS_WRONG_FILE_TYPE;
+
+ default:
+ LogRelMax(45, ("ISO/UDF: Warning! Unknown file type: %#x\n", bFileType));
+ return VERR_ISOFS_UNKNOWN_FILE_TYPE;
+ }
+
+ /*
+ * Permissions:
+ */
+ if (fPermission & UDF_PERM_OTH_EXEC)
+ fAttrib |= RTFS_UNIX_IXOTH;
+ if (fPermission & UDF_PERM_OTH_READ)
+ fAttrib |= RTFS_UNIX_IROTH;
+ if (fPermission & UDF_PERM_OTH_WRITE)
+ fAttrib |= RTFS_UNIX_IWOTH;
+
+ if (fPermission & UDF_PERM_GRP_EXEC)
+ fAttrib |= RTFS_UNIX_IXGRP;
+ if (fPermission & UDF_PERM_GRP_READ)
+ fAttrib |= RTFS_UNIX_IRGRP;
+ if (fPermission & UDF_PERM_GRP_WRITE)
+ fAttrib |= RTFS_UNIX_IWGRP;
+
+ if (fPermission & UDF_PERM_USR_EXEC)
+ fAttrib |= RTFS_UNIX_IXUSR;
+ if (fPermission & UDF_PERM_USR_READ)
+ fAttrib |= RTFS_UNIX_IRUSR;
+ if (fPermission & UDF_PERM_USR_WRITE)
+ fAttrib |= RTFS_UNIX_IWUSR;
+
+ if ( !(fAttrib & (UDF_PERM_OTH_WRITE | UDF_PERM_GRP_WRITE | UDF_PERM_USR_WRITE))
+ && (fAttrib & (UDF_PERM_OTH_READ | UDF_PERM_GRP_READ | UDF_PERM_USR_READ)) )
+ fAttrib |= RTFS_DOS_READONLY;
+
+ /*
+ * Attributes:
+ */
+ if (fIcbTagFlags & UDF_ICB_FLAGS_ARCHIVE)
+ fAttrib |= RTFS_DOS_ARCHIVED;
+ if (fIcbTagFlags & UDF_ICB_FLAGS_SYSTEM)
+ fAttrib |= RTFS_DOS_SYSTEM;
+ if (fIcbTagFlags & UDF_ICB_FLAGS_ARCHIVE)
+ fAttrib |= RTFS_DOS_ARCHIVED;
+
+ if (fIcbTagFlags & UDF_ICB_FLAGS_SET_UID)
+ fAttrib |= RTFS_UNIX_ISUID;
+ if (fIcbTagFlags & UDF_ICB_FLAGS_SET_GID)
+ fAttrib |= RTFS_UNIX_ISGID;
+ if (fIcbTagFlags & UDF_ICB_FLAGS_STICKY)
+ fAttrib |= RTFS_UNIX_ISTXT;
+
+ /* Warn about weird flags. */
+ if (fIcbTagFlags & UDF_ICB_FLAGS_TRANSFORMED)
+ LogRelMax(45, ("ISO/UDF: Warning! UDF_ICB_FLAGS_TRANSFORMED!\n"));
+ if (fIcbTagFlags & UDF_ICB_FLAGS_MULTI_VERSIONS)
+ LogRelMax(45, ("ISO/UDF: Warning! UDF_ICB_FLAGS_MULTI_VERSIONS!\n"));
+ if (fIcbTagFlags & UDF_ICB_FLAGS_STREAM)
+ LogRelMax(45, ("ISO/UDF: Warning! UDF_ICB_FLAGS_STREAM!\n"));
+ if (fIcbTagFlags & UDF_ICB_FLAGS_RESERVED_MASK)
+ LogRelMax(45, ("ISO/UDF: Warning! UDF_ICB_FLAGS_RESERVED_MASK (%#x)!\n", fIcbTagFlags & UDF_ICB_FLAGS_RESERVED_MASK));
+
+ *pfAttrib = fAttrib;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize/update a core object structure from an UDF extended file entry.
+ *
+ * @returns IPRT status code
+ * @param pCore The core object structure to initialize.
+ * @param pFileEntry The file entry.
+ * @param idxDefaultPart The default data partition.
+ * @param pcProcessed Variable to increment on success.
+ * @param pVol The volume instance.
+ */
+static int rtFsIsoCore_InitFromUdfIcbExFileEntry(PRTFSISOCORE pCore, PCUDFEXFILEENTRY pFileEntry, uint32_t idxDefaultPart,
+ uint32_t *pcProcessed, PRTFSISOVOL pVol)
+{
+#ifdef LOG_ENABLED
+ /*
+ * Log it.
+ */
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", IcbTag.cEntiresBeforeThis);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.uStrategyType);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.abStrategyParams[0]);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.abStrategyParams[1]);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.cMaxEntries);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.bReserved);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.bFileType);
+ UDF_LOG2_MEMBER_LBADDR(pFileEntry, IcbTag.ParentIcb);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.fFlags);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uid);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", gid);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", fPermissions);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", cHardlinks);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", uRecordFormat);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", fRecordDisplayAttribs);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbRecord);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cbData);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cbObject);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cLogicalBlocks);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, AccessTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, ModificationTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, BirthTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, ChangeTime);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uCheckpoint);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uReserved);
+ UDF_LOG2_MEMBER_LONGAD(pFileEntry, ExtAttribIcb);
+ UDF_LOG2_MEMBER_LONGAD(pFileEntry, StreamDirIcb);
+ UDF_LOG2_MEMBER_ENTITY_ID(pFileEntry, idImplementation);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", INodeId);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbExtAttribs);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbAllocDescs);
+ if (pFileEntry->cbExtAttribs > 0)
+ Log2((pFileEntry->cbExtAttribs <= 16 ? "ISO/UDF: %-32s %.*Rhxs\n" : "ISO/UDF: %-32s\n%.*RhxD\n",
+ "abExtAttribs:", pFileEntry->cbExtAttribs, pFileEntry->abExtAttribs));
+ if (pFileEntry->cbAllocDescs > 0)
+ switch (pFileEntry->IcbTag.fFlags & UDF_ICB_FLAGS_AD_TYPE_MASK)
+ {
+ case UDF_ICB_FLAGS_AD_TYPE_SHORT:
+ {
+ PCUDFSHORTAD paDescs = (PCUDFSHORTAD)&pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs];
+ uint32_t cDescs = pFileEntry->cbAllocDescs / sizeof(paDescs[0]);
+ for (uint32_t i = 0; i < cDescs; i++)
+ Log2(("ISO/UDF: ShortAD[%u]: %#010RX32 LB %#010RX32; type=%u\n",
+ i, paDescs[i].off, paDescs[i].cb, paDescs[i].uType));
+ break;
+ }
+ case UDF_ICB_FLAGS_AD_TYPE_LONG:
+ {
+ PCUDFLONGAD paDescs = (PCUDFLONGAD)&pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs];
+ uint32_t cDescs = pFileEntry->cbAllocDescs / sizeof(paDescs[0]);
+ for (uint32_t i = 0; i < cDescs; i++)
+ Log2(("ISO/UDF: LongAD[%u]: %#06RX16:%#010RX32 LB %#010RX32; type=%u iu=%.6Rhxs\n",
+ i, paDescs[i].Location.uPartitionNo, paDescs[i].Location.off,
+ paDescs[i].cb, paDescs[i].uType, &paDescs[i].ImplementationUse));
+ break;
+ }
+ default:
+ Log2(("ISO/UDF: %-32s Type=%u\n%.*RhxD\n",
+ "abExtAttribs:", pFileEntry->IcbTag.fFlags & UDF_ICB_FLAGS_AD_TYPE_MASK,
+ pFileEntry->cbAllocDescs, &pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs]));
+ break;
+ }
+ }
+#endif
+
+ /*
+ * Basic sanity checking of what we use.
+ */
+ if ( RT_UOFFSETOF(UDFFILEENTRY, abExtAttribs) + pFileEntry->cbExtAttribs + pFileEntry->cbAllocDescs
+ > pVol->Udf.VolInfo.cbBlock
+ || (pFileEntry->cbExtAttribs & 3) != 0
+ || pFileEntry->cbExtAttribs >= pVol->Udf.VolInfo.cbBlock
+ || (pFileEntry->cbAllocDescs & 3) != 0
+ || pFileEntry->cbAllocDescs >= pVol->Udf.VolInfo.cbBlock)
+ {
+ LogRelMax(45, ("ISO/UDF: Extended file entry (ICB) is bad size values: cbAllocDesc=%#x cbExtAttribs=%#x (cbBlock=%#x)\n",
+ pFileEntry->cbAllocDescs, pFileEntry->cbExtAttribs, pVol->Udf.VolInfo.cbBlock));
+ return VERR_ISOFS_BAD_FILE_ENTRY;
+ }
+
+ //pCore->uid = pFileEntry->uid;
+ //pCore->gid = pFileEntry->gid;
+ //pCore->cHardlinks = RT_MIN(pFileEntry->cHardlinks, 1);
+ pCore->cbObject = pFileEntry->cbData;
+ //pCore->cbAllocated = pFileEntry->cLogicalBlocks << pVol->Udf.VolInfo.cShiftBlock;
+ pCore->idINode = pFileEntry->INodeId;
+
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->AccessTime, &pFileEntry->AccessTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->ModificationTime, &pFileEntry->ModificationTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->BirthTime, &pFileEntry->BirthTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->ChangeTime, &pFileEntry->ChangeTime);
+
+ if ( pFileEntry->uRecordFormat
+ || pFileEntry->fRecordDisplayAttribs
+ || pFileEntry->cbRecord)
+ LogRelMax(45, ("ISO/UDF: uRecordFormat=%#x fRecordDisplayAttribs=%#x cbRecord=%#x\n",
+ pFileEntry->uRecordFormat, pFileEntry->fRecordDisplayAttribs, pFileEntry->cbRecord));
+
+ /*
+ * Conver the file mode.
+ */
+ int rc = rtFsIsoCore_UdfStuffToFileMode(pFileEntry->IcbTag.fFlags, pFileEntry->IcbTag.bFileType,
+ pFileEntry->fPermissions, &pCore->fAttrib);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert extent info.
+ */
+ rc = rtFsIsoCore_InitExtentsUdfIcbEntry(pCore,
+ &pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs],
+ pFileEntry->cbAllocDescs,
+ pFileEntry->IcbTag.fFlags,
+ idxDefaultPart,
+ ((uint64_t)pFileEntry->Tag.offTag << pVol->Udf.VolInfo.cShiftBlock)
+ + RT_UOFFSETOF(UDFFILEENTRY, abExtAttribs) + pFileEntry->cbExtAttribs,
+ pVol);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * We're good.
+ */
+ *pcProcessed += 1;
+ return VINF_SUCCESS;
+ }
+
+ /* Just in case. */
+ if (pCore->paExtents)
+ {
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ }
+ pCore->cExtents = 0;
+ }
+ return rc;
+}
+
+
+/**
+ * Initialize/update a core object structure from an UDF file entry.
+ *
+ * @returns IPRT status code
+ * @param pCore The core object structure to initialize.
+ * @param pFileEntry The file entry.
+ * @param idxDefaultPart The default data partition.
+ * @param pcProcessed Variable to increment on success.
+ * @param pVol The volume instance.
+ */
+static int rtFsIsoCore_InitFromUdfIcbFileEntry(PRTFSISOCORE pCore, PCUDFFILEENTRY pFileEntry, uint32_t idxDefaultPart,
+ uint32_t *pcProcessed, PRTFSISOVOL pVol)
+{
+#ifdef LOG_ENABLED
+ /*
+ * Log it.
+ */
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", IcbTag.cEntiresBeforeThis);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.uStrategyType);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.abStrategyParams[0]);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.abStrategyParams[1]);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.cMaxEntries);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.bReserved);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", IcbTag.bFileType);
+ UDF_LOG2_MEMBER_LBADDR(pFileEntry, IcbTag.ParentIcb);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", IcbTag.fFlags);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uid);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", gid);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", fPermissions);
+ UDF_LOG2_MEMBER(pFileEntry, "#06RX16", cHardlinks);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", uRecordFormat);
+ UDF_LOG2_MEMBER(pFileEntry, "#04RX8", fRecordDisplayAttribs);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbRecord);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cbData);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", cLogicalBlocks);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, AccessTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, ModificationTime);
+ UDF_LOG2_MEMBER_TIMESTAMP(pFileEntry, ChangeTime);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", uCheckpoint);
+ UDF_LOG2_MEMBER_LONGAD(pFileEntry, ExtAttribIcb);
+ UDF_LOG2_MEMBER_ENTITY_ID(pFileEntry, idImplementation);
+ UDF_LOG2_MEMBER(pFileEntry, "#018RX64", INodeId);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbExtAttribs);
+ UDF_LOG2_MEMBER(pFileEntry, "#010RX32", cbAllocDescs);
+ if (pFileEntry->cbExtAttribs > 0)
+ Log2((pFileEntry->cbExtAttribs <= 16 ? "ISO/UDF: %-32s %.*Rhxs\n" : "ISO/UDF: %-32s\n%.*RhxD\n",
+ "abExtAttribs:", pFileEntry->cbExtAttribs, pFileEntry->abExtAttribs));
+ if (pFileEntry->cbAllocDescs > 0)
+ switch (pFileEntry->IcbTag.fFlags & UDF_ICB_FLAGS_AD_TYPE_MASK)
+ {
+ case UDF_ICB_FLAGS_AD_TYPE_SHORT:
+ {
+ PCUDFSHORTAD paDescs = (PCUDFSHORTAD)&pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs];
+ uint32_t cDescs = pFileEntry->cbAllocDescs / sizeof(paDescs[0]);
+ for (uint32_t i = 0; i < cDescs; i++)
+ Log2(("ISO/UDF: ShortAD[%u]: %#010RX32 LB %#010RX32; type=%u\n",
+ i, paDescs[i].off, paDescs[i].cb, paDescs[i].uType));
+ break;
+ }
+ case UDF_ICB_FLAGS_AD_TYPE_LONG:
+ {
+ PCUDFLONGAD paDescs = (PCUDFLONGAD)&pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs];
+ uint32_t cDescs = pFileEntry->cbAllocDescs / sizeof(paDescs[0]);
+ for (uint32_t i = 0; i < cDescs; i++)
+ Log2(("ISO/UDF: LongAD[%u]: %#06RX16:%#010RX32 LB %#010RX32; type=%u iu=%.6Rhxs\n",
+ i, paDescs[i].Location.uPartitionNo, paDescs[i].Location.off,
+ paDescs[i].cb, paDescs[i].uType, &paDescs[i].ImplementationUse));
+ break;
+ }
+ default:
+ Log2(("ISO/UDF: %-32s Type=%u\n%.*RhxD\n",
+ "abExtAttribs:", pFileEntry->IcbTag.fFlags & UDF_ICB_FLAGS_AD_TYPE_MASK,
+ pFileEntry->cbAllocDescs, &pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs]));
+ break;
+ }
+ }
+#endif
+
+ /*
+ * Basic sanity checking of what we use.
+ */
+ if ( RT_UOFFSETOF(UDFFILEENTRY, abExtAttribs) + pFileEntry->cbExtAttribs + pFileEntry->cbAllocDescs
+ > pVol->Udf.VolInfo.cbBlock
+ || (pFileEntry->cbExtAttribs & 3) != 0
+ || pFileEntry->cbExtAttribs >= pVol->Udf.VolInfo.cbBlock
+ || (pFileEntry->cbAllocDescs & 3) != 0
+ || pFileEntry->cbAllocDescs >= pVol->Udf.VolInfo.cbBlock)
+ {
+ LogRelMax(45, ("ISO/UDF: File entry (ICB) is bad size values: cbAllocDesc=%#x cbExtAttribs=%#x (cbBlock=%#x)\n",
+ pFileEntry->cbAllocDescs, pFileEntry->cbExtAttribs, pVol->Udf.VolInfo.cbBlock));
+ return VERR_ISOFS_BAD_FILE_ENTRY;
+ }
+
+ //pCore->uid = pFileEntry->uid;
+ //pCore->gid = pFileEntry->gid;
+ //pCore->cHardlinks = RT_MIN(pFileEntry->cHardlinks, 1);
+ pCore->cbObject = pFileEntry->cbData;
+ //pCore->cbAllocated = pFileEntry->cLogicalBlocks << pVol->Udf.VolInfo.cShiftBlock;
+ pCore->idINode = pFileEntry->INodeId;
+
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->AccessTime, &pFileEntry->AccessTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->ModificationTime, &pFileEntry->ModificationTime);
+ rtFsIsoUdfTimestamp2TimeSpec(&pCore->ChangeTime, &pFileEntry->ChangeTime);
+ pCore->BirthTime = pCore->ModificationTime;
+ if (RTTimeSpecCompare(&pCore->BirthTime, &pCore->ChangeTime) > 0)
+ pCore->BirthTime = pCore->ChangeTime;
+ if (RTTimeSpecCompare(&pCore->BirthTime, &pCore->AccessTime) > 0)
+ pCore->BirthTime = pCore->AccessTime;
+
+ if ( pFileEntry->uRecordFormat
+ || pFileEntry->fRecordDisplayAttribs
+ || pFileEntry->cbRecord)
+ LogRelMax(45, ("ISO/UDF: uRecordFormat=%#x fRecordDisplayAttribs=%#x cbRecord=%#x\n",
+ pFileEntry->uRecordFormat, pFileEntry->fRecordDisplayAttribs, pFileEntry->cbRecord));
+
+ /*
+ * Conver the file mode.
+ */
+ int rc = rtFsIsoCore_UdfStuffToFileMode(pFileEntry->IcbTag.fFlags, pFileEntry->IcbTag.bFileType,
+ pFileEntry->fPermissions, &pCore->fAttrib);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert extent info.
+ */
+ rc = rtFsIsoCore_InitExtentsUdfIcbEntry(pCore,
+ &pFileEntry->abExtAttribs[pFileEntry->cbExtAttribs],
+ pFileEntry->cbAllocDescs,
+ pFileEntry->IcbTag.fFlags,
+ idxDefaultPart,
+ ((uint64_t)pFileEntry->Tag.offTag << pVol->Udf.VolInfo.cShiftBlock)
+ + RT_UOFFSETOF(UDFFILEENTRY, abExtAttribs) + pFileEntry->cbExtAttribs,
+ pVol);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * We're good.
+ */
+ *pcProcessed += 1;
+ return VINF_SUCCESS;
+ }
+
+ /* Just in case. */
+ if (pCore->paExtents)
+ {
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ }
+ pCore->cExtents = 0;
+ }
+ return rc;
+}
+
+
+/**
+ * Recursive helper for rtFsIsoCore_InitFromUdfIcbAndFileIdDesc.
+ *
+ * @returns IRPT status code.
+ * @param pCore The core structure to initialize.
+ * @param AllocDesc The ICB allocation descriptor.
+ * @param pbBuf The buffer, one logical block in size.
+ * @param cNestings The number of recursive nestings (should be zero).
+ * @param pcProcessed Variable to update when we've processed something
+ * useful.
+ * @param pcIndirections Variable tracing the number of indirections we've
+ * taken during the processing. This is used to
+ * prevent us from looping forever on a bad chain
+ * @param pVol The volue instance data.
+ */
+static int rtFsIsoCore_InitFromUdfIcbRecursive(PRTFSISOCORE pCore, UDFLONGAD AllocDesc, uint8_t *pbBuf, uint32_t cNestings,
+ uint32_t *pcProcessed, uint32_t *pcIndirections, PRTFSISOVOL pVol)
+{
+ if (cNestings >= 8)
+ return VERR_ISOFS_TOO_DEEP_ICB_RECURSION;
+
+ for (;;)
+ {
+ if (*pcIndirections >= 32)
+ return VERR_ISOFS_TOO_MANY_ICB_INDIRECTIONS;
+
+ /*
+ * Check the basic validity of the allocation descriptor.
+ */
+ if ( AllocDesc.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED
+ && AllocDesc.cb >= sizeof(UDFICBTAG) )
+ { /* likely */ }
+ else if (AllocDesc.uType != UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ {
+ Log(("ISO/UDF: ICB has alloc type %d!\n", AllocDesc.uType));
+ return VINF_SUCCESS;
+ }
+ else
+ {
+ LogRelMax(45, ("ISO/UDF: ICB is too small: %u bytes\n", AllocDesc.cb));
+ return AllocDesc.cb == 0 ? VINF_SUCCESS : VERR_ISOFS_ICB_ENTRY_TOO_SMALL;
+ }
+
+ /*
+ * Process it block by block.
+ */
+ uint32_t cBlocks = (AllocDesc.cb + pVol->Udf.VolInfo.cbBlock - 1) >> pVol->Udf.VolInfo.cShiftBlock;
+ for (uint32_t idxBlock = 0; ; idxBlock++)
+ {
+ /*
+ * Read a block
+ */
+ size_t cbToRead = RT_MIN(pVol->Udf.VolInfo.cbBlock, AllocDesc.cb);
+ int rc = rtFsIsoVolUdfVpRead(pVol, AllocDesc.Location.uPartitionNo, AllocDesc.Location.off + idxBlock, 0,
+ pbBuf, cbToRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbToRead < pVol->Udf.VolInfo.cbBlock)
+ RT_BZERO(&pbBuf[cbToRead], pVol->Udf.VolInfo.cbBlock - cbToRead);
+
+ /*
+ * Verify the TAG.
+ */
+ PUDFICBHDR pHdr = (PUDFICBHDR)pbBuf;
+ rc = rtFsIsoVolValidateUdfDescTagAndCrc(&pHdr->Tag, pVol->Udf.VolInfo.cbBlock, UINT16_MAX,
+ AllocDesc.Location.off + idxBlock, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Do specific processing.
+ */
+ if (pHdr->Tag.idTag == UDF_TAG_ID_FILE_ENTRY)
+ rc = rtFsIsoCore_InitFromUdfIcbFileEntry(pCore, (PCUDFFILEENTRY)pHdr, AllocDesc.Location.uPartitionNo,
+ pcProcessed, pVol);
+ else if (pHdr->Tag.idTag == UDF_TAG_ID_EXTENDED_FILE_ENTRY)
+ rc = rtFsIsoCore_InitFromUdfIcbExFileEntry(pCore, (PCUDFEXFILEENTRY)pHdr, AllocDesc.Location.uPartitionNo,
+ pcProcessed, pVol);
+ else if (pHdr->Tag.idTag == UDF_TAG_ID_INDIRECT_ENTRY)
+ {
+ PUDFINDIRECTENTRY pIndir = (PUDFINDIRECTENTRY)pHdr;
+ *pcIndirections += 1;
+ if (pIndir->IndirectIcb.cb != 0)
+ {
+ if (idxBlock + 1 == cBlocks)
+ {
+ AllocDesc = pIndir->IndirectIcb;
+ Log2(("ISO/UDF: ICB: Indirect entry - looping: %x:%#010RX32 LB %#x; uType=%d\n",
+ AllocDesc.Location.uPartitionNo, AllocDesc.Location.off, AllocDesc.cb, AllocDesc.uType));
+ break;
+ }
+ Log2(("ISO/UDF: ICB: Indirect entry - recursing: %x:%#010RX32 LB %#x; uType=%d\n",
+ pIndir->IndirectIcb.Location.uPartitionNo, pIndir->IndirectIcb.Location.off,
+ pIndir->IndirectIcb.cb, pIndir->IndirectIcb.uType));
+ rc = rtFsIsoCore_InitFromUdfIcbRecursive(pCore, pIndir->IndirectIcb, pbBuf, cNestings,
+ pcProcessed, pcIndirections, pVol);
+ }
+ else
+ Log(("ISO/UDF: zero length indirect entry\n"));
+ }
+ else if (pHdr->Tag.idTag == UDF_TAG_ID_TERMINAL_ENTRY)
+ {
+ Log2(("ISO/UDF: Terminal ICB entry\n"));
+ return VINF_SUCCESS;
+ }
+ else if (pHdr->Tag.idTag == UDF_TAG_ID_UNALLOCATED_SPACE_ENTRY)
+ {
+ Log2(("ISO/UDF: Unallocated space entry: skipping\n"));
+ /* Ignore since we don't do writing (UDFUNALLOCATEDSPACEENTRY) */
+ }
+ else
+ {
+ LogRelMax(90, ("ISO/UDF: Unknown ICB type %#x\n", pHdr->Tag.idTag));
+ return VERR_ISOFS_UNSUPPORTED_ICB;
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Advance.
+ */
+ if (idxBlock + 1 >= cBlocks)
+ return VINF_SUCCESS;
+ }
+
+ /* If we get here, we've jumped thru an indirect entry. */
+ }
+ /* never reached */
+}
+
+
+
+/**
+ * Initialize a core structure from an UDF ICB range and optionally a file ID.
+ *
+ * @returns IPRT status code.
+ * @param pCore The core structure to initialize.
+ * Caller must've ZEROed this structure!
+ * @param pAllocDesc The ICB allocation descriptor.
+ * @param pFid The file ID descriptor. Optional.
+ * @param offInDir The offset of the file ID descriptor in the
+ * parent directory. This is used when looking up
+ * shared directory objects. (Pass 0 for root.)
+ * @param pVol The instance.
+ *
+ * @note Caller must check for UDF_FILE_FLAGS_DELETED before calling if the
+ * object is supposed to be used for real stuff.
+ */
+static int rtFsIsoCore_InitFromUdfIcbAndFileIdDesc(PRTFSISOCORE pCore, PCUDFLONGAD pAllocDesc,
+ PCUDFFILEIDDESC pFid, uintptr_t offInDir, PRTFSISOVOL pVol)
+{
+ Assert(pCore->cRefs == 0);
+ Assert(pCore->cExtents == 0);
+ Assert(pCore->paExtents == NULL);
+ Assert(pCore->pVol == NULL);
+
+ /*
+ * Some size sanity checking.
+ */
+ if (pAllocDesc->cb <= _64K)
+ {
+ if (pAllocDesc->cb >= sizeof(UDFICBHDR))
+ { /* likely */ }
+ else
+ {
+ Log(("rtFsIsoCore_InitFromUdfIcbAndFileIdDesc: ICB too small: %#04x:%010RX32 LB %#x\n",
+ pAllocDesc->Location.uPartitionNo, pAllocDesc->Location.off, pAllocDesc->cb));
+ return VERR_ISOFS_ICB_TOO_SMALL;
+ }
+ }
+ else
+ {
+ Log(("rtFsIsoCore_InitFromUdfIcbAndFileIdDesc: ICB too big: %#04x:%010RX32 LB %#x\n",
+ pAllocDesc->Location.uPartitionNo, pAllocDesc->Location.off, pAllocDesc->cb));
+ return VERR_ISOFS_ICB_TOO_BIG;
+ }
+
+ /*
+ * Allocate a temporary buffer, one logical block in size.
+ */
+ uint8_t * const pbBuf = (uint8_t *)RTMemTmpAlloc(pVol->Udf.VolInfo.cbBlock);
+ if (pbBuf)
+ {
+ uint32_t cProcessed = 0;
+ uint32_t cIndirections = 0;
+ int rc = rtFsIsoCore_InitFromUdfIcbRecursive(pCore, *pAllocDesc, pbBuf, 0, &cProcessed, &cIndirections, pVol);
+ RTMemTmpFree(pbBuf);
+ if (RT_SUCCESS(rc))
+ {
+ if (cProcessed > 0)
+ {
+ if (pFid)
+ {
+ if (pFid->fFlags & UDF_FILE_FLAGS_HIDDEN)
+ pCore->fAttrib |= RTFS_DOS_HIDDEN;
+ if (pFid->fFlags & UDF_FILE_FLAGS_DELETED)
+ pCore->fAttrib = (pCore->fAttrib & ~RTFS_TYPE_MASK) | RTFS_TYPE_WHITEOUT;
+ }
+
+ pCore->cRefs = 1;
+ pCore->pVol = pVol;
+ pCore->offDirRec = offInDir;
+ return VINF_SUCCESS;
+ }
+ rc = VERR_ISOFS_NO_DIRECT_ICB_ENTRIES;
+ }
+
+ /* White-out fix. Caller must be checking for UDF_FILE_FLAGS_DELETED */
+ if ( pFid
+ && (pFid->fFlags & UDF_FILE_FLAGS_DELETED))
+ {
+ pCore->fAttrib = (pCore->fAttrib & ~RTFS_TYPE_MASK) | RTFS_TYPE_WHITEOUT;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+
+ pCore->pVol = NULL;
+ return VERR_NO_TMP_MEMORY;
+}
+
+
+/**
+ * Simple UDF read function.
+ *
+ * This deals with extent mappings as well as virtual partition related block
+ * mapping and such.
+ *
+ * @returns VBox status code.
+ * @param pCore The core object to read data from.
+ * @param offRead The offset to start reading at.
+ * @param pvBuf The output buffer.
+ * @param cbToRead The number of bytes to read.
+ * @param pcbRead Where to return the number of bytes read.
+ * @param poffPosMov Where to return the number of bytes to move the read
+ * position. Optional. (Essentially same as pcbRead
+ * except without the behavior change.)
+ */
+static int rtFsIsoCore_ReadWorker(PRTFSISOCORE pCore, uint64_t offRead, void *pvBuf, size_t cbToRead,
+ size_t *pcbRead, size_t *poffPosMov)
+{
+ /*
+ * Check for EOF.
+ */
+ if (offRead >= pCore->cbObject)
+ {
+ if (poffPosMov)
+ *poffPosMov = 0;
+ if (pcbRead)
+ {
+ *pcbRead = 0;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+ int rcRet = VINF_SUCCESS;
+ if ( cbToRead > pCore->cbObject
+ || offRead + cbToRead > pCore->cbObject)
+ {
+ if (!pcbRead)
+ {
+ if (poffPosMov)
+ *poffPosMov = 0;
+ return VERR_EOF;
+ }
+ cbToRead = pCore->cbObject - offRead;
+ rcRet = VINF_EOF;
+ }
+
+ uint64_t cbActual = 0;
+
+ /*
+ * Don't bother looking up the extent if we're not going to
+ * read anything from it.
+ */
+ if (cbToRead > 0)
+ {
+ /*
+ * Locate the first extent.
+ */
+ uint64_t offExtent = 0;
+ uint32_t iExtent = 0;
+ PCRTFSISOEXTENT pCurExtent = &pCore->FirstExtent;
+ if (offRead < pCurExtent->cbExtent)
+ { /* likely */ }
+ else
+ do
+ {
+ offExtent += pCurExtent->cbExtent;
+ pCurExtent = &pCore->paExtents[iExtent++];
+ if (iExtent >= pCore->cExtents)
+ {
+ memset(pvBuf, 0, cbToRead);
+
+ if (pcbRead)
+ *pcbRead = cbToRead;
+ if (poffPosMov)
+ *poffPosMov = cbToRead;
+ return rcRet;
+ }
+ } while (offExtent < offRead);
+ Assert(offRead - offExtent < pCurExtent->cbExtent);
+
+ /*
+ * Do the reading part.
+ */
+ PRTFSISOVOL pVol = pCore->pVol;
+ for (;;)
+ {
+ uint64_t offIntoExtent = offRead - offExtent;
+ size_t cbThisRead = pCurExtent->cbExtent - offIntoExtent;
+ if (cbThisRead > cbToRead)
+ cbThisRead = cbToRead;
+
+ if (pCurExtent->off == UINT64_MAX)
+ RT_BZERO(pvBuf, cbThisRead);
+ else
+ {
+ int rc2;
+ if (pCurExtent->idxPart == UINT32_MAX)
+ rc2 = RTVfsFileReadAt(pVol->hVfsBacking, pCurExtent->off + offIntoExtent, pvBuf, cbThisRead, NULL);
+ else
+ {
+ Assert(pVol->enmType == RTFSISOVOLTYPE_UDF);
+ if (pCurExtent->idxPart < pVol->Udf.VolInfo.cPartitions)
+ {
+ PRTFSISOVOLUDFPMAP pPart = &pVol->Udf.VolInfo.paPartitions[pCurExtent->idxPart];
+ switch (pPart->bType)
+ {
+ case RTFSISO_UDF_PMAP_T_PLAIN:
+ rc2 = RTVfsFileReadAt(pVol->hVfsBacking, pPart->offByteLocation + pCurExtent->off + offIntoExtent,
+ pvBuf, cbThisRead, NULL);
+ break;
+
+ default:
+ AssertFailed();
+ rc2 = VERR_ISOFS_IPE_1;
+ break;
+ }
+ }
+ else
+ {
+ Log(("ISO/UDF: Invalid partition index %#x (offset %#RX64), max partitions %#x; iExtent=%#x\n",
+ pCurExtent->idxPart, pCurExtent->off + offIntoExtent, pVol->Udf.VolInfo.cPartitions, iExtent));
+ rc2 = VERR_ISOFS_INVALID_PARTITION_INDEX;
+ }
+ }
+ if (RT_FAILURE(rc2))
+ {
+ rcRet = rc2;
+ break;
+ }
+ }
+
+ /*
+ * Advance the buffer position and check if we're done (probable).
+ */
+ cbActual += cbThisRead;
+ cbToRead -= cbThisRead;
+ if (!cbToRead)
+ break;
+ pvBuf = (uint8_t *)pvBuf + cbThisRead;
+
+ /*
+ * Advance to the next extent.
+ */
+ offExtent += pCurExtent->cbExtent;
+ pCurExtent = &pCore->paExtents[iExtent++];
+ if (iExtent >= pCore->cExtents)
+ {
+ memset(pvBuf, 0, cbToRead);
+ cbActual += cbToRead;
+ break;
+ }
+ }
+ }
+ else
+ Assert(rcRet == VINF_SUCCESS);
+
+ if (poffPosMov)
+ *poffPosMov = cbActual;
+ if (pcbRead)
+ *pcbRead = cbActual;
+ return rcRet;
+}
+
+
+/**
+ * Worker for rtFsIsoFile_QueryInfo and rtFsIsoDir_QueryInfo.
+ */
+static int rtFsIsoCore_QueryInfo(PRTFSISOCORE pCore, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ pObjInfo->cbObject = pCore->cbObject;
+ pObjInfo->cbAllocated = RT_ALIGN_64(pCore->cbObject, pCore->pVol->cbBlock);
+ pObjInfo->AccessTime = pCore->AccessTime;
+ pObjInfo->ModificationTime = pCore->ModificationTime;
+ pObjInfo->ChangeTime = pCore->ChangeTime;
+ pObjInfo->BirthTime = pCore->BirthTime;
+ pObjInfo->Attr.fMode = pCore->fAttrib;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING: RT_FALL_THRU();
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
+ pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = pCore->idINode;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = pCore->uVersion;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = 0;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = 0;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if ( pCore->fHaveRockInfo
+ && enmAddAttr != RTFSOBJATTRADD_NOTHING)
+ {
+ /** @todo Read the the rock info for this entry. */
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtFsIsoFile_Close and rtFsIsoDir_Close that does common work.
+ *
+ * @param pCore The common shared structure.
+ */
+static void rtFsIsoCore_Destroy(PRTFSISOCORE pCore)
+{
+ if (pCore->pParentDir)
+ rtFsIsoDirShrd_RemoveOpenChild(pCore->pParentDir, pCore);
+ if (pCore->paExtents)
+ {
+ RTMemFree(pCore->paExtents);
+ pCore->paExtents = NULL;
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Close(void *pvThis)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ LogFlow(("rtFsIsoFile_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSISOFILESHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ {
+ if (ASMAtomicDecU32(&pShared->Core.cRefs) == 0)
+ {
+ LogFlow(("rtFsIsoFile_Close: Destroying shared structure %p\n", pShared));
+ rtFsIsoCore_Destroy(&pShared->Core);
+ RTMemFree(pShared);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ return rtFsIsoCore_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ PRTFSISOFILESHRD pShared = pThis->pShared;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+#if 1
+ /* Apply default offset. */
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ /* Do the read. */
+ size_t offDelta = 0;
+ int rc = rtFsIsoCore_ReadWorker(&pShared->Core, off, (uint8_t *)pSgBuf->paSegs[0].pvSeg,
+ pSgBuf->paSegs[0].cbSeg, pcbRead, &offDelta);
+
+ /* Update the file position and return. */
+ pThis->offFile = off + offDelta;
+ return rc;
+#else
+
+
+ /*
+ * Check for EOF.
+ */
+ if (off == -1)
+ off = pThis->offFile;
+ if ((uint64_t)off >= pShared->Core.cbObject)
+ {
+ if (pcbRead)
+ {
+ *pcbRead = 0;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+
+ if (pShared->Core.pVol->enmType == RTFSISOVOLTYPE_UDF)
+ {
+ return VERR_ISOFS_UDF_NOT_IMPLEMENTED;
+ }
+
+ /*
+ * Simple case: File has a single extent.
+ */
+ int rc = VINF_SUCCESS;
+ size_t cbRead = 0;
+ uint64_t cbFileLeft = pShared->Core.cbObject - (uint64_t)off;
+ size_t cbLeft = pSgBuf->paSegs[0].cbSeg;
+ uint8_t *pbDst = (uint8_t *)pSgBuf->paSegs[0].pvSeg;
+ if (pShared->Core.cExtents == 1)
+ {
+ if (cbLeft > 0)
+ {
+ size_t cbToRead = cbLeft;
+ if (cbToRead > cbFileLeft)
+ cbToRead = (size_t)cbFileLeft;
+ rc = RTVfsFileReadAt(pShared->Core.pVol->hVfsBacking, pShared->Core.FirstExtent.off + off, pbDst, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ off += cbToRead;
+ pbDst += cbToRead;
+ cbRead += cbToRead;
+ cbFileLeft -= cbToRead;
+ cbLeft -= cbToRead;
+ }
+ }
+ }
+ /*
+ * Complicated case: Work the file content extent by extent.
+ */
+ else
+ {
+ return VERR_NOT_IMPLEMENTED; /** @todo multi-extent stuff . */
+ }
+
+ /* Update the offset and return. */
+ pThis->offFile = off;
+ if (pcbRead)
+ *pcbRead = cbRead;
+ return VINF_SUCCESS;
+#endif
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ NOREF(pvThis);
+ int rc;
+ if (fEvents != RTPOLL_EVT_ERROR)
+ {
+ *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR;
+ rc = VINF_SUCCESS;
+ }
+ else if (fIntr)
+ rc = RTThreadSleep(cMillies);
+ else
+ {
+ uint64_t uMsStart = RTTimeMilliTS();
+ do
+ rc = RTThreadSleep(cMillies);
+ while ( rc == VERR_INTERRUPTED
+ && !fIntr
+ && RTTimeMilliTS() - uMsStart < cMillies);
+ if (rc == VERR_INTERRUPTED)
+ rc = VERR_TIMEOUT;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = (RTFOFF)pThis->pShared->Core.cbObject + offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNew = (RTFOFF)pThis->offFile + offSeek;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ if (offNew >= 0)
+ {
+ pThis->offFile = offNew;
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+ }
+ return VERR_NEGATIVE_SEEK;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtFsIsoFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSISOFILEOBJ pThis = (PRTFSISOFILEOBJ)pvThis;
+ *pcbFile = pThis->pShared->Core.cbObject;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * ISO FS file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtFsIsoFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "FatFile",
+ rtFsIsoFile_Close,
+ rtFsIsoFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsIsoFile_Read,
+ NULL /*Write*/,
+ rtFsIsoFile_Flush,
+ rtFsIsoFile_PollOne,
+ rtFsIsoFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ NULL /*SetMode*/,
+ NULL /*SetTimes*/,
+ NULL /*SetOwner*/,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsIsoFile_Seek,
+ rtFsIsoFile_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Instantiates a new file, from ISO 9660 info.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory (shared part).
+ * @param pDirRec The directory record.
+ * @param cDirRecs Number of directory records if more than one.
+ * @param offDirRec The byte offset of the directory record.
+ * @param offEntryInDir The byte offset of the directory entry in the parent
+ * directory.
+ * @param fOpen RTFILE_O_XXX flags.
+ * @param uVersion The file version number (since the caller already
+ * parsed the filename, we don't want to repeat the
+ * effort here).
+ * @param pRockInfo Optional rock ridge info for the file.
+ * @param phVfsFile Where to return the file handle.
+ */
+static int rtFsIsoFile_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec, uint32_t cDirRecs,
+ uint64_t offDirRec, uint64_t fOpen, uint32_t uVersion, PCRTFSISOROCKINFO pRockInfo,
+ PRTVFSFILE phVfsFile)
+{
+ AssertPtr(pParentDir);
+
+ /*
+ * Create a VFS object.
+ */
+ PRTFSISOFILEOBJ pNewFile;
+ int rc = RTVfsNewFile(&g_rtFsIsoFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSISOFILESHRD pShared = (PRTFSISOFILESHRD)rtFsIsoDir_LookupShared(pParentDir, offDirRec);
+ if (pShared)
+ {
+ LogFlow(("rtFsIsoFile_New9660: cbObject=%#RX64 First Extent: off=%#RX64 cb=%#RX64\n",
+ pShared->Core.cbObject, pShared->Core.FirstExtent.off, pShared->Core.FirstExtent.cbExtent));
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+
+ pShared = (PRTFSISOFILESHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rc = rtFsIsoCore_InitFrom9660DirRec(&pShared->Core, pDirRec, cDirRecs, offDirRec, uVersion, pRockInfo, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rtFsIsoDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+ LogFlow(("rtFsIsoFile_New9660: cbObject=%#RX64 First Extent: off=%#RX64 cb=%#RX64\n",
+ pShared->Core.cbObject, pShared->Core.FirstExtent.off, pShared->Core.FirstExtent.cbExtent));
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pShared);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ /* Destroy the file object. */
+ pNewFile->offFile = 0;
+ pNewFile->pShared = NULL;
+ RTVfsFileRelease(*phVfsFile);
+ }
+ *phVfsFile = NIL_RTVFSFILE;
+ return rc;
+}
+
+
+/**
+ * Instantiates a new file, from UDF info.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory (shared part).
+ * @param pFid The file ID descriptor. (Points to parent directory
+ * content.)
+ * @param fOpen RTFILE_O_XXX flags.
+ * @param phVfsFile Where to return the file handle.
+ */
+static int rtFsIsoFile_NewUdf(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCUDFFILEIDDESC pFid,
+ uint64_t fOpen, PRTVFSFILE phVfsFile)
+{
+ AssertPtr(pParentDir);
+ uintptr_t const offInDir = (uintptr_t)pFid - (uintptr_t)pParentDir->pbDir;
+ Assert(offInDir < pParentDir->cbDir);
+ Assert(!(pFid->fFlags & UDF_FILE_FLAGS_DELETED));
+ Assert(!(pFid->fFlags & UDF_FILE_FLAGS_DIRECTORY));
+
+ /*
+ * Create a VFS object.
+ */
+ PRTFSISOFILEOBJ pNewFile;
+ int rc = RTVfsNewFile(&g_rtFsIsoFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Look for existing shared object. Make sure it's a file.
+ */
+ PRTFSISOFILESHRD pShared = (PRTFSISOFILESHRD)rtFsIsoDir_LookupShared(pParentDir, offInDir);
+ if (pShared)
+ {
+ if (!RTFS_IS_FILE(pShared->Core.fAttrib))
+ {
+ LogFlow(("rtFsIsoFile_NewUdf: cbObject=%#RX64 First Extent: off=%#RX64 cb=%#RX64\n",
+ pShared->Core.cbObject, pShared->Core.FirstExtent.off, pShared->Core.FirstExtent.cbExtent));
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+ }
+ /*
+ * Create a shared object for this alleged file.
+ */
+ else
+ {
+ pShared = (PRTFSISOFILESHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rc = rtFsIsoCore_InitFromUdfIcbAndFileIdDesc(&pShared->Core, &pFid->Icb, pFid, offInDir, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_FILE(pShared->Core.fAttrib))
+ {
+ rtFsIsoDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+
+ LogFlow(("rtFsIsoFile_NewUdf: cbObject=%#RX64 First Extent: off=%#RX64 cb=%#RX64\n",
+ pShared->Core.cbObject, pShared->Core.FirstExtent.off, pShared->Core.FirstExtent.cbExtent));
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+ rtFsIsoCore_Destroy(&pShared->Core);
+ }
+ RTMemFree(pShared);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ /* Destroy the file object. */
+ pNewFile->offFile = 0;
+ pNewFile->pShared = NULL;
+ RTVfsFileRelease(*phVfsFile);
+ }
+ *phVfsFile = NIL_RTVFSFILE;
+ return rc;
+}
+
+
+/**
+ * Looks up the shared structure for a child.
+ *
+ * @returns Referenced pointer to the shared structure, NULL if not found.
+ * @param pThis The directory.
+ * @param offDirRec The directory record offset of the child.
+ */
+static PRTFSISOCORE rtFsIsoDir_LookupShared(PRTFSISODIRSHRD pThis, uint64_t offDirRec)
+{
+ PRTFSISOCORE pCur;
+ RTListForEach(&pThis->OpenChildren, pCur, RTFSISOCORE, Entry)
+ {
+ if (pCur->offDirRec == offDirRec)
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs);
+ Assert(cRefs > 1); RT_NOREF(cRefs);
+ return pCur;
+ }
+ }
+ return NULL;
+}
+
+
+#ifdef RT_STRICT
+/**
+ * Checks if @a pNext is an extent of @a pFirst.
+ *
+ * @returns true if @a pNext is the next extent, false if not
+ * @param pFirst The directory record describing the first or the
+ * previous extent.
+ * @param pNext The directory record alleged to be the next extent.
+ */
+DECLINLINE(bool) rtFsIsoDir_Is9660DirRecNextExtent(PCISO9660DIRREC pFirst, PCISO9660DIRREC pNext)
+{
+ if (RT_LIKELY(pNext->bFileIdLength == pFirst->bFileIdLength))
+ {
+ if (RT_LIKELY((pNext->fFileFlags | ISO9660_FILE_FLAGS_MULTI_EXTENT) == pFirst->fFileFlags))
+ {
+ if (RT_LIKELY(memcmp(pNext->achFileId, pFirst->achFileId, pNext->bFileIdLength) == 0))
+ return true;
+ }
+ }
+ return false;
+}
+#endif /* RT_STRICT */
+
+
+/**
+ * Parses rock ridge information if present in the directory entry.
+ *
+ * @param pVol The volume structure.
+ * @param pParseInfo Parse info and output.
+ * @param pbSys The system area of the directory record.
+ * @param cbSys The number of bytes present in the sys area.
+ * @param fIsFirstDirRec Set if this is the '.' directory entry in the
+ * root directory. (Some entries applies only to
+ * it.)
+ * @param fContinuationRecord Set if we're processing a continuation record in
+ * living in the abRockBuf.
+ */
+static void rtFsIsoDirShrd_ParseRockRidgeData(PRTFSISOVOL pVol, PRTFSISOROCKINFO pParseInfo, uint8_t const *pbSys,
+ size_t cbSys, bool fIsFirstDirRec, bool fContinuationRecord)
+{
+ while (cbSys >= 4)
+ {
+ /*
+ * Check header length and advance the sys variables.
+ */
+ PCISO9660SUSPUNION pUnion = (PCISO9660SUSPUNION)pbSys;
+ if ( pUnion->Hdr.cbEntry > cbSys
+ || pUnion->Hdr.cbEntry < sizeof(pUnion->Hdr))
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: cbEntry=%#x cbSys=%#x (%#x %#x)\n",
+ pUnion->Hdr.cbEntry, cbSys, pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ break;
+ }
+ pbSys += pUnion->Hdr.cbEntry;
+ cbSys -= pUnion->Hdr.cbEntry;
+
+ /*
+ * Process fields.
+ */
+ uint16_t const uSig = SUSP_MAKE_SIG(pUnion->Hdr.bSig1, pUnion->Hdr.bSig2);
+ switch (uSig)
+ {
+ /*
+ * System use sharing protocol entries.
+ */
+ case SUSP_MAKE_SIG(ISO9660SUSPCE_SIG1, ISO9660SUSPCE_SIG2):
+ {
+ if (RT_BE2H_U32(pUnion->CE.offBlock.be) != RT_LE2H_U32(pUnion->CE.offBlock.le))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Invalid CE offBlock field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.offBlock.be), RT_LE2H_U32(pUnion->CE.offBlock.le)));
+ else if (RT_BE2H_U32(pUnion->CE.cbData.be) != RT_LE2H_U32(pUnion->CE.cbData.le))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Invalid CE cbData field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.cbData.be), RT_LE2H_U32(pUnion->CE.cbData.le)));
+ else if (RT_BE2H_U32(pUnion->CE.offData.be) != RT_LE2H_U32(pUnion->CE.offData.le))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Invalid CE offData field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.offData.be), RT_LE2H_U32(pUnion->CE.offData.le)));
+ else if (!fContinuationRecord)
+ {
+ uint64_t offData = ISO9660_GET_ENDIAN(&pUnion->CE.offBlock) * (uint64_t)ISO9660_SECTOR_SIZE;
+ offData += ISO9660_GET_ENDIAN(&pUnion->CE.offData);
+ uint32_t cbData = ISO9660_GET_ENDIAN(&pUnion->CE.cbData);
+ if (cbData <= sizeof(pVol->abRockBuf) - (uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK))
+ {
+ RTCritSectEnter(&pVol->RockBufLock);
+
+ AssertCompile(sizeof(pVol->abRockBuf) == ISO9660_SECTOR_SIZE);
+ uint64_t offDataBlock = offData & ~(uint64_t)ISO9660_SECTOR_OFFSET_MASK;
+ if (pVol->offRockBuf == offDataBlock)
+ rtFsIsoDirShrd_ParseRockRidgeData(pVol, pParseInfo,
+ &pVol->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, fIsFirstDirRec, true /*fContinuationRecord*/);
+ else
+ {
+ int rc = RTVfsFileReadAt(pVol->hVfsBacking, offDataBlock,
+ pVol->abRockBuf, sizeof(pVol->abRockBuf), NULL);
+ if (RT_SUCCESS(rc))
+ rtFsIsoDirShrd_ParseRockRidgeData(pVol, pParseInfo,
+ &pVol->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, fIsFirstDirRec, true /*fContinuationRecord*/);
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Error reading continuation record at %#RX64: %Rrc\n",
+ offDataBlock, rc));
+ }
+
+ RTCritSectLeave(&pVol->RockBufLock);
+ }
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: continuation record isn't within a sector! offData=%#RX64 cbData=%#RX32\n",
+ cbData, offData));
+ }
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: nested continuation record!\n"));
+ break;
+ }
+
+ case SUSP_MAKE_SIG(ISO9660SUSPSP_SIG1, ISO9660SUSPSP_SIG2): /* SP */
+ if ( pUnion->Hdr.cbEntry != ISO9660SUSPSP_LEN
+ || pUnion->Hdr.bVersion != ISO9660SUSPSP_VER
+ || pUnion->SP.bCheck1 != ISO9660SUSPSP_CHECK1
+ || pUnion->SP.bCheck2 != ISO9660SUSPSP_CHECK2
+ || pUnion->SP.cbSkip > UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SP' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x), bCheck1=%#x (vs %#x), bCheck2=%#x (vs %#x), cbSkip=%#x (vs max %#x)\n",
+ pUnion->Hdr.cbEntry, ISO9660SUSPSP_LEN, pUnion->Hdr.bVersion, ISO9660SUSPSP_VER,
+ pUnion->SP.bCheck1, ISO9660SUSPSP_CHECK1, pUnion->SP.bCheck2, ISO9660SUSPSP_CHECK2,
+ pUnion->SP.cbSkip, UINT8_MAX - RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]) ));
+ else if (!fIsFirstDirRec)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignorining 'SP' entry in non-root directory record\n"));
+ else if (pParseInfo->fSuspSeenSP)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignorining additional 'SP' entry\n"));
+ else
+ {
+ pVol->offSuspSkip = pUnion->SP.cbSkip;
+ if (pUnion->SP.cbSkip != 0)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: SP: cbSkip=%#x\n", pUnion->SP.cbSkip));
+ }
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660SUSPER_SIG1, ISO9660SUSPER_SIG2): /* ER */
+ if ( pUnion->Hdr.cbEntry > RT_UOFFSETOF(ISO9660SUSPER, achPayload) + (uint32_t)pUnion->ER.cchIdentifier
+ + (uint32_t)pUnion->ER.cchDescription + (uint32_t)pUnion->ER.cchSource
+ || pUnion->Hdr.bVersion != ISO9660SUSPER_VER)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'ER' entry: cbEntry=%#x bVersion=%#x (vs %#x) cchIdentifier=%#x cchDescription=%#x cchSource=%#x\n",
+ pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion, ISO9660SUSPER_VER, pUnion->ER.cchIdentifier,
+ pUnion->ER.cchDescription, pUnion->ER.cchSource));
+ else if (!fIsFirstDirRec)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignorining 'ER' entry in non-root directory record\n"));
+ else if ( pUnion->ER.bVersion == 1 /* RRIP detection */
+ && ( (pUnion->ER.cchIdentifier >= 4 && strncmp(pUnion->ER.achPayload, ISO9660_RRIP_ID, 4 /*RRIP*/) == 0)
+ || (pUnion->ER.cchIdentifier >= 10 && strncmp(pUnion->ER.achPayload, RT_STR_TUPLE(ISO9660_RRIP_1_12_ID)) == 0) ))
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Rock Ridge 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n",
+ pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload,
+ pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier],
+ pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription]));
+ pVol->fHaveRock = true;
+ pParseInfo->cRockEntries++;
+ }
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Unknown extension in 'ER' entry: v%u id='%.*s' desc='%.*s' source='%.*s'\n",
+ pUnion->ER.bVersion, pUnion->ER.cchIdentifier, pUnion->ER.achPayload,
+ pUnion->ER.cchDescription, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier],
+ pUnion->ER.cchSource, &pUnion->ER.achPayload[pUnion->ER.cchIdentifier + pUnion->ER.cchDescription]));
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660SUSPPD_SIG1, ISO9660SUSPPD_SIG2): /* PD - ignored */
+ case SUSP_MAKE_SIG(ISO9660SUSPST_SIG1, ISO9660SUSPST_SIG2): /* ST - ignore for now */
+ case SUSP_MAKE_SIG(ISO9660SUSPES_SIG1, ISO9660SUSPES_SIG2): /* ES - ignore for now */
+ break;
+
+ /*
+ * Rock ridge interchange protocol entries.
+ */
+ case SUSP_MAKE_SIG(ISO9660RRIPRR_SIG1, ISO9660RRIPRR_SIG2): /* RR */
+ if ( pUnion->RR.Hdr.cbEntry != ISO9660RRIPRR_LEN
+ || pUnion->RR.Hdr.bVersion != ISO9660RRIPRR_VER)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'RR' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n",
+ pUnion->RR.Hdr.cbEntry, ISO9660RRIPRR_LEN, pUnion->RR.Hdr.bVersion, ISO9660RRIPRR_VER, pUnion->RR.fFlags));
+ else
+ pParseInfo->cRockEntries++; /* otherwise ignored */
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPPX_SIG1, ISO9660RRIPPX_SIG2): /* PX */
+ if ( ( pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN
+ && pUnion->PX.Hdr.cbEntry != ISO9660RRIPPX_LEN_NO_INODE)
+ || pUnion->PX.Hdr.bVersion != ISO9660RRIPPX_VER
+ || RT_BE2H_U32(pUnion->PX.fMode.be) != RT_LE2H_U32(pUnion->PX.fMode.le)
+ || RT_BE2H_U32(pUnion->PX.cHardlinks.be) != RT_LE2H_U32(pUnion->PX.cHardlinks.le)
+ || RT_BE2H_U32(pUnion->PX.uid.be) != RT_LE2H_U32(pUnion->PX.uid.le)
+ || RT_BE2H_U32(pUnion->PX.gid.be) != RT_LE2H_U32(pUnion->PX.gid.le)
+ || ( pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN
+ && RT_BE2H_U32(pUnion->PX.INode.be) != RT_LE2H_U32(pUnion->PX.INode.le)) )
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'PX' entry: cbEntry=%#x (vs %#x or %#x), bVersion=%#x (vs %#x) fMode=%#x/%#x cHardlinks=%#x/%#x uid=%#x/%#x gid=%#x/%#x inode=%#x/%#x\n",
+ pUnion->PX.Hdr.cbEntry, ISO9660RRIPPX_LEN, ISO9660RRIPPX_LEN_NO_INODE,
+ pUnion->PX.Hdr.bVersion, ISO9660RRIPPX_VER,
+ RT_BE2H_U32(pUnion->PX.fMode.be), RT_LE2H_U32(pUnion->PX.fMode.le),
+ RT_BE2H_U32(pUnion->PX.cHardlinks.be), RT_LE2H_U32(pUnion->PX.cHardlinks.le),
+ RT_BE2H_U32(pUnion->PX.uid.be), RT_LE2H_U32(pUnion->PX.uid.le),
+ RT_BE2H_U32(pUnion->PX.gid.be), RT_LE2H_U32(pUnion->PX.gid.le),
+ pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_BE2H_U32(pUnion->PX.INode.be) : 0,
+ pUnion->PX.Hdr.cbEntry == ISO9660RRIPPX_LEN ? RT_LE2H_U32(pUnion->PX.INode.le) : 0 ));
+ else
+ {
+ if ( RTFS_IS_DIRECTORY(ISO9660_GET_ENDIAN(&pUnion->PX.fMode))
+ == RTFS_IS_DIRECTORY(pParseInfo->Info.Attr.fMode))
+ pParseInfo->Info.Attr.fMode = ISO9660_GET_ENDIAN(&pUnion->PX.fMode);
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 'PX' entry changes directory-ness: fMode=%#x, existing %#x; ignored\n",
+ ISO9660_GET_ENDIAN(&pUnion->PX.fMode), pParseInfo->Info.Attr.fMode));
+ pParseInfo->Info.Attr.u.Unix.cHardlinks = ISO9660_GET_ENDIAN(&pUnion->PX.cHardlinks);
+ pParseInfo->Info.Attr.u.Unix.uid = ISO9660_GET_ENDIAN(&pUnion->PX.uid);
+ pParseInfo->Info.Attr.u.Unix.gid = ISO9660_GET_ENDIAN(&pUnion->PX.gid);
+ /* ignore inode */
+ pParseInfo->cRockEntries++;
+ }
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPPN_SIG1, ISO9660RRIPPN_SIG2): /* PN */
+ if ( pUnion->PN.Hdr.cbEntry != ISO9660RRIPPN_LEN
+ || pUnion->PN.Hdr.bVersion != ISO9660RRIPPN_VER
+ || RT_BE2H_U32(pUnion->PN.Major.be) != RT_LE2H_U32(pUnion->PN.Major.le)
+ || RT_BE2H_U32(pUnion->PN.Minor.be) != RT_LE2H_U32(pUnion->PN.Minor.le))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'PN' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) Major=%#x/%#x Minor=%#x/%#x\n",
+ pUnion->PN.Hdr.cbEntry, ISO9660RRIPPN_LEN, pUnion->PN.Hdr.bVersion, ISO9660RRIPPN_VER,
+ RT_BE2H_U32(pUnion->PN.Major.be), RT_LE2H_U32(pUnion->PN.Major.le),
+ RT_BE2H_U32(pUnion->PN.Minor.be), RT_LE2H_U32(pUnion->PN.Minor.le) ));
+ else if (RTFS_IS_DIRECTORY(pParseInfo->Info.Attr.fMode))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignorning 'PN' entry for directory (%#x/%#x)\n",
+ ISO9660_GET_ENDIAN(&pUnion->PN.Major), ISO9660_GET_ENDIAN(&pUnion->PN.Minor) ));
+ else
+ {
+ pParseInfo->Info.Attr.u.Unix.Device = RTDEV_MAKE(ISO9660_GET_ENDIAN(&pUnion->PN.Major),
+ ISO9660_GET_ENDIAN(&pUnion->PN.Minor));
+ pParseInfo->cRockEntries++;
+ }
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPTF_SIG1, ISO9660RRIPTF_SIG2): /* TF */
+ if ( pUnion->TF.Hdr.bVersion != ISO9660RRIPTF_VER
+ || pUnion->TF.Hdr.cbEntry < Iso9660RripTfCalcLength(pUnion->TF.fFlags))
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'TF' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x\n",
+ pUnion->TF.Hdr.cbEntry, Iso9660RripTfCalcLength(pUnion->TF.fFlags),
+ pUnion->TF.Hdr.bVersion, ISO9660RRIPTF_VER, RT_BE2H_U32(pUnion->TF.fFlags) ));
+ else if (!(pUnion->TF.fFlags & ISO9660RRIPTF_F_LONG_FORM))
+ {
+ PCISO9660RECTIMESTAMP pTimestamp = (PCISO9660RECTIMESTAMP)&pUnion->TF.abPayload[0];
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH)
+ {
+ rtFsIso9660DateTime2TimeSpec(&pParseInfo->Info.BirthTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY)
+ {
+ rtFsIso9660DateTime2TimeSpec(&pParseInfo->Info.ModificationTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS)
+ {
+ rtFsIso9660DateTime2TimeSpec(&pParseInfo->Info.AccessTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE)
+ {
+ rtFsIso9660DateTime2TimeSpec(&pParseInfo->Info.ChangeTime, pTimestamp);
+ pTimestamp++;
+ }
+ pParseInfo->cRockEntries++;
+ }
+ else
+ {
+ PCISO9660TIMESTAMP pTimestamp = (PCISO9660TIMESTAMP)&pUnion->TF.abPayload[0];
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_BIRTH)
+ {
+ rtFsIso9660DateTime2TimeSpecIfValid(&pParseInfo->Info.BirthTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_MODIFY)
+ {
+ rtFsIso9660DateTime2TimeSpecIfValid(&pParseInfo->Info.ModificationTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_ACCESS)
+ {
+ rtFsIso9660DateTime2TimeSpecIfValid(&pParseInfo->Info.AccessTime, pTimestamp);
+ pTimestamp++;
+ }
+ if (pUnion->TF.fFlags & ISO9660RRIPTF_F_CHANGE)
+ {
+ rtFsIso9660DateTime2TimeSpecIfValid(&pParseInfo->Info.ChangeTime, pTimestamp);
+ pTimestamp++;
+ }
+ pParseInfo->cRockEntries++;
+ }
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPSF_SIG1, ISO9660RRIPSF_SIG2): /* SF */
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Sparse file support not yet implemented!\n"));
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPSL_SIG1, ISO9660RRIPSL_SIG2): /* SL */
+ if ( pUnion->SL.Hdr.bVersion != ISO9660RRIPSL_VER
+ || pUnion->SL.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2])
+ || (pUnion->SL.fFlags & ~ISO9660RRIP_SL_F_CONTINUE)
+ || (pUnion->SL.abComponents[0] & ISO9660RRIP_SL_C_RESERVED_MASK) )
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SL' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x comp[0].fFlags=%#x\n",
+ pUnion->SL.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPSL, abComponents[2]),
+ pUnion->SL.Hdr.bVersion, ISO9660RRIPSL_VER, pUnion->SL.fFlags, pUnion->SL.abComponents[0]));
+ else if (pParseInfo->fSeenLastSL)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Unexpected 'SL!' entry\n"));
+ else
+ {
+ pParseInfo->cRockEntries++;
+ pParseInfo->fSeenLastSL = !(pUnion->SL.fFlags & ISO9660RRIP_SL_F_CONTINUE); /* used in loop */
+
+ size_t offDst = pParseInfo->cchLinkTarget;
+ uint8_t const *pbSrc = &pUnion->SL.abComponents[0];
+ uint8_t cbSrcLeft = pUnion->SL.Hdr.cbEntry - RT_UOFFSETOF(ISO9660RRIPSL, abComponents);
+ while (cbSrcLeft >= 2)
+ {
+ uint8_t const fFlags = pbSrc[0];
+ uint8_t cchCopy = pbSrc[1];
+ uint8_t const cbSkip = cchCopy + 2;
+ if (cbSkip > cbSrcLeft)
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SL' component: component flags=%#x, component length+2=%#x vs %#x left\n",
+ fFlags, cbSkip, cbSrcLeft));
+ break;
+ }
+
+ const char *pszCopy;
+ switch (fFlags & ~ISO9660RRIP_SL_C_CONTINUE)
+ {
+ case 0:
+ pszCopy = (const char *)&pbSrc[2];
+ break;
+
+ case ISO9660RRIP_SL_C_CURRENT:
+ if (cchCopy != 0)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SL' component: CURRENT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = ".";
+ cchCopy = 1;
+ break;
+
+ case ISO9660RRIP_SL_C_PARENT:
+ if (cchCopy != 0)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SL' component: PARENT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = "..";
+ cchCopy = 2;
+ break;
+
+ case ISO9660RRIP_SL_C_ROOT:
+ if (cchCopy != 0)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SL' component: ROOT + %u bytes, ignoring bytes\n", cchCopy));
+ pszCopy = "/";
+ cchCopy = 1;
+ break;
+
+ default:
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'SL' component: component flags=%#x (bad), component length=%#x vs %#x left\n",
+ fFlags, cchCopy, cbSrcLeft));
+ pszCopy = NULL;
+ cchCopy = 0;
+ break;
+ }
+
+ if (offDst + cchCopy < sizeof(pParseInfo->szLinkTarget))
+ {
+ memcpy(&pParseInfo->szLinkTarget[offDst], pszCopy, cchCopy);
+ offDst += cchCopy;
+ }
+ else
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 'SL' constructs a too long target! '%.*s%.*s'\n",
+ offDst, pParseInfo->szLinkTarget, cchCopy, pszCopy));
+ memcpy(&pParseInfo->szLinkTarget[offDst], pszCopy, sizeof(pParseInfo->szLinkTarget) - offDst - 1);
+ offDst = sizeof(pParseInfo->szLinkTarget) - 1;
+ pParseInfo->fOverflowSL = true;
+ break;
+ }
+
+ /* Advance */
+ pbSrc += cbSkip;
+ cbSrcLeft -= cbSkip;
+
+ /* Append slash if appropriate. */
+ if ( !(fFlags & ISO9660RRIP_SL_C_CONTINUE)
+ && (cbSrcLeft >= 2 || !pParseInfo->fSeenLastSL) )
+ {
+ if (offDst + 1 < sizeof(pParseInfo->szLinkTarget))
+ pParseInfo->szLinkTarget[offDst++] = '/';
+ else
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 'SL' constructs a too long target! '%.*s/'\n",
+ offDst, pParseInfo->szLinkTarget));
+ pParseInfo->fOverflowSL = true;
+ break;
+ }
+ }
+ }
+ Assert(offDst < sizeof(pParseInfo->szLinkTarget));
+ pParseInfo->szLinkTarget[offDst] = '\0';
+ pParseInfo->cchLinkTarget = (uint16_t)offDst;
+ }
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPNM_SIG1, ISO9660RRIPNM_SIG2): /* NM */
+ if ( pUnion->NM.Hdr.bVersion != ISO9660RRIPNM_VER
+ || pUnion->NM.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPNM, achName)
+ || (pUnion->NM.fFlags & ISO9660RRIP_NM_F_RESERVED_MASK) )
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Malformed 'NM' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x %.*Rhxs\n",
+ pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName),
+ pUnion->NM.Hdr.bVersion, ISO9660RRIPNM_VER, pUnion->NM.fFlags,
+ pUnion->NM.Hdr.cbEntry - RT_MIN(pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName)),
+ &pUnion->NM.achName[0] ));
+ else if (pParseInfo->fSeenLastNM)
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Unexpected 'NM' entry!\n"));
+ else
+ {
+ pParseInfo->cRockEntries++;
+ pParseInfo->fSeenLastNM = !(pUnion->NM.fFlags & ISO9660RRIP_NM_F_CONTINUE);
+
+ uint8_t const cchName = pUnion->NM.Hdr.cbEntry - (uint8_t)RT_UOFFSETOF(ISO9660RRIPNM, achName);
+ if (pUnion->NM.fFlags & (ISO9660RRIP_NM_F_CURRENT | ISO9660RRIP_NM_F_PARENT))
+ {
+ if (cchName == 0 && pParseInfo->szName[0] == '\0')
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignoring 'NM' entry for '.' and '..'\n"));
+ else
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignoring malformed 'NM' using '.' or '..': fFlags=%#x cchName=%#x %.*Rhxs; szRockNameBuf='%s'\n",
+ pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName, pParseInfo->szName));
+ pParseInfo->szName[0] = '\0';
+ pParseInfo->cchName = 0;
+ pParseInfo->fSeenLastNM = true;
+ }
+ else
+ {
+ size_t offDst = pParseInfo->cchName;
+ if (offDst + cchName < sizeof(pParseInfo->szName))
+ {
+ memcpy(&pParseInfo->szName[offDst], pUnion->NM.achName, cchName);
+ offDst += cchName;
+ pParseInfo->szName[offDst] = '\0';
+ pParseInfo->cchName = (uint16_t)offDst;
+ }
+ else
+ {
+ Log4(("rtFsIsoDir_ParseRockRidgeData: 'NM' constructs a too long name, ignoring it all: '%s%.*s'\n",
+ pParseInfo->szName, cchName, pUnion->NM.achName));
+ pParseInfo->szName[0] = '\0';
+ pParseInfo->cchName = 0;
+ pParseInfo->fSeenLastNM = true;
+ }
+ }
+ }
+ break;
+
+ case SUSP_MAKE_SIG(ISO9660RRIPCL_SIG1, ISO9660RRIPCL_SIG2): /* CL - just warn for now. */
+ case SUSP_MAKE_SIG(ISO9660RRIPPL_SIG1, ISO9660RRIPPL_SIG2): /* PL - just warn for now. */
+ case SUSP_MAKE_SIG(ISO9660RRIPRE_SIG1, ISO9660RRIPRE_SIG2): /* RE - just warn for now. */
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Ignorning directory relocation entry '%c%c'!\n", pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ break;
+
+ default:
+ Log4(("rtFsIsoDir_ParseRockRidgeData: Unknown SUSP entry: %#x %#x, %#x bytes, v%u\n",
+ pUnion->Hdr.bSig1, pUnion->Hdr.bSig2, pUnion->Hdr.cbEntry, pUnion->Hdr.bVersion));
+ break;
+ }
+ }
+
+ /*
+ * Set the valid flag if we found anything of interest.
+ */
+ if (pParseInfo->cRockEntries > 1)
+ pParseInfo->fValid = true;
+}
+
+
+/**
+ * Initializes the rock info structure with info from the standard ISO-9660
+ * directory record.
+ *
+ * @param pRockInfo The structure to initialize.
+ * @param pDirRec The directory record to take basic data from.
+ */
+static void rtFsIsoDirShrd_InitRockInfo(PRTFSISOROCKINFO pRockInfo, PCISO9660DIRREC pDirRec)
+{
+ pRockInfo->fValid = false;
+ pRockInfo->fSuspSeenSP = false;
+ pRockInfo->fSeenLastNM = false;
+ pRockInfo->fSeenLastSL = false;
+ pRockInfo->fOverflowSL = false;
+ pRockInfo->cRockEntries = 0;
+ pRockInfo->cchName = 0;
+ pRockInfo->cchLinkTarget = 0;
+ pRockInfo->szName[0] = '\0';
+ pRockInfo->szName[sizeof(pRockInfo->szName) - 1] = '\0';
+ pRockInfo->szLinkTarget[0] = '\0';
+ pRockInfo->szLinkTarget[sizeof(pRockInfo->szLinkTarget) - 1] = '\0';
+ pRockInfo->Info.cbObject = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ pRockInfo->Info.cbAllocated = pRockInfo->Info.cbObject;
+ rtFsIso9660DateTime2TimeSpec(&pRockInfo->Info.AccessTime, &pDirRec->RecTime);
+ pRockInfo->Info.ModificationTime = pRockInfo->Info.AccessTime;
+ pRockInfo->Info.ChangeTime = pRockInfo->Info.AccessTime;
+ pRockInfo->Info.BirthTime = pRockInfo->Info.AccessTime;
+ pRockInfo->Info.Attr.fMode = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY
+ ? RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | 0555
+ : RTFS_TYPE_FILE | RTFS_DOS_ARCHIVED | 0444;
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_HIDDEN)
+ pRockInfo->Info.Attr.fMode |= RTFS_DOS_HIDDEN;
+ pRockInfo->Info.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ pRockInfo->Info.Attr.u.Unix.uid = NIL_RTUID;
+ pRockInfo->Info.Attr.u.Unix.gid = NIL_RTGID;
+ pRockInfo->Info.Attr.u.Unix.cHardlinks = 1;
+ pRockInfo->Info.Attr.u.Unix.INodeIdDevice = 0;
+ pRockInfo->Info.Attr.u.Unix.INodeId = 0;
+ pRockInfo->Info.Attr.u.Unix.fFlags = 0;
+ pRockInfo->Info.Attr.u.Unix.GenerationId = 0;
+ pRockInfo->Info.Attr.u.Unix.Device = 0;
+}
+
+
+static void rtFsIsoDirShrd_ParseRockForDirRec(PRTFSISODIRSHRD pThis, PCISO9660DIRREC pDirRec, PRTFSISOROCKINFO pRockInfo)
+{
+ rtFsIsoDirShrd_InitRockInfo(pRockInfo, pDirRec); /* Always! */
+
+ PRTFSISOVOL const pVol = pThis->Core.pVol;
+ uint8_t cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId)
+ - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1);
+ uint8_t const *pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)];
+ if (cbSys >= 4 + pVol->offSuspSkip)
+ {
+ pbSys += pVol->offSuspSkip;
+ cbSys -= pVol->offSuspSkip;
+ rtFsIsoDirShrd_ParseRockRidgeData(pVol, pRockInfo, pbSys, cbSys,
+ false /*fIsFirstDirRec*/, false /*fContinuationRecord*/);
+ }
+}
+
+
+static void rtFsIsoDirShrd_ParseRockForRoot(PRTFSISODIRSHRD pThis, PCISO9660DIRREC pDirRec)
+{
+ uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId)
+ - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1);
+ uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)];
+ if (cbSys >= 4)
+ {
+ RTFSISOROCKINFO RockInfo;
+ rtFsIsoDirShrd_InitRockInfo(&RockInfo, pDirRec);
+ rtFsIsoDirShrd_ParseRockRidgeData(pThis->Core.pVol, &RockInfo, pbSys, cbSys,
+ true /*fIsFirstDirRec*/, false /*fContinuationRecord*/);
+ if (RockInfo.fValid)
+ {
+ pThis->Core.fHaveRockInfo = true;
+ pThis->Core.BirthTime = RockInfo.Info.BirthTime;
+ pThis->Core.ChangeTime = RockInfo.Info.ChangeTime;
+ pThis->Core.AccessTime = RockInfo.Info.AccessTime;
+ pThis->Core.ModificationTime = RockInfo.Info.ModificationTime;
+ if (RTFS_IS_DIRECTORY(RockInfo.Info.Attr.fMode))
+ pThis->Core.fAttrib = RockInfo.Info.Attr.fMode;
+ }
+ }
+}
+
+
+/**
+ * Compares rock ridge information if present in the directory entry.
+ *
+ * @param pThis The shared directory structure.
+ * @param pbSys The system area of the directory record.
+ * @param cbSys The number of bytes present in the sys area.
+ * @param pNameCmp The name comparsion data.
+ * @param fContinuationRecord Set if we're processing a continuation record in
+ * living in the abRockBuf.
+ */
+static int rtFsIsoDirShrd_CompareRockRidgeName(PRTFSISODIRSHRD pThis, uint8_t const *pbSys, size_t cbSys,
+ PRTFSISOROCKNAMECOMP pNameCmp, bool fContinuationRecord)
+{
+ PRTFSISOVOL const pVol = pThis->Core.pVol;
+
+ /*
+ * Do skipping if specified.
+ */
+ if (pVol->offSuspSkip)
+ {
+ if (cbSys <= pVol->offSuspSkip)
+ return fContinuationRecord ? VERR_MORE_DATA : VERR_MISMATCH;
+ pbSys += pVol->offSuspSkip;
+ cbSys -= pVol->offSuspSkip;
+ }
+
+ while (cbSys >= 4)
+ {
+ /*
+ * Check header length and advance the sys variables.
+ */
+ PCISO9660SUSPUNION pUnion = (PCISO9660SUSPUNION)pbSys;
+ if ( pUnion->Hdr.cbEntry > cbSys
+ && pUnion->Hdr.cbEntry < sizeof(pUnion->Hdr))
+ {
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: cbEntry=%#x cbSys=%#x (%#x %#x)\n",
+ pUnion->Hdr.cbEntry, cbSys, pUnion->Hdr.bSig1, pUnion->Hdr.bSig2));
+ break;
+ }
+ pbSys += pUnion->Hdr.cbEntry;
+ cbSys -= pUnion->Hdr.cbEntry;
+
+ /*
+ * Process the fields we need, nothing else.
+ */
+ uint16_t const uSig = SUSP_MAKE_SIG(pUnion->Hdr.bSig1, pUnion->Hdr.bSig2);
+
+
+ /*
+ * CE - continuation entry
+ */
+ if (uSig == SUSP_MAKE_SIG(ISO9660SUSPCE_SIG1, ISO9660SUSPCE_SIG2))
+ {
+ if (RT_BE2H_U32(pUnion->CE.offBlock.be) != RT_LE2H_U32(pUnion->CE.offBlock.le))
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Invalid CE offBlock field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.offBlock.be), RT_LE2H_U32(pUnion->CE.offBlock.le)));
+ else if (RT_BE2H_U32(pUnion->CE.cbData.be) != RT_LE2H_U32(pUnion->CE.cbData.le))
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Invalid CE cbData field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.cbData.be), RT_LE2H_U32(pUnion->CE.cbData.le)));
+ else if (RT_BE2H_U32(pUnion->CE.offData.be) != RT_LE2H_U32(pUnion->CE.offData.le))
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Invalid CE offData field: be=%#x vs le=%#x\n",
+ RT_BE2H_U32(pUnion->CE.offData.be), RT_LE2H_U32(pUnion->CE.offData.le)));
+ else if (!fContinuationRecord)
+ {
+ uint64_t offData = ISO9660_GET_ENDIAN(&pUnion->CE.offBlock) * (uint64_t)ISO9660_SECTOR_SIZE;
+ offData += ISO9660_GET_ENDIAN(&pUnion->CE.offData);
+ uint32_t cbData = ISO9660_GET_ENDIAN(&pUnion->CE.cbData);
+ if (cbData <= sizeof(pVol->abRockBuf) - (uint32_t)(offData & ISO9660_SECTOR_OFFSET_MASK))
+ {
+ RTCritSectEnter(&pVol->RockBufLock);
+
+ AssertCompile(sizeof(pVol->abRockBuf) == ISO9660_SECTOR_SIZE);
+ uint64_t offDataBlock = offData & ~(uint64_t)ISO9660_SECTOR_OFFSET_MASK;
+ int rc;
+ if (pVol->offRockBuf == offDataBlock)
+ rc = rtFsIsoDirShrd_CompareRockRidgeName(pThis, &pVol->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, pNameCmp, true /*fContinuationRecord*/);
+ else
+ {
+ rc = RTVfsFileReadAt(pVol->hVfsBacking, offDataBlock, pVol->abRockBuf, sizeof(pVol->abRockBuf), NULL);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoDirShrd_CompareRockRidgeName(pThis, &pVol->abRockBuf[offData & ISO9660_SECTOR_OFFSET_MASK],
+ cbData, pNameCmp, true /*fContinuationRecord*/);
+ else
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Error reading continuation record at %#RX64: %Rrc\n",
+ offDataBlock, rc));
+ }
+
+ RTCritSectLeave(&pVol->RockBufLock);
+ if (rc != VERR_MORE_DATA)
+ return rc;
+ }
+ else
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: continuation record isn't within a sector! offData=%#RX64 cbData=%#RX32\n",
+ cbData, offData));
+ }
+ else
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: nested continuation record!\n"));
+ }
+ /*
+ * NM - Name entry.
+ *
+ * The character set is supposed to be limited to the portable filename
+ * character set defined in section 2.2.2.60 of POSIX.1: A-Za-z0-9._-
+ * If there are any other characters used, we consider them as UTF-8
+ * for reasons of simplicitiy, however we do not make any effort dealing
+ * with codepoint encodings across NM records for now because it is
+ * probably a complete waste of time.
+ */
+ else if (uSig == SUSP_MAKE_SIG(ISO9660RRIPNM_SIG1, ISO9660RRIPNM_SIG2))
+ {
+ if ( pUnion->NM.Hdr.bVersion != ISO9660RRIPNM_VER
+ || pUnion->NM.Hdr.cbEntry < RT_UOFFSETOF(ISO9660RRIPNM, achName)
+ || (pUnion->NM.fFlags & ISO9660RRIP_NM_F_RESERVED_MASK) )
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Malformed 'NM' entry: cbEntry=%#x (vs %#x), bVersion=%#x (vs %#x) fFlags=%#x %.*Rhxs\n",
+ pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName),
+ pUnion->NM.Hdr.bVersion, ISO9660RRIPNM_VER, pUnion->NM.fFlags,
+ pUnion->NM.Hdr.cbEntry - RT_MIN(pUnion->NM.Hdr.cbEntry, RT_UOFFSETOF(ISO9660RRIPNM, achName)),
+ &pUnion->NM.achName[0] ));
+ else
+ {
+ uint8_t const cchName = pUnion->NM.Hdr.cbEntry - (uint8_t)RT_UOFFSETOF(ISO9660RRIPNM, achName);
+ if (!(pUnion->NM.fFlags & (ISO9660RRIP_NM_F_CURRENT | ISO9660RRIP_NM_F_PARENT)))
+ { /* likely */ }
+ else
+ {
+ if (cchName == 0)
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Ignoring 'NM' entry for '.' and '..'\n"));
+ else
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: Ignoring malformed 'NM' using '.' or '..': fFlags=%#x cchName=%#x %.*Rhxs\n",
+ pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName));
+ pNameCmp->offMatched = ~(size_t)0 / 2;
+ return VERR_MISMATCH;
+ }
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': fFlags=%#x cchName=%#x '%.*s' (%.*Rhxs); offMatched=%#zx cchEntry=%#zx\n",
+ pUnion->NM.fFlags, cchName, cchName, pUnion->NM.achName, cchName, pUnion->NM.achName, pNameCmp->offMatched, pNameCmp->cchEntry));
+ AssertReturn(pNameCmp->offMatched < pNameCmp->cchEntry, VERR_MISMATCH);
+
+ if (RTStrNICmp(&pNameCmp->pszEntry[pNameCmp->offMatched], pUnion->NM.achName, cchName) == 0)
+ {
+ /** @todo Incorrectly ASSUMES all upper and lower codepoints have the same
+ * encoding length. However, since this shouldn't be UTF-8, but plain
+ * limited ASCII that's not really all that important. */
+ pNameCmp->offMatched += cchName;
+ if (!(pUnion->NM.fFlags & ISO9660RRIP_NM_F_CONTINUE))
+ {
+ if (pNameCmp->offMatched >= pNameCmp->cchEntry)
+ {
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': returning VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+ }
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': returning VERR_MISMATCH - %zu unmatched bytes\n",
+ pNameCmp->cchEntry - pNameCmp->offMatched));
+ return VERR_MISMATCH;
+ }
+ if (pNameCmp->offMatched >= pNameCmp->cchEntry)
+ {
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': returning VERR_MISMATCH - match full name but ISO9660RRIP_NM_F_CONTINUE is set!\n"));
+ return VERR_MISMATCH;
+ }
+ }
+ else
+ {
+ Log4(("rtFsIsoDirShrd_CompareRockRidgeName: 'NM': returning VERR_MISMATCH - mismatch\n"));
+ pNameCmp->offMatched = ~(size_t)0 / 2;
+ return VERR_MISMATCH;
+ }
+ }
+ }
+ }
+ return fContinuationRecord ? VERR_MORE_DATA : VERR_MISMATCH;
+}
+
+
+/**
+ * Worker for rtFsIsoDir_FindEntry9660 that compares a name with the rock ridge
+ * info in the directory record, if present.
+ *
+ * @returns true if equal, false if not.
+ * @param pThis The directory.
+ * @param pDirRec The directory record.
+ * @param pszEntry The string to compare with.
+ * @param cbEntry The length of @a pszEntry including terminator.
+ */
+static bool rtFsIsoDir_IsEntryEqualRock(PRTFSISODIRSHRD pThis, PCISO9660DIRREC pDirRec, const char *pszEntry, size_t cbEntry)
+{
+ /*
+ * Is there room for any rock ridge data?
+ */
+ uint8_t const cbSys = pDirRec->cbDirRec - RT_UOFFSETOF(ISO9660DIRREC, achFileId)
+ - pDirRec->bFileIdLength - !(pDirRec->bFileIdLength & 1);
+ uint8_t const * const pbSys = (uint8_t const *)&pDirRec->achFileId[pDirRec->bFileIdLength + !(pDirRec->bFileIdLength & 1)];
+ if (cbSys >= 4)
+ {
+ RTFSISOROCKNAMECOMP NameCmp;
+ NameCmp.pszEntry = pszEntry;
+ NameCmp.cchEntry = cbEntry - 1;
+ NameCmp.offMatched = 0;
+ int rc = rtFsIsoDirShrd_CompareRockRidgeName(pThis, pbSys, cbSys, &NameCmp, false /*fContinuationRecord*/);
+ if (rc == VINF_SUCCESS)
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Worker for rtFsIsoDir_FindEntry9660 that compares a UTF-16BE name with a
+ * directory record.
+ *
+ * @returns true if equal, false if not.
+ * @param pDirRec The directory record.
+ * @param pwszEntry The UTF-16BE string to compare with.
+ * @param cbEntry The compare string length in bytes (sans zero
+ * terminator).
+ * @param cwcEntry The compare string length in RTUTF16 units.
+ * @param puVersion Where to return any file version number.
+ */
+DECL_FORCE_INLINE(bool) rtFsIsoDir_IsEntryEqualUtf16Big(PCISO9660DIRREC pDirRec, PCRTUTF16 pwszEntry, size_t cbEntry,
+ size_t cwcEntry, uint32_t *puVersion)
+{
+ /* ASSUME directories cannot have any version tags. */
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ {
+ if (RT_LIKELY(pDirRec->bFileIdLength != cbEntry))
+ return false;
+ if (RT_LIKELY(RTUtf16BigNICmp((PCRTUTF16)pDirRec->achFileId, pwszEntry, cwcEntry) != 0))
+ return false;
+ }
+ else
+ {
+ size_t cbNameDelta = (size_t)pDirRec->bFileIdLength - cbEntry;
+ if (RT_LIKELY(cbNameDelta > (size_t)12 /* ;12345 */))
+ return false;
+ if (cbNameDelta == 0)
+ {
+ if (RT_LIKELY(RTUtf16BigNICmp((PCRTUTF16)pDirRec->achFileId, pwszEntry, cwcEntry) != 0))
+ return false;
+ *puVersion = 1;
+ }
+ else
+ {
+ if (RT_LIKELY(RT_MAKE_U16(pDirRec->achFileId[cbEntry + 1], pDirRec->achFileId[cbEntry]) != ';'))
+ return false;
+ if (RT_LIKELY(RTUtf16BigNICmp((PCRTUTF16)pDirRec->achFileId, pwszEntry, cwcEntry) != 0))
+ return false;
+ uint32_t uVersion;
+ size_t cwcVersion = rtFsIso9660GetVersionLengthUtf16Big((PCRTUTF16)pDirRec->achFileId,
+ pDirRec->bFileIdLength, &uVersion);
+ if (RT_LIKELY(cwcVersion * sizeof(RTUTF16) == cbNameDelta))
+ *puVersion = uVersion;
+ else
+ return false;
+ }
+ }
+
+ /* (No need to check for dot and dot-dot here, because cbEntry must be a
+ multiple of two.) */
+ Assert(!(cbEntry & 1));
+ return true;
+}
+
+
+/**
+ * Worker for rtFsIsoDir_FindEntry9660 that compares an ASCII name with a
+ * directory record.
+ *
+ * @returns true if equal, false if not.
+ * @param pDirRec The directory record.
+ * @param pszEntry The uppercased ASCII string to compare with.
+ * @param cchEntry The length of the compare string.
+ * @param puVersion Where to return any file version number.
+ *
+ * @note We're using RTStrNICmpAscii here because of non-conforming ISOs with
+ * entirely lowercase name or mixed cased names.
+ */
+DECL_FORCE_INLINE(bool) rtFsIsoDir_IsEntryEqualAscii(PCISO9660DIRREC pDirRec, const char *pszEntry, size_t cchEntry,
+ uint32_t *puVersion)
+{
+ /* ASSUME directories cannot have any version tags. */
+ if (pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ {
+ if (RT_LIKELY(pDirRec->bFileIdLength != cchEntry))
+ return false;
+ if (RT_LIKELY(RTStrNICmpAscii(pDirRec->achFileId, pszEntry, cchEntry) != 0))
+ return false;
+ }
+ else
+ {
+ size_t cchNameDelta = (size_t)pDirRec->bFileIdLength - cchEntry;
+ if (RT_LIKELY(cchNameDelta > (size_t)6 /* ;12345 */))
+ return false;
+ if (cchNameDelta == 0)
+ {
+ if (RT_LIKELY(RTStrNICmpAscii(pDirRec->achFileId, pszEntry, cchEntry) != 0))
+ return false;
+ *puVersion = 1;
+ }
+ else
+ {
+ if (RT_LIKELY(pDirRec->achFileId[cchEntry] != ';'))
+ return false;
+ if (RT_LIKELY(RTStrNICmpAscii(pDirRec->achFileId, pszEntry, cchEntry) != 0))
+ return false;
+ uint32_t uVersion;
+ size_t cchVersion = rtFsIso9660GetVersionLengthAscii(pDirRec->achFileId, pDirRec->bFileIdLength, &uVersion);
+ if (RT_LIKELY(cchVersion == cchNameDelta))
+ *puVersion = uVersion;
+ else
+ return false;
+ }
+ }
+
+ /* Don't match the 'dot' and 'dot-dot' directory records. */
+ if (RT_LIKELY( pDirRec->bFileIdLength != 1
+ || (uint8_t)pDirRec->achFileId[0] > (uint8_t)0x01))
+ return true;
+ return false;
+}
+
+
+/**
+ * Locates a directory entry in a directory.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_FILE_NOT_FOUND if not found.
+ * @param pThis The directory to search.
+ * @param pszEntry The entry to look for.
+ * @param poffDirRec Where to return the offset of the directory record
+ * on the disk.
+ * @param ppDirRec Where to return the pointer to the directory record
+ * (the whole directory is buffered).
+ * @param pcDirRecs Where to return the number of directory records
+ * related to this entry.
+ * @param pfMode Where to return the file type, rock ridge adjusted.
+ * @param puVersion Where to return the file version number.
+ * @param pRockInfo Where to return rock ridge info. This is NULL if
+ * the volume didn't advertise any rock ridge info.
+ */
+static int rtFsIsoDir_FindEntry9660(PRTFSISODIRSHRD pThis, const char *pszEntry, uint64_t *poffDirRec, PCISO9660DIRREC *ppDirRec,
+ uint32_t *pcDirRecs, PRTFMODE pfMode, uint32_t *puVersion, PRTFSISOROCKINFO pRockInfo)
+{
+ Assert(pThis->Core.pVol->enmType != RTFSISOVOLTYPE_UDF);
+
+ /* Set return values. */
+ *poffDirRec = UINT64_MAX;
+ *ppDirRec = NULL;
+ *pcDirRecs = 1;
+ *pfMode = UINT32_MAX;
+ *puVersion = 0;
+ if (pRockInfo)
+ pRockInfo->fValid = false;
+
+ /*
+ * If we're in UTF-16BE mode, convert the input name to UTF-16BE. Otherwise try
+ * uppercase it into a ISO 9660 compliant name.
+ */
+ int rc;
+ bool const fIsUtf16 = pThis->Core.pVol->fIsUtf16;
+ size_t cwcEntry = 0;
+ size_t cbEntry = 0;
+ size_t cchUpper = ~(size_t)0;
+ union
+ {
+ RTUTF16 wszEntry[260 + 1];
+ struct
+ {
+ char szUpper[255 + 1];
+ char szRock[260 + 1];
+ } s;
+ } uBuf;
+ if (fIsUtf16)
+ {
+ PRTUTF16 pwszEntry = uBuf.wszEntry;
+ rc = RTStrToUtf16BigEx(pszEntry, RTSTR_MAX, &pwszEntry, RT_ELEMENTS(uBuf.wszEntry), &cwcEntry);
+ if (RT_FAILURE(rc))
+ return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc;
+ cbEntry = cwcEntry * 2;
+ }
+ else
+ {
+ rc = RTStrCopy(uBuf.s.szUpper, sizeof(uBuf.s.szUpper), pszEntry);
+ if (RT_FAILURE(rc))
+ return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc;
+ RTStrToUpper(uBuf.s.szUpper);
+ cchUpper = strlen(uBuf.s.szUpper);
+ cbEntry = strlen(pszEntry) + 1;
+ }
+
+ /*
+ * Scan the directory buffer by buffer.
+ */
+ uint32_t offEntryInDir = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ while (offEntryInDir + RT_UOFFSETOF(ISO9660DIRREC, achFileId) <= cbDir)
+ {
+ PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pThis->pbDir[offEntryInDir];
+
+ /* If null length, skip to the next sector. */
+ if (pDirRec->cbDirRec == 0)
+ offEntryInDir = (offEntryInDir + pThis->Core.pVol->cbSector) & ~(pThis->Core.pVol->cbSector - 1U);
+ else
+ {
+ /*
+ * Try match the filename.
+ */
+ /** @todo not sure if it's a great idea to match both name spaces... */
+ if (RT_LIKELY( fIsUtf16
+ ? !rtFsIsoDir_IsEntryEqualUtf16Big(pDirRec, uBuf.wszEntry, cbEntry, cwcEntry, puVersion)
+ && ( !pRockInfo
+ || !rtFsIsoDir_IsEntryEqualRock(pThis, pDirRec, pszEntry, cbEntry))
+ : ( !pRockInfo
+ || !rtFsIsoDir_IsEntryEqualRock(pThis, pDirRec, pszEntry, cbEntry))
+ && !rtFsIsoDir_IsEntryEqualAscii(pDirRec, uBuf.s.szUpper, cchUpper, puVersion) ))
+ {
+ /* Advance */
+ offEntryInDir += pDirRec->cbDirRec;
+ continue;
+ }
+
+ /*
+ * Get info for the entry.
+ */
+ if (!pRockInfo)
+ *pfMode = pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY
+ ? 0755 | RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY
+ : 0644 | RTFS_TYPE_FILE;
+ else
+ {
+ rtFsIsoDirShrd_ParseRockForDirRec(pThis, pDirRec, pRockInfo);
+ *pfMode = pRockInfo->Info.Attr.fMode;
+ }
+ *poffDirRec = pThis->Core.FirstExtent.off + offEntryInDir;
+ *ppDirRec = pDirRec;
+
+ /*
+ * Deal with the unlikely scenario of multi extent records.
+ */
+ if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ *pcDirRecs = 1;
+ else
+ {
+ offEntryInDir += pDirRec->cbDirRec;
+
+ uint32_t cDirRecs = 1;
+ while (offEntryInDir + RT_UOFFSETOF(ISO9660DIRREC, achFileId) <= cbDir)
+ {
+ PCISO9660DIRREC pDirRec2 = (PCISO9660DIRREC)&pThis->pbDir[offEntryInDir];
+ if (pDirRec2->cbDirRec != 0)
+ {
+ Assert(rtFsIsoDir_Is9660DirRecNextExtent(pDirRec, pDirRec2));
+ cDirRecs++;
+ if (!(pDirRec2->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ break;
+ offEntryInDir += pDirRec2->cbDirRec;
+ }
+ else
+ offEntryInDir = (offEntryInDir + pThis->Core.pVol->cbSector) & ~(pThis->Core.pVol->cbSector - 1U);
+ }
+
+ *pcDirRecs = cDirRecs;
+ }
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+/**
+ * Locates a directory entry in a directory.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_FILE_NOT_FOUND if not found.
+ * @param pThis The directory to search.
+ * @param pszEntry The entry to look for.
+ * @param ppFid Where to return the pointer to the file ID entry.
+ * (Points to the directory content.)
+ */
+static int rtFsIsoDir_FindEntryUdf(PRTFSISODIRSHRD pThis, const char *pszEntry, PCUDFFILEIDDESC *ppFid)
+{
+ Assert(pThis->Core.pVol->enmType == RTFSISOVOLTYPE_UDF);
+ *ppFid = NULL;
+
+ /*
+ * Recode the entry name as 8-bit (if possible) and 16-bit strings.
+ * This also disposes of entries that definitely are too long.
+ */
+ size_t cb8Bit;
+ bool fSimple;
+ size_t cb16Bit;
+ size_t cwc16Bit;
+ uint8_t ab8Bit[255];
+ RTUTF16 wsz16Bit[255];
+
+ /* 16-bit */
+ PRTUTF16 pwsz16Bit = wsz16Bit;
+ int rc = RTStrToUtf16BigEx(pszEntry, RTSTR_MAX, &pwsz16Bit, RT_ELEMENTS(wsz16Bit), &cwc16Bit);
+ if (RT_SUCCESS(rc))
+ cb16Bit = 1 + cwc16Bit * sizeof(RTUTF16);
+ else
+ return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc;
+
+ /* 8-bit (can't possibly overflow) */
+ fSimple = true;
+ cb8Bit = 0;
+ const char *pszSrc = pszEntry;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc2 = RTStrGetCpEx(&pszSrc, &uc);
+ AssertRCReturn(rc2, rc2);
+ if (uc <= 0x7f)
+ {
+ if (uc)
+ ab8Bit[cb8Bit++] = (uint8_t)uc;
+ else
+ break;
+ }
+ else if (uc <= 0xff)
+ {
+ ab8Bit[cb8Bit++] = (uint8_t)uc;
+ fSimple = false;
+ }
+ else
+ {
+ cb8Bit = UINT32_MAX / 2;
+ break;
+ }
+ }
+ Assert(cb8Bit <= sizeof(ab8Bit) || cb8Bit == UINT32_MAX / 2);
+ cb8Bit++;
+
+ /*
+ * Scan the directory content.
+ */
+ uint32_t offDesc = 0;
+ uint32_t const cbDir = pThis->Core.cbObject;
+ while (offDesc + RT_UOFFSETOF(UDFFILEIDDESC, abImplementationUse) <= cbDir)
+ {
+ PCUDFFILEIDDESC pFid = (PCUDFFILEIDDESC)&pThis->pbDir[offDesc];
+ uint32_t const cbFid = UDFFILEIDDESC_GET_SIZE(pFid);
+ if ( offDesc + cbFid <= cbDir
+ && pFid->Tag.idTag == UDF_TAG_ID_FILE_ID_DESC)
+ { /* likely */ }
+ else
+ break;
+
+ uint8_t const *pbName = UDFFILEIDDESC_2_NAME(pFid);
+ if (*pbName == 16)
+ {
+ if (cb16Bit == pFid->cbName)
+ {
+ if (RTUtf16BigNICmp((PCRTUTF16)(&pbName[1]), wsz16Bit, cwc16Bit) == 0)
+ {
+ *ppFid = pFid;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ else if (*pbName == 8)
+ {
+ if ( cb8Bit == pFid->cbName
+ && cb8Bit != UINT16_MAX)
+ {
+ if (fSimple)
+ {
+ if (RTStrNICmp((const char *)&pbName[1], (const char *)ab8Bit, cb8Bit - 1) == 0)
+ {
+ *ppFid = pFid;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ size_t cch = cb8Bit - 1;
+ size_t off;
+ for (off = 0; off < cch; off++)
+ {
+ RTUNICP uc1 = ab8Bit[off];
+ RTUNICP uc2 = pbName[off + 1];
+ if ( uc1 == uc2
+ || RTUniCpToLower(uc1) == RTUniCpToLower(uc2)
+ || RTUniCpToUpper(uc1) == RTUniCpToUpper(uc2))
+ { /* matches */ }
+ else
+ break;
+ }
+ if (off == cch)
+ {
+ *ppFid = pFid;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+
+ /* advance */
+ offDesc += cbFid;
+ }
+
+ return VERR_FILE_NOT_FOUND;
+}
+
+
+/**
+ * Releases a reference to a shared directory structure.
+ *
+ * @param pShared The shared directory structure.
+ */
+static void rtFsIsoDirShrd_Release(PRTFSISODIRSHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pShared->Core.cRefs);
+ Assert(cRefs < UINT32_MAX / 2);
+ if (cRefs == 0)
+ {
+ LogFlow(("rtFsIsoDirShrd_Release: Destroying shared structure %p\n", pShared));
+ Assert(pShared->Core.cRefs == 0);
+ if (pShared->pbDir)
+ {
+ RTMemFree(pShared->pbDir);
+ pShared->pbDir = NULL;
+ }
+ rtFsIsoCore_Destroy(&pShared->Core);
+ RTMemFree(pShared);
+ }
+}
+
+
+/**
+ * Retains a reference to a shared directory structure.
+ *
+ * @param pShared The shared directory structure.
+ */
+static void rtFsIsoDirShrd_Retain(PRTFSISODIRSHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pShared->Core.cRefs);
+ Assert(cRefs > 1); NOREF(cRefs);
+}
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_Close(void *pvThis)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ LogFlow(("rtFsIsoDir_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSISODIRSHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ rtFsIsoDirShrd_Release(pShared);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ return rtFsIsoCore_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ PRTFSISODIRSHRD pShared = pThis->pShared;
+ int rc;
+
+ /*
+ * We cannot create or replace anything, just open stuff.
+ */
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ { /* likely */ }
+ else
+ return VERR_WRITE_PROTECT;
+
+ /*
+ * Special cases '.' and '..'
+ */
+ if (pszEntry[0] == '.')
+ {
+ PRTFSISODIRSHRD pSharedToOpen;
+ if (pszEntry[1] == '\0')
+ pSharedToOpen = pShared;
+ else if (pszEntry[1] == '.' && pszEntry[2] == '\0')
+ {
+ pSharedToOpen = pShared->Core.pParentDir;
+ if (!pSharedToOpen)
+ pSharedToOpen = pShared;
+ }
+ else
+ pSharedToOpen = NULL;
+ if (pSharedToOpen)
+ {
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ rtFsIsoDirShrd_Retain(pSharedToOpen);
+ RTVFSDIR hVfsDir;
+ rc = rtFsIsoDir_NewWithShared(pShared->Core.pVol, pSharedToOpen, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ return rc;
+ }
+ }
+
+ /*
+ * Try open whatever it is.
+ */
+ if (pShared->Core.pVol->enmType != RTFSISOVOLTYPE_UDF)
+ {
+
+ /*
+ * ISO 9660
+ */
+ PCISO9660DIRREC pDirRec;
+ uint64_t offDirRec;
+ uint32_t cDirRecs;
+ RTFMODE fMode;
+ uint32_t uVersion;
+ PRTFSISOROCKINFO pRockInfo = NULL;
+ if (pShared->Core.pVol->fHaveRock)
+ pRockInfo = (PRTFSISOROCKINFO)alloca(sizeof(*pRockInfo));
+ rc = rtFsIsoDir_FindEntry9660(pShared, pszEntry, &offDirRec, &pDirRec, &cDirRecs, &fMode, &uVersion, pRockInfo);
+ Log2(("rtFsIsoDir_Open: FindEntry9660(,%s,) -> %Rrc\n", pszEntry, rc));
+ if (RT_SUCCESS(rc))
+ {
+ switch (fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ if (fFlags & RTVFSOBJ_F_OPEN_FILE)
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsIsoFile_New9660(pShared->Core.pVol, pShared, pDirRec, cDirRecs, offDirRec, fOpen,
+ uVersion, pRockInfo && pRockInfo->fValid ? pRockInfo : NULL, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_FILE;
+ break;
+
+ case RTFS_TYPE_DIRECTORY:
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsIsoDir_New9660(pShared->Core.pVol, pShared, pDirRec, cDirRecs, offDirRec,
+ pRockInfo && pRockInfo->fValid ? pRockInfo : NULL, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ break;
+
+ case RTFS_TYPE_SYMLINK:
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_FIFO:
+ case RTFS_TYPE_SOCKET:
+ case RTFS_TYPE_WHITEOUT:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+
+ default:
+ rc = VERR_PATH_NOT_FOUND;
+ break;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * UDF
+ */
+ PCUDFFILEIDDESC pFid;
+ rc = rtFsIsoDir_FindEntryUdf(pShared, pszEntry, &pFid);
+ Log2(("rtFsIsoDir_Open: FindEntryUdf(,%s,) -> %Rrc\n", pszEntry, rc));
+ if (RT_SUCCESS(rc))
+ {
+ if (!(pFid->fFlags & UDF_FILE_FLAGS_DELETED))
+ {
+ if (!(pFid->fFlags & UDF_FILE_FLAGS_DIRECTORY))
+ {
+ if (fFlags & RTVFSOBJ_F_OPEN_FILE)
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsIsoFile_NewUdf(pShared->Core.pVol, pShared, pFid, fOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_FILE;
+ }
+ else
+ {
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsIsoDir_NewUdf(pShared->Core.pVol, pShared, pFid, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ }
+ }
+ /* We treat UDF_FILE_FLAGS_DELETED like RTFS_TYPE_WHITEOUT for now. */
+ else
+ rc = VERR_PATH_NOT_FOUND;
+ }
+ }
+ return rc;
+
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_RewindDir(void *pvThis)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ pThis->offDir = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The ISO 9660 worker for rtFsIsoDir_ReadDir
+ */
+static int rtFsIsoDir_ReadDir9660(PRTFSISODIROBJ pThis, PRTFSISODIRSHRD pShared, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISOROCKINFO pRockInfo = NULL;
+ if (pShared->Core.pVol->fHaveRock)
+ pRockInfo = (PRTFSISOROCKINFO)alloca(sizeof(*pRockInfo));
+
+ while (pThis->offDir + RT_UOFFSETOF(ISO9660DIRREC, achFileId) <= pShared->cbDir)
+ {
+ PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pShared->pbDir[pThis->offDir];
+
+ /* If null length, skip to the next sector. */
+ if (pDirRec->cbDirRec == 0)
+ pThis->offDir = (pThis->offDir + pShared->Core.pVol->cbSector) & ~(pShared->Core.pVol->cbSector - 1U);
+ else
+ {
+ /*
+ * Do names first as they may cause overflows.
+ */
+ uint32_t uVersion = 0;
+ if ( pDirRec->bFileIdLength == 1
+ && pDirRec->achFileId[0] == '\0')
+ {
+ if (*pcbDirEntry < RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2;
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW (dot)\n"));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 1;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '\0';
+ }
+ else if ( pDirRec->bFileIdLength == 1
+ && pDirRec->achFileId[0] == '\1')
+ {
+ if (*pcbDirEntry < RT_UOFFSETOF(RTDIRENTRYEX, szName) + 3)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 3;
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW (dot-dot)\n"));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 2;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '.';
+ pDirEntry->szName[2] = '\0';
+ }
+ else if (pShared->Core.pVol->fIsUtf16)
+ {
+ PCRTUTF16 pawcSrc = (PCRTUTF16)&pDirRec->achFileId[0];
+ size_t cwcSrc = pDirRec->bFileIdLength / sizeof(RTUTF16);
+ size_t cwcVer = !(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ ? rtFsIso9660GetVersionLengthUtf16Big(pawcSrc, cwcSrc, &uVersion) : 0;
+ size_t cchNeeded = 0;
+ size_t cbDst = *pcbDirEntry - RT_UOFFSETOF(RTDIRENTRYEX, szName);
+ char *pszDst = pDirEntry->szName;
+
+ int rc = RTUtf16BigToUtf8Ex(pawcSrc, cwcSrc - cwcVer, &pszDst, cbDst, &cchNeeded);
+ if (RT_SUCCESS(rc))
+ pDirEntry->cbName = (uint16_t)cchNeeded;
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchNeeded + 1;
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW - cbDst=%zu cchNeeded=%zu (UTF-16BE)\n", cbDst, cchNeeded));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ else
+ {
+ ssize_t cchNeeded2 = RTStrPrintf2(pszDst, cbDst, "bad-name-%#x", pThis->offDir);
+ if (cchNeeded2 >= 0)
+ pDirEntry->cbName = (uint16_t)cchNeeded2;
+ else
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + (size_t)-cchNeeded2;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ else
+ {
+ /* This is supposed to be upper case ASCII, however, purge the encoding anyway. */
+ size_t cchVer = !(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY)
+ ? rtFsIso9660GetVersionLengthAscii(pDirRec->achFileId, pDirRec->bFileIdLength, &uVersion) : 0;
+ size_t cchName = pDirRec->bFileIdLength - cchVer;
+ size_t cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchName + 1;
+ if (*pcbDirEntry < cbNeeded)
+ {
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (ASCII)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = (uint16_t)cchName;
+ memcpy(pDirEntry->szName, pDirRec->achFileId, cchName);
+ pDirEntry->szName[cchName] = '\0';
+ RTStrPurgeEncoding(pDirEntry->szName);
+ }
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+ /*
+ * To avoid duplicating code in rtFsIsoCore_InitFrom9660DirRec and
+ * rtFsIsoCore_QueryInfo, we create a dummy RTFSISOCORE on the stack.
+ */
+ RTFSISOCORE TmpObj;
+ RT_ZERO(TmpObj);
+ rtFsIsoCore_InitFrom9660DirRec(&TmpObj, pDirRec, 1 /* cDirRecs - see below why 1 */,
+ pThis->offDir + pShared->Core.FirstExtent.off, uVersion, NULL, pShared->Core.pVol);
+ int rc = rtFsIsoCore_QueryInfo(&TmpObj, &pDirEntry->Info, enmAddAttr);
+
+ /*
+ * Look for rock ridge info associated with this entry
+ * and merge that into the record.
+ */
+ if (pRockInfo)
+ {
+ rtFsIsoDirShrd_ParseRockForDirRec(pShared, pDirRec, pRockInfo);
+ if (pRockInfo->fValid)
+ {
+ if ( pRockInfo->fSeenLastNM
+ && pRockInfo->cchName > 0
+ && !pShared->Core.pVol->fIsUtf16
+ && ( pDirRec->bFileIdLength != 1
+ || ( pDirRec->achFileId[0] != '\0' /* . */
+ && pDirRec->achFileId[0] != '\1'))) /* .. */
+ {
+ size_t const cchName = pRockInfo->cchName;
+ Assert(strlen(pRockInfo->szName) == cchName);
+ size_t const cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchName + 1;
+ if (*pcbDirEntry < cbNeeded)
+ {
+ Log3(("rtFsIsoDir_ReadDir9660: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (Rock)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = (uint16_t)cchName;
+ memcpy(pDirEntry->szName, pRockInfo->szName, cchName);
+ pDirEntry->szName[cchName] = '\0';
+
+ RTStrPurgeEncoding(pDirEntry->szName);
+ }
+ }
+ }
+
+ /*
+ * Update the directory location and handle multi extent records.
+ *
+ * Multi extent records only affect the file size and the directory location,
+ * so we deal with it here instead of involving rtFsIsoCore_InitFrom9660DirRec
+ * which would potentially require freeing memory and such.
+ */
+ if (!(pDirRec->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ {
+ Log3(("rtFsIsoDir_ReadDir9660: offDir=%#07x: %s (rc=%Rrc)\n", pThis->offDir, pDirEntry->szName, rc));
+ pThis->offDir += pDirRec->cbDirRec;
+ }
+ else
+ {
+ uint32_t cExtents = 1;
+ uint32_t offDir = pThis->offDir + pDirRec->cbDirRec;
+ while (offDir + RT_UOFFSETOF(ISO9660DIRREC, achFileId) <= pShared->cbDir)
+ {
+ PCISO9660DIRREC pDirRec2 = (PCISO9660DIRREC)&pShared->pbDir[offDir];
+ if (pDirRec2->cbDirRec != 0)
+ {
+ pDirEntry->Info.cbObject += ISO9660_GET_ENDIAN(&pDirRec2->cbData);
+ offDir += pDirRec2->cbDirRec;
+ cExtents++;
+ if (!(pDirRec2->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT))
+ break;
+ }
+ else
+ offDir = (offDir + pShared->Core.pVol->cbSector) & ~(pShared->Core.pVol->cbSector - 1U);
+ }
+ Log3(("rtFsIsoDir_ReadDir9660: offDir=%#07x, %u extents ending at %#07x: %s (rc=%Rrc)\n",
+ pThis->offDir, cExtents, offDir, pDirEntry->szName, rc));
+ pThis->offDir = offDir;
+ }
+
+ return rc;
+ }
+ }
+
+ Log3(("rtFsIsoDir_ReadDir9660: offDir=%#07x: VERR_NO_MORE_FILES\n", pThis->offDir));
+ return VERR_NO_MORE_FILES;
+}
+
+
+/**
+ * The UDF worker for rtFsIsoDir_ReadDir
+ */
+static int rtFsIsoDir_ReadDirUdf(PRTFSISODIROBJ pThis, PRTFSISODIRSHRD pShared, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ /*
+ * At offset zero we've got the '.' entry. This has to be generated
+ * manually as it's not part of the directory content. The directory
+ * offset has to be faked for this too, so offDir == 0 indicates the '.'
+ * entry whereas offDir == 1 is the first file id descriptor.
+ */
+ if (pThis->offDir == 0)
+ {
+ if (*pcbDirEntry < RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2;
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW (dot)\n"));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 1;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '\0';
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+ int rc = rtFsIsoCore_QueryInfo(&pShared->Core, &pDirEntry->Info, enmAddAttr);
+
+ Log3(("rtFsIsoDir_ReadDirUdf: offDir=%#07x: %s (rc=%Rrc)\n", pThis->offDir, pDirEntry->szName, rc));
+ pThis->offDir = 1;
+ return rc;
+ }
+
+ /*
+ * Do the directory content.
+ */
+ while (pThis->offDir + RT_UOFFSETOF(UDFFILEIDDESC, abImplementationUse) <= pShared->cbDir + 1)
+ {
+ PCUDFFILEIDDESC pFid = (PCUDFFILEIDDESC)&pShared->pbDir[pThis->offDir - 1];
+ uint32_t const cbFid = UDFFILEIDDESC_GET_SIZE(pFid);
+
+ if (pThis->offDir + cbFid <= pShared->cbDir + 1)
+ { /* likely */ }
+ else
+ break;
+
+ /*
+ * Do names first as they may cause overflows.
+ */
+ if (pFid->cbName > 1)
+ {
+ uint8_t const *pbName = UDFFILEIDDESC_2_NAME(pFid);
+ uint32_t cbSrc = pFid->cbName;
+ if (*pbName == 8)
+ {
+ /* Figure out the UTF-8 length first. */
+ bool fSimple = true;
+ uint32_t cchDst = 0;
+ for (uint32_t offSrc = 1; offSrc < cbSrc; offSrc++)
+ if (!(pbName[offSrc] & 0x80))
+ cchDst++;
+ else
+ {
+ cchDst += 2;
+ fSimple = false;
+ }
+
+ size_t cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchDst + 1;
+ if (*pcbDirEntry >= cbNeeded)
+ {
+ if (fSimple)
+ {
+ Assert(cbSrc - 1 == cchDst);
+ memcpy(pDirEntry->szName, &pbName[1], cchDst);
+ pDirEntry->szName[cchDst] = '\0';
+ }
+ else
+ {
+ char *pszDst = pDirEntry->szName;
+ for (uint32_t offSrc = 1; offSrc < cbSrc; offSrc++)
+ pszDst = RTStrPutCp(pszDst, pbName[offSrc]);
+ *pszDst = '\0';
+ Assert((size_t)(pszDst - &pDirEntry->szName[0]) == cchDst);
+ }
+ }
+ else
+ {
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (8-bit)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ {
+ /* Let RTUtf16BigToUtf8Ex do the bounds checking. */
+ char *pszDst = pDirEntry->szName;
+ size_t cbDst = *pcbDirEntry - RT_UOFFSETOF(RTDIRENTRYEX, szName);
+ size_t cchNeeded = 0;
+ int rc;
+ if (*pbName == 16)
+ rc = RTUtf16BigToUtf8Ex((PCRTUTF16)(pbName + 1), (cbSrc - 1) / sizeof(RTUTF16), &pszDst, cbDst, &cchNeeded);
+ else
+ rc = VERR_INVALID_NAME;
+ if (RT_SUCCESS(rc))
+ pDirEntry->cbName = (uint16_t)cchNeeded;
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + cchNeeded + 1;
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW - cbDst=%zu cchNeeded=%zu (16-bit)\n", cbDst, cchNeeded));
+ return VERR_BUFFER_OVERFLOW;
+ }
+ else
+ {
+ LogRelMax(90, ("ISO/UDF: Malformed directory entry name at %#x: %.*Rhxs\n", pThis->offDir - 1, cbSrc, pbName));
+ ssize_t cchNeeded2 = RTStrPrintf2(pszDst, cbDst, "bad-name-%#x", pThis->offDir - 1);
+ if (cchNeeded2 >= 0)
+ pDirEntry->cbName = (uint16_t)cchNeeded2;
+ else
+ {
+ *pcbDirEntry = RT_UOFFSETOF(RTDIRENTRYEX, szName) + (size_t)-cchNeeded2;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ }
+ else if (pFid->fFlags & UDF_FILE_FLAGS_PARENT)
+ {
+ size_t cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 2 + 1;
+ if (*pcbDirEntry < cbNeeded)
+ {
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (dot-dot)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 2;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[1] = '.';
+ pDirEntry->szName[2] = '\0';
+ }
+ else
+ {
+ size_t cbNeeded = RT_UOFFSETOF(RTDIRENTRYEX, szName) + 1;
+ if (*pcbDirEntry < cbNeeded)
+ {
+ Log3(("rtFsIsoDir_ReadDirUdf: VERR_BUFFER_OVERFLOW - cbDst=%zu cbNeeded=%zu (empty)\n", *pcbDirEntry, cbNeeded));
+ *pcbDirEntry = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ pDirEntry->cbName = 0;
+ pDirEntry->szName[0] = '\0';
+ }
+
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+ /*
+ * To avoid duplicating code in rtFsIsoCore_InitUdf and
+ * rtFsIsoCore_QueryInfo, we create a dummy RTFSISOCORE on the stack.
+ */
+ RTFSISOCORE TmpObj;
+ RT_ZERO(TmpObj);
+ int rc = rtFsIsoCore_InitFromUdfIcbAndFileIdDesc(&TmpObj, &pFid->Icb, pFid, pThis->offDir - 1, pShared->Core.pVol);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoCore_QueryInfo(&TmpObj, &pDirEntry->Info, enmAddAttr);
+ rtFsIsoCore_Destroy(&TmpObj);
+ }
+
+ /*
+ * Update.
+ */
+ Log3(("rtFsIsoDir_ReadDirUdf: offDir=%#07x: %s (rc=%Rrc)\n", pThis->offDir, pDirEntry->szName, rc));
+ pThis->offDir += cbFid;
+
+ return rc;
+ }
+
+ Log3(("rtFsIsoDir_ReadDirUdf: offDir=%#07x: VERR_NO_MORE_FILES\n", pThis->offDir));
+ return VERR_NO_MORE_FILES;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsIsoDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSISODIROBJ pThis = (PRTFSISODIROBJ)pvThis;
+ PRTFSISODIRSHRD pShared = pThis->pShared;
+ int rc;
+ if (pShared->Core.pVol->enmType != RTFSISOVOLTYPE_UDF)
+ rc = rtFsIsoDir_ReadDir9660(pThis, pShared, pDirEntry, pcbDirEntry, enmAddAttr);
+ else
+ rc = rtFsIsoDir_ReadDirUdf(pThis, pShared, pDirEntry, pcbDirEntry, enmAddAttr);
+ return rc;
+}
+
+
+/**
+ * ISO file operations.
+ */
+static const RTVFSDIROPS g_rtFsIsoDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "ISO 9660 Dir",
+ rtFsIsoDir_Close,
+ rtFsIsoDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ NULL /*SetMode*/,
+ NULL /*SetTimes*/,
+ NULL /*SetOwner*/,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsIsoDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile */,
+ NULL /* pfnOpenDir */,
+ rtFsIsoDir_CreateDir,
+ rtFsIsoDir_OpenSymlink,
+ rtFsIsoDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsIsoDir_UnlinkEntry,
+ rtFsIsoDir_RenameEntry,
+ rtFsIsoDir_RewindDir,
+ rtFsIsoDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+/**
+ * Adds an open child to the parent directory's shared structure.
+ *
+ * Maintains an additional reference to the parent dir to prevent it from going
+ * away. If @a pDir is the root directory, it also ensures the volume is
+ * referenced and sticks around until the last open object is gone.
+ *
+ * @param pDir The directory.
+ * @param pChild The child being opened.
+ * @sa rtFsIsoDirShrd_RemoveOpenChild
+ */
+static void rtFsIsoDirShrd_AddOpenChild(PRTFSISODIRSHRD pDir, PRTFSISOCORE pChild)
+{
+ rtFsIsoDirShrd_Retain(pDir);
+
+ RTListAppend(&pDir->OpenChildren, &pChild->Entry);
+ pChild->pParentDir = pDir;
+}
+
+
+/**
+ * Removes an open child to the parent directory.
+ *
+ * @param pDir The directory.
+ * @param pChild The child being removed.
+ *
+ * @remarks This is the very last thing you do as it may cause a few other
+ * objects to be released recursively (parent dir and the volume).
+ *
+ * @sa rtFsIsoDirShrd_AddOpenChild
+ */
+static void rtFsIsoDirShrd_RemoveOpenChild(PRTFSISODIRSHRD pDir, PRTFSISOCORE pChild)
+{
+ AssertReturnVoid(pChild->pParentDir == pDir);
+ RTListNodeRemove(&pChild->Entry);
+ pChild->pParentDir = NULL;
+
+ rtFsIsoDirShrd_Release(pDir);
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Logs the content of a directory.
+ */
+static void rtFsIsoDirShrd_Log9660Content(PRTFSISODIRSHRD pThis)
+{
+ if (LogIs2Enabled())
+ {
+ uint32_t offRec = 0;
+ while (offRec < pThis->cbDir)
+ {
+ PCISO9660DIRREC pDirRec = (PCISO9660DIRREC)&pThis->pbDir[offRec];
+ if (pDirRec->cbDirRec == 0)
+ break;
+
+ RTUTF16 wszName[128];
+ if (pThis->Core.pVol->fIsUtf16)
+ {
+ PRTUTF16 pwszDst = &wszName[pDirRec->bFileIdLength / sizeof(RTUTF16)];
+ PCRTUTF16 pwszSrc = (PCRTUTF16)&pDirRec->achFileId[pDirRec->bFileIdLength];
+ pwszSrc--;
+ *pwszDst-- = '\0';
+ while ((uintptr_t)pwszDst >= (uintptr_t)&wszName[0])
+ {
+ *pwszDst = RT_BE2H_U16(*pwszSrc);
+ pwszDst--;
+ pwszSrc--;
+ }
+ }
+ else
+ {
+ PRTUTF16 pwszDst = wszName;
+ for (uint32_t off = 0; off < pDirRec->bFileIdLength; off++)
+ *pwszDst++ = pDirRec->achFileId[off];
+ *pwszDst = '\0';
+ }
+
+ Log2(("ISO9660: %04x: rec=%#x ea=%#x cb=%#010RX32 off=%#010RX32 fl=%#04x %04u-%02u-%02u %02u:%02u:%02u%+03d unit=%#x igap=%#x idVol=%#x '%ls'\n",
+ offRec,
+ pDirRec->cbDirRec,
+ pDirRec->cExtAttrBlocks,
+ ISO9660_GET_ENDIAN(&pDirRec->cbData),
+ ISO9660_GET_ENDIAN(&pDirRec->offExtent),
+ pDirRec->fFileFlags,
+ pDirRec->RecTime.bYear + 1900,
+ pDirRec->RecTime.bMonth,
+ pDirRec->RecTime.bDay,
+ pDirRec->RecTime.bHour,
+ pDirRec->RecTime.bMinute,
+ pDirRec->RecTime.bSecond,
+ pDirRec->RecTime.offUtc*4/60,
+ pDirRec->bFileUnitSize,
+ pDirRec->bInterleaveGapSize,
+ ISO9660_GET_ENDIAN(&pDirRec->VolumeSeqNo),
+ wszName));
+
+ uint32_t offSysUse = RT_UOFFSETOF_DYN(ISO9660DIRREC, achFileId[pDirRec->bFileIdLength])
+ + !(pDirRec->bFileIdLength & 1);
+ if (offSysUse < pDirRec->cbDirRec)
+ {
+ Log2(("ISO9660: system use (%#x bytes):\n%.*RhxD\n", pDirRec->cbDirRec - offSysUse,
+ pDirRec->cbDirRec - offSysUse, (uint8_t *)pDirRec + offSysUse));
+ }
+
+ /* advance */
+ offRec += pDirRec->cbDirRec;
+ }
+ }
+}
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Instantiates a new shared directory structure, given 9660 records.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pDirRec The directory record. Will access @a cDirRecs
+ * records.
+ * @param cDirRecs Number of directory records if more than one.
+ * @param offDirRec The byte offset of the directory record.
+ * @param pRockInfo Optional pointer to rock ridge info for the entry.
+ * @param ppShared Where to return the shared directory structure.
+ */
+static int rtFsIsoDirShrd_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec,
+ uint32_t cDirRecs, uint64_t offDirRec, PCRTFSISOROCKINFO pRockInfo, PRTFSISODIRSHRD *ppShared)
+{
+ /*
+ * Allocate a new structure and initialize it.
+ */
+ int rc = VERR_NO_MEMORY;
+ PRTFSISODIRSHRD pShared = (PRTFSISODIRSHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rc = rtFsIsoCore_InitFrom9660DirRec(&pShared->Core, pDirRec, cDirRecs, offDirRec, 0 /*uVersion*/, pRockInfo, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pShared->OpenChildren);
+ pShared->cbDir = ISO9660_GET_ENDIAN(&pDirRec->cbData);
+ pShared->pbDir = (uint8_t *)RTMemAllocZ(pShared->cbDir + 256);
+ if (pShared->pbDir)
+ {
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, pShared->Core.FirstExtent.off, pShared->pbDir, pShared->cbDir, NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsIsoDirShrd_Log9660Content(pShared);
+#endif
+
+ /*
+ * If this is the root directory, check if rock ridge info is present.
+ */
+ if ( !pParentDir
+ && !(pThis->fFlags & RTFSISO9660_F_NO_ROCK)
+ && pShared->cbDir > RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]))
+ {
+ PCISO9660DIRREC pDirRec0 = (PCISO9660DIRREC)pShared->pbDir;
+ if ( pDirRec0->bFileIdLength == 1
+ && pDirRec0->achFileId[0] == 0
+ && pDirRec0->cbDirRec > RT_UOFFSETOF(ISO9660DIRREC, achFileId[1]))
+ rtFsIsoDirShrd_ParseRockForRoot(pShared, pDirRec0);
+ }
+
+ /*
+ * Link into parent directory so we can use it to update
+ * our directory entry.
+ */
+ if (pParentDir)
+ rtFsIsoDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+ *ppShared = pShared;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ RTMemFree(pShared);
+ }
+ *ppShared = NULL;
+ return rc;
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Logs the content of a directory.
+ */
+static void rtFsIsoDirShrd_LogUdfContent(PRTFSISODIRSHRD pThis)
+{
+ if (LogIs2Enabled())
+ {
+ uint32_t offDesc = 0;
+ while (offDesc + RT_UOFFSETOF(UDFFILEIDDESC, abImplementationUse) < pThis->cbDir)
+ {
+ PCUDFFILEIDDESC pFid = (PCUDFFILEIDDESC)&pThis->pbDir[offDesc];
+ uint32_t const cbFid = UDFFILEIDDESC_GET_SIZE(pFid);
+ if (offDesc + cbFid > pThis->cbDir)
+ break;
+
+ uint32_t cwcName = 0;
+ RTUTF16 wszName[260];
+ if (pFid->cbName > 0)
+ {
+ uint8_t const *pbName = UDFFILEIDDESC_2_NAME(pFid);
+ uint32_t offSrc = 1;
+ if (*pbName == 8)
+ while (offSrc < pFid->cbName)
+ {
+ wszName[cwcName] = pbName[offSrc];
+ cwcName++;
+ offSrc++;
+ }
+ else if (*pbName == 16)
+ while (offSrc + 1 <= pFid->cbName)
+ {
+ wszName[cwcName] = RT_MAKE_U16(pbName[offSrc + 1], pbName[offSrc]);
+ cwcName++;
+ offSrc += 2;
+ }
+ else
+ {
+ RTUtf16CopyAscii(wszName, RT_ELEMENTS(wszName), "<bad type>");
+ cwcName = 10;
+ }
+ }
+ else if (pFid->fFlags & UDF_FILE_FLAGS_PARENT)
+ {
+ wszName[0] = '.';
+ wszName[1] = '.';
+ cwcName = 2;
+ }
+ else
+ {
+ RTUtf16CopyAscii(wszName, RT_ELEMENTS(wszName), "<empty>");
+ cwcName = 7;
+ }
+ wszName[cwcName] = '\0';
+
+ Log2(("ISO/UDF: %04x: fFlags=%#x uVer=%u Icb={%#04x:%#010RX32 LB %#06x t=%u} cbName=%#04x cbIU=%#x '%ls'\n",
+ offDesc,
+ pFid->fFlags,
+ pFid->uVersion,
+ pFid->Icb.Location.uPartitionNo,
+ pFid->Icb.Location.off,
+ pFid->Icb.cb,
+ pFid->Icb.uType,
+ pFid->cbName,
+ pFid->cbImplementationUse,
+ wszName));
+ int rc = rtFsIsoVolValidateUdfDescTagAndCrc(&pFid->Tag, pThis->cbDir - offDesc,
+ UDF_TAG_ID_FILE_ID_DESC, pFid->Tag.offTag, NULL);
+ if (RT_FAILURE(rc))
+ Log2(("ISO/UDF: Bad Tag: %Rrc - idTag=%#x\n", rc, pFid->Tag.idTag));
+ if (pFid->cbImplementationUse > 32)
+ Log2(("ISO/UDF: impl use (%#x bytes):\n%.*RhxD\n",
+ pFid->cbImplementationUse, pFid->cbImplementationUse, pFid->abImplementationUse));
+ else if (pFid->cbImplementationUse > 0)
+ Log2(("ISO/UDF: impl use (%#x bytes): %.*Rhxs\n",
+ pFid->cbImplementationUse, pFid->cbImplementationUse, pFid->abImplementationUse));
+
+ /* advance */
+ offDesc += cbFid;
+ }
+
+ if (offDesc < pThis->cbDir)
+ Log2(("ISO/UDF: warning! %#x trailing bytes in directory:\n%.*RhxD\n",
+ pThis->cbDir - offDesc, pThis->cbDir - offDesc, &pThis->pbDir[offDesc]));
+ }
+}
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Instantiates a new shared directory structure, given UDF descriptors.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pAllocDesc The allocation descriptor for the directory ICB.
+ * @param pFileIdDesc The file ID descriptor. This is NULL for the root.
+ * @param offInDir The offset of the file ID descriptor in the parent
+ * directory. This is used when looking up shared
+ * directory objects. (Pass 0 for root.)
+ * @param ppShared Where to return the shared directory structure.
+ */
+static int rtFsIsoDirShrd_NewUdf(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCUDFLONGAD pAllocDesc,
+ PCUDFFILEIDDESC pFileIdDesc, uintptr_t offInDir, PRTFSISODIRSHRD *ppShared)
+{
+ /*
+ * Allocate a new structure and initialize it.
+ */
+ int rc = VERR_NO_MEMORY;
+ PRTFSISODIRSHRD pShared = (PRTFSISODIRSHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ rc = rtFsIsoCore_InitFromUdfIcbAndFileIdDesc(&pShared->Core, pAllocDesc, pFileIdDesc, offInDir, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pShared->OpenChildren);
+
+ if (pShared->Core.cbObject < RTFSISO_MAX_DIR_SIZE)
+ {
+ pShared->cbDir = (uint32_t)pShared->Core.cbObject;
+ pShared->pbDir = (uint8_t *)RTMemAllocZ(RT_MAX(RT_ALIGN_32(pShared->cbDir, 512), 512));
+ if (pShared->pbDir)
+ {
+ rc = rtFsIsoCore_ReadWorker(&pShared->Core, 0, pShared->pbDir, pShared->cbDir, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsIsoDirShrd_LogUdfContent(pShared);
+#endif
+
+ /*
+ * Link into parent directory so we can use it to update
+ * our directory entry.
+ */
+ if (pParentDir)
+ rtFsIsoDirShrd_AddOpenChild(pParentDir, &pShared->Core);
+ *ppShared = pShared;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ RTMemFree(pShared);
+ }
+
+ *ppShared = NULL;
+ return rc;
+}
+
+
+/**
+ * Instantiates a new directory with a shared structure presupplied.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pShared Referenced pointer to the shared structure. The
+ * reference is always CONSUMED.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsIsoDir_NewWithShared(PRTFSISOVOL pThis, PRTFSISODIRSHRD pShared, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Create VFS object around the shared structure.
+ */
+ PRTFSISODIROBJ pNewDir;
+ int rc = RTVfsNewDir(&g_rtFsIsoDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf,
+ NIL_RTVFSLOCK /*use volume lock*/, phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ * We CONSUME a reference to pShared here.
+ */
+ pNewDir->offDir = 0;
+ pNewDir->pShared = pShared;
+ return VINF_SUCCESS;
+ }
+
+ rtFsIsoDirShrd_Release(pShared);
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+}
+
+
+
+/**
+ * Instantiates a new directory VFS instance for ISO 9660, creating the shared
+ * structure as necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory. This is NULL for the root
+ * directory.
+ * @param pDirRec The directory record.
+ * @param cDirRecs Number of directory records if more than one.
+ * @param offDirRec The byte offset of the directory record.
+ * @param pRockInfo Optional pointer to rock ridge info for the entry.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsIsoDir_New9660(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCISO9660DIRREC pDirRec,
+ uint32_t cDirRecs, uint64_t offDirRec, PCRTFSISOROCKINFO pRockInfo, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSISODIRSHRD pShared = (PRTFSISODIRSHRD)rtFsIsoDir_LookupShared(pParentDir, offDirRec);
+ if (!pShared)
+ {
+ int rc = rtFsIsoDirShrd_New9660(pThis, pParentDir, pDirRec, cDirRecs, offDirRec, pRockInfo, &pShared);
+ if (RT_FAILURE(rc))
+ {
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+ }
+ }
+ return rtFsIsoDir_NewWithShared(pThis, pShared, phVfsDir);
+}
+
+
+/**
+ * Instantiates a new directory VFS instance for UDF, creating the shared
+ * structure as necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO volume instance.
+ * @param pParentDir The parent directory.
+ * @param pFid The file ID descriptor for the directory.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsIsoDir_NewUdf(PRTFSISOVOL pThis, PRTFSISODIRSHRD pParentDir, PCUDFFILEIDDESC pFid, PRTVFSDIR phVfsDir)
+{
+ Assert(pFid);
+ Assert(pParentDir);
+ uintptr_t const offInDir = (uintptr_t)pFid - (uintptr_t)pParentDir->pbDir;
+ Assert(offInDir < pParentDir->cbDir);
+
+ /*
+ * Look for existing shared object, create a new one if necessary.
+ */
+ PRTFSISODIRSHRD pShared = (PRTFSISODIRSHRD)rtFsIsoDir_LookupShared(pParentDir, offInDir);
+ if (!pShared)
+ {
+ int rc = rtFsIsoDirShrd_NewUdf(pThis, pParentDir, &pFid->Icb, pFid, offInDir, &pShared);
+ if (RT_FAILURE(rc))
+ {
+ *phVfsDir = NIL_RTVFSDIR;
+ return rc;
+ }
+ }
+ return rtFsIsoDir_NewWithShared(pThis, pShared, phVfsDir);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_Close(void *pvThis)
+{
+ PRTFSISOVOL pThis = (PRTFSISOVOL)pvThis;
+ Log(("rtFsIsoVol_Close(%p)\n", pThis));
+
+ if (pThis->pRootDir)
+ {
+ Assert(RTListIsEmpty(&pThis->pRootDir->OpenChildren));
+ Assert(pThis->pRootDir->Core.cRefs == 1);
+ rtFsIsoDirShrd_Release(pThis->pRootDir);
+ pThis->pRootDir = NULL;
+ }
+
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+
+ if (RTCritSectIsInitialized(&pThis->RockBufLock))
+ RTCritSectDelete(&pThis->RockBufLock);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+static int rtFsIsoVol_ReturnUdfDString(const char *pachSrc, size_t cchSrc, void *pvDst, size_t cbDst, size_t *pcbRet)
+{
+ char *pszDst = (char *)pvDst;
+
+ if (pachSrc[0] == 8)
+ {
+ size_t const cchText = RT_MIN((uint8_t)pachSrc[cchSrc - 1], cchSrc - 2);
+ size_t const cchActual = RTStrNLen(&pachSrc[1], cchText);
+ *pcbRet = cchActual + 1;
+ int rc = RTStrCopyEx(pszDst, cbDst, &pachSrc[1], cchActual);
+ if (cbDst > 0)
+ RTStrPurgeEncoding(pszDst);
+ return rc;
+ }
+
+ if (pachSrc[0] == 16)
+ {
+ PCRTUTF16 pwszSrc = (PCRTUTF16)&pachSrc[1];
+ if (cchSrc > 0)
+ return RTUtf16BigToUtf8Ex(pwszSrc, (cchSrc - 2) / sizeof(RTUTF16), &pszDst, cchSrc, pcbRet);
+ int rc = RTUtf16CalcUtf8LenEx(pwszSrc, (cchSrc - 2) / sizeof(RTUTF16), pcbRet);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbRet += 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ return rc;
+ }
+
+ if (ASMMemIsZero(pachSrc, cchSrc))
+ {
+ *pcbRet = 1;
+ if (cbDst >= 1)
+ {
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+ }
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ *pcbRet = 0;
+ return VERR_INVALID_UTF8_ENCODING; /** @todo better status here */
+}
+
+
+/**
+ * For now this is a sanitized version of rtFsIsoVolGetMaybeUtf16Be, which is
+ * probably not correct or anything, but will have to do for now.
+ */
+static int rtFsIsoVol_ReturnIso9660D1String(const char *pachSrc, size_t cchSrc, void *pvDst, size_t cbDst, size_t *pcbRet)
+{
+ char *pszDst = (char *)pvDst;
+
+ /*
+ * Check if it may be some UTF16 variant by scanning for zero bytes
+ * (ISO-9660 doesn't allow zeros).
+ */
+ size_t cFirstZeros = 0;
+ size_t cSecondZeros = 0;
+ for (size_t off = 0; off + 1 < cchSrc; off += 2)
+ {
+ cFirstZeros += pachSrc[off] == '\0';
+ cSecondZeros += pachSrc[off + 1] == '\0';
+ }
+ if (cFirstZeros > cSecondZeros)
+ {
+ /*
+ * UTF-16BE / UTC-2BE:
+ */
+ if (cchSrc & 1)
+ {
+ AssertReturn(pachSrc[cchSrc - 1] == '\0' || pachSrc[cchSrc - 1] == ' ', VERR_INVALID_UTF16_ENCODING);
+ cchSrc--;
+ }
+ while ( cchSrc >= 2
+ && pachSrc[cchSrc - 1] == ' '
+ && pachSrc[cchSrc - 2] == '\0')
+ cchSrc -= 2;
+
+ if (cbDst > 0)
+ return RTUtf16BigToUtf8Ex((PCRTUTF16)pachSrc, cchSrc / sizeof(RTUTF16), &pszDst, cbDst, pcbRet);
+ int rc = RTUtf16BigCalcUtf8LenEx((PCRTUTF16)pachSrc, cchSrc / sizeof(RTUTF16), pcbRet);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbRet += 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ return rc;
+ }
+
+ if (cSecondZeros > 0)
+ {
+ /*
+ * Little endian UTF-16 / UCS-2.
+ */
+ if (cchSrc & 1)
+ {
+ AssertReturn(pachSrc[cchSrc - 1] == '\0' || pachSrc[cchSrc - 1] == ' ', VERR_INVALID_UTF16_ENCODING);
+ cchSrc--;
+ }
+ while ( cchSrc >= 2
+ && pachSrc[cchSrc - 1] == '\0'
+ && pachSrc[cchSrc - 2] == ' ')
+ cchSrc -= 2;
+
+ if (cbDst)
+ return RTUtf16LittleToUtf8Ex((PCRTUTF16)pachSrc, cchSrc / sizeof(RTUTF16), &pszDst, cbDst, pcbRet);
+ int rc = RTUtf16LittleCalcUtf8LenEx((PCRTUTF16)pachSrc, cchSrc / sizeof(RTUTF16), pcbRet);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbRet += 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ return rc;
+ }
+
+ /*
+ * ASSUME UTF-8/ASCII.
+ */
+ while ( cchSrc > 0
+ && pachSrc[cchSrc - 1] == ' ')
+ cchSrc--;
+
+ *pcbRet = cchSrc + 1;
+ int rc = RTStrCopyEx(pszDst, cbDst, pachSrc, cchSrc);
+ if (cbDst > 0)
+ RTStrPurgeEncoding(pszDst);
+ return rc;
+}
+
+
+static int rtFsIsoVol_ReturnIso9660DString(const char *pachSrc, size_t cchSrc, void *pvDst, size_t cbDst, size_t *pcbRet)
+{
+ /* Lazy bird: */
+ return rtFsIsoVol_ReturnIso9660D1String(pachSrc, cchSrc, pvDst, cbDst, pcbRet);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfoEx}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_QueryInfoEx(void *pvThis, RTVFSQIEX enmInfo, void *pvInfo, size_t cbInfo, size_t *pcbRet)
+{
+ PRTFSISOVOL pThis = (PRTFSISOVOL)pvThis;
+ LogFlow(("rtFsIsoVol_QueryInfo(%p, %d,, %#zx,)\n", pThis, enmInfo, cbInfo));
+
+ union
+ {
+ uint8_t ab[RTFSISO_MAX_LOGICAL_BLOCK_SIZE];
+ ISO9660PRIMARYVOLDESC PriVolDesc;
+ ISO9660SUPVOLDESC SupVolDesc;
+ } uBuf;
+
+ switch (enmInfo)
+ {
+ case RTVFSQIEX_VOL_LABEL:
+ case RTVFSQIEX_VOL_LABEL_ALT:
+ {
+ if (pThis->enmType == RTFSISOVOLTYPE_UDF
+ && ( enmInfo == RTVFSQIEX_VOL_LABEL
+ || pThis->offPrimaryVolDesc == 0))
+ return rtFsIsoVol_ReturnUdfDString(pThis->Udf.VolInfo.achLogicalVolumeID,
+ sizeof(pThis->Udf.VolInfo.achLogicalVolumeID), pvInfo, cbInfo, pcbRet);
+
+ bool const fPrimary = enmInfo == RTVFSQIEX_VOL_LABEL_ALT
+ || pThis->enmType == RTFSISOVOLTYPE_ISO9960;
+
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking,
+ fPrimary ? pThis->offPrimaryVolDesc : pThis->offSecondaryVolDesc,
+ uBuf.ab, RT_MAX(RT_MIN(pThis->cbSector, sizeof(uBuf)), sizeof(uBuf.PriVolDesc)), NULL);
+ AssertRCReturn(rc, rc);
+
+ if (fPrimary)
+ return rtFsIsoVol_ReturnIso9660DString(uBuf.PriVolDesc.achVolumeId, sizeof(uBuf.PriVolDesc.achVolumeId),
+ pvInfo, cbInfo, pcbRet);
+ return rtFsIsoVol_ReturnIso9660D1String(uBuf.SupVolDesc.achVolumeId, sizeof(uBuf.SupVolDesc.achVolumeId),
+ pvInfo, cbInfo, pcbRet);
+ }
+
+ default:
+ return VERR_NOT_SUPPORTED;
+
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSISOVOL pThis = (PRTFSISOVOL)pvThis;
+
+ rtFsIsoDirShrd_Retain(pThis->pRootDir); /* consumed by the next call */
+ return rtFsIsoDir_NewWithShared(pThis, pThis->pRootDir, phVfsDir);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOPS,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsIsoVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed)
+{
+ RT_NOREF(pvThis, off, cb, pfUsed);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+DECL_HIDDEN_CONST(const RTVFSOPS) g_rtFsIsoVolOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_VFS,
+ "ISO 9660/UDF",
+ rtFsIsoVol_Close,
+ rtFsIsoVol_QueryInfo,
+ rtFsIsoVol_QueryInfoEx,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSOPS_VERSION,
+ 0 /* fFeatures */,
+ rtFsIsoVol_OpenRoot,
+ rtFsIsoVol_QueryRangeState,
+ RTVFSOPS_VERSION
+};
+
+
+/**
+ * Checks the descriptor tag and CRC.
+ *
+ * @retval IPRT status code.
+ * @retval VERR_ISOFS_TAG_IS_ALL_ZEROS
+ * @retval VERR_MISMATCH
+ * @retval VERR_ISOFS_UNSUPPORTED_TAG_VERSION
+ * @retval VERR_ISOFS_TAG_SECTOR_MISMATCH
+ * @retval VERR_ISOFS_BAD_TAG_CHECKSUM
+ *
+ * @param pTag The tag to check.
+ * @param idTag The expected descriptor tag ID, UINT16_MAX matches any
+ * tag ID.
+ * @param offTag The sector offset of the tag.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolValidateUdfDescTag(PCUDFTAG pTag, uint16_t idTag, uint32_t offTag, PRTERRINFO pErrInfo)
+{
+ /*
+ * Checksum the tag first.
+ */
+ const uint8_t *pbTag = (const uint8_t *)pTag;
+ uint8_t const bChecksum = pbTag[0]
+ + pbTag[1]
+ + pbTag[2]
+ + pbTag[3]
+ + pbTag[5] /* skipping byte 4 as that's the checksum. */
+ + pbTag[6]
+ + pbTag[7]
+ + pbTag[8]
+ + pbTag[9]
+ + pbTag[10]
+ + pbTag[11]
+ + pbTag[12]
+ + pbTag[13]
+ + pbTag[14]
+ + pbTag[15];
+ if (pTag->uChecksum == bChecksum)
+ {
+ /*
+ * Do the matching.
+ */
+ if ( pTag->uVersion == 3
+ || pTag->uVersion == 2)
+ {
+ if ( pTag->idTag == idTag
+ || idTag == UINT16_MAX)
+ {
+ if (pTag->offTag == offTag)
+ {
+ //Log3(("ISO/UDF: Valid descriptor %#06x at %#010RX32; cbDescriptorCrc=%#06RX32 uTagSerialNo=%#x\n",
+ // pTag->idTag, offTag, pTag->cbDescriptorCrc, pTag->uTagSerialNo));
+ return VINF_SUCCESS;
+ }
+
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): Sector mismatch: %#RX32 (%.*Rhxs)\n",
+ idTag, offTag, pTag->offTag, sizeof(*pTag), pTag));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_TAG_SECTOR_MISMATCH,
+ "Descriptor tag sector number mismatch: %#x, expected %#x (%.*Rhxs)",
+ pTag->offTag, offTag, sizeof(*pTag), pTag);
+ }
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): Tag ID mismatch: %#x (%.*Rhxs)\n",
+ idTag, offTag, pTag->idTag, sizeof(*pTag), pTag));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_MISMATCH, "Descriptor tag ID mismatch: %#x, expected %#x (%.*Rhxs)",
+ pTag->idTag, idTag, sizeof(*pTag), pTag);
+ }
+ if (ASMMemIsZero(pTag, sizeof(*pTag)))
+ {
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): All zeros\n", idTag, offTag));
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_TAG_IS_ALL_ZEROS, "Descriptor is all zeros");
+ }
+
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): Unsupported version: %#x (%.*Rhxs)\n",
+ idTag, offTag, pTag->uVersion, sizeof(*pTag), pTag));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNSUPPORTED_TAG_VERSION, "Unsupported descriptor tag version: %#x, expected 2 or 3 (%.*Rhxs)",
+ pTag->uVersion, sizeof(*pTag), pTag);
+ }
+ Log(("rtFsIsoVolValidateUdfDescTag(,%#x,%#010RX32,): checksum error: %#x, calc %#x (%.*Rhxs)\n",
+ idTag, offTag, pTag->uChecksum, bChecksum, sizeof(*pTag), pTag));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_BAD_TAG_CHECKSUM,
+ "Descriptor tag checksum error: %#x, calculated %#x (%.*Rhxs)",
+ pTag->uChecksum, bChecksum, sizeof(*pTag), pTag);
+}
+
+
+/**
+ * Checks the descriptor CRC.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC
+ * @retval VERR_ISOFS_DESC_CRC_MISMATCH
+ *
+ * @param pTag The descriptor buffer to checksum.
+ * @param cbDesc The size of the descriptor buffer.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolValidateUdfDescCrc(PCUDFTAG pTag, size_t cbDesc, PRTERRINFO pErrInfo)
+{
+ if (pTag->cbDescriptorCrc + sizeof(*pTag) <= cbDesc)
+ {
+ uint16_t uCrc = RTCrc16Ccitt(pTag + 1, pTag->cbDescriptorCrc);
+ if (pTag->uDescriptorCrc == uCrc)
+ return VINF_SUCCESS;
+
+ Log(("rtFsIsoVolValidateUdfDescCrc(,%#x,%#010RX32,): Descriptor CRC mismatch: expected %#x, calculated %#x (cbDescriptorCrc=%#x)\n",
+ pTag->idTag, pTag->offTag, pTag->uDescriptorCrc, uCrc, pTag->cbDescriptorCrc));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_DESC_CRC_MISMATCH,
+ "Descriptor CRC mismatch: exepcted %#x, calculated %#x (cbDescriptor=%#x, idTag=%#x, offTag=%#010RX32)",
+ pTag->uDescriptorCrc, uCrc, pTag->cbDescriptorCrc, pTag->idTag, pTag->offTag);
+ }
+
+ Log(("rtFsIsoVolValidateUdfDescCrc(,%#x,%#010RX32,): Insufficient data to CRC: cbDescriptorCrc=%#x cbDesc=%#zx\n",
+ pTag->idTag, pTag->offTag, pTag->cbDescriptorCrc, cbDesc));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC,
+ "Insufficient data to CRC: cbDescriptorCrc=%#x cbDesc=%#zx (idTag=%#x, offTag=%#010RX32)",
+ pTag->cbDescriptorCrc, cbDesc, pTag->idTag, pTag->offTag);
+}
+
+
+/**
+ * Checks the descriptor tag and CRC.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC
+ * @retval VERR_ISOFS_TAG_IS_ALL_ZEROS
+ * @retval VERR_MISMATCH
+ * @retval VERR_ISOFS_UNSUPPORTED_TAG_VERSION
+ * @retval VERR_ISOFS_TAG_SECTOR_MISMATCH
+ * @retval VERR_ISOFS_BAD_TAG_CHECKSUM
+ * @retval VERR_ISOFS_DESC_CRC_MISMATCH
+ *
+ * @param pTag The descriptor buffer to check the tag of and to
+ * checksum.
+ * @param cbDesc The size of the descriptor buffer.
+ * @param idTag The expected descriptor tag ID, UINT16_MAX
+ * matches any tag ID.
+ * @param offTag The sector offset of the tag.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolValidateUdfDescTagAndCrc(PCUDFTAG pTag, size_t cbDesc, uint16_t idTag, uint32_t offTag, PRTERRINFO pErrInfo)
+{
+ int rc = rtFsIsoVolValidateUdfDescTag(pTag, idTag, offTag, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoVolValidateUdfDescCrc(pTag, cbDesc, pErrInfo);
+ return rc;
+}
+
+
+
+
+static int rtFsIsoVolProcessUdfFileSetDescs(PRTFSISOVOL pThis, uint8_t *pbBuf, size_t cbBuf, PRTERRINFO pErrInfo)
+{
+
+ /*
+ * We assume there is a single file descriptor and don't bother checking what comes next.
+ */
+ PUDFFILESETDESC pFsd = (PUDFFILESETDESC)pbBuf;
+ Assert(cbBuf > sizeof(*pFsd)); NOREF(cbBuf);
+ RT_ZERO(*pFsd);
+ size_t cbToRead = RT_MAX(pThis->Udf.VolInfo.FileSetDescriptor.cb, sizeof(*pFsd));
+ int rc = rtFsIsoVolUdfVpRead(pThis, pThis->Udf.VolInfo.FileSetDescriptor.Location.uPartitionNo,
+ pThis->Udf.VolInfo.FileSetDescriptor.Location.off, 0, pFsd, cbToRead);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoVolValidateUdfDescTagAndCrc(&pFsd->Tag, cbToRead, UDF_TAG_ID_FILE_SET_DESC,
+ pThis->Udf.VolInfo.FileSetDescriptor.Location.off, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: File set descriptor at %#RX32 (%#RX32:%#RX32)\n", pFsd->Tag.offTag,
+ pThis->Udf.VolInfo.FileSetDescriptor.Location.uPartitionNo,
+ pThis->Udf.VolInfo.FileSetDescriptor.Location.off));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER_TIMESTAMP(pFsd, RecordingTimestamp);
+ UDF_LOG2_MEMBER(pFsd, "#06RX16", uInterchangeLevel);
+ UDF_LOG2_MEMBER(pFsd, "#06RX16", uMaxInterchangeLevel);
+ UDF_LOG2_MEMBER(pFsd, "#010RX32", fCharacterSets);
+ UDF_LOG2_MEMBER(pFsd, "#010RX32", fMaxCharacterSets);
+ UDF_LOG2_MEMBER(pFsd, "#010RX32", uFileSetNo);
+ UDF_LOG2_MEMBER(pFsd, "#010RX32", uFileSetDescNo);
+ UDF_LOG2_MEMBER_CHARSPEC(pFsd, LogicalVolumeIDCharSet);
+ UDF_LOG2_MEMBER_DSTRING(pFsd, achLogicalVolumeID);
+ UDF_LOG2_MEMBER_CHARSPEC(pFsd, FileSetCharSet);
+ UDF_LOG2_MEMBER_DSTRING(pFsd, achFileSetID);
+ UDF_LOG2_MEMBER_DSTRING(pFsd, achCopyrightFile);
+ UDF_LOG2_MEMBER_DSTRING(pFsd, achAbstractFile);
+ UDF_LOG2_MEMBER_LONGAD(pFsd, RootDirIcb);
+ UDF_LOG2_MEMBER_ENTITY_ID(pFsd, idDomain);
+ UDF_LOG2_MEMBER_LONGAD(pFsd, NextExtent);
+ UDF_LOG2_MEMBER_LONGAD(pFsd, SystemStreamDirIcb);
+ if (!ASMMemIsZero(&pFsd->abReserved[0], sizeof(pFsd->abReserved)))
+ UDF_LOG2_MEMBER(pFsd, ".32Rhxs", abReserved);
+ }
+#endif
+
+ /*
+ * Do some basic sanity checking.
+ */
+ if (!UDF_IS_CHAR_SET_OSTA(&pFsd->FileSetCharSet))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_FSD_UNSUPPORTED_CHAR_SET,
+ "Invalid file set charset %.64Rhxs", &pFsd->FileSetCharSet);
+ if ( pFsd->RootDirIcb.cb == 0
+ || pFsd->RootDirIcb.uType != UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_FSD_ZERO_ROOT_DIR,
+ "Root Dir ICB location is zero or malformed: uType=%#x cb=%#x loc=%#x:%#RX32",
+ pFsd->RootDirIcb.uType, pFsd->RootDirIcb.cb,
+ pFsd->RootDirIcb.Location.uPartitionNo, pFsd->RootDirIcb.Location.off);
+ if ( pFsd->NextExtent.cb != 0
+ && pFsd->NextExtent.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_FSD_NEXT_EXTENT,
+ "NextExtent isn't zero: uType=%#x cb=%#x loc=%#x:%#RX32",
+ pFsd->NextExtent.uType, pFsd->NextExtent.cb,
+ pFsd->NextExtent.Location.uPartitionNo, pFsd->NextExtent.Location.off);
+
+ /*
+ * Copy the information we need.
+ */
+ pThis->Udf.VolInfo.RootDirIcb = pFsd->RootDirIcb;
+ if ( pFsd->SystemStreamDirIcb.cb > 0
+ && pFsd->SystemStreamDirIcb.uType == UDF_AD_TYPE_RECORDED_AND_ALLOCATED)
+ pThis->Udf.VolInfo.SystemStreamDirIcb = pFsd->SystemStreamDirIcb;
+ else
+ RT_ZERO(pThis->Udf.VolInfo.SystemStreamDirIcb);
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading file set descriptor");
+}
+
+
+/**
+ * Check validatity and extract information from the descriptors in the VDS seq.
+ *
+ * @returns IPRT status code
+ * @param pThis The instance.
+ * @param pInfo The VDS sequence info.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolProcessUdfVdsSeqInfo(PRTFSISOVOL pThis, PRTFSISOVDSINFO pInfo, PRTERRINFO pErrInfo)
+{
+ /*
+ * Check the basic descriptor counts.
+ */
+ PUDFPRIMARYVOLUMEDESC pPvd;
+ if (pInfo->cPrimaryVols == 1)
+ pPvd = pInfo->apPrimaryVols[0];
+ else
+ {
+ if (pInfo->cPrimaryVols == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_NO_PVD, "No primary volume descriptor was found");
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_MULTIPLE_PVDS,
+ "More than one primary volume descriptor was found: %u", pInfo->cPrimaryVols);
+ }
+
+ PUDFLOGICALVOLUMEDESC pLvd;
+ if (pInfo->cLogicalVols == 1)
+ pLvd = pInfo->apLogicalVols[0];
+ else
+ {
+ if (pInfo->cLogicalVols == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_NO_LVD, "No logical volume descriptor was found");
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_MULTIPLE_LVDS,
+ "More than one logical volume descriptor was found: %u", pInfo->cLogicalVols);
+ }
+
+#if 0
+ if (pInfo->cPartitions == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_NO_PD, "No partition descriptors was found");
+#endif
+
+ /*
+ * Check out the partition map in the logical volume descriptor.
+ * Produce the mapping table while going about that.
+ */
+ if (pLvd->cPartitionMaps > 64)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_TOO_MANY_PART_MAPS,
+ "Too many partition maps: %u (max 64)", pLvd->cPartitionMaps);
+
+ PRTFSISOVOLUDFPMAP paPartMaps = NULL;
+ if (pLvd->cPartitionMaps > 0)
+ {
+ pInfo->paPartMaps = paPartMaps = (PRTFSISOVOLUDFPMAP)RTMemAllocZ(sizeof(paPartMaps[0]) * pLvd->cPartitionMaps);
+ if (!paPartMaps)
+ return VERR_NO_MEMORY;
+ }
+ uint32_t cPartMaps = 0;
+
+ if (pLvd->cbMapTable)
+ {
+ uint32_t off = 0;
+ while (off + sizeof(UDFPARTMAPHDR) <= pLvd->cbMapTable)
+ {
+ PCUDFPARTMAPHDR pHdr = (PCUDFPARTMAPHDR)&pLvd->abPartitionMaps[off];
+
+ /*
+ * Bounds checking.
+ */
+ if (off + pHdr->cb > pLvd->cbMapTable)
+ {
+ if (cPartMaps < pLvd->cbMapTable)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_MALFORMED_PART_MAP_TABLE,
+ "Incomplete partition map entry at offset %#x: cb=%#x -> offEnd=%#x cbMapTable=%#x (type=%#x)",
+ off, pHdr->cb, off + pHdr->cb, pLvd->cbMapTable, pHdr->bType);
+ LogRel(("ISO/UDF: Warning: Incomplete partition map entry at offset %#x: cb=%#x -> offEnd=%#x cbMapTable=%#x (type=%#x)\n",
+ off, pHdr->cb, off + pHdr->cb, pLvd->cbMapTable, pHdr->bType));
+ break;
+ }
+ if (cPartMaps >= pLvd->cPartitionMaps)
+ {
+ LogRel(("ISO/UDF: Warning: LVD::cPartitionMaps is %u but there are more bytes in the table. (off=%#x cb=%#x cbMapTable=%#x bType=%#x)\n",
+ cPartMaps - pLvd->cPartitionMaps, off, pHdr->cb, pLvd->cbMapTable, pHdr->bType));
+ break;
+ }
+
+ /*
+ * Extract relevant info out of the entry.
+ */
+ paPartMaps[cPartMaps].offMapTable = (uint16_t)off;
+ uint16_t uPartitionNo;
+ if (pHdr->bType == 1)
+ {
+ PCUDFPARTMAPTYPE1 pType1 = (PCUDFPARTMAPTYPE1)pHdr;
+ paPartMaps[cPartMaps].uVolumeSeqNo = pType1->uVolumeSeqNo;
+ paPartMaps[cPartMaps].bType = RTFSISO_UDF_PMAP_T_PLAIN;
+ uPartitionNo = pType1->uPartitionNo;
+ }
+ else if (pHdr->bType == 2)
+ {
+ PCUDFPARTMAPTYPE2 pType2 = (PCUDFPARTMAPTYPE2)pHdr;
+ if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_VPM_PARTITION_TYPE))
+ {
+ paPartMaps[cPartMaps].bType = pType2->idPartitionType.Suffix.Udf.uUdfRevision >= 0x200
+ ? RTFSISO_UDF_PMAP_T_VPM_20 : RTFSISO_UDF_PMAP_T_VPM_15;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_VPM_NOT_SUPPORTED, "Partition type '%.23s' (%#x) not supported",
+ pType2->idPartitionType.achIdentifier, pType2->idPartitionType.Suffix.Udf.uUdfRevision);
+ }
+ else if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_SPM_PARTITION_TYPE))
+ {
+ paPartMaps[cPartMaps].bType = RTFSISO_UDF_PMAP_T_SPM;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_SPM_NOT_SUPPORTED, "Partition type '%.23s' (%#x) not supported",
+ pType2->idPartitionType.achIdentifier, pType2->idPartitionType.Suffix.Udf.uUdfRevision);
+ }
+ else if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_MPM_PARTITION_TYPE))
+ {
+ paPartMaps[cPartMaps].bType = RTFSISO_UDF_PMAP_T_MPM;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_MPM_NOT_SUPPORTED, "Partition type '%.23s' (%#x) not supported",
+ pType2->idPartitionType.achIdentifier, pType2->idPartitionType.Suffix.Udf.uUdfRevision);
+ }
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNKNOWN_PART_MAP_TYPE_ID,
+ "Unknown partition map ID for #%u @ %#x: %.23s",
+ cPartMaps, off, pType2->idPartitionType.achIdentifier);
+#if 0 /* unreachable code */
+ paPartMaps[cPartMaps].uVolumeSeqNo = pType2->uVolumeSeqNo;
+ uPartitionNo = pType2->uPartitionNo;
+#endif
+ }
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNKNOWN_PART_MAP_ENTRY_TYPE,
+ "Unknown partition map entry type #%u @ %#x: %u", cPartMaps, off, pHdr->bType);
+ paPartMaps[cPartMaps].uPartitionNo = uPartitionNo;
+
+ /*
+ * Lookup the partition number and retrieve the relevant info from the partition descriptor.
+ */
+ uint32_t i = pInfo->cPartitions;
+ while (i-- > 0)
+ {
+ PUDFPARTITIONDESC pPd = pInfo->apPartitions[i];
+ if (paPartMaps[cPartMaps].uPartitionNo == pPd->uPartitionNo)
+ {
+ paPartMaps[cPartMaps].idxPartDesc = (uint16_t)i;
+ paPartMaps[cPartMaps].cSectors = pPd->cSectors;
+ paPartMaps[cPartMaps].offLocation = pPd->offLocation;
+ paPartMaps[cPartMaps].offByteLocation = (uint64_t)pPd->offLocation * pThis->cbSector;
+ paPartMaps[cPartMaps].fFlags = pPd->fFlags;
+ paPartMaps[cPartMaps].uAccessType = pPd->uAccessType;
+ if (!UDF_ENTITY_ID_EQUALS(&pPd->PartitionContents, UDF_ENTITY_ID_PD_PARTITION_CONTENTS_UDF))
+ paPartMaps[cPartMaps].fHaveHdr = false;
+ else
+ {
+ paPartMaps[cPartMaps].fHaveHdr = true;
+ paPartMaps[cPartMaps].Hdr = pPd->ContentsUse.Hdr;
+ }
+ break;
+ }
+ }
+ if (i > pInfo->cPartitions)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_PARTITION_NOT_FOUND,
+ "Partition #%u (%#x) specified by mapping entry #%u (@ %#x) was not found! (int-type %u)",
+ uPartitionNo, uPartitionNo, cPartMaps, off, paPartMaps[cPartMaps].bType);
+
+ /*
+ * Advance.
+ */
+ cPartMaps++;
+ off += pHdr->cb;
+ }
+
+ if (cPartMaps < pLvd->cPartitionMaps)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_INCOMPLETE_PART_MAP_TABLE,
+ "Only found %u of the %u announced partition mapping table entries",
+ cPartMaps, pLvd->cPartitionMaps);
+ }
+
+ /* It might be theoretically possible to not use virtual partitions for
+ accessing data, so just warn if there aren't any. */
+ if (cPartMaps == 0)
+ LogRel(("ISO/UDF: Warning: No partition maps!\n"));
+
+ /*
+ * Check out the logical volume descriptor.
+ */
+ if ( pLvd->cbLogicalBlock < pThis->cbSector
+ || pLvd->cbLogicalBlock > RTFSISO_MAX_LOGICAL_BLOCK_SIZE
+ || (pLvd->cbLogicalBlock % pThis->cbSector) != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNSUPPORTED_LOGICAL_BLOCK_SIZE,
+ "Logical block size of %#x is not supported with a sector size of %#x",
+ pLvd->cbLogicalBlock, pThis->cbSector);
+
+ if (!UDF_ENTITY_ID_EQUALS(&pLvd->idDomain, UDF_ENTITY_ID_LVD_DOMAIN))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_BAD_LVD_DOMAIN_ID,
+ "Unsupported domain ID in logical volume descriptor: '%.23s'", pLvd->idDomain.achIdentifier);
+
+ if ( pLvd->ContentsUse.FileSetDescriptor.uType != UDF_AD_TYPE_RECORDED_AND_ALLOCATED
+ || pLvd->ContentsUse.FileSetDescriptor.cb == 0
+ || pLvd->ContentsUse.FileSetDescriptor.Location.uPartitionNo >= cPartMaps)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_BAD_LVD_FILE_SET_DESC_LOCATION,
+ "Malformed file set descriptor location (type=%u cb=%#x part=%#x)",
+ pLvd->ContentsUse.FileSetDescriptor.uType,
+ pLvd->ContentsUse.FileSetDescriptor.cb,
+ pLvd->ContentsUse.FileSetDescriptor.Location.uPartitionNo);
+
+ bool fLvdHaveVolId = !ASMMemIsZero(pLvd->achLogicalVolumeID, sizeof(pLvd->achLogicalVolumeID));
+ if ( fLvdHaveVolId
+ && !UDF_IS_CHAR_SET_OSTA(&pLvd->DescCharSet))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_BAD_LVD_DESC_CHAR_SET,
+ "Logical volume ID is not using OSTA compressed unicode");
+
+ /*
+ * We can ignore much, if not all of the primary volume descriptor.
+ */
+
+ /*
+ * We're good. So copy over the data.
+ */
+ pThis->Udf.VolInfo.FileSetDescriptor = pLvd->ContentsUse.FileSetDescriptor;
+ pThis->Udf.VolInfo.cbBlock = pLvd->cbLogicalBlock;
+ pThis->Udf.VolInfo.cShiftBlock = 9;
+ while (pThis->Udf.VolInfo.cbBlock != RT_BIT_32(pThis->Udf.VolInfo.cShiftBlock))
+ pThis->Udf.VolInfo.cShiftBlock++;
+ pThis->Udf.VolInfo.fFlags = pPvd->fFlags;
+ pThis->Udf.VolInfo.cPartitions = cPartMaps;
+ pThis->Udf.VolInfo.paPartitions = paPartMaps;
+ pInfo->paPartMaps = NULL;
+ if (fLvdHaveVolId)
+ memcpy(pThis->Udf.VolInfo.achLogicalVolumeID, pLvd->achLogicalVolumeID, sizeof(pThis->Udf.VolInfo.achLogicalVolumeID));
+ else
+ RT_ZERO(pThis->Udf.VolInfo.achLogicalVolumeID);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes a primary volume descriptor in the VDS (UDF).
+ *
+ * @returns IPRT status code.
+ * @param pInfo Where we gather descriptor information.
+ * @param pDesc The descriptor.
+ * @param pErrInfo Where to return extended error information.
+ */
+//cmd: kmk VBoxRT && kmk_redirect -E VBOX_LOG_DEST="nofile stderr" -E VBOX_LOG="rt_fs=~0" -E VBOX_LOG_FLAGS="unbuffered enabled" -- e:\vbox\svn\trunk\out\win.amd64\debug\bin\tools\RTLs.exe :iprtvfs:file(open,d:\Downloads\en_windows_10_enterprise_version_1703_updated_march_2017_x64_dvd_10189290.iso,r):vfs(isofs):/ -la
+static int rtFsIsoVolProcessUdfPrimaryVolDesc(PRTFSISOVDSINFO pInfo, PCUDFPRIMARYVOLUMEDESC pDesc, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: Primary volume descriptor at sector %#RX32\n", pDesc->Tag.offTag));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uVolumeDescSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uPrimaryVolumeDescNo);
+ UDF_LOG2_MEMBER_DSTRING(pDesc, achVolumeID);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uVolumeSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uVolumeSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uMaxVolumeSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uInterchangeLevel);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uMaxInterchangeLevel);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", fCharacterSets);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", fMaxCharacterSets);
+ UDF_LOG2_MEMBER_DSTRING(pDesc, achVolumeSetID);
+ UDF_LOG2_MEMBER_CHARSPEC(pDesc, DescCharSet);
+ UDF_LOG2_MEMBER_CHARSPEC(pDesc, ExplanatoryCharSet);
+ UDF_LOG2_MEMBER_EXTENTAD(pDesc, VolumeAbstract);
+ UDF_LOG2_MEMBER_EXTENTAD(pDesc, VolumeCopyrightNotice);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idApplication);
+ UDF_LOG2_MEMBER_TIMESTAMP(pDesc, RecordingTimestamp);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idImplementation);
+ if (!ASMMemIsZero(&pDesc->abImplementationUse, sizeof(pDesc->abImplementationUse)))
+ Log2(("ISO/UDF: %-32s %.64Rhxs\n", "abReserved[64]:", &pDesc->abImplementationUse[0]));
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", offPredecessorVolDescSeq);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", fFlags);
+ if (!ASMMemIsZero(&pDesc->abReserved, sizeof(pDesc->abReserved)))
+ Log2(("ISO/UDF: %-32s %.22Rhxs\n", "abReserved[22]:", &pDesc->abReserved[0]));
+ }
+#endif
+
+ /*
+ * Check if this is a new revision of an existing primary volume descriptor.
+ */
+ PUDFPRIMARYVOLUMEDESC pEndianConvert = NULL;
+ uint32_t i = pInfo->cPrimaryVols;
+ while (i--> 0)
+ {
+ if ( memcmp(pDesc->achVolumeID, pInfo->apPrimaryVols[i]->achVolumeID, sizeof(pDesc->achVolumeID)) == 0
+ && memcmp(&pDesc->DescCharSet, &pInfo->apPrimaryVols[i]->DescCharSet, sizeof(pDesc->DescCharSet)) == 0)
+ {
+ if (RT_LE2H_U32(pDesc->uVolumeDescSeqNo) >= pInfo->apPrimaryVols[i]->uVolumeDescSeqNo)
+ {
+ Log(("ISO/UDF: Primary descriptor prevails over previous! (%u >= %u)\n",
+ RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apPartitions[i]->uVolumeDescSeqNo));
+ pEndianConvert = pInfo->apPrimaryVols[i];
+ memcpy(pEndianConvert, pDesc, sizeof(*pDesc));
+ }
+ else
+ Log(("ISO/UDF: Primary descriptor has lower sequence number than the previous! (%u < %u)\n",
+ RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apPartitions[i]->uVolumeDescSeqNo));
+ break;
+ }
+ }
+ if (i >= pInfo->cPrimaryVols)
+ {
+ /*
+ * It wasn't. Append it.
+ */
+ i = pInfo->cPrimaryVols;
+ if (i < RT_ELEMENTS(pInfo->apPrimaryVols))
+ {
+ pInfo->apPrimaryVols[i] = pEndianConvert = (PUDFPRIMARYVOLUMEDESC)RTMemDup(pDesc, sizeof(*pDesc));
+ if (pEndianConvert)
+ pInfo->cPrimaryVols = i + 1;
+ else
+ return VERR_NO_MEMORY;
+ Log2(("ISO/UDF: ++New primary descriptor.\n"));
+ }
+ else
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_TOO_MANY_PVDS, "Encountered too many primary volume descriptors");
+ }
+
+#ifdef RT_BIG_ENDIAN
+ /*
+ * Do endian conversion of the descriptor.
+ */
+ if (pEndianConvert)
+ {
+ AssertFailed();
+ }
+#else
+ RT_NOREF(pEndianConvert);
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes an logical volume descriptor in the VDS (UDF).
+ *
+ * @returns IPRT status code.
+ * @param pInfo Where we gather descriptor information.
+ * @param pDesc The descriptor.
+ * @param cbSector The sector size (UDF defines the logical and physical
+ * sector size to be the same).
+ * @param pErrInfo Where to return extended error information.
+ */
+static int rtFsIsoVolProcessUdfLogicalVolumeDesc(PRTFSISOVDSINFO pInfo, PCUDFLOGICALVOLUMEDESC pDesc,
+ uint32_t cbSector, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: Logical volume descriptor at sector %#RX32\n", pDesc->Tag.offTag));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uVolumeDescSeqNo);
+ UDF_LOG2_MEMBER_CHARSPEC(pDesc, DescCharSet);
+ UDF_LOG2_MEMBER_DSTRING(pDesc, achLogicalVolumeID);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", cbLogicalBlock);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idDomain);
+ if (UDF_ENTITY_ID_EQUALS(&pDesc->idDomain, UDF_ENTITY_ID_LVD_DOMAIN))
+ UDF_LOG2_MEMBER_LONGAD(pDesc, ContentsUse.FileSetDescriptor);
+ else if (!ASMMemIsZero(&pDesc->ContentsUse.ab[0], sizeof(pDesc->ContentsUse.ab)))
+ Log2(("ISO/UDF: %-32s %.16Rhxs\n", "ContentsUse.ab[16]:", &pDesc->ContentsUse.ab[0]));
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", cbMapTable);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", cPartitionMaps);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idImplementation);
+ if (!ASMMemIsZero(&pDesc->ImplementationUse.ab[0], sizeof(pDesc->ImplementationUse.ab)))
+ Log2(("ISO/UDF: %-32s\n%.128RhxD\n", "ImplementationUse.ab[128]:", &pDesc->ImplementationUse.ab[0]));
+ UDF_LOG2_MEMBER_EXTENTAD(pDesc, IntegritySeqExtent);
+ if (pDesc->cbMapTable)
+ {
+ Log2(("ISO/UDF: %-32s\n", "abPartitionMaps"));
+ uint32_t iMap = 0;
+ uint32_t off = 0;
+ while (off + sizeof(UDFPARTMAPHDR) <= pDesc->cbMapTable)
+ {
+ PCUDFPARTMAPHDR pHdr = (PCUDFPARTMAPHDR)&pDesc->abPartitionMaps[off];
+ Log2(("ISO/UDF: %02u @ %#05x: type %u, length %u\n", iMap, off, pHdr->bType, pHdr->cb));
+ if (off + pHdr->cb > pDesc->cbMapTable)
+ {
+ Log2(("ISO/UDF: BAD! Entry is %d bytes too long!\n", off + pHdr->cb - pDesc->cbMapTable));
+ break;
+ }
+ if (pHdr->bType == 1)
+ {
+ PCUDFPARTMAPTYPE1 pType1 = (PCUDFPARTMAPTYPE1)pHdr;
+ UDF_LOG2_MEMBER_EX(pType1, "#06RX16", uVolumeSeqNo, 5);
+ UDF_LOG2_MEMBER_EX(pType1, "#06RX16", uPartitionNo, 5);
+ }
+ else if (pHdr->bType == 2)
+ {
+ PCUDFPARTMAPTYPE2 pType2 = (PCUDFPARTMAPTYPE2)pHdr;
+ UDF_LOG2_MEMBER_ENTITY_ID_EX(pType2, idPartitionType, 5);
+ UDF_LOG2_MEMBER_EX(pType2, "#06RX16", uVolumeSeqNo, 5);
+ UDF_LOG2_MEMBER_EX(pType2, "#06RX16", uPartitionNo, 5);
+ if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_SPM_PARTITION_TYPE))
+ {
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#06RX16", Spm.cBlocksPerPacket, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#04RX8", Spm.cSparingTables, 5);
+ if (pType2->u.Spm.bReserved2)
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#04RX8", Spm.bReserved2, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.cbSparingTable, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.aoffSparingTables[0], 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.aoffSparingTables[1], 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.aoffSparingTables[2], 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Spm.aoffSparingTables[3], 5);
+ }
+ else if (UDF_ENTITY_ID_EQUALS(&pType2->idPartitionType, UDF_ENTITY_ID_MPM_PARTITION_TYPE))
+ {
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Mpm.offMetadataFile, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Mpm.offMetadataMirrorFile, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Mpm.offMetadataBitmapFile, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#010RX32", Mpm.cBlocksAllocationUnit, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#06RX16", Mpm.cBlocksAlignmentUnit, 5);
+ UDF_LOG2_MEMBER_EX(&pType2->u, "#04RX8", Mpm.fFlags, 5);
+ if (!ASMMemIsZero(pType2->u.Mpm.abReserved2, sizeof(pType2->u.Mpm.abReserved2)))
+ UDF_LOG2_MEMBER_EX(&pType2->u, ".5Rhxs", Mpm.abReserved2, 5);
+ }
+ }
+ else
+ Log2(("ISO/UDF: BAD! Unknown type!\n"));
+
+ /* advance */
+ off += pHdr->cb;
+ iMap++;
+ }
+ }
+ }
+#endif
+
+ /*
+ * Check if this is a newer revision of an existing primary volume descriptor.
+ */
+ size_t cbDesc = (size_t)pDesc->cbMapTable + RT_UOFFSETOF(UDFLOGICALVOLUMEDESC, abPartitionMaps);
+ if ( pDesc->cbMapTable >= (UINT32_MAX >> 1)
+ || cbDesc > cbSector)
+ {
+ Log(("ISO/UDF: Logical volume descriptor is too big: %#zx (cbSector=%#x)\n", cbDesc, cbSector));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_TOO_BIT_PARTMAP_IN_LVD,
+ "Logical volume descriptor is too big: %#zx (cbSector=%#x)\n", cbDesc, cbSector);
+ }
+
+ PUDFLOGICALVOLUMEDESC pEndianConvert = NULL;
+ uint32_t i = pInfo->cLogicalVols;
+ while (i--> 0)
+ if ( memcmp(pDesc->achLogicalVolumeID, pInfo->apLogicalVols[i]->achLogicalVolumeID,
+ sizeof(pDesc->achLogicalVolumeID)) == 0
+ && memcmp(&pDesc->DescCharSet, &pInfo->apLogicalVols[i]->DescCharSet,
+ sizeof(pDesc->DescCharSet)) == 0)
+ {
+ if (RT_LE2H_U32(pDesc->uVolumeDescSeqNo) >= pInfo->apLogicalVols[i]->uVolumeDescSeqNo)
+ {
+ Log(("ISO/UDF: Logical descriptor prevails over previous! (%u >= %u)\n",
+ RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apLogicalVols[i]->uVolumeDescSeqNo));
+ pEndianConvert = (PUDFLOGICALVOLUMEDESC)RTMemDup(pDesc, cbDesc);
+ if (!pEndianConvert)
+ return VERR_NO_MEMORY;
+ RTMemFree(pInfo->apLogicalVols[i]);
+ pInfo->apLogicalVols[i] = pEndianConvert;
+ }
+ else
+ Log(("ISO/UDF: Logical descriptor has lower sequence number than the previous! (%u >= %u)\n",
+ RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apLogicalVols[i]->uVolumeDescSeqNo));
+ break;
+ }
+ if (i >= pInfo->cLogicalVols)
+ {
+ /*
+ * It wasn't. Append it.
+ */
+ i = pInfo->cLogicalVols;
+ if (i < RT_ELEMENTS(pInfo->apLogicalVols))
+ {
+ pInfo->apLogicalVols[i] = pEndianConvert = (PUDFLOGICALVOLUMEDESC)RTMemDup(pDesc, cbDesc);
+ if (pEndianConvert)
+ pInfo->cLogicalVols = i + 1;
+ else
+ return VERR_NO_MEMORY;
+ Log2(("ISO/UDF: ++New logical volume descriptor.\n"));
+ }
+ else
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_TOO_MANY_LVDS, "Too many logical volume descriptors");
+ }
+
+#ifdef RT_BIG_ENDIAN
+ /*
+ * Do endian conversion of the descriptor.
+ */
+ if (pEndianConvert)
+ {
+ AssertFailed();
+ }
+#else
+ RT_NOREF(pEndianConvert);
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes an partition descriptor in the VDS (UDF).
+ *
+ * @returns IPRT status code.
+ * @param pInfo Where we gather descriptor information.
+ * @param pDesc The descriptor.
+ * @param pErrInfo Where to return extended error information.
+ */
+static int rtFsIsoVolProcessUdfPartitionDesc(PRTFSISOVDSINFO pInfo, PCUDFPARTITIONDESC pDesc, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: Partition descriptor at sector %#RX32\n", pDesc->Tag.offTag));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uVolumeDescSeqNo);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", fFlags);
+ UDF_LOG2_MEMBER(pDesc, "#06RX16", uPartitionNo);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, PartitionContents);
+ if (UDF_ENTITY_ID_EQUALS(&pDesc->PartitionContents, UDF_ENTITY_ID_PD_PARTITION_CONTENTS_UDF))
+ {
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.UnallocatedSpaceTable);
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.UnallocatedSpaceBitmap);
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.PartitionIntegrityTable);
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.FreedSpaceTable);
+ UDF_LOG2_MEMBER_SHORTAD(&pDesc->ContentsUse, Hdr.FreedSpaceBitmap);
+ if (!ASMMemIsZero(&pDesc->ContentsUse.Hdr.abReserved[0], sizeof(pDesc->ContentsUse.Hdr.abReserved)))
+ Log2(("ISO/UDF: %-32s\n%.88RhxD\n", "Hdr.abReserved[88]:", &pDesc->ContentsUse.Hdr.abReserved[0]));
+ }
+ else if (!ASMMemIsZero(&pDesc->ContentsUse.ab[0], sizeof(pDesc->ContentsUse.ab)))
+ Log2(("ISO/UDF: %-32s\n%.128RhxD\n", "ContentsUse.ab[128]:", &pDesc->ContentsUse.ab[0]));
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uAccessType);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", offLocation);
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", cSectors);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idImplementation);
+ if (!ASMMemIsZero(&pDesc->ImplementationUse.ab[0], sizeof(pDesc->ImplementationUse.ab)))
+ Log2(("ISO/UDF: %-32s\n%.128RhxD\n", "ImplementationUse.ab[128]:", &pDesc->ImplementationUse.ab[0]));
+
+ if (!ASMMemIsZero(&pDesc->abReserved[0], sizeof(pDesc->abReserved)))
+ Log2(("ISO/UDF: %-32s\n%.156RhxD\n", "ImplementationUse.ab[156]:", &pDesc->abReserved[0]));
+ }
+#endif
+
+ /*
+ * Check if this is a newer revision of an existing primary volume descriptor.
+ */
+ PUDFPARTITIONDESC pEndianConvert = NULL;
+ uint32_t i = pInfo->cPartitions;
+ while (i--> 0)
+ if (pDesc->uPartitionNo == pInfo->apPartitions[i]->uPartitionNo)
+ {
+ if (RT_LE2H_U32(pDesc->uVolumeDescSeqNo) >= pInfo->apPartitions[i]->uVolumeDescSeqNo)
+ {
+ Log(("ISO/UDF: Partition descriptor for part %#u prevails over previous! (%u >= %u)\n",
+ pDesc->uPartitionNo, RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apPartitions[i]->uVolumeDescSeqNo));
+ pEndianConvert = pInfo->apPartitions[i];
+ memcpy(pEndianConvert, pDesc, sizeof(*pDesc));
+ }
+ else
+ Log(("ISO/UDF: Partition descriptor for part %#u has a lower sequence number than the previous! (%u < %u)\n",
+ pDesc->uPartitionNo, RT_LE2H_U32(pDesc->uVolumeDescSeqNo), pInfo->apPartitions[i]->uVolumeDescSeqNo));
+ break;
+ }
+ if (i >= pInfo->cPartitions)
+ {
+ /*
+ * It wasn't. Append it.
+ */
+ i = pInfo->cPartitions;
+ if (i < RT_ELEMENTS(pInfo->apPartitions))
+ {
+ pInfo->apPartitions[i] = pEndianConvert = (PUDFPARTITIONDESC)RTMemDup(pDesc, sizeof(*pDesc));
+ if (pEndianConvert)
+ pInfo->cPartitions = i + 1;
+ else
+ return VERR_NO_MEMORY;
+ Log2(("ISO/UDF: ++New partition descriptor.\n"));
+ }
+ else
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_ISOFS_TOO_MANY_PDS, "Too many physical volume descriptors");
+ }
+
+#ifdef RT_BIG_ENDIAN
+ /*
+ * Do endian conversion of the descriptor.
+ */
+ if (pEndianConvert)
+ {
+ AssertFailed();
+ }
+#else
+ RT_NOREF(pEndianConvert);
+#endif
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes an implementation use descriptor in the VDS (UDF).
+ *
+ * @returns IPRT status code.
+ * @param pInfo Where we gather descriptor information.
+ * @param pDesc The descriptor.
+ * @param pErrInfo Where to return extended error information.
+ */
+static int rtFsIsoVolProcessUdfImplUseVolDesc(PRTFSISOVDSINFO pInfo, PCUDFIMPLEMENTATIONUSEVOLUMEDESC pDesc, PRTERRINFO pErrInfo)
+{
+#ifdef LOG_ENABLED
+ Log(("ISO/UDF: Implementation use volume descriptor at sector %#RX32\n", pDesc->Tag.offTag));
+ if (LogIs2Enabled())
+ {
+ UDF_LOG2_MEMBER(pDesc, "#010RX32", uVolumeDescSeqNo);
+ UDF_LOG2_MEMBER_ENTITY_ID(pDesc, idImplementation);
+ if (UDF_ENTITY_ID_EQUALS(&pDesc->idImplementation, UDF_ENTITY_ID_IUVD_IMPLEMENTATION))
+ {
+ UDF_LOG2_MEMBER_CHARSPEC(&pDesc->ImplementationUse, Lvi.Charset);
+ UDF_LOG2_MEMBER_DSTRING(&pDesc->ImplementationUse, Lvi.achVolumeID);
+ UDF_LOG2_MEMBER_DSTRING(&pDesc->ImplementationUse, Lvi.achInfo1);
+ UDF_LOG2_MEMBER_DSTRING(&pDesc->ImplementationUse, Lvi.achInfo2);
+ UDF_LOG2_MEMBER_DSTRING(&pDesc->ImplementationUse, Lvi.achInfo3);
+ UDF_LOG2_MEMBER_ENTITY_ID(&pDesc->ImplementationUse, Lvi.idImplementation);
+ if (!ASMMemIsZero(&pDesc->ImplementationUse.Lvi.abUse[0], sizeof(pDesc->ImplementationUse.Lvi.abUse)))
+ Log2(("ISO/UDF: %-32s\n%.128RhxD\n", "Lvi.abUse[128]:", &pDesc->ImplementationUse.Lvi.abUse[0]));
+ }
+ else if (!ASMMemIsZero(&pDesc->ImplementationUse.ab[0], sizeof(pDesc->ImplementationUse.ab)))
+ Log2(("ISO/UDF: %-32s\n%.460RhxD\n", "ImplementationUse.ab[460]:", &pDesc->ImplementationUse.ab[0]));
+ }
+#endif
+
+ RT_NOREF(pInfo, pDesc, pErrInfo);
+ return VINF_SUCCESS;
+}
+
+
+
+typedef struct RTFSISOSEENSEQENCES
+{
+ /** Number of sequences we've seen thus far. */
+ uint32_t cSequences;
+ /** The per sequence data. */
+ struct
+ {
+ uint64_t off; /**< Byte offset of the sequence. */
+ uint32_t cb; /**< Size of the sequence. */
+ } aSequences[8];
+} RTFSISOSEENSEQENCES;
+typedef RTFSISOSEENSEQENCES *PRTFSISOSEENSEQENCES;
+
+
+
+/**
+ * Process a VDS sequence, recursively dealing with volume descriptor pointers.
+ *
+ * This function only gathers information from the sequence, handling the
+ * prevailing descriptor fun.
+ *
+ * @returns IPRT status code.
+ * @param pThis The instance.
+ * @param pInfo Where to store info from the VDS sequence.
+ * @param offSeq The byte offset of the sequence.
+ * @param cbSeq The length of the sequence.
+ * @param pbBuf Read buffer.
+ * @param cbBuf Size of the read buffer. This is at least one
+ * sector big.
+ * @param cNestings The VDS nesting depth.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolReadAndProcessUdfVdsSeq(PRTFSISOVOL pThis, PRTFSISOVDSINFO pInfo, uint64_t offSeq, uint32_t cbSeq,
+ uint8_t *pbBuf, size_t cbBuf, uint32_t cNestings, PRTERRINFO pErrInfo)
+{
+ AssertReturn(cbBuf >= pThis->cbSector, VERR_INTERNAL_ERROR);
+
+ /*
+ * Check nesting depth.
+ */
+ if (cNestings > 5)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_TOO_MUCH_DATA, "The volume descriptor sequence (VDS) is nested too deeply.");
+
+
+ /*
+ * Do the processing sector by sector to keep things simple.
+ */
+ uint32_t offInSeq = 0;
+ while (offInSeq < cbSeq)
+ {
+ int rc;
+
+ /*
+ * Read the next sector. Zero pad if less that a sector.
+ */
+ Assert((offInSeq & (pThis->cbSector - 1)) == 0);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offSeq + offInSeq, pbBuf, pThis->cbSector, NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Error reading VDS content at %RX64 (LB %#x): %Rrc",
+ offSeq + offInSeq, pThis->cbSector, rc);
+ if (cbSeq - offInSeq < pThis->cbSector)
+ memset(&pbBuf[cbSeq - offInSeq], 0, pThis->cbSector - (cbSeq - offInSeq));
+
+ /*
+ * Check tag.
+ */
+ PCUDFTAG pTag = (PCUDFTAG)pbBuf;
+ rc = rtFsIsoVolValidateUdfDescTagAndCrc(pTag, pThis->cbSector, UINT16_MAX, (offSeq + offInSeq) / pThis->cbSector, pErrInfo);
+ if ( RT_SUCCESS(rc)
+ || ( rc == VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC
+ && ( pTag->idTag == UDF_TAG_ID_LOGICAL_VOLUME_INTEGRITY_DESC
+ || pTag->idTag == UDF_TAG_ID_LOGICAL_VOLUME_DESC
+ || pTag->idTag == UDF_TAG_ID_UNALLOCATED_SPACE_DESC
+ )
+ )
+ )
+ {
+ switch (pTag->idTag)
+ {
+ case UDF_TAG_ID_PRIMARY_VOL_DESC:
+ rc = rtFsIsoVolProcessUdfPrimaryVolDesc(pInfo, (PCUDFPRIMARYVOLUMEDESC)pTag, pErrInfo);
+ break;
+
+ case UDF_TAG_ID_IMPLEMENTATION_USE_VOLUME_DESC:
+ rc = rtFsIsoVolProcessUdfImplUseVolDesc(pInfo, (PCUDFIMPLEMENTATIONUSEVOLUMEDESC)pTag, pErrInfo);
+ break;
+
+ case UDF_TAG_ID_PARTITION_DESC:
+ rc = rtFsIsoVolProcessUdfPartitionDesc(pInfo, (PCUDFPARTITIONDESC)pTag, pErrInfo);
+ break;
+
+ case UDF_TAG_ID_LOGICAL_VOLUME_DESC:
+ if (rc != VERR_ISOFS_INSUFFICIENT_DATA_FOR_DESC_CRC)
+ rc = rtFsIsoVolProcessUdfLogicalVolumeDesc(pInfo, (PCUDFLOGICALVOLUMEDESC)pTag,
+ pThis->cbSector, pErrInfo);
+ else
+ rc = VERR_ISOFS_TOO_BIT_PARTMAP_IN_LVD;
+ break;
+
+ case UDF_TAG_ID_LOGICAL_VOLUME_INTEGRITY_DESC:
+ Log(("ISO/UDF: Ignoring logical volume integrity descriptor at offset %#RX64.\n", offSeq + offInSeq));
+ rc = VINF_SUCCESS;
+ break;
+
+ case UDF_TAG_ID_UNALLOCATED_SPACE_DESC:
+ Log(("ISO/UDF: Ignoring unallocated space descriptor at offset %#RX64.\n", offSeq + offInSeq));
+ rc = VINF_SUCCESS;
+ break;
+
+ case UDF_TAG_ID_ANCHOR_VOLUME_DESC_PTR:
+ Log(("ISO/UDF: Ignoring AVDP in VDS (at offset %#RX64).\n", offSeq + offInSeq));
+ rc = VINF_SUCCESS;
+ break;
+
+ case UDF_TAG_ID_VOLUME_DESC_PTR:
+ {
+ PCUDFVOLUMEDESCPTR pVdp = (PCUDFVOLUMEDESCPTR)pTag;
+ Log(("ISO/UDF: Processing volume descriptor pointer at offset %#RX64: %#x LB %#x (seq %#x); cNestings=%d\n",
+ offSeq + offInSeq, pVdp->NextVolumeDescSeq.off, pVdp->NextVolumeDescSeq.cb,
+ pVdp->uVolumeDescSeqNo, cNestings));
+ rc = rtFsIsoVolReadAndProcessUdfVdsSeq(pThis, pInfo, (uint64_t)pVdp->NextVolumeDescSeq.off * pThis->cbSector,
+ pVdp->NextVolumeDescSeq.cb, pbBuf, cbBuf, cNestings + 1, pErrInfo);
+ break;
+ }
+
+ case UDF_TAG_ID_TERMINATING_DESC:
+ Log(("ISO/UDF: Terminating descriptor at offset %#RX64\n", offSeq + offInSeq));
+ return VINF_SUCCESS;
+
+ default:
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_ISOFS_UNEXPECTED_VDS_DESC,
+ "Unexpected/unknown VDS descriptor %#x at byte offset %#RX64",
+ pThis->cbSector, offSeq + offInSeq);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ /* The descriptor sequence is usually zero padded to 16 sectors. Just
+ ignore zero descriptors. */
+ else if (rc != VERR_ISOFS_TAG_IS_ALL_ZEROS)
+ return rc;
+
+ /*
+ * Advance.
+ */
+ offInSeq += pThis->cbSector;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Processes a volume descriptor sequence (VDS).
+ *
+ * @returns IPRT status code.
+ * @param pThis The instance.
+ * @param offSeq The byte offset of the sequence.
+ * @param cbSeq The length of the sequence.
+ * @param pSeenSequences Structure where to keep track of VDSes we've already
+ * processed, to avoid redoing one that we don't
+ * understand.
+ * @param pbBuf Read buffer.
+ * @param cbBuf Size of the read buffer. This is at least one
+ * sector big.
+ * @param pErrInfo Where to report extended error information.
+ */
+static int rtFsIsoVolReadAndProcessUdfVds(PRTFSISOVOL pThis, uint64_t offSeq, uint32_t cbSeq,
+ PRTFSISOSEENSEQENCES pSeenSequences, uint8_t *pbBuf, size_t cbBuf,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * Skip if already seen.
+ */
+ uint32_t i = pSeenSequences->cSequences;
+ while (i-- > 0)
+ if ( pSeenSequences->aSequences[i].off == offSeq
+ && pSeenSequences->aSequences[i].cb == cbSeq)
+ return VERR_NOT_FOUND;
+
+ /* Not seen, so add it. */
+ Assert(pSeenSequences->cSequences + 1 <= RT_ELEMENTS(pSeenSequences->aSequences));
+ pSeenSequences->aSequences[pSeenSequences->cSequences].cb = cbSeq;
+ pSeenSequences->aSequences[pSeenSequences->cSequences].off = offSeq;
+ pSeenSequences->cSequences++;
+
+ LogFlow(("ISO/UDF: Processing anchor volume descriptor sequence at offset %#RX64 LB %#RX32\n", offSeq, cbSeq));
+
+ /*
+ * Gather relevant descriptor info from the VDS then process it and on
+ * success copy it into the instance.
+ *
+ * The processing has to be done in a different function because there may
+ * be links to sub-sequences that needs to be processed. We do this by
+ * recursing and check that we don't go to deep.
+ */
+ RTFSISOVDSINFO Info;
+ RT_ZERO(Info);
+ int rc = rtFsIsoVolReadAndProcessUdfVdsSeq(pThis, &Info, offSeq, cbSeq, pbBuf, cbBuf, 0, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoVolProcessUdfVdsSeqInfo(pThis, &Info, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsIsoVolProcessUdfFileSetDescs(pThis, pbBuf, cbBuf, pErrInfo);
+ }
+
+ /*
+ * Clean up info.
+ */
+ i = Info.cPrimaryVols;
+ while (i-- > 0)
+ RTMemFree(Info.apPrimaryVols[i]);
+
+ i = Info.cLogicalVols;
+ while (i-- > 0)
+ RTMemFree(Info.apLogicalVols[i]);
+
+ i = Info.cPartitions;
+ while (i-- > 0)
+ RTMemFree(Info.apPartitions[i]);
+
+ RTMemFree(Info.paPartMaps);
+
+ return rc;
+}
+
+
+static int rtFsIsoVolReadAndHandleUdfAvdp(PRTFSISOVOL pThis, uint64_t offAvdp, uint8_t *pbBuf, size_t cbBuf,
+ PRTFSISOSEENSEQENCES pSeenSequences, PRTERRINFO pErrInfo)
+{
+ /*
+ * Try read the descriptor and validate its tag.
+ */
+ PUDFANCHORVOLUMEDESCPTR pAvdp = (PUDFANCHORVOLUMEDESCPTR)pbBuf;
+ size_t cbAvdpRead = RT_MIN(pThis->cbSector, cbBuf);
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, offAvdp, pAvdp, cbAvdpRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoVolValidateUdfDescTag(&pAvdp->Tag, UDF_TAG_ID_ANCHOR_VOLUME_DESC_PTR, offAvdp / pThis->cbSector, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ Log2(("ISO/UDF: AVDP: MainVolumeDescSeq=%#RX32 LB %#RX32, ReserveVolumeDescSeq=%#RX32 LB %#RX32\n",
+ pAvdp->MainVolumeDescSeq.off, pAvdp->MainVolumeDescSeq.cb,
+ pAvdp->ReserveVolumeDescSeq.off, pAvdp->ReserveVolumeDescSeq.cb));
+
+ /*
+ * Try the main sequence if it looks sane.
+ */
+ UDFEXTENTAD const ReserveVolumeDescSeq = pAvdp->ReserveVolumeDescSeq;
+ if ( pAvdp->MainVolumeDescSeq.off < pThis->cBackingSectors
+ && (uint64_t)pAvdp->MainVolumeDescSeq.off
+ + (pAvdp->MainVolumeDescSeq.cb + pThis->cbSector - 1) / pThis->cbSector
+ <= pThis->cBackingSectors)
+ {
+ rc = rtFsIsoVolReadAndProcessUdfVds(pThis, (uint64_t)pAvdp->MainVolumeDescSeq.off * pThis->cbSector,
+ pAvdp->MainVolumeDescSeq.cb, pSeenSequences, pbBuf, cbBuf, pErrInfo);
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_FOUND,
+ "MainVolumeDescSeq is out of bounds: sector %#RX32 LB %#RX32 bytes, image is %#RX64 sectors",
+ pAvdp->MainVolumeDescSeq.off, pAvdp->MainVolumeDescSeq.cb, pThis->cBackingSectors);
+ if (ReserveVolumeDescSeq.cb > 0)
+ {
+ if ( ReserveVolumeDescSeq.off < pThis->cBackingSectors
+ && (uint64_t)ReserveVolumeDescSeq.off
+ + (ReserveVolumeDescSeq.cb + pThis->cbSector - 1) / pThis->cbSector
+ <= pThis->cBackingSectors)
+ {
+ rc = rtFsIsoVolReadAndProcessUdfVds(pThis, (uint64_t)ReserveVolumeDescSeq.off * pThis->cbSector,
+ ReserveVolumeDescSeq.cb, pSeenSequences, pbBuf, cbBuf, pErrInfo);
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_FOUND,
+ "ReserveVolumeDescSeq is out of bounds: sector %#RX32 LB %#RX32 bytes, image is %#RX64 sectors",
+ ReserveVolumeDescSeq.off, ReserveVolumeDescSeq.cb, pThis->cBackingSectors);
+ }
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, rc,
+ "Error reading sector at offset %#RX64 (anchor volume descriptor pointer): %Rrc", offAvdp, rc);
+
+ return rc;
+}
+
+
+/**
+ * Goes looking for UDF when we've seens a volume recognition sequence.
+ *
+ * @returns IPRT status code.
+ * @param pThis The volume instance data.
+ * @param puUdfLevel The UDF level indicated by the VRS.
+ * @param offUdfBootVolDesc The offset of the BOOT2 descriptor, UINT64_MAX
+ * if not encountered.
+ * @param pbBuf Buffer for reading into.
+ * @param cbBuf The size of the buffer. At least one sector.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtFsIsoVolHandleUdfDetection(PRTFSISOVOL pThis, uint8_t *puUdfLevel, uint64_t offUdfBootVolDesc,
+ uint8_t *pbBuf, size_t cbBuf, PRTERRINFO pErrInfo)
+{
+ NOREF(offUdfBootVolDesc);
+
+ /*
+ * There are up to three anchor volume descriptor pointers that can give us
+ * two different descriptor sequences each. Usually, the different AVDP
+ * structures points to the same two sequences. The idea here is that
+ * sectors may deteriorate and become unreadable, and we're supposed to try
+ * out alternative sectors to get the job done. If we really took this
+ * seriously, we could try read all sequences in parallel and use the
+ * sectors that are good. However, we'll try keep things reasonably simple
+ * since we'll most likely be reading from hard disks rather than optical
+ * media.
+ *
+ * We keep track of which sequences we've processed so we don't try to do it
+ * again when alternative AVDP sectors points to the same sequences.
+ */
+ pThis->Udf.uLevel = *puUdfLevel;
+ RTFSISOSEENSEQENCES SeenSequences;
+ RT_ZERO(SeenSequences);
+ int rc1 = rtFsIsoVolReadAndHandleUdfAvdp(pThis, 256 * pThis->cbSector, pbBuf, cbBuf,
+ &SeenSequences, pErrInfo);
+ if (RT_SUCCESS(rc1))
+ return rc1;
+
+ int rc2 = rtFsIsoVolReadAndHandleUdfAvdp(pThis, pThis->cbBacking - 256 * pThis->cbSector,
+ pbBuf, cbBuf, &SeenSequences, pErrInfo);
+ if (RT_SUCCESS(rc2))
+ return rc2;
+
+ int rc3 = rtFsIsoVolReadAndHandleUdfAvdp(pThis, pThis->cbBacking - pThis->cbSector,
+ pbBuf, cbBuf, &SeenSequences, pErrInfo);
+ if (RT_SUCCESS(rc3))
+ return rc3;
+
+ /*
+ * Return failure if the alternatives have been excluded.
+ *
+ * Note! The error info won't be correct here.
+ */
+ pThis->Udf.uLevel = *puUdfLevel = 0;
+
+ if (RTFSISO9660_F_IS_ONLY_TYPE(pThis->fFlags, RTFSISO9660_F_NO_UDF))
+ return rc1 != VERR_NOT_FOUND ? rc1 : rc2 != VERR_NOT_FOUND ? rc2 : rc3;
+ return VINF_SUCCESS;
+}
+
+
+
+#ifdef LOG_ENABLED
+
+/** Logging helper. */
+static size_t rtFsIsoVolGetStrippedLength(const char *pachField, size_t cchField)
+{
+ while (cchField > 0 && pachField[cchField - 1] == ' ')
+ cchField--;
+ return cchField;
+}
+
+/** Logging helper. */
+static char *rtFsIsoVolGetMaybeUtf16Be(const char *pachField, size_t cchField, char *pszDst, size_t cbDst)
+{
+ /* Check the format by looking for zero bytes. ISO-9660 doesn't allow zeros.
+ This doesn't have to be a UTF-16BE string. */
+ size_t cFirstZeros = 0;
+ size_t cSecondZeros = 0;
+ for (size_t off = 0; off + 1 < cchField; off += 2)
+ {
+ cFirstZeros += pachField[off] == '\0';
+ cSecondZeros += pachField[off + 1] == '\0';
+ }
+
+ int rc = VINF_SUCCESS;
+ char *pszTmp = &pszDst[10];
+ size_t cchRet = 0;
+ if (cFirstZeros > cSecondZeros)
+ {
+ /* UTF-16BE / UTC-2BE: */
+ if (cchField & 1)
+ {
+ if (pachField[cchField - 1] == '\0' || pachField[cchField - 1] == ' ')
+ cchField--;
+ else
+ rc = VERR_INVALID_UTF16_ENCODING;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ while ( cchField >= 2
+ && pachField[cchField - 1] == ' '
+ && pachField[cchField - 2] == '\0')
+ cchField -= 2;
+
+ rc = RTUtf16BigToUtf8Ex((PCRTUTF16)pachField, cchField / sizeof(RTUTF16), &pszTmp, cbDst - 10 - 1, &cchRet);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pszDst[0] = 'U';
+ pszDst[1] = 'T';
+ pszDst[2] = 'F';
+ pszDst[3] = '-';
+ pszDst[4] = '1';
+ pszDst[5] = '6';
+ pszDst[6] = 'B';
+ pszDst[7] = 'E';
+ pszDst[8] = ':';
+ pszDst[9] = '\'';
+ pszDst[10 + cchRet] = '\'';
+ pszDst[10 + cchRet + 1] = '\0';
+ }
+ else
+ RTStrPrintf(pszDst, cbDst, "UTF-16BE: %.*Rhxs", cchField, pachField);
+ }
+ else if (cSecondZeros > 0)
+ {
+ /* Little endian UTF-16 / UCS-2 (ASSUMES host is little endian, sorry) */
+ if (cchField & 1)
+ {
+ if (pachField[cchField - 1] == '\0' || pachField[cchField - 1] == ' ')
+ cchField--;
+ else
+ rc = VERR_INVALID_UTF16_ENCODING;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ while ( cchField >= 2
+ && pachField[cchField - 1] == '\0'
+ && pachField[cchField - 2] == ' ')
+ cchField -= 2;
+
+ rc = RTUtf16ToUtf8Ex((PCRTUTF16)pachField, cchField / sizeof(RTUTF16), &pszTmp, cbDst - 10 - 1, &cchRet);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pszDst[0] = 'U';
+ pszDst[1] = 'T';
+ pszDst[2] = 'F';
+ pszDst[3] = '-';
+ pszDst[4] = '1';
+ pszDst[5] = '6';
+ pszDst[6] = 'L';
+ pszDst[7] = 'E';
+ pszDst[8] = ':';
+ pszDst[9] = '\'';
+ pszDst[10 + cchRet] = '\'';
+ pszDst[10 + cchRet + 1] = '\0';
+ }
+ else
+ RTStrPrintf(pszDst, cbDst, "UTF-16LE: %.*Rhxs", cchField, pachField);
+ }
+ else
+ {
+ /* ASSUME UTF-8/ASCII. */
+ while ( cchField > 0
+ && pachField[cchField - 1] == ' ')
+ cchField--;
+ rc = RTStrValidateEncodingEx(pachField, cchField, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ RTStrPrintf(pszDst, cbDst, "UTF-8: '%.*s'", cchField, pachField);
+ else
+ RTStrPrintf(pszDst, cbDst, "UNK-8: %.*Rhxs", cchField, pachField);
+ }
+ return pszDst;
+}
+
+
+/**
+ * Logs the primary or supplementary volume descriptor
+ *
+ * @param pVolDesc The descriptor.
+ */
+static void rtFsIsoVolLogPrimarySupplementaryVolDesc(PCISO9660SUPVOLDESC pVolDesc)
+{
+ if (LogIs2Enabled())
+ {
+ char szTmp[384];
+ Log2(("ISO9660: fVolumeFlags: %#RX8\n", pVolDesc->fVolumeFlags));
+ Log2(("ISO9660: achSystemId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achSystemId, sizeof(pVolDesc->achSystemId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achVolumeId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achVolumeId, sizeof(pVolDesc->achVolumeId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: Unused73: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->Unused73.be), RT_LE2H_U32(pVolDesc->Unused73.le)));
+ Log2(("ISO9660: VolumeSpaceSize: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le)));
+ Log2(("ISO9660: abEscapeSequences: '%.*s'\n", rtFsIsoVolGetStrippedLength((char *)pVolDesc->abEscapeSequences, sizeof(pVolDesc->abEscapeSequences)), pVolDesc->abEscapeSequences));
+ Log2(("ISO9660: cVolumesInSet: {%#RX16,%#RX16}\n", RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le)));
+ Log2(("ISO9660: VolumeSeqNo: {%#RX16,%#RX16}\n", RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le)));
+ Log2(("ISO9660: cbLogicalBlock: {%#RX16,%#RX16}\n", RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le)));
+ Log2(("ISO9660: cbPathTable: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->cbPathTable.be), RT_LE2H_U32(pVolDesc->cbPathTable.le)));
+ Log2(("ISO9660: offTypeLPathTable: %#RX32\n", RT_LE2H_U32(pVolDesc->offTypeLPathTable)));
+ Log2(("ISO9660: offOptionalTypeLPathTable: %#RX32\n", RT_LE2H_U32(pVolDesc->offOptionalTypeLPathTable)));
+ Log2(("ISO9660: offTypeMPathTable: %#RX32\n", RT_BE2H_U32(pVolDesc->offTypeMPathTable)));
+ Log2(("ISO9660: offOptionalTypeMPathTable: %#RX32\n", RT_BE2H_U32(pVolDesc->offOptionalTypeMPathTable)));
+ Log2(("ISO9660: achVolumeSetId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achVolumeSetId, sizeof(pVolDesc->achVolumeSetId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achPublisherId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achPublisherId, sizeof(pVolDesc->achPublisherId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achDataPreparerId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achDataPreparerId, sizeof(pVolDesc->achDataPreparerId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achApplicationId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achApplicationId, sizeof(pVolDesc->achApplicationId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achCopyrightFileId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achCopyrightFileId, sizeof(pVolDesc->achCopyrightFileId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achAbstractFileId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achAbstractFileId, sizeof(pVolDesc->achAbstractFileId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: achBibliographicFileId: %s\n", rtFsIsoVolGetMaybeUtf16Be(pVolDesc->achBibliographicFileId, sizeof(pVolDesc->achBibliographicFileId), szTmp, sizeof(szTmp)) ));
+ Log2(("ISO9660: BirthTime: %.4s-%.2s-%.2s %.2s:%.2s:%.2s.%.2s%+03d\n",
+ pVolDesc->BirthTime.achYear,
+ pVolDesc->BirthTime.achMonth,
+ pVolDesc->BirthTime.achDay,
+ pVolDesc->BirthTime.achHour,
+ pVolDesc->BirthTime.achMinute,
+ pVolDesc->BirthTime.achSecond,
+ pVolDesc->BirthTime.achCentisecond,
+ pVolDesc->BirthTime.offUtc*4/60));
+ Log2(("ISO9660: ModifyTime: %.4s-%.2s-%.2s %.2s:%.2s:%.2s.%.2s%+03d\n",
+ pVolDesc->ModifyTime.achYear,
+ pVolDesc->ModifyTime.achMonth,
+ pVolDesc->ModifyTime.achDay,
+ pVolDesc->ModifyTime.achHour,
+ pVolDesc->ModifyTime.achMinute,
+ pVolDesc->ModifyTime.achSecond,
+ pVolDesc->ModifyTime.achCentisecond,
+ pVolDesc->ModifyTime.offUtc*4/60));
+ Log2(("ISO9660: ExpireTime: %.4s-%.2s-%.2s %.2s:%.2s:%.2s.%.2s%+03d\n",
+ pVolDesc->ExpireTime.achYear,
+ pVolDesc->ExpireTime.achMonth,
+ pVolDesc->ExpireTime.achDay,
+ pVolDesc->ExpireTime.achHour,
+ pVolDesc->ExpireTime.achMinute,
+ pVolDesc->ExpireTime.achSecond,
+ pVolDesc->ExpireTime.achCentisecond,
+ pVolDesc->ExpireTime.offUtc*4/60));
+ Log2(("ISO9660: EffectiveTime: %.4s-%.2s-%.2s %.2s:%.2s:%.2s.%.2s%+03d\n",
+ pVolDesc->EffectiveTime.achYear,
+ pVolDesc->EffectiveTime.achMonth,
+ pVolDesc->EffectiveTime.achDay,
+ pVolDesc->EffectiveTime.achHour,
+ pVolDesc->EffectiveTime.achMinute,
+ pVolDesc->EffectiveTime.achSecond,
+ pVolDesc->EffectiveTime.achCentisecond,
+ pVolDesc->EffectiveTime.offUtc*4/60));
+ Log2(("ISO9660: bFileStructureVersion: %#RX8\n", pVolDesc->bFileStructureVersion));
+ Log2(("ISO9660: bReserved883: %#RX8\n", pVolDesc->bReserved883));
+
+ Log2(("ISO9660: RootDir.cbDirRec: %#RX8\n", pVolDesc->RootDir.DirRec.cbDirRec));
+ Log2(("ISO9660: RootDir.cExtAttrBlocks: %#RX8\n", pVolDesc->RootDir.DirRec.cExtAttrBlocks));
+ Log2(("ISO9660: RootDir.offExtent: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->RootDir.DirRec.offExtent.be), RT_LE2H_U32(pVolDesc->RootDir.DirRec.offExtent.le)));
+ Log2(("ISO9660: RootDir.cbData: {%#RX32,%#RX32}\n", RT_BE2H_U32(pVolDesc->RootDir.DirRec.cbData.be), RT_LE2H_U32(pVolDesc->RootDir.DirRec.cbData.le)));
+ Log2(("ISO9660: RootDir.RecTime: %04u-%02u-%02u %02u:%02u:%02u%+03d\n",
+ pVolDesc->RootDir.DirRec.RecTime.bYear + 1900,
+ pVolDesc->RootDir.DirRec.RecTime.bMonth,
+ pVolDesc->RootDir.DirRec.RecTime.bDay,
+ pVolDesc->RootDir.DirRec.RecTime.bHour,
+ pVolDesc->RootDir.DirRec.RecTime.bMinute,
+ pVolDesc->RootDir.DirRec.RecTime.bSecond,
+ pVolDesc->RootDir.DirRec.RecTime.offUtc*4/60));
+ Log2(("ISO9660: RootDir.RecTime.fFileFlags: %RX8\n", pVolDesc->RootDir.DirRec.fFileFlags));
+ Log2(("ISO9660: RootDir.RecTime.bFileUnitSize: %RX8\n", pVolDesc->RootDir.DirRec.bFileUnitSize));
+ Log2(("ISO9660: RootDir.RecTime.bInterleaveGapSize: %RX8\n", pVolDesc->RootDir.DirRec.bInterleaveGapSize));
+ Log2(("ISO9660: RootDir.RecTime.VolumeSeqNo: {%#RX16,%#RX16}\n", RT_BE2H_U16(pVolDesc->RootDir.DirRec.VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->RootDir.DirRec.VolumeSeqNo.le)));
+ Log2(("ISO9660: RootDir.RecTime.bFileIdLength: %RX8\n", pVolDesc->RootDir.DirRec.bFileIdLength));
+ Log2(("ISO9660: RootDir.RecTime.achFileId: '%.*s'\n", pVolDesc->RootDir.DirRec.bFileIdLength, pVolDesc->RootDir.DirRec.achFileId));
+ uint32_t offSysUse = RT_UOFFSETOF_DYN(ISO9660DIRREC, achFileId[pVolDesc->RootDir.DirRec.bFileIdLength])
+ + !(pVolDesc->RootDir.DirRec.bFileIdLength & 1);
+ if (offSysUse < pVolDesc->RootDir.DirRec.cbDirRec)
+ {
+ Log2(("ISO9660: RootDir System Use:\n%.*RhxD\n",
+ pVolDesc->RootDir.DirRec.cbDirRec - offSysUse, &pVolDesc->RootDir.ab[offSysUse]));
+ }
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+/**
+ * Deal with a root directory from a primary or supplemental descriptor.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO 9660 instance being initialized.
+ * @param pRootDir The root directory record to check out.
+ * @param pDstRootDir Where to store a copy of the root dir record.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsIsoVolHandleRootDir(PRTFSISOVOL pThis, PCISO9660DIRREC pRootDir,
+ PISO9660DIRREC pDstRootDir, PRTERRINFO pErrInfo)
+{
+ if (pRootDir->cbDirRec < RT_UOFFSETOF(ISO9660DIRREC, achFileId))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Root dir record size is too small: %#x (min %#x)",
+ pRootDir->cbDirRec, RT_UOFFSETOF(ISO9660DIRREC, achFileId));
+
+ if (!(pRootDir->fFileFlags & ISO9660_FILE_FLAGS_DIRECTORY))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Root dir is not flagged as directory: %#x", pRootDir->fFileFlags);
+ if (pRootDir->fFileFlags & ISO9660_FILE_FLAGS_MULTI_EXTENT)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Root dir is cannot be multi-extent: %#x", pRootDir->fFileFlags);
+
+ if (RT_LE2H_U32(pRootDir->cbData.le) != RT_BE2H_U32(pRootDir->cbData.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid root dir size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pRootDir->cbData.be), RT_LE2H_U32(pRootDir->cbData.le));
+ if (RT_LE2H_U32(pRootDir->cbData.le) == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Zero sized root dir");
+
+ if (RT_LE2H_U32(pRootDir->offExtent.le) != RT_BE2H_U32(pRootDir->offExtent.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid root dir extent: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pRootDir->offExtent.be), RT_LE2H_U32(pRootDir->offExtent.le));
+
+ if (RT_LE2H_U16(pRootDir->VolumeSeqNo.le) != RT_BE2H_U16(pRootDir->VolumeSeqNo.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid root dir volume sequence ID: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pRootDir->VolumeSeqNo.be), RT_LE2H_U16(pRootDir->VolumeSeqNo.le));
+ if (RT_LE2H_U16(pRootDir->VolumeSeqNo.le) != pThis->idPrimaryVol)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Expected root dir to have same volume sequence number as primary volume: %#x, expected %#x",
+ RT_LE2H_U16(pRootDir->VolumeSeqNo.le), pThis->idPrimaryVol);
+
+ /*
+ * Seems okay, copy it.
+ */
+ *pDstRootDir = *pRootDir;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Deal with a primary volume descriptor.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO 9660 instance being initialized.
+ * @param pVolDesc The volume descriptor to handle.
+ * @param offVolDesc The disk offset of the volume descriptor.
+ * @param pRootDir Where to return a copy of the root directory record.
+ * @param poffRootDirRec Where to return the disk offset of the root dir.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsIsoVolHandlePrimaryVolDesc(PRTFSISOVOL pThis, PCISO9660PRIMARYVOLDESC pVolDesc, uint32_t offVolDesc,
+ PISO9660DIRREC pRootDir, uint64_t *poffRootDirRec, PRTERRINFO pErrInfo)
+{
+ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion);
+
+ /*
+ * Take down the location of the primary volume descriptor so we can get
+ * the volume lable and other info from it later.
+ */
+ pThis->offPrimaryVolDesc = offVolDesc;
+
+ /*
+ * We need the block size ...
+ */
+ pThis->cbBlock = RT_LE2H_U16(pVolDesc->cbLogicalBlock.le);
+ if ( pThis->cbBlock != RT_BE2H_U16(pVolDesc->cbLogicalBlock.be)
+ || !RT_IS_POWER_OF_TWO(pThis->cbBlock)
+ || pThis->cbBlock / pThis->cbSector < 1)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid logical block size: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cbLogicalBlock.be), RT_LE2H_U16(pVolDesc->cbLogicalBlock.le));
+ if (pThis->cbBlock / pThis->cbSector > 128)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Unsupported block size: %#x\n", pThis->cbBlock);
+
+ /*
+ * ... volume space size ...
+ */
+ pThis->cBlocksInPrimaryVolumeSpace = RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le);
+ if (pThis->cBlocksInPrimaryVolumeSpace != RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid volume space size: {%#RX32,%#RX32}",
+ RT_BE2H_U32(pVolDesc->VolumeSpaceSize.be), RT_LE2H_U32(pVolDesc->VolumeSpaceSize.le));
+ pThis->cbPrimaryVolumeSpace = pThis->cBlocksInPrimaryVolumeSpace * (uint64_t)pThis->cbBlock;
+
+ /*
+ * ... number of volumes in the set ...
+ */
+ pThis->cVolumesInSet = RT_LE2H_U16(pVolDesc->cVolumesInSet.le);
+ if ( pThis->cVolumesInSet != RT_BE2H_U16(pVolDesc->cVolumesInSet.be)
+ || pThis->cVolumesInSet == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid volume set size: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->cVolumesInSet.be), RT_LE2H_U16(pVolDesc->cVolumesInSet.le));
+ if (pThis->cVolumesInSet > 32)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "Too large volume set size: %#x\n", pThis->cVolumesInSet);
+
+ /*
+ * ... primary volume sequence ID ...
+ */
+ pThis->idPrimaryVol = RT_LE2H_U16(pVolDesc->VolumeSeqNo.le);
+ if (pThis->idPrimaryVol != RT_BE2H_U16(pVolDesc->VolumeSeqNo.be))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Invalid volume sequence ID: {%#RX16,%#RX16}",
+ RT_BE2H_U16(pVolDesc->VolumeSeqNo.be), RT_LE2H_U16(pVolDesc->VolumeSeqNo.le));
+ if ( pThis->idPrimaryVol > pThis->cVolumesInSet
+ || pThis->idPrimaryVol < 1)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Volume sequence ID out of of bound: %#x (1..%#x)\n", pThis->idPrimaryVol, pThis->cVolumesInSet);
+
+ /*
+ * ... and the root directory record.
+ */
+ *poffRootDirRec = offVolDesc + RT_UOFFSETOF(ISO9660PRIMARYVOLDESC, RootDir.DirRec);
+ return rtFsIsoVolHandleRootDir(pThis, &pVolDesc->RootDir.DirRec, pRootDir, pErrInfo);
+}
+
+
+/**
+ * Deal with a supplementary volume descriptor.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO 9660 instance being initialized.
+ * @param pVolDesc The volume descriptor to handle.
+ * @param offVolDesc The disk offset of the volume descriptor.
+ * @param pbUcs2Level Where to return the joliet level, if found. Caller
+ * initializes this to zero, we'll return 1, 2 or 3 if
+ * joliet was detected.
+ * @param pRootDir Where to return the root directory, if found.
+ * @param poffRootDirRec Where to return the disk offset of the root dir.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsIsoVolHandleSupplementaryVolDesc(PRTFSISOVOL pThis, PCISO9660SUPVOLDESC pVolDesc, uint32_t offVolDesc,
+ uint8_t *pbUcs2Level, PISO9660DIRREC pRootDir, uint64_t *poffRootDirRec,
+ PRTERRINFO pErrInfo)
+{
+ if (pVolDesc->bFileStructureVersion != ISO9660_FILE_STRUCTURE_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported file structure version: %#x", pVolDesc->bFileStructureVersion);
+
+ /*
+ * Is this a joliet volume descriptor? If not, we probably don't need to
+ * care about it.
+ */
+ if ( pVolDesc->abEscapeSequences[0] != ISO9660_JOLIET_ESC_SEQ_0
+ || pVolDesc->abEscapeSequences[1] != ISO9660_JOLIET_ESC_SEQ_1
+ || ( pVolDesc->abEscapeSequences[2] != ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1
+ && pVolDesc->abEscapeSequences[2] != ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2
+ && pVolDesc->abEscapeSequences[2] != ISO9660_JOLIET_ESC_SEQ_2_LEVEL_3))
+ return VINF_SUCCESS;
+
+ /*
+ * Skip if joliet is unwanted.
+ */
+ if (pThis->fFlags & RTFSISO9660_F_NO_JOLIET)
+ return VINF_SUCCESS;
+
+ /*
+ * Check that the joliet descriptor matches the primary one.
+ * Note! These are our assumptions and may be wrong.
+ */
+ if (pThis->cbBlock == 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Supplementary joliet volume descriptor is not supported when appearing before the primary volume descriptor");
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock) != pThis->cbBlock)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Logical block size for joliet volume descriptor differs from primary: %#RX16 vs %#RX16\n",
+ ISO9660_GET_ENDIAN(&pVolDesc->cbLogicalBlock), pThis->cbBlock);
+#if 0 /* Not necessary. */
+ /* Used to be !=, changed to > for ubuntu 20.10 and later. Wonder if they exclude a few files
+ and thus end up with a different total. Obviously, this test is a big bogus, as we don't
+ really seem to care about the value at all... */
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize) > pThis->cBlocksInPrimaryVolumeSpace)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Volume space size for joliet volume descriptor differs from primary: %#RX32 vs %#RX32\n",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSpaceSize), pThis->cBlocksInPrimaryVolumeSpace);
+#endif
+ if (ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet) != pThis->cVolumesInSet)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Volume set size for joliet volume descriptor differs from primary: %#RX16 vs %#RX32\n",
+ ISO9660_GET_ENDIAN(&pVolDesc->cVolumesInSet), pThis->cVolumesInSet);
+ if (ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo) != pThis->idPrimaryVol)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Volume sequence ID for joliet volume descriptor differs from primary: %#RX16 vs %#RX32\n",
+ ISO9660_GET_ENDIAN(&pVolDesc->VolumeSeqNo), pThis->idPrimaryVol);
+
+ if (*pbUcs2Level != 0)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "More than one supplementary joliet volume descriptor");
+
+ /*
+ * Switch to the joliet root dir as it has UTF-16 stuff in it.
+ */
+ int rc = rtFsIsoVolHandleRootDir(pThis, &pVolDesc->RootDir.DirRec, pRootDir, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *poffRootDirRec = offVolDesc + RT_UOFFSETOF(ISO9660SUPVOLDESC, RootDir.DirRec);
+ *pbUcs2Level = pVolDesc->abEscapeSequences[2] == ISO9660_JOLIET_ESC_SEQ_2_LEVEL_1 ? 1
+ : pVolDesc->abEscapeSequences[2] == ISO9660_JOLIET_ESC_SEQ_2_LEVEL_2 ? 2 : 3;
+ Log(("ISO9660: Joliet with UCS-2 level %u\n", *pbUcs2Level));
+
+ /*
+ * Take down the location of the secondary volume descriptor so we can get
+ * the volume lable and other info from it later.
+ */
+ pThis->offSecondaryVolDesc = offVolDesc;
+ }
+ return rc;
+}
+
+
+
+/**
+ * Worker for RTFsIso9660VolOpen.
+ *
+ * @returns IPRT status code.
+ * @param pThis The ISO VFS instance to initialize.
+ * @param hVfsSelf The ISO VFS handle (no reference consumed).
+ * @param hVfsBacking The file backing the alleged ISO file system.
+ * Reference is consumed (via rtFsIsoVol_Close).
+ * @param fFlags Flags, RTFSISO9660_F_XXX.
+ * @param pErrInfo Where to return additional error info. Can be NULL.
+ */
+static int rtFsIsoVolTryInit(PRTFSISOVOL pThis, RTVFS hVfsSelf, RTVFSFILE hVfsBacking, uint32_t fFlags, PRTERRINFO pErrInfo)
+{
+ uint32_t const cbSector = 2048;
+
+ /*
+ * First initialize the state so that rtFsIsoVol_Close won't trip up.
+ */
+ pThis->hVfsSelf = hVfsSelf;
+ pThis->hVfsBacking = hVfsBacking; /* Caller referenced it for us, we consume it; rtFsIsoVol_Close releases it. */
+ pThis->cbBacking = 0;
+ pThis->cBackingSectors = 0;
+ pThis->fFlags = fFlags;
+ pThis->cbSector = cbSector;
+ pThis->cbBlock = 0;
+ pThis->cBlocksInPrimaryVolumeSpace = 0;
+ pThis->cbPrimaryVolumeSpace = 0;
+ pThis->cVolumesInSet = 0;
+ pThis->idPrimaryVol = UINT32_MAX;
+ pThis->fIsUtf16 = false;
+ pThis->pRootDir = NULL;
+ pThis->fHaveRock = false;
+ pThis->offSuspSkip = 0;
+ pThis->offRockBuf = UINT64_MAX;
+
+ /*
+ * Do init stuff that may fail.
+ */
+ int rc = RTCritSectInit(&pThis->RockBufLock);
+ AssertRCReturn(rc, rc);
+
+ rc = RTVfsFileQuerySize(hVfsBacking, &pThis->cbBacking);
+ if (RT_SUCCESS(rc))
+ pThis->cBackingSectors = pThis->cbBacking / pThis->cbSector;
+ else
+ return rc;
+
+ /*
+ * Read the volume descriptors starting at logical sector 16.
+ */
+ union
+ {
+ uint8_t ab[RTFSISO_MAX_LOGICAL_BLOCK_SIZE];
+ uint16_t au16[RTFSISO_MAX_LOGICAL_BLOCK_SIZE / 2];
+ uint32_t au32[RTFSISO_MAX_LOGICAL_BLOCK_SIZE / 4];
+ ISO9660VOLDESCHDR VolDescHdr;
+ ISO9660BOOTRECORD BootRecord;
+ ISO9660PRIMARYVOLDESC PrimaryVolDesc;
+ ISO9660SUPVOLDESC SupVolDesc;
+ ISO9660VOLPARTDESC VolPartDesc;
+ } Buf;
+ RT_ZERO(Buf);
+
+ uint64_t offRootDirRec = UINT64_MAX;
+ ISO9660DIRREC RootDir;
+ RT_ZERO(RootDir);
+
+ uint64_t offJolietRootDirRec = UINT64_MAX;
+ uint8_t bJolietUcs2Level = 0;
+ ISO9660DIRREC JolietRootDir;
+ RT_ZERO(JolietRootDir);
+
+ uint8_t uUdfLevel = 0;
+ uint64_t offUdfBootVolDesc = UINT64_MAX;
+
+ uint32_t cPrimaryVolDescs = 0;
+ uint32_t cSupplementaryVolDescs = 0;
+ uint32_t cBootRecordVolDescs = 0;
+ uint32_t offVolDesc = 16 * cbSector;
+ enum
+ {
+ kStateStart = 0,
+ kStateNoSeq,
+ kStateCdSeq,
+ kStateUdfSeq
+ } enmState = kStateStart;
+ for (uint32_t iVolDesc = 0; ; iVolDesc++, offVolDesc += cbSector)
+ {
+ if (iVolDesc > 32)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "More than 32 volume descriptors, doesn't seem right...");
+
+ /* Read the next one and check the signature. */
+ rc = RTVfsFileReadAt(hVfsBacking, offVolDesc, &Buf, cbSector, NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "Unable to read volume descriptor #%u", iVolDesc);
+
+#define MATCH_STD_ID(a_achStdId1, a_szStdId2) \
+ ( (a_achStdId1)[0] == (a_szStdId2)[0] \
+ && (a_achStdId1)[1] == (a_szStdId2)[1] \
+ && (a_achStdId1)[2] == (a_szStdId2)[2] \
+ && (a_achStdId1)[3] == (a_szStdId2)[3] \
+ && (a_achStdId1)[4] == (a_szStdId2)[4] )
+#define MATCH_HDR(a_pStd, a_bType2, a_szStdId2, a_bVer2) \
+ ( MATCH_STD_ID((a_pStd)->achStdId, a_szStdId2) \
+ && (a_pStd)->bDescType == (a_bType2) \
+ && (a_pStd)->bDescVersion == (a_bVer2) )
+
+ /*
+ * ISO 9660 ("CD001").
+ */
+ if ( ( enmState == kStateStart
+ || enmState == kStateCdSeq
+ || enmState == kStateNoSeq)
+ && MATCH_STD_ID(Buf.VolDescHdr.achStdId, ISO9660VOLDESC_STD_ID) )
+ {
+ enmState = kStateCdSeq;
+
+ /* Do type specific handling. */
+ Log(("ISO9660: volume desc #%u: type=%#x\n", iVolDesc, Buf.VolDescHdr.bDescType));
+ if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_PRIMARY)
+ {
+ cPrimaryVolDescs++;
+ if (Buf.VolDescHdr.bDescVersion != ISO9660PRIMARYVOLDESC_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported primary volume descriptor version: %#x", Buf.VolDescHdr.bDescVersion);
+#ifdef LOG_ENABLED
+ rtFsIsoVolLogPrimarySupplementaryVolDesc(&Buf.SupVolDesc);
+#endif
+ if (cPrimaryVolDescs == 1)
+ rc = rtFsIsoVolHandlePrimaryVolDesc(pThis, &Buf.PrimaryVolDesc, offVolDesc, &RootDir, &offRootDirRec, pErrInfo);
+ else if (cPrimaryVolDescs == 2)
+ Log(("ISO9660: ignoring 2nd primary descriptor\n")); /* so we can read the w2k3 ifs kit */
+ else
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "More than one primary volume descriptor");
+ }
+ else if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_SUPPLEMENTARY)
+ {
+ cSupplementaryVolDescs++;
+ if (Buf.VolDescHdr.bDescVersion != ISO9660SUPVOLDESC_VERSION)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported supplemental volume descriptor version: %#x", Buf.VolDescHdr.bDescVersion);
+#ifdef LOG_ENABLED
+ rtFsIsoVolLogPrimarySupplementaryVolDesc(&Buf.SupVolDesc);
+#endif
+ rc = rtFsIsoVolHandleSupplementaryVolDesc(pThis, &Buf.SupVolDesc, offVolDesc, &bJolietUcs2Level, &JolietRootDir,
+ &offJolietRootDirRec, pErrInfo);
+ }
+ else if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_BOOT_RECORD)
+ {
+ cBootRecordVolDescs++;
+ }
+ else if (Buf.VolDescHdr.bDescType == ISO9660VOLDESC_TYPE_TERMINATOR)
+ {
+ if (!cPrimaryVolDescs)
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "No primary volume descriptor");
+ enmState = kStateNoSeq;
+ }
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unknown volume descriptor: %#x", Buf.VolDescHdr.bDescType);
+ }
+ /*
+ * UDF volume recognition sequence (VRS).
+ */
+ else if ( ( enmState == kStateNoSeq
+ || enmState == kStateStart)
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_BEGIN, UDF_EXT_VOL_DESC_VERSION) )
+ {
+ if (uUdfLevel == 0)
+ enmState = kStateUdfSeq;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Only one BEA01 sequence is supported");
+ }
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_NSR_02, UDF_EXT_VOL_DESC_VERSION) )
+ uUdfLevel = 2;
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_NSR_03, UDF_EXT_VOL_DESC_VERSION) )
+ uUdfLevel = 3;
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_BOOT, UDF_EXT_VOL_DESC_VERSION) )
+ {
+ if (offUdfBootVolDesc == UINT64_MAX)
+ offUdfBootVolDesc = iVolDesc * cbSector;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Only one BOOT2 descriptor is supported");
+ }
+ else if ( enmState == kStateUdfSeq
+ && MATCH_HDR(&Buf.VolDescHdr, UDF_EXT_VOL_DESC_TYPE, UDF_EXT_VOL_DESC_STD_ID_TERM, UDF_EXT_VOL_DESC_VERSION) )
+ {
+ if (uUdfLevel != 0)
+ enmState = kStateNoSeq;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Found BEA01 & TEA01, but no NSR02 or NSR03 descriptors");
+ }
+ /*
+ * Unknown, probably the end.
+ */
+ else if (enmState == kStateNoSeq)
+ break;
+ else if (enmState == kStateStart)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Not ISO? Unable to recognize volume descriptor signature: %.5Rhxs", Buf.VolDescHdr.achStdId);
+ else if (enmState == kStateCdSeq)
+ {
+#if 1
+ /* The warp server for ebusiness update ISOs known as ACP2 & MCP2 ends up here,
+ as they do in deed miss a terminator volume descriptor and we're now at the
+ root directory already. Just detect this, ignore it and get on with things. */
+ Log(("rtFsIsoVolTryInit: Ignoring missing ISO 9660 terminator volume descriptor (found %.5Rhxs).\n",
+ Buf.VolDescHdr.achStdId));
+ break;
+#else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Missing ISO 9660 terminator volume descriptor? (Found %.5Rhxs)", Buf.VolDescHdr.achStdId);
+#endif
+ }
+ else if (enmState == kStateUdfSeq)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Missing UDF terminator volume descriptor? (Found %.5Rhxs)", Buf.VolDescHdr.achStdId);
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Unknown volume descriptor signature found at sector %u: %.5Rhxs",
+ 16 + iVolDesc, Buf.VolDescHdr.achStdId);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * If we found a UDF VRS and are interested in UDF, we have more work to do here.
+ */
+ if (uUdfLevel > 0 && !(fFlags & RTFSISO9660_F_NO_UDF))
+ {
+ Log(("rtFsIsoVolTryInit: uUdfLevel=%d\n", uUdfLevel));
+ rc = rtFsIsoVolHandleUdfDetection(pThis, &uUdfLevel, offUdfBootVolDesc, Buf.ab, sizeof(Buf), pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Decide which to prefer.
+ *
+ * By default we pick UDF over any of the two ISO 9960, there is currently
+ * no way to override this without using the RTFSISO9660_F_NO_XXX options.
+ *
+ * If there isn't UDF, we may be faced with choosing between joliet and
+ * rock ridge. The joliet option is generally favorable as we don't have
+ * to guess wrt to the file name encoding. So, we'll pick that for now.
+ *
+ * Note! Should we change this preference for joliet, there fun wrt making sure
+ * there really is rock ridge stuff in the primary volume as well as
+ * making sure there really is anything of value in the primary volume.
+ */
+ if (uUdfLevel > 0)
+ {
+ pThis->enmType = RTFSISOVOLTYPE_UDF;
+ rc = rtFsIsoDirShrd_NewUdf(pThis, NULL /*pParent*/, &pThis->Udf.VolInfo.RootDirIcb,
+ NULL /*pFileIdDesc*/, 0 /*offInDir*/, &pThis->pRootDir);
+ /** @todo fall back on failure? */
+ return rc;
+ }
+ if (bJolietUcs2Level != 0)
+ {
+ pThis->enmType = RTFSISOVOLTYPE_JOLIET;
+ pThis->fIsUtf16 = true;
+ return rtFsIsoDirShrd_New9660(pThis, NULL, &JolietRootDir, 1, offJolietRootDirRec, NULL, &pThis->pRootDir);
+ }
+ pThis->enmType = RTFSISOVOLTYPE_ISO9960;
+ return rtFsIsoDirShrd_New9660(pThis, NULL, &RootDir, 1, offRootDirRec, NULL, &pThis->pRootDir);
+}
+
+
+/**
+ * Opens an ISO 9660 file system volume.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFileIn The file or device backing the volume.
+ * @param fFlags RTFSISO9660_F_XXX.
+ * @param phVfs Where to return the virtual file system handle.
+ * @param pErrInfo Where to return additional error information.
+ */
+RTDECL(int) RTFsIso9660VolOpen(RTVFSFILE hVfsFileIn, uint32_t fFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ /*
+ * Quick input validation.
+ */
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ *phVfs = NIL_RTVFS;
+ AssertReturn(!(fFlags & ~RTFSISO9660_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a new ISO VFS instance and try initialize it using the given input file.
+ */
+ RTVFS hVfs = NIL_RTVFS;
+ PRTFSISOVOL pThis = NULL;
+ int rc = RTVfsNew(&g_rtFsIsoVolOps, sizeof(RTFSISOVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsIsoVolTryInit(pThis, hVfs, hVfsFileIn, fFlags, pErrInfo);
+ if (RT_SUCCESS(rc))
+ *phVfs = hVfs;
+ else
+ RTVfsRelease(hVfs);
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainIsoFsVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
+ return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
+ if ( pElement->enmType != RTVFSOBJTYPE_VFS
+ && pElement->enmType != RTVFSOBJTYPE_DIR)
+ return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ uint32_t fFlags = 0;
+ if (pElement->cArgs > 0)
+ {
+ for (uint32_t iArg = 0; iArg < pElement->cArgs; iArg++)
+ {
+ const char *psz = pElement->paArgs[iArg].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "nojoliet"))
+ fFlags |= RTFSISO9660_F_NO_JOLIET;
+ else if (!strcmp(psz, "norock"))
+ fFlags |= RTFSISO9660_F_NO_ROCK;
+ else if (!strcmp(psz, "noudf"))
+ fFlags |= RTFSISO9660_F_NO_UDF;
+ else
+ {
+ *poffError = pElement->paArgs[iArg].offSpec;
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Only knows: 'nojoliet' and 'norock'");
+ }
+ }
+ }
+ }
+
+ pElement->uProvider = fFlags;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainIsoFsVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError);
+
+ int rc;
+ RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
+ if (hVfsFileIn != NIL_RTVFSFILE)
+ {
+ RTVFS hVfs;
+ rc = RTFsIso9660VolOpen(hVfsFileIn, pElement->uProvider, &hVfs, pErrInfo);
+ RTVfsFileRelease(hVfsFileIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromVfs(hVfs);
+ RTVfsRelease(hVfs);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainIsoFsVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider
+ || !pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'file'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainIsoFsVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "isofs",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a ISO 9660 or UDF file system, requires a file object on the left side.\n"
+ "The 'noudf' option make it ignore any UDF.\n"
+ "The 'nojoliet' option make it ignore any joliet supplemental volume.\n"
+ "The 'norock' option make it ignore any rock ridge info.\n",
+ /* pfnValidate = */ rtVfsChainIsoFsVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainIsoFsVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainIsoFsVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainIsoFsVolReg, rtVfsChainIsoFsVolReg);
+
diff --git a/src/VBox/Runtime/common/fs/ntfsvfs.cpp b/src/VBox/Runtime/common/fs/ntfsvfs.cpp
new file mode 100644
index 00000000..865ce3fb
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/ntfsvfs.cpp
@@ -0,0 +1,5698 @@
+/* $Id: ntfsvfs.cpp $ */
+/** @file
+ * IPRT - NTFS Virtual Filesystem, currently only for reading allocation bitmap.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include <iprt/fsvfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/utf16.h>
+#include <iprt/formats/ntfs.h>
+
+#include <internal/fs.h> /* For RTFSMODE_SYMLINK_REPARSE_TAG. */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The maximum bitmap size to try cache in its entirity (in bytes). */
+#define RTFSNTFS_MAX_WHOLE_BITMAP_CACHE _64K
+/** The maximum node cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSNTFS_MAX_CORE_CACHE_SIZE _512K
+#else
+# define RTFSNTFS_MAX_CORE_CACHE_SIZE _128K
+#endif
+/** The maximum node cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSNTFS_MAX_NODE_CACHE_SIZE _1M
+#else
+# define RTFSNTFS_MAX_NODE_CACHE_SIZE _256K
+#endif
+
+/** Makes a combined NTFS version value.
+ * @see RTFSNTFSVOL::uNtfsVersion */
+#define RTFSNTFS_MAKE_VERSION(a_uMajor, a_uMinor) RT_MAKE_U16(a_uMinor, a_uMajor)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the instance data for a NTFS volume. */
+typedef struct RTFSNTFSVOL *PRTFSNTFSVOL;
+/** Pointer to a NTFS MFT record. */
+typedef struct RTFSNTFSMFTREC *PRTFSNTFSMFTREC;
+/** Poitner to a NTFS core object record. */
+typedef struct RTFSNTFSCORE *PRTFSNTFSCORE;
+/** Pointer to an index node. */
+typedef struct RTFSNTFSIDXNODE *PRTFSNTFSIDXNODE;
+/** Pointer to a shared NTFS directory object. */
+typedef struct RTFSNTFSDIRSHRD *PRTFSNTFSDIRSHRD;
+/** Pointer to a shared NTFS file object. */
+typedef struct RTFSNTFSFILESHRD *PRTFSNTFSFILESHRD;
+
+
+/**
+ * NTFS disk allocation extent (internal representation).
+ */
+typedef struct RTFSNTFSEXTENT
+{
+ /** The disk or partition byte offset.
+ * This is set to UINT64_MAX for parts of sparse files that aren't recorded. */
+ uint64_t off;
+ /** The size of the extent in bytes. */
+ uint64_t cbExtent;
+} RTFSNTFSEXTENT;
+/** Pointer to an NTFS 9660 extent. */
+typedef RTFSNTFSEXTENT *PRTFSNTFSEXTENT;
+/** Pointer to a const NTFS 9660 extent. */
+typedef RTFSNTFSEXTENT const *PCRTFSNTFSEXTENT;
+
+/**
+ * An array of zero or more extents.
+ */
+typedef struct RTFSNTFSEXTENTS
+{
+ /** Number of bytes covered by the extents. */
+ uint64_t cbData;
+ /** Number of allocation extents. */
+ uint32_t cExtents;
+ /** Array of allocation extents. */
+ PRTFSNTFSEXTENT paExtents;
+} RTFSNTFSEXTENTS;
+/** Pointer to an extent array. */
+typedef RTFSNTFSEXTENTS *PRTFSNTFSEXTENTS;
+/** Pointer to a const extent array. */
+typedef RTFSNTFSEXTENTS const *PCRTFSNTFSEXTENTS;
+
+
+/**
+ * NTFS MFT record.
+ *
+ * These are kept in a tree to , so
+ */
+typedef struct RTFSNTFSMFTREC
+{
+ /** MFT record number (index) as key. */
+ AVLU64NODECORE TreeNode;
+ /** Pointer to the next MFT record if chained. Holds a reference. */
+ PRTFSNTFSMFTREC pNext;
+ union
+ {
+ /** Generic record pointer. RTFSNTFSVOL::cbMftRecord in size. */
+ uint8_t *pbRec;
+ /** Pointer to the file record. */
+ PNTFSRECFILE pFileRec;
+ } RT_UNION_NM(u);
+ /** Pointer to the core object with the parsed data.
+ * This is a weak reference. Non-base MFT record all point to the base one. */
+ PRTFSNTFSCORE pCore;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Set if this is a base MFT record. */
+ bool fIsBase;
+} RTFSNTFSMFTREC;
+
+
+/** Pointer to a attribute subrecord structure. */
+typedef struct RTFSNTFSATTRSUBREC *PRTFSNTFSATTRSUBREC;
+
+/**
+ * An attribute subrecord.
+ *
+ * This is for covering non-resident attributes that have had their allocation
+ * list split.
+ */
+typedef struct RTFSNTFSATTRSUBREC
+{
+ /** Pointer to the next one. */
+ PRTFSNTFSATTRSUBREC pNext;
+ /** Pointer to the attribute header.
+ * The MFT is held down by RTFSNTFSCORE via pMftEntry. */
+ PNTFSATTRIBHDR pAttrHdr;
+ /** Disk space allocation if non-resident. */
+ RTFSNTFSEXTENTS Extents;
+} RTFSNTFSATTRSUBREC;
+
+/**
+ * An attribute.
+ */
+typedef struct RTFSNTFSATTR
+{
+ /** List entry (head RTFSNTFSCORE::AttribHead). */
+ RTLISTNODE ListEntry;
+ /** Pointer to the core object this attribute belongs to. */
+ PRTFSNTFSCORE pCore;
+ /** Pointer to the attribute header.
+ * The MFT is held down by RTFSNTFSCORE via pMftEntry. */
+ PNTFSATTRIBHDR pAttrHdr;
+ /** The offset of the attribute header in the MFT record.
+ * This is needed to validate header relative offsets. */
+ uint32_t offAttrHdrInMftRec;
+ /** Number of resident bytes available (can be smaller than cbValue).
+ * Set to zero for non-resident attributes. */
+ uint32_t cbResident;
+ /** The (uncompressed) attribute size. */
+ uint64_t cbValue;
+ /** Disk space allocation if non-resident. */
+ RTFSNTFSEXTENTS Extents;
+ /** Pointer to any subrecords containing further allocation extents. */
+ PRTFSNTFSATTRSUBREC pSubRecHead;
+ /** Pointer to the VFS object for this attribute.
+ * This is a weak reference since it's the VFS object that is referencing us. */
+ union
+ {
+ /** Pointer to a shared directory (NTFS_AT_DIRECTORY). */
+ PRTFSNTFSDIRSHRD pSharedDir;
+ /** Pointer to a shared file (NTFS_AT_DATA). */
+ PRTFSNTFSFILESHRD pSharedFile;
+ } uObj;
+} RTFSNTFSATTR;
+/** Pointer to a attribute structure. */
+typedef RTFSNTFSATTR *PRTFSNTFSATTR;
+
+
+/**
+ * NTFS file system object, shared part.
+ */
+typedef struct RTFSNTFSCORE
+{
+ /** Entry in either the RTFSNTFSVOL::CoreInUseHead or CoreUnusedHead.
+ * Instances is moved to/from CoreUnusedHead as cRefs reaches zero and one
+ * respectively. */
+ RTLISTNODE ListEntry;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The estimated memory cost of this object. */
+ uint32_t cbCost;
+ /** Pointer to the volume. */
+ PRTFSNTFSVOL pVol;
+ /** Pointer to the head of the MFT record chain for this object.
+ * Holds a reference. */
+ PRTFSNTFSMFTREC pMftRec;
+ /** List of attributes (RTFSNTFSATTR). */
+ RTLISTANCHOR AttribHead;
+} RTFSNTFSCORE;
+
+
+/**
+ * Node lookup information for facilitating binary searching of node.
+ */
+typedef struct RTFSNTFSIDXNODEINFO
+{
+ /** The index header. */
+ PCNTFSINDEXHDR pIndexHdr;
+ /** Number of entries. */
+ uint32_t cEntries;
+ /** Set if internal node. */
+ bool fInternal;
+ /** Array with pointers to the entries. */
+ PCNTFSIDXENTRYHDR *papEntries;
+ /** Pointer to the index node this info is for, NULL if root node.
+ * This is for reducing the enumeration stack entry size. */
+ PRTFSNTFSIDXNODE pNode;
+ /** Pointer to the NTFS volume instace. */
+ PRTFSNTFSVOL pVol;
+} RTFSNTFSIDXNODEINFO;
+/** Pointer to index node lookup info. */
+typedef RTFSNTFSIDXNODEINFO *PRTFSNTFSIDXNODEINFO;
+/** Pointer to const index node lookup info. */
+typedef RTFSNTFSIDXNODEINFO const *PCRTFSNTFSIDXNODEINFO;
+
+/**
+ * Index node, cached.
+ *
+ * These are cached to avoid reading, validating and parsing things each time a
+ * subnode is accessed.
+ */
+typedef struct RTFSNTFSIDXNODE
+{
+ /** Entry in RTFSNTFSVOL::IdxNodeCahceRoot, key is disk byte offset. */
+ AVLU64NODECORE TreeNode;
+ /** List entry on the unused list. Gets removed from it when cRefs is
+ * increase to one, and added when it reaches zero. */
+ RTLISTNODE UnusedListEntry;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The estimated memory cost of this node. */
+ uint32_t cbCost;
+ /** Pointer to the node data. */
+ PNTFSATINDEXALLOC pNode;
+ /** Node info. */
+ RTFSNTFSIDXNODEINFO NodeInfo;
+} RTFSNTFSIDXNODE;
+
+/**
+ * Common index root structure.
+ */
+typedef struct RTFSNTFSIDXROOTINFO
+{
+ /** Pointer to the index root attribute value. */
+ PCNTFSATINDEXROOT pRoot;
+ /** Pointer to the index allocation attribute, if present.
+ * This and the bitmap may be absent if the whole directory fits into the
+ * root index. */
+ PRTFSNTFSATTR pAlloc;
+ /** End of the node addresses range (exclusive). */
+ uint64_t uEndNodeAddresses;
+ /** Node address misalignement mask. */
+ uint32_t fNodeAddressMisalign;
+ /** The byte shift count for node addresses. */
+ uint8_t cNodeAddressByteShift;
+ /** Node info for the root. */
+ RTFSNTFSIDXNODEINFO NodeInfo;
+ /** Pointer to the index root attribute. We reference the core thru this and
+ * use it to zero RTFSNTFSATTR::uObj::pSharedDir on destruction. */
+ PRTFSNTFSATTR pRootAttr;
+} RTFSNTFSIDXROOTINFO;
+/** Pointer to an index root structure. */
+typedef RTFSNTFSIDXROOTINFO *PRTFSNTFSIDXROOTINFO;
+/** Pointer to a const index root structure. */
+typedef RTFSNTFSIDXROOTINFO const *PCRTFSNTFSIDXROOTINFO;
+
+/**
+ * Shared NTFS directory object.
+ */
+typedef struct RTFSNTFSDIRSHRD
+{
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Index root information. */
+ RTFSNTFSIDXROOTINFO RootInfo;
+} RTFSNTFSDIRSHRD;
+
+/**
+ * Index stack entry for index enumeration.
+ */
+typedef struct RTFSNTFSIDXSTACKENTRY
+{
+ /** The next entry to process in this stack entry. */
+ uint32_t iNext;
+ /** Set if we need to descend first. */
+ bool fDescend;
+ /** Pointer to the node info for this entry. */
+ PRTFSNTFSIDXNODEINFO pNodeInfo;
+} RTFSNTFSIDXSTACKENTRY;
+/** Pointer to an index enumeration stack entry. */
+typedef RTFSNTFSIDXSTACKENTRY *PRTFSNTFSIDXSTACKENTRY;
+
+
+/**
+ * Open directory instance.
+ */
+typedef struct RTFSNTFSDIR
+{
+ /** Pointer to the shared directory instance (referenced). */
+ PRTFSNTFSDIRSHRD pShared;
+ /** Set if we've reached the end of the directory enumeration. */
+ bool fNoMoreFiles;
+ /** The enumeration stack size. */
+ uint32_t cEnumStackEntries;
+ /** The allocated enumeration stack depth. */
+ uint32_t cEnumStackMaxDepth;
+ /** The numeration stack. Allocated as needed. */
+ PRTFSNTFSIDXSTACKENTRY paEnumStack;
+} RTFSNTFSDIR;
+/** Pointer to an open directory instance. */
+typedef RTFSNTFSDIR *PRTFSNTFSDIR;
+
+
+/**
+ * Shared NTFS file object.
+ */
+typedef struct RTFSNTFSFILESHRD
+{
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** Pointer to the data attribute (core is referenced thru this). */
+ PRTFSNTFSATTR pData;
+} RTFSNTFSFILESHRD;
+/** Pointer to shared data for a file or data stream. */
+typedef RTFSNTFSFILESHRD *PRTFSNTFSFILESHRD;
+
+
+/**
+ * Open NTFS file instance.
+ */
+typedef struct RTFSNTFSFILE
+{
+ /** Pointer to the shared file data (referenced). */
+ PRTFSNTFSFILESHRD pShared;
+ /** Current file offset. */
+ uint64_t offFile;
+} RTFSNTFSFILE;
+/** Pointer to an NTFS open file instance. */
+typedef RTFSNTFSFILE *PRTFSNTFSFILE;
+
+/**
+ * Instance data for an NTFS volume.
+ */
+typedef struct RTFSNTFSVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the NTFS volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+ /** The formatted size of the volume. */
+ uint64_t cbVolume;
+ /** cbVolume expressed as a cluster count. */
+ uint64_t cClusters;
+
+ /** RTVFSMNT_F_XXX. */
+ uint32_t fMntFlags;
+ /** RTFSNTVFS_F_XXX (currently none defined). */
+ uint32_t fNtfsFlags;
+
+ /** The (logical) sector size. */
+ uint32_t cbSector;
+
+ /** The (logical) cluster size. */
+ uint32_t cbCluster;
+ /** Max cluster count value that won't overflow a signed 64-bit when
+ * converted to bytes. Inclusive. */
+ uint64_t iMaxVirtualCluster;
+ /** The shift count for converting between bytes and clusters. */
+ uint8_t cClusterShift;
+
+ /** Explicit padding. */
+ uint8_t abReserved[3];
+ /** The NTFS version of the volume (RTFSNTFS_MAKE_VERSION). */
+ uint16_t uNtfsVersion;
+ /** The NTFS_VOLUME_F_XXX. */
+ uint16_t fVolumeFlags;
+
+ /** The logical cluster number of the MFT. */
+ uint64_t uLcnMft;
+ /** The logical cluster number of the mirror MFT. */
+ uint64_t uLcnMftMirror;
+
+ /** The MFT record size. */
+ uint32_t cbMftRecord;
+ /** The default index (B-tree) node size. */
+ uint32_t cbDefaultIndexNode;
+
+ /** The volume serial number. */
+ uint64_t uSerialNo;
+
+ /** @name MFT record and core object cache.
+ * @{ */
+ /** The '$Mft' data attribute. */
+ PRTFSNTFSATTR pMftData;
+ /** Root of the MFT record tree (RTFSNTFSMFTREC). */
+ AVLU64TREE MftRoot;
+ /** List of in use core objects (RTFSNTFSCORE::cRefs > 0). (RTFSNTFSCORE) */
+ RTLISTANCHOR CoreInUseHead;
+ /** List of unused core objects (RTFSNTFSCORE::cRefs == 0). (RTFSNTFSCORE)
+ * The most recently used nodes are found at the of the list. So, when
+ * cbCoreObjects gets to high, we remove and destroy objects from the tail. */
+ RTLISTANCHOR CoreUnusedHead;
+ /** Total core object memory cost (sum of all RTFSNTFSCORE::cbCost). */
+ size_t cbCoreObjects;
+ /** @} */
+
+ /** @name Allocation bitmap and cache.
+ * @{ */
+ /** The '$Bitmap' data attribute. */
+ PRTFSNTFSATTR pMftBitmap;
+ /** The first cluster currently loaded into the bitmap cache . */
+ uint64_t iFirstBitmapCluster;
+ /** The number of clusters currently loaded into the bitmap cache */
+ uint32_t cBitmapClusters;
+ /** The size of the pvBitmap allocation. */
+ uint32_t cbBitmapAlloc;
+ /** Allocation bitmap cache buffer. */
+ void *pvBitmap;
+ /** @} */
+
+ /** @name Directory/index related.
+ * @{ */
+ /** Tree of index nodes, index by disk byte offset. (RTFSNTFSIDXNODE) */
+ AVLU64TREE IdxNodeCacheRoot;
+ /** List of currently unreferenced index nodes. (RTFSNTFSIDXNODE)
+ * Most recently used nodes are found at the end of the list. Nodes are added
+ * when their reference counter reaches zero. They are removed when it
+ * increases to one again.
+ *
+ * The nodes are still in the index node cache tree (IdxNodeCacheRoot), but
+ * we'll trim this from the end when we reach a certain size. */
+ RTLISTANCHOR IdxNodeUnusedHead;
+ /** Number of unreferenced index nodes. */
+ uint32_t cUnusedIdxNodes;
+ /** Number of cached index nodes. */
+ uint32_t cIdxNodes;
+ /** Total index node memory cost. */
+ size_t cbIdxNodes;
+ /** The root directory. */
+ PRTFSNTFSDIRSHRD pRootDir;
+ /** Lower to uppercase conversion table for this filesystem.
+ * This always has 64K valid entries. */
+ PRTUTF16 pawcUpcase;
+ /** @} */
+
+} RTFSNTFSVOL;
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static uint32_t rtFsNtfsCore_Destroy(PRTFSNTFSCORE pThis);
+static void rtFsNtfsIdxVol_TrimCoreObjectCache(PRTFSNTFSVOL pThis);
+static uint32_t rtFsNtfsCore_Release(PRTFSNTFSCORE pThis);
+static uint32_t rtFsNtfsCore_Retain(PRTFSNTFSCORE pThis);
+#ifdef LOG_ENABLED
+static void rtFsNtfsVol_LogIndexRoot(PCNTFSATINDEXROOT pIdxRoot, uint32_t cbIdxRoot);
+#endif
+static int rtFsNtfsVol_NewDirFromShared(PRTFSNTFSVOL pThis, PRTFSNTFSDIRSHRD pSharedDir, PRTVFSDIR phVfsDir);
+static uint32_t rtFsNtfsDirShrd_Retain(PRTFSNTFSDIRSHRD pThis);
+static void rtFsNtfsIdxVol_TrimIndexNodeCache(PRTFSNTFSVOL pThis);
+static uint32_t rtFsNtfsIdxNode_Release(PRTFSNTFSIDXNODE pNode);
+static uint32_t rtFsNtfsIdxNode_Retain(PRTFSNTFSIDXNODE pNode);
+
+
+
+/**
+ * Checks if a bit is set in an NTFS bitmap (little endian).
+ *
+ * @returns true if set, false if not.
+ * @param pvBitmap The bitmap buffer.
+ * @param iBit The bit.
+ */
+DECLINLINE(bool) rtFsNtfsBitmap_IsSet(void *pvBitmap, uint32_t iBit)
+{
+#if 0 //def RT_LITTLE_ENDIAN
+ return ASMBitTest(pvBitmap, iBit);
+#else
+ uint8_t b = ((uint8_t const *)pvBitmap)[iBit >> 3];
+ return RT_BOOL(b & (1 << (iBit & 7)));
+#endif
+}
+
+
+
+static PRTFSNTFSMFTREC rtFsNtfsVol_NewMftRec(PRTFSNTFSVOL pVol, uint64_t idMft)
+{
+ PRTFSNTFSMFTREC pRec = (PRTFSNTFSMFTREC)RTMemAllocZ(sizeof(*pRec));
+ if (pRec)
+ {
+ pRec->pbRec = (uint8_t *)RTMemAllocZ(pVol->cbMftRecord);
+ if (pRec->pbRec)
+ {
+ pRec->TreeNode.Key = idMft;
+ pRec->pNext = NULL;
+ pRec->cRefs = 1;
+ if (RTAvlU64Insert(&pVol->MftRoot, &pRec->TreeNode))
+ return pRec;
+ RTMemFree(pRec->pbRec);
+ }
+
+ RTMemFree(pRec);
+ }
+ return NULL;
+}
+
+
+static uint32_t rtFsNtfsMftRec_Destroy(PRTFSNTFSMFTREC pThis, PRTFSNTFSVOL pVol)
+{
+ RTMemFree(pThis->pbRec);
+ pThis->pbRec = NULL;
+
+ PAVLU64NODECORE pRemoved = RTAvlU64Remove(&pVol->MftRoot, pThis->TreeNode.Key);
+ Assert(pRemoved == &pThis->TreeNode); NOREF(pRemoved);
+
+ RTMemFree(pThis);
+
+ return 0;
+}
+
+
+static uint32_t rtFsNtfsMftRec_Retain(PRTFSNTFSMFTREC pThis)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs < 64);
+ return cRefs;
+}
+
+static uint32_t rtFsNtfsMftRec_Release(PRTFSNTFSMFTREC pThis, PRTFSNTFSVOL pVol)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 64);
+ if (cRefs != 0)
+ return cRefs;
+ return rtFsNtfsMftRec_Destroy(pThis, pVol);
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Logs the MFT record
+ *
+ * @param pRec The MFT record to log.
+ * @param cbMftRecord MFT record size (from RTFSNTFSVOL).
+ */
+static void rtfsNtfsMftRec_Log(PRTFSNTFSMFTREC pRec, uint32_t cbMftRecord)
+{
+ if (LogIs2Enabled())
+ {
+ PCNTFSRECFILE pFileRec = pRec->pFileRec;
+ Log2(("NTFS: MFT #%#RX64\n", pRec->TreeNode.Key));
+ if (pFileRec->Hdr.uMagic == NTFSREC_MAGIC_FILE)
+ {
+ size_t const cbRec = cbMftRecord;
+ uint8_t const * const pbRec = pRec->pbRec;
+
+ Log2(("NTFS: FILE record: \n"));
+ Log2(("NTFS: UpdateSeqArray %#x L %#x\n", RT_LE2H_U16(pFileRec->Hdr.offUpdateSeqArray), RT_LE2H_U16(pFileRec->Hdr.cUpdateSeqEntries) ));
+ Log2(("NTFS: uLsn %#RX64\n", RT_LE2H_U64(pFileRec->uLsn)));
+ Log2(("NTFS: uRecReuseSeqNo %#RX16\n", RT_LE2H_U16(pFileRec->uRecReuseSeqNo)));
+ Log2(("NTFS: cLinks %#RX16\n", RT_LE2H_U16(pFileRec->cLinks)));
+ Log2(("NTFS: offFirstAttrib %#RX16\n", RT_LE2H_U16(pFileRec->offFirstAttrib)));
+ Log2(("NTFS: fFlags %#RX16%s%s\n", RT_LE2H_U16(pFileRec->fFlags),
+ RT_LE2H_U16(pFileRec->fFlags) & NTFSRECFILE_F_IN_USE ? " in-use" : "",
+ RT_LE2H_U16(pFileRec->fFlags) & NTFSRECFILE_F_DIRECTORY ? " directory" : ""));
+ Log2(("NTFS: cbRecUsed %#RX32\n", RT_LE2H_U32(pFileRec->cbRecUsed)));
+ Log2(("NTFS: BaseMftRec %#RX64, sqn %#x\n",
+ NTFSMFTREF_GET_IDX(&pFileRec->BaseMftRec), NTFSMFTREF_GET_SEQ(&pFileRec->BaseMftRec)));
+ Log2(("NTFS: idNextAttrib %#RX16\n", RT_LE2H_U16(pFileRec->idNextAttrib)));
+ if ( RT_LE2H_U16(pFileRec->offFirstAttrib) >= sizeof(*pFileRec)
+ && ( RT_LE2H_U16(pFileRec->Hdr.offUpdateSeqArray) >= sizeof(*pFileRec)
+ || pFileRec->Hdr.offUpdateSeqArray == 0))
+ {
+ Log2(("NTFS: uPaddingOrUsa %#RX16\n", pFileRec->uPaddingOrUsa));
+ Log2(("NTFS: idxMftSelf %#RX32\n", RT_LE2H_U32(pFileRec->idxMftSelf)));
+ }
+
+ uint32_t offRec = pFileRec->offFirstAttrib;
+ size_t cbRecUsed = RT_MIN(cbRec, pFileRec->cbRecUsed);
+ while (offRec + NTFSATTRIBHDR_SIZE_RESIDENT <= cbRecUsed)
+ {
+ PCNTFSATTRIBHDR pHdr = (PCNTFSATTRIBHDR)&pbRec[offRec];
+ uint32_t const cbAttrib = RT_LE2H_U32(pHdr->cbAttrib);
+ Log2(("NTFS: @%#05x: Attrib record: %#x LB %#x, instance #%#x, fFlags=%#RX16, %s\n", offRec,
+ RT_LE2H_U32(pHdr->uAttrType), cbAttrib, RT_LE2H_U16(pHdr->idAttrib), RT_LE2H_U16(pHdr->fFlags),
+ pHdr->fNonResident == 0 ? "resident" : pHdr->fNonResident == 1 ? "non-resident" : "bad-resident-flag"));
+ if (pHdr->offName && pHdr->cwcName)
+ {
+ if (offRec + RT_LE2H_U16(pHdr->offName) + pHdr->cwcName * sizeof(RTUTF16) <= cbRec)
+ Log2(("NTFS: Name %.*ls\n", pHdr->cwcName,&pbRec[offRec + RT_LE2H_U16(pHdr->offName)]));
+ else
+ Log2(("NTFS: Name <!out of bounds!> %#x L %#x\n", RT_LE2H_U16(pHdr->offName), pHdr->cwcName));
+ }
+ switch (pHdr->uAttrType)
+ {
+ case NTFS_AT_UNUSED: Log2(("NTFS: Type: UNUSED\n")); break;
+ case NTFS_AT_STANDARD_INFORMATION: Log2(("NTFS: Type: STANDARD_INFORMATION\n")); break;
+ case NTFS_AT_ATTRIBUTE_LIST: Log2(("NTFS: Type: ATTRIBUTE_LIST\n")); break;
+ case NTFS_AT_FILENAME: Log2(("NTFS: Type: FILENAME\n")); break;
+ case NTFS_AT_OBJECT_ID: Log2(("NTFS: Type: OBJECT_ID\n")); break;
+ case NTFS_AT_SECURITY_DESCRIPTOR: Log2(("NTFS: Type: SECURITY_DESCRIPTOR\n")); break;
+ case NTFS_AT_VOLUME_NAME: Log2(("NTFS: Type: VOLUME_NAME\n")); break;
+ case NTFS_AT_VOLUME_INFORMATION: Log2(("NTFS: Type: VOLUME_INFORMATION\n")); break;
+ case NTFS_AT_DATA: Log2(("NTFS: Type: DATA\n")); break;
+ case NTFS_AT_INDEX_ROOT: Log2(("NTFS: Type: INDEX_ROOT\n")); break;
+ case NTFS_AT_INDEX_ALLOCATION: Log2(("NTFS: Type: INDEX_ALLOCATION\n")); break;
+ case NTFS_AT_BITMAP: Log2(("NTFS: Type: BITMAP\n")); break;
+ case NTFS_AT_REPARSE_POINT: Log2(("NTFS: Type: REPARSE_POINT\n")); break;
+ case NTFS_AT_EA_INFORMATION: Log2(("NTFS: Type: EA_INFORMATION\n")); break;
+ case NTFS_AT_EA: Log2(("NTFS: Type: EA\n")); break;
+ case NTFS_AT_PROPERTY_SET: Log2(("NTFS: Type: PROPERTY_SET\n")); break;
+ case NTFS_AT_LOGGED_UTILITY_STREAM: Log2(("NTFS: Type: LOGGED_UTILITY_STREAM\n")); break;
+ default:
+ if (RT_LE2H_U32(pHdr->uAttrType) >= RT_LE2H_U32_C(NTFS_AT_FIRST_USER_DEFINED))
+ Log2(("NTFS: Type: unknown user defined - %#x!\n", RT_LE2H_U32(pHdr->uAttrType)));
+ else
+ Log2(("NTFS: Type: unknown - %#x!\n", RT_LE2H_U32(pHdr->uAttrType)));
+ break;
+ }
+
+ size_t const cbMaxAttrib = cbRec - offRec;
+ if (!pHdr->fNonResident)
+ {
+ uint16_t const offValue = RT_LE2H_U16(pHdr->u.Res.offValue);
+ uint32_t const cbValue = RT_LE2H_U32(pHdr->u.Res.cbValue);
+ Log2(("NTFS: Value: %#x LB %#x, fFlags=%#x bReserved=%#x\n",
+ offValue, cbValue, pHdr->u.Res.fFlags, pHdr->u.Res.bReserved));
+ if ( offValue < cbMaxAttrib
+ && cbValue < cbMaxAttrib
+ && offValue + cbValue <= cbMaxAttrib)
+ {
+ uint8_t const *pbValue = &pbRec[offRec + offValue];
+ RTTIMESPEC Spec;
+ char sz[80];
+ switch (pHdr->uAttrType)
+ {
+ case NTFS_AT_STANDARD_INFORMATION:
+ {
+ PCNTFSATSTDINFO pInfo = (PCNTFSATSTDINFO)pbValue;
+ if (cbValue >= NTFSATSTDINFO_SIZE_NTFS_V12)
+ {
+ Log2(("NTFS: iCreationTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iCreationTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iCreationTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastDataModTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastDataModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastDataModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastMftModTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastMftModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastMftModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastAccessTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastAccessTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastAccessTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: fFileAttribs %#RX32\n", RT_LE2H_U32(pInfo->fFileAttribs) ));
+ Log2(("NTFS: cMaxFileVersions %#RX32\n", RT_LE2H_U32(pInfo->cMaxFileVersions) ));
+ Log2(("NTFS: uFileVersion %#RX32\n", RT_LE2H_U32(pInfo->uFileVersion) ));
+ }
+ else
+ Log2(("NTFS: Error! cbValue=%#x is smaller than expected (%#x) for NTFSATSTDINFO!\n",
+ cbValue, NTFSATSTDINFO_SIZE_NTFS_V12));
+ if (cbValue >= sizeof(*pInfo))
+ {
+ Log2(("NTFS: idClass %#RX32\n", RT_LE2H_U32(pInfo->idClass) ));
+ Log2(("NTFS: idOwner %#RX32\n", RT_LE2H_U32(pInfo->idOwner) ));
+ Log2(("NTFS: idSecurity %#RX32\n", RT_LE2H_U32(pInfo->idSecurity) ));
+ Log2(("NTFS: cbQuotaChared %#RX64\n", RT_LE2H_U64(pInfo->cbQuotaChared) ));
+ Log2(("NTFS: idxUpdateSequence %#RX64\n", RT_LE2H_U64(pInfo->idxUpdateSequence) ));
+ }
+ if (cbValue > sizeof(*pInfo))
+ Log2(("NTFS: Undefined data: %.*Rhxs\n", cbValue - sizeof(*pInfo), &pbValue[sizeof(*pInfo)]));
+ break;
+ }
+
+ case NTFS_AT_ATTRIBUTE_LIST:
+ {
+ uint32_t iEntry = 0;
+ uint32_t offEntry = 0;
+ while (offEntry + NTFSATLISTENTRY_SIZE_MINIMAL < cbValue)
+ {
+ PCNTFSATLISTENTRY pInfo = (PCNTFSATLISTENTRY)&pbValue[offEntry];
+ Log2(("NTFS: attr[%u]: %#x in %#RX64 (sqn %#x), instance %#x, VNC=%#RX64-, name %#x L %#x\n",
+ iEntry, RT_LE2H_U32(pInfo->uAttrType), NTFSMFTREF_GET_IDX(&pInfo->InMftRec),
+ NTFSMFTREF_GET_SEQ(&pInfo->InMftRec), RT_LE2H_U16(pInfo->idAttrib),
+ RT_LE2H_U64(pInfo->iVcnFirst), pInfo->offName, pInfo->cwcName));
+ if ( pInfo->cwcName > 0
+ && pInfo->offName < pInfo->cbEntry)
+ Log2(("NTFS: name '%.*ls'\n", pInfo->cwcName, (uint8_t *)pInfo + pInfo->offName));
+
+ /* next */
+ if (pInfo->cbEntry < NTFSATLISTENTRY_SIZE_MINIMAL)
+ {
+ Log2(("NTFS: cbEntry is too small! cbEntry=%#x, min %#x\n",
+ pInfo->cbEntry, NTFSATLISTENTRY_SIZE_MINIMAL));
+ break;
+ }
+ iEntry++;
+ offEntry += RT_ALIGN_32(pInfo->cbEntry, 8);
+ }
+ break;
+ }
+
+ case NTFS_AT_FILENAME:
+ {
+ PCNTFSATFILENAME pInfo = (PCNTFSATFILENAME)pbValue;
+ if (cbValue >= RT_UOFFSETOF(NTFSATFILENAME, wszFilename))
+ {
+ Log2(("NTFS: ParentDirMftRec %#RX64, sqn %#x\n",
+ NTFSMFTREF_GET_IDX(&pInfo->ParentDirMftRec), NTFSMFTREF_GET_SEQ(&pInfo->ParentDirMftRec) ));
+ Log2(("NTFS: iCreationTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iCreationTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iCreationTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastDataModTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastDataModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastDataModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastMftModTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastMftModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastMftModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastAccessTime %#RX64 %s\n", RT_LE2H_U64(pInfo->iLastAccessTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pInfo->iLastAccessTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: cbAllocated %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pInfo->cbAllocated), RT_LE2H_U64(pInfo->cbAllocated)));
+ Log2(("NTFS: cbData %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pInfo->cbData), RT_LE2H_U64(pInfo->cbData)));
+ Log2(("NTFS: fFileAttribs %#RX32\n", RT_LE2H_U32(pInfo->fFileAttribs) ));
+ if (RT_LE2H_U32(pInfo->fFileAttribs) & NTFS_FA_REPARSE_POINT)
+ Log2(("NTFS: uReparseTag %#RX32\n", RT_LE2H_U32(pInfo->u.uReparseTag) ));
+ else
+ Log2(("NTFS: cbPackedEas %#RX16\n", RT_LE2H_U16(pInfo->u.cbPackedEas) ));
+ Log2(("NTFS: cwcFilename %#x\n", pInfo->cwcFilename));
+ Log2(("NTFS: fFilenameType %#x\n", pInfo->fFilenameType));
+ if (RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pInfo->cwcFilename]) <= cbValue)
+ Log2(("NTFS: wszFilename '%.*ls'\n", pInfo->cwcFilename, pInfo->wszFilename ));
+ else
+ Log2(("NTFS: Error! Truncated filename!!\n"));
+ }
+ else
+ Log2(("NTFS: Error! cbValue=%#x is smaller than expected (%#zx) for NTFSATFILENAME!\n",
+ cbValue, RT_UOFFSETOF(NTFSATFILENAME, wszFilename) ));
+ break;
+ }
+
+ //case NTFS_AT_OBJECT_ID:
+ //case NTFS_AT_SECURITY_DESCRIPTOR:
+ //case NTFS_AT_VOLUME_NAME:
+ //case NTFS_AT_VOLUME_INFORMATION:
+ //case NTFS_AT_DATA:
+
+ case NTFS_AT_INDEX_ROOT:
+ rtFsNtfsVol_LogIndexRoot((PCNTFSATINDEXROOT)pbValue, cbValue);
+ break;
+
+ //case NTFS_AT_INDEX_ALLOCATION:
+ //case NTFS_AT_BITMAP:
+ //case NTFS_AT_REPARSE_POINT:
+ //case NTFS_AT_EA_INFORMATION:
+ //case NTFS_AT_EA:
+ //case NTFS_AT_PROPERTY_SET:
+ //case NTFS_AT_LOGGED_UTILITY_STREAM:
+
+ default:
+ if (cbValue <= 24)
+ Log2(("NTFS: %.*Rhxs\n", cbValue, pbValue));
+ else
+ Log2(("%.*Rhxd\n", cbValue, pbValue));
+ break;
+ }
+
+ }
+ else
+ Log2(("NTFS: !Value is out of bounds!\n"));
+ }
+ else if (RT_MAX(cbAttrib, NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED) <= cbMaxAttrib)
+ {
+ Log2(("NTFS: VNC range %#RX64 .. %#RX64 (%#RX64 clusters)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.iVcnFirst), RT_LE2H_U64(pHdr->u.NonRes.iVcnLast),
+ RT_LE2H_U64(pHdr->u.NonRes.iVcnLast) - RT_LE2H_U64(pHdr->u.NonRes.iVcnFirst) + 1));
+ Log2(("NTFS: cbAllocated %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.cbAllocated), RT_LE2H_U64(pHdr->u.NonRes.cbAllocated)));
+ Log2(("NTFS: cbData %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.cbData), RT_LE2H_U64(pHdr->u.NonRes.cbData)));
+ Log2(("NTFS: cbInitialized %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.cbInitialized), RT_LE2H_U64(pHdr->u.NonRes.cbInitialized)));
+ uint16_t const offMappingPairs = RT_LE2H_U16(pHdr->u.NonRes.offMappingPairs);
+ Log2(("NTFS: offMappingPairs %#RX16\n", offMappingPairs));
+ if ( pHdr->u.NonRes.abReserved[0] || pHdr->u.NonRes.abReserved[1]
+ || pHdr->u.NonRes.abReserved[2] || pHdr->u.NonRes.abReserved[3] || pHdr->u.NonRes.abReserved[4] )
+ Log2(("NTFS: abReserved %.7Rhxs\n", pHdr->u.NonRes.abReserved));
+ if (pHdr->u.NonRes.uCompressionUnit != 0)
+ Log2(("NTFS: Compression unit 2^%u clusters\n", pHdr->u.NonRes.uCompressionUnit));
+
+ if ( cbMaxAttrib >= NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED
+ && cbAttrib >= NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED
+ && ( offMappingPairs >= NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED
+ || offMappingPairs < NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED))
+ Log2(("NTFS: cbCompressed %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pHdr->u.NonRes.cbCompressed), RT_LE2H_U64(pHdr->u.NonRes.cbCompressed)));
+ else if ( pHdr->u.NonRes.uCompressionUnit != 0
+ && pHdr->u.NonRes.uCompressionUnit != 64
+ && pHdr->u.NonRes.iVcnFirst == 0)
+ Log2(("NTFS: !Error! Compressed attrib fields are out of bound!\n"));
+
+ if ( offMappingPairs < cbAttrib
+ && offMappingPairs >= NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED)
+ {
+ uint8_t const *pbPairs = &pbRec[offRec + offMappingPairs];
+ uint32_t const cbMaxPairs = cbAttrib - offMappingPairs;
+ int64_t iVnc = pHdr->u.NonRes.iVcnFirst;
+ if (cbMaxPairs < 48)
+ Log2(("NTFS: Mapping Pairs: cbMaxPairs=%#x %.*Rhxs\n", cbMaxPairs, cbMaxPairs, pbPairs));
+ else
+ Log2(("NTFS: Mapping Pairs: cbMaxPairs=%#x\n%.*Rhxd\n", cbMaxPairs, cbMaxPairs, pbPairs));
+ if (!iVnc && !*pbPairs)
+ Log2(("NTFS: [0]: Empty\n"));
+ else
+ {
+ if (iVnc != 0)
+ Log2(("NTFS: [00/0x000]: VCN=%#012RX64 L %#012RX64 - not mapped\n", 0, iVnc));
+ int64_t iLnc = 0;
+ uint32_t iPair = 0;
+ uint32_t offPairs = 0;
+ while (offPairs < cbMaxPairs)
+ {
+ /* First byte: 4-bit length of each of the pair values */
+ uint8_t const bLengths = pbPairs[offPairs];
+ if (!bLengths)
+ break;
+ uint8_t const cbRun = (bLengths & 0x0f) + (bLengths >> 4);
+ if (offPairs + cbRun > cbMaxPairs)
+ {
+ Log2(("NTFS: [%02d/%#05x]: run overrun! cbRun=%#x bLengths=%#x offPairs=%#x cbMaxPairs=%#x\n",
+ iPair, offPairs, cbRun, bLengths, offPairs, cbMaxPairs));
+ break;
+ }
+ //Log2(("NTFS: @%#05x: %.*Rhxs\n", offPairs, cbRun + 1, &pbPairs[offPairs]));
+
+ /* Value 1: Number of (virtual) clusters in this run. */
+ int64_t cClustersInRun;
+ uint8_t cbNum = (bLengths & 0xf);
+ if (cbNum)
+ {
+ uint8_t const *pbNum = &pbPairs[offPairs + cbNum]; /* last byte */
+ cClustersInRun = (int8_t)*pbNum--;
+ while (cbNum-- > 1)
+ cClustersInRun = (cClustersInRun << 8) + *pbNum--;
+ }
+ else
+ cClustersInRun = -1;
+
+ /* Value 2: The logical cluster delta to get to the first cluster in the run. */
+ cbNum = bLengths >> 4;
+ if (cbNum)
+ {
+ uint8_t const *pbNum = &pbPairs[offPairs + cbNum + (bLengths & 0xf)]; /* last byte */
+ int64_t cLcnDelta = (int8_t)*pbNum--;
+ while (cbNum-- > 1)
+ cLcnDelta = (cLcnDelta << 8) + *pbNum--;
+ iLnc += cLcnDelta;
+ Log2(("NTFS: [%02d/%#05x]: VNC=%#012RX64 L %#012RX64 => LNC=%#012RX64\n",
+ iPair, offPairs, iVnc, cClustersInRun, iLnc));
+ }
+ else
+ Log2(("NTFS: [%02d/%#05x]: VNC=%#012RX64 L %#012RX64 => HOLE\n",
+ iPair, offPairs, iVnc, cClustersInRun));
+
+ /* Advance. */
+ iVnc += cClustersInRun;
+ offPairs += 1 + cbRun;
+ iPair++;
+ }
+ }
+ }
+ else if ( cbAttrib != NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED
+ && cbAttrib != NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED)
+ {
+ Log2(("NTFS: Warning! Odd non-resident attribute size: %#x!\n", cbAttrib));
+ if (cbAttrib >= NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED)
+ Log2(("NTFS: @%05x: %.*Rhxs!\n", offRec + NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED,
+ cbAttrib - NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED,
+ &pbRec[offRec + NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED]));
+ }
+ }
+ else
+ Log2(("NTFS: !Attrib header is out of bound!\n"));
+
+ /* Advance. */
+ offRec += RT_MAX(cbAttrib, NTFSATTRIBHDR_SIZE_RESIDENT);
+ }
+
+ /* Anything left? */
+ if (offRec < cbRecUsed)
+ Log2(("NTFS: @%#05x: Tail: %.*Rhxs\n", offRec, cbRecUsed - offRec, &pbRec[offRec]));
+ }
+ else
+ Log2(("NTFS: Unknown record type: %.4Rhxs\n", pFileRec));
+ }
+}
+#endif /* LOG_ENABLED */
+
+
+static int rtFsNtfsAttr_ParseExtents(PRTFSNTFSATTR pAttrib, PRTFSNTFSEXTENTS pExtents, uint8_t cClusterShift, int64_t iVcnFirst,
+ uint64_t cbVolume, PRTERRINFO pErrInfo, uint64_t idxMft, uint32_t offAttrib)
+{
+ PCNTFSATTRIBHDR pAttrHdr = pAttrib->pAttrHdr;
+ Assert(pAttrHdr->fNonResident);
+ Assert(pExtents->cExtents == 0);
+ Assert(pExtents->paExtents == NULL);
+
+ /** @todo Not entirely sure how to best detect empty mapping pair program.
+ * Not sure if this is a real problem as zero length stuff can be
+ * resident. */
+ uint16_t const offMappingPairs = RT_LE2H_U16(pAttrHdr->u.NonRes.offMappingPairs);
+ uint32_t const cbAttrib = RT_LE2H_U32(pAttrHdr->cbAttrib);
+ if ( offMappingPairs != cbAttrib
+ && offMappingPairs != 0)
+ {
+ if (pAttrHdr->u.NonRes.iVcnFirst < iVcnFirst)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x) has a lower starting VNC than expected: %#RX64, %#RX64",
+ idxMft, offAttrib, pAttrHdr->u.NonRes.iVcnFirst, iVcnFirst);
+
+ if ( offMappingPairs >= cbAttrib
+ || offMappingPairs < NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Mapping pair program for attribute (@%#x) is out of bounds: %#x, cbAttrib=%#x",
+ idxMft, offAttrib, offMappingPairs, cbAttrib);
+
+ /*
+ * Count the pairs.
+ */
+ uint8_t const * const pbPairs = (const uint8_t *)pAttrHdr + pAttrHdr->u.NonRes.offMappingPairs;
+ uint32_t const cbPairs = cbAttrib - offMappingPairs;
+ uint32_t offPairs = 0;
+ uint32_t cPairs = 0;
+ while (offPairs < cbPairs)
+ {
+ uint8_t const bLengths = pbPairs[offPairs];
+ if (bLengths)
+ {
+ uint8_t const cbRunField = bLengths & 0x0f;
+ uint8_t const cbLcnField = bLengths >> 4;
+ if ( cbRunField > 0
+ && cbRunField <= 8)
+ {
+ if (cbLcnField <= 8)
+ {
+ cPairs++;
+
+ /* Advance and check for overflow/end. */
+ offPairs += 1 + cbRunField + cbLcnField;
+ if (offPairs <= cbAttrib)
+ continue;
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Mapping pair #%#x for attribute (@%#x) is out of bounds",
+ idxMft, cPairs - 1, offAttrib);
+ }
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Mapping pair #%#x for attribute (@%#x): cbLcnField is out of bound: %u",
+ idxMft, cPairs - 1, offAttrib, cbLcnField);
+
+ }
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Mapping pair #%#x for attribute (@%#x): cbRunField is out of bound: %u",
+ idxMft, cPairs - 1, offAttrib, cbRunField);
+ }
+ break;
+ }
+
+ /*
+ * Allocate an the extent table for them.
+ */
+ uint32_t const cExtents = cPairs + (pAttrHdr->u.NonRes.iVcnFirst != iVcnFirst);
+ if (cExtents)
+ {
+ PRTFSNTFSEXTENT paExtents = (PRTFSNTFSEXTENT)RTMemAllocZ(sizeof(paExtents[0]) * cExtents);
+ AssertReturn(paExtents, VERR_NO_MEMORY);
+
+ /*
+ * Fill the table.
+ */
+ uint32_t iExtent = 0;
+
+ /* A sparse hole between this and the previous extent table? */
+ if (pAttrHdr->u.NonRes.iVcnFirst != iVcnFirst)
+ {
+ paExtents[iExtent].off = UINT64_MAX;
+ paExtents[iExtent].cbExtent = (pAttrHdr->u.NonRes.iVcnFirst - iVcnFirst) << cClusterShift;
+ Log3((" paExtent[%#04x]: %#018RX64 LB %#010RX64\n", iExtent, paExtents[iExtent].off, paExtents[iExtent].cbExtent));
+ iExtent++;
+ }
+
+ /* Run the program again, now with values and without verbose error checking. */
+ uint64_t cMaxClustersInRun = (INT64_MAX >> cClusterShift) - pAttrHdr->u.NonRes.iVcnFirst;
+ uint64_t cbData = 0;
+ int64_t iLcn = 0;
+ int rc = VINF_SUCCESS;
+ offPairs = 0;
+ for (; iExtent < cExtents; iExtent++)
+ {
+ uint8_t const bLengths = pbPairs[offPairs++];
+ uint8_t const cbRunField = bLengths & 0x0f;
+ uint8_t const cbLcnField = bLengths >> 4;
+ AssertBreakStmt((unsigned)cbRunField - 1U <= 7U, rc = VERR_VFS_BOGUS_FORMAT);
+ AssertBreakStmt((unsigned)cbLcnField <= 8U, rc = VERR_VFS_BOGUS_FORMAT);
+
+ AssertBreakStmt(!(pbPairs[offPairs + cbRunField - 1] & 0x80),
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Extent #%#x for attribute (@%#x): Negative runlength value",
+ idxMft, iExtent, offAttrib));
+ uint64_t cClustersInRun = 0;
+ switch (cbRunField)
+ {
+ case 8: cClustersInRun |= (uint64_t)pbPairs[offPairs + 7] << 56; RT_FALL_THRU();
+ case 7: cClustersInRun |= (uint64_t)pbPairs[offPairs + 6] << 48; RT_FALL_THRU();
+ case 6: cClustersInRun |= (uint64_t)pbPairs[offPairs + 5] << 40; RT_FALL_THRU();
+ case 5: cClustersInRun |= (uint64_t)pbPairs[offPairs + 4] << 32; RT_FALL_THRU();
+ case 4: cClustersInRun |= (uint32_t)pbPairs[offPairs + 3] << 24; RT_FALL_THRU();
+ case 3: cClustersInRun |= (uint32_t)pbPairs[offPairs + 2] << 16; RT_FALL_THRU();
+ case 2: cClustersInRun |= (uint16_t)pbPairs[offPairs + 1] << 8; RT_FALL_THRU();
+ case 1: cClustersInRun |= (uint16_t)pbPairs[offPairs + 0] << 0;
+ }
+ offPairs += cbRunField;
+ AssertBreakStmt(cClustersInRun <= cMaxClustersInRun,
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Extent #%#x for attribute (@%#x): too many clusters %#RX64, max %#RX64",
+ idxMft, iExtent, offAttrib, cClustersInRun, cMaxClustersInRun));
+ cMaxClustersInRun -= cClustersInRun;
+ paExtents[iExtent].cbExtent = cClustersInRun << cClusterShift;
+ cbData += cClustersInRun << cClusterShift;
+
+ if (cbLcnField)
+ {
+ unsigned offVncDelta = cbLcnField;
+ int64_t cLncDelta = (int8_t)pbPairs[--offVncDelta + offPairs];
+ while (offVncDelta-- > 0)
+ cLncDelta = (cLncDelta << 8) | pbPairs[offVncDelta + offPairs];
+ offPairs += cbLcnField;
+
+ iLcn += cLncDelta;
+ if (iLcn >= 0)
+ {
+ paExtents[iExtent].off = (uint64_t)iLcn << cClusterShift;
+ AssertBreakStmt((paExtents[iExtent].off >> cClusterShift) == (uint64_t)iLcn,
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Extent #%#x for attribute (@%#x): iLcn %RX64 overflows when shifted by %u",
+ idxMft, iExtent, offAttrib, iLcn, cClusterShift));
+ AssertBreakStmt( paExtents[iExtent].off < cbVolume
+ || paExtents[iExtent].cbExtent < cbVolume
+ || paExtents[iExtent].off + paExtents[iExtent].cbExtent <= cbVolume,
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Extent #%#x for attribute (@%#x) outside volume: %#RX64 LB %#RX64, cbVolume=%#RX64",
+ idxMft, iExtent, offAttrib, paExtents[iExtent].off,
+ paExtents[iExtent].cbExtent, cbVolume));
+ }
+ else
+ paExtents[iExtent].off = UINT64_MAX;
+ }
+ else
+ paExtents[iExtent].off = UINT64_MAX;
+ Log3((" paExtent[%#04x]: %#018RX64 LB %#010RX64\n", iExtent, paExtents[iExtent].off, paExtents[iExtent].cbExtent));
+ }
+
+ /* Commit if everything went fine? */
+ if (RT_SUCCESS(rc))
+ {
+ pExtents->cbData = cbData;
+ pExtents->cExtents = cExtents;
+ pExtents->paExtents = paExtents;
+ }
+ else
+ {
+ RTMemFree(paExtents);
+ return rc;
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses the given MTF record and all related records, putting the result in
+ * pRec->pCore (with one reference for the caller).
+ *
+ * ASSUMES caller will insert pRec->pCore into the CoreInUseHead list on
+ * success, and destroy it on failure. It is better to have caller do the
+ * inserting/destroy, since we don't want to cache a failed parsing attempt.
+ * (It is also preferable to add RTFSNTFSCORE::cbCost once it's fully calculated
+ * and in the place as the insertion.)
+ *
+ * @returns IPRT status code.
+ * @param pThis The volume.
+ * @param pRec The MFT record to parse.
+ * @param pErrInfo Where to return additional error information. Optional.
+ */
+static int rtFsNtfsVol_ParseMft(PRTFSNTFSVOL pThis, PRTFSNTFSMFTREC pRec, PRTERRINFO pErrInfo)
+{
+ AssertReturn(!pRec->pCore, VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Check that it is a file record and that its base MFT record number is zero.
+ * Caller should do the base record resolving.
+ */
+ PNTFSRECFILE pFileRec = pRec->pFileRec;
+ if (pFileRec->Hdr.uMagic != NTFSREC_MAGIC_FILE)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Not a FILE entry (%.4Rhxs)",
+ pRec->TreeNode.Key, &pFileRec->Hdr);
+ if (pFileRec->BaseMftRec.u64 != 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Not a base record (%#RX64, sqn %#x)",
+ pRec->TreeNode.Key, NTFSMFTREF_GET_IDX(&pFileRec->BaseMftRec),
+ NTFSMFTREF_GET_SEQ(&pFileRec->BaseMftRec) );
+
+ /*
+ * Create a core node (1 reference, returned even on error).
+ */
+ PRTFSNTFSCORE pCore = (PRTFSNTFSCORE)RTMemAllocZ(sizeof(*pCore));
+ AssertReturn(pCore, VERR_NO_MEMORY);
+
+ pCore->cRefs = 1;
+ pCore->cbCost = pThis->cbMftRecord + sizeof(*pCore);
+ pCore->pVol = pThis;
+ RTListInit(&pCore->AttribHead);
+ pCore->pMftRec = pRec;
+ rtFsNtfsMftRec_Retain(pRec);
+ pRec->pCore = pCore;
+
+ /*
+ * Parse attributes.
+ * We process any attribute list afterwards, skipping attributes in this MFT record.
+ */
+ PRTFSNTFSATTR pAttrList = NULL;
+ uint8_t * const pbRec = pRec->pbRec;
+ uint32_t offRec = pFileRec->offFirstAttrib;
+ uint32_t const cbRecUsed = RT_MIN(pThis->cbMftRecord, pFileRec->cbRecUsed);
+ while (offRec + NTFSATTRIBHDR_SIZE_RESIDENT <= cbRecUsed)
+ {
+ PNTFSATTRIBHDR pAttrHdr = (PNTFSATTRIBHDR)&pbRec[offRec];
+
+ /*
+ * Validate the attribute data.
+ */
+ uint32_t const cbAttrib = RT_LE2H_U32(pAttrHdr->cbAttrib);
+ uint32_t const cbMin = !pAttrHdr->fNonResident ? NTFSATTRIBHDR_SIZE_RESIDENT
+ : pAttrHdr->u.NonRes.uCompressionUnit == 0 ? NTFSATTRIBHDR_SIZE_NONRES_UNCOMPRESSED
+ : NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED;
+ if (cbAttrib < cbMin)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x) is too small (%#x, cbMin=%#x)",
+ pRec->TreeNode.Key, offRec, cbAttrib, cbMin);
+ if (offRec + cbAttrib > cbRecUsed)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x) is too long (%#x, cbRecUsed=%#x)",
+ pRec->TreeNode.Key, offRec, cbAttrib, cbRecUsed);
+ if (cbAttrib & 0x7)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x) size is misaligned: %#x",
+ pRec->TreeNode.Key, offRec, cbAttrib);
+ if (pAttrHdr->fNonResident)
+ {
+ int64_t const cbAllocated = RT_LE2H_U64(pAttrHdr->u.NonRes.cbAllocated);
+ if (cbAllocated < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbAllocated (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, cbAllocated);
+ if ((uint64_t)cbAllocated & (pThis->cbCluster - 1))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbAllocated (%#RX64) isn't cluster aligned (cbCluster=%#x)",
+ pRec->TreeNode.Key, offRec, cbAllocated, pThis->cbCluster);
+
+ int64_t const cbData = RT_LE2H_U64(pAttrHdr->u.NonRes.cbData);
+ if (cbData < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbData (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, cbData);
+
+ int64_t const cbInitialized = RT_LE2H_U64(pAttrHdr->u.NonRes.cbInitialized);
+ if (cbInitialized < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbInitialized (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, cbInitialized);
+
+ int64_t const iVcnFirst = RT_LE2H_U64(pAttrHdr->u.NonRes.iVcnFirst);
+ int64_t const iVcnLast = RT_LE2H_U64(pAttrHdr->u.NonRes.iVcnLast);
+ if ( iVcnFirst > iVcnLast
+ && ( iVcnLast != -1
+ || cbAllocated != 0))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): iVcnFirst (%#RX64) is higher than iVcnLast (%#RX64)",
+ pRec->TreeNode.Key, offRec, iVcnFirst, iVcnLast);
+ if (iVcnFirst < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): iVcnFirst (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, iVcnFirst);
+ if ((uint64_t)iVcnLast > pThis->iMaxVirtualCluster)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): iVcnLast (%#RX64) is too high, max %RX64 (shift %#x)",
+ pRec->TreeNode.Key, offRec, iVcnLast, pThis->cClusterShift, pThis->iMaxVirtualCluster);
+ uint16_t const offMappingPairs = RT_LE2H_U16(pAttrHdr->u.NonRes.offMappingPairs);
+ if ( (offMappingPairs != 0 && offMappingPairs < cbMin)
+ || offMappingPairs > cbAttrib)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): offMappingPairs (%#x) is out of bounds (cbAttrib=%#x, cbMin=%#x)",
+ pRec->TreeNode.Key, offRec, offMappingPairs, cbAttrib, cbMin);
+ if (pAttrHdr->u.NonRes.uCompressionUnit > 16)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): uCompressionUnit (%#x) is too high",
+ pRec->TreeNode.Key, offRec, pAttrHdr->u.NonRes.uCompressionUnit);
+
+ if (cbMin >= NTFSATTRIBHDR_SIZE_NONRES_COMPRESSED)
+ {
+ int64_t const cbCompressed = RT_LE2H_U64(pAttrHdr->u.NonRes.cbCompressed);
+ if (cbAllocated < 0)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): cbCompressed (%#RX64) is negative",
+ pRec->TreeNode.Key, offRec, cbCompressed);
+ }
+ }
+ else
+ {
+ uint16_t const offValue = RT_LE2H_U32(pAttrHdr->u.Res.offValue);
+ if ( offValue > cbAttrib
+ || offValue < NTFSATTRIBHDR_SIZE_RESIDENT)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): offValue (%#RX16) is out of bounds (cbAttrib=%#RX32, cbValue=%#RX32)",
+ pRec->TreeNode.Key, offRec, offValue, cbAttrib, RT_LE2H_U32(pAttrHdr->u.Res.cbValue));
+ if ((pAttrHdr->fFlags & NTFS_AF_COMPR_FMT_MASK) != NTFS_AF_COMPR_FMT_NONE)
+ {
+#if 1 /* Seen on INDEX_ROOT of ReportQueue on w7, so turned into debug log warning. */
+ Log(("NTFS: Warning! Bad MFT record %#RX64: Attribute (@%#x): fFlags (%#RX16) indicate compression of a resident attribute\n",
+ pRec->TreeNode.Key, offRec, RT_LE2H_U16(pAttrHdr->fFlags) ));
+#else
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): fFlags (%#RX16) indicate compression of a resident attribute",
+ pRec->TreeNode.Key, offRec, RT_LE2H_U16(pAttrHdr->fFlags));
+#endif
+ }
+ }
+
+ if (pAttrHdr->cwcName != 0)
+ {
+ uint16_t offName = RT_LE2H_U16(pAttrHdr->offName);
+ if ( offName < cbMin
+ || offName >= cbAttrib)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): offName (%#RX16) is out of bounds (cbAttrib=%#RX32, cbMin=%#RX32)",
+ pRec->TreeNode.Key, offRec, offName, cbAttrib, cbMin);
+ if (offName + pAttrHdr->cwcName * sizeof(RTUTF16) > cbAttrib)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Bad MFT record %#RX64: Attribute (@%#x): offName (%#RX16) + cwcName (%#x) is out of bounds (cbAttrib=%#RX32)",
+ pRec->TreeNode.Key, offRec, offName, pAttrHdr->cwcName, cbAttrib);
+ }
+
+ /*
+ * Allocate and initialize a new attribute.
+ */
+ PRTFSNTFSATTR pAttrib = (PRTFSNTFSATTR)RTMemAllocZ(sizeof(*pAttrib));
+ AssertReturn(pAttrib, VERR_NO_MEMORY);
+ pAttrib->pAttrHdr = pAttrHdr;
+ pAttrib->offAttrHdrInMftRec = offRec;
+ pAttrib->pCore = pCore;
+ //pAttrib->cbResident = 0;
+ //pAttrib->cbValue = 0;
+ //pAttrib->Extents.cExtents = 0;
+ //pAttrib->Extents.paExtents = NULL;
+ //pAttrib->pSubRecHead = NULL;
+ if (pAttrHdr->fNonResident)
+ {
+ pAttrib->cbValue = RT_LE2H_U64(pAttrHdr->u.NonRes.cbData);
+ int rc = rtFsNtfsAttr_ParseExtents(pAttrib, &pAttrib->Extents, pThis->cClusterShift, 0 /*iVncFirst*/,
+ pThis->cbVolume, pErrInfo, pRec->TreeNode.Key, offRec);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pAttrib);
+ return rc;
+ }
+ }
+ else
+ {
+ pAttrib->cbValue = RT_LE2H_U32(pAttrHdr->u.Res.cbValue);
+ if ( (uint32_t)pAttrib->cbValue > 0
+ && RT_LE2H_U16(pAttrHdr->u.Res.offValue) < cbAttrib)
+ {
+ pAttrib->cbResident = cbAttrib - RT_LE2H_U16(pAttrHdr->u.Res.offValue);
+ if (pAttrib->cbResident > (uint32_t)pAttrib->cbValue)
+ pAttrib->cbResident = (uint32_t)pAttrib->cbValue;
+ }
+ }
+
+ RTListAppend(&pCore->AttribHead, &pAttrib->ListEntry);
+
+ if (pAttrHdr->uAttrType == NTFS_AT_ATTRIBUTE_LIST)
+ pAttrList = pAttrib;
+
+ /* Advance. */
+ offRec += cbAttrib;
+ }
+
+ /*
+ * Process any attribute list.
+ */
+ if (pAttrList)
+ {
+ /** @todo */
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Translates a attribute value offset to a disk offset.
+ *
+ * @returns Disk offset, UINT64_MAX if not translatable for some reason.
+ * @param pAttr The
+ * @param off The offset to translate.
+ * @param pcbValid Where to return the run length at the return offset.
+ * Optional.
+ */
+static uint64_t rtFsNtfsAttr_OffsetToDisk(PRTFSNTFSATTR pAttr, uint64_t off, uint64_t *pcbValid)
+{
+ /*
+ * Searching the extend list is a tad complicated since it starts in one
+ * structure and continues in a different one. But whatever.
+ */
+ PRTFSNTFSEXTENTS pTable = &pAttr->Extents;
+ PRTFSNTFSATTRSUBREC pCurSub = NULL;
+ for (;;)
+ {
+ if (off < pTable->cbData)
+ {
+ uint32_t iExtent = 0;
+ while ( iExtent < pTable->cExtents
+ && off >= pTable->paExtents[iExtent].cbExtent)
+ {
+ off -= pTable->paExtents[iExtent].cbExtent;
+ iExtent++;
+ }
+ AssertReturn(iExtent < pTable->cExtents, UINT64_MAX);
+ if (pcbValid)
+ *pcbValid = pTable->paExtents[iExtent].cbExtent - off;
+ return pTable->paExtents[iExtent].off != UINT64_MAX ? pTable->paExtents[iExtent].off + off : UINT64_MAX;
+ }
+
+ /* Next table. */
+ off -= pTable->cbData;
+ if (!pCurSub)
+ pCurSub = pAttr->pSubRecHead;
+ else
+ pCurSub = pCurSub->pNext;
+ if (!pCurSub)
+ {
+ if (pcbValid)
+ *pcbValid = 0;
+ return UINT64_MAX;
+ }
+ pTable = &pCurSub->Extents;
+ }
+ /* not reached */
+}
+
+
+static int rtFsNtfsAttr_Read(PRTFSNTFSATTR pAttr, uint64_t off, void *pvBuf, size_t cbToRead)
+{
+ PRTFSNTFSVOL pVol = pAttr->pCore->pVol;
+ int rc;
+ if (!pAttr->pAttrHdr->fNonResident)
+ {
+ /*
+ * The attribute is resident.
+ */
+ uint32_t cbAttrib = RT_LE2H_U32(pAttr->pAttrHdr->cbAttrib);
+ uint32_t cbValue = RT_LE2H_U32(pAttr->pAttrHdr->u.Res.cbValue);
+ uint16_t offValue = RT_LE2H_U16(pAttr->pAttrHdr->u.Res.offValue);
+ if ( off < cbValue
+ && cbToRead <= cbValue
+ && off + cbToRead <= cbValue)
+ {
+ if (offValue <= cbAttrib)
+ {
+ cbAttrib -= offValue;
+ if (off < cbAttrib)
+ {
+ /** @todo check if its possible to have cbValue larger than the attribute and
+ * reading those extra bytes as zero. */
+ if ( pAttr->offAttrHdrInMftRec + offValue + cbAttrib <= pVol->cbMftRecord
+ && cbAttrib <= pVol->cbMftRecord)
+ {
+ size_t cbToCopy = cbAttrib - off;
+ if (cbToCopy > cbToRead)
+ cbToCopy = cbToRead;
+ memcpy(pvBuf, (uint8_t *)pAttr->pAttrHdr + offValue, cbToCopy);
+ pvBuf = (uint8_t *)pvBuf + cbToCopy;
+ cbToRead -= cbToCopy;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = VERR_VFS_BOGUS_OFFSET;
+ Log(("rtFsNtfsAttr_Read: bad resident attribute!\n"));
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ else
+ rc = VERR_EOF;
+ }
+ else if (pAttr->pAttrHdr->u.NonRes.uCompressionUnit == 0)
+ {
+ /*
+ * Uncompressed non-resident attribute.
+ */
+ uint64_t const cbAllocated = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbAllocated);
+ if ( off >= cbAllocated
+ || cbToRead > cbAllocated
+ || off + cbToRead > cbAllocated)
+ rc = VERR_EOF;
+ else
+ {
+ rc = VINF_SUCCESS;
+
+ uint64_t const cbInitialized = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbInitialized);
+ if ( off < cbInitialized
+ && cbToRead > 0)
+ {
+ /*
+ * Locate the first extent. This is a tad complicated.
+ *
+ * We move off along as we traverse the extent tables, so that it is relative
+ * to the start of the current extent.
+ */
+ PRTFSNTFSEXTENTS pTable = &pAttr->Extents;
+ uint32_t iExtent = 0;
+ PRTFSNTFSATTRSUBREC pCurSub = NULL;
+ for (;;)
+ {
+ if (off < pTable->cbData)
+ {
+ while ( iExtent < pTable->cExtents
+ && off >= pTable->paExtents[iExtent].cbExtent)
+ {
+ off -= pTable->paExtents[iExtent].cbExtent;
+ iExtent++;
+ }
+ AssertReturn(iExtent < pTable->cExtents, VERR_INTERNAL_ERROR_2);
+ break;
+ }
+
+ /* Next table. */
+ off -= pTable->cbData;
+ if (!pCurSub)
+ pCurSub = pAttr->pSubRecHead;
+ else
+ pCurSub = pCurSub->pNext;
+ if (!pCurSub)
+ {
+ iExtent = UINT32_MAX;
+ break;
+ }
+ pTable = &pCurSub->Extents;
+ iExtent = 0;
+ }
+
+ /*
+ * The read loop.
+ */
+ while (iExtent != UINT32_MAX)
+ {
+ uint64_t cbMaxRead = pTable->paExtents[iExtent].cbExtent;
+ Assert(off < cbMaxRead);
+ cbMaxRead -= off;
+ size_t const cbThisRead = cbMaxRead >= cbToRead ? cbToRead : (size_t)cbMaxRead;
+ if (pTable->paExtents[iExtent].off == UINT64_MAX)
+ RT_BZERO(pvBuf, cbThisRead);
+ else
+ {
+ rc = RTVfsFileReadAt(pVol->hVfsBacking, pTable->paExtents[iExtent].off + off, pvBuf, cbThisRead, NULL);
+ Log4(("NTFS: Volume read: @%#RX64 LB %#zx -> %Rrc\n", pTable->paExtents[iExtent].off + off, cbThisRead, rc));
+ if (RT_FAILURE(rc))
+ break;
+ }
+ pvBuf = (uint8_t *)pvBuf + cbThisRead;
+ cbToRead -= cbThisRead;
+ if (!cbToRead)
+ break;
+ off = 0;
+
+ /*
+ * Advance to the next extent.
+ */
+ iExtent++;
+ if (iExtent >= pTable->cExtents)
+ {
+ pCurSub = pCurSub ? pCurSub->pNext : pAttr->pSubRecHead;
+ if (!pCurSub)
+ break;
+ pTable = &pCurSub->Extents;
+ iExtent = 0;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ LogRel(("rtFsNtfsAttr_Read: Compressed files are not supported\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Anything else beyond the end of what's stored/initialized?
+ */
+ if ( cbToRead > 0
+ && RT_SUCCESS(rc))
+ {
+ RT_BZERO(pvBuf, cbToRead);
+ }
+
+ return rc;
+}
+
+
+/**
+ *
+ * @note Only modifying non-resident data is currently supported. No
+ * shrinking or growing. Metadata is not modified.
+ */
+static int rtFsNtfsAttr_Write(PRTFSNTFSATTR pAttr, uint64_t off, void const *pvBuf, size_t cbToWrite)
+{
+ PRTFSNTFSVOL pVol = pAttr->pCore->pVol;
+ int rc;
+ if (!pAttr->pAttrHdr->fNonResident)
+ {
+ /*
+ * The attribute is resident. Currently not supported.
+ */
+#if 0
+ uint32_t cbAttrib = RT_LE2H_U32(pAttr->pAttrHdr->cbAttrib);
+ uint32_t cbValue = RT_LE2H_U32(pAttr->pAttrHdr->u.Res.cbValue);
+ uint16_t offValue = RT_LE2H_U16(pAttr->pAttrHdr->u.Res.offValue);
+ if ( off < cbValue
+ && cbToWrite <= cbValue
+ && off + cbToWrite <= cbValue)
+ {
+ if (offValue <= cbAttrib)
+ {
+ cbAttrib -= offValue;
+ if (off < cbAttrib)
+ {
+ /** @todo check if its possible to have cbValue larger than the attribute and
+ * reading those extra bytes as zero. */
+ if ( pAttr->offAttrHdrInMftRec + offValue + cbAttrib <= pVol->cbMftRecord
+ && cbAttrib <= pVol->cbMftRecord)
+ {
+ size_t cbToCopy = cbAttrib - off;
+ if (cbToCopy > cbToWrite)
+ cbToCopy = cbToWrite;
+ memcpy(pvBuf, (uint8_t *)pAttr->pAttrHdr + offValue, cbToCopy);
+ pvBuf = (uint8_t *)pvBuf + cbToCopy;
+ cbToWrite -= cbToCopy;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = VERR_VFS_BOGUS_OFFSET;
+ Log(("rtFsNtfsAttr_Write: bad resident attribute!\n"));
+ }
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ else
+ rc = VERR_EOF;
+#else
+ LogRel(("rtFsNtfsAttr_Write: file too small to write to.\n"));
+ rc = VERR_INTERNAL_ERROR_3;
+#endif
+ }
+ else if (pAttr->pAttrHdr->u.NonRes.uCompressionUnit == 0)
+ {
+ /*
+ * Uncompressed non-resident attribute.
+ * Note! We currently
+ */
+ uint64_t const cbAllocated = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbAllocated);
+ if ( off >= cbAllocated
+ || cbToWrite > cbAllocated
+ || off + cbToWrite > cbAllocated)
+ rc = VERR_EOF;
+ else
+ {
+ rc = VINF_SUCCESS;
+
+ uint64_t const cbInitialized = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbInitialized);
+ if ( off < cbInitialized
+ && cbToWrite > 0)
+ {
+ /*
+ * Locate the first extent. This is a tad complicated.
+ *
+ * We move off along as we traverse the extent tables, so that it is relative
+ * to the start of the current extent.
+ */
+ PRTFSNTFSEXTENTS pTable = &pAttr->Extents;
+ uint32_t iExtent = 0;
+ PRTFSNTFSATTRSUBREC pCurSub = NULL;
+ for (;;)
+ {
+ if (off < pTable->cbData)
+ {
+ while ( iExtent < pTable->cExtents
+ && off >= pTable->paExtents[iExtent].cbExtent)
+ {
+ off -= pTable->paExtents[iExtent].cbExtent;
+ iExtent++;
+ }
+ AssertReturn(iExtent < pTable->cExtents, VERR_INTERNAL_ERROR_2);
+ break;
+ }
+
+ /* Next table. */
+ off -= pTable->cbData;
+ if (!pCurSub)
+ pCurSub = pAttr->pSubRecHead;
+ else
+ pCurSub = pCurSub->pNext;
+ if (!pCurSub)
+ {
+ iExtent = UINT32_MAX;
+ break;
+ }
+ pTable = &pCurSub->Extents;
+ iExtent = 0;
+ }
+
+ /*
+ * The write loop.
+ */
+ while (iExtent != UINT32_MAX)
+ {
+ uint64_t cbMaxWrite = pTable->paExtents[iExtent].cbExtent;
+ Assert(off < cbMaxWrite);
+ cbMaxWrite -= off;
+ size_t const cbThisWrite = cbMaxWrite >= cbToWrite ? cbToWrite : (size_t)cbMaxWrite;
+ if (pTable->paExtents[iExtent].off == UINT64_MAX)
+ {
+ if (!ASMMemIsZero(pvBuf, cbThisWrite))
+ {
+ LogRel(("rtFsNtfsAttr_Write: Unable to modify sparse section of file!\n"));
+ rc = VERR_INTERNAL_ERROR_2;
+ break;
+ }
+ }
+ else
+ {
+ rc = RTVfsFileWriteAt(pVol->hVfsBacking, pTable->paExtents[iExtent].off + off, pvBuf, cbThisWrite, NULL);
+ Log4(("NTFS: Volume write: @%#RX64 LB %#zx -> %Rrc\n", pTable->paExtents[iExtent].off + off, cbThisWrite, rc));
+ if (RT_FAILURE(rc))
+ break;
+ }
+ pvBuf = (uint8_t const *)pvBuf + cbThisWrite;
+ cbToWrite -= cbThisWrite;
+ if (!cbToWrite)
+ break;
+ off = 0;
+
+ /*
+ * Advance to the next extent.
+ */
+ iExtent++;
+ if (iExtent >= pTable->cExtents)
+ {
+ pCurSub = pCurSub ? pCurSub->pNext : pAttr->pSubRecHead;
+ if (!pCurSub)
+ break;
+ pTable = &pCurSub->Extents;
+ iExtent = 0;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ LogRel(("rtFsNtfsAttr_Write: Compressed files are not supported\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+
+ /*
+ * Anything else beyond the end of what's stored/initialized?
+ */
+ if ( cbToWrite > 0
+ && RT_SUCCESS(rc))
+ {
+ LogRel(("rtFsNtfsAttr_Write: Unable to modify sparse section (tail) of file!\n"));
+ rc = VERR_INTERNAL_ERROR_2;
+ }
+
+ return rc;
+}
+
+
+/**
+ *
+ * @returns
+ * @param pRecHdr .
+ * @param cbRec .
+ * @param fRelaxedUsa .
+ * @param pErrInfo .
+ *
+ * @see https://msdn.microsoft.com/en-us/library/bb470212%28v=vs.85%29.aspx
+ */
+static int rtFsNtfsRec_DoMultiSectorFixups(PNTFSRECHDR pRecHdr, uint32_t cbRec, bool fRelaxedUsa, PRTERRINFO pErrInfo)
+{
+ /*
+ * Do sanity checking.
+ */
+ uint16_t offUpdateSeqArray = RT_LE2H_U16(pRecHdr->offUpdateSeqArray);
+ uint16_t cUpdateSeqEntries = RT_LE2H_U16(pRecHdr->cUpdateSeqEntries);
+ if ( !(cbRec & (NTFS_MULTI_SECTOR_STRIDE - 1))
+ && !(offUpdateSeqArray & 1) /* two byte aligned */
+ && cUpdateSeqEntries == 1 + cbRec / NTFS_MULTI_SECTOR_STRIDE
+ && offUpdateSeqArray + (uint32_t)cUpdateSeqEntries * 2U < NTFS_MULTI_SECTOR_STRIDE - 2U)
+ {
+ uint16_t const *pauUsa = (uint16_t const *)((uint8_t *)pRecHdr + offUpdateSeqArray);
+
+ /*
+ * The first update seqence array entry is the value stored at
+ * the fixup locations at the end of the blocks. We read this
+ * and check each of the blocks.
+ */
+ uint16_t const uCheck = *pauUsa++;
+ cUpdateSeqEntries--;
+ for (uint16_t iBlock = 0; iBlock < cUpdateSeqEntries; iBlock++)
+ {
+ uint16_t const *puBlockCheck = (uint16_t const *)((uint8_t *)pRecHdr + (iBlock + 1) * NTFS_MULTI_SECTOR_STRIDE - 2U);
+ if (*puBlockCheck == uCheck)
+ { /* likely */ }
+ else if (!fRelaxedUsa)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_OFFSET,
+ "Multisector transfer error: block #%u ends with %#x instead of %#x (fixup: %#x)",
+ iBlock, RT_LE2H_U16(*puBlockCheck), RT_LE2H_U16(uCheck), RT_LE2H_U16(pauUsa[iBlock]) );
+ else
+ {
+ Log(("NTFS: Multisector transfer warning: block #%u ends with %#x instead of %#x (fixup: %#x)\n",
+ iBlock, RT_LE2H_U16(*puBlockCheck), RT_LE2H_U16(uCheck), RT_LE2H_U16(pauUsa[iBlock]) ));
+ return VINF_SUCCESS;
+ }
+ }
+
+ /*
+ * Apply the fixups.
+ * Note! We advanced pauUsa above, so it's now at the fixup values.
+ */
+ for (uint16_t iBlock = 0; iBlock < cUpdateSeqEntries; iBlock++)
+ {
+ uint16_t *puFixup = (uint16_t *)((uint8_t *)pRecHdr + (iBlock + 1) * NTFS_MULTI_SECTOR_STRIDE - 2U);
+ *puFixup = pauUsa[iBlock];
+ }
+ return VINF_SUCCESS;
+ }
+ if (fRelaxedUsa)
+ {
+ Log(("NTFS: Ignoring bogus multisector update sequence: cbRec=%#x uMagic=%#RX32 offUpdateSeqArray=%#x cUpdateSeqEntries=%#x\n",
+ cbRec, RT_LE2H_U32(pRecHdr->uMagic), offUpdateSeqArray, cUpdateSeqEntries ));
+ return VINF_SUCCESS;
+ }
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_OFFSET,
+ "Bogus multisector update sequence: cbRec=%#x uMagic=%#RX32 offUpdateSeqArray=%#x cUpdateSeqEntries=%#x",
+ cbRec, RT_LE2H_U32(pRecHdr->uMagic), offUpdateSeqArray, cUpdateSeqEntries);
+}
+
+
+/**
+ * Allocate and parse an MFT record, returning a core object structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param idxMft The index of the MTF record.
+ * @param fRelaxedUsa Relaxed update sequence checking. Won't fail if
+ * checks doesn't work or not present.
+ * @param ppCore Where to return the core object structure.
+ * @param pErrInfo Where to return error details. Optional.
+ */
+static int rtFsNtfsVol_NewCoreForMftIdx(PRTFSNTFSVOL pThis, uint64_t idxMft, bool fRelaxedUsa,
+ PRTFSNTFSCORE *ppCore, PRTERRINFO pErrInfo)
+{
+ *ppCore = NULL;
+ Assert(pThis->pMftData);
+ Assert(RTAvlU64Get(&pThis->MftRoot, idxMft) == NULL);
+
+ PRTFSNTFSMFTREC pRec = rtFsNtfsVol_NewMftRec(pThis, idxMft);
+ AssertReturn(pRec, VERR_NO_MEMORY);
+
+ uint64_t offRec = idxMft * pThis->cbMftRecord;
+ int rc = rtFsNtfsAttr_Read(pThis->pMftData, offRec, pRec->pbRec, pThis->cbMftRecord);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsRec_DoMultiSectorFixups(&pRec->pFileRec->Hdr, pThis->cbMftRecord, fRelaxedUsa, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtfsNtfsMftRec_Log(pRec, pThis->cbMftRecord);
+#endif
+ rc = rtFsNtfsVol_ParseMft(pThis, pRec, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSCORE pCore = pRec->pCore;
+ rtFsNtfsMftRec_Release(pRec, pThis);
+
+ /* Insert core into the cache list and update the cost, maybe trimming the cache. */
+ RTListAppend(&pThis->CoreInUseHead, &pCore->ListEntry);
+ pThis->cbCoreObjects += pCore->cbCost;
+ if (pThis->cbCoreObjects > RTFSNTFS_MAX_CORE_CACHE_SIZE)
+ rtFsNtfsIdxVol_TrimCoreObjectCache(pThis);
+
+ *ppCore = pCore;
+ return VINF_SUCCESS;
+ }
+
+ if (pRec->pCore)
+ rtFsNtfsCore_Destroy(pRec->pCore);
+ rtFsNtfsMftRec_Release(pRec, pThis);
+ }
+ return rc;
+}
+
+
+/**
+ * Queries the core object struct for the given MFT record reference.
+ *
+ * Does caching.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pMftRef The MFT reference to get the corresponding core
+ * for.
+ * @param fRelaxedUsa Relaxed update sequence checking. Won't fail if
+ * checks doesn't work or not present.
+ * @param ppCore Where to return the referenced core object
+ * structure.
+ * @param pErrInfo Where to return error details. Optional.
+ */
+static int rtFsNtfsVol_QueryCoreForMftRef(PRTFSNTFSVOL pThis, PCNTFSMFTREF pMftRef , bool fRelaxedUsa,
+ PRTFSNTFSCORE *ppCore, PRTERRINFO pErrInfo)
+{
+ *ppCore = NULL;
+ Assert(pThis->pMftData);
+
+ int rc;
+ PRTFSNTFSMFTREC pMftRec = (PRTFSNTFSMFTREC)RTAvlU64Get(&pThis->MftRoot, NTFSMFTREF_GET_IDX(pMftRef));
+ if (pMftRec)
+ {
+ /*
+ * Cache hit. Check that the resure sequence number matches.
+ * To be slightly paranoid, also check that it's a base MFT record and that it has been parsed already.
+ */
+ if (RT_LE2H_U16(pMftRec->pFileRec->uRecReuseSeqNo) == NTFSMFTREF_GET_SEQ(pMftRef))
+ {
+ if ( NTFSMFTREF_IS_ZERO(&pMftRec->pFileRec->BaseMftRec)
+ && pMftRec->pCore)
+ {
+ rtFsNtfsCore_Retain(pMftRec->pCore);
+ *ppCore = pMftRec->pCore;
+ rc = VINF_SUCCESS;
+ }
+ else
+ AssertLogRelMsgFailedStmt(("pCore=%p; BaseMftRec=%#RX64 sqn %#x\n", pMftRec->pCore,
+ NTFSMFTREF_GET_IDX(&pMftRec->pFileRec->BaseMftRec),
+ NTFSMFTREF_GET_SEQ(&pMftRec->pFileRec->BaseMftRec)),
+ rc = VERR_INTERNAL_ERROR_3 );
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_OFFSET,
+ "Stale parent directory MFT reference: %#RX64 sqn %#x - current sqn %#x",
+ NTFSMFTREF_GET_IDX(pMftRef), NTFSMFTREF_GET_SEQ(pMftRef),
+ RT_LE2H_U16(pMftRec->pFileRec->uRecReuseSeqNo) );
+ }
+ else
+ {
+ /*
+ * Load new and check that the reuse sequence number match.
+ */
+ rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFSMFTREF_GET_IDX(pMftRef), fRelaxedUsa, ppCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSCORE pCore = *ppCore;
+ if (RT_LE2H_U16(pCore->pMftRec->pFileRec->uRecReuseSeqNo) == NTFSMFTREF_GET_SEQ(pMftRef))
+ rc = VINF_SUCCESS;
+ else
+ {
+ rtFsNtfsCore_Release(pCore);
+ *ppCore = NULL;
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_OFFSET,
+ "Stale parent directory MFT reference: %#RX64 sqn %#x - current sqn %#x",
+ NTFSMFTREF_GET_IDX(pMftRef), NTFSMFTREF_GET_SEQ(pMftRef),
+ RT_LE2H_U16(pCore->pMftRec->pFileRec->uRecReuseSeqNo) );
+ }
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Destroys a core structure.
+ *
+ * ASSUMES the caller has remove @a pThis from the list it's on and updated the
+ * cbCoreObjects as necessary.
+ *
+ * @returns 0
+ * @param pThis The core structure.
+ */
+static uint32_t rtFsNtfsCore_Destroy(PRTFSNTFSCORE pThis)
+{
+ /*
+ * Free attributes.
+ */
+ PRTFSNTFSATTR pCurAttr;
+ PRTFSNTFSATTR pNextAttr;
+ RTListForEachSafe(&pThis->AttribHead, pCurAttr, pNextAttr, RTFSNTFSATTR, ListEntry)
+ {
+ PRTFSNTFSATTRSUBREC pSub = pCurAttr->pSubRecHead;
+ while (pSub)
+ {
+ pCurAttr->pSubRecHead = pSub->pNext;
+ RTMemFree(pSub->Extents.paExtents);
+ pSub->Extents.paExtents = NULL;
+ pSub->pAttrHdr = NULL;
+ pSub->pNext = NULL;
+ RTMemFree(pSub);
+
+ pSub = pCurAttr->pSubRecHead;
+ }
+
+ pCurAttr->pCore = NULL;
+ pCurAttr->pAttrHdr = NULL;
+ RTMemFree(pCurAttr->Extents.paExtents);
+ pCurAttr->Extents.paExtents = NULL;
+ }
+
+ /*
+ * Release the MFT chain.
+ */
+ PRTFSNTFSMFTREC pMftRec = pThis->pMftRec;
+ while (pMftRec)
+ {
+ pThis->pMftRec = pMftRec->pNext;
+ Assert(pMftRec->pCore == pThis);
+ pMftRec->pNext = NULL;
+ pMftRec->pCore = NULL;
+ rtFsNtfsMftRec_Release(pMftRec, pThis->pVol);
+
+ pMftRec = pThis->pMftRec;
+ }
+
+ RTMemFree(pThis);
+
+ return 0;
+}
+
+
+/**
+ * Trims the core object cache down to RTFSNTFS_MAX_CORE_CACHE_SIZE.
+ *
+ * @param pThis The NTFS volume instance.
+ */
+static void rtFsNtfsIdxVol_TrimCoreObjectCache(PRTFSNTFSVOL pThis)
+{
+ while (pThis->cbCoreObjects > RTFSNTFS_MAX_CORE_CACHE_SIZE)
+ {
+ PRTFSNTFSCORE pCore = RTListRemoveFirst(&pThis->CoreUnusedHead, RTFSNTFSCORE, ListEntry);
+ if (!pCore)
+ break;
+ pThis->cbCoreObjects -= pCore->cbCost;
+ rtFsNtfsCore_Destroy(pCore);
+ }
+}
+
+
+/**
+ * Releases a refernece to a core structure, maybe destroying it.
+ *
+ * @returns New reference count.
+ * @param pThis The core structure.
+ */
+static uint32_t rtFsNtfsCore_Release(PRTFSNTFSCORE pThis)
+{
+ if (pThis)
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 128);
+ if (cRefs != 0)
+ return cRefs;
+
+ /* Move from in-use list to unused list. Trim the cache if too big. */
+ RTListNodeRemove(&pThis->ListEntry);
+
+ PRTFSNTFSVOL pVol = pThis->pVol;
+ RTListAppend(&pVol->CoreUnusedHead, &pThis->ListEntry);
+ if (pVol->cbCoreObjects > RTFSNTFS_MAX_CORE_CACHE_SIZE)
+ rtFsNtfsIdxVol_TrimCoreObjectCache(pVol);
+ }
+ return 0;
+}
+
+
+/**
+ * Retains a refernece to a core structure.
+ *
+ * @returns New reference count.
+ * @param pThis The core structure.
+ */
+static uint32_t rtFsNtfsCore_Retain(PRTFSNTFSCORE pThis)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ if (cRefs == 1)
+ {
+ /* Move from unused list to in-use list. */
+ RTListNodeRemove(&pThis->ListEntry);
+ RTListAppend(&pThis->pVol->CoreInUseHead, &pThis->ListEntry);
+ }
+ Assert(cRefs < 128);
+ return cRefs;
+}
+
+
+/**
+ * Finds an unnamed attribute.
+ *
+ * @returns Pointer to the attribute structure if found, NULL if not.
+ * @param pThis The core object structure to search.
+ * @param uAttrType The attribute type to find.
+ */
+static PRTFSNTFSATTR rtFsNtfsCore_FindUnnamedAttribute(PRTFSNTFSCORE pThis, uint32_t uAttrType)
+{
+ PRTFSNTFSATTR pCurAttr;
+ RTListForEach(&pThis->AttribHead, pCurAttr, RTFSNTFSATTR, ListEntry)
+ {
+ PNTFSATTRIBHDR pAttrHdr = pCurAttr->pAttrHdr;
+ if ( pAttrHdr->uAttrType == uAttrType
+ && pAttrHdr->cwcName == 0)
+ return pCurAttr;
+ }
+ return NULL;
+}
+
+
+/**
+ * Finds a named attribute, case insensitive ASCII variant.
+ *
+ * @returns Pointer to the attribute structure if found, NULL if not.
+ * @param pThis The core object structure to search.
+ * @param uAttrType The attribute type to find.
+ * @param pszAttrib The attribute name, predefined 7-bit ASCII name.
+ * @param cchAttrib The length of the attribute.
+ */
+static PRTFSNTFSATTR rtFsNtfsCore_FindNamedAttributeAscii(PRTFSNTFSCORE pThis, uint32_t uAttrType,
+ const char *pszAttrib, size_t cchAttrib)
+{
+ Assert(cchAttrib > 0);
+ PRTFSNTFSATTR pCurAttr;
+ RTListForEach(&pThis->AttribHead, pCurAttr, RTFSNTFSATTR, ListEntry)
+ {
+ PNTFSATTRIBHDR pAttrHdr = pCurAttr->pAttrHdr;
+ if ( pAttrHdr->uAttrType == uAttrType
+ && pAttrHdr->cwcName == cchAttrib
+ && RTUtf16NICmpAscii(NTFSATTRIBHDR_GET_NAME(pAttrHdr), pszAttrib, cchAttrib) == 0)
+ return pCurAttr;
+ }
+ return NULL;
+}
+
+
+/**
+ * This attribute conversion code is a slightly modified version of rtFsModeFromDos.
+ *
+ * @returns IPRT fmode mask.
+ * @param fFileAttribs The NT file attributes.
+ * @param pFilename The filename attribute structure, optional.
+ * @param cbFilename The size of the filename attribute structure.
+ */
+static RTFMODE rtFsNtfsConvertFileattribsToMode(uint32_t fFileAttribs, PCNTFSATFILENAME pFilename, uint32_t cbFilename)
+{
+ RTFMODE fMode = (fFileAttribs << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT;
+ if (fFileAttribs & NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT)
+ fMode |= RTFS_DOS_DIRECTORY;
+
+ /* everything is readable. */
+ fMode |= RTFS_UNIX_IRUSR | RTFS_UNIX_IRGRP | RTFS_UNIX_IROTH;
+ if (fMode & RTFS_DOS_DIRECTORY)
+ /* directories are executable. */
+ fMode |= RTFS_TYPE_DIRECTORY | RTFS_UNIX_IXUSR | RTFS_UNIX_IXGRP | RTFS_UNIX_IXOTH;
+ else
+ {
+ fMode |= RTFS_TYPE_FILE;
+ if ( pFilename
+ && pFilename->cwcFilename >= 4
+ && RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pFilename->cwcFilename]) <= cbFilename)
+ {
+ PCRTUTF16 pwcExt = &pFilename->wszFilename[pFilename->cwcFilename - 4];
+ if ( *pwcExt++ == '.')
+ {
+ /* check for executable extension. */
+ if ( (unsigned)pwcExt[0] < 0x7fU
+ && (unsigned)pwcExt[1] < 0x7fU
+ && (unsigned)pwcExt[2] < 0x7fU)
+ {
+ char szExt[4];
+ szExt[0] = RT_C_TO_LOWER(pwcExt[0]);
+ szExt[1] = RT_C_TO_LOWER(pwcExt[1]);
+ szExt[2] = RT_C_TO_LOWER(pwcExt[2]);
+ szExt[3] = '\0';
+ if ( !memcmp(szExt, "exe", 4)
+ || !memcmp(szExt, "bat", 4)
+ || !memcmp(szExt, "com", 4)
+ || !memcmp(szExt, "cmd", 4)
+ || !memcmp(szExt, "btm", 4)
+ )
+ fMode |= RTFS_UNIX_IXUSR | RTFS_UNIX_IXGRP | RTFS_UNIX_IXOTH;
+ }
+ }
+ }
+ }
+
+ /* Is it really a symbolic link? */
+ if ( (fMode & RTFS_DOS_NT_REPARSE_POINT)
+ && pFilename
+ && pFilename->u.uReparseTag == RTFSMODE_SYMLINK_REPARSE_TAG)
+ fMode = (fMode & ~RTFS_TYPE_MASK) | RTFS_TYPE_SYMLINK;
+
+ /* writable? */
+ if (!(fMode & RTFS_DOS_READONLY))
+ fMode |= RTFS_UNIX_IWUSR | RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH;
+
+ return fMode;
+}
+
+
+/**
+ * Worker for various QueryInfo methods.
+ *
+ * @returns IPRT status code.
+ * @param pThis The core object structure to return info for.
+ * @param pAttr The attribute that's being presented. Take the
+ * allocation and timestamp info from it, if
+ * non-resident.
+ * @param pObjInfo Where to return object info.
+ * @param enmAddAttr What additional info to return.
+ */
+static int rtFsNtfsCore_QueryInfo(PRTFSNTFSCORE pThis, PRTFSNTFSATTR pAttr, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ /*
+ * Wipe the structure and fill in common dummy value.
+ */
+ RT_ZERO(*pObjInfo);
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
+ pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ //pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = pThis->pMftRec->TreeNode.Key;
+ //pObjInfo->Attr.u.Unix.fFlags = 0;
+ //pObjInfo->Attr.u.Unix.GenerationId = 0;
+ //pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = NIL_RTUID;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = NIL_RTGID;
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * Look for the standard information attribute and use that as basis.
+ */
+ uint32_t fFileAttribs;
+ PRTFSNTFSATTR pStdInfoAttr = rtFsNtfsCore_FindUnnamedAttribute(pThis, NTFS_AT_STANDARD_INFORMATION);
+ if ( pStdInfoAttr
+ && pStdInfoAttr->cbResident >= sizeof(NTFSATSTDINFO) )
+ {
+ Assert(!pStdInfoAttr->pAttrHdr->fNonResident);
+ PCNTFSATSTDINFO pStdInfo = (PCNTFSATSTDINFO)NTFSATTRIBHDR_GET_RES_VALUE_PTR(pStdInfoAttr->pAttrHdr);
+ RTTimeSpecSetNtTime(&pObjInfo->BirthTime, RT_LE2H_U64(pStdInfo->iCreationTime));
+ RTTimeSpecSetNtTime(&pObjInfo->ModificationTime, RT_LE2H_U64(pStdInfo->iLastDataModTime));
+ RTTimeSpecSetNtTime(&pObjInfo->ChangeTime, RT_LE2H_U64(pStdInfo->iLastMftModTime));
+ RTTimeSpecSetNtTime(&pObjInfo->AccessTime, RT_LE2H_U64(pStdInfo->iLastAccessTime));
+ if (enmAddAttr == RTFSOBJATTRADD_UNIX)
+ {
+ pObjInfo->Attr.u.Unix.uid = pStdInfo->idOwner;
+ pObjInfo->Attr.u.Unix.GenerationId = pStdInfo->uFileVersion;
+ }
+ else if (enmAddAttr == RTFSOBJATTRADD_UNIX_OWNER)
+ pObjInfo->Attr.u.UnixOwner.uid = pStdInfo->idOwner;
+ fFileAttribs = pStdInfo->fFileAttribs;
+ }
+ else
+ {
+ /** @todo check out the filename record? */
+ switch (pAttr->pAttrHdr->uAttrType)
+ {
+ default:
+ AssertFailed();
+ RT_FALL_THRU();
+ case NTFS_AT_DATA:
+ fFileAttribs = NTFS_FA_NORMAL;
+ break;
+
+ case NTFS_AT_INDEX_ROOT:
+ case NTFS_AT_INDEX_ALLOCATION:
+ fFileAttribs = NTFS_FA_DIRECTORY;
+ break;
+ }
+ }
+
+ /*
+ * Take the allocation info from the destilled attribute data.
+ */
+ pObjInfo->cbObject = pAttr->cbValue;
+ pObjInfo->cbAllocated = pAttr->Extents.cbData;
+ if ( pAttr->pAttrHdr->fNonResident
+ && (int64_t)pObjInfo->cbAllocated < (int64_t)RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbAllocated))
+ pObjInfo->cbAllocated = RT_LE2H_U64(pAttr->pAttrHdr->u.NonRes.cbAllocated);
+
+ /*
+ * See if we can find a filename record before we try convert the file attributes to mode.
+ */
+ PCNTFSATFILENAME pFilename = NULL;
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pThis, NTFS_AT_FILENAME);
+ if ( pFilenameAttr
+ && pFilenameAttr->cbResident >= RT_UOFFSETOF(NTFSATFILENAME, wszFilename) )
+ {
+ Assert(!pFilenameAttr->pAttrHdr->fNonResident);
+ pFilename = (PCNTFSATFILENAME)NTFSATTRIBHDR_GET_RES_VALUE_PTR(pFilenameAttr->pAttrHdr);
+ if (pStdInfoAttr)
+ fFileAttribs |= pFilename->fFileAttribs;
+ else
+ fFileAttribs = pFilename->fFileAttribs;
+ }
+
+ /*
+ * Convert attribs to file mode flags.
+ */
+ pObjInfo->Attr.fMode = rtFsNtfsConvertFileattribsToMode(fFileAttribs, pFilename,
+ pFilenameAttr ? pFilenameAttr->cbResident : 0);
+
+ return VINF_SUCCESS;
+}
+
+
+
+
+/*
+ *
+ * File operations.
+ * File operations.
+ * File operations.
+ *
+ */
+
+/**
+ * Releases a reference to a shared NTFS file structure.
+ *
+ * @returns New reference count.
+ * @param pShared The shared NTFS file structure.
+ */
+static uint32_t rtFsNtfsFileShrd_Release(PRTFSNTFSFILESHRD pShared)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pShared->cRefs);
+ Assert(cRefs < 64);
+ if (cRefs == 0)
+ {
+ LogFlow(("rtFsNtfsFileShrd_Release(%p): Destroying it\n", pShared));
+ Assert(pShared->pData->uObj.pSharedFile == pShared);
+ pShared->pData->uObj.pSharedFile = NULL;
+ rtFsNtfsCore_Release(pShared->pData->pCore);
+ pShared->pData = NULL;
+ RTMemFree(pShared);
+ }
+ return cRefs;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Close(void *pvThis)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ LogFlow(("rtFsNtfsFile_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSNTFSFILESHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ rtFsNtfsFileShrd_Release(pShared);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ PRTFSNTFSATTR pDataAttr = pThis->pShared->pData;
+ return rtFsNtfsCore_QueryInfo(pDataAttr->pCore, pDataAttr, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ size_t cbRead = pSgBuf->paSegs[0].cbSeg;
+ if (!pcbRead)
+ {
+ rc = rtFsNtfsAttr_Read(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbRead);
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbRead;
+ Log6(("rtFsNtfsFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc\n", off, pSgBuf->paSegs[0].cbSeg, rc));
+ }
+ else
+ {
+ PRTFSNTFSATTR pDataAttr = pThis->pShared->pData;
+ if ((uint64_t)off >= pDataAttr->cbValue)
+ {
+ *pcbRead = 0;
+ rc = VINF_EOF;
+ }
+ else
+ {
+ if ((uint64_t)off + cbRead <= pDataAttr->cbValue)
+ rc = rtFsNtfsAttr_Read(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbRead);
+ else
+ {
+ /* Return VINF_EOF if beyond end-of-file. */
+ cbRead = (size_t)(pDataAttr->cbValue - (uint64_t)off);
+ rc = rtFsNtfsAttr_Read(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbRead);
+ if (RT_SUCCESS(rc))
+ rc = VINF_EOF;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offFile = off + cbRead;
+ *pcbRead = cbRead;
+ }
+ else
+ *pcbRead = 0;
+ }
+ Log6(("rtFsNtfsFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc *pcbRead=%#x\n", off, pSgBuf->paSegs[0].cbSeg, rc, *pcbRead));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ PRTFSNTFSATTR pDataAttr = pThis->pShared->pData;
+ size_t cbToWrite = pSgBuf->paSegs[0].cbSeg;
+ if ((uint64_t)off + cbToWrite <= pDataAttr->cbValue)
+ {
+ rc = rtFsNtfsAttr_Write(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbToWrite);
+ Log6(("rtFsNtfsFile_Write: off=%#RX64 cbToWrite=%#zx -> %Rrc\n", off, cbToWrite, rc));
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbToWrite;
+ if (pcbWritten)
+ *pcbWritten = RT_SUCCESS(rc) ? cbToWrite : 0;
+ }
+ else if ((uint64_t)off < pDataAttr->cbValue)
+ {
+ size_t cbWritten = pDataAttr->cbValue - off;
+ rc = rtFsNtfsAttr_Write(pThis->pShared->pData, off, pSgBuf->paSegs[0].pvSeg, cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ Log6(("rtFsNtfsFile_Write: off=%#RX64 cbToWrite=%#zx -> VERR_EOF [EOF: %#RX64, Written: %#zx]\n",
+ off, cbToWrite, pDataAttr->cbValue, cbWritten));
+ pThis->offFile = off + cbWritten;
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ rc = VERR_EOF;
+ }
+ else
+ {
+ Log6(("rtFsNtfsFile_Write: off=%#RX64 cbToWrite=%#zx -> %Rrc [EOF: %#RX64]\n", off, cbToWrite, rc, pDataAttr->cbValue));
+ if (pcbWritten)
+ *pcbWritten = 0;
+ }
+ }
+ else
+ {
+ Log6(("rtFsNtfsFile_Write: off=%#RX64 cbToWrite=%#zx -> VERR_EOF [EOF: %#RX64]\n", off, cbToWrite, pDataAttr->cbValue));
+ rc = VERR_EOF;
+ if (pcbWritten)
+ *pcbWritten = 0;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = (RTFOFF)pThis->pShared->pData->cbValue + offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNew = (RTFOFF)pThis->offFile + offSeek;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ if (offNew >= 0)
+ {
+ pThis->offFile = offNew;
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+ }
+ return VERR_NEGATIVE_SEEK;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSNTFSFILE pThis = (PRTFSNTFSFILE)pvThis;
+ *pcbFile = pThis->pShared->pData->cbValue;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ NOREF(pvThis); NOREF(cbFile); NOREF(fFlags);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtFsNtfsFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = INT64_MAX;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * NTFS file operations.
+ */
+static const RTVFSFILEOPS g_rtFsNtfsFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "NTFS File",
+ rtFsNtfsFile_Close,
+ rtFsNtfsFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsNtfsFile_Read,
+ rtFsNtfsFile_Write,
+ rtFsNtfsFile_Flush,
+ NULL /*PollOne*/,
+ rtFsNtfsFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtFsNtfsFile_SetMode,
+ rtFsNtfsFile_SetTimes,
+ rtFsNtfsFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsNtfsFile_Seek,
+ rtFsNtfsFile_QuerySize,
+ rtFsNtfsFile_SetSize,
+ rtFsNtfsFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+static int rtFsNtfsVol_NewFile(PRTFSNTFSVOL pThis, uint64_t fOpen, PCNTFSIDXENTRYHDR pEntryHdr, const char *pszStreamName,
+ PRTVFSFILE phVfsFile, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ /*
+ * Get the core structure for the MFT record and check that it's a directory we've got.
+ */
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_QueryCoreForMftRef(pThis, &pEntryHdr->u.FileMftRec, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (!(pCore->pMftRec->pFileRec->fFlags & NTFSRECFILE_F_DIRECTORY))
+ {
+ /*
+ * Locate the data attribute.
+ */
+ PRTFSNTFSATTR pDataAttr;
+ if (pszStreamName == NULL)
+ {
+ pDataAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_DATA);
+ if (pDataAttr)
+ rc = VINF_SUCCESS;
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_FILE, "%s: no unamed data stream", pszWhat);
+ }
+ else
+ {
+ NOREF(pszStreamName);
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_IMPLEMENTED, "%s: named data streams not implemented yet", pszWhat);
+ pDataAttr = NULL;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Get a referenced shared file structure, creating it if necessary.
+ */
+ PRTFSNTFSFILESHRD pShared = pDataAttr->uObj.pSharedFile;
+ if (pShared)
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pShared->cRefs);
+ Assert(cRefs > 1); NOREF(cRefs);
+ }
+ else
+ {
+ pShared = (PRTFSNTFSFILESHRD)RTMemAllocZ(sizeof(*pShared));
+ if (pShared)
+ {
+ pShared->cRefs = 1;
+ pShared->pData = pDataAttr;
+ rtFsNtfsCore_Retain(pCore);
+ pDataAttr->uObj.pSharedFile = pShared;
+ }
+ }
+ if (pShared)
+ {
+ /*
+ * Create the open file instance.
+ */
+ PRTFSNTFSFILE pNewFile;
+ rc = RTVfsNewFile(&g_rtFsNtfsFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ pNewFile->offFile = 0;
+ pNewFile->pShared = pShared;
+ rtFsNtfsCore_Release(pCore);
+ return VINF_SUCCESS;
+ }
+
+ rtFsNtfsFileShrd_Release(pShared);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_FILE, "%s: fFlags=%#x", pszWhat, pCore->pMftRec->pFileRec->fFlags);
+ rtFsNtfsCore_Release(pCore);
+ }
+ return rc;
+}
+
+
+
+/*
+ *
+ * NTFS directory code.
+ * NTFS directory code.
+ * NTFS directory code.
+ *
+ */
+
+#ifdef LOG_ENABLED
+
+/**
+ * Logs an index header and all the entries.
+ *
+ * @param pIdxHdr The index header.
+ * @param cbIndex The number of valid bytes starting with the header.
+ * @param offIndex The offset of the index header into the parent
+ * structure.
+ * @param pszPrefix The log prefix.
+ * @param uIdxType The index type.
+ */
+static void rtFsNtfsVol_LogIndexHdrAndEntries(PCNTFSINDEXHDR pIdxHdr, uint32_t cbIndex, uint32_t offIndex,
+ const char *pszPrefix, uint32_t uIdxType)
+{
+ if (!LogIs2Enabled())
+ return;
+
+ /*
+ * Do the header.
+ */
+ if (cbIndex <= sizeof(*pIdxHdr))
+ {
+ Log2(("NTFS: %s: Error! Not enough space for the index header! cbIndex=%#x, index head needs %#x\n",
+ pszPrefix, cbIndex, sizeof(*pIdxHdr)));
+ return;
+ }
+
+ Log2(("NTFS: %s: offFirstEntry %#x%s\n", pszPrefix, RT_LE2H_U32(pIdxHdr->offFirstEntry),
+ RT_LE2H_U32(pIdxHdr->offFirstEntry) >= cbIndex ? " !out-of-bounds!" : ""));
+ Log2(("NTFS: %s: cbUsed %#x%s\n", pszPrefix, RT_LE2H_U32(pIdxHdr->cbUsed),
+ RT_LE2H_U32(pIdxHdr->cbUsed) > cbIndex ? " !out-of-bounds!" : ""));
+ Log2(("NTFS: %s: cbAllocated %#x%s\n", pszPrefix, RT_LE2H_U32(pIdxHdr->cbAllocated),
+ RT_LE2H_U32(pIdxHdr->cbAllocated) > cbIndex ? " !out-of-bounds!" : ""));
+ Log2(("NTFS: %s: fFlags %#x (%s%s)\n", pszPrefix, pIdxHdr->fFlags,
+ pIdxHdr->fFlags & NTFSINDEXHDR_F_INTERNAL ? "internal" : "leaf",
+ pIdxHdr->fFlags & ~NTFSINDEXHDR_F_INTERNAL ? " !!unknown-flags!!" : ""));
+ if (pIdxHdr->abReserved[0]) Log2(("NTFS: %s: abReserved[0] %#x\n", pszPrefix, pIdxHdr->abReserved[0]));
+ if (pIdxHdr->abReserved[1]) Log2(("NTFS: %s: abReserved[0] %#x\n", pszPrefix, pIdxHdr->abReserved[1]));
+ if (pIdxHdr->abReserved[2]) Log2(("NTFS: %s: abReserved[0] %#x\n", pszPrefix, pIdxHdr->abReserved[2]));
+
+ /*
+ * The entries.
+ */
+ bool fSeenEnd = false;
+ uint32_t iEntry = 0;
+ uint32_t offCurEntry = RT_LE2H_U32(pIdxHdr->offFirstEntry);
+ while (offCurEntry < cbIndex)
+ {
+ if (offCurEntry + sizeof(NTFSIDXENTRYHDR) > cbIndex)
+ {
+ Log2(("NTFS: Entry[%#04x]: Out of bounds: %#x LB %#x, max %#x\n",
+ iEntry, offCurEntry, sizeof(NTFSIDXENTRYHDR), cbIndex));
+ break;
+ }
+ PCNTFSIDXENTRYHDR pEntryHdr = (PCNTFSIDXENTRYHDR)((uint8_t const *)pIdxHdr + offCurEntry);
+ Log2(("NTFS: [%#04x]: @%#05x/@%#05x cbEntry=%#x cbKey=%#x fFlags=%#x (%s%s%s)\n",
+ iEntry, offCurEntry, offCurEntry + offIndex, RT_LE2H_U16(pEntryHdr->cbEntry), RT_LE2H_U16(pEntryHdr->cbKey),
+ RT_LE2H_U16(pEntryHdr->fFlags),
+ pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_INTERNAL ? "internal" : "leaf",
+ pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END ? " end" : "",
+ pEntryHdr->fFlags & ~(NTFSIDXENTRYHDR_F_INTERNAL | NTFSIDXENTRYHDR_F_END) ? " !unknown!" : ""));
+ if (uIdxType == NTFSATINDEXROOT_TYPE_DIR)
+ Log2(("NTFS: FileMftRec %#RX64 sqn %#x\n",
+ NTFSMFTREF_GET_IDX(&pEntryHdr->u.FileMftRec), NTFSMFTREF_GET_SEQ(&pEntryHdr->u.FileMftRec) ));
+ else
+ Log2(("NTFS: offData=%#x cbData=%#x uReserved=%#x\n",
+ RT_LE2H_U16(pEntryHdr->u.View.offData), RT_LE2H_U16(pEntryHdr->u.View.cbData),
+ RT_LE2H_U32(pEntryHdr->u.View.uReserved) ));
+ if (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ Log2(("NTFS: Subnode=%#RX64\n", RT_LE2H_U64(NTFSIDXENTRYHDR_GET_SUBNODE(pEntryHdr)) ));
+
+ if ( RT_LE2H_U16(pEntryHdr->cbKey) >= RT_UOFFSETOF(NTFSATFILENAME, wszFilename)
+ && uIdxType == NTFSATINDEXROOT_TYPE_DIR)
+ {
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)(pEntryHdr + 1);
+ RTTIMESPEC Spec;
+ char sz[80];
+ Log2(("NTFS: iCreationTime %#RX64 %s\n", RT_LE2H_U64(pFilename->iCreationTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pFilename->iCreationTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastDataModTime %#RX64 %s\n", RT_LE2H_U64(pFilename->iLastDataModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pFilename->iLastDataModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastMftModTime %#RX64 %s\n", RT_LE2H_U64(pFilename->iLastMftModTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pFilename->iLastMftModTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: iLastAccessTime %#RX64 %s\n", RT_LE2H_U64(pFilename->iLastAccessTime),
+ RTTimeSpecToString(RTTimeSpecSetNtTime(&Spec, RT_LE2H_U64(pFilename->iLastAccessTime)), sz, sizeof(sz)) ));
+ Log2(("NTFS: cbAllocated %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pFilename->cbAllocated), RT_LE2H_U64(pFilename->cbAllocated)));
+ Log2(("NTFS: cbData %#RX64 (%Rhcb)\n",
+ RT_LE2H_U64(pFilename->cbData), RT_LE2H_U64(pFilename->cbData)));
+ Log2(("NTFS: fFileAttribs %#RX32\n", RT_LE2H_U32(pFilename->fFileAttribs) ));
+ if (RT_LE2H_U32(pFilename->fFileAttribs) & NTFS_FA_REPARSE_POINT)
+ Log2(("NTFS: uReparseTag %#RX32\n", RT_LE2H_U32(pFilename->u.uReparseTag) ));
+ else
+ Log2(("NTFS: cbPackedEas %#RX16\n", RT_LE2H_U16(pFilename->u.cbPackedEas) ));
+ Log2(("NTFS: cwcFilename %#x\n", pFilename->cwcFilename));
+ Log2(("NTFS: fFilenameType %#x\n", pFilename->fFilenameType));
+ if (RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pFilename->cwcFilename]) <= RT_LE2H_U16(pEntryHdr->cbKey))
+ Log2(("NTFS: wszFilename '%.*ls'\n", pFilename->cwcFilename, pFilename->wszFilename ));
+ else
+ Log2(("NTFS: Error! Truncated filename!!\n"));
+ }
+
+
+ /* next */
+ iEntry++;
+ offCurEntry += RT_LE2H_U16(pEntryHdr->cbEntry);
+ fSeenEnd = RT_BOOL(pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END);
+ if (fSeenEnd || RT_LE2H_U16(pEntryHdr->cbEntry) < sizeof(*pEntryHdr))
+ break;
+ }
+ if (!fSeenEnd)
+ Log2(("NTFS: %s: Warning! Missing NTFSIDXENTRYHDR_F_END node!\n", pszPrefix));
+}
+
+# if 0 /* unused */
+static void rtFsNtfsVol_LogIndexNode(PCNTFSATINDEXALLOC pIdxNode, uint32_t cbIdxNode, uint32_t uType)
+{
+ if (!LogIs2Enabled())
+ return;
+ if (cbIdxNode < sizeof(*pIdxNode))
+ Log2(("NTFS: Index Node: Error! Too small! cbIdxNode=%#x, index node needs %#x\n", cbIdxNode, sizeof(*pIdxNode)));
+ else
+ {
+ Log2(("NTFS: Index Node: uMagic %#x\n", RT_LE2H_U32(pIdxNode->RecHdr.uMagic)));
+ Log2(("NTFS: Index Node: UpdateSeqArray %#x L %#x\n",
+ RT_LE2H_U16(pIdxNode->RecHdr.offUpdateSeqArray), RT_LE2H_U16(pIdxNode->RecHdr.cUpdateSeqEntries) ));
+ Log2(("NTFS: Index Node: uLsn %#RX64\n", RT_LE2H_U64(pIdxNode->uLsn) ));
+ Log2(("NTFS: Index Node: iSelfAddress %#RX64\n", RT_LE2H_U64(pIdxNode->iSelfAddress) ));
+ if (pIdxNode->RecHdr.uMagic == NTFSREC_MAGIC_INDEX_ALLOC)
+ rtFsNtfsVol_LogIndexHdrAndEntries(&pIdxNode->Hdr, cbIdxNode - RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr),
+ RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr), "Index Node Hdr", uType);
+ else
+ Log2(("NTFS: Index Node: !Error! Invalid magic!\n"));
+ }
+}
+# endif
+
+/**
+ * Logs a index root structure and what follows (index header + entries).
+ *
+ * @param pIdxRoot The index root.
+ * @param cbIdxRoot Number of valid bytes starting with @a pIdxRoot.
+ */
+static void rtFsNtfsVol_LogIndexRoot(PCNTFSATINDEXROOT pIdxRoot, uint32_t cbIdxRoot)
+{
+ if (!LogIs2Enabled())
+ return;
+ if (cbIdxRoot < sizeof(*pIdxRoot))
+ Log2(("NTFS: Index Root: Error! Too small! cbIndex=%#x, index head needs %#x\n", cbIdxRoot, sizeof(*pIdxRoot)));
+ else
+ {
+ Log2(("NTFS: Index Root: cbIdxRoot %#x\n", cbIdxRoot));
+ Log2(("NTFS: Index Root: uType %#x %s\n", RT_LE2H_U32(pIdxRoot->uType),
+ pIdxRoot->uType == NTFSATINDEXROOT_TYPE_VIEW ? "view"
+ : pIdxRoot->uType == NTFSATINDEXROOT_TYPE_DIR ? "directory" : "!unknown!"));
+ Log2(("NTFS: Index Root: uCollationRules %#x %s\n", RT_LE2H_U32(pIdxRoot->uCollationRules),
+ pIdxRoot->uCollationRules == NTFS_COLLATION_BINARY ? "binary"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_FILENAME ? "filename"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_UNICODE_STRING ? "unicode-string"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_UINT32 ? "uint32"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_SID ? "sid"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_UINT32_PAIR ? "uint32-pair"
+ : pIdxRoot->uCollationRules == NTFS_COLLATION_UINT32_SEQ ? "uint32-sequence" : "!unknown!"));
+ Log2(("NTFS: Index Root: cbIndexNode %#x\n", RT_LE2H_U32(pIdxRoot->cbIndexNode) ));
+ Log2(("NTFS: Index Root: cAddressesPerIndexNode %#x => cbNodeAddressingUnit=%#x\n",
+ pIdxRoot->cAddressesPerIndexNode, RT_LE2H_U32(pIdxRoot->cbIndexNode) / RT_MAX(1, pIdxRoot->cAddressesPerIndexNode) ));
+ if (pIdxRoot->abReserved[0]) Log2(("NTFS: Index Root: abReserved[0] %#x\n", pIdxRoot->abReserved[0]));
+ if (pIdxRoot->abReserved[1]) Log2(("NTFS: Index Root: abReserved[1] %#x\n", pIdxRoot->abReserved[1]));
+ if (pIdxRoot->abReserved[2]) Log2(("NTFS: Index Root: abReserved[2] %#x\n", pIdxRoot->abReserved[2]));
+
+ rtFsNtfsVol_LogIndexHdrAndEntries(&pIdxRoot->Hdr, cbIdxRoot - RT_UOFFSETOF(NTFSATINDEXROOT, Hdr),
+ RT_UOFFSETOF(NTFSATINDEXROOT, Hdr), "Index Root Hdr", pIdxRoot->uType);
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+
+/**
+ * Validates an index header.
+ *
+ * @returns IPRT status code.
+ * @param pRootInfo Pointer to the index root info.
+ * @param pNodeInfo Pointer to the node info structure to load.
+ * @param pIndexHdr Pointer to the index header.
+ * @param cbIndex Size of the index.
+ * @param pErrInfo Where to return extra error info.
+ * @param pszWhat Error prefix.
+ */
+static int rtFsNtfsVol_LoadIndexNodeInfo(PCRTFSNTFSIDXROOTINFO pRootInfo, PRTFSNTFSIDXNODEINFO pNodeInfo, PCNTFSINDEXHDR pIndexHdr,
+ uint32_t cbIndex, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ uint32_t const cbMinIndex = sizeof(*pIndexHdr) + sizeof(NTFSIDXENTRYHDR);
+ if (cbIndex < cbMinIndex)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Not enough room for the index header and one entry header! cbIndex=%#x (cbMinIndex=%#x)",
+ pszWhat, cbIndex, cbMinIndex);
+ uint32_t const cbAllocated = RT_LE2H_U32(pIndexHdr->cbAllocated);
+ if ( cbAllocated > cbIndex
+ || cbAllocated < cbMinIndex
+ || (cbAllocated & 7) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus index allocation size: %#x (min %#x, max %#x, 8 byte aligned)",
+ pszWhat, cbAllocated, cbMinIndex, cbIndex);
+ uint32_t const cbUsed = RT_LE2H_U32(pIndexHdr->cbUsed);
+ if ( cbUsed > cbAllocated
+ || cbUsed < cbMinIndex
+ || (cbUsed & 7) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus index used size: %#x (min %#x, max %#x, 8 byte aligned)",
+ pszWhat, cbUsed, cbMinIndex, cbAllocated);
+ uint32_t const offFirstEntry = RT_LE2H_U32(pIndexHdr->offFirstEntry);
+ if ( offFirstEntry < sizeof(*pIndexHdr)
+ || ( offFirstEntry > cbUsed - sizeof(NTFSIDXENTRYHDR)
+ && offFirstEntry != cbUsed /* empty dir */)
+ || (offFirstEntry & 7) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus first entry offset: %#x (min %#x, max %#x, 8 byte aligned)",
+ pszWhat, offFirstEntry, sizeof(*pIndexHdr), cbUsed - sizeof(NTFSIDXENTRYHDR));
+
+ /*
+ * The index entries.
+ */
+ uint32_t const uType = pRootInfo->pRoot->uType;
+ uint32_t offEntry = offFirstEntry;
+ uint32_t iEntry = 0;
+ for (;;)
+ {
+ if (offEntry + sizeof(NTFSIDXENTRYHDR) > cbUsed)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u is out of bound: offset %#x (cbUsed=%#x)",
+ pszWhat, iEntry, offEntry, cbUsed);
+ PCNTFSIDXENTRYHDR pEntryHdr = (PCNTFSIDXENTRYHDR)((uint8_t const *)pIndexHdr + offEntry);
+ uint16_t const cbEntry = RT_LE2H_U16(pEntryHdr->cbEntry);
+ uint32_t const cbSubnodeAddr = (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_INTERNAL ? sizeof(int64_t) : 0);
+ uint32_t const cbMinEntry = sizeof(*pEntryHdr) + cbSubnodeAddr;
+ if ( cbEntry < cbMinEntry
+ || offEntry + cbEntry > cbUsed
+ || (cbEntry & 7) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u has a bogus size: %#x (min %#x, max %#x, 8 byte aligned)",
+ pszWhat, iEntry, cbEntry, cbMinEntry, cbUsed - offEntry);
+
+ uint32_t const cbMaxKey = cbEntry - sizeof(*pEntryHdr) - cbSubnodeAddr;
+ uint32_t const cbMinKey = (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END) ? 0
+ : uType == NTFSATINDEXROOT_TYPE_DIR ? RT_UOFFSETOF(NTFSATFILENAME, wszFilename) : 0;
+ uint16_t const cbKey = RT_LE2H_U16(pEntryHdr->cbKey);
+ if ( cbKey < cbMinKey
+ || cbKey > cbMaxKey)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u has a bogus key size: %#x (min %#x, max %#x)",
+ pszWhat, iEntry, cbKey, cbMinKey, cbMaxKey);
+ if ( !(pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END)
+ && uType == NTFSATINDEXROOT_TYPE_DIR)
+ {
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)(pEntryHdr + 1);
+ if (RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pFilename->cwcFilename]) > cbKey)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u filename is out of bounds: cwcFilename=%#x -> %#x key, max %#x",
+ pszWhat, iEntry, pFilename->cwcFilename,
+ RT_UOFFSETOF_DYN(NTFSATFILENAME, wszFilename[pFilename->cwcFilename]), cbKey);
+ }
+
+ if (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ {
+ int64_t iSubnode = NTFSIDXENTRYHDR_GET_SUBNODE(pEntryHdr);
+ if ( (uint64_t)iSubnode >= pRootInfo->uEndNodeAddresses
+ || (iSubnode & pRootInfo->fNodeAddressMisalign) )
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Entry #%u has bogus subnode address: %#RX64 (max %#RX64, misalign %#x)",
+ pszWhat, iEntry, iSubnode, pRootInfo->uEndNodeAddresses,
+ pRootInfo->fNodeAddressMisalign);
+ }
+
+ /* Advance. */
+ offEntry += cbEntry;
+ iEntry++;
+ if (pEntryHdr->fFlags & NTFSIDXENTRYHDR_F_END)
+ break;
+ }
+
+ /*
+ * Popuplate the node info structure.
+ */
+ pNodeInfo->pIndexHdr = pIndexHdr;
+ pNodeInfo->fInternal = RT_BOOL(pIndexHdr->fFlags & NTFSINDEXHDR_F_INTERNAL);
+ if (pNodeInfo != &pRootInfo->NodeInfo)
+ pNodeInfo->pVol = pRootInfo->NodeInfo.pVol;
+ pNodeInfo->cEntries = iEntry;
+ pNodeInfo->papEntries = (PCNTFSIDXENTRYHDR *)RTMemAlloc(iEntry * sizeof(pNodeInfo->papEntries[0]));
+ if (pNodeInfo->papEntries)
+ {
+ PCNTFSIDXENTRYHDR pEntryHdr = NTFSINDEXHDR_GET_FIRST_ENTRY(pIndexHdr);
+ for (iEntry = 0; iEntry < pNodeInfo->cEntries; iEntry++)
+ {
+ pNodeInfo->papEntries[iEntry] = pEntryHdr;
+ pEntryHdr = NTFSIDXENTRYHDR_GET_NEXT(pEntryHdr);
+ }
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Creates a shared directory structure given a MFT core.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pCore The MFT core structure that's allegedly a directory.
+ * (No reference consumed of course.)
+ * @param ppSharedDir Where to return the pointer to the new shared directory
+ * structure on success. (Referenced.)
+ * @param pErrInfo Where to return additions error info. Optional.
+ * @param pszWhat Context prefix for error reporting and logging.
+ */
+static int rtFsNtfsVol_NewSharedDirFromCore(PRTFSNTFSVOL pThis, PRTFSNTFSCORE pCore, PRTFSNTFSDIRSHRD *ppSharedDir,
+ PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ *ppSharedDir = NULL;
+
+ /*
+ * Look for the index root and validate it.
+ */
+ PRTFSNTFSATTR pRootAttr = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ROOT,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ if (!pRootAttr)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "%s: Found no INDEX_ROOT attribute named $I30", pszWhat);
+ if (pRootAttr->pAttrHdr->fNonResident)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "%s: INDEX_ROOT is is not resident", pszWhat);
+ if (pRootAttr->cbResident < sizeof(NTFSATINDEXROOT))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "%s: INDEX_ROOT is too small: %#x, min %#x ",
+ pszWhat, pRootAttr->cbResident, sizeof(pRootAttr->cbResident));
+
+ PCNTFSATINDEXROOT pIdxRoot = (PCNTFSATINDEXROOT)NTFSATTRIBHDR_GET_RES_VALUE_PTR(pRootAttr->pAttrHdr);
+#ifdef LOG_ENABLED
+ rtFsNtfsVol_LogIndexRoot(pIdxRoot, pRootAttr->cbResident);
+#endif
+ if (pIdxRoot->uType != NTFSATINDEXROOT_TYPE_DIR)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Wrong INDEX_ROOT type for a directory: %#x, expected %#x",
+ pszWhat, RT_LE2H_U32(pIdxRoot->uType), RT_LE2H_U32_C(NTFSATINDEXROOT_TYPE_DIR));
+ if (pIdxRoot->uCollationRules != NTFS_COLLATION_FILENAME)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Wrong collation rules for a directory: %#x, expected %#x",
+ pszWhat, RT_LE2H_U32(pIdxRoot->uCollationRules), RT_LE2H_U32_C(NTFS_COLLATION_FILENAME));
+ uint32_t cbIndexNode = RT_LE2H_U32(pIdxRoot->cbIndexNode);
+ if (cbIndexNode < 512 || cbIndexNode > _64K || !RT_IS_POWER_OF_TWO(cbIndexNode))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus index node size: %#x (expected power of two between 512 and 64KB)",
+ pszWhat, cbIndexNode);
+ unsigned const cNodeAddressShift = cbIndexNode >= pThis->cbCluster ? pThis->cClusterShift : 9;
+ if (((uint32_t)pIdxRoot->cAddressesPerIndexNode << cNodeAddressShift) != cbIndexNode)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: Bogus addresses per index node value: %#x (cbIndexNode=%#x cNodeAddressShift=%#x)",
+ pszWhat, pIdxRoot->cAddressesPerIndexNode, cbIndexNode, cNodeAddressShift);
+ AssertReturn(pRootAttr->uObj.pSharedDir == NULL, VERR_INTERNAL_ERROR_3);
+
+ /*
+ * Check for the node data stream and related allocation bitmap.
+ */
+ PRTFSNTFSATTR pIndexAlloc = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ALLOCATION,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ PRTFSNTFSATTR pIndexBitmap = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_BITMAP,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ if (pIndexAlloc && !pIndexBitmap)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: INDEX_ALLOCATION attribute without BITMAP", pszWhat);
+ if (!pIndexAlloc && pIndexBitmap)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: BITMAP attribute without INDEX_ALLOCATION", pszWhat);
+ uint64_t uNodeAddressEnd = 0;
+ if (pIndexAlloc)
+ {
+ if (!pIndexAlloc->pAttrHdr->fNonResident)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT, "%s: INDEX_ALLOCATION is resident", pszWhat);
+ if (pIndexAlloc->cbValue & (cbIndexNode - 1))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: INDEX_ALLOCATION size isn't aligned on node boundrary: %#RX64, cbIndexNode=%#x",
+ pszWhat, pIndexAlloc->cbValue, cbIndexNode);
+ uint64_t const cNodes = pIndexAlloc->cbValue / cbIndexNode;
+ if (pIndexBitmap->cbValue < (RT_ALIGN_64(cNodes, 64) >> 3))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "%s: BITMAP size does not match INDEX_ALLOCATION: %#RX64, expected min %#RX64 (cbIndexNode=%#x, cNodes=%#RX64)",
+ pszWhat, pIndexBitmap->cbValue, RT_ALIGN_64(cNodes, 64) >> 3, cbIndexNode, cNodes);
+ uNodeAddressEnd = cNodes * pIdxRoot->cAddressesPerIndexNode;
+ }
+
+ /*
+ * Create a directory instance.
+ */
+ PRTFSNTFSDIRSHRD pNewDir = (PRTFSNTFSDIRSHRD)RTMemAllocZ(sizeof(*pNewDir));
+ if (!pNewDir)
+ return VERR_NO_MEMORY;
+
+ pNewDir->cRefs = 1;
+ rtFsNtfsCore_Retain(pCore);
+ pNewDir->RootInfo.pRootAttr = pRootAttr;
+ pNewDir->RootInfo.pRoot = pIdxRoot;
+ pNewDir->RootInfo.pAlloc = pIndexAlloc;
+ pNewDir->RootInfo.uEndNodeAddresses = uNodeAddressEnd;
+ pNewDir->RootInfo.cNodeAddressByteShift = cNodeAddressShift;
+ pNewDir->RootInfo.fNodeAddressMisalign = pIdxRoot->cAddressesPerIndexNode - 1;
+ pNewDir->RootInfo.NodeInfo.pVol = pThis;
+
+ /*
+ * Finally validate the index header and entries.
+ */
+ int rc = rtFsNtfsVol_LoadIndexNodeInfo(&pNewDir->RootInfo, &pNewDir->RootInfo.NodeInfo, &pIdxRoot->Hdr,
+ pRootAttr->cbResident - RT_UOFFSETOF(NTFSATINDEXROOT, Hdr), pErrInfo, pszWhat);
+ if (RT_SUCCESS(rc))
+ {
+ *ppSharedDir = pNewDir;
+ pRootAttr->uObj.pSharedDir = pNewDir;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pNewDir);
+ rtFsNtfsCore_Release(pCore);
+ return rc;
+}
+
+
+/**
+ * Gets a shared directory structure given an MFT record reference, creating a
+ * new one if necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pDirMftRef The MFT record reference to follow.
+ * @param ppSharedDir Where to return the shared directory structure
+ * (referenced).
+ * @param pErrInfo Where to return error details. Optional.
+ * @param pszWhat Error/log prefix.
+ */
+static int rtFsNtfsVol_QueryOrCreateSharedDirByMftRef(PRTFSNTFSVOL pThis, PCNTFSMFTREF pDirMftRef,
+ PRTFSNTFSDIRSHRD *ppSharedDir, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ /*
+ * Get the core structure for the MFT record and check that it's a directory we've got.
+ */
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_QueryCoreForMftRef(pThis, pDirMftRef, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (pCore->pMftRec->pFileRec->fFlags & NTFSRECFILE_F_DIRECTORY)
+ {
+ /*
+ * Locate the $I30 root index attribute as we associate the
+ * pointer to the shared directory pointer with it.
+ */
+ PRTFSNTFSATTR pRootAttr = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ROOT,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ if (pRootAttr)
+ {
+ if (!pRootAttr->uObj.pSharedDir)
+ rc = rtFsNtfsVol_NewSharedDirFromCore(pThis, pCore, ppSharedDir, pErrInfo, pszWhat);
+ else
+ {
+ Assert(pRootAttr->uObj.pSharedDir->RootInfo.pRootAttr->pCore == pCore);
+ rtFsNtfsDirShrd_Retain(pRootAttr->uObj.pSharedDir);
+ *ppSharedDir = pRootAttr->uObj.pSharedDir;
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_NOT_A_DIRECTORY,
+ "%s: Found INDEX_ROOT attribute named $I30, even though NTFSRECFILE_F_DIRECTORY is set",
+ pszWhat);
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_DIRECTORY, "%s: fFlags=%#x", pszWhat, pCore->pMftRec->pFileRec->fFlags);
+ rtFsNtfsCore_Release(pCore);
+ }
+ return rc;
+}
+
+
+/**
+ * Frees resource kept by an index node info structure.
+ *
+ * @param pNodeInfo The index node info structure to delelte.
+ */
+static void rtFsNtfsIdxNodeInfo_Delete(PRTFSNTFSIDXNODEINFO pNodeInfo)
+{
+ RTMemFree(pNodeInfo->papEntries);
+ pNodeInfo->papEntries = NULL;
+ pNodeInfo->pNode = NULL;
+ pNodeInfo->pVol = NULL;
+}
+
+
+/**
+ * Gets or loads the specified subnode.
+ *
+ * @returns IPRT status code.
+ * @param pRootInfo The index root info.
+ * @param iNode The address of the node being queried.
+ * @param ppNode Where to return the referenced pointer to the node.
+ */
+static int rtFsNtfsIdxRootInfo_QueryNode(PRTFSNTFSIDXROOTINFO pRootInfo, int64_t iNode, PRTFSNTFSIDXNODE *ppNode)
+{
+ PRTFSNTFSVOL pVol = pRootInfo->NodeInfo.pVol;
+
+ /*
+ * A bit of paranoia. These has been checked already when loading, but it
+ * usually doesn't hurt too much to be careful.
+ */
+ AssertReturn(!(iNode & pRootInfo->fNodeAddressMisalign), VERR_VFS_BOGUS_OFFSET);
+ AssertReturn((uint64_t)iNode < pRootInfo->uEndNodeAddresses, VERR_VFS_BOGUS_OFFSET);
+ AssertReturn(pRootInfo->pAlloc, VERR_VFS_BOGUS_OFFSET);
+
+ /*
+ * First translate the node address to a disk byte offset and check the index node cache.
+ */
+ uint64_t offNode = iNode << pRootInfo->cNodeAddressByteShift;
+ uint64_t offNodeOnDisk = rtFsNtfsAttr_OffsetToDisk(pRootInfo->pAlloc, offNode, NULL);
+ PRTFSNTFSIDXNODE pNode = (PRTFSNTFSIDXNODE)RTAvlU64Get(&pVol->IdxNodeCacheRoot, offNodeOnDisk);
+ if (pNode)
+ {
+ rtFsNtfsIdxNode_Retain(pNode);
+ *ppNode = pNode;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Need to create a load a new node.
+ */
+ pNode = (PRTFSNTFSIDXNODE)RTMemAllocZ(sizeof(*pNode));
+ AssertReturn(pNode, VERR_NO_MEMORY);
+
+ pNode->TreeNode.Key = offNodeOnDisk;
+ uint32_t cbIndexNode = RT_LE2H_U32(pRootInfo->pRoot->cbIndexNode);
+ pNode->cbCost = sizeof(*pNode) + cbIndexNode;
+ pNode->cRefs = 1;
+ pNode->pNode = (PNTFSATINDEXALLOC)RTMemAllocZ(cbIndexNode);
+ int rc;
+ if (pNode->pNode)
+ {
+ rc = rtFsNtfsAttr_Read(pRootInfo->pAlloc, offNode, pNode->pNode, cbIndexNode);
+ if (RT_SUCCESS(rc))
+ {
+ rc = VERR_VFS_BOGUS_FORMAT;
+ if (pNode->pNode->RecHdr.uMagic != NTFSREC_MAGIC_INDEX_ALLOC)
+ LogRel(("rtFsNtfsIdxRootInfo_QueryNode(iNode=%#x): Invalid node magic %#x -> VERR_VFS_BOGUS_FORMAT\n",
+ iNode, RT_LE2H_U32(pNode->pNode->RecHdr.uMagic) ));
+ else if ((int64_t)RT_LE2H_U64(pNode->pNode->iSelfAddress) != iNode)
+ LogRel(("rtFsNtfsIdxRootInfo_QueryNode(iNode=%#x): Wrong iSelfAddress: %#x -> VERR_VFS_BOGUS_FORMAT\n",
+ iNode, RT_LE2H_U64(pNode->pNode->iSelfAddress) ));
+ else
+ {
+ rc = rtFsNtfsRec_DoMultiSectorFixups(&pNode->pNode->RecHdr, cbIndexNode, false /*fRelaxedUsa*/, NULL /*pErrInfo*/);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Validate/parse it
+ */
+#ifdef LOG_ENABLED
+ rtFsNtfsVol_LogIndexHdrAndEntries(&pNode->pNode->Hdr,
+ cbIndexNode - RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr),
+ RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr), "index node",
+ pRootInfo->pRoot->uType);
+#endif
+ rc = rtFsNtfsVol_LoadIndexNodeInfo(pRootInfo, &pNode->NodeInfo, &pNode->pNode->Hdr,
+ cbIndexNode - RT_UOFFSETOF(NTFSATINDEXALLOC, Hdr),
+ NULL /*pErrInfo*/, "index node");
+ if (RT_SUCCESS(rc))
+ {
+ pNode->cbCost += pNode->NodeInfo.cEntries * sizeof(pNode->NodeInfo.papEntries[0]);
+
+ /*
+ * Insert it into the cache, trimming the cache if necessary.
+ */
+ bool fInsertOkay = RTAvlU64Insert(&pVol->IdxNodeCacheRoot, &pNode->TreeNode);
+ Assert(fInsertOkay);
+ if (fInsertOkay)
+ {
+ pVol->cIdxNodes += 1;
+ pVol->cbIdxNodes += pNode->cbCost;
+ if (pVol->cbIdxNodes > RTFSNTFS_MAX_CORE_CACHE_SIZE)
+ rtFsNtfsIdxVol_TrimIndexNodeCache(pVol);
+
+ *ppNode = pNode;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ }
+
+ RTMemFree(pNode->pNode);
+ pNode->pNode = NULL;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ RTMemFree(pNode);
+ return rc;
+}
+
+
+/**
+ * Frees resource kept by an index root info structure.
+ *
+ * @param pRootInfo The index root info structure to delete.
+ */
+static void rtFsNtfsIdxRootInfo_Delete(PRTFSNTFSIDXROOTINFO pRootInfo)
+{
+ rtFsNtfsIdxNodeInfo_Delete(&pRootInfo->NodeInfo);
+ pRootInfo->pRootAttr->uObj.pSharedDir = NULL;
+ rtFsNtfsCore_Release(pRootInfo->pRootAttr->pCore);
+ pRootInfo->pRootAttr = NULL;
+ pRootInfo->pAlloc = NULL;
+ pRootInfo->pRoot = NULL;
+}
+
+
+/**
+ * Destroys a shared directory structure when the reference count reached zero.
+ *
+ * @returns zero
+ * @param pThis The shared directory structure to destroy.
+ */
+static uint32_t rtFsNtfsDirShrd_Destroy(PRTFSNTFSDIRSHRD pThis)
+{
+ rtFsNtfsIdxRootInfo_Delete(&pThis->RootInfo);
+ RTMemFree(pThis);
+ return 0;
+}
+
+
+/**
+ * Releases a references to a shared directory structure.
+ *
+ * @returns New reference count.
+ * @param pThis The shared directory structure.
+ */
+static uint32_t rtFsNtfsDirShrd_Release(PRTFSNTFSDIRSHRD pThis)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 4096);
+ if (cRefs > 0)
+ return cRefs;
+ return rtFsNtfsDirShrd_Destroy(pThis);
+}
+
+
+/**
+ * Retains a references to a shared directory structure.
+ *
+ * @returns New reference count.
+ * @param pThis The shared directory structure.
+ */
+static uint32_t rtFsNtfsDirShrd_Retain(PRTFSNTFSDIRSHRD pThis)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < 4096);
+ return cRefs;
+}
+
+
+/**
+ * Compares the two filenames in an case insentivie manner.
+ *
+ * @retval -1 if the first filename comes first
+ * @retval 0 if equal
+ * @retval 1 if the second filename comes first.
+ *
+ * @param pwszUpper1 The first filename, this has been uppercase already.
+ * @param cwcUpper1 The length of the first filename.
+ * @param pawcFilename2 The second filename to compare it with. Not zero
+ * terminated.
+ * @param cwcFilename2 The length of the second filename.
+ * @param pawcUpcase The uppercase table. 64K entries.
+ */
+static int rtFsNtfsIdxComp_Filename(PCRTUTF16 pwszUpper1, uint8_t cwcUpper1, PCRTUTF16 pawcFilename2, uint8_t cwcFilename2,
+ PCRTUTF16 const pawcUpcase)
+{
+ while (cwcUpper1 > 0 && cwcFilename2 > 0)
+ {
+ RTUTF16 uc1 = *pwszUpper1++;
+ RTUTF16 uc2 = *pawcFilename2++;
+ if (uc1 != uc2)
+ {
+ uc2 = pawcUpcase[uc2];
+ if (uc1 != uc2)
+ return uc1 < uc2 ? -1 : 1;
+ }
+
+ /* Decrement the lengths and loop. */
+ cwcUpper1--;
+ cwcFilename2--;
+ }
+
+ if (!cwcUpper1)
+ {
+ if (!cwcFilename2)
+ return 0;
+ return -1;
+ }
+ return 1;
+}
+
+
+/**
+ * Look up a name in the directory.
+ *
+ * @returns IPRT status code.
+ * @param pShared The shared directory structure.
+ * @param pszEntry The name to lookup.
+ * @param ppFilename Where to return the pointer to the filename structure.
+ * @param ppEntryHdr Where to return the poitner to the entry header
+ * structure.
+ * @param ppNode Where to return the pointer to the node the filename
+ * structure resides in. This must be released. It will
+ * be set to NULL if the name was found in the root node.
+ */
+static int rtFsNtfsDirShrd_Lookup(PRTFSNTFSDIRSHRD pShared, const char *pszEntry,
+ PCNTFSATFILENAME *ppFilename, PCNTFSIDXENTRYHDR *ppEntryHdr, PRTFSNTFSIDXNODE *ppNode)
+{
+ PRTFSNTFSVOL pVol = pShared->RootInfo.NodeInfo.pVol;
+
+ *ppFilename = NULL;
+ *ppEntryHdr = NULL;
+ *ppNode = NULL;
+ /** @todo do streams (split on ':') */
+
+ /*
+ * Convert the filename to UTF16 and uppercase.
+ */
+ PCRTUTF16 const pawcUpcase = pVol->pawcUpcase;
+ RTUTF16 wszFilename[256+4];
+ PRTUTF16 pwszDst = wszFilename;
+ PRTUTF16 pwszEnd = &wszFilename[255];
+ const char *pszSrc = pszEntry;
+ for (;;)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc != 0)
+ {
+ if (uc < _64K)
+ uc = pawcUpcase[uc];
+ pwszDst = RTUtf16PutCp(pwszDst, uc);
+ if ((uintptr_t)pwszDst <= (uintptr_t)pwszEnd)
+ { /* likely */ }
+ else
+ {
+ Log(("rtFsNtfsDirShrd_Lookup: Filename too long '%s'\n", pszEntry));
+ return VERR_FILENAME_TOO_LONG;
+ }
+ }
+ else
+ {
+ *pwszDst = '\0';
+ break;
+ }
+ }
+ else
+ {
+ Log(("rtFsNtfsDirShrd_Lookup: Invalid UTF-8 encoding (%Rrc): %.*Rhxs\n", rc, strlen(pszEntry), pszEntry));
+ return rc;
+ }
+ }
+ uint8_t const cwcFilename = (uint8_t)(pwszDst - wszFilename);
+
+ /*
+ * Do the tree traversal.
+ */
+ PRTFSNTFSIDXROOTINFO pRootInfo = &pShared->RootInfo;
+ PRTFSNTFSIDXNODEINFO pNodeInfo = &pRootInfo->NodeInfo;
+ PRTFSNTFSIDXNODE pNode = NULL;
+ for (;;)
+ {
+ /*
+ * Search it.
+ */
+ PCNTFSIDXENTRYHDR *papEntries = pNodeInfo->papEntries;
+ uint32_t iEnd = pNodeInfo->cEntries;
+ AssertReturn(iEnd > 0, VERR_INTERNAL_ERROR_3);
+
+ /* Exclude the end node from the serach as it doesn't have any key. */
+ if (papEntries[iEnd - 1]->fFlags & NTFSIDXENTRYHDR_F_END)
+ iEnd--;
+
+ uint32_t iEntry;
+ if (1 /*iEnd < 8*/ )
+ {
+ if (iEnd > 0)
+ {
+ for (iEntry = 0; iEntry < iEnd; iEntry++)
+ {
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)(papEntries[iEntry] + 1);
+ int iDiff = rtFsNtfsIdxComp_Filename(wszFilename, cwcFilename, pFilename->wszFilename,
+ pFilename->cwcFilename, pawcUpcase);
+ if (iDiff > 0)
+ { /* likely */ }
+ else if (iDiff == 0)
+ {
+ *ppNode = pNode;
+ *ppEntryHdr = papEntries[iEntry];
+ *ppFilename = pFilename;
+ LogFlow(("rtFsNtfsDirShrd_Lookup(%s): Found it! (iEntry=%u, FileMftRec=%#RX64 sqn %#x)\n",
+ pszEntry, iEntry, NTFSMFTREF_GET_IDX(&papEntries[iEntry]->u.FileMftRec),
+ NTFSMFTREF_GET_SEQ(&papEntries[iEntry]->u.FileMftRec) ));
+ return VINF_SUCCESS;
+ }
+ else
+ break;
+ }
+ }
+ else
+ iEntry = iEnd;
+ }
+ /* else: implement binary search */
+
+ /*
+ * Decend thru node iEntry.
+ *
+ * We could be bold and ASSUME that there is always an END node, but we're
+ * playing safe for now.
+ */
+ if (iEnd < pNodeInfo->cEntries)
+ {
+ PCNTFSIDXENTRYHDR pEntry = papEntries[iEntry];
+ if (pEntry->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ {
+ int64_t iSubnode = NTFSIDXENTRYHDR_GET_SUBNODE(pEntry);
+ rtFsNtfsIdxNode_Release(pNode);
+ int rc = rtFsNtfsIdxRootInfo_QueryNode(pRootInfo, iSubnode, &pNode);
+ if (RT_SUCCESS(rc))
+ {
+ pNodeInfo = &pNode->NodeInfo;
+ continue;
+ }
+ LogFlow(("rtFsNtfsDirShrd_Lookup(%s): rtFsNtfsIdxRootInfo_QueryNode(%#RX64) error %Rrc!\n",
+ pszEntry, iSubnode, rc));
+ return rc;
+ }
+ }
+ rtFsNtfsIdxNode_Release(pNode);
+ LogFlow(("rtFsNtfsDirShrd_Lookup(%s): Not found! (#2)\n", pszEntry));
+ return VERR_FILE_NOT_FOUND;
+ }
+
+ /* not reached */
+}
+
+
+/**
+ * Gets the shared directory structure for the parent.
+ *
+ * @returns IPRT status code.
+ * @param pThis The directory which parent we want.
+ * @param ppDotDot Where to return the referenced shared parent dir
+ * structure.
+ *
+ */
+static int rtFsNtfsDirShrd_QueryParent(PRTFSNTFSDIRSHRD pThis, PRTFSNTFSDIRSHRD *ppDotDot)
+{
+ /*
+ * The root directory has no parent from our perspective.
+ */
+ if (pThis == pThis->RootInfo.NodeInfo.pVol->pRootDir)
+ {
+ rtFsNtfsDirShrd_Retain(pThis);
+ *ppDotDot = pThis;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Look for a filename record so we know where we go from here.
+ */
+ PRTFSNTFSCORE pCore = pThis->RootInfo.pRootAttr->pCore;
+ PRTFSNTFSATTR pCurAttr;
+ RTListForEach(&pCore->AttribHead, pCurAttr, RTFSNTFSATTR, ListEntry)
+ {
+ if ( pCurAttr->pAttrHdr->uAttrType == NTFS_AT_FILENAME
+ && pCurAttr->cbResident >= RT_UOFFSETOF(NTFSATFILENAME, wszFilename))
+ {
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)NTFSATTRIBHDR_GET_RES_VALUE_PTR(pCurAttr->pAttrHdr);
+ int rc = rtFsNtfsVol_QueryOrCreateSharedDirByMftRef(pThis->RootInfo.NodeInfo.pVol, &pFilename->ParentDirMftRec,
+ ppDotDot, NULL /*pErrInfo*/, "..");
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ LogRel(("rtFsNtfsDirShrd_QueryParent: rtFsNtfsVol_QueryOrCreateSharedDirByMftRef failed: %Rrc\n", rc));
+ return rc;
+ }
+ }
+
+ LogRel(("rtFsNtfsDirShrd_QueryParent: Couldn't find '..' filename for MFT record %RX64!\n",
+ pThis->RootInfo.pRootAttr->pCore->pMftRec->TreeNode.Key));
+ return VERR_VFS_BOGUS_FORMAT;
+}
+
+
+
+/**
+ * Destroys an index node.
+ *
+ * This will remove it from the cache tree, however the caller must make sure
+ * its not in the reuse list any more.
+ *
+ * @param pNode The node to destroy.
+ */
+static void rtFsNtfsIdxNode_Destroy(PRTFSNTFSIDXNODE pNode)
+{
+ PRTFSNTFSVOL pVol = pNode->NodeInfo.pVol;
+
+ /* Remove it from the volume node cache. */
+ PAVLU64NODECORE pAssertRemove = RTAvlU64Remove(&pVol->IdxNodeCacheRoot, pNode->TreeNode.Key);
+ Assert(pAssertRemove == &pNode->TreeNode); NOREF(pAssertRemove);
+ pVol->cIdxNodes--;
+ pVol->cbIdxNodes -= pNode->cbCost;
+
+ /* Destroy it. */
+ rtFsNtfsIdxNodeInfo_Delete(&pNode->NodeInfo);
+ RTMemFree(pNode->pNode);
+ pNode->pNode = NULL;
+ RTMemFree(pNode);
+}
+
+
+/**
+ * Trims the index node cache.
+ *
+ * @param pThis The NTFS volume instance which index node cache
+ * needs trimming.
+ */
+static void rtFsNtfsIdxVol_TrimIndexNodeCache(PRTFSNTFSVOL pThis)
+{
+ while ( pThis->cbIdxNodes > RTFSNTFS_MAX_NODE_CACHE_SIZE
+ && pThis->cUnusedIdxNodes)
+ {
+ PRTFSNTFSIDXNODE pNode = RTListRemoveFirst(&pThis->IdxNodeUnusedHead, RTFSNTFSIDXNODE, UnusedListEntry);
+ pThis->cUnusedIdxNodes--;
+ rtFsNtfsIdxNode_Destroy(pNode);
+ }
+}
+
+
+/**
+ * Index node reference reached zero, put it in the unused list and trim the
+ * cache.
+ *
+ * @returns zero
+ * @param pNode The index node.
+ */
+static uint32_t rtFsNtfsIdxNode_MaybeDestroy(PRTFSNTFSIDXNODE pNode)
+{
+ PRTFSNTFSVOL pVol = pNode->NodeInfo.pVol;
+ if (pVol)
+ {
+ RTListAppend(&pVol->IdxNodeUnusedHead, &pNode->UnusedListEntry);
+ pVol->cUnusedIdxNodes++;
+ if (pVol->cbIdxNodes > RTFSNTFS_MAX_NODE_CACHE_SIZE)
+ rtFsNtfsIdxVol_TrimIndexNodeCache(pVol);
+ return 0;
+ }
+ /* not sure if this is needed yet... */
+ rtFsNtfsIdxNodeInfo_Delete(&pNode->NodeInfo);
+ RTMemFree(pNode);
+ return 0;
+}
+
+
+/**
+ * Releases a reference to an index node.
+ *
+ * @returns New reference count.
+ * @param pNode The index node to release. NULL is ignored.
+ */
+static uint32_t rtFsNtfsIdxNode_Release(PRTFSNTFSIDXNODE pNode)
+{
+ if (pNode)
+ {
+ uint32_t cRefs = ASMAtomicDecU32(&pNode->cRefs);
+ Assert(cRefs < 128);
+ if (cRefs > 0)
+ return cRefs;
+ return rtFsNtfsIdxNode_MaybeDestroy(pNode);
+ }
+ return 0;
+}
+
+
+/**
+ * Retains a reference to an index node.
+ *
+ * This will remove it from the unused list if necessary.
+ *
+ * @returns New reference count.
+ * @param pNode The index to reference.
+ */
+static uint32_t rtFsNtfsIdxNode_Retain(PRTFSNTFSIDXNODE pNode)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pNode->cRefs);
+ if (cRefs == 1)
+ {
+ RTListNodeRemove(&pNode->UnusedListEntry);
+ pNode->NodeInfo.pVol->cUnusedIdxNodes--;
+ }
+ return cRefs;
+}
+
+
+
+
+/*
+ *
+ * Directory instance methods
+ * Directory instance methods
+ * Directory instance methods
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_Close(void *pvThis)
+{
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ LogFlow(("rtFsNtfsDir_Close(%p/%p)\n", pThis, pThis->pShared));
+
+ PRTFSNTFSDIRSHRD pShared = pThis->pShared;
+ pThis->pShared = NULL;
+ if (pShared)
+ rtFsNtfsDirShrd_Release(pShared);
+
+ while (pThis->cEnumStackEntries > 0)
+ {
+ PRTFSNTFSIDXSTACKENTRY pEntry = &pThis->paEnumStack[--pThis->cEnumStackEntries];
+ rtFsNtfsIdxNode_Release(pEntry->pNodeInfo->pNode);
+ pEntry->pNodeInfo = NULL;
+ }
+ RTMemFree(pThis->paEnumStack);
+ pThis->paEnumStack = NULL;
+ pThis->cEnumStackMaxDepth = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ Log(("rtFsNtfsDir_QueryInfo\n"));
+ return rtFsNtfsCore_QueryInfo(pThis->pShared->RootInfo.pRootAttr->pCore,
+ pThis->pShared->RootInfo.pAlloc ? pThis->pShared->RootInfo.pAlloc
+ : pThis->pShared->RootInfo.pRootAttr,
+ pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ Log(("rtFsNtfsDir_SetMode\n"));
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ Log(("rtFsNtfsDir_SetTimes\n"));
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ Log(("rtFsNtfsDir_SetOwner\n"));
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ LogFlow(("rtFsNtfsDir_Open: pszEntry='%s' fOpen=%#RX64 fFlags=%#x\n", pszEntry, fOpen, fFlags));
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ PRTFSNTFSDIRSHRD pShared = pThis->pShared;
+ PRTFSNTFSVOL pVol = pShared->RootInfo.NodeInfo.pVol;
+ int rc;
+
+ /*
+ * We cannot create or replace anything, just open stuff.
+ */
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ { /* likely */ }
+ else
+ return VERR_WRITE_PROTECT;
+
+ /*
+ * Special cases '.' and '..'
+ */
+ if ( pszEntry[0] == '.'
+ && ( pszEntry[1] == '\0'
+ || ( pszEntry[1] == '.'
+ && pszEntry[2] == '\0')))
+ {
+ if (!(fFlags & RTVFSOBJ_F_OPEN_DIRECTORY))
+ return VERR_IS_A_DIRECTORY;
+
+ PRTFSNTFSDIRSHRD pSharedToOpen;
+ if (pszEntry[1] == '\0')
+ {
+ pSharedToOpen = pShared;
+ rtFsNtfsDirShrd_Retain(pSharedToOpen);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ pSharedToOpen = NULL;
+ rc = rtFsNtfsDirShrd_QueryParent(pShared, &pSharedToOpen);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsNtfsVol_NewDirFromShared(pVol, pSharedToOpen, &hVfsDir);
+ rtFsNtfsDirShrd_Release(pSharedToOpen);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ LogFlow(("rtFsNtfsDir_Open(%s): returns %Rrc\n", pszEntry, rc));
+ return rc;
+ }
+
+ /*
+ * Lookup the index entry.
+ */
+ PRTFSNTFSIDXNODE pNode;
+ PCNTFSIDXENTRYHDR pEntryHdr;
+ PCNTFSATFILENAME pFilename;
+ rc = rtFsNtfsDirShrd_Lookup(pShared, pszEntry, &pFilename, &pEntryHdr, &pNode);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fFileAttribs = RT_LE2H_U32(pFilename->fFileAttribs);
+ switch (fFileAttribs & (NTFS_FA_DIRECTORY | NTFS_FA_REPARSE_POINT | NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT))
+ {
+ /*
+ * File.
+ */
+ case 0:
+ if (fFlags & RTVFSOBJ_F_OPEN_FILE)
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsNtfsVol_NewFile(pVol, fOpen, pEntryHdr, NULL /*pszStreamName*/, &hVfsFile, NULL, pszEntry);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_IS_A_FILE;
+ break;
+
+ /*
+ * Directory
+ */
+ case NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT:
+ case NTFS_FA_DIRECTORY | NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT:
+ case NTFS_FA_DIRECTORY:
+ if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ PRTFSNTFSDIRSHRD pSharedToOpen;
+ rc = rtFsNtfsVol_QueryOrCreateSharedDirByMftRef(pVol, &pEntryHdr->u.FileMftRec,
+ &pSharedToOpen, NULL, pszEntry);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsNtfsVol_NewDirFromShared(pVol, pSharedToOpen, &hVfsDir);
+ rtFsNtfsDirShrd_Release(pSharedToOpen);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ break;
+
+ /*
+ * Possible symbolic links.
+ */
+ case NTFS_FA_REPARSE_POINT:
+ case NTFS_FA_REPARSE_POINT | NTFS_FA_DIRECTORY:
+ case NTFS_FA_REPARSE_POINT | NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT:
+ case NTFS_FA_REPARSE_POINT | NTFS_FA_DIRECTORY | NTFS_FA_DUP_FILE_NAME_INDEX_PRESENT:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_FILE_NOT_FOUND;
+ break;
+ }
+ rtFsNtfsIdxNode_Release(pNode);
+ }
+
+ LogFlow(("rtFsNtfsDir_Open(%s): returns %Rrc\n", pszEntry, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
+ Log(("rtFsNtfsDir_CreateDir\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ Log(("rtFsNtfsDir_OpenSymlink\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ Log(("rtFsNtfsDir_CreateSymlink\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ Log(("rtFsNtfsDir_UnlinkEntry\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ Log(("rtFsNtfsDir_RenameEntry\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * Cleans up the directory enumeration stack, releasing all node references.
+ *
+ * @param pThis The open directory instance data.
+ */
+static void rtFsNtfsDir_StackCleanup(PRTFSNTFSDIR pThis)
+{
+ while (pThis->cEnumStackEntries > 0)
+ {
+ PRTFSNTFSIDXSTACKENTRY pEntry = &pThis->paEnumStack[--pThis->cEnumStackEntries];
+ rtFsNtfsIdxNode_Release(pEntry->pNodeInfo->pNode);
+ pEntry->pNodeInfo = NULL;
+ }
+ if (pThis->paEnumStack)
+ pThis->paEnumStack[0].iNext = 0;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_RewindDir(void *pvThis)
+{
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ LogFlow(("rtFsNtfsDir_RewindDir\n"));
+
+ rtFsNtfsDir_StackCleanup(pThis);
+ pThis->fNoMoreFiles = false;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Descends down @a iSubnode to the first entry in left most leaf node.
+ *
+ * @returns IPRT status code.
+ * @param pThis The open directory instance data.
+ * @param pRootInfo The root info structure.
+ * @param iSubnode The subnode address to descend thru.
+ */
+static int rtFsNtfsDir_StackDescend(PRTFSNTFSDIR pThis, PRTFSNTFSIDXROOTINFO pRootInfo, int64_t iSubnode)
+{
+ for (;;)
+ {
+ /* Load the node. */
+ PRTFSNTFSIDXNODE pNode;
+ int rc = rtFsNtfsIdxRootInfo_QueryNode(pRootInfo, iSubnode, &pNode);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ {
+ LogFlow(("rtFsNtfsDir_StackDescend: rtFsNtfsIdxRootInfo_QueryNode(%#RX64) error %Rrc!\n", iSubnode, rc));
+ return rc;
+ }
+
+ /* Push it onto the stack. */
+ uint32_t iStack = pThis->cEnumStackEntries;
+ if (iStack + 1 < pThis->cEnumStackMaxDepth)
+ { /* likely */ }
+ else if (pThis->cEnumStackMaxDepth < 1024)
+ {
+ Assert(pThis->cEnumStackMaxDepth> 0);
+ uint32_t cDepth = pThis->cEnumStackMaxDepth * 2;
+ Log5(("rtFsNtfsDir_ReadDir: Growing stack size to %u entries (from %u)\n", cDepth, pThis->cEnumStackMaxDepth));
+ void *pvNew = RTMemRealloc(pThis->paEnumStack, cDepth * sizeof(pThis->paEnumStack[0]));
+ if (pvNew)
+ pThis->paEnumStack = (PRTFSNTFSIDXSTACKENTRY)pvNew;
+ else
+ return VERR_NO_MEMORY;
+ pThis->cEnumStackMaxDepth = cDepth;
+ }
+ else
+ {
+ LogRel(("rtFsNtfsDir_StackDescend: Badly unbalanced index! (MFT record #%#RX64) -> VERR_VFS_BOGUS_FORMAT\n",
+ pThis->pShared->RootInfo.pRootAttr->pCore->pMftRec->TreeNode.Key));
+ return VERR_VFS_BOGUS_FORMAT;
+ }
+
+ Log5(("rtFsNtfsDir_ReadDir: pushing %#RX64 (cEntries=%u, iStack=%u)\n", iSubnode, pNode->NodeInfo.cEntries, iStack));
+ pThis->paEnumStack[iStack].iNext = 0;
+ pThis->paEnumStack[iStack].fDescend = false;
+ pThis->paEnumStack[iStack].pNodeInfo = &pNode->NodeInfo;
+ pThis->cEnumStackEntries = iStack + 1;
+
+ /* Stop if this is a leaf node. */
+ if ( !pNode->NodeInfo.fInternal
+ || !pNode->NodeInfo.cEntries /* paranoia */)
+ return VINF_SUCCESS;
+
+ /* Get the first entry and check that it's an internal node before trying to following it. */
+ PCNTFSIDXENTRYHDR pFirstEntry = pNode->NodeInfo.papEntries[0];
+ if (pFirstEntry->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ { /* likely */ }
+ else
+ return VINF_SUCCESS;
+ iSubnode = NTFSIDXENTRYHDR_GET_SUBNODE(pFirstEntry);
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsNtfsDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSNTFSDIR pThis = (PRTFSNTFSDIR)pvThis;
+ PRTFSNTFSDIRSHRD pShared = pThis->pShared;
+ int rc;
+ Log(("rtFsNtfsDir_ReadDir\n"));
+
+ /*
+ * Return immediately if no files at hand.
+ */
+ if (pThis->fNoMoreFiles)
+ return VERR_NO_MORE_FILES;
+
+ /*
+ * Make sure we've got a stack before we jump into the fray.
+ */
+ if (!pThis->cEnumStackMaxDepth)
+ {
+ uint32_t cDepth;
+ if (!pShared->RootInfo.pAlloc)
+ cDepth = 2;
+ else
+ {
+ cDepth = ASMBitFirstSetU64(pShared->RootInfo.pAlloc->cbValue / RT_LE2H_U32(pShared->RootInfo.pRoot->cbIndexNode));
+ cDepth += 3;
+ }
+
+ pThis->paEnumStack = (PRTFSNTFSIDXSTACKENTRY)RTMemAllocZ(cDepth * sizeof(pThis->paEnumStack[0]));
+ if (!pThis->paEnumStack)
+ return VERR_NO_MEMORY;
+ pThis->cEnumStackMaxDepth = cDepth;
+ pThis->cEnumStackEntries = 0;
+ Log5(("rtFsNtfsDir_ReadDir: Initial stack size: %u entries\n", cDepth));
+ //pThis->paEnumStack[0].iNext = 0;
+ }
+
+ /*
+ * Deal with '.' and '..' by using stack entry zero without setting cEnumStack to zero.
+ * This is fine because we've got the fNoMoreFiles flag that got checked already.
+ */
+ size_t const cbDirEntry = *pcbDirEntry;
+ if (pThis->cEnumStackEntries == 0)
+ {
+ if (pThis->paEnumStack[0].iNext <= 1)
+ {
+
+ *pcbDirEntry = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[pThis->paEnumStack[0].iNext + 2]);
+ if (*pcbDirEntry > cbDirEntry)
+ return VERR_BUFFER_OVERFLOW;
+
+ /* Names. */
+ pDirEntry->cbName = pThis->paEnumStack[0].iNext + 1;
+ pDirEntry->szName[0] = '.';
+ pDirEntry->szName[pDirEntry->cbName - 1] = '.';
+ pDirEntry->szName[pDirEntry->cbName] = '\0';
+ pDirEntry->wszShortName[0] = '\0';
+ pDirEntry->cwcShortName = 0;
+
+ /* Get referenced shared directory structure that we return info about. */
+ PRTFSNTFSDIRSHRD pDotShared;
+ if (pThis->paEnumStack[0].iNext == 0)
+ {
+ rtFsNtfsDirShrd_Retain(pShared);
+ pDotShared = pShared;
+ }
+ else
+ {
+ pDotShared = NULL;
+ rc = rtFsNtfsDirShrd_QueryParent(pShared, &pDotShared);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("rtFsNtfsDir_ReadDir: couldn't find '..' filename! %Rrc\n", rc));
+ return rc;
+ }
+ }
+
+ /* Get the info. */
+ rc = rtFsNtfsCore_QueryInfo(pDotShared->RootInfo.pRootAttr->pCore, pDotShared->RootInfo.pRootAttr,
+ &pDirEntry->Info, enmAddAttr);
+ rtFsNtfsDirShrd_Release(pDotShared);
+ if (RT_SUCCESS(rc))
+ pThis->paEnumStack[0].iNext++;
+ Log5(("rtFsNtfsDir_ReadDir: => '%s' (%Rrc)\n", pDirEntry->szName, rc));
+ return rc;
+ }
+
+ /*
+ * Push the root onto the stack and decend down the left side of the tree.
+ */
+ PRTFSNTFSIDXNODEINFO pNodeInfo = &pShared->RootInfo.NodeInfo;
+ pThis->paEnumStack[0].pNodeInfo = pNodeInfo;
+ pThis->paEnumStack[0].iNext = 0;
+ pThis->cEnumStackEntries = 1;
+ Log5(("rtFsNtfsDir_ReadDir: pushing root\n"));
+ if ( pNodeInfo->fInternal
+ && pNodeInfo->cEntries > 0
+ && (pNodeInfo->papEntries[0]->fFlags & NTFSIDXENTRYHDR_F_INTERNAL) /* parnaoia */ )
+ {
+ rc = rtFsNtfsDir_StackDescend(pThis, &pShared->RootInfo, NTFSIDXENTRYHDR_GET_SUBNODE(pNodeInfo->papEntries[0]));
+ if (RT_FAILURE(rc))
+ {
+ pThis->fNoMoreFiles = true;
+ rtFsNtfsDir_StackCleanup(pThis);
+ return rc;
+ }
+ }
+ }
+
+ /*
+ * Work the stack.
+ */
+ int32_t iStack = pThis->cEnumStackEntries - 1;
+ while (iStack >= 0)
+ {
+ PRTFSNTFSIDXNODEINFO pNodeInfo = pThis->paEnumStack[iStack].pNodeInfo;
+ uint32_t iNext = pThis->paEnumStack[iStack].iNext;
+ if (iNext < pNodeInfo->cEntries)
+ {
+ PCNTFSIDXENTRYHDR pEntry = pNodeInfo->papEntries[iNext];
+ if ( !(pEntry->fFlags & NTFSIDXENTRYHDR_F_INTERNAL)
+ || !pThis->paEnumStack[iStack].fDescend)
+ {
+ if (!(pEntry->fFlags & NTFSIDXENTRYHDR_F_END))
+ {
+ /*
+ * Try return the current entry.
+ */
+ PCNTFSATFILENAME pFilename = (PCNTFSATFILENAME)(pEntry + 1);
+
+ /* Deal with the filename. */
+ size_t cchFilename;
+ rc = RTUtf16CalcUtf8LenEx(pFilename->wszFilename, pFilename->cwcFilename, &cchFilename);
+ if (RT_FAILURE(rc))
+ {
+ cchFilename = 48;
+ LogRel(("rtFsNtfsDir_ReadDir: Bad filename (%Rrc) %.*Rhxs\n",
+ rc, pFilename->cwcFilename * sizeof(RTUTF16), pFilename->wszFilename));
+ }
+ *pcbDirEntry = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cchFilename + 1]);
+ if (*pcbDirEntry > cbDirEntry)
+ {
+ Log5(("rtFsNtfsDir_ReadDir: returns VERR_BUFFER_OVERFLOW (for '%.*ls')\n",
+ pFilename->cwcFilename, pFilename->wszFilename));
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ char *pszDst = pDirEntry->szName;
+ if (RT_SUCCESS(rc))
+ rc = RTUtf16ToUtf8Ex(pFilename->wszFilename, pFilename->cwcFilename, &pszDst,
+ cbDirEntry - RT_UOFFSETOF(RTDIRENTRYEX, szName), &cchFilename);
+ if (RT_FAILURE(rc))
+ cchFilename = RTStrPrintf(pDirEntry->szName, cbDirEntry - RT_UOFFSETOF(RTDIRENTRYEX, szName),
+ "{invalid-name-%#RX64}", NTFSMFTREF_GET_IDX(&pEntry->u.FileMftRec));
+ pDirEntry->cbName = (uint16_t)cchFilename;
+
+ /* Figure out how to detect short names. */
+ pDirEntry->cwcShortName = 0;
+ pDirEntry->wszShortName[0] = '\0';
+
+ /* Standard attributes: file mode, sizes and timestamps. */
+ pDirEntry->Info.cbObject = RT_LE2H_U64(pFilename->cbData);
+ pDirEntry->Info.cbAllocated = RT_LE2H_U64(pFilename->cbAllocated);
+ RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime, RT_LE2H_U64(pFilename->iCreationTime));
+ RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime, RT_LE2H_U64(pFilename->iLastDataModTime));
+ RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime, RT_LE2H_U64(pFilename->iLastMftModTime));
+ RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime, RT_LE2H_U64(pFilename->iLastAccessTime));
+ pDirEntry->Info.Attr.fMode = rtFsNtfsConvertFileattribsToMode(RT_LE2H_U32(pFilename->fFileAttribs), pFilename,
+ RT_LE2H_U16(pEntry->cbKey));
+
+ /* additional stuff. */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ enmAddAttr = RTFSOBJATTRADD_UNIX;
+ RT_FALL_THRU();
+ case RTFSOBJATTRADD_UNIX:
+ pDirEntry->Info.Attr.u.Unix.uid = NIL_RTUID;
+ pDirEntry->Info.Attr.u.Unix.gid = NIL_RTGID;
+ pDirEntry->Info.Attr.u.Unix.cHardlinks = 1;
+ pDirEntry->Info.Attr.u.Unix.INodeIdDevice = 0;
+ pDirEntry->Info.Attr.u.Unix.INodeId = NTFSMFTREF_GET_IDX(&pEntry->u.FileMftRec);
+ pDirEntry->Info.Attr.u.Unix.fFlags = 0;
+ pDirEntry->Info.Attr.u.Unix.GenerationId = 0;
+ pDirEntry->Info.Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pDirEntry->Info.Attr.u.UnixOwner.uid = NIL_RTUID;
+ pDirEntry->Info.Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pDirEntry->Info.Attr.u.UnixGroup.gid = NIL_RTGID;
+ pDirEntry->Info.Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ if (!(pFilename->fFileAttribs & RT_H2LE_U32_C(NTFS_FA_REPARSE_POINT)))
+ pDirEntry->Info.Attr.u.EASize.cb = pFilename->u.cbPackedEas;
+ else
+ pDirEntry->Info.Attr.u.EASize.cb = 0;
+ break;
+
+ default:
+ AssertFailed();
+ RT_ZERO(pDirEntry->Info.Attr.u);
+ break;
+ }
+ pDirEntry->Info.Attr.enmAdditional = enmAddAttr;
+
+ /*
+ * Advance the stack entry to the next entry and return.
+ */
+ Log5(("rtFsNtfsDir_ReadDir: => iStack=%u iNext=%u - '%.*ls'\n",
+ iStack, iNext, pFilename->cwcFilename, pFilename->wszFilename));
+ pThis->paEnumStack[iStack].iNext = iNext + 1;
+ pThis->paEnumStack[iStack].fDescend = true;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * End node, so pop it. We join the beoynd-end-of-entries path
+ * further down, forcing the descend code to use continue.
+ */
+ }
+ else
+ {
+ /*
+ * Descend.
+ */
+ rc = rtFsNtfsDir_StackDescend(pThis, &pShared->RootInfo,
+ NTFSIDXENTRYHDR_GET_SUBNODE(pNodeInfo->papEntries[iNext]));
+ if (RT_SUCCESS(rc))
+ {
+ pThis->paEnumStack[iStack].fDescend = false;
+ iStack = pThis->cEnumStackEntries - 1;
+ continue;
+ }
+ pThis->fNoMoreFiles = true;
+ rtFsNtfsDir_StackCleanup(pThis);
+ return rc;
+ }
+ }
+
+ /*
+ * Pop at stack entry.
+ */
+ Log5(("rtFsNtfsDir_ReadDir: popping %#RX64 (iNext=%u, cEntries=%u, iStack=%u) -> %#RX64 (iNext=%d, cEntries=%u)\n",
+ pNodeInfo->pNode ? pNodeInfo->pNode->pNode->iSelfAddress : 0, iNext, pNodeInfo->cEntries, iStack,
+ iStack > 0 && pThis->paEnumStack[iStack - 1].pNodeInfo->pNode
+ ? pThis->paEnumStack[iStack - 1].pNodeInfo->pNode->pNode->iSelfAddress : UINT64_MAX,
+ iStack > 0 ? pThis->paEnumStack[iStack - 1].iNext : -1,
+ iStack > 0 ? pThis->paEnumStack[iStack - 1].pNodeInfo->cEntries : 0 ));
+ rtFsNtfsIdxNode_Release(pNodeInfo->pNode);
+ pThis->paEnumStack[iStack].pNodeInfo = NULL;
+ pThis->cEnumStackEntries = iStack;
+ iStack--;
+ Assert(iStack < 0 || !pThis->paEnumStack[iStack].fDescend);
+ }
+
+ /*
+ * The End.
+ */
+ Log5(("rtFsNtfsDir_ReadDir: no more files\n"));
+ pThis->fNoMoreFiles = true;
+ return VERR_NO_MORE_FILES;
+}
+
+
+/**
+ * NTFS directory operations.
+ */
+static const RTVFSDIROPS g_rtFsNtfsDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "NTFS Dir",
+ rtFsNtfsDir_Close,
+ rtFsNtfsDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtFsNtfsDir_SetMode,
+ rtFsNtfsDir_SetTimes,
+ rtFsNtfsDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsNtfsDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile */,
+ NULL /* pfnOpenDir */,
+ rtFsNtfsDir_CreateDir,
+ rtFsNtfsDir_OpenSymlink,
+ rtFsNtfsDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsNtfsDir_UnlinkEntry,
+ rtFsNtfsDir_RenameEntry,
+ rtFsNtfsDir_RewindDir,
+ rtFsNtfsDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+/**
+ * Creates a new directory instance given a shared directory structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pSharedDir The shared directory structure to create a new
+ * handle to.
+ * @param phVfsDir Where to return the directory handle.
+ */
+static int rtFsNtfsVol_NewDirFromShared(PRTFSNTFSVOL pThis, PRTFSNTFSDIRSHRD pSharedDir, PRTVFSDIR phVfsDir)
+{
+ PRTFSNTFSDIR pNewDir;
+ int rc = RTVfsNewDir(&g_rtFsNtfsDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ rtFsNtfsDirShrd_Retain(pSharedDir);
+ pNewDir->pShared = pSharedDir;
+ pNewDir->cEnumStackEntries = 0;
+ pNewDir->cEnumStackMaxDepth = 0;
+ pNewDir->paEnumStack = NULL;
+ return VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+
+/*
+ *
+ * Volume level code.
+ * Volume level code.
+ * Volume level code.
+ *
+ */
+
+
+/**
+ * Slow path for querying the allocation state of a cluster.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param iCluster The cluster to query.
+ * @param pfState Where to return the state.
+ */
+static int rtFsNtfsVol_QueryClusterStateSlow(PRTFSNTFSVOL pThis, uint64_t iCluster, bool *pfState)
+{
+ int rc;
+ uint64_t const cbWholeBitmap = RT_LE2H_U64(pThis->pMftBitmap->pAttrHdr->u.NonRes.cbData);
+ uint64_t const offInBitmap = iCluster >> 3;
+ if (offInBitmap < cbWholeBitmap)
+ {
+ if (!pThis->pvBitmap)
+ {
+ /*
+ * Try cache the whole bitmap if it's not too large.
+ */
+ if ( cbWholeBitmap <= RTFSNTFS_MAX_WHOLE_BITMAP_CACHE
+ && cbWholeBitmap >= RT_ALIGN_64(pThis->cClusters >> 3, 8))
+ {
+ pThis->cbBitmapAlloc = RT_ALIGN_Z((uint32_t)cbWholeBitmap, 8);
+ pThis->pvBitmap = RTMemAlloc(pThis->cbBitmapAlloc);
+ if (pThis->pvBitmap)
+ {
+ memset(pThis->pvBitmap, 0xff, pThis->cbBitmapAlloc);
+ rc = rtFsNtfsAttr_Read(pThis->pMftBitmap, 0, pThis->pvBitmap, (uint32_t)cbWholeBitmap);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->iFirstBitmapCluster = 0;
+ pThis->cBitmapClusters = pThis->cClusters;
+ *pfState = rtFsNtfsBitmap_IsSet(pThis->pvBitmap, (uint32_t)iCluster);
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pThis->pvBitmap);
+ pThis->pvBitmap = NULL;
+ pThis->cbBitmapAlloc = 0;
+ return rc;
+ }
+ }
+
+ /*
+ * Do a cluster/4K cache.
+ */
+ pThis->cbBitmapAlloc = RT_MAX(pThis->cbCluster, _4K);
+ pThis->pvBitmap = RTMemAlloc(pThis->cbBitmapAlloc);
+ if (!pThis->pvBitmap)
+ {
+ pThis->cbBitmapAlloc = 0;
+ return VERR_NO_MEMORY;
+ }
+ }
+
+ /*
+ * Load a cache line.
+ */
+ Assert(RT_IS_POWER_OF_TWO(pThis->cbBitmapAlloc));
+ uint64_t offLoad = offInBitmap & ~(uint64_t)(pThis->cbBitmapAlloc - 1);
+ uint32_t cbLoad = (uint32_t)RT_MIN(cbWholeBitmap - offLoad, pThis->cbBitmapAlloc);
+
+ memset(pThis->pvBitmap, 0xff, pThis->cbBitmapAlloc);
+ rc = rtFsNtfsAttr_Read(pThis->pMftBitmap, offLoad, pThis->pvBitmap, cbLoad);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->iFirstBitmapCluster = offLoad << 3;
+ pThis->cBitmapClusters = cbLoad << 3;
+ *pfState = rtFsNtfsBitmap_IsSet(pThis->pvBitmap, (uint32_t)(iCluster - pThis->iFirstBitmapCluster));
+ return VINF_SUCCESS;
+ }
+ pThis->cBitmapClusters = 0;
+ }
+ else
+ {
+ LogRel(("rtFsNtfsVol_QueryClusterStateSlow: iCluster=%#RX64 is outside the bitmap (%#RX64)\n", iCluster, cbWholeBitmap));
+ rc = VERR_OUT_OF_RANGE;
+ }
+ return rc;
+}
+
+
+/**
+ * Query the allocation state of the given cluster.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param iCluster The cluster to query.
+ * @param pfState Where to return the state.
+ */
+static int rtFsNtfsVol_QueryClusterState(PRTFSNTFSVOL pThis, uint64_t iCluster, bool *pfState)
+{
+ uint64_t iClusterInCache = iCluster - pThis->iFirstBitmapCluster;
+ if (iClusterInCache < pThis->cBitmapClusters)
+ {
+ *pfState = rtFsNtfsBitmap_IsSet(pThis->pvBitmap, (uint32_t)iClusterInCache);
+ return VINF_SUCCESS;
+ }
+ return rtFsNtfsVol_QueryClusterStateSlow(pThis, iCluster, pfState);
+}
+
+
+/**
+ * Callback for RTAvlU64Destroy used by rtFsNtfsVol_Close to destroy the MFT
+ * record cache.
+ *
+ * @returns VINF_SUCCESS
+ * @param pNode The MFT record to destroy.
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int) rtFsNtFsVol_DestroyCachedMftRecord(PAVLU64NODECORE pNode, void *pvUser)
+{
+ PRTFSNTFSMFTREC pMftRec = (PRTFSNTFSMFTREC)pNode;
+ RT_NOREF(pvUser);
+
+ RTMemFree(pMftRec->pbRec);
+ pMftRec->pbRec = NULL;
+ RTMemFree(pMftRec);
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Callback for RTAvlU64Destroy used by rtFsNtfsVol_Close to destroy the index
+ * node cache.
+ *
+ * @returns VINF_SUCCESS
+ * @param pNode The index node to destroy.
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_DestroyIndexNode(PAVLU64NODECORE pNode, void *pvUser)
+{
+ PRTFSNTFSIDXNODE pIdxNode = (PRTFSNTFSIDXNODE)pNode;
+ RT_NOREF(pvUser);
+
+ RTMemFree(pIdxNode->pNode);
+ RTMemFree(pIdxNode->NodeInfo.papEntries);
+ pIdxNode->pNode = NULL;
+ pIdxNode->NodeInfo.papEntries = NULL;
+ pIdxNode->NodeInfo.pIndexHdr = NULL;
+ pIdxNode->NodeInfo.pVol = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_Close(void *pvThis)
+{
+ PRTFSNTFSVOL pThis = (PRTFSNTFSVOL)pvThis;
+ Log(("rtFsNtfsVol_Close(%p):\n", pThis));
+
+ /*
+ * Index / directory related members.
+ */
+ if (pThis->pRootDir)
+ {
+ rtFsNtfsDirShrd_Release(pThis->pRootDir);
+ pThis->pRootDir = NULL;
+ }
+
+ RTAvlU64Destroy(&pThis->IdxNodeCacheRoot, rtFsNtfsVol_DestroyIndexNode, NULL);
+
+ RTMemFree(pThis->pawcUpcase);
+ pThis->pawcUpcase = NULL;
+
+ pThis->IdxNodeUnusedHead.pPrev = pThis->IdxNodeUnusedHead.pNext = NULL;
+
+ /*
+ * Allocation bitmap cache.
+ */
+ if (pThis->pMftBitmap)
+ {
+ rtFsNtfsCore_Release(pThis->pMftBitmap->pCore);
+ pThis->pMftBitmap = NULL;
+ }
+ RTMemFree(pThis->pvBitmap);
+ pThis->pvBitmap = NULL;
+
+ /*
+ * The MFT and MFT cache.
+ */
+ if (pThis->pMftData)
+ {
+ rtFsNtfsCore_Release(pThis->pMftData->pCore);
+ pThis->pMftData = NULL;
+ }
+
+ Assert(RTListIsEmpty(&pThis->CoreInUseHead));
+ PRTFSNTFSCORE pCurCore, pNextCore;
+ RTListForEachSafe(&pThis->CoreInUseHead, pCurCore, pNextCore, RTFSNTFSCORE, ListEntry)
+ rtFsNtfsCore_Destroy(pCurCore);
+ RTListForEachSafe(&pThis->CoreUnusedHead, pCurCore, pNextCore, RTFSNTFSCORE, ListEntry)
+ rtFsNtfsCore_Destroy(pCurCore);
+
+ pThis->CoreInUseHead.pPrev = pThis->CoreInUseHead.pNext = NULL;
+ pThis->CoreUnusedHead.pPrev = pThis->CoreUnusedHead.pNext = NULL;
+
+ Assert(pThis->MftRoot == NULL);
+ RTAvlU64Destroy(&pThis->MftRoot, rtFsNtFsVol_DestroyCachedMftRecord, NULL);
+
+ /*
+ * Backing file and handles.
+ */
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+ pThis->hVfsSelf = NIL_RTVFS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ NOREF(pvThis); NOREF(pObjInfo); NOREF(enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSNTFSVOL pThis = (PRTFSNTFSVOL)pvThis;
+ AssertReturn(pThis->pRootDir, VERR_INTERNAL_ERROR_4);
+ int rc = rtFsNtfsVol_NewDirFromShared(pThis, pThis->pRootDir, phVfsDir);
+ LogFlow(("rtFsNtfsVol_OpenRoot: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsNtfsVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed)
+{
+ PRTFSNTFSVOL pThis = (PRTFSNTFSVOL)pvThis;
+ *pfUsed = true;
+
+ /*
+ * Round to a cluster range.
+ */
+ uint64_t iCluster = off >> pThis->cClusterShift;
+
+ Assert(RT_IS_POWER_OF_TWO(pThis->cbCluster));
+ cb += off & (pThis->cbCluster - 1);
+ cb = RT_ALIGN_Z(cb, pThis->cbCluster);
+ size_t cClusters = cb >> pThis->cClusterShift;
+
+ /*
+ * Check the clusters one-by-one.
+ * Just to be cautious, we will always check the cluster at off, even when cb is zero.
+ */
+ do
+ {
+ bool fState = true;
+ int rc = rtFsNtfsVol_QueryClusterState(pThis, iCluster, &fState);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (fState)
+ {
+ *pfUsed = true;
+ LogFlow(("rtFsNtfsVol_QueryRangeState: %RX64 LB %#x - used\n", off & ~(uint64_t)(pThis->cbCluster - 1), cb));
+ return VINF_SUCCESS;
+ }
+
+ iCluster++;
+ } while (cClusters-- > 0);
+
+ LogFlow(("rtFsNtfsVol_QueryRangeState: %RX64 LB %#x - unused\n", off & ~(uint64_t)(pThis->cbCluster - 1), cb));
+ *pfUsed = false;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * NTFS volume operations.
+ */
+static const RTVFSOPS g_rtFsNtfsVolOps =
+{
+ /* .Obj = */
+ {
+ /* .uVersion = */ RTVFSOBJOPS_VERSION,
+ /* .enmType = */ RTVFSOBJTYPE_VFS,
+ /* .pszName = */ "NtfsVol",
+ /* .pfnClose = */ rtFsNtfsVol_Close,
+ /* .pfnQueryInfo = */ rtFsNtfsVol_QueryInfo,
+ /* .pfnQueryInfoEx = */ NULL,
+ /* .uEndMarker = */ RTVFSOBJOPS_VERSION
+ },
+ /* .uVersion = */ RTVFSOPS_VERSION,
+ /* .fFeatures = */ 0,
+ /* .pfnOpenRoot = */ rtFsNtfsVol_OpenRoot,
+ /* .pfnQueryRangeState = */ rtFsNtfsVol_QueryRangeState,
+ /* .uEndMarker = */ RTVFSOPS_VERSION
+};
+
+
+/**
+ * Checks that the storage for the given attribute is all marked allocated in
+ * the allocation bitmap of the volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance.
+ * @param pAttr The attribute to check.
+ * @param pszDesc Description of the attribute.
+ * @param pErrInfo Where to return error details.
+ */
+static int rtFsNtfsVolCheckBitmap(PRTFSNTFSVOL pThis, PRTFSNTFSATTR pAttr, const char *pszDesc, PRTERRINFO pErrInfo)
+{
+ PRTFSNTFSATTRSUBREC pSubRec = NULL;
+ PRTFSNTFSEXTENTS pTable = &pAttr->Extents;
+ uint64_t offFile = 0;
+ for (;;)
+ {
+ uint32_t const cExtents = pTable->cExtents;
+ PRTFSNTFSEXTENT paExtents = pTable->paExtents;
+ for (uint32_t iExtent = 0; iExtent < cExtents; iExtent++)
+ {
+ uint64_t const off = paExtents[iExtent].off;
+ if (off == UINT64_MAX)
+ offFile += paExtents[iExtent].cbExtent;
+ else
+ {
+ uint64_t iCluster = off >> pThis->cClusterShift;
+ uint64_t cClusters = paExtents[iExtent].cbExtent >> pThis->cClusterShift;
+ Assert((cClusters << pThis->cClusterShift) == paExtents[iExtent].cbExtent);
+ Assert(cClusters != 0);
+
+ while (cClusters-- > 0)
+ {
+ bool fState = false;
+ int rc = rtFsNtfsVol_QueryClusterState(pThis, iCluster, &fState);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, rc,
+ "Error querying allocation bitmap entry %#RX64 (for %s offset %#RX64)",
+ iCluster, pszDesc, offFile);
+ if (!fState)
+ return RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "Cluster %#RX64 at offset %#RX64 in %s is not marked allocated",
+ iCluster, offFile, pszDesc);
+ offFile += pThis->cbCluster;
+ }
+ }
+ }
+
+ /* Next table. */
+ pSubRec = pSubRec ? pSubRec->pNext : pAttr->pSubRecHead;
+ if (!pSubRec)
+ return VINF_SUCCESS;
+ pTable = &pSubRec->Extents;
+ }
+}
+
+
+/**
+ * Loads, validates and setups the '.' (NTFS_MFT_IDX_ROOT) MFT entry.
+ *
+ * @returns IPRT status code
+ * @param pThis The NTFS volume instance. Will set pawcUpcase.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsNtfsVolLoadRootDir(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ /*
+ * Load it and do some checks.
+ */
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFS_MFT_IDX_ROOT, false /*fRelaxedUsa*/, &pCore, pErrInfo); // DON'T COMMIT
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[1]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "RootDir: FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 1
+ || ( RTUtf16NICmpAscii(pFilename->wszFilename, ".", 1) != 0
+ && RTUtf16NICmpAscii(pFilename->wszFilename, "$", 1) != 0))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "RootDir: FILENAME is not '.' nor '$: '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ PRTFSNTFSATTR pIndexRoot = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ROOT,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ PRTFSNTFSATTR pIndexAlloc = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_INDEX_ALLOCATION,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ PRTFSNTFSATTR pIndexBitmap = rtFsNtfsCore_FindNamedAttributeAscii(pCore, NTFS_AT_BITMAP,
+ RT_STR_TUPLE(NTFS_DIR_ATTRIBUTE_NAME));
+ if (!pIndexRoot)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: Found no INDEX_ROOT attribute named $I30");
+ else if (!pIndexAlloc && pIndexBitmap)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: Found no INDEX_ALLOCATION attribute named $I30");
+ else if (!pIndexBitmap && pIndexAlloc)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "RootDir: Found no BITMAP attribute named $I30");
+ if (RT_SUCCESS(rc) && pIndexAlloc)
+ rc = rtFsNtfsVolCheckBitmap(pThis, pIndexAlloc, "RootDir", pErrInfo);
+ if (RT_SUCCESS(rc) && pIndexBitmap)
+ rc = rtFsNtfsVolCheckBitmap(pThis, pIndexBitmap, "RootDir/bitmap", pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Load it as a normal directory.
+ */
+ PRTFSNTFSDIRSHRD pSharedDir;
+ rc = rtFsNtfsVol_NewSharedDirFromCore(pThis, pCore, &pSharedDir, pErrInfo, "RootDir");
+ if (RT_SUCCESS(rc))
+ {
+ rtFsNtfsCore_Release(pCore);
+ pThis->pRootDir = pSharedDir;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ rtFsNtfsCore_Release(pCore);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "Root dir: Error reading MFT record");
+ return rc;
+}
+
+
+/**
+ * Loads, validates and setups the '$UpCase' (NTFS_MFT_IDX_UP_CASE) MFT entry.
+ *
+ * This is needed for filename lookups, I think.
+ *
+ * @returns IPRT status code
+ * @param pThis The NTFS volume instance. Will set pawcUpcase.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsNtfsVolLoadUpCase(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFS_MFT_IDX_UP_CASE, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSATTR pDataAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_DATA);
+ if (pDataAttr)
+ {
+ /*
+ * Validate the '$Upcase' MFT record.
+ */
+ uint32_t const cbMin = 512;
+ uint32_t const cbMax = _128K;
+ if (!pDataAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$UpCase: unnamed DATA attribute is resident!");
+ else if ( (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated) < cbMin
+ || (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated) > cbMax)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: unnamed DATA attribute allocated size is out of range: %#RX64, expected at least %#RX32 and no more than %#RX32",
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated), cbMin, cbMax);
+ else if ( (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData) < cbMin
+ || (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData)
+ > (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData)
+ || ((uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData) & 1) )
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: unnamed DATA attribute initialized size is out of range: %#RX64, expected at least %#RX32 and no more than %#RX64",
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbData), cbMin,
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated) );
+ else if ( (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbInitialized) < cbMin
+ || (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbInitialized)
+ > (uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated)
+ || ((uint64_t)RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbInitialized) & 1) )
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: unnamed DATA attribute initialized size is out of range: %#RX64, expected at least %#RX32 and no more than %#RX64",
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbInitialized), cbMin,
+ RT_LE2H_U64(pDataAttr->pAttrHdr->u.NonRes.cbAllocated) );
+ else if (pDataAttr->pAttrHdr->u.NonRes.uCompressionUnit != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: unnamed DATA attribute is compressed: %#x",
+ pDataAttr->pAttrHdr->u.NonRes.uCompressionUnit);
+ else
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$UpCase has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$UpCase FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[7]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 7
+ || RTUtf16NICmpAscii(pFilename->wszFilename, "$UpCase", 7) != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase: FILENAME isn't '$UpCase': '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ /*
+ * Allocate memory for the uppercase table and read it.
+ */
+ PRTUTF16 pawcUpcase;
+ pThis->pawcUpcase = pawcUpcase = (PRTUTF16)RTMemAlloc(_64K * sizeof(pThis->pawcUpcase[0]));
+ if (pawcUpcase)
+ {
+ for (size_t i = 0; i < _64K; i++)
+ pawcUpcase[i] = (uint16_t)i;
+
+ rc = rtFsNtfsAttr_Read(pDataAttr, 0, pawcUpcase, pDataAttr->pAttrHdr->u.NonRes.cbData);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check the data.
+ */
+ for (size_t i = 1; i < _64K; i++)
+ if (pawcUpcase[i] == 0)
+ {
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$UpCase entry %#x is zero!", i);
+ break;
+ }
+
+ /*
+ * While we still have the $UpCase file open, check it against the allocation bitmap.
+ */
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolCheckBitmap(pThis, pDataAttr, "$UpCase", pErrInfo);
+
+ /* We're done, no need for special success return here though. */
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, rc, "Error reading $UpCase data into memory");
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$UpCase: has no unnamed DATA attribute!");
+ rtFsNtfsCore_Release(pCore);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "$UpCase: Error reading the MFT record");
+ return rc;
+}
+
+
+/**
+ * Loads the allocation bitmap and does basic validation of.
+ *
+ * @returns IPRT status code.
+ * @param pThis The NTFS volume instance. Will set up the
+ * 'Allocation bitmap and cache' fields.
+ * @param pErrInfo Where to return error details.
+ */
+static int rtFsNtfsVolLoadBitmap(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFS_MFT_IDX_BITMAP, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSATTR pMftBitmap;
+ pThis->pMftBitmap = pMftBitmap = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_DATA);
+ if (pMftBitmap)
+ {
+ /*
+ * Validate the '$Bitmap' MFT record.
+ * We expect the bitmap to be fully initialized and be sized according to the
+ * formatted volume size. Allegedly, NTFS pads it to an even 8 byte in size.
+ */
+ uint64_t const cbMinBitmap = RT_ALIGN_64(pThis->cbVolume >> (pThis->cClusterShift + 3), 8);
+ uint64_t const cbMaxBitmap = RT_ALIGN_64(cbMinBitmap, pThis->cbCluster);
+ //uint64_t const cbMinInitialized = RT_ALIGN_64((RT_MAX(pThis->uLcnMft, pThis->uLcnMftMirror) + 16) >> 3, 8);
+ if (!pMftBitmap->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #6 unnamed DATA attribute is resident!");
+ else if ( (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated) < cbMinBitmap
+ || (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated) > cbMaxBitmap)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute allocated size is out of range: %#RX64, expected at least %#RX64 and no more than %#RX64",
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated), cbMinBitmap, cbMaxBitmap);
+ else if ( (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbData) < cbMinBitmap
+ || (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbData)
+ > (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbData))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute initialized size is out of range: %#RX64, expected at least %#RX64 and no more than %#RX64",
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbData), cbMinBitmap,
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated) );
+ else if ( (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbInitialized) < cbMinBitmap
+ || (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbInitialized)
+ > (uint64_t)RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute initialized size is out of range: %#RX64, expected at least %#RX64 and no more than %#RX64",
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbInitialized), cbMinBitmap,
+ RT_LE2H_U64(pMftBitmap->pAttrHdr->u.NonRes.cbAllocated) );
+ else if (pMftBitmap->pAttrHdr->u.NonRes.uCompressionUnit != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute is compressed: %#x",
+ pMftBitmap->pAttrHdr->u.NonRes.uCompressionUnit);
+ else if (pMftBitmap->Extents.cExtents != 1) /* paranoia for now */
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: unnamed DATA attribute is expected to have a single extent: %u extents",
+ pMftBitmap->Extents.cExtents);
+ else if (pMftBitmap->Extents.paExtents[0].off == UINT64_MAX)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #6 unnamed DATA attribute is sparse");
+ else
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #6 has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #6 FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[7]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 7
+ || RTUtf16NICmpAscii(pFilename->wszFilename, "$Bitmap", 7) != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Bitmap: FILENAME isn't '$Bitmap': '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ /*
+ * Read some of it into the buffer and check that essential stuff is flagged as allocated.
+ */
+ /* The boot sector. */
+ bool fState = false;
+ rc = rtFsNtfsVol_QueryClusterState(pThis, 0, &fState);
+ if (RT_SUCCESS(rc) && !fState)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT allocation bitmap error: Bootsector isn't marked allocated!");
+ else if (RT_FAILURE(rc))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT allocation bitmap (offset 0) read error: %Rrc", rc);
+
+ /* The bitmap ifself, the MFT data, and the MFT bitmap. */
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolCheckBitmap(pThis, pThis->pMftBitmap, "allocation bitmap", pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolCheckBitmap(pThis, pThis->pMftData, "MFT", pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolCheckBitmap(pThis,
+ rtFsNtfsCore_FindUnnamedAttribute(pThis->pMftData->pCore, NTFS_AT_BITMAP),
+ "MFT Bitmap", pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Looks like the bitmap is good.
+ */
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ pThis->pMftBitmap = NULL;
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$Bitmap: has no unnamed DATA attribute!");
+ rtFsNtfsCore_Release(pCore);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "$Bitmap: Error MFT record");
+ return rc;
+}
+
+
+/**
+ * Loads, validates and setups the '$Volume' (NTFS_MFT_IDX_VOLUME) MFT entry.
+ *
+ * @returns IPRT status code
+ * @param pThis The NTFS volume instance. Will set uNtfsVersion
+ * and fVolumeFlags.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsNtfsVolLoadVolumeInfo(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ PRTFSNTFSCORE pCore;
+ int rc = rtFsNtfsVol_NewCoreForMftIdx(pThis, NTFS_MFT_IDX_VOLUME, false /*fRelaxedUsa*/, &pCore, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSATTR pVolInfoAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_VOLUME_INFORMATION);
+ if (pVolInfoAttr)
+ {
+ /*
+ * Validate the '$Volume' MFT record.
+ */
+ if (pVolInfoAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$Volume unnamed VOLUME_INFORMATION attribute is not resident!");
+ else if ( pVolInfoAttr->cbResident != sizeof(NTFSATVOLUMEINFO)
+ || pVolInfoAttr->cbValue != sizeof(NTFSATVOLUMEINFO))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Volume VOLUME_INFORMATION attribute has the wrong size: cbValue=%#RX64, cbResident=%#RX32, expected %#x\n",
+ pVolInfoAttr->cbValue, pVolInfoAttr->cbResident, sizeof(NTFSATVOLUMEINFO));
+ else
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$Volume has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "$Volume FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[7]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Volume FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 7
+ || RTUtf16NICmpAscii(pFilename->wszFilename, "$Volume", 7) != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "$Volume FILENAME isn't '$Volume': '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ /*
+ * Look at the information.
+ */
+ PCNTFSATVOLUMEINFO pVolInfo;
+ pVolInfo = (PCNTFSATVOLUMEINFO)((uint8_t *)pVolInfoAttr->pAttrHdr + pVolInfoAttr->pAttrHdr->u.Res.offValue);
+ pThis->uNtfsVersion = RTFSNTFS_MAKE_VERSION(pVolInfo->uMajorVersion, pVolInfo->uMinorVersion);
+ pThis->fVolumeFlags = RT_LE2H_U16(pVolInfo->fFlags);
+ Log(("NTFS: Version %u.%u, flags=%#x\n", pVolInfo->uMajorVersion, pVolInfo->uMinorVersion, pThis->fVolumeFlags));
+
+ /* We're done, no need for special success return here though. */
+ }
+ }
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record $Volume has no unnamed VOLUME_INFORMATION attribute!");
+ rtFsNtfsCore_Release(pCore);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "Error reading $Volume MFT record");
+ return rc;
+}
+
+
+/**
+ * Loads, validates and setups the '$Mft' (NTFS_MFT_IDX_MFT) MFT entry.
+ *
+ * This is the first thing we do after we've checked out the boot sector and
+ * extracted information from it, since everything else depends on us being able
+ * to access the MFT data.
+ *
+ * @returns IPRT status code
+ * @param pThis The NTFS volume instance. Will set pMftData.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsNtfsVolLoadMft(PRTFSNTFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ /*
+ * Bootstrap the MFT data stream.
+ */
+ PRTFSNTFSMFTREC pRec = rtFsNtfsVol_NewMftRec(pThis, NTFS_MFT_IDX_MFT);
+ AssertReturn(pRec, VERR_NO_MEMORY);
+
+#if 0 && defined(LOG_ENABLED)
+ for (uint32_t i = 0; i < 128; i++)
+ {
+ uint64_t const offDisk = (pThis->uLcnMft << pThis->cClusterShift) + i * pThis->cbMftRecord;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, offDisk, pRec->pbRec, pThis->cbMftRecord, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pRec->TreeNode.Key = i;
+ rtfsNtfsMftRec_Log(pRec, pThis->cbMftRecord);
+ pRec->TreeNode.Key = 0;
+ }
+ }
+#endif
+
+ uint64_t const offDisk = pThis->uLcnMft << pThis->cClusterShift;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, offDisk, pRec->pbRec, pThis->cbMftRecord, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsNtfsRec_DoMultiSectorFixups(&pRec->pFileRec->Hdr, pThis->cbMftRecord, true, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtfsNtfsMftRec_Log(pRec, pThis->cbMftRecord);
+#endif
+ rc = rtFsNtfsVol_ParseMft(pThis, pRec, pErrInfo);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSNTFSCORE pCore = pRec->pCore;
+ PRTFSNTFSATTR pMftData;
+ pThis->pMftData = pMftData = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_DATA);
+ if (pMftData)
+ {
+ /*
+ * Validate the '$Mft' MFT record.
+ */
+ PNTFSATTRIBHDR pAttrHdr = pMftData->pAttrHdr;
+ if (!pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 unnamed DATA attribute is resident!");
+ else if ( (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbAllocated) < pThis->cbMftRecord * 16U
+ || (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbAllocated) >= pThis->cbBacking)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute allocated size is out of range: %#RX64",
+ RT_LE2H_U64(pAttrHdr->u.NonRes.cbAllocated));
+ else if ( (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbInitialized) < pThis->cbMftRecord * 16U
+ || (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbInitialized) >= pThis->cbBacking)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute initialized size is out of range: %#RX64",
+ RT_LE2H_U64(pAttrHdr->u.NonRes.cbInitialized));
+ else if ( (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbData) < pThis->cbMftRecord * 16U
+ || (uint64_t)RT_LE2H_U64(pAttrHdr->u.NonRes.cbData) >= pThis->cbBacking)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute allocated size is out of range: %#RX64",
+ RT_LE2H_U64(pAttrHdr->u.NonRes.cbData));
+ else if (pAttrHdr->u.NonRes.uCompressionUnit != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute is compressed: %#x",
+ pAttrHdr->u.NonRes.uCompressionUnit);
+ else if (pMftData->Extents.cExtents == 0)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute has no data on the disk");
+ else if (pMftData->Extents.paExtents[0].off != offDisk)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 unnamed DATA attribute has a bogus disk offset: %#RX64, expected %#RX64",
+ pMftData->Extents.paExtents[0].off, offDisk);
+ else if (!rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_BITMAP))
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 has no unnamed BITMAP attribute!");
+ else
+ {
+ PRTFSNTFSATTR pFilenameAttr = rtFsNtfsCore_FindUnnamedAttribute(pCore, NTFS_AT_FILENAME);
+ if (!pFilenameAttr)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 has no FILENAME attribute!");
+ else if (pFilenameAttr->pAttrHdr->fNonResident)
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 FILENAME attribute is non-resident!");
+ else if (pFilenameAttr->pAttrHdr->u.Res.cbValue < RT_UOFFSETOF(NTFSATFILENAME, wszFilename[4]))
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 FILENAME attribute value size is too small: %#x",
+ pFilenameAttr->pAttrHdr->u.Res.cbValue);
+ else
+ {
+ PNTFSATFILENAME pFilename = (PNTFSATFILENAME)( (uint8_t *)pFilenameAttr->pAttrHdr
+ + pFilenameAttr->pAttrHdr->u.Res.offValue);
+ if ( pFilename->cwcFilename != 4
+ || RTUtf16NICmpAscii(pFilename->wszFilename, "$Mft", 4) != 0)
+ rc = RTERRINFO_LOG_REL_SET_F(pErrInfo, VERR_VFS_BOGUS_FORMAT,
+ "MFT record #0 FILENAME isn't '$Mft': '%.*ls'",
+ pFilename->cwcFilename, pFilename->wszFilename);
+ else
+ {
+ /*
+ * Looks like we're good. Insert core record into the cache.
+ */
+ RTListAppend(&pThis->CoreInUseHead, &pCore->ListEntry);
+ pThis->cbCoreObjects += pCore->cbCost;
+
+ Assert(pCore->cRefs == 1);
+ Assert(pRec->cRefs == 2);
+ rtFsNtfsMftRec_Release(pRec, pThis);
+
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ pThis->pMftData = NULL;
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, VERR_VFS_BOGUS_FORMAT, "MFT record #0 has no unnamed DATA attribute!");
+ }
+ if (pRec->pCore)
+ rtFsNtfsCore_Destroy(pRec->pCore);
+ rtFsNtfsMftRec_Release(pRec, pThis);
+ }
+ else
+ rc = RTERRINFO_LOG_REL_SET(pErrInfo, rc, "Error reading MFT record #0");
+ return rc;
+}
+
+
+/**
+ * Loads the bootsector and parses it, copying values into the instance data.
+ *
+ * @returns IRPT status code.
+ * @param pThis The instance data.
+ * @param pvBuf The buffer.
+ * @param cbBuf The buffer size.
+ * @param pErrInfo Where to return additional error details.
+ */
+static int rtFsNtfsVolLoadAndParseBootsector(PRTFSNTFSVOL pThis, void *pvBuf, size_t cbBuf, PRTERRINFO pErrInfo)
+{
+ AssertReturn(cbBuf >= sizeof(FATBOOTSECTOR), VERR_INTERNAL_ERROR_2);
+
+ /*
+ * Read the boot sector and check that it makes sense for a NTFS volume.
+ *
+ * Note! There are two potential backup locations of the boot sector, however we
+ * currently don't implement falling back on these on corruption/read errors.
+ */
+ PFATBOOTSECTOR pBootSector = (PFATBOOTSECTOR)pvBuf;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, 0, pBootSector, sizeof(*pBootSector), NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading boot sector");
+
+ if (memcmp(pBootSector->achOemName, RT_STR_TUPLE(NTFS_OEM_ID_MAGIC)) != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Not NTFS - OEM field mismatch: %.8Rhxs'", pBootSector->achOemName);
+
+ /* Check must-be-zero BPB fields. */
+ if (pBootSector->Bpb.Ntfs.Bpb.cReservedSectors != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cReservedSectors=%u",
+ RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cReservedSectors));
+ if (pBootSector->Bpb.Ntfs.Bpb.cFats != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cFats=%u",
+ pBootSector->Bpb.Ntfs.Bpb.cFats);
+ if (pBootSector->Bpb.Ntfs.Bpb.cMaxRootDirEntries != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cMaxRootDirEntries=%u",
+ RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cMaxRootDirEntries));
+ if (pBootSector->Bpb.Ntfs.Bpb.cTotalSectors16 != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cTotalSectors16=%u",
+ RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cTotalSectors16));
+ if (pBootSector->Bpb.Ntfs.Bpb.cSectorsPerFat != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - MBZ: BPB.cSectorsPerFat=%u",
+ RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cSectorsPerFat));
+
+ /* Check other relevant BPB fields. */
+ uint32_t cbSector = RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cbSector);
+ if ( cbSector != 512
+ && cbSector != 1024
+ && cbSector != 2048
+ && cbSector != 4096)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not NTFS - BPB.cbSector is ouf of range: %u", cbSector);
+ pThis->cbSector = cbSector;
+ Log2(("NTFS BPB: cbSector=%#x\n", cbSector));
+
+ uint32_t cClusterPerSector = RT_LE2H_U16(pBootSector->Bpb.Ntfs.Bpb.cSectorsPerCluster);
+ if ( !RT_IS_POWER_OF_TWO(cClusterPerSector)
+ || cClusterPerSector == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
+ "Not NTFS - BPB.cCluster is ouf of range: %u", cClusterPerSector);
+
+ pThis->cbCluster = cClusterPerSector * cbSector;
+ if (pThis->cbCluster > _64K)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "cluster size exceeds 64KB: %#x", pThis->cbCluster);
+ pThis->cClusterShift = ASMBitFirstSetU32(pThis->cbCluster) - 1;
+ Log2(("NTFS BPB: cClusterPerSector=%#x => %#x bytes, %u shift\n", cClusterPerSector, pThis->cbCluster, pThis->cClusterShift));
+ pThis->iMaxVirtualCluster = (uint64_t)INT64_MAX >> pThis->cClusterShift;
+ Log2(("NTFS BPB: iMaxVirtualCluster=%#RX64\n", pThis->iMaxVirtualCluster));
+
+ /* NTFS BPB: cSectors. */
+ uint64_t cSectors = RT_LE2H_U64(pBootSector->Bpb.Ntfs.cSectors);
+ if (cSectors > pThis->cbBacking / pThis->cbSector)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS sector count exceeds volume size: %#RX64 vs %#RX64",
+ cSectors, pThis->cbBacking / pThis->cbSector);
+ if (cSectors < 256)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "NTFS sector count too small: %#RX64", cSectors);
+ pThis->cbVolume = cSectors * pThis->cbSector;
+ pThis->cClusters = cSectors / cClusterPerSector;
+ Log2(("NTFS BPB: cSectors=%#RX64 => %#xu bytes (%Rhcb) => cClusters=%#RX64\n",
+ cSectors, pThis->cbVolume, pThis->cbVolume, pThis->cClusters));
+
+ /* NTFS BPB: MFT location. */
+ uint64_t uLcn = RT_LE2H_U64(pBootSector->Bpb.Ntfs.uLcnMft);
+ if ( uLcn < 1
+ || uLcn >= pThis->cClusters)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS MFT location is out of bounds: %#RX64 (%#RX64 clusters)", uLcn, pThis->cClusters);
+ pThis->uLcnMft = uLcn;
+ Log2(("NTFS BPB: uLcnMft=%#RX64 (byte offset %#RX64)\n", uLcn, uLcn << pThis->cClusterShift));
+
+ /* NTFS BPB: Mirror MFT location. */
+ uLcn = RT_LE2H_U64(pBootSector->Bpb.Ntfs.uLcnMftMirror);
+ if ( uLcn < 1
+ || uLcn >= pThis->cClusters)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS mirror MFT location is out of bounds: %#RX64 (%#RX64 clusters)", uLcn, pThis->cClusters);
+ pThis->uLcnMftMirror = uLcn;
+ Log2(("NTFS BPB: uLcnMftMirror=%#RX64 (byte offset %#RX64)\n", uLcn, uLcn << pThis->cClusterShift));
+
+ /* NTFS BPB: Size of MFT file record. */
+ if (pBootSector->Bpb.Ntfs.cClustersPerMftRecord >= 0)
+ {
+ if ( !RT_IS_POWER_OF_TWO((uint32_t)pBootSector->Bpb.Ntfs.cClustersPerMftRecord)
+ || pBootSector->Bpb.Ntfs.cClustersPerMftRecord == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS clusters-per-mft-record value is zero or not a power of two: %#x",
+ pBootSector->Bpb.Ntfs.cClustersPerMftRecord);
+ pThis->cbMftRecord = (uint32_t)pBootSector->Bpb.Ntfs.cClustersPerMftRecord << pThis->cClusterShift;
+ Assert(pThis->cbMftRecord == pBootSector->Bpb.Ntfs.cClustersPerMftRecord * pThis->cbCluster);
+ }
+ else if ( pBootSector->Bpb.Ntfs.cClustersPerMftRecord < -20
+ || pBootSector->Bpb.Ntfs.cClustersPerMftRecord > -9)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS clusters-per-mft-record is out of shift range: %d",
+ pBootSector->Bpb.Ntfs.cClustersPerMftRecord);
+ else
+ pThis->cbMftRecord = UINT32_C(1) << -pBootSector->Bpb.Ntfs.cClustersPerMftRecord;
+ Log2(("NTFS BPB: cbMftRecord=%#x\n", pThis->cbMftRecord));
+ if ( pThis->cbMftRecord > _32K
+ || pThis->cbMftRecord < 256)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "Unsupported NTFS MFT record size: %#x", pThis->cbMftRecord);
+
+ /* NTFS BPB: Default index node size */
+ if (pBootSector->Bpb.Ntfs.cClustersPerIndexNode >= 0)
+ {
+ if ( !RT_IS_POWER_OF_TWO((uint32_t)pBootSector->Bpb.Ntfs.cClustersPerIndexNode)
+ || pBootSector->Bpb.Ntfs.cClustersPerIndexNode == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS default clusters-per-index-tree-node is zero or not a power of two: %#x",
+ pBootSector->Bpb.Ntfs.cClustersPerIndexNode);
+ pThis->cbDefaultIndexNode = (uint32_t)pBootSector->Bpb.Ntfs.cClustersPerIndexNode << pThis->cClusterShift;
+ Assert(pThis->cbDefaultIndexNode == pBootSector->Bpb.Ntfs.cClustersPerIndexNode * pThis->cbCluster);
+ }
+ else if ( pBootSector->Bpb.Ntfs.cClustersPerIndexNode < -32
+ || pBootSector->Bpb.Ntfs.cClustersPerIndexNode > -9)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT,
+ "NTFS default clusters-per-index-tree-node is out of shift range: %d",
+ pBootSector->Bpb.Ntfs.cClustersPerIndexNode);
+ else
+ pThis->cbDefaultIndexNode = UINT32_C(1) << -pBootSector->Bpb.Ntfs.cClustersPerMftRecord;
+ Log2(("NTFS BPB: cbDefaultIndexNode=%#x\n", pThis->cbDefaultIndexNode));
+
+ pThis->uSerialNo = RT_LE2H_U64(pBootSector->Bpb.Ntfs.uSerialNumber);
+ Log2(("NTFS BPB: uSerialNo=%#x\n", pThis->uSerialNo));
+
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFsNtfsVolOpen(RTVFSFILE hVfsFileIn, uint32_t fMntFlags, uint32_t fNtfsFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ AssertReturn(!(fMntFlags & ~RTVFSMNT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!fNtfsFlags, VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a VFS instance and initialize the data so rtFsNtfsVol_Close works.
+ */
+ RTVFS hVfs;
+ PRTFSNTFSVOL pThis;
+ int rc = RTVfsNew(&g_rtFsNtfsVolOps, sizeof(*pThis), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsBacking = hVfsFileIn;
+ pThis->hVfsSelf = hVfs;
+ pThis->fMntFlags = fMntFlags;
+ pThis->fNtfsFlags = fNtfsFlags;
+ RTListInit(&pThis->CoreInUseHead);
+ RTListInit(&pThis->CoreUnusedHead);
+ RTListInit(&pThis->IdxNodeUnusedHead);
+
+ rc = RTVfsFileQuerySize(pThis->hVfsBacking, &pThis->cbBacking);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvBuf = RTMemTmpAlloc(_64K);
+ if (pvBuf)
+ {
+ rc = rtFsNtfsVolLoadAndParseBootsector(pThis, pvBuf, _64K, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadMft(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadVolumeInfo(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadBitmap(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadUpCase(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsNtfsVolLoadRootDir(pThis, pErrInfo);
+ RTMemTmpFree(pvBuf);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfs = hVfs;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+
+ RTVfsRelease(hVfs);
+ *phVfs = NIL_RTVFS;
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainNtfsVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
+ return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
+ if ( pElement->enmType != RTVFSOBJTYPE_VFS
+ && pElement->enmType != RTVFSOBJTYPE_DIR)
+ return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly ? RTVFSMNT_F_READ_ONLY : 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainNtfsVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError);
+
+ int rc;
+ RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
+ if (hVfsFileIn != NIL_RTVFSFILE)
+ {
+ RTVFS hVfs;
+ rc = RTFsNtfsVolOpen(hVfsFileIn, (uint32_t)pElement->uProvider, (uint32_t)(pElement->uProvider >> 32), &hVfs, pErrInfo);
+ RTVfsFileRelease(hVfsFileIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromVfs(hVfs);
+ RTVfsRelease(hVfs);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainNtfsVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider
+ || !pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'file'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainNtfsVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "ntfs",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a NTFS file system, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainNtfsVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainNtfsVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainNtfsVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainNtfsVolReg, rtVfsChainNtfsVolReg);
+
diff --git a/src/VBox/Runtime/common/fs/xfsvfs.cpp b/src/VBox/Runtime/common/fs/xfsvfs.cpp
new file mode 100644
index 00000000..8d430f1b
--- /dev/null
+++ b/src/VBox/Runtime/common/fs/xfsvfs.cpp
@@ -0,0 +1,2460 @@
+/* $Id: xfsvfs.cpp $ */
+/** @file
+ * IPRT - XFS Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include <iprt/fsvfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/formats/xfs.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The maximum allocation group cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSXFS_MAX_AG_CACHE_SIZE _512K
+#else
+# define RTFSXFS_MAX_AG_CACHE_SIZE _128K
+#endif
+/** The maximum inode cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSXFS_MAX_INODE_CACHE_SIZE _512K
+#else
+# define RTFSXFS_MAX_INODE_CACHE_SIZE _128K
+#endif
+/** The maximum extent tree cache size (in bytes). */
+#if ARCH_BITS >= 64
+# define RTFSXFS_MAX_BLOCK_CACHE_SIZE _512K
+#else
+# define RTFSXFS_MAX_BLOCK_CACHE_SIZE _128K
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the XFS filesystem data. */
+typedef struct RTFSXFSVOL *PRTFSXFSVOL;
+
+
+/**
+ * Cached allocation group descriptor data.
+ */
+typedef struct RTFSXFSAG
+{
+ /** AVL tree node, indexed by the allocation group number. */
+ AVLU32NODECORE Core;
+ /** List node for the LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** @todo */
+} RTFSXFSAG;
+/** Pointer to allocation group descriptor data. */
+typedef RTFSXFSAG *PRTFSXFSAG;
+
+
+/**
+ * In-memory inode.
+ */
+typedef struct RTFSXFSINODE
+{
+ /** AVL tree node, indexed by the inode number. */
+ AVLU64NODECORE Core;
+ /** List node for the inode LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** Byte offset in the backing file where the inode is stored.. */
+ uint64_t offInode;
+ /** Inode data. */
+ RTFSOBJINFO ObjInfo;
+ /** Inode data fork format. */
+ uint8_t enmFormat;
+ /** Inode flags. */
+ uint16_t fFlags;
+ /** Inode version. */
+ uint8_t uVersion;
+ /** Number of extents in the data fork for XFS_INODE_FORMAT_EXTENTS. */
+ uint32_t cExtentsData;
+ /** Raw inode data. */
+ uint8_t abData[1];
+} RTFSXFSINODE;
+/** Pointer to an in-memory inode. */
+typedef RTFSXFSINODE *PRTFSXFSINODE;
+
+
+/**
+ * Block cache entry.
+ */
+typedef struct RTFSXFSBLOCKENTRY
+{
+ /** AVL tree node, indexed by the filesystem block number. */
+ AVLU64NODECORE Core;
+ /** List node for the inode LRU list used for eviction. */
+ RTLISTNODE NdLru;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** The block data. */
+ uint8_t abData[1];
+} RTFSXFSBLOCKENTRY;
+/** Pointer to a block cache entry. */
+typedef RTFSXFSBLOCKENTRY *PRTFSXFSBLOCKENTRY;
+
+
+/**
+ * Open directory instance.
+ */
+typedef struct RTFSXFSDIR
+{
+ /** Volume this directory belongs to. */
+ PRTFSXFSVOL pVol;
+ /** The underlying inode structure. */
+ PRTFSXFSINODE pInode;
+ /** Set if we've reached the end of the directory enumeration. */
+ bool fNoMoreFiles;
+ /** Current offset into the directory where the next entry should be read. */
+ uint64_t offEntry;
+ /** Next entry index (for logging purposes). */
+ uint32_t idxEntry;
+} RTFSXFSDIR;
+/** Pointer to an open directory instance. */
+typedef RTFSXFSDIR *PRTFSXFSDIR;
+
+
+/**
+ * Open file instance.
+ */
+typedef struct RTFSXFSFILE
+{
+ /** Volume this directory belongs to. */
+ PRTFSXFSVOL pVol;
+ /** The underlying inode structure. */
+ PRTFSXFSINODE pInode;
+ /** Current offset into the file for I/O. */
+ RTFOFF offFile;
+} RTFSXFSFILE;
+/** Pointer to an open file instance. */
+typedef RTFSXFSFILE *PRTFSXFSFILE;
+
+
+/**
+ * XFS filesystem volume.
+ */
+typedef struct RTFSXFSVOL
+{
+ /** Handle to itself. */
+ RTVFS hVfsSelf;
+ /** The file, partition, or whatever backing the ext volume. */
+ RTVFSFILE hVfsBacking;
+ /** The size of the backing thingy. */
+ uint64_t cbBacking;
+
+ /** RTVFSMNT_F_XXX. */
+ uint32_t fMntFlags;
+ /** RTFSXFSVFS_F_XXX (currently none defined). */
+ uint32_t fXfsFlags;
+
+ /** Size of one sector. */
+ size_t cbSector;
+ /** Size of one block. */
+ size_t cbBlock;
+ /** Number of bits to shift for converting a block number to byte offset. */
+ uint32_t cBlockShift;
+ /** Number of blocks per allocation group. */
+ XFSAGNUMBER cBlocksPerAg;
+ /** Number of blocks per allocation group as log2. */
+ uint32_t cAgBlocksLog;
+ /** Number of allocation groups for this volume. */
+ uint32_t cAgs;
+ /** inode of the root directory. */
+ XFSINO uInodeRoot;
+ /** Inode size in bytes. */
+ size_t cbInode;
+ /** Number of inodes per block. */
+ uint32_t cInodesPerBlock;
+ /** Number of inodes per block as log2. */
+ uint32_t cInodesPerBlockLog;
+
+ /** @name Allocation group cache.
+ * @{ */
+ /** LRU list anchor. */
+ RTLISTANCHOR LstAgLru;
+ /** Root of the cached allocation group tree. */
+ AVLU32TREE AgRoot;
+ /** Size of the cached allocation groups. */
+ size_t cbAgs;
+ /** @} */
+
+ /** @name Inode cache.
+ * @{ */
+ /** LRU list anchor for the inode cache. */
+ RTLISTANCHOR LstInodeLru;
+ /** Root of the cached inode tree. */
+ AVLU64TREE InodeRoot;
+ /** Size of the cached inodes. */
+ size_t cbInodes;
+ /** @} */
+
+ /** @name Block cache.
+ * @{ */
+ /** LRU list anchor for the block cache. */
+ RTLISTANCHOR LstBlockLru;
+ /** Root of the cached block tree. */
+ AVLU64TREE BlockRoot;
+ /** Size of cached blocks. */
+ size_t cbBlocks;
+ /** @} */
+} RTFSXFSVOL;
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtFsXfsVol_OpenDirByInode(PRTFSXFSVOL pThis, uint32_t iInode, PRTVFSDIR phVfsDir);
+
+#ifdef LOG_ENABLED
+/**
+ * Logs the XFS filesystem superblock.
+ *
+ * @param iAg The allocation group number for the given super block.
+ * @param pSb Pointer to the superblock.
+ */
+static void rtFsXfsSb_Log(uint32_t iAg, PCXFSSUPERBLOCK pSb)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("XFS: Superblock %#RX32:\n", iAg));
+ Log2(("XFS: u32Magic %#RX32\n", RT_BE2H_U32(pSb->u32Magic)));
+ Log2(("XFS: cbBlock %RU32\n", RT_BE2H_U32(pSb->cbBlock)));
+ Log2(("XFS: cBlocks %RU64\n", RT_BE2H_U64(pSb->cBlocks)));
+ Log2(("XFS: cBlocksRtDev %RU64\n", RT_BE2H_U64(pSb->cBlocksRtDev)));
+ Log2(("XFS: cExtentsRtDev %RU64\n", RT_BE2H_U64(pSb->cExtentsRtDev)));
+ Log2(("XFS: abUuid <todo>\n"));
+ Log2(("XFS: uBlockJournal %#RX64\n", RT_BE2H_U64(pSb->uBlockJournal)));
+ Log2(("XFS: uInodeRoot %#RX64\n", RT_BE2H_U64(pSb->uInodeRoot)));
+ Log2(("XFS: uInodeBitmapRtExt %#RX64\n", RT_BE2H_U64(pSb->uInodeBitmapRtExt)));
+ Log2(("XFS: uInodeBitmapSummary %#RX64\n", RT_BE2H_U64(pSb->uInodeBitmapSummary)));
+ Log2(("XFS: cRtExtent %RU32\n", RT_BE2H_U32(pSb->cRtExtent)));
+ Log2(("XFS: cAgBlocks %RU32\n", RT_BE2H_U32(pSb->cAgBlocks)));
+ Log2(("XFS: cAg %RU32\n", RT_BE2H_U32(pSb->cAg)));
+ Log2(("XFS: cRtBitmapBlocks %RU32\n", RT_BE2H_U32(pSb->cRtBitmapBlocks)));
+ Log2(("XFS: cJournalBlocks %RU32\n", RT_BE2H_U32(pSb->cJournalBlocks)));
+ Log2(("XFS: fVersion %#RX16%s%s%s%s%s%s%s%s%s%s%s\n", RT_BE2H_U16(pSb->fVersion),
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_ATTR ? " attr" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_NLINK ? " nlink" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_QUOTA ? " quota" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_ALIGN ? " align" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_DALIGN ? " dalign" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_SHARED ? " shared" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_LOGV2 ? " logv2" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_SECTOR ? " sector" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_EXTFLG ? " extflg" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_DIRV2 ? " dirv2" : "",
+ RT_BE2H_U16(pSb->fVersion) & XFS_SB_VERSION_F_FEAT2 ? " feat2" : ""));
+ Log2(("XFS: cbSector %RU16\n", RT_BE2H_U16(pSb->cbSector)));
+ Log2(("XFS: cbInode %RU16\n", RT_BE2H_U16(pSb->cbInode)));
+ Log2(("XFS: cIndoesPerBlock %RU16\n", RT_BE2H_U16(pSb->cInodesPerBlock)));
+ Log2(("XFS: achFsName %12s\n", &pSb->achFsName[0]));
+ Log2(("XFS: cBlockSzLog %RU8\n", pSb->cBlockSzLog));
+ Log2(("XFS: cSectorSzLog %RU8\n", pSb->cSectorSzLog));
+ Log2(("XFS: cInodeSzLog %RU8\n", pSb->cInodeSzLog));
+ Log2(("XFS: cInodesPerBlockLog %RU8\n", pSb->cInodesPerBlockLog));
+ Log2(("XFS: cAgBlocksLog %RU8\n", pSb->cAgBlocksLog));
+ Log2(("XFS: cExtentsRtDevLog %RU8\n", pSb->cExtentsRtDevLog));
+ Log2(("XFS: fInProgress %RU8\n", pSb->fInProgress));
+ Log2(("XFS: cInodeMaxPct %RU8\n", pSb->cInodeMaxPct));
+ Log2(("XFS: cInodesGlobal %#RX64\n", RT_BE2H_U64(pSb->cInodesGlobal)));
+ Log2(("XFS: cInodesGlobalFree %#RX64\n", RT_BE2H_U64(pSb->cInodesGlobalFree)));
+ Log2(("XFS: cBlocksFree %#RX64\n", RT_BE2H_U64(pSb->cBlocksFree)));
+ Log2(("XFS: cExtentsRtFree %#RX64\n", RT_BE2H_U64(pSb->cExtentsRtFree)));
+ Log2(("XFS: uInodeQuotaUsr %#RX64\n", RT_BE2H_U64(pSb->uInodeQuotaUsr)));
+ Log2(("XFS: uInodeQuotaGrp %#RX64\n", RT_BE2H_U64(pSb->uInodeQuotaGrp)));
+ Log2(("XFS: fQuotaFlags %#RX16\n", RT_BE2H_U16(pSb->fQuotaFlags)));
+ Log2(("XFS: fFlagsMisc %#RX8\n", pSb->fFlagsMisc));
+ Log2(("XFS: uSharedVn %#RX8\n", pSb->uSharedVn));
+ Log2(("XFS: cBlocksInodeAlignment %#RX32\n", RT_BE2H_U32(pSb->cBlocksInodeAlignment)));
+ Log2(("XFS: cBlocksRaidStripe %#RX32\n", RT_BE2H_U32(pSb->cBlocksRaidStripe)));
+ Log2(("XFS: cBlocksRaidWidth %#RX32\n", RT_BE2H_U32(pSb->cBlocksRaidWidth)));
+ Log2(("XFS: cDirBlockAllocLog %RU8\n", pSb->cDirBlockAllocLog));
+ Log2(("XFS: cLogDevSubVolSectorSzLog %RU8\n", pSb->cLogDevSubVolSectorSzLog));
+ Log2(("XFS: cLogDevSectorSzLog %RU16\n", RT_BE2H_U16(pSb->cLogDevSectorSzLog)));
+ Log2(("XFS: cLogDevRaidStripe %RU32\n", RT_BE2H_U32(pSb->cLogDevRaidStripe)));
+ Log2(("XFS: fFeatures2 %#RX32\n", RT_BE2H_U32(pSb->fFeatures2)));
+ Log2(("XFS: fFeaturesRw %#RX32\n", RT_BE2H_U32(pSb->fFeaturesRw)));
+ Log2(("XFS: fFeaturesRo %#RX32\n", RT_BE2H_U32(pSb->fFeaturesRo)));
+ Log2(("XFS: fFeaturesIncompatRw %#RX32\n", RT_BE2H_U32(pSb->fFeaturesIncompatRw)));
+ Log2(("XFS: fFeaturesJrnlIncompatRw %#RX32\n", RT_BE2H_U32(pSb->fFeaturesJrnlIncompatRw)));
+ Log2(("XFS: u32Chksum %#RX32\n", RT_BE2H_U32(pSb->u32Chksum)));
+ Log2(("XFS: u32SparseInodeAlignment %#RX32\n", RT_BE2H_U32(pSb->u32SparseInodeAlignment)));
+ Log2(("XFS: uInodeProjectQuota %#RX64\n", RT_BE2H_U64(pSb->uInodeProjectQuota)));
+ Log2(("XFS: uJrnlSeqSbUpdate %#RX64\n", RT_BE2H_U64(pSb->uJrnlSeqSbUpdate)));
+ Log2(("XFS: abUuidMeta <todo>\n"));
+ Log2(("XFS: uInodeRm %#RX64\n", RT_BE2H_U64(pSb->uInodeRm)));
+ }
+}
+
+#if 0 /* unused */
+/**
+ * Logs a AG free space block.
+ *
+ * @param iAg The allocation group number for the given free space block.
+ * @param pAgf The AG free space block.
+ */
+static void rtFsXfsAgf_Log(uint32_t iAg, PCXFSAGF pAgf)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("XFS: AGF %#RX32:\n", iAg));
+ Log2(("XFS: u32Magic %#RX32\n", RT_BE2H_U32(pAgf->u32Magic)));
+ Log2(("XFS: uVersion %#RX32\n", RT_BE2H_U32(pAgf->uVersion)));
+ Log2(("XFS: uSeqNo %#RX32\n", RT_BE2H_U32(pAgf->uSeqNo)));
+ Log2(("XFS: cLengthBlocks %#RX32\n", RT_BE2H_U32(pAgf->cLengthBlocks)));
+ Log2(("XFS: auRoots[0] %#RX32\n", RT_BE2H_U32(pAgf->auRoots[0])));
+ Log2(("XFS: auRoots[1] %#RX32\n", RT_BE2H_U32(pAgf->auRoots[1])));
+ Log2(("XFS: auRoots[2] %#RX32\n", RT_BE2H_U32(pAgf->auRoots[2])));
+ Log2(("XFS: acLvls[0] %RU32\n", RT_BE2H_U32(pAgf->acLvls[0])));
+ Log2(("XFS: acLvls[1] %RU32\n", RT_BE2H_U32(pAgf->acLvls[1])));
+ Log2(("XFS: acLvls[2] %RU32\n", RT_BE2H_U32(pAgf->acLvls[2])));
+ Log2(("XFS: idxFreeListFirst %RU32\n", RT_BE2H_U32(pAgf->idxFreeListFirst)));
+ Log2(("XFS: idxFreeListLast %RU32\n", RT_BE2H_U32(pAgf->idxFreeListLast)));
+ Log2(("XFS: cFreeListBlocks %RU32\n", RT_BE2H_U32(pAgf->cFreeListBlocks)));
+ Log2(("XFS: cFreeBlocks %RU32\n", RT_BE2H_U32(pAgf->cFreeBlocks)));
+ Log2(("XFS: cFreeBlocksLongest %RU32\n", RT_BE2H_U32(pAgf->cFreeBlocksLongest)));
+ Log2(("XFS: cBlocksBTrees %RU32\n", RT_BE2H_U32(pAgf->cBlocksBTrees)));
+ Log2(("XFS: abUuid <todo>\n"));
+ Log2(("XFS: cBlocksRevMap %RU32\n", RT_BE2H_U32(pAgf->cBlocksRevMap)));
+ Log2(("XFS: cBlocksRefcountBTree %RU32\n", RT_BE2H_U32(pAgf->cBlocksRefcountBTree)));
+ Log2(("XFS: uRootRefcount %#RX32\n", RT_BE2H_U32(pAgf->uRootRefcount)));
+ Log2(("XFS: cLvlRefcount %RU32\n", RT_BE2H_U32(pAgf->cLvlRefcount)));
+ Log2(("XFS: uSeqNoLastWrite %#RX64\n", RT_BE2H_U64(pAgf->uSeqNoLastWrite)));
+ Log2(("XFS: uChkSum %#RX32\n", RT_BE2H_U32(pAgf->uChkSum)));
+ }
+}
+#endif
+
+/**
+ * Loads an AG inode information block.
+ *
+ * @param iAg The allocation group number for the given inode information block.
+ * @param pAgi The AG inode information block.
+ */
+static void rtFsXfsAgi_Log(uint32_t iAg, PCXFSAGI pAgi)
+{
+ if (LogIs2Enabled())
+ {
+ Log2(("XFS: AGI %#RX32:\n", iAg));
+ Log2(("XFS: u32Magic %#RX32\n", RT_BE2H_U32(pAgi->u32Magic)));
+ Log2(("XFS: uVersion %#RX32\n", RT_BE2H_U32(pAgi->uVersion)));
+ Log2(("XFS: uSeqNo %#RX32\n", RT_BE2H_U32(pAgi->uSeqNo)));
+ Log2(("XFS: cLengthBlocks %#RX32\n", RT_BE2H_U32(pAgi->cLengthBlocks)));
+ Log2(("XFS: cInodesAlloc %#RX32\n", RT_BE2H_U32(pAgi->cInodesAlloc)));
+ Log2(("XFS: uRootInode %#RX32\n", RT_BE2H_U32(pAgi->uRootInode)));
+ Log2(("XFS: cLvlsInode %RU32\n", RT_BE2H_U32(pAgi->cLvlsInode)));
+ Log2(("XFS: uInodeNew %#RX32\n", RT_BE2H_U32(pAgi->uInodeNew)));
+ Log2(("XFS: uInodeDir %#RX32\n", RT_BE2H_U32(pAgi->uInodeDir)));
+ Log2(("XFS: au32HashUnlinked[0..63] <todo>\n"));
+ Log2(("XFS: abUuid <todo>\n"));
+ Log2(("XFS: uChkSum %#RX32\n", RT_BE2H_U32(pAgi->uChkSum)));
+ Log2(("XFS: uSeqNoLastWrite %#RX64\n", RT_BE2H_U64(pAgi->uSeqNoLastWrite)));
+ Log2(("XFS: uRootFreeInode %#RX32\n", RT_BE2H_U32(pAgi->uRootFreeInode)));
+ Log2(("XFS: cLvlsFreeInode %RU32\n", RT_BE2H_U32(pAgi->cLvlsFreeInode)));
+ }
+}
+
+
+/**
+ * Logs a XFS filesystem inode.
+ *
+ * @param pThis The XFS volume instance.
+ * @param iInode Inode number.
+ * @param pInode Pointer to the inode.
+ */
+static void rtFsXfsInode_Log(PRTFSXFSVOL pThis, XFSINO iInode, PCXFSINODECORE pInode)
+{
+ RT_NOREF(pThis);
+
+ if (LogIs2Enabled())
+ {
+ RTTIMESPEC Spec;
+ char sz[80];
+
+ Log2(("XFS: Inode %#RX64:\n", iInode));
+ Log2(("XFS: u16Magic %#RX16\n", RT_BE2H_U16(pInode->u16Magic)));
+ Log2(("XFS: fMode %#RX16\n", RT_BE2H_U16(pInode->fMode)));
+ Log2(("XFS: iVersion %#RX8\n", pInode->iVersion));
+ Log2(("XFS: enmFormat %#RX8\n", pInode->enmFormat));
+ Log2(("XFS: cOnLinks %RU16\n", RT_BE2H_U16(pInode->cOnLinks)));
+ Log2(("XFS: uUid %#RX32\n", RT_BE2H_U32(pInode->uUid)));
+ Log2(("XFS: uGid %#RX32\n", RT_BE2H_U32(pInode->uGid)));
+ Log2(("XFS: cLinks %#RX32\n", RT_BE2H_U32(pInode->cLinks)));
+ Log2(("XFS: uProjIdLow %#RX16\n", RT_BE2H_U16(pInode->uProjIdLow)));
+ Log2(("XFS: uProjIdHigh %#RX16\n", RT_BE2H_U16(pInode->uProjIdHigh)));
+ Log2(("XFS: cFlush %RU16\n", RT_BE2H_U16(pInode->cFlush)));
+ Log2(("XFS: TsLastAccessed %#RX32:%#RX32 %s\n", RT_BE2H_U32(pInode->TsLastAccessed.cSecEpoch),
+ RT_BE2H_U32(pInode->TsLastAccessed.cNanoSec),
+ RTTimeSpecToString(RTTimeSpecAddNano(RTTimeSpecSetSeconds(&Spec, RT_BE2H_U32(pInode->TsLastAccessed.cSecEpoch)),
+ RT_BE2H_U32(pInode->TsLastAccessed.cNanoSec)),
+ sz, sizeof(sz))));
+ Log2(("XFS: TsLastModified %#RX32:%#RX32 %s\n", RT_BE2H_U32(pInode->TsLastModified.cSecEpoch),
+ RT_BE2H_U32(pInode->TsLastModified.cNanoSec),
+ RTTimeSpecToString(RTTimeSpecAddNano(RTTimeSpecSetSeconds(&Spec, RT_BE2H_U32(pInode->TsLastModified.cSecEpoch)),
+ RT_BE2H_U32(pInode->TsLastModified.cNanoSec)),
+ sz, sizeof(sz))));
+ Log2(("XFS: TsCreatedModified %#RX32:%#RX32 %s\n", RT_BE2H_U32(pInode->TsCreatedModified.cSecEpoch),
+ RT_BE2H_U32(pInode->TsCreatedModified.cNanoSec),
+ RTTimeSpecToString(RTTimeSpecAddNano(RTTimeSpecSetSeconds(&Spec, RT_BE2H_U32(pInode->TsCreatedModified.cSecEpoch)),
+ RT_BE2H_U32(pInode->TsCreatedModified.cNanoSec)),
+ sz, sizeof(sz))));
+ Log2(("XFS: cbInode %#RX64\n", RT_BE2H_U64(pInode->cbInode)));
+ Log2(("XFS: cBlocks %#RX64\n", RT_BE2H_U64(pInode->cBlocks)));
+ Log2(("XFS: cExtentBlocksMin %#RX32\n", RT_BE2H_U32(pInode->cExtentBlocksMin)));
+ Log2(("XFS: cExtentsData %#RX32\n", RT_BE2H_U32(pInode->cExtentsData)));
+ Log2(("XFS: cExtentsAttr %#RX16\n", RT_BE2H_U16(pInode->cExtentsAttr)));
+ Log2(("XFS: offAttrFork %#RX8\n", pInode->offAttrFork));
+ Log2(("XFS: enmFormatAttr %#RX8\n", pInode->enmFormatAttr));
+ Log2(("XFS: fEvtMaskDmig %#RX32\n", RT_BE2H_U32(pInode->fEvtMaskDmig)));
+ Log2(("XFS: uStateDmig %#RX16\n", RT_BE2H_U16(pInode->uStateDmig)));
+ Log2(("XFS: fFlags %#RX16\n", RT_BE2H_U16(pInode->fFlags)));
+ Log2(("XFS: cGeneration %#RX32\n", RT_BE2H_U32(pInode->cGeneration)));
+ Log2(("XFS: offBlockUnlinkedNext %#RX32\n", RT_BE2H_U32(pInode->offBlockUnlinkedNext)));
+ Log2(("XFS: uChkSum %#RX32\n", RT_BE2H_U32(pInode->uChkSum)));
+ Log2(("XFS: cAttrChanges %#RX64\n", RT_BE2H_U64(pInode->cAttrChanges)));
+ Log2(("XFS: uFlushSeqNo %#RX64\n", RT_BE2H_U64(pInode->uFlushSeqNo)));
+ Log2(("XFS: fFlags2 %#RX64\n", RT_BE2H_U64(pInode->fFlags2)));
+ Log2(("XFS: cExtentCowMin %#RX32\n", RT_BE2H_U32(pInode->cExtentCowMin)));
+ Log2(("XFS: TsCreation %#RX32:%#RX32 %s\n", RT_BE2H_U32(pInode->TsCreation.cSecEpoch),
+ RT_BE2H_U32(pInode->TsCreation.cNanoSec),
+ RTTimeSpecToString(RTTimeSpecAddNano(RTTimeSpecSetSeconds(&Spec, RT_BE2H_U32(pInode->TsCreation.cSecEpoch)),
+ RT_BE2H_U32(pInode->TsCreation.cNanoSec)),
+ sz, sizeof(sz))));
+ Log2(("XFS: uInode %#RX64\n", RT_BE2H_U64(pInode->uInode)));
+ Log2(("XFS: abUuid <todo>\n"));
+ }
+}
+
+
+#if 0
+/**
+ * Logs a XFS filesystem directory entry.
+ *
+ * @param pThis The XFS volume instance.
+ * @param idxDirEntry Directory entry index number.
+ * @param pDirEntry The directory entry.
+ */
+static void rtFsXfsDirEntry_Log(PRTFSXFSVOL pThis, uint32_t idxDirEntry, PCXFSDIRENTRYEX pDirEntry)
+{
+ if (LogIs2Enabled())
+ {
+ }
+}
+#endif
+#endif
+
+
+/**
+ * Converts a block number to a byte offset.
+ *
+ * @returns Offset in bytes for the given block number.
+ * @param pThis The XFS volume instance.
+ * @param iBlock The block number to convert.
+ */
+DECLINLINE(uint64_t) rtFsXfsBlockIdxToDiskOffset(PRTFSXFSVOL pThis, uint64_t iBlock)
+{
+ return iBlock << pThis->cBlockShift;
+}
+
+
+/**
+ * Converts a byte offset to a block number.
+ *
+ * @returns Block number.
+ * @param pThis The XFS volume instance.
+ * @param iBlock The offset to convert.
+ */
+DECLINLINE(uint64_t) rtFsXfsDiskOffsetToBlockIdx(PRTFSXFSVOL pThis, uint64_t off)
+{
+ return off >> pThis->cBlockShift;
+}
+
+
+/**
+ * Splits the given absolute inode number into the AG number, block inside the AG
+ * and the offset into the block where to find the inode structure.
+ *
+ * @param pThis The XFS volume instance.
+ * @param iInode The inode to split.
+ * @param piAg Where to store the AG number.
+ * @param puBlock Where to store the block number inside the AG.
+ * @param poffBlock Where to store the offset into the block.
+ */
+DECLINLINE(void) rtFsXfsInodeSplitAbs(PRTFSXFSVOL pThis, XFSINO iInode,
+ uint32_t *piAg, uint32_t *puBlock,
+ uint32_t *poffBlock)
+{
+ *poffBlock = iInode & (pThis->cInodesPerBlock - 1);
+ iInode >>= pThis->cInodesPerBlockLog;
+ *puBlock = iInode & (RT_BIT_32(pThis->cAgBlocksLog) - 1); /* Using the log2 value here as it is rounded. */
+ iInode >>= RT_BIT_32(pThis->cAgBlocksLog) - 1;
+ *piAg = (uint32_t)iInode;
+}
+
+
+/**
+ * Returns the size of the core inode structure on disk for the given version.
+ *
+ * @returns Size of the on disk inode structure in bytes.
+ * @param uVersion The inode version.
+ */
+DECLINLINE(size_t) rtFsXfsInodeGetSz(uint8_t uVersion)
+{
+ if (uVersion < 3)
+ return RT_OFFSETOF(XFSINODECORE, uChkSum);
+ return sizeof(XFSINODECORE);
+}
+
+
+/**
+ * Returns the pointer to the data fork of the given inode.
+ *
+ * @returns Pointer to the data fork.
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode to get the data fork for.
+ * @param pcb Where to store the size of the remaining data area beginning with the fork.
+ */
+DECLINLINE(void *) rtFsXfsInodeGetDataFork(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode, size_t *pcb)
+{
+ size_t offDataFork = rtFsXfsInodeGetSz(pInode->uVersion);
+ size_t cbInodeData = pThis->cbInode - offDataFork;
+ if (pcb)
+ *pcb = cbInodeData;
+
+ return &pInode->abData[offDataFork];
+}
+
+
+/**
+ * Allocates a new block group.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param cbAlloc How much to allocate.
+ * @param iBlockGroup Block group number.
+ */
+static PRTFSXFSBLOCKENTRY rtFsXfsVol_BlockAlloc(PRTFSXFSVOL pThis, size_t cbAlloc, uint64_t iBlock)
+{
+ PRTFSXFSBLOCKENTRY pBlock = (PRTFSXFSBLOCKENTRY)RTMemAllocZ(cbAlloc);
+ if (RT_LIKELY(pBlock))
+ {
+ pBlock->Core.Key = iBlock;
+ pBlock->cRefs = 0;
+ pThis->cbBlocks += cbAlloc;
+ }
+
+ return pBlock;
+}
+
+
+/**
+ * Returns a new block entry utilizing the cache if possible.
+ *
+ * @returns Pointer to the new block entry or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iBlock Block number.
+ */
+static PRTFSXFSBLOCKENTRY rtFsXfsVol_BlockGetNew(PRTFSXFSVOL pThis, uint64_t iBlock)
+{
+ PRTFSXFSBLOCKENTRY pBlock = NULL;
+ size_t cbAlloc = RT_UOFFSETOF_DYN(RTFSXFSBLOCKENTRY, abData[pThis->cbBlock]);
+ if (pThis->cbBlocks + cbAlloc <= RTFSXFS_MAX_BLOCK_CACHE_SIZE)
+ pBlock = rtFsXfsVol_BlockAlloc(pThis, cbAlloc, iBlock);
+ else
+ {
+ pBlock = RTListRemoveLast(&pThis->LstBlockLru, RTFSXFSBLOCKENTRY, NdLru);
+ if (!pBlock)
+ pBlock = rtFsXfsVol_BlockAlloc(pThis, cbAlloc, iBlock);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->BlockRoot, pBlock->Core.Key);
+ Assert(pCore == &pBlock->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pBlock->cRefs);
+ pBlock->Core.Key = iBlock;
+ pBlock->cRefs = 1;
+
+ return pBlock;
+}
+
+
+/**
+ * Frees the given block.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pBlock The block to free.
+ */
+static void rtFsXfsVol_BlockFree(PRTFSXFSVOL pThis, PRTFSXFSBLOCKENTRY pBlock)
+{
+ Assert(!pBlock->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the block group
+ * is freed right away.
+ */
+ if (pThis->cbBlocks <= RTFSXFS_MAX_BLOCK_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstBlockLru, &pBlock->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->BlockRoot, pBlock->Core.Key);
+ Assert(pCore == &pBlock->Core); RT_NOREF(pCore);
+ RTMemFree(pBlock);
+ pThis->cbBlocks -= RT_UOFFSETOF_DYN(RTFSXFSBLOCKENTRY, abData[pThis->cbBlock]);
+ }
+}
+
+
+/**
+ * Gets the specified block data from the volume.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param iBlock The filesystem block to load.
+ * @param ppBlock Where to return the pointer to the block entry on success.
+ * @param ppvData Where to return the pointer to the block data on success.
+ */
+static int rtFsXfsVol_BlockLoad(PRTFSXFSVOL pThis, uint64_t iBlock, PRTFSXFSBLOCKENTRY *ppBlock, void **ppvData)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the block group from the cache first. */
+ PRTFSXFSBLOCKENTRY pBlock = (PRTFSXFSBLOCKENTRY)RTAvlU64Get(&pThis->BlockRoot, iBlock);
+ if (!pBlock)
+ {
+ /* Slow path, load from disk. */
+ pBlock = rtFsXfsVol_BlockGetNew(pThis, iBlock);
+ if (RT_LIKELY(pBlock))
+ {
+ uint64_t offRead = rtFsXfsBlockIdxToDiskOffset(pThis, iBlock);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pBlock->abData[0], pThis->cbBlock, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ bool fIns = RTAvlU64Insert(&pThis->BlockRoot, &pBlock->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pBlock->cRefs);
+ if (cRefs == 1) /* Blocks get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pBlock->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppBlock = pBlock;
+ *ppvData = &pBlock->abData[0];
+ }
+ else if (pBlock)
+ {
+ ASMAtomicDecU32(&pBlock->cRefs);
+ rtFsXfsVol_BlockFree(pThis, pBlock); /* Free the block. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given block.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pBlock The block to release.
+ */
+static void rtFsXfsVol_BlockRelease(PRTFSXFSVOL pThis, PRTFSXFSBLOCKENTRY pBlock)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pBlock->cRefs);
+ if (!cRefs)
+ rtFsXfsVol_BlockFree(pThis, pBlock);
+}
+
+#if 0 /* unused */
+/**
+ * Allocates a new alloction group.
+ *
+ * @returns Pointer to the new allocation group descriptor or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iAG Allocation group number.
+ */
+static PRTFSXFSAG rtFsXfsAg_Alloc(PRTFSXFSVOL pThis, uint32_t iAg)
+{
+ PRTFSXFSAG pAg = (PRTFSXFSAG)RTMemAllocZ(sizeof(RTFSXFSAG));
+ if (RT_LIKELY(pAg))
+ {
+ pAg->Core.Key = iAg;
+ pAg->cRefs = 0;
+ pThis->cbAgs += sizeof(RTFSXFSAG);
+ }
+
+ return pAg;
+}
+
+
+/**
+ * Frees the given allocation group.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pAg The allocation group to free.
+ */
+static void rtFsXfsAg_Free(PRTFSXFSVOL pThis, PRTFSXFSAG pAg)
+{
+ Assert(!pAg->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the allocation group
+ * is freed right away.
+ */
+ if (pThis->cbAgs <= RTFSXFS_MAX_AG_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstAgLru, &pAg->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->AgRoot, pAg->Core.Key);
+ Assert(pCore == &pAg->Core); RT_NOREF(pCore);
+ RTMemFree(pAg);
+ pThis->cbAgs -= sizeof(RTFSXFSAG);
+ }
+}
+
+
+/**
+ * Returns a new block group utilizing the cache if possible.
+ *
+ * @returns Pointer to the new block group descriptor or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iAg Allocation group number.
+ */
+static PRTFSXFSAG rtFsXfsAg_GetNew(PRTFSXFSVOL pThis, uint32_t iAg)
+{
+ PRTFSXFSAG pAg = NULL;
+ if (pThis->cbAgs + sizeof(RTFSXFSAG) <= RTFSXFS_MAX_AG_CACHE_SIZE)
+ pAg = rtFsXfsAg_Alloc(pThis, iAg);
+ else
+ {
+ pAg = RTListRemoveLast(&pThis->LstAgLru, RTFSXFSAG, NdLru);
+ if (!pAg)
+ pAg = rtFsXfsAg_Alloc(pThis, iAg);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU32NODECORE pCore = RTAvlU32Remove(&pThis->AgRoot, pAg->Core.Key);
+ Assert(pCore == &pAg->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pAg->cRefs);
+ pAg->Core.Key = iAg;
+ pAg->cRefs = 1;
+
+ return pAg;
+}
+
+
+/**
+ * Loads the given allocation group number and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param iAg The allocation group to load.
+ * @param ppAg Where to store the allocation group on success.
+ */
+static int rtFsXfsAg_Load(PRTFSXFSVOL pThis, uint32_t iAg, PRTFSXFSAG *ppAg)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertReturn(iAg < pThis->cAgs, VERR_VFS_BOGUS_FORMAT);
+
+ /* Try to fetch the allocation group from the cache first. */
+ PRTFSXFSAG pAg = (PRTFSXFSAG)RTAvlU32Get(&pThis->AgRoot, iAg);
+ if (!pAg)
+ {
+ /* Slow path, load from disk. */
+ pAg = rtFsXfsAg_GetNew(pThis, iAg);
+ if (RT_LIKELY(pAg))
+ {
+ uint64_t offRead = rtFsXfsBlockIdxToDiskOffset(pThis, iAg * pThis->cBlocksPerAg);
+ XFSSUPERBLOCK Sb;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &Sb, sizeof(Sb), NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsXfsSb_Log(iAg, &Sb);
+#endif
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pAg->cRefs);
+ if (cRefs == 1) /* Block groups get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pAg->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppAg = pAg;
+ else if (pAg)
+ {
+ ASMAtomicDecU32(&pAg->cRefs);
+ rtFsXfsAg_Free(pThis, pAg); /* Free the allocation group. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given allocation group.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pAg The allocation group to release.
+ */
+static void rtFsXfsAg_Release(PRTFSXFSVOL pThis, PRTFSXFSAG pAg)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pAg->cRefs);
+ if (!cRefs)
+ rtFsXfsAg_Free(pThis, pAg);
+}
+#endif
+
+/**
+ * Allocates a new inode.
+ *
+ * @returns Pointer to the new inode or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iInode Inode number.
+ */
+static PRTFSXFSINODE rtFsXfsInode_Alloc(PRTFSXFSVOL pThis, uint32_t iInode)
+{
+ size_t cbAlloc = RT_UOFFSETOF_DYN(RTFSXFSINODE, abData[pThis->cbInode]);
+ PRTFSXFSINODE pInode = (PRTFSXFSINODE)RTMemAllocZ(cbAlloc);
+ if (RT_LIKELY(pInode))
+ {
+ pInode->Core.Key = iInode;
+ pInode->cRefs = 0;
+ pThis->cbInodes += cbAlloc;
+ }
+
+ return pInode;
+}
+
+
+/**
+ * Frees the given inode.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode to free.
+ */
+static void rtFsXfsInode_Free(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode)
+{
+ Assert(!pInode->cRefs);
+
+ /*
+ * Put it into the cache if the limit wasn't exceeded, otherwise the inode
+ * is freed right away.
+ */
+ if (pThis->cbInodes <= RTFSXFS_MAX_INODE_CACHE_SIZE)
+ {
+ /* Put onto the LRU list. */
+ RTListPrepend(&pThis->LstInodeLru, &pInode->NdLru);
+ }
+ else
+ {
+ /* Remove from the tree and free memory. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->InodeRoot, pInode->Core.Key);
+ Assert(pCore == &pInode->Core); RT_NOREF(pCore);
+ RTMemFree(pInode);
+ pThis->cbInodes -= RT_UOFFSETOF_DYN(RTFSXFSINODE, abData[pThis->cbInode]);
+ }
+}
+
+
+/**
+ * Returns a new inodep utilizing the cache if possible.
+ *
+ * @returns Pointer to the new inode or NULL if out of memory.
+ * @param pThis The XFS volume instance.
+ * @param iInode Inode number.
+ */
+static PRTFSXFSINODE rtFsXfsInode_GetNew(PRTFSXFSVOL pThis, XFSINO iInode)
+{
+ PRTFSXFSINODE pInode = NULL;
+ if (pThis->cbInodes + RT_UOFFSETOF_DYN(RTFSXFSINODE, abData[pThis->cbInode]) <= RTFSXFS_MAX_INODE_CACHE_SIZE)
+ pInode = rtFsXfsInode_Alloc(pThis, iInode);
+ else
+ {
+ pInode = RTListRemoveLast(&pThis->LstInodeLru, RTFSXFSINODE, NdLru);
+ if (!pInode)
+ pInode = rtFsXfsInode_Alloc(pThis, iInode);
+ else
+ {
+ /* Remove the block group from the tree because it gets a new key. */
+ PAVLU64NODECORE pCore = RTAvlU64Remove(&pThis->InodeRoot, pInode->Core.Key);
+ Assert(pCore == &pInode->Core); RT_NOREF(pCore);
+ }
+ }
+
+ Assert(!pInode->cRefs);
+ pInode->Core.Key = iInode;
+ pInode->cRefs = 1;
+
+ return pInode;
+}
+
+
+/**
+ * Loads the given inode number and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param iInode The inode to load.
+ * @param ppInode Where to store the inode on success.
+ */
+static int rtFsXfsInode_Load(PRTFSXFSVOL pThis, XFSINO iInode, PRTFSXFSINODE *ppInode)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Try to fetch the inode from the cache first. */
+ PRTFSXFSINODE pInode = (PRTFSXFSINODE)RTAvlU64Get(&pThis->InodeRoot, iInode);
+ if (!pInode)
+ {
+ /* Slow path, load from disk. */
+ pInode = rtFsXfsInode_GetNew(pThis, iInode);
+ if (RT_LIKELY(pInode))
+ {
+ uint32_t iAg;
+ uint32_t uBlock;
+ uint32_t offBlock;
+
+ rtFsXfsInodeSplitAbs(pThis, iInode, &iAg, &uBlock, &offBlock);
+
+ uint64_t offRead = (iAg * pThis->cBlocksPerAg + uBlock) * pThis->cbBlock + offBlock;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead, &pInode->abData[0], pThis->cbInode, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ PCXFSINODECORE pInodeCore = (PCXFSINODECORE)&pInode->abData[0];
+
+#ifdef LOG_ENABLED
+ rtFsXfsInode_Log(pThis, iInode, pInodeCore);
+#endif
+
+ pInode->offInode = offRead;
+ pInode->fFlags = RT_BE2H_U16(pInodeCore->fFlags);
+ pInode->enmFormat = pInodeCore->enmFormat;
+ pInode->cExtentsData = RT_BE2H_U32(pInodeCore->cExtentsData);
+ pInode->ObjInfo.cbObject = RT_BE2H_U64(pInodeCore->cbInode);
+ pInode->ObjInfo.cbAllocated = RT_BE2H_U64(pInodeCore->cBlocks) * pThis->cbBlock;
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.AccessTime, RT_BE2H_U32(pInodeCore->TsLastAccessed.cSecEpoch));
+ RTTimeSpecAddNano(&pInode->ObjInfo.AccessTime, RT_BE2H_U32(pInodeCore->TsLastAccessed.cNanoSec));
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.ModificationTime, RT_BE2H_U32(pInodeCore->TsLastModified.cSecEpoch));
+ RTTimeSpecAddNano(&pInode->ObjInfo.ModificationTime, RT_BE2H_U32(pInodeCore->TsLastModified.cNanoSec));
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.ChangeTime, RT_BE2H_U32(pInodeCore->TsCreatedModified.cSecEpoch));
+ RTTimeSpecAddNano(&pInode->ObjInfo.ChangeTime, RT_BE2H_U32(pInodeCore->TsCreatedModified.cNanoSec));
+ pInode->ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ pInode->ObjInfo.Attr.u.Unix.uid = RT_BE2H_U32(pInodeCore->uUid);
+ pInode->ObjInfo.Attr.u.Unix.gid = RT_BE2H_U32(pInodeCore->uGid);
+ pInode->ObjInfo.Attr.u.Unix.cHardlinks = RT_BE2H_U16(pInodeCore->cOnLinks); /** @todo v2 inodes. */
+ pInode->ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ pInode->ObjInfo.Attr.u.Unix.INodeId = iInode;
+ pInode->ObjInfo.Attr.u.Unix.fFlags = 0;
+ pInode->ObjInfo.Attr.u.Unix.GenerationId = RT_BE2H_U32(pInodeCore->cGeneration);
+ pInode->ObjInfo.Attr.u.Unix.Device = 0;
+ if (pInodeCore->iVersion >= 3)
+ {
+ RTTimeSpecSetSeconds(&pInode->ObjInfo.BirthTime, RT_BE2H_U32(pInodeCore->TsCreation.cSecEpoch));
+ RTTimeSpecAddNano(&pInode->ObjInfo.BirthTime, RT_BE2H_U32(pInodeCore->TsCreation.cNanoSec));
+ }
+ else
+ pInode->ObjInfo.BirthTime = pInode->ObjInfo.ChangeTime;
+
+ /* Fill in the mode. */
+ pInode->ObjInfo.Attr.fMode = 0;
+ uint16_t fInodeMode = RT_BE2H_U16(pInodeCore->fMode);
+ switch (XFS_INODE_MODE_TYPE_GET_TYPE(fInodeMode))
+ {
+ case XFS_INODE_MODE_TYPE_FIFO:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_FIFO;
+ break;
+ case XFS_INODE_MODE_TYPE_CHAR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DEV_CHAR;
+ break;
+ case XFS_INODE_MODE_TYPE_DIR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DIRECTORY;
+ break;
+ case XFS_INODE_MODE_TYPE_BLOCK:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_DEV_BLOCK;
+ break;
+ case XFS_INODE_MODE_TYPE_REGULAR:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_FILE;
+ break;
+ case XFS_INODE_MODE_TYPE_SYMLINK:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_SYMLINK;
+ break;
+ case XFS_INODE_MODE_TYPE_SOCKET:
+ pInode->ObjInfo.Attr.fMode |= RTFS_TYPE_SOCKET;
+ break;
+ default:
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+ if (fInodeMode & XFS_INODE_MODE_EXEC_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXOTH;
+ if (fInodeMode & XFS_INODE_MODE_WRITE_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWOTH;
+ if (fInodeMode & XFS_INODE_MODE_READ_OTHER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IROTH;
+ if (fInodeMode & XFS_INODE_MODE_EXEC_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXGRP;
+ if (fInodeMode & XFS_INODE_MODE_WRITE_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWGRP;
+ if (fInodeMode & XFS_INODE_MODE_READ_GROUP)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IRGRP;
+ if (fInodeMode & XFS_INODE_MODE_EXEC_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IXUSR;
+ if (fInodeMode & XFS_INODE_MODE_WRITE_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IWUSR;
+ if (fInodeMode & XFS_INODE_MODE_READ_OWNER)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_IRUSR;
+ if (fInodeMode & XFS_INODE_MODE_STICKY)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISTXT;
+ if (fInodeMode & XFS_INODE_MODE_SET_GROUP_ID)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISGID;
+ if (fInodeMode & XFS_INODE_MODE_SET_USER_ID)
+ pInode->ObjInfo.Attr.fMode |= RTFS_UNIX_ISUID;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ {
+ /* Remove from current LRU list position and add to the beginning. */
+ uint32_t cRefs = ASMAtomicIncU32(&pInode->cRefs);
+ if (cRefs == 1) /* Inodes get removed from the LRU list if they are referenced. */
+ RTListNodeRemove(&pInode->NdLru);
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppInode = pInode;
+ else if (pInode)
+ {
+ ASMAtomicDecU32(&pInode->cRefs);
+ rtFsXfsInode_Free(pThis, pInode); /* Free the inode. */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Releases a reference of the given inode.
+ *
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode to release.
+ */
+static void rtFsXfsInode_Release(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pInode->cRefs);
+ if (!cRefs)
+ rtFsXfsInode_Free(pThis, pInode);
+}
+
+
+/**
+ * Worker for various QueryInfo methods.
+ *
+ * @returns IPRT status code.
+ * @param pInode The inode structure to return info for.
+ * @param pObjInfo Where to return object info.
+ * @param enmAddAttr What additional info to return.
+ */
+static int rtFsXfsInode_QueryInfo(PRTFSXFSINODE pInode, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_ZERO(*pObjInfo);
+
+ pObjInfo->cbObject = pInode->ObjInfo.cbObject;
+ pObjInfo->cbAllocated = pInode->ObjInfo.cbAllocated;
+ pObjInfo->AccessTime = pInode->ObjInfo.AccessTime;
+ pObjInfo->ModificationTime = pInode->ObjInfo.ModificationTime;
+ pObjInfo->ChangeTime = pInode->ObjInfo.ChangeTime;
+ pObjInfo->BirthTime = pInode->ObjInfo.BirthTime;
+ pObjInfo->Attr.fMode = pInode->ObjInfo.Attr.fMode;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_UNIX:
+ memcpy(&pObjInfo->Attr.u.Unix, &pInode->ObjInfo.Attr.u.Unix, sizeof(pInode->ObjInfo.Attr.u.Unix));
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = pInode->ObjInfo.Attr.u.Unix.uid;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = pInode->ObjInfo.Attr.u.Unix.gid;
+ break;
+
+ default:
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Locates the location of the next level in the B+Tree mapping the given offset.
+ *
+ * @returns Filesystem block number where the next level of the B+Tree is stored.
+ * @param paoffFile Array of file offset mappings.
+ * @param pauFsBlock Array of filesystem block mappings.
+ * @param cEntries Number of entries in the extent index node array.
+ * @param iBlock The block to resolve.
+ */
+DECLINLINE(XFSDFSBNO) rtFsXfsInode_BTreeNdLocateNextLvl(XFSDFILOFF *paoffFile, XFSDFSBNO *pauFsBlock,
+ uint16_t cEntries, XFSDFILOFF offFile)
+{
+ for (uint32_t i = 1; i < cEntries; i++)
+ {
+ if ( RT_BE2H_U64(paoffFile[i - 1]) <= offFile
+ && RT_BE2H_U64(paoffFile[i]) > offFile)
+ return RT_BE2H_U64(pauFsBlock[i]);
+ }
+
+ /* Nothing found so far, the last entry must cover the block as the array is sorted. */
+ return RT_BE2H_U64(pauFsBlock[cEntries - 1]);
+}
+
+
+/**
+ * Locates the extent mapping the file offset in the given extents list.
+ *
+ * @returns IPRT status.
+ * @param pExtents The array of extents to search.
+ * @param cEntries Number of entries in the array.
+ * @param uBlock The file offset to search the matching mapping for.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ */
+DECLINLINE(int) rtFsXfsInode_ExtentLocate(PCXFSEXTENT paExtents, uint16_t cEntries, XFSDFILOFF uBlock,
+ size_t cBlocks, uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ int rc = VERR_VFS_BOGUS_FORMAT;
+
+ for (uint32_t i = 0; i < cEntries; i++)
+ {
+ PCXFSEXTENT pExtent = &paExtents[i];
+ uint64_t iBlockExtent = XFS_EXTENT_GET_LOGICAL_BLOCK(pExtent);
+ size_t cBlocksExtent = XFS_EXTENT_GET_BLOCK_COUNT(pExtent);
+
+ if ( uBlock >= iBlockExtent
+ && uBlock < iBlockExtent + cBlocksExtent)
+ {
+ uint64_t offExtentBlocks = uBlock - iBlockExtent;
+ *piBlockFs = XFS_EXTENT_GET_DISK_BLOCK(pExtent) + offExtentBlocks;
+ *pcBlocks = RT_MIN(cBlocks, cBlocksExtent - offExtentBlocks);
+ *pfSparse = XFS_EXTENT_IS_UNWRITTEN(pExtent);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Validates the given node header.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pNd The node header to validate.
+ * @param iLvl The current level.
+ */
+static int rtFsXfsInode_BTreeNdValidate(PRTFSXFSVOL pThis, PCXFSBTREENODEHDR pNd, uint16_t iLvl)
+{
+ RT_NOREF(pThis, pNd, iLvl);
+ /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Maps the given inode block to the destination filesystem block.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode structure to read from.
+ * @param iBlock The inode block to map.
+ * @param cBlocks Number of blocks requested.
+ * @param piBlockFs Where to store the filesystem block on success.
+ * @param pcBlocks Where to store the number of contiguous blocks on success.
+ * @param pfSparse Where to store the sparse flag on success.
+ *
+ * @todo Optimize
+ */
+static int rtFsXfsInode_MapBlockToFs(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode, uint64_t iBlock, size_t cBlocks,
+ uint64_t *piBlockFs, size_t *pcBlocks, bool *pfSparse)
+{
+ int rc = VINF_SUCCESS;
+
+ switch (pInode->enmFormat)
+ {
+ case XFS_INODE_FORMAT_EXTENTS:
+ {
+ size_t cbRemaining = 0;
+ PCXFSEXTENT paExtents = (PCXFSEXTENT)rtFsXfsInodeGetDataFork(pThis, pInode, &cbRemaining);
+
+ if (cbRemaining <= pInode->cExtentsData * sizeof(XFSEXTENT))
+ rc = rtFsXfsInode_ExtentLocate(paExtents, pInode->cExtentsData, cBlocks, iBlock,
+ piBlockFs, pcBlocks, pfSparse);
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ break;
+ }
+ case XFS_INODE_FORMAT_BTREE:
+ {
+ size_t cbRemaining = 0;
+ PCXFSBTREEROOTHDR pRoot = (PCXFSBTREEROOTHDR)rtFsXfsInodeGetDataFork(pThis, pInode, &cbRemaining);
+ if (cbRemaining >= RT_BE2H_U16(pRoot->cRecs) * (sizeof(XFSDFSBNO) + sizeof(XFSDFILOFF)) + sizeof(XFSBTREEROOTHDR))
+ {
+ XFSDFILOFF *poffFile = (XFSDFILOFF *)(pRoot + 1);
+ XFSDFSBNO *puFsBlock = (XFSDFSBNO *)(&poffFile[RT_BE2H_U16(pRoot->cRecs)]);
+
+ XFSDFSBNO uFsBlock = rtFsXfsInode_BTreeNdLocateNextLvl(poffFile, puFsBlock, RT_BE2H_U16(pRoot->cRecs),
+ iBlock);
+ uint16_t iLvl = RT_BE2H_U16(pRoot->iLvl) - 1;
+
+ /* Resolve intermediate levels. */
+ while ( iLvl > 0
+ && RT_SUCCESS(rc))
+ {
+ PRTFSXFSBLOCKENTRY pEntry;
+ PCXFSBTREENODEHDR pNd;
+
+ rc = rtFsXfsVol_BlockLoad(pThis, uFsBlock, &pEntry, (void **)&pNd);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsXfsInode_BTreeNdValidate(pThis, pNd, iLvl);
+ if (RT_SUCCESS(rc))
+ {
+ poffFile = (XFSDFILOFF *)(pNd + 1);
+ puFsBlock = (XFSDFSBNO *)(&poffFile[RT_BE2H_U16(pNd->cRecs)]);
+ uFsBlock = rtFsXfsInode_BTreeNdLocateNextLvl(poffFile, puFsBlock, RT_BE2H_U16(pRoot->cRecs),
+ iBlock);
+ iLvl--;
+ }
+ rtFsXfsVol_BlockRelease(pThis, pEntry);
+ }
+ }
+
+ /* Load the leave node and parse it. */
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSXFSBLOCKENTRY pEntry;
+ PCXFSBTREENODEHDR pNd;
+
+ rc = rtFsXfsVol_BlockLoad(pThis, uFsBlock, &pEntry, (void **)&pNd);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsXfsInode_BTreeNdValidate(pThis, pNd, iLvl);
+ if (RT_SUCCESS(rc))
+ {
+ PCXFSEXTENT paExtents = (PCXFSEXTENT)(pNd + 1);
+ rc = rtFsXfsInode_ExtentLocate(paExtents, RT_BE2H_U16(pNd->cRecs), cBlocks, iBlock,
+ piBlockFs, pcBlocks, pfSparse);
+ }
+ rtFsXfsVol_BlockRelease(pThis, pEntry);
+ }
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+ break;
+ }
+ case XFS_INODE_FORMAT_LOCAL:
+ case XFS_INODE_FORMAT_UUID:
+ case XFS_INODE_FORMAT_DEV:
+ default:
+ rc = VERR_VFS_BOGUS_FORMAT;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Reads data from the given inode at the given byte offset.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pInode The inode structure to read from.
+ * @param off The byte offset to start reading from.
+ * @param pvBuf Where to store the read data to.
+ * @param pcbRead Where to return the amount of data read.
+ */
+static int rtFsXfsInode_Read(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode, uint64_t off, void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pbBuf = (uint8_t *)pvBuf;
+
+ if (((uint64_t)pInode->ObjInfo.cbObject < off + cbRead))
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ else
+ cbRead = (uint64_t)pInode->ObjInfo.cbObject - off;
+ }
+
+ if (pInode->enmFormat == XFS_INODE_FORMAT_LOCAL)
+ {
+ /* Fast path when the data is inlined in the inode. */
+ size_t cbRemaining = 0;
+ uint8_t *pbSrc = (uint8_t *)rtFsXfsInodeGetDataFork(pThis, pInode, &cbRemaining);
+ if (off + cbRemaining <= (uint64_t)pInode->ObjInfo.cbObject)
+ {
+ memcpy(pvBuf, &pbSrc[off], cbRead);
+ *pcbRead = cbRead;
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+
+ return rc;
+ }
+
+ while ( cbRead
+ && RT_SUCCESS(rc))
+ {
+ uint64_t iBlockStart = rtFsXfsDiskOffsetToBlockIdx(pThis, off);
+ uint32_t offBlockStart = off % pThis->cbBlock;
+
+ /* Resolve the inode block to the proper filesystem block. */
+ uint64_t iBlockFs = 0;
+ size_t cBlocks = 0;
+ bool fSparse = false;
+ rc = rtFsXfsInode_MapBlockToFs(pThis, pInode, iBlockStart, 1, &iBlockFs, &cBlocks, &fSparse);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cBlocks == 1);
+
+ size_t cbThisRead = RT_MIN(cbRead, pThis->cbBlock - offBlockStart);
+
+ if (!fSparse)
+ {
+ uint64_t offRead = rtFsXfsBlockIdxToDiskOffset(pThis, iBlockFs);
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, offRead + offBlockStart, pbBuf, cbThisRead, NULL);
+ }
+ else
+ memset(pbBuf, 0, cbThisRead);
+
+ if (RT_SUCCESS(rc))
+ {
+ pbBuf += cbThisRead;
+ cbRead -= cbThisRead;
+ off += cbThisRead;
+ if (pcbRead)
+ *pcbRead += cbThisRead;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * File operations.
+ * File operations.
+ * File operations.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Close(void *pvThis)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ LogFlow(("rtFsXfsFile_Close(%p/%p)\n", pThis, pThis->pInode));
+
+ rtFsXfsInode_Release(pThis->pVol, pThis->pInode);
+ pThis->pInode = NULL;
+ pThis->pVol = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ return rtFsXfsInode_QueryInfo(pThis->pInode, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INTERNAL_ERROR_3);
+ RT_NOREF(fBlocking);
+
+ if (off == -1)
+ off = pThis->offFile;
+ else
+ AssertReturn(off >= 0, VERR_INTERNAL_ERROR_3);
+
+ int rc;
+ size_t cbRead = pSgBuf->paSegs[0].cbSeg;
+ if (!pcbRead)
+ {
+ rc = rtFsXfsInode_Read(pThis->pVol, pThis->pInode, (uint64_t)off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->offFile = off + cbRead;
+ Log6(("rtFsXfsFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc\n", off, pSgBuf->paSegs[0].cbSeg, rc));
+ }
+ else
+ {
+ PRTFSXFSINODE pInode = pThis->pInode;
+ if (off >= pInode->ObjInfo.cbObject)
+ {
+ *pcbRead = 0;
+ rc = VINF_EOF;
+ }
+ else
+ {
+ if ((uint64_t)off + cbRead <= (uint64_t)pInode->ObjInfo.cbObject)
+ rc = rtFsXfsInode_Read(pThis->pVol, pThis->pInode, (uint64_t)off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ else
+ {
+ /* Return VINF_EOF if beyond end-of-file. */
+ cbRead = (size_t)(pInode->ObjInfo.cbObject - off);
+ rc = rtFsXfsInode_Read(pThis->pVol, pThis->pInode, off, pSgBuf->paSegs[0].pvSeg, cbRead, NULL);
+ if (RT_SUCCESS(rc))
+ rc = VINF_EOF;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offFile = off + cbRead;
+ *pcbRead = cbRead;
+ }
+ else
+ *pcbRead = 0;
+ }
+ Log6(("rtFsXfsFile_Read: off=%#RX64 cbSeg=%#x -> %Rrc *pcbRead=%#x\n", off, pSgBuf->paSegs[0].cbSeg, rc, *pcbRead));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ RT_NOREF(pvThis, off, pSgBuf, fBlocking, pcbWritten);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Flush(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ RTFOFF offNew;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNew = offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNew = pThis->pInode->ObjInfo.cbObject + offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNew = (RTFOFF)pThis->offFile + offSeek;
+ break;
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+ if (offNew >= 0)
+ {
+ pThis->offFile = offNew;
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+ }
+ return VERR_NEGATIVE_SEEK;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTFSXFSFILE pThis = (PRTFSXFSFILE)pvThis;
+ *pcbFile = (uint64_t)pThis->pInode->ObjInfo.cbObject;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ RT_NOREF(pvThis, cbFile, fFlags);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtFsXfsFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = INT64_MAX; /** @todo */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * XFS file operations.
+ */
+static const RTVFSFILEOPS g_rtFsXfsFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "XFS File",
+ rtFsXfsFile_Close,
+ rtFsXfsFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtFsXfsFile_Read,
+ rtFsXfsFile_Write,
+ rtFsXfsFile_Flush,
+ NULL /*PollOne*/,
+ rtFsXfsFile_Tell,
+ NULL /*pfnSkip*/,
+ NULL /*pfnZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtFsXfsFile_SetMode,
+ rtFsXfsFile_SetTimes,
+ rtFsXfsFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsXfsFile_Seek,
+ rtFsXfsFile_QuerySize,
+ rtFsXfsFile_SetSize,
+ rtFsXfsFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Creates a new VFS file from the given regular file inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param fOpen Open flags passed.
+ * @param iInode The inode for the file.
+ * @param phVfsFile Where to store the VFS file handle on success.
+ * @param pErrInfo Where to record additional error information on error, optional.
+ * @param pszWhat Logging prefix.
+ */
+static int rtFsXfsVol_NewFile(PRTFSXFSVOL pThis, uint64_t fOpen, uint32_t iInode,
+ PRTVFSFILE phVfsFile, PRTERRINFO pErrInfo, const char *pszWhat)
+{
+ /*
+ * Load the inode and check that it really is a file.
+ */
+ PRTFSXFSINODE pInode = NULL;
+ int rc = rtFsXfsInode_Load(pThis, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_FILE(pInode->ObjInfo.Attr.fMode))
+ {
+ PRTFSXFSFILE pNewFile;
+ rc = RTVfsNewFile(&g_rtFsXfsFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsFile, (void **)&pNewFile);
+ if (RT_SUCCESS(rc))
+ {
+ pNewFile->pVol = pThis;
+ pNewFile->pInode = pInode;
+ pNewFile->offFile = 0;
+ }
+ }
+ else
+ rc = RTERRINFO_LOG_SET_F(pErrInfo, VERR_NOT_A_FILE, "%s: fMode=%#RX32", pszWhat, pInode->ObjInfo.Attr.fMode);
+
+ if (RT_FAILURE(rc))
+ rtFsXfsInode_Release(pThis, pInode);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * XFS directory code.
+ * XFS directory code.
+ * XFS directory code.
+ *
+ */
+
+/**
+ * Looks up an entry in the given directory inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pInode The directory inode structure to.
+ * @param pszEntry The entry to lookup.
+ * @param piInode Where to store the inode number if the entry was found.
+ */
+static int rtFsXfsDir_Lookup(PRTFSXFSVOL pThis, PRTFSXFSINODE pInode, const char *pszEntry, uint32_t *piInode)
+{
+ uint64_t offEntry = 0;
+ int rc = VERR_FILE_NOT_FOUND;
+ uint32_t idxDirEntry = 0;
+ size_t cchEntry = strlen(pszEntry);
+
+ if (cchEntry > 255)
+ return VERR_FILENAME_TOO_LONG;
+
+ RT_NOREF(pThis, idxDirEntry, offEntry, pInode, piInode);
+
+#if 0 /** @todo */
+ while (offEntry < (uint64_t)pInode->ObjInfo.cbObject)
+ {
+ EXTDIRENTRYEX DirEntry;
+ size_t cbThis = RT_MIN(sizeof(DirEntry), (uint64_t)pInode->ObjInfo.cbObject - offEntry);
+ int rc2 = rtFsXfsInode_Read(pThis, pInode, offEntry, &DirEntry, cbThis, NULL);
+ if (RT_SUCCESS(rc2))
+ {
+#ifdef LOG_ENABLED
+ rtFsExtDirEntry_Log(pThis, idxDirEntry, &DirEntry);
+#endif
+
+ uint16_t cbName = pThis->fFeaturesIncompat & EXT_SB_FEAT_INCOMPAT_DIR_FILETYPE
+ ? DirEntry.Core.u.v2.cbName
+ : RT_LE2H_U16(DirEntry.Core.u.v1.cbName);
+ if ( cchEntry == cbName
+ && !memcmp(pszEntry, &DirEntry.Core.achName[0], cchEntry))
+ {
+ *piInode = RT_LE2H_U32(DirEntry.Core.iInodeRef);
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ offEntry += RT_LE2H_U16(DirEntry.Core.cbRecord);
+ idxDirEntry++;
+ }
+ else
+ {
+ rc = rc2;
+ break;
+ }
+ }
+#endif
+ return rc;
+}
+
+
+
+/*
+ *
+ * Directory instance methods
+ * Directory instance methods
+ * Directory instance methods
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_Close(void *pvThis)
+{
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ LogFlowFunc(("pThis=%p\n", pThis));
+ rtFsXfsInode_Release(pThis->pVol, pThis->pInode);
+ pThis->pInode = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ LogFlowFunc(("\n"));
+ return rtFsXfsInode_QueryInfo(pThis->pInode, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, fMode, fMask);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ LogFlowFunc(("\n"));
+ RT_NOREF(pvThis, uid, gid);
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen,
+ uint32_t fFlags, PRTVFSOBJ phVfsObj)
+{
+ LogFlowFunc(("pszEntry='%s' fOpen=%#RX64 fFlags=%#x\n", pszEntry, fOpen, fFlags));
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ PRTFSXFSVOL pVol = pThis->pVol;
+ int rc = VINF_SUCCESS;
+
+ RT_NOREF(pThis, pVol, phVfsObj, pszEntry, fFlags);
+
+ /*
+ * We cannot create or replace anything, just open stuff.
+ */
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE)
+ { /* likely */ }
+ else
+ return VERR_WRITE_PROTECT;
+
+ /*
+ * Lookup the entry.
+ */
+ uint32_t iInode = 0;
+ rc = rtFsXfsDir_Lookup(pVol, pThis->pInode, pszEntry, &iInode);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFSXFSINODE pInode = NULL;
+ rc = rtFsXfsInode_Load(pVol, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(pInode->ObjInfo.Attr.fMode))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtFsXfsVol_OpenDirByInode(pVol, iInode, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else if (RTFS_IS_FILE(pInode->ObjInfo.Attr.fMode))
+ {
+ RTVFSFILE hVfsFile;
+ rc = rtFsXfsVol_NewFile(pVol, fOpen, iInode, &hVfsFile, NULL, pszEntry);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ }
+ }
+
+ LogFlow(("rtFsXfsDir_Open(%s): returns %Rrc\n", pszEntry, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ RT_NOREF(pvThis, pszEntry, fType);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ RT_NOREF(pvThis, pszEntry, fType, pszNewName);
+ LogFlowFunc(("\n"));
+ return VERR_WRITE_PROTECT;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_RewindDir(void *pvThis)
+{
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ LogFlowFunc(("\n"));
+
+ pThis->fNoMoreFiles = false;
+ pThis->offEntry = 0;
+ pThis->idxEntry = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtFsXfsDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
+ RTFSOBJATTRADD enmAddAttr)
+{
+ PRTFSXFSDIR pThis = (PRTFSXFSDIR)pvThis;
+ PRTFSXFSINODE pInode = pThis->pInode;
+ LogFlowFunc(("\n"));
+
+ if (pThis->fNoMoreFiles)
+ return VERR_NO_MORE_FILES;
+
+ RT_NOREF(pInode, pDirEntry, pcbDirEntry, enmAddAttr);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * XFS directory operations.
+ */
+static const RTVFSDIROPS g_rtFsXfsDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "XFS Dir",
+ rtFsXfsDir_Close,
+ rtFsXfsDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtFsXfsDir_SetMode,
+ rtFsXfsDir_SetTimes,
+ rtFsXfsDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtFsXfsDir_Open,
+ NULL /* pfnFollowAbsoluteSymlink */,
+ NULL /* pfnOpenFile */,
+ NULL /* pfnOpenDir */,
+ rtFsXfsDir_CreateDir,
+ rtFsXfsDir_OpenSymlink,
+ rtFsXfsDir_CreateSymlink,
+ NULL /* pfnQueryEntryInfo */,
+ rtFsXfsDir_UnlinkEntry,
+ rtFsXfsDir_RenameEntry,
+ rtFsXfsDir_RewindDir,
+ rtFsXfsDir_ReadDir,
+ RTVFSDIROPS_VERSION,
+};
+
+
+/**
+ * Opens a directory by the given inode.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param iInode The inode to open.
+ * @param phVfsDir Where to store the handle to the VFS directory on success.
+ */
+static int rtFsXfsVol_OpenDirByInode(PRTFSXFSVOL pThis, uint32_t iInode, PRTVFSDIR phVfsDir)
+{
+ PRTFSXFSINODE pInode = NULL;
+ int rc = rtFsXfsInode_Load(pThis, iInode, &pInode);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(pInode->ObjInfo.Attr.fMode))
+ {
+ PRTFSXFSDIR pNewDir;
+ rc = RTVfsNewDir(&g_rtFsXfsDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf, NIL_RTVFSLOCK,
+ phVfsDir, (void **)&pNewDir);
+ if (RT_SUCCESS(rc))
+ {
+ pNewDir->fNoMoreFiles = false;
+ pNewDir->pVol = pThis;
+ pNewDir->pInode = pInode;
+ }
+ }
+ else
+ rc = VERR_VFS_BOGUS_FORMAT;
+
+ if (RT_FAILURE(rc))
+ rtFsXfsInode_Release(pThis, pInode);
+ }
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * Volume level code.
+ * Volume level code.
+ * Volume level code.
+ *
+ */
+
+static DECLCALLBACK(int) rtFsXfsVolAgTreeDestroy(PAVLU32NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSXFSAG pAg = (PRTFSXFSAG)pCore;
+ Assert(!pAg->cRefs);
+ RTMemFree(pAg);
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtFsXfsVolInodeTreeDestroy(PAVLU64NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSXFSINODE pInode = (PRTFSXFSINODE)pCore;
+ Assert(!pInode->cRefs);
+ RTMemFree(pInode);
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtFsXfsVolBlockTreeDestroy(PAVLU64NODECORE pCore, void *pvUser)
+{
+ RT_NOREF(pvUser);
+
+ PRTFSXFSBLOCKENTRY pBlock = (PRTFSXFSBLOCKENTRY)pCore;
+ Assert(!pBlock->cRefs);
+ RTMemFree(pBlock);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
+ */
+static DECLCALLBACK(int) rtFsXfsVol_Close(void *pvThis)
+{
+ PRTFSXFSVOL pThis = (PRTFSXFSVOL)pvThis;
+
+ /* Destroy the block group tree. */
+ RTAvlU32Destroy(&pThis->AgRoot, rtFsXfsVolAgTreeDestroy, pThis);
+ pThis->AgRoot = NULL;
+ RTListInit(&pThis->LstAgLru);
+
+ /* Destroy the inode tree. */
+ RTAvlU64Destroy(&pThis->InodeRoot, rtFsXfsVolInodeTreeDestroy, pThis);
+ pThis->InodeRoot = NULL;
+ RTListInit(&pThis->LstInodeLru);
+
+ /* Destroy the block cache tree. */
+ RTAvlU64Destroy(&pThis->BlockRoot, rtFsXfsVolBlockTreeDestroy, pThis);
+ pThis->BlockRoot = NULL;
+ RTListInit(&pThis->LstBlockLru);
+
+ /*
+ * Backing file and handles.
+ */
+ RTVfsFileRelease(pThis->hVfsBacking);
+ pThis->hVfsBacking = NIL_RTVFSFILE;
+ pThis->hVfsSelf = NIL_RTVFS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtFsXfsVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis, pObjInfo, enmAddAttr);
+ return VERR_WRONG_TYPE;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnOpenRoot}
+ */
+static DECLCALLBACK(int) rtFsXfsVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
+{
+ PRTFSXFSVOL pThis = (PRTFSXFSVOL)pvThis;
+ int rc = rtFsXfsVol_OpenDirByInode(pThis, pThis->uInodeRoot, phVfsDir);
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryRangeState}
+ */
+static DECLCALLBACK(int) rtFsXfsVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed)
+{
+ RT_NOREF(pvThis, off, cb, pfUsed);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+DECL_HIDDEN_CONST(const RTVFSOPS) g_rtFsXfsVolOps =
+{
+ /* .Obj = */
+ {
+ /* .uVersion = */ RTVFSOBJOPS_VERSION,
+ /* .enmType = */ RTVFSOBJTYPE_VFS,
+ /* .pszName = */ "XfsVol",
+ /* .pfnClose = */ rtFsXfsVol_Close,
+ /* .pfnQueryInfo = */ rtFsXfsVol_QueryInfo,
+ /* .pfnQueryInfoEx = */ NULL,
+ /* .uEndMarker = */ RTVFSOBJOPS_VERSION
+ },
+ /* .uVersion = */ RTVFSOPS_VERSION,
+ /* .fFeatures = */ 0,
+ /* .pfnOpenRoot = */ rtFsXfsVol_OpenRoot,
+ /* .pfnQueryRangeState = */ rtFsXfsVol_QueryRangeState,
+ /* .uEndMarker = */ RTVFSOPS_VERSION
+};
+
+
+/**
+ * Loads and parses the AGI block.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsXfsVolLoadAgi(PRTFSXFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ XFSAGI Agi;
+ int rc = RTVfsFileReadAt(pThis->hVfsBacking, 2 * pThis->cbSector, &Agi, sizeof(&Agi), NULL);
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ rtFsXfsAgi_Log(0, &Agi);
+#endif
+
+ /** @todo Verification */
+ RT_NOREF(pErrInfo);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Loads and parses the superblock of the filesystem.
+ *
+ * @returns IPRT status code.
+ * @param pThis The XFS volume instance.
+ * @param pErrInfo Where to return additional error info.
+ */
+static int rtFsXfsVolLoadAndParseSuperblock(PRTFSXFSVOL pThis, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+ XFSSUPERBLOCK Sb;
+ rc = RTVfsFileReadAt(pThis->hVfsBacking, XFS_SB_OFFSET, &Sb, sizeof(XFSSUPERBLOCK), NULL);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET(pErrInfo, rc, "Error reading super block");
+
+ /* Validate the superblock. */
+ if (RT_BE2H_U32(Sb.u32Magic) != XFS_SB_MAGIC)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "Not XFS - Signature mismatch: %RX32", RT_BE2H_U32(Sb.u32Magic));
+
+#ifdef LOG_ENABLED
+ rtFsXfsSb_Log(0, &Sb);
+#endif
+
+ /** @todo More verification */
+ pThis->cbSector = RT_BE2H_U32(Sb.cbSector);
+ pThis->cbBlock = RT_BE2H_U32(Sb.cbBlock);
+ pThis->cBlockShift = Sb.cBlockSzLog;
+ pThis->cBlocksPerAg = RT_BE2H_U32(Sb.cAgBlocks);
+ pThis->cAgs = RT_BE2H_U32(Sb.cAg);
+ pThis->uInodeRoot = RT_BE2H_U64(Sb.uInodeRoot);
+ pThis->cbInode = RT_BE2H_U16(Sb.cbInode);
+ pThis->cInodesPerBlock = RT_BE2H_U16(Sb.cInodesPerBlock);
+ pThis->cAgBlocksLog = Sb.cAgBlocksLog;
+ pThis->cInodesPerBlockLog = Sb.cInodesPerBlockLog;
+ return rc;
+}
+
+
+RTDECL(int) RTFsXfsVolOpen(RTVFSFILE hVfsFileIn, uint32_t fMntFlags, uint32_t fXfsFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
+ AssertReturn(!(fMntFlags & ~RTVFSMNT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!fXfsFlags, VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create a VFS instance and initialize the data so rtFsXfsVol_Close works.
+ */
+ RTVFS hVfs;
+ PRTFSXFSVOL pThis;
+ int rc = RTVfsNew(&g_rtFsXfsVolOps, sizeof(*pThis), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsBacking = hVfsFileIn;
+ pThis->hVfsSelf = hVfs;
+ pThis->fMntFlags = fMntFlags;
+ pThis->fXfsFlags = fXfsFlags;
+ pThis->AgRoot = NULL;
+ pThis->InodeRoot = NULL;
+ pThis->BlockRoot = NULL;
+ pThis->cbAgs = 0;
+ pThis->cbInodes = 0;
+ pThis->cbBlocks = 0;
+ RTListInit(&pThis->LstAgLru);
+ RTListInit(&pThis->LstInodeLru);
+ RTListInit(&pThis->LstBlockLru);
+
+ rc = RTVfsFileQuerySize(pThis->hVfsBacking, &pThis->cbBacking);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFsXfsVolLoadAndParseSuperblock(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFsXfsVolLoadAgi(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfs = hVfs;
+ return VINF_SUCCESS;
+ }
+ }
+
+ RTVfsRelease(hVfs);
+ *phVfs = NIL_RTVFS;
+ }
+ else
+ RTVfsFileRelease(hVfsFileIn);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainXfsVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
+ return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
+ if ( pElement->enmType != RTVFSOBJTYPE_VFS
+ && pElement->enmType != RTVFSOBJTYPE_DIR)
+ return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly ? RTVFSMNT_F_READ_ONLY : 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainXfsVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError);
+
+ int rc;
+ RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
+ if (hVfsFileIn != NIL_RTVFSFILE)
+ {
+ RTVFS hVfs;
+ rc = RTFsXfsVolOpen(hVfsFileIn, (uint32_t)pElement->uProvider, (uint32_t)(pElement->uProvider >> 32), &hVfs, pErrInfo);
+ RTVfsFileRelease(hVfsFileIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromVfs(hVfs);
+ RTVfsRelease(hVfs);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainXfsVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider
+ || !pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'xfs'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainXfsVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "xfs",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a XFS file system, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainXfsVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainXfsVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainXfsVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainXfsVolReg, rtVfsChainXfsVolReg);
+
diff --git a/src/VBox/Runtime/common/fuzz/Makefile.kup b/src/VBox/Runtime/common/fuzz/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/fuzz/Makefile.kup
diff --git a/src/VBox/Runtime/common/fuzz/fuzz-config.cpp b/src/VBox/Runtime/common/fuzz/fuzz-config.cpp
new file mode 100644
index 00000000..9bf27e08
--- /dev/null
+++ b/src/VBox/Runtime/common/fuzz/fuzz-config.cpp
@@ -0,0 +1,651 @@
+/* $Id: fuzz-config.cpp $ */
+/** @file
+ * IPRT - Fuzzing framework API, config API.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/fuzz.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/json.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** The index filename used to get all the other content. */
+#define RTFUZZ_CFG_INDEX_FILE_NAME "index.json"
+/** The custom config object member name. */
+#define RTFUZZ_CFG_JSON_CUSTOM_CFG "CustomCfg"
+/** The input corpus array member name. */
+#define RTFUZZ_CFG_JSON_INPUT_CORPUS "InputCorpus"
+/** The input name. */
+#define RTFUZZ_CFG_JSON_INPUT_NAME "Name"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Internal fuzzer config state.
+ */
+typedef struct RTFUZZCFGINT
+{
+ /** Magic value identifying the struct. */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** The VFS file handle we get the config from. */
+ RTVFSFILE hVfsFile;
+ /** The JSON root handle of the config. */
+ RTJSONVAL hJsonRoot;
+ /** The custom config file handle if existing. */
+ RTVFSFILE hVfsFileCustomCfg;
+} RTFUZZCFGINT;
+/** Pointer to the internal fuzzer config state. */
+typedef RTFUZZCFGINT *PRTFUZZCFGINT;
+/** Pointer to a const internal fuzzer config state. */
+typedef const RTFUZZCFGINT *PCRTFUZZCFGINT;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Creates a filesystem stream from the given VFS file object.
+ *
+ * @returns IPRT status code.
+ * @param phVfsFss Where to store the handle to the filesystem stream on success.
+ * @param hVFsFile The VFS file handle.
+ */
+static int rtFuzzCfgTarFssFromVfsFile(PRTVFSFSSTREAM phVfsFss, RTVFSFILE hVfsFile)
+{
+ int rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSIOSTREAM hVfsFileIos = RTVfsFileToIoStream(hVfsFile);
+ if (hVfsFileIos != NIL_RTVFSIOSTREAM)
+ {
+ RTVFSIOSTREAM hGunzipIos;
+ rc = RTZipGzipDecompressIoStream(hVfsFileIos, 0 /*fFlags*/, &hGunzipIos);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFSSTREAM hTarFss;
+ rc = RTZipTarFsStreamFromIoStream(hGunzipIos, 0 /*fFlags*/, &hTarFss);
+ if (RT_SUCCESS(rc))
+ {
+ RTVfsIoStrmRelease(hGunzipIos);
+ RTVfsIoStrmRelease(hVfsFileIos);
+ *phVfsFss = hTarFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hGunzipIos);
+ }
+
+ RTVfsIoStrmRelease(hVfsFileIos);
+ }
+ else
+ rc = VERR_INVALID_STATE; /** @todo */
+ }
+
+ return rc;
+}
+
+
+/**
+ * Finds a given file in the filesystem stream.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFss The filesystem stream handle.
+ * @param pszFilename The filename to look for.
+ * @param fValidateUtf8 Flag whether tpo validate the content as UTF-8.
+ * @param phVfsFile Where to store the VFS file handle on success (content is completely in memory).
+ */
+static int rtFuzzCfgFindFile(RTVFSFSSTREAM hVfsFss, const char *pszFilename, bool fValidateUtf8,
+ PRTVFSFILE phVfsFile)
+{
+ int rc = VINF_SUCCESS;
+
+ *phVfsFile = NIL_RTVFSFILE;
+ for (;;)
+ {
+ /*
+ * Get the next stream object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ RTVFSOBJTYPE enmType;
+ rc = RTVfsFsStrmNext(hVfsFss, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_EOF)
+ rc = VERR_NOT_FOUND;
+ break;
+ }
+ const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
+
+ if ( !strcmp(pszAdjName, pszFilename)
+ && ( enmType == RTVFSOBJTYPE_FILE
+ || enmType == RTVFSOBJTYPE_IO_STREAM))
+ {
+ RTStrFree(pszName);
+
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ rc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, phVfsFile);
+ if ( RT_SUCCESS(rc)
+ && fValidateUtf8)
+ rc = RTVfsIoStrmValidateUtf8Encoding(hVfsIos,
+ RTVFS_VALIDATE_UTF8_BY_RTC_3629 | RTVFS_VALIDATE_UTF8_NO_NULL,
+ NULL);
+
+ RTVfsObjRelease(hVfsObj);
+ RTVfsIoStrmRelease(hVfsIos);
+ if ( RT_FAILURE(rc)
+ && *phVfsFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(*phVfsFile);
+ *phVfsFile = NIL_RTVFSFILE;
+ }
+ return rc;
+ }
+
+ /*
+ * Clean up.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Returns the memorized file handle for the given name from the given tarball VFS file handle.
+ *
+ * @returns IPRT status code.
+ * @param hVfsTarball The VFS file handle of the tarball containing the object.
+ * @param pszFilename The filename to look for.
+ * @param fValidateUtf8 Flag whether tpo validate the content as UTF-8.
+ * @param phVfsFile Where to store the VFS file handle on success (content is completely in memory).
+ */
+static int rtFuzzCfgGrabFileFromTarball(RTVFSFILE hVfsTarball, const char *pszFilename, bool fValidateUtf8, PRTVFSFILE phVfsFile)
+{
+ RTVFSFSSTREAM hVfsFss;
+ int rc = rtFuzzCfgTarFssFromVfsFile(&hVfsFss, hVfsTarball);
+ if (RT_SUCCESS(rc))
+ {
+ /* Search for the index file and parse it. */
+ RTVFSFILE hVfsJson;
+ rc = rtFuzzCfgFindFile(hVfsFss, pszFilename, fValidateUtf8, &hVfsJson);
+ RTVfsFsStrmRelease(hVfsFss);
+ if (RT_SUCCESS(rc))
+ *phVfsFile = hVfsJson;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Loads the given fuzzing config.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing config instance.
+ * @param pErrInfo Additional error information, optional.
+ */
+static int rtFuzzCfgLoad(PRTFUZZCFGINT pThis, PRTERRINFO pErrInfo)
+{
+ /* Search for the index file and parse it. */
+ RTVFSFILE hVfsJson;
+ int rc = rtFuzzCfgGrabFileFromTarball(pThis->hVfsFile, RTFUZZ_CFG_INDEX_FILE_NAME, true /*fValidateUtf8*/, &hVfsJson);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTJsonParseFromVfsFile(&pThis->hJsonRoot, hVfsJson, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /* Look for the custom config in the JSON and find it in the VFS file. */
+ char *pszCustomCfgFilename = NULL;
+ rc = RTJsonValueQueryStringByName(pThis->hJsonRoot, RTFUZZ_CFG_JSON_CUSTOM_CFG, &pszCustomCfgFilename);
+ if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS; /* The custom config is optional. */
+ if ( RT_SUCCESS(rc)
+ && pszCustomCfgFilename)
+ {
+ rc = rtFuzzCfgGrabFileFromTarball(pThis->hVfsFile, pszCustomCfgFilename, false /*fValidateUtf8*/, &pThis->hVfsFileCustomCfg);
+ RTStrFree(pszCustomCfgFilename);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTJsonValueRelease(pThis->hJsonRoot);
+ pThis->hJsonRoot = NIL_RTJSONVAL;
+ }
+ }
+
+ RTVfsFileRelease(hVfsJson);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Searches for the given object name in the given JSON array, returning the object on success.
+ *
+ * @returns IPRT status code.
+ * @param hJsonValArr JSON value handle containing the input corpus objects.
+ * @param pszName The name to look for.
+ * @param phJsonVal Where to store the referenced JSON value on success.
+ */
+static int rtFuzzCfgQueryInputCorpusEntryFromArray(RTJSONVAL hJsonValArr, const char *pszName, PRTJSONVAL phJsonVal)
+{
+ int rc = VERR_NOT_FOUND;
+ uint32_t cEntries = RTJsonValueGetArraySize(hJsonValArr);
+
+ for (uint32_t i = 0; i < cEntries; i++)
+ {
+ RTJSONVAL hJsonVal;
+ int rc2 = RTJsonValueQueryByIndex(hJsonValArr, i, &hJsonVal);
+ if (RT_SUCCESS(rc2))
+ {
+ char *pszObjName;
+ rc2 = RTJsonValueQueryStringByName(hJsonVal, RTFUZZ_CFG_JSON_INPUT_NAME, &pszObjName);
+ if (RT_SUCCESS(rc2))
+ {
+ if (!strcmp(pszObjName, pszName))
+ {
+ RTStrFree(pszObjName);
+ *phJsonVal = hJsonVal;
+ return VINF_SUCCESS;
+ }
+
+ RTStrFree(pszObjName);
+ }
+
+ RTJsonValueRelease(hJsonVal);
+ }
+
+ if (RT_FAILURE(rc2))
+ {
+ rc = rc2;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Queries a 64bit unsigned integer.
+ *
+ * @returns IPRT status code.
+ * @param hJsonInp JSON object handle to search in.
+ * @param pszName Value name to look for.
+ * @param pu64Val Where to store the value on success.
+ */
+static int rtFuzzCfgInputQueryU64(RTJSONVAL hJsonInp, const char *pszName, uint64_t *pu64Val)
+{
+ int64_t i64Val;
+ int rc = RTJsonValueQueryIntegerByName(hJsonInp, pszName, &i64Val);
+ if (RT_SUCCESS(rc))
+ {
+ if (i64Val >= 0)
+ *pu64Val = (uint64_t)i64Val;
+ else
+ rc = VERR_OUT_OF_RANGE;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Queries a 64bit unsigned integer, supplying a default value if the name is not found in the
+ * given JSON object.
+ *
+ * @returns IPRT status code.
+ * @param hJsonInp JSON object handle to search in.
+ * @param pszName Value name to look for.
+ * @param pu64Val Where to store the value on success.
+ * @param u64Def The value to set if the value is not found.
+ */
+static int rtFuzzCfgInputQueryU64Def(RTJSONVAL hJsonInp, const char *pszName, uint64_t *pu64Val, uint64_t u64Def)
+{
+ int rc = rtFuzzCfgInputQueryU64(hJsonInp, pszName, pu64Val);
+ if (rc == VERR_NOT_FOUND)
+ {
+ *pu64Val = u64Def;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Adds the given input to the given fuzzing contexts input corpus.
+ *
+ * @returns IPRT status code.
+ * @param hFuzzCtx The fuzzing context to add the input to.
+ * @param hJsonInp The JSON input object with further parameters.
+ * @param hVfsIos The VFS I/O stream of the input data to add.
+ */
+static int rtFuzzCfgAddInputToCtx(RTFUZZCTX hFuzzCtx, RTJSONVAL hJsonInp, RTVFSIOSTREAM hVfsIos)
+{
+ uint64_t offMutStart = 0;
+ int rc = rtFuzzCfgInputQueryU64Def(hJsonInp, "MutationStartOffset", &offMutStart, 0);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t cbMutRange = UINT64_MAX;
+ rc = rtFuzzCfgInputQueryU64Def(hJsonInp, "MutationRangeSize", &cbMutRange, UINT64_MAX);
+ if (RT_SUCCESS(rc))
+ rc = RTFuzzCtxCorpusInputAddFromVfsIoStrmEx(hFuzzCtx, hVfsIos, offMutStart, cbMutRange);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Sets the global fuzzer config form the given JSON object.
+ *
+ * @returns IPRT status code.
+ * @param hJsonRoot The JSON object handle for the fuzzer config.
+ * @param hFuzzCtx The fuzzing context to configure.
+ */
+static int rtFuzzCfgSetFuzzCtxCfg(RTJSONVAL hJsonRoot, RTFUZZCTX hFuzzCtx)
+{
+ uint64_t u64Tmp;
+ int rc = rtFuzzCfgInputQueryU64(hJsonRoot, "Seed", &u64Tmp);
+ if (RT_SUCCESS(rc))
+ rc = RTFuzzCtxReseed(hFuzzCtx, u64Tmp);
+ else if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFuzzCfgInputQueryU64(hJsonRoot, "InputSizeMax", &u64Tmp);
+ if (RT_SUCCESS(rc))
+ rc = RTFuzzCtxCfgSetInputSeedMaximum(hFuzzCtx, (size_t)u64Tmp);
+ else if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t offMutateStart = 0;
+ uint64_t cbMutateRange = UINT64_MAX;
+ rc = rtFuzzCfgInputQueryU64(hJsonRoot, "MutationStartOffset", &offMutateStart);
+ if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFuzzCfgInputQueryU64(hJsonRoot, "MutationRangeSize", &cbMutateRange);
+ if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = RTFuzzCtxCfgSetMutationRange(hFuzzCtx, offMutateStart, cbMutateRange);
+ }
+
+ /** @todo More here */
+ return rc;
+}
+
+
+/**
+ * Adds all inputs in the iven config file to the given fuzzer context.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing config instance.
+ * @param hJsonValCorpusArr The JSON array handle containing the input corpus configuration.
+ * @param hFuzzCtx The fuzzing context to configure.
+ */
+static int rtFuzzCfgAddFuzzCtxInputs(PRTFUZZCFGINT pThis, RTJSONVAL hJsonValCorpusArr, RTFUZZCTX hFuzzCtx)
+{
+ /*
+ * Go through the tarball sequentially and search the corresponding entries in the JSON array
+ * instead of the other way around because reopening the tarball and seeking around
+ * each time (filesystem stream) is much more expensive.
+ */
+ RTVFSFSSTREAM hVfsFss;
+ int rc = rtFuzzCfgTarFssFromVfsFile(&hVfsFss, pThis->hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ /*
+ * Get the next stream object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ RTVFSOBJTYPE enmType;
+ rc = RTVfsFsStrmNext(hVfsFss, &pszName, &enmType, &hVfsObj);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_EOF)
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ if ( enmType == RTVFSOBJTYPE_FILE
+ || enmType == RTVFSOBJTYPE_IO_STREAM)
+ {
+ const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
+
+ /* Skip the index.json. */
+ if (strcmp(pszAdjName, RTFUZZ_CFG_INDEX_FILE_NAME))
+ {
+ /* Look for a JSON object with the matching filename and process it. */
+ RTJSONVAL hJsonInp = NIL_RTJSONVAL;
+ rc = rtFuzzCfgQueryInputCorpusEntryFromArray(hJsonValCorpusArr, pszAdjName, &hJsonInp);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ rc = rtFuzzCfgAddInputToCtx(hFuzzCtx, hJsonInp, hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ RTJsonValueRelease(hJsonInp);
+ }
+ }
+ }
+
+ /*
+ * Clean up.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ if (RT_FAILURE(rc))
+ break; /* Abort on error. */
+ }
+
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Destroys the given fuzzing config.
+ *
+ * @param pThis The fuzzing config instance to destroy.
+ */
+static void rtFuzzCfgDestroy(PRTFUZZCFGINT pThis)
+{
+ RTJsonValueRelease(pThis->hJsonRoot);
+ RTVfsFileRelease(pThis->hVfsFile);
+ if (pThis->hVfsFileCustomCfg != NIL_RTVFSFILE)
+ RTVfsFileRelease(pThis->hVfsFileCustomCfg);
+ pThis->hVfsFile = NIL_RTVFSFILE;
+ RTMemFree(pThis);
+}
+
+
+RTDECL(int) RTFuzzCfgCreateFromVfsFile(PRTFUZZCFG phFuzzCfg, RTVFSFILE hVfsFile, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phFuzzCfg, VERR_INVALID_POINTER);
+
+ int rc;
+ PRTFUZZCFGINT pThis = (PRTFUZZCFGINT)RTMemAllocZ(sizeof(*pThis));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->u32Magic = 0; /** @todo */
+ pThis->cRefs = 1;
+ RTVfsFileRetain(hVfsFile);
+ pThis->hVfsFile = hVfsFile;
+ pThis->hVfsFileCustomCfg = NIL_RTVFSFILE;
+
+ rc = rtFuzzCfgLoad(pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *phFuzzCfg = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsFileRelease(hVfsFile);
+ pThis->hVfsFile = NULL;
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCfgCreateFromFile(PRTFUZZCFG phFuzzCfg, const char *pszFilename, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsFileOpenNormal(pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzCfgCreateFromVfsFile(phFuzzCfg, hVfsFile, pErrInfo);
+ RTVfsFileRelease(hVfsFile);
+ }
+
+ return rc;
+}
+
+
+RTDECL(uint32_t) RTFuzzCfgRetain(RTFUZZCFG hFuzzCfg)
+{
+ PRTFUZZCFGINT pThis = hFuzzCfg;
+
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTFuzzCfgRelease(RTFUZZCFG hFuzzCfg)
+{
+ PRTFUZZCFGINT pThis = hFuzzCfg;
+ if (pThis == NIL_RTFUZZCFG)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0)
+ rtFuzzCfgDestroy(pThis);
+ return cRefs;
+}
+
+
+RTDECL(int) RTFuzzCfgImport(RTFUZZCFG hFuzzCfg, RTFUZZCTX hFuzzCtx, uint32_t fFlags)
+{
+ AssertReturn(hFuzzCfg != NIL_RTFUZZCFG, VERR_INVALID_HANDLE);
+ AssertReturn(hFuzzCtx != NIL_RTFUZZCTX, VERR_INVALID_HANDLE);
+ AssertReturn(!(fFlags & ~RTFUZZCFG_IMPORT_F_VALID), VERR_INVALID_PARAMETER);
+
+ /* Get the input corpus array. */
+ PRTFUZZCFGINT pThis = hFuzzCfg;
+ RTJSONVAL hJsonValCorpusArr;
+ int rc = RTJsonValueQueryByName(pThis->hJsonRoot, RTFUZZ_CFG_JSON_INPUT_CORPUS, &hJsonValCorpusArr);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTJsonValueGetType(hJsonValCorpusArr) == RTJSONVALTYPE_ARRAY)
+ {
+ /* If not ommitted set the global fuzzing context config now. */
+ if (!(fFlags & RTFUZZCFG_IMPORT_F_ONLY_INPUT))
+ rc = rtFuzzCfgSetFuzzCtxCfg(pThis->hJsonRoot, hFuzzCtx);
+
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCfgAddFuzzCtxInputs(pThis, hJsonValCorpusArr, hFuzzCtx);
+ }
+ else
+ rc = VERR_JSON_VALUE_INVALID_TYPE;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCfgQueryCustomCfg(RTFUZZCFG hFuzzCfg, PRTVFSFILE phVfsFile)
+{
+ PRTFUZZCFGINT pThis = hFuzzCfg;
+
+ if (pThis->hVfsFileCustomCfg != NIL_RTVFSFILE)
+ {
+ RTVfsFileRetain(pThis->hVfsFileCustomCfg);
+ *phVfsFile = pThis->hVfsFileCustomCfg;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_NOT_FOUND;
+}
diff --git a/src/VBox/Runtime/common/fuzz/fuzz-observer.cpp b/src/VBox/Runtime/common/fuzz/fuzz-observer.cpp
new file mode 100644
index 00000000..1022f8f6
--- /dev/null
+++ b/src/VBox/Runtime/common/fuzz/fuzz-observer.cpp
@@ -0,0 +1,1402 @@
+/* $Id: fuzz-observer.cpp $ */
+/** @file
+ * IPRT - Fuzzing framework API, observer.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/fuzz.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/env.h>
+#include <iprt/file.h>
+#include <iprt/md5.h>
+#include <iprt/mem.h>
+#include <iprt/mp.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+
+
+/** Poll ID for the reading end of the stdout pipe from the client process. */
+#define RTFUZZOBS_EXEC_CTX_POLL_ID_STDOUT 0
+/** Poll ID for the reading end of the stderr pipe from the client process. */
+#define RTFUZZOBS_EXEC_CTX_POLL_ID_STDERR 1
+/** Poll ID for the writing end of the stdin pipe to the client process. */
+#define RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN 2
+
+/** Length of the input queue for an observer thread. */
+# define RTFUZZOBS_THREAD_INPUT_QUEUE_MAX UINT32_C(5)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the internal fuzzing observer state. */
+typedef struct RTFUZZOBSINT *PRTFUZZOBSINT;
+
+
+/**
+ * Observer thread state for one process.
+ */
+typedef struct RTFUZZOBSTHRD
+{
+ /** The thread handle. */
+ RTTHREAD hThread;
+ /** The observer ID. */
+ uint32_t idObs;
+ /** Flag whether to shutdown. */
+ volatile bool fShutdown;
+ /** Pointer to te global observer state. */
+ PRTFUZZOBSINT pFuzzObs;
+ /** Number of inputs in the queue. */
+ volatile uint32_t cInputs;
+ /** Where to insert the next input. */
+ volatile uint32_t offQueueInputW;
+ /** Where to retrieve the next input from. */
+ volatile uint32_t offQueueInputR;
+ /** The input queue for this thread. */
+ RTFUZZINPUT ahQueueInput[RTFUZZOBS_THREAD_INPUT_QUEUE_MAX];
+} RTFUZZOBSTHRD;
+/** Pointer to an observer thread state. */
+typedef RTFUZZOBSTHRD *PRTFUZZOBSTHRD;
+
+
+/**
+ * Internal fuzzing observer state.
+ */
+typedef struct RTFUZZOBSINT
+{
+ /** The fuzzing context used for this observer. */
+ RTFUZZCTX hFuzzCtx;
+ /** The target state recorder. */
+ RTFUZZTGTREC hTgtRec;
+ /** Temp directory for input files. */
+ char *pszTmpDir;
+ /** Results directory. */
+ char *pszResultsDir;
+ /** The binary to run. */
+ char *pszBinary;
+ /** The filename path of the binary. */
+ const char *pszBinaryFilename;
+ /** Arguments to run the binary with, terminated by a NULL entry. */
+ char **papszArgs;
+ /** The environment to use for the target. */
+ RTENV hEnv;
+ /** Any configured sanitizers. */
+ uint32_t fSanitizers;
+ /** Sanitizer related options set in the environment block. */
+ char *pszSanitizerOpts;
+ /** Number of arguments. */
+ uint32_t cArgs;
+ /** Maximum time to wait for the client to terminate until it is considered hung and killed. */
+ RTMSINTERVAL msWaitMax;
+ /** The channel the binary expects the input. */
+ RTFUZZOBSINPUTCHAN enmInputChan;
+ /** Flag whether to shutdown the master and all workers. */
+ volatile bool fShutdown;
+ /** Global observer thread handle. */
+ RTTHREAD hThreadGlobal;
+ /** The event semaphore handle for the global observer thread. */
+ RTSEMEVENT hEvtGlobal;
+ /** Notification event bitmap. */
+ volatile uint64_t bmEvt;
+ /** Number of threads created - one for each process. */
+ uint32_t cThreads;
+ /** Pointer to the array of observer thread states. */
+ PRTFUZZOBSTHRD paObsThreads;
+ /** Timestamp of the last stats query. */
+ uint64_t tsLastStats;
+ /** Last number of fuzzed inputs per second if we didn't gather enough data in between
+ * statistic queries. */
+ uint32_t cFuzzedInputsPerSecLast;
+ /** Fuzzing statistics. */
+ RTFUZZOBSSTATS Stats;
+} RTFUZZOBSINT;
+
+
+/**
+ * Worker execution context.
+ */
+typedef struct RTFUZZOBSEXECCTX
+{
+ /** The stdout pipe handle - reading end. */
+ RTPIPE hPipeStdoutR;
+ /** The stdout pipe handle - writing end. */
+ RTPIPE hPipeStdoutW;
+ /** The stderr pipe handle - reading end. */
+ RTPIPE hPipeStderrR;
+ /** The stderr pipe handle - writing end. */
+ RTPIPE hPipeStderrW;
+ /** The stdin pipe handle - reading end. */
+ RTPIPE hPipeStdinR;
+ /** The stind pipe handle - writing end. */
+ RTPIPE hPipeStdinW;
+ /** The stdout handle. */
+ RTHANDLE StdoutHandle;
+ /** The stderr handle. */
+ RTHANDLE StderrHandle;
+ /** The stdin handle. */
+ RTHANDLE StdinHandle;
+ /** The pollset to monitor. */
+ RTPOLLSET hPollSet;
+ /** The environment block to use. */
+ RTENV hEnv;
+ /** The process to monitor. */
+ RTPROCESS hProc;
+ /** Execution time of the process. */
+ RTMSINTERVAL msExec;
+ /** The recording state handle. */
+ RTFUZZTGTSTATE hTgtState;
+ /** Current input data pointer. */
+ uint8_t *pbInputCur;
+ /** Number of bytes left for the input. */
+ size_t cbInputLeft;
+ /** Modified arguments vector - variable in size. */
+ char *apszArgs[1];
+} RTFUZZOBSEXECCTX;
+/** Pointer to an execution context. */
+typedef RTFUZZOBSEXECCTX *PRTFUZZOBSEXECCTX;
+/** Pointer to an execution context pointer. */
+typedef PRTFUZZOBSEXECCTX *PPRTFUZZOBSEXECCTX;
+
+
+/**
+ * A variable descriptor.
+ */
+typedef struct RTFUZZOBSVARIABLE
+{
+ /** The variable. */
+ const char *pszVar;
+ /** Length of the variable in characters - excluding the terminator. */
+ uint32_t cchVar;
+ /** The replacement value. */
+ const char *pszVal;
+} RTFUZZOBSVARIABLE;
+/** Pointer to a variable descriptor. */
+typedef RTFUZZOBSVARIABLE *PRTFUZZOBSVARIABLE;
+
+
+
+/**
+ * Replaces a variable with its value.
+ *
+ * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY.
+ * @param ppszNew In/Out.
+ * @param pcchNew In/Out. (Messed up on failure.)
+ * @param offVar Variable offset.
+ * @param cchVar Variable length.
+ * @param pszValue The value.
+ * @param cchValue Value length.
+ */
+static int rtFuzzObsReplaceStringVariable(char **ppszNew, size_t *pcchNew, size_t offVar, size_t cchVar,
+ const char *pszValue, size_t cchValue)
+{
+ size_t const cchAfter = *pcchNew - offVar - cchVar;
+ if (cchVar < cchValue)
+ {
+ *pcchNew += cchValue - cchVar;
+ int rc = RTStrRealloc(ppszNew, *pcchNew + 1);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ char *pszNew = *ppszNew;
+ memmove(&pszNew[offVar + cchValue], &pszNew[offVar + cchVar], cchAfter + 1);
+ memcpy(&pszNew[offVar], pszValue, cchValue);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Replace the variables found in the source string, returning a new string that
+ * lives on the string heap.
+ *
+ * @returns IPRT status code.
+ * @param pszSrc The source string.
+ * @param paVars Pointer to the array of known variables.
+ * @param ppszNew Where to return the new string.
+ */
+static int rtFuzzObsReplaceStringVariables(const char *pszSrc, PRTFUZZOBSVARIABLE paVars, char **ppszNew)
+{
+ /* Lazy approach that employs memmove. */
+ int rc = VINF_SUCCESS;
+ size_t cchNew = strlen(pszSrc);
+ char *pszNew = RTStrDup(pszSrc);
+
+ if (paVars)
+ {
+ char *pszDollar = pszNew;
+ while ((pszDollar = strchr(pszDollar, '$')) != NULL)
+ {
+ if (pszDollar[1] == '{')
+ {
+ const char *pszEnd = strchr(&pszDollar[2], '}');
+ if (pszEnd)
+ {
+ size_t const cchVar = pszEnd - pszDollar + 1; /* includes "${}" */
+ size_t offDollar = pszDollar - pszNew;
+ PRTFUZZOBSVARIABLE pVar = paVars;
+ while (pVar->pszVar != NULL)
+ {
+ if ( cchVar == pVar->cchVar
+ && !memcmp(pszDollar, pVar->pszVar, cchVar))
+ {
+ size_t const cchValue = strlen(pVar->pszVal);
+ rc = rtFuzzObsReplaceStringVariable(&pszNew, &cchNew, offDollar,
+ cchVar, pVar->pszVal, cchValue);
+ offDollar += cchValue;
+ break;
+ }
+
+ pVar++;
+ }
+
+ pszDollar = &pszNew[offDollar];
+
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(pszNew);
+ *ppszNew = NULL;
+ return rc;
+ }
+ }
+ }
+ }
+ }
+
+ *ppszNew = pszNew;
+ return rc;
+}
+
+/**
+ * Prepares the argument vector for the child process.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal fuzzing observer state.
+ * @param pExecCtx The execution context to prepare the argument vector for.
+ * @param paVars Pointer to the array of known variables.
+ */
+static int rtFuzzObsExecCtxArgvPrepare(PRTFUZZOBSINT pThis, PRTFUZZOBSEXECCTX pExecCtx, PRTFUZZOBSVARIABLE paVars)
+{
+ int rc = VINF_SUCCESS;
+ for (unsigned i = 0; i < pThis->cArgs && RT_SUCCESS(rc); i++)
+ rc = rtFuzzObsReplaceStringVariables(pThis->papszArgs[i], paVars, &pExecCtx->apszArgs[i]);
+
+ return rc;
+}
+
+
+/**
+ * Creates a new execution context.
+ *
+ * @returns IPRT status code.
+ * @param ppExecCtx Where to store the pointer to the execution context on success.
+ * @param pThis The internal fuzzing observer state.
+ */
+static int rtFuzzObsExecCtxCreate(PPRTFUZZOBSEXECCTX ppExecCtx, PRTFUZZOBSINT pThis)
+{
+ int rc = VINF_SUCCESS;
+ PRTFUZZOBSEXECCTX pExecCtx = (PRTFUZZOBSEXECCTX)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFUZZOBSEXECCTX, apszArgs[pThis->cArgs + 1]));
+ if (RT_LIKELY(pExecCtx))
+ {
+ pExecCtx->hPipeStdoutR = NIL_RTPIPE;
+ pExecCtx->hPipeStdoutW = NIL_RTPIPE;
+ pExecCtx->hPipeStderrR = NIL_RTPIPE;
+ pExecCtx->hPipeStderrW = NIL_RTPIPE;
+ pExecCtx->hPipeStdinR = NIL_RTPIPE;
+ pExecCtx->hPipeStdinW = NIL_RTPIPE;
+ pExecCtx->hPollSet = NIL_RTPOLLSET;
+ pExecCtx->hProc = NIL_RTPROCESS;
+ pExecCtx->msExec = 0;
+
+ rc = RTEnvClone(&pExecCtx->hEnv, pThis->hEnv);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzTgtRecorderCreateNewState(pThis->hTgtRec, &pExecCtx->hTgtState);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetCreate(&pExecCtx->hPollSet);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPipeCreate(&pExecCtx->hPipeStdoutR, &pExecCtx->hPipeStdoutW, RTPIPE_C_INHERIT_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ RTHANDLE Handle;
+ Handle.enmType = RTHANDLETYPE_PIPE;
+ Handle.u.hPipe = pExecCtx->hPipeStdoutR;
+ rc = RTPollSetAdd(pExecCtx->hPollSet, &Handle, RTPOLL_EVT_READ, RTFUZZOBS_EXEC_CTX_POLL_ID_STDOUT);
+ AssertRC(rc);
+
+ rc = RTPipeCreate(&pExecCtx->hPipeStderrR, &pExecCtx->hPipeStderrW, RTPIPE_C_INHERIT_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ Handle.u.hPipe = pExecCtx->hPipeStderrR;
+ rc = RTPollSetAdd(pExecCtx->hPollSet, &Handle, RTPOLL_EVT_READ, RTFUZZOBS_EXEC_CTX_POLL_ID_STDERR);
+ AssertRC(rc);
+
+ /* Create the stdin pipe handles if not a file input. */
+ if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_STDIN || pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT)
+ {
+ rc = RTPipeCreate(&pExecCtx->hPipeStdinR, &pExecCtx->hPipeStdinW, RTPIPE_C_INHERIT_READ);
+ if (RT_SUCCESS(rc))
+ {
+ pExecCtx->StdinHandle.enmType = RTHANDLETYPE_PIPE;
+ pExecCtx->StdinHandle.u.hPipe = pExecCtx->hPipeStdinR;
+
+ Handle.u.hPipe = pExecCtx->hPipeStdinW;
+ rc = RTPollSetAdd(pExecCtx->hPollSet, &Handle, RTPOLL_EVT_WRITE, RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN);
+ AssertRC(rc);
+ }
+ }
+ else
+ {
+ pExecCtx->StdinHandle.enmType = RTHANDLETYPE_PIPE;
+ pExecCtx->StdinHandle.u.hPipe = NIL_RTPIPE;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pExecCtx->StdoutHandle.enmType = RTHANDLETYPE_PIPE;
+ pExecCtx->StdoutHandle.u.hPipe = pExecCtx->hPipeStdoutW;
+ pExecCtx->StderrHandle.enmType = RTHANDLETYPE_PIPE;
+ pExecCtx->StderrHandle.u.hPipe = pExecCtx->hPipeStderrW;
+ *ppExecCtx = pExecCtx;
+ return VINF_SUCCESS;
+ }
+
+ RTPipeClose(pExecCtx->hPipeStderrR);
+ RTPipeClose(pExecCtx->hPipeStderrW);
+ }
+
+ RTPipeClose(pExecCtx->hPipeStdoutR);
+ RTPipeClose(pExecCtx->hPipeStdoutW);
+ }
+
+ RTPollSetDestroy(pExecCtx->hPollSet);
+ }
+
+ RTFuzzTgtStateRelease(pExecCtx->hTgtState);
+ }
+
+ RTEnvDestroy(pExecCtx->hEnv);
+ }
+
+ RTMemFree(pExecCtx);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Destroys the given execution context.
+ *
+ * @param pThis The internal fuzzing observer state.
+ * @param pExecCtx The execution context to destroy.
+ */
+static void rtFuzzObsExecCtxDestroy(PRTFUZZOBSINT pThis, PRTFUZZOBSEXECCTX pExecCtx)
+{
+ RTPipeClose(pExecCtx->hPipeStdoutR);
+ RTPipeClose(pExecCtx->hPipeStdoutW);
+ RTPipeClose(pExecCtx->hPipeStderrR);
+ RTPipeClose(pExecCtx->hPipeStderrW);
+
+ if ( pThis->enmInputChan == RTFUZZOBSINPUTCHAN_STDIN
+ || pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT)
+ {
+ RTPipeClose(pExecCtx->hPipeStdinR);
+ RTPipeClose(pExecCtx->hPipeStdinW);
+ }
+
+ RTPollSetDestroy(pExecCtx->hPollSet);
+ char **ppszArg = &pExecCtx->apszArgs[0];
+ while (*ppszArg != NULL)
+ {
+ RTStrFree(*ppszArg);
+ ppszArg++;
+ }
+
+ if (pExecCtx->hTgtState != NIL_RTFUZZTGTSTATE)
+ RTFuzzTgtStateRelease(pExecCtx->hTgtState);
+ RTEnvDestroy(pExecCtx->hEnv);
+ RTMemFree(pExecCtx);
+}
+
+
+/**
+ * Runs the client binary pumping all data back and forth waiting for the client to finish.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_TIMEOUT if the client didn't finish in the given deadline and was killed.
+ * @param pThis The internal fuzzing observer state.
+ * @param pExecCtx The execution context.
+ * @param pProcStat Where to store the process exit status on success.
+ */
+static int rtFuzzObsExecCtxClientRun(PRTFUZZOBSINT pThis, PRTFUZZOBSEXECCTX pExecCtx, PRTPROCSTATUS pProcStat)
+{
+ int rc = RTProcCreateEx(pThis->pszBinary, &pExecCtx->apszArgs[0], pExecCtx->hEnv, 0 /*fFlags*/, &pExecCtx->StdinHandle,
+ &pExecCtx->StdoutHandle, &pExecCtx->StderrHandle, NULL, NULL, NULL, &pExecCtx->hProc);
+ if (RT_SUCCESS(rc))
+ {
+ uint64_t tsMilliesStart = RTTimeSystemMilliTS();
+ for (;;)
+ {
+ /* Wait a bit for something to happen on one of the pipes. */
+ uint32_t fEvtsRecv = 0;
+ uint32_t idEvt = 0;
+ rc = RTPoll(pExecCtx->hPollSet, 10 /*cMillies*/, &fEvtsRecv, &idEvt);
+ if (RT_SUCCESS(rc))
+ {
+ if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDOUT)
+ {
+ Assert(fEvtsRecv & RTPOLL_EVT_READ);
+ rc = RTFuzzTgtStateAppendStdoutFromPipe(pExecCtx->hTgtState, pExecCtx->hPipeStdoutR);
+ AssertRC(rc);
+ }
+ else if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDERR)
+ {
+ Assert(fEvtsRecv & RTPOLL_EVT_READ);
+
+ rc = RTFuzzTgtStateAppendStderrFromPipe(pExecCtx->hTgtState, pExecCtx->hPipeStderrR);
+ AssertRC(rc);
+ }
+ else if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN)
+ {
+ /* Feed the next input. */
+ Assert(fEvtsRecv & RTPOLL_EVT_WRITE);
+ size_t cbWritten = 0;
+ rc = RTPipeWrite(pExecCtx->hPipeStdinW, pExecCtx->pbInputCur, pExecCtx->cbInputLeft, &cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ pExecCtx->cbInputLeft -= cbWritten;
+ if (!pExecCtx->cbInputLeft)
+ {
+ /* Close stdin pipe. */
+ rc = RTPollSetRemove(pExecCtx->hPollSet, RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN);
+ AssertRC(rc);
+ RTPipeClose(pExecCtx->hPipeStdinW);
+ }
+ }
+ }
+ else
+ AssertMsgFailed(("Invalid poll ID returned: %u!\n", idEvt));
+ }
+ else
+ Assert(rc == VERR_TIMEOUT);
+
+ /* Check the process status. */
+ rc = RTProcWait(pExecCtx->hProc, RTPROCWAIT_FLAGS_NOBLOCK, pProcStat);
+ if (RT_SUCCESS(rc))
+ {
+ /* Add the coverage report to the sanitizer if enabled. */
+ if (pThis->fSanitizers & RTFUZZOBS_SANITIZER_F_SANCOV)
+ {
+ char szSanCovReport[RTPATH_MAX];
+ ssize_t cch = RTStrPrintf2(&szSanCovReport[0], sizeof(szSanCovReport),
+ "%s%c%s.%u.sancov",
+ pThis->pszTmpDir, RTPATH_SLASH,
+ pThis->pszBinaryFilename, pExecCtx->hProc);
+ Assert(cch > 0); RT_NOREF(cch);
+ rc = RTFuzzTgtStateAddSanCovReportFromFile(pExecCtx->hTgtState, &szSanCovReport[0]);
+ RTFileDelete(&szSanCovReport[0]);
+ }
+ break;
+ }
+ else
+ {
+ Assert(rc == VERR_PROCESS_RUNNING);
+ /* Check whether we reached the limit. */
+ if (RTTimeSystemMilliTS() - tsMilliesStart > pThis->msWaitMax)
+ {
+ rc = VERR_TIMEOUT;
+ break;
+ }
+ }
+ } /* for (;;) */
+
+ /* Kill the process on a timeout. */
+ if (rc == VERR_TIMEOUT)
+ {
+ int rc2 = RTProcTerminate(pExecCtx->hProc);
+ AssertRC(rc2);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Runs the fuzzing aware client binary pumping all data back and forth waiting for the client to crash.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_TIMEOUT if the client didn't finish in the given deadline and was killed.
+ * @param pThis The internal fuzzing observer state.
+ * @param pExecCtx The execution context.
+ * @param pProcStat Where to store the process exit status on success.
+ */
+static int rtFuzzObsExecCtxClientRunFuzzingAware(PRTFUZZOBSINT pThis, PRTFUZZOBSEXECCTX pExecCtx, PRTPROCSTATUS pProcStat)
+{
+ int rc = RTProcCreateEx(pThis->pszBinary, &pExecCtx->apszArgs[0], pExecCtx->hEnv, 0 /*fFlags*/, &pExecCtx->StdinHandle,
+ &pExecCtx->StdoutHandle, &pExecCtx->StderrHandle, NULL, NULL, NULL, &pExecCtx->hProc);
+ if (RT_SUCCESS(rc))
+ {
+ /* Send the initial fuzzing context state over to the client. */
+ void *pvState = NULL;
+ size_t cbState = 0;
+ rc = RTFuzzCtxStateExportToMem(pThis->hFuzzCtx, &pvState, &cbState);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbStateWr = (uint32_t)cbState;
+ rc = RTPipeWriteBlocking(pExecCtx->hPipeStdinW, &cbStateWr, sizeof(cbStateWr), NULL);
+ rc = RTPipeWriteBlocking(pExecCtx->hPipeStdinW, pvState, cbState, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetRemove(pExecCtx->hPollSet, RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN);
+ AssertRC(rc);
+
+ uint64_t tsMilliesLastSignal = RTTimeSystemMilliTS();
+ uint32_t cFuzzedInputs = 0;
+ for (;;)
+ {
+ /* Wait a bit for something to happen on one of the pipes. */
+ uint32_t fEvtsRecv = 0;
+ uint32_t idEvt = 0;
+ rc = RTPoll(pExecCtx->hPollSet, 10 /*cMillies*/, &fEvtsRecv, &idEvt);
+ if (RT_SUCCESS(rc))
+ {
+ if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDOUT)
+ {
+ Assert(fEvtsRecv & RTPOLL_EVT_READ);
+ for (;;)
+ {
+ char achBuf[512];
+ size_t cbRead = 0;
+ rc = RTPipeRead(pExecCtx->hPipeStdoutR, &achBuf[0], sizeof(achBuf), &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ if (!cbRead)
+ break;
+
+ tsMilliesLastSignal = RTTimeMilliTS();
+ for (unsigned i = 0; i < cbRead; i++)
+ {
+ ASMAtomicIncU32(&pThis->Stats.cFuzzedInputs);
+ ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsPerSec);
+
+ if (achBuf[i] == '.')
+ cFuzzedInputs++;
+ else if (achBuf[i] == 'A')
+ {
+ /** @todo Advance our fuzzer to get the added input. */
+ }
+ }
+ }
+ else
+ break;
+ }
+ AssertRC(rc);
+ }
+ else if (idEvt == RTFUZZOBS_EXEC_CTX_POLL_ID_STDERR)
+ {
+ Assert(fEvtsRecv & RTPOLL_EVT_READ);
+ rc = RTFuzzTgtStateAppendStderrFromPipe(pExecCtx->hTgtState, pExecCtx->hPipeStderrR);
+ AssertRC(rc);
+ }
+ else
+ AssertMsgFailed(("Invalid poll ID returned: %u!\n", idEvt));
+ }
+ else
+ Assert(rc == VERR_TIMEOUT);
+
+ /* Check the process status. */
+ rc = RTProcWait(pExecCtx->hProc, RTPROCWAIT_FLAGS_NOBLOCK, pProcStat);
+ if (RT_SUCCESS(rc))
+ break;
+ else
+ {
+ Assert(rc == VERR_PROCESS_RUNNING);
+ /* Check when the last response from the client was. */
+ if (RTTimeSystemMilliTS() - tsMilliesLastSignal > pThis->msWaitMax)
+ {
+ rc = VERR_TIMEOUT;
+ break;
+ }
+ }
+ } /* for (;;) */
+
+ /* Kill the process on a timeout. */
+ if (rc == VERR_TIMEOUT)
+ {
+ int rc2 = RTProcTerminate(pExecCtx->hProc);
+ AssertRC(rc2);
+ }
+ }
+ }
+ }
+
+ RTHANDLE Handle;
+ Handle.enmType = RTHANDLETYPE_PIPE;
+ Handle.u.hPipe = pExecCtx->hPipeStdinW;
+ rc = RTPollSetAdd(pExecCtx->hPollSet, &Handle, RTPOLL_EVT_WRITE, RTFUZZOBS_EXEC_CTX_POLL_ID_STDIN);
+ AssertRC(rc);
+
+ return rc;
+}
+
+
+/**
+ * Adds the input to the results directory.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal fuzzing observer state.
+ * @param hFuzzInput Fuzzing input handle to write.
+ * @param pExecCtx Execution context.
+ */
+static int rtFuzzObsAddInputToResults(PRTFUZZOBSINT pThis, RTFUZZINPUT hFuzzInput, PRTFUZZOBSEXECCTX pExecCtx)
+{
+ char aszDigest[RTMD5_STRING_LEN + 1];
+ int rc = RTFuzzInputQueryDigestString(hFuzzInput, &aszDigest[0], sizeof(aszDigest));
+ if (RT_SUCCESS(rc))
+ {
+ /* Create a directory. */
+ char szPath[RTPATH_MAX];
+ rc = RTPathJoin(szPath, sizeof(szPath), pThis->pszResultsDir, &aszDigest[0]);
+ AssertRC(rc);
+
+ rc = RTDirCreate(&szPath[0], 0700, 0 /*fCreate*/);
+ if (RT_SUCCESS(rc))
+ {
+ /* Write the input. */
+ char szTmp[RTPATH_MAX];
+ rc = RTPathJoin(szTmp, sizeof(szTmp), &szPath[0], "input");
+ AssertRC(rc);
+
+ rc = RTFuzzInputWriteToFile(hFuzzInput, &szTmp[0]);
+ if (RT_SUCCESS(rc))
+ rc = RTFuzzTgtStateDumpToDir(pExecCtx->hTgtState, &szPath[0]);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Fuzzing observer worker loop.
+ *
+ * @returns IPRT status code.
+ * @param hThrd The thread handle.
+ * @param pvUser Opaque user data.
+ */
+static DECLCALLBACK(int) rtFuzzObsWorkerLoop(RTTHREAD hThrd, void *pvUser)
+{
+ PRTFUZZOBSTHRD pObsThrd = (PRTFUZZOBSTHRD)pvUser;
+ PRTFUZZOBSINT pThis = pObsThrd->pFuzzObs;
+ PRTFUZZOBSEXECCTX pExecCtx = NULL;
+
+ int rc = rtFuzzObsExecCtxCreate(&pExecCtx, pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ char szInput[RTPATH_MAX]; RT_ZERO(szInput);
+ if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FILE)
+ {
+ char szFilename[32];
+
+ ssize_t cbBuf = RTStrPrintf2(&szFilename[0], sizeof(szFilename), "%u", pObsThrd->idObs);
+ Assert(cbBuf > 0); RT_NOREF(cbBuf);
+
+ rc = RTPathJoin(szInput, sizeof(szInput), pThis->pszTmpDir, &szFilename[0]);
+ AssertRC(rc);
+
+ RTFUZZOBSVARIABLE aVar[2] =
+ {
+ { "${INPUT}", sizeof("${INPUT}") - 1, &szInput[0] },
+ { NULL, 0, NULL }
+ };
+ rc = rtFuzzObsExecCtxArgvPrepare(pThis, pExecCtx, &aVar[0]);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ while (!pObsThrd->fShutdown)
+ {
+ /* Wait for work. */
+ if (!ASMAtomicReadU32(&pObsThrd->cInputs))
+ {
+ rc = RTThreadUserWait(hThrd, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+
+ if (pObsThrd->fShutdown)
+ break;
+
+ if (!ASMAtomicReadU32(&pObsThrd->cInputs))
+ continue;
+
+ uint32_t offRead = ASMAtomicReadU32(&pObsThrd->offQueueInputR);
+ RTFUZZINPUT hFuzzInput = pObsThrd->ahQueueInput[offRead];
+
+ ASMAtomicDecU32(&pObsThrd->cInputs);
+ offRead = (offRead + 1) % RT_ELEMENTS(pObsThrd->ahQueueInput);
+ ASMAtomicWriteU32(&pObsThrd->offQueueInputR, offRead);
+ if (!ASMAtomicBitTestAndSet(&pThis->bmEvt, pObsThrd->idObs))
+ RTSemEventSignal(pThis->hEvtGlobal);
+
+ if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FILE)
+ rc = RTFuzzInputWriteToFile(hFuzzInput, &szInput[0]);
+ else if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_STDIN)
+ {
+ rc = RTFuzzInputQueryBlobData(hFuzzInput, (void **)&pExecCtx->pbInputCur, &pExecCtx->cbInputLeft);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzObsExecCtxArgvPrepare(pThis, pExecCtx, NULL);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ RTPROCSTATUS ProcSts;
+ if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT)
+ rc = rtFuzzObsExecCtxClientRunFuzzingAware(pThis, pExecCtx, &ProcSts);
+ else
+ {
+ rc = rtFuzzObsExecCtxClientRun(pThis, pExecCtx, &ProcSts);
+ ASMAtomicIncU32(&pThis->Stats.cFuzzedInputs);
+ ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsPerSec);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzTgtStateAddProcSts(pExecCtx->hTgtState, &ProcSts);
+ AssertRC(rc);
+
+ if (ProcSts.enmReason != RTPROCEXITREASON_NORMAL)
+ {
+ ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsCrash);
+ rc = rtFuzzObsAddInputToResults(pThis, hFuzzInput, pExecCtx);
+ }
+ }
+ else if (rc == VERR_TIMEOUT)
+ {
+ ASMAtomicIncU32(&pThis->Stats.cFuzzedInputsHang);
+ rc = rtFuzzObsAddInputToResults(pThis, hFuzzInput, pExecCtx);
+ }
+ else
+ AssertFailed();
+
+ /*
+ * Check whether we reached an unknown target state and add the input to the
+ * corpus in that case.
+ */
+ rc = RTFuzzTgtStateAddToRecorder(pExecCtx->hTgtState);
+ if (RT_SUCCESS(rc))
+ {
+ /* Add to corpus and create a new target state for the next run. */
+ RTFuzzInputAddToCtxCorpus(hFuzzInput);
+ RTFuzzTgtStateRelease(pExecCtx->hTgtState);
+ pExecCtx->hTgtState = NIL_RTFUZZTGTSTATE;
+ rc = RTFuzzTgtRecorderCreateNewState(pThis->hTgtRec, &pExecCtx->hTgtState);
+ AssertRC(rc);
+ }
+ else
+ {
+ Assert(rc == VERR_ALREADY_EXISTS);
+ /* Reset the state for the next run. */
+ rc = RTFuzzTgtStateReset(pExecCtx->hTgtState);
+ AssertRC(rc);
+ }
+ RTFuzzInputRelease(hFuzzInput);
+
+ if (pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FILE)
+ RTFileDelete(&szInput[0]);
+ }
+ }
+
+ rtFuzzObsExecCtxDestroy(pThis, pExecCtx);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Fills the input queue of the given observer thread until it is full.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to the observer instance data.
+ * @param pObsThrd The observer thread instance to fill.
+ */
+static int rtFuzzObsMasterInputQueueFill(PRTFUZZOBSINT pThis, PRTFUZZOBSTHRD pObsThrd)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t cInputsAdded = 0;
+ uint32_t cInputsAdd = RTFUZZOBS_THREAD_INPUT_QUEUE_MAX - ASMAtomicReadU32(&pObsThrd->cInputs);
+ uint32_t offW = ASMAtomicReadU32(&pObsThrd->offQueueInputW);
+
+ while ( cInputsAdded < cInputsAdd
+ && RT_SUCCESS(rc))
+ {
+ RTFUZZINPUT hFuzzInput = NIL_RTFUZZINPUT;
+ rc = RTFuzzCtxInputGenerate(pThis->hFuzzCtx, &hFuzzInput);
+ if (RT_SUCCESS(rc))
+ {
+ pObsThrd->ahQueueInput[offW] = hFuzzInput;
+ offW = (offW + 1) % RTFUZZOBS_THREAD_INPUT_QUEUE_MAX;
+ cInputsAdded++;
+ }
+ }
+
+ ASMAtomicWriteU32(&pObsThrd->offQueueInputW, offW);
+ ASMAtomicAddU32(&pObsThrd->cInputs, cInputsAdded);
+
+ return rc;
+}
+
+
+/**
+ * Fuzzing observer master worker loop.
+ *
+ * @returns IPRT status code.
+ * @param hThread The thread handle.
+ * @param pvUser Opaque user data.
+ */
+static DECLCALLBACK(int) rtFuzzObsMasterLoop(RTTHREAD hThread, void *pvUser)
+{
+ RT_NOREF(hThread);
+ int rc = VINF_SUCCESS;
+ PRTFUZZOBSINT pThis = (PRTFUZZOBSINT)pvUser;
+
+ RTThreadUserSignal(hThread);
+
+ while ( !pThis->fShutdown
+ && RT_SUCCESS(rc))
+ {
+ uint64_t bmEvt = ASMAtomicXchgU64(&pThis->bmEvt, 0);
+ uint32_t idxObs = 0;
+ while (bmEvt != 0)
+ {
+ if (bmEvt & 0x1)
+ {
+ /* Create a new input for this observer and kick it. */
+ PRTFUZZOBSTHRD pObsThrd = &pThis->paObsThreads[idxObs];
+
+ rc = rtFuzzObsMasterInputQueueFill(pThis, pObsThrd);
+ if (RT_SUCCESS(rc))
+ RTThreadUserSignal(pObsThrd->hThread);
+ }
+
+ idxObs++;
+ bmEvt >>= 1;
+ }
+
+ rc = RTSemEventWait(pThis->hEvtGlobal, RT_INDEFINITE_WAIT);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initializes the given worker thread structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal fuzzing observer state.
+ * @param iObs Observer ID.
+ * @param pObsThrd The observer thread structure.
+ */
+static int rtFuzzObsWorkerThreadInit(PRTFUZZOBSINT pThis, uint32_t idObs, PRTFUZZOBSTHRD pObsThrd)
+{
+ pObsThrd->pFuzzObs = pThis;
+ pObsThrd->idObs = idObs;
+ pObsThrd->fShutdown = false;
+ pObsThrd->cInputs = 0;
+ pObsThrd->offQueueInputW = 0;
+ pObsThrd->offQueueInputR = 0;
+
+ ASMAtomicBitSet(&pThis->bmEvt, idObs);
+ return RTThreadCreate(&pObsThrd->hThread, rtFuzzObsWorkerLoop, pObsThrd, 0, RTTHREADTYPE_IO,
+ RTTHREADFLAGS_WAITABLE, "Fuzz-Worker");
+}
+
+
+/**
+ * Creates the given amount of worker threads and puts them into waiting state.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal fuzzing observer state.
+ * @param cThreads Number of worker threads to create.
+ */
+static int rtFuzzObsWorkersCreate(PRTFUZZOBSINT pThis, uint32_t cThreads)
+{
+ int rc = VINF_SUCCESS;
+ PRTFUZZOBSTHRD paObsThreads = (PRTFUZZOBSTHRD)RTMemAllocZ(cThreads * sizeof(RTFUZZOBSTHRD));
+ if (RT_LIKELY(paObsThreads))
+ {
+ for (unsigned i = 0; i < cThreads && RT_SUCCESS(rc); i++)
+ {
+ rc = rtFuzzObsWorkerThreadInit(pThis, i, &paObsThreads[i]);
+ if (RT_FAILURE(rc))
+ {
+ /* Rollback. */
+
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pThis->paObsThreads = paObsThreads;
+ pThis->cThreads = cThreads;
+ }
+ else
+ RTMemFree(paObsThreads);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Creates the global worker thread managing the input creation and other worker threads.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal fuzzing observer state.
+ */
+static int rtFuzzObsMasterCreate(PRTFUZZOBSINT pThis)
+{
+ pThis->fShutdown = false;
+
+ int rc = RTSemEventCreate(&pThis->hEvtGlobal);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadCreate(&pThis->hThreadGlobal, rtFuzzObsMasterLoop, pThis, 0, RTTHREADTYPE_IO,
+ RTTHREADFLAGS_WAITABLE, "Fuzz-Master");
+ if (RT_SUCCESS(rc))
+ {
+ RTThreadUserWait(pThis->hThreadGlobal, RT_INDEFINITE_WAIT);
+ }
+ else
+ {
+ RTSemEventDestroy(pThis->hEvtGlobal);
+ pThis->hEvtGlobal = NIL_RTSEMEVENT;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Sets up any configured sanitizers to cooperate with the observer.
+ *
+ * @returns IPRT status code.
+ * @param pThis The internal fuzzing observer state.
+ */
+static int rtFuzzObsSetupSanitizerCfg(PRTFUZZOBSINT pThis)
+{
+ int rc = VINF_SUCCESS;
+ bool fSep = false;
+
+ if (pThis->fSanitizers & RTFUZZOBS_SANITIZER_F_ASAN)
+ {
+ /*
+ * Need to set abort_on_error=1 in ASAN_OPTIONS or
+ * the sanitizer will call exit() instead of abort() and we
+ * don't catch invalid memory accesses.
+ */
+ rc = RTStrAAppend(&pThis->pszSanitizerOpts, "abort_on_error=1");
+ fSep = true;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && (pThis->fSanitizers & RTFUZZOBS_SANITIZER_F_SANCOV))
+ {
+ /*
+ * The coverage sanitizer will dump coverage information into a file
+ * on process exit. Need to configure the directory where to dump it.
+ */
+ char aszSanCovCfg[_4K];
+ ssize_t cch = RTStrPrintf2(&aszSanCovCfg[0], sizeof(aszSanCovCfg),
+ "%scoverage=1:coverage_dir=%s",
+ fSep ? ":" : "", pThis->pszTmpDir);
+ if (cch > 0)
+ rc = RTStrAAppend(&pThis->pszSanitizerOpts, &aszSanCovCfg[0]);
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ fSep = true;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pThis->pszSanitizerOpts)
+ {
+ /* Add it to the environment. */
+ if (pThis->hEnv == RTENV_DEFAULT)
+ {
+ /* Clone the environment to keep the default one untouched. */
+ rc = RTEnvClone(&pThis->hEnv, RTENV_DEFAULT);
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTEnvSetEx(pThis->hEnv, "ASAN_OPTIONS", pThis->pszSanitizerOpts);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzObsCreate(PRTFUZZOBS phFuzzObs, RTFUZZCTXTYPE enmType, uint32_t fTgtRecFlags)
+{
+ AssertPtrReturn(phFuzzObs, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ PRTFUZZOBSINT pThis = (PRTFUZZOBSINT)RTMemAllocZ(sizeof(*pThis));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->pszBinary = NULL;
+ pThis->pszBinaryFilename = NULL;
+ pThis->papszArgs = NULL;
+ pThis->hEnv = RTENV_DEFAULT;
+ pThis->msWaitMax = 1000;
+ pThis->hThreadGlobal = NIL_RTTHREAD;
+ pThis->hEvtGlobal = NIL_RTSEMEVENT;
+ pThis->bmEvt = 0;
+ pThis->cThreads = 0;
+ pThis->paObsThreads = NULL;
+ pThis->tsLastStats = RTTimeMilliTS();
+ pThis->Stats.cFuzzedInputsPerSec = 0;
+ pThis->Stats.cFuzzedInputs = 0;
+ pThis->Stats.cFuzzedInputsHang = 0;
+ pThis->Stats.cFuzzedInputsCrash = 0;
+ rc = RTFuzzCtxCreate(&pThis->hFuzzCtx, enmType);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzTgtRecorderCreate(&pThis->hTgtRec, fTgtRecFlags);
+ if (RT_SUCCESS(rc))
+ {
+ *phFuzzObs = pThis;
+ return VINF_SUCCESS;
+ }
+ RTFuzzCtxRelease(pThis->hFuzzCtx);
+ }
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzObsDestroy(RTFUZZOBS hFuzzObs)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ RTFuzzObsExecStop(hFuzzObs);
+
+ /* Clean up all acquired resources. */
+ for (unsigned i = 0; i < pThis->cArgs; i++)
+ RTStrFree(pThis->papszArgs[i]);
+
+ RTMemFree(pThis->papszArgs);
+
+ if (pThis->hEvtGlobal != NIL_RTSEMEVENT)
+ RTSemEventDestroy(pThis->hEvtGlobal);
+
+ if (pThis->pszResultsDir)
+ RTStrFree(pThis->pszResultsDir);
+ if (pThis->pszTmpDir)
+ RTStrFree(pThis->pszTmpDir);
+ if (pThis->pszBinary)
+ RTStrFree(pThis->pszBinary);
+ if (pThis->pszSanitizerOpts)
+ RTStrFree(pThis->pszSanitizerOpts);
+ if (pThis->hEnv != RTENV_DEFAULT)
+ {
+ RTEnvDestroy(pThis->hEnv);
+ pThis->hEnv = RTENV_DEFAULT;
+ }
+ RTFuzzTgtRecorderRelease(pThis->hTgtRec);
+ RTFuzzCtxRelease(pThis->hFuzzCtx);
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzObsQueryCtx(RTFUZZOBS hFuzzObs, PRTFUZZCTX phFuzzCtx)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER);
+
+ RTFuzzCtxRetain(pThis->hFuzzCtx);
+ *phFuzzCtx = pThis->hFuzzCtx;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzObsQueryStats(RTFUZZOBS hFuzzObs, PRTFUZZOBSSTATS pStats)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pStats, VERR_INVALID_POINTER);
+
+ uint64_t tsStatsQuery = RTTimeMilliTS();
+ uint32_t cFuzzedInputsPerSec = ASMAtomicXchgU32(&pThis->Stats.cFuzzedInputsPerSec, 0);
+
+ pStats->cFuzzedInputsCrash = ASMAtomicReadU32(&pThis->Stats.cFuzzedInputsCrash);
+ pStats->cFuzzedInputsHang = ASMAtomicReadU32(&pThis->Stats.cFuzzedInputsHang);
+ pStats->cFuzzedInputs = ASMAtomicReadU32(&pThis->Stats.cFuzzedInputs);
+ uint64_t cPeriodSec = (tsStatsQuery - pThis->tsLastStats) / 1000;
+ if (cPeriodSec)
+ {
+ pStats->cFuzzedInputsPerSec = cFuzzedInputsPerSec / cPeriodSec;
+ pThis->cFuzzedInputsPerSecLast = pStats->cFuzzedInputsPerSec;
+ pThis->tsLastStats = tsStatsQuery;
+ }
+ else
+ pStats->cFuzzedInputsPerSec = pThis->cFuzzedInputsPerSecLast;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzObsSetTmpDirectory(RTFUZZOBS hFuzzObs, const char *pszTmp)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszTmp, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ pThis->pszTmpDir = RTStrDup(pszTmp);
+ if (!pThis->pszTmpDir)
+ rc = VERR_NO_STR_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzObsSetResultDirectory(RTFUZZOBS hFuzzObs, const char *pszResults)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszResults, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ pThis->pszResultsDir = RTStrDup(pszResults);
+ if (!pThis->pszResultsDir)
+ rc = VERR_NO_STR_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzObsSetTestBinary(RTFUZZOBS hFuzzObs, const char *pszBinary, RTFUZZOBSINPUTCHAN enmInputChan)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszBinary, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ pThis->enmInputChan = enmInputChan;
+ pThis->pszBinary = RTStrDup(pszBinary);
+ if (RT_UNLIKELY(!pThis->pszBinary))
+ rc = VERR_NO_STR_MEMORY;
+ else
+ pThis->pszBinaryFilename = RTPathFilename(pThis->pszBinary);
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzObsSetTestBinaryArgs(RTFUZZOBS hFuzzObs, const char * const *papszArgs, unsigned cArgs)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ int rc = VINF_SUCCESS;
+ char **papszArgsOld = pThis->papszArgs;
+ if (papszArgs)
+ {
+ pThis->papszArgs = (char **)RTMemAllocZ(sizeof(char **) * (cArgs + 1));
+ if (RT_LIKELY(pThis->papszArgs))
+ {
+ for (unsigned i = 0; i < cArgs; i++)
+ {
+ pThis->papszArgs[i] = RTStrDup(papszArgs[i]);
+ if (RT_UNLIKELY(!pThis->papszArgs[i]))
+ {
+ while (i > 0)
+ {
+ i--;
+ RTStrFree(pThis->papszArgs[i]);
+ }
+ break;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ RTMemFree(pThis->papszArgs);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_FAILURE(rc))
+ pThis->papszArgs = papszArgsOld;
+ else
+ pThis->cArgs = cArgs;
+ }
+ else
+ {
+ pThis->papszArgs = NULL;
+ pThis->cArgs = 0;
+ if (papszArgsOld)
+ {
+ char **ppsz = papszArgsOld;
+ while (*ppsz != NULL)
+ {
+ RTStrFree(*ppsz);
+ ppsz++;
+ }
+ RTMemFree(papszArgsOld);
+ }
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzObsSetTestBinaryEnv(RTFUZZOBS hFuzzObs, RTENV hEnv)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ pThis->hEnv = hEnv;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzObsSetTestBinarySanitizers(RTFUZZOBS hFuzzObs, uint32_t fSanitizers)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ pThis->fSanitizers = fSanitizers;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzObsSetTestBinaryTimeout(RTFUZZOBS hFuzzObs, RTMSINTERVAL msTimeoutMax)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ pThis->msWaitMax = msTimeoutMax;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzObsExecStart(RTFUZZOBS hFuzzObs, uint32_t cProcs)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(cProcs <= sizeof(uint64_t) * 8, VERR_INVALID_PARAMETER);
+ AssertReturn( pThis->enmInputChan == RTFUZZOBSINPUTCHAN_FILE
+ || pThis->pszTmpDir != NULL,
+ VERR_INVALID_STATE);
+
+ int rc = VINF_SUCCESS;
+ if (!cProcs)
+ cProcs = RT_MIN(RTMpGetPresentCoreCount(), sizeof(uint64_t) * 8);
+
+ rc = rtFuzzObsSetupSanitizerCfg(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /* Spin up the worker threads first. */
+ rc = rtFuzzObsWorkersCreate(pThis, cProcs);
+ if (RT_SUCCESS(rc))
+ {
+ /* Spin up the global thread. */
+ rc = rtFuzzObsMasterCreate(pThis);
+ }
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzObsExecStop(RTFUZZOBS hFuzzObs)
+{
+ PRTFUZZOBSINT pThis = hFuzzObs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ /* Wait for the master thread to terminate. */
+ if (pThis->hThreadGlobal != NIL_RTTHREAD)
+ {
+ ASMAtomicXchgBool(&pThis->fShutdown, true);
+ RTSemEventSignal(pThis->hEvtGlobal);
+ RTThreadWait(pThis->hThreadGlobal, RT_INDEFINITE_WAIT, NULL);
+ pThis->hThreadGlobal = NIL_RTTHREAD;
+ }
+
+ /* Destroy the workers. */
+ if (pThis->paObsThreads)
+ {
+ for (unsigned i = 0; i < pThis->cThreads; i++)
+ {
+ PRTFUZZOBSTHRD pThrd = &pThis->paObsThreads[i];
+ ASMAtomicXchgBool(&pThrd->fShutdown, true);
+ RTThreadUserSignal(pThrd->hThread);
+ RTThreadWait(pThrd->hThread, RT_INDEFINITE_WAIT, NULL);
+ }
+
+ RTMemFree(pThis->paObsThreads);
+ pThis->paObsThreads = NULL;
+ pThis->cThreads = 0;
+ }
+
+ RTSemEventDestroy(pThis->hEvtGlobal);
+ pThis->hEvtGlobal = NIL_RTSEMEVENT;
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/fuzz/fuzz-target-recorder.cpp b/src/VBox/Runtime/common/fuzz/fuzz-target-recorder.cpp
new file mode 100644
index 00000000..36651427
--- /dev/null
+++ b/src/VBox/Runtime/common/fuzz/fuzz-target-recorder.cpp
@@ -0,0 +1,797 @@
+/* $Id: fuzz-target-recorder.cpp $ */
+/** @file
+ * IPRT - Fuzzing framework API, target state recorder.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/fuzz.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/crc.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/pipe.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the internal fuzzed target recorder state. */
+typedef struct RTFUZZTGTRECINT *PRTFUZZTGTRECINT;
+
+
+/**
+ * Stdout/Stderr buffer.
+ */
+typedef struct RTFUZZTGTSTDOUTERRBUF
+{
+ /** Current amount buffered. */
+ size_t cbBuf;
+ /** Maxmium amount to buffer. */
+ size_t cbBufMax;
+ /** Base pointer to the data buffer. */
+ uint8_t *pbBase;
+} RTFUZZTGTSTDOUTERRBUF;
+/** Pointer to a stdout/stderr buffer. */
+typedef RTFUZZTGTSTDOUTERRBUF *PRTFUZZTGTSTDOUTERRBUF;
+
+
+/**
+ * Internal fuzzed target state.
+ */
+typedef struct RTFUZZTGTSTATEINT
+{
+ /** Node for the list of states. */
+ RTLISTNODE NdStates;
+ /** Checksum for the state. */
+ uint64_t uChkSum;
+ /** Magic identifying the structure. */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** The owning recorder instance. */
+ PRTFUZZTGTRECINT pTgtRec;
+ /** Flag whether the state is finalized. */
+ bool fFinalized;
+ /** Flag whether the state is contained in the recorded set. */
+ bool fInRecSet;
+ /** The stdout data buffer. */
+ RTFUZZTGTSTDOUTERRBUF StdOutBuf;
+ /** The stderr data buffer. */
+ RTFUZZTGTSTDOUTERRBUF StdErrBuf;
+ /** Process status. */
+ RTPROCSTATUS ProcSts;
+ /** Coverage report buffer. */
+ void *pvCovReport;
+ /** Size of the coverage report in bytes. */
+ size_t cbCovReport;
+ /** Number of traced edges. */
+ size_t cEdges;
+} RTFUZZTGTSTATEINT;
+/** Pointer to an internal fuzzed target state. */
+typedef RTFUZZTGTSTATEINT *PRTFUZZTGTSTATEINT;
+
+
+/**
+ * Recorder states node in the AVL tree.
+ */
+typedef struct RTFUZZTGTRECNODE
+{
+ /** The AVL tree core (keyed by checksum). */
+ AVLU64NODECORE Core;
+ /** The list anchor for the individual states. */
+ RTLISTANCHOR LstStates;
+} RTFUZZTGTRECNODE;
+/** Pointer to a recorder states node. */
+typedef RTFUZZTGTRECNODE *PRTFUZZTGTRECNODE;
+
+
+/**
+ * Edge information node.
+ */
+typedef struct RTFUZZTGTEDGE
+{
+ /** The AVL tree core (keyed by offset). */
+ AVLU64NODECORE Core;
+ /** Number of times the edge was hit. */
+ volatile uint64_t cHits;
+} RTFUZZTGTEDGE;
+/** Pointer to a edge information node. */
+typedef RTFUZZTGTEDGE *PRTFUZZTGTEDGE;
+
+
+/**
+ * Internal fuzzed target recorder state.
+ */
+typedef struct RTFUZZTGTRECINT
+{
+ /** Magic value for identification. */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** Flags passed when the recorder was created. */
+ uint32_t fRecFlags;
+ /** Semaphore protecting the states tree. */
+ RTSEMRW hSemRwStates;
+ /** The AVL tree for indexing the recorded state (keyed by stdout/stderr buffer size). */
+ AVLU64TREE TreeStates;
+ /** Semaphore protecting the edges tree. */
+ RTSEMRW hSemRwEdges;
+ /** The AVL tree for discovered edges when coverage reports are collected. */
+ AVLU64TREE TreeEdges;
+ /** Number of edges discovered so far. */
+ volatile uint64_t cEdges;
+ /** The discovered offset width. */
+ volatile uint32_t cbCovOff;
+} RTFUZZTGTRECINT;
+
+
+/** SanCov magic for 64bit offsets. */
+#define SANCOV_MAGIC_64 UINT64_C(0xc0bfffffffffff64)
+/** SanCov magic for 32bit offsets. */
+#define SANCOV_MAGIC_32 UINT64_C(0xc0bfffffffffff32)
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes the given stdout/stderr buffer.
+ *
+ * @param pBuf The buffer to initialize.
+ */
+static void rtFuzzTgtStdOutErrBufInit(PRTFUZZTGTSTDOUTERRBUF pBuf)
+{
+ pBuf->cbBuf = 0;
+ pBuf->cbBufMax = 0;
+ pBuf->pbBase = NULL;
+}
+
+
+/**
+ * Frees all allocated resources in the given stdout/stderr buffer.
+ *
+ * @param pBuf The buffer to free.
+ */
+static void rtFuzzTgtStdOutErrBufFree(PRTFUZZTGTSTDOUTERRBUF pBuf)
+{
+ if (pBuf->pbBase)
+ RTMemFree(pBuf->pbBase);
+}
+
+
+/**
+ * Fills the given stdout/stderr buffer from the given pipe.
+ *
+ * @returns IPRT status code.
+ * @param pBuf The buffer to fill.
+ * @param hPipeRead The pipe to read from.
+ */
+static int rtFuzzTgtStdOutErrBufFillFromPipe(PRTFUZZTGTSTDOUTERRBUF pBuf, RTPIPE hPipeRead)
+{
+ int rc = VINF_SUCCESS;
+
+ size_t cbRead = 0;
+ size_t cbThisRead = 0;
+ do
+ {
+ cbThisRead = pBuf->cbBufMax - pBuf->cbBuf;
+ if (!cbThisRead)
+ {
+ /* Try to increase the buffer. */
+ uint8_t *pbNew = (uint8_t *)RTMemRealloc(pBuf->pbBase, pBuf->cbBufMax + _4K);
+ if (RT_LIKELY(pbNew))
+ {
+ pBuf->cbBufMax += _4K;
+ pBuf->pbBase = pbNew;
+ }
+ cbThisRead = pBuf->cbBufMax - pBuf->cbBuf;
+ }
+
+ if (cbThisRead)
+ {
+ rc = RTPipeRead(hPipeRead, pBuf->pbBase + pBuf->cbBuf, cbThisRead, &cbRead);
+ if (RT_SUCCESS(rc))
+ pBuf->cbBuf += cbRead;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ } while ( RT_SUCCESS(rc)
+ && cbRead == cbThisRead);
+
+ return rc;
+}
+
+
+/**
+ * Writes the given buffer to the given file.
+ *
+ * @returns IPRT status code.
+ * @param pBuf The buffer to write.
+ * @param pszFilename Where to write the buffer.
+ */
+static int rtFuzzTgtStateStdOutErrBufWriteToFile(PRTFUZZTGTSTDOUTERRBUF pBuf, const char *pszFilename)
+{
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFile, pBuf->pbBase, pBuf->cbBuf, NULL);
+ AssertRC(rc);
+ RTFileClose(hFile);
+
+ if (RT_FAILURE(rc))
+ RTFileDelete(pszFilename);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Scans the given target state for newly discovered edges in the coverage report.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzer target recorder instance.
+ * @param pTgtState The target state to check.
+ */
+static int rtFuzzTgtRecScanStateForNewEdges(PRTFUZZTGTRECINT pThis, PRTFUZZTGTSTATEINT pTgtState)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pTgtState->pvCovReport)
+ {
+ rc = RTSemRWRequestRead(pThis->hSemRwEdges, RT_INDEFINITE_WAIT); AssertRC(rc);
+
+ uint32_t cbCovOff = ASMAtomicReadU32(&pThis->cbCovOff);
+ Assert(cbCovOff != 0);
+
+ uint8_t *pbCovCur = (uint8_t *)pTgtState->pvCovReport;
+ size_t cEdgesLeft = pTgtState->cbCovReport / cbCovOff;
+ while (cEdgesLeft)
+ {
+ uint64_t offCur = cbCovOff == sizeof(uint64_t)
+ ? *(uint64_t *)pbCovCur
+ : *(uint32_t *)pbCovCur;
+
+ PRTFUZZTGTEDGE pEdge = (PRTFUZZTGTEDGE)RTAvlU64Get(&pThis->TreeEdges, offCur);
+ if (!pEdge)
+ {
+ /* New edge discovered, allocate and add. */
+ rc = RTSemRWReleaseRead(pThis->hSemRwEdges); AssertRC(rc);
+
+ pEdge = (PRTFUZZTGTEDGE)RTMemAllocZ(sizeof(RTFUZZTGTEDGE));
+ if (RT_LIKELY(pEdge))
+ {
+ pEdge->Core.Key = offCur;
+ pEdge->cHits = 1;
+ rc = RTSemRWRequestWrite(pThis->hSemRwEdges, RT_INDEFINITE_WAIT); AssertRC(rc);
+
+ bool fIns = RTAvlU64Insert(&pThis->TreeEdges, &pEdge->Core);
+ if (!fIns)
+ {
+ /* Someone raced us, free and query again. */
+ RTMemFree(pEdge);
+ pEdge = (PRTFUZZTGTEDGE)RTAvlU64Get(&pThis->TreeEdges, offCur);
+ AssertPtr(pEdge);
+
+ ASMAtomicIncU64(&pEdge->cHits);
+ }
+ else
+ ASMAtomicIncU64(&pThis->cEdges);
+
+ rc = RTSemRWReleaseWrite(pThis->hSemRwEdges); AssertRC(rc);
+ rc = RTSemRWRequestRead(pThis->hSemRwEdges, RT_INDEFINITE_WAIT); AssertRC(rc);
+ }
+ else
+ {
+ rc = RTSemRWRequestRead(pThis->hSemRwEdges, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ else
+ ASMAtomicIncU64(&pEdge->cHits);
+
+ pbCovCur += cbCovOff;
+ cEdgesLeft--;
+ }
+
+ rc = RTSemRWReleaseRead(pThis->hSemRwEdges); AssertRC(rc);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Destorys the given fuzzer target recorder freeing all allocated resources.
+ *
+ * @param pThis The fuzzer target recorder instance.
+ */
+static void rtFuzzTgtRecDestroy(PRTFUZZTGTRECINT pThis)
+{
+ RT_NOREF(pThis);
+}
+
+
+/**
+ * Destroys the given fuzzer target state freeing all allocated resources.
+ *
+ * @param pThis The fuzzed target state instance.
+ */
+static void rtFuzzTgtStateDestroy(PRTFUZZTGTSTATEINT pThis)
+{
+ pThis->u32Magic = ~(uint32_t)0; /** @todo Dead magic */
+ rtFuzzTgtStdOutErrBufFree(&pThis->StdOutBuf);
+ rtFuzzTgtStdOutErrBufFree(&pThis->StdErrBuf);
+ RTMemFree(pThis);
+}
+
+
+/**
+ * Compares two given target states, checking whether they match.
+ *
+ * @returns Flag whether the states are identical.
+ * @param pThis Target state 1.
+ * @param pThat Target state 2.
+ */
+static bool rtFuzzTgtStateDoMatch(PRTFUZZTGTSTATEINT pThis, PRTFUZZTGTSTATEINT pThat)
+{
+ PRTFUZZTGTRECINT pTgtRec = pThis->pTgtRec;
+ Assert(pTgtRec == pThat->pTgtRec);
+
+ if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_STDOUT)
+ && ( pThis->StdOutBuf.cbBuf != pThat->StdOutBuf.cbBuf
+ || ( pThis->StdOutBuf.cbBuf > 0
+ && memcmp(pThis->StdOutBuf.pbBase, pThat->StdOutBuf.pbBase, pThis->StdOutBuf.cbBuf))))
+ return false;
+
+ if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_STDERR)
+ && ( pThis->StdErrBuf.cbBuf != pThat->StdErrBuf.cbBuf
+ || ( pThis->StdErrBuf.cbBuf > 0
+ && memcmp(pThis->StdErrBuf.pbBase, pThat->StdErrBuf.pbBase, pThis->StdErrBuf.cbBuf))))
+ return false;
+
+ if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_PROCSTATUS)
+ && memcmp(&pThis->ProcSts, &pThat->ProcSts, sizeof(RTPROCSTATUS)))
+ return false;
+
+ if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_SANCOV)
+ && ( pThis->cbCovReport != pThat->cbCovReport
+ || ( pThis->cbCovReport > 0
+ && memcmp(pThis->pvCovReport, pThat->pvCovReport, pThis->cbCovReport))))
+ return false;
+
+ return true;
+}
+
+
+RTDECL(int) RTFuzzTgtRecorderCreate(PRTFUZZTGTREC phFuzzTgtRec, uint32_t fRecFlags)
+{
+ AssertPtrReturn(phFuzzTgtRec, VERR_INVALID_POINTER);
+ AssertReturn(!(fRecFlags & ~RTFUZZTGT_REC_STATE_F_VALID), VERR_INVALID_PARAMETER);
+
+ int rc;
+ PRTFUZZTGTRECINT pThis = (PRTFUZZTGTRECINT)RTMemAllocZ(sizeof(*pThis));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->u32Magic = 0; /** @todo */
+ pThis->cRefs = 1;
+ pThis->TreeStates = NULL;
+ pThis->TreeEdges = NULL;
+ pThis->cbCovOff = 0;
+ pThis->fRecFlags = fRecFlags;
+
+ rc = RTSemRWCreate(&pThis->hSemRwStates);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemRWCreate(&pThis->hSemRwEdges);
+ if (RT_SUCCESS(rc))
+ {
+ *phFuzzTgtRec = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTSemRWDestroy(pThis->hSemRwStates);
+ }
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(uint32_t) RTFuzzTgtRecorderRetain(RTFUZZTGTREC hFuzzTgtRec)
+{
+ PRTFUZZTGTRECINT pThis = hFuzzTgtRec;
+
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTFuzzTgtRecorderRelease(RTFUZZTGTREC hFuzzTgtRec)
+{
+ PRTFUZZTGTRECINT pThis = hFuzzTgtRec;
+ if (pThis == NIL_RTFUZZTGTREC)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0)
+ rtFuzzTgtRecDestroy(pThis);
+ return cRefs;
+}
+
+
+RTDECL(int) RTFuzzTgtRecorderCreateNewState(RTFUZZTGTREC hFuzzTgtRec, PRTFUZZTGTSTATE phFuzzTgtState)
+{
+ PRTFUZZTGTRECINT pThis = hFuzzTgtRec;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phFuzzTgtState, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ PRTFUZZTGTSTATEINT pState = (PRTFUZZTGTSTATEINT)RTMemAllocZ(sizeof(*pState));
+ if (RT_LIKELY(pState))
+ {
+ pState->u32Magic = 0; /** @todo */
+ pState->cRefs = 1;
+ pState->pTgtRec = pThis;
+ pState->fFinalized = false;
+ rtFuzzTgtStdOutErrBufInit(&pState->StdOutBuf);
+ rtFuzzTgtStdOutErrBufInit(&pState->StdErrBuf);
+ *phFuzzTgtState = pState;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(uint32_t) RTFuzzTgtStateRetain(RTFUZZTGTSTATE hFuzzTgtState)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTFuzzTgtStateRelease(RTFUZZTGTSTATE hFuzzTgtState)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ if (pThis == NIL_RTFUZZTGTSTATE)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0 && !pThis->fInRecSet)
+ rtFuzzTgtStateDestroy(pThis);
+ return cRefs;
+}
+
+
+RTDECL(int) RTFuzzTgtStateReset(RTFUZZTGTSTATE hFuzzTgtState)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ /* Clear the buffers. */
+ pThis->StdOutBuf.cbBuf = 0;
+ pThis->StdErrBuf.cbBuf = 0;
+ RT_ZERO(pThis->ProcSts);
+ if (pThis->pvCovReport)
+ RTMemFree(pThis->pvCovReport);
+ pThis->pvCovReport = NULL;
+ pThis->fFinalized = false;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzTgtStateFinalize(RTFUZZTGTSTATE hFuzzTgtState)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ /* Create the checksum. */
+ PRTFUZZTGTRECINT pTgtRec = pThis->pTgtRec;
+ uint64_t uChkSum = RTCrc64Start();
+ if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_STDOUT)
+ && pThis->StdOutBuf.cbBuf)
+ uChkSum = RTCrc64Process(uChkSum, pThis->StdOutBuf.pbBase, pThis->StdOutBuf.cbBuf);
+ if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_STDERR)
+ && pThis->StdErrBuf.cbBuf)
+ uChkSum = RTCrc64Process(uChkSum, pThis->StdErrBuf.pbBase, pThis->StdErrBuf.cbBuf);
+ if (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_PROCSTATUS)
+ uChkSum = RTCrc64Process(uChkSum, &pThis->ProcSts, sizeof(RTPROCSTATUS));
+ if ( (pTgtRec->fRecFlags & RTFUZZTGT_REC_STATE_F_SANCOV)
+ && pThis->pvCovReport)
+ uChkSum = RTCrc64Process(uChkSum, pThis->pvCovReport, pThis->cbCovReport);
+
+ pThis->uChkSum = RTCrc64Finish(uChkSum);
+ pThis->fFinalized = true;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzTgtStateAddToRecorder(RTFUZZTGTSTATE hFuzzTgtState)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ if (!pThis->fFinalized)
+ {
+ int rc = RTFuzzTgtStateFinalize(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ PRTFUZZTGTRECINT pTgtRec = pThis->pTgtRec;
+
+ /* Try to find a node matching the stdout and sterr sizes first. */
+ int rc = RTSemRWRequestRead(pTgtRec->hSemRwStates, RT_INDEFINITE_WAIT); AssertRC(rc);
+ PRTFUZZTGTRECNODE pNode = (PRTFUZZTGTRECNODE)RTAvlU64Get(&pTgtRec->TreeStates, pThis->uChkSum);
+ if (pNode)
+ {
+ /* Traverse the states and check if any matches the stdout and stderr buffers exactly. */
+ PRTFUZZTGTSTATEINT pIt;
+ bool fMatchFound = false;
+ RTListForEach(&pNode->LstStates, pIt, RTFUZZTGTSTATEINT, NdStates)
+ {
+ if (rtFuzzTgtStateDoMatch(pThis, pIt))
+ {
+ fMatchFound = true;
+ break;
+ }
+ }
+
+ rc = RTSemRWReleaseRead(pTgtRec->hSemRwStates); AssertRC(rc);
+ if (!fMatchFound)
+ {
+ rc = RTSemRWRequestWrite(pTgtRec->hSemRwStates, RT_INDEFINITE_WAIT); AssertRC(rc);
+ RTListAppend(&pNode->LstStates, &pThis->NdStates);
+ rc = RTSemRWReleaseWrite(pTgtRec->hSemRwStates); AssertRC(rc);
+ pThis->fInRecSet = true;
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+ }
+ else
+ {
+ rc = RTSemRWReleaseRead(pTgtRec->hSemRwStates); AssertRC(rc);
+
+ /* No node found, create new one and insert in to the tree right away. */
+ pNode = (PRTFUZZTGTRECNODE)RTMemAllocZ(sizeof(*pNode));
+ if (RT_LIKELY(pNode))
+ {
+ pNode->Core.Key = pThis->uChkSum;
+ RTListInit(&pNode->LstStates);
+ RTListAppend(&pNode->LstStates, &pThis->NdStates);
+ rc = RTSemRWRequestWrite(pTgtRec->hSemRwStates, RT_INDEFINITE_WAIT); AssertRC(rc);
+ bool fIns = RTAvlU64Insert(&pTgtRec->TreeStates, &pNode->Core);
+ if (!fIns)
+ {
+ /* Someone raced us, get the new node and append there. */
+ RTMemFree(pNode);
+ pNode = (PRTFUZZTGTRECNODE)RTAvlU64Get(&pTgtRec->TreeStates, pThis->uChkSum);
+ AssertPtr(pNode);
+ RTListAppend(&pNode->LstStates, &pThis->NdStates);
+ }
+ rc = RTSemRWReleaseWrite(pTgtRec->hSemRwStates); AssertRC(rc);
+ pThis->fInRecSet = true;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pThis->fInRecSet)
+ rc = rtFuzzTgtRecScanStateForNewEdges(pTgtRec, pThis);
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzTgtStateAppendStdoutFromBuf(RTFUZZTGTSTATE hFuzzTgtState, const void *pvStdOut, size_t cbStdOut)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ RT_NOREF(pvStdOut, cbStdOut);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+RTDECL(int) RTFuzzTgtStateAppendStderrFromBuf(RTFUZZTGTSTATE hFuzzTgtState, const void *pvStdErr, size_t cbStdErr)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ RT_NOREF(pvStdErr, cbStdErr);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+RTDECL(int) RTFuzzTgtStateAppendStdoutFromPipe(RTFUZZTGTSTATE hFuzzTgtState, RTPIPE hPipe)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ return rtFuzzTgtStdOutErrBufFillFromPipe(&pThis->StdOutBuf, hPipe);
+}
+
+
+RTDECL(int) RTFuzzTgtStateAppendStderrFromPipe(RTFUZZTGTSTATE hFuzzTgtState, RTPIPE hPipe)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ return rtFuzzTgtStdOutErrBufFillFromPipe(&pThis->StdErrBuf, hPipe);
+}
+
+
+RTDECL(int) RTFuzzTgtStateAddSanCovReportFromFile(RTFUZZTGTSTATE hFuzzTgtState, const char *pszFilename)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ uint8_t *pbSanCov = NULL;
+ size_t cbSanCov = 0;
+ int rc = RTFileReadAll(pszFilename, (void **)&pbSanCov, &cbSanCov);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check for the magic identifying whether the offsets are 32bit or 64bit. */
+ if ( cbSanCov >= sizeof(uint64_t)
+ && ( *(uint64_t *)pbSanCov == SANCOV_MAGIC_64
+ || *(uint64_t *)pbSanCov == SANCOV_MAGIC_32))
+ {
+ uint32_t cbCovOff = sizeof(uint32_t);
+ if (*(uint64_t *)pbSanCov == SANCOV_MAGIC_64)
+ cbCovOff = sizeof(uint64_t);
+
+ uint32_t cbCovDet = ASMAtomicReadU32(&pThis->pTgtRec->cbCovOff);
+ if (!cbCovDet)
+ {
+ /* Set the detected offset width. */
+ if (!ASMAtomicCmpXchgU32(&pThis->pTgtRec->cbCovOff, cbCovOff, 0))
+ {
+ /* Someone raced us, check again. */
+ cbCovDet = ASMAtomicReadU32(&pThis->pTgtRec->cbCovOff);
+ Assert(cbCovDet != 0);
+ }
+ else
+ cbCovDet = cbCovOff;
+ }
+
+ if (cbCovDet == cbCovOff)
+ {
+ /*
+ * Just copy the offsets into the state for now. Now further analysis
+ * is happening right now, just checking whether the content changed for
+ * the states.to spot newly discovered edges.
+ */
+ pThis->cbCovReport = cbSanCov - sizeof(uint64_t);
+ pThis->pvCovReport = RTMemDup(pbSanCov + sizeof(uint64_t), pThis->cbCovReport);
+ if (!pThis->pvCovReport)
+ {
+ pThis->cbCovReport = 0;
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ else
+ rc = VERR_INVALID_STATE; /* Mixing 32bit and 64bit offsets shouldn't happen, is not supported. */
+ }
+ else
+ rc = VERR_INVALID_STATE;
+ RTFileReadAllFree(pbSanCov, cbSanCov);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzTgtStateAddProcSts(RTFUZZTGTSTATE hFuzzTgtState, PCRTPROCSTATUS pProcSts)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pProcSts, VERR_INVALID_POINTER);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ pThis->ProcSts = *pProcSts;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzTgtStateDumpToDir(RTFUZZTGTSTATE hFuzzTgtState, const char *pszDirPath)
+{
+ PRTFUZZTGTSTATEINT pThis = hFuzzTgtState;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszDirPath, VERR_INVALID_POINTER);
+ AssertReturn(!pThis->fFinalized, VERR_WRONG_ORDER);
+
+ int rc = VINF_SUCCESS;
+ char szPath[RTPATH_MAX];
+ if (pThis->StdOutBuf.cbBuf)
+ {
+ rc = RTPathJoin(szPath, sizeof(szPath), pszDirPath, "stdout"); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzTgtStateStdOutErrBufWriteToFile(&pThis->StdOutBuf, &szPath[0]);
+ }
+
+ if ( RT_SUCCESS(rc)
+ && pThis->StdErrBuf.cbBuf)
+ {
+ rc = RTPathJoin(szPath, sizeof(szPath), pszDirPath, "stderr"); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzTgtStateStdOutErrBufWriteToFile(&pThis->StdErrBuf, &szPath[0]);
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/fuzz/fuzz.cpp b/src/VBox/Runtime/common/fuzz/fuzz.cpp
new file mode 100644
index 00000000..db68ddc0
--- /dev/null
+++ b/src/VBox/Runtime/common/fuzz/fuzz.cpp
@@ -0,0 +1,2322 @@
+/* $Id: fuzz.cpp $ */
+/** @file
+ * IPRT - Fuzzing framework API, core.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/fuzz.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/avl.h>
+#include <iprt/critsect.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/md5.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/rand.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTFUZZCTX_MAGIC UINT32_C(0xdeadc0de) /** @todo */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to the internal fuzzer state. */
+typedef struct RTFUZZCTXINT *PRTFUZZCTXINT;
+/** Pointer to a fuzzed mutation. */
+typedef struct RTFUZZMUTATION *PRTFUZZMUTATION;
+/** Pointer to a fuzzed mutation pointer. */
+typedef PRTFUZZMUTATION *PPRTFUZZMUTATION;
+/** Pointer to a const mutation. */
+typedef const struct RTFUZZMUTATION *PCRTFUZZMUTATION;
+
+
+/**
+ * Mutator class.
+ */
+typedef enum RTFUZZMUTATORCLASS
+{
+ /** Invalid class, do not use. */
+ RTFUZZMUTATORCLASS_INVALID = 0,
+ /** Mutator operates on single bits. */
+ RTFUZZMUTATORCLASS_BITS,
+ /** Mutator operates on bytes (single or multiple). */
+ RTFUZZMUTATORCLASS_BYTES,
+ /** Mutator interpretes data as integers and operates on them. */
+ RTFUZZMUTATORCLASS_INTEGERS,
+ /** Mutator uses multiple mutations to create new mutations. */
+ RTFUZZMUTATORCLASS_MUTATORS,
+ /** 32bit hack. */
+ RTFUZZMUTATORCLASS_32BIT_HACK = 0x7fffffff
+} RTFUZZMUTATORCLASS;
+
+
+/**
+ * Mutator preparation callback.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzer context instance.
+ * @param offStart Where the mutation should start.
+ * @param pMutationParent The parent mutation to start working from.
+ * @param ppMutation Where to store the created mutation on success.
+ */
+typedef DECLCALLBACKTYPE(int, FNRTFUZZCTXMUTATORPREP,(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation));
+/** Pointer to a mutator preparation callback. */
+typedef FNRTFUZZCTXMUTATORPREP *PFNRTFUZZCTXMUTATORPREP;
+
+
+/**
+ * Mutator execution callback.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzer context instance.
+ * @param pMutation The mutation to work on.
+ * @param pvMutation Mutation dependent data.
+ * @param pbBuf The buffer to work on.
+ * @param cbBuf Size of the remaining buffer.
+ */
+typedef DECLCALLBACKTYPE(int, FNRTFUZZCTXMUTATOREXEC,(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf));
+/** Pointer to a mutator execution callback. */
+typedef FNRTFUZZCTXMUTATOREXEC *PFNRTFUZZCTXMUTATOREXEC;
+
+
+/**
+ * Mutator export callback.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzer context instance.
+ * @param pMutation The mutation to work on.
+ * @param pvMutation Mutation dependent data.
+ * @param pfnExport The export callback.
+ * @param pvUser Opaque user data to pass to the export callback.
+ */
+typedef DECLCALLBACKTYPE(int, FNRTFUZZCTXMUTATOREXPORT,(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ PFNRTFUZZCTXEXPORT pfnExport, void *pvUser));
+/** Pointer to a mutator export callback. */
+typedef FNRTFUZZCTXMUTATOREXPORT *PFNRTFUZZCTXMUTATOREXPORT;
+
+
+/**
+ * Mutator import callback.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzer context instance.
+ * @param pMutation The mutation to work on.
+ * @param pvMutation Mutation dependent data.
+ * @param pfnExport The import callback.
+ * @param pvUser Opaque user data to pass to the import callback.
+ */
+typedef DECLCALLBACKTYPE(int, FNRTFUZZCTXMUTATORIMPORT,(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation,
+ PFNRTFUZZCTXIMPORT pfnImport, void *pvUser));
+/** Pointer to a mutator import callback. */
+typedef FNRTFUZZCTXMUTATORIMPORT *PFNRTFUZZCTXMUTATORIMPORT;
+
+
+/**
+ * A fuzzing mutator descriptor.
+ */
+typedef struct RTFUZZMUTATOR
+{
+ /** Id of the mutator. */
+ const char *pszId;
+ /** Mutator description. */
+ const char *pszDesc;
+ /** Mutator index. */
+ uint32_t uMutator;
+ /** Mutator class. */
+ RTFUZZMUTATORCLASS enmClass;
+ /** Additional flags for the mutator, controlling the behavior. */
+ uint64_t fFlags;
+ /** The preparation callback. */
+ PFNRTFUZZCTXMUTATORPREP pfnPrep;
+ /** The execution callback. */
+ PFNRTFUZZCTXMUTATOREXEC pfnExec;
+ /** The export callback. */
+ PFNRTFUZZCTXMUTATOREXPORT pfnExport;
+ /** The import callback. */
+ PFNRTFUZZCTXMUTATORIMPORT pfnImport;
+} RTFUZZMUTATOR;
+/** Pointer to a fuzzing mutator descriptor. */
+typedef RTFUZZMUTATOR *PRTFUZZMUTATOR;
+/** Pointer to a const fuzzing mutator descriptor. */
+typedef const RTFUZZMUTATOR *PCRTFUZZMUTATOR;
+
+/** The special corpus mutator. */
+#define RTFUZZMUTATOR_ID_CORPUS UINT32_C(0xffffffff)
+
+/** Mutator always works from the end of the buffer (no starting offset generation). */
+#define RTFUZZMUTATOR_F_END_OF_BUF RT_BIT_64(0)
+/** Default flags. */
+#define RTFUZZMUTATOR_F_DEFAULT (0)
+
+
+/**
+ * A fuzzed mutation.
+ */
+typedef struct RTFUZZMUTATION
+{
+ /** The AVL tree core. */
+ AVLU64NODECORE Core;
+ /** The list node if the mutation has the mutated
+ * data allocated. */
+ RTLISTNODE NdAlloc;
+ /** Magic identifying this structure. */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** The fuzzer this mutation belongs to. */
+ PRTFUZZCTXINT pFuzzer;
+ /** Parent mutation (no reference is held), NULL means root or original data. */
+ PRTFUZZMUTATION pMutationParent;
+ /** Start offset where new mutations are allowed to start. */
+ uint64_t offMutStartNew;
+ /** Size of the range in bytes where mutations are allowed to happen. */
+ uint64_t cbMutNew;
+ /** Mutation level. */
+ uint32_t iLvl;
+ /** The mutator causing this mutation, NULL if original input data. */
+ PCRTFUZZMUTATOR pMutator;
+ /** Byte offset where the mutation starts. */
+ uint64_t offMutation;
+ /** Size of the generated input data in bytes after the mutation was applied. */
+ size_t cbInput;
+ /** Size of the mutation dependent data. */
+ size_t cbMutation;
+ /** Size allocated for the input. */
+ size_t cbAlloc;
+ /** Pointer to the input data if created. */
+ void *pvInput;
+ /** Flag whether the mutation is contained in the tree of the context. */
+ bool fInTree;
+ /** Flag whether the mutation input data is cached. */
+ bool fCached;
+ /** Mutation dependent data, variable in size. */
+ uint8_t abMutation[1];
+} RTFUZZMUTATION;
+
+
+/**
+ * A fuzzing input seed.
+ */
+typedef struct RTFUZZINPUTINT
+{
+ /** Magic identifying this structure. */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** The fuzzer this input belongs to. */
+ PRTFUZZCTXINT pFuzzer;
+ /** The top mutation to work from (reference held). */
+ PRTFUZZMUTATION pMutationTop;
+ /** Fuzzer context type dependent data. */
+ union
+ {
+ /** Blob data. */
+ struct
+ {
+ /** Pointer to the input data if created. */
+ void *pvInput;
+ } Blob;
+ /** Stream state. */
+ struct
+ {
+ /** Number of bytes seen so far. */
+ size_t cbSeen;
+ } Stream;
+ } u;
+} RTFUZZINPUTINT;
+/** Pointer to the internal input state. */
+typedef RTFUZZINPUTINT *PRTFUZZINPUTINT;
+/** Pointer to an internal input state pointer. */
+typedef PRTFUZZINPUTINT *PPRTFUZZINPUTINT;
+
+
+/**
+ * The fuzzer state.
+ */
+typedef struct RTFUZZCTXINT
+{
+ /** Magic value for identification. */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ volatile uint32_t cRefs;
+ /** The random number generator. */
+ RTRAND hRand;
+ /** Fuzzing context type. */
+ RTFUZZCTXTYPE enmType;
+ /** Semaphore protecting the mutations tree. */
+ RTSEMRW hSemRwMutations;
+ /** The AVL tree for indexing the mutations (keyed by counter). */
+ AVLU64TREE TreeMutations;
+ /** Number of inputs currently in the tree. */
+ volatile uint64_t cMutations;
+ /** The maximum size of one input seed to generate. */
+ size_t cbInputMax;
+ /** Behavioral flags. */
+ uint32_t fFlagsBehavioral;
+ /** Number of enabled mutators. */
+ uint32_t cMutators;
+ /** Pointer to the mutator descriptors. */
+ PRTFUZZMUTATOR paMutators;
+ /** Maximum amount of bytes of mutated inputs to cache. */
+ size_t cbMutationsAllocMax;
+ /** Current amount of bytes of cached mutated inputs. */
+ size_t cbMutationsAlloc;
+ /** List of mutators having data allocated currently. */
+ RTLISTANCHOR LstMutationsAlloc;
+ /** Critical section protecting the allocation list. */
+ RTCRITSECT CritSectAlloc;
+ /** Total number of bytes of memory currently allocated in total for this context. */
+ volatile size_t cbMemTotal;
+ /** Start offset in the input where a mutation is allowed to happen. */
+ uint64_t offMutStart;
+ /** size of the range where a mutation can happen. */
+ uint64_t cbMutRange;
+} RTFUZZCTXINT;
+
+
+/**
+ * The fuzzer state to be exported - all members are stored in little endian form.
+ */
+typedef struct RTFUZZCTXSTATE
+{
+ /** Magic value for identification. */
+ uint32_t u32Magic;
+ /** Context type. */
+ uint32_t uCtxType;
+ /** Size of the PRNG state following in bytes. */
+ uint32_t cbPrng;
+ /** Number of mutator descriptors following. */
+ uint32_t cMutators;
+ /** Number of mutation descriptors following. */
+ uint32_t cMutations;
+ /** Behavioral flags. */
+ uint32_t fFlagsBehavioral;
+ /** Maximum input size to generate. */
+ uint64_t cbInputMax;
+} RTFUZZCTXSTATE;
+/** Pointer to a fuzzing context state. */
+typedef RTFUZZCTXSTATE *PRTFUZZCTXSTATE;
+
+/** BLOB context type. */
+#define RTFUZZCTX_STATE_TYPE_BLOB UINT32_C(0)
+/** Stream context type. */
+#define RTFUZZCTX_STATE_TYPE_STREAM UINT32_C(1)
+
+
+/**
+ * The fuzzer mutation state to be exported - all members are stored in little endian form.
+ */
+typedef struct RTFUZZMUTATIONSTATE
+{
+ /** The mutation identifier. */
+ uint64_t u64Id;
+ /** The mutation identifier of the parent, 0 for no parent. */
+ uint64_t u64IdParent;
+ /** The byte offset where the mutation starts. */
+ uint64_t u64OffMutation;
+ /** Size of input data after mutation was applied. */
+ uint64_t cbInput;
+ /** Size of mutation dependent data following. */
+ uint64_t cbMutation;
+ /** The mutator ID. */
+ uint32_t u32IdMutator;
+ /** The mutation level. */
+ uint32_t iLvl;
+ /** Magic value for identification. */
+ uint32_t u32Magic;
+} RTFUZZMUTATIONSTATE;
+
+
+/**
+ * Fuzzing context memory header.
+ */
+typedef struct RTFUZZMEMHDR
+{
+ /** Size of the memory area following. */
+ size_t cb;
+#if HC_ARCH_BITS == 32
+ /** Some padding. */
+ uint32_t uPadding0;
+#elif HC_ARCH_BITS == 64
+ /** Some padding. */
+ uint64_t uPadding0;
+#else
+# error "Port me"
+#endif
+} RTFUZZMEMHDR;
+/** Pointer to a memory header. */
+typedef RTFUZZMEMHDR *PRTFUZZMEMHDR;
+
+
+/**
+ * Fuzzing context export AVL arguments.
+ */
+typedef struct RTFUZZEXPORTARGS
+{
+ /** Pointer to the export callback. */
+ PFNRTFUZZCTXEXPORT pfnExport;
+ /** Opaque user data to pass to the callback. */
+ void *pvUser;
+} RTFUZZEXPORTARGS;
+/** Pointer to the export arguments. */
+typedef RTFUZZEXPORTARGS *PRTFUZZEXPORTARGS;
+/** Pointer to the constant export arguments. */
+typedef const RTFUZZEXPORTARGS *PCRTFUZZEXPORTARGS;
+
+
+/**
+ * Integer replacing mutator additional data.
+ */
+typedef struct RTFUZZMUTATORINTEGER
+{
+ /** The integer class. */
+ uint8_t uIntClass;
+ /** Flag whether to do a byte swap. */
+ bool fByteSwap;
+ /** The index into the class specific array. */
+ uint16_t idxInt;
+} RTFUZZMUTATORINTEGER;
+/** Pointer to additional integer replacing mutator data. */
+typedef RTFUZZMUTATORINTEGER *PRTFUZZMUTATORINTEGER;
+/** Pointer to constant additional integer replacing mutator data. */
+typedef const RTFUZZMUTATORINTEGER *PCRTFUZZMUTATORINTEGER;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlipPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplacePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteInsertPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsertAppendPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteDeletePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDeletePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation);
+static DECLCALLBACK(int) rtFuzzCtxMutatorIntegerReplacePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation);
+static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation);
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorCorpusExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlipExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplaceExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteInsertExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsertAppendExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteDeleteExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDeleteExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFuzzCtxMutatorIntegerReplaceExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf);
+static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf);
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorExportDefault(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ PFNRTFUZZCTXEXPORT pfnExport, void *pvUser);
+static DECLCALLBACK(int) rtFuzzCtxMutatorImportDefault(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation,
+ PFNRTFUZZCTXIMPORT pfnImport, void *pvUser);
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverExport(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ PFNRTFUZZCTXEXPORT pfnExport, void *pvUser);
+static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverImport(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation,
+ PFNRTFUZZCTXIMPORT pfnImport, void *pvUser);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+/** Signed 8bit interesting values. */
+static int8_t s_ai8Interesting[] = { INT8_MIN, INT8_MIN + 1, -1, 0, 1, INT8_MAX - 1, INT8_MAX };
+/** Unsigned 8bit interesting values. */
+static uint8_t s_au8Interesting[] = { 0, 1, UINT8_MAX - 1, UINT8_MAX };
+/** Signed 16bit interesting values. */
+static int16_t s_ai16Interesting[] = { INT16_MIN, INT16_MIN + 1, -1, 0, 1, INT16_MAX - 1, INT16_MAX };
+/** Unsigned 16bit interesting values. */
+static uint16_t s_au16Interesting[] = { 0, 1, UINT16_MAX - 1, UINT16_MAX };
+/** Signed 32bit interesting values. */
+static int32_t s_ai32Interesting[] = { INT32_MIN, INT32_MIN + 1, -1, 0, 1, INT32_MAX - 1, INT32_MAX };
+/** Unsigned 32bit interesting values. */
+static uint32_t s_au32Interesting[] = { 0, 1, UINT32_MAX - 1, UINT32_MAX };
+/** Signed 64bit interesting values. */
+static int64_t s_ai64Interesting[] = { INT64_MIN, INT64_MIN + 1, -1, 0, 1, INT64_MAX - 1, INT64_MAX };
+/** Unsigned 64bit interesting values. */
+static uint64_t s_au64Interesting[] = { 0, 1, UINT64_MAX - 1, UINT64_MAX };
+
+
+/**
+ * The special corpus mutator for the original data.
+ */
+static RTFUZZMUTATOR const g_MutatorCorpus =
+{
+ /** pszId */
+ "Corpus",
+ /** pszDesc */
+ "Special mutator, which is assigned to the initial corpus",
+ /** uMutator. */
+ RTFUZZMUTATOR_ID_CORPUS,
+ /** enmClass. */
+ RTFUZZMUTATORCLASS_BYTES,
+ /** fFlags */
+ RTFUZZMUTATOR_F_DEFAULT,
+ /** pfnPrep */
+ NULL,
+ /** pfnExec */
+ rtFuzzCtxMutatorCorpusExec,
+ /** pfnExport */
+ rtFuzzCtxMutatorExportDefault,
+ /** pfnImport */
+ rtFuzzCtxMutatorImportDefault
+};
+
+/**
+ * Array of all available mutators.
+ */
+static RTFUZZMUTATOR const g_aMutators[] =
+{
+ /* pszId pszDesc uMutator enmClass fFlags pfnPrep pfnExec pfnExport pfnImport */
+ { "BitFlip", "Flips a single bit in the input", 0, RTFUZZMUTATORCLASS_BITS, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorBitFlipPrep, rtFuzzCtxMutatorBitFlipExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault },
+ { "ByteReplace", "Replaces a single byte in the input", 1, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteReplacePrep, rtFuzzCtxMutatorByteReplaceExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault },
+ { "ByteInsert", "Inserts a single byte sequence into the input", 2, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteInsertPrep, rtFuzzCtxMutatorByteInsertExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault },
+ { "ByteSeqIns", "Inserts a byte sequence in the input", 3, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteSequenceInsertAppendPrep, rtFuzzCtxMutatorByteSequenceInsertAppendExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault },
+ { "ByteSeqApp", "Appends a byte sequence to the input", 4, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_END_OF_BUF, rtFuzzCtxMutatorByteSequenceInsertAppendPrep, rtFuzzCtxMutatorByteSequenceInsertAppendExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault },
+ { "ByteDelete", "Deletes a single byte sequence from the input", 5, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteDeletePrep, rtFuzzCtxMutatorByteDeleteExec, NULL, NULL },
+ { "ByteSeqDel", "Deletes a byte sequence from the input", 6, RTFUZZMUTATORCLASS_BYTES, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorByteSequenceDeletePrep, rtFuzzCtxMutatorByteSequenceDeleteExec, NULL, NULL },
+ { "IntReplace", "Replaces a possible integer with an interesting one", 7, RTFUZZMUTATORCLASS_INTEGERS, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorIntegerReplacePrep, rtFuzzCtxMutatorIntegerReplaceExec, rtFuzzCtxMutatorExportDefault, rtFuzzCtxMutatorImportDefault },
+ { "MutCrossover", "Creates a crossover of two other mutations", 8, RTFUZZMUTATORCLASS_MUTATORS, RTFUZZMUTATOR_F_DEFAULT, rtFuzzCtxMutatorCrossoverPrep, rtFuzzCtxMutatorCrossoverExec, rtFuzzCtxMutatorCrossoverExport, rtFuzzCtxMutatorCrossoverImport }
+};
+
+
+/**
+ * Allocates the given number of bytes.
+ *
+ * @returns Pointer to the allocated memory
+ * @param pThis The fuzzer context instance.
+ * @param cb How much to allocate.
+ */
+static void *rtFuzzCtxMemoryAlloc(PRTFUZZCTXINT pThis, size_t cb)
+{
+ AssertReturn(cb > 0, NULL);
+
+ PRTFUZZMEMHDR pMemHdr = (PRTFUZZMEMHDR)RTMemAllocZ(cb + sizeof(RTFUZZMEMHDR));
+ if (RT_LIKELY(pMemHdr))
+ {
+ pMemHdr->cb = cb;
+ size_t cbIgn = ASMAtomicAddZ(&pThis->cbMemTotal, cb + sizeof(RTFUZZMEMHDR)); RT_NOREF(cbIgn);
+ return pMemHdr + 1;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Frees the given memory.
+ *
+ * @param pThis The fuzzer context instance.
+ * @param pv Pointer to the memory area to free.
+ */
+static void rtFuzzCtxMemoryFree(PRTFUZZCTXINT pThis, void *pv)
+{
+ AssertReturnVoid(pv != NULL);
+ PRTFUZZMEMHDR pMemHdr = ((PRTFUZZMEMHDR)pv) - 1;
+
+ size_t cbIgn = ASMAtomicSubZ(&pThis->cbMemTotal, pMemHdr->cb + sizeof(RTFUZZMEMHDR)); RT_NOREF(cbIgn);
+ RTMemFree(pMemHdr);
+}
+
+
+/**
+ * Frees the cached inputs until the given amount is free.
+ *
+ * @returns Whether the amount of memory is free.
+ * @param pThis The fuzzer context instance.
+ * @param cb How many bytes to reclaim
+ */
+static bool rtFuzzCtxMutationAllocReclaim(PRTFUZZCTXINT pThis, size_t cb)
+{
+ while ( !RTListIsEmpty(&pThis->LstMutationsAlloc)
+ && pThis->cbMutationsAlloc + cb > pThis->cbMutationsAllocMax)
+ {
+ PRTFUZZMUTATION pMutation = RTListGetLast(&pThis->LstMutationsAlloc, RTFUZZMUTATION, NdAlloc);
+ AssertPtr(pMutation);
+ AssertPtr(pMutation->pvInput);
+
+ rtFuzzCtxMemoryFree(pThis, pMutation->pvInput);
+ pThis->cbMutationsAlloc -= pMutation->cbAlloc;
+ pMutation->pvInput = NULL;
+ pMutation->cbAlloc = 0;
+ pMutation->fCached = false;
+ RTListNodeRemove(&pMutation->NdAlloc);
+ }
+
+ return pThis->cbMutationsAlloc + cb <= pThis->cbMutationsAllocMax;
+}
+
+
+/**
+ * Updates the cache status of the given mutation.
+ *
+ * @param pThis The fuzzer context instance.
+ * @param pMutation The mutation to update.
+ */
+static void rtFuzzCtxMutationMaybeEnterCache(PRTFUZZCTXINT pThis, PRTFUZZMUTATION pMutation)
+{
+ RTCritSectEnter(&pThis->CritSectAlloc);
+
+ /* Initial corpus mutations are not freed. */
+ if ( pMutation->pvInput
+ && pMutation->pMutator != &g_MutatorCorpus)
+ {
+ Assert(!pMutation->fCached);
+
+ if (rtFuzzCtxMutationAllocReclaim(pThis, pMutation->cbAlloc))
+ {
+ RTListPrepend(&pThis->LstMutationsAlloc, &pMutation->NdAlloc);
+ pThis->cbMutationsAlloc += pMutation->cbAlloc;
+ pMutation->fCached = true;
+ }
+ else
+ {
+ rtFuzzCtxMemoryFree(pThis, pMutation->pvInput);
+ pMutation->pvInput = NULL;
+ pMutation->cbAlloc = 0;
+ pMutation->fCached = false;
+ }
+ }
+ RTCritSectLeave(&pThis->CritSectAlloc);
+}
+
+
+/**
+ * Removes a cached mutation from the cache.
+ *
+ * @param pThis The fuzzer context instance.
+ * @param pMutation The mutation to remove.
+ */
+static void rtFuzzCtxMutationCacheRemove(PRTFUZZCTXINT pThis, PRTFUZZMUTATION pMutation)
+{
+ RTCritSectEnter(&pThis->CritSectAlloc);
+ if (pMutation->fCached)
+ {
+ RTListNodeRemove(&pMutation->NdAlloc);
+ pThis->cbMutationsAlloc -= pMutation->cbAlloc;
+ pMutation->fCached = false;
+ }
+ RTCritSectLeave(&pThis->CritSectAlloc);
+}
+
+
+/**
+ * Destroys the given mutation.
+ *
+ * @param pMutation The mutation to destroy.
+ */
+static void rtFuzzMutationDestroy(PRTFUZZMUTATION pMutation)
+{
+ if (pMutation->pvInput)
+ {
+ rtFuzzCtxMemoryFree(pMutation->pFuzzer, pMutation->pvInput);
+ if (pMutation->fCached)
+ {
+ RTCritSectEnter(&pMutation->pFuzzer->CritSectAlloc);
+ RTListNodeRemove(&pMutation->NdAlloc);
+ pMutation->pFuzzer->cbMutationsAlloc -= pMutation->cbAlloc;
+ RTCritSectLeave(&pMutation->pFuzzer->CritSectAlloc);
+ }
+ pMutation->pvInput = NULL;
+ pMutation->cbAlloc = 0;
+ pMutation->fCached = false;
+ }
+ rtFuzzCtxMemoryFree(pMutation->pFuzzer, pMutation);
+}
+
+
+/**
+ * Retains an external reference to the given mutation.
+ *
+ * @returns New reference count on success.
+ * @param pMutation The mutation to retain.
+ */
+static uint32_t rtFuzzMutationRetain(PRTFUZZMUTATION pMutation)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pMutation->cRefs);
+ AssertMsg( ( cRefs > 1
+ || pMutation->fInTree)
+ && cRefs < _1M, ("%#x %p\n", cRefs, pMutation));
+
+ if (cRefs == 1)
+ rtFuzzCtxMutationCacheRemove(pMutation->pFuzzer, pMutation);
+ return cRefs;
+}
+
+
+/**
+ * Releases an external reference from the given mutation.
+ *
+ * @returns New reference count on success.
+ * @param pMutation The mutation to retain.
+ */
+static uint32_t rtFuzzMutationRelease(PRTFUZZMUTATION pMutation)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pMutation->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pMutation));
+
+ if (cRefs == 0)
+ {
+ if (!pMutation->fInTree)
+ rtFuzzMutationDestroy(pMutation);
+ else
+ rtFuzzCtxMutationMaybeEnterCache(pMutation->pFuzzer, pMutation);
+ }
+
+ return cRefs;
+}
+
+
+/**
+ * Adds the given mutation to the corpus of the given fuzzer context.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzer context instance.
+ * @param pMutation The mutation to add.
+ */
+static int rtFuzzCtxMutationAdd(PRTFUZZCTXINT pThis, PRTFUZZMUTATION pMutation)
+{
+ int rc = VINF_SUCCESS;
+
+ pMutation->Core.Key = ASMAtomicIncU64(&pThis->cMutations);
+ rc = RTSemRWRequestWrite(pThis->hSemRwMutations, RT_INDEFINITE_WAIT);
+ AssertRC(rc); RT_NOREF(rc);
+ bool fIns = RTAvlU64Insert(&pThis->TreeMutations, &pMutation->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ rc = RTSemRWReleaseWrite(pThis->hSemRwMutations);
+ AssertRC(rc); RT_NOREF(rc);
+
+ pMutation->fInTree = true;
+ return rc;
+}
+
+
+/**
+ * Locates the mutation with the given key.
+ *
+ * @returns Pointer to the mutation if found or NULL otherwise.
+ * @param pThis The fuzzer context instance.
+ * @param uKey The key to locate.
+ */
+static PRTFUZZMUTATION rtFuzzCtxMutationLocate(PRTFUZZCTXINT pThis, uint64_t uKey)
+{
+ int rc = RTSemRWRequestRead(pThis->hSemRwMutations, RT_INDEFINITE_WAIT);
+ AssertRC(rc); RT_NOREF(rc);
+
+ /*
+ * Using best fit getter here as there might be a racing mutation insertion and the mutation counter has increased
+ * already but the mutation is not yet in the tree.
+ */
+ PRTFUZZMUTATION pMutation = (PRTFUZZMUTATION)RTAvlU64GetBestFit(&pThis->TreeMutations, uKey, false /*fAbove*/);
+ if (RT_LIKELY(pMutation))
+ rtFuzzMutationRetain(pMutation);
+
+ rc = RTSemRWReleaseRead(pThis->hSemRwMutations);
+ AssertRC(rc); RT_NOREF(rc);
+
+ return pMutation;
+}
+
+
+/**
+ * Returns a random mutation from the corpus of the given fuzzer context.
+ *
+ * @returns Pointer to a randomly picked mutation (reference count is increased).
+ * @param pThis The fuzzer context instance.
+ */
+static PRTFUZZMUTATION rtFuzzCtxMutationPickRnd(PRTFUZZCTXINT pThis)
+{
+ uint64_t idxMutation = RTRandAdvU64Ex(pThis->hRand, 1, ASMAtomicReadU64(&pThis->cMutations));
+ return rtFuzzCtxMutationLocate(pThis, idxMutation);
+}
+
+
+/**
+ * Creates a new mutation capable of holding the additional number of bytes - extended version.
+ *
+ * @returns Pointer to the newly created mutation or NULL if out of memory.
+ * @param pThis The fuzzer context instance.
+ * @param offMutation The starting offset for the mutation.
+ * @param pMutationParent The parent mutation, can be NULL.
+ * @param offMuStartNew Offset where descendants of the created mutation can start to mutate.
+ * @param cbMutNew Range in bytes where descendants of the created mutation can mutate.c
+ * @param cbAdditional Additional number of bytes to allocate after the core structure.
+ * @param ppvMutation Where to store the pointer to the mutation dependent data on success.
+ */
+static PRTFUZZMUTATION rtFuzzMutationCreateEx(PRTFUZZCTXINT pThis, uint64_t offMutation, PRTFUZZMUTATION pMutationParent,
+ uint64_t offMutStartNew, uint64_t cbMutNew, size_t cbAdditional, void **ppvMutation)
+{
+ PRTFUZZMUTATION pMutation = (PRTFUZZMUTATION)rtFuzzCtxMemoryAlloc(pThis, sizeof(RTFUZZMUTATION) + cbAdditional);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->u32Magic = 0; /** @todo */
+ pMutation->pFuzzer = pThis;
+ pMutation->cRefs = 1;
+ pMutation->iLvl = 0;
+ pMutation->offMutation = offMutation;
+ pMutation->pMutationParent = pMutationParent;
+ pMutation->offMutStartNew = offMutStartNew;
+ pMutation->cbMutNew = cbMutNew;
+ pMutation->cbMutation = cbAdditional;
+ pMutation->fInTree = false;
+ pMutation->fCached = false;
+ pMutation->pvInput = NULL;
+ pMutation->cbInput = 0;
+ pMutation->cbAlloc = 0;
+
+ if (pMutationParent)
+ pMutation->iLvl = pMutationParent->iLvl + 1;
+ if (ppvMutation)
+ *ppvMutation = &pMutation->abMutation[0];
+ }
+
+ return pMutation;
+}
+
+
+/**
+ * Creates a new mutation capable of holding the additional number of bytes.
+ *
+ * @returns Pointer to the newly created mutation or NULL if out of memory.
+ * @param pThis The fuzzer context instance.
+ * @param offMutation The starting offset for the mutation.
+ * @param pMutationParent The parent mutation, can be NULL.
+ * @param cbAdditional Additional number of bytes to allocate after the core structure.
+ * @param ppvMutation Where to store the pointer to the mutation dependent data on success.
+ */
+DECLINLINE(PRTFUZZMUTATION) rtFuzzMutationCreate(PRTFUZZCTXINT pThis, uint64_t offMutation, PRTFUZZMUTATION pMutationParent,
+ size_t cbAdditional, void **ppvMutation)
+{
+ uint64_t offMutNew = pMutationParent ? pMutationParent->offMutStartNew : pThis->offMutStart;
+ uint64_t cbMutNew = pMutationParent ? pMutationParent->cbMutNew : pThis->cbMutRange;
+
+ return rtFuzzMutationCreateEx(pThis, offMutation, pMutationParent, offMutNew, cbMutNew, cbAdditional, ppvMutation);
+}
+
+
+/**
+ * Destroys the given fuzzer context freeing all allocated resources.
+ *
+ * @param pThis The fuzzer context instance.
+ */
+static void rtFuzzCtxDestroy(PRTFUZZCTXINT pThis)
+{
+ RT_NOREF(pThis);
+}
+
+
+/**
+ * Creates the final input data applying all accumulated mutations.
+ *
+ * @returns IPRT status code.
+ * @param pMutation The mutation to finalize.
+ */
+static int rtFuzzMutationDataFinalize(PRTFUZZMUTATION pMutation)
+{
+ if (pMutation->pvInput)
+ return VINF_SUCCESS;
+
+ /* Traverse the mutations top to bottom and insert into the array. */
+ int rc = VINF_SUCCESS;
+ uint32_t idx = pMutation->iLvl + 1;
+ PRTFUZZMUTATION *papMutations = (PRTFUZZMUTATION *)RTMemTmpAlloc(idx * sizeof(PCRTFUZZMUTATION));
+ if (RT_LIKELY(papMutations))
+ {
+ PRTFUZZMUTATION pMutationCur = pMutation;
+ size_t cbAlloc = 0;
+
+ /*
+ * As soon as a mutation with allocated input data is encountered the insertion is
+ * stopped as it contains all necessary mutated inputs we can start from.
+ */
+ while (idx > 0)
+ {
+ rtFuzzMutationRetain(pMutationCur);
+ papMutations[idx - 1] = pMutationCur;
+ cbAlloc = RT_MAX(cbAlloc, pMutationCur->cbInput);
+ if (pMutationCur->pvInput)
+ {
+ idx--;
+ break;
+ }
+ pMutationCur = pMutationCur->pMutationParent;
+ idx--;
+ }
+
+ pMutation->cbAlloc = cbAlloc;
+ uint8_t *pbBuf = (uint8_t *)rtFuzzCtxMemoryAlloc(pMutation->pFuzzer, cbAlloc);
+ if (RT_LIKELY(pbBuf))
+ {
+ pMutation->pvInput = pbBuf;
+
+ /* Copy the initial input data. */
+ size_t cbInputNow = papMutations[idx]->cbInput;
+ memcpy(pbBuf, papMutations[idx]->pvInput, cbInputNow);
+ rtFuzzMutationRelease(papMutations[idx]);
+
+ for (uint32_t i = idx + 1; i < pMutation->iLvl + 1; i++)
+ {
+ PRTFUZZMUTATION pCur = papMutations[i];
+ pCur->pMutator->pfnExec(pCur->pFuzzer, pCur, (void *)&pCur->abMutation[0],
+ pbBuf + pCur->offMutation,
+ cbInputNow - pCur->offMutation);
+
+ cbInputNow = pCur->cbInput;
+ rtFuzzMutationRelease(pCur);
+ }
+
+ Assert(cbInputNow == pMutation->cbInput);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTMemTmpFree(papMutations);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Default mutator export callback (just writing the raw data).
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorExportDefault(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ PFNRTFUZZCTXEXPORT pfnExport, void *pvUser)
+{
+ return pfnExport(pThis, pvMutation, pMutation->cbMutation, pvUser);
+}
+
+
+/**
+ * Default mutator import callback (just reading the raw data).
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorImportDefault(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation,
+ PFNRTFUZZCTXIMPORT pfnImport, void *pvUser)
+{
+ return pfnImport(pThis, pvMutation, pMutation->cbMutation, NULL, pvUser);
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorCorpusExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(pThis, cbBuf, pvMutation);
+ memcpy(pbBuf, pvMutation, pMutation->cbInput);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Mutator callback - flips a single bit in the input.
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlipPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pidxBitFlip = 0;
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, sizeof(*pidxBitFlip), (void **)&pidxBitFlip);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->cbInput = pMutationParent->cbInput; /* Bit flips don't change the input size. */
+ *pidxBitFlip = (uint8_t)RTRandAdvU32Ex(pThis->hRand, 0, sizeof(uint8_t) * 8 - 1);
+ *ppMutation = pMutation;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorBitFlipExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(pThis, cbBuf, pMutation);
+ uint8_t idxBitFlip = *(uint8_t *)pvMutation;
+ ASMBitToggle(pbBuf, idxBitFlip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Mutator callback - replaces a single byte in the input.
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplacePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pbReplace = 0;
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, sizeof(*pbReplace), (void **)&pbReplace);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->cbInput = pMutationParent->cbInput; /* Byte replacements don't change the input size. */
+ RTRandAdvBytes(pThis->hRand, pbReplace, 1); /** @todo Filter out same values. */
+ *ppMutation = pMutation;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteReplaceExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(pThis, cbBuf, pMutation);
+ uint8_t bReplace = *(uint8_t *)pvMutation;
+ *pbBuf = bReplace;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Mutator callback - inserts a single byte into the input.
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteInsertPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pbInsert = 0;
+ if (pMutationParent->cbInput < pThis->cbInputMax)
+ {
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, 1 /*cbAdditional*/, (void **)&pbInsert);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->cbInput = pMutationParent->cbInput + 1;
+ RTRandAdvBytes(pThis->hRand, pbInsert, 1);
+ *ppMutation = pMutation;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteInsertExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(pThis, pMutation, pvMutation);
+
+ /* Just move the residual data one byte to the back. */
+ memmove(pbBuf + 1, pbBuf, cbBuf);
+ *pbBuf = *(uint8_t *)pvMutation;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Mutator callback - inserts a byte sequence into the input.
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsertAppendPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation)
+{
+ int rc = VINF_SUCCESS;
+ if (pMutationParent->cbInput < pThis->cbInputMax)
+ {
+ size_t cbInputMutated = (size_t)RTRandAdvU64Ex(pThis->hRand, pMutationParent->cbInput + 1, pThis->cbInputMax);
+ size_t cbInsert = cbInputMutated - pMutationParent->cbInput;
+ uint8_t *pbAdd = NULL;
+
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, cbInsert, (void **)&pbAdd);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->cbInput = cbInputMutated;
+ RTRandAdvBytes(pThis->hRand, pbAdd, cbInsert);
+ *ppMutation = pMutation;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceInsertAppendExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(pThis);
+ size_t cbInsert = pMutation->cbInput - pMutation->pMutationParent->cbInput;
+
+ /* Move any remaining data to the end. */
+ if (cbBuf)
+ memmove(pbBuf + cbInsert, pbBuf, cbBuf);
+
+ memcpy(pbBuf, pvMutation, cbInsert);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Mutator callback - deletes a single byte in the input.
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteDeletePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation)
+{
+ int rc = VINF_SUCCESS;
+ if (pMutationParent->cbInput - offStart >= 1)
+ {
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, 0 /*cbAdditional*/, NULL);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->cbInput = pMutationParent->cbInput - 1;
+ *ppMutation = pMutation;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteDeleteExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(pThis, pMutation, pvMutation);
+
+ /* Just move the residual data to the front. */
+ memmove(pbBuf, pbBuf + 1, cbBuf - 1);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Mutator callback - deletes a byte sequence in the input.
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDeletePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation)
+{
+ int rc = VINF_SUCCESS;
+ if ( pMutationParent->cbInput > offStart
+ && pMutationParent->cbInput > 1)
+ {
+ size_t cbInputMutated = (size_t)RTRandAdvU64Ex(pThis->hRand, offStart, pMutationParent->cbInput - 1);
+
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, 0 /*cbAdditional*/, NULL);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->cbInput = cbInputMutated;
+ *ppMutation = pMutation;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorByteSequenceDeleteExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(pThis, pvMutation);
+ Assert(pMutation->pMutationParent->cbInput > pMutation->cbInput);
+ size_t cbDel = pMutation->pMutationParent->cbInput - pMutation->cbInput;
+
+ /* Just move the residual data to the front. */
+ memmove(pbBuf, pbBuf + cbDel, cbBuf - cbDel);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Mutator callback - replaces a possible integer with something interesting.
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorIntegerReplacePrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation)
+{
+ int rc = VINF_SUCCESS;
+ PRTFUZZMUTATORINTEGER pMutInt = NULL;
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, sizeof(*pMutInt), (void **)&pMutInt);
+ if (RT_LIKELY(pMutation))
+ {
+ size_t cbLeft = pMutationParent->cbInput - offStart;
+ uint32_t uClassMax = 0;
+
+ switch (cbLeft)
+ {
+ case 1:
+ uClassMax = 1;
+ break;
+ case 2:
+ case 3:
+ uClassMax = 3;
+ break;
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ uClassMax = 5;
+ break;
+ default:
+ uClassMax = 7;
+ break;
+ }
+
+ pMutInt->uIntClass = (uint8_t)RTRandAdvU32Ex(pThis->hRand, 0, uClassMax);
+ pMutInt->fByteSwap = RT_BOOL(RTRandAdvU32Ex(pThis->hRand, 0, 1));
+
+ switch (pMutInt->uIntClass)
+ {
+ case 0:
+ pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_ai8Interesting) - 1);
+ break;
+ case 1:
+ pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_au8Interesting) - 1);
+ break;
+ case 2:
+ pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_ai16Interesting) - 1);
+ break;
+ case 3:
+ pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_au16Interesting) - 1);
+ break;
+ case 4:
+ pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_ai32Interesting) - 1);
+ break;
+ case 5:
+ pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_au32Interesting) - 1);
+ break;
+ case 6:
+ pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_ai64Interesting) - 1);
+ break;
+ case 7:
+ pMutInt->idxInt = (uint16_t)RTRandAdvU32Ex(pThis->hRand, 0, RT_ELEMENTS(s_au64Interesting) - 1);
+ break;
+ default:
+ AssertReleaseFailed();
+ }
+
+ pMutation->cbInput = pMutationParent->cbInput;
+ *ppMutation = pMutation;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorIntegerReplaceExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(pThis, pMutation, cbBuf);
+ union
+ {
+ int8_t i8;
+ uint8_t u8;
+ int16_t i16;
+ uint16_t u16;
+ int32_t i32;
+ uint32_t u32;
+ int64_t i64;
+ uint64_t u64;
+ } Int;
+ PCRTFUZZMUTATORINTEGER pMutInt = (PCRTFUZZMUTATORINTEGER)pvMutation;
+ size_t cb = 0;
+
+ switch (pMutInt->uIntClass)
+ {
+ case 0:
+ Int.i8 = s_ai8Interesting[pMutInt->idxInt];
+ cb = 1;
+ break;
+ case 1:
+ Int.u8 = s_au8Interesting[pMutInt->idxInt];
+ cb = 1;
+ break;
+ case 2:
+ Int.i16 = s_ai16Interesting[pMutInt->idxInt];
+ cb = 2;
+ if (pMutInt->fByteSwap)
+ Int.u16 = RT_BSWAP_U16(Int.u16);
+ break;
+ case 3:
+ Int.u16 = s_au16Interesting[pMutInt->idxInt];
+ cb = 2;
+ if (pMutInt->fByteSwap)
+ Int.u16 = RT_BSWAP_U16(Int.u16);
+ break;
+ case 4:
+ Int.i32 = s_ai32Interesting[pMutInt->idxInt];
+ cb = 4;
+ if (pMutInt->fByteSwap)
+ Int.u32 = RT_BSWAP_U32(Int.u32);
+ break;
+ case 5:
+ Int.u32 = s_au32Interesting[pMutInt->idxInt];
+ cb = 4;
+ if (pMutInt->fByteSwap)
+ Int.u32 = RT_BSWAP_U32(Int.u32);
+ break;
+ case 6:
+ Int.i64 = s_ai64Interesting[pMutInt->idxInt];
+ cb = 8;
+ if (pMutInt->fByteSwap)
+ Int.u64 = RT_BSWAP_U64(Int.u64);
+ break;
+ case 7:
+ Int.u64 = s_au64Interesting[pMutInt->idxInt];
+ cb = 8;
+ if (pMutInt->fByteSwap)
+ Int.u64 = RT_BSWAP_U64(Int.u64);
+ break;
+ default:
+ AssertReleaseFailed();
+ }
+
+ memcpy(pbBuf, &Int, cb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Mutator callback - crosses over two mutations at the given point.
+ */
+static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverPrep(PRTFUZZCTXINT pThis, uint64_t offStart, PRTFUZZMUTATION pMutationParent,
+ PPRTFUZZMUTATION ppMutation)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->cMutations > 1)
+ {
+ uint64_t *pidxMutCrossover = NULL;
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreate(pThis, offStart, pMutationParent, sizeof(*pidxMutCrossover), (void **)&pidxMutCrossover);
+ if (RT_LIKELY(pMutation))
+ {
+ uint32_t cTries = 10;
+ PRTFUZZMUTATION pMutCrossover = NULL;
+ /*
+ * Pick a random mutation to crossover with (making sure it is not the current one
+ * or the crossover point is beyond the end of input).
+ */
+ do
+ {
+ if (pMutCrossover)
+ rtFuzzMutationRelease(pMutCrossover);
+ pMutCrossover = rtFuzzCtxMutationPickRnd(pThis);
+ cTries--;
+ } while ( ( pMutCrossover == pMutationParent
+ || offStart >= pMutCrossover->cbInput)
+ && cTries > 0);
+
+ if (cTries)
+ {
+ pMutation->cbInput = pMutCrossover->cbInput;
+ *pidxMutCrossover = pMutCrossover->Core.Key;
+ *ppMutation = pMutation;
+ }
+ else
+ rtFuzzMutationDestroy(pMutation);
+
+ rtFuzzMutationRelease(pMutCrossover);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverExec(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ uint8_t *pbBuf, size_t cbBuf)
+{
+ RT_NOREF(cbBuf);
+ uint64_t idxMutCrossover = *(uint64_t *)pvMutation;
+
+ PRTFUZZMUTATION pMutCrossover = rtFuzzCtxMutationLocate(pThis, idxMutCrossover);
+ int rc = rtFuzzMutationDataFinalize(pMutCrossover);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(pbBuf, (uint8_t *)pMutCrossover->pvInput + pMutation->offMutation,
+ pMutCrossover->cbInput - pMutation->offMutation);
+ rtFuzzMutationRelease(pMutCrossover);
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverExport(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, const void *pvMutation,
+ PFNRTFUZZCTXEXPORT pfnExport, void *pvUser)
+{
+ RT_NOREF(pMutation);
+
+ uint64_t idxMutCrossover = *(uint64_t *)pvMutation;
+ idxMutCrossover = RT_H2LE_U64(idxMutCrossover);
+ return pfnExport(pThis, &idxMutCrossover, sizeof(idxMutCrossover), pvUser);
+}
+
+
+static DECLCALLBACK(int) rtFuzzCtxMutatorCrossoverImport(PRTFUZZCTXINT pThis, PCRTFUZZMUTATION pMutation, void *pvMutation,
+ PFNRTFUZZCTXIMPORT pfnImport, void *pvUser)
+{
+ RT_NOREF(pMutation);
+
+ uint64_t uKey = 0;
+ int rc = pfnImport(pThis, &uKey, sizeof(uKey), NULL, pvUser);
+ if (RT_SUCCESS(rc))
+ {
+ uKey = RT_LE2H_U64(uKey);
+ *(uint64_t *)pvMutation = uKey;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Creates an empty fuzzing context.
+ *
+ * @returns IPRT status code.
+ * @param ppThis Where to store the pointer to the internal fuzzing context instance on success.
+ * @param enmType Fuzzing context type.
+ */
+static int rtFuzzCtxCreateEmpty(PRTFUZZCTXINT *ppThis, RTFUZZCTXTYPE enmType)
+{
+ int rc;
+ PRTFUZZCTXINT pThis = (PRTFUZZCTXINT)RTMemAllocZ(sizeof(*pThis));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->u32Magic = RTFUZZCTX_MAGIC;
+ pThis->cRefs = 1;
+ pThis->enmType = enmType;
+ pThis->TreeMutations = NULL;
+ pThis->cbInputMax = UINT32_MAX;
+ pThis->cMutations = 0;
+ pThis->fFlagsBehavioral = 0;
+ pThis->cbMutationsAllocMax = _1G;
+ pThis->cbMemTotal = 0;
+ pThis->offMutStart = 0;
+ pThis->cbMutRange = UINT64_MAX;
+ RTListInit(&pThis->LstMutationsAlloc);
+
+ /* Copy the default mutator descriptors over. */
+ pThis->paMutators = (PRTFUZZMUTATOR)RTMemAllocZ(RT_ELEMENTS(g_aMutators) * sizeof(RTFUZZMUTATOR));
+ if (RT_LIKELY(pThis->paMutators))
+ {
+ pThis->cMutators = RT_ELEMENTS(g_aMutators);
+ memcpy(&pThis->paMutators[0], &g_aMutators[0], RT_ELEMENTS(g_aMutators) * sizeof(RTFUZZMUTATOR));
+
+ rc = RTSemRWCreate(&pThis->hSemRwMutations);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pThis->CritSectAlloc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTRandAdvCreateParkMiller(&pThis->hRand);
+ if (RT_SUCCESS(rc))
+ {
+ RTRandAdvSeed(pThis->hRand, RTTimeSystemNanoTS());
+ *ppThis = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTCritSectDelete(&pThis->CritSectAlloc);
+ }
+
+ RTSemRWDestroy(pThis->hSemRwMutations);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * Destroys the given fuzzing input.
+ *
+ * @param pThis The fuzzing input to destroy.
+ */
+static void rtFuzzInputDestroy(PRTFUZZINPUTINT pThis)
+{
+ PRTFUZZCTXINT pFuzzer = pThis->pFuzzer;
+
+ rtFuzzMutationRelease(pThis->pMutationTop);
+ rtFuzzCtxMemoryFree(pFuzzer, pThis);
+ RTFuzzCtxRelease(pFuzzer);
+}
+
+
+RTDECL(int) RTFuzzCtxCreate(PRTFUZZCTX phFuzzCtx, RTFUZZCTXTYPE enmType)
+{
+ AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER);
+
+ return rtFuzzCtxCreateEmpty(phFuzzCtx, enmType);
+}
+
+
+RTDECL(int) RTFuzzCtxCreateFromState(PRTFUZZCTX phFuzzCtx, PFNRTFUZZCTXIMPORT pfnImport, void *pvUser)
+{
+ AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnImport, VERR_INVALID_POINTER);
+
+#if 0
+ int rc = VINF_SUCCESS;
+ if (cbState >= sizeof(RTFUZZCTXSTATE))
+ {
+ RTFUZZCTXSTATE StateImport;
+
+ memcpy(&StateImport, pvState, sizeof(RTFUZZCTXSTATE));
+ if ( RT_LE2H_U32(StateImport.u32Magic) == RTFUZZCTX_MAGIC
+ && RT_LE2H_U32(StateImport.cbPrng) <= cbState - sizeof(RTFUZZCTXSTATE))
+ {
+ PRTFUZZCTXINT pThis = rtFuzzCtxCreateEmpty();
+ if (RT_LIKELY(pThis))
+ {
+ pThis->cbInputMax = (size_t)RT_LE2H_U64(StateImport.cbInputMax);
+ pThis->fFlagsBehavioral = RT_LE2H_U32(StateImport.fFlagsBehavioral);
+
+ uint8_t *pbState = (uint8_t *)pvState;
+ uint32_t cInputs = RT_LE2H_U32(StateImport.cInputs);
+ rc = RTRandAdvRestoreState(pThis->hRand, (const char *)&pbState[sizeof(RTFUZZCTXSTATE)]);
+ if (RT_SUCCESS(rc))
+ {
+ /* Go through the inputs and add them. */
+ pbState += sizeof(RTFUZZCTXSTATE) + RT_LE2H_U32(StateImport.cbPrng);
+ cbState -= sizeof(RTFUZZCTXSTATE) + RT_LE2H_U32(StateImport.cbPrng);
+
+ uint32_t idx = 0;
+ while ( idx < cInputs
+ && RT_SUCCESS(rc))
+ {
+ size_t cbInput = 0;
+ if (cbState >= sizeof(uint32_t))
+ {
+ memcpy(&cbInput, pbState, sizeof(uint32_t));
+ cbInput = RT_LE2H_U32(cbInput);
+ pbState += sizeof(uint32_t);
+ }
+
+ if ( cbInput
+ && cbInput <= cbState)
+ {
+ PRTFUZZINPUTINT pInput = rtFuzzCtxInputCreate(pThis, cbInput);
+ if (RT_LIKELY(pInput))
+ {
+ memcpy(&pInput->abInput[0], pbState, cbInput);
+ RTMd5(&pInput->abInput[0], pInput->cbInput, &pInput->abMd5Hash[0]);
+ rc = rtFuzzCtxInputAdd(pThis, pInput);
+ if (RT_FAILURE(rc))
+ RTMemFree(pInput);
+ pbState += cbInput;
+ }
+ }
+ else
+ rc = VERR_INVALID_STATE;
+
+ idx++;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *phFuzzCtx = pThis;
+ return VINF_SUCCESS;
+ }
+ }
+
+ rtFuzzCtxDestroy(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_INVALID_MAGIC;
+ }
+ else
+ rc = VERR_INVALID_MAGIC;
+
+ return rc;
+#else
+ RT_NOREF(pvUser);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+RTDECL(int) RTFuzzCtxCreateFromStateMem(PRTFUZZCTX phFuzzCtx, const void *pvState, size_t cbState)
+{
+ AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvState, VERR_INVALID_POINTER);
+ AssertPtrReturn(cbState, VERR_INVALID_POINTER);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+RTDECL(int) RTFuzzCtxCreateFromStateFile(PRTFUZZCTX phFuzzCtx, const char *pszFilename)
+{
+ AssertPtrReturn(phFuzzCtx, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ void *pv = NULL;
+ size_t cb = 0;
+ int rc = RTFileReadAll(pszFilename, &pv, &cb);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzCtxCreateFromStateMem(phFuzzCtx, pv, cb);
+ RTFileReadAllFree(pv, cb);
+ }
+
+ return rc;
+}
+
+
+RTDECL(uint32_t) RTFuzzCtxRetain(RTFUZZCTX hFuzzCtx)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTFuzzCtxRelease(RTFUZZCTX hFuzzCtx)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ if (pThis == NIL_RTFUZZCTX)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0)
+ rtFuzzCtxDestroy(pThis);
+ return cRefs;
+}
+
+
+RTDECL(int) RTFuzzCtxQueryStats(RTFUZZCTX hFuzzCtx, PRTFUZZCTXSTATS pStats)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pStats, VERR_INVALID_POINTER);
+
+ pStats->cbMemory = ASMAtomicReadZ(&pThis->cbMemTotal);
+ pStats->cMutations = ASMAtomicReadU64(&pThis->cMutations);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Fuzzing context export callback for a single mutation.
+ */
+static DECLCALLBACK(int) rtFuzzCtxStateExportMutations(PAVLU64NODECORE pCore, void *pvParam)
+{
+ PRTFUZZMUTATION pMutation = (PRTFUZZMUTATION)pCore;
+ PCRTFUZZMUTATOR pMutator = pMutation->pMutator;
+ PCRTFUZZEXPORTARGS pArgs = (PCRTFUZZEXPORTARGS)pvParam;
+ RTFUZZMUTATIONSTATE MutationState;
+
+ MutationState.u64Id = RT_H2LE_U64(pMutation->Core.Key);
+ if (pMutation->pMutationParent)
+ MutationState.u64IdParent = RT_H2LE_U64(pMutation->pMutationParent->Core.Key);
+ else
+ MutationState.u64IdParent = 0;
+ MutationState.u64OffMutation = RT_H2LE_U64(pMutation->offMutation);
+ MutationState.cbInput = RT_H2LE_U64((uint64_t)pMutation->cbInput);
+ MutationState.cbMutation = RT_H2LE_U64((uint64_t)pMutation->cbMutation);
+ MutationState.u32IdMutator = RT_H2LE_U32(pMutator->uMutator);
+ MutationState.iLvl = RT_H2LE_U32(pMutation->iLvl);
+ MutationState.u32Magic = RT_H2LE_U32(pMutation->u32Magic);
+
+ int rc = pArgs->pfnExport(pMutation->pFuzzer, &MutationState, sizeof(MutationState), pArgs->pvUser);
+ if ( RT_SUCCESS(rc)
+ && pMutator->pfnExport)
+ rc = pMutator->pfnExport(pMutation->pFuzzer, pMutation, &pMutation->abMutation[0], pArgs->pfnExport, pArgs->pvUser);
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCtxStateExport(RTFUZZCTX hFuzzCtx, PFNRTFUZZCTXEXPORT pfnExport, void *pvUser)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnExport, VERR_INVALID_POINTER);
+
+ char aszPrngExport[_4K]; /* Should be plenty of room here. */
+ size_t cbPrng = sizeof(aszPrngExport);
+ int rc = RTRandAdvSaveState(pThis->hRand, &aszPrngExport[0], &cbPrng);
+ if (RT_SUCCESS(rc))
+ {
+ RTFUZZCTXSTATE StateExport;
+
+ StateExport.u32Magic = RT_H2LE_U32(RTFUZZCTX_MAGIC);
+ switch (pThis->enmType)
+ {
+ case RTFUZZCTXTYPE_BLOB:
+ StateExport.uCtxType = RT_H2LE_U32(RTFUZZCTX_STATE_TYPE_BLOB);
+ break;
+ case RTFUZZCTXTYPE_STREAM:
+ StateExport.uCtxType = RT_H2LE_U32(RTFUZZCTX_STATE_TYPE_STREAM);
+ break;
+ default:
+ AssertFailed();
+ break;
+ }
+ StateExport.cbPrng = RT_H2LE_U32((uint32_t)cbPrng);
+ StateExport.cMutations = RT_H2LE_U32(pThis->cMutations);
+ StateExport.cMutators = RT_H2LE_U32(pThis->cMutators);
+ StateExport.fFlagsBehavioral = RT_H2LE_U32(pThis->fFlagsBehavioral);
+ StateExport.cbInputMax = RT_H2LE_U64(pThis->cbInputMax);
+
+ /* Write the context state and PRNG state first. */
+ rc = pfnExport(pThis, &StateExport, sizeof(StateExport), pvUser);
+ if (RT_SUCCESS(rc))
+ rc = pfnExport(pThis, &aszPrngExport[0], cbPrng, pvUser);
+ if (RT_SUCCESS(rc))
+ {
+ /* Write the mutator descriptors next. */
+ for (uint32_t i = 0; i < pThis->cMutators && RT_SUCCESS(rc); i++)
+ {
+ PRTFUZZMUTATOR pMutator = &pThis->paMutators[i];
+ uint32_t cchId = (uint32_t)strlen(pMutator->pszId) + 1;
+ uint32_t cchIdW = RT_H2LE_U32(cchId);
+
+ rc = pfnExport(pThis, &cchIdW, sizeof(cchIdW), pvUser);
+ if (RT_SUCCESS(rc))
+ rc = pfnExport(pThis, &pMutator->pszId[0], cchId, pvUser);
+ }
+ }
+
+ /* Write the mutations last. */
+ if (RT_SUCCESS(rc))
+ {
+ RTFUZZEXPORTARGS Args;
+
+ Args.pfnExport = pfnExport;
+ Args.pvUser = pvUser;
+ rc = RTAvlU64DoWithAll(&pThis->TreeMutations, true /*fFromLeft*/, rtFuzzCtxStateExportMutations, &Args);
+ }
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCtxStateExportToMem(RTFUZZCTX hFuzzCtx, void **ppvState, size_t *pcbState)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppvState, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcbState, VERR_INVALID_POINTER);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * Export to file callback.
+ */
+static DECLCALLBACK(int) rtFuzzCtxStateExportFile(RTFUZZCTX hFuzzCtx, const void *pvBuf, size_t cbWrite, void *pvUser)
+{
+ RT_NOREF(hFuzzCtx);
+
+ RTFILE hFile = (RTFILE)pvUser;
+ return RTFileWrite(hFile, pvBuf, cbWrite, NULL);
+}
+
+
+RTDECL(int) RTFuzzCtxStateExportToFile(RTFUZZCTX hFuzzCtx, const char *pszFilename)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzCtxStateExport(hFuzzCtx, rtFuzzCtxStateExportFile, hFile);
+ RTFileClose(hFile);
+ if (RT_FAILURE(rc))
+ RTFileDelete(pszFilename);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCtxCorpusInputAdd(RTFUZZCTX hFuzzCtx, const void *pvInput, size_t cbInput)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvInput, VERR_INVALID_POINTER);
+ AssertReturn(cbInput, VERR_INVALID_POINTER);
+
+ return RTFuzzCtxCorpusInputAddEx(hFuzzCtx, pvInput, cbInput, pThis->offMutStart, pThis->cbMutRange);
+}
+
+
+RTDECL(int) RTFuzzCtxCorpusInputAddEx(RTFUZZCTX hFuzzCtx, const void *pvInput, size_t cbInput,
+ uint64_t offMutStart, uint64_t cbMutRange)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pvInput, VERR_INVALID_POINTER);
+ AssertReturn(cbInput, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ void *pvCorpus = NULL;
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreateEx(pThis, 0, NULL, offMutStart, cbMutRange,
+ cbInput, &pvCorpus);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->pMutator = &g_MutatorCorpus;
+ pMutation->cbInput = cbInput;
+ pMutation->pvInput = pvCorpus;
+ memcpy(pvCorpus, pvInput, cbInput);
+ rc = rtFuzzCtxMutationAdd(pThis, pMutation);
+ if (RT_FAILURE(rc))
+ rtFuzzMutationDestroy(pMutation);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCtxCorpusInputAddFromFile(RTFUZZCTX hFuzzCtx, const char *pszFilename)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ return RTFuzzCtxCorpusInputAddFromFileEx(hFuzzCtx, pszFilename, pThis->offMutStart, pThis->cbMutRange);
+}
+
+
+RTDECL(int) RTFuzzCtxCorpusInputAddFromFileEx(RTFUZZCTX hFuzzCtx, const char *pszFilename,
+ uint64_t offMutStart, uint64_t cbMutRange)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ void *pv = NULL;
+ size_t cb = 0;
+ int rc = RTFileReadAll(pszFilename, &pv, &cb);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzCtxCorpusInputAddEx(hFuzzCtx, pv, cb, offMutStart, cbMutRange);
+ RTFileReadAllFree(pv, cb);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCtxCorpusInputAddFromVfsFile(RTFUZZCTX hFuzzCtx, RTVFSFILE hVfsFile)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(hVfsFile != NIL_RTVFSFILE, VERR_INVALID_HANDLE);
+
+ return RTFuzzCtxCorpusInputAddFromVfsFileEx(hFuzzCtx, hVfsFile, pThis->offMutStart, pThis->cbMutRange);
+}
+
+
+RTDECL(int) RTFuzzCtxCorpusInputAddFromVfsFileEx(RTFUZZCTX hFuzzCtx, RTVFSFILE hVfsFile,
+ uint64_t offMutStart, uint64_t cbMutRange)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(hVfsFile != NIL_RTVFSFILE, VERR_INVALID_HANDLE);
+
+ uint64_t cbFile = 0;
+ void *pvCorpus = NULL;
+ int rc = RTVfsFileQuerySize(hVfsFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreateEx(pThis, 0, NULL, offMutStart, cbMutRange,
+ cbFile, &pvCorpus);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->pMutator = &g_MutatorCorpus;
+ pMutation->cbInput = cbFile;
+ pMutation->pvInput = pvCorpus;
+ rc = RTVfsFileRead(hVfsFile, pvCorpus, cbFile, NULL);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCtxMutationAdd(pThis, pMutation);
+
+ if (RT_FAILURE(rc))
+ rtFuzzMutationDestroy(pMutation);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCtxCorpusInputAddFromVfsIoStrm(RTFUZZCTX hFuzzCtx, RTVFSIOSTREAM hVfsIos)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE);
+
+ return RTFuzzCtxCorpusInputAddFromVfsIoStrmEx(hFuzzCtx, hVfsIos, pThis->offMutStart, pThis->cbMutRange);
+}
+
+RTDECL(int) RTFuzzCtxCorpusInputAddFromVfsIoStrmEx(RTFUZZCTX hFuzzCtx, RTVFSIOSTREAM hVfsIos,
+ uint64_t offMutStart, uint64_t cbMutRange)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE);
+
+ void *pvCorpus = NULL;
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsIoStrmQueryInfo(hVfsIos, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(rc))
+ {
+ PRTFUZZMUTATION pMutation = rtFuzzMutationCreateEx(pThis, 0, NULL, offMutStart, cbMutRange,
+ ObjInfo.cbObject, &pvCorpus);
+ if (RT_LIKELY(pMutation))
+ {
+ pMutation->pMutator = &g_MutatorCorpus;
+ pMutation->cbInput = ObjInfo.cbObject;
+ pMutation->pvInput = pvCorpus;
+ rc = RTVfsIoStrmRead(hVfsIos, pvCorpus, ObjInfo.cbObject, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCtxMutationAdd(pThis, pMutation);
+
+ if (RT_FAILURE(rc))
+ rtFuzzMutationDestroy(pMutation);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCtxCorpusInputAddFromDirPath(RTFUZZCTX hFuzzCtx, const char *pszDirPath)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDirPath, VERR_INVALID_POINTER);
+
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, pszDirPath);
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ RTDIRENTRY DirEntry;
+ rc = RTDirRead(hDir, &DirEntry, NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Skip '.', '..' and other non-files. */
+ if ( DirEntry.enmType != RTDIRENTRYTYPE_UNKNOWN
+ && DirEntry.enmType != RTDIRENTRYTYPE_FILE)
+ continue;
+ if (RTDirEntryIsStdDotLink(&DirEntry))
+ continue;
+
+ /* Compose the full path, result 'unknown' entries and skip non-files. */
+ char szFile[RTPATH_MAX];
+ RT_ZERO(szFile);
+ rc = RTPathJoin(szFile, sizeof(szFile), pszDirPath, DirEntry.szName);
+ if (RT_FAILURE(rc))
+ break;
+
+ if (DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
+ {
+ RTDirQueryUnknownType(szFile, false, &DirEntry.enmType);
+ if (DirEntry.enmType != RTDIRENTRYTYPE_FILE)
+ continue;
+ }
+
+ /* Okay, it's a file we can add. */
+ rc = RTFuzzCtxCorpusInputAddFromFile(hFuzzCtx, szFile);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ RTDirClose(hDir);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzCtxCfgSetInputSeedMaximum(RTFUZZCTX hFuzzCtx, size_t cbMax)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ pThis->cbInputMax = cbMax;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(size_t) RTFuzzCtxCfgGetInputSeedMaximum(RTFUZZCTX hFuzzCtx)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, 0);
+
+ return pThis->cbInputMax;
+}
+
+
+RTDECL(int) RTFuzzCtxCfgSetBehavioralFlags(RTFUZZCTX hFuzzCtx, uint32_t fFlags)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(!(fFlags & ~RTFUZZCTX_F_BEHAVIORAL_VALID), VERR_INVALID_PARAMETER);
+
+ pThis->fFlagsBehavioral = fFlags;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(uint32_t) RTFuzzCfgGetBehavioralFlags(RTFUZZCTX hFuzzCtx)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, 0);
+
+ return pThis->fFlagsBehavioral;
+}
+
+
+RTDECL(int) RTFuzzCtxCfgSetTmpDirectory(RTFUZZCTX hFuzzCtx, const char *pszPathTmp)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPathTmp, VERR_INVALID_POINTER);
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+RTDECL(const char *) RTFuzzCtxCfgGetTmpDirectory(RTFUZZCTX hFuzzCtx)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, NULL);
+
+ return NULL;
+}
+
+
+RTDECL(int) RTFuzzCtxCfgSetMutationRange(RTFUZZCTX hFuzzCtx, uint64_t offStart, uint64_t cbRange)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ pThis->offMutStart = offStart;
+ pThis->cbMutRange = cbRange;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzCtxReseed(RTFUZZCTX hFuzzCtx, uint64_t uSeed)
+{
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+
+ RTRandAdvSeed(pThis->hRand, uSeed);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTFuzzCtxInputGenerate(RTFUZZCTX hFuzzCtx, PRTFUZZINPUT phFuzzInput)
+{
+ int rc = VINF_SUCCESS;
+ PRTFUZZCTXINT pThis = hFuzzCtx;
+ AssertPtrReturn(pThis, VERR_INVALID_POINTER);
+ AssertPtrReturn(phFuzzInput, VERR_INVALID_POINTER);
+
+ uint32_t cTries = 0;
+ PRTFUZZMUTATION pMutationParent = rtFuzzCtxMutationPickRnd(pThis);
+ do
+ {
+ uint32_t idxMutator = RTRandAdvU32Ex(pThis->hRand, 0, pThis->cMutators - 1);
+ PCRTFUZZMUTATOR pMutator = &pThis->paMutators[idxMutator];
+ PRTFUZZMUTATION pMutation = NULL;
+
+ uint64_t offStart = 0;
+ if (!(pMutator->fFlags & RTFUZZMUTATOR_F_END_OF_BUF))
+ {
+ uint64_t offMax = pMutationParent->cbInput - 1;
+ if ( pMutationParent->cbMutNew != UINT64_MAX
+ && pMutationParent->offMutStartNew + pMutationParent->cbMutNew < offMax)
+ offMax = pMutationParent->offMutStartNew + pMutationParent->cbMutNew - 1;
+
+ offMax = RT_MAX(pMutationParent->offMutStartNew, offMax);
+ offStart = RTRandAdvU64Ex(pThis->hRand, pMutationParent->offMutStartNew, offMax);
+ }
+ else
+ offStart = pMutationParent->cbInput;
+
+ rc = pMutator->pfnPrep(pThis, offStart, pMutationParent, &pMutation);
+ if ( RT_SUCCESS(rc)
+ && RT_VALID_PTR(pMutation))
+ {
+ pMutation->pMutator = pMutator;
+
+ if (pThis->fFlagsBehavioral & RTFUZZCTX_F_BEHAVIORAL_ADD_INPUT_AUTOMATICALLY_TO_CORPUS)
+ rtFuzzCtxMutationAdd(pThis, pMutation);
+
+ /* Create a new input. */
+ PRTFUZZINPUTINT pInput = (PRTFUZZINPUTINT)rtFuzzCtxMemoryAlloc(pThis, sizeof(RTFUZZINPUTINT));
+ if (RT_LIKELY(pInput))
+ {
+ pInput->u32Magic = 0; /** @todo */
+ pInput->cRefs = 1;
+ pInput->pFuzzer = pThis;
+ pInput->pMutationTop = pMutation;
+ RTFuzzCtxRetain(pThis);
+
+ rtFuzzMutationRelease(pMutationParent);
+ *phFuzzInput = pInput;
+ return rc;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ } while (++cTries <= 50);
+
+ rtFuzzMutationRelease(pMutationParent);
+ if (RT_SUCCESS(rc))
+ rc = VERR_INVALID_STATE;
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzInputQueryBlobData(RTFUZZINPUT hFuzzInput, void **ppv, size_t *pcb)
+{
+ PRTFUZZINPUTINT pThis = hFuzzInput;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->pFuzzer->enmType == RTFUZZCTXTYPE_BLOB, VERR_INVALID_STATE);
+
+ int rc = VINF_SUCCESS;
+ if (!pThis->pMutationTop->pvInput)
+ rc = rtFuzzMutationDataFinalize(pThis->pMutationTop);
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppv = pThis->pMutationTop->pvInput;
+ *pcb = pThis->pMutationTop->cbInput;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzInputMutateStreamData(RTFUZZINPUT hFuzzInput, void *pvBuf, size_t cbBuf)
+{
+ PRTFUZZINPUTINT pThis = hFuzzInput;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->pFuzzer->enmType == RTFUZZCTXTYPE_STREAM, VERR_INVALID_STATE);
+
+ RT_NOREF(pvBuf, cbBuf);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+RTDECL(uint32_t) RTFuzzInputRetain(RTFUZZINPUT hFuzzInput)
+{
+ PRTFUZZINPUTINT pThis = hFuzzInput;
+
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTFuzzInputRelease(RTFUZZINPUT hFuzzInput)
+{
+ PRTFUZZINPUTINT pThis = hFuzzInput;
+ if (pThis == NIL_RTFUZZINPUT)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0)
+ rtFuzzInputDestroy(pThis);
+ return cRefs;
+}
+
+
+RTDECL(int) RTFuzzInputQueryDigestString(RTFUZZINPUT hFuzzInput, char *pszDigest, size_t cchDigest)
+{
+ PRTFUZZINPUTINT pThis = hFuzzInput;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->pFuzzer->enmType == RTFUZZCTXTYPE_BLOB, VERR_INVALID_STATE);
+ AssertPtrReturn(pszDigest, VERR_INVALID_POINTER);
+ AssertReturn(cchDigest >= RTMD5_STRING_LEN + 1, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ if (!pThis->pMutationTop->pvInput)
+ rc = rtFuzzMutationDataFinalize(pThis->pMutationTop);
+
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t abHash[RTMD5_HASH_SIZE];
+ RTMd5(pThis->pMutationTop->pvInput, pThis->pMutationTop->cbInput, &abHash[0]);
+ rc = RTMd5ToString(&abHash[0], pszDigest, cchDigest);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzInputWriteToFile(RTFUZZINPUT hFuzzInput, const char *pszFilename)
+{
+ PRTFUZZINPUTINT pThis = hFuzzInput;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->pFuzzer->enmType == RTFUZZCTXTYPE_BLOB, VERR_INVALID_STATE);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ if (!pThis->pMutationTop->pvInput)
+ rc = rtFuzzMutationDataFinalize(pThis->pMutationTop);
+
+ if (RT_SUCCESS(rc))
+ {
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileWrite(hFile, pThis->pMutationTop->pvInput, pThis->pMutationTop->cbInput, NULL);
+ AssertRC(rc);
+ RTFileClose(hFile);
+
+ if (RT_FAILURE(rc))
+ RTFileDelete(pszFilename);
+ }
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTFuzzInputAddToCtxCorpus(RTFUZZINPUT hFuzzInput)
+{
+ PRTFUZZINPUTINT pThis = hFuzzInput;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ return rtFuzzCtxMutationAdd(pThis->pFuzzer, pThis->pMutationTop);
+}
+
+
+RTDECL(int) RTFuzzInputRemoveFromCtxCorpus(RTFUZZINPUT hFuzzInput)
+{
+ PRTFUZZINPUTINT pThis = hFuzzInput;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+#if 0
+ int rc = VINF_SUCCESS;
+ PRTFUZZINTERMEDIATE pIntermediate = NULL;
+ PRTFUZZINPUTINT pInputLoc = rtFuzzCtxInputLocate(pThis->pFuzzer, &pThis->abMd5Hash[0], true /*fExact*/,
+ &pIntermediate);
+ if (pInputLoc)
+ {
+ AssertPtr(pIntermediate);
+ Assert(pInputLoc == pThis);
+
+ uint64_t u64Md5Low = *(uint64_t *)&pThis->abMd5Hash[0];
+ RTAvlU64Remove(&pIntermediate->TreeSeedsLow, u64Md5Low);
+ RTFuzzInputRelease(hFuzzInput);
+ }
+ else
+ rc = VERR_NOT_FOUND;
+#endif
+
+ return VERR_NOT_IMPLEMENTED;
+}
+
diff --git a/src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp b/src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp
new file mode 100644
index 00000000..d32f579b
--- /dev/null
+++ b/src/VBox/Runtime/common/fuzz/fuzzclientcmd.cpp
@@ -0,0 +1,329 @@
+/* $Id: fuzzclientcmd.cpp $ */
+/** @file
+ * IPRT - Fuzzing framework API, fuzzed client command.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/fuzz.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/ldr.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/types.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef DECLCALLBACKTYPE(int, FNLLVMFUZZERTESTONEINPUT,(const uint8_t *pbData, size_t cbData));
+typedef FNLLVMFUZZERTESTONEINPUT *PFNLLVMFUZZERTESTONEINPUT;
+
+
+/**
+ * Fuzzing client command state.
+ */
+typedef struct RTFUZZCMDCLIENT
+{
+ /** Our own fuzzing context containing all the data. */
+ RTFUZZCTX hFuzzCtx;
+ /** Consumption callback. */
+ PFNFUZZCLIENTCONSUME pfnConsume;
+ /** Opaque user data to pass to the consumption callback. */
+ void *pvUser;
+ /** The LLVM libFuzzer compatible entry point if configured */
+ PFNLLVMFUZZERTESTONEINPUT pfnLlvmFuzzerTestOneInput;
+ /** The selected input channel. */
+ RTFUZZOBSINPUTCHAN enmInputChan;
+ /** Standard input VFS handle. */
+ RTVFSIOSTREAM hVfsStdIn;
+ /** Standard output VFS handle. */
+ RTVFSIOSTREAM hVfsStdOut;
+} RTFUZZCMDCLIENT;
+/** Pointer to a fuzzing client command state. */
+typedef RTFUZZCMDCLIENT *PRTFUZZCMDCLIENT;
+
+
+
+/**
+ * Runs the appropriate consumption callback with the provided data.
+ *
+ * @returns Status code, 0 for success.
+ * @param pThis The fuzzing client command state.
+ * @param pvData The data to consume.
+ * @param cbData Size of the data in bytes.
+ */
+static int rtFuzzCmdClientConsume(PRTFUZZCMDCLIENT pThis, const void *pvData, size_t cbData)
+{
+ if (pThis->pfnLlvmFuzzerTestOneInput)
+ return pThis->pfnLlvmFuzzerTestOneInput((const uint8_t *)pvData, cbData);
+ else
+ return pThis->pfnConsume(pvData, cbData, pThis->pvUser);
+}
+
+
+/**
+ * The fuzzing client mainloop.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing client command state.
+ */
+static int rtFuzzCmdClientMainloop(PRTFUZZCMDCLIENT pThis)
+{
+ int rc = VINF_SUCCESS;
+ bool fShutdown = false;
+
+ while ( !fShutdown
+ && RT_SUCCESS(rc))
+ {
+ RTFUZZINPUT hFuzzInput;
+
+ rc = RTFuzzCtxInputGenerate(pThis->hFuzzCtx, &hFuzzInput);
+ if (RT_SUCCESS(rc))
+ {
+ void *pv = NULL;
+ size_t cb = 0;
+ rc = RTFuzzInputQueryBlobData(hFuzzInput, &pv, &cb);
+ if (RT_SUCCESS(rc))
+ {
+ char bResp = '.';
+ int rc2 = rtFuzzCmdClientConsume(pThis, pv, cb);
+ if (RT_SUCCESS(rc2))
+ {
+ rc = RTFuzzInputAddToCtxCorpus(hFuzzInput);
+ bResp = 'A';
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmWrite(pThis->hVfsStdOut, &bResp, 1, true /*fBlocking*/, NULL);
+ }
+
+ RTFuzzInputRelease(hFuzzInput);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Run the fuzzing client.
+ *
+ * @returns Process exit status.
+ * @param pThis The fuzzing client command state.
+ */
+static RTEXITCODE rtFuzzCmdClientRun(PRTFUZZCMDCLIENT pThis)
+{
+ int rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT, 0, true /*fLeaveOpen*/, &pThis->hVfsStdIn);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, 0, true /*fLeaveOpen*/, &pThis->hVfsStdOut);
+ if (RT_SUCCESS(rc))
+ {
+ /* Read the initial input fuzzer state from the standard input. */
+ uint32_t cbFuzzCtxState;
+ rc = RTVfsIoStrmRead(pThis->hVfsStdIn, &cbFuzzCtxState, sizeof(cbFuzzCtxState), true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ void *pvFuzzCtxState = RTMemAllocZ(cbFuzzCtxState);
+ if (RT_LIKELY(pvFuzzCtxState))
+ {
+ rc = RTVfsIoStrmRead(pThis->hVfsStdIn, pvFuzzCtxState, cbFuzzCtxState, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzCtxCreateFromStateMem(&pThis->hFuzzCtx, pvFuzzCtxState, cbFuzzCtxState);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCmdClientMainloop(pThis);
+ }
+
+ RTMemFree(pvFuzzCtxState);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ return RTEXITCODE_SUCCESS;
+
+ return RTEXITCODE_FAILURE;
+}
+
+
+/**
+ * Run a single iteration of the fuzzing client and return.
+ *
+ * @returns Process exit status.
+ * @param pThis The fuzzing client command state.
+ */
+static RTEXITCODE rtFuzzCmdClientRunFile(PRTFUZZCMDCLIENT pThis, const char *pszFilename)
+{
+ void *pv = NULL;
+ size_t cbFile = 0;
+ int rc = RTFileReadAll(pszFilename, &pv, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ rtFuzzCmdClientConsume(pThis, pv, cbFile);
+ RTFileReadAllFree(pv, cbFile);
+ return RTEXITCODE_SUCCESS;
+ }
+
+ return RTEXITCODE_FAILURE;
+}
+
+
+RTR3DECL(RTEXITCODE) RTFuzzCmdFuzzingClient(unsigned cArgs, char **papszArgs, PFNFUZZCLIENTCONSUME pfnConsume, void *pvUser)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ { "--llvm-input", 'l', RTGETOPT_REQ_STRING },
+ { "--file", 'f', RTGETOPT_REQ_STRING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_SUCCESS(rc))
+ {
+ /* Option variables: */
+ RTFUZZCMDCLIENT This;
+ RTLDRMOD hLlvmMod = NIL_RTLDRMOD;
+ const char *pszFilename = NULL;
+
+ This.pfnConsume = pfnConsume;
+ This.pvUser = pvUser;
+ This.enmInputChan = RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT;
+
+ /* Argument parsing loop. */
+ bool fContinue = true;
+ bool fExit = false;
+ do
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ fContinue = false;
+ break;
+
+ case 'f':
+ {
+ pszFilename = ValueUnion.psz;
+ This.enmInputChan = RTFUZZOBSINPUTCHAN_FILE;
+ break;
+ }
+
+ case 'l':
+ {
+ /*
+ * Load the indicated library and try to resolve LLVMFuzzerTestOneInput,
+ * which will act as the input callback.
+ */
+ rc = RTLdrLoad(ValueUnion.psz, &hLlvmMod);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hLlvmMod, "LLVMFuzzerTestOneInput", (void **)&This.pfnLlvmFuzzerTestOneInput);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to query '%s' from '%s': %Rrc",
+ "LLVMFuzzerTestOneInput",
+ ValueUnion.psz,
+ rc);
+ }
+ break;
+ }
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ fContinue = false;
+ fExit = true;
+ break;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ fContinue = false;
+ fExit = true;
+ break;
+
+ default:
+ rcExit = RTGetOptPrintError(chOpt, &ValueUnion);
+ fContinue = false;
+ break;
+ }
+ } while (fContinue);
+
+ if ( rcExit == RTEXITCODE_SUCCESS
+ && !fExit)
+ {
+ switch (This.enmInputChan)
+ {
+ case RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT:
+ rcExit = rtFuzzCmdClientRun(&This);
+ break;
+ case RTFUZZOBSINPUTCHAN_FILE:
+ rcExit = rtFuzzCmdClientRunFile(&This, pszFilename);
+ break;
+ default:
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "Input channel unknown/not implemented yet");
+ }
+ }
+
+ if (hLlvmMod != NIL_RTLDRMOD)
+ RTLdrClose(hLlvmMod);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp b/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp
new file mode 100644
index 00000000..562218e1
--- /dev/null
+++ b/src/VBox/Runtime/common/fuzz/fuzzmastercmd.cpp
@@ -0,0 +1,1877 @@
+/* $Id: fuzzmastercmd.cpp $ */
+/** @file
+ * IPRT - Fuzzing framework API, master command.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/fuzz.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/base64.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/json.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/process.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+
+/**
+ * A running fuzzer state.
+ */
+typedef struct RTFUZZRUN
+{
+ /** List node. */
+ RTLISTNODE NdFuzzed;
+ /** Identifier. */
+ char *pszId;
+ /** Number of processes. */
+ uint32_t cProcs;
+ /** Target recorder flags. */
+ uint32_t fTgtRecFlags;
+ /** The fuzzing observer state handle. */
+ RTFUZZOBS hFuzzObs;
+ /** Flag whether fuzzing was started. */
+ bool fStarted;
+ /** Time when this run was created. */
+ RTTIME TimeCreated;
+ /** Millisecond timestamp when the run was created. */
+ uint64_t tsCreatedMs;
+} RTFUZZRUN;
+/** Pointer to a running fuzzer state. */
+typedef RTFUZZRUN *PRTFUZZRUN;
+
+
+/**
+ * Fuzzing master command state.
+ */
+typedef struct RTFUZZCMDMASTER
+{
+ /** List of running fuzzers. */
+ RTLISTANCHOR LstFuzzed;
+ /** The port to listen on. */
+ uint16_t uPort;
+ /** The TCP server for requests. */
+ PRTTCPSERVER hTcpSrv;
+ /** The root temp directory. */
+ const char *pszTmpDir;
+ /** The root results directory. */
+ const char *pszResultsDir;
+ /** Flag whether to shutdown. */
+ bool fShutdown;
+ /** The response message. */
+ char *pszResponse;
+} RTFUZZCMDMASTER;
+/** Pointer to a fuzzing master command state. */
+typedef RTFUZZCMDMASTER *PRTFUZZCMDMASTER;
+
+
+/**
+ * Wrapper around RTErrInfoSetV / RTMsgErrorV.
+ *
+ * @returns @a rc
+ * @param pErrInfo Extended error info.
+ * @param rc The return code.
+ * @param pszFormat The message format.
+ * @param ... The message format arguments.
+ */
+static int rtFuzzCmdMasterErrorRc(PRTERRINFO pErrInfo, int rc, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ if (pErrInfo)
+ RTErrInfoSetV(pErrInfo, rc, pszFormat, va);
+ else
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Returns a running fuzzer state by the given ID.
+ *
+ * @returns Pointer to the running fuzzer state or NULL if not found.
+ * @param pThis The fuzzing master command state.
+ * @param pszId The ID to look for.
+ */
+static PRTFUZZRUN rtFuzzCmdMasterGetFuzzerById(PRTFUZZCMDMASTER pThis, const char *pszId)
+{
+ PRTFUZZRUN pIt = NULL;
+ RTListForEach(&pThis->LstFuzzed, pIt, RTFUZZRUN, NdFuzzed)
+ {
+ if (!RTStrCmp(pIt->pszId, pszId))
+ return pIt;
+ }
+
+ return NULL;
+}
+
+
+#if 0 /* unused */
+/**
+ * Processes and returns the value of the given config item in the JSON request.
+ *
+ * @returns IPRT status code.
+ * @param ppszStr Where to store the pointer to the string on success.
+ * @param pszCfgItem The config item to resolve.
+ * @param hJsonCfg The JSON object containing the item.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessCfgString(char **ppszStr, const char *pszCfgItem, RTJSONVAL hJsonCfg, PRTERRINFO pErrInfo)
+{
+ int rc = RTJsonValueQueryStringByName(hJsonCfg, pszCfgItem, ppszStr);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query string value of \"%s\"", pszCfgItem);
+
+ return rc;
+}
+
+
+/**
+ * Processes and returns the value of the given config item in the JSON request.
+ *
+ * @returns IPRT status code.
+ * @param pfVal Where to store the config value on success.
+ * @param pszCfgItem The config item to resolve.
+ * @param hJsonCfg The JSON object containing the item.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessCfgBool(bool *pfVal, const char *pszCfgItem, RTJSONVAL hJsonCfg, PRTERRINFO pErrInfo)
+{
+ int rc = RTJsonValueQueryBooleanByName(hJsonCfg, pszCfgItem, pfVal);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query boolean value of \"%s\"", pszCfgItem);
+
+ return rc;
+}
+
+
+/**
+ * Processes and returns the value of the given config item in the JSON request.
+ *
+ * @returns IPRT status code.
+ * @param pfVal Where to store the config value on success.
+ * @param pszCfgItem The config item to resolve.
+ * @param hJsonCfg The JSON object containing the item.
+ * @param fDef Default value if the item wasn't found.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessCfgBoolDef(bool *pfVal, const char *pszCfgItem, RTJSONVAL hJsonCfg, bool fDef, PRTERRINFO pErrInfo)
+{
+ int rc = RTJsonValueQueryBooleanByName(hJsonCfg, pszCfgItem, pfVal);
+ if (rc == VERR_NOT_FOUND)
+ {
+ *pfVal = fDef;
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query boolean value of \"%s\"", pszCfgItem);
+
+ return rc;
+}
+#endif
+
+
+/**
+ * Processes and returns the value of the given config item in the JSON request.
+ *
+ * @returns IPRT status code.
+ * @param pcbVal Where to store the config value on success.
+ * @param pszCfgItem The config item to resolve.
+ * @param hJsonCfg The JSON object containing the item.
+ * @param cbDef Default value if the item wasn't found.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessCfgSizeDef(size_t *pcbVal, const char *pszCfgItem, RTJSONVAL hJsonCfg, size_t cbDef, PRTERRINFO pErrInfo)
+{
+ *pcbVal = cbDef; /* Make GCC 6.3.0 happy. */
+
+ int64_t i64Val = 0;
+ int rc = RTJsonValueQueryIntegerByName(hJsonCfg, pszCfgItem, &i64Val);
+ if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS;
+ else if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query size_t value of \"%s\"", pszCfgItem);
+ else if (i64Val < 0 || (size_t)i64Val != (uint64_t)i64Val)
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_OUT_OF_RANGE, "JSON request malformed: Integer \"%s\" is out of range", pszCfgItem);
+ else
+ *pcbVal = (size_t)i64Val;
+
+ return rc;
+}
+
+
+/**
+ * Processes and returns the value of the given config item in the JSON request.
+ *
+ * @returns IPRT status code.
+ * @param pcbVal Where to store the config value on success.
+ * @param pszCfgItem The config item to resolve.
+ * @param hJsonCfg The JSON object containing the item.
+ * @param cbDef Default value if the item wasn't found.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessCfgU32Def(uint32_t *pu32Val, const char *pszCfgItem, RTJSONVAL hJsonCfg, uint32_t u32Def, PRTERRINFO pErrInfo)
+{
+ int64_t i64Val = 0;
+ int rc = RTJsonValueQueryIntegerByName(hJsonCfg, pszCfgItem, &i64Val);
+ if (rc == VERR_NOT_FOUND)
+ {
+ *pu32Val = u32Def;
+ rc = VINF_SUCCESS;
+ }
+ else if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query uint32_t value of \"%s\"", pszCfgItem);
+ else if (i64Val < 0 || (uint32_t)i64Val != (uint64_t)i64Val)
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_OUT_OF_RANGE, "JSON request malformed: Integer \"%s\" is out of range", pszCfgItem);
+ else
+ *pu32Val = (uint32_t)i64Val;
+
+ return rc;
+}
+
+
+/**
+ * Returns the configured input channel for the binary under test.
+ *
+ * @returns Selected input channel or RTFUZZOBSINPUTCHAN_INVALID if an error occurred.
+ * @param pszCfgItem The config item to resolve.
+ * @param hJsonCfg The JSON object containing the item.
+ * @param enmChanDef Default value if the item wasn't found.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static RTFUZZOBSINPUTCHAN rtFuzzCmdMasterFuzzRunProcessCfgGetInputChan(const char *pszCfgItem, RTJSONVAL hJsonCfg, RTFUZZOBSINPUTCHAN enmChanDef, PRTERRINFO pErrInfo)
+{
+ RTFUZZOBSINPUTCHAN enmInputChan = RTFUZZOBSINPUTCHAN_INVALID;
+
+ RTJSONVAL hJsonVal;
+ int rc = RTJsonValueQueryByName(hJsonCfg, pszCfgItem, &hJsonVal);
+ if (rc == VERR_NOT_FOUND)
+ enmInputChan = enmChanDef;
+ else if (RT_SUCCESS(rc))
+ {
+ const char *pszBinary = RTJsonValueGetString(hJsonVal);
+ if (pszBinary)
+ {
+ if (!RTStrCmp(pszBinary, "File"))
+ enmInputChan = RTFUZZOBSINPUTCHAN_FILE;
+ else if (!RTStrCmp(pszBinary, "Stdin"))
+ enmInputChan = RTFUZZOBSINPUTCHAN_STDIN;
+ else if (!RTStrCmp(pszBinary, "FuzzingAware"))
+ enmInputChan = RTFUZZOBSINPUTCHAN_FUZZING_AWARE_CLIENT;
+ else
+ rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_PARAMETER, "JSON request malformed: \"%s\" for \"%s\" is not known", pszCfgItem, pszBinary);
+ }
+ else
+ rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"%s\" is not a string", pszCfgItem);
+
+ RTJsonValueRelease(hJsonVal);
+ }
+ else
+ rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query \"%s\"", pszCfgItem);
+
+ return enmInputChan;
+}
+
+
+/**
+ * Processes binary related configs for the given fuzzing run.
+ *
+ * @returns IPRT status code.
+ * @param pFuzzRun The fuzzing run.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessBinaryCfg(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ RTJSONVAL hJsonVal;
+ int rc = RTJsonValueQueryByName(hJsonRoot, "BinaryPath", &hJsonVal);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszBinary = RTJsonValueGetString(hJsonVal);
+ if (RT_LIKELY(pszBinary))
+ {
+ RTFUZZOBSINPUTCHAN enmInputChan = rtFuzzCmdMasterFuzzRunProcessCfgGetInputChan("InputChannel", hJsonRoot, RTFUZZOBSINPUTCHAN_STDIN, pErrInfo);
+ if (enmInputChan != RTFUZZOBSINPUTCHAN_INVALID)
+ {
+ rc = RTFuzzObsSetTestBinary(pFuzzRun->hFuzzObs, pszBinary, enmInputChan);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to add the binary path for the fuzzing run");
+ }
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"BinaryPath\" is not a string");
+ RTJsonValueRelease(hJsonVal);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query value of \"BinaryPath\"");
+
+ return rc;
+}
+
+
+/**
+ * Processes argument related configs for the given fuzzing run.
+ *
+ * @returns IPRT status code.
+ * @param pFuzzRun The fuzzing run.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessArgCfg(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ RTJSONVAL hJsonValArgArray;
+ int rc = RTJsonValueQueryByName(hJsonRoot, "Arguments", &hJsonValArgArray);
+ if (RT_SUCCESS(rc))
+ {
+ unsigned cArgs = 0;
+ rc = RTJsonValueQueryArraySize(hJsonValArgArray, &cArgs);
+ if (RT_SUCCESS(rc))
+ {
+ if (cArgs > 0)
+ {
+ const char **papszArgs = (const char **)RTMemAllocZ(cArgs * sizeof(const char *));
+ RTJSONVAL *pahJsonVal = (RTJSONVAL *)RTMemAllocZ(cArgs * sizeof(RTJSONVAL));
+ if (RT_LIKELY(papszArgs && pahJsonVal))
+ {
+ unsigned idx = 0;
+
+ for (idx = 0; idx < cArgs && RT_SUCCESS(rc); idx++)
+ {
+ rc = RTJsonValueQueryByIndex(hJsonValArgArray, idx, &pahJsonVal[idx]);
+ if (RT_SUCCESS(rc))
+ {
+ papszArgs[idx] = RTJsonValueGetString(pahJsonVal[idx]);
+ if (RT_UNLIKELY(!papszArgs[idx]))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Argument %u is not a string", idx);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzObsSetTestBinaryArgs(pFuzzRun->hFuzzObs, papszArgs, cArgs);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to set arguments for the fuzzing run");
+ }
+
+ /* Release queried values. */
+ while (idx > 0)
+ {
+ RTJsonValueRelease(pahJsonVal[idx - 1]);
+ idx--;
+ }
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_MEMORY, "Out of memory allocating memory for the argument vector");
+
+ if (papszArgs)
+ RTMemFree(papszArgs);
+ if (pahJsonVal)
+ RTMemFree(pahJsonVal);
+ }
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"Arguments\" is not an array");
+ RTJsonValueRelease(hJsonValArgArray);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes process environment related configs for the given fuzzing run.
+ *
+ * @returns IPRT status code.
+ * @param pFuzzRun The fuzzing run.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessEnvironment(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ RTJSONVAL hJsonValEnv;
+ int rc = RTJsonValueQueryByName(hJsonRoot, "Env", &hJsonValEnv);
+ if (RT_SUCCESS(rc))
+ {
+ bool fReplaceEnv = false; /* false means to append everything to the default block. */
+
+ rc = RTJsonValueQueryBooleanByName(hJsonRoot, "EnvReplace", &fReplaceEnv);
+ if ( RT_SUCCESS(rc)
+ || rc == VERR_NOT_FOUND)
+ {
+ RTJSONIT hEnvIt;
+ RTENV hEnv = NULL;
+
+ if (fReplaceEnv)
+ rc = RTEnvCreate(&hEnv);
+ else
+ rc = RTEnvClone(&hEnv, RTENV_DEFAULT);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTJsonIteratorBeginArray(hJsonValEnv, &hEnvIt);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ RTJSONVAL hVal;
+ rc = RTJsonIteratorQueryValue(hEnvIt, &hVal, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszVar = RTJsonValueGetString(hVal);
+ if (RT_LIKELY(pszVar))
+ rc = RTEnvPutEx(hEnv, pszVar);
+ RTJsonValueRelease(hVal);
+ }
+ rc = RTJsonIteratorNext(hEnvIt);
+ } while (RT_SUCCESS(rc));
+
+ if ( rc == VERR_JSON_IS_EMPTY
+ || rc == VERR_JSON_ITERATOR_END)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to parse environment");
+
+ RTJsonIteratorFree(hEnvIt);
+ }
+ else if (rc == VERR_JSON_IS_EMPTY)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"Environment\" is not an array");
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzObsSetTestBinaryEnv(pFuzzRun->hFuzzObs, hEnv);
+ AssertRC(rc);
+ }
+ else if (hEnv)
+ RTEnvDestroy(hEnv);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to create environment block");
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query \"EnvReplace\"");
+
+ RTJsonValueRelease(hJsonValEnv);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS; /* Just keep using the default environment. */
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query the \"Environment\"");
+
+ return rc;
+}
+
+
+/**
+ * Processes process environment related configs for the given fuzzing run.
+ *
+ * @returns IPRT status code.
+ * @param pFuzzRun The fuzzing run.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessSanitizers(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ RTJSONVAL hJsonValSan;
+ int rc = RTJsonValueQueryByName(hJsonRoot, "Sanitizers", &hJsonValSan);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fSanitizers = 0;
+ RTJSONIT hSanIt;
+ rc = RTJsonIteratorBeginArray(hJsonValSan, &hSanIt);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ RTJSONVAL hVal;
+ rc = RTJsonIteratorQueryValue(hSanIt, &hVal, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszSan = RTJsonValueGetString(hVal);
+ if (!RTStrICmp(pszSan, "Asan"))
+ fSanitizers |= RTFUZZOBS_SANITIZER_F_ASAN;
+ else if (!RTStrICmp(pszSan, "SanCov"))
+ fSanitizers |= RTFUZZOBS_SANITIZER_F_SANCOV;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NOT_FOUND, "JSON request malformed: The sanitizer '%s' is not known", pszSan);
+ RTJsonValueRelease(hVal);
+ }
+ rc = RTJsonIteratorNext(hSanIt);
+ } while (RT_SUCCESS(rc));
+
+ if ( rc == VERR_JSON_IS_EMPTY
+ || rc == VERR_JSON_ITERATOR_END)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to parse sanitizers");
+
+ RTJsonIteratorFree(hSanIt);
+ }
+ else if (rc == VERR_JSON_IS_EMPTY)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"Sanitizers\" is not an array");
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzObsSetTestBinarySanitizers(pFuzzRun->hFuzzObs, fSanitizers);
+ AssertRC(rc);
+ }
+
+ RTJsonValueRelease(hJsonValSan);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS; /* Just keep using the defaults. */
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query the \"Sanitizers\"");
+
+ return rc;
+}
+
+
+/**
+ * Processes the given seed and adds it to the input corpus.
+ *
+ * @returns IPRT status code.
+ * @param hFuzzCtx The fuzzing context handle.
+ * @param pszCompression Compression used for the seed.
+ * @param pszSeed The seed as a base64 encoded string.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessSeed(RTFUZZCTX hFuzzCtx, const char *pszCompression, const char *pszSeed, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+ ssize_t cbSeedDecoded = RTBase64DecodedSize(pszSeed, NULL);
+ if (cbSeedDecoded > 0)
+ {
+ uint8_t *pbSeedDecoded = (uint8_t *)RTMemAllocZ(cbSeedDecoded);
+ if (RT_LIKELY(pbSeedDecoded))
+ {
+ rc = RTBase64Decode(pszSeed, pbSeedDecoded, cbSeedDecoded, NULL, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* Decompress if applicable. */
+ if (!RTStrICmp(pszCompression, "None"))
+ rc = RTFuzzCtxCorpusInputAdd(hFuzzCtx, pbSeedDecoded, cbSeedDecoded);
+ else
+ {
+ RTVFSIOSTREAM hVfsIosSeed;
+ rc = RTVfsIoStrmFromBuffer(RTFILE_O_READ, pbSeedDecoded, cbSeedDecoded, &hVfsIosSeed);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSIOSTREAM hVfsDecomp = NIL_RTVFSIOSTREAM;
+
+ if (!RTStrICmp(pszCompression, "Gzip"))
+ rc = RTZipGzipDecompressIoStream(hVfsIosSeed, RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR, &hVfsDecomp);
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Compression \"%s\" is not known", pszCompression);
+
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTVfsMemFileCreate(hVfsDecomp, 2 * _1M, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* The VFS file contains the buffer for the seed now. */
+ rc = RTFuzzCtxCorpusInputAddFromVfsFile(hFuzzCtx, hVfsFile);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to add input seed");
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to seek to the beginning of the seed");
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to decompress input seed");
+
+ RTVfsIoStrmRelease(hVfsDecomp);
+ }
+
+ RTVfsIoStrmRelease(hVfsIosSeed);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create I/O stream from seed buffer");
+ }
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to decode the seed string");
+
+ RTMemFree(pbSeedDecoded);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_MEMORY, "Request error: Failed to allocate %zd bytes of memory for the seed", cbSeedDecoded);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: Couldn't find \"Seed\" doesn't contain a base64 encoded value");
+
+ return rc;
+}
+
+
+/**
+ * Processes a signle input seed for the given fuzzing run.
+ *
+ * @returns IPRT status code.
+ * @param pFuzzRun The fuzzing run.
+ * @param hJsonSeed The seed node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessInputSeedSingle(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonSeed, PRTERRINFO pErrInfo)
+{
+ RTFUZZCTX hFuzzCtx;
+ int rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONVAL hJsonValComp;
+ rc = RTJsonValueQueryByName(hJsonSeed, "Compression", &hJsonValComp);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszCompression = RTJsonValueGetString(hJsonValComp);
+ if (RT_LIKELY(pszCompression))
+ {
+ RTJSONVAL hJsonValSeed;
+ rc = RTJsonValueQueryByName(hJsonSeed, "Seed", &hJsonValSeed);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszSeed = RTJsonValueGetString(hJsonValSeed);
+ if (RT_LIKELY(pszSeed))
+ rc = rtFuzzCmdMasterFuzzRunProcessSeed(hFuzzCtx, pszCompression, pszSeed, pErrInfo);
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"Seed\" value is not a string");
+
+ RTJsonValueRelease(hJsonValSeed);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Seed\" value");
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"Compression\" value is not a string");
+
+ RTJsonValueRelease(hJsonValComp);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Compression\" value");
+
+ RTFuzzCtxRelease(hFuzzCtx);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to query fuzzing context from observer");
+
+ return rc;
+}
+
+
+/**
+ * Processes the given seed file and adds it to the input corpus.
+ *
+ * @returns IPRT status code.
+ * @param hFuzzCtx The fuzzing context handle.
+ * @param pszCompression Compression used for the seed.
+ * @param pszSeed The seed as a base64 encoded string.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessSeedFile(RTFUZZCTX hFuzzCtx, const char *pszCompression, const char *pszFile, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Decompress if applicable. */
+ if (!RTStrICmp(pszCompression, "None"))
+ rc = RTFuzzCtxCorpusInputAddFromFile(hFuzzCtx, pszFile);
+ else
+ {
+ RTVFSIOSTREAM hVfsIosSeed;
+ rc = RTVfsIoStrmOpenNormal(pszFile, RTFILE_O_OPEN | RTFILE_O_READ, &hVfsIosSeed);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSIOSTREAM hVfsDecomp = NIL_RTVFSIOSTREAM;
+
+ if (!RTStrICmp(pszCompression, "Gzip"))
+ rc = RTZipGzipDecompressIoStream(hVfsIosSeed, RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR, &hVfsDecomp);
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Compression \"%s\" is not known", pszCompression);
+
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTVfsMemFileCreate(hVfsDecomp, 2 * _1M, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* The VFS file contains the buffer for the seed now. */
+ rc = RTFuzzCtxCorpusInputAddFromVfsFile(hFuzzCtx, hVfsFile);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to add input seed");
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to seek to the beginning of the seed");
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "Request error: Failed to decompress input seed");
+
+ RTVfsIoStrmRelease(hVfsDecomp);
+ }
+
+ RTVfsIoStrmRelease(hVfsIosSeed);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create I/O stream from seed buffer");
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes a signle input seed given as a file path for the given fuzzing run.
+ *
+ * @returns IPRT status code.
+ * @param pFuzzRun The fuzzing run.
+ * @param hJsonSeed The seed node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessInputSeedFileSingle(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonSeed, PRTERRINFO pErrInfo)
+{
+ RTFUZZCTX hFuzzCtx;
+ int rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONVAL hJsonValComp;
+ rc = RTJsonValueQueryByName(hJsonSeed, "Compression", &hJsonValComp);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszCompression = RTJsonValueGetString(hJsonValComp);
+ if (RT_LIKELY(pszCompression))
+ {
+ RTJSONVAL hJsonValFile;
+ rc = RTJsonValueQueryByName(hJsonSeed, "File", &hJsonValFile);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszFile = RTJsonValueGetString(hJsonValFile);
+ if (RT_LIKELY(pszFile))
+ rc = rtFuzzCmdMasterFuzzRunProcessSeedFile(hFuzzCtx, pszCompression, pszFile, pErrInfo);
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"File\" value is not a string");
+
+ RTJsonValueRelease(hJsonValFile);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"File\" value");
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_INVALID_STATE, "JSON request malformed: \"Compression\" value is not a string");
+
+ RTJsonValueRelease(hJsonValComp);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Compression\" value");
+
+ RTFuzzCtxRelease(hFuzzCtx);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Failed to query fuzzing context from observer");
+
+ return rc;
+}
+
+
+/**
+ * Processes input seed related configs for the given fuzzing run.
+ *
+ * @returns IPRT status code.
+ * @param pFuzzRun The fuzzing run.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessInputSeeds(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ RTJSONVAL hJsonValSeedArray;
+ int rc = RTJsonValueQueryByName(hJsonRoot, "InputSeeds", &hJsonValSeedArray);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONIT hIt;
+ rc = RTJsonIteratorBegin(hJsonValSeedArray, &hIt);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONVAL hJsonInpSeed;
+ while ( RT_SUCCESS(rc)
+ && RTJsonIteratorQueryValue(hIt, &hJsonInpSeed, NULL) != VERR_JSON_ITERATOR_END)
+ {
+ rc = rtFuzzCmdMasterFuzzRunProcessInputSeedSingle(pFuzzRun, hJsonInpSeed, pErrInfo);
+ RTJsonValueRelease(hJsonInpSeed);
+ if (RT_FAILURE(rc))
+ break;
+ rc = RTJsonIteratorNext(hIt);
+ }
+
+ if (rc == VERR_JSON_ITERATOR_END)
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to create array iterator");
+
+ RTJsonValueRelease(hJsonValSeedArray);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS;
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTJsonValueQueryByName(hJsonRoot, "InputSeedFiles", &hJsonValSeedArray);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONIT hIt;
+ rc = RTJsonIteratorBegin(hJsonValSeedArray, &hIt);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONVAL hJsonInpSeed;
+ while ( RT_SUCCESS(rc)
+ && RTJsonIteratorQueryValue(hIt, &hJsonInpSeed, NULL) != VERR_JSON_ITERATOR_END)
+ {
+ rc = rtFuzzCmdMasterFuzzRunProcessInputSeedFileSingle(pFuzzRun, hJsonInpSeed, pErrInfo);
+ RTJsonValueRelease(hJsonInpSeed);
+ if (RT_FAILURE(rc))
+ break;
+ rc = RTJsonIteratorNext(hIt);
+ }
+
+ if (rc == VERR_JSON_ITERATOR_END)
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to create array iterator");
+
+ RTJsonValueRelease(hJsonValSeedArray);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes miscellaneous config items.
+ *
+ * @returns IPRT status code.
+ * @param pFuzzRun The fuzzing run.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessMiscCfg(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ size_t cbTmp;
+ int rc = rtFuzzCmdMasterFuzzRunProcessCfgSizeDef(&cbTmp, "InputSeedMax", hJsonRoot, 0, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ RTFUZZCTX hFuzzCtx;
+ rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx);
+ AssertRC(rc);
+
+ rc = RTFuzzCtxCfgSetInputSeedMaximum(hFuzzCtx, cbTmp);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to set maximum input seed size to %zu", cbTmp);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCmdMasterFuzzRunProcessCfgU32Def(&pFuzzRun->cProcs, "FuzzingProcs", hJsonRoot, 0, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t msTimeoutMax = 0;
+ rc = rtFuzzCmdMasterFuzzRunProcessCfgU32Def(&msTimeoutMax, "TimeoutMax", hJsonRoot, 1000, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = RTFuzzObsSetTestBinaryTimeout(pFuzzRun->hFuzzObs, msTimeoutMax);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes target recording related configs for the given fuzzing run.
+ *
+ * @returns IPRT status code.
+ * @param pFuzzRun The fuzzing run.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunProcessTgtRecFlags(PRTFUZZRUN pFuzzRun, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ RTJSONVAL hJsonValTgt;
+ int rc = RTJsonValueQueryByName(hJsonRoot, "TgtRec", &hJsonValTgt);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fTgtRecFlags = 0;
+ RTJSONIT hTgtIt;
+ rc = RTJsonIteratorBeginArray(hJsonValTgt, &hTgtIt);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ RTJSONVAL hVal;
+ rc = RTJsonIteratorQueryValue(hTgtIt, &hVal, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszTgtRec = RTJsonValueGetString(hVal);
+ if (!RTStrICmp(pszTgtRec, "StdOut"))
+ fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_STDOUT;
+ else if (!RTStrICmp(pszTgtRec, "StdErr"))
+ fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_STDERR;
+ else if (!RTStrICmp(pszTgtRec, "ProcSts"))
+ fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_PROCSTATUS;
+ else if (!RTStrICmp(pszTgtRec, "SanCov"))
+ fTgtRecFlags |= RTFUZZTGT_REC_STATE_F_SANCOV;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NOT_FOUND, "JSON request malformed: The recording flags '%s' is not known", pszTgtRec);
+ RTJsonValueRelease(hVal);
+ }
+ rc = RTJsonIteratorNext(hTgtIt);
+ } while (RT_SUCCESS(rc));
+
+ if ( rc == VERR_JSON_IS_EMPTY
+ || rc == VERR_JSON_ITERATOR_END)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to parse target recording flags");
+
+ RTJsonIteratorFree(hTgtIt);
+ }
+ else if (rc == VERR_JSON_IS_EMPTY)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: \"TgtRec\" is not an array");
+
+ pFuzzRun->fTgtRecFlags = fTgtRecFlags;
+
+ RTJsonValueRelease(hJsonValTgt);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ {
+ pFuzzRun->fTgtRecFlags = RTFUZZTGT_REC_STATE_F_PROCSTATUS;
+ rc = VINF_SUCCESS; /* Just keep using the defaults. */
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Failed to query \"TgtRec\"");
+
+ return rc;
+}
+
+
+/**
+ * Sets up the directories for the given fuzzing run.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param pFuzzRun The fuzzing run to setup the directories for.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterFuzzRunSetupDirectories(PRTFUZZCMDMASTER pThis, PRTFUZZRUN pFuzzRun, PRTERRINFO pErrInfo)
+{
+ /* Create temp directories. */
+ char szTmpDir[RTPATH_MAX];
+ int rc = RTPathJoin(&szTmpDir[0], sizeof(szTmpDir), pThis->pszTmpDir, pFuzzRun->pszId);
+ AssertRC(rc);
+ rc = RTDirCreate(szTmpDir, 0700, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET
+ | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (rc == VERR_ALREADY_EXISTS)
+ {
+ /* Clear the directory. */
+ rc = RTDirRemoveRecursive(szTmpDir, RTDIRRMREC_F_CONTENT_ONLY);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzObsSetTmpDirectory(pFuzzRun->hFuzzObs, szTmpDir);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPathJoin(&szTmpDir[0], sizeof(szTmpDir), pThis->pszResultsDir, pFuzzRun->pszId);
+ AssertRC(rc);
+ rc = RTDirCreate(szTmpDir, 0700, RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_SET
+ | RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_NOT_CRITICAL);
+ if (RT_SUCCESS(rc) || rc == VERR_ALREADY_EXISTS)
+ {
+ rc = RTFuzzObsSetResultDirectory(pFuzzRun->hFuzzObs, szTmpDir);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to set results directory to %s", szTmpDir);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create results directory %s", szTmpDir);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to set temporary directory to %s", szTmpDir);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to create temporary directory %s", szTmpDir);
+
+ return rc;
+}
+
+
+/**
+ * Creates a new fuzzing run with the given ID.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param pszId The ID to use.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterCreateFuzzRunWithId(PRTFUZZCMDMASTER pThis, const char *pszId, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ int rc = VINF_SUCCESS;
+ PRTFUZZRUN pFuzzRun = (PRTFUZZRUN)RTMemAllocZ(sizeof(*pFuzzRun));
+ if (RT_LIKELY(pFuzzRun))
+ {
+ pFuzzRun->pszId = RTStrDup(pszId);
+ if (RT_LIKELY(pFuzzRun->pszId))
+ {
+ rc = rtFuzzCmdMasterFuzzRunProcessTgtRecFlags(pFuzzRun, hJsonRoot, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzObsCreate(&pFuzzRun->hFuzzObs, RTFUZZCTXTYPE_BLOB, pFuzzRun->fTgtRecFlags);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFuzzCmdMasterFuzzRunProcessBinaryCfg(pFuzzRun, hJsonRoot, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCmdMasterFuzzRunProcessArgCfg(pFuzzRun, hJsonRoot, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCmdMasterFuzzRunProcessEnvironment(pFuzzRun, hJsonRoot, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCmdMasterFuzzRunProcessInputSeeds(pFuzzRun, hJsonRoot, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCmdMasterFuzzRunProcessMiscCfg(pFuzzRun, hJsonRoot, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCmdMasterFuzzRunProcessSanitizers(pFuzzRun, hJsonRoot, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCmdMasterFuzzRunSetupDirectories(pThis, pFuzzRun, pErrInfo);
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Start fuzzing. */
+ RTListAppend(&pThis->LstFuzzed, &pFuzzRun->NdFuzzed);
+ rc = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs);
+ if (RT_SUCCESS(rc))
+ {
+ RTTIMESPEC TimeSpec;
+ RTTimeNow(&TimeSpec);
+ RTTimeLocalExplode(&pFuzzRun->TimeCreated, &TimeSpec);
+ pFuzzRun->tsCreatedMs = RTTimeMilliTS();
+ pFuzzRun->fStarted = true;
+ return VINF_SUCCESS;
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to start fuzzing with %Rrc", rc);
+ }
+
+ int rc2 = RTFuzzObsDestroy(pFuzzRun->hFuzzObs);
+ AssertRC(rc2); RT_NOREF(rc2);
+ }
+ }
+
+ RTStrFree(pFuzzRun->pszId);
+ pFuzzRun->pszId = NULL;
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ RTMemFree(pFuzzRun);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_MEMORY, "Request error: Out of memory allocating the fuzzer state");
+
+ return rc;
+}
+
+
+/**
+ * Resolves the fuzzing run from the given ID config item and the given JSON request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pszIdItem The JSON item which contains the ID of the fuzzing run.
+ * @param ppFuzzRun Where to store the pointer to the fuzzing run on success.
+ */
+static int rtFuzzCmdMasterQueryFuzzRunFromJson(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, const char *pszIdItem, PRTERRINFO pErrInfo,
+ PRTFUZZRUN *ppFuzzRun)
+{
+ RTJSONVAL hJsonValId;
+ int rc = RTJsonValueQueryByName(hJsonRoot, pszIdItem, &hJsonValId);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszId = RTJsonValueGetString(hJsonValId);
+ if (pszId)
+ {
+ PRTFUZZRUN pFuzzRun = rtFuzzCmdMasterGetFuzzerById(pThis, pszId);
+ if (pFuzzRun)
+ *ppFuzzRun = pFuzzRun;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NOT_FOUND, "Request error: The ID \"%s\" wasn't found", pszId);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Id\" is not a string value");
+
+ RTJsonValueRelease(hJsonValId);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Id\" value");
+ return rc;
+}
+
+
+/**
+ * Processes the "StartFuzzing" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param hJsonRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterProcessJsonReqStart(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ RTJSONVAL hJsonValId;
+ int rc = RTJsonValueQueryByName(hJsonRoot, "Id", &hJsonValId);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszId = RTJsonValueGetString(hJsonValId);
+ if (pszId)
+ {
+ PRTFUZZRUN pFuzzRun = rtFuzzCmdMasterGetFuzzerById(pThis, pszId);
+ if (!pFuzzRun)
+ rc = rtFuzzCmdMasterCreateFuzzRunWithId(pThis, pszId, hJsonRoot, pErrInfo);
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_ALREADY_EXISTS, "Request error: The ID \"%s\" is already registered", pszId);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Id\" is not a string value");
+
+ RTJsonValueRelease(hJsonValId);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Id\" value");
+ return rc;
+}
+
+
+/**
+ * Processes the "StopFuzzing" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param hJsonValRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterProcessJsonReqStop(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ PRTFUZZRUN pFuzzRun;
+ int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun);
+ if (RT_SUCCESS(rc))
+ {
+ RTListNodeRemove(&pFuzzRun->NdFuzzed);
+ RTFuzzObsExecStop(pFuzzRun->hFuzzObs);
+ RTFuzzObsDestroy(pFuzzRun->hFuzzObs);
+ RTStrFree(pFuzzRun->pszId);
+ RTMemFree(pFuzzRun);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes the "SuspendFuzzing" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param hJsonValRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterProcessJsonReqSuspend(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ PRTFUZZRUN pFuzzRun;
+ int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun);
+ if (RT_SUCCESS(rc))
+ {
+ if (pFuzzRun->fStarted)
+ {
+ rc = RTFuzzObsExecStop(pFuzzRun->hFuzzObs);
+ if (RT_SUCCESS(rc))
+ pFuzzRun->fStarted = false;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Suspending the fuzzing process failed");
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes the "ResumeFuzzing" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param hJsonValRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterProcessJsonReqResume(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ PRTFUZZRUN pFuzzRun;
+ int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pFuzzRun->fStarted)
+ {
+ rc = rtFuzzCmdMasterFuzzRunProcessCfgU32Def(&pFuzzRun->cProcs, "FuzzingProcs", hJsonRoot, pFuzzRun->cProcs, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs);
+ if (RT_SUCCESS(rc))
+ pFuzzRun->fStarted = true;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Resuming the fuzzing process failed");
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Processes the "SaveFuzzingState" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param hJsonValRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterProcessJsonReqSaveState(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ PRTFUZZRUN pFuzzRun;
+ int rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun);
+ if (RT_SUCCESS(rc))
+ {
+ /* Suspend fuzzing, save and resume if not stopped. */
+ if (pFuzzRun->fStarted)
+ {
+ rc = RTFuzzObsExecStop(pFuzzRun->hFuzzObs);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Suspending the fuzzing process failed");
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ RTFUZZCTX hFuzzCtx;
+ rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx);
+ AssertRC(rc);
+
+ void *pvState = NULL;
+ size_t cbState = 0;
+ rc = RTFuzzCtxStateExportToMem(hFuzzCtx, &pvState, &cbState);
+ if (RT_SUCCESS(rc))
+ {
+ /* Encode to base64. */
+ size_t cbStateStr = RTBase64EncodedLength(cbState) + 1;
+ char *pszState = (char *)RTMemAllocZ(cbStateStr);
+ if (pszState)
+ {
+ rc = RTBase64Encode(pvState, cbState, pszState, cbStateStr, &cbStateStr);
+ if (RT_SUCCESS(rc))
+ {
+ /* Strip all new lines from the srting. */
+ size_t offStr = 0;
+ while (offStr < cbStateStr)
+ {
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ char *pszEol = strchr(&pszState[offStr], '\r');
+#else
+ char *pszEol = strchr(&pszState[offStr], '\n');
+#endif
+ if (pszEol)
+ {
+ offStr += pszEol - &pszState[offStr];
+ memmove(pszEol, &pszEol[RTBASE64_EOL_SIZE], cbStateStr - offStr - RTBASE64_EOL_SIZE);
+ cbStateStr -= RTBASE64_EOL_SIZE;
+ }
+ else
+ break;
+ }
+
+ const char s_szState[] = "{ \"State\": %s }";
+ pThis->pszResponse = RTStrAPrintf2(s_szState, pszState);
+ if (RT_UNLIKELY(!pThis->pszResponse))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_BUFFER_OVERFLOW, "Request error: Response data buffer overflow", rc);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to encode the state as a base64 string");
+ RTMemFree(pszState);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_NO_STR_MEMORY, "Request error: Failed to allocate a state string for the response");
+ RTMemFree(pvState);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Exporting the state failed");
+ }
+
+ if (pFuzzRun->fStarted)
+ {
+ int rc2 = RTFuzzObsExecStart(pFuzzRun->hFuzzObs, pFuzzRun->cProcs);
+ if (RT_FAILURE(rc2))
+ rtFuzzCmdMasterErrorRc(pErrInfo, rc2, "Request error: Resuming the fuzzing process failed");
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Queries the statistics for the given fuzzing run and adds the result to the response.
+ *
+ * @returns IPRT static code.
+ * @param pThis The fuzzing master command state.
+ * @param pFuzzRun The fuzzing run.
+ * @param pszIndent Indentation to use.
+ * @param fLast Flags whether this is the last element in the list.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterProcessQueryRunStats(PRTFUZZCMDMASTER pThis, PRTFUZZRUN pFuzzRun,
+ const char *pszIndent, bool fLast, PRTERRINFO pErrInfo)
+{
+ RTFUZZOBSSTATS ObsStats;
+ RTFUZZCTXSTATS CtxStats;
+ RTFUZZCTX hFuzzCtx;
+ RT_ZERO(ObsStats); RT_ZERO(CtxStats);
+
+ int rc = RTFuzzObsQueryCtx(pFuzzRun->hFuzzObs, &hFuzzCtx);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFuzzCtxQueryStats(hFuzzCtx, &CtxStats);
+ RTFuzzCtxRelease(hFuzzCtx);
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = RTFuzzObsQueryStats(pFuzzRun->hFuzzObs, &ObsStats);
+ if (RT_SUCCESS(rc))
+ {
+ const char s_szStatsFmt[] = "%s{ \n"
+ "%s \"Id\": \"%s\"\n"
+ "%s \"TimeCreated\": \"%s\"\n"
+ "%s \"UptimeSec\": %llu\n"
+ "%s \"FuzzedInputsPerSec\": %u\n"
+ "%s \"FuzzedInputs\": %u\n"
+ "%s \"FuzzedInputsHang\": %u\n"
+ "%s \"FuzzedInputsCrash\": %u\n"
+ "%s \"MemoryUsage\": %zu\n"
+ "%s \"CorpusSize\": %llu\n"
+ "%s}%s\n";
+ char aszTime[_1K]; RT_ZERO(aszTime);
+ char aszStats[_4K]; RT_ZERO(aszStats);
+
+ if (RTTimeToString(&pFuzzRun->TimeCreated, aszTime, sizeof(aszTime)))
+ {
+ ssize_t cchStats = RTStrPrintf2(&aszStats[0], sizeof(aszStats),
+ s_szStatsFmt, pszIndent,
+ pszIndent, pFuzzRun->pszId,
+ pszIndent, aszTime,
+ pszIndent, (RTTimeMilliTS() - pFuzzRun->tsCreatedMs) / RT_MS_1SEC_64,
+ pszIndent, ObsStats.cFuzzedInputsPerSec,
+ pszIndent, ObsStats.cFuzzedInputs,
+ pszIndent, ObsStats.cFuzzedInputsHang,
+ pszIndent, ObsStats.cFuzzedInputsCrash,
+ pszIndent, CtxStats.cbMemory,
+ pszIndent, CtxStats.cMutations,
+ pszIndent, fLast ? "" : ",");
+ if (RT_LIKELY(cchStats > 0))
+ {
+ rc = RTStrAAppend(&pThis->pszResponse, &aszStats[0]);
+ if (RT_FAILURE(rc))
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to build statistics response", rc);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_BUFFER_OVERFLOW, "Request error: Response data buffer overflow");
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_BUFFER_OVERFLOW, "Request error: Buffer overflow conerting time to string");
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "Request error: Failed to query fuzzing statistics with %Rrc", rc);
+
+ return rc;
+}
+
+
+/**
+ * Processes the "QueryStats" request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param hJsonValRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterProcessJsonReqQueryStats(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ RTJSONVAL hJsonValId;
+ int rc = RTJsonValueQueryByName(hJsonRoot, "Id", &hJsonValId);
+ if (RT_SUCCESS(rc))
+ {
+ RTJsonValueRelease(hJsonValId);
+ PRTFUZZRUN pFuzzRun;
+ rc = rtFuzzCmdMasterQueryFuzzRunFromJson(pThis, hJsonRoot, "Id", pErrInfo, &pFuzzRun);
+ if (RT_SUCCESS(rc))
+ rc = rtFuzzCmdMasterProcessQueryRunStats(pThis, pFuzzRun, " ",
+ true /*fLast*/, pErrInfo);
+ }
+ else if (rc == VERR_NOT_FOUND)
+ {
+ /* Id is not there, so collect statistics of all running jobs. */
+ rc = RTStrAAppend(&pThis->pszResponse, " [\n");
+ if (RT_SUCCESS(rc))
+ {
+ PRTFUZZRUN pRun = NULL;
+ RTListForEach(&pThis->LstFuzzed, pRun, RTFUZZRUN, NdFuzzed)
+ {
+ bool fLast = RTListNodeIsLast(&pThis->LstFuzzed, &pRun->NdFuzzed);
+ rc = rtFuzzCmdMasterProcessQueryRunStats(pThis, pRun, " ", fLast, pErrInfo);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTStrAAppend(&pThis->pszResponse, " ]\n");
+ }
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't get \"Id\" value");
+
+ return rc;
+}
+
+
+/**
+ * Processes a JSON request.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param hJsonValRoot The root node of the JSON request.
+ * @param pErrInfo Where to store the error information on failure, optional.
+ */
+static int rtFuzzCmdMasterProcessJsonReq(PRTFUZZCMDMASTER pThis, RTJSONVAL hJsonRoot, PRTERRINFO pErrInfo)
+{
+ RTJSONVAL hJsonValReq;
+ int rc = RTJsonValueQueryByName(hJsonRoot, "Request", &hJsonValReq);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszReq = RTJsonValueGetString(hJsonValReq);
+ if (pszReq)
+ {
+ if (!RTStrCmp(pszReq, "StartFuzzing"))
+ rc = rtFuzzCmdMasterProcessJsonReqStart(pThis, hJsonRoot, pErrInfo);
+ else if (!RTStrCmp(pszReq, "StopFuzzing"))
+ rc = rtFuzzCmdMasterProcessJsonReqStop(pThis, hJsonRoot, pErrInfo);
+ else if (!RTStrCmp(pszReq, "SuspendFuzzing"))
+ rc = rtFuzzCmdMasterProcessJsonReqSuspend(pThis, hJsonRoot, pErrInfo);
+ else if (!RTStrCmp(pszReq, "ResumeFuzzing"))
+ rc = rtFuzzCmdMasterProcessJsonReqResume(pThis, hJsonRoot, pErrInfo);
+ else if (!RTStrCmp(pszReq, "SaveFuzzingState"))
+ rc = rtFuzzCmdMasterProcessJsonReqSaveState(pThis, hJsonRoot, pErrInfo);
+ else if (!RTStrCmp(pszReq, "QueryStats"))
+ rc = rtFuzzCmdMasterProcessJsonReqQueryStats(pThis, hJsonRoot, pErrInfo);
+ else if (!RTStrCmp(pszReq, "Shutdown"))
+ pThis->fShutdown = true;
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Request\" contains unknown value \"%s\"", pszReq);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, VERR_JSON_VALUE_INVALID_TYPE, "JSON request malformed: \"Request\" is not a string value");
+
+ RTJsonValueRelease(hJsonValReq);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(pErrInfo, rc, "JSON request malformed: Couldn't find \"Request\" value");
+
+ return rc;
+}
+
+
+/**
+ * Loads a fuzzing configuration for immediate startup from the given file.
+ *
+ * @returns IPRT status code.
+ * @param pThis The fuzzing master command state.
+ * @param pszFuzzCfg The fuzzing config to load.
+ */
+static int rtFuzzCmdMasterFuzzCfgLoadFromFile(PRTFUZZCMDMASTER pThis, const char *pszFuzzCfg)
+{
+ RTJSONVAL hJsonRoot;
+ int rc = RTJsonParseFromFile(&hJsonRoot, pszFuzzCfg, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFuzzCmdMasterProcessJsonReqStart(pThis, hJsonRoot, NULL);
+ RTJsonValueRelease(hJsonRoot);
+ }
+ else
+ rc = rtFuzzCmdMasterErrorRc(NULL, rc, "JSON request malformed: Couldn't load file \"%s\"", pszFuzzCfg);
+
+ return rc;
+}
+
+
+/**
+ * Destroys all running fuzzers for the given master state.
+ *
+ * @param pThis The fuzzing master command state.
+ */
+static void rtFuzzCmdMasterDestroy(PRTFUZZCMDMASTER pThis)
+{
+ RT_NOREF(pThis);
+}
+
+
+/**
+ * Sends an ACK response to the client.
+ *
+ * @param hSocket The socket handle to send the ACK to.
+ * @param pszResponse Additional response data.
+ */
+static void rtFuzzCmdMasterTcpSendAck(RTSOCKET hSocket, const char *pszResponse)
+{
+ const char s_szSucc[] = "{ \"Status\": \"ACK\" }\n";
+ const char s_szSuccResp[] = "{ \"Status\": \"ACK\"\n \"Response\":\n";
+ const char s_szSuccRespClose[] = "\n }\n";
+ if (pszResponse)
+ {
+ RTSGSEG aSegs[3];
+ RTSGBUF SgBuf;
+ aSegs[0].pvSeg = (void *)s_szSuccResp;
+ aSegs[0].cbSeg = sizeof(s_szSuccResp) - 1;
+ aSegs[1].pvSeg = (void *)pszResponse;
+ aSegs[1].cbSeg = strlen(pszResponse);
+ aSegs[2].pvSeg = (void *)s_szSuccRespClose;
+ aSegs[2].cbSeg = sizeof(s_szSuccRespClose) - 1;
+
+ RTSgBufInit(&SgBuf, &aSegs[0], RT_ELEMENTS(aSegs));
+ RTTcpSgWrite(hSocket, &SgBuf);
+ }
+ else
+ RTTcpWrite(hSocket, s_szSucc, sizeof(s_szSucc));
+}
+
+
+/**
+ * Sends an NACK response to the client.
+ *
+ * @param hSocket The socket handle to send the ACK to.
+ * @param pErrInfo Optional error information to send along.
+ */
+static void rtFuzzCmdMasterTcpSendNAck(RTSOCKET hSocket, PRTERRINFO pErrInfo)
+{
+ const char s_szFail[] = "{ \"Status\": \"NACK\" }\n";
+ const char s_szFailInfo[] = "{ \"Status\": \"NACK\"\n \"Information\": \"%s\" }\n";
+
+ if (pErrInfo)
+ {
+ char szTmp[_1K];
+ ssize_t cchResp = RTStrPrintf2(szTmp, sizeof(szTmp), s_szFailInfo, pErrInfo->pszMsg);
+ if (cchResp > 0)
+ RTTcpWrite(hSocket, szTmp, cchResp);
+ else
+ RTTcpWrite(hSocket, s_szFail, strlen(s_szFail));
+ }
+ else
+ RTTcpWrite(hSocket, s_szFail, strlen(s_szFail));
+}
+
+
+/**
+ * TCP server serving callback for a single connection.
+ *
+ * @returns IPRT status code.
+ * @param hSocket The socket handle of the connection.
+ * @param pvUser Opaque user data.
+ */
+static DECLCALLBACK(int) rtFuzzCmdMasterTcpServe(RTSOCKET hSocket, void *pvUser)
+{
+ PRTFUZZCMDMASTER pThis = (PRTFUZZCMDMASTER)pvUser;
+ size_t cbReqMax = _32K;
+ size_t cbReq = 0;
+ uint8_t *pbReq = (uint8_t *)RTMemAllocZ(cbReqMax);
+
+ if (RT_LIKELY(pbReq))
+ {
+ uint8_t *pbCur = pbReq;
+
+ for (;;)
+ {
+ size_t cbThisRead = cbReqMax - cbReq;
+ int rc = RTTcpRead(hSocket, pbCur, cbThisRead, &cbThisRead);
+ if ( RT_SUCCESS(rc)
+ && cbThisRead)
+ {
+ cbReq += cbThisRead;
+
+ /* Check for a zero terminator marking the end of the request. */
+ uint8_t *pbEnd = (uint8_t *)memchr(pbCur, 0, cbThisRead);
+ if (pbEnd)
+ {
+ /* Adjust request size, data coming after the zero terminiator is ignored right now. */
+ cbReq -= cbThisRead - (pbEnd - pbCur) + 1;
+
+ RTJSONVAL hJsonReq;
+ RTERRINFOSTATIC ErrInfo;
+ RTErrInfoInitStatic(&ErrInfo);
+
+ rc = RTJsonParseFromBuf(&hJsonReq, pbReq, cbReq, &ErrInfo.Core);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtFuzzCmdMasterProcessJsonReq(pThis, hJsonReq, &ErrInfo.Core);
+ if (RT_SUCCESS(rc))
+ rtFuzzCmdMasterTcpSendAck(hSocket, pThis->pszResponse);
+ else
+ rtFuzzCmdMasterTcpSendNAck(hSocket, &ErrInfo.Core);
+ RTJsonValueRelease(hJsonReq);
+ }
+ else
+ rtFuzzCmdMasterTcpSendNAck(hSocket, &ErrInfo.Core);
+
+ if (pThis->pszResponse)
+ {
+ RTStrFree(pThis->pszResponse);
+ pThis->pszResponse = NULL;
+ }
+ break;
+ }
+ else if (cbReq == cbReqMax)
+ {
+ /* Try to increase the buffer. */
+ uint8_t *pbReqNew = (uint8_t *)RTMemRealloc(pbReq, cbReqMax + _32K);
+ if (RT_LIKELY(pbReqNew))
+ {
+ cbReqMax += _32K;
+ pbReq = pbReqNew;
+ pbCur = pbReq + cbReq;
+ }
+ else
+ rtFuzzCmdMasterTcpSendNAck(hSocket, NULL);
+ }
+ else
+ pbCur += cbThisRead;
+ }
+ else
+ break;
+ }
+ }
+ else
+ rtFuzzCmdMasterTcpSendNAck(hSocket, NULL);
+
+ if (pbReq)
+ RTMemFree(pbReq);
+
+ return pThis->fShutdown ? VERR_TCP_SERVER_STOP : VINF_SUCCESS;
+}
+
+
+/**
+ * Mainloop for the fuzzing master.
+ *
+ * @returns Process exit code.
+ * @param pThis The fuzzing master command state.
+ * @param pszLoadCfg Initial config to load.
+ */
+static RTEXITCODE rtFuzzCmdMasterRun(PRTFUZZCMDMASTER pThis, const char *pszLoadCfg)
+{
+ if (pszLoadCfg)
+ {
+ int rc = rtFuzzCmdMasterFuzzCfgLoadFromFile(pThis, pszLoadCfg);
+ if (RT_FAILURE(rc))
+ return RTEXITCODE_FAILURE;
+ }
+
+ /* Start up the control server. */
+ int rc = RTTcpServerCreateEx(NULL, pThis->uPort, &pThis->hTcpSrv);
+ if (RT_SUCCESS(rc))
+ {
+ do
+ {
+ rc = RTTcpServerListen(pThis->hTcpSrv, rtFuzzCmdMasterTcpServe, pThis);
+ } while (rc != VERR_TCP_SERVER_STOP);
+ }
+
+ RTTcpServerDestroy(pThis->hTcpSrv);
+ rtFuzzCmdMasterDestroy(pThis);
+ return RTEXITCODE_SUCCESS;
+}
+
+
+RTR3DECL(RTEXITCODE) RTFuzzCmdMaster(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--fuzz-config", 'c', RTGETOPT_REQ_STRING },
+ { "--temp-dir", 't', RTGETOPT_REQ_STRING },
+ { "--results-dir", 'r', RTGETOPT_REQ_STRING },
+ { "--listen-port", 'p', RTGETOPT_REQ_UINT16 },
+ { "--daemonize", 'd', RTGETOPT_REQ_NOTHING },
+ { "--daemonized", 'Z', RTGETOPT_REQ_NOTHING },
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ };
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_SUCCESS(rc))
+ {
+ /* Option variables: */
+ bool fDaemonize = false;
+ bool fDaemonized = false;
+ const char *pszLoadCfg = NULL;
+ RTFUZZCMDMASTER This;
+
+ RTListInit(&This.LstFuzzed);
+ This.hTcpSrv = NIL_RTTCPSERVER;
+ This.uPort = 4242;
+ This.pszTmpDir = NULL;
+ This.pszResultsDir = NULL;
+ This.fShutdown = false;
+ This.pszResponse = NULL;
+
+ /* Argument parsing loop. */
+ bool fContinue = true;
+ do
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ fContinue = false;
+ break;
+
+ case 'c':
+ pszLoadCfg = ValueUnion.psz;
+ break;
+
+ case 'p':
+ This.uPort = ValueUnion.u16;
+ break;
+
+ case 't':
+ This.pszTmpDir = ValueUnion.psz;
+ break;
+
+ case 'r':
+ This.pszResultsDir = ValueUnion.psz;
+ break;
+
+ case 'd':
+ fDaemonize = true;
+ break;
+
+ case 'Z':
+ fDaemonized = true;
+ fDaemonize = false;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ fContinue = false;
+ break;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ fContinue = false;
+ break;
+
+ default:
+ rcExit = RTGetOptPrintError(chOpt, &ValueUnion);
+ fContinue = false;
+ break;
+ }
+ } while (fContinue);
+
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Daemonize ourselves if asked to.
+ */
+ if (fDaemonize)
+ {
+ rc = RTProcDaemonize(papszArgs, "--daemonized");
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize: %Rrc\n", rc);
+ }
+ else
+ rcExit = rtFuzzCmdMasterRun(&This, pszLoadCfg);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/common/ioqueue/ioqueue-aiofile-provider.cpp b/src/VBox/Runtime/common/ioqueue/ioqueue-aiofile-provider.cpp
new file mode 100644
index 00000000..bed7bcbd
--- /dev/null
+++ b/src/VBox/Runtime/common/ioqueue/ioqueue-aiofile-provider.cpp
@@ -0,0 +1,346 @@
+/* $Id: ioqueue-aiofile-provider.cpp $ */
+/** @file
+ * IPRT - I/O queue, Async I/O file provider.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_IOQUEUE
+#include <iprt/ioqueue.h>
+
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+
+#include "internal/ioqueue.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+
+/**
+ * Internal I/O queue provider instance data.
+ */
+typedef struct RTIOQUEUEPROVINT
+{
+ /** The async I/O context handle. */
+ RTFILEAIOCTX hAioCtx;
+ /** Pointer to the array of requests waiting for commit. */
+ PRTFILEAIOREQ pahReqsToCommit;
+ /** Maximum number of requests to wait for commit.. */
+ size_t cReqsToCommitMax;
+ /** Number of requests waiting for commit. */
+ uint32_t cReqsToCommit;
+ /** Array of free cached request handles. */
+ PRTFILEAIOREQ pahReqsFree;
+ /** Maximum number of cached requests. */
+ uint32_t cReqsFreeMax;
+ /** Number of free cached requests. */
+ volatile uint32_t cReqsFree;
+} RTIOQUEUEPROVINT;
+/** Pointer to the internal I/O queue provider instance data. */
+typedef RTIOQUEUEPROVINT *PRTIOQUEUEPROVINT;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnIsSupported} */
+static DECLCALLBACK(bool) rtIoQueueAioFileProv_IsSupported(void)
+{
+ /* The common code/public API already checked for the proper handle type. */
+ /** @todo Check that the file was opened with async I/O enabled on some platforms? */
+ return true;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnQueueInit} */
+static DECLCALLBACK(int) rtIoQueueAioFileProv_QueueInit(RTIOQUEUEPROV hIoQueueProv, uint32_t fFlags,
+ uint32_t cSqEntries, uint32_t cCqEntries)
+{
+ RT_NOREF(fFlags, cCqEntries);
+
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+ int rc = VINF_SUCCESS;
+
+ pThis->cReqsToCommitMax = cSqEntries;
+ pThis->cReqsFreeMax = cSqEntries;
+ pThis->cReqsFree = 0;
+
+ pThis->pahReqsToCommit = (PRTFILEAIOREQ)RTMemAllocZ(cSqEntries * sizeof(PRTFILEAIOREQ));
+ if (RT_LIKELY(pThis->pahReqsToCommit))
+ {
+ pThis->pahReqsFree = (PRTFILEAIOREQ)RTMemAllocZ(cSqEntries * sizeof(PRTFILEAIOREQ));
+ if (RT_LIKELY(pThis->pahReqsFree))
+ {
+ rc = RTFileAioCtxCreate(&pThis->hAioCtx, cSqEntries, RTFILEAIOCTX_FLAGS_WAIT_WITHOUT_PENDING_REQUESTS);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTMemFree(pThis->pahReqsFree);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTMemFree(pThis->pahReqsToCommit);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnQueueDestroy} */
+static DECLCALLBACK(void) rtIoQueueAioFileProv_QueueDestroy(RTIOQUEUEPROV hIoQueueProv)
+{
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+
+ RTFileAioCtxDestroy(pThis->hAioCtx);
+
+ while (pThis->cReqsFree--)
+ {
+ RTFILEAIOREQ hReq = pThis->pahReqsFree[pThis->cReqsFree];
+ RTFileAioReqDestroy(hReq);
+ pThis->pahReqsFree[pThis->cReqsFree] = NULL;
+ }
+
+ RTMemFree(pThis->pahReqsFree);
+ RTMemFree(pThis->pahReqsToCommit);
+ RT_BZERO(pThis, sizeof(*pThis));
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnHandleRegister} */
+static DECLCALLBACK(int) rtIoQueueAioFileProv_HandleRegister(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle)
+{
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+
+ return RTFileAioCtxAssociateWithFile(pThis->hAioCtx, pHandle->u.hFile);
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnHandleDeregister} */
+static DECLCALLBACK(int) rtIoQueueAioFileProv_HandleDeregister(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle)
+{
+ RT_NOREF(hIoQueueProv, pHandle);
+
+ /** @todo For Windows there doesn't seem to be a way to deregister the file handle without reopening the file,
+ *.for all other hosts this is a nop, just like the register method.
+ */
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnReqPrepare} */
+static DECLCALLBACK(int) rtIoQueueAioFileProv_ReqPrepare(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle, RTIOQUEUEOP enmOp,
+ uint64_t off, void *pvBuf, size_t cbBuf, uint32_t fReqFlags,
+ void *pvUser)
+{
+ RT_NOREF(fReqFlags);
+
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+
+ /* Try to grab a free request structure from the cache. */
+ RTFILEAIOREQ hReq = NIL_RTFILEAIOREQ;
+ int rc = VINF_SUCCESS;
+ uint32_t cReqsFree = ASMAtomicReadU32(&pThis->cReqsFree);
+ if (cReqsFree)
+ {
+ do
+ {
+ cReqsFree = ASMAtomicReadU32(&pThis->cReqsFree);
+ hReq = pThis->pahReqsFree[pThis->cReqsFree - 1];
+ } while (!ASMAtomicCmpXchgU32(&pThis->cReqsFree, cReqsFree - 1, cReqsFree));
+ }
+ else
+ rc = RTFileAioReqCreate(&hReq);
+
+ if (RT_SUCCESS(rc))
+ {
+ switch (enmOp)
+ {
+ case RTIOQUEUEOP_READ:
+ rc = RTFileAioReqPrepareRead(hReq, pHandle->u.hFile, (RTFOFF)off, pvBuf, cbBuf, pvUser);
+ break;
+ case RTIOQUEUEOP_WRITE:
+ rc = RTFileAioReqPrepareWrite(hReq, pHandle->u.hFile, (RTFOFF)off, pvBuf, cbBuf, pvUser);
+ break;
+ case RTIOQUEUEOP_SYNC:
+ rc = RTFileAioReqPrepareFlush(hReq, pHandle->u.hFile, pvUser);
+ break;
+ default:
+ AssertMsgFailedReturn(("Invalid I/O queue operation: %d\n", enmOp), VERR_INTERNAL_ERROR);
+ }
+
+ if (RT_SUCCESS(rc))
+ pThis->pahReqsToCommit[pThis->cReqsToCommit++] = hReq;
+ else
+ {
+ int rc2 = RTFileAioReqDestroy(hReq);
+ Assert(rc2); RT_NOREF(rc2);
+ }
+ }
+
+ return rc;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnCommit} */
+static DECLCALLBACK(int) rtIoQueueAioFileProv_Commit(RTIOQUEUEPROV hIoQueueProv, uint32_t *pcReqsCommitted)
+{
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+
+ int rc = RTFileAioCtxSubmit(pThis->hAioCtx, pThis->pahReqsToCommit, pThis->cReqsToCommit);
+ if (RT_SUCCESS(rc))
+ {
+ *pcReqsCommitted = pThis->cReqsToCommit;
+ pThis->cReqsToCommit = 0;
+ }
+
+ return rc;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnEvtWait} */
+static DECLCALLBACK(int) rtIoQueueAioFileProv_EvtWait(RTIOQUEUEPROV hIoQueueProv, PRTIOQUEUECEVT paCEvt, uint32_t cCEvt,
+ uint32_t cMinWait, uint32_t *pcCEvt, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+ int rc = VINF_SUCCESS;
+ uint32_t idxCEvt = 0;
+
+ while ( RT_SUCCESS(rc)
+ && cMinWait
+ && cCEvt)
+ {
+ RTFILEAIOREQ ahReqs[64];
+ uint32_t cReqsCompleted = 0;
+
+ rc = RTFileAioCtxWait(pThis->hAioCtx, cMinWait, RT_INDEFINITE_WAIT,
+ &ahReqs[0], RT_MIN(RT_ELEMENTS(ahReqs), cCEvt), &cReqsCompleted);
+ if (RT_SUCCESS(rc))
+ {
+ for (unsigned i = 0; i < cReqsCompleted; i++)
+ {
+ RTFILEAIOREQ hReq = ahReqs[i];
+
+ paCEvt[idxCEvt].rcReq = RTFileAioReqGetRC(hReq, &paCEvt[idxCEvt].cbXfered);
+ paCEvt[idxCEvt].pvUser = RTFileAioReqGetUser(hReq);
+ idxCEvt++;
+
+ /* Try to insert the free request into the cache. */
+ uint32_t cReqsFree = ASMAtomicReadU32(&pThis->cReqsFree);
+ if (cReqsFree < pThis->cReqsFreeMax)
+ {
+ do
+ {
+ cReqsFree = ASMAtomicReadU32(&pThis->cReqsFree);
+ pThis->pahReqsFree[pThis->cReqsFree] = hReq;
+ } while (!ASMAtomicCmpXchgU32(&pThis->cReqsFree, cReqsFree + 1, cReqsFree));
+ }
+ else
+ rc = RTFileAioReqDestroy(hReq);
+ }
+
+ cCEvt -= cReqsCompleted;
+ cMinWait -= RT_MIN(cMinWait, cReqsCompleted);
+ }
+ }
+
+ *pcCEvt = idxCEvt;
+ return rc;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnEvtWaitWakeup} */
+static DECLCALLBACK(int) rtIoQueueAioFileProv_EvtWaitWakeup(RTIOQUEUEPROV hIoQueueProv)
+{
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+
+ return RTFileAioCtxWakeup(pThis->hAioCtx);
+}
+
+
+/**
+ * Async file I/O queue provider virtual method table.
+ */
+RT_DECL_DATA_CONST(RTIOQUEUEPROVVTABLE const) g_RTIoQueueAioFileProv =
+{
+ /** uVersion */
+ RTIOQUEUEPROVVTABLE_VERSION,
+ /** pszId */
+ "AioFile",
+ /** cbIoQueueProv */
+ sizeof(RTIOQUEUEPROVINT),
+ /** enmHnd */
+ RTHANDLETYPE_FILE,
+ /** fFlags */
+ 0,
+ /** pfnIsSupported */
+ rtIoQueueAioFileProv_IsSupported,
+ /** pfnQueueInit */
+ rtIoQueueAioFileProv_QueueInit,
+ /** pfnQueueDestroy */
+ rtIoQueueAioFileProv_QueueDestroy,
+ /** pfnHandleRegister */
+ rtIoQueueAioFileProv_HandleRegister,
+ /** pfnHandleDeregister */
+ rtIoQueueAioFileProv_HandleDeregister,
+ /** pfnReqPrepare */
+ rtIoQueueAioFileProv_ReqPrepare,
+ /** pfnReqPrepareSg */
+ NULL,
+ /** pfnCommit */
+ rtIoQueueAioFileProv_Commit,
+ /** pfnEvtWait */
+ rtIoQueueAioFileProv_EvtWait,
+ /** pfnEvtWaitWakeup */
+ rtIoQueueAioFileProv_EvtWaitWakeup,
+ /** uEndMarker */
+ RTIOQUEUEPROVVTABLE_VERSION
+};
+
diff --git a/src/VBox/Runtime/common/ioqueue/ioqueue-stdfile-provider.cpp b/src/VBox/Runtime/common/ioqueue/ioqueue-stdfile-provider.cpp
new file mode 100644
index 00000000..b3e32105
--- /dev/null
+++ b/src/VBox/Runtime/common/ioqueue/ioqueue-stdfile-provider.cpp
@@ -0,0 +1,548 @@
+/* $Id: ioqueue-stdfile-provider.cpp $ */
+/** @file
+ * IPRT - I/O queue, Standard file provider.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_IOQUEUE
+#include <iprt/ioqueue.h>
+
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#include "internal/ioqueue.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** The I/O queue worker thread needs to wake up the waiting thread when requests completed. */
+#define RTIOQUEUE_STDFILE_PROV_STATE_F_EVTWAIT_NEED_WAKEUP RT_BIT(0)
+/** The waiting thread was interrupted by the external wakeup call. */
+#define RTIOQUEUE_STDFILE_PROV_STATE_F_EVTWAIT_INTR RT_BIT(1)
+#define RTIOQUEUE_STDFILE_PROV_STATE_F_EVTWAIT_INTR_BIT 1
+/** The I/O queue worker thread needs to be woken up to process new requests. */
+#define RTIOQUEUE_STDFILE_PROV_STATE_F_WORKER_NEED_WAKEUP RT_BIT(2)
+#define RTIOQUEUE_STDFILE_PROV_STATE_F_WORKER_NEED_WAKEUP_BIT 2
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+
+/**
+ * Submission queue entry.
+ */
+typedef struct RTIOQUEUESSQENTRY
+{
+ /** The file to work on. */
+ RTFILE hFile;
+ /** I/O operation. */
+ RTIOQUEUEOP enmOp;
+ /** Start offset. */
+ uint64_t off;
+ /** Additional request flags. */
+ uint32_t fReqFlags;
+ /** Size of the request. */
+ size_t cbReq;
+ /** Opaque user data passed on completion. */
+ void *pvUser;
+ /** Flag whether this is a S/G or standard request. */
+ bool fSg;
+ /** Type dependent data. */
+ union
+ {
+ /** Pointer to buffer for non S/G requests. */
+ void *pvBuf;
+ /** Pointer to S/G buffer. */
+ PCRTSGBUF pSgBuf;
+ } u;
+} RTIOQUEUESSQENTRY;
+/** Pointer to a submission queue entry. */
+typedef RTIOQUEUESSQENTRY *PRTIOQUEUESSQENTRY;
+/** Pointer to a constant submission queue entry. */
+typedef const RTIOQUEUESSQENTRY *PCRTIOQUEUESSQENTRY;
+
+
+/**
+ * Internal I/O queue provider instance data.
+ */
+typedef struct RTIOQUEUEPROVINT
+{
+ /** Size of the submission queue in entries. */
+ uint32_t cSqEntries;
+ /** Size of the completion queue in entries. */
+ uint32_t cCqEntries;
+ /** Pointer to the submission queue base. */
+ PRTIOQUEUESSQENTRY paSqEntryBase;
+ /** Submission queue producer index. */
+ volatile uint32_t idxSqProd;
+ /** Submission queue producer value for any uncommitted requests. */
+ uint32_t idxSqProdUncommit;
+ /** Submission queue consumer index. */
+ volatile uint32_t idxSqCons;
+ /** Pointer to the completion queue base. */
+ PRTIOQUEUECEVT paCqEntryBase;
+ /** Completion queue producer index. */
+ volatile uint32_t idxCqProd;
+ /** Completion queue consumer index. */
+ volatile uint32_t idxCqCons;
+ /** Various state flags for synchronizing the worker thread with other participants. */
+ volatile uint32_t fState;
+ /** The worker thread handle. */
+ RTTHREAD hThrdWork;
+ /** Event semaphore the worker thread waits on for work. */
+ RTSEMEVENT hSemEvtWorker;
+ /** Event semaphore the caller waits for completion events. */
+ RTSEMEVENT hSemEvtWaitEvts;
+ /** Flag whether to shutdown the worker thread. */
+ volatile bool fShutdown;
+} RTIOQUEUEPROVINT;
+/** Pointer to the internal I/O queue provider instance data. */
+typedef RTIOQUEUEPROVINT *PRTIOQUEUEPROVINT;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Processes the given submission queue entry and reports back the result in the completion queue.
+ *
+ * @param pSqEntry The submission queue entry to process.
+ * @param pCqEntry The comppletion queue entry to store the result in.
+ */
+static void rtIoQueueStdFileProv_SqEntryProcess(PCRTIOQUEUESSQENTRY pSqEntry, PRTIOQUEUECEVT pCqEntry)
+{
+ int rcReq = VINF_SUCCESS;
+
+ switch (pSqEntry->enmOp)
+ {
+ case RTIOQUEUEOP_READ:
+ if (!pSqEntry->fSg)
+ rcReq = RTFileReadAt(pSqEntry->hFile, pSqEntry->off, pSqEntry->u.pvBuf, pSqEntry->cbReq, NULL);
+ else
+ {
+ RTSGBUF SgBuf;
+ RTSgBufClone(&SgBuf, pSqEntry->u.pSgBuf);
+ rcReq = RTFileSgReadAt(pSqEntry->hFile, pSqEntry->off, &SgBuf, pSqEntry->cbReq, NULL);
+ }
+ break;
+ case RTIOQUEUEOP_WRITE:
+ if (!pSqEntry->fSg)
+ rcReq = RTFileWriteAt(pSqEntry->hFile, pSqEntry->off, pSqEntry->u.pvBuf, pSqEntry->cbReq, NULL);
+ else
+ {
+ RTSGBUF SgBuf;
+ RTSgBufClone(&SgBuf, pSqEntry->u.pSgBuf);
+ rcReq = RTFileSgWriteAt(pSqEntry->hFile, pSqEntry->off, &SgBuf, pSqEntry->cbReq, NULL);
+ }
+ break;
+ case RTIOQUEUEOP_SYNC:
+ rcReq = RTFileFlush(pSqEntry->hFile);
+ break;
+ default:
+ AssertMsgFailedReturnVoid(("Invalid I/O queue operation: %d\n", pSqEntry->enmOp));
+ }
+
+ /* Write the result back into the completion queue. */
+ pCqEntry->rcReq = rcReq;
+ pCqEntry->pvUser = pSqEntry->pvUser;
+ pCqEntry->cbXfered = RT_SUCCESS(rcReq) ? pSqEntry->cbReq : 0;
+}
+
+
+/**
+ * The main I/O queue worker loop which processes the incoming I/O requests.
+ */
+static DECLCALLBACK(int) rtIoQueueStdFileProv_WorkerLoop(RTTHREAD hThrdSelf, void *pvUser)
+{
+ PRTIOQUEUEPROVINT pThis = (PRTIOQUEUEPROVINT)pvUser;
+
+ /* Signal that we started up. */
+ int rc = RTThreadUserSignal(hThrdSelf);
+ AssertRC(rc);
+
+ while (!ASMAtomicReadBool(&pThis->fShutdown))
+ {
+ /* Wait for some work. */
+ ASMAtomicOrU32(&pThis->fState, RTIOQUEUE_STDFILE_PROV_STATE_F_WORKER_NEED_WAKEUP);
+ uint32_t idxSqProd = ASMAtomicReadU32(&pThis->idxSqProd);
+ uint32_t idxSqCons = ASMAtomicReadU32(&pThis->idxSqCons);
+ uint32_t idxCqCons = ASMAtomicReadU32(&pThis->idxCqCons);
+
+ if (idxSqCons == idxSqProd)
+ {
+ rc = RTSemEventWait(pThis->hSemEvtWorker, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+
+ idxSqProd = ASMAtomicReadU32(&pThis->idxSqProd);
+ idxSqCons = ASMAtomicReadU32(&pThis->idxSqCons);
+ idxCqCons = ASMAtomicReadU32(&pThis->idxCqCons);
+ }
+
+ ASMAtomicBitTestAndClear(&pThis->fState, RTIOQUEUE_STDFILE_PROV_STATE_F_WORKER_NEED_WAKEUP_BIT);
+
+ /* Process all requests. */
+ uint32_t cCqFree = 0;
+ if (idxCqCons > pThis->idxCqProd)
+ cCqFree = pThis->cCqEntries - (pThis->cCqEntries - idxCqCons) - pThis->idxCqProd;
+ else
+ cCqFree = pThis->cCqEntries - pThis->idxCqProd - idxCqCons;
+ do
+ {
+ while ( idxSqCons != idxSqProd
+ && cCqFree)
+ {
+ PCRTIOQUEUESSQENTRY pSqEntry = &pThis->paSqEntryBase[idxSqCons];
+ PRTIOQUEUECEVT pCqEntry = &pThis->paCqEntryBase[pThis->idxCqProd];
+
+ rtIoQueueStdFileProv_SqEntryProcess(pSqEntry, pCqEntry);
+ ASMWriteFence();
+
+ idxSqCons = (idxSqCons + 1) % pThis->cSqEntries;
+ cCqFree--;
+ pThis->idxCqProd = (pThis->idxCqProd + 1) % pThis->cCqEntries;
+ ASMAtomicWriteU32(&pThis->idxSqCons, idxSqCons);
+ ASMWriteFence();
+ if (ASMAtomicReadU32(&pThis->fState) & RTIOQUEUE_STDFILE_PROV_STATE_F_EVTWAIT_NEED_WAKEUP)
+ {
+ rc = RTSemEventSignal(pThis->hSemEvtWaitEvts);
+ AssertRC(rc);
+ }
+ }
+
+ idxSqProd = ASMAtomicReadU32(&pThis->idxSqProd);
+ } while ( idxSqCons != idxSqProd
+ && cCqFree);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnIsSupported} */
+static DECLCALLBACK(bool) rtIoQueueStdFileProv_IsSupported(void)
+{
+ /* The common code/public API already checked for the proper handle type. */
+ return true;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnQueueInit} */
+static DECLCALLBACK(int) rtIoQueueStdFileProv_QueueInit(RTIOQUEUEPROV hIoQueueProv, uint32_t fFlags,
+ uint32_t cSqEntries, uint32_t cCqEntries)
+{
+ RT_NOREF(fFlags);
+
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+ int rc = VINF_SUCCESS;
+
+ cSqEntries++;
+ cCqEntries++;
+
+ pThis->cSqEntries = cSqEntries;
+ pThis->cCqEntries = cCqEntries;
+ pThis->idxSqProd = 0;
+ pThis->idxSqProdUncommit = 0;
+ pThis->idxSqCons = 0;
+ pThis->idxCqProd = 0;
+ pThis->idxCqCons = 0;
+ pThis->fShutdown = false;
+ pThis->fState = 0;
+
+ pThis->paSqEntryBase = (PRTIOQUEUESSQENTRY)RTMemAllocZ(cSqEntries * sizeof(RTIOQUEUESSQENTRY));
+ if (RT_LIKELY(pThis->paSqEntryBase))
+ {
+ pThis->paCqEntryBase = (PRTIOQUEUECEVT)RTMemAllocZ(cCqEntries * sizeof(RTIOQUEUECEVT));
+ if (RT_LIKELY(pThis->paSqEntryBase))
+ {
+ rc = RTSemEventCreate(&pThis->hSemEvtWorker);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventCreate(&pThis->hSemEvtWaitEvts);
+ if (RT_SUCCESS(rc))
+ {
+ /* Spin up the worker thread. */
+ rc = RTThreadCreate(&pThis->hThrdWork, rtIoQueueStdFileProv_WorkerLoop, pThis, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE,
+ "IoQ-StdFile");
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTThreadUserWait(pThis->hThrdWork, 10 * RT_MS_1SEC);
+ AssertRC(rc);
+
+ return VINF_SUCCESS;
+ }
+
+ RTSemEventDestroy(pThis->hSemEvtWaitEvts);
+ }
+
+ RTSemEventDestroy(pThis->hSemEvtWorker);
+ }
+
+ RTMemFree(pThis->paCqEntryBase);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTMemFree(pThis->paSqEntryBase);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnQueueDestroy} */
+static DECLCALLBACK(void) rtIoQueueStdFileProv_QueueDestroy(RTIOQUEUEPROV hIoQueueProv)
+{
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+
+ ASMAtomicXchgBool(&pThis->fShutdown, true);
+ RTSemEventSignal(pThis->hSemEvtWorker);
+
+ int rc = RTThreadWait(pThis->hThrdWork, 60 * RT_MS_1SEC, NULL);
+ AssertRC(rc);
+
+ RTSemEventDestroy(pThis->hSemEvtWaitEvts);
+ RTSemEventDestroy(pThis->hSemEvtWorker);
+ RTMemFree(pThis->paCqEntryBase);
+ RTMemFree(pThis->paSqEntryBase);
+ RT_BZERO(pThis, sizeof(*pThis));
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnHandleRegister} */
+static DECLCALLBACK(int) rtIoQueueStdFileProv_HandleRegister(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle)
+{
+ RT_NOREF(hIoQueueProv, pHandle);
+
+ /* Nothing to do here. */
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnHandleDeregister} */
+static DECLCALLBACK(int) rtIoQueueStdFileProv_HandleDeregister(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle)
+{
+ RT_NOREF(hIoQueueProv, pHandle);
+
+ /* Nothing to do here. */
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnReqPrepare} */
+static DECLCALLBACK(int) rtIoQueueStdFileProv_ReqPrepare(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle, RTIOQUEUEOP enmOp,
+ uint64_t off, void *pvBuf, size_t cbBuf, uint32_t fReqFlags,
+ void *pvUser)
+{
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+ PRTIOQUEUESSQENTRY pSqEntry = &pThis->paSqEntryBase[pThis->idxSqProdUncommit];
+
+ pSqEntry->hFile = pHandle->u.hFile;
+ pSqEntry->enmOp = enmOp;
+ pSqEntry->off = off;
+ pSqEntry->fReqFlags = fReqFlags;
+ pSqEntry->cbReq = cbBuf;
+ pSqEntry->pvUser = pvUser;
+ pSqEntry->fSg = false;
+ pSqEntry->u.pvBuf = pvBuf;
+
+ pThis->idxSqProdUncommit = (pThis->idxSqProdUncommit + 1) % pThis->cSqEntries;
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnReqPrepareSg} */
+static DECLCALLBACK(int) rtIoQueueStdFileProv_ReqPrepareSg(RTIOQUEUEPROV hIoQueueProv, PCRTHANDLE pHandle, RTIOQUEUEOP enmOp,
+ uint64_t off, PCRTSGBUF pSgBuf, size_t cbSg, uint32_t fReqFlags,
+ void *pvUser)
+{
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+ PRTIOQUEUESSQENTRY pSqEntry = &pThis->paSqEntryBase[pThis->idxSqProdUncommit];
+
+ pSqEntry->hFile = pHandle->u.hFile;
+ pSqEntry->enmOp = enmOp;
+ pSqEntry->off = off;
+ pSqEntry->fReqFlags = fReqFlags;
+ pSqEntry->cbReq = cbSg;
+ pSqEntry->pvUser = pvUser;
+ pSqEntry->fSg = true;
+ pSqEntry->u.pSgBuf = pSgBuf;
+
+ pThis->idxSqProdUncommit = (pThis->idxSqProdUncommit + 1) % pThis->cSqEntries;
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnCommit} */
+static DECLCALLBACK(int) rtIoQueueStdFileProv_Commit(RTIOQUEUEPROV hIoQueueProv, uint32_t *pcReqsCommitted)
+{
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+
+ if (pThis->idxSqProd > pThis->idxSqProdUncommit)
+ *pcReqsCommitted = pThis->cSqEntries - pThis->idxSqProd + pThis->idxSqProdUncommit;
+ else
+ *pcReqsCommitted = pThis->idxSqProdUncommit - pThis->idxSqProd;
+
+ ASMWriteFence();
+ ASMAtomicWriteU32(&pThis->idxSqProd, pThis->idxSqProdUncommit);
+ return RTSemEventSignal(pThis->hSemEvtWorker);
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnEvtWait} */
+static DECLCALLBACK(int) rtIoQueueStdFileProv_EvtWait(RTIOQUEUEPROV hIoQueueProv, PRTIOQUEUECEVT paCEvt, uint32_t cCEvt,
+ uint32_t cMinWait, uint32_t *pcCEvt, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+ int rc = VINF_SUCCESS;
+ uint32_t idxCEvt = 0;
+
+ while ( RT_SUCCESS(rc)
+ && cMinWait
+ && cCEvt)
+ {
+ ASMAtomicOrU32(&pThis->fState, RTIOQUEUE_STDFILE_PROV_STATE_F_EVTWAIT_NEED_WAKEUP);
+ uint32_t idxCqProd = ASMAtomicReadU32(&pThis->idxCqProd);
+ uint32_t idxCqCons = ASMAtomicReadU32(&pThis->idxCqCons);
+
+ if (idxCqCons == idxCqProd)
+ {
+ rc = RTSemEventWait(pThis->hSemEvtWaitEvts, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ if (ASMAtomicBitTestAndClear(&pThis->fState, RTIOQUEUE_STDFILE_PROV_STATE_F_EVTWAIT_INTR_BIT))
+ {
+ rc = VERR_INTERRUPTED;
+ ASMAtomicBitTestAndClear(&pThis->fState, RTIOQUEUE_STDFILE_PROV_STATE_F_WORKER_NEED_WAKEUP_BIT);
+ break;
+ }
+
+ idxCqProd = ASMAtomicReadU32(&pThis->idxCqProd);
+ idxCqCons = ASMAtomicReadU32(&pThis->idxCqCons);
+ }
+
+ ASMAtomicBitTestAndClear(&pThis->fState, RTIOQUEUE_STDFILE_PROV_STATE_F_WORKER_NEED_WAKEUP_BIT);
+
+ /* Process all requests. */
+ while ( idxCqCons != idxCqProd
+ && cCEvt)
+ {
+ PRTIOQUEUECEVT pCqEntry = &pThis->paCqEntryBase[idxCqCons];
+
+ paCEvt[idxCEvt].rcReq = pCqEntry->rcReq;
+ paCEvt[idxCEvt].pvUser = pCqEntry->pvUser;
+ paCEvt[idxCEvt].cbXfered = pCqEntry->cbXfered;
+ ASMReadFence();
+
+ idxCEvt++;
+ cCEvt--;
+ cMinWait--;
+
+ idxCqCons = (idxCqCons + 1) % pThis->cCqEntries;
+ pThis->idxCqCons = (pThis->idxCqCons + 1) % pThis->cCqEntries;
+ ASMWriteFence();
+ }
+ }
+
+ *pcCEvt = idxCEvt;
+ return rc;
+}
+
+
+/** @interface_method_impl{RTIOQUEUEPROVVTABLE,pfnEvtWaitWakeup} */
+static DECLCALLBACK(int) rtIoQueueStdFileProv_EvtWaitWakeup(RTIOQUEUEPROV hIoQueueProv)
+{
+ PRTIOQUEUEPROVINT pThis = hIoQueueProv;
+
+ ASMAtomicOrU32(&pThis->fState, RTIOQUEUE_STDFILE_PROV_STATE_F_EVTWAIT_INTR);
+ return RTSemEventSignal(pThis->hSemEvtWaitEvts);
+}
+
+
+/**
+ * Standard file I/O queue provider virtual method table.
+ */
+RT_DECL_DATA_CONST(RTIOQUEUEPROVVTABLE const) g_RTIoQueueStdFileProv =
+{
+ /** uVersion */
+ RTIOQUEUEPROVVTABLE_VERSION,
+ /** pszId */
+ "StdFile",
+ /** cbIoQueueProv */
+ sizeof(RTIOQUEUEPROVINT),
+ /** enmHnd */
+ RTHANDLETYPE_FILE,
+ /** fFlags */
+ 0,
+ /** pfnIsSupported */
+ rtIoQueueStdFileProv_IsSupported,
+ /** pfnQueueInit */
+ rtIoQueueStdFileProv_QueueInit,
+ /** pfnQueueDestroy */
+ rtIoQueueStdFileProv_QueueDestroy,
+ /** pfnHandleRegister */
+ rtIoQueueStdFileProv_HandleRegister,
+ /** pfnHandleDeregister */
+ rtIoQueueStdFileProv_HandleDeregister,
+ /** pfnReqPrepare */
+ rtIoQueueStdFileProv_ReqPrepare,
+ /** pfnReqPrepareSg */
+ rtIoQueueStdFileProv_ReqPrepareSg,
+ /** pfnCommit */
+ rtIoQueueStdFileProv_Commit,
+ /** pfnEvtWait */
+ rtIoQueueStdFileProv_EvtWait,
+ /** pfnEvtWaitWakeup */
+ rtIoQueueStdFileProv_EvtWaitWakeup,
+ /** uEndMarker */
+ RTIOQUEUEPROVVTABLE_VERSION
+};
+
diff --git a/src/VBox/Runtime/common/ioqueue/ioqueuebase.cpp b/src/VBox/Runtime/common/ioqueue/ioqueuebase.cpp
new file mode 100644
index 00000000..f2bcb9da
--- /dev/null
+++ b/src/VBox/Runtime/common/ioqueue/ioqueuebase.cpp
@@ -0,0 +1,297 @@
+/* $Id: ioqueuebase.cpp $ */
+/** @file
+ * IPRT - I/O queue, Base/Public API.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_IOQUEUE
+#include <iprt/ioqueue.h>
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+
+#include "internal/ioqueue.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Internal I/O queue instance data.
+ */
+typedef struct RTIOQUEUEINT
+{
+ /** Magic identifying the I/O queue structure. */
+ uint32_t u32Magic;
+ /** Pointer to the provider vtable. */
+ PCRTIOQUEUEPROVVTABLE pVTbl;
+ /** I/O queue provider instance handle. */
+ RTIOQUEUEPROV hIoQueueProv;
+ /** Maximum number of submission queue entries - constant. */
+ uint32_t cSqEntries;
+ /** Maximum number of completion queue entries - constant. */
+ uint32_t cCqEntries;
+ /** Number of currently committed and not completed requests. */
+ volatile uint32_t cReqsCommitted;
+ /** Number of prepared requests. */
+ volatile uint32_t cReqsPrepared;
+ /** Start of the provider specific instance data - vvariable in size. */
+ uint8_t abInst[1];
+} RTIOQUEUEINT;
+/** Pointer to the internal I/O queue instance data. */
+typedef RTIOQUEUEINT *PRTIOQUEUEINT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Array of I/O queue providers we know about, order is important for each type.
+ * The best suited ones for each platform should come first.
+ */
+static PCRTIOQUEUEPROVVTABLE g_apIoQueueProviders[] =
+{
+#if defined(RT_OS_LINUX)
+ &g_RTIoQueueLnxIoURingProv,
+#endif
+#ifndef RT_OS_OS2
+ &g_RTIoQueueAioFileProv,
+#endif
+ &g_RTIoQueueStdFileProv
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+RTDECL(PCRTIOQUEUEPROVVTABLE) RTIoQueueProviderGetBestForHndType(RTHANDLETYPE enmHnd)
+{
+ /* Go through the array and pick the first supported provider for the given handle type. */
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apIoQueueProviders); i++)
+ {
+ PCRTIOQUEUEPROVVTABLE pIoQueueProv = g_apIoQueueProviders[i];
+ if ( pIoQueueProv->enmHnd == enmHnd
+ && pIoQueueProv->pfnIsSupported())
+ return pIoQueueProv;
+ }
+
+ return NULL;
+}
+
+
+RTDECL(PCRTIOQUEUEPROVVTABLE) RTIoQueueProviderGetById(const char *pszId)
+{
+ for (unsigned i = 0; i < RT_ELEMENTS(g_apIoQueueProviders); i++)
+ {
+ PCRTIOQUEUEPROVVTABLE pIoQueueProv = g_apIoQueueProviders[i];
+ if (!strcmp(pIoQueueProv->pszId, pszId))
+ return pIoQueueProv;
+ }
+
+ return NULL;
+}
+
+
+RTDECL(int) RTIoQueueCreate(PRTIOQUEUE phIoQueue, PCRTIOQUEUEPROVVTABLE pProvVTable,
+ uint32_t fFlags, uint32_t cSqEntries, uint32_t cCqEntries)
+{
+ AssertPtrReturn(phIoQueue, VERR_INVALID_POINTER);
+ AssertPtrReturn(pProvVTable, VERR_INVALID_POINTER);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+ AssertReturn(cSqEntries > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(cCqEntries > 0, VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PRTIOQUEUEINT pThis = (PRTIOQUEUEINT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTIOQUEUEINT, abInst[pProvVTable->cbIoQueueProv]));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->pVTbl = pProvVTable;
+ pThis->hIoQueueProv = (RTIOQUEUEPROV)&pThis->abInst[0];
+ pThis->cSqEntries = cSqEntries;
+ pThis->cCqEntries = cCqEntries;
+ pThis->cReqsCommitted = 0;
+ pThis->cReqsPrepared = 0;
+
+ rc = pThis->pVTbl->pfnQueueInit(pThis->hIoQueueProv, fFlags, cSqEntries, cCqEntries);
+ if (RT_SUCCESS(rc))
+ {
+ *phIoQueue = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTIoQueueDestroy(RTIOQUEUE hIoQueue)
+{
+ PRTIOQUEUEINT pThis = hIoQueue;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(ASMAtomicReadU32(&pThis->cReqsCommitted) == 0, VERR_IOQUEUE_BUSY);
+
+ pThis->pVTbl->pfnQueueDestroy(pThis->hIoQueueProv);
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTIoQueueHandleRegister(RTIOQUEUE hIoQueue, PCRTHANDLE pHandle)
+{
+ PRTIOQUEUEINT pThis = hIoQueue;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ /** @todo Efficiently check that handle wasn't registered previously. */
+ return pThis->pVTbl->pfnHandleRegister(pThis->hIoQueueProv, pHandle);
+}
+
+
+RTDECL(int) RTIoQueueHandleDeregister(RTIOQUEUE hIoQueue, PCRTHANDLE pHandle)
+{
+ PRTIOQUEUEINT pThis = hIoQueue;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ /** @todo Efficiently check that handle was registered previously. */
+ return pThis->pVTbl->pfnHandleDeregister(pThis->hIoQueueProv, pHandle);
+}
+
+
+RTDECL(int) RTIoQueueRequestPrepare(RTIOQUEUE hIoQueue, PCRTHANDLE pHandle, RTIOQUEUEOP enmOp,
+ uint64_t off, void *pvBuf, size_t cbBuf, uint32_t fReqFlags,
+ void *pvUser)
+{
+ PRTIOQUEUEINT pThis = hIoQueue;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pHandle->enmType == pThis->pVTbl->enmHnd, VERR_INVALID_HANDLE);
+
+ /** @todo Efficiently check that handle was registered previously. */
+ int rc = pThis->pVTbl->pfnReqPrepare(pThis->hIoQueueProv, pHandle, enmOp, off, pvBuf, cbBuf,
+ fReqFlags, pvUser);
+ if (RT_SUCCESS(rc))
+ ASMAtomicIncU32(&pThis->cReqsPrepared);
+
+ return rc;
+}
+
+
+RTDECL(int) RTIoQueueRequestPrepareSg(RTIOQUEUE hIoQueue, PCRTHANDLE pHandle, RTIOQUEUEOP enmOp,
+ uint64_t off, PCRTSGBUF pSgBuf, size_t cbSg, uint32_t fReqFlags,
+ void *pvUser)
+{
+ PRTIOQUEUEINT pThis = hIoQueue;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pHandle->enmType == pThis->pVTbl->enmHnd, VERR_INVALID_HANDLE);
+
+ /** @todo Efficiently check that handle was registered previously. */
+ int rc = pThis->pVTbl->pfnReqPrepareSg(pThis->hIoQueueProv, pHandle, enmOp, off, pSgBuf, cbSg,
+ fReqFlags, pvUser);
+ if (RT_SUCCESS(rc))
+ ASMAtomicIncU32(&pThis->cReqsPrepared);
+
+ return rc;
+}
+
+
+RTDECL(int) RTIoQueueCommit(RTIOQUEUE hIoQueue)
+{
+ PRTIOQUEUEINT pThis = hIoQueue;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(ASMAtomicReadU32(&pThis->cReqsPrepared) > 0, VERR_IOQUEUE_EMPTY);
+
+ uint32_t cReqsPreparedOld = 0;
+ uint32_t cReqsCommitted = 0;
+ int rc = VINF_SUCCESS;
+ do
+ {
+ rc = pThis->pVTbl->pfnCommit(pThis->hIoQueueProv, &cReqsCommitted);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicAddU32(&pThis->cReqsCommitted, cReqsCommitted);
+ cReqsPreparedOld = ASMAtomicSubU32(&pThis->cReqsPrepared, cReqsCommitted);
+ }
+ } while (RT_SUCCESS(rc) && cReqsPreparedOld - cReqsCommitted > 0);
+
+ return rc;
+}
+
+
+RTDECL(int) RTIoQueueEvtWait(RTIOQUEUE hIoQueue, PRTIOQUEUECEVT paCEvt, uint32_t cCEvt, uint32_t cMinWait,
+ uint32_t *pcCEvt, uint32_t fFlags)
+{
+ PRTIOQUEUEINT pThis = hIoQueue;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(paCEvt, VERR_INVALID_POINTER);
+ AssertReturn(cCEvt > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(cMinWait > 0, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcCEvt, VERR_INVALID_POINTER);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+ AssertReturn(ASMAtomicReadU32(&pThis->cReqsCommitted) > 0, VERR_IOQUEUE_EMPTY);
+
+ *pcCEvt = 0;
+ int rc = pThis->pVTbl->pfnEvtWait(pThis->hIoQueueProv, paCEvt, cCEvt, cMinWait, pcCEvt, fFlags);
+ if ( (RT_SUCCESS(rc) || rc == VERR_INTERRUPTED)
+ && *pcCEvt > 0)
+ ASMAtomicSubU32(&pThis->cReqsCommitted, *pcCEvt);
+
+ return rc;
+}
+
+
+RTDECL(int) RTIoQueueEvtWaitWakeup(RTIOQUEUE hIoQueue)
+{
+ PRTIOQUEUEINT pThis = hIoQueue;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ return pThis->pVTbl->pfnEvtWaitWakeup(pThis->hIoQueueProv);
+}
+
diff --git a/src/VBox/Runtime/common/ldr/Makefile.kup b/src/VBox/Runtime/common/ldr/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/Makefile.kup
diff --git a/src/VBox/Runtime/common/ldr/ldr.cpp b/src/VBox/Runtime/common/ldr/ldr.cpp
new file mode 100644
index 00000000..0d697974
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldr.cpp
@@ -0,0 +1,186 @@
+/* $Id: ldr.cpp $ */
+/** @file
+ * IPRT - Binary Image Loader.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloc.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include "internal/ldr.h"
+
+
+RTDECL(int) RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue)
+{
+ LogFlow(("RTLdrGetSymbol: hLdrMod=%RTldrm pszSymbol=%p:{%s} ppvValue=%p\n",
+ hLdrMod, pszSymbol, pszSymbol, ppvValue));
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertMsgReturn(pszSymbol, ("pszSymbol=%p\n", pszSymbol), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppvValue, VERR_INVALID_POINTER);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ /*
+ * Do it.
+ */
+ int rc;
+ if (pMod->pOps->pfnGetSymbol)
+ rc = pMod->pOps->pfnGetSymbol(pMod, pszSymbol, ppvValue);
+ else
+ {
+ RTUINTPTR Value = 0;
+ rc = pMod->pOps->pfnGetSymbolEx(pMod, NULL, 0, UINT32_MAX, pszSymbol, &Value);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvValue = (void *)(uintptr_t)Value;
+ if ((uintptr_t)*ppvValue != Value)
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ }
+ LogFlow(("RTLdrGetSymbol: return %Rrc *ppvValue=%p\n", rc, *ppvValue));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrGetSymbol);
+
+
+RTDECL(PFNRT) RTLdrGetFunction(RTLDRMOD hLdrMod, const char *pszSymbol)
+{
+ PFNRT pfn;
+ int rc = RTLdrGetSymbol(hLdrMod, pszSymbol, (void **)&pfn);
+ if (RT_SUCCESS(rc))
+ return pfn;
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTLdrGetFunction);
+
+
+RTDECL(RTLDRFMT) RTLdrGetFormat(RTLDRMOD hLdrMod)
+{
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRFMT_INVALID);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ return pMod->enmFormat;
+}
+RT_EXPORT_SYMBOL(RTLdrGetFormat);
+
+
+RTDECL(RTLDRTYPE) RTLdrGetType(RTLDRMOD hLdrMod)
+{
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRTYPE_INVALID);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ return pMod->enmType;
+}
+RT_EXPORT_SYMBOL(RTLdrGetType);
+
+
+RTDECL(RTLDRENDIAN) RTLdrGetEndian(RTLDRMOD hLdrMod)
+{
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRENDIAN_INVALID);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ return pMod->enmEndian;
+}
+RT_EXPORT_SYMBOL(RTLdrGetEndian);
+
+
+RTDECL(RTLDRARCH) RTLdrGetArch(RTLDRMOD hLdrMod)
+{
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRARCH_INVALID);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ return pMod->enmArch;
+}
+RT_EXPORT_SYMBOL(RTLdrGetArch);
+
+
+RTDECL(int) RTLdrClose(RTLDRMOD hLdrMod)
+{
+ LogFlow(("RTLdrClose: hLdrMod=%RTldrm\n", hLdrMod));
+
+ /*
+ * Validate input.
+ */
+ if (hLdrMod == NIL_RTLDRMOD)
+ return VINF_SUCCESS;
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ /*
+ * Do it.
+ */
+ int rc = pMod->pOps->pfnClose(pMod);
+ AssertRC(rc);
+ pMod->eState = LDR_STATE_INVALID;
+ pMod->u32Magic++;
+ if (pMod->pReader)
+ {
+ rc = pMod->pReader->pfnDestroy(pMod->pReader);
+ AssertRC(rc);
+ pMod->pReader = NULL;
+ }
+ RTMemFree(pMod);
+
+ LogFlow(("RTLdrClose: returns VINF_SUCCESS\n"));
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTLdrClose);
+
+
+RTDECL(RTLDRARCH) RTLdrGetHostArch(void)
+{
+#if defined(RT_ARCH_AMD64)
+ RTLDRARCH enmArch = RTLDRARCH_AMD64;
+#elif defined(RT_ARCH_X86)
+ RTLDRARCH enmArch = RTLDRARCH_X86_32;
+#elif defined(RT_ARCH_ARM) || defined(RT_ARCH_ARM32)
+ RTLDRARCH enmArch = RTLDRARCH_ARM32;
+#elif defined(RT_ARCH_ARM64)
+ RTLDRARCH enmArch = RTLDRARCH_ARM64;
+#else
+ RTLDRARCH enmArch = RTLDRARCH_WHATEVER;
+#endif
+ return enmArch;
+}
+RT_EXPORT_SYMBOL(RTLdrGetHostArch);
+
diff --git a/src/VBox/Runtime/common/ldr/ldrELF.cpp b/src/VBox/Runtime/common/ldr/ldrELF.cpp
new file mode 100644
index 00000000..ad380088
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrELF.cpp
@@ -0,0 +1,381 @@
+/* $Id: ldrELF.cpp $ */
+/** @file
+ * IPRT - Binary Image Loader, Executable and Linker Format (ELF).
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/dbg.h>
+#include <iprt/string.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/err.h>
+#include <iprt/crypto/digest.h>
+#include <iprt/formats/elf32.h>
+#include <iprt/formats/elf64.h>
+#include <iprt/formats/elf-i386.h>
+#include <iprt/formats/elf-amd64.h>
+#include "internal/ldr.h"
+#include "internal/dbgmod.h"
+
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Finds an ELF symbol table string. */
+#define ELF_STR(pHdrs, iStr) ((pHdrs)->Rel.pStr + (iStr))
+/** Finds an ELF symbol table string. */
+#define ELF_DYN_STR(pHdrs, iStr) ((pHdrs)->Dyn.pStr + (iStr))
+/** Finds an ELF section header string. */
+#define ELF_SH_STR(pHdrs, iStr) ((pHdrs)->pShStr + (iStr))
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Magic string for RTLDRLNXMODSIG::achMagic */
+#define RTLDRLNXMODSIG_MAGIC "~Module signature appended~\n"
+AssertCompile(sizeof(RTLDRLNXMODSIG_MAGIC) == 29);
+
+/**
+ * Linux kernel module signature footer - found at the end of the file.
+ */
+typedef struct RTLDRLNXMODSIG
+{
+ /** Zero. */
+ uint8_t bAlgo;
+ /** Zero. */
+ uint8_t bHash;
+ /** Signature type (RTLDRLNXMODSIG_TYPE_PKCS7). */
+ uint8_t bType;
+ /** Zero. */
+ uint8_t cbSignerName;
+ /** Zero. */
+ uint8_t cbKeyId;
+ /** Zero padding. */
+ uint8_t abReserved[3];
+ /** The length of the signature preceeding this footer structure. */
+ uint32_t cbSignature;
+ /** Magic value identifying this structure. */
+ char achMagic[sizeof(RTLDRLNXMODSIG_MAGIC) - 1];
+} RTLDRLNXMODSIG;
+typedef RTLDRLNXMODSIG *PRTLDRLNXMODSIG;
+typedef RTLDRLNXMODSIG const *PCRTLDRLNXMODSIG;
+/** Signature type. */
+#define RTLDRLNXMODSIG_TYPE_PKCS7 2
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtLdrELFLnxKModQueryPropIsSigned(PRTLDRREADER pReader, bool *pfRet);
+static int rtLdrELFLnxKModQueryPropPkcs7SignedData(PRTLDRREADER pReader, void *pvBuf, size_t cbBuf, size_t *pcbRet);
+static DECLCALLBACK(int) rtldrELFLnxKModHashImage(PRTLDRMODINTERNAL pMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash);
+#ifdef LOG_ENABLED
+static const char *rtldrElfGetShdrType(uint32_t iType);
+static const char *rtldrElfGetPhdrType(uint32_t iType);
+#endif
+
+
+/* Select ELF mode and include the template. */
+#define ELF_MODE 32
+#define Elf_Reloc Elf_Rel
+#include "ldrELFRelocatable.cpp.h"
+#undef ELF_MODE
+#undef Elf_Reloc
+
+
+#define ELF_MODE 64
+#define Elf_Reloc Elf_Rela
+#include "ldrELFRelocatable.cpp.h"
+#undef ELF_MODE
+#undef Elf_Reloc
+
+
+#ifdef LOG_ENABLED
+
+/**
+ * Gets the section type.
+ *
+ * @returns Pointer to read only string.
+ * @param iType The section type index.
+ */
+static const char *rtldrElfGetShdrType(uint32_t iType)
+{
+ switch (iType)
+ {
+ RT_CASE_RET_STR(SHT_NULL);
+ RT_CASE_RET_STR(SHT_PROGBITS);
+ RT_CASE_RET_STR(SHT_SYMTAB);
+ RT_CASE_RET_STR(SHT_STRTAB);
+ RT_CASE_RET_STR(SHT_RELA);
+ RT_CASE_RET_STR(SHT_HASH);
+ RT_CASE_RET_STR(SHT_DYNAMIC);
+ RT_CASE_RET_STR(SHT_NOTE);
+ RT_CASE_RET_STR(SHT_NOBITS);
+ RT_CASE_RET_STR(SHT_REL);
+ RT_CASE_RET_STR(SHT_SHLIB);
+ RT_CASE_RET_STR(SHT_DYNSYM);
+ default:
+ return "";
+ }
+}
+
+/**
+ * Gets the program header type.
+ *
+ * @returns Pointer to read only string.
+ * @param iType The section type index.
+ */
+static const char *rtldrElfGetPhdrType(uint32_t iType)
+{
+ switch (iType)
+ {
+ RT_CASE_RET_STR(PT_NULL);
+ RT_CASE_RET_STR(PT_LOAD);
+ RT_CASE_RET_STR(PT_DYNAMIC);
+ RT_CASE_RET_STR(PT_INTERP);
+ RT_CASE_RET_STR(PT_NOTE);
+ RT_CASE_RET_STR(PT_SHLIB);
+ RT_CASE_RET_STR(PT_PHDR);
+ RT_CASE_RET_STR(PT_TLS);
+ RT_CASE_RET_STR(PT_GNU_EH_FRAME);
+ RT_CASE_RET_STR(PT_GNU_STACK);
+ RT_CASE_RET_STR(PT_GNU_RELRO);
+ RT_CASE_RET_STR(PT_GNU_PROPERTY);
+ default:
+ return "";
+ }
+}
+
+#endif /* LOG_ENABLED*/
+
+/**
+ * Reads in what migt be a linux kernel module signature footer.
+ */
+static int rtLdrELFLnxKModReadFooter(PRTLDRREADER pReader, PRTLDRLNXMODSIG pSigFooter, uint64_t *pcbFile)
+{
+ /*
+ * Look for the linux module signature at the end of the file.
+ * This should be safe to read w/o any size checking as it is smaller than the elf header.
+ */
+ uint64_t cbFile = pReader->pfnSize(pReader);
+ *pcbFile = cbFile;
+
+ AssertCompile(sizeof(*pSigFooter) <= sizeof(Elf32_Ehdr));
+ return pReader->pfnRead(pReader, pSigFooter, sizeof(*pSigFooter), cbFile - sizeof(*pSigFooter));
+}
+
+
+/**
+ * Check that a linux kernel module signature footer is valid.
+ */
+static bool rtLdrELFLnxKModIsFooterValid(PCRTLDRLNXMODSIG pSigFooter, uint64_t cbFile)
+{
+ if (memcmp(pSigFooter->achMagic, RTLDRLNXMODSIG_MAGIC, sizeof(pSigFooter->achMagic)) == 0)
+ {
+ uint32_t const cbSignature = RT_N2H_U32(pSigFooter->cbSignature);
+ if (cbSignature > 32 && cbSignature + sizeof(*pSigFooter) < cbFile)
+ return pSigFooter->bAlgo == 0
+ && pSigFooter->bHash == 0
+ && pSigFooter->cbSignerName == 0
+ && pSigFooter->cbKeyId == 0;
+ }
+ return false;
+}
+
+
+/**
+ * Handles the linux kernel module signature part of RTLDRPROP_IS_SIGNED
+ * queries.
+ */
+static int rtLdrELFLnxKModQueryPropIsSigned(PRTLDRREADER pReader, bool *pfRet)
+{
+ *pfRet = false;
+ AssertReturn(pReader, VERR_INVALID_STATE);
+
+ uint64_t cbFile;
+ RTLDRLNXMODSIG SigFooter;
+ int rc = rtLdrELFLnxKModReadFooter(pReader, &SigFooter, &cbFile);
+ if (RT_SUCCESS(rc))
+ *pfRet = rtLdrELFLnxKModIsFooterValid(&SigFooter, cbFile);
+ return rc;
+}
+
+
+/**
+ * Handles the linux kernel module signature part of RTLDRPROP_IS_SIGNED
+ * queries.
+ */
+static int rtLdrELFLnxKModQueryPropPkcs7SignedData(PRTLDRREADER pReader, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ AssertReturn(pReader, VERR_INVALID_STATE);
+
+ uint64_t cbFile;
+ RTLDRLNXMODSIG SigFooter;
+ int rc = rtLdrELFLnxKModReadFooter(pReader, &SigFooter, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if ( rtLdrELFLnxKModIsFooterValid(&SigFooter, cbFile)
+ && SigFooter.bType == RTLDRLNXMODSIG_TYPE_PKCS7)
+ {
+ uint32_t const cbSignature = RT_N2H_U32(SigFooter.cbSignature);
+ *pcbRet = cbSignature;
+ if (cbSignature <= cbBuf)
+ rc = pReader->pfnRead(pReader, pvBuf, cbSignature, cbFile - sizeof(SigFooter) - cbSignature);
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnHashImage,
+ * Handles the linux kernel module signatures.}
+ */
+static DECLCALLBACK(int) rtldrELFLnxKModHashImage(PRTLDRMODINTERNAL pMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash)
+{
+ PRTLDRREADER pReader = pMod->pReader;
+ AssertReturn(pReader, VERR_INVALID_STATE);
+
+ /*
+ * Get the file size and subtract any linux kernel module signature from it
+ * since it's not part of the hash.
+ */
+ uint64_t cbFile;
+ RTLDRLNXMODSIG SigFooter;
+ int rc = rtLdrELFLnxKModReadFooter(pReader, &SigFooter, &cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ if (rtLdrELFLnxKModIsFooterValid(&SigFooter, cbFile))
+ cbFile -= sizeof(SigFooter) + RT_N2H_U32(SigFooter.cbSignature);
+
+ /*
+ * Now hash the file.
+ */
+ RTCRDIGEST hDigest;
+ rc = RTCrDigestCreateByType(&hDigest, enmDigest);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cbBuf = _64K;
+ void *pvBuf = RTMemTmpAlloc(_64K);
+ void *pvBufFree = pvBuf;
+ if (!pvBuf)
+ {
+ cbBuf = _4K;
+ pvBuf = alloca(_4K);
+ }
+
+ for (uint64_t offFile = 0; offFile < cbFile; )
+ {
+ uint64_t cbLeft = cbFile - offFile;
+ uint32_t cbToRead = cbLeft >= cbBuf ? cbBuf : (uint32_t)cbLeft;
+ rc = pReader->pfnRead(pReader, pvBuf, cbToRead, offFile);
+ AssertRCBreak(rc);
+
+ rc = RTCrDigestUpdate(hDigest, pvBuf, cbToRead);
+ offFile += cbToRead;
+ AssertRCBreak(rc);
+ }
+
+ RTMemTmpFree(pvBufFree);
+
+ if (RT_SUCCESS(rc))
+ rc = RTCrDigestFinal(hDigest, pabHash, cbHash);
+ RTCrDigestRelease(hDigest);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Open an ELF image.
+ *
+ * @returns iprt status code.
+ * @param pReader The loader reader instance which will provide the raw image bits.
+ * @param fFlags Reserved, MBZ.
+ * @param enmArch Architecture specifier.
+ * @param phLdrMod Where to store the handle.
+ * @param pErrInfo Where to return extended error information. Optional.
+ */
+DECLHIDDEN(int) rtldrELFOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo)
+{
+ const char *pszLogName = pReader->pfnLogName(pReader); NOREF(pszLogName);
+
+ /*
+ * Read the ident to decide if this is 32-bit or 64-bit
+ * and worth dealing with.
+ */
+ uint8_t e_ident[EI_NIDENT];
+ int rc = pReader->pfnRead(pReader, &e_ident, sizeof(e_ident), 0);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if ( e_ident[EI_MAG0] != ELFMAG0
+ || e_ident[EI_MAG1] != ELFMAG1
+ || e_ident[EI_MAG2] != ELFMAG2
+ || e_ident[EI_MAG3] != ELFMAG3
+ || ( e_ident[EI_CLASS] != ELFCLASS32
+ && e_ident[EI_CLASS] != ELFCLASS64)
+ )
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Unsupported/invalid ident %.*Rhxs", pszLogName, sizeof(e_ident), e_ident);
+
+ if (e_ident[EI_DATA] != ELFDATA2LSB)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_ODD_ENDIAN,
+ "%s: ELF endian %x is unsupported", pszLogName, e_ident[EI_DATA]);
+
+ if (e_ident[EI_CLASS] == ELFCLASS32)
+ rc = rtldrELF32Open(pReader, fFlags, enmArch, phLdrMod, pErrInfo);
+ else
+ rc = rtldrELF64Open(pReader, fFlags, enmArch, phLdrMod, pErrInfo);
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/ldr/ldrELFRelocatable.cpp.h b/src/VBox/Runtime/common/ldr/ldrELFRelocatable.cpp.h
new file mode 100644
index 00000000..1eb33be8
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrELFRelocatable.cpp.h
@@ -0,0 +1,3163 @@
+/* $Id: ldrELFRelocatable.cpp.h $ */
+/** @file
+ * IPRT - Binary Image Loader, Template for ELF Relocatable Images.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+#if ELF_MODE == 32
+# define RTLDRELF_NAME(name) rtldrELF32##name
+# define RTLDRELF_SUFF(name) name##32
+# define RTLDRELF_MID(pre,suff) pre##32##suff
+# define FMT_ELF_ADDR "%08RX32"
+# define FMT_ELF_ADDR7 "%07RX32"
+# define FMT_ELF_HALF "%04RX16"
+# define FMT_ELF_OFF "%08RX32"
+# define FMT_ELF_SIZE "%08RX32"
+# define FMT_ELF_SWORD "%RI32"
+# define FMT_ELF_WORD "%08RX32"
+# define FMT_ELF_XWORD "%08RX32"
+# define FMT_ELF_SXWORD "%RI32"
+# define Elf_Xword Elf32_Word
+# define Elf_Sxword Elf32_Sword
+
+#elif ELF_MODE == 64
+# define RTLDRELF_NAME(name) rtldrELF64##name
+# define RTLDRELF_SUFF(name) name##64
+# define RTLDRELF_MID(pre,suff) pre##64##suff
+# define FMT_ELF_ADDR "%016RX64"
+# define FMT_ELF_ADDR7 "%08RX64"
+# define FMT_ELF_HALF "%04RX16"
+# define FMT_ELF_SHALF "%RI16"
+# define FMT_ELF_OFF "%016RX64"
+# define FMT_ELF_SIZE "%016RX64"
+# define FMT_ELF_SWORD "%RI32"
+# define FMT_ELF_WORD "%08RX32"
+# define FMT_ELF_XWORD "%016RX64"
+# define FMT_ELF_SXWORD "%RI64"
+# define Elf_Xword Elf64_Xword
+# define Elf_Sxword Elf64_Sxword
+#endif
+
+#define Elf_Ehdr RTLDRELF_MID(Elf,_Ehdr)
+#define Elf_Phdr RTLDRELF_MID(Elf,_Phdr)
+#define Elf_Shdr RTLDRELF_MID(Elf,_Shdr)
+#define Elf_Sym RTLDRELF_MID(Elf,_Sym)
+#define Elf_Rel RTLDRELF_MID(Elf,_Rel)
+#define Elf_Rela RTLDRELF_MID(Elf,_Rela)
+#define Elf_Nhdr RTLDRELF_MID(Elf,_Nhdr)
+#define Elf_Dyn RTLDRELF_MID(Elf,_Dyn)
+#define Elf_Addr RTLDRELF_MID(Elf,_Addr)
+#define Elf_Half RTLDRELF_MID(Elf,_Half)
+#define Elf_Off RTLDRELF_MID(Elf,_Off)
+#define Elf_Size RTLDRELF_MID(Elf,_Size)
+#define Elf_Sword RTLDRELF_MID(Elf,_Sword)
+#define Elf_Word RTLDRELF_MID(Elf,_Word)
+
+#define RTLDRMODELF RTLDRELF_MID(RTLDRMODELF,RT_NOTHING)
+#define PRTLDRMODELF RTLDRELF_MID(PRTLDRMODELF,RT_NOTHING)
+
+#define RTLDRMODELFSHX RTLDRELF_MID(RTLDRMODELFSHX,RT_NOTHING)
+#define PRTLDRMODELFSHX RTLDRELF_MID(PRTLDRMODELFSHX,RT_NOTHING)
+
+#define ELF_R_SYM(info) RTLDRELF_MID(ELF,_R_SYM)(info)
+#define ELF_R_TYPE(info) RTLDRELF_MID(ELF,_R_TYPE)(info)
+#define ELF_R_INFO(sym, type) RTLDRELF_MID(ELF,_R_INFO)(sym, type)
+
+#define ELF_ST_BIND(info) RTLDRELF_MID(ELF,_ST_BIND)(info)
+
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/**
+ * Extra section info.
+ */
+typedef struct RTLDRMODELFSHX
+{
+ /** The corresponding program header. */
+ uint16_t idxPhdr;
+ /** The corresponding dynamic section entry (address). */
+ uint16_t idxDt;
+ /** The DT tag. */
+ uint32_t uDtTag;
+} RTLDRMODELFSHX;
+typedef RTLDRMODELFSHX *PRTLDRMODELFSHX;
+
+/**
+ * The ELF loader structure.
+ */
+typedef struct RTLDRMODELF
+{
+ /** Core module structure. */
+ RTLDRMODINTERNAL Core;
+ /** Pointer to readonly mapping of the image bits.
+ * This mapping is provided by the pReader. */
+ const void *pvBits;
+
+ /** The ELF header. */
+ Elf_Ehdr Ehdr;
+ /** Pointer to our copy of the section headers with sh_addr as RVAs.
+ * The virtual addresses in this array is the 0 based assignments we've given the image.
+ * Not valid if the image is DONE. */
+ Elf_Shdr *paShdrs;
+ /** Unmodified section headers (allocated after paShdrs, so no need to free).
+ * Not valid if the image is DONE. */
+ Elf_Shdr const *paOrgShdrs;
+ /** Runs parallel to paShdrs and is part of the same allocation. */
+ PRTLDRMODELFSHX paShdrExtras;
+ /** Base section number, either 1 or zero depending on whether we've
+ * re-used the NULL entry for .elf.headers in ET_EXEC/ET_DYN. */
+ unsigned iFirstSect;
+ /** Set if the SHF_ALLOC section headers are in order of sh_addr. */
+ bool fShdrInOrder;
+ /** The size of the loaded image. */
+ size_t cbImage;
+
+ /** The image base address if it's an EXEC or DYN image. */
+ Elf_Addr LinkAddress;
+
+ struct
+ {
+ /** The symbol section index. */
+ unsigned iSymSh;
+ /** Number of symbols in the table. */
+ unsigned cSyms;
+ /** Pointer to symbol table within RTLDRMODELF::pvBits. */
+ const Elf_Sym *paSyms;
+
+ /** The string section index. */
+ unsigned iStrSh;
+ /** Size of the string table. */
+ unsigned cbStr;
+ /** Pointer to string table within RTLDRMODELF::pvBits. */
+ const char *pStr;
+ } Rel /**< Regular symbols and strings. */
+ , Dyn /**< Dynamic symbols and strings. */;
+
+ /** Pointer to section header string table within RTLDRMODELF::pvBits. */
+ const char *pShStr;
+ /** Size of the section header string table. */
+ unsigned cbShStr;
+
+ /** The '.eh_frame' section index. Zero if not searched for, ~0U if not found. */
+ unsigned iShEhFrame;
+ /** The '.eh_frame_hdr' section index. Zero if not searched for, ~0U if not found. */
+ unsigned iShEhFrameHdr;
+
+ /** The '.dynamic' / SHT_DYNAMIC section index. ~0U if not present. */
+ unsigned iShDynamic;
+ /** Number of entries in paDynamic. */
+ unsigned cDynamic;
+ /** The dynamic section (NULL for ET_REL). */
+ Elf_Dyn *paDynamic;
+ /** Program headers (NULL for ET_REL). */
+ Elf_Phdr *paPhdrs;
+
+ /** Info extracted from PT_DYNAMIC and the program headers. */
+ struct
+ {
+ /** DT_RELA/DT_REL. */
+ Elf_Addr uPtrRelocs;
+ /** DT_RELASZ/DT_RELSZ. */
+ Elf_Xword cbRelocs;
+ /** Non-zero if we've seen DT_RELAENT/DT_RELENT. */
+ unsigned cbRelocEntry;
+ /** DT_RELA or DT_REL. */
+ unsigned uRelocType;
+ /** The index of the section header matching DT_RELA/DT_REL. */
+ unsigned idxShRelocs;
+
+ /** DT_JMPREL. */
+ Elf_Addr uPtrJmpRelocs;
+ /** DT_PLTRELSZ. */
+ Elf_Xword cbJmpRelocs;
+ /** DT_RELA or DT_REL (if we've seen DT_PLTREL). */
+ unsigned uJmpRelocType;
+ /** The index of the section header matching DT_JMPREL. */
+ unsigned idxShJmpRelocs;
+ } DynInfo;
+} RTLDRMODELF;
+/** Pointer to an ELF module instance. */
+typedef RTLDRMODELF *PRTLDRMODELF;
+
+
+/**
+ * Maps the image bits into memory and resolve pointers into it.
+ *
+ * @returns iprt status code.
+ * @param pModElf The ELF loader module instance data.
+ * @param fNeedsBits Set if we actually need the pvBits member.
+ * If we don't, we can simply read the string and symbol sections, thus saving memory.
+ */
+static int RTLDRELF_NAME(MapBits)(PRTLDRMODELF pModElf, bool fNeedsBits)
+{
+ NOREF(fNeedsBits);
+ if (pModElf->pvBits)
+ return VINF_SUCCESS;
+ int rc = pModElf->Core.pReader->pfnMap(pModElf->Core.pReader, &pModElf->pvBits);
+ if (RT_SUCCESS(rc))
+ {
+ const uint8_t *pu8 = (const uint8_t *)pModElf->pvBits;
+ if (pModElf->Rel.iSymSh != ~0U)
+ pModElf->Rel.paSyms = (const Elf_Sym *)(pu8 + pModElf->paShdrs[pModElf->Rel.iSymSh].sh_offset);
+ if (pModElf->Rel.iStrSh != ~0U)
+ pModElf->Rel.pStr = (const char *)(pu8 + pModElf->paShdrs[pModElf->Rel.iStrSh].sh_offset);
+ if (pModElf->Dyn.iSymSh != ~0U)
+ pModElf->Dyn.paSyms = (const Elf_Sym *)(pu8 + pModElf->paShdrs[pModElf->Dyn.iSymSh].sh_offset);
+ if (pModElf->Dyn.iStrSh != ~0U)
+ pModElf->Dyn.pStr = (const char *)(pu8 + pModElf->paShdrs[pModElf->Dyn.iStrSh].sh_offset);
+ pModElf->pShStr = (const char *)(pu8 + pModElf->paShdrs[pModElf->Ehdr.e_shstrndx].sh_offset);
+
+ /*
+ * Verify that the ends of the string tables have a zero terminator
+ * (this avoids duplicating the appropriate checks later in the code accessing the string tables).
+ *
+ * sh_offset and sh_size were verfied in RTLDRELF_NAME(ValidateSectionHeader)() already so they
+ * are safe to use.
+ */
+ AssertMsgStmt( pModElf->Rel.iStrSh == ~0U
+ || pModElf->Rel.pStr[pModElf->paShdrs[pModElf->Rel.iStrSh].sh_size - 1] == '\0',
+ ("The string table is not zero terminated!\n"),
+ rc = VERR_LDRELF_UNTERMINATED_STRING_TAB);
+ AssertMsgStmt( pModElf->Dyn.iStrSh == ~0U
+ || pModElf->Dyn.pStr[pModElf->paShdrs[pModElf->Dyn.iStrSh].sh_size - 1] == '\0',
+ ("The string table is not zero terminated!\n"),
+ rc = VERR_LDRELF_UNTERMINATED_STRING_TAB);
+ AssertMsgStmt(pModElf->pShStr[pModElf->paShdrs[pModElf->Ehdr.e_shstrndx].sh_size - 1] == '\0',
+ ("The section header string table is not zero terminated!\n"),
+ rc = VERR_LDRELF_UNTERMINATED_STRING_TAB);
+
+ if (RT_FAILURE(rc))
+ {
+ /* Unmap. */
+ int rc2 = pModElf->Core.pReader->pfnUnmap(pModElf->Core.pReader, pModElf->pvBits);
+ AssertRC(rc2);
+ pModElf->pvBits = NULL;
+ pModElf->Rel.paSyms = NULL;
+ pModElf->Rel.pStr = NULL;
+ pModElf->Dyn.paSyms = NULL;
+ pModElf->Dyn.pStr = NULL;
+ pModElf->pShStr = NULL;
+ }
+ }
+ return rc;
+}
+
+
+/*
+ *
+ * EXEC & DYN.
+ * EXEC & DYN.
+ * EXEC & DYN.
+ * EXEC & DYN.
+ * EXEC & DYN.
+ *
+ */
+
+/**
+ * Get the symbol and symbol value.
+ *
+ * @returns iprt status code.
+ * @param pModElf The ELF loader module instance data.
+ * @param BaseAddr The base address which the module is being fixedup to.
+ * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals).
+ * @param pvUser User argument to pass to the callback.
+ * @param iSym The symbol to get.
+ * @param ppSym Where to store the symbol pointer on success. (read only)
+ * @param pSymValue Where to store the symbol value on success.
+ */
+static int RTLDRELF_NAME(SymbolExecDyn)(PRTLDRMODELF pModElf, Elf_Addr BaseAddr, PFNRTLDRIMPORT pfnGetImport, void *pvUser,
+ Elf_Size iSym, const Elf_Sym **ppSym, Elf_Addr *pSymValue)
+{
+ /*
+ * Validate and find the symbol.
+ */
+ AssertMsgReturn(iSym < pModElf->Dyn.cSyms, ("iSym=%d is an invalid symbol index!\n", iSym), VERR_LDRELF_INVALID_SYMBOL_INDEX);
+ const Elf_Sym *pSym = &pModElf->Dyn.paSyms[iSym];
+ *ppSym = pSym;
+
+ AssertMsgReturn(pSym->st_name < pModElf->Dyn.cbStr,
+ ("iSym=%d st_name=%d str sh_size=%d\n", iSym, pSym->st_name, pModElf->Dyn.cbStr),
+ VERR_LDRELF_INVALID_SYMBOL_NAME_OFFSET);
+ const char * const pszName = pModElf->Dyn.pStr + pSym->st_name;
+
+ /*
+ * Determine the symbol value.
+ *
+ * Symbols needs different treatment depending on which section their are in.
+ * Undefined and absolute symbols goes into special non-existing sections.
+ */
+ switch (pSym->st_shndx)
+ {
+ /*
+ * Undefined symbol, needs resolving.
+ *
+ * Since ELF has no generic concept of importing from specific module (the OS/2 ELF format
+ * has but that's an OS extension and only applies to programs and dlls), we'll have to ask
+ * the resolver callback to do a global search.
+ */
+ case SHN_UNDEF:
+ {
+ /* Try to resolve the symbol. */
+ RTUINTPTR Value;
+ int rc = pfnGetImport(&pModElf->Core, "", pszName, ~0U, &Value, pvUser);
+ AssertMsgRCReturn(rc, ("Failed to resolve '%s' (iSym=" FMT_ELF_SIZE " rc=%Rrc\n", pszName, iSym, rc), rc);
+
+ *pSymValue = (Elf_Addr)Value;
+ AssertMsgReturn((RTUINTPTR)*pSymValue == Value,
+ ("Symbol value overflowed! '%s' (iSym=" FMT_ELF_SIZE "\n", pszName, iSym), VERR_SYMBOL_VALUE_TOO_BIG);
+
+ Log2(("rtldrELF: #%-3d - UNDEF " FMT_ELF_ADDR " '%s'\n", iSym, *pSymValue, pszName));
+ break;
+ }
+
+ /*
+ * Absolute symbols needs no fixing since they are, well, absolute.
+ */
+ case SHN_ABS:
+ *pSymValue = pSym->st_value;
+ Log2(("rtldrELF: #%-3d - ABS " FMT_ELF_ADDR " '%s'\n", iSym, *pSymValue, pszName));
+ break;
+
+ /*
+ * All other symbols are addressed relative the image base in DYN and EXEC binaries.
+ */
+ default:
+ AssertMsgReturn(pSym->st_shndx < pModElf->Ehdr.e_shnum,
+ ("iSym=%d st_shndx=%d e_shnum=%d pszName=%s\n", iSym, pSym->st_shndx, pModElf->Ehdr.e_shnum, pszName),
+ VERR_BAD_EXE_FORMAT);
+ *pSymValue = pSym->st_value + BaseAddr;
+ Log2(("rtldrELF: #%-3d - %5d " FMT_ELF_ADDR " '%s'\n", iSym, pSym->st_shndx, *pSymValue, pszName));
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+#if ELF_MODE == 32
+/** Helper for RelocateSectionExecDyn. */
+DECLINLINE(const Elf_Shdr *) RTLDRELF_NAME(RvaToSectionHeader)(PRTLDRMODELF pModElf, Elf_Addr uRva)
+{
+ const Elf_Shdr * const pShdrFirst = pModElf->paShdrs;
+ const Elf_Shdr *pShdr = pShdrFirst + pModElf->Ehdr.e_shnum;
+ while (--pShdr != pShdrFirst)
+ if (uRva - pShdr->sh_addr /*rva*/ < pShdr->sh_size)
+ return pShdr;
+ AssertFailed();
+ return pShdr;
+}
+#endif
+
+
+/**
+ * Applies the fixups for a section in an executable image.
+ *
+ * @returns iprt status code.
+ * @param pModElf The ELF loader module instance data.
+ * @param BaseAddr The base address which the module is being fixedup to.
+ * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals).
+ * @param pvUser User argument to pass to the callback.
+ * @param SecAddr The section address. This is the address the relocations are relative to.
+ * @param cbSec The section size. The relocations must be inside this.
+ * @param pu8SecBaseR Where we read section bits from.
+ * @param pu8SecBaseW Where we write section bits to.
+ * @param pvRelocs Pointer to where we read the relocations from.
+ * @param cbRelocs Size of the relocations.
+ */
+static int RTLDRELF_NAME(RelocateSectionExecDyn)(PRTLDRMODELF pModElf, Elf_Addr BaseAddr,
+ PFNRTLDRIMPORT pfnGetImport, void *pvUser,
+ const Elf_Addr SecAddr, Elf_Size cbSec,
+ const uint8_t *pu8SecBaseR, uint8_t *pu8SecBaseW,
+ const void *pvRelocs, Elf_Size cbRelocs)
+{
+#if ELF_MODE != 32
+ NOREF(pu8SecBaseR);
+#endif
+
+ /*
+ * Iterate the relocations.
+ * The relocations are stored in an array of Elf32_Rel records and covers the entire relocation section.
+ */
+#if ELF_MODE == 32
+ const Elf_Shdr *pShdr = pModElf->paShdrs;
+ const Elf_Addr offDelta = BaseAddr - pModElf->LinkAddress;
+#endif
+ const Elf_Reloc *paRels = (const Elf_Reloc *)pvRelocs;
+ const unsigned iRelMax = (unsigned)(cbRelocs / sizeof(paRels[0]));
+ AssertMsgReturn(iRelMax == cbRelocs / sizeof(paRels[0]), (FMT_ELF_SIZE "\n", cbRelocs / sizeof(paRels[0])),
+ VERR_IMAGE_TOO_BIG);
+ for (unsigned iRel = 0; iRel < iRelMax; iRel++)
+ {
+ /*
+ * Apply fixups not taking a symbol (will 'continue' rather than 'break').
+ */
+ AssertMsgReturn(paRels[iRel].r_offset < cbSec, (FMT_ELF_ADDR " " FMT_ELF_SIZE "\n", paRels[iRel].r_offset, cbSec),
+ VERR_LDRELF_INVALID_RELOCATION_OFFSET);
+#if ELF_MODE == 32
+ if (paRels[iRel].r_offset - pShdr->sh_addr /*rva*/ >= pShdr->sh_size)
+ pShdr = RTLDRELF_NAME(RvaToSectionHeader)(pModElf, paRels[iRel].r_offset);
+ static const Elf_Addr s_uZero = 0;
+ const Elf_Addr *pAddrR = RT_LIKELY(pShdr->sh_type != SHT_NOBITS) /* Where to read the addend. */
+ ? (const Elf_Addr *)(pu8SecBaseR + paRels[iRel].r_offset - pShdr->sh_addr /*rva*/
+ + pShdr->sh_offset)
+ : &s_uZero;
+#endif
+ Elf_Addr *pAddrW = (Elf_Addr *)(pu8SecBaseW + paRels[iRel].r_offset); /* Where to write the fixup. */
+ switch (ELF_R_TYPE(paRels[iRel].r_info))
+ {
+ /*
+ * Image relative (addend + base).
+ */
+#if ELF_MODE == 32
+ case R_386_RELATIVE:
+ {
+ const Elf_Addr Value = *pAddrR + BaseAddr;
+ *(uint32_t *)pAddrW = Value;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_RELATIVE Value=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value));
+ AssertCompile(sizeof(Value) == sizeof(uint32_t));
+ continue;
+ }
+#elif ELF_MODE == 64
+ case R_X86_64_RELATIVE:
+ {
+ const Elf_Addr Value = paRels[iRel].r_addend + BaseAddr;
+ *(uint64_t *)pAddrW = (uint64_t)Value;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_RELATIVE Value=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value));
+ AssertCompile(sizeof(Value) == sizeof(uint64_t));
+ continue;
+ }
+#endif
+
+ /*
+ * R_XXX_NONE.
+ */
+#if ELF_MODE == 32
+ case R_386_NONE:
+#elif ELF_MODE == 64
+ case R_X86_64_NONE:
+#endif
+ continue;
+ }
+
+ /*
+ * Validate and find the symbol, resolve undefined ones.
+ */
+ const Elf_Sym *pSym = NULL; /* shut up gcc */
+ Elf_Addr SymValue = 0; /* shut up gcc-4 */
+ int rc = RTLDRELF_NAME(SymbolExecDyn)(pModElf, BaseAddr, pfnGetImport, pvUser, ELF_R_SYM(paRels[iRel].r_info), &pSym, &SymValue);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Apply the fixup.
+ */
+ switch (ELF_R_TYPE(paRels[iRel].r_info))
+ {
+#if ELF_MODE == 32
+ /*
+ * GOT/PLT.
+ */
+ case R_386_GLOB_DAT:
+ {
+ *(uint32_t *)pAddrW = (uint32_t)SymValue;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_GLOB_DAT Value=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, SymValue));
+ AssertCompile(sizeof(SymValue) == sizeof(uint32_t));
+ break;
+ }
+
+ case R_386_JMP_SLOT:
+ {
+ *(uint32_t *)pAddrW = (uint32_t)SymValue;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_JMP_SLOT Value=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, SymValue));
+ AssertCompile(sizeof(SymValue) == sizeof(uint32_t));
+ break;
+ }
+
+ /*
+ * Absolute addressing.
+ */
+ case R_386_32:
+ {
+ Elf_Addr Value;
+ if (pSym->st_shndx < pModElf->Ehdr.e_shnum)
+ Value = *pAddrR + offDelta; /* Simplified. */
+ else if (pSym->st_shndx == SHN_ABS)
+ continue; /* Internal fixup, no need to apply it. */
+ else if (pSym->st_shndx == SHN_UNDEF)
+ Value = SymValue + *pAddrR;
+ else
+ AssertFailedReturn(VERR_LDR_GENERAL_FAILURE); /** @todo SHN_COMMON */
+ *(uint32_t *)pAddrW = Value;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_32 Value=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value));
+ break;
+ }
+
+ /*
+ * PC relative addressing.
+ */
+ case R_386_PC32:
+ {
+ Elf_Addr Value;
+ if (pSym->st_shndx < pModElf->Ehdr.e_shnum)
+ continue; /* Internal fixup, no need to apply it. */
+ else if (pSym->st_shndx == SHN_ABS)
+ Value = *pAddrR + offDelta; /* Simplified. */
+ else if (pSym->st_shndx == SHN_UNDEF)
+ {
+ const Elf_Addr SourceAddr = SecAddr + paRels[iRel].r_offset + BaseAddr; /* Where the source really is. */
+ Value = SymValue + *(uint32_t *)pAddrR - SourceAddr;
+ *(uint32_t *)pAddrW = Value;
+ }
+ else
+ AssertFailedReturn(VERR_LDR_GENERAL_FAILURE); /** @todo SHN_COMMON */
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_PC32 Value=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value));
+ break;
+ }
+
+#elif ELF_MODE == 64
+ /*
+ * GOT/PLT.
+ */
+ case R_X86_64_GLOB_DAT:
+ {
+ *(uint64_t *)pAddrW = (uint64_t)SymValue;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_GLOB_DAT Value=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, SymValue));
+ AssertCompile(sizeof(SymValue) == sizeof(uint64_t));
+ break;
+ }
+
+ case R_X86_64_JMP_SLOT:
+ {
+ *(uint64_t *)pAddrW = (uint64_t)SymValue;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_JMP_SLOT Value=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, SymValue));
+ AssertCompile(sizeof(SymValue) == sizeof(uint64_t));
+ break;
+ }
+
+ /*
+ * Absolute addressing.
+ */
+ case R_X86_64_64:
+ {
+ const Elf_Addr Value = SymValue + paRels[iRel].r_addend;
+ *(uint64_t *)pAddrW = Value;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_64 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value, SymValue));
+ break;
+ }
+
+ /*
+ * Truncated 32-bit value (zero-extendedable to the 64-bit value).
+ */
+ case R_X86_64_32:
+ {
+ const Elf_Addr Value = SymValue + paRels[iRel].r_addend;
+ *(uint32_t *)pAddrW = (uint32_t)Value;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value, SymValue));
+ AssertMsgReturn((Elf_Addr)*(uint32_t *)pAddrW == SymValue, ("Value=" FMT_ELF_ADDR "\n", SymValue),
+ VERR_SYMBOL_VALUE_TOO_BIG);
+ break;
+ }
+
+ /*
+ * Truncated 32-bit value (sign-extendedable to the 64-bit value).
+ */
+ case R_X86_64_32S:
+ {
+ const Elf_Addr Value = SymValue + paRels[iRel].r_addend;
+ *(int32_t *)pAddrW = (int32_t)Value;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_32S Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value, SymValue));
+ AssertMsgReturn((Elf_Addr)*(int32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); /** @todo check the sign-extending here. */
+ break;
+ }
+
+ /*
+ * PC relative addressing.
+ */
+ case R_X86_64_PC32:
+ {
+ const Elf_Addr SourceAddr = SecAddr + paRels[iRel].r_offset + BaseAddr; /* Where the source really is. */
+ const Elf_Addr Value = SymValue + paRels[iRel].r_addend - SourceAddr;
+ *(int32_t *)pAddrW = (int32_t)Value;
+ Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_PC32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SourceAddr, paRels[iRel].r_offset, Value, SymValue));
+ AssertMsgReturn((Elf_Addr)*(int32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); /** @todo check the sign-extending here. */
+ break;
+ }
+
+#endif
+ default:
+ AssertMsgFailed(("Unknown relocation type: %d (iRel=%d iRelMax=%d)\n",
+ ELF_R_TYPE(paRels[iRel].r_info), iRel, iRelMax));
+ return VERR_LDRELF_RELOCATION_NOT_SUPPORTED;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+/*
+ *
+ * REL
+ * REL
+ * REL
+ * REL
+ * REL
+ *
+ */
+
+/**
+ * Get the symbol and symbol value.
+ *
+ * @returns iprt status code.
+ * @param pModElf The ELF loader module instance data.
+ * @param BaseAddr The base address which the module is being fixedup to.
+ * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals).
+ * @param pvUser User argument to pass to the callback.
+ * @param iSym The symbol to get.
+ * @param ppSym Where to store the symbol pointer on success. (read only)
+ * @param pSymValue Where to store the symbol value on success.
+ */
+static int RTLDRELF_NAME(Symbol)(PRTLDRMODELF pModElf, Elf_Addr BaseAddr, PFNRTLDRIMPORT pfnGetImport, void *pvUser,
+ Elf_Size iSym, const Elf_Sym **ppSym, Elf_Addr *pSymValue)
+{
+ /*
+ * Validate and find the symbol.
+ */
+ AssertMsgReturn(iSym < pModElf->Rel.cSyms, ("iSym=%d is an invalid symbol index!\n", iSym), VERR_LDRELF_INVALID_SYMBOL_INDEX);
+ const Elf_Sym *pSym = &pModElf->Rel.paSyms[iSym];
+ *ppSym = pSym;
+
+ AssertMsgReturn(pSym->st_name < pModElf->Rel.cbStr,
+ ("iSym=%d st_name=%d str sh_size=%d\n", iSym, pSym->st_name, pModElf->Rel.cbStr),
+ VERR_LDRELF_INVALID_SYMBOL_NAME_OFFSET);
+ const char *pszName = ELF_STR(pModElf, pSym->st_name);
+
+ /*
+ * Determine the symbol value.
+ *
+ * Symbols needs different treatment depending on which section their are in.
+ * Undefined and absolute symbols goes into special non-existing sections.
+ */
+ switch (pSym->st_shndx)
+ {
+ /*
+ * Undefined symbol, needs resolving.
+ *
+ * Since ELF has no generic concept of importing from specific module (the OS/2 ELF format
+ * has but that's an OS extension and only applies to programs and dlls), we'll have to ask
+ * the resolver callback to do a global search.
+ */
+ case SHN_UNDEF:
+ {
+ /* Try to resolve the symbol. */
+ RTUINTPTR Value;
+ int rc = pfnGetImport(&pModElf->Core, "", pszName, ~0U, &Value, pvUser);
+ AssertMsgRCReturn(rc, ("Failed to resolve '%s' (iSym=" FMT_ELF_SIZE " rc=%Rrc\n", pszName, iSym, rc), rc);
+ *pSymValue = (Elf_Addr)Value;
+
+ AssertMsgReturn((RTUINTPTR)*pSymValue == Value,
+ ("Symbol value overflowed! '%s' (iSym=" FMT_ELF_SIZE ")\n", pszName, iSym),
+ VERR_SYMBOL_VALUE_TOO_BIG);
+
+ Log2(("rtldrELF: #%-3d - UNDEF " FMT_ELF_ADDR " '%s'\n", iSym, *pSymValue, pszName));
+ break;
+ }
+
+ /*
+ * Absolute symbols needs no fixing since they are, well, absolute.
+ */
+ case SHN_ABS:
+ *pSymValue = pSym->st_value;
+ Log2(("rtldrELF: #%-3d - ABS " FMT_ELF_ADDR " '%s'\n", iSym, *pSymValue, pszName));
+ break;
+
+ /*
+ * All other symbols are addressed relative to their section and need to be fixed up.
+ */
+ default:
+ if (pSym->st_shndx >= pModElf->Ehdr.e_shnum)
+ {
+ /* what about common symbols? */
+ AssertMsg(pSym->st_shndx < pModElf->Ehdr.e_shnum,
+ ("iSym=%d st_shndx=%d e_shnum=%d pszName=%s\n", iSym, pSym->st_shndx, pModElf->Ehdr.e_shnum, pszName));
+ return VERR_BAD_EXE_FORMAT;
+ }
+ *pSymValue = pSym->st_value + pModElf->paShdrs[pSym->st_shndx].sh_addr + BaseAddr;
+ Log2(("rtldrELF: #%-3d - %5d " FMT_ELF_ADDR " '%s'\n", iSym, pSym->st_shndx, *pSymValue, pszName));
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Applies the fixups for a sections.
+ *
+ * @returns iprt status code.
+ * @param pModElf The ELF loader module instance data.
+ * @param BaseAddr The base address which the module is being fixedup to.
+ * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals).
+ * @param pvUser User argument to pass to the callback.
+ * @param SecAddr The section address. This is the address the relocations are relative to.
+ * @param cbSec The section size. The relocations must be inside this.
+ * @param pu8SecBaseR Where we read section bits from.
+ * @param pu8SecBaseW Where we write section bits to.
+ * @param pvRelocs Pointer to where we read the relocations from.
+ * @param cbRelocs Size of the relocations.
+ */
+static int RTLDRELF_NAME(RelocateSectionRel)(PRTLDRMODELF pModElf, Elf_Addr BaseAddr, PFNRTLDRIMPORT pfnGetImport, void *pvUser,
+ const Elf_Addr SecAddr, Elf_Size cbSec, const uint8_t *pu8SecBaseR,
+ uint8_t *pu8SecBaseW, const void *pvRelocs, Elf_Size cbRelocs)
+{
+#if ELF_MODE != 32
+ NOREF(pu8SecBaseR);
+#endif
+
+ /*
+ * Iterate the relocations.
+ * The relocations are stored in an array of Elf32_Rel records and covers the entire relocation section.
+ */
+ const Elf_Reloc *paRels = (const Elf_Reloc *)pvRelocs;
+ const unsigned iRelMax = (unsigned)(cbRelocs / sizeof(paRels[0]));
+ AssertMsgReturn(iRelMax == cbRelocs / sizeof(paRels[0]), (FMT_ELF_SIZE "\n", cbRelocs / sizeof(paRels[0])), VERR_IMAGE_TOO_BIG);
+ for (unsigned iRel = 0; iRel < iRelMax; iRel++)
+ {
+ /*
+ * Skip R_XXX_NONE entries early to avoid confusion in the symbol
+ * getter code.
+ */
+#if ELF_MODE == 32
+ if (ELF_R_TYPE(paRels[iRel].r_info) == R_386_NONE)
+ continue;
+#elif ELF_MODE == 64
+ if (ELF_R_TYPE(paRels[iRel].r_info) == R_X86_64_NONE)
+ continue;
+#endif
+
+
+ /*
+ * Get the symbol.
+ */
+ const Elf_Sym *pSym = NULL; /* shut up gcc */
+ Elf_Addr SymValue = 0; /* shut up gcc-4 */
+ int rc = RTLDRELF_NAME(Symbol)(pModElf, BaseAddr, pfnGetImport, pvUser, ELF_R_SYM(paRels[iRel].r_info), &pSym, &SymValue);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Log3(("rtldrELF: " FMT_ELF_ADDR " %02x %06x - " FMT_ELF_ADDR " %3d %02x %s\n",
+ paRels[iRel].r_offset, ELF_R_TYPE(paRels[iRel].r_info), (unsigned)ELF_R_SYM(paRels[iRel].r_info),
+ SymValue, (unsigned)pSym->st_shndx, pSym->st_info, ELF_STR(pModElf, pSym->st_name)));
+
+ /*
+ * Apply the fixup.
+ */
+ AssertMsgReturn(paRels[iRel].r_offset < cbSec, (FMT_ELF_ADDR " " FMT_ELF_SIZE "\n", paRels[iRel].r_offset, cbSec), VERR_LDRELF_INVALID_RELOCATION_OFFSET);
+#if ELF_MODE == 32
+ const Elf_Addr *pAddrR = (const Elf_Addr *)(pu8SecBaseR + paRels[iRel].r_offset); /* Where to read the addend. */
+#endif
+ Elf_Addr *pAddrW = (Elf_Addr *)(pu8SecBaseW + paRels[iRel].r_offset); /* Where to write the fixup. */
+ switch (ELF_R_TYPE(paRels[iRel].r_info))
+ {
+#if ELF_MODE == 32
+ /*
+ * Absolute addressing.
+ */
+ case R_386_32:
+ {
+ const Elf_Addr Value = SymValue + *pAddrR;
+ *(uint32_t *)pAddrW = Value;
+ Log4((FMT_ELF_ADDR": R_386_32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, Value, SymValue));
+ break;
+ }
+
+ /*
+ * PC relative addressing.
+ */
+ case R_386_PC32:
+ {
+ const Elf_Addr SourceAddr = SecAddr + paRels[iRel].r_offset + BaseAddr; /* Where the source really is. */
+ const Elf_Addr Value = SymValue + *(uint32_t *)pAddrR - SourceAddr;
+ *(uint32_t *)pAddrW = Value;
+ Log4((FMT_ELF_ADDR": R_386_PC32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SourceAddr, Value, SymValue));
+ break;
+ }
+
+ /* ignore */
+ case R_386_NONE:
+ break;
+
+#elif ELF_MODE == 64
+
+ /*
+ * Absolute addressing
+ */
+ case R_X86_64_64:
+ {
+ const Elf_Addr Value = SymValue + paRels[iRel].r_addend;
+ *(uint64_t *)pAddrW = Value;
+ Log4((FMT_ELF_ADDR": R_X86_64_64 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, Value, SymValue));
+ break;
+ }
+
+ /*
+ * Truncated 32-bit value (zero-extendedable to the 64-bit value).
+ */
+ case R_X86_64_32:
+ {
+ const Elf_Addr Value = SymValue + paRels[iRel].r_addend;
+ *(uint32_t *)pAddrW = (uint32_t)Value;
+ Log4((FMT_ELF_ADDR": R_X86_64_32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, Value, SymValue));
+ AssertMsgReturn((Elf_Addr)*(uint32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG);
+ break;
+ }
+
+ /*
+ * Truncated 32-bit value (sign-extendedable to the 64-bit value).
+ */
+ case R_X86_64_32S:
+ {
+ const Elf_Addr Value = SymValue + paRels[iRel].r_addend;
+ *(int32_t *)pAddrW = (int32_t)Value;
+ Log4((FMT_ELF_ADDR": R_X86_64_32S Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SecAddr + paRels[iRel].r_offset + BaseAddr, Value, SymValue));
+ AssertMsgReturn((Elf_Addr)*(int32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); /** @todo check the sign-extending here. */
+ break;
+ }
+
+ /*
+ * PC relative addressing.
+ */
+ case R_X86_64_PC32:
+ case R_X86_64_PLT32: /* binutils commit 451875b4f976a527395e9303224c7881b65e12ed feature/regression. */
+ {
+ const Elf_Addr SourceAddr = SecAddr + paRels[iRel].r_offset + BaseAddr; /* Where the source really is. */
+ const Elf_Addr Value = SymValue + paRels[iRel].r_addend - SourceAddr;
+ *(int32_t *)pAddrW = (int32_t)Value;
+ Log4((FMT_ELF_ADDR": R_X86_64_PC32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n",
+ SourceAddr, Value, SymValue));
+ AssertMsgReturn((Elf_Addr)*(int32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); /** @todo check the sign-extending here. */
+ break;
+ }
+
+ /* ignore */
+ case R_X86_64_NONE:
+ break;
+#endif
+
+ default:
+ AssertMsgFailed(("Unknown relocation type: %d (iRel=%d iRelMax=%d)\n",
+ ELF_R_TYPE(paRels[iRel].r_info), iRel, iRelMax));
+ return VERR_LDRELF_RELOCATION_NOT_SUPPORTED;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+/** @copydoc RTLDROPS::pfnClose */
+static DECLCALLBACK(int) RTLDRELF_NAME(Close)(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+
+ if (pModElf->paShdrs)
+ {
+ RTMemFree(pModElf->paShdrs);
+ pModElf->paShdrs = NULL;
+ }
+
+ if (pModElf->paPhdrs)
+ {
+ RTMemFree(pModElf->paPhdrs);
+ pModElf->paPhdrs = NULL;
+ }
+
+ if (pModElf->paDynamic)
+ {
+ RTMemFree(pModElf->paDynamic);
+ pModElf->paDynamic = NULL;
+ }
+
+ if (pModElf->pvBits)
+ {
+ pModElf->Core.pReader->pfnUnmap(pModElf->Core.pReader, pModElf->pvBits);
+ pModElf->pvBits = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTLDROPS::Done */
+static DECLCALLBACK(int) RTLDRELF_NAME(Done)(PRTLDRMODINTERNAL pMod)
+{
+ NOREF(pMod); /*PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;*/
+ /** @todo Have to think more about this .... */
+ return -1;
+}
+
+
+/** @copydoc RTLDROPS::pfnEnumSymbols */
+static DECLCALLBACK(int) RTLDRELF_NAME(EnumSymbols)(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits,
+ RTUINTPTR BaseAddress, PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+ NOREF(pvBits);
+
+ /*
+ * Validate the input.
+ */
+ Elf_Addr BaseAddr = (Elf_Addr)BaseAddress;
+ AssertMsgReturn((RTUINTPTR)BaseAddr == BaseAddress, ("%RTptr", BaseAddress), VERR_IMAGE_BASE_TOO_HIGH);
+
+ /*
+ * Make sure we've got the string and symbol tables. (We don't need the pvBits.)
+ */
+ int rc = RTLDRELF_NAME(MapBits)(pModElf, false);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Enumerate the symbol table.
+ */
+ const Elf_Sym *paSyms = pModElf->Rel.paSyms;
+ unsigned cSyms = pModElf->Rel.cSyms;
+ const char *pszzStr = pModElf->Rel.pStr;
+ unsigned cbStr = pModElf->Rel.cbStr;
+ if ( ( !(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_ALL)
+ && pModElf->Dyn.cSyms > 0)
+ || cSyms == 0)
+ {
+ paSyms = pModElf->Dyn.paSyms;
+ cSyms = pModElf->Dyn.cSyms;
+ pszzStr = pModElf->Dyn.pStr;
+ cbStr = pModElf->Dyn.cbStr;
+ }
+
+ for (unsigned iSym = 1; iSym < cSyms; iSym++)
+ {
+ /*
+ * Skip imports (undefined).
+ */
+ if (paSyms[iSym].st_shndx != SHN_UNDEF)
+ {
+ /*
+ * Calc value and get name.
+ */
+ Elf_Addr Value;
+ if (paSyms[iSym].st_shndx == SHN_ABS)
+ /* absolute symbols are not subject to any relocation. */
+ Value = paSyms[iSym].st_value;
+ else if (paSyms[iSym].st_shndx < pModElf->Ehdr.e_shnum)
+ {
+ if (pModElf->Ehdr.e_type == ET_REL)
+ /* relative to the section. */
+ Value = BaseAddr + paSyms[iSym].st_value + pModElf->paShdrs[paSyms[iSym].st_shndx].sh_addr;
+ else /* Fixed up for link address. */
+ Value = BaseAddr + paSyms[iSym].st_value - pModElf->LinkAddress;
+ }
+ else
+ {
+ AssertMsgFailed(("Arg! paSyms[%u].st_shndx=" FMT_ELF_HALF "\n", iSym, paSyms[iSym].st_shndx));
+ return VERR_BAD_EXE_FORMAT;
+ }
+
+ AssertMsgReturn(paSyms[iSym].st_name < cbStr,
+ ("String outside string table! iSym=%d paSyms[iSym].st_name=%#x\n", iSym, paSyms[iSym].st_name),
+ VERR_LDRELF_INVALID_SYMBOL_NAME_OFFSET);
+ const char * const pszName = pszzStr + paSyms[iSym].st_name;
+
+ /* String termination was already checked when the string table was mapped. */
+ if ( *pszName != '\0'
+ && ( (fFlags & RTLDR_ENUM_SYMBOL_FLAGS_ALL)
+ || ELF_ST_BIND(paSyms[iSym].st_info) == STB_GLOBAL) )
+ {
+ /*
+ * Call back.
+ */
+ AssertMsgReturn(Value == (RTUINTPTR)Value, (FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG);
+ rc = pfnCallback(pMod, pszName, iSym, (RTUINTPTR)Value, pvUser);
+ if (rc)
+ return rc;
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTLDROPS::GetImageSize */
+static DECLCALLBACK(size_t) RTLDRELF_NAME(GetImageSize)(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+
+ return pModElf->cbImage;
+}
+
+
+/** @copydoc RTLDROPS::GetBits */
+static DECLCALLBACK(int) RTLDRELF_NAME(GetBits)(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+
+ /*
+ * This operation is currently only available on relocatable images.
+ */
+ switch (pModElf->Ehdr.e_type)
+ {
+ case ET_REL:
+ case ET_DYN:
+ break;
+ case ET_EXEC:
+ Log(("RTLdrELF: %s: Executable images are not supported yet!\n", pModElf->Core.pReader->pfnLogName(pModElf->Core.pReader)));
+ return VERR_LDRELF_EXEC;
+ default: AssertFailedReturn(VERR_BAD_EXE_FORMAT);
+ }
+
+ /*
+ * Load the bits into pvBits.
+ */
+ const Elf_Shdr *paShdrs = pModElf->paShdrs;
+ for (unsigned iShdr = 0; iShdr < pModElf->Ehdr.e_shnum; iShdr++)
+ {
+ if (paShdrs[iShdr].sh_flags & SHF_ALLOC)
+ {
+ AssertMsgReturn((size_t)paShdrs[iShdr].sh_size == (size_t)paShdrs[iShdr].sh_size, (FMT_ELF_SIZE "\n", paShdrs[iShdr].sh_size), VERR_IMAGE_TOO_BIG);
+ switch (paShdrs[iShdr].sh_type)
+ {
+ case SHT_NOBITS:
+ memset((uint8_t *)pvBits + paShdrs[iShdr].sh_addr, 0, (size_t)paShdrs[iShdr].sh_size);
+ break;
+
+ case SHT_PROGBITS:
+ default:
+ {
+ int rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, (uint8_t *)pvBits + paShdrs[iShdr].sh_addr,
+ (size_t)paShdrs[iShdr].sh_size, paShdrs[iShdr].sh_offset);
+ if (RT_FAILURE(rc))
+ {
+ Log(("RTLdrELF: %s: Read error when reading " FMT_ELF_SIZE " bytes at " FMT_ELF_OFF ", iShdr=%d\n",
+ pModElf->Core.pReader->pfnLogName(pModElf->Core.pReader),
+ paShdrs[iShdr].sh_size, paShdrs[iShdr].sh_offset, iShdr));
+ return rc;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Relocate the image.
+ */
+ return pModElf->Core.pOps->pfnRelocate(pMod, pvBits, BaseAddress, ~(RTUINTPTR)0, pfnGetImport, pvUser);
+}
+
+
+/** @copydoc RTLDROPS::Relocate */
+static DECLCALLBACK(int) RTLDRELF_NAME(Relocate)(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress,
+ RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+#ifdef LOG_ENABLED
+ const char *pszLogName = pModElf->Core.pReader->pfnLogName(pModElf->Core.pReader);
+#endif
+ NOREF(OldBaseAddress);
+
+ /*
+ * This operation is currently only available on relocatable images.
+ */
+ switch (pModElf->Ehdr.e_type)
+ {
+ case ET_REL:
+ case ET_DYN:
+ break;
+ case ET_EXEC:
+ Log(("RTLdrELF: %s: Executable images are not supported yet!\n", pszLogName));
+ return VERR_LDRELF_EXEC;
+ default: AssertFailedReturn(VERR_BAD_EXE_FORMAT);
+ }
+
+ /*
+ * Validate the input.
+ */
+ Elf_Addr BaseAddr = (Elf_Addr)NewBaseAddress;
+ AssertMsgReturn((RTUINTPTR)BaseAddr == NewBaseAddress, ("%RTptr", NewBaseAddress), VERR_IMAGE_BASE_TOO_HIGH);
+
+ /*
+ * Map the image bits if not already done and setup pointer into it.
+ */
+ int rc = RTLDRELF_NAME(MapBits)(pModElf, true);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Iterate the sections looking for interesting SHT_REL[A] sections.
+ *
+ * In ET_REL files the SHT_REL[A] sections have the section index of
+ * the section they contain fixups for in the sh_info member.
+ */
+ const Elf_Shdr *paShdrs = pModElf->paShdrs;
+ Log2(("rtLdrElf: %s: Fixing up image\n", pszLogName));
+ for (unsigned iShdr = 0; iShdr < pModElf->Ehdr.e_shnum; iShdr++)
+ {
+ const Elf_Shdr *pShdrRel = &paShdrs[iShdr];
+
+ /*
+ * Skip sections without interest to us.
+ */
+#if ELF_MODE == 32
+ if (pShdrRel->sh_type != SHT_REL)
+#else
+ if (pShdrRel->sh_type != SHT_RELA)
+#endif
+ continue;
+ if (pModElf->Ehdr.e_type == ET_REL)
+ {
+ if (pShdrRel->sh_info >= pModElf->Ehdr.e_shnum)
+ continue;
+ const Elf_Shdr *pShdr = &paShdrs[pShdrRel->sh_info]; /* the section to fixup. */
+ if (!(pShdr->sh_flags & SHF_ALLOC))
+ continue;
+
+ /*
+ * Relocate the section.
+ */
+ Log2(("rtldrELF: %s: Relocation records for #%d [%s] (sh_info=%d sh_link=%d) found in #%d [%s] (sh_info=%d sh_link=%d)\n",
+ pszLogName, (int)pShdrRel->sh_info, ELF_SH_STR(pModElf, pShdr->sh_name), (int)pShdr->sh_info, (int)pShdr->sh_link,
+ iShdr, ELF_SH_STR(pModElf, pShdrRel->sh_name), (int)pShdrRel->sh_info, (int)pShdrRel->sh_link));
+
+ rc = RTLDRELF_NAME(RelocateSectionRel)(pModElf, BaseAddr, pfnGetImport, pvUser,
+ pShdr->sh_addr,
+ pShdr->sh_size,
+ (const uint8_t *)pModElf->pvBits + pShdr->sh_offset,
+ (uint8_t *)pvBits + pShdr->sh_addr,
+ (const uint8_t *)pModElf->pvBits + pShdrRel->sh_offset,
+ pShdrRel->sh_size);
+ }
+ else
+ rc = RTLDRELF_NAME(RelocateSectionExecDyn)(pModElf, BaseAddr, pfnGetImport, pvUser,
+ 0, (Elf_Size)pModElf->cbImage,
+ (const uint8_t *)pModElf->pvBits /** @todo file offset ?? */,
+ (uint8_t *)pvBits,
+ (const uint8_t *)pModElf->pvBits + pShdrRel->sh_offset,
+ pShdrRel->sh_size);
+
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for pfnGetSymbolEx.
+ */
+static int RTLDRELF_NAME(ReturnSymbol)(PRTLDRMODELF pThis, const Elf_Sym *pSym, Elf_Addr uBaseAddr, PRTUINTPTR pValue)
+{
+ Elf_Addr Value;
+ if (pSym->st_shndx == SHN_ABS)
+ /* absolute symbols are not subject to any relocation. */
+ Value = pSym->st_value;
+ else if (pSym->st_shndx < pThis->Ehdr.e_shnum)
+ {
+ if (pThis->Ehdr.e_type == ET_REL)
+ /* relative to the section. */
+ Value = uBaseAddr + pSym->st_value + pThis->paShdrs[pSym->st_shndx].sh_addr;
+ else /* Fixed up for link address. */
+ Value = uBaseAddr + pSym->st_value - pThis->LinkAddress;
+ }
+ else
+ {
+ AssertMsgFailed(("Arg! pSym->st_shndx=%d\n", pSym->st_shndx));
+ return VERR_BAD_EXE_FORMAT;
+ }
+ AssertMsgReturn(Value == (RTUINTPTR)Value, (FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG);
+ *pValue = (RTUINTPTR)Value;
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTLDROPS::pfnGetSymbolEx */
+static DECLCALLBACK(int) RTLDRELF_NAME(GetSymbolEx)(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress,
+ uint32_t iOrdinal, const char *pszSymbol, RTUINTPTR *pValue)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+ NOREF(pvBits);
+
+ /*
+ * Validate the input.
+ */
+ Elf_Addr uBaseAddr = (Elf_Addr)BaseAddress;
+ AssertMsgReturn((RTUINTPTR)uBaseAddr == BaseAddress, ("%RTptr", BaseAddress), VERR_IMAGE_BASE_TOO_HIGH);
+
+ /*
+ * Map the image bits if not already done and setup pointer into it.
+ */
+ int rc = RTLDRELF_NAME(MapBits)(pModElf, true);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Calc all kinds of pointers before we start iterating the symbol table.
+ */
+ const Elf_Sym *paSyms = pModElf->Rel.paSyms;
+ unsigned cSyms = pModElf->Rel.cSyms;
+ const char *pszzStr = pModElf->Rel.pStr;
+ unsigned cbStr = pModElf->Rel.cbStr;
+ if (pModElf->Dyn.cSyms > 0)
+ {
+ paSyms = pModElf->Dyn.paSyms;
+ cSyms = pModElf->Dyn.cSyms;
+ pszzStr = pModElf->Dyn.pStr;
+ cbStr = pModElf->Dyn.cbStr;
+ }
+
+ if (iOrdinal == UINT32_MAX)
+ {
+ for (unsigned iSym = 1; iSym < cSyms; iSym++)
+ {
+ /* Undefined symbols are not exports, they are imports. */
+ if ( paSyms[iSym].st_shndx != SHN_UNDEF
+ && ( ELF_ST_BIND(paSyms[iSym].st_info) == STB_GLOBAL
+ || ELF_ST_BIND(paSyms[iSym].st_info) == STB_WEAK))
+ {
+ /* Validate the name string and try match with it. */
+ AssertMsgReturn(paSyms[iSym].st_name < cbStr,
+ ("String outside string table! iSym=%d paSyms[iSym].st_name=%#x\n", iSym, paSyms[iSym].st_name),
+ VERR_LDRELF_INVALID_SYMBOL_NAME_OFFSET);
+ if (!strcmp(pszSymbol, pszzStr + paSyms[iSym].st_name))
+ {
+ /* matched! */
+ return RTLDRELF_NAME(ReturnSymbol)(pModElf, &paSyms[iSym], uBaseAddr, pValue);
+ }
+ }
+ }
+ }
+ else if (iOrdinal < cSyms)
+ {
+ if ( paSyms[iOrdinal].st_shndx != SHN_UNDEF
+ && ( ELF_ST_BIND(paSyms[iOrdinal].st_info) == STB_GLOBAL
+ || ELF_ST_BIND(paSyms[iOrdinal].st_info) == STB_WEAK))
+ return RTLDRELF_NAME(ReturnSymbol)(pModElf, &paSyms[iOrdinal], uBaseAddr, pValue);
+ }
+
+ return VERR_SYMBOL_NOT_FOUND;
+}
+
+
+/** @copydoc RTLDROPS::pfnEnumDbgInfo */
+static DECLCALLBACK(int) RTLDRELF_NAME(EnumDbgInfo)(PRTLDRMODINTERNAL pMod, const void *pvBits,
+ PFNRTLDRENUMDBG pfnCallback, void *pvUser)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+ RT_NOREF_PV(pvBits);
+
+ /*
+ * Map the image bits if not already done and setup pointer into it.
+ */
+ int rc = RTLDRELF_NAME(MapBits)(pModElf, true);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Do the enumeration.
+ */
+ const Elf_Shdr *paShdrs = pModElf->paOrgShdrs;
+ for (unsigned iShdr = 0; iShdr < pModElf->Ehdr.e_shnum; iShdr++)
+ {
+ /* Debug sections are expected to be PROGBITS and not allocated. */
+ if (paShdrs[iShdr].sh_type != SHT_PROGBITS)
+ continue;
+ if (paShdrs[iShdr].sh_flags & SHF_ALLOC)
+ continue;
+
+ RTLDRDBGINFO DbgInfo;
+ const char *pszSectName = ELF_SH_STR(pModElf, paShdrs[iShdr].sh_name);
+ if ( !strncmp(pszSectName, RT_STR_TUPLE(".debug_"))
+ || !strcmp(pszSectName, ".WATCOM_references") )
+ {
+ RT_ZERO(DbgInfo.u);
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF;
+ DbgInfo.pszExtFile = NULL;
+ DbgInfo.offFile = paShdrs[iShdr].sh_offset;
+ DbgInfo.cb = paShdrs[iShdr].sh_size;
+ DbgInfo.u.Dwarf.pszSection = pszSectName;
+ }
+ else if (!strcmp(pszSectName, ".gnu_debuglink"))
+ {
+ if ((paShdrs[iShdr].sh_size & 3) || paShdrs[iShdr].sh_size < 8)
+ return VERR_BAD_EXE_FORMAT;
+
+ RT_ZERO(DbgInfo.u);
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO;
+ DbgInfo.pszExtFile = (const char *)((uintptr_t)pModElf->pvBits + (uintptr_t)paShdrs[iShdr].sh_offset);
+ if (!RTStrEnd(DbgInfo.pszExtFile, paShdrs[iShdr].sh_size))
+ return VERR_BAD_EXE_FORMAT;
+ DbgInfo.u.Dwo.uCrc32 = *(uint32_t *)((uintptr_t)DbgInfo.pszExtFile + (uintptr_t)paShdrs[iShdr].sh_size
+ - sizeof(uint32_t));
+ DbgInfo.offFile = -1;
+ DbgInfo.cb = 0;
+ }
+ else
+ continue;
+
+ DbgInfo.LinkAddress = NIL_RTLDRADDR;
+ DbgInfo.iDbgInfo = iShdr - 1;
+
+ rc = pfnCallback(pMod, &DbgInfo, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Locate the next allocated section by RVA (sh_addr).
+ *
+ * This is a helper for EnumSegments and SegOffsetToRva.
+ *
+ * @returns Pointer to the section header if found, NULL if none.
+ * @param pModElf The module instance.
+ * @param iShdrCur The current section header.
+ */
+static const Elf_Shdr *RTLDRELF_NAME(GetNextAllocatedSection)(PRTLDRMODELF pModElf, unsigned iShdrCur)
+{
+ unsigned const cShdrs = pModElf->Ehdr.e_shnum;
+ const Elf_Shdr * const paShdrs = pModElf->paShdrs;
+ if (pModElf->fShdrInOrder)
+ {
+ for (unsigned iShdr = iShdrCur + 1; iShdr < cShdrs; iShdr++)
+ if (paShdrs[iShdr].sh_flags & SHF_ALLOC)
+ return &paShdrs[iShdr];
+ }
+ else
+ {
+ Elf_Addr const uEndCur = paShdrs[iShdrCur].sh_addr + paShdrs[iShdrCur].sh_size;
+ Elf_Addr offBest = ~(Elf_Addr)0;
+ unsigned iBest = cShdrs;
+ for (unsigned iShdr = pModElf->iFirstSect; iShdr < cShdrs; iShdr++)
+ if ((paShdrs[iShdr].sh_flags & SHF_ALLOC) && iShdr != iShdrCur)
+ {
+ Elf_Addr const offDelta = paShdrs[iShdr].sh_addr - uEndCur;
+ if ( offDelta < offBest
+ && paShdrs[iShdr].sh_addr >= uEndCur)
+ {
+ offBest = offDelta;
+ iBest = iShdr;
+ }
+ }
+ if (iBest < cShdrs)
+ return &paShdrs[iBest];
+ }
+ return NULL;
+}
+
+
+/** @copydoc RTLDROPS::pfnEnumSegments. */
+static DECLCALLBACK(int) RTLDRELF_NAME(EnumSegments)(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+
+ /*
+ * Map the image bits if not already done and setup pointer into it.
+ */
+ int rc = RTLDRELF_NAME(MapBits)(pModElf, true);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Do the enumeration.
+ */
+ char szName[32];
+ Elf_Addr uPrevMappedRva = 0;
+ const Elf_Shdr *paShdrs = pModElf->paShdrs;
+ const Elf_Shdr *paOrgShdrs = pModElf->paOrgShdrs;
+ for (unsigned iShdr = pModElf->iFirstSect; iShdr < pModElf->Ehdr.e_shnum; iShdr++)
+ {
+ RTLDRSEG Seg;
+ if (iShdr != 0)
+ {
+ Seg.pszName = ELF_SH_STR(pModElf, paShdrs[iShdr].sh_name);
+ Seg.cchName = (uint32_t)strlen(Seg.pszName);
+ if (Seg.cchName == 0)
+ {
+ Seg.pszName = szName;
+ Seg.cchName = (uint32_t)RTStrPrintf(szName, sizeof(szName), "UnamedSect%02u", iShdr);
+ }
+ }
+ else
+ {
+ Seg.pszName = ".elf.headers";
+ Seg.cchName = 12;
+ }
+ Seg.SelFlat = 0;
+ Seg.Sel16bit = 0;
+ Seg.fFlags = 0;
+ Seg.fProt = RTMEM_PROT_READ;
+ if (paShdrs[iShdr].sh_flags & SHF_WRITE)
+ Seg.fProt |= RTMEM_PROT_WRITE;
+ if (paShdrs[iShdr].sh_flags & SHF_EXECINSTR)
+ Seg.fProt |= RTMEM_PROT_EXEC;
+ Seg.cb = paShdrs[iShdr].sh_size;
+ Seg.Alignment = paShdrs[iShdr].sh_addralign;
+ if (paShdrs[iShdr].sh_flags & SHF_ALLOC)
+ {
+ Seg.LinkAddress = paOrgShdrs[iShdr].sh_addr;
+ Seg.RVA = paShdrs[iShdr].sh_addr;
+ const Elf_Shdr *pShdr2 = RTLDRELF_NAME(GetNextAllocatedSection)(pModElf, iShdr);
+ if (pShdr2)
+ Seg.cbMapped = pShdr2->sh_addr - paShdrs[iShdr].sh_addr;
+ else
+ Seg.cbMapped = pModElf->cbImage - paShdrs[iShdr].sh_addr;
+ uPrevMappedRva = Seg.RVA;
+ }
+ else
+ {
+ Seg.LinkAddress = NIL_RTLDRADDR;
+ Seg.RVA = NIL_RTLDRADDR;
+ Seg.cbMapped = NIL_RTLDRADDR;
+ }
+ if (paShdrs[iShdr].sh_type != SHT_NOBITS)
+ {
+ Seg.offFile = paShdrs[iShdr].sh_offset;
+ Seg.cbFile = paShdrs[iShdr].sh_size;
+ }
+ else
+ {
+ Seg.offFile = -1;
+ Seg.cbFile = 0;
+ }
+
+ rc = pfnCallback(pMod, &Seg, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTLDROPS::pfnLinkAddressToSegOffset. */
+static DECLCALLBACK(int) RTLDRELF_NAME(LinkAddressToSegOffset)(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress,
+ uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+
+ const Elf_Shdr *pShdrEnd = NULL;
+ unsigned cLeft = pModElf->Ehdr.e_shnum - pModElf->iFirstSect;
+ const Elf_Shdr *pShdr = &pModElf->paOrgShdrs[pModElf->Ehdr.e_shnum];
+ while (cLeft-- > 0)
+ {
+ pShdr--;
+ if (pShdr->sh_flags & SHF_ALLOC)
+ {
+ RTLDRADDR offSeg = LinkAddress - pShdr->sh_addr;
+ if (offSeg < pShdr->sh_size)
+ {
+ *poffSeg = offSeg;
+ *piSeg = cLeft;
+ return VINF_SUCCESS;
+ }
+ if (offSeg == pShdr->sh_size)
+ pShdrEnd = pShdr;
+ }
+ }
+
+ if (pShdrEnd)
+ {
+ *poffSeg = pShdrEnd->sh_size;
+ *piSeg = pShdrEnd - pModElf->paOrgShdrs - pModElf->iFirstSect;
+ return VINF_SUCCESS;
+ }
+
+ return VERR_LDR_INVALID_LINK_ADDRESS;
+}
+
+
+/** @copydoc RTLDROPS::pfnLinkAddressToRva. */
+static DECLCALLBACK(int) RTLDRELF_NAME(LinkAddressToRva)(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+ uint32_t iSeg;
+ RTLDRADDR offSeg;
+ int rc = RTLDRELF_NAME(LinkAddressToSegOffset)(pMod, LinkAddress, &iSeg, &offSeg);
+ if (RT_SUCCESS(rc))
+ *pRva = pModElf->paShdrs[iSeg + pModElf->iFirstSect].sh_addr + offSeg;
+ return rc;
+}
+
+
+/** @copydoc RTLDROPS::pfnSegOffsetToRva. */
+static DECLCALLBACK(int) RTLDRELF_NAME(SegOffsetToRva)(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg,
+ PRTLDRADDR pRva)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+ if (iSeg >= pModElf->Ehdr.e_shnum - pModElf->iFirstSect)
+ return VERR_LDR_INVALID_SEG_OFFSET;
+
+ iSeg += pModElf->iFirstSect; /* skip section 0 if not used */
+ if (offSeg > pModElf->paShdrs[iSeg].sh_size)
+ {
+ const Elf_Shdr *pShdr2 = RTLDRELF_NAME(GetNextAllocatedSection)(pModElf, iSeg);
+ if ( !pShdr2
+ || offSeg > (pShdr2->sh_addr - pModElf->paShdrs[iSeg].sh_addr))
+ return VERR_LDR_INVALID_SEG_OFFSET;
+ }
+
+ if (!(pModElf->paShdrs[iSeg].sh_flags & SHF_ALLOC))
+ return VERR_LDR_INVALID_SEG_OFFSET;
+
+ *pRva = pModElf->paShdrs[iSeg].sh_addr;
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTLDROPS::pfnRvaToSegOffset. */
+static DECLCALLBACK(int) RTLDRELF_NAME(RvaToSegOffset)(PRTLDRMODINTERNAL pMod, RTLDRADDR Rva,
+ uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;
+ Elf_Addr PrevAddr = 0;
+ unsigned cLeft = pModElf->Ehdr.e_shnum - pModElf->iFirstSect;
+ const Elf_Shdr *pShdr = &pModElf->paShdrs[pModElf->Ehdr.e_shnum];
+ while (cLeft-- > 0)
+ {
+ pShdr--;
+ if (pShdr->sh_flags & SHF_ALLOC)
+ {
+ Elf_Addr cbSeg = PrevAddr ? PrevAddr - pShdr->sh_addr : pShdr->sh_size;
+ RTLDRADDR offSeg = Rva - pShdr->sh_addr;
+ if (offSeg <= cbSeg)
+ {
+ *poffSeg = offSeg;
+ *piSeg = cLeft;
+ return VINF_SUCCESS;
+ }
+ PrevAddr = pShdr->sh_addr;
+ }
+ }
+
+ return VERR_LDR_INVALID_RVA;
+}
+
+
+/** @callback_method_impl{FNRTLDRIMPORT, Stub used by ReadDbgInfo.} */
+static DECLCALLBACK(int) RTLDRELF_NAME(GetImportStubCallback)(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol,
+ unsigned uSymbol, PRTLDRADDR pValue, void *pvUser)
+{
+ RT_NOREF_PV(hLdrMod); RT_NOREF_PV(pszModule); RT_NOREF_PV(pszSymbol);
+ RT_NOREF_PV(uSymbol); RT_NOREF_PV(pValue); RT_NOREF_PV(pvUser);
+ return VERR_SYMBOL_NOT_FOUND;
+}
+
+
+/** @copydoc RTLDROPS::pfnReadDbgInfo. */
+static DECLCALLBACK(int) RTLDRELF_NAME(ReadDbgInfo)(PRTLDRMODINTERNAL pMod, uint32_t iDbgInfo, RTFOFF off,
+ size_t cb, void *pvBuf)
+{
+ PRTLDRMODELF pThis = (PRTLDRMODELF)pMod;
+ LogFlow(("%s: iDbgInfo=%#x off=%RTfoff cb=%#zu\n", __FUNCTION__, iDbgInfo, off, cb));
+
+ /*
+ * Input validation.
+ */
+ AssertReturn(iDbgInfo < pThis->Ehdr.e_shnum && iDbgInfo + 1 < pThis->Ehdr.e_shnum, VERR_INVALID_PARAMETER);
+ iDbgInfo++;
+ AssertReturn(!(pThis->paShdrs[iDbgInfo].sh_flags & SHF_ALLOC), VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->paShdrs[iDbgInfo].sh_type == SHT_PROGBITS, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->paShdrs[iDbgInfo].sh_offset == (uint64_t)off, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->paShdrs[iDbgInfo].sh_size == cb, VERR_INVALID_PARAMETER);
+ uint64_t cbRawImage = pThis->Core.pReader->pfnSize(pThis->Core.pReader);
+ AssertReturn(off >= 0 && cb <= cbRawImage && (uint64_t)off + cb <= cbRawImage, VERR_INVALID_PARAMETER);
+
+ /*
+ * Read it from the file and look for fixup sections.
+ */
+ int rc;
+ if (pThis->pvBits)
+ memcpy(pvBuf, (const uint8_t *)pThis->pvBits + (size_t)off, cb);
+ else
+ {
+ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvBuf, cb, off);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ uint32_t iRelocs = iDbgInfo + 1;
+ if ( iRelocs >= pThis->Ehdr.e_shnum
+ || pThis->paShdrs[iRelocs].sh_info != iDbgInfo
+ || ( pThis->paShdrs[iRelocs].sh_type != SHT_REL
+ && pThis->paShdrs[iRelocs].sh_type != SHT_RELA) )
+ {
+ iRelocs = 0;
+ while ( iRelocs < pThis->Ehdr.e_shnum
+ && ( pThis->paShdrs[iRelocs].sh_info != iDbgInfo
+ || ( pThis->paShdrs[iRelocs].sh_type != SHT_REL
+ && pThis->paShdrs[iRelocs].sh_type != SHT_RELA)) )
+ iRelocs++;
+ }
+ if ( iRelocs < pThis->Ehdr.e_shnum
+ && pThis->paShdrs[iRelocs].sh_size > 0)
+ {
+ /*
+ * Load the relocations.
+ */
+ uint8_t *pbRelocsBuf = NULL;
+ const uint8_t *pbRelocs;
+ if (pThis->pvBits)
+ pbRelocs = (const uint8_t *)pThis->pvBits + pThis->paShdrs[iRelocs].sh_offset;
+ else
+ {
+ pbRelocs = pbRelocsBuf = (uint8_t *)RTMemTmpAlloc(pThis->paShdrs[iRelocs].sh_size);
+ if (!pbRelocsBuf)
+ return VERR_NO_TMP_MEMORY;
+ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pbRelocsBuf,
+ pThis->paShdrs[iRelocs].sh_size,
+ pThis->paShdrs[iRelocs].sh_offset);
+ if (RT_FAILURE(rc))
+ {
+ RTMemTmpFree(pbRelocsBuf);
+ return rc;
+ }
+ }
+
+ /*
+ * Apply the relocations.
+ */
+ if (pThis->Ehdr.e_type == ET_REL)
+ rc = RTLDRELF_NAME(RelocateSectionRel)(pThis, pThis->LinkAddress,
+ RTLDRELF_NAME(GetImportStubCallback), NULL /*pvUser*/,
+ pThis->paShdrs[iDbgInfo].sh_addr,
+ pThis->paShdrs[iDbgInfo].sh_size,
+ (const uint8_t *)pvBuf,
+ (uint8_t *)pvBuf,
+ pbRelocs,
+ pThis->paShdrs[iRelocs].sh_size);
+ else
+ rc = RTLDRELF_NAME(RelocateSectionExecDyn)(pThis, pThis->LinkAddress,
+ RTLDRELF_NAME(GetImportStubCallback), NULL /*pvUser*/,
+ pThis->paShdrs[iDbgInfo].sh_addr,
+ pThis->paShdrs[iDbgInfo].sh_size,
+ (const uint8_t *)pvBuf,
+ (uint8_t *)pvBuf,
+ pbRelocs,
+ pThis->paShdrs[iRelocs].sh_size);
+
+ RTMemTmpFree(pbRelocsBuf);
+ }
+ else
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+/**
+ * Handles RTLDRPROP_BUILDID queries.
+ */
+static int RTLDRELF_NAME(QueryPropBuildId)(PRTLDRMODELF pThis, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ /*
+ * Map the image bits if not already done and setup pointer into it.
+ */
+ int rc = RTLDRELF_NAME(MapBits)(pThis, true);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Search for the build ID.
+ */
+ const Elf_Shdr *paShdrs = pThis->paOrgShdrs;
+ for (unsigned iShdr = 0; iShdr < pThis->Ehdr.e_shnum; iShdr++)
+ {
+ const char *pszSectName = ELF_SH_STR(pThis, paShdrs[iShdr].sh_name);
+
+ if (!strcmp(pszSectName, ".note.gnu.build-id"))
+ {
+ if ((paShdrs[iShdr].sh_size & 3) || paShdrs[iShdr].sh_size < sizeof(Elf_Nhdr))
+ return VERR_BAD_EXE_FORMAT;
+
+ Elf_Nhdr *pNHdr = (Elf_Nhdr *)((uintptr_t)pThis->pvBits + (uintptr_t)paShdrs[iShdr].sh_offset);
+ if ( pNHdr->n_namesz > paShdrs[iShdr].sh_size
+ || pNHdr->n_descsz > paShdrs[iShdr].sh_size
+ || (paShdrs[iShdr].sh_size - pNHdr->n_descsz) < pNHdr->n_namesz
+ || pNHdr->n_type != NT_GNU_BUILD_ID)
+ return VERR_BAD_EXE_FORMAT;
+
+ const char *pszOwner = (const char *)(pNHdr + 1);
+ if ( !RTStrEnd(pszOwner, pNHdr->n_namesz)
+ || strcmp(pszOwner, "GNU"))
+ return VERR_BAD_EXE_FORMAT;
+
+ if (cbBuf < pNHdr->n_descsz)
+ return VERR_BUFFER_OVERFLOW;
+
+ memcpy(pvBuf, pszOwner + pNHdr->n_namesz, pNHdr->n_descsz);
+ *pcbRet = pNHdr->n_descsz;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnQueryProp} */
+static DECLCALLBACK(int) RTLDRELF_NAME(QueryProp)(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits,
+ void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ PRTLDRMODELF pThis = (PRTLDRMODELF)pMod;
+ RT_NOREF(pvBits);
+ switch (enmProp)
+ {
+ case RTLDRPROP_BUILDID:
+ return RTLDRELF_NAME(QueryPropBuildId)(pThis, pvBuf, cbBuf, pcbRet);
+
+ case RTLDRPROP_IS_SIGNED:
+ *pcbRet = sizeof(bool);
+ return rtLdrELFLnxKModQueryPropIsSigned(pThis->Core.pReader, (bool *)pvBuf);
+
+ case RTLDRPROP_PKCS7_SIGNED_DATA:
+ *pcbRet = sizeof(bool);
+ return rtLdrELFLnxKModQueryPropPkcs7SignedData(pThis->Core.pReader, pvBuf, cbBuf, pcbRet);
+
+ default:
+ return VERR_NOT_FOUND;
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnUnwindFrame}
+ */
+static DECLCALLBACK(int)
+RTLDRELF_NAME(UnwindFrame)(PRTLDRMODINTERNAL pMod, void const *pvBits, uint32_t iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ PRTLDRMODELF pThis = (PRTLDRMODELF)pMod;
+ LogFlow(("%s: iSeg=%#x off=%RTptr\n", __FUNCTION__, iSeg, off));
+
+ /*
+ * Process the input address, making us both RVA and proper seg:offset out of it.
+ */
+ int rc;
+ RTLDRADDR uRva = off;
+ if (iSeg == UINT32_MAX)
+ rc = RTLDRELF_NAME(RvaToSegOffset)(pMod, uRva, &iSeg, &off);
+ else
+ rc = RTLDRELF_NAME(SegOffsetToRva)(pMod, iSeg, off, &uRva);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Map the image bits if not already done and setup pointer into it.
+ */
+ RT_NOREF(pvBits); /** @todo Try use passed in pvBits? */
+ rc = RTLDRELF_NAME(MapBits)(pThis, true);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Do we need to search for .eh_frame and .eh_frame_hdr?
+ */
+ if (pThis->iShEhFrame == 0)
+ {
+ pThis->iShEhFrame = ~0U;
+ pThis->iShEhFrameHdr = ~0U;
+ unsigned cLeft = 2;
+ for (unsigned iShdr = 1; iShdr < pThis->Ehdr.e_shnum; iShdr++)
+ {
+ const char *pszName = ELF_SH_STR(pThis, pThis->paShdrs[iShdr].sh_name);
+ if ( pszName[0] == '.'
+ && pszName[1] == 'e'
+ && pszName[2] == 'h'
+ && pszName[3] == '_'
+ && pszName[4] == 'f'
+ && pszName[5] == 'r'
+ && pszName[6] == 'a'
+ && pszName[7] == 'm'
+ && pszName[8] == 'e')
+ {
+ if (pszName[9] == '\0')
+ pThis->iShEhFrame = iShdr;
+ else if ( pszName[9] == '_'
+ && pszName[10] == 'h'
+ && pszName[11] == 'd'
+ && pszName[12] == 'r'
+ && pszName[13] == '\0')
+ pThis->iShEhFrameHdr = iShdr;
+ else
+ continue;
+ if (--cLeft == 0)
+ break;
+ }
+ }
+ }
+
+ /*
+ * Any info present?
+ */
+ unsigned iShdr = pThis->iShEhFrame;
+ if ( iShdr != ~0U
+ && pThis->paShdrs[iShdr].sh_size > 0)
+ {
+ if (pThis->paShdrs[iShdr].sh_flags & SHF_ALLOC)
+ return rtDwarfUnwind_EhData((uint8_t const *)pThis->pvBits + pThis->paShdrs[iShdr].sh_addr,
+ pThis->paShdrs[iShdr].sh_size, pThis->paShdrs[iShdr].sh_addr,
+ iSeg, off, uRva, pState, pThis->Core.enmArch);
+ }
+ return VERR_DBG_NO_UNWIND_INFO;
+}
+
+
+/**
+ * The ELF module operations.
+ */
+static RTLDROPS RTLDRELF_MID(s_rtldrElf,Ops) =
+{
+#if ELF_MODE == 32
+ "elf32",
+#elif ELF_MODE == 64
+ "elf64",
+#endif
+ RTLDRELF_NAME(Close),
+ NULL, /* Get Symbol */
+ RTLDRELF_NAME(Done),
+ RTLDRELF_NAME(EnumSymbols),
+ /* ext: */
+ RTLDRELF_NAME(GetImageSize),
+ RTLDRELF_NAME(GetBits),
+ RTLDRELF_NAME(Relocate),
+ RTLDRELF_NAME(GetSymbolEx),
+ NULL /*pfnQueryForwarderInfo*/,
+ RTLDRELF_NAME(EnumDbgInfo),
+ RTLDRELF_NAME(EnumSegments),
+ RTLDRELF_NAME(LinkAddressToSegOffset),
+ RTLDRELF_NAME(LinkAddressToRva),
+ RTLDRELF_NAME(SegOffsetToRva),
+ RTLDRELF_NAME(RvaToSegOffset),
+ RTLDRELF_NAME(ReadDbgInfo),
+ RTLDRELF_NAME(QueryProp),
+ NULL /*pfnVerifySignature*/,
+ rtldrELFLnxKModHashImage,
+ RTLDRELF_NAME(UnwindFrame),
+ 42
+};
+
+
+
+/**
+ * Validates the ELF header.
+ *
+ * @returns iprt status code.
+ * @param pEhdr Pointer to the ELF header.
+ * @param cbRawImage The size of the raw image.
+ * @param pszLogName The log name.
+ * @param penmArch Where to return the architecture.
+ * @param pErrInfo Where to return extended error info. Optional.
+ */
+static int RTLDRELF_NAME(ValidateElfHeader)(const Elf_Ehdr *pEhdr, uint64_t cbRawImage, const char *pszLogName,
+ PRTLDRARCH penmArch, PRTERRINFO pErrInfo)
+{
+ Log3(("RTLdrELF: e_ident: %.*Rhxs\n"
+ "RTLdrELF: e_type: " FMT_ELF_HALF "\n"
+ "RTLdrELF: e_version: " FMT_ELF_HALF "\n"
+ "RTLdrELF: e_entry: " FMT_ELF_ADDR "\n"
+ "RTLdrELF: e_phoff: " FMT_ELF_OFF "\n"
+ "RTLdrELF: e_shoff: " FMT_ELF_OFF "\n"
+ "RTLdrELF: e_flags: " FMT_ELF_WORD "\n"
+ "RTLdrELF: e_ehsize: " FMT_ELF_HALF "\n"
+ "RTLdrELF: e_phentsize: " FMT_ELF_HALF "\n"
+ "RTLdrELF: e_phnum: " FMT_ELF_HALF "\n"
+ "RTLdrELF: e_shentsize: " FMT_ELF_HALF "\n"
+ "RTLdrELF: e_shnum: " FMT_ELF_HALF "\n"
+ "RTLdrELF: e_shstrndx: " FMT_ELF_HALF "\n",
+ RT_ELEMENTS(pEhdr->e_ident), &pEhdr->e_ident[0], pEhdr->e_type, pEhdr->e_version,
+ pEhdr->e_entry, pEhdr->e_phoff, pEhdr->e_shoff,pEhdr->e_flags, pEhdr->e_ehsize, pEhdr->e_phentsize,
+ pEhdr->e_phnum, pEhdr->e_shentsize, pEhdr->e_shnum, pEhdr->e_shstrndx));
+
+ if ( pEhdr->e_ident[EI_MAG0] != ELFMAG0
+ || pEhdr->e_ident[EI_MAG1] != ELFMAG1
+ || pEhdr->e_ident[EI_MAG2] != ELFMAG2
+ || pEhdr->e_ident[EI_MAG3] != ELFMAG3)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Invalid ELF magic (%.*Rhxs)", pszLogName, sizeof(pEhdr->e_ident), pEhdr->e_ident);
+ if (pEhdr->e_ident[EI_CLASS] != RTLDRELF_SUFF(ELFCLASS))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Invalid ELF class (%.*Rhxs)", pszLogName, sizeof(pEhdr->e_ident), pEhdr->e_ident);
+ if (pEhdr->e_ident[EI_DATA] != ELFDATA2LSB)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_ODD_ENDIAN,
+ "%s: ELF endian %x is unsupported", pszLogName, pEhdr->e_ident[EI_DATA]);
+ if (pEhdr->e_version != EV_CURRENT)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_VERSION,
+ "%s: ELF version %x is unsupported", pszLogName, pEhdr->e_version);
+
+ if (sizeof(Elf_Ehdr) != pEhdr->e_ehsize)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Elf header e_ehsize is %d expected %d!", pszLogName, pEhdr->e_ehsize, sizeof(Elf_Ehdr));
+ if ( sizeof(Elf_Phdr) != pEhdr->e_phentsize
+ && ( pEhdr->e_phnum != 0
+ || pEhdr->e_type == ET_DYN
+ || pEhdr->e_type == ET_EXEC))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Elf header e_phentsize is %d expected %d!",
+ pszLogName, pEhdr->e_phentsize, sizeof(Elf_Phdr));
+ if (sizeof(Elf_Shdr) != pEhdr->e_shentsize)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Elf header e_shentsize is %d expected %d!",
+ pszLogName, pEhdr->e_shentsize, sizeof(Elf_Shdr));
+
+ switch (pEhdr->e_type)
+ {
+ case ET_REL:
+ case ET_EXEC:
+ case ET_DYN:
+ break;
+ default:
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: image type %#x is not supported!",
+ pszLogName, pEhdr->e_type);
+ }
+
+ switch (pEhdr->e_machine)
+ {
+#if ELF_MODE == 32
+ case EM_386:
+ case EM_486:
+ *penmArch = RTLDRARCH_X86_32;
+ break;
+#elif ELF_MODE == 64
+ case EM_X86_64:
+ *penmArch = RTLDRARCH_AMD64;
+ break;
+#endif
+ default:
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_MACHINE,
+ "%s: machine type %u is not supported!", pszLogName, pEhdr->e_machine);
+ }
+
+ if ( pEhdr->e_phoff < pEhdr->e_ehsize
+ && !(pEhdr->e_phoff && pEhdr->e_phnum)
+ && pEhdr->e_phnum)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: The program headers overlap with the ELF header! e_phoff=" FMT_ELF_OFF,
+ pszLogName, pEhdr->e_phoff);
+ if ( pEhdr->e_phoff + pEhdr->e_phnum * pEhdr->e_phentsize > cbRawImage
+ || pEhdr->e_phoff + pEhdr->e_phnum * pEhdr->e_phentsize < pEhdr->e_phoff)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: The program headers extends beyond the file! e_phoff=" FMT_ELF_OFF " e_phnum=" FMT_ELF_HALF,
+ pszLogName, pEhdr->e_phoff, pEhdr->e_phnum);
+
+
+ if ( pEhdr->e_shoff < pEhdr->e_ehsize
+ && !(pEhdr->e_shoff && pEhdr->e_shnum))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: The section headers overlap with the ELF header! e_shoff=" FMT_ELF_OFF,
+ pszLogName, pEhdr->e_shoff);
+ if ( pEhdr->e_shoff + pEhdr->e_shnum * pEhdr->e_shentsize > cbRawImage
+ || pEhdr->e_shoff + pEhdr->e_shnum * pEhdr->e_shentsize < pEhdr->e_shoff)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: The section headers extends beyond the file! e_shoff=" FMT_ELF_OFF " e_shnum=" FMT_ELF_HALF,
+ pszLogName, pEhdr->e_shoff, pEhdr->e_shnum);
+
+ if (pEhdr->e_shstrndx == 0 || pEhdr->e_shstrndx > pEhdr->e_shnum)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: The section headers string table is out of bounds! e_shstrndx=" FMT_ELF_HALF " e_shnum=" FMT_ELF_HALF,
+ pszLogName, pEhdr->e_shstrndx, pEhdr->e_shnum);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the section header name.
+ *
+ * @returns pszName.
+ * @param pEhdr The elf header.
+ * @param offName The offset of the section header name.
+ * @param pszName Where to store the name.
+ * @param cbName The size of the buffer pointed to by pszName.
+ */
+const char *RTLDRELF_NAME(GetSHdrName)(PRTLDRMODELF pModElf, Elf_Word offName, char *pszName, size_t cbName)
+{
+ RTFOFF off = pModElf->paShdrs[pModElf->Ehdr.e_shstrndx].sh_offset + offName;
+ int rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, pszName, cbName - 1, off);
+ if (RT_FAILURE(rc))
+ {
+ /* read by for byte. */
+ for (unsigned i = 0; i < cbName; i++, off++)
+ {
+ rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, pszName + i, 1, off);
+ if (RT_FAILURE(rc))
+ {
+ pszName[i] = '\0';
+ break;
+ }
+ }
+ }
+
+ pszName[cbName - 1] = '\0';
+ return pszName;
+}
+
+
+/**
+ * Validates a section header.
+ *
+ * @returns iprt status code.
+ * @param pModElf Pointer to the module structure.
+ * @param iShdr The index of section header which should be validated.
+ * The section headers are found in the pModElf->paShdrs array.
+ * @param cbRawImage The size of the raw image.
+ * @param pszLogName The log name.
+ * @param pErrInfo Where to return extended error info. Optional.
+ */
+static int RTLDRELF_NAME(ValidateSectionHeader)(PRTLDRMODELF pModElf, unsigned iShdr, uint64_t cbRawImage,
+ const char *pszLogName, PRTERRINFO pErrInfo)
+{
+ const Elf_Shdr *pShdr = &pModElf->paShdrs[iShdr];
+ char szSectionName[80]; NOREF(szSectionName);
+ Log3(("RTLdrELF: Section Header #%d:\n"
+ "RTLdrELF: sh_name: " FMT_ELF_WORD " - %s\n"
+ "RTLdrELF: sh_type: " FMT_ELF_WORD " (%s)\n"
+ "RTLdrELF: sh_flags: " FMT_ELF_XWORD "\n"
+ "RTLdrELF: sh_addr: " FMT_ELF_ADDR "\n"
+ "RTLdrELF: sh_offset: " FMT_ELF_OFF "\n"
+ "RTLdrELF: sh_size: " FMT_ELF_XWORD "\n"
+ "RTLdrELF: sh_link: " FMT_ELF_WORD "\n"
+ "RTLdrELF: sh_info: " FMT_ELF_WORD "\n"
+ "RTLdrELF: sh_addralign: " FMT_ELF_XWORD "\n"
+ "RTLdrELF: sh_entsize: " FMT_ELF_XWORD "\n",
+ iShdr,
+ pShdr->sh_name, RTLDRELF_NAME(GetSHdrName)(pModElf, pShdr->sh_name, szSectionName, sizeof(szSectionName)),
+ pShdr->sh_type, rtldrElfGetShdrType(pShdr->sh_type), pShdr->sh_flags, pShdr->sh_addr,
+ pShdr->sh_offset, pShdr->sh_size, pShdr->sh_link, pShdr->sh_info, pShdr->sh_addralign,
+ pShdr->sh_entsize));
+
+ if (iShdr == 0)
+ {
+ if ( pShdr->sh_name != 0
+ || pShdr->sh_type != SHT_NULL
+ || pShdr->sh_flags != 0
+ || pShdr->sh_addr != 0
+ || pShdr->sh_size != 0
+ || pShdr->sh_offset != 0
+ || pShdr->sh_link != SHN_UNDEF
+ || pShdr->sh_addralign != 0
+ || pShdr->sh_entsize != 0 )
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Bad #0 section: %.*Rhxs", pszLogName, sizeof(*pShdr), pShdr);
+ return VINF_SUCCESS;
+ }
+
+ if (pShdr->sh_name >= pModElf->cbShStr)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Shdr #%d: sh_name (%d) is beyond the end of the section header string table (%d)!",
+ pszLogName, iShdr, pShdr->sh_name, pModElf->cbShStr);
+
+ if (pShdr->sh_link >= pModElf->Ehdr.e_shnum)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Shdr #%d: sh_link (%d) is beyond the end of the section table (%d)!",
+ pszLogName, iShdr, pShdr->sh_link, pModElf->Ehdr.e_shnum);
+
+ switch (pShdr->sh_type)
+ {
+ /** @todo find specs and check up which sh_info fields indicates section table entries */
+ case 12301230:
+ if (pShdr->sh_info >= pModElf->Ehdr.e_shnum)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Shdr #%d: sh_info (%d) is beyond the end of the section table (%d)!",
+ pszLogName, iShdr, pShdr->sh_link, pModElf->Ehdr.e_shnum);
+ break;
+
+ case SHT_NULL:
+ break;
+ case SHT_PROGBITS:
+ case SHT_SYMTAB:
+ case SHT_STRTAB:
+ case SHT_RELA:
+ case SHT_HASH:
+ case SHT_DYNAMIC:
+ case SHT_NOTE:
+ case SHT_NOBITS:
+ case SHT_REL:
+ case SHT_SHLIB:
+ case SHT_DYNSYM:
+ /*
+ * For these types sh_info doesn't have any special meaning, or anything which
+ * we need/can validate now.
+ */
+ break;
+
+
+ default:
+ Log(("RTLdrELF: %s: Warning, unknown type %d!\n", pszLogName, pShdr->sh_type));
+ break;
+ }
+
+ if ( pShdr->sh_type != SHT_NOBITS
+ && pShdr->sh_size)
+ {
+ uint64_t offEnd = pShdr->sh_offset + pShdr->sh_size;
+ if ( offEnd > cbRawImage
+ || offEnd < (uint64_t)pShdr->sh_offset)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Shdr #%d: sh_offset (" FMT_ELF_OFF ") + sh_size (" FMT_ELF_XWORD " = %RX64) is beyond the end of the file (%RX64)!",
+ pszLogName, iShdr, pShdr->sh_offset, pShdr->sh_size, offEnd, cbRawImage);
+ if (pShdr->sh_offset < sizeof(Elf_Ehdr))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Shdr #%d: sh_offset (" FMT_ELF_OFF ") + sh_size (" FMT_ELF_XWORD ") is starting in the ELF header!",
+ pszLogName, iShdr, pShdr->sh_offset, pShdr->sh_size);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Process the section headers.
+ *
+ * @returns iprt status code.
+ * @param pModElf Pointer to the module structure.
+ * @param paShdrs The section headers.
+ * @param cbRawImage The size of the raw image.
+ * @param pszLogName The log name.
+ * @param pErrInfo Where to return extended error info. Optional.
+ */
+static int RTLDRELF_NAME(ValidateAndProcessSectionHeaders)(PRTLDRMODELF pModElf, Elf_Shdr *paShdrs, uint64_t cbRawImage,
+ const char *pszLogName, PRTERRINFO pErrInfo)
+{
+ Elf_Addr uNextAddr = 0;
+ for (unsigned i = 0; i < pModElf->Ehdr.e_shnum; i++)
+ {
+ int rc = RTLDRELF_NAME(ValidateSectionHeader)(pModElf, i, cbRawImage, pszLogName, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * We're looking for symbol tables.
+ */
+ if (paShdrs[i].sh_type == SHT_SYMTAB)
+ {
+ if (pModElf->Rel.iSymSh != ~0U)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_MULTIPLE_SYMTABS,
+ "%s: Multiple symbol tabs! iSymSh=%d i=%d", pszLogName, pModElf->Rel.iSymSh, i);
+ pModElf->Rel.iSymSh = i;
+ pModElf->Rel.cSyms = (unsigned)(paShdrs[i].sh_size / sizeof(Elf_Sym));
+ AssertBreakStmt(pModElf->Rel.cSyms == paShdrs[i].sh_size / sizeof(Elf_Sym), rc = VERR_IMAGE_TOO_BIG);
+ pModElf->Rel.iStrSh = paShdrs[i].sh_link;
+ pModElf->Rel.cbStr = (unsigned)paShdrs[pModElf->Rel.iStrSh].sh_size;
+ AssertBreakStmt(pModElf->Rel.cbStr == paShdrs[pModElf->Rel.iStrSh].sh_size, rc = VERR_IMAGE_TOO_BIG);
+ }
+ else if (paShdrs[i].sh_type == SHT_DYNSYM)
+ {
+ if (pModElf->Dyn.iSymSh != ~0U)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_MULTIPLE_SYMTABS,
+ "%s: Multiple dynamic symbol tabs! iSymSh=%d i=%d", pszLogName, pModElf->Dyn.iSymSh, i);
+ if (pModElf->Ehdr.e_type != ET_DYN && pModElf->Ehdr.e_type != ET_EXEC)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Unexpected SHT_DYNSYM (i=%d) for e_type=%d", pszLogName, i, pModElf->Ehdr.e_type);
+ pModElf->Dyn.iSymSh = i;
+ pModElf->Dyn.cSyms = (unsigned)(paShdrs[i].sh_size / sizeof(Elf_Sym));
+ AssertBreakStmt(pModElf->Dyn.cSyms == paShdrs[i].sh_size / sizeof(Elf_Sym), rc = VERR_IMAGE_TOO_BIG);
+ pModElf->Dyn.iStrSh = paShdrs[i].sh_link;
+ pModElf->Dyn.cbStr = (unsigned)paShdrs[pModElf->Dyn.iStrSh].sh_size;
+ AssertBreakStmt(pModElf->Dyn.cbStr == paShdrs[pModElf->Dyn.iStrSh].sh_size, rc = VERR_IMAGE_TOO_BIG);
+ }
+ /*
+ * We're also look for the dynamic section.
+ */
+ else if (paShdrs[i].sh_type == SHT_DYNAMIC)
+ {
+ if (pModElf->iShDynamic != ~0U)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Multiple dynamic sections! iShDynamic=%d i=%d",
+ pszLogName, pModElf->iShDynamic, i);
+ if (pModElf->Ehdr.e_type != ET_DYN && pModElf->Ehdr.e_type != ET_EXEC)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Unexpected SHT_DYNAMIC (i=%d) for e_type=%d", pszLogName, i, pModElf->Ehdr.e_type);
+ if (paShdrs[i].sh_entsize != sizeof(Elf_Dyn))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: SHT_DYNAMIC (i=%d) sh_entsize=" FMT_ELF_XWORD ", expected %#zx",
+ pszLogName, i, paShdrs[i].sh_entsize, sizeof(Elf_Dyn));
+ pModElf->iShDynamic = i;
+ Elf_Xword const cDynamic = paShdrs[i].sh_size / sizeof(Elf_Dyn);
+ if (cDynamic > _64K || cDynamic < 2)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: SHT_DYNAMIC (i=%d) sh_size=" FMT_ELF_XWORD " is out of range (2..64K)",
+ pszLogName, i, paShdrs[i].sh_size);
+ pModElf->cDynamic = (unsigned)cDynamic;
+ }
+
+ /*
+ * Special checks for the section string table.
+ */
+ if (i == pModElf->Ehdr.e_shstrndx)
+ {
+ if (paShdrs[i].sh_type != SHT_STRTAB)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Section header string table is not a SHT_STRTAB: %#x",
+ pszLogName, paShdrs[i].sh_type);
+ if (paShdrs[i].sh_size == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Section header string table is empty", pszLogName);
+ }
+
+ /*
+ * Kluge for the .data..percpu segment in 64-bit linux kernels.
+ */
+ if (paShdrs[i].sh_flags & SHF_ALLOC)
+ {
+ if ( paShdrs[i].sh_addr == 0
+ && paShdrs[i].sh_addr < uNextAddr)
+ {
+ Elf_Addr uAddr = RT_ALIGN_T(uNextAddr, paShdrs[i].sh_addralign, Elf_Addr);
+ Log(("RTLdrElf: Out of order section #%d; adjusting sh_addr from " FMT_ELF_ADDR " to " FMT_ELF_ADDR "\n",
+ i, paShdrs[i].sh_addr, uAddr));
+ paShdrs[i].sh_addr = uAddr;
+ }
+ uNextAddr = paShdrs[i].sh_addr + paShdrs[i].sh_size;
+ }
+ } /* for each section header */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Process the section headers.
+ *
+ * @returns iprt status code.
+ * @param pModElf Pointer to the module structure.
+ * @param paShdrs The section headers.
+ * @param cbRawImage The size of the raw image.
+ * @param pszLogName The log name.
+ * @param pErrInfo Where to return extended error info. Optional.
+ */
+static int RTLDRELF_NAME(ValidateAndProcessDynamicInfo)(PRTLDRMODELF pModElf, uint64_t cbRawImage, uint32_t fFlags,
+ const char *pszLogName, PRTERRINFO pErrInfo)
+{
+ /*
+ * Check preconditions.
+ */
+ AssertReturn(pModElf->Ehdr.e_type == ET_DYN || pModElf->Ehdr.e_type == ET_EXEC, VERR_INTERNAL_ERROR_2);
+ if (pModElf->Ehdr.e_phnum <= 1 || pModElf->Ehdr.e_phnum >= _32K)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: e_phnum=%u is out of bounds (2..32K)", pszLogName, pModElf->Ehdr.e_phnum);
+ if (pModElf->iShDynamic == ~0U)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: no .dynamic section", pszLogName);
+ AssertReturn(pModElf->cDynamic > 1 && pModElf->cDynamic <= _64K, VERR_INTERNAL_ERROR_3);
+
+ /* ASSUME that the sections are ordered by address. That simplifies
+ validation code further down. */
+ AssertReturn(pModElf->Ehdr.e_shnum >= 2, VERR_INTERNAL_ERROR_4);
+ Elf_Shdr const *paShdrs = pModElf->paShdrs;
+ Elf_Addr uPrevEnd = paShdrs[1].sh_addr + paShdrs[1].sh_size;
+ for (unsigned i = 2; i < pModElf->Ehdr.e_shnum; i++)
+ if (paShdrs[i].sh_flags & SHF_ALLOC)
+ {
+ if (uPrevEnd > paShdrs[i].sh_addr)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: section %u is out of order: uPrevEnd=" FMT_ELF_ADDR " sh_addr=" FMT_ELF_ADDR,
+ pszLogName, i, uPrevEnd, paShdrs[i].sh_addr);
+ uPrevEnd = paShdrs[i].sh_addr + paShdrs[i].sh_size;
+ }
+
+ /* Must have string and symbol tables. */
+ if (pModElf->Dyn.iStrSh == ~0U)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: No dynamic string table section", pszLogName);
+ if (pModElf->Dyn.iSymSh == ~0U)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: No dynamic symbol table section", pszLogName);
+
+ /*
+ * Load the program headers.
+ */
+ size_t const cbPhdrs = sizeof(pModElf->paPhdrs[0]) * pModElf->Ehdr.e_phnum;
+ Elf_Phdr *paPhdrs = (Elf_Phdr *)RTMemAllocZ(cbPhdrs);
+ pModElf->paPhdrs = paPhdrs;
+ AssertReturn(paPhdrs, VERR_NO_MEMORY);
+
+ int rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, paPhdrs, cbPhdrs, pModElf->Ehdr.e_phoff);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "%s: pfnRead(,,%#zx, " FMT_ELF_OFF ") -> %Rrc",
+ pszLogName, cbPhdrs, pModElf->Ehdr.e_phoff, rc);
+
+ /*
+ * Validate them.
+ */
+ unsigned cbPage = _4K; /** @todo generalize architecture specific stuff using its own code template header. */
+ switch (pModElf->Core.enmArch)
+ {
+ case RTLDRARCH_AMD64:
+ case RTLDRARCH_X86_32:
+ break;
+ default:
+ AssertFailedBreak(/** @todo page size for got.plt hacks */);
+ }
+ unsigned iLoad = 0;
+ unsigned iLoadShdr = 1; /* ASSUMES ordered (checked above). */
+ unsigned cDynamic = 0;
+ Elf_Addr cbImage = 0;
+ Elf_Addr uLinkAddress = ~(Elf_Addr)0;
+ for (unsigned i = 0; i < pModElf->Ehdr.e_phnum; i++)
+ {
+ const Elf_Phdr * const pPhdr = &paPhdrs[i];
+ Log3(("RTLdrELF: Program Header #%d:\n"
+ "RTLdrELF: p_type: " FMT_ELF_WORD " (%s)\n"
+ "RTLdrELF: p_flags: " FMT_ELF_WORD "\n"
+ "RTLdrELF: p_offset: " FMT_ELF_OFF "\n"
+ "RTLdrELF: p_vaddr: " FMT_ELF_ADDR "\n"
+ "RTLdrELF: p_paddr: " FMT_ELF_ADDR "\n"
+ "RTLdrELF: p_filesz: " FMT_ELF_XWORD "\n"
+ "RTLdrELF: p_memsz: " FMT_ELF_XWORD "\n"
+ "RTLdrELF: p_align: " FMT_ELF_XWORD "\n",
+ i,
+ pPhdr->p_type, rtldrElfGetPhdrType(pPhdr->p_type), pPhdr->p_flags, pPhdr->p_offset,
+ pPhdr->p_vaddr, pPhdr->p_paddr, pPhdr->p_filesz, pPhdr->p_memsz, pPhdr->p_align));
+
+ if (pPhdr->p_type == DT_NULL)
+ continue;
+
+ if ( pPhdr->p_filesz != 0
+ && ( pPhdr->p_offset >= cbRawImage
+ || pPhdr->p_filesz > cbRawImage
+ || pPhdr->p_offset + pPhdr->p_filesz > cbRawImage))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Prog Hdr #%u: bogus p_offset=" FMT_ELF_OFF " & p_filesz=" FMT_ELF_XWORD " (file size %#RX64)",
+ pszLogName, i, pPhdr->p_offset, pPhdr->p_filesz, cbRawImage);
+
+ if (pPhdr->p_flags & ~(Elf64_Word)(PF_X | PF_R | PF_W))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Prog Hdr #%u: bogus p_flags=" FMT_ELF_WORD,
+ pszLogName, i, pPhdr->p_flags);
+
+ if (!RT_IS_POWER_OF_TWO(pPhdr->p_align))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Prog Hdr #%u: bogus p_align=" FMT_ELF_XWORD,
+ pszLogName, i, pPhdr->p_align);
+
+ if ( pPhdr->p_align > 1
+ && pPhdr->p_memsz > 0
+ && pPhdr->p_filesz > 0
+ && (pPhdr->p_offset & (pPhdr->p_align - 1)) != (pPhdr->p_vaddr & (pPhdr->p_align - 1)))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Prog Hdr #%u: misaligned p_offset=" FMT_ELF_OFF " p_vaddr=" FMT_ELF_ADDR " p_align=" FMT_ELF_XWORD,
+ pszLogName, i, pPhdr->p_offset, pPhdr->p_vaddr, pPhdr->p_align);
+
+ /* Do some type specfic checks: */
+ switch (pPhdr->p_type)
+ {
+ case PT_LOAD:
+ {
+ if (pPhdr->p_memsz < pPhdr->p_filesz)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Prog Hdr #%u/LOAD#%u: bogus p_memsz=" FMT_ELF_XWORD " or p_filesz=" FMT_ELF_XWORD,
+ pszLogName, i, iLoad, pPhdr->p_memsz, pPhdr->p_filesz);
+ cbImage = pPhdr->p_vaddr + pPhdr->p_memsz;
+ if (iLoad == 0)
+ uLinkAddress = pPhdr->p_vaddr;
+
+ /* Find the corresponding sections, checking their addresses and
+ file offsets since the rest of the code is still section based
+ rather than using program headers as it should... */
+ Elf_Off off = pPhdr->p_offset;
+ Elf_Addr uAddr = pPhdr->p_vaddr;
+ Elf_Xword cbMem = pPhdr->p_memsz;
+ Elf_Xword cbFile = pPhdr->p_filesz;
+
+ /* HACK to allow loading isolinux-debug.elf where program headers aren't
+ sorted by virtual address. */
+ if ( (fFlags & RTLDR_O_FOR_DEBUG)
+ && uAddr != paShdrs[iLoadShdr].sh_addr)
+ {
+ for (unsigned iShdr = 1; iShdr < pModElf->Ehdr.e_shnum; iShdr++)
+ if (uAddr == paShdrs[iShdr].sh_addr)
+ {
+ iLoadShdr = iShdr;
+ break;
+ }
+ }
+
+ while (cbMem > 0)
+ {
+ if (iLoadShdr < pModElf->Ehdr.e_shnum)
+ { /* likely */ }
+ else if (iLoadShdr == pModElf->Ehdr.e_shnum)
+ {
+ /** @todo anything else to check here? */
+ iLoadShdr++;
+ break;
+ }
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Prog Hdr #%u/LOAD#%u: Out of sections at " FMT_ELF_ADDR " LB " FMT_ELF_XWORD,
+ pszLogName, i, iLoad, uAddr, cbMem);
+ if (!(paShdrs[iLoadShdr].sh_flags & SHF_ALLOC))
+ {
+ if ( paShdrs[iLoadShdr].sh_type != SHT_NOBITS
+ && paShdrs[iLoadShdr].sh_size > 0
+ && off < paShdrs[iLoadShdr].sh_offset + paShdrs[iLoadShdr].sh_size
+ && paShdrs[iLoadShdr].sh_offset < off + cbMem)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Prog Hdr #%u/LOAD#%u: Overlaps with !SHF_ALLOC section at " FMT_ELF_OFF " LB " FMT_ELF_XWORD,
+ pszLogName, i, iLoad, paShdrs[iLoadShdr].sh_offset, paShdrs[iLoadShdr].sh_size);
+ pModElf->paShdrExtras[iLoadShdr].idxPhdr = UINT16_MAX;
+ iLoadShdr++;
+ continue;
+ }
+
+ if (uAddr != paShdrs[iLoadShdr].sh_addr)
+ {
+ /* Before the first section we expect headers to be loaded, so
+ that the file is simply mapped from file offset zero. */
+ if ( iLoadShdr == 1
+ && iLoad == 0
+ && paShdrs[1].sh_addr == paShdrs[1].sh_offset
+ && cbFile >= paShdrs[1].sh_offset
+ && cbMem >= paShdrs[1].sh_offset)
+ {
+ /* Modify paShdrs[0] to describe the gap. ".elf.headers" */
+ pModElf->iFirstSect = 0;
+ pModElf->paShdrs[0].sh_name = 0;
+ pModElf->paShdrs[0].sh_type = SHT_PROGBITS;
+ pModElf->paShdrs[0].sh_flags = SHF_ALLOC
+ | (pPhdr->p_flags & PF_W ? SHF_WRITE : 0)
+ | (pPhdr->p_flags & PF_X ? SHF_EXECINSTR : 0);
+ pModElf->paShdrs[0].sh_addr = uAddr;
+ pModElf->paShdrs[0].sh_offset = off;
+ pModElf->paShdrs[0].sh_size = paShdrs[1].sh_offset;
+ pModElf->paShdrs[0].sh_link = 0;
+ pModElf->paShdrs[0].sh_info = 0;
+ pModElf->paShdrs[0].sh_addralign = pPhdr->p_align;
+ pModElf->paShdrs[0].sh_entsize = 0;
+ *(Elf_Shdr *)pModElf->paOrgShdrs = pModElf->paShdrs[0]; /* (necessary for segment enumeration) */
+
+ uAddr += paShdrs[1].sh_offset;
+ cbMem -= paShdrs[1].sh_offset;
+ cbFile -= paShdrs[1].sh_offset;
+ off = paShdrs[1].sh_offset;
+ }
+ /* Alignment padding? Allow up to a page size. */
+ else if ( paShdrs[iLoadShdr].sh_addr > uAddr
+ && paShdrs[iLoadShdr].sh_addr - uAddr
+ < RT_MAX(paShdrs[iLoadShdr].sh_addralign, cbPage /*got.plt hack*/))
+ {
+ Elf_Xword cbAlignPadding = paShdrs[iLoadShdr].sh_addr - uAddr;
+ if (cbAlignPadding >= cbMem)
+ break;
+ cbMem -= cbAlignPadding;
+ uAddr += cbAlignPadding;
+ if (cbFile > cbAlignPadding)
+ {
+ off += cbAlignPadding;
+ cbFile -= cbAlignPadding;
+ }
+ else
+ {
+ off += cbFile;
+ cbFile = 0;
+ }
+ }
+ }
+
+ if ( uAddr == paShdrs[iLoadShdr].sh_addr
+ && cbMem >= paShdrs[iLoadShdr].sh_size
+ && ( paShdrs[iLoadShdr].sh_type != SHT_NOBITS
+ ? off == paShdrs[iLoadShdr].sh_offset
+ && cbFile >= paShdrs[iLoadShdr].sh_size /* this might be too strict... */
+ : cbFile == 0
+ || cbMem > paShdrs[iLoadShdr].sh_size /* isolinux.elf: linker merge no-bits and progbits sections */) )
+ {
+ if ( paShdrs[iLoadShdr].sh_type != SHT_NOBITS
+ || cbFile != 0)
+ {
+ off += paShdrs[iLoadShdr].sh_size;
+ cbFile -= paShdrs[iLoadShdr].sh_size;
+ }
+ uAddr += paShdrs[iLoadShdr].sh_size;
+ cbMem -= paShdrs[iLoadShdr].sh_size;
+ }
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Prog Hdr #%u/LOAD#%u: Mismatch at " FMT_ELF_ADDR " LB " FMT_ELF_XWORD " (file " FMT_ELF_OFF " LB " FMT_ELF_XWORD ") with section #%u " FMT_ELF_ADDR " LB " FMT_ELF_XWORD " (file " FMT_ELF_OFF " sh_type=" FMT_ELF_WORD ")",
+ pszLogName, i, iLoad, uAddr, cbMem, off, cbFile,
+ iLoadShdr, paShdrs[iLoadShdr].sh_addr, paShdrs[iLoadShdr].sh_size,
+ paShdrs[iLoadShdr].sh_offset, paShdrs[iLoadShdr].sh_type);
+
+ pModElf->paShdrExtras[iLoadShdr].idxPhdr = iLoad;
+ iLoadShdr++;
+ } /* section loop */
+
+ iLoad++;
+ break;
+ }
+
+ case PT_DYNAMIC:
+ {
+ const Elf_Shdr *pShdr = &pModElf->paShdrs[pModElf->iShDynamic];
+ if (pPhdr->p_offset != pShdr->sh_offset)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Prog Hdr #%u/DYNAMIC: p_offset=" FMT_ELF_OFF " expected " FMT_ELF_OFF,
+ pszLogName, i, pPhdr->p_offset, pShdr->sh_offset);
+ if (RT_MAX(pPhdr->p_memsz, pPhdr->p_filesz) != pShdr->sh_size)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: Prog Hdr #%u/DYNAMIC: expected " FMT_ELF_XWORD " for RT_MAX(p_memsz=" FMT_ELF_XWORD ", p_filesz=" FMT_ELF_XWORD ")",
+ pszLogName, i, pShdr->sh_size, pPhdr->p_memsz, pPhdr->p_filesz);
+ cDynamic++;
+ break;
+ }
+ }
+ }
+
+ if (iLoad == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "%s: No PT_LOAD program headers", pszLogName);
+ if (cDynamic != 1)
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "%s: No program header for the DYNAMIC section", pszLogName);
+
+ cbImage -= uLinkAddress;
+ pModElf->cbImage = (uint64_t)cbImage;
+ pModElf->LinkAddress = uLinkAddress;
+ AssertReturn(pModElf->cbImage == cbImage, VERR_INTERNAL_ERROR_5);
+ Log3(("RTLdrELF: LinkAddress=" FMT_ELF_ADDR " cbImage=" FMT_ELF_ADDR " (from PT_LOAD)\n", uLinkAddress, cbImage));
+
+ for (; iLoadShdr < pModElf->Ehdr.e_shnum; iLoadShdr++)
+ if ( !(paShdrs[iLoadShdr].sh_flags & SHF_ALLOC)
+ || paShdrs[iLoadShdr].sh_size == 0)
+ pModElf->paShdrExtras[iLoadShdr].idxPhdr = UINT16_MAX;
+ else
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: No PT_LOAD for section #%u " FMT_ELF_ADDR " LB " FMT_ELF_XWORD " (file " FMT_ELF_OFF " sh_type=" FMT_ELF_WORD ")",
+ pszLogName, iLoadShdr, paShdrs[iLoadShdr].sh_addr, paShdrs[iLoadShdr].sh_size,
+ paShdrs[iLoadShdr].sh_offset, paShdrs[iLoadShdr].sh_type);
+
+ /*
+ * Load and validate the dynamic table. We have got / will get most of the
+ * info we need from the section table, so we must make sure this matches up.
+ */
+ Log3(("RTLdrELF: Dynamic section - %u entries\n", pModElf->cDynamic));
+ size_t const cbDynamic = pModElf->cDynamic * sizeof(pModElf->paDynamic[0]);
+ Elf_Dyn * const paDynamic = (Elf_Dyn *)RTMemAlloc(cbDynamic);
+ AssertReturn(paDynamic, VERR_NO_MEMORY);
+ pModElf->paDynamic = paDynamic;
+
+ rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, paDynamic, cbDynamic, paShdrs[pModElf->iShDynamic].sh_offset);
+ if (RT_FAILURE(rc))
+ return RTERRINFO_LOG_SET_F(pErrInfo, rc, "%s: pfnRead(,,%#zx, " FMT_ELF_OFF ") -> %Rrc",
+ pszLogName, cbDynamic, paShdrs[pModElf->iShDynamic].sh_offset, rc);
+
+ for (uint32_t i = 0; i < pModElf->cDynamic; i++)
+ {
+#define LOG_VALIDATE_PTR_RET(szName) do { \
+ Log3(("RTLdrELF: DT[%u]: %16s " FMT_ELF_ADDR "\n", i, szName, paDynamic[i].d_un.d_ptr)); \
+ if ((uint64_t)paDynamic[i].d_un.d_ptr - uLinkAddress < cbImage) { /* likely */ } \
+ else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" szName ": Invalid address " FMT_ELF_ADDR " (valid range: " FMT_ELF_ADDR " LB " FMT_ELF_ADDR ")", \
+ pszLogName, i, paDynamic[i].d_un.d_ptr, uLinkAddress, cbImage); \
+ } while (0)
+#define LOG_VALIDATE_PTR_VAL_RET(szName, uExpected) do { \
+ Log3(("RTLdrELF: DT[%u]: %16s " FMT_ELF_ADDR "\n", i, szName, (uint64_t)paDynamic[i].d_un.d_ptr)); \
+ if (paDynamic[i].d_un.d_ptr == (Elf_Addr)(uExpected)) { /* likely */ } \
+ else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" szName ": " FMT_ELF_ADDR ", expected " FMT_ELF_ADDR, \
+ pszLogName, i, paDynamic[i].d_un.d_ptr, (Elf_Addr)(uExpected)); \
+ } while (0)
+#define LOG_VALIDATE_STR_RET(szName) do { \
+ Log3(("RTLdrELF: DT[%u]: %16s %#RX64\n", i, szName, (uint64_t)paDynamic[i].d_un.d_val)); \
+ if ((uint64_t)paDynamic[i].d_un.d_val < pModElf->Dyn.cbStr) { /* likely */ } \
+ else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" szName ": Invalid string table offset %#RX64 (max %#x)", \
+ pszLogName, i, (uint64_t)paDynamic[i].d_un.d_val, pModElf->Dyn.cbStr); \
+ } while (0)
+#define LOG_VALIDATE_VAL_RET(szName, uExpected) do { \
+ Log3(("RTLdrELF: DT[%u]: %16s %#RX64\n", i, szName, (uint64_t)paDynamic[i].d_un.d_val)); \
+ if ((uint64_t)paDynamic[i].d_un.d_val == (uint64_t)(uExpected)) { /* likely */ } \
+ else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" szName ": %#RX64, expected %#RX64", \
+ pszLogName, i, (uint64_t)paDynamic[i].d_un.d_val, (uint64_t)(uExpected)); \
+ } while (0)
+#define SET_RELOC_TYPE_RET(a_szName, a_uType) do { \
+ if (pModElf->DynInfo.uRelocType == 0 || pModElf->DynInfo.uRelocType == (a_uType)) \
+ pModElf->DynInfo.uRelocType = (a_uType); \
+ else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": Mixing DT_RELA and DT_REL", pszLogName, i); \
+ } while (0)
+#define SET_INFO_FIELD_RET(a_szName, a_Field, a_Value, a_UnsetValue, a_szFmt) do { \
+ if ((a_Field) == (a_UnsetValue) && (a_Value) != (a_UnsetValue)) \
+ (a_Field) = (a_Value); /* likely */ \
+ else if ((a_Field) != (a_UnsetValue)) \
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": Multiple entries (first value " a_szFmt ", second " a_szFmt ")", pszLogName, i, (a_Field), (a_Value)); \
+ else if ((a_Value) != (a_UnsetValue)) \
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": Unexpected value " a_szFmt, pszLogName, i, (a_Value)); \
+ } while (0)
+#define FIND_MATCHING_SECTION_RET(a_szName, a_ExtraMatchExpr, a_idxShFieldToSet) do { \
+ unsigned iSh; \
+ for (iSh = 1; iSh < pModElf->Ehdr.e_shnum; iSh++) \
+ if ( paShdrs[iSh].sh_addr == paDynamic[i].d_un.d_ptr \
+ && (a_ExtraMatchExpr)) \
+ { \
+ (a_idxShFieldToSet) = iSh; \
+ if (pModElf->paShdrExtras[iSh].idxDt != UINT16_MAX) \
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, \
+ "%s: DT[%u]/" a_szName ": section #%u (" FMT_ELF_ADDR ") already referenced by DT[%u]", \
+ pszLogName, i, iSh, paShdrs[iSh].sh_addr, pModElf->paShdrExtras[iSh].idxDt); \
+ pModElf->paShdrExtras[iSh].idxDt = i; \
+ pModElf->paShdrExtras[iSh].uDtTag = (uint32_t)paDynamic[i].d_tag; \
+ break; \
+ } \
+ if (iSh < pModElf->Ehdr.e_shnum) { /* likely */ } \
+ else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": No matching section for " FMT_ELF_ADDR, pszLogName, i, paDynamic[i].d_un.d_ptr); \
+ } while (0)
+#define ONLY_FOR_DEBUG_OR_VALIDATION_RET(a_szName) do { \
+ if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) { /* likely */ } \
+ else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": Not supported (" FMT_ELF_ADDR ")", pszLogName, i, paDynamic[i].d_un.d_ptr); \
+ } while (0)
+#define LOG_NON_VALUE_ENTRY(a_szName) Log3(("RTLdrELF: DT[%u]: %16s (%#RX64)\n", i, a_szName, (uint64_t)paDynamic[i].d_un.d_val))
+
+ switch (paDynamic[i].d_tag)
+ {
+ case DT_NULL:
+ LOG_NON_VALUE_ENTRY("DT_NULL");
+ for (unsigned iNull = i + 1; iNull < pModElf->cDynamic; iNull++)
+ if (paDynamic[i].d_tag == DT_NULL) /* Not technically a bug, but let's try being extremely strict for now */
+ LOG_NON_VALUE_ENTRY("DT_NULL");
+ else if (!(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: DT[%u]/DT_NULL: Dynamic section isn't zero padded (extra #%u of #%u)",
+ pszLogName, i, iNull - i, pModElf->cDynamic - i);
+ i = pModElf->cDynamic;
+ break;
+ case DT_NEEDED:
+ LOG_VALIDATE_STR_RET("DT_NEEDED");
+ break;
+ case DT_PLTRELSZ:
+ Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_PLTRELSZ", (uint64_t)paDynamic[i].d_un.d_val));
+ SET_INFO_FIELD_RET("DT_PLTRELSZ", pModElf->DynInfo.cbJmpRelocs, (Elf_Xword)paDynamic[i].d_un.d_val, 0, FMT_ELF_XWORD);
+ break;
+ case DT_PLTGOT:
+ LOG_VALIDATE_PTR_RET("DT_PLTGOT");
+ break;
+ case DT_HASH:
+ LOG_VALIDATE_PTR_RET("DT_HASH");
+ break;
+ case DT_STRTAB:
+ LOG_VALIDATE_PTR_VAL_RET("DT_STRTAB", paShdrs[pModElf->Dyn.iStrSh].sh_addr);
+ pModElf->paShdrExtras[pModElf->Dyn.iStrSh].idxDt = i;
+ pModElf->paShdrExtras[pModElf->Dyn.iSymSh].uDtTag = DT_STRTAB;
+ break;
+ case DT_SYMTAB:
+ LOG_VALIDATE_PTR_VAL_RET("DT_SYMTAB", paShdrs[pModElf->Dyn.iSymSh].sh_addr);
+ pModElf->paShdrExtras[pModElf->Dyn.iSymSh].idxDt = i;
+ pModElf->paShdrExtras[pModElf->Dyn.iSymSh].uDtTag = DT_SYMTAB;
+ break;
+ case DT_RELA:
+ LOG_VALIDATE_PTR_RET("DT_RELA");
+ SET_RELOC_TYPE_RET("DT_RELA", DT_RELA);
+ SET_INFO_FIELD_RET("DT_RELA", pModElf->DynInfo.uPtrRelocs, paDynamic[i].d_un.d_ptr, ~(Elf_Addr)0, FMT_ELF_ADDR);
+ FIND_MATCHING_SECTION_RET("DT_RELA", paShdrs[iSh].sh_type == SHT_RELA, pModElf->DynInfo.idxShRelocs);
+ break;
+ case DT_RELASZ:
+ Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_RELASZ", (uint64_t)paDynamic[i].d_un.d_val));
+ SET_RELOC_TYPE_RET("DT_RELASZ", DT_RELA);
+ SET_INFO_FIELD_RET("DT_RELASZ", pModElf->DynInfo.cbRelocs, (Elf_Xword)paDynamic[i].d_un.d_val, 0, FMT_ELF_XWORD);
+ break;
+ case DT_RELAENT:
+ LOG_VALIDATE_VAL_RET("DT_RELAENT", sizeof(Elf_Rela));
+ SET_RELOC_TYPE_RET("DT_RELAENT", DT_RELA);
+ SET_INFO_FIELD_RET("DT_RELAENT", pModElf->DynInfo.cbRelocEntry, (unsigned)sizeof(Elf_Rela), 0, "%u");
+ break;
+ case DT_STRSZ:
+ LOG_VALIDATE_VAL_RET("DT_STRSZ", pModElf->Dyn.cbStr);
+ break;
+ case DT_SYMENT:
+ LOG_VALIDATE_VAL_RET("DT_SYMENT", sizeof(Elf_Sym));
+ break;
+ case DT_INIT:
+ LOG_VALIDATE_PTR_RET("DT_INIT");
+ ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_INIT");
+ break;
+ case DT_FINI:
+ LOG_VALIDATE_PTR_RET("DT_FINI");
+ ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_FINI");
+ break;
+ case DT_SONAME:
+ LOG_VALIDATE_STR_RET("DT_SONAME");
+ break;
+ case DT_RPATH:
+ LOG_VALIDATE_STR_RET("DT_RPATH");
+ break;
+ case DT_SYMBOLIC:
+ LOG_NON_VALUE_ENTRY("DT_SYMBOLIC");
+ break;
+ case DT_REL:
+ LOG_VALIDATE_PTR_RET("DT_REL");
+ SET_RELOC_TYPE_RET("DT_REL", DT_REL);
+ SET_INFO_FIELD_RET("DT_REL", pModElf->DynInfo.uPtrRelocs, paDynamic[i].d_un.d_ptr, ~(Elf_Addr)0, FMT_ELF_ADDR);
+ FIND_MATCHING_SECTION_RET("DT_REL", paShdrs[iSh].sh_type == SHT_REL, pModElf->DynInfo.idxShRelocs);
+ break;
+ case DT_RELSZ:
+ Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_RELSZ", (uint64_t)paDynamic[i].d_un.d_val));
+ SET_RELOC_TYPE_RET("DT_RELSZ", DT_REL);
+ SET_INFO_FIELD_RET("DT_RELSZ", pModElf->DynInfo.cbRelocs, (Elf_Xword)paDynamic[i].d_un.d_val, 0, FMT_ELF_XWORD);
+ break;
+ case DT_RELENT:
+ LOG_VALIDATE_VAL_RET("DT_RELENT", sizeof(Elf_Rel));
+ SET_RELOC_TYPE_RET("DT_RELENT", DT_REL);
+ SET_INFO_FIELD_RET("DT_RELENT", pModElf->DynInfo.cbRelocEntry, (unsigned)sizeof(Elf_Rel), 0, "%u");
+ break;
+ case DT_PLTREL:
+ if (paDynamic[i].d_un.d_val != DT_RELA && paDynamic[i].d_un.d_val != DT_REL)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/DT_PLTREL: Invalid value %#RX64",
+ pszLogName, i, (uint64_t)paDynamic[i].d_un.d_val);
+ Log3(("RTLdrELF: DT[%u]: %16s DT_REL%s\n", i, "DT_PLTREL", paDynamic[i].d_un.d_val == DT_RELA ? "A" : ""));
+ SET_INFO_FIELD_RET("DT_PLTREL", pModElf->DynInfo.uJmpRelocType, (unsigned)paDynamic[i].d_un.d_val, 0, "%u");
+ break;
+ case DT_DEBUG:
+ /*
+ * DT_DEBUG is filled in by the dynamic linker to point a debugger to the head of the link map,
+ * it can point anywhere in userspace. For binaries not being executed it will be 0,
+ * so there is nothing we can validate here (and it is not required as we don't use
+ * this dynamic section). See https://ypl.coffee/dl-resolve-full-relro/ for more information.
+ */
+ break;
+ case DT_TEXTREL:
+ LOG_NON_VALUE_ENTRY("DT_TEXTREL");
+ break;
+ case DT_JMPREL:
+ LOG_VALIDATE_PTR_RET("DT_JMPREL");
+ SET_INFO_FIELD_RET("DT_JMPREL", pModElf->DynInfo.uPtrJmpRelocs, paDynamic[i].d_un.d_ptr, ~(Elf_Addr)0, FMT_ELF_ADDR);
+ FIND_MATCHING_SECTION_RET("DT_JMPREL", 1, pModElf->DynInfo.idxShJmpRelocs);
+ break;
+ case DT_BIND_NOW:
+ LOG_NON_VALUE_ENTRY("DT_BIND_NOW");
+ break;
+ case DT_INIT_ARRAY:
+ LOG_VALIDATE_PTR_RET("DT_INIT_ARRAY");
+ ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_INIT_ARRAY");
+ break;
+ case DT_FINI_ARRAY:
+ LOG_VALIDATE_PTR_RET("DT_FINI_ARRAY");
+ ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_FINI_ARRAY");
+ break;
+ case DT_INIT_ARRAYSZ:
+ Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_INIT_ARRAYSZ", (uint64_t)paDynamic[i].d_un.d_val));
+ ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_INIT_ARRAYSZ");
+ break;
+ case DT_FINI_ARRAYSZ:
+ Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_FINI_ARRAYSZ", (uint64_t)paDynamic[i].d_un.d_val));
+ ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_FINI_ARRAYSZ");
+ break;
+ case DT_RUNPATH:
+ LOG_VALIDATE_STR_RET("DT_RUNPATH");
+ break;
+ case DT_FLAGS:
+ Log3(("RTLdrELF: DT[%u]: %16s %#RX64\n", i, "DT_FLAGS", (uint64_t)paDynamic[i].d_un.d_val));
+ break;
+ case DT_PREINIT_ARRAY:
+ LOG_VALIDATE_PTR_RET("DT_PREINIT_ARRAY");
+ ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_PREINIT_ARRAY");
+ break;
+ case DT_PREINIT_ARRAYSZ:
+ Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_PREINIT_ARRAYSZ", (uint64_t)paDynamic[i].d_un.d_val));
+ ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_PREINIT_ARRAYSZ");
+ break;
+ default:
+ if ( paDynamic[i].d_tag < DT_ENCODING
+ || paDynamic[i].d_tag >= DT_LOOS
+ || (paDynamic[i].d_tag & 1))
+ Log3(("RTLdrELF: DT[%u]: %#010RX64 %#RX64%s\n", i, (uint64_t)paDynamic[i].d_tag,
+ (uint64_t)paDynamic[i].d_un.d_val, paDynamic[i].d_un.d_val >= DT_ENCODING ? " (val)" : ""));
+ else
+ {
+ Log3(("RTLdrELF: DT[%u]: %#010RX64 " FMT_ELF_ADDR " (addr)\n",
+ i, (uint64_t)paDynamic[i].d_tag, paDynamic[i].d_un.d_ptr));
+ if ((uint64_t)paDynamic[i].d_un.d_ptr - uLinkAddress >= cbImage)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: DT[%u]/%#RX64: Invalid address " FMT_ELF_ADDR " (valid range: " FMT_ELF_ADDR " LB " FMT_ELF_ADDR ")",
+ pszLogName, i, (uint64_t)paDynamic[i].d_tag,
+ paDynamic[i].d_un.d_ptr, uLinkAddress, cbImage);
+ }
+ break;
+ }
+#undef LOG_VALIDATE_VAL_RET
+#undef LOG_VALIDATE_STR_RET
+#undef LOG_VALIDATE_PTR_VAL_RET
+#undef LOG_VALIDATE_PTR_RET
+#undef SET_RELOC_TYPE_RET
+#undef SET_INFO_FIELD_RET
+#undef FIND_MATCHING_SECTION_RET
+#undef ONLY_FOR_DEBUG_OR_VALIDATION_RET
+ }
+
+ /*
+ * Validate the relocation information we've gathered.
+ */
+ Elf_Word uShTypeArch = SHT_RELA; /** @todo generalize architecture specific stuff using its own code template header. */
+ switch (pModElf->Core.enmArch)
+ {
+ case RTLDRARCH_AMD64:
+ break;
+ case RTLDRARCH_X86_32:
+ uShTypeArch = SHT_REL;
+ break;
+ default:
+ AssertFailedBreak(/** @todo page size for got.plt hacks */);
+
+ }
+
+ if (pModElf->DynInfo.uRelocType != 0)
+ {
+ const char * const pszModifier = pModElf->DynInfo.uRelocType == DT_RELA ? "A" : "";
+ if (pModElf->DynInfo.uPtrRelocs == ~(Elf_Addr)0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_REL%s", pszLogName, pszModifier);
+ if (pModElf->DynInfo.cbRelocs == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_REL%sSZ", pszLogName, pszModifier);
+ if (pModElf->DynInfo.cbRelocEntry == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_REL%sENT", pszLogName, pszModifier);
+ Elf_Shdr const *pShdrRelocs = &paShdrs[pModElf->DynInfo.idxShRelocs];
+ Elf_Word const uShType = pModElf->DynInfo.uJmpRelocType == DT_RELA ? SHT_RELA : SHT_REL;
+ if (pShdrRelocs->sh_type != uShType)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_REL%s* does not match section type: %u vs %u",
+ pszLogName, pszModifier, pShdrRelocs->sh_type, uShType);
+ if (pShdrRelocs->sh_size != pModElf->DynInfo.cbRelocs)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_REL%sSZ does not match section size: %u vs %u",
+ pszLogName, pszModifier, pShdrRelocs->sh_size, pModElf->DynInfo.cbRelocs);
+ if (uShType != uShTypeArch)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_REL%s* does not match architecture: %u, arch wants %u",
+ pszLogName, pszModifier, uShType, uShTypeArch);
+ }
+
+ if ( pModElf->DynInfo.uPtrJmpRelocs != ~(Elf_Addr)0
+ || pModElf->DynInfo.cbJmpRelocs != 0
+ || pModElf->DynInfo.uJmpRelocType != 0)
+ {
+ if (pModElf->DynInfo.uPtrJmpRelocs == ~(Elf_Addr)0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_JMPREL", pszLogName);
+ if (pModElf->DynInfo.cbJmpRelocs == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_PLTRELSZ", pszLogName);
+ if (pModElf->DynInfo.uJmpRelocType == 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_PLTREL", pszLogName);
+ Elf_Shdr const *pShdrRelocs = &paShdrs[pModElf->DynInfo.idxShJmpRelocs];
+ Elf_Word const uShType = pModElf->DynInfo.uJmpRelocType == DT_RELA ? SHT_RELA : SHT_REL;
+ if (pShdrRelocs->sh_type != uShType)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_PLTREL does not match section type: %u vs %u",
+ pszLogName, pShdrRelocs->sh_type, uShType);
+ if (pShdrRelocs->sh_size != pModElf->DynInfo.cbJmpRelocs)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_PLTRELSZ does not match section size: %u vs %u",
+ pszLogName, pShdrRelocs->sh_size, pModElf->DynInfo.cbJmpRelocs);
+ if (uShType != uShTypeArch)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_PLTREL does not match architecture: %u, arch wants %u",
+ pszLogName, uShType, uShTypeArch);
+ }
+
+ /*
+ * Check that there aren't any other relocations hiding in the section table.
+ */
+ for (uint32_t i = 1; i < pModElf->Ehdr.e_shnum; i++)
+ if ( (paShdrs[i].sh_type == SHT_REL || paShdrs[i].sh_type == SHT_RELA)
+ && pModElf->paShdrExtras[i].uDtTag != DT_REL
+ && pModElf->paShdrExtras[i].uDtTag != DT_RELA
+ && pModElf->paShdrExtras[i].uDtTag != DT_JMPREL)
+ {
+ char szSecHdrNm[80];
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "%s: section header #%u (%s type=" FMT_ELF_WORD " size=" FMT_ELF_XWORD ") contains relocations not referenced by the dynamic section",
+ pszLogName, i,
+ RTLDRELF_NAME(GetSHdrName)(pModElf, paShdrs[i].sh_name, szSecHdrNm, sizeof(szSecHdrNm)),
+ paShdrs[i].sh_type, paShdrs[i].sh_size);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Opens an ELF image, fixed bitness.
+ *
+ * @returns iprt status code.
+ * @param pReader The loader reader instance which will provide the raw image bits.
+ * @param fFlags Reserved, MBZ.
+ * @param enmArch Architecture specifier.
+ * @param phLdrMod Where to store the handle.
+ * @param pErrInfo Where to return extended error info. Optional.
+ */
+static int RTLDRELF_NAME(Open)(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo)
+{
+ const char *pszLogName = pReader->pfnLogName(pReader);
+ uint64_t cbRawImage = pReader->pfnSize(pReader);
+ RT_NOREF_PV(fFlags);
+
+ /*
+ * Create the loader module instance.
+ */
+ PRTLDRMODELF pModElf = (PRTLDRMODELF)RTMemAllocZ(sizeof(*pModElf));
+ if (!pModElf)
+ return VERR_NO_MEMORY;
+
+ pModElf->Core.u32Magic = RTLDRMOD_MAGIC;
+ pModElf->Core.eState = LDR_STATE_INVALID;
+ pModElf->Core.pReader = pReader;
+ pModElf->Core.enmFormat = RTLDRFMT_ELF;
+ pModElf->Core.enmType = RTLDRTYPE_OBJECT;
+ pModElf->Core.enmEndian = RTLDRENDIAN_LITTLE;
+#if ELF_MODE == 32
+ pModElf->Core.enmArch = RTLDRARCH_X86_32;
+#else
+ pModElf->Core.enmArch = RTLDRARCH_AMD64;
+#endif
+ //pModElf->pvBits = NULL;
+ //pModElf->Ehdr = {0};
+ //pModElf->paShdrs = NULL;
+ //pModElf->Rel.paSyms = NULL;
+ pModElf->Rel.iSymSh = ~0U;
+ //pModElf->Rel.cSyms = 0;
+ pModElf->Rel.iStrSh = ~0U;
+ //pModElf->Rel.cbStr = 0;
+ //pModElf->Rel.pStr = NULL;
+ //pModElf->Dyn.paSyms = NULL;
+ pModElf->Dyn.iSymSh = ~0U;
+ //pModElf->Dyn.cSyms = 0;
+ pModElf->Dyn.iStrSh = ~0U;
+ //pModElf->Dyn.cbStr = 0;
+ //pModElf->Dyn.pStr = NULL;
+ pModElf->iFirstSect = 1;
+ //pModElf->fShdrInOrder = false;
+ //pModElf->cbImage = 0;
+ pModElf->LinkAddress = ~(Elf_Addr)0;
+ //pModElf->cbShStr = 0;
+ //pModElf->pShStr = NULL;
+ //pModElf->iShEhFrame = 0;
+ //pModElf->iShEhFrameHdr= 0;
+ pModElf->iShDynamic = ~0U;
+ //pModElf->cDynamic = 0;
+ //pModElf->paDynamic = NULL;
+ //pModElf->paPhdrs = NULL;
+ pModElf->DynInfo.uPtrRelocs = ~(Elf_Addr)0;
+ //pModElf->DynInfo.cbRelocs = 0;
+ //pModElf->DynInfo.cbRelocEntry = 0;
+ //pModElf->DynInfo.uRelocType = 0;
+ //pModElf->DynInfo.idxShRelocs = 0;
+ pModElf->DynInfo.uPtrJmpRelocs = ~(Elf_Addr)0;
+ //pModElf->DynInfo.cbJmpRelocs = 0;
+ //pModElf->DynInfo.uJmpRelocType = 0;
+ //pModElf->DynInfo.idxShJmpRelocs = 0;
+
+ /*
+ * Read and validate the ELF header and match up the CPU architecture.
+ */
+ int rc = pReader->pfnRead(pReader, &pModElf->Ehdr, sizeof(pModElf->Ehdr), 0);
+ if (RT_SUCCESS(rc))
+ {
+ RTLDRARCH enmArchImage = RTLDRARCH_INVALID; /* shut up gcc */
+ rc = RTLDRELF_NAME(ValidateElfHeader)(&pModElf->Ehdr, cbRawImage, pszLogName, &enmArchImage, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if ( enmArch != RTLDRARCH_WHATEVER
+ && enmArch != enmArchImage)
+ rc = VERR_LDR_ARCH_MISMATCH;
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read the section headers, keeping a prestine copy for the module
+ * introspection methods.
+ */
+ size_t const cbShdrs = pModElf->Ehdr.e_shnum * sizeof(Elf_Shdr);
+ Elf_Shdr *paShdrs = (Elf_Shdr *)RTMemAlloc(cbShdrs * 2 + sizeof(RTLDRMODELFSHX) * pModElf->Ehdr.e_shnum);
+ if (paShdrs)
+ {
+ pModElf->paShdrs = paShdrs;
+ rc = pReader->pfnRead(pReader, paShdrs, cbShdrs, pModElf->Ehdr.e_shoff);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&paShdrs[pModElf->Ehdr.e_shnum], paShdrs, cbShdrs);
+ pModElf->paOrgShdrs = &paShdrs[pModElf->Ehdr.e_shnum];
+
+ pModElf->paShdrExtras = (PRTLDRMODELFSHX)&pModElf->paOrgShdrs[pModElf->Ehdr.e_shnum];
+ memset(pModElf->paShdrExtras, 0xff, sizeof(RTLDRMODELFSHX) * pModElf->Ehdr.e_shnum);
+
+ pModElf->cbShStr = paShdrs[pModElf->Ehdr.e_shstrndx].sh_size;
+
+ /*
+ * Validate the section headers and find relevant sections.
+ */
+ rc = RTLDRELF_NAME(ValidateAndProcessSectionHeaders)(pModElf, paShdrs, cbRawImage, pszLogName, pErrInfo);
+
+ /*
+ * Read validate and process program headers if ET_DYN or ET_EXEC.
+ */
+ if (RT_SUCCESS(rc) && (pModElf->Ehdr.e_type == ET_DYN || pModElf->Ehdr.e_type == ET_EXEC))
+ rc = RTLDRELF_NAME(ValidateAndProcessDynamicInfo)(pModElf, cbRawImage, fFlags, pszLogName, pErrInfo);
+
+ /*
+ * Massage the section headers.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (pModElf->Ehdr.e_type == ET_REL)
+ {
+ /* Do allocations and figure the image size: */
+ pModElf->LinkAddress = 0;
+ for (unsigned i = 1; i < pModElf->Ehdr.e_shnum; i++)
+ if (paShdrs[i].sh_flags & SHF_ALLOC)
+ {
+ paShdrs[i].sh_addr = paShdrs[i].sh_addralign
+ ? RT_ALIGN_T(pModElf->cbImage, paShdrs[i].sh_addralign, Elf_Addr)
+ : (Elf_Addr)pModElf->cbImage;
+ Elf_Addr EndAddr = paShdrs[i].sh_addr + paShdrs[i].sh_size;
+ if (pModElf->cbImage < EndAddr)
+ {
+ pModElf->cbImage = (size_t)EndAddr;
+ AssertMsgBreakStmt(pModElf->cbImage == EndAddr, (FMT_ELF_ADDR "\n", EndAddr), rc = VERR_IMAGE_TOO_BIG);
+ }
+ Log2(("RTLdrElf: %s: Assigned " FMT_ELF_ADDR " to section #%d\n", pszLogName, paShdrs[i].sh_addr, i));
+ }
+ }
+ else
+ {
+ /* Convert sh_addr to RVA: */
+ Assert(pModElf->LinkAddress != ~(Elf_Addr)0);
+ for (unsigned i = 0 /*!*/; i < pModElf->Ehdr.e_shnum; i++)
+ if (paShdrs[i].sh_flags & SHF_ALLOC)
+ paShdrs[i].sh_addr -= pModElf->LinkAddress;
+ }
+ }
+
+ /*
+ * Check if the sections are in order by address, as that will simplify
+ * enumeration and address translation.
+ */
+ pModElf->fShdrInOrder = true;
+ Elf_Addr uEndAddr = 0;
+ for (unsigned i = pModElf->iFirstSect; i < pModElf->Ehdr.e_shnum; i++)
+ if (paShdrs[i].sh_flags & SHF_ALLOC)
+ {
+ if (uEndAddr <= paShdrs[i].sh_addr)
+ uEndAddr = paShdrs[i].sh_addr + paShdrs[i].sh_size;
+ else
+ {
+ pModElf->fShdrInOrder = false;
+ break;
+ }
+ }
+
+ Log2(("RTLdrElf: iSymSh=%u cSyms=%u iStrSh=%u cbStr=%u rc=%Rrc cbImage=%#zx LinkAddress=" FMT_ELF_ADDR " fShdrInOrder=%RTbool\n",
+ pModElf->Rel.iSymSh, pModElf->Rel.cSyms, pModElf->Rel.iStrSh, pModElf->Rel.cbStr, rc,
+ pModElf->cbImage, pModElf->LinkAddress, pModElf->fShdrInOrder));
+ if (RT_SUCCESS(rc))
+ {
+ pModElf->Core.pOps = &RTLDRELF_MID(s_rtldrElf,Ops);
+ pModElf->Core.eState = LDR_STATE_OPENED;
+ *phLdrMod = &pModElf->Core;
+
+ LogFlow(("%s: %s: returns VINF_SUCCESS *phLdrMod=%p\n", __FUNCTION__, pszLogName, *phLdrMod));
+ return VINF_SUCCESS;
+ }
+ }
+
+ RTMemFree(paShdrs);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ RTMemFree(pModElf);
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+
+
+
+/*******************************************************************************
+* Cleanup Constants And Macros *
+*******************************************************************************/
+#undef RTLDRELF_NAME
+#undef RTLDRELF_SUFF
+#undef RTLDRELF_MID
+
+#undef FMT_ELF_ADDR
+#undef FMT_ELF_ADDR7
+#undef FMT_ELF_HALF
+#undef FMT_ELF_SHALF
+#undef FMT_ELF_OFF
+#undef FMT_ELF_SIZE
+#undef FMT_ELF_SWORD
+#undef FMT_ELF_WORD
+#undef FMT_ELF_XWORD
+#undef FMT_ELF_SXWORD
+
+#undef Elf_Ehdr
+#undef Elf_Phdr
+#undef Elf_Shdr
+#undef Elf_Sym
+#undef Elf_Rel
+#undef Elf_Rela
+#undef Elf_Reloc
+#undef Elf_Nhdr
+#undef Elf_Dyn
+
+#undef Elf_Addr
+#undef Elf_Half
+#undef Elf_Off
+#undef Elf_Size
+#undef Elf_Sword
+#undef Elf_Word
+#undef Elf_Xword
+#undef Elf_Sxword
+
+#undef RTLDRMODELF
+#undef PRTLDRMODELF
+
+#undef ELF_R_SYM
+#undef ELF_R_TYPE
+#undef ELF_R_INFO
+
+#undef ELF_ST_BIND
+
diff --git a/src/VBox/Runtime/common/ldr/ldrEx.cpp b/src/VBox/Runtime/common/ldr/ldrEx.cpp
new file mode 100644
index 00000000..d4a644ab
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrEx.cpp
@@ -0,0 +1,773 @@
+/* $Id: ldrEx.cpp $ */
+/** @file
+ * IPRT - Binary Image Loader, Extended Features.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/dbg.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/md5.h>
+#include <iprt/mem.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/formats/mz.h>
+#include <iprt/formats/mach-o.h>
+#include "internal/ldr.h"
+
+#if defined(LDR_ONLY_PE) || defined(LDR_ONLY_MACHO)
+# undef LDR_WITH_PE
+# undef LDR_WITH_ELF
+# undef LDR_WITH_LX
+# undef LDR_WITH_LE
+# undef LDR_WITH_MACHO
+# undef LDR_WITH_NE
+# undef LDR_WITH_MZ
+# undef LDR_WITH_AOUT
+# ifdef LDR_ONLY_PE
+# define LDR_WITH_PE
+# endif
+# ifdef LDR_ONLY_MACHO
+# define LDR_WITH_MACHO
+# endif
+#endif
+
+
+RTDECL(int) RTLdrOpenWithReader(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phMod, PRTERRINFO pErrInfo)
+{
+ /*
+ * Resolve RTLDRARCH_HOST.
+ */
+ if (enmArch == RTLDRARCH_HOST)
+ enmArch = RTLdrGetHostArch();
+
+ /*
+ * Read and verify the file signature.
+ */
+ union
+ {
+ char ach[4];
+ uint16_t au16[2];
+ uint32_t u32;
+ } uSign;
+ int rc = pReader->pfnRead(pReader, &uSign, sizeof(uSign), 0);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ( uSign.au16[0] != IMAGE_DOS_SIGNATURE
+ && uSign.u32 != IMAGE_NT_SIGNATURE
+ && uSign.u32 != IMAGE_ELF_SIGNATURE
+ && uSign.au16[0] != IMAGE_LX_SIGNATURE
+ && uSign.u32 != IMAGE_MACHO64_SIGNATURE
+ && uSign.u32 != IMAGE_MACHO64_SIGNATURE_OE
+ && uSign.u32 != IMAGE_MACHO32_SIGNATURE
+ && uSign.u32 != IMAGE_MACHO32_SIGNATURE_OE
+ && uSign.u32 != IMAGE_FAT_SIGNATURE
+ && uSign.u32 != IMAGE_FAT_SIGNATURE_OE )
+ {
+ Log(("rtldrOpenWithReader: %s: unknown magic %#x / '%.4s\n", pReader->pfnLogName(pReader), uSign.u32, &uSign.ach[0]));
+ return VERR_INVALID_EXE_SIGNATURE;
+ }
+ uint32_t offHdr = 0;
+ if (uSign.au16[0] == IMAGE_DOS_SIGNATURE)
+ {
+ rc = pReader->pfnRead(pReader, &offHdr, sizeof(offHdr), RT_UOFFSETOF(IMAGE_DOS_HEADER, e_lfanew));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (offHdr <= sizeof(IMAGE_DOS_HEADER))
+ {
+ Log(("rtldrOpenWithReader: %s: no new header / invalid offset %#RX32\n", pReader->pfnLogName(pReader), offHdr));
+ return VERR_INVALID_EXE_SIGNATURE;
+ }
+ rc = pReader->pfnRead(pReader, &uSign, sizeof(uSign), offHdr);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ( uSign.u32 != IMAGE_NT_SIGNATURE
+ && uSign.au16[0] != IMAGE_LX_SIGNATURE
+ && uSign.au16[0] != IMAGE_LE_SIGNATURE
+ && uSign.au16[0] != IMAGE_NE_SIGNATURE)
+ {
+ Log(("rtldrOpenWithReader: %s: unknown new magic %#x / '%.4s\n", pReader->pfnLogName(pReader), uSign.u32, &uSign.ach[0]));
+ return VERR_INVALID_EXE_SIGNATURE;
+ }
+ }
+
+ /*
+ * Create image interpreter instance depending on the signature.
+ */
+ if (uSign.u32 == IMAGE_NT_SIGNATURE)
+#ifdef LDR_WITH_PE
+ rc = rtldrPEOpen(pReader, fFlags, enmArch, offHdr, phMod, pErrInfo);
+#else
+ rc = VERR_PE_EXE_NOT_SUPPORTED;
+#endif
+ else if (uSign.u32 == IMAGE_ELF_SIGNATURE)
+#if defined(LDR_WITH_ELF)
+ rc = rtldrELFOpen(pReader, fFlags, enmArch, phMod, pErrInfo);
+#else
+ rc = VERR_ELF_EXE_NOT_SUPPORTED;
+#endif
+ else if ( uSign.u32 == IMAGE_MACHO64_SIGNATURE
+ || uSign.u32 == IMAGE_MACHO64_SIGNATURE_OE
+ || uSign.u32 == IMAGE_MACHO32_SIGNATURE
+ || uSign.u32 == IMAGE_MACHO32_SIGNATURE_OE)
+#if defined(LDR_WITH_MACHO)
+ rc = rtldrMachOOpen(pReader, fFlags, enmArch, offHdr, phMod, pErrInfo);
+#else
+ rc = VERR_INVALID_EXE_SIGNATURE;
+#endif
+ else if ( uSign.u32 == IMAGE_FAT_SIGNATURE
+ || uSign.u32 == IMAGE_FAT_SIGNATURE_OE)
+#if defined(LDR_WITH_MACHO)
+ rc = rtldrFatOpen(pReader, fFlags, enmArch, phMod, pErrInfo);
+#else
+ rc = VERR_INVALID_EXE_SIGNATURE;
+#endif
+ else if (uSign.au16[0] == IMAGE_LX_SIGNATURE)
+#ifdef LDR_WITH_LX
+ rc = rtldrLXOpen(pReader, fFlags, enmArch, offHdr, phMod, pErrInfo);
+#else
+ rc = VERR_LX_EXE_NOT_SUPPORTED;
+#endif
+ else if (uSign.au16[0] == IMAGE_LE_SIGNATURE)
+#ifdef LDR_WITH_LE
+ rc = rtldrLEOpen(pReader, fFlags, enmArch, phMod, pErrInfo);
+#else
+ rc = VERR_LE_EXE_NOT_SUPPORTED;
+#endif
+ else if (uSign.au16[0] == IMAGE_NE_SIGNATURE)
+#ifdef LDR_WITH_NE
+ rc = rtldrNEOpen(pReader, fFlags, enmArch, phMod, pErrInfo);
+#else
+ rc = VERR_NE_EXE_NOT_SUPPORTED;
+#endif
+ else if (uSign.au16[0] == IMAGE_DOS_SIGNATURE)
+#ifdef LDR_WITH_MZ
+ rc = rtldrMZOpen(pReader, fFlags, enmArch, phMod, pErrInfo);
+#else
+ rc = VERR_MZ_EXE_NOT_SUPPORTED;
+#endif
+ else if (/* uSign.u32 == IMAGE_AOUT_A_SIGNATURE
+ || uSign.u32 == IMAGE_AOUT_Z_SIGNATURE*/ /** @todo find the aout magics in emx or binutils. */
+ 0)
+#ifdef LDR_WITH_AOUT
+ rc = rtldrAOUTOpen(pReader, fFlags, enmArch, phMod, pErrInfo);
+#else
+ rc = VERR_AOUT_EXE_NOT_SUPPORTED;
+#endif
+ else
+ {
+ Log(("rtldrOpenWithReader: %s: the format isn't implemented %#x / '%.4s\n", pReader->pfnLogName(pReader), uSign.u32, &uSign.ach[0]));
+ rc = VERR_INVALID_EXE_SIGNATURE;
+ }
+
+ LogFlow(("rtldrOpenWithReader: %s: returns %Rrc *phMod=%p\n", pReader->pfnLogName(pReader), rc, *phMod));
+ return rc;
+}
+
+
+RTDECL(size_t) RTLdrSize(RTLDRMOD hLdrMod)
+{
+ LogFlow(("RTLdrSize: hLdrMod=%RTldrm\n", hLdrMod));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), ~(size_t)0);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), ~(size_t)0);
+
+ /*
+ * Do it.
+ */
+ size_t cb = pMod->pOps->pfnGetImageSize(pMod);
+ LogFlow(("RTLdrSize: returns %zu\n", cb));
+ return cb;
+}
+RT_EXPORT_SYMBOL(RTLdrSize);
+
+
+RTDECL(int) RTLdrGetBits(RTLDRMOD hLdrMod, void *pvBits, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ LogFlow(("RTLdrGetBits: hLdrMod=%RTldrm pvBits=%p BaseAddress=%RTptr pfnGetImport=%p pvUser=%p\n",
+ hLdrMod, pvBits, BaseAddress, pfnGetImport, pvUser));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrReturn(pvBits, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pfnGetImport, VERR_INVALID_POINTER);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ /*
+ * Do it.
+ */
+ int rc = pMod->pOps->pfnGetBits(pMod, pvBits, BaseAddress, pfnGetImport, pvUser);
+ LogFlow(("RTLdrGetBits: returns %Rrc\n",rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrGetBits);
+
+
+RTDECL(int) RTLdrRelocate(RTLDRMOD hLdrMod, void *pvBits, RTLDRADDR NewBaseAddress, RTLDRADDR OldBaseAddress,
+ PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ LogFlow(("RTLdrRelocate: hLdrMod=%RTldrm pvBits=%p NewBaseAddress=%RTptr OldBaseAddress=%RTptr pfnGetImport=%p pvUser=%p\n",
+ hLdrMod, pvBits, NewBaseAddress, OldBaseAddress, pfnGetImport, pvUser));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrReturn(pvBits, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnGetImport, VERR_INVALID_POINTER);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ /*
+ * Do it.
+ */
+ int rc = pMod->pOps->pfnRelocate(pMod, pvBits, NewBaseAddress, OldBaseAddress, pfnGetImport, pvUser);
+ LogFlow(("RTLdrRelocate: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrRelocate);
+
+
+RTDECL(int) RTLdrGetSymbolEx(RTLDRMOD hLdrMod, const void *pvBits, RTLDRADDR BaseAddress,
+ uint32_t iOrdinal, const char *pszSymbol, PRTLDRADDR pValue)
+{
+ LogFlow(("RTLdrGetSymbolEx: hLdrMod=%RTldrm pvBits=%p BaseAddress=%RTptr iOrdinal=%#x pszSymbol=%p:{%s} pValue=%p\n",
+ hLdrMod, pvBits, BaseAddress, iOrdinal, pszSymbol, pszSymbol, pValue));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pvBits, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pszSymbol, VERR_INVALID_POINTER);
+ AssertReturn(pszSymbol || iOrdinal != UINT32_MAX, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pValue, VERR_INVALID_POINTER);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+
+ /*
+ * Do it.
+ */
+ int rc;
+ if (pMod->pOps->pfnGetSymbolEx)
+ rc = pMod->pOps->pfnGetSymbolEx(pMod, pvBits, BaseAddress, iOrdinal, pszSymbol, pValue);
+ else if (!BaseAddress && !pvBits && iOrdinal == UINT32_MAX)
+ {
+ void *pvValue;
+ rc = pMod->pOps->pfnGetSymbol(pMod, pszSymbol, &pvValue);
+ if (RT_SUCCESS(rc))
+ *pValue = (uintptr_t)pvValue;
+ }
+ else
+ AssertMsgFailedReturn(("BaseAddress=%RTptr pvBits=%p\n", BaseAddress, pvBits), VERR_INVALID_FUNCTION);
+ LogFlow(("RTLdrGetSymbolEx: returns %Rrc *pValue=%p\n", rc, *pValue));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrGetSymbolEx);
+
+
+RTDECL(int) RTLdrQueryForwarderInfo(RTLDRMOD hLdrMod, const void *pvBits, uint32_t iOrdinal, const char *pszSymbol,
+ PRTLDRIMPORTINFO pInfo, size_t cbInfo)
+{
+ LogFlow(("RTLdrQueryForwarderInfo: hLdrMod=%RTldrm pvBits=%p iOrdinal=%#x pszSymbol=%p:{%s} pInfo=%p cbInfo=%zu\n",
+ hLdrMod, pvBits, iOrdinal, pszSymbol, pszSymbol, pInfo, cbInfo));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pvBits, VERR_INVALID_POINTER);
+ AssertMsgReturn(pszSymbol, ("pszSymbol=%p\n", pszSymbol), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pInfo, VERR_INVALID_PARAMETER);
+ AssertReturn(cbInfo >= sizeof(*pInfo), VERR_INVALID_PARAMETER);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+
+ /*
+ * Do it.
+ */
+ int rc;
+ if (pMod->pOps->pfnQueryForwarderInfo)
+ {
+ rc = pMod->pOps->pfnQueryForwarderInfo(pMod, pvBits, iOrdinal, pszSymbol, pInfo, cbInfo);
+ if (RT_SUCCESS(rc))
+ LogFlow(("RTLdrQueryForwarderInfo: returns %Rrc pInfo={%#x,%#x,%s,%s}\n", rc,
+ pInfo->iSelfOrdinal, pInfo->iOrdinal, pInfo->pszSymbol, pInfo->szModule));
+ else
+ LogFlow(("RTLdrQueryForwarderInfo: returns %Rrc\n", rc));
+ }
+ else
+ {
+ LogFlow(("RTLdrQueryForwarderInfo: returns VERR_NOT_SUPPORTED\n"));
+ rc = VERR_NOT_SUPPORTED;
+ }
+ return rc;
+
+}
+RT_EXPORT_SYMBOL(RTLdrQueryForwarderInfo);
+
+
+RTDECL(int) RTLdrEnumSymbols(RTLDRMOD hLdrMod, unsigned fFlags, const void *pvBits, RTLDRADDR BaseAddress,
+ PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ LogFlow(("RTLdrEnumSymbols: hLdrMod=%RTldrm fFlags=%#x pvBits=%p BaseAddress=%RTptr pfnCallback=%p pvUser=%p\n",
+ hLdrMod, fFlags, pvBits, BaseAddress, pfnCallback, pvUser));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pvBits, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ /*
+ * Do it.
+ */
+ int rc = pMod->pOps->pfnEnumSymbols(pMod, fFlags, pvBits, BaseAddress, pfnCallback, pvUser);
+ LogFlow(("RTLdrEnumSymbols: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrEnumSymbols);
+
+
+RTDECL(int) RTLdrEnumDbgInfo(RTLDRMOD hLdrMod, const void *pvBits, PFNRTLDRENUMDBG pfnCallback, void *pvUser)
+{
+ LogFlow(("RTLdrEnumDbgInfo: hLdrMod=%RTldrm pvBits=%p pfnCallback=%p pvUser=%p\n",
+ hLdrMod, pvBits, pfnCallback, pvUser));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pvBits, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ /*
+ * Do it.
+ */
+ int rc;
+ if (pMod->pOps->pfnEnumDbgInfo)
+ rc = pMod->pOps->pfnEnumDbgInfo(pMod, pvBits, pfnCallback, pvUser);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlow(("RTLdrEnumDbgInfo: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrEnumDbgInfo);
+
+
+RTDECL(int) RTLdrEnumSegments(RTLDRMOD hLdrMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser)
+{
+ LogFlow(("RTLdrEnumSegments: hLdrMod=%RTldrm pfnCallback=%p pvUser=%p\n",
+ hLdrMod, pfnCallback, pvUser));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ /*
+ * Do it.
+ */
+ int rc;
+ if (pMod->pOps->pfnEnumSegments)
+ rc = pMod->pOps->pfnEnumSegments(pMod, pfnCallback, pvUser);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlow(("RTLdrEnumSegments: returns %Rrc\n", rc));
+ return rc;
+
+}
+RT_EXPORT_SYMBOL(RTLdrEnumSegments);
+
+
+RTDECL(int) RTLdrLinkAddressToSegOffset(RTLDRMOD hLdrMod, RTLDRADDR LinkAddress, uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ LogFlow(("RTLdrLinkAddressToSegOffset: hLdrMod=%RTldrm LinkAddress=%RTptr piSeg=%p poffSeg=%p\n",
+ hLdrMod, LinkAddress, piSeg, poffSeg));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrReturn(piSeg, VERR_INVALID_POINTER);
+ AssertPtrReturn(poffSeg, VERR_INVALID_POINTER);
+
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ *piSeg = UINT32_MAX;
+ *poffSeg = ~(RTLDRADDR)0;
+
+ /*
+ * Do it.
+ */
+ int rc;
+ if (pMod->pOps->pfnLinkAddressToSegOffset)
+ rc = pMod->pOps->pfnLinkAddressToSegOffset(pMod, LinkAddress, piSeg, poffSeg);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlow(("RTLdrLinkAddressToSegOffset: returns %Rrc %#x:%RTptr\n", rc, *piSeg, *poffSeg));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrLinkAddressToSegOffset);
+
+
+RTDECL(int) RTLdrLinkAddressToRva(RTLDRMOD hLdrMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva)
+{
+ LogFlow(("RTLdrLinkAddressToRva: hLdrMod=%RTldrm LinkAddress=%RTptr pRva=%p\n",
+ hLdrMod, LinkAddress, pRva));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrReturn(pRva, VERR_INVALID_POINTER);
+
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ *pRva = ~(RTLDRADDR)0;
+
+ /*
+ * Do it.
+ */
+ int rc;
+ if (pMod->pOps->pfnLinkAddressToRva)
+ rc = pMod->pOps->pfnLinkAddressToRva(pMod, LinkAddress, pRva);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlow(("RTLdrLinkAddressToRva: returns %Rrc %RTptr\n", rc, *pRva));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrLinkAddressToRva);
+
+
+RTDECL(int) RTLdrSegOffsetToRva(RTLDRMOD hLdrMod, uint32_t iSeg, RTLDRADDR offSeg, PRTLDRADDR pRva)
+{
+ LogFlow(("RTLdrSegOffsetToRva: hLdrMod=%RTldrm iSeg=%#x offSeg=%RTptr pRva=%p\n", hLdrMod, iSeg, offSeg, pRva));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrReturn(pRva, VERR_INVALID_POINTER);
+
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ *pRva = ~(RTLDRADDR)0;
+
+ /*
+ * Do it.
+ */
+ int rc;
+ if (pMod->pOps->pfnSegOffsetToRva)
+ rc = pMod->pOps->pfnSegOffsetToRva(pMod, iSeg, offSeg, pRva);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlow(("RTLdrSegOffsetToRva: returns %Rrc %RTptr\n", rc, *pRva));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrSegOffsetToRva);
+
+RTDECL(int) RTLdrRvaToSegOffset(RTLDRMOD hLdrMod, RTLDRADDR Rva, uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ LogFlow(("RTLdrRvaToSegOffset: hLdrMod=%RTldrm Rva=%RTptr piSeg=%p poffSeg=%p\n",
+ hLdrMod, Rva, piSeg, poffSeg));
+
+ /*
+ * Validate input.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ AssertPtrReturn(piSeg, VERR_INVALID_POINTER);
+ AssertPtrReturn(poffSeg, VERR_INVALID_POINTER);
+
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER);
+
+ *piSeg = UINT32_MAX;
+ *poffSeg = ~(RTLDRADDR)0;
+
+ /*
+ * Do it.
+ */
+ int rc;
+ if (pMod->pOps->pfnRvaToSegOffset)
+ rc = pMod->pOps->pfnRvaToSegOffset(pMod, Rva, piSeg, poffSeg);
+ else
+ rc = VERR_NOT_SUPPORTED;
+
+ LogFlow(("RTLdrRvaToSegOffset: returns %Rrc %#x:%RTptr\n", rc, *piSeg, *poffSeg));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrRvaToSegOffset);
+
+
+RTDECL(int) RTLdrQueryProp(RTLDRMOD hLdrMod, RTLDRPROP enmProp, void *pvBuf, size_t cbBuf)
+{
+ return RTLdrQueryPropEx(hLdrMod, enmProp, NULL /*pvBits*/, pvBuf, cbBuf, NULL);
+}
+RT_EXPORT_SYMBOL(RTLdrQueryProp);
+
+
+RTDECL(int) RTLdrQueryPropEx(RTLDRMOD hLdrMod, RTLDRPROP enmProp, void *pvBits, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRENDIAN_INVALID);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+
+ AssertPtrNullReturn(pcbRet, VERR_INVALID_POINTER);
+ size_t cbRet;
+ if (!pcbRet)
+ pcbRet = &cbRet;
+
+ /*
+ * Do some pre screening of the input
+ */
+ switch (enmProp)
+ {
+ case RTLDRPROP_UUID:
+ *pcbRet = sizeof(RTUUID);
+ AssertReturn(cbBuf == sizeof(RTUUID), VERR_INVALID_PARAMETER);
+ break;
+ case RTLDRPROP_TIMESTAMP_SECONDS:
+ *pcbRet = sizeof(int64_t);
+ AssertReturn(cbBuf == sizeof(int32_t) || cbBuf == sizeof(int64_t), VERR_INVALID_PARAMETER);
+ *pcbRet = cbBuf;
+ break;
+ case RTLDRPROP_IS_SIGNED:
+ *pcbRet = sizeof(bool);
+ AssertReturn(cbBuf == sizeof(bool), VERR_INVALID_PARAMETER);
+ break;
+ case RTLDRPROP_PKCS7_SIGNED_DATA:
+ case RTLDRPROP_SHA1_PAGE_HASHES:
+ case RTLDRPROP_SHA256_PAGE_HASHES:
+ *pcbRet = 0;
+ break;
+ case RTLDRPROP_HASHABLE_PAGES:
+ *pcbRet = sizeof(uint32_t);
+ AssertReturn(cbBuf >= sizeof(uint32_t), VERR_INVALID_PARAMETER);
+ break;
+ case RTLDRPROP_SIGNATURE_CHECKS_ENFORCED:
+ *pcbRet = sizeof(bool);
+ AssertReturn(cbBuf == sizeof(bool), VERR_INVALID_PARAMETER);
+ break;
+ case RTLDRPROP_IMPORT_COUNT:
+ *pcbRet = sizeof(uint32_t);
+ AssertReturn(cbBuf == sizeof(uint32_t), VERR_INVALID_PARAMETER);
+ break;
+ case RTLDRPROP_IMPORT_MODULE:
+ *pcbRet = sizeof(uint32_t);
+ AssertReturn(cbBuf >= sizeof(uint32_t), VERR_INVALID_PARAMETER);
+ break;
+ case RTLDRPROP_FILE_OFF_HEADER:
+ *pcbRet = sizeof(uint64_t);
+ AssertReturn(cbBuf == sizeof(uint32_t) || cbBuf == sizeof(uint64_t), VERR_INVALID_PARAMETER);
+ break;
+ case RTLDRPROP_INTERNAL_NAME:
+ case RTLDRPROP_UNWIND_TABLE:
+ *pcbRet = 0;
+ break;
+
+ case RTLDRPROP_UNWIND_INFO:
+ AssertReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf >= sizeof(uint32_t), VERR_INVALID_PARAMETER);
+ *pcbRet = 0;
+ break;
+
+ case RTLDRPROP_BUILDID:
+ *pcbRet = 0;
+ break;
+
+ default:
+ AssertFailedReturn(VERR_INVALID_FUNCTION);
+ }
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+
+ /*
+ * Call the image specific worker, if there is one.
+ */
+ if (!pMod->pOps->pfnQueryProp)
+ return VERR_NOT_SUPPORTED;
+ return pMod->pOps->pfnQueryProp(pMod, enmProp, pvBits, pvBuf, cbBuf, pcbRet);
+}
+RT_EXPORT_SYMBOL(RTLdrQueryPropEx);
+
+
+RTDECL(int) RTLdrVerifySignature(RTLDRMOD hLdrMod, PFNRTLDRVALIDATESIGNEDDATA pfnCallback, void *pvUser, PRTERRINFO pErrInfo)
+{
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);
+
+ /*
+ * Call the image specific worker, if there is one.
+ */
+ if (!pMod->pOps->pfnVerifySignature)
+ return VERR_NOT_SUPPORTED;
+ return pMod->pOps->pfnVerifySignature(pMod, pfnCallback, pvUser, pErrInfo);
+}
+RT_EXPORT_SYMBOL(RTLdrVerifySignature);
+
+
+RTDECL(int) RTLdrHashImage(RTLDRMOD hLdrMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash)
+{
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+
+ /*
+ * Make sure there is sufficient space for the wanted digest and that
+ * it's supported.
+ */
+ switch (enmDigest)
+ {
+ case RTDIGESTTYPE_MD5: AssertReturn(cbHash >= RTMD5_HASH_SIZE, VERR_BUFFER_OVERFLOW); break;
+ case RTDIGESTTYPE_SHA1: AssertReturn(cbHash >= RTSHA1_HASH_SIZE, VERR_BUFFER_OVERFLOW); break;
+ case RTDIGESTTYPE_SHA256: AssertReturn(cbHash >= RTSHA256_HASH_SIZE, VERR_BUFFER_OVERFLOW); break;
+ case RTDIGESTTYPE_SHA512: AssertReturn(cbHash >= RTSHA512_HASH_SIZE, VERR_BUFFER_OVERFLOW); break;
+ default:
+ if (enmDigest > RTDIGESTTYPE_INVALID && enmDigest < RTDIGESTTYPE_END)
+ return VERR_NOT_SUPPORTED;
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+ AssertPtrReturn(pabHash, VERR_INVALID_POINTER);
+
+ /*
+ * Call the image specific worker, if there is one.
+ */
+ if (!pMod->pOps->pfnHashImage)
+ return VERR_NOT_SUPPORTED;
+ return pMod->pOps->pfnHashImage(pMod, enmDigest, pabHash, cbHash);
+}
+RT_EXPORT_SYMBOL(RTLdrHashImage);
+
+
+RTDECL(int) RTLdrUnwindFrame(RTLDRMOD hLdrMod, void const *pvBits, uint32_t iSeg, RTLDRADDR off, PRTDBGUNWINDSTATE pState)
+{
+ /*
+ * Validate.
+ */
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+ AssertPtr(pState);
+ AssertReturn(pState->u32Magic == RTDBGUNWINDSTATE_MAGIC, VERR_INVALID_MAGIC);
+
+ /*
+ * Pass on the work.
+ */
+ if (pMod->pOps->pfnUnwindFrame)
+ return pMod->pOps->pfnUnwindFrame(pMod, pvBits, iSeg, off, pState);
+ return VERR_DBG_NO_UNWIND_INFO;
+}
+RT_EXPORT_SYMBOL(RTLdrUnwindFrame);
+
+
+/**
+ * Internal method used by the IPRT debug bits.
+ *
+ * @returns IPRT status code.
+ * @param hLdrMod The loader handle which executable we wish to
+ * read from.
+ * @param pvBuf The output buffer.
+ * @param iDbgInfo The debug info ordinal number if the request
+ * corresponds exactly to a debug info part from
+ * pfnEnumDbgInfo. Otherwise, pass UINT32_MAX.
+ * @param off Where in the executable file to start reading.
+ * @param cb The number of bytes to read.
+ *
+ * @remarks Fixups will only be applied if @a iDbgInfo is specified.
+ */
+DECLHIDDEN(int) rtLdrReadAt(RTLDRMOD hLdrMod, void *pvBuf, uint32_t iDbgInfo, RTFOFF off, size_t cb)
+{
+ AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE);
+ PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod;
+
+ if (iDbgInfo != UINT32_MAX)
+ {
+ AssertReturn(pMod->pOps->pfnReadDbgInfo, VERR_NOT_SUPPORTED);
+ return pMod->pOps->pfnReadDbgInfo(pMod, iDbgInfo, off, cb, pvBuf);
+ }
+
+ AssertReturn(pMod->pReader, VERR_NOT_SUPPORTED);
+ return pMod->pReader->pfnRead(pMod->pReader, pvBuf, cb, off);
+}
+
+
+RTDECL(const char *) RTLdrArchName(RTLDRARCH enmArch)
+{
+ switch (enmArch)
+ {
+ case RTLDRARCH_INVALID: return "INVALID";
+ case RTLDRARCH_WHATEVER: return "WHATEVER";
+ case RTLDRARCH_HOST: return "HOST";
+ case RTLDRARCH_AMD64: return "AMD64";
+ case RTLDRARCH_X86_16: return "X86_16";
+ case RTLDRARCH_X86_32: return "X86_32";
+ case RTLDRARCH_ARM32: return "ARM32";
+ case RTLDRARCH_ARM64: return "ARM64";
+
+ case RTLDRARCH_END:
+ case RTLDRARCH_32BIT_HACK:
+ break;
+ }
+ return "UNKNOWN";
+}
+RT_EXPORT_SYMBOL(RTLdrArchName);
+
diff --git a/src/VBox/Runtime/common/ldr/ldrFile.cpp b/src/VBox/Runtime/common/ldr/ldrFile.cpp
new file mode 100644
index 00000000..ea570524
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrFile.cpp
@@ -0,0 +1,317 @@
+/* $Id: ldrFile.cpp $ */
+/** @file
+ * IPRT - Binary Image Loader, The File Oriented Parts.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloc.h>
+#include <iprt/file.h>
+#include <iprt/assert.h>
+#include <iprt/log.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/formats/mz.h>
+#include "internal/ldr.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * File Reader instance.
+ * This provides raw image bits from a file.
+ */
+typedef struct RTLDRREADERFILE
+{
+ /** The core. */
+ RTLDRREADER Core;
+ /** The file. */
+ RTFILE hFile;
+ /** The file size. */
+ uint64_t cbFile;
+ /** The current offset. */
+ RTFOFF off;
+ /** Number of users or the mapping. */
+ RTUINT cMappings;
+ /** Pointer to the in memory mapping. */
+ void *pvMapping;
+ /** The filename (variable size). */
+ char szFilename[1];
+} RTLDRREADERFILE, *PRTLDRREADERFILE;
+
+
+/** @copydoc RTLDRREADER::pfnRead */
+static DECLCALLBACK(int) rtldrFileRead(PRTLDRREADER pReader, void *pvBuf, size_t cb, RTFOFF off)
+{
+ PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader;
+
+ /*
+ * Seek.
+ */
+ if (pFileReader->off != off)
+ {
+ int rc = RTFileSeek(pFileReader->hFile, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(rc))
+ {
+ pFileReader->off = -1;
+ return rc;
+ }
+ pFileReader->off = off;
+ }
+
+ /*
+ * Read.
+ */
+ int rc = RTFileRead(pFileReader->hFile, pvBuf, cb, NULL);
+ if (RT_SUCCESS(rc))
+ pFileReader->off += cb;
+ else
+ pFileReader->off = -1;
+ return rc;
+}
+
+
+/** @copydoc RTLDRREADER::pfnTell */
+static DECLCALLBACK(RTFOFF) rtldrFileTell(PRTLDRREADER pReader)
+{
+ PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader;
+ return pFileReader->off;
+}
+
+
+/** @copydoc RTLDRREADER::pfnSize */
+static DECLCALLBACK(uint64_t) rtldrFileSize(PRTLDRREADER pReader)
+{
+ PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader;
+ return pFileReader->cbFile;
+}
+
+
+/** @copydoc RTLDRREADER::pfnLogName */
+static DECLCALLBACK(const char *) rtldrFileLogName(PRTLDRREADER pReader)
+{
+ PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader;
+ return pFileReader->szFilename;
+}
+
+
+/** @copydoc RTLDRREADER::pfnMap */
+static DECLCALLBACK(int) rtldrFileMap(PRTLDRREADER pReader, const void **ppvBits)
+{
+ PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader;
+
+ /*
+ * Already mapped?
+ */
+ if (pFileReader->pvMapping)
+ {
+ pFileReader->cMappings++;
+ *ppvBits = pFileReader->pvMapping;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Allocate memory.
+ */
+ size_t cb = (size_t)pFileReader->cbFile;
+ if ((uint64_t)cb != pFileReader->cbFile)
+ return VERR_IMAGE_TOO_BIG;
+ pFileReader->pvMapping = RTMemAlloc(cb);
+ if (!pFileReader->pvMapping)
+ return VERR_NO_MEMORY;
+ int rc = rtldrFileRead(pReader, pFileReader->pvMapping, cb, 0);
+ if (RT_SUCCESS(rc))
+ {
+ pFileReader->cMappings = 1;
+ *ppvBits = pFileReader->pvMapping;
+ }
+ else
+ {
+ RTMemFree(pFileReader->pvMapping);
+ pFileReader->pvMapping = NULL;
+ }
+
+ return rc;
+}
+
+
+/** @copydoc RTLDRREADER::pfnUnmap */
+static DECLCALLBACK(int) rtldrFileUnmap(PRTLDRREADER pReader, const void *pvBits)
+{
+ PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader;
+ AssertReturn(pFileReader->cMappings > 0, VERR_INVALID_PARAMETER);
+
+ if (!--pFileReader->cMappings)
+ {
+ RTMemFree(pFileReader->pvMapping);
+ pFileReader->pvMapping = NULL;
+ }
+
+ NOREF(pvBits);
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTLDRREADER::pfnDestroy */
+static DECLCALLBACK(int) rtldrFileDestroy(PRTLDRREADER pReader)
+{
+ int rc = VINF_SUCCESS;
+ PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader;
+
+ Assert(!pFileReader->cMappings);
+
+ if (pFileReader->hFile != NIL_RTFILE)
+ {
+ rc = RTFileClose(pFileReader->hFile);
+ AssertRC(rc);
+ pFileReader->hFile = NIL_RTFILE;
+ }
+
+ if (pFileReader->pvMapping)
+ {
+ RTMemFree(pFileReader->pvMapping);
+ pFileReader->pvMapping = NULL;
+ }
+
+ RTMemFree(pFileReader);
+ return rc;
+}
+
+
+/**
+ * Opens a loader file reader.
+ *
+ * @returns iprt status code.
+ * @param ppReader Where to store the reader instance on success.
+ * @param pszFilename The file to open.
+ */
+static int rtldrFileCreate(PRTLDRREADER *ppReader, const char *pszFilename)
+{
+ size_t cchFilename = strlen(pszFilename);
+ int rc = VERR_NO_MEMORY;
+ PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)RTMemAlloc(sizeof(*pFileReader) + cchFilename);
+ if (pFileReader)
+ {
+ memcpy(pFileReader->szFilename, pszFilename, cchFilename + 1);
+ rc = RTFileOpen(&pFileReader->hFile, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileQuerySize(pFileReader->hFile, &pFileReader->cbFile);
+ if (RT_SUCCESS(rc))
+ {
+ pFileReader->Core.uMagic = RTLDRREADER_MAGIC;
+ pFileReader->Core.pfnRead = rtldrFileRead;
+ pFileReader->Core.pfnTell = rtldrFileTell;
+ pFileReader->Core.pfnSize = rtldrFileSize;
+ pFileReader->Core.pfnLogName = rtldrFileLogName;
+ pFileReader->Core.pfnMap = rtldrFileMap;
+ pFileReader->Core.pfnUnmap = rtldrFileUnmap;
+ pFileReader->Core.pfnDestroy = rtldrFileDestroy;
+ pFileReader->off = 0;
+ pFileReader->cMappings = 0;
+ pFileReader->pvMapping = NULL;
+ *ppReader = &pFileReader->Core;
+ return VINF_SUCCESS;
+ }
+
+ RTFileClose(pFileReader->hFile);
+ }
+ RTMemFree(pFileReader);
+ }
+ *ppReader = NULL;
+ return rc;
+}
+
+
+/**
+ * Open a binary image file.
+ *
+ * @returns iprt status code.
+ * @param pszFilename Image filename.
+ * @param fFlags Valid RTLDR_O_XXX combination.
+ * @param enmArch CPU architecture specifier for the image to be loaded.
+ * @param phLdrMod Where to store the handle to the loader module.
+ */
+RTDECL(int) RTLdrOpen(const char *pszFilename, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod)
+{
+ return RTLdrOpenEx(pszFilename, fFlags, enmArch, phLdrMod, NULL /*pErrInfo*/);
+}
+RT_EXPORT_SYMBOL(RTLdrOpen);
+
+
+/**
+ * Open a binary image file, extended version.
+ *
+ * @returns iprt status code.
+ * @param pszFilename Image filename.
+ * @param fFlags Valid RTLDR_O_XXX combination.
+ * @param enmArch CPU architecture specifier for the image to be loaded.
+ * @param phLdrMod Where to store the handle to the loader module.
+ * @param pErrInfo Where to return extended error information. Optional.
+ */
+RTDECL(int) RTLdrOpenEx(const char *pszFilename, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo)
+{
+ LogFlow(("RTLdrOpenEx: pszFilename=%p:{%s} fFlags=%#x enmArch=%d phLdrMod=%p\n",
+ pszFilename, pszFilename, fFlags, enmArch, phLdrMod));
+ AssertMsgReturn(!(fFlags & ~RTLDR_O_VALID_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(enmArch > RTLDRARCH_INVALID && enmArch < RTLDRARCH_END, ("%d\n", enmArch), VERR_INVALID_PARAMETER);
+
+ /*
+ * Create file reader & invoke worker which identifies and calls the image interpreter.
+ */
+ PRTLDRREADER pReader;
+ int rc = rtldrFileCreate(&pReader, pszFilename);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrOpenWithReader(pReader, fFlags, enmArch, phLdrMod, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("RTLdrOpenEx: return %Rrc *phLdrMod=%p\n", rc, *phLdrMod));
+ return rc;
+ }
+ pReader->pfnDestroy(pReader);
+ }
+ *phLdrMod = NIL_RTLDRMOD;
+ LogFlow(("RTLdrOpenEx: return %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrOpenEx);
+
diff --git a/src/VBox/Runtime/common/ldr/ldrLX.cpp b/src/VBox/Runtime/common/ldr/ldrLX.cpp
new file mode 100644
index 00000000..a61e8d50
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrLX.cpp
@@ -0,0 +1,3121 @@
+/* $Id: ldrLX.cpp $ */
+/** @file
+ * kLdr - The Module Interpreter for the Linear eXecutable (LX) Format.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ * --------------------------------------------------------------------
+ *
+ * This code is based on: kLdr/kLdrModLX.c from kStuff r113.
+ *
+ * Copyright (c) 2006-2007 Knut St. Osmundsen <bird-kStuff-spamix@anduin.net>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+#include <iprt/formats/lx.h>
+#include <iprt/formats/pecoff.h>
+#include <iprt/formats/codeview.h>
+#include <iprt/formats/elf32.h>
+#include "internal/ldr.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def KLDRMODLX_STRICT
+ * Define KLDRMODLX_STRICT to enabled strict checks in KLDRMODLX. */
+#define KLDRMODLX_STRICT 1
+
+/** @def KLDRMODLX_ASSERT
+ * Assert that an expression is true when KLDR_STRICT is defined.
+ */
+#ifdef KLDRMODLX_STRICT
+# define KLDRMODLX_ASSERT(expr) Assert(expr)
+#else
+# define KLDRMODLX_ASSERT(expr) do {} while (0)
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Instance data for the LX module interpreter.
+ */
+typedef struct KLDRMODLX
+{
+ /** Core module structure. */
+ RTLDRMODINTERNAL Core;
+
+ /** Pointer to the user mapping. */
+ const void *pvMapping;
+ /** The size of the mapped LX image. */
+ size_t cbMapped;
+ /** Reserved flags. */
+ uint32_t f32Reserved;
+
+ /** The offset of the LX header. */
+ RTFOFF offHdr;
+ /** Copy of the LX header. */
+ struct e32_exe Hdr;
+
+ /** Pointer to the loader section.
+ * Allocated together with this strcture. */
+ const uint8_t *pbLoaderSection;
+ /** Pointer to the last byte in the loader section. */
+ const uint8_t *pbLoaderSectionLast;
+ /** Pointer to the object table in the loader section. */
+ const struct o32_obj *paObjs;
+ /** Pointer to the object page map table in the loader section. */
+ const struct o32_map *paPageMappings;
+ /** Pointer to the resource table in the loader section. */
+ const struct rsrc32 *paRsrcs;
+ /** Pointer to the resident name table in the loader section. */
+ const uint8_t *pbResNameTab;
+ /** Pointer to the entry table in the loader section. */
+ const uint8_t *pbEntryTab;
+
+ /** Pointer to the non-resident name table. */
+ uint8_t *pbNonResNameTab;
+ /** Pointer to the last byte in the non-resident name table. */
+ const uint8_t *pbNonResNameTabLast;
+
+ /** Pointer to the fixup section. */
+ uint8_t *pbFixupSection;
+ /** Pointer to the last byte in the fixup section. */
+ const uint8_t *pbFixupSectionLast;
+ /** Pointer to the fixup page table within pvFixupSection. */
+ const uint32_t *paoffPageFixups;
+ /** Pointer to the fixup record table within pvFixupSection. */
+ const uint8_t *pbFixupRecs;
+ /** Pointer to the import module name table within pvFixupSection. */
+ const uint8_t *pbImportMods;
+ /** Pointer to the import module name table within pvFixupSection. */
+ const uint8_t *pbImportProcs;
+
+ /** Pointer to the module name (in the resident name table). */
+ const char *pszName;
+ /** The name length. */
+ size_t cchName;
+
+ /** The target CPU. */
+ RTLDRCPU enmCpu;
+ /** Number of segments in aSegments. */
+ uint32_t cSegments;
+ /** Segment info. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ RTLDRSEG aSegments[RT_FLEXIBLE_ARRAY];
+} KLDRMODLX, *PKLDRMODLX;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int kldrModLXHasDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits);
+static DECLCALLBACK(int) rtldrLX_RelocateBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress,
+ RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser);
+static const uint8_t *kldrModLXDoNameTableLookupByOrdinal(const uint8_t *pbNameTable, ssize_t cbNameTable, uint32_t iOrdinal);
+static int kldrModLXDoNameLookup(PKLDRMODLX pModLX, const char *pchSymbol, size_t cchSymbol, uint32_t *piSymbol);
+static const uint8_t *kldrModLXDoNameTableLookupByName(const uint8_t *pbNameTable, ssize_t cbNameTable,
+ const char *pchSymbol, size_t cchSymbol);
+static int kldrModLXGetImport(PKLDRMODLX pThis, const void *pvBits, uint32_t iImport,
+ char *pszName, size_t cchName, size_t *pcbNeeded);
+static int kldrModLXDoLoadBits(PKLDRMODLX pModLX, void *pvBits);
+static int kldrModLXDoIterDataUnpacking(uint8_t *pbDst, const uint8_t *pbSrc, int cbSrc);
+static int kldrModLXDoIterData2Unpacking(uint8_t *pbDst, const uint8_t *pbSrc, int cbSrc);
+static void kLdrModLXMemCopyW(uint8_t *pbDst, const uint8_t *pbSrc, int cb);
+static int kldrModLXDoForwarderQuery(PKLDRMODLX pModLX, const struct e32_entry *pEntry,
+ PFNRTLDRIMPORT pfnGetForwarder, void *pvUser, PRTLDRADDR puValue, uint32_t *pfKind);
+#if 0
+static int kldrModLXDoProtect(PKLDRMODLX pModLX, void *pvBits, unsigned fUnprotectOrProtect);
+static int kldrModLXDoCallDLL(PKLDRMODLX pModLX, void *pvMapping, unsigned uOp, uintptr_t uHandle);
+static int32_t kldrModLXDoCall(uintptr_t uEntrypoint, uintptr_t uHandle, uint32_t uOp, void *pvReserved);
+#endif
+static int kldrModLXDoLoadFixupSection(PKLDRMODLX pModLX);
+static int kldrModLXDoReloc(uint8_t *pbPage, int off, RTLDRADDR PageAddress, const struct r32_rlc *prlc,
+ int iSelector, RTLDRADDR uValue, uint32_t fKind);
+
+
+/**
+ * Separate function for reading creating the LX module instance to
+ * simplify cleanup on failure.
+ */
+static int kldrModLXDoCreate(PRTLDRREADER pRdr, RTFOFF offNewHdr, uint32_t fFlags, PKLDRMODLX *ppModLX, PRTERRINFO pErrInfo)
+{
+ struct e32_exe Hdr;
+ PKLDRMODLX pModLX;
+ uint32_t off, offEnd;
+ uint32_t i;
+ int fCanOptimizeMapping;
+ uint32_t NextRVA;
+
+ RT_NOREF(fFlags);
+ *ppModLX = NULL;
+
+ /*
+ * Read the signature and file header.
+ */
+ int rc = pRdr->pfnRead(pRdr, &Hdr, sizeof(Hdr), offNewHdr > 0 ? offNewHdr : 0);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, rc, "Error reading LX header at %RTfoff: %Rrc", offNewHdr, rc);
+ if ( Hdr.e32_magic[0] != E32MAGIC1
+ || Hdr.e32_magic[1] != E32MAGIC2)
+ return RTErrInfoSetF(pErrInfo, VERR_INVALID_EXE_SIGNATURE, "Not LX magic: %02x %02x", Hdr.e32_magic[0], Hdr.e32_magic[1]);
+
+ /* We're not interested in anything but x86 images. */
+ if ( Hdr.e32_level != E32LEVEL
+ || Hdr.e32_border != E32LEBO
+ || Hdr.e32_worder != E32LEWO
+ || Hdr.e32_cpu < E32CPU286
+ || Hdr.e32_cpu > E32CPU486
+ || Hdr.e32_pagesize != OBJPAGELEN
+ )
+ return VERR_LDRLX_BAD_HEADER;
+
+ /* Some rough sanity checks. */
+ offEnd = pRdr->pfnSize(pRdr) >= (uint64_t)~(uint32_t)16 ? ~(uint32_t)16 : (uint32_t)pRdr->pfnSize(pRdr);
+ if ( Hdr.e32_itermap > offEnd
+ || Hdr.e32_datapage > offEnd
+ || Hdr.e32_nrestab > offEnd
+ || Hdr.e32_nrestab + Hdr.e32_cbnrestab > offEnd
+ || Hdr.e32_ldrsize > offEnd - offNewHdr - sizeof(Hdr)
+ || Hdr.e32_fixupsize > offEnd - offNewHdr - sizeof(Hdr)
+ || Hdr.e32_fixupsize + Hdr.e32_ldrsize > offEnd - offNewHdr - sizeof(Hdr))
+ return VERR_LDRLX_BAD_HEADER;
+
+ /* Verify the loader section. */
+ offEnd = Hdr.e32_objtab + Hdr.e32_ldrsize;
+ if (Hdr.e32_objtab < sizeof(Hdr) && Hdr.e32_objcnt)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION,
+ "Object table is inside the header: %#x", Hdr.e32_objtab);
+ off = Hdr.e32_objtab + sizeof(struct o32_obj) * Hdr.e32_objcnt;
+ if (off > offEnd)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION,
+ "Object table spans beyond the executable: e32_objcnt=%u", Hdr.e32_objcnt);
+ if (Hdr.e32_objcnt >= _32K)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION, "Too many segments: %#x\n", Hdr.e32_objcnt);
+ if ( Hdr.e32_objmap
+ && (Hdr.e32_objmap < off || Hdr.e32_objmap > offEnd))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION,
+ "Bad object page map table offset: %#x", Hdr.e32_objmap);
+ if ( Hdr.e32_rsrccnt
+ && ( Hdr.e32_rsrctab < off
+ || Hdr.e32_rsrctab > offEnd
+ || Hdr.e32_rsrctab + sizeof(struct rsrc32) * Hdr.e32_rsrccnt > offEnd))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION,
+ "Resource table is out of bounds: %#x entries at %#x", Hdr.e32_rsrccnt, Hdr.e32_rsrctab);
+ if ( Hdr.e32_restab
+ && (Hdr.e32_restab < off || Hdr.e32_restab > offEnd - 2))
+ return VERR_LDRLX_BAD_LOADER_SECTION;
+ if ( Hdr.e32_enttab
+ && (Hdr.e32_enttab < off || Hdr.e32_enttab >= offEnd))
+ return VERR_LDRLX_BAD_LOADER_SECTION;
+ if ( Hdr.e32_dircnt
+ && (Hdr.e32_dirtab < off || Hdr.e32_dirtab > offEnd - 2))
+ return VERR_LDRLX_BAD_LOADER_SECTION;
+
+ /* Verify the fixup section. */
+ off = offEnd;
+ offEnd = off + Hdr.e32_fixupsize;
+ if ( Hdr.e32_fpagetab
+ && (Hdr.e32_fpagetab < off || Hdr.e32_fpagetab > offEnd))
+ {
+ /*
+ * wlink mixes the fixup section and the loader section.
+ */
+ off = Hdr.e32_fpagetab;
+ offEnd = off + Hdr.e32_fixupsize;
+ Hdr.e32_ldrsize = off - Hdr.e32_objtab;
+ }
+ if ( Hdr.e32_frectab
+ && (Hdr.e32_frectab < off || Hdr.e32_frectab > offEnd))
+ return VERR_LDRLX_BAD_FIXUP_SECTION;
+ if ( Hdr.e32_impmod
+ && (Hdr.e32_impmod < off || Hdr.e32_impmod > offEnd || Hdr.e32_impmod + Hdr.e32_impmodcnt > offEnd))
+ return VERR_LDRLX_BAD_FIXUP_SECTION;
+ if ( Hdr.e32_impproc
+ && (Hdr.e32_impproc < off || Hdr.e32_impproc > offEnd))
+ return VERR_LDRLX_BAD_FIXUP_SECTION;
+
+ /*
+ * Calc the instance size, allocate and initialize it.
+ */
+ size_t cbModLXAndSegments = RT_ALIGN_Z(RT_UOFFSETOF_DYN(KLDRMODLX, aSegments[Hdr.e32_objcnt + 1]), 8);
+ cbModLXAndSegments += sizeof("segXXXXX") * (Hdr.e32_objcnt + 1);
+
+ pModLX = (PKLDRMODLX)RTMemAlloc(cbModLXAndSegments + Hdr.e32_ldrsize + 2 /*for two extra zeros*/);
+ if (!pModLX)
+ return VERR_NO_MEMORY;
+ *ppModLX = pModLX;
+
+ /* Core & CPU. */
+ pModLX->Core.u32Magic = 0; /* set by caller. */
+ pModLX->Core.eState = LDR_STATE_OPENED;
+ pModLX->Core.pOps = NULL; /* set by caller. */
+ pModLX->Core.pReader = pRdr;
+ switch (Hdr.e32_cpu)
+ {
+ case E32CPU286:
+ pModLX->enmCpu = RTLDRCPU_I80286;
+ pModLX->Core.enmArch = RTLDRARCH_X86_16;
+ break;
+ case E32CPU386:
+ pModLX->enmCpu = RTLDRCPU_I386;
+ pModLX->Core.enmArch = RTLDRARCH_X86_32;
+ break;
+ case E32CPU486:
+ pModLX->enmCpu = RTLDRCPU_I486;
+ pModLX->Core.enmArch = RTLDRARCH_X86_32;
+ break;
+ }
+ pModLX->Core.enmEndian = RTLDRENDIAN_LITTLE;
+ pModLX->Core.enmFormat = RTLDRFMT_LX;
+ switch (Hdr.e32_mflags & E32MODMASK)
+ {
+ case E32MODEXE:
+ pModLX->Core.enmType = !(Hdr.e32_mflags & E32NOINTFIX)
+ ? RTLDRTYPE_EXECUTABLE_RELOCATABLE
+ : RTLDRTYPE_EXECUTABLE_FIXED;
+ break;
+
+ case E32MODDLL:
+ case E32PROTDLL:
+ case E32MODPROTDLL:
+ pModLX->Core.enmType = !(Hdr.e32_mflags & E32SYSDLL)
+ ? RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE
+ : RTLDRTYPE_SHARED_LIBRARY_FIXED;
+ break;
+
+ case E32MODPDEV:
+ case E32MODVDEV:
+ pModLX->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE;
+ break;
+ }
+
+ /* KLDRMODLX */
+ pModLX->cSegments = Hdr.e32_objcnt;
+ pModLX->pszName = NULL; /* finalized further down */
+ pModLX->cchName = 0;
+ pModLX->pvMapping = 0;
+ pModLX->cbMapped = 0;
+ pModLX->f32Reserved = 0;
+
+ pModLX->offHdr = offNewHdr >= 0 ? offNewHdr : 0;
+ memcpy(&pModLX->Hdr, &Hdr, sizeof(Hdr));
+
+ pModLX->pbLoaderSection = (uint8_t *)pModLX + cbModLXAndSegments;
+ pModLX->pbLoaderSectionLast = pModLX->pbLoaderSection + pModLX->Hdr.e32_ldrsize - 1;
+ pModLX->paObjs = NULL;
+ pModLX->paPageMappings = NULL;
+ pModLX->paRsrcs = NULL;
+ pModLX->pbResNameTab = NULL;
+ pModLX->pbEntryTab = NULL;
+
+ pModLX->pbNonResNameTab = NULL;
+ pModLX->pbNonResNameTabLast = NULL;
+
+ pModLX->pbFixupSection = NULL;
+ pModLX->pbFixupSectionLast = NULL;
+ pModLX->paoffPageFixups = NULL;
+ pModLX->pbFixupRecs = NULL;
+ pModLX->pbImportMods = NULL;
+ pModLX->pbImportProcs = NULL;
+
+ /*
+ * Read the loader data.
+ */
+ rc = pRdr->pfnRead(pRdr, (void *)pModLX->pbLoaderSection, pModLX->Hdr.e32_ldrsize, pModLX->Hdr.e32_objtab + pModLX->offHdr);
+ if (RT_FAILURE(rc))
+ return rc;
+ ((uint8_t *)pModLX->pbLoaderSectionLast)[1] = 0;
+ ((uint8_t *)pModLX->pbLoaderSectionLast)[2] = 0;
+ if (pModLX->Hdr.e32_objcnt)
+ pModLX->paObjs = (const struct o32_obj *)pModLX->pbLoaderSection;
+ if (pModLX->Hdr.e32_objmap)
+ pModLX->paPageMappings = (const struct o32_map *)(pModLX->pbLoaderSection + pModLX->Hdr.e32_objmap - pModLX->Hdr.e32_objtab);
+ if (pModLX->Hdr.e32_rsrccnt)
+ pModLX->paRsrcs = (const struct rsrc32 *)(pModLX->pbLoaderSection + pModLX->Hdr.e32_rsrctab - pModLX->Hdr.e32_objtab);
+ if (pModLX->Hdr.e32_restab)
+ pModLX->pbResNameTab = pModLX->pbLoaderSection + pModLX->Hdr.e32_restab - pModLX->Hdr.e32_objtab;
+ if (pModLX->Hdr.e32_enttab)
+ pModLX->pbEntryTab = pModLX->pbLoaderSection + pModLX->Hdr.e32_enttab - pModLX->Hdr.e32_objtab;
+
+ /*
+ * Get the soname from the resident name table.
+ * Very convenient that it's the 0 ordinal, because then we get a
+ * free string terminator.
+ * (The table entry consists of a pascal string followed by a 16-bit ordinal.)
+ */
+ if (pModLX->pbResNameTab)
+ pModLX->pszName = (const char *)kldrModLXDoNameTableLookupByOrdinal(pModLX->pbResNameTab,
+ pModLX->pbLoaderSectionLast - pModLX->pbResNameTab + 1,
+ 0);
+ if (!pModLX->pszName)
+ return VERR_LDRLX_NO_SONAME;
+ pModLX->cchName = *(const uint8_t *)pModLX->pszName++;
+ if ( pModLX->pszName[pModLX->cchName] != '\0'
+ || pModLX->cchName != strlen(pModLX->pszName))
+ return VERR_LDRLX_BAD_SONAME;
+
+ /*
+ * Quick validation of the object table.
+ */
+ for (i = 0; i < pModLX->cSegments; i++)
+ {
+ if (pModLX->paObjs[i].o32_base & (OBJPAGELEN - 1))
+ return VERR_LDRLX_BAD_OBJECT_TABLE;
+ if (pModLX->paObjs[i].o32_base + pModLX->paObjs[i].o32_size <= pModLX->paObjs[i].o32_base)
+ return VERR_LDRLX_BAD_OBJECT_TABLE;
+ if (pModLX->paObjs[i].o32_mapsize > (pModLX->paObjs[i].o32_size + (OBJPAGELEN - 1)))
+ return VERR_LDRLX_BAD_OBJECT_TABLE;
+ if ( pModLX->paObjs[i].o32_mapsize
+ && ( (uint8_t *)&pModLX->paPageMappings[pModLX->paObjs[i].o32_pagemap] > pModLX->pbLoaderSectionLast
+ || (uint8_t *)&pModLX->paPageMappings[pModLX->paObjs[i].o32_pagemap + pModLX->paObjs[i].o32_mapsize]
+ > pModLX->pbLoaderSectionLast))
+ return VERR_LDRLX_BAD_OBJECT_TABLE;
+ if (i > 0 && !(pModLX->paObjs[i].o32_flags & OBJRSRC))
+ {
+ if (pModLX->paObjs[i].o32_base <= pModLX->paObjs[i - 1].o32_base)
+ return VERR_LDRLX_BAD_OBJECT_TABLE;
+ if (pModLX->paObjs[i].o32_base < pModLX->paObjs[i - 1].o32_base + pModLX->paObjs[i - 1].o32_mapsize)
+ return VERR_LDRLX_BAD_OBJECT_TABLE;
+ }
+ }
+
+ /*
+ * Check if we can optimize the mapping by using a different
+ * object alignment. The linker typically uses 64KB alignment,
+ * we can easily get away with page alignment in most cases.
+ *
+ * However, this screws up DwARF debug info, let's not do this
+ * when the purpose is reading debug info.
+ */
+ /** @todo Add flag for enabling this optimization. */
+ fCanOptimizeMapping = !(Hdr.e32_mflags & (E32NOINTFIX | E32SYSDLL))
+ && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION));
+ NextRVA = 0;
+
+ /*
+ * Setup the KLDRMOD segment array.
+ */
+ char *pszSegNm = (char *)&pModLX->aSegments[pModLX->cSegments];
+ for (i = 0; i < pModLX->cSegments; i++)
+ {
+ /* dummy segment name */
+ pModLX->aSegments[i].pszName = pszSegNm;
+ size_t cchName = RTStrPrintf(pszSegNm, sizeof("segXXXXX"), "seg%u", i);
+ pszSegNm += cchName + 1;
+ pModLX->aSegments[i].cchName = (uint32_t)cchName;
+
+ /* unused */
+ pModLX->aSegments[i].offFile = -1;
+ pModLX->aSegments[i].cbFile = -1;
+ pModLX->aSegments[i].SelFlat = 0;
+ pModLX->aSegments[i].Sel16bit = 0;
+
+ /* flags */
+ pModLX->aSegments[i].fFlags = 0;
+ if (pModLX->paObjs[i].o32_flags & OBJBIGDEF)
+ pModLX->aSegments[i].fFlags = RTLDRSEG_FLAG_16BIT;
+ if (pModLX->paObjs[i].o32_flags & OBJALIAS16)
+ pModLX->aSegments[i].fFlags = RTLDRSEG_FLAG_OS2_ALIAS16;
+ if (pModLX->paObjs[i].o32_flags & OBJCONFORM)
+ pModLX->aSegments[i].fFlags = RTLDRSEG_FLAG_OS2_CONFORM;
+ if (pModLX->paObjs[i].o32_flags & OBJIOPL)
+ pModLX->aSegments[i].fFlags = RTLDRSEG_FLAG_OS2_IOPL;
+
+ /* size and addresses */
+ pModLX->aSegments[i].Alignment = OBJPAGELEN;
+ pModLX->aSegments[i].cb = pModLX->paObjs[i].o32_size;
+ pModLX->aSegments[i].LinkAddress = pModLX->paObjs[i].o32_base;
+ pModLX->aSegments[i].RVA = NextRVA;
+ if ( fCanOptimizeMapping
+ || i + 1 >= pModLX->cSegments
+ || (pModLX->paObjs[i].o32_flags & OBJRSRC)
+ || (pModLX->paObjs[i + 1].o32_flags & OBJRSRC))
+ pModLX->aSegments[i].cbMapped = RT_ALIGN_Z(pModLX->paObjs[i].o32_size, OBJPAGELEN);
+ else
+ pModLX->aSegments[i].cbMapped = pModLX->paObjs[i + 1].o32_base - pModLX->paObjs[i].o32_base;
+ /** @todo Above probably doesn't work for os2krnl and other images
+ * non-sequential virtual address assignments. */
+ NextRVA += (uint32_t)pModLX->aSegments[i].cbMapped;
+
+ /* protection */
+ switch ( pModLX->paObjs[i].o32_flags
+ & (OBJSHARED | OBJREAD | OBJWRITE | OBJEXEC))
+ {
+ case 0:
+ case OBJSHARED:
+ pModLX->aSegments[i].fProt = 0;
+ break;
+ case OBJREAD:
+ case OBJREAD | OBJSHARED:
+ pModLX->aSegments[i].fProt = RTMEM_PROT_READ;
+ break;
+ case OBJWRITE:
+ case OBJWRITE | OBJREAD:
+ pModLX->aSegments[i].fProt = RTMEM_PROT_READ | RTMEM_PROT_WRITECOPY;
+ break;
+ case OBJWRITE | OBJSHARED:
+ case OBJWRITE | OBJSHARED | OBJREAD:
+ pModLX->aSegments[i].fProt = RTMEM_PROT_READ | RTMEM_PROT_WRITE;
+ break;
+ case OBJEXEC:
+ case OBJEXEC | OBJSHARED:
+ pModLX->aSegments[i].fProt = RTMEM_PROT_EXEC;
+ break;
+ case OBJEXEC | OBJREAD:
+ case OBJEXEC | OBJREAD | OBJSHARED:
+ pModLX->aSegments[i].fProt = RTMEM_PROT_EXEC | RTMEM_PROT_READ;
+ break;
+ case OBJEXEC | OBJWRITE:
+ case OBJEXEC | OBJWRITE | OBJREAD:
+ pModLX->aSegments[i].fProt = RTMEM_PROT_EXEC | RTMEM_PROT_READ | RTMEM_PROT_WRITECOPY;
+ break;
+ case OBJEXEC | OBJWRITE | OBJSHARED:
+ case OBJEXEC | OBJWRITE | OBJSHARED | OBJREAD:
+ pModLX->aSegments[i].fProt = RTMEM_PROT_EXEC | RTMEM_PROT_READ | RTMEM_PROT_WRITE;
+ break;
+ }
+ if ((pModLX->paObjs[i].o32_flags & (OBJREAD | OBJWRITE | OBJEXEC | OBJRSRC)) == OBJRSRC)
+ pModLX->aSegments[i].fProt = RTMEM_PROT_READ;
+ /*pModLX->aSegments[i].f16bit = !(pModLX->paObjs[i].o32_flags & OBJBIGDEF)
+ pModLX->aSegments[i].fIOPL = !(pModLX->paObjs[i].o32_flags & OBJIOPL)
+ pModLX->aSegments[i].fConforming = !(pModLX->paObjs[i].o32_flags & OBJCONFORM) */
+ }
+
+ /* set the mapping size */
+ pModLX->cbMapped = NextRVA;
+
+ /*
+ * We're done.
+ */
+ *ppModLX = pModLX;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtldrLX_Close(PRTLDRMODINTERNAL pMod)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ KLDRMODLX_ASSERT(!pModLX->pvMapping);
+
+ if (pModLX->pbNonResNameTab)
+ {
+ RTMemFree(pModLX->pbNonResNameTab);
+ pModLX->pbNonResNameTab = NULL;
+ }
+ if (pModLX->pbFixupSection)
+ {
+ RTMemFree(pModLX->pbFixupSection);
+ pModLX->pbFixupSection = NULL;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Resolved base address aliases.
+ *
+ * @param pModLX The interpreter module instance
+ * @param pBaseAddress The base address, IN & OUT.
+ */
+static void kldrModLXResolveBaseAddress(PKLDRMODLX pModLX, PRTLDRADDR pBaseAddress)
+{
+ if (*pBaseAddress == RTLDR_BASEADDRESS_LINK)
+ *pBaseAddress = pModLX->aSegments[0].LinkAddress;
+}
+
+
+static int kldrModLXQuerySymbol(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, uint32_t iSymbol,
+ const char *pchSymbol, size_t cchSymbol, const char *pszVersion,
+ PFNRTLDRIMPORT pfnGetForwarder, void *pvUser, PRTLDRADDR puValue, uint32_t *pfKind)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ uint32_t iOrdinal;
+ int rc;
+ const struct b32_bundle *pBundle;
+ RT_NOREF(pvBits);
+ RT_NOREF(pszVersion);
+
+ /*
+ * Give up at once if there is no entry table.
+ */
+ if (!pModLX->Hdr.e32_enttab)
+ return VERR_SYMBOL_NOT_FOUND;
+
+ /*
+ * Translate the symbol name into an ordinal.
+ */
+ if (pchSymbol)
+ {
+ rc = kldrModLXDoNameLookup(pModLX, pchSymbol, cchSymbol, &iSymbol);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Iterate the entry table.
+ * (The entry table is made up of bundles of similar exports.)
+ */
+ iOrdinal = 1;
+ pBundle = (const struct b32_bundle *)pModLX->pbEntryTab;
+ while (pBundle->b32_cnt && iOrdinal <= iSymbol)
+ {
+ static const size_t s_cbEntry[] = { 0, 3, 5, 5, 7 };
+
+ /*
+ * Check for a hit first.
+ */
+ iOrdinal += pBundle->b32_cnt;
+ if (iSymbol < iOrdinal)
+ {
+ uint32_t offObject;
+ const struct e32_entry *pEntry = (const struct e32_entry *)((uintptr_t)(pBundle + 1)
+ + (iSymbol - (iOrdinal - pBundle->b32_cnt))
+ * s_cbEntry[pBundle->b32_type]);
+
+ /*
+ * Calculate the return address.
+ */
+ kldrModLXResolveBaseAddress(pModLX, &BaseAddress);
+ switch (pBundle->b32_type)
+ {
+ /* empty bundles are place holders unused ordinal ranges. */
+ case EMPTY:
+ return VERR_SYMBOL_NOT_FOUND;
+
+ /* e32_flags + a 16-bit offset. */
+ case ENTRY16:
+ offObject = pEntry->e32_variant.e32_offset.offset16;
+ if (pfKind)
+ *pfKind = RTLDRSYMKIND_16BIT | RTLDRSYMKIND_NO_TYPE;
+ break;
+
+ /* e32_flags + a 16-bit offset + a 16-bit callgate selector. */
+ case GATE16:
+ offObject = pEntry->e32_variant.e32_callgate.offset;
+ if (pfKind)
+ *pfKind = RTLDRSYMKIND_16BIT | RTLDRSYMKIND_CODE;
+ break;
+
+ /* e32_flags + a 32-bit offset. */
+ case ENTRY32:
+ offObject = pEntry->e32_variant.e32_offset.offset32;
+ if (pfKind)
+ *pfKind = RTLDRSYMKIND_32BIT;
+ break;
+
+ /* e32_flags + 16-bit import module ordinal + a 32-bit procname or ordinal. */
+ case ENTRYFWD:
+ return kldrModLXDoForwarderQuery(pModLX, pEntry, pfnGetForwarder, pvUser, puValue, pfKind);
+
+ default:
+ /* anyone actually using TYPEINFO will end up here. */
+ KLDRMODLX_ASSERT(!"Bad bundle type");
+ return VERR_LDRLX_BAD_BUNDLE;
+ }
+
+ /*
+ * Validate the object number and calc the return address.
+ */
+ if ( pBundle->b32_obj <= 0
+ || pBundle->b32_obj > pModLX->cSegments)
+ return VERR_LDRLX_BAD_BUNDLE;
+ if (puValue)
+ *puValue = BaseAddress
+ + offObject
+ + pModLX->aSegments[pBundle->b32_obj - 1].RVA;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Skip the bundle.
+ */
+ if (pBundle->b32_type > ENTRYFWD)
+ {
+ KLDRMODLX_ASSERT(!"Bad type"); /** @todo figure out TYPEINFO. */
+ return VERR_LDRLX_BAD_BUNDLE;
+ }
+ if (pBundle->b32_type == 0)
+ pBundle = (const struct b32_bundle *)((const uint8_t *)pBundle + 2);
+ else
+ pBundle = (const struct b32_bundle *)((const uint8_t *)(pBundle + 1) + s_cbEntry[pBundle->b32_type] * pBundle->b32_cnt);
+ }
+
+ return VERR_SYMBOL_NOT_FOUND;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnGetSymbolEx}
+ */
+static DECLCALLBACK(int) rtldrLX_GetSymbolEx(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress,
+ uint32_t iOrdinal, const char *pszSymbol, RTUINTPTR *pValue)
+{
+ uint32_t fKind = RTLDRSYMKIND_REQ_FLAT;
+ return kldrModLXQuerySymbol(pMod, pvBits, BaseAddress, iOrdinal, pszSymbol, pszSymbol ? strlen(pszSymbol) : 0,
+ NULL, NULL, NULL, pValue, &fKind);
+}
+
+
+/**
+ * Do name lookup.
+ *
+ * @returns IPRT status code.
+ * @param pModLX The module to lookup the symbol in.
+ * @param pchSymbol The symbol to lookup.
+ * @param cchSymbol The symbol name length.
+ * @param piSymbol Where to store the symbol ordinal.
+ */
+static int kldrModLXDoNameLookup(PKLDRMODLX pModLX, const char *pchSymbol, size_t cchSymbol, uint32_t *piSymbol)
+{
+
+ /*
+ * First do a hash table lookup.
+ */
+ /** @todo hash name table for speed. */
+
+ /*
+ * Search the name tables.
+ */
+ const uint8_t *pbName = kldrModLXDoNameTableLookupByName(pModLX->pbResNameTab,
+ pModLX->pbLoaderSectionLast - pModLX->pbResNameTab + 1,
+ pchSymbol, cchSymbol);
+ if (!pbName)
+ {
+ if (!pModLX->pbNonResNameTab)
+ {
+ /* lazy load it */
+ /** @todo non-resident name table. */
+ }
+ if (pModLX->pbNonResNameTab)
+ pbName = kldrModLXDoNameTableLookupByName(pModLX->pbResNameTab,
+ pModLX->pbNonResNameTabLast - pModLX->pbResNameTab + 1,
+ pchSymbol, cchSymbol);
+ }
+ if (!pbName)
+ return VERR_SYMBOL_NOT_FOUND;
+
+ *piSymbol = *(const uint16_t *)(pbName + 1 + *pbName);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Lookup a name table entry by name.
+ *
+ * @returns Pointer to the name table entry if found.
+ * @returns NULL if not found.
+ * @param pbNameTable Pointer to the name table that should be searched.
+ * @param cbNameTable The size of the name table.
+ * @param pchSymbol The name of the symbol we're looking for.
+ * @param cchSymbol The length of the symbol name.
+ */
+static const uint8_t *kldrModLXDoNameTableLookupByName(const uint8_t *pbNameTable, ssize_t cbNameTable,
+ const char *pchSymbol, size_t cchSymbol)
+{
+ /*
+ * Determin the namelength up front so we can skip anything which doesn't matches the length.
+ */
+ uint8_t cbSymbol8Bit = (uint8_t)cchSymbol;
+ if (cbSymbol8Bit != cchSymbol)
+ return NULL; /* too long. */
+
+ /*
+ * Walk the name table.
+ */
+ while (*pbNameTable != 0 && cbNameTable > 0)
+ {
+ const uint8_t cbName = *pbNameTable;
+
+ cbNameTable -= cbName + 1 + 2;
+ if (cbNameTable < 0)
+ break;
+
+ if ( cbName == cbSymbol8Bit
+ && !memcmp(pbNameTable + 1, pchSymbol, cbName))
+ return pbNameTable;
+
+ /* next entry */
+ pbNameTable += cbName + 1 + 2;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Deal with a forwarder entry.
+ *
+ * @returns IPRT status code.
+ * @param pModLX The PE module interpreter instance.
+ * @param pEntry The forwarder entry.
+ * @param pfnGetForwarder The callback for resolving forwarder symbols. (optional)
+ * @param pvUser The user argument for the callback.
+ * @param puValue Where to put the value. (optional)
+ * @param pfKind Where to put the symbol kind. (optional)
+ */
+static int kldrModLXDoForwarderQuery(PKLDRMODLX pModLX, const struct e32_entry *pEntry,
+ PFNRTLDRIMPORT pfnGetForwarder, void *pvUser, PRTLDRADDR puValue, uint32_t *pfKind)
+{
+ if (!pfnGetForwarder)
+ return VERR_LDR_FORWARDER;
+
+ /*
+ * Validate the entry import module ordinal.
+ */
+ if ( !pEntry->e32_variant.e32_fwd.modord
+ || pEntry->e32_variant.e32_fwd.modord > pModLX->Hdr.e32_impmodcnt)
+ return VERR_LDRLX_BAD_FORWARDER;
+
+ char szImpModule[256];
+ int rc = kldrModLXGetImport(pModLX, NULL, pEntry->e32_variant.e32_fwd.modord - 1, szImpModule, sizeof(szImpModule), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Figure out the parameters.
+ */
+ uint32_t iSymbol;
+ const char *pszSymbol;
+ char szSymbol[256];
+ if (pEntry->e32_flags & FWD_ORDINAL)
+ {
+ iSymbol = pEntry->e32_variant.e32_fwd.value;
+ pszSymbol = NULL; /* no symbol name. */
+ }
+ else
+ {
+ const uint8_t *pbName;
+
+ /* load the fixup section if necessary. */
+ if (!pModLX->pbImportProcs)
+ {
+ rc = kldrModLXDoLoadFixupSection(pModLX);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /* Make name pointer. */
+ pbName = pModLX->pbImportProcs + pEntry->e32_variant.e32_fwd.value;
+ if ( pbName >= pModLX->pbFixupSectionLast
+ || pbName < pModLX->pbFixupSection
+ || !*pbName)
+ return VERR_LDRLX_BAD_FORWARDER;
+
+
+ /* check for '#' name. */
+ if (pbName[1] == '#')
+ {
+ uint8_t cbLeft = *pbName;
+ const uint8_t *pb = pbName + 1;
+ unsigned uBase;
+
+ /* base detection */
+ uBase = 10;
+ if ( cbLeft > 1
+ && pb[1] == '0'
+ && (pb[2] == 'x' || pb[2] == 'X'))
+ {
+ uBase = 16;
+ pb += 2;
+ cbLeft -= 2;
+ }
+
+ /* ascii to integer */
+ iSymbol = 0;
+ while (cbLeft-- > 0)
+ {
+ /* convert char to digit. */
+ unsigned uDigit = *pb++;
+ if (uDigit >= '0' && uDigit <= '9')
+ uDigit -= '0';
+ else if (uDigit >= 'a' && uDigit <= 'z')
+ uDigit -= 'a' + 10;
+ else if (uDigit >= 'A' && uDigit <= 'Z')
+ uDigit -= 'A' + 10;
+ else if (!uDigit)
+ break;
+ else
+ return VERR_LDRLX_BAD_FORWARDER;
+ if (uDigit >= uBase)
+ return VERR_LDRLX_BAD_FORWARDER;
+
+ /* insert the digit */
+ iSymbol *= uBase;
+ iSymbol += uDigit;
+ }
+ if (!iSymbol)
+ return VERR_LDRLX_BAD_FORWARDER;
+
+ pszSymbol = NULL; /* no symbol name. */
+ }
+ else
+ {
+ memcpy(szSymbol, pbName + 1, *pbName);
+ szSymbol[*pbName] = '\0';
+ pszSymbol = szSymbol;
+ iSymbol = UINT32_MAX;
+ }
+ }
+
+ /*
+ * Resolve the forwarder.
+ */
+ rc = pfnGetForwarder(&pModLX->Core, szImpModule, pszSymbol, iSymbol, puValue, /*pfKind, */pvUser);
+ if (RT_SUCCESS(rc) && pfKind)
+ *pfKind |= RTLDRSYMKIND_FORWARDER;
+ return rc;
+}
+
+
+/**
+ * Loads the fixup section from the executable image.
+ *
+ * The fixup section isn't loaded until it's accessed. It's also freed by kLdrModDone().
+ *
+ * @returns IPRT status code.
+ * @param pModLX The PE module interpreter instance.
+ */
+static int kldrModLXDoLoadFixupSection(PKLDRMODLX pModLX)
+{
+ void *pv = RTMemAlloc(pModLX->Hdr.e32_fixupsize);
+ if (!pv)
+ return VERR_NO_MEMORY;
+
+ uint32_t off = pModLX->Hdr.e32_objtab + pModLX->Hdr.e32_ldrsize;
+ int rc = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, pv, pModLX->Hdr.e32_fixupsize,
+ off + pModLX->offHdr);
+ if (RT_SUCCESS(rc))
+ {
+ pModLX->pbFixupSection = (uint8_t *)pv;
+ pModLX->pbFixupSectionLast = pModLX->pbFixupSection + pModLX->Hdr.e32_fixupsize;
+ KLDRMODLX_ASSERT(!pModLX->paoffPageFixups);
+ if (pModLX->Hdr.e32_fpagetab)
+ pModLX->paoffPageFixups = (const uint32_t *)(pModLX->pbFixupSection + pModLX->Hdr.e32_fpagetab - off);
+ KLDRMODLX_ASSERT(!pModLX->pbFixupRecs);
+ if (pModLX->Hdr.e32_frectab)
+ pModLX->pbFixupRecs = pModLX->pbFixupSection + pModLX->Hdr.e32_frectab - off;
+ KLDRMODLX_ASSERT(!pModLX->pbImportMods);
+ if (pModLX->Hdr.e32_impmod)
+ pModLX->pbImportMods = pModLX->pbFixupSection + pModLX->Hdr.e32_impmod - off;
+ KLDRMODLX_ASSERT(!pModLX->pbImportProcs);
+ if (pModLX->Hdr.e32_impproc)
+ pModLX->pbImportProcs = pModLX->pbFixupSection + pModLX->Hdr.e32_impproc - off;
+ }
+ else
+ RTMemFree(pv);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnEnumSymbols}
+ */
+static DECLCALLBACK(int) rtldrLX_EnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits,
+ RTUINTPTR BaseAddress, PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ RT_NOREF(pvBits);
+ RT_NOREF(fFlags);
+
+ kldrModLXResolveBaseAddress(pModLX, &BaseAddress);
+
+ /*
+ * Enumerate the entry table.
+ * (The entry table is made up of bundles of similar exports.)
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t iOrdinal = 1;
+ const struct b32_bundle *pBundle = (const struct b32_bundle *)pModLX->pbEntryTab;
+ while (pBundle->b32_cnt && iOrdinal)
+ {
+ static const size_t s_cbEntry[] = { 0, 3, 5, 5, 7 };
+
+ /*
+ * Enum the entries in the bundle.
+ */
+ if (pBundle->b32_type != EMPTY)
+ {
+ const struct e32_entry *pEntry;
+ size_t cbEntry;
+ RTLDRADDR BundleRVA;
+ unsigned cLeft;
+
+
+ /* Validate the bundle. */
+ switch (pBundle->b32_type)
+ {
+ case ENTRY16:
+ case GATE16:
+ case ENTRY32:
+ if ( pBundle->b32_obj <= 0
+ || pBundle->b32_obj > pModLX->cSegments)
+ return VERR_LDRLX_BAD_BUNDLE;
+ BundleRVA = pModLX->aSegments[pBundle->b32_obj - 1].RVA;
+ break;
+
+ case ENTRYFWD:
+ BundleRVA = 0;
+ break;
+
+ default:
+ /* anyone actually using TYPEINFO will end up here. */
+ KLDRMODLX_ASSERT(!"Bad bundle type");
+ return VERR_LDRLX_BAD_BUNDLE;
+ }
+
+ /* iterate the bundle entries. */
+ cbEntry = s_cbEntry[pBundle->b32_type];
+ pEntry = (const struct e32_entry *)(pBundle + 1);
+ cLeft = pBundle->b32_cnt;
+ while (cLeft-- > 0)
+ {
+ RTLDRADDR uValue;
+ uint32_t fKind;
+ int fFoundName;
+ const uint8_t *pbName;
+
+ /*
+ * Calc the symbol value and kind.
+ */
+ switch (pBundle->b32_type)
+ {
+ /* e32_flags + a 16-bit offset. */
+ case ENTRY16:
+ uValue = BaseAddress + BundleRVA + pEntry->e32_variant.e32_offset.offset16;
+ fKind = RTLDRSYMKIND_16BIT | RTLDRSYMKIND_NO_TYPE;
+ break;
+
+ /* e32_flags + a 16-bit offset + a 16-bit callgate selector. */
+ case GATE16:
+ uValue = BaseAddress + BundleRVA + pEntry->e32_variant.e32_callgate.offset;
+ fKind = RTLDRSYMKIND_16BIT | RTLDRSYMKIND_CODE;
+ break;
+
+ /* e32_flags + a 32-bit offset. */
+ case ENTRY32:
+ uValue = BaseAddress + BundleRVA + pEntry->e32_variant.e32_offset.offset32;
+ fKind = RTLDRSYMKIND_32BIT;
+ break;
+
+ /* e32_flags + 16-bit import module ordinal + a 32-bit procname or ordinal. */
+ case ENTRYFWD:
+ uValue = 0; /** @todo implement enumeration of forwarders properly. */
+ fKind = RTLDRSYMKIND_FORWARDER;
+ break;
+
+ default: /* shut up gcc. */
+ uValue = 0;
+ fKind = RTLDRSYMKIND_NO_BIT | RTLDRSYMKIND_NO_TYPE;
+ break;
+ }
+
+ /*
+ * Any symbol names?
+ */
+ fFoundName = 0;
+ char szName[256];
+
+ /* resident name table. */
+ pbName = pModLX->pbResNameTab;
+ if (pbName)
+ {
+ do
+ {
+ pbName = kldrModLXDoNameTableLookupByOrdinal(pbName, pModLX->pbLoaderSectionLast - pbName + 1, iOrdinal);
+ if (!pbName)
+ break;
+ fFoundName = 1;
+ memcpy(szName, (const char *)pbName + 1, *pbName);
+ szName[*pbName] = '\0';
+ rc = pfnCallback(pMod, szName, iOrdinal, uValue, /*fKind,*/ pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ /* skip to the next entry */
+ pbName += 1 + *pbName + 2;
+ } while (pbName < pModLX->pbLoaderSectionLast);
+ }
+
+ /* resident name table. */
+ pbName = pModLX->pbNonResNameTab;
+ /** @todo lazy load the non-resident name table. */
+ if (pbName)
+ {
+ do
+ {
+ pbName = kldrModLXDoNameTableLookupByOrdinal(pbName, pModLX->pbNonResNameTabLast - pbName + 1, iOrdinal);
+ if (!pbName)
+ break;
+ fFoundName = 1;
+ memcpy(szName, (const char *)pbName + 1, *pbName);
+ szName[*pbName] = '\0';
+ rc = pfnCallback(pMod, szName, iOrdinal, uValue, /*fKind,*/ pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ /* skip to the next entry */
+ pbName += 1 + *pbName + 2;
+ } while (pbName < pModLX->pbLoaderSectionLast);
+ }
+
+ /*
+ * If no names, call once with the ordinal only.
+ */
+ if (!fFoundName)
+ {
+ RT_NOREF(fKind);
+ rc = pfnCallback(pMod, NULL /*pszName*/, iOrdinal, uValue, /*fKind,*/ pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+
+ /* next */
+ iOrdinal++;
+ pEntry = (const struct e32_entry *)((uintptr_t)pEntry + cbEntry);
+ }
+ }
+
+ /*
+ * The next bundle.
+ */
+ if (pBundle->b32_type > ENTRYFWD)
+ {
+ KLDRMODLX_ASSERT(!"Bad type"); /** @todo figure out TYPEINFO. */
+ return VERR_LDRLX_BAD_BUNDLE;
+ }
+ if (pBundle->b32_type == 0)
+ pBundle = (const struct b32_bundle *)((const uint8_t *)pBundle + 2);
+ else
+ pBundle = (const struct b32_bundle *)((const uint8_t *)(pBundle + 1) + s_cbEntry[pBundle->b32_type] * pBundle->b32_cnt);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Lookup a name table entry by ordinal.
+ *
+ * @returns Pointer to the name table entry if found.
+ * @returns NULL if not found.
+ * @param pbNameTable Pointer to the name table that should be searched.
+ * @param cbNameTable The size of the name table.
+ * @param iOrdinal The ordinal to search for.
+ */
+static const uint8_t *kldrModLXDoNameTableLookupByOrdinal(const uint8_t *pbNameTable, ssize_t cbNameTable, uint32_t iOrdinal)
+{
+ while (*pbNameTable != 0 && cbNameTable > 0)
+ {
+ const uint8_t cbName = *pbNameTable;
+ uint32_t iName;
+
+ cbNameTable -= cbName + 1 + 2;
+ if (cbNameTable < 0)
+ break;
+
+ iName = *(pbNameTable + cbName + 1)
+ | ((unsigned)*(pbNameTable + cbName + 2) << 8);
+ if (iName == iOrdinal)
+ return pbNameTable;
+
+ /* next entry */
+ pbNameTable += cbName + 1 + 2;
+ }
+
+ return NULL;
+}
+
+
+static int kldrModLXGetImport(PKLDRMODLX pModLX, const void *pvBits, uint32_t iImport, char *pszName, size_t cchName,
+ size_t *pcbNeeded)
+{
+ const uint8_t *pb;
+ int rc;
+ RT_NOREF(pvBits);
+
+ /*
+ * Validate
+ */
+ if (iImport >= pModLX->Hdr.e32_impmodcnt)
+ return VERR_LDRLX_IMPORT_ORDINAL_OUT_OF_BOUNDS;
+
+ /*
+ * Lazy loading the fixup section.
+ */
+ if (!pModLX->pbImportMods)
+ {
+ rc = kldrModLXDoLoadFixupSection(pModLX);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Iterate the module import table until we reach the requested import ordinal.
+ */
+ pb = pModLX->pbImportMods;
+ while (iImport-- > 0)
+ pb += *pb + 1;
+
+ /*
+ * Copy out the result.
+ */
+ if (pcbNeeded)
+ *pcbNeeded = *pb + 1;
+ if (*pb < cchName)
+ {
+ memcpy(pszName, pb + 1, *pb);
+ pszName[*pb] = '\0';
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ memcpy(pszName, pb + 1, cchName);
+ if (cchName)
+ pszName[cchName - 1] = '\0';
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+
+ return rc;
+}
+
+#if 0
+
+/** @copydoc kLdrModNumberOfImports */
+static int32_t kldrModLXNumberOfImports(PRTLDRMODINTERNAL pMod, const void *pvBits)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ RT_NOREF(pvBits);
+ return pModLX->Hdr.e32_impmodcnt;
+}
+
+
+/** @copydoc kLdrModGetStackInfo */
+static int kldrModLXGetStackInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, PKLDRSTACKINFO pStackInfo)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ const uint32_t i = pModLX->Hdr.e32_stackobj;
+ RT_NOREF(pvBits);
+
+ if ( i
+ && i <= pModLX->cSegments
+ && pModLX->Hdr.e32_esp <= pModLX->aSegments[i - 1].LinkAddress + pModLX->aSegments[i - 1].cb
+ && pModLX->Hdr.e32_stacksize
+ && pModLX->Hdr.e32_esp - pModLX->Hdr.e32_stacksize >= pModLX->aSegments[i - 1].LinkAddress)
+ {
+
+ kldrModLXResolveBaseAddress(pModLX, &BaseAddress);
+ pStackInfo->LinkAddress = pModLX->Hdr.e32_esp - pModLX->Hdr.e32_stacksize;
+ pStackInfo->Address = BaseAddress
+ + pModLX->aSegments[i - 1].RVA
+ + pModLX->Hdr.e32_esp - pModLX->Hdr.e32_stacksize - pModLX->aSegments[i - 1].LinkAddress;
+ }
+ else
+ {
+ pSt0ackInfo->Address = NIL_RTLDRADDR;
+ pStackInfo->LinkAddress = NIL_RTLDRADDR;
+ }
+ pStackInfo->cbStack = pModLX->Hdr.e32_stacksize;
+ pStackInfo->cbStackThread = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModQueryMainEntrypoint */
+static int kldrModLXQueryMainEntrypoint(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, PRTLDRADDR pMainEPAddress)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ RT_NOREF(pvBits);
+
+ /*
+ * Convert the address from the header.
+ */
+ kldrModLXResolveBaseAddress(pModLX, &BaseAddress);
+ *pMainEPAddress = pModLX->Hdr.e32_startobj
+ && pModLX->Hdr.e32_startobj <= pModLX->cSegments
+ && pModLX->Hdr.e32_eip < pModLX->aSegments[pModLX->Hdr.e32_startobj - 1].cb
+ ? BaseAddress + pModLX->aSegments[pModLX->Hdr.e32_startobj - 1].RVA + pModLX->Hdr.e32_eip
+ : NIL_RTLDRADDR;
+ return VINF_SUCCESS;
+}
+
+#endif
+
+/** Helper for rtldrLX_EnumDbgInfo. */
+static int rtldrLx_EnumDbgInfoHelper(PKLDRMODLX pModLX, PFNRTLDRENUMDBG pfnCallback, void *pvUser,
+ uint8_t *pbBuf, uint32_t cbRead, uint32_t offDbgInfo, bool *pfReturn)
+{
+ RTLDRDBGINFO DbgInfo;
+ uint32_t iDbgInfo = 0;
+ uint32_t cbDbgInfo = pModLX->Hdr.e32_debuglen;
+
+ /*
+ * Recent watcom linkers emit PE style IMAGE_DEBUG_MISC for specifying
+ * external file with CV info.
+ */
+ if (cbRead >= sizeof(IMAGE_DEBUG_MISC))
+ {
+ PCIMAGE_DEBUG_MISC pMisc = (PCIMAGE_DEBUG_MISC)pbBuf;
+ if ( pMisc->DataType == IMAGE_DEBUG_MISC_EXENAME
+ && pMisc->Length <= cbRead
+ && pMisc->Length >= RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data[4])
+ && pMisc->Unicode == 0
+ && pMisc->Reserved[0] == 0
+ && pMisc->Reserved[1] == 0
+ && pMisc->Reserved[2] == 0
+ && pMisc->Data[0] >= 0x20
+ && pMisc->Data[0] < 0x7f
+ && pMisc->Data[1] >= 0x20
+ && pMisc->Data[1] < 0x7f
+ && pMisc->Data[2] >= 0x20
+ && pMisc->Data[2] < 0x7f )
+ {
+ uint32_t cchMaxName = pMisc->Length - RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data[0]);
+ for (uint32_t cchName = 3; cchName < cchMaxName; cchName++)
+ {
+ char const ch = pMisc->Data[cchName];
+ if (ch == 0)
+ {
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW;
+ DbgInfo.iDbgInfo = iDbgInfo;
+ DbgInfo.offFile = offDbgInfo;
+ DbgInfo.LinkAddress = NIL_RTLDRADDR;
+ DbgInfo.cb = pMisc->Length;
+ DbgInfo.pszExtFile = (char *)&pMisc->Data[0];
+ DbgInfo.u.Cv.cbImage = pModLX->Hdr.e32_mpages * pModLX->Hdr.e32_pagesize;
+ DbgInfo.u.Cv.uTimestamp = 0;
+ DbgInfo.u.Cv.uMajorVer = 0;
+ DbgInfo.u.Cv.uMinorVer = 0;
+
+ *pfReturn = true;
+ int rc = pfnCallback(&pModLX->Core, &DbgInfo, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ else if (ch >= 0x30 && ch < 0x7f)
+ continue;
+ break;
+ }
+
+ /* Skip it. */
+ pbBuf += pMisc->Length;
+ cbRead -= pMisc->Length;
+ offDbgInfo += pMisc->Length;
+ cbDbgInfo -= pMisc->Length;
+ iDbgInfo++;
+ }
+ }
+
+ /*
+ * Look for codeview signature.
+ */
+ RTCVHDR const *pCvHdr = (RTCVHDR const *)pbBuf;
+ if ( cbRead > sizeof(*pCvHdr)
+ && pCvHdr->off >= sizeof(*pCvHdr)
+ && pCvHdr->off < cbDbgInfo)
+ {
+ switch (pCvHdr->u32Magic)
+ {
+ case RTCVHDR_MAGIC_NB11:
+ case RTCVHDR_MAGIC_NB09:
+ case RTCVHDR_MAGIC_NB08:
+ case RTCVHDR_MAGIC_NB07:
+ case RTCVHDR_MAGIC_NB06:
+ case RTCVHDR_MAGIC_NB05:
+ case RTCVHDR_MAGIC_NB04:
+ case RTCVHDR_MAGIC_NB02:
+ case RTCVHDR_MAGIC_NB01:
+ case RTCVHDR_MAGIC_NB00:
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW;
+ DbgInfo.iDbgInfo = iDbgInfo;
+ DbgInfo.offFile = offDbgInfo;
+ DbgInfo.LinkAddress = NIL_RTLDRADDR;
+ DbgInfo.cb = cbDbgInfo;
+ DbgInfo.pszExtFile = NULL;
+ DbgInfo.u.Cv.cbImage = pModLX->Hdr.e32_mpages * pModLX->Hdr.e32_pagesize;
+ DbgInfo.u.Cv.uTimestamp = 0;
+ DbgInfo.u.Cv.uMajorVer = 0;
+ DbgInfo.u.Cv.uMinorVer = 0;
+
+ *pfReturn = true;
+ return pfnCallback(&pModLX->Core, &DbgInfo, pvUser);
+ }
+ }
+
+ /*
+ * Watcom wraps its DWARF output in an ELF image, so look for and ELF magic.
+ */
+ Elf32_Ehdr const *pElfHdr = (Elf32_Ehdr const *)pbBuf;
+ if ( cbRead >= sizeof(*pElfHdr)
+ && pElfHdr->e_ident[EI_MAG0] == ELFMAG0
+ && pElfHdr->e_ident[EI_MAG1] == ELFMAG1
+ && pElfHdr->e_ident[EI_MAG2] == ELFMAG2
+ && pElfHdr->e_ident[EI_MAG3] == ELFMAG3
+ && pElfHdr->e_ident[EI_CLASS] == ELFCLASS32
+ && pElfHdr->e_ident[EI_DATA] == ELFDATA2LSB
+ && pElfHdr->e_ident[EI_VERSION] == EV_CURRENT
+ && pElfHdr->e_shentsize == sizeof(Elf32_Shdr)
+ && pElfHdr->e_shnum >= 2
+ && pElfHdr->e_shnum < _32K + 10
+ && pElfHdr->e_shstrndx <= pElfHdr->e_shnum
+ && pElfHdr->e_shstrndx > 0
+ )
+ {
+ /** @todo try use pBuf for reading into and try to read more at once. */
+ uint32_t const offShdrs = pElfHdr->e_shoff + offDbgInfo;
+ uint32_t const cShdrs = pElfHdr->e_shnum;
+ uint32_t const cbShdr = pElfHdr->e_shentsize;
+ int rc = VINF_SUCCESS;
+
+ /* Read the section string table. */
+ Elf32_Shdr Shdr;
+ int rc2 = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, &Shdr, sizeof(Shdr),
+ offShdrs + pElfHdr->e_shstrndx * cbShdr);
+ if ( RT_SUCCESS(rc2)
+ && Shdr.sh_offset > 0
+ && Shdr.sh_size > 0
+ && Shdr.sh_size < _256K
+ && Shdr.sh_type == SHT_STRTAB)
+ {
+ uint32_t const cbStrTab = Shdr.sh_size;
+ char * const pszStrTab = (char *)RTMemTmpAlloc(cbStrTab + 2);
+ if (pszStrTab)
+ {
+ rc2 = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, pszStrTab, Shdr.sh_size, offDbgInfo + Shdr.sh_offset);
+ if (RT_SUCCESS(rc2))
+ {
+ pszStrTab[cbStrTab] = '\0';
+
+ /* Iterate the sections, one by one. */
+ for (uint32_t i = 1; i < cShdrs; i++)
+ {
+ rc = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, &Shdr, sizeof(Shdr), offShdrs + i * cbShdr);
+ if ( RT_SUCCESS(rc)
+ && Shdr.sh_name < cbStrTab
+ && strncmp(&pszStrTab[Shdr.sh_name], RT_STR_TUPLE(".debug_")) == 0)
+ {
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF;
+ DbgInfo.iDbgInfo = iDbgInfo;
+ DbgInfo.offFile = offDbgInfo + Shdr.sh_offset;
+ DbgInfo.LinkAddress = NIL_RTLDRADDR;
+ DbgInfo.cb = Shdr.sh_size;
+ DbgInfo.pszExtFile = NULL;
+ DbgInfo.u.Dwarf.pszSection = &pszStrTab[Shdr.sh_name];
+
+ *pfReturn = true;
+ rc = pfnCallback(&pModLX->Core, &DbgInfo, pvUser);
+ if (rc != VINF_SUCCESS)
+ break;
+ iDbgInfo++;
+ }
+ }
+ }
+ RTMemTmpFree(pszStrTab);
+ }
+ }
+ return rc;
+ }
+
+ /*
+ * Watcom debug info? Don't know how to detect it...
+ */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnEnumDbgInfo}
+ */
+static DECLCALLBACK(int) rtldrLX_EnumDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits,
+ PFNRTLDRENUMDBG pfnCallback, void *pvUser)
+{
+ /*PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);*/
+ RT_NOREF(pfnCallback);
+ RT_NOREF(pvUser);
+
+ /*
+ * Quit immediately if no debug info.
+ */
+ if (kldrModLXHasDbgInfo(pMod, pvBits))
+ return VINF_SUCCESS;
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+
+ /*
+ * Read the debug info and look for familiar magics and structures.
+ */
+ union
+ {
+ uint8_t ab[1024];
+ IMAGE_DEBUG_MISC Misc;
+ RTCVHDR CvHdr;
+ } uBuf;
+
+ bool fReturn = false;
+
+ /* Try the offset without header displacement first. */
+ uint32_t cbToRead = RT_MIN(pModLX->Hdr.e32_debuglen, sizeof(uBuf));
+ int rc = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, &uBuf, cbToRead, pModLX->Hdr.e32_debuginfo);
+ if (RT_SUCCESS(rc))
+ rc = rtldrLx_EnumDbgInfoHelper(pModLX, pfnCallback, pvUser, &uBuf.ab[0], cbToRead, pModLX->Hdr.e32_debuginfo, &fReturn);
+
+ /* If that didn't yield anything, try displaying it by the header offset. */
+ if (!fReturn && pModLX->offHdr > 0)
+ {
+ rc = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, &uBuf, cbToRead, pModLX->Hdr.e32_debuginfo + pModLX->offHdr);
+ if (RT_SUCCESS(rc))
+ rc = rtldrLx_EnumDbgInfoHelper(pModLX, pfnCallback, pvUser, &uBuf.ab[0], cbToRead,
+ pModLX->Hdr.e32_debuginfo + pModLX->offHdr, &fReturn);
+ }
+ return rc;
+}
+
+
+static int kldrModLXHasDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ RT_NOREF(pvBits);
+
+ /*
+ * Don't currently bother with linkers which doesn't advertise it in the header.
+ */
+ if ( !pModLX->Hdr.e32_debuginfo
+ || !pModLX->Hdr.e32_debuglen)
+ return VERR_NOT_FOUND;
+ return VINF_SUCCESS;
+}
+
+#if 0
+
+/** @copydoc kLdrModMap */
+static int kldrModLXMap(PRTLDRMODINTERNAL pMod)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ unsigned fFixed;
+ void *pvBase;
+ int rc;
+
+ /*
+ * Already mapped?
+ */
+ if (pModLX->pvMapping)
+ return KLDR_ERR_ALREADY_MAPPED;
+
+ /*
+ * Allocate memory for it.
+ */
+ /* fixed image? */
+ fFixed = pModLX->Core.enmType == RTLDRTYPE_EXECUTABLE_FIXED
+ || pModLX->Core.enmType == RTLDRTYPE_SHARED_LIBRARY_FIXED;
+ if (!fFixed)
+ pvBase = NULL;
+ else
+ {
+ pvBase = (void *)(uintptr_t)pModLX->aSegments[0].LinkAddress;
+ if ((uintptr_t)pvBase != pModLX->aSegments[0].LinkAddress)
+ return KLDR_ERR_ADDRESS_OVERFLOW;
+ }
+ rc = kHlpPageAlloc(&pvBase, pModLX->cbMapped, KPROT_EXECUTE_READWRITE, fFixed);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Load the bits, apply page protection, and update the segment table.
+ */
+ rc = kldrModLXDoLoadBits(pModLX, pvBase);
+ if (RT_SUCCESS(rc))
+ rc = kldrModLXDoProtect(pModLX, pvBase, 0 /* protect */);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t i;
+ for (i = 0; i < pModLX->cSegments; i++)
+ {
+ if (pModLX->aSegments[i].RVA != NIL_RTLDRADDR)
+ pModLX->aSegments[i].MapAddress = (uintptr_t)pvBase + (uintptr_t)pModLX->aSegments[i].RVA;
+ }
+ pModLX->pvMapping = pvBase;
+ }
+ else
+ kHlpPageFree(pvBase, pModLX->cbMapped);
+ return rc;
+}
+
+#endif
+
+/**
+ * Loads the LX pages into the specified memory mapping.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pModLX The LX module interpreter instance.
+ * @param pvBits Where to load the bits.
+ */
+static int kldrModLXDoLoadBits(PKLDRMODLX pModLX, void *pvBits)
+{
+ const PRTLDRREADER pRdr = pModLX->Core.pReader;
+ uint8_t *pbTmpPage = NULL;
+ int rc = VINF_SUCCESS;
+ uint32_t i;
+
+ /*
+ * Iterate the segments.
+ */
+ for (i = 0; i < pModLX->Hdr.e32_objcnt; i++)
+ {
+ const struct o32_obj * const pObj = &pModLX->paObjs[i];
+ const uint32_t cPages = (uint32_t)(pModLX->aSegments[i].cbMapped / OBJPAGELEN);
+ uint32_t iPage;
+ uint8_t *pbPage = (uint8_t *)pvBits + (uintptr_t)pModLX->aSegments[i].RVA;
+
+ /*
+ * Iterate the page map pages.
+ */
+ for (iPage = 0; RT_SUCCESS(rc) && iPage < pObj->o32_mapsize; iPage++, pbPage += OBJPAGELEN)
+ {
+ const struct o32_map *pMap = &pModLX->paPageMappings[iPage + pObj->o32_pagemap - 1];
+ switch (pMap->o32_pageflags)
+ {
+ case VALID:
+ if (pMap->o32_pagesize == OBJPAGELEN)
+ rc = pRdr->pfnRead(pRdr, pbPage, OBJPAGELEN,
+ pModLX->Hdr.e32_datapage + (pMap->o32_pagedataoffset << pModLX->Hdr.e32_pageshift));
+ else if (pMap->o32_pagesize < OBJPAGELEN)
+ {
+ rc = pRdr->pfnRead(pRdr, pbPage, pMap->o32_pagesize,
+ pModLX->Hdr.e32_datapage + (pMap->o32_pagedataoffset << pModLX->Hdr.e32_pageshift));
+ memset(pbPage + pMap->o32_pagesize, 0, OBJPAGELEN - pMap->o32_pagesize);
+ }
+ else
+ rc = VERR_LDRLX_BAD_PAGE_MAP;
+ break;
+
+ case ITERDATA:
+ case ITERDATA2:
+ /* make sure we've got a temp page .*/
+ if (!pbTmpPage)
+ {
+ pbTmpPage = (uint8_t *)RTMemAlloc(OBJPAGELEN + 256);
+ if (!pbTmpPage)
+ break;
+ }
+ /* validate the size. */
+ if (pMap->o32_pagesize > OBJPAGELEN + 252)
+ {
+ rc = VERR_LDRLX_BAD_PAGE_MAP;
+ break;
+ }
+
+ /* read it and ensure 4 extra zero bytes. */
+ rc = pRdr->pfnRead(pRdr, pbTmpPage, pMap->o32_pagesize,
+ pModLX->Hdr.e32_datapage + (pMap->o32_pagedataoffset << pModLX->Hdr.e32_pageshift));
+ if (RT_FAILURE(rc))
+ break;
+ memset(pbTmpPage + pMap->o32_pagesize, 0, 4);
+
+ /* unpack it into the image page. */
+ if (pMap->o32_pageflags == ITERDATA2)
+ rc = kldrModLXDoIterData2Unpacking(pbPage, pbTmpPage, pMap->o32_pagesize);
+ else
+ rc = kldrModLXDoIterDataUnpacking(pbPage, pbTmpPage, pMap->o32_pagesize);
+ break;
+
+ case INVALID: /* we're probably not dealing correctly with INVALID pages... */
+ case ZEROED:
+ memset(pbPage, 0, OBJPAGELEN);
+ break;
+
+ case RANGE:
+ KLDRMODLX_ASSERT(!"RANGE");
+ RT_FALL_THRU();
+ default:
+ rc = VERR_LDRLX_BAD_PAGE_MAP;
+ break;
+ }
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /*
+ * Zero the remaining pages.
+ */
+ if (iPage < cPages)
+ memset(pbPage, 0, (cPages - iPage) * OBJPAGELEN);
+ }
+
+ if (pbTmpPage)
+ RTMemFree(pbTmpPage);
+ return rc;
+}
+
+
+/**
+ * Unpacks iterdata (aka EXEPACK).
+ *
+ * @returns IPRT status code.
+ * @param pbDst Where to put the uncompressed data. (Assumes OBJPAGELEN size.)
+ * @param pbSrc The compressed source data.
+ * @param cbSrc The file size of the compressed data. The source buffer
+ * contains 4 additional zero bytes.
+ */
+static int kldrModLXDoIterDataUnpacking(uint8_t *pbDst, const uint8_t *pbSrc, int cbSrc)
+{
+ const struct LX_Iter *pIter = (const struct LX_Iter *)pbSrc;
+ int cbDst = OBJPAGELEN;
+
+ /* Validate size of data. */
+ if (cbSrc >= (int)OBJPAGELEN - 2)
+ return VERR_LDRLX_BAD_ITERDATA;
+
+ /*
+ * Expand the page.
+ */
+ while (cbSrc > 0 && pIter->LX_nIter)
+ {
+ if (pIter->LX_nBytes == 1)
+ {
+ /*
+ * Special case - one databyte.
+ */
+ cbDst -= pIter->LX_nIter;
+ if (cbDst < 0)
+ return VERR_LDRLX_BAD_ITERDATA;
+
+ cbSrc -= 4 + 1;
+ if (cbSrc < -4)
+ return VERR_LDRLX_BAD_ITERDATA;
+
+ memset(pbDst, pIter->LX_Iterdata, pIter->LX_nIter);
+ pbDst += pIter->LX_nIter;
+ pIter++;
+ }
+ else
+ {
+ /*
+ * General.
+ */
+ int i;
+
+ cbDst -= pIter->LX_nIter * pIter->LX_nBytes;
+ if (cbDst < 0)
+ return VERR_LDRLX_BAD_ITERDATA;
+
+ cbSrc -= 4 + pIter->LX_nBytes;
+ if (cbSrc < -4)
+ return VERR_LDRLX_BAD_ITERDATA;
+
+ for (i = pIter->LX_nIter; i > 0; i--, pbDst += pIter->LX_nBytes)
+ memcpy(pbDst, &pIter->LX_Iterdata, pIter->LX_nBytes);
+ pIter = (struct LX_Iter *)((char*)pIter + 4 + pIter->LX_nBytes);
+ }
+ }
+
+ /*
+ * Zero remainder of the page.
+ */
+ if (cbDst > 0)
+ memset(pbDst, 0, cbDst);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unpacks iterdata (aka EXEPACK).
+ *
+ * @returns IPRT status code.
+ * @param pbDst Where to put the uncompressed data. (Assumes OBJPAGELEN size.)
+ * @param pbSrc The compressed source data.
+ * @param cbSrc The file size of the compressed data. The source buffer
+ * contains 4 additional zero bytes.
+ */
+static int kldrModLXDoIterData2Unpacking(uint8_t *pbDst, const uint8_t *pbSrc, int cbSrc)
+{
+ int cbDst = OBJPAGELEN;
+
+ while (cbSrc > 0)
+ {
+ /*
+ * Bit 0 and 1 is the encoding type.
+ */
+ switch (*pbSrc & 0x03)
+ {
+ /*
+ *
+ * 0 1 2 3 4 5 6 7
+ * type | |
+ * ----------------
+ * cb <cb bytes of data>
+ *
+ * Bits 2-7 is, if not zero, the length of an uncompressed run
+ * starting at the following byte.
+ *
+ * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+ * type | | | | | |
+ * ---------------- ---------------------- -----------------------
+ * zero cb char to multiply
+ *
+ * If the bits are zero, the following two bytes describes a 1 byte interation
+ * run. First byte is count, second is the byte to copy. A count of zero is
+ * means end of data, and we simply stops. In that case the rest of the data
+ * should be zero.
+ */
+ case 0:
+ {
+ if (*pbSrc)
+ {
+ const int cb = *pbSrc >> 2;
+ cbDst -= cb;
+ if (cbDst < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ cbSrc -= cb + 1;
+ if (cbSrc < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ memcpy(pbDst, ++pbSrc, cb);
+ pbDst += cb;
+ pbSrc += cb;
+ }
+ else if (cbSrc < 2)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ else
+ {
+ const int cb = pbSrc[1];
+ if (!cb)
+ goto l_endloop;
+ cbDst -= cb;
+ if (cbDst < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ cbSrc -= 3;
+ if (cbSrc < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ memset(pbDst, pbSrc[2], cb);
+ pbDst += cb;
+ pbSrc += 3;
+ }
+ break;
+ }
+
+
+ /*
+ * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ * type | | | | | |
+ * ---- ------- -------------------------
+ * cb1 cb2 - 3 offset <cb1 bytes of data>
+ *
+ * Two bytes layed out as described above, followed by cb1 bytes of data to be copied.
+ * The cb2(+3) and offset describes an amount of data to be copied from the expanded
+ * data relative to the current position. The data copied as you would expect it to be.
+ */
+ case 1:
+ {
+ cbSrc -= 2;
+ if (cbSrc < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ else
+ {
+ const unsigned off = ((unsigned)pbSrc[1] << 1) | (*pbSrc >> 7);
+ const int cb1 = (*pbSrc >> 2) & 3;
+ const int cb2 = ((*pbSrc >> 4) & 7) + 3;
+
+ pbSrc += 2;
+ cbSrc -= cb1;
+ if (cbSrc < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ cbDst -= cb1;
+ if (cbDst < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ memcpy(pbDst, pbSrc, cb1);
+ pbDst += cb1;
+ pbSrc += cb1;
+
+ if (off > OBJPAGELEN - (unsigned)cbDst)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ cbDst -= cb2;
+ if (cbDst < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ memmove(pbDst, pbDst - off, cb2);
+ pbDst += cb2;
+ }
+ break;
+ }
+
+
+ /*
+ * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ * type | | | |
+ * ---- ----------------------------------
+ * cb-3 offset
+ *
+ * Two bytes layed out as described above.
+ * The cb(+3) and offset describes an amount of data to be copied from the expanded
+ * data relative to the current position.
+ *
+ * If offset == 1 the data is not copied as expected, but in the memcpyw manner.
+ */
+ case 2:
+ {
+ cbSrc -= 2;
+ if (cbSrc < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ else
+ {
+ const unsigned off = ((unsigned)pbSrc[1] << 4) | (*pbSrc >> 4);
+ const int cb = ((*pbSrc >> 2) & 3) + 3;
+
+ pbSrc += 2;
+ if (off > OBJPAGELEN - (unsigned)cbDst)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ cbDst -= cb;
+ if (cbDst < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ kLdrModLXMemCopyW(pbDst, pbDst - off, cb);
+ pbDst += cb;
+ }
+ break;
+ }
+
+
+ /*
+ * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
+ * type | | | | | |
+ * ---------- ---------------- ----------------------------------
+ * cb1 cb2 offset <cb1 bytes of data>
+ *
+ * Three bytes layed out as described above, followed by cb1 bytes of data to be copied.
+ * The cb2 and offset describes an amount of data to be copied from the expanded
+ * data relative to the current position.
+ *
+ * If offset == 1 the data is not copied as expected, but in the memcpyw manner.
+ */
+ case 3:
+ {
+ cbSrc -= 3;
+ if (cbSrc < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ else
+ {
+ const int cb1 = (*pbSrc >> 2) & 0xf;
+ const int cb2 = ((pbSrc[1] & 0xf) << 2) | (*pbSrc >> 6);
+ const unsigned off = ((unsigned)pbSrc[2] << 4) | (pbSrc[1] >> 4);
+
+ pbSrc += 3;
+ cbSrc -= cb1;
+ if (cbSrc < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ cbDst -= cb1;
+ if (cbDst < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ memcpy(pbDst, pbSrc, cb1);
+ pbDst += cb1;
+ pbSrc += cb1;
+
+ if (off > OBJPAGELEN - (unsigned)cbDst)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ cbDst -= cb2;
+ if (cbDst < 0)
+ return VERR_LDRLX_BAD_ITERDATA2;
+ kLdrModLXMemCopyW(pbDst, pbDst - off, cb2);
+ pbDst += cb2;
+ }
+ break;
+ }
+ } /* type switch. */
+ } /* unpack loop */
+
+l_endloop:
+
+
+ /*
+ * Zero remainder of the page.
+ */
+ if (cbDst > 0)
+ memset(pbDst, 0, cbDst);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Special memcpy employed by the iterdata2 algorithm.
+ *
+ * Emulate a 16-bit memcpy (copying 16-bit at a time) and the effects this
+ * has if src is very close to the destination.
+ *
+ * @param pbDst Destination pointer.
+ * @param pbSrc Source pointer. Will always be <= pbDst.
+ * @param cb Amount of data to be copied.
+ * @remark This assumes that unaligned word and dword access is fine.
+ */
+static void kLdrModLXMemCopyW(uint8_t *pbDst, const uint8_t *pbSrc, int cb)
+{
+ switch (pbDst - pbSrc)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ /* 16-bit copy (unaligned) */
+ if (cb & 1)
+ *pbDst++ = *pbSrc++;
+ for (cb >>= 1; cb > 0; cb--, pbDst += 2, pbSrc += 2)
+ *(uint16_t *)pbDst = *(const uint16_t *)pbSrc;
+ break;
+
+ default:
+ /* 32-bit copy (unaligned) */
+ if (cb & 1)
+ *pbDst++ = *pbSrc++;
+ if (cb & 2)
+ {
+ *(uint16_t *)pbDst = *(const uint16_t *)pbSrc;
+ pbDst += 2;
+ pbSrc += 2;
+ }
+ for (cb >>= 2; cb > 0; cb--, pbDst += 4, pbSrc += 4)
+ *(uint32_t *)pbDst = *(const uint32_t *)pbSrc;
+ break;
+ }
+}
+
+#if 0
+
+/**
+ * Unprotects or protects the specified image mapping.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pModLX The LX module interpreter instance.
+ * @param pvBits The mapping to protect.
+ * @param UnprotectOrProtect If 1 unprotect (i.e. make all writable), otherwise
+ * protect according to the object table.
+ */
+static int kldrModLXDoProtect(PKLDRMODLX pModLX, void *pvBits, unsigned fUnprotectOrProtect)
+{
+ uint32_t i;
+
+ /*
+ * Change object protection.
+ */
+ for (i = 0; i < pModLX->cSegments; i++)
+ {
+ int rc;
+ void *pv;
+ KPROT enmProt;
+
+ /* calc new protection. */
+ enmProt = pModLX->aSegments[i].enmProt;
+ if (fUnprotectOrProtect)
+ {
+ switch (enmProt)
+ {
+ case KPROT_NOACCESS:
+ case KPROT_READONLY:
+ case KPROT_READWRITE:
+ case KPROT_WRITECOPY:
+ enmProt = KPROT_READWRITE;
+ break;
+ case KPROT_EXECUTE:
+ case KPROT_EXECUTE_READ:
+ case KPROT_EXECUTE_READWRITE:
+ case KPROT_EXECUTE_WRITECOPY:
+ enmProt = KPROT_EXECUTE_READWRITE;
+ break;
+ default:
+ KLDRMODLX_ASSERT(!"bad enmProt");
+ return -1;
+ }
+ }
+ else
+ {
+ /* copy on write -> normal write. */
+ if (enmProt == KPROT_EXECUTE_WRITECOPY)
+ enmProt = KPROT_EXECUTE_READWRITE;
+ else if (enmProt == KPROT_WRITECOPY)
+ enmProt = KPROT_READWRITE;
+ }
+
+
+ /* calc the address and set page protection. */
+ pv = (uint8_t *)pvBits + pModLX->aSegments[i].RVA;
+
+ rc = kHlpPageProtect(pv, pModLX->aSegments[i].cbMapped, enmProt);
+ if (RT_FAILURE(rc))
+ break;
+
+ /** @todo the gap page should be marked NOACCESS! */
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModUnmap */
+static int kldrModLXUnmap(PRTLDRMODINTERNAL pMod)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ uint32_t i;
+ int rc;
+
+ /*
+ * Mapped?
+ */
+ if (!pModLX->pvMapping)
+ return KLDR_ERR_NOT_MAPPED;
+
+ /*
+ * Free the mapping and update the segments.
+ */
+ rc = kHlpPageFree((void *)pModLX->pvMapping, pModLX->cbMapped);
+ KLDRMODLX_ASSERT(!rc);
+ pModLX->pvMapping = NULL;
+
+ for (i = 0; i < pModLX->cSegments; i++)
+ pModLX->aSegments[i].MapAddress = 0;
+
+ return rc;
+}
+
+
+/** @copydoc kLdrModAllocTLS */
+static int kldrModLXAllocTLS(PRTLDRMODINTERNAL pMod, void *pvMapping)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+
+ /* no tls, just do the error checking. */
+ if ( pvMapping == KLDRMOD_INT_MAP
+ && pModLX->pvMapping)
+ return KLDR_ERR_NOT_MAPPED;
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModFreeTLS */
+static void kldrModLXFreeTLS(PRTLDRMODINTERNAL pMod, void *pvMapping)
+{
+ /* no tls. */
+ RT_NOREF(pMod);
+ RT_NOREF(pvMapping);
+
+}
+
+
+/** @copydoc kLdrModReload */
+static int kldrModLXReload(PRTLDRMODINTERNAL pMod)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ int rc, rc2;
+
+ /*
+ * Mapped?
+ */
+ if (!pModLX->pvMapping)
+ return KLDR_ERR_NOT_MAPPED;
+
+ /*
+ * Before doing anything we'll have to make all pages writable.
+ */
+ rc = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 1 /* unprotect */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Load the bits again.
+ */
+ rc = kldrModLXDoLoadBits(pModLX, (void *)pModLX->pvMapping);
+
+ /*
+ * Restore protection.
+ */
+ rc2 = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 0 /* protect */);
+ if (RT_SUCCESS(rc) && RT_FAILURE(rc2))
+ rc = rc2;
+ return rc;
+}
+
+
+/** @copydoc kLdrModFixupMapping */
+static int kldrModLXFixupMapping(PRTLDRMODINTERNAL pMod, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ int rc, rc2;
+
+ /*
+ * Mapped?
+ */
+ if (!pModLX->pvMapping)
+ return KLDR_ERR_NOT_MAPPED;
+
+ /*
+ * Before doing anything we'll have to make all pages writable.
+ */
+ rc = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 1 /* unprotect */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Apply fixups and resolve imports.
+ */
+ rc = rtldrLX_RelocateBits(pMod, (void *)pModLX->pvMapping, (uintptr_t)pModLX->pvMapping,
+ pModLX->aSegments[0].LinkAddress, pfnGetImport, pvUser);
+
+ /*
+ * Restore protection.
+ */
+ rc2 = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 0 /* protect */);
+ if (RT_SUCCESS(rc) && RT_FAILURE(rc2))
+ rc = rc2;
+ return rc;
+}
+
+
+/** @copydoc kLdrModCallInit */
+static int kldrModLXCallInit(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ int rc;
+
+ /*
+ * Mapped?
+ */
+ if (pvMapping == KLDRMOD_INT_MAP)
+ {
+ pvMapping = (void *)pModLX->pvMapping;
+ if (!pvMapping)
+ return KLDR_ERR_NOT_MAPPED;
+ }
+
+ /*
+ * Do TLS callbacks first and then call the init/term function if it's a DLL.
+ */
+ if ((pModLX->Hdr.e32_mflags & E32MODMASK) == E32MODDLL)
+ rc = kldrModLXDoCallDLL(pModLX, pvMapping, 0 /* attach */, uHandle);
+ else
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+/**
+ * Call the DLL entrypoint.
+ *
+ * @returns 0 on success.
+ * @returns KLDR_ERR_MODULE_INIT_FAILED or KLDR_ERR_THREAD_ATTACH_FAILED on failure.
+ * @param pModLX The LX module interpreter instance.
+ * @param pvMapping The module mapping to use (resolved).
+ * @param uOp The operation (DLL_*).
+ * @param uHandle The module handle to present.
+ */
+static int kldrModLXDoCallDLL(PKLDRMODLX pModLX, void *pvMapping, unsigned uOp, uintptr_t uHandle)
+{
+ int rc;
+
+ /*
+ * If no entrypoint there isn't anything to be done.
+ */
+ if ( !pModLX->Hdr.e32_startobj
+ || pModLX->Hdr.e32_startobj > pModLX->Hdr.e32_objcnt)
+ return VINF_SUCCESS;
+
+ /*
+ * Invoke the entrypoint and convert the boolean result to a kLdr status code.
+ */
+ rc = kldrModLXDoCall((uintptr_t)pvMapping
+ + (uintptr_t)pModLX->aSegments[pModLX->Hdr.e32_startobj - 1].RVA
+ + pModLX->Hdr.e32_eip,
+ uHandle, uOp, NULL);
+ if (rc)
+ rc = VINF_SUCCESS;
+ else if (uOp == 0 /* attach */)
+ rc = KLDR_ERR_MODULE_INIT_FAILED;
+ else /* detach: ignore failures */
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+/**
+ * Do a 3 parameter callback.
+ *
+ * @returns 32-bit callback return.
+ * @param uEntrypoint The address of the function to be called.
+ * @param uHandle The first argument, the module handle.
+ * @param uOp The second argumnet, the reason we're calling.
+ * @param pvReserved The third argument, reserved argument. (figure this one out)
+ */
+static int32_t kldrModLXDoCall(uintptr_t uEntrypoint, uintptr_t uHandle, uint32_t uOp, void *pvReserved)
+{
+#if defined(__X86__) || defined(__i386__) || defined(_M_IX86)
+ int32_t rc;
+/** @todo try/except */
+
+ /*
+ * Paranoia.
+ */
+# ifdef __GNUC__
+ __asm__ __volatile__(
+ "pushl %2\n\t"
+ "pushl %1\n\t"
+ "pushl %0\n\t"
+ "lea 12(%%esp), %2\n\t"
+ "call *%3\n\t"
+ "movl %2, %%esp\n\t"
+ : "=a" (rc)
+ : "d" (uOp),
+ "S" (0),
+ "c" (uEntrypoint),
+ "0" (uHandle));
+# elif defined(_MSC_VER)
+ __asm {
+ mov eax, [uHandle]
+ mov edx, [uOp]
+ mov ecx, 0
+ mov ebx, [uEntrypoint]
+ push edi
+ mov edi, esp
+ push ecx
+ push edx
+ push eax
+ call ebx
+ mov esp, edi
+ pop edi
+ mov [rc], eax
+ }
+# else
+# error "port me!"
+# endif
+ RT_NOREF(pvReserved);
+ return rc;
+
+#else
+ RT_NOREF(uEntrypoint);
+ RT_NOREF(uHandle);
+ RT_NOREF(uOp);
+ RT_NOREF(pvReserved);
+ return KCPU_ERR_ARCH_CPU_NOT_COMPATIBLE;
+#endif
+}
+
+
+/** @copydoc kLdrModCallTerm */
+static int kldrModLXCallTerm(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+
+ /*
+ * Mapped?
+ */
+ if (pvMapping == KLDRMOD_INT_MAP)
+ {
+ pvMapping = (void *)pModLX->pvMapping;
+ if (!pvMapping)
+ return KLDR_ERR_NOT_MAPPED;
+ }
+
+ /*
+ * Do the call.
+ */
+ if ((pModLX->Hdr.e32_mflags & E32MODMASK) == E32MODDLL)
+ kldrModLXDoCallDLL(pModLX, pvMapping, 1 /* detach */, uHandle);
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModCallThread */
+static int kldrModLXCallThread(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle, unsigned fAttachingOrDetaching)
+{
+ /* no thread attach/detach callout. */
+ RT_NOREF(pMod);
+ RT_NOREF(pvMapping);
+ RT_NOREF(uHandle);
+ RT_NOREF(fAttachingOrDetaching);
+ return VINF_SUCCESS;
+}
+
+#endif
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnGetImageSize}
+ */
+static DECLCALLBACK(size_t) rtldrLX_GetImageSize(PRTLDRMODINTERNAL pMod)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ return pModLX->cbMapped;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnGetBits}
+ */
+static DECLCALLBACK(int) rtldrLX_GetBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress,
+ PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+
+ /*
+ * Load the image bits.
+ */
+ int rc = kldrModLXDoLoadBits(pModLX, pvBits);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Perform relocations.
+ */
+ rc = rtldrLX_RelocateBits(pMod, pvBits, BaseAddress, pModLX->aSegments[0].LinkAddress, pfnGetImport, pvUser);
+ }
+ return rc;
+}
+
+
+/* GCC goes boinkers if we put this inside the function. */
+union RELOC_VISIBILITY_STUPIDITY
+{
+ const uint8_t *pb;
+ const struct r32_rlc *prlc;
+};
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnRelocate}
+ */
+static DECLCALLBACK(int) rtldrLX_RelocateBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress,
+ RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ uint32_t iSeg;
+ int rc;
+
+ /*
+ * Do we need to to *anything*?
+ */
+ if ( NewBaseAddress == OldBaseAddress
+ && NewBaseAddress == pModLX->paObjs[0].o32_base
+ && !pModLX->Hdr.e32_impmodcnt)
+ return VINF_SUCCESS;
+
+ /*
+ * Load the fixup section.
+ */
+ if (!pModLX->pbFixupSection)
+ {
+ rc = kldrModLXDoLoadFixupSection(pModLX);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Iterate the segments.
+ */
+ for (iSeg = 0; iSeg < pModLX->Hdr.e32_objcnt; iSeg++)
+ {
+ const struct o32_obj * const pObj = &pModLX->paObjs[iSeg];
+ RTLDRADDR PageAddress = NewBaseAddress + pModLX->aSegments[iSeg].RVA;
+ uint32_t iPage;
+ uint8_t *pbPage = (uint8_t *)pvBits + (uintptr_t)pModLX->aSegments[iSeg].RVA;
+
+ /*
+ * Iterate the page map pages.
+ */
+ for (iPage = 0, rc = VINF_SUCCESS;
+ RT_SUCCESS(rc) && iPage < pObj->o32_mapsize;
+ iPage++, pbPage += OBJPAGELEN, PageAddress += OBJPAGELEN)
+ {
+ const uint8_t * const pbFixupRecEnd = pModLX->pbFixupRecs + pModLX->paoffPageFixups[iPage + pObj->o32_pagemap];
+ const uint8_t *pb = pModLX->pbFixupRecs + pModLX->paoffPageFixups[iPage + pObj->o32_pagemap - 1];
+ RTLDRADDR uValue = NIL_RTLDRADDR;
+ uint32_t fKind = 0;
+ int iSelector;
+
+ /* sanity */
+ if (pbFixupRecEnd < pb)
+ return VERR_LDR_BAD_FIXUP;
+ if (pbFixupRecEnd - 1 > pModLX->pbFixupSectionLast)
+ return VERR_LDR_BAD_FIXUP;
+ if (pb < pModLX->pbFixupSection)
+ return VERR_LDR_BAD_FIXUP;
+
+ /*
+ * Iterate the fixup record.
+ */
+ while (pb < pbFixupRecEnd)
+ {
+ union RELOC_VISIBILITY_STUPIDITY u;
+ char szImpModule[256];
+ u.pb = pb;
+ pb += 3 + (u.prlc->nr_stype & NRCHAIN ? 0 : 1); /* place pch at the 4th member. */
+
+ /*
+ * Figure out the target.
+ */
+ switch (u.prlc->nr_flags & NRRTYP)
+ {
+ /*
+ * Internal fixup.
+ */
+ case NRRINT:
+ {
+ uint16_t iTrgObject;
+ uint32_t offTrgObject;
+
+ /* the object */
+ if (u.prlc->nr_flags & NR16OBJMOD)
+ {
+ iTrgObject = *(const uint16_t *)pb;
+ pb += 2;
+ }
+ else
+ iTrgObject = *pb++;
+ iTrgObject--;
+ if (iTrgObject >= pModLX->Hdr.e32_objcnt)
+ return VERR_LDR_BAD_FIXUP;
+
+ /* the target */
+ if ((u.prlc->nr_stype & NRSRCMASK) != NRSSEG)
+ {
+ if (u.prlc->nr_flags & NR32BITOFF)
+ {
+ offTrgObject = *(const uint32_t *)pb;
+ pb += 4;
+ }
+ else
+ {
+ offTrgObject = *(const uint16_t *)pb;
+ pb += 2;
+ }
+
+ /* calculate the symbol info. */
+ uValue = offTrgObject + NewBaseAddress + pModLX->aSegments[iTrgObject].RVA;
+ }
+ else
+ uValue = NewBaseAddress + pModLX->aSegments[iTrgObject].RVA;
+ if ( (u.prlc->nr_stype & NRALIAS)
+ || (pModLX->aSegments[iTrgObject].fFlags & RTLDRSEG_FLAG_16BIT))
+ iSelector = pModLX->aSegments[iTrgObject].Sel16bit;
+ else
+ iSelector = pModLX->aSegments[iTrgObject].SelFlat;
+ fKind = 0;
+ break;
+ }
+
+ /*
+ * Import by symbol ordinal.
+ */
+ case NRRORD:
+ {
+ uint16_t iModule;
+ uint32_t iSymbol;
+
+ /* the module ordinal */
+ if (u.prlc->nr_flags & NR16OBJMOD)
+ {
+ iModule = *(const uint16_t *)pb;
+ pb += 2;
+ }
+ else
+ iModule = *pb++;
+ iModule--;
+ if (iModule >= pModLX->Hdr.e32_impmodcnt)
+ return VERR_LDR_BAD_FIXUP;
+ rc = kldrModLXGetImport(pModLX, NULL, iModule, szImpModule, sizeof(szImpModule), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#if 1
+ if (u.prlc->nr_flags & NRICHAIN)
+ return VERR_LDR_BAD_FIXUP;
+#endif
+
+ /* . */
+ if (u.prlc->nr_flags & NR32BITOFF)
+ {
+ iSymbol = *(const uint32_t *)pb;
+ pb += 4;
+ }
+ else if (!(u.prlc->nr_flags & NR8BITORD))
+ {
+ iSymbol = *(const uint16_t *)pb;
+ pb += 2;
+ }
+ else
+ iSymbol = *pb++;
+
+ /* resolve it. */
+ rc = pfnGetImport(pMod, szImpModule, NULL, iSymbol, &uValue, /*&fKind,*/ pvUser);
+ if (RT_FAILURE(rc))
+ return rc;
+ iSelector = -1;
+ break;
+ }
+
+ /*
+ * Import by symbol name.
+ */
+ case NRRNAM:
+ {
+ uint32_t iModule;
+ uint16_t offSymbol;
+ const uint8_t *pbSymbol;
+
+ /* the module ordinal */
+ if (u.prlc->nr_flags & NR16OBJMOD)
+ {
+ iModule = *(const uint16_t *)pb;
+ pb += 2;
+ }
+ else
+ iModule = *pb++;
+ iModule--;
+ if (iModule >= pModLX->Hdr.e32_impmodcnt)
+ return VERR_LDR_BAD_FIXUP;
+ rc = kldrModLXGetImport(pModLX, NULL, iModule, szImpModule, sizeof(szImpModule), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+#if 1
+ if (u.prlc->nr_flags & NRICHAIN)
+ return VERR_LDR_BAD_FIXUP;
+#endif
+
+ /* . */
+ if (u.prlc->nr_flags & NR32BITOFF)
+ {
+ offSymbol = *(const uint32_t *)pb;
+ pb += 4;
+ }
+ else if (!(u.prlc->nr_flags & NR8BITORD))
+ {
+ offSymbol = *(const uint16_t *)pb;
+ pb += 2;
+ }
+ else
+ offSymbol = *pb++;
+ pbSymbol = pModLX->pbImportProcs + offSymbol;
+ if ( pbSymbol < pModLX->pbImportProcs
+ || pbSymbol > pModLX->pbFixupSectionLast)
+ return VERR_LDR_BAD_FIXUP;
+ char szSymbol[256];
+ memcpy(szSymbol, pbSymbol + 1, *pbSymbol);
+ szSymbol[*pbSymbol] = '\0';
+
+ /* resolve it. */
+ rc = pfnGetImport(pMod, szImpModule, szSymbol, UINT32_MAX, &uValue, /*&fKind,*/ pvUser);
+ if (RT_FAILURE(rc))
+ return rc;
+ iSelector = -1;
+ break;
+ }
+
+ case NRRENT:
+ KLDRMODLX_ASSERT(!"NRRENT");
+ RT_FALL_THRU();
+ default:
+ iSelector = -1;
+ break;
+ }
+
+ /* addend */
+ if (u.prlc->nr_flags & NRADD)
+ {
+ if (u.prlc->nr_flags & NR32BITADD)
+ {
+ uValue += *(const uint32_t *)pb;
+ pb += 4;
+ }
+ else
+ {
+ uValue += *(const uint16_t *)pb;
+ pb += 2;
+ }
+ }
+
+
+ /*
+ * Deal with the 'source' (i.e. the place that should be modified - very logical).
+ */
+ if (!(u.prlc->nr_stype & NRCHAIN))
+ {
+ int off = u.prlc->r32_soff;
+
+ /* common / simple */
+ if ( (u.prlc->nr_stype & NRSRCMASK) == NROFF32
+ && off >= 0
+ && off <= (int)OBJPAGELEN - 4)
+ *(uint32_t *)&pbPage[off] = (uint32_t)uValue;
+ else if ( (u.prlc->nr_stype & NRSRCMASK) == NRSOFF32
+ && off >= 0
+ && off <= (int)OBJPAGELEN - 4)
+ *(uint32_t *)&pbPage[off] = (uint32_t)(uValue - (PageAddress + off + 4));
+ else
+ {
+ /* generic */
+ rc = kldrModLXDoReloc(pbPage, off, PageAddress, u.prlc, iSelector, uValue, fKind);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ else if (!(u.prlc->nr_flags & NRICHAIN))
+ {
+ const int16_t *poffSrc = (const int16_t *)pb;
+ uint8_t c = u.pb[2];
+
+ /* common / simple */
+ if ((u.prlc->nr_stype & NRSRCMASK) == NROFF32)
+ {
+ while (c-- > 0)
+ {
+ int off = *poffSrc++;
+ if (off >= 0 && off <= (int)OBJPAGELEN - 4)
+ *(uint32_t *)&pbPage[off] = (uint32_t)uValue;
+ else
+ {
+ rc = kldrModLXDoReloc(pbPage, off, PageAddress, u.prlc, iSelector, uValue, fKind);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ }
+ else if ((u.prlc->nr_stype & NRSRCMASK) == NRSOFF32)
+ {
+ while (c-- > 0)
+ {
+ int off = *poffSrc++;
+ if (off >= 0 && off <= (int)OBJPAGELEN - 4)
+ *(uint32_t *)&pbPage[off] = (uint32_t)(uValue - (PageAddress + off + 4));
+ else
+ {
+ rc = kldrModLXDoReloc(pbPage, off, PageAddress, u.prlc, iSelector, uValue, fKind);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ }
+ else
+ {
+ while (c-- > 0)
+ {
+ rc = kldrModLXDoReloc(pbPage, *poffSrc++, PageAddress, u.prlc, iSelector, uValue, fKind);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ }
+ pb = (const uint8_t *)poffSrc;
+ }
+ else
+ {
+ /* This is a pain because it will require virgin pages on a relocation. */
+ KLDRMODLX_ASSERT(!"NRICHAIN");
+ return VERR_LDRLX_NRICHAIN_NOT_SUPPORTED;
+ }
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Applies the relocation to one 'source' in a page.
+ *
+ * This takes care of the more esotic case while the common cases
+ * are dealt with seperately.
+ *
+ * @returns IPRT status code.
+ * @param pbPage The page in which to apply the fixup.
+ * @param off Page relative offset of where to apply the offset.
+ * @param PageAddress The page address.
+ * @param prlc The relocation record.
+ * @param iSelector Selector value, -1 if flat.
+ * @param uValue The target value.
+ * @param fKind The target kind.
+ */
+static int kldrModLXDoReloc(uint8_t *pbPage, int off, RTLDRADDR PageAddress, const struct r32_rlc *prlc,
+ int iSelector, RTLDRADDR uValue, uint32_t fKind)
+{
+#pragma pack(1) /* just to be sure */
+ union
+ {
+ uint8_t ab[6];
+ uint32_t off32;
+ uint16_t off16;
+ uint8_t off8;
+ struct
+ {
+ uint16_t off;
+ uint16_t Sel;
+ } Far16;
+ struct
+ {
+ uint32_t off;
+ uint16_t Sel;
+ } Far32;
+ } uData;
+#pragma pack()
+ const uint8_t *pbSrc;
+ uint8_t *pbDst;
+ uint8_t cb;
+
+ RT_NOREF(fKind);
+
+ /*
+ * Compose the fixup data.
+ */
+ switch (prlc->nr_stype & NRSRCMASK)
+ {
+ case NRSBYT:
+ uData.off8 = (uint8_t)uValue;
+ cb = 1;
+ break;
+ case NRSSEG:
+ if (iSelector == -1)
+ {
+ /* fixme */
+ }
+ uData.off16 = iSelector;
+ cb = 2;
+ break;
+ case NRSPTR:
+ if (iSelector == -1)
+ {
+ /* fixme */
+ }
+ uData.Far16.off = (uint16_t)uValue;
+ uData.Far16.Sel = iSelector;
+ cb = 4;
+ break;
+ case NRSOFF:
+ uData.off16 = (uint16_t)uValue;
+ cb = 2;
+ break;
+ case NRPTR48:
+ if (iSelector == -1)
+ {
+ /* fixme */
+ }
+ uData.Far32.off = (uint32_t)uValue;
+ uData.Far32.Sel = iSelector;
+ cb = 6;
+ break;
+ case NROFF32:
+ uData.off32 = (uint32_t)uValue;
+ cb = 4;
+ break;
+ case NRSOFF32:
+ uData.off32 = (uint32_t)(uValue - (PageAddress + off + 4));
+ cb = 4;
+ break;
+ default:
+ return VERR_LDRLX_BAD_FIXUP_SECTION; /** @todo fix error, add more checks! */
+ }
+
+ /*
+ * Apply it. This is sloooow...
+ */
+ pbSrc = &uData.ab[0];
+ pbDst = pbPage + off;
+ while (cb-- > 0)
+ {
+ if (off > (int)OBJPAGELEN)
+ break;
+ if (off >= 0)
+ *pbDst = *pbSrc;
+ pbSrc++;
+ pbDst++;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnEnumSegments}
+ */
+static DECLCALLBACK(int) rtldrLX_EnumSegments(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser)
+{
+ PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ uint32_t const cSegments = pThis->cSegments;
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ {
+ int rc = pfnCallback(pMod, &pThis->aSegments[iSeg], pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnLinkAddressToSegOffset}
+ */
+static DECLCALLBACK(int) rtldrLX_LinkAddressToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress,
+ uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ uint32_t const cSegments = pThis->cSegments;
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ {
+ RTLDRADDR offSeg = LinkAddress - pThis->aSegments[iSeg].LinkAddress;
+ if ( offSeg < pThis->aSegments[iSeg].cbMapped
+ || offSeg < pThis->aSegments[iSeg].cb)
+ {
+ *piSeg = iSeg;
+ *poffSeg = offSeg;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_LDR_INVALID_LINK_ADDRESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnLinkAddressToRva}
+ */
+static DECLCALLBACK(int) rtldrLX_LinkAddressToRva(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva)
+{
+ PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ uint32_t const cSegments = pThis->cSegments;
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ {
+ RTLDRADDR offSeg = LinkAddress - pThis->aSegments[iSeg].LinkAddress;
+ if ( offSeg < pThis->aSegments[iSeg].cbMapped
+ || offSeg < pThis->aSegments[iSeg].cb)
+ {
+ *pRva = pThis->aSegments[iSeg].RVA + offSeg;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_LDR_INVALID_RVA;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnSegOffsetToRva}
+ */
+static DECLCALLBACK(int) rtldrLX_SegOffsetToRva(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg, PRTLDRADDR pRva)
+{
+ PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+
+ if (iSeg >= pThis->cSegments)
+ return VERR_LDR_INVALID_SEG_OFFSET;
+ PCRTLDRSEG pSegment = &pThis->aSegments[iSeg];
+
+ if ( offSeg > pSegment->cbMapped
+ && offSeg > pSegment->cb
+ && ( pSegment->cbFile < 0
+ || offSeg > (uint64_t)pSegment->cbFile))
+ return VERR_LDR_INVALID_SEG_OFFSET;
+
+ *pRva = pSegment->RVA + offSeg;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnRvaToSegOffset}
+ */
+static DECLCALLBACK(int) rtldrLX_RvaToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR Rva, uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ uint32_t const cSegments = pThis->cSegments;
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ {
+ RTLDRADDR offSeg = Rva - pThis->aSegments[iSeg].RVA;
+ if ( offSeg < pThis->aSegments[iSeg].cbMapped
+ || offSeg < pThis->aSegments[iSeg].cb)
+ {
+ *piSeg = iSeg;
+ *poffSeg = offSeg;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_LDR_INVALID_RVA;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnReadDbgInfo}
+ */
+static DECLCALLBACK(int) rtldrLX_ReadDbgInfo(PRTLDRMODINTERNAL pMod, uint32_t iDbgInfo, RTFOFF off, size_t cb, void *pvBuf)
+{
+ PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ RT_NOREF(iDbgInfo);
+ return pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvBuf, cb, off);
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnQueryProp}
+ */
+static DECLCALLBACK(int) rtldrLX_QueryProp(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits,
+ void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);
+ int rc;
+ switch (enmProp)
+ {
+ case RTLDRPROP_IMPORT_COUNT:
+ Assert(cbBuf == sizeof(uint32_t));
+ Assert(*pcbRet == cbBuf);
+ *(uint32_t *)pvBuf = pThis->Hdr.e32_impmodcnt;
+ rc = VINF_SUCCESS;
+ break;
+
+ case RTLDRPROP_IMPORT_MODULE:
+ rc = kldrModLXGetImport(pThis, pvBits, *(uint32_t const *)pvBuf, (char *)pvBuf, cbBuf, pcbRet);
+ break;
+
+ case RTLDRPROP_INTERNAL_NAME:
+ *pcbRet = pThis->cchName + 1;
+ if (cbBuf >= pThis->cchName + 1)
+ {
+ memcpy(pvBuf, pThis->pszName, pThis->cchName + 1);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+
+
+ default:
+ rc = VERR_NOT_FOUND;
+ break;
+ }
+ RT_NOREF_PV(pvBits);
+ return rc;
+}
+
+
+/**
+ * Operations for a Mach-O module interpreter.
+ */
+static const RTLDROPS s_rtldrLXOps=
+{
+ "LX",
+ rtldrLX_Close,
+ NULL,
+ NULL /*pfnDone*/,
+ rtldrLX_EnumSymbols,
+ /* ext */
+ rtldrLX_GetImageSize,
+ rtldrLX_GetBits,
+ rtldrLX_RelocateBits,
+ rtldrLX_GetSymbolEx,
+ NULL /*pfnQueryForwarderInfo*/,
+ rtldrLX_EnumDbgInfo,
+ rtldrLX_EnumSegments,
+ rtldrLX_LinkAddressToSegOffset,
+ rtldrLX_LinkAddressToRva,
+ rtldrLX_SegOffsetToRva,
+ rtldrLX_RvaToSegOffset,
+ rtldrLX_ReadDbgInfo,
+ rtldrLX_QueryProp,
+ NULL /*pfnVerifySignature*/,
+ NULL /*pfnHashImage*/,
+ NULL /*pfnUnwindFrame*/,
+ 42
+};
+
+
+/**
+ * Handles opening LX images.
+ */
+DECLHIDDEN(int) rtldrLXOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, RTFOFF offLxHdr,
+ PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo)
+{
+
+ /*
+ * Create the instance data and do a minimal header validation.
+ */
+ PKLDRMODLX pThis = NULL;
+ int rc = kldrModLXDoCreate(pReader, offLxHdr, fFlags, &pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Match up against the requested CPU architecture.
+ */
+ if ( enmArch == RTLDRARCH_WHATEVER
+ || pThis->Core.enmArch == enmArch)
+ {
+ pThis->Core.pOps = &s_rtldrLXOps;
+ pThis->Core.u32Magic = RTLDRMOD_MAGIC;
+ *phLdrMod = &pThis->Core;
+ return VINF_SUCCESS;
+ }
+ rc = VERR_LDR_ARCH_MISMATCH;
+ }
+ if (pThis)
+ RTMemFree(pThis);
+ return rc;
+
+}
+
diff --git a/src/VBox/Runtime/common/ldr/ldrMachO.cpp b/src/VBox/Runtime/common/ldr/ldrMachO.cpp
new file mode 100644
index 00000000..7d5c655a
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrMachO.cpp
@@ -0,0 +1,5734 @@
+/* $Id: ldrMachO.cpp $ */
+/** @file
+ * kLdr - The Module Interpreter for the MACH-O format.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ * --------------------------------------------------------------------
+ *
+ * This code is based on: kLdr/kLdrModMachO.c from kStuff r113.
+ *
+ * Copyright (c) 2006-2013 Knut St. Osmundsen <bird-kStuff-spamix@anduin.net>
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/base64.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/sha.h>
+#include <iprt/crypto/digest.h>
+
+#include <iprt/formats/mach-o.h>
+#include <iprt/crypto/applecodesign.h>
+#include "internal/ldr.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def RTLDRMODMACHO_STRICT
+ * Define RTLDRMODMACHO_STRICT to enabled strict checks in RTLDRMODMACHO. */
+#define RTLDRMODMACHO_STRICT 1
+#define RTLDRMODMACHO_STRICT2
+
+/** @def RTLDRMODMACHO_ASSERT
+ * Assert that an expression is true when KLDR_STRICT is defined.
+ */
+#ifdef RTLDRMODMACHO_STRICT
+# define RTLDRMODMACHO_ASSERT(expr) Assert(expr)
+#else
+# define RTLDRMODMACHO_ASSERT(expr) do {} while (0)
+#endif
+
+/** @def RTLDRMODMACHO_CHECK_RETURN
+ * Checks that an expression is true and return if it isn't.
+ * This is a debug aid.
+ */
+#ifdef RTLDRMODMACHO_STRICT2
+# define RTLDRMODMACHO_CHECK_RETURN(expr, rc) AssertReturn(expr, rc)
+#else
+# define RTLDRMODMACHO_CHECK_RETURN(expr, rc) do { if (RT_LIKELY(expr)) {/* likely */ } else return (rc); } while (0)
+#endif
+
+/** @def RTLDRMODMACHO_CHECK_MSG_RETURN
+ * Checks that an expression is true and return if it isn't.
+ * This is a debug aid.
+ */
+#ifdef RTLDRMODMACHO_STRICT2
+# define RTLDRMODMACHO_CHECK_MSG_RETURN(expr, msgargs, rc) AssertMsgReturn(expr, msgargs, rc)
+#else
+# define RTLDRMODMACHO_CHECK_MSG_RETURN(expr, msgargs, rc) do { if (RT_LIKELY(expr)) {/* likely */ } else return (rc); } while (0)
+#endif
+
+/** @def RTLDRMODMACHO_CHECK_RETURN
+ * Checks that an expression is true and return if it isn't.
+ * This is a debug aid.
+ */
+#ifdef RTLDRMODMACHO_STRICT2
+# define RTLDRMODMACHO_FAILED_RETURN(rc) AssertFailedReturn(rc)
+#else
+# define RTLDRMODMACHO_FAILED_RETURN(rc) return (rc)
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Mach-O section details.
+ */
+typedef struct RTLDRMODMACHOSECT
+{
+ /** The size of the section (in bytes). */
+ RTLDRADDR cb;
+ /** The link address of this section. */
+ RTLDRADDR LinkAddress;
+ /** The RVA of this section. */
+ RTLDRADDR RVA;
+ /** The file offset of this section.
+ * This is -1 if the section doesn't have a file backing. */
+ RTFOFF offFile;
+ /** The number of fixups. */
+ uint32_t cFixups;
+ /** The array of fixups. (lazy loaded) */
+ macho_relocation_union_t *paFixups;
+ /** Array of virgin data running parallel to paFixups */
+ PRTUINT64U pauFixupVirginData;
+ /** The file offset of the fixups for this section.
+ * This is -1 if the section doesn't have any fixups. */
+ RTFOFF offFixups;
+ /** Mach-O section flags. */
+ uint32_t fFlags;
+ /** kLdr segment index. */
+ uint32_t iSegment;
+ /** Pointer to the Mach-O section structure. */
+ void *pvMachoSection;
+} RTLDRMODMACHOSECT, *PRTLDRMODMACHOSECT;
+
+/**
+ * Extra per-segment info.
+ *
+ * This is corresponds to a kLdr segment, not a Mach-O segment!
+ */
+typedef struct RTLDRMODMACHOSEG
+{
+ /** Common segment info. */
+ RTLDRSEG SegInfo;
+
+ /** The orignal segment number (in case we had to resort it). */
+ uint32_t iOrgSegNo;
+ /** The number of sections in the segment. */
+ uint32_t cSections;
+ /** Pointer to the sections belonging to this segment.
+ * The array resides in the big memory chunk allocated for
+ * the module handle, so it doesn't need freeing. */
+ PRTLDRMODMACHOSECT paSections;
+
+} RTLDRMODMACHOSEG, *PRTLDRMODMACHOSEG;
+
+/**
+ * Instance data for the Mach-O MH_OBJECT module interpreter.
+ * @todo interpret the other MH_* formats.
+ */
+typedef struct RTLDRMODMACHO
+{
+ /** Core module structure. */
+ RTLDRMODINTERNAL Core;
+
+ /** The minium cpu this module was built for.
+ * This might not be accurate, so use kLdrModCanExecuteOn() to check. */
+ RTLDRCPU enmCpu;
+ /** The number of segments in the module. */
+ uint32_t cSegments;
+
+ /** Pointer to the RDR file mapping of the raw file bits. NULL if not mapped. */
+ const void *pvBits;
+ /** Pointer to the user mapping. */
+ void *pvMapping;
+ /** The module open flags. */
+ uint32_t fOpenFlags;
+
+ /** The offset of the image. (FAT fun.) */
+ RTFOFF offImage;
+ /** The link address. */
+ RTLDRADDR LinkAddress;
+ /** The size of the mapped image. */
+ RTLDRADDR cbImage;
+ /** Whether we're capable of loading the image. */
+ bool fCanLoad;
+ /** Whether we're creating a global offset table segment.
+ * This dependes on the cputype and image type. */
+ bool fMakeGot;
+ /** The size of a indirect GOT jump stub entry.
+ * This is 0 if not needed. */
+ uint32_t cbJmpStub;
+ /** Effective file type. If the original was a MH_OBJECT file, the
+ * corresponding MH_DSYM needs the segment translation of a MH_OBJECT too.
+ * The MH_DSYM normally has a separate __DWARF segment, but this is
+ * automatically skipped during the transation. */
+ uint32_t uEffFileType;
+ /** Pointer to the load commands. (endian converted) */
+ uint8_t *pbLoadCommands;
+ /** The Mach-O header. (endian converted)
+ * @remark The reserved field is only valid for real 64-bit headers. */
+ mach_header_64_t Hdr;
+
+ /** The offset of the symbol table. */
+ RTFOFF offSymbols;
+ /** The number of symbols. */
+ uint32_t cSymbols;
+ /** The pointer to the loaded symbol table. */
+ void *pvaSymbols;
+ /** The offset of the string table. */
+ RTFOFF offStrings;
+ /** The size of the of the string table. */
+ uint32_t cchStrings;
+ /** Pointer to the loaded string table. */
+ char *pchStrings;
+ /** Pointer to the dynamic symbol table command if present. */
+ dysymtab_command_t *pDySymTab;
+ /** The indirect symbol table (size given by pDySymTab->nindirectsymb).
+ * @remarks Host endian. */
+ uint32_t *paidxIndirectSymbols;
+ /** Dynamic relocations, first pDySymTab->nextrel external relocs followed by
+ * pDySymTab->nlocrel local ones. */
+ macho_relocation_union_t *paRelocations;
+ /** Array of virgin data running parallel to paRelocations */
+ PRTUINT64U pauRelocationsVirginData;
+
+ /** The image UUID, all zeros if not found. */
+ uint8_t abImageUuid[16];
+
+ /** The code signature offset. */
+ uint32_t offCodeSignature;
+ /** The code signature size (0 if not signed). */
+ uint32_t cbCodeSignature;
+ /** Pointer to the code signature blob if loaded. */
+ union
+ {
+ uint8_t *pb;
+ PCRTCRAPLCSSUPERBLOB pSuper;
+ } PtrCodeSignature;
+ /** File offset of segment 0 (relative to Mach-O header). */
+ uint64_t offSeg0ForCodeSign;
+ /** File size of segment 0. */
+ uint64_t cbSeg0ForCodeSign;
+ /** Segment 0 flags. */
+ uint64_t fSeg0ForCodeSign;
+
+ /** The RVA of the Global Offset Table. */
+ RTLDRADDR GotRVA;
+ /** The RVA of the indirect GOT jump stubs. */
+ RTLDRADDR JmpStubsRVA;
+
+ /** The number of sections. */
+ uint32_t cSections;
+ /** Pointer to the section array running in parallel to the Mach-O one. */
+ PRTLDRMODMACHOSECT paSections;
+
+ /** Array of segments parallel to the one in KLDRMOD. */
+ RTLDRMODMACHOSEG aSegments[1];
+} RTLDRMODMACHO;
+/** Pointer instance data for an Mach-O module. */
+typedef RTLDRMODMACHO *PRTLDRMODMACHO;
+
+/**
+ * Code directory data.
+ */
+typedef struct RTLDRMACHCODEDIR
+{
+ PCRTCRAPLCSCODEDIRECTORY pCodeDir;
+ /** The slot type. */
+ uint32_t uSlot;
+ /** The naturalized size. */
+ uint32_t cb;
+ /** The digest type. */
+ RTDIGESTTYPE enmDigest;
+} RTLDRMACHCODEDIR;
+/** Pointer to code directory data. */
+typedef RTLDRMACHCODEDIR *PRTLDRMACHCODEDIR;
+
+/**
+ * Decoded apple Mach-O signature data.
+ * @note The raw signature data lives in RTLDRMODMACHO::PtrCodeSignature.
+ */
+typedef struct RTLDRMACHOSIGNATURE
+{
+ /** Number of code directory slots. */
+ uint32_t cCodeDirs;
+ /** Code directories. */
+ RTLDRMACHCODEDIR aCodeDirs[6];
+
+ /** The index of the PKCS#7 slot. */
+ uint32_t idxPkcs7;
+ /** The size of the PKCS#7 data. */
+ uint32_t cbPkcs7;
+ /** Pointer to the PKCS#7 data. */
+ uint8_t const *pbPkcs7;
+ /** Parsed PKCS#7 data. */
+ RTCRPKCS7CONTENTINFO ContentInfo;
+ /** Pointer to the decoded SignedData inside the ContentInfo member. */
+ PRTCRPKCS7SIGNEDDATA pSignedData;
+} RTLDRMACHOSIGNATURE;
+/** Pointer to decoded apple code signing data. */
+typedef RTLDRMACHOSIGNATURE *PRTLDRMACHOSIGNATURE;
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#if 0
+static int32_t kldrModMachONumberOfImports(PRTLDRMODINTERNAL pMod, const void *pvBits);
+#endif
+static DECLCALLBACK(int) rtldrMachO_RelocateBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress,
+ RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser);
+
+
+static int kldrModMachOPreParseLoadCommands(uint8_t *pbLoadCommands, const mach_header_32_t *pHdr, PRTLDRREADER pRdr, RTFOFF offImage,
+ uint32_t fOpenFlags, uint32_t *pcSegments, uint32_t *pcSections, uint32_t *pcbStringPool,
+ bool *pfCanLoad, PRTLDRADDR pLinkAddress, uint8_t *puEffFileType, PRTERRINFO pErrInfo);
+static int kldrModMachOParseLoadCommands(PRTLDRMODMACHO pThis, char *pbStringPool, uint32_t cbStringPool);
+
+static int kldrModMachOLoadObjSymTab(PRTLDRMODMACHO pThis);
+static int kldrModMachOLoadFixups(PRTLDRMODMACHO pThis, RTFOFF offFixups, uint32_t cFixups, macho_relocation_union_t **ppaFixups);
+
+static int kldrModMachODoQuerySymbol32Bit(PRTLDRMODMACHO pThis, const macho_nlist_32_t *paSyms, uint32_t cSyms, const char *pchStrings,
+ uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t iSymbol, const char *pchSymbol,
+ uint32_t cchSymbol, PRTLDRADDR puValue, uint32_t *pfKind);
+static int kldrModMachODoQuerySymbol64Bit(PRTLDRMODMACHO pThis, const macho_nlist_64_t *paSyms, uint32_t cSyms, const char *pchStrings,
+ uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t iSymbol, const char *pchSymbol,
+ uint32_t cchSymbol, PRTLDRADDR puValue, uint32_t *pfKind);
+static int kldrModMachODoEnumSymbols32Bit(PRTLDRMODMACHO pThis, const macho_nlist_32_t *paSyms, uint32_t cSyms,
+ const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress,
+ uint32_t fFlags, PFNRTLDRENUMSYMS pfnCallback, void *pvUser);
+static int kldrModMachODoEnumSymbols64Bit(PRTLDRMODMACHO pThis, const macho_nlist_64_t *paSyms, uint32_t cSyms,
+ const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress,
+ uint32_t fFlags, PFNRTLDRENUMSYMS pfnCallback, void *pvUser);
+static int kldrModMachOObjDoImports(PRTLDRMODMACHO pThis, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser);
+static int kldrModMachOObjDoFixups(PRTLDRMODMACHO pThis, void *pvMapping, RTLDRADDR NewBaseAddress);
+static int kldrModMachOApplyFixupsGeneric32Bit(PRTLDRMODMACHO pThis, uint8_t *pbSectBits, size_t cbSectBits, RTLDRADDR uBitsRva,
+ RTLDRADDR uBitsLinkAddr, const macho_relocation_union_t *paFixups,
+ const uint32_t cFixups, PCRTUINT64U const pauVirginData,
+ macho_nlist_32_t *paSyms, uint32_t cSyms, RTLDRADDR NewBaseAddress);
+static int kldrModMachOApplyFixupsAMD64(PRTLDRMODMACHO pThis, uint8_t *pbSectBits, size_t cbSectBits, RTLDRADDR uBitsRva,
+ const macho_relocation_union_t *paFixups,
+ const uint32_t cFixups, PCRTUINT64U const pauVirginData,
+ macho_nlist_64_t *paSyms, uint32_t cSyms, RTLDRADDR NewBaseAddress);
+
+static int kldrModMachOMakeGOT(PRTLDRMODMACHO pThis, void *pvBits, RTLDRADDR NewBaseAddress);
+
+/*static int kldrModMachODoFixups(PRTLDRMODMACHO pThis, void *pvMapping, RTLDRADDR NewBaseAddress, RTLDRADDR OldBaseAddress);
+static int kldrModMachODoImports(PRTLDRMODMACHO pThis, void *pvMapping, PFNRTLDRIMPORT pfnGetImport, void *pvUser);*/
+
+
+
+/**
+ * Separate function for reading creating the Mach-O module instance to
+ * simplify cleanup on failure.
+ */
+static int kldrModMachODoCreate(PRTLDRREADER pRdr, RTFOFF offImage, uint32_t fOpenFlags,
+ PRTLDRMODMACHO *ppModMachO, PRTERRINFO pErrInfo)
+{
+ *ppModMachO = NULL;
+
+ /*
+ * Read the Mach-O header.
+ */
+ union
+ {
+ mach_header_32_t Hdr32;
+ mach_header_64_t Hdr64;
+ } s;
+ Assert(&s.Hdr32.magic == &s.Hdr64.magic);
+ Assert(&s.Hdr32.flags == &s.Hdr64.flags);
+ int rc = pRdr->pfnRead(pRdr, &s, sizeof(s), offImage);
+ if (rc)
+ return RTErrInfoSetF(pErrInfo, rc, "Error reading Mach-O header at %RTfoff: %Rrc", offImage, rc);
+ if ( s.Hdr32.magic != IMAGE_MACHO32_SIGNATURE
+ && s.Hdr32.magic != IMAGE_MACHO64_SIGNATURE)
+ {
+ if ( s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE_OE
+ || s.Hdr32.magic == IMAGE_MACHO64_SIGNATURE_OE)
+ return VERR_LDRMACHO_OTHER_ENDIAN_NOT_SUPPORTED;
+ return VERR_INVALID_EXE_SIGNATURE;
+ }
+
+ /* sanity checks. */
+ if ( s.Hdr32.sizeofcmds > pRdr->pfnSize(pRdr) - sizeof(mach_header_32_t)
+ || s.Hdr32.sizeofcmds < sizeof(load_command_t) * s.Hdr32.ncmds
+ || (s.Hdr32.flags & ~MH_VALID_FLAGS))
+ return VERR_LDRMACHO_BAD_HEADER;
+
+ bool fMakeGot;
+ uint8_t cbJmpStub;
+ switch (s.Hdr32.cputype)
+ {
+ case CPU_TYPE_X86:
+ fMakeGot = false;
+ cbJmpStub = 0;
+ break;
+ case CPU_TYPE_X86_64:
+ fMakeGot = s.Hdr32.filetype == MH_OBJECT || s.Hdr32.filetype == MH_KEXT_BUNDLE;
+ cbJmpStub = fMakeGot ? 8 : 0;
+ break;
+ case CPU_TYPE_ARM64:
+ fMakeGot = s.Hdr32.filetype == MH_OBJECT || s.Hdr32.filetype == MH_KEXT_BUNDLE;
+ cbJmpStub = fMakeGot ? 8 : 0; /** @todo Not sure if this is right. Need to expore ARM64/MachO a bit more... */
+ break;
+ default:
+ return VERR_LDRMACHO_UNSUPPORTED_MACHINE;
+ }
+
+ if ( s.Hdr32.filetype != MH_OBJECT
+ && s.Hdr32.filetype != MH_EXECUTE
+ && s.Hdr32.filetype != MH_DYLIB
+ && s.Hdr32.filetype != MH_BUNDLE
+ && s.Hdr32.filetype != MH_DSYM
+ && s.Hdr32.filetype != MH_KEXT_BUNDLE)
+ return VERR_LDRMACHO_UNSUPPORTED_FILE_TYPE;
+
+ /*
+ * Read and pre-parse the load commands to figure out how many segments we'll be needing.
+ */
+ uint8_t *pbLoadCommands = (uint8_t *)RTMemAlloc(s.Hdr32.sizeofcmds);
+ if (!pbLoadCommands)
+ return VERR_NO_MEMORY;
+ rc = pRdr->pfnRead(pRdr, pbLoadCommands, s.Hdr32.sizeofcmds,
+ s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE
+ || s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE_OE
+ ? sizeof(mach_header_32_t) + offImage
+ : sizeof(mach_header_64_t) + offImage);
+
+ uint32_t cSegments = 0;
+ uint32_t cSections = 0;
+ uint32_t cbStringPool = 0;
+ bool fCanLoad = true;
+ RTLDRADDR LinkAddress = NIL_RTLDRADDR;
+ uint8_t uEffFileType = 0;
+ if (RT_SUCCESS(rc))
+ rc = kldrModMachOPreParseLoadCommands(pbLoadCommands, &s.Hdr32, pRdr, offImage, fOpenFlags,
+ &cSegments, &cSections, &cbStringPool, &fCanLoad, &LinkAddress, &uEffFileType,
+ pErrInfo);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pbLoadCommands);
+ return rc;
+ }
+ cSegments += fMakeGot;
+
+
+ /*
+ * Calc the instance size, allocate and initialize it.
+ */
+ size_t const cbModAndSegs = RT_ALIGN_Z(RT_UOFFSETOF_DYN(RTLDRMODMACHO, aSegments[cSegments])
+ + sizeof(RTLDRMODMACHOSECT) * cSections, 16);
+ PRTLDRMODMACHO pThis = (PRTLDRMODMACHO)RTMemAlloc(cbModAndSegs + cbStringPool);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+ *ppModMachO = pThis;
+ pThis->pbLoadCommands = pbLoadCommands;
+ pThis->offImage = offImage;
+
+ /* Core & CPU.*/
+ pThis->Core.u32Magic = 0; /* set by caller */
+ pThis->Core.eState = LDR_STATE_OPENED;
+ pThis->Core.pOps = NULL; /* set by caller. */
+ pThis->Core.pReader = pRdr;
+ switch (s.Hdr32.cputype)
+ {
+ case CPU_TYPE_X86:
+ pThis->Core.enmArch = RTLDRARCH_X86_32;
+ pThis->Core.enmEndian = RTLDRENDIAN_LITTLE;
+ switch (s.Hdr32.cpusubtype)
+ {
+ case CPU_SUBTYPE_I386_ALL: /* == CPU_SUBTYPE_386 */
+ pThis->enmCpu = RTLDRCPU_X86_32_BLEND;
+ break;
+ case CPU_SUBTYPE_486:
+ pThis->enmCpu = RTLDRCPU_I486;
+ break;
+ case CPU_SUBTYPE_486SX:
+ pThis->enmCpu = RTLDRCPU_I486SX;
+ break;
+ case CPU_SUBTYPE_PENT: /* == CPU_SUBTYPE_586 */
+ pThis->enmCpu = RTLDRCPU_I586;
+ break;
+ case CPU_SUBTYPE_PENTPRO:
+ case CPU_SUBTYPE_PENTII_M3:
+ case CPU_SUBTYPE_PENTII_M5:
+ case CPU_SUBTYPE_CELERON:
+ case CPU_SUBTYPE_CELERON_MOBILE:
+ case CPU_SUBTYPE_PENTIUM_3:
+ case CPU_SUBTYPE_PENTIUM_3_M:
+ case CPU_SUBTYPE_PENTIUM_3_XEON:
+ pThis->enmCpu = RTLDRCPU_I686;
+ break;
+ case CPU_SUBTYPE_PENTIUM_M:
+ case CPU_SUBTYPE_PENTIUM_4:
+ case CPU_SUBTYPE_PENTIUM_4_M:
+ case CPU_SUBTYPE_XEON:
+ case CPU_SUBTYPE_XEON_MP:
+ pThis->enmCpu = RTLDRCPU_P4;
+ break;
+
+ default:
+ /* Hack for kextutil output. */
+ if ( s.Hdr32.cpusubtype == 0
+ && s.Hdr32.filetype == MH_OBJECT)
+ break;
+ return VERR_LDRMACHO_UNSUPPORTED_MACHINE;
+ }
+ break;
+
+ case CPU_TYPE_X86_64:
+ pThis->Core.enmArch = RTLDRARCH_AMD64;
+ pThis->Core.enmEndian = RTLDRENDIAN_LITTLE;
+ switch (s.Hdr32.cpusubtype & ~CPU_SUBTYPE_MASK)
+ {
+ case CPU_SUBTYPE_X86_64_ALL: pThis->enmCpu = RTLDRCPU_AMD64_BLEND; break;
+ default:
+ return VERR_LDRMACHO_UNSUPPORTED_MACHINE;
+ }
+ break;
+
+ case CPU_TYPE_ARM64:
+ pThis->Core.enmArch = RTLDRARCH_ARM64;
+ pThis->Core.enmEndian = RTLDRENDIAN_LITTLE;
+ switch (s.Hdr32.cpusubtype & ~CPU_SUBTYPE_MASK)
+ {
+ case CPU_SUBTYPE_ARM64_ALL: pThis->enmCpu = RTLDRCPU_ARM64_BLEND; break;
+ case CPU_SUBTYPE_ARM64_V8: pThis->enmCpu = RTLDRCPU_ARM64_V8; break;
+ case CPU_SUBTYPE_ARM64E: pThis->enmCpu = RTLDRCPU_ARM64E; break;
+ default:
+ return VERR_LDRMACHO_UNSUPPORTED_MACHINE;
+ }
+ break;
+
+ default:
+ return VERR_LDRMACHO_UNSUPPORTED_MACHINE;
+ }
+
+ pThis->Core.enmFormat = RTLDRFMT_MACHO;
+ switch (s.Hdr32.filetype)
+ {
+ case MH_OBJECT: pThis->Core.enmType = RTLDRTYPE_OBJECT; break;
+ case MH_EXECUTE: pThis->Core.enmType = RTLDRTYPE_EXECUTABLE_FIXED; break;
+ case MH_DYLIB: pThis->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; break;
+ case MH_BUNDLE: pThis->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; break;
+ case MH_KEXT_BUNDLE:pThis->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; break;
+ case MH_DSYM: pThis->Core.enmType = RTLDRTYPE_DEBUG_INFO; break;
+ default:
+ return VERR_LDRMACHO_UNSUPPORTED_FILE_TYPE;
+ }
+
+ /* RTLDRMODMACHO */
+ pThis->cSegments = cSegments;
+ pThis->pvBits = NULL;
+ pThis->pvMapping = NULL;
+ pThis->fOpenFlags = fOpenFlags;
+ pThis->Hdr = s.Hdr64;
+ if ( s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE
+ || s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ pThis->Hdr.reserved = 0;
+ pThis->LinkAddress = LinkAddress;
+ pThis->cbImage = 0;
+ pThis->fCanLoad = fCanLoad;
+ pThis->fMakeGot = fMakeGot;
+ pThis->cbJmpStub = cbJmpStub;
+ pThis->uEffFileType = uEffFileType;
+ pThis->offSymbols = 0;
+ pThis->cSymbols = 0;
+ pThis->pvaSymbols = NULL;
+ pThis->pDySymTab = NULL;
+ pThis->paRelocations = NULL;
+ pThis->pauRelocationsVirginData = NULL;
+ pThis->paidxIndirectSymbols = NULL;
+ pThis->offStrings = 0;
+ pThis->cchStrings = 0;
+ pThis->pchStrings = NULL;
+ memset(pThis->abImageUuid, 0, sizeof(pThis->abImageUuid));
+ pThis->offCodeSignature = 0;
+ pThis->cbCodeSignature = 0;
+ pThis->PtrCodeSignature.pb = NULL;
+ pThis->GotRVA = NIL_RTLDRADDR;
+ pThis->JmpStubsRVA = NIL_RTLDRADDR;
+ pThis->cSections = cSections;
+ pThis->paSections = (PRTLDRMODMACHOSECT)&pThis->aSegments[pThis->cSegments];
+
+ /*
+ * Setup the KLDRMOD segment array.
+ */
+ rc = kldrModMachOParseLoadCommands(pThis, (char *)pThis + cbModAndSegs, cbStringPool);
+
+ /*
+ * We're done.
+ */
+ return rc;
+}
+
+
+/**
+ * Converts, validates and preparses the load commands before we carve
+ * out the module instance.
+ *
+ * The conversion that's preformed is format endian to host endian. The
+ * preparsing has to do with segment counting, section counting and string pool
+ * sizing.
+ *
+ * Segment are created in two different ways, depending on the file type.
+ *
+ * For object files there is only one segment command without a given segment
+ * name. The sections inside that segment have different segment names and are
+ * not sorted by their segname attribute. We create one segment for each
+ * section, with the segment name being 'segname.sectname' in order to hopefully
+ * keep the names unique. Debug sections does not get segments.
+ *
+ * For non-object files, one kLdr segment is created for each Mach-O segment.
+ * Debug segments is not exposed by kLdr via the kLdr segment table, but via the
+ * debug enumeration callback API.
+ *
+ * @returns IPRT status code.
+ * @param pbLoadCommands The load commands to parse.
+ * @param pHdr The header.
+ * @param pRdr The file reader.
+ * @param offImage The image header (FAT fun).
+ * @param fOpenFlags RTLDR_O_XXX.
+ * @param pcSegments Where to store the segment count.
+ * @param pcSections Where to store the section count.
+ * @param pcbStringPool Where to store the string pool size.
+ * @param pfCanLoad Where to store the can-load-image indicator.
+ * @param pLinkAddress Where to store the image link address (i.e. the
+ * lowest segment address).
+ * @param puEffFileType Where to store the effective file type.
+ * @param pErrInfo Where to return additional error info. Optional.
+ */
+static int kldrModMachOPreParseLoadCommands(uint8_t *pbLoadCommands, const mach_header_32_t *pHdr, PRTLDRREADER pRdr,
+ RTFOFF offImage, uint32_t fOpenFlags, uint32_t *pcSegments, uint32_t *pcSections,
+ uint32_t *pcbStringPool, bool *pfCanLoad, PRTLDRADDR pLinkAddress,
+ uint8_t *puEffFileType, PRTERRINFO pErrInfo)
+{
+ union
+ {
+ uint8_t *pb;
+ load_command_t *pLoadCmd;
+ segment_command_32_t *pSeg32;
+ segment_command_64_t *pSeg64;
+ thread_command_t *pThread;
+ symtab_command_t *pSymTab;
+ dysymtab_command_t *pDySymTab;
+ uuid_command_t *pUuid;
+ } u;
+ const uint64_t cbFile = pRdr->pfnSize(pRdr) - offImage;
+ int const fConvertEndian = pHdr->magic == IMAGE_MACHO32_SIGNATURE_OE
+ || pHdr->magic == IMAGE_MACHO64_SIGNATURE_OE;
+ uint32_t cSegments = 0;
+ uint32_t cSections = 0;
+ size_t cbStringPool = 0;
+ uint32_t cLeft = pHdr->ncmds;
+ uint32_t cbLeft = pHdr->sizeofcmds;
+ uint8_t *pb = pbLoadCommands;
+ int cSegmentCommands = 0;
+ int cSymbolTabs = 0;
+ uint32_t cSymbols = 0; /* Copy of u.pSymTab->nsyms. */
+ uint32_t cDySymbolTabs = 0;
+ bool fDySymbolTabWithRelocs = false;
+ uint32_t cSectionsWithRelocs = 0;
+ uint8_t uEffFileType = *puEffFileType = pHdr->filetype;
+
+ *pcSegments = 0;
+ *pcSections = 0;
+ *pcbStringPool = 0;
+ *pfCanLoad = true;
+ *pLinkAddress = ~(RTLDRADDR)0;
+
+ while (cLeft-- > 0)
+ {
+ u.pb = pb;
+
+ /*
+ * Convert and validate command header.
+ */
+ RTLDRMODMACHO_CHECK_RETURN(cbLeft >= sizeof(load_command_t), VERR_LDRMACHO_BAD_LOAD_COMMAND);
+ if (fConvertEndian)
+ {
+ u.pLoadCmd->cmd = RT_BSWAP_U32(u.pLoadCmd->cmd);
+ u.pLoadCmd->cmdsize = RT_BSWAP_U32(u.pLoadCmd->cmdsize);
+ }
+ RTLDRMODMACHO_CHECK_RETURN(u.pLoadCmd->cmdsize <= cbLeft, VERR_LDRMACHO_BAD_LOAD_COMMAND);
+ cbLeft -= u.pLoadCmd->cmdsize;
+ pb += u.pLoadCmd->cmdsize;
+
+ /*
+ * Segment macros for avoiding code duplication.
+ */
+ /* Validation code shared with the 64-bit variant. */
+ #define VALIDATE_AND_ADD_SEGMENT(a_cBits) \
+ do { \
+ bool fSkipSeg = !strcmp(pSrcSeg->segname, "__DWARF") /* Note: Not for non-object files. */ \
+ || ( !strcmp(pSrcSeg->segname, "__CTF") /* Their CTF tool did/does weird things, */ \
+ && pSrcSeg->vmsize == 0) /* overlapping vmaddr and zero vmsize. */ \
+ || (cSectionsLeft > 0 && (pFirstSect->flags & S_ATTR_DEBUG)); \
+ \
+ /* MH_DSYM files for MH_OBJECT files must have MH_OBJECT segment translation. */ \
+ if ( uEffFileType == MH_DSYM \
+ && cSegmentCommands == 0 \
+ && pSrcSeg->segname[0] == '\0') \
+ *puEffFileType = uEffFileType = MH_OBJECT; \
+ \
+ RTLDRMODMACHO_CHECK_RETURN( pSrcSeg->filesize == 0 \
+ || ( pSrcSeg->fileoff <= cbFile \
+ && (uint64_t)pSrcSeg->fileoff + pSrcSeg->filesize <= cbFile), \
+ VERR_LDRMACHO_BAD_LOAD_COMMAND); \
+ RTLDRMODMACHO_CHECK_RETURN( pSrcSeg->filesize <= pSrcSeg->vmsize \
+ || (fSkipSeg && !strcmp(pSrcSeg->segname, "__CTF") /* see above */), \
+ VERR_LDRMACHO_BAD_LOAD_COMMAND); \
+ RTLDRMODMACHO_CHECK_RETURN(!(~pSrcSeg->maxprot & pSrcSeg->initprot), \
+ VERR_LDRMACHO_BAD_LOAD_COMMAND); \
+ RTLDRMODMACHO_CHECK_MSG_RETURN(!(pSrcSeg->flags & ~(SG_HIGHVM | SG_FVMLIB | SG_NORELOC | SG_PROTECTED_VERSION_1 | SG_READ_ONLY)), \
+ ("flags=%#x %s\n", pSrcSeg->flags, pSrcSeg->segname), \
+ VERR_LDRMACHO_BAD_LOAD_COMMAND); \
+ RTLDRMODMACHO_CHECK_RETURN( pSrcSeg->nsects * sizeof(section_##a_cBits##_t) \
+ <= u.pLoadCmd->cmdsize - sizeof(segment_command_##a_cBits##_t), \
+ VERR_LDRMACHO_BAD_LOAD_COMMAND); \
+ RTLDRMODMACHO_CHECK_RETURN( uEffFileType != MH_OBJECT \
+ || cSegmentCommands == 0 \
+ || ( cSegmentCommands == 1 \
+ && uEffFileType == MH_OBJECT \
+ && pHdr->filetype == MH_DSYM \
+ && fSkipSeg), \
+ VERR_LDRMACHO_BAD_OBJECT_FILE); \
+ cSegmentCommands++; \
+ \
+ /* Add the segment, if not object file. */ \
+ if (!fSkipSeg && uEffFileType != MH_OBJECT) \
+ { \
+ cbStringPool += RTStrNLen(&pSrcSeg->segname[0], sizeof(pSrcSeg->segname)) + 1; \
+ cSegments++; \
+ if (cSegments == 1) /* The link address is set by the first segment. */ \
+ *pLinkAddress = pSrcSeg->vmaddr; \
+ } \
+ } while (0)
+
+
+ /* Validation code shared with the 64-bit variant. */
+ #define VALIDATE_AND_ADD_SECTION(a_cBits) \
+ do { \
+ int fFileBits; \
+ \
+ /* validate */ \
+ if (uEffFileType != MH_OBJECT) \
+ RTLDRMODMACHO_CHECK_RETURN(!strcmp(pSect->segname, pSrcSeg->segname),\
+ VERR_LDRMACHO_BAD_SECTION); \
+ \
+ switch (pSect->flags & SECTION_TYPE) \
+ { \
+ case S_ZEROFILL: \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \
+ fFileBits = 0; \
+ break; \
+ case S_REGULAR: \
+ case S_CSTRING_LITERALS: \
+ case S_COALESCED: \
+ case S_4BYTE_LITERALS: \
+ case S_8BYTE_LITERALS: \
+ case S_16BYTE_LITERALS: \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \
+ fFileBits = 1; \
+ break; \
+ \
+ case S_SYMBOL_STUBS: \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \
+ /* reserved2 == stub size. 0 has been seen (corecrypto.kext) */ \
+ RTLDRMODMACHO_CHECK_RETURN(pSect->reserved2 < 64, VERR_LDRMACHO_BAD_SECTION); \
+ fFileBits = 1; \
+ break; \
+ \
+ case S_NON_LAZY_SYMBOL_POINTERS: \
+ case S_LAZY_SYMBOL_POINTERS: \
+ case S_LAZY_DYLIB_SYMBOL_POINTERS: \
+ /* (reserved 1 = is indirect symbol table index) */ \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \
+ Log(("ldrMachO: Can't load because of section flags: %#x\n", pSect->flags & SECTION_TYPE)); \
+ *pfCanLoad = false; \
+ fFileBits = -1; /* __DATA.__got in the 64-bit mach_kernel has bits, any things without bits? */ \
+ break; \
+ \
+ case S_MOD_INIT_FUNC_POINTERS: \
+ /** @todo this requires a query API or flag... (e.g. C++ constructors) */ \
+ RTLDRMODMACHO_CHECK_RETURN(fOpenFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION), \
+ VERR_LDRMACHO_UNSUPPORTED_INIT_SECTION); \
+ RT_FALL_THRU(); \
+ case S_MOD_TERM_FUNC_POINTERS: \
+ /** @todo this requires a query API or flag... (e.g. C++ destructors) */ \
+ RTLDRMODMACHO_CHECK_RETURN(fOpenFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION), \
+ VERR_LDRMACHO_UNSUPPORTED_TERM_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \
+ fFileBits = 1; \
+ break; /* ignored */ \
+ \
+ case S_LITERAL_POINTERS: \
+ case S_DTRACE_DOF: \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \
+ fFileBits = 1; \
+ break; \
+ \
+ case S_INTERPOSING: \
+ case S_GB_ZEROFILL: \
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_UNSUPPORTED_SECTION); \
+ \
+ default: \
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_UNKNOWN_SECTION); \
+ } \
+ RTLDRMODMACHO_CHECK_RETURN(!(pSect->flags & ~( S_ATTR_PURE_INSTRUCTIONS | S_ATTR_NO_TOC | S_ATTR_STRIP_STATIC_SYMS \
+ | S_ATTR_NO_DEAD_STRIP | S_ATTR_LIVE_SUPPORT | S_ATTR_SELF_MODIFYING_CODE \
+ | S_ATTR_DEBUG | S_ATTR_SOME_INSTRUCTIONS | S_ATTR_EXT_RELOC \
+ | S_ATTR_LOC_RELOC | SECTION_TYPE)), \
+ VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN((pSect->flags & S_ATTR_DEBUG) == (pFirstSect->flags & S_ATTR_DEBUG), \
+ VERR_LDRMACHO_MIXED_DEBUG_SECTION_FLAGS); \
+ \
+ RTLDRMODMACHO_CHECK_RETURN(pSect->addr - pSrcSeg->vmaddr <= pSrcSeg->vmsize, \
+ VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN( pSect->addr - pSrcSeg->vmaddr + pSect->size <= pSrcSeg->vmsize \
+ || !strcmp(pSrcSeg->segname, "__CTF") /* see above */, \
+ VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN(pSect->align < 31, \
+ VERR_LDRMACHO_BAD_SECTION); \
+ /* Workaround for buggy ld64 (or as, llvm, ++) that produces a misaligned __TEXT.__unwind_info. */ \
+ /* Seen: pSect->align = 4, pSect->addr = 0x5ebe14. Just adjust the alignment down. */ \
+ if ( ((RT_BIT_32(pSect->align) - UINT32_C(1)) & pSect->addr) \
+ && pSect->align == 4 \
+ && strcmp(pSect->sectname, "__unwind_info") == 0) \
+ pSect->align = 2; \
+ RTLDRMODMACHO_CHECK_RETURN(!((RT_BIT_32(pSect->align) - UINT32_C(1)) & pSect->addr), \
+ VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN(!((RT_BIT_32(pSect->align) - UINT32_C(1)) & pSrcSeg->vmaddr), \
+ VERR_LDRMACHO_BAD_SECTION); \
+ \
+ /* Adjust the section offset before we check file offset. */ \
+ offSect = (offSect + RT_BIT_64(pSect->align) - UINT64_C(1)) & ~(RT_BIT_64(pSect->align) - UINT64_C(1)); \
+ if (pSect->addr) \
+ { \
+ RTLDRMODMACHO_CHECK_RETURN(offSect <= pSect->addr - pSrcSeg->vmaddr, VERR_LDRMACHO_BAD_SECTION); \
+ if (offSect < pSect->addr - pSrcSeg->vmaddr) \
+ offSect = pSect->addr - pSrcSeg->vmaddr; \
+ } \
+ \
+ if (fFileBits && pSect->offset == 0 && pSrcSeg->fileoff == 0 && pHdr->filetype == MH_DSYM) \
+ fFileBits = 0; \
+ if (fFileBits) \
+ { \
+ if (uEffFileType != MH_OBJECT) \
+ { \
+ RTLDRMODMACHO_CHECK_RETURN(pSect->offset == pSrcSeg->fileoff + offSect, \
+ VERR_LDRMACHO_NON_CONT_SEG_BITS); \
+ RTLDRMODMACHO_CHECK_RETURN(pSect->offset - pSrcSeg->fileoff <= pSrcSeg->filesize, \
+ VERR_LDRMACHO_BAD_SECTION); \
+ } \
+ RTLDRMODMACHO_CHECK_RETURN(pSect->offset <= cbFile, \
+ VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN((uint64_t)pSect->offset + pSect->size <= cbFile, \
+ VERR_LDRMACHO_BAD_SECTION); \
+ } \
+ else \
+ RTLDRMODMACHO_CHECK_RETURN(pSect->offset == 0, VERR_LDRMACHO_BAD_SECTION); \
+ \
+ if (!pSect->nreloc) \
+ RTLDRMODMACHO_CHECK_RETURN(!pSect->reloff, \
+ VERR_LDRMACHO_BAD_SECTION); \
+ else \
+ { \
+ RTLDRMODMACHO_CHECK_RETURN(pSect->reloff <= cbFile, \
+ VERR_LDRMACHO_BAD_SECTION); \
+ RTLDRMODMACHO_CHECK_RETURN( (uint64_t)pSect->reloff \
+ + (RTFOFF)pSect->nreloc * sizeof(macho_relocation_info_t) \
+ <= cbFile, \
+ VERR_LDRMACHO_BAD_SECTION); \
+ cSectionsWithRelocs++; \
+ } \
+ \
+ /* Validate against file type (pointless?) and count the section, for object files add segment. */ \
+ switch (uEffFileType) \
+ { \
+ case MH_OBJECT: \
+ if ( !(pSect->flags & S_ATTR_DEBUG) \
+ && strcmp(pSect->segname, "__DWARF")) \
+ { \
+ cbStringPool += RTStrNLen(&pSect->segname[0], sizeof(pSect->segname)) + 1; \
+ cbStringPool += RTStrNLen(&pSect->sectname[0], sizeof(pSect->sectname)) + 1; \
+ cSegments++; \
+ if (cSegments == 1) /* The link address is set by the first segment. */ \
+ *pLinkAddress = pSect->addr; \
+ } \
+ RT_FALL_THRU(); \
+ case MH_EXECUTE: \
+ case MH_DYLIB: \
+ case MH_BUNDLE: \
+ case MH_DSYM: \
+ case MH_KEXT_BUNDLE: \
+ cSections++; \
+ break; \
+ default: \
+ RTLDRMODMACHO_FAILED_RETURN(VERR_INVALID_PARAMETER); \
+ } \
+ \
+ /* Advance the section offset, since we're also aligning it. */ \
+ offSect += pSect->size; \
+ } while (0) /* VALIDATE_AND_ADD_SECTION */
+
+ /*
+ * Convert endian if needed, parse and validate the command.
+ */
+ switch (u.pLoadCmd->cmd)
+ {
+ case LC_SEGMENT_32:
+ {
+ segment_command_32_t *pSrcSeg = (segment_command_32_t *)u.pLoadCmd;
+ section_32_t *pFirstSect = (section_32_t *)(pSrcSeg + 1);
+ section_32_t *pSect = pFirstSect;
+ uint32_t cSectionsLeft = pSrcSeg->nsects;
+ uint64_t offSect = 0;
+
+ /* Convert and verify the segment. */
+ RTLDRMODMACHO_CHECK_RETURN(u.pLoadCmd->cmdsize >= sizeof(segment_command_32_t), VERR_LDRMACHO_BAD_LOAD_COMMAND);
+ RTLDRMODMACHO_CHECK_RETURN( pHdr->magic == IMAGE_MACHO32_SIGNATURE_OE
+ || pHdr->magic == IMAGE_MACHO32_SIGNATURE, VERR_LDRMACHO_BIT_MIX);
+ if (fConvertEndian)
+ {
+ pSrcSeg->vmaddr = RT_BSWAP_U32(pSrcSeg->vmaddr);
+ pSrcSeg->vmsize = RT_BSWAP_U32(pSrcSeg->vmsize);
+ pSrcSeg->fileoff = RT_BSWAP_U32(pSrcSeg->fileoff);
+ pSrcSeg->filesize = RT_BSWAP_U32(pSrcSeg->filesize);
+ pSrcSeg->maxprot = RT_BSWAP_U32(pSrcSeg->maxprot);
+ pSrcSeg->initprot = RT_BSWAP_U32(pSrcSeg->initprot);
+ pSrcSeg->nsects = RT_BSWAP_U32(pSrcSeg->nsects);
+ pSrcSeg->flags = RT_BSWAP_U32(pSrcSeg->flags);
+ }
+
+ VALIDATE_AND_ADD_SEGMENT(32);
+
+
+ /*
+ * Convert, validate and parse the sections.
+ */
+ cSectionsLeft = pSrcSeg->nsects;
+ pFirstSect = pSect = (section_32_t *)(pSrcSeg + 1);
+ while (cSectionsLeft-- > 0)
+ {
+ if (fConvertEndian)
+ {
+ pSect->addr = RT_BSWAP_U32(pSect->addr);
+ pSect->size = RT_BSWAP_U32(pSect->size);
+ pSect->offset = RT_BSWAP_U32(pSect->offset);
+ pSect->align = RT_BSWAP_U32(pSect->align);
+ pSect->reloff = RT_BSWAP_U32(pSect->reloff);
+ pSect->nreloc = RT_BSWAP_U32(pSect->nreloc);
+ pSect->flags = RT_BSWAP_U32(pSect->flags);
+ pSect->reserved1 = RT_BSWAP_U32(pSect->reserved1);
+ pSect->reserved2 = RT_BSWAP_U32(pSect->reserved2);
+ }
+
+ VALIDATE_AND_ADD_SECTION(32);
+
+ /* next */
+ pSect++;
+ }
+ break;
+ }
+
+ case LC_SEGMENT_64:
+ {
+ segment_command_64_t *pSrcSeg = (segment_command_64_t *)u.pLoadCmd;
+ section_64_t *pFirstSect = (section_64_t *)(pSrcSeg + 1);
+ section_64_t *pSect = pFirstSect;
+ uint32_t cSectionsLeft = pSrcSeg->nsects;
+ uint64_t offSect = 0;
+
+ /* Convert and verify the segment. */
+ RTLDRMODMACHO_CHECK_RETURN(u.pLoadCmd->cmdsize >= sizeof(segment_command_64_t), VERR_LDRMACHO_BAD_LOAD_COMMAND);
+ RTLDRMODMACHO_CHECK_RETURN( pHdr->magic == IMAGE_MACHO64_SIGNATURE_OE
+ || pHdr->magic == IMAGE_MACHO64_SIGNATURE, VERR_LDRMACHO_BIT_MIX);
+ if (fConvertEndian)
+ {
+ pSrcSeg->vmaddr = RT_BSWAP_U64(pSrcSeg->vmaddr);
+ pSrcSeg->vmsize = RT_BSWAP_U64(pSrcSeg->vmsize);
+ pSrcSeg->fileoff = RT_BSWAP_U64(pSrcSeg->fileoff);
+ pSrcSeg->filesize = RT_BSWAP_U64(pSrcSeg->filesize);
+ pSrcSeg->maxprot = RT_BSWAP_U32(pSrcSeg->maxprot);
+ pSrcSeg->initprot = RT_BSWAP_U32(pSrcSeg->initprot);
+ pSrcSeg->nsects = RT_BSWAP_U32(pSrcSeg->nsects);
+ pSrcSeg->flags = RT_BSWAP_U32(pSrcSeg->flags);
+ }
+
+ VALIDATE_AND_ADD_SEGMENT(64);
+
+ /*
+ * Convert, validate and parse the sections.
+ */
+ while (cSectionsLeft-- > 0)
+ {
+ if (fConvertEndian)
+ {
+ pSect->addr = RT_BSWAP_U64(pSect->addr);
+ pSect->size = RT_BSWAP_U64(pSect->size);
+ pSect->offset = RT_BSWAP_U32(pSect->offset);
+ pSect->align = RT_BSWAP_U32(pSect->align);
+ pSect->reloff = RT_BSWAP_U32(pSect->reloff);
+ pSect->nreloc = RT_BSWAP_U32(pSect->nreloc);
+ pSect->flags = RT_BSWAP_U32(pSect->flags);
+ pSect->reserved1 = RT_BSWAP_U32(pSect->reserved1);
+ pSect->reserved2 = RT_BSWAP_U32(pSect->reserved2);
+ }
+
+ VALIDATE_AND_ADD_SECTION(64);
+
+ /* next */
+ pSect++;
+ }
+ break;
+ } /* LC_SEGMENT_64 */
+
+
+ case LC_SYMTAB:
+ {
+ size_t cbSym;
+ if (fConvertEndian)
+ {
+ u.pSymTab->symoff = RT_BSWAP_U32(u.pSymTab->symoff);
+ u.pSymTab->nsyms = RT_BSWAP_U32(u.pSymTab->nsyms);
+ u.pSymTab->stroff = RT_BSWAP_U32(u.pSymTab->stroff);
+ u.pSymTab->strsize = RT_BSWAP_U32(u.pSymTab->strsize);
+ }
+
+ /* verify */
+ cbSym = pHdr->magic == IMAGE_MACHO32_SIGNATURE
+ || pHdr->magic == IMAGE_MACHO32_SIGNATURE_OE
+ ? sizeof(macho_nlist_32_t)
+ : sizeof(macho_nlist_64_t);
+ if ( u.pSymTab->symoff >= cbFile
+ || (uint64_t)u.pSymTab->symoff + u.pSymTab->nsyms * cbSym > cbFile)
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND);
+ if ( u.pSymTab->stroff >= cbFile
+ || (uint64_t)u.pSymTab->stroff + u.pSymTab->strsize > cbFile)
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND);
+
+ /* Only one object table, please. */
+ cSymbolTabs++;
+ if (cSymbolTabs != 1)
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_OBJECT_FILE);
+
+ cSymbols = u.pSymTab->nsyms;
+ break;
+ }
+
+ case LC_DYSYMTAB:
+ {
+ if (pHdr->filetype == MH_OBJECT)
+ RTLDRMODMACHO_FAILED_RETURN(RTErrInfoSet(pErrInfo, VERR_LDRMACHO_BAD_OBJECT_FILE,
+ "Not expecting LC_DYSYMTAB in MH_OBJECT"));
+ if (fConvertEndian)
+ {
+ u.pDySymTab->ilocalsym = RT_BSWAP_U32(u.pDySymTab->ilocalsym);
+ u.pDySymTab->nlocalsym = RT_BSWAP_U32(u.pDySymTab->nlocalsym);
+ u.pDySymTab->iextdefsym = RT_BSWAP_U32(u.pDySymTab->iextdefsym);
+ u.pDySymTab->nextdefsym = RT_BSWAP_U32(u.pDySymTab->nextdefsym);
+ u.pDySymTab->iundefsym = RT_BSWAP_U32(u.pDySymTab->iundefsym);
+ u.pDySymTab->nundefsym = RT_BSWAP_U32(u.pDySymTab->nundefsym);
+ u.pDySymTab->tocoff = RT_BSWAP_U32(u.pDySymTab->tocoff);
+ u.pDySymTab->ntoc = RT_BSWAP_U32(u.pDySymTab->ntoc);
+ u.pDySymTab->modtaboff = RT_BSWAP_U32(u.pDySymTab->modtaboff);
+ u.pDySymTab->nmodtab = RT_BSWAP_U32(u.pDySymTab->nmodtab);
+ u.pDySymTab->extrefsymoff = RT_BSWAP_U32(u.pDySymTab->extrefsymoff);
+ u.pDySymTab->nextrefsym = RT_BSWAP_U32(u.pDySymTab->nextrefsym);
+ u.pDySymTab->indirectsymboff = RT_BSWAP_U32(u.pDySymTab->indirectsymboff);
+ u.pDySymTab->nindirectsymb = RT_BSWAP_U32(u.pDySymTab->nindirectsymb);
+ u.pDySymTab->extreloff = RT_BSWAP_U32(u.pDySymTab->extreloff);
+ u.pDySymTab->nextrel = RT_BSWAP_U32(u.pDySymTab->nextrel);
+ u.pDySymTab->locreloff = RT_BSWAP_U32(u.pDySymTab->locreloff);
+ u.pDySymTab->nlocrel = RT_BSWAP_U32(u.pDySymTab->nlocrel);
+ }
+
+ /* verify */
+ RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->ilocalsym + u.pDySymTab->nlocalsym <= cSymbols,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "ilocalsym=%#x + nlocalsym=%#x vs cSymbols=%#x",
+ u.pDySymTab->ilocalsym, u.pDySymTab->nlocalsym, cSymbols));
+ RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->iextdefsym + u.pDySymTab->nextdefsym <= cSymbols,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "iextdefsym=%#x + nextdefsym=%#x vs cSymbols=%#x",
+ u.pDySymTab->iextdefsym, u.pDySymTab->nextdefsym, cSymbols));
+ RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->iundefsym + u.pDySymTab->nundefsym <= cSymbols,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "iundefsym=%#x + nundefsym=%#x vs cSymbols=%#x",
+ u.pDySymTab->iundefsym, u.pDySymTab->nundefsym, cSymbols));
+ RTLDRMODMACHO_CHECK_RETURN( (uint64_t)u.pDySymTab->tocoff + u.pDySymTab->ntoc * sizeof(dylib_table_of_contents_t)
+ <= cbFile,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "tocoff=%#x + ntoc=%#x vs cbFile=%#RX64",
+ u.pDySymTab->tocoff, u.pDySymTab->ntoc, cbFile));
+ const uint32_t cbModTabEntry = pHdr->magic == IMAGE_MACHO32_SIGNATURE
+ || pHdr->magic == IMAGE_MACHO32_SIGNATURE_OE
+ ? sizeof(dylib_module_32_t) : sizeof(dylib_module_64_t);
+ RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->modtaboff + u.pDySymTab->nmodtab * cbModTabEntry <= cbFile,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "modtaboff=%#x + nmodtab=%#x cbModTabEntry=%#x vs cbFile=%#RX64",
+ u.pDySymTab->modtaboff, u.pDySymTab->nmodtab, cbModTabEntry, cbFile));
+ RTLDRMODMACHO_CHECK_RETURN( (uint64_t)u.pDySymTab->extrefsymoff + u.pDySymTab->nextrefsym * sizeof(dylib_reference_t)
+ <= cbFile,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "extrefsymoff=%#x + nextrefsym=%#x vs cbFile=%#RX64",
+ u.pDySymTab->extrefsymoff, u.pDySymTab->nextrefsym, cbFile));
+ RTLDRMODMACHO_CHECK_RETURN( (uint64_t)u.pDySymTab->indirectsymboff + u.pDySymTab->nindirectsymb * sizeof(uint32_t)
+ <= cbFile,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "indirectsymboff=%#x + nindirectsymb=%#x vs cbFile=%#RX64",
+ u.pDySymTab->indirectsymboff, u.pDySymTab->nindirectsymb, cbFile));
+ RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->extreloff + u.pDySymTab->nextrel * sizeof(macho_relocation_info_t) <= cbFile,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "extreloff=%#x + nextrel=%#x vs cbFile=%#RX64",
+ u.pDySymTab->extreloff, u.pDySymTab->nextrel, cbFile));
+ RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->locreloff + u.pDySymTab->nlocrel * sizeof(macho_relocation_info_t) <= cbFile,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "locreloff=%#x + nlocrel=%#x vs cbFile=%#RX64",
+ u.pDySymTab->locreloff, u.pDySymTab->nlocrel, cbFile));
+ cDySymbolTabs++;
+ fDySymbolTabWithRelocs |= (u.pDySymTab->nlocrel + u.pDySymTab->nextrel) != 0;
+ break;
+ }
+
+ case LC_THREAD:
+ case LC_UNIXTHREAD:
+ {
+ uint32_t *pu32 = (uint32_t *)(u.pb + sizeof(load_command_t));
+ uint32_t cItemsLeft = (u.pThread->cmdsize - sizeof(load_command_t)) / sizeof(uint32_t);
+ while (cItemsLeft)
+ {
+ /* convert & verify header items ([0] == flavor, [1] == uint32_t count). */
+ if (cItemsLeft < 2)
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND);
+ if (fConvertEndian)
+ {
+ pu32[0] = RT_BSWAP_U32(pu32[0]);
+ pu32[1] = RT_BSWAP_U32(pu32[1]);
+ }
+ if (pu32[1] + 2 > cItemsLeft)
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND);
+
+ /* convert & verify according to flavor. */
+ switch (pu32[0])
+ {
+ /** @todo */
+ default:
+ break;
+ }
+
+ /* next */
+ cItemsLeft -= pu32[1] + 2;
+ pu32 += pu32[1] + 2;
+ }
+ break;
+ }
+
+ case LC_UUID:
+ if (u.pUuid->cmdsize != sizeof(uuid_command_t))
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND);
+ /** @todo Check anything here need converting? */
+ break;
+
+ case LC_CODE_SIGNATURE:
+ if (u.pUuid->cmdsize != sizeof(linkedit_data_command_t))
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND);
+ break;
+
+ case LC_VERSION_MIN_MACOSX:
+ case LC_VERSION_MIN_IPHONEOS:
+ if (u.pUuid->cmdsize != sizeof(version_min_command_t))
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND);
+ break;
+
+ case LC_SOURCE_VERSION: /* Harmless. It just gives a clue regarding the source code revision/version. */
+ case LC_BUILD_VERSION: /* Harmless. It just gives a clue regarding the tool/sdk versions. */
+ case LC_DATA_IN_CODE: /* Ignore */
+ case LC_DYLIB_CODE_SIGN_DRS:/* Ignore */
+ /** @todo valid command size. */
+ break;
+
+ case LC_FUNCTION_STARTS: /** @todo dylib++ */
+ /* Ignore for now. */
+ break;
+ case LC_ID_DYLIB: /** @todo dylib */
+ case LC_LOAD_DYLIB: /** @todo dylib */
+ case LC_LOAD_DYLINKER: /** @todo dylib */
+ case LC_TWOLEVEL_HINTS: /** @todo dylib */
+ case LC_LOAD_WEAK_DYLIB: /** @todo dylib */
+ case LC_ID_DYLINKER: /** @todo dylib */
+ case LC_RPATH: /** @todo dylib */
+ case LC_SEGMENT_SPLIT_INFO: /** @todo dylib++ */
+ case LC_REEXPORT_DYLIB: /** @todo dylib */
+ case LC_DYLD_INFO: /** @todo dylib */
+ case LC_DYLD_INFO_ONLY: /** @todo dylib */
+ case LC_LOAD_UPWARD_DYLIB: /** @todo dylib */
+ case LC_DYLD_ENVIRONMENT: /** @todo dylib */
+ case LC_MAIN: /** @todo parse this and find and entry point or smth. */
+ /** @todo valid command size. */
+ if (!(fOpenFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)))
+ RTLDRMODMACHO_FAILED_RETURN(RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_UNSUPPORTED_LOAD_COMMAND,
+ "cmd=%#x", u.pLoadCmd->cmd));
+ Log(("ldrMachO: Can't load because of load command: %#x\n", u.pLoadCmd->cmd));
+ *pfCanLoad = false;
+ break;
+
+ case LC_LOADFVMLIB:
+ case LC_IDFVMLIB:
+ case LC_IDENT:
+ case LC_FVMFILE:
+ case LC_PREPAGE:
+ case LC_PREBOUND_DYLIB:
+ case LC_ROUTINES:
+ case LC_ROUTINES_64:
+ case LC_SUB_FRAMEWORK:
+ case LC_SUB_UMBRELLA:
+ case LC_SUB_CLIENT:
+ case LC_SUB_LIBRARY:
+ case LC_PREBIND_CKSUM:
+ case LC_SYMSEG:
+ RTLDRMODMACHO_FAILED_RETURN(RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_UNSUPPORTED_LOAD_COMMAND,
+ "cmd=%#x", u.pLoadCmd->cmd));
+
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_UNKNOWN_LOAD_COMMAND,
+ "cmd=%#x", u.pLoadCmd->cmd));
+ }
+ }
+
+ /* be strict. */
+ if (cbLeft)
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND);
+
+ RTLDRMODMACHO_CHECK_RETURN(cDySymbolTabs <= 1,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "More than one LC_DYSYMTAB command: %u", cDySymbolTabs));
+ RTLDRMODMACHO_CHECK_RETURN(!fDySymbolTabWithRelocs || cSectionsWithRelocs == 0,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "Have relocations both in sections and LC_DYSYMTAB"));
+ if (!cSegments)
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_OBJECT_FILE);
+
+ switch (uEffFileType)
+ {
+ case MH_OBJECT:
+ case MH_EXECUTE:
+ RTLDRMODMACHO_CHECK_RETURN(!fDySymbolTabWithRelocs || (fOpenFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)),
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "Did not expect relocations in LC_DYSYMTAB (file type %u)", uEffFileType));
+ break;
+
+ case MH_DYLIB:
+ case MH_BUNDLE:
+ case MH_KEXT_BUNDLE:
+ RTLDRMODMACHO_CHECK_RETURN(cDySymbolTabs > 0,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "No LC_DYSYMTAB command (file type %u)", uEffFileType));
+ RTLDRMODMACHO_CHECK_RETURN(fDySymbolTabWithRelocs || cSectionsWithRelocs == 0,
+ RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND,
+ "Expected relocations in LC_DYSYMTAB (file type %u)", uEffFileType));
+ break;
+
+ case MH_DSYM:
+ break;
+ }
+
+ /*
+ * Set return values and return.
+ */
+ *pcSegments = cSegments;
+ *pcSections = cSections;
+ *pcbStringPool = (uint32_t)cbStringPool;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses the load commands after we've carved out the module instance.
+ *
+ * This fills in the segment table and perhaps some other properties.
+ *
+ * @returns IPRT status code.
+ * @param pThis The module.
+ * @param pbStringPool The string pool
+ * @param cbStringPool The size of the string pool.
+ */
+static int kldrModMachOParseLoadCommands(PRTLDRMODMACHO pThis, char *pbStringPool, uint32_t cbStringPool)
+{
+ union
+ {
+ const uint8_t *pb;
+ const load_command_t *pLoadCmd;
+ const segment_command_32_t *pSeg32;
+ const segment_command_64_t *pSeg64;
+ const symtab_command_t *pSymTab;
+ const uuid_command_t *pUuid;
+ const linkedit_data_command_t *pData;
+ } u;
+ uint32_t cLeft = pThis->Hdr.ncmds;
+ uint32_t cbLeft = pThis->Hdr.sizeofcmds;
+ const uint8_t *pb = pThis->pbLoadCommands;
+ PRTLDRMODMACHOSEG pDstSeg = &pThis->aSegments[0];
+ PRTLDRMODMACHOSECT pSectExtra = pThis->paSections;
+ const uint32_t cSegments = pThis->cSegments;
+ PRTLDRMODMACHOSEG pSegItr;
+ bool fFirstSeg = true;
+ RT_NOREF(cbStringPool);
+
+ while (cLeft-- > 0)
+ {
+ u.pb = pb;
+ cbLeft -= u.pLoadCmd->cmdsize;
+ pb += u.pLoadCmd->cmdsize;
+
+ /*
+ * Convert endian if needed, parse and validate the command.
+ */
+ switch (u.pLoadCmd->cmd)
+ {
+ case LC_SEGMENT_32:
+ {
+ const segment_command_32_t *pSrcSeg = (const segment_command_32_t *)u.pLoadCmd;
+ section_32_t *pFirstSect = (section_32_t *)(pSrcSeg + 1);
+ section_32_t *pSect = pFirstSect;
+ uint32_t cSectionsLeft = pSrcSeg->nsects;
+
+ /* Adds a segment, used by the macro below and thus shared with the 64-bit segment variant. */
+#define NEW_SEGMENT(a_cBits, a_achName1, a_fObjFile, a_achName2, a_SegAddr, a_cbSeg, a_fFileBits, a_offFile, a_cbFile) \
+ do { \
+ pDstSeg->SegInfo.pszName = pbStringPool; \
+ pDstSeg->SegInfo.cchName = (uint32_t)RTStrNLen(a_achName1, sizeof(a_achName1)); \
+ memcpy(pbStringPool, a_achName1, pDstSeg->SegInfo.cchName); \
+ pbStringPool += pDstSeg->SegInfo.cchName; \
+ if (a_fObjFile) \
+ { /* MH_OBJECT: Add '.sectname' - sections aren't sorted by segments. */ \
+ size_t cchName2 = RTStrNLen(a_achName2, sizeof(a_achName2)); \
+ *pbStringPool++ = '.'; \
+ memcpy(pbStringPool, a_achName2, cchName2); \
+ pbStringPool += cchName2; \
+ pDstSeg->SegInfo.cchName += (uint32_t)cchName2; \
+ } \
+ *pbStringPool++ = '\0'; \
+ pDstSeg->SegInfo.SelFlat = 0; \
+ pDstSeg->SegInfo.Sel16bit = 0; \
+ pDstSeg->SegInfo.fFlags = 0; \
+ pDstSeg->SegInfo.fProt = RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC; /** @todo fixme! */ \
+ pDstSeg->SegInfo.cb = (a_cbSeg); \
+ pDstSeg->SegInfo.Alignment = 1; /* updated while parsing sections. */ \
+ pDstSeg->SegInfo.LinkAddress = (a_SegAddr); \
+ if (a_fFileBits) \
+ { \
+ pDstSeg->SegInfo.offFile = (RTFOFF)((a_offFile) + pThis->offImage); \
+ pDstSeg->SegInfo.cbFile = (RTFOFF)(a_cbFile); \
+ } \
+ else \
+ { \
+ pDstSeg->SegInfo.offFile = -1; \
+ pDstSeg->SegInfo.cbFile = -1; \
+ } \
+ pDstSeg->SegInfo.RVA = (a_SegAddr) - pThis->LinkAddress; \
+ pDstSeg->SegInfo.cbMapped = 0; \
+ \
+ pDstSeg->iOrgSegNo = (uint32_t)(pDstSeg - &pThis->aSegments[0]); \
+ pDstSeg->cSections = 0; \
+ pDstSeg->paSections = pSectExtra; \
+ } while (0)
+
+ /* Closes the new segment - part of NEW_SEGMENT. */
+#define CLOSE_SEGMENT() \
+ do { \
+ pDstSeg->cSections = (uint32_t)(pSectExtra - pDstSeg->paSections); \
+ pDstSeg++; \
+ } while (0)
+
+
+ /* Shared with the 64-bit variant. */
+#define ADD_SEGMENT_AND_ITS_SECTIONS(a_cBits) \
+ do { \
+ bool fAddSegOuter = false; \
+ \
+ /* \
+ * Check that the segment name is unique. We couldn't do that \
+ * in the preparsing stage. \
+ */ \
+ if (pThis->uEffFileType != MH_OBJECT) \
+ for (pSegItr = &pThis->aSegments[0]; pSegItr != pDstSeg; pSegItr++) \
+ if (!strncmp(pSegItr->SegInfo.pszName, pSrcSeg->segname, sizeof(pSrcSeg->segname))) \
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_DUPLICATE_SEGMENT_NAME); \
+ \
+ /* \
+ * Create a new segment, unless we're supposed to skip this one. \
+ */ \
+ if ( pThis->uEffFileType != MH_OBJECT \
+ && (cSectionsLeft == 0 || !(pFirstSect->flags & S_ATTR_DEBUG)) \
+ && strcmp(pSrcSeg->segname, "__DWARF") \
+ && strcmp(pSrcSeg->segname, "__CTF") ) \
+ { \
+ NEW_SEGMENT(a_cBits, pSrcSeg->segname, false /*a_fObjFile*/, "" /*a_achName2*/, \
+ pSrcSeg->vmaddr, pSrcSeg->vmsize, \
+ pSrcSeg->filesize != 0, pSrcSeg->fileoff, pSrcSeg->filesize); \
+ fAddSegOuter = true; \
+ } \
+ \
+ /* \
+ * Convert and parse the sections. \
+ */ \
+ while (cSectionsLeft-- > 0) \
+ { \
+ /* New segment if object file. */ \
+ bool fAddSegInner = false; \
+ if ( pThis->uEffFileType == MH_OBJECT \
+ && !(pSect->flags & S_ATTR_DEBUG) \
+ && strcmp(pSrcSeg->segname, "__DWARF") \
+ && strcmp(pSrcSeg->segname, "__CTF") ) \
+ { \
+ Assert(!fAddSegOuter); \
+ NEW_SEGMENT(a_cBits, pSect->segname, true /*a_fObjFile*/, pSect->sectname, \
+ pSect->addr, pSect->size, \
+ pSect->offset != 0, pSect->offset, pSect->size); \
+ fAddSegInner = true; \
+ } \
+ \
+ /* Section data extract. */ \
+ pSectExtra->cb = pSect->size; \
+ pSectExtra->RVA = pSect->addr - pDstSeg->SegInfo.LinkAddress; \
+ pSectExtra->LinkAddress = pSect->addr; \
+ if (pSect->offset) \
+ pSectExtra->offFile = pSect->offset + pThis->offImage; \
+ else \
+ pSectExtra->offFile = -1; \
+ pSectExtra->cFixups = pSect->nreloc; \
+ pSectExtra->paFixups = NULL; \
+ pSectExtra->pauFixupVirginData = NULL; \
+ if (pSect->nreloc) \
+ pSectExtra->offFixups = pSect->reloff + pThis->offImage; \
+ else \
+ pSectExtra->offFixups = -1; \
+ pSectExtra->fFlags = pSect->flags; \
+ pSectExtra->iSegment = (uint32_t)(pDstSeg - &pThis->aSegments[0]); \
+ pSectExtra->pvMachoSection = pSect; \
+ \
+ /* Update the segment alignment, if we're not skipping it. */ \
+ if ( (fAddSegOuter || fAddSegInner) \
+ && pDstSeg->SegInfo.Alignment < ((RTLDRADDR)1 << pSect->align)) \
+ pDstSeg->SegInfo.Alignment = (RTLDRADDR)1 << pSect->align; \
+ \
+ /* Next section, and if object file next segment. */ \
+ pSectExtra++; \
+ pSect++; \
+ if (fAddSegInner) \
+ CLOSE_SEGMENT(); \
+ } \
+ \
+ /* Close the segment and advance. */ \
+ if (fAddSegOuter) \
+ CLOSE_SEGMENT(); \
+ \
+ /* Take down 'execSeg' info for signing */ \
+ if (fFirstSeg) \
+ { \
+ fFirstSeg = false; \
+ pThis->offSeg0ForCodeSign = pSrcSeg->fileoff; \
+ pThis->cbSeg0ForCodeSign = pSrcSeg->filesize; /** @todo file or vm size? */ \
+ pThis->fSeg0ForCodeSign = pSrcSeg->flags; \
+ } \
+ } while (0) /* ADD_SEGMENT_AND_ITS_SECTIONS */
+
+ ADD_SEGMENT_AND_ITS_SECTIONS(32);
+ break;
+ }
+
+ case LC_SEGMENT_64:
+ {
+ const segment_command_64_t *pSrcSeg = (const segment_command_64_t *)u.pLoadCmd;
+ section_64_t *pFirstSect = (section_64_t *)(pSrcSeg + 1);
+ section_64_t *pSect = pFirstSect;
+ uint32_t cSectionsLeft = pSrcSeg->nsects;
+
+ ADD_SEGMENT_AND_ITS_SECTIONS(64);
+ break;
+ }
+
+ case LC_SYMTAB:
+ switch (pThis->uEffFileType)
+ {
+ case MH_OBJECT:
+ case MH_EXECUTE:
+ case MH_DYLIB:
+ case MH_BUNDLE:
+ case MH_DSYM:
+ case MH_KEXT_BUNDLE:
+ pThis->offSymbols = u.pSymTab->symoff + pThis->offImage;
+ pThis->cSymbols = u.pSymTab->nsyms;
+ pThis->offStrings = u.pSymTab->stroff + pThis->offImage;
+ pThis->cchStrings = u.pSymTab->strsize;
+ break;
+ }
+ break;
+
+ case LC_DYSYMTAB:
+ pThis->pDySymTab = (dysymtab_command_t *)u.pb;
+ break;
+
+ case LC_UUID:
+ memcpy(pThis->abImageUuid, u.pUuid->uuid, sizeof(pThis->abImageUuid));
+ break;
+
+ case LC_CODE_SIGNATURE:
+ pThis->offCodeSignature = u.pData->dataoff;
+ pThis->cbCodeSignature = u.pData->datasize;
+ break;
+
+ default:
+ break;
+ } /* command switch */
+ } /* while more commands */
+
+ Assert(pDstSeg == &pThis->aSegments[cSegments - pThis->fMakeGot]);
+
+ /*
+ * Adjust mapping addresses calculating the image size.
+ */
+ {
+ bool fLoadLinkEdit = RT_BOOL(pThis->fOpenFlags & RTLDR_O_MACHO_LOAD_LINKEDIT);
+ PRTLDRMODMACHOSECT pSectExtraItr;
+ RTLDRADDR uNextRVA = 0;
+ RTLDRADDR cb;
+ uint32_t cSegmentsToAdjust = cSegments - pThis->fMakeGot;
+ uint32_t c;
+
+ for (;;)
+ {
+ /* Check if there is __DWARF segment at the end and make sure it's left
+ out of the RVA negotiations and image loading. */
+ if ( cSegmentsToAdjust > 0
+ && !strcmp(pThis->aSegments[cSegmentsToAdjust - 1].SegInfo.pszName, "__DWARF"))
+ {
+ cSegmentsToAdjust--;
+ pThis->aSegments[cSegmentsToAdjust].SegInfo.RVA = NIL_RTLDRADDR;
+ pThis->aSegments[cSegmentsToAdjust].SegInfo.cbMapped = NIL_RTLDRADDR;
+ continue;
+ }
+
+ /* If we're skipping the __LINKEDIT segment, check for it and adjust
+ the number of segments we'll be messing with here. ASSUMES it's
+ last (typcially is, but not always for mach_kernel). */
+ if ( !fLoadLinkEdit
+ && cSegmentsToAdjust > 0
+ && !strcmp(pThis->aSegments[cSegmentsToAdjust - 1].SegInfo.pszName, "__LINKEDIT"))
+ {
+ cSegmentsToAdjust--;
+ pThis->aSegments[cSegmentsToAdjust].SegInfo.RVA = NIL_RTLDRADDR;
+ pThis->aSegments[cSegmentsToAdjust].SegInfo.cbMapped = NIL_RTLDRADDR;
+ continue;
+ }
+ break;
+ }
+
+ /* Adjust RVAs. */
+ c = cSegmentsToAdjust;
+ for (pDstSeg = &pThis->aSegments[0]; c-- > 0; pDstSeg++)
+ {
+ uNextRVA = RTLDR_ALIGN_ADDR(uNextRVA, pDstSeg->SegInfo.Alignment);
+ cb = pDstSeg->SegInfo.RVA - uNextRVA;
+ if (cb >= 0x00100000) /* 1MB */
+ {
+ pDstSeg->SegInfo.RVA = uNextRVA;
+ //pThis->pMod->fFlags |= KLDRMOD_FLAGS_NON_CONTIGUOUS_LINK_ADDRS;
+ }
+ uNextRVA = pDstSeg->SegInfo.RVA + pDstSeg->SegInfo.cb;
+ }
+
+ /* Calculate the cbMapping members. */
+ c = cSegmentsToAdjust;
+ for (pDstSeg = &pThis->aSegments[0]; c-- > 1; pDstSeg++)
+ {
+
+ cb = pDstSeg[1].SegInfo.RVA - pDstSeg->SegInfo.RVA;
+ pDstSeg->SegInfo.cbMapped = (size_t)cb == cb ? (size_t)cb : ~(size_t)0;
+ }
+
+ cb = RTLDR_ALIGN_ADDR(pDstSeg->SegInfo.cb, pDstSeg->SegInfo.Alignment);
+ pDstSeg->SegInfo.cbMapped = (size_t)cb == cb ? (size_t)cb : ~(size_t)0;
+
+ /* Set the image size. */
+ pThis->cbImage = pDstSeg->SegInfo.RVA + cb;
+
+ /* Fixup the section RVAs (internal). */
+ c = cSegmentsToAdjust;
+ uNextRVA = pThis->cbImage;
+ pDstSeg = &pThis->aSegments[0];
+ for (pSectExtraItr = pThis->paSections; pSectExtraItr != pSectExtra; pSectExtraItr++)
+ {
+ if (pSectExtraItr->iSegment < c)
+ pSectExtraItr->RVA += pDstSeg[pSectExtraItr->iSegment].SegInfo.RVA;
+ else
+ {
+ pSectExtraItr->RVA = uNextRVA;
+ uNextRVA += RTLDR_ALIGN_ADDR(pSectExtraItr->cb, 64);
+ }
+ }
+ }
+
+ /*
+ * Make the GOT segment if necessary.
+ */
+ if (pThis->fMakeGot)
+ {
+ uint32_t cbPtr = ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ ? sizeof(uint32_t)
+ : sizeof(uint64_t);
+ uint32_t cbGot = pThis->cSymbols * cbPtr;
+ uint32_t cbJmpStubs;
+
+ pThis->GotRVA = pThis->cbImage;
+
+ if (pThis->cbJmpStub)
+ {
+ cbGot = RT_ALIGN_Z(cbGot, 64);
+ pThis->JmpStubsRVA = pThis->GotRVA + cbGot;
+ cbJmpStubs = pThis->cbJmpStub * pThis->cSymbols;
+ }
+ else
+ {
+ pThis->JmpStubsRVA = NIL_RTLDRADDR;
+ cbJmpStubs = 0;
+ }
+
+ pDstSeg = &pThis->aSegments[cSegments - 1];
+ pDstSeg->SegInfo.pszName = "GOT";
+ pDstSeg->SegInfo.cchName = 3;
+ pDstSeg->SegInfo.SelFlat = 0;
+ pDstSeg->SegInfo.Sel16bit = 0;
+ pDstSeg->SegInfo.fFlags = 0;
+ pDstSeg->SegInfo.fProt = RTMEM_PROT_READ;
+ pDstSeg->SegInfo.cb = cbGot + cbJmpStubs;
+ pDstSeg->SegInfo.Alignment = 64;
+ pDstSeg->SegInfo.LinkAddress = pThis->LinkAddress + pThis->GotRVA;
+ pDstSeg->SegInfo.offFile = -1;
+ pDstSeg->SegInfo.cbFile = -1;
+ pDstSeg->SegInfo.RVA = pThis->GotRVA;
+ pDstSeg->SegInfo.cbMapped = (size_t)RTLDR_ALIGN_ADDR(cbGot + cbJmpStubs, pDstSeg->SegInfo.Alignment);
+
+ pDstSeg->iOrgSegNo = UINT32_MAX;
+ pDstSeg->cSections = 0;
+ pDstSeg->paSections = NULL;
+
+ pThis->cbImage += pDstSeg->SegInfo.cbMapped;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtldrMachO_Close(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ RTLDRMODMACHO_ASSERT(!pThis->pvMapping);
+
+ uint32_t i = pThis->cSegments;
+ while (i-- > 0)
+ {
+ uint32_t j = pThis->aSegments[i].cSections;
+ while (j-- > 0)
+ {
+ RTMemFree(pThis->aSegments[i].paSections[j].paFixups);
+ pThis->aSegments[i].paSections[j].paFixups = NULL;
+ RTMemFree(pThis->aSegments[i].paSections[j].pauFixupVirginData);
+ pThis->aSegments[i].paSections[j].pauFixupVirginData = NULL;
+ }
+ }
+
+ RTMemFree(pThis->pbLoadCommands);
+ pThis->pbLoadCommands = NULL;
+ RTMemFree(pThis->pchStrings);
+ pThis->pchStrings = NULL;
+ RTMemFree(pThis->pvaSymbols);
+ pThis->pvaSymbols = NULL;
+ RTMemFree(pThis->paidxIndirectSymbols);
+ pThis->paidxIndirectSymbols = NULL;
+ RTMemFree(pThis->paRelocations);
+ pThis->paRelocations = NULL;
+ RTMemFree(pThis->pauRelocationsVirginData);
+ pThis->pauRelocationsVirginData = NULL;
+ RTMemFree(pThis->PtrCodeSignature.pb);
+ pThis->PtrCodeSignature.pb = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Gets the right base address.
+ *
+ * @param pThis The interpreter module instance
+ * @param pBaseAddress The base address, IN & OUT. Optional.
+ */
+static void kldrModMachOAdjustBaseAddress(PRTLDRMODMACHO pThis, PRTLDRADDR pBaseAddress)
+{
+ /*
+ * Adjust the base address.
+ */
+ if (*pBaseAddress == RTLDR_BASEADDRESS_LINK)
+ *pBaseAddress = pThis->LinkAddress;
+}
+
+
+/**
+ * Resolves a linker generated symbol.
+ *
+ * The Apple linker generates symbols indicating the start and end of sections
+ * and segments. This function checks for these and returns the right value.
+ *
+ * @returns VINF_SUCCESS or VERR_SYMBOL_NOT_FOUND.
+ * @param pThis The interpreter module instance.
+ * @param pchSymbol The symbol.
+ * @param cchSymbol The length of the symbol.
+ * @param BaseAddress The base address to apply when calculating the
+ * value.
+ * @param puValue Where to return the symbol value.
+ */
+static int kldrModMachOQueryLinkerSymbol(PRTLDRMODMACHO pThis, const char *pchSymbol, size_t cchSymbol,
+ RTLDRADDR BaseAddress, PRTLDRADDR puValue)
+{
+ /*
+ * Match possible name prefixes.
+ */
+ static const struct
+ {
+ const char *pszPrefix;
+ uint32_t cchPrefix;
+ bool fSection;
+ bool fStart;
+ } s_aPrefixes[] =
+ {
+ { "section$start$", (uint8_t)sizeof("section$start$") - 1, true, true },
+ { "section$end$", (uint8_t)sizeof("section$end$") - 1, true, false},
+ { "segment$start$", (uint8_t)sizeof("segment$start$") - 1, false, true },
+ { "segment$end$", (uint8_t)sizeof("segment$end$") - 1, false, false},
+ };
+ size_t cchSectName = 0;
+ const char *pchSectName = "";
+ size_t cchSegName = 0;
+ const char *pchSegName = NULL;
+ uint32_t iPrefix = RT_ELEMENTS(s_aPrefixes) - 1;
+ uint32_t iSeg;
+ RTLDRADDR uValue;
+
+ for (;;)
+ {
+ uint8_t const cchPrefix = s_aPrefixes[iPrefix].cchPrefix;
+ if ( cchSymbol > cchPrefix
+ && strncmp(pchSymbol, s_aPrefixes[iPrefix].pszPrefix, cchPrefix) == 0)
+ {
+ pchSegName = pchSymbol + cchPrefix;
+ cchSegName = cchSymbol - cchPrefix;
+ break;
+ }
+
+ /* next */
+ if (!iPrefix)
+ return VERR_SYMBOL_NOT_FOUND;
+ iPrefix--;
+ }
+
+ /*
+ * Split the remainder into segment and section name, if necessary.
+ */
+ if (s_aPrefixes[iPrefix].fSection)
+ {
+ pchSectName = (const char *)memchr(pchSegName, '$', cchSegName);
+ if (!pchSectName)
+ return VERR_SYMBOL_NOT_FOUND;
+ cchSegName = pchSectName - pchSegName;
+ pchSectName++;
+ cchSectName = cchSymbol - (pchSectName - pchSymbol);
+ }
+
+ /*
+ * Locate the segment.
+ */
+ if (!pThis->cSegments)
+ return VERR_SYMBOL_NOT_FOUND;
+ for (iSeg = 0; iSeg < pThis->cSegments; iSeg++)
+ {
+ if ( pThis->aSegments[iSeg].SegInfo.cchName >= cchSegName
+ && memcmp(pThis->aSegments[iSeg].SegInfo.pszName, pchSegName, cchSegName) == 0)
+ {
+ section_32_t const *pSect;
+ if ( pThis->aSegments[iSeg].SegInfo.cchName == cchSegName
+ && pThis->Hdr.filetype != MH_OBJECT /* Good enough for __DWARF segs in MH_DHSYM, I hope. */)
+ break;
+
+ pSect = (section_32_t *)pThis->aSegments[iSeg].paSections[0].pvMachoSection;
+ if ( pThis->uEffFileType == MH_OBJECT
+ && pThis->aSegments[iSeg].SegInfo.cchName > cchSegName + 1
+ && pThis->aSegments[iSeg].SegInfo.pszName[cchSegName] == '.'
+ && strncmp(&pThis->aSegments[iSeg].SegInfo.pszName[cchSegName + 1], pSect->sectname, sizeof(pSect->sectname)) == 0
+ && pThis->aSegments[iSeg].SegInfo.cchName - cchSegName - 1 <= sizeof(pSect->sectname) )
+ break;
+ }
+ }
+ if (iSeg >= pThis->cSegments)
+ return VERR_SYMBOL_NOT_FOUND;
+
+ if (!s_aPrefixes[iPrefix].fSection)
+ {
+ /*
+ * Calculate the segment start/end address.
+ */
+ uValue = pThis->aSegments[iSeg].SegInfo.RVA;
+ if (!s_aPrefixes[iPrefix].fStart)
+ uValue += pThis->aSegments[iSeg].SegInfo.cb;
+ }
+ else
+ {
+ /*
+ * Locate the section.
+ */
+ uint32_t iSect = pThis->aSegments[iSeg].cSections;
+ if (!iSect)
+ return VERR_SYMBOL_NOT_FOUND;
+ for (;;)
+ {
+ section_32_t *pSect = (section_32_t *)pThis->aSegments[iSeg].paSections[iSect].pvMachoSection;
+ if ( cchSectName <= sizeof(pSect->sectname)
+ && memcmp(pSect->sectname, pchSectName, cchSectName) == 0
+ && ( cchSectName == sizeof(pSect->sectname)
+ || pSect->sectname[cchSectName] == '\0') )
+ break;
+ /* next */
+ if (!iSect)
+ return VERR_SYMBOL_NOT_FOUND;
+ iSect--;
+ }
+
+ uValue = pThis->aSegments[iSeg].paSections[iSect].RVA;
+ if (!s_aPrefixes[iPrefix].fStart)
+ uValue += pThis->aSegments[iSeg].paSections[iSect].cb;
+ }
+
+ /*
+ * Convert from RVA to load address.
+ */
+ uValue += BaseAddress;
+ if (puValue)
+ *puValue = uValue;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnGetSymbolEx}
+ */
+static DECLCALLBACK(int) rtldrMachO_GetSymbolEx(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress,
+ uint32_t iOrdinal, const char *pszSymbol, RTUINTPTR *pValue)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ RT_NOREF(pvBits);
+ //RT_NOREF(pszVersion);
+ //RT_NOREF(pfnGetForwarder);
+ //RT_NOREF(pvUser);
+ uint32_t fKind = RTLDRSYMKIND_REQ_FLAT;
+ uint32_t *pfKind = &fKind;
+ size_t cchSymbol = pszSymbol ? strlen(pszSymbol) : 0;
+
+ /*
+ * Resolve defaults.
+ */
+ kldrModMachOAdjustBaseAddress(pThis, &BaseAddress);
+
+ /*
+ * Refuse segmented requests for now.
+ */
+ RTLDRMODMACHO_CHECK_RETURN( !pfKind
+ || (*pfKind & RTLDRSYMKIND_REQ_TYPE_MASK) == RTLDRSYMKIND_REQ_FLAT,
+ VERR_LDRMACHO_TODO);
+
+ /*
+ * Take action according to file type.
+ */
+ int rc;
+ if ( pThis->Hdr.filetype == MH_OBJECT
+ || pThis->Hdr.filetype == MH_EXECUTE /** @todo dylib, execute, dsym: symbols */
+ || pThis->Hdr.filetype == MH_DYLIB
+ || pThis->Hdr.filetype == MH_BUNDLE
+ || pThis->Hdr.filetype == MH_DSYM
+ || pThis->Hdr.filetype == MH_KEXT_BUNDLE)
+ {
+ rc = kldrModMachOLoadObjSymTab(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ rc = kldrModMachODoQuerySymbol32Bit(pThis, (macho_nlist_32_t *)pThis->pvaSymbols, pThis->cSymbols,
+ pThis->pchStrings, pThis->cchStrings, BaseAddress, iOrdinal, pszSymbol,
+ (uint32_t)cchSymbol, pValue, pfKind);
+ else
+ rc = kldrModMachODoQuerySymbol64Bit(pThis, (macho_nlist_64_t *)pThis->pvaSymbols, pThis->cSymbols,
+ pThis->pchStrings, pThis->cchStrings, BaseAddress, iOrdinal, pszSymbol,
+ (uint32_t)cchSymbol, pValue, pfKind);
+ }
+
+ /*
+ * Check for link-editor generated symbols and supply what we can.
+ *
+ * As small service to clients that insists on adding a '_' prefix
+ * before querying symbols, we will ignore the prefix.
+ */
+ if ( rc == VERR_SYMBOL_NOT_FOUND
+ && cchSymbol > sizeof("section$end$") - 1
+ && ( pszSymbol[0] == 's'
+ || (pszSymbol[1] == 's' && pszSymbol[0] == '_') )
+ && memchr(pszSymbol, '$', cchSymbol) )
+ {
+ if (pszSymbol[0] == '_')
+ rc = kldrModMachOQueryLinkerSymbol(pThis, pszSymbol + 1, cchSymbol - 1, BaseAddress, pValue);
+ else
+ rc = kldrModMachOQueryLinkerSymbol(pThis, pszSymbol, cchSymbol, BaseAddress, pValue);
+ }
+ }
+ else
+ rc = VERR_LDRMACHO_TODO;
+
+ return rc;
+}
+
+
+/**
+ * Lookup a symbol in a 32-bit symbol table.
+ *
+ * @returns IPRT status code.
+ * @param pThis
+ * @param paSyms Pointer to the symbol table.
+ * @param cSyms Number of symbols in the table.
+ * @param pchStrings Pointer to the string table.
+ * @param cchStrings Size of the string table.
+ * @param BaseAddress Adjusted base address, see kLdrModQuerySymbol.
+ * @param iSymbol See kLdrModQuerySymbol.
+ * @param pchSymbol See kLdrModQuerySymbol.
+ * @param cchSymbol See kLdrModQuerySymbol.
+ * @param puValue See kLdrModQuerySymbol.
+ * @param pfKind See kLdrModQuerySymbol.
+ */
+static int kldrModMachODoQuerySymbol32Bit(PRTLDRMODMACHO pThis, const macho_nlist_32_t *paSyms, uint32_t cSyms,
+ const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t iSymbol,
+ const char *pchSymbol, uint32_t cchSymbol, PRTLDRADDR puValue, uint32_t *pfKind)
+{
+ /*
+ * Find a valid symbol matching the search criteria.
+ */
+ if (iSymbol == UINT32_MAX)
+ {
+ /* simplify validation. */
+ /** @todo figure out a better way to deal with underscore prefixes. sigh. */
+ if (cchStrings <= cchSymbol + 1)
+ return VERR_SYMBOL_NOT_FOUND;
+ cchStrings -= cchSymbol + 1;
+
+ /* external symbols are usually at the end, so search the other way. */
+ for (iSymbol = cSyms - 1; iSymbol != UINT32_MAX; iSymbol--)
+ {
+ const char *psz;
+
+ /* Skip irrellevant and non-public symbols. */
+ if (paSyms[iSymbol].n_type & MACHO_N_STAB)
+ continue;
+ if ((paSyms[iSymbol].n_type & MACHO_N_TYPE) == MACHO_N_UNDF)
+ continue;
+ if (!(paSyms[iSymbol].n_type & MACHO_N_EXT)) /*??*/
+ continue;
+ if (paSyms[iSymbol].n_type & MACHO_N_PEXT) /*??*/
+ continue;
+
+ /* get name */
+ if (!paSyms[iSymbol].n_un.n_strx)
+ continue;
+ if ((uint32_t)paSyms[iSymbol].n_un.n_strx >= cchStrings)
+ continue;
+ psz = &pchStrings[paSyms[iSymbol].n_un.n_strx];
+ if (psz[cchSymbol + 1])
+ continue;
+ /** @todo figure out a better way to deal with underscore prefixes. sigh. */
+ if (*psz != '_' || memcmp(psz + 1, pchSymbol, cchSymbol))
+ continue;
+
+ /* match! */
+ break;
+ }
+ if (iSymbol == UINT32_MAX)
+ return VERR_SYMBOL_NOT_FOUND;
+ }
+ else
+ {
+ if (iSymbol >= cSyms)
+ return VERR_SYMBOL_NOT_FOUND;
+ if (paSyms[iSymbol].n_type & MACHO_N_STAB)
+ return VERR_SYMBOL_NOT_FOUND;
+ if ((paSyms[iSymbol].n_type & MACHO_N_TYPE) == MACHO_N_UNDF)
+ return VERR_SYMBOL_NOT_FOUND;
+ }
+
+ /*
+ * Calc the return values.
+ */
+ if (pfKind)
+ {
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ *pfKind = RTLDRSYMKIND_32BIT | RTLDRSYMKIND_NO_TYPE;
+ else
+ *pfKind = RTLDRSYMKIND_64BIT | RTLDRSYMKIND_NO_TYPE;
+ if (paSyms[iSymbol].n_desc & N_WEAK_DEF)
+ *pfKind |= RTLDRSYMKIND_WEAK;
+ }
+
+ switch (paSyms[iSymbol].n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSect;
+ RTLDRADDR offSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)(paSyms[iSymbol].n_sect - 1) < pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSect = &pThis->paSections[paSyms[iSymbol].n_sect - 1];
+
+ offSect = paSyms[iSymbol].n_value - pSect->LinkAddress;
+ RTLDRMODMACHO_CHECK_RETURN( offSect <= pSect->cb
+ || ( paSyms[iSymbol].n_sect == 1 /* special hack for __mh_execute_header */
+ && offSect == 0U - pSect->RVA
+ && pThis->uEffFileType != MH_OBJECT),
+ VERR_LDRMACHO_BAD_SYMBOL);
+ if (puValue)
+ *puValue = BaseAddress + pSect->RVA + offSect;
+
+ if ( pfKind
+ && (pSect->fFlags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SELF_MODIFYING_CODE)))
+ *pfKind = (*pfKind & ~RTLDRSYMKIND_TYPE_MASK) | RTLDRSYMKIND_CODE;
+ break;
+ }
+
+ case MACHO_N_ABS:
+ if (puValue)
+ *puValue = paSyms[iSymbol].n_value;
+ /*if (pfKind)
+ pfKind |= RTLDRSYMKIND_ABS;*/
+ break;
+
+ case MACHO_N_PBUD:
+ case MACHO_N_INDR:
+ /** @todo implement indirect and prebound symbols. */
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Lookup a symbol in a 64-bit symbol table.
+ *
+ * @returns IPRT status code.
+ * @param pThis
+ * @param paSyms Pointer to the symbol table.
+ * @param cSyms Number of symbols in the table.
+ * @param pchStrings Pointer to the string table.
+ * @param cchStrings Size of the string table.
+ * @param BaseAddress Adjusted base address, see kLdrModQuerySymbol.
+ * @param iSymbol See kLdrModQuerySymbol.
+ * @param pchSymbol See kLdrModQuerySymbol.
+ * @param cchSymbol See kLdrModQuerySymbol.
+ * @param puValue See kLdrModQuerySymbol.
+ * @param pfKind See kLdrModQuerySymbol.
+ */
+static int kldrModMachODoQuerySymbol64Bit(PRTLDRMODMACHO pThis, const macho_nlist_64_t *paSyms, uint32_t cSyms,
+ const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t iSymbol,
+ const char *pchSymbol, uint32_t cchSymbol, PRTLDRADDR puValue, uint32_t *pfKind)
+{
+ /*
+ * Find a valid symbol matching the search criteria.
+ */
+ if (iSymbol == UINT32_MAX)
+ {
+ /* simplify validation. */
+ /** @todo figure out a better way to deal with underscore prefixes. sigh. */
+ if (cchStrings <= cchSymbol + 1)
+ return VERR_SYMBOL_NOT_FOUND;
+ cchStrings -= cchSymbol + 1;
+
+ /* external symbols are usually at the end, so search the other way. */
+ for (iSymbol = cSyms - 1; iSymbol != UINT32_MAX; iSymbol--)
+ {
+ const char *psz;
+
+ /* Skip irrellevant and non-public symbols. */
+ if (paSyms[iSymbol].n_type & MACHO_N_STAB)
+ continue;
+ if ((paSyms[iSymbol].n_type & MACHO_N_TYPE) == MACHO_N_UNDF)
+ continue;
+ if (!(paSyms[iSymbol].n_type & MACHO_N_EXT)) /*??*/
+ continue;
+ if (paSyms[iSymbol].n_type & MACHO_N_PEXT) /*??*/
+ continue;
+
+ /* get name */
+ if (!paSyms[iSymbol].n_un.n_strx)
+ continue;
+ if ((uint32_t)paSyms[iSymbol].n_un.n_strx >= cchStrings)
+ continue;
+ psz = &pchStrings[paSyms[iSymbol].n_un.n_strx];
+ if (psz[cchSymbol + 1])
+ continue;
+ if (*psz != '_' || memcmp(psz + 1, pchSymbol, cchSymbol))
+ continue;
+
+ /* match! */
+ break;
+ }
+ if (iSymbol == UINT32_MAX)
+ return VERR_SYMBOL_NOT_FOUND;
+ }
+ else
+ {
+ if (iSymbol >= cSyms)
+ return VERR_SYMBOL_NOT_FOUND;
+ if (paSyms[iSymbol].n_type & MACHO_N_STAB)
+ return VERR_SYMBOL_NOT_FOUND;
+ if ((paSyms[iSymbol].n_type & MACHO_N_TYPE) == MACHO_N_UNDF)
+ return VERR_SYMBOL_NOT_FOUND;
+ }
+
+ /*
+ * Calc the return values.
+ */
+ if (pfKind)
+ {
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ *pfKind = RTLDRSYMKIND_32BIT | RTLDRSYMKIND_NO_TYPE;
+ else
+ *pfKind = RTLDRSYMKIND_64BIT | RTLDRSYMKIND_NO_TYPE;
+ if (paSyms[iSymbol].n_desc & N_WEAK_DEF)
+ *pfKind |= RTLDRSYMKIND_WEAK;
+ }
+
+ switch (paSyms[iSymbol].n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSect;
+ RTLDRADDR offSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)(paSyms[iSymbol].n_sect - 1) < pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSect = &pThis->paSections[paSyms[iSymbol].n_sect - 1];
+
+ offSect = paSyms[iSymbol].n_value - pSect->LinkAddress;
+ RTLDRMODMACHO_CHECK_RETURN( offSect <= pSect->cb
+ || ( paSyms[iSymbol].n_sect == 1 /* special hack for __mh_execute_header */
+ && offSect == 0U - pSect->RVA
+ && pThis->uEffFileType != MH_OBJECT),
+ VERR_LDRMACHO_BAD_SYMBOL);
+ if (puValue)
+ *puValue = BaseAddress + pSect->RVA + offSect;
+
+ if ( pfKind
+ && (pSect->fFlags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SELF_MODIFYING_CODE)))
+ *pfKind = (*pfKind & ~RTLDRSYMKIND_TYPE_MASK) | RTLDRSYMKIND_CODE;
+ break;
+ }
+
+ case MACHO_N_ABS:
+ if (puValue)
+ *puValue = paSyms[iSymbol].n_value;
+ /*if (pfKind)
+ pfKind |= RTLDRSYMKIND_ABS;*/
+ break;
+
+ case MACHO_N_PBUD:
+ case MACHO_N_INDR:
+ /** @todo implement indirect and prebound symbols. */
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnEnumSymbols}
+ */
+static DECLCALLBACK(int) rtldrMachO_EnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits,
+ RTUINTPTR BaseAddress, PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ RT_NOREF(pvBits);
+
+ /*
+ * Resolve defaults.
+ */
+ kldrModMachOAdjustBaseAddress(pThis, &BaseAddress);
+
+ /*
+ * Take action according to file type.
+ */
+ int rc;
+ if ( pThis->Hdr.filetype == MH_OBJECT
+ || pThis->Hdr.filetype == MH_EXECUTE /** @todo dylib, execute, dsym: symbols */
+ || pThis->Hdr.filetype == MH_DYLIB
+ || pThis->Hdr.filetype == MH_BUNDLE
+ || pThis->Hdr.filetype == MH_DSYM
+ || pThis->Hdr.filetype == MH_KEXT_BUNDLE)
+ {
+ rc = kldrModMachOLoadObjSymTab(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ rc = kldrModMachODoEnumSymbols32Bit(pThis, (macho_nlist_32_t *)pThis->pvaSymbols, pThis->cSymbols,
+ pThis->pchStrings, pThis->cchStrings, BaseAddress,
+ fFlags, pfnCallback, pvUser);
+ else
+ rc = kldrModMachODoEnumSymbols64Bit(pThis, (macho_nlist_64_t *)pThis->pvaSymbols, pThis->cSymbols,
+ pThis->pchStrings, pThis->cchStrings, BaseAddress,
+ fFlags, pfnCallback, pvUser);
+ }
+ }
+ else
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+
+ return rc;
+}
+
+
+/**
+ * Enum a 32-bit symbol table.
+ *
+ * @returns IPRT status code.
+ * @param pThis
+ * @param paSyms Pointer to the symbol table.
+ * @param cSyms Number of symbols in the table.
+ * @param pchStrings Pointer to the string table.
+ * @param cchStrings Size of the string table.
+ * @param BaseAddress Adjusted base address, see kLdrModEnumSymbols.
+ * @param fFlags See kLdrModEnumSymbols.
+ * @param pfnCallback See kLdrModEnumSymbols.
+ * @param pvUser See kLdrModEnumSymbols.
+ */
+static int kldrModMachODoEnumSymbols32Bit(PRTLDRMODMACHO pThis, const macho_nlist_32_t *paSyms, uint32_t cSyms,
+ const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress,
+ uint32_t fFlags, PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ const uint32_t fKindBase = pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE
+ ? RTLDRSYMKIND_32BIT : RTLDRSYMKIND_64BIT;
+ uint32_t iSym;
+ int rc;
+
+ /*
+ * Iterate the symbol table.
+ */
+ for (iSym = 0; iSym < cSyms; iSym++)
+ {
+ uint32_t fKind;
+ RTLDRADDR uValue;
+ const char *psz;
+ size_t cch;
+
+ /* Skip debug symbols and undefined symbols. */
+ if (paSyms[iSym].n_type & MACHO_N_STAB)
+ continue;
+ if ((paSyms[iSym].n_type & MACHO_N_TYPE) == MACHO_N_UNDF)
+ continue;
+
+ /* Skip non-public symbols unless they are requested explicitly. */
+ if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_ALL))
+ {
+ if (!(paSyms[iSym].n_type & MACHO_N_EXT)) /*??*/
+ continue;
+ if (paSyms[iSym].n_type & MACHO_N_PEXT) /*??*/
+ continue;
+ if (!paSyms[iSym].n_un.n_strx)
+ continue;
+ }
+
+ /*
+ * Gather symbol info
+ */
+
+ /* name */
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)paSyms[iSym].n_un.n_strx < cchStrings, VERR_LDRMACHO_BAD_SYMBOL);
+ psz = &pchStrings[paSyms[iSym].n_un.n_strx];
+ cch = strlen(psz);
+ if (!cch)
+ psz = NULL;
+
+ /* kind & value */
+ fKind = fKindBase;
+ if (paSyms[iSym].n_desc & N_WEAK_DEF)
+ fKind |= RTLDRSYMKIND_WEAK;
+ switch (paSyms[iSym].n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)(paSyms[iSym].n_sect - 1) < pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSect = &pThis->paSections[paSyms[iSym].n_sect - 1];
+
+ uValue = paSyms[iSym].n_value - pSect->LinkAddress;
+ RTLDRMODMACHO_CHECK_RETURN( uValue <= pSect->cb
+ || ( paSyms[iSym].n_sect == 1 /* special hack for __mh_execute_header */
+ && uValue == 0U - pSect->RVA
+ && pThis->uEffFileType != MH_OBJECT),
+ VERR_LDRMACHO_BAD_SYMBOL);
+ uValue += BaseAddress + pSect->RVA;
+
+ if (pSect->fFlags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SELF_MODIFYING_CODE))
+ fKind |= RTLDRSYMKIND_CODE;
+ else
+ fKind |= RTLDRSYMKIND_NO_TYPE;
+ break;
+ }
+
+ case MACHO_N_ABS:
+ uValue = paSyms[iSym].n_value;
+ fKind |= RTLDRSYMKIND_NO_TYPE /*RTLDRSYMKIND_ABS*/;
+ break;
+
+ case MACHO_N_PBUD:
+ case MACHO_N_INDR:
+ /** @todo implement indirect and prebound symbols. */
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ }
+
+ /*
+ * Do callback.
+ */
+ /** @todo figure out a better way to deal with underscore prefixes. sigh. */
+ if (cch > 1 && *psz == '_')
+ psz++;
+ rc = pfnCallback(&pThis->Core, psz, iSym, uValue/*, fKind*/, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Enum a 64-bit symbol table.
+ *
+ * @returns IPRT status code.
+ * @param pThis
+ * @param paSyms Pointer to the symbol table.
+ * @param cSyms Number of symbols in the table.
+ * @param pchStrings Pointer to the string table.
+ * @param cchStrings Size of the string table.
+ * @param BaseAddress Adjusted base address, see kLdrModEnumSymbols.
+ * @param fFlags See kLdrModEnumSymbols.
+ * @param pfnCallback See kLdrModEnumSymbols.
+ * @param pvUser See kLdrModEnumSymbols.
+ */
+static int kldrModMachODoEnumSymbols64Bit(PRTLDRMODMACHO pThis, const macho_nlist_64_t *paSyms, uint32_t cSyms,
+ const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress,
+ uint32_t fFlags, PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ const uint32_t fKindBase = pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE
+ ? RTLDRSYMKIND_64BIT : RTLDRSYMKIND_32BIT;
+ uint32_t iSym;
+ int rc;
+
+ /*
+ * Iterate the symbol table.
+ */
+ for (iSym = 0; iSym < cSyms; iSym++)
+ {
+ uint32_t fKind;
+ RTLDRADDR uValue;
+ const char *psz;
+ size_t cch;
+
+ /* Skip debug symbols and undefined symbols. */
+ if (paSyms[iSym].n_type & MACHO_N_STAB)
+ continue;
+ if ((paSyms[iSym].n_type & MACHO_N_TYPE) == MACHO_N_UNDF)
+ continue;
+
+ /* Skip non-public symbols unless they are requested explicitly. */
+ if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_ALL))
+ {
+ if (!(paSyms[iSym].n_type & MACHO_N_EXT)) /*??*/
+ continue;
+ if (paSyms[iSym].n_type & MACHO_N_PEXT) /*??*/
+ continue;
+ if (!paSyms[iSym].n_un.n_strx)
+ continue;
+ }
+
+ /*
+ * Gather symbol info
+ */
+
+ /* name */
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)paSyms[iSym].n_un.n_strx < cchStrings, VERR_LDRMACHO_BAD_SYMBOL);
+ psz = &pchStrings[paSyms[iSym].n_un.n_strx];
+ cch = strlen(psz);
+ if (!cch)
+ psz = NULL;
+
+ /* kind & value */
+ fKind = fKindBase;
+ if (paSyms[iSym].n_desc & N_WEAK_DEF)
+ fKind |= RTLDRSYMKIND_WEAK;
+ switch (paSyms[iSym].n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)(paSyms[iSym].n_sect - 1) < pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSect = &pThis->paSections[paSyms[iSym].n_sect - 1];
+
+ uValue = paSyms[iSym].n_value - pSect->LinkAddress;
+ RTLDRMODMACHO_CHECK_RETURN( uValue <= pSect->cb
+ || ( paSyms[iSym].n_sect == 1 /* special hack for __mh_execute_header */
+ && uValue == 0U - pSect->RVA
+ && pThis->uEffFileType != MH_OBJECT),
+ VERR_LDRMACHO_BAD_SYMBOL);
+ uValue += BaseAddress + pSect->RVA;
+
+ if (pSect->fFlags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SELF_MODIFYING_CODE))
+ fKind |= RTLDRSYMKIND_CODE;
+ else
+ fKind |= RTLDRSYMKIND_NO_TYPE;
+ break;
+ }
+
+ case MACHO_N_ABS:
+ uValue = paSyms[iSym].n_value;
+ fKind |= RTLDRSYMKIND_NO_TYPE /*RTLDRSYMKIND_ABS*/;
+ break;
+
+ case MACHO_N_PBUD:
+ case MACHO_N_INDR:
+ /** @todo implement indirect and prebound symbols. */
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ }
+
+ /*
+ * Do callback.
+ */
+ /** @todo figure out a better way to deal with underscore prefixes. sigh. */
+ if (cch > 1 && *psz == '_')
+ psz++;
+ rc = pfnCallback(&pThis->Core, psz, iSym, uValue/*, fKind*/, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+#if 0
+
+/** @copydoc kLdrModGetImport */
+static int kldrModMachOGetImport(PRTLDRMODINTERNAL pMod, const void *pvBits, uint32_t iImport, char *pszName, size_t cchName)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ RT_NOREF(pvBits);
+ RT_NOREF(iImport);
+ RT_NOREF(pszName);
+ RT_NOREF(cchName);
+
+ if (pThis->Hdr.filetype == MH_OBJECT)
+ return KLDR_ERR_IMPORT_ORDINAL_OUT_OF_BOUNDS;
+
+ /* later */
+ return KLDR_ERR_IMPORT_ORDINAL_OUT_OF_BOUNDS;
+}
+
+
+
+/** @copydoc kLdrModNumberOfImports */
+static int32_t kldrModMachONumberOfImports(PRTLDRMODINTERNAL pMod, const void *pvBits)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ RT_NOREF(pvBits);
+
+ if (pThis->Hdr.filetype == MH_OBJECT)
+ return VINF_SUCCESS;
+
+ /* later */
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModGetStackInfo */
+static int kldrModMachOGetStackInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, PKLDRSTACKINFO pStackInfo)
+{
+ /*PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);*/
+ RT_NOREF(pMod);
+ RT_NOREF(pvBits);
+ RT_NOREF(BaseAddress);
+
+ pStackInfo->Address = NIL_RTLDRADDR;
+ pStackInfo->LinkAddress = NIL_RTLDRADDR;
+ pStackInfo->cbStack = pStackInfo->cbStackThread = 0;
+ /* later */
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModQueryMainEntrypoint */
+static int kldrModMachOQueryMainEntrypoint(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, PRTLDRADDR pMainEPAddress)
+{
+#if 0
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ int rc;
+
+ /*
+ * Resolve base address alias if any.
+ */
+ rc = kldrModMachOBitsAndBaseAddress(pThis, NULL, &BaseAddress);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Convert the address from the header.
+ */
+ *pMainEPAddress = pThis->Hdrs.OptionalHeader.AddressOfEntryPoint
+ ? BaseAddress + pThis->Hdrs.OptionalHeader.AddressOfEntryPoint
+ : NIL_RTLDRADDR;
+#else
+ *pMainEPAddress = NIL_RTLDRADDR;
+ RT_NOREF(pvBits);
+ RT_NOREF(BaseAddress);
+ RT_NOREF(pMod);
+#endif
+ return VINF_SUCCESS;
+}
+
+#endif
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnEnumDbgInfo}
+ */
+static DECLCALLBACK(int) rtldrMachO_EnumDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, PFNRTLDRENUMDBG pfnCallback, void *pvUser)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ int rc = VINF_SUCCESS;
+ uint32_t iSect;
+ RT_NOREF(pvBits);
+
+ for (iSect = 0; iSect < pThis->cSections; iSect++)
+ {
+ /* (32-bit & 64-bit starts the same way) */
+ section_32_t *pMachOSect = (section_32_t *)pThis->paSections[iSect].pvMachoSection;
+ char szTmp[sizeof(pMachOSect->sectname) + 1];
+
+ if (strcmp(pMachOSect->segname, "__DWARF"))
+ continue;
+
+ memcpy(szTmp, pMachOSect->sectname, sizeof(pMachOSect->sectname));
+ szTmp[sizeof(pMachOSect->sectname)] = '\0';
+
+ RTLDRDBGINFO DbgInfo;
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF;
+ DbgInfo.iDbgInfo = iSect;
+ DbgInfo.LinkAddress = pThis->paSections[iSect].LinkAddress;
+ DbgInfo.cb = pThis->paSections[iSect].cb;
+ DbgInfo.pszExtFile = NULL;
+ DbgInfo.u.Dwarf.pszSection = szTmp;
+ rc = pfnCallback(&pThis->Core, &DbgInfo, pvUser);
+ if (rc != VINF_SUCCESS)
+ break;
+ }
+
+ return rc;
+}
+
+#if 0
+
+/** @copydoc kLdrModHasDbgInfo */
+static int kldrModMachOHasDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits)
+{
+ /*PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);*/
+
+#if 0
+ /*
+ * Base this entirely on the presence of a debug directory.
+ */
+ if ( pThis->Hdrs.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size
+ < sizeof(IMAGE_DEBUG_DIRECTORY) /* screw borland linkers */
+ || !pThis->Hdrs.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress)
+ return KLDR_ERR_NO_DEBUG_INFO;
+ return VINF_SUCCESS;
+#else
+ RT_NOREF(pMod);
+ RT_NOREF(pvBits);
+ return VERR_LDR_NO_DEBUG_INFO;
+#endif
+}
+
+
+/** @copydoc kLdrModMap */
+static int kldrModMachOMap(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ unsigned fFixed;
+ uint32_t i;
+ void *pvBase;
+ int rc;
+
+ if (!pThis->fCanLoad)
+ return VERR_LDRMACHO_TODO;
+
+ /*
+ * Already mapped?
+ */
+ if (pThis->pvMapping)
+ return KLDR_ERR_ALREADY_MAPPED;
+
+ /*
+ * Map it.
+ */
+ /* fixed image? */
+ fFixed = pMod->enmType == KLDRTYPE_EXECUTABLE_FIXED
+ || pMod->enmType == KLDRTYPE_SHARED_LIBRARY_FIXED;
+ if (!fFixed)
+ pvBase = NULL;
+ else
+ {
+ pvBase = (void *)(uintptr_t)pMod->aSegments[0].LinkAddress;
+ if ((uintptr_t)pvBase != pMod->aSegments[0].LinkAddress)
+ return VERR_LDR_ADDRESS_OVERFLOW;
+ }
+
+ /* try do the prepare */
+ rc = kRdrMap(pMod->pRdr, &pvBase, pMod->cSegments, pMod->aSegments, fFixed);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Update the segments with their map addresses.
+ */
+ for (i = 0; i < pMod->cSegments; i++)
+ {
+ if (pMod->aSegments[i].RVA != NIL_RTLDRADDR)
+ pMod->aSegments[i].MapAddress = (uintptr_t)pvBase + (uintptr_t)pMod->aSegments[i].RVA;
+ }
+ pThis->pvMapping = pvBase;
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModUnmap */
+static int kldrModMachOUnmap(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ uint32_t i;
+ int rc;
+
+ /*
+ * Mapped?
+ */
+ if (!pThis->pvMapping)
+ return KLDR_ERR_NOT_MAPPED;
+
+ /*
+ * Try unmap the image.
+ */
+ rc = kRdrUnmap(pMod->pRdr, pThis->pvMapping, pMod->cSegments, pMod->aSegments);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Update the segments to reflect that they aren't mapped any longer.
+ */
+ pThis->pvMapping = NULL;
+ for (i = 0; i < pMod->cSegments; i++)
+ pMod->aSegments[i].MapAddress = 0;
+
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModAllocTLS */
+static int kldrModMachOAllocTLS(PRTLDRMODINTERNAL pMod, void *pvMapping)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+
+ /*
+ * Mapped?
+ */
+ if ( pvMapping == KLDRMOD_INT_MAP
+ && !pThis->pvMapping )
+ return KLDR_ERR_NOT_MAPPED;
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModFreeTLS */
+static void kldrModMachOFreeTLS(PRTLDRMODINTERNAL pMod, void *pvMapping)
+{
+ RT_NOREF(pMod);
+ RT_NOREF(pvMapping);
+}
+
+
+
+/** @copydoc kLdrModReload */
+static int kldrModMachOReload(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+
+ /*
+ * Mapped?
+ */
+ if (!pThis->pvMapping)
+ return KLDR_ERR_NOT_MAPPED;
+
+ /* the file provider does it all */
+ return kRdrRefresh(pMod->pRdr, pThis->pvMapping, pMod->cSegments, pMod->aSegments);
+}
+
+
+/** @copydoc kLdrModFixupMapping */
+static int kldrModMachOFixupMapping(PRTLDRMODINTERNAL pMod, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ int rc, rc2;
+
+ /*
+ * Mapped?
+ */
+ if (!pThis->pvMapping)
+ return KLDR_ERR_NOT_MAPPED;
+
+ /*
+ * Before doing anything we'll have to make all pages writable.
+ */
+ rc = kRdrProtect(pMod->pRdr, pThis->pvMapping, pMod->cSegments, pMod->aSegments, 1 /* unprotect */);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Resolve imports and apply base relocations.
+ */
+ rc = rtldrMachO_RelocateBits(pMod, pThis->pvMapping, (uintptr_t)pThis->pvMapping, pThis->LinkAddress,
+ pfnGetImport, pvUser);
+
+ /*
+ * Restore protection.
+ */
+ rc2 = kRdrProtect(pMod->pRdr, pThis->pvMapping, pMod->cSegments, pMod->aSegments, 0 /* protect */);
+ if (RT_SUCCESS(rc) && RT_FAILURE(rc2)
+ rc = rc2;
+ return rc;
+}
+
+#endif
+
+
+/**
+ * Worker for resolving an undefined 32-bit symbol table entry.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param pSym The symbol table entry.
+ * @param BaseAddress The module base address.
+ * @param pfnGetImport The callback for resolving an imported symbol.
+ * @param pvUser User argument to the callback.
+ */
+DECLINLINE(int) rtdlrModMachOHandleUndefinedSymbol32(PRTLDRMODMACHO pThis, macho_nlist_32_t *pSym,
+ RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ RTLDRADDR Value = NIL_RTLDRADDR;
+
+ /** @todo Implement N_REF_TO_WEAK. */
+ RTLDRMODMACHO_CHECK_RETURN(!(pSym->n_desc & N_REF_TO_WEAK), VERR_LDRMACHO_TODO);
+
+ /* Get the symbol name. */
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_un.n_strx < pThis->cchStrings, VERR_LDRMACHO_BAD_SYMBOL);
+ const char *pszSymbol = &pThis->pchStrings[pSym->n_un.n_strx];
+ size_t cchSymbol = strlen(pszSymbol);
+
+ /* Check for linker defined symbols relating to sections and segments. */
+ int rc;
+ if ( cchSymbol <= sizeof("section$end$") - 1
+ || *pszSymbol != 's'
+ || memchr(pszSymbol, '$', cchSymbol) == NULL)
+ rc = VERR_SYMBOL_NOT_FOUND;
+ else
+ rc = kldrModMachOQueryLinkerSymbol(pThis, pszSymbol, cchSymbol, BaseAddress, &Value);
+
+ /* Ask the user for an address to the symbol. */
+ //uint32_t fKind = RTLDRSYMKIND_REQ_FLAT;
+ /** @todo figure out a better way to deal with underscore prefixes. sigh. */
+ if (RT_FAILURE_NP(rc))
+ rc = pfnGetImport(&pThis->Core, NULL /*pszModule*/, pszSymbol + (pszSymbol[0] == '_'),
+ UINT32_MAX, &Value/*, &fKind*/, pvUser);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ /* If weak reference we can continue, otherwise fail? */
+ else if (pSym->n_desc & N_WEAK_REF)
+ Value = 0;
+ else
+ return rc;
+
+ /* Update the symbol. */
+ pSym->n_value = (uint32_t)Value;
+ if (pSym->n_value == Value)
+ return VINF_SUCCESS;
+ return VERR_LDR_ADDRESS_OVERFLOW;
+}
+
+
+/**
+ * Worker for resolving an undefined 64-bit symbol table entry.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param pSym The symbol table entry.
+ * @param BaseAddress The module base address.
+ * @param pfnGetImport The callback for resolving an imported symbol.
+ * @param pvUser User argument to the callback.
+ */
+DECLINLINE(int) rtdlrModMachOHandleUndefinedSymbol64(PRTLDRMODMACHO pThis, macho_nlist_64_t *pSym,
+ RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ RTLDRADDR Value = NIL_RTLDRADDR;
+
+ /** @todo Implement N_REF_TO_WEAK. */
+ RTLDRMODMACHO_CHECK_RETURN(!(pSym->n_desc & N_REF_TO_WEAK), VERR_LDRMACHO_TODO);
+
+ /* Get the symbol name. */
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_un.n_strx < pThis->cchStrings, VERR_LDRMACHO_BAD_SYMBOL);
+ const char *pszSymbol = &pThis->pchStrings[pSym->n_un.n_strx];
+ size_t cchSymbol = strlen(pszSymbol);
+
+ /* Check for linker defined symbols relating to sections and segments. */
+ int rc;
+ if ( cchSymbol <= sizeof("section$end$") - 1
+ || *pszSymbol != 's'
+ || memchr(pszSymbol, '$', cchSymbol) == NULL)
+ rc = VERR_SYMBOL_NOT_FOUND;
+ else
+ rc = kldrModMachOQueryLinkerSymbol(pThis, pszSymbol, cchSymbol, BaseAddress, &Value);
+
+ /* Ask the user for an address to the symbol. */
+ //uint32_t fKind = RTLDRSYMKIND_REQ_FLAT;
+ /** @todo figure out a better way to deal with underscore prefixes. sigh. */
+ if (RT_FAILURE_NP(rc))
+ rc = pfnGetImport(&pThis->Core, NULL /*pszModule*/, pszSymbol + (pszSymbol[0] == '_'),
+ UINT32_MAX, &Value/*, &fKind*/, pvUser);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ /* If weak reference we can continue, otherwise fail? */
+ else if (pSym->n_desc & N_WEAK_REF)
+ Value = 0;
+ else
+ return rc;
+
+ /* Update the symbol. */
+ pSym->n_value = (uint64_t)Value;
+ if (pSym->n_value == Value)
+ return VINF_SUCCESS;
+ return VERR_LDR_ADDRESS_OVERFLOW;
+}
+
+
+/**
+ * MH_OBJECT: Resolves undefined symbols (imports).
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param BaseAddress The module base address.
+ * @param pfnGetImport The callback for resolving an imported symbol.
+ * @param pvUser User argument to the callback.
+ */
+static int kldrModMachOObjDoImports(PRTLDRMODMACHO pThis, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+
+ /*
+ * Ensure that we've got the symbol table.
+ */
+ int rc = kldrModMachOLoadObjSymTab(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Iterate the symbol table and resolve undefined symbols.
+ * We currently ignore REFERENCE_TYPE.
+ */
+ const uint32_t cSyms = pThis->cSymbols;
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ {
+ macho_nlist_32_t *paSyms = (macho_nlist_32_t *)pThis->pvaSymbols;
+ for (uint32_t iSym = 0; iSym < cSyms; iSym++)
+ {
+ /* skip stabs */
+ if (paSyms[iSym].n_type & MACHO_N_STAB)
+ continue;
+
+ if ((paSyms[iSym].n_type & MACHO_N_TYPE) == MACHO_N_UNDF)
+ {
+ rc = rtdlrModMachOHandleUndefinedSymbol32(pThis, &paSyms[iSym], BaseAddress, pfnGetImport, pvUser);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (paSyms[iSym].n_desc & N_WEAK_DEF)
+ {
+ /** @todo implement weak symbols. */
+ /*return VERR_LDRMACHO_TODO; - ignored for now. */
+ }
+ }
+ }
+ else
+ {
+ /* (Identical to the 32-bit code, just different paSym type. (and n_strx is unsigned)) */
+ macho_nlist_64_t *paSyms = (macho_nlist_64_t *)pThis->pvaSymbols;
+ for (uint32_t iSym = 0; iSym < cSyms; iSym++)
+ {
+ /* skip stabs */
+ if (paSyms[iSym].n_type & MACHO_N_STAB)
+ continue;
+
+ if ((paSyms[iSym].n_type & MACHO_N_TYPE) == MACHO_N_UNDF)
+ {
+ rc = rtdlrModMachOHandleUndefinedSymbol64(pThis, &paSyms[iSym], BaseAddress, pfnGetImport, pvUser);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else if (paSyms[iSym].n_desc & N_WEAK_DEF)
+ {
+ /** @todo implement weak symbols. */
+ /*return VERR_LDRMACHO_TODO; - ignored for now. */
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Dylib: Resolves undefined symbols (imports).
+ *
+ * This is conceptually identically to kldrModMachOObjDoImports, only
+ * LC_DYSYMTAB helps us avoid working over the whole symbol table.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param BaseAddress The module base address.
+ * @param pfnGetImport The callback for resolving an imported symbol.
+ * @param pvUser User argument to the callback.
+ */
+static int kldrModMachODylibDoImports(PRTLDRMODMACHO pThis, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ /*
+ * There must be a LC_DYSYMTAB.
+ * We might be lucky, though, and not have any imports.
+ */
+ dysymtab_command_t const *pDySymTab = pThis->pDySymTab;
+ AssertReturn(pDySymTab, VERR_INTERNAL_ERROR_2);
+ if (pDySymTab->nundefsym == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Ensure that we've got the symbol table.
+ */
+ int rc = kldrModMachOLoadObjSymTab(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Iterate the give symbol table section containing undefined symbols and resolve them.
+ */
+ uint32_t const cSyms = pDySymTab->iundefsym + pDySymTab->nundefsym;
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ {
+ macho_nlist_32_t *paSyms = (macho_nlist_32_t *)pThis->pvaSymbols;
+ for (uint32_t iSym = pDySymTab->iundefsym; RT_SUCCESS(rc) && iSym < cSyms; iSym++)
+ {
+ AssertContinue((paSyms[iSym].n_type & (MACHO_N_TYPE | MACHO_N_STAB)) == MACHO_N_UNDF);
+ rc = rtdlrModMachOHandleUndefinedSymbol32(pThis, &paSyms[iSym], BaseAddress, pfnGetImport, pvUser);
+ }
+ }
+ else
+ {
+ /* (Identical to the 32-bit code, just different paSym type. (and n_strx is unsigned)) */
+ macho_nlist_64_t *paSyms = (macho_nlist_64_t *)pThis->pvaSymbols;
+ for (uint32_t iSym = pDySymTab->iundefsym; RT_SUCCESS(rc) && iSym < cSyms; iSym++)
+ {
+ AssertContinue((paSyms[iSym].n_type & (MACHO_N_TYPE | MACHO_N_STAB)) == MACHO_N_UNDF);
+ rc = rtdlrModMachOHandleUndefinedSymbol64(pThis, &paSyms[iSym], BaseAddress, pfnGetImport, pvUser);
+ }
+ }
+
+ return rc;
+}
+
+
+static int kldrModMachODylibDoIndirectSymbols(PRTLDRMODMACHO pThis, void *pvBits, RTLDRADDR offDelta)
+{
+ /*
+ * There must be a LC_DYSYMTAB.
+ * We might be lucky, though, and not have any imports.
+ */
+ dysymtab_command_t const *pDySymTab = pThis->pDySymTab;
+ AssertReturn(pDySymTab, VERR_INTERNAL_ERROR_2);
+ uint32_t const cIndirectSymbols = pDySymTab->nindirectsymb;
+ if (cIndirectSymbols == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Ensure that we've got the symbol table.
+ */
+ int rc = kldrModMachOLoadObjSymTab(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Load the indirect symbol table.
+ */
+ if (!pThis->paidxIndirectSymbols)
+ {
+ uint32_t *paidxIndirectSymbols = (uint32_t *)RTMemAlloc(cIndirectSymbols * sizeof(uint32_t));
+ if (!paidxIndirectSymbols)
+ return VERR_NO_MEMORY;
+ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, paidxIndirectSymbols, cIndirectSymbols * sizeof(uint32_t),
+ pDySymTab->indirectsymboff);
+ if (RT_SUCCESS(rc))
+ pThis->paidxIndirectSymbols = paidxIndirectSymbols;
+ else
+ {
+ RTMemFree(paidxIndirectSymbols);
+ return rc;
+ }
+
+ /* Byte swap if needed. */
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE
+ || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE)
+ for (uint32_t i = 0; i < cIndirectSymbols; i++)
+ paidxIndirectSymbols[i] = RT_BSWAP_U32(paidxIndirectSymbols[i]);
+ }
+ uint32_t const *paidxIndirectSymbols = pThis->paidxIndirectSymbols;
+
+ /*
+ * Process the sections using indirect symbols.
+ */
+ const uint32_t cSymbols = pThis->cSymbols;
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ {
+ macho_nlist_32_t const *paSymbols = (macho_nlist_32_t *)pThis->pvaSymbols;
+ for (uint32_t iSect = 0; iSect < pThis->cSections; iSect++)
+ {
+ section_32_t const *pSect = (section_32_t const *)pThis->paSections[iSect].pvMachoSection;
+ switch (pSect->flags & SECTION_TYPE)
+ {
+ case S_NON_LAZY_SYMBOL_POINTERS:
+ case S_LAZY_SYMBOL_POINTERS:
+ {
+ uint32_t *pauDstPtrs = (uint32_t *)((uintptr_t)pvBits + (uintptr_t)pThis->paSections[iSect].RVA);
+ uint32_t const cDstPtrs = pThis->paSections[iSect].cb / sizeof(pauDstPtrs[0]);
+ uint32_t const idxSrcSkip = pSect->reserved1;
+ if ((uint64_t)idxSrcSkip + cDstPtrs > cIndirectSymbols)
+ return VERR_BAD_EXE_FORMAT; /// @todo better error code.
+
+ for (uint32_t i = 0; i < cDstPtrs; i++)
+ {
+ uint32_t const idxSym = paidxIndirectSymbols[idxSrcSkip + i];
+ if (idxSym == INDIRECT_SYMBOL_LOCAL)
+ pauDstPtrs[i] += (int32_t)offDelta;
+ else if (idxSym != INDIRECT_SYMBOL_ABS)
+ {
+ AssertMsgReturn(idxSym < cSymbols,
+ ("i=%#x idxSym=%#x cSymbols=%#x iSect=%#x\n", i, idxSym, cSymbols, iSect),
+ VERR_BAD_EXE_FORMAT); /// @todo better error code.
+ pauDstPtrs[i] = paSymbols[idxSym].n_value;
+ }
+ }
+ break;
+ }
+
+ case S_SYMBOL_STUBS:
+ if ( pThis->Core.enmArch == RTLDRARCH_X86_32
+ && (pSect->flags & S_ATTR_SELF_MODIFYING_CODE)
+ && pSect->reserved2 == 5)
+ {
+ uint32_t uDstRva = pThis->paSections[iSect].RVA;
+ uint8_t *pbDst = (uint8_t *)((uintptr_t)pvBits + uDstRva);
+ uint32_t const cDstPtrs = pThis->paSections[iSect].cb / 5;
+ uint32_t const idxSrcSkip = pSect->reserved1;
+ if ((uint64_t)idxSrcSkip + cDstPtrs > cIndirectSymbols)
+ return VERR_BAD_EXE_FORMAT; /// @todo better error code.
+
+ for (uint32_t i = 0; i < cDstPtrs; i++, uDstRva += 5, pbDst += 5)
+ {
+ uint32_t const idxSym = paidxIndirectSymbols[idxSrcSkip + i];
+ if (idxSym != INDIRECT_SYMBOL_ABS && idxSym != INDIRECT_SYMBOL_LOCAL)
+ {
+ AssertMsgReturn(idxSym < cSymbols,
+ ("i=%#x idxSym=%#x cSymbols=%#x iSect=%#x\n", i, idxSym, cSymbols, iSect),
+ VERR_BAD_EXE_FORMAT); /// @todo better error code.
+ pbDst[0] = 0xeb; /* JMP rel32 */
+ uint32_t offDisp = paSymbols[idxSym].n_value - (uint32_t)uDstRva - 5;
+ pbDst[1] = (uint8_t)offDisp;
+ offDisp >>= 8;
+ pbDst[2] = (uint8_t)offDisp;
+ offDisp >>= 8;
+ pbDst[3] = (uint8_t)offDisp;
+ offDisp >>= 8;
+ pbDst[4] = (uint8_t)offDisp;
+ }
+ }
+ break;
+ }
+ break;
+ }
+
+ }
+ }
+ else
+ {
+ /* Exact like for 32-bit, except for 64-bit symbol table, 64-bit addresses and no need to process S_SYMBOL_STUBS. */
+ macho_nlist_64_t const *paSymbols = (macho_nlist_64_t *)pThis->pvaSymbols;
+ for (uint32_t iSect = 0; iSect < pThis->cSections; iSect++)
+ {
+ section_64_t const *pSect = (section_64_t const *)pThis->paSections[iSect].pvMachoSection;
+ switch (pSect->flags & SECTION_TYPE)
+ {
+ case S_NON_LAZY_SYMBOL_POINTERS:
+ case S_LAZY_SYMBOL_POINTERS:
+ {
+ uint64_t *pauDstPtrs = (uint64_t *)((uintptr_t)pvBits + (uintptr_t)pThis->paSections[iSect].RVA);
+ uint32_t const cDstPtrs = pThis->paSections[iSect].cb / sizeof(pauDstPtrs[0]);
+ uint32_t const idxSrcSkip = pSect->reserved1;
+ if ((uint64_t)idxSrcSkip + cDstPtrs > cIndirectSymbols)
+ return VERR_BAD_EXE_FORMAT; /// @todo better error code.
+
+ for (uint32_t i = 0; i < cDstPtrs; i++)
+ {
+ uint32_t const idxSym = paidxIndirectSymbols[idxSrcSkip + i];
+ if (idxSym == INDIRECT_SYMBOL_LOCAL)
+ pauDstPtrs[i] += (int64_t)offDelta;
+ else if (idxSym != INDIRECT_SYMBOL_ABS)
+ {
+ AssertMsgReturn(idxSym < cSymbols,
+ ("i=%#x idxSym=%#x cSymbols=%#x iSect=%#x\n", i, idxSym, cSymbols, iSect),
+ VERR_BAD_EXE_FORMAT); /// @todo better error code.
+ pauDstPtrs[i] = paSymbols[idxSym].n_value;
+ }
+ }
+ break;
+ }
+
+ case S_SYMBOL_STUBS:
+ if ( pThis->Core.enmArch == RTLDRARCH_X86_32
+ && (pSect->flags & S_ATTR_SELF_MODIFYING_CODE)
+ && pSect->reserved2 == 5)
+ return VERR_BAD_EXE_FORMAT;
+ break;
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * MH_OBJECT: Applies base relocations to an (unprotected) image mapping.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param pvMapping The mapping to fixup.
+ * @param NewBaseAddress The address to fixup the mapping to.
+ */
+static int kldrModMachOObjDoFixups(PRTLDRMODMACHO pThis, void *pvMapping, RTLDRADDR NewBaseAddress)
+{
+ /*
+ * Ensure that we've got the symbol table.
+ */
+ int rc = kldrModMachOLoadObjSymTab(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Iterate over the segments and their sections and apply fixups.
+ */
+ rc = VINF_SUCCESS;
+ for (uint32_t iSeg = 0; RT_SUCCESS(rc) && iSeg < pThis->cSegments; iSeg++)
+ {
+ PRTLDRMODMACHOSEG pSeg = &pThis->aSegments[iSeg];
+ for (uint32_t iSect = 0; iSect < pSeg->cSections; iSect++)
+ {
+ PRTLDRMODMACHOSECT pSect = &pSeg->paSections[iSect];
+
+ /* skip sections without fixups. */
+ if (!pSect->cFixups)
+ continue;
+ AssertReturn(pSect->paFixups, VERR_INTERNAL_ERROR_4);
+ AssertReturn(pSect->pauFixupVirginData, VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Apply the fixups.
+ */
+ uint8_t *pbSectBits = (uint8_t *)pvMapping + (uintptr_t)pSect->RVA;
+ if (pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE) /** @todo this aint right. */
+ rc = kldrModMachOApplyFixupsGeneric32Bit(pThis, pbSectBits, (size_t)pSect->cb, pSect->RVA, pSect->LinkAddress,
+ pSect->paFixups, pSect->cFixups, pSect->pauFixupVirginData,
+ (macho_nlist_32_t *)pThis->pvaSymbols, pThis->cSymbols, NewBaseAddress);
+ else if ( pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE
+ && pThis->Hdr.cputype == CPU_TYPE_X86_64)
+ rc = kldrModMachOApplyFixupsAMD64(pThis, pbSectBits, (size_t)pSect->cb, pSect->RVA,
+ pSect->paFixups, pSect->cFixups, pSect->pauFixupVirginData,
+ (macho_nlist_64_t *)pThis->pvaSymbols, pThis->cSymbols, NewBaseAddress);
+ else
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Dylib: Applies base relocations to an (unprotected) image mapping.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param pvMapping The mapping to fixup.
+ * @param NewBaseAddress The address to fixup the mapping to.
+ */
+static int kldrModMachODylibDoFixups(PRTLDRMODMACHO pThis, void *pvMapping, RTLDRADDR NewBaseAddress)
+{
+ /*
+ * There must be a LC_DYSYMTAB.
+ * We might be lucky, though, and not have any imports.
+ */
+ dysymtab_command_t const *pDySymTab = pThis->pDySymTab;
+ AssertReturn(pDySymTab, VERR_INTERNAL_ERROR_2);
+ uint32_t cRelocations = pDySymTab->nlocrel + pDySymTab->nextrel;
+ if (cRelocations == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Ensure that we've got the symbol table.
+ */
+ int rc = kldrModMachOLoadObjSymTab(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Load the relocations if needed.
+ */
+ macho_relocation_union_t const *paRelocations = pThis->paRelocations;
+ if (!paRelocations)
+ {
+ uint32_t *paRawRelocs = (uint32_t *)RTMemAlloc(cRelocations * sizeof(macho_relocation_union_t));
+ if (!paRawRelocs)
+ return VERR_NO_MEMORY;
+ if (pDySymTab->nextrel)
+ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, paRawRelocs, pDySymTab->nextrel * sizeof(macho_relocation_union_t),
+ pDySymTab->extreloff);
+ if (pDySymTab->nlocrel && RT_SUCCESS(rc))
+ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader,
+ (uint8_t *)paRawRelocs + pDySymTab->nextrel * sizeof(macho_relocation_union_t),
+ pDySymTab->nlocrel * sizeof(macho_relocation_union_t), pDySymTab->locreloff);
+ if (RT_SUCCESS(rc))
+ pThis->paRelocations = (macho_relocation_union_t *)paRawRelocs;
+ else
+ {
+ RTMemFree(paRawRelocs);
+ return rc;
+ }
+
+ /* Byte swap if needed. */
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE
+ || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE)
+ {
+ for (uint32_t i = 0; i < cRelocations; i++)
+ {
+ paRawRelocs[i * 2] = RT_BSWAP_U32(paRawRelocs[i * 2]);
+ paRawRelocs[i * 2 + 1] = RT_BSWAP_U32(paRawRelocs[i * 2 + 1]);
+ }
+ ASMCompilerBarrier();
+ }
+
+ paRelocations = pThis->paRelocations;
+ }
+
+ /*
+ * Apply the fixups.
+ */
+ if (pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE) /** @todo this aint right. */
+ return kldrModMachOApplyFixupsGeneric32Bit(pThis, (uint8_t *)pvMapping, (size_t)pThis->cbImage, 0, pThis->LinkAddress,
+ paRelocations, cRelocations, pThis->pauRelocationsVirginData,
+ (macho_nlist_32_t *)pThis->pvaSymbols, pThis->cSymbols, NewBaseAddress);
+ if ( pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE
+ && pThis->Hdr.cputype == CPU_TYPE_X86_64)
+ return kldrModMachOApplyFixupsAMD64(pThis, (uint8_t *)pvMapping, (size_t)pThis->cbImage, 0,
+ paRelocations, cRelocations, pThis->pauRelocationsVirginData,
+ (macho_nlist_64_t *)pThis->pvaSymbols, pThis->cSymbols, NewBaseAddress);
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+}
+
+
+/**
+ * Applies generic fixups to a section in an image of the same endian-ness
+ * as the host CPU.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param pbBits Pointer to the bits to fix up.
+ * @param cbBits Size of the bits to fix up.
+ * @param uBitsRva The RVA of the bits.
+ * @param uBitsLinkAddr The link address of the bits.
+ * @param paFixups The fixups.
+ * @param cFixups Number of fixups.
+ * @param pauVirginData The virgin data / addends. Parallel to paFixups.
+ * @param paSyms Pointer to the symbol table.
+ * @param cSyms Number of symbols.
+ * @param NewBaseAddress The new base image address.
+ */
+static int kldrModMachOApplyFixupsGeneric32Bit(PRTLDRMODMACHO pThis, uint8_t *pbBits, size_t cbBits, RTLDRADDR uBitsRva,
+ RTLDRADDR uBitsLinkAddr, const macho_relocation_union_t *paFixups,
+ const uint32_t cFixups, PCRTUINT64U const pauVirginData,
+ macho_nlist_32_t *paSyms, uint32_t cSyms, RTLDRADDR NewBaseAddress)
+{
+ /*
+ * Iterate the fixups and apply them.
+ */
+ for (uint32_t iFixup = 0; iFixup < cFixups; iFixup++)
+ {
+ macho_relocation_union_t Fixup = paFixups[iFixup];
+ RTLDRADDR SymAddr = ~(RTLDRADDR)0;
+ RTPTRUNION uFix;
+
+ if (!(Fixup.r.r_address & R_SCATTERED))
+ {
+ /* sanity */
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)Fixup.r.r_address + RT_BIT_32(Fixup.r.r_length) <= cbBits, VERR_LDR_BAD_FIXUP);
+
+ /* Calc the fixup address. */
+ uFix.pv = pbBits + Fixup.r.r_address;
+
+ /*
+ * Calc the symbol value.
+ */
+ /* Calc the linked symbol address / addend. */
+ switch (Fixup.r.r_length)
+ {
+ case 0: SymAddr = (int8_t)pauVirginData[iFixup].au8[0]; break;
+ case 1: SymAddr = (int16_t)pauVirginData[iFixup].au16[0]; break;
+ case 2: SymAddr = (int32_t)pauVirginData[iFixup].au32[0]; break;
+ case 3: SymAddr = (int64_t)pauVirginData[iFixup].u; break;
+ default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP);
+ }
+ if (Fixup.r.r_pcrel)
+ SymAddr += Fixup.r.r_address + uBitsLinkAddr;
+
+ /* Add symbol / section address. */
+ if (Fixup.r.r_extern)
+ {
+ const macho_nlist_32_t *pSym;
+ if (Fixup.r.r_symbolnum >= cSyms)
+ return VERR_LDR_BAD_FIXUP;
+ pSym = &paSyms[Fixup.r.r_symbolnum];
+
+ if (pSym->n_type & MACHO_N_STAB)
+ return VERR_LDR_BAD_FIXUP;
+
+ switch (pSym->n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSymSect = &pThis->paSections[pSym->n_sect - 1];
+
+ SymAddr += pSym->n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress;
+ break;
+ }
+
+ case MACHO_N_UNDF:
+ case MACHO_N_ABS:
+ SymAddr += pSym->n_value;
+ break;
+
+ case MACHO_N_INDR:
+ case MACHO_N_PBUD:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL);
+ }
+ }
+ else if (Fixup.r.r_symbolnum != R_ABS)
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ if (Fixup.r.r_symbolnum > pThis->cSections)
+ return VERR_LDR_BAD_FIXUP;
+ pSymSect = &pThis->paSections[Fixup.r.r_symbolnum - 1];
+
+ SymAddr -= pSymSect->LinkAddress;
+ SymAddr += pSymSect->RVA + NewBaseAddress;
+ }
+
+ /* adjust for PC relative */
+ if (Fixup.r.r_pcrel)
+ SymAddr -= Fixup.r.r_address + uBitsRva + NewBaseAddress;
+ }
+ else
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ uint32_t iSymSect;
+ RTLDRADDR Value;
+
+ /* sanity */
+ RTLDRMODMACHO_ASSERT(Fixup.s.r_scattered);
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)Fixup.s.r_address + RT_BIT_32(Fixup.s.r_length) <= cbBits, VERR_LDR_BAD_FIXUP);
+
+ /* Calc the fixup address. */
+ uFix.pv = pbBits + Fixup.s.r_address;
+
+ /*
+ * Calc the symbol value.
+ */
+ /* The addend is stored in the code. */
+ switch (Fixup.s.r_length)
+ {
+ case 0: SymAddr = (int8_t)pauVirginData[iFixup].au8[0]; break;
+ case 1: SymAddr = (int16_t)pauVirginData[iFixup].au16[0]; break;
+ case 2: SymAddr = (int32_t)pauVirginData[iFixup].au32[0]; break;
+ case 3: SymAddr = (int64_t)pauVirginData[iFixup].u; break;
+ default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP);
+ }
+ if (Fixup.s.r_pcrel)
+ SymAddr += Fixup.s.r_address;
+ Value = Fixup.s.r_value;
+ SymAddr -= Value; /* (-> addend only) */
+
+ /* Find the section number from the r_value. */
+ pSymSect = NULL;
+ for (iSymSect = 0; iSymSect < pThis->cSections; iSymSect++)
+ {
+ RTLDRADDR off = Value - pThis->paSections[iSymSect].LinkAddress;
+ if (off < pThis->paSections[iSymSect].cb)
+ {
+ pSymSect = &pThis->paSections[iSymSect];
+ break;
+ }
+ else if (off == pThis->paSections[iSymSect].cb) /* edge case */
+ pSymSect = &pThis->paSections[iSymSect];
+ }
+ if (!pSymSect)
+ return VERR_LDR_BAD_FIXUP;
+
+ /* Calc the symbol address. */
+ SymAddr += Value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress;
+ if (Fixup.s.r_pcrel)
+ SymAddr -= Fixup.s.r_address + uBitsRva + NewBaseAddress;
+
+ Fixup.r.r_length = ((scattered_relocation_info_t *)&paFixups[iFixup])->r_length;
+ Fixup.r.r_type = ((scattered_relocation_info_t *)&paFixups[iFixup])->r_type;
+ }
+
+ /*
+ * Write back the fixed up value.
+ */
+ if (Fixup.r.r_type == GENERIC_RELOC_VANILLA)
+ {
+ switch (Fixup.r.r_length)
+ {
+ case 0: *uFix.pu8 = (uint8_t)SymAddr; break;
+ case 1: *uFix.pu16 = (uint16_t)SymAddr; break;
+ case 2: *uFix.pu32 = (uint32_t)SymAddr; break;
+ case 3: *uFix.pu64 = (uint64_t)SymAddr; break;
+ }
+ }
+ else if (Fixup.r.r_type <= GENERIC_RELOC_LOCAL_SECTDIFF)
+ return VERR_LDRMACHO_UNSUPPORTED_FIXUP_TYPE;
+ else
+ return VERR_LDR_BAD_FIXUP;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Applies AMD64 fixups to a section.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param pbBits Pointer to the section bits.
+ * @param cbBits Size of the bits to fix up.
+ * @param uBitsRva The RVA of the bits.
+ * @param paFixups The fixups.
+ * @param cFixups Number of fixups.
+ * @param pauVirginData The virgin data / addends. Parallel to paFixups.
+ * @param paSyms Pointer to the symbol table.
+ * @param cSyms Number of symbols.
+ * @param NewBaseAddress The new base image address.
+ */
+static int kldrModMachOApplyFixupsAMD64(PRTLDRMODMACHO pThis, uint8_t *pbBits, size_t cbBits, RTLDRADDR uBitsRva,
+ const macho_relocation_union_t *paFixups,
+ const uint32_t cFixups, PCRTUINT64U const pauVirginData,
+ macho_nlist_64_t *paSyms, uint32_t cSyms, RTLDRADDR NewBaseAddress)
+{
+ /*
+ * Iterate the fixups and apply them.
+ */
+ for (uint32_t iFixup = 0; iFixup < cFixups; iFixup++)
+ {
+ macho_relocation_union_t Fixup = paFixups[iFixup];
+
+ /* AMD64 doesn't use scattered fixups. */
+ RTLDRMODMACHO_CHECK_RETURN(!(Fixup.r.r_address & R_SCATTERED), VERR_LDR_BAD_FIXUP);
+
+ /* sanity */
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)Fixup.r.r_address + RT_BIT_32(Fixup.r.r_length) <= cbBits, VERR_LDR_BAD_FIXUP);
+
+ /* calc fixup addresses. */
+ RTPTRUNION uFix;
+ uFix.pv = pbBits + Fixup.r.r_address;
+
+ /*
+ * Calc the symbol value.
+ */
+ /* Calc the linked symbol address / addend. */
+ RTLDRADDR SymAddr;
+ switch (Fixup.r.r_length)
+ {
+ case 2: SymAddr = (int32_t)pauVirginData[iFixup].au32[0]; break;
+ case 3: SymAddr = (int64_t)pauVirginData[iFixup].u; break;
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP);
+ }
+
+ /* Add symbol / section address. */
+ if (Fixup.r.r_extern)
+ {
+ const macho_nlist_64_t *pSym;
+
+ RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_symbolnum < cSyms, VERR_LDR_BAD_FIXUP);
+ pSym = &paSyms[Fixup.r.r_symbolnum];
+ RTLDRMODMACHO_CHECK_RETURN(!(pSym->n_type & MACHO_N_STAB), VERR_LDR_BAD_FIXUP);
+
+ switch (Fixup.r.r_type)
+ {
+ /* GOT references just needs to have their symbol verified.
+ Later, we'll optimize GOT building here using a parallel sym->got array. */
+ case X86_64_RELOC_GOT_LOAD:
+ case X86_64_RELOC_GOT:
+ switch (pSym->n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ case MACHO_N_UNDF:
+ case MACHO_N_ABS:
+ break;
+ case MACHO_N_INDR:
+ case MACHO_N_PBUD:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL);
+ }
+ SymAddr = sizeof(uint64_t) * Fixup.r.r_symbolnum + pThis->GotRVA + NewBaseAddress;
+ RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_length == 2, VERR_LDR_BAD_FIXUP);
+ SymAddr -= 4;
+ break;
+
+ /* Verify the r_pcrel field for signed fixups on the way into the default case. */
+ case X86_64_RELOC_BRANCH:
+ case X86_64_RELOC_SIGNED:
+ case X86_64_RELOC_SIGNED_1:
+ case X86_64_RELOC_SIGNED_2:
+ case X86_64_RELOC_SIGNED_4:
+ RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP);
+ RT_FALL_THRU();
+ default:
+ {
+ /* Adjust with fixup specific addend and verify unsigned/r_pcrel. */
+ switch (Fixup.r.r_type)
+ {
+ case X86_64_RELOC_UNSIGNED:
+ RTLDRMODMACHO_CHECK_RETURN(!Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP);
+ break;
+ case X86_64_RELOC_BRANCH:
+ RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_length == 2, VERR_LDR_BAD_FIXUP);
+ SymAddr -= 4;
+ break;
+ case X86_64_RELOC_SIGNED:
+ case X86_64_RELOC_SIGNED_1:
+ case X86_64_RELOC_SIGNED_2:
+ case X86_64_RELOC_SIGNED_4:
+ SymAddr -= 4;
+ break;
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP);
+ }
+
+ switch (pSym->n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSymSect = &pThis->paSections[pSym->n_sect - 1];
+ SymAddr += pSym->n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress;
+ break;
+ }
+
+ case MACHO_N_UNDF:
+ /* branch to an external symbol may have to take a short detour. */
+ if ( Fixup.r.r_type == X86_64_RELOC_BRANCH
+ && SymAddr + Fixup.r.r_address + uBitsRva + NewBaseAddress
+ - pSym->n_value
+ + UINT64_C(0x80000000)
+ >= UINT64_C(0xffffff20))
+ {
+ RTLDRMODMACHO_CHECK_RETURN(pThis->JmpStubsRVA != NIL_RTLDRADDR, VERR_LDR_ADDRESS_OVERFLOW);
+ SymAddr += pThis->cbJmpStub * Fixup.r.r_symbolnum + pThis->JmpStubsRVA + NewBaseAddress;
+ }
+ else
+ SymAddr += pSym->n_value;
+ break;
+
+ case MACHO_N_ABS:
+ SymAddr += pSym->n_value;
+ break;
+
+ case MACHO_N_INDR:
+ case MACHO_N_PBUD:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL);
+ }
+ break;
+ }
+
+ /*
+ * This is a weird customer, it will always be follows by an UNSIGNED fixup.
+ * The value is calculated: target - pair_target.
+ * Note! The linker generally eliminate these when linking modules rather
+ * than objects (-r).
+ */
+ case X86_64_RELOC_SUBTRACTOR:
+ {
+ /* Deal with the SUBTRACT symbol first, by subtracting it from SymAddr. */
+ switch (pSym->n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSymSect = &pThis->paSections[pSym->n_sect - 1];
+ SymAddr -= pSym->n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress;
+ break;
+ }
+
+ case MACHO_N_UNDF:
+ case MACHO_N_ABS:
+ SymAddr -= pSym->n_value;
+ break;
+
+ case MACHO_N_INDR:
+ case MACHO_N_PBUD:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL);
+ }
+
+ /* Load the 2nd fixup, check sanity. */
+ iFixup++;
+ RTLDRMODMACHO_CHECK_RETURN(!Fixup.r.r_pcrel && iFixup < cFixups, VERR_LDR_BAD_FIXUP);
+ macho_relocation_info_t const Fixup2 = paFixups[iFixup].r;
+ RTLDRMODMACHO_CHECK_RETURN( Fixup2.r_address == Fixup.r.r_address
+ && Fixup2.r_length == Fixup.r.r_length
+ && Fixup2.r_type == X86_64_RELOC_UNSIGNED
+ && !Fixup2.r_pcrel
+ && Fixup2.r_symbolnum < cSyms,
+ VERR_LDR_BAD_FIXUP);
+
+ if (Fixup2.r_extern)
+ {
+ RTLDRMODMACHO_CHECK_RETURN(Fixup2.r_symbolnum < cSyms, VERR_LDR_BAD_FIXUP);
+ pSym = &paSyms[Fixup2.r_symbolnum];
+ RTLDRMODMACHO_CHECK_RETURN(!(pSym->n_type & MACHO_N_STAB), VERR_LDR_BAD_FIXUP);
+
+ /* Add its value to SymAddr. */
+ switch (pSym->n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSymSect = &pThis->paSections[pSym->n_sect - 1];
+ SymAddr += pSym->n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress;
+ break;
+ }
+
+ case MACHO_N_UNDF:
+ case MACHO_N_ABS:
+ SymAddr += pSym->n_value;
+ break;
+
+ case MACHO_N_INDR:
+ case MACHO_N_PBUD:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL);
+ }
+ }
+ else if (Fixup2.r_symbolnum != R_ABS)
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ RTLDRMODMACHO_CHECK_RETURN(Fixup2.r_symbolnum <= pThis->cSections, VERR_LDR_BAD_FIXUP);
+ pSymSect = &pThis->paSections[Fixup2.r_symbolnum - 1];
+ SymAddr += pSymSect->RVA - pSymSect->LinkAddress + NewBaseAddress;
+ }
+ else
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP);
+ }
+ break;
+ }
+ }
+ else
+ {
+ /* verify against fixup type and make adjustments */
+ switch (Fixup.r.r_type)
+ {
+ case X86_64_RELOC_UNSIGNED:
+ RTLDRMODMACHO_CHECK_RETURN(!Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP);
+ break;
+ case X86_64_RELOC_BRANCH:
+ RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP);
+ SymAddr += 4; /* dunno what the assmbler/linker really is doing here... */
+ break;
+ case X86_64_RELOC_SIGNED:
+ case X86_64_RELOC_SIGNED_1:
+ case X86_64_RELOC_SIGNED_2:
+ case X86_64_RELOC_SIGNED_4:
+ RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP);
+ break;
+ /*case X86_64_RELOC_GOT_LOAD:*/
+ /*case X86_64_RELOC_GOT: */
+ /*case X86_64_RELOC_SUBTRACTOR: - must be r_extern=1 says as. */
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP);
+ }
+ if (Fixup.r.r_symbolnum != R_ABS)
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_symbolnum <= pThis->cSections, VERR_LDR_BAD_FIXUP);
+ pSymSect = &pThis->paSections[Fixup.r.r_symbolnum - 1];
+
+ SymAddr -= pSymSect->LinkAddress;
+ SymAddr += pSymSect->RVA + NewBaseAddress;
+ if (Fixup.r.r_pcrel)
+ SymAddr += Fixup.r.r_address;
+ }
+ }
+
+ /* adjust for PC relative */
+ if (Fixup.r.r_pcrel)
+ SymAddr -= Fixup.r.r_address + uBitsRva + NewBaseAddress;
+
+ /*
+ * Write back the fixed up value.
+ */
+ switch (Fixup.r.r_length)
+ {
+ case 3:
+ *uFix.pu64 = (uint64_t)SymAddr;
+ break;
+ case 2:
+ RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_pcrel || Fixup.r.r_type == X86_64_RELOC_SUBTRACTOR, VERR_LDR_BAD_FIXUP);
+ RTLDRMODMACHO_CHECK_RETURN((int32_t)SymAddr == (int64_t)SymAddr, VERR_LDR_ADDRESS_OVERFLOW);
+ *uFix.pu32 = (uint32_t)SymAddr;
+ break;
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Loads the symbol table (LC_SYMTAB).
+ *
+ * The symbol table is pointed to by RTLDRMODMACHO::pvaSymbols.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ */
+static int kldrModMachOLoadObjSymTab(PRTLDRMODMACHO pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ if ( !pThis->pvaSymbols
+ && pThis->cSymbols)
+ {
+ size_t cbSyms;
+ size_t cbSym;
+ void *pvSyms;
+ void *pvStrings;
+
+ /* sanity */
+ RTLDRMODMACHO_CHECK_RETURN( pThis->offSymbols
+ && (!pThis->cchStrings || pThis->offStrings),
+ VERR_LDRMACHO_BAD_OBJECT_FILE);
+
+ /* allocate */
+ cbSym = pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE
+ ? sizeof(macho_nlist_32_t)
+ : sizeof(macho_nlist_64_t);
+ cbSyms = pThis->cSymbols * cbSym;
+ RTLDRMODMACHO_CHECK_RETURN(cbSyms / cbSym == pThis->cSymbols, VERR_LDRMACHO_BAD_SYMTAB_SIZE);
+ rc = VERR_NO_MEMORY;
+ pvSyms = RTMemAlloc(cbSyms);
+ if (pvSyms)
+ {
+ if (pThis->cchStrings)
+ pvStrings = RTMemAlloc(pThis->cchStrings);
+ else
+ pvStrings = RTMemAllocZ(4);
+ if (pvStrings)
+ {
+ /* read */
+ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvSyms, cbSyms, pThis->offSymbols);
+ if (RT_SUCCESS(rc) && pThis->cchStrings)
+ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvStrings, pThis->cchStrings, pThis->offStrings);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pvaSymbols = pvSyms;
+ pThis->pchStrings = (char *)pvStrings;
+
+ /* perform endian conversion? */
+ if (pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ {
+ uint32_t cLeft = pThis->cSymbols;
+ macho_nlist_32_t *pSym = (macho_nlist_32_t *)pvSyms;
+ while (cLeft-- > 0)
+ {
+ pSym->n_un.n_strx = RT_BSWAP_U32(pSym->n_un.n_strx);
+ pSym->n_desc = (int16_t)RT_BSWAP_U16(pSym->n_desc);
+ pSym->n_value = RT_BSWAP_U32(pSym->n_value);
+ pSym++;
+ }
+ }
+ else if (pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE)
+ {
+ uint32_t cLeft = pThis->cSymbols;
+ macho_nlist_64_t *pSym = (macho_nlist_64_t *)pvSyms;
+ while (cLeft-- > 0)
+ {
+ pSym->n_un.n_strx = RT_BSWAP_U32(pSym->n_un.n_strx);
+ pSym->n_desc = (int16_t)RT_BSWAP_U16(pSym->n_desc);
+ pSym->n_value = RT_BSWAP_U64(pSym->n_value);
+ pSym++;
+ }
+ }
+
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pvStrings);
+ }
+ RTMemFree(pvSyms);
+ }
+ }
+ else
+ RTLDRMODMACHO_ASSERT(pThis->pchStrings || pThis->Hdr.filetype == MH_DSYM);
+
+ return rc;
+}
+
+
+/**
+ * Loads the fixups at the given address and performs endian
+ * conversion if necessary.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param offFixups The file offset of the fixups.
+ * @param cFixups The number of fixups to load.
+ * @param ppaFixups Where to put the pointer to the allocated fixup array.
+ */
+static int kldrModMachOLoadFixups(PRTLDRMODMACHO pThis, RTFOFF offFixups, uint32_t cFixups, macho_relocation_union_t **ppaFixups)
+{
+ macho_relocation_union_t *paFixups;
+ size_t cbFixups;
+
+ /* allocate the memory. */
+ cbFixups = cFixups * sizeof(*paFixups);
+ RTLDRMODMACHO_CHECK_RETURN(cbFixups / sizeof(*paFixups) == cFixups, VERR_LDRMACHO_BAD_SYMTAB_SIZE);
+ paFixups = (macho_relocation_union_t *)RTMemAlloc(cbFixups);
+ if (!paFixups)
+ return VERR_NO_MEMORY;
+
+ /* read the fixups. */
+ int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, paFixups, cbFixups, offFixups);
+ if (RT_SUCCESS(rc))
+ {
+ *ppaFixups = paFixups;
+
+ /* do endian conversion if necessary. */
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE
+ || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE)
+ {
+ uint32_t iFixup;
+ for (iFixup = 0; iFixup < cFixups; iFixup++)
+ {
+ uint32_t *pu32 = (uint32_t *)&paFixups[iFixup];
+ pu32[0] = RT_BSWAP_U32(pu32[0]);
+ pu32[1] = RT_BSWAP_U32(pu32[1]);
+ }
+ }
+ }
+ else
+ RTMemFree(paFixups);
+ return rc;
+}
+
+
+/**
+ * Loads virgin data (addends) for an array of fixups.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param pbBits The virgin bits to lift the data from
+ * @param cbBits The number of virgin bytes.
+ * @param paFixups The fixups.
+ * @param cFixups Number of fixups
+ * @param pszName Name for logging.
+ * @param ppauVirginData Where to return the virgin data.
+ */
+static int rtldrMachOLoadVirginData(PRTLDRMODMACHO pThis, uint8_t const *pbBits, size_t cbBits,
+ macho_relocation_union_t const *paFixups, uint32_t cFixups, const char *pszName,
+ PRTUINT64U *ppauVirginData)
+{
+ /*
+ * In case we jettisoned the fixups, we will leave virgin data.
+ */
+ if (*ppauVirginData)
+ return VINF_SUCCESS;
+
+#ifdef LOG_ENABLED
+ /*
+ * Ensure that we've got the symbol table if we're logging fixups.
+ */
+ if (LogIs5Enabled())
+ {
+ int rc = kldrModMachOLoadObjSymTab(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+#endif
+
+
+ /*
+ * Allocate memory and iterate the fixups to get the data.
+ */
+ PRTUINT64U pauVirginData = *ppauVirginData = (PRTUINT64U)RTMemAllocZ(sizeof(uint64_t) * cFixups);
+ if (pauVirginData)
+ {
+ Log5(("Fixups for %s: (%u)\n", pszName, cFixups));
+ for (uint32_t i = 0; i < cFixups; i++)
+ {
+ uint32_t off;
+ uint32_t cShift;
+ if (!paFixups[i].s.r_scattered)
+ {
+ off = paFixups[i].r.r_address;
+ cShift = paFixups[i].r.r_length;
+ }
+ else
+ {
+ off = paFixups[i].s.r_address;
+ cShift = paFixups[i].s.r_length;
+ }
+ RTLDRMODMACHO_CHECK_RETURN(off + RT_BIT_32(cShift) <= cbBits, VERR_LDR_BAD_FIXUP);
+
+ /** @todo This ASSUMES same endian in the image and on the host. Would need
+ * to check target cpu (pThis->Core.enmArch) endianness against host to get
+ * it right... (outside the loop, obviously) */
+ switch (cShift)
+ {
+ case 3:
+ pauVirginData[i].u = RT_MAKE_U64_FROM_U8(pbBits[off], pbBits[off + 1], pbBits[off + 2], pbBits[off + 3],
+ pbBits[off + 4], pbBits[off + 5], pbBits[off + 6], pbBits[off + 7]);
+ break;
+ case 2:
+ pauVirginData[i].u = (int32_t)RT_MAKE_U32_FROM_U8(pbBits[off], pbBits[off + 1], pbBits[off + 2], pbBits[off + 3]);
+ break;
+ case 1:
+ pauVirginData[i].u = (int16_t)RT_MAKE_U16(pbBits[off], pbBits[off + 1]);
+ break;
+ case 0:
+ pauVirginData[i].u = (int8_t)pbBits[off];
+ break;
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP);
+ }
+
+#ifdef LOG_ENABLED
+ if (LogIs5Enabled())
+ {
+ if (!paFixups[i].s.r_scattered)
+ {
+ Log5((" #%06x: %#08x LB %u: t=%#x pc=%u ex=%u sym=%#010x add=%#RX64\n",
+ i, off, RT_BIT_32(cShift), paFixups[i].r.r_type, paFixups[i].r.r_pcrel, paFixups[i].r.r_extern,
+ paFixups[i].r.r_symbolnum, pauVirginData[i].u));
+ if (paFixups[i].r.r_symbolnum < pThis->cSymbols)
+ {
+ if (pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE)
+ {
+ macho_nlist_64_t const *pSym = (macho_nlist_64_t const *)pThis->pvaSymbols + paFixups[i].r.r_symbolnum;
+ Log5((" sym: %#04x:%#018RX64 t=%#04x d=%#06x %s\n",
+ pSym->n_sect, pSym->n_value, pSym->n_type, pSym->n_desc,
+ pSym->n_un.n_strx < pThis->cchStrings ? &pThis->pchStrings[pSym->n_un.n_strx] : ""));
+
+ }
+ else
+ {
+ macho_nlist_32_t const *pSym = (macho_nlist_32_t const *)pThis->pvaSymbols + paFixups[i].r.r_symbolnum;
+ Log5((" sym: %#04x:%#010RX32 t=%#04x d=%#06x %s\n",
+ pSym->n_sect, pSym->n_value, pSym->n_type, pSym->n_desc,
+ (uint32_t)pSym->n_un.n_strx < pThis->cchStrings ? &pThis->pchStrings[pSym->n_un.n_strx] : ""));
+ }
+ }
+ }
+ else
+ Log5((" #%06x: %#08x LB %u: t=%#x pc=%u val=%#010x add=%#RX64\n", i, off, RT_BIT_32(cShift),
+ paFixups[i].s.r_type, paFixups[i].s.r_pcrel, paFixups[i].s.r_value, pauVirginData[i].u));
+ }
+#endif
+ }
+ return VINF_SUCCESS;
+ }
+ RT_NOREF(pThis, pszName);
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * MH_OBJECT: Loads fixups and addends for each section.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param pbBits The image bits. First time we're called, these are
+ * ASSUMED to be in virgin state and suitable for
+ * saving addends.
+ */
+static int rtldrMachOObjLoadFixupsAndVirginData(PRTLDRMODMACHO pThis, uint8_t const *pbBits)
+{
+ PRTLDRMODMACHOSECT pSect = pThis->paSections;
+ for (uint32_t i = 0; i < pThis->cSections; i++, pSect++)
+ if ( !pSect->paFixups
+ && pSect->cFixups > 0)
+ {
+ /*
+ * Load and endian convert the fixups.
+ */
+ int rc = kldrModMachOLoadFixups(pThis, pSect->offFixups, pSect->cFixups, &pSect->paFixups);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Save virgin data (addends) for each fixup.
+ */
+ rc = rtldrMachOLoadVirginData(pThis, &pbBits[(size_t)pSect->RVA], (size_t)pSect->cb, pSect->paFixups, pSect->cFixups,
+ pThis->aSegments[pSect->iSegment].SegInfo.pszName, &pSect->pauFixupVirginData);
+ if (RT_SUCCESS(rc))
+ continue;
+
+ RTMemFree(pSect->pauFixupVirginData);
+ pSect->pauFixupVirginData = NULL;
+ RTMemFree(pSect->paFixups);
+ pSect->paFixups = NULL;
+ }
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Dylib: Loads fixups and addends.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module interpreter instance.
+ * @param pbBits The image bits. First time we're called, these are
+ * ASSUMED to be in virgin state and suitable for
+ * saving addends.
+ */
+static int rtldrMachODylibLoadFixupsAndVirginData(PRTLDRMODMACHO pThis, uint8_t const *pbBits)
+{
+ /*
+ * Don't do it again if we already loaded them.
+ */
+ if (pThis->paRelocations)
+ {
+ Assert(pThis->pauRelocationsVirginData);
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * There must be a LC_DYSYMTAB. Fixups are optionals.
+ */
+ dysymtab_command_t const *pDySymTab = pThis->pDySymTab;
+ AssertReturn(pDySymTab, VERR_INTERNAL_ERROR_2);
+ uint32_t cRelocations = pDySymTab->nlocrel + pDySymTab->nextrel;
+ if (cRelocations == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Load fixups.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t *paRawRelocs = (uint32_t *)RTMemAlloc(cRelocations * sizeof(macho_relocation_union_t));
+ if (paRawRelocs)
+ {
+ pThis->paRelocations = (macho_relocation_union_t *)paRawRelocs;
+
+ if (pDySymTab->nextrel)
+ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, paRawRelocs,
+ pDySymTab->nextrel * sizeof(macho_relocation_union_t), pDySymTab->extreloff);
+ if (pDySymTab->nlocrel && RT_SUCCESS(rc))
+ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader,
+ (uint8_t *)paRawRelocs + pDySymTab->nextrel * sizeof(macho_relocation_union_t),
+ pDySymTab->nlocrel * sizeof(macho_relocation_union_t), pDySymTab->locreloff);
+ if (RT_SUCCESS(rc))
+ {
+ /* Byte swap if needed. */
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE
+ || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE)
+ {
+ for (uint32_t i = 0; i < cRelocations; i++)
+ {
+ paRawRelocs[i * 2] = RT_BSWAP_U32(paRawRelocs[i * 2]);
+ paRawRelocs[i * 2 + 1] = RT_BSWAP_U32(paRawRelocs[i * 2 + 1]);
+ }
+ ASMCompilerBarrier();
+ }
+
+ /*
+ * Load virgin data (addends).
+ */
+ rc = rtldrMachOLoadVirginData(pThis, pbBits, (size_t)pThis->cbImage, pThis->paRelocations, cRelocations,
+ "whole-image", &pThis->pauRelocationsVirginData);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTMemFree(pThis->pauRelocationsVirginData);
+ pThis->pauRelocationsVirginData = NULL;
+ }
+ RTMemFree(pThis->paRelocations);
+ pThis->paRelocations = NULL;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+#if 0
+
+/** @copydoc kLdrModCallInit */
+static int kldrModMachOCallInit(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle)
+{
+ /* later */
+ RT_NOREF(pMod);
+ RT_NOREF(pvMapping);
+ RT_NOREF(uHandle);
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModCallTerm */
+static int kldrModMachOCallTerm(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle)
+{
+ /* later */
+ RT_NOREF(pMod);
+ RT_NOREF(pvMapping);
+ RT_NOREF(uHandle);
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc kLdrModCallThread */
+static int kldrModMachOCallThread(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle, unsigned fAttachingOrDetaching)
+{
+ /* Relevant for Mach-O? */
+ RT_NOREF(pMod);
+ RT_NOREF(pvMapping);
+ RT_NOREF(uHandle);
+ RT_NOREF(fAttachingOrDetaching);
+ return VINF_SUCCESS;
+}
+
+#endif
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnGetImageSize}
+ */
+static DECLCALLBACK(size_t) rtldrMachO_GetImageSize(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ return pThis->cbImage;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnGetBits}
+ */
+static DECLCALLBACK(int) rtldrMachO_GetBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress,
+ PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+
+ if (!pThis->fCanLoad)
+ return VERR_LDRMACHO_TODO;
+
+ /*
+ * Zero the entire buffer first to simplify things.
+ */
+ memset(pvBits, 0, (size_t)pThis->cbImage);
+
+ /*
+ * When possible use the segment table to load the data.
+ */
+ for (uint32_t i = 0; i < pThis->cSegments; i++)
+ {
+ /* skip it? */
+ if ( pThis->aSegments[i].SegInfo.cbFile == -1
+ || pThis->aSegments[i].SegInfo.offFile == -1
+ || pThis->aSegments[i].SegInfo.RVA == NIL_RTLDRADDR
+ || pThis->aSegments[i].SegInfo.cbMapped == 0
+ || !pThis->aSegments[i].SegInfo.Alignment)
+ continue;
+ int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader,
+ (uint8_t *)pvBits + pThis->aSegments[i].SegInfo.RVA,
+ pThis->aSegments[i].SegInfo.cbFile,
+ pThis->aSegments[i].SegInfo.offFile);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Perform relocations.
+ */
+ return rtldrMachO_RelocateBits(pMod, pvBits, BaseAddress, pThis->LinkAddress, pfnGetImport, pvUser);
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnRelocate}
+ */
+static DECLCALLBACK(int) rtldrMachO_RelocateBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress,
+ RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ int rc;
+
+ /*
+ * Call workers to do the jobs.
+ */
+ if (pThis->Hdr.filetype == MH_OBJECT)
+ {
+ rc = rtldrMachOObjLoadFixupsAndVirginData(pThis, (uint8_t const *)pvBits);
+ if (RT_SUCCESS(rc))
+ rc = kldrModMachOObjDoImports(pThis, NewBaseAddress, pfnGetImport, pvUser);
+ if (RT_SUCCESS(rc))
+ rc = kldrModMachOObjDoFixups(pThis, pvBits, NewBaseAddress);
+
+ }
+ else
+ {
+ rc = rtldrMachODylibLoadFixupsAndVirginData(pThis, (uint8_t const *)pvBits);
+ if (RT_SUCCESS(rc))
+ rc = kldrModMachODylibDoImports(pThis, NewBaseAddress, pfnGetImport, pvUser);
+ if (RT_SUCCESS(rc))
+ rc = kldrModMachODylibDoIndirectSymbols(pThis, pvBits, NewBaseAddress - OldBaseAddress);
+ if (RT_SUCCESS(rc))
+ rc = kldrModMachODylibDoFixups(pThis, pvBits, NewBaseAddress);
+ }
+
+ /*
+ * Construct the global offset table if necessary, it's always the last
+ * segment when present.
+ */
+ if (RT_SUCCESS(rc) && pThis->fMakeGot)
+ rc = kldrModMachOMakeGOT(pThis, pvBits, NewBaseAddress);
+
+ return rc;
+}
+
+
+/**
+ * Builds the GOT.
+ *
+ * Assumes the symbol table has all external symbols resolved correctly and that
+ * the bits has been cleared up front.
+ */
+static int kldrModMachOMakeGOT(PRTLDRMODMACHO pThis, void *pvBits, RTLDRADDR NewBaseAddress)
+{
+ uint32_t iSym = pThis->cSymbols;
+ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE
+ || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE)
+ {
+ macho_nlist_32_t const *paSyms = (macho_nlist_32_t const *)pThis->pvaSymbols;
+ uint32_t *paGOT = (uint32_t *)((uint8_t *)pvBits + pThis->GotRVA);
+ while (iSym-- > 0)
+ switch (paSyms[iSym].n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)paSyms[iSym].n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSymSect = &pThis->paSections[paSyms[iSym].n_sect - 1];
+ paGOT[iSym] = (uint32_t)(paSyms[iSym].n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress);
+ break;
+ }
+
+ case MACHO_N_UNDF:
+ case MACHO_N_ABS:
+ paGOT[iSym] = paSyms[iSym].n_value;
+ break;
+ }
+ }
+ else
+ {
+ macho_nlist_64_t const *paSyms = (macho_nlist_64_t const *)pThis->pvaSymbols;
+ uint64_t *paGOT = (uint64_t *)((uint8_t *)pvBits + pThis->GotRVA);
+ while (iSym-- > 0)
+ {
+ switch (paSyms[iSym].n_type & MACHO_N_TYPE)
+ {
+ case MACHO_N_SECT:
+ {
+ PRTLDRMODMACHOSECT pSymSect;
+ RTLDRMODMACHO_CHECK_RETURN((uint32_t)paSyms[iSym].n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL);
+ pSymSect = &pThis->paSections[paSyms[iSym].n_sect - 1];
+ paGOT[iSym] = paSyms[iSym].n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress;
+ break;
+ }
+
+ case MACHO_N_UNDF:
+ case MACHO_N_ABS:
+ paGOT[iSym] = paSyms[iSym].n_value;
+ break;
+ }
+ }
+
+ if (pThis->JmpStubsRVA != NIL_RTLDRADDR)
+ {
+ iSym = pThis->cSymbols;
+ switch (pThis->Hdr.cputype)
+ {
+ /*
+ * AMD64 is simple since the GOT and the indirect jmps are parallel
+ * arrays with entries of the same size. The relative offset will
+ * be the the same for each entry, kind of nice. :-)
+ */
+ case CPU_TYPE_X86_64:
+ {
+ uint64_t *paJmps = (uint64_t *)((uint8_t *)pvBits + pThis->JmpStubsRVA);
+ int32_t off;
+ uint64_t u64Tmpl;
+ union
+ {
+ uint8_t ab[8];
+ uint64_t u64;
+ } Tmpl;
+
+ /* create the template. */
+ off = (int32_t)(pThis->GotRVA - (pThis->JmpStubsRVA + 6));
+ Tmpl.ab[0] = 0xff; /* jmp [GOT-entry wrt RIP] */
+ Tmpl.ab[1] = 0x25;
+ Tmpl.ab[2] = off & 0xff;
+ Tmpl.ab[3] = (off >> 8) & 0xff;
+ Tmpl.ab[4] = (off >> 16) & 0xff;
+ Tmpl.ab[5] = (off >> 24) & 0xff;
+ Tmpl.ab[6] = 0xcc;
+ Tmpl.ab[7] = 0xcc;
+ u64Tmpl = Tmpl.u64;
+
+ /* copy the template to every jmp table entry. */
+ while (iSym-- > 0)
+ paJmps[iSym] = u64Tmpl;
+ break;
+ }
+
+ default:
+ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO);
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnEnumSegments}
+ */
+static DECLCALLBACK(int) rtldrMachO_EnumSegments(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ uint32_t const cSegments = pThis->cSegments;
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ {
+ int rc = pfnCallback(pMod, &pThis->aSegments[iSeg].SegInfo, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnLinkAddressToSegOffset}
+ */
+static DECLCALLBACK(int) rtldrMachO_LinkAddressToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress,
+ uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ uint32_t const cSegments = pThis->cSegments;
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ if (pThis->aSegments[iSeg].SegInfo.RVA != NIL_RTLDRADDR)
+ {
+ Assert(pThis->aSegments[iSeg].SegInfo.cbMapped != NIL_RTLDRADDR);
+ RTLDRADDR offSeg = LinkAddress - pThis->aSegments[iSeg].SegInfo.LinkAddress;
+ if ( offSeg < pThis->aSegments[iSeg].SegInfo.cbMapped
+ || offSeg < pThis->aSegments[iSeg].SegInfo.cb)
+ {
+ *piSeg = iSeg;
+ *poffSeg = offSeg;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_LDR_INVALID_LINK_ADDRESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnLinkAddressToRva}
+ */
+static DECLCALLBACK(int) rtldrMachO_LinkAddressToRva(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ uint32_t const cSegments = pThis->cSegments;
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ if (pThis->aSegments[iSeg].SegInfo.RVA != NIL_RTLDRADDR)
+ {
+ Assert(pThis->aSegments[iSeg].SegInfo.cbMapped != NIL_RTLDRADDR);
+ RTLDRADDR offSeg = LinkAddress - pThis->aSegments[iSeg].SegInfo.LinkAddress;
+ if ( offSeg < pThis->aSegments[iSeg].SegInfo.cbMapped
+ || offSeg < pThis->aSegments[iSeg].SegInfo.cb)
+ {
+ *pRva = pThis->aSegments[iSeg].SegInfo.RVA + offSeg;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_LDR_INVALID_RVA;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnSegOffsetToRva}
+ */
+static DECLCALLBACK(int) rtldrMachO_SegOffsetToRva(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg, PRTLDRADDR pRva)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+
+ if (iSeg >= pThis->cSegments)
+ return VERR_LDR_INVALID_SEG_OFFSET;
+ RTLDRMODMACHOSEG const *pSegment = &pThis->aSegments[iSeg];
+
+ if (pSegment->SegInfo.RVA == NIL_RTLDRADDR)
+ return VERR_LDR_INVALID_SEG_OFFSET;
+
+ if ( offSeg > pSegment->SegInfo.cbMapped
+ && offSeg > pSegment->SegInfo.cb
+ && ( pSegment->SegInfo.cbFile < 0
+ || offSeg > (uint64_t)pSegment->SegInfo.cbFile))
+ return VERR_LDR_INVALID_SEG_OFFSET;
+
+ *pRva = pSegment->SegInfo.RVA + offSeg;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnRvaToSegOffset}
+ */
+static DECLCALLBACK(int) rtldrMachO_RvaToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR Rva, uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ uint32_t const cSegments = pThis->cSegments;
+ for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++)
+ if (pThis->aSegments[iSeg].SegInfo.RVA != NIL_RTLDRADDR)
+ {
+ Assert(pThis->aSegments[iSeg].SegInfo.cbMapped != NIL_RTLDRADDR);
+ RTLDRADDR offSeg = Rva - pThis->aSegments[iSeg].SegInfo.RVA;
+ if ( offSeg < pThis->aSegments[iSeg].SegInfo.cbMapped
+ || offSeg < pThis->aSegments[iSeg].SegInfo.cb)
+ {
+ *piSeg = iSeg;
+ *poffSeg = offSeg;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_LDR_INVALID_RVA;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnReadDbgInfo}
+ */
+static DECLCALLBACK(int) rtldrMachO_ReadDbgInfo(PRTLDRMODINTERNAL pMod, uint32_t iDbgInfo, RTFOFF off, size_t cb, void *pvBuf)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+
+ /** @todo May have to apply fixups here. */
+ if (iDbgInfo < pThis->cSections)
+ return pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvBuf, cb, off);
+ return VERR_OUT_OF_RANGE;
+}
+
+
+/**
+ * Loads the code signing blob if necessary (RTLDRMODMACHO::PtrCodeSignature).
+ *
+ * @returns IPRT status code.
+ * @param pThis The mach-o instance.
+ */
+static int rtldrMachO_LoadSignatureBlob(PRTLDRMODMACHO pThis)
+{
+ Assert(pThis->cbCodeSignature > 0);
+ if (pThis->PtrCodeSignature.pb != NULL)
+ return VINF_SUCCESS;
+
+ if ( pThis->cbCodeSignature > sizeof(RTCRAPLCSHDR)
+ && pThis->cbCodeSignature <= _1M)
+ {
+ /* Allocate and read. */
+ void *pv = RTMemAllocZ(RT_ALIGN_Z(pThis->cbCodeSignature, 16));
+ AssertReturn(pv, VERR_NO_MEMORY);
+ int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pv, pThis->cbCodeSignature,
+ pThis->offImage + pThis->offCodeSignature);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check blob signature. */
+ PCRTCRAPLCSSUPERBLOB pSuper = (PCRTCRAPLCSSUPERBLOB)pv;
+ if (pSuper->Hdr.uMagic == RTCRAPLCS_MAGIC_EMBEDDED_SIGNATURE)
+ {
+ uint32_t cbHdr = RT_BE2H_U32(pSuper->Hdr.cb);
+ uint32_t cSlots = RT_BE2H_U32(pSuper->cSlots);
+ if ( cbHdr <= pThis->cbCodeSignature
+ && cbHdr > RT_UOFFSETOF(RTCRAPLCSSUPERBLOB, aSlots)
+ && cSlots > 0
+ && cSlots < 128
+ && RT_UOFFSETOF_DYN(RTCRAPLCSSUPERBLOB, aSlots[cSlots]) <= cbHdr)
+ {
+ pThis->PtrCodeSignature.pSuper = pSuper;
+ return VINF_SUCCESS;
+ }
+ rc = VERR_LDRVI_BAD_CERT_HDR_LENGTH;
+ }
+ else
+ rc = VERR_LDRVI_BAD_CERT_HDR_TYPE;
+ }
+ RTMemFree(pv);
+ return rc;
+ }
+ return VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY;
+}
+
+
+/**
+ * Handles a RTLDRPROP_PKCS7_SIGNED_DATA query.
+ */
+static int rtldrMachO_QueryPkcs7SignedData(PRTLDRMODMACHO pThis, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ int rc = rtldrMachO_LoadSignatureBlob(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Locate the signature slot.
+ */
+ uint32_t iSlot = RT_BE2H_U32(pThis->PtrCodeSignature.pSuper->cSlots);
+ PCRTCRAPLCSBLOBSLOT pSlot = &pThis->PtrCodeSignature.pSuper->aSlots[iSlot];
+ while (iSlot-- > 0)
+ {
+ pSlot--;
+ if (pSlot->uType == RTCRAPLCS_SLOT_SIGNATURE)
+ {
+ /*
+ * Validate the data offset.
+ */
+ uint32_t offData = RT_BE2H_U32(pSlot->offData);
+ if ( offData < pThis->cbCodeSignature - sizeof(RTCRAPLCSHDR)
+ || !(offData & 3) )
+ {
+ /*
+ * The data is prefixed by a header with magic set to blob wrapper.
+ * Check that the size is within the bounds of the code signing blob.
+ */
+ PCRTCRAPLCSHDR pHdr = (PCRTCRAPLCSHDR)&pThis->PtrCodeSignature.pb[offData];
+ if (pHdr->uMagic == RTCRAPLCS_MAGIC_BLOBWRAPPER)
+ {
+ uint32_t cbData = RT_BE2H_U32(pHdr->cb);
+ uint32_t cbMax = pThis->cbCodeSignature - offData ;
+ if ( cbData <= cbMax
+ && cbData > sizeof(RTCRAPLCSHDR))
+ {
+ /*
+ * Copy out the requested data.
+ */
+ *pcbRet = cbData;
+ if (cbData <= cbBuf)
+ {
+ memcpy(pvBuf, pHdr + 1, cbData);
+ return VINF_SUCCESS;
+ }
+ memcpy(pvBuf, pHdr + 1, cbBuf);
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ return VERR_LDRVI_BAD_CERT_FORMAT;
+ }
+ }
+ rc = VERR_NOT_FOUND;
+ }
+ return rc;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnQueryProp} */
+static DECLCALLBACK(int) rtldrMachO_QueryProp(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits,
+ void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ int rc = VERR_NOT_FOUND;
+ switch (enmProp)
+ {
+ case RTLDRPROP_UUID:
+ Assert(cbBuf >= sizeof(pThis->abImageUuid));
+ if (!ASMMemIsZero(pThis->abImageUuid, sizeof(pThis->abImageUuid)))
+ {
+ *pcbRet = sizeof(pThis->abImageUuid);
+ memcpy(pvBuf, pThis->abImageUuid, sizeof(pThis->abImageUuid));
+ return VINF_SUCCESS;
+ }
+ break;
+
+ case RTLDRPROP_FILE_OFF_HEADER:
+ Assert(cbBuf == sizeof(uint32_t) || cbBuf == sizeof(uint64_t));
+ if (cbBuf == sizeof(uint32_t))
+ *(uint32_t *)pvBuf = pThis->offImage;
+ else
+ *(uint64_t *)pvBuf = pThis->offImage;
+ return VINF_SUCCESS;
+
+ case RTLDRPROP_IS_SIGNED:
+ Assert(cbBuf == sizeof(bool));
+ Assert(*pcbRet == cbBuf);
+ *(bool *)pvBuf = pThis->cbCodeSignature > 0;
+ return VINF_SUCCESS;
+
+ case RTLDRPROP_PKCS7_SIGNED_DATA:
+ if (pThis->cbCodeSignature > 0)
+ return rtldrMachO_QueryPkcs7SignedData(pThis, pvBuf, cbBuf, pcbRet);
+ break;
+
+
+#if 0 /** @todo return LC_ID_DYLIB */
+ case RTLDRPROP_INTERNAL_NAME:
+#endif
+
+ default:
+ break;
+ }
+ NOREF(cbBuf);
+ RT_NOREF_PV(pvBits);
+ return rc;
+}
+
+
+#ifndef IPRT_WITHOUT_LDR_VERIFY
+
+/**
+ * Decodes the signature blob at RTLDRMODMACHO::PtrCodeSignature.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module instance.
+ * @param ppSignature Where to return the decoded signature data.
+ * @param pErrInfo Where to supply extra error details. Optional.
+ */
+static int rtldrMachO_VerifySignatureDecode(PRTLDRMODMACHO pThis, PRTLDRMACHOSIGNATURE *ppSignature, PRTERRINFO pErrInfo)
+{
+ Assert(pThis->PtrCodeSignature.pSuper != NULL);
+
+ /*
+ * Allocate and init decoded signature data structure.
+ */
+ PRTLDRMACHOSIGNATURE pSignature = (PRTLDRMACHOSIGNATURE)RTMemTmpAllocZ(sizeof(*pSignature));
+ *ppSignature = pSignature;
+ if (!pSignature)
+ return VERR_NO_TMP_MEMORY;
+ pSignature->idxPkcs7 = UINT32_MAX;
+
+ /*
+ * Parse the slots, validating the slot headers.
+ */
+ PCRTCRAPLCSSUPERBLOB pSuper = pThis->PtrCodeSignature.pSuper;
+ uint32_t const cSlots = RT_BE2H_U32(pSuper->cSlots);
+ uint32_t const offFirst = RT_UOFFSETOF_DYN(RTCRAPLCSSUPERBLOB, aSlots[cSlots]);
+ uint32_t const cbBlob = RT_BE2H_U32(pSuper->Hdr.cb);
+ for (uint32_t iSlot = 0; iSlot < cSlots; iSlot++)
+ {
+ /*
+ * Check that the data offset is valid. There appears to be no alignment
+ * requirements here, which is a little weird consindering the PPC heritage.
+ */
+ uint32_t const offData = RT_BE2H_U32(pSuper->aSlots[iSlot].offData);
+ if ( offData < offFirst
+ || offData > cbBlob - sizeof(RTCRAPLCSHDR))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u has an invalid data offset: %#x (min %#x, max %#x-4)",
+ iSlot, offData, offFirst, cbBlob);
+ uint32_t const cbMaxData = cbBlob - offData;
+
+ /*
+ * PKCS#7/CMS signature.
+ */
+ if (pSuper->aSlots[iSlot].uType == RTCRAPLCS_SLOT_SIGNATURE)
+ {
+ if (pSignature->idxPkcs7 != UINT32_MAX)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Already have PKCS#7 data in slot %#u", iSlot, pSignature->idxPkcs7);
+ PCRTCRAPLCSHDR pHdr = (PCRTCRAPLCSHDR)&pThis->PtrCodeSignature.pb[offData];
+ if (pHdr->uMagic != RTCRAPLCS_MAGIC_BLOBWRAPPER)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Invalid PKCS#7 wrapper magic: %#x", iSlot, RT_BE2H_U32(pHdr->uMagic));
+ uint32_t const cb = RT_BE2H_U32(pHdr->cb);
+ if (cb > cbMaxData || cb < sizeof(*pHdr) + 2U)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Invalid PKCS#7 size is out of bound: %#x (min %#x, max %#x)",
+ iSlot, cb, sizeof(*pHdr) + 2, cbMaxData);
+ pSignature->idxPkcs7 = iSlot;
+ pSignature->pbPkcs7 = (uint8_t const *)(pHdr + 1);
+ pSignature->cbPkcs7 = cb - sizeof(*pHdr);
+ }
+ /*
+ * Code directories.
+ */
+ else if ( pSuper->aSlots[iSlot].uType == RTCRAPLCS_SLOT_CODEDIRECTORY
+ || ( RT_BE2H_U32(pSuper->aSlots[iSlot].uType) - RT_BE2H_U32_C(RTCRAPLCS_SLOT_ALTERNATE_CODEDIRECTORIES)
+ < RTCRAPLCS_SLOT_ALTERNATE_CODEDIRECTORIES_COUNT))
+ {
+ /* Make sure we don't get too many code directories and that the first one is a regular one. */
+ if (pSignature->cCodeDirs >= RT_ELEMENTS(pSignature->aCodeDirs))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Too many code directory slots (%u found thus far)",
+ iSlot, pSignature->cCodeDirs + 1);
+ if ( pSuper->aSlots[iSlot].uType == RTCRAPLCS_SLOT_CODEDIRECTORY
+ && pSignature->cCodeDirs > 0)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Already have primary code directory in slot #%u",
+ iSlot, pSignature->aCodeDirs[0].uSlot);
+ if ( pSuper->aSlots[iSlot].uType != RTCRAPLCS_SLOT_CODEDIRECTORY /* lazy bird */
+ && pSignature->cCodeDirs == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Expected alternative code directory after the primary one", iSlot);
+
+ /* Check data header: */
+ if (cbMaxData < RT_UOFFSETOF(RTCRAPLCSCODEDIRECTORY, uUnused1))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Insufficient data vailable for code directory (max %#x)", iSlot, cbMaxData);
+
+ PCRTCRAPLCSCODEDIRECTORY pCodeDir = (PCRTCRAPLCSCODEDIRECTORY)&pThis->PtrCodeSignature.pb[offData];
+ if (pCodeDir->Hdr.uMagic != RTCRAPLCS_MAGIC_CODEDIRECTORY)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Invalid code directory magic: %#x", iSlot, RT_BE2H_U32(pCodeDir->Hdr.uMagic));
+ uint32_t const cbCodeDir = RT_BE2H_U32(pCodeDir->Hdr.cb);
+ if (cbCodeDir > cbMaxData || cbCodeDir < RT_UOFFSETOF(RTCRAPLCSCODEDIRECTORY, offScatter))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Code directory size is out of bound: %#x (min %#x, max %#x)",
+ iSlot, cbCodeDir, RT_UOFFSETOF(RTCRAPLCSCODEDIRECTORY, offScatter), cbMaxData);
+ pSignature->aCodeDirs[pSignature->cCodeDirs].pCodeDir = pCodeDir;
+ pSignature->aCodeDirs[pSignature->cCodeDirs].cb = cbCodeDir;
+
+ /* Check Version: */
+ uint32_t const uVersion = RT_BE2H_U32(pCodeDir->uVersion);
+ if ( uVersion < RTCRAPLCS_VER_2_0
+ || uVersion >= RT_MAKE_U32(0, 3))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Code directory version is out of bounds: %#07x", iSlot, uVersion);
+ uint32_t cbSelf = uVersion >= RTCRAPLCS_VER_SUPPORTS_EXEC_SEG ? RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, fExecSeg)
+ : uVersion >= RTCRAPLCS_VER_SUPPORTS_CODE_LIMIT_64 ? RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, cbCodeLimit64)
+ : uVersion >= RTCRAPLCS_VER_SUPPORTS_TEAMID ? RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, offTeamId)
+ : uVersion >= RTCRAPLCS_VER_SUPPORTS_SCATTER ? RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, offScatter)
+ : RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, uUnused1);
+ if (cbSelf > cbCodeDir)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Code directory size is out of bound: %#x (min %#x, max %#x)",
+ iSlot, cbCodeDir, cbSelf, cbCodeDir);
+
+ /* hash type and size. */
+ uint8_t cbHash;
+ RTDIGESTTYPE enmDigest;
+ switch (pCodeDir->bHashType)
+ {
+ case RTCRAPLCS_HASHTYPE_SHA1:
+ enmDigest = RTDIGESTTYPE_SHA1;
+ cbHash = RTSHA1_HASH_SIZE;
+ break;
+ case RTCRAPLCS_HASHTYPE_SHA256:
+ enmDigest = RTDIGESTTYPE_SHA256;
+ cbHash = RTSHA256_HASH_SIZE;
+ break;
+ case RTCRAPLCS_HASHTYPE_SHA256_TRUNCATED:
+ enmDigest = RTDIGESTTYPE_SHA256;
+ cbHash = RTSHA1_HASH_SIZE; /* truncated to SHA-1 size. */
+ break;
+ case RTCRAPLCS_HASHTYPE_SHA384:
+ enmDigest = RTDIGESTTYPE_SHA384;
+ cbHash = RTSHA384_HASH_SIZE;
+ break;
+ default:
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Unknown hash type %#x (LB %#x)",
+ iSlot, pCodeDir->bHashType, pCodeDir->cbHash);
+ }
+ pSignature->aCodeDirs[pSignature->cCodeDirs].enmDigest = enmDigest;
+ if (pCodeDir->cbHash != cbHash)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Unexpected hash size for %s: %#x, expected %#x",
+ iSlot, RTCrDigestTypeToName(enmDigest), pCodeDir->cbHash, cbHash);
+
+ /* Hash slot offset and counts. Special slots are counted backwards from offHashSlots. */
+ uint32_t const cSpecialSlots = RT_BE2H_U32(pCodeDir->cSpecialSlots);
+ if (cSpecialSlots > 256)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Too many special slots: %#x", iSlot, cSpecialSlots);
+ uint32_t const cCodeSlots = RT_BE2H_U32(pCodeDir->cCodeSlots);
+ if ( cCodeSlots >= UINT32_MAX / 2
+ || cCodeSlots + cSpecialSlots > (cbCodeDir - cbHash) / cbHash)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Too many code slots: %#x + %#x (max %#x)",
+ iSlot, cCodeSlots, cSpecialSlots, (cbCodeDir - cbHash) / cbHash);
+ uint32_t const offHashSlots = RT_BE2H_U32(pCodeDir->offHashSlots);
+ if ( offHashSlots > cbCodeDir - cCodeSlots * cbHash
+ || offHashSlots < cbSelf + cSpecialSlots * cbHash)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Code directory hash offset is out of bounds: %#x (min: %#x, max: %#x)",
+ iSlot, offHashSlots, cbSelf + cSpecialSlots * cbHash, cbCodeDir - cCodeSlots * cbHash);
+
+ /* page shift */
+ if (pCodeDir->cPageShift == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Unsupported page shift of zero in code directory", iSlot);
+ uint32_t cMaxPageShift;
+ if ( pThis->Core.enmArch == RTLDRARCH_AMD64
+ || pThis->Core.enmArch == RTLDRARCH_X86_32
+ || pThis->Core.enmArch == RTLDRARCH_ARM32)
+ cMaxPageShift = 12;
+ else if (pThis->Core.enmArch == RTLDRARCH_ARM64)
+ cMaxPageShift = 16; /* 16KB */
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Unsupported architecture: %d", pThis->Core.enmArch);
+ if ( pCodeDir->cPageShift < 12 /* */
+ || pCodeDir->cPageShift > cMaxPageShift)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Page shift in code directory is out of range: %d (min: 12, max: %d)",
+ iSlot, pCodeDir->cPageShift, cMaxPageShift);
+
+ /* code limit vs page shift and code hash slots */
+ uint32_t const cbCodeLimit32 = RT_BE2H_U32(pCodeDir->cbCodeLimit32);
+ uint32_t const cExpectedCodeHashes = pCodeDir->cPageShift == 0 ? 1
+ : (cbCodeLimit32 + RT_BIT_32(pCodeDir->cPageShift) - 1) >> pCodeDir->cPageShift;
+ if (cExpectedCodeHashes != cCodeSlots)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Code limit and page shift value does not match code hash slots: cbCodeLimit32=%#x cPageShift=%u -> %#x; cCodeSlots=%#x",
+ iSlot, cbCodeLimit32, pCodeDir->cPageShift, cExpectedCodeHashes, cCodeSlots);
+
+ /* Identifier offset: */
+ if (pCodeDir->offIdentifier)
+ {
+ uint32_t const offIdentifier = RT_BE2H_U32(pCodeDir->offIdentifier);
+ if ( offIdentifier < cbSelf
+ || offIdentifier >= cbCodeDir)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Identifier offset is out of bounds: %#x (min: %#x, max: %#x)",
+ iSlot, offIdentifier, cbSelf, cbCodeDir - 1);
+ int rc = RTStrValidateEncodingEx((char const *)pCodeDir + offIdentifier, cbCodeDir - offIdentifier,
+ RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Malformed identifier string: %Rrc", iSlot, rc);
+ }
+
+ /* Team identifier: */
+ if (cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, offTeamId) && pCodeDir->offTeamId)
+ {
+ uint32_t const offTeamId = RT_BE2H_U32(pCodeDir->offTeamId);
+ if ( offTeamId < cbSelf
+ || offTeamId >= cbCodeDir)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Team identifier offset is out of bounds: %#x (min: %#x, max: %#x)",
+ iSlot, offTeamId, cbSelf, cbCodeDir - 1);
+ int rc = RTStrValidateEncodingEx((char const *)pCodeDir + offTeamId, cbCodeDir - offTeamId,
+ RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Malformed team identifier string: %Rrc", iSlot, rc);
+ }
+
+ /* We don't support scatter. */
+ if (cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, offScatter) && pCodeDir->offScatter)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Scatter not supported.", iSlot);
+
+ /* We don't really support the 64-bit code limit either: */
+ if ( cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, cbCodeLimit64)
+ && pCodeDir->cbCodeLimit64
+ && RT_BE2H_U64(pCodeDir->cbCodeLimit64) != cbCodeLimit32)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: 64-bit code limit does not match 32-bit: %#RX64 vs %#RX32",
+ iSlot, RT_BE2H_U64(pCodeDir->cbCodeLimit64), cbCodeLimit32);
+
+ /* Check executable segment info if present: */
+ if ( cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, fExecSeg)
+ && ( pThis->offSeg0ForCodeSign != RT_BE2H_U64(pCodeDir->offExecSeg)
+ || pThis->cbSeg0ForCodeSign != RT_BE2H_U64(pCodeDir->cbExecSeg)
+ || pThis->fSeg0ForCodeSign != RT_BE2H_U64(pCodeDir->fExecSeg)) )
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Segment #0 info mismatch: @%#RX64 LB %#RX64 flags=%#RX64; expected @%#RX64 LB %#RX64 flags=%#RX64",
+ iSlot, RT_BE2H_U64(pCodeDir->offExecSeg), RT_BE2H_U64(pCodeDir->cbExecSeg),
+ RT_BE2H_U64(pCodeDir->fExecSeg), pThis->offSeg0ForCodeSign, pThis->cbSeg0ForCodeSign,
+ pThis->fSeg0ForCodeSign);
+
+ /* Check fields that must be zero (don't want anyone to use them to counter changes): */
+ if (pCodeDir->uUnused1 != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Unused field #1 is non-zero: %#x", iSlot, RT_BE2H_U32(pCodeDir->uUnused1));
+ if ( cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, uUnused2)
+ && pCodeDir->uUnused2 != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Unused field #2 is non-zero: %#x", iSlot, RT_BE2H_U32(pCodeDir->uUnused2));
+
+ /** @todo idPlatform values. */
+ /** @todo Check for gaps if we know the version number? Alignment? */
+
+ /* If first code directory, check that the code limit covers the whole image up to the signature data. */
+ if (pSignature->cCodeDirs == 0)
+ {
+ /** @todo verify the that the signature data is at the very end... */
+ if (cbCodeLimit32 != pThis->offCodeSignature)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Unexpected code limit: %#x, expected %#x",
+ iSlot, cbCodeLimit32, pThis->offCodeSignature);
+ }
+ /* Otherwise, check that the code limit matches the previous directories. */
+ else
+ for (uint32_t i = 0; i < pSignature->cCodeDirs; i++)
+ if (pSignature->aCodeDirs[i].pCodeDir->cbCodeLimit32 != pCodeDir->cbCodeLimit32)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Slot #%u: Code limit differs from previous directory: %#x, expected %#x",
+ iSlot, cbCodeLimit32, RT_BE2H_U32(pSignature->aCodeDirs[i].pCodeDir->cbCodeLimit32));
+
+ /* Commit the code dir entry: */
+ pSignature->aCodeDirs[pSignature->cCodeDirs++].uSlot = iSlot;
+ }
+ }
+
+ /*
+ * Check that we've got at least one code directory and one PKCS#7 signature.
+ */
+ if (pSignature->cCodeDirs == 0)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "No code directory slot in the code signature");
+ if (pSignature->idxPkcs7 == UINT32_MAX)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "No PKCS#7 slot in the code signature");
+
+ /*
+ * Decode the PKCS#7 signature.
+ */
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor, pSignature->pbPkcs7, pSignature->cbPkcs7,
+ pErrInfo, &g_RTAsn1DefaultAllocator, 0, "Mach-O-BLOB");
+ int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &pSignature->ContentInfo, "CI");
+ if (RT_SUCCESS(rc))
+ {
+ if (RTCrPkcs7ContentInfo_IsSignedData(&pSignature->ContentInfo))
+ {
+ pSignature->pSignedData = pSignature->ContentInfo.u.pSignedData;
+
+ /*
+ * Check that the signedData stuff adds up.
+ */
+ if (!strcmp(pSignature->pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID))
+ {
+ rc = RTCrPkcs7SignedData_CheckSanity(pSignature->pSignedData,
+ RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE /** @todo consider not piggy-backing on auth-code */
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
+ pErrInfo, "SD");
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID,
+ "Unexpected pSignedData.ContentInfo.ContentType.szObjId value: %s (expected %s)",
+ pSignature->pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, /** @todo error code*/
+ "PKCS#7 is not 'signedData': %s", pSignature->ContentInfo.ContentType.szObjId);
+ }
+ return rc;
+}
+
+/**
+ * Destroys the decoded signature data structure.
+ *
+ * @param pSignature The decoded signature data. Can be NULL.
+ */
+static void rtldrMachO_VerifySignatureDestroy(PRTLDRMACHOSIGNATURE pSignature)
+{
+ if (pSignature)
+ {
+ RTCrPkcs7ContentInfo_Delete(&pSignature->ContentInfo);
+ RTMemTmpFree(pSignature);
+ }
+}
+
+
+/**
+ * Worker for rtldrMachO_VerifySignatureValidatePkcs7Hashes that handles plists
+ * with code directory hashes inside them.
+ *
+ * It is assumed that these plist files was invented to handle alternative code
+ * directories.
+ *
+ * @note Putting an XML plist into the authenticated attribute list was
+ * probably not such a great idea, given all the optional and
+ * adjustable white-space padding. We should probably validate
+ * everything very strictly, limiting the elements, require certain
+ * attribute lists and even have strict expectations about the
+ * white-space, but right now let just make sure it's xml and get the
+ * data in the cdhashes array.
+ *
+ * @todo The code here is a little braindead and bulky. It should be
+ * possible to describe the expected XML structure using a tables.
+ */
+static int rtldrMachO_VerifySignatureValidateCdHashesPlist(PRTLDRMACHOSIGNATURE pSignature, char *pszPlist,
+ uint8_t *pbHash, uint32_t cbHash, PRTERRINFO pErrInfo)
+{
+ const char * const pszStart = pszPlist;
+#define CHECK_ISTR_AND_SKIP_OR_RETURN(a_szLead) \
+ do { \
+ if (!RTStrNICmp(pszPlist, RT_STR_TUPLE(a_szLead))) \
+ pszPlist += sizeof(a_szLead) - 1; \
+ else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, \
+ "Expected '%s' found '%.16s...' at %#zu in plist", a_szLead, pszPlist, pszPlist - pszStart); \
+ } while (0)
+#define CHECK_STR_AND_SKIP_OR_RETURN(a_szLead) \
+ do { \
+ if (!RTStrNCmp(pszPlist, RT_STR_TUPLE(a_szLead))) \
+ pszPlist += sizeof(a_szLead) - 1; \
+ else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, \
+ "Expected '%s' found '%.16s...' at %#zu in plist", a_szLead, pszPlist, pszPlist - pszStart); \
+ } while (0)
+#define SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN() \
+ do { /* currently only permitting spaces, tabs and newline, following char must be '<'. */ \
+ char chMacro; \
+ while ((chMacro = *pszPlist) == ' ' || chMacro == '\n' || chMacro == '\t') \
+ pszPlist++; \
+ if (chMacro == '<') { /* likely */ } \
+ else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, \
+ "Expected '<' found '%.16s...' at %#zu in plist", pszPlist, pszPlist - pszStart); \
+ } while (0)
+#define SKIP_SPACE_BEFORE_VALUE() \
+ do { /* currently only permitting spaces, tabs and newline. */ \
+ char chMacro; \
+ while ((chMacro = *pszPlist) == ' ' || chMacro == '\n' || chMacro == '\t') \
+ pszPlist++; \
+ } while (0)
+#define SKIP_REQUIRED_SPACE_BETWEEN_ATTRIBUTES_OR_RETURN() \
+ do { /* currently only permitting a single space */ \
+ if (pszPlist[0] == ' ' && pszPlist[1] != ' ') \
+ pszPlist++; \
+ else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, \
+ "Expected ' ' found '%.16s...' at %#zu in plist", pszPlist, pszPlist - pszStart); \
+ } while (0)
+
+ /* Example:
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>cdhashes</key>
+ <array>
+ <data>
+ hul2SSkDQFRXbGlt3AmCp25MU0Y=
+ </data>
+ <data>
+ N0kvxg0CJBNuZTq135PntAaRczw=
+ </data>
+ </array>
+</dict>
+</plist>
+ */
+
+ /* <?xml version="1.0" encoding="UTF-8"?> */
+ CHECK_STR_AND_SKIP_OR_RETURN("<?xml");
+ SKIP_REQUIRED_SPACE_BETWEEN_ATTRIBUTES_OR_RETURN();
+ CHECK_STR_AND_SKIP_OR_RETURN("version=\"1.0\"");
+ SKIP_REQUIRED_SPACE_BETWEEN_ATTRIBUTES_OR_RETURN();
+ CHECK_STR_AND_SKIP_OR_RETURN("encoding=\"UTF-8\"");
+ CHECK_STR_AND_SKIP_OR_RETURN("?>");
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+ /* <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> */
+ CHECK_STR_AND_SKIP_OR_RETURN("<!DOCTYPE");
+ SKIP_REQUIRED_SPACE_BETWEEN_ATTRIBUTES_OR_RETURN();
+ CHECK_STR_AND_SKIP_OR_RETURN("plist");
+ SKIP_REQUIRED_SPACE_BETWEEN_ATTRIBUTES_OR_RETURN();
+ CHECK_STR_AND_SKIP_OR_RETURN("PUBLIC");
+ SKIP_REQUIRED_SPACE_BETWEEN_ATTRIBUTES_OR_RETURN();
+ CHECK_STR_AND_SKIP_OR_RETURN("\"-//Apple//DTD PLIST 1.0//EN\"");
+ SKIP_REQUIRED_SPACE_BETWEEN_ATTRIBUTES_OR_RETURN();
+ CHECK_STR_AND_SKIP_OR_RETURN("\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"");
+ CHECK_STR_AND_SKIP_OR_RETURN(">");
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+ /* <plist version="1.0"> */
+ CHECK_STR_AND_SKIP_OR_RETURN("<plist");
+ SKIP_REQUIRED_SPACE_BETWEEN_ATTRIBUTES_OR_RETURN();
+ CHECK_STR_AND_SKIP_OR_RETURN("version=\"1.0\"");
+ CHECK_STR_AND_SKIP_OR_RETURN(">");
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+ /* <dict> */
+ CHECK_STR_AND_SKIP_OR_RETURN("<dict>");
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+ /* <key>cdhashes</key> */
+ CHECK_STR_AND_SKIP_OR_RETURN("<key>cdhashes</key>");
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+ /* <array> */
+ CHECK_STR_AND_SKIP_OR_RETURN("<array>");
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+ /*
+ * Repeated: <data>hul2SSkDQFRXbGlt3AmCp25MU0Y=</data>
+ */
+ uint32_t iCodeDir = 0;
+ for (;;)
+ {
+ /* Decode the binary data (base64) and skip it. */
+ CHECK_STR_AND_SKIP_OR_RETURN("<data>");
+ SKIP_SPACE_BEFORE_VALUE();
+
+ char ch;
+ size_t cchBase64 = 0;
+ while (RT_C_IS_ALNUM(ch = pszPlist[cchBase64]) || ch == '+' || ch == '/' || ch == '=')
+ cchBase64++;
+ size_t cbActualHash = cbHash;
+ char *pszEnd = NULL;
+ int rc = RTBase64DecodeEx(pszPlist, cchBase64, pbHash, cbHash, &cbActualHash, &pszEnd);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Failed to decode hash #%u in authenticated plist attribute: %Rrc (%.*s)",
+ iCodeDir, rc, cchBase64, pszPlist);
+ pszPlist += cchBase64;
+ AssertReturn(pszPlist == pszEnd, VERR_INTERNAL_ERROR_2);
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+ /* The binary hash data must be exactly the size of SHA1, larger
+ hash like SHA-256 and SHA-384 are truncated for some reason. */
+ if (cbActualHash != RTSHA1_HASH_SIZE)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Hash #%u in authenticated plist attribute has the wrong length: %u, exepcted %u",
+ iCodeDir, cbActualHash, RTSHA1_HASH_SIZE);
+
+ /* Skip closing tag. */
+ CHECK_STR_AND_SKIP_OR_RETURN("</data>");
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+
+ /* Calculate the hash and compare. */
+ RTCRDIGEST hDigest;
+ rc = RTCrDigestCreateByType(&hDigest, pSignature->aCodeDirs[iCodeDir].enmDigest);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrDigestUpdate(hDigest, pSignature->aCodeDirs[iCodeDir].pCodeDir, pSignature->aCodeDirs[iCodeDir].cb);
+ if (RT_SUCCESS(rc))
+ {
+ if (memcmp(pbHash, RTCrDigestGetHash(hDigest), cbActualHash) == 0)
+ rc = VINF_SUCCESS;
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_IMAGE_HASH_MISMATCH,
+ "Code directory #%u hash mismatch (plist):\n"
+ "signed: %.*Rhxs\n"
+ "our: %.*Rhxs\n",
+ iCodeDir, cbActualHash, pbHash,
+ RTCrDigestGetHashSize(hDigest), RTCrDigestGetHash(hDigest));
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "RTCrDigestUpdate failed: %Rrc", rc);
+ RTCrDigestRelease(hDigest);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "Failed to create a digest of type %u verifying code dir #%u: %Rrc",
+ pSignature->aCodeDirs[iCodeDir].enmDigest, iCodeDir, rc);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Advance.
+ */
+ iCodeDir++;
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+ if (RTStrNCmp(pszPlist, RT_STR_TUPLE("<data>")) == 0)
+ {
+ if (iCodeDir >= pSignature->cCodeDirs)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Authenticated plist attribute has too many code directories (%u in blob)",
+ pSignature->cCodeDirs);
+ }
+ else if (iCodeDir == pSignature->cCodeDirs)
+ break;
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Authenticated plist attribute does not include all code directors: %u out of %u",
+ iCodeDir, pSignature->cCodeDirs);
+ }
+
+ /*</array>*/
+ CHECK_STR_AND_SKIP_OR_RETURN("</array>");
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+ /*</dict>*/
+ CHECK_STR_AND_SKIP_OR_RETURN("</dict>");
+ SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN();
+
+ /*</plist>*/
+ CHECK_STR_AND_SKIP_OR_RETURN("</plist>");
+ SKIP_SPACE_BEFORE_VALUE();
+
+ if (*pszPlist == '\0')
+ return VINF_SUCCESS;
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Authenticated plist attribute has unexpected trailing content: %.32s", pszPlist);
+}
+
+
+/**
+ * Verifies the code directory hashes embedded in the PKCS\#7 data.
+ *
+ * @returns IPRT status code.
+ * @param pSignature The decoded signature data.
+ * @param pErrInfo Where to supply extra error details. Optional.
+ */
+static int rtldrMachO_VerifySignatureValidatePkcs7Hashes(PRTLDRMACHOSIGNATURE pSignature, PRTERRINFO pErrInfo)
+{
+ /*
+ * Look thru the authenticated attributes in the signer info array.
+ */
+ PRTCRPKCS7SIGNEDDATA pSignedData = pSignature->pSignedData;
+ for (uint32_t iSignerInfo = 0; iSignerInfo < pSignedData->SignerInfos.cItems; iSignerInfo++)
+ {
+ PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[iSignerInfo];
+ bool fMsgDigest = false;
+ bool fPlist = false;
+ for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->AuthenticatedAttributes.cItems; iAttrib++)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->AuthenticatedAttributes.papItems[iAttrib];
+ if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_MESSAGE_DIGEST_OID) == 0)
+ {
+ /*
+ * Validate the message digest while we're here.
+ */
+ AssertReturn(pAttrib->uValues.pOctetStrings && pAttrib->uValues.pOctetStrings->cItems == 1, VERR_INTERNAL_ERROR_5);
+
+ RTCRDIGEST hDigest;
+ int rc = RTCrDigestCreateByObjId(&hDigest, &pSignerInfo->DigestAlgorithm.Algorithm);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCrDigestUpdate(hDigest, pSignature->aCodeDirs[0].pCodeDir, pSignature->aCodeDirs[0].cb);
+ if (RT_SUCCESS(rc))
+ {
+ if (!RTCrDigestMatch(hDigest,
+ pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pv,
+ pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb))
+ rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MESSAGE_DIGEST_ATTRIB_MISMATCH,
+ "Authenticated message-digest attribute mismatch:\n"
+ "signed: %.*Rhxs\n"
+ "our: %.*Rhxs\n",
+ pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb,
+ pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pv,
+ RTCrDigestGetHashSize(hDigest), RTCrDigestGetHash(hDigest));
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "RTCrDigestUpdate failed: %Rrc", rc);
+ RTCrDigestRelease(hDigest);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, rc, "Failed to create a digest for OID %s: %Rrc",
+ pSignerInfo->DigestAlgorithm.Algorithm.szObjId, rc);
+ if (RT_FAILURE(rc))
+ return rc;
+ fMsgDigest = true;
+ }
+ else if (pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_APPLE_MULTI_CD_PLIST)
+ {
+ /*
+ * An XML (better be) property list with code directory hashes in it.
+ */
+ if (!pAttrib->uValues.pOctetStrings || pAttrib->uValues.pOctetStrings->cItems != 1)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Bad authenticated plist attribute");
+
+ uint32_t cch = pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb;
+ char const *pch = pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pch;
+ int rc = RTStrValidateEncodingEx(pch, cch, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Authenticated plist attribute is not valid UTF-8: %Rrc", rc);
+ uint32_t const cchMin = sizeof("<?xml?><plist><dict><key>cdhashes</key><array><data>hul2SSkDQFRXbGlt3AmCp25MU0Y=</data></array></dict></plist>") - 1;
+ if (cch < cchMin)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Authenticated plist attribute is too short: %#x, min: %#x", cch, cchMin);
+ if (cch > _64K)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT,
+ "Authenticated plist attribute is too long: %#x, max: 64KB", cch);
+
+ /* Copy the plist into a buffer and zero terminate it. Also allocate room for decoding a hash. */
+ const uint32_t cbMaxHash = 128;
+ char *pszTmp = (char *)RTMemTmpAlloc(cbMaxHash + cch + 3);
+ if (!pszTmp)
+ return VERR_NO_TMP_MEMORY;
+ pszTmp[cbMaxHash + cch] = '\0';
+ pszTmp[cbMaxHash + cch + 1] = '\0';
+ pszTmp[cbMaxHash + cch + 2] = '\0';
+ rc = rtldrMachO_VerifySignatureValidateCdHashesPlist(pSignature, (char *)memcpy(pszTmp + cbMaxHash, pch, cch),
+ (uint8_t *)pszTmp, cbMaxHash, pErrInfo);
+ RTMemTmpFree(pszTmp);
+ if (RT_FAILURE(rc))
+ return rc;
+ fPlist = true;
+ }
+ }
+ if (!fMsgDigest && pSignature->cCodeDirs > 1)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Missing authenticated message-digest attribute");
+ if (!fPlist && pSignature->cCodeDirs > 1)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Missing authenticated code directory hash plist attribute");
+ }
+ if (pSignedData->SignerInfos.cItems < 1)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "PKCS#7 signed data contains no signatures");
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Verifies the page hashes of the given code directory.
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module instance.
+ * @param pEntry The data entry for the code directory to validate.
+ * @param pbBuf Read buffer.
+ * @param cbBuf Buffer size.
+ * @param pErrInfo Where to supply extra error details. Optional.
+ */
+static int rtldrMachO_VerifySignatureValidateCodeDir(PRTLDRMODMACHO pThis, PRTLDRMACHCODEDIR pEntry,
+ uint8_t *pbBuf, uint32_t cbBuf, PRTERRINFO pErrInfo)
+{
+ RTCRDIGEST hDigest;
+ int rc = RTCrDigestCreateByType(&hDigest, pEntry->enmDigest);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTCRAPLCSCODEDIRECTORY pCodeDir = pEntry->pCodeDir;
+ PRTLDRREADER const pRdr = pThis->Core.pReader;
+ uint32_t cbCodeLimit = RT_BE2H_U32(pCodeDir->cbCodeLimit32);
+ uint32_t const cbPage = RT_BIT_32(pCodeDir->cPageShift);
+ uint32_t const cHashes = RT_BE2H_U32(pCodeDir->cCodeSlots);
+ uint8_t const cbHash = pCodeDir->cbHash;
+ uint8_t const *pbHash = (uint8_t const *)pCodeDir + RT_BE2H_U32(pCodeDir->offHashSlots);
+ RTFOFF offFile = pThis->offImage;
+ if ( RT_BE2H_U32(pCodeDir->uVersion) < RTCRAPLCS_VER_SUPPORTS_SCATTER
+ || pCodeDir->offScatter == 0)
+ {
+ /*
+ * Work the image in linear fashion.
+ */
+ for (uint32_t iHash = 0; iHash < cHashes; iHash++, pbHash += cbHash, cbCodeLimit -= cbPage)
+ {
+ RTFOFF const offPage = offFile;
+
+ /*
+ * Read and digest the data for the current hash page.
+ */
+ rc = RTCrDigestReset(hDigest);
+ AssertRCBreak(rc);
+ Assert(cbCodeLimit > cbPage || iHash + 1 == cHashes);
+ uint32_t cbLeft = iHash + 1 < cHashes ? cbPage : cbCodeLimit;
+ while (cbLeft > 0)
+ {
+ uint32_t const cbToRead = RT_MIN(cbBuf, cbLeft);
+ rc = pRdr->pfnRead(pRdr, pbBuf, cbToRead, offFile);
+ AssertRCBreak(rc);
+
+ rc = RTCrDigestUpdate(hDigest, pbBuf, cbToRead);
+ AssertRCBreak(rc);
+
+ offFile += cbToRead;
+ cbLeft -= cbToRead;
+ }
+ AssertRCBreak(rc);
+ rc = RTCrDigestFinal(hDigest, NULL, 0);
+ AssertRCBreak(rc);
+
+ /*
+ * Compare it.
+ * Note! Don't use RTCrDigestMatch here as there is a truncated SHA-256 variant.
+ */
+ if (memcmp(pbHash, RTCrDigestGetHash(hDigest), cbHash) != 0)
+ {
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_MISMATCH,
+ "Hash #%u (@%RX64 LB %#x) mismatch in code dir #%u: %.*Rhxs, expected %.*Rhxs",
+ iHash, offPage, cbPage, pEntry->uSlot, (int)cbHash, pbHash,
+ (int)cbHash, RTCrDigestGetHash(hDigest));
+ break;
+ }
+
+ }
+ }
+ /*
+ * Work the image in scattered fashion.
+ */
+ else
+ rc = VERR_INTERNAL_ERROR_4;
+
+ RTCrDigestRelease(hDigest);
+ }
+ return rc;
+}
+
+
+/**
+ * Verifies the page hashes of all the code directories
+ *
+ * @returns IPRT status code.
+ * @param pThis The Mach-O module instance.
+ * @param pSignature The decoded signature data.
+ * @param pErrInfo Where to supply extra error details. Optional.
+ */
+static int rtldrMachO_VerifySignatureValidateCodeDirs(PRTLDRMODMACHO pThis, PRTLDRMACHOSIGNATURE pSignature, PRTERRINFO pErrInfo)
+{
+ void *pvBuf = RTMemTmpAllocZ(_4K);
+ if (pvBuf)
+ {
+ int rc = VERR_INTERNAL_ERROR_3;
+ for (uint32_t i = 0; i < pSignature->cCodeDirs; i++)
+ {
+ rc = rtldrMachO_VerifySignatureValidateCodeDir(pThis, &pSignature->aCodeDirs[i], (uint8_t *)pvBuf, _4K, pErrInfo);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTMemTmpFree(pvBuf);
+ return rc;
+ }
+ return VERR_NO_TMP_MEMORY;
+}
+
+#endif /* !IPRT_WITHOUT_LDR_VERIFY*/
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnVerifySignature}
+ */
+static DECLCALLBACK(int)
+rtldrMachO_VerifySignature(PRTLDRMODINTERNAL pMod, PFNRTLDRVALIDATESIGNEDDATA pfnCallback, void *pvUser, PRTERRINFO pErrInfo)
+{
+#ifndef IPRT_WITHOUT_LDR_VERIFY
+ PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);
+ int rc = rtldrMachO_LoadSignatureBlob(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ PRTLDRMACHOSIGNATURE pSignature = NULL;
+ rc = rtldrMachO_VerifySignatureDecode(pThis, &pSignature, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtldrMachO_VerifySignatureValidatePkcs7Hashes(pSignature, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtldrMachO_VerifySignatureValidateCodeDirs(pThis, pSignature, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Finally, let the caller verify the certificate chain for the PKCS#7 bit.
+ */
+ RTLDRSIGNATUREINFO Info;
+ Info.iSignature = 0;
+ Info.cSignatures = 1;
+ Info.enmType = RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA;
+ Info.pvSignature = &pSignature->ContentInfo;
+ Info.cbSignature = sizeof(pSignature->ContentInfo);
+ Info.pvExternalData = pSignature->aCodeDirs[0].pCodeDir;
+ Info.cbExternalData = pSignature->aCodeDirs[0].cb;
+ rc = pfnCallback(&pThis->Core, &Info, pErrInfo, pvUser);
+ }
+ }
+ }
+ rtldrMachO_VerifySignatureDestroy(pSignature);
+ }
+ return rc;
+#else
+ RT_NOREF_PV(pMod); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(pvUser); RT_NOREF_PV(pErrInfo);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+
+/**
+ * Operations for a Mach-O module interpreter.
+ */
+static const RTLDROPS s_rtldrMachOOps=
+{
+ "mach-o",
+ rtldrMachO_Close,
+ NULL,
+ NULL /*pfnDone*/,
+ rtldrMachO_EnumSymbols,
+ /* ext */
+ rtldrMachO_GetImageSize,
+ rtldrMachO_GetBits,
+ rtldrMachO_RelocateBits,
+ rtldrMachO_GetSymbolEx,
+ NULL /*pfnQueryForwarderInfo*/,
+ rtldrMachO_EnumDbgInfo,
+ rtldrMachO_EnumSegments,
+ rtldrMachO_LinkAddressToSegOffset,
+ rtldrMachO_LinkAddressToRva,
+ rtldrMachO_SegOffsetToRva,
+ rtldrMachO_RvaToSegOffset,
+ rtldrMachO_ReadDbgInfo,
+ rtldrMachO_QueryProp,
+ rtldrMachO_VerifySignature,
+ NULL /*pfnHashImage*/,
+ NULL /*pfnUnwindFrame*/,
+ 42
+};
+
+
+/**
+ * Handles opening Mach-O images (non-fat).
+ */
+DECLHIDDEN(int) rtldrMachOOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, RTFOFF offImage,
+ PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo)
+{
+
+ /*
+ * Create the instance data and do a minimal header validation.
+ */
+ PRTLDRMODMACHO pThis = NULL;
+ int rc = kldrModMachODoCreate(pReader, offImage, fFlags, &pThis, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Match up against the requested CPU architecture.
+ */
+ if ( enmArch == RTLDRARCH_WHATEVER
+ || pThis->Core.enmArch == enmArch)
+ {
+ pThis->Core.pOps = &s_rtldrMachOOps;
+ pThis->Core.u32Magic = RTLDRMOD_MAGIC;
+ *phLdrMod = &pThis->Core;
+ return VINF_SUCCESS;
+ }
+ rc = VERR_LDR_ARCH_MISMATCH;
+ }
+ if (pThis)
+ {
+ RTMemFree(pThis->pbLoadCommands);
+ RTMemFree(pThis);
+ }
+ return rc;
+
+}
+
+
+/**
+ * Handles opening FAT Mach-O image.
+ */
+DECLHIDDEN(int) rtldrFatOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo)
+{
+ fat_header_t FatHdr;
+ int rc = pReader->pfnRead(pReader, &FatHdr, sizeof(FatHdr), 0);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, rc, "Read error at offset 0: %Rrc", rc);
+
+ if (FatHdr.magic == IMAGE_FAT_SIGNATURE)
+ { /* likely */ }
+ else if (FatHdr.magic == IMAGE_FAT_SIGNATURE_OE)
+ FatHdr.nfat_arch = RT_BSWAP_U32(FatHdr.nfat_arch);
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_INVALID_EXE_SIGNATURE, "magic=%#x", FatHdr.magic);
+ if (FatHdr.nfat_arch < 64)
+ return RTErrInfoSetF(pErrInfo, VERR_INVALID_EXE_SIGNATURE, "Bad nfat_arch value: %#x", FatHdr.nfat_arch);
+
+ uint32_t offEntry = sizeof(FatHdr);
+ for (uint32_t i = 0; i < FatHdr.nfat_arch; i++, offEntry += sizeof(fat_arch_t))
+ {
+ fat_arch_t FatEntry;
+ rc = pReader->pfnRead(pReader, &FatEntry, sizeof(FatEntry), offEntry);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, rc, "Read error at offset 0: %Rrc", rc);
+ if (FatHdr.magic == IMAGE_FAT_SIGNATURE_OE)
+ {
+ FatEntry.cputype = (int32_t)RT_BSWAP_U32((uint32_t)FatEntry.cputype);
+ //FatEntry.cpusubtype = (int32_t)RT_BSWAP_U32((uint32_t)FatEntry.cpusubtype);
+ FatEntry.offset = RT_BSWAP_U32(FatEntry.offset);
+ //FatEntry.size = RT_BSWAP_U32(FatEntry.size);
+ //FatEntry.align = RT_BSWAP_U32(FatEntry.align);
+ }
+
+ /*
+ * Match enmArch.
+ */
+ bool fMatch = false;
+ switch (enmArch)
+ {
+ case RTLDRARCH_WHATEVER:
+ fMatch = true;
+ break;
+
+ case RTLDRARCH_X86_32:
+ fMatch = FatEntry.cputype == CPU_TYPE_X86;
+ break;
+
+ case RTLDRARCH_AMD64:
+ fMatch = FatEntry.cputype == CPU_TYPE_X86_64;
+ break;
+
+ case RTLDRARCH_ARM32:
+ fMatch = FatEntry.cputype == CPU_TYPE_ARM32;
+ break;
+
+ case RTLDRARCH_ARM64:
+ fMatch = FatEntry.cputype == CPU_TYPE_ARM64;
+ break;
+
+ case RTLDRARCH_X86_16:
+ fMatch = false;
+ break;
+
+ case RTLDRARCH_INVALID:
+ case RTLDRARCH_HOST:
+ case RTLDRARCH_END:
+ case RTLDRARCH_32BIT_HACK:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+ if (fMatch)
+ return rtldrMachOOpen(pReader, fFlags, enmArch, FatEntry.offset, phLdrMod, pErrInfo);
+ }
+
+ return VERR_LDR_ARCH_MISMATCH;
+
+}
+
diff --git a/src/VBox/Runtime/common/ldr/ldrMemory.cpp b/src/VBox/Runtime/common/ldr/ldrMemory.cpp
new file mode 100644
index 00000000..80e8dce2
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrMemory.cpp
@@ -0,0 +1,336 @@
+
+/* $Id: ldrMemory.cpp $ */
+/** @file
+ * IPRT - Binary Image Loader, The Memory/Debugger Oriented Parts.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/log.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include "internal/ldr.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Memory reader (for debuggers) instance.
+ */
+typedef struct RTLDRRDRMEM
+{
+ /** The core. */
+ RTLDRREADER Core;
+ /** The size of the image. */
+ size_t cbImage;
+ /** The current offset. */
+ size_t offCur;
+
+ /** User parameter for the reader and destructor functions.*/
+ void *pvUser;
+ /** Read function. */
+ PFNRTLDRRDRMEMREAD pfnRead;
+ /** Destructor callback. */
+ PFNRTLDRRDRMEMDTOR pfnDtor;
+
+ /** Mapping of the file. */
+ void *pvMapping;
+ /** Mapping usage counter. */
+ uint32_t cMappings;
+
+ /** The fake filename (variable size). */
+ char szName[1];
+} RTLDRRDRMEM;
+/** Memory based loader reader instance data. */
+typedef RTLDRRDRMEM *PRTLDRRDRMEM;
+
+
+/**
+ * @callback_method_impl{FNRTLDRRDRMEMDTOR,
+ * Default destructor - pvUser points to the image memory block}
+ */
+static DECLCALLBACK(void) rtldrRdrMemDefaultDtor(void *pvUser, size_t cbImage)
+{
+ RT_NOREF(cbImage);
+ RTMemFree(pvUser);
+}
+
+
+/**
+ * @callback_method_impl{FNRTLDRRDRMEMREAD,
+ * Default memory reader - pvUser points to the image memory block}
+ */
+static DECLCALLBACK(int) rtldrRdrMemDefaultReader(void *pvBuf, size_t cb, size_t off, void *pvUser)
+{
+ memcpy(pvBuf, (uint8_t *)pvUser + off, cb);
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTLDRREADER,pfnRead} */
+static DECLCALLBACK(int) rtldrRdrMem_Read(PRTLDRREADER pReader, void *pvBuf, size_t cb, RTFOFF off)
+{
+ PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader;
+
+ AssertReturn(off >= 0, VERR_INVALID_PARAMETER);
+ if ( cb > pThis->cbImage
+ || off > (RTFOFF)pThis->cbImage
+ || off + (RTFOFF)cb > (RTFOFF)pThis->cbImage)
+ {
+ pThis->offCur = pThis->cbImage;
+ return VERR_EOF;
+ }
+
+ int rc = pThis->pfnRead(pvBuf, cb, (size_t)off, pThis->pvUser);
+ if (RT_SUCCESS(rc))
+ pThis->offCur = (size_t)off + cb;
+ else
+ pThis->offCur = ~(size_t)0;
+ return rc;
+}
+
+
+/** @interface_method_impl{RTLDRREADER,pfnTell} */
+static DECLCALLBACK(RTFOFF) rtldrRdrMem_Tell(PRTLDRREADER pReader)
+{
+ PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader;
+ return pThis->offCur;
+}
+
+
+/** @interface_method_impl{RTLDRREADER,pfnSize} */
+static DECLCALLBACK(uint64_t) rtldrRdrMem_Size(PRTLDRREADER pReader)
+{
+ PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader;
+ return pThis->cbImage;
+}
+
+
+/** @interface_method_impl{RTLDRREADER,pfnLogName} */
+static DECLCALLBACK(const char *) rtldrRdrMem_LogName(PRTLDRREADER pReader)
+{
+ PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader;
+ return pThis->szName;
+}
+
+
+/** @interface_method_impl{RTLDRREADER,pfnMap} */
+static DECLCALLBACK(int) rtldrRdrMem_Map(PRTLDRREADER pReader, const void **ppvBits)
+{
+ PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader;
+
+ /*
+ * Already mapped?
+ */
+ if (pThis->pvMapping)
+ {
+ pThis->cMappings++;
+ *ppvBits = pThis->pvMapping;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Allocate memory.
+ */
+ pThis->pvMapping = RTMemAlloc(pThis->cbImage);
+ if (!pThis->pvMapping)
+ return VERR_NO_MEMORY;
+ int rc = rtldrRdrMem_Read(pReader, pThis->pvMapping, pThis->cbImage, 0);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cMappings = 1;
+ *ppvBits = pThis->pvMapping;
+ }
+ else
+ {
+ RTMemFree(pThis->pvMapping);
+ pThis->pvMapping = NULL;
+ }
+
+ return rc;
+}
+
+
+/** @interface_method_impl{RTLDRREADER,pfnUnmap} */
+static DECLCALLBACK(int) rtldrRdrMem_Unmap(PRTLDRREADER pReader, const void *pvBits)
+{
+ PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader;
+ AssertReturn(pThis->cMappings > 0, VERR_INVALID_PARAMETER);
+
+ if (!--pThis->cMappings)
+ {
+ RTMemFree(pThis->pvMapping);
+ pThis->pvMapping = NULL;
+ }
+
+ NOREF(pvBits);
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTLDRREADER,pfnDestroy} */
+static DECLCALLBACK(int) rtldrRdrMem_Destroy(PRTLDRREADER pReader)
+{
+ PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader;
+ pThis->pfnDtor(pThis->pvUser, pThis->cbImage);
+ pThis->pfnDtor = NULL;
+ pThis->pvUser = NULL;
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Opens a memory based loader reader.
+ *
+ * @returns iprt status code.
+ * @param ppReader Where to store the reader instance on success.
+ * @param pszName The name to give the image.
+ * @param cbImage The image size.
+ * @param pfnRead The reader function. If NULL, a default reader is
+ * used that assumes pvUser points to a memory buffer
+ * of at least @a cbImage size.
+ * @param pfnDtor The destructor. If NULL, a default destructore is
+ * used that will call RTMemFree on @a pvUser.
+ * @param pvUser User argument. If either @a pfnRead or @a pfnDtor
+ * is NULL, this must be a pointer to readable memory
+ * (see above).
+ */
+static int rtldrRdrMem_Create(PRTLDRREADER *ppReader, const char *pszName, size_t cbImage,
+ PFNRTLDRRDRMEMREAD pfnRead, PFNRTLDRRDRMEMDTOR pfnDtor, void *pvUser)
+{
+#if ARCH_BITS > 32 /* 'ing gcc. */
+ AssertReturn(cbImage < RTFOFF_MAX, VERR_INVALID_PARAMETER);
+#endif
+ AssertReturn((RTFOFF)cbImage > 0, VERR_INVALID_PARAMETER);
+
+ size_t cchName = strlen(pszName);
+ int rc = VERR_NO_MEMORY;
+ PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)RTMemAlloc(sizeof(*pThis) + cchName);
+ if (pThis)
+ {
+ memcpy(pThis->szName, pszName, cchName + 1);
+ pThis->cbImage = cbImage;
+ pThis->pvUser = pvUser;
+ pThis->offCur = 0;
+ pThis->pvUser = pvUser;
+ pThis->pfnRead = pfnRead ? pfnRead : rtldrRdrMemDefaultReader;
+ pThis->pfnDtor = pfnDtor ? pfnDtor : rtldrRdrMemDefaultDtor;
+ pThis->pvMapping = NULL;
+ pThis->cMappings = 0;
+ pThis->Core.uMagic = RTLDRREADER_MAGIC;
+ pThis->Core.pfnRead = rtldrRdrMem_Read;
+ pThis->Core.pfnTell = rtldrRdrMem_Tell;
+ pThis->Core.pfnSize = rtldrRdrMem_Size;
+ pThis->Core.pfnLogName = rtldrRdrMem_LogName;
+ pThis->Core.pfnMap = rtldrRdrMem_Map;
+ pThis->Core.pfnUnmap = rtldrRdrMem_Unmap;
+ pThis->Core.pfnDestroy = rtldrRdrMem_Destroy;
+ *ppReader = &pThis->Core;
+ return VINF_SUCCESS;
+ }
+
+ *ppReader = NULL;
+ return rc;
+}
+
+
+RTDECL(int) RTLdrOpenInMemory(const char *pszName, uint32_t fFlags, RTLDRARCH enmArch, size_t cbImage,
+ PFNRTLDRRDRMEMREAD pfnRead, PFNRTLDRRDRMEMDTOR pfnDtor, void *pvUser,
+ PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo)
+{
+ LogFlow(("RTLdrOpenInMemory: pszName=%p:{%s} fFlags=%#x enmArch=%d cbImage=%#zx pfnRead=%p pfnDtor=%p pvUser=%p phLdrMod=%p pErrInfo=%p\n",
+ pszName, pszName, fFlags, enmArch, cbImage, pfnRead, pfnDtor, pvUser, phLdrMod, pErrInfo));
+
+ if (!pfnRead || !pfnDtor)
+ AssertPtrReturn(pvUser, VERR_INVALID_POINTER);
+ if (!pfnDtor)
+ pfnDtor = rtldrRdrMemDefaultDtor;
+ else
+ AssertPtrReturn(pfnDtor, VERR_INVALID_POINTER);
+
+ /* The rest of the validations will call the destructor. */
+ AssertMsgReturnStmt(!(fFlags & ~RTLDR_O_VALID_MASK), ("%#x\n", fFlags),
+ pfnDtor(pvUser, cbImage), VERR_INVALID_PARAMETER);
+ AssertMsgReturnStmt(enmArch > RTLDRARCH_INVALID && enmArch < RTLDRARCH_END, ("%d\n", enmArch),
+ pfnDtor(pvUser, cbImage), VERR_INVALID_PARAMETER);
+ if (!pfnRead)
+ pfnRead = rtldrRdrMemDefaultReader;
+ else
+ AssertReturnStmt(RT_VALID_PTR(pfnRead), pfnDtor(pvUser, cbImage), VERR_INVALID_POINTER);
+ AssertReturnStmt(cbImage > 0, pfnDtor(pvUser, cbImage), VERR_INVALID_PARAMETER);
+
+ /*
+ * Resolve RTLDRARCH_HOST.
+ */
+ if (enmArch == RTLDRARCH_HOST)
+ enmArch = RTLdrGetHostArch();
+
+ /*
+ * Create file reader & invoke worker which identifies and calls the image interpreter.
+ */
+ PRTLDRREADER pReader = NULL; /* gcc may be wrong */
+ int rc = rtldrRdrMem_Create(&pReader, pszName, cbImage, pfnRead, pfnDtor, pvUser);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrOpenWithReader(pReader, fFlags, enmArch, phLdrMod, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("RTLdrOpen: return %Rrc *phLdrMod=%p\n", rc, *phLdrMod));
+ return rc;
+ }
+
+ pReader->pfnDestroy(pReader);
+ }
+ else
+ {
+ pfnDtor(pvUser, cbImage);
+ rc = RTErrInfoSetF(pErrInfo, rc, "rtldrRdrMem_Create failed: %Rrc", rc);
+ }
+ *phLdrMod = NIL_RTLDRMOD;
+
+ LogFlow(("RTLdrOpen: return %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrOpenInMemory);
+
diff --git a/src/VBox/Runtime/common/ldr/ldrNative.cpp b/src/VBox/Runtime/common/ldr/ldrNative.cpp
new file mode 100644
index 00000000..8675a5ae
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrNative.cpp
@@ -0,0 +1,336 @@
+/* $Id: ldrNative.cpp $ */
+/** @file
+ * IPRT - Binary Image Loader, Native interface.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/log.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/err.h>
+#include "internal/ldr.h"
+
+
+/** @copydoc RTLDROPS::pfnEnumSymbols */
+static DECLCALLBACK(int) rtldrNativeEnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits,
+ RTUINTPTR BaseAddress, PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ NOREF(pMod); NOREF(fFlags); NOREF(pvBits); NOREF(BaseAddress); NOREF(pfnCallback); NOREF(pvUser);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/** @copydoc RTLDROPS::pfnDone */
+static DECLCALLBACK(int) rtldrNativeDone(PRTLDRMODINTERNAL pMod)
+{
+ NOREF(pMod);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Operations for a native module.
+ */
+static const RTLDROPS g_rtldrNativeOps =
+{
+ "native",
+ rtldrNativeClose,
+ rtldrNativeGetSymbol,
+ rtldrNativeDone,
+ rtldrNativeEnumSymbols,
+ /* ext: */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL /*pfnQueryForwarderInfo*/,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL /*pfnUnwindFrame*/,
+ 42
+};
+
+
+
+RTDECL(int) RTLdrLoad(const char *pszFilename, PRTLDRMOD phLdrMod)
+{
+ return RTLdrLoadEx(pszFilename, phLdrMod, RTLDRLOAD_FLAGS_LOCAL, NULL);
+}
+RT_EXPORT_SYMBOL(RTLdrLoad);
+
+
+RTDECL(int) RTLdrLoadEx(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo)
+{
+ LogFlow(("RTLdrLoadEx: pszFilename=%p:{%s} phLdrMod=%p fFlags=%#x pErrInfo=%p\n", pszFilename, pszFilename, phLdrMod, fFlags, pErrInfo));
+
+ /*
+ * Validate and massage the input.
+ */
+ RTErrInfoClear(pErrInfo);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertPtrReturn(phLdrMod, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTLDRLOAD_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ /*
+ * Allocate and initialize module structure.
+ */
+ int rc = VERR_NO_MEMORY;
+ PRTLDRMODNATIVE pMod = (PRTLDRMODNATIVE)RTMemAlloc(sizeof(*pMod));
+ if (pMod)
+ {
+ pMod->Core.u32Magic = RTLDRMOD_MAGIC;
+ pMod->Core.eState = LDR_STATE_LOADED;
+ pMod->Core.pOps = &g_rtldrNativeOps;
+ pMod->Core.pReader = NULL;
+ pMod->Core.enmFormat = RTLDRFMT_NATIVE;
+ pMod->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; /* approx */
+#ifdef RT_BIG_ENDIAN
+ pMod->Core.enmEndian = RTLDRENDIAN_BIG;
+#else
+ pMod->Core.enmEndian = RTLDRENDIAN_LITTLE;
+#endif
+#ifdef RT_ARCH_AMD64
+ pMod->Core.enmArch = RTLDRARCH_AMD64;
+#elif defined(RT_ARCH_X86)
+ pMod->Core.enmArch = RTLDRARCH_X86_32;
+#else
+ pMod->Core.enmArch = RTLDRARCH_HOST;
+#endif
+ pMod->hNative = ~(uintptr_t)0;
+ pMod->fFlags = fFlags;
+
+ /*
+ * Attempt to open the module.
+ */
+ rc = rtldrNativeLoad(pszFilename, &pMod->hNative, fFlags, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (fFlags & RTLDRLOAD_FLAGS_NO_UNLOAD)
+ RTMEM_MAY_LEAK(pMod);
+
+ *phLdrMod = &pMod->Core;
+ LogFlow(("RTLdrLoad: returns %Rrc *phLdrMod=%RTldrm\n", rc,*phLdrMod));
+ return rc;
+ }
+
+ RTMemFree(pMod);
+ }
+ else
+ RTErrInfoSetF(pErrInfo, rc, "Failed to allocate %zu bytes for the module handle", sizeof(*pMod));
+ *phLdrMod = NIL_RTLDRMOD;
+ LogFlow(("RTLdrLoad: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrLoadEx);
+
+
+RTDECL(int) RTLdrLoadSystem(const char *pszFilename, bool fNoUnload, PRTLDRMOD phLdrMod)
+{
+ return RTLdrLoadSystemEx(pszFilename, fNoUnload ? RTLDRLOAD_FLAGS_NO_UNLOAD : 0, phLdrMod);
+}
+
+
+RTDECL(int) RTLdrLoadSystemEx(const char *pszFilename, uint32_t fFlags, PRTLDRMOD phLdrMod)
+{
+ LogFlow(("RTLdrLoadSystemEx: pszFilename=%p:{%s} fFlags=%#RX32 phLdrMod=%p\n", pszFilename, pszFilename, fFlags, phLdrMod));
+
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(phLdrMod, VERR_INVALID_PARAMETER);
+ *phLdrMod = NIL_RTLDRMOD;
+ AssertPtrReturn(pszFilename, VERR_INVALID_PARAMETER);
+ AssertMsgReturn(!RTPathHasPath(pszFilename), ("%s\n", pszFilename), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(!(fFlags & ~(RTLDRLOAD_FLAGS_VALID_MASK | RTLDRLOAD_FLAGS_SO_VER_BEGIN_MASK | RTLDRLOAD_FLAGS_SO_VER_END_MASK)),
+ ("fFlags=%#RX32\n", fFlags), VERR_INVALID_FLAGS);
+
+ /*
+ * Check the filename.
+ */
+ size_t cchFilename = strlen(pszFilename);
+ AssertMsgReturn(cchFilename < (RTPATH_MAX / 4) * 3, ("%zu\n", cchFilename), VERR_INVALID_PARAMETER);
+
+ const char *pszSuffix = NULL;
+ if (!RTPathHasSuffix(pszFilename))
+ pszSuffix = RTLdrGetSuff();
+
+ /*
+ * Let the platform specific code do the rest.
+ */
+ int rc = rtldrNativeLoadSystem(pszFilename, pszSuffix, fFlags, phLdrMod);
+ LogFlow(("RTLdrLoadSystem: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+RTDECL(void *) RTLdrGetSystemSymbol(const char *pszFilename, const char *pszSymbol)
+{
+ return RTLdrGetSystemSymbolEx(pszFilename, pszSymbol, RTLDRLOAD_FLAGS_NO_UNLOAD);
+}
+
+
+RTDECL(void *) RTLdrGetSystemSymbolEx(const char *pszFilename, const char *pszSymbol, uint32_t fFlags)
+{
+ void *pvRet = NULL;
+ RTLDRMOD hLdrMod;
+ int rc = RTLdrLoadSystemEx(pszFilename, fFlags | RTLDRLOAD_FLAGS_NO_UNLOAD, &hLdrMod);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTLdrGetSymbol(hLdrMod, pszSymbol, &pvRet);
+ if (RT_FAILURE(rc))
+ pvRet = NULL; /* paranoia */
+ RTLdrClose(hLdrMod);
+ }
+ return pvRet;
+}
+
+
+RTDECL(int) RTLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod)
+{
+ LogFlow(("RTLdrLoadAppPriv: pszFilename=%p:{%s} phLdrMod=%p\n", pszFilename, pszFilename, phLdrMod));
+
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(phLdrMod, VERR_INVALID_PARAMETER);
+ *phLdrMod = NIL_RTLDRMOD;
+ AssertPtrReturn(pszFilename, VERR_INVALID_PARAMETER);
+ AssertMsgReturn(!RTPathHasPath(pszFilename), ("%s\n", pszFilename), VERR_INVALID_PARAMETER);
+
+ /*
+ * Check the filename.
+ */
+ size_t cchFilename = strlen(pszFilename);
+ AssertMsgReturn(cchFilename < (RTPATH_MAX / 4) * 3, ("%zu\n", cchFilename), VERR_INVALID_PARAMETER);
+
+ const char *pszSuffix = "";
+ size_t cchSuffix = 0;
+ if (!RTPathHasSuffix(pszFilename))
+ {
+ pszSuffix = RTLdrGetSuff();
+ cchSuffix = strlen(pszSuffix);
+ }
+
+ /*
+ * Construct the private arch path and check if the file exists.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = RTPathAppPrivateArch(szPath, sizeof(szPath) - 1 - cchSuffix - cchFilename);
+ AssertRCReturn(rc, rc);
+
+ char *psz = strchr(szPath, '\0');
+ *psz++ = RTPATH_SLASH;
+ memcpy(psz, pszFilename, cchFilename);
+ psz += cchFilename;
+ memcpy(psz, pszSuffix, cchSuffix + 1);
+
+ if (!RTPathExists(szPath))
+ {
+ LogRel(("RTLdrLoadAppPriv: \"%s\" not found\n", szPath));
+ return VERR_FILE_NOT_FOUND;
+ }
+
+ /*
+ * Pass it on to RTLdrLoad.
+ */
+ rc = RTLdrLoad(szPath, phLdrMod);
+
+ LogFlow(("RTLdrLoadAppPriv: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrLoadAppPriv);
+
+
+RTDECL(const char *) RTLdrGetSuff(void)
+{
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ static const char s_szSuff[] = ".DLL";
+#elif defined(RT_OS_L4)
+ static const char s_szSuff[] = ".s.so";
+#elif defined(RT_OS_DARWIN)
+ static const char s_szSuff[] = ".dylib";
+#else
+ static const char s_szSuff[] = ".so";
+#endif
+
+ return s_szSuff;
+}
+RT_EXPORT_SYMBOL(RTLdrGetSuff);
+
+
+RTDECL(uintptr_t) RTLdrGetNativeHandle(RTLDRMOD hLdrMod)
+{
+ PRTLDRMODNATIVE pThis = (PRTLDRMODNATIVE)hLdrMod;
+ AssertPtrReturn(pThis, ~(uintptr_t)0);
+ AssertReturn(pThis->Core.u32Magic == RTLDRMOD_MAGIC, ~(uintptr_t)0);
+ AssertReturn(pThis->Core.pOps == &g_rtldrNativeOps, ~(uintptr_t)0);
+ return pThis->hNative;
+}
+RT_EXPORT_SYMBOL(RTLdrGetNativeHandle);
+
+
+RTDECL(bool) RTLdrIsLoadable(const char *pszFilename)
+{
+ /*
+ * Try to load the library.
+ */
+ RTLDRMOD hLib;
+ int rc = RTLdrLoad(pszFilename, &hLib);
+ if (RT_SUCCESS(rc))
+ {
+ RTLdrClose(hLib);
+ return true;
+ }
+ return false;
+}
+RT_EXPORT_SYMBOL(RTLdrIsLoadable);
+
diff --git a/src/VBox/Runtime/common/ldr/ldrPE.cpp b/src/VBox/Runtime/common/ldr/ldrPE.cpp
new file mode 100644
index 00000000..486be79e
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrPE.cpp
@@ -0,0 +1,5194 @@
+/* $Id: ldrPE.cpp $ */
+/** @file
+ * IPRT - Binary Image Loader, Portable Executable (PE).
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/dbg.h>
+#include <iprt/err.h>
+#include <iprt/latin1.h>
+#include <iprt/log.h>
+#include <iprt/md5.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/x86.h>
+#if !defined(IPRT_WITHOUT_LDR_VERIFY) || !defined(IPRT_WITHOUT_LDR_PAGE_HASHING)
+# include <iprt/zero.h>
+#endif
+#ifndef IPRT_WITHOUT_LDR_VERIFY
+# include <iprt/crypto/pkcs7.h>
+# include <iprt/crypto/spc.h>
+# include <iprt/crypto/x509.h>
+#endif
+#include <iprt/formats/codeview.h>
+#include <iprt/formats/pecoff.h>
+#include "internal/ldr.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Converts rva to a type.
+ * @param pvBits Pointer to base of image bits.
+ * @param rva Relative virtual address.
+ * @param type Type.
+ */
+#define PE_RVA2TYPE(pvBits, rva, type) ((type) ((uintptr_t)pvBits + (uintptr_t)(rva)) )
+
+/** The max size of the security directory. */
+#ifdef IN_RING3
+# define RTLDRMODPE_MAX_SECURITY_DIR_SIZE _4M
+#else
+# define RTLDRMODPE_MAX_SECURITY_DIR_SIZE _1M
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * The PE loader structure.
+ */
+typedef struct RTLDRMODPE
+{
+ /** Core module structure. */
+ RTLDRMODINTERNAL Core;
+ /** Pointer to internal copy of image bits.
+ * @todo the reader should take care of this. */
+ void *pvBits;
+ /** The offset of the NT headers. */
+ RTFOFF offNtHdrs;
+ /** The offset of the first byte after the section table. */
+ RTFOFF offEndOfHdrs;
+
+ /** The machine type (IMAGE_FILE_HEADER::Machine). */
+ uint16_t u16Machine;
+ /** The file flags (IMAGE_FILE_HEADER::Characteristics). */
+ uint16_t fFile;
+ /** Number of sections (IMAGE_FILE_HEADER::NumberOfSections). */
+ unsigned cSections;
+ /** Pointer to an array of the section headers related to the file. */
+ PIMAGE_SECTION_HEADER paSections;
+
+ /** The RVA of the entry point (IMAGE_OPTIONAL_HEADER32::AddressOfEntryPoint). */
+ RTUINTPTR uEntryPointRVA;
+ /** The base address of the image at link time (IMAGE_OPTIONAL_HEADER32::ImageBase). */
+ RTUINTPTR uImageBase;
+ /** The size of the loaded image (IMAGE_OPTIONAL_HEADER32::SizeOfImage). */
+ uint32_t cbImage;
+ /** Size of the header (IMAGE_OPTIONAL_HEADER32::SizeOfHeaders). */
+ uint32_t cbHeaders;
+ /** Section alignment (IMAGE_OPTIONAL_HEADER32::SectionAlignment). */
+ uint32_t uSectionAlign;
+ /** The image timestamp. */
+ uint32_t uTimestamp;
+ /** The number of imports. UINT32_MAX if not determined. */
+ uint32_t cImports;
+ /** Set if the image is 64-bit, clear if 32-bit. */
+ bool f64Bit;
+ /** The import data directory entry. */
+ IMAGE_DATA_DIRECTORY ImportDir;
+ /** The base relocation data directory entry. */
+ IMAGE_DATA_DIRECTORY RelocDir;
+ /** The export data directory entry. */
+ IMAGE_DATA_DIRECTORY ExportDir;
+ /** The debug directory entry. */
+ IMAGE_DATA_DIRECTORY DebugDir;
+ /** The security directory entry. */
+ IMAGE_DATA_DIRECTORY SecurityDir;
+ /** The exception data directory entry. */
+ IMAGE_DATA_DIRECTORY ExceptionDir;
+
+ /** Offset of the first PKCS \#7 SignedData signature if present. */
+ uint32_t offPkcs7SignedData;
+ /** Size of the first PKCS \#7 SignedData. */
+ uint32_t cbPkcs7SignedData;
+
+ /** Copy of the optional header field DllCharacteristics. */
+ uint16_t fDllCharacteristics;
+} RTLDRMODPE;
+/** Pointer to the instance data for a PE loader module. */
+typedef RTLDRMODPE *PRTLDRMODPE;
+
+
+/**
+ * PE Loader module operations.
+ *
+ * The PE loader has one operation which is a bit different between 32-bit and 64-bit PE images,
+ * and for historical and performance reasons have been split into separate functions. Thus the
+ * PE loader extends the RTLDROPS structure with this one entry.
+ */
+typedef struct RTLDROPSPE
+{
+ /** The usual ops. */
+ RTLDROPS Core;
+
+ /**
+ * Resolves all imports.
+ *
+ * @returns iprt status code.
+ * @param pModPe Pointer to the PE loader module structure.
+ * @param pvBitsR Where to read raw image bits. (optional)
+ * @param pvBitsW Where to store the imports. The size of this buffer is equal or
+ * larger to the value returned by pfnGetImageSize().
+ * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals).
+ * @param pvUser User argument to pass to the callback.
+ */
+ DECLCALLBACKMEMBER(int, pfnResolveImports,(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, PFNRTLDRIMPORT pfnGetImport, void *pvUser));
+
+ /** Dummy entry to make sure we've initialized it all. */
+ RTUINT uDummy;
+} RTLDROPSPE, *PRTLDROPSPE;
+
+
+/**
+ * PE hash context union.
+ */
+typedef union RTLDRPEHASHCTXUNION
+{
+ RTSHA512CONTEXT Sha512;
+ RTSHA384CONTEXT Sha384;
+ RTSHA256CONTEXT Sha256;
+ RTSHA1CONTEXT Sha1;
+ RTMD5CONTEXT Md5;
+} RTLDRPEHASHCTXUNION;
+/** Pointer to a PE hash context union. */
+typedef RTLDRPEHASHCTXUNION *PRTLDRPEHASHCTXUNION;
+
+
+/**
+ * PE hash digests
+ */
+typedef union RTLDRPEHASHRESUNION
+{
+ uint8_t abSha512[RTSHA512_HASH_SIZE];
+ uint8_t abSha384[RTSHA384_HASH_SIZE];
+ uint8_t abSha256[RTSHA256_HASH_SIZE];
+ uint8_t abSha1[RTSHA1_HASH_SIZE];
+ uint8_t abMd5[RTMD5_HASH_SIZE];
+} RTLDRPEHASHRESUNION;
+/** Pointer to a PE hash work set. */
+typedef RTLDRPEHASHRESUNION *PRTLDRPEHASHRESUNION;
+
+/**
+ * Special places to watch out for when hashing a PE image.
+ */
+typedef struct RTLDRPEHASHSPECIALS
+{
+ uint32_t cbToHash;
+ uint32_t offCksum;
+ uint32_t cbCksum;
+ uint32_t offSecDir;
+ uint32_t cbSecDir;
+ uint32_t offEndSpecial;
+} RTLDRPEHASHSPECIALS;
+/** Pointer to the structure with the special hash places. */
+typedef RTLDRPEHASHSPECIALS *PRTLDRPEHASHSPECIALS;
+
+
+#ifndef IPRT_WITHOUT_LDR_VERIFY
+/**
+ * Parsed data for one signature.
+ */
+typedef struct RTLDRPESIGNATUREONE
+{
+ /** The outer content info wrapper. */
+ PRTCRPKCS7CONTENTINFO pContentInfo;
+ /** Pointer to the decoded SignedData inside the ContentInfo member. */
+ PRTCRPKCS7SIGNEDDATA pSignedData;
+ /** Pointer to the indirect data content. */
+ PRTCRSPCINDIRECTDATACONTENT pIndData;
+ /** The digest type employed by the signature. */
+ RTDIGESTTYPE enmDigest;
+ /** Set if we've already validate the image hash. */
+ bool fValidatedImageHash;
+ /** The signature number. */
+ uint16_t iSignature;
+ /** Hash result. */
+ RTLDRPEHASHRESUNION HashRes;
+} RTLDRPESIGNATUREONE;
+/** Pointer to the parsed data of one signature. */
+typedef RTLDRPESIGNATUREONE *PRTLDRPESIGNATUREONE;
+
+/**
+ * Parsed signature data.
+ */
+typedef struct RTLDRPESIGNATURE
+{
+ /** Pointer to the raw signatures. This is allocated in the continuation of
+ * this structure to keep things simple. The size is given by the security
+ * export directory. */
+ WIN_CERTIFICATE const *pRawData;
+ /** The outer content info wrapper (primary signature). */
+ RTCRPKCS7CONTENTINFO PrimaryContentInfo;
+ /** The info for the primary signature. */
+ RTLDRPESIGNATUREONE Primary;
+ /** Number of nested signatures (zero if none). */
+ uint16_t cNested;
+ /** Pointer to an array of nested signatures (NULL if none). */
+ PRTLDRPESIGNATUREONE paNested;
+ /** Hash scratch data. */
+ RTLDRPEHASHCTXUNION HashCtx;
+} RTLDRPESIGNATURE;
+/** Pointed to SigneData parsing stat and output. */
+typedef RTLDRPESIGNATURE *PRTLDRPESIGNATURE;
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtldrPEConvert32BitOptionalHeaderTo64Bit(PIMAGE_OPTIONAL_HEADER64 pOptHdr);
+static void rtldrPEConvert32BitLoadConfigTo64Bit(PIMAGE_LOAD_CONFIG_DIRECTORY64 pLoadCfg);
+static int rtldrPEApplyFixups(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, RTUINTPTR BaseAddress, RTUINTPTR OldBaseAddress);
+#ifndef IPRT_WITHOUT_LDR_PAGE_HASHING
+static int rtLdrPE_QueryPageHashes(PRTLDRMODPE pModPe, RTDIGESTTYPE enmDigest, void *pvBuf, size_t cbBuf, size_t *pcbRet);
+static uint32_t rtLdrPE_GetHashablePages(PRTLDRMODPE pModPe);
+#endif
+
+
+
+/**
+ * Reads a section of a PE image given by RVA + size, using mapped bits if
+ * available or allocating heap memory and reading from the file.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to the PE loader module structure.
+ * @param pvBits Read only bits if available. NULL if not.
+ * @param uRva The RVA to read at.
+ * @param cbMem The number of bytes to read.
+ * @param ppvMem Where to return the memory on success (heap or
+ * inside pvBits).
+ */
+static int rtldrPEReadPartByRva(PRTLDRMODPE pThis, const void *pvBits, uint32_t uRva, uint32_t cbMem, void const **ppvMem)
+{
+ *ppvMem = NULL;
+ if (!cbMem)
+ return VINF_SUCCESS;
+
+ /*
+ * Use bits if we've got some.
+ */
+ if (pvBits)
+ {
+ *ppvMem = (uint8_t const *)pvBits + uRva;
+ return VINF_SUCCESS;
+ }
+ if (pThis->pvBits)
+ {
+ *ppvMem = (uint8_t const *)pThis->pvBits + uRva;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Allocate a buffer and read the bits from the file (or whatever).
+ */
+ if (!pThis->Core.pReader)
+ return VERR_ACCESS_DENIED;
+
+ uint8_t *pbMem = (uint8_t *)RTMemAllocZ(cbMem);
+ if (!pbMem)
+ return VERR_NO_MEMORY;
+ *ppvMem = pbMem;
+
+ /* Do the reading on a per section base. */
+ uint64_t const cbFile = pThis->Core.pReader->pfnSize(pThis->Core.pReader);
+ for (;;)
+ {
+ /* Translate the RVA into a file offset. */
+ uint32_t offFile = uRva;
+ uint32_t cbToRead = cbMem;
+ uint32_t cbToAdv = cbMem;
+
+ if (uRva < pThis->paSections[0].VirtualAddress)
+ {
+ /* Special header section. */
+ cbToRead = pThis->paSections[0].VirtualAddress - uRva;
+ if (cbToRead > cbMem)
+ cbToRead = cbMem;
+ cbToAdv = cbToRead;
+
+ /* The following capping is an approximation. */
+ uint32_t offFirstRawData = RT_ALIGN(pThis->cbHeaders, _4K);
+ if ( pThis->paSections[0].PointerToRawData > 0
+ && pThis->paSections[0].SizeOfRawData > 0)
+ offFirstRawData = pThis->paSections[0].PointerToRawData;
+ if (offFile >= offFirstRawData)
+ cbToRead = 0;
+ else if (offFile + cbToRead > offFirstRawData)
+ cbToRead = offFile - offFirstRawData;
+ }
+ else
+ {
+ /* Find the matching section and its mapping size. */
+ uint32_t j = 0;
+ uint32_t cbMapping = 0;
+ uint32_t offSection = 0;
+ while (j < pThis->cSections)
+ {
+ cbMapping = (j + 1 < pThis->cSections ? pThis->paSections[j + 1].VirtualAddress : pThis->cbImage)
+ - pThis->paSections[j].VirtualAddress;
+ offSection = uRva - pThis->paSections[j].VirtualAddress;
+ if (offSection < cbMapping)
+ break;
+ j++;
+ }
+ if (j >= cbMapping)
+ break; /* This shouldn't happen, just return zeros if it does. */
+
+ /* Adjust the sizes and calc the file offset. */
+ if (offSection + cbToAdv > cbMapping)
+ cbToAdv = cbToRead = cbMapping - offSection;
+
+ if ( pThis->paSections[j].PointerToRawData > 0
+ && pThis->paSections[j].SizeOfRawData > 0)
+ {
+ offFile = offSection;
+ if (offFile + cbToRead > pThis->paSections[j].SizeOfRawData)
+ cbToRead = pThis->paSections[j].SizeOfRawData - offFile;
+ offFile += pThis->paSections[j].PointerToRawData;
+ }
+ else
+ {
+ offFile = UINT32_MAX;
+ cbToRead = 0;
+ }
+ }
+
+ /* Perform the read after adjusting a little (paranoia). */
+ if (offFile > cbFile)
+ cbToRead = 0;
+ if (cbToRead)
+ {
+ if ((uint64_t)offFile + cbToRead > cbFile)
+ cbToRead = (uint32_t)(cbFile - (uint64_t)offFile);
+ int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pbMem, cbToRead, offFile);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree((void *)*ppvMem);
+ *ppvMem = NULL;
+ return rc;
+ }
+ }
+
+ /* Advance */
+ if (cbMem <= cbToAdv)
+ break;
+ cbMem -= cbToAdv;
+ pbMem += cbToAdv;
+ uRva += cbToAdv;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads a part of a PE file from the file and into a heap block.
+ *
+ * @returns IRPT status code.
+ * @param pThis Pointer to the PE loader module structure..
+ * @param offFile The file offset.
+ * @param cbMem The number of bytes to read.
+ * @param ppvMem Where to return the heap block with the bytes on
+ * success.
+ */
+static int rtldrPEReadPartFromFile(PRTLDRMODPE pThis, uint32_t offFile, uint32_t cbMem, void const **ppvMem)
+{
+ *ppvMem = NULL;
+ if (!cbMem)
+ return VINF_SUCCESS;
+
+ /*
+ * Allocate a buffer and read the bits from the file (or whatever).
+ */
+ if (!pThis->Core.pReader)
+ return VERR_ACCESS_DENIED;
+
+ uint8_t *pbMem = (uint8_t *)RTMemAlloc(cbMem);
+ if (!pbMem)
+ return VERR_NO_MEMORY;
+
+ int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pbMem, cbMem, offFile);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree((void *)*ppvMem);
+ return rc;
+ }
+
+ *ppvMem = pbMem;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads a part of a PE image into memory one way or another.
+ *
+ * Either the RVA or the offFile must be valid. We'll prefer the RVA if
+ * possible.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to the PE loader module structure.
+ * @param pvBits Read only bits if available. NULL if not.
+ * @param uRva The RVA to read at.
+ * @param offFile The file offset.
+ * @param cbMem The number of bytes to read.
+ * @param ppvMem Where to return the memory on success (heap or
+ * inside pvBits).
+ */
+static int rtldrPEReadPart(PRTLDRMODPE pThis, const void *pvBits, RTFOFF offFile, RTLDRADDR uRva,
+ uint32_t cbMem, void const **ppvMem)
+{
+ if ( uRva == NIL_RTLDRADDR
+ || uRva > pThis->cbImage
+ || cbMem > pThis->cbImage
+ || uRva + cbMem > pThis->cbImage)
+ {
+ if (offFile < 0 || offFile >= UINT32_MAX)
+ return VERR_INVALID_PARAMETER;
+ return rtldrPEReadPartFromFile(pThis, (uint32_t)offFile, cbMem, ppvMem);
+ }
+ return rtldrPEReadPartByRva(pThis, pvBits, (uint32_t)uRva, cbMem, ppvMem);
+}
+
+
+/**
+ * Frees up memory returned by rtldrPEReadPart*.
+ *
+ * @param pThis Pointer to the PE loader module structure..
+ * @param pvBits Read only bits if available. NULL if not..
+ * @param pvMem The memory we were given by the reader method.
+ */
+static void rtldrPEFreePart(PRTLDRMODPE pThis, const void *pvBits, void const *pvMem)
+{
+ if (!pvMem)
+ return;
+
+ if (pvBits && (uintptr_t)pvMem - (uintptr_t)pvBits < pThis->cbImage)
+ return;
+ if (pThis->pvBits && (uintptr_t)pvMem - (uintptr_t)pThis->pvBits < pThis->cbImage)
+ return;
+
+ RTMemFree((void *)pvMem);
+}
+
+
+/**
+ * Reads a section of a PE image given by RVA + size.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to the PE loader module structure.
+ * @param pvBits Read only bits if available. NULL if not.
+ * @param uRva The RVA to read at.
+ * @param cbMem The number of bytes to read.
+ * @param pvDst The destination buffer.
+ */
+static int rtldrPEReadPartByRvaInfoBuf(PRTLDRMODPE pThis, const void *pvBits, uint32_t uRva, uint32_t cbMem, void *pvDst)
+{
+ /** @todo consider optimizing this. */
+ const void *pvSrc = NULL;
+ int rc = rtldrPEReadPartByRva(pThis, pvBits, uRva, cbMem, &pvSrc);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(pvDst, pvSrc, cbMem);
+ rtldrPEFreePart(pThis, NULL, pvSrc);
+ }
+ return rc;
+}
+
+
+
+
+
+/** @interface_method_impl{RTLDROPS,pfnGetImageSize} */
+static DECLCALLBACK(size_t) rtldrPEGetImageSize(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+ return pModPe->cbImage;
+}
+
+
+/**
+ * Reads the image into memory.
+ *
+ * @returns iprt status code.
+ * @param pModPe The PE module.
+ * @param pvBits Where to store the bits, this buffer is at least pItem->Core.cbImage in size.
+ */
+static int rtldrPEGetBitsNoImportsNorFixups(PRTLDRMODPE pModPe, void *pvBits)
+{
+ /*
+ * Both these checks are related to pfnDone().
+ */
+ PRTLDRREADER pReader = pModPe->Core.pReader;
+ if (!pReader)
+ {
+ AssertMsgFailed(("You've called done!\n"));
+ return VERR_WRONG_ORDER;
+ }
+ if (!pvBits)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Zero everything (could be done per section).
+ */
+ memset(pvBits, 0, pModPe->cbImage);
+
+#ifdef PE_FILE_OFFSET_EQUALS_RVA
+ /*
+ * Read the entire image / file.
+ */
+ const uint64_t cbRawImage = pReader->pfnSize(pReader)
+ rc = pReader->pfnRead(pReader, pvBits, RT_MIN(pModPe->cbImage, cbRawImage), 0);
+ if (RT_FAILURE(rc))
+ Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc!!! (the entire image)\n",
+ pReader->pfnLogName(pReader), RT_MIN(pModPe->cbImage, cbRawImage), 0, rc));
+#else
+
+ /*
+ * Read the headers.
+ */
+ int rc = pReader->pfnRead(pReader, pvBits, pModPe->cbHeaders, 0);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Read the sections.
+ */
+ PIMAGE_SECTION_HEADER pSH = pModPe->paSections;
+ for (unsigned cLeft = pModPe->cSections; cLeft > 0; cLeft--, pSH++)
+ if ( pSH->SizeOfRawData
+ && pSH->Misc.VirtualSize
+ && !(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD))
+ {
+ uint32_t const cbToRead = RT_MIN(pSH->SizeOfRawData, pModPe->cbImage - pSH->VirtualAddress);
+ Assert(pSH->VirtualAddress <= pModPe->cbImage);
+
+ rc = pReader->pfnRead(pReader, (uint8_t *)pvBits + pSH->VirtualAddress, cbToRead, pSH->PointerToRawData);
+ if (RT_FAILURE(rc))
+ {
+ Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc - section #%d '%.*s'!!!\n",
+ pReader->pfnLogName(pReader), pSH->SizeOfRawData, pSH->PointerToRawData, rc,
+ pSH - pModPe->paSections, sizeof(pSH->Name), pSH->Name));
+ break;
+ }
+ }
+ }
+ else
+ Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc!!!\n",
+ pReader->pfnLogName(pReader), pModPe->cbHeaders, 0, rc));
+#endif
+ return rc;
+}
+
+
+/**
+ * Reads the bits into the internal buffer pointed to by PRTLDRMODPE::pvBits.
+ *
+ * @returns iprt status code.
+ * @param pModPe The PE module.
+ */
+static int rtldrPEReadBits(PRTLDRMODPE pModPe)
+{
+ Assert(!pModPe->pvBits);
+ void *pvBitsW = RTMemAllocZ(pModPe->cbImage);
+ if (!pvBitsW)
+ return VERR_NO_MEMORY;
+ int rc = rtldrPEGetBitsNoImportsNorFixups(pModPe, pvBitsW);
+ if (RT_SUCCESS(rc))
+ pModPe->pvBits = pvBitsW;
+ else
+ RTMemFree(pvBitsW);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnGetBits} */
+static DECLCALLBACK(int) rtldrPEGetBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+
+ /*
+ * Read the image.
+ */
+ int rc = rtldrPEGetBitsNoImportsNorFixups(pModPe, pvBits);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Resolve imports.
+ */
+ if (pfnGetImport)
+ rc = ((PRTLDROPSPE)pMod->pOps)->pfnResolveImports(pModPe, pvBits, pvBits, pfnGetImport, pvUser);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Apply relocations.
+ */
+ rc = rtldrPEApplyFixups(pModPe, pvBits, pvBits, BaseAddress, pModPe->uImageBase);
+ if (RT_SUCCESS(rc))
+ return rc;
+ AssertMsgFailed(("Failed to apply fixups. rc=%Rrc\n", rc));
+ }
+#ifndef IN_SUP_HARDENED_R3
+ else
+ AssertMsgFailed(("Failed to resolve imports. rc=%Rrc\n", rc));
+#endif
+ }
+ return rc;
+}
+
+
+/* The image_thunk_data32/64 structures are not very helpful except for getting RSI. keep them around till all the code has been converted. */
+typedef struct _IMAGE_THUNK_DATA32
+{
+ union
+ {
+ uint32_t ForwarderString;
+ uint32_t Function;
+ uint32_t Ordinal;
+ uint32_t AddressOfData;
+ } u1;
+} IMAGE_THUNK_DATA32;
+typedef IMAGE_THUNK_DATA32 *PIMAGE_THUNK_DATA32;
+
+
+/** @copydoc RTLDROPSPE::pfnResolveImports */
+static DECLCALLBACK(int) rtldrPEResolveImports32(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ /*
+ * Check if there is actually anything to work on.
+ */
+ if ( !pModPe->ImportDir.VirtualAddress
+ || !pModPe->ImportDir.Size)
+ return 0;
+
+ /*
+ * Walk the IMAGE_IMPORT_DESCRIPTOR table.
+ */
+ int rc = VINF_SUCCESS;
+ PIMAGE_IMPORT_DESCRIPTOR pImps;
+ for (pImps = PE_RVA2TYPE(pvBitsR, pModPe->ImportDir.VirtualAddress, PIMAGE_IMPORT_DESCRIPTOR);
+ !rc && pImps->Name != 0 && pImps->FirstThunk != 0;
+ pImps++)
+ {
+ AssertReturn(pImps->Name < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
+ const char *pszModName = PE_RVA2TYPE(pvBitsR, pImps->Name, const char *);
+ AssertReturn(pImps->FirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
+ AssertReturn(pImps->u.OriginalFirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
+
+ Log3(("RTLdrPE: Import descriptor: %s\n", pszModName));
+ Log4(("RTLdrPE: OriginalFirstThunk = %#RX32\n"
+ "RTLdrPE: TimeDateStamp = %#RX32\n"
+ "RTLdrPE: ForwarderChain = %#RX32\n"
+ "RTLdrPE: Name = %#RX32\n"
+ "RTLdrPE: FirstThunk = %#RX32\n",
+ pImps->u.OriginalFirstThunk, pImps->TimeDateStamp,
+ pImps->ForwarderChain, pImps->Name, pImps->FirstThunk));
+
+ /*
+ * Walk the thunks table(s).
+ */
+ PIMAGE_THUNK_DATA32 pFirstThunk = PE_RVA2TYPE(pvBitsW, pImps->FirstThunk, PIMAGE_THUNK_DATA32); /* update this. */
+ PIMAGE_THUNK_DATA32 pThunk = pImps->u.OriginalFirstThunk == 0 /* read from this. */
+ ? PE_RVA2TYPE(pvBitsR, pImps->FirstThunk, PIMAGE_THUNK_DATA32)
+ : PE_RVA2TYPE(pvBitsR, pImps->u.OriginalFirstThunk, PIMAGE_THUNK_DATA32);
+ while (!rc && pThunk->u1.Ordinal != 0)
+ {
+ RTUINTPTR Value = 0;
+ if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32)
+ {
+ rc = pfnGetImport(&pModPe->Core, pszModName, NULL, IMAGE_ORDINAL32(pThunk->u1.Ordinal), &Value, pvUser);
+ Log4((RT_SUCCESS(rc) ? "RTLdrPE: %RTptr #%u\n" : "RTLdrPE: %08RX32 #%u rc=%Rrc\n",
+ (uint32_t)Value, IMAGE_ORDINAL32(pThunk->u1.Ordinal), rc));
+ }
+ else if ( pThunk->u1.Ordinal > 0
+ && pThunk->u1.Ordinal < pModPe->cbImage)
+ {
+ rc = pfnGetImport(&pModPe->Core, pszModName,
+ PE_RVA2TYPE(pvBitsR, (char*)(uintptr_t)pThunk->u1.AddressOfData + 2, const char *),
+ ~0U, &Value, pvUser);
+ Log4((RT_SUCCESS(rc) ? "RTLdrPE: %RTptr %s\n" : "RTLdrPE: %08RX32 %s rc=%Rrc\n",
+ (uint32_t)Value, PE_RVA2TYPE(pvBitsR, (char*)(uintptr_t)pThunk->u1.AddressOfData + 2, const char *), rc));
+ }
+ else
+ {
+ AssertMsgFailed(("bad import data thunk!\n"));
+ rc = VERR_BAD_EXE_FORMAT;
+ }
+ pFirstThunk->u1.Function = (uint32_t)Value;
+ if (pFirstThunk->u1.Function != Value)
+ {
+ AssertMsgFailed(("external symbol address to big!\n"));
+ rc = VERR_ADDRESS_CONFLICT; /** @todo get me a better error status code. */
+ }
+ pThunk++;
+ pFirstThunk++;
+ }
+ }
+
+ return rc;
+}
+
+
+/* The image_thunk_data32/64 structures are not very helpful except for getting RSI. keep them around till all the code has been converted. */
+typedef struct _IMAGE_THUNK_DATA64
+{
+ union
+ {
+ uint64_t ForwarderString;
+ uint64_t Function;
+ uint64_t Ordinal;
+ uint64_t AddressOfData;
+ } u1;
+} IMAGE_THUNK_DATA64;
+typedef IMAGE_THUNK_DATA64 *PIMAGE_THUNK_DATA64;
+
+
+/** @copydoc RTLDROPSPE::pfnResolveImports */
+static DECLCALLBACK(int) rtldrPEResolveImports64(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW,
+ PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ /*
+ * Check if there is actually anything to work on.
+ */
+ if ( !pModPe->ImportDir.VirtualAddress
+ || !pModPe->ImportDir.Size)
+ return 0;
+
+ /*
+ * Walk the IMAGE_IMPORT_DESCRIPTOR table.
+ */
+ int rc = VINF_SUCCESS;
+ PIMAGE_IMPORT_DESCRIPTOR pImps;
+ for (pImps = PE_RVA2TYPE(pvBitsR, pModPe->ImportDir.VirtualAddress, PIMAGE_IMPORT_DESCRIPTOR);
+ !rc && pImps->Name != 0 && pImps->FirstThunk != 0;
+ pImps++)
+ {
+ AssertReturn(pImps->Name < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
+ const char *pszModName = PE_RVA2TYPE(pvBitsR, pImps->Name, const char *);
+ AssertReturn(pImps->FirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
+ AssertReturn(pImps->u.OriginalFirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT);
+
+ Log3(("RTLdrPE: Import descriptor: %s\n", pszModName));
+ Log4(("RTLdrPE: OriginalFirstThunk = %#RX32\n"
+ "RTLdrPE: TimeDateStamp = %#RX32\n"
+ "RTLdrPE: ForwarderChain = %#RX32\n"
+ "RTLdrPE: Name = %#RX32\n"
+ "RTLdrPE: FirstThunk = %#RX32\n",
+ pImps->u.OriginalFirstThunk, pImps->TimeDateStamp,
+ pImps->ForwarderChain, pImps->Name, pImps->FirstThunk));
+
+ /*
+ * Walk the thunks table(s).
+ */
+ PIMAGE_THUNK_DATA64 pFirstThunk = PE_RVA2TYPE(pvBitsW, pImps->FirstThunk, PIMAGE_THUNK_DATA64); /* update this. */
+ PIMAGE_THUNK_DATA64 pThunk = pImps->u.OriginalFirstThunk == 0 /* read from this. */
+ ? PE_RVA2TYPE(pvBitsR, pImps->FirstThunk, PIMAGE_THUNK_DATA64)
+ : PE_RVA2TYPE(pvBitsR, pImps->u.OriginalFirstThunk, PIMAGE_THUNK_DATA64);
+ while (!rc && pThunk->u1.Ordinal != 0)
+ {
+ RTUINTPTR Value = 0;
+ if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64)
+ {
+ rc = pfnGetImport(&pModPe->Core, pszModName, NULL, (unsigned)IMAGE_ORDINAL64(pThunk->u1.Ordinal), &Value, pvUser);
+ Log4((RT_SUCCESS(rc) ? "RTLdrPE: %016RX64 #%u\n" : "RTLdrPE: %016RX64 #%u rc=%Rrc\n",
+ (uint64_t)Value, (unsigned)IMAGE_ORDINAL64(pThunk->u1.Ordinal), rc));
+ }
+ else if ( pThunk->u1.Ordinal > 0
+ && pThunk->u1.Ordinal < pModPe->cbImage)
+ {
+ /** @todo add validation of the string pointer! */
+ rc = pfnGetImport(&pModPe->Core, pszModName, PE_RVA2TYPE(pvBitsR, (uintptr_t)pThunk->u1.AddressOfData + 2, const char *),
+ ~0U, &Value, pvUser);
+ Log4((RT_SUCCESS(rc) ? "RTLdrPE: %016RX64 %s\n" : "RTLdrPE: %016RX64 %s rc=%Rrc\n",
+ (uint64_t)Value, PE_RVA2TYPE(pvBitsR, (uintptr_t)pThunk->u1.AddressOfData + 2, const char *), rc));
+ }
+ else
+ {
+ AssertMsgFailed(("bad import data thunk!\n"));
+ rc = VERR_BAD_EXE_FORMAT;
+ }
+ pFirstThunk->u1.Function = Value;
+ pThunk++;
+ pFirstThunk++;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Applies fixups.
+ */
+static int rtldrPEApplyFixups(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, RTUINTPTR BaseAddress,
+ RTUINTPTR OldBaseAddress)
+{
+ if ( !pModPe->RelocDir.VirtualAddress
+ || !pModPe->RelocDir.Size)
+ return 0;
+
+ /*
+ * Apply delta fixups iterating fixup chunks.
+ */
+ PIMAGE_BASE_RELOCATION pbr = PE_RVA2TYPE(pvBitsR, pModPe->RelocDir.VirtualAddress, PIMAGE_BASE_RELOCATION);
+ PIMAGE_BASE_RELOCATION pBaseRelocs = pbr;
+ unsigned cbBaseRelocs = pModPe->RelocDir.Size;
+ RTUINTPTR uDelta = BaseAddress - OldBaseAddress;
+ Log2(("RTLdrPE: Fixups: uDelta=%#RTptr BaseAddress=%#RTptr OldBaseAddress=%#RTptr\n", uDelta, BaseAddress, OldBaseAddress));
+ Log4(("RTLdrPE: BASERELOC: VirtualAddres=%RX32 Size=%RX32\n", pModPe->RelocDir.VirtualAddress, pModPe->RelocDir.Size));
+ Assert(sizeof(*pbr) == sizeof(uint32_t) * 2);
+
+ while ( (uintptr_t)pbr - (uintptr_t)pBaseRelocs + 8 < cbBaseRelocs /* 8= VirtualAddress and SizeOfBlock members */
+ && pbr->SizeOfBlock >= 8)
+ {
+ uint16_t *pwoffFixup = (uint16_t *)(pbr + 1);
+ uint32_t cRelocations = (pbr->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(uint16_t);
+ Log3(("RTLdrPE: base relocs for %#010x, size %#06x (%d relocs)\n", pbr->VirtualAddress, pbr->SizeOfBlock, cRelocations));
+
+ /* Some bound checking just to be sure it works... */
+ if ((uintptr_t)pbr - (uintptr_t)pBaseRelocs + pbr->SizeOfBlock > cbBaseRelocs)
+ cRelocations = (uint32_t)( (((uintptr_t)pBaseRelocs + cbBaseRelocs) - (uintptr_t)pbr - sizeof(IMAGE_BASE_RELOCATION))
+ / sizeof(uint16_t) );
+
+ /*
+ * Loop thru the fixups in this chunk.
+ */
+ while (cRelocations != 0)
+ {
+ /*
+ * Common fixup
+ */
+ static const char * const s_apszReloc[16] =
+ {
+ "ABS", "HIGH", "LOW", "HIGHLOW", "HIGHADJ", "MIPS_JMPADDR", "RES6", "RES7",
+ "RES8", "IA64_IMM64", "DIR64", "HIGH3ADJ", "RES12", "RES13", "RES14", "RES15"
+ }; NOREF(s_apszReloc);
+ union
+ {
+ uint16_t *pu16;
+ uint32_t *pu32;
+ uint64_t *pu64;
+ } u;
+ const int offFixup = *pwoffFixup & 0xfff;
+ u.pu32 = PE_RVA2TYPE(pvBitsW, offFixup + pbr->VirtualAddress, uint32_t *);
+ const int fType = *pwoffFixup >> 12;
+ Log4(("RTLdrPE: %08x %s\n", offFixup + pbr->VirtualAddress, s_apszReloc[fType]));
+ switch (fType)
+ {
+ case IMAGE_REL_BASED_HIGHLOW: /* 32-bit, add delta. */
+ *u.pu32 += (uint32_t)uDelta;
+ break;
+ case IMAGE_REL_BASED_DIR64: /* 64-bit, add delta. */
+ *u.pu64 += (RTINTPTR)uDelta;
+ break;
+ case IMAGE_REL_BASED_ABSOLUTE: /* Alignment placeholder. */
+ break;
+ /* odd ones */
+ case IMAGE_REL_BASED_LOW: /* 16-bit, add 1st 16-bit part of the delta. */
+ *u.pu16 += (uint16_t)uDelta;
+ break;
+ case IMAGE_REL_BASED_HIGH: /* 16-bit, add 2nd 16-bit part of the delta. */
+ *u.pu16 += (uint16_t)(uDelta >> 16);
+ break;
+ /* never ever seen these next two, and I'm not 100% sure they are correctly implemented here. */
+ case IMAGE_REL_BASED_HIGHADJ:
+ {
+ if (cRelocations <= 1)
+ {
+ AssertMsgFailed(("HIGHADJ missing 2nd record!\n"));
+ return VERR_BAD_EXE_FORMAT;
+ }
+ cRelocations--;
+ pwoffFixup++;
+ int32_t i32 = (uint32_t)(*u.pu16 << 16) | *pwoffFixup;
+ i32 += (uint32_t)uDelta;
+ i32 += 0x8000; //??
+ *u.pu16 = (uint16_t)(i32 >> 16);
+ break;
+ }
+ case IMAGE_REL_BASED_HIGH3ADJ:
+ {
+ if (cRelocations <= 2)
+ {
+ AssertMsgFailed(("HIGHADJ3 missing 2nd record!\n"));
+ return VERR_BAD_EXE_FORMAT;
+ }
+ cRelocations -= 2;
+ pwoffFixup++;
+ int64_t i64 = ((uint64_t)*u.pu16 << 32) | *(uint32_t *)pwoffFixup++;
+ i64 += (int64_t)uDelta << 16; //??
+ i64 += 0x80000000;//??
+ *u.pu16 = (uint16_t)(i64 >> 32);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Unknown fixup type %d offset=%#x\n", fType, offFixup));
+ break;
+ }
+
+ /*
+ * Next offset/type
+ */
+ pwoffFixup++;
+ cRelocations--;
+ } /* while loop */
+
+ /*
+ * Next Fixup chunk. (i.e. next page)
+ */
+ pbr = (PIMAGE_BASE_RELOCATION)((uintptr_t)pbr + pbr->SizeOfBlock);
+ } /* while loop */
+
+ return 0;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnRelocate} */
+static DECLCALLBACK(int) rtldrPERelocate(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress, RTUINTPTR OldBaseAddress,
+ PFNRTLDRIMPORT pfnGetImport, void *pvUser)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+
+ /*
+ * Do we have to read the image bits?
+ */
+ if (!pModPe->pvBits)
+ {
+ int rc = rtldrPEReadBits(pModPe);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Process imports.
+ */
+ int rc = ((PRTLDROPSPE)pMod->pOps)->pfnResolveImports(pModPe, pModPe->pvBits, pvBits, pfnGetImport, pvUser);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Apply relocations.
+ */
+ rc = rtldrPEApplyFixups(pModPe, pModPe->pvBits, pvBits, NewBaseAddress, OldBaseAddress);
+ AssertRC(rc);
+ }
+ return rc;
+}
+
+
+/**
+ * Internal worker for pfnGetSymbolEx and pfnQueryForwarderInfo.
+ *
+ * @returns IPRT status code.
+ * @param pModPe The PE module instance.
+ * @param iOrdinal The symbol ordinal, UINT32_MAX if named symbol.
+ * @param pszSymbol The symbol name.
+ * @param ppvBits The image bits pointer (input/output).
+ * @param puRvaExport Where to return the symbol RVA.
+ * @param puOrdinal Where to return the ordinal number. Optional.
+ */
+static int rtLdrPE_ExportToRva(PRTLDRMODPE pModPe, uint32_t iOrdinal, const char *pszSymbol,
+ const void **ppvBits, uint32_t *puRvaExport, uint32_t *puOrdinal)
+{
+ /*
+ * Check if there is actually anything to work on.
+ */
+ if ( !pModPe->ExportDir.VirtualAddress
+ || !pModPe->ExportDir.Size)
+ return VERR_SYMBOL_NOT_FOUND;
+
+ /*
+ * No bits supplied? Do we need to read the bits?
+ */
+ void const *pvBits = *ppvBits;
+ if (!pvBits)
+ {
+ if (!pModPe->pvBits)
+ {
+ int rc = rtldrPEReadBits(pModPe);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ *ppvBits = pvBits = pModPe->pvBits;
+ }
+
+ PIMAGE_EXPORT_DIRECTORY pExpDir = PE_RVA2TYPE(pvBits, pModPe->ExportDir.VirtualAddress, PIMAGE_EXPORT_DIRECTORY);
+ int iExpOrdinal = 0; /* index into address table. */
+ if (iOrdinal != UINT32_MAX)
+ {
+ /*
+ * Find ordinal export: Simple table lookup.
+ */
+ if ( iOrdinal >= pExpDir->Base + RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions)
+ || iOrdinal < pExpDir->Base)
+ return VERR_SYMBOL_NOT_FOUND;
+ iExpOrdinal = iOrdinal - pExpDir->Base;
+ }
+ else
+ {
+ /*
+ * Find Named Export: Do binary search on the name table.
+ */
+ uint32_t *paRVANames = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNames, uint32_t *);
+ uint16_t *paOrdinals = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNameOrdinals, uint16_t *);
+ int iStart = 1;
+ int iEnd = pExpDir->NumberOfNames;
+
+ for (;;)
+ {
+ /* end of search? */
+ if (iStart > iEnd)
+ {
+#ifdef RT_STRICT
+ /* do a linear search just to verify the correctness of the above algorithm */
+ for (unsigned i = 0; i < pExpDir->NumberOfNames; i++)
+ {
+ AssertMsg(i == 0 || strcmp(PE_RVA2TYPE(pvBits, paRVANames[i], const char *), PE_RVA2TYPE(pvBits, paRVANames[i - 1], const char *)) > 0,
+ ("bug in binary export search!!!\n"));
+ AssertMsg(strcmp(PE_RVA2TYPE(pvBits, paRVANames[i], const char *), pszSymbol) != 0,
+ ("bug in binary export search!!!\n"));
+ }
+#endif
+ return VERR_SYMBOL_NOT_FOUND;
+ }
+
+ int i = (iEnd - iStart) / 2 + iStart;
+ const char *pszExpName = PE_RVA2TYPE(pvBits, paRVANames[i - 1], const char *);
+ int diff = strcmp(pszExpName, pszSymbol);
+ if (diff > 0) /* pszExpName > pszSymbol: search chunck before i */
+ iEnd = i - 1;
+ else if (diff) /* pszExpName < pszSymbol: search chunk after i */
+ iStart = i + 1;
+ else /* pszExpName == pszSymbol */
+ {
+ iExpOrdinal = paOrdinals[i - 1];
+ break;
+ }
+ } /* binary search thru name table */
+ }
+
+ /*
+ * Found export (iExpOrdinal).
+ */
+ uint32_t *paAddress = PE_RVA2TYPE(pvBits, pExpDir->AddressOfFunctions, uint32_t *);
+ *puRvaExport = paAddress[iExpOrdinal];
+ if (puOrdinal)
+ *puOrdinal = iExpOrdinal;
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnGetSymbolEx} */
+static DECLCALLBACK(int) rtldrPEGetSymbolEx(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress,
+ uint32_t iOrdinal, const char *pszSymbol, RTUINTPTR *pValue)
+{
+ PRTLDRMODPE pThis = (PRTLDRMODPE)pMod;
+ uint32_t uRvaExport;
+ int rc = rtLdrPE_ExportToRva(pThis, iOrdinal, pszSymbol, &pvBits, &uRvaExport, NULL);
+ if (RT_SUCCESS(rc))
+ {
+
+ uint32_t offForwarder = uRvaExport - pThis->ExportDir.VirtualAddress;
+ if (offForwarder >= pThis->ExportDir.Size)
+ /* Get plain export address */
+ *pValue = PE_RVA2TYPE(BaseAddress, uRvaExport, RTUINTPTR);
+ else
+ {
+ /* Return the approximate length of the forwarder buffer. */
+ const char *pszForwarder = PE_RVA2TYPE(pvBits, uRvaExport, const char *);
+ *pValue = sizeof(RTLDRIMPORTINFO) + RTStrNLen(pszForwarder, offForwarder - pThis->ExportDir.Size);
+ rc = VERR_LDR_FORWARDER;
+ }
+ }
+ return rc;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnQueryForwarderInfo} */
+static DECLCALLBACK(int) rtldrPE_QueryForwarderInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, uint32_t iOrdinal,
+ const char *pszSymbol, PRTLDRIMPORTINFO pInfo, size_t cbInfo)
+{
+ AssertReturn(cbInfo >= sizeof(*pInfo), VERR_INVALID_PARAMETER);
+
+ PRTLDRMODPE pThis = (PRTLDRMODPE)pMod;
+ uint32_t uRvaExport;
+ int rc = rtLdrPE_ExportToRva(pThis, iOrdinal, pszSymbol, &pvBits, &uRvaExport, &iOrdinal);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t offForwarder = uRvaExport - pThis->ExportDir.VirtualAddress;
+ if (offForwarder < pThis->ExportDir.Size)
+ {
+ const char *pszForwarder = PE_RVA2TYPE(pvBits, uRvaExport, const char *);
+
+ /*
+ * Parse and validate the string. We must make sure it's valid
+ * UTF-8, so we restrict it to ASCII.
+ */
+ const char *pszEnd = RTStrEnd(pszForwarder, offForwarder - pThis->ExportDir.Size);
+ if (pszEnd)
+ {
+ /* The module name. */
+ char ch;
+ uint32_t off = 0;
+ while ((ch = pszForwarder[off]) != '.' && ch != '\0')
+ {
+ if (RT_UNLIKELY((uint8_t)ch >= 0x80))
+ return VERR_LDR_BAD_FORWARDER;
+ off++;
+ }
+ if (RT_UNLIKELY(ch != '.'))
+ return VERR_LDR_BAD_FORWARDER;
+ uint32_t const offDot = off;
+ off++;
+
+ /* The function name or ordinal number. Ordinals starts with a hash. */
+ uint32_t iImpOrdinal;
+ if (pszForwarder[off] != '#')
+ {
+ iImpOrdinal = UINT32_MAX;
+ while ((ch = pszForwarder[off]) != '\0')
+ {
+ if (RT_UNLIKELY((uint8_t)ch >= 0x80))
+ return VERR_LDR_BAD_FORWARDER;
+ off++;
+ }
+ if (RT_UNLIKELY(off == offDot + 1))
+ return VERR_LDR_BAD_FORWARDER;
+ }
+ else
+ {
+ rc = RTStrToUInt32Full(&pszForwarder[off + 1], 10, &iImpOrdinal);
+ if (RT_UNLIKELY(rc != VINF_SUCCESS || iImpOrdinal > UINT16_MAX))
+ return VERR_LDR_BAD_FORWARDER;
+ }
+
+ /*
+ * Enough buffer?
+ */
+ uint32_t cbNeeded = RT_UOFFSETOF_DYN(RTLDRIMPORTINFO, szModule[iImpOrdinal != UINT32_MAX ? offDot + 1 : off + 1]);
+ if (cbNeeded > cbInfo)
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Fill in the return buffer.
+ */
+ pInfo->iSelfOrdinal = iOrdinal;
+ pInfo->iOrdinal = iImpOrdinal;
+ if (iImpOrdinal == UINT32_MAX)
+ {
+ pInfo->pszSymbol = &pInfo->szModule[offDot + 1];
+ memcpy(&pInfo->szModule[0], pszForwarder, off + 1);
+ }
+ else
+ {
+ pInfo->pszSymbol = NULL;
+ memcpy(&pInfo->szModule[0], pszForwarder, offDot);
+ }
+ pInfo->szModule[offDot] = '\0';
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_LDR_BAD_FORWARDER;
+ }
+ else
+ rc = VERR_LDR_NOT_FORWARDER;
+ }
+ return rc;
+}
+
+
+/**
+ * Slow version of rtldrPEEnumSymbols that'll work without all of the image
+ * being accessible.
+ *
+ * This is mainly for use in debuggers and similar.
+ */
+static int rtldrPEEnumSymbolsSlow(PRTLDRMODPE pThis, unsigned fFlags, RTUINTPTR BaseAddress,
+ PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ /*
+ * We enumerates by ordinal, which means using a slow linear search for
+ * getting any name
+ */
+ PCIMAGE_EXPORT_DIRECTORY pExpDir = NULL;
+ int rc = rtldrPEReadPartByRva(pThis, NULL, pThis->ExportDir.VirtualAddress, pThis->ExportDir.Size,
+ (void const **)&pExpDir);
+ if (RT_FAILURE(rc))
+ return rc;
+ uint32_t const cOrdinals = RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions);
+
+ uint32_t const *paAddress = NULL;
+ rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfFunctions, cOrdinals * sizeof(uint32_t),
+ (void const **)&paAddress);
+ uint32_t const *paRVANames = NULL;
+ if (RT_SUCCESS(rc) && pExpDir->NumberOfNames)
+ rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfNames, pExpDir->NumberOfNames * sizeof(uint32_t),
+ (void const **)&paRVANames);
+ uint16_t const *paOrdinals = NULL;
+ if (RT_SUCCESS(rc) && pExpDir->NumberOfNames)
+ rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfNameOrdinals, pExpDir->NumberOfNames * sizeof(uint16_t),
+ (void const **)&paOrdinals);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t uNamePrev = 0;
+ for (uint32_t uOrdinal = 0; uOrdinal < cOrdinals; uOrdinal++)
+ {
+ if (paAddress[uOrdinal] /* needed? */)
+ {
+ /*
+ * Look for name.
+ */
+ uint32_t uRvaName = UINT32_MAX;
+ /* Search from previous + 1 to the end. */
+ unsigned uName = uNamePrev + 1;
+ while (uName < pExpDir->NumberOfNames)
+ {
+ if (paOrdinals[uName] == uOrdinal)
+ {
+ uRvaName = paRVANames[uName];
+ uNamePrev = uName;
+ break;
+ }
+ uName++;
+ }
+ if (uRvaName == UINT32_MAX)
+ {
+ /* Search from start to the previous. */
+ uName = 0;
+ for (uName = 0 ; uName <= uNamePrev; uName++)
+ {
+ if (paOrdinals[uName] == uOrdinal)
+ {
+ uRvaName = paRVANames[uName];
+ uNamePrev = uName;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Get address.
+ */
+ uint32_t uRVAExport = paAddress[uOrdinal];
+ RTUINTPTR Value;
+ if (uRVAExport - pThis->ExportDir.VirtualAddress >= pThis->ExportDir.Size)
+ Value = PE_RVA2TYPE(BaseAddress, uRVAExport, RTUINTPTR);
+ else if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD))
+ Value = RTLDR_ENUM_SYMBOL_FWD_ADDRESS;
+ else
+ continue;
+
+ /* Read in the name if found one. */
+ char szAltName[32];
+ const char *pszName = NULL;
+ if (uRvaName != UINT32_MAX)
+ {
+ uint32_t cbName = 0x1000 - (uRvaName & 0xfff);
+ if (cbName < 10 || cbName > 512)
+ cbName = 128;
+ rc = rtldrPEReadPartByRva(pThis, NULL, uRvaName, cbName, (void const **)&pszName);
+ while (RT_SUCCESS(rc) && RTStrNLen(pszName, cbName) == cbName)
+ {
+ rtldrPEFreePart(pThis, NULL, pszName);
+ pszName = NULL;
+ if (cbName >= _4K)
+ break;
+ cbName += 128;
+ rc = rtldrPEReadPartByRva(pThis, NULL, uRvaName, cbName, (void const **)&pszName);
+ }
+ }
+ if (!pszName)
+ {
+ RTStrPrintf(szAltName, sizeof(szAltName), "Ordinal%#x", uOrdinal);
+ pszName = szAltName;
+ }
+
+ /*
+ * Call back.
+ */
+ rc = pfnCallback(&pThis->Core, pszName, uOrdinal + pExpDir->Base, Value, pvUser);
+ if (pszName != szAltName && pszName)
+ rtldrPEFreePart(pThis, NULL, pszName);
+ if (rc)
+ break;
+ }
+ }
+ }
+
+ rtldrPEFreePart(pThis, NULL, paOrdinals);
+ rtldrPEFreePart(pThis, NULL, paRVANames);
+ rtldrPEFreePart(pThis, NULL, paAddress);
+ rtldrPEFreePart(pThis, NULL, pExpDir);
+ return rc;
+
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnEnumSymbols} */
+static DECLCALLBACK(int) rtldrPEEnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits, RTUINTPTR BaseAddress,
+ PFNRTLDRENUMSYMS pfnCallback, void *pvUser)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+ NOREF(fFlags); /* ignored ... */
+
+ /*
+ * Check if there is actually anything to work on.
+ */
+ if ( !pModPe->ExportDir.VirtualAddress
+ || !pModPe->ExportDir.Size)
+ return VERR_SYMBOL_NOT_FOUND;
+
+ /*
+ * No bits supplied? Do we need to read the bits?
+ */
+ if (!pvBits)
+ {
+ if (!pModPe->pvBits)
+ {
+ int rc = rtldrPEReadBits(pModPe);
+ if (RT_FAILURE(rc))
+ return rtldrPEEnumSymbolsSlow(pModPe, fFlags, BaseAddress, pfnCallback, pvUser);
+ }
+ pvBits = pModPe->pvBits;
+ }
+
+ /*
+ * We enumerates by ordinal, which means using a slow linear search for
+ * getting any name
+ */
+ PIMAGE_EXPORT_DIRECTORY pExpDir = PE_RVA2TYPE(pvBits, pModPe->ExportDir.VirtualAddress, PIMAGE_EXPORT_DIRECTORY);
+ uint32_t *paAddress = PE_RVA2TYPE(pvBits, pExpDir->AddressOfFunctions, uint32_t *);
+ uint32_t *paRVANames = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNames, uint32_t *);
+ uint16_t *paOrdinals = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNameOrdinals, uint16_t *);
+ uint32_t uNamePrev = 0;
+ unsigned cOrdinals = RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions);
+ for (unsigned uOrdinal = 0; uOrdinal < cOrdinals; uOrdinal++)
+ {
+ if (paAddress[uOrdinal] /* needed? */)
+ {
+ /*
+ * Look for name.
+ */
+ const char *pszName = NULL;
+ /* Search from previous + 1 to the end. */
+ uint32_t uName = uNamePrev + 1;
+ while (uName < pExpDir->NumberOfNames)
+ {
+ if (paOrdinals[uName] == uOrdinal)
+ {
+ pszName = PE_RVA2TYPE(pvBits, paRVANames[uName], const char *);
+ uNamePrev = uName;
+ break;
+ }
+ uName++;
+ }
+ if (!pszName)
+ {
+ /* Search from start to the previous. */
+ uName = 0;
+ for (uName = 0 ; uName <= uNamePrev; uName++)
+ {
+ if (paOrdinals[uName] == uOrdinal)
+ {
+ pszName = PE_RVA2TYPE(pvBits, paRVANames[uName], const char *);
+ uNamePrev = uName;
+ break;
+ }
+ }
+ }
+
+ /*
+ * Get address.
+ */
+ uint32_t uRVAExport = paAddress[uOrdinal];
+ RTUINTPTR Value;
+ if (uRVAExport - pModPe->ExportDir.VirtualAddress >= pModPe->ExportDir.Size)
+ Value = PE_RVA2TYPE(BaseAddress, uRVAExport, RTUINTPTR);
+ else if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD))
+ Value = RTLDR_ENUM_SYMBOL_FWD_ADDRESS;
+ else
+ continue;
+
+ /*
+ * Call back.
+ */
+ int rc = pfnCallback(pMod, pszName, uOrdinal + pExpDir->Base, Value, pvUser);
+ if (rc)
+ return rc;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnEnumDbgInfo} */
+static DECLCALLBACK(int) rtldrPE_EnumDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits,
+ PFNRTLDRENUMDBG pfnCallback, void *pvUser)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+ int rc;
+
+ /*
+ * Debug info directory empty?
+ */
+ if ( !pModPe->DebugDir.VirtualAddress
+ || !pModPe->DebugDir.Size)
+ return VINF_SUCCESS;
+
+ /*
+ * Allocate temporary memory for a path buffer (this code is also compiled
+ * and maybe even used in stack starved environments).
+ */
+ char *pszPath = (char *)RTMemTmpAlloc(RTPATH_MAX);
+ if (!pszPath)
+ return VERR_NO_TMP_MEMORY;
+
+ /*
+ * Get the debug directory.
+ */
+ if (!pvBits)
+ pvBits = pModPe->pvBits;
+
+ PCIMAGE_DEBUG_DIRECTORY paDbgDir;
+ int rcRet = rtldrPEReadPartByRva(pModPe, pvBits, pModPe->DebugDir.VirtualAddress, pModPe->DebugDir.Size,
+ (void const **)&paDbgDir);
+ if (RT_FAILURE(rcRet))
+ {
+ RTMemTmpFree(pszPath);
+ return rcRet;
+ }
+
+ /*
+ * Enumerate the debug directory.
+ */
+ uint32_t const cEntries = pModPe->DebugDir.Size / sizeof(paDbgDir[0]);
+ for (uint32_t i = 0; i < cEntries; i++)
+ {
+ if (paDbgDir[i].PointerToRawData < pModPe->offEndOfHdrs)
+ continue;
+ if (paDbgDir[i].SizeOfData < 4)
+ continue;
+
+ void const *pvPart = NULL;
+ RTLDRDBGINFO DbgInfo;
+ RT_ZERO(DbgInfo.u);
+ DbgInfo.iDbgInfo = i;
+ DbgInfo.offFile = paDbgDir[i].PointerToRawData;
+ DbgInfo.LinkAddress = paDbgDir[i].AddressOfRawData < pModPe->cbImage
+ && paDbgDir[i].AddressOfRawData >= pModPe->offEndOfHdrs
+ ? paDbgDir[i].AddressOfRawData : NIL_RTLDRADDR;
+ DbgInfo.cb = paDbgDir[i].SizeOfData;
+ DbgInfo.pszExtFile = NULL;
+
+ rc = VINF_SUCCESS;
+ switch (paDbgDir[i].Type)
+ {
+ case IMAGE_DEBUG_TYPE_CODEVIEW:
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW;
+ DbgInfo.u.Cv.cbImage = pModPe->cbImage;
+ DbgInfo.u.Cv.uMajorVer = paDbgDir[i].MajorVersion;
+ DbgInfo.u.Cv.uMinorVer = paDbgDir[i].MinorVersion;
+ DbgInfo.u.Cv.uTimestamp = paDbgDir[i].TimeDateStamp;
+ if ( paDbgDir[i].SizeOfData < RTPATH_MAX
+ && paDbgDir[i].SizeOfData > 16
+ && ( DbgInfo.LinkAddress != NIL_RTLDRADDR
+ || DbgInfo.offFile > 0)
+ )
+ {
+ rc = rtldrPEReadPart(pModPe, pvBits, DbgInfo.offFile, DbgInfo.LinkAddress, paDbgDir[i].SizeOfData, &pvPart);
+ if (RT_SUCCESS(rc))
+ {
+ PCCVPDB20INFO pCv20 = (PCCVPDB20INFO)pvPart;
+ if ( pCv20->u32Magic == CVPDB20INFO_MAGIC
+ && pCv20->offDbgInfo == 0
+ && paDbgDir[i].SizeOfData > RT_UOFFSETOF(CVPDB20INFO, szPdbFilename) )
+ {
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB20;
+ DbgInfo.u.Pdb20.cbImage = pModPe->cbImage;
+ DbgInfo.u.Pdb20.uTimestamp = pCv20->uTimestamp;
+ DbgInfo.u.Pdb20.uAge = pCv20->uAge;
+ DbgInfo.pszExtFile = (const char *)&pCv20->szPdbFilename[0];
+ }
+ else if ( pCv20->u32Magic == CVPDB70INFO_MAGIC
+ && paDbgDir[i].SizeOfData > RT_UOFFSETOF(CVPDB70INFO, szPdbFilename) )
+ {
+ PCCVPDB70INFO pCv70 = (PCCVPDB70INFO)pCv20;
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70;
+ DbgInfo.u.Pdb70.cbImage = pModPe->cbImage;
+ DbgInfo.u.Pdb70.Uuid = pCv70->PdbUuid;
+ DbgInfo.u.Pdb70.uAge = pCv70->uAge;
+ DbgInfo.pszExtFile = (const char *)&pCv70->szPdbFilename[0];
+ }
+ }
+ else
+ rcRet = rc;
+ }
+ break;
+
+ case IMAGE_DEBUG_TYPE_MISC:
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_UNKNOWN;
+ if ( paDbgDir[i].SizeOfData < RTPATH_MAX
+ && paDbgDir[i].SizeOfData > RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data))
+ {
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_DBG;
+ DbgInfo.u.Dbg.cbImage = pModPe->cbImage;
+ if (DbgInfo.LinkAddress != NIL_RTLDRADDR)
+ DbgInfo.u.Dbg.uTimestamp = paDbgDir[i].TimeDateStamp;
+ else
+ DbgInfo.u.Dbg.uTimestamp = pModPe->uTimestamp; /* NT4 SP1 ntfs.sys hack. Generic? */
+
+ rc = rtldrPEReadPart(pModPe, pvBits, DbgInfo.offFile, DbgInfo.LinkAddress, paDbgDir[i].SizeOfData, &pvPart);
+ if (RT_SUCCESS(rc))
+ {
+ PCIMAGE_DEBUG_MISC pMisc = (PCIMAGE_DEBUG_MISC)pvPart;
+ if ( pMisc->DataType == IMAGE_DEBUG_MISC_EXENAME
+ && pMisc->Length == paDbgDir[i].SizeOfData)
+ {
+ if (!pMisc->Unicode)
+ DbgInfo.pszExtFile = (const char *)&pMisc->Data[0];
+ else
+ {
+ rc = RTUtf16ToUtf8Ex((PCRTUTF16)&pMisc->Data[0],
+ (pMisc->Length - RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data)) / sizeof(RTUTF16),
+ &pszPath, RTPATH_MAX, NULL);
+ if (RT_SUCCESS(rc))
+ DbgInfo.pszExtFile = pszPath;
+ else
+ rcRet = rc; /* continue without a filename. */
+ }
+ }
+ }
+ else
+ rcRet = rc; /* continue without a filename. */
+ }
+ break;
+
+ case IMAGE_DEBUG_TYPE_COFF:
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_COFF;
+ DbgInfo.u.Coff.cbImage = pModPe->cbImage;
+ DbgInfo.u.Coff.uMajorVer = paDbgDir[i].MajorVersion;
+ DbgInfo.u.Coff.uMinorVer = paDbgDir[i].MinorVersion;
+ DbgInfo.u.Coff.uTimestamp = paDbgDir[i].TimeDateStamp;
+ break;
+
+ default:
+ DbgInfo.enmType = RTLDRDBGINFOTYPE_UNKNOWN;
+ break;
+ }
+
+ /* Fix (hack) the file name encoding. We don't have Windows-1252 handy,
+ so we'll be using Latin-1 as a reasonable approximation.
+ (I don't think we know exactly which encoding this is anyway, as
+ it's probably the current ANSI/Windows code page for the process
+ generating the image anyways.) */
+ if (DbgInfo.pszExtFile && DbgInfo.pszExtFile != pszPath)
+ {
+ rc = RTLatin1ToUtf8Ex(DbgInfo.pszExtFile,
+ paDbgDir[i].SizeOfData - ((uintptr_t)DbgInfo.pszExtFile - (uintptr_t)pvBits),
+ &pszPath, RTPATH_MAX, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rcRet = rc;
+ DbgInfo.pszExtFile = NULL;
+ }
+ }
+ if (DbgInfo.pszExtFile)
+ RTPathChangeToUnixSlashes(pszPath, true /*fForce*/);
+
+ rc = pfnCallback(pMod, &DbgInfo, pvUser);
+ rtldrPEFreePart(pModPe, pvBits, pvPart);
+ if (rc != VINF_SUCCESS)
+ {
+ rcRet = rc;
+ break;
+ }
+ }
+
+ rtldrPEFreePart(pModPe, pvBits, paDbgDir);
+ RTMemTmpFree(pszPath);
+ return rcRet;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnEnumSegments} */
+static DECLCALLBACK(int) rtldrPE_EnumSegments(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+ RTLDRSEG SegInfo;
+
+ /*
+ * The first section is a fake one covering the headers.
+ */
+ SegInfo.pszName = "NtHdrs";
+ SegInfo.cchName = 6;
+ SegInfo.SelFlat = 0;
+ SegInfo.Sel16bit = 0;
+ SegInfo.fFlags = 0;
+ SegInfo.fProt = RTMEM_PROT_READ;
+ SegInfo.Alignment = 1;
+ SegInfo.LinkAddress = pModPe->uImageBase;
+ SegInfo.RVA = 0;
+ SegInfo.offFile = 0;
+ SegInfo.cb = pModPe->cbHeaders;
+ SegInfo.cbFile = pModPe->cbHeaders;
+ SegInfo.cbMapped = pModPe->cbHeaders;
+ if (!(pModPe->paSections[0].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
+ SegInfo.cbMapped = pModPe->paSections[0].VirtualAddress;
+ int rc = pfnCallback(pMod, &SegInfo, pvUser);
+
+ /*
+ * Then all the normal sections.
+ */
+ PCIMAGE_SECTION_HEADER pSh = pModPe->paSections;
+ for (uint32_t i = 0; i < pModPe->cSections && rc == VINF_SUCCESS; i++, pSh++)
+ {
+ char szName[32];
+ SegInfo.pszName = (const char *)&pSh->Name[0];
+ SegInfo.cchName = (uint32_t)RTStrNLen(SegInfo.pszName, sizeof(pSh->Name));
+ if (SegInfo.cchName >= sizeof(pSh->Name))
+ {
+ memcpy(szName, &pSh->Name[0], sizeof(pSh->Name));
+ szName[sizeof(pSh->Name)] = '\0';
+ SegInfo.pszName = szName;
+ }
+ else if (SegInfo.cchName == 0)
+ {
+ SegInfo.pszName = szName;
+ SegInfo.cchName = (uint32_t)RTStrPrintf(szName, sizeof(szName), "UnamedSect%02u", i);
+ }
+ SegInfo.SelFlat = 0;
+ SegInfo.Sel16bit = 0;
+ SegInfo.fFlags = 0;
+ SegInfo.fProt = RTMEM_PROT_NONE;
+ if (pSh->Characteristics & IMAGE_SCN_MEM_READ)
+ SegInfo.fProt |= RTMEM_PROT_READ;
+ if (pSh->Characteristics & IMAGE_SCN_MEM_WRITE)
+ SegInfo.fProt |= RTMEM_PROT_WRITE;
+ if (pSh->Characteristics & IMAGE_SCN_MEM_EXECUTE)
+ SegInfo.fProt |= RTMEM_PROT_EXEC;
+ SegInfo.Alignment = (pSh->Characteristics & IMAGE_SCN_ALIGN_MASK) >> IMAGE_SCN_ALIGN_SHIFT;
+ if (SegInfo.Alignment > 0)
+ SegInfo.Alignment = RT_BIT_64(SegInfo.Alignment - 1);
+ else
+ SegInfo.Alignment = pModPe->uSectionAlign;
+ if (pSh->Characteristics & IMAGE_SCN_TYPE_NOLOAD)
+ {
+ SegInfo.LinkAddress = NIL_RTLDRADDR;
+ SegInfo.RVA = NIL_RTLDRADDR;
+ SegInfo.cbMapped = 0;
+ }
+ else
+ {
+ SegInfo.LinkAddress = pSh->VirtualAddress + pModPe->uImageBase;
+ SegInfo.RVA = pSh->VirtualAddress;
+ SegInfo.cbMapped = RT_ALIGN(pSh->Misc.VirtualSize, SegInfo.Alignment);
+ if (i + 1 < pModPe->cSections && !(pSh[1].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
+ SegInfo.cbMapped = pSh[1].VirtualAddress - pSh->VirtualAddress;
+ }
+ SegInfo.cb = pSh->Misc.VirtualSize;
+ if (pSh->PointerToRawData == 0 || pSh->SizeOfRawData == 0)
+ {
+ SegInfo.offFile = -1;
+ SegInfo.cbFile = 0;
+ }
+ else
+ {
+ SegInfo.offFile = pSh->PointerToRawData;
+ SegInfo.cbFile = pSh->SizeOfRawData;
+ }
+
+ rc = pfnCallback(pMod, &SegInfo, pvUser);
+ }
+
+ return rc;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnLinkAddressToSegOffset} */
+static DECLCALLBACK(int) rtldrPE_LinkAddressToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress,
+ uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+
+ LinkAddress -= pModPe->uImageBase;
+
+ /* Special header segment. */
+ if (LinkAddress < pModPe->paSections[0].VirtualAddress)
+ {
+ *piSeg = 0;
+ *poffSeg = LinkAddress;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Search the normal sections. (Could do this in binary fashion, they're
+ * sorted, but too much bother right now.)
+ */
+ if (LinkAddress > pModPe->cbImage)
+ return VERR_LDR_INVALID_LINK_ADDRESS;
+ uint32_t i = pModPe->cSections;
+ PCIMAGE_SECTION_HEADER paShs = pModPe->paSections;
+ while (i-- > 0)
+ if (!(paShs[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
+ {
+ uint32_t uAddr = paShs[i].VirtualAddress;
+ if (LinkAddress >= uAddr)
+ {
+ *poffSeg = LinkAddress - uAddr;
+ *piSeg = i + 1;
+ return VINF_SUCCESS;
+ }
+ }
+
+ return VERR_LDR_INVALID_LINK_ADDRESS;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnLinkAddressToRva} */
+static DECLCALLBACK(int) rtldrPE_LinkAddressToRva(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+
+ LinkAddress -= pModPe->uImageBase;
+ if (LinkAddress > pModPe->cbImage)
+ return VERR_LDR_INVALID_LINK_ADDRESS;
+ *pRva = LinkAddress;
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnSegOffsetToRva} */
+static DECLCALLBACK(int) rtldrPE_SegOffsetToRva(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg,
+ PRTLDRADDR pRva)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+
+ if (iSeg > pModPe->cSections)
+ return VERR_LDR_INVALID_SEG_OFFSET;
+
+ /** @todo should validate offSeg here... too lazy right now. */
+ if (iSeg == 0)
+ *pRva = offSeg;
+ else if (!(pModPe->paSections[iSeg - 1].Characteristics & IMAGE_SCN_TYPE_NOLOAD))
+ *pRva = offSeg + pModPe->paSections[iSeg - 1].VirtualAddress;
+ else
+ return VERR_LDR_INVALID_SEG_OFFSET;
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnRvaToSegOffset} */
+static DECLCALLBACK(int) rtldrPE_RvaToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR Rva,
+ uint32_t *piSeg, PRTLDRADDR poffSeg)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+ int rc = rtldrPE_LinkAddressToSegOffset(pMod, Rva + pModPe->uImageBase, piSeg, poffSeg);
+ if (RT_FAILURE(rc))
+ rc = VERR_LDR_INVALID_RVA;
+ return rc;
+}
+
+
+/**
+ * Worker for rtLdrPE_QueryProp and rtLdrPE_QueryImportModule that counts the
+ * number of imports, storing the result in RTLDRMODPE::cImports.
+ *
+ * @returns IPRT status code.
+ * @param pThis The PE module instance.
+ * @param pvBits Image bits if the caller had them available, NULL if
+ * not. Saves a couple of file accesses.
+ */
+static int rtLdrPE_CountImports(PRTLDRMODPE pThis, void const *pvBits)
+{
+ PCIMAGE_IMPORT_DESCRIPTOR paImpDescs;
+ int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ImportDir.VirtualAddress, pThis->ImportDir.Size,
+ (void const **)&paImpDescs);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t const cMax = pThis->ImportDir.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR);
+ uint32_t i = 0;
+ while ( i < cMax
+ && paImpDescs[i].Name > pThis->offNtHdrs
+ && paImpDescs[i].Name < pThis->cbImage
+ && paImpDescs[i].FirstThunk > pThis->offNtHdrs
+ && paImpDescs[i].FirstThunk < pThis->cbImage)
+ i++;
+ pThis->cImports = i;
+
+ rtldrPEFreePart(pThis, pvBits, paImpDescs);
+ }
+ return rc;
+}
+
+/**
+ * Worker for rtLdrPE_QueryImportModule and rtLdrPE_QueryInternalName that
+ * copies a zero termianted string at the given RVA into the RTLdrQueryPropEx
+ * output buffer.
+ *
+ * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size.
+ * @param pThis The PE module instance.
+ * @param pvBits Image bits if the caller had them available, NULL if
+ * not. Saves a couple of file accesses.
+ * @param uRvaString The RVA of the string to copy.
+ * @param cbMaxString The max string length.
+ * @param pvBuf The output buffer.
+ * @param cbBuf The buffer size.
+ * @param pcbRet Where to return the number of bytes we've returned
+ * (or in case of VERR_BUFFER_OVERFLOW would have).
+ */
+static int rtLdrPE_QueryNameAtRva(PRTLDRMODPE pThis, void const *pvBits, uint32_t uRvaString, uint32_t cbMaxString,
+ void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ int rc;
+ if ( uRvaString >= pThis->cbHeaders
+ && uRvaString < pThis->cbImage)
+ {
+ /*
+ * Limit the string.
+ */
+ uint32_t cbMax = pThis->cbImage - uRvaString;
+ if (cbMax > cbMaxString)
+ cbMax = cbMaxString;
+ char *pszString;
+ rc = rtldrPEReadPartByRva(pThis, pvBits, uRvaString, cbMax, (void const **)&pszString);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Make sure it's null terminated and valid UTF-8 encoding.
+ *
+ * Which encoding this really is isn't defined, I think,
+ * but we need to make sure we don't get bogus UTF-8 into
+ * the process, so making sure it's valid UTF-8 is a good
+ * as anything else since it covers ASCII.
+ */
+ size_t cchString = RTStrNLen(pszString, cbMaxString);
+ if (cchString < cbMaxString)
+ {
+ rc = RTStrValidateEncodingEx(pszString, cchString, 0 /*fFlags*/);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Copy out the result and we're done.
+ * (We have to do all the cleanup code though, so no return success here.)
+ */
+ *pcbRet = cchString + 1;
+ if (cbBuf >= cchString + 1)
+ memcpy(pvBuf, pszString, cchString + 1);
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ rc = VERR_BAD_EXE_FORMAT;
+ rtldrPEFreePart(pThis, pvBits, pszString);
+ }
+ }
+ else
+ rc = VERR_BAD_EXE_FORMAT;
+ return rc;
+}
+
+
+/**
+ * Worker for rtLdrPE_QueryProp that retrievs the name of an import DLL.
+ *
+ * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size.
+ * @param pThis The PE module instance.
+ * @param pvBits Image bits if the caller had them available, NULL if
+ * not. Saves a couple of file accesses.
+ * @param iImport The index of the import table descriptor to fetch
+ * the name from.
+ * @param pvBuf The output buffer.
+ * @param cbBuf The buffer size.
+ * @param pcbRet Where to return the number of bytes we've returned
+ * (or in case of VERR_BUFFER_OVERFLOW would have).
+ */
+static int rtLdrPE_QueryImportModule(PRTLDRMODPE pThis, void const *pvBits, uint32_t iImport,
+ void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ /*
+ * Make sure we got the import count.
+ */
+ int rc;
+ if (pThis->cImports == UINT32_MAX)
+ {
+ rc = rtLdrPE_CountImports(pThis, pvBits);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Check the index first, converting it to an RVA.
+ */
+ if (iImport < pThis->cImports)
+ {
+ uint32_t offEntry = iImport * sizeof(IMAGE_IMPORT_DESCRIPTOR) + pThis->ImportDir.VirtualAddress;
+
+ /*
+ * Retrieve the import table descriptor.
+ * Using 1024 as the max name length (should be more than enough).
+ */
+ PCIMAGE_IMPORT_DESCRIPTOR pImpDesc;
+ rc = rtldrPEReadPartByRva(pThis, pvBits, offEntry, sizeof(*pImpDesc), (void const **)&pImpDesc);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtLdrPE_QueryNameAtRva(pThis, pvBits, pImpDesc->Name, 1024 /*cchMaxString*/, pvBuf, cbBuf, pcbRet);
+ rtldrPEFreePart(pThis, pvBits, pImpDesc);
+ }
+ }
+ else
+ rc = VERR_NOT_FOUND;
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ *pcbRet = 0;
+ return rc;
+}
+
+
+/**
+ * Worker for rtLdrPE_QueryProp that retrieves the internal module name.
+ *
+ * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size.
+ * @param pThis The PE module instance.
+ * @param pvBits Image bits if the caller had them available, NULL if
+ * not. Saves a couple of file accesses.
+ * @param pvBuf The output buffer.
+ * @param cbBuf The buffer size.
+ * @param pcbRet Where to return the number of bytes we've returned
+ * (or in case of VERR_BUFFER_OVERFLOW would have).
+ */
+static int rtLdrPE_QueryInternalName(PRTLDRMODPE pThis, void const *pvBits, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ *pcbRet = 0;
+
+ if ( pThis->ExportDir.Size < sizeof(IMAGE_EXPORT_DIRECTORY)
+ || pThis->ExportDir.VirtualAddress == 0)
+ return VERR_NOT_FOUND;
+
+ PCIMAGE_EXPORT_DIRECTORY pExpDir;
+ int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ExportDir.VirtualAddress, sizeof(*pExpDir), (void const **)&pExpDir);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtLdrPE_QueryNameAtRva(pThis, pvBits, pExpDir->Name, 1024 /*cchMaxString*/, pvBuf, cbBuf, pcbRet);
+ rtldrPEFreePart(pThis, pvBits, pExpDir);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Worker for rtLdrPE_QueryProp that retrieves unwind information.
+ *
+ * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size.
+ * @param pThis The PE module instance.
+ * @param pvBits Image bits if the caller had them available, NULL if
+ * not. Saves a couple of file accesses.
+ * @param pvBuf The output buffer.
+ * @param cbBuf The buffer size.
+ * @param pcbRet Where to return the number of bytes we've returned
+ * (or in case of VERR_BUFFER_OVERFLOW would have).
+ */
+static int rtLdrPE_QueryUnwindTable(PRTLDRMODPE pThis, void const *pvBits, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ int rc;
+ uint32_t const cbSrc = pThis->ExceptionDir.Size;
+ if ( cbSrc > 0
+ && pThis->ExceptionDir.VirtualAddress > 0)
+ {
+ *pcbRet = cbSrc;
+ if (cbBuf >= cbSrc)
+ rc = rtldrPEReadPartByRvaInfoBuf(pThis, pvBits, pThis->ExceptionDir.VirtualAddress, cbSrc, pvBuf);
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else
+ {
+ *pcbRet = 0;
+ rc = VERR_NOT_FOUND;
+ }
+ return rc;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnQueryProp} */
+static DECLCALLBACK(int) rtldrPE_QueryProp(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits,
+ void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+ switch (enmProp)
+ {
+ case RTLDRPROP_TIMESTAMP_SECONDS:
+ Assert(*pcbRet == cbBuf);
+ if (cbBuf == sizeof(int32_t))
+ *(int32_t *)pvBuf = pModPe->uTimestamp;
+ else if (cbBuf == sizeof(int64_t))
+ *(int64_t *)pvBuf = pModPe->uTimestamp;
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ break;
+
+ case RTLDRPROP_IS_SIGNED:
+ Assert(cbBuf == sizeof(bool));
+ Assert(*pcbRet == cbBuf);
+ *(bool *)pvBuf = pModPe->offPkcs7SignedData != 0;
+ break;
+
+ case RTLDRPROP_PKCS7_SIGNED_DATA:
+ {
+ if (pModPe->cbPkcs7SignedData == 0)
+ return VERR_NOT_FOUND;
+ Assert(pModPe->offPkcs7SignedData > pModPe->SecurityDir.VirtualAddress);
+
+ *pcbRet = pModPe->cbPkcs7SignedData;
+ if (cbBuf < pModPe->cbPkcs7SignedData)
+ return VERR_BUFFER_OVERFLOW;
+ return pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pvBuf, pModPe->cbPkcs7SignedData,
+ pModPe->offPkcs7SignedData);
+ }
+
+#ifndef IPRT_WITHOUT_LDR_PAGE_HASHING
+ case RTLDRPROP_HASHABLE_PAGES:
+ *pcbRet = sizeof(uint32_t);
+ *(uint32_t *)pvBuf = rtLdrPE_GetHashablePages(pModPe);
+ return VINF_SUCCESS;
+
+ case RTLDRPROP_SHA1_PAGE_HASHES:
+ return rtLdrPE_QueryPageHashes(pModPe, RTDIGESTTYPE_SHA1, pvBuf, cbBuf, pcbRet);
+
+ case RTLDRPROP_SHA256_PAGE_HASHES:
+ return rtLdrPE_QueryPageHashes(pModPe, RTDIGESTTYPE_SHA256, pvBuf, cbBuf, pcbRet);
+#endif
+
+ case RTLDRPROP_SIGNATURE_CHECKS_ENFORCED:
+ Assert(cbBuf == sizeof(bool));
+ Assert(*pcbRet == cbBuf);
+ *(bool *)pvBuf = pModPe->offPkcs7SignedData > 0
+ && (pModPe->fDllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY);
+ break;
+
+ case RTLDRPROP_IMPORT_COUNT:
+ Assert(cbBuf == sizeof(uint32_t));
+ Assert(*pcbRet == cbBuf);
+ if (pModPe->cImports == UINT32_MAX)
+ {
+ int rc = rtLdrPE_CountImports(pModPe, pvBits);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ *(uint32_t *)pvBuf = pModPe->cImports;
+ break;
+
+ case RTLDRPROP_IMPORT_MODULE:
+ Assert(cbBuf >= sizeof(uint32_t));
+ return rtLdrPE_QueryImportModule(pModPe, pvBits, *(uint32_t *)pvBuf, pvBuf, cbBuf, pcbRet);
+
+ case RTLDRPROP_FILE_OFF_HEADER:
+ Assert(cbBuf == sizeof(uint32_t) || cbBuf == sizeof(uint64_t));
+ if (cbBuf == sizeof(uint32_t))
+ *(uint32_t *)pvBuf = pModPe->offNtHdrs;
+ else
+ *(uint64_t *)pvBuf = pModPe->offNtHdrs;
+ return VINF_SUCCESS;
+
+ case RTLDRPROP_INTERNAL_NAME:
+ return rtLdrPE_QueryInternalName(pModPe, pvBits, pvBuf, cbBuf, pcbRet);
+
+ case RTLDRPROP_UNWIND_TABLE:
+ return rtLdrPE_QueryUnwindTable(pModPe, pvBits, pvBuf, cbBuf, pcbRet);
+
+ case RTLDRPROP_UNWIND_INFO:
+ {
+ uint32_t uRva = *(uint32_t const *)pvBuf;
+ if (uRva < pModPe->cbImage)
+ {
+ uint32_t cbLeft = pModPe->cbImage - uRva;
+ uint32_t cbToRead = (uint32_t)RT_MIN(cbLeft, cbBuf);
+ *pcbRet = cbToRead;
+ return rtldrPEReadPartByRvaInfoBuf(pModPe, pvBits, uRva, cbToRead, pvBuf);
+ }
+ *pcbRet = 0;
+ return VINF_SUCCESS;
+ }
+
+ default:
+ return VERR_NOT_FOUND;
+ }
+ return VINF_SUCCESS;
+}
+
+
+
+/*
+ * Lots of Authenticode fun ahead.
+ */
+
+
+/**
+ * Initializes the hash context.
+ *
+ * @returns VINF_SUCCESS or VERR_NOT_SUPPORTED.
+ * @param pHashCtx The hash context union.
+ * @param enmDigest The hash type we're calculating..
+ */
+static int rtLdrPE_HashInit(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest)
+{
+ switch (enmDigest)
+ {
+ case RTDIGESTTYPE_SHA512: RTSha512Init(&pHashCtx->Sha512); break;
+ case RTDIGESTTYPE_SHA384: RTSha384Init(&pHashCtx->Sha384); break;
+ case RTDIGESTTYPE_SHA256: RTSha256Init(&pHashCtx->Sha256); break;
+ case RTDIGESTTYPE_SHA1: RTSha1Init(&pHashCtx->Sha1); break;
+ case RTDIGESTTYPE_MD5: RTMd5Init(&pHashCtx->Md5); break;
+ default: AssertFailedReturn(VERR_NOT_SUPPORTED);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Updates the hash with more data.
+ *
+ * @param pHashCtx The hash context union.
+ * @param enmDigest The hash type we're calculating..
+ * @param pvBuf Pointer to a buffer with bytes to add to thash.
+ * @param cbBuf How many bytes to add from @a pvBuf.
+ */
+static void rtLdrPE_HashUpdate(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest, void const *pvBuf, size_t cbBuf)
+{
+ switch (enmDigest)
+ {
+ case RTDIGESTTYPE_SHA512: RTSha512Update(&pHashCtx->Sha512, pvBuf, cbBuf); break;
+ case RTDIGESTTYPE_SHA384: RTSha384Update(&pHashCtx->Sha384, pvBuf, cbBuf); break;
+ case RTDIGESTTYPE_SHA256: RTSha256Update(&pHashCtx->Sha256, pvBuf, cbBuf); break;
+ case RTDIGESTTYPE_SHA1: RTSha1Update(&pHashCtx->Sha1, pvBuf, cbBuf); break;
+ case RTDIGESTTYPE_MD5: RTMd5Update(&pHashCtx->Md5, pvBuf, cbBuf); break;
+ default: AssertReleaseFailed();
+ }
+}
+
+
+/**
+ * Finalizes the hash calculations.
+ *
+ * @param pHashCtx The hash context union.
+ * @param enmDigest The hash type we're calculating..
+ * @param pHashRes The hash result union.
+ */
+static void rtLdrPE_HashFinalize(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest, PRTLDRPEHASHRESUNION pHashRes)
+{
+ switch (enmDigest)
+ {
+ case RTDIGESTTYPE_SHA512: RTSha512Final(&pHashCtx->Sha512, pHashRes->abSha512); break;
+ case RTDIGESTTYPE_SHA384: RTSha384Final(&pHashCtx->Sha384, pHashRes->abSha384); break;
+ case RTDIGESTTYPE_SHA256: RTSha256Final(&pHashCtx->Sha256, pHashRes->abSha256); break;
+ case RTDIGESTTYPE_SHA1: RTSha1Final(&pHashCtx->Sha1, pHashRes->abSha1); break;
+ case RTDIGESTTYPE_MD5: RTMd5Final(pHashRes->abMd5, &pHashCtx->Md5); break;
+ default: AssertReleaseFailed();
+ }
+}
+
+
+/**
+ * Returns the digest size for the given digest type.
+ *
+ * @returns Size in bytes.
+ * @param enmDigest The hash type in question.
+ */
+static uint32_t rtLdrPE_HashGetHashSize(RTDIGESTTYPE enmDigest)
+{
+ switch (enmDigest)
+ {
+ case RTDIGESTTYPE_SHA512: return RTSHA512_HASH_SIZE;
+ case RTDIGESTTYPE_SHA384: return RTSHA384_HASH_SIZE;
+ case RTDIGESTTYPE_SHA256: return RTSHA256_HASH_SIZE;
+ case RTDIGESTTYPE_SHA1: return RTSHA1_HASH_SIZE;
+ case RTDIGESTTYPE_MD5: return RTMD5_HASH_SIZE;
+ default: AssertReleaseFailedReturn(0);
+ }
+}
+
+
+#ifndef IPRT_WITHOUT_LDR_VERIFY
+/**
+ * Checks if the hash type is supported.
+ *
+ * @returns true/false.
+ * @param enmDigest The hash type in question.
+ */
+static bool rtLdrPE_HashIsSupported(RTDIGESTTYPE enmDigest)
+{
+ switch (enmDigest)
+ {
+ case RTDIGESTTYPE_SHA512:
+ case RTDIGESTTYPE_SHA384:
+ case RTDIGESTTYPE_SHA256:
+ case RTDIGESTTYPE_SHA1:
+ case RTDIGESTTYPE_MD5:
+ return true;
+ default:
+ return false;
+ }
+}
+#endif
+
+
+/**
+ * Calculate the special too watch out for when hashing the image.
+ *
+ * @returns IPRT status code.
+ * @param pModPe The PE module.
+ * @param pPlaces The structure where to store the special places.
+ * @param pErrInfo Optional error info.
+ */
+static int rtldrPe_CalcSpecialHashPlaces(PRTLDRMODPE pModPe, PRTLDRPEHASHSPECIALS pPlaces, PRTERRINFO pErrInfo)
+{
+ /*
+ * If we're here despite a missing signature, we need to get the file size.
+ */
+ pPlaces->cbToHash = pModPe->SecurityDir.VirtualAddress;
+ if (pPlaces->cbToHash == 0)
+ {
+ uint64_t cbFile = pModPe->Core.pReader->pfnSize(pModPe->Core.pReader);
+ pPlaces->cbToHash = (uint32_t)cbFile;
+ if (pPlaces->cbToHash != (uint64_t)cbFile)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_FILE_LENGTH_ERROR, "File is too large: %RTfoff", cbFile);
+ }
+
+ /*
+ * Calculate the special places.
+ */
+ pPlaces->offCksum = (uint32_t)pModPe->offNtHdrs
+ + (pModPe->f64Bit
+ ? RT_UOFFSETOF(IMAGE_NT_HEADERS64, OptionalHeader.CheckSum)
+ : RT_UOFFSETOF(IMAGE_NT_HEADERS32, OptionalHeader.CheckSum));
+ pPlaces->cbCksum = RT_SIZEOFMEMB(IMAGE_NT_HEADERS32, OptionalHeader.CheckSum);
+ pPlaces->offSecDir = (uint32_t)pModPe->offNtHdrs
+ + (pModPe->f64Bit
+ ? RT_UOFFSETOF(IMAGE_NT_HEADERS64, OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY])
+ : RT_UOFFSETOF(IMAGE_NT_HEADERS32, OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]));
+ pPlaces->cbSecDir = sizeof(IMAGE_DATA_DIRECTORY);
+ pPlaces->offEndSpecial = pPlaces->offSecDir + pPlaces->cbSecDir;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Calculates the whole image hash.
+ *
+ * The Authenticode_PE.docx version 1.0 explains how the hash is calculated,
+ * points 8 thru 14 are bogus. If you study them a little carefully, it is
+ * clear that the algorithm will only work if the raw data for the section have
+ * no gaps between them or in front of them. So, this elaborate section sorting
+ * by PointerToRawData and working them section by section could simply be
+ * replaced by one point:
+ *
+ * 8. Add all the file content between SizeOfHeaders and the
+ * attribute certificate table to the hash. Then finalize
+ * the hash.
+ *
+ * Not sure if Microsoft is screwing with us on purpose here or whether they
+ * assigned some of this work to less talented engineers and tech writers. I
+ * love fact that they say it's "simplified" and should yield the correct hash
+ * for "almost all" files. Stupid, Stupid, Microsofties!!
+ *
+ * My simplified implementation that just hashes the entire file up to the
+ * signature or end of the file produces the same SHA1 values as "signtool
+ * verify /v" does both for edited executables with gaps between/before/after
+ * sections raw data and normal executables without any gaps.
+ *
+ * @returns IPRT status code.
+ * @param pModPe The PE module.
+ * @param pvScratch Scratch buffer.
+ * @param cbScratch Size of the scratch buffer.
+ * @param enmDigest The hash digest type we're calculating.
+ * @param pHashCtx Hash context scratch area.
+ * @param pHashRes Hash result buffer.
+ * @param pErrInfo Optional error info buffer.
+ */
+static int rtldrPE_HashImageCommon(PRTLDRMODPE pModPe, void *pvScratch, uint32_t cbScratch, RTDIGESTTYPE enmDigest,
+ PRTLDRPEHASHCTXUNION pHashCtx, PRTLDRPEHASHRESUNION pHashRes, PRTERRINFO pErrInfo)
+{
+ int rc = rtLdrPE_HashInit(pHashCtx, enmDigest);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Calculate the special places.
+ */
+ RTLDRPEHASHSPECIALS SpecialPlaces = { 0, 0, 0, 0, 0, 0 }; /* shut up gcc */
+ rc = rtldrPe_CalcSpecialHashPlaces(pModPe, &SpecialPlaces, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Work our way thru the image data.
+ */
+ uint32_t off = 0;
+ while (off < SpecialPlaces.cbToHash)
+ {
+ uint32_t cbRead = RT_MIN(SpecialPlaces.cbToHash - off, cbScratch);
+ uint8_t *pbCur = (uint8_t *)pvScratch;
+ rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pbCur, cbRead, off);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_HASH, "Hash read error at %#x: %Rrc (cbRead=%#zx)",
+ off, rc, cbRead);
+
+ if (off < SpecialPlaces.offEndSpecial)
+ {
+ if (off < SpecialPlaces.offCksum)
+ {
+ /* Hash everything up to the checksum. */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum - off, cbRead);
+ rtLdrPE_HashUpdate(pHashCtx, enmDigest, pbCur, cbChunk);
+ pbCur += cbChunk;
+ cbRead -= cbChunk;
+ off += cbChunk;
+ }
+
+ if (off < SpecialPlaces.offCksum + SpecialPlaces.cbCksum && off >= SpecialPlaces.offCksum)
+ {
+ /* Skip the checksum */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum + SpecialPlaces.cbCksum - off, cbRead);
+ pbCur += cbChunk;
+ cbRead -= cbChunk;
+ off += cbChunk;
+ }
+
+ if (off < SpecialPlaces.offSecDir && off >= SpecialPlaces.offCksum + SpecialPlaces.cbCksum)
+ {
+ /* Hash everything between the checksum and the data dir entry. */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir - off, cbRead);
+ rtLdrPE_HashUpdate(pHashCtx, enmDigest, pbCur, cbChunk);
+ pbCur += cbChunk;
+ cbRead -= cbChunk;
+ off += cbChunk;
+ }
+
+ if (off < SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir && off >= SpecialPlaces.offSecDir)
+ {
+ /* Skip the security data directory entry. */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir - off, cbRead);
+ pbCur += cbChunk;
+ cbRead -= cbChunk;
+ off += cbChunk;
+ }
+ }
+
+ rtLdrPE_HashUpdate(pHashCtx, enmDigest, pbCur, cbRead);
+
+ /* Advance */
+ off += cbRead;
+ }
+
+ /*
+ * If there isn't a signature, experiments with signtool indicates that we
+ * have to zero padd the file size until it's a multiple of 8. (This is
+ * most likely to give 64-bit values in the certificate a natural alignment
+ * when memory mapped.)
+ */
+ if ( pModPe->SecurityDir.VirtualAddress != SpecialPlaces.cbToHash
+ && SpecialPlaces.cbToHash != RT_ALIGN_32(SpecialPlaces.cbToHash, WIN_CERTIFICATE_ALIGNMENT))
+ {
+ static const uint8_t s_abZeros[WIN_CERTIFICATE_ALIGNMENT] = { 0,0,0,0, 0,0,0,0 };
+ rtLdrPE_HashUpdate(pHashCtx, enmDigest, s_abZeros,
+ RT_ALIGN_32(SpecialPlaces.cbToHash, WIN_CERTIFICATE_ALIGNMENT) - SpecialPlaces.cbToHash);
+ }
+
+ /*
+ * Done. Finalize the hashes.
+ */
+ rtLdrPE_HashFinalize(pHashCtx, enmDigest, pHashRes);
+ return VINF_SUCCESS;
+}
+
+#ifndef IPRT_WITHOUT_LDR_PAGE_HASHING
+
+/**
+ * Returns the size of the page hashes, including the terminator entry.
+ *
+ * Used for handling RTLDRPROP_HASHABLE_PAGES.
+ *
+ * @returns Number of page hashes.
+ * @param pModPe The PE module.
+ */
+static uint32_t rtLdrPE_GetHashablePages(PRTLDRMODPE pModPe)
+{
+ uint32_t const cbPage = _4K;
+ uint32_t cPages = 1; /* termination entry */
+
+ /* Add implicit header section: */
+ cPages += (pModPe->cbHeaders + cbPage - 1) / cbPage;
+
+ /* Add on disk pages for each section. Each starts with a fresh page and
+ we ASSUMES that it is page aligned (in memory). */
+ for (uint32_t i = 0; i < pModPe->cSections; i++)
+ {
+ uint32_t const cbRawData = pModPe->paSections[i].SizeOfRawData;
+ if (cbRawData > 0)
+ cPages += (cbRawData + cbPage - 1) / cbPage;
+ }
+
+ return cPages;
+}
+
+
+/**
+ * Worker for rtLdrPE_QueryPageHashes.
+ *
+ * Keep in mind that rtldrPE_VerifyAllPageHashes does similar work, so some
+ * fixes may apply both places.
+ */
+static int rtLdrPE_CalcPageHashes(PRTLDRMODPE pModPe, RTDIGESTTYPE const enmDigest, uint32_t const cbHash,
+ uint8_t *pbDst, uint8_t *pbScratch, uint32_t cbScratch, uint32_t const cbPage)
+{
+ /*
+ * Calculate the special places.
+ */
+ RTLDRPEHASHSPECIALS SpecialPlaces = { 0, 0, 0, 0, 0, 0 }; /* shut up gcc */
+ int rc = rtldrPe_CalcSpecialHashPlaces(pModPe, &SpecialPlaces, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Walk section table and hash the pages in each. Because the headers are
+ * in an implicit section, the loop advancing is a little funky.
+ */
+ int32_t const cSections = pModPe->cSections;
+ int32_t iSection = -1;
+ uint32_t offRawData = 0;
+ uint32_t cbRawData = pModPe->cbHeaders;
+ uint32_t offLastPage = 0;
+
+ uint32_t const cbScratchReadMax = cbScratch / cbPage * cbPage;
+ uint32_t cbScratchRead = 0;
+ uint32_t offScratchRead = 0;
+
+ for (;;)
+ {
+ /*
+ * Process the pages in this section.
+ */
+ uint32_t cPagesInSection = (cbRawData + cbPage - 1) / cbPage;
+ for (uint32_t iPage = 0; iPage < cPagesInSection; iPage++)
+ {
+ uint32_t const offPageInSect = iPage * cbPage;
+ uint32_t const offPageInFile = offRawData + offPageInSect;
+ uint32_t const cbPageInFile = RT_MIN(cbPage, cbRawData - offPageInSect);
+ offLastPage = offPageInFile;
+
+ /* Calculate and output the page offset. */
+ *(uint32_t *)pbDst = offPageInFile;
+ pbDst += sizeof(uint32_t);
+
+ /*
+ * Read/find in the raw page.
+ */
+ /* Did we get a cache hit? */
+ uint8_t *pbCur = pbScratch;
+ if ( offPageInFile + cbPageInFile <= offScratchRead + cbScratchRead
+ && offPageInFile >= offScratchRead)
+ pbCur += offPageInFile - offScratchRead;
+ /* Missed, read more. */
+ else
+ {
+ offScratchRead = offPageInFile;
+ cbScratchRead = SpecialPlaces.cbToHash - offPageInFile;
+ if (cbScratchRead > cbScratchReadMax)
+ cbScratchRead = cbScratchReadMax;
+ rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pbCur, cbScratchRead, offScratchRead);
+ if (RT_FAILURE(rc))
+ return VERR_LDRVI_READ_ERROR_HASH;
+ }
+
+ /*
+ * Hash it.
+ */
+ RTLDRPEHASHCTXUNION HashCtx;
+ rc = rtLdrPE_HashInit(&HashCtx, enmDigest);
+ AssertRCReturn(rc, rc);
+
+ /* Deal with special places. */
+ uint32_t cbLeft = cbPageInFile;
+ if (offPageInFile < SpecialPlaces.offEndSpecial)
+ {
+ uint32_t off = offPageInFile;
+ if (off < SpecialPlaces.offCksum)
+ {
+ /* Hash everything up to the checksum. */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum - off, cbLeft);
+ rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk);
+ pbCur += cbChunk;
+ cbLeft -= cbChunk;
+ off += cbChunk;
+ }
+
+ if (off < SpecialPlaces.offCksum + SpecialPlaces.cbCksum && off >= SpecialPlaces.offCksum)
+ {
+ /* Skip the checksum */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum + SpecialPlaces.cbCksum - off, cbLeft);
+ pbCur += cbChunk;
+ cbLeft -= cbChunk;
+ off += cbChunk;
+ }
+
+ if (off < SpecialPlaces.offSecDir && off >= SpecialPlaces.offCksum + SpecialPlaces.cbCksum)
+ {
+ /* Hash everything between the checksum and the data dir entry. */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir - off, cbLeft);
+ rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk);
+ pbCur += cbChunk;
+ cbLeft -= cbChunk;
+ off += cbChunk;
+ }
+
+ if (off < SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir && off >= SpecialPlaces.offSecDir)
+ {
+ /* Skip the security data directory entry. */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir - off, cbLeft);
+ pbCur += cbChunk;
+ cbLeft -= cbChunk;
+ off += cbChunk;
+ }
+ }
+
+ rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbLeft);
+ if (cbPageInFile < cbPage)
+ rtLdrPE_HashUpdate(&HashCtx, enmDigest, g_abRTZero4K, cbPage - cbPageInFile);
+
+ /*
+ * Finish the hash calculation storing it in the table.
+ */
+ rtLdrPE_HashFinalize(&HashCtx, enmDigest, (PRTLDRPEHASHRESUNION)pbDst);
+ pbDst += cbHash;
+ }
+
+ /*
+ * Advance to the next section.
+ */
+ iSection++;
+ if (iSection >= cSections)
+ break;
+ offRawData = pModPe->paSections[iSection].PointerToRawData;
+ cbRawData = pModPe->paSections[iSection].SizeOfRawData;
+ }
+
+ /*
+ * Add the terminator entry.
+ */
+ *(uint32_t *)pbDst = offLastPage + cbPage;
+ RT_BZERO(&pbDst[sizeof(uint32_t)], cbHash);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Creates the page hash table for the image.
+ *
+ * Used for handling RTLDRPROP_SHA1_PAGE_HASHES and
+ * RTLDRPROP_SHA256_PAGE_HASHES.
+ *
+ * @returns IPRT status code.
+ * @param pModPe The PE module.
+ * @param enmDigest The digest to use when hashing the pages.
+ * @param pvBuf Where to return the page hash table.
+ * @param cbBuf The size of the buffer @a pvBuf points to.
+ * @param pcbRet Where to return the output/needed size.
+ */
+static int rtLdrPE_QueryPageHashes(PRTLDRMODPE pModPe, RTDIGESTTYPE enmDigest, void *pvBuf, size_t cbBuf, size_t *pcbRet)
+{
+ /*
+ * Check that we've got enough buffer space.
+ */
+ uint32_t const cbPage = _4K;
+ uint32_t const cEntries = rtLdrPE_GetHashablePages(pModPe);
+ uint32_t const cbHash = rtLdrPE_HashGetHashSize(enmDigest);
+ AssertReturn(cbHash > 0, VERR_INTERNAL_ERROR_3);
+
+ size_t const cbNeeded = (size_t)(cbHash + 4) * cEntries;
+ *pcbRet = cbNeeded;
+ if (cbNeeded > cbBuf)
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Allocate a scratch buffer and call worker to do the real job.
+ */
+# ifdef IN_RING0
+ uint32_t cbScratch = _256K - _4K;
+# else
+ uint32_t cbScratch = _1M;
+# endif
+ void *pvScratch = RTMemTmpAlloc(cbScratch);
+ if (!pvScratch)
+ {
+ cbScratch = _4K;
+ pvScratch = RTMemTmpAlloc(cbScratch);
+ if (!pvScratch)
+ return VERR_NO_TMP_MEMORY;
+ }
+
+ int rc = rtLdrPE_CalcPageHashes(pModPe, enmDigest, cbHash, (uint8_t *)pvBuf, (uint8_t *)pvScratch, cbScratch, cbPage);
+
+ RTMemTmpFree(pvScratch);
+ return rc;
+}
+
+#endif /* !IPRT_WITHOUT_LDR_PAGE_HASHING */
+#ifndef IPRT_WITHOUT_LDR_VERIFY
+
+/**
+ * Verifies image preconditions not checked by the open validation code.
+ *
+ * @returns IPRT status code.
+ * @param pModPe The PE module.
+ * @param pErrInfo Optional error info buffer.
+ */
+static int rtldrPE_VerifySignatureImagePrecoditions(PRTLDRMODPE pModPe, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate the sections. While doing so, track the amount of section raw
+ * section data in the file so we can use this to validate the signature
+ * table location later.
+ */
+ uint32_t offNext = pModPe->cbHeaders; /* same */
+ for (uint32_t i = 0; i < pModPe->cSections; i++)
+ if (pModPe->paSections[i].SizeOfRawData > 0)
+ {
+ uint64_t offEnd = (uint64_t)pModPe->paSections[i].PointerToRawData + pModPe->paSections[i].SizeOfRawData;
+ if (offEnd > offNext)
+ {
+ if (offEnd >= _2G)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_SECTION_RAW_DATA_VALUES,
+ "Section %#u specifies file data after 2GB: PointerToRawData=%#x SizeOfRawData=%#x",
+ i, pModPe->paSections[i].PointerToRawData, pModPe->paSections[i].SizeOfRawData);
+ offNext = (uint32_t)offEnd;
+ }
+ }
+ uint32_t offEndOfSectionData = offNext;
+
+ /*
+ * Validate the signature.
+ */
+ if (!pModPe->SecurityDir.Size)
+ return RTErrInfoSet(pErrInfo, VERR_LDRVI_NOT_SIGNED, "Not signed.");
+
+ uint32_t const offSignature = pModPe->SecurityDir.VirtualAddress;
+ uint32_t const cbSignature = pModPe->SecurityDir.Size;
+ if ( cbSignature <= sizeof(WIN_CERTIFICATE)
+ || cbSignature >= RTLDRMODPE_MAX_SECURITY_DIR_SIZE
+ || offSignature >= _2G)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY,
+ "Invalid security data dir entry: cb=%#x off=%#x", cbSignature, offSignature);
+
+ if (offSignature < offEndOfSectionData)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY,
+ "Invalid security data dir entry offset: %#x offEndOfSectionData=%#x",
+ offSignature, offEndOfSectionData);
+
+ if (RT_ALIGN_32(offSignature, WIN_CERTIFICATE_ALIGNMENT) != offSignature)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY,
+ "Misaligned security dir entry offset: %#x (alignment=%#x)",
+ offSignature, WIN_CERTIFICATE_ALIGNMENT);
+
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads and checks the raw signature data.
+ *
+ * @returns IPRT status code.
+ * @param pModPe The PE module.
+ * @param ppSignature Where to return the pointer to the parsed
+ * signature data. Pass to
+ * rtldrPE_VerifySignatureDestroy when done.
+ * @param pErrInfo Optional error info buffer.
+ */
+static int rtldrPE_VerifySignatureRead(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE *ppSignature, PRTERRINFO pErrInfo)
+{
+ *ppSignature = NULL;
+ AssertReturn(pModPe->SecurityDir.Size > 0, VERR_INTERNAL_ERROR_2);
+
+ /*
+ * Allocate memory for reading and parsing it.
+ */
+ if (pModPe->SecurityDir.Size >= RTLDRMODPE_MAX_SECURITY_DIR_SIZE)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY,
+ "Signature directory is to large: %#x", pModPe->SecurityDir.Size);
+
+ PRTLDRPESIGNATURE pSignature = (PRTLDRPESIGNATURE)RTMemTmpAllocZ(sizeof(*pSignature) + 64 + pModPe->SecurityDir.Size);
+ if (!pSignature)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_NO_MEMORY_SIGNATURE, "Failed to allocate %zu bytes",
+ sizeof(*pSignature) + 64 + pModPe->SecurityDir.Size);
+ pSignature->pRawData = RT_ALIGN_PT(pSignature + 1, 64, WIN_CERTIFICATE const *);
+
+
+ /*
+ * Read it.
+ */
+ int rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, (void *)pSignature->pRawData,
+ pModPe->SecurityDir.Size, pModPe->SecurityDir.VirtualAddress);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check the table we've read in.
+ */
+ uint32_t cbLeft = pModPe->SecurityDir.Size;
+ WIN_CERTIFICATE const *pEntry = pSignature->pRawData;
+ for (;;)
+ {
+ if ( cbLeft < sizeof(*pEntry)
+ || pEntry->dwLength > cbLeft
+ || pEntry->dwLength < sizeof(*pEntry))
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_HDR_LENGTH,
+ "Bad WIN_CERTIFICATE length: %#x (max %#x, signature=%u)",
+ pEntry->dwLength, cbLeft, 0);
+ else if (pEntry->wRevision != WIN_CERT_REVISION_2_0)
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_HDR_REVISION,
+ "Unsupported WIN_CERTIFICATE revision value: %#x (signature=%u)",
+ pEntry->wRevision, 0);
+ else if (pEntry->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA)
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_HDR_TYPE,
+ "Unsupported WIN_CERTIFICATE certificate type: %#x (signature=%u)",
+ pEntry->wCertificateType, 0);
+ else
+ {
+ /* advance */
+ uint32_t cbEntry = RT_ALIGN(pEntry->dwLength, WIN_CERTIFICATE_ALIGNMENT);
+ if (cbEntry >= cbLeft)
+ break;
+ cbLeft -= cbEntry;
+ pEntry = (WIN_CERTIFICATE *)((uintptr_t)pEntry + cbEntry);
+
+ /* For now, only one entry is supported. */
+ rc = RTErrInfoSet(pErrInfo, VERR_LDRVI_BAD_CERT_MULTIPLE, "Multiple WIN_CERTIFICATE entries are not supported.");
+ }
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *ppSignature = pSignature;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_SIGNATURE, "Signature read error: %Rrc", rc);
+ RTMemTmpFree(pSignature);
+ return rc;
+}
+
+
+/**
+ * Destroys the parsed signature.
+ *
+ * @param pModPe The PE module.
+ * @param pSignature The signature data to destroy.
+ */
+static void rtldrPE_VerifySignatureDestroy(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature)
+{
+ RT_NOREF_PV(pModPe);
+ RTCrPkcs7ContentInfo_Delete(&pSignature->PrimaryContentInfo);
+ if (pSignature->paNested)
+ {
+ RTMemTmpFree(pSignature->paNested);
+ pSignature->paNested = NULL;
+ }
+ RTMemTmpFree(pSignature);
+}
+
+
+/**
+ * Handles nested signatures.
+ *
+ * @returns IPRT status code.
+ * @param pSignature The signature status structure. Returns with
+ * cNested = 0 and paNested = NULL if no nested
+ * signatures.
+ * @param pErrInfo Where to return extended error info (optional).
+ */
+static int rtldrPE_VerifySignatureDecodeNested(PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo)
+{
+ Assert(pSignature->cNested == 0);
+ Assert(pSignature->paNested == NULL);
+
+ /*
+ * Count nested signatures.
+ */
+ uint32_t cNested = 0;
+ for (uint32_t iSignerInfo = 0; iSignerInfo < pSignature->Primary.pSignedData->SignerInfos.cItems; iSignerInfo++)
+ {
+ PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignature->Primary.pSignedData->SignerInfos.papItems[iSignerInfo];
+ for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->UnauthenticatedAttributes.cItems; iAttrib++)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib];
+ if (pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE)
+ {
+ Assert(pAttrib->uValues.pContentInfos);
+ cNested += pAttrib->uValues.pContentInfos->cItems;
+ }
+ }
+ }
+ if (!cNested)
+ return VINF_SUCCESS;
+
+ /*
+ * Allocate and populate the info structures.
+ */
+ pSignature->paNested = (PRTLDRPESIGNATUREONE)RTMemTmpAllocZ(sizeof(pSignature->paNested[0]) * cNested);
+ if (!pSignature->paNested)
+ return RTErrInfoSetF(pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate space for %u nested signatures", cNested);
+ pSignature->cNested = cNested;
+
+ cNested = 0;
+ for (uint32_t iSignerInfo = 0; iSignerInfo < pSignature->Primary.pSignedData->SignerInfos.cItems; iSignerInfo++)
+ {
+ PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignature->Primary.pSignedData->SignerInfos.papItems[iSignerInfo];
+ for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->UnauthenticatedAttributes.cItems; iAttrib++)
+ {
+ PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib];
+ if (pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE)
+ {
+ for (uint32_t iItem = 0; iItem < pAttrib->uValues.pContentInfos->cItems; iItem++, cNested++)
+ {
+ PRTLDRPESIGNATUREONE pInfo = &pSignature->paNested[cNested];
+ PRTCRPKCS7CONTENTINFO pContentInfo = pAttrib->uValues.pContentInfos->papItems[iItem];
+ pInfo->pContentInfo = pContentInfo;
+ pInfo->iSignature = cNested;
+
+ if (RTCrPkcs7ContentInfo_IsSignedData(pInfo->pContentInfo))
+ { /* likely */ }
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, /** @todo error code*/
+ "Nested#%u: PKCS#7 is not 'signedData': %s", cNested, pInfo->pContentInfo->ContentType.szObjId);
+ PRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData;
+ pInfo->pSignedData = pSignedData;
+
+ /*
+ * Check the authenticode bits.
+ */
+ if (!strcmp(pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID))
+ { /* likely */ }
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID,
+ "Nested#%u: Unknown pSignedData.ContentInfo.ContentType.szObjId value: %s (expected %s)",
+ cNested, pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID);
+ pInfo->pIndData = pSignedData->ContentInfo.u.pIndirectDataContent;
+ Assert(pInfo->pIndData);
+
+ /*
+ * Check that things add up.
+ */
+ int rc = RTCrPkcs7SignedData_CheckSanity(pSignedData,
+ RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
+ pErrInfo, "SD");
+ if (RT_SUCCESS(rc))
+ rc = RTCrSpcIndirectDataContent_CheckSanityEx(pInfo->pIndData,
+ pSignedData,
+ RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH,
+ pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTCRX509ALGORITHMIDENTIFIER pDigestAlgorithm = &pInfo->pIndData->DigestInfo.DigestAlgorithm;
+ pInfo->enmDigest = RTCrX509AlgorithmIdentifier_GetDigestType(pDigestAlgorithm,
+ true /*fPureDigestsOnly*/);
+ AssertReturn(pInfo->enmDigest != RTDIGESTTYPE_INVALID, VERR_INTERNAL_ERROR_4); /* Checked above! */
+ }
+ else
+ return rc;
+ }
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Decodes the raw signature.
+ *
+ * @returns IPRT status code.
+ * @param pModPe The PE module.
+ * @param pSignature The signature data.
+ * @param pErrInfo Optional error info buffer.
+ */
+static int rtldrPE_VerifySignatureDecode(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo)
+{
+ WIN_CERTIFICATE const *pEntry = pSignature->pRawData;
+ AssertReturn(pEntry->wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA, VERR_INTERNAL_ERROR_2);
+ AssertReturn(pEntry->wRevision == WIN_CERT_REVISION_2_0, VERR_INTERNAL_ERROR_2);
+ RT_NOREF_PV(pModPe);
+
+ RTASN1CURSORPRIMARY PrimaryCursor;
+ RTAsn1CursorInitPrimary(&PrimaryCursor,
+ &pEntry->bCertificate[0],
+ pEntry->dwLength - RT_UOFFSETOF(WIN_CERTIFICATE, bCertificate),
+ pErrInfo,
+ &g_RTAsn1DefaultAllocator,
+ 0,
+ "WinCert");
+
+ PRTLDRPESIGNATUREONE pInfo = &pSignature->Primary;
+ pInfo->pContentInfo = &pSignature->PrimaryContentInfo;
+ int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, pInfo->pContentInfo, "CI");
+ if (RT_SUCCESS(rc))
+ {
+ if (RTCrPkcs7ContentInfo_IsSignedData(pInfo->pContentInfo))
+ {
+ pInfo->pSignedData = pInfo->pContentInfo->u.pSignedData;
+
+ /*
+ * Decode the authenticode bits.
+ */
+ if (!strcmp(pInfo->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID))
+ {
+ pInfo->pIndData = pInfo->pSignedData->ContentInfo.u.pIndirectDataContent;
+ Assert(pInfo->pIndData);
+
+ /*
+ * Check that things add up.
+ */
+ rc = RTCrPkcs7SignedData_CheckSanity(pInfo->pSignedData,
+ RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
+ | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
+ pErrInfo, "SD");
+ if (RT_SUCCESS(rc))
+ rc = RTCrSpcIndirectDataContent_CheckSanityEx(pInfo->pIndData,
+ pInfo->pSignedData,
+ RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH,
+ pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTCRX509ALGORITHMIDENTIFIER pDigestAlgorithm = &pInfo->pIndData->DigestInfo.DigestAlgorithm;
+ pInfo->enmDigest = RTCrX509AlgorithmIdentifier_GetDigestType(pDigestAlgorithm, true /*fPureDigestsOnly*/);
+ AssertReturn(pInfo->enmDigest != RTDIGESTTYPE_INVALID, VERR_INTERNAL_ERROR_4); /* Checked above! */
+
+ /*
+ * Deal with nested signatures.
+ */
+ rc = rtldrPE_VerifySignatureDecodeNested(pSignature, pErrInfo);
+ }
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID,
+ "Unknown pSignedData.ContentInfo.ContentType.szObjId value: %s (expected %s)",
+ pInfo->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID);
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, /** @todo error code*/
+ "PKCS#7 is not 'signedData': %s", pInfo->pContentInfo->ContentType.szObjId);
+ }
+ return rc;
+}
+
+
+
+static int rtldrPE_VerifyAllPageHashes(PRTLDRMODPE pModPe, PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE pAttrib, RTDIGESTTYPE enmDigest,
+ void *pvScratch, size_t cbScratch, uint32_t iSignature, PRTERRINFO pErrInfo)
+{
+ AssertReturn(cbScratch >= _4K, VERR_INTERNAL_ERROR_3);
+
+ /*
+ * Calculate the special places.
+ */
+ RTLDRPEHASHSPECIALS SpecialPlaces = { 0, 0, 0, 0, 0, 0 }; /* shut up gcc */
+ int rc = rtldrPe_CalcSpecialHashPlaces(pModPe, &SpecialPlaces, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint32_t const cbHash = rtLdrPE_HashGetHashSize(enmDigest);
+ uint32_t const cPages = pAttrib->u.pPageHashes->RawData.Asn1Core.cb / (cbHash + 4);
+ if (cPages * (cbHash + 4) != pAttrib->u.pPageHashes->RawData.Asn1Core.cb)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_SIZE_OVERFLOW,
+ "Signature #%u - Page hashes size issue in: cb=%#x cbHash=%#x",
+ iSignature, pAttrib->u.pPageHashes->RawData.Asn1Core.cb, cbHash);
+
+ /*
+ * Walk the table.
+ */
+ uint32_t const cbScratchReadMax = cbScratch & ~(uint32_t)(_4K - 1);
+ uint32_t cbScratchRead = 0;
+ uint32_t offScratchRead = 0;
+
+ uint32_t offPrev = 0;
+#ifdef COMPLICATED_AND_WRONG
+ uint32_t offSectEnd = pModPe->cbHeaders;
+ uint32_t iSh = UINT32_MAX;
+#endif
+ uint8_t const *pbHashTab = pAttrib->u.pPageHashes->RawData.Asn1Core.uData.pu8;
+ for (uint32_t iPage = 0; iPage < cPages - 1; iPage++)
+ {
+ /* Decode the page offset. */
+ uint32_t const offPageInFile = RT_MAKE_U32_FROM_U8(pbHashTab[0], pbHashTab[1], pbHashTab[2], pbHashTab[3]);
+ if (RT_UNLIKELY(offPageInFile >= SpecialPlaces.cbToHash))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_TOO_LONG,
+ "Signature #%u - Page hash entry #%u is beyond the signature table start: %#x, %#x",
+ iSignature, iPage, offPageInFile, SpecialPlaces.cbToHash);
+ if (RT_UNLIKELY(offPageInFile < offPrev))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_NOT_STRICTLY_SORTED,
+ "Signature #%u - Page hash table is not strictly sorted: entry #%u @%#x, previous @%#x\n",
+ iSignature, iPage, offPageInFile, offPrev);
+
+#ifdef COMPLICATED_AND_WRONG
+ /* Figure out how much to read and how much to zero. Need keep track
+ of the on-disk section boundraries. */
+ if (offPageInFile >= offSectEnd)
+ {
+ iSh++;
+ if ( iSh < pModPe->cSections
+ && offPageInFile - pModPe->paSections[iSh].PointerToRawData < pModPe->paSections[iSh].SizeOfRawData)
+ offSectEnd = pModPe->paSections[iSh].PointerToRawData + pModPe->paSections[iSh].SizeOfRawData;
+ else
+ {
+ iSh = 0;
+ while ( iSh < pModPe->cSections
+ && offPageInFile - pModPe->paSections[iSh].PointerToRawData >= pModPe->paSections[iSh].SizeOfRawData)
+ iSh++;
+ if (iSh < pModPe->cSections)
+ offSectEnd = pModPe->paSections[iSh].PointerToRawData + pModPe->paSections[iSh].SizeOfRawData;
+ else
+ return RTErrInfoSetF(pErrInfo, VERR_PAGE_HASH_TAB_HASHES_NON_SECTION_DATA,
+ "Signature #%u - Page hash entry #%u isn't in any section: %#x",
+ iSignature, iPage, offPageInFile);
+ }
+ }
+
+#else
+ /* Figure out how much to read and how much take as zero. Use the next
+ page offset and the signature as upper boundraries. */
+#endif
+ uint32_t cbPageInFile = _4K;
+#ifdef COMPLICATED_AND_WRONG
+ if (offPageInFile + cbPageInFile > offSectEnd)
+ cbPageInFile = offSectEnd - offPageInFile;
+#else
+ if (iPage + 1 < cPages)
+ {
+ uint32_t offNextPage = RT_MAKE_U32_FROM_U8(pbHashTab[0 + 4 + cbHash], pbHashTab[1 + 4 + cbHash],
+ pbHashTab[2 + 4 + cbHash], pbHashTab[3 + 4 + cbHash]);
+ if (offNextPage - offPageInFile < cbPageInFile)
+ cbPageInFile = offNextPage - offPageInFile;
+ }
+#endif
+
+ if (offPageInFile + cbPageInFile > SpecialPlaces.cbToHash)
+ cbPageInFile = SpecialPlaces.cbToHash - offPageInFile;
+
+ /* Did we get a cache hit? */
+ uint8_t *pbCur = (uint8_t *)pvScratch;
+ if ( offPageInFile + cbPageInFile <= offScratchRead + cbScratchRead
+ && offPageInFile >= offScratchRead)
+ pbCur += offPageInFile - offScratchRead;
+ /* Missed, read more. */
+ else
+ {
+ offScratchRead = offPageInFile;
+#ifdef COMPLICATED_AND_WRONG
+ cbScratchRead = offSectEnd - offPageInFile;
+#else
+ cbScratchRead = SpecialPlaces.cbToHash - offPageInFile;
+#endif
+ if (cbScratchRead > cbScratchReadMax)
+ cbScratchRead = cbScratchReadMax;
+ rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pbCur, cbScratchRead, offScratchRead);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_HASH,
+ "Signature #%u - Page hash read error at %#x: %Rrc (cbScratchRead=%#zx)",
+ iSignature, offScratchRead, rc, cbScratchRead);
+ }
+
+ /*
+ * Hash it.
+ */
+ RTLDRPEHASHCTXUNION HashCtx;
+ rc = rtLdrPE_HashInit(&HashCtx, enmDigest);
+ AssertRCReturn(rc, rc);
+
+ /* Deal with special places. */
+ uint32_t cbLeft = cbPageInFile;
+ if (offPageInFile < SpecialPlaces.offEndSpecial)
+ {
+ uint32_t off = offPageInFile;
+ if (off < SpecialPlaces.offCksum)
+ {
+ /* Hash everything up to the checksum. */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum - off, cbLeft);
+ rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk);
+ pbCur += cbChunk;
+ cbLeft -= cbChunk;
+ off += cbChunk;
+ }
+
+ if (off < SpecialPlaces.offCksum + SpecialPlaces.cbCksum && off >= SpecialPlaces.offCksum)
+ {
+ /* Skip the checksum */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum + SpecialPlaces.cbCksum - off, cbLeft);
+ pbCur += cbChunk;
+ cbLeft -= cbChunk;
+ off += cbChunk;
+ }
+
+ if (off < SpecialPlaces.offSecDir && off >= SpecialPlaces.offCksum + SpecialPlaces.cbCksum)
+ {
+ /* Hash everything between the checksum and the data dir entry. */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir - off, cbLeft);
+ rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk);
+ pbCur += cbChunk;
+ cbLeft -= cbChunk;
+ off += cbChunk;
+ }
+
+ if (off < SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir && off >= SpecialPlaces.offSecDir)
+ {
+ /* Skip the security data directory entry. */
+ uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir - off, cbLeft);
+ pbCur += cbChunk;
+ cbLeft -= cbChunk;
+ off += cbChunk;
+ }
+ }
+
+ rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbLeft);
+ if (cbPageInFile < _4K)
+ rtLdrPE_HashUpdate(&HashCtx, enmDigest, g_abRTZero4K, _4K - cbPageInFile);
+
+ /*
+ * Finish the hash calculation and compare the result.
+ */
+ RTLDRPEHASHRESUNION HashRes;
+ rtLdrPE_HashFinalize(&HashCtx, enmDigest, &HashRes);
+
+ pbHashTab += 4;
+ if (memcmp(pbHashTab, &HashRes, cbHash) != 0)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_MISMATCH,
+ "Signature #%u - Page hash failed for page #%u, @%#x, %#x bytes: %.*Rhxs != %.*Rhxs",
+ iSignature, iPage, offPageInFile, cbPageInFile, (size_t)cbHash, pbHashTab,
+ (size_t)cbHash, &HashRes);
+ pbHashTab += cbHash;
+ offPrev = offPageInFile;
+ }
+
+ /*
+ * Check that the last table entry has a hash value of zero.
+ */
+ if (!ASMMemIsZero(pbHashTab + 4, cbHash))
+ return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_TOO_LONG,
+ "Signature #%u - Malformed final page hash table entry: #%u %#010x %.*Rhxs",
+ iSignature, cPages - 1, RT_MAKE_U32_FROM_U8(pbHashTab[0], pbHashTab[1], pbHashTab[2], pbHashTab[3]),
+ (size_t)cbHash, pbHashTab + 4);
+ return VINF_SUCCESS;
+}
+
+
+static int rtldrPE_VerifySignatureValidateOnePageHashes(PRTLDRMODPE pModPe, PRTLDRPESIGNATUREONE pInfo,
+ void *pvScratch, uint32_t cbScratch, PRTERRINFO pErrInfo)
+{
+ /*
+ * Compare the page hashes if present.
+ *
+ * Seems the difference between V1 and V2 page hash attributes is
+ * that v1 uses SHA-1 while v2 uses SHA-256. The data structures
+ * seems to be identical otherwise. Initially we assumed the digest
+ * algorithm was supposed to be RTCRSPCINDIRECTDATACONTENT::DigestInfo,
+ * i.e. the same as for the whole image hash. The initial approach
+ * worked just fine, but this makes more sense.
+ *
+ * (See also comments in osslsigncode.c (google it).)
+ */
+ PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE pAttrib;
+ /* V2 - SHA-256: */
+ pAttrib = RTCrSpcIndirectDataContent_GetPeImageObjAttrib(pInfo->pIndData,
+ RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V2);
+ if (pAttrib)
+ return rtldrPE_VerifyAllPageHashes(pModPe, pAttrib, RTDIGESTTYPE_SHA256, pvScratch, cbScratch,
+ pInfo->iSignature + 1, pErrInfo);
+
+ /* V1 - SHA-1: */
+ pAttrib = RTCrSpcIndirectDataContent_GetPeImageObjAttrib(pInfo->pIndData,
+ RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1);
+ if (pAttrib)
+ return rtldrPE_VerifyAllPageHashes(pModPe, pAttrib, RTDIGESTTYPE_SHA1, pvScratch, cbScratch,
+ pInfo->iSignature + 1, pErrInfo);
+
+ /* No page hashes: */
+ return VINF_SUCCESS;
+}
+
+
+static int rtldrPE_VerifySignatureValidateOneImageHash(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature,
+ PRTLDRPESIGNATUREONE pInfo, void *pvScratch, uint32_t cbScratch,
+ PRTERRINFO pErrInfo)
+{
+ /*
+ * Assert sanity.
+ */
+ AssertReturn(pInfo->enmDigest > RTDIGESTTYPE_INVALID && pInfo->enmDigest < RTDIGESTTYPE_END, VERR_INTERNAL_ERROR_4);
+ AssertPtrReturn(pInfo->pIndData, VERR_INTERNAL_ERROR_5);
+ AssertReturn(RTASN1CORE_IS_PRESENT(&pInfo->pIndData->DigestInfo.Digest.Asn1Core), VERR_INTERNAL_ERROR_5);
+ AssertPtrReturn(pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, VERR_INTERNAL_ERROR_5);
+
+ /* Check that the hash is supported by the code here before continuing. */
+ AssertReturn(rtLdrPE_HashIsSupported(pInfo->enmDigest),
+ RTErrInfoSetF(pErrInfo, VERR_CR_DIGEST_NOT_SUPPORTED, "Unsupported digest type: %d", pInfo->enmDigest));
+
+ /*
+ * Skip it if we've already verified it.
+ */
+ if (pInfo->fValidatedImageHash)
+ return VINF_SUCCESS;
+
+ /*
+ * Calculate it.
+ */
+ uint32_t const cbHash = rtLdrPE_HashGetHashSize(pInfo->enmDigest);
+ AssertReturn(pInfo->pIndData->DigestInfo.Digest.Asn1Core.cb == cbHash, VERR_INTERNAL_ERROR_5);
+
+ int rc = rtldrPE_HashImageCommon(pModPe, pvScratch, cbScratch, pInfo->enmDigest,
+ &pSignature->HashCtx, &pInfo->HashRes, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pInfo->fValidatedImageHash = true;
+ if (memcmp(&pInfo->HashRes, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, cbHash) == 0)
+ {
+ /*
+ * Verify other signatures with the same digest type.
+ */
+ RTLDRPEHASHRESUNION const * const pHashRes = &pInfo->HashRes;
+ RTDIGESTTYPE const enmDigestType = pInfo->enmDigest;
+ for (uint32_t i = 0; i < pSignature->cNested; i++)
+ {
+ pInfo = &pSignature->paNested[i]; /* Note! pInfo changes! */
+ if ( !pInfo->fValidatedImageHash
+ && pInfo->enmDigest == enmDigestType
+ /* paranoia from the top of this function: */
+ && pInfo->pIndData
+ && RTASN1CORE_IS_PRESENT(&pInfo->pIndData->DigestInfo.Digest.Asn1Core)
+ && pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv
+ && pInfo->pIndData->DigestInfo.Digest.Asn1Core.cb == cbHash)
+ {
+ pInfo->fValidatedImageHash = true;
+ if (memcmp(pHashRes, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, cbHash) != 0)
+ {
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_IMAGE_HASH_MISMATCH,
+ "Full image signature #%u mismatch: %.*Rhxs, expected %.*Rhxs", pInfo->iSignature + 1,
+ cbHash, pHashRes,
+ cbHash, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv);
+ break;
+ }
+ }
+ }
+ }
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_IMAGE_HASH_MISMATCH,
+ "Full image signature #%u mismatch: %.*Rhxs, expected %.*Rhxs", pInfo->iSignature + 1,
+ cbHash, &pInfo->HashRes,
+ cbHash, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv);
+ }
+ return rc;
+}
+
+
+/**
+ * Validates the image hash, including page hashes if present.
+ *
+ * @returns IPRT status code.
+ * @param pModPe The PE module.
+ * @param pSignature The decoded signature data.
+ * @param pErrInfo Optional error info buffer.
+ */
+static int rtldrPE_VerifySignatureValidateHash(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo)
+{
+ /*
+ * Allocate a temporary memory buffer.
+ * Note! The _4K that gets subtracted is to avoid that the 16-byte heap
+ * block header in ring-0 (iprt) caused any unnecessary internal
+ * heap fragmentation.
+ */
+# ifdef IN_RING0
+ uint32_t cbScratch = _256K - _4K;
+# else
+ uint32_t cbScratch = _1M;
+# endif
+ void *pvScratch = RTMemTmpAlloc(cbScratch);
+ if (!pvScratch)
+ {
+ cbScratch = _4K;
+ pvScratch = RTMemTmpAlloc(cbScratch);
+ if (!pvScratch)
+ return RTErrInfoSet(pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate 4KB of scratch space for hashing image.");
+ }
+
+ /*
+ * Verify signatures.
+ */
+ /* Image hashes: */
+ int rc = rtldrPE_VerifySignatureValidateOneImageHash(pModPe, pSignature, &pSignature->Primary,
+ pvScratch, cbScratch, pErrInfo);
+ for (unsigned i = 0; i < pSignature->cNested && RT_SUCCESS(rc); i++)
+ rc = rtldrPE_VerifySignatureValidateOneImageHash(pModPe, pSignature, &pSignature->paNested[i],
+ pvScratch, cbScratch, pErrInfo);
+
+ /* Page hashes: */
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtldrPE_VerifySignatureValidateOnePageHashes(pModPe, &pSignature->Primary, pvScratch, cbScratch, pErrInfo);
+ for (unsigned i = 0; i < pSignature->cNested && RT_SUCCESS(rc); i++)
+ rc = rtldrPE_VerifySignatureValidateOnePageHashes(pModPe, &pSignature->paNested[i], pvScratch, cbScratch, pErrInfo);
+ }
+
+ /*
+ * Ditch the scratch buffer.
+ */
+ RTMemTmpFree(pvScratch);
+ return rc;
+}
+
+#endif /* !IPRT_WITHOUT_LDR_VERIFY */
+
+
+/** @interface_method_impl{RTLDROPS,pfnVerifySignature} */
+static DECLCALLBACK(int) rtldrPE_VerifySignature(PRTLDRMODINTERNAL pMod, PFNRTLDRVALIDATESIGNEDDATA pfnCallback, void *pvUser,
+ PRTERRINFO pErrInfo)
+{
+#ifndef IPRT_WITHOUT_LDR_VERIFY
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+
+ int rc = rtldrPE_VerifySignatureImagePrecoditions(pModPe, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ PRTLDRPESIGNATURE pSignature = NULL;
+ rc = rtldrPE_VerifySignatureRead(pModPe, &pSignature, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtldrPE_VerifySignatureDecode(pModPe, pSignature, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rc = rtldrPE_VerifySignatureValidateHash(pModPe, pSignature, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Work the callback.
+ */
+ /* The primary signature: */
+ RTLDRSIGNATUREINFO Info;
+ Info.iSignature = 0;
+ Info.cSignatures = (uint16_t)(1 + pSignature->cNested);
+ Info.enmType = RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA;
+ Info.pvSignature = pSignature->Primary.pContentInfo;
+ Info.cbSignature = sizeof(*pSignature->Primary.pContentInfo);
+ Info.pvExternalData = NULL;
+ Info.cbExternalData = 0;
+ rc = pfnCallback(&pModPe->Core, &Info, pErrInfo, pvUser);
+
+ /* The nested signatures: */
+ for (uint32_t iNested = 0; iNested < pSignature->cNested && rc == VINF_SUCCESS; iNested++)
+ {
+ Info.iSignature = (uint16_t)(1 + iNested);
+ Info.cSignatures = (uint16_t)(1 + pSignature->cNested);
+ Info.enmType = RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA;
+ Info.pvSignature = pSignature->paNested[iNested].pContentInfo;
+ Info.cbSignature = sizeof(*pSignature->paNested[iNested].pContentInfo);
+ Info.pvExternalData = NULL;
+ Info.cbExternalData = 0;
+ rc = pfnCallback(&pModPe->Core, &Info, pErrInfo, pvUser);
+ }
+ }
+ rtldrPE_VerifySignatureDestroy(pModPe, pSignature);
+ }
+ }
+ return rc;
+#else
+ RT_NOREF_PV(pMod); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(pvUser); RT_NOREF_PV(pErrInfo);
+ return VERR_NOT_SUPPORTED;
+#endif
+}
+
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnHashImage}
+ */
+static DECLCALLBACK(int) rtldrPE_HashImage(PRTLDRMODINTERNAL pMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+
+ /*
+ * Allocate a temporary memory buffer.
+ */
+ uint32_t cbScratch = _16K;
+ void *pvScratch = RTMemTmpAlloc(cbScratch);
+ if (!pvScratch)
+ {
+ cbScratch = _4K;
+ pvScratch = RTMemTmpAlloc(cbScratch);
+ if (!pvScratch)
+ return VERR_NO_TMP_MEMORY;
+ }
+
+ /*
+ * Do the hashing.
+ */
+ RTLDRPEHASHCTXUNION HashCtx;
+ RTLDRPEHASHRESUNION HashRes;
+ int rc = rtldrPE_HashImageCommon(pModPe, pvScratch, cbScratch, enmDigest, &HashCtx, &HashRes, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Copy out the result.
+ */
+ RT_NOREF(cbHash); /* verified by caller */
+ switch (enmDigest)
+ {
+ case RTDIGESTTYPE_SHA512: memcpy(pabHash, HashRes.abSha512, sizeof(HashRes.abSha512)); break;
+ case RTDIGESTTYPE_SHA256: memcpy(pabHash, HashRes.abSha256, sizeof(HashRes.abSha256)); break;
+ case RTDIGESTTYPE_SHA1: memcpy(pabHash, HashRes.abSha1, sizeof(HashRes.abSha1)); break;
+ case RTDIGESTTYPE_MD5: memcpy(pabHash, HashRes.abMd5, sizeof(HashRes.abMd5)); break;
+ default: AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Binary searches the lookup table.
+ *
+ * @returns RVA of unwind info on success, UINT32_MAX on failure.
+ * @param paFunctions The table to lookup @a uRva in.
+ * @param iEnd Size of the table.
+ * @param uRva The RVA of the function we want.
+ */
+DECLINLINE(PCIMAGE_RUNTIME_FUNCTION_ENTRY)
+rtldrPE_LookupRuntimeFunctionEntry(PCIMAGE_RUNTIME_FUNCTION_ENTRY paFunctions, size_t iEnd, uint32_t uRva)
+{
+ size_t iBegin = 0;
+ while (iBegin < iEnd)
+ {
+ size_t const i = iBegin + (iEnd - iBegin) / 2;
+ PCIMAGE_RUNTIME_FUNCTION_ENTRY pEntry = &paFunctions[i];
+ if (uRva < pEntry->BeginAddress)
+ iEnd = i;
+ else if (uRva > pEntry->EndAddress)
+ iBegin = i + 1;
+ else
+ return pEntry;
+ }
+ return NULL;
+}
+
+
+/**
+ * Processes an IRET frame.
+ *
+ * @returns IPRT status code.
+ * @param pState The unwind state being worked.
+ * @param fErrCd Non-zero if there is an error code on the stack.
+ */
+static int rtldrPE_UnwindFrame_Amd64_IRet(PRTDBGUNWINDSTATE pState, uint8_t fErrCd)
+{
+ /* POP ErrCd (optional): */
+ Assert(fErrCd <= 1);
+ int rcRet;
+ if (fErrCd)
+ {
+ pState->u.x86.uErrCd = 0;
+ pState->u.x86.Loaded.s.fErrCd = 1;
+ rcRet = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.uErrCd);
+ pState->u.x86.auRegs[X86_GREG_xSP] += 8;
+ }
+ else
+ {
+ pState->u.x86.Loaded.s.fErrCd = 0;
+ rcRet = VINF_SUCCESS;
+ }
+
+ /* Set return type and frame pointer. */
+ pState->enmRetType = RTDBGRETURNTYPE_IRET64;
+ pState->u.x86.FrameAddr.off = pState->u.x86.auRegs[X86_GREG_xSP] - /* pretend rbp is pushed on the stack */ 8;
+ pState->u.x86.FrameAddr.sel = pState->u.x86.auSegs[X86_SREG_SS];
+
+ /* POP RIP: */
+ int rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->uPc);
+ if (RT_FAILURE(rc))
+ rcRet = rc;
+ pState->u.x86.auRegs[X86_GREG_xSP] += 8;
+
+ /* POP CS: */
+ rc = RTDbgUnwindLoadStackU16(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.auSegs[X86_SREG_CS]);
+ if (RT_FAILURE(rc))
+ rcRet = rc;
+ pState->u.x86.auRegs[X86_GREG_xSP] += 8;
+
+ /* POP RFLAGS: */
+ rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.uRFlags);
+ if (RT_FAILURE(rc))
+ rcRet = rc;
+ pState->u.x86.auRegs[X86_GREG_xSP] += 8;
+
+ /* POP RSP, part 1: */
+ uint64_t uNewRsp = (pState->u.x86.auRegs[X86_GREG_xSP] - 8) & ~(uint64_t)15;
+ rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &uNewRsp);
+ if (RT_FAILURE(rc))
+ rcRet = rc;
+ pState->u.x86.auRegs[X86_GREG_xSP] += 8;
+
+ /* POP SS: */
+ rc = RTDbgUnwindLoadStackU16(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.auSegs[X86_SREG_SS]);
+ if (RT_FAILURE(rc))
+ rcRet = rc;
+ pState->u.x86.auRegs[X86_GREG_xSP] += 8;
+
+ /* POP RSP, part 2: */
+ pState->u.x86.auRegs[X86_GREG_xSP] = uNewRsp;
+
+ /* Set loaded indicators: */
+ pState->u.x86.Loaded.s.fRegs |= RT_BIT(X86_GREG_xSP);
+ pState->u.x86.Loaded.s.fSegs |= RT_BIT(X86_SREG_CS) | RT_BIT(X86_SREG_SS);
+ pState->u.x86.Loaded.s.fPc = 1;
+ pState->u.x86.Loaded.s.fFrameAddr = 1;
+ pState->u.x86.Loaded.s.fRFlags = 1;
+ return VINF_SUCCESS;
+}
+
+
+static int rtldrPE_UnwindFrame_Amd64(PRTLDRMODPE pThis, void const *pvBits, PRTDBGUNWINDSTATE pState, uint32_t uRvaPc,
+ PCIMAGE_RUNTIME_FUNCTION_ENTRY pEntry)
+{
+ /* Did we find any unwind information? */
+ if (!pEntry)
+ return VERR_DBG_UNWIND_INFO_NOT_FOUND;
+
+ /*
+ * Do the unwinding.
+ */
+ IMAGE_RUNTIME_FUNCTION_ENTRY ChainedEntry;
+ unsigned iFrameReg = ~0U;
+ unsigned offFrameReg = 0;
+
+ int fInEpilog = -1; /* -1: not-determined-assume-false; 0: false; 1: true. */
+ uint8_t cbEpilog = 0;
+ uint8_t offEpilog = UINT8_MAX;
+ int rcRet = VINF_SUCCESS;
+ int rc;
+ for (unsigned cChainLoops = 0; ; cChainLoops++)
+ {
+ /*
+ * Get the info.
+ */
+ union
+ {
+ uint32_t uRva;
+ uint8_t ab[ RT_OFFSETOF(IMAGE_UNWIND_INFO, aOpcodes)
+ + sizeof(IMAGE_UNWIND_CODE) * 256
+ + sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY)];
+ } uBuf;
+ rc = rtldrPEReadPartByRvaInfoBuf(pThis, pvBits, pEntry->UnwindInfoAddress, sizeof(uBuf), &uBuf);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Check the info.
+ */
+ ASMCompilerBarrier(); /* we're aliasing */
+ PCIMAGE_UNWIND_INFO pInfo = (PCIMAGE_UNWIND_INFO)&uBuf;
+
+ if (pInfo->Version != 1 && pInfo->Version != 2)
+ return VERR_DBG_MALFORMED_UNWIND_INFO;
+
+ /*
+ * Execute the opcodes.
+ */
+ unsigned const cOpcodes = pInfo->CountOfCodes;
+ unsigned iOpcode = 0;
+
+ /*
+ * Check for epilog opcodes at the start and see if we're in an epilog.
+ */
+ if ( pInfo->Version >= 2
+ && iOpcode < cOpcodes
+ && pInfo->aOpcodes[iOpcode].u.UnwindOp == IMAGE_AMD64_UWOP_EPILOG)
+ {
+ if (fInEpilog == -1)
+ {
+ cbEpilog = pInfo->aOpcodes[iOpcode].u.CodeOffset;
+ Assert(cbEpilog > 0);
+
+ uint32_t uRvaEpilog = pEntry->EndAddress - cbEpilog;
+ iOpcode++;
+ if ( (pInfo->aOpcodes[iOpcode - 1].u.OpInfo & 1)
+ && uRvaPc >= uRvaEpilog)
+ {
+ offEpilog = uRvaPc - uRvaEpilog;
+ fInEpilog = 1;
+ }
+ else
+ {
+ fInEpilog = 0;
+ while (iOpcode < cOpcodes && pInfo->aOpcodes[iOpcode].u.UnwindOp == IMAGE_AMD64_UWOP_EPILOG)
+ {
+ uRvaEpilog = pEntry->EndAddress
+ - (pInfo->aOpcodes[iOpcode].u.CodeOffset + (pInfo->aOpcodes[iOpcode].u.OpInfo << 8));
+ iOpcode++;
+ if (uRvaPc - uRvaEpilog < cbEpilog)
+ {
+ offEpilog = uRvaPc - uRvaEpilog;
+ fInEpilog = 1;
+ break;
+ }
+ }
+ }
+ }
+ while (iOpcode < cOpcodes && pInfo->aOpcodes[iOpcode].u.UnwindOp == IMAGE_AMD64_UWOP_EPILOG)
+ iOpcode++;
+ }
+ if (fInEpilog != 1)
+ {
+ /*
+ * Skip opcodes that doesn't apply to us if we're in the prolog.
+ */
+ uint32_t offPc = uRvaPc - pEntry->BeginAddress;
+ if (offPc < pInfo->SizeOfProlog)
+ while (iOpcode < cOpcodes && pInfo->aOpcodes[iOpcode].u.CodeOffset > offPc)
+ iOpcode++;
+
+ /*
+ * Execute the opcodes.
+ */
+ if (pInfo->FrameRegister != 0)
+ {
+ iFrameReg = pInfo->FrameRegister;
+ offFrameReg = pInfo->FrameOffset * 16;
+ }
+ while (iOpcode < cOpcodes)
+ {
+ Assert(pInfo->aOpcodes[iOpcode].u.CodeOffset <= offPc);
+ uint8_t const uOpInfo = pInfo->aOpcodes[iOpcode].u.OpInfo;
+ uint8_t const uUnwindOp = pInfo->aOpcodes[iOpcode].u.UnwindOp;
+ switch (uUnwindOp)
+ {
+ case IMAGE_AMD64_UWOP_PUSH_NONVOL:
+ rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.auRegs[uOpInfo]);
+ if (RT_FAILURE(rc))
+ rcRet = rc;
+ pState->u.x86.Loaded.s.fRegs |= RT_BIT(uOpInfo);
+ pState->u.x86.auRegs[X86_GREG_xSP] += 8;
+ iOpcode++;
+ break;
+
+ case IMAGE_AMD64_UWOP_ALLOC_LARGE:
+ if (uOpInfo == 0)
+ {
+ iOpcode += 2;
+ AssertBreak(iOpcode <= cOpcodes);
+ pState->u.x86.auRegs[X86_GREG_xSP] += pInfo->aOpcodes[iOpcode - 1].FrameOffset * 8;
+ }
+ else
+ {
+ iOpcode += 3;
+ AssertBreak(iOpcode <= cOpcodes);
+ pState->u.x86.auRegs[X86_GREG_xSP] += RT_MAKE_U32(pInfo->aOpcodes[iOpcode - 2].FrameOffset,
+ pInfo->aOpcodes[iOpcode - 1].FrameOffset);
+ }
+ break;
+
+ case IMAGE_AMD64_UWOP_ALLOC_SMALL:
+ AssertBreak(iOpcode <= cOpcodes);
+ pState->u.x86.auRegs[X86_GREG_xSP] += uOpInfo * 8 + 8;
+ iOpcode++;
+ break;
+
+ case IMAGE_AMD64_UWOP_SET_FPREG:
+ iFrameReg = uOpInfo;
+ offFrameReg = pInfo->FrameOffset * 16;
+ pState->u.x86.auRegs[X86_GREG_xSP] = pState->u.x86.auRegs[iFrameReg] - offFrameReg;
+ iOpcode++;
+ break;
+
+ case IMAGE_AMD64_UWOP_SAVE_NONVOL:
+ case IMAGE_AMD64_UWOP_SAVE_NONVOL_FAR:
+ {
+ uint32_t off = 0;
+ iOpcode++;
+ if (iOpcode < cOpcodes)
+ {
+ off = pInfo->aOpcodes[iOpcode].FrameOffset;
+ iOpcode++;
+ if (uUnwindOp == IMAGE_AMD64_UWOP_SAVE_NONVOL_FAR && iOpcode < cOpcodes)
+ {
+ off |= (uint32_t)pInfo->aOpcodes[iOpcode].FrameOffset << 16;
+ iOpcode++;
+ }
+ }
+ off *= 8;
+ rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP] + off,
+ &pState->u.x86.auRegs[uOpInfo]);
+ if (RT_FAILURE(rc))
+ rcRet = rc;
+ pState->u.x86.Loaded.s.fRegs |= RT_BIT(uOpInfo);
+ break;
+ }
+
+ case IMAGE_AMD64_UWOP_SAVE_XMM128:
+ iOpcode += 2;
+ break;
+
+ case IMAGE_AMD64_UWOP_SAVE_XMM128_FAR:
+ iOpcode += 3;
+ break;
+
+ case IMAGE_AMD64_UWOP_PUSH_MACHFRAME:
+ return rtldrPE_UnwindFrame_Amd64_IRet(pState, uOpInfo);
+
+ case IMAGE_AMD64_UWOP_EPILOG:
+ iOpcode += 1;
+ break;
+
+ case IMAGE_AMD64_UWOP_RESERVED_7:
+ AssertFailedReturn(VERR_DBG_MALFORMED_UNWIND_INFO);
+
+ default:
+ AssertMsgFailedReturn(("%u\n", uUnwindOp), VERR_DBG_MALFORMED_UNWIND_INFO);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * We're in the POP sequence of an epilog. The POP sequence should
+ * mirror the PUSH sequence exactly.
+ *
+ * Note! We should only end up here for the initial frame (just consider
+ * RSP, stack allocations, non-volatile register restores, ++).
+ */
+ while (iOpcode < cOpcodes)
+ {
+ uint8_t const uOpInfo = pInfo->aOpcodes[iOpcode].u.OpInfo;
+ uint8_t const uUnwindOp = pInfo->aOpcodes[iOpcode].u.UnwindOp;
+ switch (uUnwindOp)
+ {
+ case IMAGE_AMD64_UWOP_PUSH_NONVOL:
+ pState->u.x86.auRegs[X86_GREG_xSP] += 8;
+ if (offEpilog == 0)
+ {
+ rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP],
+ &pState->u.x86.auRegs[uOpInfo]);
+ if (RT_FAILURE(rc))
+ rcRet = rc;
+ pState->u.x86.Loaded.s.fRegs |= RT_BIT(uOpInfo);
+ }
+ else
+ {
+ /* Decrement offEpilog by estimated POP instruction length. */
+ offEpilog -= 1;
+ if (offEpilog > 0 && uOpInfo >= 8)
+ offEpilog -= 1;
+ }
+ iOpcode++;
+ break;
+
+ case IMAGE_AMD64_UWOP_PUSH_MACHFRAME: /* Must terminate an epilog, so always execute this. */
+ return rtldrPE_UnwindFrame_Amd64_IRet(pState, uOpInfo);
+
+ case IMAGE_AMD64_UWOP_ALLOC_SMALL:
+ case IMAGE_AMD64_UWOP_SET_FPREG:
+ case IMAGE_AMD64_UWOP_EPILOG:
+ iOpcode++;
+ break;
+ case IMAGE_AMD64_UWOP_SAVE_NONVOL:
+ case IMAGE_AMD64_UWOP_SAVE_XMM128:
+ iOpcode += 2;
+ break;
+ case IMAGE_AMD64_UWOP_ALLOC_LARGE:
+ case IMAGE_AMD64_UWOP_SAVE_NONVOL_FAR:
+ case IMAGE_AMD64_UWOP_SAVE_XMM128_FAR:
+ iOpcode += 3;
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%u\n", uUnwindOp), VERR_DBG_MALFORMED_UNWIND_INFO);
+ }
+ }
+ }
+
+ /*
+ * Chained stuff?
+ */
+ if (!(pInfo->Flags & IMAGE_UNW_FLAGS_CHAININFO))
+ break;
+ ChainedEntry = *(PCIMAGE_RUNTIME_FUNCTION_ENTRY)&pInfo->aOpcodes[(cOpcodes + 1) & ~1];
+ pEntry = &ChainedEntry;
+ AssertReturn(cChainLoops < 32, VERR_DBG_MALFORMED_UNWIND_INFO);
+ }
+
+ /*
+ * RSP should now give us the return address, so perform a RET.
+ */
+ pState->enmRetType = RTDBGRETURNTYPE_NEAR64;
+
+ pState->u.x86.FrameAddr.off = pState->u.x86.auRegs[X86_GREG_xSP] - /* pretend rbp is pushed on the stack */ 8;
+ pState->u.x86.FrameAddr.sel = pState->u.x86.auSegs[X86_SREG_SS];
+ pState->u.x86.Loaded.s.fFrameAddr = 1;
+
+ rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->uPc);
+ if (RT_FAILURE(rc))
+ rcRet = rc;
+ pState->u.x86.auRegs[X86_GREG_xSP] += 8;
+ pState->u.x86.Loaded.s.fPc = 1;
+ return rcRet;
+}
+
+
+/**
+ * @interface_method_impl{RTLDROPS,pfnUnwindFrame}
+ */
+static DECLCALLBACK(int) rtldrPE_UnwindFrame(PRTLDRMODINTERNAL pMod, void const *pvBits,
+ uint32_t iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState)
+{
+ PRTLDRMODPE pThis = (PRTLDRMODPE)pMod;
+
+ /*
+ * Translate the segment + offset into an RVA.
+ */
+ RTLDRADDR uRvaPc = off;
+ if (iSeg != UINT32_MAX)
+ {
+ int rc = rtldrPE_SegOffsetToRva(pMod, iSeg, off, &uRvaPc);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Check for unwind info and match the architecture.
+ */
+ if ( pThis->ExceptionDir.Size == 0
+ || pThis->ExceptionDir.VirtualAddress < pThis->cbHeaders)
+ return VERR_DBG_NO_UNWIND_INFO;
+ if (pThis->Core.enmArch != pState->enmArch)
+ return VERR_DBG_UNWIND_INFO_NOT_FOUND;
+
+ /* Currently only AMD64 unwinding is implemented, so head it off right away. */
+ if (pThis->Core.enmArch != RTLDRARCH_AMD64)
+ return VERR_DBG_UNWIND_INFO_NOT_FOUND;
+
+ /*
+ * Make the lookup table available to us.
+ */
+ void const *pvTable = NULL;
+ uint32_t const cbTable = pThis->ExceptionDir.Size;
+ AssertReturn( cbTable < pThis->cbImage
+ && pThis->ExceptionDir.VirtualAddress < pThis->cbImage
+ && pThis->ExceptionDir.VirtualAddress + cbTable <= pThis->cbImage, VERR_INTERNAL_ERROR_3);
+ int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ExceptionDir.VirtualAddress, pThis->ExceptionDir.Size, &pvTable);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * The rest is architecture dependent.
+ *
+ * Note! On windows we try catch access violations so we can safely use
+ * this code on mapped images during assertions.
+ */
+#if defined(_MSC_VER) && defined(IN_RING3) && !defined(IN_SUP_HARDENED_R3)
+ __try
+ {
+#endif
+ switch (pThis->Core.enmArch)
+ {
+ case RTLDRARCH_AMD64:
+ rc = rtldrPE_UnwindFrame_Amd64(pThis, pvBits, pState, uRvaPc,
+ rtldrPE_LookupRuntimeFunctionEntry((PCIMAGE_RUNTIME_FUNCTION_ENTRY)pvTable,
+ cbTable / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY),
+ (uint32_t)uRvaPc));
+ break;
+
+ default:
+ rc = VERR_DBG_UNWIND_INFO_NOT_FOUND;
+ break;
+ }
+#if defined(_MSC_VER) && defined(IN_RING3) && !defined(IN_SUP_HARDENED_R3)
+ }
+ __except (1 /*EXCEPTION_EXECUTE_HANDLER*/)
+ {
+ rc = VERR_DBG_UNWIND_INFO_NOT_FOUND;
+ }
+#endif
+ rtldrPEFreePart(pThis, pvBits, pvTable);
+ return rc;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnDone} */
+static DECLCALLBACK(int) rtldrPEDone(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+ if (pModPe->pvBits)
+ {
+ RTMemFree(pModPe->pvBits);
+ pModPe->pvBits = NULL;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{RTLDROPS,pfnClose} */
+static DECLCALLBACK(int) rtldrPEClose(PRTLDRMODINTERNAL pMod)
+{
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod;
+ if (pModPe->paSections)
+ {
+ RTMemFree(pModPe->paSections);
+ pModPe->paSections = NULL;
+ }
+ if (pModPe->pvBits)
+ {
+ RTMemFree(pModPe->pvBits);
+ pModPe->pvBits = NULL;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Operations for a 32-bit PE module.
+ */
+static const RTLDROPSPE s_rtldrPE32Ops =
+{
+ {
+ "pe32",
+ rtldrPEClose,
+ NULL,
+ rtldrPEDone,
+ rtldrPEEnumSymbols,
+ /* ext */
+ rtldrPEGetImageSize,
+ rtldrPEGetBits,
+ rtldrPERelocate,
+ rtldrPEGetSymbolEx,
+ rtldrPE_QueryForwarderInfo,
+ rtldrPE_EnumDbgInfo,
+ rtldrPE_EnumSegments,
+ rtldrPE_LinkAddressToSegOffset,
+ rtldrPE_LinkAddressToRva,
+ rtldrPE_SegOffsetToRva,
+ rtldrPE_RvaToSegOffset,
+ NULL,
+ rtldrPE_QueryProp,
+ rtldrPE_VerifySignature,
+ rtldrPE_HashImage,
+ NULL /*pfnUnwindFrame*/,
+ 42
+ },
+ rtldrPEResolveImports32,
+ 42
+};
+
+
+/**
+ * Operations for a 64-bit PE module.
+ */
+static const RTLDROPSPE s_rtldrPE64Ops =
+{
+ {
+ "pe64",
+ rtldrPEClose,
+ NULL,
+ rtldrPEDone,
+ rtldrPEEnumSymbols,
+ /* ext */
+ rtldrPEGetImageSize,
+ rtldrPEGetBits,
+ rtldrPERelocate,
+ rtldrPEGetSymbolEx,
+ rtldrPE_QueryForwarderInfo,
+ rtldrPE_EnumDbgInfo,
+ rtldrPE_EnumSegments,
+ rtldrPE_LinkAddressToSegOffset,
+ rtldrPE_LinkAddressToRva,
+ rtldrPE_SegOffsetToRva,
+ rtldrPE_RvaToSegOffset,
+ NULL,
+ rtldrPE_QueryProp,
+ rtldrPE_VerifySignature,
+ rtldrPE_HashImage,
+ rtldrPE_UnwindFrame,
+ 42
+ },
+ rtldrPEResolveImports64,
+ 42
+};
+
+
+/**
+ * Converts the optional header from 32 bit to 64 bit.
+ * This is a rather simple task, if you start from the right end.
+ *
+ * @param pOptHdr On input this is a PIMAGE_OPTIONAL_HEADER32.
+ * On output this will be a PIMAGE_OPTIONAL_HEADER64.
+ */
+static void rtldrPEConvert32BitOptionalHeaderTo64Bit(PIMAGE_OPTIONAL_HEADER64 pOptHdr)
+{
+ /*
+ * volatile everywhere! Trying to prevent the compiler being a smarta$$ and reorder stuff.
+ */
+ IMAGE_OPTIONAL_HEADER32 volatile *pOptHdr32 = (IMAGE_OPTIONAL_HEADER32 volatile *)pOptHdr;
+ IMAGE_OPTIONAL_HEADER64 volatile *pOptHdr64 = pOptHdr;
+
+ /* from LoaderFlags and out the difference is 4 * 32-bits. */
+ Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, LoaderFlags) + 16 == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, LoaderFlags));
+ Assert( RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]) + 16
+ == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]));
+ uint32_t volatile *pu32Dst = (uint32_t *)&pOptHdr64->DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] - 1;
+ const uint32_t volatile *pu32Src = (uint32_t *)&pOptHdr32->DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] - 1;
+ const uint32_t volatile *pu32SrcLast = (uint32_t *)&pOptHdr32->LoaderFlags;
+ while (pu32Src >= pu32SrcLast)
+ *pu32Dst-- = *pu32Src--;
+
+ /* the previous 4 fields are 32/64 and needs special attention. */
+ pOptHdr64->SizeOfHeapCommit = pOptHdr32->SizeOfHeapCommit;
+ pOptHdr64->SizeOfHeapReserve = pOptHdr32->SizeOfHeapReserve;
+ pOptHdr64->SizeOfStackCommit = pOptHdr32->SizeOfStackCommit;
+ uint32_t u32SizeOfStackReserve = pOptHdr32->SizeOfStackReserve;
+ pOptHdr64->SizeOfStackReserve = u32SizeOfStackReserve;
+
+ /* The rest matches except for BaseOfData which has been merged into ImageBase in the 64-bit version..
+ * Thus, ImageBase needs some special treatment. It will probably work fine assigning one to the
+ * other since this is all declared volatile, but taking now chances, we'll use a temp variable.
+ */
+ Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, SizeOfStackReserve) == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, SizeOfStackReserve));
+ Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, BaseOfData) == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, ImageBase));
+ Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, SectionAlignment) == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, SectionAlignment));
+ uint32_t u32ImageBase = pOptHdr32->ImageBase;
+ pOptHdr64->ImageBase = u32ImageBase;
+}
+
+
+/**
+ * Converts the load config directory from 32 bit to 64 bit.
+ * This is a rather simple task, if you start from the right end.
+ *
+ * @param pLoadCfg On input this is a PIMAGE_LOAD_CONFIG_DIRECTORY32.
+ * On output this will be a PIMAGE_LOAD_CONFIG_DIRECTORY64.
+ */
+static void rtldrPEConvert32BitLoadConfigTo64Bit(PIMAGE_LOAD_CONFIG_DIRECTORY64 pLoadCfg)
+{
+ /*
+ * volatile everywhere! Trying to prevent the compiler being a smarta$$ and reorder stuff.
+ */
+ IMAGE_LOAD_CONFIG_DIRECTORY32_V13 volatile *pLoadCfg32 = (IMAGE_LOAD_CONFIG_DIRECTORY32_V13 volatile *)pLoadCfg;
+ IMAGE_LOAD_CONFIG_DIRECTORY64_V13 volatile *pLoadCfg64 = pLoadCfg;
+
+ pLoadCfg64->CastGuardOsDeterminedFailureMode = pLoadCfg32->CastGuardOsDeterminedFailureMode;
+ pLoadCfg64->GuardXFGTableDispatchFunctionPointer = pLoadCfg32->GuardXFGTableDispatchFunctionPointer;
+ pLoadCfg64->GuardXFGDispatchFunctionPointer = pLoadCfg32->GuardXFGDispatchFunctionPointer;
+ pLoadCfg64->GuardXFGCheckFunctionPointer = pLoadCfg32->GuardXFGCheckFunctionPointer;
+ pLoadCfg64->GuardEHContinuationCount = pLoadCfg32->GuardEHContinuationCount;
+ pLoadCfg64->GuardEHContinuationTable = pLoadCfg32->GuardEHContinuationTable;
+ pLoadCfg64->VolatileMetadataPointer = pLoadCfg32->VolatileMetadataPointer;
+ pLoadCfg64->EnclaveConfigurationPointer = pLoadCfg32->EnclaveConfigurationPointer;
+ pLoadCfg64->Reserved3 = pLoadCfg32->Reserved3;
+ pLoadCfg64->HotPatchTableOffset = pLoadCfg32->HotPatchTableOffset;
+ pLoadCfg64->GuardRFVerifyStackPointerFunctionPointer = pLoadCfg32->GuardRFVerifyStackPointerFunctionPointer;
+ pLoadCfg64->Reserved2 = pLoadCfg32->Reserved2;
+ pLoadCfg64->DynamicValueRelocTableSection = pLoadCfg32->DynamicValueRelocTableSection;
+ pLoadCfg64->DynamicValueRelocTableOffset = pLoadCfg32->DynamicValueRelocTableOffset;
+ pLoadCfg64->GuardRFFailureRoutineFunctionPointer = pLoadCfg32->GuardRFFailureRoutineFunctionPointer;
+ pLoadCfg64->GuardRFFailureRoutine = pLoadCfg32->GuardRFFailureRoutine;
+ pLoadCfg64->CHPEMetadataPointer = pLoadCfg32->CHPEMetadataPointer;
+ pLoadCfg64->DynamicValueRelocTable = pLoadCfg32->DynamicValueRelocTable;
+ pLoadCfg64->GuardLongJumpTargetCount = pLoadCfg32->GuardLongJumpTargetCount;
+ pLoadCfg64->GuardLongJumpTargetTable = pLoadCfg32->GuardLongJumpTargetTable;
+ pLoadCfg64->GuardAddressTakenIatEntryCount = pLoadCfg32->GuardAddressTakenIatEntryCount;
+ pLoadCfg64->GuardAddressTakenIatEntryTable = pLoadCfg32->GuardAddressTakenIatEntryTable;
+ pLoadCfg64->CodeIntegrity.Reserved = pLoadCfg32->CodeIntegrity.Reserved;
+ pLoadCfg64->CodeIntegrity.CatalogOffset = pLoadCfg32->CodeIntegrity.CatalogOffset;
+ pLoadCfg64->CodeIntegrity.Catalog = pLoadCfg32->CodeIntegrity.Catalog;
+ pLoadCfg64->CodeIntegrity.Flags = pLoadCfg32->CodeIntegrity.Flags;
+ pLoadCfg64->GuardFlags = pLoadCfg32->GuardFlags;
+ pLoadCfg64->GuardCFFunctionCount = pLoadCfg32->GuardCFFunctionCount;
+ pLoadCfg64->GuardCFFunctionTable = pLoadCfg32->GuardCFFunctionTable;
+ pLoadCfg64->GuardCFDispatchFunctionPointer = pLoadCfg32->GuardCFDispatchFunctionPointer;
+ pLoadCfg64->GuardCFCCheckFunctionPointer = pLoadCfg32->GuardCFCCheckFunctionPointer;
+ pLoadCfg64->SEHandlerCount = pLoadCfg32->SEHandlerCount;
+ pLoadCfg64->SEHandlerTable = pLoadCfg32->SEHandlerTable;
+ pLoadCfg64->SecurityCookie = pLoadCfg32->SecurityCookie;
+ pLoadCfg64->EditList = pLoadCfg32->EditList;
+ pLoadCfg64->DependentLoadFlags = pLoadCfg32->DependentLoadFlags;
+ pLoadCfg64->CSDVersion = pLoadCfg32->CSDVersion;
+ pLoadCfg64->ProcessHeapFlags = pLoadCfg32->ProcessHeapFlags; /* switched place with ProcessAffinityMask, but we're more than 16 byte off by now so it doesn't matter. */
+ pLoadCfg64->ProcessAffinityMask = pLoadCfg32->ProcessAffinityMask;
+ pLoadCfg64->VirtualMemoryThreshold = pLoadCfg32->VirtualMemoryThreshold;
+ pLoadCfg64->MaximumAllocationSize = pLoadCfg32->MaximumAllocationSize;
+ pLoadCfg64->LockPrefixTable = pLoadCfg32->LockPrefixTable;
+ pLoadCfg64->DeCommitTotalFreeThreshold = pLoadCfg32->DeCommitTotalFreeThreshold;
+ uint32_t u32DeCommitFreeBlockThreshold = pLoadCfg32->DeCommitFreeBlockThreshold;
+ pLoadCfg64->DeCommitFreeBlockThreshold = u32DeCommitFreeBlockThreshold;
+ /* the rest is equal. */
+ Assert( RT_UOFFSETOF(IMAGE_LOAD_CONFIG_DIRECTORY32, DeCommitFreeBlockThreshold)
+ == RT_UOFFSETOF(IMAGE_LOAD_CONFIG_DIRECTORY64, DeCommitFreeBlockThreshold));
+}
+
+
+/**
+ * Translate the PE/COFF machine name to a string.
+ *
+ * @returns Name string (read-only).
+ * @param uMachine The PE/COFF machine.
+ */
+static const char *rtldrPEGetArchName(uint16_t uMachine)
+{
+ switch (uMachine)
+ {
+ case IMAGE_FILE_MACHINE_I386: return "X86_32";
+ case IMAGE_FILE_MACHINE_AMD64: return "AMD64";
+
+ case IMAGE_FILE_MACHINE_UNKNOWN: return "UNKNOWN";
+ case IMAGE_FILE_MACHINE_AM33: return "AM33";
+ case IMAGE_FILE_MACHINE_ARM: return "ARM";
+ case IMAGE_FILE_MACHINE_THUMB: return "THUMB";
+ case IMAGE_FILE_MACHINE_ARMNT: return "ARMNT";
+ case IMAGE_FILE_MACHINE_ARM64: return "ARM64";
+ case IMAGE_FILE_MACHINE_EBC: return "EBC";
+ case IMAGE_FILE_MACHINE_IA64: return "IA64";
+ case IMAGE_FILE_MACHINE_M32R: return "M32R";
+ case IMAGE_FILE_MACHINE_MIPS16: return "MIPS16";
+ case IMAGE_FILE_MACHINE_MIPSFPU: return "MIPSFPU";
+ case IMAGE_FILE_MACHINE_MIPSFPU16: return "MIPSFPU16";
+ case IMAGE_FILE_MACHINE_WCEMIPSV2: return "WCEMIPSV2";
+ case IMAGE_FILE_MACHINE_POWERPC: return "POWERPC";
+ case IMAGE_FILE_MACHINE_POWERPCFP: return "POWERPCFP";
+ case IMAGE_FILE_MACHINE_R4000: return "R4000";
+ case IMAGE_FILE_MACHINE_SH3: return "SH3";
+ case IMAGE_FILE_MACHINE_SH3DSP: return "SH3DSP";
+ case IMAGE_FILE_MACHINE_SH4: return "SH4";
+ case IMAGE_FILE_MACHINE_SH5: return "SH5";
+ default: return "UnknownMachine";
+ }
+}
+
+
+/**
+ * Validates the file header.
+ *
+ * @returns iprt status code.
+ * @param pFileHdr Pointer to the file header that needs validating.
+ * @param fFlags Valid RTLDR_O_XXX combination.
+ * @param pszLogName The log name to prefix the errors with.
+ * @param penmArch Where to store the CPU architecture.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtldrPEValidateFileHeader(PIMAGE_FILE_HEADER pFileHdr, uint32_t fFlags, const char *pszLogName,
+ PRTLDRARCH penmArch, PRTERRINFO pErrInfo)
+{
+ RT_NOREF_PV(pszLogName);
+
+ size_t cbOptionalHeader;
+ switch (pFileHdr->Machine)
+ {
+ case IMAGE_FILE_MACHINE_I386:
+ cbOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER32);
+ *penmArch = RTLDRARCH_X86_32;
+ break;
+ case IMAGE_FILE_MACHINE_AMD64:
+ cbOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER64);
+ *penmArch = RTLDRARCH_AMD64;
+ break;
+
+ default:
+ Log(("rtldrPEOpen: %s: Unsupported Machine=%#x\n", pszLogName, pFileHdr->Machine));
+ *penmArch = RTLDRARCH_INVALID;
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Unsupported Machine=%#x", pFileHdr->Machine);
+ }
+ if (pFileHdr->SizeOfOptionalHeader != cbOptionalHeader)
+ {
+ Log(("rtldrPEOpen: %s: SizeOfOptionalHeader=%#x expected %#x\n", pszLogName, pFileHdr->SizeOfOptionalHeader, cbOptionalHeader));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfOptionalHeader=%#x expected %#x",
+ pFileHdr->SizeOfOptionalHeader, cbOptionalHeader);
+ }
+ /* This restriction needs to be implemented elsewhere. */
+ if ( (pFileHdr->Characteristics & IMAGE_FILE_RELOCS_STRIPPED)
+ && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)))
+ {
+ Log(("rtldrPEOpen: %s: IMAGE_FILE_RELOCS_STRIPPED\n", pszLogName));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "IMAGE_FILE_RELOCS_STRIPPED");
+ }
+ if (pFileHdr->NumberOfSections > 42)
+ {
+ Log(("rtldrPEOpen: %s: NumberOfSections=%d - our limit is 42, please raise it if the binary makes sense.(!!!)\n",
+ pszLogName, pFileHdr->NumberOfSections));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "NumberOfSections=%d, implementation max is 42", pFileHdr->NumberOfSections);
+ }
+ if (pFileHdr->NumberOfSections < 1)
+ {
+ Log(("rtldrPEOpen: %s: NumberOfSections=%d - we can't have an image without sections (!!!)\n",
+ pszLogName, pFileHdr->NumberOfSections));
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_BAD_EXE_FORMAT, "Image has no sections");
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates the optional header (64/32-bit)
+ *
+ * @returns iprt status code.
+ * @param pOptHdr Pointer to the optional header which needs validation.
+ * @param pszLogName The log name to prefix the errors with.
+ * @param offNtHdrs The offset of the NT headers from the start of the file.
+ * @param pFileHdr Pointer to the file header (valid).
+ * @param cbRawImage The raw image size.
+ * @param fFlags Loader flags, RTLDR_O_XXX.
+ * @param pErrInfo Where to return additional error information.
+ */
+static int rtldrPEValidateOptionalHeader(const IMAGE_OPTIONAL_HEADER64 *pOptHdr, const char *pszLogName, RTFOFF offNtHdrs,
+ const IMAGE_FILE_HEADER *pFileHdr, uint64_t cbRawImage, uint32_t fFlags, PRTERRINFO pErrInfo)
+{
+ RT_NOREF_PV(pszLogName);
+
+ const uint16_t CorrectMagic = pFileHdr->SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER32)
+ ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC;
+ if (pOptHdr->Magic != CorrectMagic)
+ {
+ Log(("rtldrPEOpen: %s: Magic=%#x - expected %#x!!!\n", pszLogName, pOptHdr->Magic, CorrectMagic));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Magic=%#x, expected %#x", pOptHdr->Magic, CorrectMagic);
+ }
+ const uint32_t cbImage = pOptHdr->SizeOfImage;
+ if (cbImage > _1G)
+ {
+ Log(("rtldrPEOpen: %s: SizeOfImage=%#x - Our limit is 1GB (%#x)!!!\n", pszLogName, cbImage, _1G));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfImage=%#x - Our limit is 1GB (%#x)", cbImage, _1G);
+ }
+ const uint32_t cbMinImageSize = pFileHdr->SizeOfOptionalHeader + sizeof(*pFileHdr) + 4 + (uint32_t)offNtHdrs;
+ if (cbImage < cbMinImageSize)
+ {
+ Log(("rtldrPEOpen: %s: SizeOfImage=%#x to small, minimum %#x!!!\n", pszLogName, cbImage, cbMinImageSize));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfImage=%#x to small, minimum %#x", cbImage, cbMinImageSize);
+ }
+ if (pOptHdr->AddressOfEntryPoint >= cbImage)
+ {
+ Log(("rtldrPEOpen: %s: AddressOfEntryPoint=%#x - beyond image size (%#x)!!!\n",
+ pszLogName, pOptHdr->AddressOfEntryPoint, cbImage));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "AddressOfEntryPoint=%#x - beyond image size (%#x)", pOptHdr->AddressOfEntryPoint, cbImage);
+ }
+ if (pOptHdr->BaseOfCode >= cbImage)
+ {
+ Log(("rtldrPEOpen: %s: BaseOfCode=%#x - beyond image size (%#x)!!!\n",
+ pszLogName, pOptHdr->BaseOfCode, cbImage));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "BaseOfCode=%#x - beyond image size (%#x)", pOptHdr->BaseOfCode, cbImage);
+ }
+#if 0/* only in 32-bit header */
+ if (pOptHdr->BaseOfData >= cbImage)
+ {
+ Log(("rtldrPEOpen: %s: BaseOfData=%#x - beyond image size (%#x)!!!\n",
+ pszLogName, pOptHdr->BaseOfData, cbImage));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "BaseOfData=%#x - beyond image size (%#x)", pOptHdr->BaseOfData, cbImage);
+ }
+#endif
+ if (!RT_IS_POWER_OF_TWO(pOptHdr->SectionAlignment))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "SectionAlignment=%#x - not a power of two", pOptHdr->SectionAlignment);
+ if (pOptHdr->SectionAlignment < 16 || pOptHdr->SectionAlignment > _128K)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "SectionAlignment=%#x - unsupported value, not between 16 and 128KB", pOptHdr->SectionAlignment);
+ if (pOptHdr->SizeOfHeaders >= cbImage)
+ {
+ Log(("rtldrPEOpen: %s: SizeOfHeaders=%#x - beyond image size (%#x)!!!\n",
+ pszLogName, pOptHdr->SizeOfHeaders, cbImage));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT,
+ "SizeOfHeaders=%#x - beyond image size (%#x)", pOptHdr->SizeOfHeaders, cbImage);
+ }
+ /* don't know how to do the checksum, so ignore it. */
+ if (pOptHdr->Subsystem == IMAGE_SUBSYSTEM_UNKNOWN)
+ {
+ Log(("rtldrPEOpen: %s: Subsystem=%#x (unknown)!!!\n", pszLogName, pOptHdr->Subsystem));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Subsystem=%#x (unknown)", pOptHdr->Subsystem);
+ }
+ if (pOptHdr->SizeOfHeaders < cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER))
+ {
+ Log(("rtldrPEOpen: %s: SizeOfHeaders=%#x - cbMinImageSize %#x + sections %#x = %#llx!!!\n",
+ pszLogName, pOptHdr->SizeOfHeaders,
+ cbMinImageSize, pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER),
+ cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER)));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfHeaders=%#x - cbMinImageSize %#x + sections %#x = %#llx",
+ pOptHdr->SizeOfHeaders, cbMinImageSize,
+ pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER),
+ cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER) );
+ }
+ if (pOptHdr->SizeOfStackReserve < pOptHdr->SizeOfStackCommit)
+ {
+ Log(("rtldrPEOpen: %s: SizeOfStackReserve %#x < SizeOfStackCommit %#x!!!\n",
+ pszLogName, pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfStackReserve %#x < SizeOfStackCommit %#x",
+ pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit);
+ }
+ if (pOptHdr->SizeOfHeapReserve < pOptHdr->SizeOfHeapCommit)
+ {
+ Log(("rtldrPEOpen: %s: SizeOfStackReserve %#x < SizeOfStackCommit %#x!!!\n",
+ pszLogName, pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfStackReserve %#x < SizeOfStackCommit %#x\n",
+ pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit);
+ }
+
+ /* DataDirectory */
+ if (pOptHdr->NumberOfRvaAndSizes != RT_ELEMENTS(pOptHdr->DataDirectory))
+ {
+ Log(("rtldrPEOpen: %s: NumberOfRvaAndSizes=%d!!!\n", pszLogName, pOptHdr->NumberOfRvaAndSizes));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "NumberOfRvaAndSizes=%d, expected %d",
+ pOptHdr->NumberOfRvaAndSizes, RT_ELEMENTS(pOptHdr->DataDirectory));
+ }
+ for (unsigned i = 0; i < RT_ELEMENTS(pOptHdr->DataDirectory); i++)
+ {
+ IMAGE_DATA_DIRECTORY const *pDir = &pOptHdr->DataDirectory[i];
+ if (!pDir->Size)
+ continue;
+ size_t cb = cbImage;
+ switch (i)
+ {
+ case IMAGE_DIRECTORY_ENTRY_EXPORT: // 0
+ case IMAGE_DIRECTORY_ENTRY_IMPORT: // 1
+ case IMAGE_DIRECTORY_ENTRY_RESOURCE: // 2
+ case IMAGE_DIRECTORY_ENTRY_EXCEPTION: // 3
+ case IMAGE_DIRECTORY_ENTRY_BASERELOC: // 5
+ case IMAGE_DIRECTORY_ENTRY_DEBUG: // 6
+ case IMAGE_DIRECTORY_ENTRY_COPYRIGHT: // 7
+ case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: // 11
+ case IMAGE_DIRECTORY_ENTRY_IAT: // 12 /* Import Address Table */
+ break;
+ case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: // 10 - need to check for lock prefixes.
+ /* Delay inspection after section table is validated. */
+ break;
+
+ case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: // 13
+ if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
+ break;
+ Log(("rtldrPEOpen: %s: dir no. %d (DELAY_IMPORT) VirtualAddress=%#x Size=%#x is not supported!!!\n",
+ pszLogName, i, pDir->VirtualAddress, pDir->Size));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_DELAY_IMPORT,
+ "DELAY_IMPORT VirtualAddress=%#x Size=%#x: not supported", pDir->VirtualAddress, pDir->Size);
+
+ case IMAGE_DIRECTORY_ENTRY_SECURITY: // 4
+ /* The VirtualAddress is a PointerToRawData. */
+ cb = (size_t)cbRawImage; Assert((uint64_t)cb == cbRawImage);
+ Log(("rtldrPEOpen: %s: dir no. %d (SECURITY) VirtualAddress=%#x Size=%#x\n", pszLogName, i, pDir->VirtualAddress, pDir->Size));
+ if (pDir->Size < sizeof(WIN_CERTIFICATE))
+ {
+ Log(("rtldrPEOpen: %s: Security directory #%u is too small: %#x bytes\n", pszLogName, i, pDir->Size));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
+ "Security directory is too small: %#x bytes", pDir->Size);
+ }
+ if (pDir->Size >= RTLDRMODPE_MAX_SECURITY_DIR_SIZE)
+ {
+ Log(("rtldrPEOpen: %s: Security directory #%u is too large: %#x bytes\n", pszLogName, i, pDir->Size));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
+ "Security directory is too large: %#x bytes", pDir->Size);
+ }
+ if (pDir->VirtualAddress & 7)
+ {
+ Log(("rtldrPEOpen: %s: Security directory #%u is misaligned: %#x\n", pszLogName, i, pDir->VirtualAddress));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
+ "Security directory is misaligned: %#x", pDir->VirtualAddress);
+ }
+ /* When using the in-memory reader with a debugger, we may get
+ into trouble here since we might not have access to the whole
+ physical file. So skip the tests below. Makes VBoxGuest.sys
+ load and check out just fine, for instance. */
+ if (fFlags & RTLDR_O_FOR_DEBUG)
+ continue;
+ break;
+
+ case IMAGE_DIRECTORY_ENTRY_GLOBALPTR: // 8 /* (MIPS GP) */
+ Log(("rtldrPEOpen: %s: dir no. %d (GLOBALPTR) VirtualAddress=%#x Size=%#x is not supported!!!\n",
+ pszLogName, i, pDir->VirtualAddress, pDir->Size));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_GLOBALPTR, "GLOBALPTR VirtualAddress=%#x Size=%#x: not supported",
+ pDir->VirtualAddress, pDir->Size);
+
+ case IMAGE_DIRECTORY_ENTRY_TLS: // 9
+ if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
+ break;
+ Log(("rtldrPEOpen: %s: dir no. %d (TLS) VirtualAddress=%#x Size=%#x is not supported!!!\n",
+ pszLogName, i, pDir->VirtualAddress, pDir->Size));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_TLS, "TLS VirtualAddress=%#x Size=%#x: not supported",
+ pDir->VirtualAddress, pDir->Size);
+
+ case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:// 14
+ if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
+ break;
+ Log(("rtldrPEOpen: %s: dir no. %d (COM_DESCRIPTOR) VirtualAddress=%#x Size=%#x is not supported!!!\n",
+ pszLogName, i, pDir->VirtualAddress, pDir->Size));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_COM_DESCRIPTOR,
+ "COM_DESCRIPTOR VirtualAddress=%#x Size=%#x: not supported",
+ pDir->VirtualAddress, pDir->Size);
+
+ default:
+ Log(("rtldrPEOpen: %s: dir no. %d VirtualAddress=%#x Size=%#x is not supported!!!\n",
+ pszLogName, i, pDir->VirtualAddress, pDir->Size));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "dir no. %d VirtualAddress=%#x Size=%#x is not supported",
+ i, pDir->VirtualAddress, pDir->Size);
+ }
+ if (pDir->VirtualAddress >= cb)
+ {
+ Log(("rtldrPEOpen: %s: dir no. %d VirtualAddress=%#x is invalid (limit %#x)!!!\n",
+ pszLogName, i, pDir->VirtualAddress, cb));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "dir no. %d VirtualAddress=%#x is invalid (limit %#x)",
+ i, pDir->VirtualAddress, cb);
+ }
+ if (pDir->Size > cb - pDir->VirtualAddress)
+ {
+ Log(("rtldrPEOpen: %s: dir no. %d Size=%#x is invalid (rva=%#x, limit=%#x)!!!\n",
+ pszLogName, i, pDir->Size, pDir->VirtualAddress, cb));
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "dir no. %d Size=%#x is invalid (rva=%#x, limit=%#x)",
+ i, pDir->Size, pDir->VirtualAddress, cb);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates and touch up the section headers.
+ *
+ * The touching up is restricted to setting the VirtualSize field for old-style
+ * linkers that sets it to zero.
+ *
+ * @returns iprt status code.
+ * @param paSections Pointer to the array of sections that is to be validated.
+ * @param cSections Number of sections in that array.
+ * @param pszLogName The log name to prefix the errors with.
+ * @param pOptHdr Pointer to the optional header (valid).
+ * @param cbRawImage The raw image size.
+ * @param fFlags Loader flags, RTLDR_O_XXX.
+ * @param fNoCode Verify that the image contains no code.
+ */
+static int rtldrPEValidateAndTouchUpSectionHeaders(IMAGE_SECTION_HEADER *paSections, unsigned cSections, const char *pszLogName,
+ const IMAGE_OPTIONAL_HEADER64 *pOptHdr, uint64_t cbRawImage, uint32_t fFlags,
+ bool fNoCode)
+{
+ RT_NOREF_PV(pszLogName);
+
+ /*
+ * Do a quick pass to detect linker setting VirtualSize to zero.
+ */
+ bool fFixupVirtualSize = true;
+ IMAGE_SECTION_HEADER *pSH = &paSections[0];
+ for (unsigned cSHdrsLeft = cSections; cSHdrsLeft > 0; cSHdrsLeft--, pSH++)
+ if ( pSH->Misc.VirtualSize != 0
+ && !(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD))
+ {
+ fFixupVirtualSize = false;
+ break;
+ }
+
+ /*
+ * Actual pass.
+ */
+ const uint32_t cbImage = pOptHdr->SizeOfImage;
+ uint32_t uRvaPrev = pOptHdr->SizeOfHeaders;
+ pSH = &paSections[0];
+ Log3(("RTLdrPE: Section Headers:\n"));
+ for (unsigned cSHdrsLeft = cSections; cSHdrsLeft > 0; cSHdrsLeft--, pSH++)
+ {
+ const unsigned iSH = (unsigned)(pSH - &paSections[0]); NOREF(iSH);
+ Log3(("RTLdrPE: #%d '%-8.8s' Characteristics: %08RX32\n"
+ "RTLdrPE: VirtAddr: %08RX32 VirtSize: %08RX32\n"
+ "RTLdrPE: FileOff: %08RX32 FileSize: %08RX32\n"
+ "RTLdrPE: RelocOff: %08RX32 #Relocs: %08RX32\n"
+ "RTLdrPE: LineOff: %08RX32 #Lines: %08RX32\n",
+ iSH, pSH->Name, pSH->Characteristics,
+ pSH->VirtualAddress, pSH->Misc.VirtualSize,
+ pSH->PointerToRawData, pSH->SizeOfRawData,
+ pSH->PointerToRelocations, pSH->NumberOfRelocations,
+ pSH->PointerToLinenumbers, pSH->NumberOfLinenumbers));
+
+ AssertCompile(IMAGE_SCN_MEM_16BIT == IMAGE_SCN_MEM_PURGEABLE);
+ if ( ( pSH->Characteristics & (IMAGE_SCN_MEM_PURGEABLE | IMAGE_SCN_MEM_PRELOAD | IMAGE_SCN_MEM_FARDATA) )
+ && !(fFlags & RTLDR_O_FOR_DEBUG)) /* purgable/16-bit seen on w2ksp0 hal.dll, ignore the bunch. */
+ {
+ Log(("rtldrPEOpen: %s: Unsupported section flag(s) %#x section #%d '%.*s'!!!\n",
+ pszLogName, pSH->Characteristics, iSH, sizeof(pSH->Name), pSH->Name));
+ return VERR_BAD_EXE_FORMAT;
+ }
+
+ if ( pSH->PointerToRawData > cbRawImage /// @todo pSH->PointerToRawData >= cbRawImage ?
+ || pSH->SizeOfRawData > cbRawImage
+ || pSH->PointerToRawData + pSH->SizeOfRawData > cbRawImage)
+ {
+ Log(("rtldrPEOpen: %s: PointerToRawData=%#x SizeOfRawData=%#x - beyond end of file (%#llx) - section #%d '%.*s'!!!\n",
+ pszLogName, pSH->PointerToRawData, pSH->SizeOfRawData, cbRawImage,
+ iSH, sizeof(pSH->Name), pSH->Name));
+ return VERR_BAD_EXE_FORMAT;
+ }
+
+ if (pSH->PointerToRawData & (pOptHdr->FileAlignment - 1)) //ASSUMES power of 2 alignment.
+ {
+ Log(("rtldrPEOpen: %s: PointerToRawData=%#x misaligned (%#x) - section #%d '%.*s'!!!\n",
+ pszLogName, pSH->PointerToRawData, pOptHdr->FileAlignment, iSH, sizeof(pSH->Name), pSH->Name));
+ return VERR_BAD_EXE_FORMAT;
+ }
+
+ if (!(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD)) /* binutils uses this for '.stab' even if it's reserved/obsoleted by MS. */
+ {
+ /* Calc VirtualSize if necessary. This is for internal reasons. */
+ if ( pSH->Misc.VirtualSize == 0
+ && fFixupVirtualSize)
+ {
+ pSH->Misc.VirtualSize = cbImage - RT_MIN(pSH->VirtualAddress, cbImage);
+ for (uint32_t i = 1; i < cSHdrsLeft; i++)
+ if ( !(pSH[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD)
+ && pSH[i].VirtualAddress >= pSH->VirtualAddress)
+ {
+ pSH->Misc.VirtualSize = RT_MIN(pSH[i].VirtualAddress - pSH->VirtualAddress, pSH->Misc.VirtualSize);
+ break;
+ }
+ }
+
+ if (pSH->Misc.VirtualSize > 0)
+ {
+ if (pSH->VirtualAddress < uRvaPrev)
+ {
+ Log(("rtldrPEOpen: %s: Overlaps previous section or sections aren't in ascending order, VirtualAddress=%#x uRvaPrev=%#x - section #%d '%.*s'!!!\n",
+ pszLogName, pSH->VirtualAddress, uRvaPrev, iSH, sizeof(pSH->Name), pSH->Name));
+ return VERR_BAD_EXE_FORMAT;
+ }
+ if (pSH->VirtualAddress > cbImage)
+ {
+ Log(("rtldrPEOpen: %s: VirtualAddress=%#x - beyond image size (%#x) - section #%d '%.*s'!!!\n",
+ pszLogName, pSH->VirtualAddress, cbImage, iSH, sizeof(pSH->Name), pSH->Name));
+ return VERR_BAD_EXE_FORMAT;
+ }
+
+ if (pSH->VirtualAddress & (pOptHdr->SectionAlignment - 1)) //ASSUMES power of 2 alignment.
+ {
+ Log(("rtldrPEOpen: %s: VirtualAddress=%#x misaligned (%#x) - section #%d '%.*s'!!!\n",
+ pszLogName, pSH->VirtualAddress, pOptHdr->SectionAlignment, iSH, sizeof(pSH->Name), pSH->Name));
+ return VERR_BAD_EXE_FORMAT;
+ }
+
+#ifdef PE_FILE_OFFSET_EQUALS_RVA
+ /* Our loader code assume rva matches the file offset. */
+ if ( pSH->SizeOfRawData
+ && pSH->PointerToRawData != pSH->VirtualAddress)
+ {
+ Log(("rtldrPEOpen: %s: ASSUMPTION FAILED: file offset %#x != RVA %#x - section #%d '%.*s'!!!\n",
+ pszLogName, pSH->PointerToRawData, pSH->VirtualAddress, iSH, sizeof(pSH->Name), pSH->Name));
+ return VERR_BAD_EXE_FORMAT;
+ }
+#endif
+
+ uRvaPrev = pSH->VirtualAddress + pSH->Misc.VirtualSize;
+ }
+ }
+
+ /* ignore the relocations and linenumbers. */
+ }
+
+ /*
+ * Do a separate run if we need to validate the no-code claim from the
+ * optional header.
+ */
+ if (fNoCode)
+ {
+ pSH = &paSections[0];
+ for (unsigned cSHdrsLeft = cSections; cSHdrsLeft > 0; cSHdrsLeft--, pSH++)
+ if (pSH->Characteristics & (IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE))
+ return VERR_LDR_ARCH_MISMATCH;
+ }
+
+
+ /** @todo r=bird: more sanity checks! */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads image data by RVA using the section headers.
+ *
+ * @returns iprt status code.
+ * @param pModPe The PE module instance.
+ * @param pvBuf Where to store the bits.
+ * @param cb Number of bytes to tread.
+ * @param RVA Where to read from.
+ */
+static int rtldrPEReadRVA(PRTLDRMODPE pModPe, void *pvBuf, uint32_t cb, uint32_t RVA)
+{
+ const IMAGE_SECTION_HEADER *pSH = pModPe->paSections;
+ PRTLDRREADER pReader = pModPe->Core.pReader;
+ uint32_t cbRead;
+ int rc;
+
+ /*
+ * Is it the headers, i.e. prior to the first section.
+ */
+ if (RVA < pModPe->cbHeaders)
+ {
+ cbRead = RT_MIN(pModPe->cbHeaders - RVA, cb);
+ rc = pReader->pfnRead(pReader, pvBuf, cbRead, RVA);
+ if ( cbRead == cb
+ || RT_FAILURE(rc))
+ return rc;
+ cb -= cbRead;
+ RVA += cbRead;
+ pvBuf = (uint8_t *)pvBuf + cbRead;
+ }
+
+ /* In the zero space between headers and the first section? */
+ if (RVA < pSH->VirtualAddress)
+ {
+ cbRead = RT_MIN(pSH->VirtualAddress - RVA, cb);
+ memset(pvBuf, 0, cbRead);
+ if (cbRead == cb)
+ return VINF_SUCCESS;
+ cb -= cbRead;
+ RVA += cbRead;
+ pvBuf = (uint8_t *)pvBuf + cbRead;
+ }
+
+ /*
+ * Iterate the sections.
+ */
+ for (unsigned cLeft = pModPe->cSections;
+ cLeft > 0;
+ cLeft--, pSH++)
+ {
+ uint32_t off = RVA - pSH->VirtualAddress;
+ if (off < pSH->Misc.VirtualSize)
+ {
+ cbRead = RT_MIN(pSH->Misc.VirtualSize - off, cb);
+ rc = pReader->pfnRead(pReader, pvBuf, cbRead, pSH->PointerToRawData + off);
+ if ( cbRead == cb
+ || RT_FAILURE(rc))
+ return rc;
+ cb -= cbRead;
+ RVA += cbRead;
+ pvBuf = (uint8_t *)pvBuf + cbRead;
+ }
+ uint32_t RVANext = cLeft ? pSH[1].VirtualAddress : pModPe->cbImage;
+ if (RVA < RVANext)
+ {
+ cbRead = RT_MIN(RVANext - RVA, cb);
+ memset(pvBuf, 0, cbRead);
+ if (cbRead == cb)
+ return VINF_SUCCESS;
+ cb -= cbRead;
+ RVA += cbRead;
+ pvBuf = (uint8_t *)pvBuf + cbRead;
+ }
+ }
+
+ AssertFailed();
+ return VERR_INTERNAL_ERROR;
+}
+
+
+/**
+ * Validates the data of some selected data directories entries and remember
+ * important bits for later.
+ *
+ * This requires a valid section table and thus has to wait till after we've
+ * read and validated it.
+ *
+ * @returns iprt status code.
+ * @param pModPe The PE module instance.
+ * @param pOptHdr Pointer to the optional header (valid).
+ * @param fFlags Loader flags, RTLDR_O_XXX.
+ * @param pErrInfo Where to return extended error information. Optional.
+ */
+static int rtldrPEValidateDirectoriesAndRememberStuff(PRTLDRMODPE pModPe, const IMAGE_OPTIONAL_HEADER64 *pOptHdr, uint32_t fFlags,
+ PRTERRINFO pErrInfo)
+{
+ const char *pszLogName = pModPe->Core.pReader->pfnLogName(pModPe->Core.pReader); NOREF(pszLogName);
+ union /* combine stuff we're reading to help reduce stack usage. */
+ {
+ IMAGE_LOAD_CONFIG_DIRECTORY64 Cfg64;
+ uint8_t abZeros[sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64) * 4];
+ } u;
+
+ /*
+ * The load config entry may include lock prefix tables and whatnot which we don't implement.
+ * It does also include a lot of stuff which we can ignore, so we'll have to inspect the
+ * actual data before we can make up our mind about it all.
+ */
+ IMAGE_DATA_DIRECTORY Dir = pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG];
+ if (Dir.Size)
+ {
+ const size_t cbExpectV13 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V13)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V13);
+ const size_t cbExpectV12 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V12)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V12);
+ const size_t cbExpectV11 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V11)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V11);
+ const size_t cbExpectV10 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V10)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V10);
+ const size_t cbExpectV9 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V9)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V9);
+ const size_t cbExpectV8 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V8)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V8);
+ const size_t cbExpectV7 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V7)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V7);
+ const size_t cbExpectV6 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V6)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V6);
+ const size_t cbExpectV5 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V5)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V5);
+ const size_t cbExpectV4 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V4)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V4);
+ const size_t cbExpectV3 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V3)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V3);
+ const size_t cbExpectV2 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V2)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V2);
+ const size_t cbExpectV1 = !pModPe->f64Bit
+ ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V1)
+ : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V2) /*No V1*/;
+ const size_t cbNewHack = cbExpectV5; /* Playing safe here since there might've been revisions between V5 and V6 we don't know about . */
+ const size_t cbMaxKnown = cbExpectV12;
+
+ bool fNewerStructureHack = false;
+ if ( Dir.Size != cbExpectV13
+ && Dir.Size != cbExpectV12
+ && Dir.Size != cbExpectV11
+ && Dir.Size != cbExpectV10
+ && Dir.Size != cbExpectV9
+ && Dir.Size != cbExpectV8
+ && Dir.Size != cbExpectV7
+ && Dir.Size != cbExpectV6
+ && Dir.Size != cbExpectV5
+ && Dir.Size != cbExpectV4
+ && Dir.Size != cbExpectV3
+ && Dir.Size != cbExpectV2
+ && Dir.Size != cbExpectV1)
+ {
+ fNewerStructureHack = Dir.Size > cbNewHack /* These structure changes are slowly getting to us! More futher down. */
+ && Dir.Size <= sizeof(u);
+ Log(("rtldrPEOpen: %s: load cfg dir: unexpected dir size of %u bytes, expected %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu.%s\n",
+ pszLogName, Dir.Size, cbExpectV13, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1,
+ fNewerStructureHack ? " Will try ignore extra bytes if all zero." : ""));
+ if (!fNewerStructureHack)
+ return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE,
+ "Unexpected load config dir size of %u bytes; supported sized: %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu",
+ Dir.Size, cbExpectV13, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1);
+ }
+
+ /*
+ * Read, check new stuff and convert to 64-bit.
+ *
+ * If we accepted a newer structures when loading for debug or validation,
+ * otherwise we require the new bits to be all zero and hope that they are
+ * insignificant where image loading is concerned (that's mostly been the
+ * case even for non-zero bits, only hard exception is LockPrefixTable).
+ */
+ RT_ZERO(u.Cfg64);
+ int rc = rtldrPEReadRVA(pModPe, &u.Cfg64, Dir.Size, Dir.VirtualAddress);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ( fNewerStructureHack
+ && Dir.Size > cbMaxKnown
+ && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
+ && !ASMMemIsZero(&u.abZeros[cbMaxKnown], Dir.Size - cbMaxKnown))
+ {
+ Log(("rtldrPEOpen: %s: load cfg dir: Unexpected bytes are non-zero (%u bytes of which %u expected to be zero): %.*Rhxs\n",
+ pszLogName, Dir.Size, Dir.Size - cbMaxKnown, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]));
+ return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE,
+ "Grown load config (%u to %u bytes) includes non-zero bytes: %.*Rhxs",
+ cbMaxKnown, Dir.Size, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]);
+ }
+ rtldrPEConvert32BitLoadConfigTo64Bit(&u.Cfg64);
+
+ if (u.Cfg64.Size != Dir.Size)
+ {
+ /* Kludge #1: ntdll.dll from XP seen with Dir.Size=0x40 and Cfg64.Size=0x00. */
+ if (Dir.Size == 0x40 && u.Cfg64.Size == 0x00 && !pModPe->f64Bit)
+ {
+ Log(("rtldrPEOpen: %s: load cfg dir: Header (%d) and directory (%d) size mismatch, applying the XP kludge.\n",
+ pszLogName, u.Cfg64.Size, Dir.Size));
+ u.Cfg64.Size = Dir.Size;
+ }
+ /* Kludge #2: This happens a lot. Structure changes, but the linker doesn't get
+ updated and stores some old size in the directory. Use the header size. */
+ else if ( u.Cfg64.Size == cbExpectV13
+ || u.Cfg64.Size == cbExpectV12
+ || u.Cfg64.Size == cbExpectV11
+ || u.Cfg64.Size == cbExpectV10
+ || u.Cfg64.Size == cbExpectV9
+ || u.Cfg64.Size == cbExpectV8
+ || u.Cfg64.Size == cbExpectV7
+ || u.Cfg64.Size == cbExpectV6
+ || u.Cfg64.Size == cbExpectV5
+ || u.Cfg64.Size == cbExpectV4
+ || u.Cfg64.Size == cbExpectV3
+ || u.Cfg64.Size == cbExpectV2
+ || u.Cfg64.Size == cbExpectV1
+ || (fNewerStructureHack = (u.Cfg64.Size > cbNewHack && u.Cfg64.Size <= sizeof(u))) )
+ {
+ Log(("rtldrPEOpen: %s: load cfg dir: Header (%d) and directory (%d) size mismatch, applying the old linker kludge.\n",
+ pszLogName, u.Cfg64.Size, Dir.Size));
+
+ uint32_t const uOrgDir = Dir.Size;
+ Dir.Size = u.Cfg64.Size;
+ RT_ZERO(u.Cfg64);
+ rc = rtldrPEReadRVA(pModPe, &u.Cfg64, Dir.Size, Dir.VirtualAddress);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ( fNewerStructureHack
+ && Dir.Size > cbMaxKnown
+ && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))
+ && !ASMMemIsZero(&u.abZeros[cbMaxKnown], Dir.Size - cbMaxKnown))
+ {
+ Log(("rtldrPEOpen: %s: load cfg dir: Unknown bytes are non-zero (%u bytes of which %u expected to be zero): %.*Rhxs\n",
+ pszLogName, Dir.Size, Dir.Size - cbMaxKnown, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]));
+ return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE,
+ "Grown load config (%u to %u bytes, dir %u) includes non-zero bytes: %.*Rhxs",
+ cbMaxKnown, Dir.Size, uOrgDir, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]);
+ }
+ rtldrPEConvert32BitLoadConfigTo64Bit(&u.Cfg64);
+ AssertReturn(u.Cfg64.Size == Dir.Size,
+ RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE, "Data changed while reading! (%d vs %d)\n",
+ u.Cfg64.Size, Dir.Size));
+ }
+ else
+ {
+ Log(("rtldrPEOpen: %s: load cfg hdr: unexpected hdr size of %u bytes (dir %u), expected %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu.\n",
+ pszLogName, u.Cfg64.Size, Dir.Size, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1));
+ return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE,
+ "Unexpected load config header size of %u bytes (dir %u); supported sized: %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu",
+ u.Cfg64.Size, Dir.Size, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1);
+ }
+ }
+ if (u.Cfg64.LockPrefixTable && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)))
+ {
+ Log(("rtldrPEOpen: %s: load cfg dir: lock prefix table at %RX64. We don't support lock prefix tables!\n",
+ pszLogName, u.Cfg64.LockPrefixTable));
+ return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOCK_PREFIX_TABLE,
+ "Lock prefix table not supported: %RX64", u.Cfg64.LockPrefixTable);
+ }
+#if 0/* this seems to be safe to ignore. */
+ if ( u.Cfg64.SEHandlerTable
+ || u.Cfg64.SEHandlerCount)
+ {
+ Log(("rtldrPEOpen: %s: load cfg dir: SEHandlerTable=%RX64 and SEHandlerCount=%RX64 are unsupported!\n",
+ pszLogName, u.Cfg64.SEHandlerTable, u.Cfg64.SEHandlerCount));
+ return VERR_BAD_EXE_FORMAT;
+ }
+#endif
+ if (u.Cfg64.EditList && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)))
+ {
+ Log(("rtldrPEOpen: %s: load cfg dir: EditList=%RX64 is unsupported!\n",
+ pszLogName, u.Cfg64.EditList));
+ return RTErrInfoSetF(pErrInfo, VERR_BAD_EXE_FORMAT, "Load config EditList=%RX64 is not supported", u.Cfg64.EditList);
+ }
+ /** @todo GuardCFC? Possibly related to:
+ * http://research.microsoft.com/pubs/69217/ccs05-cfi.pdf
+ * Not trusting something designed by bakas who don't know how to modify a
+ * structure without messing up its natural alignment. */
+ if ( ( u.Cfg64.GuardCFCCheckFunctionPointer
+ || u.Cfg64.GuardCFDispatchFunctionPointer
+ || u.Cfg64.GuardCFFunctionTable
+ || u.Cfg64.GuardCFFunctionCount
+ || u.Cfg64.GuardFlags
+ || u.Cfg64.GuardAddressTakenIatEntryTable
+ || u.Cfg64.GuardAddressTakenIatEntryCount
+ || u.Cfg64.GuardLongJumpTargetTable
+ || u.Cfg64.GuardLongJumpTargetCount)
+ && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) )
+ {
+ Log(("rtldrPEOpen: %s: load cfg dir: Guard stuff: %RX64,%RX64,%RX64,%RX64,%RX32,%RX64,%RX64,%RX64,%RX64!\n",
+ pszLogName, u.Cfg64.GuardCFCCheckFunctionPointer, u.Cfg64.GuardCFDispatchFunctionPointer,
+ u.Cfg64.GuardCFFunctionTable, u.Cfg64.GuardCFFunctionCount, u.Cfg64.GuardFlags,
+ u.Cfg64.GuardAddressTakenIatEntryTable, u.Cfg64.GuardAddressTakenIatEntryCount,
+ u.Cfg64.GuardLongJumpTargetTable, u.Cfg64.GuardLongJumpTargetCount ));
+#if 0 /* ntdll 15002 uses this. */
+ return RTErrInfoSetF(pErrInfo, VERR_LDRPE_GUARD_CF_STUFF,
+ "Guard bits in load config: %RX64,%RX64,%RX64,%RX64,%RX32,%RX64,%RX64,%RX64,%RX64!",
+ u.Cfg64.GuardCFCCheckFunctionPointer, u.Cfg64.GuardCFDispatchFunctionPointer,
+ u.Cfg64.GuardCFFunctionTable, u.Cfg64.GuardCFFunctionCount, u.Cfg64.GuardFlags,
+ u.Cfg64.GuardAddressTakenIatEntryTable, u.Cfg64.GuardAddressTakenIatEntryCount,
+ u.Cfg64.GuardLongJumpTargetTable, u.Cfg64.GuardLongJumpTargetCount);
+#endif
+ }
+ }
+
+ /*
+ * If the image is signed and we're not doing this for debug purposes,
+ * take a look at the signature.
+ */
+ Dir = pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
+ if (Dir.Size)
+ {
+ PWIN_CERTIFICATE pFirst = (PWIN_CERTIFICATE)RTMemTmpAlloc(Dir.Size);
+ if (!pFirst)
+ return VERR_NO_TMP_MEMORY;
+ int rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pFirst, Dir.Size, Dir.VirtualAddress);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t off = 0;
+ do
+ {
+ PWIN_CERTIFICATE pCur = (PWIN_CERTIFICATE)((uint8_t *)pFirst + off);
+
+ /* validate the members. */
+ if ( pCur->dwLength < sizeof(WIN_CERTIFICATE)
+ || pCur->dwLength + off > Dir.Size)
+ {
+ Log(("rtldrPEOpen: %s: cert at %#x/%#x: dwLength=%#x\n", pszLogName, off, Dir.Size, pCur->dwLength));
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
+ "Cert at %#x LB %#x: Bad header length value: %#x", off, Dir.Size, pCur->dwLength);
+ break;
+ }
+ if ( pCur->wRevision != WIN_CERT_REVISION_2_0
+ && pCur->wRevision != WIN_CERT_REVISION_1_0)
+ {
+ Log(("rtldrPEOpen: %s: cert at %#x/%#x: wRevision=%#x\n", pszLogName, off, Dir.Size, pCur->wRevision));
+ if (pCur->wRevision >= WIN_CERT_REVISION_1_0)
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_UNSUPPORTED,
+ "Cert at %#x LB %#x: Unsupported revision: %#x", off, Dir.Size, pCur->wRevision);
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
+ "Cert at %#x LB %#x: Malformed revision: %#x", off, Dir.Size, pCur->wRevision);
+ break;
+ }
+ if ( pCur->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA
+ && pCur->wCertificateType != WIN_CERT_TYPE_X509
+ /*&& pCur->wCertificateType != WIN_CERT_TYPE_RESERVED_1*/
+ /*&& pCur->wCertificateType != WIN_CERT_TYPE_TS_STACK_SIGNED*/
+ && pCur->wCertificateType != WIN_CERT_TYPE_EFI_PKCS115
+ && pCur->wCertificateType != WIN_CERT_TYPE_EFI_GUID
+ )
+ {
+ Log(("rtldrPEOpen: %s: cert at %#x/%#x: wCertificateType=%#x\n", pszLogName, off, Dir.Size, pCur->wCertificateType));
+ if (pCur->wCertificateType)
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_UNSUPPORTED,
+ "Cert at %#x LB %#x: Unsupported certificate type: %#x",
+ off, Dir.Size, pCur->wCertificateType);
+ else
+ rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_MALFORMED,
+ "Cert at %#x LB %#x: Malformed certificate type: %#x",
+ off, Dir.Size, pCur->wCertificateType);
+ break;
+ }
+
+ /* Remember the first signed data certificate. */
+ if ( pCur->wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA
+ && pModPe->offPkcs7SignedData == 0)
+ {
+ pModPe->offPkcs7SignedData = Dir.VirtualAddress
+ + (uint32_t)((uintptr_t)&pCur->bCertificate[0] - (uintptr_t)pFirst);
+ pModPe->cbPkcs7SignedData = pCur->dwLength - RT_UOFFSETOF(WIN_CERTIFICATE, bCertificate);
+ }
+
+ /* next */
+ off += RT_ALIGN(pCur->dwLength, WIN_CERTIFICATE_ALIGNMENT);
+ } while (off < Dir.Size);
+ }
+ RTMemTmpFree(pFirst);
+ if (RT_FAILURE(rc) && !(fFlags & RTLDR_O_FOR_DEBUG))
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Open a PE image.
+ *
+ * @returns iprt status code.
+ * @param pReader The loader reader instance which will provide the raw image bits.
+ * @param fFlags Loader flags, RTLDR_O_XXX.
+ * @param enmArch Architecture specifier.
+ * @param offNtHdrs The offset of the NT headers (where you find "PE\0\0").
+ * @param phLdrMod Where to store the handle.
+ * @param pErrInfo Where to return extended error information. Optional.
+ */
+DECLHIDDEN(int) rtldrPEOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, RTFOFF offNtHdrs,
+ PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo)
+{
+ /*
+ * Read and validate the file header.
+ */
+ IMAGE_FILE_HEADER FileHdr;
+ int rc = pReader->pfnRead(pReader, &FileHdr, sizeof(FileHdr), offNtHdrs + 4);
+ if (RT_FAILURE(rc))
+ return rc;
+ RTLDRARCH enmArchImage;
+ const char *pszLogName = pReader->pfnLogName(pReader);
+ rc = rtldrPEValidateFileHeader(&FileHdr, fFlags, pszLogName, &enmArchImage, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Match the CPU architecture.
+ */
+ bool fArchNoCodeCheckPending = false;
+ if ( enmArch != enmArchImage
+ && ( enmArch != RTLDRARCH_WHATEVER
+ && !(fFlags & RTLDR_O_WHATEVER_ARCH)) )
+ {
+ if (!(fFlags & RTLDR_O_IGNORE_ARCH_IF_NO_CODE))
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDR_ARCH_MISMATCH, "Image is for '%s', only accepting images for '%s'.",
+ rtldrPEGetArchName(FileHdr.Machine), RTLdrArchName(enmArch));
+ fArchNoCodeCheckPending = true;
+ }
+
+ /*
+ * Read and validate the "optional" header. Convert 32->64 if necessary.
+ */
+ IMAGE_OPTIONAL_HEADER64 OptHdr;
+ rc = pReader->pfnRead(pReader, &OptHdr, FileHdr.SizeOfOptionalHeader, offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER));
+ if (RT_FAILURE(rc))
+ return rc;
+ if (FileHdr.SizeOfOptionalHeader != sizeof(OptHdr))
+ rtldrPEConvert32BitOptionalHeaderTo64Bit(&OptHdr);
+ rc = rtldrPEValidateOptionalHeader(&OptHdr, pszLogName, offNtHdrs, &FileHdr, pReader->pfnSize(pReader), fFlags, pErrInfo);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (fArchNoCodeCheckPending && OptHdr.SizeOfCode != 0)
+ return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDR_ARCH_MISMATCH,
+ "Image is for '%s' and contains code (%#x), only accepting images for '%s' with code.",
+ rtldrPEGetArchName(FileHdr.Machine), OptHdr.SizeOfCode, RTLdrArchName(enmArch));
+
+ /*
+ * Read and validate section headers.
+ */
+ const size_t cbSections = sizeof(IMAGE_SECTION_HEADER) * FileHdr.NumberOfSections;
+ PIMAGE_SECTION_HEADER paSections = (PIMAGE_SECTION_HEADER)RTMemAlloc(cbSections);
+ if (!paSections)
+ return VERR_NO_MEMORY;
+ rc = pReader->pfnRead(pReader, paSections, cbSections,
+ offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER) + FileHdr.SizeOfOptionalHeader);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtldrPEValidateAndTouchUpSectionHeaders(paSections, FileHdr.NumberOfSections, pszLogName,
+ &OptHdr, pReader->pfnSize(pReader), fFlags, fArchNoCodeCheckPending);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate and initialize the PE module structure.
+ */
+ PRTLDRMODPE pModPe = (PRTLDRMODPE)RTMemAllocZ(sizeof(*pModPe));
+ if (pModPe)
+ {
+ pModPe->Core.u32Magic = RTLDRMOD_MAGIC;
+ pModPe->Core.eState = LDR_STATE_OPENED;
+ if (FileHdr.SizeOfOptionalHeader == sizeof(OptHdr))
+ pModPe->Core.pOps = &s_rtldrPE64Ops.Core;
+ else
+ pModPe->Core.pOps = &s_rtldrPE32Ops.Core;
+ pModPe->Core.pReader = pReader;
+ pModPe->Core.enmFormat= RTLDRFMT_PE;
+ pModPe->Core.enmType = FileHdr.Characteristics & IMAGE_FILE_DLL
+ ? FileHdr.Characteristics & IMAGE_FILE_RELOCS_STRIPPED
+ ? RTLDRTYPE_EXECUTABLE_FIXED
+ : RTLDRTYPE_EXECUTABLE_RELOCATABLE
+ : FileHdr.Characteristics & IMAGE_FILE_RELOCS_STRIPPED
+ ? RTLDRTYPE_SHARED_LIBRARY_FIXED
+ : RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE;
+ pModPe->Core.enmEndian= RTLDRENDIAN_LITTLE;
+ pModPe->Core.enmArch = FileHdr.Machine == IMAGE_FILE_MACHINE_I386
+ ? RTLDRARCH_X86_32
+ : FileHdr.Machine == IMAGE_FILE_MACHINE_AMD64
+ ? RTLDRARCH_AMD64
+ : RTLDRARCH_WHATEVER;
+ pModPe->pvBits = NULL;
+ pModPe->offNtHdrs = offNtHdrs;
+ pModPe->offEndOfHdrs = offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER) + FileHdr.SizeOfOptionalHeader + cbSections;
+ pModPe->u16Machine = FileHdr.Machine;
+ pModPe->fFile = FileHdr.Characteristics;
+ pModPe->cSections = FileHdr.NumberOfSections;
+ pModPe->paSections = paSections;
+ pModPe->uEntryPointRVA= OptHdr.AddressOfEntryPoint;
+ pModPe->uImageBase = (RTUINTPTR)OptHdr.ImageBase;
+ pModPe->cbImage = OptHdr.SizeOfImage;
+ pModPe->cbHeaders = OptHdr.SizeOfHeaders;
+ pModPe->uSectionAlign = OptHdr.SectionAlignment;
+ pModPe->uTimestamp = FileHdr.TimeDateStamp;
+ pModPe->cImports = UINT32_MAX;
+ pModPe->f64Bit = FileHdr.SizeOfOptionalHeader == sizeof(OptHdr);
+ pModPe->ImportDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
+ pModPe->RelocDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
+ pModPe->ExportDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
+ pModPe->DebugDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG];
+ pModPe->SecurityDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY];
+ pModPe->ExceptionDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
+ pModPe->fDllCharacteristics = OptHdr.DllCharacteristics;
+
+ /*
+ * Perform validation of some selected data directories which requires
+ * inspection of the actual data. This also saves some certificate
+ * information.
+ */
+ rc = rtldrPEValidateDirectoriesAndRememberStuff(pModPe, &OptHdr, fFlags, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ *phLdrMod = &pModPe->Core;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pModPe);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ RTMemFree(paSections);
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/ldr/ldrVfsFile.cpp b/src/VBox/Runtime/common/ldr/ldrVfsFile.cpp
new file mode 100644
index 00000000..3f717583
--- /dev/null
+++ b/src/VBox/Runtime/common/ldr/ldrVfsFile.cpp
@@ -0,0 +1,293 @@
+/* $Id: ldrVfsFile.cpp $ */
+/** @file
+ * IPRT - Binary Image Loader, The File Oriented Parts, VFS variant.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_LDR
+#include <iprt/ldr.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloc.h>
+#include <iprt/file.h>
+#include <iprt/assert.h>
+#include <iprt/log.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/formats/mz.h>
+#include "internal/ldr.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * VFS file Reader instance.
+ * This provides raw image bits from a file.
+ */
+typedef struct RTLDRREADERVFSFILE
+{
+ /** The core. */
+ RTLDRREADER Core;
+ /** The VFS file. */
+ RTVFSFILE hVfsFile;
+ /** Number of users or the mapping. */
+ RTUINT cMappings;
+ /** Pointer to the in memory mapping. */
+ void *pvMapping;
+ /** The filename (variable size). */
+ char szFilename[1];
+} RTLDRREADERVFSFILE;
+typedef RTLDRREADERVFSFILE *PRTLDRREADERVFSFILE;
+
+
+/** @copydoc RTLDRREADER::pfnRead */
+static DECLCALLBACK(int) rtldrVfsFileRead(PRTLDRREADER pReader, void *pvBuf, size_t cb, RTFOFF off)
+{
+ PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader;
+ return RTVfsFileReadAt(pFileReader->hVfsFile, off, pvBuf, cb, NULL);
+}
+
+
+/** @copydoc RTLDRREADER::pfnTell */
+static DECLCALLBACK(RTFOFF) rtldrVfsFileTell(PRTLDRREADER pReader)
+{
+ PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader;
+ return RTVfsFileTell(pFileReader->hVfsFile);
+}
+
+
+/** @copydoc RTLDRREADER::pfnSize */
+static DECLCALLBACK(uint64_t) rtldrVfsFileSize(PRTLDRREADER pReader)
+{
+ PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader;
+ uint64_t cbFile;
+ int rc = RTVfsFileQuerySize(pFileReader->hVfsFile, &cbFile);
+ if (RT_SUCCESS(rc))
+ return cbFile;
+ return 0;
+}
+
+
+/** @copydoc RTLDRREADER::pfnLogName */
+static DECLCALLBACK(const char *) rtldrVfsFileLogName(PRTLDRREADER pReader)
+{
+ PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader;
+ return pFileReader->szFilename;
+}
+
+
+/** @copydoc RTLDRREADER::pfnMap */
+static DECLCALLBACK(int) rtldrVfsFileMap(PRTLDRREADER pReader, const void **ppvBits)
+{
+ PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader;
+
+ /*
+ * Already mapped?
+ */
+ if (pFileReader->pvMapping)
+ {
+ pFileReader->cMappings++;
+ *ppvBits = pFileReader->pvMapping;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Allocate memory.
+ */
+ uint64_t cbFile = rtldrVfsFileSize(pReader);
+ size_t cb = (size_t)cbFile;
+ if ((uint64_t)cb != cbFile)
+ return VERR_IMAGE_TOO_BIG;
+ pFileReader->pvMapping = RTMemAlloc(cb);
+ if (!pFileReader->pvMapping)
+ return VERR_NO_MEMORY;
+ int rc = rtldrVfsFileRead(pReader, pFileReader->pvMapping, cb, 0);
+ if (RT_SUCCESS(rc))
+ {
+ pFileReader->cMappings = 1;
+ *ppvBits = pFileReader->pvMapping;
+ }
+ else
+ {
+ RTMemFree(pFileReader->pvMapping);
+ pFileReader->pvMapping = NULL;
+ }
+
+ return rc;
+}
+
+
+/** @copydoc RTLDRREADER::pfnUnmap */
+static DECLCALLBACK(int) rtldrVfsFileUnmap(PRTLDRREADER pReader, const void *pvBits)
+{
+ PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader;
+ AssertReturn(pFileReader->cMappings > 0, VERR_INVALID_PARAMETER);
+
+ if (!--pFileReader->cMappings)
+ {
+ RTMemFree(pFileReader->pvMapping);
+ pFileReader->pvMapping = NULL;
+ }
+
+ NOREF(pvBits);
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTLDRREADER::pfnDestroy */
+static DECLCALLBACK(int) rtldrVfsFileDestroy(PRTLDRREADER pReader)
+{
+ PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader;
+ if (pFileReader->hVfsFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pFileReader->hVfsFile);
+ pFileReader->hVfsFile = NIL_RTVFSFILE;
+ }
+ RTMemFree(pFileReader);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Opens a loader file reader.
+ *
+ * @returns iprt status code.
+ * @param pszFilename The file to open, can be VFS chain.
+ * @param ppReader Where to store the reader instance on success.
+ * @param poffError Where to return the offset into @a pszFilename of an VFS
+ * chain element causing trouble. Optional.
+ * @param pErrInfo Where to return extended error information. Optional.
+ */
+static int rtldrVfsFileCreate(const char *pszFilename, PRTLDRREADER *ppReader, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ size_t cbFilename = strlen(pszFilename) + 1;
+ int rc = VERR_NO_MEMORY;
+ PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)RTMemAlloc(RT_UOFFSETOF_DYN(RTLDRREADERVFSFILE, szFilename[cbFilename]));
+ if (pFileReader)
+ {
+ memcpy(pFileReader->szFilename, pszFilename, cbFilename);
+ pFileReader->szFilename[0] = '\0';
+ rc = RTVfsChainOpenFile(pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &pFileReader->hVfsFile,
+ poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ pFileReader->Core.uMagic = RTLDRREADER_MAGIC;
+ pFileReader->Core.pfnRead = rtldrVfsFileRead;
+ pFileReader->Core.pfnTell = rtldrVfsFileTell;
+ pFileReader->Core.pfnSize = rtldrVfsFileSize;
+ pFileReader->Core.pfnLogName = rtldrVfsFileLogName;
+ pFileReader->Core.pfnMap = rtldrVfsFileMap;
+ pFileReader->Core.pfnUnmap = rtldrVfsFileUnmap;
+ pFileReader->Core.pfnDestroy = rtldrVfsFileDestroy;
+ pFileReader->cMappings = 0;
+ pFileReader->pvMapping = NULL;
+ *ppReader = &pFileReader->Core;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pFileReader);
+ }
+ *ppReader = NULL;
+ return rc;
+}
+
+
+/**
+ * Open a binary image file allowing VFS chains in the filename.
+ *
+ * @returns iprt status code.
+ * @param pszFilename Image filename, VFS chain specifiers allowed.
+ * @param fFlags Valid RTLDR_O_XXX combination.
+ * @param enmArch CPU architecture specifier for the image to be loaded.
+ * @param phLdrMod Where to store the handle to the loader module.
+ * @param poffError Where to return the offset into @a pszFilename of an VFS
+ * chain element causing trouble. Optional.
+ * @param pErrInfo Where to return extended error information. Optional.
+ */
+RTDECL(int) RTLdrOpenVfsChain(const char *pszFilename, uint32_t fFlags, RTLDRARCH enmArch,
+ PRTLDRMOD phLdrMod, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ LogFlow(("RTLdrOpenVfsChain: pszFilename=%p:{%s} fFlags=%#x enmArch=%d phLdrMod=%p\n",
+ pszFilename, pszFilename, fFlags, enmArch, phLdrMod));
+ AssertMsgReturn(!(fFlags & ~RTLDR_O_VALID_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(enmArch > RTLDRARCH_INVALID && enmArch < RTLDRARCH_END, ("%d\n", enmArch), VERR_INVALID_PARAMETER);
+
+ /*
+ * Create file reader & invoke worker which identifies and calls the image interpreter.
+ */
+ PRTLDRREADER pReader;
+ int rc = rtldrVfsFileCreate(pszFilename, &pReader, poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (poffError)
+ *poffError = 0;
+ rc = RTLdrOpenWithReader(pReader, fFlags, enmArch, phLdrMod, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("RTLdrOpenEx: return %Rrc *phLdrMod=%p\n", rc, *phLdrMod));
+ return rc;
+ }
+ pReader->pfnDestroy(pReader);
+ }
+ *phLdrMod = NIL_RTLDRMOD;
+ LogFlow(("RTLdrOpenEx: return %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLdrOpenVfsChain);
+
+
+/**
+ * Open a binary image file using kLdr allowing VFS chains in the filename.
+ *
+ * @returns iprt status code.
+ * @param pszFilename Image filename.
+ * @param fFlags Reserved, MBZ.
+ * @param enmArch CPU architecture specifier for the image to be loaded.
+ * @param phLdrMod Where to store the handle to the loaded module.
+ * @param poffError Where to return the offset into @a pszFilename of an VFS
+ * chain element causing trouble. Optional.
+ * @param pErrInfo Where to return extended error information. Optional.
+ * @remark Primarily for testing the loader.
+ */
+RTDECL(int) RTLdrOpenVfsChainkLdr(const char *pszFilename, uint32_t fFlags, RTLDRARCH enmArch,
+ PRTLDRMOD phLdrMod, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ return RTLdrOpenVfsChain(pszFilename, fFlags, enmArch, phLdrMod, poffError, pErrInfo);
+}
+RT_EXPORT_SYMBOL(RTLdrOpenVfsChainkLdr);
+
diff --git a/src/VBox/Runtime/common/log/Makefile.kup b/src/VBox/Runtime/common/log/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/log/Makefile.kup
diff --git a/src/VBox/Runtime/common/log/RTLogCreateEx.cpp b/src/VBox/Runtime/common/log/RTLogCreateEx.cpp
new file mode 100644
index 00000000..be25354d
--- /dev/null
+++ b/src/VBox/Runtime/common/log/RTLogCreateEx.cpp
@@ -0,0 +1,68 @@
+/* $Id: RTLogCreateEx.cpp $ */
+/** @file
+ * Runtime VBox - RTLogCreateEx.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/log.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+
+RTDECL(int) RTLogCreateEx(PRTLOGGER *ppLogger, const char *pszEnvVarBase, uint64_t fFlags, const char *pszGroupSettings,
+ unsigned cGroups, const char * const *papszGroups, uint32_t cMaxEntriesPerGroup,
+ uint32_t cBufDescs, PRTLOGBUFFERDESC paBufDescs, uint32_t fDestFlags,
+ PFNRTLOGPHASE pfnPhase, uint32_t cHistory, uint64_t cbHistoryFileMax, uint32_t cSecsHistoryTimeSlot,
+ PCRTLOGOUTPUTIF pOutputIf, void *pvOutputIfUser,
+ PRTERRINFO pErrInfo, const char *pszFilenameFmt, ...)
+{
+ va_list va;
+ int rc;
+
+ va_start(va, pszFilenameFmt);
+ rc = RTLogCreateExV(ppLogger, pszEnvVarBase, fFlags, pszGroupSettings, cGroups, papszGroups, cMaxEntriesPerGroup,
+ cBufDescs, paBufDescs, fDestFlags,
+ pfnPhase, cHistory, cbHistoryFileMax, cSecsHistoryTimeSlot,
+ pOutputIf, pvOutputIfUser,
+ pErrInfo, pszFilenameFmt, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogCreateEx);
+
diff --git a/src/VBox/Runtime/common/log/RTLogSetR0ThreadNameF.cpp b/src/VBox/Runtime/common/log/RTLogSetR0ThreadNameF.cpp
new file mode 100644
index 00000000..489810d7
--- /dev/null
+++ b/src/VBox/Runtime/common/log/RTLogSetR0ThreadNameF.cpp
@@ -0,0 +1,57 @@
+/* $Id: RTLogSetR0ThreadNameF.cpp $ */
+/** @file
+ * Runtime VBox - RTLogSetR0ThreadNameF.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/log.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTR0DECL(int) RTLogSetR0ThreadNameF(PRTLOGGER pLogger, const char *pszNameFmt, ...)
+{
+ int rc;
+ va_list va;
+ va_start(va, pszNameFmt);
+ rc = RTLogSetR0ThreadNameV(pLogger, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogSetR0ThreadNameF);
+
diff --git a/src/VBox/Runtime/common/log/log-weak-assert.cpp b/src/VBox/Runtime/common/log/log-weak-assert.cpp
new file mode 100644
index 00000000..13c37abd
--- /dev/null
+++ b/src/VBox/Runtime/common/log/log-weak-assert.cpp
@@ -0,0 +1,55 @@
+/* $Id: log-weak-assert.cpp $ */
+/** @file
+ * Runtime VBox - Logger, Weak Function Pointers for Assertions.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/log.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+RTDATADECL(PFNRTLOGASSERTION) g_pfnRTLogAssert;
+RTDATADECL(PFNRTLOGASSERTIONV) g_pfnRTLogAssertV;
+#else
+RTDATADECL(PFNRTLOGASSERTION) g_pfnRTLogAssert = RTLogAssert;
+RTDATADECL(PFNRTLOGASSERTIONV) g_pfnRTLogAssertV = RTLogAssertV;
+#endif
+
diff --git a/src/VBox/Runtime/common/log/log-weak-rel.cpp b/src/VBox/Runtime/common/log/log-weak-rel.cpp
new file mode 100644
index 00000000..8e31f1d1
--- /dev/null
+++ b/src/VBox/Runtime/common/log/log-weak-rel.cpp
@@ -0,0 +1,55 @@
+/* $Id: log-weak-rel.cpp $ */
+/** @file
+ * Runtime VBox - Logger, Weak Function Pointers for Release Logging.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/log.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+RTDATADECL(PFNLOGRELGETDEFAULTINSTANCE) g_pfnRTLogRelGetDefaultInstance;
+RTDATADECL(PFNLOGRELGETDEFAULTINSTANCEEX) g_pfnRTLogRelGetDefaultInstanceEx;
+#else
+RTDATADECL(PFNLOGRELGETDEFAULTINSTANCE) g_pfnRTLogRelGetDefaultInstance = RTLogRelGetDefaultInstance;
+RTDATADECL(PFNLOGRELGETDEFAULTINSTANCEEX) g_pfnRTLogRelGetDefaultInstanceEx = RTLogRelGetDefaultInstanceEx;
+#endif
+
diff --git a/src/VBox/Runtime/common/log/log-weak.cpp b/src/VBox/Runtime/common/log/log-weak.cpp
new file mode 100644
index 00000000..18610038
--- /dev/null
+++ b/src/VBox/Runtime/common/log/log-weak.cpp
@@ -0,0 +1,86 @@
+/* $Id: log-weak.cpp $ */
+/** @file
+ * Runtime VBox - Logger, Weak Function Pointers and related wrappers.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/log.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+RTDATADECL(PFNRTLOGLOGGEREXV) g_pfnRTLogLoggerExV;
+RTDATADECL(PFNLOGGETDEFAULTINSTANCE) g_pfnRTLogGetDefaultInstance;
+RTDATADECL(PFNLOGGETDEFAULTINSTANCEEX) g_pfnRTLogGetDefaultInstanceEx;
+#else
+RTDATADECL(PFNRTLOGLOGGEREXV) g_pfnRTLogLoggerExV = RTLogLoggerExV;
+RTDATADECL(PFNLOGGETDEFAULTINSTANCE) g_pfnRTLogGetDefaultInstance = RTLogGetDefaultInstance;
+RTDATADECL(PFNLOGGETDEFAULTINSTANCEEX) g_pfnRTLogGetDefaultInstanceEx = RTLogGetDefaultInstanceEx;
+#endif
+
+
+#if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+
+RTDECL(void) RTLogLoggerExWeak(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, ...)
+{
+ if (g_pfnRTLogLoggerExV)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ g_pfnRTLogLoggerExV(pLogger, fFlags, iGroup, pszFormat, va);
+ va_end(va);
+ }
+}
+
+
+RTDECL(void) RTLogLoggerWeak(PRTLOGGER pLogger, void *pvCallerRet, const char *pszFormat, ...)
+{
+ RT_NOREF(pvCallerRet);
+ if (g_pfnRTLogLoggerExV)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ g_pfnRTLogLoggerExV(pLogger, 0, ~0U, pszFormat, va);
+ va_end(va);
+ }
+}
+
+#endif
+
diff --git a/src/VBox/Runtime/common/log/log.cpp b/src/VBox/Runtime/common/log/log.cpp
new file mode 100644
index 00000000..18a272e4
--- /dev/null
+++ b/src/VBox/Runtime/common/log/log.cpp
@@ -0,0 +1,4337 @@
+/* $Id: log.cpp $ */
+/** @file
+ * Runtime VBox - Logger.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/log.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloc.h>
+#include <iprt/crc.h>
+#include <iprt/process.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/mp.h>
+#ifdef IN_RING3
+# include <iprt/env.h>
+# include <iprt/file.h>
+# include <iprt/lockvalidator.h>
+# include <iprt/path.h>
+#endif
+#include <iprt/time.h>
+#include <iprt/asm.h>
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+# include <iprt/asm-amd64-x86.h>
+#endif
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/param.h>
+
+#include <iprt/stdarg.h>
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+#ifdef IN_RING3
+# include <iprt/alloca.h>
+# ifndef IPRT_NO_CRT
+# include <stdio.h>
+# endif
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def RTLOG_RINGBUF_DEFAULT_SIZE
+ * The default ring buffer size. */
+/** @def RTLOG_RINGBUF_MAX_SIZE
+ * The max ring buffer size. */
+/** @def RTLOG_RINGBUF_MIN_SIZE
+ * The min ring buffer size. */
+#ifdef IN_RING0
+# define RTLOG_RINGBUF_DEFAULT_SIZE _64K
+# define RTLOG_RINGBUF_MAX_SIZE _4M
+# define RTLOG_RINGBUF_MIN_SIZE _1K
+#elif defined(IN_RING3) || defined(DOXYGEN_RUNNING)
+# define RTLOG_RINGBUF_DEFAULT_SIZE _512K
+# define RTLOG_RINGBUF_MAX_SIZE _1G
+# define RTLOG_RINGBUF_MIN_SIZE _4K
+#endif
+/** The start of ring buffer eye catcher (16 bytes). */
+#define RTLOG_RINGBUF_EYE_CATCHER "START RING BUF\0"
+AssertCompile(sizeof(RTLOG_RINGBUF_EYE_CATCHER) == 16);
+/** The end of ring buffer eye catcher (16 bytes). This also ensures that the ring buffer
+ * forms are properly terminated C string (leading zero chars). */
+#define RTLOG_RINGBUF_EYE_CATCHER_END "\0\0\0END RING BUF"
+AssertCompile(sizeof(RTLOG_RINGBUF_EYE_CATCHER_END) == 16);
+
+/** The default buffer size. */
+#ifdef IN_RING0
+# define RTLOG_BUFFER_DEFAULT_SIZE _16K
+#else
+# define RTLOG_BUFFER_DEFAULT_SIZE _128K
+#endif
+/** Buffer alignment used RTLogCreateExV. */
+#define RTLOG_BUFFER_ALIGN 64
+
+
+/** Resolved a_pLoggerInt to the default logger if NULL, returning @a a_rcRet if
+ * no default logger could be created. */
+#define RTLOG_RESOLVE_DEFAULT_RET(a_pLoggerInt, a_rcRet) do {\
+ if (a_pLoggerInt) { /*maybe*/ } \
+ else \
+ { \
+ a_pLoggerInt = (PRTLOGGERINTERNAL)rtLogDefaultInstanceCommon(); \
+ if (a_pLoggerInt) { /*maybe*/ } \
+ else \
+ return (a_rcRet); \
+ } \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Internal logger data.
+ *
+ * @remarks Don't make casual changes to this structure.
+ */
+typedef struct RTLOGGERINTERNAL
+{
+ /** The public logger core. */
+ RTLOGGER Core;
+
+ /** The structure revision (RTLOGGERINTERNAL_REV). */
+ uint32_t uRevision;
+ /** The size of the internal logger structure. */
+ uint32_t cbSelf;
+
+ /** Logger instance flags - RTLOGFLAGS. */
+ uint64_t fFlags;
+ /** Destination flags - RTLOGDEST. */
+ uint32_t fDestFlags;
+
+ /** Number of buffer descriptors. */
+ uint8_t cBufDescs;
+ /** Index of the current buffer descriptor. */
+ uint8_t idxBufDesc;
+ /** Pointer to buffer the descriptors. */
+ PRTLOGBUFFERDESC paBufDescs;
+ /** Pointer to the current buffer the descriptor. */
+ PRTLOGBUFFERDESC pBufDesc;
+
+ /** Spinning mutex semaphore. Can be NIL. */
+ RTSEMSPINMUTEX hSpinMtx;
+ /** Pointer to the flush function. */
+ PFNRTLOGFLUSH pfnFlush;
+
+ /** Custom prefix callback. */
+ PFNRTLOGPREFIX pfnPrefix;
+ /** Prefix callback argument. */
+ void *pvPrefixUserArg;
+ /** This is set if a prefix is pending. */
+ bool fPendingPrefix;
+ /** Alignment padding. */
+ bool afPadding1[2];
+ /** Set if fully created. Used to avoid confusing in a few functions used to
+ * parse logger settings from environment variables. */
+ bool fCreated;
+
+ /** The max number of groups that there is room for in afGroups and papszGroups.
+ * Used by RTLogCopyGroupAndFlags(). */
+ uint32_t cMaxGroups;
+ /** Pointer to the group name array.
+ * (The data is readonly and provided by the user.) */
+ const char * const *papszGroups;
+
+ /** The number of log entries per group. NULL if
+ * RTLOGFLAGS_RESTRICT_GROUPS is not specified. */
+ uint32_t *pacEntriesPerGroup;
+ /** The max number of entries per group. */
+ uint32_t cMaxEntriesPerGroup;
+
+ /** @name Ring buffer logging
+ * The ring buffer records the last cbRingBuf - 1 of log output. The
+ * other configured log destinations are not touched until someone calls
+ * RTLogFlush(), when the ring buffer content is written to them all.
+ *
+ * The aim here is a fast logging destination, that avoids wasting storage
+ * space saving disk space when dealing with huge log volumes where the
+ * interesting bits usually are found near the end of the log. This is
+ * typically the case for scenarios that crashes or hits assertions.
+ *
+ * RTLogFlush() is called implicitly when hitting an assertion. While on a
+ * crash the most debuggers are able to make calls these days, it's usually
+ * possible to view the ring buffer memory.
+ *
+ * @{ */
+ /** Ring buffer size (including both eye catchers). */
+ uint32_t cbRingBuf;
+ /** Number of bytes passing thru the ring buffer since last RTLogFlush call.
+ * (This is used to avoid writing out the same bytes twice.) */
+ uint64_t volatile cbRingBufUnflushed;
+ /** Ring buffer pointer (points at RTLOG_RINGBUF_EYE_CATCHER). */
+ char *pszRingBuf;
+ /** Current ring buffer position (where to write the next char). */
+ char * volatile pchRingBufCur;
+ /** @} */
+
+ /** Program time base for ring-0 (copy of g_u64ProgramStartNanoTS). */
+ uint64_t nsR0ProgramStart;
+ /** Thread name for use in ring-0 with RTLOGFLAGS_PREFIX_THREAD. */
+ char szR0ThreadName[16];
+
+#ifdef IN_RING3
+ /** @name File logging bits for the logger.
+ * @{ */
+ /** Pointer to the function called when starting logging, and when
+ * ending or starting a new log file as part of history rotation.
+ * This can be NULL. */
+ PFNRTLOGPHASE pfnPhase;
+ /** Pointer to the output interface used. */
+ PCRTLOGOUTPUTIF pOutputIf;
+ /** Opaque user data passed to the callbacks in the output interface. */
+ void *pvOutputIfUser;
+
+ /** Handle to log file (if open) - only used by the default output interface to avoid additional layers of indirection. */
+ RTFILE hFile;
+ /** Log file history settings: maximum amount of data to put in a file. */
+ uint64_t cbHistoryFileMax;
+ /** Log file history settings: current amount of data in a file. */
+ uint64_t cbHistoryFileWritten;
+ /** Log file history settings: maximum time to use a file (in seconds). */
+ uint32_t cSecsHistoryTimeSlot;
+ /** Log file history settings: in what time slot was the file created. */
+ uint32_t uHistoryTimeSlotStart;
+ /** Log file history settings: number of older files to keep.
+ * 0 means no history. */
+ uint32_t cHistory;
+ /** Pointer to filename. */
+ char szFilename[RTPATH_MAX];
+ /** Flag whether the log file was opened successfully. */
+ bool fLogOpened;
+ /** @} */
+#endif /* IN_RING3 */
+
+ /** Number of groups in the afGroups and papszGroups members. */
+ uint32_t cGroups;
+ /** Group flags array - RTLOGGRPFLAGS.
+ * This member have variable length and may extend way beyond
+ * the declared size of 1 entry. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ uint32_t afGroups[RT_FLEXIBLE_ARRAY];
+} RTLOGGERINTERNAL;
+
+/** The revision of the internal logger structure. */
+# define RTLOGGERINTERNAL_REV UINT32_C(13)
+
+AssertCompileMemberAlignment(RTLOGGERINTERNAL, cbRingBufUnflushed, sizeof(uint64_t));
+#ifdef IN_RING3
+AssertCompileMemberAlignment(RTLOGGERINTERNAL, hFile, sizeof(void *));
+AssertCompileMemberAlignment(RTLOGGERINTERNAL, cbHistoryFileMax, sizeof(uint64_t));
+#endif
+
+
+/** Pointer to internal logger bits. */
+typedef struct RTLOGGERINTERNAL *PRTLOGGERINTERNAL;
+/**
+ * Arguments passed to the output function.
+ */
+typedef struct RTLOGOUTPUTPREFIXEDARGS
+{
+ /** The logger instance. */
+ PRTLOGGERINTERNAL pLoggerInt;
+ /** The flags. (used for prefixing.) */
+ unsigned fFlags;
+ /** The group. (used for prefixing.) */
+ unsigned iGroup;
+ /** Used by RTLogBulkNestedWrite. */
+ const char *pszInfix;
+} RTLOGOUTPUTPREFIXEDARGS, *PRTLOGOUTPUTPREFIXEDARGS;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static unsigned rtlogGroupFlags(const char *psz);
+#ifdef IN_RING3
+static int rtR3LogOpenFileDestination(PRTLOGGERINTERNAL pLoggerInt, PRTERRINFO pErrInfo);
+#endif
+static void rtLogRingBufFlush(PRTLOGGERINTERNAL pLoggerInt);
+static void rtlogFlush(PRTLOGGERINTERNAL pLoggerInt, bool fNeedSpace);
+#ifdef IN_RING3
+static FNRTLOGPHASEMSG rtlogPhaseMsgLocked;
+static FNRTLOGPHASEMSG rtlogPhaseMsgNormal;
+#endif
+static DECLCALLBACK(size_t) rtLogOutputPrefixed(void *pv, const char *pachChars, size_t cbChars);
+static void rtlogLoggerExFLocked(PRTLOGGERINTERNAL pLoggerInt, unsigned fFlags, unsigned iGroup, const char *pszFormat, ...);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Default logger instance. */
+static PRTLOGGER g_pLogger;
+/** Default release logger instance. */
+static PRTLOGGER g_pRelLogger;
+#ifdef IN_RING3
+/** The RTThreadGetWriteLockCount() change caused by the logger mutex semaphore. */
+static uint32_t volatile g_cLoggerLockCount;
+#endif
+
+#ifdef IN_RING0
+/** Number of per-thread loggers. */
+static int32_t volatile g_cPerThreadLoggers;
+/** Per-thread loggers.
+ * This is just a quick TLS hack suitable for debug logging only.
+ * If we run out of entries, just unload and reload the driver. */
+static struct RTLOGGERPERTHREAD
+{
+ /** The thread. */
+ RTNATIVETHREAD volatile NativeThread;
+ /** The (process / session) key. */
+ uintptr_t volatile uKey;
+ /** The logger instance.*/
+ PRTLOGGER volatile pLogger;
+} g_aPerThreadLoggers[8] =
+{
+ { NIL_RTNATIVETHREAD, 0, 0},
+ { NIL_RTNATIVETHREAD, 0, 0},
+ { NIL_RTNATIVETHREAD, 0, 0},
+ { NIL_RTNATIVETHREAD, 0, 0},
+ { NIL_RTNATIVETHREAD, 0, 0},
+ { NIL_RTNATIVETHREAD, 0, 0},
+ { NIL_RTNATIVETHREAD, 0, 0},
+ { NIL_RTNATIVETHREAD, 0, 0}
+};
+#endif /* IN_RING0 */
+
+/**
+ * Logger flags instructions.
+ */
+static struct
+{
+ const char *pszInstr; /**< The name */
+ size_t cchInstr; /**< The size of the name. */
+ uint64_t fFlag; /**< The flag value. */
+ bool fInverted; /**< Inverse meaning? */
+ uint32_t fFixedDest; /**< RTLOGDEST_FIXED_XXX flags blocking this. */
+} const g_aLogFlags[] =
+{
+ { "disabled", sizeof("disabled" ) - 1, RTLOGFLAGS_DISABLED, false, 0 },
+ { "enabled", sizeof("enabled" ) - 1, RTLOGFLAGS_DISABLED, true, 0 },
+ { "buffered", sizeof("buffered" ) - 1, RTLOGFLAGS_BUFFERED, false, 0 },
+ { "unbuffered", sizeof("unbuffered" ) - 1, RTLOGFLAGS_BUFFERED, true, 0 },
+ { "usecrlf", sizeof("usecrlf" ) - 1, RTLOGFLAGS_USECRLF, false, 0 },
+ { "uself", sizeof("uself" ) - 1, RTLOGFLAGS_USECRLF, true, 0 },
+ { "append", sizeof("append" ) - 1, RTLOGFLAGS_APPEND, false, RTLOGDEST_FIXED_FILE },
+ { "overwrite", sizeof("overwrite" ) - 1, RTLOGFLAGS_APPEND, true, RTLOGDEST_FIXED_FILE },
+ { "rel", sizeof("rel" ) - 1, RTLOGFLAGS_REL_TS, false, 0 },
+ { "abs", sizeof("abs" ) - 1, RTLOGFLAGS_REL_TS, true, 0 },
+ { "dec", sizeof("dec" ) - 1, RTLOGFLAGS_DECIMAL_TS, false, 0 },
+ { "hex", sizeof("hex" ) - 1, RTLOGFLAGS_DECIMAL_TS, true, 0 },
+ { "writethru", sizeof("writethru" ) - 1, RTLOGFLAGS_WRITE_THROUGH, false, 0 },
+ { "writethrough", sizeof("writethrough") - 1, RTLOGFLAGS_WRITE_THROUGH, false, 0 },
+ { "flush", sizeof("flush" ) - 1, RTLOGFLAGS_FLUSH, false, 0 },
+ { "lockcnts", sizeof("lockcnts" ) - 1, RTLOGFLAGS_PREFIX_LOCK_COUNTS, false, 0 },
+ { "cpuid", sizeof("cpuid" ) - 1, RTLOGFLAGS_PREFIX_CPUID, false, 0 },
+ { "pid", sizeof("pid" ) - 1, RTLOGFLAGS_PREFIX_PID, false, 0 },
+ { "flagno", sizeof("flagno" ) - 1, RTLOGFLAGS_PREFIX_FLAG_NO, false, 0 },
+ { "flag", sizeof("flag" ) - 1, RTLOGFLAGS_PREFIX_FLAG, false, 0 },
+ { "groupno", sizeof("groupno" ) - 1, RTLOGFLAGS_PREFIX_GROUP_NO, false, 0 },
+ { "group", sizeof("group" ) - 1, RTLOGFLAGS_PREFIX_GROUP, false, 0 },
+ { "tid", sizeof("tid" ) - 1, RTLOGFLAGS_PREFIX_TID, false, 0 },
+ { "thread", sizeof("thread" ) - 1, RTLOGFLAGS_PREFIX_THREAD, false, 0 },
+ { "custom", sizeof("custom" ) - 1, RTLOGFLAGS_PREFIX_CUSTOM, false, 0 },
+ { "timeprog", sizeof("timeprog" ) - 1, RTLOGFLAGS_PREFIX_TIME_PROG, false, 0 },
+ { "time", sizeof("time" ) - 1, RTLOGFLAGS_PREFIX_TIME, false, 0 },
+ { "msprog", sizeof("msprog" ) - 1, RTLOGFLAGS_PREFIX_MS_PROG, false, 0 },
+ { "tsc", sizeof("tsc" ) - 1, RTLOGFLAGS_PREFIX_TSC, false, 0 }, /* before ts! */
+ { "ts", sizeof("ts" ) - 1, RTLOGFLAGS_PREFIX_TS, false, 0 },
+ /* We intentionally omit RTLOGFLAGS_RESTRICT_GROUPS. */
+};
+
+/**
+ * Logger destination instructions.
+ */
+static struct
+{
+ const char *pszInstr; /**< The name. */
+ size_t cchInstr; /**< The size of the name. */
+ uint32_t fFlag; /**< The corresponding destination flag. */
+} const g_aLogDst[] =
+{
+ { RT_STR_TUPLE("file"), RTLOGDEST_FILE }, /* Must be 1st! */
+ { RT_STR_TUPLE("dir"), RTLOGDEST_FILE }, /* Must be 2nd! */
+ { RT_STR_TUPLE("history"), 0 }, /* Must be 3rd! */
+ { RT_STR_TUPLE("histsize"), 0 }, /* Must be 4th! */
+ { RT_STR_TUPLE("histtime"), 0 }, /* Must be 5th! */
+ { RT_STR_TUPLE("ringbuf"), RTLOGDEST_RINGBUF }, /* Must be 6th! */
+ { RT_STR_TUPLE("stdout"), RTLOGDEST_STDOUT },
+ { RT_STR_TUPLE("stderr"), RTLOGDEST_STDERR },
+ { RT_STR_TUPLE("debugger"), RTLOGDEST_DEBUGGER },
+ { RT_STR_TUPLE("com"), RTLOGDEST_COM },
+ { RT_STR_TUPLE("nodeny"), RTLOGDEST_F_NO_DENY },
+ { RT_STR_TUPLE("vmmrel"), RTLOGDEST_VMM_REL }, /* before vmm */
+ { RT_STR_TUPLE("vmm"), RTLOGDEST_VMM },
+ { RT_STR_TUPLE("user"), RTLOGDEST_USER },
+ /* The RTLOGDEST_FIXED_XXX flags are omitted on purpose. */
+};
+
+#ifdef IN_RING3
+/** Log rotation backoff table - millisecond sleep intervals.
+ * Important on Windows host, especially for VBoxSVC release logging. Only a
+ * medium term solution, until a proper fix for log file handling is available.
+ * 10 seconds total.
+ */
+static const uint32_t g_acMsLogBackoff[] =
+{ 10, 10, 10, 20, 50, 100, 200, 200, 200, 200, 500, 500, 500, 500, 1000, 1000, 1000, 1000, 1000, 1000, 1000 };
+#endif
+
+
+/**
+ * Locks the logger instance.
+ *
+ * @returns See RTSemSpinMutexRequest().
+ * @param pLoggerInt The logger instance.
+ */
+DECLINLINE(int) rtlogLock(PRTLOGGERINTERNAL pLoggerInt)
+{
+ AssertMsgReturn(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC, ("%#x != %#x\n", pLoggerInt->Core.u32Magic, RTLOGGER_MAGIC),
+ VERR_INVALID_MAGIC);
+ AssertMsgReturn(pLoggerInt->uRevision == RTLOGGERINTERNAL_REV, ("%#x != %#x\n", pLoggerInt->uRevision, RTLOGGERINTERNAL_REV),
+ VERR_LOG_REVISION_MISMATCH);
+ AssertMsgReturn(pLoggerInt->cbSelf == sizeof(*pLoggerInt), ("%#x != %#x\n", pLoggerInt->cbSelf, sizeof(*pLoggerInt)),
+ VERR_LOG_REVISION_MISMATCH);
+ if (pLoggerInt->hSpinMtx != NIL_RTSEMSPINMUTEX)
+ {
+ int rc = RTSemSpinMutexRequest(pLoggerInt->hSpinMtx);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unlocks the logger instance.
+ * @param pLoggerInt The logger instance.
+ */
+DECLINLINE(void) rtlogUnlock(PRTLOGGERINTERNAL pLoggerInt)
+{
+ if (pLoggerInt->hSpinMtx != NIL_RTSEMSPINMUTEX)
+ RTSemSpinMutexRelease(pLoggerInt->hSpinMtx);
+ return;
+}
+
+
+/*********************************************************************************************************************************
+* Logger Instance Management. *
+*********************************************************************************************************************************/
+
+/**
+ * Common worker for RTLogDefaultInstance and RTLogDefaultInstanceEx.
+ */
+DECL_NO_INLINE(static, PRTLOGGER) rtLogDefaultInstanceCreateNew(void)
+{
+ PRTLOGGER pRet = NULL;
+
+ /*
+ * It's soo easy to end up in a infinite recursion here when enabling 'all'
+ * the logging groups. So, only allow one thread to instantiate the default
+ * logger, muting other attempts at logging while it's being created.
+ */
+ static volatile bool s_fCreating = false;
+ if (ASMAtomicCmpXchgBool(&s_fCreating, true, false))
+ {
+ pRet = RTLogDefaultInit();
+ if (pRet)
+ {
+ bool fRc = ASMAtomicCmpXchgPtr(&g_pLogger, pRet, NULL);
+ if (!fRc)
+ {
+ RTLogDestroy(pRet);
+ pRet = g_pLogger;
+ }
+ }
+ ASMAtomicWriteBool(&s_fCreating, true);
+ }
+ return pRet;
+}
+
+
+/**
+ * Common worker for RTLogDefaultInstance and RTLogDefaultInstanceEx.
+ */
+DECL_FORCE_INLINE(PRTLOGGER) rtLogDefaultInstanceCommon(void)
+{
+ PRTLOGGER pRet;
+
+#ifdef IN_RING0
+ /*
+ * Check per thread loggers first.
+ */
+ if (g_cPerThreadLoggers)
+ {
+ const RTNATIVETHREAD Self = RTThreadNativeSelf();
+ int32_t i = RT_ELEMENTS(g_aPerThreadLoggers);
+ while (i-- > 0)
+ if (g_aPerThreadLoggers[i].NativeThread == Self)
+ return g_aPerThreadLoggers[i].pLogger;
+ }
+#endif /* IN_RING0 */
+
+ /*
+ * If no per thread logger, use the default one.
+ */
+ pRet = g_pLogger;
+ if (RT_LIKELY(pRet))
+ { /* likely */ }
+ else
+ pRet = rtLogDefaultInstanceCreateNew();
+ return pRet;
+}
+
+
+RTDECL(PRTLOGGER) RTLogDefaultInstance(void)
+{
+ return rtLogDefaultInstanceCommon();
+}
+RT_EXPORT_SYMBOL(RTLogDefaultInstance);
+
+
+/**
+ * Worker for RTLogDefaultInstanceEx, RTLogGetDefaultInstanceEx,
+ * RTLogRelGetDefaultInstanceEx and RTLogCheckGroupFlags.
+ */
+DECL_FORCE_INLINE(PRTLOGGERINTERNAL) rtLogCheckGroupFlagsWorker(PRTLOGGERINTERNAL pLoggerInt, uint32_t fFlagsAndGroup)
+{
+ if (pLoggerInt->fFlags & RTLOGFLAGS_DISABLED)
+ pLoggerInt = NULL;
+ else
+ {
+ uint32_t const fFlags = RT_LO_U16(fFlagsAndGroup);
+ uint16_t const iGroup = RT_HI_U16(fFlagsAndGroup);
+ if ( iGroup != UINT16_MAX
+ && ( (pLoggerInt->afGroups[iGroup < pLoggerInt->cGroups ? iGroup : 0] & (fFlags | RTLOGGRPFLAGS_ENABLED))
+ != (fFlags | RTLOGGRPFLAGS_ENABLED)))
+ pLoggerInt = NULL;
+ }
+ return pLoggerInt;
+}
+
+
+RTDECL(PRTLOGGER) RTLogDefaultInstanceEx(uint32_t fFlagsAndGroup)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)rtLogDefaultInstanceCommon();
+ if (pLoggerInt)
+ pLoggerInt = rtLogCheckGroupFlagsWorker(pLoggerInt, fFlagsAndGroup);
+ AssertCompileMemberOffset(RTLOGGERINTERNAL, Core, 0);
+ return (PRTLOGGER)pLoggerInt;
+}
+RT_EXPORT_SYMBOL(RTLogDefaultInstanceEx);
+
+
+/**
+ * Common worker for RTLogGetDefaultInstance and RTLogGetDefaultInstanceEx.
+ */
+DECL_FORCE_INLINE(PRTLOGGER) rtLogGetDefaultInstanceCommon(void)
+{
+#ifdef IN_RING0
+ /*
+ * Check per thread loggers first.
+ */
+ if (g_cPerThreadLoggers)
+ {
+ const RTNATIVETHREAD Self = RTThreadNativeSelf();
+ int32_t i = RT_ELEMENTS(g_aPerThreadLoggers);
+ while (i-- > 0)
+ if (g_aPerThreadLoggers[i].NativeThread == Self)
+ return g_aPerThreadLoggers[i].pLogger;
+ }
+#endif /* IN_RING0 */
+
+ return g_pLogger;
+}
+
+
+RTDECL(PRTLOGGER) RTLogGetDefaultInstance(void)
+{
+ return rtLogGetDefaultInstanceCommon();
+}
+RT_EXPORT_SYMBOL(RTLogGetDefaultInstance);
+
+
+RTDECL(PRTLOGGER) RTLogGetDefaultInstanceEx(uint32_t fFlagsAndGroup)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)rtLogGetDefaultInstanceCommon();
+ if (pLoggerInt)
+ pLoggerInt = rtLogCheckGroupFlagsWorker(pLoggerInt, fFlagsAndGroup);
+ AssertCompileMemberOffset(RTLOGGERINTERNAL, Core, 0);
+ return (PRTLOGGER)pLoggerInt;
+}
+RT_EXPORT_SYMBOL(RTLogGetDefaultInstanceEx);
+
+
+RTDECL(PRTLOGGER) RTLogSetDefaultInstance(PRTLOGGER pLogger)
+{
+#if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+ /* Set the pointers for emulating "weak symbols" the first time we're
+ called with something useful: */
+ if (pLogger != NULL && g_pfnRTLogGetDefaultInstanceEx == NULL)
+ {
+ g_pfnRTLogGetDefaultInstance = RTLogGetDefaultInstance;
+ g_pfnRTLogGetDefaultInstanceEx = RTLogGetDefaultInstanceEx;
+ }
+#endif
+ return ASMAtomicXchgPtrT(&g_pLogger, pLogger, PRTLOGGER);
+}
+RT_EXPORT_SYMBOL(RTLogSetDefaultInstance);
+
+
+#ifdef IN_RING0
+/**
+ * Changes the default logger instance for the current thread.
+ *
+ * @returns IPRT status code.
+ * @param pLogger The logger instance. Pass NULL for deregistration.
+ * @param uKey Associated key for cleanup purposes. If pLogger is NULL,
+ * all instances with this key will be deregistered. So in
+ * order to only deregister the instance associated with the
+ * current thread use 0.
+ */
+RTR0DECL(int) RTLogSetDefaultInstanceThread(PRTLOGGER pLogger, uintptr_t uKey)
+{
+ int rc;
+ RTNATIVETHREAD Self = RTThreadNativeSelf();
+ if (pLogger)
+ {
+ int32_t i;
+ unsigned j;
+
+ AssertReturn(pLogger->u32Magic == RTLOGGER_MAGIC, VERR_INVALID_MAGIC);
+
+ /*
+ * Iterate the table to see if there is already an entry for this thread.
+ */
+ i = RT_ELEMENTS(g_aPerThreadLoggers);
+ while (i-- > 0)
+ if (g_aPerThreadLoggers[i].NativeThread == Self)
+ {
+ ASMAtomicWritePtr((void * volatile *)&g_aPerThreadLoggers[i].uKey, (void *)uKey);
+ g_aPerThreadLoggers[i].pLogger = pLogger;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Allocate a new table entry.
+ */
+ i = ASMAtomicIncS32(&g_cPerThreadLoggers);
+ if (i > (int32_t)RT_ELEMENTS(g_aPerThreadLoggers))
+ {
+ ASMAtomicDecS32(&g_cPerThreadLoggers);
+ return VERR_BUFFER_OVERFLOW; /* horrible error code! */
+ }
+
+ for (j = 0; j < 10; j++)
+ {
+ i = RT_ELEMENTS(g_aPerThreadLoggers);
+ while (i-- > 0)
+ {
+ AssertCompile(sizeof(RTNATIVETHREAD) == sizeof(void*));
+ if ( g_aPerThreadLoggers[i].NativeThread == NIL_RTNATIVETHREAD
+ && ASMAtomicCmpXchgPtr((void * volatile *)&g_aPerThreadLoggers[i].NativeThread, (void *)Self, (void *)NIL_RTNATIVETHREAD))
+ {
+ ASMAtomicWritePtr((void * volatile *)&g_aPerThreadLoggers[i].uKey, (void *)uKey);
+ ASMAtomicWritePtr(&g_aPerThreadLoggers[i].pLogger, pLogger);
+ return VINF_SUCCESS;
+ }
+ }
+ }
+
+ ASMAtomicDecS32(&g_cPerThreadLoggers);
+ rc = VERR_INTERNAL_ERROR;
+ }
+ else
+ {
+ /*
+ * Search the array for the current thread.
+ */
+ int32_t i = RT_ELEMENTS(g_aPerThreadLoggers);
+ while (i-- > 0)
+ if ( g_aPerThreadLoggers[i].NativeThread == Self
+ || g_aPerThreadLoggers[i].uKey == uKey)
+ {
+ ASMAtomicWriteNullPtr((void * volatile *)&g_aPerThreadLoggers[i].uKey);
+ ASMAtomicWriteNullPtr(&g_aPerThreadLoggers[i].pLogger);
+ ASMAtomicWriteHandle(&g_aPerThreadLoggers[i].NativeThread, NIL_RTNATIVETHREAD);
+ ASMAtomicDecS32(&g_cPerThreadLoggers);
+ }
+
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogSetDefaultInstanceThread);
+#endif /* IN_RING0 */
+
+
+RTDECL(PRTLOGGER) RTLogRelGetDefaultInstance(void)
+{
+ return g_pRelLogger;
+}
+RT_EXPORT_SYMBOL(RTLogRelGetDefaultInstance);
+
+
+RTDECL(PRTLOGGER) RTLogRelGetDefaultInstanceEx(uint32_t fFlagsAndGroup)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)g_pRelLogger;
+ if (pLoggerInt)
+ pLoggerInt = rtLogCheckGroupFlagsWorker(pLoggerInt, fFlagsAndGroup);
+ return (PRTLOGGER)pLoggerInt;
+}
+RT_EXPORT_SYMBOL(RTLogRelGetDefaultInstanceEx);
+
+
+RTDECL(PRTLOGGER) RTLogRelSetDefaultInstance(PRTLOGGER pLogger)
+{
+#if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+ /* Set the pointers for emulating "weak symbols" the first time we're
+ called with something useful: */
+ if (pLogger != NULL && g_pfnRTLogRelGetDefaultInstanceEx == NULL)
+ {
+ g_pfnRTLogRelGetDefaultInstance = RTLogRelGetDefaultInstance;
+ g_pfnRTLogRelGetDefaultInstanceEx = RTLogRelGetDefaultInstanceEx;
+ }
+#endif
+ return ASMAtomicXchgPtrT(&g_pRelLogger, pLogger, PRTLOGGER);
+}
+RT_EXPORT_SYMBOL(RTLogRelSetDefaultInstance);
+
+
+RTDECL(PRTLOGGER) RTLogCheckGroupFlags(PRTLOGGER pLogger, uint32_t fFlagsAndGroup)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ if (pLoggerInt)
+ pLoggerInt = rtLogCheckGroupFlagsWorker(pLoggerInt, fFlagsAndGroup);
+ return (PRTLOGGER)pLoggerInt;
+}
+RT_EXPORT_SYMBOL(RTLogCheckGroupFlags);
+
+
+/*********************************************************************************************************************************
+* Default file I/O interface *
+*********************************************************************************************************************************/
+
+#ifdef IN_RING3
+static DECLCALLBACK(int) rtLogOutputIfDefOpen(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename, uint32_t fFlags)
+{
+ RT_NOREF(pIf);
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pvUser;
+
+ return RTFileOpen(&pLoggerInt->hFile, pszFilename, fFlags);
+}
+
+
+static DECLCALLBACK(int) rtLogOutputIfDefClose(PCRTLOGOUTPUTIF pIf, void *pvUser)
+{
+ RT_NOREF(pIf);
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pvUser;
+
+ int rc = VINF_SUCCESS;
+ if (pLoggerInt->hFile != NIL_RTFILE)
+ rc = RTFileClose(pLoggerInt->hFile);
+
+ pLoggerInt->hFile = NIL_RTFILE;
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtLogOutputIfDefDelete(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilename)
+{
+ RT_NOREF(pIf, pvUser);
+ return RTFileDelete(pszFilename);
+}
+
+
+static DECLCALLBACK(int) rtLogOutputIfDefRename(PCRTLOGOUTPUTIF pIf, void *pvUser, const char *pszFilenameOld,
+ const char *pszFilenameNew, uint32_t fFlags)
+{
+ RT_NOREF(pIf, pvUser);
+ return RTFileRename(pszFilenameOld, pszFilenameNew, fFlags);
+}
+
+
+static DECLCALLBACK(int) rtLogOutputIfDefQuerySize(PCRTLOGOUTPUTIF pIf, void *pvUser, uint64_t *pcbSize)
+{
+ RT_NOREF(pIf);
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pvUser;
+
+ if (pLoggerInt->hFile != NIL_RTFILE)
+ return RTFileQuerySize(pLoggerInt->hFile, pcbSize);
+
+ *pcbSize = 0;
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtLogOutputIfDefWrite(PCRTLOGOUTPUTIF pIf, void *pvUser, const void *pvBuf,
+ size_t cbWrite, size_t *pcbWritten)
+{
+ RT_NOREF(pIf);
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pvUser;
+
+ if (pLoggerInt->hFile != NIL_RTFILE)
+ return RTFileWrite(pLoggerInt->hFile, pvBuf, cbWrite, pcbWritten);
+
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtLogOutputIfDefFlush(PCRTLOGOUTPUTIF pIf, void *pvUser)
+{
+ RT_NOREF(pIf);
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pvUser;
+
+ if (pLoggerInt->hFile != NIL_RTFILE)
+ return RTFileFlush(pLoggerInt->hFile);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The default file output interface.
+ */
+static const RTLOGOUTPUTIF g_LogOutputIfDef =
+{
+ rtLogOutputIfDefOpen,
+ rtLogOutputIfDefClose,
+ rtLogOutputIfDefDelete,
+ rtLogOutputIfDefRename,
+ rtLogOutputIfDefQuerySize,
+ rtLogOutputIfDefWrite,
+ rtLogOutputIfDefFlush
+};
+#endif
+
+
+/*********************************************************************************************************************************
+* Ring Buffer *
+*********************************************************************************************************************************/
+
+/**
+ * Adjusts the ring buffer.
+ *
+ * @returns IPRT status code.
+ * @param pLoggerInt The logger instance.
+ * @param cbNewSize The new ring buffer size (0 == default).
+ * @param fForce Whether to do this even if the logger instance hasn't
+ * really been fully created yet (i.e. during RTLogCreate).
+ */
+static int rtLogRingBufAdjust(PRTLOGGERINTERNAL pLoggerInt, uint32_t cbNewSize, bool fForce)
+{
+ /*
+ * If this is early logger init, don't do anything.
+ */
+ if (!pLoggerInt->fCreated && !fForce)
+ return VINF_SUCCESS;
+
+ /*
+ * Lock the logger and make the necessary changes.
+ */
+ int rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbNewSize == 0)
+ cbNewSize = RTLOG_RINGBUF_DEFAULT_SIZE;
+ if ( pLoggerInt->cbRingBuf != cbNewSize
+ || !pLoggerInt->pchRingBufCur)
+ {
+ uintptr_t offOld = pLoggerInt->pchRingBufCur - pLoggerInt->pszRingBuf;
+ if (offOld < sizeof(RTLOG_RINGBUF_EYE_CATCHER))
+ offOld = sizeof(RTLOG_RINGBUF_EYE_CATCHER);
+ else if (offOld >= cbNewSize)
+ {
+ memmove(pLoggerInt->pszRingBuf, &pLoggerInt->pszRingBuf[offOld - cbNewSize], cbNewSize);
+ offOld = sizeof(RTLOG_RINGBUF_EYE_CATCHER);
+ }
+
+ void *pvNew = RTMemRealloc(pLoggerInt->pchRingBufCur, cbNewSize);
+ if (pvNew)
+ {
+ pLoggerInt->pszRingBuf = (char *)pvNew;
+ pLoggerInt->pchRingBufCur = (char *)pvNew + offOld;
+ pLoggerInt->cbRingBuf = cbNewSize;
+ memcpy(pvNew, RTLOG_RINGBUF_EYE_CATCHER, sizeof(RTLOG_RINGBUF_EYE_CATCHER));
+ memcpy((char *)pvNew + cbNewSize - sizeof(RTLOG_RINGBUF_EYE_CATCHER_END),
+ RTLOG_RINGBUF_EYE_CATCHER_END, sizeof(RTLOG_RINGBUF_EYE_CATCHER_END));
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ rtlogUnlock(pLoggerInt);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Writes text to the ring buffer.
+ *
+ * @param pInt The internal logger data structure.
+ * @param pachText The text to write.
+ * @param cchText The number of chars (bytes) to write.
+ */
+static void rtLogRingBufWrite(PRTLOGGERINTERNAL pInt, const char *pachText, size_t cchText)
+{
+ /*
+ * Get the ring buffer data, adjusting it to only describe the writable
+ * part of the buffer.
+ */
+ char * const pchStart = &pInt->pszRingBuf[sizeof(RTLOG_RINGBUF_EYE_CATCHER)];
+ size_t const cchBuf = pInt->cbRingBuf - sizeof(RTLOG_RINGBUF_EYE_CATCHER) - sizeof(RTLOG_RINGBUF_EYE_CATCHER_END);
+ char *pchCur = pInt->pchRingBufCur;
+ size_t cchLeft = pchCur - pchStart;
+ if (RT_LIKELY(cchLeft < cchBuf))
+ cchLeft = cchBuf - cchLeft;
+ else
+ {
+ /* May happen in ring-0 where a thread or two went ahead without getting the lock. */
+ pchCur = pchStart;
+ cchLeft = cchBuf;
+ }
+ Assert(cchBuf < pInt->cbRingBuf);
+
+ if (cchText < cchLeft)
+ {
+ /*
+ * The text fits in the remaining space.
+ */
+ memcpy(pchCur, pachText, cchText);
+ pchCur[cchText] = '\0';
+ pInt->pchRingBufCur = &pchCur[cchText];
+ pInt->cbRingBufUnflushed += cchText;
+ }
+ else
+ {
+ /*
+ * The text wraps around. Taking the simple but inefficient approach
+ * to input texts that are longer than the ring buffer since that
+ * is unlikely to the be a frequent case.
+ */
+ /* Fill to the end of the buffer. */
+ memcpy(pchCur, pachText, cchLeft);
+ pachText += cchLeft;
+ cchText -= cchLeft;
+ pInt->cbRingBufUnflushed += cchLeft;
+ pInt->pchRingBufCur = pchStart;
+
+ /* Ring buffer overflows (the plainly inefficient bit). */
+ while (cchText >= cchBuf)
+ {
+ memcpy(pchStart, pachText, cchBuf);
+ pachText += cchBuf;
+ cchText -= cchBuf;
+ pInt->cbRingBufUnflushed += cchBuf;
+ }
+
+ /* The final bit, if any. */
+ if (cchText > 0)
+ {
+ memcpy(pchStart, pachText, cchText);
+ pInt->cbRingBufUnflushed += cchText;
+ }
+ pchStart[cchText] = '\0';
+ pInt->pchRingBufCur = &pchStart[cchText];
+ }
+}
+
+
+/**
+ * Flushes the ring buffer to all the other log destinations.
+ *
+ * @param pLoggerInt The logger instance which ring buffer should be flushed.
+ */
+static void rtLogRingBufFlush(PRTLOGGERINTERNAL pLoggerInt)
+{
+ const char *pszPreamble;
+ size_t cchPreamble;
+ const char *pszFirst;
+ size_t cchFirst;
+ const char *pszSecond;
+ size_t cchSecond;
+
+ /*
+ * Get the ring buffer data, adjusting it to only describe the writable
+ * part of the buffer.
+ */
+ uint64_t cchUnflushed = pLoggerInt->cbRingBufUnflushed;
+ char * const pszBuf = &pLoggerInt->pszRingBuf[sizeof(RTLOG_RINGBUF_EYE_CATCHER)];
+ size_t const cchBuf = pLoggerInt->cbRingBuf - sizeof(RTLOG_RINGBUF_EYE_CATCHER) - sizeof(RTLOG_RINGBUF_EYE_CATCHER_END);
+ size_t offCur = pLoggerInt->pchRingBufCur - pszBuf;
+ size_t cchAfter;
+ if (RT_LIKELY(offCur < cchBuf))
+ cchAfter = cchBuf - offCur;
+ else /* May happen in ring-0 where a thread or two went ahead without getting the lock. */
+ {
+ offCur = 0;
+ cchAfter = cchBuf;
+ }
+
+ pLoggerInt->cbRingBufUnflushed = 0;
+
+ /*
+ * Figure out whether there are one or two segments that needs writing,
+ * making the last segment is terminated. (The first is always
+ * terminated because of the eye-catcher at the end of the buffer.)
+ */
+ if (cchUnflushed == 0)
+ return;
+ pszBuf[offCur] = '\0';
+ if (cchUnflushed >= cchBuf)
+ {
+ pszFirst = &pszBuf[offCur + 1];
+ cchFirst = cchAfter ? cchAfter - 1 : 0;
+ pszSecond = pszBuf;
+ cchSecond = offCur;
+ pszPreamble = "\n*FLUSH RING BUF*\n";
+ cchPreamble = sizeof("\n*FLUSH RING BUF*\n") - 1;
+ }
+ else if ((size_t)cchUnflushed <= offCur)
+ {
+ cchFirst = (size_t)cchUnflushed;
+ pszFirst = &pszBuf[offCur - cchFirst];
+ pszSecond = "";
+ cchSecond = 0;
+ pszPreamble = "";
+ cchPreamble = 0;
+ }
+ else
+ {
+ cchFirst = (size_t)cchUnflushed - offCur;
+ pszFirst = &pszBuf[cchBuf - cchFirst];
+ pszSecond = pszBuf;
+ cchSecond = offCur;
+ pszPreamble = "";
+ cchPreamble = 0;
+ }
+
+ /*
+ * Write the ring buffer to all other destiations.
+ */
+ if (pLoggerInt->fDestFlags & RTLOGDEST_USER)
+ {
+ if (cchPreamble)
+ RTLogWriteUser(pszPreamble, cchPreamble);
+ if (cchFirst)
+ RTLogWriteUser(pszFirst, cchFirst);
+ if (cchSecond)
+ RTLogWriteUser(pszSecond, cchSecond);
+ }
+
+# if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64)
+ if (pLoggerInt->fDestFlags & RTLOGDEST_VMM)
+ {
+ if (cchPreamble)
+ RTLogWriteVmm(pszPreamble, cchPreamble, false /*fReleaseLog*/);
+ if (cchFirst)
+ RTLogWriteVmm(pszFirst, cchFirst, false /*fReleaseLog*/);
+ if (cchSecond)
+ RTLogWriteVmm(pszSecond, cchSecond, false /*fReleaseLog*/);
+ }
+
+ if (pLoggerInt->fDestFlags & RTLOGDEST_VMM_REL)
+ {
+ if (cchPreamble)
+ RTLogWriteVmm(pszPreamble, cchPreamble, true /*fReleaseLog*/);
+ if (cchFirst)
+ RTLogWriteVmm(pszFirst, cchFirst, true /*fReleaseLog*/);
+ if (cchSecond)
+ RTLogWriteVmm(pszSecond, cchSecond, true /*fReleaseLog*/);
+ }
+# endif
+
+ if (pLoggerInt->fDestFlags & RTLOGDEST_DEBUGGER)
+ {
+ if (cchPreamble)
+ RTLogWriteDebugger(pszPreamble, cchPreamble);
+ if (cchFirst)
+ RTLogWriteDebugger(pszFirst, cchFirst);
+ if (cchSecond)
+ RTLogWriteDebugger(pszSecond, cchSecond);
+ }
+
+# ifdef IN_RING3
+ if (pLoggerInt->fDestFlags & RTLOGDEST_FILE)
+ {
+ if (pLoggerInt->fLogOpened)
+ {
+ if (cchPreamble)
+ pLoggerInt->pOutputIf->pfnWrite(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ pszPreamble, cchPreamble, NULL /*pcbWritten*/);
+ if (cchFirst)
+ pLoggerInt->pOutputIf->pfnWrite(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ pszFirst, cchFirst, NULL /*pcbWritten*/);
+ if (cchSecond)
+ pLoggerInt->pOutputIf->pfnWrite(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ pszSecond, cchSecond, NULL /*pcbWritten*/);
+ if (pLoggerInt->fFlags & RTLOGFLAGS_FLUSH)
+ pLoggerInt->pOutputIf->pfnFlush(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser);
+ }
+ if (pLoggerInt->cHistory)
+ pLoggerInt->cbHistoryFileWritten += cchFirst + cchSecond;
+ }
+# endif
+
+ if (pLoggerInt->fDestFlags & RTLOGDEST_STDOUT)
+ {
+ if (cchPreamble)
+ RTLogWriteStdOut(pszPreamble, cchPreamble);
+ if (cchFirst)
+ RTLogWriteStdOut(pszFirst, cchFirst);
+ if (cchSecond)
+ RTLogWriteStdOut(pszSecond, cchSecond);
+ }
+
+ if (pLoggerInt->fDestFlags & RTLOGDEST_STDERR)
+ {
+ if (cchPreamble)
+ RTLogWriteStdErr(pszPreamble, cchPreamble);
+ if (cchFirst)
+ RTLogWriteStdErr(pszFirst, cchFirst);
+ if (cchSecond)
+ RTLogWriteStdErr(pszSecond, cchSecond);
+ }
+
+# if defined(IN_RING0) && !defined(LOG_NO_COM)
+ if (pLoggerInt->fDestFlags & RTLOGDEST_COM)
+ {
+ if (cchPreamble)
+ RTLogWriteCom(pszPreamble, cchPreamble);
+ if (cchFirst)
+ RTLogWriteCom(pszFirst, cchFirst);
+ if (cchSecond)
+ RTLogWriteCom(pszSecond, cchSecond);
+ }
+# endif
+}
+
+
+/*********************************************************************************************************************************
+* Create, Destroy, Setup *
+*********************************************************************************************************************************/
+
+RTDECL(int) RTLogCreateExV(PRTLOGGER *ppLogger, const char *pszEnvVarBase, uint64_t fFlags, const char *pszGroupSettings,
+ uint32_t cGroups, const char * const *papszGroups, uint32_t cMaxEntriesPerGroup,
+ uint32_t cBufDescs, PRTLOGBUFFERDESC paBufDescs, uint32_t fDestFlags,
+ PFNRTLOGPHASE pfnPhase, uint32_t cHistory, uint64_t cbHistoryFileMax, uint32_t cSecsHistoryTimeSlot,
+ PCRTLOGOUTPUTIF pOutputIf, void *pvOutputIfUser,
+ PRTERRINFO pErrInfo, const char *pszFilenameFmt, va_list args)
+{
+ int rc;
+ size_t cbLogger;
+ size_t offBuffers;
+ PRTLOGGERINTERNAL pLoggerInt;
+ uint32_t i;
+
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(ppLogger, VERR_INVALID_POINTER);
+ *ppLogger = NULL;
+ if (cGroups)
+ {
+ AssertPtrReturn(papszGroups, VERR_INVALID_POINTER);
+ AssertReturn(cGroups < _8K, VERR_OUT_OF_RANGE);
+ }
+ AssertMsgReturn(cHistory < _1M, ("%#x", cHistory), VERR_OUT_OF_RANGE);
+ AssertReturn(cBufDescs <= 128, VERR_OUT_OF_RANGE);
+
+ /*
+ * Calculate the logger size.
+ */
+ AssertCompileSize(RTLOGGER, 32);
+ cbLogger = RT_UOFFSETOF_DYN(RTLOGGERINTERNAL, afGroups[cGroups]);
+ if (fFlags & RTLOGFLAGS_RESTRICT_GROUPS)
+ cbLogger += cGroups * sizeof(uint32_t);
+ if (cBufDescs == 0)
+ {
+ /* Allocate one buffer descriptor and a default sized buffer. */
+ cbLogger = RT_ALIGN_Z(cbLogger, RTLOG_BUFFER_ALIGN);
+ offBuffers = cbLogger;
+ cbLogger += RT_ALIGN_Z(sizeof(paBufDescs[0]), RTLOG_BUFFER_ALIGN) + RTLOG_BUFFER_DEFAULT_SIZE;
+ }
+ else
+ {
+ /* Caller-supplied buffer descriptors. If pchBuf is NULL, we have to allocate the buffers. */
+ AssertPtrReturn(paBufDescs, VERR_INVALID_POINTER);
+ if (paBufDescs[0].pchBuf != NULL)
+ offBuffers = 0;
+ else
+ {
+ cbLogger = RT_ALIGN_Z(cbLogger, RTLOG_BUFFER_ALIGN);
+ offBuffers = cbLogger;
+ }
+
+ for (i = 0; i < cBufDescs; i++)
+ {
+ AssertReturn(paBufDescs[i].u32Magic == RTLOGBUFFERDESC_MAGIC, VERR_INVALID_MAGIC);
+ AssertReturn(paBufDescs[i].uReserved == 0, VERR_INVALID_PARAMETER);
+ AssertMsgReturn(paBufDescs[i].cbBuf >= _1K && paBufDescs[i].cbBuf <= _64M,
+ ("paBufDesc[%u].cbBuf=%#x\n", i, paBufDescs[i].cbBuf), VERR_OUT_OF_RANGE);
+ AssertReturn(paBufDescs[i].offBuf == 0, VERR_INVALID_PARAMETER);
+ if (offBuffers != 0)
+ {
+ cbLogger += RT_ALIGN_Z(paBufDescs[i].cbBuf, RTLOG_BUFFER_ALIGN);
+ AssertReturn(paBufDescs[i].pchBuf == NULL, VERR_INVALID_PARAMETER);
+ AssertReturn(paBufDescs[i].pAux == NULL, VERR_INVALID_PARAMETER);
+ }
+ else
+ {
+ AssertPtrReturn(paBufDescs[i].pchBuf, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(paBufDescs[i].pAux, VERR_INVALID_POINTER);
+ }
+ }
+ }
+
+ /*
+ * Allocate a logger instance.
+ */
+ pLoggerInt = (PRTLOGGERINTERNAL)RTMemAllocZVarTag(cbLogger, "may-leak:log-instance");
+ if (pLoggerInt)
+ {
+# if defined(RT_ARCH_X86) && !defined(LOG_USE_C99)
+ uint8_t *pu8Code;
+# endif
+ pLoggerInt->Core.u32Magic = RTLOGGER_MAGIC;
+ pLoggerInt->cGroups = cGroups;
+ pLoggerInt->fFlags = fFlags;
+ pLoggerInt->fDestFlags = fDestFlags;
+ pLoggerInt->uRevision = RTLOGGERINTERNAL_REV;
+ pLoggerInt->cbSelf = sizeof(RTLOGGERINTERNAL);
+ pLoggerInt->hSpinMtx = NIL_RTSEMSPINMUTEX;
+ pLoggerInt->pfnFlush = NULL;
+ pLoggerInt->pfnPrefix = NULL;
+ pLoggerInt->pvPrefixUserArg = NULL;
+ pLoggerInt->fPendingPrefix = true;
+ pLoggerInt->fCreated = false;
+ pLoggerInt->nsR0ProgramStart = 0;
+ RT_ZERO(pLoggerInt->szR0ThreadName);
+ pLoggerInt->cMaxGroups = cGroups;
+ pLoggerInt->papszGroups = papszGroups;
+ if (fFlags & RTLOGFLAGS_RESTRICT_GROUPS)
+ pLoggerInt->pacEntriesPerGroup = &pLoggerInt->afGroups[cGroups];
+ else
+ pLoggerInt->pacEntriesPerGroup = NULL;
+ pLoggerInt->cMaxEntriesPerGroup = cMaxEntriesPerGroup ? cMaxEntriesPerGroup : UINT32_MAX;
+# ifdef IN_RING3
+ pLoggerInt->pfnPhase = pfnPhase;
+ pLoggerInt->hFile = NIL_RTFILE;
+ pLoggerInt->fLogOpened = false;
+ pLoggerInt->cHistory = cHistory;
+ if (cbHistoryFileMax == 0)
+ pLoggerInt->cbHistoryFileMax = UINT64_MAX;
+ else
+ pLoggerInt->cbHistoryFileMax = cbHistoryFileMax;
+ if (cSecsHistoryTimeSlot == 0)
+ pLoggerInt->cSecsHistoryTimeSlot = UINT32_MAX;
+ else
+ pLoggerInt->cSecsHistoryTimeSlot = cSecsHistoryTimeSlot;
+
+ if (pOutputIf)
+ {
+ pLoggerInt->pOutputIf = pOutputIf;
+ pLoggerInt->pvOutputIfUser = pvOutputIfUser;
+ }
+ else
+ {
+ /* Use the default interface for output logging. */
+ pLoggerInt->pOutputIf = &g_LogOutputIfDef;
+ pLoggerInt->pvOutputIfUser = pLoggerInt;
+ }
+
+# else /* !IN_RING3 */
+ RT_NOREF_PV(pfnPhase); RT_NOREF_PV(cHistory); RT_NOREF_PV(cbHistoryFileMax); RT_NOREF_PV(cSecsHistoryTimeSlot);
+ RT_NOREF_PV(pOutputIf); RT_NOREF_PV(pvOutputIfUser);
+# endif /* !IN_RING3 */
+ if (pszGroupSettings)
+ RTLogGroupSettings(&pLoggerInt->Core, pszGroupSettings);
+
+ /*
+ * Buffer descriptors.
+ */
+ if (!offBuffers)
+ {
+ /* Caller-supplied descriptors: */
+ pLoggerInt->cBufDescs = cBufDescs;
+ pLoggerInt->paBufDescs = paBufDescs;
+ }
+ else if (cBufDescs)
+ {
+ /* Caller-supplied descriptors, but we allocate the actual buffers: */
+ pLoggerInt->cBufDescs = cBufDescs;
+ pLoggerInt->paBufDescs = paBufDescs;
+ for (i = 0; i < cBufDescs; i++)
+ {
+ paBufDescs[i].pchBuf = (char *)pLoggerInt + offBuffers;
+ offBuffers = RT_ALIGN_Z(offBuffers + paBufDescs[i].cbBuf, RTLOG_BUFFER_ALIGN);
+ }
+ Assert(offBuffers == cbLogger);
+ }
+ else
+ {
+ /* One descriptor with a default sized buffer. */
+ pLoggerInt->cBufDescs = cBufDescs = 1;
+ pLoggerInt->paBufDescs = paBufDescs = (PRTLOGBUFFERDESC)((char *)(char *)pLoggerInt + offBuffers);
+ offBuffers = RT_ALIGN_Z(offBuffers + sizeof(paBufDescs[0]) * cBufDescs, RTLOG_BUFFER_ALIGN);
+ for (i = 0; i < cBufDescs; i++)
+ {
+ paBufDescs[i].u32Magic = RTLOGBUFFERDESC_MAGIC;
+ paBufDescs[i].uReserved = 0;
+ paBufDescs[i].cbBuf = RTLOG_BUFFER_DEFAULT_SIZE;
+ paBufDescs[i].offBuf = 0;
+ paBufDescs[i].pAux = NULL;
+ paBufDescs[i].pchBuf = (char *)pLoggerInt + offBuffers;
+ offBuffers = RT_ALIGN_Z(offBuffers + RTLOG_BUFFER_DEFAULT_SIZE, RTLOG_BUFFER_ALIGN);
+ }
+ Assert(offBuffers == cbLogger);
+ }
+ pLoggerInt->pBufDesc = paBufDescs;
+ pLoggerInt->idxBufDesc = 0;
+
+# if defined(RT_ARCH_X86) && !defined(LOG_USE_C99) && 0 /* retired */
+ /*
+ * Emit wrapper code.
+ */
+ pu8Code = (uint8_t *)RTMemExecAlloc(64);
+ if (pu8Code)
+ {
+ pLoggerInt->Core.pfnLogger = *(PFNRTLOGGER *)&pu8Code;
+ *pu8Code++ = 0x68; /* push imm32 */
+ *(void **)pu8Code = &pLoggerInt->Core;
+ pu8Code += sizeof(void *);
+ *pu8Code++ = 0xe8; /* call rel32 */
+ *(uint32_t *)pu8Code = (uintptr_t)RTLogLogger - ((uintptr_t)pu8Code + sizeof(uint32_t));
+ pu8Code += sizeof(uint32_t);
+ *pu8Code++ = 0x8d; /* lea esp, [esp + 4] */
+ *pu8Code++ = 0x64;
+ *pu8Code++ = 0x24;
+ *pu8Code++ = 0x04;
+ *pu8Code++ = 0xc3; /* ret near */
+ AssertMsg((uintptr_t)pu8Code - (uintptr_t)pLoggerInt->Core.pfnLogger <= 64,
+ ("Wrapper assembly is too big! %d bytes\n", (uintptr_t)pu8Code - (uintptr_t)pLoggerInt->Core.pfnLogger));
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ rc = VERR_NO_MEMORY;
+# ifdef RT_OS_LINUX
+ /* Most probably SELinux causing trouble since the larger RTMemAlloc succeeded. */
+ RTErrInfoSet(pErrInfo, rc, N_("mmap(PROT_WRITE | PROT_EXEC) failed -- SELinux?"));
+# endif
+ }
+ if (RT_SUCCESS(rc))
+# endif /* X86 wrapper code */
+ {
+# ifdef IN_RING3 /* files and env.vars. are only accessible when in R3 at the present time. */
+ /*
+ * Format the filename.
+ */
+ if (pszFilenameFmt)
+ {
+ /** @todo validate the length, fail on overflow. */
+ RTStrPrintfV(pLoggerInt->szFilename, sizeof(pLoggerInt->szFilename), pszFilenameFmt, args);
+ if (pLoggerInt->szFilename[0])
+ pLoggerInt->fDestFlags |= RTLOGDEST_FILE;
+ }
+
+ /*
+ * Parse the environment variables.
+ */
+ if (pszEnvVarBase)
+ {
+ /* make temp copy of environment variable base. */
+ size_t cchEnvVarBase = strlen(pszEnvVarBase);
+ char *pszEnvVar = (char *)alloca(cchEnvVarBase + 16);
+ memcpy(pszEnvVar, pszEnvVarBase, cchEnvVarBase);
+
+ /*
+ * Destination.
+ */
+ strcpy(pszEnvVar + cchEnvVarBase, "_DEST");
+ const char *pszValue = RTEnvGet(pszEnvVar);
+ if (pszValue)
+ RTLogDestinations(&pLoggerInt->Core, pszValue);
+
+ /*
+ * The flags.
+ */
+ strcpy(pszEnvVar + cchEnvVarBase, "_FLAGS");
+ pszValue = RTEnvGet(pszEnvVar);
+ if (pszValue)
+ RTLogFlags(&pLoggerInt->Core, pszValue);
+
+ /*
+ * The group settings.
+ */
+ pszEnvVar[cchEnvVarBase] = '\0';
+ pszValue = RTEnvGet(pszEnvVar);
+ if (pszValue)
+ RTLogGroupSettings(&pLoggerInt->Core, pszValue);
+
+ /*
+ * Group limit.
+ */
+ strcpy(pszEnvVar + cchEnvVarBase, "_MAX_PER_GROUP");
+ pszValue = RTEnvGet(pszEnvVar);
+ if (pszValue)
+ {
+ uint32_t cMax;
+ rc = RTStrToUInt32Full(pszValue, 0, &cMax);
+ if (RT_SUCCESS(rc))
+ pLoggerInt->cMaxEntriesPerGroup = cMax ? cMax : UINT32_MAX;
+ else
+ AssertMsgFailed(("Invalid group limit! %s=%s\n", pszEnvVar, pszValue));
+ }
+
+ }
+# else /* !IN_RING3 */
+ RT_NOREF_PV(pszEnvVarBase); RT_NOREF_PV(pszFilenameFmt); RT_NOREF_PV(args);
+# endif /* !IN_RING3 */
+
+ /*
+ * Open the destination(s).
+ */
+ rc = VINF_SUCCESS;
+ if ((pLoggerInt->fDestFlags & (RTLOGDEST_F_DELAY_FILE | RTLOGDEST_FILE)) == RTLOGDEST_F_DELAY_FILE)
+ pLoggerInt->fDestFlags &= ~RTLOGDEST_F_DELAY_FILE;
+# ifdef IN_RING3
+ if ((pLoggerInt->fDestFlags & (RTLOGDEST_FILE | RTLOGDEST_F_DELAY_FILE)) == RTLOGDEST_FILE)
+ rc = rtR3LogOpenFileDestination(pLoggerInt, pErrInfo);
+# endif
+
+ if ((pLoggerInt->fDestFlags & RTLOGDEST_RINGBUF) && RT_SUCCESS(rc))
+ rc = rtLogRingBufAdjust(pLoggerInt, pLoggerInt->cbRingBuf, true /*fForce*/);
+
+ /*
+ * Create mutex and check how much it counts when entering the lock
+ * so that we can report the values for RTLOGFLAGS_PREFIX_LOCK_COUNTS.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (!(fFlags & RTLOG_F_NO_LOCKING))
+ rc = RTSemSpinMutexCreate(&pLoggerInt->hSpinMtx, RTSEMSPINMUTEX_FLAGS_IRQ_SAFE);
+ if (RT_SUCCESS(rc))
+ {
+# ifdef IN_RING3 /** @todo do counters in ring-0 too? */
+ RTTHREAD Thread = RTThreadSelf();
+ if (Thread != NIL_RTTHREAD)
+ {
+ int32_t c = RTLockValidatorWriteLockGetCount(Thread);
+ RTSemSpinMutexRequest(pLoggerInt->hSpinMtx);
+ c = RTLockValidatorWriteLockGetCount(Thread) - c;
+ RTSemSpinMutexRelease(pLoggerInt->hSpinMtx);
+ ASMAtomicWriteU32(&g_cLoggerLockCount, c);
+ }
+
+ /* Use the callback to generate some initial log contents. */
+ AssertPtrNull(pLoggerInt->pfnPhase);
+ if (pLoggerInt->pfnPhase)
+ pLoggerInt->pfnPhase(&pLoggerInt->Core, RTLOGPHASE_BEGIN, rtlogPhaseMsgNormal);
+# endif
+ pLoggerInt->fCreated = true;
+ *ppLogger = &pLoggerInt->Core;
+
+# if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+ /* Make sure the weak symbol emulation bits are ready before returning. */
+ if (!g_pfnRTLogLoggerExV)
+ g_pfnRTLogLoggerExV = RTLogLoggerExV;
+# endif
+ return VINF_SUCCESS;
+ }
+
+ RTErrInfoSet(pErrInfo, rc, N_("failed to create semaphore"));
+ }
+# ifdef IN_RING3
+ pLoggerInt->pOutputIf->pfnClose(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser);
+# endif
+# if defined(RT_ARCH_X86) && !defined(LOG_USE_C99) && 0 /* retired */
+ if (pLoggerInt->Core.pfnLogger)
+ {
+ RTMemExecFree(*(void **)&pLoggerInt->Core.pfnLogger, 64);
+ pLoggerInt->Core.pfnLogger = NULL;
+ }
+# endif
+ }
+ RTMemFree(pLoggerInt);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogCreateExV);
+
+
+RTDECL(int) RTLogCreate(PRTLOGGER *ppLogger, uint64_t fFlags, const char *pszGroupSettings,
+ const char *pszEnvVarBase, unsigned cGroups, const char * const * papszGroups,
+ uint32_t fDestFlags, const char *pszFilenameFmt, ...)
+{
+ va_list va;
+ int rc;
+
+ va_start(va, pszFilenameFmt);
+ rc = RTLogCreateExV(ppLogger, pszEnvVarBase, fFlags, pszGroupSettings, cGroups, papszGroups,
+ UINT32_MAX /*cMaxEntriesPerGroup*/,
+ 0 /*cBufDescs*/, NULL /*paBufDescs*/, fDestFlags,
+ NULL /*pfnPhase*/, 0 /*cHistory*/, 0 /*cbHistoryFileMax*/, 0 /*cSecsHistoryTimeSlot*/,
+ NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/,
+ NULL /*pErrInfo*/, pszFilenameFmt, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogCreate);
+
+
+RTDECL(int) RTLogDestroy(PRTLOGGER pLogger)
+{
+ int rc;
+ uint32_t iGroup;
+ RTSEMSPINMUTEX hSpinMtx;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+
+ /*
+ * Validate input.
+ */
+ if (!pLoggerInt)
+ return VINF_SUCCESS;
+ AssertPtrReturn(pLoggerInt, VERR_INVALID_POINTER);
+ AssertReturn(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC, VERR_INVALID_MAGIC);
+
+ /*
+ * Acquire logger instance sem and disable all logging. (paranoia)
+ */
+ rc = rtlogLock(pLoggerInt);
+ AssertMsgRCReturn(rc, ("%Rrc\n", rc), rc);
+
+ pLoggerInt->fFlags |= RTLOGFLAGS_DISABLED;
+ iGroup = pLoggerInt->cGroups;
+ while (iGroup-- > 0)
+ pLoggerInt->afGroups[iGroup] = 0;
+
+ /*
+ * Flush it.
+ */
+ rtlogFlush(pLoggerInt, false /*fNeedSpace*/);
+
+# ifdef IN_RING3
+ /*
+ * Add end of logging message.
+ */
+ if ( (pLoggerInt->fDestFlags & RTLOGDEST_FILE)
+ && pLoggerInt->fLogOpened)
+ pLoggerInt->pfnPhase(&pLoggerInt->Core, RTLOGPHASE_END, rtlogPhaseMsgLocked);
+
+ /*
+ * Close output stuffs.
+ */
+ if (pLoggerInt->fLogOpened)
+ {
+ int rc2 = pLoggerInt->pOutputIf->pfnClose(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ pLoggerInt->fLogOpened = false;
+ }
+# endif
+
+ /*
+ * Free the mutex, the wrapper and the instance memory.
+ */
+ hSpinMtx = pLoggerInt->hSpinMtx;
+ pLoggerInt->hSpinMtx = NIL_RTSEMSPINMUTEX;
+ if (hSpinMtx != NIL_RTSEMSPINMUTEX)
+ {
+ int rc2;
+ RTSemSpinMutexRelease(hSpinMtx);
+ rc2 = RTSemSpinMutexDestroy(hSpinMtx);
+ AssertRC(rc2);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+
+# if defined(RT_ARCH_X86) && !defined(LOG_USE_C99) && 0 /* retired */
+ if (pLoggerInt->Core.pfnLogger)
+ {
+ RTMemExecFree(*(void **)&pLoggerInt->Core.pfnLogger, 64);
+ pLoggerInt->Core.pfnLogger = NULL;
+ }
+# endif
+ RTMemFree(pLoggerInt);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogDestroy);
+
+
+RTDECL(int) RTLogSetCustomPrefixCallback(PRTLOGGER pLogger, PFNRTLOGPREFIX pfnCallback, void *pvUser)
+{
+ int rc;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+
+ /*
+ * Do the work.
+ */
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ pLoggerInt->pvPrefixUserArg = pvUser;
+ pLoggerInt->pfnPrefix = pfnCallback;
+ rtlogUnlock(pLoggerInt);
+ }
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogSetCustomPrefixCallback);
+
+
+RTDECL(int) RTLogSetFlushCallback(PRTLOGGER pLogger, PFNRTLOGFLUSH pfnFlush)
+{
+ int rc;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+
+ /*
+ * Do the work.
+ */
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ if (pLoggerInt->pfnFlush && pLoggerInt->pfnFlush != pfnFlush)
+ rc = VWRN_ALREADY_EXISTS;
+ pLoggerInt->pfnFlush = pfnFlush;
+ rtlogUnlock(pLoggerInt);
+ }
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogSetFlushCallback);
+
+
+/**
+ * Matches a group name with a pattern mask in an case insensitive manner (ASCII).
+ *
+ * @returns true if matching and *ppachMask set to the end of the pattern.
+ * @returns false if no match.
+ * @param pszGrp The group name.
+ * @param ppachMask Pointer to the pointer to the mask. Only wildcard supported is '*'.
+ * @param cchMask The length of the mask, including modifiers. The modifiers is why
+ * we update *ppachMask on match.
+ */
+static bool rtlogIsGroupMatching(const char *pszGrp, const char **ppachMask, size_t cchMask)
+{
+ const char *pachMask;
+
+ if (!pszGrp || !*pszGrp)
+ return false;
+ pachMask = *ppachMask;
+ for (;;)
+ {
+ if (RT_C_TO_LOWER(*pszGrp) != RT_C_TO_LOWER(*pachMask))
+ {
+ const char *pszTmp;
+
+ /*
+ * Check for wildcard and do a minimal match if found.
+ */
+ if (*pachMask != '*')
+ return false;
+
+ /* eat '*'s. */
+ do pachMask++;
+ while (--cchMask && *pachMask == '*');
+
+ /* is there more to match? */
+ if ( !cchMask
+ || *pachMask == '.'
+ || *pachMask == '=')
+ break; /* we're good */
+
+ /* do extremely minimal matching (fixme) */
+ pszTmp = strchr(pszGrp, RT_C_TO_LOWER(*pachMask));
+ if (!pszTmp)
+ pszTmp = strchr(pszGrp, RT_C_TO_UPPER(*pachMask));
+ if (!pszTmp)
+ return false;
+ pszGrp = pszTmp;
+ continue;
+ }
+
+ /* done? */
+ if (!*++pszGrp)
+ {
+ /* trailing wildcard is ok. */
+ do
+ {
+ pachMask++;
+ cchMask--;
+ } while (cchMask && *pachMask == '*');
+ if ( !cchMask
+ || *pachMask == '.'
+ || *pachMask == '=')
+ break; /* we're good */
+ return false;
+ }
+
+ if (!--cchMask)
+ return false;
+ pachMask++;
+ }
+
+ /* match */
+ *ppachMask = pachMask;
+ return true;
+}
+
+
+RTDECL(int) RTLogGroupSettings(PRTLOGGER pLogger, const char *pszValue)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+ Assert(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC);
+
+ /*
+ * Iterate the string.
+ */
+ while (*pszValue)
+ {
+ /*
+ * Skip prefixes (blanks, ;, + and -).
+ */
+ bool fEnabled = true;
+ char ch;
+ const char *pszStart;
+ unsigned i;
+ size_t cch;
+
+ while ((ch = *pszValue) == '+' || ch == '-' || ch == ' ' || ch == '\t' || ch == '\n' || ch == ';')
+ {
+ if (ch == '+' || ch == '-' || ch == ';')
+ fEnabled = ch != '-';
+ pszValue++;
+ }
+ if (!*pszValue)
+ break;
+
+ /*
+ * Find end.
+ */
+ pszStart = pszValue;
+ while ((ch = *pszValue) != '\0' && ch != '+' && ch != '-' && ch != ' ' && ch != '\t')
+ pszValue++;
+
+ /*
+ * Find the group (ascii case insensitive search).
+ * Special group 'all'.
+ */
+ cch = pszValue - pszStart;
+ if ( cch >= 3
+ && (pszStart[0] == 'a' || pszStart[0] == 'A')
+ && (pszStart[1] == 'l' || pszStart[1] == 'L')
+ && (pszStart[2] == 'l' || pszStart[2] == 'L')
+ && (cch == 3 || pszStart[3] == '.' || pszStart[3] == '='))
+ {
+ /*
+ * All.
+ */
+ unsigned fFlags = cch == 3
+ ? RTLOGGRPFLAGS_ENABLED | RTLOGGRPFLAGS_LEVEL_1
+ : rtlogGroupFlags(&pszStart[3]);
+ for (i = 0; i < pLoggerInt->cGroups; i++)
+ {
+ if (fEnabled)
+ pLoggerInt->afGroups[i] |= fFlags;
+ else
+ pLoggerInt->afGroups[i] &= ~fFlags;
+ }
+ }
+ else
+ {
+ /*
+ * Specific group(s).
+ */
+ for (i = 0; i < pLoggerInt->cGroups; i++)
+ {
+ const char *psz2 = (const char*)pszStart;
+ if (rtlogIsGroupMatching(pLoggerInt->papszGroups[i], &psz2, cch))
+ {
+ unsigned fFlags = RTLOGGRPFLAGS_ENABLED | RTLOGGRPFLAGS_LEVEL_1;
+ if (*psz2 == '.' || *psz2 == '=')
+ fFlags = rtlogGroupFlags(psz2);
+ if (fEnabled)
+ pLoggerInt->afGroups[i] |= fFlags;
+ else
+ pLoggerInt->afGroups[i] &= ~fFlags;
+ }
+ } /* for each group */
+ }
+
+ } /* parse specification */
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTLogGroupSettings);
+
+
+/**
+ * Interprets the group flags suffix.
+ *
+ * @returns Flags specified. (0 is possible!)
+ * @param psz Start of Suffix. (Either dot or equal sign.)
+ */
+static unsigned rtlogGroupFlags(const char *psz)
+{
+ unsigned fFlags = 0;
+
+ /*
+ * Literal flags.
+ */
+ while (*psz == '.')
+ {
+ static struct
+ {
+ const char *pszFlag; /* lowercase!! */
+ unsigned fFlag;
+ } aFlags[] =
+ {
+ { "eo", RTLOGGRPFLAGS_ENABLED },
+ { "enabledonly",RTLOGGRPFLAGS_ENABLED },
+ { "e", RTLOGGRPFLAGS_ENABLED | RTLOGGRPFLAGS_LEVEL_1 | RTLOGGRPFLAGS_WARN },
+ { "enabled", RTLOGGRPFLAGS_ENABLED | RTLOGGRPFLAGS_LEVEL_1 | RTLOGGRPFLAGS_WARN },
+ { "l1", RTLOGGRPFLAGS_LEVEL_1 },
+ { "level1", RTLOGGRPFLAGS_LEVEL_1 },
+ { "l", RTLOGGRPFLAGS_LEVEL_2 },
+ { "l2", RTLOGGRPFLAGS_LEVEL_2 },
+ { "level2", RTLOGGRPFLAGS_LEVEL_2 },
+ { "l3", RTLOGGRPFLAGS_LEVEL_3 },
+ { "level3", RTLOGGRPFLAGS_LEVEL_3 },
+ { "l4", RTLOGGRPFLAGS_LEVEL_4 },
+ { "level4", RTLOGGRPFLAGS_LEVEL_4 },
+ { "l5", RTLOGGRPFLAGS_LEVEL_5 },
+ { "level5", RTLOGGRPFLAGS_LEVEL_5 },
+ { "l6", RTLOGGRPFLAGS_LEVEL_6 },
+ { "level6", RTLOGGRPFLAGS_LEVEL_6 },
+ { "l7", RTLOGGRPFLAGS_LEVEL_7 },
+ { "level7", RTLOGGRPFLAGS_LEVEL_7 },
+ { "l8", RTLOGGRPFLAGS_LEVEL_8 },
+ { "level8", RTLOGGRPFLAGS_LEVEL_8 },
+ { "l9", RTLOGGRPFLAGS_LEVEL_9 },
+ { "level9", RTLOGGRPFLAGS_LEVEL_9 },
+ { "l10", RTLOGGRPFLAGS_LEVEL_10 },
+ { "level10", RTLOGGRPFLAGS_LEVEL_10 },
+ { "l11", RTLOGGRPFLAGS_LEVEL_11 },
+ { "level11", RTLOGGRPFLAGS_LEVEL_11 },
+ { "l12", RTLOGGRPFLAGS_LEVEL_12 },
+ { "level12", RTLOGGRPFLAGS_LEVEL_12 },
+ { "f", RTLOGGRPFLAGS_FLOW },
+ { "flow", RTLOGGRPFLAGS_FLOW },
+ { "w", RTLOGGRPFLAGS_WARN },
+ { "warn", RTLOGGRPFLAGS_WARN },
+ { "warning", RTLOGGRPFLAGS_WARN },
+ { "restrict", RTLOGGRPFLAGS_RESTRICT },
+
+ };
+ unsigned i;
+ bool fFound = false;
+ psz++;
+ for (i = 0; i < RT_ELEMENTS(aFlags) && !fFound; i++)
+ {
+ const char *psz1 = aFlags[i].pszFlag;
+ const char *psz2 = psz;
+ while (*psz1 == RT_C_TO_LOWER(*psz2))
+ {
+ psz1++;
+ psz2++;
+ if (!*psz1)
+ {
+ if ( (*psz2 >= 'a' && *psz2 <= 'z')
+ || (*psz2 >= 'A' && *psz2 <= 'Z')
+ || (*psz2 >= '0' && *psz2 <= '9') )
+ break;
+ fFlags |= aFlags[i].fFlag;
+ fFound = true;
+ psz = psz2;
+ break;
+ }
+ } /* strincmp */
+ } /* for each flags */
+ AssertMsg(fFound, ("%.15s...", psz));
+ }
+
+ /*
+ * Flag value.
+ */
+ if (*psz == '=')
+ {
+ psz++;
+ if (*psz == '~')
+ fFlags = ~RTStrToInt32(psz + 1);
+ else
+ fFlags = RTStrToInt32(psz);
+ }
+
+ return fFlags;
+}
+
+
+/**
+ * Helper for RTLogGetGroupSettings.
+ */
+static int rtLogGetGroupSettingsAddOne(const char *pszName, uint32_t fGroup, char **ppszBuf, size_t *pcchBuf, bool *pfNotFirst)
+{
+#define APPEND_PSZ(psz,cch) do { memcpy(*ppszBuf, (psz), (cch)); *ppszBuf += (cch); *pcchBuf -= (cch); } while (0)
+#define APPEND_SZ(sz) APPEND_PSZ(sz, sizeof(sz) - 1)
+#define APPEND_CH(ch) do { **ppszBuf = (ch); *ppszBuf += 1; *pcchBuf -= 1; } while (0)
+
+ /*
+ * Add the name.
+ */
+ size_t cchName = strlen(pszName);
+ if (cchName + 1 + *pfNotFirst > *pcchBuf)
+ return VERR_BUFFER_OVERFLOW;
+ if (*pfNotFirst)
+ APPEND_CH(' ');
+ else
+ *pfNotFirst = true;
+ APPEND_PSZ(pszName, cchName);
+
+ /*
+ * Only generate mnemonics for the simple+common bits.
+ */
+ if (fGroup == (RTLOGGRPFLAGS_ENABLED | RTLOGGRPFLAGS_LEVEL_1))
+ /* nothing */;
+ else if ( fGroup == (RTLOGGRPFLAGS_ENABLED | RTLOGGRPFLAGS_LEVEL_1 | RTLOGGRPFLAGS_LEVEL_2 | RTLOGGRPFLAGS_FLOW)
+ && *pcchBuf >= sizeof(".e.l.f"))
+ APPEND_SZ(".e.l.f");
+ else if ( fGroup == (RTLOGGRPFLAGS_ENABLED | RTLOGGRPFLAGS_LEVEL_1 | RTLOGGRPFLAGS_FLOW)
+ && *pcchBuf >= sizeof(".e.f"))
+ APPEND_SZ(".e.f");
+ else if (*pcchBuf >= 1 + 10 + 1)
+ {
+ size_t cch;
+ APPEND_CH('=');
+ cch = RTStrFormatNumber(*ppszBuf, fGroup, 16, 0, 0, RTSTR_F_SPECIAL | RTSTR_F_32BIT);
+ *ppszBuf += cch;
+ *pcchBuf -= cch;
+ }
+ else
+ return VERR_BUFFER_OVERFLOW;
+
+#undef APPEND_PSZ
+#undef APPEND_SZ
+#undef APPEND_CH
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLogQueryGroupSettings(PRTLOGGER pLogger, char *pszBuf, size_t cchBuf)
+{
+ bool fNotFirst = false;
+ int rc = VINF_SUCCESS;
+ uint32_t cGroups;
+ uint32_t fGroup;
+ uint32_t i;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+ Assert(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC);
+ Assert(cchBuf);
+
+ /*
+ * Check if all are the same.
+ */
+ cGroups = pLoggerInt->cGroups;
+ fGroup = pLoggerInt->afGroups[0];
+ for (i = 1; i < cGroups; i++)
+ if (pLoggerInt->afGroups[i] != fGroup)
+ break;
+ if (i >= cGroups)
+ rc = rtLogGetGroupSettingsAddOne("all", fGroup, &pszBuf, &cchBuf, &fNotFirst);
+ else
+ {
+
+ /*
+ * Iterate all the groups and print all that are enabled.
+ */
+ for (i = 0; i < cGroups; i++)
+ {
+ fGroup = pLoggerInt->afGroups[i];
+ if (fGroup)
+ {
+ const char *pszName = pLoggerInt->papszGroups[i];
+ if (pszName)
+ {
+ rc = rtLogGetGroupSettingsAddOne(pszName, fGroup, &pszBuf, &cchBuf, &fNotFirst);
+ if (rc)
+ break;
+ }
+ }
+ }
+ }
+
+ *pszBuf = '\0';
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogQueryGroupSettings);
+
+
+RTDECL(int) RTLogFlags(PRTLOGGER pLogger, const char *pszValue)
+{
+ int rc = VINF_SUCCESS;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+ Assert(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC);
+
+ /*
+ * Iterate the string.
+ */
+ while (*pszValue)
+ {
+ /* check no prefix. */
+ bool fNo = false;
+ char ch;
+ unsigned i;
+
+ /* skip blanks. */
+ while (RT_C_IS_SPACE(*pszValue))
+ pszValue++;
+ if (!*pszValue)
+ return rc;
+
+ while ((ch = *pszValue) != '\0')
+ {
+ if (ch == 'n' && pszValue[1] == 'o')
+ {
+ pszValue += 2;
+ fNo = !fNo;
+ }
+ else if (ch == '+')
+ {
+ pszValue++;
+ fNo = true;
+ }
+ else if (ch == '-' || ch == '!' || ch == '~')
+ {
+ pszValue++;
+ fNo = !fNo;
+ }
+ else
+ break;
+ }
+
+ /* instruction. */
+ for (i = 0; i < RT_ELEMENTS(g_aLogFlags); i++)
+ {
+ if (!strncmp(pszValue, g_aLogFlags[i].pszInstr, g_aLogFlags[i].cchInstr))
+ {
+ if (!(g_aLogFlags[i].fFixedDest & pLoggerInt->fDestFlags))
+ {
+ if (fNo == g_aLogFlags[i].fInverted)
+ pLoggerInt->fFlags |= g_aLogFlags[i].fFlag;
+ else
+ pLoggerInt->fFlags &= ~g_aLogFlags[i].fFlag;
+ }
+ pszValue += g_aLogFlags[i].cchInstr;
+ break;
+ }
+ }
+
+ /* unknown instruction? */
+ if (i >= RT_ELEMENTS(g_aLogFlags))
+ {
+ AssertMsgFailed(("Invalid flags! unknown instruction %.20s\n", pszValue));
+ pszValue++;
+ }
+
+ /* skip blanks and delimiters. */
+ while (RT_C_IS_SPACE(*pszValue) || *pszValue == ';')
+ pszValue++;
+ } /* while more environment variable value left */
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogFlags);
+
+
+RTDECL(bool) RTLogSetBuffering(PRTLOGGER pLogger, bool fBuffered)
+{
+ int rc;
+ bool fOld = false;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, false);
+
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ fOld = !!(pLoggerInt->fFlags & RTLOGFLAGS_BUFFERED);
+ if (fBuffered)
+ pLoggerInt->fFlags |= RTLOGFLAGS_BUFFERED;
+ else
+ pLoggerInt->fFlags &= ~RTLOGFLAGS_BUFFERED;
+ rtlogUnlock(pLoggerInt);
+ }
+
+ return fOld;
+}
+RT_EXPORT_SYMBOL(RTLogSetBuffering);
+
+
+RTDECL(uint32_t) RTLogSetGroupLimit(PRTLOGGER pLogger, uint32_t cMaxEntriesPerGroup)
+{
+ int rc;
+ uint32_t cOld = UINT32_MAX;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, UINT32_MAX);
+
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ cOld = pLoggerInt->cMaxEntriesPerGroup;
+ pLoggerInt->cMaxEntriesPerGroup = cMaxEntriesPerGroup;
+ rtlogUnlock(pLoggerInt);
+ }
+
+ return cOld;
+}
+RT_EXPORT_SYMBOL(RTLogSetGroupLimit);
+
+
+#ifdef IN_RING0
+
+RTR0DECL(int) RTLogSetR0ThreadNameV(PRTLOGGER pLogger, const char *pszNameFmt, va_list va)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ int rc;
+ if (pLoggerInt)
+ {
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ ssize_t cch = RTStrPrintf2V(pLoggerInt->szR0ThreadName, sizeof(pLoggerInt->szR0ThreadName), pszNameFmt, va);
+ rtlogUnlock(pLoggerInt);
+ rc = cch > 0 ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogSetR0ThreadNameV);
+
+
+RTR0DECL(int) RTLogSetR0ProgramStart(PRTLOGGER pLogger, uint64_t nsStart)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ int rc;
+ if (pLoggerInt)
+ {
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ pLoggerInt->nsR0ProgramStart = nsStart;
+ rtlogUnlock(pLoggerInt);
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogSetR0ProgramStart);
+
+#endif /* IN_RING0 */
+
+RTDECL(uint64_t) RTLogGetFlags(PRTLOGGER pLogger)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, UINT64_MAX);
+ Assert(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC);
+ return pLoggerInt->fFlags;
+}
+RT_EXPORT_SYMBOL(RTLogGetFlags);
+
+
+RTDECL(int) RTLogChangeFlags(PRTLOGGER pLogger, uint64_t fSet, uint64_t fClear)
+{
+ int rc;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ AssertReturn(!(fSet & ~RTLOG_F_VALID_MASK), VERR_INVALID_FLAGS);
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+
+ /*
+ * Make the changes.
+ */
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ pLoggerInt->fFlags &= ~fClear;
+ pLoggerInt->fFlags |= fSet;
+ rtlogUnlock(pLoggerInt);
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogChangeFlags);
+
+
+RTDECL(int) RTLogQueryFlags(PRTLOGGER pLogger, char *pszBuf, size_t cchBuf)
+{
+ bool fNotFirst = false;
+ int rc = VINF_SUCCESS;
+ uint32_t fFlags;
+ unsigned i;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+
+ Assert(cchBuf);
+ *pszBuf = '\0';
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+ Assert(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC);
+
+ /*
+ * Add the flags in the list.
+ */
+ fFlags = pLoggerInt->fFlags;
+ for (i = 0; i < RT_ELEMENTS(g_aLogFlags); i++)
+ if ( !g_aLogFlags[i].fInverted
+ ? (g_aLogFlags[i].fFlag & fFlags)
+ : !(g_aLogFlags[i].fFlag & fFlags))
+ {
+ size_t cchInstr = g_aLogFlags[i].cchInstr;
+ if (cchInstr + fNotFirst + 1 > cchBuf)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ if (fNotFirst)
+ {
+ *pszBuf++ = ' ';
+ cchBuf--;
+ }
+ memcpy(pszBuf, g_aLogFlags[i].pszInstr, cchInstr);
+ pszBuf += cchInstr;
+ cchBuf -= cchInstr;
+ fNotFirst = true;
+ }
+ *pszBuf = '\0';
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogQueryFlags);
+
+
+/**
+ * Finds the end of a destination value.
+ *
+ * The value ends when we counter a ';' or a free standing word (space on both
+ * from the g_aLogDst table. (If this is problematic for someone, we could
+ * always do quoting and escaping.)
+ *
+ * @returns Value length in chars.
+ * @param pszValue The first char after '=' or ':'.
+ */
+static size_t rtLogDestFindValueLength(const char *pszValue)
+{
+ size_t off = 0;
+ char ch;
+ while ((ch = pszValue[off]) != '\0' && ch != ';')
+ {
+ if (!RT_C_IS_SPACE(ch))
+ off++;
+ else
+ {
+ unsigned i;
+ size_t cchThusFar = off;
+ do
+ off++;
+ while ((ch = pszValue[off]) != '\0' && RT_C_IS_SPACE(ch));
+ if (ch == ';')
+ return cchThusFar;
+
+ if (ch == 'n' && pszValue[off + 1] == 'o')
+ off += 2;
+ for (i = 0; i < RT_ELEMENTS(g_aLogDst); i++)
+ if (!strncmp(&pszValue[off], g_aLogDst[i].pszInstr, g_aLogDst[i].cchInstr))
+ {
+ ch = pszValue[off + g_aLogDst[i].cchInstr];
+ if (ch == '\0' || RT_C_IS_SPACE(ch) || ch == '=' || ch == ':' || ch == ';')
+ return cchThusFar;
+ }
+ }
+ }
+ return off;
+}
+
+
+RTDECL(int) RTLogDestinations(PRTLOGGER pLogger, char const *pszValue)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+ Assert(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC);
+ /** @todo locking? */
+
+ /*
+ * Do the parsing.
+ */
+ while (*pszValue)
+ {
+ bool fNo;
+ unsigned i;
+
+ /* skip blanks. */
+ while (RT_C_IS_SPACE(*pszValue))
+ pszValue++;
+ if (!*pszValue)
+ break;
+
+ /* check no prefix. */
+ fNo = false;
+ if ( pszValue[0] == 'n'
+ && pszValue[1] == 'o'
+ && ( pszValue[2] != 'd'
+ || pszValue[3] != 'e'
+ || pszValue[4] != 'n'
+ || pszValue[5] != 'y'))
+ {
+ fNo = true;
+ pszValue += 2;
+ }
+
+ /* instruction. */
+ for (i = 0; i < RT_ELEMENTS(g_aLogDst); i++)
+ {
+ if (!strncmp(pszValue, g_aLogDst[i].pszInstr, g_aLogDst[i].cchInstr))
+ {
+ if (!fNo)
+ pLoggerInt->fDestFlags |= g_aLogDst[i].fFlag;
+ else
+ pLoggerInt->fDestFlags &= ~g_aLogDst[i].fFlag;
+ pszValue += g_aLogDst[i].cchInstr;
+
+ /* check for value. */
+ while (RT_C_IS_SPACE(*pszValue))
+ pszValue++;
+ if (*pszValue == '=' || *pszValue == ':')
+ {
+ pszValue++;
+ size_t cch = rtLogDestFindValueLength(pszValue);
+ const char *pszEnd = pszValue + cch;
+
+# ifdef IN_RING3
+ char szTmp[sizeof(pLoggerInt->szFilename)];
+# else
+ char szTmp[32];
+# endif
+ if (0)
+ { /* nothing */ }
+# ifdef IN_RING3
+
+ /* log file name */
+ else if (i == 0 /* file */ && !fNo)
+ {
+ if (!(pLoggerInt->fDestFlags & RTLOGDEST_FIXED_FILE))
+ {
+ AssertReturn(cch < sizeof(pLoggerInt->szFilename), VERR_OUT_OF_RANGE);
+ memcpy(pLoggerInt->szFilename, pszValue, cch);
+ pLoggerInt->szFilename[cch] = '\0';
+ /** @todo reopen log file if pLoggerInt->fCreated is true ... */
+ }
+ }
+ /* log directory */
+ else if (i == 1 /* dir */ && !fNo)
+ {
+ if (!(pLoggerInt->fDestFlags & RTLOGDEST_FIXED_DIR))
+ {
+ const char *pszFile = RTPathFilename(pLoggerInt->szFilename);
+ size_t cchFile = pszFile ? strlen(pszFile) : 0;
+ AssertReturn(cchFile + cch + 1 < sizeof(pLoggerInt->szFilename), VERR_OUT_OF_RANGE);
+ memcpy(szTmp, cchFile ? pszFile : "", cchFile + 1);
+
+ memcpy(pLoggerInt->szFilename, pszValue, cch);
+ pLoggerInt->szFilename[cch] = '\0';
+ RTPathStripTrailingSlash(pLoggerInt->szFilename);
+
+ cch = strlen(pLoggerInt->szFilename);
+ pLoggerInt->szFilename[cch++] = '/';
+ memcpy(&pLoggerInt->szFilename[cch], szTmp, cchFile);
+ pLoggerInt->szFilename[cch + cchFile] = '\0';
+ /** @todo reopen log file if pLoggerInt->fCreated is true ... */
+ }
+ }
+ else if (i == 2 /* history */)
+ {
+ if (!fNo)
+ {
+ uint32_t cHistory = 0;
+ int rc = RTStrCopyEx(szTmp, sizeof(szTmp), pszValue, cch);
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt32Full(szTmp, 0, &cHistory);
+ AssertMsgReturn(RT_SUCCESS(rc) && cHistory < _1M, ("Invalid history value %s (%Rrc)!\n", szTmp, rc), rc);
+ pLoggerInt->cHistory = cHistory;
+ }
+ else
+ pLoggerInt->cHistory = 0;
+ }
+ else if (i == 3 /* histsize */)
+ {
+ if (!fNo)
+ {
+ int rc = RTStrCopyEx(szTmp, sizeof(szTmp), pszValue, cch);
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt64Full(szTmp, 0, &pLoggerInt->cbHistoryFileMax);
+ AssertMsgRCReturn(rc, ("Invalid history file size value %s (%Rrc)!\n", szTmp, rc), rc);
+ if (pLoggerInt->cbHistoryFileMax == 0)
+ pLoggerInt->cbHistoryFileMax = UINT64_MAX;
+ }
+ else
+ pLoggerInt->cbHistoryFileMax = UINT64_MAX;
+ }
+ else if (i == 4 /* histtime */)
+ {
+ if (!fNo)
+ {
+ int rc = RTStrCopyEx(szTmp, sizeof(szTmp), pszValue, cch);
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt32Full(szTmp, 0, &pLoggerInt->cSecsHistoryTimeSlot);
+ AssertMsgRCReturn(rc, ("Invalid history time slot value %s (%Rrc)!\n", szTmp, rc), rc);
+ if (pLoggerInt->cSecsHistoryTimeSlot == 0)
+ pLoggerInt->cSecsHistoryTimeSlot = UINT32_MAX;
+ }
+ else
+ pLoggerInt->cSecsHistoryTimeSlot = UINT32_MAX;
+ }
+# endif /* IN_RING3 */
+ else if (i == 5 /* ringbuf */ && !fNo)
+ {
+ int rc = RTStrCopyEx(szTmp, sizeof(szTmp), pszValue, cch);
+ uint32_t cbRingBuf = 0;
+ if (RT_SUCCESS(rc))
+ rc = RTStrToUInt32Full(szTmp, 0, &cbRingBuf);
+ AssertMsgRCReturn(rc, ("Invalid ring buffer size value '%s' (%Rrc)!\n", szTmp, rc), rc);
+
+ if (cbRingBuf == 0)
+ cbRingBuf = RTLOG_RINGBUF_DEFAULT_SIZE;
+ else if (cbRingBuf < RTLOG_RINGBUF_MIN_SIZE)
+ cbRingBuf = RTLOG_RINGBUF_MIN_SIZE;
+ else if (cbRingBuf > RTLOG_RINGBUF_MAX_SIZE)
+ cbRingBuf = RTLOG_RINGBUF_MAX_SIZE;
+ else
+ cbRingBuf = RT_ALIGN_32(cbRingBuf, 64);
+ rc = rtLogRingBufAdjust(pLoggerInt, cbRingBuf, false /*fForce*/);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ AssertMsgFailedReturn(("Invalid destination value! %s%s doesn't take a value!\n",
+ fNo ? "no" : "", g_aLogDst[i].pszInstr),
+ VERR_INVALID_PARAMETER);
+
+ pszValue = pszEnd + (*pszEnd != '\0');
+ }
+ else if (i == 5 /* ringbuf */ && !fNo && !pLoggerInt->pszRingBuf)
+ {
+ int rc = rtLogRingBufAdjust(pLoggerInt, pLoggerInt->cbRingBuf, false /*fForce*/);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ break;
+ }
+ }
+
+ /* assert known instruction */
+ AssertMsgReturn(i < RT_ELEMENTS(g_aLogDst),
+ ("Invalid destination value! unknown instruction %.20s\n", pszValue),
+ VERR_INVALID_PARAMETER);
+
+ /* skip blanks and delimiters. */
+ while (RT_C_IS_SPACE(*pszValue) || *pszValue == ';')
+ pszValue++;
+ } /* while more environment variable value left */
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTLogDestinations);
+
+
+RTDECL(int) RTLogClearFileDelayFlag(PRTLOGGER pLogger, PRTERRINFO pErrInfo)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+
+ /*
+ * Do the work.
+ */
+ int rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ if (pLoggerInt->fDestFlags & RTLOGDEST_F_DELAY_FILE)
+ {
+ pLoggerInt->fDestFlags &= ~RTLOGDEST_F_DELAY_FILE;
+# ifdef IN_RING3
+ if ( pLoggerInt->fDestFlags & RTLOGDEST_FILE
+ && !pLoggerInt->fLogOpened)
+ {
+ rc = rtR3LogOpenFileDestination(pLoggerInt, pErrInfo);
+ if (RT_SUCCESS(rc))
+ rtlogFlush(pLoggerInt, false /*fNeedSpace*/);
+ }
+# endif
+ RT_NOREF(pErrInfo); /** @todo fix create API to use RTErrInfo */
+ }
+ rtlogUnlock(pLoggerInt);
+ }
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTLogClearFileDelayFlag);
+
+
+RTDECL(int) RTLogChangeDestinations(PRTLOGGER pLogger, uint32_t fSet, uint32_t fClear)
+{
+ int rc;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ AssertCompile((RTLOG_DST_VALID_MASK & RTLOG_DST_CHANGE_MASK) == RTLOG_DST_CHANGE_MASK);
+ AssertReturn(!(fSet & ~RTLOG_DST_CHANGE_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!(fClear & ~RTLOG_DST_CHANGE_MASK), VERR_INVALID_FLAGS);
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+
+ /*
+ * Make the changes.
+ */
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ pLoggerInt->fDestFlags &= ~fClear;
+ pLoggerInt->fDestFlags |= fSet;
+ rtlogUnlock(pLoggerInt);
+ }
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTLogChangeDestinations);
+
+
+RTDECL(uint32_t) RTLogGetDestinations(PRTLOGGER pLogger)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ if (!pLoggerInt)
+ {
+ pLoggerInt = (PRTLOGGERINTERNAL)RTLogDefaultInstance();
+ if (!pLoggerInt)
+ return UINT32_MAX;
+ }
+ return pLoggerInt->fDestFlags;
+}
+RT_EXPORT_SYMBOL(RTLogGetDestinations);
+
+
+RTDECL(int) RTLogQueryDestinations(PRTLOGGER pLogger, char *pszBuf, size_t cchBuf)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ bool fNotFirst = false;
+ int rc = VINF_SUCCESS;
+ uint32_t fDestFlags;
+ unsigned i;
+
+ AssertReturn(cchBuf, VERR_INVALID_PARAMETER);
+ *pszBuf = '\0';
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+ Assert(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC);
+
+ /*
+ * Add the flags in the list.
+ */
+ fDestFlags = pLoggerInt->fDestFlags;
+ for (i = 6; i < RT_ELEMENTS(g_aLogDst); i++)
+ if (g_aLogDst[i].fFlag & fDestFlags)
+ {
+ if (fNotFirst)
+ {
+ rc = RTStrCopyP(&pszBuf, &cchBuf, " ");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ rc = RTStrCopyP(&pszBuf, &cchBuf, g_aLogDst[i].pszInstr);
+ if (RT_FAILURE(rc))
+ return rc;
+ fNotFirst = true;
+ }
+
+ char szNum[32];
+
+# ifdef IN_RING3
+ /*
+ * Add the filename.
+ */
+ if (fDestFlags & RTLOGDEST_FILE)
+ {
+ rc = RTStrCopyP(&pszBuf, &cchBuf, fNotFirst ? " file=" : "file=");
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTStrCopyP(&pszBuf, &cchBuf, pLoggerInt->szFilename);
+ if (RT_FAILURE(rc))
+ return rc;
+ fNotFirst = true;
+
+ if (pLoggerInt->cHistory)
+ {
+ RTStrPrintf(szNum, sizeof(szNum), fNotFirst ? " history=%u" : "history=%u", pLoggerInt->cHistory);
+ rc = RTStrCopyP(&pszBuf, &cchBuf, szNum);
+ if (RT_FAILURE(rc))
+ return rc;
+ fNotFirst = true;
+ }
+ if (pLoggerInt->cbHistoryFileMax != UINT64_MAX)
+ {
+ RTStrPrintf(szNum, sizeof(szNum), fNotFirst ? " histsize=%llu" : "histsize=%llu", pLoggerInt->cbHistoryFileMax);
+ rc = RTStrCopyP(&pszBuf, &cchBuf, szNum);
+ if (RT_FAILURE(rc))
+ return rc;
+ fNotFirst = true;
+ }
+ if (pLoggerInt->cSecsHistoryTimeSlot != UINT32_MAX)
+ {
+ RTStrPrintf(szNum, sizeof(szNum), fNotFirst ? " histtime=%llu" : "histtime=%llu", pLoggerInt->cSecsHistoryTimeSlot);
+ rc = RTStrCopyP(&pszBuf, &cchBuf, szNum);
+ if (RT_FAILURE(rc))
+ return rc;
+ fNotFirst = true;
+ }
+ }
+# endif /* IN_RING3 */
+
+ /*
+ * Add the ring buffer.
+ */
+ if (fDestFlags & RTLOGDEST_RINGBUF)
+ {
+ if (pLoggerInt->cbRingBuf == RTLOG_RINGBUF_DEFAULT_SIZE)
+ rc = RTStrCopyP(&pszBuf, &cchBuf, fNotFirst ? " ringbuf" : "ringbuf");
+ else
+ {
+ RTStrPrintf(szNum, sizeof(szNum), fNotFirst ? " ringbuf=%#x" : "ringbuf=%#x", pLoggerInt->cbRingBuf);
+ rc = RTStrCopyP(&pszBuf, &cchBuf, szNum);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+ fNotFirst = true;
+ }
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTLogQueryDestinations);
+
+
+/**
+ * Helper for calculating the CRC32 of all the group names.
+ */
+static uint32_t rtLogCalcGroupNameCrc32(PRTLOGGERINTERNAL pLoggerInt)
+{
+ const char * const * const papszGroups = pLoggerInt->papszGroups;
+ uint32_t iGroup = pLoggerInt->cGroups;
+ uint32_t uCrc32 = RTCrc32Start();
+ while (iGroup-- > 0)
+ {
+ const char *pszGroup = papszGroups[iGroup];
+ uCrc32 = RTCrc32Process(uCrc32, pszGroup, strlen(pszGroup) + 1);
+ }
+ return RTCrc32Finish(uCrc32);
+}
+
+#ifdef IN_RING3
+
+/**
+ * Opens/creates the log file.
+ *
+ * @param pLoggerInt The logger instance to update. NULL is not allowed!
+ * @param pErrInfo Where to return extended error information.
+ * Optional.
+ */
+static int rtlogFileOpen(PRTLOGGERINTERNAL pLoggerInt, PRTERRINFO pErrInfo)
+{
+ uint32_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_NONE;
+ if (pLoggerInt->fFlags & RTLOGFLAGS_APPEND)
+ fOpen |= RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND;
+ else
+ {
+ pLoggerInt->pOutputIf->pfnDelete(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ pLoggerInt->szFilename);
+ fOpen |= RTFILE_O_CREATE;
+ }
+ if (pLoggerInt->fFlags & RTLOGFLAGS_WRITE_THROUGH)
+ fOpen |= RTFILE_O_WRITE_THROUGH;
+ if (pLoggerInt->fDestFlags & RTLOGDEST_F_NO_DENY)
+ fOpen = (fOpen & ~RTFILE_O_DENY_NONE) | RTFILE_O_DENY_NOT_DELETE;
+
+ unsigned cBackoff = 0;
+ int rc = pLoggerInt->pOutputIf->pfnOpen(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ pLoggerInt->szFilename, fOpen);
+ while ( ( rc == VERR_SHARING_VIOLATION
+ || (rc == VERR_ALREADY_EXISTS && !(pLoggerInt->fFlags & RTLOGFLAGS_APPEND)))
+ && cBackoff < RT_ELEMENTS(g_acMsLogBackoff))
+ {
+ RTThreadSleep(g_acMsLogBackoff[cBackoff++]);
+ if (!(pLoggerInt->fFlags & RTLOGFLAGS_APPEND))
+ pLoggerInt->pOutputIf->pfnDelete(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ pLoggerInt->szFilename);
+ rc = pLoggerInt->pOutputIf->pfnOpen(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ pLoggerInt->szFilename, fOpen);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pLoggerInt->fLogOpened = true;
+
+ rc = pLoggerInt->pOutputIf->pfnQuerySize(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ &pLoggerInt->cbHistoryFileWritten);
+ if (RT_FAILURE(rc))
+ {
+ /* Don't complain if this fails, assume the file is empty. */
+ pLoggerInt->cbHistoryFileWritten = 0;
+ rc = VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ pLoggerInt->fLogOpened = false;
+ RTErrInfoSetF(pErrInfo, rc, N_("could not open file '%s' (fOpen=%#x)"), pLoggerInt->szFilename, fOpen);
+ }
+ return rc;
+}
+
+
+/**
+ * Closes, rotates and opens the log files if necessary.
+ *
+ * Used by the rtlogFlush() function as well as RTLogCreateExV() by way of
+ * rtR3LogOpenFileDestination().
+ *
+ * @param pLoggerInt The logger instance to update. NULL is not allowed!
+ * @param uTimeSlot Current time slot (for tikme based rotation).
+ * @param fFirst Flag whether this is the beginning of logging, i.e.
+ * called from RTLogCreateExV. Prevents pfnPhase from
+ * being called.
+ * @param pErrInfo Where to return extended error information. Optional.
+ */
+static void rtlogRotate(PRTLOGGERINTERNAL pLoggerInt, uint32_t uTimeSlot, bool fFirst, PRTERRINFO pErrInfo)
+{
+ /* Suppress rotating empty log files simply because the time elapsed. */
+ if (RT_UNLIKELY(!pLoggerInt->cbHistoryFileWritten))
+ pLoggerInt->uHistoryTimeSlotStart = uTimeSlot;
+
+ /* Check rotation condition: file still small enough and not too old? */
+ if (RT_LIKELY( pLoggerInt->cbHistoryFileWritten < pLoggerInt->cbHistoryFileMax
+ && uTimeSlot == pLoggerInt->uHistoryTimeSlotStart))
+ return;
+
+ /*
+ * Save "disabled" log flag and make sure logging is disabled.
+ * The logging in the functions called during log file history
+ * rotation would cause severe trouble otherwise.
+ */
+ uint32_t const fSavedFlags = pLoggerInt->fFlags;
+ pLoggerInt->fFlags |= RTLOGFLAGS_DISABLED;
+
+ /*
+ * Disable log rotation temporarily, otherwise with extreme settings and
+ * chatty phase logging we could run into endless rotation.
+ */
+ uint32_t const cSavedHistory = pLoggerInt->cHistory;
+ pLoggerInt->cHistory = 0;
+
+ /*
+ * Close the old log file.
+ */
+ if (pLoggerInt->fLogOpened)
+ {
+ /* Use the callback to generate some final log contents, but only if
+ * this is a rotation with a fully set up logger. Leave the other case
+ * to the RTLogCreateExV function. */
+ if (pLoggerInt->pfnPhase && !fFirst)
+ {
+ uint32_t fODestFlags = pLoggerInt->fDestFlags;
+ pLoggerInt->fDestFlags &= RTLOGDEST_FILE;
+ pLoggerInt->pfnPhase(&pLoggerInt->Core, RTLOGPHASE_PREROTATE, rtlogPhaseMsgLocked);
+ pLoggerInt->fDestFlags = fODestFlags;
+ }
+
+ pLoggerInt->pOutputIf->pfnClose(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser);
+ }
+
+ if (cSavedHistory)
+ {
+ /*
+ * Rotate the log files.
+ */
+ for (uint32_t i = cSavedHistory - 1; i + 1 > 0; i--)
+ {
+ char szOldName[sizeof(pLoggerInt->szFilename) + 32];
+ if (i > 0)
+ RTStrPrintf(szOldName, sizeof(szOldName), "%s.%u", pLoggerInt->szFilename, i);
+ else
+ RTStrCopy(szOldName, sizeof(szOldName), pLoggerInt->szFilename);
+
+ char szNewName[sizeof(pLoggerInt->szFilename) + 32];
+ RTStrPrintf(szNewName, sizeof(szNewName), "%s.%u", pLoggerInt->szFilename, i + 1);
+
+ unsigned cBackoff = 0;
+ int rc = pLoggerInt->pOutputIf->pfnRename(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ szOldName, szNewName, RTFILEMOVE_FLAGS_REPLACE);
+ while ( rc == VERR_SHARING_VIOLATION
+ && cBackoff < RT_ELEMENTS(g_acMsLogBackoff))
+ {
+ RTThreadSleep(g_acMsLogBackoff[cBackoff++]);
+ rc = pLoggerInt->pOutputIf->pfnRename(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ szOldName, szNewName, RTFILEMOVE_FLAGS_REPLACE);
+ }
+
+ if (rc == VERR_FILE_NOT_FOUND)
+ pLoggerInt->pOutputIf->pfnDelete(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser, szNewName);
+ }
+
+ /*
+ * Delete excess log files.
+ */
+ for (uint32_t i = cSavedHistory + 1; ; i++)
+ {
+ char szExcessName[sizeof(pLoggerInt->szFilename) + 32];
+ RTStrPrintf(szExcessName, sizeof(szExcessName), "%s.%u", pLoggerInt->szFilename, i);
+ int rc = pLoggerInt->pOutputIf->pfnDelete(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser, szExcessName);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ /*
+ * Update logger state and create new log file.
+ */
+ pLoggerInt->cbHistoryFileWritten = 0;
+ pLoggerInt->uHistoryTimeSlotStart = uTimeSlot;
+ rtlogFileOpen(pLoggerInt, pErrInfo);
+
+ /*
+ * Use the callback to generate some initial log contents, but only if this
+ * is a rotation with a fully set up logger. Leave the other case to the
+ * RTLogCreateExV function.
+ */
+ if (pLoggerInt->pfnPhase && !fFirst)
+ {
+ uint32_t const fSavedDestFlags = pLoggerInt->fDestFlags;
+ pLoggerInt->fDestFlags &= RTLOGDEST_FILE;
+ pLoggerInt->pfnPhase(&pLoggerInt->Core, RTLOGPHASE_POSTROTATE, rtlogPhaseMsgLocked);
+ pLoggerInt->fDestFlags = fSavedDestFlags;
+ }
+
+ /* Restore saved values. */
+ pLoggerInt->cHistory = cSavedHistory;
+ pLoggerInt->fFlags = fSavedFlags;
+}
+
+
+/**
+ * Worker for RTLogCreateExV and RTLogClearFileDelayFlag.
+ *
+ * This will later be used to reopen the file by RTLogDestinations.
+ *
+ * @returns IPRT status code.
+ * @param pLoggerInt The logger.
+ * @param pErrInfo Where to return extended error information.
+ * Optional.
+ */
+static int rtR3LogOpenFileDestination(PRTLOGGERINTERNAL pLoggerInt, PRTERRINFO pErrInfo)
+{
+ int rc;
+ if (pLoggerInt->fFlags & RTLOGFLAGS_APPEND)
+ {
+ rc = rtlogFileOpen(pLoggerInt, pErrInfo);
+
+ /* Rotate in case of appending to a too big log file,
+ otherwise this simply doesn't do anything. */
+ rtlogRotate(pLoggerInt, 0, true /* fFirst */, pErrInfo);
+ }
+ else
+ {
+ /* Force rotation if it is configured. */
+ pLoggerInt->cbHistoryFileWritten = UINT64_MAX;
+ rtlogRotate(pLoggerInt, 0, true /* fFirst */, pErrInfo);
+
+ /* If the file is not open then rotation is not set up. */
+ if (!pLoggerInt->fLogOpened)
+ {
+ pLoggerInt->cbHistoryFileWritten = 0;
+ rc = rtlogFileOpen(pLoggerInt, pErrInfo);
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+#endif /* IN_RING3 */
+
+
+/*********************************************************************************************************************************
+* Bulk Reconfig & Logging for ring-0 EMT loggers. *
+*********************************************************************************************************************************/
+
+RTDECL(int) RTLogBulkUpdate(PRTLOGGER pLogger, uint64_t fFlags, uint32_t uGroupCrc32, uint32_t cGroups, uint32_t const *pafGroups)
+{
+ int rc;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+
+ /*
+ * Do the updating.
+ */
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ pLoggerInt->fFlags = fFlags;
+ if ( uGroupCrc32 == rtLogCalcGroupNameCrc32(pLoggerInt)
+ && pLoggerInt->cGroups == cGroups)
+ {
+ RT_BCOPY_UNFORTIFIED(pLoggerInt->afGroups, pafGroups, sizeof(pLoggerInt->afGroups[0]) * cGroups);
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_MISMATCH;
+
+ rtlogUnlock(pLoggerInt);
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogBulkUpdate);
+
+
+RTDECL(int) RTLogQueryBulk(PRTLOGGER pLogger, uint64_t *pfFlags, uint32_t *puGroupCrc32, uint32_t *pcGroups, uint32_t *pafGroups)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ uint32_t const cGroupsAlloc = *pcGroups;
+
+ *pfFlags = 0;
+ *puGroupCrc32 = 0;
+ *pcGroups = 0;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+ AssertReturn(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC, VERR_INVALID_MAGIC);
+
+ /*
+ * Get the data.
+ */
+ *pfFlags = pLoggerInt->fFlags;
+ *pcGroups = pLoggerInt->cGroups;
+ if (cGroupsAlloc >= pLoggerInt->cGroups)
+ {
+ memcpy(pafGroups, pLoggerInt->afGroups, sizeof(pLoggerInt->afGroups[0]) * pLoggerInt->cGroups);
+ *puGroupCrc32 = rtLogCalcGroupNameCrc32(pLoggerInt);
+ return VINF_SUCCESS;
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTLogQueryBulk);
+
+
+RTDECL(int) RTLogBulkWrite(PRTLOGGER pLogger, const char *pszBefore, const char *pch, size_t cch, const char *pszAfter)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+
+ /*
+ * Lock and validate it.
+ */
+ int rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ if (cch > 0)
+ {
+ /*
+ * Heading/marker.
+ */
+ if (pszBefore)
+ rtlogLoggerExFLocked(pLoggerInt, RTLOGGRPFLAGS_LEVEL_1, UINT32_MAX, "%s", pszBefore);
+
+ /*
+ * Do the copying.
+ */
+ do
+ {
+ PRTLOGBUFFERDESC const pBufDesc = pLoggerInt->pBufDesc;
+ char * const pchBuf = pBufDesc->pchBuf;
+ uint32_t const cbBuf = pBufDesc->cbBuf;
+ uint32_t offBuf = pBufDesc->offBuf;
+ if (cch + 1 < cbBuf - offBuf)
+ {
+ memcpy(&pchBuf[offBuf], pch, cch);
+ offBuf += (uint32_t)cch;
+ pchBuf[offBuf] = '\0';
+ pBufDesc->offBuf = offBuf;
+ if (pBufDesc->pAux)
+ pBufDesc->pAux->offBuf = offBuf;
+ if (!(pLoggerInt->fDestFlags & RTLOGFLAGS_BUFFERED))
+ rtlogFlush(pLoggerInt, false /*fNeedSpace*/);
+ break;
+ }
+
+ /* Not enough space. */
+ if (offBuf + 1 < cbBuf)
+ {
+ uint32_t cbToCopy = cbBuf - offBuf - 1;
+ memcpy(&pchBuf[offBuf], pch, cbToCopy);
+ offBuf += cbToCopy;
+ pchBuf[offBuf] = '\0';
+ pBufDesc->offBuf = offBuf;
+ if (pBufDesc->pAux)
+ pBufDesc->pAux->offBuf = offBuf;
+ pch += cbToCopy;
+ cch -= cbToCopy;
+ }
+
+ rtlogFlush(pLoggerInt, false /*fNeedSpace*/);
+ } while (cch > 0);
+
+ /*
+ * Footer/marker.
+ */
+ if (pszAfter)
+ rtlogLoggerExFLocked(pLoggerInt, RTLOGGRPFLAGS_LEVEL_1, UINT32_MAX, "%s", pszAfter);
+ }
+
+ rtlogUnlock(pLoggerInt);
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogBulkWrite);
+
+
+RTDECL(int) RTLogBulkNestedWrite(PRTLOGGER pLogger, const char *pch, size_t cch, const char *pszInfix)
+{
+ if (cch > 0)
+ {
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+
+ /*
+ * Lock and validate it.
+ */
+ int rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * If we've got an auxilary descriptor, check if the buffer was flushed.
+ */
+ PRTLOGBUFFERDESC pBufDesc = pLoggerInt->pBufDesc;
+ PRTLOGBUFFERAUXDESC pAuxDesc = pBufDesc->pAux;
+ if (!pAuxDesc || !pAuxDesc->fFlushedIndicator)
+ { /* likely, except maybe for ring-0 */ }
+ else
+ {
+ pAuxDesc->fFlushedIndicator = false;
+ pBufDesc->offBuf = 0;
+ }
+
+ /*
+ * Write the stuff.
+ */
+ RTLOGOUTPUTPREFIXEDARGS Args;
+ Args.pLoggerInt = pLoggerInt;
+ Args.fFlags = 0;
+ Args.iGroup = ~0U;
+ Args.pszInfix = pszInfix;
+ rtLogOutputPrefixed(&Args, pch, cch);
+ rtLogOutputPrefixed(&Args, pch, 0); /* termination call */
+
+ /*
+ * Maybe flush the buffer and update the auxiliary descriptor if there is one.
+ */
+ pBufDesc = pLoggerInt->pBufDesc; /* (the descriptor may have changed) */
+ if ( !(pLoggerInt->fFlags & RTLOGFLAGS_BUFFERED)
+ && pBufDesc->offBuf)
+ rtlogFlush(pLoggerInt, false /*fNeedSpace*/);
+ else
+ {
+ pAuxDesc = pBufDesc->pAux;
+ if (pAuxDesc)
+ pAuxDesc->offBuf = pBufDesc->offBuf;
+ }
+
+ rtlogUnlock(pLoggerInt);
+ }
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTLogBulkNestedWrite);
+
+
+/*********************************************************************************************************************************
+* Flushing *
+*********************************************************************************************************************************/
+
+RTDECL(int) RTLogFlush(PRTLOGGER pLogger)
+{
+ if (!pLogger)
+ {
+ pLogger = rtLogGetDefaultInstanceCommon(); /* Get it if it exists, do _not_ create one if it doesn't. */
+ if (!pLogger)
+ return VINF_LOG_NO_LOGGER;
+ }
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ Assert(pLoggerInt->Core.u32Magic == RTLOGGER_MAGIC);
+ AssertPtr(pLoggerInt->pBufDesc);
+ Assert(pLoggerInt->pBufDesc->u32Magic == RTLOGBUFFERDESC_MAGIC);
+
+ /*
+ * Acquire logger instance sem.
+ */
+ int rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Any thing to flush?
+ */
+ if ( pLoggerInt->pBufDesc->offBuf > 0
+ || (pLoggerInt->fDestFlags & RTLOGDEST_RINGBUF))
+ {
+ /*
+ * Call worker.
+ */
+ rtlogFlush(pLoggerInt, false /*fNeedSpace*/);
+
+ /*
+ * Since this is an explicit flush call, the ring buffer content should
+ * be flushed to the other destinations if active.
+ */
+ if ( (pLoggerInt->fDestFlags & RTLOGDEST_RINGBUF)
+ && pLoggerInt->pszRingBuf /* paranoia */)
+ rtLogRingBufFlush(pLoggerInt);
+ }
+
+ rtlogUnlock(pLoggerInt);
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogFlush);
+
+
+/**
+ * Writes the buffer to the given log device without checking for buffered
+ * data or anything.
+ *
+ * Used by the RTLogFlush() function.
+ *
+ * @param pLoggerInt The logger instance to write to. NULL is not allowed!
+ * @param fNeedSpace Set if the caller assumes space will be made available.
+ */
+static void rtlogFlush(PRTLOGGERINTERNAL pLoggerInt, bool fNeedSpace)
+{
+ PRTLOGBUFFERDESC pBufDesc = pLoggerInt->pBufDesc;
+ uint32_t cchToFlush = pBufDesc->offBuf;
+ char * pchToFlush = pBufDesc->pchBuf;
+ uint32_t const cbBuf = pBufDesc->cbBuf;
+ Assert(pBufDesc->u32Magic == RTLOGBUFFERDESC_MAGIC);
+
+ NOREF(fNeedSpace);
+ if (cchToFlush == 0)
+ return; /* nothing to flush. */
+
+ AssertPtrReturnVoid(pchToFlush);
+ AssertReturnVoid(cbBuf > 0);
+ AssertMsgStmt(cchToFlush < cbBuf, ("%#x vs %#x\n", cchToFlush, cbBuf), cchToFlush = cbBuf - 1);
+
+ /*
+ * If the ring buffer is active, the other destinations are only written
+ * to when the ring buffer is flushed by RTLogFlush().
+ */
+ if ( (pLoggerInt->fDestFlags & RTLOGDEST_RINGBUF)
+ && pLoggerInt->pszRingBuf /* paranoia */)
+ {
+ rtLogRingBufWrite(pLoggerInt, pchToFlush, cchToFlush);
+
+ /* empty the buffer. */
+ pBufDesc->offBuf = 0;
+ *pchToFlush = '\0';
+ }
+ /*
+ * In file delay mode, we ignore flush requests except when we're full
+ * and the caller really needs some scratch space to get work done.
+ */
+ else
+#ifdef IN_RING3
+ if (!(pLoggerInt->fDestFlags & RTLOGDEST_F_DELAY_FILE))
+#endif
+ {
+ /* Make sure the string is terminated. On Windows, RTLogWriteDebugger
+ will get upset if it isn't. */
+ pchToFlush[cchToFlush] = '\0';
+
+ if (pLoggerInt->fDestFlags & RTLOGDEST_USER)
+ RTLogWriteUser(pchToFlush, cchToFlush);
+
+#if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64)
+ if (pLoggerInt->fDestFlags & RTLOGDEST_VMM)
+ RTLogWriteVmm(pchToFlush, cchToFlush, false /*fReleaseLog*/);
+
+ if (pLoggerInt->fDestFlags & RTLOGDEST_VMM_REL)
+ RTLogWriteVmm(pchToFlush, cchToFlush, true /*fReleaseLog*/);
+#endif
+
+ if (pLoggerInt->fDestFlags & RTLOGDEST_DEBUGGER)
+ RTLogWriteDebugger(pchToFlush, cchToFlush);
+
+#ifdef IN_RING3
+ if ((pLoggerInt->fDestFlags & (RTLOGDEST_FILE | RTLOGDEST_RINGBUF)) == RTLOGDEST_FILE)
+ {
+ if (pLoggerInt->fLogOpened)
+ {
+ pLoggerInt->pOutputIf->pfnWrite(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser,
+ pchToFlush, cchToFlush, NULL /*pcbWritten*/);
+ if (pLoggerInt->fFlags & RTLOGFLAGS_FLUSH)
+ pLoggerInt->pOutputIf->pfnFlush(pLoggerInt->pOutputIf, pLoggerInt->pvOutputIfUser);
+ }
+ if (pLoggerInt->cHistory)
+ pLoggerInt->cbHistoryFileWritten += cchToFlush;
+ }
+#endif
+
+ if (pLoggerInt->fDestFlags & RTLOGDEST_STDOUT)
+ RTLogWriteStdOut(pchToFlush, cchToFlush);
+
+ if (pLoggerInt->fDestFlags & RTLOGDEST_STDERR)
+ RTLogWriteStdErr(pchToFlush, cchToFlush);
+
+#if (defined(IN_RING0) || defined(IN_RC)) && !defined(LOG_NO_COM)
+ if (pLoggerInt->fDestFlags & RTLOGDEST_COM)
+ RTLogWriteCom(pchToFlush, cchToFlush);
+#endif
+
+ if (pLoggerInt->pfnFlush)
+ {
+ /*
+ * We have a custom flush callback. Before calling it we must make
+ * sure the aux descriptor is up to date. When we get back, we may
+ * need to switch to the next buffer if the current is being flushed
+ * asynchronously. This of course requires there to be more than one
+ * buffer. (The custom flush callback is responsible for making sure
+ * the next buffer isn't being flushed before returning.)
+ */
+ if (pBufDesc->pAux)
+ pBufDesc->pAux->offBuf = cchToFlush;
+ if (!pLoggerInt->pfnFlush(&pLoggerInt->Core, pBufDesc))
+ {
+ /* advance to the next buffer */
+ Assert(pLoggerInt->cBufDescs > 1);
+ size_t idxBufDesc = pBufDesc - pLoggerInt->paBufDescs;
+ Assert(idxBufDesc < pLoggerInt->cBufDescs);
+ idxBufDesc = (idxBufDesc + 1) % pLoggerInt->cBufDescs;
+ pLoggerInt->idxBufDesc = (uint8_t)idxBufDesc;
+ pLoggerInt->pBufDesc = pBufDesc = &pLoggerInt->paBufDescs[idxBufDesc];
+ pchToFlush = pBufDesc->pchBuf;
+ }
+ }
+
+ /* Empty the buffer. */
+ pBufDesc->offBuf = 0;
+ if (pBufDesc->pAux)
+ pBufDesc->pAux->offBuf = 0;
+ *pchToFlush = '\0';
+
+#ifdef IN_RING3
+ /*
+ * Rotate the log file if configured. Must be done after everything is
+ * flushed, since this will also use logging/flushing to write the header
+ * and footer messages.
+ */
+ if ( pLoggerInt->cHistory > 0
+ && (pLoggerInt->fDestFlags & RTLOGDEST_FILE))
+ rtlogRotate(pLoggerInt, RTTimeProgramSecTS() / pLoggerInt->cSecsHistoryTimeSlot, false /*fFirst*/, NULL /*pErrInfo*/);
+#endif
+ }
+#ifdef IN_RING3
+ else
+ {
+ /*
+ * Delay file open but the caller really need some space. So, give him half a
+ * buffer and insert a message indicating that we've dropped output.
+ */
+ uint32_t offHalf = cbBuf / 2;
+ if (cchToFlush > offHalf)
+ {
+ static const char s_szDropMsgLf[] = "\n[DROP DROP DROP]\n";
+ static const char s_szDropMsgCrLf[] = "\r\n[DROP DROP DROP]\r\n";
+ if (!(pLoggerInt->fFlags & RTLOGFLAGS_USECRLF))
+ {
+ memcpy(&pchToFlush[offHalf], RT_STR_TUPLE(s_szDropMsgLf));
+ offHalf += sizeof(s_szDropMsgLf) - 1;
+ }
+ else
+ {
+ memcpy(&pchToFlush[offHalf], RT_STR_TUPLE(s_szDropMsgCrLf));
+ offHalf += sizeof(s_szDropMsgCrLf) - 1;
+ }
+ pBufDesc->offBuf = offHalf;
+ }
+ }
+#endif
+}
+
+
+/*********************************************************************************************************************************
+* Logger Core *
+*********************************************************************************************************************************/
+
+#ifdef IN_RING0
+
+/**
+ * For rtR0LogLoggerExFallbackOutput and rtR0LogLoggerExFallbackFlush.
+ */
+typedef struct RTR0LOGLOGGERFALLBACK
+{
+ /** The current scratch buffer offset. */
+ uint32_t offScratch;
+ /** The destination flags. */
+ uint32_t fDestFlags;
+ /** For ring buffer output. */
+ PRTLOGGERINTERNAL pInt;
+ /** The scratch buffer. */
+ char achScratch[80];
+} RTR0LOGLOGGERFALLBACK;
+/** Pointer to RTR0LOGLOGGERFALLBACK which is used by
+ * rtR0LogLoggerExFallbackOutput. */
+typedef RTR0LOGLOGGERFALLBACK *PRTR0LOGLOGGERFALLBACK;
+
+
+/**
+ * Flushes the fallback buffer.
+ *
+ * @param pThis The scratch buffer.
+ */
+static void rtR0LogLoggerExFallbackFlush(PRTR0LOGLOGGERFALLBACK pThis)
+{
+ if (!pThis->offScratch)
+ return;
+
+ if ( (pThis->fDestFlags & RTLOGDEST_RINGBUF)
+ && pThis->pInt
+ && pThis->pInt->pszRingBuf /* paranoia */)
+ rtLogRingBufWrite(pThis->pInt, pThis->achScratch, pThis->offScratch);
+ else
+ {
+ if (pThis->fDestFlags & RTLOGDEST_USER)
+ RTLogWriteUser(pThis->achScratch, pThis->offScratch);
+
+# if defined(RT_ARCH_X86) || defined(RT_ARCH_AMD64)
+ if (pThis->fDestFlags & RTLOGDEST_VMM)
+ RTLogWriteVmm(pThis->achScratch, pThis->offScratch, false /*fReleaseLog*/);
+
+ if (pThis->fDestFlags & RTLOGDEST_VMM_REL)
+ RTLogWriteVmm(pThis->achScratch, pThis->offScratch, true /*fReleaseLog*/);
+# endif
+
+ if (pThis->fDestFlags & RTLOGDEST_DEBUGGER)
+ RTLogWriteDebugger(pThis->achScratch, pThis->offScratch);
+
+ if (pThis->fDestFlags & RTLOGDEST_STDOUT)
+ RTLogWriteStdOut(pThis->achScratch, pThis->offScratch);
+
+ if (pThis->fDestFlags & RTLOGDEST_STDERR)
+ RTLogWriteStdErr(pThis->achScratch, pThis->offScratch);
+
+# ifndef LOG_NO_COM
+ if (pThis->fDestFlags & RTLOGDEST_COM)
+ RTLogWriteCom(pThis->achScratch, pThis->offScratch);
+# endif
+ }
+
+ /* empty the buffer. */
+ pThis->offScratch = 0;
+}
+
+
+/**
+ * Callback for RTLogFormatV used by rtR0LogLoggerExFallback.
+ * See PFNLOGOUTPUT() for details.
+ */
+static DECLCALLBACK(size_t) rtR0LogLoggerExFallbackOutput(void *pv, const char *pachChars, size_t cbChars)
+{
+ PRTR0LOGLOGGERFALLBACK pThis = (PRTR0LOGLOGGERFALLBACK)pv;
+ if (cbChars)
+ {
+ size_t cbRet = 0;
+ for (;;)
+ {
+ /* how much */
+ uint32_t cb = sizeof(pThis->achScratch) - pThis->offScratch - 1; /* minus 1 - for the string terminator. */
+ if (cb > cbChars)
+ cb = (uint32_t)cbChars;
+
+ /* copy */
+ memcpy(&pThis->achScratch[pThis->offScratch], pachChars, cb);
+
+ /* advance */
+ pThis->offScratch += cb;
+ cbRet += cb;
+ cbChars -= cb;
+
+ /* done? */
+ if (cbChars <= 0)
+ return cbRet;
+
+ pachChars += cb;
+
+ /* flush */
+ pThis->achScratch[pThis->offScratch] = '\0';
+ rtR0LogLoggerExFallbackFlush(pThis);
+ }
+
+ /* won't ever get here! */
+ }
+ else
+ {
+ /*
+ * Termination call, flush the log.
+ */
+ pThis->achScratch[pThis->offScratch] = '\0';
+ rtR0LogLoggerExFallbackFlush(pThis);
+ return 0;
+ }
+}
+
+
+/**
+ * Ring-0 fallback for cases where we're unable to grab the lock.
+ *
+ * This will happen when we're at a too high IRQL on Windows for instance and
+ * needs to be dealt with or we'll drop a lot of log output. This fallback will
+ * only output to some of the log destinations as a few of them may be doing
+ * dangerous things. We won't be doing any prefixing here either, at least not
+ * for the present, because it's too much hassle.
+ *
+ * @param fDestFlags The destination flags.
+ * @param fFlags The logger flags.
+ * @param pInt The internal logger data, for ring buffer output.
+ * @param pszFormat The format string.
+ * @param va The format arguments.
+ */
+static void rtR0LogLoggerExFallback(uint32_t fDestFlags, uint32_t fFlags, PRTLOGGERINTERNAL pInt,
+ const char *pszFormat, va_list va)
+{
+ RTR0LOGLOGGERFALLBACK This;
+ This.fDestFlags = fDestFlags;
+ This.pInt = pInt;
+
+ /* fallback indicator. */
+ This.offScratch = 2;
+ This.achScratch[0] = '[';
+ This.achScratch[1] = 'F';
+
+ /* selected prefixes */
+ if (fFlags & RTLOGFLAGS_PREFIX_PID)
+ {
+ RTPROCESS Process = RTProcSelf();
+ This.achScratch[This.offScratch++] = ' ';
+ This.offScratch += RTStrFormatNumber(&This.achScratch[This.offScratch], Process, 16, sizeof(RTPROCESS) * 2, 0, RTSTR_F_ZEROPAD);
+ }
+ if (fFlags & RTLOGFLAGS_PREFIX_TID)
+ {
+ RTNATIVETHREAD Thread = RTThreadNativeSelf();
+ This.achScratch[This.offScratch++] = ' ';
+ This.offScratch += RTStrFormatNumber(&This.achScratch[This.offScratch], Thread, 16, sizeof(RTNATIVETHREAD) * 2, 0, RTSTR_F_ZEROPAD);
+ }
+
+ This.achScratch[This.offScratch++] = ']';
+ This.achScratch[This.offScratch++] = ' ';
+
+ RTLogFormatV(rtR0LogLoggerExFallbackOutput, &This, pszFormat, va);
+}
+
+#endif /* IN_RING0 */
+
+
+/**
+ * Callback for RTLogFormatV which writes to the com port.
+ * See PFNLOGOUTPUT() for details.
+ */
+static DECLCALLBACK(size_t) rtLogOutput(void *pv, const char *pachChars, size_t cbChars)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pv;
+ if (cbChars)
+ {
+ size_t cbRet = 0;
+ for (;;)
+ {
+ PRTLOGBUFFERDESC const pBufDesc = pLoggerInt->pBufDesc;
+ if (pBufDesc->offBuf < pBufDesc->cbBuf)
+ {
+ /* how much */
+ char *pchBuf = pBufDesc->pchBuf;
+ uint32_t offBuf = pBufDesc->offBuf;
+ size_t cb = pBufDesc->cbBuf - offBuf - 1;
+ if (cb > cbChars)
+ cb = cbChars;
+
+ switch (cb)
+ {
+ default:
+ memcpy(&pchBuf[offBuf], pachChars, cb);
+ pBufDesc->offBuf = offBuf + (uint32_t)cb;
+ cbRet += cb;
+ cbChars -= cb;
+ if (cbChars <= 0)
+ return cbRet;
+ pachChars += cb;
+ break;
+
+ case 1:
+ pchBuf[offBuf] = pachChars[0];
+ pBufDesc->offBuf = offBuf + 1;
+ if (cbChars == 1)
+ return cbRet + 1;
+ cbChars -= 1;
+ pachChars += 1;
+ break;
+
+ case 2:
+ pchBuf[offBuf] = pachChars[0];
+ pchBuf[offBuf + 1] = pachChars[1];
+ pBufDesc->offBuf = offBuf + 2;
+ if (cbChars == 2)
+ return cbRet + 2;
+ cbChars -= 2;
+ pachChars += 2;
+ break;
+
+ case 3:
+ pchBuf[offBuf] = pachChars[0];
+ pchBuf[offBuf + 1] = pachChars[1];
+ pchBuf[offBuf + 2] = pachChars[2];
+ pBufDesc->offBuf = offBuf + 3;
+ if (cbChars == 3)
+ return cbRet + 3;
+ cbChars -= 3;
+ pachChars += 3;
+ break;
+ }
+
+ }
+#if defined(RT_STRICT) && defined(IN_RING3)
+ else
+ {
+# ifndef IPRT_NO_CRT
+ fprintf(stderr, "pBufDesc->offBuf >= pBufDesc->cbBuf (%#x >= %#x)\n", pBufDesc->offBuf, pBufDesc->cbBuf);
+# else
+ RTLogWriteStdErr(RT_STR_TUPLE("pBufDesc->offBuf >= pBufDesc->cbBuf\n"));
+# endif
+ AssertBreakpoint(); AssertBreakpoint();
+ }
+#endif
+
+ /* flush */
+ rtlogFlush(pLoggerInt, true /*fNeedSpace*/);
+ }
+
+ /* won't ever get here! */
+ }
+ else
+ {
+ /*
+ * Termination call.
+ * There's always space for a terminator, and it's not counted.
+ */
+ PRTLOGBUFFERDESC const pBufDesc = pLoggerInt->pBufDesc;
+ pBufDesc->pchBuf[RT_MIN(pBufDesc->offBuf, pBufDesc->cbBuf - 1)] = '\0';
+ return 0;
+ }
+}
+
+
+/**
+ * stpncpy implementation for use in rtLogOutputPrefixed w/ padding.
+ *
+ * @returns Pointer to the destination buffer byte following the copied string.
+ * @param pszDst The destination buffer.
+ * @param pszSrc The source string.
+ * @param cchSrcMax The maximum number of characters to copy from
+ * the string.
+ * @param cchMinWidth The minimum field with, padd with spaces to
+ * reach this.
+ */
+DECLINLINE(char *) rtLogStPNCpyPad(char *pszDst, const char *pszSrc, size_t cchSrcMax, size_t cchMinWidth)
+{
+ size_t cchSrc = 0;
+ if (pszSrc)
+ {
+ cchSrc = strlen(pszSrc);
+ if (cchSrc > cchSrcMax)
+ cchSrc = cchSrcMax;
+
+ memcpy(pszDst, pszSrc, cchSrc);
+ pszDst += cchSrc;
+ }
+ do
+ *pszDst++ = ' ';
+ while (cchSrc++ < cchMinWidth);
+
+ return pszDst;
+}
+
+
+/**
+ * stpncpy implementation for use in rtLogOutputPrefixed w/ padding.
+ *
+ * @returns Pointer to the destination buffer byte following the copied string.
+ * @param pszDst The destination buffer.
+ * @param pszSrc The source string.
+ * @param cchSrc The number of characters to copy from the
+ * source. Equal or less than string length.
+ * @param cchMinWidth The minimum field with, padd with spaces to
+ * reach this.
+ */
+DECLINLINE(char *) rtLogStPNCpyPad2(char *pszDst, const char *pszSrc, size_t cchSrc, size_t cchMinWidth)
+{
+ Assert(pszSrc);
+ Assert(strlen(pszSrc) >= cchSrc);
+
+ memcpy(pszDst, pszSrc, cchSrc);
+ pszDst += cchSrc;
+ do
+ *pszDst++ = ' ';
+ while (cchSrc++ < cchMinWidth);
+
+ return pszDst;
+}
+
+
+
+/**
+ * Callback for RTLogFormatV which writes to the logger instance.
+ * This version supports prefixes.
+ *
+ * See PFNLOGOUTPUT() for details.
+ */
+static DECLCALLBACK(size_t) rtLogOutputPrefixed(void *pv, const char *pachChars, size_t cbChars)
+{
+ PRTLOGOUTPUTPREFIXEDARGS pArgs = (PRTLOGOUTPUTPREFIXEDARGS)pv;
+ PRTLOGGERINTERNAL pLoggerInt = pArgs->pLoggerInt;
+ if (cbChars)
+ {
+ uint64_t const fFlags = pLoggerInt->fFlags;
+ size_t cbRet = 0;
+ for (;;)
+ {
+ PRTLOGBUFFERDESC const pBufDesc = pLoggerInt->pBufDesc;
+ char * const pchBuf = pBufDesc->pchBuf;
+ uint32_t const cbBuf = pBufDesc->cbBuf;
+ uint32_t offBuf = pBufDesc->offBuf;
+ size_t cb = cbBuf - offBuf - 1;
+ const char *pszNewLine;
+ char *psz;
+
+#if defined(RT_STRICT) && defined(IN_RING3)
+ /* sanity */
+ if (offBuf < cbBuf)
+ { /* likely */ }
+ else
+ {
+# ifndef IPRT_NO_CRT
+ fprintf(stderr, "offBuf >= cbBuf (%#x >= %#x)\n", offBuf, cbBuf);
+# else
+ RTLogWriteStdErr(RT_STR_TUPLE("offBuf >= cbBuf\n"));
+# endif
+ AssertBreakpoint(); AssertBreakpoint();
+ }
+#endif
+
+ /*
+ * Pending prefix?
+ */
+ if (pLoggerInt->fPendingPrefix)
+ {
+ /*
+ * Flush the buffer if there isn't enough room for the maximum prefix config.
+ * Max is 265, add a couple of extra bytes. See CCH_PREFIX check way below.
+ */
+ if (cb >= 265 + 16)
+ pLoggerInt->fPendingPrefix = false;
+ else
+ {
+ rtlogFlush(pLoggerInt, true /*fNeedSpace*/);
+ continue;
+ }
+
+ /*
+ * Write the prefixes.
+ * psz is pointing to the current position.
+ */
+ psz = &pchBuf[offBuf];
+ if (fFlags & RTLOGFLAGS_PREFIX_TS)
+ {
+ uint64_t u64 = RTTimeNanoTS();
+ int iBase = 16;
+ unsigned int fStrFlags = RTSTR_F_ZEROPAD;
+ if (fFlags & RTLOGFLAGS_DECIMAL_TS)
+ {
+ iBase = 10;
+ fStrFlags = 0;
+ }
+ if (fFlags & RTLOGFLAGS_REL_TS)
+ {
+ static volatile uint64_t s_u64LastTs;
+ uint64_t u64DiffTs = u64 - s_u64LastTs;
+ s_u64LastTs = u64;
+ /* We could have been preempted just before reading of s_u64LastTs by
+ * another thread which wrote s_u64LastTs. In that case the difference
+ * is negative which we simply ignore. */
+ u64 = (int64_t)u64DiffTs < 0 ? 0 : u64DiffTs;
+ }
+ /* 1E15 nanoseconds = 11 days */
+ psz += RTStrFormatNumber(psz, u64, iBase, 16, 0, fStrFlags);
+ *psz++ = ' ';
+ }
+#define CCH_PREFIX_01 0 + 17
+
+ if (fFlags & RTLOGFLAGS_PREFIX_TSC)
+ {
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ uint64_t u64 = ASMReadTSC();
+#else
+ uint64_t u64 = RTTimeNanoTS();
+#endif
+ int iBase = 16;
+ unsigned int fStrFlags = RTSTR_F_ZEROPAD;
+ if (fFlags & RTLOGFLAGS_DECIMAL_TS)
+ {
+ iBase = 10;
+ fStrFlags = 0;
+ }
+ if (fFlags & RTLOGFLAGS_REL_TS)
+ {
+ static volatile uint64_t s_u64LastTsc;
+ int64_t i64DiffTsc = u64 - s_u64LastTsc;
+ s_u64LastTsc = u64;
+ /* We could have been preempted just before reading of s_u64LastTsc by
+ * another thread which wrote s_u64LastTsc. In that case the difference
+ * is negative which we simply ignore. */
+ u64 = i64DiffTsc < 0 ? 0 : i64DiffTsc;
+ }
+ /* 1E15 ticks at 4GHz = 69 hours */
+ psz += RTStrFormatNumber(psz, u64, iBase, 16, 0, fStrFlags);
+ *psz++ = ' ';
+ }
+#define CCH_PREFIX_02 CCH_PREFIX_01 + 17
+
+ if (fFlags & RTLOGFLAGS_PREFIX_MS_PROG)
+ {
+#ifndef IN_RING0
+ uint64_t u64 = RTTimeProgramMilliTS();
+#else
+ uint64_t u64 = (RTTimeNanoTS() - pLoggerInt->nsR0ProgramStart) / RT_NS_1MS;
+#endif
+ /* 1E8 milliseconds = 27 hours */
+ psz += RTStrFormatNumber(psz, u64, 10, 9, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ' ';
+ }
+#define CCH_PREFIX_03 CCH_PREFIX_02 + 21
+
+ if (fFlags & RTLOGFLAGS_PREFIX_TIME)
+ {
+#if defined(IN_RING3) || defined(IN_RING0)
+ RTTIMESPEC TimeSpec;
+ RTTIME Time;
+ RTTimeExplode(&Time, RTTimeNow(&TimeSpec));
+ psz += RTStrFormatNumber(psz, Time.u8Hour, 10, 2, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ':';
+ psz += RTStrFormatNumber(psz, Time.u8Minute, 10, 2, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ':';
+ psz += RTStrFormatNumber(psz, Time.u8Second, 10, 2, 0, RTSTR_F_ZEROPAD);
+ *psz++ = '.';
+ psz += RTStrFormatNumber(psz, Time.u32Nanosecond / 1000, 10, 6, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ' ';
+#else
+ memset(psz, ' ', 16);
+ psz += 16;
+#endif
+ }
+#define CCH_PREFIX_04 CCH_PREFIX_03 + (3+1+3+1+3+1+7+1)
+
+ if (fFlags & RTLOGFLAGS_PREFIX_TIME_PROG)
+ {
+
+#ifndef IN_RING0
+ uint64_t u64 = RTTimeProgramMicroTS();
+#else
+ uint64_t u64 = (RTTimeNanoTS() - pLoggerInt->nsR0ProgramStart) / RT_NS_1US;
+
+#endif
+ psz += RTStrFormatNumber(psz, (uint32_t)(u64 / RT_US_1HOUR), 10, 2, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ':';
+ uint32_t u32 = (uint32_t)(u64 % RT_US_1HOUR);
+ psz += RTStrFormatNumber(psz, u32 / RT_US_1MIN, 10, 2, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ':';
+ u32 %= RT_US_1MIN;
+
+ psz += RTStrFormatNumber(psz, u32 / RT_US_1SEC, 10, 2, 0, RTSTR_F_ZEROPAD);
+ *psz++ = '.';
+ psz += RTStrFormatNumber(psz, u32 % RT_US_1SEC, 10, 6, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ' ';
+ }
+#define CCH_PREFIX_05 CCH_PREFIX_04 + (9+1+2+1+2+1+6+1)
+
+# if 0
+ if (fFlags & RTLOGFLAGS_PREFIX_DATETIME)
+ {
+ char szDate[32];
+ RTTIMESPEC Time;
+ RTTimeSpecToString(RTTimeNow(&Time), szDate, sizeof(szDate));
+ size_t cch = strlen(szDate);
+ memcpy(psz, szDate, cch);
+ psz += cch;
+ *psz++ = ' ';
+ }
+# define CCH_PREFIX_06 CCH_PREFIX_05 + 32
+# else
+# define CCH_PREFIX_06 CCH_PREFIX_05 + 0
+# endif
+
+ if (fFlags & RTLOGFLAGS_PREFIX_PID)
+ {
+ RTPROCESS Process = RTProcSelf();
+ psz += RTStrFormatNumber(psz, Process, 16, sizeof(RTPROCESS) * 2, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ' ';
+ }
+#define CCH_PREFIX_07 CCH_PREFIX_06 + 9
+
+ if (fFlags & RTLOGFLAGS_PREFIX_TID)
+ {
+ RTNATIVETHREAD Thread = RTThreadNativeSelf();
+ psz += RTStrFormatNumber(psz, Thread, 16, sizeof(RTNATIVETHREAD) * 2, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ' ';
+ }
+#define CCH_PREFIX_08 CCH_PREFIX_07 + 17
+
+ if (fFlags & RTLOGFLAGS_PREFIX_THREAD)
+ {
+#ifdef IN_RING3
+ const char *pszName = RTThreadSelfName();
+#elif defined IN_RC
+ const char *pszName = "EMT-RC";
+#else
+ const char *pszName = pLoggerInt->szR0ThreadName[0] ? pLoggerInt->szR0ThreadName : "R0";
+#endif
+ psz = rtLogStPNCpyPad(psz, pszName, 16, 8);
+ }
+#define CCH_PREFIX_09 CCH_PREFIX_08 + 17
+
+ if (fFlags & RTLOGFLAGS_PREFIX_CPUID)
+ {
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ const uint8_t idCpu = ASMGetApicId();
+#else
+ const RTCPUID idCpu = RTMpCpuId();
+#endif
+ psz += RTStrFormatNumber(psz, idCpu, 16, sizeof(idCpu) * 2, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ' ';
+ }
+#define CCH_PREFIX_10 CCH_PREFIX_09 + 17
+
+ if ( (fFlags & RTLOGFLAGS_PREFIX_CUSTOM)
+ && pLoggerInt->pfnPrefix)
+ {
+ psz += pLoggerInt->pfnPrefix(&pLoggerInt->Core, psz, 31, pLoggerInt->pvPrefixUserArg);
+ *psz++ = ' '; /* +32 */
+ }
+#define CCH_PREFIX_11 CCH_PREFIX_10 + 32
+
+ if (fFlags & RTLOGFLAGS_PREFIX_LOCK_COUNTS)
+ {
+#ifdef IN_RING3 /** @todo implement these counters in ring-0 too? */
+ RTTHREAD Thread = RTThreadSelf();
+ if (Thread != NIL_RTTHREAD)
+ {
+ uint32_t cReadLocks = RTLockValidatorReadLockGetCount(Thread);
+ uint32_t cWriteLocks = RTLockValidatorWriteLockGetCount(Thread) - g_cLoggerLockCount;
+ cReadLocks = RT_MIN(0xfff, cReadLocks);
+ cWriteLocks = RT_MIN(0xfff, cWriteLocks);
+ psz += RTStrFormatNumber(psz, cReadLocks, 16, 1, 0, RTSTR_F_ZEROPAD);
+ *psz++ = '/';
+ psz += RTStrFormatNumber(psz, cWriteLocks, 16, 1, 0, RTSTR_F_ZEROPAD);
+ }
+ else
+#endif
+ {
+ *psz++ = '?';
+ *psz++ = '/';
+ *psz++ = '?';
+ }
+ *psz++ = ' ';
+ }
+#define CCH_PREFIX_12 CCH_PREFIX_11 + 8
+
+ if (fFlags & RTLOGFLAGS_PREFIX_FLAG_NO)
+ {
+ psz += RTStrFormatNumber(psz, pArgs->fFlags, 16, 8, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ' ';
+ }
+#define CCH_PREFIX_13 CCH_PREFIX_12 + 9
+
+ if (fFlags & RTLOGFLAGS_PREFIX_FLAG)
+ {
+#ifdef IN_RING3
+ const char *pszGroup = pArgs->iGroup != ~0U ? pLoggerInt->papszGroups[pArgs->iGroup] : NULL;
+#else
+ const char *pszGroup = NULL;
+#endif
+ psz = rtLogStPNCpyPad(psz, pszGroup, 16, 8);
+ }
+#define CCH_PREFIX_14 CCH_PREFIX_13 + 17
+
+ if (fFlags & RTLOGFLAGS_PREFIX_GROUP_NO)
+ {
+ if (pArgs->iGroup != ~0U)
+ {
+ psz += RTStrFormatNumber(psz, pArgs->iGroup, 16, 3, 0, RTSTR_F_ZEROPAD);
+ *psz++ = ' ';
+ }
+ else
+ {
+ memcpy(psz, "-1 ", sizeof("-1 ") - 1);
+ psz += sizeof("-1 ") - 1;
+ } /* +9 */
+ }
+#define CCH_PREFIX_15 CCH_PREFIX_14 + 9
+
+ if (fFlags & RTLOGFLAGS_PREFIX_GROUP)
+ {
+ const unsigned fGrp = pLoggerInt->afGroups[pArgs->iGroup != ~0U ? pArgs->iGroup : 0];
+ const char *pszGroup;
+ size_t cchGroup;
+ switch (pArgs->fFlags & fGrp)
+ {
+ case 0: pszGroup = "--------"; cchGroup = sizeof("--------") - 1; break;
+ case RTLOGGRPFLAGS_ENABLED: pszGroup = "enabled" ; cchGroup = sizeof("enabled" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_1: pszGroup = "level 1" ; cchGroup = sizeof("level 1" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_2: pszGroup = "level 2" ; cchGroup = sizeof("level 2" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_3: pszGroup = "level 3" ; cchGroup = sizeof("level 3" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_4: pszGroup = "level 4" ; cchGroup = sizeof("level 4" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_5: pszGroup = "level 5" ; cchGroup = sizeof("level 5" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_6: pszGroup = "level 6" ; cchGroup = sizeof("level 6" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_7: pszGroup = "level 7" ; cchGroup = sizeof("level 7" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_8: pszGroup = "level 8" ; cchGroup = sizeof("level 8" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_9: pszGroup = "level 9" ; cchGroup = sizeof("level 9" ) - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_10: pszGroup = "level 10"; cchGroup = sizeof("level 10") - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_11: pszGroup = "level 11"; cchGroup = sizeof("level 11") - 1; break;
+ case RTLOGGRPFLAGS_LEVEL_12: pszGroup = "level 12"; cchGroup = sizeof("level 12") - 1; break;
+ case RTLOGGRPFLAGS_FLOW: pszGroup = "flow" ; cchGroup = sizeof("flow" ) - 1; break;
+ case RTLOGGRPFLAGS_WARN: pszGroup = "warn" ; cchGroup = sizeof("warn" ) - 1; break;
+ default: pszGroup = "????????"; cchGroup = sizeof("????????") - 1; break;
+ }
+ psz = rtLogStPNCpyPad2(psz, pszGroup, RT_MIN(cchGroup, 16), 8);
+ }
+#define CCH_PREFIX_16 CCH_PREFIX_15 + 17
+
+ if (pArgs->pszInfix)
+ {
+ size_t cchInfix = strlen(pArgs->pszInfix);
+ psz = rtLogStPNCpyPad2(psz, pArgs->pszInfix, RT_MIN(cchInfix, 8), 1);
+ }
+#define CCH_PREFIX_17 CCH_PREFIX_16 + 9
+
+
+#define CCH_PREFIX ( CCH_PREFIX_17 )
+ { AssertCompile(CCH_PREFIX < 265); }
+
+ /*
+ * Done, figure what we've used and advance the buffer and free size.
+ */
+ AssertMsg(psz - &pchBuf[offBuf] <= 223,
+ ("%#zx (%zd) - fFlags=%#x\n", psz - &pchBuf[offBuf], psz - &pchBuf[offBuf], fFlags));
+ pBufDesc->offBuf = offBuf = (uint32_t)(psz - pchBuf);
+ cb = cbBuf - offBuf - 1;
+ }
+ else if (cb <= 2) /* 2 - Make sure we can write a \r\n and not loop forever. */
+ {
+ rtlogFlush(pLoggerInt, true /*fNeedSpace*/);
+ continue;
+ }
+
+ /*
+ * Done with the prefixing. Copy message text past the next newline.
+ */
+
+ /* how much */
+ if (cb > cbChars)
+ cb = cbChars;
+
+ /* have newline? */
+ pszNewLine = (const char *)memchr(pachChars, '\n', cb);
+ if (pszNewLine)
+ {
+ cb = pszNewLine - pachChars;
+ if (!(fFlags & RTLOGFLAGS_USECRLF))
+ {
+ cb += 1;
+ memcpy(&pchBuf[offBuf], pachChars, cb);
+ pLoggerInt->fPendingPrefix = true;
+ }
+ else if (cb + 2U < cbBuf - offBuf)
+ {
+ memcpy(&pchBuf[offBuf], pachChars, cb);
+ pchBuf[offBuf + cb++] = '\r';
+ pchBuf[offBuf + cb++] = '\n';
+ cbChars++; /* Discount the extra '\r'. */
+ pachChars--; /* Ditto. */
+ cbRet--; /* Ditto. */
+ pLoggerInt->fPendingPrefix = true;
+ }
+ else
+ {
+ /* Insufficient buffer space, leave the '\n' for the next iteration. */
+ memcpy(&pchBuf[offBuf], pachChars, cb);
+ }
+ }
+ else
+ memcpy(&pchBuf[offBuf], pachChars, cb);
+
+ /* advance */
+ pBufDesc->offBuf = offBuf += (uint32_t)cb;
+ cbRet += cb;
+ cbChars -= cb;
+
+ /* done? */
+ if (cbChars <= 0)
+ return cbRet;
+ pachChars += cb;
+ }
+
+ /* won't ever get here! */
+ }
+ else
+ {
+ /*
+ * Termination call.
+ * There's always space for a terminator, and it's not counted.
+ */
+ PRTLOGBUFFERDESC const pBufDesc = pLoggerInt->pBufDesc;
+ pBufDesc->pchBuf[RT_MIN(pBufDesc->offBuf, pBufDesc->cbBuf - 1)] = '\0';
+ return 0;
+ }
+}
+
+
+/**
+ * Write to a logger instance (worker function).
+ *
+ * This function will check whether the instance, group and flags makes up a
+ * logging kind which is currently enabled before writing anything to the log.
+ *
+ * @param pLoggerInt Pointer to logger instance. Must be non-NULL.
+ * @param fFlags The logging flags.
+ * @param iGroup The group.
+ * The value ~0U is reserved for compatibility with RTLogLogger[V] and is
+ * only for internal usage!
+ * @param pszFormat Format string.
+ * @param args Format arguments.
+ */
+static void rtlogLoggerExVLocked(PRTLOGGERINTERNAL pLoggerInt, unsigned fFlags, unsigned iGroup,
+ const char *pszFormat, va_list args)
+{
+ /*
+ * If we've got an auxilary descriptor, check if the buffer was flushed.
+ */
+ PRTLOGBUFFERDESC pBufDesc = pLoggerInt->pBufDesc;
+ PRTLOGBUFFERAUXDESC pAuxDesc = pBufDesc->pAux;
+ if (!pAuxDesc || !pAuxDesc->fFlushedIndicator)
+ { /* likely, except maybe for ring-0 */ }
+ else
+ {
+ pAuxDesc->fFlushedIndicator = false;
+ pBufDesc->offBuf = 0;
+ }
+
+ /*
+ * Format the message.
+ */
+ if (pLoggerInt->fFlags & (RTLOGFLAGS_PREFIX_MASK | RTLOGFLAGS_USECRLF))
+ {
+ RTLOGOUTPUTPREFIXEDARGS OutputArgs;
+ OutputArgs.pLoggerInt = pLoggerInt;
+ OutputArgs.iGroup = iGroup;
+ OutputArgs.fFlags = fFlags;
+ OutputArgs.pszInfix = NULL;
+ RTLogFormatV(rtLogOutputPrefixed, &OutputArgs, pszFormat, args);
+ }
+ else
+ RTLogFormatV(rtLogOutput, pLoggerInt, pszFormat, args);
+
+ /*
+ * Maybe flush the buffer and update the auxiliary descriptor if there is one.
+ */
+ pBufDesc = pLoggerInt->pBufDesc; /* (the descriptor may have changed) */
+ if ( !(pLoggerInt->fFlags & RTLOGFLAGS_BUFFERED)
+ && pBufDesc->offBuf)
+ rtlogFlush(pLoggerInt, false /*fNeedSpace*/);
+ else
+ {
+ pAuxDesc = pBufDesc->pAux;
+ if (pAuxDesc)
+ pAuxDesc->offBuf = pBufDesc->offBuf;
+ }
+}
+
+
+/**
+ * For calling rtlogLoggerExVLocked.
+ *
+ * @param pLoggerInt The logger.
+ * @param fFlags The logging flags.
+ * @param iGroup The group.
+ * The value ~0U is reserved for compatibility with RTLogLogger[V] and is
+ * only for internal usage!
+ * @param pszFormat Format string.
+ * @param ... Format arguments.
+ */
+static void rtlogLoggerExFLocked(PRTLOGGERINTERNAL pLoggerInt, unsigned fFlags, unsigned iGroup, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ rtlogLoggerExVLocked(pLoggerInt, fFlags, iGroup, pszFormat, va);
+ va_end(va);
+}
+
+
+RTDECL(int) RTLogLoggerExV(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, va_list args)
+{
+ int rc;
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ RTLOG_RESOLVE_DEFAULT_RET(pLoggerInt, VINF_LOG_NO_LOGGER);
+
+ /*
+ * Validate and correct iGroup.
+ */
+ if (iGroup != ~0U && iGroup >= pLoggerInt->cGroups)
+ iGroup = 0;
+
+ /*
+ * If no output, then just skip it.
+ */
+ if ( (pLoggerInt->fFlags & RTLOGFLAGS_DISABLED)
+ || !pLoggerInt->fDestFlags
+ || !pszFormat || !*pszFormat)
+ return VINF_LOG_DISABLED;
+ if ( iGroup != ~0U
+ && (pLoggerInt->afGroups[iGroup] & (fFlags | RTLOGGRPFLAGS_ENABLED)) != (fFlags | RTLOGGRPFLAGS_ENABLED))
+ return VINF_LOG_DISABLED;
+
+ /*
+ * Acquire logger instance sem.
+ */
+ rc = rtlogLock(pLoggerInt);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check group restrictions and call worker.
+ */
+ if (RT_LIKELY( !(pLoggerInt->fFlags & RTLOGFLAGS_RESTRICT_GROUPS)
+ || iGroup >= pLoggerInt->cGroups
+ || !(pLoggerInt->afGroups[iGroup] & RTLOGGRPFLAGS_RESTRICT)
+ || ++pLoggerInt->pacEntriesPerGroup[iGroup] < pLoggerInt->cMaxEntriesPerGroup ))
+ rtlogLoggerExVLocked(pLoggerInt, fFlags, iGroup, pszFormat, args);
+ else
+ {
+ uint32_t cEntries = pLoggerInt->pacEntriesPerGroup[iGroup];
+ if (cEntries > pLoggerInt->cMaxEntriesPerGroup)
+ pLoggerInt->pacEntriesPerGroup[iGroup] = cEntries - 1;
+ else
+ {
+ rtlogLoggerExVLocked(pLoggerInt, fFlags, iGroup, pszFormat, args);
+ if ( pLoggerInt->papszGroups
+ && pLoggerInt->papszGroups[iGroup])
+ rtlogLoggerExFLocked(pLoggerInt, fFlags, iGroup, "%u messages from group %s (#%u), muting it.\n",
+ cEntries, pLoggerInt->papszGroups[iGroup], iGroup);
+ else
+ rtlogLoggerExFLocked(pLoggerInt, fFlags, iGroup, "%u messages from group #%u, muting it.\n", cEntries, iGroup);
+ }
+ }
+
+ /*
+ * Release the semaphore.
+ */
+ rtlogUnlock(pLoggerInt);
+ return VINF_SUCCESS;
+ }
+
+#ifdef IN_RING0
+ if (pLoggerInt->fDestFlags & ~RTLOGDEST_FILE)
+ {
+ rtR0LogLoggerExFallback(pLoggerInt->fDestFlags, pLoggerInt->fFlags, pLoggerInt, pszFormat, args);
+ return VINF_SUCCESS;
+ }
+#endif
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLogLoggerExV);
+
+
+RTDECL(void) RTLogLoggerV(PRTLOGGER pLogger, const char *pszFormat, va_list args)
+{
+ RTLogLoggerExV(pLogger, 0, ~0U, pszFormat, args);
+}
+RT_EXPORT_SYMBOL(RTLogLoggerV);
+
+
+RTDECL(void) RTLogPrintfV(const char *pszFormat, va_list va)
+{
+ RTLogLoggerV(NULL, pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTLogPrintfV);
+
+
+RTDECL(void) RTLogDumpPrintfV(void *pvUser, const char *pszFormat, va_list va)
+{
+ RTLogLoggerV((PRTLOGGER)pvUser, pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTLogDumpPrintfV);
+
+
+RTDECL(void) RTLogAssert(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTLogAssertV(pszFormat,va);
+ va_end(va);
+}
+
+
+RTDECL(void) RTLogAssertV(const char *pszFormat, va_list va)
+{
+ /*
+ * To the release log if we got one.
+ */
+ PRTLOGGER pLogger = RTLogRelGetDefaultInstance();
+ if (pLogger)
+ {
+ va_list vaCopy;
+ va_copy(vaCopy, va);
+ RTLogLoggerExV(pLogger, 0 /*fFlags*/, ~0U /*uGroup*/, pszFormat, vaCopy);
+ va_end(vaCopy);
+#ifndef IN_RC
+ RTLogFlush(pLogger);
+#endif
+ }
+
+ /*
+ * To the debug log if we got one, however when LOG_ENABLE (debug builds and
+ * such) we'll allow it to be created here.
+ */
+#ifdef LOG_ENABLED
+ pLogger = RTLogDefaultInstance();
+#else
+ pLogger = RTLogGetDefaultInstance();
+#endif
+ if (pLogger)
+ {
+ RTLogLoggerExV(pLogger, 0 /*fFlags*/, ~0U /*uGroup*/, pszFormat, va);
+# ifndef IN_RC /* flushing is done automatically in RC */
+ RTLogFlush(pLogger);
+#endif
+ }
+}
+
+
+#if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+/**
+ * "Weak symbol" emulation to prevent dragging in log.cpp and all its friends
+ * just because some code is using Assert() in a statically linked binary.
+ *
+ * The pointers are in log-assert-pfn.cpp, so users only drag in that file and
+ * they remain NULL unless this file is also linked into the binary.
+ */
+class RTLogAssertWeakSymbolEmulator
+{
+public:
+ RTLogAssertWeakSymbolEmulator(void)
+ {
+ g_pfnRTLogAssert = RTLogAssert;
+ g_pfnRTLogAssertV = RTLogAssertV;
+ }
+};
+static RTLogAssertWeakSymbolEmulator rtLogInitWeakSymbolPointers;
+#endif
+
+
+#ifdef IN_RING3
+
+/**
+ * @callback_method_impl{FNRTLOGPHASEMSG,
+ * Log phase callback function - assumes the lock is already held.}
+ */
+static DECLCALLBACK(void) rtlogPhaseMsgLocked(PRTLOGGER pLogger, const char *pszFormat, ...)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ AssertPtrReturnVoid(pLoggerInt);
+ Assert(pLoggerInt->hSpinMtx != NIL_RTSEMSPINMUTEX);
+
+ va_list args;
+ va_start(args, pszFormat);
+ rtlogLoggerExVLocked(pLoggerInt, 0, ~0U, pszFormat, args);
+ va_end(args);
+}
+
+
+/**
+ * @callback_method_impl{FNRTLOGPHASEMSG,
+ * Log phase callback function - assumes the lock is not held.}
+ */
+static DECLCALLBACK(void) rtlogPhaseMsgNormal(PRTLOGGER pLogger, const char *pszFormat, ...)
+{
+ PRTLOGGERINTERNAL pLoggerInt = (PRTLOGGERINTERNAL)pLogger;
+ AssertPtrReturnVoid(pLoggerInt);
+ Assert(pLoggerInt->hSpinMtx != NIL_RTSEMSPINMUTEX);
+
+ va_list args;
+ va_start(args, pszFormat);
+ RTLogLoggerExV(&pLoggerInt->Core, 0, ~0U, pszFormat, args);
+ va_end(args);
+}
+
+#endif /* IN_RING3 */
+
diff --git a/src/VBox/Runtime/common/log/logcom.cpp b/src/VBox/Runtime/common/log/logcom.cpp
new file mode 100644
index 00000000..844e48c1
--- /dev/null
+++ b/src/VBox/Runtime/common/log/logcom.cpp
@@ -0,0 +1,156 @@
+/* $Id: logcom.cpp $ */
+/** @file
+ * IPRT - Logging to Serial Port.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef IPRT_UART_BASE
+/** The port address of the COM port to log to.
+ *
+ * To override the default (COM1) append IPRT_UART_BASE=0xWXYZ to DEFS in your
+ * LocalConfig.kmk. Alternatively you can edit this file, but the don't forget
+ * to also update the default found in VBox/asmdefs.h.
+ *
+ * Standard port assignments are: COM1=0x3f8, COM2=0x2f8, COM3=0x3e8, COM4=0x2e8.
+ */
+# define IPRT_UART_BASE 0x3f8
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/log.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86) /** @todo consider fixing the config instead. */
+# include <iprt/asm-amd64-x86.h>
+#endif
+#include <iprt/stdarg.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(size_t) rtLogComOutput(void *pv, const char *pachChars, size_t cbChars);
+
+
+/**
+ * Prints a formatted string to the serial port used for logging.
+ *
+ * @returns Number of bytes written.
+ * @param pszFormat Format string.
+ * @param ... Optional arguments specified in the format string.
+ */
+RTDECL(size_t) RTLogComPrintf(const char *pszFormat, ...)
+{
+ va_list args;
+ size_t cb;
+ va_start(args, pszFormat);
+ cb = RTLogComPrintfV(pszFormat, args);
+ va_end(args);
+
+ return cb;
+}
+RT_EXPORT_SYMBOL(RTLogComPrintf);
+
+
+/**
+ * Prints a formatted string to the serial port used for logging.
+ *
+ * @returns Number of bytes written.
+ * @param pszFormat Format string.
+ * @param args Optional arguments specified in the format string.
+ */
+RTDECL(size_t) RTLogComPrintfV(const char *pszFormat, va_list args)
+{
+ return RTLogFormatV(rtLogComOutput, NULL, pszFormat, args);
+}
+RT_EXPORT_SYMBOL(RTLogComPrintfV);
+
+
+/**
+ * Callback for RTLogFormatV which writes to the com port.
+ * See PFNLOGOUTPUT() for details.
+ */
+static DECLCALLBACK(size_t) rtLogComOutput(void *pv, const char *pachChars, size_t cbChars)
+{
+ NOREF(pv);
+ if (cbChars)
+ RTLogWriteCom(pachChars, cbChars);
+ return cbChars;
+}
+
+
+/**
+ * Write log buffer to COM port.
+ *
+ * @param pach Pointer to the buffer to write.
+ * @param cb Number of bytes to write.
+ */
+RTDECL(void) RTLogWriteCom(const char *pach, size_t cb)
+{
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ const uint8_t *pu8;
+ for (pu8 = (const uint8_t *)pach; cb-- > 0; pu8++)
+ {
+ unsigned cMaxWait;
+ uint8_t u8;
+
+ /* expand \n -> \r\n */
+ if (*pu8 == '\n')
+ RTLogWriteCom("\r", 1);
+
+ /* Check if port is ready. */
+ cMaxWait = ~0U;
+ do
+ {
+ u8 = ASMInU8(IPRT_UART_BASE + 5);
+ cMaxWait--;
+ } while (!(u8 & 0x20) && u8 != 0xff && cMaxWait);
+
+ /* write */
+ ASMOutU8(IPRT_UART_BASE, *pu8);
+ }
+#else
+ /* PORTME? */
+#endif
+}
+RT_EXPORT_SYMBOL(RTLogWriteCom);
+
diff --git a/src/VBox/Runtime/common/log/logellipsis.cpp b/src/VBox/Runtime/common/log/logellipsis.cpp
new file mode 100644
index 00000000..2606c56a
--- /dev/null
+++ b/src/VBox/Runtime/common/log/logellipsis.cpp
@@ -0,0 +1,115 @@
+/* $Id: logellipsis.cpp $ */
+/** @file
+ * Runtime VBox - Logger, the ellipsis variants.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/log.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/stdarg.h>
+
+
+/**
+ * Write to a logger instance.
+ *
+ * @param pLogger Pointer to logger instance.
+ * @param pvCallerRet Ignored.
+ * @param pszFormat Format string.
+ * @param ... Format arguments.
+ */
+RTDECL(void) RTLogLogger(PRTLOGGER pLogger, void *pvCallerRet, const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+#if defined(RT_OS_DARWIN) && defined(RT_ARCH_X86) && defined(IN_RING3)
+ /* manually align the stack before doing the call.
+ * We boldly assume that there is a stack frame here! */
+ __asm__ __volatile__("andl $-32, %%esp\t\n" ::: "%esp");
+ RTLogLoggerExV(pLogger, 0, ~0U, pszFormat, args);
+#else
+ RTLogLoggerExV(pLogger, 0, ~0U, pszFormat, args);
+#endif
+ va_end(args);
+ NOREF(pvCallerRet);
+}
+RT_EXPORT_SYMBOL(RTLogLogger);
+
+
+/**
+ * Write to a logger instance.
+ *
+ * This function will check whether the instance, group and flags makes up a
+ * logging kind which is currently enabled before writing anything to the log.
+ *
+ * @param pLogger Pointer to logger instance. If NULL the default logger instance will be attempted.
+ * @param fFlags The logging flags.
+ * @param iGroup The group.
+ * The value ~0U is reserved for compatibility with RTLogLogger[V] and is
+ * only for internal usage!
+ * @param pszFormat Format string.
+ * @param ... Format arguments.
+ * @remark This is a worker function of LogIt.
+ */
+RTDECL(void) RTLogLoggerEx(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ RTLogLoggerExV(pLogger, fFlags, iGroup, pszFormat, args);
+ va_end(args);
+}
+RT_EXPORT_SYMBOL(RTLogLoggerEx);
+
+
+/**
+ * printf like function for writing to the default log.
+ *
+ * @param pszFormat Printf like format string.
+ * @param ... Optional arguments as specified in pszFormat.
+ *
+ * @remark The API doesn't support formatting of floating point numbers at the moment.
+ */
+RTDECL(void) RTLogPrintf(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ RTLogPrintfV(pszFormat, args);
+ va_end(args);
+}
+RT_EXPORT_SYMBOL(RTLogPrintf);
+
diff --git a/src/VBox/Runtime/common/log/logformat.cpp b/src/VBox/Runtime/common/log/logformat.cpp
new file mode 100644
index 00000000..22a3cb31
--- /dev/null
+++ b/src/VBox/Runtime/common/log/logformat.cpp
@@ -0,0 +1,110 @@
+/* $Id: logformat.cpp $ */
+/** @file
+ * IPRT - Log Formatter.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/log.h>
+#include "internal/iprt.h"
+
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#ifdef IN_RING3
+# include <iprt/thread.h>
+# include <iprt/errcore.h>
+#endif
+
+#include <iprt/stdarg.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(size_t) rtlogFormatStr(void *pvArg, PFNRTSTROUTPUT pfnOutput,
+ void *pvArgOutput, const char **ppszFormat,
+ va_list *pArgs, int cchWidth, int cchPrecision,
+ unsigned fFlags, char chArgSize);
+
+
+/**
+ * Partial vsprintf worker implementation.
+ *
+ * @returns number of bytes formatted.
+ * @param pfnOutput Output worker.
+ * Called in two ways. Normally with a string an it's length.
+ * For termination, it's called with NULL for string, 0 for length.
+ * @param pvArg Argument to output worker.
+ * @param pszFormat Format string.
+ * @param args Argument list.
+ */
+RTDECL(size_t) RTLogFormatV(PFNRTSTROUTPUT pfnOutput, void *pvArg, const char *pszFormat, va_list args)
+{
+ return RTStrFormatV(pfnOutput, pvArg, rtlogFormatStr, NULL, pszFormat, args);
+}
+RT_EXPORT_SYMBOL(RTLogFormatV);
+
+
+/**
+ * Callback to format VBox formatting extentions.
+ * See @ref pg_rt_str_format for a reference on the format types.
+ *
+ * @returns The number of bytes formatted.
+ * @param pvArg Formatter argument.
+ * @param pfnOutput Pointer to output function.
+ * @param pvArgOutput Argument for the output function.
+ * @param ppszFormat Pointer to the format string pointer. Advance this till the char
+ * after the format specifier.
+ * @param pArgs Pointer to the argument list. Use this to fetch the arguments.
+ * @param cchWidth Format Width. -1 if not specified.
+ * @param cchPrecision Format Precision. -1 if not specified.
+ * @param fFlags Flags (RTSTR_NTFS_*).
+ * @param chArgSize The argument size specifier, 'l' or 'L'.
+ */
+static DECLCALLBACK(size_t) rtlogFormatStr(void *pvArg, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput,
+ const char **ppszFormat, va_list *pArgs, int cchWidth,
+ int cchPrecision, unsigned fFlags, char chArgSize)
+{
+ char ch = *(*ppszFormat)++;
+
+ AssertMsgFailed(("Invalid logger format type '%%%c%.10s'!\n", ch, *ppszFormat)); NOREF(ch);
+
+ NOREF(pvArg); NOREF(pfnOutput); NOREF(pvArgOutput); NOREF(pArgs); NOREF(cchWidth);
+ NOREF(cchPrecision); NOREF(fFlags); NOREF(chArgSize);
+ return 0;
+}
+
diff --git a/src/VBox/Runtime/common/log/logrel.cpp b/src/VBox/Runtime/common/log/logrel.cpp
new file mode 100644
index 00000000..b1bcfeaa
--- /dev/null
+++ b/src/VBox/Runtime/common/log/logrel.cpp
@@ -0,0 +1,106 @@
+/* $Id: logrel.cpp $ */
+/** @file
+ * Runtime VBox - Release Logger.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/log.h>
+#include "internal/iprt.h"
+
+
+/**
+ * Write to a logger instance, defaulting to the release one.
+ *
+ * This function will check whether the instance, group and flags makes up a
+ * logging kind which is currently enabled before writing anything to the log.
+ *
+ * @param pLogger Pointer to logger instance. If NULL the default release instance is attempted.
+ * @param fFlags The logging flags.
+ * @param iGroup The group.
+ * The value ~0U is reserved for compatibility with RTLogLogger[V] and is
+ * only for internal usage!
+ * @param pszFormat Format string.
+ * @param args Format arguments.
+ */
+RTDECL(void) RTLogRelLoggerV(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, va_list args)
+{
+ /*
+ * A NULL logger means default instance.
+ */
+ if (!pLogger)
+ {
+ pLogger = RTLogRelGetDefaultInstance();
+ if (!pLogger)
+ return;
+ }
+ RTLogLoggerExV(pLogger, fFlags, iGroup, pszFormat, args);
+}
+RT_EXPORT_SYMBOL(RTLogRelLoggerV);
+
+
+/**
+ * vprintf like function for writing to the default release log.
+ *
+ * @param pszFormat Printf like format string.
+ * @param args Optional arguments as specified in pszFormat.
+ *
+ * @remark The API doesn't support formatting of floating point numbers at the moment.
+ */
+RTDECL(void) RTLogRelPrintfV(const char *pszFormat, va_list args)
+{
+ RTLogRelLoggerV(NULL, 0, ~0U, pszFormat, args);
+}
+RT_EXPORT_SYMBOL(RTLogRelPrintfV);
+
+
+/**
+ * Changes the buffering setting of the default release logger.
+ *
+ * This can be used for optimizing longish logging sequences.
+ *
+ * @returns The old state.
+ * @param fBuffered The new state.
+ */
+RTDECL(bool) RTLogRelSetBuffering(bool fBuffered)
+{
+ PRTLOGGER pLogger = RTLogRelGetDefaultInstance();
+ if (pLogger)
+ return RTLogSetBuffering(pLogger, fBuffered);
+ return false;
+}
+RT_EXPORT_SYMBOL(RTLogRelSetBuffering);
+
diff --git a/src/VBox/Runtime/common/log/logrelellipsis.cpp b/src/VBox/Runtime/common/log/logrelellipsis.cpp
new file mode 100644
index 00000000..ccf569de
--- /dev/null
+++ b/src/VBox/Runtime/common/log/logrelellipsis.cpp
@@ -0,0 +1,88 @@
+/* $Id: logrelellipsis.cpp $ */
+/** @file
+ * Runtime VBox - Logger, the release ellipsis variants.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/log.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+/**
+ * Write to a logger instance, defaulting to the release one.
+ *
+ * This function will check whether the instance, group and flags makes up a
+ * logging kind which is currently enabled before writing anything to the log.
+ *
+ * @param pLogger Pointer to logger instance.
+ * @param fFlags The logging flags.
+ * @param iGroup The group.
+ * The value ~0U is reserved for compatibility with RTLogLogger[V] and is
+ * only for internal usage!
+ * @param pszFormat Format string.
+ * @param ... Format arguments.
+ * @remark This is a worker function for LogRelIt.
+ */
+RTDECL(void) RTLogRelLogger(PRTLOGGER pLogger, unsigned fFlags, unsigned iGroup, const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ RTLogRelLoggerV(pLogger, fFlags, iGroup, pszFormat, args);
+ va_end(args);
+}
+RT_EXPORT_SYMBOL(RTLogRelLogger);
+
+
+/**
+ * printf like function for writing to the default release log.
+ *
+ * @param pszFormat Printf like format string.
+ * @param ... Optional arguments as specified in pszFormat.
+ *
+ * @remark The API doesn't support formatting of floating point numbers at the moment.
+ */
+RTDECL(void) RTLogRelPrintf(const char *pszFormat, ...)
+{
+ va_list args;
+ va_start(args, pszFormat);
+ RTLogRelPrintfV(pszFormat, args);
+ va_end(args);
+}
+RT_EXPORT_SYMBOL(RTLogRelPrintf);
+
diff --git a/src/VBox/Runtime/common/log/tracebuf.cpp b/src/VBox/Runtime/common/log/tracebuf.cpp
new file mode 100644
index 00000000..4ade3c0c
--- /dev/null
+++ b/src/VBox/Runtime/common/log/tracebuf.cpp
@@ -0,0 +1,698 @@
+/* $Id: tracebuf.cpp $ */
+/** @file
+ * IPRT - Tracebuffer common functions.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/trace.h>
+
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#ifndef IN_RC
+# include <iprt/mem.h>
+#endif
+#include <iprt/mp.h>
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+# include <iprt/asm-amd64-x86.h>
+#endif
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Alignment used to place the trace buffer members, this should be a multiple
+ * of the cache line size if possible. (We should dynamically determine it.) */
+#define RTTRACEBUF_ALIGNMENT 64
+AssertCompile(RTTRACEBUF_ALIGNMENT >= sizeof(uint64_t) * 2);
+
+/** The maximum number of entries. */
+#define RTTRACEBUF_MAX_ENTRIES _64K
+/** The minimum number of entries. */
+#define RTTRACEBUF_MIN_ENTRIES 4
+/** The default number of entries. */
+#define RTTRACEBUF_DEF_ENTRIES 256
+
+/** The maximum entry size. */
+#define RTTRACEBUF_MAX_ENTRY_SIZE _1M
+/** The minimum entry size. */
+#define RTTRACEBUF_MIN_ENTRY_SIZE RTTRACEBUF_ALIGNMENT
+/** The default entry size. */
+#define RTTRACEBUF_DEF_ENTRY_SIZE 256
+AssertCompile(!(RTTRACEBUF_DEF_ENTRY_SIZE & (RTTRACEBUF_DEF_ENTRY_SIZE - 1)));
+
+/**
+ * The volatile trace buffer members.
+ */
+typedef struct RTTRACEBUFVOLATILE
+{
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The next entry to make use of. */
+ uint32_t volatile iEntry;
+} RTTRACEBUFVOLATILE;
+/** Pointer to the volatile parts of a trace buffer. */
+typedef RTTRACEBUFVOLATILE *PRTTRACEBUFVOLATILE;
+
+
+/**
+ * Trace buffer entry.
+ */
+typedef struct RTTRACEBUFENTRY
+{
+ /** The nano second entry time stamp. */
+ uint64_t NanoTS;
+ /** The ID of the CPU the event was recorded. */
+ RTCPUID idCpu;
+ /** The message. */
+ char szMsg[RTTRACEBUF_ALIGNMENT - sizeof(uint64_t) - sizeof(RTCPUID)];
+} RTTRACEBUFENTRY;
+AssertCompile(sizeof(RTTRACEBUFENTRY) <= RTTRACEBUF_ALIGNMENT);
+/** Pointer to a trace buffer entry. */
+typedef RTTRACEBUFENTRY *PRTTRACEBUFENTRY;
+
+
+
+/**
+ * Trace buffer structure.
+ *
+ * @remarks This structure must be context agnostic, i.e. no pointers or
+ * other types that may differ between contexts (R3/R0/RC).
+ */
+typedef struct RTTRACEBUFINT
+{
+ /** Magic value (RTTRACEBUF_MAGIC). */
+ uint32_t u32Magic;
+ /** The entry size. */
+ uint32_t cbEntry;
+ /** The number of entries. */
+ uint32_t cEntries;
+ /** Flags (always zero for now). */
+ uint32_t fFlags;
+ /** The offset to the volatile members (RTTRACEBUFVOLATILE) (relative to
+ * the start of this structure). */
+ uint32_t offVolatile;
+ /** The offset to the entries (relative to the start of this structure). */
+ uint32_t offEntries;
+ /** Reserved entries. */
+ uint32_t au32Reserved[2];
+} RTTRACEBUFINT;
+/** Pointer to a const trace buffer. */
+typedef RTTRACEBUFINT const *PCRTTRACEBUFINT;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/**
+ * Get the current CPU Id.
+ */
+#if defined(IN_RING0) \
+ || defined(RT_OS_WINDOWS) \
+ || (!defined(RT_ARCH_AMD64) && !defined(RT_ARCH_X86))
+# define RTTRACEBUF_CUR_CPU() RTMpCpuId()
+#else
+# define RTTRACEBUF_CUR_CPU() ASMGetApicId() /** @todo this isn't good enough for big boxes with lots of CPUs/cores. */
+#endif
+
+/** Calculates the address of the volatile trace buffer members. */
+#define RTTRACEBUF_TO_VOLATILE(a_pThis) ((PRTTRACEBUFVOLATILE)((uint8_t *)(a_pThis) + (a_pThis)->offVolatile))
+
+/** Calculates the address of a trace buffer entry. */
+#define RTTRACEBUF_TO_ENTRY(a_pThis, a_iEntry) \
+ ((PRTTRACEBUFENTRY)( (uint8_t *)(a_pThis) + (a_pThis)->offEntries + (a_iEntry) * (a_pThis)->cbEntry ))
+
+/** Validates a trace buffer handle and returns rc if not valid. */
+#define RTTRACEBUF_VALID_RETURN_RC(a_pThis, a_rc) \
+ do { \
+ AssertPtrReturn((a_pThis), (a_rc)); \
+ AssertReturn((a_pThis)->u32Magic == RTTRACEBUF_MAGIC, (a_rc)); \
+ AssertReturn((a_pThis)->offVolatile < RTTRACEBUF_ALIGNMENT * 2, (a_rc)); \
+ AssertReturn(RTTRACEBUF_TO_VOLATILE(a_pThis)->cRefs > 0, (a_rc)); \
+ } while (0)
+
+/**
+ * Resolves and validates a trace buffer handle and returns rc if not valid.
+ *
+ * @param a_hTraceBuf The trace buffer handle passed by the user.
+ * @param a_pThis Where to store the trace buffer pointer.
+ */
+#define RTTRACEBUF_RESOLVE_VALIDATE_RETAIN_RETURN(a_hTraceBuf, a_pThis) \
+ do { \
+ uint32_t cRefs; \
+ if ((a_hTraceBuf) == RTTRACEBUF_DEFAULT) \
+ { \
+ (a_pThis) = RTTraceGetDefaultBuf(); \
+ if (!RT_VALID_PTR(a_pThis)) \
+ return VERR_NOT_FOUND; \
+ } \
+ else \
+ { \
+ (a_pThis) = (a_hTraceBuf); \
+ AssertPtrReturn((a_pThis), VERR_INVALID_HANDLE); \
+ } \
+ AssertReturn((a_pThis)->u32Magic == RTTRACEBUF_MAGIC, VERR_INVALID_HANDLE); \
+ AssertReturn((a_pThis)->offVolatile < RTTRACEBUF_ALIGNMENT * 2, VERR_INVALID_HANDLE); \
+ \
+ cRefs = ASMAtomicIncU32(&RTTRACEBUF_TO_VOLATILE(a_pThis)->cRefs); \
+ if (RT_UNLIKELY(cRefs < 1 || cRefs >= _1M)) \
+ { \
+ ASMAtomicDecU32(&RTTRACEBUF_TO_VOLATILE(a_pThis)->cRefs); \
+ AssertFailedReturn(VERR_INVALID_HANDLE); \
+ } \
+ } while (0)
+
+
+/**
+ * Drops a trace buffer reference.
+ *
+ * @param a_pThis Pointer to the trace buffer.
+ */
+#define RTTRACEBUF_DROP_REFERENCE(a_pThis) \
+ do { \
+ uint32_t cRefs = ASMAtomicDecU32(&RTTRACEBUF_TO_VOLATILE(a_pThis)->cRefs); \
+ if (!cRefs) \
+ rtTraceBufDestroy((RTTRACEBUFINT *)a_pThis); \
+ } while (0)
+
+
+/**
+ * The prologue code for a RTTraceAddSomething function.
+ *
+ * Resolves a trace buffer handle, grabs a reference to it and allocates the
+ * next entry. Return with an appropriate error status on failure.
+ *
+ * @param a_hTraceBuf The trace buffer handle passed by the user.
+ *
+ * @remarks This is kind of ugly, sorry.
+ */
+#define RTTRACEBUF_ADD_PROLOGUE(a_hTraceBuf) \
+ int rc; \
+ uint32_t cRefs; \
+ uint32_t iEntry; \
+ PCRTTRACEBUFINT pThis; \
+ PRTTRACEBUFVOLATILE pVolatile; \
+ PRTTRACEBUFENTRY pEntry; \
+ char *pszBuf; \
+ size_t cchBuf; \
+ \
+ /* Resolve and validate the handle. */ \
+ if ((a_hTraceBuf) == RTTRACEBUF_DEFAULT) \
+ { \
+ pThis = RTTraceGetDefaultBuf(); \
+ if (!RT_VALID_PTR(pThis)) \
+ return VERR_NOT_FOUND; \
+ } \
+ else if ((a_hTraceBuf) != NIL_RTTRACEBUF) \
+ { \
+ pThis = (a_hTraceBuf); \
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE); \
+ } \
+ else \
+ return VERR_INVALID_HANDLE; \
+ \
+ AssertReturn(pThis->u32Magic == RTTRACEBUF_MAGIC, VERR_INVALID_HANDLE); \
+ if (pThis->fFlags & RTTRACEBUF_FLAGS_DISABLED) \
+ return VINF_SUCCESS; \
+ AssertReturn(pThis->offVolatile < RTTRACEBUF_ALIGNMENT * 2, VERR_INVALID_HANDLE); \
+ pVolatile = RTTRACEBUF_TO_VOLATILE(pThis); \
+ \
+ /* Grab a reference. */ \
+ cRefs = ASMAtomicIncU32(&pVolatile->cRefs); \
+ if (RT_UNLIKELY(cRefs < 1 || cRefs >= _1M)) \
+ { \
+ ASMAtomicDecU32(&pVolatile->cRefs); \
+ AssertFailedReturn(VERR_INVALID_HANDLE); \
+ } \
+ \
+ /* Grab the next entry and set the time stamp. */ \
+ iEntry = ASMAtomicIncU32(&pVolatile->iEntry) - 1; \
+ iEntry %= pThis->cEntries; \
+ pEntry = RTTRACEBUF_TO_ENTRY(pThis, iEntry); \
+ pEntry->NanoTS = RTTimeNanoTS(); \
+ pEntry->idCpu = RTTRACEBUF_CUR_CPU(); \
+ pszBuf = &pEntry->szMsg[0]; \
+ *pszBuf = '\0'; \
+ cchBuf = pThis->cbEntry - RT_UOFFSETOF(RTTRACEBUFENTRY, szMsg) - 1; \
+ rc = VINF_SUCCESS
+
+
+/**
+ * Used by a RTTraceAddPosSomething to store the source position in the entry
+ * prior to adding the actual trace message text.
+ *
+ * Both pszBuf and cchBuf will be adjusted such that pszBuf points and the zero
+ * terminator after the source position part.
+ */
+#define RTTRACEBUF_ADD_STORE_SRC_POS() \
+ do { \
+ /* file(line): - no path */ \
+ size_t cchPos = RTStrPrintf(pszBuf, cchBuf, "%s(%d): ", RTPathFilename(pszFile), iLine); \
+ pszBuf += cchPos; \
+ cchBuf -= cchPos; \
+ NOREF(pszFunction); \
+ } while (0)
+
+
+/**
+ * The epilogue code for a RTTraceAddSomething function.
+ *
+ * This will release the trace buffer reference.
+ */
+#define RTTRACEBUF_ADD_EPILOGUE() \
+ cRefs = ASMAtomicDecU32(&pVolatile->cRefs); \
+ if (!cRefs) \
+ rtTraceBufDestroy((RTTRACEBUFINT *)pThis); \
+ return rc
+
+
+#ifndef IN_RC /* Drop this in RC context (too lazy to split the file). */
+
+RTDECL(int) RTTraceBufCreate(PRTTRACEBUF phTraceBuf, uint32_t cEntries, uint32_t cbEntry, uint32_t fFlags)
+{
+ AssertPtrReturn(phTraceBuf, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~(RTTRACEBUF_FLAGS_MASK & ~ RTTRACEBUF_FLAGS_FREE_ME)), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(cbEntry <= RTTRACEBUF_MAX_ENTRIES, ("%#x\n", cbEntry), VERR_OUT_OF_RANGE);
+ AssertMsgReturn(cEntries <= RTTRACEBUF_MAX_ENTRY_SIZE, ("%#x\n", cEntries), VERR_OUT_OF_RANGE);
+
+ /*
+ * Apply default and alignment adjustments.
+ */
+ if (!cbEntry)
+ cbEntry = RTTRACEBUF_DEF_ENTRY_SIZE;
+ else
+ cbEntry = RT_ALIGN_32(cbEntry, RTTRACEBUF_ALIGNMENT);
+
+ if (!cEntries)
+ cEntries = RTTRACEBUF_DEF_ENTRIES;
+ else if (cEntries < RTTRACEBUF_MIN_ENTRIES)
+ cEntries = RTTRACEBUF_MIN_ENTRIES;
+
+ /*
+ * Calculate the required buffer size, allocte it and hand it on to the
+ * carver API.
+ */
+ size_t cbBlock = cbEntry * cEntries
+ + RT_ALIGN_Z(sizeof(RTTRACEBUFINT), RTTRACEBUF_ALIGNMENT)
+ + RT_ALIGN_Z(sizeof(RTTRACEBUFVOLATILE), RTTRACEBUF_ALIGNMENT);
+ void *pvBlock = RTMemAlloc(cbBlock);
+ if (!((uintptr_t)pvBlock & (RTTRACEBUF_ALIGNMENT - 1)))
+ {
+ RTMemFree(pvBlock);
+ cbBlock += RTTRACEBUF_ALIGNMENT - 1;
+ pvBlock = RTMemAlloc(cbBlock);
+ }
+ int rc;
+ if (pvBlock)
+ {
+ rc = RTTraceBufCarve(phTraceBuf, cEntries, cbEntry, fFlags, pvBlock, &cbBlock);
+ if (RT_FAILURE(rc))
+ RTMemFree(pvBlock);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTTraceBufCarve(PRTTRACEBUF phTraceBuf, uint32_t cEntries, uint32_t cbEntry, uint32_t fFlags,
+ void *pvBlock, size_t *pcbBlock)
+{
+ AssertPtrReturn(phTraceBuf, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTTRACEBUF_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertMsgReturn(cbEntry <= RTTRACEBUF_MAX_ENTRIES, ("%#x\n", cbEntry), VERR_OUT_OF_RANGE);
+ AssertMsgReturn(cEntries <= RTTRACEBUF_MAX_ENTRY_SIZE, ("%#x\n", cEntries), VERR_OUT_OF_RANGE);
+ AssertPtrReturn(pcbBlock, VERR_INVALID_POINTER);
+ size_t const cbBlock = *pcbBlock;
+ AssertReturn(RT_VALID_PTR(pvBlock) || !cbBlock, VERR_INVALID_POINTER);
+
+ /*
+ * Apply defaults, align sizes and check against available buffer space.
+ * This code can be made a bit more clever, if someone feels like it.
+ */
+ size_t const cbHdr = RT_ALIGN_Z(sizeof(RTTRACEBUFINT), RTTRACEBUF_ALIGNMENT)
+ + RT_ALIGN_Z(sizeof(RTTRACEBUFVOLATILE), RTTRACEBUF_ALIGNMENT);
+ size_t const cbEntryBuf = cbBlock > cbHdr ? cbBlock - cbHdr : 0;
+ if (cbEntry)
+ cbEntry = RT_ALIGN_32(cbEntry, RTTRACEBUF_ALIGNMENT);
+ else
+ {
+ if (!cbEntryBuf)
+ {
+ cbEntry = RTTRACEBUF_DEF_ENTRY_SIZE;
+ cEntries = RTTRACEBUF_DEF_ENTRIES;
+ }
+ else if (cEntries)
+ {
+ size_t cbEntryZ = cbBlock / cEntries;
+ cbEntryZ &= ~(RTTRACEBUF_ALIGNMENT - 1);
+ if (cbEntryZ > RTTRACEBUF_MAX_ENTRIES)
+ cbEntryZ = RTTRACEBUF_MAX_ENTRIES;
+ cbEntry = (uint32_t)cbEntryZ;
+ }
+ else if (cbBlock >= RT_ALIGN_32(512, RTTRACEBUF_ALIGNMENT) * 256)
+ cbEntry = RT_ALIGN_32(512, RTTRACEBUF_ALIGNMENT);
+ else if (cbBlock >= RT_ALIGN_32(256, RTTRACEBUF_ALIGNMENT) * 64)
+ cbEntry = RT_ALIGN_32(256, RTTRACEBUF_ALIGNMENT);
+ else if (cbBlock >= RT_ALIGN_32(128, RTTRACEBUF_ALIGNMENT) * 32)
+ cbEntry = RT_ALIGN_32(128, RTTRACEBUF_ALIGNMENT);
+ else
+ cbEntry = sizeof(RTTRACEBUFENTRY);
+ }
+ Assert(RT_ALIGN_32(cbEntry, RTTRACEBUF_ALIGNMENT) == cbEntry);
+
+ if (!cEntries)
+ {
+ size_t cEntriesZ = cbEntryBuf / cbEntry;
+ if (cEntriesZ > RTTRACEBUF_MAX_ENTRIES)
+ cEntriesZ = RTTRACEBUF_MAX_ENTRIES;
+ cEntries = (uint32_t)cEntriesZ;
+ }
+ if (cEntries < RTTRACEBUF_MIN_ENTRIES)
+ cEntries = RTTRACEBUF_MIN_ENTRIES;
+
+ uint32_t offVolatile = RTTRACEBUF_ALIGNMENT - ((uintptr_t)pvBlock & (RTTRACEBUF_ALIGNMENT - 1));
+ if (offVolatile < sizeof(RTTRACEBUFINT))
+ offVolatile += RTTRACEBUF_ALIGNMENT;
+ size_t cbReqBlock = offVolatile
+ + RT_ALIGN_Z(sizeof(RTTRACEBUFVOLATILE), RTTRACEBUF_ALIGNMENT)
+ + cbEntry * cEntries;
+ if (*pcbBlock < cbReqBlock)
+ {
+ *pcbBlock = cbReqBlock;
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /*
+ * Do the carving.
+ */
+ memset(pvBlock, 0, cbBlock);
+
+ RTTRACEBUFINT *pThis = (RTTRACEBUFINT *)pvBlock;
+ pThis->u32Magic = RTTRACEBUF_MAGIC;
+ pThis->cbEntry = cbEntry;
+ pThis->cEntries = cEntries;
+ pThis->fFlags = fFlags;
+ pThis->offVolatile = offVolatile;
+ pThis->offEntries = offVolatile + RT_ALIGN_Z(sizeof(RTTRACEBUFVOLATILE), RTTRACEBUF_ALIGNMENT);
+
+ PRTTRACEBUFVOLATILE pVolatile = (PRTTRACEBUFVOLATILE)((uint8_t *)pThis + offVolatile);
+ pVolatile->cRefs = 1;
+ pVolatile->iEntry = 0;
+
+ *pcbBlock = cbBlock - cbReqBlock;
+ *phTraceBuf = pThis;
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RC */
+
+
+/**
+ * Destructor.
+ *
+ * @param pThis The trace buffer to destroy.
+ */
+static void rtTraceBufDestroy(RTTRACEBUFINT *pThis)
+{
+ AssertReturnVoid(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTTRACEBUF_MAGIC_DEAD, RTTRACEBUF_MAGIC));
+ if (pThis->fFlags & RTTRACEBUF_FLAGS_FREE_ME)
+ {
+#ifdef IN_RC
+ AssertReleaseFailed();
+#else
+ RTMemFree(pThis);
+#endif
+ }
+}
+
+
+RTDECL(uint32_t) RTTraceBufRetain(RTTRACEBUF hTraceBuf)
+{
+ PCRTTRACEBUFINT pThis = hTraceBuf;
+ RTTRACEBUF_VALID_RETURN_RC(pThis, UINT32_MAX);
+ return ASMAtomicIncU32(&RTTRACEBUF_TO_VOLATILE(pThis)->cRefs);
+}
+
+
+RTDECL(uint32_t) RTTraceBufRelease(RTTRACEBUF hTraceBuf)
+{
+ if (hTraceBuf == NIL_RTTRACEBUF)
+ return 0;
+
+ PCRTTRACEBUFINT pThis = hTraceBuf;
+ RTTRACEBUF_VALID_RETURN_RC(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&RTTRACEBUF_TO_VOLATILE(pThis)->cRefs);
+ if (!cRefs)
+ rtTraceBufDestroy((RTTRACEBUFINT *)pThis);
+ return cRefs;
+}
+
+
+RTDECL(int) RTTraceBufAddMsg(RTTRACEBUF hTraceBuf, const char *pszMsg)
+{
+ RTTRACEBUF_ADD_PROLOGUE(hTraceBuf);
+ RTStrCopy(pszBuf, cchBuf, pszMsg);
+ RTTRACEBUF_ADD_EPILOGUE();
+}
+
+
+RTDECL(int) RTTraceBufAddMsgEx( RTTRACEBUF hTraceBuf, const char *pszMsg, size_t cbMaxMsg)
+{
+ RTTRACEBUF_ADD_PROLOGUE(hTraceBuf);
+ RTStrCopyEx(pszBuf, cchBuf, pszMsg, cbMaxMsg);
+ RTTRACEBUF_ADD_EPILOGUE();
+}
+
+
+RTDECL(int) RTTraceBufAddMsgF(RTTRACEBUF hTraceBuf, const char *pszMsgFmt, ...)
+{
+ int rc;
+ va_list va;
+ va_start(va, pszMsgFmt);
+ rc = RTTraceBufAddMsgV(hTraceBuf, pszMsgFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(int) RTTraceBufAddMsgV(RTTRACEBUF hTraceBuf, const char *pszMsgFmt, va_list va)
+{
+ RTTRACEBUF_ADD_PROLOGUE(hTraceBuf);
+ RTStrPrintfV(pszBuf, cchBuf, pszMsgFmt, va);
+ RTTRACEBUF_ADD_EPILOGUE();
+}
+
+
+RTDECL(int) RTTraceBufAddPos(RTTRACEBUF hTraceBuf, RT_SRC_POS_DECL)
+{
+ RTTRACEBUF_ADD_PROLOGUE(hTraceBuf);
+ RTTRACEBUF_ADD_STORE_SRC_POS();
+ RTTRACEBUF_ADD_EPILOGUE();
+}
+
+
+RTDECL(int) RTTraceBufAddPosMsg(RTTRACEBUF hTraceBuf, RT_SRC_POS_DECL, const char *pszMsg)
+{
+ RTTRACEBUF_ADD_PROLOGUE(hTraceBuf);
+ RTTRACEBUF_ADD_STORE_SRC_POS();
+ RTStrCopy(pszBuf, cchBuf, pszMsg);
+ RTTRACEBUF_ADD_EPILOGUE();
+}
+
+
+RTDECL(int) RTTraceBufAddPosMsgEx(RTTRACEBUF hTraceBuf, RT_SRC_POS_DECL, const char *pszMsg, size_t cbMaxMsg)
+{
+ RTTRACEBUF_ADD_PROLOGUE(hTraceBuf);
+ RTTRACEBUF_ADD_STORE_SRC_POS();
+ RTStrCopyEx(pszBuf, cchBuf, pszMsg, cbMaxMsg);
+ RTTRACEBUF_ADD_EPILOGUE();
+}
+
+
+RTDECL(int) RTTraceBufAddPosMsgF(RTTRACEBUF hTraceBuf, RT_SRC_POS_DECL, const char *pszMsgFmt, ...)
+{
+ int rc;
+ va_list va;
+ va_start(va, pszMsgFmt);
+ rc = RTTraceBufAddPosMsgV(hTraceBuf, RT_SRC_POS_ARGS, pszMsgFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(int) RTTraceBufAddPosMsgV(RTTRACEBUF hTraceBuf, RT_SRC_POS_DECL, const char *pszMsgFmt, va_list va)
+{
+ RTTRACEBUF_ADD_PROLOGUE(hTraceBuf);
+ RTTRACEBUF_ADD_STORE_SRC_POS();
+ RTStrPrintfV(pszBuf, cchBuf, pszMsgFmt, va);
+ RTTRACEBUF_ADD_EPILOGUE();
+}
+
+
+RTDECL(int) RTTraceBufEnumEntries(RTTRACEBUF hTraceBuf, PFNRTTRACEBUFCALLBACK pfnCallback, void *pvUser)
+{
+ int rc = VINF_SUCCESS;
+ uint32_t iBase;
+ uint32_t cLeft;
+ PCRTTRACEBUFINT pThis;
+ RTTRACEBUF_RESOLVE_VALIDATE_RETAIN_RETURN(hTraceBuf, pThis);
+
+ iBase = ASMAtomicReadU32(&RTTRACEBUF_TO_VOLATILE(pThis)->iEntry);
+ cLeft = pThis->cEntries;
+ while (cLeft--)
+ {
+ PRTTRACEBUFENTRY pEntry;
+
+ iBase %= pThis->cEntries;
+ pEntry = RTTRACEBUF_TO_ENTRY(pThis, iBase);
+ if (pEntry->NanoTS)
+ {
+ rc = pfnCallback((RTTRACEBUF)pThis, cLeft, pEntry->NanoTS, pEntry->idCpu, pEntry->szMsg, pvUser);
+ if (rc != VINF_SUCCESS)
+ break;
+ }
+
+ /* next */
+ iBase += 1;
+ }
+
+ RTTRACEBUF_DROP_REFERENCE(pThis);
+ return rc;
+}
+
+
+RTDECL(uint32_t) RTTraceBufGetEntrySize(RTTRACEBUF hTraceBuf)
+{
+ PCRTTRACEBUFINT pThis = hTraceBuf;
+ RTTRACEBUF_VALID_RETURN_RC(pThis, 0);
+ return pThis->cbEntry;
+}
+
+
+RTDECL(uint32_t) RTTraceBufGetEntryCount(RTTRACEBUF hTraceBuf)
+{
+ PCRTTRACEBUFINT pThis = hTraceBuf;
+ RTTRACEBUF_VALID_RETURN_RC(pThis, 0);
+ return pThis->cEntries;
+}
+
+
+RTDECL(bool) RTTraceBufDisable(RTTRACEBUF hTraceBuf)
+{
+ PCRTTRACEBUFINT pThis = hTraceBuf;
+ RTTRACEBUF_VALID_RETURN_RC(pThis, false);
+ return !ASMAtomicBitTestAndSet((void volatile *)&pThis->fFlags, RTTRACEBUF_FLAGS_DISABLED_BIT);
+}
+
+
+RTDECL(bool) RTTraceBufEnable(RTTRACEBUF hTraceBuf)
+{
+ PCRTTRACEBUFINT pThis = hTraceBuf;
+ RTTRACEBUF_VALID_RETURN_RC(pThis, false);
+ return !ASMAtomicBitTestAndClear((void volatile *)&pThis->fFlags, RTTRACEBUF_FLAGS_DISABLED_BIT);
+}
+
+
+/*
+ *
+ * Move the following to a separate file, consider using the enumerator.
+ *
+ */
+
+RTDECL(int) RTTraceBufDumpToLog(RTTRACEBUF hTraceBuf)
+{
+ uint32_t iBase;
+ uint32_t cLeft;
+ PCRTTRACEBUFINT pThis;
+ RTTRACEBUF_RESOLVE_VALIDATE_RETAIN_RETURN(hTraceBuf, pThis);
+
+ iBase = ASMAtomicReadU32(&RTTRACEBUF_TO_VOLATILE(pThis)->iEntry);
+ cLeft = pThis->cEntries;
+ while (cLeft--)
+ {
+ PRTTRACEBUFENTRY pEntry;
+
+ iBase %= pThis->cEntries;
+ pEntry = RTTRACEBUF_TO_ENTRY(pThis, iBase);
+ if (pEntry->NanoTS)
+ RTLogPrintf("%04u/%'llu/%02x: %s\n", cLeft, pEntry->NanoTS, pEntry->idCpu, pEntry->szMsg);
+
+ /* next */
+ iBase += 1;
+ }
+
+ RTTRACEBUF_DROP_REFERENCE(pThis);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTTraceBufDumpToAssert(RTTRACEBUF hTraceBuf)
+{
+ uint32_t iBase;
+ uint32_t cLeft;
+ PCRTTRACEBUFINT pThis;
+ RTTRACEBUF_RESOLVE_VALIDATE_RETAIN_RETURN(hTraceBuf, pThis);
+
+ iBase = ASMAtomicReadU32(&RTTRACEBUF_TO_VOLATILE(pThis)->iEntry);
+ cLeft = pThis->cEntries;
+ while (cLeft--)
+ {
+ PRTTRACEBUFENTRY pEntry;
+
+ iBase %= pThis->cEntries;
+ pEntry = RTTRACEBUF_TO_ENTRY(pThis, iBase);
+ if (pEntry->NanoTS)
+ RTAssertMsg2AddWeak("%u/%'llu/%02x: %s\n", cLeft, pEntry->NanoTS, pEntry->idCpu, pEntry->szMsg);
+
+ /* next */
+ iBase += 1;
+ }
+
+ RTTRACEBUF_DROP_REFERENCE(pThis);
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/log/tracedefault.cpp b/src/VBox/Runtime/common/log/tracedefault.cpp
new file mode 100644
index 00000000..2ebb77ac
--- /dev/null
+++ b/src/VBox/Runtime/common/log/tracedefault.cpp
@@ -0,0 +1,92 @@
+/* $Id: tracedefault.cpp $ */
+/** @file
+ * IPRT - Tracebuffer common functions.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/trace.h>
+
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The default trace buffer handle. */
+static RTTRACEBUF g_hDefaultTraceBuf = NIL_RTTRACEBUF;
+
+
+
+RTDECL(int) RTTraceSetDefaultBuf(RTTRACEBUF hTraceBuf)
+{
+ /* Retain the new buffer. */
+ if (hTraceBuf != NIL_RTTRACEBUF)
+ {
+ uint32_t cRefs = RTTraceBufRetain(hTraceBuf);
+ if (cRefs >= _1M)
+ return VERR_INVALID_HANDLE;
+ }
+
+ RTTRACEBUF hOldTraceBuf;
+#ifdef IN_RC
+ hOldTraceBuf = (RTTRACEBUF)ASMAtomicXchgPtr((void **)&g_hDefaultTraceBuf, hTraceBuf);
+#else
+ ASMAtomicXchgHandle(&g_hDefaultTraceBuf, hTraceBuf, &hOldTraceBuf);
+#endif
+
+ if ( hOldTraceBuf != NIL_RTTRACEBUF
+ && hOldTraceBuf != hTraceBuf)
+ {
+ /* Race prevention kludge. */
+#ifndef IN_RC
+ RTThreadSleep(33);
+#endif
+ RTTraceBufRelease(hOldTraceBuf);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(RTTRACEBUF) RTTraceGetDefaultBuf(void)
+{
+ return g_hDefaultTraceBuf;
+}
+
diff --git a/src/VBox/Runtime/common/log/tracelogreader.cpp b/src/VBox/Runtime/common/log/tracelogreader.cpp
new file mode 100644
index 00000000..0f02a5c9
--- /dev/null
+++ b/src/VBox/Runtime/common/log/tracelogreader.cpp
@@ -0,0 +1,1945 @@
+/* $Id: tracelogreader.cpp $ */
+/** @file
+ * IPRT - Trace log reader.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/formats/tracelog.h>
+#include <iprt/tracelog.h>
+
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/strcache.h>
+#include <iprt/time.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a trace log reader instance. */
+typedef struct RTTRACELOGRDRINT *PRTTRACELOGRDRINT;
+
+/**
+ * State enums the trace log reader can be in.
+ */
+typedef enum RTTRACELOGRDRSTATE
+{
+ /** Invalid state. */
+ RTTRACELOGRDRSTATE_INVALID = 0,
+ /** The header is currently being received. */
+ RTTRACELOGRDRSTATE_RECV_HDR,
+ /** The header description is being received (if available). */
+ RTTRACELOGRDRSTATE_RECV_HDR_DESC,
+ /** the magic is being received to decide what to do next. */
+ RTTRACELOGRDRSTATE_RECV_MAGIC,
+ /** The event descriptor is being received. */
+ RTTRACELOGRDRSTATE_RECV_EVT_DESC,
+ /** The event descriptor ID is being received. */
+ RTTRACELOGRDRSTATE_RECV_EVT_DESC_ID,
+ /** The event descriptor description is being received. */
+ RTTRACELOGRDRSTATE_RECV_EVT_DESC_DESC,
+ /** The event item descriptor is being received. */
+ RTTRACELOGRDRSTATE_RECV_EVT_ITEM_DESC,
+ /** The event item descriptor name is being received. */
+ RTTRACELOGRDRSTATE_RECV_EVT_ITEM_DESC_NAME,
+ /** The event item descriptor description is being received. */
+ RTTRACELOGRDRSTATE_RECV_EVT_ITEM_DESC_DESC,
+ /** The event marker is being received. */
+ RTTRACELOGRDRSTATE_RECV_EVT_MARKER,
+ /** The event data is being received. */
+ RTTRACELOGRDRSTATE_RECV_EVT_DATA,
+ /** 32bit hack. */
+ RTTRACELOGRDRSTATE_32BIT_HACK = 0x7fffffff
+} RTTRACELOGRDRSTATE;
+
+
+/** Pointer to internal trace log reader event descriptor. */
+typedef struct RTTRACELOGRDREVTDESC *PRTTRACELOGRDREVTDESC;
+/** Pointer to const internal trace log reader event descriptor. */
+typedef const RTTRACELOGRDREVTDESC *PCRTTRACELOGRDREVTDESC;
+
+
+/**
+ * Trace log reader event.
+ */
+typedef struct RTTRACELOGRDREVTINT
+{
+ /** List node for the global list of events. */
+ RTLISTNODE NdGlob;
+ /** The trace log reader instance the event belongs to. */
+ PRTTRACELOGRDRINT pRdr;
+ /** Trace log sequence number. */
+ uint64_t u64SeqNo;
+ /** Marker time stamp. */
+ uint64_t u64Ts;
+ /** Pointer to the event descriptor, describing the data layout. */
+ PCRTTRACELOGRDREVTDESC pEvtDesc;
+ /** Parent group ID if assigned. */
+ RTTRACELOGEVTGRPID idGrpParent;
+ /** Group ID this event belongs to. */
+ RTTRACELOGEVTGRPID idGrp;
+ /** Pointer to the array holding the non static raw data size values. */
+ size_t *pacbRawData;
+ /** Overall event data size in bytes, including non static data. */
+ size_t cbEvtData;
+ /** Event data, variable in size. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ uint8_t abEvtData[RT_FLEXIBLE_ARRAY];
+} RTTRACELOGRDREVTINT;
+/** Pointer to a trace log reader event. */
+typedef RTTRACELOGRDREVTINT *PRTTRACELOGRDREVTINT;
+/** Pointer to a const trace log reader event. */
+typedef const RTTRACELOGRDREVTINT *PCRTTRACELOGRDREVTINT;
+
+
+/**
+ * Trace log reader internal event descriptor.
+ */
+typedef struct RTTRACELOGRDREVTDESC
+{
+ /** Overall size of the event data not counting variable raw data items. */
+ size_t cbEvtData;
+ /** Number of non static raw binary items in the descriptor. */
+ uint32_t cRawDataNonStatic;
+ /** Current event item descriptor to work on. */
+ uint32_t idxEvtItemCur;
+ /** Size of the name of the current item to work on. */
+ size_t cbStrItemName;
+ /** Size of the description of the current item to work on. */
+ size_t cbStrItemDesc;
+ /** Size of the ID in bytes including the terminator. */
+ size_t cbStrId;
+ /** Size of the description in bytes including the terminator. */
+ size_t cbStrDesc;
+ /** Embedded event descriptor. */
+ RTTRACELOGEVTDESC EvtDesc;
+ /** Array of event item descriptors, variable in size. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ RTTRACELOGEVTITEMDESC aEvtItemDesc[RT_FLEXIBLE_ARRAY];
+} RTTRACELOGRDREVTDESC;
+
+
+/**
+ * Trace log reader instance data.
+ */
+typedef struct RTTRACELOGRDRINT
+{
+ /** Magic for identification. */
+ uint32_t u32Magic;
+ /** Stream out callback. */
+ PFNRTTRACELOGRDRSTREAM pfnStreamIn;
+ /** Stream close callback .*/
+ PFNRTTRACELOGSTREAMCLOSE pfnStreamClose;
+ /** Opaque user data passed to the stream callback. */
+ void *pvUser;
+ /** Mutex protecting the structure. */
+ RTSEMMUTEX hMtx;
+ /** Current state the reader is in. */
+ RTTRACELOGRDRSTATE enmState;
+ /** Flag whether to convert all inputs to the host endianess. */
+ bool fConvEndianess;
+ /** String cache for descriptions and IDs. */
+ RTSTRCACHE hStrCache;
+ /** Size of the description in characters. */
+ size_t cchDesc;
+ /** Pointer to the description if set. */
+ const char *pszDesc;
+ /** List of received events (PRTTRACELOGRDREVTINT::NdGlob). */
+ RTLISTANCHOR LstEvts;
+ /** Number of event descriptors known. */
+ uint32_t cEvtDescsCur;
+ /** Maximum number of event descriptors currently fitting into the array. */
+ uint32_t cEvtDescsMax;
+ /** Pointer to the array of event descriptor pointers. */
+ PRTTRACELOGRDREVTDESC *papEvtDescs;
+ /** Current event descriptor being initialised. */
+ PRTTRACELOGRDREVTDESC pEvtDescCur;
+ /** The current event being received. */
+ PRTTRACELOGRDREVTINT pEvtCur;
+ /** Last seen sequence number. */
+ uint64_t u64SeqNoLast;
+ /** Size of the scratch buffer holding the received data. */
+ size_t cbScratch;
+ /** Pointer to the scratch buffer. */
+ uint8_t *pbScratch;
+ /** Current offset into the scratch buffer to write fetched data to. */
+ uint32_t offScratch;
+ /** Number of bytes left to receive until processing the data. */
+ size_t cbRecvLeft;
+ /** Starting timestamp fetched from the header. */
+ uint64_t u64TsStart;
+ /** Size of the pointer type in the trace log. */
+ size_t cbTypePtr;
+ /** Size of the size_t type in the trace log. */
+ size_t cbTypeSize;
+} RTTRACELOGRDRINT;
+
+
+/**
+ * Internal reader iterator instance data.
+ */
+typedef struct RTTRACELOGRDRITINT
+{
+ /** The reader instance this iterator belongs to. */
+ PRTTRACELOGRDRINT pRdr;
+ /** The current event. */
+ PRTTRACELOGRDREVTINT pEvt;
+} RTTRACELOGRDRITINT;
+/** Pointer to an internal reader iterator instance. */
+typedef RTTRACELOGRDRITINT *PRTTRACELOGRDRITINT;
+
+
+/**
+ * Trace log handler state callback.
+ *
+ * @returns IPRT status code.
+ * @param pThis The trace log reader instance.
+ * @param penmEvt Where to store the event indicator if a user visible event happened.
+ * @param pfContinuePoll Where to store the flag whether to continue polling.
+ */
+typedef DECLCALLBACKTYPE(int, FNRTTRACELOGRDRSTATEHANDLER,(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll));
+/** Pointer to a trace log reader state handler. */
+typedef FNRTTRACELOGRDRSTATEHANDLER *PFNRTTRACELOGRDRSTATEHANDLER;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) rtTraceLogRdrHdrRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrHdrDescRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrMagicRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrEvtDescRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrEvtDescIdRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrEvtDescDescriptionRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrEvtItemDescRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrEvtItemDescNameRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrEvtItemDescDescriptionRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrEvtMarkerRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+static DECLCALLBACK(int) rtTraceLogRdrEvtDataRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt, bool *pfContinuePoll);
+
+/**
+ * State handlers.
+ * @note The struct wrapper is for working around a Clang nothrow attrib oddity.
+ */
+static struct { PFNRTTRACELOGRDRSTATEHANDLER pfn; } g_aStateHandlers[] =
+{
+ { NULL },
+ { rtTraceLogRdrHdrRecvd },
+ { rtTraceLogRdrHdrDescRecvd },
+ { rtTraceLogRdrMagicRecvd },
+ { rtTraceLogRdrEvtDescRecvd },
+ { rtTraceLogRdrEvtDescIdRecvd },
+ { rtTraceLogRdrEvtDescDescriptionRecvd },
+ { rtTraceLogRdrEvtItemDescRecvd },
+ { rtTraceLogRdrEvtItemDescNameRecvd },
+ { rtTraceLogRdrEvtItemDescDescriptionRecvd },
+ { rtTraceLogRdrEvtMarkerRecvd },
+ { rtTraceLogRdrEvtDataRecvd },
+ { NULL }
+};
+
+/**
+ * Wrapper around the stream in callback.
+ *
+ * @returns IPRT status code returned by the stream callback.
+ * @param pThis The trace log reader instance.
+ * @param pvBuf The data to stream.
+ * @param cbBuf Number of bytes to read in.
+ * @param pcbRead Where to store the amount of data read.
+ * @param cMsTimeout How long to wait for something to arrive.
+ */
+DECLINLINE(int) rtTraceLogRdrStreamRead(PRTTRACELOGRDRINT pThis, void *pvBuf, size_t cbBuf,
+ size_t *pcbRead, RTMSINTERVAL cMsTimeout)
+{
+ return pThis->pfnStreamIn(pThis->pvUser, pvBuf, cbBuf, pcbRead, cMsTimeout);
+}
+
+
+/**
+ * Converts the header endianess to the host endianess.
+ *
+ * @param pHdr The trace log header to convert.
+ */
+static void rtTraceLogRdrHdrEndianessConv(PTRACELOGHDR pHdr)
+{
+ pHdr->u32Endianess = RT_BSWAP_U32(pHdr->u32Endianess);
+ pHdr->u32Version = RT_BSWAP_U32(pHdr->u32Version);
+ pHdr->fFlags = RT_BSWAP_U32(pHdr->fFlags);
+ pHdr->cbStrDesc = RT_BSWAP_U32(pHdr->cbStrDesc);
+ pHdr->u64TsStart = RT_BSWAP_U64(pHdr->u64TsStart);
+}
+
+
+/**
+ * Converts the event descriptor endianess to the host endianess.
+ *
+ * @param pEvtDesc The trace log event descriptor to convert.
+ */
+static void rtTraceLogRdrEvtDescEndianessConv(PTRACELOGEVTDESC pEvtDesc)
+{
+ pEvtDesc->u32Id = RT_BSWAP_U32(pEvtDesc->u32Id);
+ pEvtDesc->u32Severity = RT_BSWAP_U32(pEvtDesc->u32Severity);
+ pEvtDesc->cbStrId = RT_BSWAP_U32(pEvtDesc->cbStrId);
+ pEvtDesc->cbStrDesc = RT_BSWAP_U32(pEvtDesc->cbStrDesc);
+ pEvtDesc->cEvtItems = RT_BSWAP_U32(pEvtDesc->cEvtItems);
+}
+
+
+/**
+ * Converts the event item descriptor endianess to host endianess.
+ *
+ * @param pEvtItemDesc The trace log event item descriptor to convert.
+ */
+static void rtTraceLogRdrEvtItemDescEndianessConv(PTRACELOGEVTITEMDESC pEvtItemDesc)
+{
+ pEvtItemDesc->cbStrName = RT_BSWAP_U32(pEvtItemDesc->cbStrName);
+ pEvtItemDesc->cbStrDesc = RT_BSWAP_U32(pEvtItemDesc->cbStrDesc);
+ pEvtItemDesc->u32Type = RT_BSWAP_U32(pEvtItemDesc->u32Type);
+ pEvtItemDesc->cbRawData = RT_BSWAP_U32(pEvtItemDesc->cbRawData);
+}
+
+
+/**
+ * Converts the event marker endianess to host endianess.
+ *
+ * @param pEvt The trace log event marker to convert.
+ */
+static void rtTraceLogRdrEvtEndianessConv(PTRACELOGEVT pEvt)
+{
+ pEvt->u64SeqNo = RT_BSWAP_U64(pEvt->u64SeqNo);
+ pEvt->u64Ts = RT_BSWAP_U64(pEvt->u64Ts);
+ pEvt->u64EvtGrpId = RT_BSWAP_U64(pEvt->u64EvtGrpId);
+ pEvt->u64EvtParentGrpId = RT_BSWAP_U64(pEvt->u64EvtParentGrpId);
+ pEvt->fFlags = RT_BSWAP_U32(pEvt->fFlags);
+ pEvt->u32EvtDescId = RT_BSWAP_U32(pEvt->u32EvtDescId);
+ pEvt->cbEvtData = RT_BSWAP_U32(pEvt->cbEvtData);
+ pEvt->cRawEvtDataSz = RT_BSWAP_U32(pEvt->cRawEvtDataSz);
+}
+
+
+/**
+ * Converts severity field from stream to API value.
+ *
+ * @returns API severity enum, RTTRACELOGEVTSEVERITY_INVALID if the supplied stream value
+ * is invalid.
+ * @param u32Severity The severity value from the stream.
+ */
+static RTTRACELOGEVTSEVERITY rtTraceLogRdrConvSeverity(uint32_t u32Severity)
+{
+ RTTRACELOGEVTSEVERITY enmSeverity = RTTRACELOGEVTSEVERITY_INVALID;
+
+ switch (u32Severity)
+ {
+ case TRACELOG_EVTDESC_SEVERITY_INFO:
+ enmSeverity = RTTRACELOGEVTSEVERITY_INFO;
+ break;
+ case TRACELOG_EVTDESC_SEVERITY_WARNING:
+ enmSeverity = RTTRACELOGEVTSEVERITY_WARNING;
+ break;
+ case TRACELOG_EVTDESC_SEVERITY_ERROR:
+ enmSeverity = RTTRACELOGEVTSEVERITY_ERROR;
+ break;
+ case TRACELOG_EVTDESC_SEVERITY_FATAL:
+ enmSeverity = RTTRACELOGEVTSEVERITY_FATAL;
+ break;
+ case TRACELOG_EVTDESC_SEVERITY_DEBUG:
+ enmSeverity = RTTRACELOGEVTSEVERITY_DEBUG;
+ break;
+ default:
+ enmSeverity = RTTRACELOGEVTSEVERITY_INVALID;
+ }
+
+ return enmSeverity;
+}
+
+
+/**
+ * Converts type field from stream to API value.
+ *
+ * @returns API type enum, RTTRACELOGTYPE_INVALID if the supplied stream value
+ * is invalid.
+ * @param u32Type The type value from the stream.
+ */
+static RTTRACELOGTYPE rtTraceLogRdrConvType(uint32_t u32Type)
+{
+ RTTRACELOGTYPE enmType = RTTRACELOGTYPE_INVALID;
+
+ switch (u32Type)
+ {
+ case TRACELOG_EVTITEMDESC_TYPE_BOOL:
+ enmType = RTTRACELOGTYPE_BOOL;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_UINT8:
+ enmType = RTTRACELOGTYPE_UINT8;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_INT8:
+ enmType = RTTRACELOGTYPE_INT8;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_UINT16:
+ enmType = RTTRACELOGTYPE_UINT16;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_INT16:
+ enmType = RTTRACELOGTYPE_INT16;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_UINT32:
+ enmType = RTTRACELOGTYPE_UINT32;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_INT32:
+ enmType = RTTRACELOGTYPE_INT32;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_UINT64:
+ enmType = RTTRACELOGTYPE_UINT64;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_INT64:
+ enmType = RTTRACELOGTYPE_INT64;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_FLOAT32:
+ enmType = RTTRACELOGTYPE_FLOAT32;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_FLOAT64:
+ enmType = RTTRACELOGTYPE_FLOAT64;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_RAWDATA:
+ enmType = RTTRACELOGTYPE_RAWDATA;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_POINTER:
+ enmType = RTTRACELOGTYPE_POINTER;
+ break;
+ case TRACELOG_EVTITEMDESC_TYPE_SIZE:
+ enmType = RTTRACELOGTYPE_SIZE;
+ break;
+ default:
+ enmType = RTTRACELOGTYPE_INVALID;
+ }
+
+ return enmType;
+}
+
+
+/**
+ * Converts the type enum to the size of the the event item data in bytes.
+ *
+ * @returns Event item data size in bytes.
+ * @param pThis The trace log reader instance.
+ * @param pEvtItemDesc The event item descriptor.
+ */
+static size_t rtTraceLogRdrGetEvtItemDataSz(PRTTRACELOGRDRINT pThis, PCRTTRACELOGEVTITEMDESC pEvtItemDesc)
+{
+ size_t cb = 0;
+
+ switch (pEvtItemDesc->enmType)
+ {
+ case RTTRACELOGTYPE_BOOL:
+ case RTTRACELOGTYPE_UINT8:
+ case RTTRACELOGTYPE_INT8:
+ {
+ cb = 1;
+ break;
+ }
+ case RTTRACELOGTYPE_UINT16:
+ case RTTRACELOGTYPE_INT16:
+ {
+ cb = 2;
+ break;
+ }
+ case RTTRACELOGTYPE_UINT32:
+ case RTTRACELOGTYPE_INT32:
+ case RTTRACELOGTYPE_FLOAT32:
+ {
+ cb = 4;
+ break;
+ }
+ case RTTRACELOGTYPE_UINT64:
+ case RTTRACELOGTYPE_INT64:
+ case RTTRACELOGTYPE_FLOAT64:
+ {
+ cb = 8;
+ break;
+ }
+ case RTTRACELOGTYPE_RAWDATA:
+ {
+ cb = pEvtItemDesc->cbRawData;
+ break;
+ }
+ case RTTRACELOGTYPE_POINTER:
+ {
+ cb = pThis->cbTypePtr;
+ break;
+ }
+ case RTTRACELOGTYPE_SIZE:
+ {
+ cb = pThis->cbTypeSize;
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid type %d\n", pEvtItemDesc->enmType));
+ }
+
+ return cb;
+}
+
+
+/**
+ * Calculates the overall event data size from the items in the event descriptor.
+ *
+ * @param pThis The trace log reader instance.
+ * @param pEvtDesc The event descriptor.
+ */
+static void rtTraceLogRdrEvtCalcEvtDataSz(PRTTRACELOGRDRINT pThis, PRTTRACELOGRDREVTDESC pEvtDesc)
+{
+ pEvtDesc->cbEvtData = 0;
+ pEvtDesc->cRawDataNonStatic = 0;
+
+ for (unsigned i = 0; i < pEvtDesc->EvtDesc.cEvtItems; i++)
+ {
+ PCRTTRACELOGEVTITEMDESC pEvtItemDesc = &pEvtDesc->aEvtItemDesc[i];
+
+ pEvtDesc->cbEvtData += rtTraceLogRdrGetEvtItemDataSz(pThis, pEvtItemDesc);
+ if ( pEvtItemDesc->enmType == RTTRACELOGTYPE_RAWDATA
+ && pEvtItemDesc->cbRawData == 0)
+ pEvtDesc->cRawDataNonStatic++;
+ }
+}
+
+
+/**
+ * Ensures that the scratch buffer can hold at least the given amount of data.
+ *
+ * @returns IPRT status code.
+ * @param pThis The trace log reader instance.
+ * @param cbScratch New size of the scratch buffer in bytes.
+ */
+static int rtTraceLogRdrScratchEnsureSz(PRTTRACELOGRDRINT pThis, size_t cbScratch)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->cbScratch < cbScratch)
+ {
+ cbScratch = RT_ALIGN_Z(cbScratch, 64);
+ uint8_t *pbScratchNew = (uint8_t *)RTMemRealloc(pThis->pbScratch, cbScratch);
+ if (RT_LIKELY(pbScratchNew))
+ {
+ pThis->cbScratch = cbScratch;
+ pThis->pbScratch = pbScratchNew;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Advances to the next state resetting the scratch/receive buffers to the given state.
+ *
+ * @returns IPRT status.
+ * @param pThis The trace log reader instance.
+ * @param enmState The next state.
+ * @param cbRecv How much to receive before processing the new data.
+ * @param offScratch Offset to set the receive buffer to (used
+ * when the magic was received which should still be saved).
+ */
+static int rtTraceLogRdrStateAdvanceEx(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRSTATE enmState, size_t cbRecv,
+ uint32_t offScratch)
+{
+ Assert(cbRecv >= offScratch);
+
+ pThis->enmState = enmState;
+ pThis->cbRecvLeft = cbRecv - offScratch;
+ pThis->offScratch = offScratch;
+ int rc = rtTraceLogRdrScratchEnsureSz(pThis, cbRecv);
+
+ /* Zero out scratch buffer (don't care whether growing it failed, the old buffer is still there). */
+ memset(pThis->pbScratch + offScratch, 0, pThis->cbScratch - offScratch);
+
+ return rc;
+}
+
+
+/**
+ * Advances to the next state resetting the scratch/receive buffers.
+ *
+ * @returns IPRT status.
+ * @param pThis The trace log reader instance.
+ * @param enmState The next state.
+ * @param cbRecv How much to receive before processing the new data.
+ */
+static int rtTraceLogRdrStateAdvance(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRSTATE enmState, size_t cbRecv)
+{
+ return rtTraceLogRdrStateAdvanceEx(pThis, enmState, cbRecv, 0);
+}
+
+
+/**
+ * Marks a received event descriptor as completed and adds it to the array of known descriptors.
+ *
+ * @returns IPRT status code.
+ * @param pThis The trace log reader instance.
+ * @param pEvtDesc The event descriptor which completed.
+ */
+static int rtTraceLogRdrEvtDescComplete(PRTTRACELOGRDRINT pThis, PRTTRACELOGRDREVTDESC pEvtDesc)
+{
+ int rc = VINF_SUCCESS;
+
+ rtTraceLogRdrEvtCalcEvtDataSz(pThis, pEvtDesc);
+ /* Insert into array of known event descriptors. */
+ if (pThis->cEvtDescsCur == pThis->cEvtDescsMax)
+ {
+ uint32_t cEvtDescsNew = pThis->cEvtDescsMax + 10;
+ size_t cbNew = cEvtDescsNew * sizeof(PRTTRACELOGRDREVTDESC *);
+ PRTTRACELOGRDREVTDESC *papEvtDescsNew = (PRTTRACELOGRDREVTDESC *)RTMemRealloc(pThis->papEvtDescs, cbNew);
+ if (RT_LIKELY(papEvtDescsNew))
+ {
+ pThis->papEvtDescs = papEvtDescsNew;
+ pThis->cEvtDescsMax = cEvtDescsNew;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pThis->papEvtDescs[pThis->cEvtDescsCur++] = pEvtDesc;
+ pThis->pEvtDescCur = NULL;
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_MAGIC, TRACELOG_MAGIC_SZ);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Decides which state to enter next after one event item descriptor was completed successfully.
+ *
+ * @returns IPRT status code.
+ * @param pThis The trace log reader instance.
+ * @param penmEvt Where to store the event indicator if a user visible event happened.
+ * @param pfContinuePoll Where to store the flag whether to continue polling.
+ */
+static int rtTraceLogRdrEvtItemDescComplete(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ RT_NOREF(penmEvt, pfContinuePoll);
+
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGRDREVTDESC pEvtDesc = pThis->pEvtDescCur;
+ pEvtDesc->idxEvtItemCur++;
+
+ /* If this event descriptor is complete add it to the array of known descriptors. */
+ if (pEvtDesc->idxEvtItemCur == pEvtDesc->EvtDesc.cEvtItems)
+ rc = rtTraceLogRdrEvtDescComplete(pThis, pEvtDesc);
+ else
+ {
+ /* Not done yet. */
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_EVT_ITEM_DESC, sizeof(TRACELOGEVTITEMDESC));
+ }
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received header.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrHdrRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ RT_NOREF(penmEvt, pfContinuePoll);
+ int rc = VINF_SUCCESS;
+ PTRACELOGHDR pHdr = (PTRACELOGHDR)pThis->pbScratch;
+
+ /* Verify magic. */
+ if (!memcmp(&pHdr->szMagic[0], TRACELOG_HDR_MAGIC, sizeof(pHdr->szMagic)))
+ {
+ /* Check endianess. */
+ if (pHdr->u32Endianess == TRACELOG_HDR_ENDIANESS)
+ pThis->fConvEndianess = false;
+ else if (RT_BSWAP_U32(pHdr->u32Endianess) == TRACELOG_HDR_ENDIANESS)
+ {
+ pThis->fConvEndianess = true;
+ rtTraceLogRdrHdrEndianessConv(pHdr);
+ }
+ else
+ rc = VERR_TRACELOG_READER_MALFORMED_LOG;
+
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pHdr->u32Endianess == TRACELOG_HDR_ENDIANESS);
+
+ /* Enforce strict limits to avoid exhausting memory. */
+ if ( pHdr->u32Version == TRACELOG_VERSION
+ && pHdr->cbStrDesc < _1K
+ && pHdr->cbTypePtr <= 8
+ && (pHdr->cbTypeSize == 8 || pHdr->cbTypeSize == 4))
+ {
+ pThis->u64TsStart = pHdr->u64TsStart;
+ pThis->cbTypePtr = pHdr->cbTypePtr;
+ pThis->cbTypeSize = pHdr->cbTypeSize;
+ pThis->cchDesc = pHdr->cbStrDesc;
+ pThis->cEvtDescsMax = 10;
+
+ /* Allocate array to hold event descriptors later on. */
+ pThis->papEvtDescs = (PRTTRACELOGRDREVTDESC *)RTMemAllocZ(pThis->cEvtDescsMax * sizeof(PRTTRACELOGRDREVTDESC));
+ if (RT_LIKELY(pThis->papEvtDescs))
+ {
+ /* Switch to the next state. */
+ if (pHdr->cbStrDesc)
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_HDR_DESC, pHdr->cbStrDesc);
+ else
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_MAGIC, TRACELOG_MAGIC_SZ);
+
+ if (RT_SUCCESS(rc))
+ {
+ *penmEvt = RTTRACELOGRDRPOLLEVT_HDR_RECVD;
+ *pfContinuePoll = false;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_TRACELOG_READER_LOG_UNSUPPORTED;
+ }
+ }
+ else
+ rc = VERR_TRACELOG_READER_MALFORMED_LOG;
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received log description.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrHdrDescRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ RT_NOREF(penmEvt, pfContinuePoll);
+ int rc = VINF_SUCCESS;
+ char *pszDesc = (char *)pThis->pbScratch;
+
+ RTStrPurgeEncoding(pszDesc);
+ pThis->pszDesc = RTStrCacheEnterN(pThis->hStrCache, pszDesc, pThis->cchDesc);
+ if (pThis->pszDesc)
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_MAGIC, TRACELOG_MAGIC_SZ);
+ else
+ rc = VERR_NO_STR_MEMORY;
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received magic.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrMagicRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ RT_NOREF(penmEvt, pfContinuePoll);
+ int rc = VINF_SUCCESS;
+ char *pszMagic = (char *)pThis->pbScratch;
+
+ if (!memcmp(pszMagic, TRACELOG_EVTDESC_MAGIC, TRACELOG_MAGIC_SZ))
+ rc = rtTraceLogRdrStateAdvanceEx(pThis, RTTRACELOGRDRSTATE_RECV_EVT_DESC,
+ sizeof(TRACELOGEVTDESC), TRACELOG_MAGIC_SZ);
+ else if (!memcmp(pszMagic, TRACELOG_EVT_MAGIC, TRACELOG_MAGIC_SZ))
+ rc = rtTraceLogRdrStateAdvanceEx(pThis, RTTRACELOGRDRSTATE_RECV_EVT_MARKER,
+ sizeof(TRACELOGEVT), TRACELOG_MAGIC_SZ);
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received event descriptor.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrEvtDescRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ RT_NOREF(penmEvt, pfContinuePoll);
+ int rc = VINF_SUCCESS;
+ PTRACELOGEVTDESC pEvtDesc = (PTRACELOGEVTDESC)pThis->pbScratch;
+ if (pThis->fConvEndianess)
+ rtTraceLogRdrEvtDescEndianessConv(pEvtDesc);
+
+ if ( !memcmp(&pEvtDesc->szMagic[0], TRACELOG_EVTDESC_MAGIC, sizeof(pEvtDesc->szMagic))
+ && pEvtDesc->u32Id == pThis->cEvtDescsCur
+ && (pEvtDesc->cbStrId >= 1 && pEvtDesc->cbStrId < 128)
+ && pEvtDesc->cbStrDesc < _1K
+ && pEvtDesc->cEvtItems < 128)
+ {
+ RTTRACELOGEVTSEVERITY enmSeverity = rtTraceLogRdrConvSeverity(pEvtDesc->u32Severity);
+ if (RT_LIKELY(enmSeverity != RTTRACELOGEVTSEVERITY_INVALID))
+ {
+ /* Allocate new internal event descriptor state. */
+ size_t cbEvtDesc = RT_UOFFSETOF_DYN(RTTRACELOGRDREVTDESC, aEvtItemDesc[pEvtDesc->cEvtItems]);
+ PRTTRACELOGRDREVTDESC pEvtDescInt = (PRTTRACELOGRDREVTDESC)RTMemAllocZ(cbEvtDesc);
+ if (RT_LIKELY(pEvtDescInt))
+ {
+ pEvtDescInt->cbStrId = pEvtDesc->cbStrId;
+ pEvtDescInt->cbStrDesc = pEvtDesc->cbStrDesc;
+ pEvtDescInt->EvtDesc.enmSeverity = enmSeverity;
+ pEvtDescInt->EvtDesc.cEvtItems = pEvtDesc->cEvtItems;
+ pEvtDescInt->EvtDesc.paEvtItemDesc = &pEvtDescInt->aEvtItemDesc[0];
+
+ pThis->pEvtDescCur = pEvtDescInt;
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_EVT_DESC_ID, pEvtDescInt->cbStrId);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_TRACELOG_READER_MALFORMED_LOG;
+ }
+ else
+ rc = VERR_TRACELOG_READER_MALFORMED_LOG;
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received event descriptor ID.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrEvtDescIdRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ RT_NOREF(penmEvt, pfContinuePoll);
+
+ int rc = VINF_SUCCESS;
+ pThis->pEvtDescCur->EvtDesc.pszId = RTStrCacheEnterN(pThis->hStrCache, (const char *)pThis->pbScratch,
+ pThis->pEvtDescCur->cbStrId);
+ if (RT_LIKELY(pThis->pEvtDescCur->EvtDesc.pszId))
+ {
+ if (pThis->pEvtDescCur->cbStrDesc)
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_EVT_DESC_DESC, pThis->pEvtDescCur->cbStrDesc);
+ else if (pThis->pEvtDescCur->EvtDesc.cEvtItems > 0)
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_EVT_ITEM_DESC, sizeof(TRACELOGEVTITEMDESC));
+ else
+ rc = rtTraceLogRdrEvtDescComplete(pThis, pThis->pEvtDescCur);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received event descriptor description.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrEvtDescDescriptionRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ RT_NOREF(penmEvt, pfContinuePoll);
+ int rc = VINF_SUCCESS;
+ pThis->pEvtDescCur->EvtDesc.pszDesc = RTStrCacheEnterN(pThis->hStrCache, (const char *)pThis->pbScratch,
+ pThis->pEvtDescCur->cbStrDesc);
+ if (RT_LIKELY(pThis->pEvtDescCur->EvtDesc.pszDesc))
+ {
+ if (pThis->pEvtDescCur->EvtDesc.cEvtItems > 0)
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_EVT_ITEM_DESC, sizeof(TRACELOGEVTITEMDESC));
+ else
+ rc = rtTraceLogRdrEvtDescComplete(pThis, pThis->pEvtDescCur);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received event item descriptor.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrEvtItemDescRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ RT_NOREF(penmEvt, pfContinuePoll);
+ int rc = VINF_SUCCESS;
+ PTRACELOGEVTITEMDESC pEvtItemDesc = (PTRACELOGEVTITEMDESC)pThis->pbScratch;
+ if (pThis->fConvEndianess)
+ rtTraceLogRdrEvtItemDescEndianessConv(pEvtItemDesc);
+
+ if ( !memcmp(&pEvtItemDesc->szMagic[0], TRACELOG_EVTITEMDESC_MAGIC, sizeof(pEvtItemDesc->szMagic))
+ && (pEvtItemDesc->cbStrName >= 1 && pEvtItemDesc->cbStrName < 128)
+ && pEvtItemDesc->cbStrDesc < _1K
+ && pEvtItemDesc->cbRawData < _1M)
+ {
+ RTTRACELOGTYPE enmType = rtTraceLogRdrConvType(pEvtItemDesc->u32Type);
+ if (RT_LIKELY(enmType != RTTRACELOGTYPE_INVALID))
+ {
+ PRTTRACELOGEVTITEMDESC pEvtDesc = &pThis->pEvtDescCur->aEvtItemDesc[pThis->pEvtDescCur->idxEvtItemCur];
+
+ pThis->pEvtDescCur->cbStrItemName = pEvtItemDesc->cbStrName;
+ pThis->pEvtDescCur->cbStrItemDesc = pEvtItemDesc->cbStrDesc;
+
+ pEvtDesc->enmType = enmType;
+ pEvtDesc->cbRawData = pEvtItemDesc->cbRawData;
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_EVT_ITEM_DESC_NAME, pEvtItemDesc->cbStrName);
+ }
+ else
+ rc = VERR_TRACELOG_READER_MALFORMED_LOG;
+ }
+ else
+ rc = VERR_TRACELOG_READER_MALFORMED_LOG;
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received event item descriptor name.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrEvtItemDescNameRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGEVTITEMDESC pEvtDesc = &pThis->pEvtDescCur->aEvtItemDesc[pThis->pEvtDescCur->idxEvtItemCur];
+ pEvtDesc->pszName = RTStrCacheEnterN(pThis->hStrCache, (const char *)pThis->pbScratch, pThis->pEvtDescCur->cbStrItemName);
+ if (RT_LIKELY(pEvtDesc->pszName))
+ {
+ if (pThis->pEvtDescCur->cbStrItemDesc)
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_EVT_ITEM_DESC_DESC, pThis->pEvtDescCur->cbStrItemDesc);
+ else
+ rc = rtTraceLogRdrEvtItemDescComplete(pThis, penmEvt, pfContinuePoll);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received event item description.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrEvtItemDescDescriptionRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGEVTITEMDESC pEvtDesc = &pThis->pEvtDescCur->aEvtItemDesc[pThis->pEvtDescCur->idxEvtItemCur];
+ pEvtDesc->pszDesc = RTStrCacheEnterN(pThis->hStrCache, (const char *)pThis->pbScratch, pThis->pEvtDescCur->cbStrItemDesc);
+ if (RT_LIKELY(pEvtDesc->pszDesc))
+ rc = rtTraceLogRdrEvtItemDescComplete(pThis, penmEvt, pfContinuePoll);
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles a received event marker.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrEvtMarkerRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ int rc = VINF_SUCCESS;
+ PTRACELOGEVT pEvtStrm = (PTRACELOGEVT)pThis->pbScratch;
+ if (pThis->fConvEndianess)
+ rtTraceLogRdrEvtEndianessConv(pEvtStrm);
+
+ if ( (pEvtStrm->u64SeqNo == pThis->u64SeqNoLast + 1)
+ && !(pEvtStrm->fFlags & ~TRACELOG_EVT_F_VALID)
+ && pEvtStrm->u32EvtDescId < pThis->cEvtDescsCur)
+ {
+ PRTTRACELOGRDREVTDESC pEvtDesc = pThis->papEvtDescs[pEvtStrm->u32EvtDescId];
+ if ( ( !pEvtDesc->cRawDataNonStatic
+ && pEvtStrm->cbEvtData == pEvtDesc->cbEvtData)
+ || ( pEvtDesc->cRawDataNonStatic
+ && pEvtStrm->cbEvtData >= pEvtDesc->cbEvtData
+ && pEvtStrm->cRawEvtDataSz == pEvtDesc->cRawDataNonStatic))
+ {
+ size_t cbEvt = RT_UOFFSETOF_DYN(RTTRACELOGRDREVTINT, abEvtData[pEvtStrm->cbEvtData]);
+ cbEvt += pEvtDesc->cRawDataNonStatic * sizeof(size_t);
+ PRTTRACELOGRDREVTINT pEvt = (PRTTRACELOGRDREVTINT)RTMemAllocZ(cbEvt);
+ if (RT_LIKELY(pEvt))
+ {
+ pEvt->pRdr = pThis;
+ pEvt->u64SeqNo = pEvtStrm->u64SeqNo;
+ pEvt->u64Ts = pEvtStrm->u64Ts;
+ pEvt->pEvtDesc = pEvtDesc;
+ pEvt->cbEvtData = pEvtStrm->cbEvtData;
+ pEvt->pacbRawData = pEvtDesc->cRawDataNonStatic ? (size_t *)&pEvt->abEvtData[pEvtStrm->cbEvtData] : NULL;
+ /** @todo Group handling and parenting. */
+
+ size_t cbEvtDataRecv = pEvtStrm->cRawEvtDataSz * pThis->cbTypeSize + pEvtStrm->cbEvtData;
+ if (cbEvtDataRecv)
+ {
+ pThis->pEvtCur = pEvt;
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_EVT_DATA, cbEvtDataRecv);
+ }
+ else
+ {
+ pThis->pEvtCur = NULL;
+ RTSemMutexRequest(pThis->hMtx, RT_INDEFINITE_WAIT);
+ pThis->u64SeqNoLast = pEvt->u64SeqNo;
+ RTListAppend(&pThis->LstEvts, &pEvt->NdGlob);
+ RTSemMutexRelease(pThis->hMtx);
+ *penmEvt = RTTRACELOGRDRPOLLEVT_TRACE_EVENT_RECVD;
+ *pfContinuePoll = false;
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_MAGIC, TRACELOG_MAGIC_SZ);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_TRACELOG_READER_MALFORMED_LOG;
+ }
+ else
+ rc = VERR_TRACELOG_READER_MALFORMED_LOG;
+
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNRTTRACELOGRDRSTATEHANDLER, Handles received event data.}
+ */
+static DECLCALLBACK(int) rtTraceLogRdrEvtDataRecvd(PRTTRACELOGRDRINT pThis, RTTRACELOGRDRPOLLEVT *penmEvt,
+ bool *pfContinuePoll)
+{
+ RT_NOREF(penmEvt, pfContinuePoll);
+
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGRDREVTINT pEvt = pThis->pEvtCur;
+ PCRTTRACELOGRDREVTDESC pEvtDesc = pEvt->pEvtDesc;
+ uint8_t *pbData = pThis->pbScratch;
+ size_t cbRawDataNonStatic = 0;
+
+ /* Retrieve any raw data size indicators first. */
+ for (unsigned i = 0; i < pEvtDesc->cRawDataNonStatic; i++)
+ {
+ size_t cb = 0;
+ if (pThis->cbTypeSize == 4)
+ {
+ if (pThis->fConvEndianess)
+ cb = RT_BSWAP_U32(*(uint32_t *)pbData);
+ else
+ cb = *(uint32_t *)pbData;
+ pbData += 4;
+ }
+ else if (pThis->cbTypeSize == 8)
+ {
+ if (pThis->fConvEndianess)
+ cb = RT_BSWAP_U64(*(uint64_t *)pbData);
+ else
+ cb = *(uint64_t *)pbData;
+ pbData += 8;
+ }
+ else
+ AssertMsgFailed(("Invalid size_t size %u\n", pThis->cbTypeSize));
+
+ pEvt->pacbRawData[i] = cb;
+ cbRawDataNonStatic += cb;
+ }
+
+ /* Verify that sizes add up. */
+ if (pEvt->cbEvtData == pEvtDesc->cbEvtData + cbRawDataNonStatic)
+ {
+ /* Copy the data over. */
+ memcpy(&pEvt->abEvtData[0], pbData, pEvt->cbEvtData);
+
+ /* Done add event to global list and generate event. */
+ pThis->pEvtCur = NULL;
+ RTSemMutexRequest(pThis->hMtx, RT_INDEFINITE_WAIT);
+ pThis->u64SeqNoLast = pEvt->u64SeqNo;
+ RTListAppend(&pThis->LstEvts, &pEvt->NdGlob);
+ RTSemMutexRelease(pThis->hMtx);
+ *penmEvt = RTTRACELOGRDRPOLLEVT_TRACE_EVENT_RECVD;
+ *pfContinuePoll = false;
+ rc = rtTraceLogRdrStateAdvance(pThis, RTTRACELOGRDRSTATE_RECV_MAGIC, TRACELOG_MAGIC_SZ);
+ }
+ else
+ rc = VERR_TRACELOG_READER_MALFORMED_LOG;
+
+ return rc;
+}
+
+
+/**
+ * @copydoc FNRTTRACELOGRDRSTREAM
+ */
+static DECLCALLBACK(int) rtTraceLogRdrFileStream(void *pvUser, void *pvBuf, size_t cbBuf, size_t *pcbRead,
+ RTMSINTERVAL cMsTimeout)
+{
+ RT_NOREF(cMsTimeout);
+ RTFILE hFile = (RTFILE)pvUser;
+ return RTFileRead(hFile, pvBuf, cbBuf, pcbRead);
+}
+
+
+/**
+ * @copydoc FNRTTRACELOGSTREAMCLOSE
+ */
+static DECLCALLBACK(int) rtTraceLogRdrFileStreamClose(void *pvUser)
+{
+ RTFILE hFile = (RTFILE)pvUser;
+ return RTFileClose(hFile);
+}
+
+
+/**
+ * Returns the size of the data for the given event item descriptor.
+ *
+ * @returns Size in bytes for the given event item descriptor.
+ * @param pThis The trace log rader instance.
+ * @param pEvtItemDesc The event item descriptor.
+ * @param pacbRawData The raw data size array for he associated event to get the size for non static raw data items.
+ * @param pidxRawData The index into the raw data size array for the next item to use.
+ */
+static size_t rtTraceLogRdrEvtItemGetSz(PRTTRACELOGRDRINT pThis, PCRTTRACELOGEVTITEMDESC pEvtItemDesc,
+ size_t *pacbRawData, unsigned *pidxRawData)
+{
+ size_t cbRet = 0;
+
+ switch (pEvtItemDesc->enmType)
+ {
+ case RTTRACELOGTYPE_BOOL:
+ cbRet = sizeof(bool);
+ break;
+ case RTTRACELOGTYPE_UINT8:
+ cbRet = sizeof(uint8_t);
+ break;
+ case RTTRACELOGTYPE_INT8:
+ cbRet = sizeof(int8_t);
+ break;
+ case RTTRACELOGTYPE_UINT16:
+ cbRet = sizeof(uint16_t);
+ break;
+ case RTTRACELOGTYPE_INT16:
+ cbRet = sizeof(int16_t);
+ break;
+ case RTTRACELOGTYPE_UINT32:
+ cbRet = sizeof(uint32_t);
+ break;
+ case RTTRACELOGTYPE_INT32:
+ cbRet = sizeof(int32_t);
+ break;
+ case RTTRACELOGTYPE_UINT64:
+ cbRet = sizeof(uint64_t);
+ break;
+ case RTTRACELOGTYPE_INT64:
+ cbRet = sizeof(int64_t);
+ break;
+ case RTTRACELOGTYPE_FLOAT32:
+ cbRet = sizeof(float);
+ break;
+ case RTTRACELOGTYPE_FLOAT64:
+ cbRet = sizeof(double);
+ break;
+ case RTTRACELOGTYPE_RAWDATA:
+ if (pEvtItemDesc->cbRawData == 0)
+ {
+ cbRet = pacbRawData[*pidxRawData];
+ *pidxRawData++;
+ }
+ else
+ cbRet = pEvtItemDesc->cbRawData;
+ break;
+ case RTTRACELOGTYPE_POINTER:
+ cbRet = pThis->cbTypePtr;
+ break;
+ case RTTRACELOGTYPE_SIZE:
+ cbRet = pThis->cbTypeSize;
+ break;
+ default:
+ AssertMsgFailed(("Invalid type given %d\n", pEvtItemDesc->enmType));
+ }
+
+ return cbRet;
+}
+
+
+/**
+ * Resolves the offset of the field with the given name returning the offset and data type.
+ *
+ * @returns IPRT status code.
+ * @param pEvt The event to fetch the data for.
+ * @param pszName The field to fetch.
+ * @param poffData Where to store the offset to the data on success.
+ * @param pcbEvtData Where to store the size of the size of the event data.
+ * @param ppEvtItemDesc Where to store the event item descriptor.
+ */
+static int rtTraceLogRdrEvtResolveData(PCRTTRACELOGRDREVTINT pEvt, const char *pszName, uint32_t *poffData,
+ size_t *pcbEvtData, PPCRTTRACELOGEVTITEMDESC ppEvtItemDesc)
+{
+ PCRTTRACELOGRDREVTDESC pEvtDesc = pEvt->pEvtDesc;
+ uint32_t offData = 0;
+ unsigned idxRawData = 0;
+
+ for (unsigned i = 0; i < pEvtDesc->EvtDesc.cEvtItems; i++)
+ {
+ PCRTTRACELOGEVTITEMDESC pEvtItemDesc = &pEvtDesc->aEvtItemDesc[i];
+
+ if (!RTStrCmp(pszName, pEvtItemDesc->pszName))
+ {
+ *poffData = offData;
+ *pcbEvtData = rtTraceLogRdrEvtItemGetSz(pEvt->pRdr, pEvtItemDesc, pEvt->pacbRawData, &idxRawData);
+ *ppEvtItemDesc = pEvtItemDesc;
+ return VINF_SUCCESS;
+ }
+
+ offData += (uint32_t)rtTraceLogRdrEvtItemGetSz(pEvt->pRdr, pEvtItemDesc, pEvt->pacbRawData, &idxRawData);
+ }
+
+ return VERR_NOT_FOUND;
+}
+
+
+/**
+ * Fills a value with the given event data.
+ *
+ * @returns IPRT status code.
+ * @param pEvt The event to fetch the data for.
+ * @param offData Offset the data is located in the event.
+ * @param cbData Number of bytes for the data.
+ * @param pEvtItemDesc The event item descriptor.
+ * @param pVal The value to fill.
+ */
+static int rtTraceLogRdrEvtFillVal(PCRTTRACELOGRDREVTINT pEvt, uint32_t offData, size_t cbData, PCRTTRACELOGEVTITEMDESC pEvtItemDesc,
+ PRTTRACELOGEVTVAL pVal)
+{
+ PRTTRACELOGRDRINT pThis = pEvt->pRdr;
+ const uint8_t *pbData = &pEvt->abEvtData[offData];
+
+ pVal->pItemDesc = pEvtItemDesc;
+ switch (pEvtItemDesc->enmType)
+ {
+ case RTTRACELOGTYPE_BOOL:
+ pVal->u.f = *(bool *)pbData;
+ break;
+ case RTTRACELOGTYPE_UINT8:
+ pVal->u.u8 = *pbData;
+ break;
+ case RTTRACELOGTYPE_INT8:
+ pVal->u.i8 = *(int8_t *)pbData;
+ break;
+ case RTTRACELOGTYPE_UINT16:
+ {
+ uint16_t u16Tmp = *(uint16_t *)pbData;
+ if (pThis->fConvEndianess)
+ pVal->u.u16 = RT_BSWAP_U16(u16Tmp);
+ else
+ pVal->u.u16 = u16Tmp;
+ break;
+ }
+ case RTTRACELOGTYPE_INT16:
+ {
+ uint8_t abData[2];
+ if (pThis->fConvEndianess)
+ {
+ abData[0] = pbData[1];
+ abData[1] = pbData[0];
+ }
+ else
+ {
+ abData[0] = pbData[0];
+ abData[1] = pbData[1];
+ }
+
+ pVal->u.i16 = *(int16_t *)&abData[0];
+ break;
+ }
+ case RTTRACELOGTYPE_UINT32:
+ {
+ uint32_t u32Tmp = *(uint32_t *)pbData;
+ if (pThis->fConvEndianess)
+ pVal->u.u32 = RT_BSWAP_U32(u32Tmp);
+ else
+ pVal->u.u32 = u32Tmp;
+ break;
+ }
+ case RTTRACELOGTYPE_INT32:
+ {
+ uint8_t abData[4];
+ if (pThis->fConvEndianess)
+ {
+ abData[0] = pbData[3];
+ abData[1] = pbData[2];
+ abData[2] = pbData[1];
+ abData[3] = pbData[0];
+ }
+ else
+ {
+ abData[0] = pbData[0];
+ abData[1] = pbData[1];
+ abData[2] = pbData[2];
+ abData[3] = pbData[3];
+ }
+
+ pVal->u.i32 = *(int32_t *)&abData[0];
+ break;
+ }
+ case RTTRACELOGTYPE_UINT64:
+ {
+ uint64_t u64Tmp = *(uint64_t *)pbData;
+ if (pThis->fConvEndianess)
+ pVal->u.u64 = RT_BSWAP_U64(u64Tmp);
+ else
+ pVal->u.u64 = u64Tmp;
+ break;
+ }
+ case RTTRACELOGTYPE_INT64:
+ {
+ uint8_t abData[8];
+ if (pThis->fConvEndianess)
+ {
+ abData[0] = pbData[7];
+ abData[1] = pbData[6];
+ abData[2] = pbData[5];
+ abData[3] = pbData[4];
+ abData[4] = pbData[3];
+ abData[5] = pbData[2];
+ abData[6] = pbData[1];
+ abData[7] = pbData[0];
+ }
+ else
+ {
+ abData[0] = pbData[0];
+ abData[1] = pbData[1];
+ abData[2] = pbData[2];
+ abData[3] = pbData[3];
+ abData[4] = pbData[4];
+ abData[5] = pbData[5];
+ abData[6] = pbData[6];
+ abData[7] = pbData[7];
+ }
+
+ pVal->u.i32 = *(int64_t *)&abData[0];
+ break;
+ }
+ case RTTRACELOGTYPE_FLOAT32:
+ {
+ uint8_t abData[4];
+ if (pThis->fConvEndianess)
+ {
+ abData[0] = pbData[3];
+ abData[1] = pbData[2];
+ abData[2] = pbData[1];
+ abData[3] = pbData[0];
+ }
+ else
+ {
+ abData[0] = pbData[0];
+ abData[1] = pbData[1];
+ abData[2] = pbData[2];
+ abData[3] = pbData[3];
+ }
+
+ pVal->u.f32 = *(float *)&abData[0];
+ break;
+ }
+ case RTTRACELOGTYPE_FLOAT64:
+ {
+ uint8_t abData[8];
+ if (pThis->fConvEndianess)
+ {
+ abData[0] = pbData[7];
+ abData[1] = pbData[6];
+ abData[2] = pbData[5];
+ abData[3] = pbData[4];
+ abData[4] = pbData[3];
+ abData[5] = pbData[2];
+ abData[6] = pbData[1];
+ abData[7] = pbData[0];
+ }
+ else
+ {
+ abData[0] = pbData[0];
+ abData[1] = pbData[1];
+ abData[2] = pbData[2];
+ abData[3] = pbData[3];
+ abData[4] = pbData[4];
+ abData[5] = pbData[5];
+ abData[6] = pbData[6];
+ abData[7] = pbData[7];
+ }
+
+ pVal->u.f64 = *(double *)&abData[0];
+ break;
+ }
+ case RTTRACELOGTYPE_RAWDATA:
+ pVal->u.RawData.pb = pbData;
+ if (pEvtItemDesc->cbRawData == 0)
+ pVal->u.RawData.cb = cbData;
+ else
+ pVal->u.RawData.cb = pEvtItemDesc->cbRawData;
+ break;
+ case RTTRACELOGTYPE_POINTER:
+ {
+ if (pThis->cbTypePtr == 4)
+ {
+ if (pThis->fConvEndianess)
+ pVal->u.uPtr = RT_BSWAP_U32(*(uint32_t *)pbData);
+ else
+ pVal->u.uPtr = *(uint32_t *)pbData;
+ }
+ else if (pThis->cbTypePtr == 8)
+ {
+ if (pThis->fConvEndianess)
+ pVal->u.uPtr = RT_BSWAP_U64(*(uint64_t *)pbData);
+ else
+ pVal->u.uPtr = *(uint64_t *)pbData;
+ }
+ else
+ AssertMsgFailed(("Invalid pointer size %d, should not happen!\n", pThis->cbTypePtr));
+ break;
+ }
+ case RTTRACELOGTYPE_SIZE:
+ {
+ if (pThis->cbTypeSize == 4)
+ {
+ if (pThis->fConvEndianess)
+ pVal->u.sz = RT_BSWAP_U32(*(uint32_t *)pbData);
+ else
+ pVal->u.sz = *(uint32_t *)pbData;
+ }
+ else if (pThis->cbTypeSize == 8)
+ {
+ if (pThis->fConvEndianess)
+ pVal->u.sz = RT_BSWAP_U64(*(uint64_t *)pbData);
+ else
+ pVal->u.sz = *(uint64_t *)pbData;
+ }
+ else
+ AssertMsgFailed(("Invalid size_t size %d, should not happen!\n", pThis->cbTypeSize));
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid type given %d\n", pEvtItemDesc->enmType));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Finds the mapping descriptor for the given event.
+ *
+ * @returns Pointer to the mapping descriptor or NULL if not found.
+ * @param paMapDesc Pointer to the array of mapping descriptors.
+ * @param pEvt The event to look for the matching mapping descriptor.
+ */
+static PCRTTRACELOGRDRMAPDESC rtTraceLogRdrMapDescFindForEvt(PCRTTRACELOGRDRMAPDESC paMapDesc, PCRTTRACELOGRDREVTINT pEvt)
+{
+ AssertPtrReturn(paMapDesc, NULL);
+ AssertPtrReturn(pEvt, NULL);
+
+ while (paMapDesc->pszEvtId)
+ {
+ if (!RTStrCmp(paMapDesc->pszEvtId, pEvt->pEvtDesc->EvtDesc.pszId))
+ return paMapDesc;
+
+ paMapDesc++;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Fills the given event header with data from the given event using the matching mapping descriptor.
+ *
+ * @returns IPRT statsu code.
+ * @param pEvtHdr The event header to fill.
+ * @param pMapDesc The mapping descriptor to use.
+ * @param pEvt The raw event to get the data from.
+ */
+static int rtTraceLogRdrMapFillEvt(PRTTRACELOGRDREVTHDR pEvtHdr, PCRTTRACELOGRDRMAPDESC pMapDesc, PCRTTRACELOGRDREVTINT pEvt)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Fill in the status parts. */
+ pEvtHdr->pEvtMapDesc = pMapDesc;
+ pEvtHdr->pEvtDesc = &pEvt->pEvtDesc->EvtDesc;
+ pEvtHdr->idSeqNo = pEvt->u64SeqNo;
+ pEvtHdr->tsEvt = pEvt->u64Ts;
+ pEvtHdr->paEvtItems = NULL;
+
+ /* Now the individual items if any. */
+ if (pMapDesc->cEvtItems)
+ {
+ /* Allocate values for the items. */
+ pEvtHdr->paEvtItems = (PCRTTRACELOGEVTVAL)RTMemAllocZ(pMapDesc->cEvtItems * sizeof(RTTRACELOGEVTVAL));
+ if (RT_LIKELY(pEvtHdr->paEvtItems))
+ {
+ for (uint32_t i = 0; (i < pMapDesc->cEvtItems) && RT_SUCCESS(rc); i++)
+ {
+ uint32_t offData = 0;
+ size_t cbData = 0;
+ PCRTTRACELOGEVTITEMDESC pEvtItemDesc = NULL;
+ rc = rtTraceLogRdrEvtResolveData(pEvt, pMapDesc->paMapItems[i].pszName, &offData, &cbData, &pEvtItemDesc);
+ if (RT_SUCCESS(rc))
+ rc = rtTraceLogRdrEvtFillVal(pEvt, offData, cbData, pEvtItemDesc, (PRTTRACELOGEVTVAL)&pEvtHdr->paEvtItems[i]);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree((void *)pEvtHdr->paEvtItems);
+ pEvtHdr->paEvtItems = NULL;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogRdrCreate(PRTTRACELOGRDR phTraceLogRdr, PFNRTTRACELOGRDRSTREAM pfnStreamIn,
+ PFNRTTRACELOGSTREAMCLOSE pfnStreamClose, void *pvUser)
+{
+ AssertPtrReturn(phTraceLogRdr, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnStreamIn, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnStreamClose, VERR_INVALID_POINTER);
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGRDRINT pThis = (PRTTRACELOGRDRINT)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ rc = RTSemMutexCreate(&pThis->hMtx);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCacheCreate(&pThis->hStrCache, "TRACELOGRDR");
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pThis->LstEvts);
+ pThis->u32Magic = RTTRACELOGRDR_MAGIC;
+ pThis->pfnStreamIn = pfnStreamIn;
+ pThis->pfnStreamClose = pfnStreamClose;
+ pThis->pvUser = pvUser;
+ pThis->enmState = RTTRACELOGRDRSTATE_RECV_HDR;
+ pThis->fConvEndianess = false;
+ pThis->pszDesc = NULL;
+ pThis->cEvtDescsCur = 0;
+ pThis->cEvtDescsMax = 0;
+ pThis->papEvtDescs = NULL;
+ pThis->pEvtDescCur = NULL;
+ pThis->u64SeqNoLast = 0;
+ pThis->cbScratch = sizeof(TRACELOGHDR);
+ pThis->offScratch = 0;
+ pThis->cbRecvLeft = sizeof(TRACELOGHDR);
+ pThis->pbScratch = (uint8_t *)RTMemAllocZ(pThis->cbScratch);
+ if (RT_LIKELY(pThis->pbScratch))
+ {
+ *phTraceLogRdr = pThis;
+ return VINF_SUCCESS;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTStrCacheDestroy(pThis->hStrCache);
+ }
+
+ RTSemMutexDestroy(pThis->hMtx);
+ }
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogRdrCreateFromFile(PRTTRACELOGRDR phTraceLogRdr, const char *pszFilename)
+{
+ AssertPtrReturn(phTraceLogRdr, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTraceLogRdrCreate(phTraceLogRdr, rtTraceLogRdrFileStream, rtTraceLogRdrFileStreamClose, hFile);
+ if (RT_FAILURE(rc))
+ {
+ RTFileClose(hFile);
+ RTFileDelete(pszFilename);
+ }
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogRdrDestroy(RTTRACELOGRDR hTraceLogRdr)
+{
+ if (hTraceLogRdr == NIL_RTTRACELOGRDR)
+ return VINF_SUCCESS;
+ PRTTRACELOGRDRINT pThis = hTraceLogRdr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTTRACELOGRDR_MAGIC, VERR_INVALID_HANDLE);
+
+ pThis->u32Magic = RTTRACELOGRDR_MAGIC_DEAD;
+ int rc = pThis->pfnStreamClose(pThis->pvUser);
+ AssertRC(rc);
+
+ for (unsigned i = 0; i < pThis->cEvtDescsCur; i++)
+ RTMemFree(pThis->papEvtDescs[i]);
+ if (pThis->papEvtDescs)
+ {
+ RTMemFree(pThis->papEvtDescs);
+ pThis->papEvtDescs = NULL;
+ }
+
+ if (pThis->pEvtCur)
+ {
+ RTMemFree(pThis->pEvtCur);
+ pThis->pEvtCur = NULL;
+ }
+
+ PRTTRACELOGRDREVTINT pCur, pNext;
+ RTListForEachSafe(&pThis->LstEvts, pCur, pNext, RTTRACELOGRDREVTINT, NdGlob)
+ {
+ RTMemFree(pCur);
+ }
+
+ RTSemMutexDestroy(pThis->hMtx);
+ pThis->hMtx = NIL_RTSEMMUTEX;
+
+ RTMemFree(pThis->pbScratch);
+ pThis->pbScratch = NULL;
+
+ RTStrCacheDestroy(pThis->hStrCache);
+ pThis->hStrCache = NIL_RTSTRCACHE;
+
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTTraceLogRdrEvtPoll(RTTRACELOGRDR hTraceLogRdr, RTTRACELOGRDRPOLLEVT *penmEvt, RTMSINTERVAL cMsTimeout)
+{
+ PRTTRACELOGRDRINT pThis = hTraceLogRdr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTTRACELOGRDR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(penmEvt, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ bool fContinue = true;
+ while ( RT_SUCCESS(rc)
+ && fContinue)
+ {
+ size_t cbRecvd = 0;
+
+ rc = rtTraceLogRdrStreamRead(pThis, &pThis->pbScratch[pThis->offScratch],
+ pThis->cbRecvLeft, &cbRecvd, cMsTimeout);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbRecvd == pThis->cbRecvLeft)
+ {
+ /* Act according to the current state. */
+ rc = g_aStateHandlers[pThis->enmState].pfn(pThis, penmEvt, &fContinue);
+ }
+ else
+ pThis->cbRecvLeft -= cbRecvd;
+ }
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogRdrQueryLastEvt(RTTRACELOGRDR hTraceLogRdr, PRTTRACELOGRDREVT phRdrEvt)
+{
+ PRTTRACELOGRDRINT pThis = hTraceLogRdr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTTRACELOGRDR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phRdrEvt, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ RTSemMutexRequest(pThis->hMtx, RT_INDEFINITE_WAIT);
+ PRTTRACELOGRDREVTINT pEvt = RTListGetLast(&pThis->LstEvts, RTTRACELOGRDREVTINT, NdGlob);
+ *phRdrEvt = pEvt;
+ if (!pEvt)
+ rc = VERR_NOT_FOUND;
+ RTSemMutexRelease(pThis->hMtx);
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogRdrQueryIterator(RTTRACELOGRDR hTraceLogRdr, PRTTRACELOGRDRIT phIt)
+{
+ PRTTRACELOGRDRINT pThis = hTraceLogRdr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTTRACELOGRDR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phIt, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGRDRITINT pIt = (PRTTRACELOGRDRITINT)RTMemAllocZ(sizeof(*pIt));
+ if (RT_LIKELY(pIt))
+ {
+ pIt->pRdr = pThis;
+ pIt->pEvt = RTListGetFirst(&pThis->LstEvts, RTTRACELOGRDREVTINT, NdGlob);
+ *phIt = pIt;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogRdrEvtMapToStruct(RTTRACELOGRDR hTraceLogRdr, uint32_t fFlags, uint32_t cEvts,
+ PCRTTRACELOGRDRMAPDESC paMapDesc, PCRTTRACELOGRDREVTHDR *ppaEvtHdr,
+ uint32_t *pcEvts)
+{
+ RT_NOREF(fFlags);
+
+ PRTTRACELOGRDRINT pThis = hTraceLogRdr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTTRACELOGRDR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(paMapDesc, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppaEvtHdr, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcEvts, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ uint32_t cEvtsAlloc = cEvts != UINT32_MAX ? cEvts : _4K;
+ PRTTRACELOGRDREVTHDR paEvtHdr = (PRTTRACELOGRDREVTHDR)RTMemAllocZ(cEvtsAlloc * sizeof(*paEvtHdr));
+ if (RT_LIKELY(paEvtHdr))
+ {
+ uint32_t cEvtsRecv = 0;
+
+ while ( RT_SUCCESS(rc)
+ && cEvtsRecv < cEvts)
+ {
+ RTTRACELOGRDRPOLLEVT enmEvt = RTTRACELOGRDRPOLLEVT_INVALID;
+ rc = RTTraceLogRdrEvtPoll(pThis, &enmEvt, 0 /*cMsTimeout*/);
+ if ( RT_SUCCESS(rc)
+ && enmEvt == RTTRACELOGRDRPOLLEVT_TRACE_EVENT_RECVD)
+ {
+ /* Find the mapping descriptor. */
+ PRTTRACELOGRDREVTINT pEvt = NULL;
+ rc = RTTraceLogRdrQueryLastEvt(hTraceLogRdr, &pEvt);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTTRACELOGRDRMAPDESC pMapDesc = rtTraceLogRdrMapDescFindForEvt(paMapDesc, pEvt);
+ if (pMapDesc)
+ {
+ if (cEvtsRecv == cEvtsAlloc)
+ {
+ Assert(cEvts == UINT32_MAX);
+ PRTTRACELOGRDREVTHDR paEvtHdrNew = (PRTTRACELOGRDREVTHDR)RTMemRealloc(paEvtHdr, (cEvtsAlloc + _4K) * sizeof(*paEvtHdr));
+ if (RT_LIKELY(paEvtHdrNew))
+ {
+ paEvtHdr = paEvtHdrNew;
+ cEvtsAlloc += _4K;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = rtTraceLogRdrMapFillEvt(&paEvtHdr[cEvtsRecv++], pMapDesc, pEvt);
+ cEvtsRecv++;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppaEvtHdr = paEvtHdr;
+ *pcEvts = cEvtsRecv;
+ }
+ else
+ RTTraceLogRdrEvtMapFree(paEvtHdr, cEvtsRecv);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(void) RTTraceLogRdrEvtMapFree(PCRTTRACELOGRDREVTHDR paEvtHdr, uint32_t cEvts)
+{
+ for (uint32_t i = 0; i < cEvts; i++)
+ {
+ if (paEvtHdr[i].paEvtItems)
+ RTMemFree((void *)paEvtHdr[i].paEvtItems);
+ }
+
+ RTMemFree((void *)paEvtHdr);
+}
+
+
+RTDECL(void) RTTraceLogRdrIteratorFree(RTTRACELOGRDRIT hIt)
+{
+ PRTTRACELOGRDRITINT pIt = hIt;
+ AssertPtrReturnVoid(pIt);
+
+ RTMemFree(pIt);
+}
+
+
+RTDECL(int) RTTraceLogRdrIteratorNext(RTTRACELOGRDRIT hIt)
+{
+ PRTTRACELOGRDRITINT pIt = hIt;
+ AssertPtrReturn(pIt, VERR_INVALID_HANDLE);
+
+ if (!pIt->pEvt)
+ return VERR_TRACELOG_READER_ITERATOR_END;
+
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGRDREVTINT pEvtNext = RTListGetNext(&pIt->pRdr->LstEvts, pIt->pEvt, RTTRACELOGRDREVTINT, NdGlob);
+
+ if (pEvtNext)
+ pIt->pEvt = pEvtNext;
+ else
+ rc = VERR_TRACELOG_READER_ITERATOR_END;
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogRdrIteratorQueryEvent(RTTRACELOGRDRIT hIt, PRTTRACELOGRDREVT phRdrEvt)
+{
+ PRTTRACELOGRDRITINT pIt = hIt;
+ AssertPtrReturn(pIt, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phRdrEvt, VERR_INVALID_POINTER);
+
+ *phRdrEvt = pIt->pEvt;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(uint64_t) RTTraceLogRdrEvtGetSeqNo(RTTRACELOGRDREVT hRdrEvt)
+{
+ PRTTRACELOGRDREVTINT pEvt = hRdrEvt;
+ AssertPtrReturn(pEvt, 0);
+
+ return pEvt->u64SeqNo;
+}
+
+
+RTDECL(uint64_t) RTTraceLogRdrEvtGetTs(RTTRACELOGRDREVT hRdrEvt)
+{
+ PRTTRACELOGRDREVTINT pEvt = hRdrEvt;
+ AssertPtrReturn(pEvt, 0);
+
+ return pEvt->u64Ts;
+}
+
+
+RTDECL(bool) RTTraceLogRdrEvtIsGrouped(RTTRACELOGRDREVT hRdrEvt)
+{
+ PRTTRACELOGRDREVTINT pEvt = hRdrEvt;
+ AssertPtrReturn(pEvt, false);
+
+ return pEvt->idGrp != 0;
+}
+
+
+RTDECL(PCRTTRACELOGEVTDESC) RTTraceLogRdrEvtGetDesc(RTTRACELOGRDREVT hRdrEvt)
+{
+ PRTTRACELOGRDREVTINT pEvt = hRdrEvt;
+ AssertPtrReturn(pEvt, NULL);
+
+ return &pEvt->pEvtDesc->EvtDesc;
+}
+
+
+RTDECL(int) RTTraceLogRdrEvtQueryVal(RTTRACELOGRDREVT hRdrEvt, const char *pszName, PRTTRACELOGEVTVAL pVal)
+{
+ PRTTRACELOGRDREVTINT pEvt = hRdrEvt;
+ AssertPtrReturn(pEvt, VERR_INVALID_HANDLE);
+
+ uint32_t offData = 0;
+ size_t cbData = 0;
+ PCRTTRACELOGEVTITEMDESC pEvtItemDesc = NULL;
+ int rc = rtTraceLogRdrEvtResolveData(pEvt, pszName, &offData, &cbData, &pEvtItemDesc);
+ if (RT_SUCCESS(rc))
+ rc = rtTraceLogRdrEvtFillVal(pEvt, offData, cbData, pEvtItemDesc, pVal);
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogRdrEvtFillVals(RTTRACELOGRDREVT hRdrEvt, unsigned idxItemStart, PRTTRACELOGEVTVAL paVals,
+ unsigned cVals, unsigned *pcVals)
+{
+ PRTTRACELOGRDREVTINT pEvt = hRdrEvt;
+ AssertPtrReturn(pEvt, VERR_INVALID_HANDLE);
+
+ PCRTTRACELOGRDREVTDESC pEvtDesc = pEvt->pEvtDesc;
+ AssertReturn(idxItemStart < pEvtDesc->EvtDesc.cEvtItems, VERR_INVALID_PARAMETER);
+
+ /* Advance to the item the caller wants to fill in. */
+ uint32_t offData = 0;
+ unsigned idxRawData = 0;
+
+ for (unsigned i = 0; i < idxItemStart; i++)
+ {
+ PCRTTRACELOGEVTITEMDESC pEvtItemDesc = &pEvtDesc->aEvtItemDesc[i];
+ offData += (uint32_t)rtTraceLogRdrEvtItemGetSz(pEvt->pRdr, pEvtItemDesc, pEvt->pacbRawData, &idxRawData);
+ }
+
+ int rc = VINF_SUCCESS;
+ unsigned idxItemEnd = RT_MIN(idxItemStart + cVals, pEvtDesc->EvtDesc.cEvtItems);
+ for (unsigned i = idxItemStart; i < idxItemEnd && RT_SUCCESS(rc); i++)
+ {
+ PCRTTRACELOGEVTITEMDESC pEvtItemDesc = &pEvtDesc->aEvtItemDesc[i];
+ size_t cbData = rtTraceLogRdrEvtItemGetSz(pEvt->pRdr, pEvtItemDesc, pEvt->pacbRawData, &idxRawData);
+
+ rc = rtTraceLogRdrEvtFillVal(pEvt, offData, cbData, pEvtItemDesc, &paVals[i - idxItemStart]);
+ offData += (uint32_t)cbData;
+ }
+
+ *pcVals = idxItemEnd - idxItemStart;
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/log/tracelogwriter.cpp b/src/VBox/Runtime/common/log/tracelogwriter.cpp
new file mode 100644
index 00000000..01aadff6
--- /dev/null
+++ b/src/VBox/Runtime/common/log/tracelogwriter.cpp
@@ -0,0 +1,969 @@
+/* $Id: tracelogwriter.cpp $ */
+/** @file
+ * IPRT - Trace log writer.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/formats/tracelog.h>
+#include <iprt/tracelog.h>
+
+
+#include <iprt/avl.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/tcp.h>
+#include <iprt/time.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+
+/**
+ * Trace log writer internal event descriptor.
+ */
+typedef struct RTTRACELOGWREVTDESC
+{
+ /** AVL node core data */
+ AVLPVNODECORE Core;
+ /** The ID associated with this event descriptor. */
+ uint32_t u32Id;
+ /** Overall size of the event data not counting variable raw data items. */
+ size_t cbEvtData;
+ /** Number of non static raw binary items in the descriptor. */
+ uint32_t cRawDataNonStatic;
+ /** Pointer to the scratch event data buffer when adding events. */
+ uint8_t *pbEvt;
+ /** Embedded event descriptor. */
+ RTTRACELOGEVTDESC EvtDesc;
+ /** Array of event item descriptors, variable in size. */
+ RTTRACELOGEVTITEMDESC aEvtItemDesc[1];
+} RTTRACELOGWREVTDESC;
+/** Pointer to internal trace log writer event descriptor. */
+typedef RTTRACELOGWREVTDESC *PRTTRACELOGWREVTDESC;
+/** Pointer to const internal trace log writer event descriptor. */
+typedef const RTTRACELOGWREVTDESC *PCRTTRACELOGWREVTDESC;
+
+
+/**
+ * Trace log writer instance data.
+ */
+typedef struct RTTRACELOGWRINT
+{
+ /** Magic for identification. */
+ uint32_t u32Magic;
+ /** Stream out callback. */
+ PFNRTTRACELOGWRSTREAM pfnStreamOut;
+ /** Stream close callback .*/
+ PFNRTTRACELOGSTREAMCLOSE pfnStreamClose;
+ /** Opaque user data passed to the stream callback. */
+ void *pvUser;
+ /** Mutex protecting the structure. */
+ RTSEMMUTEX hMtx;
+ /** Next sequence number to use. */
+ volatile uint64_t u64SeqNoNext;
+ /** AVL tree root for event descriptor lookups. */
+ AVLPVTREE pTreeEvtDescs;
+ /** Number of event descriptors known. */
+ uint32_t cEvtDescs;
+} RTTRACELOGWRINT;
+/** Pointer to a trace log writer instance. */
+typedef RTTRACELOGWRINT *PRTTRACELOGWRINT;
+
+
+/**
+ * The TCP server/client state.
+ */
+typedef struct RTTRACELOGWRTCP
+{
+ /** Flag whether this is a server or client instance. */
+ bool fIsServer;
+ /** The TCP socket handle for the connection. */
+ RTSOCKET hSock;
+ /** The TCP server. */
+ PRTTCPSERVER pTcpSrv;
+} RTTRACELOGWRTCP;
+/** Pointer to a TCP server/client state. */
+typedef RTTRACELOGWRTCP *PRTTRACELOGWRTCP;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/**
+ * Returns the size required for the internal event descriptor representation.
+ *
+ * @returns Number of bytes required.
+ * @param pEvtDesc Pointer to the external descriptor.
+ */
+static size_t rtTraceLogWrtEvtDescGetSz(PCRTTRACELOGEVTDESC pEvtDesc)
+{
+ size_t cbAlloc = RT_UOFFSETOF_DYN(RTTRACELOGWREVTDESC, aEvtItemDesc[pEvtDesc->cEvtItems]);
+
+ cbAlloc += strlen(pEvtDesc->pszId) + 1;
+ if (pEvtDesc->pszDesc)
+ cbAlloc += strlen(pEvtDesc->pszDesc) + 1;
+ for (unsigned i = 0; i < pEvtDesc->cEvtItems; i++)
+ {
+ PCRTTRACELOGEVTITEMDESC pEvtItemDesc = &pEvtDesc->paEvtItemDesc[i];
+
+ cbAlloc += strlen(pEvtItemDesc->pszName) + 1;
+ if (pEvtItemDesc->pszDesc)
+ cbAlloc += strlen(pEvtItemDesc->pszDesc) + 1;
+ }
+
+ return cbAlloc;
+}
+
+
+/**
+ * Copies a string into a supplied buffer assigning the start to the given string pointer.
+ *
+ * @returns Pointer to the memory after the destination buffer holding the string.
+ * @param ppsz Where to store the pointer to the start of the string.
+ * @param pszTo Where to copy the string including the temrinator to.
+ * @param pszFrom The string to copy.
+ */
+DECLINLINE(char *) rtTraceLogWrCopyStr(const char **ppsz, char *pszTo, const char *pszFrom)
+{
+ *ppsz = pszTo;
+ size_t cchCopy = strlen(pszFrom) + 1;
+ memcpy(pszTo, pszFrom, cchCopy);
+
+ return pszTo + cchCopy;
+}
+
+
+/**
+ * Converts the type enum to the size of the the event item data in bytes.
+ *
+ * @returns Event item data size in bytes.
+ * @param pEvtItemDesc The event item descriptor.
+ */
+static size_t rtTraceLogWrGetEvtItemDataSz(PCRTTRACELOGEVTITEMDESC pEvtItemDesc)
+{
+ size_t cb = 0;
+
+ switch (pEvtItemDesc->enmType)
+ {
+ case RTTRACELOGTYPE_BOOL:
+ case RTTRACELOGTYPE_UINT8:
+ case RTTRACELOGTYPE_INT8:
+ {
+ cb = 1;
+ break;
+ }
+ case RTTRACELOGTYPE_UINT16:
+ case RTTRACELOGTYPE_INT16:
+ {
+ cb = 2;
+ break;
+ }
+ case RTTRACELOGTYPE_UINT32:
+ case RTTRACELOGTYPE_INT32:
+ case RTTRACELOGTYPE_FLOAT32:
+ {
+ cb = 4;
+ break;
+ }
+ case RTTRACELOGTYPE_UINT64:
+ case RTTRACELOGTYPE_INT64:
+ case RTTRACELOGTYPE_FLOAT64:
+ {
+ cb = 8;
+ break;
+ }
+ case RTTRACELOGTYPE_RAWDATA:
+ {
+ cb = pEvtItemDesc->cbRawData;
+ break;
+ }
+ case RTTRACELOGTYPE_POINTER:
+ {
+ cb = sizeof(uintptr_t);
+ break;
+ }
+ case RTTRACELOGTYPE_SIZE:
+ {
+ cb = sizeof(size_t);
+ break;
+ }
+ default:
+ AssertMsgFailed(("Invalid type %d\n", pEvtItemDesc->enmType));
+ }
+
+ return cb;
+}
+
+
+/**
+ * Converts API severity enum to the stream representation.
+ *
+ * @returns Stream representation of the severity.
+ * @param enmSeverity The API severity.
+ */
+static uint32_t rtTraceLogWrConvSeverityToStream(RTTRACELOGEVTSEVERITY enmSeverity)
+{
+ switch (enmSeverity)
+ {
+ case RTTRACELOGEVTSEVERITY_INFO:
+ return TRACELOG_EVTDESC_SEVERITY_INFO;
+ case RTTRACELOGEVTSEVERITY_WARNING:
+ return TRACELOG_EVTDESC_SEVERITY_WARNING;
+ case RTTRACELOGEVTSEVERITY_ERROR:
+ return TRACELOG_EVTDESC_SEVERITY_ERROR;
+ case RTTRACELOGEVTSEVERITY_FATAL:
+ return TRACELOG_EVTDESC_SEVERITY_FATAL;
+ case RTTRACELOGEVTSEVERITY_DEBUG:
+ return TRACELOG_EVTDESC_SEVERITY_DEBUG;
+ default:
+ AssertMsgFailed(("Invalid severity %d\n", enmSeverity));
+ }
+
+ /* Should not happen. */
+ return TRACELOG_EVTDESC_SEVERITY_FATAL;
+}
+
+
+/**
+ * Converts API type enum to the stream representation.
+ *
+ * @returns Stream representation of the type.
+ * @param enmType The API type.
+ */
+static uint32_t rtTraceLogWrConvTypeToStream(RTTRACELOGTYPE enmType)
+{
+ switch (enmType)
+ {
+ case RTTRACELOGTYPE_BOOL:
+ return TRACELOG_EVTITEMDESC_TYPE_BOOL;
+ case RTTRACELOGTYPE_UINT8:
+ return TRACELOG_EVTITEMDESC_TYPE_UINT8;
+ case RTTRACELOGTYPE_INT8:
+ return TRACELOG_EVTITEMDESC_TYPE_INT8;
+ case RTTRACELOGTYPE_UINT16:
+ return TRACELOG_EVTITEMDESC_TYPE_UINT16;
+ case RTTRACELOGTYPE_INT16:
+ return TRACELOG_EVTITEMDESC_TYPE_INT16;
+ case RTTRACELOGTYPE_UINT32:
+ return TRACELOG_EVTITEMDESC_TYPE_UINT32;
+ case RTTRACELOGTYPE_INT32:
+ return TRACELOG_EVTITEMDESC_TYPE_INT32;
+ case RTTRACELOGTYPE_UINT64:
+ return TRACELOG_EVTITEMDESC_TYPE_UINT64;
+ case RTTRACELOGTYPE_INT64:
+ return TRACELOG_EVTITEMDESC_TYPE_INT64;
+ case RTTRACELOGTYPE_FLOAT32:
+ return TRACELOG_EVTITEMDESC_TYPE_FLOAT32;
+ case RTTRACELOGTYPE_FLOAT64:
+ return TRACELOG_EVTITEMDESC_TYPE_FLOAT64;
+ case RTTRACELOGTYPE_RAWDATA:
+ return TRACELOG_EVTITEMDESC_TYPE_RAWDATA;
+ case RTTRACELOGTYPE_POINTER:
+ return TRACELOG_EVTITEMDESC_TYPE_POINTER;
+ case RTTRACELOGTYPE_SIZE:
+ return TRACELOG_EVTITEMDESC_TYPE_SIZE;
+ default:
+ AssertMsgFailed(("Invalid type %d\n", enmType));
+ }
+
+ /* Should not happen. */
+ return RTTRACELOGTYPE_RAWDATA;
+}
+
+
+/**
+ * Initializes the internal representation of the event descriptor from the given one.
+ *
+ * @returns Pointer to the internal instance of the event descriptor.
+ * NULL if out of memory.
+ * @param pEvtDesc Pointer to the external descriptor.
+ */
+static PRTTRACELOGWREVTDESC rtTraceLogWrEvtDescInit(PCRTTRACELOGEVTDESC pEvtDesc)
+{
+ size_t cbAlloc = rtTraceLogWrtEvtDescGetSz(pEvtDesc);
+ size_t cbEvtData = 0;
+ PRTTRACELOGWREVTDESC pEvtDescInt = (PRTTRACELOGWREVTDESC)RTMemAllocZ(cbAlloc);
+ if (RT_LIKELY(pEvtDescInt))
+ {
+ char *pszStrSpace = (char *)&pEvtDescInt->aEvtItemDesc[pEvtDesc->cEvtItems]; /* Get space for strings after the descriptor. */
+
+ pEvtDescInt->EvtDesc.enmSeverity = pEvtDesc->enmSeverity;
+ pEvtDescInt->EvtDesc.cEvtItems = pEvtDesc->cEvtItems;
+ pEvtDescInt->EvtDesc.paEvtItemDesc = &pEvtDescInt->aEvtItemDesc[0];
+
+ /* Copy ID and optional description over. */
+ pszStrSpace = rtTraceLogWrCopyStr(&pEvtDescInt->EvtDesc.pszId, pszStrSpace, pEvtDesc->pszId);
+ if (pEvtDesc->pszDesc)
+ pszStrSpace = rtTraceLogWrCopyStr(&pEvtDescInt->EvtDesc.pszDesc, pszStrSpace, pEvtDesc->pszDesc);
+
+ /* Go through the event item descriptors and initialize them too. */
+ for (unsigned i = 0; i < pEvtDesc->cEvtItems; i++)
+ {
+ PCRTTRACELOGEVTITEMDESC pEvtItemDescFrom = &pEvtDesc->paEvtItemDesc[i];
+ PRTTRACELOGEVTITEMDESC pEvtItemDescTo = &pEvtDescInt->aEvtItemDesc[i];
+
+ pEvtItemDescTo->enmType = pEvtItemDescFrom->enmType;
+ pEvtItemDescTo->cbRawData = pEvtItemDescFrom->cbRawData;
+
+ cbEvtData += rtTraceLogWrGetEvtItemDataSz(pEvtItemDescFrom);
+ if ( pEvtItemDescTo->enmType == RTTRACELOGTYPE_RAWDATA
+ && !pEvtItemDescFrom->cbRawData)
+ pEvtDescInt->cRawDataNonStatic++;
+
+ pszStrSpace = rtTraceLogWrCopyStr(&pEvtItemDescTo->pszName, pszStrSpace, pEvtItemDescFrom->pszName);
+ if (pEvtItemDescFrom->pszDesc)
+ pszStrSpace = rtTraceLogWrCopyStr(&pEvtItemDescTo->pszDesc, pszStrSpace, pEvtItemDescFrom->pszDesc);
+ }
+
+ pEvtDescInt->cbEvtData = cbEvtData;
+ if (cbEvtData)
+ {
+ pEvtDescInt->pbEvt = (uint8_t *)RTMemAllocZ(cbEvtData);
+ if (!pEvtDescInt->pbEvt)
+ {
+ RTMemFree(pEvtDescInt);
+ pEvtDescInt = NULL;
+ }
+ }
+ }
+
+ return pEvtDescInt;
+}
+
+
+/**
+ * Wrapper around the stream callback.
+ *
+ * @returns IPRT status code returned by the stream callback.
+ * @param pThis The trace log writer instance.
+ * @param pvBuf The data to stream.
+ * @param cbBuf Number of bytes to stream.
+ */
+DECLINLINE(int) rtTraceLogWrStream(PRTTRACELOGWRINT pThis, const void *pvBuf, size_t cbBuf)
+{
+ return pThis->pfnStreamOut(pThis->pvUser, pvBuf, cbBuf, NULL);
+}
+
+
+/**
+ * Initializes a given event structure.
+ *
+ * @returns Total number of bytes for the event data associated with this event.
+ * @param pEvt Pointer to the event structure to initialise.
+ * @param pEvtDescInt The internal event descriptor to format the data accordingly to.
+ * @param fFlags Flags to use for this event.
+ * @param uGrpId The group ID to identify grouped events.
+ * @param uParentGrpId The parent group ID.
+ * @param pacbRawData Array of raw data size indicators.
+ */
+DECLINLINE(size_t) rtTraceLogWrEvtInit(PTRACELOGEVT pEvt,
+ PRTTRACELOGWREVTDESC pEvtDescInt, uint32_t fFlags,
+ RTTRACELOGEVTGRPID uGrpId, RTTRACELOGEVTGRPID uParentGrpId,
+ size_t *pacbRawData)
+{
+ uint32_t cbEvtData = (uint32_t)pEvtDescInt->cbEvtData;
+ for (unsigned i = 0; i < pEvtDescInt->cRawDataNonStatic; i++)
+ cbEvtData += (uint32_t)pacbRawData[i];
+
+ uint32_t fEvtFlags = 0;
+ if (fFlags & RTTRACELOG_WR_ADD_EVT_F_GRP_START)
+ fEvtFlags |= TRACELOG_EVT_F_GRP_START;
+ if (fFlags & RTTRACELOG_WR_ADD_EVT_F_GRP_FINISH)
+ fEvtFlags |= TRACELOG_EVT_F_GRP_END;
+
+ memcpy(&pEvt->szMagic[0], TRACELOG_EVT_MAGIC, sizeof(pEvt->szMagic));
+ pEvt->u64Ts = RTTimeNanoTS();
+ pEvt->u64EvtGrpId = uGrpId;
+ pEvt->u64EvtParentGrpId = uParentGrpId;
+ pEvt->fFlags = fEvtFlags;
+ pEvt->u32EvtDescId = pEvtDescInt->u32Id;
+ pEvt->cbEvtData = cbEvtData;
+ pEvt->cRawEvtDataSz = pEvtDescInt->cRawDataNonStatic;
+
+ return cbEvtData;
+}
+
+
+/**
+ * Streams the whole event including associated data.
+ *
+ * @returns IPRT status code.
+ * @param pThis The trace log writer instance.
+ * @param pEvt Pointer to the initialised event structure.
+ * @param pvEvtData The raw event data.
+ * @param cbEvtData Size of the event data.
+ * @param pacbRawData Pointer to the array of size indicators for non static
+ * raw data in the event data stream.
+ */
+DECLINLINE(int) rtTraceLogWrEvtStream(PRTTRACELOGWRINT pThis, PTRACELOGEVT pEvt, const void *pvEvtData,
+ size_t cbEvtData, size_t *pacbRawData)
+{
+ /** @todo Get rid of locking. */
+ int rc = RTSemMutexRequest(pThis->hMtx, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ pEvt->u64SeqNo = ASMAtomicIncU64(&pThis->u64SeqNoNext);
+
+ /* Write the data out. */
+ rc = rtTraceLogWrStream(pThis, pEvt, sizeof(TRACELOGEVT));
+ if ( RT_SUCCESS(rc)
+ && pEvt->cRawEvtDataSz)
+ rc = rtTraceLogWrStream(pThis, pacbRawData, pEvt->cRawEvtDataSz * sizeof(size_t));
+ if ( RT_SUCCESS(rc)
+ && cbEvtData)
+ rc = rtTraceLogWrStream(pThis, pvEvtData, cbEvtData);
+ RTSemMutexRelease(pThis->hMtx);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Returns the intenral event descriptor for the given event descriptor.
+ *
+ * @returns Pointer to the internal event descriptor or NULL if not found.
+ * @param pThis The trace log writer instance.
+ * @param pEvtDesc The event descriptor to search for.
+ */
+DECLINLINE(PRTTRACELOGWREVTDESC) rtTraceLogWrEvtDescGetInternal(PRTTRACELOGWRINT pThis,
+ PCRTTRACELOGEVTDESC pEvtDesc)
+{
+ int rc = RTSemMutexRequest(pThis->hMtx, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ PRTTRACELOGWREVTDESC pEvtDescInt = (PRTTRACELOGWREVTDESC)RTAvlPVGet(&pThis->pTreeEvtDescs, (void *)pEvtDesc);
+ RTSemMutexRelease(pThis->hMtx);
+ return pEvtDescInt;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Initializes the trace log.
+ *
+ * @returns IPRT status code.
+ * @param pThis The trace log writer instance.
+ * @param pszDesc The description to use.
+ */
+static int rtTraceLogWrInit(PRTTRACELOGWRINT pThis, const char *pszDesc)
+{
+ /* Start by assembling the header. */
+ TRACELOGHDR Hdr;
+
+ RT_ZERO(Hdr);
+ memcpy(&Hdr.szMagic[0], TRACELOG_HDR_MAGIC, sizeof(Hdr.szMagic));
+ Hdr.u32Endianess = TRACELOG_HDR_ENDIANESS; /* Endianess marker. */
+ Hdr.u32Version = TRACELOG_VERSION;
+ Hdr.fFlags = 0;
+ Hdr.cbStrDesc = pszDesc ? (uint32_t)strlen(pszDesc) : 0;
+ Hdr.cbTypePtr = sizeof(uintptr_t);
+ Hdr.cbTypeSize = sizeof(size_t);
+ Hdr.u64TsStart = RTTimeNanoTS();
+ int rc = rtTraceLogWrStream(pThis, &Hdr, sizeof(Hdr));
+ if ( RT_SUCCESS(rc)
+ && pszDesc)
+ rc = rtTraceLogWrStream(pThis, pszDesc, Hdr.cbStrDesc);
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) rtTraceLogWrCheckForOverlappingIds(PAVLPVNODECORE pCore, void *pvParam)
+{
+ PCRTTRACELOGEVTDESC pEvtDesc = (PCRTTRACELOGEVTDESC)pvParam;
+ PRTTRACELOGWREVTDESC pEvtDescInt = (PRTTRACELOGWREVTDESC)pCore;
+
+ if (!RTStrCmp(pEvtDesc->pszId, pEvtDescInt->EvtDesc.pszId))
+ return VERR_ALREADY_EXISTS;
+
+ return VINF_SUCCESS;
+}
+
+
+static DECLCALLBACK(int) rtTraceLogWrEvtDescsDestroy(PAVLPVNODECORE pCore, void *pvParam)
+{
+ PRTTRACELOGWREVTDESC pEvtDesc = (PRTTRACELOGWREVTDESC)pCore;
+ RT_NOREF(pvParam);
+
+ RTMemFree(pEvtDesc->pbEvt);
+ RTMemFree(pEvtDesc);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Adds a new event descriptor to the trace log.
+ *
+ * @returns IPRT status code.
+ * @param pThis The trace log writer instance.
+ * @param pEvtDesc The event descriptor to add.
+ * @param ppEvtDescInt Where to store the pointer to the internal
+ * event descriptor - optional.
+ */
+static int rtTraceLogWrEvtDescAdd(PRTTRACELOGWRINT pThis, PCRTTRACELOGEVTDESC pEvtDesc,
+ PRTTRACELOGWREVTDESC *ppEvtDescInt)
+{
+ int rc = RTSemMutexRequest(pThis->hMtx, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ PRTTRACELOGWREVTDESC pEvtDescInt = (PRTTRACELOGWREVTDESC)RTAvlPVGet(&pThis->pTreeEvtDescs, (void *)pEvtDesc);
+ if (!pEvtDescInt)
+ {
+ rc = RTAvlPVDoWithAll(&pThis->pTreeEvtDescs, true, rtTraceLogWrCheckForOverlappingIds, (void *)pEvtDesc);
+ if (RT_SUCCESS(rc))
+ {
+ pEvtDescInt = rtTraceLogWrEvtDescInit(pEvtDesc);
+ if (RT_LIKELY(pEvtDescInt))
+ {
+ pEvtDescInt->Core.Key = (void *)pEvtDesc;
+ pEvtDescInt->u32Id = pThis->cEvtDescs++;
+ bool fIns = RTAvlPVInsert(&pThis->pTreeEvtDescs, &pEvtDescInt->Core);
+ Assert(fIns); RT_NOREF(fIns);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ {
+ TRACELOGEVTDESC EvtDesc;
+
+ RT_ZERO(EvtDesc);
+ memcpy(&EvtDesc.szMagic[0], TRACELOG_EVTDESC_MAGIC, sizeof(EvtDesc.szMagic));
+ EvtDesc.u32Id = pEvtDescInt->u32Id;
+ EvtDesc.u32Severity = rtTraceLogWrConvSeverityToStream(pEvtDescInt->EvtDesc.enmSeverity);
+ EvtDesc.cbStrId = (uint32_t)strlen(pEvtDescInt->EvtDesc.pszId);
+ EvtDesc.cbStrDesc = pEvtDescInt->EvtDesc.pszDesc ? (uint32_t)strlen(pEvtDescInt->EvtDesc.pszDesc) : 0;
+ EvtDesc.cEvtItems = pEvtDescInt->EvtDesc.cEvtItems;
+ rc = rtTraceLogWrStream(pThis, &EvtDesc, sizeof(EvtDesc));
+ if (RT_SUCCESS(rc))
+ rc = rtTraceLogWrStream(pThis, pEvtDescInt->EvtDesc.pszId, EvtDesc.cbStrId);
+ if ( RT_SUCCESS(rc)
+ && pEvtDescInt->EvtDesc.pszDesc)
+ rc = rtTraceLogWrStream(pThis, pEvtDescInt->EvtDesc.pszDesc, EvtDesc.cbStrDesc);
+ if (RT_SUCCESS(rc))
+ {
+ /* Go through the event items. */
+ for (unsigned idxEvtItem = 0; idxEvtItem < EvtDesc.cEvtItems && RT_SUCCESS(rc); idxEvtItem++)
+ {
+ PCRTTRACELOGEVTITEMDESC pEvtItemDesc = &pEvtDescInt->EvtDesc.paEvtItemDesc[idxEvtItem];
+ TRACELOGEVTITEMDESC EvtItemDesc;
+
+ RT_ZERO(EvtItemDesc);
+ memcpy(&EvtItemDesc.szMagic[0], TRACELOG_EVTITEMDESC_MAGIC, sizeof(EvtItemDesc.szMagic));
+ EvtItemDesc.cbStrName = (uint32_t)strlen(pEvtItemDesc->pszName);
+ EvtItemDesc.cbStrDesc = pEvtItemDesc->pszDesc ? (uint32_t)strlen(pEvtItemDesc->pszDesc) : 0;
+ EvtItemDesc.u32Type = rtTraceLogWrConvTypeToStream(pEvtItemDesc->enmType);
+ EvtItemDesc.cbRawData = (uint32_t)pEvtItemDesc->cbRawData;
+ rc = rtTraceLogWrStream(pThis, &EvtItemDesc, sizeof(EvtItemDesc));
+ if (RT_SUCCESS(rc))
+ rc = rtTraceLogWrStream(pThis, pEvtItemDesc->pszName, EvtItemDesc.cbStrName);
+ if ( RT_SUCCESS(rc)
+ && pEvtItemDesc->pszDesc)
+ rc = rtTraceLogWrStream(pThis, pEvtItemDesc->pszDesc, EvtItemDesc.cbStrDesc);
+ }
+ }
+ }
+ }
+
+ if ( RT_SUCCESS(rc)
+ && ppEvtDescInt)
+ *ppEvtDescInt = pEvtDescInt;
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+ RTSemMutexRelease(pThis->hMtx);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Fills a given buffer with the given event data as described in the given descriptor.
+ *
+ * @returns IPRT status code.
+ * @param pThis The trace log writer instance.
+ * @param pEvtDescInt Pointer to the internal event descriptor.
+ * @param pb The byte buffer to fill.
+ * @param va The event data.
+ */
+static int rtTraceLogWrEvtFill(PRTTRACELOGWRINT pThis, PRTTRACELOGWREVTDESC pEvtDescInt, uint8_t *pb, va_list va)
+{
+ int rc = VINF_SUCCESS;
+ uint8_t *pbCur = pb;
+
+ RT_NOREF(pThis);
+
+ for (unsigned i = 0; i < pEvtDescInt->EvtDesc.cEvtItems; i++)
+ {
+ PCRTTRACELOGEVTITEMDESC pEvtItemDesc = &pEvtDescInt->EvtDesc.paEvtItemDesc[i];
+
+ size_t cbItem = rtTraceLogWrGetEvtItemDataSz(pEvtItemDesc);
+ switch (cbItem)
+ {
+ case sizeof(uint8_t):
+ *pbCur++ = va_arg(va, /*uint8_t*/ unsigned);
+ break;
+ case sizeof(uint16_t):
+ *(uint16_t *)pbCur = va_arg(va, /*uint16_t*/ unsigned);
+ pbCur += sizeof(uint16_t);
+ break;
+ case sizeof(uint32_t):
+ *(uint32_t *)pbCur = va_arg(va, uint32_t);
+ pbCur += sizeof(uint32_t);
+ break;
+ case sizeof(uint64_t):
+ *(uint64_t *)pbCur = va_arg(va, uint64_t);
+ pbCur += sizeof(uint64_t);
+ break;
+ default:
+ /* Some raw data item. */
+ Assert(pEvtItemDesc->enmType == RTTRACELOGTYPE_RAWDATA);
+ if (cbItem != 0)
+ {
+ /* Static raw data. */
+ void *pvSrc = va_arg(va, void *);
+ memcpy(pbCur, pvSrc, cbItem);
+ pbCur += cbItem;
+ }
+ else
+ {
+ AssertMsgFailed(("Not implemented!\n"));
+ rc = VERR_NOT_IMPLEMENTED;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @copydoc FNRTTRACELOGWRSTREAM
+ */
+static DECLCALLBACK(int) rtTraceLogWrFileStream(void *pvUser, const void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ RTFILE hFile = (RTFILE)pvUser;
+ return RTFileWrite(hFile, pvBuf, cbBuf, pcbWritten);
+}
+
+
+/**
+ * @copydoc FNRTTRACELOGSTREAMCLOSE
+ */
+static DECLCALLBACK(int) rtTraceLogWrFileStreamClose(void *pvUser)
+{
+ RTFILE hFile = (RTFILE)pvUser;
+ return RTFileClose(hFile);
+}
+
+
+RTDECL(int) RTTraceLogWrCreate(PRTTRACELOGWR phTraceLogWr, const char *pszDesc,
+ PFNRTTRACELOGWRSTREAM pfnStreamOut,
+ PFNRTTRACELOGSTREAMCLOSE pfnStreamClose, void *pvUser)
+{
+ AssertPtrReturn(phTraceLogWr, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnStreamOut, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnStreamClose, VERR_INVALID_POINTER);
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGWRINT pThis = (PRTTRACELOGWRINT)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ rc = RTSemMutexCreate(&pThis->hMtx);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->u32Magic = RTTRACELOGWR_MAGIC;
+ pThis->pfnStreamOut = pfnStreamOut;
+ pThis->pfnStreamClose = pfnStreamClose;
+ pThis->pvUser = pvUser;
+ pThis->u64SeqNoNext = 0;
+ pThis->pTreeEvtDescs = NULL;
+ pThis->cEvtDescs = 0;
+ rc = rtTraceLogWrInit(pThis, pszDesc);
+ if (RT_SUCCESS(rc))
+ {
+ *phTraceLogWr = pThis;
+ return VINF_SUCCESS;
+ }
+ }
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogWrCreateFile(PRTTRACELOGWR phTraceLogWr, const char *pszDesc,
+ const char *pszFilename)
+{
+ AssertPtrReturn(phTraceLogWr, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ RTFILE hFile = NIL_RTFILE;
+ int rc = RTFileOpen(&hFile, pszFilename, RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTraceLogWrCreate(phTraceLogWr, pszDesc, rtTraceLogWrFileStream,
+ rtTraceLogWrFileStreamClose, hFile);
+ if (RT_FAILURE(rc))
+ {
+ RTFileClose(hFile);
+ RTFileDelete(pszFilename);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @copydoc FNRTTRACELOGWRSTREAM
+ */
+static DECLCALLBACK(int) rtTraceLogWrTcpStream(void *pvUser, const void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ PRTTRACELOGWRTCP pTraceLogTcp = (PRTTRACELOGWRTCP)pvUser;
+ int rc = RTTcpWrite(pTraceLogTcp->hSock, pvBuf, cbBuf);
+ if ( RT_SUCCESS(rc)
+ && pcbWritten)
+ *pcbWritten = cbBuf;
+
+ return rc;
+}
+
+
+/**
+ * @copydoc FNRTTRACELOGSTREAMCLOSE
+ */
+static DECLCALLBACK(int) rtTraceLogWrTcpStreamClose(void *pvUser)
+{
+ PRTTRACELOGWRTCP pTraceLogTcp = (PRTTRACELOGWRTCP)pvUser;
+ if (pTraceLogTcp->fIsServer)
+ {
+ RTTcpServerDisconnectClient2(pTraceLogTcp->hSock);
+ RTTcpServerDestroy(pTraceLogTcp->pTcpSrv);
+ }
+ else
+ RTTcpClientClose(pTraceLogTcp->hSock);
+
+ RTMemFree(pTraceLogTcp);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTTraceLogWrCreateTcpServer(PRTTRACELOGWR phTraceLogWr, const char *pszDesc,
+ const char *pszListen, unsigned uPort)
+{
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGWRTCP pTraceLogTcp = (PRTTRACELOGWRTCP)RTMemAllocZ(sizeof(*pTraceLogTcp));
+ if (RT_LIKELY(pTraceLogTcp))
+ {
+ pTraceLogTcp->fIsServer = true;
+
+ rc = RTTcpServerCreateEx(pszListen, uPort, &pTraceLogTcp->pTcpSrv);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTcpServerListen2(pTraceLogTcp->pTcpSrv, &pTraceLogTcp->hSock);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTraceLogWrCreate(phTraceLogWr, pszDesc, rtTraceLogWrTcpStream,
+ rtTraceLogWrTcpStreamClose, pTraceLogTcp);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTTcpServerDisconnectClient2(pTraceLogTcp->hSock);
+ }
+
+ RTTcpServerDestroy(pTraceLogTcp->pTcpSrv);
+ }
+
+ RTMemFree(pTraceLogTcp);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogWrCreateTcpClient(PRTTRACELOGWR phTraceLogWr, const char *pszDesc,
+ const char *pszAddress, unsigned uPort)
+{
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGWRTCP pTraceLogTcp = (PRTTRACELOGWRTCP)RTMemAllocZ(sizeof(*pTraceLogTcp));
+ if (RT_LIKELY(pTraceLogTcp))
+ {
+ pTraceLogTcp->fIsServer = false;
+
+ rc = RTTcpClientConnect(pszAddress, uPort, &pTraceLogTcp->hSock);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTraceLogWrCreate(phTraceLogWr, pszDesc, rtTraceLogWrTcpStream,
+ rtTraceLogWrTcpStreamClose, pTraceLogTcp);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTTcpClientClose(pTraceLogTcp->hSock);
+ }
+
+ RTMemFree(pTraceLogTcp);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogWrDestroy(RTTRACELOGWR hTraceLogWr)
+{
+ if (hTraceLogWr == NIL_RTTRACELOGWR)
+ return VINF_SUCCESS;
+ PRTTRACELOGWRINT pThis = hTraceLogWr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTTRACELOGWR_MAGIC, VERR_INVALID_HANDLE);
+
+ pThis->u32Magic = RTTRACELOGWR_MAGIC_DEAD;
+ pThis->pfnStreamClose(pThis->pvUser);
+ RTAvlPVDestroy(&pThis->pTreeEvtDescs, rtTraceLogWrEvtDescsDestroy, NULL);
+ RTSemMutexDestroy(pThis->hMtx);
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTTraceLogWrAddEvtDesc(RTTRACELOGWR hTraceLogWr, PCRTTRACELOGEVTDESC pEvtDesc)
+{
+ PRTTRACELOGWRINT pThis = hTraceLogWr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTTRACELOGWR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pEvtDesc, VERR_INVALID_POINTER);
+
+ return rtTraceLogWrEvtDescAdd(pThis, pEvtDesc, NULL);
+}
+
+
+RTDECL(int) RTTraceLogWrEvtAdd(RTTRACELOGWR hTraceLogWr, PCRTTRACELOGEVTDESC pEvtDesc, uint32_t fFlags,
+ RTTRACELOGEVTGRPID uGrpId, RTTRACELOGEVTGRPID uParentGrpId,
+ const void *pvEvtData, size_t *pacbRawData)
+{
+ PRTTRACELOGWRINT pThis = hTraceLogWr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTTRACELOGWR_MAGIC, VERR_INVALID_HANDLE);
+
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGWREVTDESC pEvtDescInt = rtTraceLogWrEvtDescGetInternal(pThis, pEvtDesc);
+ if (RT_UNLIKELY(!pEvtDescInt))
+ rc = rtTraceLogWrEvtDescAdd(pThis, pEvtDesc, &pEvtDescInt);
+
+ if ( RT_SUCCESS(rc)
+ && RT_VALID_PTR(pEvtDescInt))
+ {
+ TRACELOGEVT Evt;
+ size_t cbEvtData = rtTraceLogWrEvtInit(&Evt, pEvtDescInt, fFlags, uGrpId, uParentGrpId, pacbRawData);
+
+ rc = rtTraceLogWrEvtStream(pThis, &Evt, pvEvtData, cbEvtData, pacbRawData);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogWrEvtAddSg(RTTRACELOGWR hTraceLogWr, PCRTTRACELOGEVTDESC pEvtDesc, uint32_t fFlags,
+ RTTRACELOGEVTGRPID uGrpId, RTTRACELOGEVTGRPID uParentGrpId,
+ PRTSGBUF *pSgBufEvtData, size_t *pacbRawData)
+{
+ RT_NOREF(hTraceLogWr, pEvtDesc, fFlags, uGrpId, uParentGrpId, pSgBufEvtData, pacbRawData);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+RTDECL(int) RTTraceLogWrEvtAddLV(RTTRACELOGWR hTraceLogWr, PCRTTRACELOGEVTDESC pEvtDesc, uint32_t fFlags,
+ RTTRACELOGEVTGRPID uGrpId, RTTRACELOGEVTGRPID uParentGrpId, va_list va)
+{
+ PRTTRACELOGWRINT pThis = hTraceLogWr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTTRACELOGWR_MAGIC, VERR_INVALID_HANDLE);
+
+ int rc = VINF_SUCCESS;
+ PRTTRACELOGWREVTDESC pEvtDescInt = rtTraceLogWrEvtDescGetInternal(pThis, pEvtDesc);
+ if (RT_UNLIKELY(!pEvtDescInt))
+ rc = rtTraceLogWrEvtDescAdd(pThis, pEvtDesc, &pEvtDescInt);
+
+ if ( RT_SUCCESS(rc)
+ && RT_VALID_PTR(pEvtDescInt))
+ {
+ TRACELOGEVT Evt;
+ size_t cbEvtData = rtTraceLogWrEvtInit(&Evt, pEvtDescInt, fFlags, uGrpId, uParentGrpId, NULL);
+
+ if (cbEvtData)
+ rc = rtTraceLogWrEvtFill(pThis, pEvtDescInt, pEvtDescInt->pbEvt, va);
+ if (RT_SUCCESS(rc))
+ rc = rtTraceLogWrEvtStream(pThis, &Evt, pEvtDescInt->pbEvt, cbEvtData, NULL);
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTTraceLogWrEvtAddL(RTTRACELOGWR hTraceLogWr, PCRTTRACELOGEVTDESC pEvtDesc, uint32_t fFlags,
+ RTTRACELOGEVTGRPID uGrpId, RTTRACELOGEVTGRPID uParentGrpId, ...)
+{
+ va_list va;
+ va_start(va, uParentGrpId);
+ int rc = RTTraceLogWrEvtAddLV(hTraceLogWr, pEvtDesc, fFlags, uGrpId, uParentGrpId, va);
+ va_end(va);
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/math/Makefile.kup b/src/VBox/Runtime/common/math/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/math/Makefile.kup
diff --git a/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm b/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm
new file mode 100644
index 00000000..9c14fc5b
--- /dev/null
+++ b/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm
@@ -0,0 +1,91 @@
+; $Id: RTUInt128MulByU64.asm $
+;; @file
+; IPRT - RTUInt128MulByU64 - AMD64 implementation.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "internal/bignum.mac"
+
+
+BEGINCODE
+
+;;
+; Multiplies a 128-bit number with a 64-bit one.
+;
+; @returns puResult.
+; @param puResult x86:[ebp + 8] gcc:rdi msc:rcx
+; @param puValue1 x86:[ebp + 12] gcc:rsi msc:rdx
+; @param uValue2 x86:[ebp + 16] gcc:rdx msc:r8
+;
+RT_BEGINPROC RTUInt128MulByU64
+; SEH64_SET_FRAME_xSP 0
+SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_GCC
+ %define puResult rdi
+ %define puValue1 rsi
+ %define uValue2 r8
+ mov r8, rdx
+ %else
+ %define puResult rcx
+ %define puValue1 r9
+ %define uValue2 r8
+ mov r9, rdx
+ %endif
+
+ ; puValue1->s.Lo * uValue2
+ mov rax, [puValue1]
+ mul uValue2
+ mov [puResult], rax
+ mov r11, rdx ; Store the lower half of the result.
+
+ ; puValue1->s.Hi * uValue2
+ mov rax, [puValue1 + 8]
+ mul uValue2
+ add r11, rax ; Calc the second half of the result.
+ mov [puResult + 8], r11 ; Store the high half of the result.
+
+ mov rax, puResult
+
+;%elifdef RT_ARCH_X86
+%else
+ %error "unsupported arch"
+%endif
+
+ ret
+ENDPROC RTUInt128MulByU64
+
diff --git a/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm b/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm
new file mode 100644
index 00000000..6e913677
--- /dev/null
+++ b/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm
@@ -0,0 +1,95 @@
+; $Id: RTUInt128MulByU64Ex.asm $
+;; @file
+; IPRT - RTUInt128MulByU64 - AMD64 implementation.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "internal/bignum.mac"
+
+
+BEGINCODE
+
+;;
+; Multiplies a 128-bit number with a 64-bit one, returning a 256-bit result.
+;
+; @returns puResult.
+; @param puResult x86:[ebp + 8] gcc:rdi msc:rcx
+; @param puValue1 x86:[ebp + 12] gcc:rsi msc:rdx
+; @param uValue2 x86:[ebp + 16] gcc:rdx msc:r8
+;
+RT_BEGINPROC RTUInt128MulByU64Ex
+; SEH64_SET_FRAME_xSP 0
+SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_GCC
+ %define puResult rdi
+ %define puValue1 rsi
+ %define uValue2 r8
+ mov r8, rdx
+ %else
+ %define puResult rcx
+ %define puValue1 r9
+ %define uValue2 r8
+ mov r9, rdx
+ %endif
+
+ ; puValue1->s.Lo * uValue2
+ mov rax, [puValue1]
+ mul uValue2
+ mov [puResult], rax ; Store the 1st 64-bit part of the result.
+ mov r11, rdx ; Save the upper 64 bits for later.
+
+ ; puValue1->s.Hi * uValue2
+ mov rax, [puValue1 + 8]
+ mul uValue2
+ add r11, rax ; Calc the second half of the result.
+ adc rdx, 0
+ mov [puResult + 8], r11 ; Store the 2nd 64-bit part of the result.
+ mov [puResult + 16], rdx ; Store the 3rd 64-bit part of the result.
+ xor r10, r10
+ mov [puResult + 24], r10 ; Store the 4th 64-bit part of the result.
+
+ mov rax, puResult
+
+;%elifdef RT_ARCH_X86
+%else
+ %error "unsupported arch"
+%endif
+
+ ret
+ENDPROC RTUInt128MulByU64Ex
+
diff --git a/src/VBox/Runtime/common/math/__fpclassifyd.cpp b/src/VBox/Runtime/common/math/__fpclassifyd.cpp
new file mode 100644
index 00000000..d07957ba
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__fpclassifyd.cpp
@@ -0,0 +1,66 @@
+/* $Id: __fpclassifyd.cpp $ */
+/** @file
+ * IPRT - No-CRT - __fpclassifyd().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __fpclassifyd
+int RT_NOCRT(__fpclassifyd)(double rd)
+{
+ AssertCompile(sizeof(rd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = rd;
+ if (RTFLOAT64U_IS_ZERO(&u))
+ return RT_NOCRT_FP_ZERO;
+ if (RTFLOAT64U_IS_NORMAL(&u))
+ return RT_NOCRT_FP_NORMAL;
+ if (RTFLOAT64U_IS_NAN(&u))
+ return RT_NOCRT_FP_NAN;
+ if (RTFLOAT64U_IS_INF(&u))
+ return RT_NOCRT_FP_INFINITE;
+ Assert(RTFLOAT64U_IS_SUBNORMAL(&u));
+ return RT_NOCRT_FP_SUBNORMAL;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__fpclassifyd);
+
diff --git a/src/VBox/Runtime/common/math/__fpclassifyf.cpp b/src/VBox/Runtime/common/math/__fpclassifyf.cpp
new file mode 100644
index 00000000..ca674eab
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__fpclassifyf.cpp
@@ -0,0 +1,66 @@
+/* $Id: __fpclassifyf.cpp $ */
+/** @file
+ * IPRT - No-CRT - __fpclassifyf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __fpclassifyf
+int RT_NOCRT(__fpclassifyf)(float r32)
+{
+ AssertCompile(sizeof(r32) == sizeof(RTFLOAT32U));
+ RTFLOAT32U u;
+ u.r = r32;
+ if (RTFLOAT32U_IS_ZERO(&u))
+ return RT_NOCRT_FP_ZERO;
+ if (RTFLOAT32U_IS_NORMAL(&u))
+ return RT_NOCRT_FP_NORMAL;
+ if (RTFLOAT32U_IS_NAN(&u))
+ return RT_NOCRT_FP_NAN;
+ if (RTFLOAT32U_IS_INF(&u))
+ return RT_NOCRT_FP_INFINITE;
+ Assert(RTFLOAT32U_IS_SUBNORMAL(&u));
+ return RT_NOCRT_FP_SUBNORMAL;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__fpclassifyf);
+
diff --git a/src/VBox/Runtime/common/math/__fpclassifyl.cpp b/src/VBox/Runtime/common/math/__fpclassifyl.cpp
new file mode 100644
index 00000000..4a29baa4
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__fpclassifyl.cpp
@@ -0,0 +1,101 @@
+/* $Id: __fpclassifyl.cpp $ */
+/** @file
+ * IPRT - No-CRT - __fpclassifyl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __fpclassifyl
+int RT_NOCRT(__fpclassifyl)(long double lrd)
+{
+#ifdef RT_COMPILER_WITH_128BIT_LONG_DOUBLE
+ RTFLOAT128U u;
+ u.rd = lrd;
+ if (RTFLOAT128U_IS_ZERO(&u))
+ return RT_NOCRT_FP_ZERO;
+ if (RTFLOAT128U_IS_NORMAL(&u))
+ return RT_NOCRT_FP_NORMAL;
+ if (RTFLOAT128U_IS_NAN(&u))
+ return RT_NOCRT_FP_NAN;
+ if (RTFLOAT128U_IS_INF(&u))
+ return RT_NOCRT_FP_INFINITE;
+ Assert(RTFLOAT128U_IS_SUBNORMAL(&u));
+ return RT_NOCRT_FP_SUBNORMAL;
+
+#elif defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+ RTFLOAT80U2 u;
+ u.lrd = lrd;
+ if (RTFLOAT80U_IS_ZERO(&u))
+ return RT_NOCRT_FP_ZERO;
+ if (RTFLOAT80U_IS_NORMAL(&u))
+ return RT_NOCRT_FP_NORMAL;
+ if (RTFLOAT80U_IS_NAN(&u))
+ return RT_NOCRT_FP_NAN;
+ if (RTFLOAT80U_IS_INF(&u))
+ return RT_NOCRT_FP_INFINITE;
+ if (RTFLOAT80U_IS_DENORMAL_OR_PSEUDO_DENORMAL(&u))
+ return RT_NOCRT_FP_SUBNORMAL;
+
+ /* Following i387 invalid operand rules here. Adjust as needed for
+ other architectures. */
+ Assert(RTFLOAT80U_IS_387_INVALID(&u));
+ return RT_NOCRT_FP_NAN;
+
+#else
+ AssertCompile(sizeof(lrd) == sizeof(uint64_t));
+ RTFLOAT64U u;
+ u.rd = lrd;
+ if (RTFLOAT64U_IS_ZERO(&u))
+ return RT_NOCRT_FP_ZERO;
+ if (RTFLOAT64U_IS_NORMAL(&u))
+ return RT_NOCRT_FP_NORMAL;
+ if (RTFLOAT64U_IS_NAN(&u))
+ return RT_NOCRT_FP_NAN;
+ if (RTFLOAT64U_IS_INF(&u))
+ return RT_NOCRT_FP_INFINITE;
+ Assert(RTFLOAT64U_IS_SUBNORMAL(&u));
+ return RT_NOCRT_FP_SUBNORMAL;
+#endif
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__fpclassifyl);
+
diff --git a/src/VBox/Runtime/common/math/__isfinite.cpp b/src/VBox/Runtime/common/math/__isfinite.cpp
new file mode 100644
index 00000000..dadb17cb
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__isfinite.cpp
@@ -0,0 +1,57 @@
+/* $Id: __isfinite.cpp $ */
+/** @file
+ * IPRT - No-CRT - __isfinite().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __isfinite
+int RT_NOCRT(__isfinite)(double rd)
+{
+ AssertCompile(sizeof(rd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = rd;
+ return RTFLOAT64U_IS_ZERO(&u) || RTFLOAT64U_IS_NORMAL(&u) || RTFLOAT64U_IS_SUBNORMAL(&u);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__isfinite);
+
diff --git a/src/VBox/Runtime/common/math/__isfinitef.cpp b/src/VBox/Runtime/common/math/__isfinitef.cpp
new file mode 100644
index 00000000..39dae5f8
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__isfinitef.cpp
@@ -0,0 +1,57 @@
+/* $Id: __isfinitef.cpp $ */
+/** @file
+ * IPRT - No-CRT - __isfinitef().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __isfinitef
+int RT_NOCRT(__isfinitef)(float r32)
+{
+ AssertCompile(sizeof(r32) == sizeof(RTFLOAT32U));
+ RTFLOAT32U u;
+ u.r = r32;
+ return RTFLOAT32U_IS_ZERO(&u) || RTFLOAT32U_IS_NORMAL(&u) || RTFLOAT32U_IS_SUBNORMAL(&u);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__isfinitef);
+
diff --git a/src/VBox/Runtime/common/math/__isfinitel.cpp b/src/VBox/Runtime/common/math/__isfinitel.cpp
new file mode 100644
index 00000000..3b655781
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__isfinitel.cpp
@@ -0,0 +1,63 @@
+/* $Id: __isfinitel.cpp $ */
+/** @file
+ * IPRT - No-CRT - __isfinitel().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __isfinitel
+int RT_NOCRT(__isfinitel)(long double lrd)
+{
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ RTFLOAT80U2 u;
+ u.lrd = lrd;
+ return RTFLOAT80U_IS_ZERO(&u) || RTFLOAT80U_IS_NORMAL(&u) || RTFLOAT80U_IS_SUBNORMAL(&u);
+#else
+ AssertCompile(sizeof(lrd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = lrd;
+ return RTFLOAT64U_IS_ZERO(&u) || RTFLOAT64U_IS_NORMAL(&u) || RTFLOAT64U_IS_SUBNORMAL(&u);
+#endif
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__isfinitel);
+
diff --git a/src/VBox/Runtime/common/math/__isinff.cpp b/src/VBox/Runtime/common/math/__isinff.cpp
new file mode 100644
index 00000000..8d4f74e4
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__isinff.cpp
@@ -0,0 +1,57 @@
+/* $Id: __isinff.cpp $ */
+/** @file
+ * IPRT - No-CRT - __isinff().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __isinff
+int RT_NOCRT(__isinff)(float r32)
+{
+ AssertCompile(sizeof(r32) == sizeof(RTFLOAT32U));
+ RTFLOAT32U u;
+ u.r = r32;
+ return RTFLOAT32U_IS_INF(&u);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__isinff);
+
diff --git a/src/VBox/Runtime/common/math/__isinfl.cpp b/src/VBox/Runtime/common/math/__isinfl.cpp
new file mode 100644
index 00000000..6f9f5400
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__isinfl.cpp
@@ -0,0 +1,63 @@
+/* $Id: __isinfl.cpp $ */
+/** @file
+ * IPRT - No-CRT - __isinfl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __isinfl
+int RT_NOCRT(__isinfl)(long double lrd)
+{
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ RTFLOAT80U2 u;
+ u.lrd = lrd;
+ return RTFLOAT80U_IS_INF(&u);
+#else
+ AssertCompile(sizeof(lrd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = lrd;
+ return RTFLOAT64U_IS_INF(&u);
+#endif
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__isinfl);
+
diff --git a/src/VBox/Runtime/common/math/__isnanl.cpp b/src/VBox/Runtime/common/math/__isnanl.cpp
new file mode 100644
index 00000000..7b80a721
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__isnanl.cpp
@@ -0,0 +1,63 @@
+/* $Id: __isnanl.cpp $ */
+/** @file
+ * IPRT - No-CRT - __isnanl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __isnanl
+int RT_NOCRT(__isnanl)(long double lrd)
+{
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ RTFLOAT80U2 u;
+ u.lrd = lrd;
+ return RTFLOAT80U_IS_NAN(&u) || RTFLOAT80U_IS_387_INVALID(&u); /* PORTME */
+#else
+ AssertCompile(sizeof(lrd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = lrd;
+ return RTFLOAT64U_IS_NAN(&u);
+#endif
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__isnanl);
+
diff --git a/src/VBox/Runtime/common/math/__isnormal.cpp b/src/VBox/Runtime/common/math/__isnormal.cpp
new file mode 100644
index 00000000..0d34f3a5
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__isnormal.cpp
@@ -0,0 +1,57 @@
+/* $Id: __isnormal.cpp $ */
+/** @file
+ * IPRT - No-CRT - __isnormal().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __isnormal
+int RT_NOCRT(__isnormal)(double rd)
+{
+ AssertCompile(sizeof(rd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = rd;
+ return RTFLOAT64U_IS_NORMAL(&u);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__isnormal);
+
diff --git a/src/VBox/Runtime/common/math/__isnormalf.cpp b/src/VBox/Runtime/common/math/__isnormalf.cpp
new file mode 100644
index 00000000..e0754293
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__isnormalf.cpp
@@ -0,0 +1,57 @@
+/* $Id: __isnormalf.cpp $ */
+/** @file
+ * IPRT - No-CRT - __isnormalf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __isnormalf
+int RT_NOCRT(__isnormalf)(float r32)
+{
+ AssertCompile(sizeof(r32) == sizeof(RTFLOAT32U));
+ RTFLOAT32U u;
+ u.r = r32;
+ return RTFLOAT32U_IS_NORMAL(&u);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__isnormalf);
+
diff --git a/src/VBox/Runtime/common/math/__isnormall.cpp b/src/VBox/Runtime/common/math/__isnormall.cpp
new file mode 100644
index 00000000..3c153b2b
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__isnormall.cpp
@@ -0,0 +1,63 @@
+/* $Id: __isnormall.cpp $ */
+/** @file
+ * IPRT - No-CRT - __isnormall().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __isnormall
+int RT_NOCRT(__isnormall)(long double lrd)
+{
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ RTFLOAT80U2 u;
+ u.lrd = lrd;
+ return RTFLOAT80U_IS_NORMAL(&u);
+#else
+ AssertCompile(sizeof(lrd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = lrd;
+ return RTFLOAT64U_IS_NORMAL(&u);
+#endif
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__isnormall);
+
diff --git a/src/VBox/Runtime/common/math/__signbit.cpp b/src/VBox/Runtime/common/math/__signbit.cpp
new file mode 100644
index 00000000..576bedcd
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__signbit.cpp
@@ -0,0 +1,57 @@
+/* $Id: __signbit.cpp $ */
+/** @file
+ * IPRT - No-CRT - __signbit().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __signbit
+int RT_NOCRT(__signbit)(double rd)
+{
+ AssertCompile(sizeof(rd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = rd;
+ return u.s.fSign;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__signbit);
+
diff --git a/src/VBox/Runtime/common/math/__signbitf.cpp b/src/VBox/Runtime/common/math/__signbitf.cpp
new file mode 100644
index 00000000..e47a4301
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__signbitf.cpp
@@ -0,0 +1,57 @@
+/* $Id: __signbitf.cpp $ */
+/** @file
+ * IPRT - No-CRT - __signbitf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __signbitf
+int RT_NOCRT(__signbitf)(float r32)
+{
+ AssertCompile(sizeof(r32) == sizeof(RTFLOAT32U));
+ RTFLOAT32U u;
+ u.r = r32;
+ return u.s.fSign;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__signbitf);
+
diff --git a/src/VBox/Runtime/common/math/__signbitl.cpp b/src/VBox/Runtime/common/math/__signbitl.cpp
new file mode 100644
index 00000000..ece02d0d
--- /dev/null
+++ b/src/VBox/Runtime/common/math/__signbitl.cpp
@@ -0,0 +1,62 @@
+/* $Id: __signbitl.cpp $ */
+/** @file
+ * IPRT - No-CRT - __signbitl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef __signbitl
+int RT_NOCRT(__signbitl)(long double lrd)
+{
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ RTFLOAT80U2 u;
+ u.lrd = lrd;
+#else
+ AssertCompile(sizeof(lrd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = lrd;
+#endif
+ return u.s.fSign;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__signbitl);
+
diff --git a/src/VBox/Runtime/common/math/atan.asm b/src/VBox/Runtime/common/math/atan.asm
new file mode 100644
index 00000000..58fdd24a
--- /dev/null
+++ b/src/VBox/Runtime/common/math/atan.asm
@@ -0,0 +1,77 @@
+; $Id: atan.asm $
+;; @file
+; IPRT - No-CRT atan - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Arctangent (partial).
+;
+; @returns st(0) / xmm0
+; @param rd [rbp + 8] / xmm0
+RT_NOCRT_BEGINPROC atan
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ movsd [xSP], xmm0
+ fld qword [xSP]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+ fld1
+
+ fpatan
+
+%ifdef RT_ARCH_AMD64
+ fstp qword [xSP]
+ movsd xmm0, [xSP]
+%endif
+ leave
+ ret
+ENDPROC RT_NOCRT(atan)
+
diff --git a/src/VBox/Runtime/common/math/atan2.asm b/src/VBox/Runtime/common/math/atan2.asm
new file mode 100644
index 00000000..9f01f7e2
--- /dev/null
+++ b/src/VBox/Runtime/common/math/atan2.asm
@@ -0,0 +1,72 @@
+; $Id: atan2.asm $
+;; @file
+; IPRT - No-CRT atan2 - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Arctangent (partial).
+;
+; @returns st(0) / xmm0
+; @param rdY [rbp + 8] / xmm0
+; @param rdX [rbp + 8] / xmm0
+RT_NOCRT_BEGINPROC atan2
+ push xBP
+ mov xBP, xSP
+
+%ifdef RT_ARCH_AMD64
+ sub xSP, 20h
+ movsd [xSP + 10h], xmm1
+ movsd [xSP], xmm0
+ fld qword [xSP]
+ fld qword [xSP + 10h]
+%else
+ fld qword [xBP + xCB*2]
+ fld qword [xBP + xCB*2 + 8]
+%endif
+
+ fpatan
+
+%ifdef RT_ARCH_AMD64
+ fstp qword [xSP]
+ movsd xmm0, [xSP]
+%endif
+ leave
+ ret
+ENDPROC RT_NOCRT(atan2)
+
diff --git a/src/VBox/Runtime/common/math/atan2f.asm b/src/VBox/Runtime/common/math/atan2f.asm
new file mode 100644
index 00000000..6245cbba
--- /dev/null
+++ b/src/VBox/Runtime/common/math/atan2f.asm
@@ -0,0 +1,72 @@
+; $Id: atan2f.asm $
+;; @file
+; IPRT - No-CRT atan2f - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Arctangent (partial).
+;
+; @returns st(0) / xmm0
+; @param r32Y [rbp + 8] / xmm0
+; @param r32X [rbp + 8] / xmm0
+RT_NOCRT_BEGINPROC atan2f
+ push xBP
+ mov xBP, xSP
+
+%ifdef RT_ARCH_AMD64
+ sub xSP, 20h
+ movss [xSP + 10h], xmm1
+ movss [xSP], xmm0
+ fld dword [xSP]
+ fld dword [xSP + 10h]
+%else
+ fld dword [xBP + xCB*2]
+ fld dword [xBP + xCB*2 + 4]
+%endif
+
+ fpatan
+
+%ifdef RT_ARCH_AMD64
+ fstp dword [xSP]
+ movss xmm0, [xSP]
+%endif
+ leave
+ ret
+ENDPROC RT_NOCRT(atan2f)
+
diff --git a/src/VBox/Runtime/common/math/atanf.asm b/src/VBox/Runtime/common/math/atanf.asm
new file mode 100644
index 00000000..7b0862ae
--- /dev/null
+++ b/src/VBox/Runtime/common/math/atanf.asm
@@ -0,0 +1,77 @@
+; $Id: atanf.asm $
+;; @file
+; IPRT - No-CRT atanf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Arctangent (partial).
+;
+; @returns st(0) / xmm0
+; @param r32 [rbp + 8] / xmm0
+RT_NOCRT_BEGINPROC atanf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ movss [xSP], xmm0
+ fld dword [xSP]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+ fld1
+
+ fpatan
+
+%ifdef RT_ARCH_AMD64
+ fstp dword [xSP]
+ movss xmm0, [xSP]
+%endif
+ leave
+ ret
+ENDPROC RT_NOCRT(atanf)
+
diff --git a/src/VBox/Runtime/common/math/bignum-amd64-x86.asm b/src/VBox/Runtime/common/math/bignum-amd64-x86.asm
new file mode 100644
index 00000000..32ea98aa
--- /dev/null
+++ b/src/VBox/Runtime/common/math/bignum-amd64-x86.asm
@@ -0,0 +1,891 @@
+; $Id: bignum-amd64-x86.asm $
+;; @file
+; IPRT - Big Integer Numbers, AMD64 and X86 Assembly Workers
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+;*********************************************************************************************************************************
+;* Header Files *
+;*********************************************************************************************************************************
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "internal/bignum.mac"
+
+
+;*********************************************************************************************************************************
+;* Defined Constants And Macros *
+;*********************************************************************************************************************************
+%ifdef RT_ARCH_AMD64
+ %macro sahf 0
+ %error "SAHF not supported on ancient AMD64"
+ %endmacro
+ %macro lahf 0
+ %error "LAHF not supported on ancient AMD64"
+ %endmacro
+%endif
+
+
+BEGINCODE
+
+;;
+; Subtracts a number (pauSubtrahend) from a larger number (pauMinuend) and
+; stores the result in pauResult.
+;
+; All three numbers are zero padded such that a borrow can be carried one (or
+; two for 64-bit) elements beyond the end of the largest number.
+;
+; @returns nothing.
+; @param pauResult x86:[ebp + 8] gcc:rdi msc:rcx
+; @param pauMinuend x86:[ebp + 12] gcc:rsi msc:rdx
+; @param pauSubtrahend x86:[ebp + 16] gcc:rdx msc:r8
+; @param cUsed x86:[ebp + 20] gcc:rcx msc:r9
+;
+BEGINPROC rtBigNumMagnitudeSubAssemblyWorker
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_GCC
+ %define pauResult rdi
+ %define pauMinuend rsi
+ %define pauSubtrahend rdx
+ %define cUsed ecx
+ %else
+ %define pauResult rcx
+ %define pauMinuend rdx
+ %define pauSubtrahend r8
+ %define cUsed r9d
+ %endif
+ xor r11d, r11d ; index register.
+
+ %if RTBIGNUM_ELEMENT_SIZE == 4
+ add cUsed, 1 ; cUsed = RT_ALIGN(cUsed, 2) / 2
+ shr cUsed, 1
+ %endif
+ cmp cUsed, 8 ; Skip the big loop if small number.
+ jb .small_job
+
+ mov r10d, cUsed
+ shr r10d, 3
+ clc
+.big_loop:
+ mov rax, [pauMinuend + r11]
+ sbb rax, [pauSubtrahend + r11]
+ mov [pauResult + r11], rax
+ mov rax, [pauMinuend + r11 + 8]
+ sbb rax, [pauSubtrahend + r11 + 8]
+ mov [pauResult + r11 + 8], rax
+ mov rax, [pauMinuend + r11 + 16]
+ sbb rax, [pauSubtrahend + r11 + 16]
+ mov [pauResult + r11 + 16], rax
+ mov rax, [pauMinuend + r11 + 24]
+ sbb rax, [pauSubtrahend + r11 + 24]
+ mov [pauResult + r11 + 24], rax
+ mov rax, [pauMinuend + r11 + 32]
+ sbb rax, [pauSubtrahend + r11 + 32]
+ mov [pauResult + r11 + 32], rax
+ mov rax, [pauMinuend + r11 + 40]
+ sbb rax, [pauSubtrahend + r11 + 40]
+ mov [pauResult + r11 + 40], rax
+ mov rax, [pauMinuend + r11 + 48]
+ sbb rax, [pauSubtrahend + r11 + 48]
+ mov [pauResult + r11 + 48], rax
+ mov rax, [pauMinuend + r11 + 56]
+ sbb rax, [pauSubtrahend + r11 + 56]
+ mov [pauResult + r11 + 56], rax
+ lea r11, [r11 + 64]
+ dec r10d ; Does not change CF.
+ jnz .big_loop
+
+ %if 0 ; Ancient AMD CPUs does have lahf/sahf, thus the mess in the %else.
+ lahf ; Save CF
+ and cUsed, 7 ; Up to seven odd rounds.
+ jz .done
+ sahf ; Restore CF.
+ jmp .small_loop ; Skip CF=1 (clc).
+ %else
+ jnc .no_carry
+ and cUsed, 7 ; Up to seven odd rounds.
+ jz .done
+ stc
+ jmp .small_loop ; Skip CF=1 (clc).
+.no_carry:
+ and cUsed, 7 ; Up to seven odd rounds.
+ jz .done
+ %endif
+.small_job:
+ clc
+.small_loop:
+ mov rax, [pauMinuend + r11]
+ sbb rax, [pauSubtrahend + r11]
+ mov [pauResult + r11], rax
+ lea r11, [r11 + 8]
+ dec cUsed ; does not change CF.
+ jnz .small_loop
+ %ifdef RT_STRICT
+ jnc .done
+ int3
+ %endif
+.done:
+
+%elifdef RT_ARCH_X86
+ push edi
+ push esi
+ push ebx
+
+ mov edi, [ebp + 08h] ; pauResult
+ %define pauResult edi
+ mov ecx, [ebp + 0ch] ; pauMinuend
+ %define pauMinuend ecx
+ mov edx, [ebp + 10h] ; pauSubtrahend
+ %define pauSubtrahend edx
+ mov esi, [ebp + 14h] ; cUsed
+ %define cUsed esi
+
+ xor ebx, ebx ; index register.
+
+ cmp cUsed, 8 ; Skip the big loop if small number.
+ jb .small_job
+
+ shr cUsed, 3
+ clc
+.big_loop:
+ mov eax, [pauMinuend + ebx]
+ sbb eax, [pauSubtrahend + ebx]
+ mov [pauResult + ebx], eax
+ mov eax, [pauMinuend + ebx + 4]
+ sbb eax, [pauSubtrahend + ebx + 4]
+ mov [pauResult + ebx + 4], eax
+ mov eax, [pauMinuend + ebx + 8]
+ sbb eax, [pauSubtrahend + ebx + 8]
+ mov [pauResult + ebx + 8], eax
+ mov eax, [pauMinuend + ebx + 12]
+ sbb eax, [pauSubtrahend + ebx + 12]
+ mov [pauResult + ebx + 12], eax
+ mov eax, [pauMinuend + ebx + 16]
+ sbb eax, [pauSubtrahend + ebx + 16]
+ mov [pauResult + ebx + 16], eax
+ mov eax, [pauMinuend + ebx + 20]
+ sbb eax, [pauSubtrahend + ebx + 20]
+ mov [pauResult + ebx + 20], eax
+ mov eax, [pauMinuend + ebx + 24]
+ sbb eax, [pauSubtrahend + ebx + 24]
+ mov [pauResult + ebx + 24], eax
+ mov eax, [pauMinuend + ebx + 28]
+ sbb eax, [pauSubtrahend + ebx + 28]
+ mov [pauResult + ebx + 28], eax
+ lea ebx, [ebx + 32]
+ dec cUsed ; Does not change CF.
+ jnz .big_loop
+
+ lahf ; Save CF
+ mov cUsed, [ebp + 14h] ; Up to three final rounds.
+ and cUsed, 7
+ jz .done
+ sahf ; Restore CF.
+ jmp .small_loop ; Skip CF=1 (clc).
+
+.small_job:
+ clc
+.small_loop:
+ mov eax, [pauMinuend + ebx]
+ sbb eax, [pauSubtrahend + ebx]
+ mov [pauResult + ebx], eax
+ lea ebx, [ebx + 4]
+ dec cUsed ; Does not change CF
+ jnz .small_loop
+ %ifdef RT_STRICT
+ jnc .done
+ int3
+ %endif
+.done:
+
+ pop ebx
+ pop esi
+ pop edi
+%else
+ %error "Unsupported arch"
+%endif
+
+ leave
+ ret
+%undef pauResult
+%undef pauMinuend
+%undef pauSubtrahend
+%undef cUsed
+ENDPROC rtBigNumMagnitudeSubAssemblyWorker
+
+
+
+;;
+; Subtracts a number (pauSubtrahend) from a larger number (pauMinuend) and
+; stores the result in pauResult.
+;
+; All three numbers are zero padded such that a borrow can be carried one (or
+; two for 64-bit) elements beyond the end of the largest number.
+;
+; @returns nothing.
+; @param pauResultMinuend x86:[ebp + 8] gcc:rdi msc:rcx
+; @param pauSubtrahend x86:[ebp + 12] gcc:rsi msc:rdx
+; @param cUsed x86:[ebp + 16] gcc:rdx msc:r8
+;
+BEGINPROC rtBigNumMagnitudeSubThisAssemblyWorker
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_GCC
+ %define pauResultMinuend rdi
+ %define pauSubtrahend rsi
+ %define cUsed edx
+ %else
+ %define pauResultMinuend rcx
+ %define pauSubtrahend rdx
+ %define cUsed r8d
+ %endif
+ xor r11d, r11d ; index register.
+
+ %if RTBIGNUM_ELEMENT_SIZE == 4
+ add cUsed, 1 ; cUsed = RT_ALIGN(cUsed, 2) / 2
+ shr cUsed, 1
+ %endif
+ cmp cUsed, 8 ; Skip the big loop if small number.
+ jb .small_job
+
+ mov r10d, cUsed
+ shr r10d, 3
+ clc
+.big_loop:
+ mov rax, [pauSubtrahend + r11]
+ sbb [pauResultMinuend + r11], rax
+ mov rax, [pauSubtrahend + r11 + 8]
+ sbb [pauResultMinuend + r11 + 8], rax
+ mov rax, [pauSubtrahend + r11 + 16]
+ sbb [pauResultMinuend + r11 + 16], rax
+ mov rax, [pauSubtrahend + r11 + 24]
+ sbb [pauResultMinuend + r11 + 24], rax
+ mov rax, [pauSubtrahend + r11 + 32]
+ sbb [pauResultMinuend + r11 + 32], rax
+ mov rax, [pauSubtrahend + r11 + 40]
+ sbb [pauResultMinuend + r11 + 40], rax
+ mov rax, [pauSubtrahend + r11 + 48]
+ sbb [pauResultMinuend + r11 + 48], rax
+ mov rax, [pauSubtrahend + r11 + 56]
+ sbb [pauResultMinuend + r11 + 56], rax
+ lea r11, [r11 + 64]
+ dec r10d ; Does not change CF.
+ jnz .big_loop
+
+ %if 0 ; Ancient AMD CPUs does have lahf/sahf, thus the mess in the %else.
+ lahf ; Save CF
+ and cUsed, 7 ; Up to seven odd rounds.
+ jz .done
+ sahf ; Restore CF.
+ jmp .small_loop ; Skip CF=1 (clc).
+ %else
+ jnc .no_carry
+ and cUsed, 7 ; Up to seven odd rounds.
+ jz .done
+ stc
+ jmp .small_loop ; Skip CF=1 (clc).
+.no_carry:
+ and cUsed, 7 ; Up to seven odd rounds.
+ jz .done
+ %endif
+.small_job:
+ clc
+.small_loop:
+ mov rax, [pauSubtrahend + r11]
+ sbb [pauResultMinuend + r11], rax
+ lea r11, [r11 + 8]
+ dec cUsed ; does not change CF.
+ jnz .small_loop
+ %ifdef RT_STRICT
+ jnc .done
+ int3
+ %endif
+.done:
+
+%elifdef RT_ARCH_X86
+ push edi
+ push ebx
+
+ mov edi, [ebp + 08h] ; pauResultMinuend
+ %define pauResultMinuend edi
+ mov edx, [ebp + 0ch] ; pauSubtrahend
+ %define pauSubtrahend edx
+ mov ecx, [ebp + 10h] ; cUsed
+ %define cUsed ecx
+
+ xor ebx, ebx ; index register.
+
+ cmp cUsed, 8 ; Skip the big loop if small number.
+ jb .small_job
+
+ shr cUsed, 3
+ clc
+.big_loop:
+ mov eax, [pauSubtrahend + ebx]
+ sbb [pauResultMinuend + ebx], eax
+ mov eax, [pauSubtrahend + ebx + 4]
+ sbb [pauResultMinuend + ebx + 4], eax
+ mov eax, [pauSubtrahend + ebx + 8]
+ sbb [pauResultMinuend + ebx + 8], eax
+ mov eax, [pauSubtrahend + ebx + 12]
+ sbb [pauResultMinuend + ebx + 12], eax
+ mov eax, [pauSubtrahend + ebx + 16]
+ sbb [pauResultMinuend + ebx + 16], eax
+ mov eax, [pauSubtrahend + ebx + 20]
+ sbb [pauResultMinuend + ebx + 20], eax
+ mov eax, [pauSubtrahend + ebx + 24]
+ sbb [pauResultMinuend + ebx + 24], eax
+ mov eax, [pauSubtrahend + ebx + 28]
+ sbb [pauResultMinuend + ebx + 28], eax
+ lea ebx, [ebx + 32]
+ dec cUsed ; Does not change CF.
+ jnz .big_loop
+
+ lahf ; Save CF
+ mov cUsed, [ebp + 10h] ; Up to seven odd rounds.
+ and cUsed, 7
+ jz .done
+ sahf ; Restore CF.
+ jmp .small_loop ; Skip CF=1 (clc).
+
+.small_job:
+ clc
+.small_loop:
+ mov eax, [pauSubtrahend + ebx]
+ sbb [pauResultMinuend + ebx], eax
+ lea ebx, [ebx + 4]
+ dec cUsed ; Does not change CF
+ jnz .small_loop
+ %ifdef RT_STRICT
+ jnc .done
+ int3
+ %endif
+.done:
+
+ pop ebx
+ pop edi
+%else
+ %error "Unsupported arch"
+%endif
+
+ leave
+ ret
+ENDPROC rtBigNumMagnitudeSubThisAssemblyWorker
+
+
+;;
+; Shifts an element array one bit to the left, returning the final carry value.
+;
+; On 64-bit hosts the array is always zero padded to a multiple of 8 bytes, so
+; we can use 64-bit operand sizes even if the element type is 32-bit.
+;
+; @returns The final carry value.
+; @param pauElements x86:[ebp + 8] gcc:rdi msc:rcx
+; @param cUsed x86:[ebp + 12] gcc:rsi msc:rdx
+; @param uCarry x86:[ebp + 16] gcc:rdx msc:r8
+;
+BEGINPROC rtBigNumMagnitudeShiftLeftOneAssemblyWorker
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_GCC
+ %define pauElements rdi
+ %define cUsed esi
+ %define uCarry edx
+ %else
+ %define pauElements rcx
+ %define cUsed edx
+ %define uCarry r8d
+ %endif
+%elifdef RT_ARCH_X86
+ %define pauElements ecx
+ mov pauElements, [ebp + 08h]
+ %define cUsed edx
+ mov cUsed, [ebp + 0ch]
+ %define uCarry eax
+ mov uCarry, [ebp + 10h]
+%else
+ %error "Unsupported arch."
+%endif
+ ; Lots to do?
+ cmp cUsed, 8
+ jae .big_loop_init
+
+ ; Check for empty array.
+ test cUsed, cUsed
+ jz .no_elements
+ jmp .small_loop_init
+
+ ; Big loop - 8 unrolled loop iterations.
+.big_loop_init:
+%ifdef RT_ARCH_AMD64
+ mov r11d, cUsed
+%endif
+ shr cUsed, 3
+ test uCarry, uCarry ; clear the carry flag
+ jz .big_loop
+ stc
+.big_loop:
+%if RTBIGNUM_ELEMENT_SIZE == 8
+ rcl qword [pauElements], 1
+ rcl qword [pauElements + 8], 1
+ rcl qword [pauElements + 16], 1
+ rcl qword [pauElements + 24], 1
+ rcl qword [pauElements + 32], 1
+ rcl qword [pauElements + 40], 1
+ rcl qword [pauElements + 48], 1
+ rcl qword [pauElements + 56], 1
+ lea pauElements, [pauElements + 64]
+%else
+ rcl dword [pauElements], 1
+ rcl dword [pauElements + 4], 1
+ rcl dword [pauElements + 8], 1
+ rcl dword [pauElements + 12], 1
+ rcl dword [pauElements + 16], 1
+ rcl dword [pauElements + 20], 1
+ rcl dword [pauElements + 24], 1
+ rcl dword [pauElements + 28], 1
+ lea pauElements, [pauElements + 32]
+%endif
+ dec cUsed
+ jnz .big_loop
+
+ ; More to do?
+ pushf ; save carry flag (uCarry no longer used on x86).
+%ifdef RT_ARCH_AMD64
+ mov cUsed, r11d
+%else
+ mov cUsed, [ebp + 0ch]
+%endif
+ and cUsed, 7
+ jz .restore_cf_and_return ; Jump if we're good and done.
+ popf ; Restore CF.
+ jmp .small_loop ; Deal with the odd rounds.
+.restore_cf_and_return:
+ popf
+ jmp .carry_to_eax
+
+ ; Small loop - One round at the time.
+.small_loop_init:
+ test uCarry, uCarry ; clear the carry flag
+ jz .small_loop
+ stc
+.small_loop:
+%if RTBIGNUM_ELEMENT_SIZE == 8
+ rcl qword [pauElements], 1
+ lea pauElements, [pauElements + 8]
+%else
+ rcl dword [pauElements], 1
+ lea pauElements, [pauElements + 4]
+%endif
+ dec cUsed
+ jnz .small_loop
+
+ ; Calculate return value.
+.carry_to_eax:
+ mov eax, 0
+ jnc .return
+ inc eax
+.return:
+ leave
+ ret
+
+.no_elements:
+ mov eax, uCarry
+ jmp .return
+ENDPROC rtBigNumMagnitudeShiftLeftOneAssemblyWorker
+
+
+;;
+; Performs a 128-bit by 64-bit division on 64-bit and
+; a 64-bit by 32-bit divison on 32-bit.
+;
+; @returns nothing.
+; @param puQuotient x86:[ebp + 8] gcc:rdi msc:rcx Double element.
+; @param puRemainder x86:[ebp + 12] gcc:rsi msc:rdx Normal element.
+; @param uDividendHi x86:[ebp + 16] gcc:rdx msc:r8
+; @param uDividendLo x86:[ebp + 20] gcc:rcx msc:r9
+; @param uDivisior x86:[ebp + 24] gcc:r8 msc:[rbp + 30h]
+;
+BEGINPROC rtBigNumElement2xDiv2xBy1x
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %if RTBIGNUM_ELEMENT_SIZE == 4
+ %error "sorry not implemented yet."
+ sorry not implemented yet.
+ %endif
+
+ %define uDividendHi rdx
+ %define uDividendLo rax
+ %ifdef ASM_CALL64_GCC
+ %define uDivisor r8
+ %define puQuotient rdi
+ %define puRemainder rsi
+ mov rax, rcx
+ %else
+ %define puQuotient rcx
+ %define puRemainder r11
+ %define uDivisor r10
+ mov r11, rdx
+ mov r10, [rbp + 30h]
+ mov rdx, r8
+ mov rax, r9
+ %endif
+
+%elifdef RT_ARCH_X86
+ push edi
+ push ebx
+
+ %define uDividendHi edx
+ mov uDividendHi, [ebp + 10h]
+ %define uDividendLo eax
+ mov uDividendLo, [ebp + 14h]
+ %define uDivisor ecx
+ mov uDivisor, [ebp + 18h]
+ %define puQuotient edi
+ mov puQuotient, [ebp + 08h]
+ %define puRemainder ebx
+ mov puRemainder, [ebp + 0ch]
+%else
+ %error "Unsupported arch."
+%endif
+
+%ifdef RT_STRICT
+ ;
+ ; The dividend shall not be zero.
+ ;
+ test uDivisor, uDivisor
+ jnz .divisor_not_zero
+ int3
+.divisor_not_zero:
+%endif
+
+ ;
+ ; Avoid division overflow. This will calculate the high part of the quotient.
+ ;
+ mov RTBIGNUM_ELEMENT_PRE [puQuotient + RTBIGNUM_ELEMENT_SIZE], 0
+ cmp uDividendHi, uDivisor
+ jb .do_divide
+ push xAX
+ mov xAX, xDX
+ xor edx, edx
+ div uDivisor
+ mov RTBIGNUM_ELEMENT_PRE [puQuotient + RTBIGNUM_ELEMENT_SIZE], xAX
+ pop xAX
+
+ ;
+ ; Perform the division and store the result.
+ ;
+.do_divide:
+ div uDivisor
+ mov RTBIGNUM_ELEMENT_PRE [puQuotient], xAX
+ mov RTBIGNUM_ELEMENT_PRE [puRemainder], xDX
+
+
+%ifdef RT_ARCH_X86
+ pop ebx
+ pop edi
+%endif
+ leave
+ ret
+ENDPROC rtBigNumElement2xDiv2xBy1x
+
+
+;;
+; Performs the core of long multiplication.
+;
+; @returns nothing.
+; @param pauResult x86:[ebp + 8] gcc:rdi msc:rcx Initialized to zero.
+; @param pauMultiplier x86:[ebp + 12] gcc:rsi msc:rdx
+; @param cMultiplier x86:[ebp + 16] gcc:rdx msc:r8
+; @param pauMultiplicand x86:[ebp + 20] gcc:rcx msc:r9
+; @param cMultiplicand x86:[ebp + 24] gcc:r8 msc:[rbp + 30h]
+;
+BEGINPROC rtBigNumMagnitudeMultiplyAssemblyWorker
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %if RTBIGNUM_ELEMENT_SIZE == 4
+ %error "sorry not implemented yet."
+ sorry not implemented yet.
+ %endif
+
+ %ifdef ASM_CALL64_GCC
+ %define pauResult rdi
+ %define pauMultiplier rsi
+ %define cMultiplier r9
+ %define pauMultiplicand rcx
+ %define cMultiplicand r8
+ mov r9d, edx ; cMultiplier
+ mov r8d, r8d ; cMultiplicand - paranoia
+ %define uMultiplier r10
+ %define iMultiplicand r11
+ %else
+ %define pauResult rcx
+ %define pauMultiplier r11
+ %define cMultiplier r8
+ %define pauMultiplicand r9
+ %define cMultiplicand r10
+ mov pauMultiplier, rdx
+ mov r10d, dword [rbp + 30h] ; cMultiplicand
+ mov r8d, r8d ; cMultiplier - paranoia
+ %define uMultiplier r12
+ push r12
+ %define iMultiplicand r13
+ push r13
+ %endif
+
+%elifdef RT_ARCH_X86
+ push edi
+ push esi
+ push ebx
+ sub esp, 10h
+ %define pauResult edi
+ mov pauResult, [ebp + 08h]
+ %define pauMultiplier dword [ebp + 0ch]
+ %define cMultiplier dword [ebp + 10h]
+ %define pauMultiplicand ecx
+ mov pauMultiplicand, [ebp + 14h]
+ %define cMultiplicand dword [ebp + 18h]
+ %define uMultiplier dword [ebp - 10h]
+ %define iMultiplicand ebx
+
+%else
+ %error "Unsupported arch."
+%endif
+
+ ;
+ ; Check that the multiplicand isn't empty (avoids an extra jump in the inner loop).
+ ;
+ cmp cMultiplicand, 0
+ je .done
+
+ ;
+ ; Loop thru each element in the multiplier.
+ ;
+ ; while (cMultiplier-- > 0)
+.multiplier_loop:
+ cmp cMultiplier, 0
+ jz .done
+ dec cMultiplier
+
+ ; uMultiplier = *pauMultiplier
+%ifdef RT_ARCH_X86
+ mov edx, pauMultiplier
+ mov eax, [edx]
+ mov uMultiplier, eax
+%else
+ mov uMultiplier, [pauMultiplier]
+%endif
+ ; for (iMultiplicand = 0; iMultiplicand < cMultiplicand; iMultiplicand++)
+ xor iMultiplicand, iMultiplicand
+.multiplicand_loop:
+ mov xAX, [pauMultiplicand + iMultiplicand * RTBIGNUM_ELEMENT_SIZE]
+ mul uMultiplier
+ add [pauResult + iMultiplicand * RTBIGNUM_ELEMENT_SIZE], xAX
+ adc [pauResult + iMultiplicand * RTBIGNUM_ELEMENT_SIZE + RTBIGNUM_ELEMENT_SIZE], xDX
+ jnc .next_multiplicand
+ lea xDX, [iMultiplicand + 2]
+.next_adc:
+ adc RTBIGNUM_ELEMENT_PRE [pauResult + xDX * RTBIGNUM_ELEMENT_SIZE], 0
+ inc xDX
+ jc .next_adc
+
+.next_multiplicand:
+ inc iMultiplicand ; iMultiplicand++
+ cmp iMultiplicand, cMultiplicand ; iMultiplicand < cMultiplicand
+ jb .multiplicand_loop
+
+ ; Advance and loop on multiplier.
+ add pauMultiplier, RTBIGNUM_ELEMENT_SIZE
+ add pauResult, RTBIGNUM_ELEMENT_SIZE
+ jmp .multiplier_loop
+
+.done:
+
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_GCC
+ %else
+ pop r13
+ pop r12
+ %endif
+%elifdef RT_ARCH_X86
+ add esp, 10h
+ pop ebx
+ pop esi
+ pop edi
+%endif
+ leave
+ ret
+ENDPROC rtBigNumMagnitudeMultiplyAssemblyWorker
+
+;;
+; Assembly implementation of the D4 step of Knuth's division algorithm.
+;
+; This subtracts Divisor * Qhat from the dividend at the current J index.
+;
+; @returns true if negative result (unlikely), false if positive.
+; @param pauDividendJ x86:[ebp + 8] gcc:rdi msc:rcx Initialized to zero.
+; @param pauDivisor x86:[ebp + 12] gcc:rsi msc:rdx
+; @param cDivisor x86:[ebp + 16] gcc:edx msc:r8d
+; @param uQhat x86:[ebp + 16] gcc:rcx msc:r9
+;
+BEGINPROC rtBigNumKnuthD4_MulSub
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %if RTBIGNUM_ELEMENT_SIZE == 4
+ %error "sorry not implemented yet."
+ sorry not implemented yet.
+ %endif
+
+ %ifdef ASM_CALL64_GCC
+ %define pauDividendJ rdi
+ %define pauDivisor rsi
+ %define cDivisor r8
+ %define uQhat rcx
+ mov r8d, edx ; cDivisor
+ %define uMulCarry r11
+ %else
+ %define pauDividendJ rcx
+ %define pauDivisor r10
+ %define cDivisor r8
+ %define uQhat r9
+ mov r10, rdx ; pauDivisor
+ mov r8d, r8d ; cDivisor - paranoia
+ %define uMulCarry r11
+ %endif
+
+%elifdef RT_ARCH_X86
+ push edi
+ push esi
+ push ebx
+ %define pauDividendJ edi
+ mov pauDividendJ, [ebp + 08h]
+ %define pauDivisor esi
+ mov pauDivisor, [ebp + 0ch]
+ %define cDivisor ecx
+ mov cDivisor, [ebp + 10h]
+ %define uQhat dword [ebp + 14h]
+ %define uMulCarry ebx
+%else
+ %error "Unsupported arch."
+%endif
+
+%ifdef RT_STRICT
+ ;
+ ; Some sanity checks.
+ ;
+ cmp cDivisor, 0
+ jne .cDivisor_not_zero
+ int3
+.cDivisor_not_zero:
+%endif
+
+ ;
+ ; Initialize the loop.
+ ;
+ xor uMulCarry, uMulCarry
+
+ ;
+ ; do ... while (cDivisor-- > 0);
+ ;
+.the_loop:
+ ; RTUInt128MulU64ByU64(&uSub, uQhat, pauDivisor[i]);
+ mov xAX, uQhat
+ mul RTBIGNUM_ELEMENT_PRE [pauDivisor]
+ ; RTUInt128AssignAddU64(&uSub, uMulCarry);
+ add xAX, uMulCarry
+ adc xDX, 0
+ mov uMulCarry, xDX
+ ; Subtract uSub.s.Lo+fCarry from pauDividendJ[i]
+ sub [pauDividendJ], xAX
+ adc uMulCarry, 0
+%ifdef RT_STRICT
+ jnc .uMulCarry_did_not_overflow
+ int3
+.uMulCarry_did_not_overflow:
+%endif
+
+ ; Advance.
+ add pauDividendJ, RTBIGNUM_ELEMENT_SIZE
+ add pauDivisor, RTBIGNUM_ELEMENT_SIZE
+ dec cDivisor
+ jnz .the_loop
+
+ ;
+ ; Final dividend element (no corresponding divisor element).
+ ;
+ sub [pauDividendJ], uMulCarry
+ sbb eax, eax
+ and eax, 1
+
+.done:
+%ifdef RT_ARCH_AMD64
+%elifdef RT_ARCH_X86
+ pop ebx
+ pop esi
+ pop edi
+%endif
+ leave
+ ret
+ENDPROC rtBigNumKnuthD4_MulSub
+
diff --git a/src/VBox/Runtime/common/math/bignum.cpp b/src/VBox/Runtime/common/math/bignum.cpp
new file mode 100644
index 00000000..efa67644
--- /dev/null
+++ b/src/VBox/Runtime/common/math/bignum.cpp
@@ -0,0 +1,2877 @@
+/* $Id: bignum.cpp $ */
+/** @file
+ * IPRT - Big Integer Numbers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/*#ifdef IN_RING3
+# define RTMEM_WRAP_TO_EF_APIS
+#endif*/
+#include "internal/iprt.h"
+#include <iprt/bignum.h>
+
+#include <iprt/asm.h>
+#include <iprt/asm-math.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/memsafer.h>
+#include <iprt/string.h>
+#if RTBIGNUM_ELEMENT_BITS == 64
+# include <iprt/uint128.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Allocation alignment in elements. */
+#ifndef RTMEM_WRAP_TO_EF_APIS
+# define RTBIGNUM_ALIGNMENT 4U
+#else
+# define RTBIGNUM_ALIGNMENT 1U
+#endif
+
+/** The max size (in bytes) of an elements array. */
+#define RTBIGNUM_MAX_SIZE _4M
+
+
+/** Assert the validity of a big number structure pointer in strict builds. */
+#ifdef RT_STRICT
+# define RTBIGNUM_ASSERT_VALID(a_pBigNum) \
+ do { \
+ AssertPtr(a_pBigNum); \
+ Assert(!(a_pBigNum)->fCurScrambled); \
+ Assert( (a_pBigNum)->cUsed == (a_pBigNum)->cAllocated \
+ || ASMMemIsZero(&(a_pBigNum)->pauElements[(a_pBigNum)->cUsed], \
+ ((a_pBigNum)->cAllocated - (a_pBigNum)->cUsed) * RTBIGNUM_ELEMENT_SIZE)); \
+ } while (0)
+#else
+# define RTBIGNUM_ASSERT_VALID(a_pBigNum) do {} while (0)
+#endif
+
+
+/** Enable assembly optimizations. */
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+# define IPRT_BIGINT_WITH_ASM
+#endif
+
+
+/** @def RTBIGNUM_ZERO_ALIGN
+ * For calculating the rtBigNumEnsureExtraZeroElements argument from cUsed.
+ * This has to do with 64-bit assembly instruction operating as RTBIGNUMELEMENT
+ * was 64-bit on some hosts.
+ */
+#if defined(IPRT_BIGINT_WITH_ASM) && ARCH_BITS == 64 && RTBIGNUM_ELEMENT_SIZE == 4 && defined(RT_LITTLE_ENDIAN)
+# define RTBIGNUM_ZERO_ALIGN(a_cUsed) RT_ALIGN_32(a_cUsed, 2)
+#elif defined(IPRT_BIGINT_WITH_ASM)
+# define RTBIGNUM_ZERO_ALIGN(a_cUsed) (a_cUsed)
+#else
+# define RTBIGNUM_ZERO_ALIGN(a_cUsed) (a_cUsed)
+#endif
+
+#define RTBIGNUMELEMENT_HALF_MASK ( ((RTBIGNUMELEMENT)1 << (RTBIGNUM_ELEMENT_BITS / 2)) - (RTBIGNUMELEMENT)1)
+#define RTBIGNUMELEMENT_LO_HALF(a_uElement) ( (RTBIGNUMELEMENT_HALF_MASK) & (a_uElement) )
+#define RTBIGNUMELEMENT_HI_HALF(a_uElement) ( (a_uElement) >> (RTBIGNUM_ELEMENT_BITS / 2) )
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Type the size of two elements. */
+#if RTBIGNUM_ELEMENT_BITS == 64
+typedef RTUINT128U RTBIGNUMELEMENT2X;
+#else
+typedef RTUINT64U RTBIGNUMELEMENT2X;
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+DECLINLINE(int) rtBigNumSetUsed(PRTBIGNUM pBigNum, uint32_t cNewUsed);
+
+#ifdef IPRT_BIGINT_WITH_ASM
+/* bignum-amd64-x86.asm: */
+DECLASM(void) rtBigNumMagnitudeSubAssemblyWorker(RTBIGNUMELEMENT *pauResult, RTBIGNUMELEMENT const *pauMinuend,
+ RTBIGNUMELEMENT const *pauSubtrahend, uint32_t cUsed);
+DECLASM(void) rtBigNumMagnitudeSubThisAssemblyWorker(RTBIGNUMELEMENT *pauMinuendResult, RTBIGNUMELEMENT const *pauSubtrahend,
+ uint32_t cUsed);
+DECLASM(RTBIGNUMELEMENT) rtBigNumMagnitudeShiftLeftOneAssemblyWorker(RTBIGNUMELEMENT *pauElements, uint32_t cUsed,
+ RTBIGNUMELEMENT uCarry);
+DECLASM(void) rtBigNumElement2xDiv2xBy1x(RTBIGNUMELEMENT2X *puQuotient, RTBIGNUMELEMENT *puRemainder,
+ RTBIGNUMELEMENT uDividendHi, RTBIGNUMELEMENT uDividendLo, RTBIGNUMELEMENT uDivisor);
+DECLASM(void) rtBigNumMagnitudeMultiplyAssemblyWorker(PRTBIGNUMELEMENT pauResult,
+ PCRTBIGNUMELEMENT pauMultiplier, uint32_t cMultiplier,
+ PCRTBIGNUMELEMENT pauMultiplicand, uint32_t cMultiplicand);
+#endif
+
+
+
+
+
+/** @name Functions working on one element.
+ * @{ */
+
+DECLINLINE(uint32_t) rtBigNumElementBitCount(RTBIGNUMELEMENT uElement)
+{
+#if RTBIGNUM_ELEMENT_SIZE == 8
+ if (uElement >> 32)
+ return ASMBitLastSetU32((uint32_t)(uElement >> 32)) + 32;
+ return ASMBitLastSetU32((uint32_t)uElement);
+#elif RTBIGNUM_ELEMENT_SIZE == 4
+ return ASMBitLastSetU32(uElement);
+#else
+# error "Bad RTBIGNUM_ELEMENT_SIZE value"
+#endif
+}
+
+
+/**
+ * Does addition with carry.
+ *
+ * This is a candidate for inline assembly on some platforms.
+ *
+ * @returns The result (the sum)
+ * @param uAugend What to add to.
+ * @param uAddend What to add to it.
+ * @param pfCarry Where to read the input carry and return the output
+ * carry.
+ */
+DECLINLINE(RTBIGNUMELEMENT) rtBigNumElementAddWithCarry(RTBIGNUMELEMENT uAugend, RTBIGNUMELEMENT uAddend,
+ RTBIGNUMELEMENT *pfCarry)
+{
+ RTBIGNUMELEMENT uRet = uAugend + uAddend;
+ if (!*pfCarry)
+ *pfCarry = uRet < uAugend;
+ else
+ {
+ uRet += 1;
+ *pfCarry = uRet <= uAugend;
+ }
+ return uRet;
+}
+
+
+#if !defined(IPRT_BIGINT_WITH_ASM) || defined(RT_STRICT)
+/**
+ * Does addition with borrow.
+ *
+ * This is a candidate for inline assembly on some platforms.
+ *
+ * @returns The result (the sum)
+ * @param uMinuend What to subtract from.
+ * @param uSubtrahend What to subtract.
+ * @param pfBorrow Where to read the input borrow and return the output
+ * borrow.
+ */
+DECLINLINE(RTBIGNUMELEMENT) rtBigNumElementSubWithBorrow(RTBIGNUMELEMENT uMinuend, RTBIGNUMELEMENT uSubtrahend,
+ RTBIGNUMELEMENT *pfBorrow)
+{
+ RTBIGNUMELEMENT uRet = uMinuend - uSubtrahend - *pfBorrow;
+
+ /* Figure out if we borrowed. */
+ *pfBorrow = !*pfBorrow ? uMinuend < uSubtrahend : uMinuend <= uSubtrahend;
+ return uRet;
+}
+#endif
+
+/** @} */
+
+
+
+
+/** @name Double element primitives.
+ * @{ */
+
+static int rtBigNumElement2xCopyToMagnitude(RTBIGNUMELEMENT2X const *pValue2x, PRTBIGNUM pDst)
+{
+ int rc;
+ if (pValue2x->s.Hi)
+ {
+ rc = rtBigNumSetUsed(pDst, 2);
+ if (RT_SUCCESS(rc))
+ {
+ pDst->pauElements[0] = pValue2x->s.Lo;
+ pDst->pauElements[1] = pValue2x->s.Hi;
+ }
+ }
+ else if (pValue2x->s.Lo)
+ {
+ rc = rtBigNumSetUsed(pDst, 1);
+ if (RT_SUCCESS(rc))
+ pDst->pauElements[0] = pValue2x->s.Lo;
+ }
+ else
+ rc = rtBigNumSetUsed(pDst, 0);
+ return rc;
+}
+
+static void rtBigNumElement2xDiv(RTBIGNUMELEMENT2X *puQuotient, RTBIGNUMELEMENT2X *puRemainder,
+ RTBIGNUMELEMENT uDividendHi, RTBIGNUMELEMENT uDividendLo,
+ RTBIGNUMELEMENT uDivisorHi, RTBIGNUMELEMENT uDivisorLo)
+{
+ RTBIGNUMELEMENT2X uDividend;
+ uDividend.s.Lo = uDividendLo;
+ uDividend.s.Hi = uDividendHi;
+
+ RTBIGNUMELEMENT2X uDivisor;
+ uDivisor.s.Lo = uDivisorLo;
+ uDivisor.s.Hi = uDivisorHi;
+
+#if RTBIGNUM_ELEMENT_BITS == 64
+ RTUInt128DivRem(puQuotient, puRemainder, &uDividend, &uDivisor);
+#else
+ puQuotient->u = uDividend.u / uDivisor.u;
+ puRemainder->u = uDividend.u % uDivisor.u;
+#endif
+}
+
+#ifndef IPRT_BIGINT_WITH_ASM
+static void rtBigNumElement2xDiv2xBy1x(RTBIGNUMELEMENT2X *puQuotient, RTBIGNUMELEMENT *puRemainder,
+ RTBIGNUMELEMENT uDividendHi, RTBIGNUMELEMENT uDividendLo, RTBIGNUMELEMENT uDivisor)
+{
+ RTBIGNUMELEMENT2X uDividend;
+ uDividend.s.Lo = uDividendLo;
+ uDividend.s.Hi = uDividendHi;
+
+# if RTBIGNUM_ELEMENT_BITS == 64
+ RTBIGNUMELEMENT2X uRemainder2x;
+ RTBIGNUMELEMENT2X uDivisor2x;
+ uDivisor2x.s.Hi = 0;
+ uDivisor2x.s.Lo = uDivisor;
+ /** @todo optimize this. */
+ RTUInt128DivRem(puQuotient, &uRemainder2x, &uDividend, &uDivisor2x);
+ *puRemainder = uRemainder2x.s.Lo;
+# else
+ puQuotient->u = uDividend.u / uDivisor;
+ puRemainder->u = uDividend.u % uDivisor;
+# endif
+}
+#endif
+
+DECLINLINE(void) rtBigNumElement2xDec(RTBIGNUMELEMENT2X *puValue)
+{
+#if RTBIGNUM_ELEMENT_BITS == 64
+ if (puValue->s.Lo-- == 0)
+ puValue->s.Hi--;
+#else
+ puValue->u -= 1;
+#endif
+}
+
+#if 0 /* unused */
+DECLINLINE(void) rtBigNumElement2xAdd1x(RTBIGNUMELEMENT2X *puValue, RTBIGNUMELEMENT uAdd)
+{
+#if RTBIGNUM_ELEMENT_BITS == 64
+ RTUInt128AssignAddU64(puValue, uAdd);
+#else
+ puValue->u += uAdd;
+#endif
+}
+#endif /* unused */
+
+/** @} */
+
+
+
+
+
+/**
+ * Scrambles a big number if required.
+ *
+ * @param pBigNum The big number.
+ */
+DECLINLINE(void) rtBigNumScramble(PRTBIGNUM pBigNum)
+{
+ if (pBigNum->fSensitive)
+ {
+ AssertReturnVoid(!pBigNum->fCurScrambled);
+ if (pBigNum->pauElements)
+ {
+ int rc = RTMemSaferScramble(pBigNum->pauElements, pBigNum->cAllocated * RTBIGNUM_ELEMENT_SIZE); AssertRC(rc);
+ pBigNum->fCurScrambled = RT_SUCCESS(rc);
+ }
+ else
+ pBigNum->fCurScrambled = true;
+ }
+}
+
+
+/**
+ * Unscrambles a big number if required.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ */
+DECLINLINE(int) rtBigNumUnscramble(PRTBIGNUM pBigNum)
+{
+ if (pBigNum->fSensitive)
+ {
+ AssertReturn(pBigNum->fCurScrambled, VERR_INTERNAL_ERROR_2);
+ if (pBigNum->pauElements)
+ {
+ int rc = RTMemSaferUnscramble(pBigNum->pauElements, pBigNum->cAllocated * RTBIGNUM_ELEMENT_SIZE); AssertRC(rc);
+ pBigNum->fCurScrambled = !RT_SUCCESS(rc);
+ return rc;
+ }
+ else
+ pBigNum->fCurScrambled = false;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Getter function for pauElements which extends the array to infinity.
+ *
+ * @returns The element value.
+ * @param pBigNum The big number.
+ * @param iElement The element index.
+ */
+DECLINLINE(RTBIGNUMELEMENT) rtBigNumGetElement(PCRTBIGNUM pBigNum, uint32_t iElement)
+{
+ if (iElement < pBigNum->cUsed)
+ return pBigNum->pauElements[iElement];
+ return 0;
+}
+
+
+/**
+ * Grows the pauElements array so it can fit at least @a cNewUsed entries.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ * @param cNewUsed The new cUsed value.
+ * @param cMinElements The minimum number of elements.
+ */
+static int rtBigNumGrow(PRTBIGNUM pBigNum, uint32_t cNewUsed, uint32_t cMinElements)
+{
+ Assert(cMinElements >= cNewUsed);
+ uint32_t const cbOld = pBigNum->cAllocated * RTBIGNUM_ELEMENT_SIZE;
+ uint32_t const cNew = RT_ALIGN_32(cMinElements, RTBIGNUM_ALIGNMENT);
+ uint32_t const cbNew = cNew * RTBIGNUM_ELEMENT_SIZE;
+ Assert(cbNew > cbOld);
+ if (cbNew <= RTBIGNUM_MAX_SIZE && cbNew > cbOld)
+ {
+ void *pvNew;
+ if (pBigNum->fSensitive)
+ pvNew = RTMemSaferReallocZ(cbOld, pBigNum->pauElements, cbNew);
+ else
+ pvNew = RTMemRealloc(pBigNum->pauElements, cbNew);
+ if (RT_LIKELY(pvNew))
+ {
+ if (cbNew > cbOld)
+ RT_BZERO((char *)pvNew + cbOld, cbNew - cbOld);
+ if (pBigNum->cUsed > cNewUsed)
+ RT_BZERO((RTBIGNUMELEMENT *)pvNew + cNewUsed, (pBigNum->cUsed - cNewUsed) * RTBIGNUM_ELEMENT_SIZE);
+
+ pBigNum->pauElements = (RTBIGNUMELEMENT *)pvNew;
+ pBigNum->cUsed = cNewUsed;
+ pBigNum->cAllocated = cNew;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+ }
+ return VERR_OUT_OF_RANGE;
+}
+
+
+/**
+ * Changes the cUsed member, growing the pauElements array if necessary.
+ *
+ * Any elements added to the array will be initialized to zero.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ * @param cNewUsed The new cUsed value.
+ */
+DECLINLINE(int) rtBigNumSetUsed(PRTBIGNUM pBigNum, uint32_t cNewUsed)
+{
+ if (pBigNum->cAllocated >= cNewUsed)
+ {
+ if (pBigNum->cUsed > cNewUsed)
+ RT_BZERO(&pBigNum->pauElements[cNewUsed], (pBigNum->cUsed - cNewUsed) * RTBIGNUM_ELEMENT_SIZE);
+#ifdef RT_STRICT
+ else if (pBigNum->cUsed != cNewUsed)
+ Assert(ASMMemIsZero(&pBigNum->pauElements[pBigNum->cUsed], (cNewUsed - pBigNum->cUsed) * RTBIGNUM_ELEMENT_SIZE));
+#endif
+ pBigNum->cUsed = cNewUsed;
+ return VINF_SUCCESS;
+ }
+ return rtBigNumGrow(pBigNum, cNewUsed, cNewUsed);
+}
+
+
+/**
+ * Extended version of rtBigNumSetUsed that also allow specifying the number of
+ * zero elements required.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ * @param cNewUsed The new cUsed value.
+ * @param cMinElements The minimum number of elements allocated. The
+ * difference between @a cNewUsed and @a cMinElements
+ * is initialized to zero because all free elements are
+ * zero.
+ */
+DECLINLINE(int) rtBigNumSetUsedEx(PRTBIGNUM pBigNum, uint32_t cNewUsed, uint32_t cMinElements)
+{
+ if (pBigNum->cAllocated >= cMinElements)
+ {
+ if (pBigNum->cUsed > cNewUsed)
+ RT_BZERO(&pBigNum->pauElements[cNewUsed], (pBigNum->cUsed - cNewUsed) * RTBIGNUM_ELEMENT_SIZE);
+#ifdef RT_STRICT
+ else if (pBigNum->cUsed != cNewUsed)
+ Assert(ASMMemIsZero(&pBigNum->pauElements[pBigNum->cUsed], (cNewUsed - pBigNum->cUsed) * RTBIGNUM_ELEMENT_SIZE));
+#endif
+ pBigNum->cUsed = cNewUsed;
+ return VINF_SUCCESS;
+ }
+ return rtBigNumGrow(pBigNum, cNewUsed, cMinElements);
+}
+
+
+/**
+ * For ensuring zero padding of pauElements for sub/add with carry assembly
+ * operations.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ * @param cElements The number of elements that must be in the elements
+ * array array, where those after pBigNum->cUsed must
+ * be zero.
+ */
+DECLINLINE(int) rtBigNumEnsureExtraZeroElements(PRTBIGNUM pBigNum, uint32_t cElements)
+{
+ if (pBigNum->cAllocated >= cElements)
+ {
+ Assert( pBigNum->cAllocated == pBigNum->cUsed
+ || ASMMemIsZero(&pBigNum->pauElements[pBigNum->cUsed],
+ (pBigNum->cAllocated - pBigNum->cUsed) * RTBIGNUM_ELEMENT_SIZE));
+ return VINF_SUCCESS;
+ }
+ return rtBigNumGrow(pBigNum, pBigNum->cUsed, cElements);
+}
+
+
+/**
+ * The slow part of rtBigNumEnsureElementPresent where we need to do actual zero
+ * extending.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ * @param iElement The element we wish to access.
+ */
+static int rtBigNumEnsureElementPresentSlow(PRTBIGNUM pBigNum, uint32_t iElement)
+{
+ uint32_t const cOldUsed = pBigNum->cUsed;
+ int rc = rtBigNumSetUsed(pBigNum, iElement + 1);
+ if (RT_SUCCESS(rc))
+ {
+ RT_BZERO(&pBigNum->pauElements[cOldUsed], (iElement + 1 - cOldUsed) * RTBIGNUM_ELEMENT_SIZE);
+ return VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+/**
+ * Zero extends the element array to make sure a the specified element index is
+ * accessible.
+ *
+ * This is typically used with bit operations and self modifying methods. Any
+ * new elements added will be initialized to zero. The caller is responsible
+ * for there not being any trailing zero elements.
+ *
+ * The number must be unscrambled.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ * @param iElement The element we wish to access.
+ */
+DECLINLINE(int) rtBigNumEnsureElementPresent(PRTBIGNUM pBigNum, uint32_t iElement)
+{
+ if (iElement < pBigNum->cUsed)
+ return VINF_SUCCESS;
+ return rtBigNumEnsureElementPresentSlow(pBigNum, iElement);
+}
+
+
+/**
+ * Strips zero elements from the magnitude value.
+ *
+ * @param pBigNum The big number to strip.
+ */
+static void rtBigNumStripTrailingZeros(PRTBIGNUM pBigNum)
+{
+ uint32_t i = pBigNum->cUsed;
+ while (i > 0 && pBigNum->pauElements[i - 1] == 0)
+ i--;
+ pBigNum->cUsed = i;
+}
+
+
+/**
+ * Initialize the big number to zero.
+ *
+ * @returns @a pBigNum
+ * @param pBigNum The big number.
+ * @param fFlags The flags.
+ * @internal
+ */
+DECLINLINE(PRTBIGNUM) rtBigNumInitZeroInternal(PRTBIGNUM pBigNum, uint32_t fFlags)
+{
+ RT_ZERO(*pBigNum);
+ pBigNum->fSensitive = RT_BOOL(fFlags & RTBIGNUMINIT_F_SENSITIVE);
+ return pBigNum;
+}
+
+
+/**
+ * Initialize the big number to zero from a template variable.
+ *
+ * @returns @a pBigNum
+ * @param pBigNum The big number.
+ * @param pTemplate The template big number.
+ * @internal
+ */
+DECLINLINE(PRTBIGNUM) rtBigNumInitZeroTemplate(PRTBIGNUM pBigNum, PCRTBIGNUM pTemplate)
+{
+ RT_ZERO(*pBigNum);
+ pBigNum->fSensitive = pTemplate->fSensitive;
+ return pBigNum;
+}
+
+
+RTDECL(int) RTBigNumInit(PRTBIGNUM pBigNum, uint32_t fFlags, void const *pvRaw, size_t cbRaw)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pBigNum, VERR_INVALID_POINTER);
+ AssertReturn(RT_BOOL(fFlags & RTBIGNUMINIT_F_ENDIAN_BIG) ^ RT_BOOL(fFlags & RTBIGNUMINIT_F_ENDIAN_LITTLE),
+ VERR_INVALID_PARAMETER);
+ AssertReturn(RT_BOOL(fFlags & RTBIGNUMINIT_F_UNSIGNED) ^ RT_BOOL(fFlags & RTBIGNUMINIT_F_SIGNED), VERR_INVALID_PARAMETER);
+ if (cbRaw)
+ AssertPtrReturn(pvRaw, VERR_INVALID_POINTER);
+
+ /*
+ * Initalize the big number to zero.
+ */
+ rtBigNumInitZeroInternal(pBigNum, fFlags);
+
+ /*
+ * Strip the input and figure the sign flag.
+ */
+ uint8_t const *pb = (uint8_t const *)pvRaw;
+ if (cbRaw)
+ {
+ if (fFlags & RTBIGNUMINIT_F_ENDIAN_LITTLE)
+ {
+ if (fFlags & RTBIGNUMINIT_F_UNSIGNED)
+ {
+ while (cbRaw > 0 && pb[cbRaw - 1] == 0)
+ cbRaw--;
+ }
+ else
+ {
+ if (pb[cbRaw - 1] >> 7)
+ {
+ pBigNum->fNegative = 1;
+ while (cbRaw > 1 && pb[cbRaw - 1] == 0xff)
+ cbRaw--;
+ }
+ else
+ while (cbRaw > 0 && pb[cbRaw - 1] == 0)
+ cbRaw--;
+ }
+ }
+ else
+ {
+ if (fFlags & RTBIGNUMINIT_F_UNSIGNED)
+ {
+ while (cbRaw > 0 && *pb == 0)
+ pb++, cbRaw--;
+ }
+ else
+ {
+ if (*pb >> 7)
+ {
+ pBigNum->fNegative = 1;
+ while (cbRaw > 1 && *pb == 0xff)
+ pb++, cbRaw--;
+ }
+ else
+ while (cbRaw > 0 && *pb == 0)
+ pb++, cbRaw--;
+ }
+ }
+ }
+
+ /*
+ * Allocate memory for the elements.
+ */
+ size_t cbAligned = RT_ALIGN_Z(cbRaw, RTBIGNUM_ELEMENT_SIZE);
+ if (RT_UNLIKELY(cbAligned >= RTBIGNUM_MAX_SIZE))
+ return VERR_OUT_OF_RANGE;
+ pBigNum->cUsed = (uint32_t)cbAligned / RTBIGNUM_ELEMENT_SIZE;
+ if (pBigNum->cUsed)
+ {
+ pBigNum->cAllocated = RT_ALIGN_32(pBigNum->cUsed, RTBIGNUM_ALIGNMENT);
+ if (pBigNum->fSensitive)
+ pBigNum->pauElements = (RTBIGNUMELEMENT *)RTMemSaferAllocZ(pBigNum->cAllocated * RTBIGNUM_ELEMENT_SIZE);
+ else
+ pBigNum->pauElements = (RTBIGNUMELEMENT *)RTMemAlloc(pBigNum->cAllocated * RTBIGNUM_ELEMENT_SIZE);
+ if (RT_UNLIKELY(!pBigNum->pauElements))
+ return VERR_NO_MEMORY;
+
+ /*
+ * Initialize the array.
+ */
+ uint32_t i = 0;
+ if (fFlags & RTBIGNUMINIT_F_ENDIAN_LITTLE)
+ {
+ while (cbRaw >= RTBIGNUM_ELEMENT_SIZE)
+ {
+#if RTBIGNUM_ELEMENT_SIZE == 8
+ pBigNum->pauElements[i] = RT_MAKE_U64_FROM_U8(pb[0], pb[1], pb[2], pb[3], pb[4], pb[5], pb[6], pb[7]);
+#elif RTBIGNUM_ELEMENT_SIZE == 4
+ pBigNum->pauElements[i] = RT_MAKE_U32_FROM_U8(pb[0], pb[1], pb[2], pb[3]);
+#else
+# error "Bad RTBIGNUM_ELEMENT_SIZE value"
+#endif
+ i++;
+ pb += RTBIGNUM_ELEMENT_SIZE;
+ cbRaw -= RTBIGNUM_ELEMENT_SIZE;
+ }
+
+ if (cbRaw > 0)
+ {
+ RTBIGNUMELEMENT uLast = pBigNum->fNegative ? ~(RTBIGNUMELEMENT)0 : 0;
+ switch (cbRaw)
+ {
+ default: AssertFailed();
+#if RTBIGNUM_ELEMENT_SIZE == 8
+ RT_FALL_THRU();
+ case 7: uLast = (uLast << 8) | pb[6]; RT_FALL_THRU();
+ case 6: uLast = (uLast << 8) | pb[5]; RT_FALL_THRU();
+ case 5: uLast = (uLast << 8) | pb[4]; RT_FALL_THRU();
+ case 4: uLast = (uLast << 8) | pb[3];
+#endif
+ RT_FALL_THRU();
+ case 3: uLast = (uLast << 8) | pb[2]; RT_FALL_THRU();
+ case 2: uLast = (uLast << 8) | pb[1]; RT_FALL_THRU();
+ case 1: uLast = (uLast << 8) | pb[0];
+ }
+ pBigNum->pauElements[i] = uLast;
+ }
+ }
+ else
+ {
+ pb += cbRaw;
+ while (cbRaw >= RTBIGNUM_ELEMENT_SIZE)
+ {
+ pb -= RTBIGNUM_ELEMENT_SIZE;
+#if RTBIGNUM_ELEMENT_SIZE == 8
+ pBigNum->pauElements[i] = RT_MAKE_U64_FROM_U8(pb[7], pb[6], pb[5], pb[4], pb[3], pb[2], pb[1], pb[0]);
+#elif RTBIGNUM_ELEMENT_SIZE == 4
+ pBigNum->pauElements[i] = RT_MAKE_U32_FROM_U8(pb[3], pb[2], pb[1], pb[0]);
+#else
+# error "Bad RTBIGNUM_ELEMENT_SIZE value"
+#endif
+ i++;
+ cbRaw -= RTBIGNUM_ELEMENT_SIZE;
+ }
+
+ if (cbRaw > 0)
+ {
+ RTBIGNUMELEMENT uLast = pBigNum->fNegative ? ~(RTBIGNUMELEMENT)0 : 0;
+ pb -= cbRaw;
+ switch (cbRaw)
+ {
+ default: AssertFailed();
+#if RTBIGNUM_ELEMENT_SIZE == 8
+ RT_FALL_THRU();
+ case 7: uLast = (uLast << 8) | *pb++; RT_FALL_THRU();
+ case 6: uLast = (uLast << 8) | *pb++; RT_FALL_THRU();
+ case 5: uLast = (uLast << 8) | *pb++; RT_FALL_THRU();
+ case 4: uLast = (uLast << 8) | *pb++;
+#endif
+ RT_FALL_THRU();
+ case 3: uLast = (uLast << 8) | *pb++; RT_FALL_THRU();
+ case 2: uLast = (uLast << 8) | *pb++; RT_FALL_THRU();
+ case 1: uLast = (uLast << 8) | *pb++;
+ }
+ pBigNum->pauElements[i] = uLast;
+ }
+ }
+
+ /*
+ * If negative, negate it so we get a positive magnitude value in pauElements.
+ */
+ if (pBigNum->fNegative)
+ {
+ pBigNum->pauElements[0] = 0U - pBigNum->pauElements[0];
+ for (i = 1; i < pBigNum->cUsed; i++)
+ pBigNum->pauElements[i] = 0U - pBigNum->pauElements[i] - 1U;
+ }
+
+ /*
+ * Clear unused elements.
+ */
+ if (pBigNum->cUsed != pBigNum->cAllocated)
+ {
+ RTBIGNUMELEMENT *puUnused = &pBigNum->pauElements[pBigNum->cUsed];
+ AssertCompile(RTBIGNUM_ALIGNMENT <= 4);
+ switch (pBigNum->cAllocated - pBigNum->cUsed)
+ {
+ default: AssertFailed(); RT_FALL_THRU();
+ case 3: *puUnused++ = 0; RT_FALL_THRU();
+ case 2: *puUnused++ = 0; RT_FALL_THRU();
+ case 1: *puUnused++ = 0;
+ }
+ }
+ RTBIGNUM_ASSERT_VALID(pBigNum);
+ }
+
+ rtBigNumScramble(pBigNum);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTBigNumInitZero(PRTBIGNUM pBigNum, uint32_t fFlags)
+{
+ AssertReturn(!(fFlags & ~RTBIGNUMINIT_F_SENSITIVE), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pBigNum, VERR_INVALID_POINTER);
+
+ rtBigNumInitZeroInternal(pBigNum, fFlags);
+ rtBigNumScramble(pBigNum);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Internal clone function that assumes the caller takes care of scrambling.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The target number.
+ * @param pSrc The source number.
+ */
+static int rtBigNumCloneInternal(PRTBIGNUM pBigNum, PCRTBIGNUM pSrc)
+{
+ Assert(!pSrc->fCurScrambled);
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Copy over the data.
+ */
+ RT_ZERO(*pBigNum);
+ pBigNum->fNegative = pSrc->fNegative;
+ pBigNum->fSensitive = pSrc->fSensitive;
+ pBigNum->cUsed = pSrc->cUsed;
+ if (pSrc->cUsed)
+ {
+ /* Duplicate the element array. */
+ pBigNum->cAllocated = RT_ALIGN_32(pBigNum->cUsed, RTBIGNUM_ALIGNMENT);
+ if (pBigNum->fSensitive)
+ pBigNum->pauElements = (RTBIGNUMELEMENT *)RTMemSaferAllocZ(pBigNum->cAllocated * RTBIGNUM_ELEMENT_SIZE);
+ else
+ pBigNum->pauElements = (RTBIGNUMELEMENT *)RTMemAlloc(pBigNum->cAllocated * RTBIGNUM_ELEMENT_SIZE);
+ if (RT_LIKELY(pBigNum->pauElements))
+ {
+ memcpy(pBigNum->pauElements, pSrc->pauElements, pBigNum->cUsed * RTBIGNUM_ELEMENT_SIZE);
+ if (pBigNum->cUsed != pBigNum->cAllocated)
+ RT_BZERO(&pBigNum->pauElements[pBigNum->cUsed], (pBigNum->cAllocated - pBigNum->cUsed) * RTBIGNUM_ELEMENT_SIZE);
+ }
+ else
+ {
+ RT_ZERO(*pBigNum);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumClone(PRTBIGNUM pBigNum, PCRTBIGNUM pSrc)
+{
+ int rc = rtBigNumUnscramble((PRTBIGNUM)pSrc);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pSrc);
+ rc = rtBigNumCloneInternal(pBigNum, pSrc);
+ if (RT_SUCCESS(rc))
+ rtBigNumScramble(pBigNum);
+ rtBigNumScramble((PRTBIGNUM)pSrc);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumDestroy(PRTBIGNUM pBigNum)
+{
+ if (pBigNum)
+ {
+ if (pBigNum->pauElements)
+ {
+ Assert(pBigNum->cAllocated > 0);
+ if (!pBigNum->fSensitive)
+ RTMemFree(pBigNum->pauElements);
+ else
+ {
+ RTMemSaferFree(pBigNum->pauElements, pBigNum->cAllocated * RTBIGNUM_ELEMENT_SIZE);
+ RT_ZERO(*pBigNum);
+ }
+ pBigNum->pauElements = NULL;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTBigNumAssign(PRTBIGNUM pDst, PCRTBIGNUM pSrc)
+{
+ AssertReturn(pDst->fSensitive >= pSrc->fSensitive, VERR_BIGNUM_SENSITIVE_INPUT);
+ int rc = rtBigNumUnscramble(pDst);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pDst);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pSrc);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pSrc);
+ if ( pDst->fSensitive == pSrc->fSensitive
+ || pDst->fSensitive)
+ {
+ if (pDst->cAllocated >= pSrc->cUsed)
+ {
+ if (pDst->cUsed > pSrc->cUsed)
+ RT_BZERO(&pDst->pauElements[pSrc->cUsed], (pDst->cUsed - pSrc->cUsed) * RTBIGNUM_ELEMENT_SIZE);
+ pDst->cUsed = pSrc->cUsed;
+ pDst->fNegative = pSrc->fNegative;
+ memcpy(pDst->pauElements, pSrc->pauElements, pSrc->cUsed * RTBIGNUM_ELEMENT_SIZE);
+ }
+ else
+ {
+ rc = rtBigNumGrow(pDst, pSrc->cUsed, pSrc->cUsed);
+ if (RT_SUCCESS(rc))
+ {
+ pDst->fNegative = pSrc->fNegative;
+ memcpy(pDst->pauElements, pSrc->pauElements, pSrc->cUsed * RTBIGNUM_ELEMENT_SIZE);
+ }
+ }
+ }
+ else
+ rc = VERR_BIGNUM_SENSITIVE_INPUT;
+ rtBigNumScramble((PRTBIGNUM)pSrc);
+ }
+ rtBigNumScramble(pDst);
+ }
+ return rc;
+}
+
+
+/**
+ * Same as RTBigNumBitWidth, except that it ignore the signed bit.
+ *
+ * The number must be unscrambled.
+ *
+ * @returns The effective width of the magnitude, in bits. Returns 0 if the
+ * value is zero.
+ * @param pBigNum The bit number.
+ */
+static uint32_t rtBigNumMagnitudeBitWidth(PCRTBIGNUM pBigNum)
+{
+ uint32_t idxLast = pBigNum->cUsed;
+ if (idxLast)
+ {
+ idxLast--;
+ RTBIGNUMELEMENT uLast = pBigNum->pauElements[idxLast]; Assert(uLast);
+ return rtBigNumElementBitCount(uLast) + idxLast * RTBIGNUM_ELEMENT_BITS;
+ }
+ return 0;
+}
+
+
+RTDECL(uint32_t) RTBigNumBitWidth(PCRTBIGNUM pBigNum)
+{
+ uint32_t idxLast = pBigNum->cUsed;
+ if (idxLast)
+ {
+ idxLast--;
+ rtBigNumUnscramble((PRTBIGNUM)pBigNum);
+ RTBIGNUMELEMENT uLast = pBigNum->pauElements[idxLast]; Assert(uLast);
+ rtBigNumScramble((PRTBIGNUM)pBigNum);
+ return rtBigNumElementBitCount(uLast) + idxLast * RTBIGNUM_ELEMENT_BITS + pBigNum->fNegative;
+ }
+ return 0;
+}
+
+
+RTDECL(uint32_t) RTBigNumByteWidth(PCRTBIGNUM pBigNum)
+{
+ uint32_t cBits = RTBigNumBitWidth(pBigNum);
+ return (cBits + 7) / 8;
+}
+
+
+RTDECL(int) RTBigNumToBytesBigEndian(PCRTBIGNUM pBigNum, void *pvBuf, size_t cbWanted)
+{
+ AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbWanted > 0, VERR_INVALID_PARAMETER);
+
+ int rc = rtBigNumUnscramble((PRTBIGNUM)pBigNum);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pBigNum);
+ rc = VINF_SUCCESS;
+ if (pBigNum->cUsed != 0)
+ {
+ uint8_t *pbDst = (uint8_t *)pvBuf;
+ pbDst += cbWanted - 1;
+ for (uint32_t i = 0; i < pBigNum->cUsed; i++)
+ {
+ RTBIGNUMELEMENT uElement = pBigNum->pauElements[i];
+ if (pBigNum->fNegative)
+ uElement = (RTBIGNUMELEMENT)0 - uElement - (i > 0);
+ if (cbWanted >= sizeof(uElement))
+ {
+ *pbDst-- = (uint8_t)uElement;
+ uElement >>= 8;
+ *pbDst-- = (uint8_t)uElement;
+ uElement >>= 8;
+ *pbDst-- = (uint8_t)uElement;
+ uElement >>= 8;
+ *pbDst-- = (uint8_t)uElement;
+#if RTBIGNUM_ELEMENT_SIZE == 8
+ uElement >>= 8;
+ *pbDst-- = (uint8_t)uElement;
+ uElement >>= 8;
+ *pbDst-- = (uint8_t)uElement;
+ uElement >>= 8;
+ *pbDst-- = (uint8_t)uElement;
+ uElement >>= 8;
+ *pbDst-- = (uint8_t)uElement;
+#elif RTBIGNUM_ELEMENT_SIZE != 4
+# error "Bad RTBIGNUM_ELEMENT_SIZE value"
+#endif
+ cbWanted -= sizeof(uElement);
+ }
+ else
+ {
+
+ uint32_t cBitsLeft = RTBIGNUM_ELEMENT_BITS;
+ while (cbWanted > 0)
+ {
+ *pbDst-- = (uint8_t)uElement;
+ uElement >>= 8;
+ cBitsLeft -= 8;
+ cbWanted--;
+ }
+ Assert(cBitsLeft > 0); Assert(cBitsLeft < RTBIGNUM_ELEMENT_BITS);
+ if ( i + 1 < pBigNum->cUsed
+ || ( !pBigNum->fNegative
+ ? uElement != 0
+ : uElement != ((RTBIGNUMELEMENT)1 << cBitsLeft) - 1U ) )
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ }
+
+ /* Sign extend the number to the desired output size. */
+ if (cbWanted > 0)
+ memset(pbDst - cbWanted, pBigNum->fNegative ? 0 : 0xff, cbWanted);
+ }
+ else
+ RT_BZERO(pvBuf, cbWanted);
+ rtBigNumScramble((PRTBIGNUM)pBigNum);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumCompare(PRTBIGNUM pLeft, PRTBIGNUM pRight)
+{
+ int rc = rtBigNumUnscramble(pLeft);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pLeft);
+ rc = rtBigNumUnscramble(pRight);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pRight);
+ if (pLeft->fNegative == pRight->fNegative)
+ {
+ if (pLeft->cUsed == pRight->cUsed)
+ {
+ rc = 0;
+ uint32_t i = pLeft->cUsed;
+ while (i-- > 0)
+ if (pLeft->pauElements[i] != pRight->pauElements[i])
+ {
+ rc = pLeft->pauElements[i] < pRight->pauElements[i] ? -1 : 1;
+ break;
+ }
+ if (pLeft->fNegative)
+ rc = -rc;
+ }
+ else
+ rc = !pLeft->fNegative
+ ? pLeft->cUsed < pRight->cUsed ? -1 : 1
+ : pLeft->cUsed < pRight->cUsed ? 1 : -1;
+ }
+ else
+ rc = pLeft->fNegative ? -1 : 1;
+
+ rtBigNumScramble(pRight);
+ }
+ rtBigNumScramble(pLeft);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumCompareWithU64(PRTBIGNUM pLeft, uint64_t uRight)
+{
+ int rc = rtBigNumUnscramble(pLeft);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pLeft);
+ if (!pLeft->fNegative)
+ {
+ if (pLeft->cUsed * RTBIGNUM_ELEMENT_SIZE <= sizeof(uRight))
+ {
+ if (pLeft->cUsed == 0)
+ rc = uRight == 0 ? 0 : -1;
+ else
+ {
+#if RTBIGNUM_ELEMENT_SIZE == 8
+ uint64_t uLeft = rtBigNumGetElement(pLeft, 0);
+ if (uLeft < uRight)
+ rc = -1;
+ else
+ rc = uLeft == uRight ? 0 : 1;
+#elif RTBIGNUM_ELEMENT_SIZE == 4
+ uint32_t uSubLeft = rtBigNumGetElement(pLeft, 1);
+ uint32_t uSubRight = uRight >> 32;
+ if (uSubLeft == uSubRight)
+ {
+ uSubLeft = rtBigNumGetElement(pLeft, 0);
+ uSubRight = (uint32_t)uRight;
+ }
+ if (uSubLeft < uSubRight)
+ rc = -1;
+ else
+ rc = uSubLeft == uSubRight ? 0 : 1;
+#else
+# error "Bad RTBIGNUM_ELEMENT_SIZE value"
+#endif
+ }
+ }
+ else
+ rc = 1;
+ }
+ else
+ rc = -1;
+ rtBigNumScramble(pLeft);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumCompareWithS64(PRTBIGNUM pLeft, int64_t iRight)
+{
+ int rc = rtBigNumUnscramble(pLeft);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pLeft);
+ if (pLeft->fNegative == (unsigned)(iRight < 0)) /* (unsigned cast is for MSC weirdness) */
+ {
+ AssertCompile(RTBIGNUM_ELEMENT_SIZE <= sizeof(iRight));
+ if (pLeft->cUsed * RTBIGNUM_ELEMENT_SIZE <= sizeof(iRight))
+ {
+ uint64_t uRightMagn = !pLeft->fNegative ? (uint64_t)iRight : (uint64_t)-iRight;
+#if RTBIGNUM_ELEMENT_SIZE == 8
+ uint64_t uLeft = rtBigNumGetElement(pLeft, 0);
+ if (uLeft < uRightMagn)
+ rc = -1;
+ else
+ rc = uLeft == (uint64_t)uRightMagn ? 0 : 1;
+#elif RTBIGNUM_ELEMENT_SIZE == 4
+ uint32_t uSubLeft = rtBigNumGetElement(pLeft, 1);
+ uint32_t uSubRight = uRightMagn >> 32;
+ if (uSubLeft == uSubRight)
+ {
+ uSubLeft = rtBigNumGetElement(pLeft, 0);
+ uSubRight = (uint32_t)uRightMagn;
+ }
+ if (uSubLeft < uSubRight)
+ rc = -1;
+ else
+ rc = uSubLeft == uSubRight ? 0 : 1;
+#else
+# error "Bad RTBIGNUM_ELEMENT_SIZE value"
+#endif
+ if (pLeft->fNegative)
+ rc = -rc;
+ }
+ else
+ rc = pLeft->fNegative ? -1 : 1;
+ }
+ else
+ rc = pLeft->fNegative ? -1 : 1;
+ rtBigNumScramble(pLeft);
+ }
+ return rc;
+}
+
+
+/**
+ * Compares the magnitude values of two big numbers.
+ *
+ * @retval -1 if pLeft is smaller than pRight.
+ * @retval 0 if pLeft is equal to pRight.
+ * @retval 1 if pLeft is larger than pRight.
+ * @param pLeft The left side number.
+ * @param pRight The right side number.
+ */
+static int rtBigNumMagnitudeCompare(PCRTBIGNUM pLeft, PCRTBIGNUM pRight)
+{
+ Assert(!pLeft->fCurScrambled); Assert(!pRight->fCurScrambled);
+ int rc;
+ uint32_t i = pLeft->cUsed;
+ if (i == pRight->cUsed)
+ {
+ rc = 0;
+ while (i-- > 0)
+ if (pLeft->pauElements[i] != pRight->pauElements[i])
+ {
+ rc = pLeft->pauElements[i] < pRight->pauElements[i] ? -1 : 1;
+ break;
+ }
+ }
+ else
+ rc = i < pRight->cUsed ? -1 : 1;
+ return rc;
+}
+
+
+/**
+ * Copies the magnitude of on number (@a pSrc) to another (@a pBigNum).
+ *
+ * The variables must be unscrambled. The sign flag is not considered nor
+ * touched.
+ *
+ * @returns IPRT status code.
+ * @param pDst The destination number.
+ * @param pSrc The source number.
+ */
+DECLINLINE(int) rtBigNumMagnitudeCopy(PRTBIGNUM pDst, PCRTBIGNUM pSrc)
+{
+ int rc = rtBigNumSetUsed(pDst, pSrc->cUsed);
+ if (RT_SUCCESS(rc))
+ memcpy(pDst->pauElements, pSrc->pauElements, pSrc->cUsed * RTBIGNUM_ELEMENT_SIZE);
+ return rc;
+}
+
+
+
+/**
+ * Adds two magnitudes and stores them into a third.
+ *
+ * All variables must be unscrambled. The sign flag is not considered nor
+ * touched.
+ *
+ * @returns IPRT status code.
+ * @param pResult The resultant.
+ * @param pAugend To whom it shall be addede.
+ * @param pAddend The nombre to addede.
+ */
+static int rtBigNumMagnitudeAdd(PRTBIGNUM pResult, PCRTBIGNUM pAugend, PCRTBIGNUM pAddend)
+{
+ Assert(!pResult->fCurScrambled); Assert(!pAugend->fCurScrambled); Assert(!pAddend->fCurScrambled);
+ Assert(pResult != pAugend); Assert(pResult != pAddend);
+
+ uint32_t cElements = RT_MAX(pAugend->cUsed, pAddend->cUsed);
+ int rc = rtBigNumSetUsed(pResult, cElements);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * The primitive way, requires at least two additions for each entry
+ * without machine code help.
+ */
+ RTBIGNUMELEMENT fCarry = 0;
+ for (uint32_t i = 0; i < cElements; i++)
+ pResult->pauElements[i] = rtBigNumElementAddWithCarry(rtBigNumGetElement(pAugend, i),
+ rtBigNumGetElement(pAddend, i),
+ &fCarry);
+ if (fCarry)
+ {
+ rc = rtBigNumSetUsed(pResult, cElements + 1);
+ if (RT_SUCCESS(rc))
+ pResult->pauElements[cElements++] = 1;
+ }
+ Assert(pResult->cUsed == cElements || RT_FAILURE_NP(rc));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Substracts a smaller (or equal) magnitude from another one and stores it into
+ * a third.
+ *
+ * All variables must be unscrambled. The sign flag is not considered nor
+ * touched. For this reason, the @a pMinuend must be larger or equal to @a
+ * pSubtrahend.
+ *
+ * @returns IPRT status code.
+ * @param pResult There to store the result.
+ * @param pMinuend What to subtract from.
+ * @param pSubtrahend What to subtract.
+ */
+static int rtBigNumMagnitudeSub(PRTBIGNUM pResult, PCRTBIGNUM pMinuend, PCRTBIGNUM pSubtrahend)
+{
+ Assert(!pResult->fCurScrambled); Assert(!pMinuend->fCurScrambled); Assert(!pSubtrahend->fCurScrambled);
+ Assert(pResult != pMinuend); Assert(pResult != pSubtrahend);
+ Assert(pMinuend->cUsed >= pSubtrahend->cUsed);
+
+ int rc;
+ if (pSubtrahend->cUsed)
+ {
+ /*
+ * Resize the result. In the assembly case, ensure that all three arrays
+ * has the same number of used entries, possibly with an extra zero
+ * element on 64-bit systems.
+ */
+ rc = rtBigNumSetUsedEx(pResult, pMinuend->cUsed, RTBIGNUM_ZERO_ALIGN(pMinuend->cUsed));
+#ifdef IPRT_BIGINT_WITH_ASM
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumEnsureExtraZeroElements((PRTBIGNUM)pMinuend, RTBIGNUM_ZERO_ALIGN(pMinuend->cUsed));
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumEnsureExtraZeroElements((PRTBIGNUM)pSubtrahend, RTBIGNUM_ZERO_ALIGN(pMinuend->cUsed));
+#endif
+ if (RT_SUCCESS(rc))
+ {
+#ifdef IPRT_BIGINT_WITH_ASM
+ /*
+ * Call assembly to do the work.
+ */
+ rtBigNumMagnitudeSubAssemblyWorker(pResult->pauElements, pMinuend->pauElements,
+ pSubtrahend->pauElements, pMinuend->cUsed);
+# ifdef RT_STRICT
+ RTBIGNUMELEMENT fBorrow = 0;
+ for (uint32_t i = 0; i < pMinuend->cUsed; i++)
+ {
+ RTBIGNUMELEMENT uCorrect = rtBigNumElementSubWithBorrow(pMinuend->pauElements[i], rtBigNumGetElement(pSubtrahend, i), &fBorrow);
+ AssertMsg(pResult->pauElements[i] == uCorrect, ("[%u]=%#x, expected %#x\n", i, pResult->pauElements[i], uCorrect));
+ }
+# endif
+#else
+ /*
+ * The primitive C way.
+ */
+ RTBIGNUMELEMENT fBorrow = 0;
+ for (uint32_t i = 0; i < pMinuend->cUsed; i++)
+ pResult->pauElements[i] = rtBigNumElementSubWithBorrow(pMinuend->pauElements[i],
+ rtBigNumGetElement(pSubtrahend, i),
+ &fBorrow);
+ Assert(fBorrow == 0);
+#endif
+
+ /*
+ * Trim the result.
+ */
+ rtBigNumStripTrailingZeros(pResult);
+ }
+ }
+ /*
+ * Special case: Subtrahend is zero.
+ */
+ else
+ rc = rtBigNumMagnitudeCopy(pResult, pMinuend);
+
+ return rc;
+}
+
+
+/**
+ * Substracts a smaller (or equal) magnitude from another one and stores the
+ * result into the first.
+ *
+ * All variables must be unscrambled. The sign flag is not considered nor
+ * touched. For this reason, the @a pMinuendResult must be larger or equal to
+ * @a pSubtrahend.
+ *
+ * @returns IPRT status code (memory alloc error).
+ * @param pMinuendResult What to subtract from and return as result.
+ * @param pSubtrahend What to subtract.
+ */
+static int rtBigNumMagnitudeSubThis(PRTBIGNUM pMinuendResult, PCRTBIGNUM pSubtrahend)
+{
+ Assert(!pMinuendResult->fCurScrambled); Assert(!pSubtrahend->fCurScrambled);
+ Assert(pMinuendResult != pSubtrahend);
+ Assert(pMinuendResult->cUsed >= pSubtrahend->cUsed);
+
+#ifdef IPRT_BIGINT_WITH_ASM
+ /*
+ * Use the assembly worker. Requires same sized element arrays, so zero extend them.
+ */
+ int rc = rtBigNumEnsureExtraZeroElements(pMinuendResult, RTBIGNUM_ZERO_ALIGN(pMinuendResult->cUsed));
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumEnsureExtraZeroElements((PRTBIGNUM)pSubtrahend, RTBIGNUM_ZERO_ALIGN(pMinuendResult->cUsed));
+ if (RT_FAILURE(rc))
+ return rc;
+ rtBigNumMagnitudeSubThisAssemblyWorker(pMinuendResult->pauElements, pSubtrahend->pauElements, pMinuendResult->cUsed);
+#else
+ /*
+ * The primitive way, as usual.
+ */
+ RTBIGNUMELEMENT fBorrow = 0;
+ for (uint32_t i = 0; i < pMinuendResult->cUsed; i++)
+ pMinuendResult->pauElements[i] = rtBigNumElementSubWithBorrow(pMinuendResult->pauElements[i],
+ rtBigNumGetElement(pSubtrahend, i),
+ &fBorrow);
+ Assert(fBorrow == 0);
+#endif
+
+ /*
+ * Trim the result.
+ */
+ rtBigNumStripTrailingZeros(pMinuendResult);
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTBigNumAdd(PRTBIGNUM pResult, PCRTBIGNUM pAugend, PCRTBIGNUM pAddend)
+{
+ Assert(pResult != pAugend); Assert(pResult != pAddend);
+ AssertReturn(pResult->fSensitive >= (pAugend->fSensitive | pAddend->fSensitive), VERR_BIGNUM_SENSITIVE_INPUT);
+
+ int rc = rtBigNumUnscramble(pResult);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pResult);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pAugend);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pAugend);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pAddend);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pAddend);
+
+ /*
+ * Same sign: Add magnitude, keep sign.
+ * 1 + 1 = 2
+ * (-1) + (-1) = -2
+ */
+ if (pAugend->fNegative == pAddend->fNegative)
+ {
+ pResult->fNegative = pAugend->fNegative;
+ rc = rtBigNumMagnitudeAdd(pResult, pAugend, pAddend);
+ }
+ /*
+ * Different sign: Subtract smaller from larger, keep sign of larger.
+ * (-5) + 3 = -2
+ * 5 + (-3) = 2
+ * (-1) + 3 = 2
+ * 1 + (-3) = -2
+ */
+ else if (rtBigNumMagnitudeCompare(pAugend, pAddend) >= 0)
+ {
+ pResult->fNegative = pAugend->fNegative;
+ rc = rtBigNumMagnitudeSub(pResult, pAugend, pAddend);
+ if (!pResult->cUsed)
+ pResult->fNegative = 0;
+ }
+ else
+ {
+ pResult->fNegative = pAddend->fNegative;
+ rc = rtBigNumMagnitudeSub(pResult, pAddend, pAugend);
+ }
+ rtBigNumScramble((PRTBIGNUM)pAddend);
+ }
+ rtBigNumScramble((PRTBIGNUM)pAugend);
+ }
+ rtBigNumScramble(pResult);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumSubtract(PRTBIGNUM pResult, PCRTBIGNUM pMinuend, PCRTBIGNUM pSubtrahend)
+{
+ Assert(pResult != pMinuend); Assert(pResult != pSubtrahend);
+ AssertReturn(pResult->fSensitive >= (pMinuend->fSensitive | pSubtrahend->fSensitive), VERR_BIGNUM_SENSITIVE_INPUT);
+
+ int rc = rtBigNumUnscramble(pResult);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pResult);
+ if (pMinuend != pSubtrahend)
+ {
+ rc = rtBigNumUnscramble((PRTBIGNUM)pMinuend);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pMinuend);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pSubtrahend);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pSubtrahend);
+
+ /*
+ * Different sign: Add magnitude, keep sign of first.
+ * 1 - (-2) == 3
+ * -1 - 2 == -3
+ */
+ if (pMinuend->fNegative != pSubtrahend->fNegative)
+ {
+ pResult->fNegative = pMinuend->fNegative;
+ rc = rtBigNumMagnitudeAdd(pResult, pMinuend, pSubtrahend);
+ }
+ /*
+ * Same sign, minuend has greater or equal absolute value: Subtract, keep sign of first.
+ * 10 - 7 = 3
+ */
+ else if (rtBigNumMagnitudeCompare(pMinuend, pSubtrahend) >= 0)
+ {
+ pResult->fNegative = pMinuend->fNegative;
+ rc = rtBigNumMagnitudeSub(pResult, pMinuend, pSubtrahend);
+ }
+ /*
+ * Same sign, subtrahend is larger: Reverse and subtract, invert sign of first.
+ * 7 - 10 = -3
+ * -1 - (-3) = 2
+ */
+ else
+ {
+ pResult->fNegative = !pMinuend->fNegative;
+ rc = rtBigNumMagnitudeSub(pResult, pSubtrahend, pMinuend);
+ }
+ rtBigNumScramble((PRTBIGNUM)pSubtrahend);
+ }
+ rtBigNumScramble((PRTBIGNUM)pMinuend);
+ }
+ }
+ else
+ {
+ /* zero. */
+ pResult->fNegative = 0;
+ rtBigNumSetUsed(pResult, 0);
+ }
+ rtBigNumScramble(pResult);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumNegateThis(PRTBIGNUM pThis)
+{
+ pThis->fNegative = !pThis->fNegative;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTBigNumNegate(PRTBIGNUM pResult, PCRTBIGNUM pBigNum)
+{
+ int rc = RTBigNumAssign(pResult, pBigNum);
+ if (RT_SUCCESS(rc))
+ rc = RTBigNumNegateThis(pResult);
+ return rc;
+}
+
+
+/**
+ * Multiplies the magnitudes of two values, letting the caller care about the
+ * sign bit.
+ *
+ * @returns IPRT status code.
+ * @param pResult Where to store the result.
+ * @param pMultiplicand The first value.
+ * @param pMultiplier The second value.
+ */
+static int rtBigNumMagnitudeMultiply(PRTBIGNUM pResult, PCRTBIGNUM pMultiplicand, PCRTBIGNUM pMultiplier)
+{
+ Assert(pResult != pMultiplicand); Assert(pResult != pMultiplier);
+ Assert(!pResult->fCurScrambled); Assert(!pMultiplicand->fCurScrambled); Assert(!pMultiplier->fCurScrambled);
+
+ /*
+ * Multiplication involving zero is zero.
+ */
+ if (!pMultiplicand->cUsed || !pMultiplier->cUsed)
+ {
+ pResult->fNegative = 0;
+ rtBigNumSetUsed(pResult, 0);
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Allocate a result array that is the sum of the two factors, initialize
+ * it to zero.
+ */
+ uint32_t cMax = pMultiplicand->cUsed + pMultiplier->cUsed;
+ int rc = rtBigNumSetUsed(pResult, cMax);
+ if (RT_SUCCESS(rc))
+ {
+ RT_BZERO(pResult->pauElements, pResult->cUsed * RTBIGNUM_ELEMENT_SIZE);
+
+#ifdef IPRT_BIGINT_WITH_ASM
+ rtBigNumMagnitudeMultiplyAssemblyWorker(pResult->pauElements,
+ pMultiplier->pauElements, pMultiplier->cUsed,
+ pMultiplicand->pauElements, pMultiplicand->cUsed);
+#else
+ for (uint32_t i = 0; i < pMultiplier->cUsed; i++)
+ {
+ RTBIGNUMELEMENT uMultiplier = pMultiplier->pauElements[i];
+ for (uint32_t j = 0; j < pMultiplicand->cUsed; j++)
+ {
+ RTBIGNUMELEMENT uHi;
+ RTBIGNUMELEMENT uLo;
+#if RTBIGNUM_ELEMENT_SIZE == 4
+ uint64_t u64 = ASMMult2xU32RetU64(pMultiplicand->pauElements[j], uMultiplier);
+ uLo = (uint32_t)u64;
+ uHi = u64 >> 32;
+#elif RTBIGNUM_ELEMENT_SIZE == 8
+ uLo = ASMMult2xU64Ret2xU64(pMultiplicand->pauElements[j], uMultiplier, &uHi);
+#else
+# error "Invalid RTBIGNUM_ELEMENT_SIZE value"
+#endif
+ RTBIGNUMELEMENT fCarry = 0;
+ uint64_t k = i + j;
+ pResult->pauElements[k] = rtBigNumElementAddWithCarry(pResult->pauElements[k], uLo, &fCarry);
+ k++;
+ pResult->pauElements[k] = rtBigNumElementAddWithCarry(pResult->pauElements[k], uHi, &fCarry);
+ while (fCarry)
+ {
+ k++;
+ pResult->pauElements[k] = rtBigNumElementAddWithCarry(pResult->pauElements[k], 0, &fCarry);
+ }
+ Assert(k < cMax);
+ }
+ }
+#endif
+
+ /* It's possible we overestimated the output size by 1 element. */
+ rtBigNumStripTrailingZeros(pResult);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumMultiply(PRTBIGNUM pResult, PCRTBIGNUM pMultiplicand, PCRTBIGNUM pMultiplier)
+{
+ Assert(pResult != pMultiplicand); Assert(pResult != pMultiplier);
+ AssertReturn(pResult->fSensitive >= (pMultiplicand->fSensitive | pMultiplier->fSensitive), VERR_BIGNUM_SENSITIVE_INPUT);
+
+ int rc = rtBigNumUnscramble(pResult);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pResult);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pMultiplicand);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pMultiplicand);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pMultiplier);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pMultiplier);
+
+ /*
+ * The sign values follow XOR rules:
+ * -1 * 1 = -1; 1 ^ 0 = 1
+ * 1 * -1 = -1; 1 ^ 0 = 1
+ * -1 * -1 = 1; 1 ^ 1 = 0
+ * 1 * 1 = 1; 0 ^ 0 = 0
+ */
+ pResult->fNegative = pMultiplicand->fNegative ^ pMultiplier->fNegative;
+ rc = rtBigNumMagnitudeMultiply(pResult, pMultiplicand, pMultiplier);
+
+ rtBigNumScramble((PRTBIGNUM)pMultiplier);
+ }
+ rtBigNumScramble((PRTBIGNUM)pMultiplicand);
+ }
+ rtBigNumScramble(pResult);
+ }
+ return rc;
+}
+
+
+#if 0 /* unused */
+/**
+ * Clears a bit in the magnitude of @a pBigNum.
+ *
+ * The variables must be unscrambled.
+ *
+ * @param pBigNum The big number.
+ * @param iBit The bit to clear (0-based).
+ */
+DECLINLINE(void) rtBigNumMagnitudeClearBit(PRTBIGNUM pBigNum, uint32_t iBit)
+{
+ uint32_t iElement = iBit / RTBIGNUM_ELEMENT_BITS;
+ if (iElement < pBigNum->cUsed)
+ {
+ iBit &= RTBIGNUM_ELEMENT_BITS - 1;
+ pBigNum->pauElements[iElement] &= ~RTBIGNUM_ELEMENT_BIT(iBit);
+ if (iElement + 1 == pBigNum->cUsed && !pBigNum->pauElements[iElement])
+ rtBigNumStripTrailingZeros(pBigNum);
+ }
+}
+#endif /* unused */
+
+
+/**
+ * Sets a bit in the magnitude of @a pBigNum.
+ *
+ * The variables must be unscrambled.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ * @param iBit The bit to clear (0-based).
+ */
+DECLINLINE(int) rtBigNumMagnitudeSetBit(PRTBIGNUM pBigNum, uint32_t iBit)
+{
+ uint32_t iElement = iBit / RTBIGNUM_ELEMENT_BITS;
+ int rc = rtBigNumEnsureElementPresent(pBigNum, iElement);
+ if (RT_SUCCESS(rc))
+ {
+ iBit &= RTBIGNUM_ELEMENT_BITS - 1;
+ pBigNum->pauElements[iElement] |= RTBIGNUM_ELEMENT_BIT(iBit);
+ return VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+#if 0 /* unused */
+/**
+ * Writes a bit in the magnitude of @a pBigNum.
+ *
+ * The variables must be unscrambled.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ * @param iBit The bit to write (0-based).
+ * @param fValue The bit value.
+ */
+DECLINLINE(int) rtBigNumMagnitudeWriteBit(PRTBIGNUM pBigNum, uint32_t iBit, bool fValue)
+{
+ if (fValue)
+ return rtBigNumMagnitudeSetBit(pBigNum, iBit);
+ rtBigNumMagnitudeClearBit(pBigNum, iBit);
+ return VINF_SUCCESS;
+}
+#endif
+
+
+/**
+ * Returns the given magnitude bit.
+ *
+ * The variables must be unscrambled.
+ *
+ * @returns The bit value (1 or 0).
+ * @param pBigNum The big number.
+ * @param iBit The bit to return (0-based).
+ */
+DECLINLINE(RTBIGNUMELEMENT) rtBigNumMagnitudeGetBit(PCRTBIGNUM pBigNum, uint32_t iBit)
+{
+ uint32_t iElement = iBit / RTBIGNUM_ELEMENT_BITS;
+ if (iElement < pBigNum->cUsed)
+ {
+ iBit &= RTBIGNUM_ELEMENT_BITS - 1;
+ return (pBigNum->pauElements[iElement] >> iBit) & 1;
+ }
+ return 0;
+}
+
+
+/**
+ * Shifts the magnitude left by one.
+ *
+ * The variables must be unscrambled.
+ *
+ * @returns IPRT status code.
+ * @param pBigNum The big number.
+ * @param uCarry The value to shift in at the bottom.
+ */
+DECLINLINE(int) rtBigNumMagnitudeShiftLeftOne(PRTBIGNUM pBigNum, RTBIGNUMELEMENT uCarry)
+{
+ Assert(uCarry <= 1);
+
+ /* Do the shifting. */
+ uint32_t cUsed = pBigNum->cUsed;
+#ifdef IPRT_BIGINT_WITH_ASM
+ uCarry = rtBigNumMagnitudeShiftLeftOneAssemblyWorker(pBigNum->pauElements, cUsed, uCarry);
+#else
+ for (uint32_t i = 0; i < cUsed; i++)
+ {
+ RTBIGNUMELEMENT uTmp = pBigNum->pauElements[i];
+ pBigNum->pauElements[i] = (uTmp << 1) | uCarry;
+ uCarry = uTmp >> (RTBIGNUM_ELEMENT_BITS - 1);
+ }
+#endif
+
+ /* If we still carry a bit, we need to increase the size. */
+ if (uCarry)
+ {
+ int rc = rtBigNumSetUsed(pBigNum, cUsed + 1);
+ AssertRCReturn(rc, rc);
+ pBigNum->pauElements[cUsed] = uCarry;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Shifts the magnitude left by @a cBits.
+ *
+ * The variables must be unscrambled.
+ *
+ * @returns IPRT status code.
+ * @param pResult Where to store the result.
+ * @param pValue The value to shift.
+ * @param cBits The shift count.
+ */
+static int rtBigNumMagnitudeShiftLeft(PRTBIGNUM pResult, PCRTBIGNUM pValue, uint32_t cBits)
+{
+ int rc;
+ if (cBits)
+ {
+ uint32_t cBitsNew = rtBigNumMagnitudeBitWidth(pValue);
+ if (cBitsNew > 0)
+ {
+ if (cBitsNew + cBits > cBitsNew)
+ {
+ cBitsNew += cBits;
+ rc = rtBigNumSetUsedEx(pResult, 0, RT_ALIGN_32(cBitsNew, RTBIGNUM_ELEMENT_BITS) / RTBIGNUM_ELEMENT_BITS);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumSetUsed(pResult, RT_ALIGN_32(cBitsNew, RTBIGNUM_ELEMENT_BITS) / RTBIGNUM_ELEMENT_BITS);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t const cLeft = pValue->cUsed;
+ PCRTBIGNUMELEMENT pauSrc = pValue->pauElements;
+ PRTBIGNUMELEMENT pauDst = pResult->pauElements;
+
+ Assert(ASMMemIsZero(pauDst, (cBits / RTBIGNUM_ELEMENT_BITS) * RTBIGNUM_ELEMENT_SIZE));
+ pauDst += cBits / RTBIGNUM_ELEMENT_BITS;
+
+ cBits &= RTBIGNUM_ELEMENT_BITS - 1;
+ if (cBits)
+ {
+ RTBIGNUMELEMENT uPrev = 0;
+ for (uint32_t i = 0; i < cLeft; i++)
+ {
+ RTBIGNUMELEMENT uCur = pauSrc[i];
+ pauDst[i] = (uCur << cBits) | (uPrev >> (RTBIGNUM_ELEMENT_BITS - cBits));
+ uPrev = uCur;
+ }
+ uPrev >>= RTBIGNUM_ELEMENT_BITS - cBits;
+ if (uPrev)
+ pauDst[pValue->cUsed] = uPrev;
+ }
+ else
+ memcpy(pauDst, pauSrc, cLeft * RTBIGNUM_ELEMENT_SIZE);
+ }
+ }
+ else
+ rc = VERR_OUT_OF_RANGE;
+ }
+ /* Shifting zero always yields a zero result. */
+ else
+ rc = rtBigNumSetUsed(pResult, 0);
+ }
+ else
+ rc = rtBigNumMagnitudeCopy(pResult, pValue);
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumShiftLeft(PRTBIGNUM pResult, PCRTBIGNUM pValue, uint32_t cBits)
+{
+ Assert(pResult != pValue);
+ AssertReturn(pResult->fSensitive >= pValue->fSensitive, VERR_BIGNUM_SENSITIVE_INPUT);
+
+ int rc = rtBigNumUnscramble(pResult);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pResult);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pValue);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pValue);
+
+ pResult->fNegative = pValue->fNegative;
+ rc = rtBigNumMagnitudeShiftLeft(pResult, pValue, cBits);
+
+ rtBigNumScramble((PRTBIGNUM)pValue);
+ }
+ rtBigNumScramble(pResult);
+ }
+ return rc;
+}
+
+
+/**
+ * Shifts the magnitude right by @a cBits.
+ *
+ * The variables must be unscrambled.
+ *
+ * @returns IPRT status code.
+ * @param pResult Where to store the result.
+ * @param pValue The value to shift.
+ * @param cBits The shift count.
+ */
+static int rtBigNumMagnitudeShiftRight(PRTBIGNUM pResult, PCRTBIGNUM pValue, uint32_t cBits)
+{
+ int rc;
+ if (cBits)
+ {
+ uint32_t cBitsNew = rtBigNumMagnitudeBitWidth(pValue);
+ if (cBitsNew > cBits)
+ {
+ cBitsNew -= cBits;
+ uint32_t cElementsNew = RT_ALIGN_32(cBitsNew, RTBIGNUM_ELEMENT_BITS) / RTBIGNUM_ELEMENT_BITS;
+ rc = rtBigNumSetUsed(pResult, cElementsNew);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t i = cElementsNew;
+ PCRTBIGNUMELEMENT pauSrc = pValue->pauElements;
+ PRTBIGNUMELEMENT pauDst = pResult->pauElements;
+
+ pauSrc += cBits / RTBIGNUM_ELEMENT_BITS;
+
+ cBits &= RTBIGNUM_ELEMENT_BITS - 1;
+ if (cBits)
+ {
+ RTBIGNUMELEMENT uPrev = &pauSrc[i] == &pValue->pauElements[pValue->cUsed] ? 0 : pauSrc[i];
+ while (i-- > 0)
+ {
+ RTBIGNUMELEMENT uCur = pauSrc[i];
+ pauDst[i] = (uCur >> cBits) | (uPrev << (RTBIGNUM_ELEMENT_BITS - cBits));
+ uPrev = uCur;
+ }
+ }
+ else
+ memcpy(pauDst, pauSrc, i * RTBIGNUM_ELEMENT_SIZE);
+ }
+ }
+ else
+ rc = rtBigNumSetUsed(pResult, 0);
+ }
+ else
+ rc = rtBigNumMagnitudeCopy(pResult, pValue);
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumShiftRight(PRTBIGNUM pResult, PCRTBIGNUM pValue, uint32_t cBits)
+{
+ Assert(pResult != pValue);
+ AssertReturn(pResult->fSensitive >= pValue->fSensitive, VERR_BIGNUM_SENSITIVE_INPUT);
+
+ int rc = rtBigNumUnscramble(pResult);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pResult);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pValue);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pValue);
+
+ pResult->fNegative = pValue->fNegative;
+ rc = rtBigNumMagnitudeShiftRight(pResult, pValue, cBits);
+ if (!pResult->cUsed)
+ pResult->fNegative = 0;
+
+ rtBigNumScramble((PRTBIGNUM)pValue);
+ }
+ rtBigNumScramble(pResult);
+ }
+ return rc;
+}
+
+
+/**
+ * Implements the D3 test for Qhat decrementation.
+ *
+ * @returns True if Qhat should be decremented.
+ * @param puQhat Pointer to Qhat.
+ * @param uRhat The remainder.
+ * @param uDivisorY The penultimate divisor element.
+ * @param uDividendJMinus2 The j-2 dividend element.
+ */
+DECLINLINE(bool) rtBigNumKnuthD3_ShouldDecrementQhat(RTBIGNUMELEMENT2X const *puQhat, RTBIGNUMELEMENT uRhat,
+ RTBIGNUMELEMENT uDivisorY, RTBIGNUMELEMENT uDividendJMinus2)
+{
+ if (puQhat->s.Lo == RTBIGNUM_ELEMENT_MAX && puQhat->s.Hi == 0)
+ return true;
+#if RTBIGNUM_ELEMENT_BITS == 64
+ RTBIGNUMELEMENT2X TmpLeft;
+ RTUInt128MulByU64(&TmpLeft, puQhat, uDivisorY);
+
+ RTBIGNUMELEMENT2X TmpRight;
+ TmpRight.s.Lo = 0;
+ TmpRight.s.Hi = uRhat;
+ RTUInt128AssignAddU64(&TmpRight, uDividendJMinus2);
+
+ if (RTUInt128Compare(&TmpLeft, &TmpRight) > 0)
+ return true;
+#else
+ if (puQhat->u * uDivisorY > ((uint64_t)uRhat << 32) + uDividendJMinus2)
+ return true;
+#endif
+ return false;
+}
+
+
+/**
+ * C implementation of the D3 step of Knuth's division algorithm.
+ *
+ * This estimates a value Qhat that will be used as quotient "digit" (element)
+ * at the current level of the division (j).
+ *
+ * @returns The Qhat value we've estimated.
+ * @param pauDividendJN Pointer to the j+n (normalized) dividend element.
+ * Will access up to two elements prior to this.
+ * @param uDivZ The last element in the (normalized) divisor.
+ * @param uDivY The penultimate element in the (normalized) divisor.
+ */
+DECLINLINE(RTBIGNUMELEMENT) rtBigNumKnuthD3_EstimateQhat(PCRTBIGNUMELEMENT pauDividendJN,
+ RTBIGNUMELEMENT uDivZ, RTBIGNUMELEMENT uDivY)
+{
+ RTBIGNUMELEMENT2X uQhat;
+ RTBIGNUMELEMENT uRhat;
+ RTBIGNUMELEMENT uDividendJN = pauDividendJN[0];
+ Assert(uDividendJN <= uDivZ);
+ if (uDividendJN != uDivZ)
+ rtBigNumElement2xDiv2xBy1x(&uQhat, &uRhat, uDividendJN, pauDividendJN[-1], uDivZ);
+ else
+ {
+ /*
+ * This is the case where we end up with an initial Qhat that's all Fs.
+ */
+ /* Calc the remainder for max Qhat value. */
+ RTBIGNUMELEMENT2X uTmp1; /* (v[j+n] << bits) + v[J+N-1] */
+ uTmp1.s.Hi = uDivZ;
+ uTmp1.s.Lo = pauDividendJN[-1];
+
+ RTBIGNUMELEMENT2X uTmp2; /* uQhat * uDividendJN */
+ uTmp2.s.Hi = uDivZ - 1;
+ uTmp2.s.Lo = 0 - uDivZ;
+#if RTBIGNUM_ELEMENT_BITS == 64
+ RTUInt128AssignSub(&uTmp1, &uTmp2);
+#else
+ uTmp1.u -= uTmp2.u;
+#endif
+ /* If we overflowed the remainder, don't bother trying to adjust. */
+ if (uTmp1.s.Hi)
+ return RTBIGNUM_ELEMENT_MAX;
+
+ uRhat = uTmp1.s.Lo;
+ uQhat.s.Lo = RTBIGNUM_ELEMENT_MAX;
+ uQhat.s.Hi = 0;
+ }
+
+ /*
+ * Adjust Q to eliminate all cases where it's two to large and most cases
+ * where it's one too large.
+ */
+ while (rtBigNumKnuthD3_ShouldDecrementQhat(&uQhat, uRhat, uDivY, pauDividendJN[-2]))
+ {
+ rtBigNumElement2xDec(&uQhat);
+ uRhat += uDivZ;
+ if (uRhat < uDivZ /* overflow */ || uRhat == RTBIGNUM_ELEMENT_MAX)
+ break;
+ }
+
+ return uQhat.s.Lo;
+}
+
+
+#ifdef IPRT_BIGINT_WITH_ASM
+DECLASM(bool) rtBigNumKnuthD4_MulSub(PRTBIGNUMELEMENT pauDividendJ, PRTBIGNUMELEMENT pauDivisor,
+ uint32_t cDivisor, RTBIGNUMELEMENT uQhat);
+#else
+/**
+ * C implementation of the D4 step of Knuth's division algorithm.
+ *
+ * This subtracts Divisor * Qhat from the dividend at the current J index.
+ *
+ * @returns true if negative result (unlikely), false if positive.
+ * @param pauDividendJ Pointer to the j-th (normalized) dividend element.
+ * Will access up to two elements prior to this.
+ * @param uDivZ The last element in the (normalized) divisor.
+ * @param uDivY The penultimate element in the (normalized) divisor.
+ */
+DECLINLINE(bool) rtBigNumKnuthD4_MulSub(PRTBIGNUMELEMENT pauDividendJ, PRTBIGNUMELEMENT pauDivisor,
+ uint32_t cDivisor, RTBIGNUMELEMENT uQhat)
+{
+ uint32_t i;
+ bool fBorrow = false;
+ RTBIGNUMELEMENT uMulCarry = 0;
+ for (i = 0; i < cDivisor; i++)
+ {
+ RTBIGNUMELEMENT2X uSub;
+# if RTBIGNUM_ELEMENT_BITS == 64
+ RTUInt128MulU64ByU64(&uSub, uQhat, pauDivisor[i]);
+ RTUInt128AssignAddU64(&uSub, uMulCarry);
+# else
+ uSub.u = (uint64_t)uQhat * pauDivisor[i] + uMulCarry;
+# endif
+ uMulCarry = uSub.s.Hi;
+
+ RTBIGNUMELEMENT uDividendI = pauDividendJ[i];
+ if (!fBorrow)
+ {
+ fBorrow = uDividendI < uSub.s.Lo;
+ uDividendI -= uSub.s.Lo;
+ }
+ else
+ {
+ fBorrow = uDividendI <= uSub.s.Lo;
+ uDividendI -= uSub.s.Lo + 1;
+ }
+ pauDividendJ[i] = uDividendI;
+ }
+
+ /* Carry and borrow into the final dividend element. */
+ RTBIGNUMELEMENT uDividendI = pauDividendJ[i];
+ if (!fBorrow)
+ {
+ fBorrow = uDividendI < uMulCarry;
+ pauDividendJ[i] = uDividendI - uMulCarry;
+ }
+ else
+ {
+ fBorrow = uDividendI <= uMulCarry;
+ pauDividendJ[i] = uDividendI - uMulCarry - 1;
+ }
+
+ return fBorrow;
+}
+#endif /* !IPRT_BIGINT_WITH_ASM */
+
+
+/**
+ * C implementation of the D6 step of Knuth's division algorithm.
+ *
+ * This adds the divisor to the dividend to undo the negative value step D4
+ * produced. This is not very frequent occurence.
+ *
+ * @param pauDividendJ Pointer to the j-th (normalized) dividend element.
+ * Will access up to two elements prior to this.
+ * @param pauDivisor The last element in the (normalized) divisor.
+ * @param cDivisor The penultimate element in the (normalized) divisor.
+ */
+DECLINLINE(void) rtBigNumKnuthD6_AddBack(PRTBIGNUMELEMENT pauDividendJ, PRTBIGNUMELEMENT pauDivisor, uint32_t cDivisor)
+{
+ RTBIGNUMELEMENT2X uTmp;
+ uTmp.s.Lo = 0;
+
+ uint32_t i;
+ for (i = 0; i < cDivisor; i++)
+ {
+ uTmp.s.Hi = 0;
+#if RTBIGNUM_ELEMENT_BITS == 64
+ RTUInt128AssignAddU64(&uTmp, pauDivisor[i]);
+ RTUInt128AssignAddU64(&uTmp, pauDividendJ[i]);
+#else
+ uTmp.u += pauDivisor[i];
+ uTmp.u += pauDividendJ[i];
+#endif
+ pauDividendJ[i] = uTmp.s.Lo;
+ uTmp.s.Lo = uTmp.s.Hi;
+ }
+
+ /* The final dividend entry. */
+ Assert(pauDividendJ[i] + uTmp.s.Lo < uTmp.s.Lo);
+ pauDividendJ[i] += uTmp.s.Lo;
+}
+
+
+/**
+ * Knuth's division (core).
+ *
+ * @returns IPRT status code.
+ * @param pQuotient Where to return the quotient. Can be NULL.
+ * @param pRemainder Where to return the remainder.
+ * @param pDividend What to divide.
+ * @param pDivisor What to divide by.
+ */
+static int rtBigNumMagnitudeDivideKnuth(PRTBIGNUM pQuotient, PRTBIGNUM pRemainder, PCRTBIGNUM pDividend, PCRTBIGNUM pDivisor)
+{
+ Assert(pDivisor->cUsed > 1);
+ uint32_t const cDivisor = pDivisor->cUsed;
+ Assert(pDividend->cUsed >= cDivisor);
+
+ /*
+ * Make sure we've got enough space in the quotient, so we can build it
+ * without any trouble come step D5.
+ */
+ int rc;
+ if (pQuotient)
+ {
+ rc = rtBigNumSetUsedEx(pQuotient, 0, pDividend->cUsed - cDivisor + 1);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumSetUsed(pQuotient, pDividend->cUsed - cDivisor + 1);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * D1. Normalize. The goal here is to make sure the last element in the
+ * divisor is greater than RTBIGNUMELEMENTS_MAX/2. We must also make sure
+ * we can access element pDividend->cUsed of the normalized dividend.
+ */
+ RTBIGNUM NormDividend;
+ RTBIGNUM NormDivisor;
+ PCRTBIGNUM pNormDivisor = &NormDivisor;
+ rtBigNumInitZeroTemplate(&NormDivisor, pDividend);
+
+ uint32_t cNormShift = (RTBIGNUM_ELEMENT_BITS - rtBigNumMagnitudeBitWidth(pDivisor)) & (RTBIGNUM_ELEMENT_BITS - 1);
+ if (cNormShift)
+ {
+ rtBigNumInitZeroTemplate(&NormDividend, pDividend);
+ rc = rtBigNumMagnitudeShiftLeft(&NormDividend, pDividend, cNormShift);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumMagnitudeShiftLeft(&NormDivisor, pDivisor, cNormShift);
+ }
+ else
+ {
+ pNormDivisor = pDivisor;
+ rc = rtBigNumCloneInternal(&NormDividend, pDividend);
+ }
+ if (RT_SUCCESS(rc) && pDividend->cUsed == NormDividend.cUsed)
+ rc = rtBigNumEnsureExtraZeroElements(&NormDividend, NormDividend.cUsed + 1);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * D2. Initialize the j index so we can loop thru the elements in the
+ * dividend that makes it larger than the divisor.
+ */
+ uint32_t j = pDividend->cUsed - cDivisor;
+
+ RTBIGNUMELEMENT const DivZ = pNormDivisor->pauElements[cDivisor - 1];
+ RTBIGNUMELEMENT const DivY = pNormDivisor->pauElements[cDivisor - 2];
+ for (;;)
+ {
+ /*
+ * D3. Estimate a Q' by dividing the j and j-1 dividen elements by
+ * the last divisor element, then adjust against the next elements.
+ */
+ RTBIGNUMELEMENT uQhat = rtBigNumKnuthD3_EstimateQhat(&NormDividend.pauElements[j + cDivisor], DivZ, DivY);
+
+ /*
+ * D4. Multiply and subtract.
+ */
+ bool fNegative = rtBigNumKnuthD4_MulSub(&NormDividend.pauElements[j], pNormDivisor->pauElements, cDivisor, uQhat);
+
+ /*
+ * D5. Test remainder.
+ * D6. Add back.
+ */
+ if (fNegative)
+ {
+//__debugbreak();
+ rtBigNumKnuthD6_AddBack(&NormDividend.pauElements[j], pNormDivisor->pauElements, cDivisor);
+ uQhat--;
+ }
+
+ if (pQuotient)
+ pQuotient->pauElements[j] = uQhat;
+
+ /*
+ * D7. Loop on j.
+ */
+ if (j == 0)
+ break;
+ j--;
+ }
+
+ /*
+ * D8. Unnormalize the remainder.
+ */
+ rtBigNumStripTrailingZeros(&NormDividend);
+ if (cNormShift)
+ rc = rtBigNumMagnitudeShiftRight(pRemainder, &NormDividend, cNormShift);
+ else
+ rc = rtBigNumMagnitudeCopy(pRemainder, &NormDividend);
+ if (pQuotient)
+ rtBigNumStripTrailingZeros(pQuotient);
+ }
+
+ /*
+ * Delete temporary variables.
+ */
+ RTBigNumDestroy(&NormDividend);
+ if (pNormDivisor == &NormDivisor)
+ RTBigNumDestroy(&NormDivisor);
+ return rc;
+}
+
+
+static int rtBigNumMagnitudeDivideSlowLong(PRTBIGNUM pQuotient, PRTBIGNUM pRemainder, PCRTBIGNUM pDividend, PCRTBIGNUM pDivisor)
+{
+ /*
+ * Do very simple long division. This ain't fast, but it does the trick.
+ */
+ int rc = VINF_SUCCESS;
+ uint32_t iBit = rtBigNumMagnitudeBitWidth(pDividend);
+ while (iBit-- > 0)
+ {
+ rc = rtBigNumMagnitudeShiftLeftOne(pRemainder, rtBigNumMagnitudeGetBit(pDividend, iBit));
+ AssertRCBreak(rc);
+ int iDiff = rtBigNumMagnitudeCompare(pRemainder, pDivisor);
+ if (iDiff >= 0)
+ {
+ if (iDiff != 0)
+ {
+ rc = rtBigNumMagnitudeSubThis(pRemainder, pDivisor);
+ AssertRCBreak(rc);
+ }
+ else
+ rtBigNumSetUsed(pRemainder, 0);
+ rc = rtBigNumMagnitudeSetBit(pQuotient, iBit);
+ AssertRCBreak(rc);
+ }
+ }
+
+ /* This shouldn't be necessary. */
+ rtBigNumStripTrailingZeros(pQuotient);
+ rtBigNumStripTrailingZeros(pRemainder);
+
+ return rc;
+}
+
+
+/**
+ * Divides the magnitudes of two values, letting the caller care about the sign
+ * bit.
+ *
+ * All variables must be unscrambled. The sign flag is not considered nor
+ * touched, this means the caller have to check for zero outputs.
+ *
+ * @returns IPRT status code.
+ * @param pQuotient Where to return the quotient.
+ * @param pRemainder Where to return the remainder.
+ * @param pDividend What to divide.
+ * @param pDivisor What to divide by.
+ * @param fForceLong Force long division.
+ */
+static int rtBigNumMagnitudeDivide(PRTBIGNUM pQuotient, PRTBIGNUM pRemainder, PCRTBIGNUM pDividend, PCRTBIGNUM pDivisor,
+ bool fForceLong)
+{
+ Assert(pQuotient != pDividend); Assert(pQuotient != pDivisor); Assert(pRemainder != pDividend); Assert(pRemainder != pDivisor); Assert(pRemainder != pQuotient);
+ Assert(!pQuotient->fCurScrambled); Assert(!pRemainder->fCurScrambled); Assert(!pDividend->fCurScrambled); Assert(!pDivisor->fCurScrambled);
+
+ /*
+ * Just set both output values to zero as that's the return for several
+ * special case and the initial state of the general case.
+ */
+ rtBigNumSetUsed(pQuotient, 0);
+ rtBigNumSetUsed(pRemainder, 0);
+
+ /*
+ * Dividing something by zero is undefined.
+ * Diving zero by something is zero, unless the divsor is also zero.
+ */
+ if (!pDivisor->cUsed || !pDividend->cUsed)
+ return pDivisor->cUsed ? VINF_SUCCESS : VERR_BIGNUM_DIV_BY_ZERO;
+
+ /*
+ * Dividing by one? Quotient = dividend, no remainder.
+ */
+ if (pDivisor->cUsed == 1 && pDivisor->pauElements[0] == 1)
+ return rtBigNumMagnitudeCopy(pQuotient, pDividend);
+
+ /*
+ * Dividend smaller than the divisor. Zero quotient, all divisor.
+ */
+ int iDiff = rtBigNumMagnitudeCompare(pDividend, pDivisor);
+ if (iDiff < 0)
+ return rtBigNumMagnitudeCopy(pRemainder, pDividend);
+
+ /*
+ * Since we already have done the compare, check if the two values are the
+ * same. The result is 1 and no remainder then.
+ */
+ if (iDiff == 0)
+ {
+ int rc = rtBigNumSetUsed(pQuotient, 1);
+ if (RT_SUCCESS(rc))
+ pQuotient->pauElements[0] = 1;
+ return rc;
+ }
+
+ /*
+ * Sort out special cases before going to the preferred or select algorithm.
+ */
+ int rc;
+ if (pDividend->cUsed <= 2 && !fForceLong)
+ {
+ if (pDividend->cUsed < 2)
+ {
+ /*
+ * Single element division.
+ */
+ RTBIGNUMELEMENT uQ = pDividend->pauElements[0] / pDivisor->pauElements[0];
+ RTBIGNUMELEMENT uR = pDividend->pauElements[0] % pDivisor->pauElements[0];
+ rc = VINF_SUCCESS;
+ if (uQ)
+ {
+ rc = rtBigNumSetUsed(pQuotient, 1);
+ if (RT_SUCCESS(rc))
+ pQuotient->pauElements[0] = uQ;
+ }
+ if (uR && RT_SUCCESS(rc))
+ {
+ rc = rtBigNumSetUsed(pRemainder, 1);
+ if (RT_SUCCESS(rc))
+ pRemainder->pauElements[0] = uR;
+ }
+ }
+ else
+ {
+ /*
+ * Two elements dividend by a one or two element divisor.
+ */
+ RTBIGNUMELEMENT2X uQ, uR;
+ if (pDivisor->cUsed == 1)
+ {
+ rtBigNumElement2xDiv2xBy1x(&uQ, &uR.s.Lo, pDividend->pauElements[1], pDividend->pauElements[0],
+ pDivisor->pauElements[0]);
+ uR.s.Hi = 0;
+ }
+ else
+ rtBigNumElement2xDiv(&uQ, &uR, pDividend->pauElements[1], pDividend->pauElements[0],
+ pDivisor->pauElements[1], pDivisor->pauElements[0]);
+ rc = rtBigNumElement2xCopyToMagnitude(&uQ, pQuotient);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumElement2xCopyToMagnitude(&uR, pRemainder);
+ }
+ }
+ /*
+ * Decide upon which algorithm to use. Knuth requires a divisor that's at
+ * least 2 elements big.
+ */
+ else if (pDivisor->cUsed < 2 || fForceLong)
+ rc = rtBigNumMagnitudeDivideSlowLong(pQuotient, pRemainder, pDividend, pDivisor);
+ else
+ rc = rtBigNumMagnitudeDivideKnuth(pQuotient, pRemainder, pDividend, pDivisor);
+ return rc;
+}
+
+
+static int rtBigNumDivideCommon(PRTBIGNUM pQuotient, PRTBIGNUM pRemainder,
+ PCRTBIGNUM pDividend, PCRTBIGNUM pDivisor, bool fForceLong)
+{
+ Assert(pQuotient != pDividend); Assert(pQuotient != pDivisor); Assert(pRemainder != pDividend); Assert(pRemainder != pDivisor); Assert(pRemainder != pQuotient);
+ AssertReturn(pQuotient->fSensitive >= (pDividend->fSensitive | pDivisor->fSensitive), VERR_BIGNUM_SENSITIVE_INPUT);
+ AssertReturn(pRemainder->fSensitive >= (pDividend->fSensitive | pDivisor->fSensitive), VERR_BIGNUM_SENSITIVE_INPUT);
+
+ int rc = rtBigNumUnscramble(pQuotient);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pQuotient);
+ rc = rtBigNumUnscramble(pRemainder);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pRemainder);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pDividend);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pDividend);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pDivisor);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pDivisor);
+
+ /*
+ * The sign value of the remainder is the same as the dividend.
+ * The sign values of the quotient follow XOR rules, just like multiplication:
+ * -3 / 2 = -1; r=-1; 1 ^ 0 = 1
+ * 3 / -2 = -1; r= 1; 1 ^ 0 = 1
+ * -3 / -2 = 1; r=-1; 1 ^ 1 = 0
+ * 3 / 2 = 1; r= 1; 0 ^ 0 = 0
+ */
+ pQuotient->fNegative = pDividend->fNegative ^ pDivisor->fNegative;
+ pRemainder->fNegative = pDividend->fNegative;
+
+ rc = rtBigNumMagnitudeDivide(pQuotient, pRemainder, pDividend, pDivisor, fForceLong);
+
+ if (pQuotient->cUsed == 0)
+ pQuotient->fNegative = 0;
+ if (pRemainder->cUsed == 0)
+ pRemainder->fNegative = 0;
+
+ rtBigNumScramble((PRTBIGNUM)pDivisor);
+ }
+ rtBigNumScramble((PRTBIGNUM)pDividend);
+ }
+ rtBigNumScramble(pRemainder);
+ }
+ rtBigNumScramble(pQuotient);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumDivide(PRTBIGNUM pQuotient, PRTBIGNUM pRemainder, PCRTBIGNUM pDividend, PCRTBIGNUM pDivisor)
+{
+ return rtBigNumDivideCommon(pQuotient, pRemainder, pDividend, pDivisor, false /*fForceLong*/);
+}
+
+
+RTDECL(int) RTBigNumDivideLong(PRTBIGNUM pQuotient, PRTBIGNUM pRemainder, PCRTBIGNUM pDividend, PCRTBIGNUM pDivisor)
+{
+ return rtBigNumDivideCommon(pQuotient, pRemainder, pDividend, pDivisor, true /*fForceLong*/);
+}
+
+
+/**
+ * Calculates the modulus of a magnitude value, leaving the sign bit to the
+ * caller.
+ *
+ * All variables must be unscrambled. The sign flag is not considered nor
+ * touched, this means the caller have to check for zero outputs.
+ *
+ * @returns IPRT status code.
+ * @param pRemainder Where to return the remainder.
+ * @param pDividend What to divide.
+ * @param pDivisor What to divide by.
+ */
+static int rtBigNumMagnitudeModulo(PRTBIGNUM pRemainder, PCRTBIGNUM pDividend, PCRTBIGNUM pDivisor)
+{
+ Assert(pRemainder != pDividend); Assert(pRemainder != pDivisor);
+ Assert(!pRemainder->fCurScrambled); Assert(!pDividend->fCurScrambled); Assert(!pDivisor->fCurScrambled);
+
+ /*
+ * Just set the output value to zero as that's the return for several
+ * special case and the initial state of the general case.
+ */
+ rtBigNumSetUsed(pRemainder, 0);
+
+ /*
+ * Dividing something by zero is undefined.
+ * Diving zero by something is zero, unless the divsor is also zero.
+ */
+ if (!pDivisor->cUsed || !pDividend->cUsed)
+ return pDivisor->cUsed ? VINF_SUCCESS : VERR_BIGNUM_DIV_BY_ZERO;
+
+ /*
+ * Dividing by one? Quotient = dividend, no remainder.
+ */
+ if (pDivisor->cUsed == 1 && pDivisor->pauElements[0] == 1)
+ return VINF_SUCCESS;
+
+ /*
+ * Dividend smaller than the divisor. Zero quotient, all divisor.
+ */
+ int iDiff = rtBigNumMagnitudeCompare(pDividend, pDivisor);
+ if (iDiff < 0)
+ return rtBigNumMagnitudeCopy(pRemainder, pDividend);
+
+ /*
+ * Since we already have done the compare, check if the two values are the
+ * same. The result is 1 and no remainder then.
+ */
+ if (iDiff == 0)
+ return VINF_SUCCESS;
+
+ /** @todo optimize small numbers. */
+ int rc = VINF_SUCCESS;
+ if (pDivisor->cUsed < 2)
+ {
+ /*
+ * Do very simple long division. This ain't fast, but it does the trick.
+ */
+ uint32_t iBit = rtBigNumMagnitudeBitWidth(pDividend);
+ while (iBit-- > 0)
+ {
+ rc = rtBigNumMagnitudeShiftLeftOne(pRemainder, rtBigNumMagnitudeGetBit(pDividend, iBit));
+ AssertRCBreak(rc);
+ iDiff = rtBigNumMagnitudeCompare(pRemainder, pDivisor);
+ if (iDiff >= 0)
+ {
+ if (iDiff != 0)
+ {
+ rc = rtBigNumMagnitudeSubThis(pRemainder, pDivisor);
+ AssertRCBreak(rc);
+ }
+ else
+ rtBigNumSetUsed(pRemainder, 0);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Join paths with division.
+ */
+ rc = rtBigNumMagnitudeDivideKnuth(NULL, pRemainder, pDividend, pDivisor);
+ }
+
+ /* This shouldn't be necessary. */
+ rtBigNumStripTrailingZeros(pRemainder);
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumModulo(PRTBIGNUM pRemainder, PCRTBIGNUM pDividend, PCRTBIGNUM pDivisor)
+{
+ Assert(pRemainder != pDividend); Assert(pRemainder != pDivisor);
+ AssertReturn(pRemainder->fSensitive >= (pDividend->fSensitive | pDivisor->fSensitive), VERR_BIGNUM_SENSITIVE_INPUT);
+
+ int rc = rtBigNumUnscramble(pRemainder);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pRemainder);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pDividend);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pDividend);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pDivisor);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pDivisor);
+
+ /*
+ * The sign value of the remainder is the same as the dividend.
+ */
+ pRemainder->fNegative = pDividend->fNegative;
+
+ rc = rtBigNumMagnitudeModulo(pRemainder, pDividend, pDivisor);
+
+ if (pRemainder->cUsed == 0)
+ pRemainder->fNegative = 0;
+
+ rtBigNumScramble((PRTBIGNUM)pDivisor);
+ }
+ rtBigNumScramble((PRTBIGNUM)pDividend);
+ }
+ rtBigNumScramble(pRemainder);
+ }
+ return rc;
+}
+
+
+
+/**
+ * Exponentiate the magnitude.
+ *
+ * All variables must be unscrambled. The sign flag is not considered nor
+ * touched, this means the caller have to reject negative exponents.
+ *
+ * @returns IPRT status code.
+ * @param pResult Where to return power.
+ * @param pBase The base value.
+ * @param pExponent The exponent (assumed positive or zero).
+ */
+static int rtBigNumMagnitudeExponentiate(PRTBIGNUM pResult, PCRTBIGNUM pBase, PCRTBIGNUM pExponent)
+{
+ Assert(pResult != pBase); Assert(pResult != pExponent);
+ Assert(!pResult->fCurScrambled); Assert(!pBase->fCurScrambled); Assert(!pExponent->fCurScrambled);
+
+ /*
+ * A couple of special cases.
+ */
+ int rc;
+ /* base ^ 0 => 1. */
+ if (pExponent->cUsed == 0)
+ {
+ rc = rtBigNumSetUsed(pResult, 1);
+ if (RT_SUCCESS(rc))
+ pResult->pauElements[0] = 1;
+ return rc;
+ }
+
+ /* base ^ 1 => base. */
+ if (pExponent->cUsed == 1 && pExponent->pauElements[0] == 1)
+ return rtBigNumMagnitudeCopy(pResult, pBase);
+
+ /*
+ * Set up.
+ */
+ /* Init temporary power-of-two variable to base. */
+ RTBIGNUM Pow2;
+ rc = rtBigNumCloneInternal(&Pow2, pBase);
+ if (RT_SUCCESS(rc))
+ {
+ /* Init result to 1. */
+ rc = rtBigNumSetUsed(pResult, 1);
+ if (RT_SUCCESS(rc))
+ {
+ pResult->pauElements[0] = 1;
+
+ /* Make a temporary variable that we can use for temporary storage of the result. */
+ RTBIGNUM TmpMultiplicand;
+ rc = rtBigNumCloneInternal(&TmpMultiplicand, pResult);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Exponentiation by squaring. Reduces the number of
+ * multiplications to: NumBitsSet(Exponent) + BitWidth(Exponent).
+ */
+ uint32_t const cExpBits = rtBigNumMagnitudeBitWidth(pExponent);
+ uint32_t iBit = 0;
+ for (;;)
+ {
+ if (rtBigNumMagnitudeGetBit(pExponent, iBit) != 0)
+ {
+ rc = rtBigNumMagnitudeCopy(&TmpMultiplicand, pResult);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumMagnitudeMultiply(pResult, &TmpMultiplicand, &Pow2);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* Done? */
+ iBit++;
+ if (iBit >= cExpBits)
+ break;
+
+ /* Not done yet, square the base again. */
+ rc = rtBigNumMagnitudeCopy(&TmpMultiplicand, &Pow2);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumMagnitudeMultiply(&Pow2, &TmpMultiplicand, &TmpMultiplicand);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ RTBigNumDestroy(&TmpMultiplicand);
+ }
+ }
+ RTBigNumDestroy(&Pow2);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumExponentiate(PRTBIGNUM pResult, PCRTBIGNUM pBase, PCRTBIGNUM pExponent)
+{
+ Assert(pResult != pBase); Assert(pResult != pExponent);
+ AssertReturn(pResult->fSensitive >= (pBase->fSensitive | pExponent->fSensitive), VERR_BIGNUM_SENSITIVE_INPUT);
+
+ int rc = rtBigNumUnscramble(pResult);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pResult);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pBase);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pBase);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pExponent);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pExponent);
+ if (!pExponent->fNegative)
+ {
+ pResult->fNegative = pBase->fNegative; /* sign unchanged. */
+ rc = rtBigNumMagnitudeExponentiate(pResult, pBase, pExponent);
+ }
+ else
+ rc = VERR_BIGNUM_NEGATIVE_EXPONENT;
+
+ rtBigNumScramble((PRTBIGNUM)pExponent);
+ }
+ rtBigNumScramble((PRTBIGNUM)pBase);
+ }
+ rtBigNumScramble(pResult);
+ }
+ return rc;
+}
+
+
+/**
+ * Modular exponentiation, magnitudes only.
+ *
+ * All variables must be unscrambled. The sign flag is not considered nor
+ * touched, this means the caller have to reject negative exponents and do any
+ * other necessary sign bit fiddling.
+ *
+ * @returns IPRT status code.
+ * @param pResult Where to return the remainder of the power.
+ * @param pBase The base value.
+ * @param pExponent The exponent (assumed positive or zero).
+ * @param pModulus The modulus value (or divisor if you like).
+ */
+static int rtBigNumMagnitudeModExp(PRTBIGNUM pResult, PRTBIGNUM pBase, PRTBIGNUM pExponent, PRTBIGNUM pModulus)
+{
+ Assert(pResult != pBase); Assert(pResult != pBase); Assert(pResult != pExponent); Assert(pResult != pModulus);
+ Assert(!pResult->fCurScrambled); Assert(!pBase->fCurScrambled); Assert(!pExponent->fCurScrambled); Assert(!pModulus->fCurScrambled);
+ int rc;
+
+ /*
+ * Check some special cases to get them out of the way.
+ */
+ /* Div by 0 => invalid. */
+ if (pModulus->cUsed == 0)
+ return VERR_BIGNUM_DIV_BY_ZERO;
+
+ /* Div by 1 => no remainder. */
+ if (pModulus->cUsed == 1 && pModulus->pauElements[0] == 1)
+ {
+ rtBigNumSetUsed(pResult, 0);
+ return VINF_SUCCESS;
+ }
+
+ /* base ^ 0 => 1. */
+ if (pExponent->cUsed == 0)
+ {
+ rc = rtBigNumSetUsed(pResult, 1);
+ if (RT_SUCCESS(rc))
+ pResult->pauElements[0] = 1;
+ return rc;
+ }
+
+ /* base ^ 1 => base. */
+ if (pExponent->cUsed == 1 && pExponent->pauElements[0] == 1)
+ return rtBigNumMagnitudeModulo(pResult, pBase, pModulus);
+
+ /*
+ * Set up.
+ */
+ /* Result = 1; preallocate space for the result while at it. */
+ rc = rtBigNumSetUsed(pResult, pModulus->cUsed + 1);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumSetUsed(pResult, 1);
+ if (RT_SUCCESS(rc))
+ {
+ pResult->pauElements[0] = 1;
+
+ /* ModBase = pBase or pBase % pModulus depending on the difference in size. */
+ RTBIGNUM Pow2;
+ if (pBase->cUsed <= pModulus->cUsed + pModulus->cUsed / 2)
+ rc = rtBigNumCloneInternal(&Pow2, pBase);
+ else
+ rc = rtBigNumMagnitudeModulo(rtBigNumInitZeroTemplate(&Pow2, pBase), pBase, pModulus);
+
+ /* Need a couple of temporary variables. */
+ RTBIGNUM TmpMultiplicand;
+ rtBigNumInitZeroTemplate(&TmpMultiplicand, pResult);
+
+ RTBIGNUM TmpProduct;
+ rtBigNumInitZeroTemplate(&TmpProduct, pResult);
+
+ /*
+ * We combine the exponentiation by squaring with the fact that:
+ * (a*b) mod n = ( (a mod n) * (b mod n) ) mod n
+ *
+ * Thus, we can reduce the size of intermediate results by mod'ing them
+ * in each step.
+ */
+ uint32_t const cExpBits = rtBigNumMagnitudeBitWidth(pExponent);
+ uint32_t iBit = 0;
+ for (;;)
+ {
+ if (rtBigNumMagnitudeGetBit(pExponent, iBit) != 0)
+ {
+ rc = rtBigNumMagnitudeCopy(&TmpMultiplicand, pResult);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumMagnitudeMultiply(&TmpProduct, &TmpMultiplicand, &Pow2);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumMagnitudeModulo(pResult, &TmpProduct, pModulus);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ /* Done? */
+ iBit++;
+ if (iBit >= cExpBits)
+ break;
+
+ /* Not done yet, square and mod the base again. */
+ rc = rtBigNumMagnitudeCopy(&TmpMultiplicand, &Pow2);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumMagnitudeMultiply(&TmpProduct, &TmpMultiplicand, &TmpMultiplicand);
+ if (RT_SUCCESS(rc))
+ rc = rtBigNumMagnitudeModulo(&Pow2, &TmpProduct, pModulus);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ RTBigNumDestroy(&TmpMultiplicand);
+ RTBigNumDestroy(&TmpProduct);
+ RTBigNumDestroy(&Pow2);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTBigNumModExp(PRTBIGNUM pResult, PRTBIGNUM pBase, PRTBIGNUM pExponent, PRTBIGNUM pModulus)
+{
+ Assert(pResult != pBase); Assert(pResult != pBase); Assert(pResult != pExponent); Assert(pResult != pModulus);
+ AssertReturn(pResult->fSensitive >= (pBase->fSensitive | pExponent->fSensitive | pModulus->fSensitive),
+ VERR_BIGNUM_SENSITIVE_INPUT);
+
+ int rc = rtBigNumUnscramble(pResult);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pResult);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pBase);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pBase);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pExponent);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pExponent);
+ rc = rtBigNumUnscramble((PRTBIGNUM)pModulus);
+ if (RT_SUCCESS(rc))
+ {
+ RTBIGNUM_ASSERT_VALID(pModulus);
+ if (!pExponent->fNegative)
+ {
+ pResult->fNegative = pModulus->fNegative; /* pBase ^ pExponent / pModulus; result = remainder. */
+ rc = rtBigNumMagnitudeModExp(pResult, pBase, pExponent, pModulus);
+ }
+ else
+ rc = VERR_BIGNUM_NEGATIVE_EXPONENT;
+ rtBigNumScramble((PRTBIGNUM)pModulus);
+ }
+ rtBigNumScramble((PRTBIGNUM)pExponent);
+ }
+ rtBigNumScramble((PRTBIGNUM)pBase);
+ }
+ rtBigNumScramble(pResult);
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/math/ceil.asm b/src/VBox/Runtime/common/math/ceil.asm
new file mode 100644
index 00000000..b5acff75
--- /dev/null
+++ b/src/VBox/Runtime/common/math/ceil.asm
@@ -0,0 +1,79 @@
+; $Id: ceil.asm $
+;; @file
+; IPRT - No-CRT ceil - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Compute the smallest integral value not less than lrd.
+; @returns st(0) / xmm0
+; @param rd [rbp + 8] / xmm0
+RT_NOCRT_BEGINPROC ceil
+ push xBP
+ mov xBP, xSP
+ sub xSP, 10h
+
+%ifdef RT_ARCH_AMD64 ;; @todo there is probably some sse instruction for this.
+ movsd [xSP], xmm0
+ fld qword [xSP]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ; Make it round up by modifying the fpu control word.
+ fstcw [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or eax, 00800h
+ and eax, 0fbffh
+ mov [xBP - 08h], eax
+ fldcw [xBP - 08h]
+
+ ; Round ST(0) to integer.
+ frndint
+
+ ; Restore the fpu control word.
+ fldcw [xBP - 10h]
+
+%ifdef RT_ARCH_AMD64
+ fstp qword [xSP]
+ movsd xmm0, [xSP]
+%endif
+ leave
+ ret
+ENDPROC RT_NOCRT(ceil)
+
diff --git a/src/VBox/Runtime/common/math/ceilf.asm b/src/VBox/Runtime/common/math/ceilf.asm
new file mode 100644
index 00000000..c6d0dc2a
--- /dev/null
+++ b/src/VBox/Runtime/common/math/ceilf.asm
@@ -0,0 +1,79 @@
+; $Id: ceilf.asm $
+;; @file
+; IPRT - No-CRT ceilf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Compute the smallest integral value not less than r32.
+; @returns st(0) / xmm0
+; @param r32 [rbp + 8] / xmm0
+RT_NOCRT_BEGINPROC ceilf
+ push xBP
+ mov xBP, xSP
+ sub xSP, 10h
+
+%ifdef RT_ARCH_AMD64 ;; @todo there is probably some sse instruction for this.
+ movss [xSP], xmm0
+ fld dword [xSP]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ; Make it round up by modifying the fpu control word.
+ fstcw [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or eax, 00800h
+ and eax, 0fbffh
+ mov [xBP - 08h], eax
+ fldcw [xBP - 08h]
+
+ ; Round ST(0) to integer.
+ frndint
+
+ ; Restore the fpu control word.
+ fldcw [xBP - 10h]
+
+%ifdef RT_ARCH_AMD64
+ fstp dword [xSP]
+ movss xmm0, [xSP]
+%endif
+ leave
+ ret
+ENDPROC RT_NOCRT(ceilf)
+
diff --git a/src/VBox/Runtime/common/math/ceill.asm b/src/VBox/Runtime/common/math/ceill.asm
new file mode 100644
index 00000000..3f5057d9
--- /dev/null
+++ b/src/VBox/Runtime/common/math/ceill.asm
@@ -0,0 +1,70 @@
+; $Id: ceill.asm $
+;; @file
+; IPRT - No-CRT ceill - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Compute the smallest integral value not less than lrd.
+; @returns st(0)
+; @param lrd [rbp + 8]
+RT_NOCRT_BEGINPROC ceill
+ push xBP
+ mov xBP, xSP
+ sub xSP, 10h
+
+ fld tword [xBP + xCB*2]
+
+ ; Make it round up by modifying the fpu control word.
+ fstcw [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or eax, 00800h
+ and eax, 0fbffh
+ mov [xBP - 08h], eax
+ fldcw [xBP - 08h]
+
+ ; Round ST(0) to integer.
+ frndint
+
+ ; Restore the fpu control word.
+ fldcw [xBP - 10h]
+
+ leave
+ ret
+ENDPROC RT_NOCRT(ceill)
+
diff --git a/src/VBox/Runtime/common/math/consts.c b/src/VBox/Runtime/common/math/consts.c
new file mode 100644
index 00000000..3711dc44
--- /dev/null
+++ b/src/VBox/Runtime/common/math/consts.c
@@ -0,0 +1,59 @@
+/* $Id: consts.c $ */
+/** @file
+ * IPRT - No-CRT - Math Constants.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#undef __infinity
+const union __infinity_un RT_NOCRT(__infinity) = { RTFLOAT64U_INIT_C(0, 0, RTFLOAT64U_EXP_MAX) };
+AssertCompile(sizeof(double) == sizeof(RTFLOAT64U));
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__infinity);
+
+#undef __nanf
+const union __nanf_un RT_NOCRT(__nanf) = { RTFLOAT32U_INIT(0, RT_BIT_32(RTFLOAT32U_FRACTION_BITS - 1) | 1, RTFLOAT32U_EXP_MAX) };
+AssertCompile(sizeof(float) == sizeof(RTFLOAT32U));
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL_WITHOUT_UNDERSCORE(__nanf);
+
diff --git a/src/VBox/Runtime/common/math/copysign.cpp b/src/VBox/Runtime/common/math/copysign.cpp
new file mode 100644
index 00000000..db9fb370
--- /dev/null
+++ b/src/VBox/Runtime/common/math/copysign.cpp
@@ -0,0 +1,63 @@
+/* $Id: copysign.cpp $ */
+/** @file
+ * IPRT - No-CRT - copysign().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+
+
+#undef copysign
+double RT_NOCRT(copysign)(double rdMagnitude, double rdSign)
+{
+ AssertCompile(sizeof(rdMagnitude) == sizeof(RTFLOAT64U));
+#ifndef RT_LITTLE_ENDIAN /* MSC outputs a lot better code for the alternative below. */
+ RTFLOAT64U uRet, uSign;
+ uSign.rd = rdSign;
+ uRet.rd = rdMagnitude;
+ uRet.s.fSign = uSign.s.fSign;
+#else
+ RTFLOAT64U uRet;
+ uRet.u = (*(uint64_t const *)&rdMagnitude & (RT_BIT_64(63) - 1U)) | (*(uint64_t const *)&rdSign & RT_BIT_64(63));
+#endif
+ return uRet.rd;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(copysign);
+
diff --git a/src/VBox/Runtime/common/math/copysignf.cpp b/src/VBox/Runtime/common/math/copysignf.cpp
new file mode 100644
index 00000000..6bc573be
--- /dev/null
+++ b/src/VBox/Runtime/common/math/copysignf.cpp
@@ -0,0 +1,63 @@
+/* $Id: copysignf.cpp $ */
+/** @file
+ * IPRT - No-CRT - copysignf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+
+
+#undef copysignf
+float RT_NOCRT(copysignf)(float r32Magnitude, float r32Sign)
+{
+ AssertCompile(sizeof(r32Magnitude) == sizeof(RTFLOAT32U));
+#ifndef RT_LITTLE_ENDIAN /* MSC outputs better code for the alternative below. */
+ RTFLOAT32U uRet, uSign;
+ uSign.r = r32Sign;
+ uRet.r = r32Magnitude;
+ uRet.s.fSign = uSign.s.fSign;
+#else
+ RTFLOAT32U uRet;
+ uRet.u = (*((uint32_t const *)&r32Magnitude) & (RT_BIT_32(31) - 1U)) | (*((uint32_t const *)&r32Sign) & RT_BIT_32(31));
+#endif
+ return uRet.r;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(copysignf);
+
diff --git a/src/VBox/Runtime/common/math/copysignl.cpp b/src/VBox/Runtime/common/math/copysignl.cpp
new file mode 100644
index 00000000..7b8b47f2
--- /dev/null
+++ b/src/VBox/Runtime/common/math/copysignl.cpp
@@ -0,0 +1,66 @@
+/* $Id: copysignl.cpp $ */
+/** @file
+ * IPRT - No-CRT - copysignl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+
+
+#undef copysignl
+long double RT_NOCRT(copysignl)(long double lrdMagnitude, long double lrdSign)
+{
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ RTFLOAT80U2 uRet, uSign;
+ uSign.lrd = lrdSign;
+ uRet.lrd = lrdMagnitude;
+ uRet.s.fSign = uSign.s.fSign;
+ return uRet.lrd;
+#else
+ AssertCompile(sizeof(lrdMagnitude) == sizeof(RTFLOAT64U));
+ RTFLOAT64U uRet, uSign;
+ uSign.rd = lrdSign;
+ uRet.rd = lrdMagnitude;
+ uRet.s.fSign = uSign.s.fSign;
+ return uRet.rd;
+#endif
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(copysignl);
+
diff --git a/src/VBox/Runtime/common/math/cos.asm b/src/VBox/Runtime/common/math/cos.asm
new file mode 100644
index 00000000..da83ef81
--- /dev/null
+++ b/src/VBox/Runtime/common/math/cos.asm
@@ -0,0 +1,213 @@
+; $Id: cos.asm $
+;; @file
+; IPRT - No-CRT cos - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Compute the cosine of rd, measured in radians.
+;
+; @returns st(0) / xmm0
+; @param rd [rbp + xCB*2] / xmm0
+;
+RT_NOCRT_BEGINPROC cos
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+%ifdef RT_OS_WINDOWS
+ ;
+ ; Make sure we use full precision and not the windows default of 53 bits.
+ ;
+;; @todo not sure if this makes any difference...
+ fnstcw [xBP - 20h]
+ mov ax, [xBP - 20h]
+ or ax, X86_FCW_PC_64 ; includes both bits, so no need to clear the mask.
+ mov [xBP - 1ch], ax
+ fldcw [xBP - 1ch]
+%endif
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd [xBP - 10h], xmm0
+ fld qword [xBP - 10h]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ;
+ ; The FCOS instruction has a very narrow range (-3pi/8 to 3pi/8) where it
+ ; works reliably, so outside that we'll use the FSIN instruction instead
+ ; as it has a larger good range (-5pi/4 to 1pi/4 for cosine).
+ ; Input conversion follows: cos(x) = sin(x + pi/2)
+ ;
+ ; We examin the input and weed out non-finit numbers first.
+ ;
+
+ ; We only do the range check on normal finite numbers.
+ fxam
+ fnstsw ax
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals - treat them as zero.
+ je .zero
+ cmp ax, X86_FSW_C0 ; NaN - must handle it special,
+ je .nan
+
+ ; Pass infinities and unsupported inputs to fcos, assuming it does the right thing.
+ ; We also jump here if we get a finite number in the "good" range, see below.
+.do_fcos:
+ fcos
+ jmp .return_val
+
+ ;
+ ; Finite number.
+ ;
+ ; First check if it's a very tiny number where we can simply return 1.
+ ; Next check if it's in the range where FCOS is reasonable, otherwise
+ ; go to FSIN to do the work.
+ ;
+.finite:
+ fld st0
+ fabs
+ fld qword [.s_r64TinyCosTo1 xWrtRIP]
+ fcomip st1
+ ja .zero_extra_pop
+
+.not_that_tiny_input:
+ fld qword [.s_r64FCosOkay xWrtRIP]
+ fcomip st1
+ ffreep st0 ; pop fabs(input)
+ ja .do_fcos ; jmp if fabs(input) < .s_r64FCosOkay
+
+ ;
+ ; If we have a positive number we subtract 3pi/2, for negative we add pi/2.
+ ; We still have the FXAM result in AX.
+ ;
+.outside_fcos_range:
+ test ax, X86_FSW_C1 ; The sign bit.
+ jnz .adjust_negative_to_sine
+
+ ; Calc -3pi/2 using FPU-internal pi constant.
+ fldpi
+ fadd st0, st0 ; st0=2pi
+ fldpi
+ fdiv qword [.s_r64Two xWrtRIP] ; st1=2pi; st0=pi/2
+ fsubp st1, st0 ; st0=3pi/2
+ fchs ; st0=-3pi/2
+ jmp .make_sine_adjustment
+
+.adjust_negative_to_sine:
+ ; Calc +pi/2.
+ fldpi
+ fdiv qword [.s_r64Two xWrtRIP] ; st1=2pi; st0=pi/2
+
+.make_sine_adjustment:
+ faddp st1, st0
+
+ ;
+ ; Call internal sine worker to calculate st0=sin(st0)
+ ;
+.do_sine:
+ mov ecx, 1 ; double
+ extern NAME(rtNoCrtMathSinCore)
+ call NAME(rtNoCrtMathSinCore)
+
+ ;
+ ; Return st0.
+ ;
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp qword [xBP - 10h]
+ movsd xmm0, [xBP - 10h]
+%endif
+%ifdef RT_OS_WINDOWS
+ fldcw [xBP - 20h] ; restore original
+%endif
+.return:
+ leave
+ ret
+
+ ;
+ ; cos(+/-0) = +1.0
+ ;
+.zero_extra_pop:
+ ffreep st0
+.zero:
+ ffreep st0
+ fld1
+ jmp .return_val
+
+ ;
+ ; Input is NaN, output it unmodified as far as we can (FLD changes SNaN
+ ; to QNaN when masked).
+ ;
+.nan:
+%ifdef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ jmp .return
+
+ ;
+ ; Local constants.
+ ;
+ALIGNCODE(8)
+ ; About 2**-27. When fabs(input) is below this limit we can consider cos(input) ~= 1.0.
+.s_r64TinyCosTo1:
+ dq 7.4505806e-9
+
+ ; The absolute limit for the range which FCOS is expected to produce reasonable results.
+.s_r64FCosOkay:
+ dq 1.1780972450961724644225 ; 3*pi/8
+
+.s_r64Two:
+ dq 2.0
+ENDPROC RT_NOCRT(cos)
+
diff --git a/src/VBox/Runtime/common/math/cosf.asm b/src/VBox/Runtime/common/math/cosf.asm
new file mode 100644
index 00000000..5f47ba14
--- /dev/null
+++ b/src/VBox/Runtime/common/math/cosf.asm
@@ -0,0 +1,213 @@
+; $Id: cosf.asm $
+;; @file
+; IPRT - No-CRT cosf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Compute the cosine of rf, measured in radians.
+;
+; @returns st(0) / xmm0
+; @param rf [rbp + xCB*2] / xmm0
+;
+RT_NOCRT_BEGINPROC cosf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+%ifdef RT_OS_WINDOWS
+ ;
+ ; Make sure we use full precision and not the windows default of 53 bits.
+ ;
+;; @todo not sure if this makes any difference...
+ fnstcw [xBP - 20h]
+ mov ax, [xBP - 20h]
+ or ax, X86_FCW_PC_64 ; includes both bits, so no need to clear the mask.
+ mov [xBP - 1ch], ax
+ fldcw [xBP - 1ch]
+%endif
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movss [xBP - 10h], xmm0
+ fld dword [xBP - 10h]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ;
+ ; The FCOS instruction has a very narrow range (-3pi/8 to 3pi/8) where it
+ ; works reliably, so outside that we'll use the FSIN instruction instead
+ ; as it has a larger good range (-5pi/4 to 1pi/4 for cosine).
+ ; Input conversion follows: cosf(x) = sinf(x + pi/2)
+ ;
+ ; We examin the input and weed out non-finit numbers first.
+ ;
+
+ ; We only do the range check on normal finite numbers.
+ fxam
+ fnstsw ax
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals - treat them as zero.
+ je .zero
+ cmp ax, X86_FSW_C0 ; NaN - must handle it special,
+ je .nan
+
+ ; Pass infinities and unsupported inputs to fcos, assuming it does the right thing.
+ ; We also jump here if we get a finite number in the "good" range, see below.
+.do_fcos:
+ fcos
+ jmp .return_val
+
+ ;
+ ; Finite number.
+ ;
+ ; First check if it's a very tiny number where we can simply return 1.
+ ; Next check if it's in the range where FCOS is reasonable, otherwise
+ ; go to FSIN to do the work.
+ ;
+.finite:
+ fld st0
+ fabs
+ fld qword [.s_r64TinyCosTo1 xWrtRIP]
+ fcomip st1
+ ja .zero_extra_pop
+
+.not_that_tiny_input:
+ fld qword [.s_r64FCosOkay xWrtRIP]
+ fcomip st1
+ ffreep st0 ; pop fabs(input)
+ ja .do_fcos ; jmp if fabs(input) < .s_r64FCosOkay
+
+ ;
+ ; If we have a positive number we subtract 3pi/2, for negative we add pi/2.
+ ; We still have the FXAM result in AX.
+ ;
+.outside_fcos_range:
+ test ax, X86_FSW_C1 ; The sign bit.
+ jnz .adjust_negative_to_sine
+
+ ; Calc -3pi/2 using FPU-internal pi constant.
+ fldpi
+ fadd st0, st0 ; st0=2pi
+ fldpi
+ fdiv qword [.s_r64Two xWrtRIP] ; st1=2pi; st0=pi/2
+ fsubp st1, st0 ; st0=3pi/2
+ fchs ; st0=-3pi/2
+ jmp .make_sine_adjustment
+
+.adjust_negative_to_sine:
+ ; Calc +pi/2.
+ fldpi
+ fdiv qword [.s_r64Two xWrtRIP] ; st1=2pi; st0=pi/2
+
+.make_sine_adjustment:
+ faddp st1, st0
+
+ ;
+ ; Call internal sine worker to calculate st0=sin(st0)
+ ;
+.do_sine:
+ mov ecx, 0 ; double
+ extern NAME(rtNoCrtMathSinCore)
+ call NAME(rtNoCrtMathSinCore)
+
+ ;
+ ; Return st0.
+ ;
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp dword [xBP - 10h]
+ movss xmm0, [xBP - 10h]
+%endif
+%ifdef RT_OS_WINDOWS
+ fldcw [xBP - 20h] ; restore original
+%endif
+.return:
+ leave
+ ret
+
+ ;
+ ; cosf(+/-0) = +1.0
+ ;
+.zero_extra_pop:
+ ffreep st0
+.zero:
+ ffreep st0
+ fld1
+ jmp .return_val
+
+ ;
+ ; Input is NaN, output it unmodified as far as we can (FLD changes SNaN
+ ; to QNaN when masked).
+ ;
+.nan:
+%ifdef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ jmp .return
+
+ ;
+ ; Local constants.
+ ;
+ALIGNCODE(8)
+ ; About 2**-18. When fabs(input) is below this limit we can consider cosf(input) ~= 1.0.
+.s_r64TinyCosTo1:
+ dq 0.000244140625
+
+ ; The absolute limit for the range which FCOS is expected to produce reasonable results.
+.s_r64FCosOkay:
+ dq 1.1780972450961724644225 ; 3*pi/8
+
+.s_r64Two:
+ dq 2.0
+ENDPROC RT_NOCRT(cosf)
+
diff --git a/src/VBox/Runtime/common/math/cosl.asm b/src/VBox/Runtime/common/math/cosl.asm
new file mode 100644
index 00000000..524789f8
--- /dev/null
+++ b/src/VBox/Runtime/common/math/cosl.asm
@@ -0,0 +1,72 @@
+; $Id: cosl.asm $
+;; @file
+; IPRT - No-CRT cosl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; compute the cosine of ldr, measured in radians.
+; @returns st(0)
+; @param lrd [rbp + xCB*2]
+RT_NOCRT_BEGINPROC cosl
+ push xBP
+ mov xBP, xSP
+ sub xSP, 10h
+
+ fld tword [xBP + xCB*2]
+ fcos
+ fnstsw ax
+ test ah, 4
+ jz .done
+
+ fldpi
+ fadd st0, st0
+ fxch st1
+.again:
+ fprem1
+ fnstsw ax
+ test ah, 4
+ jnz .again
+
+ fstp st0
+ fcos
+
+.done:
+ leave
+ ret
+ENDPROC RT_NOCRT(cosl)
+
diff --git a/src/VBox/Runtime/common/math/exp.asm b/src/VBox/Runtime/common/math/exp.asm
new file mode 100644
index 00000000..4e954164
--- /dev/null
+++ b/src/VBox/Runtime/common/math/exp.asm
@@ -0,0 +1,151 @@
+; $Id: exp.asm $
+;; @file
+; IPRT - No-CRT exp - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+extern NAME(RT_NOCRT(feraiseexcept))
+
+;;
+; Compute the e (2.7182818...) to the power of rd.
+; @returns st(0) / xmm0
+; @param rd [xSP + xCB*2] / xmm0
+RT_NOCRT_BEGINPROC exp
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd [xBP - 10h], xmm0
+ fld qword [xBP - 10h]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Weed out non-normal values.
+ ;
+ fxam
+ fnstsw ax
+ mov cx, ax
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals
+ je .finite
+ cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity.
+ je .inf
+ jmp .nan
+
+.finite:
+ ;
+ ; Convert to power of 2 and it'll be the same as exp2.
+ ;
+ fldl2e ; -> st0=log2(e); st1=input
+ fmulp ; -> st0=input*log2(e)
+
+ ;
+ ; Split the job in two on the fraction and integer input parts.
+ ;
+ fld st0 ; Push a copy of the input on the stack.
+ frndint ; st0 = (int)(input*log2(e))
+ fsub st1, st0 ; st1 = input*log2(e) - (int)input*log2(e); i.e. st1 = fraction, st0 = integer.
+ fxch ; st0 = fraction, st1 = integer.
+
+ ; 1. Calculate on the fraction.
+ f2xm1 ; st0 = 2**fraction - 1.0
+ fld1
+ faddp ; st0 = 2**fraction
+
+ ; 2. Apply the integer power of two.
+ fscale ; st0 = result; st1 = integer part of input.
+ fstp st1 ; st0 = result; no st1.
+
+ ;
+ ; Return st0.
+ ;
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp qword [xBP - 10h]
+ movsd xmm0, [xBP - 10h]
+%endif
+.return:
+ leave
+ ret
+
+ ;
+ ; +/-0.0: Return +1.0
+ ;
+.zero:
+ ffreep st0
+ fld1
+ jmp .return_val
+
+ ;
+ ; -Inf: Return +0.0.
+ ; +Inf: Return +Inf. Join path with NaN.
+ ;
+.inf:
+ test cx, X86_FSW_C1 ; sign bit
+ jz .nan
+ ffreep st0
+ fldz
+ jmp .return_val
+
+ ;
+ ; NaN: Return the input NaN value as is, if we can.
+ ;
+.nan:
+%ifdef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ jmp .return
+ENDPROC RT_NOCRT(exp)
+
diff --git a/src/VBox/Runtime/common/math/exp2.asm b/src/VBox/Runtime/common/math/exp2.asm
new file mode 100644
index 00000000..dc818135
--- /dev/null
+++ b/src/VBox/Runtime/common/math/exp2.asm
@@ -0,0 +1,117 @@
+; $Id: exp2.asm $
+;; @file
+; IPRT - No-CRT exp2 - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+BEGINCODE
+
+;;
+; Calculate two to the power of @a rd.
+;
+; @returns st(0) / xmm0
+; @param rd [rbp + 8] / xmm0
+RT_NOCRT_BEGINPROC exp2
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the value into st(0).
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd [xSP], xmm0
+ fld qword [xSP]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Return immediately if NaN or infinity.
+ ;
+ fxam
+ fstsw ax
+ test ax, X86_FSW_C0 ; C0 is set for NaN, Infinity and Empty register. The latter is not the case.
+ jz .input_ok
+%ifdef RT_ARCH_AMD64
+ ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN.
+%endif
+ test ax, X86_FSW_C2 ; C2 is clear for NaN (and Empty) but set for Infinity.
+ jz .return_val2
+ test ax, X86_FSW_C1 ; C1 = sign bit
+ jz .return_val2 ; Not sign, return +Inf.
+%ifndef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ fldz ; Signed, so return zero as that's a good approximation for 2**-Inf.
+ jmp .return_val
+.input_ok:
+
+ ;
+ ; Split the job in two on the fraction and integer input parts.
+ ;
+ fld st0 ; Push a copy of the input on the stack.
+ frndint ; st0 = (int)input
+ fsub st1, st0 ; st1 = input - (int)input; i.e. st1 = fraction, st0 = integer.
+ fxch ; st0 = fraction, st1 = integer.
+
+ ; 1. Calculate on the fraction.
+ f2xm1 ; st0 = 2**fraction - 1.0
+ fld1
+ faddp ; st0 = 2**fraction
+
+ ; 2. Apply the integer power of two.
+ fscale ; st0 = result; st1 = integer part of input.
+ fstp st1 ; st0 = result; no st1.
+
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp qword [xSP]
+ movsd xmm0, [xSP]
+%endif
+.return_val2:
+ leave
+ ret
+ENDPROC RT_NOCRT(exp2)
+
diff --git a/src/VBox/Runtime/common/math/exp2f.asm b/src/VBox/Runtime/common/math/exp2f.asm
new file mode 100644
index 00000000..90b48c75
--- /dev/null
+++ b/src/VBox/Runtime/common/math/exp2f.asm
@@ -0,0 +1,117 @@
+; $Id: exp2f.asm $
+;; @file
+; IPRT - No-CRT exp2f - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+BEGINCODE
+
+;;
+; Calculate two to the power of @a r32.
+;
+; @returns st(0) / xmm0
+; @param r32 [rbp + 8] / xmm0
+RT_NOCRT_BEGINPROC exp2f
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the value into st(0).
+ ;
+%ifdef RT_ARCH_AMD64
+ movss [xSP], xmm0
+ fld dword [xSP]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Return immediately if NaN or infinity.
+ ;
+ fxam
+ fstsw ax
+ test ax, X86_FSW_C0 ; C0 is set for NaN, Infinity and Empty register. The latter is not the case.
+ jz .input_ok
+%ifdef RT_ARCH_AMD64
+ ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN.
+%endif
+ test ax, X86_FSW_C2 ; C2 is clear for NaN (and Empty) but set for Infinity.
+ jz .return_val2
+ test ax, X86_FSW_C1 ; C1 = sign bit
+ jz .return_val2 ; Not sign, return +Inf.
+%ifndef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ fldz ; Signed, so return zero as that's a good approximation for 2**-Inf.
+ jmp .return_val
+.input_ok:
+
+ ;
+ ; Split the job in two on the fraction and integer input parts.
+ ;
+ fld st0 ; Push a copy of the input on the stack.
+ frndint ; st0 = (int)input
+ fsub st1, st0 ; st1 = input - (int)input; i.e. st1 = fraction, st0 = integer.
+ fxch ; st0 = fraction, st1 = integer.
+
+ ; 1. Calculate on the fraction.
+ f2xm1 ; st0 = 2**fraction - 1.0
+ fld1
+ faddp ; st0 = 2**fraction
+
+ ; 2. Apply the integer power of two.
+ fscale ; st0 = result; st1 = integer part of input.
+ fstp st1 ; st0 = result; no st1.
+
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp dword [xSP]
+ movss xmm0, [xSP]
+%endif
+.return_val2:
+ leave
+ ret
+ENDPROC RT_NOCRT(exp2f)
+
diff --git a/src/VBox/Runtime/common/math/expf.asm b/src/VBox/Runtime/common/math/expf.asm
new file mode 100644
index 00000000..81d12434
--- /dev/null
+++ b/src/VBox/Runtime/common/math/expf.asm
@@ -0,0 +1,151 @@
+; $Id: expf.asm $
+;; @file
+; IPRT - No-CRT expf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+extern NAME(RT_NOCRT(feraiseexcept))
+
+;;
+; Compute the e (2.7182818...) to the power of rd.
+; @returns st(0) / xmm0
+; @param rd [xSP + xCB*2] / xmm0
+RT_NOCRT_BEGINPROC expf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movss [xBP - 10h], xmm0
+ fld dword [xBP - 10h]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Weed out non-normal values.
+ ;
+ fxam
+ fnstsw ax
+ mov cx, ax
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals
+ je .finite
+ cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity.
+ je .inf
+ jmp .nan
+
+.finite:
+ ;
+ ; Convert to power of 2 and it'll be the same as exp2.
+ ;
+ fldl2e ; -> st0=log2(e); st1=input
+ fmulp ; -> st0=input*log2(e)
+
+ ;
+ ; Split the job in two on the fraction and integer input parts.
+ ;
+ fld st0 ; Push a copy of the input on the stack.
+ frndint ; st0 = (int)(input*log2(e))
+ fsub st1, st0 ; st1 = input*log2(e) - (int)input*log2(e); i.e. st1 = fraction, st0 = integer.
+ fxch ; st0 = fraction, st1 = integer.
+
+ ; 1. Calculate on the fraction.
+ f2xm1 ; st0 = 2**fraction - 1.0
+ fld1
+ faddp ; st0 = 2**fraction
+
+ ; 2. Apply the integer power of two.
+ fscale ; st0 = result; st1 = integer part of input.
+ fstp st1 ; st0 = result; no st1.
+
+ ;
+ ; Return st0.
+ ;
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp dword [xBP - 10h]
+ movss xmm0, [xBP - 10h]
+%endif
+.return:
+ leave
+ ret
+
+ ;
+ ; +/-0.0: Return +1.0
+ ;
+.zero:
+ ffreep st0
+ fld1
+ jmp .return_val
+
+ ;
+ ; -Inf: Return +0.0.
+ ; +Inf: Return +Inf. Join path with NaN.
+ ;
+.inf:
+ test cx, X86_FSW_C1 ; sign bit
+ jz .nan
+ ffreep st0
+ fldz
+ jmp .return_val
+
+ ;
+ ; NaN: Return the input NaN value as is, if we can.
+ ;
+.nan:
+%ifdef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ jmp .return
+ENDPROC RT_NOCRT(expf)
+
diff --git a/src/VBox/Runtime/common/math/fabs.asm b/src/VBox/Runtime/common/math/fabs.asm
new file mode 100644
index 00000000..a8e69d28
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fabs.asm
@@ -0,0 +1,73 @@
+; $Id: fabs.asm $
+;; @file
+; IPRT - No-CRT fabs - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Compute the absolute value of rd (|rd|).
+; @returns 32-bit: st(0) 64-bit: xmm0
+; @param rd 32-bit: [ebp + 8] 64-bit: xmm0
+RT_NOCRT_BEGINPROC fabs
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ andps xmm0, [g_r64ClearSignMask xWrtRIP]
+%else
+ fld qword [xBP + xCB*2] ; This turns SNaN into QNaN.
+ fabs
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(fabs)
+
+ALIGNCODE(16)
+g_r64ClearSignMask:
+ dd 0ffffffffh
+ dd 07fffffffh
+
+ dd 0ffffffffh
+ dd 07fffffffh
+
diff --git a/src/VBox/Runtime/common/math/fabsf.asm b/src/VBox/Runtime/common/math/fabsf.asm
new file mode 100644
index 00000000..840c89b8
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fabsf.asm
@@ -0,0 +1,72 @@
+; $Id: fabsf.asm $
+;; @file
+; IPRT - No-CRT fabsf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Compute the absolute value of rf (|rf|).
+; @returns 32-bit: st(0) 64-bit: xmm0
+; @param rf 32-bit: [ebp + 8] 64-bit: xmm0
+RT_NOCRT_BEGINPROC fabsf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ andps xmm0, [g_r32ClearSignMask xWrtRIP]
+%else
+ fld dword [xBP + xCB*2] ; This turns SNaN into QNaN.
+ fabs
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(fabsf)
+
+ALIGNCODE(16)
+g_r32ClearSignMask:
+ dd 07fffffffh
+ dd 07fffffffh
+ dd 07fffffffh
+ dd 07fffffffh
+
diff --git a/src/VBox/Runtime/common/math/fabsl.asm b/src/VBox/Runtime/common/math/fabsl.asm
new file mode 100644
index 00000000..d2781de3
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fabsl.asm
@@ -0,0 +1,61 @@
+; $Id: fabsl.asm $
+;; @file
+; IPRT - No-CRT fabsl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Compute the absolute value of lrd (|lrd|).
+; @returns st(0)
+; @param lrd [xSP + xCB*2]
+RT_NOCRT_BEGINPROC fabsl
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+ fld tword [xBP + xCB*2]
+ fabs
+
+ leave
+ ret
+ENDPROC RT_NOCRT(fabsl)
+
diff --git a/src/VBox/Runtime/common/math/feclearexcept.asm b/src/VBox/Runtime/common/math/feclearexcept.asm
new file mode 100644
index 00000000..5781d99b
--- /dev/null
+++ b/src/VBox/Runtime/common/math/feclearexcept.asm
@@ -0,0 +1,121 @@
+; $Id: feclearexcept.asm $
+;; @file
+; IPRT - No-CRT feclearexcept - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Sets the hardware rounding mode.
+;
+; @returns eax = 0 on success, non-zero on failure.
+; @param fXcpts 32-bit: [xBP+8]; msc64: ecx; gcc64: edi; -- Zero or more bits from X86_FSW_XCPT_MASK
+;
+RT_NOCRT_BEGINPROC feclearexcept
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into ecx, validate and adjust it.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov ecx, edi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+%if 0
+ and ecx, X86_FSW_XCPT_MASK
+%else
+ or eax, -1
+ test ecx, ~X86_FSW_XCPT_MASK
+ jnz .return
+%endif
+
+ ; #IE implies #SF
+ mov al, cl
+ and al, X86_FSW_IE
+ shl al, X86_FSW_SF_BIT - X86_FSW_IE_BIT
+ or cl, al
+
+ ; Make it into and AND mask suitable for clearing the specified exceptions.
+ not ecx
+
+ ;
+ ; Make the changes.
+ ;
+
+ ; Modify the x87 flags first (ecx preserved).
+ cmp ecx, ~X86_FSW_XCPT_MASK ; This includes all the x87 exceptions, including stack error.
+ jne .partial_mask
+ fnclex
+ jmp .do_sse
+
+.partial_mask:
+ fnstenv [xBP - 20h]
+ and word [xBP - 20h + 4], cx ; The FCW is at offset 4 in the 32-bit prot mode layout
+ fldenv [xBP - 20h] ; Recalculates the FSW.ES flag.
+.do_sse:
+
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ jz .return_ok
+%endif
+
+ ; Modify the SSE flags (modifies ecx).
+ stmxcsr [xBP - 10h]
+ or ecx, X86_FSW_XCPT_MASK & ~X86_MXCSR_XCPT_FLAGS ; Don't mix X86_FSW_SF with X86_MXCSR_DAZ.
+ and [xBP - 10h], ecx
+ ldmxcsr [xBP - 10h]
+
+.return_ok:
+ xor eax, eax
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(feclearexcept)
+
diff --git a/src/VBox/Runtime/common/math/fedisableexcept.asm b/src/VBox/Runtime/common/math/fedisableexcept.asm
new file mode 100644
index 00000000..3fb339c3
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fedisableexcept.asm
@@ -0,0 +1,117 @@
+; $Id: fedisableexcept.asm $
+;; @file
+; IPRT - No-CRT fedisableexcept - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Enables a set of exceptions (BSD/GNU extension).
+;
+; @returns eax = Previous enabled exceptions on success (not subject to fXcpt),
+; -1 on failure.
+; @param fXcpt 32-bit: [xBP+8]; msc64: ecx; gcc64: edi; -- Mask of exceptions to disable.
+;
+RT_NOCRT_BEGINPROC fedisableexcept
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into ecx.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov ecx, edi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+ or eax, -1
+ test ecx, ~X86_FCW_XCPT_MASK
+%ifndef RT_STRICT
+ jnz .return
+%else
+ jz .input_ok
+ int3
+ jmp .return
+.input_ok:
+%endif
+
+ ;
+ ; Make the changes (old mask in eax).
+ ;
+
+ ; Modify the x87 mask first (ecx preserved).
+ fstcw [xBP - 10h]
+%ifdef RT_ARCH_X86 ; Return the inverted x87 mask in 32-bit mode.
+ movzx eax, word [xBP - 10h]
+%endif
+ or word [xBP - 10h], cx
+ fldcw [xBP - 10h]
+
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ jz .return_ok
+%endif
+
+ ; Modify the SSE mask (modifies ecx).
+ stmxcsr [xBP - 10h]
+%ifdef RT_ARCH_AMD64 ; Return the inverted MXCSR exception mask on AMD64 because windows doesn't necessarily set the x87 one.
+ mov eax, [xBP - 10h]
+ shr eax, X86_MXCSR_XCPT_MASK_SHIFT
+%endif
+ shl ecx, X86_MXCSR_XCPT_MASK_SHIFT
+ or [xBP - 10h], ecx
+ ldmxcsr [xBP - 10h]
+
+.return_ok:
+ not eax ; Invert it as we return the enabled rather than masked exceptions.
+ and eax, X86_FCW_XCPT_MASK
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(fedisableexcept)
+
diff --git a/src/VBox/Runtime/common/math/feenableexcept.asm b/src/VBox/Runtime/common/math/feenableexcept.asm
new file mode 100644
index 00000000..6f874ee7
--- /dev/null
+++ b/src/VBox/Runtime/common/math/feenableexcept.asm
@@ -0,0 +1,121 @@
+; $Id: feenableexcept.asm $
+;; @file
+; IPRT - No-CRT feenableexcept - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Enables a set of exceptions (BSD/GNU extension).
+;
+; @returns eax = Previous enabled exceptions on success (not subject to fXcpt),
+; -1 on failure.
+; @param fXcpt 32-bit: [xBP+8] msc64: ecx gcc64: edi - Mask of exceptions to enable.
+;
+RT_NOCRT_BEGINPROC feenableexcept
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into ecx.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov ecx, edi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+ or eax, -1
+ test ecx, ~X86_FCW_XCPT_MASK
+%ifndef RT_STRICT
+ jnz .return
+%else
+ jz .input_ok
+ int3
+ jmp .return
+.input_ok:
+%endif
+
+ ; Invert the mask as we're enabling the exceptions, not masking them.
+ not ecx
+
+ ;
+ ; Make the changes (old mask in eax).
+ ;
+
+ ; Modify the x87 mask first (ecx preserved).
+ fstcw [xBP - 10h]
+%ifdef RT_ARCH_X86 ; Return the inverted x87 mask in 32-bit mode.
+ mov ax, word [xBP - 10h]
+ and eax, X86_FCW_XCPT_MASK
+%endif
+ and word [xBP - 10h], cx
+ fldcw [xBP - 10h]
+
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ jz .return_ok
+%endif
+
+ ; Modify the SSE mask (modifies ecx).
+ stmxcsr [xBP - 10h]
+%ifdef RT_ARCH_AMD64 ; Return the inverted MXCSR exception mask on AMD64 because windows doesn't necessarily set the x87 one.
+ mov eax, [xBP - 10h]
+ and eax, X86_MXCSR_XCPT_MASK
+ shr eax, X86_MXCSR_XCPT_MASK_SHIFT
+%endif
+ rol ecx, X86_MXCSR_XCPT_MASK_SHIFT
+ and [xBP - 10h], ecx
+ ldmxcsr [xBP - 10h]
+
+.return_ok:
+ not eax ; Invert it as we return the enabled rather than masked exceptions.
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(feenableexcept)
+
diff --git a/src/VBox/Runtime/common/math/fegetenv.asm b/src/VBox/Runtime/common/math/fegetenv.asm
new file mode 100644
index 00000000..073e64f4
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fegetenv.asm
@@ -0,0 +1,90 @@
+; $Id: fegetenv.asm $
+;; @file
+; IPRT - No-CRT fegetenv - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Gets the FPU+SSE environment.
+;
+; @returns eax = 0 on success (-1 on failure),
+; @param pEnv 32-bit: [xBP+8]; msc64: rcx; gcc64: rdi -- Pointer to where to store the enviornment.
+;
+RT_NOCRT_BEGINPROC fegetenv
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into rcx.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov rcx, rdi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+
+ ;
+ ; Save the FPU environment and MXCSR.
+ ;
+ fnstenv [xCX]
+
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ and dword [xCX + 28], 0h
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ jz .return_nosse
+%endif
+ stmxcsr [xCX + 28]
+.return_nosse:
+
+ ;
+ ; Return success.
+ ;
+ xor eax, eax
+ leave
+ ret
+ENDPROC RT_NOCRT(fegetenv)
+
diff --git a/src/VBox/Runtime/common/math/fegetexcept.asm b/src/VBox/Runtime/common/math/fegetexcept.asm
new file mode 100644
index 00000000..e7cc35cd
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fegetexcept.asm
@@ -0,0 +1,82 @@
+; $Id: fegetexcept.asm $
+;; @file
+; IPRT - No-CRT fegetexcept - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Gets the mask of enabled exceptions, e.g. unmasked (BSD/GNU extension).
+;
+; @returns eax = inverted x87/sse exception mask (X86_MXCSR_XCPT_FLAGS).
+; Will not return X86_FSW_SF.
+;
+RT_NOCRT_BEGINPROC fegetexcept
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Save control word and isolate the exception mask.
+ ;
+ ; On 64-bit we'll use the MXCSR since the windows compiler/CRT doesn't
+ ; necessarily keep them in sync. We'll still return the x87-style flags.
+ ;
+%ifdef RT_ARCH_AMD64
+ stmxcsr [xBP - 10h]
+ mov eax, [xBP - 10h]
+ shr eax, X86_MXCSR_XCPT_MASK_SHIFT
+%else
+ fstcw [xBP - 10h]
+ movzx eax, word [xBP - 10h]
+%endif
+
+ not eax ; Invert it as we return the enabled rather than masked exceptions.
+ and eax, X86_MXCSR_XCPT_FLAGS ; Use the SSE mask so we don't return X86_FSW_SF here.
+
+.return_val:
+ leave
+ ret
+ENDPROC RT_NOCRT(fegetexcept)
+
diff --git a/src/VBox/Runtime/common/math/fegetexceptflag.asm b/src/VBox/Runtime/common/math/fegetexceptflag.asm
new file mode 100644
index 00000000..015565f2
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fegetexceptflag.asm
@@ -0,0 +1,117 @@
+; $Id: fegetexceptflag.asm $
+;; @file
+; IPRT - No-CRT fegetexceptflag - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Gets the pending exceptions.
+;
+; @returns eax = 0 on success, non-zero on failure.
+; @param pfXcpts 32-bit: [xBP+8]; msc64: rcx; gcc64: rdi; -- Where to store the flags (pointer to fexcept_t (16-bit)).
+; @param fXcptMask 32-bit: [xBP+c]; msc64: edx; gcc64: esi; -- The exception flags to get (X86_FSW_XCPT_MASK).
+; Accepts X86_FSW_SF and will return it if given as input.
+;
+RT_NOCRT_BEGINPROC fegetexceptflag
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into rcx (pfXcpts) and edx (fXcptMask).
+ ;
+%ifdef ASM_CALL64_GCC
+ mov rcx, rdi
+ mov edx, esi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+ mov edx, [xBP + xCB*3]
+%endif
+%if 0
+ and edx, X86_FSW_XCPT_MASK
+%else
+ or eax, -1
+ test edx, ~X86_FSW_XCPT_MASK
+ %ifndef RT_STRICT
+ jnz .return
+ %else
+ jz .input_ok
+ int3
+ jmp .return
+.input_ok:
+ %endif
+%endif
+
+ ;
+ ; Get the pending exceptions.
+ ;
+
+ ; x87.
+ fnstsw ax
+ and ax, dx
+ mov [xCX], ax
+
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ jz .return_ok
+%endif
+
+ ; Modify the SSE flags.
+ stmxcsr [xBP - 10h]
+ mov ax, [xBP - 10h]
+ and ax, dx
+ and ax, X86_MXCSR_XCPT_FLAGS ; Don't confuse X86_MXCSR_DAZ for X86_FSW_SF.
+ or [xCX], ax
+
+.return_ok:
+ xor eax, eax
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(fegetexceptflag)
+
diff --git a/src/VBox/Runtime/common/math/fegetround.asm b/src/VBox/Runtime/common/math/fegetround.asm
new file mode 100644
index 00000000..3da8da75
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fegetround.asm
@@ -0,0 +1,79 @@
+; $Id: fegetround.asm $
+;; @file
+; IPRT - No-CRT fegetround - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Gets the hardware rounding mode.
+; @returns eax x87 rounding mask (X86_FCW_RC_MASK)
+;
+RT_NOCRT_BEGINPROC fegetround
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Save control word and isolate the rounding mode.
+ ;
+ ; On 64-bit we'll use the MXCSR since the windows compiler/CRT doesn't
+ ; necessarily keep them in sync. We'll still return the x87-style flags.
+ ;
+%ifdef RT_ARCH_AMD64
+ stmxcsr [xBP - 10h]
+ mov eax, [xBP - 10h]
+ and eax, X86_MXCSR_RC_MASK
+ shr eax, X86_MXCSR_RC_SHIFT - X86_FCW_RC_SHIFT
+%else
+ fstcw [xBP - 10h]
+ movzx eax, word [xBP - 10h]
+ and eax, X86_FCW_RC_MASK
+%endif
+
+.return_val:
+ leave
+ ret
+ENDPROC RT_NOCRT(fegetround)
+
diff --git a/src/VBox/Runtime/common/math/fegetx87precision.asm b/src/VBox/Runtime/common/math/fegetx87precision.asm
new file mode 100644
index 00000000..66c13074
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fegetx87precision.asm
@@ -0,0 +1,70 @@
+; $Id: fegetx87precision.asm $
+;; @file
+; IPRT - No-CRT fegetx87precision - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Gets the x87 hardware precision mode - IPRT extension.
+;
+; @returns eax = precision mode, -1 on failure.
+;
+RT_NOCRT_BEGINPROC fegetx87precision
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Extract the current from the x87 FCW and return it.
+ ;
+ fnstcw [xBP - 10h]
+ mov ax, [xBP - 10h]
+ and eax, X86_FCW_PC_MASK
+
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(fegetx87precision)
+
diff --git a/src/VBox/Runtime/common/math/feholdexcept.asm b/src/VBox/Runtime/common/math/feholdexcept.asm
new file mode 100644
index 00000000..106751b8
--- /dev/null
+++ b/src/VBox/Runtime/common/math/feholdexcept.asm
@@ -0,0 +1,99 @@
+; $Id: feholdexcept.asm $
+;; @file
+; IPRT - No-CRT feholdexcept - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Gets the FPU+SSE environment and disables (masks) all exceptions.
+;
+; @returns eax = 0 on success (-1 on failure)
+; @param pEnv 32-bit: [xBP+8] msc64: rcx gcc64: rdi
+;
+RT_NOCRT_BEGINPROC feholdexcept
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into rcx.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov rcx, rdi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+
+ ;
+ ; Save the FPU environment and MXCSR.
+ ;
+ fnstenv [xCX]
+ mov al, [xCX] ; Save FCW.
+ or byte [xCX], X86_FCW_MASK_ALL
+ fldcw [xCX]
+ mov [xCX], al ; Restore FCW.
+
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ and dword [xCX + 28], 0h
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ jz .return_nosse
+%endif
+ stmxcsr [xCX + 28]
+ mov eax, [xCX + 28] ; Save MXCSR.
+ or dword [xCX + 28], X86_MXCSR_XCPT_MASK
+ ldmxcsr [xCX + 28]
+ mov [xCX + 28], eax ; Restore MXCSR.
+
+.return_nosse:
+
+ ;
+ ; Return success.
+ ;
+ xor eax, eax
+ leave
+ ret
+ENDPROC RT_NOCRT(feholdexcept)
+
diff --git a/src/VBox/Runtime/common/math/feraiseexcept.asm b/src/VBox/Runtime/common/math/feraiseexcept.asm
new file mode 100644
index 00000000..12534848
--- /dev/null
+++ b/src/VBox/Runtime/common/math/feraiseexcept.asm
@@ -0,0 +1,188 @@
+; $Id: feraiseexcept.asm $
+;; @file
+; IPRT - No-CRT feraiseexcept - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+%ifdef RT_ARCH_AMD64
+ %define RT_NOCRT_RAISE_FPU_EXCEPT_IN_SSE_MODE
+%endif
+
+
+BEGINCODE
+
+;;
+; Raises the given FPU/SSE exceptions.
+;
+; @returns eax = 0 on success, -1 on failure.
+; @param fXcpt 32-bit: [xBP+8]; msc64: ecx; gcc64: edi; -- The exceptions to raise.
+; Accepts X86_FSW_XCPT_MASK, but ignores X86_FSW_DE and X86_FSW_SF.
+;
+RT_NOCRT_BEGINPROC feraiseexcept
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifndef RT_NOCRT_RAISE_FPU_EXCEPT_IN_SSE_MODE
+ sub xBP, 20h
+ SEH64_ALLOCATE_STACK 20h
+%endif
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into rcx.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov rcx, rdi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+%ifdef RT_STRICT
+ test ecx, ~X86_FSW_XCPT_MASK
+ jz .input_ok
+ int3
+.input_ok:
+%endif
+
+ ;
+ ; We have to raise these buggers one-by-one and order is said to be important.
+ ; We ASSUME that x86 runs is okay with the x87 raising the exception.
+ ;
+
+ ; 1. Invalid operation. Like +0.0 / +0.0.
+ test cl, X86_FSW_IE
+ jz .not_ie
+%ifdef RT_NOCRT_RAISE_FPU_EXCEPT_IN_SSE_MODE
+ movss xmm0, [g_r32Zero xWrtRIP]
+ divss xmm0, xmm0
+%else
+ fnstenv [xBP - 20h]
+ or byte [xBP - 20h + X86FSTENV32P.FSW], X86_FSW_IE
+ fldenv [xBP - 20h]
+ fwait
+%endif
+.not_ie:
+
+ ; 2. Division by zero.
+ test cl, X86_FSW_ZE
+ jz .not_ze
+%ifdef RT_NOCRT_RAISE_FPU_EXCEPT_IN_SSE_MODE
+ movss xmm0, [g_r32One xWrtRIP]
+ movss xmm1, [g_r32Zero xWrtRIP]
+ divss xmm0, xmm1
+%else
+ fnstenv [xBP - 20h]
+ or byte [xBP - 20h + X86FSTENV32P.FSW], X86_FSW_ZE
+ fldenv [xBP - 20h]
+ fwait
+%endif
+.not_ze:
+
+ ; 3. Overflow.
+ test cl, X86_FSW_OE
+ jz .not_oe
+%ifdef RT_NOCRT_RAISE_FPU_EXCEPT_IN_SSE_MODE
+ xorps xmm0, [g_r32Large xWrtRIP]
+ movss xmm1, [g_r32Tiny xWrtRIP]
+ divss xmm0, xmm1
+%else
+ fnstenv [xBP - 20h]
+ or byte [xBP - 20h + X86FSTENV32P.FSW], X86_FSW_OE
+ fldenv [xBP - 20h]
+ fwait
+%endif
+.not_oe:
+
+ ; 4. Underflow.
+ test cl, X86_FSW_UE
+ jz .not_ue
+%ifdef RT_NOCRT_RAISE_FPU_EXCEPT_IN_SSE_MODE
+ xorps xmm0, [g_r32Tiny xWrtRIP]
+ movss xmm1, [g_r32Large xWrtRIP]
+ divss xmm0, xmm1
+%else
+ fnstenv [xBP - 20h]
+ or byte [xBP - 20h + X86FSTENV32P.FSW], X86_FSW_UE
+ fldenv [xBP - 20h]
+ fwait
+%endif
+.not_ue:
+
+ ; 5. Precision.
+ test cl, X86_FSW_PE
+ jz .not_pe
+%ifdef RT_NOCRT_RAISE_FPU_EXCEPT_IN_SSE_MODE
+ xorps xmm0, [g_r32Two xWrtRIP]
+ movss xmm1, [g_r32Three xWrtRIP]
+ divss xmm0, xmm1
+%else
+ fnstenv [xBP - 20h]
+ or byte [xBP - 20h + X86FSTENV32P.FSW], X86_FSW_PE
+ fldenv [xBP - 20h]
+ fwait
+%endif
+.not_pe:
+
+ ; We currently do not raise X86_FSW_DE or X86_FSW_SF.
+
+ ;
+ ; Return success.
+ ;
+ xor eax, eax
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(feraiseexcept)
+
+
+%ifdef RT_NOCRT_RAISE_FPU_EXCEPT_IN_SSE_MODE
+g_r32Zero:
+ dd 0.0
+g_r32One:
+ dd 1.0
+g_r32Two:
+ dd 2.0
+g_r32Three:
+ dd 3.0
+g_r32Large:
+ dd 1.0e+38
+g_r32Tiny:
+ dd 1.0e-37
+%endif
+
diff --git a/src/VBox/Runtime/common/math/fesetenv.asm b/src/VBox/Runtime/common/math/fesetenv.asm
new file mode 100644
index 00000000..394d3fce
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fesetenv.asm
@@ -0,0 +1,194 @@
+; $Id: fesetenv.asm $
+;; @file
+; IPRT - No-CRT fesetenv - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+%define RT_NOCRT_FE_DFL_ENV 1
+%define RT_NOCRT_FE_NOMASK_ENV 2
+%define RT_NOCRT_FE_PC53_ENV 3
+%define RT_NOCRT_FE_PC64_ENV 4
+%define RT_NOCRT_FE_LAST_ENV 4
+
+
+BEGINCODE
+
+;;
+; Sets the FPU+SSE environment.
+;
+; @returns eax = 0 on success, -1 on failure.
+; @param pEnv 32-bit: [xBP+8] msc64: rcx gcc64: rdi - Saved environment to restore.
+;
+RT_NOCRT_BEGINPROC fesetenv
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xBP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into rcx.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov rcx, rdi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+
+ ;
+ ; For the x87 state we only set FSW.XCPT, FCW.XCPT, FCW.RC and FCW.PC.
+ ; So we save the current environment, merge those fields and load it.
+ ;
+ fnstenv [xBP - 20h]
+
+ ; Check for special "pointer" values:
+ cmp xCX, RT_NOCRT_FE_LAST_ENV
+ ja .x87_regular
+
+ or eax, -1
+ test xCX, xCX
+ jnz .x87_special
+%ifdef RT_STRICT
+ int3
+%endif
+ jmp .return
+
+ ;
+ ; Special x87 state. Clear all pending exceptions.
+ ;
+ ; We have 4 special environments with only some differences in FCW differs, so set
+ ; up FCW in AX, starting with a NOMASK environment as it has the fewest bits set.
+ ;
+.x87_special:
+ and word [xBP - 20h + X86FSTENV32P.FSW], ~X86_FSW_XCPT_ES_MASK
+ mov ax, [xBP - 20h + X86FSTENV32P.FCW]
+ and ax, ~(X86_FCW_MASK_ALL | X86_FCW_PC_MASK | X86_FCW_RC_MASK | X86_FCW_IC_MASK)
+%ifdef RT_OS_WINDOWS
+ or ax, X86_FCW_DM | X86_FCW_PC_53 | X86_FCW_RC_NEAREST | X86_FCW_IC_PROJECTIVE
+%else
+ or ax, X86_FCW_DM | X86_FCW_PC_64 | X86_FCW_RC_NEAREST | X86_FCW_IC_PROJECTIVE
+%endif
+ cmp xCX, RT_NOCRT_FE_NOMASK_ENV
+ je .x87_special_done
+ or ax, X86_FCW_MASK_ALL
+
+%ifdef RT_OS_WINDOWS
+ cmp xCX, RT_NOCRT_FE_PC64_ENV
+ jne .x87_special_done
+ or ax, X86_FCW_PC_64 ; X86_FCW_PC_64 is a super set of X86_FCW_PC_53, so no need to clear bits
+%else
+ cmp xCX, RT_NOCRT_FE_PC53_ENV
+ jne .x87_special_done
+ and ax, X86_FCW_PC_64 & ~X86_FCW_PC_53 ; X86_FCW_PC_64 is a super set of X86_FCW_PC_53, so clear the bit that differs.
+%endif
+
+.x87_special_done:
+ mov [xBP - 20h + X86FSTENV32P.FCW], ax
+ jmp .x87_common
+
+ ;
+ ; Merge input and current.
+ ;
+.x87_regular:
+ ; FCW:
+ mov ax, [xCX + X86FSTENV32P.FCW]
+ mov dx, [xBP - 20h + X86FSTENV32P.FCW]
+ and ax, X86_FCW_MASK_ALL | X86_FCW_RC_MASK | X86_FCW_PC_MASK
+ and dx, ~(X86_FCW_MASK_ALL | X86_FCW_RC_MASK | X86_FCW_PC_MASK)
+ or dx, ax
+ mov [xBP - 20h + X86FSTENV32P.FCW], dx
+ ; FSW
+ mov ax, [xCX + X86FSTENV32P.FSW]
+ mov dx, [xBP - 20h + X86FSTENV32P.FSW]
+ and ax, X86_FSW_XCPT_MASK
+ and dx, ~(X86_FSW_XCPT_MASK)
+ or dx, ax
+ mov [xBP - 20h + X86FSTENV32P.FSW], dx
+
+.x87_common:
+ ; Clear the exception info.
+ xor eax, eax
+ mov [xBP - 20h + X86FSTENV32P.FPUIP], eax
+ mov [xBP - 20h + X86FSTENV32P.FPUCS], eax ; covers FOP too.
+ mov [xBP - 20h + X86FSTENV32P.FPUDP], eax
+ mov [xBP - 20h + X86FSTENV32P.FPUDS], eax
+
+ ; Load the merged and cleaned up environment.
+ fldenv [xBP - 20h]
+
+
+ ;
+ ; Now for SSE, if supported, where we'll restore everything as is.
+ ;
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ jz .return_okay
+%endif
+
+ cmp xCX, RT_NOCRT_FE_LAST_ENV
+ jb .sse_special_env
+ ldmxcsr [xCX + 28]
+ jmp .return_okay
+
+.sse_special_env:
+ stmxcsr [xBP - 10h]
+ mov eax, [xBP - 10h]
+ and eax, ~(X86_MXCSR_XCPT_FLAGS | X86_MXCSR_XCPT_MASK | X86_MXCSR_RC_MASK | X86_MXCSR_DAZ | X86_MXCSR_FZ)
+ or eax, X86_MXCSR_RC_NEAREST | X86_MXCSR_DM
+ cmp xCX, RT_NOCRT_FE_NOMASK_ENV ; Only the NOMASK one differs here.
+ je .sse_special_load_eax
+ or eax, X86_MXCSR_RC_NEAREST | X86_MXCSR_XCPT_MASK ; default environment masks all exceptions
+.sse_special_load_eax:
+ mov [xBP - 10h], eax
+ ldmxcsr [xBP - 10h]
+
+ ;
+ ; Return success.
+ ;
+.return_okay:
+ xor eax, eax
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(fesetenv)
+
diff --git a/src/VBox/Runtime/common/math/fesetexceptflag.asm b/src/VBox/Runtime/common/math/fesetexceptflag.asm
new file mode 100644
index 00000000..548437a8
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fesetexceptflag.asm
@@ -0,0 +1,127 @@
+; $Id: fesetexceptflag.asm $
+;; @file
+; IPRT - No-CRT fesetexceptflag - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Gets the pending exceptions.
+;
+; @returns eax = 0 on success, non-zero on failure.
+; @param pfXcpts 32-bit: [xBP+8]; msc64: rcx; gcc64: rdi; -- pointer to fexcept_t (16-bit)
+; @param fXcptMask 32-bit: [xBP+c]; msc64: edx; gcc64: esi; -- X86_MXCSR_XCPT_FLAGS (X86_FSW_XCPT_MASK)
+; Accepts X86_FSW_SF.
+;
+RT_NOCRT_BEGINPROC fesetexceptflag
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into ecx (*pfXcpts) and edx (fXcptMask) and validate the latter.
+ ;
+%ifdef ASM_CALL64_GCC
+ movzx ecx, word [rdi]
+ mov edx, esi
+%elifdef ASM_CALL64_MSC
+ movzx ecx, word [rcx]
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+ movzx ecx, word [ecx]
+ mov edx, [xBP + xCB*3]
+%endif
+%if 0
+ and ecx, X86_FSW_XCPT_MASK
+ and edx, X86_FSW_XCPT_MASK
+%else
+ or eax, -1
+ test edx, ~X86_FSW_XCPT_MASK
+ jnz .return
+ test ecx, ~X86_FSW_XCPT_MASK
+ jnz .return
+%endif
+
+ ;
+ ; Apply the AND mask to ECX and invert it so we can use it to clear flags
+ ; before OR'ing in the new values.
+ ;
+ and ecx, edx
+ not edx
+
+ ;
+ ; Make the modifications
+ ;
+
+ ; Modify the pending x87 exceptions (FSW).
+ fnstenv [xBP - 20h]
+ and [xBP - 20h + X86FSTENV32P.FSW], dx
+ or [xBP - 20h + X86FSTENV32P.FSW], cx
+ fldenv [xSP - 20h]
+
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ jz .return_ok
+%endif
+
+ ; Modify the pending SSE exceptions (same bit positions as in FSW).
+ stmxcsr [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or edx, X86_FSW_XCPT_MASK & ~X86_MXCSR_XCPT_FLAGS ; Don't mix X86_FSW_SF with X86_MXCSR_DAZ.
+ and ecx, X86_MXCSR_XCPT_FLAGS ; Ditto
+ and eax, edx
+ or eax, ecx
+ mov [xBP - 10h], eax
+ ldmxcsr [xBP - 10h]
+
+.return_ok:
+ xor eax, eax
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(fesetexceptflag)
+
diff --git a/src/VBox/Runtime/common/math/fesetround.asm b/src/VBox/Runtime/common/math/fesetround.asm
new file mode 100644
index 00000000..e0aca7ab
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fesetround.asm
@@ -0,0 +1,107 @@
+; $Id: fesetround.asm $
+;; @file
+; IPRT - No-CRT fesetround - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Sets the hardware rounding mode.
+;
+; @returns eax = 0 on success, non-zero on failure.
+; @param iRoundingMode 32-bit: [xBP+8] msc64: ecx gcc64: edi
+;
+RT_NOCRT_BEGINPROC fesetround
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into ecx.
+ ;
+ or eax, -1
+%ifdef ASM_CALL64_GCC
+ mov ecx, edi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+ test ecx, ~X86_FCW_RC_MASK
+ jnz .return
+
+ ;
+ ; Make the changes.
+ ;
+
+ ; Set x87 rounding first (ecx preserved).
+ fstcw [xBP - 10h]
+ mov ax, word [xBP - 10h]
+ and ax, ~X86_FCW_RC_MASK
+ or ax, cx
+ mov [xBP - 10h], ax
+ fldcw [xBP - 10h]
+
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ jz .return_ok
+%endif
+
+ ; Set SSE rounding (modifies ecx).
+ stmxcsr [xBP - 10h]
+ mov eax, [xBP - 10h]
+ and eax, ~X86_MXCSR_RC_MASK
+ shl ecx, X86_MXCSR_RC_SHIFT - X86_FCW_RC_SHIFT
+ or eax, ecx
+ mov [xBP - 10h], eax
+ ldmxcsr [xBP - 10h]
+
+.return_ok:
+ xor eax, eax
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(fesetround)
+
diff --git a/src/VBox/Runtime/common/math/fesetx87precision.asm b/src/VBox/Runtime/common/math/fesetx87precision.asm
new file mode 100644
index 00000000..1d0c54c1
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fesetx87precision.asm
@@ -0,0 +1,88 @@
+; $Id: fesetx87precision.asm $
+;; @file
+; IPRT - No-CRT fesetx87precision - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Sets the x87 hardware precision mode - IPRT extension.
+;
+; @returns eax = previous precision mode, -1 on failure.
+; @param iPrecisionMode 32-bit: [xBP+8] msc64: ecx gcc64: edi
+;
+RT_NOCRT_BEGINPROC fesetx87precision
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into ecx.
+ ;
+ or eax, -1
+%ifdef ASM_CALL64_GCC
+ mov ecx, edi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+ test ecx, ~X86_FCW_PC_MASK
+ jnz .return
+
+ ;
+ ; Extract the current from the x87 FCW.
+ ;
+ fnstcw [xBP - 10h]
+ mov dx, [xBP - 10h]
+ mov ax, dx
+ and dx, ~X86_FCW_PC_MASK
+ or dx, cx
+ mov [xBP - 10h], dx
+ fldcw [xBP - 10h]
+
+ and eax, X86_FCW_PC_MASK
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(fesetx87precision)
+
diff --git a/src/VBox/Runtime/common/math/fetestexcept.asm b/src/VBox/Runtime/common/math/fetestexcept.asm
new file mode 100644
index 00000000..93c6abf1
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fetestexcept.asm
@@ -0,0 +1,107 @@
+; $Id: fetestexcept.asm $
+;; @file
+; IPRT - No-CRT fetestexcept - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Return the pending exceptions in the given mask.
+;
+; Basically a simpler fegetexceptflags function.
+;
+; @returns eax = pending exceptions (X86_FSW_XCPT_MASK) & fXcptMask.
+; @param fXcptMask 32-bit: [xBP+8]; msc64: ecx; gcc64: edi; -- Exceptions to test for (X86_FSW_XCPT_MASK).
+; Accepts X86_FSW_SF and will return it if given as input.
+;
+RT_NOCRT_BEGINPROC fetestexcept
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into ecx (fXcptMask).
+ ;
+%ifdef ASM_CALL64_GCC
+ mov ecx, edi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+%if 0
+ and ecx, X86_FSW_XCPT_MASK
+%else
+ or eax, -1
+ test ecx, ~X86_FSW_XCPT_MASK
+ jnz .return
+%endif
+
+ ;
+ ; Get the pending exceptions.
+ ;
+
+ ; Get x87 exceptions first.
+ fnstsw ax
+ and eax, ecx
+
+%ifdef RT_ARCH_X86
+ ; SSE supported (ecx preserved)?
+ mov ch, al ; Save the return value - it's only the lower 6 bits.
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse)
+ test al, al
+ mov al, ch ; Restore the return value - no need for movzx here.
+ jz .return
+%endif
+
+ ; OR in the SSE exceptions (modifies ecx).
+ stmxcsr [xBP - 10h]
+ and ecx, [xBP - 10h]
+ and ecx, X86_MXCSR_XCPT_FLAGS
+ or eax, ecx
+
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(fetestexcept)
+
diff --git a/src/VBox/Runtime/common/math/feupdateenv.asm b/src/VBox/Runtime/common/math/feupdateenv.asm
new file mode 100644
index 00000000..2c66af41
--- /dev/null
+++ b/src/VBox/Runtime/common/math/feupdateenv.asm
@@ -0,0 +1,128 @@
+; $Id: feupdateenv.asm $
+;; @file
+; IPRT - No-CRT feupdateenv - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+extern NAME(RT_NOCRT(fesetenv))
+extern NAME(RT_NOCRT(feraiseexcept))
+
+
+;;
+; Updates the FPU+SSE environment.
+;
+; This will restore @a pEnv and merge in pending exception flags.
+;
+; @returns eax = 0 on success, -1 on failure.
+; @param pEnv 32-bit: [xBP+8] msc64: rcx gcc64: rdi - Saved environment.
+;
+RT_NOCRT_BEGINPROC feupdateenv
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 30h
+ SEH64_ALLOCATE_STACK 30h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the parameter into rcx.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov rcx, rdi
+%elifdef RT_ARCH_X86
+ mov ecx, [xBP + xCB*2]
+%endif
+
+ ;
+ ; Save the pending exceptions.
+ ;
+%ifdef RT_ARCH_X86
+ extern NAME(rtNoCrtHasSse)
+ call NAME(rtNoCrtHasSse) ; Preserves all except xAX.
+ xor edx, edx
+ test al, al
+ jz .no_sse
+%endif
+ stmxcsr [xBP - 10h]
+ mov edx, [xBP - 10h]
+ and edx, X86_MXCSR_XCPT_FLAGS
+.no_sse:
+ fnstsw ax
+ or edx, eax
+ mov [xBP - 8h], edx ; save the pending exceptions here (will apply X86_FSW_XCPT_MASK later).
+
+ ;
+ ; Call fesetenv to update the environment.
+ ; Note! We have not yet modified the parameter registers for calling
+ ; convensions using them. So, parameters only needs to be loaded
+ ; for the stacked based convention.
+ ;
+%ifdef RT_ARCH_X86
+ mov [xSP], ecx
+%endif
+ call NAME(RT_NOCRT(fesetenv))
+
+ ;
+ ; Raise exceptions if any are pending.
+ ;
+%ifdef ASM_CALL64_GCC
+ mov edi, [xBP - 8h]
+ and edi, X86_FSW_XCPT_MASK
+%elifdef ASM_CALL64_MSC
+ mov ecx, [xBP - 8h]
+ and ecx, X86_FSW_XCPT_MASK
+%else
+ mov ecx, [xBP - 8h]
+ and ecx, X86_FSW_XCPT_MASK
+ mov [xSP], ecx
+%endif
+ jz .no_exceptions_to_raise
+ call NAME(RT_NOCRT(feraiseexcept))
+.no_exceptions_to_raise:
+
+ ;
+ ; Return success.
+ ;
+ xor eax, eax
+ leave
+ ret
+ENDPROC RT_NOCRT(feupdateenv)
+
diff --git a/src/VBox/Runtime/common/math/floor.asm b/src/VBox/Runtime/common/math/floor.asm
new file mode 100644
index 00000000..5e8c267e
--- /dev/null
+++ b/src/VBox/Runtime/common/math/floor.asm
@@ -0,0 +1,78 @@
+; $Id: floor.asm $
+;; @file
+; IPRT - No-CRT floor - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Compute the largest integral value not greater than rd.
+; @returns 32-bit: st(0) 64-bit: xmm0
+; @param rd 32-bit: [ebp + 8] 64-bit: xmm0
+RT_NOCRT_BEGINPROC floor
+ push xBP
+ mov xBP, xSP
+ sub xSP, 10h
+
+%ifdef RT_ARCH_AMD64
+ movsd [xSP], xmm0
+ fld qword [xSP]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ; Make it round down by modifying the fpu control word.
+ fstcw [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or eax, 00400h
+ and eax, 0f7ffh
+ mov [xBP - 08h], eax
+ fldcw [xBP - 08h]
+
+ ; Round ST(0) to integer.
+ frndint
+
+ ; Restore the fpu control word.
+ fldcw [xBP - 10h]
+
+%ifdef RT_ARCH_AMD64
+ fstp qword [xSP]
+ movsd xmm0, [xSP]
+%endif
+ leave
+ ret
+ENDPROC RT_NOCRT(floor)
+
diff --git a/src/VBox/Runtime/common/math/floorf.asm b/src/VBox/Runtime/common/math/floorf.asm
new file mode 100644
index 00000000..df0a67cb
--- /dev/null
+++ b/src/VBox/Runtime/common/math/floorf.asm
@@ -0,0 +1,78 @@
+; $Id: floorf.asm $
+;; @file
+; IPRT - No-CRT floorf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Compute the largest integral value not greater than rf.
+; @returns st(0)
+; @param rf 32-bit: [ebp + 8] 64-bit: xmm0
+RT_NOCRT_BEGINPROC floorf
+ push xBP
+ mov xBP, xSP
+ sub xSP, 10h
+
+%ifdef RT_ARCH_AMD64
+ movss [xSP], xmm0
+ fld dword [xSP]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ; Make it round down by modifying the fpu control word.
+ fstcw [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or eax, 00400h
+ and eax, 0f7ffh
+ mov [xBP - 08h], eax
+ fldcw [xBP - 08h]
+
+ ; Round ST(0) to integer.
+ frndint
+
+ ; Restore the fpu control word.
+ fldcw [xBP - 10h]
+
+%ifdef RT_ARCH_AMD64
+ fstp dword [xSP]
+ movss xmm0, [xSP]
+%endif
+ leave
+ ret
+ENDPROC RT_NOCRT(floorf)
+
diff --git a/src/VBox/Runtime/common/math/floorl.asm b/src/VBox/Runtime/common/math/floorl.asm
new file mode 100644
index 00000000..42af21a5
--- /dev/null
+++ b/src/VBox/Runtime/common/math/floorl.asm
@@ -0,0 +1,69 @@
+; $Id: floorl.asm $
+;; @file
+; IPRT - No-CRT floorl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Compute the largest integral value not greater than lrd.
+; @returns st(0)
+; @param lrd [rbp + 8]
+RT_NOCRT_BEGINPROC floorl
+ push xBP
+ mov xBP, xSP
+ sub xSP, 10h
+
+ fld tword [xBP + xCB*2]
+
+ ; Make it round down by modifying the fpu control word.
+ fstcw [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or eax, 00400h
+ and eax, 0f7ffh
+ mov [xBP - 08h], eax
+ fldcw [xBP - 08h]
+
+ ; Round ST(0) to integer.
+ frndint
+
+ ; Restore the fpu control word.
+ fldcw [xBP - 10h]
+
+ leave
+ ret
+ENDPROC RT_NOCRT(floorl)
+
diff --git a/src/VBox/Runtime/common/math/fma-asm.asm b/src/VBox/Runtime/common/math/fma-asm.asm
new file mode 100644
index 00000000..3caa6153
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fma-asm.asm
@@ -0,0 +1,104 @@
+; $Id: fma-asm.asm $
+;; @file
+; IPRT - No-CRT fma alternatives - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Fused multiplication and add, intel version.
+;
+; @returns st(0) / xmm0
+; @param rdFactor1 [rbp + 08h] / xmm0
+; @param rdFactor2 [rbp + 10h] / xmm1
+; @param rdAddend [rbp + 18h] / xmm2
+BEGINPROC rtNoCrtMathFma3
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_X86
+ movsd xmm0, qword [xBP + xCB*2 + 00h]
+ movsd xmm1, qword [xBP + xCB*2 + 08h]
+ movsd xmm2, qword [xBP + xCB*2 + 10h]
+%endif
+
+ vfmadd132sd xmm0, xmm2, xmm1 ; xmm0 = xmm0 * xmm1 + xmm2 (132 = multiply op1 with op3 and add op2)
+
+%ifdef RT_ARCH_X86
+ sub xSP, 10h
+ movsd [xSP], xmm0
+ fld qword [xSP]
+%endif
+ leave
+ ret
+ENDPROC rtNoCrtMathFma3
+
+
+;;
+; Fused multiplication and add, amd version.
+;
+; @returns st(0) / xmm0
+; @param rdFactor1 [rbp + 08h] / xmm0
+; @param rdFactor2 [rbp + 10h] / xmm1
+; @param rdAddend [rbp + 18h] / xmm2
+BEGINPROC rtNoCrtMathFma4
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_X86
+ movsd xmm0, qword [xBP + xCB*2 + 00h]
+ movsd xmm1, qword [xBP + xCB*2 + 08h]
+ movsd xmm2, qword [xBP + xCB*2 + 10h]
+%endif
+
+ vfmaddsd xmm0, xmm0, xmm1, xmm2 ; xmm0 = xmm0 * xmm1 + xmm2
+
+%ifdef RT_ARCH_X86
+ sub xSP, 10h
+ movsd [xSP], xmm0
+ fld qword [xSP]
+%endif
+ leave
+ ret
+ENDPROC rtNoCrtMathFma4
+
diff --git a/src/VBox/Runtime/common/math/fma.cpp b/src/VBox/Runtime/common/math/fma.cpp
new file mode 100644
index 00000000..14d98f9a
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fma.cpp
@@ -0,0 +1,100 @@
+/* $Id: fma.cpp $ */
+/** @file
+ * IPRT - No-CRT - fma().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+# include <iprt/asm-amd64-x86.h>
+# include <iprt/x86.h>
+#endif
+#include <softfloat.h>
+
+
+/*********************************************************************************************************************************
+* External Symbols *
+*********************************************************************************************************************************/
+DECLASM(double) rtNoCrtMathFma3(double rdFactor1, double rdFactor2, double rdAddend);
+DECLASM(double) rtNoCrtMathFma4(double rdFactor1, double rdFactor2, double rdAddend);
+
+
+#undef fma
+double RT_NOCRT(fma)(double rdFactor1, double rdFactor2, double rdAddend)
+{
+ /*
+ * We prefer using native FMA instructions when available.
+ */
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ typedef enum { kCpuDetect = 0, kCpuWithFma3, kCpuWithFma4, kCpuWithoutFma } CPUFMASUPPORT;
+ static CPUFMASUPPORT volatile s_enmSup = kCpuDetect;
+ CPUFMASUPPORT enmSup = s_enmSup;
+ if (enmSup != kCpuDetect)
+ { }
+ else
+ {
+ if (ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_FMA)
+ enmSup = kCpuWithFma3;
+ else if (ASMCpuId_ECX(UINT32_C(0x80000001)) & X86_CPUID_AMD_FEATURE_ECX_FMA4)
+ enmSup = kCpuWithFma4;
+ else
+ enmSup = kCpuWithoutFma;
+ s_enmSup = enmSup;
+ }
+ if (enmSup == kCpuWithFma3)
+ return rtNoCrtMathFma3(rdFactor1, rdFactor2, rdAddend);
+ if (enmSup == kCpuWithFma4)
+ return rtNoCrtMathFma4(rdFactor1, rdFactor2, rdAddend);
+#endif
+
+ /*
+ * Fall back on SoftFloat.
+ */
+ AssertCompile(sizeof(rdFactor1) == sizeof(RTFLOAT64U));
+ softfloat_state_t State = SOFTFLOAT_STATE_INIT_DEFAULTS(); /** @todo init from MXCSR/FCW */
+ union { RTFLOAT64U Iprt; float64_t SoftFloat; } uFactor1, uFactor2, uAddend, uResult;
+ uFactor1.Iprt.rd = rdFactor1;
+ uFactor2.Iprt.rd = rdFactor2;
+ uAddend.Iprt.rd = rdAddend;
+ uResult.SoftFloat = f64_mulAdd(uFactor1.SoftFloat, uFactor2.SoftFloat, uAddend.SoftFloat, &State);
+ return uResult.Iprt.rd;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fma);
+
diff --git a/src/VBox/Runtime/common/math/fmaf-asm.asm b/src/VBox/Runtime/common/math/fmaf-asm.asm
new file mode 100644
index 00000000..105044e1
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fmaf-asm.asm
@@ -0,0 +1,104 @@
+; $Id: fmaf-asm.asm $
+;; @file
+; IPRT - No-CRT fmaf alternatives - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Fused multiplication and add, intel version.
+;
+; @returns st(0) / xmm0
+; @param r32Factor1 [rbp + 08h] / xmm0
+; @param r32Factor2 [rbp + 0ch] / xmm1
+; @param r32Addend [rbp + 10h] / xmm2
+BEGINPROC rtNoCrtMathFma3f
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_X86
+ movss xmm0, dword [xBP + xCB*2 + 00h]
+ movss xmm1, dword [xBP + xCB*2 + 04h]
+ movss xmm2, dword [xBP + xCB*2 + 08h]
+%endif
+
+ vfmadd132ss xmm0, xmm2, xmm1 ; xmm0 = xmm0 * xmm1 + xmm2 (132 = multiply op1 with op3 and add op2)
+
+%ifdef RT_ARCH_X86
+ sub xSP, 10h
+ movss [xSP], xmm0
+ fld dword [xSP]
+%endif
+ leave
+ ret
+ENDPROC rtNoCrtMathFma3f
+
+
+;;
+; Fused multiplication and add, amd version.
+;
+; @returns st(0) / xmm0
+; @param r32Factor1 [rbp + 08h] / xmm0
+; @param r32Factor2 [rbp + 10h] / xmm1
+; @param r32Addend [rbp + 18h] / xmm2
+BEGINPROC rtNoCrtMathFma4f
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_X86
+ movss xmm0, dword [xBP + xCB*2 + 00h]
+ movss xmm1, dword [xBP + xCB*2 + 04h]
+ movss xmm2, dword [xBP + xCB*2 + 08h]
+%endif
+
+ vfmaddss xmm0, xmm0, xmm1, xmm2 ; xmm0 = xmm0 * xmm1 + xmm2
+
+%ifdef RT_ARCH_X86
+ sub xSP, 10h
+ movss [xSP], xmm0
+ fld dword [xSP]
+%endif
+ leave
+ ret
+ENDPROC rtNoCrtMathFma4f
+
diff --git a/src/VBox/Runtime/common/math/fmaf.cpp b/src/VBox/Runtime/common/math/fmaf.cpp
new file mode 100644
index 00000000..86c00b6e
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fmaf.cpp
@@ -0,0 +1,101 @@
+/* $Id: fmaf.cpp $ */
+/** @file
+ * IPRT - No-CRT - fmaf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+# include <iprt/asm-amd64-x86.h>
+# include <iprt/x86.h>
+#endif
+#include <softfloat.h>
+
+
+/*********************************************************************************************************************************
+* External Symbols *
+*********************************************************************************************************************************/
+DECLASM(float) rtNoCrtMathFma3f(float r32Factor1, float r32Factor2, float r32Addend);
+DECLASM(float) rtNoCrtMathFma4f(float r32Factor1, float r32Factor2, float r32Addend);
+
+
+#undef fmaf
+float RT_NOCRT(fmaf)(float r32Factor1, float r32Factor2, float r32Addend)
+{
+ /*
+ * We prefer using native FMA instructions when available.
+ */
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ typedef enum { kCpuDetect = 0, kCpuWithFma3, kCpuWithFma4, kCpuWithoutFma } CPUFMASUPPORT;
+ static CPUFMASUPPORT volatile s_enmSup = kCpuDetect;
+ CPUFMASUPPORT enmSup = s_enmSup;
+ if (enmSup != kCpuDetect)
+ { }
+ else
+ {
+ if (ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_FMA)
+ enmSup = kCpuWithFma3;
+ else if (ASMCpuId_ECX(UINT32_C(0x80000001)) & X86_CPUID_AMD_FEATURE_ECX_FMA4)
+ enmSup = kCpuWithFma4;
+ else
+ enmSup = kCpuWithoutFma;
+ s_enmSup = enmSup;
+ }
+ if (enmSup == kCpuWithFma3)
+ return rtNoCrtMathFma3f(r32Factor1, r32Factor2, r32Addend);
+ if (enmSup == kCpuWithFma4)
+ return rtNoCrtMathFma4f(r32Factor1, r32Factor2, r32Addend);
+#endif
+
+ /*
+ * Fall back on SoftFloat.
+ */
+ /** @todo couldn't we just use double as a fallback here? */
+ AssertCompile(sizeof(r32Factor1) == sizeof(RTFLOAT32U));
+ softfloat_state_t State = SOFTFLOAT_STATE_INIT_DEFAULTS(); /** @todo init from MXCSR/FCW */
+ union { RTFLOAT32U Iprt; float32_t SoftFloat; } uFactor1, uFactor2, uAddend, uResult;
+ uFactor1.Iprt.r = r32Factor1;
+ uFactor2.Iprt.r = r32Factor2;
+ uAddend.Iprt.r = r32Addend;
+ uResult.SoftFloat = f32_mulAdd(uFactor1.SoftFloat, uFactor2.SoftFloat, uAddend.SoftFloat, &State);
+ return uResult.Iprt.r;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fmaf);
+
diff --git a/src/VBox/Runtime/common/math/fmax.cpp b/src/VBox/Runtime/common/math/fmax.cpp
new file mode 100644
index 00000000..f7cb87c9
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fmax.cpp
@@ -0,0 +1,64 @@
+/* $Id: fmax.cpp $ */
+/** @file
+ * IPRT - No-CRT - fmax().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+
+
+#undef fmax
+double RT_NOCRT(fmax)(double rdLeft, double rdRight)
+{
+ if (!isnan(rdLeft))
+ {
+ if (!isnan(rdRight))
+ {
+ /* We don't trust the hw with comparing signed zeros, thus
+ the 0.0 test and signbit fun here. */
+ if (rdLeft != rdRight || rdLeft != 0.0)
+ return rdLeft >= rdRight ? rdLeft : rdRight;
+ return signbit(rdLeft) <= signbit(rdRight) ? rdLeft : rdRight;
+ }
+ return rdLeft;
+ }
+ return rdRight;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fmax);
+
diff --git a/src/VBox/Runtime/common/math/fmaxf.cpp b/src/VBox/Runtime/common/math/fmaxf.cpp
new file mode 100644
index 00000000..2e6c0222
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fmaxf.cpp
@@ -0,0 +1,64 @@
+/* $Id: fmaxf.cpp $ */
+/** @file
+ * IPRT - No-CRT - fmaxf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+
+
+#undef fmaxf
+float RT_NOCRT(fmaxf)(float r32Left, float r32Right)
+{
+ if (!isnan(r32Left))
+ {
+ if (!isnan(r32Right))
+ {
+ /* We don't trust the hw with comparing signed zeros, thus
+ the 0.0 test and signbit fun here. */
+ if (r32Left != r32Right || r32Left != 0.0)
+ return r32Left >= r32Right ? r32Left : r32Right;
+ return signbit(r32Left) <= signbit(r32Right) ? r32Left : r32Right;
+ }
+ return r32Left;
+ }
+ return r32Right;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fmaxf);
+
diff --git a/src/VBox/Runtime/common/math/fmaxl.cpp b/src/VBox/Runtime/common/math/fmaxl.cpp
new file mode 100644
index 00000000..8eccc9ba
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fmaxl.cpp
@@ -0,0 +1,64 @@
+/* $Id: fmaxl.cpp $ */
+/** @file
+ * IPRT - No-CRT - fmaxl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+
+
+#undef fmaxl
+long double RT_NOCRT(fmaxl)(long double lrdLeft, long double lrdRight)
+{
+ if (!isnan(lrdLeft))
+ {
+ if (!isnan(lrdRight))
+ {
+ /* We don't trust the hw with comparing signed zeros, thus
+ the 0.0 test and signbit fun here. */
+ if (lrdLeft != lrdRight || lrdLeft != 0.0)
+ return lrdLeft >= lrdRight ? lrdLeft : lrdRight;
+ return signbit(lrdLeft) <= signbit(lrdRight) ? lrdLeft : lrdRight;
+ }
+ return lrdLeft;
+ }
+ return lrdRight;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fmaxl);
+
diff --git a/src/VBox/Runtime/common/math/fmin.cpp b/src/VBox/Runtime/common/math/fmin.cpp
new file mode 100644
index 00000000..5d104e42
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fmin.cpp
@@ -0,0 +1,64 @@
+/* $Id: fmin.cpp $ */
+/** @file
+ * IPRT - No-CRT - fmin().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+
+
+#undef fmin
+double RT_NOCRT(fmin)(double rdLeft, double rdRight)
+{
+ if (!isnan(rdLeft))
+ {
+ if (!isnan(rdRight))
+ {
+ /* We don't trust the hw with comparing signed zeros, thus
+ the 0.0 test and signbit fun here. */
+ if (rdLeft != rdRight || rdLeft != 0.0)
+ return rdLeft <= rdRight ? rdLeft : rdRight;
+ return signbit(rdLeft) >= signbit(rdRight) ? rdLeft : rdRight;
+ }
+ return rdLeft;
+ }
+ return rdRight;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fmin);
+
diff --git a/src/VBox/Runtime/common/math/fminf.cpp b/src/VBox/Runtime/common/math/fminf.cpp
new file mode 100644
index 00000000..ed3aa90d
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fminf.cpp
@@ -0,0 +1,64 @@
+/* $Id: fminf.cpp $ */
+/** @file
+ * IPRT - No-CRT - fminf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+
+
+#undef fminf
+float RT_NOCRT(fminf)(float r32Left, float r32Right)
+{
+ if (!isnan(r32Left))
+ {
+ if (!isnan(r32Right))
+ {
+ /* We don't trust the hw with comparing signed zeros, thus
+ the 0.0 test and signbit fun here. */
+ if (r32Left != r32Right || r32Left != 0.0)
+ return r32Left <= r32Right ? r32Left : r32Right;
+ return signbit(r32Left) >= signbit(r32Right) ? r32Left : r32Right;
+ }
+ return r32Left;
+ }
+ return r32Right;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fminf);
+
diff --git a/src/VBox/Runtime/common/math/fminl.cpp b/src/VBox/Runtime/common/math/fminl.cpp
new file mode 100644
index 00000000..ceb37669
--- /dev/null
+++ b/src/VBox/Runtime/common/math/fminl.cpp
@@ -0,0 +1,64 @@
+/* $Id: fminl.cpp $ */
+/** @file
+ * IPRT - No-CRT - fminl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+
+
+#undef fminl
+long double RT_NOCRT(fminl)(long double lrdLeft, long double lrdRight)
+{
+ if (!isnan(lrdLeft))
+ {
+ if (!isnan(lrdRight))
+ {
+ /* We don't trust the hw with comparing signed zeros, thus
+ the 0.0 test and signbit fun here. */
+ if (lrdLeft != lrdRight || lrdLeft != 0.0)
+ return lrdLeft <= lrdRight ? lrdLeft : lrdRight;
+ return signbit(lrdLeft) >= signbit(lrdRight) ? lrdLeft : lrdRight;
+ }
+ return lrdLeft;
+ }
+ return lrdRight;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(fminl);
+
diff --git a/src/VBox/Runtime/common/math/frexp.cpp b/src/VBox/Runtime/common/math/frexp.cpp
new file mode 100644
index 00000000..46c2f3dc
--- /dev/null
+++ b/src/VBox/Runtime/common/math/frexp.cpp
@@ -0,0 +1,88 @@
+/* $Id: frexp.cpp $ */
+/** @file
+ * IPRT - No-CRT - frexp().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/nocrt/limits.h>
+
+
+/* Similar to the fxtract instruction. */
+#undef frexp
+double RT_NOCRT(frexp)(double rdValue, int *piExp)
+{
+ RTFLOAT64U Value;
+ AssertCompile(sizeof(Value) == sizeof(rdValue));
+ Value.r = rdValue;
+
+ if (RTFLOAT64U_IS_NORMAL(&Value))
+ {
+ *piExp = (int)Value.s.uExponent - RTFLOAT64U_EXP_BIAS + 1;
+ Value.s.uExponent = RTFLOAT64U_EXP_BIAS - 1;
+ }
+ else if (RTFLOAT64U_IS_ZERO(&Value))
+ {
+ *piExp = 0;
+ return rdValue;
+ }
+ else if (RTFLOAT64U_IS_SUBNORMAL(&Value))
+ {
+ int iExp = -RTFLOAT64U_EXP_BIAS + 1;
+ uint64_t uFraction = Value.s64.uFraction;
+ while (!(uFraction & RT_BIT_64(RTFLOAT64U_FRACTION_BITS)))
+ {
+ iExp--;
+ uFraction <<= 1;
+ }
+ Value.s64.uFraction = uFraction;
+ Value.s64.uExponent = RTFLOAT64U_EXP_BIAS - 1;
+ *piExp = iExp + 1;
+ }
+ else
+ {
+ /* NaN, Inf */
+ *piExp = Value.s.fSign ? INT_MIN : INT_MAX;
+ return rdValue;
+ }
+ return Value.r;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(frexp);
+
diff --git a/src/VBox/Runtime/common/math/frexpf.cpp b/src/VBox/Runtime/common/math/frexpf.cpp
new file mode 100644
index 00000000..f7d63173
--- /dev/null
+++ b/src/VBox/Runtime/common/math/frexpf.cpp
@@ -0,0 +1,87 @@
+/* $Id: frexpf.cpp $ */
+/** @file
+ * IPRT - No-CRT - frexpf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/nocrt/limits.h>
+
+
+/* Similar to the fxtract instruction. */
+#undef frexpf
+float RT_NOCRT(frexpf)(float rfValue, int *piExp)
+{
+ RTFLOAT32U Value;
+ AssertCompile(sizeof(Value) == sizeof(rfValue));
+ Value.r = rfValue;
+
+ if (RTFLOAT32U_IS_NORMAL(&Value))
+ {
+ *piExp = (int)Value.s.uExponent - RTFLOAT32U_EXP_BIAS + 1;
+ Value.s.uExponent = RTFLOAT32U_EXP_BIAS - 1;
+ }
+ else if (RTFLOAT32U_IS_ZERO(&Value))
+ {
+ *piExp = 0;
+ return rfValue;
+ }
+ else if (RTFLOAT32U_IS_SUBNORMAL(&Value))
+ {
+ int iExp = -RTFLOAT32U_EXP_BIAS + 1;
+ uint32_t uFraction = Value.s.uFraction;
+ while (!(uFraction & RT_BIT_32(RTFLOAT32U_FRACTION_BITS)))
+ {
+ iExp--;
+ uFraction <<= 1;
+ }
+ Value.s.uFraction = uFraction;
+ Value.s.uExponent = RTFLOAT32U_EXP_BIAS - 1;
+ *piExp = iExp + 1;
+ }
+ else /* NaN, Inf */
+ {
+ *piExp = Value.s.fSign ? INT_MIN : INT_MAX;
+ return rfValue;
+ }
+ return Value.r;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(frexpf);
+
diff --git a/src/VBox/Runtime/common/math/frexpl.cpp b/src/VBox/Runtime/common/math/frexpl.cpp
new file mode 100644
index 00000000..161d9b35
--- /dev/null
+++ b/src/VBox/Runtime/common/math/frexpl.cpp
@@ -0,0 +1,167 @@
+/* $Id: frexpl.cpp $ */
+/** @file
+ * IPRT - No-CRT - frexpl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/nocrt/limits.h>
+#ifdef RT_COMPILER_WITH_128BIT_LONG_DOUBLE
+# include <iprt/uint128.h>
+#endif
+
+
+/* Similar to the fxtract instruction. */
+#undef frexpl
+long double RT_NOCRT(frexpl)(long double lrdValue, int *piExp)
+{
+#ifdef RT_COMPILER_WITH_64BIT_LONG_DOUBLE
+ RTFLOAT64U Value;
+ AssertCompile(sizeof(Value) == sizeof(lrdValue));
+ Value.lrd = lrdValue;
+
+ if (RTFLOAT64U_IS_NORMAL(&Value))
+ {
+ *piExp = (int)Value.s.uExponent - RTFLOAT64U_EXP_BIAS + 1;
+ Value.s.uExponent = RTFLOAT64U_EXP_BIAS - 1;
+ }
+ else if (RTFLOAT64U_IS_ZERO(&Value))
+ {
+ *piExp = 0;
+ return lrdValue;
+ }
+ else if (RTFLOAT64U_IS_SUBNORMAL(&Value))
+ {
+ int iExp = -RTFLOAT64U_EXP_BIAS + 1;
+ uint64_t uFraction = Value.s64.uFraction;
+ while (!(uFraction & RT_BIT_64(RTFLOAT64U_FRACTION_BITS)))
+ {
+ iExp--;
+ uFraction <<= 1;
+ }
+ Value.s64.uFraction = uFraction;
+ Value.s64.uExponent = RTFLOAT64U_EXP_BIAS - 1;
+ *piExp = iExp + 1;
+ }
+ else
+ {
+ /* NaN, Inf */
+ *piExp = Value.s.fSign ? INT_MIN : INT_MAX;
+ return lrdValue;
+ }
+ return Value.lrd;
+
+#elif defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+ RTFLOAT80U2 Value;
+ Value.r = lrdValue;
+
+ if (RTFLOAT80U_IS_NORMAL(&Value))
+ {
+ *piExp = (int)Value.s.uExponent - RTFLOAT80U_EXP_BIAS + 1;
+ Value.s.uExponent = RTFLOAT80U_EXP_BIAS - 1;
+ }
+ else if (RTFLOAT80U_IS_ZERO(&Value))
+ {
+ *piExp = 0;
+ return lrdValue;
+ }
+ else if (RTFLOAT80U_IS_DENORMAL_OR_PSEUDO_DENORMAL(&Value))
+ {
+ int iExp = -RTFLOAT80U_EXP_BIAS + 1;
+ while (!(Value.s.uMantissa & RT_BIT_64(RTFLOAT80U_FRACTION_BITS)))
+ {
+ iExp--;
+ Value.s.uMantissa <<= 1;
+ }
+ Value.s.uExponent = RTFLOAT80U_EXP_BIAS - 1;
+ *piExp = iExp + 1;
+ }
+ else /* NaN, Inf */
+ {
+ *piExp = Value.s.fSign ? INT_MIN : INT_MAX;
+ return lrdValue;
+ }
+ return Value.r;
+
+
+#elif defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ RTFLOAT128U Value;
+ AssertCompile(sizeof(Value) == sizeof(lrdValue));
+ Value.r = lrdValue;
+
+ if (RTFLOAT128U_IS_NORMAL(&Value))
+ {
+ *piExp = (int)Value.s.uExponent - RTFLOAT128U_EXP_BIAS + 1;
+ Value.s.uExponent = RTFLOAT128U_EXP_BIAS - 1;
+ }
+ else if (RTFLOAT128U_IS_ZERO(&Value))
+ {
+ *piExp = 0;
+ return lrdValue;
+ }
+ else if (RTFLOAT128U_IS_SUBNORMAL(&Value))
+ {
+ int iExp = -RTFLOAT128U_EXP_BIAS + 1;
+ RTUINT128U uFraction;
+ uFraction.s.Hi = Value.s64.uFractionHi;
+ uFraction.s.Lo = Value.s64.uFractionLo;
+ while (!(uFraction.s.Hi & RT_BIT_64(RTFLOAT128U_FRACTION_BITS - 64)))
+ {
+ iExp--;
+ RTUInt128AssignShiftLeft(&uFraction, 1);
+ }
+ Value.s64.uFractionHi = uFraction.s.Hi;
+ Value.s64.uFractionLo = uFraction.s.Lo;
+ Value.s64.uExponent = RTFLOAT64U_EXP_BIAS - 1;
+ *piExp = iExp + 1;
+ }
+ else
+ {
+ /* NaN, Inf */
+ *piExp = Value.s.fSign ? INT_MIN : INT_MAX;
+ return lrdValue;
+ }
+ return Value.r;
+#else
+# error "Port ME!"
+#endif
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(frexpl);
+
diff --git a/src/VBox/Runtime/common/math/gcc/Makefile.kup b/src/VBox/Runtime/common/math/gcc/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/Makefile.kup
diff --git a/src/VBox/Runtime/common/math/gcc/adddi3.c b/src/VBox/Runtime/common/math/gcc/adddi3.c
new file mode 100644
index 00000000..ce2b1d85
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/adddi3.c
@@ -0,0 +1,63 @@
+/* $NetBSD: adddi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)adddi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: adddi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Add two quads. This is trivial since a one-bit carry from a single
+ * u_int addition x+y occurs if and only if the sum x+y is less than
+ * either x or y (the choice to compare with x or y is arbitrary).
+ */
+quad_t
+__adddi3(a, b)
+ quad_t a, b;
+{
+ union uu aa, bb, sum;
+
+ aa.q = a;
+ bb.q = b;
+ sum.ul[L] = aa.ul[L] + bb.ul[L];
+ sum.ul[H] = aa.ul[H] + bb.ul[H] + (sum.ul[L] < bb.ul[L]);
+ return (sum.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/anddi3.c b/src/VBox/Runtime/common/math/gcc/anddi3.c
new file mode 100644
index 00000000..2f35ced8
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/anddi3.c
@@ -0,0 +1,61 @@
+/* $NetBSD: anddi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)anddi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: anddi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Return a & b, in quad.
+ */
+quad_t
+__anddi3(a, b)
+ quad_t a, b;
+{
+ union uu aa, bb;
+
+ aa.q = a;
+ bb.q = b;
+ aa.ul[0] &= bb.ul[0];
+ aa.ul[1] &= bb.ul[1];
+ return (aa.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/ashldi3.c b/src/VBox/Runtime/common/math/gcc/ashldi3.c
new file mode 100644
index 00000000..e7df3c18
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/ashldi3.c
@@ -0,0 +1,70 @@
+/* $NetBSD: ashldi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)ashldi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: ashldi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Shift a (signed) quad value left (arithmetic shift left).
+ * This is the same as logical shift left!
+ */
+quad_t
+__ashldi3(a, shift)
+ quad_t a;
+ qshift_t shift;
+{
+ union uu aa;
+
+ if (shift == 0)
+ return(a);
+ aa.q = a;
+ if (shift >= INT_BITS) {
+ aa.ul[H] = aa.ul[L] << (shift - INT_BITS);
+ aa.ul[L] = 0;
+ } else {
+ aa.ul[H] = (aa.ul[H] << shift) |
+ (aa.ul[L] >> (INT_BITS - shift));
+ aa.ul[L] <<= shift;
+ }
+ return (aa.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/ashrdi3.c b/src/VBox/Runtime/common/math/gcc/ashrdi3.c
new file mode 100644
index 00000000..aaa1c71b
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/ashrdi3.c
@@ -0,0 +1,82 @@
+/* $NetBSD: ashrdi3.c,v 1.10 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)ashrdi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: ashrdi3.c,v 1.10 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Shift a (signed) quad value right (arithmetic shift right).
+ */
+quad_t
+__ashrdi3(a, shift)
+ quad_t a;
+ qshift_t shift;
+{
+ union uu aa;
+
+ if (shift == 0)
+ return(a);
+ aa.q = a;
+ if (shift >= INT_BITS) {
+ int s;
+
+ /*
+ * Smear bits rightward using the machine's right-shift
+ * method, whether that is sign extension or zero fill,
+ * to get the `sign word' s. Note that shifting by
+ * INT_BITS is undefined, so we shift (INT_BITS-1),
+ * then 1 more, to get our answer.
+ */
+ /* LINTED inherits machine dependency */
+ s = (aa.sl[H] >> (INT_BITS - 1)) >> 1;
+ /* LINTED inherits machine dependency*/
+ aa.ul[L] = aa.sl[H] >> (shift - INT_BITS);
+ aa.ul[H] = s;
+ } else {
+ aa.ul[L] = (aa.ul[L] >> shift) |
+ (aa.ul[H] << (INT_BITS - shift));
+ /* LINTED inherits machine dependency */
+ aa.sl[H] >>= shift;
+ }
+ return (aa.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/cmpdi2.c b/src/VBox/Runtime/common/math/gcc/cmpdi2.c
new file mode 100644
index 00000000..c876eb4e
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/cmpdi2.c
@@ -0,0 +1,62 @@
+/* $NetBSD: cmpdi2.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)cmpdi2.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: cmpdi2.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Return 0, 1, or 2 as a <, =, > b respectively.
+ * Both a and b are considered signed---which means only the high word is
+ * signed.
+ */
+int
+__cmpdi2(a, b)
+ quad_t a, b;
+{
+ union uu aa, bb;
+
+ aa.q = a;
+ bb.q = b;
+ return (aa.sl[H] < bb.sl[H] ? 0 : aa.sl[H] > bb.sl[H] ? 2 :
+ aa.ul[L] < bb.ul[L] ? 0 : aa.ul[L] > bb.ul[L] ? 2 : 1);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/divdi3.c b/src/VBox/Runtime/common/math/gcc/divdi3.c
new file mode 100644
index 00000000..eecc17ad
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/divdi3.c
@@ -0,0 +1,70 @@
+/* $NetBSD: divdi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)divdi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: divdi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Divide two signed quads.
+ * ??? if -1/2 should produce -1 on this machine, this code is wrong
+ */
+quad_t
+__divdi3(a, b)
+ quad_t a, b;
+{
+ u_quad_t ua, ub, uq;
+ int neg = 0;
+
+ ua = a;
+ ub = b;
+
+ if (a < 0)
+ ua = -ua, neg ^= 1;
+ if (b < 0)
+ ub = -ub, neg ^= 1;
+
+ uq = __qdivrem(ua, ub, (u_quad_t *)0);
+ if (neg)
+ uq = - uq;
+ return uq;
+}
diff --git a/src/VBox/Runtime/common/math/gcc/divmoddi4.c b/src/VBox/Runtime/common/math/gcc/divmoddi4.c
new file mode 100644
index 00000000..c85498d3
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/divmoddi4.c
@@ -0,0 +1,84 @@
+/* $Id: divmoddi4.c $ */
+/** @file
+ * IPRT - __divmoddi4 implementation
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <iprt/stdint.h>
+#include <iprt/uint64.h>
+
+int64_t __divmoddi4(int64_t i64A, int64_t i64B, int64_t *pi64R);
+uint64_t __udivmoddi4(uint64_t u64A, uint64_t u64B, uint64_t *pu64R);
+
+/**
+ * __divmoddi4() implementation to satisfy external references from 32-bit code
+ * generated by gcc-7 or later (more likely with gcc-11).
+ *
+ * @param i64A The divident value.
+ * @param i64B The divisor value.
+ * @param pi64R A pointer to the reminder. May be NULL.
+ * @returns i64A / i64B
+ */
+int64_t __divmoddi4(int64_t i64A, int64_t i64B, int64_t *pi64R)
+{
+ int64_t i64Ret;
+ if (i64A >= 0)
+ {
+ /* Dividing two non-negative numbers is the same as unsigned division. */
+ if (i64B >= 0)
+ i64Ret = (int64_t)__udivmoddi4((uint64_t)i64A, (uint64_t)i64B, (uint64_t *)pi64R);
+ /* Dividing a non-negative number by a negative one yields a negative
+ result and positive remainder. */
+ else
+ i64Ret = -(int64_t)__udivmoddi4((uint64_t)i64A, (uint64_t)-i64B, (uint64_t *)pi64R);
+ }
+ else
+ {
+ uint64_t u64R;
+
+ /* Dividing a negative number by a non-negative one yields a negative
+ result and negative remainder. */
+ if (i64B >= 0)
+ i64Ret = -(int64_t)__udivmoddi4((uint64_t)-i64A, (uint64_t)i64B, &u64R);
+ /* Dividing two negative numbers yields a positive result and a
+ negative remainder. */
+ else
+ i64Ret = (int64_t)__udivmoddi4((uint64_t)-i64A, (uint64_t)-i64B, &u64R);
+
+ if (pi64R)
+ *pi64R = -(int64_t)u64R;
+ }
+
+ return i64Ret;
+}
+
diff --git a/src/VBox/Runtime/common/math/gcc/iordi3.c b/src/VBox/Runtime/common/math/gcc/iordi3.c
new file mode 100644
index 00000000..3d0a7eda
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/iordi3.c
@@ -0,0 +1,61 @@
+/* $NetBSD: iordi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)iordi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: iordi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Return a | b, in quad.
+ */
+quad_t
+__iordi3(a, b)
+ quad_t a, b;
+{
+ union uu aa, bb;
+
+ aa.q = a;
+ bb.q = b;
+ aa.ul[0] |= bb.ul[0];
+ aa.ul[1] |= bb.ul[1];
+ return (aa.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/lshldi3.c b/src/VBox/Runtime/common/math/gcc/lshldi3.c
new file mode 100644
index 00000000..611cb08d
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/lshldi3.c
@@ -0,0 +1,70 @@
+/* $NetBSD: lshldi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)lshldi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: lshldi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Shift an (unsigned) quad value left (logical shift left).
+ * This is the same as arithmetic shift left!
+ */
+quad_t
+__lshldi3(a, shift)
+ quad_t a;
+ qshift_t shift;
+{
+ union uu aa;
+
+ if (shift == 0)
+ return(a);
+ aa.q = a;
+ if (shift >= INT_BITS) {
+ aa.ul[H] = aa.ul[L] << (shift - INT_BITS);
+ aa.ul[L] = 0;
+ } else {
+ aa.ul[H] = (aa.ul[H] << shift) |
+ (aa.ul[L] >> (INT_BITS - shift));
+ aa.ul[L] <<= shift;
+ }
+ return (aa.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/lshrdi3.c b/src/VBox/Runtime/common/math/gcc/lshrdi3.c
new file mode 100644
index 00000000..3dba60c3
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/lshrdi3.c
@@ -0,0 +1,69 @@
+/* $NetBSD: lshrdi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)lshrdi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: lshrdi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Shift an (unsigned) quad value right (logical shift right).
+ */
+quad_t
+__lshrdi3(a, shift)
+ quad_t a;
+ qshift_t shift;
+{
+ union uu aa;
+
+ if (shift == 0)
+ return(a);
+ aa.q = a;
+ if (shift >= INT_BITS) {
+ aa.ul[L] = aa.ul[H] >> (shift - INT_BITS);
+ aa.ul[H] = 0;
+ } else {
+ aa.ul[L] = (aa.ul[L] >> shift) |
+ (aa.ul[H] << (INT_BITS - shift));
+ aa.ul[H] >>= shift;
+ }
+ return (aa.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/moddi3.c b/src/VBox/Runtime/common/math/gcc/moddi3.c
new file mode 100644
index 00000000..764ea01d
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/moddi3.c
@@ -0,0 +1,70 @@
+/* $NetBSD: moddi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)moddi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: moddi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Return remainder after dividing two signed quads.
+ *
+ * XXX we assume a % b < 0 iff a < 0, but this is actually machine-dependent.
+ */
+quad_t
+__moddi3(a, b)
+ quad_t a, b;
+{
+ u_quad_t ua, ub, ur;
+ int neg = 0;
+
+ ua = a;
+ ub = b;
+
+ if (a < 0)
+ ua = -ua, neg ^= 1;
+ if (b < 0)
+ ub = -ub;
+ (void)__qdivrem(ua, ub, &ur);
+ if (neg)
+ ur = -ur;
+ return (ur);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/muldi3.c b/src/VBox/Runtime/common/math/gcc/muldi3.c
new file mode 100644
index 00000000..370ef3d2
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/muldi3.c
@@ -0,0 +1,249 @@
+/* $NetBSD: muldi3.c,v 1.10 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)muldi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: muldi3.c,v 1.10 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Multiply two quads.
+ *
+ * Our algorithm is based on the following. Split incoming quad values
+ * u and v (where u,v >= 0) into
+ *
+ * u = 2^n u1 * u0 (n = number of bits in `u_int', usu. 32)
+ *
+ * and
+ *
+ * v = 2^n v1 * v0
+ *
+ * Then
+ *
+ * uv = 2^2n u1 v1 + 2^n u1 v0 + 2^n v1 u0 + u0 v0
+ * = 2^2n u1 v1 + 2^n (u1 v0 + v1 u0) + u0 v0
+ *
+ * Now add 2^n u1 v1 to the first term and subtract it from the middle,
+ * and add 2^n u0 v0 to the last term and subtract it from the middle.
+ * This gives:
+ *
+ * uv = (2^2n + 2^n) (u1 v1) +
+ * (2^n) (u1 v0 - u1 v1 + u0 v1 - u0 v0) +
+ * (2^n + 1) (u0 v0)
+ *
+ * Factoring the middle a bit gives us:
+ *
+ * uv = (2^2n + 2^n) (u1 v1) + [u1v1 = high]
+ * (2^n) (u1 - u0) (v0 - v1) + [(u1-u0)... = mid]
+ * (2^n + 1) (u0 v0) [u0v0 = low]
+ *
+ * The terms (u1 v1), (u1 - u0) (v0 - v1), and (u0 v0) can all be done
+ * in just half the precision of the original. (Note that either or both
+ * of (u1 - u0) or (v0 - v1) may be negative.)
+ *
+ * This algorithm is from Knuth vol. 2 (2nd ed), section 4.3.3, p. 278.
+ *
+ * Since C does not give us a `int * int = quad' operator, we split
+ * our input quads into two ints, then split the two ints into two
+ * shorts. We can then calculate `short * short = int' in native
+ * arithmetic.
+ *
+ * Our product should, strictly speaking, be a `long quad', with 128
+ * bits, but we are going to discard the upper 64. In other words,
+ * we are not interested in uv, but rather in (uv mod 2^2n). This
+ * makes some of the terms above vanish, and we get:
+ *
+ * (2^n)(high) + (2^n)(mid) + (2^n + 1)(low)
+ *
+ * or
+ *
+ * (2^n)(high + mid + low) + low
+ *
+ * Furthermore, `high' and `mid' can be computed mod 2^n, as any factor
+ * of 2^n in either one will also vanish. Only `low' need be computed
+ * mod 2^2n, and only because of the final term above.
+ */
+static quad_t __lmulq(u_int, u_int);
+
+quad_t
+__muldi3(a, b)
+ quad_t a, b;
+{
+ union uu u, v, low, prod;
+ u_int high, mid, udiff, vdiff;
+ int negall, negmid;
+#define u1 u.ul[H]
+#define u0 u.ul[L]
+#define v1 v.ul[H]
+#define v0 v.ul[L]
+
+ /*
+ * Get u and v such that u, v >= 0. When this is finished,
+ * u1, u0, v1, and v0 will be directly accessible through the
+ * int fields.
+ */
+ if (a >= 0)
+ u.q = a, negall = 0;
+ else
+ u.q = -a, negall = 1;
+ if (b >= 0)
+ v.q = b;
+ else
+ v.q = -b, negall ^= 1;
+
+ if (u1 == 0 && v1 == 0) {
+ /*
+ * An (I hope) important optimization occurs when u1 and v1
+ * are both 0. This should be common since most numbers
+ * are small. Here the product is just u0*v0.
+ */
+ prod.q = __lmulq(u0, v0);
+ } else {
+ /*
+ * Compute the three intermediate products, remembering
+ * whether the middle term is negative. We can discard
+ * any upper bits in high and mid, so we can use native
+ * u_int * u_int => u_int arithmetic.
+ */
+ low.q = __lmulq(u0, v0);
+
+ if (u1 >= u0)
+ negmid = 0, udiff = u1 - u0;
+ else
+ negmid = 1, udiff = u0 - u1;
+ if (v0 >= v1)
+ vdiff = v0 - v1;
+ else
+ vdiff = v1 - v0, negmid ^= 1;
+ mid = udiff * vdiff;
+
+ high = u1 * v1;
+
+ /*
+ * Assemble the final product.
+ */
+ prod.ul[H] = high + (negmid ? -mid : mid) + low.ul[L] +
+ low.ul[H];
+ prod.ul[L] = low.ul[L];
+ }
+ return (negall ? -prod.q : prod.q);
+#undef u1
+#undef u0
+#undef v1
+#undef v0
+}
+
+/*
+ * Multiply two 2N-bit ints to produce a 4N-bit quad, where N is half
+ * the number of bits in an int (whatever that is---the code below
+ * does not care as long as quad.h does its part of the bargain---but
+ * typically N==16).
+ *
+ * We use the same algorithm from Knuth, but this time the modulo refinement
+ * does not apply. On the other hand, since N is half the size of an int,
+ * we can get away with native multiplication---none of our input terms
+ * exceeds (UINT_MAX >> 1).
+ *
+ * Note that, for u_int l, the quad-precision result
+ *
+ * l << N
+ *
+ * splits into high and low ints as HHALF(l) and LHUP(l) respectively.
+ */
+static quad_t
+__lmulq(u_int u, u_int v)
+{
+ u_int u1, u0, v1, v0, udiff, vdiff, high, mid, low;
+ u_int prodh, prodl, was;
+ union uu prod;
+ int neg;
+
+ u1 = HHALF(u);
+ u0 = LHALF(u);
+ v1 = HHALF(v);
+ v0 = LHALF(v);
+
+ low = u0 * v0;
+
+ /* This is the same small-number optimization as before. */
+ if (u1 == 0 && v1 == 0)
+ return (low);
+
+ if (u1 >= u0)
+ udiff = u1 - u0, neg = 0;
+ else
+ udiff = u0 - u1, neg = 1;
+ if (v0 >= v1)
+ vdiff = v0 - v1;
+ else
+ vdiff = v1 - v0, neg ^= 1;
+ mid = udiff * vdiff;
+
+ high = u1 * v1;
+
+ /* prod = (high << 2N) + (high << N); */
+ prodh = high + HHALF(high);
+ prodl = LHUP(high);
+
+ /* if (neg) prod -= mid << N; else prod += mid << N; */
+ if (neg) {
+ was = prodl;
+ prodl -= LHUP(mid);
+ prodh -= HHALF(mid) + (prodl > was);
+ } else {
+ was = prodl;
+ prodl += LHUP(mid);
+ prodh += HHALF(mid) + (prodl < was);
+ }
+
+ /* prod += low << N */
+ was = prodl;
+ prodl += LHUP(low);
+ prodh += HHALF(low) + (prodl < was);
+ /* ... + low; */
+ if ((prodl += low) < low)
+ prodh++;
+
+ /* return 4N-bit product */
+ prod.ul[H] = prodh;
+ prod.ul[L] = prodl;
+ return (prod.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/negdi2.c b/src/VBox/Runtime/common/math/gcc/negdi2.c
new file mode 100644
index 00000000..2eafcffa
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/negdi2.c
@@ -0,0 +1,60 @@
+/* $NetBSD: negdi2.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)negdi2.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: negdi2.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Return -a (or, equivalently, 0 - a), in quad. See subdi3.c.
+ */
+quad_t
+__negdi2(a)
+ quad_t a;
+{
+ union uu aa, res;
+
+ aa.q = a;
+ res.ul[L] = -aa.ul[L];
+ res.ul[H] = -aa.ul[H] - (res.ul[L] > 0);
+ return (res.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/notdi2.c b/src/VBox/Runtime/common/math/gcc/notdi2.c
new file mode 100644
index 00000000..c671e037
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/notdi2.c
@@ -0,0 +1,61 @@
+/* $NetBSD: notdi2.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)notdi2.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: notdi2.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Return ~a. For some reason gcc calls this `one's complement' rather
+ * than `not'.
+ */
+quad_t
+__one_cmpldi2(a)
+ quad_t a;
+{
+ union uu aa;
+
+ aa.q = a;
+ aa.ul[0] = ~aa.ul[0];
+ aa.ul[1] = ~aa.ul[1];
+ return (aa.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/qdivrem.c b/src/VBox/Runtime/common/math/gcc/qdivrem.c
new file mode 100644
index 00000000..7ca2d38c
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/qdivrem.c
@@ -0,0 +1,285 @@
+/* $NetBSD: qdivrem.c,v 1.12 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)qdivrem.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: qdivrem.c,v 1.12 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+/*
+ * Multiprecision divide. This algorithm is from Knuth vol. 2 (2nd ed),
+ * section 4.3.1, pp. 257--259.
+ */
+
+#include "quad.h"
+
+#define B ((int)1 << HALF_BITS) /* digit base */
+
+/* Combine two `digits' to make a single two-digit number. */
+#define COMBINE(a, b) (((u_int)(a) << HALF_BITS) | (b))
+
+/* select a type for digits in base B: use unsigned short if they fit */
+#if UINT_MAX == 0xffffffffU && USHRT_MAX >= 0xffff
+typedef unsigned short digit;
+#else
+typedef u_int digit;
+#endif
+
+static void shl __P((digit *p, int len, int sh));
+
+/*
+ * __qdivrem(u, v, rem) returns u/v and, optionally, sets *rem to u%v.
+ *
+ * We do this in base 2-sup-HALF_BITS, so that all intermediate products
+ * fit within u_int. As a consequence, the maximum length dividend and
+ * divisor are 4 `digits' in this base (they are shorter if they have
+ * leading zeros).
+ */
+u_quad_t
+__qdivrem(uq, vq, arq)
+ u_quad_t uq, vq, *arq;
+{
+ union uu tmp;
+ digit *u, *v, *q;
+ digit v1, v2;
+ u_int qhat, rhat, t;
+ int m, n, d, j, i;
+ digit uspace[5], vspace[5], qspace[5];
+
+ /*
+ * Take care of special cases: divide by zero, and u < v.
+ */
+ if (vq == 0) {
+ /* divide by zero. */
+ static volatile const unsigned int zero = 0;
+
+ tmp.ul[H] = tmp.ul[L] = 1 / zero;
+ if (arq)
+ *arq = uq;
+ return (tmp.q);
+ }
+ if (uq < vq) {
+ if (arq)
+ *arq = uq;
+ return (0);
+ }
+ u = &uspace[0];
+ v = &vspace[0];
+ q = &qspace[0];
+
+ /*
+ * Break dividend and divisor into digits in base B, then
+ * count leading zeros to determine m and n. When done, we
+ * will have:
+ * u = (u[1]u[2]...u[m+n]) sub B
+ * v = (v[1]v[2]...v[n]) sub B
+ * v[1] != 0
+ * 1 < n <= 4 (if n = 1, we use a different division algorithm)
+ * m >= 0 (otherwise u < v, which we already checked)
+ * m + n = 4
+ * and thus
+ * m = 4 - n <= 2
+ */
+ tmp.uq = uq;
+ u[0] = 0;
+ u[1] = (digit)HHALF(tmp.ul[H]);
+ u[2] = (digit)LHALF(tmp.ul[H]);
+ u[3] = (digit)HHALF(tmp.ul[L]);
+ u[4] = (digit)LHALF(tmp.ul[L]);
+ tmp.uq = vq;
+ v[1] = (digit)HHALF(tmp.ul[H]);
+ v[2] = (digit)LHALF(tmp.ul[H]);
+ v[3] = (digit)HHALF(tmp.ul[L]);
+ v[4] = (digit)LHALF(tmp.ul[L]);
+ for (n = 4; v[1] == 0; v++) {
+ if (--n == 1) {
+ u_int rbj; /* r*B+u[j] (not root boy jim) */
+ digit q1, q2, q3, q4;
+
+ /*
+ * Change of plan, per exercise 16.
+ * r = 0;
+ * for j = 1..4:
+ * q[j] = floor((r*B + u[j]) / v),
+ * r = (r*B + u[j]) % v;
+ * We unroll this completely here.
+ */
+ t = v[2]; /* nonzero, by definition */
+ q1 = (digit)(u[1] / t);
+ rbj = COMBINE(u[1] % t, u[2]);
+ q2 = (digit)(rbj / t);
+ rbj = COMBINE(rbj % t, u[3]);
+ q3 = (digit)(rbj / t);
+ rbj = COMBINE(rbj % t, u[4]);
+ q4 = (digit)(rbj / t);
+ if (arq)
+ *arq = rbj % t;
+ tmp.ul[H] = COMBINE(q1, q2);
+ tmp.ul[L] = COMBINE(q3, q4);
+ return (tmp.q);
+ }
+ }
+
+ /*
+ * By adjusting q once we determine m, we can guarantee that
+ * there is a complete four-digit quotient at &qspace[1] when
+ * we finally stop.
+ */
+ for (m = 4 - n; u[1] == 0; u++)
+ m--;
+ for (i = 4 - m; --i >= 0;)
+ q[i] = 0;
+ q += 4 - m;
+
+ /*
+ * Here we run Program D, translated from MIX to C and acquiring
+ * a few minor changes.
+ *
+ * D1: choose multiplier 1 << d to ensure v[1] >= B/2.
+ */
+ d = 0;
+ for (t = v[1]; t < B / 2; t <<= 1)
+ d++;
+ if (d > 0) {
+ shl(&u[0], m + n, d); /* u <<= d */
+ shl(&v[1], n - 1, d); /* v <<= d */
+ }
+ /*
+ * D2: j = 0.
+ */
+ j = 0;
+ v1 = v[1]; /* for D3 -- note that v[1..n] are constant */
+ v2 = v[2]; /* for D3 */
+ do {
+ digit uj0, uj1, uj2;
+
+ /*
+ * D3: Calculate qhat (\^q, in TeX notation).
+ * Let qhat = min((u[j]*B + u[j+1])/v[1], B-1), and
+ * let rhat = (u[j]*B + u[j+1]) mod v[1].
+ * While rhat < B and v[2]*qhat > rhat*B+u[j+2],
+ * decrement qhat and increase rhat correspondingly.
+ * Note that if rhat >= B, v[2]*qhat < rhat*B.
+ */
+ uj0 = u[j + 0]; /* for D3 only -- note that u[j+...] change */
+ uj1 = u[j + 1]; /* for D3 only */
+ uj2 = u[j + 2]; /* for D3 only */
+ if (uj0 == v1) {
+ qhat = B;
+ rhat = uj1;
+ goto qhat_too_big;
+ } else {
+ u_int nn = COMBINE(uj0, uj1);
+ qhat = nn / v1;
+ rhat = nn % v1;
+ }
+ while (v2 * qhat > COMBINE(rhat, uj2)) {
+ qhat_too_big:
+ qhat--;
+ if ((rhat += v1) >= B)
+ break;
+ }
+ /*
+ * D4: Multiply and subtract.
+ * The variable `t' holds any borrows across the loop.
+ * We split this up so that we do not require v[0] = 0,
+ * and to eliminate a final special case.
+ */
+ for (t = 0, i = n; i > 0; i--) {
+ t = u[i + j] - v[i] * qhat - t;
+ u[i + j] = (digit)LHALF(t);
+ t = (B - HHALF(t)) & (B - 1);
+ }
+ t = u[j] - t;
+ u[j] = (digit)LHALF(t);
+ /*
+ * D5: test remainder.
+ * There is a borrow if and only if HHALF(t) is nonzero;
+ * in that (rare) case, qhat was too large (by exactly 1).
+ * Fix it by adding v[1..n] to u[j..j+n].
+ */
+ if (HHALF(t)) {
+ qhat--;
+ for (t = 0, i = n; i > 0; i--) { /* D6: add back. */
+ t += u[i + j] + v[i];
+ u[i + j] = (digit)LHALF(t);
+ t = HHALF(t);
+ }
+ u[j] = (digit)LHALF(u[j] + t);
+ }
+ q[j] = (digit)qhat;
+ } while (++j <= m); /* D7: loop on j. */
+
+ /*
+ * If caller wants the remainder, we have to calculate it as
+ * u[m..m+n] >> d (this is at most n digits and thus fits in
+ * u[m+1..m+n], but we may need more source digits).
+ */
+ if (arq) {
+ if (d) {
+ for (i = m + n; i > m; --i)
+ u[i] = (digit)(((u_int)u[i] >> d) |
+ LHALF((u_int)u[i - 1] << (HALF_BITS - d)));
+ u[i] = 0;
+ }
+ tmp.ul[H] = COMBINE(uspace[1], uspace[2]);
+ tmp.ul[L] = COMBINE(uspace[3], uspace[4]);
+ *arq = tmp.q;
+ }
+
+ tmp.ul[H] = COMBINE(qspace[1], qspace[2]);
+ tmp.ul[L] = COMBINE(qspace[3], qspace[4]);
+ return (tmp.q);
+}
+
+/*
+ * Shift p[0]..p[len] left `sh' bits, ignoring any bits that
+ * `fall out' the left (there never will be any such anyway).
+ * We may assume len >= 0. NOTE THAT THIS WRITES len+1 DIGITS.
+ */
+static void
+shl(digit *p, int len, int sh)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ p[i] = (digit)(LHALF((u_int)p[i] << sh) |
+ ((u_int)p[i + 1] >> (HALF_BITS - sh)));
+ p[i] = (digit)(LHALF((u_int)p[i] << sh));
+}
diff --git a/src/VBox/Runtime/common/math/gcc/quad.h b/src/VBox/Runtime/common/math/gcc/quad.h
new file mode 100644
index 00000000..c4197795
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/quad.h
@@ -0,0 +1,174 @@
+/* $NetBSD: quad.h,v 1.17 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)quad.h 8.1 (Berkeley) 6/4/93
+ */
+
+#ifndef IPRT_INCLUDED_COMMON_MATH_quad_h
+#define IPRT_INCLUDED_COMMON_MATH_quad_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+/*
+ * Quad arithmetic.
+ *
+ * This library makes the following assumptions:
+ *
+ * - The type long long (aka quad_t) exists.
+ *
+ * - A quad variable is exactly twice as long as `int'.
+ *
+ * - The machine's arithmetic is two's complement.
+ *
+ * This library can provide 128-bit arithmetic on a machine with 128-bit
+ * quads and 64-bit ints, for instance, or 96-bit arithmetic on machines
+ * with 48-bit ints.
+ */
+
+#if 0 /* iprt */
+#include <sys/types.h>
+#if !defined(_KERNEL) && !defined(_STANDALONE)
+#include <limits.h>
+#else
+#include <machine/limits.h>
+#endif
+#else /* iprt */
+# include <iprt/types.h>
+# include <iprt/nocrt/limits.h>
+# undef __P
+# define __P(a) a
+# undef __GNUC_PREREQ__
+# define __GNUC_PREREQ__(m1,m2) 1
+# if 1 /* ASSUMES: little endian */
+# define _QUAD_HIGHWORD 1
+# define _QUAD_LOWWORD 0
+# else
+# define _QUAD_HIGHWORD 0
+# define _QUAD_LOWWORD 1
+# endif
+# if !defined(RT_OS_LINUX) || !defined(__KERNEL__) /* (linux/types.h defines u_int) */
+ typedef unsigned int u_int;
+# endif
+# if !defined(RT_OS_SOLARIS)
+ typedef int64_t quad_t;
+# else
+# define quad_t int64_t
+# endif
+ typedef uint64_t u_quad_t;
+ typedef quad_t *qaddr_t;
+#endif /* iprt */
+
+/*
+ * Depending on the desired operation, we view a `long long' (aka quad_t) in
+ * one or more of the following formats.
+ */
+union uu {
+ quad_t q; /* as a (signed) quad */
+ u_quad_t uq; /* as an unsigned quad */
+ int sl[2]; /* as two signed ints */
+ u_int ul[2]; /* as two unsigned ints */
+};
+
+/*
+ * Define high and low parts of a quad_t.
+ */
+#define H _QUAD_HIGHWORD
+#define L _QUAD_LOWWORD
+
+/*
+ * Total number of bits in a quad_t and in the pieces that make it up.
+ * These are used for shifting, and also below for halfword extraction
+ * and assembly.
+ */
+#define QUAD_BITS (sizeof(quad_t) * CHAR_BIT)
+#define INT_BITS (sizeof(int) * CHAR_BIT)
+#define HALF_BITS (sizeof(int) * CHAR_BIT / 2)
+
+/*
+ * Extract high and low shortwords from longword, and move low shortword of
+ * longword to upper half of long, i.e., produce the upper longword of
+ * ((quad_t)(x) << (number_of_bits_in_int/2)). (`x' must actually be u_int.)
+ *
+ * These are used in the multiply code, to split a longword into upper
+ * and lower halves, and to reassemble a product as a quad_t, shifted left
+ * (sizeof(int)*CHAR_BIT/2).
+ */
+#define HHALF(x) ((u_int)(x) >> HALF_BITS)
+#define LHALF(x) ((u_int)(x) & (((int)1 << HALF_BITS) - 1))
+#define LHUP(x) ((u_int)(x) << HALF_BITS)
+
+/*
+ * XXX
+ * Compensate for gcc 1 vs gcc 2. Gcc 1 defines ?sh?di3's second argument
+ * as u_quad_t, while gcc 2 correctly uses int. Unfortunately, we still use
+ * both compilers.
+ */
+#if __GNUC_PREREQ__(2, 0) || defined(lint)
+typedef unsigned int qshift_t;
+#else
+typedef u_quad_t qshift_t;
+#endif
+
+RT_C_DECLS_BEGIN
+quad_t __adddi3 __P((quad_t, quad_t));
+quad_t __anddi3 __P((quad_t, quad_t));
+quad_t __ashldi3 __P((quad_t, qshift_t));
+quad_t __ashrdi3 __P((quad_t, qshift_t));
+int __cmpdi2 __P((quad_t, quad_t ));
+quad_t __divdi3 __P((quad_t, quad_t));
+quad_t __fixdfdi __P((double));
+quad_t __fixsfdi __P((float));
+u_quad_t __fixunsdfdi __P((double));
+u_quad_t __fixunssfdi __P((float));
+double __floatdidf __P((quad_t));
+float __floatdisf __P((quad_t));
+double __floatunsdidf __P((u_quad_t));
+quad_t __iordi3 __P((quad_t, quad_t));
+quad_t __lshldi3 __P((quad_t, qshift_t));
+quad_t __lshrdi3 __P((quad_t, qshift_t));
+quad_t __moddi3 __P((quad_t, quad_t));
+quad_t __muldi3 __P((quad_t, quad_t));
+quad_t __negdi2 __P((quad_t));
+quad_t __one_cmpldi2 __P((quad_t));
+u_quad_t __qdivrem __P((u_quad_t, u_quad_t, u_quad_t *));
+quad_t __subdi3 __P((quad_t, quad_t));
+int __ucmpdi2 __P((u_quad_t, u_quad_t));
+u_quad_t __udivdi3 __P((u_quad_t, u_quad_t ));
+u_quad_t __umoddi3 __P((u_quad_t, u_quad_t ));
+quad_t __xordi3 __P((quad_t, quad_t));
+RT_C_DECLS_END
+
+#endif
+
diff --git a/src/VBox/Runtime/common/math/gcc/subdi3.c b/src/VBox/Runtime/common/math/gcc/subdi3.c
new file mode 100644
index 00000000..2751acc2
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/subdi3.c
@@ -0,0 +1,62 @@
+/* $NetBSD: subdi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)subdi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: subdi3.c,v 1.9 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Subtract two quad values. This is trivial since a one-bit carry
+ * from a single u_int difference x-y occurs if and only if (x-y) > x.
+ */
+quad_t
+__subdi3(a, b)
+ quad_t a, b;
+{
+ union uu aa, bb, diff;
+
+ aa.q = a;
+ bb.q = b;
+ diff.ul[L] = aa.ul[L] - bb.ul[L];
+ diff.ul[H] = aa.ul[H] - bb.ul[H] - (diff.ul[L] > aa.ul[L]);
+ return (diff.q);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/ucmpdi2.c b/src/VBox/Runtime/common/math/gcc/ucmpdi2.c
new file mode 100644
index 00000000..47d79164
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/ucmpdi2.c
@@ -0,0 +1,61 @@
+/* $NetBSD: ucmpdi2.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)ucmpdi2.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: ucmpdi2.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Return 0, 1, or 2 as a <, =, > b respectively.
+ * Neither a nor b are considered signed.
+ */
+int
+__ucmpdi2(a, b)
+ u_quad_t a, b;
+{
+ union uu aa, bb;
+
+ aa.uq = a;
+ bb.uq = b;
+ return (aa.ul[H] < bb.ul[H] ? 0 : aa.ul[H] > bb.ul[H] ? 2 :
+ aa.ul[L] < bb.ul[L] ? 0 : aa.ul[L] > bb.ul[L] ? 2 : 1);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/udivdi3.c b/src/VBox/Runtime/common/math/gcc/udivdi3.c
new file mode 100644
index 00000000..9069f4d2
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/udivdi3.c
@@ -0,0 +1,56 @@
+/* $NetBSD: udivdi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)udivdi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: udivdi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Divide two unsigned quads.
+ */
+u_quad_t
+__udivdi3(a, b)
+ u_quad_t a, b;
+{
+
+ return (__qdivrem(a, b, (u_quad_t *)0));
+}
diff --git a/src/VBox/Runtime/common/math/gcc/udivmoddi4.c b/src/VBox/Runtime/common/math/gcc/udivmoddi4.c
new file mode 100644
index 00000000..b6173b46
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/udivmoddi4.c
@@ -0,0 +1,65 @@
+/* $Id: udivmoddi4.c $ */
+/** @file
+ * IPRT - __udivmoddi4 implementation
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <iprt/stdint.h>
+#include <iprt/uint64.h>
+
+uint64_t __udivmoddi4(uint64_t u64A, uint64_t u64B, uint64_t *pu64R);
+
+/**
+ * __udivmoddi4() implementation to satisfy external references from 32-bit
+ * code generated by gcc-7 or later.
+ *
+ * @param u64A The divident value.
+ * @param u64B The divisor value.
+ * @param pu64R A pointer to the reminder. May be NULL.
+ * @returns u64A / u64B
+ */
+uint64_t __udivmoddi4(uint64_t u64A, uint64_t u64B, uint64_t *pu64R)
+{
+ RTUINT64U Divident;
+ RTUINT64U Divisor;
+ RTUINT64U Quotient;
+ RTUINT64U Reminder;
+ Divident.u = u64A;
+ Divisor.u = u64B;
+ Quotient.u = 0; /* shut up gcc 10 */
+ Reminder.u = 0; /* shut up gcc 10 */
+ RTUInt64DivRem(&Quotient, &Reminder, &Divident, &Divisor);
+ if (pu64R)
+ *pu64R = Reminder.u;
+ return Quotient.u;
+}
diff --git a/src/VBox/Runtime/common/math/gcc/umoddi3.c b/src/VBox/Runtime/common/math/gcc/umoddi3.c
new file mode 100644
index 00000000..2e65ecab
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/umoddi3.c
@@ -0,0 +1,58 @@
+/* $NetBSD: umoddi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)umoddi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: umoddi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Return remainder after dividing two unsigned quads.
+ */
+u_quad_t
+__umoddi3(a, b)
+ u_quad_t a, b;
+{
+ u_quad_t r;
+
+ (void)__qdivrem(a, b, &r);
+ return (r);
+}
diff --git a/src/VBox/Runtime/common/math/gcc/xordi3.c b/src/VBox/Runtime/common/math/gcc/xordi3.c
new file mode 100644
index 00000000..aa5db229
--- /dev/null
+++ b/src/VBox/Runtime/common/math/gcc/xordi3.c
@@ -0,0 +1,61 @@
+/* $NetBSD: xordi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $ */
+
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This software was developed by the Computer Systems Engineering group
+ * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and
+ * contributed to Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)xordi3.c 8.1 (Berkeley) 6/4/93";
+#else
+__RCSID("$NetBSD: xordi3.c,v 1.8 2005/12/11 12:24:37 christos Exp $");
+#endif
+#endif*/ /* LIBC_SCCS and not lint */
+
+#include "quad.h"
+
+/*
+ * Return a ^ b, in quad.
+ */
+quad_t
+__xordi3(a, b)
+ quad_t a, b;
+{
+ union uu aa, bb;
+
+ aa.q = a;
+ bb.q = b;
+ aa.ul[0] ^= bb.ul[0];
+ aa.ul[1] ^= bb.ul[1];
+ return (aa.q);
+}
diff --git a/src/VBox/Runtime/common/math/isinf.cpp b/src/VBox/Runtime/common/math/isinf.cpp
new file mode 100644
index 00000000..6632c888
--- /dev/null
+++ b/src/VBox/Runtime/common/math/isinf.cpp
@@ -0,0 +1,57 @@
+/* $Id: isinf.cpp $ */
+/** @file
+ * IPRT - No-CRT - isinf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef isinf
+int RT_NOCRT(isinf)(double rd)
+{
+ AssertCompile(sizeof(rd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = rd;
+ return RTFLOAT64U_IS_INF(&u);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(isinf);
+
diff --git a/src/VBox/Runtime/common/math/isnan.cpp b/src/VBox/Runtime/common/math/isnan.cpp
new file mode 100644
index 00000000..acd0d0a4
--- /dev/null
+++ b/src/VBox/Runtime/common/math/isnan.cpp
@@ -0,0 +1,57 @@
+/* $Id: isnan.cpp $ */
+/** @file
+ * IPRT - No-CRT - isnan().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef isnan
+int RT_NOCRT(isnan)(double rd)
+{
+ AssertCompile(sizeof(rd) == sizeof(RTFLOAT64U));
+ RTFLOAT64U u;
+ u.rd = rd;
+ return RTFLOAT64U_IS_NAN(&u);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(isnan);
+
diff --git a/src/VBox/Runtime/common/math/isnanf.cpp b/src/VBox/Runtime/common/math/isnanf.cpp
new file mode 100644
index 00000000..cb6f9e8a
--- /dev/null
+++ b/src/VBox/Runtime/common/math/isnanf.cpp
@@ -0,0 +1,57 @@
+/* $Id: isnanf.cpp $ */
+/** @file
+ * IPRT - No-CRT - isnanf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/assertcompile.h>
+#include <iprt/assert.h>
+
+
+#undef isnanf
+int RT_NOCRT(isnanf)(float r32)
+{
+ AssertCompile(sizeof(r32) == sizeof(RTFLOAT32U));
+ RTFLOAT32U u;
+ u.r = r32;
+ return RTFLOAT32U_IS_NAN(&u);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(isnanf);
+
diff --git a/src/VBox/Runtime/common/math/ldexp.asm b/src/VBox/Runtime/common/math/ldexp.asm
new file mode 100644
index 00000000..257c4172
--- /dev/null
+++ b/src/VBox/Runtime/common/math/ldexp.asm
@@ -0,0 +1,89 @@
+; $Id: ldexp.asm $
+;; @file
+; IPRT - No-CRT ldexp - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Computes rd * 2^exp
+; @returns st(0) / xmm0
+; @param rd [rbp + xCB*2] / xmm0
+; @param exp [ebp + 10h] gcc:edi msc:edx
+RT_NOCRT_BEGINPROC ldexp
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the value and scaling factor.
+ ;
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_GCC
+ mov [rbp - 8h], edi
+ %else
+ mov [rbp - 8h], edx
+ %endif
+ fild dword [rbp - 8h]
+ movsd [rbp - 10h], xmm0
+ fld qword [rbp - 10h]
+%else
+ fild dword [xBP + xCB*2 + 8]
+ fld qword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Do the scaling and return the result.
+ ;
+ fscale
+
+ fstp st1
+%ifdef RT_ARCH_AMD64
+ fstp qword [rbp - 10h]
+ movsd xmm0, [rbp - 10h]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(ldexp)
+
diff --git a/src/VBox/Runtime/common/math/ldexpf.asm b/src/VBox/Runtime/common/math/ldexpf.asm
new file mode 100644
index 00000000..68cce655
--- /dev/null
+++ b/src/VBox/Runtime/common/math/ldexpf.asm
@@ -0,0 +1,89 @@
+; $Id: ldexpf.asm $
+;; @file
+; IPRT - No-CRT ldexpf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Computes r32 * 2^exp
+; @returns st(0) / xmm0
+; @param r32 [rbp + xCB*2] / xmm0
+; @param exp [ebp + 0ch] gcc:edi msc:edx
+RT_NOCRT_BEGINPROC ldexpf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the value and scaling factor.
+ ;
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_GCC
+ mov [rbp - 8h], edi
+ %else
+ mov [rbp - 8h], edx
+ %endif
+ fild dword [rbp - 8h]
+ movss [rbp - 10h], xmm0
+ fld dword [rbp - 10h]
+%else
+ fild dword [eBP + xCB*2 + 4]
+ fld dword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Do the scaling and return the result.
+ ;
+ fscale
+
+ fstp st1
+%ifdef RT_ARCH_AMD64
+ fstp dword [rbp - 10h]
+ movss xmm0, [rbp - 10h]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(ldexpf)
+
diff --git a/src/VBox/Runtime/common/math/ldexpl.asm b/src/VBox/Runtime/common/math/ldexpl.asm
new file mode 100644
index 00000000..6ad3591e
--- /dev/null
+++ b/src/VBox/Runtime/common/math/ldexpl.asm
@@ -0,0 +1,80 @@
+; $Id: ldexpl.asm $
+;; @file
+; IPRT - No-CRT ldexpl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Computes lrd * 2^exp
+; @returns st(0)
+; @param lrd [rbp + xCB*2]
+; @param exp [ebp + 14h] gcc:edi msc:edx
+RT_NOCRT_BEGINPROC ldexpl
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the value and scaling factor.
+ ;
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_GCC
+ mov [rbp - 10h], edi
+ %else
+ mov [rbp - 10h], edx
+ %endif
+ fild dword [rbp - 10h]
+%else
+ fild dword [ebp + xCB*2 + RTLRD_CB]
+%endif
+ fld tword [xBP + xCB*2]
+
+ ;
+ ; Do the scaling and return the result.
+ ;
+ fscale
+ fstp st1
+
+ leave
+ ret
+ENDPROC RT_NOCRT(ldexpl)
+
diff --git a/src/VBox/Runtime/common/math/llrint.asm b/src/VBox/Runtime/common/math/llrint.asm
new file mode 100644
index 00000000..bcc7d071
--- /dev/null
+++ b/src/VBox/Runtime/common/math/llrint.asm
@@ -0,0 +1,72 @@
+; $Id: llrint.asm $
+;; @file
+; IPRT - No-CRT llrint - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Round rd to the nearest integer value, rounding according to the current rounding direction.
+; @returns 32-bit: edx:eax 64-bit: rax
+; @param rd 32-bit: [esp + 4h] 64-bit: xmm0
+RT_NOCRT_BEGINPROC llrint
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_X86
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ cvtsd2si rax, xmm0
+%else
+ fld qword [ebp + 8h]
+ fistp qword [esp]
+ fwait
+ mov eax, [esp]
+ mov edx, [esp + 4]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(llrint)
+
diff --git a/src/VBox/Runtime/common/math/llrintf.asm b/src/VBox/Runtime/common/math/llrintf.asm
new file mode 100644
index 00000000..6b741d76
--- /dev/null
+++ b/src/VBox/Runtime/common/math/llrintf.asm
@@ -0,0 +1,72 @@
+; $Id: llrintf.asm $
+;; @file
+; IPRT - No-CRT llrintf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Round rf to the nearest integer value, rounding according to the current rounding direction.
+; @returns 32-bit: edx:eax 64-bit: rax
+; @param rf 32-bit: [esp + 4h] 64-bit: xmm0
+RT_NOCRT_BEGINPROC llrintf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_X86
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ cvtss2si rax, xmm0
+%else
+ fld dword [ebp + 8h]
+ fistp qword [esp]
+ fwait
+ mov eax, [esp]
+ mov edx, [esp + 4]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(llrintf)
+
diff --git a/src/VBox/Runtime/common/math/llrintl.asm b/src/VBox/Runtime/common/math/llrintl.asm
new file mode 100644
index 00000000..d21b8b4d
--- /dev/null
+++ b/src/VBox/Runtime/common/math/llrintl.asm
@@ -0,0 +1,70 @@
+; $Id: llrintl.asm $
+;; @file
+; IPRT - No-CRT llrintl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Round lrd to the nearest integer value, rounding according to the current rounding direction.
+; @returns 32-bit: edx:eax 64-bit: rax
+; @param lrd [rbp + xCB*2]
+RT_NOCRT_BEGINPROC llrintl
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ fld tword [xBP + xCB*2]
+ fistp qword [xSP]
+ fwait
+%ifdef RT_ARCH_AMD64
+ mov rax, [xSP]
+%else
+ mov eax, [xSP]
+ mov edx, [xSP + 4]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(llrintl)
+
diff --git a/src/VBox/Runtime/common/math/llround.cpp b/src/VBox/Runtime/common/math/llround.cpp
new file mode 100644
index 00000000..dd8fde3a
--- /dev/null
+++ b/src/VBox/Runtime/common/math/llround.cpp
@@ -0,0 +1,65 @@
+/* $Id: llround.cpp $ */
+/** @file
+ * IPRT - No-CRT - llround().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/fenv.h>
+
+
+#undef llround
+long long RT_NOCRT(llround)(double rd)
+{
+ if (isfinite(rd))
+ {
+ rd = RT_NOCRT(round)(rd);
+ if (rd >= (double)LLONG_MIN && rd <= (double)LLONG_MAX)
+ return (long long)rd;
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ return rd > 0.0 ? LLONG_MAX : LLONG_MIN;
+ }
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ if (RT_NOCRT(isinf)(rd) && rd < 0.0)
+ return LLONG_MIN;
+ return LLONG_MAX;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(llround);
+
diff --git a/src/VBox/Runtime/common/math/llroundf.cpp b/src/VBox/Runtime/common/math/llroundf.cpp
new file mode 100644
index 00000000..ae58186f
--- /dev/null
+++ b/src/VBox/Runtime/common/math/llroundf.cpp
@@ -0,0 +1,65 @@
+/* $Id: llroundf.cpp $ */
+/** @file
+ * IPRT - No-CRT - llroundf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/fenv.h>
+
+
+#undef llroundf
+long long RT_NOCRT(llroundf)(float r32)
+{
+ if (isfinite(r32))
+ {
+ r32 = RT_NOCRT(roundf)(r32);
+ if (r32 >= (float)LLONG_MIN && r32 <= (float)LLONG_MAX)
+ return (long long)r32;
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ return r32 > 0.0f ? LLONG_MAX : LLONG_MIN;
+ }
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ if (RT_NOCRT(__isinff)(r32) && r32 < 0.0)
+ return LLONG_MIN;
+ return LLONG_MAX;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(llroundf);
+
diff --git a/src/VBox/Runtime/common/math/llroundl.cpp b/src/VBox/Runtime/common/math/llroundl.cpp
new file mode 100644
index 00000000..439fad8f
--- /dev/null
+++ b/src/VBox/Runtime/common/math/llroundl.cpp
@@ -0,0 +1,65 @@
+/* $Id: llroundl.cpp $ */
+/** @file
+ * IPRT - No-CRT - llroundl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/fenv.h>
+
+
+#undef llroundl
+long long RT_NOCRT(llroundl)(long double lrd)
+{
+ if (isfinite(lrd))
+ {
+ lrd = RT_NOCRT(roundl)(lrd);
+ if (lrd >= (long double)LLONG_MIN && lrd <= (long double)LLONG_MAX)
+ return (long long)lrd;
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ return lrd > 0.0L ? LLONG_MAX : LLONG_MIN;
+ }
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ if (RT_NOCRT(__isinfl)(lrd) && lrd < 0.0)
+ return LLONG_MIN;
+ return LLONG_MAX;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(llroundl);
+
diff --git a/src/VBox/Runtime/common/math/log.asm b/src/VBox/Runtime/common/math/log.asm
new file mode 100644
index 00000000..b94f3ba6
--- /dev/null
+++ b/src/VBox/Runtime/common/math/log.asm
@@ -0,0 +1,97 @@
+; $Id: log.asm $
+;; @file
+; IPRT - No-CRT log - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; compute the natural logarithm of rd
+; @returns st(0) / xmm0
+; @param rd [rbp + xCB*2] / xmm0
+RT_NOCRT_BEGINPROC log
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+ fldln2 ; st0=log(2)
+%ifdef RT_ARCH_AMD64
+ movsd [xBP - 10h], xmm0
+ fld qword [xBP - 10h]
+%else
+ fld qword [xBP + xCB*2] ; st1=log(2) st0=lrd
+%endif
+ fld st0 ; st1=log(2) st0=lrd st0=lrd
+ fsub qword [.one xWrtRIP] ; st2=log(2) st1=lrd st0=lrd-1.0
+ fld st0 ; st3=log(2) st2=lrd st1=lrd-1.0 st0=lrd-1.0
+
+ fabs ; st3=log(2) st2=lrd st1=lrd-1.0 st0=abs(lrd-1.0)
+ fcomp qword [.limit xWrtRIP] ; st2=log(2) st1=lrd st0=lrd-1.0
+ fnstsw ax
+ and eax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ jnz .use_st1
+
+ fstp st0 ; st1=log(2) st0=lrd
+ fyl2x ; log(lrd)
+ jmp .done
+
+.use_st1:
+ fstp st1 ; st1=log(2) st0=lrd-1.0
+ fyl2xp1 ; log(lrd)
+
+.done:
+%ifdef RT_ARCH_AMD64
+ fstp qword [xBP - 10h]
+ movsd xmm0, [xBP - 10h]
+%endif
+ leave
+ ret
+
+ALIGNCODE(8)
+.one: dq 1.0
+.limit: dq 0.29
+ENDPROC RT_NOCRT(log)
+
diff --git a/src/VBox/Runtime/common/math/log2.asm b/src/VBox/Runtime/common/math/log2.asm
new file mode 100644
index 00000000..f1fbf283
--- /dev/null
+++ b/src/VBox/Runtime/common/math/log2.asm
@@ -0,0 +1,229 @@
+; $Id: log2.asm $
+;; @file
+; IPRT - No-CRT log2 - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+extern NAME(RT_NOCRT(feraiseexcept))
+
+;;
+; Compute the log2 of rd
+; @returns st(0) / xmm0
+; @param rd [xSP + xCB*2] / xmm0
+RT_NOCRT_BEGINPROC log2
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd [xBP - 10h], xmm0
+ fld qword [xBP - 10h]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Weed out non-normal values.
+ ;
+ fxam
+ fnstsw ax
+ mov cx, ax
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals
+ je .finite
+ cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity.
+ je .inf
+ jmp .nan
+
+.finite:
+ ; Negative number?
+ test cx, X86_FSW_C1
+ jnz .negative
+
+ ; Is it +1.0?
+ fld1
+ fcomip st1
+ jz .plus_one
+
+ ;
+ ; The fyl2xp1 instruction (ST1=ST1*log2(ST0+1.0), popping ST0) has a
+ ; valid ST0 range of 1(1-sqrt(0.5)) (approx 0.29289321881) on both
+ ; sides of zero. We try use it if we can.
+ ;
+.above_one:
+ ; For both fyl2xp1 and fyl2xp1 we need st1=1.0.
+ fld1
+ fxch st0, st1 ; -> st0=input; st1=1.0
+
+ ; Check if the input is within the fyl2xp1 range.
+ fld qword [.s_r64AbsFyL2xP1InputMax xWrtRIP]
+ fcomip st0, st1
+ jbe .cannot_use_fyl2xp1
+
+ fld qword [.s_r64AbsFyL2xP1InputMin xWrtRIP]
+ fcomip st0, st1
+ jae .cannot_use_fyl2xp1
+
+ ; Do the calculation.
+.use_fyl2xp1:
+ fsub st0, st1 ; -> st0=input-1; st1=1.0
+ fyl2xp1 ; -> st0=1.0*log2(st0+1.0)
+ jmp .return_val
+
+.cannot_use_fyl2xp1:
+ fyl2x ; -> st0=1.0*log2(st0)
+
+ ;
+ ; Return st0.
+ ;
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp qword [xBP - 10h]
+ movsd xmm0, [xBP - 10h]
+%endif
+.return:
+ leave
+ ret
+
+
+ ;
+ ; +1.0: Return +0.0.
+ ;
+.plus_one:
+ ffreep st0
+ fldz
+ jmp .return_val
+
+ ;
+ ; Negative numbers: Return NaN and raise invalid operation.
+ ;
+.negative:
+.minus_inf:
+ ; Raise invalid operation
+%ifdef RT_ARCH_X86
+ mov dword [xSP], X86_FSW_IE
+%elifdef ASM_CALL64_GCC
+ mov edi, X86_FSW_IE
+%elifdef ASM_CALL64_MSC
+ mov ecx, X86_FSW_IE
+%else
+ %error calling conv.
+%endif
+ call NAME(RT_NOCRT(feraiseexcept))
+
+ ; Load NaN
+%ifdef RT_ARCH_AMD64
+ movsd xmm0, [.s_r64NaN xWrtRIP]
+%else
+ fld qword [.s_r64NaN xWrtRIP]
+%endif
+ jmp .return
+
+ ;
+ ; +/-0.0: Return inf and raise divide by zero error.
+ ;
+.zero:
+ ffreep st0
+
+ ; Raise div/0
+%ifdef RT_ARCH_X86
+ mov dword [xSP], X86_FSW_ZE
+%elifdef ASM_CALL64_GCC
+ mov edi, X86_FSW_ZE
+%elifdef ASM_CALL64_MSC
+ mov ecx, X86_FSW_ZE
+%else
+ %error calling conv.
+%endif
+ call NAME(RT_NOCRT(feraiseexcept))
+
+ ; Load +Inf
+%ifdef RT_ARCH_AMD64
+ movsd xmm0, [.s_r64MinusInf xWrtRIP]
+%else
+ fld qword [.s_r64MinusInf xWrtRIP]
+%endif
+ jmp .return
+
+ ;
+ ; -Inf: Same as other negative numbers
+ ; +Inf: return +Inf. Join path with NaN.
+ ;
+.inf:
+ test cx, X86_FSW_C1 ; sign bit
+ jnz .minus_inf
+
+ ;
+ ; NaN: Return the input NaN value as is, if we can.
+ ;
+.nan:
+%ifdef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ jmp .return
+
+ALIGNCODE(8)
+ ;; The fyl2xp1 instruction only works between +/-1(1-sqrt(0.5)).
+ ; These two variables is that range + 1.0, so we can compare directly
+ ; with the input w/o any extra fsub and fabs work.
+.s_r64AbsFyL2xP1InputMin:
+ dq 0.708 ; -0.292 + 1.0
+.s_r64AbsFyL2xP1InputMax:
+ dq 1.292
+;.s_r64AbsFyL2xP1Range:
+; dq 0.292
+.s_r64MinusInf:
+ dq RTFLOAT64U_INF_MINUS
+.s_r64NaN:
+ dq RTFLOAT64U_QNAN_MINUS
+ENDPROC RT_NOCRT(log2)
+
diff --git a/src/VBox/Runtime/common/math/log2f.asm b/src/VBox/Runtime/common/math/log2f.asm
new file mode 100644
index 00000000..c9b41696
--- /dev/null
+++ b/src/VBox/Runtime/common/math/log2f.asm
@@ -0,0 +1,227 @@
+; $Id: log2f.asm $
+;; @file
+; IPRT - No-CRT log2f - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+extern NAME(RT_NOCRT(feraiseexcept))
+
+;;
+; Compute the log2f of rf
+; @returns st(0) / xmm0
+; @param rf [xSP + xCB*2] / xmm0
+RT_NOCRT_BEGINPROC log2f
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movss [xBP - 10h], xmm0
+ fld dword [xBP - 10h]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Weed out non-normal values.
+ ;
+ fxam
+ fnstsw ax
+ mov cx, ax
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals
+ je .finite
+ cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity.
+ je .inf
+ jmp .nan
+
+.finite:
+ ; Negative number?
+ test cx, X86_FSW_C1
+ jnz .negative
+
+ ; Is it +1.0?
+ fld1
+ fcomip st1
+ jz .plus_one
+
+ ;
+ ; The fyl2xp1 instruction (ST1=ST1*log2(ST0+1.0), popping ST0) has a
+ ; valid ST0 range of 1(1-sqrt(0.5)) (approx 0.29289321881) on both
+ ; sides of zero. We try use it if we can.
+ ;
+.above_one:
+ ; For both fyl2xp1 and fyl2xp1 we need st1=1.0.
+ fld1
+ fxch st0, st1 ; -> st0=input; st1=1.0
+
+ ; Check if the input is within the fyl2xp1 range.
+ fld qword [.s_r64AbsFyL2xP1InputMax xWrtRIP]
+ fcomip st0, st1
+ jbe .cannot_use_fyl2xp1
+
+ fld qword [.s_r64AbsFyL2xP1InputMin xWrtRIP]
+ fcomip st0, st1
+ jae .cannot_use_fyl2xp1
+
+ ; Do the calculation.
+.use_fyl2xp1:
+ fsub st0, st1 ; -> st0=input-1; st1=1.0
+ fyl2xp1 ; -> st0=1.0*log2(st0+1.0)
+ jmp .return_val
+
+.cannot_use_fyl2xp1:
+ fyl2x ; -> st0=1.0*log2(st0)
+
+ ;
+ ; Run st0.
+ ;
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp dword [xBP - 10h]
+ movss xmm0, [xBP - 10h]
+%endif
+.return:
+ leave
+ ret
+
+
+ ;
+ ; +1.0: Return +0.0.
+ ;
+.plus_one:
+ ffreep st0
+ fldz
+ jmp .return_val
+
+ ;
+ ; Negative numbers: Return NaN and raise invalid operation.
+ ;
+.negative:
+.minus_inf:
+ ; Raise invalid operation
+%ifdef RT_ARCH_X86
+ mov dword [xSP], X86_FSW_IE
+%elifdef ASM_CALL64_GCC
+ mov edi, X86_FSW_IE
+%elifdef ASM_CALL64_MSC
+ mov ecx, X86_FSW_IE
+%else
+ %error calling conv.
+%endif
+ call NAME(RT_NOCRT(feraiseexcept))
+
+ ; Load NaN
+%ifdef RT_ARCH_AMD64
+ movss xmm0, [.s_r32NaN xWrtRIP]
+%else
+ fld dword [.s_r32NaN xWrtRIP]
+%endif
+ jmp .return
+
+ ;
+ ; +/-0.0: Return inf and raise divide by zero error.
+ ;
+.zero:
+ ffreep st0
+
+ ; Raise div/0
+%ifdef RT_ARCH_X86
+ mov dword [xSP], X86_FSW_ZE
+%elifdef ASM_CALL64_GCC
+ mov edi, X86_FSW_ZE
+%elifdef ASM_CALL64_MSC
+ mov ecx, X86_FSW_ZE
+%else
+ %error calling conv.
+%endif
+ call NAME(RT_NOCRT(feraiseexcept))
+
+ ; Load +Inf
+%ifdef RT_ARCH_AMD64
+ movss xmm0, [.s_r32MinusInf xWrtRIP]
+%else
+ fld dword [.s_r32MinusInf xWrtRIP]
+%endif
+ jmp .return
+
+ ;
+ ; -Inf: Same as other negative numbers
+ ; +Inf: return +Inf. Join path with NaN.
+ ;
+.inf:
+ test cx, X86_FSW_C1 ; sign bit
+ jnz .minus_inf
+
+ ;
+ ; NaN: Return the input NaN value as is, if we can.
+ ;
+.nan:
+%ifdef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ jmp .return
+
+ALIGNCODE(8)
+ ;; The fyl2xp1 instruction only works between +/-1(1-sqrt(0.5)).
+ ; These two variables is that range + 1.0, so we can compare directly
+ ; with the input w/o any extra fsub and fabs work.
+.s_r64AbsFyL2xP1InputMin:
+ dq 0.708 ; -0.292 + 1.0
+.s_r64AbsFyL2xP1InputMax:
+ dq 1.292
+.s_r32MinusInf:
+ dd RTFLOAT32U_INF_MINUS
+.s_r32NaN:
+ dd RTFLOAT32U_QNAN_MINUS
+ENDPROC RT_NOCRT(log2f)
+
diff --git a/src/VBox/Runtime/common/math/logf.asm b/src/VBox/Runtime/common/math/logf.asm
new file mode 100644
index 00000000..53ba0c6a
--- /dev/null
+++ b/src/VBox/Runtime/common/math/logf.asm
@@ -0,0 +1,97 @@
+; $Id: logf.asm $
+;; @file
+; IPRT - No-CRT logf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; compute the natural logarithm of r32
+; @returns st(0) / xmm0
+; @param r32 [rbp + xCB*2] / xmm0
+RT_NOCRT_BEGINPROC logf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+ fldln2 ; st0=log(2)
+%ifdef RT_ARCH_AMD64
+ movss [xBP - 10h], xmm0
+ fld dword [xBP - 10h]
+%else
+ fld dword [xBP + xCB*2] ; st1=log(2) st0=lrd
+%endif
+ fld st0 ; st1=log(2) st0=lrd st0=lrd
+ fsub qword [.one xWrtRIP] ; st2=log(2) st1=lrd st0=lrd-1.0
+ fld st0 ; st3=log(2) st2=lrd st1=lrd-1.0 st0=lrd-1.0
+
+ fabs ; st3=log(2) st2=lrd st1=lrd-1.0 st0=abs(lrd-1.0)
+ fcomp qword [.limit xWrtRIP] ; st2=log(2) st1=lrd st0=lrd-1.0
+ fnstsw ax
+ and eax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ jnz .use_st1
+
+ fstp st0 ; st1=log(2) st0=lrd
+ fyl2x ; log(lrd)
+ jmp .done
+
+.use_st1:
+ fstp st1 ; st1=log(2) st0=lrd-1.0
+ fyl2xp1 ; log(lrd)
+
+.done:
+%ifdef RT_ARCH_AMD64
+ fstp dword [xBP - 10h]
+ movss xmm0, [xBP - 10h]
+%endif
+ leave
+ ret
+
+ALIGNCODE(8)
+.one: dq 1.0
+.limit: dq 0.29
+ENDPROC RT_NOCRT(logf)
+
diff --git a/src/VBox/Runtime/common/math/logl.asm b/src/VBox/Runtime/common/math/logl.asm
new file mode 100644
index 00000000..6c9127c3
--- /dev/null
+++ b/src/VBox/Runtime/common/math/logl.asm
@@ -0,0 +1,84 @@
+; $Id: logl.asm $
+;; @file
+; IPRT - No-CRT logl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; compute the natural logarithm of lrd
+; @returns st(0)
+; @param lrd [rbp + xCB*2]
+RT_NOCRT_BEGINPROC logl
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+ fldln2 ; st0=log(2)
+ fld tword [xBP + xCB*2] ; st1=log(2) st0=lrd
+ fld st0 ; st1=log(2) st0=lrd st0=lrd
+ fsub qword [.one xWrtRIP] ; st2=log(2) st1=lrd st0=lrd-1.0
+ fld st0 ; st3=log(2) st2=lrd st1=lrd-1.0 st0=lrd-1.0
+
+ fabs ; st3=log(2) st2=lrd st1=lrd-1.0 st0=abs(lrd-1.0)
+ fcomp qword [.limit xWrtRIP] ; st2=log(2) st1=lrd st0=lrd-1.0
+ fnstsw ax
+ and eax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ jnz .use_st1
+
+ fstp st0 ; st1=log(2) st0=lrd
+ fyl2x ; log(lrd)
+ jmp .done
+
+.use_st1:
+ fstp st1 ; st1=log(2) st0=lrd-1.0
+ fyl2xp1 ; log(lrd)
+
+.done:
+ leave
+ ret
+
+ALIGNCODE(8)
+.one: dq 1.0
+.limit: dq 0.29
+ENDPROC RT_NOCRT(logl)
+
diff --git a/src/VBox/Runtime/common/math/lrint.asm b/src/VBox/Runtime/common/math/lrint.asm
new file mode 100644
index 00000000..5d25b160
--- /dev/null
+++ b/src/VBox/Runtime/common/math/lrint.asm
@@ -0,0 +1,75 @@
+; $Id: lrint.asm $
+;; @file
+; IPRT - No-CRT lrint - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Round rd to the nearest integer value, rounding according to the current rounding direction.
+; @returns 32-bit: eax 64-bit: rax (non-windows) or eax (windows)
+; @param rd 32-bit: [esp + 4h] 64-bit: xmm0
+RT_NOCRT_BEGINPROC lrint
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_X86
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %ifdef RT_OS_WINDOWS
+ cvtsd2si eax, xmm0
+ %else
+ cvtsd2si rax, xmm0
+ %endif
+%else
+ fld qword [ebp + 8h]
+ fistp dword [esp]
+ fwait
+ mov eax, [esp]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(lrint)
+
diff --git a/src/VBox/Runtime/common/math/lrintf.asm b/src/VBox/Runtime/common/math/lrintf.asm
new file mode 100644
index 00000000..441482c5
--- /dev/null
+++ b/src/VBox/Runtime/common/math/lrintf.asm
@@ -0,0 +1,74 @@
+; $Id: lrintf.asm $
+;; @file
+; IPRT - No-CRT lrintf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Round rd to the nearest integer value, rounding according to the current rounding direction.
+; @returns 32-bit: eax 64-bit: rax (non-windows) or eax (windows)
+; @param rf 32-bit: [esp + 4h] 64-bit: xmm0
+RT_NOCRT_BEGINPROC lrintf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_X86
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ %ifdef RT_OS_WINDOWS
+ cvtss2si eax, xmm0
+ %else
+ cvtss2si rax, xmm0
+ %endif
+%else
+ fld dword [ebp + 8h]
+ fistp dword [esp]
+ fwait
+ mov eax, [esp]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(lrintf)
+
diff --git a/src/VBox/Runtime/common/math/lrintl.asm b/src/VBox/Runtime/common/math/lrintl.asm
new file mode 100644
index 00000000..057c45ee
--- /dev/null
+++ b/src/VBox/Runtime/common/math/lrintl.asm
@@ -0,0 +1,77 @@
+; $Id: lrintl.asm $
+;; @file
+; IPRT - No-CRT lrintl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; Round lrd to the nearest integer value, rounding according to the current rounding direction.
+; @returns 32-bit: eax 64-bit: rax (non-windows) or eax (windows)
+; @param lrd [rbp + xCB*2]
+RT_NOCRT_BEGINPROC lrintl
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ fld tword [xBP + xCB*2]
+%ifdef RT_ARCH_AMD64
+ %ifdef RT_OS_WINDOWS
+ fistp dword [xSP]
+ fwait
+ mov eax, [xSP]
+ %else
+ fistp qword [xSP]
+ fwait
+ mov rax, [xSP]
+ %endif
+%else
+ fistp dword [xSP]
+ fwait
+ mov eax, [xSP]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(lrintl)
+
diff --git a/src/VBox/Runtime/common/math/lround.cpp b/src/VBox/Runtime/common/math/lround.cpp
new file mode 100644
index 00000000..1348398f
--- /dev/null
+++ b/src/VBox/Runtime/common/math/lround.cpp
@@ -0,0 +1,65 @@
+/* $Id: lround.cpp $ */
+/** @file
+ * IPRT - No-CRT - lround().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/fenv.h>
+
+
+#undef lround
+long RT_NOCRT(lround)(double rd)
+{
+ if (isfinite(rd))
+ {
+ rd = RT_NOCRT(round)(rd);
+ if (rd >= (double)LONG_MIN && rd <= (double)LONG_MAX)
+ return (long)rd;
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ return rd > 0.0 ? LONG_MAX : LONG_MIN;
+ }
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ if (RT_NOCRT(isinf)(rd) && rd < 0.0)
+ return LONG_MIN;
+ return LONG_MAX;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(lround);
+
diff --git a/src/VBox/Runtime/common/math/lroundf.cpp b/src/VBox/Runtime/common/math/lroundf.cpp
new file mode 100644
index 00000000..c753504d
--- /dev/null
+++ b/src/VBox/Runtime/common/math/lroundf.cpp
@@ -0,0 +1,65 @@
+/* $Id: lroundf.cpp $ */
+/** @file
+ * IPRT - No-CRT - lroundf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/fenv.h>
+
+
+#undef lroundf
+long RT_NOCRT(lroundf)(float r32)
+{
+ if (isfinite(r32))
+ {
+ r32 = RT_NOCRT(roundf)(r32);
+ if (r32 >= (float)LONG_MIN && r32 <= (float)LONG_MAX)
+ return (long)r32;
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ return r32 > 0.0f ? LONG_MAX : LONG_MIN;
+ }
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ if (RT_NOCRT(__isinff)(r32) && r32 < 0.0)
+ return LONG_MIN;
+ return LONG_MAX;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(lroundf);
+
diff --git a/src/VBox/Runtime/common/math/lroundl.cpp b/src/VBox/Runtime/common/math/lroundl.cpp
new file mode 100644
index 00000000..c5618396
--- /dev/null
+++ b/src/VBox/Runtime/common/math/lroundl.cpp
@@ -0,0 +1,65 @@
+/* $Id: lroundl.cpp $ */
+/** @file
+ * IPRT - No-CRT - lroundl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/fenv.h>
+
+
+#undef lroundl
+long RT_NOCRT(lroundl)(long double lrd)
+{
+ if (isfinite(lrd))
+ {
+ lrd = RT_NOCRT(roundl)(lrd);
+ if (lrd >= (long double)LONG_MIN && lrd <= (long double)LONG_MAX)
+ return (long)lrd;
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ return lrd > 0.0L ? LONG_MAX : LONG_MIN;
+ }
+ RT_NOCRT(feraiseexcept)(FE_INVALID);
+ if (RT_NOCRT(__isinfl)(lrd) && lrd < 0.0)
+ return LONG_MIN;
+ return LONG_MAX;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(lroundl);
+
diff --git a/src/VBox/Runtime/common/math/nocrt-abs.cpp b/src/VBox/Runtime/common/math/nocrt-abs.cpp
new file mode 100644
index 00000000..bfa3e3ea
--- /dev/null
+++ b/src/VBox/Runtime/common/math/nocrt-abs.cpp
@@ -0,0 +1,52 @@
+/* $Id: nocrt-abs.cpp $ */
+/** @file
+ * IPRT - No-CRT - abs().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+
+
+#undef abs
+int RT_NOCRT(abs)(int iValue) RT_NOEXCEPT
+{
+ return iValue >= 0 ? iValue : -iValue;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(abs);
+
diff --git a/src/VBox/Runtime/common/math/nocrt-labs.cpp b/src/VBox/Runtime/common/math/nocrt-labs.cpp
new file mode 100644
index 00000000..e8a28ff4
--- /dev/null
+++ b/src/VBox/Runtime/common/math/nocrt-labs.cpp
@@ -0,0 +1,52 @@
+/* $Id: nocrt-labs.cpp $ */
+/** @file
+ * IPRT - No-CRT - labs().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+
+
+#undef labs
+long RT_NOCRT(labs)(long iValue) RT_NOEXCEPT
+{
+ return iValue >= 0 ? iValue : -iValue;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(labs);
+
diff --git a/src/VBox/Runtime/common/math/nocrt-llabs.cpp b/src/VBox/Runtime/common/math/nocrt-llabs.cpp
new file mode 100644
index 00000000..b404ddb2
--- /dev/null
+++ b/src/VBox/Runtime/common/math/nocrt-llabs.cpp
@@ -0,0 +1,52 @@
+/* $Id: nocrt-llabs.cpp $ */
+/** @file
+ * IPRT - No-CRT - llabs().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+
+
+#undef llabs
+long long RT_NOCRT(llabs)(long long iValue) RT_NOEXCEPT
+{
+ return iValue >= 0 ? iValue : -iValue;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(llabs);
+
diff --git a/src/VBox/Runtime/common/math/pow.asm b/src/VBox/Runtime/common/math/pow.asm
new file mode 100644
index 00000000..c9e760ff
--- /dev/null
+++ b/src/VBox/Runtime/common/math/pow.asm
@@ -0,0 +1,127 @@
+; $Id: pow.asm $
+;; @file
+; IPRT - No-CRT pow - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+extern NAME(rtNoCrtMathPowCore)
+
+;;
+; Compute the rdBase to the power of rdExp.
+; @returns st(0) / xmm0
+; @param rdBase [xSP + xCB*2] / xmm0
+; @param rdExp [xSP + xCB*2 + 8] / xmm1
+;
+RT_NOCRT_BEGINPROC pow
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ push xBX
+ SEH64_PUSH_GREG rbx
+ sub xSP, 30h - xCB
+ SEH64_ALLOCATE_STACK 30h - xCB
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load rdBase into st1 and rdExp into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd [xBP - 20h], xmm0
+ fld qword [xBP - 20h]
+ fxam
+ fnstsw ax
+ mov dx, ax ; dx=fxam(base)
+
+ movsd [xBP - 30h], xmm1
+ fld qword [xBP - 30h]
+%else
+ fld qword [xBP + xCB*2]
+ fxam
+ fnstsw ax
+ mov dx, ax ; dx=fxam(base)
+
+ fld qword [xBP + xCB*2 + RTLRD_CB]
+%endif
+
+ ;
+ ; Call common worker for the calculation.
+ ;
+ mov ebx, 1 ; float
+ call NAME(rtNoCrtMathPowCore)
+
+ ;
+ ; Normally, we return with eax==0 and we have to load the result
+ ; from st0 and into xmm0.
+ ;
+ cmp eax, 0
+ jne .return_input_reg
+
+ fstp qword [xSP - 30h]
+ movsd xmm0, [xSP - 30h]
+
+.return:
+ lea xSP, [xBP - xCB]
+ pop xBX
+ leave
+ ret
+
+ ;
+ ; But sometimes, like if we have NaN or other special inputs, we should
+ ; return the input as-is and ditch the st0 value.
+ ;
+.return_input_reg:
+ ffreep st0
+ cmp eax, 2
+ je .return_exp
+%ifdef RT_STRICT
+ cmp eax, 1
+ je .return_base
+ int3
+%endif
+.return_base:
+ jmp .return
+
+.return_exp:
+ movsd xmm0, xmm1
+ jmp .return
+ENDPROC RT_NOCRT(pow)
+
diff --git a/src/VBox/Runtime/common/math/powcore.asm b/src/VBox/Runtime/common/math/powcore.asm
new file mode 100644
index 00000000..e37af891
--- /dev/null
+++ b/src/VBox/Runtime/common/math/powcore.asm
@@ -0,0 +1,633 @@
+; $Id: powcore.asm $
+;; @file
+; IPRT - No-CRT common pow code - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+extern NAME(RT_NOCRT(feraiseexcept))
+
+;;
+; Call feraiseexcept(%1)
+%macro CALL_feraiseexcept_WITH 1
+ %ifdef RT_ARCH_X86
+ mov dword [xSP], X86_FSW_IE
+ %elifdef ASM_CALL64_GCC
+ mov edi, X86_FSW_IE
+ %elifdef ASM_CALL64_MSC
+ mov ecx, X86_FSW_IE
+ %else
+ %error calling conv.
+ %endif
+ call NAME(RT_NOCRT(feraiseexcept))
+%endmacro
+
+
+;;
+; Compute the st1 to the power of st0.
+;
+; @returns st(0) = result
+; eax = what's being returned:
+; 0 - Just a value.
+; 1 - The rBase value. Caller may take steps to ensure it's exactly the same.
+; 2 - The rExp value. Caller may take steps to ensure it's exactly the same.
+; @param rBase/st1 The base.
+; @param rExp/st0 The exponent
+; @param fFxamBase/dx The status flags after fxam(rBase).
+; @param enmType/ebx The original parameter and return types:
+; 0 - 32-bit / float
+; 1 - 64-bit / double
+; 2 - 80-bit / long double
+;
+BEGINPROC rtNoCrtMathPowCore
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 30h
+ SEH64_ALLOCATE_STACK 30h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Weed out special values, starting with the exponent.
+ ;
+ fxam
+ fnstsw ax
+ mov cx, ax ; cx=fxam(exp)
+
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .exp_finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .exp_zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals
+ je .exp_finite
+ cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity.
+ je .exp_inf
+ jmp .exp_nan
+
+.exp_finite:
+ ;
+ ; Detect special base values.
+ ;
+ mov ax, dx ; ax=fxam(base)
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .base_finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .base_zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals
+ je .base_finite
+ cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity.
+ je .base_inf
+ jmp .base_nan
+
+.base_finite:
+ ;
+ ; 1 in the base is also special.
+ ; Rule 6 (see below): base == +1 and exponent = whatever: Return +1.0
+ ;
+ fld1
+ fcomip st0, st2
+ je .return_base_value
+
+ ;
+ ; Check if the exponent is an integer value we can handle in a 64-bit
+ ; GRP as that is simpler to handle accurately.
+ ;
+ ; In 64-bit integer range?
+ fld tword [.s_r80MaxInt xWrtRIP]
+ fcomip st0, st1
+ jb .not_integer_exp
+
+ fld tword [.s_r80MinInt xWrtRIP]
+ fcomip st0, st1
+ ja .not_integer_exp
+
+ ; Convert it to integer.
+ fld st0 ; -> st0=exp; st1=exp; st2=base
+ fistp qword [xBP - 8] ; Save and pop 64-bit int (no non-popping version of this instruction).
+
+ fild qword [xBP - 8] ; Load it again for comparison.
+ fucomip st0, st1 ; Compare integer exp and floating point exp to see if they are the same. Pop.
+ jne .not_integer_exp
+
+
+ ;
+ ;
+ ; Ok, we've got an integer exponent value in that fits into a 64-bit.
+ ; We'll multiply the base exponention bit by exponention bit, applying
+ ; it as a factor for bits that are set.
+ ;
+ ;
+.integer_exp:
+ ; Load the integer value into edx:exx / rdx and ditch the floating point exponent.
+ mov xDX, [xBP - 8]
+%ifdef RT_ARCH_X86
+ mov eax, [xBP - 8 + 4]
+%endif
+ ffreep st0 ; -> st0=base;
+
+ ; Load a 1 onto the stack, we'll need it below as well as for converting
+ ; a negative exponent to a positive one.
+ fld1 ; -> st0=1.0; st1=base;
+
+ ; If the exponent is negative, negate it and change base to 1/base.
+ or xDX, xDX
+ jns .integer_exp_positive
+ neg xDX
+%ifdef RT_ARCH_X86
+ neg eax
+ sbb edx, 0
+%endif
+ fdivr st1, st0 ; -> st0=1.0; st1=1/base
+.integer_exp_positive:
+
+ ;
+ ; We'll process edx:eax / rdx bit by bit till it's zero, using st0 for
+ ; the multiplication factor corresponding to the current exponent bit
+ ; and st1 as the result.
+ ;
+ fxch ; -> st0=base; st1=1.0;
+.integer_exp_loop:
+%ifdef RT_ARCH_X86
+ shrd eax, edx, 1
+%else
+ shr rdx, 1
+%endif
+ jnc .integer_exp_loop_advance
+ fmul st1, st0
+
+.integer_exp_loop_advance:
+ ; Check if we're done.
+%ifdef RT_ARCH_AMD64
+ jz .integer_exp_return ; (we will have the flags for the shr rdx above)
+%else
+ shr edx, 1 ; complete the above shift operation
+
+ mov ecx, edx ; check if edx:eax is zero.
+ or ecx, eax
+ jz .integer_exp_return
+%endif
+ ; Calculate the factor for the next bit.
+ fmul st0, st0
+ jmp .integer_exp_loop
+
+.integer_exp_return:
+ ffreep st0 ; drop the factor -> st0=result; no st1.
+ jmp .return_val
+
+
+ ;
+ ;
+ ; Non-integer or value was out of range for an int64_t.
+ ;
+ ; The approach here is the same as in exp.asm, only we have to do the
+ ; log2(base) calculation first as it's a parameter and not a constant.
+ ;
+ ;
+.not_integer_exp:
+
+ ; First reject negative numbers. We still have the fxam(base) status in dx.
+ test dx, X86_FSW_C1
+ jnz .base_negative_non_integer_exp
+
+ ; Swap the items on the stack, so we can process the base first.
+ fxch st0, st1 ; -> st0=base; st1=exponent;
+
+ ;
+ ; From log2.asm:
+ ;
+ ; The fyl2xp1 instruction (ST1=ST1*log2(ST0+1.0), popping ST0) has a
+ ; valid ST0 range of 1(1-sqrt(0.5)) (approx 0.29289321881) on both
+ ; sides of zero. We try use it if we can.
+ ;
+.above_one:
+ ; For both fyl2xp1 and fyl2xp1 we need st1=1.0.
+ fld1
+ fxch st0, st1 ; -> st0=base; st1=1.0; st2=exponent
+
+ ; Check if the input is within the fyl2xp1 range.
+ fld qword [.s_r64AbsFyL2xP1InputMax xWrtRIP]
+ fcomip st0, st1
+ jbe .cannot_use_fyl2xp1
+
+ fld qword [.s_r64AbsFyL2xP1InputMin xWrtRIP]
+ fcomip st0, st1
+ jae .cannot_use_fyl2xp1
+
+ ; Do the calculation.
+.use_fyl2xp1:
+ fsub st0, st1 ; -> st0=base-1; st1=1.0; st2=exponent
+ fyl2xp1 ; -> st0=1.0*log2(base-1.0+1.0); st1=exponent
+ jmp .done_log2
+
+.cannot_use_fyl2xp1:
+ fyl2x ; -> st0=1.0*log2(base); st1=exponent
+.done_log2:
+
+ ;
+ ; From exp.asm:
+ ;
+ ; Convert to power of 2 and it'll be the same as exp2.
+ ;
+ fmulp ; st0=log2(base); st1=exponent -> st0=pow2exp
+
+ ;
+ ; Split the job in two on the fraction and integer l2base parts.
+ ;
+ fld st0 ; Push a copy of the pow2exp on the stack.
+ frndint ; st0 = (int)pow2exp
+ fsub st1, st0 ; st1 = pow2exp - (int)pow2exp; i.e. st1 = fraction, st0 = integer.
+ fxch ; st0 = fraction, st1 = integer.
+
+ ; 1. Calculate on the fraction.
+ f2xm1 ; st0 = 2**fraction - 1.0
+ fld1
+ faddp ; st0 = 2**fraction
+
+ ; 2. Apply the integer power of two.
+ fscale ; st0 = result; st1 = integer part of pow2exp.
+ fstp st1 ; st0 = result; no st1.
+
+ ;
+ ; Return st0.
+ ;
+.return_val:
+ xor eax, eax
+.return:
+ leave
+ ret
+
+
+ ;
+ ;
+ ; pow() has a lot of defined behavior for special values, which is why
+ ; this is the largest and most difficult part of the code. :-)
+ ;
+ ; On https://pubs.opengroup.org/onlinepubs/9699919799/functions/pow.html
+ ; there are 21 error conditions listed in the return value section.
+ ; The code below refers to this by number.
+ ;
+ ; When we get here:
+ ; dx=fxam(base)
+ ; cx=fxam(exponent)
+ ; st1=base
+ ; st0=exponent
+ ;
+
+ ;
+ ; 1. Finit base < 0 and finit non-interger exponent: -> domain error (#IE) + NaN.
+ ;
+ ; The non-integer exponent claim might be wrong, as we only check if it
+ ; fits into a int64_t register. But, I don't see how we can calculate
+ ; it right now.
+ ;
+.base_negative_non_integer_exp:
+ CALL_feraiseexcept_WITH X86_FSW_IE
+ jmp .return_nan
+
+ ;
+ ; 7. Exponent = +/-0.0, any base value including NaN: return +1.0
+ ; Note! According to https://en.cppreference.com/w/c/numeric/math/pow a
+ ; domain error (#IE) occur if base=+/-0. Not implemented.
+.exp_zero:
+.return_plus_one:
+ fld1
+ jmp .return_pop_pop_val
+
+ ;
+ ; 6. Exponent = whatever and base = 1: Return 1.0
+ ; 10. Exponent = +/-Inf and base = -1: Return 1.0
+ ;6+10 => Exponent = +/-Inf and |base| = 1: Return 1.0
+ ; 11. Exponent = -Inf and |base| < 1: Return +Inf
+ ; 12. Exponent = -Inf and |base| > 1: Return +0
+ ; 13. Exponent = +Inf and |base| < 1: Return +0
+ ; 14. Exponent = +Inf and |base| > 1: Return +Inf
+ ;
+ ; Note! Rule 4 would trigger for the same conditions as 11 when base == 0,
+ ; but it's optional to raise div/0 and it's apparently marked as
+ ; obsolete in C23, so not implemented.
+ ;
+.exp_inf:
+ ; Check if base is NaN or unsupported.
+ and dx, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0 ; fxam(base)
+ cmp dx, X86_FSW_C0
+ jbe .return_base_nan
+
+ ; Calc fabs(base) and replace the exponent with 1.0 as we're very likely to need this here.
+ ffreep st0
+ fabs
+ fld1 ; st0=1.0; st1=|rdBase|
+ fcomi st0, st1
+ je .return_plus_one ; Matches rule 6 + 10 (base is +/-1).
+ ja .exp_inf_base_smaller_than_one
+.exp_inf_base_larger_than_one:
+ test cx, X86_FSW_C1 ; cx=faxm(exponent); C1=sign
+ jz .return_plus_inf ; Matches rule 14 (exponent is +Inf).
+ jmp .return_plus_zero ; Matches rule 12 (exponent is -Inf).
+
+.exp_inf_base_smaller_than_one:
+ test cx, X86_FSW_C1 ; cx=faxm(exponent); C1=sign
+ jnz .return_plus_inf ; Matches rule 11 (exponent is -Inf).
+ jmp .return_plus_zero ; Matches rule 13 (exponent is +Inf).
+
+ ;
+ ; 6. Exponent = whatever and base = 1: Return 1.0
+ ; 5. Unless specified elsewhere, return NaN if any of the parameters are NaN.
+ ;
+.exp_nan:
+ ; Check if base is a number and possible 1.
+ test dx, X86_FSW_C2 ; dx=fxam(base); C2 is set for finite number, infinity and denormals.
+ jz .return_exp_nan
+ fld1
+ fcomip st0, st2
+ jne .return_exp_nan
+ jmp .return_plus_one
+
+ ;
+ ; 4a. base == +/-0.0 and exp < 0 and exp is odd integer: Return +/-Inf, raise div/0.
+ ; 4b. base == +/-0.0 and exp < 0 and exp is not odd int: Return +Inf, raise div/0.
+ ; 8. base == +/-0.0 and exp > 0 and exp is odd integer: Return +/-0.0
+ ; 9. base == +/-0.0 and exp > 0 and exp is not odd int: Return +0
+ ;
+ ; Note! Exponent must be finite and non-zero if we get here.
+ ;
+.base_zero:
+ fldz
+ fcomip st0, st1
+ jbe .base_zero_plus_exp
+.base_zero_minus_exp:
+ mov cx, dx ; stashing fxam(base) in CX because EDX is trashed by .is_exp_odd_integer
+ call .is_exp_odd_integer ; trashes EDX but no ECX.
+ or eax, eax
+ jz .base_zero_minus_exp_not_odd_int
+
+ ; Matching 4a.
+.base_zero_minus_exp_odd_int:
+ test cx, X86_FSW_C1 ; base sign
+ jz .raise_de_and_return_plus_inf
+.raise_de_and_return_minus_inf:
+ CALL_feraiseexcept_WITH X86_FSW_DE
+ jmp .return_minus_inf
+.raise_de_and_return_plus_inf:
+ CALL_feraiseexcept_WITH X86_FSW_DE
+ jmp .return_plus_inf
+
+ ; Matching 4b.
+.base_zero_minus_exp_not_odd_int:
+ CALL_feraiseexcept_WITH X86_FSW_DE
+ jmp .return_plus_inf
+
+.base_zero_plus_exp:
+ call .is_exp_odd_integer
+ or eax, eax
+ jnz .return_base_value ; Matching 8
+.return_plus_zero: ; Matching 9
+ fldz
+ jmp .return_pop_pop_val
+
+ ;
+ ; 15. base == -Inf and exp < 0 and exp is odd integer: Return -0
+ ; 16. base == -Inf and exp < 0 and exp is not odd int: Return +0
+ ; 17. base == -Inf and exp > 0 and exp is odd integer: Return -Inf
+ ; 18. base == -Inf and exp > 0 and exp is not odd int: Return +Inf
+ ; 19. base == +Inf and exp < 0: Return +0
+ ; 20. base == +Inf and exp > 0: Return +Inf
+ ;
+ ; Note! Exponent must be finite and non-zero if we get here.
+ ;
+.base_inf:
+ fldz
+ fcomip st0, st1
+ jbe .base_inf_plus_exp
+.base_inf_minus_exp:
+ test dx, X86_FSW_C1
+ jz .return_plus_zero ; Matches 19 (base == +Inf).
+.base_minus_inf_minus_exp:
+ call .is_exp_odd_integer
+ or eax, eax
+ jz .return_plus_zero ; Matches 16 (exp not odd and < 0, base == -Inf)
+.return_minus_zero: ; Matches 15 (exp is odd and < 0, base == -Inf)
+ fldz
+ fchs
+ jmp .return_pop_pop_val
+
+.base_inf_plus_exp:
+ test dx, X86_FSW_C1
+ jz .return_plus_inf ; Matches 20 (base == +Inf).
+.base_minus_inf_plus_exp:
+ call .is_exp_odd_integer
+ or eax, eax
+ jnz .return_minus_inf ; Matches 17 (exp is odd and > 0, base == +Inf)
+ jmp .return_plus_inf ; Matches 18 (exp not odd and > 0, base == +Inf)
+
+ ;
+ ; Return the exponent NaN (or whatever) value.
+ ;
+.return_exp_nan:
+ fld st0
+ mov eax, 2 ; return param 2
+ jmp .return_pop_pop_val_with_eax
+
+ ;
+ ; Return the base NaN (or whatever) value.
+ ;
+.return_base_nan:
+.return_base_value:
+.base_nan: ; 5. Unless specified elsewhere, return NaN if any of the parameters are NaN.
+ fld st1
+ mov eax, 1 ; return param 1
+ jmp .return_pop_pop_val_with_eax
+
+ ;
+ ; Pops the two values off the FPU stack and returns NaN.
+ ;
+.return_nan:
+ fld qword [.s_r64QNan xWrtRIP]
+ jmp .return_pop_pop_val
+
+ ;
+ ; Pops the two values off the FPU stack and returns +Inf.
+ ;
+.return_plus_inf:
+ fld qword [.s_r64PlusInf xWrtRIP]
+ jmp .return_pop_pop_val
+
+ ;
+ ; Pops the two values off the FPU stack and returns -Inf.
+ ;
+.return_minus_inf:
+ fld qword [.s_r64MinusInf xWrtRIP]
+ jmp .return_pop_pop_val
+
+ ;
+ ; Return st0, remove st1 and st2.
+ ;
+.return_pop_pop_val:
+ xor eax, eax
+.return_pop_pop_val_with_eax:
+ fstp st2
+ ffreep st0
+ jmp .return
+
+
+ALIGNCODE(8)
+.s_r80MaxInt:
+ dt +9223372036854775807.0
+
+ALIGNCODE(8)
+.s_r80MinInt:
+ dt -9223372036854775807.0
+
+ALIGNCODE(8)
+ ;; The fyl2xp1 instruction only works between +/-1(1-sqrt(0.5)).
+ ; These two variables is that range + 1.0, so we can compare directly
+ ; with the input w/o any extra fsub and fabs work.
+.s_r64AbsFyL2xP1InputMin:
+ dq 0.708 ; -0.292 + 1.0
+.s_r64AbsFyL2xP1InputMax:
+ dq 1.292
+
+.s_r64QNan:
+ dq RTFLOAT64U_QNAN_MINUS
+.s_r64PlusInf:
+ dq RTFLOAT64U_INF_PLUS
+.s_r64MinusInf:
+ dq RTFLOAT64U_INF_MINUS
+
+ ;;
+ ; Sub-function that checks if the exponent (st0) is an odd integer or not.
+ ;
+ ; @returns eax = 1 if odd, 0 if even or not integer.
+ ; @uses eax, edx, eflags.
+ ;
+.is_exp_odd_integer:
+ ;
+ ; Save the FPU enviornment and mask all exceptions.
+ ;
+ fnstenv [xBP - 30h]
+ mov ax, [xBP - 30h + X86FSTENV32P.FCW]
+ or word [xBP - 30h + X86FSTENV32P.FCW], X86_FCW_MASK_ALL
+ fldcw [xBP - 30h + X86FSTENV32P.FCW]
+ mov [xBP - 30h + X86FSTENV32P.FCW], ax
+
+ ;
+ ; Convert to 64-bit integer (probably not 100% correct).
+ ;
+ fld st0 ; -> st0=exponent st1=exponent; st2=base;
+ fistp qword [xBP - 10h]
+ fild qword [xBP - 10h] ; -> st0=int(exponent) st1=exponent; st2=base;
+ fcomip st0, st1 ; -> st0=exponent; st1=base;
+ jne .is_exp_odd_integer__return_false ; jump if not integer.
+ mov xAX, [xBP - 10h]
+%ifdef
+ mov edx, [xBP - 10h + 4]
+%endif
+
+ ;
+ ; Check the lowest bit if it might be odd.
+ ; This works both for positive and negative numbers.
+ ;
+ test al, 1
+ jz .is_exp_odd_integer__return_false ; jump if even.
+
+ ;
+ ; If the result is negative, convert to positive.
+ ;
+%ifdef RT_ARCH_AMD64
+ bt rax, 63
+%else
+ bt edx, 31
+%endif
+ jnc .is_exp_odd_integer__positive
+%ifdef RT_ARCH_AMD64
+ neg xAX
+%else
+ neg edx
+ neg eax
+ sbb edx, 0
+%endif
+.is_exp_odd_integer__positive:
+
+ ;
+ ; Now find the most significant bit in the value so we can verify that
+ ; the odd bit was part of the mantissa/fraction of the input.
+ ;
+ cmp bl, 3 ; Skip if 80-bit input, as it has a 64-bit mantissa which
+ je .is_exp_odd_integer__return_true ; makes it a 1 bit more precision than out integer reg(s).
+
+%ifdef RT_ARCH_AMD64
+ bsr rax, rax
+%else
+ bsr edx, edx
+ jnz .is_exp_odd_integer__high_dword_is_zero
+ lea eax, [edx + 20h]
+ jmp .is_exp_odd_integer__first_bit_in_eax
+.is_exp_odd_integer__high_dword_is_zero:
+ bsr eax, eax
+.is_exp_odd_integer__first_bit_in_eax:
+%endif
+ ;
+ ; The limit is 53 for double precision (one implicit bit + 52 bits fraction),
+ ; and 24 for single precision types.
+ ;
+ mov ah, 53 ; RTFLOAT64U_FRACTION_BITS + 1
+ cmp bl, 0
+ jne .is_exp_odd_integer__is_double_limit
+ mov ah, 24 ; RTFLOAT32U_FRACTION_BITS + 1
+.is_exp_odd_integer__is_double_limit:
+
+ cmp al, ah
+ jae .is_exp_odd_integer__return_false
+ mov eax, 1
+
+ ; Return.
+.is_exp_odd_integer__return_true:
+ jmp .is_exp_odd_integer__return
+.is_exp_odd_integer__return_false:
+ xor eax, eax
+.is_exp_odd_integer__return:
+ ffreep st0
+ fldenv [xBP - 30h]
+ ret
+
+ENDPROC rtNoCrtMathPowCore
+
diff --git a/src/VBox/Runtime/common/math/powf.asm b/src/VBox/Runtime/common/math/powf.asm
new file mode 100644
index 00000000..19aba29e
--- /dev/null
+++ b/src/VBox/Runtime/common/math/powf.asm
@@ -0,0 +1,127 @@
+; $Id: powf.asm $
+;; @file
+; IPRT - No-CRT powf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+extern NAME(rtNoCrtMathPowCore)
+
+;;
+; Compute the rfBase to the power of rfExp.
+; @returns st(0) / xmm0
+; @param rfBase [xSP + xCB*2] / xmm0
+; @param rfExp [xSP + xCB*2 + 4] / xmm1
+;
+RT_NOCRT_BEGINPROC powf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ push xBX
+ SEH64_PUSH_GREG rbx
+ sub xSP, 30h - xCB
+ SEH64_ALLOCATE_STACK 30h - xCB
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load rdBase into st1 and rdExp into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movss [xBP - 20h], xmm0
+ fld dword [xBP - 20h]
+ fxam
+ fnstsw ax
+ mov dx, ax ; dx=fxam(base)
+
+ movss [xBP - 30h], xmm1
+ fld dword [xBP - 30h]
+%else
+ fld dword [xBP + xCB*2]
+ fxam
+ fnstsw ax
+ mov dx, ax ; dx=fxam(base)
+
+ fld dword [xBP + xCB*2 + RTLRD_CB]
+%endif
+
+ ;
+ ; Call common worker for the calculation.
+ ;
+ mov ebx, 1 ; float
+ call NAME(rtNoCrtMathPowCore)
+
+ ;
+ ; Normally, we return with eax==0 and we have to load the result
+ ; from st0 and into xmm0.
+ ;
+ cmp eax, 0
+ jne .return_input_reg
+
+ fstp dword [xSP - 30h]
+ movss xmm0, [xSP - 30h]
+
+.return:
+ lea xSP, [xBP - xCB]
+ pop xBX
+ leave
+ ret
+
+ ;
+ ; But sometimes, like if we have NaN or other special inputs, we should
+ ; return the input as-is and ditch the st0 value.
+ ;
+.return_input_reg:
+ ffreep st0
+ cmp eax, 2
+ je .return_exp
+%ifdef RT_STRICT
+ cmp eax, 1
+ je .return_base
+ int3
+%endif
+.return_base:
+ jmp .return
+
+.return_exp:
+ movss xmm0, xmm1
+ jmp .return
+ENDPROC RT_NOCRT(powf)
+
diff --git a/src/VBox/Runtime/common/math/remainder.asm b/src/VBox/Runtime/common/math/remainder.asm
new file mode 100644
index 00000000..65666390
--- /dev/null
+++ b/src/VBox/Runtime/common/math/remainder.asm
@@ -0,0 +1,104 @@
+; $Id: remainder.asm $
+;; @file
+; IPRT - No-CRT remainder - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; See SUS.
+; @returns st(0) / xmm0
+; @param rd1 [ebp + 8h] xmm0 Dividend.
+; @param rd2 [ebp + 10h] xmm1 Divisor.
+RT_NOCRT_BEGINPROC remainder
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+%endif
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the dividend into st0 and divisor into st1.
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd [xBP - 20h], xmm1
+ movsd [xBP - 10h], xmm0
+ fld qword [xBP - 20h]
+ fld qword [xBP - 10h]
+%else
+ fld qword [ebp + 10h]
+ fld qword [ebp + 08h]
+%endif
+
+ ;
+ ; The fprem1 only does between 32 and 64 rounds, so we have to loop
+ ; here till we've got a final result. We count down in ECX to
+ ; avoid getting stuck here...
+ ;
+ mov ecx, 2048 / 32 + 4
+.again:
+ fprem1
+ fstsw ax
+ test ah, (X86_FSW_C2 >> 8)
+ jz .done
+ dec cx
+ jnz .again
+%ifdef RT_STRICT
+ int3
+%endif
+
+ ;
+ ; Return the result.
+ ;
+.done:
+ fstp st1
+%ifdef RT_ARCH_AMD64
+ fstp qword [rsp]
+ movsd xmm0, [rsp]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(remainder)
+
diff --git a/src/VBox/Runtime/common/math/remainderf.asm b/src/VBox/Runtime/common/math/remainderf.asm
new file mode 100644
index 00000000..f2c79ce7
--- /dev/null
+++ b/src/VBox/Runtime/common/math/remainderf.asm
@@ -0,0 +1,104 @@
+; $Id: remainderf.asm $
+;; @file
+; IPRT - No-CRT remainderf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; See SUS.
+; @returns st(0) / xmm0
+; @param rf1 [ebp + 8h] xmm0 Dividend.
+; @param rf2 [ebp + 10h] xmm1 Divisor.
+RT_NOCRT_BEGINPROC remainderf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+%endif
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the dividend into st0 and divisor into st1.
+ ;
+%ifdef RT_ARCH_AMD64
+ movss [xBP - 20h], xmm1
+ movss [xBP - 10h], xmm0
+ fld dword [xBP - 20h]
+ fld dword [xBP - 10h]
+%else
+ fld dword [ebp + 0ch]
+ fld dword [ebp + 08h]
+%endif
+
+ ;
+ ; The fprem1 only does between 32 and 64 rounds, so we have to loop
+ ; here till we've got a final result. We count down in ECX to
+ ; avoid getting stuck here...
+ ;
+ mov ecx, 256 / 32 + 4
+.again:
+ fprem1
+ fstsw ax
+ test ah, (X86_FSW_C2 >> 8)
+ jz .done
+ dec cx
+ jnz .again
+%ifdef RT_STRICT
+ int3
+%endif
+
+ ;
+ ; Return the result.
+ ;
+.done:
+ fstp st1
+%ifdef RT_ARCH_AMD64
+ fstp dword [rsp]
+ movss xmm0, [rsp]
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(remainderf)
+
diff --git a/src/VBox/Runtime/common/math/remainderl.asm b/src/VBox/Runtime/common/math/remainderl.asm
new file mode 100644
index 00000000..3eade8df
--- /dev/null
+++ b/src/VBox/Runtime/common/math/remainderl.asm
@@ -0,0 +1,88 @@
+; $Id: remainderl.asm $
+;; @file
+; IPRT - No-CRT remainderl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; See SUS.
+; @returns st(0)
+; @param lrd1 [rbp + 10h]
+; @param lrd2 [rbp + 20h]
+RT_NOCRT_BEGINPROC remainderl
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the dividend into st0 and divisor into st1.
+ ;
+ fld tword [xBP + 2*xCB + RTLRD_CB]
+ fld tword [xBP + 2*xCB]
+
+ ;
+ ; The fprem1 only does between 32 and 64 rounds, so we have to loop
+ ; here till we've got a final result. We count down in ECX to
+ ; avoid getting stuck here...
+ ;
+ mov ecx, 16384 / 32 + 4
+.again:
+ fprem1
+ fstsw ax
+ test ah, (X86_FSW_C2 >> 8)
+ jz .done
+ dec cx
+ jnz .again
+%ifdef RT_STRICT
+ int3
+%endif
+
+ ;
+ ; Return the result.
+ ;
+.done:
+ fstp st1
+ leave
+ ret
+ENDPROC RT_NOCRT(remainderl)
+
diff --git a/src/VBox/Runtime/common/math/rint.asm b/src/VBox/Runtime/common/math/rint.asm
new file mode 100644
index 00000000..2ef8edac
--- /dev/null
+++ b/src/VBox/Runtime/common/math/rint.asm
@@ -0,0 +1,99 @@
+; $Id: rint.asm $
+;; @file
+; IPRT - No-CRT rint - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Round to integer value according to current rounding mode.
+;
+; ASSUME FCW and MXCSR are in sync for AMD64.
+;
+; @returns st(0) / xmm0
+; @param rd [rbp + 08h] / xmm0
+RT_NOCRT_BEGINPROC rint
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the value into st(0). This messes up SNaN values.
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd qword [xSP], xmm0
+ fld qword [xSP]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Return immediately if NaN or infinity.
+ ;
+ fxam
+ fstsw ax
+ test ax, X86_FSW_C0 ; C0 is set for NaN, Infinity and Empty register. The latter is not the case.
+ jz .input_ok
+%ifdef RT_ARCH_AMD64
+ ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN.
+%endif
+ jmp .return
+.input_ok:
+
+ ;
+ ; Do the job and return.
+ ;
+ frndint
+
+%ifdef RT_ARCH_AMD64
+ fstp qword [xSP]
+ movsd xmm0, qword [xSP]
+%endif
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(rint)
+
diff --git a/src/VBox/Runtime/common/math/rintf.asm b/src/VBox/Runtime/common/math/rintf.asm
new file mode 100644
index 00000000..10bbda44
--- /dev/null
+++ b/src/VBox/Runtime/common/math/rintf.asm
@@ -0,0 +1,99 @@
+; $Id: rintf.asm $
+;; @file
+; IPRT - No-CRT rintf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Round to integer value according to current rounding mode.
+;
+; ASSUME FCW and MXCSR are in sync for AMD64.
+;
+; @returns st(0) / xmm0
+; @param rd [rbp + 08h] / xmm0
+RT_NOCRT_BEGINPROC rintf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+%ifdef RT_ARCH_AMD64
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+%endif
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the value into st(0). This messes up SNaN values.
+ ;
+%ifdef RT_ARCH_AMD64
+ movss dword [xSP], xmm0
+ fld dword [xSP]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Return immediately if NaN or infinity.
+ ;
+ fxam
+ fstsw ax
+ test ax, X86_FSW_C0 ; C0 is set for NaN, Infinity and Empty register. The latter is not the case.
+ jz .input_ok
+%ifdef RT_ARCH_AMD64
+ ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN.
+%endif
+ jmp .return
+.input_ok:
+
+ ;
+ ; Do the job and return.
+ ;
+ frndint
+
+%ifdef RT_ARCH_AMD64
+ fstp dword [xSP]
+ movss xmm0, dword [xSP]
+%endif
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(rintf)
+
diff --git a/src/VBox/Runtime/common/math/round.cpp b/src/VBox/Runtime/common/math/round.cpp
new file mode 100644
index 00000000..f9771250
--- /dev/null
+++ b/src/VBox/Runtime/common/math/round.cpp
@@ -0,0 +1,69 @@
+/* $Id: round.cpp $ */
+/** @file
+ * IPRT - No-CRT - round().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+
+
+#undef round
+double RT_NOCRT(round)(double rd)
+{
+ if (isfinite(rd))
+ {
+ double const rdIn = rd;
+ if (rd >= 0.0)
+ {
+ rd = RT_NOCRT(ceil)(rd);
+ if (rd - rdIn > 0.5)
+ rd -= 1.0;
+ }
+ else
+ {
+ rd = RT_NOCRT(ceil)(-rd);
+ if (rd + rdIn > 0.5)
+ rd -= 1.0;
+ rd = -rd;
+ }
+ }
+ return rd;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(round);
+
diff --git a/src/VBox/Runtime/common/math/roundf.cpp b/src/VBox/Runtime/common/math/roundf.cpp
new file mode 100644
index 00000000..12a8915c
--- /dev/null
+++ b/src/VBox/Runtime/common/math/roundf.cpp
@@ -0,0 +1,69 @@
+/* $Id: roundf.cpp $ */
+/** @file
+ * IPRT - No-CRT - roundf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+
+
+#undef roundf
+float RT_NOCRT(roundf)(float r32)
+{
+ if (isfinite(r32))
+ {
+ float const r32In = r32;
+ if (r32 >= 0.0)
+ {
+ r32 = RT_NOCRT(ceilf)(r32);
+ if (r32 - r32In > 0.5)
+ r32 -= 1.0;
+ }
+ else
+ {
+ r32 = RT_NOCRT(ceilf)(-r32);
+ if (r32 + r32In > 0.5)
+ r32 -= 1.0;
+ r32 = -r32;
+ }
+ }
+ return r32;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(roundf);
+
diff --git a/src/VBox/Runtime/common/math/roundl.cpp b/src/VBox/Runtime/common/math/roundl.cpp
new file mode 100644
index 00000000..49344cf0
--- /dev/null
+++ b/src/VBox/Runtime/common/math/roundl.cpp
@@ -0,0 +1,69 @@
+/* $Id: roundl.cpp $ */
+/** @file
+ * IPRT - No-CRT - roundl().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/math.h>
+
+
+#undef roundl
+long double RT_NOCRT(roundl)(long double lrd)
+{
+ if (isfinite(lrd))
+ {
+ long double const lrdIn = lrd;
+ if (lrd >= 0.0)
+ {
+ lrd = RT_NOCRT(ceill)(lrd);
+ if (lrd - lrdIn > 0.5)
+ lrd -= 1.0;
+ }
+ else
+ {
+ lrd = RT_NOCRT(ceill)(-lrd);
+ if (lrd + lrdIn > 0.5)
+ lrd -= 1.0;
+ lrd = -lrd;
+ }
+ }
+ return lrd;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(roundl);
+
diff --git a/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm b/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm
new file mode 100644
index 00000000..15c1bf4f
--- /dev/null
+++ b/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm
@@ -0,0 +1,78 @@
+; $Id: rtNoCrtHasSse.asm $
+;; @file
+; IPRT - No-CRT rtNoCrtHasSse - X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINDATA
+g_frtNoCrtHasSse: db 0x80
+
+
+BEGINCODE
+
+;;
+; Checks if SSE is supported.
+; @returns 1 if supported, 0 if not. Entire eax/rax is set.
+; @uses rax only
+;
+BEGINPROC rtNoCrtHasSse
+ mov al, [g_frtNoCrtHasSse]
+ test al, 0x80
+ jnz .detect_sse
+ ret
+
+.detect_sse:
+ push ebx
+ push ecx
+ push edx
+
+ mov eax, 1
+ cpuid
+
+ mov eax, 1
+ test edx, X86_CPUID_FEATURE_EDX_SSE
+ jz .no_supported
+ xor eax, eax
+.no_supported:
+
+ pop edx
+ pop ecx
+ pop ebx
+ ret
+ENDPROC rtNoCrtHasSse
+
diff --git a/src/VBox/Runtime/common/math/sin.asm b/src/VBox/Runtime/common/math/sin.asm
new file mode 100644
index 00000000..d0c478a3
--- /dev/null
+++ b/src/VBox/Runtime/common/math/sin.asm
@@ -0,0 +1,185 @@
+; $Id: sin.asm $
+;; @file
+; IPRT - No-CRT sin - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+
+;;
+; Compute the sine of rf, measured in radians.
+;
+; @returns st(0) / xmm0
+; @param rf [rbp + xCB*2] / xmm0
+;
+RT_NOCRT_BEGINPROC sin
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+%ifdef RT_OS_WINDOWS
+ ;
+ ; Make sure we use full precision and not the windows default of 53 bits.
+ ;
+ fnstcw [xBP - 20h]
+ mov ax, [xBP - 20h]
+ or ax, X86_FCW_PC_64 ; includes both bits, so no need to clear the mask.
+ mov [xBP - 1ch], ax
+ fldcw [xBP - 1ch]
+%endif
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd [xBP - 10h], xmm0
+ fld qword [xBP - 10h]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ;
+ ; We examin the input and weed out non-finit numbers first.
+ ;
+ fxam
+ fnstsw ax
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals - treat them as zero.
+ je .zero
+ cmp ax, X86_FSW_C0 ; NaN - must handle it special,
+ je .nan
+
+ ; Pass infinities and unsupported inputs to fsin, assuming it does the right thing.
+.do_sin:
+ fsin
+ jmp .return_val
+
+ ;
+ ; Finite number.
+ ;
+.finite:
+ ; For very tiny numbers, 0 < abs(input) < 2**-25, we can return the
+ ; input value directly.
+ fld st0 ; duplicate st0
+ fabs ; make it an absolute (positive) value.
+ fld qword [.s_r64Tiny xWrtRIP]
+ fcomip st1 ; compare s_r64Tiny and fabs(input)
+ ja .return_tiny_number_as_is ; jump if fabs(input) is smaller
+
+ ; FSIN is documented to be reasonable for the range ]-3pi/4,3pi/4[, so
+ ; while we have fabs(input) loaded already, check for that here and
+ ; allow rtNoCrtMathSinCore to assume it won't see values very close to
+ ; zero, except by cos -> sin conversion where they won't be relevant to
+ ; any assumpttions about precision approximation.
+ fld qword [.s_r64FSinOkay xWrtRIP]
+ fcomip st1
+ ffreep st0 ; drop the fabs(input) value
+ ja .do_sin
+
+ ;
+ ; Call common sine/cos worker.
+ ;
+ mov ecx, 1 ; double
+ extern NAME(rtNoCrtMathSinCore)
+ call NAME(rtNoCrtMathSinCore)
+
+ ;
+ ; Run st0.
+ ;
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp qword [xBP - 10h]
+ movsd xmm0, [xBP - 10h]
+%endif
+%ifdef RT_OS_WINDOWS
+ fldcw [xBP - 20h] ; restore original
+%endif
+.return:
+ leave
+ ret
+
+ ;
+ ; As explained already, we can return tiny numbers directly too as the
+ ; output from sin(input) = input given our precision.
+ ; We can skip the st0 -> xmm0 translation here, so follow the same path
+ ; as .zero & .nan, after we've removed the fabs(input) value.
+ ;
+.return_tiny_number_as_is:
+ ffreep st0
+
+ ;
+ ; sin(+/-0.0) = +/-0.0 (preserve the sign)
+ ; We can skip the st0 -> xmm0 translation here, so follow the .nan code path.
+ ;
+.zero:
+
+ ;
+ ; Input is NaN, output it unmodified as far as we can (FLD changes SNaN
+ ; to QNaN when masked).
+ ;
+.nan:
+%ifdef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ jmp .return
+
+ALIGNCODE(8)
+ ; Ca. 2**-17, absolute value. Inputs closer to zero than this can be
+ ; returns directly as the sin(input) value should be basically the same
+ ; given the precision we're working with and FSIN probably won't even
+ ; manage that.
+ ;; @todo experiment when FSIN gets better than this.
+.s_r64Tiny:
+ dq 0.00000762939453125
+ ; The absolute limit of FSIN "good" range.
+.s_r64FSinOkay:
+ dq 2.356194490192344928845 ; 3pi/4
+ ;dq 1.57079632679489661923 ; pi/2 - alternative.
+
+ENDPROC RT_NOCRT(sin)
+
diff --git a/src/VBox/Runtime/common/math/sincore.asm b/src/VBox/Runtime/common/math/sincore.asm
new file mode 100644
index 00000000..50493cbf
--- /dev/null
+++ b/src/VBox/Runtime/common/math/sincore.asm
@@ -0,0 +1,352 @@
+; $Id: sincore.asm $
+;; @file
+; IPRT - No-CRT common sin & cos - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Internal sine and cosine worker that calculates the sine of st0 returning
+; it in st0.
+;
+; When called by a sine function, fabs(st0) >= pi/2.
+; When called by a cosine function, fabs(original input value) >= 3pi/8.
+;
+; That the input isn't a tiny number close to zero, means that we can do a bit
+; cruder rounding when operating close to a pi/2 boundrary. The value in the
+; ecx register indicates the input precision and controls the crudeness of the
+; rounding.
+;
+; @returns st0 = sine
+; @param st0 A finite number to calucate sine of.
+; @param ecx Set to 0 if original input was a 32-bit float.
+; Set to 1 if original input was a 64-bit double.
+; set to 2 if original input was a 80-bit long double.
+;
+BEGINPROC rtNoCrtMathSinCore
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the pointer to the rounding crudeness factor into xDX.
+ ;
+ lea xDX, [.s_ar64NearZero xWrtRIP]
+ lea xDX, [xDX + xCX * xCB]
+
+ ;
+ ; Finite number. We want it in the range [0,2pi] and will preform
+ ; a remainder division if it isn't.
+ ;
+ fcom qword [.s_r64Max xWrtRIP] ; compares st0 and 2*pi
+ fnstsw ax
+ test ax, X86_FSW_C3 | X86_FSW_C0 | X86_FSW_C2 ; C3 := st0 == mem; C0 := st0 < mem; C2 := unordered (should be the case);
+ jz .reduce_st0 ; Jump if st0 > mem
+
+ fcom qword [.s_r64Min xWrtRIP] ; compares st0 and 0.0
+ fnstsw ax
+ test ax, X86_FSW_C3 | X86_FSW_C0
+ jnz .reduce_st0 ; Jump if st0 <= mem
+
+ ;
+ ; We get here if st0 is in the [0,2pi] range.
+ ;
+ ; Now, FSIN is documented to be reasonably accurate for the range
+ ; -3pi/4 to +3pi/4, so we have to make some more effort to calculate
+ ; in that range only.
+ ;
+.in_range:
+ ; if (st0 < pi)
+ fldpi
+ fcom st1 ; compares st0 (pi) with st1 (the normalized value)
+ fnstsw ax
+ test ax, X86_FSW_C0 ; st1 > pi
+ jnz .larger_than_pi
+ test ax, X86_FSW_C3
+ jnz .equals_pi
+
+ ;
+ ; input in the range [0,pi[
+ ;
+.smaller_than_pi:
+ fdiv qword [.s_r64Two xWrtRIP] ; st0 = pi/2
+
+ ; if (st0 < pi/2)
+ fcom st1 ; compares st0 (pi/2) with st1
+ fnstsw ax
+ test ax, X86_FSW_C0 ; st1 > pi
+ jnz .between_half_pi_and_pi
+ test ax, X86_FSW_C3
+ jnz .equals_half_pi
+
+ ;
+ ; The value is between zero and half pi, including the zero value.
+ ;
+ ; This is in range where FSIN works reasonably reliably. So drop the
+ ; half pi in st0 and do the calculation.
+ ;
+.between_zero_and_half_pi:
+ ; Check if we're so close to pi/2 that it makes no difference.
+ fsub st0, st1 ; st0 = pi/2 - st1
+ fcom qword [xDX]
+ fnstsw ax
+ test ax, X86_FSW_C0 | X86_FSW_C3 ; st0 <= very small positive number.
+ jnz .equals_half_pi
+ ffreep st0
+
+ ; Check if we're so close to zero that it makes no difference given the
+ ; internal accuracy of the FPU.
+ fcom qword [xDX]
+ fnstsw ax
+ test ax, X86_FSW_C0 | X86_FSW_C3 ; st0 <= very small positive number.
+ jnz .equals_zero_popped_one
+
+ ; Ok, calculate sine.
+ fsin
+ jmp .return
+
+ ;
+ ; The value is in the range ]pi/2,pi[
+ ;
+ ; This is outside the comfortable FSIN range, but if we subtract PI and
+ ; move to the ]-pi/2,0[ range we just have to change the sign to get
+ ; the value we want.
+ ;
+.between_half_pi_and_pi:
+ ; Check if we're so close to pi/2 that it makes no difference.
+ fsubr st0, st1 ; st0 = st1 - st0
+ fcom qword [xDX]
+ fnstsw ax
+ test ax, X86_FSW_C0 | X86_FSW_C3 ; st0 <= very small positive number.
+ jnz .equals_half_pi
+ ffreep st0
+
+ ; Check if we're so close to pi that it makes no difference.
+ fldpi
+ fsub st0, st1 ; st0 = st0 - st1
+ fcom qword [xDX]
+ fnstsw ax
+ test ax, X86_FSW_C0 | X86_FSW_C3 ; st0 <= very small positive number.
+ jnz .equals_pi
+ ffreep st0
+
+ ; Ok, transform the value and calculate sine.
+ fldpi
+ fsubp st1, st0
+
+ fsin
+ fchs
+ jmp .return
+
+ ;
+ ; input in the range ]pi,2pi[
+ ;
+.larger_than_pi:
+ fsub st1, st0 ; st1 -= pi
+ fdiv qword [.s_r64Two xWrtRIP] ; st0 = pi/2
+
+ ; if (st0 < pi/2)
+ fcom st1 ; compares st0 (pi/2) with reduced st1
+ fnstsw ax
+ test ax, X86_FSW_C0 ; st1 > pi
+ jnz .between_3_half_pi_and_2pi
+ test ax, X86_FSW_C3
+ jnz .equals_3_half_pi
+
+ ;
+ ; The value is in the the range: ]pi,3pi/2[
+ ;
+ ; The actual st0 is in the range ]pi,pi/2[ where FSIN is performing okay
+ ; and we can get the desired result by changing the sign (-FSIN).
+ ;
+.between_pi_and_3_half_pi:
+ ; Check if we're so close to pi/2 that it makes no difference.
+ fsub st0, st1 ; st0 = pi/2 - st1
+ fcom qword [xDX]
+ fnstsw ax
+ test ax, X86_FSW_C0 | X86_FSW_C3 ; st0 <= very small positive number.
+ jnz .equals_3_half_pi
+ ffreep st0
+
+ ; Check if we're so close to zero that it makes no difference given the
+ ; internal accuracy of the FPU.
+ fcom qword [xDX]
+ fnstsw ax
+ test ax, X86_FSW_C0 | X86_FSW_C3 ; st0 <= very small positive number.
+ jnz .equals_pi_popped
+
+ ; Ok, calculate sine and flip the sign.
+ fsin
+ fchs
+ jmp .return
+
+ ;
+ ; The value is in the last pi/2 of the range: ]3pi/2,2pi[
+ ;
+ ; Since FSIN should work reasonably well for ]-pi/2,pi], we can just
+ ; subtract pi again (we subtracted pi at .larger_than_pi above) and
+ ; run FSIN on it. (st1 is currently in the range ]pi/2,pi[.)
+ ;
+.between_3_half_pi_and_2pi:
+ ; Check if we're so close to pi/2 that it makes no difference.
+ fsubr st0, st1 ; st0 = st1 - st0
+ fcom qword [xDX]
+ fnstsw ax
+ test ax, X86_FSW_C0 | X86_FSW_C3 ; st0 <= very small positive number.
+ jnz .equals_3_half_pi
+ ffreep st0
+
+ ; Check if we're so close to pi that it makes no difference.
+ fldpi
+ fsub st0, st1 ; st0 = st0 - st1
+ fcom qword [xDX]
+ fnstsw ax
+ test ax, X86_FSW_C0 | X86_FSW_C3 ; st0 <= very small positive number.
+ jnz .equals_2pi
+ ffreep st0
+
+ ; Ok, adjust input and calculate sine.
+ fldpi
+ fsubp st1, st0
+ fsin
+ jmp .return
+
+ ;
+ ; sin(0) = 0
+ ; sin(pi) = 0
+ ;
+.equals_zero:
+.equals_pi:
+.equals_2pi:
+ ffreep st0
+.equals_zero_popped_one:
+.equals_pi_popped:
+ ffreep st0
+ fldz
+ jmp .return
+
+ ;
+ ; sin(pi/2) = 1
+ ;
+.equals_half_pi:
+ ffreep st0
+ ffreep st0
+ fld1
+ jmp .return
+
+ ;
+ ; sin(3*pi/2) = -1
+ ;
+.equals_3_half_pi:
+ ffreep st0
+ ffreep st0
+ fld1
+ fchs
+ jmp .return
+
+ ;
+ ; Return.
+ ;
+.return:
+ leave
+ ret
+
+ ;
+ ; Reduce st0 by reminder division by PI*2. The result should be positive here.
+ ;
+ ;; @todo this is one of our weak spots (really any calculation involving PI is).
+.reduce_st0:
+ fldpi
+ fadd st0, st0
+ fxch st1 ; st0=input (dividend) st1=2pi (divisor)
+.again:
+ fprem1
+ fnstsw ax
+ test ah, (X86_FSW_C2 >> 8) ; C2 is set if partial result.
+ jnz .again ; Loop till C2 == 0 and we have a final result.
+
+ ;
+ ; Make sure the result is positive.
+ ;
+ fxam
+ fnstsw ax
+ test ax, X86_FSW_C1 ; The sign bit
+ jz .reduced_to_positive
+
+ fadd st0, st1 ; st0 += 2pi, which should make it positive
+
+%ifdef RT_STRICT
+ fxam
+ fnstsw ax
+ test ax, X86_FSW_C1
+ jz .reduced_to_positive
+ int3
+%endif
+
+.reduced_to_positive:
+ fstp st1 ; Get rid of the 2pi value.
+ jmp .in_range
+
+ALIGNCODE(8)
+.s_r64Max:
+ dq +6.28318530717958647692 ; 2*pi
+.s_r64Min:
+ dq 0.0
+.s_r64Two:
+ dq 2.0
+ ;;
+ ; Close to 2/pi rounding limits for 32-bit, 64-bit and 80-bit floating point operations.
+ ; Given that the original input is at least +/-3pi/8 (1.178) and that precision of the
+ ; PI constant used during reduction/whatever, I think we can round to a whole pi/2
+ ; step when we get close enough.
+ ;
+ ; Look to RTFLOAT64U for the format details, but 52 is the shift for the exponent field
+ ; and 1023 is the exponent bias. Since the format uses an implied 1 in the mantissa,
+ ; we only have to set the exponent to get a valid number.
+ ;
+.s_ar64NearZero:
+;; @todo check how sensible these really are...
+ dq (-18 + 1023) << 52 ; float / 32-bit / single precision input
+ dq (-40 + 1023) << 52 ; double / 64-bit / double precision input
+ dq (-52 + 1023) << 52 ; long double / 80-bit / extended precision input
+ENDPROC rtNoCrtMathSinCore
+
diff --git a/src/VBox/Runtime/common/math/sinf.asm b/src/VBox/Runtime/common/math/sinf.asm
new file mode 100644
index 00000000..1d2325d1
--- /dev/null
+++ b/src/VBox/Runtime/common/math/sinf.asm
@@ -0,0 +1,185 @@
+; $Id: sinf.asm $
+;; @file
+; IPRT - No-CRT sinf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+
+;;
+; Compute the sine of rd, measured in radians.
+;
+; @returns st(0) / xmm0
+; @param rd [rbp + xCB*2] / xmm0
+;
+RT_NOCRT_BEGINPROC sinf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+%ifdef RT_OS_WINDOWS
+ ;
+ ; Make sure we use full precision and not the windows default of 53 bits.
+ ;
+ fnstcw [xBP - 20h]
+ mov ax, [xBP - 20h]
+ or ax, X86_FCW_PC_64 ; includes both bits, so no need to clear the mask.
+ mov [xBP - 1ch], ax
+ fldcw [xBP - 1ch]
+%endif
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movss [xBP - 10h], xmm0
+ fld dword [xBP - 10h]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ;
+ ; We examin the input and weed out non-finit numbers first.
+ ;
+ fxam
+ fnstsw ax
+ and ax, X86_FSW_C3 | X86_FSW_C2 | X86_FSW_C0
+ cmp ax, X86_FSW_C2 ; Normal finite number (excluding zero)
+ je .finite
+ cmp ax, X86_FSW_C3 ; Zero
+ je .zero
+ cmp ax, X86_FSW_C3 | X86_FSW_C2 ; Denormals - treat them as zero.
+ je .zero
+ cmp ax, X86_FSW_C0 ; NaN - must handle it special,
+ je .nan
+
+ ; Pass infinities and unsupported inputs to fsin, assuming it does the right thing.
+.do_sin:
+ fsin
+ jmp .return_val
+
+ ;
+ ; Finite number.
+ ;
+.finite:
+ ; For very tiny numbers, 0 < abs(input) < 2**-25, we can return the
+ ; input value directly.
+ fld st0 ; duplicate st0
+ fabs ; make it an absolute (positive) value.
+ fld qword [.s_r64Tiny xWrtRIP]
+ fcomip st1 ; compare s_r64Tiny and fabs(input)
+ ja .return_tiny_number_as_is ; jump if fabs(input) is smaller
+
+ ; FSIN is documented to be reasonable for the range ]-3pi/4,3pi/4[, so
+ ; while we have fabs(input) loaded already, check for that here and
+ ; allow rtNoCrtMathSinCore to assume it won't see values very close to
+ ; zero, except by cos -> sin conversion where they won't be relevant to
+ ; any assumpttions about precision approximation.
+ fld qword [.s_r64FSinOkay xWrtRIP]
+ fcomip st1
+ ffreep st0 ; drop the fabs(input) value
+ ja .do_sin
+
+ ;
+ ; Call common sine/cos worker.
+ ;
+ mov ecx, 0 ; float
+ extern NAME(rtNoCrtMathSinCore)
+ call NAME(rtNoCrtMathSinCore)
+
+ ;
+ ; Run st0.
+ ;
+.return_val:
+%ifdef RT_ARCH_AMD64
+ fstp dword [xBP - 10h]
+ movss xmm0, [xBP - 10h]
+%endif
+%ifdef RT_OS_WINDOWS
+ fldcw [xBP - 20h] ; restore original
+%endif
+.return:
+ leave
+ ret
+
+ ;
+ ; As explained already, we can return tiny numbers directly too as the
+ ; output from sinf(input) = input given our precision.
+ ; We can skip the st0 -> xmm0 translation here, so follow the same path
+ ; as .zero & .nan, after we've removed the fabs(input) value.
+ ;
+.return_tiny_number_as_is:
+ ffreep st0
+
+ ;
+ ; sinf(+/-0.0) = +/-0.0 (preserve the sign)
+ ; We can skip the st0 -> xmm0 translation here, so follow the .nan code path.
+ ;
+.zero:
+
+ ;
+ ; Input is NaN, output it unmodified as far as we can (FLD changes SNaN
+ ; to QNaN when masked).
+ ;
+.nan:
+%ifdef RT_ARCH_AMD64
+ ffreep st0
+%endif
+ jmp .return
+
+ALIGNCODE(8)
+ ; Ca. 2**-26, absolute value. Inputs closer to zero than this can be
+ ; returns directly as the sinf(input) value should be basically the same
+ ; given the precision we're working with and FSIN probably won't even
+ ; manage that.
+ ;; @todo experiment when FSIN gets better than this.
+.s_r64Tiny:
+ dq 1.49011612e-8
+ ; The absolute limit of FSIN "good" range.
+.s_r64FSinOkay:
+ dq 2.356194490192344928845 ; 3pi/4
+ ;dq 1.57079632679489661923 ; pi/2 - alternative.
+
+ENDPROC RT_NOCRT(sinf)
+
diff --git a/src/VBox/Runtime/common/math/sinl.asm b/src/VBox/Runtime/common/math/sinl.asm
new file mode 100644
index 00000000..93fa31b6
--- /dev/null
+++ b/src/VBox/Runtime/common/math/sinl.asm
@@ -0,0 +1,71 @@
+; $Id: sinl.asm $
+;; @file
+; IPRT - No-CRT sinl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Compute the sine of lrd
+; @returns st(0)
+; @param lrd [xSP + xCB*2]
+RT_NOCRT_BEGINPROC sinl
+ push xBP
+ mov xBP, xSP
+ sub xSP, 10h
+
+ fld tword [xBP + xCB*2]
+ fsin
+ fnstsw ax
+ test ah, 04h
+ jz .done
+
+ fldpi
+ fadd st0
+ fxch st1
+.again:
+ fprem1
+ fnstsw ax
+ test ah, 04h
+ jnz .again
+ fstp st1
+ fsin
+
+.done:
+ leave
+ ret
+ENDPROC RT_NOCRT(sinl)
+
diff --git a/src/VBox/Runtime/common/math/sqrt.asm b/src/VBox/Runtime/common/math/sqrt.asm
new file mode 100644
index 00000000..64aa83c0
--- /dev/null
+++ b/src/VBox/Runtime/common/math/sqrt.asm
@@ -0,0 +1,65 @@
+; $Id: sqrt.asm $
+;; @file
+; IPRT - No-CRT sqrt - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Square root - the fast not entirely C standard confirming version.
+;
+; @returns st(0) / xmm0
+; @param rd [rbp + 08h] / xmm0
+RT_NOCRT_BEGINPROC sqrt
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ sqrtsd xmm0, xmm0
+%else
+ fld qword [xBP + xCB*2]
+ fsqrt
+ ;; @todo rounding?
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(sqrt)
+
diff --git a/src/VBox/Runtime/common/math/sqrtf.asm b/src/VBox/Runtime/common/math/sqrtf.asm
new file mode 100644
index 00000000..39bea17d
--- /dev/null
+++ b/src/VBox/Runtime/common/math/sqrtf.asm
@@ -0,0 +1,65 @@
+; $Id: sqrtf.asm $
+;; @file
+; IPRT - No-CRT sqrtf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2022-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Square root - the fast not entirely C standard confirming version.
+;
+; @returns st(0) / xmm0
+; @param r32 [rbp + 08h] / xmm0
+RT_NOCRT_BEGINPROC sqrtf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ SEH64_END_PROLOGUE
+
+%ifdef RT_ARCH_AMD64
+ sqrtss xmm0, xmm0
+%else
+ fld dword [xBP + xCB*2]
+ fsqrt
+ ;; @todo rounding?
+%endif
+
+ leave
+ ret
+ENDPROC RT_NOCRT(sqrtf)
+
diff --git a/src/VBox/Runtime/common/math/tan.asm b/src/VBox/Runtime/common/math/tan.asm
new file mode 100644
index 00000000..a29dddf8
--- /dev/null
+++ b/src/VBox/Runtime/common/math/tan.asm
@@ -0,0 +1,119 @@
+; $Id: tan.asm $
+;; @file
+; IPRT - No-CRT tan - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Compute the sine of rd
+; @returns st(0) / xmm0
+; @param rd [xSP + xCB*2] / xmm0
+RT_NOCRT_BEGINPROC tan
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+%ifdef RT_OS_WINDOWS
+ ;
+ ; Make sure we use full precision and not the windows default of 53 bits.
+ ;
+ fnstcw [xBP - 20h]
+ mov ax, [xBP - 20h]
+ or ax, X86_FCW_PC_64 ; includes both bits, so no need to clear the mask.
+ mov [xBP - 1ch], ax
+ fldcw [xBP - 1ch]
+%endif
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd [xBP - 10h], xmm0
+ fld qword [xBP - 10h]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Calculate the tangent.
+ ;
+ fptan
+ fnstsw ax
+ test ah, (X86_FSW_C2 >> 8) ; C2 is set if the input was out of range.
+ jz .return_val
+
+ ;
+ ; Input was out of range, perform reduction to +/-2pi.
+ ;
+ fldpi
+ fadd st0
+ fxch st1
+.again:
+ fprem1
+ fnstsw ax
+ test ah, (X86_FSW_C2 >> 8) ; C2 is set if partial result.
+ jnz .again ; Loop till C2 == 0 and we have a final result.
+
+ fstp st1
+
+ fptan
+
+ ;
+ ; Run st0.
+ ;
+.return_val:
+ ffreep st0 ; ignore the 1.0 fptan pushed
+%ifdef RT_ARCH_AMD64
+ fstp qword [xBP - 10h]
+ movsd xmm0, [xBP - 10h]
+%endif
+%ifdef RT_OS_WINDOWS
+ fldcw [xBP - 20h] ; restore original
+%endif
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(tan)
+
diff --git a/src/VBox/Runtime/common/math/tanf.asm b/src/VBox/Runtime/common/math/tanf.asm
new file mode 100644
index 00000000..ce7fc9b8
--- /dev/null
+++ b/src/VBox/Runtime/common/math/tanf.asm
@@ -0,0 +1,119 @@
+; $Id: tanf.asm $
+;; @file
+; IPRT - No-CRT tanf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Compute the sine of rf
+; @returns st(0) / xmm0
+; @param rf [xSP + xCB*2] / xmm0
+RT_NOCRT_BEGINPROC tanf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 20h
+ SEH64_ALLOCATE_STACK 20h
+ SEH64_END_PROLOGUE
+
+%ifdef RT_OS_WINDOWS
+ ;
+ ; Make sure we use full precision and not the windows default of 53 bits.
+ ;
+ fnstcw [xBP - 20h]
+ mov ax, [xBP - 20h]
+ or ax, X86_FCW_PC_64 ; includes both bits, so no need to clear the mask.
+ mov [xBP - 1ch], ax
+ fldcw [xBP - 1ch]
+%endif
+
+ ;
+ ; Load the input into st0.
+ ;
+%ifdef RT_ARCH_AMD64
+ movss [xBP - 10h], xmm0
+ fld dword [xBP - 10h]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Calculate the tangent.
+ ;
+ fptan
+ fnstsw ax
+ test ah, (X86_FSW_C2 >> 8) ; C2 is set if the input was out of range.
+ jz .return_val
+
+ ;
+ ; Input was out of range, perform reduction to +/-2pi.
+ ;
+ fldpi
+ fadd st0
+ fxch st1
+.again:
+ fprem1
+ fnstsw ax
+ test ah, (X86_FSW_C2 >> 8) ; C2 is set if partial result.
+ jnz .again ; Loop till C2 == 0 and we have a final result.
+
+ fstp st1
+
+ fptan
+
+ ;
+ ; Run st0.
+ ;
+.return_val:
+ ffreep st0 ; ignore the 1.0 fptan pushed
+%ifdef RT_ARCH_AMD64
+ fstp dword [xBP - 10h]
+ movss xmm0, [xBP - 10h]
+%endif
+%ifdef RT_OS_WINDOWS
+ fldcw [xBP - 20h] ; restore original
+%endif
+.return:
+ leave
+ ret
+ENDPROC RT_NOCRT(tanf)
+
diff --git a/src/VBox/Runtime/common/math/tanl.asm b/src/VBox/Runtime/common/math/tanl.asm
new file mode 100644
index 00000000..1056e60b
--- /dev/null
+++ b/src/VBox/Runtime/common/math/tanl.asm
@@ -0,0 +1,72 @@
+; $Id: tanl.asm $
+;; @file
+; IPRT - No-CRT tanl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; Compute the sine of lrd
+; @returns st(0)
+; @param lrd [xSP + xCB*2]
+RT_NOCRT_BEGINPROC tanl
+ push xBP
+ mov xBP, xSP
+ sub xSP, 10h
+
+ fld tword [xBP + xCB*2]
+ fptan
+ fnstsw ax
+ test ah, 04h ; check for C2
+ jz .done
+
+ fldpi
+ fadd st0
+ fxch st1
+.again:
+ fprem1
+ fnstsw ax
+ test ah, 04h
+ jnz .again
+ fstp st1
+ fptan
+
+.done:
+ fstp st0
+ leave
+ ret
+ENDPROC RT_NOCRT(tanl)
+
diff --git a/src/VBox/Runtime/common/math/trunc.asm b/src/VBox/Runtime/common/math/trunc.asm
new file mode 100644
index 00000000..4234f5b6
--- /dev/null
+++ b/src/VBox/Runtime/common/math/trunc.asm
@@ -0,0 +1,108 @@
+; $Id: trunc.asm $
+;; @file
+; IPRT - No-CRT trunc - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Round to truncated integer value.
+; @returns 32-bit: st(0) 64-bit: xmm0
+; @param rd 32-bit: [ebp + 8] 64-bit: xmm0
+RT_NOCRT_BEGINPROC trunc
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the value into st(0). This messes up SNaN values.
+ ;
+%ifdef RT_ARCH_AMD64
+ movsd [xSP], xmm0
+ fld qword [xSP]
+%else
+ fld qword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Return immediately if NaN or infinity.
+ ;
+ fxam
+ fstsw ax
+ test ax, X86_FSW_C0 ; C0 is set for NaN, Infinity and Empty register. The latter is not the case.
+ jz .input_ok
+%ifdef RT_ARCH_AMD64
+ ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN.
+%endif
+ jmp .return_val
+.input_ok:
+
+ ;
+ ; Make it truncate up by modifying the fpu control word.
+ ;
+ fstcw [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or eax, X86_FCW_RC_ZERO ; both bits set, so no need to clear anything first.
+ mov [xBP - 08h], eax
+ fldcw [xBP - 08h]
+
+ ;
+ ; Round ST(0) to integer.
+ ;
+ frndint
+
+ ;
+ ; Restore the fpu control word and return.
+ ;
+ fldcw [xBP - 10h]
+
+%ifdef RT_ARCH_AMD64
+ fstp qword [xSP]
+ movsd xmm0, [xSP]
+%endif
+.return_val:
+ leave
+ ret
+ENDPROC RT_NOCRT(trunc)
+
diff --git a/src/VBox/Runtime/common/math/truncf.asm b/src/VBox/Runtime/common/math/truncf.asm
new file mode 100644
index 00000000..677b7fd3
--- /dev/null
+++ b/src/VBox/Runtime/common/math/truncf.asm
@@ -0,0 +1,108 @@
+; $Id: truncf.asm $
+;; @file
+; IPRT - No-CRT truncf - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Round to truncated integer value.
+; @returns 32-bit: st(0) 64-bit: xmm0
+; @param rf 32-bit: [ebp + 8] 64-bit: xmm0
+RT_NOCRT_BEGINPROC truncf
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Load the value into st(0). This messes up SNaN values.
+ ;
+%ifdef RT_ARCH_AMD64
+ movss [xSP], xmm0
+ fld dword [xSP]
+%else
+ fld dword [xBP + xCB*2]
+%endif
+
+ ;
+ ; Return immediately if NaN or infinity.
+ ;
+ fxam
+ fstsw ax
+ test ax, X86_FSW_C0 ; C0 is set for NaN, Infinity and Empty register. The latter is not the case.
+ jz .input_ok
+%ifdef RT_ARCH_AMD64
+ ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN.
+%endif
+ jmp .return_val
+.input_ok:
+
+ ;
+ ; Make it truncate up by modifying the fpu control word.
+ ;
+ fstcw [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or eax, X86_FCW_RC_ZERO ; both bits set, so no need to clear anything first.
+ mov [xBP - 08h], eax
+ fldcw [xBP - 08h]
+
+ ;
+ ; Round ST(0) to integer.
+ ;
+ frndint
+
+ ;
+ ; Restore the fpu control word and return.
+ ;
+ fldcw [xBP - 10h]
+
+%ifdef RT_ARCH_AMD64
+ fstp dword [xSP]
+ movss xmm0, [xSP]
+%endif
+.return_val:
+ leave
+ ret
+ENDPROC RT_NOCRT(truncf)
+
diff --git a/src/VBox/Runtime/common/math/truncl.asm b/src/VBox/Runtime/common/math/truncl.asm
new file mode 100644
index 00000000..e037cab5
--- /dev/null
+++ b/src/VBox/Runtime/common/math/truncl.asm
@@ -0,0 +1,76 @@
+; $Id: truncl.asm $
+;; @file
+; IPRT - No-CRT truncl - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+%include "iprt/x86.mac"
+
+
+BEGINCODE
+
+;;
+; Round to truncated integer value.
+; @returns st(0)
+; @param lrd [rbp + 8]
+RT_NOCRT_BEGINPROC truncl
+ push xBP
+ SEH64_PUSH_xBP
+ mov xBP, xSP
+ SEH64_SET_FRAME_xBP 0
+ sub xSP, 10h
+ SEH64_ALLOCATE_STACK 10h
+ SEH64_END_PROLOGUE
+
+ fld tword [xBP + xCB*2]
+
+ ; Make it truncate up by modifying the fpu control word.
+ fstcw [xBP - 10h]
+ mov eax, [xBP - 10h]
+ or eax, X86_FCW_RC_ZERO ; both bits set, so no need to clear anything first.
+ mov [xBP - 08h], eax
+ fldcw [xBP - 08h]
+
+ ; Round ST(0) to integer.
+ frndint
+
+ ; Restore the fpu control word.
+ fldcw [xBP - 10h]
+
+ leave
+ ret
+ENDPROC RT_NOCRT(truncl)
+
diff --git a/src/VBox/Runtime/common/math/watcom/I8D-x86-32.asm b/src/VBox/Runtime/common/math/watcom/I8D-x86-32.asm
new file mode 100644
index 00000000..2aa02520
--- /dev/null
+++ b/src/VBox/Runtime/common/math/watcom/I8D-x86-32.asm
@@ -0,0 +1,108 @@
+; $Id: I8D-x86-32.asm $
+;; @file
+; BS3Kit - 32-bit Watcom C/C++, 64-bit signed integer division.
+;
+
+;
+; Copyright (C) 2007-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+extern __U8D
+
+
+;;
+; 64-bit signed integer division.
+;
+; @returns EDX:EAX Quotient, ECX:EBX Remainder.
+; @param EDX:EAX Dividend.
+; @param ECX:EBX Divisor
+;
+global __I8D
+__I8D:
+ ;
+ ; We use __U8D to do the work, we take care of the signedness.
+ ;
+ or edx, edx
+ js .negative_dividend
+
+ or ecx, ecx
+ js .negative_divisor_positive_dividend
+ jmp __U8D
+
+
+.negative_divisor_positive_dividend:
+ ; negate the divisor, do unsigned division, and negate the quotient.
+ neg ecx
+ neg ebx
+ sbb ecx, 0
+
+ call __U8D
+
+ neg edx
+ neg eax
+ sbb edx, 0
+ ret
+
+.negative_dividend:
+ neg edx
+ neg eax
+ sbb edx, 0
+
+ or ecx, ecx
+ js .negative_dividend_negative_divisor
+
+.negative_dividend_positive_divisor:
+ ; negate the dividend (above), do unsigned division, and negate both quotient and remainder
+ call __U8D
+
+ neg edx
+ neg eax
+ sbb edx, 0
+
+.return_negated_remainder:
+ neg ecx
+ neg ebx
+ sbb ecx, 0
+ ret
+
+.negative_dividend_negative_divisor:
+ ; negate both dividend (above) and divisor, do unsigned division, and negate the remainder.
+ neg ecx
+ neg ebx
+ sbb ecx, 0
+
+ call __U8D
+ jmp .return_negated_remainder
+
diff --git a/src/VBox/Runtime/common/math/watcom/RTWatcomUInt64Div.c b/src/VBox/Runtime/common/math/watcom/RTWatcomUInt64Div.c
new file mode 100644
index 00000000..fda3c972
--- /dev/null
+++ b/src/VBox/Runtime/common/math/watcom/RTWatcomUInt64Div.c
@@ -0,0 +1,48 @@
+/* $Id: RTWatcomUInt64Div.c $ */
+/** @file
+ * BS3Kit - Unsigned 64-bit division (compiler support routine helper).
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/uint64.h>
+
+
+DECLASM(void) RTWatcomUInt64Div(RTUINT64U uDividend, RTUINT64U uDivisor, RTUINT64U RT_FAR *paQuotientReminder)
+{
+ RTUInt64DivRem(&paQuotientReminder[0], &paQuotientReminder[1], &uDividend, &uDivisor);
+}
+
diff --git a/src/VBox/Runtime/common/math/watcom/U8D-x86-32.asm b/src/VBox/Runtime/common/math/watcom/U8D-x86-32.asm
new file mode 100644
index 00000000..4eb22411
--- /dev/null
+++ b/src/VBox/Runtime/common/math/watcom/U8D-x86-32.asm
@@ -0,0 +1,84 @@
+; $Id: U8D-x86-32.asm $
+;; @file
+; BS3Kit - 32-bit Watcom C/C++, 64-bit unsigned integer division.
+;
+
+;
+; Copyright (C) 2007-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+extern NAME(RTWatcomUInt64Div)
+
+
+;;
+; 64-bit unsigned integer division.
+;
+; @returns EDX:EAX Quotient, ECX:EBX Remainder.
+; @param EDX:EAX Dividend.
+; @param ECX:EBX Divisor
+;
+global __U8D
+__U8D:
+ ;
+ ; Convert to a C __cdecl call - not doing this in assembly.
+ ;
+
+ ; Set up a frame, allocating 16 bytes for the result buffer.
+ push ebp
+ mov ebp, esp
+ sub esp, 10h
+
+ ; Pointer to the return buffer.
+ push esp
+
+ ; The divisor.
+ push ecx
+ push ebx
+
+ ; The dividend.
+ push edx
+ push eax
+
+ call NAME(RTWatcomUInt64Div)
+
+ ; Load the result.
+ mov ecx, [ebp - 10h + 12]
+ mov ebx, [ebp - 10h + 8]
+ mov edx, [ebp - 10h + 4]
+ mov eax, [ebp - 10h]
+
+ leave
+ ret
+
diff --git a/src/VBox/Runtime/common/math/watcom/U8LS-x86-32.asm b/src/VBox/Runtime/common/math/watcom/U8LS-x86-32.asm
new file mode 100644
index 00000000..ea81565a
--- /dev/null
+++ b/src/VBox/Runtime/common/math/watcom/U8LS-x86-32.asm
@@ -0,0 +1,74 @@
+; $Id: U8LS-x86-32.asm $
+;; @file
+; BS3Kit - 32-bit Watcom C/C++, 64-bit integer left shift.
+;
+
+;
+; Copyright (C) 2007-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; 64-bit integer left shift.
+;
+; @returns EDX:EAX
+; @param EDX:EAX Value to shift.
+; @param BL Shift count (it's specified as ECX:EBX, but we only use BL).
+;
+global __U8LS
+__U8LS:
+global __I8LS
+__I8LS:
+ push ecx ; We're allowed to trash ECX, but why bother.
+
+ mov cl, bl
+ and cl, 3fh
+ test cl, 20h
+ jnz .big_shift
+
+ ; Shifting less than 32.
+ shld edx, eax, cl
+ shl eax, cl
+
+.return:
+ pop ecx
+ ret
+
+.big_shift:
+ ; Shifting 32 or more.
+ mov edx, eax
+ shl edx, cl ; Only uses lower 5 bits.
+ xor eax, eax
+ jmp .return
+
diff --git a/src/VBox/Runtime/common/math/watcom/U8M-I8M-x86-32.asm b/src/VBox/Runtime/common/math/watcom/U8M-I8M-x86-32.asm
new file mode 100644
index 00000000..3b01cd5c
--- /dev/null
+++ b/src/VBox/Runtime/common/math/watcom/U8M-I8M-x86-32.asm
@@ -0,0 +1,87 @@
+; $Id: U8M-I8M-x86-32.asm $
+;; @file
+; BS3Kit - 32-bit Watcom C/C++, 64-bit unsigned integer division.
+;
+
+;
+; Copyright (C) 2007-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+
+;;
+; 64-bit signed & unsigned integer multiplication.
+;
+; @returns EDX:EAX product
+; @param EDX:EAX Factor #1.
+; @param ECX:EBX Factor #2.
+; @uses ECX, EBX
+;
+global __U8M
+__U8M:
+global __I8M
+__I8M:
+ ;
+ ; See if this is a pure 32-bit multiplication. We might get lucky.
+ ;
+ test edx, edx
+ jnz .complicated
+ test ecx, ecx
+ jnz .complicated
+
+ mul ebx ; eax * ebx -> edx:eax
+ ret
+
+.complicated:
+ push eax
+ push edx
+
+ ; ecx = F1.lo * F2.hi (edx contains overflow here can be ignored)
+ mul ecx
+ mov ecx, eax
+
+ ; ecx += F1.hi * F2.lo (edx can be ignored again)
+ pop eax
+ mul ebx
+ add ecx, eax
+
+ ; edx:eax = F1.lo * F2.lo
+ pop eax
+ mul ebx
+
+ ; Add ecx to the high part (edx).
+ add edx, ecx
+
+ ret
+
diff --git a/src/VBox/Runtime/common/math/watcom/U8RS-x86-32.asm b/src/VBox/Runtime/common/math/watcom/U8RS-x86-32.asm
new file mode 100644
index 00000000..8be43d13
--- /dev/null
+++ b/src/VBox/Runtime/common/math/watcom/U8RS-x86-32.asm
@@ -0,0 +1,73 @@
+; $Id: U8RS-x86-32.asm $
+;; @file
+; BS3Kit - 32-bit Watcom C/C++, 64-bit unsigned integer right shift.
+;
+
+;
+; Copyright (C) 2007-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+
+;;
+; 64-bit unsigned integer right shift.
+;
+; @returns EDX:EAX
+; @param EDX:EAX Value to shift.
+; @param BL Shift count (it's specified as ECX:EBX, but we only use BL).
+;
+global __U8RS
+__U8RS:
+ push ecx ; We're allowed to trash ECX, but why bother.
+
+ mov cl, bl
+ and cl, 3fh
+ test cl, 20h
+ jnz .big_shift
+
+ ; Shifting less than 32.
+ shrd eax, edx, cl
+ shr edx, cl
+
+.return:
+ pop ecx
+ ret
+
+.big_shift:
+ ; Shifting 32 or more.
+ mov eax, edx
+ shr eax, cl ; Only uses lower 5 bits.
+ xor edx, edx
+ jmp .return
+
diff --git a/src/VBox/Runtime/common/misc/Makefile.kup b/src/VBox/Runtime/common/misc/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/Makefile.kup
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp
new file mode 100644
index 00000000..ab6a7283
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg1Weak.cpp
@@ -0,0 +1,52 @@
+/* $Id: RTAssertMsg1Weak.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg1Weak.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg1Weak(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
+{
+ RTAssertMsg1(pszExpr, uLine, pszFile, pszFunction);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg1Weak);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp
new file mode 100644
index 00000000..0369d8ce
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2.cpp
@@ -0,0 +1,55 @@
+/* $Id: RTAssertMsg2.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg2(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2V(pszFormat, va);
+ va_end(va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp
new file mode 100644
index 00000000..b9a8be4b
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2Add.cpp
@@ -0,0 +1,55 @@
+/* $Id: RTAssertMsg2Add.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2Add.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg2Add(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2AddV(pszFormat, va);
+ va_end(va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2Add);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp
new file mode 100644
index 00000000..335d408d
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeak.cpp
@@ -0,0 +1,55 @@
+/* $Id: RTAssertMsg2AddWeak.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2AddWeak.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg2AddWeak(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2AddWeakV(pszFormat, va);
+ va_end(va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2AddWeak);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp
new file mode 100644
index 00000000..aafd9e68
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2AddWeakV.cpp
@@ -0,0 +1,50 @@
+/* $Id: RTAssertMsg2AddWeakV.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2AddWeakV.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include "internal/iprt.h"
+
+
+RTDECL(void) RTAssertMsg2AddWeakV(const char *pszFormat, va_list va)
+{
+ RTAssertMsg2AddV(pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2AddWeakV);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp
new file mode 100644
index 00000000..7a566e56
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2Weak.cpp
@@ -0,0 +1,55 @@
+/* $Id: RTAssertMsg2Weak.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2Weak.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(void) RTAssertMsg2Weak(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2WeakV(pszFormat, va);
+ va_end(va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2Weak);
+
diff --git a/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp b/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp
new file mode 100644
index 00000000..ccd56b73
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTAssertMsg2WeakV.cpp
@@ -0,0 +1,50 @@
+/* $Id: RTAssertMsg2WeakV.cpp $ */
+/** @file
+ * IPRT - RTAssertMsg2WeakV.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include "internal/iprt.h"
+
+
+RTDECL(void) RTAssertMsg2WeakV(const char *pszFormat, va_list va)
+{
+ RTAssertMsg2V(pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2WeakV);
+
diff --git a/src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp b/src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp
new file mode 100644
index 00000000..1b6dcd3c
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTFileModeToFlags.cpp
@@ -0,0 +1,364 @@
+/* $Id: RTFileModeToFlags.cpp $ */
+/** @file
+ * IPRT - RTFileModeToFlags.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTR3DECL(int) RTFileModeToFlags(const char *pszMode, uint64_t *pfMode)
+{
+ AssertPtrReturn(pszMode, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfMode, VERR_INVALID_POINTER);
+
+ const char *pszCur = pszMode;
+ if (*pszCur == '\0')
+ return VERR_INVALID_PARAMETER;
+
+ uint64_t fMode = 0;
+ char chPrev = '\0';
+ while ( pszCur
+ && *pszCur != '\0')
+ {
+ bool fSkip = false;
+ switch (*pszCur)
+ {
+ /* Opens an existing file for writing and places the
+ * file pointer at the end of the file. The file is
+ * created if it does not exist. */
+ case 'a':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_OPEN_CREATE
+ | RTFILE_O_WRITE
+ | RTFILE_O_APPEND;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ case 'b': /* Binary mode. */
+ /* Just skip as being valid. */
+ fSkip = true;
+ break;
+
+ /* Creates a file or open an existing one for
+ * writing only. The file pointer will be placed
+ * at the beginning of the file.*/
+ case 'c':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_OPEN_CREATE
+ | RTFILE_O_WRITE;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ /* Opens an existing file for reading and places the
+ * file pointer at the beginning of the file. If the
+ * file does not exist an error will be returned. */
+ case 'r':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_OPEN
+ | RTFILE_O_READ;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ case 't': /* Text mode. */
+ /* Just skip as being valid. */
+ fSkip = true;
+ break;
+
+ /* Creates a new file or replaces an existing one
+ * for writing. Places the file pointer at the beginning.
+ * An existing file will be truncated to 0 bytes. */
+ case 'w':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_CREATE_REPLACE
+ | RTFILE_O_WRITE
+ | RTFILE_O_TRUNCATE;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ /* Creates a new file and opens it for writing. Places
+ * the file pointer at the beginning. If the file
+ * exists an error will be returned. */
+ case 'x':
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ fMode |= RTFILE_O_CREATE
+ | RTFILE_O_WRITE;
+ else
+ return VERR_INVALID_PARAMETER;
+ break;
+
+ case '+':
+ {
+ switch (chPrev)
+ {
+ case 'a':
+ case 'c':
+ case 'w':
+ case 'x':
+ /* Also open / create file with read access. */
+ fMode |= RTFILE_O_READ;
+ break;
+
+ case 'r':
+ /* Also open / create file with write access. */
+ fMode |= RTFILE_O_WRITE;
+ break;
+
+ case 'b':
+ case 't':
+ /* Silently eat skipped parameters. */
+ fSkip = true;
+ break;
+
+ case 0: /* No previous character yet. */
+ case '+':
+ /* Eat plusses which don't belong to a command. */
+ fSkip = true;
+ break;
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ break;
+ }
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (!fSkip)
+ chPrev = *pszCur;
+ pszCur++;
+ }
+
+ /* No action mask set? */
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ return VERR_INVALID_PARAMETER;
+
+ /** @todo Handle sharing mode */
+ fMode |= RTFILE_O_DENY_NONE;
+
+ /* Return. */
+ *pfMode = fMode;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTFileModeToFlags);
+
+
+RTR3DECL(int) RTFileModeToFlagsEx(const char *pszAccess, const char *pszDisposition,
+ const char *pszSharing, uint64_t *pfMode)
+{
+ AssertPtrReturn(pszAccess, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDisposition, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pszSharing, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfMode, VERR_INVALID_POINTER);
+
+ const char *pszCur = pszAccess;
+ if (*pszCur == '\0')
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * Handle access mode.
+ */
+ uint64_t fMode = 0;
+ char chPrev = '\0';
+ while ( pszCur
+ && *pszCur != '\0')
+ {
+ bool fSkip = false;
+ switch (*pszCur)
+ {
+ case 'b': /* Binary mode. */
+ /* Just skip as being valid. */
+ fSkip = true;
+ break;
+
+ case 'r': /* Read. */
+ fMode |= RTFILE_O_READ;
+ break;
+
+ case 't': /* Text mode. */
+ /* Just skip as being valid. */
+ fSkip = true;
+ break;
+
+ case 'w': /* Write. */
+ fMode |= RTFILE_O_WRITE;
+ break;
+
+ case 'a': /* Append. */
+ fMode |= RTFILE_O_WRITE | RTFILE_O_APPEND;
+ break;
+
+ case '+':
+ {
+ switch (chPrev)
+ {
+ case 'w':
+ case 'a':
+ /* Also use read access in write mode. */
+ fMode |= RTFILE_O_READ;
+ break;
+
+ case 'r':
+ /* Also use write access in read mode. */
+ fMode |= RTFILE_O_WRITE;
+ break;
+
+ case 'b':
+ case 't':
+ /* Silently eat skipped parameters. */
+ fSkip = true;
+ break;
+
+ case 0: /* No previous character yet. */
+ case '+':
+ /* Eat plusses which don't belong to a command. */
+ fSkip = true;
+ break;
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ break;
+ }
+
+ default:
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (!fSkip)
+ chPrev = *pszCur;
+ pszCur++;
+ }
+
+ /*
+ * Handle disposition.
+ */
+ pszCur = pszDisposition;
+
+ /* Create a new file, always, overwrite an existing file. */
+ if ( !RTStrCmp(pszCur, "ca")
+ || !RTStrCmp(pszCur, "create-replace"))
+ fMode |= RTFILE_O_CREATE_REPLACE;
+ /* Create a new file if it does not exist, fail if exist. */
+ else if ( !RTStrCmp(pszCur, "ce")
+ || !RTStrCmp(pszCur, "create"))
+ fMode |= RTFILE_O_CREATE;
+ /* Open existing file, create file if does not exist. */
+ else if ( !RTStrCmp(pszCur, "oc")
+ || !RTStrCmp(pszCur, "open-create"))
+ fMode |= RTFILE_O_OPEN_CREATE;
+ /* Open existing file and place the file pointer at the end of the file, if
+ * opened with write access. Create the file if does not exist.
+ * Note! This mode is ill conceived as the appending is a accesss mode not open disposition. */
+ else if ( !RTStrCmp(pszCur, "oa")
+ || !RTStrCmp(pszCur, "open-append"))
+ fMode |= RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND;
+ /* Open existing, fail if does not exist. */
+ else if ( !RTStrCmp(pszCur, "oe")
+ || !RTStrCmp(pszCur, "open"))
+ fMode |= RTFILE_O_OPEN;
+ /* Open and truncate existing, fail of not exist. */
+ else if ( !RTStrCmp(pszCur, "ot")
+ || !RTStrCmp(pszCur, "open-truncate"))
+ fMode |= RTFILE_O_OPEN | RTFILE_O_TRUNCATE;
+ else
+ return VERR_INVALID_PARAMETER;
+
+ /* No action mask set? */
+ if ((fMode & RTFILE_O_ACTION_MASK) == 0)
+ return VERR_INVALID_PARAMETER;
+
+ /*
+ * Sharing mode.
+ */
+ if (!pszSharing || !*pszSharing)
+ fMode |= RTFILE_O_DENY_NONE;
+ else
+ {
+ do
+ {
+ if (pszSharing[0] == 'n')
+ {
+ if (pszSharing[1] == 'r') /* nr (no other readers) */
+ {
+ if (pszSharing[2] == 'w') /* nrw (no other readers or writers) */
+ {
+ fMode |= RTFILE_O_DENY_READWRITE;
+ pszSharing += 3;
+ }
+ else
+ {
+ fMode |= RTFILE_O_DENY_READ;
+ pszSharing += 2;
+ }
+ }
+ else if (pszSharing[1] == 'w') /* nw (no other writers) */
+ {
+ fMode |= RTFILE_O_DENY_WRITE;
+ pszSharing += 2;
+ }
+ else
+ return VERR_INVALID_PARAMETER;
+ }
+ else if (pszSharing[0] == 'd') /* d (don't deny delete) */
+ {
+ fMode |= RTFILE_O_DENY_WRITE;
+ pszSharing++;
+ }
+ else
+ return VERR_INVALID_PARAMETER;
+ } while (*pszSharing != '\0');
+ }
+
+ /* Return. */
+ *pfMode = fMode;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTFileModeToFlagsEx);
+
diff --git a/src/VBox/Runtime/common/misc/RTFileOpenF.cpp b/src/VBox/Runtime/common/misc/RTFileOpenF.cpp
new file mode 100644
index 00000000..b6e01f0c
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTFileOpenF.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTFileOpenF.cpp $ */
+/** @file
+ * IPRT - RTFileOpenF.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/file.h>
+#include "internal/iprt.h"
+
+
+RTR3DECL(int) RTFileOpenF(PRTFILE pFile, uint64_t fOpen, const char *pszFilenameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszFilenameFmt);
+ int rc = RTFileOpenV(pFile, fOpen, pszFilenameFmt, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTFileOpenF);
+
diff --git a/src/VBox/Runtime/common/misc/RTFileOpenV.cpp b/src/VBox/Runtime/common/misc/RTFileOpenV.cpp
new file mode 100644
index 00000000..f54f31d1
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTFileOpenV.cpp
@@ -0,0 +1,58 @@
+/* $Id: RTFileOpenV.cpp $ */
+/** @file
+ * IPRT - RTFileOpenV.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/file.h>
+#include "internal/iprt.h"
+
+#include <iprt/err.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+
+
+RTR3DECL(int) RTFileOpenV(PRTFILE pFile, uint64_t fOpen, const char *pszFilenameFmt, va_list va)
+{
+ char szFilename[RTPATH_MAX];
+ size_t cchFilename = RTStrPrintfV(szFilename, sizeof(szFilename), pszFilenameFmt, va);
+ if (cchFilename >= sizeof(szFilename) - 1)
+ return VERR_FILENAME_TOO_LONG;
+ return RTFileOpen(pFile, szFilename, fOpen);
+}
+RT_EXPORT_SYMBOL(RTFileOpenV);
+
diff --git a/src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp b/src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp
new file mode 100644
index 00000000..901ebd19
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTMemWipeThoroughly.cpp
@@ -0,0 +1,65 @@
+/* $Id: RTMemWipeThoroughly.cpp $ */
+/** @file
+ * IPRT - RTMemWipeThoroughly.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/mem.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/rand.h>
+#include <iprt/string.h>
+
+
+RTDECL(void) RTMemWipeThoroughly(void *pv, size_t cb, size_t cMinPasses) RT_NO_THROW_DEF
+{
+ size_t cPasses = RT_MIN(cMinPasses, 6);
+
+ do
+ {
+ memset(pv, 0xff, cb);
+ ASMMemoryFence();
+
+ memset(pv, 0x00, cb);
+ ASMMemoryFence();
+
+ RTRandBytes(pv, cb);
+ ASMMemoryFence();
+ } while (cPasses-- > 0);
+}
+
diff --git a/src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp b/src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp
new file mode 100644
index 00000000..95ef7e8e
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTSystemFirmwareTypeName.cpp
@@ -0,0 +1,60 @@
+/* $Id: RTSystemFirmwareTypeName.cpp $ */
+/** @file
+ * IPRT - RTSystemFirmwareTypeName.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/system.h>
+
+
+RTDECL(const char *) RTSystemFirmwareTypeName(RTSYSFWTYPE enmType)
+{
+ switch (enmType)
+ {
+ case RTSYSFWTYPE_INVALID: return "Invalid";
+ case RTSYSFWTYPE_UNKNOWN: return "Unknown";
+ case RTSYSFWTYPE_BIOS: return "BIOS";
+ case RTSYSFWTYPE_UEFI: return "UEFI";
+ case RTSYSFWTYPE_END:
+ case RTSYSFWTYPE_32_BIT_HACK:
+ break;
+ }
+ return "bad-firmware-type";
+}
+RT_EXPORT_SYMBOL(RTSystemFirmwareTypeName);
+
diff --git a/src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp b/src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp
new file mode 100644
index 00000000..3e12048d
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/RTSystemIsInsideVM-amd64-x86.cpp
@@ -0,0 +1,58 @@
+/* $Id: RTSystemIsInsideVM-amd64-x86.cpp $ */
+/** @file
+ * IPRT -
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/system.h>
+
+#include <iprt/asm-amd64-x86.h>
+#include <iprt/x86.h>
+
+
+RTDECL(bool) RTSystemIsInsideVM(void)
+{
+ if (ASMHasCpuId())
+ {
+ if (ASMCpuId_ECX(1) & X86_CPUID_FEATURE_ECX_HVP)
+ return true;
+ }
+ return false;
+}
+RT_EXPORT_SYMBOL(RTSystemIsInsideVM);
+
diff --git a/src/VBox/Runtime/common/misc/assert.cpp b/src/VBox/Runtime/common/misc/assert.cpp
new file mode 100644
index 00000000..e09d9b47
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/assert.cpp
@@ -0,0 +1,348 @@
+/* $Id: assert.cpp $ */
+/** @file
+ * IPRT - Assertions, common code.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/assert.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#ifdef IPRT_WITH_ASSERT_STACK
+# ifndef IN_RING3
+# error "IPRT_WITH_ASSERT_STACK is only for ring-3 at present."
+# endif
+# include <iprt/dbg.h>
+#endif
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#include <iprt/string.h>
+#include <iprt/stdarg.h>
+#ifdef IN_RING3
+# include <iprt/env.h>
+# ifndef IPRT_NO_CRT
+# include <stdio.h>
+# endif
+# ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+# include "../../r3/win/internal-r3-win.h"
+# endif
+#endif
+#include "internal/assert.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The last assertion message, 1st part. */
+RTDATADECL(char) g_szRTAssertMsg1[1024];
+RT_EXPORT_SYMBOL(g_szRTAssertMsg1);
+/** The last assertion message, 2nd part. */
+RTDATADECL(char) g_szRTAssertMsg2[4096];
+RT_EXPORT_SYMBOL(g_szRTAssertMsg2);
+#ifdef IPRT_WITH_ASSERT_STACK
+/** The last assertion message, stack part. */
+RTDATADECL(char) g_szRTAssertStack[4096];
+RT_EXPORT_SYMBOL(g_szRTAssertStack);
+#endif
+/** The length of the g_szRTAssertMsg2 content.
+ * @remarks Race. */
+static uint32_t volatile g_cchRTAssertMsg2;
+/** The last assertion message, expression. */
+RTDATADECL(const char * volatile) g_pszRTAssertExpr;
+RT_EXPORT_SYMBOL(g_pszRTAssertExpr);
+/** The last assertion message, function name. */
+RTDATADECL(const char * volatile) g_pszRTAssertFunction;
+RT_EXPORT_SYMBOL(g_pszRTAssertFunction);
+/** The last assertion message, file name. */
+RTDATADECL(const char * volatile) g_pszRTAssertFile;
+RT_EXPORT_SYMBOL(g_pszRTAssertFile);
+/** The last assertion message, line number. */
+RTDATADECL(uint32_t volatile) g_u32RTAssertLine;
+RT_EXPORT_SYMBOL(g_u32RTAssertLine);
+
+
+/** Set if assertions are quiet. */
+static bool volatile g_fQuiet = false;
+/** Set if assertions may panic. */
+static bool volatile g_fMayPanic = true;
+
+
+RTDECL(bool) RTAssertSetQuiet(bool fQuiet)
+{
+ return ASMAtomicXchgBool(&g_fQuiet, fQuiet);
+}
+RT_EXPORT_SYMBOL(RTAssertSetQuiet);
+
+
+RTDECL(bool) RTAssertAreQuiet(void)
+{
+ return ASMAtomicUoReadBool(&g_fQuiet);
+}
+RT_EXPORT_SYMBOL(RTAssertAreQuiet);
+
+
+RTDECL(bool) RTAssertSetMayPanic(bool fMayPanic)
+{
+ return ASMAtomicXchgBool(&g_fMayPanic, fMayPanic);
+}
+RT_EXPORT_SYMBOL(RTAssertSetMayPanic);
+
+
+RTDECL(bool) RTAssertMayPanic(void)
+{
+ return ASMAtomicUoReadBool(&g_fMayPanic);
+}
+RT_EXPORT_SYMBOL(RTAssertMayPanic);
+
+
+RTDECL(void) RTAssertMsg1(const char *pszExpr, unsigned uLine, const char *pszFile, const char *pszFunction)
+{
+ /*
+ * Fill in the globals.
+ */
+ ASMAtomicUoWritePtr(&g_pszRTAssertExpr, pszExpr);
+ ASMAtomicUoWritePtr(&g_pszRTAssertFile, pszFile);
+ ASMAtomicUoWritePtr(&g_pszRTAssertFunction, pszFunction);
+ ASMAtomicUoWriteU32(&g_u32RTAssertLine, uLine);
+ RTStrPrintf(g_szRTAssertMsg1, sizeof(g_szRTAssertMsg1),
+ "\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n",
+ pszExpr, pszFile, uLine, pszFunction);
+
+ /*
+ * If not quiet, make noise.
+ */
+ if (!RTAssertAreQuiet())
+ {
+ RTERRVARS SavedErrVars;
+ RTErrVarsSave(&SavedErrVars);
+
+#ifdef IPRT_WITH_ASSERT_STACK
+ /* The stack dump. */
+ static volatile bool s_fDumpingStackAlready = false; /* for simple recursion prevention */
+ char szStack[sizeof(g_szRTAssertStack)];
+ size_t cchStack = 0;
+# if defined(IN_RING3) && defined(RT_OS_WINDOWS) /** @todo make this stack on/off thing more modular. */
+ bool fStack = (!g_pfnIsDebuggerPresent || !g_pfnIsDebuggerPresent()) && !RTEnvExist("IPRT_ASSERT_NO_STACK");
+# elif defined(IN_RING3)
+ bool fStack = !RTEnvExist("IPRT_ASSERT_NO_STACK");
+# else
+ bool fStack = true;
+# endif
+ szStack[0] = '\0';
+ if (fStack && !s_fDumpingStackAlready)
+ {
+ s_fDumpingStackAlready = true;
+ cchStack = RTDbgStackDumpSelf(szStack, sizeof(szStack), 0);
+ s_fDumpingStackAlready = false;
+ }
+ memcpy(g_szRTAssertStack, szStack, cchStack + 1);
+#endif
+
+#ifdef IN_RING0
+# ifdef IN_GUEST_R0
+ RTLogBackdoorPrintf("\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n",
+ pszExpr, pszFile, uLine, pszFunction);
+# endif
+ /** @todo fully integrate this with the logger... play safe a bit for now. */
+ rtR0AssertNativeMsg1(pszExpr, uLine, pszFile, pszFunction);
+
+#else /* !IN_RING0 */
+
+
+# if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT)) /* ugly */
+ if (g_pfnRTLogAssert)
+ g_pfnRTLogAssert(
+# else
+ RTLogAssert(
+# endif
+ "\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n"
+# ifdef IPRT_WITH_ASSERT_STACK
+ "Stack :\n%s\n"
+# endif
+ , pszExpr, pszFile, uLine, pszFunction
+# ifdef IPRT_WITH_ASSERT_STACK
+ , szStack
+# endif
+ );
+
+# ifdef IN_RING3
+ /* print to stderr, helps user and gdb debugging. */
+# ifndef IPRT_NO_CRT
+ fprintf(stderr,
+ "\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n",
+ RT_VALID_PTR(pszExpr) ? pszExpr : "<none>",
+ RT_VALID_PTR(pszFile) ? pszFile : "<none>",
+ uLine,
+ RT_VALID_PTR(pszFunction) ? pszFunction : "");
+# ifdef IPRT_WITH_ASSERT_STACK
+ fprintf(stderr, "Stack :\n%s\n", szStack);
+# endif
+ fflush(stderr);
+# else
+ char szMsg[2048];
+ size_t cchMsg = RTStrPrintf(szMsg, sizeof(szMsg),
+ "\n!!Assertion Failed!!\n"
+ "Expression: %s\n"
+ "Location : %s(%d) %s\n",
+ RT_VALID_PTR(pszExpr) ? pszExpr : "<none>",
+ RT_VALID_PTR(pszFile) ? pszFile : "<none>",
+ uLine,
+ RT_VALID_PTR(pszFunction) ? pszFunction : "");
+ RTLogWriteStdErr(szMsg, cchMsg);
+# ifdef IPRT_WITH_ASSERT_STACK
+ RTLogWriteStdErr(RT_STR_TUPLE("Stack :\n"));
+ RTLogWriteStdErr(szStack, strlen(szStack));
+ RTLogWriteStdErr(RT_STR_TUPLE("\n"));
+# endif
+# endif
+# endif
+#endif /* !IN_RING0 */
+
+ RTErrVarsRestore(&SavedErrVars);
+ }
+}
+RT_EXPORT_SYMBOL(RTAssertMsg1);
+
+
+/**
+ * Worker for RTAssertMsg2V and RTAssertMsg2AddV
+ *
+ * @param fInitial True if it's RTAssertMsg2V, otherwise false.
+ * @param pszFormat The message format string.
+ * @param va The format arguments.
+ */
+static void rtAssertMsg2Worker(bool fInitial, const char *pszFormat, va_list va)
+{
+ va_list vaCopy;
+ size_t cch;
+
+ /*
+ * The global first.
+ */
+ if (fInitial)
+ {
+ va_copy(vaCopy, va);
+ cch = RTStrPrintfV(g_szRTAssertMsg2, sizeof(g_szRTAssertMsg2), pszFormat, vaCopy);
+ ASMAtomicWriteU32(&g_cchRTAssertMsg2, (uint32_t)cch);
+ va_end(vaCopy);
+ }
+ else
+ {
+ cch = ASMAtomicReadU32(&g_cchRTAssertMsg2);
+ if (cch < sizeof(g_szRTAssertMsg2) - 4)
+ {
+ va_copy(vaCopy, va);
+ cch += RTStrPrintfV(&g_szRTAssertMsg2[cch], sizeof(g_szRTAssertMsg2) - cch, pszFormat, vaCopy);
+ ASMAtomicWriteU32(&g_cchRTAssertMsg2, (uint32_t)cch);
+ va_end(vaCopy);
+ }
+ }
+
+ /*
+ * If not quiet, make some noise.
+ */
+ if (!RTAssertAreQuiet())
+ {
+ RTERRVARS SavedErrVars;
+ RTErrVarsSave(&SavedErrVars);
+
+#ifdef IN_RING0
+# ifdef IN_GUEST_R0
+ va_copy(vaCopy, va);
+ RTLogBackdoorPrintfV(pszFormat, vaCopy);
+ va_end(vaCopy);
+# endif
+ /** @todo fully integrate this with the logger... play safe a bit for now. */
+ rtR0AssertNativeMsg2V(fInitial, pszFormat, va);
+
+#else /* !IN_RING0 */
+
+# if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+ if (g_pfnRTLogAssert)
+# endif
+ {
+ va_copy(vaCopy, va);
+# if defined(IN_RING3) && (defined(IN_RT_STATIC) || defined(IPRT_NO_CRT))
+ g_pfnRTLogAssertV(pszFormat, vaCopy);
+# else
+ RTLogAssertV(pszFormat, vaCopy);
+# endif
+ va_end(vaCopy);
+ }
+
+# ifdef IN_RING3
+ /* print to stderr, helps user and gdb debugging. */
+ char szMsg[sizeof(g_szRTAssertMsg2)];
+ va_copy(vaCopy, va);
+ size_t cchMsg = RTStrPrintfV(szMsg, sizeof(szMsg), pszFormat, vaCopy);
+ va_end(vaCopy);
+# ifndef IPRT_NO_CRT
+ fwrite(szMsg, 1, cchMsg, stderr);
+ fflush(stderr);
+# else
+ RTLogWriteStdErr(szMsg, cchMsg);
+# endif
+# endif
+#endif /* !IN_RING0 */
+
+ RTErrVarsRestore(&SavedErrVars);
+ }
+}
+
+
+RTDECL(void) RTAssertMsg2V(const char *pszFormat, va_list va)
+{
+ rtAssertMsg2Worker(true /*fInitial*/, pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2V);
+
+
+RTDECL(void) RTAssertMsg2AddV(const char *pszFormat, va_list va)
+{
+ rtAssertMsg2Worker(false /*fInitial*/, pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTAssertMsg2AddV);
+
diff --git a/src/VBox/Runtime/common/misc/buildconfig.cpp b/src/VBox/Runtime/common/misc/buildconfig.cpp
new file mode 100644
index 00000000..fa7abf00
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/buildconfig.cpp
@@ -0,0 +1,152 @@
+/* $Id: buildconfig.cpp $ */
+/** @file
+ * IPRT - Build Configuration Information.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/buildconfig.h>
+
+
+
+#ifdef IPRT_BLDCFG_SCM_REV
+RTDECL(uint32_t) RTBldCfgRevision(void)
+{
+ return IPRT_BLDCFG_SCM_REV;
+}
+
+
+RTDECL(const char *) RTBldCfgRevisionStr(void)
+{
+ return RT_XSTR(IPRT_BLDCFG_SCM_REV);
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_VERSION_STRING
+RTDECL(const char *) RTBldCfgVersion(void)
+{
+ return IPRT_BLDCFG_VERSION_STRING;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_VERSION_MAJOR
+RTDECL(uint32_t) RTBldCfgVersionMajor(void)
+{
+ return IPRT_BLDCFG_VERSION_MAJOR;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_VERSION_MINOR
+RTDECL(uint32_t) RTBldCfgVersionMinor(void)
+{
+ return IPRT_BLDCFG_VERSION_MINOR;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_VERSION_BUILD
+RTDECL(uint32_t) RTBldCfgVersionBuild(void)
+{
+ return IPRT_BLDCFG_VERSION_BUILD;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_TARGET
+RTDECL(const char *) RTBldCfgTarget(void)
+{
+ return IPRT_BLDCFG_TARGET;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_TARGET_ARCH
+RTDECL(const char *) RTBldCfgTargetArch(void)
+{
+ return IPRT_BLDCFG_TARGET_ARCH;
+}
+#endif
+
+
+#if defined(IPRT_BLDCFG_TARGET) && defined(IPRT_BLDCFG_TARGET_ARCH)
+RTDECL(const char *) RTBldCfgTargetDotArch(void)
+{
+ return IPRT_BLDCFG_TARGET "." IPRT_BLDCFG_TARGET_ARCH;
+}
+#endif
+
+
+#ifdef IPRT_BLDCFG_TYPE
+RTDECL(const char *) RTBldCfgType(void)
+{
+ return IPRT_BLDCFG_TYPE;
+}
+#endif
+
+
+RTDECL(const char *) RTBldCfgCompiler(void)
+{
+#ifdef IPRT_BLDCFG_COMPILER
+ return IPRT_BLDCFG_COMPILER;
+#elif defined(__INTEL_COMPILER)
+ return "intel";
+#elif defined(__GNUC__)
+ return "gcc";
+#elif defined(__llvm__)
+ return "llvm";
+#elif defined(__SUNPRO_CC) || defined(__SUNPRO_C)
+ return "sunpro";
+#elif defined(__IBMCPP__) || defined(__IBMC__)
+# if defined(__COMPILER_VER__)
+ return "ibmzosc";
+# elif defined(__xlC__) || defined(__xlc__)
+ return "ibmxlc";
+# else
+ return "vac";
+# endif
+#elif defined(_MSC_VER)
+ return "vcc";
+#elif defined(__WATCOMC__)
+ return "watcom";
+#else
+# error "Unknown compiler"
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/misc/cidr.cpp b/src/VBox/Runtime/common/misc/cidr.cpp
new file mode 100644
index 00000000..63ac220b
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/cidr.cpp
@@ -0,0 +1,129 @@
+/* $Id: cidr.cpp $ */
+/** @file
+ * IPRT - IPv4 address parsing.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cidr.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+
+
+RTDECL(int) RTCidrStrToIPv4(const char *pszAddress, PRTNETADDRIPV4 pNetwork, PRTNETADDRIPV4 pNetmask)
+{
+ uint8_t cBits;
+ uint8_t addr[4];
+ uint32_t u32Netmask;
+ uint32_t u32Network;
+ const char *psz = pszAddress;
+ const char *pszNetmask;
+ char *pszNext;
+ int rc = VINF_SUCCESS;
+ int cDelimiter = 0;
+ int cDelimiterLimit = 0;
+
+ AssertPtrReturn(pszAddress, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pNetwork, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pNetmask, VERR_INVALID_PARAMETER);
+
+ pszNetmask = RTStrStr(psz, "/");
+ *(uint32_t *)addr = 0;
+ if (!pszNetmask)
+ cBits = 32;
+ else
+ {
+ rc = RTStrToUInt8Ex(pszNetmask + 1, &pszNext, 10, &cBits);
+ if ( RT_FAILURE(rc)
+ || cBits > 32
+ || rc != VINF_SUCCESS) /* No trailing symbols are acceptable after the digit */
+ return VERR_INVALID_PARAMETER;
+ }
+ u32Netmask = ~(uint32_t)((1<< (32 - cBits)) - 1);
+
+ if (cBits <= 8)
+ cDelimiterLimit = 0;
+ else if (cBits <= 16)
+ cDelimiterLimit = 1;
+ else if (cBits <= 24)
+ cDelimiterLimit = 2;
+ else if (cBits <= 32)
+ cDelimiterLimit = 3;
+
+ for (;;)
+ {
+ rc = RTStrToUInt8Ex(psz, &pszNext, 10, &addr[cDelimiter]);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG)
+ return VERR_INVALID_PARAMETER;
+
+ if (*pszNext == '.')
+ cDelimiter++;
+ else if ( cDelimiter >= cDelimiterLimit
+ && ( *pszNext == '\0'
+ || *pszNext == '/'))
+ break;
+ else
+ return VERR_INVALID_PARAMETER;
+
+ if (cDelimiter > 3)
+ /* not more than four octets */
+ return VERR_INVALID_PARAMETER;
+
+ psz = pszNext + 1;
+ }
+ u32Network = RT_MAKE_U32_FROM_U8(addr[3], addr[2], addr[1], addr[0]);
+
+ /* Corner case: see RFC 790 page 2 and RFC 4632 page 6. */
+ if ( addr[0] == 0
+ && ( *(uint32_t *)addr != 0
+ || u32Netmask == (uint32_t)~0))
+ return VERR_INVALID_PARAMETER;
+
+ if ((u32Network & ~u32Netmask) != 0)
+ return VERR_INVALID_PARAMETER;
+
+ pNetmask->u = u32Netmask;
+ pNetwork->u = u32Network;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTCidrStrToIPv4);
+
diff --git a/src/VBox/Runtime/common/misc/circbuf.cpp b/src/VBox/Runtime/common/misc/circbuf.cpp
new file mode 100644
index 00000000..72c95690
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/circbuf.cpp
@@ -0,0 +1,262 @@
+/* $Id: circbuf.cpp $ */
+/** @file
+ * IPRT - Lock Free Circular Buffer
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/circbuf.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** @todo r=bird: this is missing docs and magic. */
+typedef struct RTCIRCBUF
+{
+ /** The current read position in the buffer. */
+ size_t offRead;
+ /** Is a read block acquired currently? */
+ bool fReading;
+ /** Is a write block acquired currently? */
+ bool fWriting;
+ /** The current write position in the buffer. */
+ size_t offWrite;
+ /** How much space of the buffer is currently in use. */
+ volatile size_t cbUsed;
+ /** How big is the buffer. */
+ size_t cbBuf;
+ /** The buffer itself. */
+ void *pvBuf;
+} RTCIRCBUF, *PRTCIRCBUF;
+
+
+RTDECL(int) RTCircBufCreate(PRTCIRCBUF *ppBuf, size_t cbSize)
+{
+ /* Validate input. */
+ AssertPtrReturn(ppBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbSize > 0, VERR_INVALID_PARAMETER);
+
+ PRTCIRCBUF pTmpBuf;
+ pTmpBuf = (PRTCIRCBUF)RTMemAllocZ(sizeof(RTCIRCBUF));
+ if (!pTmpBuf)
+ return VERR_NO_MEMORY;
+
+ pTmpBuf->pvBuf = RTMemAlloc(cbSize);
+ if (pTmpBuf->pvBuf)
+ {
+ pTmpBuf->cbBuf = cbSize;
+ *ppBuf = pTmpBuf;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pTmpBuf);
+ return VERR_NO_MEMORY;
+}
+
+
+RTDECL(void) RTCircBufDestroy(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ if (!pBuf)
+ return;
+ AssertPtr(pBuf);
+ RTMemFree(pBuf->pvBuf);
+ RTMemFree(pBuf);
+}
+
+
+RTDECL(void) RTCircBufReset(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+
+ pBuf->offRead = 0;
+ pBuf->offWrite = 0;
+ pBuf->cbUsed = 0;
+ pBuf->fReading = false;
+ pBuf->fWriting = false;
+}
+
+
+RTDECL(size_t) RTCircBufFree(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return pBuf->cbBuf - ASMAtomicReadZ(&pBuf->cbUsed);
+}
+
+
+RTDECL(size_t) RTCircBufUsed(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadZ(&pBuf->cbUsed);
+}
+
+RTDECL(size_t) RTCircBufSize(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return pBuf->cbBuf;
+}
+
+RTDECL(bool) RTCircBufIsReading(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadBool(&pBuf->fReading);
+}
+
+RTDECL(bool) RTCircBufIsWriting(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadBool(&pBuf->fWriting);
+}
+
+RTDECL(size_t) RTCircBufOffsetRead(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadZ(&pBuf->offRead);
+}
+
+RTDECL(size_t) RTCircBufOffsetWrite(PRTCIRCBUF pBuf)
+{
+ /* Validate input. */
+ AssertPtrReturn(pBuf, 0);
+
+ return ASMAtomicReadZ(&pBuf->offWrite);
+}
+
+RTDECL(void) RTCircBufAcquireReadBlock(PRTCIRCBUF pBuf, size_t cbReqSize, void **ppvStart, size_t *pcbSize)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+ Assert(cbReqSize > 0);
+ AssertPtr(ppvStart);
+ AssertPtr(pcbSize);
+
+ *ppvStart = 0;
+ *pcbSize = 0;
+
+ /* How much is in use? */
+ size_t cbUsed = ASMAtomicReadZ(&pBuf->cbUsed);
+ if (cbUsed > 0)
+ {
+ /* Get the size out of the requested size, the read block till the end
+ * of the buffer & the currently used size. */
+ size_t cbSize = RT_MIN(cbReqSize, RT_MIN(pBuf->cbBuf - pBuf->offRead, cbUsed));
+ if (cbSize > 0)
+ {
+ /* Return the pointer address which point to the current read
+ * position. */
+ *ppvStart = (char *)pBuf->pvBuf + pBuf->offRead;
+ *pcbSize = cbSize;
+
+ ASMAtomicWriteBool(&pBuf->fReading, true);
+ }
+ }
+}
+
+
+RTDECL(void) RTCircBufReleaseReadBlock(PRTCIRCBUF pBuf, size_t cbSize)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+
+ /* Split at the end of the buffer. */
+ pBuf->offRead = (pBuf->offRead + cbSize) % pBuf->cbBuf;
+
+ ASMAtomicSubZ(&pBuf->cbUsed, cbSize);
+ ASMAtomicWriteBool(&pBuf->fReading, false);
+}
+
+
+RTDECL(void) RTCircBufAcquireWriteBlock(PRTCIRCBUF pBuf, size_t cbReqSize, void **ppvStart, size_t *pcbSize)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+ Assert(cbReqSize > 0);
+ AssertPtr(ppvStart);
+ AssertPtr(pcbSize);
+
+ *ppvStart = 0;
+ *pcbSize = 0;
+
+ /* How much is free? */
+ size_t cbFree = pBuf->cbBuf - ASMAtomicReadZ(&pBuf->cbUsed);
+ if (cbFree > 0)
+ {
+ /* Get the size out of the requested size, then write block till the end
+ * of the buffer & the currently free size. */
+ size_t cbSize = RT_MIN(cbReqSize, RT_MIN(pBuf->cbBuf - pBuf->offWrite, cbFree));
+ if (cbSize > 0)
+ {
+ /* Return the pointer address which point to the current write
+ * position. */
+ *ppvStart = (char*)pBuf->pvBuf + pBuf->offWrite;
+ *pcbSize = cbSize;
+
+ ASMAtomicWriteBool(&pBuf->fWriting, true);
+ }
+ }
+}
+
+
+RTDECL(void) RTCircBufReleaseWriteBlock(PRTCIRCBUF pBuf, size_t cbSize)
+{
+ /* Validate input. */
+ AssertPtr(pBuf);
+
+ /* Split at the end of the buffer. */
+ pBuf->offWrite = (pBuf->offWrite + cbSize) % pBuf->cbBuf;
+
+ ASMAtomicAddZ(&pBuf->cbUsed, cbSize);
+ ASMAtomicWriteBool(&pBuf->fWriting, false);
+}
+
diff --git a/src/VBox/Runtime/common/misc/expreval.cpp b/src/VBox/Runtime/common/misc/expreval.cpp
new file mode 100644
index 00000000..1099db46
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/expreval.cpp
@@ -0,0 +1,2740 @@
+/* $Id: expreval.cpp $ */
+/** @file
+ * expreval - Expressions evaluator.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/expreval.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The max length of a string representation of a number. */
+#define EXPR_NUM_LEN ((sizeof("-9223372036854775802") + 4) & ~3)
+
+/** The max operator stack depth. */
+#define EXPR_MAX_OPERATORS 72
+/** The max operand depth. */
+#define EXPR_MAX_OPERANDS 128
+/** the max variable recursion. */
+#define EXPR_MAX_VAR_RECURSION 20
+
+/** Check if @a a_ch is a valid separator for a alphabetical binary
+ * operator, omitting isspace. */
+#define EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch) \
+ (RT_C_IS_PUNCT((a_ch)) && (a_ch) != '@' && (a_ch) != '_')
+
+/** Check if @a a_ch is a valid separator for a alphabetical binary operator. */
+#define EXPR_IS_OP_SEPARATOR(a_ch) \
+ (RT_C_IS_SPACE((a_ch)) || EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch))
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** The 64-bit signed integer type we're using. */
+typedef int64_t EXPRINT64;
+
+/** Pointer to a evaluator instance. */
+typedef struct EXPR *PEXPR;
+
+
+/**
+ * Operand variable type.
+ */
+typedef enum
+{
+ /** Invalid zero entry. */
+ kExprVar_Invalid = 0,
+ /** A number. */
+ kExprVar_Num,
+ /** A string in need of expanding (perhaps). */
+ kExprVar_String,
+ /** A simple string that doesn't need expanding. */
+ kExprVar_SimpleString,
+ /** A quoted string in need of expanding (perhaps). */
+ kExprVar_QuotedString,
+ /** A simple quoted string that doesn't need expanding. */
+ kExprVar_QuotedSimpleString,
+ /** The end of the valid variable types. */
+ kExprVar_End
+} EXPRVARTYPE;
+
+/**
+ * Operand variable.
+ */
+typedef struct
+{
+ /** The variable type. */
+ EXPRVARTYPE enmType;
+ /** The variable. */
+ union
+ {
+ /** Pointer to the string. */
+ char *psz;
+ /** The variable. */
+ EXPRINT64 i;
+ } uVal;
+} EXPRVAR;
+/** Pointer to a operand variable. */
+typedef EXPRVAR *PEXPRVAR;
+/** Pointer to a const operand variable. */
+typedef EXPRVAR const *PCEXPRVAR;
+
+/**
+ * Operator return statuses.
+ */
+typedef enum
+{
+ kExprRet_Error = -1,
+ kExprRet_Ok = 0,
+ kExprRet_Operator,
+ kExprRet_Operand,
+ kExprRet_EndOfExpr,
+ kExprRet_End
+} EXPRRET;
+
+/**
+ * Operator.
+ */
+typedef struct
+{
+ /** The operator. */
+ char szOp[11];
+ /** The length of the operator string. */
+ uint8_t cchOp;
+ /** The pair operator.
+ * This is used with '(' and '?'. */
+ char chPair;
+ /** The precedence. Higher means higher. */
+ char iPrecedence;
+ /** The number of arguments it takes. */
+ signed char cArgs;
+ /** Pointer to the method implementing the operator. */
+ EXPRRET (*pfn)(PEXPR pThis);
+} EXPROP;
+/** Pointer to a const operator. */
+typedef EXPROP const *PCEXPROP;
+
+
+/** Magic value for RTEXPREVALINT::u32Magic.
+ * @todo fixme */
+#define RTEXPREVAL_MAGIC UINT32_C(0x12345678)
+
+/**
+ * Expression evaluator instance.
+ */
+typedef struct RTEXPREVALINT
+{
+ /** Magic number (RTEXPREVAL_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** RTEXPREVAL_XXX. */
+ uint64_t fFlags;
+ /** Name for logging purposes (copy) */
+ char *pszName;
+ /** User argument to callbacks. */
+ void *pvUser;
+ /** Callback for getting variables or checking if they exists. */
+ PFNRTEXPREVALQUERYVARIABLE pfnQueryVariable;
+} RTEXPREVALINT;
+
+/**
+ * An expression being evaluated.
+ */
+typedef struct EXPR
+{
+ /** The full expression. */
+ const char *pszExpr;
+ /** The current location. */
+ const char *psz;
+ /** Error info keeper. */
+ PRTERRINFO pErrInfo;
+ /** Pointer to the instance we evaluating under. */
+ RTEXPREVALINT *pEvaluator;
+ /** Pending binary operator. */
+ PCEXPROP pPending;
+ /** Top of the operator stack. */
+ int iOp;
+ /** Top of the operand stack. */
+ int iVar;
+ /** The operator stack. */
+ PCEXPROP apOps[EXPR_MAX_OPERATORS];
+ /** The operand stack. */
+ EXPRVAR aVars[EXPR_MAX_OPERANDS];
+} EXPR;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Operator start character map.
+ * This indicates which characters that are starting operators and which aren't.
+ *
+ * Bit 0: Indicates that this char is used in operators.
+ * Bit 1: When bit 0 is clear, this indicates whitespace.
+ * When bit 1 is set, this indicates whether the operator can be used
+ * immediately next to an operand without any clear separation.
+ * Bits 2 thru 7: Index into g_aExprOps of the first operator starting with
+ * this character.
+ */
+static uint8_t g_abOpStartCharMap[256] = {0};
+/** Whether we've initialized the map. */
+static int g_fExprInitializedMap = 0;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void expr_unget_op(PEXPR pThis);
+static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis);
+
+
+
+
+/**
+ * Displays an error message.
+ *
+ * The total string length must not exceed 256 bytes.
+ *
+ * @returns kExprRet_Error
+ * @param pThis The evaluator instance.
+ * @param pszError The message format string.
+ * @param ... The message format args.
+ */
+static EXPRRET expr_error(PEXPR pThis, const char *pszError, ...)
+{
+ va_list va;
+ va_start(va, pszError);
+ RTErrInfoSetV(pThis->pErrInfo, VERR_PARSE_ERROR, pszError, va);
+ va_end(va);
+ return kExprRet_Error;
+}
+
+
+/**
+ * Converts a number to a string.
+ *
+ * @returns pszDst.
+ * @param pszDst The string buffer to write into. Assumes length of EXPR_NUM_LEN.
+ * @param iSrc The number to convert.
+ */
+static char *expr_num_to_string(char *pszDst, EXPRINT64 iSrc)
+{
+ char szTmp[64]; /* RTStrFormatNumber assumes this as a minimum size. */
+ AssertCompile(EXPR_NUM_LEN < sizeof(szTmp));
+ size_t cchTmp = RTStrFormatNumber(szTmp, iSrc, 10 /*uBase*/, 0 /*cchWidth*/, 0 /*cchPrecision*/,
+ RTSTR_F_64BIT | RTSTR_F_VALSIGNED);
+ return (char *)memcpy(pszDst, szTmp, cchTmp + 1);
+}
+
+
+/**
+ * Attempts to convert a (simple) string into a number.
+ *
+ * @returns status code.
+ * @param pThis The evaluator instance.
+ * @param piDst Where to store the numeric value on success.
+ * @param pszSrc The string to try convert.
+ * @param fQuiet Whether we should be quiet or grumpy on failure.
+ */
+static EXPRRET expr_string_to_num(PEXPR pThis, EXPRINT64 *piDst, const char *pszSrc, int fQuiet)
+{
+ EXPRRET rc = kExprRet_Ok;
+ char const *psz = pszSrc;
+ EXPRINT64 i;
+ unsigned uBase;
+ int fNegative;
+
+ /*
+ * Skip blanks.
+ */
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+ const char *const pszFirst = psz;
+
+ /*
+ * Check for '-'.
+ *
+ * At this point we will not need to deal with operators, this is
+ * just an indicator of negative numbers. If some operator ends up
+ * here it's because it came from a string expansion and thus shall
+ * not be interpreted. If this turns out to be an stupid restriction
+ * it can be fixed, but for now it stays like this.
+ */
+ fNegative = *psz == '-';
+ if (fNegative)
+ psz++;
+
+ /*
+ * Determin base.
+ * Recognize some exsotic prefixes here in addition to the two standard ones.
+ */
+ uint64_t const fFlags = pThis->pEvaluator->fFlags;
+ uBase = fFlags & RTEXPREVAL_F_DEFAULT_BASE_16 ? 16 : 10;
+ char const ch0 = psz[0];
+ if (ch0 == '0')
+ {
+ char const ch1 = psz[1];
+ switch (ch1)
+ {
+ case '\0':
+ break;
+
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': /* C-style octal */
+ if (fFlags & RTEXPREVAL_F_C_OCTAL)
+ {
+ uBase = 8;
+ psz++;
+ }
+ break;
+
+ case '8':
+ case '9':
+ break;
+
+ case 'x':
+ case 'X':
+ uBase = 16;
+ psz += 2;
+ break;
+ case 'y': case 'Y': /* windbg, VBoxDbg */
+ case 'b': case 'B': /* python and others */
+ uBase = 2;
+ psz += 2;
+ break;
+ case 'n': case 'N': /* windbg */
+ case 'i': case 'I': /* VBoxDbg */
+ uBase = 10;
+ psz += 2;
+ break;
+ case 't': case 'T': /* windbg, VBoxDbg */
+ case 'o': case 'O': /* python and others */
+ uBase = 8;
+ psz += 2;
+ break;
+ }
+ }
+
+ /*
+ * Convert digits.
+ */
+ i = 0;
+ for (;;)
+ {
+ unsigned iDigit;
+ int ch = *psz;
+ switch (ch)
+ {
+ case '0': iDigit = 0; break;
+ case '1': iDigit = 1; break;
+ case '2': iDigit = 2; break;
+ case '3': iDigit = 3; break;
+ case '4': iDigit = 4; break;
+ case '5': iDigit = 5; break;
+ case '6': iDigit = 6; break;
+ case '7': iDigit = 7; break;
+ case '8': iDigit = 8; break;
+ case '9': iDigit = 9; break;
+ case 'a':
+ case 'A': iDigit = 10; break;
+ case 'b':
+ case 'B': iDigit = 11; break;
+ case 'c':
+ case 'C': iDigit = 12; break;
+ case 'd':
+ case 'D': iDigit = 13; break;
+ case 'e':
+ case 'E': iDigit = 14; break;
+ case 'F': iDigit = 15; break;
+ case 'f':
+ /* Make 'false' -> 0: */
+ if ( psz != pszFirst
+ || strncmp(psz + 1, RT_STR_TUPLE("alse")) != 0)
+ {
+ iDigit = 15;
+ break;
+ }
+ psz += sizeof("false") - 1;
+ RT_FALL_THROUGH();
+
+ default:
+ /* Make 'true' evaluate to 1: */
+ if (psz == pszFirst && strncmp(psz, RT_STR_TUPLE("true")) == 0)
+ {
+ psz += sizeof("true") - 1;
+ i = 1;
+ }
+
+ /*
+ * Is the rest white space?
+ */
+ while (RT_C_IS_SPACE(*psz))
+ psz++;
+ if (*psz != '\0')
+ {
+ iDigit = uBase;
+ break;
+ }
+ RT_FALL_THROUGH();
+
+ case '\0':
+ if (fNegative)
+ i = -i;
+ *piDst = i;
+ return rc;
+ }
+ if (iDigit >= uBase)
+ {
+ if (fNegative)
+ i = -i;
+ *piDst = i;
+ if (!fQuiet)
+ expr_error(pThis, "Invalid %u-base number \"%.80s\"", uBase, pszSrc);
+ return kExprRet_Error;
+ }
+
+ /* add the digit and advance */
+ /** @todo check for overflow? */
+ i *= uBase;
+ i += iDigit;
+ psz++;
+ }
+ /* not reached */
+}
+
+
+/**
+ * Checks if the variable is a string or not.
+ *
+ * @returns 1 if it's a string, 0 otherwise.
+ * @param pVar The variable.
+ */
+static int expr_var_is_string(PCEXPRVAR pVar)
+{
+ return pVar->enmType >= kExprVar_String;
+}
+
+
+/**
+ * Checks if the variable contains a string that was quoted
+ * in the expression.
+ *
+ * @returns 1 if if was a quoted string, otherwise 0.
+ * @param pVar The variable.
+ */
+static int expr_var_was_quoted(PCEXPRVAR pVar)
+{
+ return pVar->enmType >= kExprVar_QuotedString;
+}
+
+
+/**
+ * Deletes a variable.
+ *
+ * @param pVar The variable.
+ */
+static void expr_var_delete(PEXPRVAR pVar)
+{
+ if (expr_var_is_string(pVar))
+ {
+ RTMemTmpFree(pVar->uVal.psz);
+ pVar->uVal.psz = NULL;
+ }
+ pVar->enmType = kExprVar_Invalid;
+}
+
+
+/**
+ * Initializes a new variables with a sub-string value.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pThis The evaluator expression instance.
+ * @param pVar The new variable.
+ * @param psz The start of the string value.
+ * @param cch The number of chars to copy.
+ * @param enmType The string type.
+ */
+static EXPRRET expr_var_init_substring(PEXPR pThis, PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType)
+{
+ /* convert string needing expanding into simple ones if possible. */
+ if ( enmType == kExprVar_String
+ && !memchr(psz, '$', cch))
+ enmType = kExprVar_SimpleString;
+ else if ( enmType == kExprVar_QuotedString
+ && !memchr(psz, '$', cch))
+ enmType = kExprVar_QuotedSimpleString;
+
+ pVar->enmType = enmType;
+ pVar->uVal.psz = (char *)RTMemTmpAlloc(cch + 1);
+ if (RT_LIKELY(pVar->uVal.psz))
+ {
+ memcpy(pVar->uVal.psz, psz, cch);
+ pVar->uVal.psz[cch] = '\0';
+ return kExprRet_Ok;
+ }
+ pVar->enmType = kExprVar_End;
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cch + 1);
+ return kExprRet_Error;
+}
+
+
+#if 0 /* unused */
+/**
+ * Initializes a new variables with a string value.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pVar The new variable.
+ * @param psz The string value.
+ * @param enmType The string type.
+ */
+static EXPRRET expr_var_init_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType)
+{
+ return expr_var_init_substring(pVar, psz, strlen(psz), enmType);
+}
+
+
+/**
+ * Assigns a sub-string value to a variable.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pVar The new variable.
+ * @param psz The start of the string value.
+ * @param cch The number of chars to copy.
+ * @param enmType The string type.
+ */
+static void expr_var_assign_substring(PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType)
+{
+ expr_var_delete(pVar);
+ return expr_var_init_substring(pVar, psz, cch, enmType);
+}
+
+
+/**
+ * Assignes a string value to a variable.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pVar The variable.
+ * @param psz The string value.
+ * @param enmType The string type.
+ */
+static void expr_var_assign_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType)
+{
+ expr_var_delete(pVar);
+ return expr_var_init_string(pVar, psz, enmType);
+}
+#endif /* unused */
+
+
+/**
+ * Finds the end of the current variable expansion, taking nested expansion
+ * into account.
+ *
+ * This is somewhat similar to the code down in expr_get_unary_or_operand.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pThis The evaluator expression instance.
+ * @param pchSrc Pointer to the dollar of the variable expansion.
+ * @param cchSrc The length of the variable expansion expression.
+ * @param pcchVarRef Where to return the length of the variable expansion.
+ * @param pfNested Where to return whether it's a nested (@c true) or plain
+ * one.
+ */
+static EXPRRET expr_expand_find_end(PEXPR pThis, const char *pchSrc, size_t cchSrc, size_t *pcchVarRef, bool *pfNested)
+{
+ const char * const pchStart = pchSrc;
+
+ /*
+ * Push the initial expression.
+ */
+ Assert(cchSrc >= 2);
+ Assert(pchSrc[0] == '$');
+ Assert(pchSrc[1] == '{');
+ unsigned cPars = 1;
+ pchSrc += 2;
+ cchSrc -= 2;
+
+ /*
+ * Parse the rest of the string till we've back at cPars == 0.
+ */
+ *pfNested = false;
+ while (cchSrc > 0)
+ {
+ char const ch = *pchSrc;
+ if ( ch == '$'
+ && cchSrc >= 2
+ && pchSrc[1] == '{')
+ {
+ if (cPars < EXPR_MAX_VAR_RECURSION)
+ cPars++;
+ else
+ {
+ *pcchVarRef = 0;
+ return expr_error(pThis, "Too deep nesting of variable expansions");
+ }
+ *pfNested = true;
+ pchSrc += 2;
+ cchSrc -= 2;
+ }
+ else
+ {
+ pchSrc += 1;
+ cchSrc -= 1;
+ if (ch == '}')
+ if (--cPars == 0)
+ {
+ *pcchVarRef = pchSrc - pchStart;
+ return kExprRet_Ok;
+ }
+ }
+ }
+ *pcchVarRef = 0;
+ return expr_error(pThis, "Unbalanced variable expansions: %.*s", pchStart, pchSrc - pchStart);
+}
+
+
+/**
+ * Returns the given string with all variables references replaced.
+ *
+ * @returns Pointer to expanded string on success (RTMemTmpFree), NULL on
+ * failure (error already set).
+ * @param pThis The evaluator expression instance.
+ * @param pchSrc The string to expand.
+ * @param cchSrc The length of the string to expand.
+ * @param cDepth The recursion depth, starting at zero.
+ */
+static char *expr_expand_string(PEXPR pThis, const char *pchSrc, size_t cchSrc, unsigned cDepth)
+{
+ if (cDepth < EXPR_MAX_VAR_RECURSION)
+ {
+ size_t cbRetAlloc = RT_ALIGN_Z(cchSrc + 1 + 16, 16);
+ char *pszRet = (char *)RTMemTmpAlloc(cbRetAlloc);
+ if (pszRet)
+ {
+ size_t offRet = 0;
+ while (cchSrc > 0)
+ {
+ /*
+ * Look for the next potential variable reference.
+ */
+ const char *pchDollar = (const char *)memchr(pchSrc, '$', cchSrc);
+ size_t cchPlain = pchDollar ? pchDollar - pchSrc : cchSrc;
+ size_t cchNext = cchPlain;
+
+ if (pchDollar)
+ {
+ /* Treat lone $ w/o a following { as plain text. */
+ if ( cchPlain + 1 >= cchSrc
+ && pchDollar[0] == '$'
+ && ( cchPlain + 1 == cchSrc
+ || pchDollar[1] != '{') )
+ {
+ cchPlain += 1;
+ cchNext += 1;
+ pchDollar += 1;
+ }
+ /* Eat up escaped dollars: $$ -> $ */
+ else
+ while (cchNext + 2 <= cchSrc && pchDollar[1] == '$' && pchDollar[0] == '$')
+ {
+ cchPlain += 1;
+ cchNext += 2;
+ pchDollar += 2;
+ }
+ }
+
+ /* Finally copy out plain text.*/
+ if (cchPlain > 0)
+ {
+ if (cchPlain >= cbRetAlloc - offRet)
+ {
+ size_t const cbNeeded = RT_ALIGN_Z(offRet + cchPlain + (!pchDollar ? 1 : offRet <= 64 ? 16 : 64), 16);
+ void *pvNew = RTMemTmpAlloc(cbNeeded);
+ if (pvNew)
+ memcpy(pvNew, pszRet, offRet);
+ RTMemTmpFree(pszRet);
+ pszRet = (char *)pvNew;
+ if (pvNew)
+ cbRetAlloc = cbNeeded;
+ else
+ {
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cbNeeded);
+ return NULL;
+ }
+ }
+
+ memcpy(&pszRet[offRet], pchSrc, cchPlain);
+ offRet += cchPlain;
+ pszRet[offRet] = '\0';
+ pchSrc += cchNext;
+ cchSrc -= cchNext;
+ if (!cchSrc)
+ break;
+
+ /* If we don't have ${, just loop. */
+ if ( cchSrc < 2
+ || pchSrc[0] != '$'
+ || pchSrc[1] != '{')
+ continue;
+ }
+
+ /*
+ * If we get down here we have a ${ or $( at pchSrc. The fun part now is
+ * finding the end of it and recursively dealing with any sub-expansions first.
+ */
+ Assert(pchSrc[0] == '$' && pchSrc[1] == '{');
+ size_t cchVarRef;
+ bool fNested;
+ if (expr_expand_find_end(pThis, pchSrc, cchSrc, &cchVarRef, &fNested) == kExprRet_Ok)
+ {
+ /* Lookup the variable. Simple when it's a plain one, for nested ones we
+ first have to expand the variable name itself before looking it up. */
+ char *pszValue;
+ int vrc;
+ if (!fNested)
+ vrc = pThis->pEvaluator->pfnQueryVariable(&pchSrc[2], cchSrc - 3, pThis->pEvaluator->pvUser, &pszValue);
+ else
+ {
+ char *pszName = expr_expand_string(pThis, &pchSrc[2], cchSrc - 3, cDepth + 1);
+ if (!pszName)
+ {
+ RTMemTmpFree(pszRet);
+ return NULL;
+ }
+ vrc = pThis->pEvaluator->pfnQueryVariable(pszName, strlen(pszName), pThis->pEvaluator->pvUser, &pszValue);
+ RTMemTmpFree(pszName);
+ }
+
+ /* Treat variables that aren't found as empty strings for now.
+ This may need to become configurable later. */
+ char *pszValueFree = pszValue;
+ static char s_szNotFound[] = "";
+ if (vrc == VERR_NOT_FOUND)
+ {
+ pszValue = s_szNotFound;
+ vrc = VINF_SUCCESS;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Append the value to the return string.
+ */
+ size_t cchValue = strlen(pszValue);
+ if (cchValue > 0)
+ {
+ if (cchValue >= cbRetAlloc - offRet)
+ {
+ size_t const cbNeeded = RT_ALIGN_Z(offRet + cchValue + (!pchDollar ? 1 : offRet <= 64 ? 16 : 64),
+ 16);
+ void *pvNew = RTMemTmpAlloc(cbNeeded);
+ if (pvNew)
+ memcpy(pvNew, pszRet, offRet);
+ RTMemTmpFree(pszRet);
+ pszRet = (char *)pvNew;
+ if (pvNew)
+ cbRetAlloc = cbNeeded;
+ else
+ {
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cbNeeded);
+ RTStrFree(pszValueFree);
+ return NULL;
+ }
+ }
+
+ memcpy(&pszRet[offRet], pszValue, cchValue);
+ offRet += cchValue;
+ pszRet[offRet] = '\0';
+ }
+ pchSrc += cchVarRef;
+ cchSrc -= cchVarRef;
+ RTStrFree(pszValueFree);
+ continue;
+ }
+ }
+ RTMemTmpFree(pszRet);
+ return NULL;
+ }
+ return pszRet;
+ }
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", cbRetAlloc);
+ }
+ else
+ RTErrInfoSet(pThis->pErrInfo, VERR_TOO_MUCH_DATA, "Too deeply nested variable expression");
+ return NULL;
+}
+
+
+/**
+ * Simplifies a string variable.
+ *
+ * @returns kExprRet_Ok or kExprRet_Error.
+ * @param pThis The evaluator expression instance.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_make_simple_string(PEXPR pThis, PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ {
+ char *psz = (char *)RTMemTmpAlloc(EXPR_NUM_LEN);
+ if (psz)
+ {
+ expr_num_to_string(psz, pVar->uVal.i);
+ pVar->uVal.psz = psz;
+ pVar->enmType = kExprVar_SimpleString;
+ }
+ else
+ {
+ RTErrInfoSetF(pThis->pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate %zu bytes", EXPR_NUM_LEN);
+ return kExprRet_Error;
+ }
+ break;
+ }
+
+ case kExprVar_String:
+ case kExprVar_QuotedString:
+ {
+ Assert(strchr(pVar->uVal.psz, '$'));
+ char *psz = expr_expand_string(pThis, pVar->uVal.psz, strlen(pVar->uVal.psz), 0);
+ if (psz)
+ {
+ RTMemTmpFree(pVar->uVal.psz);
+ pVar->uVal.psz = psz;
+
+ pVar->enmType = pVar->enmType == kExprVar_String
+ ? kExprVar_SimpleString
+ : kExprVar_QuotedSimpleString;
+ }
+ else
+ return kExprRet_Error;
+ break;
+ }
+
+ case kExprVar_SimpleString:
+ case kExprVar_QuotedSimpleString:
+ /* nothing to do. */
+ break;
+
+ default:
+ AssertMsgFailed(("%d\n", pVar->enmType));
+ }
+ return kExprRet_Ok;
+}
+
+
+#if 0 /* unused */
+/**
+ * Turns a variable into a string value.
+ *
+ * @param pVar The variable.
+ */
+static void expr_var_make_string(PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ expr_var_make_simple_string(pVar);
+ break;
+
+ case kExprVar_String:
+ case kExprVar_SimpleString:
+ case kExprVar_QuotedString:
+ case kExprVar_QuotedSimpleString:
+ /* nothing to do. */
+ break;
+
+ default:
+ AssertMsgFailed(("%d\n", pVar->enmType));
+ }
+}
+#endif /* unused */
+
+
+/**
+ * Initializes a new variables with a integer value.
+ *
+ * @param pVar The new variable.
+ * @param i The integer value.
+ */
+static void expr_var_init_num(PEXPRVAR pVar, EXPRINT64 i)
+{
+ pVar->enmType = kExprVar_Num;
+ pVar->uVal.i = i;
+}
+
+
+/**
+ * Assigns a integer value to a variable.
+ *
+ * @param pVar The variable.
+ * @param i The integer value.
+ */
+static void expr_var_assign_num(PEXPRVAR pVar, EXPRINT64 i)
+{
+ expr_var_delete(pVar);
+ expr_var_init_num(pVar, i);
+}
+
+
+/**
+ * Turns the variable into a number.
+ *
+ * @returns status code.
+ * @param pThis The evaluator instance.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_make_num(PEXPR pThis, PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ /* nothing to do. */
+ break;
+
+ case kExprVar_String:
+ {
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc != kExprRet_Ok)
+ return rc;
+ RT_FALL_THROUGH();
+ }
+ case kExprVar_SimpleString:
+ {
+ EXPRINT64 i;
+ EXPRRET rc = expr_string_to_num(pThis, &i, pVar->uVal.psz, 0 /* fQuiet */);
+ if (rc < kExprRet_Ok)
+ return rc;
+ expr_var_assign_num(pVar, i);
+ break;
+ }
+
+ case kExprVar_QuotedString:
+ case kExprVar_QuotedSimpleString:
+ return expr_error(pThis, "Cannot convert a quoted string to a number");
+
+ default:
+ AssertMsgFailedReturn(("%d\n", pVar->enmType), kExprRet_Error);
+ }
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Try to turn the variable into a number.
+ *
+ * @returns status code.
+ * @param pThis The instance.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_try_make_num(PEXPR pThis, PEXPRVAR pVar)
+{
+ EXPRRET rc;
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ /* nothing to do. */
+ break;
+
+ case kExprVar_String:
+ rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc != kExprRet_Ok)
+ return rc;
+ RT_FALL_THROUGH();
+ case kExprVar_SimpleString:
+ {
+ EXPRINT64 i;
+ rc = expr_string_to_num(pThis, &i, pVar->uVal.psz, 1 /* fQuiet */);
+ if (rc < kExprRet_Ok)
+ return rc;
+ expr_var_assign_num(pVar, i);
+ break;
+ }
+
+ case kExprVar_QuotedString:
+ case kExprVar_QuotedSimpleString:
+ /* can't do this */
+ return kExprRet_Error;
+
+ default:
+ AssertMsgFailedReturn(("%d\n", pVar->enmType), kExprRet_Error);
+ }
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Initializes a new variables with a boolean value.
+ *
+ * @param pVar The new variable.
+ * @param f The boolean value.
+ */
+static void expr_var_init_bool(PEXPRVAR pVar, int f)
+{
+ pVar->enmType = kExprVar_Num;
+ pVar->uVal.i = !!f;
+}
+
+
+/**
+ * Assigns a boolean value to a variable.
+ *
+ * @param pVar The variable.
+ * @param f The boolean value.
+ */
+static void expr_var_assign_bool(PEXPRVAR pVar, int f)
+{
+ expr_var_delete(pVar);
+ expr_var_init_bool(pVar, f);
+}
+
+
+/**
+ * Turns the variable into an boolean.
+ *
+ * @returns the boolean interpretation.
+ * @param pThis The instance.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_make_bool(PEXPR pThis, PEXPRVAR pVar)
+{
+ EXPRRET rc = kExprRet_Ok;
+
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ pVar->uVal.i = !!pVar->uVal.i;
+ break;
+
+ case kExprVar_String:
+ rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc != kExprRet_Ok)
+ break;
+ RT_FALL_THROUGH();
+ case kExprVar_SimpleString:
+ {
+ /*
+ * Try convert it to a number. If that fails, check for 'true' or
+ * 'false', if neither then use python / GNU make logic wrt strings.
+ */
+ EXPRINT64 iVal;
+ char const *psz = pVar->uVal.psz;
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+ if ( *psz
+ && expr_string_to_num(pThis, &iVal, psz, 1 /* fQuiet */) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar, iVal != 0);
+ else if ( strncmp(psz, RT_STR_TUPLE("true")) == 0
+ && *RTStrStripL(&psz[sizeof("true") - 1]) == '\0')
+ expr_var_assign_bool(pVar, true);
+ else if ( strncmp(psz, RT_STR_TUPLE("false")) == 0
+ && *RTStrStripL(&psz[sizeof("false") - 1]) == '\0')
+ expr_var_assign_bool(pVar, false);
+ else
+ expr_var_assign_bool(pVar, *psz != '\0');
+ break;
+ }
+
+ case kExprVar_QuotedString:
+ rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc != kExprRet_Ok)
+ break;
+ RT_FALL_THROUGH();
+ case kExprVar_QuotedSimpleString:
+ /*
+ * Use python / GNU make boolean logic: non-empty string means true.
+ * No stripping here, as the string is quoted as should be taken exactly as given.
+ */
+ expr_var_assign_bool(pVar, *pVar->uVal.psz != '\0');
+ break;
+
+ default:
+ AssertMsgFailed(("%d\n", pVar->enmType));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Pops a varable off the stack and deletes it.
+ * @param pThis The evaluator instance.
+ */
+static void expr_pop_and_delete_var(PEXPR pThis)
+{
+ expr_var_delete(&pThis->aVars[pThis->iVar]);
+ pThis->iVar--;
+}
+
+
+
+/**
+ * Tries to make the variables the same type.
+ *
+ * This will not convert numbers to strings, unless one of them
+ * is a quoted string.
+ *
+ * this will try convert both to numbers if neither is quoted. Both
+ * conversions will have to suceed for this to be commited.
+ *
+ * All strings will be simplified.
+ *
+ * @returns status code. Done complaining on failure.
+ *
+ * @param pThis The evaluator instance.
+ * @param pVar1 The first variable.
+ * @param pVar2 The second variable.
+ * @param pszOp The operator requesting this (for errors).
+ */
+static EXPRRET expr_var_unify_types(PEXPR pThis, PEXPRVAR pVar1, PEXPRVAR pVar2, const char *pszOp)
+{
+/** @todo Add flag for selecting preference here when forcing types */
+
+
+ /*
+ * Try make the variables the same type before comparing.
+ */
+ if ( !expr_var_was_quoted(pVar1)
+ && !expr_var_was_quoted(pVar2))
+ {
+ if ( expr_var_is_string(pVar1)
+ || expr_var_is_string(pVar2))
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_try_make_num(pThis, pVar2);
+ else if (!expr_var_is_string(pVar2))
+ expr_var_try_make_num(pThis, pVar1);
+ else
+ {
+ /*
+ * Both are strings, simplify them then see if both can be made into numbers.
+ */
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ {
+ EXPRINT64 iVar1;
+ EXPRINT64 iVar2;
+ if ( expr_string_to_num(pThis, &iVar1, pVar1->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok
+ && expr_string_to_num(pThis, &iVar2, pVar2->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok)
+ {
+ expr_var_assign_num(pVar1, iVar1);
+ expr_var_assign_num(pVar2, iVar2);
+ }
+ }
+ else
+ return rc;
+ }
+ }
+ }
+ else
+ {
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ { /* likely */ }
+ else
+ return rc;
+ }
+
+ /*
+ * Complain if they aren't the same type now.
+ */
+ if (expr_var_is_string(pVar1) != expr_var_is_string(pVar2))
+ return expr_error(pThis, "Unable to unify types for \"%s\"", pszOp);
+ return kExprRet_Ok;
+}
+
+
+
+/*********************************************************************************************************************************
+* Operators *
+*********************************************************************************************************************************/
+
+/**
+ * Is variable defined, unary.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_defined(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ {
+ int vrc = pThis->pEvaluator->pfnQueryVariable(pVar->uVal.psz, strlen(pVar->uVal.psz), pThis->pEvaluator->pvUser, NULL);
+ expr_var_assign_bool(pVar, vrc != VERR_NOT_FOUND);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Does file(/dir/whatever) exist, unary.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_exists(PEXPR pThis)
+{
+ EXPRRET rc;
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ if (pThis->pEvaluator->fFlags & RTEXPREVAL_F_EXISTS_OP)
+ {
+ rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ expr_var_assign_bool(pVar, RTPathExists(pVar->uVal.psz) == 0);
+ }
+ else
+ rc = expr_error(pThis, "The 'exists' operator is not accessible");
+
+ return rc;
+}
+
+
+/**
+ * Convert to boolean.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bool(PEXPR pThis)
+{
+ return expr_var_make_bool(pThis, &pThis->aVars[pThis->iVar]);
+}
+
+
+/**
+ * Convert to number, works on quoted strings too.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_num(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ /* unquote the string */
+ if (pVar->enmType == kExprVar_QuotedSimpleString)
+ pVar->enmType = kExprVar_SimpleString;
+ else if (pVar->enmType == kExprVar_QuotedString)
+ pVar->enmType = kExprVar_String;
+
+ return expr_var_make_num(pThis, pVar);
+}
+
+
+/**
+ * Performs a strlen() on the simplified/converted string argument.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_strlen(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ expr_var_assign_num(pVar, strlen(pVar->uVal.psz));
+
+ return rc;
+}
+
+
+/**
+ * Convert to string (simplified and quoted)
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_str(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_simple_string(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ pVar->enmType = kExprVar_QuotedSimpleString;
+
+ return rc;
+}
+
+
+/**
+ * Pluss (dummy / make_integer)
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_pluss(PEXPR pThis)
+{
+ return expr_var_make_num(pThis, &pThis->aVars[pThis->iVar]);
+}
+
+
+/**
+ * Minus (negate)
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_minus(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_num(pThis, pVar);
+ if (rc >= kExprRet_Ok)
+ pVar->uVal.i = -pVar->uVal.i;
+
+ return rc;
+}
+
+
+
+/**
+ * Bitwise NOT.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_not(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_num(pThis, pVar);
+ if (rc >= kExprRet_Ok)
+ pVar->uVal.i = ~pVar->uVal.i;
+
+ return rc;
+}
+
+
+/**
+ * Logical NOT.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_logical_not(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_make_bool(pThis, pVar);
+ if (rc == kExprRet_Ok)
+ pVar->uVal.i = !pVar->uVal.i;
+
+ return rc;
+}
+
+
+/**
+ * Multiplication.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_multiply(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i *= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+
+/**
+ * Division.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_divide(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i /= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+
+/**
+ * Modulus.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_modulus(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i %= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Addition (numeric).
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_add(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i += pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Subtract (numeric).
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_sub(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i -= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise left shift.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_shift_left(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i <<= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise right shift.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_shift_right(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i >>= pVar2->uVal.i;
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than or equal, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_less_or_equal_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "vle");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) <= 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than or equal.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_less_or_equal_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "<=");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) <= 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_less_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "vlt");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) < 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_less_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "<");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) < 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater or equal than, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_greater_or_equal_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "vge");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) >= 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater or equal than.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_greater_or_equal_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, ">=");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) >= 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater than, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_greater_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, "vgt");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) > 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater than.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_greater_than(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc = expr_var_unify_types(pThis, pVar1, pVar2, ">");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) > 0);
+ }
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Equal, version strings.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_equal(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ int const fIsString1 = expr_var_is_string(pVar1);
+
+ /*
+ * The same type?
+ */
+ if (fIsString1 == expr_var_is_string(pVar2))
+ {
+ if (!fIsString1)
+ /* numbers are simple */
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ /* try a normal string compare. */
+ rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ {
+ if (!RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz))
+ expr_var_assign_bool(pVar1, 1);
+ /* try convert and compare as number instead. */
+ else if ( expr_var_try_make_num(pThis, pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ /* ok, they really aren't equal. */
+ else
+ expr_var_assign_bool(pVar1, 0);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * If the type differs, there are now two options:
+ * 1. Try convert the string to a valid number and compare the numbers.
+ * 2. Convert the non-string to a number and compare the strings.
+ */
+ if ( expr_var_try_make_num(pThis, pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ expr_var_assign_bool(pVar1, RTStrVersionCompare(pVar1->uVal.psz, pVar2->uVal.psz) == 0);
+ }
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Not equal, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_not_equal(PEXPR pThis)
+{
+ EXPRRET rc = expr_op_ver_equal(pThis);
+ if (rc >= kExprRet_Ok)
+ rc = expr_op_logical_not(pThis);
+ return rc;
+}
+
+
+/**
+ * Equal.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_equal(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ int const fIsString1 = expr_var_is_string(pVar1);
+
+ /*
+ * The same type?
+ */
+ if (fIsString1 == expr_var_is_string(pVar2))
+ {
+ if (!fIsString1)
+ /* numbers are simple */
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ /* try a normal string compare. */
+ rc = expr_var_make_simple_string(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ rc = expr_var_make_simple_string(pThis, pVar2);
+ if (rc == kExprRet_Ok)
+ {
+ if (!strcmp(pVar1->uVal.psz, pVar2->uVal.psz))
+ expr_var_assign_bool(pVar1, 1);
+ /* try convert and compare as number instead. */
+ else if ( expr_var_try_make_num(pThis, pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ /* ok, they really aren't equal. */
+ else
+ expr_var_assign_bool(pVar1, 0);
+ }
+ }
+ }
+ else
+ {
+ /*
+ * If the type differs, there are now two options:
+ * 1. Convert the string to a valid number and compare the numbers.
+ * 2. Convert an empty string to a 'false' boolean value and compare
+ * numerically. This one is a bit questionable, so we don't try this.
+ */
+ /** @todo this needs to be redone, both because we're hiding alloc errors
+ * here but also because this should be controlled by a flag. */
+ if ( expr_var_try_make_num(pThis, pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pThis, pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ rc = expr_error(pThis, "Cannot compare strings and numbers");
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Not equal.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_not_equal(PEXPR pThis)
+{
+ EXPRRET rc = expr_op_equal(pThis);
+ if (rc >= kExprRet_Ok)
+ rc = expr_op_logical_not(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise AND.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_and(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i &= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise XOR.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_xor(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i ^= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise OR.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_or(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ EXPRRET rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i |= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Logical AND.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_logical_and(PEXPR pThis)
+{
+ bool fResult = false;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_bool(pThis, pVar1);
+ if ( rc == kExprRet_Ok
+ && pVar1->uVal.i != 0)
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_bool(pThis, pVar2);
+ if (rc == kExprRet_Ok && pVar2->uVal.i != 0)
+ fResult = true;
+ }
+ expr_var_assign_bool(pVar1, fResult);
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Logical OR.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_logical_or(PEXPR pThis)
+{
+ bool fResult = false;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ EXPRRET rc = expr_var_make_bool(pThis, pVar1);
+ if (rc == kExprRet_Ok)
+ {
+ if (pVar1->uVal.i)
+ fResult = true;
+ else
+ {
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ rc = expr_var_make_bool(pThis, pVar2);
+ if (rc == kExprRet_Ok && pVar2->uVal.i != 0)
+ fResult = true;
+ }
+ }
+ expr_var_assign_bool(pVar1, fResult);
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Left parenthesis.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_left_parenthesis(PEXPR pThis)
+{
+ /*
+ * There should be a right parenthesis operator lined up for us now,
+ * eat it. If not found there is an inbalance.
+ */
+ EXPRRET rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if ( rc == kExprRet_Operator
+ && pThis->apOps[pThis->iOp]->szOp[0] == ')')
+ {
+ /* pop it and get another one which we can leave pending. */
+ pThis->iOp--;
+ rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if (rc >= kExprRet_Ok)
+ expr_unget_op(pThis);
+ }
+ else
+ rc = expr_error(pThis, "Missing ')'");
+
+ return rc;
+}
+
+
+/**
+ * Right parenthesis, dummy that's never actually called.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_right_parenthesis(PEXPR pThis)
+{
+ RT_NOREF_PV(pThis);
+ AssertFailed();
+ return kExprRet_Ok;
+}
+
+
+
+
+
+/**
+ * The operator table.
+ *
+ * This table is NOT ordered by precedence, but for linear search
+ * allowing for first match to return the correct operator. This
+ * means that || must come before |, or else | will match all.
+ */
+static const EXPROP g_aExprOps[] =
+{
+#define EXPR_OP(szOp, iPrecedence, cArgs, pfn) { szOp, sizeof(szOp) - 1, '\0', iPrecedence, cArgs, pfn }
+ /* Name, iPrecedence, cArgs, pfn */
+ EXPR_OP("defined", 90, 1, expr_op_defined),
+ EXPR_OP("exists", 90, 1, expr_op_exists),
+ EXPR_OP("bool", 90, 1, expr_op_bool),
+ EXPR_OP("num", 90, 1, expr_op_num),
+ EXPR_OP("strlen", 90, 1, expr_op_strlen),
+ EXPR_OP("str", 90, 1, expr_op_str),
+ EXPR_OP("+", 80, 1, expr_op_pluss),
+ EXPR_OP("-", 80, 1, expr_op_minus),
+ EXPR_OP("~", 80, 1, expr_op_bitwise_not),
+ EXPR_OP("*", 75, 2, expr_op_multiply),
+ EXPR_OP("/", 75, 2, expr_op_divide),
+ EXPR_OP("%", 75, 2, expr_op_modulus),
+ EXPR_OP("+", 70, 2, expr_op_add),
+ EXPR_OP("-", 70, 2, expr_op_sub),
+ EXPR_OP("<<", 65, 2, expr_op_shift_left),
+ EXPR_OP(">>", 65, 2, expr_op_shift_right),
+ EXPR_OP("<=", 60, 2, expr_op_less_or_equal_than),
+ EXPR_OP("<", 60, 2, expr_op_less_than),
+ EXPR_OP(">=", 60, 2, expr_op_greater_or_equal_than),
+ EXPR_OP(">", 60, 2, expr_op_greater_than),
+ EXPR_OP("vle", 60, 2, expr_op_ver_less_or_equal_than),
+ EXPR_OP("vlt", 60, 2, expr_op_ver_less_than),
+ EXPR_OP("vge", 60, 2, expr_op_ver_greater_or_equal_than),
+ EXPR_OP("vgt", 60, 2, expr_op_ver_greater_than),
+ EXPR_OP("==", 55, 2, expr_op_equal),
+ EXPR_OP("veq", 55, 2, expr_op_ver_equal),
+ EXPR_OP("!=", 55, 2, expr_op_not_equal),
+ EXPR_OP("vne", 55, 2, expr_op_ver_not_equal),
+ EXPR_OP("!", 80, 1, expr_op_logical_not),
+ EXPR_OP("^", 45, 2, expr_op_bitwise_xor),
+ EXPR_OP("&&", 35, 2, expr_op_logical_and),
+ EXPR_OP("&", 50, 2, expr_op_bitwise_and),
+ EXPR_OP("||", 30, 2, expr_op_logical_or),
+ EXPR_OP("|", 40, 2, expr_op_bitwise_or),
+ { "(", 1, ')', 10, 1, expr_op_left_parenthesis },
+ { ")", 1, '(', 10, 0, expr_op_right_parenthesis },
+ /* { "?", 1, ':', 5, 2, expr_op_question },
+ { ":", 1, '?', 5, 2, expr_op_colon }, -- too weird for now. */
+#undef EXPR_OP
+};
+
+/** Dummy end of expression fake. */
+static const EXPROP g_ExprEndOfExpOp =
+{
+ "", 0, '\0', 0, 0, NULL
+};
+
+
+/**
+ * Initializes the opcode character map if necessary.
+ */
+static void expr_map_init(void)
+{
+ unsigned i;
+ if (g_fExprInitializedMap)
+ return;
+
+ /*
+ * Initialize it.
+ */
+ for (i = 0; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++)
+ {
+ unsigned int ch = (unsigned int)g_aExprOps[i].szOp[0];
+ if (!g_abOpStartCharMap[ch])
+ {
+ g_abOpStartCharMap[ch] = (i << 2) | 1;
+ if (!RT_C_IS_ALPHA(ch))
+ g_abOpStartCharMap[ch] |= 2; /* Need no clear separation from operands. */
+ }
+ }
+
+ /* whitespace (assumes C-like locale because I'm lazy): */
+#define SET_WHITESPACE(a_ch) do { \
+ Assert(g_abOpStartCharMap[(unsigned char)(a_ch)] == 0); \
+ g_abOpStartCharMap[(unsigned char)(a_ch)] |= 2; \
+ } while (0)
+ SET_WHITESPACE(' ');
+ SET_WHITESPACE('\t');
+ SET_WHITESPACE('\n');
+ SET_WHITESPACE('\r');
+ SET_WHITESPACE('\v');
+ SET_WHITESPACE('\f');
+
+ g_fExprInitializedMap = 1;
+}
+
+
+/**
+ * Looks up a character in the map.
+ *
+ * @returns the value for that char, see g_abOpStartCharMap for details.
+ * @param ch The character.
+ */
+DECLINLINE(unsigned char) expr_map_get(char ch)
+{
+ return g_abOpStartCharMap[(unsigned char)ch];
+}
+
+
+/**
+ * Searches the operator table given a potential operator start char.
+ *
+ * @returns Pointer to the matching operator. NULL if not found.
+ * @param psz Pointer to what can be an operator.
+ * @param uchVal The expr_map_get value.
+ * @param fUnary Whether it must be an unary operator or not.
+ */
+static PCEXPROP expr_lookup_op(char const *psz, unsigned char uchVal, int fUnary)
+{
+ char ch = *psz;
+ unsigned i;
+ Assert((uchVal & 2) == (RT_C_IS_ALPHA(ch) ? 0 : 2));
+
+ for (i = uchVal >> 2; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++)
+ {
+ /* compare the string... */
+ if (g_aExprOps[i].szOp[0] != ch)
+ continue;
+ switch (g_aExprOps[i].cchOp)
+ {
+ case 1:
+ break;
+ case 2:
+ if (g_aExprOps[i].szOp[1] != psz[1])
+ continue;
+ break;
+ default:
+ if (strncmp(&g_aExprOps[i].szOp[1], psz + 1, g_aExprOps[i].cchOp - 1))
+ continue;
+ break;
+ }
+
+ /* ... and the operator type. */
+ if (fUnary == (g_aExprOps[i].cArgs == 1))
+ {
+ /* Check if we've got the needed operand separation: */
+ if ( (uchVal & 2)
+ || EXPR_IS_OP_SEPARATOR(psz[g_aExprOps[i].cchOp]))
+ {
+ /* got a match! */
+ return &g_aExprOps[i];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Ungets a binary operator.
+ *
+ * The operator is poped from the stack and put in the pending position.
+ *
+ * @param pThis The evaluator instance.
+ */
+static void expr_unget_op(PEXPR pThis)
+{
+ Assert(pThis->pPending == NULL);
+ Assert(pThis->iOp >= 0);
+
+ pThis->pPending = pThis->apOps[pThis->iOp];
+ pThis->apOps[pThis->iOp] = NULL;
+ pThis->iOp--;
+}
+
+
+
+/**
+ * Get the next token, it should be a binary operator, or the end of
+ * the expression, or a right parenthesis.
+ *
+ * The operator is pushed onto the stack and the status code indicates
+ * which of the two we found.
+ *
+ * @returns status code. Will grumble on failure.
+ * @retval kExprRet_EndOfExpr if we encountered the end of the expression.
+ * @retval kExprRet_Operator if we encountered a binary operator or right
+ * parenthesis. It's on the operator stack.
+ *
+ * @param pThis The evaluator instance.
+ */
+static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis)
+{
+ /*
+ * See if there is anything pending first.
+ */
+ PCEXPROP pOp = pThis->pPending;
+ if (pOp)
+ pThis->pPending = NULL;
+ else
+ {
+ /*
+ * Eat more of the expression.
+ */
+ char const *psz = pThis->psz;
+
+ /* spaces */
+ unsigned char uchVal;
+ char ch;
+ while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2)
+ psz++;
+
+ /* see what we've got. */
+ if (ch)
+ {
+ if (uchVal & 1)
+ pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */);
+ if (!pOp)
+ return expr_error(pThis, "Expected binary operator, found \"%.42s\"...", psz);
+ psz += pOp->cchOp;
+ }
+ else
+ pOp = &g_ExprEndOfExpOp;
+ pThis->psz = psz;
+ }
+
+ /*
+ * Push it.
+ */
+ if (pThis->iOp >= EXPR_MAX_OPERATORS - 1)
+ return expr_error(pThis, "Operator stack overflow");
+ pThis->apOps[++pThis->iOp] = pOp;
+
+ return pOp->iPrecedence
+ ? kExprRet_Operator
+ : kExprRet_EndOfExpr;
+}
+
+
+
+/**
+ * Get the next token, it should be an unary operator or an operand.
+ *
+ * This will fail if encountering the end of the expression since
+ * it is implied that there should be something more.
+ *
+ * The token is pushed onto the respective stack and the status code
+ * indicates which it is.
+ *
+ * @returns status code. On failure we'll be done bitching already.
+ * @retval kExprRet_Operator if we encountered an unary operator.
+ * It's on the operator stack.
+ * @retval kExprRet_Operand if we encountered an operand operator.
+ * It's on the operand stack.
+ *
+ * @param pThis The evaluator instance.
+ */
+static EXPRRET expr_get_unary_or_operand(PEXPR pThis)
+{
+ EXPRRET rc;
+ unsigned char uchVal;
+ PCEXPROP pOp;
+ char const *psz = pThis->psz;
+ char ch;
+
+ /*
+ * Eat white space and make sure there is something after it.
+ */
+ while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2)
+ psz++;
+ if (ch == '\0')
+ return expr_error(pThis, "Unexpected end of expression");
+
+ /*
+ * Is it an operator?
+ */
+ pOp = NULL;
+ if (uchVal & 1)
+ pOp = expr_lookup_op(psz, uchVal, 1 /* fUnary */);
+ if (pOp)
+ {
+ /*
+ * Push the operator onto the stack.
+ */
+ if (pThis->iVar < EXPR_MAX_OPERANDS - 1)
+ {
+ pThis->apOps[++pThis->iOp] = pOp;
+ rc = kExprRet_Operator;
+ }
+ else
+ rc = expr_error(pThis, "Operator stack overflow");
+ psz += pOp->cchOp;
+ }
+ else if (pThis->iVar < EXPR_MAX_OPERANDS - 1)
+ {
+ /*
+ * It's an operand. Figure out where it ends and
+ * push it onto the stack.
+ */
+ const char *pszStart;
+
+ rc = kExprRet_Ok;
+ if (ch == '"')
+ {
+ pszStart = ++psz;
+ while ((ch = *psz) != '\0' && ch != '"')
+ psz++;
+ rc = expr_var_init_substring(pThis, &pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_QuotedString);
+ if (ch != '\0')
+ psz++;
+ }
+ else if (ch == '\'')
+ {
+ pszStart = ++psz;
+ while ((ch = *psz) != '\0' && ch != '\'')
+ psz++;
+ rc = expr_var_init_substring(pThis, &pThis->aVars[++pThis->iVar], pszStart, psz - pszStart,
+ kExprVar_QuotedSimpleString);
+ if (ch != '\0')
+ psz++;
+ }
+ else
+ {
+ unsigned cPars = 0;
+ pszStart = psz;
+ while ((ch = *psz) != '\0')
+ {
+ /* ${asdf} needs special handling. */
+ if ( ch == '$'
+ && psz[1] == '{')
+ {
+ psz++;
+ if (cPars < EXPR_MAX_VAR_RECURSION)
+ ++cPars;
+ else
+ {
+ rc = expr_error(pThis, "Too deep nesting of variable expansions");
+ break;
+ }
+ }
+ else if (ch == '}')
+ {
+ if (cPars > 0)
+ cPars--;
+ }
+ else if (cPars == 0)
+ {
+ uchVal = expr_map_get(ch);
+ if (uchVal == 0)
+ { /*likely*/ }
+ else if ((uchVal & 3) == 2 /*isspace*/)
+ break;
+ else if ( (uchVal & 1)
+ && psz != pszStart /* not at the start */
+ && ( (uchVal & 2) /* operator without separator needs */
+ || EXPR_IS_OP_SEPARATOR_NO_SPACE(psz[-1])))
+ {
+ pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */);
+ if (pOp)
+ break;
+ }
+ }
+
+ /* next */
+ psz++;
+ }
+
+ if (rc == kExprRet_Ok)
+ rc = expr_var_init_substring(pThis, &pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_String);
+ }
+ }
+ else
+ rc = expr_error(pThis, "Operand stack overflow");
+ pThis->psz = psz;
+
+ return rc;
+}
+
+
+/**
+ * Evaluates the current expression.
+ *
+ * @returns status code.
+ *
+ * @param pThis The instance.
+ */
+static EXPRRET expr_eval(PEXPR pThis)
+{
+ EXPRRET rc;
+ PCEXPROP pOp;
+
+ /*
+ * The main loop.
+ */
+ for (;;)
+ {
+ /*
+ * Eat unary operators until we hit an operand.
+ */
+ do
+ rc = expr_get_unary_or_operand(pThis);
+ while (rc == kExprRet_Operator);
+ if (rc < kExprRet_Ok)
+ break;
+
+ /*
+ * Look for a binary operator, right parenthesis or end of expression.
+ */
+ rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if (rc < kExprRet_Ok)
+ break;
+ expr_unget_op(pThis);
+
+ /*
+ * Pop operators and apply them.
+ *
+ * Parenthesis will be handed via precedence, where the left parenthesis
+ * will go pop the right one and make another operator pending.
+ */
+ while ( pThis->iOp >= 0
+ && pThis->apOps[pThis->iOp]->iPrecedence >= pThis->pPending->iPrecedence)
+ {
+ pOp = pThis->apOps[pThis->iOp--];
+ Assert(pThis->iVar + 1 >= pOp->cArgs);
+ rc = pOp->pfn(pThis);
+ if (rc < kExprRet_Ok)
+ break;
+ }
+ if (rc < kExprRet_Ok)
+ break;
+
+ /*
+ * Get the next binary operator or end of expression.
+ * There should be no right parenthesis here.
+ */
+ rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if (rc < kExprRet_Ok)
+ break;
+ pOp = pThis->apOps[pThis->iOp];
+ if (!pOp->iPrecedence)
+ break; /* end of expression */
+ if (!pOp->cArgs)
+ {
+ rc = expr_error(pThis, "Unexpected \"%s\"", pOp->szOp);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Destroys the given instance.
+ *
+ * @param pThis The instance to destroy.
+ */
+static void expr_destroy(PEXPR pThis)
+{
+ while (pThis->iVar >= 0)
+ {
+ expr_var_delete(pThis->aVars);
+ pThis->iVar--;
+ }
+ RTMemTmpFree(pThis);
+}
+
+
+/**
+ * Instantiates an expression evaluator.
+ *
+ * @returns The instance.
+ */
+static PEXPR expr_create(RTEXPREVALINT *pThis, const char *pch, size_t cch, PRTERRINFO pErrInfo)
+{
+ cch = RTStrNLen(pch, cch);
+
+ PEXPR pExpr = (PEXPR)RTMemTmpAllocZ(sizeof(*pExpr) + cch + 1);
+ if (pExpr)
+ {
+ pExpr->psz = pExpr->pszExpr = (char *)memcpy(pExpr + 1, pch, cch);
+ pExpr->pErrInfo = pErrInfo;
+ pExpr->pEvaluator = pThis;
+ pExpr->pPending = NULL;
+ pExpr->iVar = -1;
+ pExpr->iOp = -1;
+
+ expr_map_init();
+ }
+ return pExpr;
+}
+
+
+
+/*********************************************************************************************************************************
+* API *
+*********************************************************************************************************************************/
+
+/** @callback_method_impl{PFNRTEXPREVALQUERYVARIABLE, Stub} */
+static DECLCALLBACK(int) rtExprEvalDummyQueryVariable(const char *pchName, size_t cchName, void *pvUser, char **ppszValue)
+{
+ RT_NOREF(pchName, cchName, pvUser);
+ if (ppszValue)
+ *ppszValue = NULL;
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTExprEvalCreate(PRTEXPREVAL phEval, uint64_t fFlags, const char *pszName,
+ void *pvUser, PFNRTEXPREVALQUERYVARIABLE pfnQueryVariable)
+{
+ AssertPtrReturn(phEval, VERR_INVALID_POINTER);
+ *phEval = NULL;
+ AssertPtrNullReturn(pfnQueryVariable, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~0), VERR_INVALID_FLAGS);
+
+ char *pszNameCopy = RTStrDup(pszName);
+ if (pszNameCopy)
+ {
+ RTEXPREVALINT *pThis = (RTEXPREVALINT *)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->u32Magic = RTEXPREVAL_MAGIC;
+ pThis->cRefs = 1;
+ pThis->fFlags = fFlags;
+ pThis->pszName = pszNameCopy;
+ pThis->pvUser = pvUser;
+ pThis->pfnQueryVariable = pfnQueryVariable ? pfnQueryVariable : rtExprEvalDummyQueryVariable;
+ *phEval = pThis;
+ return VINF_SUCCESS;
+
+ }
+
+ RTStrFree(pszNameCopy);
+ return VERR_NO_MEMORY;
+ }
+ return VERR_NO_STR_MEMORY;
+}
+
+
+RTDECL(uint32_t) RTExprEvalRetain(RTEXPREVAL hEval)
+{
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, UINT32_MAX);
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < 512);
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTExprEvalRelease(RTEXPREVAL hEval)
+{
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, UINT32_MAX);
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < 512);
+ if (cRefs == 0)
+ {
+ pThis->u32Magic = ~RTEXPREVAL_MAGIC;
+ if (pThis->pszName)
+ {
+ RTStrFree(pThis->pszName);
+ pThis->pszName = NULL;
+ }
+ RTMemFree(pThis);
+ return 0;
+ }
+ return cRefs;
+}
+
+
+RTDECL(int) RTExprEvalToBool(RTEXPREVAL hEval, const char *pch, size_t cch, bool *pfResult, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(pfResult, VERR_INVALID_POINTER);
+ *pfResult = false;
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Instantiate the expression evaluator and let it have a go at it.
+ */
+ int rc;
+ PEXPR pExpr = expr_create(pThis, pch, cch, pErrInfo);
+ if (pExpr)
+ {
+ if (expr_eval(pExpr) >= kExprRet_Ok)
+ {
+ /*
+ * Convert the result (on top of the stack) to boolean and
+ * set our return value accordingly.
+ */
+ if ( expr_var_make_bool(pExpr, &pExpr->aVars[0]) == kExprRet_Ok
+ && pExpr->aVars[0].uVal.i)
+ *pfResult = true;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_PARSE_ERROR; /** @todo better errors? */
+ expr_destroy(pExpr);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTExprEvalToInteger(RTEXPREVAL hEval, const char *pch, size_t cch, int64_t *piResult, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(piResult, VERR_INVALID_POINTER);
+ *piResult = INT64_MAX;
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Instantiate the expression evaluator and let it have a go at it.
+ */
+ int rc;
+ PEXPR pExpr = expr_create(pThis, pch, cch, pErrInfo);
+ if (pExpr)
+ {
+ if (expr_eval(pExpr) >= kExprRet_Ok)
+ {
+ /*
+ * Convert the result (on top of the stack) to boolean and
+ * set our return value accordingly.
+ */
+ PEXPRVAR pVar = &pExpr->aVars[0];
+ EXPRRET rcExpr = expr_var_make_num(pExpr, pVar);
+ if (rcExpr >= kExprRet_Ok)
+ {
+ *piResult = pVar->uVal.i;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_PARSE_ERROR; /** @todo better error! */
+ }
+ else
+ rc = VERR_PARSE_ERROR; /** @todo better errors? */
+ expr_destroy(pExpr);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ return rc;
+}
+
+
+RTDECL(int) RTExprEvalToString(RTEXPREVAL hEval, const char *pch, size_t cch, char **ppszResult, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(ppszResult, VERR_INVALID_POINTER);
+ *ppszResult = NULL;
+ RTEXPREVALINT *pThis = hEval;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTEXPREVAL_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Instantiate the expression evaluator and let it have a go at it.
+ */
+ int rc;
+ PEXPR pExpr = expr_create(pThis, pch, cch, pErrInfo);
+ if (pExpr)
+ {
+ if (expr_eval(pExpr) >= kExprRet_Ok)
+ {
+ /*
+ * Convert the result (on top of the stack) to a string
+ * and copy it out the variable buffer.
+ */
+ PEXPRVAR pVar = &pExpr->aVars[0];
+ if (expr_var_make_simple_string(pExpr, pVar) == kExprRet_Ok)
+ rc = RTStrDupEx(ppszResult, pVar->uVal.psz);
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ else
+ rc = VERR_PARSE_ERROR;
+ expr_destroy(pExpr);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/misc/getopt.cpp b/src/VBox/Runtime/common/misc/getopt.cpp
new file mode 100644
index 00000000..1a079546
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/getopt.cpp
@@ -0,0 +1,922 @@
+/* $Id: getopt.cpp $ */
+/** @file
+ * IPRT - Command Line Parsing
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cidr.h>
+#include <iprt/net.h> /* must come before getopt.h */
+#include <iprt/getopt.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/message.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef IN_RT_STATIC /* We don't need full unicode case insensitive if we ASSUME basic latin only. */
+# define RTStrICmp RTStrICmpAscii
+# define RTStrNICmp RTStrNICmpAscii
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Standard options that gets included unless RTGETOPTINIT_FLAGS_NO_STD_OPTS is
+ * set.
+ */
+static RTGETOPTDEF const g_aStdOptions[] =
+{
+ { "--help", 'h', RTGETOPT_REQ_NOTHING },
+ { "-help", 'h', RTGETOPT_REQ_NOTHING },
+ { "--version", 'V', RTGETOPT_REQ_NOTHING },
+ { "-version", 'V', RTGETOPT_REQ_NOTHING },
+};
+/** The index of --help in g_aStdOptions. Used for some trickery. */
+#define RTGETOPT_STD_OPTIONS_HELP_IDX 0
+
+
+
+RTDECL(int) RTGetOptInit(PRTGETOPTSTATE pState, int argc, char **argv,
+ PCRTGETOPTDEF paOptions, size_t cOptions,
+ int iFirst, uint32_t fFlags)
+{
+ AssertReturn(!(fFlags & ~(RTGETOPTINIT_FLAGS_OPTS_FIRST | RTGETOPTINIT_FLAGS_NO_STD_OPTS)), VERR_INVALID_PARAMETER);
+
+ pState->argv = argv;
+ pState->argc = argc;
+ pState->paOptions = paOptions;
+ pState->cOptions = cOptions;
+ pState->iNext = iFirst;
+ pState->pszNextShort = NULL;
+ pState->pDef = NULL;
+ pState->uIndex = UINT32_MAX;
+ pState->fFlags = fFlags;
+ pState->cNonOptions = 0;
+
+#ifdef RT_STRICT
+ /* validate the options. */
+ for (size_t i = 0; i < cOptions; i++)
+ {
+ Assert(!(paOptions[i].fFlags & ~RTGETOPT_VALID_MASK));
+ Assert( !(paOptions[i].fFlags & (RTGETOPT_FLAG_INDEX_DEF_MASK | RTGETOPT_FLAG_INDEX_DEF_DASH))
+ || (paOptions[i].fFlags & RTGETOPT_FLAG_INDEX) );
+ Assert(paOptions[i].iShort > 0);
+ Assert(paOptions[i].iShort != VINF_GETOPT_NOT_OPTION);
+ Assert(paOptions[i].iShort != '-');
+ if (paOptions[i].fFlags & RTGETOPT_FLAG_ICASE)
+ {
+ const char *psz = paOptions[i].pszLong;
+ unsigned char ch;
+ while ((ch = *psz++) != '\0')
+ Assert(ch <= 0x7f); /* ASSUMPTION that we can use RTStrICmpAscii and RTStrNICmpAscii. */
+ }
+ }
+#endif
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTGetOptInit);
+
+#ifndef IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES
+
+/**
+ * Converts an stringified IPv4 address into the RTNETADDRIPV4 representation.
+ *
+ * @returns VINF_SUCCESS on success, VERR_GETOPT_INVALID_ARGUMENT_FORMAT on
+ * failure.
+ *
+ * @param pszValue The value to convert.
+ * @param pAddr Where to store the result.
+ */
+static int rtgetoptConvertIPv4Addr(const char *pszValue, PRTNETADDRIPV4 pAddr)
+{
+ if (RT_FAILURE(RTNetStrToIPv4Addr(pszValue, pAddr)))
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts an stringified Ethernet MAC address into the RTMAC representation.
+ *
+ * @returns VINF_SUCCESS on success, VERR_GETOPT_INVALID_ARGUMENT_FORMAT on
+ * failure.
+ *
+ * @param pszValue The value to convert.
+ * @param pAddr Where to store the result.
+ */
+static int rtgetoptConvertMacAddr(const char *pszValue, PRTMAC pAddr)
+{
+
+ int rc = RTNetStrToMacAddr(pszValue, pAddr);
+ if (RT_FAILURE(rc))
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+
+ return VINF_SUCCESS;
+}
+
+#endif /* IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES */
+
+/**
+ * Searches for a long option.
+ *
+ * @returns Pointer to a matching option.
+ * @param pszOption The alleged long option.
+ * @param paOptions Option array.
+ * @param cOptions Number of items in the array.
+ * @param fFlags Init flags.
+ */
+static PCRTGETOPTDEF rtGetOptSearchLong(const char *pszOption, PCRTGETOPTDEF paOptions, size_t cOptions, uint32_t fFlags)
+{
+ PCRTGETOPTDEF pOpt = paOptions;
+ while (cOptions-- > 0)
+ {
+ if (pOpt->pszLong)
+ {
+ uint32_t const fOptFlags = pOpt->fFlags;
+ if ((fOptFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
+ {
+ /*
+ * A value is required with the argument. We're trying to be
+ * understanding here and will permit any of the following:
+ * --long12:value, --long12=value, --long12 value,
+ * --long:value, --long=value, --long value,
+ *
+ * If the option is index, then all trailing chars must be
+ * digits. For error reporting reasons we also match where
+ * there is no index.
+ */
+ size_t cchLong = strlen(pOpt->pszLong);
+ if ( !strncmp(pszOption, pOpt->pszLong, cchLong)
+ || ( (fOptFlags & RTGETOPT_FLAG_ICASE)
+ && !RTStrNICmp(pszOption, pOpt->pszLong, cchLong)))
+ {
+ if ( (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
+ && pszOption[cchLong] == '-'
+ && RT_C_IS_DIGIT(pszOption[cchLong + 1])) /* given "--long" we match "--long-1" but not "--long-". */
+ cchLong++;
+ if (fOptFlags & RTGETOPT_FLAG_INDEX)
+ while (RT_C_IS_DIGIT(pszOption[cchLong]))
+ cchLong++;
+ if ( pszOption[cchLong] == '\0'
+ || pszOption[cchLong] == ':'
+ || pszOption[cchLong] == '=')
+ return pOpt;
+ }
+ }
+ else if (fOptFlags & RTGETOPT_FLAG_INDEX)
+ {
+ /*
+ * The option takes an index but no value.
+ * As above, we also match where there is no index.
+ */
+ size_t cchLong = strlen(pOpt->pszLong);
+ if ( !strncmp(pszOption, pOpt->pszLong, cchLong)
+ || ( (fOptFlags & RTGETOPT_FLAG_ICASE)
+ && !RTStrNICmp(pszOption, pOpt->pszLong, cchLong)))
+ {
+ if ( (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
+ && pszOption[cchLong] == '-'
+ && RT_C_IS_DIGIT(pszOption[cchLong + 1]))
+ cchLong++;
+ while (RT_C_IS_DIGIT(pszOption[cchLong]))
+ cchLong++;
+ if (pszOption[cchLong] == '\0')
+ return pOpt;
+ }
+ }
+ else if ( !strcmp(pszOption, pOpt->pszLong)
+ || ( (fOptFlags & RTGETOPT_FLAG_ICASE)
+ && !RTStrICmp(pszOption, pOpt->pszLong)))
+ return pOpt;
+ }
+ pOpt++;
+ }
+
+ if (!(fFlags & RTGETOPTINIT_FLAGS_NO_STD_OPTS))
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aStdOptions); i++)
+ if ( !strcmp(pszOption, g_aStdOptions[i].pszLong)
+ || ( g_aStdOptions[i].fFlags & RTGETOPT_FLAG_ICASE
+ && !RTStrICmp(pszOption, g_aStdOptions[i].pszLong)))
+ return &g_aStdOptions[i];
+
+ return NULL;
+}
+
+
+/**
+ * Searches for a matching short option.
+ *
+ * @returns Pointer to a matching option.
+ * @param chOption The option char.
+ * @param paOptions Option array.
+ * @param cOptions Number of items in the array.
+ * @param fFlags Init flags.
+ */
+static PCRTGETOPTDEF rtGetOptSearchShort(int chOption, PCRTGETOPTDEF paOptions, size_t cOptions, uint32_t fFlags)
+{
+ PCRTGETOPTDEF pOpt = paOptions;
+ while (cOptions-- > 0)
+ {
+ if (pOpt->iShort == chOption)
+ return pOpt;
+ pOpt++;
+ }
+
+ if (!(fFlags & RTGETOPTINIT_FLAGS_NO_STD_OPTS))
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(g_aStdOptions); i++)
+ if (g_aStdOptions[i].iShort == chOption)
+ return &g_aStdOptions[i];
+ if (chOption == '?')
+ return &g_aStdOptions[RTGETOPT_STD_OPTIONS_HELP_IDX];
+ }
+ return NULL;
+}
+
+
+/**
+ * Value string -> Value union.
+ *
+ * @returns IPRT status code.
+ * @param fFlags The value flags.
+ * @param pszValue The value string.
+ * @param pValueUnion Where to return the processed value.
+ */
+static int rtGetOptProcessValue(uint32_t fFlags, const char *pszValue, PRTGETOPTUNION pValueUnion)
+{
+ /*
+ * Transform into a option value as requested.
+ * If decimal conversion fails, we'll check for "0x<xdigit>" and
+ * try a 16 based conversion. We will not interpret any of the
+ * generic ints as octals.
+ */
+ uint32_t const fSwitchValue = fFlags & ( RTGETOPT_REQ_MASK
+ | RTGETOPT_FLAG_HEX
+ | RTGETOPT_FLAG_DEC
+ | RTGETOPT_FLAG_OCT);
+ switch (fSwitchValue)
+ {
+ case RTGETOPT_REQ_STRING:
+ pValueUnion->psz = pszValue;
+ break;
+
+ case RTGETOPT_REQ_BOOL:
+ if ( !RTStrICmp(pszValue, "true")
+ || !RTStrICmp(pszValue, "t")
+ || !RTStrICmp(pszValue, "yes")
+ || !RTStrICmp(pszValue, "y")
+ || !RTStrICmp(pszValue, "enabled")
+ || !RTStrICmp(pszValue, "enable")
+ || !RTStrICmp(pszValue, "en")
+ || !RTStrICmp(pszValue, "e")
+ || !RTStrICmp(pszValue, "on")
+ || !RTStrCmp(pszValue, "1")
+ )
+ pValueUnion->f = true;
+ else if ( !RTStrICmp(pszValue, "false")
+ || !RTStrICmp(pszValue, "f")
+ || !RTStrICmp(pszValue, "no")
+ || !RTStrICmp(pszValue, "n")
+ || !RTStrICmp(pszValue, "disabled")
+ || !RTStrICmp(pszValue, "disable")
+ || !RTStrICmp(pszValue, "dis")
+ || !RTStrICmp(pszValue, "d")
+ || !RTStrICmp(pszValue, "off")
+ || !RTStrCmp(pszValue, "0")
+ )
+ pValueUnion->f = false;
+ else
+ {
+ pValueUnion->psz = pszValue;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+ break;
+
+ case RTGETOPT_REQ_BOOL_ONOFF:
+ if (!RTStrICmp(pszValue, "on"))
+ pValueUnion->f = true;
+ else if (!RTStrICmp(pszValue, "off"))
+ pValueUnion->f = false;
+ else
+ {
+ pValueUnion->psz = pszValue;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+ break;
+
+#define MY_INT_CASE(req, type, memb, convfn) \
+ case req: \
+ { \
+ type Value; \
+ if ( convfn(pszValue, 10, &Value) != VINF_SUCCESS \
+ && ( pszValue[0] != '0' \
+ || (pszValue[1] != 'x' && pszValue[1] != 'X') \
+ || !RT_C_IS_XDIGIT(pszValue[2]) \
+ || convfn(pszValue, 16, &Value) != VINF_SUCCESS ) ) \
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
+ pValueUnion->memb = Value; \
+ break; \
+ }
+#define MY_BASE_INT_CASE(req, type, memb, convfn, base) \
+ case req: \
+ { \
+ type Value; \
+ if (convfn(pszValue, base, &Value) != VINF_SUCCESS) \
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
+ pValueUnion->memb = Value; \
+ break; \
+ }
+
+ MY_INT_CASE(RTGETOPT_REQ_INT8, int8_t, i8, RTStrToInt8Full)
+ MY_INT_CASE(RTGETOPT_REQ_INT16, int16_t, i16, RTStrToInt16Full)
+ MY_INT_CASE(RTGETOPT_REQ_INT32, int32_t, i32, RTStrToInt32Full)
+ MY_INT_CASE(RTGETOPT_REQ_INT64, int64_t, i64, RTStrToInt64Full)
+ MY_INT_CASE(RTGETOPT_REQ_UINT8, uint8_t, u8, RTStrToUInt8Full)
+ MY_INT_CASE(RTGETOPT_REQ_UINT16, uint16_t, u16, RTStrToUInt16Full)
+ MY_INT_CASE(RTGETOPT_REQ_UINT32, uint32_t, u32, RTStrToUInt32Full)
+ MY_INT_CASE(RTGETOPT_REQ_UINT64, uint64_t, u64, RTStrToUInt64Full)
+
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_HEX, int8_t, i8, RTStrToInt8Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_HEX, int16_t, i16, RTStrToInt16Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_HEX, int32_t, i32, RTStrToInt32Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_HEX, int64_t, i64, RTStrToInt64Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_HEX, uint8_t, u8, RTStrToUInt8Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_HEX, uint16_t, u16, RTStrToUInt16Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_HEX, uint32_t, u32, RTStrToUInt32Full, 16)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_HEX, uint64_t, u64, RTStrToUInt64Full, 16)
+
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_DEC, int8_t, i8, RTStrToInt8Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_DEC, int16_t, i16, RTStrToInt16Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_DEC, int32_t, i32, RTStrToInt32Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_DEC, int64_t, i64, RTStrToInt64Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_DEC, uint8_t, u8, RTStrToUInt8Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_DEC, uint16_t, u16, RTStrToUInt16Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_DEC, uint32_t, u32, RTStrToUInt32Full, 10)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_DEC, uint64_t, u64, RTStrToUInt64Full, 10)
+
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT8 | RTGETOPT_FLAG_OCT, int8_t, i8, RTStrToInt8Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT16 | RTGETOPT_FLAG_OCT, int16_t, i16, RTStrToInt16Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT32 | RTGETOPT_FLAG_OCT, int32_t, i32, RTStrToInt32Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_INT64 | RTGETOPT_FLAG_OCT, int64_t, i64, RTStrToInt64Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT8 | RTGETOPT_FLAG_OCT, uint8_t, u8, RTStrToUInt8Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT16 | RTGETOPT_FLAG_OCT, uint16_t, u16, RTStrToUInt16Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT, uint32_t, u32, RTStrToUInt32Full, 8)
+ MY_BASE_INT_CASE(RTGETOPT_REQ_UINT64 | RTGETOPT_FLAG_OCT, uint64_t, u64, RTStrToUInt64Full, 8)
+
+#undef MY_INT_CASE
+#undef MY_BASE_INT_CASE
+
+#ifndef IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES
+
+ case RTGETOPT_REQ_IPV4ADDR:
+ {
+ RTNETADDRIPV4 Addr;
+ if (rtgetoptConvertIPv4Addr(pszValue, &Addr) != VINF_SUCCESS)
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pValueUnion->IPv4Addr = Addr;
+ break;
+ }
+
+ case RTGETOPT_REQ_IPV4CIDR:
+ {
+ RTNETADDRIPV4 network;
+ RTNETADDRIPV4 netmask;
+ if (RT_FAILURE(RTCidrStrToIPv4(pszValue, &network, &netmask)))
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pValueUnion->CidrIPv4.IPv4Network.u = network.u;
+ pValueUnion->CidrIPv4.IPv4Netmask.u = netmask.u;
+ break;
+ }
+
+ case RTGETOPT_REQ_MACADDR:
+ {
+ RTMAC Addr;
+ if (rtgetoptConvertMacAddr(pszValue, &Addr) != VINF_SUCCESS)
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pValueUnion->MacAddr = Addr;
+ break;
+ }
+
+#endif /* IPRT_GETOPT_WITHOUT_NETWORK_ADDRESSES */
+
+ case RTGETOPT_REQ_UUID:
+ {
+ RTUUID Uuid;
+ if (RTUuidFromStr(&Uuid, pszValue) != VINF_SUCCESS)
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pValueUnion->Uuid = Uuid;
+ break;
+ }
+
+#define MY_INT_PAIR_CASE(a_fReqValue, a_fReqValueOptional, a_Type, a_MemberPrefix, a_fnConv, a_ConvBase, a_DefaultValue) \
+ case a_fReqValue: \
+ case a_fReqValueOptional: \
+ { \
+ /* First value: */ \
+ a_Type Value1; \
+ char *pszNext = NULL; \
+ unsigned uBase = pszValue[0] == '0' \
+ && (pszValue[1] == 'x' || pszValue[1] == 'X') \
+ && RT_C_IS_XDIGIT(pszValue[2]) \
+ ? 16 : a_ConvBase; \
+ int rc = a_fnConv(pszValue, &pszNext, uBase, &Value1); \
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES) \
+ { \
+ /* The second value, could be optional: */ \
+ a_Type Value2 = a_DefaultValue; \
+ pszValue = pszNext;\
+ if (pszValue) \
+ { \
+ while (RT_C_IS_BLANK(*pszValue)) \
+ pszValue++; \
+ if (*pszValue == ':' || *pszValue == '/' || *pszValue == '|') \
+ do pszValue++; \
+ while (RT_C_IS_BLANK(*pszValue)); \
+ if (pszValue != pszNext) \
+ { \
+ uBase = pszValue[0] == '0' \
+ && (pszValue[1] == 'x' || pszValue[1] == 'X') \
+ && RT_C_IS_XDIGIT(pszValue[2]) \
+ ? 16 : a_ConvBase; \
+ rc = a_fnConv(pszValue, &pszNext, uBase, &Value2); \
+ if (rc == VINF_SUCCESS) \
+ { /* likely */ } \
+ else \
+ AssertMsgFailedReturn(("z rc=%Rrc: '%s' '%s' uBase=%d\n", rc, pszValue, pszNext, uBase), \
+ VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
+ } \
+ else if (fSwitchValue != (a_fReqValueOptional)) \
+ AssertMsgFailedReturn(("x\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
+ } \
+ else if (fSwitchValue != (a_fReqValueOptional)) \
+ AssertMsgFailedReturn(("y\n"), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); \
+ pValueUnion->a_MemberPrefix##Second = Value2; \
+ pValueUnion->a_MemberPrefix##First = Value1; \
+ break; \
+ } \
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT; \
+ }
+
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR,
+ uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
+ uint32_t, PairU32.u, RTStrToUInt32Ex, 10, UINT32_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
+ uint32_t, PairU32.u, RTStrToUInt32Ex, 16, UINT32_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT32_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT32_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
+ uint32_t, PairU32.u, RTStrToUInt32Ex, 8, UINT32_MAX)
+
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR,
+ uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_DEC, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_DEC,
+ uint64_t, PairU64.u, RTStrToUInt64Ex, 10, UINT64_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_HEX, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_HEX,
+ uint64_t, PairU64.u, RTStrToUInt64Ex, 16, UINT64_MAX)
+ MY_INT_PAIR_CASE(RTGETOPT_REQ_UINT64_PAIR | RTGETOPT_FLAG_OCT, RTGETOPT_REQ_UINT64_OPTIONAL_PAIR | RTGETOPT_FLAG_OCT,
+ uint64_t, PairU64.u, RTStrToUInt64Ex, 8, UINT64_MAX)
+
+ default:
+ AssertMsgFailed(("f=%#x\n", fFlags));
+ return VERR_INTERNAL_ERROR;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Moves one argv option entries.
+ *
+ * @param papszTo Destination.
+ * @param papszFrom Source.
+ */
+static void rtGetOptMoveArgvEntries(char **papszTo, char **papszFrom)
+{
+ if (papszTo != papszFrom)
+ {
+ Assert((uintptr_t)papszTo < (uintptr_t)papszFrom);
+ char * const pszMoved = papszFrom[0];
+ memmove(&papszTo[1], &papszTo[0], (uintptr_t)papszFrom - (uintptr_t)papszTo);
+ papszTo[0] = pszMoved;
+ }
+}
+
+
+RTDECL(int) RTGetOpt(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion)
+{
+ /*
+ * Reset the variables kept in state.
+ */
+ pState->pDef = NULL;
+ pState->uIndex = UINT32_MAX;
+
+ /*
+ * Make sure the union is completely cleared out, whatever happens below.
+ */
+ pValueUnion->u64 = 0;
+ pValueUnion->pDef = NULL;
+
+ /*
+ * The next option.
+ */
+ bool fShort;
+ int iThis;
+ const char *pszArgThis;
+ PCRTGETOPTDEF pOpt;
+
+ if (pState->pszNextShort)
+ {
+ /*
+ * We've got short options left over from the previous call.
+ */
+ pOpt = rtGetOptSearchShort(*pState->pszNextShort, pState->paOptions, pState->cOptions, pState->fFlags);
+ if (!pOpt)
+ {
+ pValueUnion->psz = pState->pszNextShort;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+ pState->pszNextShort++;
+ pszArgThis = pState->pszNextShort - 2;
+ iThis = pState->iNext;
+ fShort = true;
+ }
+ else
+ {
+ /*
+ * Pop off the next argument. Sorting options and dealing with the
+ * dash-dash makes this a little extra complicated.
+ */
+ for (;;)
+ {
+ if (pState->iNext >= pState->argc)
+ return 0;
+
+ if (pState->cNonOptions)
+ {
+ if (pState->cNonOptions == INT32_MAX)
+ {
+ pValueUnion->psz = pState->argv[pState->iNext++];
+ return VINF_GETOPT_NOT_OPTION;
+ }
+
+ if (pState->iNext + pState->cNonOptions >= pState->argc)
+ {
+ pState->cNonOptions = INT32_MAX;
+ continue;
+ }
+ }
+
+ iThis = pState->iNext++;
+ pszArgThis = pState->argv[iThis + pState->cNonOptions];
+
+ /*
+ * Do a long option search first and then a short option one.
+ * This way we can make sure single dash long options doesn't
+ * get mixed up with short ones.
+ */
+ pOpt = rtGetOptSearchLong(pszArgThis, pState->paOptions, pState->cOptions, pState->fFlags);
+ if ( !pOpt
+ && pszArgThis[0] == '-'
+ && pszArgThis[1] != '-'
+ && pszArgThis[1] != '\0')
+ {
+ pOpt = rtGetOptSearchShort(pszArgThis[1], pState->paOptions, pState->cOptions, pState->fFlags);
+ fShort = pOpt != NULL;
+ }
+ else
+ fShort = false;
+
+ /* Look for dash-dash. */
+ if (!pOpt && !strcmp(pszArgThis, "--"))
+ {
+ rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
+ pState->cNonOptions = INT32_MAX;
+ continue;
+ }
+
+ /* Options first hacks. */
+ if (pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST)
+ {
+ if (pOpt)
+ rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
+ else if (*pszArgThis == '-')
+ {
+ pValueUnion->psz = pszArgThis;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+ else
+ {
+ /* not an option, add it to the non-options and try again. */
+ pState->iNext--;
+ pState->cNonOptions++;
+
+ /* Switch to returning non-options if we've reached the end. */
+ if (pState->iNext + pState->cNonOptions >= pState->argc)
+ pState->cNonOptions = INT32_MAX;
+ continue;
+ }
+ }
+
+ /* done */
+ break;
+ }
+ }
+
+ if (pOpt)
+ {
+ pValueUnion->pDef = pOpt; /* in case of no value or error. */
+
+ uint32_t const fOptFlags = pOpt->fFlags;
+ if ((fOptFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING)
+ {
+ /*
+ * Find the argument value.
+ *
+ * A value is required with the argument. We're trying to be
+ * understanding here and will permit any of the following:
+ * -svalue, -s value, -s:value and -s=value
+ * (Ditto for long options.)
+ */
+ const char *pszValue;
+ if (fShort)
+ {
+ if (pszArgThis[2] == '\0')
+ {
+ if (iThis + 1 >= pState->argc)
+ return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
+ pszValue = pState->argv[iThis + pState->cNonOptions + 1];
+ rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
+ pState->iNext++;
+ }
+ else /* same argument. */
+ pszValue = &pszArgThis[2 + (pszArgThis[2] == ':' || pszArgThis[2] == '=')];
+ if (pState->pszNextShort)
+ {
+ pState->pszNextShort = NULL;
+ pState->iNext++;
+ }
+ }
+ else
+ {
+ size_t cchLong = strlen(pOpt->pszLong);
+ if (fOptFlags & RTGETOPT_FLAG_INDEX)
+ {
+ if ( pszArgThis[cchLong] != '\0'
+ || (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK))
+ {
+ if ( (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
+ && pszArgThis[cchLong] == '-')
+ cchLong++;
+
+ uint32_t uIndex;
+ char *pszRet = NULL;
+ int rc = RTStrToUInt32Ex(&pszArgThis[cchLong], &pszRet, 10, &uIndex);
+ if ( rc == VERR_NO_DIGITS
+ && (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK))
+ {
+ uIndex = ((fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK) >> RTGETOPT_FLAG_INDEX_DEF_SHIFT) - 1;
+ rc = pszRet[0] == '\0' ? VINF_SUCCESS : VWRN_TRAILING_CHARS;
+ }
+ if (rc == VWRN_TRAILING_CHARS)
+ {
+ if ( pszRet[0] != ':'
+ && pszRet[0] != '=')
+ return VERR_GETOPT_INVALID_ARGUMENT_FORMAT;
+ pState->uIndex = uIndex;
+ pszValue = pszRet + 1;
+ }
+ else if (rc == VINF_SUCCESS)
+ {
+ if (iThis + 1 + pState->cNonOptions >= pState->argc)
+ return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
+ pState->uIndex = uIndex;
+ pszValue = pState->argv[iThis + pState->cNonOptions + 1];
+ rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
+ pState->iNext++;
+ }
+ else
+ AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
+ }
+ else
+ return VERR_GETOPT_INDEX_MISSING;
+ }
+ else
+ {
+ if (pszArgThis[cchLong] == '\0')
+ {
+ if (iThis + 1 + pState->cNonOptions >= pState->argc)
+ return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
+ pszValue = pState->argv[iThis + pState->cNonOptions + 1];
+ rtGetOptMoveArgvEntries(&pState->argv[iThis + 1], &pState->argv[iThis + pState->cNonOptions + 1]);
+ pState->iNext++;
+ }
+ else /* same argument. */
+ pszValue = &pszArgThis[cchLong + 1];
+ }
+ }
+
+ /*
+ * Set up the ValueUnion.
+ */
+ int rc = rtGetOptProcessValue(fOptFlags, pszValue, pValueUnion);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else if (fShort)
+ {
+ /*
+ * Deal with "compressed" short option lists, correcting the next
+ * state variables for the start and end cases.
+ */
+ if (pszArgThis[2])
+ {
+ if (!pState->pszNextShort)
+ {
+ /* start */
+ pState->pszNextShort = &pszArgThis[2];
+ pState->iNext--;
+ }
+ }
+ else if (pState->pszNextShort)
+ {
+ /* end */
+ pState->pszNextShort = NULL;
+ pState->iNext++;
+ }
+ }
+ else if (fOptFlags & RTGETOPT_FLAG_INDEX)
+ {
+ size_t cchLong = strlen(pOpt->pszLong);
+ uint32_t uIndex;
+ if (pszArgThis[cchLong] != '\0')
+ {
+ if ( (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_DASH)
+ && pszArgThis[cchLong] == '-')
+ cchLong++;
+ if (RTStrToUInt32Full(&pszArgThis[cchLong], 10, &uIndex) == VINF_SUCCESS)
+ pState->uIndex = uIndex;
+ else
+ AssertMsgFailedReturn(("%s\n", pszArgThis), VERR_GETOPT_INVALID_ARGUMENT_FORMAT); /* search bug */
+ }
+ else if (fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK)
+ uIndex = ((fOptFlags & RTGETOPT_FLAG_INDEX_DEF_MASK) >> RTGETOPT_FLAG_INDEX_DEF_SHIFT) - 1;
+ else
+ return VERR_GETOPT_INDEX_MISSING;
+ }
+
+ pState->pDef = pOpt;
+ return pOpt->iShort;
+ }
+
+ /*
+ * Not a known option argument. If it starts with a switch char (-) we'll
+ * fail with unknown option, and if it doesn't we'll return it as a non-option.
+ */
+ if (*pszArgThis == '-')
+ {
+ pValueUnion->psz = pszArgThis;
+ return VERR_GETOPT_UNKNOWN_OPTION;
+ }
+
+ pValueUnion->psz = pszArgThis;
+ return VINF_GETOPT_NOT_OPTION;
+}
+RT_EXPORT_SYMBOL(RTGetOpt);
+
+
+RTDECL(int) RTGetOptFetchValue(PRTGETOPTSTATE pState, PRTGETOPTUNION pValueUnion, uint32_t fFlags)
+{
+ /*
+ * Validate input.
+ */
+ PCRTGETOPTDEF pOpt = pState->pDef;
+ AssertReturn(!(fFlags & ~RTGETOPT_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertReturn((fFlags & RTGETOPT_REQ_MASK) != RTGETOPT_REQ_NOTHING, VERR_INVALID_PARAMETER);
+
+ /*
+ * Make sure the union is completely cleared out, whatever happens below.
+ */
+ pValueUnion->u64 = 0;
+ pValueUnion->pDef = NULL;
+
+ /*
+ * Pop off the next argument and convert it into a value union.
+ */
+ if (pState->iNext >= pState->argc)
+ return VERR_GETOPT_REQUIRED_ARGUMENT_MISSING;
+ int iThis = pState->iNext++;
+ const char *pszValue = pState->argv[iThis + (pState->cNonOptions != INT32_MAX ? pState->cNonOptions : 0)];
+ pValueUnion->pDef = pOpt; /* in case of no value or error. */
+
+ if (pState->cNonOptions && pState->cNonOptions != INT32_MAX)
+ rtGetOptMoveArgvEntries(&pState->argv[iThis], &pState->argv[iThis + pState->cNonOptions]);
+
+ return rtGetOptProcessValue(fFlags, pszValue, pValueUnion);
+}
+RT_EXPORT_SYMBOL(RTGetOptFetchValue);
+
+
+RTDECL(char **) RTGetOptNonOptionArrayPtr(PRTGETOPTSTATE pState)
+{
+ AssertReturn(pState->fFlags & RTGETOPTINIT_FLAGS_OPTS_FIRST, NULL);
+ return &pState->argv[pState->iNext - 1];
+}
+RT_EXPORT_SYMBOL(RTGetOptNonOptionArrayPtr);
+
+
+RTDECL(RTEXITCODE) RTGetOptPrintError(int ch, PCRTGETOPTUNION pValueUnion)
+{
+ if (ch == VINF_GETOPT_NOT_OPTION)
+ RTMsgError("Invalid parameter: %s", pValueUnion->psz);
+ else if (ch > 0)
+ {
+ if (RT_C_IS_GRAPH(ch))
+ RTMsgError("Unhandled option: -%c", ch);
+ else
+ RTMsgError("Unhandled option: %i (%#x)", ch, ch);
+ }
+ else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
+ RTMsgError("Unknown option: '%s'", pValueUnion->psz);
+ else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
+ /** @todo r=klaus not really ideal, as the value isn't available */
+ RTMsgError("The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
+ else if (pValueUnion->pDef)
+ RTMsgError("%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
+ else
+ RTMsgError("%Rrs\n", ch);
+
+ return RTEXITCODE_SYNTAX;
+}
+RT_EXPORT_SYMBOL(RTGetOptPrintError);
+
+
+RTDECL(ssize_t) RTGetOptFormatError(char *pszBuf, size_t cbBuf, int ch, PCRTGETOPTUNION pValueUnion)
+{
+ ssize_t cchRet;
+ if (ch == VINF_GETOPT_NOT_OPTION)
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "Invalid parameter: %s", pValueUnion->psz);
+ else if (ch > 0)
+ {
+ if (RT_C_IS_GRAPH(ch))
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: -%c", ch);
+ else
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unhandled option: %i (%#x)", ch, ch);
+ }
+ else if (ch == VERR_GETOPT_UNKNOWN_OPTION)
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "Unknown option: '%s'", pValueUnion->psz);
+ else if (pValueUnion->pDef && ch == VERR_GETOPT_INVALID_ARGUMENT_FORMAT)
+ /** @todo r=klaus not really ideal, as the value isn't available */
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "The value given '%s' has an invalid format.", pValueUnion->pDef->pszLong);
+ else if (pValueUnion->pDef)
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "%s: %Rrs\n", pValueUnion->pDef->pszLong, ch);
+ else
+ cchRet = RTStrPrintf2(pszBuf, cbBuf, "%Rrs\n", ch);
+
+ return cchRet;
+}
+RT_EXPORT_SYMBOL(RTGetOptFormatError);
+
diff --git a/src/VBox/Runtime/common/misc/getoptargv.cpp b/src/VBox/Runtime/common/misc/getoptargv.cpp
new file mode 100644
index 00000000..9a84507a
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/getoptargv.cpp
@@ -0,0 +1,654 @@
+/* $Id: getoptargv.cpp $ */
+/** @file
+ * IPRT - Command Line Parsing, Argument Vector.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/getopt.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/**
+ * Array indexed by the quoting type and 7-bit ASCII character.
+ *
+ * We include some extra stuff here that the corresponding shell would normally
+ * require quoting of.
+ */
+static uint8_t
+#ifndef IPRT_REGENERATE_QUOTE_CHARS
+const
+#endif
+g_abmQuoteChars[RTGETOPTARGV_CNV_QUOTE_MASK + 1][16] =
+{
+ { 0xfe, 0xff, 0xff, 0xff, 0x65, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 },
+ { 0xfe, 0xff, 0xff, 0xff, 0xd7, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x50 },
+};
+
+
+#ifdef IPRT_REGENERATE_QUOTE_CHARS /* To re-generate the bitmaps. */
+# include <stdio.h>
+int main()
+{
+ RT_ZERO(g_abmQuoteChars);
+
+# define SET_ALL(ch) \
+ do { \
+ for (size_t iType = 0; iType <= RTGETOPTARGV_CNV_QUOTE_MASK; iType++) \
+ ASMBitSet(&g_abmQuoteChars[iType], (ch)); \
+ } while (0)
+# define SET(ConstSuffix, ch) \
+ do { \
+ ASMBitSet(&g_abmQuoteChars[RTGETOPTARGV_CNV_QUOTE_##ConstSuffix], (ch)); \
+ printf(#ConstSuffix ": %#x %d %c\n", (ch), (ch), (ch)); \
+ } while (0)
+
+ /* just flag all the control chars as in need of quoting. */
+ for (char ch = 1; ch < 0x20; ch++)
+ SET_ALL(ch);
+
+ /* ... and space of course */
+ SET_ALL(' ');
+
+ /* MS CRT / CMD.EXE: */
+ SET(MS_CRT, '"');
+ SET(MS_CRT, '&');
+ SET(MS_CRT, '>');
+ SET(MS_CRT, '<');
+ SET(MS_CRT, '|');
+ SET(MS_CRT, '%');
+
+ /* Bourne shell: */
+ SET(BOURNE_SH, '!');
+ SET(BOURNE_SH, '"');
+ SET(BOURNE_SH, '$');
+ SET(BOURNE_SH, '&');
+ SET(BOURNE_SH, '(');
+ SET(BOURNE_SH, ')');
+ SET(BOURNE_SH, '*');
+ SET(BOURNE_SH, ';');
+ SET(BOURNE_SH, '<');
+ SET(BOURNE_SH, '>');
+ SET(BOURNE_SH, '?');
+ SET(BOURNE_SH, '[');
+ SET(BOURNE_SH, '\'');
+ SET(BOURNE_SH, '\\');
+ SET(BOURNE_SH, '`');
+ SET(BOURNE_SH, '|');
+ SET(BOURNE_SH, '~');
+
+ for (size_t iType = 0; iType <= RTGETOPTARGV_CNV_QUOTE_MASK; iType++)
+ {
+ printf(" {");
+ for (size_t iByte = 0; iByte < 16; iByte++)
+ printf(iByte == 0 ? " 0x%02x" : ", 0x%02x", g_abmQuoteChars[iType][iByte]);
+ printf(" },\n");
+ }
+ return 0;
+}
+
+#else /* !IPRT_REGENERATE_QUOTE_CHARS */
+
+/**
+ * Look for an unicode code point in the separator string.
+ *
+ * @returns true if it's a separator, false if it isn't.
+ * @param Cp The code point.
+ * @param pszSeparators The separators.
+ */
+static bool rtGetOptIsUniCpInString(RTUNICP Cp, const char *pszSeparators)
+{
+ /* This could be done in a more optimal fashion. Probably worth a
+ separate RTStr function at some point. */
+ for (;;)
+ {
+ RTUNICP CpSep;
+ int rc = RTStrGetCpEx(&pszSeparators, &CpSep);
+ AssertRCReturn(rc, false);
+ if (CpSep == Cp)
+ return true;
+ if (!CpSep)
+ return false;
+ }
+}
+
+
+/**
+ * Look for an 7-bit ASCII character in the separator string.
+ *
+ * @returns true if it's a separator, false if it isn't.
+ * @param ch The character.
+ * @param pszSeparators The separators.
+ * @param cchSeparators The number of separators chars.
+ */
+DECLINLINE(bool) rtGetOptIsAsciiInSet(char ch, const char *pszSeparators, size_t cchSeparators)
+{
+ switch (cchSeparators)
+ {
+ case 8: if (ch == pszSeparators[7]) return true; RT_FALL_THRU();
+ case 7: if (ch == pszSeparators[6]) return true; RT_FALL_THRU();
+ case 6: if (ch == pszSeparators[5]) return true; RT_FALL_THRU();
+ case 5: if (ch == pszSeparators[4]) return true; RT_FALL_THRU();
+ case 4: if (ch == pszSeparators[3]) return true; RT_FALL_THRU();
+ case 3: if (ch == pszSeparators[2]) return true; RT_FALL_THRU();
+ case 2: if (ch == pszSeparators[1]) return true; RT_FALL_THRU();
+ case 1: if (ch == pszSeparators[0]) return true;
+ return false;
+ default:
+ return memchr(pszSeparators, ch, cchSeparators) != NULL;
+ }
+}
+
+
+/**
+ * Checks if the character is in the set of separators
+ *
+ * @returns true if it is, false if it isn't.
+ *
+ * @param Cp The code point.
+ * @param pszSeparators The separators.
+ * @param cchSeparators The length of @a pszSeparators.
+ */
+DECL_FORCE_INLINE(bool) rtGetOptIsCpInSet(RTUNICP Cp, const char *pszSeparators, size_t cchSeparators)
+{
+ if (RT_LIKELY(Cp <= 127))
+ return rtGetOptIsAsciiInSet((char)Cp, pszSeparators, cchSeparators);
+ return rtGetOptIsUniCpInString(Cp, pszSeparators);
+}
+
+
+/**
+ * Skips any delimiters at the start of the string that is pointed to.
+ *
+ * @returns VINF_SUCCESS or RTStrGetCpEx status code.
+ * @param ppszSrc Where to get and return the string pointer.
+ * @param pszSeparators The separators.
+ * @param cchSeparators The length of @a pszSeparators.
+ */
+static int rtGetOptSkipDelimiters(const char **ppszSrc, const char *pszSeparators, size_t cchSeparators)
+{
+ const char *pszSrc = *ppszSrc;
+ const char *pszRet;
+ for (;;)
+ {
+ pszRet = pszSrc;
+ RTUNICP Cp;
+ int rc = RTStrGetCpEx(&pszSrc, &Cp);
+ if (RT_FAILURE(rc))
+ {
+ *ppszSrc = pszRet;
+ return rc;
+ }
+ if ( !Cp
+ || !rtGetOptIsCpInSet(Cp, pszSeparators, cchSeparators))
+ break;
+ }
+
+ *ppszSrc = pszRet;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTGetOptArgvFromString(char ***ppapszArgv, int *pcArgs, const char *pszCmdLine,
+ uint32_t fFlags, const char *pszSeparators)
+{
+ /*
+ * Some input validation.
+ */
+ AssertPtr(pszCmdLine);
+ AssertPtr(pcArgs);
+ AssertPtr(ppapszArgv);
+ AssertReturn( (fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_BOURNE_SH
+ || (fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_MS_CRT, VERR_INVALID_FLAGS);
+ AssertReturn(~(fFlags & ~RTGETOPTARGV_CNV_VALID_MASK), VERR_INVALID_FLAGS);
+
+ if (!pszSeparators)
+ pszSeparators = " \t\n\r";
+ else
+ AssertPtr(pszSeparators);
+ size_t const cchSeparators = strlen(pszSeparators);
+ AssertReturn(cchSeparators > 0, VERR_INVALID_PARAMETER);
+
+ /*
+ * Parse the command line and chop off it into argv individual argv strings.
+ */
+ const char *pszSrc = pszCmdLine;
+ char *pszDup = NULL;
+ char *pszDst;
+ if (fFlags & RTGETOPTARGV_CNV_MODIFY_INPUT)
+ pszDst = (char *)pszCmdLine;
+ else
+ {
+ pszDst = pszDup = (char *)RTMemAlloc(strlen(pszSrc) + 1);
+ if (!pszDup)
+ return VERR_NO_STR_MEMORY;
+ }
+ int rc = VINF_SUCCESS;
+ char **papszArgs = NULL;
+ unsigned iArg = 0;
+ while (*pszSrc)
+ {
+ /* Skip stuff */
+ rc = rtGetOptSkipDelimiters(&pszSrc, pszSeparators, cchSeparators);
+ if (RT_FAILURE(rc))
+ break;
+ if (!*pszSrc)
+ break;
+
+ /* Start a new entry. */
+ if ((iArg % 32) == 0)
+ {
+ void *pvNew = RTMemRealloc(papszArgs, (iArg + 33) * sizeof(char *));
+ if (!pvNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ papszArgs = (char **)pvNew;
+ }
+ papszArgs[iArg++] = pszDst;
+
+ /*
+ * Parse and copy the string over.
+ */
+ RTUNICP uc;
+ if ((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_BOURNE_SH)
+ {
+ /*
+ * Bourne shell style.
+ */
+ RTUNICP ucQuote = 0;
+ for (;;)
+ {
+ rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ if (!ucQuote)
+ {
+ if (uc == '"' || uc == '\'')
+ ucQuote = uc;
+ else if (rtGetOptIsCpInSet(uc, pszSeparators, cchSeparators))
+ break;
+ else if (uc != '\\')
+ pszDst = RTStrPutCp(pszDst, uc);
+ else
+ {
+ /* escaped char */
+ rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ pszDst = RTStrPutCp(pszDst, uc);
+ }
+ }
+ else if (ucQuote != uc)
+ {
+ if (uc != '\\' || ucQuote == '\'')
+ pszDst = RTStrPutCp(pszDst, uc);
+ else
+ {
+ /* escaped char */
+ rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ if ( uc != '"'
+ && uc != '\\'
+ && uc != '`'
+ && uc != '$'
+ && uc != '\n')
+ pszDst = RTStrPutCp(pszDst, ucQuote);
+ pszDst = RTStrPutCp(pszDst, uc);
+ }
+ }
+ else
+ ucQuote = 0;
+ }
+ }
+ else
+ {
+ /*
+ * Microsoft CRT style.
+ */
+ Assert((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_MS_CRT);
+ bool fInQuote = false;
+ for (;;)
+ {
+ rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ if (uc == '"')
+ {
+ /* Two double quotes insides a quoted string in an escape
+ sequence and we output one double quote char.
+ See http://www.daviddeley.com/autohotkey/parameters/parameters.htm */
+ if (!fInQuote)
+ fInQuote = true;
+ else if (*pszSrc != '"')
+ fInQuote = false;
+ else
+ {
+ pszDst = RTStrPutCp(pszDst, '"');
+ pszSrc++;
+ }
+ }
+ else if (!fInQuote && rtGetOptIsCpInSet(uc, pszSeparators, cchSeparators))
+ break;
+ else if (uc != '\\')
+ pszDst = RTStrPutCp(pszDst, uc);
+ else
+ {
+ /* A backslash sequence is only relevant if followed by
+ a double quote, then it will work like an escape char. */
+ size_t cSlashes = 1;
+ while (*pszSrc == '\\')
+ {
+ cSlashes++;
+ pszSrc++;
+ }
+ if (*pszSrc != '"')
+ /* Not an escape sequence. */
+ while (cSlashes-- > 0)
+ pszDst = RTStrPutCp(pszDst, '\\');
+ else
+ {
+ /* Escape sequence. Output half of the slashes. If odd
+ number, output the escaped double quote . */
+ while (cSlashes >= 2)
+ {
+ pszDst = RTStrPutCp(pszDst, '\\');
+ cSlashes -= 2;
+ }
+ if (cSlashes)
+ {
+ pszDst = RTStrPutCp(pszDst, '"');
+ pszSrc++;
+ }
+ }
+ }
+ }
+ }
+
+ *pszDst++ = '\0';
+ if (RT_FAILURE(rc) || !uc)
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pszDup);
+ RTMemFree(papszArgs);
+ return rc;
+ }
+
+ /*
+ * Terminate the array.
+ * Check for empty string to make sure we've got an array.
+ */
+ if (iArg == 0)
+ {
+ RTMemFree(pszDup);
+ papszArgs = (char **)RTMemAlloc(1 * sizeof(char *));
+ if (!papszArgs)
+ return VERR_NO_MEMORY;
+ }
+ papszArgs[iArg] = NULL;
+
+ *pcArgs = iArg;
+ *ppapszArgv = papszArgs;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTGetOptArgvFree(char **papszArgv)
+{
+ RTGetOptArgvFreeEx(papszArgv, 0);
+}
+
+
+RTDECL(void) RTGetOptArgvFreeEx(char **papszArgv, uint32_t fFlags)
+{
+ Assert(~(fFlags & ~RTGETOPTARGV_CNV_VALID_MASK));
+ if (papszArgv)
+ {
+ /*
+ * We've really only _two_ allocations here. Check the code in
+ * RTGetOptArgvFromString for the particulars.
+ */
+ if (!(fFlags & RTGETOPTARGV_CNV_MODIFY_INPUT))
+ RTMemFree(papszArgv[0]);
+ RTMemFree(papszArgv);
+ }
+}
+
+
+/**
+ * Checks if the argument needs quoting or not.
+ *
+ * @returns true if it needs, false if it don't.
+ * @param pszArg The argument.
+ * @param fFlags Quoting style.
+ * @param pcch Where to store the argument length when quoting
+ * is not required. (optimization)
+ */
+DECLINLINE(bool) rtGetOpArgvRequiresQuoting(const char *pszArg, uint32_t fFlags, size_t *pcch)
+{
+ if ((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) != RTGETOPTARGV_CNV_UNQUOTED)
+ {
+ char const *psz = pszArg;
+ unsigned char ch;
+ while ((ch = (unsigned char)*psz))
+ {
+ if ( ch < 128
+ && ASMBitTest(&g_abmQuoteChars[fFlags & RTGETOPTARGV_CNV_QUOTE_MASK], ch))
+ return true;
+ psz++;
+ }
+
+ *pcch = psz - pszArg;
+ }
+ else
+ *pcch = strlen(pszArg);
+ return false;
+}
+
+
+/**
+ * Grows the command line string buffer.
+ *
+ * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY.
+ * @param ppszCmdLine Pointer to the command line string pointer.
+ * @param pcbCmdLineAlloc Pointer to the allocation length variable.
+ * @param cchMin The minimum size to grow with, kind of.
+ */
+static int rtGetOptArgvToStringGrow(char **ppszCmdLine, size_t *pcbCmdLineAlloc, size_t cchMin)
+{
+ size_t cb = *pcbCmdLineAlloc;
+ while (cb < cchMin)
+ cb *= 2;
+ cb *= 2;
+ *pcbCmdLineAlloc = cb;
+ return RTStrRealloc(ppszCmdLine, cb);
+}
+
+/**
+ * Checks if we have a sequence of DOS slashes followed by a double quote char.
+ *
+ * @returns true / false accordingly.
+ * @param psz The string.
+ */
+DECLINLINE(bool) rtGetOptArgvMsCrtIsSlashQuote(const char *psz)
+{
+ while (*psz == '\\')
+ psz++;
+ return *psz == '"' || *psz == '\0';
+}
+
+
+RTDECL(int) RTGetOptArgvToString(char **ppszCmdLine, const char * const *papszArgv, uint32_t fFlags)
+{
+ AssertReturn((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) <= RTGETOPTARGV_CNV_UNQUOTED, VERR_INVALID_FLAGS);
+ AssertReturn(!(fFlags & (~RTGETOPTARGV_CNV_VALID_MASK | RTGETOPTARGV_CNV_MODIFY_INPUT)), VERR_INVALID_FLAGS);
+
+#define PUT_CH(ch) \
+ if (RT_UNLIKELY(off + 1 >= cbCmdLineAlloc)) { \
+ rc = rtGetOptArgvToStringGrow(&pszCmdLine, &cbCmdLineAlloc, 1); \
+ if (RT_FAILURE(rc)) \
+ break; \
+ } \
+ pszCmdLine[off++] = (ch)
+
+#define PUT_PSZ(psz, cch) \
+ if (RT_UNLIKELY(off + (cch) >= cbCmdLineAlloc)) { \
+ rc = rtGetOptArgvToStringGrow(&pszCmdLine, &cbCmdLineAlloc, (cch)); \
+ if (RT_FAILURE(rc)) \
+ break; \
+ } \
+ memcpy(&pszCmdLine[off], (psz), (cch)); \
+ off += (cch);
+#define PUT_SZ(sz) PUT_PSZ(sz, sizeof(sz) - 1)
+
+ /*
+ * Take the realloc approach, it requires less code and is probably more
+ * efficient than figuring out the size first.
+ */
+ int rc = VINF_SUCCESS;
+ size_t off = 0;
+ size_t cbCmdLineAlloc = 256;
+ char *pszCmdLine = RTStrAlloc(256);
+ if (!pszCmdLine)
+ return VERR_NO_STR_MEMORY;
+
+ for (size_t i = 0; papszArgv[i]; i++)
+ {
+ if (i > 0)
+ {
+ PUT_CH(' ');
+ }
+
+ /* does it need quoting? */
+ const char *pszArg = papszArgv[i];
+ size_t cchArg;
+ if (!rtGetOpArgvRequiresQuoting(pszArg, fFlags, &cchArg))
+ {
+ /* No quoting needed, just append the argument. */
+ PUT_PSZ(pszArg, cchArg);
+ }
+ else if ((fFlags & RTGETOPTARGV_CNV_QUOTE_MASK) == RTGETOPTARGV_CNV_QUOTE_MS_CRT)
+ {
+ /*
+ * Microsoft CRT quoting. Quote the whole argument in double
+ * quotes to make it easier to read and code.
+ */
+ PUT_CH('"');
+ char ch;
+ while ((ch = *pszArg++))
+ {
+ if ( ch == '\\'
+ && rtGetOptArgvMsCrtIsSlashQuote(pszArg))
+ {
+ PUT_SZ("\\\\");
+ }
+ else if (ch == '"')
+ {
+ PUT_SZ("\\\"");
+ }
+ else
+ {
+ PUT_CH(ch);
+ }
+ }
+ PUT_CH('"');
+ }
+ else
+ {
+ /*
+ * Bourne Shell quoting. Quote the whole thing in single quotes
+ * and use double quotes for any single quote chars.
+ */
+ PUT_CH('\'');
+ char ch;
+ while ((ch = *pszArg++))
+ {
+ if (ch == '\'')
+ {
+ PUT_SZ("'\"'\"'");
+ }
+ else
+ {
+ PUT_CH(ch);
+ }
+ }
+ PUT_CH('\'');
+ }
+ }
+
+ /* Set return value / cleanup. */
+ if (RT_SUCCESS(rc))
+ {
+ pszCmdLine[off] = '\0';
+ *ppszCmdLine = pszCmdLine;
+ }
+ else
+ RTStrFree(pszCmdLine);
+#undef PUT_SZ
+#undef PUT_PSZ
+#undef PUT_CH
+ return rc;
+}
+
+
+RTDECL(int) RTGetOptArgvToUtf16String(PRTUTF16 *ppwszCmdLine, const char * const *papszArgv, uint32_t fFlags)
+{
+ char *pszCmdLine;
+ int rc = RTGetOptArgvToString(&pszCmdLine, papszArgv, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrToUtf16(pszCmdLine, ppwszCmdLine);
+ RTStrFree(pszCmdLine);
+ }
+ return rc;
+}
+
+#endif /* !IPRT_REGENERATE_QUOTE_CHARS */
+
diff --git a/src/VBox/Runtime/common/misc/handle.cpp b/src/VBox/Runtime/common/misc/handle.cpp
new file mode 100644
index 00000000..b73a3c60
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handle.cpp
@@ -0,0 +1,85 @@
+/* $Id: handle.cpp $ */
+/** @file
+ * IPRT - Generic Handle Manipulation.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/handle.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/pipe.h>
+
+
+RTDECL(int) RTHandleClose(PRTHANDLE ph)
+{
+ int rc = VINF_SUCCESS;
+ if (ph)
+ {
+ switch (ph->enmType)
+ {
+ case RTHANDLETYPE_FILE:
+ rc = RTFileClose(ph->u.hFile);
+ ph->u.hFile = NIL_RTFILE;
+ break;
+
+ case RTHANDLETYPE_PIPE:
+ rc = RTPipeClose(ph->u.hPipe);
+ ph->u.hPipe = NIL_RTPIPE;
+ break;
+
+ case RTHANDLETYPE_SOCKET:
+ AssertMsgFailed(("Socket not supported\n"));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+
+ case RTHANDLETYPE_THREAD:
+ AssertMsgFailed(("Thread not supported\n"));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid type %d\n", ph->enmType));
+ rc = VERR_INVALID_PARAMETER;
+ break;
+ }
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/misc/handletable.cpp b/src/VBox/Runtime/common/misc/handletable.cpp
new file mode 100644
index 00000000..2da55a7f
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handletable.cpp
@@ -0,0 +1,234 @@
+/* $Id: handletable.cpp $ */
+/** @file
+ * IPRT - Handle Tables.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/handletable.h>
+#include "internal/iprt.h"
+
+#include <iprt/mem.h>
+#include <iprt/spinlock.h>
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include "internal/magics.h"
+#include "handletable.h"
+
+
+
+RTDECL(int) RTHandleTableCreateEx(PRTHANDLETABLE phHandleTable, uint32_t fFlags, uint32_t uBase, uint32_t cMax,
+ PFNRTHANDLETABLERETAIN pfnRetain, void *pvUser)
+{
+ PRTHANDLETABLEINT pThis;
+ uint32_t cLevel1;
+ size_t cb;
+
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(phHandleTable, VERR_INVALID_POINTER);
+ *phHandleTable = NIL_RTHANDLETABLE;
+ AssertPtrNullReturn(pfnRetain, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTHANDLETABLE_FLAGS_MASK), VERR_INVALID_PARAMETER);
+ AssertReturn(RT_BOOL(fFlags & RTHANDLETABLE_FLAGS_LOCKED) + RT_BOOL(fFlags & RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE) < 2,
+ VERR_INVALID_PARAMETER);
+ AssertReturn(cMax > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(UINT32_MAX - cMax >= uBase, VERR_INVALID_PARAMETER);
+
+ /*
+ * Adjust the cMax value so it is a multiple of the 2nd level tables.
+ */
+ if (cMax >= UINT32_MAX - RTHT_LEVEL2_ENTRIES)
+ cMax = UINT32_MAX - RTHT_LEVEL2_ENTRIES + 1;
+ cMax = ((cMax + RTHT_LEVEL2_ENTRIES - 1) / RTHT_LEVEL2_ENTRIES) * RTHT_LEVEL2_ENTRIES;
+
+ cLevel1 = cMax / RTHT_LEVEL2_ENTRIES;
+ Assert(cLevel1 * RTHT_LEVEL2_ENTRIES == cMax);
+
+ /*
+ * Allocate the structure, include the 1st level lookup table
+ * if it's below the threshold size.
+ */
+ cb = sizeof(RTHANDLETABLEINT);
+ if (cLevel1 < RTHT_LEVEL1_DYN_ALLOC_THRESHOLD)
+ cb = RT_ALIGN(cb, sizeof(void *)) + cLevel1 * sizeof(void *);
+ pThis = (PRTHANDLETABLEINT)RTMemAllocZ(cb);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Initialize it.
+ */
+ pThis->u32Magic = RTHANDLETABLE_MAGIC;
+ pThis->fFlags = fFlags;
+ pThis->uBase = uBase;
+ pThis->cCur = 0;
+ pThis->hSpinlock = NIL_RTSPINLOCK;
+ if (cLevel1 < RTHT_LEVEL1_DYN_ALLOC_THRESHOLD)
+ pThis->papvLevel1 = (void **)((uint8_t *)pThis + RT_ALIGN(sizeof(*pThis), sizeof(void *)));
+ else
+ pThis->papvLevel1 = NULL;
+ pThis->pfnRetain = pfnRetain;
+ pThis->pvRetainUser = pvUser;
+ pThis->cMax = cMax;
+ pThis->cCurAllocated = 0;
+ pThis->cLevel1 = cLevel1 < RTHT_LEVEL1_DYN_ALLOC_THRESHOLD ? cLevel1 : 0;
+ pThis->iFreeHead = NIL_RTHT_INDEX;
+ pThis->iFreeTail = NIL_RTHT_INDEX;
+ if (fFlags & (RTHANDLETABLE_FLAGS_LOCKED | RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE))
+ {
+ int rc;
+ if (fFlags & RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE)
+ rc = RTSpinlockCreate(&pThis->hSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "RTHandleTableCreateEx");
+ else
+ rc = RTSpinlockCreate(&pThis->hSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_UNSAFE, "RTHandleTableCreateEx");
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+ }
+
+ *phHandleTable = pThis;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTHandleTableCreateEx);
+
+
+RTDECL(int) RTHandleTableCreate(PRTHANDLETABLE phHandleTable)
+{
+ return RTHandleTableCreateEx(phHandleTable, RTHANDLETABLE_FLAGS_LOCKED, 1, 65534, NULL, NULL);
+}
+RT_EXPORT_SYMBOL(RTHandleTableCreate);
+
+
+RTDECL(int) RTHandleTableDestroy(RTHANDLETABLE hHandleTable, PFNRTHANDLETABLEDELETE pfnDelete, void *pvUser)
+{
+ PRTHANDLETABLEINT pThis;
+ uint32_t i1;
+ uint32_t i;
+
+ /*
+ * Validate input, quietly ignore the NIL handle.
+ */
+ if (hHandleTable == NIL_RTHANDLETABLE)
+ return VINF_SUCCESS;
+ pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pfnDelete, VERR_INVALID_POINTER);
+
+ /*
+ * Mark the thing as invalid / deleted.
+ * Then kill the lock.
+ */
+ rtHandleTableLock(pThis);
+ ASMAtomicWriteU32(&pThis->u32Magic, ~RTHANDLETABLE_MAGIC);
+ rtHandleTableUnlock(pThis);
+
+ if (pThis->hSpinlock != NIL_RTSPINLOCK)
+ {
+ rtHandleTableLock(pThis);
+ rtHandleTableUnlock(pThis);
+
+ RTSpinlockDestroy(pThis->hSpinlock);
+ pThis->hSpinlock = NIL_RTSPINLOCK;
+ }
+
+ if (pfnDelete)
+ {
+ /*
+ * Walk all the tables looking for used handles.
+ */
+ uint32_t cLeft = pThis->cCurAllocated;
+ if (pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT)
+ {
+ for (i1 = 0; cLeft > 0 && i1 < pThis->cLevel1; i1++)
+ {
+ PRTHTENTRYCTX paTable = (PRTHTENTRYCTX)pThis->papvLevel1[i1];
+ if (paTable)
+ for (i = 0; i < RTHT_LEVEL2_ENTRIES; i++)
+ if (!RTHT_IS_FREE(paTable[i].pvObj))
+ {
+ pfnDelete(hHandleTable, pThis->uBase + i + i1 * RTHT_LEVEL2_ENTRIES,
+ paTable[i].pvObj, paTable[i].pvCtx, pvUser);
+ Assert(cLeft > 0);
+ cLeft--;
+ }
+ }
+ }
+ else
+ {
+ for (i1 = 0; cLeft > 0 && i1 < pThis->cLevel1; i1++)
+ {
+ PRTHTENTRY paTable = (PRTHTENTRY)pThis->papvLevel1[i1];
+ if (paTable)
+ for (i = 0; i < RTHT_LEVEL2_ENTRIES; i++)
+ if (!RTHT_IS_FREE(paTable[i].pvObj))
+ {
+ pfnDelete(hHandleTable, pThis->uBase + i + i1 * RTHT_LEVEL2_ENTRIES,
+ paTable[i].pvObj, NULL, pvUser);
+ Assert(cLeft > 0);
+ cLeft--;
+ }
+ }
+ }
+ Assert(!cLeft);
+ }
+
+ /*
+ * Free the memory.
+ */
+ for (i1 = 0; i1 < pThis->cLevel1; i1++)
+ if (pThis->papvLevel1[i1])
+ {
+ RTMemFree(pThis->papvLevel1[i1]);
+ pThis->papvLevel1[i1] = NULL;
+ }
+
+ if (pThis->cMax / RTHT_LEVEL2_ENTRIES >= RTHT_LEVEL1_DYN_ALLOC_THRESHOLD)
+ RTMemFree(pThis->papvLevel1);
+
+ RTMemFree(pThis);
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTHandleTableDestroy);
+
diff --git a/src/VBox/Runtime/common/misc/handletable.h b/src/VBox/Runtime/common/misc/handletable.h
new file mode 100644
index 00000000..7ae0c38a
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handletable.h
@@ -0,0 +1,257 @@
+/* $Id: handletable.h $ */
+/** @file
+ * IPRT - Handle Tables, internal header.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_misc_handletable_h
+#define IPRT_INCLUDED_SRC_common_misc_handletable_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** The number of entries in the 2nd level lookup table. */
+#define RTHT_LEVEL2_ENTRIES 2048
+
+/** The number of (max) 1st level entries requiring dynamic allocation of the
+ * 1st level table. If the max number is below this threshold, the 1st level
+ * table will be allocated as part of the handle table structure. */
+#define RTHT_LEVEL1_DYN_ALLOC_THRESHOLD 256
+
+/** Checks whether a object pointer is really a free entry or not. */
+#define RTHT_IS_FREE(pvObj) ( ((uintptr_t)(pvObj) & 3) == 3 )
+
+/** Sets RTHTENTRYFREE::iNext. */
+#define RTHT_SET_FREE_IDX(pFree, idx) \
+ do { \
+ (pFree)->iNext = ((uintptr_t)((uint32_t)(idx)) << 2) | 3U; \
+ } while (0)
+
+/** Gets the index part of RTHTENTRYFREE::iNext. */
+#define RTHT_GET_FREE_IDX(pFree) ( (uint32_t)((pFree)->iNext >> 2) )
+
+/** @def NIL_RTHT_INDEX
+ * The NIL handle index for use in the free list. (The difference between
+ * 32-bit and 64-bit hosts here comes down to the shifting performed for
+ * RTHTENTRYFREE::iNext.) */
+#if ARCH_BITS == 32
+# define NIL_RTHT_INDEX ( UINT32_C(0x3fffffff) )
+#elif ARCH_BITS >= 34
+# define NIL_RTHT_INDEX ( UINT32_C(0xffffffff) )
+#else
+# error "Missing or unsupported ARCH_BITS."
+#endif
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+
+/**
+ * Handle table entry, simple variant.
+ */
+typedef struct RTHTENTRY
+{
+ /** The object. */
+ void *pvObj;
+} RTHTENTRY;
+/** Pointer to a handle table entry, simple variant. */
+typedef RTHTENTRY *PRTHTENTRY;
+
+
+/**
+ * Handle table entry, context variant.
+ */
+typedef struct RTHTENTRYCTX
+{
+ /** The object. */
+ void *pvObj;
+ /** The context. */
+ void *pvCtx;
+} RTHTENTRYCTX;
+/** Pointer to a handle table entry, context variant. */
+typedef RTHTENTRYCTX *PRTHTENTRYCTX;
+
+
+/**
+ * Free handle table entry, shared by all variants.
+ */
+typedef struct RTHTENTRYFREE
+{
+ /** The index of the next handle, special format.
+ * In order to distinguish free and used handle table entries we exploit
+ * the heap alignment and use the lower two bits to do this. Used entries
+ * will have these bits set to 0, while free entries will have tem set
+ * to 3. Use the RTHT_GET_FREE_IDX and RTHT_SET_FREE_IDX macros to access
+ * this field. */
+ uintptr_t iNext;
+} RTHTENTRYFREE;
+/** Pointer to a free handle table entry. */
+typedef RTHTENTRYFREE *PRTHTENTRYFREE;
+
+AssertCompile(sizeof(RTHTENTRYFREE) <= sizeof(RTHTENTRY));
+AssertCompile(sizeof(RTHTENTRYFREE) <= sizeof(RTHTENTRYCTX));
+AssertCompileMemberOffset(RTHTENTRYFREE, iNext, 0);
+AssertCompileMemberOffset(RTHTENTRY, pvObj, 0);
+AssertCompileMemberOffset(RTHTENTRYCTX, pvObj, 0);
+
+
+/**
+ * Internal handle table structure.
+ */
+typedef struct RTHANDLETABLEINT
+{
+ /** Magic value (RTHANDLETABLE_MAGIC). */
+ uint32_t u32Magic;
+ /** The handle table flags specified to RTHandleTableCreateEx. */
+ uint32_t fFlags;
+ /** The base handle value (i.e. the first handle). */
+ uint32_t uBase;
+ /** The current number of handle table entries. */
+ uint32_t cCur;
+ /** The spinlock handle (NIL if RTHANDLETABLE_FLAGS_LOCKED wasn't used). */
+ RTSPINLOCK hSpinlock;
+ /** The level one lookup table. */
+ void **papvLevel1;
+ /** The retainer callback. Can be NULL. */
+ PFNRTHANDLETABLERETAIN pfnRetain;
+ /** The user argument to the retainer. */
+ void *pvRetainUser;
+ /** The max number of handles. */
+ uint32_t cMax;
+ /** The number of handles currently allocated. (for optimizing destruction) */
+ uint32_t cCurAllocated;
+ /** The current number of 1st level entries. */
+ uint32_t cLevel1;
+ /** Head of the list of free handle entires (index). */
+ uint32_t iFreeHead;
+ /** Tail of the list of free handle entires (index). */
+ uint32_t iFreeTail;
+} RTHANDLETABLEINT;
+/** Pointer to an handle table structure. */
+typedef RTHANDLETABLEINT *PRTHANDLETABLEINT;
+
+
+/**
+ * Looks up a simple index.
+ *
+ * @returns Pointer to the handle table entry on success, NULL on failure.
+ * @param pThis The handle table structure.
+ * @param i The index to look up.
+ */
+DECLINLINE(PRTHTENTRY) rtHandleTableLookupSimpleIdx(PRTHANDLETABLEINT pThis, uint32_t i)
+{
+ if (i < pThis->cCur)
+ {
+ PRTHTENTRY paTable = (PRTHTENTRY)pThis->papvLevel1[i / RTHT_LEVEL2_ENTRIES];
+ if (paTable)
+ return &paTable[i % RTHT_LEVEL2_ENTRIES];
+ }
+ return NULL;
+}
+
+
+/**
+ * Looks up a simple handle.
+ *
+ * @returns Pointer to the handle table entry on success, NULL on failure.
+ * @param pThis The handle table structure.
+ * @param h The handle to look up.
+ */
+DECLINLINE(PRTHTENTRY) rtHandleTableLookupSimple(PRTHANDLETABLEINT pThis, uint32_t h)
+{
+ return rtHandleTableLookupSimpleIdx(pThis, h - pThis->uBase);
+}
+
+
+/**
+ * Looks up a context index.
+ *
+ * @returns Pointer to the handle table entry on success, NULL on failure.
+ * @param pThis The handle table structure.
+ * @param i The index to look up.
+ */
+DECLINLINE(PRTHTENTRYCTX) rtHandleTableLookupWithCtxIdx(PRTHANDLETABLEINT pThis, uint32_t i)
+{
+ if (i < pThis->cCur)
+ {
+ PRTHTENTRYCTX paTable = (PRTHTENTRYCTX)pThis->papvLevel1[i / RTHT_LEVEL2_ENTRIES];
+ if (paTable)
+ return &paTable[i % RTHT_LEVEL2_ENTRIES];
+ }
+ return NULL;
+}
+
+
+/**
+ * Looks up a context handle.
+ *
+ * @returns Pointer to the handle table entry on success, NULL on failure.
+ * @param pThis The handle table structure.
+ * @param h The handle to look up.
+ */
+DECLINLINE(PRTHTENTRYCTX) rtHandleTableLookupWithCtx(PRTHANDLETABLEINT pThis, uint32_t h)
+{
+ return rtHandleTableLookupWithCtxIdx(pThis, h - pThis->uBase);
+}
+
+
+/**
+ * Locks the handle table.
+ *
+ * @param pThis The handle table structure.
+ */
+DECLINLINE(void) rtHandleTableLock(PRTHANDLETABLEINT pThis)
+{
+ if (pThis->hSpinlock != NIL_RTSPINLOCK)
+ RTSpinlockAcquire(pThis->hSpinlock);
+}
+
+
+/**
+ * Locks the handle table.
+ *
+ * @param pThis The handle table structure.
+ */
+DECLINLINE(void) rtHandleTableUnlock(PRTHANDLETABLEINT pThis)
+{
+ if (pThis->hSpinlock != NIL_RTSPINLOCK)
+ RTSpinlockRelease(pThis->hSpinlock);
+}
+
+#endif /* !IPRT_INCLUDED_SRC_common_misc_handletable_h */
+
diff --git a/src/VBox/Runtime/common/misc/handletablectx.cpp b/src/VBox/Runtime/common/misc/handletablectx.cpp
new file mode 100644
index 00000000..e093fe90
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handletablectx.cpp
@@ -0,0 +1,339 @@
+/* $Id: handletablectx.cpp $ */
+/** @file
+ * IPRT - Handle Tables.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/handletable.h>
+#include "internal/iprt.h"
+
+#include <iprt/mem.h>
+#include <iprt/spinlock.h>
+#include <iprt/err.h>
+#include <iprt/assert.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include "internal/magics.h"
+#include "handletable.h"
+
+
+RTDECL(int) RTHandleTableAllocWithCtx(RTHANDLETABLE hHandleTable, void *pvObj, void *pvCtx, uint32_t *ph)
+{
+ PRTHANDLETABLEINT pThis;
+ int rc;
+
+ /* validate the input */
+ pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT, VERR_INVALID_FUNCTION);
+ AssertReturn(!RTHT_IS_FREE(pvObj), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ph, VERR_INVALID_POINTER);
+ *ph = pThis->uBase - 1;
+
+ /*
+ * Allocation loop.
+ */
+ rtHandleTableLock(pThis);
+
+ do
+ {
+ /*
+ * Try grab a free entry from the head of the free list.
+ */
+ uint32_t i = pThis->iFreeHead;
+ if (i != NIL_RTHT_INDEX)
+ {
+ PRTHTENTRYCTX pEntry;
+ PRTHTENTRYFREE pFree = (PRTHTENTRYFREE)rtHandleTableLookupWithCtxIdx(pThis, i);
+ Assert(pFree);
+ if (i == pThis->iFreeTail)
+ pThis->iFreeTail = pThis->iFreeHead = NIL_RTHT_INDEX;
+ else
+ pThis->iFreeHead = RTHT_GET_FREE_IDX(pFree);
+ pThis->cCurAllocated++;
+ Assert(pThis->cCurAllocated <= pThis->cCur);
+
+ /*
+ * Setup the entry and return.
+ */
+ pEntry = (PRTHTENTRYCTX)pFree;
+ pEntry->pvObj = pvObj;
+ pEntry->pvCtx = pvCtx;
+ *ph = i + pThis->uBase;
+ rc = VINF_SUCCESS;
+ }
+ /*
+ * Must expand the handle table, unless it's full.
+ */
+ else if (pThis->cCur >= pThis->cMax)
+ {
+ rc = VERR_NO_MORE_HANDLES;
+ Assert(pThis->cCur == pThis->cCurAllocated);
+ }
+ else
+ {
+ void **papvLevel1;
+ uint32_t iLevel1New;
+ PRTHTENTRYCTX paTable;
+
+ /*
+ * Do we have to expand the 1st level table too?
+ */
+ uint32_t const iLevel1 = pThis->cCur / RTHT_LEVEL2_ENTRIES;
+ uint32_t cLevel1 = iLevel1 >= pThis->cLevel1
+ ? pThis->cLevel1 + PAGE_SIZE / sizeof(void *)
+ : 0;
+ if (cLevel1 > pThis->cMax / RTHT_LEVEL2_ENTRIES)
+ cLevel1 = pThis->cMax / RTHT_LEVEL2_ENTRIES;
+ Assert(!cLevel1 || pThis->cMax / RTHT_LEVEL2_ENTRIES >= RTHT_LEVEL1_DYN_ALLOC_THRESHOLD);
+
+ /* leave the lock (never do fancy stuff from behind a spinlock). */
+ rtHandleTableUnlock(pThis);
+
+ /*
+ * Do the allocation(s).
+ */
+ rc = VERR_TRY_AGAIN;
+ papvLevel1 = NULL;
+ if (cLevel1)
+ {
+ papvLevel1 = (void **)RTMemAlloc(sizeof(void *) * cLevel1);
+ if (!papvLevel1)
+ return VERR_NO_MEMORY;
+ }
+
+ paTable = (PRTHTENTRYCTX)RTMemAlloc(sizeof(*paTable) * RTHT_LEVEL2_ENTRIES);
+ if (!paTable)
+ {
+ RTMemFree(papvLevel1);
+ return VERR_NO_MEMORY;
+ }
+
+ /* re-enter the lock. */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Insert the new bits, but be a bit careful as someone might have
+ * raced us expanding the table.
+ */
+ /* deal with the 1st level lookup expansion first */
+ if (cLevel1)
+ {
+ Assert(papvLevel1);
+ if (cLevel1 > pThis->cLevel1)
+ {
+ void **papvTmp;
+
+ /* Replace the 1st level table. */
+ memcpy(papvLevel1, pThis->papvLevel1, sizeof(void *) * pThis->cLevel1);
+ memset(&papvLevel1[pThis->cLevel1], 0, sizeof(void *) * (cLevel1 - pThis->cLevel1));
+ pThis->cLevel1 = cLevel1;
+ papvTmp = pThis->papvLevel1;
+ pThis->papvLevel1 = papvLevel1;
+ papvLevel1 = papvTmp;
+ }
+
+ /* free the obsolete one (outside the lock of course) */
+ rtHandleTableUnlock(pThis);
+ RTMemFree(papvLevel1);
+ rtHandleTableLock(pThis);
+ }
+
+ /* insert the table we allocated. */
+ iLevel1New = pThis->cCur / RTHT_LEVEL2_ENTRIES;
+ if ( iLevel1New < pThis->cLevel1
+ && pThis->cCur < pThis->cMax)
+ {
+ pThis->papvLevel1[iLevel1New] = paTable;
+
+ /* link all entries into a free list. */
+ Assert(!(pThis->cCur % RTHT_LEVEL2_ENTRIES));
+ for (i = 0; i < RTHT_LEVEL2_ENTRIES - 1; i++)
+ {
+ RTHT_SET_FREE_IDX((PRTHTENTRYFREE)&paTable[i], i + 1 + pThis->cCur);
+ paTable[i].pvCtx = (void *)~(uintptr_t)7;
+ }
+ RTHT_SET_FREE_IDX((PRTHTENTRYFREE)&paTable[RTHT_LEVEL2_ENTRIES - 1], NIL_RTHT_INDEX);
+ paTable[RTHT_LEVEL2_ENTRIES - 1].pvCtx = (void *)~(uintptr_t)7;
+
+ /* join the free list with the other. */
+ if (pThis->iFreeTail == NIL_RTHT_INDEX)
+ pThis->iFreeHead = pThis->cCur;
+ else
+ {
+ PRTHTENTRYFREE pPrev = (PRTHTENTRYFREE)rtHandleTableLookupWithCtxIdx(pThis, pThis->iFreeTail);
+ Assert(pPrev);
+ RTHT_SET_FREE_IDX(pPrev, pThis->cCur);
+ }
+ pThis->iFreeTail = pThis->cCur + RTHT_LEVEL2_ENTRIES - 1;
+
+ pThis->cCur += RTHT_LEVEL2_ENTRIES;
+ }
+ else
+ {
+ /* free the table (raced someone, and we lost). */
+ rtHandleTableUnlock(pThis);
+ RTMemFree(paTable);
+ rtHandleTableLock(pThis);
+ }
+
+ rc = VERR_TRY_AGAIN;
+ }
+ } while (rc == VERR_TRY_AGAIN);
+
+ rtHandleTableUnlock(pThis);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTHandleTableAllocWithCtx);
+
+
+RTDECL(void *) RTHandleTableLookupWithCtx(RTHANDLETABLE hHandleTable, uint32_t h, void *pvCtx)
+{
+ void *pvObj = NULL;
+ PRTHTENTRYCTX pEntry;
+ PRTHANDLETABLEINT pThis;
+
+ /* validate the input */
+ pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, NULL);
+ AssertReturn(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT, NULL);
+
+
+ /* acquire the lock */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Perform the lookup and retaining.
+ */
+ pEntry = rtHandleTableLookupWithCtx(pThis, h);
+ if (pEntry && pEntry->pvCtx == pvCtx)
+ {
+ pvObj = pEntry->pvObj;
+ if (!RTHT_IS_FREE(pvObj))
+ {
+ if (pThis->pfnRetain)
+ {
+ int rc = pThis->pfnRetain(hHandleTable, pEntry->pvObj, pvCtx, pThis->pvRetainUser);
+ if (RT_FAILURE(rc))
+ pvObj = NULL;
+ }
+ }
+ else
+ pvObj = NULL;
+ }
+
+ /* release the lock */
+ rtHandleTableUnlock(pThis);
+ return pvObj;
+}
+RT_EXPORT_SYMBOL(RTHandleTableLookupWithCtx);
+
+
+RTDECL(void *) RTHandleTableFreeWithCtx(RTHANDLETABLE hHandleTable, uint32_t h, void *pvCtx)
+{
+ void *pvObj = NULL;
+ PRTHTENTRYCTX pEntry;
+ PRTHANDLETABLEINT pThis;
+
+ /* validate the input */
+ pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, NULL);
+ AssertReturn(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT, NULL);
+
+
+ /* acquire the lock */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Perform the lookup and retaining.
+ */
+ pEntry = rtHandleTableLookupWithCtx(pThis, h);
+ if (pEntry && pEntry->pvCtx == pvCtx)
+ {
+ pvObj = pEntry->pvObj;
+ if (!RTHT_IS_FREE(pvObj))
+ {
+ if (pThis->pfnRetain)
+ {
+ int rc = pThis->pfnRetain(hHandleTable, pEntry->pvObj, pvCtx, pThis->pvRetainUser);
+ if (RT_FAILURE(rc))
+ pvObj = NULL;
+ }
+
+ /*
+ * Link it into the free list.
+ */
+ if (pvObj)
+ {
+ PRTHTENTRYFREE pFree;
+ uint32_t i;
+
+ pEntry->pvCtx = (void *)~(uintptr_t)7;
+
+ pFree = (PRTHTENTRYFREE)pEntry;
+ RTHT_SET_FREE_IDX(pFree, NIL_RTHT_INDEX);
+
+ i = h - pThis->uBase;
+ if (pThis->iFreeTail == NIL_RTHT_INDEX)
+ pThis->iFreeHead = pThis->iFreeTail = i;
+ else
+ {
+ PRTHTENTRYFREE pPrev = (PRTHTENTRYFREE)rtHandleTableLookupWithCtxIdx(pThis, pThis->iFreeTail);
+ Assert(pPrev);
+ RTHT_SET_FREE_IDX(pPrev, i);
+ pThis->iFreeTail = i;
+ }
+
+ Assert(pThis->cCurAllocated > 0);
+ pThis->cCurAllocated--;
+ }
+ }
+ else
+ pvObj = NULL;
+ }
+
+ /* release the lock */
+ rtHandleTableUnlock(pThis);
+ return pvObj;
+}
+RT_EXPORT_SYMBOL(RTHandleTableFreeWithCtx);
+
diff --git a/src/VBox/Runtime/common/misc/handletablesimple.cpp b/src/VBox/Runtime/common/misc/handletablesimple.cpp
new file mode 100644
index 00000000..474d9f35
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/handletablesimple.cpp
@@ -0,0 +1,314 @@
+/* $Id: handletablesimple.cpp $ */
+/** @file
+ * IPRT - Handle Tables.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/handletable.h>
+#include "internal/iprt.h"
+
+#include <iprt/mem.h>
+#include <iprt/spinlock.h>
+#include <iprt/err.h>
+#include <iprt/assert.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include "internal/magics.h"
+#include "handletable.h"
+
+
+RTDECL(int) RTHandleTableAlloc(RTHANDLETABLE hHandleTable, void *pvObj, uint32_t *ph)
+{
+ /* validate the input */
+ PRTHANDLETABLEINT pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT), VERR_INVALID_FUNCTION);
+ AssertReturn(!RTHT_IS_FREE(pvObj), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ph, VERR_INVALID_POINTER);
+ *ph = pThis->uBase - 1;
+
+ /*
+ * Allocation loop.
+ */
+ rtHandleTableLock(pThis);
+
+ int rc;
+ do
+ {
+ /*
+ * Try grab a free entry from the head of the free list.
+ */
+ uint32_t i = pThis->iFreeHead;
+ if (i != NIL_RTHT_INDEX)
+ {
+ PRTHTENTRYFREE pFree = (PRTHTENTRYFREE)rtHandleTableLookupSimpleIdx(pThis, i);
+ Assert(pFree);
+ if (i == pThis->iFreeTail)
+ pThis->iFreeTail = pThis->iFreeHead = NIL_RTHT_INDEX;
+ else
+ pThis->iFreeHead = RTHT_GET_FREE_IDX(pFree);
+ pThis->cCurAllocated++;
+ Assert(pThis->cCurAllocated <= pThis->cCur);
+
+ /*
+ * Setup the entry and return.
+ */
+ PRTHTENTRY pEntry = (PRTHTENTRY)pFree;
+ pEntry->pvObj = pvObj;
+ *ph = i + pThis->uBase;
+ rc = VINF_SUCCESS;
+ }
+ /*
+ * Must expand the handle table, unless it's full.
+ */
+ else if (pThis->cCur >= pThis->cMax)
+ {
+ rc = VERR_NO_MORE_HANDLES;
+ Assert(pThis->cCur == pThis->cCurAllocated);
+ }
+ else
+ {
+ /*
+ * Do we have to expand the 1st level table too?
+ */
+ uint32_t const iLevel1 = pThis->cCur / RTHT_LEVEL2_ENTRIES;
+ uint32_t cLevel1 = iLevel1 >= pThis->cLevel1
+ ? pThis->cLevel1 + PAGE_SIZE / sizeof(void *)
+ : 0;
+ if (cLevel1 > pThis->cMax / RTHT_LEVEL2_ENTRIES)
+ cLevel1 = pThis->cMax / RTHT_LEVEL2_ENTRIES;
+ Assert(!cLevel1 || pThis->cMax / RTHT_LEVEL2_ENTRIES >= RTHT_LEVEL1_DYN_ALLOC_THRESHOLD);
+
+ /* leave the lock (never do fancy stuff from behind a spinlock). */
+ rtHandleTableUnlock(pThis);
+
+ /*
+ * Do the allocation(s).
+ */
+ rc = VERR_TRY_AGAIN;
+ void **papvLevel1 = NULL;
+ if (cLevel1)
+ {
+ papvLevel1 = (void **)RTMemAlloc(sizeof(void *) * cLevel1);
+ if (!papvLevel1)
+ return VERR_NO_MEMORY;
+ }
+
+ PRTHTENTRY paTable = (PRTHTENTRY)RTMemAlloc(sizeof(*paTable) * RTHT_LEVEL2_ENTRIES);
+ if (!paTable)
+ {
+ RTMemFree(papvLevel1);
+ return VERR_NO_MEMORY;
+ }
+
+ /* re-enter the lock. */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Insert the new bits, but be a bit careful as someone might have
+ * raced us expanding the table.
+ */
+ /* deal with the 1st level lookup expansion first */
+ if (cLevel1)
+ {
+ Assert(papvLevel1);
+ if (cLevel1 > pThis->cLevel1)
+ {
+ /* Replace the 1st level table. */
+ memcpy(papvLevel1, pThis->papvLevel1, sizeof(void *) * pThis->cLevel1);
+ memset(&papvLevel1[pThis->cLevel1], 0, sizeof(void *) * (cLevel1 - pThis->cLevel1));
+ pThis->cLevel1 = cLevel1;
+ void **papvTmp = pThis->papvLevel1;
+ pThis->papvLevel1 = papvLevel1;
+ papvLevel1 = papvTmp;
+ }
+
+ /* free the obsolete one (outside the lock of course) */
+ rtHandleTableUnlock(pThis);
+ RTMemFree(papvLevel1);
+ rtHandleTableLock(pThis);
+ }
+
+ /* insert the table we allocated. */
+ uint32_t iLevel1New = pThis->cCur / RTHT_LEVEL2_ENTRIES;
+ if ( iLevel1New < pThis->cLevel1
+ && pThis->cCur < pThis->cMax)
+ {
+ pThis->papvLevel1[iLevel1New] = paTable;
+
+ /* link all entries into a free list. */
+ Assert(!(pThis->cCur % RTHT_LEVEL2_ENTRIES));
+ for (i = 0; i < RTHT_LEVEL2_ENTRIES - 1; i++)
+ RTHT_SET_FREE_IDX((PRTHTENTRYFREE)&paTable[i], i + 1 + pThis->cCur);
+ RTHT_SET_FREE_IDX((PRTHTENTRYFREE)&paTable[RTHT_LEVEL2_ENTRIES - 1], NIL_RTHT_INDEX);
+
+ /* join the free list with the other. */
+ if (pThis->iFreeTail == NIL_RTHT_INDEX)
+ pThis->iFreeHead = pThis->cCur;
+ else
+ {
+ PRTHTENTRYFREE pPrev = (PRTHTENTRYFREE)rtHandleTableLookupSimpleIdx(pThis, pThis->iFreeTail);
+ Assert(pPrev);
+ RTHT_SET_FREE_IDX(pPrev, pThis->cCur);
+ }
+ pThis->iFreeTail = pThis->cCur + RTHT_LEVEL2_ENTRIES - 1;
+
+ pThis->cCur += RTHT_LEVEL2_ENTRIES;
+ }
+ else
+ {
+ /* free the table (raced someone, and we lost). */
+ rtHandleTableUnlock(pThis);
+ RTMemFree(paTable);
+ rtHandleTableLock(pThis);
+ }
+
+ rc = VERR_TRY_AGAIN;
+ }
+ } while (rc == VERR_TRY_AGAIN);
+
+ rtHandleTableUnlock(pThis);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTHandleTableAlloc);
+
+
+RTDECL(void *) RTHandleTableLookup(RTHANDLETABLE hHandleTable, uint32_t h)
+{
+ /* validate the input */
+ PRTHANDLETABLEINT pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, NULL);
+ AssertReturn(!(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT), NULL);
+
+ void *pvObj = NULL;
+
+ /* acquire the lock */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Perform the lookup and retaining.
+ */
+ PRTHTENTRY pEntry = rtHandleTableLookupSimple(pThis, h);
+ if (pEntry)
+ {
+ pvObj = pEntry->pvObj;
+ if (!RTHT_IS_FREE(pvObj))
+ {
+ if (pThis->pfnRetain)
+ {
+ int rc = pThis->pfnRetain(hHandleTable, pEntry->pvObj, NULL, pThis->pvRetainUser);
+ if (RT_FAILURE(rc))
+ pvObj = NULL;
+ }
+ }
+ else
+ pvObj = NULL;
+ }
+
+ /* release the lock */
+ rtHandleTableUnlock(pThis);
+ return pvObj;
+}
+RT_EXPORT_SYMBOL(RTHandleTableLookup);
+
+
+RTDECL(void *) RTHandleTableFree(RTHANDLETABLE hHandleTable, uint32_t h)
+{
+ /* validate the input */
+ PRTHANDLETABLEINT pThis = (PRTHANDLETABLEINT)hHandleTable;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->u32Magic == RTHANDLETABLE_MAGIC, NULL);
+ AssertReturn(!(pThis->fFlags & RTHANDLETABLE_FLAGS_CONTEXT), NULL);
+
+ void *pvObj = NULL;
+
+ /* acquire the lock */
+ rtHandleTableLock(pThis);
+
+ /*
+ * Perform the lookup and retaining.
+ */
+ PRTHTENTRY pEntry = rtHandleTableLookupSimple(pThis, h);
+ if (pEntry)
+ {
+ pvObj = pEntry->pvObj;
+ if (!RTHT_IS_FREE(pvObj))
+ {
+ if (pThis->pfnRetain)
+ {
+ int rc = pThis->pfnRetain(hHandleTable, pEntry->pvObj, NULL, pThis->pvRetainUser);
+ if (RT_FAILURE(rc))
+ pvObj = NULL;
+ }
+
+ /*
+ * Link it into the free list.
+ */
+ if (pvObj)
+ {
+ PRTHTENTRYFREE pFree = (PRTHTENTRYFREE)pEntry;
+ RTHT_SET_FREE_IDX(pFree, NIL_RTHT_INDEX);
+
+ uint32_t const i = h - pThis->uBase;
+ if (pThis->iFreeTail == NIL_RTHT_INDEX)
+ pThis->iFreeHead = pThis->iFreeTail = i;
+ else
+ {
+ PRTHTENTRYFREE pPrev = (PRTHTENTRYFREE)rtHandleTableLookupSimpleIdx(pThis, pThis->iFreeTail);
+ Assert(pPrev);
+ RTHT_SET_FREE_IDX(pPrev, i);
+ pThis->iFreeTail = i;
+ }
+
+ Assert(pThis->cCurAllocated > 0);
+ pThis->cCurAllocated--;
+ }
+ }
+ else
+ pvObj = NULL;
+ }
+
+ /* release the lock */
+ rtHandleTableUnlock(pThis);
+ return pvObj;
+}
+RT_EXPORT_SYMBOL(RTHandleTableFree);
+
diff --git a/src/VBox/Runtime/common/misc/inifile.cpp b/src/VBox/Runtime/common/misc/inifile.cpp
new file mode 100644
index 00000000..ef173573
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/inifile.cpp
@@ -0,0 +1,733 @@
+/* $Id: inifile.cpp $ */
+/** @file
+ * IPRT - INI-file parser.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/inifile.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/latin1.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def RTINIFILE_MAX_SIZE
+ * The maximum INI-file size we accept loading. */
+#if ARCH_BITS > 32
+# define RTINIFILE_MAX_SIZE (_64M - 2U)
+#elif ARCH_BITS > 16
+# define RTINIFILE_MAX_SIZE (_16M - 2U)
+#else
+# define RTINIFILE_MAX_SIZE (_64K - 2U)
+#endif
+
+/** @def RTINIFILE_MAX_SECTIONS
+ * The maximum number of sections we accept in an INI-file. */
+#if ARCH_BITS > 32
+# define RTINIFILE_MAX_SECTIONS (_1M)
+#elif ARCH_BITS > 16
+# define RTINIFILE_MAX_SECTIONS (_256K)
+#else
+# define RTINIFILE_MAX_SECTIONS (_1K)
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * File encoding types.
+ */
+typedef enum RTINIFILEENCODING
+{
+ /** The customary invalid zero value. */
+ RTINIFILEENCODING_INVALID = 0,
+ /** We treat this as latin-1. */
+ RTINIFILEENCODING_ANSI,
+ /** UTF-8. */
+ RTINIFILEENCODING_UTF8,
+ /** Little endian UTF-16. */
+ RTINIFILEENCODING_UTF16LE,
+ /** Big endian UTF-16. */
+ RTINIFILEENCODING_UTF16BE,
+ /** End of valid encoding types. */
+ RTINIFILEENCODING_END
+} RTINIFILEENCODING;
+
+
+/**
+ * Preparsed section info.
+ */
+typedef struct RTINIFILESECTION
+{
+ /** The section name offset (byte). */
+ uint32_t offName;
+ /** The section length in bytes starting with the name. */
+ uint32_t cchSection;
+ /** The UTF-8 length of the section name. */
+ uint32_t cchName;
+ /** Offset into the section where to start looking for values. */
+ uint32_t cchSkipToValues : 24;
+ /** @todo use 4 bits for flags and stuff. like escaped name. */
+} RTINIFILESECTION;
+/** Pointer to preparsed section info. */
+typedef RTINIFILESECTION *PRTINIFILESECTION;
+
+
+/**
+ * INI-file instance data.
+ */
+typedef struct RTINIFILEINT
+{
+ /** Magic value (RTINIFILEINT_MAGIC). */
+ uint32_t u32Magic;
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The file we're working on. */
+ RTVFSFILE hVfsFile;
+ /** Flags, RTINIFILE_F_XXX. */
+ uint32_t fFlags;
+
+ /** The original file encoding. */
+ RTINIFILEENCODING enmEncoding;
+ /** Pointer to the file content (converted to UTF-8). */
+ char *pszFile;
+ /** The file size. */
+ uint32_t cbFile;
+ /** Number of sections. */
+ uint32_t cSections;
+ /** Sections in the loaded file. */
+ PRTINIFILESECTION paSections;
+
+} RTINIFILEINT;
+/** Pointer to an INI-file instance. */
+typedef RTINIFILEINT *PRTINIFILEINT;
+
+
+static int rtIniFileLoad(PRTINIFILEINT pThis)
+{
+ /*
+ * Load the entire file into memory, ensuring two terminating zeros.
+ */
+ uint64_t cbFile;
+ int rc = RTVfsFileQuerySize(pThis->hVfsFile, &cbFile);
+ AssertRCReturn(rc, rc);
+
+ if (cbFile > RTINIFILE_MAX_SIZE)
+ return VERR_TOO_MUCH_DATA;
+ if (cbFile == 0)
+ return VINF_SUCCESS; /* Nothing to do. */
+
+ pThis->cbFile = (uint32_t)cbFile;
+ pThis->pszFile = (char *)RTMemAllocZ(pThis->cbFile + 2);
+ if (!pThis->pszFile)
+ return VERR_NO_MEMORY;
+
+ rc = RTVfsFileReadAt(pThis->hVfsFile, 0, pThis->pszFile, pThis->cbFile, NULL);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Detect encoding and convert to BOM prefixed UTF-8.
+ */
+ if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xef)
+ && (uint8_t)pThis->pszFile[1] == UINT8_C(0xbb)
+ && (uint8_t)pThis->pszFile[2] == UINT8_C(0xbf))
+ {
+ pThis->enmEncoding = RTINIFILEENCODING_UTF8;
+ rc = RTStrValidateEncoding(&pThis->pszFile[3]);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ size_t cchUtf8;
+ if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xfe)
+ && (uint8_t)pThis->pszFile[1] == UINT8_C(0xff))
+ {
+ pThis->enmEncoding = RTINIFILEENCODING_UTF16BE;
+ rc = RTUtf16BigCalcUtf8LenEx((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &cchUtf8);
+ }
+ else if ( (uint8_t)pThis->pszFile[0] == UINT8_C(0xff)
+ && (uint8_t)pThis->pszFile[1] == UINT8_C(0xfe))
+ {
+ pThis->enmEncoding = RTINIFILEENCODING_UTF16LE;
+ rc = RTUtf16LittleCalcUtf8LenEx((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &cchUtf8);
+ }
+ else
+ {
+ pThis->enmEncoding = RTINIFILEENCODING_ANSI;
+ rc = RTLatin1CalcUtf8LenEx(pThis->pszFile, RTSTR_MAX, &cchUtf8);
+ }
+ if (RT_FAILURE(rc))
+ return rc;
+
+ char *pszUtf8Bom = (char *)RTMemAllocZ(3 + cchUtf8 + 1);
+ if (!pszUtf8Bom)
+ return VERR_NO_MEMORY;
+ pszUtf8Bom[0] = '\xEF';
+ pszUtf8Bom[1] = '\xBB';
+ pszUtf8Bom[2] = '\xBF';
+
+ char *pszUtf8 = pszUtf8Bom + 3;
+ if (pThis->enmEncoding == RTINIFILEENCODING_UTF16BE)
+ rc = RTUtf16BigToUtf8Ex((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL);
+ else if (pThis->enmEncoding == RTINIFILEENCODING_UTF16LE)
+ rc = RTUtf16LittleToUtf8Ex((PCRTUTF16)&pThis->pszFile[2], RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL);
+ else
+ rc = RTLatin1ToUtf8Ex(pThis->pszFile, RTSTR_MAX, &pszUtf8, cchUtf8 + 1, NULL);
+ AssertRCReturnStmt(rc, RTMemFree(pszUtf8Bom), rc);
+
+ RTMemFree(pThis->pszFile);
+ pThis->pszFile = pszUtf8Bom;
+ pThis->cbFile = 3 + (uint32_t)cchUtf8;
+ }
+
+ /*
+ * Do a rough section count.
+ * Section zero is for unsectioned values at the start of the file.
+ */
+ uint32_t cSections = 1;
+ const char *psz = pThis->pszFile + 3;
+ char ch;
+ while ((ch = *psz) != '\0')
+ {
+ while (RT_C_IS_SPACE(ch))
+ ch = *++psz;
+ if (ch == '[')
+ cSections++;
+
+ /* next line. */
+ psz = strchr(psz, '\n');
+ if (psz)
+ psz++;
+ else
+ break;
+ }
+ if (cSections > RTINIFILE_MAX_SECTIONS)
+ return VERR_TOO_MUCH_DATA;
+
+ /*
+ * Allocation section array and do the preparsing.
+ */
+ pThis->paSections = (PRTINIFILESECTION)RTMemAllocZ(sizeof(pThis->paSections[0]) * cSections);
+ if (!pThis->paSections)
+ return VERR_NO_MEMORY;
+
+ uint32_t iSection = 0;
+ pThis->paSections[0].offName = 3;
+ pThis->paSections[0].cchName = 0;
+ pThis->paSections[0].cchSkipToValues = 0;
+ psz = pThis->pszFile + 3;
+ while ((ch = *psz) != '\0')
+ {
+ const char *const pszLine = psz;
+
+ while (RT_C_IS_SPACE(ch))
+ ch = *++psz;
+ if (ch == '[')
+ {
+ /* Complete previous section. */
+ pThis->paSections[iSection].cchSection = (uint32_t)(pszLine - &pThis->pszFile[pThis->paSections[iSection].offName]);
+
+ /* New section. */
+ iSection++;
+ AssertReturn(iSection < cSections, VERR_INTERNAL_ERROR_3);
+ const char * const pszName = ++psz;
+ pThis->paSections[iSection].offName = (uint32_t)(psz - pThis->pszFile);
+
+ /* Figure the name length. We're very very relaxed about terminating bracket. */
+ while ((ch = *psz) != '\0' && ch != ']' && ch != '\r' && ch != '\n')
+ psz++;
+ pThis->paSections[iSection].cchName = (uint32_t)(psz - pszName);
+
+ /* Set skip count to the start of the next line. */
+ while (ch != '\0' && ch != '\n')
+ ch = *++psz;
+ pThis->paSections[iSection].cchSkipToValues = (uint32_t)(psz - pszName + 1);
+
+ if (ch == '\n')
+ psz++;
+ else
+ break;
+ }
+ else
+ {
+ psz = strchr(psz, '\n');
+ if (psz)
+ psz++;
+ else
+ break;
+ }
+ }
+
+ /* Complete the final section. */
+ pThis->paSections[iSection].cchSection = pThis->cbFile - pThis->paSections[iSection].offName;
+ pThis->cSections = iSection + 1;
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTIniFileCreateFromVfsFile(PRTINIFILE phIniFile, RTVFSFILE hVfsFile, uint32_t fFlags)
+{
+ /*
+ * Validate input, retaining a reference to the file.
+ */
+ AssertPtrReturn(phIniFile, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTINIFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create an instance.
+ */
+ PRTINIFILEINT pThis = (PRTINIFILEINT)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->u32Magic = RTINIFILE_MAGIC;
+ pThis->cRefs = 1;
+ pThis->hVfsFile = hVfsFile;
+ pThis->fFlags = fFlags;
+
+ int rc = rtIniFileLoad(pThis);
+ if (RT_SUCCESS(rc))
+ {
+
+ *phIniFile = pThis;
+ return VINF_SUCCESS;
+ }
+ RTIniFileRelease(pThis);
+ return rc;
+ }
+ RTVfsFileRelease(hVfsFile);
+ return VERR_NO_MEMORY;
+}
+
+
+RTDECL(uint32_t) RTIniFileRetain(RTINIFILE hIniFile)
+{
+ PRTINIFILEINT pThis = hIniFile;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < _64K);
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTIniFileRelease(RTINIFILE hIniFile)
+{
+ if (hIniFile == NIL_RTINIFILE)
+ return 0;
+ PRTINIFILEINT pThis = hIniFile;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ Assert(cRefs < _64K);
+ if (cRefs == 0)
+ {
+ AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTINIFILE_MAGIC_DEAD, RTINIFILE_MAGIC), UINT32_MAX);
+ RTMemFree(pThis->paSections);
+ pThis->paSections = NULL;
+ RTMemFree(pThis->pszFile);
+ pThis->pszFile = NULL;
+ RTVfsFileRelease(pThis->hVfsFile);
+ pThis->hVfsFile = NIL_RTVFSFILE;
+ RTMemFree(pThis);
+ }
+ return cRefs;
+}
+
+
+/**
+ * Worker for RTIniFileQueryValue.
+ */
+static int rtIniFileQueryValueInSection(PRTINIFILEINT pThis, PRTINIFILESECTION pSection, const char *pszKey, size_t cchKey,
+ char *pszValue, size_t cbValue, size_t *pcbActual)
+{
+ /*
+ * Scan the section, looking for the matching key.
+ */
+ Assert(pSection->cchSkipToValues <= pSection->cchSection);
+ const char * const pszEnd = &pThis->pszFile[pSection->offName + pSection->cchSection];
+ const char * pszNext = pszEnd;
+ for (const char *psz = &pThis->pszFile[pSection->offName + pSection->cchSkipToValues];
+ (uintptr_t)psz < (uintptr_t)pszEnd;
+ psz = pszNext)
+ {
+ /* Find start of next line so we can use 'continue' to skip a line. */
+ pszNext = strchr(psz, '\n');
+ if (pszNext)
+ pszNext++;
+ else
+ pszNext = pszEnd;
+
+ /* Skip leading spaces. */
+ char ch;
+ while ((ch = *psz) != '\0' && RT_C_IS_SPACE(ch))
+ psz++;
+ if ( ch != ';' /* comment line */
+ && ch != '\n' /* empty line */
+ && ch != '\r' /* empty line */
+ && (uintptr_t)psz < (uintptr_t)pszEnd)
+ {
+ /* Find end of key name, if any. */
+ const char *pszCurKey = psz;
+ size_t cchCurKey;
+ const char *pszEqual;
+ if (ch != '=')
+ {
+ /** @todo deal with escaped equal signs? */
+ pszEqual = strchr(psz, '=');
+ if (pszEqual)
+ {
+ if ((uintptr_t)pszEqual < (uintptr_t)pszNext)
+ cchCurKey = pszEqual - pszCurKey;
+ else
+ continue;
+ }
+ else
+ break;
+
+ /* Strip trailing spaces from the current key name. */
+ while (cchCurKey > 0 && RT_C_IS_SPACE(pszCurKey[cchCurKey - 1]))
+ cchCurKey--;
+ }
+ else
+ {
+ cchCurKey = 0;
+ pszEqual = psz;
+ }
+
+ /* Match the keys. */
+ /** @todo escape sequences? */
+ if ( cchCurKey == cchKey
+ && RTStrNICmp(pszCurKey, pszKey, cchKey) == 0)
+ {
+ /*
+ * Copy out the return value, without quotes.
+ */
+
+ /* Skip leading blanks. */
+ psz = pszEqual + 1;
+ while ((ch = *psz) && RT_C_IS_SPACE(ch) && ch != '\n')
+ psz++;
+
+ /* Strip trailing spaces. */
+ size_t cchCurValue = pszNext - psz;
+ while (cchCurValue > 1 && RT_C_IS_SPACE(psz[cchCurValue - 1]))
+ cchCurValue--;
+
+ /* Strip quotes. */
+ if ( cchCurValue > 2
+ && ( (ch = *psz) == '"'
+ || ch == '\'' )
+ && psz[cchCurValue - 1] == ch)
+ {
+ cchCurValue -= 2;
+ psz++;
+ }
+
+ /* Do the copying. */
+ if (cchCurValue < cbValue)
+ {
+ memcpy(pszValue, psz, cchCurValue);
+ pszValue[cchCurValue] = '\0';
+ if (pcbActual)
+ *pcbActual = cchCurValue;
+ return VINF_SUCCESS;
+ }
+
+ if (cbValue > 0)
+ {
+ memcpy(pszValue, psz, cbValue - 1);
+ pszValue[cbValue - 1] = '\0';
+ }
+ if (pcbActual)
+ *pcbActual = cchCurValue + 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTIniFileQueryValue(RTINIFILE hIniFile, const char *pszSection, const char *pszKey,
+ char *pszValue, size_t cbValue, size_t *pcbActual)
+{
+ /*
+ * Validate input.
+ */
+ PRTINIFILEINT pThis = hIniFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pszSection, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ size_t const cchKey = strlen(pszKey);
+ if (cbValue)
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pcbActual, VERR_INVALID_POINTER);
+
+ /*
+ * Search relevant sections.
+ */
+ int rc;
+ if (pszSection == NULL)
+ rc = rtIniFileQueryValueInSection(pThis, &pThis->paSections[0], pszKey, cchKey, pszValue, cbValue, pcbActual);
+ else
+ {
+ rc = VERR_NOT_FOUND;
+ uint32_t const cchSection = (uint32_t)strlen(pszSection);
+ for (uint32_t iSection = 1; iSection < pThis->cSections; iSection++)
+ if ( pThis->paSections[iSection].cchName == cchSection
+ && RTStrNICmp(&pThis->pszFile[pThis->paSections[iSection].offName], pszSection, cchSection) == 0)
+ {
+ rc = rtIniFileQueryValueInSection(pThis, &pThis->paSections[iSection], pszKey, cchKey,
+ pszValue, cbValue, pcbActual);
+ if (rc != VERR_NOT_FOUND)
+ break;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Worker for RTIniFileQueryPair.
+ *
+ * This can also be used to count the number of pairs in a section.
+ */
+static int rtIniFileQueryPairInSection(PRTINIFILEINT pThis, PRTINIFILESECTION pSection, uint32_t *pidxPair,
+ char *pszKey, size_t cbKey, size_t *pcbKeyActual,
+ char *pszValue, size_t cbValue, size_t *pcbValueActual)
+{
+ uint32_t idxPair = *pidxPair;
+
+ /*
+ * Scan the section, looking for the matching key.
+ */
+ Assert(pSection->cchSkipToValues <= pSection->cchSection);
+ const char * const pszEnd = &pThis->pszFile[pSection->offName + pSection->cchSection];
+ const char * pszNext = pszEnd;
+ for (const char *psz = &pThis->pszFile[pSection->offName + pSection->cchSkipToValues];
+ (uintptr_t)psz < (uintptr_t)pszEnd;
+ psz = pszNext)
+ {
+ /* Find start of next line so we can use 'continue' to skip a line. */
+ pszNext = strchr(psz, '\n');
+ if (pszNext)
+ pszNext++;
+ else
+ pszNext = pszEnd;
+
+ /* Skip leading spaces. */
+ char ch;
+ while ((ch = *psz) != '\0' && RT_C_IS_SPACE(ch))
+ psz++;
+ if ( ch != ';' /* comment line */
+ && ch != '\n' /* empty line */
+ && ch != '\r' /* empty line */
+ && (uintptr_t)psz < (uintptr_t)pszEnd)
+ {
+ /* Find end of key name, if any. */
+ const char *pszCurKey = psz;
+ size_t cchCurKey;
+ const char *pszEqual;
+ if (ch != '=')
+ {
+ /** @todo deal with escaped equal signs? */
+ pszEqual = strchr(psz, '=');
+ if (pszEqual)
+ {
+ if ((uintptr_t)pszEqual < (uintptr_t)pszNext)
+ cchCurKey = pszEqual - pszCurKey;
+ else
+ continue;
+ }
+ else
+ break;
+ }
+ else
+ {
+ cchCurKey = 0;
+ pszEqual = psz;
+ }
+
+ /* Is this the pair we're looking for? */
+ if (idxPair > 0)
+ idxPair--;
+ else
+ {
+ /*
+ * Yes it's the stuff we're looking for.
+ * Prepare the the return stuff.
+ */
+
+ /* Strip trailing spaces from the key name. */
+ while (cchCurKey > 0 && RT_C_IS_SPACE(pszCurKey[cchCurKey - 1]))
+ cchCurKey--;
+
+ /* Skip leading blanks from the value. */
+ psz = pszEqual + 1;
+ while ((ch = *psz) && RT_C_IS_SPACE(ch) && ch != '\n')
+ psz++;
+
+ /* Strip trailing spaces from the value. */
+ size_t cchCurValue = pszNext - psz;
+ while (cchCurValue > 1 && RT_C_IS_SPACE(psz[cchCurValue - 1]))
+ cchCurValue--;
+
+ /* Strip value quotes. */
+ if ( cchCurValue > 2
+ && ( (ch = *psz) == '"'
+ || ch == '\'' )
+ && psz[cchCurValue - 1] == ch)
+ {
+ cchCurValue -= 2;
+ psz++;
+ }
+
+ /*
+ * Copy the stuff out.
+ */
+ if ( cchCurValue < cbValue
+ && cchCurKey < cbKey)
+ {
+ memcpy(pszKey, pszCurKey, cchCurKey);
+ pszKey[cchCurKey] = '\0';
+ if (pcbKeyActual)
+ *pcbKeyActual = cchCurKey;
+
+ memcpy(pszValue, psz, cchCurValue);
+ pszValue[cchCurValue] = '\0';
+ if (pcbValueActual)
+ *pcbValueActual = cchCurValue;
+
+ *pidxPair = 0;
+ return VINF_SUCCESS;
+ }
+
+ /* Buffer overflow. Copy out what we can. */
+ if (cbKey > 0)
+ {
+ if (cchCurKey < cbKey)
+ cbKey = cchCurKey + 1;
+ memcpy(pszKey, pszCurKey, cbKey - 1);
+ pszKey[cbKey - 1] = '\0';
+ }
+ if (pcbKeyActual)
+ *pcbKeyActual = cchCurKey + 1;
+
+ if (cbValue > 0)
+ {
+ if (cchCurValue < cbValue)
+ cbValue = cchCurValue + 1;
+ memcpy(pszValue, psz, cbValue - 1);
+ pszValue[cbValue - 1] = '\0';
+ }
+ if (pcbValueActual)
+ *pcbValueActual = cchCurValue + 1;
+
+ *pidxPair = 0;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ *pidxPair = idxPair;
+ return VERR_NOT_FOUND;
+}
+
+
+RTDECL(int) RTIniFileQueryPair(RTINIFILE hIniFile, const char *pszSection, uint32_t idxPair,
+ char *pszKey, size_t cbKey, size_t *pcbKeyActual,
+ char *pszValue, size_t cbValue, size_t *pcbValueActual)
+{
+ /*
+ * Validate input.
+ */
+ PRTINIFILEINT pThis = hIniFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTINIFILE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(pszSection, VERR_INVALID_POINTER);
+ if (cbKey)
+ AssertPtrReturn(pszKey, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pcbKeyActual, VERR_INVALID_POINTER);
+ if (cbValue)
+ AssertPtrReturn(pszValue, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pcbValueActual, VERR_INVALID_POINTER);
+
+ /*
+ * Search relevant sections.
+ */
+ int rc;
+ if (pszSection == NULL)
+ rc = rtIniFileQueryPairInSection(pThis, &pThis->paSections[0], &idxPair,
+ pszKey, cbKey, pcbKeyActual, pszValue, cbValue, pcbValueActual);
+ else
+ {
+ rc = VERR_NOT_FOUND;
+ uint32_t const cchSection = (uint32_t)strlen(pszSection);
+ for (uint32_t iSection = 1; iSection < pThis->cSections; iSection++)
+ if ( pThis->paSections[iSection].cchName == cchSection
+ && RTStrNICmp(&pThis->pszFile[pThis->paSections[iSection].offName], pszSection, cchSection) == 0)
+ {
+ rc = rtIniFileQueryPairInSection(pThis, &pThis->paSections[iSection], &idxPair,
+ pszKey, cbKey, pcbKeyActual, pszValue, cbValue, pcbValueActual);
+ if (rc != VERR_NOT_FOUND)
+ break;
+ }
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/misc/json.cpp b/src/VBox/Runtime/common/misc/json.cpp
new file mode 100644
index 00000000..a74a8bf2
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/json.cpp
@@ -0,0 +1,1914 @@
+/* $Id: json.cpp $ */
+/** @file
+ * IPRT JSON parser API (JSON).
+ */
+
+/*
+ * Copyright (C) 2016-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/json.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/vfs.h>
+
+#include <stdlib.h> /* strtod() */
+#include <errno.h> /* errno */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * JSON parser position information.
+ */
+typedef struct RTJSONPOS
+{
+ /** Line in the source. */
+ size_t iLine;
+ /** Current start character .*/
+ size_t iChStart;
+ /** Current end character. */
+ size_t iChEnd;
+} RTJSONPOS;
+/** Pointer to a position. */
+typedef RTJSONPOS *PRTJSONPOS;
+
+/**
+ * JSON token class.
+ */
+typedef enum RTJSONTOKENCLASS
+{
+ /** Invalid. */
+ RTJSONTOKENCLASS_INVALID = 0,
+ /** Array begin. */
+ RTJSONTOKENCLASS_BEGIN_ARRAY,
+ /** Object begin. */
+ RTJSONTOKENCLASS_BEGIN_OBJECT,
+ /** Array end. */
+ RTJSONTOKENCLASS_END_ARRAY,
+ /** Object end. */
+ RTJSONTOKENCLASS_END_OBJECT,
+ /** Separator for name/value pairs. */
+ RTJSONTOKENCLASS_NAME_SEPARATOR,
+ /** Value separator. */
+ RTJSONTOKENCLASS_VALUE_SEPARATOR,
+ /** String */
+ RTJSONTOKENCLASS_STRING,
+ /** Integer number. */
+ RTJSONTOKENCLASS_INTEGER,
+ /** Floating point number. */
+ RTJSONTOKENCLASS_NUMBER,
+ /** null keyword. */
+ RTJSONTOKENCLASS_NULL,
+ /** false keyword. */
+ RTJSONTOKENCLASS_FALSE,
+ /** true keyword. */
+ RTJSONTOKENCLASS_TRUE,
+ /** End of stream */
+ RTJSONTOKENCLASS_EOS,
+ /** 32bit hack. */
+ RTJSONTOKENCLASS_32BIT_HACK = 0x7fffffff
+} RTJSONTOKENCLASS;
+/** Pointer to a token class. */
+typedef RTJSONTOKENCLASS *PRTJSONTOKENCLASS;
+
+/**
+ * JSON token.
+ */
+typedef struct RTJSONTOKEN
+{
+ /** Token class. */
+ RTJSONTOKENCLASS enmClass;
+ /** Token position in the source buffer. */
+ RTJSONPOS Pos;
+ /** Data based on the token class. */
+ union
+ {
+ /** String. */
+ struct
+ {
+ /** Pointer to the start of the string. */
+ char *pszStr;
+ } String;
+ /** Number. */
+ struct
+ {
+ int64_t i64Num;
+ } Integer;
+ /** Floating point number. */
+ double rdNum;
+ } Class;
+} RTJSONTOKEN;
+/** Pointer to a JSON token. */
+typedef RTJSONTOKEN *PRTJSONTOKEN;
+/** Pointer to a const script token. */
+typedef const RTJSONTOKEN *PCRTJSONTOKEN;
+
+/**
+ * Tokenizer read input callback.
+ *
+ * @returns IPRT status code.
+ * @param pvUser Opaque user data for the callee.
+ * @param offInput Start offset from the start of the input stream to read from.
+ * @param pvBuf Where to store the read data.
+ * @param cbBuf How much to read.
+ * @param pcbRead Where to store the amount of data read on success.
+ */
+typedef DECLCALLBACKTYPE(int, FNRTJSONTOKENIZERREAD,(void *pvUser, size_t offInput, void *pvBuf, size_t cbBuf,
+ size_t *pcbRead));
+/** Pointer to a tokenizer read buffer callback. */
+typedef FNRTJSONTOKENIZERREAD *PFNRTJSONTOKENIZERREAD;
+
+/**
+ * Tokenizer state.
+ */
+typedef struct RTJSONTOKENIZER
+{
+ /** Read callback. */
+ PFNRTJSONTOKENIZERREAD pfnRead;
+ /** Opaque user data. */
+ void *pvUser;
+ /** Current offset into the input stream. */
+ size_t offInput;
+ /** Number of valid bytes in the input buffer. */
+ size_t cbBuf;
+ /** Current offset into the input buffer. */
+ size_t offBuf;
+ /** Input cache buffer. */
+ char achBuf[512];
+ /** Current position into the input stream. */
+ RTJSONPOS Pos;
+ /** Token 1. */
+ RTJSONTOKEN Token1;
+ /** Token 2. */
+ RTJSONTOKEN Token2;
+ /** Pointer to the current active token. */
+ PRTJSONTOKEN pTokenCurr;
+ /** The next token in the input stream (used for peeking). */
+ PRTJSONTOKEN pTokenNext;
+ /** The tokenizer error state. */
+ int rcTok;
+ /** Where to return extended error information.*/
+ PRTERRINFO pErrInfo;
+} RTJSONTOKENIZER;
+/** Pointer to a JSON tokenizer. */
+typedef RTJSONTOKENIZER *PRTJSONTOKENIZER;
+
+/** Pointer to the internal JSON value instance. */
+typedef struct RTJSONVALINT *PRTJSONVALINT;
+
+/**
+ * A JSON value.
+ */
+typedef struct RTJSONVALINT
+{
+ /** Type of the JSON value. */
+ RTJSONVALTYPE enmType;
+ /** Reference count for this JSON value. */
+ volatile uint32_t cRefs;
+ /** Type dependent data. */
+ union
+ {
+ /** String type*/
+ struct
+ {
+ /** Pointer to the string. */
+ char *pszStr;
+ } String;
+ /** Number type. */
+ struct
+ {
+ /** Signed 64-bit integer. */
+ int64_t i64Num;
+ } Integer;
+ /** Floating point number . */
+ double rdNum;
+ /** Array type. */
+ struct
+ {
+ /** Number of elements in the array. */
+ unsigned cItems;
+ /** Pointer to the array of items. */
+ PRTJSONVALINT *papItems;
+ } Array;
+ /** Object type. */
+ struct
+ {
+ /** Number of members. */
+ unsigned cMembers;
+ /** Pointer to the array holding the member names. */
+ char **papszNames;
+ /** Pointer to the array holding the values. */
+ PRTJSONVALINT *papValues;
+ } Object;
+ } Type;
+} RTJSONVALINT;
+
+/**
+ * A JSON iterator.
+ */
+typedef struct RTJSONITINT
+{
+ /** Referenced JSON value. */
+ PRTJSONVALINT pJsonVal;
+ /** Current index. */
+ unsigned idxCur;
+} RTJSONITINT;
+/** Pointer to the internal JSON iterator instance. */
+typedef RTJSONITINT *PRTJSONITINT;
+
+/**
+ * Passing arguments for the read callbacks.
+ */
+typedef struct RTJSONREADERARGS
+{
+ /** Buffer/File size */
+ size_t cbData;
+ /** Data specific for one callback. */
+ union
+ {
+ PRTSTREAM hStream;
+ const uint8_t *pbBuf;
+ RTVFSFILE hVfsFile;
+ } u;
+} RTJSONREADERARGS;
+/** Pointer to a readers argument. */
+typedef RTJSONREADERARGS *PRTJSONREADERARGS;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtJsonParseValue(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken, PRTJSONVALINT *ppJsonVal);
+
+
+/**
+ * Fill the input buffer from the input stream.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ */
+static int rtJsonTokenizerRead(PRTJSONTOKENIZER pTokenizer)
+{
+ size_t cbRead = 0;
+ int rc = pTokenizer->pfnRead(pTokenizer->pvUser, pTokenizer->offInput, &pTokenizer->achBuf[0],
+ sizeof(pTokenizer->achBuf), &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ pTokenizer->cbBuf = cbRead;
+ pTokenizer->offInput += cbRead;
+ pTokenizer->offBuf = 0;
+ /* Validate UTF-8 encoding. */
+ rc = RTStrValidateEncodingEx(&pTokenizer->achBuf[0], cbRead, 0 /* fFlags */);
+ /* If we read less than requested we reached the end and fill the remainder with terminators. */
+ if (cbRead < sizeof(pTokenizer->achBuf))
+ memset(&pTokenizer->achBuf[cbRead], 0, sizeof(pTokenizer->achBuf) - cbRead);
+ }
+
+ return rc;
+}
+
+/**
+ * Skips the given amount of characters in the input stream.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param cchSkip The amount of characters to skip.
+ */
+static int rtJsonTokenizerSkip(PRTJSONTOKENIZER pTokenizer, size_t cchSkip)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * In case we reached the end of the stream don't even attempt to read new data.
+ * Safety precaution for possible bugs in the parser causing out of bounds reads
+ */
+ if (pTokenizer->achBuf[pTokenizer->offBuf] == '\0')
+ return rc;
+
+ while ( cchSkip > 0
+ && pTokenizer->offBuf < pTokenizer->cbBuf
+ && RT_SUCCESS(rc))
+ {
+ size_t cchThisSkip = RT_MIN(cchSkip, pTokenizer->cbBuf - pTokenizer->offBuf);
+
+ pTokenizer->offBuf += cchThisSkip;
+ /* Read new data if required and we didn't reach the end yet. */
+ if ( pTokenizer->offBuf == pTokenizer->cbBuf
+ && pTokenizer->cbBuf == sizeof(pTokenizer->achBuf))
+ rc = rtJsonTokenizerRead(pTokenizer);
+
+ cchSkip -= cchThisSkip;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Returns whether the tokenizer reached the end of the stream.
+ *
+ * @returns true if the tokenizer reached the end of stream marker
+ * false otherwise.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) rtJsonTokenizerIsEos(PRTJSONTOKENIZER pTokenizer)
+{
+ return pTokenizer->achBuf[pTokenizer->offBuf] == '\0';
+}
+
+/**
+ * Skip one character in the input stream.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) rtJsonTokenizerSkipCh(PRTJSONTOKENIZER pTokenizer)
+{
+ rtJsonTokenizerSkip(pTokenizer, 1);
+ pTokenizer->Pos.iChStart++;
+ pTokenizer->Pos.iChEnd++;
+}
+
+/**
+ * Returns the next char in the input buffer without advancing it.
+ *
+ * @returns Next character in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) rtJsonTokenizerPeekCh(PRTJSONTOKENIZER pTokenizer)
+{
+ return !rtJsonTokenizerIsEos(pTokenizer)
+ ? pTokenizer->achBuf[pTokenizer->offBuf + 1] /** @todo Read out of bounds */
+ : '\0';
+}
+
+/**
+ * Returns the next character in the input buffer advancing the internal
+ * position.
+ *
+ * @returns Next character in the stream.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(char) rtJsonTokenizerGetCh(PRTJSONTOKENIZER pTokenizer)
+{
+ char ch;
+
+ if (!rtJsonTokenizerIsEos(pTokenizer))
+ ch = pTokenizer->achBuf[pTokenizer->offBuf];
+ else
+ ch = '\0';
+
+ return ch;
+}
+
+/**
+ * Sets a new line for the tokenizer.
+ *
+ * @param pTokenizer The tokenizer state.
+ * @param cSkip Amount of characters to skip making up the new line.
+ */
+DECLINLINE(void) rtJsonTokenizerNewLine(PRTJSONTOKENIZER pTokenizer, unsigned cSkip)
+{
+ rtJsonTokenizerSkip(pTokenizer, cSkip);
+ pTokenizer->Pos.iLine++;
+ pTokenizer->Pos.iChStart = 1;
+ pTokenizer->Pos.iChEnd = 1;
+}
+
+/**
+ * Checks whether the current position in the input stream is a new line
+ * and skips it.
+ *
+ * @returns Flag whether there was a new line at the current position
+ * in the input buffer.
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(bool) rtJsonTokenizerIsSkipNewLine(PRTJSONTOKENIZER pTokenizer)
+{
+ bool fNewline = true;
+
+ if ( rtJsonTokenizerGetCh(pTokenizer) == '\r'
+ && rtJsonTokenizerPeekCh(pTokenizer) == '\n')
+ rtJsonTokenizerNewLine(pTokenizer, 2);
+ else if (rtJsonTokenizerGetCh(pTokenizer) == '\n')
+ rtJsonTokenizerNewLine(pTokenizer, 1);
+ else
+ fNewline = false;
+
+ return fNewline;
+}
+
+/**
+ * Skip all whitespace starting from the current input buffer position.
+ * Skips all present comments too.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+DECLINLINE(void) rtJsonTokenizerSkipWhitespace(PRTJSONTOKENIZER pTokenizer)
+{
+ while (!rtJsonTokenizerIsEos(pTokenizer))
+ {
+ while ( rtJsonTokenizerGetCh(pTokenizer) == ' '
+ || rtJsonTokenizerGetCh(pTokenizer) == '\t')
+ rtJsonTokenizerSkipCh(pTokenizer);
+
+ if ( !rtJsonTokenizerIsEos(pTokenizer)
+ && !rtJsonTokenizerIsSkipNewLine(pTokenizer))
+ break; /* Skipped everything, next is some real content. */
+ }
+}
+
+/**
+ * Get an literal token from the tokenizer.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static int rtJsonTokenizerGetLiteral(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ int rc = VINF_SUCCESS;
+ char ch = rtJsonTokenizerGetCh(pTokenizer);
+ size_t cchLiteral = 0;
+ char szLiteral[6]; /* false + 0 terminator as the lingest possible literal. */
+ RT_ZERO(szLiteral);
+
+ pToken->Pos = pTokenizer->Pos;
+
+ Assert(RT_C_IS_ALPHA(ch));
+
+ while ( RT_C_IS_ALPHA(ch)
+ && cchLiteral < RT_ELEMENTS(szLiteral) - 1)
+ {
+ szLiteral[cchLiteral] = ch;
+ cchLiteral++;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+
+ if (!RTStrNCmp(&szLiteral[0], "false", RT_ELEMENTS(szLiteral)))
+ pToken->enmClass = RTJSONTOKENCLASS_FALSE;
+ else if (!RTStrNCmp(&szLiteral[0], "true", RT_ELEMENTS(szLiteral)))
+ pToken->enmClass = RTJSONTOKENCLASS_TRUE;
+ else if (!RTStrNCmp(&szLiteral[0], "null", RT_ELEMENTS(szLiteral)))
+ pToken->enmClass = RTJSONTOKENCLASS_NULL;
+ else
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_INVALID;
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "malformed literal '%.6s' (line %zu col %zu)",
+ &szLiteral[0], pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ pToken->Pos.iChEnd += cchLiteral;
+ return rc;
+}
+
+/**
+ * Get a numerical constant from the tokenizer.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static int rtJsonTokenizerGetNumber(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ size_t cchNum = 0;
+ char szTmp[128]; /* Everything larger is not possible to display in signed 64bit. */
+
+ pToken->enmClass = RTJSONTOKENCLASS_INTEGER;
+
+ char ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (ch == '-')
+ {
+ szTmp[cchNum++] = '-';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+
+ while ( RT_C_IS_DIGIT(ch)
+ && cchNum < sizeof(szTmp) - 1)
+ {
+ szTmp[cchNum] = ch;
+ cchNum++;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+
+ int rc = VINF_SUCCESS;
+ if (RT_C_IS_DIGIT(ch) && cchNum >= sizeof(szTmp) - 1)
+ rc = VERR_NUMBER_TOO_BIG;
+ else if (ch != '.')
+ {
+ szTmp[cchNum] = '\0';
+ rc = RTStrToInt64Ex(&szTmp[0], NULL, 10, &pToken->Class.Integer.i64Num);
+ Assert(RT_SUCCESS(rc) || rc == VWRN_NUMBER_TOO_BIG);
+ if (rc == VWRN_NUMBER_TOO_BIG)
+ rc = VERR_NUMBER_TOO_BIG;
+ }
+ else
+ {
+ /*
+ * A floating point value.
+ */
+ pToken->enmClass = RTJSONTOKENCLASS_NUMBER;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ szTmp[cchNum++] = '.';
+
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ while ( RT_C_IS_DIGIT(ch)
+ && cchNum < sizeof(szTmp) - 1)
+ {
+ szTmp[cchNum++] = ch;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+ if ( (ch == 'e' || ch == 'E')
+ && cchNum < sizeof(szTmp) - 2)
+ {
+ szTmp[cchNum++] = 'e';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (ch == '+' || ch == '-')
+ {
+ szTmp[cchNum++] = ch;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+ while ( RT_C_IS_DIGIT(ch)
+ && cchNum < sizeof(szTmp) - 1)
+ {
+ szTmp[cchNum++] = ch;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+ }
+ if (cchNum < sizeof(szTmp) - 1)
+ {
+ szTmp[cchNum] = '\0';
+
+ /** @todo Not sure if strtod does the 100% right thing here... */
+ errno = 0;
+ char *pszNext = NULL;
+ pToken->Class.rdNum = strtod(szTmp, &pszNext);
+ if (errno == 0)
+ {
+ rc = VINF_SUCCESS;
+ Assert(!pszNext || *pszNext == '\0');
+ }
+ else
+ rc = RTErrConvertFromErrno(errno);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Parses a string constant.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static int rtJsonTokenizerGetString(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ size_t cchStrMax = 64;
+ char *pszDecoded = (char *)RTStrAlloc(cchStrMax);
+ AssertReturn(pszDecoded, VERR_NO_STR_MEMORY);
+
+ Assert(rtJsonTokenizerGetCh(pTokenizer) == '\"');
+ rtJsonTokenizerSkipCh(pTokenizer); /* Skip " */
+
+ pToken->enmClass = RTJSONTOKENCLASS_STRING;
+ pToken->Pos = pTokenizer->Pos;
+
+ size_t cchStr = 0;
+ char ch = rtJsonTokenizerGetCh(pTokenizer);
+ while ( ch != '\"'
+ && ch != '\0')
+ {
+ if (ch != '\\')
+ {
+ pszDecoded[cchStr++] = ch;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else
+ {
+ /* Escape sequence, check the next character */
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chNext = rtJsonTokenizerGetCh(pTokenizer);
+ switch (chNext)
+ {
+ case '\"':
+ pszDecoded[cchStr++] = '\"';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case '\\':
+ pszDecoded[cchStr++] = '\\';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case '/':
+ pszDecoded[cchStr++] = '/';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'b':
+ pszDecoded[cchStr++] = '\b';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'n':
+ pszDecoded[cchStr++] = '\n';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'f':
+ pszDecoded[cchStr++] = '\f';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'r':
+ pszDecoded[cchStr++] = '\r';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 't':
+ pszDecoded[cchStr++] = '\t';
+ rtJsonTokenizerSkipCh(pTokenizer);
+ break;
+ case 'u':
+ {
+ /* \uXXXX */
+ int rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chX1 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX1))
+ {
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chX2 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX2))
+ {
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chX3 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX3))
+ {
+ rtJsonTokenizerSkipCh(pTokenizer);
+ char chX4 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX4))
+ {
+ rtJsonTokenizerSkipCh(pTokenizer);
+
+ RTUNICP uc = ((RTUTF16)(chX1 <= '9' ? chX1 - '0' : (chX1 & 7) + 9) << 12)
+ | ((RTUTF16)(chX2 <= '9' ? chX2 - '0' : (chX2 & 7) + 9) << 8)
+ | ((RTUTF16)(chX3 <= '9' ? chX3 - '0' : (chX3 & 7) + 9) << 4)
+ | ((RTUTF16)(chX4 <= '9' ? chX4 - '0' : (chX4 & 7) + 9));
+ if ( !RTUtf16IsHighSurrogate((RTUTF16)uc)
+ && !RTUtf16IsLowSurrogate((RTUTF16)uc))
+ rc = VINF_SUCCESS;
+ else if (RTUtf16IsHighSurrogate((RTUTF16)uc))
+ {
+ /* The must be a low surrogate pair following the high one: */
+ rc = VINF_SUCCESS;
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (ch == '\\')
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else
+ rc = VERR_JSON_MISSING_SURROGATE_PAIR;
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (ch == 'u')
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else
+ rc = VERR_JSON_MISSING_SURROGATE_PAIR;
+ chX1 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX1))
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else if (RT_SUCCESS_NP(rc))
+ rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ chX2 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX2))
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else if (RT_SUCCESS_NP(rc))
+ rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ chX3 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX3))
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else if (RT_SUCCESS_NP(rc))
+ rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ chX4 = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_XDIGIT(chX4))
+ rtJsonTokenizerSkipCh(pTokenizer);
+ else if (RT_SUCCESS_NP(rc))
+ rc = VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE;
+ if (RT_SUCCESS(rc))
+ {
+ RTUTF16 wc2 = ((RTUTF16)(chX1 <= '9' ? chX1 - '0' : (chX1 & 7) + 9) << 12)
+ | ((RTUTF16)(chX2 <= '9' ? chX2 - '0' : (chX2 & 7) + 9) << 8)
+ | ((RTUTF16)(chX3 <= '9' ? chX3 - '0' : (chX3 & 7) + 9) << 4)
+ | ((RTUTF16)(chX4 <= '9' ? chX4 - '0' : (chX4 & 7) + 9));
+ if (RTUtf16IsLowSurrogate(wc2))
+ uc = 0x10000 + (((uc & 0x3ff) << 10) | (wc2 & 0x3ff));
+ else
+ rc = VERR_JSON_BAD_SURROGATE_PAIR_SEQUENCE;
+ }
+ }
+ else
+ rc = VERR_JSON_BAD_SURROGATE_PAIR_SEQUENCE;
+ if (RT_SUCCESS(rc))
+ {
+ if ( uc != 0
+ && uc != 0xfffe
+ && uc != 0xffff)
+ {
+ Assert(cchStr + RTStrCpSize(uc) < cchStrMax);
+ char *pszNext = RTStrPutCp(&pszDecoded[cchStr], uc);
+ Assert((size_t)(pszNext - &pszDecoded[cchStr]) == RTStrCpSize(uc));
+ cchStr += pszNext - &pszDecoded[cchStr];
+ break;
+ }
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_INVALID_CODEPOINT,
+ "Invalid \\u code point: %#x (line %zu col %zu)",
+ uc, pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+ }
+ }
+ }
+ }
+ RTStrFree(pszDecoded);
+ if (rc == VERR_JSON_INVALID_UTF16_ESCAPE_SEQUENCE)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, rc, "Invalid \\u escape sequence (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ else if (rc == VERR_JSON_MISSING_SURROGATE_PAIR)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, rc, "Missing UTF-16 surrogate pair (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ else if (rc == VERR_JSON_BAD_SURROGATE_PAIR_SEQUENCE)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, rc, "Invalid UTF-16 surrogate pair (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ return rc;
+ }
+
+ default:
+ RTStrFree(pszDecoded);
+ return RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "bad escape sequence (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+ }
+
+
+ if (cchStr < cchStrMax - 4)
+ { /* likely */ }
+ else
+ {
+ /* Increase string space. */
+ size_t cchStrMaxNew = cchStrMax < _4K ? cchStrMax * 2 : cchStrMax + _4K;
+ int rc = RTStrRealloc(&pszDecoded, cchStrMaxNew);
+ if (RT_SUCCESS(rc))
+ cchStrMax = cchStrMaxNew;
+ else
+ {
+ RTStrFree(pszDecoded);
+ return rc;
+ }
+ }
+ ch = rtJsonTokenizerGetCh(pTokenizer);
+ }
+
+ if (ch == '\"')
+ rtJsonTokenizerSkipCh(pTokenizer); /* Skip closing " */
+
+ Assert(cchStr < cchStrMax);
+ pszDecoded[cchStr] = '\0';
+ if (cchStrMax - cchStr >= cchStrMax / 2)
+ RTStrRealloc(&pszDecoded, cchStr + 1);
+ pToken->Class.String.pszStr = pszDecoded;
+
+ pToken->Pos.iChEnd = pTokenizer->Pos.iChEnd;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Get the end of stream token.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param pToken The uninitialized token.
+ */
+static int rtJsonTokenizerGetEos(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ Assert(rtJsonTokenizerGetCh(pTokenizer) == '\0');
+
+ pToken->enmClass = RTJSONTOKENCLASS_EOS;
+ pToken->Pos = pTokenizer->Pos;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the next token from the tokenizer stream.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer to read from.
+ * @param pToken Uninitialized token to fill the token data into.
+ */
+static int rtJsonTokenizerReadNextToken(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Skip all eventually existing whitespace and newlines first. */
+ rtJsonTokenizerSkipWhitespace(pTokenizer);
+
+ char ch = rtJsonTokenizerGetCh(pTokenizer);
+ if (RT_C_IS_ALPHA(ch))
+ rc = rtJsonTokenizerGetLiteral(pTokenizer, pToken);
+ else if (RT_C_IS_DIGIT(ch) || ch == '-')
+ rc = rtJsonTokenizerGetNumber(pTokenizer, pToken);
+ else if (ch == '\"')
+ rc = rtJsonTokenizerGetString(pTokenizer, pToken);
+ else if (ch == '\0')
+ rc = rtJsonTokenizerGetEos(pTokenizer, pToken);
+ else if (ch == '{')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_BEGIN_OBJECT;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == '}')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_END_OBJECT;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == '[')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_BEGIN_ARRAY;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == ']')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_END_ARRAY;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == ':')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_NAME_SEPARATOR;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else if (ch == ',')
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_VALUE_SEPARATOR;
+ rtJsonTokenizerSkipCh(pTokenizer);
+ }
+ else
+ {
+ pToken->enmClass = RTJSONTOKENCLASS_INVALID;
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "bad token '%c' (line %zu col %zu)",
+ ch, pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ if (RT_FAILURE(rc))
+ pTokenizer->rcTok = rc;
+
+ return rc;
+}
+
+/**
+ * Create a new tokenizer.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state to initialize.
+ * @param pfnRead Read callback for the input stream.
+ * @param pvUser Opaque user data to pass to the callback.
+ * @param pErrInfo Where to return extended error info.
+ */
+static int rtJsonTokenizerInit(PRTJSONTOKENIZER pTokenizer, PFNRTJSONTOKENIZERREAD pfnRead, void *pvUser, PRTERRINFO pErrInfo)
+{
+ pTokenizer->pfnRead = pfnRead;
+ pTokenizer->pvUser = pvUser;
+ pTokenizer->offInput = 0;
+ pTokenizer->cbBuf = 0;
+ pTokenizer->offBuf = 0;
+ pTokenizer->Pos.iLine = 1;
+ pTokenizer->Pos.iChStart = 1;
+ pTokenizer->Pos.iChEnd = 1;
+ pTokenizer->pTokenCurr = &pTokenizer->Token1;
+ pTokenizer->pTokenNext = &pTokenizer->Token2;
+ pTokenizer->rcTok = VINF_SUCCESS;
+ pTokenizer->pErrInfo = pErrInfo;
+
+ RT_ZERO(pTokenizer->achBuf);
+
+ /* Fill the input buffer. */
+ int rc = rtJsonTokenizerRead(pTokenizer);
+
+ /* Fill the tokenizer with two first tokens. */
+ if (RT_SUCCESS(rc))
+ rc = rtJsonTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenCurr);
+ if (RT_SUCCESS(rc))
+ rc = rtJsonTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+
+ return rc;
+}
+
+/**
+ * Cleans up any resources still in control of the given token.
+ *
+ * @param pToken The toke nto clean up.
+ */
+static void rtJsonTokenizerTokenCleanup(PRTJSONTOKEN pToken)
+{
+ if ( pToken->enmClass == RTJSONTOKENCLASS_STRING
+ && pToken->Class.String.pszStr)
+ RTStrFree(pToken->Class.String.pszStr);
+}
+
+/**
+ * Destroys a given tokenizer state.
+ *
+ * @param pTokenizer The tokenizer to destroy.
+ */
+static void rtJsonTokenizerDestroy(PRTJSONTOKENIZER pTokenizer)
+{
+ rtJsonTokenizerTokenCleanup(pTokenizer->pTokenCurr);
+ rtJsonTokenizerTokenCleanup(pTokenizer->pTokenNext);
+}
+
+/**
+ * Get the current token in the input stream.
+ *
+ * @returns Pointer to the next token in the stream.
+ * @param pTokenizer The tokenizer state.
+ * @param ppToken Where to store the pointer to the current token on success.
+ */
+DECLINLINE(int) rtJsonTokenizerGetToken(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN *ppToken)
+{
+ if (RT_SUCCESS(pTokenizer->rcTok))
+ {
+ *ppToken = pTokenizer->pTokenCurr;
+ return VINF_SUCCESS;
+ }
+
+ return pTokenizer->rcTok;
+}
+
+/**
+ * Consume the current token advancing to the next in the stream.
+ *
+ * @param pTokenizer The tokenizer state.
+ */
+static void rtJsonTokenizerConsume(PRTJSONTOKENIZER pTokenizer)
+{
+ PRTJSONTOKEN pTokenTmp = pTokenizer->pTokenCurr;
+
+ /* Switch next token to current token and read in the next token. */
+ pTokenizer->pTokenCurr = pTokenizer->pTokenNext;
+ pTokenizer->pTokenNext = pTokenTmp;
+ rtJsonTokenizerReadNextToken(pTokenizer, pTokenizer->pTokenNext);
+}
+
+/**
+ * Consumes the current token if it matches the given class returning an indicator.
+ *
+ * @returns true if the class matched and the token was consumed.
+ * @retval false otherwise.
+ * @param pTokenizer The tokenizer state.
+ * @param enmClass The token class to match against.
+ */
+static bool rtJsonTokenizerConsumeIfMatched(PRTJSONTOKENIZER pTokenizer, RTJSONTOKENCLASS enmClass)
+{
+ PRTJSONTOKEN pToken = NULL;
+ int rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ if (RT_SUCCESS(rc))
+ {
+ if (pToken->enmClass == enmClass)
+ {
+ rtJsonTokenizerConsume(pTokenizer);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Destroys a given JSON value releasing the reference to all child values.
+ *
+ * @param pThis The JSON value to destroy.
+ */
+static void rtJsonValDestroy(PRTJSONVALINT pThis)
+{
+ switch (pThis->enmType)
+ {
+ case RTJSONVALTYPE_OBJECT:
+ for (unsigned i = 0; i < pThis->Type.Object.cMembers; i++)
+ {
+ RTStrFree(pThis->Type.Object.papszNames[i]);
+ RTJsonValueRelease(pThis->Type.Object.papValues[i]);
+ }
+ RTMemFree(pThis->Type.Object.papszNames);
+ RTMemFree(pThis->Type.Object.papValues);
+ break;
+ case RTJSONVALTYPE_ARRAY:
+ for (unsigned i = 0; i < pThis->Type.Array.cItems; i++)
+ RTJsonValueRelease(pThis->Type.Array.papItems[i]);
+ RTMemFree(pThis->Type.Array.papItems);
+ break;
+ case RTJSONVALTYPE_STRING:
+ RTStrFree(pThis->Type.String.pszStr);
+ break;
+ case RTJSONVALTYPE_INTEGER:
+ case RTJSONVALTYPE_NUMBER:
+ case RTJSONVALTYPE_NULL:
+ case RTJSONVALTYPE_TRUE:
+ case RTJSONVALTYPE_FALSE:
+ /* nothing to do. */
+ break;
+ default:
+ AssertMsgFailed(("Invalid JSON value type: %u\n", pThis->enmType));
+ }
+ RTMemFree(pThis);
+}
+
+/**
+ * Creates a new JSON value with the given type.
+ *
+ * @returns Pointer to JSON value on success, NULL if out of memory.
+ * @param enmType The JSON value type.
+ */
+static PRTJSONVALINT rtJsonValueCreate(RTJSONVALTYPE enmType)
+{
+ PRTJSONVALINT pThis = (PRTJSONVALINT)RTMemAllocZ(sizeof(RTJSONVALINT));
+ if (RT_LIKELY(pThis))
+ {
+ pThis->enmType = enmType;
+ pThis->cRefs = 1;
+ }
+
+ return pThis;
+}
+
+/**
+ * Parses an JSON array.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer to use.
+ * @param pJsonVal The JSON array value to fill in.
+ */
+static int rtJsonParseArray(PRTJSONTOKENIZER pTokenizer, PRTJSONVALINT pJsonVal)
+{
+ int rc = VINF_SUCCESS;
+ PRTJSONTOKEN pToken = NULL;
+ uint32_t cItems = 0;
+ uint32_t cItemsMax = 0;
+ PRTJSONVALINT *papItems = NULL;
+
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ while ( RT_SUCCESS(rc)
+ && pToken->enmClass != RTJSONTOKENCLASS_END_ARRAY
+ && pToken->enmClass != RTJSONTOKENCLASS_EOS)
+ {
+ PRTJSONVALINT pVal = NULL;
+ rc = rtJsonParseValue(pTokenizer, pToken, &pVal);
+ if (RT_SUCCESS(rc))
+ {
+ if (cItems == cItemsMax)
+ {
+ cItemsMax += 10;
+ PRTJSONVALINT *papItemsNew = (PRTJSONVALINT *)RTMemRealloc(papItems, cItemsMax * sizeof(PRTJSONVALINT));
+ if (RT_UNLIKELY(!papItemsNew))
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ papItems = papItemsNew;
+ }
+
+ Assert(cItems < cItemsMax);
+ papItems[cItems] = pVal;
+ cItems++;
+ }
+
+ /* Skip value separator and continue with next token. */
+ bool fSkippedSep = rtJsonTokenizerConsumeIfMatched(pTokenizer, RTJSONTOKENCLASS_VALUE_SEPARATOR);
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+
+ if ( RT_SUCCESS(rc)
+ && !fSkippedSep
+ && pToken->enmClass != RTJSONTOKENCLASS_END_ARRAY)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of array (#1) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pToken->enmClass == RTJSONTOKENCLASS_END_ARRAY)
+ {
+ rtJsonTokenizerConsume(pTokenizer);
+ pJsonVal->Type.Array.cItems = cItems;
+ pJsonVal->Type.Array.papItems = papItems;
+ }
+ else
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of array (#2) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ for (uint32_t i = 0; i < cItems; i++)
+ RTJsonValueRelease(papItems[i]);
+ RTMemFree(papItems);
+ }
+
+ return rc;
+}
+
+/**
+ * Parses an JSON object.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer to use.
+ * @param pJsonVal The JSON object value to fill in.
+ */
+static int rtJsonParseObject(PRTJSONTOKENIZER pTokenizer, PRTJSONVALINT pJsonVal)
+{
+ int rc = VINF_SUCCESS;
+ PRTJSONTOKEN pToken = NULL;
+ uint32_t cMembers = 0;
+ uint32_t cMembersMax = 0;
+ PRTJSONVALINT *papValues = NULL;
+ char **papszNames = NULL;
+
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ while ( RT_SUCCESS(rc)
+ && pToken->enmClass == RTJSONTOKENCLASS_STRING)
+ {
+ char *pszName = pToken->Class.String.pszStr; /* We can consume this string as it was allocated. */
+ pToken->Class.String.pszStr = NULL;
+
+ rtJsonTokenizerConsume(pTokenizer);
+ if (rtJsonTokenizerConsumeIfMatched(pTokenizer, RTJSONTOKENCLASS_NAME_SEPARATOR))
+ {
+ PRTJSONVALINT pVal = NULL;
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ if (RT_SUCCESS(rc))
+ rc = rtJsonParseValue(pTokenizer, pToken, &pVal);
+ if (RT_SUCCESS(rc))
+ {
+ if (cMembers == cMembersMax)
+ {
+ cMembersMax += 10;
+ PRTJSONVALINT *papValuesNew = (PRTJSONVALINT *)RTMemRealloc(papValues, cMembersMax * sizeof(PRTJSONVALINT));
+ char **papszNamesNew = (char **)RTMemRealloc(papszNames, cMembersMax * sizeof(char *));
+ if (RT_UNLIKELY(!papValuesNew || !papszNamesNew))
+ {
+ if (papValuesNew)
+ RTMemFree(papValuesNew);
+ if (papszNamesNew)
+ RTMemFree(papszNamesNew);
+ RTStrFree(pszName);
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+
+ papValues = papValuesNew;
+ papszNames = papszNamesNew;
+ }
+
+ Assert(cMembers < cMembersMax);
+ papszNames[cMembers] = pszName;
+ papValues[cMembers] = pVal;
+ cMembers++;
+
+ /* Skip value separator and continue with next token. */
+ bool fSkippedSep = rtJsonTokenizerConsumeIfMatched(pTokenizer, RTJSONTOKENCLASS_VALUE_SEPARATOR);
+ rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+
+ if ( RT_SUCCESS(rc)
+ && !fSkippedSep
+ && pToken->enmClass != RTJSONTOKENCLASS_END_OBJECT)
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of object (#1) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+ else
+ RTStrFree(pszName);
+ }
+ else
+ {
+ RTStrFree(pszName);
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected name separator (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pToken->enmClass == RTJSONTOKENCLASS_END_OBJECT)
+ {
+ rtJsonTokenizerConsume(pTokenizer);
+ pJsonVal->Type.Object.cMembers = cMembers;
+ pJsonVal->Type.Object.papValues = papValues;
+ pJsonVal->Type.Object.papszNames = papszNames;
+ }
+ else
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of object (#2) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ for (uint32_t i = 0; i < cMembers; i++)
+ {
+ RTJsonValueRelease(papValues[i]);
+ RTStrFree(papszNames[i]);
+ }
+ RTMemFree(papValues);
+ RTMemFree(papszNames);
+ }
+
+ return rc;
+}
+
+/**
+ * Parses a single JSON value and returns it on success.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer to use.
+ * @param pToken The token to parse.
+ * @param ppJsonVal Where to store the pointer to the JSON value on success.
+ */
+static int rtJsonParseValue(PRTJSONTOKENIZER pTokenizer, PRTJSONTOKEN pToken, PRTJSONVALINT *ppJsonVal)
+{
+ int rc = VINF_SUCCESS;
+ PRTJSONVALINT pVal = NULL;
+
+ switch (pToken->enmClass)
+ {
+ case RTJSONTOKENCLASS_BEGIN_ARRAY:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_ARRAY);
+ if (RT_LIKELY(pVal))
+ rc = rtJsonParseArray(pTokenizer, pVal);
+ break;
+ case RTJSONTOKENCLASS_BEGIN_OBJECT:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_OBJECT);
+ if (RT_LIKELY(pVal))
+ rc = rtJsonParseObject(pTokenizer, pVal);
+ break;
+ case RTJSONTOKENCLASS_STRING:
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_STRING);
+ if (RT_LIKELY(pVal))
+ pVal->Type.String.pszStr = pToken->Class.String.pszStr;
+ rtJsonTokenizerConsume(pTokenizer);
+ break;
+ case RTJSONTOKENCLASS_INTEGER:
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_INTEGER);
+ if (RT_LIKELY(pVal))
+ pVal->Type.Integer.i64Num = pToken->Class.Integer.i64Num;
+ rtJsonTokenizerConsume(pTokenizer);
+ break;
+ case RTJSONTOKENCLASS_NUMBER:
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_NUMBER);
+ if (RT_LIKELY(pVal))
+ pVal->Type.rdNum = pToken->Class.rdNum;
+ rtJsonTokenizerConsume(pTokenizer);
+ break;
+ case RTJSONTOKENCLASS_NULL:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_NULL);
+ break;
+ case RTJSONTOKENCLASS_FALSE:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_FALSE);
+ break;
+ case RTJSONTOKENCLASS_TRUE:
+ rtJsonTokenizerConsume(pTokenizer);
+ pVal = rtJsonValueCreate(RTJSONVALTYPE_TRUE);
+ break;
+
+ case RTJSONTOKENCLASS_INVALID:
+ Assert(!pTokenizer->pErrInfo || RTErrInfoIsSet(pTokenizer->pErrInfo));
+ rc = VERR_JSON_MALFORMED;
+ break;
+ case RTJSONTOKENCLASS_END_ARRAY:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "unexpected '}' (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ case RTJSONTOKENCLASS_END_OBJECT:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "unexpected ']' (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ case RTJSONTOKENCLASS_NAME_SEPARATOR:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "unexpected ':' (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ case RTJSONTOKENCLASS_VALUE_SEPARATOR:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "unexpected ',' (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ case RTJSONTOKENCLASS_EOS:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "expected end of object (#1) (line %zu col %zu)",
+ pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ default:
+ rc = RTErrInfoSetF(pTokenizer->pErrInfo, VERR_JSON_MALFORMED, "Unexpected token class %d (line %zu col %zu)",
+ pToken->enmClass, pTokenizer->Pos.iLine, pTokenizer->Pos.iChStart);
+ break;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ if (pVal)
+ *ppJsonVal = pVal;
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else if (pVal)
+ rtJsonValDestroy(pVal);
+
+ return rc;
+}
+
+/**
+ * Entry point to parse a JSON document.
+ *
+ * @returns IPRT status code.
+ * @param pTokenizer The tokenizer state.
+ * @param ppJsonVal Where to store the root JSON value on success.
+ */
+static int rtJsonParse(PRTJSONTOKENIZER pTokenizer, PRTJSONVALINT *ppJsonVal)
+{
+ PRTJSONTOKEN pToken = NULL;
+ int rc = rtJsonTokenizerGetToken(pTokenizer, &pToken);
+ if (RT_SUCCESS(rc))
+ rc = rtJsonParseValue(pTokenizer, pToken, ppJsonVal);
+
+ return rc;
+}
+
+/**
+ * Read callback for RTJsonParseFromBuf().
+ */
+static DECLCALLBACK(int) rtJsonTokenizerParseFromBuf(void *pvUser, size_t offInput,
+ void *pvBuf, size_t cbBuf,
+ size_t *pcbRead)
+{
+ PRTJSONREADERARGS pArgs = (PRTJSONREADERARGS)pvUser;
+ size_t cbLeft = offInput < pArgs->cbData ? pArgs->cbData - offInput : 0;
+
+ if (cbLeft)
+ memcpy(pvBuf, &pArgs->u.pbBuf[offInput], RT_MIN(cbLeft, cbBuf));
+
+ *pcbRead = RT_MIN(cbLeft, cbBuf);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read callback for RTJsonParseFromString().
+ */
+static DECLCALLBACK(int) rtJsonTokenizerParseFromString(void *pvUser, size_t offInput,
+ void *pvBuf, size_t cbBuf,
+ size_t *pcbRead)
+{
+ const char *pszStr = (const char *)pvUser;
+ size_t cchStr = strlen(pszStr) + 1; /* Include zero terminator. */
+ size_t cbLeft = offInput < cchStr ? cchStr - offInput : 0;
+
+ if (cbLeft)
+ memcpy(pvBuf, &pszStr[offInput], RT_MIN(cbLeft, cbBuf));
+
+ *pcbRead = RT_MIN(cbLeft, cbBuf);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read callback for RTJsonParseFromFile().
+ */
+static DECLCALLBACK(int) rtJsonTokenizerParseFromFile(void *pvUser, size_t offInput,
+ void *pvBuf, size_t cbBuf,
+ size_t *pcbRead)
+{
+ PRTJSONREADERARGS pArgs = (PRTJSONREADERARGS)pvUser;
+
+ RT_NOREF_PV(offInput);
+
+ size_t cbRead = 0;
+ int rc = RTStrmReadEx(pArgs->u.hStream, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ *pcbRead = cbRead;
+
+ return rc;
+}
+
+/**
+ * Read callback for RTJsonParseFromVfsFile().
+ */
+static DECLCALLBACK(int) rtJsonTokenizerParseFromVfsFile(void *pvUser, size_t offInput,
+ void *pvBuf, size_t cbBuf,
+ size_t *pcbRead)
+{
+ PRTJSONREADERARGS pArgs = (PRTJSONREADERARGS)pvUser;
+
+ RT_NOREF_PV(offInput);
+
+ size_t cbRead = 0;
+ int rc = RTVfsFileRead(pArgs->u.hVfsFile, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ *pcbRead = cbRead;
+
+ return rc;
+}
+
+RTDECL(int) RTJsonParseFromBuf(PRTJSONVAL phJsonVal, const uint8_t *pbBuf, size_t cbBuf, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+ AssertPtrReturn(pbBuf, VERR_INVALID_POINTER);
+ AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER);
+
+ RTJSONTOKENIZER Tokenizer;
+ RTJSONREADERARGS Args;
+ Args.cbData = cbBuf;
+ Args.u.pbBuf = pbBuf;
+
+ int rc = rtJsonTokenizerInit(&Tokenizer, rtJsonTokenizerParseFromBuf, &Args, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtJsonParse(&Tokenizer, phJsonVal);
+ rtJsonTokenizerDestroy(&Tokenizer);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonParseFromString(PRTJSONVAL phJsonVal, const char *pszStr, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszStr, VERR_INVALID_POINTER);
+
+ /** @todo r=bird: The rtJsonTokenizerParseFromString function does
+ * strlen() on the whole pszStr for each read. For larger strings (
+ * longer than sizeof(Tokenizer.achBuf)) it would be good to join
+ * forces with RTJsonParseFromBuf. */
+ RTJSONTOKENIZER Tokenizer;
+ int rc = rtJsonTokenizerInit(&Tokenizer, rtJsonTokenizerParseFromString, (void *)pszStr, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtJsonParse(&Tokenizer, phJsonVal);
+ rtJsonTokenizerDestroy(&Tokenizer);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonParseFromFile(PRTJSONVAL phJsonVal, const char *pszFilename, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ RTJSONREADERARGS Args;
+
+ Args.cbData = 0;
+ rc = RTStrmOpen(pszFilename, "r", &Args.u.hStream);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONTOKENIZER Tokenizer;
+
+ rc = rtJsonTokenizerInit(&Tokenizer, rtJsonTokenizerParseFromFile, &Args, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtJsonParse(&Tokenizer, phJsonVal);
+ rtJsonTokenizerDestroy(&Tokenizer);
+ }
+ RTStrmClose(Args.u.hStream);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonParseFromVfsFile(PRTJSONVAL phJsonVal, RTVFSFILE hVfsFile, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+ AssertReturn(hVfsFile != NIL_RTVFSFILE, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ RTJSONREADERARGS Args;
+ RTJSONTOKENIZER Tokenizer;
+
+ Args.cbData = 0;
+ Args.u.hVfsFile = hVfsFile;
+ rc = rtJsonTokenizerInit(&Tokenizer, rtJsonTokenizerParseFromVfsFile, &Args, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtJsonParse(&Tokenizer, phJsonVal);
+ rtJsonTokenizerDestroy(&Tokenizer);
+ }
+
+ return rc;
+}
+
+RTDECL(uint32_t) RTJsonValueRetain(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ return cRefs;
+}
+
+RTDECL(uint32_t) RTJsonValueRelease(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ if (pThis == NIL_RTJSONVAL)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0)
+ rtJsonValDestroy(pThis);
+ return cRefs;
+}
+
+RTDECL(RTJSONVALTYPE) RTJsonValueGetType(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, RTJSONVALTYPE_INVALID);
+
+ if (pThis == NIL_RTJSONVAL)
+ return RTJSONVALTYPE_INVALID;
+
+ return pThis->enmType;
+}
+
+
+RTDECL(const char *) RTJsonValueTypeName(RTJSONVALTYPE enmType)
+{
+ switch (enmType)
+ {
+ case RTJSONVALTYPE_INVALID: return "invalid";
+ case RTJSONVALTYPE_OBJECT: return "object";
+ case RTJSONVALTYPE_ARRAY: return "array";
+ case RTJSONVALTYPE_STRING: return "string";
+ case RTJSONVALTYPE_INTEGER: return "integer";
+ case RTJSONVALTYPE_NUMBER: return "number";
+ case RTJSONVALTYPE_NULL: return "null";
+ case RTJSONVALTYPE_TRUE: return "true";
+ case RTJSONVALTYPE_FALSE: return "false";
+ default: return "???";
+ }
+}
+
+
+#define RTJSON_ASSERT_VALID_HANDLE_AND_TYPE_RETURN(pJson, enmExpectedType, ret) do { \
+ AssertPtrReturn((pJson), (ret)); \
+ AssertReturn((pJson) != NIL_RTJSONVAL, (ret)); \
+ AssertReturn((pJson)->enmType == (enmExpectedType), (ret)); \
+ } while (0)
+
+#define RTJSON_TYPECHECK_RETURN(pJson, enmExpectedType) do {\
+ if ((pJson)->enmType == (enmExpectedType)) { /*likely*/ } \
+ else { /*AssertFailed();*/ return VERR_JSON_VALUE_INVALID_TYPE; } \
+ } while (0)
+
+
+#define RTJSON_TYPECHECK_CONTAINER_RETURN(pJson) do { \
+ if ( (pJson)->enmType == RTJSONVALTYPE_ARRAY \
+ || (pJson)->enmType == RTJSONVALTYPE_OBJECT) \
+ { /* likely */ } \
+ else { /*AssertFailed();*/ return VERR_JSON_VALUE_INVALID_TYPE;} \
+ } while (0)
+
+
+RTDECL(const char *) RTJsonValueGetString(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ RTJSON_ASSERT_VALID_HANDLE_AND_TYPE_RETURN(pThis, RTJSONVALTYPE_STRING, NULL);
+
+ return pThis->Type.String.pszStr;
+}
+
+
+RTDECL(int) RTJsonValueQueryString(RTJSONVAL hJsonVal, const char **ppszStr)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(ppszStr, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_STRING);
+ *ppszStr = pThis->Type.String.pszStr;
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTJsonValueQueryInteger(RTJSONVAL hJsonVal, int64_t *pi64Num)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pi64Num, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_INTEGER);
+ *pi64Num = pThis->Type.Integer.i64Num;
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTJsonValueQueryNumber(RTJSONVAL hJsonVal, double *prdNum)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(prdNum, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_NUMBER);
+ *prdNum = pThis->Type.rdNum;
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTJsonValueQueryByName(RTJSONVAL hJsonVal, const char *pszName, PRTJSONVAL phJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_OBJECT);
+
+ int rc = VERR_NOT_FOUND;
+ for (unsigned i = 0; i < pThis->Type.Object.cMembers; i++)
+ {
+ if (!RTStrCmp(pThis->Type.Object.papszNames[i], pszName))
+ {
+ RTJsonValueRetain(pThis->Type.Object.papValues[i]);
+ *phJsonVal = pThis->Type.Object.papValues[i];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonValueQueryIntegerByName(RTJSONVAL hJsonVal, const char *pszName, int64_t *pi64Num)
+{
+ RTJSONVAL hJsonValNum = NIL_RTJSONVAL;
+ int rc = RTJsonValueQueryByName(hJsonVal, pszName, &hJsonValNum);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTJsonValueQueryInteger(hJsonValNum, pi64Num);
+ RTJsonValueRelease(hJsonValNum);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonValueQueryNumberByName(RTJSONVAL hJsonVal, const char *pszName, double *prdNum)
+{
+ RTJSONVAL hJsonValNum = NIL_RTJSONVAL;
+ int rc = RTJsonValueQueryByName(hJsonVal, pszName, &hJsonValNum);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTJsonValueQueryNumber(hJsonValNum, prdNum);
+ RTJsonValueRelease(hJsonValNum);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonValueQueryStringByName(RTJSONVAL hJsonVal, const char *pszName, char **ppszStr)
+{
+ RTJSONVAL hJsonValStr = NIL_RTJSONVAL;
+ int rc = RTJsonValueQueryByName(hJsonVal, pszName, &hJsonValStr);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszStr = NULL;
+ rc = RTJsonValueQueryString(hJsonValStr, &pszStr);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszStr = RTStrDup(pszStr);
+ if (!*ppszStr)
+ rc = VERR_NO_STR_MEMORY;
+ }
+ RTJsonValueRelease(hJsonValStr);
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonValueQueryBooleanByName(RTJSONVAL hJsonVal, const char *pszName, bool *pfBoolean)
+{
+ AssertPtrReturn(pfBoolean, VERR_INVALID_POINTER);
+
+ RTJSONVAL hJsonValBool = NIL_RTJSONVAL;
+ int rc = RTJsonValueQueryByName(hJsonVal, pszName, &hJsonValBool);
+ if (RT_SUCCESS(rc))
+ {
+ RTJSONVALTYPE enmType = RTJsonValueGetType(hJsonValBool);
+ if (enmType == RTJSONVALTYPE_TRUE)
+ *pfBoolean = true;
+ else if (enmType == RTJSONVALTYPE_FALSE)
+ *pfBoolean = false;
+ else
+ rc = VERR_JSON_VALUE_INVALID_TYPE;
+ RTJsonValueRelease(hJsonValBool);
+ }
+
+ return rc;
+}
+
+RTDECL(unsigned) RTJsonValueGetArraySize(RTJSONVAL hJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ RTJSON_ASSERT_VALID_HANDLE_AND_TYPE_RETURN(pThis, RTJSONVALTYPE_ARRAY, 0);
+
+ return pThis->Type.Array.cItems;
+}
+
+RTDECL(int) RTJsonValueQueryArraySize(RTJSONVAL hJsonVal, unsigned *pcItems)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pcItems, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_ARRAY);
+ *pcItems = pThis->Type.Array.cItems;
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTJsonValueQueryByIndex(RTJSONVAL hJsonVal, unsigned idx, PRTJSONVAL phJsonVal)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_ARRAY);
+ if (RT_UNLIKELY(idx >= pThis->Type.Array.cItems))
+ return VERR_OUT_OF_RANGE;
+
+ RTJsonValueRetain(pThis->Type.Array.papItems[idx]);
+ *phJsonVal = pThis->Type.Array.papItems[idx];
+ return VINF_SUCCESS;
+}
+
+static int rtJsonIteratorBeginWorker(PRTJSONVALINT pThis, PRTJSONIT phJsonIt)
+{
+ PRTJSONITINT pIt = (PRTJSONITINT)RTMemTmpAllocZ(sizeof(RTJSONITINT));
+ if (pIt)
+ {
+ RTJsonValueRetain(pThis);
+ pIt->pJsonVal = pThis;
+ pIt->idxCur = 0;
+
+ *phJsonIt = pIt;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+RTDECL(int) RTJsonIteratorBegin(RTJSONVAL hJsonVal, PRTJSONIT phJsonIt)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonIt, VERR_INVALID_POINTER);
+ RTJSON_TYPECHECK_CONTAINER_RETURN(pThis);
+
+ return rtJsonIteratorBeginWorker(pThis, phJsonIt);
+}
+
+RTDECL(int) RTJsonIteratorBeginArray(RTJSONVAL hJsonVal, PRTJSONIT phJsonIt)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonIt, VERR_INVALID_POINTER);
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_ARRAY);
+
+ if (pThis->Type.Array.cItems > 0)
+ return rtJsonIteratorBeginWorker(pThis, phJsonIt);
+ return VERR_JSON_IS_EMPTY;
+}
+
+RTDECL(int) RTJsonIteratorBeginObject(RTJSONVAL hJsonVal, PRTJSONIT phJsonIt)
+{
+ PRTJSONVALINT pThis = hJsonVal;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonIt, VERR_INVALID_POINTER);
+ RTJSON_TYPECHECK_RETURN(pThis, RTJSONVALTYPE_OBJECT);
+
+ if (pThis->Type.Object.cMembers > 0)
+ return rtJsonIteratorBeginWorker(pThis, phJsonIt);
+ return VERR_JSON_IS_EMPTY;
+}
+
+RTDECL(int) RTJsonIteratorQueryValue(RTJSONIT hJsonIt, PRTJSONVAL phJsonVal, const char **ppszName)
+{
+ PRTJSONITINT pIt = hJsonIt;
+ AssertPtrReturn(pIt, VERR_INVALID_HANDLE);
+ AssertReturn(pIt != NIL_RTJSONIT, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phJsonVal, VERR_INVALID_POINTER);
+
+ int rc = VINF_SUCCESS;
+ PRTJSONVALINT pThis = pIt->pJsonVal;
+ if (pThis->enmType == RTJSONVALTYPE_ARRAY)
+ {
+ if (pIt->idxCur < pThis->Type.Array.cItems)
+ {
+ if (ppszName)
+ *ppszName = NULL;
+
+ RTJsonValueRetain(pThis->Type.Array.papItems[pIt->idxCur]);
+ *phJsonVal = pThis->Type.Array.papItems[pIt->idxCur];
+ }
+ else
+ rc = VERR_JSON_ITERATOR_END;
+ }
+ else
+ {
+ Assert(pThis->enmType == RTJSONVALTYPE_OBJECT);
+
+ if (pIt->idxCur < pThis->Type.Object.cMembers)
+ {
+ if (ppszName)
+ *ppszName = pThis->Type.Object.papszNames[pIt->idxCur];
+
+ RTJsonValueRetain(pThis->Type.Object.papValues[pIt->idxCur]);
+ *phJsonVal = pThis->Type.Object.papValues[pIt->idxCur];
+ }
+ else
+ rc = VERR_JSON_ITERATOR_END;
+ }
+
+ return rc;
+}
+
+RTDECL(int) RTJsonIteratorNext(RTJSONIT hJsonIt)
+{
+ PRTJSONITINT pIt = hJsonIt;
+ AssertPtrReturn(pIt, VERR_INVALID_HANDLE);
+ AssertReturn(pIt != NIL_RTJSONIT, VERR_INVALID_HANDLE);
+
+ int rc = VINF_SUCCESS;
+ PRTJSONVALINT pThis = pIt->pJsonVal;
+ if (pThis->enmType == RTJSONVALTYPE_ARRAY)
+ {
+ if (pIt->idxCur < pThis->Type.Array.cItems)
+ pIt->idxCur++;
+
+ if (pIt->idxCur == pThis->Type.Object.cMembers)
+ rc = VERR_JSON_ITERATOR_END;
+ }
+ else
+ {
+ Assert(pThis->enmType == RTJSONVALTYPE_OBJECT);
+
+ if (pIt->idxCur < pThis->Type.Object.cMembers)
+ pIt->idxCur++;
+
+ if (pIt->idxCur == pThis->Type.Object.cMembers)
+ rc = VERR_JSON_ITERATOR_END;
+ }
+
+ return rc;
+}
+
+RTDECL(void) RTJsonIteratorFree(RTJSONIT hJsonIt)
+{
+ PRTJSONITINT pThis = hJsonIt;
+ AssertPtrReturnVoid(pThis);
+
+ if (pThis == NIL_RTJSONIT)
+ return;
+
+ RTJsonValueRelease(pThis->pJsonVal);
+ RTMemTmpFree(pThis);
+}
diff --git a/src/VBox/Runtime/common/misc/lockvalidator.cpp b/src/VBox/Runtime/common/misc/lockvalidator.cpp
new file mode 100644
index 00000000..340f8dcf
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/lockvalidator.cpp
@@ -0,0 +1,4482 @@
+/* $Id: lockvalidator.cpp $ */
+/** @file
+ * IPRT - Lock Validator.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/lockvalidator.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+#include "internal/lockvalidator.h"
+#include "internal/magics.h"
+#include "internal/strhash.h"
+#include "internal/thread.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Macro that asserts that a pointer is aligned correctly.
+ * Only used when fighting bugs. */
+#if 1
+# define RTLOCKVAL_ASSERT_PTR_ALIGN(p) \
+ AssertMsg(!((uintptr_t)(p) & (sizeof(uintptr_t) - 1)), ("%p\n", (p)));
+#else
+# define RTLOCKVAL_ASSERT_PTR_ALIGN(p) do { } while (0)
+#endif
+
+/** Hashes the class handle (pointer) into an apPriorLocksHash index. */
+#define RTLOCKVALCLASS_HASH(hClass) \
+ ( ((uintptr_t)(hClass) >> 6 ) \
+ % ( RT_SIZEOFMEMB(RTLOCKVALCLASSINT, apPriorLocksHash) \
+ / sizeof(PRTLOCKVALCLASSREF)) )
+
+/** The max value for RTLOCKVALCLASSINT::cRefs. */
+#define RTLOCKVALCLASS_MAX_REFS UINT32_C(0xffff0000)
+/** The max value for RTLOCKVALCLASSREF::cLookups. */
+#define RTLOCKVALCLASSREF_MAX_LOOKUPS UINT32_C(0xfffe0000)
+/** The absolute max value for RTLOCKVALCLASSREF::cLookups at which it will
+ * be set back to RTLOCKVALCLASSREF_MAX_LOOKUPS. */
+#define RTLOCKVALCLASSREF_MAX_LOOKUPS_FIX UINT32_C(0xffff0000)
+
+
+/** @def RTLOCKVAL_WITH_RECURSION_RECORDS
+ * Enable recursion records. */
+#if defined(IN_RING3) || defined(DOXYGEN_RUNNING)
+# define RTLOCKVAL_WITH_RECURSION_RECORDS 1
+#endif
+
+/** @def RTLOCKVAL_WITH_VERBOSE_DUMPS
+ * Enables some extra verbosity in the lock dumping. */
+#if defined(DOXYGEN_RUNNING)
+# define RTLOCKVAL_WITH_VERBOSE_DUMPS
+#endif
+
+/** @def RTLOCKVAL_WITH_CLASS_HASH_STATS
+ * Enables collection prior class hash lookup statistics, dumping them when
+ * complaining about the class. */
+#if defined(DEBUG) || defined(DOXYGEN_RUNNING)
+# define RTLOCKVAL_WITH_CLASS_HASH_STATS
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Deadlock detection stack entry.
+ */
+typedef struct RTLOCKVALDDENTRY
+{
+ /** The current record. */
+ PRTLOCKVALRECUNION pRec;
+ /** The current entry number if pRec is a shared one. */
+ uint32_t iEntry;
+ /** The thread state of the thread we followed to get to pFirstSibling.
+ * This is only used for validating a deadlock stack. */
+ RTTHREADSTATE enmState;
+ /** The thread we followed to get to pFirstSibling.
+ * This is only used for validating a deadlock stack. */
+ PRTTHREADINT pThread;
+ /** What pThread is waiting on, i.e. where we entered the circular list of
+ * siblings. This is used for validating a deadlock stack as well as
+ * terminating the sibling walk. */
+ PRTLOCKVALRECUNION pFirstSibling;
+} RTLOCKVALDDENTRY;
+
+
+/**
+ * Deadlock detection stack.
+ */
+typedef struct RTLOCKVALDDSTACK
+{
+ /** The number stack entries. */
+ uint32_t c;
+ /** The stack entries. */
+ RTLOCKVALDDENTRY a[32];
+} RTLOCKVALDDSTACK;
+/** Pointer to a deadlock detection stack. */
+typedef RTLOCKVALDDSTACK *PRTLOCKVALDDSTACK;
+
+
+/**
+ * Reference to another class.
+ */
+typedef struct RTLOCKVALCLASSREF
+{
+ /** The class. */
+ RTLOCKVALCLASS hClass;
+ /** The number of lookups of this class. */
+ uint32_t volatile cLookups;
+ /** Indicates whether the entry was added automatically during order checking
+ * (true) or manually via the API (false). */
+ bool fAutodidacticism;
+ /** Reserved / explicit alignment padding. */
+ bool afReserved[3];
+} RTLOCKVALCLASSREF;
+/** Pointer to a class reference. */
+typedef RTLOCKVALCLASSREF *PRTLOCKVALCLASSREF;
+
+
+/** Pointer to a chunk of class references. */
+typedef struct RTLOCKVALCLASSREFCHUNK *PRTLOCKVALCLASSREFCHUNK;
+/**
+ * Chunk of class references.
+ */
+typedef struct RTLOCKVALCLASSREFCHUNK
+{
+ /** Array of refs. */
+#if 0 /** @todo for testing allocation of new chunks. */
+ RTLOCKVALCLASSREF aRefs[ARCH_BITS == 32 ? 10 : 8];
+#else
+ RTLOCKVALCLASSREF aRefs[2];
+#endif
+ /** Pointer to the next chunk. */
+ PRTLOCKVALCLASSREFCHUNK volatile pNext;
+} RTLOCKVALCLASSREFCHUNK;
+
+
+/**
+ * Lock class.
+ */
+typedef struct RTLOCKVALCLASSINT
+{
+ /** AVL node core. */
+ AVLLU32NODECORE Core;
+ /** Magic value (RTLOCKVALCLASS_MAGIC). */
+ uint32_t volatile u32Magic;
+ /** Reference counter. See RTLOCKVALCLASS_MAX_REFS. */
+ uint32_t volatile cRefs;
+ /** Whether the class is allowed to teach it self new locking order rules. */
+ bool fAutodidact;
+ /** Whether to allow recursion. */
+ bool fRecursionOk;
+ /** Strict release order. */
+ bool fStrictReleaseOrder;
+ /** Whether this class is in the tree. */
+ bool fInTree;
+ /** Donate a reference to the next retainer. This is a hack to make
+ * RTLockValidatorClassCreateUnique work. */
+ bool volatile fDonateRefToNextRetainer;
+ /** Reserved future use / explicit alignment. */
+ bool afReserved[3];
+ /** The minimum wait interval for which we do deadlock detection
+ * (milliseconds). */
+ RTMSINTERVAL cMsMinDeadlock;
+ /** The minimum wait interval for which we do order checks (milliseconds). */
+ RTMSINTERVAL cMsMinOrder;
+ /** More padding. */
+ uint32_t au32Reserved[ARCH_BITS == 32 ? 5 : 2];
+ /** Classes that may be taken prior to this one.
+ * This is a linked list where each node contains a chunk of locks so that we
+ * reduce the number of allocations as well as localize the data. */
+ RTLOCKVALCLASSREFCHUNK PriorLocks;
+ /** Hash table containing frequently encountered prior locks. */
+ PRTLOCKVALCLASSREF apPriorLocksHash[17];
+ /** Class name. (Allocated after the end of the block as usual.) */
+ char const *pszName;
+ /** Where this class was created.
+ * This is mainly used for finding automatically created lock classes.
+ * @remarks The strings are stored after this structure so we won't crash
+ * if the class lives longer than the module (dll/so/dylib) that
+ * spawned it. */
+ RTLOCKVALSRCPOS CreatePos;
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ /** Hash hits. */
+ uint32_t volatile cHashHits;
+ /** Hash misses. */
+ uint32_t volatile cHashMisses;
+#endif
+} RTLOCKVALCLASSINT;
+AssertCompileSize(AVLLU32NODECORE, ARCH_BITS == 32 ? 20 : 32);
+AssertCompileMemberOffset(RTLOCKVALCLASSINT, PriorLocks, 64);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Serializing object destruction and deadlock detection.
+ *
+ * This makes sure that none of the memory examined by the deadlock detection
+ * code will become invalid (reused for other purposes or made not present)
+ * while the detection is in progress.
+ *
+ * NS: RTLOCKVALREC*, RTTHREADINT and RTLOCKVALDRECSHRD::papOwners destruction.
+ * EW: Deadlock detection and some related activities.
+ */
+static RTSEMXROADS g_hLockValidatorXRoads = NIL_RTSEMXROADS;
+/** Serializing class tree insert and lookups. */
+static RTSEMRW g_hLockValClassTreeRWLock= NIL_RTSEMRW;
+/** Class tree. */
+static PAVLLU32NODECORE g_LockValClassTree = NULL;
+/** Critical section serializing the teaching new rules to the classes. */
+static RTCRITSECT g_LockValClassTeachCS;
+
+/** Whether the lock validator is enabled or disabled.
+ * Only applies to new locks. */
+static bool volatile g_fLockValidatorEnabled = true;
+/** Set if the lock validator is quiet. */
+#ifdef RT_STRICT
+static bool volatile g_fLockValidatorQuiet = false;
+#else
+static bool volatile g_fLockValidatorQuiet = true;
+#endif
+/** Set if the lock validator may panic. */
+#ifdef RT_STRICT
+static bool volatile g_fLockValidatorMayPanic = true;
+#else
+static bool volatile g_fLockValidatorMayPanic = false;
+#endif
+/** Whether to return an error status on wrong locking order. */
+static bool volatile g_fLockValSoftWrongOrder = false;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtLockValidatorClassDestroy(RTLOCKVALCLASSINT *pClass);
+static uint32_t rtLockValidatorStackDepth(PRTTHREADINT pThread);
+
+
+/**
+ * Lazy initialization of the lock validator globals.
+ */
+static void rtLockValidatorLazyInit(void)
+{
+ static uint32_t volatile s_fInitializing = false;
+ if (ASMAtomicCmpXchgU32(&s_fInitializing, true, false))
+ {
+ /*
+ * The locks.
+ */
+ if (!RTCritSectIsInitialized(&g_LockValClassTeachCS))
+ RTCritSectInitEx(&g_LockValClassTeachCS, RTCRITSECT_FLAGS_NO_LOCK_VAL, NIL_RTLOCKVALCLASS,
+ RTLOCKVAL_SUB_CLASS_ANY, "RTLockVal-Teach");
+
+ if (g_hLockValClassTreeRWLock == NIL_RTSEMRW)
+ {
+ RTSEMRW hSemRW;
+ int rc = RTSemRWCreateEx(&hSemRW, RTSEMRW_FLAGS_NO_LOCK_VAL, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_ANY, "RTLockVal-Tree");
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteHandle(&g_hLockValClassTreeRWLock, hSemRW);
+ }
+
+ if (g_hLockValidatorXRoads == NIL_RTSEMXROADS)
+ {
+ RTSEMXROADS hXRoads;
+ int rc = RTSemXRoadsCreate(&hXRoads);
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteHandle(&g_hLockValidatorXRoads, hXRoads);
+ }
+
+#ifdef IN_RING3
+ /*
+ * Check the environment for our config variables.
+ */
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_ENABLED"))
+ ASMAtomicWriteBool(&g_fLockValidatorEnabled, true);
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_DISABLED"))
+ ASMAtomicWriteBool(&g_fLockValidatorEnabled, false);
+
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_MAY_PANIC"))
+ ASMAtomicWriteBool(&g_fLockValidatorMayPanic, true);
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_MAY_NOT_PANIC"))
+ ASMAtomicWriteBool(&g_fLockValidatorMayPanic, false);
+
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_NOT_QUIET"))
+ ASMAtomicWriteBool(&g_fLockValidatorQuiet, false);
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_QUIET"))
+ ASMAtomicWriteBool(&g_fLockValidatorQuiet, true);
+
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_STRICT_ORDER"))
+ ASMAtomicWriteBool(&g_fLockValSoftWrongOrder, false);
+ if (RTEnvExist("IPRT_LOCK_VALIDATOR_SOFT_ORDER"))
+ ASMAtomicWriteBool(&g_fLockValSoftWrongOrder, true);
+#endif
+
+ /*
+ * Register cleanup
+ */
+ /** @todo register some cleanup callback if we care. */
+
+ ASMAtomicWriteU32(&s_fInitializing, false);
+ }
+}
+
+
+
+/** Wrapper around ASMAtomicReadPtr. */
+DECL_FORCE_INLINE(PRTLOCKVALRECUNION) rtLockValidatorReadRecUnionPtr(PRTLOCKVALRECUNION volatile *ppRec)
+{
+ PRTLOCKVALRECUNION p = ASMAtomicReadPtrT(ppRec, PRTLOCKVALRECUNION);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(p);
+ return p;
+}
+
+
+/** Wrapper around ASMAtomicWritePtr. */
+DECL_FORCE_INLINE(void) rtLockValidatorWriteRecUnionPtr(PRTLOCKVALRECUNION volatile *ppRec, PRTLOCKVALRECUNION pRecNew)
+{
+ RTLOCKVAL_ASSERT_PTR_ALIGN(pRecNew);
+ ASMAtomicWritePtr(ppRec, pRecNew);
+}
+
+
+/** Wrapper around ASMAtomicReadPtr. */
+DECL_FORCE_INLINE(PRTTHREADINT) rtLockValidatorReadThreadHandle(RTTHREAD volatile *phThread)
+{
+ PRTTHREADINT p = ASMAtomicReadPtrT(phThread, PRTTHREADINT);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(p);
+ return p;
+}
+
+
+/** Wrapper around ASMAtomicUoReadPtr. */
+DECL_FORCE_INLINE(PRTLOCKVALRECSHRDOWN) rtLockValidatorUoReadSharedOwner(PRTLOCKVALRECSHRDOWN volatile *ppOwner)
+{
+ PRTLOCKVALRECSHRDOWN p = ASMAtomicUoReadPtrT(ppOwner, PRTLOCKVALRECSHRDOWN);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(p);
+ return p;
+}
+
+
+/**
+ * Reads a volatile thread handle field and returns the thread name.
+ *
+ * @returns Thread name (read only).
+ * @param phThread The thread handle field.
+ */
+static const char *rtLockValidatorNameThreadHandle(RTTHREAD volatile *phThread)
+{
+ PRTTHREADINT pThread = rtLockValidatorReadThreadHandle(phThread);
+ if (!pThread)
+ return "<NIL>";
+ if (!RT_VALID_PTR(pThread))
+ return "<INVALID>";
+ if (pThread->u32Magic != RTTHREADINT_MAGIC)
+ return "<BAD-THREAD-MAGIC>";
+ return pThread->szName;
+}
+
+
+/**
+ * Launch a simple assertion like complaint w/ panic.
+ *
+ * @param SRC_POS The source position where call is being made from.
+ * @param pszWhat What we're complaining about.
+ * @param ... Format arguments.
+ */
+static void rtLockValComplain(RT_SRC_POS_DECL, const char *pszWhat, ...)
+{
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ RTAssertMsg1Weak("RTLockValidator", iLine, pszFile, pszFunction);
+ va_list va;
+ va_start(va, pszWhat);
+ RTAssertMsg2WeakV(pszWhat, va);
+ va_end(va);
+ }
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ RTAssertPanic();
+}
+
+
+/**
+ * Describes the class.
+ *
+ * @param pszPrefix Message prefix.
+ * @param pClass The class to complain about.
+ * @param uSubClass My sub-class.
+ * @param fVerbose Verbose description including relations to other
+ * classes.
+ */
+static void rtLockValComplainAboutClass(const char *pszPrefix, RTLOCKVALCLASSINT *pClass, uint32_t uSubClass, bool fVerbose)
+{
+ if (ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ return;
+
+ /* Stringify the sub-class. */
+ const char *pszSubClass;
+ char szSubClass[32];
+ if (uSubClass < RTLOCKVAL_SUB_CLASS_USER)
+ switch (uSubClass)
+ {
+ case RTLOCKVAL_SUB_CLASS_NONE: pszSubClass = "none"; break;
+ case RTLOCKVAL_SUB_CLASS_ANY: pszSubClass = "any"; break;
+ default:
+ RTStrPrintf(szSubClass, sizeof(szSubClass), "invl-%u", uSubClass);
+ pszSubClass = szSubClass;
+ break;
+ }
+ else
+ {
+ RTStrPrintf(szSubClass, sizeof(szSubClass), "%u", uSubClass);
+ pszSubClass = szSubClass;
+ }
+
+ /* Validate the class pointer. */
+ if (!RT_VALID_PTR(pClass))
+ {
+ RTAssertMsg2AddWeak("%sbad class=%p sub-class=%s\n", pszPrefix, pClass, pszSubClass);
+ return;
+ }
+ if (pClass->u32Magic != RTLOCKVALCLASS_MAGIC)
+ {
+ RTAssertMsg2AddWeak("%sbad class=%p magic=%#x sub-class=%s\n", pszPrefix, pClass, pClass->u32Magic, pszSubClass);
+ return;
+ }
+
+ /* OK, dump the class info. */
+ RTAssertMsg2AddWeak("%sclass=%p %s created={%Rbn(%u) %Rfn %p} sub-class=%s\n", pszPrefix,
+ pClass,
+ pClass->pszName,
+ pClass->CreatePos.pszFile,
+ pClass->CreatePos.uLine,
+ pClass->CreatePos.pszFunction,
+ pClass->CreatePos.uId,
+ pszSubClass);
+ if (fVerbose)
+ {
+ uint32_t i = 0;
+ uint32_t cPrinted = 0;
+ for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; pChunk; pChunk = pChunk->pNext)
+ for (unsigned j = 0; j < RT_ELEMENTS(pChunk->aRefs); j++, i++)
+ {
+ RTLOCKVALCLASSINT *pCurClass = pChunk->aRefs[j].hClass;
+ if (pCurClass != NIL_RTLOCKVALCLASS)
+ {
+ RTAssertMsg2AddWeak("%s%s #%02u: %s, %s, %u lookup%s\n", pszPrefix,
+ cPrinted == 0
+ ? "Prior:"
+ : " ",
+ i,
+ pCurClass->pszName,
+ pChunk->aRefs[j].fAutodidacticism
+ ? "autodidactic"
+ : "manually ",
+ pChunk->aRefs[j].cLookups,
+ pChunk->aRefs[j].cLookups != 1 ? "s" : "");
+ cPrinted++;
+ }
+ }
+ if (!cPrinted)
+ RTAssertMsg2AddWeak("%sPrior: none\n", pszPrefix);
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ RTAssertMsg2AddWeak("%sHash Stats: %u hits, %u misses\n", pszPrefix, pClass->cHashHits, pClass->cHashMisses);
+#endif
+ }
+ else
+ {
+ uint32_t cPrinted = 0;
+ for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; pChunk; pChunk = pChunk->pNext)
+ for (unsigned j = 0; j < RT_ELEMENTS(pChunk->aRefs); j++)
+ {
+ RTLOCKVALCLASSINT *pCurClass = pChunk->aRefs[j].hClass;
+ if (pCurClass != NIL_RTLOCKVALCLASS)
+ {
+ if ((cPrinted % 10) == 0)
+ RTAssertMsg2AddWeak("%sPrior classes: %s%s", pszPrefix, pCurClass->pszName,
+ pChunk->aRefs[j].fAutodidacticism ? "*" : "");
+ else if ((cPrinted % 10) != 9)
+ RTAssertMsg2AddWeak(", %s%s", pCurClass->pszName,
+ pChunk->aRefs[j].fAutodidacticism ? "*" : "");
+ else
+ RTAssertMsg2AddWeak(", %s%s\n", pCurClass->pszName,
+ pChunk->aRefs[j].fAutodidacticism ? "*" : "");
+ cPrinted++;
+ }
+ }
+ if (!cPrinted)
+ RTAssertMsg2AddWeak("%sPrior classes: none\n", pszPrefix);
+ else if ((cPrinted % 10) != 0)
+ RTAssertMsg2AddWeak("\n");
+ }
+}
+
+
+/**
+ * Helper for getting the class name.
+ * @returns Class name string.
+ * @param pClass The class.
+ */
+static const char *rtLockValComplainGetClassName(RTLOCKVALCLASSINT *pClass)
+{
+ if (!pClass)
+ return "<nil-class>";
+ if (!RT_VALID_PTR(pClass))
+ return "<bad-class-ptr>";
+ if (pClass->u32Magic != RTLOCKVALCLASS_MAGIC)
+ return "<bad-class-magic>";
+ if (!pClass->pszName)
+ return "<no-class-name>";
+ return pClass->pszName;
+}
+
+/**
+ * Formats the sub-class.
+ *
+ * @returns Stringified sub-class.
+ * @param uSubClass The name.
+ * @param pszBuf Buffer that is big enough.
+ */
+static const char *rtLockValComplainGetSubClassName(uint32_t uSubClass, char *pszBuf)
+{
+ if (uSubClass < RTLOCKVAL_SUB_CLASS_USER)
+ switch (uSubClass)
+ {
+ case RTLOCKVAL_SUB_CLASS_NONE: return "none";
+ case RTLOCKVAL_SUB_CLASS_ANY: return "any";
+ default:
+ RTStrPrintf(pszBuf, 32, "invl-%u", uSubClass);
+ break;
+ }
+ else
+ RTStrPrintf(pszBuf, 32, "%x", uSubClass);
+ return pszBuf;
+}
+
+
+/**
+ * Helper for rtLockValComplainAboutLock.
+ */
+DECL_FORCE_INLINE(void) rtLockValComplainAboutLockHlp(const char *pszPrefix, PRTLOCKVALRECUNION pRec, const char *pszSuffix,
+ uint32_t u32Magic, PCRTLOCKVALSRCPOS pSrcPos, uint32_t cRecursion,
+ const char *pszFrameType)
+{
+ char szBuf[32];
+ switch (u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+#ifdef RTLOCKVAL_WITH_VERBOSE_DUMPS
+ RTAssertMsg2AddWeak("%s%p %s xrec=%p own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [x%s]%s", pszPrefix,
+ pRec->Excl.hLock, pRec->Excl.szName, pRec,
+ rtLockValidatorNameThreadHandle(&pRec->Excl.hThread), cRecursion,
+ rtLockValComplainGetClassName(pRec->Excl.hClass),
+ rtLockValComplainGetSubClassName(pRec->Excl.uSubClass, szBuf),
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszFrameType, pszSuffix);
+#else
+ RTAssertMsg2AddWeak("%s%p %s own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [x%s]%s", pszPrefix,
+ pRec->Excl.hLock, pRec->Excl.szName,
+ rtLockValidatorNameThreadHandle(&pRec->Excl.hThread), cRecursion,
+ rtLockValComplainGetClassName(pRec->Excl.hClass),
+ rtLockValComplainGetSubClassName(pRec->Excl.uSubClass, szBuf),
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszFrameType, pszSuffix);
+#endif
+ break;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ RTAssertMsg2AddWeak("%ss %p %s srec=%p cls=%s/%s [s%s]%s", pszPrefix,
+ pRec->Shared.hLock, pRec->Shared.szName, pRec,
+ rtLockValComplainGetClassName(pRec->Shared.hClass),
+ rtLockValComplainGetSubClassName(pRec->Shared.uSubClass, szBuf),
+ pszFrameType, pszSuffix);
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pShared = pRec->ShrdOwner.pSharedRec;
+ if ( RT_VALID_PTR(pShared)
+ && pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)
+#ifdef RTLOCKVAL_WITH_VERBOSE_DUMPS
+ RTAssertMsg2AddWeak("%s%p %s srec=%p trec=%p own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [o%s]%s", pszPrefix,
+ pShared->hLock, pShared->szName, pShared,
+ pRec, rtLockValidatorNameThreadHandle(&pRec->ShrdOwner.hThread), cRecursion,
+ rtLockValComplainGetClassName(pShared->hClass),
+ rtLockValComplainGetSubClassName(pShared->uSubClass, szBuf),
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszSuffix, pszSuffix);
+#else
+ RTAssertMsg2AddWeak("%s%p %s own=%s r=%u cls=%s/%s pos={%Rbn(%u) %Rfn %p} [o%s]%s", pszPrefix,
+ pShared->hLock, pShared->szName,
+ rtLockValidatorNameThreadHandle(&pRec->ShrdOwner.hThread), cRecursion,
+ rtLockValComplainGetClassName(pShared->hClass),
+ rtLockValComplainGetSubClassName(pShared->uSubClass, szBuf),
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszFrameType, pszSuffix);
+#endif
+ else
+ RTAssertMsg2AddWeak("%sbad srec=%p trec=%p own=%s r=%u pos={%Rbn(%u) %Rfn %p} [x%s]%s", pszPrefix,
+ pShared,
+ pRec, rtLockValidatorNameThreadHandle(&pRec->ShrdOwner.hThread), cRecursion,
+ pSrcPos->pszFile, pSrcPos->uLine, pSrcPos->pszFunction, pSrcPos->uId,
+ pszFrameType, pszSuffix);
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%#x\n", u32Magic));
+ }
+}
+
+
+/**
+ * Describes the lock.
+ *
+ * @param pszPrefix Message prefix.
+ * @param pRec The lock record we're working on.
+ * @param pszSuffix Message suffix.
+ */
+static void rtLockValComplainAboutLock(const char *pszPrefix, PRTLOCKVALRECUNION pRec, const char *pszSuffix)
+{
+#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS
+# define FIX_REC(r) 1
+#else
+# define FIX_REC(r) (r)
+#endif
+ if ( RT_VALID_PTR(pRec)
+ && !ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ rtLockValComplainAboutLockHlp(pszPrefix, pRec, pszSuffix, RTLOCKVALRECEXCL_MAGIC,
+ &pRec->Excl.SrcPos, FIX_REC(pRec->Excl.cRecursion), "");
+ break;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ rtLockValComplainAboutLockHlp(pszPrefix, pRec, pszSuffix, RTLOCKVALRECSHRD_MAGIC, NULL, 0, "");
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ rtLockValComplainAboutLockHlp(pszPrefix, pRec, pszSuffix, RTLOCKVALRECSHRDOWN_MAGIC,
+ &pRec->ShrdOwner.SrcPos, FIX_REC(pRec->ShrdOwner.cRecursion), "");
+ break;
+
+ case RTLOCKVALRECNEST_MAGIC:
+ {
+ PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec;
+ uint32_t u32Magic;
+ if ( RT_VALID_PTR(pRealRec)
+ && ( (u32Magic = pRealRec->Core.u32Magic) == RTLOCKVALRECEXCL_MAGIC
+ || u32Magic == RTLOCKVALRECSHRD_MAGIC
+ || u32Magic == RTLOCKVALRECSHRDOWN_MAGIC)
+ )
+ rtLockValComplainAboutLockHlp(pszPrefix, pRealRec, pszSuffix, u32Magic,
+ &pRec->Nest.SrcPos, pRec->Nest.cRecursion, "/r");
+ else
+ RTAssertMsg2AddWeak("%sbad rrec=%p nrec=%p r=%u pos={%Rbn(%u) %Rfn %p}%s", pszPrefix,
+ pRealRec, pRec, pRec->Nest.cRecursion,
+ pRec->Nest.SrcPos.pszFile, pRec->Nest.SrcPos.uLine, pRec->Nest.SrcPos.pszFunction, pRec->Nest.SrcPos.uId,
+ pszSuffix);
+ break;
+ }
+
+ default:
+ RTAssertMsg2AddWeak("%spRec=%p u32Magic=%#x (bad)%s", pszPrefix, pRec, pRec->Core.u32Magic, pszSuffix);
+ break;
+ }
+ }
+#undef FIX_REC
+}
+
+
+/**
+ * Dump the lock stack.
+ *
+ * @param pThread The thread which lock stack we're gonna dump.
+ * @param cchIndent The indentation in chars.
+ * @param cMinFrames The minimum number of frames to consider
+ * dumping.
+ * @param pHighightRec Record that should be marked specially in the
+ * dump.
+ */
+static void rtLockValComplainAboutLockStack(PRTTHREADINT pThread, unsigned cchIndent, uint32_t cMinFrames,
+ PRTLOCKVALRECUNION pHighightRec)
+{
+ if ( RT_VALID_PTR(pThread)
+ && !ASMAtomicUoReadBool(&g_fLockValidatorQuiet)
+ && pThread->u32Magic == RTTHREADINT_MAGIC
+ )
+ {
+ uint32_t cEntries = rtLockValidatorStackDepth(pThread);
+ if (cEntries >= cMinFrames)
+ {
+ RTAssertMsg2AddWeak("%*s---- start of lock stack for %p %s - %u entr%s ----\n", cchIndent, "",
+ pThread, pThread->szName, cEntries, cEntries == 1 ? "y" : "ies");
+ PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
+ for (uint32_t i = 0; RT_VALID_PTR(pCur); i++)
+ {
+ char szPrefix[80];
+ RTStrPrintf(szPrefix, sizeof(szPrefix), "%*s#%02u: ", cchIndent, "", i);
+ rtLockValComplainAboutLock(szPrefix, pCur, pHighightRec != pCur ? "\n" : " (*)\n");
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown); break;
+ case RTLOCKVALRECSHRDOWN_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown); break;
+ case RTLOCKVALRECNEST_MAGIC: pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown); break;
+ default:
+ RTAssertMsg2AddWeak("%*s<bad stack frame>\n", cchIndent, "");
+ pCur = NULL;
+ break;
+ }
+ }
+ RTAssertMsg2AddWeak("%*s---- end of lock stack ----\n", cchIndent, "");
+ }
+ }
+}
+
+
+/**
+ * Launch the initial complaint.
+ *
+ * @param pszWhat What we're complaining about.
+ * @param pSrcPos Where we are complaining from, as it were.
+ * @param pThreadSelf The calling thread.
+ * @param pRec The main lock involved. Can be NULL.
+ * @param fDumpStack Whether to dump the lock stack (true) or not
+ * (false).
+ */
+static void rtLockValComplainFirst(const char *pszWhat, PCRTLOCKVALSRCPOS pSrcPos, PRTTHREADINT pThreadSelf,
+ PRTLOCKVALRECUNION pRec, bool fDumpStack)
+{
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ ASMCompilerBarrier(); /* paranoia */
+ RTAssertMsg1Weak("RTLockValidator", pSrcPos ? pSrcPos->uLine : 0, pSrcPos ? pSrcPos->pszFile : NULL, pSrcPos ? pSrcPos->pszFunction : NULL);
+ if (pSrcPos && pSrcPos->uId)
+ RTAssertMsg2Weak("%s [uId=%p thrd=%s]\n", pszWhat, pSrcPos->uId, RT_VALID_PTR(pThreadSelf) ? pThreadSelf->szName : "<NIL>");
+ else
+ RTAssertMsg2Weak("%s [thrd=%s]\n", pszWhat, RT_VALID_PTR(pThreadSelf) ? pThreadSelf->szName : "<NIL>");
+ rtLockValComplainAboutLock("Lock: ", pRec, "\n");
+ if (fDumpStack)
+ rtLockValComplainAboutLockStack(pThreadSelf, 0, 1, pRec);
+ }
+}
+
+
+/**
+ * Continue bitching.
+ *
+ * @param pszFormat Format string.
+ * @param ... Format arguments.
+ */
+static void rtLockValComplainMore(const char *pszFormat, ...)
+{
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ RTAssertMsg2AddWeakV(pszFormat, va);
+ va_end(va);
+ }
+}
+
+
+/**
+ * Raise a panic if enabled.
+ */
+static void rtLockValComplainPanic(void)
+{
+ if (ASMAtomicUoReadBool(&g_fLockValidatorMayPanic))
+ RTAssertPanic();
+}
+
+
+/**
+ * Copy a source position record.
+ *
+ * @param pDst The destination.
+ * @param pSrc The source. Can be NULL.
+ */
+DECL_FORCE_INLINE(void) rtLockValidatorSrcPosCopy(PRTLOCKVALSRCPOS pDst, PCRTLOCKVALSRCPOS pSrc)
+{
+ if (pSrc)
+ {
+ ASMAtomicUoWriteU32(&pDst->uLine, pSrc->uLine);
+ ASMAtomicUoWritePtr(&pDst->pszFile, pSrc->pszFile);
+ ASMAtomicUoWritePtr(&pDst->pszFunction, pSrc->pszFunction);
+ ASMAtomicUoWritePtr((void * volatile *)&pDst->uId, (void *)pSrc->uId);
+ }
+ else
+ {
+ ASMAtomicUoWriteU32(&pDst->uLine, 0);
+ ASMAtomicUoWriteNullPtr(&pDst->pszFile);
+ ASMAtomicUoWriteNullPtr(&pDst->pszFunction);
+ ASMAtomicUoWritePtr(&pDst->uId, (RTHCUINTPTR)0);
+ }
+}
+
+
+/**
+ * Init a source position record.
+ *
+ * @param pSrcPos The source position record.
+ */
+DECL_FORCE_INLINE(void) rtLockValidatorSrcPosInit(PRTLOCKVALSRCPOS pSrcPos)
+{
+ pSrcPos->pszFile = NULL;
+ pSrcPos->pszFunction = NULL;
+ pSrcPos->uId = 0;
+ pSrcPos->uLine = 0;
+#if HC_ARCH_BITS == 64
+ pSrcPos->u32Padding = 0;
+#endif
+}
+
+
+/**
+ * Hashes the specified source position.
+ *
+ * @returns Hash.
+ * @param pSrcPos The source position record.
+ */
+static uint32_t rtLockValidatorSrcPosHash(PCRTLOCKVALSRCPOS pSrcPos)
+{
+ uint32_t uHash;
+ if ( ( pSrcPos->pszFile
+ || pSrcPos->pszFunction)
+ && pSrcPos->uLine != 0)
+ {
+ uHash = 0;
+ if (pSrcPos->pszFile)
+ uHash = sdbmInc(pSrcPos->pszFile, uHash);
+ if (pSrcPos->pszFunction)
+ uHash = sdbmInc(pSrcPos->pszFunction, uHash);
+ uHash += pSrcPos->uLine;
+ }
+ else
+ {
+ Assert(pSrcPos->uId);
+ uHash = (uint32_t)pSrcPos->uId;
+ }
+
+ return uHash;
+}
+
+
+/**
+ * Compares two source positions.
+ *
+ * @returns 0 if equal, < 0 if pSrcPos1 is smaller than pSrcPos2, > 0 if
+ * otherwise.
+ * @param pSrcPos1 The first source position.
+ * @param pSrcPos2 The second source position.
+ */
+static int rtLockValidatorSrcPosCompare(PCRTLOCKVALSRCPOS pSrcPos1, PCRTLOCKVALSRCPOS pSrcPos2)
+{
+ if (pSrcPos1->uLine != pSrcPos2->uLine)
+ return pSrcPos1->uLine < pSrcPos2->uLine ? -1 : 1;
+
+ int iDiff = RTStrCmp(pSrcPos1->pszFile, pSrcPos2->pszFile);
+ if (iDiff != 0)
+ return iDiff;
+
+ iDiff = RTStrCmp(pSrcPos1->pszFunction, pSrcPos2->pszFunction);
+ if (iDiff != 0)
+ return iDiff;
+
+ if (pSrcPos1->uId != pSrcPos2->uId)
+ return pSrcPos1->uId < pSrcPos2->uId ? -1 : 1;
+ return 0;
+}
+
+
+
+/**
+ * Serializes destruction of RTLOCKVALREC* and RTTHREADINT structures.
+ */
+DECLHIDDEN(void) rtLockValidatorSerializeDestructEnter(void)
+{
+ RTSEMXROADS hXRoads = g_hLockValidatorXRoads;
+ if (hXRoads != NIL_RTSEMXROADS)
+ RTSemXRoadsNSEnter(hXRoads);
+}
+
+
+/**
+ * Call after rtLockValidatorSerializeDestructEnter.
+ */
+DECLHIDDEN(void) rtLockValidatorSerializeDestructLeave(void)
+{
+ RTSEMXROADS hXRoads = g_hLockValidatorXRoads;
+ if (hXRoads != NIL_RTSEMXROADS)
+ RTSemXRoadsNSLeave(hXRoads);
+}
+
+
+/**
+ * Serializes deadlock detection against destruction of the objects being
+ * inspected.
+ */
+DECLINLINE(void) rtLockValidatorSerializeDetectionEnter(void)
+{
+ RTSEMXROADS hXRoads = g_hLockValidatorXRoads;
+ if (hXRoads != NIL_RTSEMXROADS)
+ RTSemXRoadsEWEnter(hXRoads);
+}
+
+
+/**
+ * Call after rtLockValidatorSerializeDetectionEnter.
+ */
+DECLHIDDEN(void) rtLockValidatorSerializeDetectionLeave(void)
+{
+ RTSEMXROADS hXRoads = g_hLockValidatorXRoads;
+ if (hXRoads != NIL_RTSEMXROADS)
+ RTSemXRoadsEWLeave(hXRoads);
+}
+
+
+/**
+ * Initializes the per thread lock validator data.
+ *
+ * @param pPerThread The data.
+ */
+DECLHIDDEN(void) rtLockValidatorInitPerThread(RTLOCKVALPERTHREAD *pPerThread)
+{
+ pPerThread->bmFreeShrdOwners = UINT32_MAX;
+
+ /* ASSUMES the rest has already been zeroed. */
+ Assert(pPerThread->pRec == NULL);
+ Assert(pPerThread->cWriteLocks == 0);
+ Assert(pPerThread->cReadLocks == 0);
+ Assert(pPerThread->fInValidator == false);
+ Assert(pPerThread->pStackTop == NULL);
+}
+
+
+/**
+ * Delete the per thread lock validator data.
+ *
+ * @param pPerThread The data.
+ */
+DECLHIDDEN(void) rtLockValidatorDeletePerThread(RTLOCKVALPERTHREAD *pPerThread)
+{
+ /*
+ * Check that the thread doesn't own any locks at this time.
+ */
+ if (pPerThread->pStackTop)
+ {
+ rtLockValComplainFirst("Thread terminating owning locks!", NULL,
+ RT_FROM_MEMBER(pPerThread, RTTHREADINT, LockValidator),
+ pPerThread->pStackTop, true);
+ rtLockValComplainPanic();
+ }
+
+ /*
+ * Free the recursion records.
+ */
+ PRTLOCKVALRECNEST pCur = pPerThread->pFreeNestRecs;
+ pPerThread->pFreeNestRecs = NULL;
+ while (pCur)
+ {
+ PRTLOCKVALRECNEST pNext = pCur->pNextFree;
+ RTMemFree(pCur);
+ pCur = pNext;
+ }
+}
+
+RTDECL(int) RTLockValidatorClassCreateEx(PRTLOCKVALCLASS phClass, PCRTLOCKVALSRCPOS pSrcPos,
+ bool fAutodidact, bool fRecursionOk, bool fStrictReleaseOrder,
+ RTMSINTERVAL cMsMinDeadlock, RTMSINTERVAL cMsMinOrder,
+ const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorClassCreateExV(phClass, pSrcPos, fAutodidact, fRecursionOk, fStrictReleaseOrder,
+ cMsMinDeadlock, cMsMinOrder, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(int) RTLockValidatorClassCreateExV(PRTLOCKVALCLASS phClass, PCRTLOCKVALSRCPOS pSrcPos,
+ bool fAutodidact, bool fRecursionOk, bool fStrictReleaseOrder,
+ RTMSINTERVAL cMsMinDeadlock, RTMSINTERVAL cMsMinOrder,
+ const char *pszNameFmt, va_list va)
+{
+ Assert(cMsMinDeadlock >= 1);
+ Assert(cMsMinOrder >= 1);
+ AssertPtr(pSrcPos);
+
+ /*
+ * Format the name and calc its length.
+ */
+ size_t cbName;
+ char szName[32];
+ if (pszNameFmt && *pszNameFmt)
+ cbName = RTStrPrintfV(szName, sizeof(szName), pszNameFmt, va) + 1;
+ else
+ {
+ static uint32_t volatile s_cAnonymous = 0;
+ uint32_t i = ASMAtomicIncU32(&s_cAnonymous);
+ cbName = RTStrPrintf(szName, sizeof(szName), "anon-%u", i - 1) + 1;
+ }
+
+ /*
+ * Figure out the file and function name lengths and allocate memory for
+ * it all.
+ */
+ size_t const cbFile = pSrcPos->pszFile ? strlen(pSrcPos->pszFile) + 1 : 0;
+ size_t const cbFunction = pSrcPos->pszFunction ? strlen(pSrcPos->pszFunction) + 1 : 0;
+ RTLOCKVALCLASSINT *pThis = (RTLOCKVALCLASSINT *)RTMemAllocVarTag(sizeof(*pThis) + cbFile + cbFunction + cbName,
+ "may-leak:RTLockValidatorClassCreateExV");
+ if (!pThis)
+ return VERR_NO_MEMORY;
+ RTMEM_MAY_LEAK(pThis);
+
+ /*
+ * Initialize the class data.
+ */
+ pThis->Core.Key = rtLockValidatorSrcPosHash(pSrcPos);
+ pThis->Core.uchHeight = 0;
+ pThis->Core.pLeft = NULL;
+ pThis->Core.pRight = NULL;
+ pThis->Core.pList = NULL;
+ pThis->u32Magic = RTLOCKVALCLASS_MAGIC;
+ pThis->cRefs = 1;
+ pThis->fAutodidact = fAutodidact;
+ pThis->fRecursionOk = fRecursionOk;
+ pThis->fStrictReleaseOrder = fStrictReleaseOrder;
+ pThis->fInTree = false;
+ pThis->fDonateRefToNextRetainer = false;
+ pThis->afReserved[0] = false;
+ pThis->afReserved[1] = false;
+ pThis->afReserved[2] = false;
+ pThis->cMsMinDeadlock = cMsMinDeadlock;
+ pThis->cMsMinOrder = cMsMinOrder;
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->au32Reserved); i++)
+ pThis->au32Reserved[i] = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->PriorLocks.aRefs); i++)
+ {
+ pThis->PriorLocks.aRefs[i].hClass = NIL_RTLOCKVALCLASS;
+ pThis->PriorLocks.aRefs[i].cLookups = 0;
+ pThis->PriorLocks.aRefs[i].fAutodidacticism = false;
+ pThis->PriorLocks.aRefs[i].afReserved[0] = false;
+ pThis->PriorLocks.aRefs[i].afReserved[1] = false;
+ pThis->PriorLocks.aRefs[i].afReserved[2] = false;
+ }
+ pThis->PriorLocks.pNext = NULL;
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->apPriorLocksHash); i++)
+ pThis->apPriorLocksHash[i] = NULL;
+ char *pszDst = (char *)(pThis + 1);
+ pThis->pszName = (char *)memcpy(pszDst, szName, cbName);
+ pszDst += cbName;
+ rtLockValidatorSrcPosCopy(&pThis->CreatePos, pSrcPos);
+ pThis->CreatePos.pszFile = pSrcPos->pszFile ? (char *)memcpy(pszDst, pSrcPos->pszFile, cbFile) : NULL;
+ pszDst += cbFile;
+ pThis->CreatePos.pszFunction= pSrcPos->pszFunction ? (char *)memcpy(pszDst, pSrcPos->pszFunction, cbFunction) : NULL;
+ Assert(rtLockValidatorSrcPosHash(&pThis->CreatePos) == pThis->Core.Key);
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ pThis->cHashHits = 0;
+ pThis->cHashMisses = 0;
+#endif
+
+ *phClass = pThis;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorClassCreate(PRTLOCKVALCLASS phClass, bool fAutodidact, RT_SRC_POS_DECL, const char *pszNameFmt, ...)
+{
+ RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_POS_NO_ID();
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorClassCreateExV(phClass, &SrcPos,
+ fAutodidact, true /*fRecursionOk*/, false /*fStrictReleaseOrder*/,
+ 1 /*cMsMinDeadlock*/, 1 /*cMsMinOrder*/,
+ pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Creates a new lock validator class with a reference that is consumed by the
+ * first call to RTLockValidatorClassRetain.
+ *
+ * This is tailored for use in the parameter list of a semaphore constructor.
+ *
+ * @returns Class handle with a reference that is automatically consumed by the
+ * first retainer. NIL_RTLOCKVALCLASS if we run into trouble.
+ *
+ * @param SRC_POS The source position where call is being made from.
+ * Use RT_SRC_POS when possible. Optional.
+ * @param pszNameFmt Class name format string, optional (NULL). Max
+ * length is 32 bytes.
+ * @param ... Format string arguments.
+ */
+RTDECL(RTLOCKVALCLASS) RTLockValidatorClassCreateUnique(RT_SRC_POS_DECL, const char *pszNameFmt, ...)
+{
+ RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_POS_NO_ID();
+ RTLOCKVALCLASSINT *pClass;
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorClassCreateExV(&pClass, &SrcPos,
+ true /*fAutodidact*/, true /*fRecursionOk*/, false /*fStrictReleaseOrder*/,
+ 1 /*cMsMinDeadlock*/, 1 /*cMsMinOrder*/,
+ pszNameFmt, va);
+ va_end(va);
+ if (RT_FAILURE(rc))
+ return NIL_RTLOCKVALCLASS;
+ ASMAtomicWriteBool(&pClass->fDonateRefToNextRetainer, true); /* see rtLockValidatorClassRetain */
+ return pClass;
+}
+
+
+/**
+ * Internal class retainer.
+ * @returns The new reference count.
+ * @param pClass The class.
+ */
+DECL_FORCE_INLINE(uint32_t) rtLockValidatorClassRetain(RTLOCKVALCLASSINT *pClass)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pClass->cRefs);
+ if (cRefs > RTLOCKVALCLASS_MAX_REFS)
+ ASMAtomicWriteU32(&pClass->cRefs, RTLOCKVALCLASS_MAX_REFS);
+ else if ( cRefs == 2
+ && ASMAtomicXchgBool(&pClass->fDonateRefToNextRetainer, false))
+ cRefs = ASMAtomicDecU32(&pClass->cRefs);
+ return cRefs;
+}
+
+
+/**
+ * Validates and retains a lock validator class.
+ *
+ * @returns @a hClass on success, NIL_RTLOCKVALCLASS on failure.
+ * @param hClass The class handle. NIL_RTLOCKVALCLASS is ok.
+ */
+DECL_FORCE_INLINE(RTLOCKVALCLASS) rtLockValidatorClassValidateAndRetain(RTLOCKVALCLASS hClass)
+{
+ if (hClass == NIL_RTLOCKVALCLASS)
+ return hClass;
+ AssertPtrReturn(hClass, NIL_RTLOCKVALCLASS);
+ AssertReturn(hClass->u32Magic == RTLOCKVALCLASS_MAGIC, NIL_RTLOCKVALCLASS);
+ rtLockValidatorClassRetain(hClass);
+ return hClass;
+}
+
+
+/**
+ * Internal class releaser.
+ * @returns The new reference count.
+ * @param pClass The class.
+ */
+DECLINLINE(uint32_t) rtLockValidatorClassRelease(RTLOCKVALCLASSINT *pClass)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pClass->cRefs);
+ if (cRefs + 1 == RTLOCKVALCLASS_MAX_REFS)
+ ASMAtomicWriteU32(&pClass->cRefs, RTLOCKVALCLASS_MAX_REFS);
+ else if (!cRefs)
+ rtLockValidatorClassDestroy(pClass);
+ return cRefs;
+}
+
+
+/**
+ * Destroys a class once there are not more references to it.
+ *
+ * @param pClass The class.
+ */
+static void rtLockValidatorClassDestroy(RTLOCKVALCLASSINT *pClass)
+{
+ AssertReturnVoid(!pClass->fInTree);
+ ASMAtomicWriteU32(&pClass->u32Magic, RTLOCKVALCLASS_MAGIC_DEAD);
+
+ PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks;
+ while (pChunk)
+ {
+ for (uint32_t i = 0; i < RT_ELEMENTS(pChunk->aRefs); i++)
+ {
+ RTLOCKVALCLASSINT *pClass2 = pChunk->aRefs[i].hClass;
+ if (pClass2 != NIL_RTLOCKVALCLASS)
+ {
+ pChunk->aRefs[i].hClass = NIL_RTLOCKVALCLASS;
+ rtLockValidatorClassRelease(pClass2);
+ }
+ }
+
+ PRTLOCKVALCLASSREFCHUNK pNext = pChunk->pNext;
+ pChunk->pNext = NULL;
+ if (pChunk != &pClass->PriorLocks)
+ RTMemFree(pChunk);
+ pChunk = pNext;
+ }
+
+ RTMemFree(pClass);
+}
+
+
+RTDECL(RTLOCKVALCLASS) RTLockValidatorClassFindForSrcPos(PRTLOCKVALSRCPOS pSrcPos)
+{
+ if (g_hLockValClassTreeRWLock == NIL_RTSEMRW)
+ rtLockValidatorLazyInit();
+ int rcLock = RTSemRWRequestRead(g_hLockValClassTreeRWLock, RT_INDEFINITE_WAIT);
+
+ uint32_t uSrcPosHash = rtLockValidatorSrcPosHash(pSrcPos);
+ RTLOCKVALCLASSINT *pClass = (RTLOCKVALCLASSINT *)RTAvllU32Get(&g_LockValClassTree, uSrcPosHash);
+ while (pClass)
+ {
+ if (rtLockValidatorSrcPosCompare(&pClass->CreatePos, pSrcPos) == 0)
+ break;
+ pClass = (RTLOCKVALCLASSINT *)pClass->Core.pList;
+ }
+
+ if (RT_SUCCESS(rcLock))
+ RTSemRWReleaseRead(g_hLockValClassTreeRWLock);
+ return pClass;
+}
+
+
+RTDECL(RTLOCKVALCLASS) RTLockValidatorClassForSrcPos(RT_SRC_POS_DECL, const char *pszNameFmt, ...)
+{
+ RTLOCKVALSRCPOS SrcPos = RTLOCKVALSRCPOS_INIT_POS_NO_ID();
+ RTLOCKVALCLASS hClass = RTLockValidatorClassFindForSrcPos(&SrcPos);
+ if (hClass == NIL_RTLOCKVALCLASS)
+ {
+ /*
+ * Create a new class and insert it into the tree.
+ */
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorClassCreateExV(&hClass, &SrcPos,
+ true /*fAutodidact*/, true /*fRecursionOk*/, false /*fStrictReleaseOrder*/,
+ 1 /*cMsMinDeadlock*/, 1 /*cMsMinOrder*/,
+ pszNameFmt, va);
+ va_end(va);
+ if (RT_SUCCESS(rc))
+ {
+ if (g_hLockValClassTreeRWLock == NIL_RTSEMRW)
+ rtLockValidatorLazyInit();
+ int rcLock = RTSemRWRequestWrite(g_hLockValClassTreeRWLock, RT_INDEFINITE_WAIT);
+
+ Assert(!hClass->fInTree);
+ hClass->fInTree = RTAvllU32Insert(&g_LockValClassTree, &hClass->Core);
+ Assert(hClass->fInTree);
+
+ if (RT_SUCCESS(rcLock))
+ RTSemRWReleaseWrite(g_hLockValClassTreeRWLock);
+ return hClass;
+ }
+ }
+ return hClass;
+}
+
+
+RTDECL(uint32_t) RTLockValidatorClassRetain(RTLOCKVALCLASS hClass)
+{
+ RTLOCKVALCLASSINT *pClass = hClass;
+ AssertPtrReturn(pClass, UINT32_MAX);
+ AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, UINT32_MAX);
+ return rtLockValidatorClassRetain(pClass);
+}
+
+
+RTDECL(uint32_t) RTLockValidatorClassRelease(RTLOCKVALCLASS hClass)
+{
+ RTLOCKVALCLASSINT *pClass = hClass;
+ if (pClass == NIL_RTLOCKVALCLASS)
+ return 0;
+ AssertPtrReturn(pClass, UINT32_MAX);
+ AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, UINT32_MAX);
+ return rtLockValidatorClassRelease(pClass);
+}
+
+
+/**
+ * Worker for rtLockValidatorClassIsPriorClass that does a linear search thru
+ * all the chunks for @a pPriorClass.
+ *
+ * @returns true / false.
+ * @param pClass The class to search.
+ * @param pPriorClass The class to search for.
+ */
+static bool rtLockValidatorClassIsPriorClassByLinearSearch(RTLOCKVALCLASSINT *pClass, RTLOCKVALCLASSINT *pPriorClass)
+{
+ for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; pChunk; pChunk = pChunk->pNext)
+ for (uint32_t i = 0; i < RT_ELEMENTS(pChunk->aRefs); i++)
+ {
+ if (pChunk->aRefs[i].hClass == pPriorClass)
+ {
+ uint32_t cLookups = ASMAtomicIncU32(&pChunk->aRefs[i].cLookups);
+ if (RT_UNLIKELY(cLookups >= RTLOCKVALCLASSREF_MAX_LOOKUPS_FIX))
+ {
+ ASMAtomicWriteU32(&pChunk->aRefs[i].cLookups, RTLOCKVALCLASSREF_MAX_LOOKUPS);
+ cLookups = RTLOCKVALCLASSREF_MAX_LOOKUPS;
+ }
+
+ /* update the hash table entry. */
+ PRTLOCKVALCLASSREF *ppHashEntry = &pClass->apPriorLocksHash[RTLOCKVALCLASS_HASH(pPriorClass)];
+ if ( !(*ppHashEntry)
+ || (*ppHashEntry)->cLookups + 128 < cLookups)
+ ASMAtomicWritePtr(ppHashEntry, &pChunk->aRefs[i]);
+
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ ASMAtomicIncU32(&pClass->cHashMisses);
+#endif
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Checks if @a pPriorClass is a known prior class.
+ *
+ * @returns true / false.
+ * @param pClass The class to search.
+ * @param pPriorClass The class to search for.
+ */
+DECL_FORCE_INLINE(bool) rtLockValidatorClassIsPriorClass(RTLOCKVALCLASSINT *pClass, RTLOCKVALCLASSINT *pPriorClass)
+{
+ /*
+ * Hash lookup here.
+ */
+ PRTLOCKVALCLASSREF pRef = pClass->apPriorLocksHash[RTLOCKVALCLASS_HASH(pPriorClass)];
+ if ( pRef
+ && pRef->hClass == pPriorClass)
+ {
+ uint32_t cLookups = ASMAtomicIncU32(&pRef->cLookups);
+ if (RT_UNLIKELY(cLookups >= RTLOCKVALCLASSREF_MAX_LOOKUPS_FIX))
+ ASMAtomicWriteU32(&pRef->cLookups, RTLOCKVALCLASSREF_MAX_LOOKUPS);
+#ifdef RTLOCKVAL_WITH_CLASS_HASH_STATS
+ ASMAtomicIncU32(&pClass->cHashHits);
+#endif
+ return true;
+ }
+
+ return rtLockValidatorClassIsPriorClassByLinearSearch(pClass, pPriorClass);
+}
+
+
+/**
+ * Adds a class to the prior list.
+ *
+ * @returns VINF_SUCCESS, VERR_NO_MEMORY or VERR_SEM_LV_WRONG_ORDER.
+ * @param pClass The class to work on.
+ * @param pPriorClass The class to add.
+ * @param fAutodidacticism Whether we're teaching ourselves (true) or
+ * somebody is teaching us via the API (false).
+ * @param pSrcPos Where this rule was added (optional).
+ */
+static int rtLockValidatorClassAddPriorClass(RTLOCKVALCLASSINT *pClass, RTLOCKVALCLASSINT *pPriorClass,
+ bool fAutodidacticism, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ NOREF(pSrcPos);
+ if (!RTCritSectIsInitialized(&g_LockValClassTeachCS))
+ rtLockValidatorLazyInit();
+ int rcLock = RTCritSectEnter(&g_LockValClassTeachCS);
+
+ /*
+ * Check that there are no conflict (no assert since we might race each other).
+ */
+ int rc = VERR_SEM_LV_INTERNAL_ERROR;
+ if (!rtLockValidatorClassIsPriorClass(pPriorClass, pClass))
+ {
+ if (!rtLockValidatorClassIsPriorClass(pClass, pPriorClass))
+ {
+ /*
+ * Scan the table for a free entry, allocating a new chunk if necessary.
+ */
+ for (PRTLOCKVALCLASSREFCHUNK pChunk = &pClass->PriorLocks; ; pChunk = pChunk->pNext)
+ {
+ bool fDone = false;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pChunk->aRefs); i++)
+ {
+ ASMAtomicCmpXchgHandle(&pChunk->aRefs[i].hClass, pPriorClass, NIL_RTLOCKVALCLASS, fDone);
+ if (fDone)
+ {
+ pChunk->aRefs[i].fAutodidacticism = fAutodidacticism;
+ rtLockValidatorClassRetain(pPriorClass);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ if (fDone)
+ break;
+
+ /* If no more chunks, allocate a new one and insert the class before linking it. */
+ if (!pChunk->pNext)
+ {
+ PRTLOCKVALCLASSREFCHUNK pNew = (PRTLOCKVALCLASSREFCHUNK)RTMemAlloc(sizeof(*pNew));
+ if (!pNew)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ RTMEM_MAY_LEAK(pNew);
+ pNew->pNext = NULL;
+ for (uint32_t i = 0; i < RT_ELEMENTS(pNew->aRefs); i++)
+ {
+ pNew->aRefs[i].hClass = NIL_RTLOCKVALCLASS;
+ pNew->aRefs[i].cLookups = 0;
+ pNew->aRefs[i].fAutodidacticism = false;
+ pNew->aRefs[i].afReserved[0] = false;
+ pNew->aRefs[i].afReserved[1] = false;
+ pNew->aRefs[i].afReserved[2] = false;
+ }
+
+ pNew->aRefs[0].hClass = pPriorClass;
+ pNew->aRefs[0].fAutodidacticism = fAutodidacticism;
+
+ ASMAtomicWritePtr(&pChunk->pNext, pNew);
+ rtLockValidatorClassRetain(pPriorClass);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ } /* chunk loop */
+ }
+ else
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = !g_fLockValSoftWrongOrder ? VERR_SEM_LV_WRONG_ORDER : VINF_SUCCESS;
+
+ if (RT_SUCCESS(rcLock))
+ RTCritSectLeave(&g_LockValClassTeachCS);
+ return rc;
+}
+
+
+RTDECL(int) RTLockValidatorClassAddPriorClass(RTLOCKVALCLASS hClass, RTLOCKVALCLASS hPriorClass)
+{
+ RTLOCKVALCLASSINT *pClass = hClass;
+ AssertPtrReturn(pClass, VERR_INVALID_HANDLE);
+ AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_INVALID_HANDLE);
+
+ RTLOCKVALCLASSINT *pPriorClass = hPriorClass;
+ AssertPtrReturn(pPriorClass, VERR_INVALID_HANDLE);
+ AssertReturn(pPriorClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_INVALID_HANDLE);
+
+ return rtLockValidatorClassAddPriorClass(pClass, pPriorClass, false /*fAutodidacticism*/, NULL);
+}
+
+
+RTDECL(int) RTLockValidatorClassEnforceStrictReleaseOrder(RTLOCKVALCLASS hClass, bool fEnabled)
+{
+ RTLOCKVALCLASSINT *pClass = hClass;
+ AssertPtrReturn(pClass, VERR_INVALID_HANDLE);
+ AssertReturn(pClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_INVALID_HANDLE);
+
+ ASMAtomicWriteBool(&pClass->fStrictReleaseOrder, fEnabled);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unlinks all siblings.
+ *
+ * This is used during record deletion and assumes no races.
+ *
+ * @param pCore One of the siblings.
+ */
+static void rtLockValidatorUnlinkAllSiblings(PRTLOCKVALRECCORE pCore)
+{
+ /* ASSUMES sibling destruction doesn't involve any races and that all
+ related records are to be disposed off now. */
+ PRTLOCKVALRECUNION pSibling = (PRTLOCKVALRECUNION)pCore;
+ while (pSibling)
+ {
+ PRTLOCKVALRECUNION volatile *ppCoreNext;
+ switch (pSibling->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ case RTLOCKVALRECEXCL_MAGIC_DEAD:
+ ppCoreNext = &pSibling->Excl.pSibling;
+ break;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ case RTLOCKVALRECSHRD_MAGIC_DEAD:
+ ppCoreNext = &pSibling->Shared.pSibling;
+ break;
+
+ default:
+ AssertFailed();
+ ppCoreNext = NULL;
+ break;
+ }
+ if (RT_UNLIKELY(ppCoreNext))
+ break;
+ pSibling = ASMAtomicXchgPtrT(ppCoreNext, NULL, PRTLOCKVALRECUNION);
+ }
+}
+
+
+RTDECL(int) RTLockValidatorRecMakeSiblings(PRTLOCKVALRECCORE pRec1, PRTLOCKVALRECCORE pRec2)
+{
+ /*
+ * Validate input.
+ */
+ PRTLOCKVALRECUNION p1 = (PRTLOCKVALRECUNION)pRec1;
+ PRTLOCKVALRECUNION p2 = (PRTLOCKVALRECUNION)pRec2;
+
+ AssertPtrReturn(p1, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn( p1->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ || p1->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ , VERR_SEM_LV_INVALID_PARAMETER);
+
+ AssertPtrReturn(p2, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn( p2->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ || p2->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ , VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Link them (circular list).
+ */
+ if ( p1->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ && p2->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)
+ {
+ p1->Excl.pSibling = p2;
+ p2->Shared.pSibling = p1;
+ }
+ else if ( p1->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ && p2->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC)
+ {
+ p1->Shared.pSibling = p2;
+ p2->Excl.pSibling = p1;
+ }
+ else
+ AssertFailedReturn(VERR_SEM_LV_INVALID_PARAMETER); /* unsupported mix */
+
+ return VINF_SUCCESS;
+}
+
+
+#if 0 /* unused */
+/**
+ * Gets the lock name for the given record.
+ *
+ * @returns Read-only lock name.
+ * @param pRec The lock record.
+ */
+DECL_FORCE_INLINE(const char *) rtLockValidatorRecName(PRTLOCKVALRECUNION pRec)
+{
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.szName;
+ case RTLOCKVALRECSHRD_MAGIC:
+ return pRec->Shared.szName;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ return pRec->ShrdOwner.pSharedRec ? pRec->ShrdOwner.pSharedRec->szName : "orphaned";
+ case RTLOCKVALRECNEST_MAGIC:
+ pRec = rtLockValidatorReadRecUnionPtr(&pRec->Nest.pRec);
+ if (RT_VALID_PTR(pRec))
+ {
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.szName;
+ case RTLOCKVALRECSHRD_MAGIC:
+ return pRec->Shared.szName;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ return pRec->ShrdOwner.pSharedRec ? pRec->ShrdOwner.pSharedRec->szName : "orphaned";
+ default:
+ return "unknown-nested";
+ }
+ }
+ return "orphaned-nested";
+ default:
+ return "unknown";
+ }
+}
+#endif /* unused */
+
+
+#if 0 /* unused */
+/**
+ * Gets the class for this locking record.
+ *
+ * @returns Pointer to the class or NIL_RTLOCKVALCLASS.
+ * @param pRec The lock validator record.
+ */
+DECLINLINE(RTLOCKVALCLASSINT *) rtLockValidatorRecGetClass(PRTLOCKVALRECUNION pRec)
+{
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.hClass;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ return pRec->Shared.hClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ return pSharedRec->hClass;
+ return NIL_RTLOCKVALCLASS;
+ }
+
+ case RTLOCKVALRECNEST_MAGIC:
+ {
+ PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec;
+ if (RT_VALID_PTR(pRealRec))
+ {
+ switch (pRealRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRealRec->Excl.hClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRealRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ return pSharedRec->hClass;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%p %p %#x\n", pRec, pRealRec, pRealRec->Core.u32Magic));
+ break;
+ }
+ }
+ return NIL_RTLOCKVALCLASS;
+ }
+
+ default:
+ AssertMsgFailed(("%#x\n", pRec->Core.u32Magic));
+ return NIL_RTLOCKVALCLASS;
+ }
+}
+#endif /* unused */
+
+/**
+ * Gets the class for this locking record and the pointer to the one below it in
+ * the stack.
+ *
+ * @returns Pointer to the class or NIL_RTLOCKVALCLASS.
+ * @param pRec The lock validator record.
+ * @param puSubClass Where to return the sub-class.
+ * @param ppDown Where to return the pointer to the record below.
+ */
+DECL_FORCE_INLINE(RTLOCKVALCLASSINT *)
+rtLockValidatorRecGetClassesAndDown(PRTLOCKVALRECUNION pRec, uint32_t *puSubClass, PRTLOCKVALRECUNION *ppDown)
+{
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ *ppDown = pRec->Excl.pDown;
+ *puSubClass = pRec->Excl.uSubClass;
+ return pRec->Excl.hClass;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ *ppDown = NULL;
+ *puSubClass = pRec->Shared.uSubClass;
+ return pRec->Shared.hClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ *ppDown = pRec->ShrdOwner.pDown;
+
+ PRTLOCKVALRECSHRD pSharedRec = pRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ {
+ *puSubClass = pSharedRec->uSubClass;
+ return pSharedRec->hClass;
+ }
+ *puSubClass = RTLOCKVAL_SUB_CLASS_NONE;
+ return NIL_RTLOCKVALCLASS;
+ }
+
+ case RTLOCKVALRECNEST_MAGIC:
+ {
+ *ppDown = pRec->Nest.pDown;
+
+ PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec;
+ if (RT_VALID_PTR(pRealRec))
+ {
+ switch (pRealRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ *puSubClass = pRealRec->Excl.uSubClass;
+ return pRealRec->Excl.hClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRealRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ {
+ *puSubClass = pSharedRec->uSubClass;
+ return pSharedRec->hClass;
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%p %p %#x\n", pRec, pRealRec, pRealRec->Core.u32Magic));
+ break;
+ }
+ }
+ *puSubClass = RTLOCKVAL_SUB_CLASS_NONE;
+ return NIL_RTLOCKVALCLASS;
+ }
+
+ default:
+ AssertMsgFailed(("%#x\n", pRec->Core.u32Magic));
+ *ppDown = NULL;
+ *puSubClass = RTLOCKVAL_SUB_CLASS_NONE;
+ return NIL_RTLOCKVALCLASS;
+ }
+}
+
+
+/**
+ * Gets the sub-class for a lock record.
+ *
+ * @returns the sub-class.
+ * @param pRec The lock validator record.
+ */
+DECLINLINE(uint32_t) rtLockValidatorRecGetSubClass(PRTLOCKVALRECUNION pRec)
+{
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.uSubClass;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ return pRec->Shared.uSubClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ return pSharedRec->uSubClass;
+ return RTLOCKVAL_SUB_CLASS_NONE;
+ }
+
+ case RTLOCKVALRECNEST_MAGIC:
+ {
+ PRTLOCKVALRECUNION pRealRec = pRec->Nest.pRec;
+ if (RT_VALID_PTR(pRealRec))
+ {
+ switch (pRealRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ return pRec->Excl.uSubClass;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ {
+ PRTLOCKVALRECSHRD pSharedRec = pRealRec->ShrdOwner.pSharedRec;
+ if (RT_LIKELY( RT_VALID_PTR(pSharedRec)
+ && pSharedRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC))
+ return pSharedRec->uSubClass;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%p %p %#x\n", pRec, pRealRec, pRealRec->Core.u32Magic));
+ break;
+ }
+ }
+ return RTLOCKVAL_SUB_CLASS_NONE;
+ }
+
+ default:
+ AssertMsgFailed(("%#x\n", pRec->Core.u32Magic));
+ return RTLOCKVAL_SUB_CLASS_NONE;
+ }
+}
+
+
+
+
+/**
+ * Calculates the depth of a lock stack.
+ *
+ * @returns Number of stack frames.
+ * @param pThread The thread.
+ */
+static uint32_t rtLockValidatorStackDepth(PRTTHREADINT pThread)
+{
+ uint32_t cEntries = 0;
+ PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
+ while (RT_VALID_PTR(pCur))
+ {
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown);
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown);
+ break;
+
+ case RTLOCKVALRECNEST_MAGIC:
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown);
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%#x\n", pCur->Core.u32Magic), cEntries);
+ }
+ cEntries++;
+ }
+ return cEntries;
+}
+
+
+#ifdef RT_STRICT
+/**
+ * Checks if the stack contains @a pRec.
+ *
+ * @returns true / false.
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ */
+static bool rtLockValidatorStackContainsRec(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop;
+ while (pCur)
+ {
+ AssertPtrReturn(pCur, false);
+ if (pCur == pRec)
+ return true;
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(pCur->Excl.cRecursion >= 1);
+ pCur = pCur->Excl.pDown;
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ Assert(pCur->ShrdOwner.cRecursion >= 1);
+ pCur = pCur->ShrdOwner.pDown;
+ break;
+
+ case RTLOCKVALRECNEST_MAGIC:
+ Assert(pCur->Nest.cRecursion > 1);
+ pCur = pCur->Nest.pDown;
+ break;
+
+ default:
+ AssertMsgFailedReturn(("%#x\n", pCur->Core.u32Magic), false);
+ }
+ }
+ return false;
+}
+#endif /* RT_STRICT */
+
+
+/**
+ * Pushes a lock record onto the stack.
+ *
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ */
+static void rtLockValidatorStackPush(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ Assert(pThreadSelf == RTThreadSelf());
+ Assert(!rtLockValidatorStackContainsRec(pThreadSelf, pRec));
+
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(pRec->Excl.cRecursion == 1);
+ Assert(pRec->Excl.pDown == NULL);
+ rtLockValidatorWriteRecUnionPtr(&pRec->Excl.pDown, pThreadSelf->LockValidator.pStackTop);
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ Assert(pRec->ShrdOwner.cRecursion == 1);
+ Assert(pRec->ShrdOwner.pDown == NULL);
+ rtLockValidatorWriteRecUnionPtr(&pRec->ShrdOwner.pDown, pThreadSelf->LockValidator.pStackTop);
+ break;
+
+ default:
+ AssertMsgFailedReturnVoid(("%#x\n", pRec->Core.u32Magic));
+ }
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, pRec);
+}
+
+
+/**
+ * Pops a lock record off the stack.
+ *
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock.
+ */
+static void rtLockValidatorStackPop(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ Assert(pThreadSelf == RTThreadSelf());
+
+ PRTLOCKVALRECUNION pDown;
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(pRec->Excl.cRecursion == 0);
+ pDown = pRec->Excl.pDown;
+ rtLockValidatorWriteRecUnionPtr(&pRec->Excl.pDown, NULL); /* lazy bird */
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ Assert(pRec->ShrdOwner.cRecursion == 0);
+ pDown = pRec->ShrdOwner.pDown;
+ rtLockValidatorWriteRecUnionPtr(&pRec->ShrdOwner.pDown, NULL);
+ break;
+
+ default:
+ AssertMsgFailedReturnVoid(("%#x\n", pRec->Core.u32Magic));
+ }
+ if (pThreadSelf->LockValidator.pStackTop == pRec)
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, pDown);
+ else
+ {
+ /* Find the pointer to our record and unlink ourselves. */
+ PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop;
+ while (pCur)
+ {
+ PRTLOCKVALRECUNION volatile *ppDown;
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(pCur->Excl.cRecursion >= 1);
+ ppDown = &pCur->Excl.pDown;
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ Assert(pCur->ShrdOwner.cRecursion >= 1);
+ ppDown = &pCur->ShrdOwner.pDown;
+ break;
+
+ case RTLOCKVALRECNEST_MAGIC:
+ Assert(pCur->Nest.cRecursion >= 1);
+ ppDown = &pCur->Nest.pDown;
+ break;
+
+ default:
+ AssertMsgFailedReturnVoid(("%#x\n", pCur->Core.u32Magic));
+ }
+ pCur = *ppDown;
+ if (pCur == pRec)
+ {
+ rtLockValidatorWriteRecUnionPtr(ppDown, pDown);
+ return;
+ }
+ }
+ AssertMsgFailed(("%p %p\n", pRec, pThreadSelf));
+ }
+}
+
+
+/**
+ * Creates and pushes lock recursion record onto the stack.
+ *
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ * @param pSrcPos Where the recursion occurred.
+ */
+static void rtLockValidatorStackPushRecursion(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ Assert(pThreadSelf == RTThreadSelf());
+ Assert(rtLockValidatorStackContainsRec(pThreadSelf, pRec));
+
+#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS
+ /*
+ * Allocate a new recursion record
+ */
+ PRTLOCKVALRECNEST pRecursionRec = pThreadSelf->LockValidator.pFreeNestRecs;
+ if (pRecursionRec)
+ pThreadSelf->LockValidator.pFreeNestRecs = pRecursionRec->pNextFree;
+ else
+ {
+ pRecursionRec = (PRTLOCKVALRECNEST)RTMemAlloc(sizeof(*pRecursionRec));
+ if (!pRecursionRec)
+ return;
+ }
+
+ /*
+ * Initialize it.
+ */
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ pRecursionRec->cRecursion = pRec->Excl.cRecursion;
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ pRecursionRec->cRecursion = pRec->ShrdOwner.cRecursion;
+ break;
+
+ default:
+ AssertMsgFailed(("%#x\n", pRec->Core.u32Magic));
+ rtLockValidatorSerializeDestructEnter();
+ rtLockValidatorSerializeDestructLeave();
+ RTMemFree(pRecursionRec);
+ return;
+ }
+ Assert(pRecursionRec->cRecursion > 1);
+ pRecursionRec->pRec = pRec;
+ pRecursionRec->pDown = NULL;
+ pRecursionRec->pNextFree = NULL;
+ rtLockValidatorSrcPosCopy(&pRecursionRec->SrcPos, pSrcPos);
+ pRecursionRec->Core.u32Magic = RTLOCKVALRECNEST_MAGIC;
+
+ /*
+ * Link it.
+ */
+ pRecursionRec->pDown = pThreadSelf->LockValidator.pStackTop;
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, (PRTLOCKVALRECUNION)pRecursionRec);
+#endif /* RTLOCKVAL_WITH_RECURSION_RECORDS */
+}
+
+
+/**
+ * Pops a lock recursion record off the stack.
+ *
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ */
+static void rtLockValidatorStackPopRecursion(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ Assert(pThreadSelf == RTThreadSelf());
+ Assert(rtLockValidatorStackContainsRec(pThreadSelf, pRec));
+
+ uint32_t cRecursion;
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC: cRecursion = pRec->Excl.cRecursion; break;
+ case RTLOCKVALRECSHRDOWN_MAGIC: cRecursion = pRec->ShrdOwner.cRecursion; break;
+ default: AssertMsgFailedReturnVoid(("%#x\n", pRec->Core.u32Magic));
+ }
+ Assert(cRecursion >= 1);
+
+#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS
+ /*
+ * Pop the recursion record.
+ */
+ PRTLOCKVALRECUNION pNest = pThreadSelf->LockValidator.pStackTop;
+ if ( pNest != NULL
+ && pNest->Core.u32Magic == RTLOCKVALRECNEST_MAGIC
+ && pNest->Nest.pRec == pRec
+ )
+ {
+ Assert(pNest->Nest.cRecursion == cRecursion + 1);
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pStackTop, pNest->Nest.pDown);
+ }
+ else
+ {
+ /* Find the record above ours. */
+ PRTLOCKVALRECUNION volatile *ppDown = NULL;
+ for (;;)
+ {
+ AssertMsgReturnVoid(pNest, ("%p %p\n", pRec, pThreadSelf));
+ switch (pNest->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ ppDown = &pNest->Excl.pDown;
+ pNest = *ppDown;
+ continue;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ ppDown = &pNest->ShrdOwner.pDown;
+ pNest = *ppDown;
+ continue;
+ case RTLOCKVALRECNEST_MAGIC:
+ if (pNest->Nest.pRec == pRec)
+ break;
+ ppDown = &pNest->Nest.pDown;
+ pNest = *ppDown;
+ continue;
+ default:
+ AssertMsgFailedReturnVoid(("%#x\n", pNest->Core.u32Magic));
+ }
+ break; /* ugly */
+ }
+ Assert(pNest->Nest.cRecursion == cRecursion + 1);
+ rtLockValidatorWriteRecUnionPtr(ppDown, pNest->Nest.pDown);
+ }
+
+ /*
+ * Invalidate and free the record.
+ */
+ ASMAtomicWriteU32(&pNest->Core.u32Magic, RTLOCKVALRECNEST_MAGIC);
+ rtLockValidatorWriteRecUnionPtr(&pNest->Nest.pDown, NULL);
+ rtLockValidatorWriteRecUnionPtr(&pNest->Nest.pRec, NULL);
+ pNest->Nest.cRecursion = 0;
+ pNest->Nest.pNextFree = pThreadSelf->LockValidator.pFreeNestRecs;
+ pThreadSelf->LockValidator.pFreeNestRecs = &pNest->Nest;
+#endif /* RTLOCKVAL_WITH_RECURSION_RECORDS */
+}
+
+
+/**
+ * Helper for rtLockValidatorStackCheckLockingOrder that does the bitching and
+ * returns VERR_SEM_LV_WRONG_ORDER.
+ */
+static int rtLockValidatorStackWrongOrder(const char *pszWhat, PCRTLOCKVALSRCPOS pSrcPos, PRTTHREADINT pThreadSelf,
+ PRTLOCKVALRECUNION pRec1, PRTLOCKVALRECUNION pRec2,
+ RTLOCKVALCLASSINT *pClass1, RTLOCKVALCLASSINT *pClass2)
+
+
+{
+ rtLockValComplainFirst(pszWhat, pSrcPos, pThreadSelf, pRec1, false);
+ rtLockValComplainAboutLock("Other lock: ", pRec2, "\n");
+ rtLockValComplainAboutClass("My class: ", pClass1, rtLockValidatorRecGetSubClass(pRec1), true /*fVerbose*/);
+ rtLockValComplainAboutClass("Other class: ", pClass2, rtLockValidatorRecGetSubClass(pRec2), true /*fVerbose*/);
+ rtLockValComplainAboutLockStack(pThreadSelf, 0, 0, pRec2);
+ rtLockValComplainPanic();
+ return !g_fLockValSoftWrongOrder ? VERR_SEM_LV_WRONG_ORDER : VINF_SUCCESS;
+}
+
+
+/**
+ * Checks if the sub-class order is ok or not.
+ *
+ * Used to deal with two locks from the same class.
+ *
+ * @returns true if ok, false if not.
+ * @param uSubClass1 The sub-class of the lock that is being
+ * considered.
+ * @param uSubClass2 The sub-class of the lock that is already being
+ * held.
+ */
+DECL_FORCE_INLINE(bool) rtLockValidatorIsSubClassOrderOk(uint32_t uSubClass1, uint32_t uSubClass2)
+{
+ if (uSubClass1 > uSubClass2)
+ {
+ /* NONE kills ANY. */
+ if (uSubClass2 == RTLOCKVAL_SUB_CLASS_NONE)
+ return false;
+ return true;
+ }
+
+ /* ANY counters all USER values. (uSubClass1 == NONE only if they are equal) */
+ AssertCompile(RTLOCKVAL_SUB_CLASS_ANY > RTLOCKVAL_SUB_CLASS_NONE);
+ if (uSubClass1 == RTLOCKVAL_SUB_CLASS_ANY)
+ return true;
+ return false;
+}
+
+
+/**
+ * Checks if the class and sub-class lock order is ok.
+ *
+ * @returns true if ok, false if not.
+ * @param pClass1 The class of the lock that is being considered.
+ * @param uSubClass1 The sub-class that goes with @a pClass1.
+ * @param pClass2 The class of the lock that is already being
+ * held.
+ * @param uSubClass2 The sub-class that goes with @a pClass2.
+ */
+DECL_FORCE_INLINE(bool) rtLockValidatorIsClassOrderOk(RTLOCKVALCLASSINT *pClass1, uint32_t uSubClass1,
+ RTLOCKVALCLASSINT *pClass2, uint32_t uSubClass2)
+{
+ if (pClass1 == pClass2)
+ return rtLockValidatorIsSubClassOrderOk(uSubClass1, uSubClass2);
+ return rtLockValidatorClassIsPriorClass(pClass1, pClass2);
+}
+
+
+/**
+ * Checks the locking order, part two.
+ *
+ * @returns VINF_SUCCESS, VERR_SEM_LV_WRONG_ORDER or VERR_SEM_LV_INTERNAL_ERROR.
+ * @param pClass The lock class.
+ * @param uSubClass The lock sub-class.
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ * @param pSrcPos The source position of the locking operation.
+ * @param pFirstBadClass The first bad class.
+ * @param pFirstBadRec The first bad lock record.
+ * @param pFirstBadDown The next record on the lock stack.
+ */
+static int rtLockValidatorStackCheckLockingOrder2(RTLOCKVALCLASSINT * const pClass, uint32_t const uSubClass,
+ PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION const pRec,
+ PCRTLOCKVALSRCPOS const pSrcPos,
+ RTLOCKVALCLASSINT * const pFirstBadClass,
+ PRTLOCKVALRECUNION const pFirstBadRec,
+ PRTLOCKVALRECUNION const pFirstBadDown)
+{
+ /*
+ * Something went wrong, pCur is pointing to where.
+ */
+ if ( pClass == pFirstBadClass
+ || rtLockValidatorClassIsPriorClass(pFirstBadClass, pClass))
+ return rtLockValidatorStackWrongOrder("Wrong locking order!", pSrcPos, pThreadSelf,
+ pRec, pFirstBadRec, pClass, pFirstBadClass);
+ if (!pClass->fAutodidact)
+ return rtLockValidatorStackWrongOrder("Wrong locking order! (unknown)", pSrcPos, pThreadSelf,
+ pRec, pFirstBadRec, pClass, pFirstBadClass);
+
+ /*
+ * This class is an autodidact, so we have to check out the rest of the stack
+ * for direct violations.
+ */
+ uint32_t cNewRules = 1;
+ PRTLOCKVALRECUNION pCur = pFirstBadDown;
+ while (pCur)
+ {
+ AssertPtrReturn(pCur, VERR_SEM_LV_INTERNAL_ERROR);
+
+ if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ pCur = pCur->Nest.pDown;
+ else
+ {
+ PRTLOCKVALRECUNION pDown;
+ uint32_t uPriorSubClass;
+ RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown);
+ if (pPriorClass != NIL_RTLOCKVALCLASS)
+ {
+ AssertPtrReturn(pPriorClass, VERR_SEM_LV_INTERNAL_ERROR);
+ AssertReturn(pPriorClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_SEM_LV_INTERNAL_ERROR);
+ if (!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass))
+ {
+ if ( pClass == pPriorClass
+ || rtLockValidatorClassIsPriorClass(pPriorClass, pClass))
+ return rtLockValidatorStackWrongOrder("Wrong locking order! (more than one)", pSrcPos, pThreadSelf,
+ pRec, pCur, pClass, pPriorClass);
+ cNewRules++;
+ }
+ }
+ pCur = pDown;
+ }
+ }
+
+ if (cNewRules == 1)
+ {
+ /*
+ * Special case the simple operation, hoping that it will be a
+ * frequent case.
+ */
+ int rc = rtLockValidatorClassAddPriorClass(pClass, pFirstBadClass, true /*fAutodidacticism*/, pSrcPos);
+ if (rc == VERR_SEM_LV_WRONG_ORDER)
+ return rtLockValidatorStackWrongOrder("Wrong locking order! (race)", pSrcPos, pThreadSelf,
+ pRec, pFirstBadRec, pClass, pFirstBadClass);
+ Assert(RT_SUCCESS(rc) || rc == VERR_NO_MEMORY);
+ }
+ else
+ {
+ /*
+ * We may be adding more than one rule, so we have to take the lock
+ * before starting to add the rules. This means we have to check
+ * the state after taking it since we might be racing someone adding
+ * a conflicting rule.
+ */
+ if (!RTCritSectIsInitialized(&g_LockValClassTeachCS))
+ rtLockValidatorLazyInit();
+ int rcLock = RTCritSectEnter(&g_LockValClassTeachCS);
+
+ /* Check */
+ pCur = pFirstBadRec;
+ while (pCur)
+ {
+ if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ pCur = pCur->Nest.pDown;
+ else
+ {
+ uint32_t uPriorSubClass;
+ PRTLOCKVALRECUNION pDown;
+ RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown);
+ if (pPriorClass != NIL_RTLOCKVALCLASS)
+ {
+ if (!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass))
+ {
+ if ( pClass == pPriorClass
+ || rtLockValidatorClassIsPriorClass(pPriorClass, pClass))
+ {
+ if (RT_SUCCESS(rcLock))
+ RTCritSectLeave(&g_LockValClassTeachCS);
+ return rtLockValidatorStackWrongOrder("Wrong locking order! (2nd)", pSrcPos, pThreadSelf,
+ pRec, pCur, pClass, pPriorClass);
+ }
+ }
+ }
+ pCur = pDown;
+ }
+ }
+
+ /* Iterate the stack yet again, adding new rules this time. */
+ pCur = pFirstBadRec;
+ while (pCur)
+ {
+ if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ pCur = pCur->Nest.pDown;
+ else
+ {
+ uint32_t uPriorSubClass;
+ PRTLOCKVALRECUNION pDown;
+ RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown);
+ if (pPriorClass != NIL_RTLOCKVALCLASS)
+ {
+ if (!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass))
+ {
+ Assert( pClass != pPriorClass
+ && !rtLockValidatorClassIsPriorClass(pPriorClass, pClass));
+ int rc = rtLockValidatorClassAddPriorClass(pClass, pPriorClass, true /*fAutodidacticism*/, pSrcPos);
+ if (RT_FAILURE(rc))
+ {
+ Assert(rc == VERR_NO_MEMORY);
+ break;
+ }
+ Assert(rtLockValidatorClassIsPriorClass(pClass, pPriorClass));
+ }
+ }
+ pCur = pDown;
+ }
+ }
+
+ if (RT_SUCCESS(rcLock))
+ RTCritSectLeave(&g_LockValClassTeachCS);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Checks the locking order.
+ *
+ * @returns VINF_SUCCESS, VERR_SEM_LV_WRONG_ORDER or VERR_SEM_LV_INTERNAL_ERROR.
+ * @param pClass The lock class.
+ * @param uSubClass The lock sub-class.
+ * @param pThreadSelf The current thread.
+ * @param pRec The lock record.
+ * @param pSrcPos The source position of the locking operation.
+ */
+static int rtLockValidatorStackCheckLockingOrder(RTLOCKVALCLASSINT * const pClass, uint32_t const uSubClass,
+ PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION const pRec,
+ PCRTLOCKVALSRCPOS pSrcPos)
+{
+ /*
+ * Some internal paranoia first.
+ */
+ AssertPtr(pClass);
+ Assert(pClass->u32Magic == RTLOCKVALCLASS_MAGIC);
+ AssertPtr(pThreadSelf);
+ Assert(pThreadSelf->u32Magic == RTTHREADINT_MAGIC);
+ AssertPtr(pRec);
+ AssertPtrNull(pSrcPos);
+
+ /*
+ * Walk the stack, delegate problems to a worker routine.
+ */
+ PRTLOCKVALRECUNION pCur = pThreadSelf->LockValidator.pStackTop;
+ if (!pCur)
+ return VINF_SUCCESS;
+
+ for (;;)
+ {
+ AssertPtrReturn(pCur, VERR_SEM_LV_INTERNAL_ERROR);
+
+ if (pCur->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ pCur = pCur->Nest.pDown;
+ else
+ {
+ uint32_t uPriorSubClass;
+ PRTLOCKVALRECUNION pDown;
+ RTLOCKVALCLASSINT *pPriorClass = rtLockValidatorRecGetClassesAndDown(pCur, &uPriorSubClass, &pDown);
+ if (pPriorClass != NIL_RTLOCKVALCLASS)
+ {
+ AssertPtrReturn(pPriorClass, VERR_SEM_LV_INTERNAL_ERROR);
+ AssertReturn(pPriorClass->u32Magic == RTLOCKVALCLASS_MAGIC, VERR_SEM_LV_INTERNAL_ERROR);
+ if (RT_UNLIKELY(!rtLockValidatorIsClassOrderOk(pClass, uSubClass, pPriorClass, uPriorSubClass)))
+ return rtLockValidatorStackCheckLockingOrder2(pClass, uSubClass, pThreadSelf, pRec, pSrcPos,
+ pPriorClass, pCur, pDown);
+ }
+ pCur = pDown;
+ }
+ if (!pCur)
+ return VINF_SUCCESS;
+ }
+}
+
+
+/**
+ * Check that the lock record is the topmost one on the stack, complain and fail
+ * if it isn't.
+ *
+ * @returns VINF_SUCCESS, VERR_SEM_LV_WRONG_RELEASE_ORDER or
+ * VERR_SEM_LV_INVALID_PARAMETER.
+ * @param pThreadSelf The current thread.
+ * @param pRec The record.
+ */
+static int rtLockValidatorStackCheckReleaseOrder(PRTTHREADINT pThreadSelf, PRTLOCKVALRECUNION pRec)
+{
+ AssertReturn(pThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(pThreadSelf == RTThreadSelf());
+
+ PRTLOCKVALRECUNION pTop = pThreadSelf->LockValidator.pStackTop;
+ if (RT_LIKELY( pTop == pRec
+ || ( pTop
+ && pTop->Core.u32Magic == RTLOCKVALRECNEST_MAGIC
+ && pTop->Nest.pRec == pRec) ))
+ return VINF_SUCCESS;
+
+#ifdef RTLOCKVAL_WITH_RECURSION_RECORDS
+ /* Look for a recursion record so the right frame is dumped and marked. */
+ while (pTop)
+ {
+ if (pTop->Core.u32Magic == RTLOCKVALRECNEST_MAGIC)
+ {
+ if (pTop->Nest.pRec == pRec)
+ {
+ pRec = pTop;
+ break;
+ }
+ pTop = pTop->Nest.pDown;
+ }
+ else if (pTop->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC)
+ pTop = pTop->Excl.pDown;
+ else if (pTop->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC)
+ pTop = pTop->ShrdOwner.pDown;
+ else
+ break;
+ }
+#endif
+
+ rtLockValComplainFirst("Wrong release order!", NULL, pThreadSelf, pRec, true);
+ rtLockValComplainPanic();
+ return !g_fLockValSoftWrongOrder ? VERR_SEM_LV_WRONG_RELEASE_ORDER : VINF_SUCCESS;
+}
+
+
+/**
+ * Checks if all owners are blocked - shared record operated in signaller mode.
+ *
+ * @returns true / false accordingly.
+ * @param pRec The record.
+ * @param pThreadSelf The current thread.
+ */
+DECL_FORCE_INLINE(bool) rtLockValidatorDdAreAllThreadsBlocked(PRTLOCKVALRECSHRD pRec, PRTTHREADINT pThreadSelf)
+{
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pRec->papOwners;
+ uint32_t cAllocated = pRec->cAllocated;
+ uint32_t cEntries = ASMAtomicUoReadU32(&pRec->cEntries);
+ if (cEntries == 0)
+ return false;
+
+ for (uint32_t i = 0; i < cAllocated; i++)
+ {
+ PRTLOCKVALRECSHRDOWN pEntry = rtLockValidatorUoReadSharedOwner(&papOwners[i]);
+ if ( pEntry
+ && pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC)
+ {
+ PRTTHREADINT pCurThread = rtLockValidatorReadThreadHandle(&pEntry->hThread);
+ if (!pCurThread)
+ return false;
+ if (pCurThread->u32Magic != RTTHREADINT_MAGIC)
+ return false;
+ if ( !RTTHREAD_IS_SLEEPING(rtThreadGetState(pCurThread))
+ && pCurThread != pThreadSelf)
+ return false;
+ if (--cEntries == 0)
+ break;
+ }
+ else
+ Assert(!pEntry || pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC_DEAD);
+ }
+
+ return true;
+}
+
+
+/**
+ * Verifies the deadlock stack before calling it a deadlock.
+ *
+ * @retval VERR_SEM_LV_DEADLOCK if it's a deadlock.
+ * @retval VERR_SEM_LV_ILLEGAL_UPGRADE if it's a deadlock on the same lock.
+ * @retval VERR_TRY_AGAIN if something changed.
+ *
+ * @param pStack The deadlock detection stack.
+ * @param pThreadSelf The current thread.
+ */
+static int rtLockValidatorDdVerifyDeadlock(PRTLOCKVALDDSTACK pStack, PRTTHREADINT pThreadSelf)
+{
+ uint32_t const c = pStack->c;
+ for (uint32_t iPass = 0; iPass < 3; iPass++)
+ {
+ for (uint32_t i = 1; i < c; i++)
+ {
+ PRTTHREADINT pThread = pStack->a[i].pThread;
+ if (pThread->u32Magic != RTTHREADINT_MAGIC)
+ return VERR_TRY_AGAIN;
+ if (rtThreadGetState(pThread) != pStack->a[i].enmState)
+ return VERR_TRY_AGAIN;
+ if (rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pRec) != pStack->a[i].pFirstSibling)
+ return VERR_TRY_AGAIN;
+ /* ASSUMES the signaller records won't have siblings! */
+ PRTLOCKVALRECUNION pRec = pStack->a[i].pRec;
+ if ( pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ && pRec->Shared.fSignaller
+ && !rtLockValidatorDdAreAllThreadsBlocked(&pRec->Shared, pThreadSelf))
+ return VERR_TRY_AGAIN;
+ }
+ RTThreadYield();
+ }
+
+ if (c == 1)
+ return VERR_SEM_LV_ILLEGAL_UPGRADE;
+ return VERR_SEM_LV_DEADLOCK;
+}
+
+
+/**
+ * Checks for stack cycles caused by another deadlock before returning.
+ *
+ * @retval VINF_SUCCESS if the stack is simply too small.
+ * @retval VERR_SEM_LV_EXISTING_DEADLOCK if a cycle was detected.
+ *
+ * @param pStack The deadlock detection stack.
+ */
+static int rtLockValidatorDdHandleStackOverflow(PRTLOCKVALDDSTACK pStack)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(pStack->a) - 1; i++)
+ {
+ PRTTHREADINT pThread = pStack->a[i].pThread;
+ for (size_t j = i + 1; j < RT_ELEMENTS(pStack->a); j++)
+ if (pStack->a[j].pThread == pThread)
+ return VERR_SEM_LV_EXISTING_DEADLOCK;
+ }
+ static bool volatile s_fComplained = false;
+ if (!s_fComplained)
+ {
+ s_fComplained = true;
+ rtLockValComplain(RT_SRC_POS, "lock validator stack is too small! (%zu entries)\n", RT_ELEMENTS(pStack->a));
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for rtLockValidatorDeadlockDetection that does the actual deadlock
+ * detection.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_SEM_LV_DEADLOCK
+ * @retval VERR_SEM_LV_EXISTING_DEADLOCK
+ * @retval VERR_SEM_LV_ILLEGAL_UPGRADE
+ * @retval VERR_TRY_AGAIN
+ *
+ * @param pStack The stack to use.
+ * @param pOriginalRec The original record.
+ * @param pThreadSelf The calling thread.
+ */
+static int rtLockValidatorDdDoDetection(PRTLOCKVALDDSTACK pStack, PRTLOCKVALRECUNION const pOriginalRec,
+ PRTTHREADINT const pThreadSelf)
+{
+ pStack->c = 0;
+
+ /* We could use a single RTLOCKVALDDENTRY variable here, but the
+ compiler may make a better job of it when using individual variables. */
+ PRTLOCKVALRECUNION pRec = pOriginalRec;
+ PRTLOCKVALRECUNION pFirstSibling = pOriginalRec;
+ uint32_t iEntry = UINT32_MAX;
+ PRTTHREADINT pThread = NIL_RTTHREAD;
+ RTTHREADSTATE enmState = RTTHREADSTATE_RUNNING;
+ for (uint32_t iLoop = 0; ; iLoop++)
+ {
+ /*
+ * Process the current record.
+ */
+ RTLOCKVAL_ASSERT_PTR_ALIGN(pRec);
+
+ /* Find the next relevant owner thread and record. */
+ PRTLOCKVALRECUNION pNextRec = NULL;
+ RTTHREADSTATE enmNextState = RTTHREADSTATE_RUNNING;
+ PRTTHREADINT pNextThread = NIL_RTTHREAD;
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ Assert(iEntry == UINT32_MAX);
+ for (;;)
+ {
+ pNextThread = rtLockValidatorReadThreadHandle(&pRec->Excl.hThread);
+ if ( !pNextThread
+ || pNextThread->u32Magic != RTTHREADINT_MAGIC)
+ break;
+ enmNextState = rtThreadGetState(pNextThread);
+ if ( !RTTHREAD_IS_SLEEPING(enmNextState)
+ && pNextThread != pThreadSelf)
+ break;
+ pNextRec = rtLockValidatorReadRecUnionPtr(&pNextThread->LockValidator.pRec);
+ if (RT_LIKELY( !pNextRec
+ || enmNextState == rtThreadGetState(pNextThread)))
+ break;
+ pNextRec = NULL;
+ }
+ if (!pNextRec)
+ {
+ pRec = pRec->Excl.pSibling;
+ if ( pRec
+ && pRec != pFirstSibling)
+ continue;
+ pNextThread = NIL_RTTHREAD;
+ }
+ break;
+
+ case RTLOCKVALRECSHRD_MAGIC:
+ if (!pRec->Shared.fSignaller)
+ {
+ /* Skip to the next sibling if same side. ASSUMES reader priority. */
+ /** @todo The read side of a read-write lock is problematic if
+ * the implementation prioritizes writers over readers because
+ * that means we should could deadlock against current readers
+ * if a writer showed up. If the RW sem implementation is
+ * wrapping some native API, it's not so easy to detect when we
+ * should do this and when we shouldn't. Checking when we
+ * shouldn't is subject to wakeup scheduling and cannot easily
+ * be made reliable.
+ *
+ * At the moment we circumvent all this mess by declaring that
+ * readers has priority. This is TRUE on linux, but probably
+ * isn't on Solaris and FreeBSD. */
+ if ( pRec == pFirstSibling
+ && pRec->Shared.pSibling != NULL
+ && pRec->Shared.pSibling != pFirstSibling)
+ {
+ pRec = pRec->Shared.pSibling;
+ Assert(iEntry == UINT32_MAX);
+ continue;
+ }
+ }
+
+ /* Scan the owner table for blocked owners. */
+ if ( ASMAtomicUoReadU32(&pRec->Shared.cEntries) > 0
+ && ( !pRec->Shared.fSignaller
+ || iEntry != UINT32_MAX
+ || rtLockValidatorDdAreAllThreadsBlocked(&pRec->Shared, pThreadSelf)
+ )
+ )
+ {
+ uint32_t cAllocated = pRec->Shared.cAllocated;
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pRec->Shared.papOwners;
+ while (++iEntry < cAllocated)
+ {
+ PRTLOCKVALRECSHRDOWN pEntry = rtLockValidatorUoReadSharedOwner(&papOwners[iEntry]);
+ if (pEntry)
+ {
+ for (;;)
+ {
+ if (pEntry->Core.u32Magic != RTLOCKVALRECSHRDOWN_MAGIC)
+ break;
+ pNextThread = rtLockValidatorReadThreadHandle(&pEntry->hThread);
+ if ( !pNextThread
+ || pNextThread->u32Magic != RTTHREADINT_MAGIC)
+ break;
+ enmNextState = rtThreadGetState(pNextThread);
+ if ( !RTTHREAD_IS_SLEEPING(enmNextState)
+ && pNextThread != pThreadSelf)
+ break;
+ pNextRec = rtLockValidatorReadRecUnionPtr(&pNextThread->LockValidator.pRec);
+ if (RT_LIKELY( !pNextRec
+ || enmNextState == rtThreadGetState(pNextThread)))
+ break;
+ pNextRec = NULL;
+ }
+ if (pNextRec)
+ break;
+ }
+ else
+ Assert(!pEntry || pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC_DEAD);
+ }
+ if (pNextRec)
+ break;
+ pNextThread = NIL_RTTHREAD;
+ }
+
+ /* Advance to the next sibling, if any. */
+ pRec = pRec->Shared.pSibling;
+ if ( pRec != NULL
+ && pRec != pFirstSibling)
+ {
+ iEntry = UINT32_MAX;
+ continue;
+ }
+ break;
+
+ case RTLOCKVALRECEXCL_MAGIC_DEAD:
+ case RTLOCKVALRECSHRD_MAGIC_DEAD:
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ case RTLOCKVALRECSHRDOWN_MAGIC_DEAD:
+ default:
+ AssertMsgFailed(("%p: %#x\n", pRec, pRec->Core.u32Magic));
+ break;
+ }
+
+ if (pNextRec)
+ {
+ /*
+ * Recurse and check for deadlock.
+ */
+ uint32_t i = pStack->c;
+ if (RT_UNLIKELY(i >= RT_ELEMENTS(pStack->a)))
+ return rtLockValidatorDdHandleStackOverflow(pStack);
+
+ pStack->c++;
+ pStack->a[i].pRec = pRec;
+ pStack->a[i].iEntry = iEntry;
+ pStack->a[i].enmState = enmState;
+ pStack->a[i].pThread = pThread;
+ pStack->a[i].pFirstSibling = pFirstSibling;
+
+ if (RT_UNLIKELY( pNextThread == pThreadSelf
+ && ( i != 0
+ || pRec->Core.u32Magic != RTLOCKVALRECSHRD_MAGIC
+ || !pRec->Shared.fSignaller) /* ASSUMES signaller records have no siblings. */
+ )
+ )
+ return rtLockValidatorDdVerifyDeadlock(pStack, pThreadSelf);
+
+ pRec = pNextRec;
+ pFirstSibling = pNextRec;
+ iEntry = UINT32_MAX;
+ enmState = enmNextState;
+ pThread = pNextThread;
+ }
+ else
+ {
+ /*
+ * No deadlock here, unwind the stack and deal with any unfinished
+ * business there.
+ */
+ uint32_t i = pStack->c;
+ for (;;)
+ {
+ /* pop */
+ if (i == 0)
+ return VINF_SUCCESS;
+ i--;
+ pRec = pStack->a[i].pRec;
+ iEntry = pStack->a[i].iEntry;
+
+ /* Examine it. */
+ uint32_t u32Magic = pRec->Core.u32Magic;
+ if (u32Magic == RTLOCKVALRECEXCL_MAGIC)
+ pRec = pRec->Excl.pSibling;
+ else if (u32Magic == RTLOCKVALRECSHRD_MAGIC)
+ {
+ if (iEntry + 1 < pRec->Shared.cAllocated)
+ break; /* continue processing this record. */
+ pRec = pRec->Shared.pSibling;
+ }
+ else
+ {
+ Assert( u32Magic == RTLOCKVALRECEXCL_MAGIC_DEAD
+ || u32Magic == RTLOCKVALRECSHRD_MAGIC_DEAD);
+ continue;
+ }
+
+ /* Any next record to advance to? */
+ if ( !pRec
+ || pRec == pStack->a[i].pFirstSibling)
+ continue;
+ iEntry = UINT32_MAX;
+ break;
+ }
+
+ /* Restore the rest of the state and update the stack. */
+ pFirstSibling = pStack->a[i].pFirstSibling;
+ enmState = pStack->a[i].enmState;
+ pThread = pStack->a[i].pThread;
+ pStack->c = i;
+ }
+
+ Assert(iLoop != 1000000);
+ }
+}
+
+
+/**
+ * Check for the simple no-deadlock case.
+ *
+ * @returns true if no deadlock, false if further investigation is required.
+ *
+ * @param pOriginalRec The original record.
+ */
+DECLINLINE(int) rtLockValidatorIsSimpleNoDeadlockCase(PRTLOCKVALRECUNION pOriginalRec)
+{
+ if ( pOriginalRec->Excl.Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ && !pOriginalRec->Excl.pSibling)
+ {
+ PRTTHREADINT pThread = rtLockValidatorReadThreadHandle(&pOriginalRec->Excl.hThread);
+ if ( !pThread
+ || pThread->u32Magic != RTTHREADINT_MAGIC)
+ return true;
+ RTTHREADSTATE enmState = rtThreadGetState(pThread);
+ if (!RTTHREAD_IS_SLEEPING(enmState))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Worker for rtLockValidatorDeadlockDetection that bitches about a deadlock.
+ *
+ * @param pStack The chain of locks causing the deadlock.
+ * @param pRec The record relating to the current thread's lock
+ * operation.
+ * @param pThreadSelf This thread.
+ * @param pSrcPos Where we are going to deadlock.
+ * @param rc The return code.
+ */
+static void rcLockValidatorDoDeadlockComplaining(PRTLOCKVALDDSTACK pStack, PRTLOCKVALRECUNION pRec,
+ PRTTHREADINT pThreadSelf, PCRTLOCKVALSRCPOS pSrcPos, int rc)
+{
+ if (!ASMAtomicUoReadBool(&g_fLockValidatorQuiet))
+ {
+ const char *pszWhat;
+ switch (rc)
+ {
+ case VERR_SEM_LV_DEADLOCK: pszWhat = "Detected deadlock!"; break;
+ case VERR_SEM_LV_EXISTING_DEADLOCK: pszWhat = "Found existing deadlock!"; break;
+ case VERR_SEM_LV_ILLEGAL_UPGRADE: pszWhat = "Illegal lock upgrade!"; break;
+ default: AssertFailed(); pszWhat = "!unexpected rc!"; break;
+ }
+ rtLockValComplainFirst(pszWhat, pSrcPos, pThreadSelf, pStack->a[0].pRec != pRec ? pRec : NULL, true);
+ rtLockValComplainMore("---- start of deadlock chain - %u entries ----\n", pStack->c);
+ for (uint32_t i = 0; i < pStack->c; i++)
+ {
+ char szPrefix[24];
+ RTStrPrintf(szPrefix, sizeof(szPrefix), "#%02u: ", i);
+ PRTLOCKVALRECUNION pShrdOwner = NULL;
+ if (pStack->a[i].pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)
+ pShrdOwner = (PRTLOCKVALRECUNION)pStack->a[i].pRec->Shared.papOwners[pStack->a[i].iEntry];
+ if (RT_VALID_PTR(pShrdOwner) && pShrdOwner->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC)
+ {
+ rtLockValComplainAboutLock(szPrefix, pShrdOwner, "\n");
+ rtLockValComplainAboutLockStack(pShrdOwner->ShrdOwner.hThread, 5, 2, pShrdOwner);
+ }
+ else
+ {
+ rtLockValComplainAboutLock(szPrefix, pStack->a[i].pRec, "\n");
+ if (pStack->a[i].pRec->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC)
+ rtLockValComplainAboutLockStack(pStack->a[i].pRec->Excl.hThread, 5, 2, pStack->a[i].pRec);
+ }
+ }
+ rtLockValComplainMore("---- end of deadlock chain ----\n");
+ }
+
+ rtLockValComplainPanic();
+}
+
+
+/**
+ * Perform deadlock detection.
+ *
+ * @retval VINF_SUCCESS
+ * @retval VERR_SEM_LV_DEADLOCK
+ * @retval VERR_SEM_LV_EXISTING_DEADLOCK
+ * @retval VERR_SEM_LV_ILLEGAL_UPGRADE
+ *
+ * @param pRec The record relating to the current thread's lock
+ * operation.
+ * @param pThreadSelf The current thread.
+ * @param pSrcPos The position of the current lock operation.
+ */
+static int rtLockValidatorDeadlockDetection(PRTLOCKVALRECUNION pRec, PRTTHREADINT pThreadSelf, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ RTLOCKVALDDSTACK Stack;
+ rtLockValidatorSerializeDetectionEnter();
+ int rc = rtLockValidatorDdDoDetection(&Stack, pRec, pThreadSelf);
+ rtLockValidatorSerializeDetectionLeave();
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ if (rc == VERR_TRY_AGAIN)
+ {
+ for (uint32_t iLoop = 0; ; iLoop++)
+ {
+ rtLockValidatorSerializeDetectionEnter();
+ rc = rtLockValidatorDdDoDetection(&Stack, pRec, pThreadSelf);
+ rtLockValidatorSerializeDetectionLeave();
+ if (RT_SUCCESS_NP(rc))
+ return VINF_SUCCESS;
+ if (rc != VERR_TRY_AGAIN)
+ break;
+ RTThreadYield();
+ if (iLoop >= 3)
+ return VINF_SUCCESS;
+ }
+ }
+
+ rcLockValidatorDoDeadlockComplaining(&Stack, pRec, pThreadSelf, pSrcPos, rc);
+ return rc;
+}
+
+
+RTDECL(void) RTLockValidatorRecExclInitV(PRTLOCKVALRECEXCL pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass,
+ void *hLock, bool fEnabled, const char *pszNameFmt, va_list va)
+{
+ RTLOCKVAL_ASSERT_PTR_ALIGN(pRec);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(hLock);
+ Assert( uSubClass >= RTLOCKVAL_SUB_CLASS_USER
+ || uSubClass == RTLOCKVAL_SUB_CLASS_NONE
+ || uSubClass == RTLOCKVAL_SUB_CLASS_ANY);
+
+ pRec->Core.u32Magic = RTLOCKVALRECEXCL_MAGIC;
+ pRec->fEnabled = fEnabled && RTLockValidatorIsEnabled();
+ pRec->afReserved[0] = 0;
+ pRec->afReserved[1] = 0;
+ pRec->afReserved[2] = 0;
+ rtLockValidatorSrcPosInit(&pRec->SrcPos);
+ pRec->hThread = NIL_RTTHREAD;
+ pRec->pDown = NULL;
+ pRec->hClass = rtLockValidatorClassValidateAndRetain(hClass);
+ pRec->uSubClass = uSubClass;
+ pRec->cRecursion = 0;
+ pRec->hLock = hLock;
+ pRec->pSibling = NULL;
+ if (pszNameFmt)
+ RTStrPrintfV(pRec->szName, sizeof(pRec->szName), pszNameFmt, va);
+ else
+ {
+ static uint32_t volatile s_cAnonymous = 0;
+ uint32_t i = ASMAtomicIncU32(&s_cAnonymous) - 1;
+ RTStrPrintf(pRec->szName, sizeof(pRec->szName), "anon-excl-%u", i);
+ }
+
+ /* Lazy initialization. */
+ if (RT_UNLIKELY(g_hLockValidatorXRoads == NIL_RTSEMXROADS))
+ rtLockValidatorLazyInit();
+}
+
+
+RTDECL(void) RTLockValidatorRecExclInit(PRTLOCKVALRECEXCL pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass,
+ void *hLock, bool fEnabled, const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ RTLockValidatorRecExclInitV(pRec, hClass, uSubClass, hLock, fEnabled, pszNameFmt, va);
+ va_end(va);
+}
+
+
+RTDECL(int) RTLockValidatorRecExclCreateV(PRTLOCKVALRECEXCL *ppRec, RTLOCKVALCLASS hClass,
+ uint32_t uSubClass, void *pvLock, bool fEnabled,
+ const char *pszNameFmt, va_list va)
+{
+ PRTLOCKVALRECEXCL pRec;
+ *ppRec = pRec = (PRTLOCKVALRECEXCL)RTMemAlloc(sizeof(*pRec));
+ if (!pRec)
+ return VERR_NO_MEMORY;
+ RTLockValidatorRecExclInitV(pRec, hClass, uSubClass, pvLock, fEnabled, pszNameFmt, va);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclCreate(PRTLOCKVALRECEXCL *ppRec, RTLOCKVALCLASS hClass,
+ uint32_t uSubClass, void *pvLock, bool fEnabled,
+ const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorRecExclCreateV(ppRec, hClass, uSubClass, pvLock, fEnabled, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(void) RTLockValidatorRecExclDelete(PRTLOCKVALRECEXCL pRec)
+{
+ Assert(pRec->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC);
+
+ rtLockValidatorSerializeDestructEnter();
+
+ /** @todo Check that it's not on our stack first. Need to make it
+ * configurable whether deleting a owned lock is acceptable? */
+
+ ASMAtomicWriteU32(&pRec->Core.u32Magic, RTLOCKVALRECEXCL_MAGIC_DEAD);
+ ASMAtomicWriteHandle(&pRec->hThread, NIL_RTTHREAD);
+ RTLOCKVALCLASS hClass;
+ ASMAtomicXchgHandle(&pRec->hClass, NIL_RTLOCKVALCLASS, &hClass);
+ if (pRec->pSibling)
+ rtLockValidatorUnlinkAllSiblings(&pRec->Core);
+ rtLockValidatorSerializeDestructLeave();
+ if (hClass != NIL_RTLOCKVALCLASS)
+ RTLockValidatorClassRelease(hClass);
+}
+
+
+RTDECL(void) RTLockValidatorRecExclDestroy(PRTLOCKVALRECEXCL *ppRec)
+{
+ PRTLOCKVALRECEXCL pRec = *ppRec;
+ *ppRec = NULL;
+ if (pRec)
+ {
+ RTLockValidatorRecExclDelete(pRec);
+ RTMemFree(pRec);
+ }
+}
+
+
+RTDECL(uint32_t) RTLockValidatorRecExclSetSubClass(PRTLOCKVALRECEXCL pRec, uint32_t uSubClass)
+{
+ AssertPtrReturn(pRec, RTLOCKVAL_SUB_CLASS_INVALID);
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID);
+ AssertReturn( uSubClass >= RTLOCKVAL_SUB_CLASS_USER
+ || uSubClass == RTLOCKVAL_SUB_CLASS_NONE
+ || uSubClass == RTLOCKVAL_SUB_CLASS_ANY,
+ RTLOCKVAL_SUB_CLASS_INVALID);
+ return ASMAtomicXchgU32(&pRec->uSubClass, uSubClass);
+}
+
+
+RTDECL(void) RTLockValidatorRecExclSetOwner(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fFirstRecursion)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return;
+ AssertReturnVoid(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC);
+ if (!pRecU->Excl.fEnabled)
+ return;
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturnVoid(hThreadSelf != NIL_RTTHREAD);
+ }
+ AssertReturnVoid(hThreadSelf->u32Magic == RTTHREADINT_MAGIC);
+ Assert(hThreadSelf == RTThreadSelf());
+
+ ASMAtomicIncS32(&hThreadSelf->LockValidator.cWriteLocks);
+
+ if (pRecU->Excl.hThread == hThreadSelf)
+ {
+ Assert(!fFirstRecursion); RT_NOREF_PV(fFirstRecursion);
+ pRecU->Excl.cRecursion++;
+ rtLockValidatorStackPushRecursion(hThreadSelf, pRecU, pSrcPos);
+ }
+ else
+ {
+ Assert(pRecU->Excl.hThread == NIL_RTTHREAD);
+
+ rtLockValidatorSrcPosCopy(&pRecU->Excl.SrcPos, pSrcPos);
+ ASMAtomicUoWriteU32(&pRecU->Excl.cRecursion, 1);
+ ASMAtomicWriteHandle(&pRecU->Excl.hThread, hThreadSelf);
+
+ rtLockValidatorStackPush(hThreadSelf, pRecU);
+ }
+}
+
+
+/**
+ * Internal worker for RTLockValidatorRecExclReleaseOwner and
+ * RTLockValidatorRecExclReleaseOwnerUnchecked.
+ */
+static void rtLockValidatorRecExclReleaseOwnerUnchecked(PRTLOCKVALRECUNION pRec, bool fFinalRecursion)
+{
+ RTTHREADINT *pThread = pRec->Excl.hThread;
+ AssertReturnVoid(pThread != NIL_RTTHREAD);
+ Assert(pThread == RTThreadSelf());
+
+ ASMAtomicDecS32(&pThread->LockValidator.cWriteLocks);
+ uint32_t c = ASMAtomicDecU32(&pRec->Excl.cRecursion);
+ if (c == 0)
+ {
+ rtLockValidatorStackPop(pThread, pRec);
+ ASMAtomicWriteHandle(&pRec->Excl.hThread, NIL_RTTHREAD);
+ }
+ else
+ {
+ Assert(c < UINT32_C(0xffff0000));
+ Assert(!fFinalRecursion); RT_NOREF_PV(fFinalRecursion);
+ rtLockValidatorStackPopRecursion(pThread, pRec);
+ }
+}
+
+RTDECL(int) RTLockValidatorRecExclReleaseOwner(PRTLOCKVALRECEXCL pRec, bool fFinalRecursion)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+
+ /*
+ * Check the release order.
+ */
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && pRecU->Excl.hClass->fStrictReleaseOrder
+ && pRecU->Excl.hClass->cMsMinOrder != RT_INDEFINITE_WAIT
+ )
+ {
+ int rc = rtLockValidatorStackCheckReleaseOrder(pRecU->Excl.hThread, pRecU);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Join paths with RTLockValidatorRecExclReleaseOwnerUnchecked.
+ */
+ rtLockValidatorRecExclReleaseOwnerUnchecked(pRecU, fFinalRecursion);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void) RTLockValidatorRecExclReleaseOwnerUnchecked(PRTLOCKVALRECEXCL pRec)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturnVoid(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC);
+ if (pRecU->Excl.fEnabled)
+ rtLockValidatorRecExclReleaseOwnerUnchecked(pRecU, false);
+}
+
+
+RTDECL(int) RTLockValidatorRecExclRecursion(PRTLOCKVALRECEXCL pRec, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Excl.cRecursion > 0, VERR_SEM_LV_INVALID_PARAMETER);
+
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && !pRecU->Excl.hClass->fRecursionOk)
+ {
+ rtLockValComplainFirst("Recursion not allowed by the class!",
+ pSrcPos, pRecU->Excl.hThread, (PRTLOCKVALRECUNION)pRec, true);
+ rtLockValComplainPanic();
+ return VERR_SEM_LV_NESTED;
+ }
+
+ Assert(pRecU->Excl.cRecursion < _1M);
+ pRecU->Excl.cRecursion++;
+ rtLockValidatorStackPushRecursion(pRecU->Excl.hThread, pRecU, pSrcPos);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclUnwind(PRTLOCKVALRECEXCL pRec)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(pRecU->Excl.hThread == RTThreadSelf());
+ AssertReturn(pRecU->Excl.cRecursion > 1, VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Check the release order.
+ */
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && pRecU->Excl.hClass->fStrictReleaseOrder
+ && pRecU->Excl.hClass->cMsMinOrder != RT_INDEFINITE_WAIT
+ )
+ {
+ int rc = rtLockValidatorStackCheckReleaseOrder(pRecU->Excl.hThread, pRecU);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Perform the unwind.
+ */
+ pRecU->Excl.cRecursion--;
+ rtLockValidatorStackPopRecursion(pRecU->Excl.hThread, pRecU);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclRecursionMixed(PRTLOCKVALRECEXCL pRec, PRTLOCKVALRECCORE pRecMixed, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ PRTLOCKVALRECUNION pRecMixedU = (PRTLOCKVALRECUNION)pRecMixed;
+ AssertReturn( pRecMixedU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ || pRecMixedU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ , VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+ Assert(pRecU->Excl.hThread == RTThreadSelf());
+ AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Excl.cRecursion > 0, VERR_SEM_LV_INVALID_PARAMETER);
+
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && !pRecU->Excl.hClass->fRecursionOk)
+ {
+ rtLockValComplainFirst("Mixed recursion not allowed by the class!",
+ pSrcPos, pRecU->Excl.hThread, (PRTLOCKVALRECUNION)pRec, true);
+ rtLockValComplainPanic();
+ return VERR_SEM_LV_NESTED;
+ }
+
+ Assert(pRecU->Excl.cRecursion < _1M);
+ pRecU->Excl.cRecursion++;
+ rtLockValidatorStackPushRecursion(pRecU->Excl.hThread, pRecU, pSrcPos);
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclUnwindMixed(PRTLOCKVALRECEXCL pRec, PRTLOCKVALRECCORE pRecMixed)
+{
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ PRTLOCKVALRECUNION pRecMixedU = (PRTLOCKVALRECUNION)pRecMixed;
+ AssertReturn( pRecMixedU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ || pRecMixedU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC
+ , VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Excl.fEnabled)
+ return VINF_SUCCESS;
+ Assert(pRecU->Excl.hThread == RTThreadSelf());
+ AssertReturn(pRecU->Excl.hThread != NIL_RTTHREAD, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Excl.cRecursion > 1, VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Check the release order.
+ */
+ if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && pRecU->Excl.hClass->fStrictReleaseOrder
+ && pRecU->Excl.hClass->cMsMinOrder != RT_INDEFINITE_WAIT
+ )
+ {
+ int rc = rtLockValidatorStackCheckReleaseOrder(pRecU->Excl.hThread, pRecU);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Perform the unwind.
+ */
+ pRecU->Excl.cRecursion--;
+ rtLockValidatorStackPopRecursion(pRecU->Excl.hThread, pRecU);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecExclCheckOrder(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, RTMSINTERVAL cMillies)
+{
+ /*
+ * Validate and adjust input. Quit early if order validation is disabled.
+ */
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return VINF_SUCCESS;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if ( !pRecU->Excl.fEnabled
+ || pRecU->Excl.hClass == NIL_RTLOCKVALCLASS
+ || pRecU->Excl.hClass->cMsMinOrder == RT_INDEFINITE_WAIT
+ || pRecU->Excl.hClass->cMsMinOrder > cMillies)
+ return VINF_SUCCESS;
+
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR);
+ }
+ AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(hThreadSelf == RTThreadSelf());
+
+ /*
+ * Detect recursion as it isn't subject to order restrictions.
+ */
+ if (pRec->hThread == hThreadSelf)
+ return VINF_SUCCESS;
+
+ return rtLockValidatorStackCheckLockingOrder(pRecU->Excl.hClass, pRecU->Excl.uSubClass, hThreadSelf, pRecU, pSrcPos);
+}
+
+
+RTDECL(int) RTLockValidatorRecExclCheckBlocking(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies,
+ RTTHREADSTATE enmSleepState, bool fReallySleeping)
+{
+ /*
+ * Fend off wild life.
+ */
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ if (!pRecU)
+ return VINF_SUCCESS;
+ AssertPtrReturn(pRecU, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECEXCL_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRec->fEnabled)
+ return VINF_SUCCESS;
+
+ PRTTHREADINT pThreadSelf = hThreadSelf;
+ AssertPtrReturn(pThreadSelf, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(pThreadSelf == RTThreadSelf());
+
+ AssertReturn(RTTHREAD_IS_SLEEPING(enmSleepState), VERR_SEM_LV_INVALID_PARAMETER);
+
+ RTTHREADSTATE enmThreadState = rtThreadGetState(pThreadSelf);
+ if (RT_UNLIKELY(enmThreadState != RTTHREADSTATE_RUNNING))
+ {
+ AssertReturn( enmThreadState == RTTHREADSTATE_TERMINATED /* rtThreadRemove uses locks too */
+ || enmThreadState == RTTHREADSTATE_INITIALIZING /* rtThreadInsert uses locks too */
+ , VERR_SEM_LV_INVALID_PARAMETER);
+ enmSleepState = enmThreadState;
+ }
+
+ /*
+ * Record the location.
+ */
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, pRecU);
+ rtLockValidatorSrcPosCopy(&pThreadSelf->LockValidator.SrcPos, pSrcPos);
+ ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, true);
+ pThreadSelf->LockValidator.enmRecState = enmSleepState;
+ rtThreadSetState(pThreadSelf, enmSleepState);
+
+ /*
+ * Don't do deadlock detection if we're recursing.
+ *
+ * On some hosts we don't do recursion accounting our selves and there
+ * isn't any other place to check for this.
+ */
+ int rc = VINF_SUCCESS;
+ if (rtLockValidatorReadThreadHandle(&pRecU->Excl.hThread) == pThreadSelf)
+ {
+ if ( !fRecursiveOk
+ || ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && !pRecU->Excl.hClass->fRecursionOk))
+ {
+ rtLockValComplainFirst("Recursion not allowed!", pSrcPos, pThreadSelf, pRecU, true);
+ rtLockValComplainPanic();
+ rc = VERR_SEM_LV_NESTED;
+ }
+ }
+ /*
+ * Perform deadlock detection.
+ */
+ else if ( pRecU->Excl.hClass != NIL_RTLOCKVALCLASS
+ && ( pRecU->Excl.hClass->cMsMinDeadlock > cMillies
+ || pRecU->Excl.hClass->cMsMinDeadlock > RT_INDEFINITE_WAIT))
+ rc = VINF_SUCCESS;
+ else if (!rtLockValidatorIsSimpleNoDeadlockCase(pRecU))
+ rc = rtLockValidatorDeadlockDetection(pRecU, pThreadSelf, pSrcPos);
+
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteBool(&pThreadSelf->fReallySleeping, fReallySleeping);
+ else
+ {
+ rtThreadSetState(pThreadSelf, enmThreadState);
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, NULL);
+ }
+ ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, false);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecExclCheckBlocking);
+
+
+RTDECL(int) RTLockValidatorRecExclCheckOrderAndBlocking(PRTLOCKVALRECEXCL pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies,
+ RTTHREADSTATE enmSleepState, bool fReallySleeping)
+{
+ int rc = RTLockValidatorRecExclCheckOrder(pRec, hThreadSelf, pSrcPos, cMillies);
+ if (RT_SUCCESS(rc))
+ rc = RTLockValidatorRecExclCheckBlocking(pRec, hThreadSelf, pSrcPos, fRecursiveOk, cMillies,
+ enmSleepState, fReallySleeping);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecExclCheckOrderAndBlocking);
+
+
+RTDECL(void) RTLockValidatorRecSharedInitV(PRTLOCKVALRECSHRD pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass,
+ void *hLock, bool fSignaller, bool fEnabled, const char *pszNameFmt, va_list va)
+{
+ RTLOCKVAL_ASSERT_PTR_ALIGN(pRec);
+ RTLOCKVAL_ASSERT_PTR_ALIGN(hLock);
+ Assert( uSubClass >= RTLOCKVAL_SUB_CLASS_USER
+ || uSubClass == RTLOCKVAL_SUB_CLASS_NONE
+ || uSubClass == RTLOCKVAL_SUB_CLASS_ANY);
+
+ pRec->Core.u32Magic = RTLOCKVALRECSHRD_MAGIC;
+ pRec->uSubClass = uSubClass;
+ pRec->hClass = rtLockValidatorClassValidateAndRetain(hClass);
+ pRec->hLock = hLock;
+ pRec->fEnabled = fEnabled && RTLockValidatorIsEnabled();
+ pRec->fSignaller = fSignaller;
+ pRec->pSibling = NULL;
+
+ /* the table */
+ pRec->cEntries = 0;
+ pRec->iLastEntry = 0;
+ pRec->cAllocated = 0;
+ pRec->fReallocating = false;
+ pRec->fPadding = false;
+ pRec->papOwners = NULL;
+
+ /* the name */
+ if (pszNameFmt)
+ RTStrPrintfV(pRec->szName, sizeof(pRec->szName), pszNameFmt, va);
+ else
+ {
+ static uint32_t volatile s_cAnonymous = 0;
+ uint32_t i = ASMAtomicIncU32(&s_cAnonymous) - 1;
+ RTStrPrintf(pRec->szName, sizeof(pRec->szName), "anon-shrd-%u", i);
+ }
+}
+
+
+RTDECL(void) RTLockValidatorRecSharedInit(PRTLOCKVALRECSHRD pRec, RTLOCKVALCLASS hClass, uint32_t uSubClass,
+ void *hLock, bool fSignaller, bool fEnabled, const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ RTLockValidatorRecSharedInitV(pRec, hClass, uSubClass, hLock, fSignaller, fEnabled, pszNameFmt, va);
+ va_end(va);
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCreateV(PRTLOCKVALRECSHRD *ppRec, RTLOCKVALCLASS hClass,
+ uint32_t uSubClass, void *pvLock, bool fSignaller, bool fEnabled,
+ const char *pszNameFmt, va_list va)
+{
+ PRTLOCKVALRECSHRD pRec;
+ *ppRec = pRec = (PRTLOCKVALRECSHRD)RTMemAlloc(sizeof(*pRec));
+ if (!pRec)
+ return VERR_NO_MEMORY;
+ RTLockValidatorRecSharedInitV(pRec, hClass, uSubClass, pvLock, fSignaller, fEnabled, pszNameFmt, va);
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCreate(PRTLOCKVALRECSHRD *ppRec, RTLOCKVALCLASS hClass,
+ uint32_t uSubClass, void *pvLock, bool fSignaller, bool fEnabled,
+ const char *pszNameFmt, ...)
+{
+ va_list va;
+ va_start(va, pszNameFmt);
+ int rc = RTLockValidatorRecSharedCreateV(ppRec, hClass, uSubClass, pvLock, fSignaller, fEnabled, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+RTDECL(void) RTLockValidatorRecSharedDelete(PRTLOCKVALRECSHRD pRec)
+{
+ Assert(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC);
+
+ /** @todo Check that it's not on our stack first. Need to make it
+ * configurable whether deleting a owned lock is acceptable? */
+
+ /*
+ * Flip it into table realloc mode and take the destruction lock.
+ */
+ rtLockValidatorSerializeDestructEnter();
+ while (!ASMAtomicCmpXchgBool(&pRec->fReallocating, true, false))
+ {
+ rtLockValidatorSerializeDestructLeave();
+
+ rtLockValidatorSerializeDetectionEnter();
+ rtLockValidatorSerializeDetectionLeave();
+
+ rtLockValidatorSerializeDestructEnter();
+ }
+
+ ASMAtomicWriteU32(&pRec->Core.u32Magic, RTLOCKVALRECSHRD_MAGIC_DEAD);
+ RTLOCKVALCLASS hClass;
+ ASMAtomicXchgHandle(&pRec->hClass, NIL_RTLOCKVALCLASS, &hClass);
+ if (pRec->papOwners)
+ {
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pRec->papOwners;
+ ASMAtomicUoWriteNullPtr(&pRec->papOwners);
+ ASMAtomicUoWriteU32(&pRec->cAllocated, 0);
+
+ RTMemFree((void *)papOwners);
+ }
+ if (pRec->pSibling)
+ rtLockValidatorUnlinkAllSiblings(&pRec->Core);
+ ASMAtomicWriteBool(&pRec->fReallocating, false);
+
+ rtLockValidatorSerializeDestructLeave();
+
+ if (hClass != NIL_RTLOCKVALCLASS)
+ RTLockValidatorClassRelease(hClass);
+}
+
+
+RTDECL(void) RTLockValidatorRecSharedDestroy(PRTLOCKVALRECSHRD *ppRec)
+{
+ PRTLOCKVALRECSHRD pRec = *ppRec;
+ *ppRec = NULL;
+ if (pRec)
+ {
+ RTLockValidatorRecSharedDelete(pRec);
+ RTMemFree(pRec);
+ }
+}
+
+
+RTDECL(uint32_t) RTLockValidatorRecSharedSetSubClass(PRTLOCKVALRECSHRD pRec, uint32_t uSubClass)
+{
+ AssertPtrReturn(pRec, RTLOCKVAL_SUB_CLASS_INVALID);
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, RTLOCKVAL_SUB_CLASS_INVALID);
+ AssertReturn( uSubClass >= RTLOCKVAL_SUB_CLASS_USER
+ || uSubClass == RTLOCKVAL_SUB_CLASS_NONE
+ || uSubClass == RTLOCKVAL_SUB_CLASS_ANY,
+ RTLOCKVAL_SUB_CLASS_INVALID);
+ return ASMAtomicXchgU32(&pRec->uSubClass, uSubClass);
+}
+
+
+/**
+ * Locates an owner (thread) in a shared lock record.
+ *
+ * @returns Pointer to the owner entry on success, NULL on failure..
+ * @param pShared The shared lock record.
+ * @param hThread The thread (owner) to find.
+ * @param piEntry Where to optionally return the table in index.
+ * Optional.
+ */
+DECLINLINE(PRTLOCKVALRECUNION)
+rtLockValidatorRecSharedFindOwner(PRTLOCKVALRECSHRD pShared, RTTHREAD hThread, uint32_t *piEntry)
+{
+ rtLockValidatorSerializeDetectionEnter();
+
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pShared->papOwners;
+ if (papOwners)
+ {
+ uint32_t const cMax = pShared->cAllocated;
+ for (uint32_t iEntry = 0; iEntry < cMax; iEntry++)
+ {
+ PRTLOCKVALRECUNION pEntry = (PRTLOCKVALRECUNION)rtLockValidatorUoReadSharedOwner(&papOwners[iEntry]);
+ if (pEntry && pEntry->ShrdOwner.hThread == hThread)
+ {
+ rtLockValidatorSerializeDetectionLeave();
+ if (piEntry)
+ *piEntry = iEntry;
+ return pEntry;
+ }
+ }
+ }
+
+ rtLockValidatorSerializeDetectionLeave();
+ return NULL;
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckOrder(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, RTMSINTERVAL cMillies)
+{
+ /*
+ * Validate and adjust input. Quit early if order validation is disabled.
+ */
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if ( !pRecU->Shared.fEnabled
+ || pRecU->Shared.hClass == NIL_RTLOCKVALCLASS
+ || pRecU->Shared.hClass->cMsMinOrder == RT_INDEFINITE_WAIT
+ || pRecU->Shared.hClass->cMsMinOrder > cMillies
+ )
+ return VINF_SUCCESS;
+
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR);
+ }
+ AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(hThreadSelf == RTThreadSelf());
+
+ /*
+ * Detect recursion as it isn't subject to order restrictions.
+ */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(&pRecU->Shared, hThreadSelf, NULL);
+ if (pEntry)
+ return VINF_SUCCESS;
+
+ return rtLockValidatorStackCheckLockingOrder(pRecU->Shared.hClass, pRecU->Shared.uSubClass, hThreadSelf, pRecU, pSrcPos);
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckBlocking(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies,
+ RTTHREADSTATE enmSleepState, bool fReallySleeping)
+{
+ /*
+ * Fend off wild life.
+ */
+ PRTLOCKVALRECUNION pRecU = (PRTLOCKVALRECUNION)pRec;
+ AssertPtrReturn(pRecU, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pRecU->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRecU->Shared.fEnabled)
+ return VINF_SUCCESS;
+
+ PRTTHREADINT pThreadSelf = hThreadSelf;
+ AssertPtrReturn(pThreadSelf, VERR_SEM_LV_INVALID_PARAMETER);
+ AssertReturn(pThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ Assert(pThreadSelf == RTThreadSelf());
+
+ AssertReturn(RTTHREAD_IS_SLEEPING(enmSleepState), VERR_SEM_LV_INVALID_PARAMETER);
+
+ RTTHREADSTATE enmThreadState = rtThreadGetState(pThreadSelf);
+ if (RT_UNLIKELY(enmThreadState != RTTHREADSTATE_RUNNING))
+ {
+ AssertReturn( enmThreadState == RTTHREADSTATE_TERMINATED /* rtThreadRemove uses locks too */
+ || enmThreadState == RTTHREADSTATE_INITIALIZING /* rtThreadInsert uses locks too */
+ , VERR_SEM_LV_INVALID_PARAMETER);
+ enmSleepState = enmThreadState;
+ }
+
+ /*
+ * Record the location.
+ */
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, pRecU);
+ rtLockValidatorSrcPosCopy(&pThreadSelf->LockValidator.SrcPos, pSrcPos);
+ ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, true);
+ pThreadSelf->LockValidator.enmRecState = enmSleepState;
+ rtThreadSetState(pThreadSelf, enmSleepState);
+
+ /*
+ * Don't do deadlock detection if we're recursing.
+ */
+ int rc = VINF_SUCCESS;
+ PRTLOCKVALRECUNION pEntry = !pRecU->Shared.fSignaller
+ ? rtLockValidatorRecSharedFindOwner(&pRecU->Shared, pThreadSelf, NULL)
+ : NULL;
+ if (pEntry)
+ {
+ if ( !fRecursiveOk
+ || ( pRec->hClass
+ && !pRec->hClass->fRecursionOk)
+ )
+ {
+ rtLockValComplainFirst("Recursion not allowed!", pSrcPos, pThreadSelf, pRecU, true);
+ rtLockValComplainPanic();
+ rc = VERR_SEM_LV_NESTED;
+ }
+ }
+ /*
+ * Perform deadlock detection.
+ */
+ else if ( pRec->hClass
+ && ( pRec->hClass->cMsMinDeadlock == RT_INDEFINITE_WAIT
+ || pRec->hClass->cMsMinDeadlock > cMillies))
+ rc = VINF_SUCCESS;
+ else if (!rtLockValidatorIsSimpleNoDeadlockCase(pRecU))
+ rc = rtLockValidatorDeadlockDetection(pRecU, pThreadSelf, pSrcPos);
+
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteBool(&pThreadSelf->fReallySleeping, fReallySleeping);
+ else
+ {
+ rtThreadSetState(pThreadSelf, enmThreadState);
+ rtLockValidatorWriteRecUnionPtr(&pThreadSelf->LockValidator.pRec, NULL);
+ }
+ ASMAtomicWriteBool(&pThreadSelf->LockValidator.fInValidator, false);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedCheckBlocking);
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckOrderAndBlocking(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf,
+ PCRTLOCKVALSRCPOS pSrcPos, bool fRecursiveOk, RTMSINTERVAL cMillies,
+ RTTHREADSTATE enmSleepState, bool fReallySleeping)
+{
+ int rc = RTLockValidatorRecSharedCheckOrder(pRec, hThreadSelf, pSrcPos, cMillies);
+ if (RT_SUCCESS(rc))
+ rc = RTLockValidatorRecSharedCheckBlocking(pRec, hThreadSelf, pSrcPos, fRecursiveOk, cMillies,
+ enmSleepState, fReallySleeping);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedCheckOrderAndBlocking);
+
+
+/**
+ * Allocates and initializes an owner entry for the shared lock record.
+ *
+ * @returns The new owner entry.
+ * @param pRec The shared lock record.
+ * @param pThreadSelf The calling thread and owner. Used for record
+ * initialization and allocation.
+ * @param pSrcPos The source position.
+ */
+DECLINLINE(PRTLOCKVALRECUNION)
+rtLockValidatorRecSharedAllocOwner(PRTLOCKVALRECSHRD pRec, PRTTHREADINT pThreadSelf, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ PRTLOCKVALRECUNION pEntry;
+
+ /*
+ * Check if the thread has any statically allocated records we can easily
+ * make use of.
+ */
+ unsigned iEntry = ASMBitFirstSetU32(ASMAtomicUoReadU32(&pThreadSelf->LockValidator.bmFreeShrdOwners));
+ if ( iEntry > 0
+ && ASMAtomicBitTestAndClear(&pThreadSelf->LockValidator.bmFreeShrdOwners, iEntry - 1))
+ {
+ pEntry = (PRTLOCKVALRECUNION)&pThreadSelf->LockValidator.aShrdOwners[iEntry - 1];
+ Assert(!pEntry->ShrdOwner.fReserved);
+ pEntry->ShrdOwner.fStaticAlloc = true;
+ rtThreadGet(pThreadSelf);
+ }
+ else
+ {
+ pEntry = (PRTLOCKVALRECUNION)RTMemAlloc(sizeof(RTLOCKVALRECSHRDOWN));
+ if (RT_UNLIKELY(!pEntry))
+ return NULL;
+ pEntry->ShrdOwner.fStaticAlloc = false;
+ }
+
+ pEntry->Core.u32Magic = RTLOCKVALRECSHRDOWN_MAGIC;
+ pEntry->ShrdOwner.cRecursion = 1;
+ pEntry->ShrdOwner.fReserved = true;
+ pEntry->ShrdOwner.hThread = pThreadSelf;
+ pEntry->ShrdOwner.pDown = NULL;
+ pEntry->ShrdOwner.pSharedRec = pRec;
+#if HC_ARCH_BITS == 32
+ pEntry->ShrdOwner.pvReserved = NULL;
+#endif
+ if (pSrcPos)
+ pEntry->ShrdOwner.SrcPos = *pSrcPos;
+ else
+ rtLockValidatorSrcPosInit(&pEntry->ShrdOwner.SrcPos);
+ return pEntry;
+}
+
+
+/**
+ * Frees an owner entry allocated by rtLockValidatorRecSharedAllocOwner.
+ *
+ * @param pEntry The owner entry.
+ */
+DECLINLINE(void) rtLockValidatorRecSharedFreeOwner(PRTLOCKVALRECSHRDOWN pEntry)
+{
+ if (pEntry)
+ {
+ Assert(pEntry->Core.u32Magic == RTLOCKVALRECSHRDOWN_MAGIC);
+ ASMAtomicWriteU32(&pEntry->Core.u32Magic, RTLOCKVALRECSHRDOWN_MAGIC_DEAD);
+
+ PRTTHREADINT pThread;
+ ASMAtomicXchgHandle(&pEntry->hThread, NIL_RTTHREAD, &pThread);
+
+ Assert(pEntry->fReserved);
+ pEntry->fReserved = false;
+
+ if (pEntry->fStaticAlloc)
+ {
+ AssertPtrReturnVoid(pThread);
+ AssertReturnVoid(pThread->u32Magic == RTTHREADINT_MAGIC);
+
+ uintptr_t iEntry = pEntry - &pThread->LockValidator.aShrdOwners[0];
+ AssertReleaseReturnVoid(iEntry < RT_ELEMENTS(pThread->LockValidator.aShrdOwners));
+
+ Assert(!ASMBitTest(&pThread->LockValidator.bmFreeShrdOwners, (int32_t)iEntry));
+ ASMAtomicBitSet(&pThread->LockValidator.bmFreeShrdOwners, (int32_t)iEntry);
+
+ rtThreadRelease(pThread);
+ }
+ else
+ {
+ rtLockValidatorSerializeDestructEnter();
+ rtLockValidatorSerializeDestructLeave();
+
+ RTMemFree(pEntry);
+ }
+ }
+}
+
+
+/**
+ * Make more room in the table.
+ *
+ * @retval true on success
+ * @retval false if we're out of memory or running into a bad race condition
+ * (probably a bug somewhere). No longer holding the lock.
+ *
+ * @param pShared The shared lock record.
+ */
+static bool rtLockValidatorRecSharedMakeRoom(PRTLOCKVALRECSHRD pShared)
+{
+ for (unsigned i = 0; i < 1000; i++)
+ {
+ /*
+ * Switch to the other data access direction.
+ */
+ rtLockValidatorSerializeDetectionLeave();
+ if (i >= 10)
+ {
+ Assert(i != 10 && i != 100);
+ RTThreadSleep(i >= 100);
+ }
+ rtLockValidatorSerializeDestructEnter();
+
+ /*
+ * Try grab the privilege to reallocating the table.
+ */
+ if ( pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC
+ && ASMAtomicCmpXchgBool(&pShared->fReallocating, true, false))
+ {
+ uint32_t cAllocated = pShared->cAllocated;
+ if (cAllocated < pShared->cEntries)
+ {
+ /*
+ * Ok, still not enough space. Reallocate the table.
+ */
+ uint32_t cInc = RT_ALIGN_32(pShared->cEntries - cAllocated, 16);
+ PRTLOCKVALRECSHRDOWN *papOwners;
+ papOwners = (PRTLOCKVALRECSHRDOWN *)RTMemRealloc((void *)pShared->papOwners,
+ (cAllocated + cInc) * sizeof(void *));
+ if (!papOwners)
+ {
+ ASMAtomicWriteBool(&pShared->fReallocating, false);
+ rtLockValidatorSerializeDestructLeave();
+ /* RTMemRealloc will assert */
+ return false;
+ }
+
+ while (cInc-- > 0)
+ {
+ papOwners[cAllocated] = NULL;
+ cAllocated++;
+ }
+
+ ASMAtomicWritePtr(&pShared->papOwners, papOwners);
+ ASMAtomicWriteU32(&pShared->cAllocated, cAllocated);
+ }
+ ASMAtomicWriteBool(&pShared->fReallocating, false);
+ }
+ rtLockValidatorSerializeDestructLeave();
+
+ rtLockValidatorSerializeDetectionEnter();
+ if (RT_UNLIKELY(pShared->Core.u32Magic != RTLOCKVALRECSHRD_MAGIC))
+ break;
+
+ if (pShared->cAllocated >= pShared->cEntries)
+ return true;
+ }
+
+ rtLockValidatorSerializeDetectionLeave();
+ AssertFailed(); /* too many iterations or destroyed while racing. */
+ return false;
+}
+
+
+/**
+ * Adds an owner entry to a shared lock record.
+ *
+ * @returns true on success, false on serious race or we're if out of memory.
+ * @param pShared The shared lock record.
+ * @param pEntry The owner entry.
+ */
+DECLINLINE(bool) rtLockValidatorRecSharedAddOwner(PRTLOCKVALRECSHRD pShared, PRTLOCKVALRECSHRDOWN pEntry)
+{
+ rtLockValidatorSerializeDetectionEnter();
+ if (RT_LIKELY(pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC)) /* paranoia */
+ {
+ if ( ASMAtomicIncU32(&pShared->cEntries) > pShared->cAllocated /** @todo add fudge */
+ && !rtLockValidatorRecSharedMakeRoom(pShared))
+ return false; /* the worker leave the lock */
+
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pShared->papOwners;
+ uint32_t const cMax = pShared->cAllocated;
+ for (unsigned i = 0; i < 100; i++)
+ {
+ for (uint32_t iEntry = 0; iEntry < cMax; iEntry++)
+ {
+ if (ASMAtomicCmpXchgPtr(&papOwners[iEntry], pEntry, NULL))
+ {
+ rtLockValidatorSerializeDetectionLeave();
+ return true;
+ }
+ }
+ Assert(i != 25);
+ }
+ AssertFailed();
+ }
+ rtLockValidatorSerializeDetectionLeave();
+ return false;
+}
+
+
+/**
+ * Remove an owner entry from a shared lock record and free it.
+ *
+ * @param pShared The shared lock record.
+ * @param pEntry The owner entry to remove.
+ * @param iEntry The last known index.
+ */
+DECLINLINE(void) rtLockValidatorRecSharedRemoveAndFreeOwner(PRTLOCKVALRECSHRD pShared, PRTLOCKVALRECSHRDOWN pEntry,
+ uint32_t iEntry)
+{
+ /*
+ * Remove it from the table.
+ */
+ rtLockValidatorSerializeDetectionEnter();
+ AssertReturnVoidStmt(pShared->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, rtLockValidatorSerializeDetectionLeave());
+ if (RT_UNLIKELY( iEntry >= pShared->cAllocated
+ || !ASMAtomicCmpXchgPtr(&pShared->papOwners[iEntry], NULL, pEntry)))
+ {
+ /* this shouldn't happen yet... */
+ AssertFailed();
+ PRTLOCKVALRECSHRDOWN volatile *papOwners = pShared->papOwners;
+ uint32_t const cMax = pShared->cAllocated;
+ for (iEntry = 0; iEntry < cMax; iEntry++)
+ if (ASMAtomicCmpXchgPtr(&papOwners[iEntry], NULL, pEntry))
+ break;
+ AssertReturnVoidStmt(iEntry < cMax, rtLockValidatorSerializeDetectionLeave());
+ }
+ uint32_t cNow = ASMAtomicDecU32(&pShared->cEntries);
+ Assert(!(cNow & RT_BIT_32(31))); NOREF(cNow);
+ rtLockValidatorSerializeDetectionLeave();
+
+ /*
+ * Successfully removed, now free it.
+ */
+ rtLockValidatorRecSharedFreeOwner(pEntry);
+}
+
+
+RTDECL(void) RTLockValidatorRecSharedResetOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ AssertReturnVoid(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC);
+ if (!pRec->fEnabled)
+ return;
+ AssertReturnVoid(hThread == NIL_RTTHREAD || hThread->u32Magic == RTTHREADINT_MAGIC);
+ AssertReturnVoid(pRec->fSignaller);
+
+ /*
+ * Free all current owners.
+ */
+ rtLockValidatorSerializeDetectionEnter();
+ while (ASMAtomicUoReadU32(&pRec->cEntries) > 0)
+ {
+ AssertReturnVoidStmt(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, rtLockValidatorSerializeDetectionLeave());
+ uint32_t iEntry = 0;
+ uint32_t cEntries = pRec->cAllocated;
+ PRTLOCKVALRECSHRDOWN volatile *papEntries = pRec->papOwners;
+ while (iEntry < cEntries)
+ {
+ PRTLOCKVALRECSHRDOWN pEntry = ASMAtomicXchgPtrT(&papEntries[iEntry], NULL, PRTLOCKVALRECSHRDOWN);
+ if (pEntry)
+ {
+ ASMAtomicDecU32(&pRec->cEntries);
+ rtLockValidatorSerializeDetectionLeave();
+
+ rtLockValidatorRecSharedFreeOwner(pEntry);
+
+ rtLockValidatorSerializeDetectionEnter();
+ if (ASMAtomicUoReadU32(&pRec->cEntries) == 0)
+ break;
+ cEntries = pRec->cAllocated;
+ papEntries = pRec->papOwners;
+ }
+ iEntry++;
+ }
+ }
+ rtLockValidatorSerializeDetectionLeave();
+
+ if (hThread != NIL_RTTHREAD)
+ {
+ /*
+ * Allocate a new owner entry and insert it into the table.
+ */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedAllocOwner(pRec, hThread, pSrcPos);
+ if ( pEntry
+ && !rtLockValidatorRecSharedAddOwner(pRec, &pEntry->ShrdOwner))
+ rtLockValidatorRecSharedFreeOwner(&pEntry->ShrdOwner);
+ }
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedResetOwner);
+
+
+RTDECL(void) RTLockValidatorRecSharedAddOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread, PCRTLOCKVALSRCPOS pSrcPos)
+{
+ AssertReturnVoid(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC);
+ if (!pRec->fEnabled)
+ return;
+ if (hThread == NIL_RTTHREAD)
+ {
+ hThread = RTThreadSelfAutoAdopt();
+ AssertReturnVoid(hThread != NIL_RTTHREAD);
+ }
+ AssertReturnVoid(hThread->u32Magic == RTTHREADINT_MAGIC);
+
+ /*
+ * Recursive?
+ *
+ * Note! This code can be optimized to try avoid scanning the table on
+ * insert. However, that's annoying work that makes the code big,
+ * so it can wait til later sometime.
+ */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, NULL);
+ if (pEntry)
+ {
+ Assert(!pRec->fSignaller);
+ pEntry->ShrdOwner.cRecursion++;
+ rtLockValidatorStackPushRecursion(hThread, pEntry, pSrcPos);
+ return;
+ }
+
+ /*
+ * Allocate a new owner entry and insert it into the table.
+ */
+ pEntry = rtLockValidatorRecSharedAllocOwner(pRec, hThread, pSrcPos);
+ if (pEntry)
+ {
+ if (rtLockValidatorRecSharedAddOwner(pRec, &pEntry->ShrdOwner))
+ {
+ if (!pRec->fSignaller)
+ rtLockValidatorStackPush(hThread, pEntry);
+ }
+ else
+ rtLockValidatorRecSharedFreeOwner(&pEntry->ShrdOwner);
+ }
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedAddOwner);
+
+
+RTDECL(void) RTLockValidatorRecSharedRemoveOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread)
+{
+ AssertReturnVoid(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC);
+ if (!pRec->fEnabled)
+ return;
+ if (hThread == NIL_RTTHREAD)
+ {
+ hThread = RTThreadSelfAutoAdopt();
+ AssertReturnVoid(hThread != NIL_RTTHREAD);
+ }
+ AssertReturnVoid(hThread->u32Magic == RTTHREADINT_MAGIC);
+
+ /*
+ * Find the entry hope it's a recursive one.
+ */
+ uint32_t iEntry = UINT32_MAX; /* shuts up gcc */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, &iEntry);
+ AssertReturnVoid(pEntry);
+ AssertReturnVoid(pEntry->ShrdOwner.cRecursion > 0);
+
+ uint32_t c = --pEntry->ShrdOwner.cRecursion;
+ if (c == 0)
+ {
+ if (!pRec->fSignaller)
+ rtLockValidatorStackPop(hThread, (PRTLOCKVALRECUNION)pEntry);
+ rtLockValidatorRecSharedRemoveAndFreeOwner(pRec, &pEntry->ShrdOwner, iEntry);
+ }
+ else
+ {
+ Assert(!pRec->fSignaller);
+ rtLockValidatorStackPopRecursion(hThread, pEntry);
+ }
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedRemoveOwner);
+
+
+RTDECL(bool) RTLockValidatorRecSharedIsOwner(PRTLOCKVALRECSHRD pRec, RTTHREAD hThread)
+{
+ /* Validate and resolve input. */
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, false);
+ if (!pRec->fEnabled)
+ return false;
+ if (hThread == NIL_RTTHREAD)
+ {
+ hThread = RTThreadSelfAutoAdopt();
+ AssertReturn(hThread != NIL_RTTHREAD, false);
+ }
+ AssertReturn(hThread->u32Magic == RTTHREADINT_MAGIC, false);
+
+ /* Do the job. */
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThread, NULL);
+ return pEntry != NULL;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorRecSharedIsOwner);
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckAndRelease(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf)
+{
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRec->fEnabled)
+ return VINF_SUCCESS;
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR);
+ }
+ Assert(hThreadSelf == RTThreadSelf());
+ AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Locate the entry for this thread in the table.
+ */
+ uint32_t iEntry = 0;
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThreadSelf, &iEntry);
+ if (RT_UNLIKELY(!pEntry))
+ {
+ rtLockValComplainFirst("Not owner (shared)!", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec, true);
+ rtLockValComplainPanic();
+ return VERR_SEM_LV_NOT_OWNER;
+ }
+
+ /*
+ * Check the release order.
+ */
+ if ( pRec->hClass != NIL_RTLOCKVALCLASS
+ && pRec->hClass->fStrictReleaseOrder
+ && pRec->hClass->cMsMinOrder != RT_INDEFINITE_WAIT
+ )
+ {
+ int rc = rtLockValidatorStackCheckReleaseOrder(hThreadSelf, (PRTLOCKVALRECUNION)pEntry);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Release the ownership or unwind a level of recursion.
+ */
+ Assert(pEntry->ShrdOwner.cRecursion > 0);
+ uint32_t c = --pEntry->ShrdOwner.cRecursion;
+ if (c == 0)
+ {
+ rtLockValidatorStackPop(hThreadSelf, pEntry);
+ rtLockValidatorRecSharedRemoveAndFreeOwner(pRec, &pEntry->ShrdOwner, iEntry);
+ }
+ else
+ rtLockValidatorStackPopRecursion(hThreadSelf, pEntry);
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTLockValidatorRecSharedCheckSignaller(PRTLOCKVALRECSHRD pRec, RTTHREAD hThreadSelf)
+{
+ AssertReturn(pRec->Core.u32Magic == RTLOCKVALRECSHRD_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+ if (!pRec->fEnabled)
+ return VINF_SUCCESS;
+ if (hThreadSelf == NIL_RTTHREAD)
+ {
+ hThreadSelf = RTThreadSelfAutoAdopt();
+ AssertReturn(hThreadSelf != NIL_RTTHREAD, VERR_SEM_LV_INTERNAL_ERROR);
+ }
+ Assert(hThreadSelf == RTThreadSelf());
+ AssertReturn(hThreadSelf->u32Magic == RTTHREADINT_MAGIC, VERR_SEM_LV_INVALID_PARAMETER);
+
+ /*
+ * Locate the entry for this thread in the table.
+ */
+ uint32_t iEntry = 0;
+ PRTLOCKVALRECUNION pEntry = rtLockValidatorRecSharedFindOwner(pRec, hThreadSelf, &iEntry);
+ if (RT_UNLIKELY(!pEntry))
+ {
+ rtLockValComplainFirst("Invalid signaller!", NULL, hThreadSelf, (PRTLOCKVALRECUNION)pRec, true);
+ rtLockValComplainPanic();
+ return VERR_SEM_LV_NOT_SIGNALLER;
+ }
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int32_t) RTLockValidatorWriteLockGetCount(RTTHREAD Thread)
+{
+ if (Thread == NIL_RTTHREAD)
+ return 0;
+
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (!pThread)
+ return VERR_INVALID_HANDLE;
+ int32_t cWriteLocks = ASMAtomicReadS32(&pThread->LockValidator.cWriteLocks);
+ rtThreadRelease(pThread);
+ return cWriteLocks;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorWriteLockGetCount);
+
+
+RTDECL(void) RTLockValidatorWriteLockInc(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ AssertReturnVoid(pThread);
+ ASMAtomicIncS32(&pThread->LockValidator.cWriteLocks);
+ rtThreadRelease(pThread);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorWriteLockInc);
+
+
+RTDECL(void) RTLockValidatorWriteLockDec(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ AssertReturnVoid(pThread);
+ ASMAtomicDecS32(&pThread->LockValidator.cWriteLocks);
+ rtThreadRelease(pThread);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorWriteLockDec);
+
+
+RTDECL(int32_t) RTLockValidatorReadLockGetCount(RTTHREAD Thread)
+{
+ if (Thread == NIL_RTTHREAD)
+ return 0;
+
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (!pThread)
+ return VERR_INVALID_HANDLE;
+ int32_t cReadLocks = ASMAtomicReadS32(&pThread->LockValidator.cReadLocks);
+ rtThreadRelease(pThread);
+ return cReadLocks;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorReadLockGetCount);
+
+
+RTDECL(void) RTLockValidatorReadLockInc(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ Assert(pThread);
+ ASMAtomicIncS32(&pThread->LockValidator.cReadLocks);
+ rtThreadRelease(pThread);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorReadLockInc);
+
+
+RTDECL(void) RTLockValidatorReadLockDec(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ Assert(pThread);
+ ASMAtomicDecS32(&pThread->LockValidator.cReadLocks);
+ rtThreadRelease(pThread);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorReadLockDec);
+
+
+RTDECL(void *) RTLockValidatorQueryBlocking(RTTHREAD hThread)
+{
+ void *pvLock = NULL;
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ RTTHREADSTATE enmState = rtThreadGetState(pThread);
+ if (RTTHREAD_IS_SLEEPING(enmState))
+ {
+ rtLockValidatorSerializeDetectionEnter();
+
+ enmState = rtThreadGetState(pThread);
+ if (RTTHREAD_IS_SLEEPING(enmState))
+ {
+ PRTLOCKVALRECUNION pRec = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pRec);
+ if (pRec)
+ {
+ switch (pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ pvLock = pRec->Excl.hLock;
+ break;
+
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ pRec = (PRTLOCKVALRECUNION)pRec->ShrdOwner.pSharedRec;
+ if (!pRec || pRec->Core.u32Magic != RTLOCKVALRECSHRD_MAGIC)
+ break;
+ RT_FALL_THRU();
+ case RTLOCKVALRECSHRD_MAGIC:
+ pvLock = pRec->Shared.hLock;
+ break;
+ }
+ if (RTThreadGetState(pThread) != enmState)
+ pvLock = NULL;
+ }
+ }
+
+ rtLockValidatorSerializeDetectionLeave();
+ }
+ rtThreadRelease(pThread);
+ }
+ return pvLock;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorQueryBlocking);
+
+
+RTDECL(bool) RTLockValidatorIsBlockedThreadInValidator(RTTHREAD hThread)
+{
+ bool fRet = false;
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ fRet = ASMAtomicReadBool(&pThread->LockValidator.fInValidator);
+ rtThreadRelease(pThread);
+ }
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorIsBlockedThreadInValidator);
+
+
+RTDECL(bool) RTLockValidatorHoldsLocksInClass(RTTHREAD hCurrentThread, RTLOCKVALCLASS hClass)
+{
+ bool fRet = false;
+ if (hCurrentThread == NIL_RTTHREAD)
+ hCurrentThread = RTThreadSelf();
+ else
+ Assert(hCurrentThread == RTThreadSelf());
+ PRTTHREADINT pThread = rtThreadGet(hCurrentThread);
+ if (pThread)
+ {
+ if (hClass != NIL_RTLOCKVALCLASS)
+ {
+ PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
+ while (RT_VALID_PTR(pCur) && !fRet)
+ {
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ fRet = pCur->Excl.hClass == hClass;
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown);
+ break;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ fRet = RT_VALID_PTR(pCur->ShrdOwner.pSharedRec)
+ && pCur->ShrdOwner.pSharedRec->hClass == hClass;
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown);
+ break;
+ case RTLOCKVALRECNEST_MAGIC:
+ switch (pCur->Nest.pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ fRet = pCur->Nest.pRec->Excl.hClass == hClass;
+ break;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ fRet = RT_VALID_PTR(pCur->ShrdOwner.pSharedRec)
+ && pCur->Nest.pRec->ShrdOwner.pSharedRec->hClass == hClass;
+ break;
+ }
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown);
+ break;
+ default:
+ pCur = NULL;
+ break;
+ }
+ }
+ }
+
+ rtThreadRelease(pThread);
+ }
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorHoldsLocksInClass);
+
+
+RTDECL(bool) RTLockValidatorHoldsLocksInSubClass(RTTHREAD hCurrentThread, RTLOCKVALCLASS hClass, uint32_t uSubClass)
+{
+ bool fRet = false;
+ if (hCurrentThread == NIL_RTTHREAD)
+ hCurrentThread = RTThreadSelf();
+ else
+ Assert(hCurrentThread == RTThreadSelf());
+ PRTTHREADINT pThread = rtThreadGet(hCurrentThread);
+ if (pThread)
+ {
+ if (hClass != NIL_RTLOCKVALCLASS)
+ {
+ PRTLOCKVALRECUNION pCur = rtLockValidatorReadRecUnionPtr(&pThread->LockValidator.pStackTop);
+ while (RT_VALID_PTR(pCur) && !fRet)
+ {
+ switch (pCur->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ fRet = pCur->Excl.hClass == hClass
+ && pCur->Excl.uSubClass == uSubClass;
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Excl.pDown);
+ break;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ fRet = RT_VALID_PTR(pCur->ShrdOwner.pSharedRec)
+ && pCur->ShrdOwner.pSharedRec->hClass == hClass
+ && pCur->ShrdOwner.pSharedRec->uSubClass == uSubClass;
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->ShrdOwner.pDown);
+ break;
+ case RTLOCKVALRECNEST_MAGIC:
+ switch (pCur->Nest.pRec->Core.u32Magic)
+ {
+ case RTLOCKVALRECEXCL_MAGIC:
+ fRet = pCur->Nest.pRec->Excl.hClass == hClass
+ && pCur->Nest.pRec->Excl.uSubClass == uSubClass;
+ break;
+ case RTLOCKVALRECSHRDOWN_MAGIC:
+ fRet = RT_VALID_PTR(pCur->ShrdOwner.pSharedRec)
+ && pCur->Nest.pRec->ShrdOwner.pSharedRec->hClass == hClass
+ && pCur->Nest.pRec->ShrdOwner.pSharedRec->uSubClass == uSubClass;
+ break;
+ }
+ pCur = rtLockValidatorReadRecUnionPtr(&pCur->Nest.pDown);
+ break;
+ default:
+ pCur = NULL;
+ break;
+ }
+ }
+ }
+
+ rtThreadRelease(pThread);
+ }
+ return fRet;
+}
+RT_EXPORT_SYMBOL(RTLockValidatorHoldsLocksInClass);
+
+
+RTDECL(bool) RTLockValidatorSetEnabled(bool fEnabled)
+{
+ return ASMAtomicXchgBool(&g_fLockValidatorEnabled, fEnabled);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorSetEnabled);
+
+
+RTDECL(bool) RTLockValidatorIsEnabled(void)
+{
+ return ASMAtomicUoReadBool(&g_fLockValidatorEnabled);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorIsEnabled);
+
+
+RTDECL(bool) RTLockValidatorSetQuiet(bool fQuiet)
+{
+ return ASMAtomicXchgBool(&g_fLockValidatorQuiet, fQuiet);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorSetQuiet);
+
+
+RTDECL(bool) RTLockValidatorIsQuiet(void)
+{
+ return ASMAtomicUoReadBool(&g_fLockValidatorQuiet);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorIsQuiet);
+
+
+RTDECL(bool) RTLockValidatorSetMayPanic(bool fMayPanic)
+{
+ return ASMAtomicXchgBool(&g_fLockValidatorMayPanic, fMayPanic);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorSetMayPanic);
+
+
+RTDECL(bool) RTLockValidatorMayPanic(void)
+{
+ return ASMAtomicUoReadBool(&g_fLockValidatorMayPanic);
+}
+RT_EXPORT_SYMBOL(RTLockValidatorMayPanic);
+
diff --git a/src/VBox/Runtime/common/misc/message.cpp b/src/VBox/Runtime/common/misc/message.cpp
new file mode 100644
index 00000000..87cffd1b
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/message.cpp
@@ -0,0 +1,266 @@
+/* $Id: message.cpp $ */
+/** @file
+ * IPRT - Error reporting to standard error.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/message.h>
+
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include "internal/process.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The program name we're using. */
+static const char * volatile g_pszProgName = NULL;
+/** Custom program name set via RTMsgSetProgName. */
+static char g_szProgName[128];
+
+
+RTDECL(int) RTMsgSetProgName(const char *pszFormat, ...)
+{
+ g_pszProgName = &g_szrtProcExePath[g_offrtProcName];
+
+ va_list va;
+ va_start(va, pszFormat);
+ RTStrPrintfV(g_szProgName, sizeof(g_szProgName) - 1, pszFormat, va);
+ va_end(va);
+
+ g_pszProgName = g_szProgName;
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTMsgSetProgName);
+
+
+static int rtMsgWorker(PRTSTREAM pDst, const char *pszPrefix, const char *pszFormat, va_list va)
+{
+ if ( !*pszFormat
+ || !strcmp(pszFormat, "\n"))
+ RTStrmPrintf(pDst, "\n");
+ else
+ {
+ const char *pszProgName = g_pszProgName;
+ if (!pszProgName)
+ g_pszProgName = pszProgName = &g_szrtProcExePath[g_offrtProcName];
+
+ char *pszMsg;
+ ssize_t cch = RTStrAPrintfV(&pszMsg, pszFormat, va);
+ if (cch >= 0)
+ {
+ /* print it line by line. */
+ char *psz = pszMsg;
+ do
+ {
+ char *pszEnd = strchr(psz, '\n');
+ if (!pszEnd)
+ {
+ RTStrmPrintf(pDst, "%s: %s%s\n", pszProgName, pszPrefix, psz);
+ break;
+ }
+ if (pszEnd == psz)
+ RTStrmPrintf(pDst, "\n");
+ else
+ {
+ *pszEnd = '\0';
+ RTStrmPrintf(pDst, "%s: %s%s\n", pszProgName, pszPrefix, psz);
+ }
+ psz = pszEnd + 1;
+ } while (*psz);
+ RTStrFree(pszMsg);
+ }
+ else
+ {
+ /* Simple fallback for handling out-of-memory conditions. */
+ RTStrmPrintf(pDst, "%s: %s", pszProgName, pszPrefix);
+ RTStrmPrintfV(pDst, pszFormat, va);
+ if (!strchr(pszFormat, '\n'))
+ RTStrmPrintf(pDst, "\n");
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+RTDECL(int) RTMsgError(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTMsgError);
+
+
+RTDECL(int) RTMsgErrorV(const char *pszFormat, va_list va)
+{
+ return rtMsgWorker(g_pStdErr, "error: ", pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTMsgErrorV);
+
+
+RTDECL(RTEXITCODE) RTMsgErrorExit(RTEXITCODE enmExitCode, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return enmExitCode;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorExit);
+
+
+RTDECL(RTEXITCODE) RTMsgErrorExitV(RTEXITCODE enmExitCode, const char *pszFormat, va_list va)
+{
+ RTMsgErrorV(pszFormat, va);
+ return enmExitCode;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorExitV);
+
+
+RTDECL(RTEXITCODE) RTMsgErrorExitFailure(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_FAILURE;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorExitFailure);
+
+
+RTDECL(RTEXITCODE) RTMsgErrorExitFailureV(const char *pszFormat, va_list va)
+{
+ RTMsgErrorV(pszFormat, va);
+ return RTEXITCODE_FAILURE;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorExitFailureV);
+
+
+RTDECL(int) RTMsgErrorRc(int rcRet, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ return rcRet;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorRcV);
+
+
+RTDECL(int) RTMsgErrorRcV(int rcRet, const char *pszFormat, va_list va)
+{
+ RTMsgErrorV(pszFormat, va);
+ return rcRet;
+}
+RT_EXPORT_SYMBOL(RTMsgErrorRcV);
+
+
+RTDECL(RTEXITCODE) RTMsgInitFailure(int rcRTR3Init)
+{
+ if ( g_offrtProcName
+ && g_offrtProcName < sizeof(g_szrtProcExePath)
+ && g_szrtProcExePath[0]
+ && g_szrtProcExePath[g_offrtProcName])
+ RTStrmPrintf(g_pStdErr, "%s: fatal error: RTR3Init: %Rrc\n", &g_szrtProcExePath[g_offrtProcName], rcRTR3Init);
+ else
+ RTStrmPrintf(g_pStdErr, "fatal error: RTR3Init: %Rrc\n", rcRTR3Init);
+ return RTEXITCODE_INIT;
+}
+RT_EXPORT_SYMBOL(RTMsgInitFailure);
+
+
+RTDECL(RTEXITCODE) RTMsgSyntax(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgSyntaxV(pszFormat, va);
+ va_end(va);
+ return RTEXITCODE_SYNTAX;
+}
+RT_EXPORT_SYMBOL(RTMsgSyntax);
+
+
+RTDECL(RTEXITCODE) RTMsgSyntaxV(const char *pszFormat, va_list va)
+{
+ rtMsgWorker(g_pStdOut, "syntax error: ", pszFormat, va);
+ return RTEXITCODE_SYNTAX;
+}
+RT_EXPORT_SYMBOL(RTMsgSyntaxV);
+
+
+RTDECL(int) RTMsgWarning(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTMsgWarningV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTMsgInfo);
+
+
+RTDECL(int) RTMsgWarningV(const char *pszFormat, va_list va)
+{
+ return rtMsgWorker(g_pStdErr, "warning: ", pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTMsgWarningV);
+
+
+RTDECL(int) RTMsgInfo(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTMsgInfoV(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTMsgInfo);
+
+
+RTDECL(int) RTMsgInfoV(const char *pszFormat, va_list va)
+{
+ return rtMsgWorker(g_pStdOut, "info: ", pszFormat, va);
+}
+RT_EXPORT_SYMBOL(RTMsgInfoV);
+
diff --git a/src/VBox/Runtime/common/misc/messagerefentry.cpp b/src/VBox/Runtime/common/misc/messagerefentry.cpp
new file mode 100644
index 00000000..2ad0f2fd
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/messagerefentry.cpp
@@ -0,0 +1,332 @@
+/* $Id: messagerefentry.cpp $ */
+/** @file
+ * IPRT - Program usage and help formatting.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/message.h>
+
+#include <iprt/env.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include "internal/process.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Spaces for intending. */
+static const char g_szSpaces[] = " ";
+
+
+/**
+ * Retruns the width for the given handle.
+ *
+ * @returns Screen width.
+ * @param pStrm The stream, g_pStdErr or g_pStdOut.
+ */
+static uint32_t getScreenWidth(PRTSTREAM pStrm)
+{
+ static uint32_t s_acch[2] = { 0, 0 };
+ uint32_t iWhich = pStrm == g_pStdErr ? 1 : 0;
+ uint32_t cch = s_acch[iWhich];
+ if (cch)
+ return cch;
+
+ const char *psz = RTEnvGet("IPRT_SCREEN_WIDTH");
+ if ( !psz
+ || RTStrToUInt32Full(psz, 0, &cch) != VINF_SUCCESS
+ || cch == 0)
+ {
+ int rc = RTStrmQueryTerminalWidth(pStrm, &cch);
+ if (rc == VERR_INVALID_FUNCTION)
+ {
+ /* It's not a console, but in case we're being piped to less/more/list
+ we look for a console handle on the other standard output handle
+ and standard input. (Latter doesn't work on windows.) */
+ rc = RTStrmQueryTerminalWidth(pStrm == g_pStdErr ? g_pStdOut : g_pStdErr, &cch);
+ if (rc == VERR_INVALID_FUNCTION || rc == VERR_INVALID_HANDLE)
+ rc = RTStrmQueryTerminalWidth(g_pStdIn, &cch);
+ if (RT_FAILURE(rc))
+ cch = 80;
+ }
+ }
+
+ s_acch[iWhich] = cch;
+ return cch;
+}
+
+
+/**
+ * Prints a string table string (paragraph), performing non-breaking-space
+ * replacement and wrapping.
+ *
+ * @returns IRPT status code.
+ * @param pStrm The output stream.
+ * @param psz The string table string to print.
+ * @param cchMaxWidth The maximum output width.
+ * @param fFlags String flags that may affect formatting.
+ * @param pcLinesWritten Pointer to variable to update with written lines.
+ */
+static int printString(PRTSTREAM pStrm, const char *psz, uint32_t cchMaxWidth, uint64_t fFlags, uint32_t *pcLinesWritten)
+{
+ uint32_t cLinesWritten;
+ size_t cch = strlen(psz);
+ const char *pszNbsp = strchr(psz, RTMSGREFENTRY_NBSP);
+ int rc;
+
+ /*
+ * No-wrap case is simpler, so handle that separately.
+ */
+ if (cch <= cchMaxWidth)
+ {
+ if (!pszNbsp)
+ rc = RTStrmWrite(pStrm, psz, cch);
+ else
+ {
+ do
+ {
+ rc = RTStrmWrite(pStrm, psz, pszNbsp - psz);
+ if (RT_SUCCESS(rc))
+ rc = RTStrmPutCh(pStrm, ' ');
+ psz = pszNbsp + 1;
+ pszNbsp = strchr(psz, RTMSGREFENTRY_NBSP);
+ } while (pszNbsp && RT_SUCCESS(rc));
+ if (RT_SUCCESS(rc))
+ rc = RTStrmWrite(pStrm, psz, strlen(psz));
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTStrmPutCh(pStrm, '\n');
+ cLinesWritten = 1;
+ }
+ /*
+ * We need to wrap stuff, too bad.
+ */
+ else
+ {
+ /* Figure the paragraph indent level first. */
+ uint32_t cchIndent = 0;
+ while (*psz == ' ')
+ cchIndent++, psz++;
+ Assert(cchIndent + 4 + 1 <= RT_ELEMENTS(g_szSpaces));
+
+ if (cchIndent + 8 >= cchMaxWidth)
+ cchMaxWidth += cchIndent + 8;
+
+ /* Work our way thru the string, line by line. */
+ uint32_t cchHangingIndent = 0;
+ cLinesWritten = 0;
+ do
+ {
+ rc = RTStrmWrite(pStrm, g_szSpaces, cchIndent + cchHangingIndent);
+ if (RT_FAILURE(rc))
+ break;
+
+ size_t offLine = cchIndent + cchHangingIndent;
+ bool fPendingSpace = false;
+ do
+ {
+ const char *pszSpace = strchr(psz, ' ');
+ size_t cchWord = pszSpace ? pszSpace - psz : strlen(psz);
+ if ( offLine + cchWord + fPendingSpace > cchMaxWidth
+ && offLine != cchIndent
+ && fPendingSpace /* don't stop before first word */)
+ break;
+
+ pszNbsp = (const char *)memchr(psz, RTMSGREFENTRY_NBSP, cchWord);
+ while (pszNbsp)
+ {
+ size_t cchSubWord = pszNbsp - psz;
+ if (fPendingSpace)
+ {
+ rc = RTStrmPutCh(pStrm, ' ');
+ if (RT_FAILURE(rc))
+ break;
+ }
+ rc = RTStrmWrite(pStrm, psz, cchSubWord);
+ if (RT_FAILURE(rc))
+ break;
+ offLine += cchSubWord + fPendingSpace;
+ psz += cchSubWord + 1;
+ cchWord -= cchSubWord + 1;
+ pszNbsp = (const char *)memchr(psz, RTMSGREFENTRY_NBSP, cchWord);
+ fPendingSpace = true;
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ if (fPendingSpace)
+ {
+ rc = RTStrmPutCh(pStrm, ' ');
+ if (RT_FAILURE(rc))
+ break;
+ }
+ rc = RTStrmWrite(pStrm, psz, cchWord);
+ if (RT_FAILURE(rc))
+ break;
+
+ offLine += cchWord + fPendingSpace;
+ psz = pszSpace ? pszSpace + 1 : strchr(psz, '\0');
+ fPendingSpace = true;
+ } while (offLine < cchMaxWidth && *psz != '\0' && RT_SUCCESS(rc));
+
+ if (RT_SUCCESS(rc))
+ rc = RTStrmPutCh(pStrm, '\n');
+ if (RT_FAILURE(rc))
+ break;
+ cLinesWritten++;
+
+ /* Set up hanging indent if relevant. */
+ if (fFlags & RTMSGREFENTRYSTR_FLAGS_SYNOPSIS)
+ cchHangingIndent = 4;
+ } while (*psz != '\0');
+ }
+ *pcLinesWritten += cLinesWritten;
+ return rc;
+}
+
+
+/**
+ * Checks if the given string is empty (only spaces).
+ * @returns true if empty, false if not.
+ * @param psz The string to examine.
+ */
+DECLINLINE(bool) isEmptyString(const char *psz)
+{
+ char ch;
+ while ((ch = *psz) == ' ')
+ psz++;
+ return ch == '\0';
+}
+
+
+/**
+ * Prints a string table.
+ *
+ * @returns Current number of pending blank lines.
+ * @param pStrm The output stream.
+ * @param pStrTab The string table.
+ * @param fScope The selection scope.
+ * @param pcPendingBlankLines In: Pending blank lines from previous string
+ * table. Out: Pending blank lines.
+ * @param pcLinesWritten Pointer to variable that should be incremented
+ * by the number of lines written. Optional.
+ */
+RTDECL(int) RTMsgRefEntryPrintStringTable(PRTSTREAM pStrm, PCRTMSGREFENTRYSTRTAB pStrTab, uint64_t fScope,
+ uint32_t *pcPendingBlankLines, uint32_t *pcLinesWritten)
+{
+ uint32_t cPendingBlankLines = pcPendingBlankLines ? *pcPendingBlankLines : 0;
+ uint32_t cLinesWritten = 0;
+ uint32_t cchWidth = getScreenWidth(pStrm) - 1; /* (Seems a -1 here is prudent, at least on windows.) */
+ uint64_t fPrevScope = fScope;
+ int rc = VINF_SUCCESS;
+ for (uint32_t i = 0; i < pStrTab->cStrings; i++)
+ {
+ uint64_t fCurScope = pStrTab->paStrings[i].fScope;
+ if ((fCurScope & RTMSGREFENTRYSTR_SCOPE_MASK) == RTMSGREFENTRYSTR_SCOPE_SAME)
+ {
+ fCurScope &= ~RTMSGREFENTRYSTR_SCOPE_MASK;
+ fCurScope |= (fPrevScope & RTMSGREFENTRYSTR_SCOPE_MASK);
+ }
+ if (fCurScope & RTMSGREFENTRYSTR_SCOPE_MASK & fScope)
+ {
+ const char *psz = pStrTab->paStrings[i].psz;
+ if (psz && !isEmptyString(psz))
+ {
+ while (cPendingBlankLines > 0 && RT_SUCCESS(rc))
+ {
+ cPendingBlankLines--;
+ rc = RTStrmPutCh(pStrm, '\n');
+ cLinesWritten++;
+ }
+ if (RT_SUCCESS(rc))
+ rc = printString(pStrm, psz, cchWidth, fCurScope & RTMSGREFENTRYSTR_FLAGS_MASK, &cLinesWritten);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ else
+ cPendingBlankLines++;
+ }
+ fPrevScope = fCurScope;
+ }
+
+ if (pcLinesWritten)
+ *pcLinesWritten += cLinesWritten;
+ if (pcPendingBlankLines)
+ *pcPendingBlankLines = cPendingBlankLines;
+ return rc;
+}
+
+
+RTDECL(int) RTMsgRefEntrySynopsisEx(PRTSTREAM pStrm, PCRTMSGREFENTRY pEntry, uint64_t fScope, uint32_t fFlags)
+{
+ AssertReturn(!(fFlags & ~RTMSGREFENTRY_SYNOPSIS_F_USAGE), VERR_INVALID_FLAGS);
+
+ if (!pStrm)
+ pStrm = g_pStdOut;
+ int rc = VINF_SUCCESS;
+ if (fFlags & RTMSGREFENTRY_SYNOPSIS_F_USAGE)
+ RTStrmPutStr(pStrm, "Usage: ");
+ if (RT_SUCCESS(rc))
+ rc = RTMsgRefEntryPrintStringTable(pStrm, &pEntry->Synopsis, fScope, NULL, NULL);
+ return rc;
+}
+
+
+RTDECL(int) RTMsgRefEntrySynopsis(PRTSTREAM pStrm, PCRTMSGREFENTRY pEntry)
+{
+ return RTMsgRefEntrySynopsisEx(pStrm, pEntry, UINT64_MAX, true /*fPrintUsage*/);
+}
+
+
+RTDECL(int) RTMsgRefEntryHelpEx(PRTSTREAM pStrm, PCRTMSGREFENTRY pEntry, uint64_t fScope, uint32_t fFlags)
+{
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+ if (!pStrm)
+ pStrm = g_pStdOut;
+ return RTMsgRefEntryPrintStringTable(pStrm, &pEntry->Help, fScope, NULL, NULL);
+}
+
+
+RTDECL(int) RTMsgRefEntryHelp(PRTSTREAM pStrm, PCRTMSGREFENTRY pEntry)
+{
+ return RTMsgRefEntryHelpEx(pStrm, pEntry, UINT64_MAX, 0 /*fFlags*/);
+}
+
diff --git a/src/VBox/Runtime/common/misc/once.cpp b/src/VBox/Runtime/common/misc/once.cpp
new file mode 100644
index 00000000..521d6971
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/once.cpp
@@ -0,0 +1,450 @@
+/* $Id: once.cpp $ */
+/** @file
+ * IPRT - Execute Once.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/once.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#ifdef IN_RING3
+# include <iprt/critsect.h>
+# define RTONCE_USE_CRITSECT_FOR_TERM
+#elif defined(IN_RING0)
+# include <iprt/spinlock.h>
+# define RTONCE_USE_SPINLOCK_FOR_TERM
+#else
+# define RTONCE_NO_TERM
+#endif
+#include <iprt/err.h>
+#include <iprt/initterm.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifndef RTONCE_NO_TERM
+/** For initializing the clean-up list code. */
+static RTONCE g_OnceCleanUp = RTONCE_INITIALIZER;
+/** Lock protecting the clean-up list. */
+#ifdef RTONCE_USE_CRITSECT_FOR_TERM
+static RTCRITSECT g_CleanUpCritSect;
+#else
+static RTSEMFASTMUTEX g_hCleanUpLock;
+#endif
+/** The clean-up list. */
+static RTLISTANCHOR g_CleanUpList;
+
+/** Locks the clean-up list. */
+#ifdef RTONCE_USE_CRITSECT_FOR_TERM
+# define RTONCE_CLEANUP_LOCK() RTCritSectEnter(&g_CleanUpCritSect)
+#else
+# define RTONCE_CLEANUP_LOCK() RTSemFastMutexRequest(g_hCleanUpLock);
+#endif
+
+/** Unlocks the clean-up list. */
+#ifdef RTONCE_USE_CRITSECT_FOR_TERM
+# define RTONCE_CLEANUP_UNLOCK() RTCritSectLeave(&g_CleanUpCritSect);
+#else
+# define RTONCE_CLEANUP_UNLOCK() RTSemFastMutexRelease(g_hCleanUpLock);
+#endif
+
+
+
+/** @callback_method_impl{FNRTTERMCALLBACK} */
+static DECLCALLBACK(void) rtOnceTermCallback(RTTERMREASON enmReason, int32_t iStatus, void *pvUser)
+{
+ bool const fLazyCleanUpOk = RTTERMREASON_IS_LAZY_CLEANUP_OK(enmReason);
+ RTONCE_CLEANUP_LOCK(); /* Potentially dangerous. */
+
+ PRTONCE pCur, pPrev;
+ RTListForEachReverseSafe(&g_CleanUpList, pCur, pPrev, RTONCE, CleanUpNode)
+ {
+ /*
+ * Mostly reset it before doing the callback.
+ *
+ * Should probably introduce some new states here, but I'm not sure
+ * it's really worth it at this point.
+ */
+ PFNRTONCECLEANUP pfnCleanUp = pCur->pfnCleanUp;
+ void *pvUserCleanUp = pCur->pvUser;
+ pCur->pvUser = NULL;
+ pCur->pfnCleanUp = NULL;
+ ASMAtomicWriteS32(&pCur->rc, VERR_WRONG_ORDER);
+
+ pfnCleanUp(pvUserCleanUp, fLazyCleanUpOk);
+
+ /*
+ * Reset the reset of the state if we're being unloaded or smth.
+ */
+ if (!fLazyCleanUpOk)
+ {
+ ASMAtomicWriteS32(&pCur->rc, VERR_INTERNAL_ERROR);
+ ASMAtomicWriteS32(&pCur->iState, RTONCESTATE_UNINITIALIZED);
+ }
+ }
+
+ RTONCE_CLEANUP_UNLOCK();
+
+ /*
+ * Reset our own structure and the critsect / mutex.
+ */
+ if (!fLazyCleanUpOk)
+ {
+# ifdef RTONCE_USE_CRITSECT_FOR_TERM
+ RTCritSectDelete(&g_CleanUpCritSect);
+# else
+ RTSemFastMutexDestroy(g_hCleanUpLock);
+ g_hCleanUpLock = NIL_RTSEMFASTMUTEX;
+# endif
+
+ ASMAtomicWriteS32(&g_OnceCleanUp.rc, VERR_INTERNAL_ERROR);
+ ASMAtomicWriteS32(&g_OnceCleanUp.iState, RTONCESTATE_UNINITIALIZED);
+ }
+
+ NOREF(pvUser); NOREF(iStatus);
+}
+
+
+
+/**
+ * Initializes the globals (using RTOnce).
+ *
+ * @returns IPRT status code
+ * @param pvUser Unused.
+ */
+static DECLCALLBACK(int32_t) rtOnceInitCleanUp(void *pvUser)
+{
+ NOREF(pvUser);
+ RTListInit(&g_CleanUpList);
+# ifdef RTONCE_USE_CRITSECT_FOR_TERM
+ int rc = RTCritSectInit(&g_CleanUpCritSect);
+# else
+ int rc = RTSemFastMutexCreate(&g_hCleanUpLock);
+# endif
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTTermRegisterCallback(rtOnceTermCallback, NULL);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+# ifdef RTONCE_USE_CRITSECT_FOR_TERM
+ RTCritSectDelete(&g_CleanUpCritSect);
+# else
+ RTSemFastMutexDestroy(g_hCleanUpLock);
+ g_hCleanUpLock = NIL_RTSEMFASTMUTEX;
+# endif
+ }
+ return rc;
+}
+
+#endif /* !RTONCE_NO_TERM */
+
+/**
+ * The state loop of the other threads.
+ *
+ * @returns VINF_SUCCESS when everything went smoothly. IPRT status code if we
+ * encountered trouble.
+ * @param pOnce The execute once structure.
+ * @param phEvtM Where to store the semaphore handle so the caller
+ * can do the cleaning up for us.
+ */
+static int rtOnceOtherThread(PRTONCE pOnce, PRTSEMEVENTMULTI phEvtM)
+{
+ uint32_t cYields = 0;
+ for (;;)
+ {
+ int32_t iState = ASMAtomicReadS32(&pOnce->iState);
+ switch (iState)
+ {
+ /*
+ * No semaphore, try create one.
+ */
+ case RTONCESTATE_BUSY_NO_SEM:
+ if (ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_CREATING_SEM, RTONCESTATE_BUSY_NO_SEM))
+ {
+ int rc = RTSemEventMultiCreate(phEvtM);
+ if (RT_SUCCESS(rc))
+ {
+ ASMAtomicWriteHandle(&pOnce->hEventMulti, *phEvtM);
+ int32_t cRefs = ASMAtomicIncS32(&pOnce->cEventRefs); Assert(cRefs == 1); NOREF(cRefs);
+
+ if (!ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_HAVE_SEM, RTONCESTATE_BUSY_CREATING_SEM))
+ {
+ /* Too slow. */
+ AssertReturn(ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_DONE_CREATING_SEM)
+ , VERR_INTERNAL_ERROR_5);
+
+ ASMAtomicWriteHandle(&pOnce->hEventMulti, NIL_RTSEMEVENTMULTI);
+ cRefs = ASMAtomicDecS32(&pOnce->cEventRefs); Assert(cRefs == 0);
+
+ RTSemEventMultiDestroy(*phEvtM);
+ *phEvtM = NIL_RTSEMEVENTMULTI;
+ }
+ }
+ else
+ {
+ AssertReturn( ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_SPIN, RTONCESTATE_BUSY_CREATING_SEM)
+ || ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_DONE_CREATING_SEM)
+ , VERR_INTERNAL_ERROR_4);
+ *phEvtM = NIL_RTSEMEVENTMULTI;
+ }
+ }
+ break;
+
+ /*
+ * This isn't nice, but it's the easy way out.
+ */
+ case RTONCESTATE_BUSY_CREATING_SEM:
+ case RTONCESTATE_BUSY_SPIN:
+ cYields++;
+ if (!(++cYields % 8))
+ RTThreadSleep(1);
+ else
+ RTThreadYield();
+ break;
+
+ /*
+ * There is a semaphore, try wait on it.
+ *
+ * We continue waiting after reaching DONE_HAVE_SEM if we
+ * already got the semaphore to avoid racing the first thread.
+ */
+ case RTONCESTATE_DONE_HAVE_SEM:
+ if (*phEvtM == NIL_RTSEMEVENTMULTI)
+ return VINF_SUCCESS;
+ RT_FALL_THRU();
+ case RTONCESTATE_BUSY_HAVE_SEM:
+ {
+ /*
+ * Grab the semaphore if we haven't got it yet.
+ * We must take care not to increment the counter if it
+ * is 0. This may happen if we're racing a state change.
+ */
+ if (*phEvtM == NIL_RTSEMEVENTMULTI)
+ {
+ int32_t cEventRefs = ASMAtomicUoReadS32(&pOnce->cEventRefs);
+ while ( cEventRefs > 0
+ && ASMAtomicUoReadS32(&pOnce->iState) == RTONCESTATE_BUSY_HAVE_SEM)
+ {
+ if (ASMAtomicCmpXchgExS32(&pOnce->cEventRefs, cEventRefs + 1, cEventRefs, &cEventRefs))
+ break;
+ ASMNopPause();
+ }
+ if (cEventRefs <= 0)
+ break;
+
+ ASMAtomicReadHandle(&pOnce->hEventMulti, phEvtM);
+ AssertReturn(*phEvtM != NIL_RTSEMEVENTMULTI, VERR_INTERNAL_ERROR_2);
+ }
+
+ /*
+ * We've got a sempahore, do the actual waiting.
+ */
+ do
+ RTSemEventMultiWaitNoResume(*phEvtM, RT_INDEFINITE_WAIT);
+ while (ASMAtomicReadS32(&pOnce->iState) == RTONCESTATE_BUSY_HAVE_SEM);
+ break;
+ }
+
+ case RTONCESTATE_DONE_CREATING_SEM:
+ case RTONCESTATE_DONE:
+ return VINF_SUCCESS;
+
+ default:
+ AssertMsgFailedReturn(("%d\n", iState), VERR_INTERNAL_ERROR_3);
+ }
+ }
+}
+
+
+RTDECL(int) RTOnceSlow(PRTONCE pOnce, PFNRTONCE pfnOnce, PFNRTONCECLEANUP pfnCleanUp, void *pvUser)
+{
+ /*
+ * Validate input (strict builds only).
+ */
+ AssertPtr(pOnce);
+ AssertPtr(pfnOnce);
+
+ /*
+ * Deal with the 'initialized' case first
+ */
+ int32_t iState = ASMAtomicUoReadS32(&pOnce->iState);
+ if (RT_LIKELY( iState == RTONCESTATE_DONE
+ || iState == RTONCESTATE_DONE_CREATING_SEM
+ || iState == RTONCESTATE_DONE_HAVE_SEM
+ ))
+ return ASMAtomicUoReadS32(&pOnce->rc);
+
+ AssertReturn( iState == RTONCESTATE_UNINITIALIZED
+ || iState == RTONCESTATE_BUSY_NO_SEM
+ || iState == RTONCESTATE_BUSY_SPIN
+ || iState == RTONCESTATE_BUSY_CREATING_SEM
+ || iState == RTONCESTATE_BUSY_HAVE_SEM
+ , VERR_INTERNAL_ERROR);
+
+#ifdef RTONCE_NO_TERM
+ AssertReturn(!pfnCleanUp, VERR_NOT_SUPPORTED);
+#else /* !RTONCE_NO_TERM */
+
+ /*
+ * Make sure our clean-up bits are working if needed later.
+ */
+ if (pfnCleanUp)
+ {
+ int rc = RTOnce(&g_OnceCleanUp, rtOnceInitCleanUp, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+#endif /* !RTONCE_NO_TERM */
+
+ /*
+ * Do we initialize it?
+ */
+ int32_t rcOnce;
+ if ( iState == RTONCESTATE_UNINITIALIZED
+ && ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_BUSY_NO_SEM, RTONCESTATE_UNINITIALIZED))
+ {
+ /*
+ * Yes, so do the execute once stuff.
+ */
+ rcOnce = pfnOnce(pvUser);
+ ASMAtomicWriteS32(&pOnce->rc, rcOnce);
+
+#ifndef RTONCE_NO_TERM
+ /*
+ * Register clean-up if requested and we were successful.
+ */
+ if (pfnCleanUp && RT_SUCCESS(rcOnce))
+ {
+ RTONCE_CLEANUP_LOCK();
+
+ pOnce->pfnCleanUp = pfnCleanUp;
+ pOnce->pvUser = pvUser;
+ RTListAppend(&g_CleanUpList, &pOnce->CleanUpNode);
+
+ RTONCE_CLEANUP_UNLOCK();
+ }
+#endif /* !RTONCE_NO_TERM */
+
+ /*
+ * If there is a sempahore to signal, we're in for some extra work here.
+ */
+ if ( !ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_BUSY_NO_SEM)
+ && !ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_BUSY_SPIN)
+ && !ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE_CREATING_SEM, RTONCESTATE_BUSY_CREATING_SEM)
+ )
+ {
+ /* Grab the sempahore by switching to 'DONE_HAVE_SEM' before reaching 'DONE'. */
+ AssertReturn(ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE_HAVE_SEM, RTONCESTATE_BUSY_HAVE_SEM),
+ VERR_INTERNAL_ERROR_2);
+
+ int32_t cRefs = ASMAtomicIncS32(&pOnce->cEventRefs);
+ Assert(cRefs > 1); NOREF(cRefs);
+
+ RTSEMEVENTMULTI hEvtM;
+ ASMAtomicReadHandle(&pOnce->hEventMulti, &hEvtM);
+ Assert(hEvtM != NIL_RTSEMEVENTMULTI);
+
+ ASMAtomicWriteS32(&pOnce->iState, RTONCESTATE_DONE);
+
+ /* Signal it and return. */
+ RTSemEventMultiSignal(hEvtM);
+ }
+ }
+ else
+ {
+ /*
+ * Wait for the first thread to complete. Delegate this to a helper
+ * function to simplify cleanup and keep things a bit shorter.
+ */
+ RTSEMEVENTMULTI hEvtM = NIL_RTSEMEVENTMULTI;
+ rcOnce = rtOnceOtherThread(pOnce, &hEvtM);
+ if (hEvtM != NIL_RTSEMEVENTMULTI)
+ {
+ if (ASMAtomicDecS32(&pOnce->cEventRefs) == 0)
+ {
+ bool fRc;
+ ASMAtomicCmpXchgHandle(&pOnce->hEventMulti, NIL_RTSEMEVENTMULTI, hEvtM, fRc); Assert(fRc);
+ fRc = ASMAtomicCmpXchgS32(&pOnce->iState, RTONCESTATE_DONE, RTONCESTATE_DONE_HAVE_SEM); Assert(fRc);
+ RTSemEventMultiDestroy(hEvtM);
+ }
+ }
+ if (RT_SUCCESS(rcOnce))
+ rcOnce = ASMAtomicUoReadS32(&pOnce->rc);
+ }
+
+ return rcOnce;
+}
+RT_EXPORT_SYMBOL(RTOnceSlow);
+
+
+RTDECL(void) RTOnceReset(PRTONCE pOnce)
+{
+ /* Cannot be done while busy! */
+ AssertPtr(pOnce);
+ Assert(pOnce->hEventMulti == NIL_RTSEMEVENTMULTI);
+ int32_t iState = ASMAtomicUoReadS32(&pOnce->iState);
+ AssertMsg( iState == RTONCESTATE_DONE
+ || iState == RTONCESTATE_UNINITIALIZED,
+ ("%d\n", iState));
+ NOREF(iState);
+
+#ifndef RTONCE_NO_TERM
+ /* Unregister clean-up. */
+ if (pOnce->pfnCleanUp)
+ {
+ RTONCE_CLEANUP_LOCK();
+
+ RTListNodeRemove(&pOnce->CleanUpNode);
+ pOnce->pfnCleanUp = NULL;
+ pOnce->pvUser = NULL;
+
+ RTONCE_CLEANUP_UNLOCK();
+ }
+#endif /* !RTONCE_NO_TERM */
+
+ /* Do the same as RTONCE_INITIALIZER does. */
+ ASMAtomicWriteS32(&pOnce->rc, VERR_INTERNAL_ERROR);
+ ASMAtomicWriteS32(&pOnce->iState, RTONCESTATE_UNINITIALIZED);
+}
+RT_EXPORT_SYMBOL(RTOnceReset);
+
diff --git a/src/VBox/Runtime/common/misc/req.cpp b/src/VBox/Runtime/common/misc/req.cpp
new file mode 100644
index 00000000..943ae84f
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/req.cpp
@@ -0,0 +1,537 @@
+/* $Id: req.cpp $ */
+/** @file
+ * IPRT - Request packets
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/req.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+
+#include "internal/req.h"
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Allocate a new request from the heap.
+ *
+ * @returns IPRT status code.
+ * @param enmType The reques type.
+ * @param fPoolOrQueue The owner type.
+ * @param pvOwner The owner.
+ * @param phReq Where to return the request handle.
+ */
+DECLHIDDEN(int) rtReqAlloc(RTREQTYPE enmType, bool fPoolOrQueue, void *pvOwner, PRTREQ *phReq)
+{
+ PRTREQ pReq = (PRTREQ)RTMemAllocZ(sizeof(*pReq));
+ if (RT_UNLIKELY(!pReq))
+ return VERR_NO_MEMORY;
+
+ /*
+ * Create the semaphore used for waiting.
+ */
+ int rc = RTSemEventCreate(&pReq->EventSem);
+ AssertRCReturnStmt(rc, RTMemFree(pReq), rc);
+
+ /*
+ * Initialize the packet and return it.
+ */
+ pReq->u32Magic = RTREQ_MAGIC;
+ pReq->fEventSemClear = true;
+ pReq->fSignalPushBack = true;
+ pReq->fPoolOrQueue = fPoolOrQueue;
+ pReq->iStatusX = VERR_RT_REQUEST_STATUS_STILL_PENDING;
+ pReq->enmState = RTREQSTATE_ALLOCATED;
+ pReq->pNext = NULL;
+ pReq->uOwner.pv = pvOwner;
+ pReq->fFlags = RTREQFLAGS_IPRT_STATUS;
+ pReq->enmType = enmType;
+ pReq->cRefs = 1;
+
+ *phReq = pReq;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Re-initializes a request when it's being recycled.
+ *
+ * @returns IRPT status code, the request is freed on failure.
+ * @param pReq The request.
+ * @param enmType The request type.
+ */
+DECLHIDDEN(int) rtReqReInit(PRTREQINT pReq, RTREQTYPE enmType)
+{
+ Assert(pReq->u32Magic == RTREQ_MAGIC);
+ Assert(pReq->enmType == RTREQTYPE_INVALID);
+ Assert(pReq->enmState == RTREQSTATE_FREE);
+ Assert(pReq->cRefs == 0);
+
+ /*
+ * Make sure the event sem is not signaled.
+ */
+ if (!pReq->fEventSemClear)
+ {
+ int rc = RTSemEventWait(pReq->EventSem, 0);
+ if (rc != VINF_SUCCESS && rc != VERR_TIMEOUT)
+ {
+ /*
+ * This shall not happen, but if it does we'll just destroy
+ * the semaphore and create a new one.
+ */
+ AssertMsgFailed(("rc=%Rrc from RTSemEventWait(%#x).\n", rc, pReq->EventSem));
+ RTSemEventDestroy(pReq->EventSem);
+ rc = RTSemEventCreate(&pReq->EventSem);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ pReq->EventSem = NIL_RTSEMEVENT;
+ rtReqFreeIt(pReq);
+ return rc;
+ }
+ }
+ pReq->fEventSemClear = true;
+ }
+ else
+ Assert(RTSemEventWait(pReq->EventSem, 0) == VERR_TIMEOUT);
+
+ /*
+ * Initialize the packet and return it.
+ */
+ ASMAtomicWriteNullPtr(&pReq->pNext);
+ pReq->iStatusX = VERR_RT_REQUEST_STATUS_STILL_PENDING;
+ pReq->enmState = RTREQSTATE_ALLOCATED;
+ pReq->fFlags = RTREQFLAGS_IPRT_STATUS;
+ pReq->enmType = enmType;
+ pReq->cRefs = 1;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(uint32_t) RTReqRetain(PRTREQ hReq)
+{
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, UINT32_MAX);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, UINT32_MAX);
+
+ return ASMAtomicIncU32(&pReq->cRefs);
+}
+RT_EXPORT_SYMBOL(RTReqRetain);
+
+
+/**
+ * Frees a request.
+ *
+ * @param pReq The request.
+ */
+DECLHIDDEN(void) rtReqFreeIt(PRTREQINT pReq)
+{
+ Assert(pReq->u32Magic == RTREQ_MAGIC);
+ Assert(pReq->cRefs == 0);
+
+ pReq->u32Magic = RTREQ_MAGIC_DEAD;
+ RTSemEventDestroy(pReq->EventSem);
+ pReq->EventSem = NIL_RTSEMEVENT;
+ RTSemEventMultiDestroy(pReq->hPushBackEvt);
+ pReq->hPushBackEvt = NIL_RTSEMEVENTMULTI;
+ RTMemFree(pReq);
+}
+
+
+RTDECL(uint32_t) RTReqRelease(PRTREQ hReq)
+{
+ /*
+ * Ignore NULL and validate the request.
+ */
+ if (!hReq)
+ return 0;
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, UINT32_MAX);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, UINT32_MAX);
+
+ /*
+ * Drop a reference, recycle the request when we reach 0.
+ */
+ uint32_t cRefs = ASMAtomicDecU32(&pReq->cRefs);
+ if (cRefs == 0)
+ {
+ /*
+ * Check packet state.
+ */
+ RTREQSTATE const enmState = pReq->enmState;
+ switch (enmState)
+ {
+ case RTREQSTATE_ALLOCATED:
+ case RTREQSTATE_COMPLETED:
+ break;
+ default:
+ AssertMsgFailedReturn(("Invalid state %d!\n", enmState), 0);
+ }
+
+ /*
+ * Make it a free packet and put it into one of the free packet lists.
+ */
+ pReq->enmState = RTREQSTATE_FREE;
+ pReq->iStatusX = VERR_RT_REQUEST_STATUS_FREED;
+ pReq->enmType = RTREQTYPE_INVALID;
+
+ bool fRecycled;
+ if (pReq->fPoolOrQueue)
+ fRecycled = rtReqPoolRecycle(pReq->uOwner.hPool, pReq);
+ else
+ fRecycled = rtReqQueueRecycle(pReq->uOwner.hQueue, pReq);
+ if (!fRecycled)
+ rtReqFreeIt(pReq);
+ }
+
+ return cRefs;
+}
+RT_EXPORT_SYMBOL(RTReqRelease);
+
+
+RTDECL(int) RTReqSubmit(PRTREQ hReq, RTMSINTERVAL cMillies)
+{
+ LogFlow(("RTReqSubmit: hReq=%p cMillies=%d\n", hReq, cMillies));
+
+ /*
+ * Verify the supplied package.
+ */
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
+ AssertMsgReturn(pReq->enmState == RTREQSTATE_ALLOCATED, ("%d\n", pReq->enmState), VERR_RT_REQUEST_STATE);
+ AssertMsgReturn(pReq->uOwner.hQueue && !pReq->pNext && pReq->EventSem != NIL_RTSEMEVENT,
+ ("Invalid request package! Anyone cooking their own packages???\n"),
+ VERR_RT_REQUEST_INVALID_PACKAGE);
+ AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
+ ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
+ pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
+ VERR_RT_REQUEST_INVALID_TYPE);
+
+ /*
+ * Insert it. Always grab a reference for the queue (we used to
+ * donate the caller's reference in the NO_WAIT case once upon a time).
+ */
+ pReq->uSubmitNanoTs = RTTimeNanoTS();
+ pReq->enmState = RTREQSTATE_QUEUED;
+ unsigned fFlags = ((RTREQ volatile *)pReq)->fFlags; /* volatile paranoia */
+ RTReqRetain(pReq);
+
+ if (!pReq->fPoolOrQueue)
+ rtReqQueueSubmit(pReq->uOwner.hQueue, pReq);
+ else
+ rtReqPoolSubmit(pReq->uOwner.hPool, pReq);
+
+ /*
+ * Wait and return.
+ */
+ int rc = VINF_SUCCESS;
+ if (!(fFlags & RTREQFLAGS_NO_WAIT))
+ rc = RTReqWait(pReq, cMillies);
+
+ LogFlow(("RTReqSubmit: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqSubmit);
+
+
+RTDECL(int) RTReqWait(PRTREQ hReq, RTMSINTERVAL cMillies)
+{
+ LogFlow(("RTReqWait: hReq=%p cMillies=%d\n", hReq, cMillies));
+
+ /*
+ * Verify the supplied package.
+ */
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
+ RTREQSTATE enmState = pReq->enmState;
+ AssertMsgReturn( enmState == RTREQSTATE_QUEUED
+ || enmState == RTREQSTATE_PROCESSING
+ || enmState == RTREQSTATE_COMPLETED
+ || enmState == RTREQSTATE_CANCELLED,
+ ("Invalid state %d\n", enmState),
+ VERR_RT_REQUEST_STATE);
+ AssertMsgReturn(pReq->uOwner.hQueue && pReq->EventSem != NIL_RTSEMEVENT,
+ ("Invalid request package! Anyone cooking their own packages???\n"),
+ VERR_RT_REQUEST_INVALID_PACKAGE);
+ AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
+ ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
+ pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
+ VERR_RT_REQUEST_INVALID_TYPE);
+
+ /*
+ * Wait on the package.
+ */
+ int rc;
+ if (cMillies != RT_INDEFINITE_WAIT)
+ rc = RTSemEventWait(pReq->EventSem, cMillies);
+ else
+ {
+ do
+ {
+ rc = RTSemEventWait(pReq->EventSem, RT_INDEFINITE_WAIT);
+ Assert(rc != VERR_TIMEOUT);
+ } while (pReq->enmState != RTREQSTATE_COMPLETED);
+ }
+ if (rc == VINF_SUCCESS)
+ ASMAtomicWriteBool(&pReq->fEventSemClear, true);
+ if (pReq->enmState == RTREQSTATE_COMPLETED)
+ rc = VINF_SUCCESS;
+ LogFlow(("RTReqWait: returns %Rrc\n", rc));
+ Assert(rc != VERR_INTERRUPTED);
+ Assert(pReq->cRefs >= 1);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqWait);
+
+
+RTDECL(int) RTReqCancel(PRTREQ hReq)
+{
+ LogFlow(("RTReqCancel: hReq=%p\n", hReq));
+
+ /*
+ * Verify the supplied package.
+ */
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, VERR_INVALID_HANDLE);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_HANDLE);
+ AssertMsgReturn(pReq->uOwner.hQueue && pReq->EventSem != NIL_RTSEMEVENT,
+ ("Invalid request package! Anyone cooking their own packages???\n"),
+ VERR_RT_REQUEST_INVALID_PACKAGE);
+ AssertMsgReturn(pReq->enmType > RTREQTYPE_INVALID && pReq->enmType < RTREQTYPE_MAX,
+ ("Invalid package type %d valid range %d-%d inclusively. This was verified on alloc too...\n",
+ pReq->enmType, RTREQTYPE_INVALID + 1, RTREQTYPE_MAX - 1),
+ VERR_RT_REQUEST_INVALID_TYPE);
+
+ /*
+ * Try cancel the request itself by changing its state.
+ */
+ int rc;
+ if (ASMAtomicCmpXchgU32((uint32_t volatile *)&pReq->enmState, RTREQSTATE_CANCELLED, RTREQSTATE_QUEUED))
+ {
+ if (pReq->fPoolOrQueue)
+ rtReqPoolCancel(pReq->uOwner.hPool, pReq);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ Assert(pReq->enmState == RTREQSTATE_PROCESSING || pReq->enmState == RTREQSTATE_COMPLETED);
+ rc = VERR_RT_REQUEST_STATE;
+ }
+
+ LogFlow(("RTReqCancel: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqCancel);
+
+
+RTDECL(int) RTReqGetStatus(PRTREQ hReq)
+{
+ PRTREQINT pReq = hReq;
+ AssertPtrReturn(pReq, VERR_INVALID_POINTER);
+ AssertReturn(pReq->u32Magic == RTREQ_MAGIC, VERR_INVALID_POINTER);
+ return pReq->iStatusX;
+}
+RT_EXPORT_SYMBOL(RTReqGetStatus);
+
+
+
+/**
+ * Process one request.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pReq Request packet to process.
+ */
+DECLHIDDEN(int) rtReqProcessOne(PRTREQINT pReq)
+{
+ LogFlow(("rtReqProcessOne: pReq=%p type=%d fFlags=%#x\n", pReq, pReq->enmType, pReq->fFlags));
+
+ /*
+ * Try switch the request status to processing.
+ */
+ int rcRet = VINF_SUCCESS; /* the return code of this function. */
+ int rcReq = VERR_NOT_IMPLEMENTED; /* the request status. */
+ if (ASMAtomicCmpXchgU32((uint32_t volatile *)&pReq->enmState, RTREQSTATE_PROCESSING, RTREQSTATE_QUEUED))
+ {
+ /*
+ * Process the request.
+ */
+ pReq->enmState = RTREQSTATE_PROCESSING;
+ switch (pReq->enmType)
+ {
+ /*
+ * A packed down call frame.
+ */
+ case RTREQTYPE_INTERNAL:
+ {
+ uintptr_t *pauArgs = &pReq->u.Internal.aArgs[0];
+ union
+ {
+ PFNRT pfn;
+ DECLCALLBACKMEMBER(int, pfn00,(void));
+ DECLCALLBACKMEMBER(int, pfn01,(uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn02,(uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn03,(uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn04,(uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn05,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn06,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn07,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn08,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn09,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn10,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn11,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ DECLCALLBACKMEMBER(int, pfn12,(uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t, uintptr_t));
+ } u;
+ u.pfn = pReq->u.Internal.pfn;
+#ifndef RT_ARCH_X86
+ switch (pReq->u.Internal.cArgs)
+ {
+ case 0: rcRet = u.pfn00(); break;
+ case 1: rcRet = u.pfn01(pauArgs[0]); break;
+ case 2: rcRet = u.pfn02(pauArgs[0], pauArgs[1]); break;
+ case 3: rcRet = u.pfn03(pauArgs[0], pauArgs[1], pauArgs[2]); break;
+ case 4: rcRet = u.pfn04(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3]); break;
+ case 5: rcRet = u.pfn05(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4]); break;
+ case 6: rcRet = u.pfn06(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5]); break;
+ case 7: rcRet = u.pfn07(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6]); break;
+ case 8: rcRet = u.pfn08(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7]); break;
+ case 9: rcRet = u.pfn09(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8]); break;
+ case 10: rcRet = u.pfn10(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9]); break;
+ case 11: rcRet = u.pfn11(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9], pauArgs[10]); break;
+ case 12: rcRet = u.pfn12(pauArgs[0], pauArgs[1], pauArgs[2], pauArgs[3], pauArgs[4], pauArgs[5], pauArgs[6], pauArgs[7], pauArgs[8], pauArgs[9], pauArgs[10], pauArgs[11]); break;
+ default:
+ AssertReleaseMsgFailed(("cArgs=%d\n", pReq->u.Internal.cArgs));
+ rcRet = rcReq = VERR_INTERNAL_ERROR;
+ break;
+ }
+#else /* RT_ARCH_X86 */
+ size_t cbArgs = pReq->u.Internal.cArgs * sizeof(uintptr_t);
+# ifdef __GNUC__
+ __asm__ __volatile__("movl %%esp, %%edx\n\t"
+ "subl %2, %%esp\n\t"
+ "andl $0xfffffff0, %%esp\n\t"
+ "shrl $2, %2\n\t"
+ "movl %%esp, %%edi\n\t"
+ "rep movsl\n\t"
+ "movl %%edx, %%edi\n\t"
+ "call *%%eax\n\t"
+ "mov %%edi, %%esp\n\t"
+ : "=a" (rcRet),
+ "=S" (pauArgs),
+ "=c" (cbArgs)
+ : "0" (u.pfn),
+ "1" (pauArgs),
+ "2" (cbArgs)
+ : "edi", "edx");
+# else
+ __asm
+ {
+ xor edx, edx /* just mess it up. */
+ mov eax, u.pfn
+ mov ecx, cbArgs
+ shr ecx, 2
+ mov esi, pauArgs
+ mov ebx, esp
+ sub esp, cbArgs
+ and esp, 0xfffffff0
+ mov edi, esp
+ rep movsd
+ call eax
+ mov esp, ebx
+ mov rcRet, eax
+ }
+# endif
+#endif /* RT_ARCH_X86 */
+ if ((pReq->fFlags & (RTREQFLAGS_RETURN_MASK)) == RTREQFLAGS_VOID)
+ rcRet = VINF_SUCCESS;
+ rcReq = rcRet;
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("pReq->enmType=%d\n", pReq->enmType));
+ rcReq = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+ }
+ else
+ {
+ Assert(pReq->enmState == RTREQSTATE_CANCELLED);
+ rcReq = VERR_CANCELLED;
+ }
+
+ /*
+ * Complete the request and then release our request handle reference.
+ */
+ pReq->iStatusX = rcReq;
+ pReq->enmState = RTREQSTATE_COMPLETED;
+ if (pReq->fFlags & RTREQFLAGS_NO_WAIT)
+ LogFlow(("rtReqProcessOne: Completed request %p: rcReq=%Rrc rcRet=%Rrc (no wait)\n",
+ pReq, rcReq, rcRet));
+ else
+ {
+ /* Notify the waiting thread. */
+ LogFlow(("rtReqProcessOne: Completed request %p: rcReq=%Rrc rcRet=%Rrc - notifying waiting thread\n",
+ pReq, rcReq, rcRet));
+ ASMAtomicWriteBool(&pReq->fEventSemClear, false);
+ int rc2 = RTSemEventSignal(pReq->EventSem);
+ if (rc2 != VINF_SUCCESS)
+ {
+ AssertRC(rc2);
+ rcRet = rc2;
+ }
+ }
+ RTReqRelease(pReq);
+ return rcRet;
+}
+
diff --git a/src/VBox/Runtime/common/misc/reqpool.cpp b/src/VBox/Runtime/common/misc/reqpool.cpp
new file mode 100644
index 00000000..1489c5b7
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/reqpool.cpp
@@ -0,0 +1,1299 @@
+/* $Id: reqpool.cpp $ */
+/** @file
+ * IPRT - Request Pool.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/req.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/err.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+#include "internal/req.h"
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The max number of worker threads. */
+#define RTREQPOOL_MAX_THREADS UINT32_C(16384)
+/** The max number of milliseconds to push back. */
+#define RTREQPOOL_PUSH_BACK_MAX_MS RT_MS_1MIN
+/** The max number of free requests to keep around. */
+#define RTREQPOOL_MAX_FREE_REQUESTS (RTREQPOOL_MAX_THREADS * 2U)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RTREQPOOLTHREAD
+{
+ /** Node in the RTREQPOOLINT::IdleThreads list. */
+ RTLISTNODE IdleNode;
+ /** Node in the RTREQPOOLINT::WorkerThreads list. */
+ RTLISTNODE ListNode;
+
+ /** The submit timestamp of the pending request. */
+ uint64_t uPendingNanoTs;
+ /** The submit timestamp of the request processing. */
+ uint64_t uProcessingNanoTs;
+ /** When this CPU went idle the last time. */
+ uint64_t uIdleNanoTs;
+ /** The number of requests processed by this thread. */
+ uint64_t cReqProcessed;
+ /** Total time the requests processed by this thread took to process. */
+ uint64_t cNsTotalReqProcessing;
+ /** Total time the requests processed by this thread had to wait in
+ * the queue before being scheduled. */
+ uint64_t cNsTotalReqQueued;
+ /** The CPU this was scheduled last time we checked. */
+ RTCPUID idLastCpu;
+
+ /** The submitter will put an incoming request here when scheduling an idle
+ * thread. */
+ PRTREQINT volatile pTodoReq;
+ /** The request the thread is currently processing. */
+ PRTREQINT volatile pPendingReq;
+
+ /** The thread handle. */
+ RTTHREAD hThread;
+ /** Nano seconds timestamp representing the birth time of the thread. */
+ uint64_t uBirthNanoTs;
+ /** Pointer to the request thread pool instance the thread is associated
+ * with. */
+ struct RTREQPOOLINT *pPool;
+} RTREQPOOLTHREAD;
+/** Pointer to a worker thread. */
+typedef RTREQPOOLTHREAD *PRTREQPOOLTHREAD;
+
+/**
+ * Request thread pool instance data.
+ */
+typedef struct RTREQPOOLINT
+{
+ /** Magic value (RTREQPOOL_MAGIC). */
+ uint32_t u32Magic;
+ /** The request pool name. */
+ char szName[12];
+
+ /** @name Config
+ * @{ */
+ /** The worker thread type. */
+ RTTHREADTYPE enmThreadType;
+ /** The work thread flags (RTTHREADFLAGS). */
+ uint32_t fThreadFlags;
+ /** The maximum number of worker threads. */
+ uint32_t cMaxThreads;
+ /** The minimum number of worker threads. */
+ uint32_t cMinThreads;
+ /** The number of milliseconds a thread needs to be idle before it is
+ * considered for retirement. */
+ uint32_t cMsMinIdle;
+ /** cMsMinIdle in nano seconds. */
+ uint64_t cNsMinIdle;
+ /** The idle thread sleep interval in milliseconds. */
+ RTMSINTERVAL cMsIdleSleep;
+ /** The number of threads which should be spawned before throttling kicks
+ * in. */
+ uint32_t cThreadsPushBackThreshold;
+ /** The max number of milliseconds to push back a submitter before creating
+ * a new worker thread once the threshold has been reached. */
+ uint32_t cMsMaxPushBack;
+ /** The minimum number of milliseconds to push back a submitter before
+ * creating a new worker thread once the threshold has been reached. */
+ uint32_t cMsMinPushBack;
+ /** The max number of free requests in the recycle LIFO. */
+ uint32_t cMaxFreeRequests;
+ /** @} */
+
+ /** Signaled by terminating worker threads. */
+ RTSEMEVENTMULTI hThreadTermEvt;
+
+ /** Destruction indicator. The worker threads checks in their loop. */
+ bool volatile fDestructing;
+
+ /** The current submitter push back in milliseconds.
+ * This is recalculated when worker threads come and go. */
+ uint32_t cMsCurPushBack;
+ /** The current number of worker threads. */
+ uint32_t cCurThreads;
+ /** Statistics: The total number of threads created. */
+ uint32_t cThreadsCreated;
+ /** Statistics: The timestamp when the last thread was created. */
+ uint64_t uLastThreadCreateNanoTs;
+ /** Linked list of worker threads. */
+ RTLISTANCHOR WorkerThreads;
+
+ /** The number of requests processed and counted in the time totals. */
+ uint64_t cReqProcessed;
+ /** Total time the requests processed by this thread took to process. */
+ uint64_t cNsTotalReqProcessing;
+ /** Total time the requests processed by this thread had to wait in
+ * the queue before being scheduled. */
+ uint64_t cNsTotalReqQueued;
+
+ /** Reference counter. */
+ uint32_t volatile cRefs;
+ /** The number of idle thread or threads in the process of becoming
+ * idle. This is increased before the to-be-idle thread tries to enter
+ * the critical section and add itself to the list. */
+ uint32_t volatile cIdleThreads;
+ /** Linked list of idle threads. */
+ RTLISTANCHOR IdleThreads;
+
+ /** Head of the request FIFO. */
+ PRTREQINT pPendingRequests;
+ /** Where to insert the next request. */
+ PRTREQINT *ppPendingRequests;
+ /** The number of requests currently pending. */
+ uint32_t cCurPendingRequests;
+ /** The number of requests currently being executed. */
+ uint32_t volatile cCurActiveRequests;
+ /** The number of requests submitted. */
+ uint64_t cReqSubmitted;
+ /** The number of cancelled. */
+ uint64_t cReqCancelled;
+
+ /** Head of the request recycling LIFO. */
+ PRTREQINT pFreeRequests;
+ /** The number of requests in the recycling LIFO. This is read without
+ * entering the critical section, thus volatile. */
+ uint32_t volatile cCurFreeRequests;
+
+ /** Critical section serializing access to members of this structure. */
+ RTCRITSECT CritSect;
+
+} RTREQPOOLINT;
+
+
+/**
+ * Used by exiting thread and the pool destruction code to cancel unexpected
+ * requests.
+ *
+ * @param pReq The request.
+ */
+static void rtReqPoolCancelReq(PRTREQINT pReq)
+{
+ pReq->uOwner.hPool = NIL_RTREQPOOL; /* force free */
+ pReq->enmState = RTREQSTATE_COMPLETED;
+ ASMAtomicWriteS32(&pReq->iStatusX, VERR_CANCELLED);
+ if (pReq->hPushBackEvt != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiSignal(pReq->hPushBackEvt);
+ RTSemEventSignal(pReq->EventSem);
+
+ RTReqRelease(pReq);
+}
+
+
+/**
+ * Recalculate the max pushback interval when adding or removing worker threads.
+ *
+ * @param pPool The pool. cMsCurPushBack will be changed.
+ */
+static void rtReqPoolRecalcPushBack(PRTREQPOOLINT pPool)
+{
+ uint32_t const cMsRange = pPool->cMsMaxPushBack - pPool->cMsMinPushBack;
+ uint32_t const cSteps = pPool->cMaxThreads - pPool->cThreadsPushBackThreshold;
+ uint32_t const iStep = pPool->cCurThreads - pPool->cThreadsPushBackThreshold;
+
+ uint32_t cMsCurPushBack;
+ if (cSteps == 0 /* disabled */)
+ cMsCurPushBack = 0;
+ else if ((cMsRange >> 2) >= cSteps)
+ cMsCurPushBack = cMsRange / cSteps * iStep;
+ else
+ cMsCurPushBack = (uint32_t)( (uint64_t)cMsRange * RT_NS_1MS / cSteps * iStep / RT_NS_1MS );
+ cMsCurPushBack += pPool->cMsMinPushBack;
+
+ pPool->cMsCurPushBack = cMsCurPushBack;
+}
+
+
+
+/**
+ * Performs thread exit.
+ *
+ * @returns Thread termination status code (VINF_SUCCESS).
+ * @param pPool The pool.
+ * @param pThread The thread.
+ * @param fLocked Whether we are inside the critical section
+ * already.
+ */
+static int rtReqPoolThreadExit(PRTREQPOOLINT pPool, PRTREQPOOLTHREAD pThread, bool fLocked)
+{
+ if (!fLocked)
+ RTCritSectEnter(&pPool->CritSect);
+
+ /* Get out of the idle list. */
+ if (!RTListIsEmpty(&pThread->IdleNode))
+ {
+ RTListNodeRemove(&pThread->IdleNode);
+ Assert(pPool->cIdleThreads > 0);
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+ }
+
+ /* Get out of the thread list. */
+ RTListNodeRemove(&pThread->ListNode);
+ Assert(pPool->cCurThreads > 0);
+ pPool->cCurThreads--;
+ rtReqPoolRecalcPushBack(pPool);
+
+ /* This shouldn't happen... */
+ PRTREQINT pReq = pThread->pTodoReq;
+ if (pReq)
+ {
+ AssertFailed();
+ pThread->pTodoReq = NULL;
+ rtReqPoolCancelReq(pReq);
+ }
+
+ /* If we're the last thread terminating, ping the destruction thread before
+ we leave the critical section. */
+ if ( RTListIsEmpty(&pPool->WorkerThreads)
+ && pPool->hThreadTermEvt != NIL_RTSEMEVENT)
+ RTSemEventMultiSignal(pPool->hThreadTermEvt);
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ RTMemFree(pThread);
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Process one request.
+ *
+ * @param pPool The pool.
+ * @param pThread The worker thread.
+ * @param pReq The request to process.
+ */
+static void rtReqPoolThreadProcessRequest(PRTREQPOOLINT pPool, PRTREQPOOLTHREAD pThread, PRTREQINT pReq)
+{
+ /*
+ * Update thread state.
+ */
+ pThread->uProcessingNanoTs = RTTimeNanoTS();
+ pThread->uPendingNanoTs = pReq->uSubmitNanoTs;
+ pThread->pPendingReq = pReq;
+ ASMAtomicIncU32(&pPool->cCurActiveRequests);
+ Assert(pReq->u32Magic == RTREQ_MAGIC);
+
+ /*
+ * Do the actual processing.
+ */
+ rtReqProcessOne(pReq);
+
+ /*
+ * Update thread statistics and state.
+ */
+ ASMAtomicDecU32(&pPool->cCurActiveRequests);
+ pThread->pPendingReq = NULL;
+ uint64_t const uNsTsEnd = RTTimeNanoTS();
+ pThread->cNsTotalReqProcessing += uNsTsEnd - pThread->uProcessingNanoTs;
+ pThread->cNsTotalReqQueued += pThread->uProcessingNanoTs - pThread->uPendingNanoTs;
+ pThread->cReqProcessed++;
+}
+
+
+
+/**
+ * The Worker Thread Procedure.
+ *
+ * @returns VINF_SUCCESS.
+ * @param hThreadSelf The thread handle (unused).
+ * @param pvArg Pointer to the thread data.
+ */
+static DECLCALLBACK(int) rtReqPoolThreadProc(RTTHREAD hThreadSelf, void *pvArg)
+{
+ PRTREQPOOLTHREAD pThread = (PRTREQPOOLTHREAD)pvArg;
+ PRTREQPOOLINT pPool = pThread->pPool;
+
+ /*
+ * The work loop.
+ */
+ uint64_t cReqPrevProcessedIdle = UINT64_MAX;
+ uint64_t cReqPrevProcessedStat = 0;
+ uint64_t cNsPrevTotalReqProcessing = 0;
+ uint64_t cNsPrevTotalReqQueued = 0;
+ while (!pPool->fDestructing)
+ {
+ /*
+ * Process pending work.
+ */
+
+ /* Check if anything is scheduled directly to us. */
+ PRTREQINT pReq = ASMAtomicXchgPtrT(&pThread->pTodoReq, NULL, PRTREQINT);
+ if (pReq)
+ {
+ Assert(RTListIsEmpty(&pThread->IdleNode)); /* Must not be in the idle list. */
+ rtReqPoolThreadProcessRequest(pPool, pThread, pReq);
+ continue;
+ }
+
+ ASMAtomicIncU32(&pPool->cIdleThreads);
+ RTCritSectEnter(&pPool->CritSect);
+
+ /* Update the global statistics. */
+ if (cReqPrevProcessedStat != pThread->cReqProcessed)
+ {
+ pPool->cReqProcessed += pThread->cReqProcessed - cReqPrevProcessedStat;
+ cReqPrevProcessedStat = pThread->cReqProcessed;
+ pPool->cNsTotalReqProcessing += pThread->cNsTotalReqProcessing - cNsPrevTotalReqProcessing;
+ cNsPrevTotalReqProcessing = pThread->cNsTotalReqProcessing;
+ pPool->cNsTotalReqQueued += pThread->cNsTotalReqQueued - cNsPrevTotalReqQueued;
+ cNsPrevTotalReqQueued = pThread->cNsTotalReqQueued;
+ }
+
+ /* Recheck the todo request pointer after entering the critsect. */
+ pReq = ASMAtomicXchgPtrT(&pThread->pTodoReq, NULL, PRTREQINT);
+ if (pReq)
+ {
+ Assert(RTListIsEmpty(&pThread->IdleNode)); /* Must not be in the idle list. */
+ RTCritSectLeave(&pPool->CritSect);
+
+ rtReqPoolThreadProcessRequest(pPool, pThread, pReq);
+ continue;
+ }
+
+ /* Any pending requests in the queue? */
+ pReq = pPool->pPendingRequests;
+ if (pReq)
+ {
+ pPool->pPendingRequests = pReq->pNext;
+ if (pReq->pNext == NULL)
+ pPool->ppPendingRequests = &pPool->pPendingRequests;
+ Assert(pPool->cCurPendingRequests > 0);
+ pPool->cCurPendingRequests--;
+
+ /* Un-idle ourselves and process the request. */
+ if (!RTListIsEmpty(&pThread->IdleNode))
+ {
+ RTListNodeRemove(&pThread->IdleNode);
+ RTListInit(&pThread->IdleNode);
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+ }
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+ RTCritSectLeave(&pPool->CritSect);
+
+ rtReqPoolThreadProcessRequest(pPool, pThread, pReq);
+ continue;
+ }
+
+ /*
+ * Nothing to do, go idle.
+ */
+ if (cReqPrevProcessedIdle != pThread->cReqProcessed)
+ {
+ cReqPrevProcessedIdle = pThread->cReqProcessed;
+ pThread->uIdleNanoTs = RTTimeNanoTS();
+ }
+ else if (pPool->cCurThreads > pPool->cMinThreads)
+ {
+ uint64_t cNsIdle = RTTimeNanoTS() - pThread->uIdleNanoTs;
+ if (cNsIdle >= pPool->cNsMinIdle)
+ return rtReqPoolThreadExit(pPool, pThread, true /*fLocked*/);
+ }
+
+ if (RTListIsEmpty(&pThread->IdleNode))
+ RTListPrepend(&pPool->IdleThreads, &pThread->IdleNode);
+ else
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+ RTThreadUserReset(hThreadSelf);
+ uint32_t const cMsSleep = pPool->cMsIdleSleep;
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ RTThreadUserWait(hThreadSelf, cMsSleep);
+ }
+
+ return rtReqPoolThreadExit(pPool, pThread, false /*fLocked*/);
+}
+
+
+/**
+ * Create a new worker thread.
+ *
+ * @param pPool The pool needing new worker thread.
+ * @remarks Caller owns the critical section
+ */
+static void rtReqPoolCreateNewWorker(RTREQPOOL pPool)
+{
+ PRTREQPOOLTHREAD pThread = (PRTREQPOOLTHREAD)RTMemAllocZ(sizeof(RTREQPOOLTHREAD));
+ if (!pThread)
+ return;
+
+ pThread->uBirthNanoTs = RTTimeNanoTS();
+ pThread->pPool = pPool;
+ pThread->idLastCpu = NIL_RTCPUID;
+ pThread->hThread = NIL_RTTHREAD;
+ RTListInit(&pThread->IdleNode);
+ RTListAppend(&pPool->WorkerThreads, &pThread->ListNode);
+ pPool->cCurThreads++;
+ pPool->cThreadsCreated++;
+
+ int rc = RTThreadCreateF(&pThread->hThread, rtReqPoolThreadProc, pThread, 0 /*default stack size*/,
+ pPool->enmThreadType, pPool->fThreadFlags, "%s%02u", pPool->szName, pPool->cThreadsCreated);
+ if (RT_SUCCESS(rc))
+ pPool->uLastThreadCreateNanoTs = pThread->uBirthNanoTs;
+ else
+ {
+ pPool->cCurThreads--;
+ RTListNodeRemove(&pThread->ListNode);
+ RTMemFree(pThread);
+ }
+}
+
+
+/**
+ * Repel the submitter, giving the worker threads a chance to process the
+ * incoming request.
+ *
+ * @returns Success if a worker picked up the request, failure if not. The
+ * critical section has been left on success, while we'll be inside it
+ * on failure.
+ * @param pPool The pool.
+ * @param pReq The incoming request.
+ */
+static int rtReqPoolPushBack(PRTREQPOOLINT pPool, PRTREQINT pReq)
+{
+ /*
+ * Lazily create the push back semaphore that we'll be blociing on.
+ */
+ int rc;
+ RTSEMEVENTMULTI hEvt = pReq->hPushBackEvt;
+ if (hEvt == NIL_RTSEMEVENTMULTI)
+ {
+ rc = RTSemEventMultiCreate(&hEvt);
+ if (RT_FAILURE(rc))
+ return rc;
+ pReq->hPushBackEvt = hEvt;
+ }
+
+ /*
+ * Prepare the request and semaphore.
+ */
+ uint32_t const cMsTimeout = pPool->cMsCurPushBack;
+ pReq->fSignalPushBack = true;
+ RTReqRetain(pReq);
+ RTSemEventMultiReset(hEvt);
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ /*
+ * Block.
+ */
+ rc = RTSemEventMultiWait(hEvt, cMsTimeout);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsg(rc == VERR_TIMEOUT, ("%Rrc\n", rc));
+ RTCritSectEnter(&pPool->CritSect);
+ }
+ RTReqRelease(pReq);
+ return rc;
+}
+
+
+
+DECLHIDDEN(void) rtReqPoolSubmit(PRTREQPOOLINT pPool, PRTREQINT pReq)
+{
+ RTCritSectEnter(&pPool->CritSect);
+
+ pPool->cReqSubmitted++;
+
+ /*
+ * Try schedule the request to a thread that's currently idle.
+ */
+ PRTREQPOOLTHREAD pThread = RTListGetFirst(&pPool->IdleThreads, RTREQPOOLTHREAD, IdleNode);
+ if (pThread)
+ {
+ /** @todo CPU affinity??? */
+ ASMAtomicWritePtr(&pThread->pTodoReq, pReq);
+
+ RTListNodeRemove(&pThread->IdleNode);
+ RTListInit(&pThread->IdleNode);
+ ASMAtomicDecU32(&pPool->cIdleThreads);
+
+ RTThreadUserSignal(pThread->hThread);
+
+ RTCritSectLeave(&pPool->CritSect);
+ return;
+ }
+ Assert(RTListIsEmpty(&pPool->IdleThreads));
+
+ /*
+ * Put the request in the pending queue.
+ */
+ pReq->pNext = NULL;
+ *pPool->ppPendingRequests = pReq;
+ pPool->ppPendingRequests = (PRTREQINT *)&pReq->pNext;
+ pPool->cCurPendingRequests++;
+
+ /*
+ * If there is an incoming worker thread already or we've reached the
+ * maximum number of worker threads, we're done.
+ */
+ if ( pPool->cIdleThreads > 0
+ || pPool->cCurThreads >= pPool->cMaxThreads)
+ {
+ RTCritSectLeave(&pPool->CritSect);
+ return;
+ }
+
+ /*
+ * Push back before creating a new worker thread.
+ */
+ if ( pPool->cCurThreads > pPool->cThreadsPushBackThreshold
+ && (RTTimeNanoTS() - pReq->uSubmitNanoTs) / RT_NS_1MS >= pPool->cMsCurPushBack )
+ {
+ int rc = rtReqPoolPushBack(pPool, pReq);
+ if (RT_SUCCESS(rc))
+ return;
+ }
+
+ /*
+ * Create a new thread for processing the request.
+ * For simplicity, we don't bother leaving the critical section while doing so.
+ */
+ rtReqPoolCreateNewWorker(pPool);
+
+ RTCritSectLeave(&pPool->CritSect);
+ return;
+}
+
+
+/**
+ * Worker for RTReqCancel that looks for the request in the pending list and
+ * completes it if found there.
+ *
+ * @param pPool The request thread pool.
+ * @param pReq The request.
+ */
+DECLHIDDEN(void) rtReqPoolCancel(PRTREQPOOLINT pPool, PRTREQINT pReq)
+{
+ RTCritSectEnter(&pPool->CritSect);
+
+ pPool->cReqCancelled++;
+
+ /*
+ * Check if the request is in the pending list.
+ */
+ PRTREQINT pPrev = NULL;
+ PRTREQINT pCur = pPool->pPendingRequests;
+ while (pCur)
+ if (pCur != pReq)
+ {
+ pPrev = pCur;
+ pCur = pCur->pNext;
+ }
+ else
+ {
+ /*
+ * Unlink it and process it.
+ */
+ if (!pPrev)
+ {
+ pPool->pPendingRequests = pReq->pNext;
+ if (!pReq->pNext)
+ pPool->ppPendingRequests = &pPool->pPendingRequests;
+ }
+ else
+ {
+ pPrev->pNext = pReq->pNext;
+ if (!pReq->pNext)
+ pPool->ppPendingRequests = (PRTREQINT *)&pPrev->pNext;
+ }
+ Assert(pPool->cCurPendingRequests > 0);
+ pPool->cCurPendingRequests--;
+
+ rtReqProcessOne(pReq);
+ break;
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+ return;
+}
+
+
+/**
+ * Frees a requst.
+ *
+ * @returns true if recycled, false if not.
+ * @param pPool The request thread pool.
+ * @param pReq The request.
+ */
+DECLHIDDEN(bool) rtReqPoolRecycle(PRTREQPOOLINT pPool, PRTREQINT pReq)
+{
+ if ( pPool
+ && ASMAtomicReadU32(&pPool->cCurFreeRequests) < pPool->cMaxFreeRequests)
+ {
+ RTCritSectEnter(&pPool->CritSect);
+ if (pPool->cCurFreeRequests < pPool->cMaxFreeRequests)
+ {
+ pReq->pNext = pPool->pFreeRequests;
+ pPool->pFreeRequests = pReq;
+ ASMAtomicIncU32(&pPool->cCurFreeRequests);
+
+ RTCritSectLeave(&pPool->CritSect);
+ return true;
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+ }
+ return false;
+}
+
+
+RTDECL(int) RTReqPoolCreate(uint32_t cMaxThreads, RTMSINTERVAL cMsMinIdle,
+ uint32_t cThreadsPushBackThreshold, uint32_t cMsMaxPushBack,
+ const char *pszName, PRTREQPOOL phPool)
+{
+ /*
+ * Validate and massage the config.
+ */
+ if (cMaxThreads == UINT32_MAX)
+ cMaxThreads = RTREQPOOL_MAX_THREADS;
+ AssertMsgReturn(cMaxThreads > 0 && cMaxThreads <= RTREQPOOL_MAX_THREADS, ("%u\n", cMaxThreads), VERR_OUT_OF_RANGE);
+ uint32_t const cMinThreads = cMaxThreads > 2 ? 2 : cMaxThreads - 1;
+
+ if (cThreadsPushBackThreshold == 0)
+ cThreadsPushBackThreshold = cMinThreads;
+ else if (cThreadsPushBackThreshold == UINT32_MAX)
+ cThreadsPushBackThreshold = cMaxThreads;
+ AssertMsgReturn(cThreadsPushBackThreshold <= cMaxThreads, ("%u/%u\n", cThreadsPushBackThreshold, cMaxThreads), VERR_OUT_OF_RANGE);
+
+ if (cMsMaxPushBack == UINT32_MAX)
+ cMsMaxPushBack = RTREQPOOL_PUSH_BACK_MAX_MS;
+ AssertMsgReturn(cMsMaxPushBack <= RTREQPOOL_PUSH_BACK_MAX_MS, ("%llu\n", cMsMaxPushBack), VERR_OUT_OF_RANGE);
+ uint32_t const cMsMinPushBack = cMsMaxPushBack >= 200 ? 100 : cMsMaxPushBack / 2;
+
+ AssertPtrReturn(pszName, VERR_INVALID_POINTER);
+ size_t cchName = strlen(pszName);
+ AssertReturn(cchName > 0, VERR_INVALID_PARAMETER);
+ Assert(cchName <= 10);
+
+ AssertPtrReturn(phPool, VERR_INVALID_POINTER);
+
+ /*
+ * Create and initialize the pool.
+ */
+ PRTREQPOOLINT pPool = (PRTREQPOOLINT)RTMemAlloc(sizeof(*pPool));
+ if (!pPool)
+ return VERR_NO_MEMORY;
+
+ pPool->u32Magic = RTREQPOOL_MAGIC;
+ RTStrCopy(pPool->szName, sizeof(pPool->szName), pszName);
+
+ pPool->enmThreadType = RTTHREADTYPE_DEFAULT;
+ pPool->fThreadFlags = 0;
+ pPool->cMaxThreads = cMaxThreads;
+ pPool->cMinThreads = cMinThreads;
+ pPool->cMsMinIdle = cMsMinIdle == RT_INDEFINITE_WAIT || cMsMinIdle >= UINT32_MAX ? UINT32_MAX : cMsMinIdle;
+ pPool->cNsMinIdle = pPool->cMsMinIdle == UINT32_MAX ? UINT64_MAX : cMsMinIdle * RT_NS_1MS_64;
+ pPool->cMsIdleSleep = pPool->cMsMinIdle == UINT32_MAX ? RT_INDEFINITE_WAIT : RT_MAX(RT_MS_1SEC, pPool->cMsMinIdle);
+ pPool->cThreadsPushBackThreshold = cThreadsPushBackThreshold;
+ pPool->cMsMaxPushBack = cMsMaxPushBack;
+ pPool->cMsMinPushBack = cMsMinPushBack;
+ pPool->cMaxFreeRequests = cMaxThreads * 2;
+ pPool->hThreadTermEvt = NIL_RTSEMEVENTMULTI;
+ pPool->fDestructing = false;
+ pPool->cMsCurPushBack = 0;
+ pPool->cCurThreads = 0;
+ pPool->cThreadsCreated = 0;
+ pPool->uLastThreadCreateNanoTs = 0;
+ RTListInit(&pPool->WorkerThreads);
+ pPool->cReqProcessed = 0;
+ pPool->cNsTotalReqProcessing= 0;
+ pPool->cNsTotalReqQueued = 0;
+ pPool->cRefs = 1;
+ pPool->cIdleThreads = 0;
+ RTListInit(&pPool->IdleThreads);
+ pPool->pPendingRequests = NULL;
+ pPool->ppPendingRequests = &pPool->pPendingRequests;
+ pPool->cCurPendingRequests = 0;
+ pPool->cCurActiveRequests = 0;
+ pPool->cReqSubmitted = 0;
+ pPool->cReqCancelled = 0;
+ pPool->pFreeRequests = NULL;
+ pPool->cCurFreeRequests = 0;
+
+ int rc = RTSemEventMultiCreate(&pPool->hThreadTermEvt);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pPool->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ *phPool = pPool;
+ return VINF_SUCCESS;
+ }
+
+ RTSemEventMultiDestroy(pPool->hThreadTermEvt);
+ }
+ pPool->u32Magic = RTREQPOOL_MAGIC_DEAD;
+ RTMemFree(pPool);
+ return rc;
+}
+
+
+
+RTDECL(int) RTReqPoolSetCfgVar(RTREQPOOL hPool, RTREQPOOLCFGVAR enmVar, uint64_t uValue)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, VERR_INVALID_HANDLE);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(enmVar > RTREQPOOLCFGVAR_INVALID && enmVar < RTREQPOOLCFGVAR_END, VERR_INVALID_PARAMETER);
+
+ RTCritSectEnter(&pPool->CritSect);
+
+ bool fWakeUpIdleThreads = false;
+ int rc = VINF_SUCCESS;
+ switch (enmVar)
+ {
+ case RTREQPOOLCFGVAR_THREAD_TYPE:
+ AssertMsgBreakStmt(uValue > (uint64_t)RTTHREADTYPE_INVALID && uValue < (uint64_t)RTTHREADTYPE_END,
+ ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+
+ pPool->enmThreadType = (RTTHREADTYPE)uValue;
+ break;
+
+ case RTREQPOOLCFGVAR_THREAD_FLAGS:
+ AssertMsgBreakStmt(!(uValue & ~(uint64_t)RTTHREADFLAGS_MASK) && !(uValue & RTTHREADFLAGS_WAITABLE),
+ ("%#llx\n", uValue), rc = VERR_INVALID_FLAGS);
+
+ pPool->fThreadFlags = (uint32_t)uValue;
+ break;
+
+ case RTREQPOOLCFGVAR_MIN_THREADS:
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_MAX_THREADS, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ fWakeUpIdleThreads = pPool->cMinThreads > (uint32_t)uValue;
+ pPool->cMinThreads = (uint32_t)uValue;
+ if (pPool->cMinThreads > pPool->cMaxThreads)
+ pPool->cMaxThreads = pPool->cMinThreads;
+ if ( pPool->cThreadsPushBackThreshold < pPool->cMinThreads
+ || pPool->cThreadsPushBackThreshold > pPool->cMaxThreads)
+ pPool->cThreadsPushBackThreshold = pPool->cMinThreads + (pPool->cMaxThreads - pPool->cMinThreads) / 2;
+ rtReqPoolRecalcPushBack(pPool);
+ break;
+
+ case RTREQPOOLCFGVAR_MAX_THREADS:
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_MAX_THREADS && uValue >= 1, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cMaxThreads = (uint32_t)uValue;
+ if (pPool->cMaxThreads < pPool->cMinThreads)
+ {
+ pPool->cMinThreads = pPool->cMaxThreads;
+ fWakeUpIdleThreads = true;
+ }
+ if (pPool->cMaxThreads < pPool->cThreadsPushBackThreshold)
+ pPool->cThreadsPushBackThreshold = pPool->cMinThreads + (pPool->cMaxThreads - pPool->cMinThreads) / 2;
+ rtReqPoolRecalcPushBack(pPool);
+ break;
+
+ case RTREQPOOLCFGVAR_MS_MIN_IDLE:
+ AssertMsgBreakStmt(uValue < UINT32_MAX || uValue == RT_INDEFINITE_WAIT, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ if (uValue < UINT32_MAX && uValue != RT_INDEFINITE_WAIT)
+ {
+ fWakeUpIdleThreads = pPool->cMsMinIdle != (uint32_t)uValue;
+ pPool->cMsMinIdle = (uint32_t)uValue;
+ pPool->cNsMinIdle = pPool->cMsMinIdle * RT_NS_1MS_64;
+ if (pPool->cMsIdleSleep > pPool->cMsMinIdle)
+ pPool->cMsIdleSleep = RT_MAX(RT_MS_1SEC, pPool->cMsMinIdle);
+ }
+ else
+ {
+ pPool->cMsMinIdle = UINT32_MAX;
+ pPool->cNsMinIdle = UINT64_MAX;
+ pPool->cMsIdleSleep = RT_INDEFINITE_WAIT;
+ }
+ break;
+
+ case RTREQPOOLCFGVAR_MS_IDLE_SLEEP:
+ AssertMsgBreakStmt(uValue <= RT_INDEFINITE_WAIT, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ fWakeUpIdleThreads = pPool->cMsMinIdle > (RTMSINTERVAL)uValue;
+ pPool->cMsIdleSleep = (RTMSINTERVAL)uValue;
+ if (pPool->cMsIdleSleep == RT_INDEFINITE_WAIT)
+ {
+ pPool->cMsMinIdle = UINT32_MAX;
+ pPool->cNsMinIdle = UINT64_MAX;
+ }
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_THRESHOLD:
+ if (uValue == UINT64_MAX)
+ pPool->cThreadsPushBackThreshold = pPool->cMaxThreads;
+ else if (uValue == 0)
+ pPool->cThreadsPushBackThreshold = pPool->cMinThreads;
+ else
+ {
+ AssertMsgBreakStmt(uValue <= pPool->cMaxThreads, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ AssertMsgBreakStmt(uValue >= pPool->cMinThreads, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cThreadsPushBackThreshold = (uint32_t)uValue;
+ }
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_MIN_MS:
+ if (uValue == UINT32_MAX || uValue == UINT64_MAX)
+ uValue = RTREQPOOL_PUSH_BACK_MAX_MS;
+ else
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_PUSH_BACK_MAX_MS, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cMsMinPushBack = (uint32_t)uValue;
+ if (pPool->cMsMaxPushBack < pPool->cMsMinPushBack)
+ pPool->cMsMaxPushBack = pPool->cMsMinPushBack;
+ rtReqPoolRecalcPushBack(pPool);
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_MAX_MS:
+ if (uValue == UINT32_MAX || uValue == UINT64_MAX)
+ uValue = RTREQPOOL_PUSH_BACK_MAX_MS;
+ else
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_PUSH_BACK_MAX_MS, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cMsMaxPushBack = (uint32_t)uValue;
+ if (pPool->cMsMinPushBack < pPool->cMsMaxPushBack)
+ pPool->cMsMinPushBack = pPool->cMsMaxPushBack;
+ rtReqPoolRecalcPushBack(pPool);
+ break;
+
+ case RTREQPOOLCFGVAR_MAX_FREE_REQUESTS:
+ if (uValue == UINT64_MAX)
+ {
+ pPool->cMaxFreeRequests = pPool->cMaxThreads * 2;
+ if (pPool->cMaxFreeRequests < 16)
+ pPool->cMaxFreeRequests = 16;
+ }
+ else
+ {
+ AssertMsgBreakStmt(uValue <= RTREQPOOL_MAX_FREE_REQUESTS, ("%llu\n", uValue), rc = VERR_OUT_OF_RANGE);
+ pPool->cMaxFreeRequests = (uint32_t)uValue;
+ }
+
+ while (pPool->cCurFreeRequests > pPool->cMaxFreeRequests)
+ {
+ PRTREQINT pReq = pPool->pFreeRequests;
+ pPool->pFreeRequests = pReq->pNext;
+ ASMAtomicDecU32(&pPool->cCurFreeRequests);
+ rtReqFreeIt(pReq);
+ }
+ break;
+
+ default:
+ AssertFailed();
+ rc = VERR_IPE_NOT_REACHED_DEFAULT_CASE;
+ }
+
+ /* Wake up all idle threads if required. */
+ if (fWakeUpIdleThreads)
+ {
+ Assert(rc == VINF_SUCCESS);
+ PRTREQPOOLTHREAD pThread;
+ RTListForEach(&pPool->WorkerThreads, pThread, RTREQPOOLTHREAD, ListNode)
+ {
+ RTThreadUserSignal(pThread->hThread);
+ }
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolSetCfgVar);
+
+
+RTDECL(uint64_t) RTReqPoolGetCfgVar(RTREQPOOL hPool, RTREQPOOLCFGVAR enmVar)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, UINT64_MAX);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, UINT64_MAX);
+ AssertReturn(enmVar > RTREQPOOLCFGVAR_INVALID && enmVar < RTREQPOOLCFGVAR_END, UINT64_MAX);
+
+ RTCritSectEnter(&pPool->CritSect);
+
+ uint64_t u64;
+ switch (enmVar)
+ {
+ case RTREQPOOLCFGVAR_THREAD_TYPE:
+ u64 = pPool->enmThreadType;
+ break;
+
+ case RTREQPOOLCFGVAR_THREAD_FLAGS:
+ u64 = pPool->fThreadFlags;
+ break;
+
+ case RTREQPOOLCFGVAR_MIN_THREADS:
+ u64 = pPool->cMinThreads;
+ break;
+
+ case RTREQPOOLCFGVAR_MAX_THREADS:
+ u64 = pPool->cMaxThreads;
+ break;
+
+ case RTREQPOOLCFGVAR_MS_MIN_IDLE:
+ u64 = pPool->cMsMinIdle;
+ break;
+
+ case RTREQPOOLCFGVAR_MS_IDLE_SLEEP:
+ u64 = pPool->cMsIdleSleep;
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_THRESHOLD:
+ u64 = pPool->cThreadsPushBackThreshold;
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_MIN_MS:
+ u64 = pPool->cMsMinPushBack;
+ break;
+
+ case RTREQPOOLCFGVAR_PUSH_BACK_MAX_MS:
+ u64 = pPool->cMsMaxPushBack;
+ break;
+
+ case RTREQPOOLCFGVAR_MAX_FREE_REQUESTS:
+ u64 = pPool->cMaxFreeRequests;
+ break;
+
+ default:
+ AssertFailed();
+ u64 = UINT64_MAX;
+ break;
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ return u64;
+}
+RT_EXPORT_SYMBOL(RTReqGetQueryCfgVar);
+
+
+RTDECL(uint64_t) RTReqPoolGetStat(RTREQPOOL hPool, RTREQPOOLSTAT enmStat)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, UINT64_MAX);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, UINT64_MAX);
+ AssertReturn(enmStat > RTREQPOOLSTAT_INVALID && enmStat < RTREQPOOLSTAT_END, UINT64_MAX);
+
+ RTCritSectEnter(&pPool->CritSect);
+
+ uint64_t u64;
+ switch (enmStat)
+ {
+ case RTREQPOOLSTAT_THREADS: u64 = pPool->cCurThreads; break;
+ case RTREQPOOLSTAT_THREADS_CREATED: u64 = pPool->cThreadsCreated; break;
+ case RTREQPOOLSTAT_REQUESTS_PROCESSED: u64 = pPool->cReqProcessed; break;
+ case RTREQPOOLSTAT_REQUESTS_SUBMITTED: u64 = pPool->cReqSubmitted; break;
+ case RTREQPOOLSTAT_REQUESTS_CANCELLED: u64 = pPool->cReqCancelled; break;
+ case RTREQPOOLSTAT_REQUESTS_PENDING: u64 = pPool->cCurPendingRequests; break;
+ case RTREQPOOLSTAT_REQUESTS_ACTIVE: u64 = pPool->cCurActiveRequests; break;
+ case RTREQPOOLSTAT_REQUESTS_FREE: u64 = pPool->cCurFreeRequests; break;
+ case RTREQPOOLSTAT_NS_TOTAL_REQ_PROCESSING: u64 = pPool->cNsTotalReqProcessing; break;
+ case RTREQPOOLSTAT_NS_TOTAL_REQ_QUEUED: u64 = pPool->cNsTotalReqQueued; break;
+ case RTREQPOOLSTAT_NS_AVERAGE_REQ_PROCESSING: u64 = pPool->cNsTotalReqProcessing / RT_MAX(pPool->cReqProcessed, 1); break;
+ case RTREQPOOLSTAT_NS_AVERAGE_REQ_QUEUED: u64 = pPool->cNsTotalReqQueued / RT_MAX(pPool->cReqProcessed, 1); break;
+ default:
+ AssertFailed();
+ u64 = UINT64_MAX;
+ break;
+ }
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ return u64;
+}
+RT_EXPORT_SYMBOL(RTReqPoolGetStat);
+
+
+RTDECL(uint32_t) RTReqPoolRetain(RTREQPOOL hPool)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, UINT32_MAX);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, UINT32_MAX);
+
+ return ASMAtomicIncU32(&pPool->cRefs);
+}
+RT_EXPORT_SYMBOL(RTReqPoolRetain);
+
+
+RTDECL(uint32_t) RTReqPoolRelease(RTREQPOOL hPool)
+{
+ /*
+ * Ignore NULL and validate the request.
+ */
+ if (!hPool)
+ return 0;
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, UINT32_MAX);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, UINT32_MAX);
+
+ /*
+ * Drop a reference, free it when it reaches zero.
+ */
+ uint32_t cRefs = ASMAtomicDecU32(&pPool->cRefs);
+ if (cRefs == 0)
+ {
+ AssertReturn(ASMAtomicCmpXchgU32(&pPool->u32Magic, RTREQPOOL_MAGIC_DEAD, RTREQPOOL_MAGIC), UINT32_MAX);
+
+ RTCritSectEnter(&pPool->CritSect);
+#ifdef RT_STRICT
+ RTTHREAD const hSelf = RTThreadSelf();
+#endif
+
+ /* Indicate to the worker threads that we're shutting down. */
+ ASMAtomicWriteBool(&pPool->fDestructing, true);
+ PRTREQPOOLTHREAD pThread;
+ RTListForEach(&pPool->WorkerThreads, pThread, RTREQPOOLTHREAD, ListNode)
+ {
+ Assert(pThread->hThread != hSelf);
+ RTThreadUserSignal(pThread->hThread);
+ }
+
+ /* Cancel pending requests. */
+ Assert(!pPool->pPendingRequests);
+ while (pPool->pPendingRequests)
+ {
+ PRTREQINT pReq = pPool->pPendingRequests;
+ pPool->pPendingRequests = pReq->pNext;
+ rtReqPoolCancelReq(pReq);
+ }
+ pPool->ppPendingRequests = NULL;
+ pPool->cCurPendingRequests = 0;
+
+ /* Wait for the workers to shut down. */
+ while (!RTListIsEmpty(&pPool->WorkerThreads))
+ {
+ RTCritSectLeave(&pPool->CritSect);
+ RTSemEventMultiWait(pPool->hThreadTermEvt, RT_MS_1MIN);
+ RTCritSectEnter(&pPool->CritSect);
+ /** @todo should we wait forever here? */
+ }
+
+ /* Free recycled requests. */
+ for (;;)
+ {
+ PRTREQINT pReq = pPool->pFreeRequests;
+ if (!pReq)
+ break;
+ pPool->pFreeRequests = pReq->pNext;
+ pPool->cCurFreeRequests--;
+ rtReqFreeIt(pReq);
+ }
+
+ /* Finally, free the critical section and pool instance. */
+ RTSemEventMultiDestroy(pPool->hThreadTermEvt);
+ RTCritSectLeave(&pPool->CritSect);
+ RTCritSectDelete(&pPool->CritSect);
+ RTMemFree(pPool);
+ }
+
+ return cRefs;
+}
+RT_EXPORT_SYMBOL(RTReqPoolRelease);
+
+
+RTDECL(int) RTReqPoolAlloc(RTREQPOOL hPool, RTREQTYPE enmType, PRTREQ *phReq)
+{
+ PRTREQPOOLINT pPool = hPool;
+ AssertPtrReturn(pPool, VERR_INVALID_HANDLE);
+ AssertReturn(pPool->u32Magic == RTREQPOOL_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Try recycle old requests.
+ */
+ if (ASMAtomicReadU32(&pPool->cCurFreeRequests) > 0)
+ {
+ RTCritSectEnter(&pPool->CritSect);
+ PRTREQINT pReq = pPool->pFreeRequests;
+ if (pReq)
+ {
+ ASMAtomicDecU32(&pPool->cCurFreeRequests);
+ pPool->pFreeRequests = pReq->pNext;
+
+ RTCritSectLeave(&pPool->CritSect);
+
+ Assert(pReq->fPoolOrQueue);
+ Assert(pReq->uOwner.hPool == pPool);
+
+ int rc = rtReqReInit(pReq, enmType);
+ if (RT_SUCCESS(rc))
+ {
+ *phReq = pReq;
+ LogFlow(("RTReqPoolAlloc: returns VINF_SUCCESS *phReq=%p recycled\n", pReq));
+ return rc;
+ }
+ }
+ else
+ RTCritSectLeave(&pPool->CritSect);
+ }
+
+ /*
+ * Allocate a new request.
+ */
+ int rc = rtReqAlloc(enmType, true /*fPoolOrQueue*/, pPool, phReq);
+ LogFlow(("RTReqPoolAlloc: returns %Rrc *phReq=%p\n", rc, *phReq));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolAlloc);
+
+
+RTDECL(int) RTReqPoolCallEx( RTREQPOOL hPool, RTMSINTERVAL cMillies, PRTREQ *phReq, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, cMillies, phReq, fFlags, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallEx);
+
+
+RTDECL(int) RTReqPoolCallExV(RTREQPOOL hPool, RTMSINTERVAL cMillies, PRTREQ *phReq, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, va_list va)
+{
+ /*
+ * Check input.
+ */
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+ AssertMsgReturn(!((uint32_t)fFlags & ~(uint32_t)(RTREQFLAGS_NO_WAIT | RTREQFLAGS_RETURN_MASK)), ("%#x\n", (uint32_t)fFlags), VERR_INVALID_PARAMETER);
+ if (!(fFlags & RTREQFLAGS_NO_WAIT) || phReq)
+ {
+ AssertPtrReturn(phReq, VERR_INVALID_POINTER);
+ *phReq = NIL_RTREQ;
+ }
+
+ PRTREQINT pReq = NULL;
+ AssertMsgReturn(cArgs * sizeof(uintptr_t) <= sizeof(pReq->u.Internal.aArgs), ("cArgs=%u\n", cArgs), VERR_TOO_MUCH_DATA);
+
+ /*
+ * Allocate and initialize the request.
+ */
+ int rc = RTReqPoolAlloc(hPool, RTREQTYPE_INTERNAL, &pReq);
+ if (RT_FAILURE(rc))
+ return rc;
+ pReq->fFlags = fFlags;
+ pReq->u.Internal.pfn = pfnFunction;
+ pReq->u.Internal.cArgs = cArgs;
+ for (unsigned iArg = 0; iArg < cArgs; iArg++)
+ pReq->u.Internal.aArgs[iArg] = va_arg(va, uintptr_t);
+
+ /*
+ * Submit the request.
+ */
+ rc = RTReqSubmit(pReq, cMillies);
+ if ( rc != VINF_SUCCESS
+ && rc != VERR_TIMEOUT)
+ {
+ Assert(rc != VERR_INTERRUPTED);
+ RTReqRelease(pReq);
+ pReq = NULL;
+ }
+
+ if (phReq)
+ {
+ *phReq = pReq;
+ LogFlow(("RTReqPoolCallExV: returns %Rrc *phReq=%p\n", rc, pReq));
+ }
+ else
+ {
+ RTReqRelease(pReq);
+ LogFlow(("RTReqPoolCallExV: returns %Rrc\n", rc));
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallExV);
+
+
+RTDECL(int) RTReqPoolCallWait(RTREQPOOL hPool, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ PRTREQINT pReq;
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, RT_INDEFINITE_WAIT, &pReq, RTREQFLAGS_IPRT_STATUS,
+ pfnFunction, cArgs, va);
+ va_end(va);
+ if (RT_SUCCESS(rc))
+ rc = pReq->iStatusX;
+ RTReqRelease(pReq);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallWait);
+
+
+RTDECL(int) RTReqPoolCallNoWait(RTREQPOOL hPool, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, 0, NULL, RTREQFLAGS_IPRT_STATUS | RTREQFLAGS_NO_WAIT,
+ pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallNoWait);
+
+
+RTDECL(int) RTReqPoolCallVoidWait(RTREQPOOL hPool, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ PRTREQINT pReq;
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, RT_INDEFINITE_WAIT, &pReq, RTREQFLAGS_VOID,
+ pfnFunction, cArgs, va);
+ va_end(va);
+ if (RT_SUCCESS(rc))
+ rc = pReq->iStatusX;
+ RTReqRelease(pReq);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallVoidWait);
+
+
+RTDECL(int) RTReqPoolCallVoidNoWait(RTREQPOOL hPool, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqPoolCallExV(hPool, 0, NULL, RTREQFLAGS_VOID | RTREQFLAGS_NO_WAIT,
+ pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqPoolCallVoidNoWait);
+
diff --git a/src/VBox/Runtime/common/misc/reqqueue.cpp b/src/VBox/Runtime/common/misc/reqqueue.cpp
new file mode 100644
index 00000000..c0c89e1a
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/reqqueue.cpp
@@ -0,0 +1,465 @@
+/* $Id: reqqueue.cpp $ */
+/** @file
+ * IPRT - Request Queue.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/req.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+
+#include "internal/req.h"
+#include "internal/magics.h"
+
+
+
+RTDECL(int) RTReqQueueCreate(RTREQQUEUE *phQueue)
+{
+ PRTREQQUEUEINT pQueue = (PRTREQQUEUEINT)RTMemAllocZ(sizeof(RTREQQUEUEINT));
+ if (!pQueue)
+ return VERR_NO_MEMORY;
+ int rc = RTSemEventCreate(&pQueue->EventSem);
+ if (RT_SUCCESS(rc))
+ {
+ pQueue->u32Magic = RTREQQUEUE_MAGIC;
+
+ *phQueue = pQueue;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pQueue);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCreate);
+
+
+RTDECL(int) RTReqQueueDestroy(RTREQQUEUE hQueue)
+{
+ /*
+ * Check input.
+ */
+ if (hQueue == NIL_RTREQQUEUE)
+ return VINF_SUCCESS;
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, VERR_INVALID_HANDLE);
+ AssertReturn(ASMAtomicCmpXchgU32(&pQueue->u32Magic, RTREQQUEUE_MAGIC_DEAD, RTREQQUEUE_MAGIC), VERR_INVALID_HANDLE);
+
+ RTSemEventDestroy(pQueue->EventSem);
+ pQueue->EventSem = NIL_RTSEMEVENT;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pQueue->apReqFree); i++)
+ {
+ PRTREQ pReq = (PRTREQ)ASMAtomicXchgPtr((void **)&pQueue->apReqFree[i], NULL);
+ while (pReq)
+ {
+ PRTREQ pNext = pReq->pNext;
+ rtReqFreeIt(pReq);
+ pReq = pNext;
+ }
+ }
+
+ RTMemFree(pQueue);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTReqQueueDestroy);
+
+
+RTDECL(int) RTReqQueueProcess(RTREQQUEUE hQueue, RTMSINTERVAL cMillies)
+{
+ LogFlow(("RTReqQueueProcess %x\n", hQueue));
+
+ /*
+ * Check input.
+ */
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, VERR_INVALID_HANDLE);
+ AssertReturn(pQueue->u32Magic == RTREQQUEUE_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Process loop. Stop (break) after the first non-VINF_SUCCESS status code.
+ */
+ int rc = VINF_SUCCESS;
+ for (;;)
+ {
+ /*
+ * Get pending requests.
+ */
+ PRTREQ pReqs = ASMAtomicXchgPtrT(&pQueue->pAlreadyPendingReqs, NULL, PRTREQ);
+ if (RT_LIKELY(!pReqs))
+ {
+ pReqs = ASMAtomicXchgPtrT(&pQueue->pReqs, NULL, PRTREQ);
+ if (!pReqs)
+ {
+ /* We do not adjust cMillies (documented behavior). */
+ ASMAtomicWriteBool(&pQueue->fBusy, false); /* this aint 100% perfect, but it's good enough for now... */
+ rc = RTSemEventWait(pQueue->EventSem, cMillies);
+ if (rc != VINF_SUCCESS)
+ break;
+ continue;
+ }
+
+ ASMAtomicWriteBool(&pQueue->fBusy, true);
+
+ /*
+ * Reverse the list to process it in FIFO order.
+ */
+ PRTREQ pReq = pReqs;
+ if (pReq->pNext)
+ Log2(("RTReqQueueProcess: 2+ requests: %p %p %p\n", pReq, pReq->pNext, pReq->pNext->pNext));
+ pReqs = NULL;
+ while (pReq)
+ {
+ Assert(pReq->enmState == RTREQSTATE_QUEUED);
+ Assert(pReq->uOwner.hQueue == pQueue);
+ PRTREQ pCur = pReq;
+ pReq = pReq->pNext;
+ pCur->pNext = pReqs;
+ pReqs = pCur;
+ }
+
+ }
+ else
+ ASMAtomicWriteBool(&pQueue->fBusy, true);
+
+ /*
+ * Process the requests.
+ */
+ while (pReqs)
+ {
+ /* Unchain the first request and advance the list. */
+ PRTREQ pReq = pReqs;
+ pReqs = pReqs->pNext;
+ pReq->pNext = NULL;
+
+ /* Process the request. */
+ rc = rtReqProcessOne(pReq);
+ if (rc != VINF_SUCCESS)
+ {
+ /* Propagate the return code to caller. If more requests pending, queue them for later. */
+ if (pReqs)
+ {
+ pReqs = ASMAtomicXchgPtrT(&pQueue->pAlreadyPendingReqs, pReqs, PRTREQ);
+ Assert(!pReqs);
+ }
+ break;
+ }
+ }
+ if (rc != VINF_SUCCESS)
+ break;
+ }
+
+ LogFlow(("RTReqQueueProcess: returns %Rrc\n", rc));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueProcess);
+
+
+RTDECL(int) RTReqQueueCall(RTREQQUEUE hQueue, PRTREQ *ppReq, RTMSINTERVAL cMillies, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqQueueCallV(hQueue, ppReq, cMillies, RTREQFLAGS_IPRT_STATUS, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCall);
+
+
+RTDECL(int) RTReqQueueCallVoid(RTREQQUEUE hQueue, PRTREQ *ppReq, RTMSINTERVAL cMillies, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqQueueCallV(hQueue, ppReq, cMillies, RTREQFLAGS_VOID, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCallVoid);
+
+
+RTDECL(int) RTReqQueueCallEx(RTREQQUEUE hQueue, PRTREQ *ppReq, RTMSINTERVAL cMillies, unsigned fFlags, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ va_list va;
+ va_start(va, cArgs);
+ int rc = RTReqQueueCallV(hQueue, ppReq, cMillies, fFlags, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCallEx);
+
+
+RTDECL(int) RTReqQueueCallV(RTREQQUEUE hQueue, PRTREQ *ppReq, RTMSINTERVAL cMillies, unsigned fFlags, PFNRT pfnFunction, unsigned cArgs, va_list Args)
+{
+ LogFlow(("RTReqQueueCallV: cMillies=%d fFlags=%#x pfnFunction=%p cArgs=%d\n", cMillies, fFlags, pfnFunction, cArgs));
+
+ /*
+ * Check input.
+ */
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, VERR_INVALID_HANDLE);
+ AssertReturn(pQueue->u32Magic == RTREQQUEUE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pfnFunction, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~(RTREQFLAGS_RETURN_MASK | RTREQFLAGS_NO_WAIT)), VERR_INVALID_PARAMETER);
+
+ if (!(fFlags & RTREQFLAGS_NO_WAIT) || ppReq)
+ {
+ AssertPtrReturn(ppReq, VERR_INVALID_POINTER);
+ *ppReq = NIL_RTREQ;
+ }
+
+ PRTREQ pReq = NULL;
+ AssertMsgReturn(cArgs * sizeof(uintptr_t) <= sizeof(pReq->u.Internal.aArgs), ("cArgs=%u\n", cArgs), VERR_TOO_MUCH_DATA);
+
+ /*
+ * Allocate request
+ */
+ int rc = RTReqQueueAlloc(pQueue, RTREQTYPE_INTERNAL, &pReq);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ /*
+ * Initialize the request data.
+ */
+ pReq->fFlags = fFlags;
+ pReq->u.Internal.pfn = pfnFunction;
+ pReq->u.Internal.cArgs = cArgs;
+ for (unsigned iArg = 0; iArg < cArgs; iArg++)
+ pReq->u.Internal.aArgs[iArg] = va_arg(Args, uintptr_t);
+
+ /*
+ * Queue the request and return.
+ */
+ rc = RTReqSubmit(pReq, cMillies);
+ if ( rc != VINF_SUCCESS
+ && rc != VERR_TIMEOUT)
+ {
+ RTReqRelease(pReq);
+ pReq = NULL;
+ }
+ if (ppReq)
+ {
+ *ppReq = pReq;
+ LogFlow(("RTReqQueueCallV: returns %Rrc *ppReq=%p\n", rc, pReq));
+ }
+ else
+ {
+ RTReqRelease(pReq);
+ LogFlow(("RTReqQueueCallV: returns %Rrc\n", rc));
+ }
+ Assert(rc != VERR_INTERRUPTED);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueCallV);
+
+
+RTDECL(bool) RTReqQueueIsBusy(RTREQQUEUE hQueue)
+{
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, false);
+
+ if (ASMAtomicReadBool(&pQueue->fBusy))
+ return true;
+ if (ASMAtomicReadPtrT(&pQueue->pReqs, PRTREQ) != NULL)
+ return true;
+ if (ASMAtomicReadBool(&pQueue->fBusy))
+ return true;
+ return false;
+}
+RT_EXPORT_SYMBOL(RTReqQueueIsBusy);
+
+
+/**
+ * Joins the list pList with whatever is linked up at *pHead.
+ */
+static void vmr3ReqJoinFreeSub(volatile PRTREQ *ppHead, PRTREQ pList)
+{
+ for (unsigned cIterations = 0;; cIterations++)
+ {
+ PRTREQ pHead = ASMAtomicXchgPtrT(ppHead, pList, PRTREQ);
+ if (!pHead)
+ return;
+ PRTREQ pTail = pHead;
+ while (pTail->pNext)
+ pTail = pTail->pNext;
+ pTail->pNext = pList;
+ if (ASMAtomicCmpXchgPtr(ppHead, pHead, pList))
+ return;
+ pTail->pNext = NULL;
+ if (ASMAtomicCmpXchgPtr(ppHead, pHead, NULL))
+ return;
+ pList = pHead;
+ Assert(cIterations != 32);
+ Assert(cIterations != 64);
+ }
+}
+
+
+/**
+ * Joins the list pList with whatever is linked up at *pHead.
+ */
+static void vmr3ReqJoinFree(PRTREQQUEUEINT pQueue, PRTREQ pList)
+{
+ /*
+ * Split the list if it's too long.
+ */
+ unsigned cReqs = 1;
+ PRTREQ pTail = pList;
+ while (pTail->pNext)
+ {
+ if (cReqs++ > 25)
+ {
+ const uint32_t i = pQueue->iReqFree;
+ vmr3ReqJoinFreeSub(&pQueue->apReqFree[(i + 2) % RT_ELEMENTS(pQueue->apReqFree)], pTail->pNext);
+
+ pTail->pNext = NULL;
+ vmr3ReqJoinFreeSub(&pQueue->apReqFree[(i + 2 + (i == pQueue->iReqFree)) % RT_ELEMENTS(pQueue->apReqFree)], pTail->pNext);
+ return;
+ }
+ pTail = pTail->pNext;
+ }
+ vmr3ReqJoinFreeSub(&pQueue->apReqFree[(pQueue->iReqFree + 2) % RT_ELEMENTS(pQueue->apReqFree)], pList);
+}
+
+
+RTDECL(int) RTReqQueueAlloc(RTREQQUEUE hQueue, RTREQTYPE enmType, PRTREQ *phReq)
+{
+ /*
+ * Validate input.
+ */
+ PRTREQQUEUEINT pQueue = hQueue;
+ AssertPtrReturn(pQueue, VERR_INVALID_HANDLE);
+ AssertReturn(pQueue->u32Magic == RTREQQUEUE_MAGIC, VERR_INVALID_HANDLE);
+ AssertMsgReturn(enmType > RTREQTYPE_INVALID && enmType < RTREQTYPE_MAX, ("%d\n", enmType), VERR_RT_REQUEST_INVALID_TYPE);
+
+ /*
+ * Try get a recycled packet.
+ *
+ * While this could all be solved with a single list with a lock, it's a sport
+ * of mine to avoid locks.
+ */
+ int cTries = RT_ELEMENTS(pQueue->apReqFree) * 2;
+ while (--cTries >= 0)
+ {
+ PRTREQ volatile *ppHead = &pQueue->apReqFree[ASMAtomicIncU32(&pQueue->iReqFree) % RT_ELEMENTS(pQueue->apReqFree)];
+ PRTREQ pReq = ASMAtomicXchgPtrT(ppHead, NULL, PRTREQ);
+ if (pReq)
+ {
+ PRTREQ pNext = pReq->pNext;
+ if ( pNext
+ && !ASMAtomicCmpXchgPtr(ppHead, pNext, NULL))
+ vmr3ReqJoinFree(pQueue, pReq->pNext);
+ ASMAtomicDecU32(&pQueue->cReqFree);
+
+ Assert(pReq->uOwner.hQueue == pQueue);
+ Assert(!pReq->fPoolOrQueue);
+
+ int rc = rtReqReInit(pReq, enmType);
+ if (RT_SUCCESS(rc))
+ {
+ *phReq = pReq;
+ LogFlow(("RTReqQueueAlloc: returns VINF_SUCCESS *phReq=%p recycled\n", pReq));
+ return VINF_SUCCESS;
+ }
+ }
+ }
+
+ /*
+ * Ok, allocate a new one.
+ */
+ int rc = rtReqAlloc(enmType, false /*fPoolOrQueue*/, pQueue, phReq);
+ LogFlow(("RTReqQueueAlloc: returns %Rrc *phReq=%p\n", rc, *phReq));
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTReqQueueAlloc);
+
+
+/**
+ * Recycles a requst.
+ *
+ * @returns true if recycled, false if it should be freed.
+ * @param pQueue The queue.
+ * @param pReq The request.
+ */
+DECLHIDDEN(bool) rtReqQueueRecycle(PRTREQQUEUEINT pQueue, PRTREQINT pReq)
+{
+ if ( !pQueue
+ || pQueue->cReqFree >= 128)
+ return false;
+
+ ASMAtomicIncU32(&pQueue->cReqFree);
+ PRTREQ volatile *ppHead = &pQueue->apReqFree[ASMAtomicIncU32(&pQueue->iReqFree) % RT_ELEMENTS(pQueue->apReqFree)];
+ PRTREQ pNext;
+ do
+ {
+ pNext = *ppHead;
+ ASMAtomicWritePtr(&pReq->pNext, pNext);
+ } while (!ASMAtomicCmpXchgPtr(ppHead, pReq, pNext));
+
+ return true;
+}
+
+
+/**
+ * Submits a request to the queue.
+ *
+ * @param pQueue The queue.
+ * @param pReq The request.
+ */
+DECLHIDDEN(void) rtReqQueueSubmit(PRTREQQUEUEINT pQueue, PRTREQINT pReq)
+{
+ PRTREQ pNext;
+ do
+ {
+ pNext = pQueue->pReqs;
+ pReq->pNext = pNext;
+ ASMAtomicWriteBool(&pQueue->fBusy, true);
+ } while (!ASMAtomicCmpXchgPtr(&pQueue->pReqs, pReq, pNext));
+
+ /*
+ * Notify queue thread.
+ */
+ RTSemEventSignal(pQueue->EventSem);
+}
+
diff --git a/src/VBox/Runtime/common/misc/sanity-c.c b/src/VBox/Runtime/common/misc/sanity-c.c
new file mode 100644
index 00000000..4df2a59e
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/sanity-c.c
@@ -0,0 +1,37 @@
+/* $Id: sanity-c.c $ */
+/** @file
+ * IPRT - Setup Sanity Checks, C.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include "sanity.h"
diff --git a/src/VBox/Runtime/common/misc/sanity-cpp.cpp b/src/VBox/Runtime/common/misc/sanity-cpp.cpp
new file mode 100644
index 00000000..57ec37f1
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/sanity-cpp.cpp
@@ -0,0 +1,38 @@
+/* $Id: sanity-cpp.cpp $ */
+/** @file
+ * IPRT - Setup Sanity Checks, C++.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include "sanity.h"
+
diff --git a/src/VBox/Runtime/common/misc/sanity.h b/src/VBox/Runtime/common/misc/sanity.h
new file mode 100644
index 00000000..7117adfd
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/sanity.h
@@ -0,0 +1,225 @@
+/* $Id: sanity.h $ */
+/** @file
+ * IPRT - Setup Sanity Checks, C and C++.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/assert.h>
+
+/*
+ * Check that the IN_[RING3|RING0|GC] and [|R3_|R0_|GC_]ARCH_BITS
+ * match up correctly.
+ *
+ * IPRT assumes r0 and r3 to has the same bit count.
+ */
+
+#if defined(IN_RING3) && ARCH_BITS != R3_ARCH_BITS
+# error "defined(IN_RING3) && ARCH_BITS != R3_ARCH_BITS"
+#endif
+#if defined(IN_RING0) && ARCH_BITS != R0_ARCH_BITS
+# error "defined(IN_RING0) && ARCH_BITS != R0_ARCH_BITS"
+#endif
+#if defined(IN_RC) && ARCH_BITS != 32
+# error "defined(IN_RC) && ARCH_BITS != 32"
+#endif
+#if (defined(IN_RING0) || defined(IN_RING3)) && HC_ARCH_BITS != ARCH_BITS
+# error "(defined(IN_RING0) || defined(IN_RING3)) && HC_ARCH_BITS != ARCH_BITS"
+#endif
+#if defined(IN_RC) && GC_ARCH_BITS != 64 && GC_ARCH_BITS != ARCH_BITS
+# error "defined(IN_RC) && GC_ARCH_BITS != ARCH_BITS"
+#endif
+
+
+/*
+ * Check basic host (hc/r0/r3) types.
+ */
+#if HC_ARCH_BITS == 64
+
+AssertCompileSize(RTHCPTR, 8);
+AssertCompileSize(RTHCINT, 4);
+AssertCompileSize(RTHCUINT, 4);
+AssertCompileSize(RTHCINTPTR, 8);
+AssertCompileSize(RTHCUINTPTR, 8);
+/*AssertCompileSize(RTHCINTREG, 8);*/
+AssertCompileSize(RTHCUINTREG, 8);
+AssertCompileSize(RTR0PTR, 8);
+/*AssertCompileSize(RTR0INT, 4);*/
+/*AssertCompileSize(RTR0UINT, 4);*/
+AssertCompileSize(RTR0INTPTR, 8);
+AssertCompileSize(RTR0UINTPTR, 8);
+/*AssertCompileSize(RTR3PTR, 8);*/
+/*AssertCompileSize(RTR3INT, 4);*/
+/*AssertCompileSize(RTR3UINT, 4);*/
+AssertCompileSize(RTR3INTPTR, 8);
+AssertCompileSize(RTR3UINTPTR, 8);
+AssertCompileSize(RTUINTPTR, 8);
+
+# if defined(IN_RING3) || defined(IN_RING0)
+/*AssertCompileSize(RTCCINTREG, 8);*/
+AssertCompileSize(RTCCUINTREG, 8);
+# endif
+
+#else
+
+AssertCompileSize(RTHCPTR, 4);
+AssertCompileSize(RTHCINT, 4);
+AssertCompileSize(RTHCUINT, 4);
+/*AssertCompileSize(RTHCINTPTR, 4);*/
+AssertCompileSize(RTHCUINTPTR, 4);
+AssertCompileSize(RTR0PTR, 4);
+/*AssertCompileSize(RTR0INT, 4);*/
+/*AssertCompileSize(RTR0UINT, 4);*/
+AssertCompileSize(RTR0INTPTR, 4);
+AssertCompileSize(RTR0UINTPTR, 4);
+/*AssertCompileSize(RTR3PTR, 4);*/
+/*AssertCompileSize(RTR3INT, 4);*/
+/*AssertCompileSize(RTR3UINT, 4);*/
+AssertCompileSize(RTR3INTPTR, 4);
+AssertCompileSize(RTR3UINTPTR, 4);
+# if GC_ARCH_BITS == 64
+AssertCompileSize(RTUINTPTR, 8);
+# else
+AssertCompileSize(RTUINTPTR, 4);
+# endif
+
+# if defined(IN_RING3) || defined(IN_RING0)
+/*AssertCompileSize(RTCCINTREG, 4);*/
+AssertCompileSize(RTCCUINTREG, 4);
+# endif
+
+#endif
+
+AssertCompileSize(RTHCPHYS, 8);
+
+
+/*
+ * Check basic guest context types.
+ */
+#if GC_ARCH_BITS == 64
+
+AssertCompileSize(RTGCINT, 8);
+AssertCompileSize(RTGCUINT, 8);
+AssertCompileSize(RTGCINTPTR, 8);
+AssertCompileSize(RTGCUINTPTR, 8);
+/*AssertCompileSize(RTGCINTREG, 8);*/
+AssertCompileSize(RTGCUINTREG, 8);
+
+# ifdef IN_RC
+/*AssertCompileSize(RTCCINTREG, 8);*/
+/* Hack alert: there is no such thing as a GC context when GC_ARCH_BITS == 64; it's still 32 bits */
+AssertCompileSize(RTCCUINTREG, 4);
+# endif
+
+#else
+
+AssertCompileSize(RTGCINT, 4);
+AssertCompileSize(RTGCUINT, 4);
+AssertCompileSize(RTGCINTPTR, 4);
+AssertCompileSize(RTGCUINTPTR, 4);
+/*AssertCompileSize(RTGCINTREG, 4);*/
+AssertCompileSize(RTGCUINTREG, 4);
+
+# ifdef IN_RC
+/*AssertCompileSize(RTCCINTREG, 4);*/
+AssertCompileSize(RTCCUINTREG, 4);
+# endif
+
+#endif
+
+AssertCompileSize(RTGCPHYS64, 8);
+AssertCompileSize(RTGCPHYS32, 4);
+AssertCompileSize(RTGCPHYS, 8);
+
+
+/*
+ * Check basic current context types.
+ */
+#if ARCH_BITS == 64
+
+AssertCompileSize(void *, 8);
+AssertCompileSize(intptr_t, 8);
+AssertCompileSize(uintptr_t, 8);
+AssertCompileSize(size_t, 8);
+AssertCompileSize(ssize_t, 8);
+
+#else
+
+AssertCompileSize(void *, 4);
+AssertCompileSize(intptr_t, 4);
+AssertCompileSize(uintptr_t, 4);
+AssertCompileSize(size_t, 4);
+AssertCompileSize(ssize_t, 4);
+
+#endif
+
+
+/*
+ * Standard sized types.
+ */
+AssertCompileSize(uint8_t, 1);
+AssertCompileSize(uint16_t, 2);
+AssertCompileSize(uint32_t, 4);
+AssertCompileSize(uint64_t, 8);
+
+#define TEST_CONST_MACRO(c,t) \
+ AssertCompile(sizeof(c) == sizeof(t) || (sizeof(c) == sizeof(int) && sizeof(t) < sizeof(int)) )
+
+TEST_CONST_MACRO(UINT8_C(1), uint8_t);
+TEST_CONST_MACRO(UINT16_C(1), uint16_t);
+TEST_CONST_MACRO(UINT32_C(1), uint32_t);
+TEST_CONST_MACRO(UINT64_C(1), uint64_t);
+
+TEST_CONST_MACRO(INT8_C(1), int8_t);
+TEST_CONST_MACRO(INT8_C(-1), int8_t);
+TEST_CONST_MACRO(INT16_C(1), int16_t);
+TEST_CONST_MACRO(INT16_C(-1), int16_t);
+TEST_CONST_MACRO(INT32_C(1), int32_t);
+TEST_CONST_MACRO(INT32_C(-1), int32_t);
+TEST_CONST_MACRO(INT64_C(1), int64_t);
+TEST_CONST_MACRO(INT64_C(-1), int64_t);
+
+
+/*
+ * Our union types.
+ */
+AssertCompileSize(RTUINT16U, 2);
+AssertCompileSize(RTUINT32U, 4);
+AssertCompileSize(RTUINT64U, 8);
+AssertCompileSize(RTUINT128U, 16);
+/*AssertCompileSize(RTFLOAT32U, 8);*/
+AssertCompileSize(RTFLOAT64U, 8);
+AssertCompileSize(RTFLOAT80U, 10);
+/*AssertCompileSize(RTFLOAT128U, 16);*/
+
diff --git a/src/VBox/Runtime/common/misc/semspingpong.cpp b/src/VBox/Runtime/common/misc/semspingpong.cpp
new file mode 100644
index 00000000..59a27aa8
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/semspingpong.cpp
@@ -0,0 +1,218 @@
+/* $Id: semspingpong.cpp $ */
+/** @file
+ * IPRT - Thread Ping-Pong Construct.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/semaphore.h>
+#include "internal/iprt.h"
+
+#include <iprt/thread.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/**
+ * Validation macro returns if invalid parameter.
+ *
+ * Expects a enmSpeaker variable to be handy and will set it to the current
+ * enmSpeaker value.
+ */
+#define RTSEMPP_VALIDATE_RETURN(pPP) \
+ do { \
+ AssertPtrReturn(pPP, VERR_INVALID_PARAMETER); \
+ AssertCompileSize(pPP->enmSpeaker, 4); \
+ enmSpeaker = (RTPINGPONGSPEAKER)ASMAtomicUoReadU32((volatile uint32_t *)&pPP->enmSpeaker); \
+ AssertMsgReturn( enmSpeaker == RTPINGPONGSPEAKER_PING \
+ || enmSpeaker == RTPINGPONGSPEAKER_PONG \
+ || enmSpeaker == RTPINGPONGSPEAKER_PONG_SIGNALED \
+ || enmSpeaker == RTPINGPONGSPEAKER_PING_SIGNALED, \
+ ("enmSpeaker=%d\n", enmSpeaker), \
+ VERR_INVALID_PARAMETER); \
+ } while (0)
+
+
+RTDECL(int) RTSemPingPongInit(PRTPINGPONG pPP)
+{
+ /*
+ * Init the structure.
+ */
+ pPP->enmSpeaker = RTPINGPONGSPEAKER_PING;
+
+ int rc = RTSemEventCreate(&pPP->Ping);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventCreate(&pPP->Pong);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ RTSemEventDestroy(pPP->Ping);
+ }
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPingPongInit);
+
+
+RTDECL(int) RTSemPingPongDelete(PRTPINGPONG pPP)
+{
+ /*
+ * Validate input
+ */
+ if (!pPP)
+ return VINF_SUCCESS;
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+
+ /*
+ * Invalidate the ping pong handle and destroy the event semaphores.
+ */
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_UNINITIALIZE);
+ int rc = RTSemEventDestroy(pPP->Ping);
+ int rc2 = RTSemEventDestroy(pPP->Pong);
+ AssertRC(rc);
+ AssertRC(rc2);
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTSemPingPongDelete);
+
+
+RTDECL(int) RTSemPing(PRTPINGPONG pPP)
+{
+ /*
+ * Validate input
+ */
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+ AssertMsgReturn(enmSpeaker == RTPINGPONGSPEAKER_PING,("Speaking out of turn! enmSpeaker=%d\n", enmSpeaker),
+ VERR_SEM_OUT_OF_TURN);
+
+ /*
+ * Signal the other thread.
+ */
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PONG_SIGNALED);
+ int rc = RTSemEventSignal(pPP->Pong);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ /* restore the state. */
+ AssertMsgFailed(("Failed to signal pong sem %x. rc=%Rrc\n", pPP->Pong, rc));
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PING);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPing);
+
+
+RTDECL(int) RTSemPong(PRTPINGPONG pPP)
+{
+ /*
+ * Validate input
+ */
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+ AssertMsgReturn(enmSpeaker == RTPINGPONGSPEAKER_PONG,("Speaking out of turn! enmSpeaker=%d\n", enmSpeaker),
+ VERR_SEM_OUT_OF_TURN);
+
+ /*
+ * Signal the other thread.
+ */
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PING_SIGNALED);
+ int rc = RTSemEventSignal(pPP->Ping);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ /* restore the state. */
+ AssertMsgFailed(("Failed to signal ping sem %x. rc=%Rrc\n", pPP->Ping, rc));
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PONG);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPong);
+
+
+RTDECL(int) RTSemPingWait(PRTPINGPONG pPP, RTMSINTERVAL cMillies)
+{
+ /*
+ * Validate input
+ */
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+ AssertMsgReturn( enmSpeaker == RTPINGPONGSPEAKER_PONG
+ || enmSpeaker == RTPINGPONGSPEAKER_PONG_SIGNALED
+ || enmSpeaker == RTPINGPONGSPEAKER_PING_SIGNALED,
+ ("Speaking out of turn! enmSpeaker=%d\n", enmSpeaker),
+ VERR_SEM_OUT_OF_TURN);
+
+ /*
+ * Wait.
+ */
+ int rc = RTSemEventWait(pPP->Ping, cMillies);
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PING);
+ Assert(rc != VERR_INTERRUPTED);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPingWait);
+
+
+RTDECL(int) RTSemPongWait(PRTPINGPONG pPP, RTMSINTERVAL cMillies)
+{
+ /*
+ * Validate input
+ */
+ RTPINGPONGSPEAKER enmSpeaker;
+ RTSEMPP_VALIDATE_RETURN(pPP);
+ AssertMsgReturn( enmSpeaker == RTPINGPONGSPEAKER_PING
+ || enmSpeaker == RTPINGPONGSPEAKER_PING_SIGNALED
+ || enmSpeaker == RTPINGPONGSPEAKER_PONG_SIGNALED,
+ ("Speaking out of turn! enmSpeaker=%d\n", enmSpeaker),
+ VERR_SEM_OUT_OF_TURN);
+
+ /*
+ * Wait.
+ */
+ int rc = RTSemEventWait(pPP->Pong, cMillies);
+ if (RT_SUCCESS(rc))
+ ASMAtomicWriteSize(&pPP->enmSpeaker, RTPINGPONGSPEAKER_PONG);
+ Assert(rc != VERR_INTERRUPTED);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTSemPongWait);
+
diff --git a/src/VBox/Runtime/common/misc/setjmp.asm b/src/VBox/Runtime/common/misc/setjmp.asm
new file mode 100644
index 00000000..f56136a0
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/setjmp.asm
@@ -0,0 +1,148 @@
+; $Id: setjmp.asm $
+;; @file
+; IPRT - No-CRT setjmp & longjmp - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+
+;;
+; @param x86:[esp+4] msc:rcx gcc:rdi The jump buffer pointer.
+RT_NOCRT_BEGINPROC setjmp
+%ifdef RT_ARCH_AMD64
+ %ifndef ASM_CALL64_MSC
+ mov rcx, rdi
+ %endif
+ mov rax, [rsp]
+ mov [rcx + 0h*8], rax ; 0 - rip
+ lea rdx, [rsp + 8]
+ mov [rcx + 1h*8], rdx ; 1 - rsp
+ mov [rcx + 2h*8], rbp
+ mov [rcx + 3h*8], r15
+ mov [rcx + 4h*8], r14
+ mov [rcx + 5h*8], r13
+ mov [rcx + 6h*8], r12
+ mov [rcx + 7h*8], rbx
+ %ifdef ASM_CALL64_MSC
+ mov [rcx + 8h*8], rsi
+ mov [rcx + 9h*8], rdi
+ movdqa [rcx + 0ah*8], xmm6
+ movdqa [rcx + 0ch*8], xmm7
+ movdqa [rcx + 0eh*8], xmm8
+ movdqa [rcx + 10h*8], xmm9
+ movdqa [rcx + 12h*8], xmm10
+ movdqa [rcx + 14h*8], xmm11
+ movdqa [rcx + 16h*8], xmm12
+ movdqa [rcx + 18h*8], xmm13
+ movdqa [rcx + 1ah*8], xmm14
+ movdqa [rcx + 1ch*8], xmm15
+ %ifndef RT_OS_WINDOWS
+ %error "Fix setjmp.h"
+ %endif
+ %endif
+%else
+ mov edx, [esp + 4h]
+ mov eax, [esp]
+ mov [edx + 0h*4], eax ; eip
+ lea ecx, [esp + 4h]
+ mov [edx + 1h*4], ecx ; esp
+ mov [edx + 2h*4], ebp
+ mov [edx + 3h*4], ebx
+ mov [edx + 4h*4], edi
+ mov [edx + 5h*4], esi
+%endif
+ xor eax, eax
+ ret
+ENDPROC RT_NOCRT(setjmp)
+
+
+;;
+; @param x86:[esp+4] msc:rcx gcc:rdi The jump buffer pointer.
+; @param x86:[esp+8] msc:rdx gcc:rsi Return value.
+RT_NOCRT_BEGINPROC longjmp
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov eax, edx ; ret
+ %else
+ mov rcx, rdi ; jmp_buf
+ mov eax, esi ; ret
+ %endif
+ mov rbp, [rcx + 2h*8]
+ mov r15, [rcx + 3h*8]
+ mov r14, [rcx + 4h*8]
+ mov r13, [rcx + 5h*8]
+ mov r12, [rcx + 6h*8]
+ mov rbx, [rcx + 7h*8]
+ %ifdef ASM_CALL64_MSC
+ mov rsi, [rcx + 8h*8]
+ mov rdi, [rcx + 9h*8]
+ movdqa xmm6, [rcx + 0ah*8]
+ movdqa xmm7, [rcx + 0ch*8]
+ movdqa xmm8, [rcx + 0eh*8]
+ movdqa xmm9, [rcx + 10h*8]
+ movdqa xmm10, [rcx + 12h*8]
+ movdqa xmm11, [rcx + 14h*8]
+ movdqa xmm12, [rcx + 16h*8]
+ movdqa xmm13, [rcx + 18h*8]
+ movdqa xmm14, [rcx + 1ah*8]
+ movdqa xmm15, [rcx + 1ch*8]
+ %ifndef RT_OS_WINDOWS
+ %error "Fix setjmp.h"
+ %endif
+ %endif
+ test eax, eax
+ jnz .fine
+ inc al
+.fine:
+ mov rsp, [rcx + 1h*8]
+ jmp qword [rcx + 0h*8]
+%else
+ mov edx, [esp + 4h] ; jmp_buf
+ mov eax, [esp + 8h] ; ret
+ mov esi, [edx + 5h*4]
+ mov edi, [edx + 4h*4]
+ mov ebx, [edx + 3h*4]
+ mov ebp, [edx + 2h*4]
+ test eax, eax
+ jnz .fine
+ inc al
+.fine:
+ mov esp, [edx + 1h*4]
+ jmp dword [edx+ 0h*4]
+%endif
+ENDPROC RT_NOCRT(longjmp)
+
diff --git a/src/VBox/Runtime/common/misc/sg.cpp b/src/VBox/Runtime/common/misc/sg.cpp
new file mode 100644
index 00000000..64829321
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/sg.cpp
@@ -0,0 +1,507 @@
+/* $Id: sg.cpp $ */
+/** @file
+ * IPRT - S/G buffer handling.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/sg.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+
+
+static void *rtSgBufGet(PRTSGBUF pSgBuf, size_t *pcbData)
+{
+ size_t cbData;
+ void *pvBuf;
+
+ /* Check that the S/G buffer has memory left. */
+ if (RT_UNLIKELY( pSgBuf->idxSeg == pSgBuf->cSegs
+ && !pSgBuf->cbSegLeft))
+ {
+ *pcbData = 0;
+ return NULL;
+ }
+
+#ifndef RDESKTOP
+ AssertMsg( pSgBuf->cbSegLeft <= 128 * _1M
+ && (uintptr_t)pSgBuf->pvSegCur >= (uintptr_t)pSgBuf->paSegs[pSgBuf->idxSeg].pvSeg
+ && (uintptr_t)pSgBuf->pvSegCur + pSgBuf->cbSegLeft <= (uintptr_t)pSgBuf->paSegs[pSgBuf->idxSeg].pvSeg + pSgBuf->paSegs[pSgBuf->idxSeg].cbSeg,
+ ("pSgBuf->idxSeg=%d pSgBuf->cSegs=%d pSgBuf->pvSegCur=%p pSgBuf->cbSegLeft=%zd pSgBuf->paSegs[%d].pvSeg=%p pSgBuf->paSegs[%d].cbSeg=%zd\n",
+ pSgBuf->idxSeg, pSgBuf->cSegs, pSgBuf->pvSegCur, pSgBuf->cbSegLeft, pSgBuf->idxSeg, pSgBuf->paSegs[pSgBuf->idxSeg].pvSeg, pSgBuf->idxSeg,
+ pSgBuf->paSegs[pSgBuf->idxSeg].cbSeg));
+#endif
+
+ cbData = RT_MIN(*pcbData, pSgBuf->cbSegLeft);
+ pvBuf = pSgBuf->pvSegCur;
+ pSgBuf->cbSegLeft -= cbData;
+
+ /* Advance to the next segment if required. */
+ if (!pSgBuf->cbSegLeft)
+ {
+ pSgBuf->idxSeg++;
+
+ if (pSgBuf->idxSeg < pSgBuf->cSegs)
+ {
+ pSgBuf->pvSegCur = pSgBuf->paSegs[pSgBuf->idxSeg].pvSeg;
+ pSgBuf->cbSegLeft = pSgBuf->paSegs[pSgBuf->idxSeg].cbSeg;
+ }
+
+ *pcbData = cbData;
+ }
+ else
+ pSgBuf->pvSegCur = (uint8_t *)pSgBuf->pvSegCur + cbData;
+
+ return pvBuf;
+}
+
+
+RTDECL(void) RTSgBufInit(PRTSGBUF pSgBuf, PCRTSGSEG paSegs, size_t cSegs)
+{
+ AssertPtr(pSgBuf);
+ Assert( (cSegs > 0 && RT_VALID_PTR(paSegs))
+ || (!cSegs && !paSegs));
+ Assert(cSegs < (~(unsigned)0 >> 1));
+
+ pSgBuf->paSegs = paSegs;
+ pSgBuf->cSegs = (unsigned)cSegs;
+ pSgBuf->idxSeg = 0;
+ if (cSegs && paSegs)
+ {
+ pSgBuf->pvSegCur = paSegs[0].pvSeg;
+ pSgBuf->cbSegLeft = paSegs[0].cbSeg;
+ }
+ else
+ {
+ pSgBuf->pvSegCur = NULL;
+ pSgBuf->cbSegLeft = 0;
+ }
+}
+
+
+RTDECL(void) RTSgBufReset(PRTSGBUF pSgBuf)
+{
+ AssertPtrReturnVoid(pSgBuf);
+
+ pSgBuf->idxSeg = 0;
+ if (pSgBuf->cSegs)
+ {
+ pSgBuf->pvSegCur = pSgBuf->paSegs[0].pvSeg;
+ pSgBuf->cbSegLeft = pSgBuf->paSegs[0].cbSeg;
+ }
+ else
+ {
+ pSgBuf->pvSegCur = NULL;
+ pSgBuf->cbSegLeft = 0;
+ }
+}
+
+
+RTDECL(void) RTSgBufClone(PRTSGBUF pSgBufTo, PCRTSGBUF pSgBufFrom)
+{
+ AssertPtr(pSgBufTo);
+ AssertPtr(pSgBufFrom);
+
+ pSgBufTo->paSegs = pSgBufFrom->paSegs;
+ pSgBufTo->cSegs = pSgBufFrom->cSegs;
+ pSgBufTo->idxSeg = pSgBufFrom->idxSeg;
+ pSgBufTo->pvSegCur = pSgBufFrom->pvSegCur;
+ pSgBufTo->cbSegLeft = pSgBufFrom->cbSegLeft;
+}
+
+
+RTDECL(void *) RTSgBufGetNextSegment(PRTSGBUF pSgBuf, size_t *pcbSeg)
+{
+ AssertPtrReturn(pSgBuf, NULL);
+ AssertPtrReturn(pcbSeg, NULL);
+
+ if (!*pcbSeg)
+ *pcbSeg = pSgBuf->cbSegLeft;
+
+ return rtSgBufGet(pSgBuf, pcbSeg);
+}
+
+
+RTDECL(size_t) RTSgBufCopy(PRTSGBUF pSgBufDst, PRTSGBUF pSgBufSrc, size_t cbCopy)
+{
+ AssertPtrReturn(pSgBufDst, 0);
+ AssertPtrReturn(pSgBufSrc, 0);
+
+ size_t cbLeft = cbCopy;
+ while (cbLeft)
+ {
+ size_t cbThisCopy = RT_MIN(RT_MIN(pSgBufDst->cbSegLeft, cbLeft), pSgBufSrc->cbSegLeft);
+ if (!cbThisCopy)
+ break;
+
+ size_t cbTmp = cbThisCopy;
+ void *pvBufDst = rtSgBufGet(pSgBufDst, &cbTmp);
+ Assert(cbTmp == cbThisCopy);
+ void *pvBufSrc = rtSgBufGet(pSgBufSrc, &cbTmp);
+ Assert(cbTmp == cbThisCopy);
+
+ memcpy(pvBufDst, pvBufSrc, cbThisCopy);
+
+ cbLeft -= cbThisCopy;
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(int) RTSgBufCmp(PCRTSGBUF pSgBuf1, PCRTSGBUF pSgBuf2, size_t cbCmp)
+{
+ AssertPtrReturn(pSgBuf1, 0);
+ AssertPtrReturn(pSgBuf2, 0);
+
+ /* Set up the temporary buffers */
+ RTSGBUF SgBuf1;
+ RTSgBufClone(&SgBuf1, pSgBuf1);
+ RTSGBUF SgBuf2;
+ RTSgBufClone(&SgBuf2, pSgBuf2);
+
+ size_t cbLeft = cbCmp;
+ while (cbLeft)
+ {
+ size_t cbThisCmp = RT_MIN(RT_MIN(SgBuf1.cbSegLeft, cbLeft), SgBuf2.cbSegLeft);
+ if (!cbThisCmp)
+ break;
+
+ size_t cbTmp = cbThisCmp;
+ void *pvBuf1 = rtSgBufGet(&SgBuf1, &cbTmp);
+ Assert(cbTmp == cbThisCmp);
+ void *pvBuf2 = rtSgBufGet(&SgBuf2, &cbTmp);
+ Assert(cbTmp == cbThisCmp);
+
+ int rc = memcmp(pvBuf1, pvBuf2, cbThisCmp);
+ if (rc)
+ return rc;
+
+ cbLeft -= cbThisCmp;
+ }
+
+ return 0;
+}
+
+
+RTDECL(int) RTSgBufCmpEx(PRTSGBUF pSgBuf1, PRTSGBUF pSgBuf2, size_t cbCmp, size_t *poffDiff, bool fAdvance)
+{
+ AssertPtrReturn(pSgBuf1, 0);
+ AssertPtrReturn(pSgBuf2, 0);
+
+ RTSGBUF SgBuf1Tmp;
+ RTSGBUF SgBuf2Tmp;
+ PRTSGBUF pSgBuf1Tmp;
+ PRTSGBUF pSgBuf2Tmp;
+
+ if (!fAdvance)
+ {
+ /* Set up the temporary buffers */
+ RTSgBufClone(&SgBuf1Tmp, pSgBuf1);
+ RTSgBufClone(&SgBuf2Tmp, pSgBuf2);
+ pSgBuf1Tmp = &SgBuf1Tmp;
+ pSgBuf2Tmp = &SgBuf2Tmp;
+ }
+ else
+ {
+ pSgBuf1Tmp = pSgBuf1;
+ pSgBuf2Tmp = pSgBuf2;
+ }
+
+ size_t cbLeft = cbCmp;
+ size_t off = 0;
+ while (cbLeft)
+ {
+ size_t cbThisCmp = RT_MIN(RT_MIN(pSgBuf1Tmp->cbSegLeft, cbLeft), pSgBuf2Tmp->cbSegLeft);
+ if (!cbThisCmp)
+ break;
+
+ size_t cbTmp = cbThisCmp;
+ uint8_t *pbBuf1 = (uint8_t *)rtSgBufGet(pSgBuf1Tmp, &cbTmp);
+ Assert(cbTmp == cbThisCmp);
+ uint8_t *pbBuf2 = (uint8_t *)rtSgBufGet(pSgBuf2Tmp, &cbTmp);
+ Assert(cbTmp == cbThisCmp);
+
+ int iDiff = memcmp(pbBuf1, pbBuf2, cbThisCmp);
+ if (iDiff)
+ {
+ /* Locate the first byte that differs if the caller requested this. */
+ if (poffDiff)
+ {
+ while ( cbThisCmp-- > 0
+ && *pbBuf1 == *pbBuf2)
+ {
+ pbBuf1++;
+ pbBuf2++;
+ off++;
+ }
+
+ *poffDiff = off;
+ }
+ return iDiff;
+ }
+
+ cbLeft -= cbThisCmp;
+ off += cbThisCmp;
+ }
+
+ return 0;
+}
+
+
+RTDECL(size_t) RTSgBufSet(PRTSGBUF pSgBuf, uint8_t ubFill, size_t cbSet)
+{
+ AssertPtrReturn(pSgBuf, 0);
+
+ size_t cbLeft = cbSet;
+
+ while (cbLeft)
+ {
+ size_t cbThisSet = cbLeft;
+ void *pvBuf = rtSgBufGet(pSgBuf, &cbThisSet);
+
+ if (!cbThisSet)
+ break;
+
+ memset(pvBuf, ubFill, cbThisSet);
+
+ cbLeft -= cbThisSet;
+ }
+
+ return cbSet - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufCopyToBuf(PRTSGBUF pSgBuf, void *pvBuf, size_t cbCopy)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pvBuf, 0);
+
+ size_t cbLeft = cbCopy;
+
+ while (cbLeft)
+ {
+ size_t cbThisCopy = cbLeft;
+ void *pvSrc = rtSgBufGet(pSgBuf, &cbThisCopy);
+
+ if (!cbThisCopy)
+ break;
+
+ memcpy(pvBuf, pvSrc, cbThisCopy);
+
+ cbLeft -= cbThisCopy;
+ pvBuf = (void *)((uintptr_t)pvBuf + cbThisCopy);
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufCopyFromBuf(PRTSGBUF pSgBuf, const void *pvBuf, size_t cbCopy)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pvBuf, 0);
+
+ size_t cbLeft = cbCopy;
+
+ while (cbLeft)
+ {
+ size_t cbThisCopy = cbLeft;
+ void *pvDst = rtSgBufGet(pSgBuf, &cbThisCopy);
+
+ if (!cbThisCopy)
+ break;
+
+ memcpy(pvDst, pvBuf, cbThisCopy);
+
+ cbLeft -= cbThisCopy;
+ pvBuf = (const void *)((uintptr_t)pvBuf + cbThisCopy);
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufCopyToFn(PRTSGBUF pSgBuf, size_t cbCopy, PFNRTSGBUFCOPYTO pfnCopyTo, void *pvUser)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pfnCopyTo, 0);
+
+ size_t cbLeft = cbCopy;
+
+ while (cbLeft)
+ {
+ size_t cbThisCopy = cbLeft;
+ void *pvSrc = rtSgBufGet(pSgBuf, &cbThisCopy);
+
+ if (!cbThisCopy)
+ break;
+
+ size_t cbThisCopied = pfnCopyTo(pSgBuf, pvSrc, cbThisCopy, pvUser);
+ cbLeft -= cbThisCopied;
+ if (cbThisCopied < cbThisCopy)
+ break;
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufCopyFromFn(PRTSGBUF pSgBuf, size_t cbCopy, PFNRTSGBUFCOPYFROM pfnCopyFrom, void *pvUser)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pfnCopyFrom, 0);
+
+ size_t cbLeft = cbCopy;
+
+ while (cbLeft)
+ {
+ size_t cbThisCopy = cbLeft;
+ void *pvDst = rtSgBufGet(pSgBuf, &cbThisCopy);
+
+ if (!cbThisCopy)
+ break;
+
+ size_t cbThisCopied = pfnCopyFrom(pSgBuf, pvDst, cbThisCopy, pvUser);
+ cbLeft -= cbThisCopied;
+ if (cbThisCopied < cbThisCopy)
+ break;
+ }
+
+ return cbCopy - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufAdvance(PRTSGBUF pSgBuf, size_t cbAdvance)
+{
+ AssertPtrReturn(pSgBuf, 0);
+
+ size_t cbLeft = cbAdvance;
+ while (cbLeft)
+ {
+ size_t cbThisAdvance = cbLeft;
+ rtSgBufGet(pSgBuf, &cbThisAdvance);
+ if (!cbThisAdvance)
+ break;
+
+ cbLeft -= cbThisAdvance;
+ }
+
+ return cbAdvance - cbLeft;
+}
+
+
+RTDECL(size_t) RTSgBufSegArrayCreate(PRTSGBUF pSgBuf, PRTSGSEG paSeg, unsigned *pcSeg, size_t cbData)
+{
+ AssertPtrReturn(pSgBuf, 0);
+ AssertPtrReturn(pcSeg, 0);
+
+ unsigned cSeg = 0;
+ size_t cb = 0;
+
+ if (!paSeg)
+ {
+ if (pSgBuf->cbSegLeft > 0)
+ {
+ size_t idx = pSgBuf->idxSeg;
+ cSeg = 1;
+
+ cb += RT_MIN(pSgBuf->cbSegLeft, cbData);
+ cbData -= RT_MIN(pSgBuf->cbSegLeft, cbData);
+
+ while ( cbData
+ && idx < pSgBuf->cSegs - 1)
+ {
+ idx++;
+ cSeg++;
+ cb += RT_MIN(pSgBuf->paSegs[idx].cbSeg, cbData);
+ cbData -= RT_MIN(pSgBuf->paSegs[idx].cbSeg, cbData);
+ }
+ }
+ }
+ else
+ {
+ while ( cbData
+ && cSeg < *pcSeg)
+ {
+ size_t cbThisSeg = cbData;
+ void *pvSeg = rtSgBufGet(pSgBuf, &cbThisSeg);
+
+ if (!cbThisSeg)
+ {
+ Assert(!pvSeg);
+ break;
+ }
+
+ AssertMsg(cbThisSeg <= cbData, ("Impossible!\n"));
+
+ paSeg[cSeg].cbSeg = cbThisSeg;
+ paSeg[cSeg].pvSeg = pvSeg;
+ cSeg++;
+ cbData -= cbThisSeg;
+ cb += cbThisSeg;
+ }
+ }
+
+ *pcSeg = cSeg;
+
+ return cb;
+}
+
+
+RTDECL(bool) RTSgBufIsZero(PRTSGBUF pSgBuf, size_t cbCheck)
+{
+ RTSGBUF SgBufTmp;
+ RTSgBufClone(&SgBufTmp, pSgBuf);
+
+ bool fIsZero = true;
+ size_t cbLeft = cbCheck;
+ while (cbLeft)
+ {
+ size_t cbThisCheck = cbLeft;
+ void *pvBuf = rtSgBufGet(&SgBufTmp, &cbThisCheck);
+ if (!cbThisCheck)
+ break;
+ fIsZero = ASMMemIsZero(pvBuf, cbThisCheck);
+ if (!fIsZero)
+ break;
+ cbLeft -= cbThisCheck;
+ }
+
+ return fIsZero;
+}
+
diff --git a/src/VBox/Runtime/common/misc/term.cpp b/src/VBox/Runtime/common/misc/term.cpp
new file mode 100644
index 00000000..e67b36a7
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/term.cpp
@@ -0,0 +1,252 @@
+/* $Id: term.cpp $ */
+/** @file
+ * IPRT - Common Termination Code.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/initterm.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a termination callback record. */
+typedef struct RTTERMCALLBACKREC *PRTTERMCALLBACKREC;
+/**
+ * Termination callback record.
+ */
+typedef struct RTTERMCALLBACKREC
+{
+ /** Pointer to the next record. */
+ PRTTERMCALLBACKREC pNext;
+ /** Pointer to the callback. */
+ PFNRTTERMCALLBACK pfnCallback;
+ /** The user argument. */
+ void *pvUser;
+} RTTERMCALLBACKREC;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Execute once construct protecting lazy callback initialization. */
+static RTONCE g_InitTermCallbacksOnce = RTONCE_INITIALIZER;
+/** Mutex protecting the callback globals. */
+static RTSEMFASTMUTEX g_hFastMutex = NIL_RTSEMFASTMUTEX;
+/** Number of registered callbacks. */
+static uint32_t g_cCallbacks = 0;
+/** The callback head. */
+static PRTTERMCALLBACKREC g_pCallbackHead = NULL;
+
+
+
+/**
+ * Initializes the globals.
+ *
+ * @returns IPRT status code
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int32_t) rtTermInitOnce(void *pvUser)
+{
+ RTSEMFASTMUTEX hFastMutex;
+ int rc;
+
+ Assert(!g_cCallbacks);
+ Assert(!g_pCallbackHead);
+ Assert(g_hFastMutex == NIL_RTSEMFASTMUTEX);
+
+ rc = RTSemFastMutexCreate(&hFastMutex);
+ if (RT_SUCCESS(rc))
+ g_hFastMutex = hFastMutex;
+
+ NOREF(pvUser);
+
+ return rc;
+}
+
+
+
+RTDECL(int) RTTermRegisterCallback(PFNRTTERMCALLBACK pfnCallback, void *pvUser)
+{
+ int rc;
+ PRTTERMCALLBACKREC pNew;
+
+ /*
+ * Validation and lazy init.
+ */
+ AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER);
+
+ rc = RTOnce(&g_InitTermCallbacksOnce, rtTermInitOnce, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Allocate and initialize a new callback record.
+ */
+ pNew = (PRTTERMCALLBACKREC)RTMemAlloc(sizeof(*pNew));
+ if (!pNew)
+ return VERR_NO_MEMORY;
+ pNew->pfnCallback = pfnCallback;
+ pNew->pvUser = pvUser;
+
+ /*
+ * Insert into the list.
+ */
+ rc = RTSemFastMutexRequest(g_hFastMutex);
+ if (RT_SUCCESS(rc))
+ {
+ g_cCallbacks++;
+ pNew->pNext = g_pCallbackHead;
+ g_pCallbackHead = pNew;
+
+ RTSemFastMutexRelease(g_hFastMutex);
+ }
+ else
+ RTMemFree(pNew);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTTermRegisterCallback);
+
+
+RTDECL(int) RTTermDeregisterCallback(PFNRTTERMCALLBACK pfnCallback, void *pvUser)
+{
+ /*
+ * g_hFastMutex will be NIL if we're not initialized.
+ */
+ int rc;
+ RTSEMFASTMUTEX hFastMutex = g_hFastMutex;
+ if (hFastMutex == NIL_RTSEMFASTMUTEX)
+ return VERR_NOT_FOUND;
+
+ rc = RTSemFastMutexRequest(hFastMutex);
+ if (RT_SUCCESS(rc))
+ {
+
+ /*
+ * Search for the specified pfnCallback/pvUser pair.
+ */
+ PRTTERMCALLBACKREC pPrev = NULL;
+ PRTTERMCALLBACKREC pCur = g_pCallbackHead;
+ while (pCur)
+ {
+ if ( pCur->pfnCallback == pfnCallback
+ && pCur->pvUser == pvUser)
+ {
+ if (pPrev)
+ pPrev->pNext = pCur->pNext;
+ else
+ g_pCallbackHead = pCur->pNext;
+ g_cCallbacks--;
+ RTSemFastMutexRelease(hFastMutex);
+
+ pCur->pfnCallback = NULL;
+ RTMemFree(pCur);
+ return VINF_SUCCESS;
+ }
+
+ /* next */
+ pPrev = pCur;
+ pCur = pCur->pNext;
+ }
+
+ RTSemFastMutexRelease(hFastMutex);
+ rc = VERR_NOT_FOUND;
+ }
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTTermDeregisterCallback);
+
+
+RTDECL(void) RTTermRunCallbacks(RTTERMREASON enmReason, int32_t iStatus)
+{
+ RTSEMFASTMUTEX hFastMutex;
+ Assert( enmReason == RTTERMREASON_EXIT
+ || enmReason == RTTERMREASON_ABEND
+ || enmReason == RTTERMREASON_SIGNAL
+ || enmReason == RTTERMREASON_UNLOAD);
+
+ /*
+ * Run the callback list. This is a bit paranoid in order to guard against
+ * recursive calls to RTTermRunCallbacks.
+ */
+ while (g_hFastMutex != NIL_RTSEMFASTMUTEX)
+ {
+ PRTTERMCALLBACKREC pCur;
+ RTTERMCALLBACKREC CurCopy;
+ int rc;
+
+ /* Unlink the head of the chain. */
+ rc = RTSemFastMutexRequest(g_hFastMutex);
+ AssertRCReturnVoid(rc);
+ pCur = g_pCallbackHead;
+ if (pCur)
+ {
+ g_pCallbackHead = pCur->pNext;
+ g_cCallbacks--;
+ }
+ RTSemFastMutexRelease(g_hFastMutex);
+ if (!pCur)
+ break;
+
+ /* Copy and free it. */
+ CurCopy = *pCur;
+ RTMemFree(pCur);
+
+ /* Make the call. */
+ CurCopy.pfnCallback(enmReason, iStatus, CurCopy.pvUser);
+ }
+
+ /*
+ * Free the lock.
+ */
+ ASMAtomicXchgHandle(&g_hFastMutex, NIL_RTSEMFASTMUTEX, &hFastMutex);
+ RTSemFastMutexDestroy(hFastMutex);
+ RTOnceReset(&g_InitTermCallbacksOnce); /* for the testcase */
+}
+RT_EXPORT_SYMBOL(RTTermRunCallbacks);
+
diff --git a/src/VBox/Runtime/common/misc/thread.cpp b/src/VBox/Runtime/common/misc/thread.cpp
new file mode 100644
index 00000000..c5d5f519
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/thread.cpp
@@ -0,0 +1,1446 @@
+/* $Id: thread.cpp $ */
+/** @file
+ * IPRT - Threads, common routines.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_THREAD
+#include <iprt/thread.h>
+#include "internal/iprt.h"
+
+#include <iprt/log.h>
+#include <iprt/avl.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/lockvalidator.h>
+#include <iprt/semaphore.h>
+#ifdef IN_RING0
+# include <iprt/spinlock.h>
+#endif
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include "internal/magics.h"
+#include "internal/thread.h"
+#include "internal/sched.h"
+#include "internal/process.h"
+#ifdef RT_WITH_ICONV_CACHE
+# include "internal/string.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef IN_RING0
+# define RT_THREAD_LOCK_RW() RTSpinlockAcquire(g_ThreadSpinlock)
+# define RT_THREAD_UNLOCK_RW() RTSpinlockRelease(g_ThreadSpinlock)
+# define RT_THREAD_LOCK_RD() RTSpinlockAcquire(g_ThreadSpinlock)
+# define RT_THREAD_UNLOCK_RD() RTSpinlockRelease(g_ThreadSpinlock)
+#else
+# define RT_THREAD_LOCK_RW() rtThreadLockRW()
+# define RT_THREAD_UNLOCK_RW() rtThreadUnLockRW()
+# define RT_THREAD_LOCK_RD() rtThreadLockRD()
+# define RT_THREAD_UNLOCK_RD() rtThreadUnLockRD()
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Indicates whether we've been initialized or not. */
+static bool g_frtThreadInitialized;
+#ifdef IN_RING3
+/** The RW lock protecting the tree. */
+static RTSEMRW g_ThreadRWSem = NIL_RTSEMRW;
+#else
+/** The spinlocks protecting the tree. */
+static RTSPINLOCK g_ThreadSpinlock = NIL_RTSPINLOCK;
+#endif
+/** The AVL thread containing the threads. */
+static PAVLPVNODECORE g_ThreadTree;
+/** The number of threads in the tree (for ring-0 termination kludge). */
+static uint32_t volatile g_cThreadInTree;
+/** Counters for each thread type. */
+DECL_HIDDEN_DATA(uint32_t volatile) g_acRTThreadTypeStats[RTTHREADTYPE_END];
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void rtThreadDestroy(PRTTHREADINT pThread);
+#ifdef IN_RING3
+static int rtThreadAdopt(RTTHREADTYPE enmType, unsigned fFlags, uint32_t fIntFlags, const char *pszName);
+#endif
+static void rtThreadRemoveLocked(PRTTHREADINT pThread);
+static PRTTHREADINT rtThreadAlloc(RTTHREADTYPE enmType, unsigned fFlags, uint32_t fIntFlags, const char *pszName);
+
+
+/** @page pg_rt_thread IPRT Thread Internals
+ *
+ * IPRT provides interface to whatever native threading that the host provides,
+ * preferably using a CRT level interface to better integrate with other libraries.
+ *
+ * Internally IPRT keeps track of threads by means of the RTTHREADINT structure.
+ * All the RTTHREADINT structures are kept in a AVL tree which is protected by a
+ * read/write lock for efficient access. A thread is inserted into the tree in
+ * three places in the code. The main thread is 'adopted' by IPRT on rtR3Init()
+ * by rtThreadAdopt(). When creating a new thread there the child and the parent
+ * race inserting the thread, this is rtThreadMain() and RTThreadCreate.
+ *
+ * RTTHREADINT objects are using reference counting as a mean of sticking around
+ * till no-one needs them any longer. Waitable threads is created with one extra
+ * reference so they won't go away until they are waited on. This introduces a
+ * major problem if we use the host thread identifier as key in the AVL tree - the
+ * host may reuse the thread identifier before the thread was waited on. So, on
+ * most platforms we are using the RTTHREADINT pointer as key and not the
+ * thread id. RTThreadSelf() then have to be implemented using a pointer stored
+ * in thread local storage (TLS).
+ *
+ * In Ring-0 we only try keep track of kernel threads created by RTThreadCreate
+ * at the moment. There we really only need the 'join' feature, but doing things
+ * the same way allow us to name threads and similar stuff.
+ */
+
+
+/**
+ * Initializes the thread database.
+ *
+ * @returns iprt status code.
+ */
+DECLHIDDEN(int) rtThreadInit(void)
+{
+#ifdef IN_RING3
+ int rc = VINF_ALREADY_INITIALIZED;
+ if (g_ThreadRWSem == NIL_RTSEMRW)
+ {
+ /*
+ * We assume the caller is the 1st thread, which we'll call 'main'.
+ * But first, we'll create the semaphore.
+ */
+ rc = RTSemRWCreateEx(&g_ThreadRWSem, RTSEMRW_FLAGS_NO_LOCK_VAL, NIL_RTLOCKVALCLASS, RTLOCKVAL_SUB_CLASS_NONE, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtThreadNativeInit();
+ if (RT_SUCCESS(rc))
+ rc = rtThreadAdopt(RTTHREADTYPE_DEFAULT, 0, RTTHREADINT_FLAGS_MAIN, "main");
+ if (RT_SUCCESS(rc))
+ rc = rtSchedNativeCalcDefaultPriority(RTTHREADTYPE_DEFAULT);
+ if (RT_SUCCESS(rc))
+ {
+ g_frtThreadInitialized = true;
+ return VINF_SUCCESS;
+ }
+
+ /* failed, clear out */
+ RTSemRWDestroy(g_ThreadRWSem);
+ g_ThreadRWSem = NIL_RTSEMRW;
+ }
+ }
+
+#elif defined(IN_RING0)
+ int rc;
+ /*
+ * Create the spinlock and to native init.
+ */
+ Assert(g_ThreadSpinlock == NIL_RTSPINLOCK);
+ rc = RTSpinlockCreate(&g_ThreadSpinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "RTThread");
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtThreadNativeInit();
+ if (RT_SUCCESS(rc))
+ {
+ g_frtThreadInitialized = true;
+ return VINF_SUCCESS;
+ }
+
+ /* failed, clear out */
+ RTSpinlockDestroy(g_ThreadSpinlock);
+ g_ThreadSpinlock = NIL_RTSPINLOCK;
+ }
+#else
+# error "!IN_RING0 && !IN_RING3"
+#endif
+ return rc;
+}
+
+
+#ifdef IN_RING3
+/**
+ * Called when IPRT was first initialized in unobtrusive mode and later changed
+ * to obtrustive.
+ *
+ * This is only applicable in ring-3.
+ */
+DECLHIDDEN(void) rtThreadReInitObtrusive(void)
+{
+ rtThreadNativeReInitObtrusive();
+}
+#endif
+
+
+/**
+ * Terminates the thread database.
+ */
+DECLHIDDEN(void) rtThreadTerm(void)
+{
+#ifdef IN_RING3
+ /* we don't cleanup here yet */
+
+#elif defined(IN_RING0)
+ /* just destroy the spinlock and assume the thread is fine... */
+ RTSpinlockDestroy(g_ThreadSpinlock);
+ g_ThreadSpinlock = NIL_RTSPINLOCK;
+ if (g_ThreadTree != NULL)
+ RTAssertMsg2Weak("WARNING: g_ThreadTree=%p\n", g_ThreadTree);
+#endif
+}
+
+
+#ifdef IN_RING3
+
+DECLINLINE(void) rtThreadLockRW(void)
+{
+ if (g_ThreadRWSem == NIL_RTSEMRW)
+ rtThreadInit();
+ int rc = RTSemRWRequestWrite(g_ThreadRWSem, RT_INDEFINITE_WAIT);
+ AssertReleaseRC(rc);
+}
+
+
+DECLINLINE(void) rtThreadLockRD(void)
+{
+ if (g_ThreadRWSem == NIL_RTSEMRW)
+ rtThreadInit();
+ int rc = RTSemRWRequestRead(g_ThreadRWSem, RT_INDEFINITE_WAIT);
+ AssertReleaseRC(rc);
+}
+
+
+DECLINLINE(void) rtThreadUnLockRW(void)
+{
+ int rc = RTSemRWReleaseWrite(g_ThreadRWSem);
+ AssertReleaseRC(rc);
+}
+
+
+DECLINLINE(void) rtThreadUnLockRD(void)
+{
+ int rc = RTSemRWReleaseRead(g_ThreadRWSem);
+ AssertReleaseRC(rc);
+}
+
+
+/**
+ * Adopts the calling thread.
+ * No locks are taken or released by this function.
+ */
+static int rtThreadAdopt(RTTHREADTYPE enmType, unsigned fFlags, uint32_t fIntFlags, const char *pszName)
+{
+ int rc;
+ PRTTHREADINT pThread;
+ Assert(!(fFlags & RTTHREADFLAGS_WAITABLE));
+ fFlags &= ~RTTHREADFLAGS_WAITABLE;
+
+ /*
+ * Allocate and insert the thread.
+ * (It is vital that rtThreadNativeAdopt updates the TLS before
+ * we try inserting the thread because of locking.)
+ */
+ rc = VERR_NO_MEMORY;
+ pThread = rtThreadAlloc(enmType, fFlags, RTTHREADINT_FLAGS_ALIEN | fIntFlags, pszName);
+ if (pThread)
+ {
+ RTNATIVETHREAD NativeThread = RTThreadNativeSelf();
+ rc = rtThreadNativeAdopt(pThread);
+ if (RT_SUCCESS(rc))
+ {
+ rtThreadInsert(pThread, NativeThread);
+ rtThreadSetState(pThread, RTTHREADSTATE_RUNNING);
+ rtThreadRelease(pThread);
+ }
+ else
+ rtThreadDestroy(pThread);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTThreadAdopt(RTTHREADTYPE enmType, unsigned fFlags, const char *pszName, PRTTHREAD pThread)
+{
+ int rc;
+ RTTHREAD Thread;
+
+ AssertReturn(!(fFlags & RTTHREADFLAGS_WAITABLE), VERR_INVALID_FLAGS);
+ AssertPtrNullReturn(pszName, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pThread, VERR_INVALID_POINTER);
+
+ rc = VINF_SUCCESS;
+ Thread = RTThreadSelf();
+ if (Thread == NIL_RTTHREAD)
+ {
+ /* generate a name if none was given. */
+ char szName[RTTHREAD_NAME_LEN];
+ if (!pszName || !*pszName)
+ {
+ static uint32_t s_i32AlienId = 0;
+ uint32_t i32Id = ASMAtomicIncU32(&s_i32AlienId);
+ RTStrPrintf(szName, sizeof(szName), "ALIEN-%RX32", i32Id);
+ pszName = szName;
+ }
+
+ /* try adopt it */
+ rc = rtThreadAdopt(enmType, fFlags, 0, pszName);
+ Thread = RTThreadSelf();
+
+ /* Don't too early during init, as rtLogLock may end up here and cause endless recursion. */
+ if (rc != VERR_FAILED_TO_SET_SELF_TLS)
+ Log(("RTThreadAdopt: %RTthrd %RTnthrd '%s' enmType=%d fFlags=%#x rc=%Rrc\n",
+ Thread, RTThreadNativeSelf(), pszName, enmType, fFlags, rc));
+ }
+ else
+ Log(("RTThreadAdopt: %RTthrd %RTnthrd '%s' enmType=%d fFlags=%#x - already adopted!\n",
+ Thread, RTThreadNativeSelf(), pszName, enmType, fFlags));
+
+ if (pThread)
+ *pThread = Thread;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadAdopt);
+
+
+RTDECL(RTTHREAD) RTThreadSelfAutoAdopt(void)
+{
+ RTTHREAD hSelf = RTThreadSelf();
+ if (RT_UNLIKELY(hSelf == NIL_RTTHREAD))
+ RTThreadAdopt(RTTHREADTYPE_DEFAULT, 0, NULL, &hSelf);
+ return hSelf;
+}
+RT_EXPORT_SYMBOL(RTThreadSelfAutoAdopt);
+
+#endif /* IN_RING3 */
+
+/**
+ * Allocates a per thread data structure and initializes the basic fields.
+ *
+ * @returns Pointer to per thread data structure.
+ * This is reference once.
+ * @returns NULL on failure.
+ * @param enmType The thread type.
+ * @param fFlags The thread flags.
+ * @param fIntFlags The internal thread flags.
+ * @param pszName Pointer to the thread name.
+ */
+PRTTHREADINT rtThreadAlloc(RTTHREADTYPE enmType, unsigned fFlags, uint32_t fIntFlags, const char *pszName)
+{
+ PRTTHREADINT pThread = (PRTTHREADINT)RTMemAllocZ(sizeof(RTTHREADINT));
+ if (pThread)
+ {
+ size_t cchName;
+ int rc;
+
+ pThread->Core.Key = (void*)NIL_RTTHREAD;
+ pThread->u32Magic = RTTHREADINT_MAGIC;
+ cchName = strlen(pszName);
+ if (cchName >= RTTHREAD_NAME_LEN)
+ cchName = RTTHREAD_NAME_LEN - 1;
+ memcpy(pThread->szName, pszName, cchName);
+ pThread->szName[cchName] = '\0';
+ pThread->cRefs = 2 + !!(fFlags & RTTHREADFLAGS_WAITABLE); /* And extra reference if waitable. */
+ pThread->rc = VERR_PROCESS_RUNNING; /** @todo get a better error code! */
+ pThread->enmType = enmType;
+ pThread->fFlags = fFlags;
+ pThread->fIntFlags = fIntFlags;
+ pThread->enmState = RTTHREADSTATE_INITIALIZING;
+ pThread->fReallySleeping = false;
+#ifdef IN_RING3
+ rtLockValidatorInitPerThread(&pThread->LockValidator);
+#endif
+#ifdef RT_WITH_ICONV_CACHE
+ rtStrIconvCacheInit(pThread);
+#endif
+#if defined(IPRT_NO_CRT) && defined(IN_RING3)
+ pThread->NoCrt.enmAllocType = RTNOCRTTHREADDATA::kAllocType_Embedded;
+ RTListInit(&pThread->NoCrt.ListEntry);
+#endif
+ rc = RTSemEventMultiCreate(&pThread->EventUser);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventMultiCreate(&pThread->EventTerminated);
+ if (RT_SUCCESS(rc))
+ return pThread;
+ RTSemEventMultiDestroy(pThread->EventUser);
+ }
+ RTMemFree(pThread);
+ }
+ return NULL;
+}
+
+
+/**
+ * Insert the per thread data structure into the tree.
+ *
+ * This can be called from both the thread it self and the parent,
+ * thus it must handle insertion failures in a nice manner.
+ *
+ * @param pThread Pointer to thread structure allocated by rtThreadAlloc().
+ * @param NativeThread The native thread id.
+ */
+DECLHIDDEN(void) rtThreadInsert(PRTTHREADINT pThread, RTNATIVETHREAD NativeThread)
+{
+ Assert(pThread);
+ Assert(pThread->u32Magic == RTTHREADINT_MAGIC);
+
+ {
+ RT_THREAD_LOCK_RW();
+
+ /*
+ * Do not insert a terminated thread.
+ *
+ * This may happen if the thread finishes before the RTThreadCreate call
+ * gets this far. Since the OS may quickly reuse the native thread ID
+ * it should not be reinserted at this point.
+ */
+ if (rtThreadGetState(pThread) != RTTHREADSTATE_TERMINATED)
+ {
+ /*
+ * Before inserting we must check if there is a thread with this id
+ * in the tree already. We're racing parent and child on insert here
+ * so that the handle is valid in both ends when they return / start.
+ *
+ * If it's not ourself we find, it's a dead alien thread and we will
+ * unlink it from the tree. Alien threads will be released at this point.
+ */
+ PRTTHREADINT pThreadOther = (PRTTHREADINT)RTAvlPVGet(&g_ThreadTree, (void *)NativeThread);
+ if (pThreadOther != pThread)
+ {
+ bool fRc;
+ /* remove dead alien if any */
+ if (pThreadOther)
+ {
+ AssertMsg(pThreadOther->fIntFlags & RTTHREADINT_FLAGS_ALIEN, ("%p:%s; %p:%s\n", pThread, pThread->szName, pThreadOther, pThreadOther->szName));
+ ASMAtomicBitClear(&pThread->fIntFlags, RTTHREADINT_FLAG_IN_TREE_BIT);
+ rtThreadRemoveLocked(pThreadOther);
+ if (pThreadOther->fIntFlags & RTTHREADINT_FLAGS_ALIEN)
+ rtThreadRelease(pThreadOther);
+ }
+
+ /* insert the thread */
+ ASMAtomicWritePtr(&pThread->Core.Key, (void *)NativeThread);
+ fRc = RTAvlPVInsert(&g_ThreadTree, &pThread->Core);
+ ASMAtomicOrU32(&pThread->fIntFlags, RTTHREADINT_FLAG_IN_TREE);
+ if (fRc)
+ {
+ ASMAtomicIncU32(&g_cThreadInTree);
+ ASMAtomicIncU32(&g_acRTThreadTypeStats[pThread->enmType]);
+
+#if defined(IPRT_NO_CRT) && defined(IN_RING3)
+ RTTLS const iTlsPerThread = g_iTlsRtNoCrtPerThread;
+ if ( iTlsPerThread != NIL_RTTLS
+ && RTTlsGet(iTlsPerThread) == NULL)
+ RTTlsSet(iTlsPerThread, &pThread->NoCrt);
+#endif
+ }
+
+ AssertReleaseMsg(fRc, ("Lock problem? %p (%RTnthrd) %s\n", pThread, NativeThread, pThread->szName));
+ NOREF(fRc);
+ }
+ }
+
+ RT_THREAD_UNLOCK_RW();
+ }
+}
+
+
+/**
+ * Removes the thread from the AVL tree, call owns the tree lock
+ * and has cleared the RTTHREADINT_FLAG_IN_TREE bit.
+ *
+ * @param pThread The thread to remove.
+ */
+static void rtThreadRemoveLocked(PRTTHREADINT pThread)
+{
+ PRTTHREADINT pThread2 = (PRTTHREADINT)RTAvlPVRemove(&g_ThreadTree, pThread->Core.Key);
+ AssertMsg(pThread2 == pThread, ("%p(%s) != %p (%p/%s)\n", pThread2, pThread2 ? pThread2->szName : "<null>",
+ pThread, pThread->Core.Key, pThread->szName));
+ if (pThread2)
+ {
+ ASMAtomicDecU32(&g_cThreadInTree);
+ ASMAtomicDecU32(&g_acRTThreadTypeStats[pThread->enmType]);
+ }
+}
+
+
+/**
+ * Removes the thread from the AVL tree.
+ *
+ * @param pThread The thread to remove.
+ */
+static void rtThreadRemove(PRTTHREADINT pThread)
+{
+ RT_THREAD_LOCK_RW();
+ if (ASMAtomicBitTestAndClear(&pThread->fIntFlags, RTTHREADINT_FLAG_IN_TREE_BIT))
+ rtThreadRemoveLocked(pThread);
+ RT_THREAD_UNLOCK_RW();
+}
+
+
+/**
+ * Checks if a thread is alive or not.
+ *
+ * @returns true if the thread is alive (or we don't really know).
+ * @returns false if the thread has surely terminate.
+ */
+DECLINLINE(bool) rtThreadIsAlive(PRTTHREADINT pThread)
+{
+ return !(pThread->fIntFlags & RTTHREADINT_FLAGS_TERMINATED);
+}
+
+
+/**
+ * Gets a thread by it's native ID.
+ *
+ * @returns pointer to the thread structure.
+ * @returns NULL if not a thread IPRT knows.
+ * @param NativeThread The native thread id.
+ */
+DECLHIDDEN(PRTTHREADINT) rtThreadGetByNative(RTNATIVETHREAD NativeThread)
+{
+ PRTTHREADINT pThread;
+ /*
+ * Simple tree lookup.
+ */
+ RT_THREAD_LOCK_RD();
+ pThread = (PRTTHREADINT)RTAvlPVGet(&g_ThreadTree, (void *)NativeThread);
+ RT_THREAD_UNLOCK_RD();
+ return pThread;
+}
+
+
+/**
+ * Gets the per thread data structure for a thread handle.
+ *
+ * @returns Pointer to the per thread data structure for Thread.
+ * The caller must release the thread using rtThreadRelease().
+ * @returns NULL if Thread was not found.
+ * @param Thread Thread id which structure is to be returned.
+ */
+DECLHIDDEN(PRTTHREADINT) rtThreadGet(RTTHREAD Thread)
+{
+ if ( Thread != NIL_RTTHREAD
+ && RT_VALID_PTR(Thread))
+ {
+ PRTTHREADINT pThread = (PRTTHREADINT)Thread;
+ if ( pThread->u32Magic == RTTHREADINT_MAGIC
+ && pThread->cRefs > 0)
+ {
+ ASMAtomicIncU32(&pThread->cRefs);
+ return pThread;
+ }
+ }
+
+ AssertMsgFailed(("Thread=%RTthrd\n", Thread));
+ return NULL;
+}
+
+/**
+ * Release a per thread data structure.
+ *
+ * @returns New reference count.
+ * @param pThread The thread structure to release.
+ */
+DECLHIDDEN(uint32_t) rtThreadRelease(PRTTHREADINT pThread)
+{
+ uint32_t cRefs;
+
+ Assert(pThread);
+ if (pThread->cRefs >= 1)
+ {
+ cRefs = ASMAtomicDecU32(&pThread->cRefs);
+ if (!cRefs)
+ rtThreadDestroy(pThread);
+ }
+ else
+ {
+ cRefs = 0;
+ AssertFailed();
+ }
+ return cRefs;
+}
+
+
+/**
+ * Destroys the per thread data.
+ *
+ * @param pThread The thread to destroy.
+ */
+static void rtThreadDestroy(PRTTHREADINT pThread)
+{
+ RTSEMEVENTMULTI hEvt1, hEvt2;
+ /*
+ * Remove it from the tree and mark it as dead.
+ *
+ * Threads that has seen rtThreadTerminate and should already have been
+ * removed from the tree. There is probably no thread that should
+ * require removing here. However, be careful making sure that cRefs
+ * isn't 0 if we do or we'll blow up because the strict locking code
+ * will be calling us back.
+ */
+ if (ASMBitTest(&pThread->fIntFlags, RTTHREADINT_FLAG_IN_TREE_BIT))
+ {
+ ASMAtomicIncU32(&pThread->cRefs);
+ rtThreadRemove(pThread);
+ ASMAtomicDecU32(&pThread->cRefs);
+ }
+
+ /*
+ * Invalidate the thread structure.
+ */
+#ifdef IN_RING3
+ rtLockValidatorSerializeDestructEnter();
+
+ rtLockValidatorDeletePerThread(&pThread->LockValidator);
+#endif
+#ifdef RT_WITH_ICONV_CACHE
+ rtStrIconvCacheDestroy(pThread);
+#endif
+ ASMAtomicXchgU32(&pThread->u32Magic, RTTHREADINT_MAGIC_DEAD);
+ ASMAtomicWritePtr(&pThread->Core.Key, (void *)NIL_RTTHREAD);
+ pThread->enmType = RTTHREADTYPE_INVALID;
+ hEvt1 = pThread->EventUser;
+ pThread->EventUser = NIL_RTSEMEVENTMULTI;
+ hEvt2 = pThread->EventTerminated;
+ pThread->EventTerminated = NIL_RTSEMEVENTMULTI;
+
+#ifdef IN_RING3
+ rtLockValidatorSerializeDestructLeave();
+#endif
+
+ /*
+ * Destroy semaphore resources and free the bugger.
+ */
+ RTSemEventMultiDestroy(hEvt1);
+ if (hEvt2 != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiDestroy(hEvt2);
+
+ rtThreadNativeDestroy(pThread);
+ RTMemFree(pThread);
+}
+
+
+/**
+ * Terminates the thread.
+ * Called by the thread wrapper function when the thread terminates.
+ *
+ * @param pThread The thread structure.
+ * @param rc The thread result code.
+ */
+DECLHIDDEN(void) rtThreadTerminate(PRTTHREADINT pThread, int rc)
+{
+ Assert(pThread->cRefs >= 1);
+
+ /*
+ * Destroy TLS entries.
+ */
+#ifdef IPRT_WITH_GENERIC_TLS
+ rtThreadTlsDestruction(pThread);
+#elif defined(RT_OS_WINDOWS) && defined(IN_RING3)
+ rtThreadWinTlsDestruction();
+#endif
+
+ /*
+ * Set the rc, mark it terminated and signal anyone waiting.
+ */
+ pThread->rc = rc;
+ rtThreadSetState(pThread, RTTHREADSTATE_TERMINATED);
+ ASMAtomicOrU32(&pThread->fIntFlags, RTTHREADINT_FLAGS_TERMINATED);
+ if (pThread->EventTerminated != NIL_RTSEMEVENTMULTI)
+ RTSemEventMultiSignal(pThread->EventTerminated);
+
+ /*
+ * Remove the thread from the tree so that there will be no
+ * key clashes in the AVL tree and release our reference to ourself.
+ */
+ rtThreadRemove(pThread);
+
+#if defined(IPRT_NO_CRT) && defined(IN_RING3)
+ RTTLS const iTlsPerThread = g_iTlsRtNoCrtPerThread;
+ if ( iTlsPerThread != NIL_RTTLS
+ && RTTlsGet(iTlsPerThread) == &pThread->NoCrt)
+ RTTlsSet(iTlsPerThread, &g_RtNoCrtPerThreadDummy);
+#endif
+
+ rtThreadRelease(pThread);
+}
+
+
+/**
+ * The common thread main function.
+ * This is called by rtThreadNativeMain().
+ *
+ * @returns The status code of the thread.
+ * pThread is dereference by the thread before returning!
+ * @param pThread The thread structure.
+ * @param NativeThread The native thread id.
+ * @param pszThreadName The name of the thread (purely a dummy for backtrace).
+ */
+DECL_HIDDEN_CALLBACK(int) rtThreadMain(PRTTHREADINT pThread, RTNATIVETHREAD NativeThread, const char *pszThreadName)
+{
+ int rc;
+ NOREF(pszThreadName);
+ rtThreadInsert(pThread, NativeThread);
+ Log(("rtThreadMain: Starting: pThread=%p NativeThread=%RTnthrd Name=%s pfnThread=%p pvUser=%p\n",
+ pThread, NativeThread, pThread->szName, pThread->pfnThread, pThread->pvUser));
+
+ /*
+ * Change the priority.
+ */
+ rc = rtThreadNativeSetPriority(pThread, pThread->enmType);
+#ifdef IN_RING3
+ AssertMsgRC(rc, ("Failed to set priority of thread %p (%RTnthrd / %s) to enmType=%d enmPriority=%d rc=%Rrc\n",
+ pThread, NativeThread, pThread->szName, pThread->enmType, g_enmProcessPriority, rc));
+#else
+ AssertMsgRC(rc, ("Failed to set priority of thread %p (%RTnthrd / %s) to enmType=%d rc=%Rrc\n",
+ pThread, NativeThread, pThread->szName, pThread->enmType, rc));
+#endif
+
+ /*
+ * Call thread function and terminate when it returns.
+ */
+ rtThreadSetState(pThread, RTTHREADSTATE_RUNNING);
+ rc = pThread->pfnThread(pThread, pThread->pvUser);
+
+ /*
+ * Paranoia checks for leftover resources.
+ */
+#ifdef RTSEMRW_STRICT
+ int32_t cWrite = ASMAtomicReadS32(&pThread->cWriteLocks);
+ Assert(!cWrite);
+ int32_t cRead = ASMAtomicReadS32(&pThread->cReadLocks);
+ Assert(!cRead);
+#endif
+
+ Log(("rtThreadMain: Terminating: rc=%d pThread=%p NativeThread=%RTnthrd Name=%s pfnThread=%p pvUser=%p\n",
+ rc, pThread, NativeThread, pThread->szName, pThread->pfnThread, pThread->pvUser));
+ rtThreadTerminate(pThread, rc);
+ return rc;
+}
+
+
+RTDECL(int) RTThreadCreate(PRTTHREAD pThread, PFNRTTHREAD pfnThread, void *pvUser, size_t cbStack,
+ RTTHREADTYPE enmType, unsigned fFlags, const char *pszName)
+{
+ int rc;
+ PRTTHREADINT pThreadInt;
+
+ LogFlow(("RTThreadCreate: pThread=%p pfnThread=%p pvUser=%p cbStack=%#x enmType=%d fFlags=%#x pszName=%p:{%s}\n",
+ pThread, pfnThread, pvUser, cbStack, enmType, fFlags, pszName, pszName));
+
+ /*
+ * Validate input.
+ */
+ AssertPtrNullReturn(pThread, VERR_INVALID_POINTER);
+ AssertPtrReturn(pfnThread, VERR_INVALID_POINTER);
+ AssertMsgReturn(pszName && *pszName != '\0' && strlen(pszName) < RTTHREAD_NAME_LEN,
+ ("pszName=%s (max len is %d because of logging)\n", pszName, RTTHREAD_NAME_LEN - 1),
+ VERR_INVALID_PARAMETER);
+ AssertMsgReturn(!(fFlags & ~RTTHREADFLAGS_MASK), ("fFlags=%#x\n", fFlags), VERR_INVALID_FLAGS);
+
+ /*
+ * Allocate thread argument.
+ */
+ pThreadInt = rtThreadAlloc(enmType, fFlags, 0, pszName);
+ if (pThreadInt)
+ {
+ RTNATIVETHREAD NativeThread;
+
+ pThreadInt->pfnThread = pfnThread;
+ pThreadInt->pvUser = pvUser;
+ pThreadInt->cbStack = cbStack;
+
+ rc = rtThreadNativeCreate(pThreadInt, &NativeThread);
+ if (RT_SUCCESS(rc))
+ {
+ rtThreadInsert(pThreadInt, NativeThread);
+ rtThreadRelease(pThreadInt);
+ Log(("RTThreadCreate: Created thread %p (%p) %s\n", pThreadInt, NativeThread, pszName));
+ if (pThread)
+ *pThread = pThreadInt;
+ return VINF_SUCCESS;
+ }
+
+ pThreadInt->cRefs = 1;
+ rtThreadRelease(pThreadInt);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ LogFlow(("RTThreadCreate: Failed to create thread, rc=%Rrc\n", rc));
+ AssertReleaseRC(rc);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadCreate);
+
+
+RTDECL(int) RTThreadCreateV(PRTTHREAD pThread, PFNRTTHREAD pfnThread, void *pvUser, size_t cbStack,
+ RTTHREADTYPE enmType, uint32_t fFlags, const char *pszNameFmt, va_list va)
+{
+ char szName[RTTHREAD_NAME_LEN * 2];
+ RTStrPrintfV(szName, sizeof(szName), pszNameFmt, va);
+ return RTThreadCreate(pThread, pfnThread, pvUser, cbStack, enmType, fFlags, szName);
+}
+RT_EXPORT_SYMBOL(RTThreadCreateV);
+
+
+RTDECL(int) RTThreadCreateF(PRTTHREAD pThread, PFNRTTHREAD pfnThread, void *pvUser, size_t cbStack,
+ RTTHREADTYPE enmType, uint32_t fFlags, const char *pszNameFmt, ...)
+{
+ va_list va;
+ int rc;
+ va_start(va, pszNameFmt);
+ rc = RTThreadCreateV(pThread, pfnThread, pvUser, cbStack, enmType, fFlags, pszNameFmt, va);
+ va_end(va);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadCreateF);
+
+
+RTDECL(RTNATIVETHREAD) RTThreadGetNative(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ RTNATIVETHREAD NativeThread = (RTNATIVETHREAD)pThread->Core.Key;
+ rtThreadRelease(pThread);
+ return NativeThread;
+ }
+ return NIL_RTNATIVETHREAD;
+}
+RT_EXPORT_SYMBOL(RTThreadGetNative);
+
+
+RTDECL(RTTHREAD) RTThreadFromNative(RTNATIVETHREAD NativeThread)
+{
+ PRTTHREADINT pThread = rtThreadGetByNative(NativeThread);
+ if (pThread)
+ return pThread;
+ return NIL_RTTHREAD;
+}
+RT_EXPORT_SYMBOL(RTThreadFromNative);
+
+
+RTDECL(const char *) RTThreadSelfName(void)
+{
+ RTTHREAD Thread = RTThreadSelf();
+ if (Thread != NIL_RTTHREAD)
+ {
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ const char *pszName = pThread->szName;
+ rtThreadRelease(pThread);
+ return pszName;
+ }
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTThreadSelfName);
+
+
+RTDECL(const char *) RTThreadGetName(RTTHREAD Thread)
+{
+ PRTTHREADINT pThread;
+ if (Thread == NIL_RTTHREAD)
+ return NULL;
+ pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ const char *szName = pThread->szName;
+ rtThreadRelease(pThread);
+ return szName;
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTThreadGetName);
+
+
+RTDECL(int) RTThreadSetName(RTTHREAD Thread, const char *pszName)
+{
+ /*
+ * Validate input.
+ */
+ PRTTHREADINT pThread;
+ size_t cchName = strlen(pszName);
+ if (cchName >= RTTHREAD_NAME_LEN)
+ {
+ AssertMsgFailed(("pszName=%s is too long, max is %d\n", pszName, RTTHREAD_NAME_LEN - 1));
+ return VERR_INVALID_PARAMETER;
+ }
+ pThread = rtThreadGet(Thread);
+ if (!pThread)
+ return VERR_INVALID_HANDLE;
+
+ /*
+ * Update the name.
+ */
+ pThread->szName[cchName] = '\0'; /* paranoia */
+ memcpy(pThread->szName, pszName, cchName);
+ rtThreadRelease(pThread);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTThreadSetName);
+
+
+RTDECL(bool) RTThreadIsMain(RTTHREAD hThread)
+{
+ if (hThread != NIL_RTTHREAD)
+ {
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ bool fRc = !!(pThread->fIntFlags & RTTHREADINT_FLAGS_MAIN);
+ rtThreadRelease(pThread);
+ return fRc;
+ }
+ }
+ return false;
+}
+RT_EXPORT_SYMBOL(RTThreadIsMain);
+
+
+RTDECL(bool) RTThreadIsSelfAlive(void)
+{
+ if (g_frtThreadInitialized)
+ {
+ RTTHREAD hSelf = RTThreadSelf();
+ if (hSelf != NIL_RTTHREAD)
+ {
+ /*
+ * Inspect the thread state. ASSUMES thread state order.
+ */
+ RTTHREADSTATE enmState = rtThreadGetState(hSelf);
+ if ( enmState >= RTTHREADSTATE_RUNNING
+ && enmState <= RTTHREADSTATE_END)
+ return true;
+ }
+ }
+ return false;
+}
+RT_EXPORT_SYMBOL(RTThreadIsSelfAlive);
+
+
+RTDECL(bool) RTThreadIsSelfKnown(void)
+{
+ if (g_frtThreadInitialized)
+ {
+ RTTHREAD hSelf = RTThreadSelf();
+ if (hSelf != NIL_RTTHREAD)
+ return true;
+ }
+ return false;
+}
+RT_EXPORT_SYMBOL(RTThreadIsSelfKnown);
+
+
+RTDECL(bool) RTThreadIsInitialized(void)
+{
+ return g_frtThreadInitialized;
+}
+RT_EXPORT_SYMBOL(RTThreadIsInitialized);
+
+
+RTDECL(int) RTThreadUserSignal(RTTHREAD Thread)
+{
+ int rc;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ rc = RTSemEventMultiSignal(pThread->EventUser);
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadUserSignal);
+
+
+RTDECL(int) RTThreadUserWait(RTTHREAD Thread, RTMSINTERVAL cMillies)
+{
+ int rc;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ rc = RTSemEventMultiWait(pThread->EventUser, cMillies);
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadUserWait);
+
+
+RTDECL(int) RTThreadUserWaitNoResume(RTTHREAD Thread, RTMSINTERVAL cMillies)
+{
+ int rc;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ rc = RTSemEventMultiWaitNoResume(pThread->EventUser, cMillies);
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadUserWaitNoResume);
+
+
+RTDECL(int) RTThreadUserReset(RTTHREAD Thread)
+{
+ int rc;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ rc = RTSemEventMultiReset(pThread->EventUser);
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadUserReset);
+
+
+/**
+ * Wait for the thread to terminate.
+ *
+ * @returns iprt status code.
+ * @param Thread The thread to wait for.
+ * @param cMillies The number of milliseconds to wait. Use RT_INDEFINITE_WAIT for
+ * an indefinite wait.
+ * @param prc Where to store the return code of the thread. Optional.
+ * @param fAutoResume Whether or not to resume the wait on VERR_INTERRUPTED.
+ */
+static int rtThreadWait(RTTHREAD Thread, RTMSINTERVAL cMillies, int *prc, bool fAutoResume)
+{
+ int rc = VERR_INVALID_HANDLE;
+ if (Thread != NIL_RTTHREAD)
+ {
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ if (pThread->fFlags & RTTHREADFLAGS_WAITABLE)
+ {
+#if defined(IN_RING3) && defined(RT_OS_WINDOWS)
+ if (RT_LIKELY(rtThreadNativeIsAliveKludge(pThread)))
+#endif
+ {
+ if (fAutoResume)
+ rc = RTSemEventMultiWait(pThread->EventTerminated, cMillies);
+ else
+ rc = RTSemEventMultiWaitNoResume(pThread->EventTerminated, cMillies);
+ }
+#if defined(IN_RING3) && defined(RT_OS_WINDOWS)
+ else
+ {
+ rc = VINF_SUCCESS;
+ if (pThread->rc == VERR_PROCESS_RUNNING)
+ pThread->rc = VERR_THREAD_IS_DEAD;
+ }
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ if (prc)
+ *prc = pThread->rc;
+
+ /*
+ * If the thread is marked as waitable, we'll do one additional
+ * release in order to free up the thread structure (see how we
+ * init cRef in rtThreadAlloc()).
+ */
+ if (ASMAtomicBitTestAndClear(&pThread->fFlags, RTTHREADFLAGS_WAITABLE_BIT))
+ {
+ rtThreadRelease(pThread);
+#ifdef IN_RING0
+ /*
+ * IPRT termination kludge. Call native code to make sure
+ * the last thread is really out of IPRT to prevent it from
+ * crashing after we destroyed the spinlock in rtThreadTerm.
+ */
+ if ( ASMAtomicReadU32(&g_cThreadInTree) == 1
+ && ASMAtomicReadU32(&pThread->cRefs) > 1)
+ rtThreadNativeWaitKludge(pThread);
+#endif
+ }
+ }
+ }
+ else
+ {
+ rc = VERR_THREAD_NOT_WAITABLE;
+ AssertRC(rc);
+ }
+ rtThreadRelease(pThread);
+ }
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTThreadWait(RTTHREAD Thread, RTMSINTERVAL cMillies, int *prc)
+{
+ int rc = rtThreadWait(Thread, cMillies, prc, true);
+ Assert(rc != VERR_INTERRUPTED);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadWait);
+
+
+RTDECL(int) RTThreadWaitNoResume(RTTHREAD Thread, RTMSINTERVAL cMillies, int *prc)
+{
+ return rtThreadWait(Thread, cMillies, prc, false);
+}
+RT_EXPORT_SYMBOL(RTThreadWaitNoResume);
+
+
+RTDECL(int) RTThreadSetType(RTTHREAD Thread, RTTHREADTYPE enmType)
+{
+ /*
+ * Validate input.
+ */
+ int rc;
+ if ( enmType > RTTHREADTYPE_INVALID
+ && enmType < RTTHREADTYPE_END)
+ {
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ if (rtThreadIsAlive(pThread))
+ {
+ /*
+ * Do the job.
+ */
+ RT_THREAD_LOCK_RW();
+ rc = rtThreadNativeSetPriority(pThread, enmType);
+ if (RT_SUCCESS(rc))
+ ASMAtomicXchgSize(&pThread->enmType, enmType);
+ RT_THREAD_UNLOCK_RW();
+ if (RT_FAILURE(rc))
+ Log(("RTThreadSetType: failed on thread %p (%s), rc=%Rrc!!!\n", Thread, pThread->szName, rc));
+ }
+ else
+ rc = VERR_THREAD_IS_DEAD;
+ rtThreadRelease(pThread);
+ }
+ else
+ rc = VERR_INVALID_HANDLE;
+ }
+ else
+ {
+ AssertMsgFailed(("enmType=%d\n", enmType));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTThreadSetType);
+
+
+RTDECL(RTTHREADTYPE) RTThreadGetType(RTTHREAD Thread)
+{
+ RTTHREADTYPE enmType = RTTHREADTYPE_INVALID;
+ PRTTHREADINT pThread = rtThreadGet(Thread);
+ if (pThread)
+ {
+ enmType = pThread->enmType;
+ rtThreadRelease(pThread);
+ }
+ return enmType;
+}
+RT_EXPORT_SYMBOL(RTThreadGetType);
+
+#ifdef IN_RING3
+
+/**
+ * Recalculates scheduling attributes for the default process
+ * priority using the specified priority type for the calling thread.
+ *
+ * The scheduling attributes are targeted at threads and they are protected
+ * by the thread read-write semaphore, that's why RTProc is forwarding the
+ * operation to RTThread.
+ *
+ * @returns iprt status code.
+ * @remarks Will only work for strict builds.
+ */
+int rtThreadDoCalcDefaultPriority(RTTHREADTYPE enmType)
+{
+ RT_THREAD_LOCK_RW();
+ int rc = rtSchedNativeCalcDefaultPriority(enmType);
+ RT_THREAD_UNLOCK_RW();
+ return rc;
+}
+
+
+/**
+ * Thread enumerator - sets the priority of one thread.
+ *
+ * @returns 0 to continue.
+ * @returns !0 to stop. In our case a VERR_ code.
+ * @param pNode The thread node.
+ * @param pvUser The new priority.
+ */
+static DECLCALLBACK(int) rtThreadSetPriorityOne(PAVLPVNODECORE pNode, void *pvUser)
+{
+ PRTTHREADINT pThread = (PRTTHREADINT)pNode;
+ if (!rtThreadIsAlive(pThread))
+ return VINF_SUCCESS;
+ int rc = rtThreadNativeSetPriority(pThread, pThread->enmType);
+ if (RT_SUCCESS(rc)) /* hide any warnings */
+ return VINF_SUCCESS;
+ NOREF(pvUser);
+ return rc;
+}
+
+
+/**
+ * Attempts to alter the priority of the current process.
+ *
+ * The scheduling attributes are targeted at threads and they are protected
+ * by the thread read-write semaphore, that's why RTProc is forwarding the
+ * operation to RTThread. This operation also involves updating all thread
+ * which is much faster done from RTThread.
+ *
+ * @returns iprt status code.
+ * @param enmPriority The new priority.
+ */
+DECLHIDDEN(int) rtThreadDoSetProcPriority(RTPROCPRIORITY enmPriority)
+{
+ LogFlow(("rtThreadDoSetProcPriority: enmPriority=%d\n", enmPriority));
+
+ /*
+ * First validate that we're allowed by the OS to use all the
+ * scheduling attributes defined by the specified process priority.
+ */
+ RT_THREAD_LOCK_RW();
+ int rc = rtProcNativeSetPriority(enmPriority);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Update the priority of existing thread.
+ */
+ rc = RTAvlPVDoWithAll(&g_ThreadTree, true, rtThreadSetPriorityOne, NULL);
+ if (RT_SUCCESS(rc))
+ ASMAtomicXchgSize(&g_enmProcessPriority, enmPriority);
+ else
+ {
+ /*
+ * Failed, restore the priority.
+ */
+ rtProcNativeSetPriority(g_enmProcessPriority);
+ RTAvlPVDoWithAll(&g_ThreadTree, true, rtThreadSetPriorityOne, NULL);
+ }
+ }
+ RT_THREAD_UNLOCK_RW();
+ LogFlow(("rtThreadDoSetProcPriority: returns %Rrc\n", rc));
+ return rc;
+}
+
+
+RTDECL(void) RTThreadBlocking(RTTHREAD hThread, RTTHREADSTATE enmState, bool fReallySleeping)
+{
+ Assert(RTTHREAD_IS_SLEEPING(enmState));
+ PRTTHREADINT pThread = hThread;
+ if (pThread != NIL_RTTHREAD)
+ {
+ Assert(pThread == RTThreadSelf());
+ if (rtThreadGetState(pThread) == RTTHREADSTATE_RUNNING)
+ rtThreadSetState(pThread, enmState);
+ ASMAtomicWriteBool(&pThread->fReallySleeping, fReallySleeping);
+ }
+}
+RT_EXPORT_SYMBOL(RTThreadBlocking);
+
+
+RTDECL(void) RTThreadUnblocked(RTTHREAD hThread, RTTHREADSTATE enmCurState)
+{
+ PRTTHREADINT pThread = hThread;
+ if (pThread != NIL_RTTHREAD)
+ {
+ Assert(pThread == RTThreadSelf());
+ ASMAtomicWriteBool(&pThread->fReallySleeping, false);
+
+ RTTHREADSTATE enmActualState = rtThreadGetState(pThread);
+ if (enmActualState == enmCurState)
+ {
+ rtThreadSetState(pThread, RTTHREADSTATE_RUNNING);
+ if ( pThread->LockValidator.pRec
+ && pThread->LockValidator.enmRecState == enmCurState)
+ ASMAtomicWriteNullPtr(&pThread->LockValidator.pRec);
+ }
+ /* This is a bit ugly... :-/ */
+ else if ( ( enmActualState == RTTHREADSTATE_TERMINATED
+ || enmActualState == RTTHREADSTATE_INITIALIZING)
+ && pThread->LockValidator.pRec)
+ ASMAtomicWriteNullPtr(&pThread->LockValidator.pRec);
+ Assert( pThread->LockValidator.pRec == NULL
+ || RTTHREAD_IS_SLEEPING(enmActualState));
+ }
+}
+RT_EXPORT_SYMBOL(RTThreadUnblocked);
+
+
+RTDECL(RTTHREADSTATE) RTThreadGetState(RTTHREAD hThread)
+{
+ RTTHREADSTATE enmState = RTTHREADSTATE_INVALID;
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ enmState = rtThreadGetState(pThread);
+ rtThreadRelease(pThread);
+ }
+ return enmState;
+}
+RT_EXPORT_SYMBOL(RTThreadGetState);
+
+
+RTDECL(RTTHREADSTATE) RTThreadGetReallySleeping(RTTHREAD hThread)
+{
+ RTTHREADSTATE enmState = RTTHREADSTATE_INVALID;
+ PRTTHREADINT pThread = rtThreadGet(hThread);
+ if (pThread)
+ {
+ enmState = rtThreadGetState(pThread);
+ if (!ASMAtomicUoReadBool(&pThread->fReallySleeping))
+ enmState = RTTHREADSTATE_RUNNING;
+ rtThreadRelease(pThread);
+ }
+ return enmState;
+}
+RT_EXPORT_SYMBOL(RTThreadGetReallySleeping);
+
+
+/**
+ * Translate a thread state into a string.
+ *
+ * @returns Pointer to a read-only string containing the state name.
+ * @param enmState The state.
+ */
+RTDECL(const char *) RTThreadStateName(RTTHREADSTATE enmState)
+{
+ switch (enmState)
+ {
+ case RTTHREADSTATE_INVALID: return "INVALID";
+ case RTTHREADSTATE_INITIALIZING: return "INITIALIZING";
+ case RTTHREADSTATE_TERMINATED: return "TERMINATED";
+ case RTTHREADSTATE_RUNNING: return "RUNNING";
+ case RTTHREADSTATE_CRITSECT: return "CRITSECT";
+ case RTTHREADSTATE_EVENT: return "EVENT";
+ case RTTHREADSTATE_EVENT_MULTI: return "EVENT_MULTI";
+ case RTTHREADSTATE_FAST_MUTEX: return "FAST_MUTEX";
+ case RTTHREADSTATE_MUTEX: return "MUTEX";
+ case RTTHREADSTATE_RW_READ: return "RW_READ";
+ case RTTHREADSTATE_RW_WRITE: return "RW_WRITE";
+ case RTTHREADSTATE_SLEEP: return "SLEEP";
+ case RTTHREADSTATE_SPIN_MUTEX: return "SPIN_MUTEX";
+ default: return "UnknownThreadState";
+ }
+}
+RT_EXPORT_SYMBOL(RTThreadStateName);
+
+#endif /* IN_RING3 */
+#ifdef IPRT_WITH_GENERIC_TLS
+
+/**
+ * Thread enumerator - clears a TLS entry.
+ *
+ * @returns 0.
+ * @param pNode The thread node.
+ * @param pvUser The TLS index.
+ */
+static DECLCALLBACK(int) rtThreadClearTlsEntryCallback(PAVLPVNODECORE pNode, void *pvUser)
+{
+ PRTTHREADINT pThread = (PRTTHREADINT)pNode;
+ RTTLS iTls = (RTTLS)(uintptr_t)pvUser;
+ ASMAtomicWriteNullPtr(&pThread->apvTlsEntries[iTls]);
+ return 0;
+}
+
+
+/**
+ * Helper for the generic TLS implementation that clears a given TLS
+ * entry on all threads.
+ *
+ * @param iTls The TLS entry. (valid)
+ */
+DECLHIDDEN(void) rtThreadClearTlsEntry(RTTLS iTls)
+{
+ RT_THREAD_LOCK_RD();
+ RTAvlPVDoWithAll(&g_ThreadTree, true /* fFromLeft*/, rtThreadClearTlsEntryCallback, (void *)(uintptr_t)iTls);
+ RT_THREAD_UNLOCK_RD();
+}
+
+#endif /* IPRT_WITH_GENERIC_TLS */
+
+
+#if defined(RT_OS_WINDOWS) && defined(IN_RING3)
+
+/**
+ * Thread enumeration callback for RTThreadNameThreads
+ */
+static DECLCALLBACK(int) rtThreadNameThreadCallback(PAVLPVNODECORE pNode, void *pvUser)
+{
+ PRTTHREADINT pThread = (PRTTHREADINT)pNode;
+ rtThreadNativeInformDebugger(pThread);
+ RT_NOREF_PV(pvUser);
+ return 0;
+}
+
+/**
+ * A function that can be called from the windows debugger to get the names of
+ * all threads when attaching to a process.
+ *
+ * Usage: .call VBoxRT!RTThreadNameThreads()
+ *
+ * @returns 0
+ * @remarks Do not call from source code as it skips locks.
+ */
+extern "C" RTDECL(int) RTThreadNameThreads(void);
+RTDECL(int) RTThreadNameThreads(void)
+{
+ return RTAvlPVDoWithAll(&g_ThreadTree, true /* fFromLeft*/, rtThreadNameThreadCallback, NULL);
+}
+
+#endif
diff --git a/src/VBox/Runtime/common/misc/uri.cpp b/src/VBox/Runtime/common/misc/uri.cpp
new file mode 100644
index 00000000..9545969f
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/uri.cpp
@@ -0,0 +1,1181 @@
+/* $Id: uri.cpp $ */
+/** @file
+ * IPRT - Uniform Resource Identifier handling.
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/uri.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Internal magic value we use to check if a RTURIPARSED structure has made it thru RTUriParse. */
+#define RTURIPARSED_MAGIC UINT32_C(0x439e0745)
+
+
+/* General URI format:
+
+ foo://example.com:8042/over/there?name=ferret#nose
+ \_/ \______________/\_________/ \_________/ \__/
+ | | | | |
+ scheme authority path query fragment
+ | _____________________|__
+ / \ / \
+ urn:example:animal:ferret:nose
+*/
+
+
+/**
+ * The following defines characters which have to be % escaped:
+ * control = 00-1F
+ * space = ' '
+ * delims = '<' , '>' , '#' , '%' , '"'
+ * unwise = '{' , '}' , '|' , '\' , '^' , '[' , ']' , '`'
+ */
+#define URI_EXCLUDED(a) \
+ ( ((a) >= 0x0 && (a) <= 0x20) \
+ || ((a) >= 0x5B && (a) <= 0x5E) \
+ || ((a) >= 0x7B && (a) <= 0x7D) \
+ || (a) == '<' || (a) == '>' || (a) == '#' \
+ || (a) == '%' || (a) == '"' || (a) == '`' )
+
+static char *rtUriPercentEncodeN(const char *pszString, size_t cchMax)
+{
+ if (!pszString)
+ return NULL;
+
+ int rc = VINF_SUCCESS;
+
+ size_t cbLen = RT_MIN(strlen(pszString), cchMax);
+ /* The new string can be max 3 times in size of the original string. */
+ char *pszNew = RTStrAlloc(cbLen * 3 + 1);
+ if (!pszNew)
+ return NULL;
+
+ char *pszRes = NULL;
+ size_t iIn = 0;
+ size_t iOut = 0;
+ while (iIn < cbLen)
+ {
+ if (URI_EXCLUDED(pszString[iIn]))
+ {
+ char szNum[3] = { 0, 0, 0 };
+ RTStrFormatU8(&szNum[0], 3, pszString[iIn++], 16, 2, 2, RTSTR_F_CAPITAL | RTSTR_F_ZEROPAD);
+ pszNew[iOut++] = '%';
+ pszNew[iOut++] = szNum[0];
+ pszNew[iOut++] = szNum[1];
+ }
+ else
+ pszNew[iOut++] = pszString[iIn++];
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pszNew[iOut] = '\0';
+ if (iOut != iIn)
+ {
+ /* If the source and target strings have different size, recreate
+ * the target string with the correct size. */
+ pszRes = RTStrDupN(pszNew, iOut);
+ RTStrFree(pszNew);
+ }
+ else
+ pszRes = pszNew;
+ }
+ else
+ RTStrFree(pszNew);
+
+ return pszRes;
+}
+
+
+/**
+ * Calculates the encoded string length.
+ *
+ * @returns Number of chars (excluding the terminator).
+ * @param pszString The string to encode.
+ * @param cchMax The maximum string length (e.g. RTSTR_MAX).
+ * @param fEncodeDosSlash Whether to encode DOS slashes or not.
+ */
+static size_t rtUriCalcEncodedLength(const char *pszString, size_t cchMax, bool fEncodeDosSlash)
+{
+ size_t cchEncoded = 0;
+ if (pszString)
+ {
+ size_t cchSrcLeft = RTStrNLen(pszString, cchMax);
+ while (cchSrcLeft-- > 0)
+ {
+ char const ch = *pszString++;
+ if (!URI_EXCLUDED(ch) || (ch == '\\' && !fEncodeDosSlash))
+ cchEncoded += 1;
+ else
+ cchEncoded += 3;
+ }
+ }
+ return cchEncoded;
+}
+
+
+/**
+ * Encodes an URI into a caller allocated buffer.
+ *
+ * @returns IPRT status code.
+ * @param pszString The string to encode.
+ * @param cchMax The maximum string length (e.g. RTSTR_MAX).
+ * @param fEncodeDosSlash Whether to encode DOS slashes or not.
+ * @param pszDst The destination buffer.
+ * @param cbDst The size of the destination buffer.
+ */
+static int rtUriEncodeIntoBuffer(const char *pszString, size_t cchMax, bool fEncodeDosSlash, char *pszDst, size_t cbDst)
+{
+ AssertReturn(pszString, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDst, VERR_INVALID_POINTER);
+
+ /*
+ * We do buffer size checking up front and every time we encode a special
+ * character. That's faster than checking for each char.
+ */
+ size_t cchSrcLeft = RTStrNLen(pszString, cchMax);
+ AssertMsgReturn(cbDst > cchSrcLeft, ("cbDst=%zu cchSrcLeft=%zu\n", cbDst, cchSrcLeft), VERR_BUFFER_OVERFLOW);
+ cbDst -= cchSrcLeft;
+
+ while (cchSrcLeft-- > 0)
+ {
+ char const ch = *pszString++;
+ if (!URI_EXCLUDED(ch) || (ch == '\\' && !fEncodeDosSlash))
+ *pszDst++ = ch;
+ else
+ {
+ AssertReturn(cbDst >= 3, VERR_BUFFER_OVERFLOW); /* 2 extra bytes + zero terminator. */
+ cbDst -= 2;
+
+ *pszDst++ = '%';
+ ssize_t cchTmp = RTStrFormatU8(pszDst, 3, (unsigned char)ch, 16, 2, 2, RTSTR_F_CAPITAL | RTSTR_F_ZEROPAD);
+ Assert(cchTmp == 2); NOREF(cchTmp);
+ pszDst += 2;
+ }
+ }
+
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+}
+
+
+static char *rtUriPercentDecodeN(const char *pszString, size_t cchString)
+{
+ AssertPtrReturn(pszString, NULL);
+ AssertReturn(memchr(pszString, '\0', cchString) == NULL, NULL);
+
+ /*
+ * The new string can only get smaller, so use the input length as a
+ * staring buffer size.
+ */
+ char *pszDecoded = RTStrAlloc(cchString + 1);
+ if (pszDecoded)
+ {
+ /*
+ * Knowing that the pszString itself is valid UTF-8, we only have to
+ * validate the escape sequences.
+ */
+ size_t cchLeft = cchString;
+ char const *pchSrc = pszString;
+ char *pchDst = pszDecoded;
+ while (cchLeft > 0)
+ {
+ const char *pchPct = (const char *)memchr(pchSrc, '%', cchLeft);
+ if (pchPct)
+ {
+ size_t cchBefore = pchPct - pchSrc;
+ if (cchBefore)
+ {
+ memcpy(pchDst, pchSrc, cchBefore);
+ pchDst += cchBefore;
+ pchSrc += cchBefore;
+ cchLeft -= cchBefore;
+ }
+
+ char chHigh, chLow;
+ if ( cchLeft >= 3
+ && RT_C_IS_XDIGIT(chHigh = pchSrc[1])
+ && RT_C_IS_XDIGIT(chLow = pchSrc[2]))
+ {
+ uint8_t b = RT_C_IS_DIGIT(chHigh) ? chHigh - '0' : (chHigh & ~0x20) - 'A' + 10;
+ b <<= 4;
+ b |= RT_C_IS_DIGIT(chLow) ? chLow - '0' : (chLow & ~0x20) - 'A' + 10;
+ *pchDst++ = (char)b;
+ pchSrc += 3;
+ cchLeft -= 3;
+ }
+ else
+ {
+ AssertFailed();
+ *pchDst++ = *pchSrc++;
+ cchLeft--;
+ }
+ }
+ else
+ {
+ memcpy(pchDst, pchSrc, cchLeft);
+ pchDst += cchLeft;
+ pchSrc += cchLeft;
+ cchLeft = 0;
+ break;
+ }
+ }
+
+ *pchDst = '\0';
+
+ /*
+ * If we've got lof space room in the result string, reallocate it.
+ */
+ size_t cchDecoded = pchDst - pszDecoded;
+ Assert(cchDecoded <= cchString);
+ if (cchString - cchDecoded > 64)
+ RTStrRealloc(&pszDecoded, cchDecoded + 1);
+ }
+ return pszDecoded;
+}
+
+
+/**
+ * Calculates the decoded string length.
+ *
+ * @returns Number of chars (excluding the terminator).
+ * @param pszString The string to decode.
+ * @param cchMax The maximum string length (e.g. RTSTR_MAX).
+ */
+static size_t rtUriCalcDecodedLength(const char *pszString, size_t cchMax)
+{
+ size_t cchDecoded;
+ if (pszString)
+ {
+ size_t cchSrcLeft = cchDecoded = RTStrNLen(pszString, cchMax);
+ while (cchSrcLeft-- > 0)
+ {
+ char const ch = *pszString++;
+ if (ch != '%')
+ { /* typical */}
+ else if ( cchSrcLeft >= 2
+ && RT_C_IS_XDIGIT(pszString[0])
+ && RT_C_IS_XDIGIT(pszString[1]))
+ {
+ cchDecoded -= 2;
+ pszString += 2;
+ cchSrcLeft -= 2;
+ }
+ }
+ }
+ else
+ cchDecoded = 0;
+ return cchDecoded;
+}
+
+
+/**
+ * Decodes a string into a buffer.
+ *
+ * @returns IPRT status code.
+ * @param pchSrc The source string.
+ * @param cchSrc The max number of bytes to decode in the source string.
+ * @param pszDst The destination buffer.
+ * @param cbDst The size of the buffer (including terminator).
+ */
+static int rtUriDecodeIntoBuffer(const char *pchSrc, size_t cchSrc, char *pszDst, size_t cbDst)
+{
+ AssertPtrReturn(pchSrc, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszDst, VERR_INVALID_POINTER);
+
+ /*
+ * Knowing that the pszString itself is valid UTF-8, we only have to
+ * validate the escape sequences.
+ */
+ cchSrc = RTStrNLen(pchSrc, cchSrc);
+ while (cchSrc > 0)
+ {
+ const char *pchPct = (const char *)memchr(pchSrc, '%', cchSrc);
+ if (pchPct)
+ {
+ size_t cchBefore = pchPct - pchSrc;
+ AssertReturn(cchBefore + 1 < cbDst, VERR_BUFFER_OVERFLOW);
+ if (cchBefore)
+ {
+ memcpy(pszDst, pchSrc, cchBefore);
+ pszDst += cchBefore;
+ cbDst -= cchBefore;
+ pchSrc += cchBefore;
+ cchSrc -= cchBefore;
+ }
+
+ char chHigh, chLow;
+ if ( cchSrc >= 3
+ && RT_C_IS_XDIGIT(chHigh = pchSrc[1])
+ && RT_C_IS_XDIGIT(chLow = pchSrc[2]))
+ {
+ uint8_t b = RT_C_IS_DIGIT(chHigh) ? chHigh - '0' : (chHigh & ~0x20) - 'A' + 10;
+ b <<= 4;
+ b |= RT_C_IS_DIGIT(chLow) ? chLow - '0' : (chLow & ~0x20) - 'A' + 10;
+ *pszDst++ = (char)b;
+ pchSrc += 3;
+ cchSrc -= 3;
+ }
+ else
+ {
+ AssertFailed();
+ *pszDst++ = *pchSrc++;
+ cchSrc--;
+ }
+ cbDst -= 1;
+ }
+ else
+ {
+ AssertReturn(cchSrc < cbDst, VERR_BUFFER_OVERFLOW);
+ memcpy(pszDst, pchSrc, cchSrc);
+ pszDst += cchSrc;
+ cbDst -= cchSrc;
+ pchSrc += cchSrc;
+ cchSrc = 0;
+ break;
+ }
+ }
+
+ AssertReturn(cbDst > 0, VERR_BUFFER_OVERFLOW);
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+}
+
+
+
+static int rtUriParse(const char *pszUri, PRTURIPARSED pParsed)
+{
+ /*
+ * Validate the input and clear the output.
+ */
+ AssertPtrReturn(pParsed, VERR_INVALID_POINTER);
+ RT_ZERO(*pParsed);
+ pParsed->uAuthorityPort = UINT32_MAX;
+
+ AssertPtrReturn(pszUri, VERR_INVALID_POINTER);
+
+ size_t const cchUri = strlen(pszUri);
+ if (RT_LIKELY(cchUri >= 3)) { /* likely */ }
+ else return cchUri ? VERR_URI_TOO_SHORT : VERR_URI_EMPTY;
+
+ /*
+ * Validating escaped text sequences is much simpler if we know that
+ * that the base URI string is valid. Also, we don't necessarily trust
+ * the developer calling us to remember to do this.
+ */
+ int rc = RTStrValidateEncoding(pszUri);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * RFC-3986, section 3.1:
+ * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ *
+ * The scheme ends with a ':', which we also skip here.
+ */
+ size_t off = 0;
+ char ch = pszUri[off++];
+ if (RT_LIKELY(RT_C_IS_ALPHA(ch))) { /* likely */ }
+ else return VERR_URI_INVALID_SCHEME;
+ for (;;)
+ {
+ ch = pszUri[off];
+ if (ch == ':')
+ break;
+ if (RT_LIKELY(RT_C_IS_ALNUM(ch) || ch == '.' || ch == '-' || ch == '+')) { /* likely */ }
+ else return VERR_URI_INVALID_SCHEME;
+ off++;
+ }
+ pParsed->cchScheme = off;
+
+ /* Require the scheme length to be at least two chars so we won't confuse
+ it with a path starting with a DOS drive letter specification. */
+ if (RT_LIKELY(off >= 2)) { /* likely */ }
+ else return VERR_URI_INVALID_SCHEME;
+
+ off++; /* (skip colon) */
+
+ /*
+ * Find the end of the path, we'll need this several times.
+ * Also, while we're potentially scanning the whole thing, check for '%'.
+ */
+ size_t const offHash = RTStrOffCharOrTerm(&pszUri[off], '#') + off;
+ size_t const offQuestionMark = RTStrOffCharOrTerm(&pszUri[off], '?') + off;
+
+ if (memchr(pszUri, '%', cchUri) != NULL)
+ pParsed->fFlags |= RTURIPARSED_F_CONTAINS_ESCAPED_CHARS;
+
+ /*
+ * RFC-3986, section 3.2:
+ * The authority component is preceeded by a double slash ("//")...
+ */
+ if ( pszUri[off] == '/'
+ && pszUri[off + 1] == '/')
+ {
+ off += 2;
+ pParsed->offAuthority = pParsed->offAuthorityUsername = pParsed->offAuthorityPassword = pParsed->offAuthorityHost = off;
+ pParsed->fFlags |= RTURIPARSED_F_HAS_AUTHORITY;
+
+ /*
+ * RFC-3986, section 3.2:
+ * ...and is terminated by the next slash ("/"), question mark ("?"),
+ * or number sign ("#") character, or by the end of the URI.
+ */
+ const char *pszAuthority = &pszUri[off];
+ size_t cchAuthority = RTStrOffCharOrTerm(pszAuthority, '/');
+ cchAuthority = RT_MIN(cchAuthority, offHash - off);
+ cchAuthority = RT_MIN(cchAuthority, offQuestionMark - off);
+ pParsed->cchAuthority = cchAuthority;
+
+ /* The Authority can be empty, like for: file:///usr/bin/grep */
+ if (cchAuthority > 0)
+ {
+ pParsed->cchAuthorityHost = cchAuthority;
+
+ /*
+ * If there is a userinfo part, it is ended by a '@'.
+ */
+ const char *pszAt = (const char *)memchr(pszAuthority, '@', cchAuthority);
+ if (pszAt)
+ {
+ size_t cchTmp = pszAt - pszAuthority;
+ pParsed->offAuthorityHost += cchTmp + 1;
+ pParsed->cchAuthorityHost -= cchTmp + 1;
+
+ /* If there is a password part, it's separated from the username with a colon. */
+ const char *pszColon = (const char *)memchr(pszAuthority, ':', cchTmp);
+ if (pszColon)
+ {
+ pParsed->cchAuthorityUsername = pszColon - pszAuthority;
+ pParsed->offAuthorityPassword = &pszColon[1] - pszUri;
+ pParsed->cchAuthorityPassword = pszAt - &pszColon[1];
+ }
+ else
+ {
+ pParsed->cchAuthorityUsername = cchTmp;
+ pParsed->offAuthorityPassword = off + cchTmp;
+ }
+ }
+
+ /*
+ * If there is a port part, its after the last colon in the host part.
+ */
+ const char *pszColon = (const char *)memrchr(&pszUri[pParsed->offAuthorityHost], ':', pParsed->cchAuthorityHost);
+ if (pszColon)
+ {
+ size_t cchTmp = &pszUri[pParsed->offAuthorityHost + pParsed->cchAuthorityHost] - &pszColon[1];
+ pParsed->cchAuthorityHost -= cchTmp + 1;
+ pParsed->fFlags |= RTURIPARSED_F_HAS_PORT;
+ if (cchTmp > 0)
+ {
+ pParsed->uAuthorityPort = 0;
+ while (cchTmp-- > 0)
+ {
+ ch = *++pszColon;
+ if ( RT_C_IS_DIGIT(ch)
+ && pParsed->uAuthorityPort < UINT32_MAX / UINT32_C(10))
+ {
+ pParsed->uAuthorityPort *= 10;
+ pParsed->uAuthorityPort += ch - '0';
+ }
+ else
+ return VERR_URI_INVALID_PORT_NUMBER;
+ }
+ }
+ }
+ }
+
+ /* Skip past the authority. */
+ off += cchAuthority;
+ }
+ else
+ pParsed->offAuthority = pParsed->offAuthorityUsername = pParsed->offAuthorityPassword = pParsed->offAuthorityHost = off;
+
+ /*
+ * RFC-3986, section 3.3: Path
+ * The path is terminated by the first question mark ("?")
+ * or number sign ("#") character, or by the end of the URI.
+ */
+ pParsed->offPath = off;
+ pParsed->cchPath = RT_MIN(offHash, offQuestionMark) - off;
+ off += pParsed->cchPath;
+
+ /*
+ * RFC-3986, section 3.4: Query
+ * The query component is indicated by the first question mark ("?")
+ * character and terminated by a number sign ("#") character or by the
+ * end of the URI.
+ */
+ if ( off == offQuestionMark
+ && off < cchUri)
+ {
+ Assert(pszUri[offQuestionMark] == '?');
+ pParsed->offQuery = ++off;
+ pParsed->cchQuery = offHash - off;
+ off = offHash;
+ }
+ else
+ {
+ Assert(!pszUri[offQuestionMark]);
+ pParsed->offQuery = off;
+ }
+
+ /*
+ * RFC-3986, section 3.5: Fragment
+ * A fragment identifier component is indicated by the presence of a
+ * number sign ("#") character and terminated by the end of the URI.
+ */
+ if ( off == offHash
+ && off < cchUri)
+ {
+ pParsed->offFragment = ++off;
+ pParsed->cchFragment = cchUri - off;
+ }
+ else
+ {
+ Assert(!pszUri[offHash]);
+ pParsed->offFragment = off;
+ }
+
+ /*
+ * If there are any escape sequences, validate them.
+ *
+ * This is reasonably simple as we already know that the string is valid UTF-8
+ * before they get decoded. Thus we only have to validate the escaped sequences.
+ */
+ if (pParsed->fFlags & RTURIPARSED_F_CONTAINS_ESCAPED_CHARS)
+ {
+ const char *pchSrc = (const char *)memchr(pszUri, '%', cchUri);
+ AssertReturn(pchSrc, VERR_INTERNAL_ERROR);
+ do
+ {
+ char szUtf8Seq[8];
+ unsigned cchUtf8Seq = 0;
+ unsigned cchNeeded = 0;
+ size_t cchLeft = &pszUri[cchUri] - pchSrc;
+ do
+ {
+ if (cchLeft >= 3)
+ {
+ char chHigh = pchSrc[1];
+ char chLow = pchSrc[2];
+ if ( RT_C_IS_XDIGIT(chHigh)
+ && RT_C_IS_XDIGIT(chLow))
+ {
+ uint8_t b = RT_C_IS_DIGIT(chHigh) ? chHigh - '0' : (chHigh & ~0x20) - 'A' + 10;
+ b <<= 4;
+ b |= RT_C_IS_DIGIT(chLow) ? chLow - '0' : (chLow & ~0x20) - 'A' + 10;
+
+ if (!(b & 0x80))
+ {
+ /* We don't want the string to be terminated prematurely. */
+ if (RT_LIKELY(b != 0)) { /* likely */ }
+ else return VERR_URI_ESCAPED_ZERO;
+
+ /* Check that we're not expecting more UTF-8 bytes. */
+ if (RT_LIKELY(cchNeeded == 0)) { /* likely */ }
+ else return VERR_URI_MISSING_UTF8_CONTINUATION_BYTE;
+ }
+ /* Are we waiting UTF-8 bytes? */
+ else if (cchNeeded > 0)
+ {
+ if (RT_LIKELY(!(b & 0x40))) { /* likely */ }
+ else return VERR_URI_INVALID_ESCAPED_UTF8_CONTINUATION_BYTE;
+
+ szUtf8Seq[cchUtf8Seq++] = (char)b;
+ if (--cchNeeded == 0)
+ {
+ szUtf8Seq[cchUtf8Seq] = '\0';
+ rc = RTStrValidateEncoding(szUtf8Seq);
+ if (RT_FAILURE(rc))
+ return VERR_URI_ESCAPED_CHARS_NOT_VALID_UTF8;
+ cchUtf8Seq = 0;
+ }
+ }
+ /* Start a new UTF-8 sequence. */
+ else
+ {
+ if ((b & 0xf8) == 0xf0)
+ cchNeeded = 3;
+ else if ((b & 0xf0) == 0xe0)
+ cchNeeded = 2;
+ else if ((b & 0xe0) == 0xc0)
+ cchNeeded = 1;
+ else
+ return VERR_URI_INVALID_ESCAPED_UTF8_LEAD_BYTE;
+ szUtf8Seq[0] = (char)b;
+ cchUtf8Seq = 1;
+ }
+ pchSrc += 3;
+ cchLeft -= 3;
+ }
+ else
+ return VERR_URI_INVALID_ESCAPE_SEQ;
+ }
+ else
+ return VERR_URI_INVALID_ESCAPE_SEQ;
+ } while (cchLeft > 0 && pchSrc[0] == '%');
+
+ /* Check that we're not expecting more UTF-8 bytes. */
+ if (RT_LIKELY(cchNeeded == 0)) { /* likely */ }
+ else return VERR_URI_MISSING_UTF8_CONTINUATION_BYTE;
+
+ /* next */
+ pchSrc = (const char *)memchr(pchSrc, '%', cchLeft);
+ } while (pchSrc);
+ }
+
+ pParsed->u32Magic = RTURIPARSED_MAGIC;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTUriParse(const char *pszUri, PRTURIPARSED pParsed)
+{
+ return rtUriParse(pszUri, pParsed);
+}
+
+
+RTDECL(char *) RTUriParsedScheme(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ return RTStrDupN(pszUri, pParsed->cchScheme);
+}
+
+
+RTDECL(char *) RTUriParsedAuthority(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchAuthority || (pParsed->fFlags & RTURIPARSED_F_HAS_AUTHORITY))
+ return rtUriPercentDecodeN(&pszUri[pParsed->offAuthority], pParsed->cchAuthority);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedAuthorityUsername(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchAuthorityUsername)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offAuthorityUsername], pParsed->cchAuthorityUsername);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedAuthorityPassword(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchAuthorityPassword)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offAuthorityPassword], pParsed->cchAuthorityPassword);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedAuthorityHost(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchAuthorityHost)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offAuthorityHost], pParsed->cchAuthorityHost);
+ return NULL;
+}
+
+
+RTDECL(uint32_t) RTUriParsedAuthorityPort(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, UINT32_MAX);
+ AssertPtrReturn(pParsed, UINT32_MAX);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, UINT32_MAX);
+ return pParsed->uAuthorityPort;
+}
+
+
+RTDECL(char *) RTUriParsedPath(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchPath)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offPath], pParsed->cchPath);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedQuery(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchQuery)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offQuery], pParsed->cchQuery);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriParsedFragment(const char *pszUri, PCRTURIPARSED pParsed)
+{
+ AssertPtrReturn(pszUri, NULL);
+ AssertPtrReturn(pParsed, NULL);
+ AssertReturn(pParsed->u32Magic == RTURIPARSED_MAGIC, NULL);
+ if (pParsed->cchFragment)
+ return rtUriPercentDecodeN(&pszUri[pParsed->offFragment], pParsed->cchFragment);
+ return NULL;
+}
+
+
+RTDECL(char *) RTUriCreate(const char *pszScheme, const char *pszAuthority, const char *pszPath, const char *pszQuery,
+ const char *pszFragment)
+{
+ if (!pszScheme) /* Scheme is minimum requirement */
+ return NULL;
+
+ char *pszResult = 0;
+ char *pszAuthority1 = 0;
+ char *pszPath1 = 0;
+ char *pszQuery1 = 0;
+ char *pszFragment1 = 0;
+
+ do
+ {
+ /* Create the percent encoded strings and calculate the necessary uri
+ * length. */
+ size_t cbSize = strlen(pszScheme) + 1 + 1; /* plus zero byte */
+ if (pszAuthority)
+ {
+ pszAuthority1 = rtUriPercentEncodeN(pszAuthority, RTSTR_MAX);
+ if (!pszAuthority1)
+ break;
+ cbSize += strlen(pszAuthority1) + 2;
+ }
+ if (pszPath)
+ {
+ pszPath1 = rtUriPercentEncodeN(pszPath, RTSTR_MAX);
+ if (!pszPath1)
+ break;
+ cbSize += strlen(pszPath1);
+ }
+ if (pszQuery)
+ {
+ pszQuery1 = rtUriPercentEncodeN(pszQuery, RTSTR_MAX);
+ if (!pszQuery1)
+ break;
+ cbSize += strlen(pszQuery1) + 1;
+ }
+ if (pszFragment)
+ {
+ pszFragment1 = rtUriPercentEncodeN(pszFragment, RTSTR_MAX);
+ if (!pszFragment1)
+ break;
+ cbSize += strlen(pszFragment1) + 1;
+ }
+
+ char *pszTmp = pszResult = (char *)RTStrAlloc(cbSize);
+ if (!pszResult)
+ break;
+ RT_BZERO(pszTmp, cbSize);
+
+ /* Compose the target uri string. */
+ RTStrCatP(&pszTmp, &cbSize, pszScheme);
+ RTStrCatP(&pszTmp, &cbSize, ":");
+ if (pszAuthority1)
+ {
+ RTStrCatP(&pszTmp, &cbSize, "//");
+ RTStrCatP(&pszTmp, &cbSize, pszAuthority1);
+ }
+ if (pszPath1)
+ {
+ RTStrCatP(&pszTmp, &cbSize, pszPath1);
+ }
+ if (pszQuery1)
+ {
+ RTStrCatP(&pszTmp, &cbSize, "?");
+ RTStrCatP(&pszTmp, &cbSize, pszQuery1);
+ }
+ if (pszFragment1)
+ {
+ RTStrCatP(&pszTmp, &cbSize, "#");
+ RTStrCatP(&pszTmp, &cbSize, pszFragment1);
+ }
+ } while (0);
+
+ /* Cleanup */
+ if (pszAuthority1)
+ RTStrFree(pszAuthority1);
+ if (pszPath1)
+ RTStrFree(pszPath1);
+ if (pszQuery1)
+ RTStrFree(pszQuery1);
+ if (pszFragment1)
+ RTStrFree(pszFragment1);
+
+ return pszResult;
+}
+
+
+RTDECL(bool) RTUriIsSchemeMatch(const char *pszUri, const char *pszScheme)
+{
+ AssertPtrReturn(pszUri, false);
+ size_t const cchScheme = strlen(pszScheme);
+ return RTStrNICmp(pszUri, pszScheme, cchScheme) == 0
+ && pszUri[cchScheme] == ':';
+}
+
+
+RTDECL(int) RTUriFileCreateEx(const char *pszPath, uint32_t fPathStyle, char **ppszUri, size_t cbUri, size_t *pcchUri)
+{
+ /*
+ * Validate and adjust input. (RTPathParse check pszPath out for us)
+ */
+ if (pcchUri)
+ {
+ AssertPtrReturn(pcchUri, VERR_INVALID_POINTER);
+ *pcchUri = ~(size_t)0;
+ }
+ AssertPtrReturn(ppszUri, VERR_INVALID_POINTER);
+ AssertReturn(!(fPathStyle & ~RTPATH_STR_F_STYLE_MASK) && fPathStyle != RTPATH_STR_F_STYLE_RESERVED, VERR_INVALID_FLAGS);
+ if (fPathStyle == RTPATH_STR_F_STYLE_HOST)
+ fPathStyle = RTPATH_STYLE;
+
+ /*
+ * Let the RTPath code parse the stuff (no reason to duplicate path parsing
+ * and get it slightly wrong here).
+ */
+ union
+ {
+ RTPATHPARSED ParsedPath;
+ uint8_t abPadding[sizeof(RTPATHPARSED)];
+ } u;
+ int rc = RTPathParse(pszPath, &u.ParsedPath, sizeof(u.ParsedPath), fPathStyle);
+ if (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* Skip leading slashes. */
+ if (u.ParsedPath.fProps & RTPATH_PROP_ROOT_SLASH)
+ {
+ if (fPathStyle == RTPATH_STR_F_STYLE_DOS)
+ while (pszPath[0] == '/' || pszPath[0] == '\\')
+ pszPath++;
+ else
+ while (pszPath[0] == '/')
+ pszPath++;
+ }
+ const size_t cchPath = strlen(pszPath);
+
+ /*
+ * Calculate the encoded length and figure destination buffering.
+ */
+ static const char s_szPrefix[] = "file:///";
+ size_t const cchPrefix = sizeof(s_szPrefix) - (u.ParsedPath.fProps & RTPATH_PROP_UNC ? 2 : 1);
+ size_t cchEncoded = rtUriCalcEncodedLength(pszPath, cchPath, fPathStyle != RTPATH_STR_F_STYLE_DOS);
+
+ if (pcchUri)
+ *pcchUri = cchEncoded;
+
+ char *pszDst;
+ char *pszFreeMe = NULL;
+ if (!cbUri || *ppszUri == NULL)
+ {
+ cbUri = RT_MAX(cbUri, cchPrefix + cchEncoded + 1);
+ *ppszUri = pszFreeMe = pszDst = RTStrAlloc(cbUri);
+ AssertReturn(pszDst, VERR_NO_STR_MEMORY);
+ }
+ else if (cchEncoded < cbUri)
+ pszDst = *ppszUri;
+ else
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Construct the URI.
+ */
+ memcpy(pszDst, s_szPrefix, cchPrefix);
+ pszDst[cchPrefix] = '\0';
+ rc = rtUriEncodeIntoBuffer(pszPath, cchPath, fPathStyle != RTPATH_STR_F_STYLE_DOS, &pszDst[cchPrefix], cbUri - cchPrefix);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(strlen(pszDst) == cbUri - 1);
+ if (fPathStyle == RTPATH_STR_F_STYLE_DOS)
+ RTPathChangeToUnixSlashes(pszDst, true /*fForce*/);
+ return VINF_SUCCESS;
+ }
+
+ AssertRC(rc); /* Impossible! rtUriCalcEncodedLength or something above is busted! */
+ if (pszFreeMe)
+ RTStrFree(pszFreeMe);
+ }
+ return rc;
+}
+
+
+RTDECL(char *) RTUriFileCreate(const char *pszPath)
+{
+ char *pszUri = NULL;
+ int rc = RTUriFileCreateEx(pszPath, RTPATH_STR_F_STYLE_HOST, &pszUri, 0 /*cbUri*/, NULL /*pcchUri*/);
+ if (RT_SUCCESS(rc))
+ return pszUri;
+ return NULL;
+}
+
+
+RTDECL(int) RTUriFilePathEx(const char *pszUri, uint32_t fPathStyle, char **ppszPath, size_t cbPath, size_t *pcchPath)
+{
+ /*
+ * Validate and adjust input.
+ */
+ if (pcchPath)
+ {
+ AssertPtrReturn(pcchPath, VERR_INVALID_POINTER);
+ *pcchPath = ~(size_t)0;
+ }
+ AssertPtrReturn(ppszPath, VERR_INVALID_POINTER);
+ AssertReturn(!(fPathStyle & ~RTPATH_STR_F_STYLE_MASK) && fPathStyle != RTPATH_STR_F_STYLE_RESERVED, VERR_INVALID_FLAGS);
+ if (fPathStyle == RTPATH_STR_F_STYLE_HOST)
+ fPathStyle = RTPATH_STYLE;
+ AssertPtrReturn(pszUri, VERR_INVALID_POINTER);
+
+ /*
+ * Check that this is a file URI.
+ */
+ if (RTStrNICmp(pszUri, RT_STR_TUPLE("file:")) == 0)
+ { /* likely */ }
+ else
+ return VERR_URI_NOT_FILE_SCHEME;
+
+ /*
+ * We may have a number of variations here, mostly thanks to
+ * various windows software. First the canonical variations:
+ * - file:///C:/Windows/System32/kernel32.dll
+ * - file:///C|/Windows/System32/kernel32.dll
+ * - file:///C:%5CWindows%5CSystem32%5Ckernel32.dll
+ * - file://localhost/C:%5CWindows%5CSystem32%5Ckernel32.dll
+ * - file://cifsserver.dev/systemshare%5CWindows%5CSystem32%5Ckernel32.dll
+ * - file://cifsserver.dev:139/systemshare%5CWindows%5CSystem32%5Ckernel32.dll (not quite sure here, but whatever)
+ *
+ * Legacy variant without any slashes after the schema:
+ * - file:C:/Windows/System32/kernel32.dll
+ * - file:C|/Windows/System32%5Ckernel32.dll
+ * - file:~/.bashrc
+ * \--path-/
+ *
+ * Legacy variant with exactly one slashes after the schema:
+ * - file:/C:/Windows/System32%5Ckernel32.dll
+ * - file:/C|/Windows/System32/kernel32.dll
+ * - file:/usr/bin/env
+ * \---path---/
+ *
+ * Legacy variant with two slashes after the schema and an unescaped DOS path:
+ * - file://C:/Windows/System32\kernel32.dll (**)
+ * - file://C|/Windows/System32\kernel32.dll
+ * \---path---------------------/
+ * -- authority, with ':' as non-working port separator
+ *
+ * Legacy variant with exactly four slashes after the schema and an unescaped DOS path.
+ * - file:////C:/Windows\System32\user32.dll
+ *
+ * Legacy variant with four or more slashes after the schema and an unescaped UNC path:
+ * - file:////cifsserver.dev/systemshare/System32%\kernel32.dll
+ * - file://///cifsserver.dev/systemshare/System32\kernel32.dll
+ * \---path--------------------------------------------/
+ *
+ * The two unescaped variants shouldn't be handed to rtUriParse, which
+ * is good as we cannot actually handle the one marked by (**). So, handle
+ * those two special when parsing.
+ */
+ RTURIPARSED Parsed;
+ int rc;
+ size_t cSlashes = 0;
+ while (pszUri[5 + cSlashes] == '/')
+ cSlashes++;
+ if ( (cSlashes == 2 || cSlashes == 4)
+ && RT_C_IS_ALPHA(pszUri[5 + cSlashes])
+ && (pszUri[5 + cSlashes + 1] == ':' || pszUri[5 + cSlashes + 1] == '|'))
+ {
+ RT_ZERO(Parsed); /* RTURIPARSED_F_CONTAINS_ESCAPED_CHARS is now clear. */
+ Parsed.offPath = 5 + cSlashes;
+ Parsed.cchPath = strlen(&pszUri[Parsed.offPath]);
+ rc = RTStrValidateEncoding(&pszUri[Parsed.offPath]);
+ }
+ else if (cSlashes >= 4)
+ {
+ RT_ZERO(Parsed);
+ Parsed.fFlags = cSlashes > 4 ? RTURIPARSED_F_CONTAINS_ESCAPED_CHARS : 0;
+ Parsed.offPath = 5 + cSlashes - 2;
+ Parsed.cchPath = strlen(&pszUri[Parsed.offPath]);
+ rc = RTStrValidateEncoding(&pszUri[Parsed.offPath]);
+ }
+ else
+ rc = rtUriParse(pszUri, &Parsed);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Ignore localhost as hostname (it's implicit).
+ */
+ static char const s_szLocalhost[] = "localhost";
+ if ( Parsed.cchAuthorityHost == sizeof(s_szLocalhost) - 1U
+ && RTStrNICmp(&pszUri[Parsed.offAuthorityHost], RT_STR_TUPLE(s_szLocalhost)) == 0)
+ {
+ Parsed.cchAuthorityHost = 0;
+ Parsed.cchAuthority = 0;
+ }
+
+ /*
+ * Ignore leading path slash/separator if we detect a DOS drive letter
+ * and we don't have a host name.
+ */
+ if ( Parsed.cchPath >= 3
+ && Parsed.cchAuthorityHost == 0
+ && pszUri[Parsed.offPath] == '/' /* Leading path slash/separator. */
+ && ( pszUri[Parsed.offPath + 2] == ':' /* Colon after drive letter. */
+ || pszUri[Parsed.offPath + 2] == '|') /* Colon alternative. */
+ && RT_C_IS_ALPHA(pszUri[Parsed.offPath + 1]) ) /* Drive letter. */
+ {
+ Parsed.offPath++;
+ Parsed.cchPath--;
+ }
+
+ /*
+ * Calculate the size of the encoded result.
+ *
+ * Since we're happily returning "C:/Windows/System32/kernel.dll"
+ * style paths when the caller requested UNIX style paths, we will
+ * return straight UNC paths too ("//cifsserver/share/dir/file").
+ */
+ size_t cchDecodedHost = 0;
+ size_t cbResult;
+ if (Parsed.fFlags & RTURIPARSED_F_CONTAINS_ESCAPED_CHARS)
+ {
+ cchDecodedHost = rtUriCalcDecodedLength(&pszUri[Parsed.offAuthorityHost], Parsed.cchAuthorityHost);
+ cbResult = cchDecodedHost + rtUriCalcDecodedLength(&pszUri[Parsed.offPath], Parsed.cchPath) + 1;
+ }
+ else
+ {
+ cchDecodedHost = 0;
+ cbResult = Parsed.cchAuthorityHost + Parsed.cchPath + 1;
+ }
+ if (pcchPath)
+ *pcchPath = cbResult - 1;
+ if (cbResult > 1)
+ {
+ /*
+ * Prepare the necessary buffer space for the result.
+ */
+ char *pszDst;
+ char *pszFreeMe = NULL;
+ if (!cbPath || *ppszPath == NULL)
+ {
+ cbPath = RT_MAX(cbPath, cbResult);
+ *ppszPath = pszFreeMe = pszDst = RTStrAlloc(cbPath);
+ AssertReturn(pszDst, VERR_NO_STR_MEMORY);
+ }
+ else if (cbResult <= cbPath)
+ pszDst = *ppszPath;
+ else
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Compose the result.
+ */
+ if (Parsed.fFlags & RTURIPARSED_F_CONTAINS_ESCAPED_CHARS)
+ {
+ rc = rtUriDecodeIntoBuffer(&pszUri[Parsed.offAuthorityHost],Parsed.cchAuthorityHost,
+ pszDst, cchDecodedHost + 1);
+ Assert(RT_SUCCESS(rc) && strlen(pszDst) == cchDecodedHost);
+ if (RT_SUCCESS(rc))
+ rc = rtUriDecodeIntoBuffer(&pszUri[Parsed.offPath], Parsed.cchPath,
+ &pszDst[cchDecodedHost], cbResult - cchDecodedHost);
+ Assert(RT_SUCCESS(rc) && strlen(pszDst) == cbResult - 1);
+ }
+ else
+ {
+ memcpy(pszDst, &pszUri[Parsed.offAuthorityHost], Parsed.cchAuthorityHost);
+ memcpy(&pszDst[Parsed.cchAuthorityHost], &pszUri[Parsed.offPath], Parsed.cchPath);
+ pszDst[cbResult - 1] = '\0';
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert colon DOS driver letter colon alternative.
+ * We do this regardless of the desired path style.
+ */
+ if ( RT_C_IS_ALPHA(pszDst[0])
+ && pszDst[1] == '|')
+ pszDst[1] = ':';
+
+ /*
+ * Fix slashes.
+ */
+ if (fPathStyle == RTPATH_STR_F_STYLE_DOS)
+ RTPathChangeToDosSlashes(pszDst, true);
+ else if (fPathStyle == RTPATH_STR_F_STYLE_UNIX)
+ RTPathChangeToUnixSlashes(pszDst, true); /** @todo not quite sure how this actually makes sense... */
+ else
+ AssertFailed();
+ return rc;
+ }
+
+ /* bail out */
+ RTStrFree(pszFreeMe);
+ }
+ else
+ rc = VERR_PATH_ZERO_LENGTH;
+ }
+ return rc;
+}
+
+
+RTDECL(char *) RTUriFilePath(const char *pszUri)
+{
+ char *pszPath = NULL;
+ int rc = RTUriFilePathEx(pszUri, RTPATH_STR_F_STYLE_HOST, &pszPath, 0 /*cbPath*/, NULL /*pcchPath*/);
+ if (RT_SUCCESS(rc))
+ return pszPath;
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/misc/zero-alt.S b/src/VBox/Runtime/common/misc/zero-alt.S
new file mode 100644
index 00000000..f1f52869
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/zero-alt.S
@@ -0,0 +1,118 @@
+/* $Id: zero-alt.S $ */
+/** @file
+ * IPRT - Zero Memory, mach-o version (for arm/sparc).
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+#ifdef ASM_FORMAT_MACHO
+; Putting it in the code segment/section for now.
+ .section __TEXT,__text,regular,pure_instructions
+ .section __TEXT,__const
+# define NAME(a) _##a
+#elif defined(ASM_FORMAT_ELF) && (defined(RT_ARCH_ARM64) || defined(RT_ARCH_SPARC) || defined(RT_ARCH_SPARC64))
+/* Putting it in the rodata segment/section for now. */
+ .file "zero-alt.S"
+ .section ".rodata"
+# define NAME(a) a
+# define NEED_HIDDEN
+#else
+# error "PORT ME!"
+#endif
+
+/* 64KB of zero memory with various sized labels. */
+ .globl NAME(g_abRTZeroPage)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZeroPage)
+# endif
+ .type NAME(g_abRTZeroPage),#object
+# if defined(RT_ARCH_SPARC) || defined(RT_ARCH_SPARC64)
+ .size NAME(g_abRTZeroPage),8192
+# else
+ .size NAME(g_abRTZeroPage),4096
+# endif
+#endif
+NAME(g_abRTZeroPage):
+ .globl NAME(g_abRTZero4K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero4K)
+# endif
+ .type NAME(g_abRTZero4K),#object
+ .size NAME(g_abRTZero4K),4096
+#endif
+NAME(g_abRTZero4K):
+ .globl NAME(g_abRTZero8K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero8K)
+# endif
+ .type NAME(g_abRTZero8K),#object
+ .size NAME(g_abRTZero8K),8192
+#endif
+NAME(g_abRTZero8K):
+ .globl NAME(g_abRTZero16K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero16K)
+# endif
+ .type NAME(g_abRTZero16K),#object
+ .size NAME(g_abRTZero16K),16384
+#endif
+NAME(g_abRTZero16K):
+ .globl NAME(g_abRTZero32K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero32K)
+# endif
+ .type NAME(g_abRTZero32K),#object
+ .size NAME(g_abRTZero32K),32768
+#endif
+NAME(g_abRTZero32K):
+ .globl NAME(g_abRTZero64K)
+#ifdef ASM_FORMAT_ELF
+# ifdef NEED_HIDDEN
+ .hidden NAME(g_abRTZero64K)
+# endif
+ .type NAME(g_abRTZero64K),#object
+ .size NAME(g_abRTZero64K),65536
+#endif
+NAME(g_abRTZero64K):
+
+#ifdef ASM_FORMAT_MACHO
+ .space 65536
+#elif defined(ASM_FORMAT_ELF)
+ .skip 65536
+#endif
+
diff --git a/src/VBox/Runtime/common/misc/zero.asm b/src/VBox/Runtime/common/misc/zero.asm
new file mode 100644
index 00000000..d6111c63
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/zero.asm
@@ -0,0 +1,83 @@
+; $Id: zero.asm $
+;; @file
+; IPRT - Zero Memory.
+;
+
+;
+; Copyright (C) 2013-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+;*******************************************************************************
+;* Header Files *
+;*******************************************************************************
+%include "iprt/asmdefs.mac"
+
+;
+; Section to put this in.
+;
+; - PE/COFF: Unless we put this in an BSS like section, the linker will
+; write out 64KB of zeros. (Tried using .rdata$zz and put it at the end
+; of that section, but the linker did not reduce the RawDataSize.) The
+; assmebler does not let us control the write flag directly, so we emit
+; a linker directive that switches of the write flag for the section.
+;
+; - Fallback: Code section.
+;
+%ifdef ASM_FORMAT_PE
+section .drectve info
+ db '-section:.zero,!W '
+section .zero bss align=4096
+%else
+BEGINCODE
+%endif
+
+;;
+; 64KB of zero memory with various sized labels.
+;
+EXPORTEDNAME_EX g_abRTZeroPage, object
+EXPORTEDNAME_EX g_abRTZero4K, object
+EXPORTEDNAME_EX g_abRTZero8K, object
+EXPORTEDNAME_EX g_abRTZero16K, object
+EXPORTEDNAME_EX g_abRTZero32K, object
+EXPORTEDNAME_EX g_abRTZero64K, object
+%ifdef ASM_FORMAT_PE
+ resb 0x10000
+%else
+ times 0x10000/(16*4) dd 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0
+%endif
+%ifdef ASM_FORMAT_ELF
+size g_abRTZeroPage _4K
+size g_abRTZero4K _4K
+size g_abRTZero8K _8K
+size g_abRTZero16K _16K
+size g_abRTZero32K _32K
+size g_abRTZero64K _64K
+%endif
+
diff --git a/src/VBox/Runtime/common/misc/zero.cpp b/src/VBox/Runtime/common/misc/zero.cpp
new file mode 100644
index 00000000..c1a32e1e
--- /dev/null
+++ b/src/VBox/Runtime/common/misc/zero.cpp
@@ -0,0 +1,52 @@
+/* $Id: zero.cpp $ */
+/** @file
+ * IPRT - Zero Memory.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cdefs.h>
+#include <iprt/types.h>
+#include <iprt/param.h>
+#include <iprt/zero.h>
+
+uint8_t const g_abRTZeroPage[PAGE_SIZE] = { 0 };
+uint8_t const g_abRTZero4K[_4K] = { 0 };
+uint8_t const g_abRTZero8K[_8K] = { 0 };
+uint8_t const g_abRTZero16K[_16K] = { 0 };
+uint8_t const g_abRTZero32K[_32K] = { 0 };
+uint8_t const g_abRTZero64K[_64K] = { 0 };
+
diff --git a/src/VBox/Runtime/common/net/Makefile.kup b/src/VBox/Runtime/common/net/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/net/Makefile.kup
diff --git a/src/VBox/Runtime/common/net/macstr.cpp b/src/VBox/Runtime/common/net/macstr.cpp
new file mode 100644
index 00000000..a5eab282
--- /dev/null
+++ b/src/VBox/Runtime/common/net/macstr.cpp
@@ -0,0 +1,123 @@
+/* $Id: macstr.cpp $ */
+/** @file
+ * IPRT - Command Line Parsing
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cidr.h>
+#include <iprt/net.h> /* must come before getopt.h */
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/message.h>
+#include <iprt/string.h>
+
+
+/**
+ * Converts an stringified Ethernet MAC address into the RTMAC representation.
+ *
+ * @returns VINF_SUCCESS on success.
+ *
+ * @param pszValue The value to convert.
+ * @param pAddr Where to store the result.
+ */
+RTDECL(int) RTNetStrToMacAddr(const char *pszValue, PRTMAC pAddr)
+{
+ /*
+ * First check if it might be a 12 xdigit string without any separators.
+ */
+ size_t cchValue = strlen(pszValue);
+ if (cchValue >= 12 && memchr(pszValue, ':', 12) == NULL)
+ {
+ bool fOkay = true;
+ for (size_t off = 0; off < 12 && fOkay; off++)
+ fOkay = RT_C_IS_XDIGIT(pszValue[off]);
+ if (fOkay && cchValue > 12)
+ for (size_t off = 12; off < cchValue && fOkay; off++)
+ fOkay = RT_C_IS_SPACE(pszValue[off]);
+ if (fOkay)
+ {
+ int rc = RTStrConvertHexBytes(pszValue, pAddr, sizeof(*pAddr), 0);
+ if (RT_SUCCESS(rc))
+ rc = VINF_SUCCESS;
+ return rc;
+ }
+ }
+
+ /*
+ * Not quite sure if I should accept stuff like "08::27:::1" here...
+ * The code is accepting "::" patterns now, except for for the first
+ * and last parts.
+ */
+
+ /* first */
+ char *pszNext;
+ int rc = RTStrToUInt8Ex(RTStrStripL(pszValue), &pszNext, 16, &pAddr->au8[0]);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
+ return VERR_INVALID_PARAMETER;
+ if (*pszNext++ != ':')
+ return VERR_INVALID_PARAMETER;
+
+ /* middle */
+ for (unsigned i = 1; i < 5; i++)
+ {
+ if (*pszNext == ':')
+ pAddr->au8[i] = 0;
+ else
+ {
+ rc = RTStrToUInt8Ex(pszNext, &pszNext, 16, &pAddr->au8[i]);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
+ return rc;
+ if (*pszNext != ':')
+ return VERR_INVALID_PARAMETER;
+ }
+ pszNext++;
+ }
+
+ /* last */
+ rc = RTStrToUInt8Ex(pszNext, &pszNext, 16, &pAddr->au8[5]);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES)
+ return rc;
+ pszNext = RTStrStripL(pszNext);
+ if (*pszNext)
+ return VERR_INVALID_PARAMETER;
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTNetStrToMacAddr);
diff --git a/src/VBox/Runtime/common/net/netaddrstr.cpp b/src/VBox/Runtime/common/net/netaddrstr.cpp
new file mode 100644
index 00000000..160c8aaf
--- /dev/null
+++ b/src/VBox/Runtime/common/net/netaddrstr.cpp
@@ -0,0 +1,1233 @@
+/* $Id: netaddrstr.cpp $ */
+/** @file
+ * IPRT - Network Address String Handling.
+ *
+ * @remarks Don't add new functionality to this file, it goes into netaddrstr2.cpp
+ * or some other suitable file (legal reasons + code not up to oracle
+ * quality standards and requires rewrite from scratch).
+ */
+
+/*
+ * Contributed by Oliver Loch.
+ *
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/net.h>
+
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include "internal/string.h"
+
+
+/** @page pg_rtnetipv6_addr IPv6 Address Format
+ *
+ * IPv6 Addresses, their representation in text and other problems.
+ *
+ * The following is based on:
+ *
+ * - http://tools.ietf.org/html/rfc4291
+ * - http://tools.ietf.org/html/rfc5952
+ * - http://tools.ietf.org/html/rfc6052
+ *
+ *
+ * Before you start using those functions, you should have an idea of
+ * what you're dealing with, before you come and blame the functions...
+ *
+ * First of all, the address itself:
+ *
+ * An address is written like this: (READ THIS FINE MANUAL!)
+ *
+ * - 2001:db8:abc:def::1
+ *
+ * The characters between two colons are called a "hextet".
+ * Each hextet consists of four characters and each IPv6 address
+ * consists of a maximum of eight hextets. So a full blown address
+ * would look like this:
+ *
+ * - 1111:2222:3333:4444:5555:6666:7777:8888
+ *
+ * The allowed characters are "0123456789abcdef". They have to be
+ * lower case. Upper case is not allowed.
+ *
+ * *** Gaps and adress shortening
+ *
+ * If an address contains hextets that contain only "0"s, they
+ * can be shortened, like this:
+ *
+ * - 1111:2222:0000:0000:0000:0000:7777:8888 -> 1111:2222::7777:8888
+ *
+ * The double colon represents the hextets that have been shortened "::".
+ * The "::" will be called "gap" from now on.
+ *
+ * When shortening an address, there are some special rules that need to be applied:
+ *
+ * - Shorten always the longest group of hextets.
+ *
+ * Let's say, you have this address: 2001:db8:0:0:0:1:0:0 then it has to be
+ * shortened to "2001:db8::1:0:0". Shortening to "2001:db8:0:0:0:1::" would
+ * return an error.
+ *
+ * - Two or more gaps the same size.
+ *
+ * Let's say you have this address: 2001:db8:0:0:1:0:0:1. As you can see, there
+ * are two gaps, both the size of two hextets. If you shorten the last two hextets,
+ * you end up in pain, as the RFC forbids this, so the correct address is:
+ * "2001:db8::1:0:0:1"
+ *
+ * It's important to note that an address can only be shortened ONE TIME!
+ * This is invalid: "2001:db8::1::1"
+ *
+ * *** The scope.
+ *
+ * Each address has a so called "scope" it is added to the end of the address,
+ * separated by a percent sign "%". If there is no scope at the end, it defaults
+ * to "0".
+ *
+ * So "2001:db8::1" is the same as "2001:db8::1%0".
+ *
+ * As in IPv6 all network interfaces can/should have the same address, the scope
+ * gives you the ability to choose on which interface the system should listen.
+ *
+ * AFAIK, the scope can be used with unicast as well as link local addresses, but
+ * it is mandatory with link local addresses (starting with fe80::).
+ *
+ * On Linux the default scope is the interface's name. On Windows it's just the index
+ * of the interface. Run "route print -6" in the shell, to see the interface's index
+ * on Winodows.
+ *
+ * All functions can deal with the scope, and DO NOT warn if you put garbage there.
+ *
+ * *** Port added to the IPv6 address
+ *
+ * There is only one way to add a port to an IPv6 address is to embed it in brackets:
+ *
+ * [2001:db8::1]:12345
+ *
+ * This gives you the address "2001:db8::1" and the port "12345".
+ *
+ * What also works, but is not recommended by rfc is to separate the port
+ * by a dot:
+ *
+ * 2001:db8::1.12345
+ *
+ * It even works with embedded IPv4 addresses.
+ *
+ * *** Special addresses and how they are written
+ *
+ * The following are notations to represent "special addresses".
+ *
+ * "::" IN6ADDR_ANY
+ * ":::123" IN6ADDR_ANY with port "123"
+ * "[::]:123" IN6ADDR_ANY with port "123"
+ * "[:::123]" -> NO. Not allowed and makes no sense
+ * "::1" -> address of the loopback device (127.0.0.1 in v4)
+ *
+ * On systems with dual sockets, one can use so called embedded IPv4 addresses:
+ *
+ * "::ffff:192.168.1.1" results in the IPv6 address "::ffff:c0a8:0101" as two octets
+ * of the IPv4 address will be converted to one hextet in the IPv6 address.
+ *
+ * The prefix of such addresses MUST BE "::ffff:", 10 bytes as zero and two bytes as 255.
+ *
+ * The so called IPv4-compatible IPv6 addresses are deprecated and no longer in use.
+ *
+ * *** Valid addresses and string
+ *
+ * If you use any of the IPv6 address functions, keep in mind, that those addresses
+ * are all returning "valid" even if the underlying system (e.g. VNC) doesn't like
+ * such strings.
+ *
+ * [2001:db8::1]
+ * [2001:db8::1]:12345
+ *
+ * and so on. So to make sure you only pass the underlying software a pure IPv6 address
+ * without any garbage, you should use the "outAddress" parameters to get a RFC compliant
+ * address returned.
+ *
+ * So after reading the above, you'll start using the functions and see a bool called
+ * "followRfc" which is true by default. This is what this bool does:
+ *
+ * The following addresses all represent the exact same address:
+ *
+ * 1 - 2001:db8::1
+ * 2 - 2001:db8:0::1
+ * 3 - 2001:0db8:0000:0000:0000:0000:0000:0001
+ * 4 - 2001:DB8::1
+ * 5 - [2001:db8::1]
+ * 6 - [2001:db8:0::1]
+ *
+ * According to RFC 5952, number two, three, four and six are invalid.
+ *
+ * #2 - because there is a single hextet that hasn't been shortened
+ *
+ * #3 - because there has nothing been shortened (hextets 3 to 7) and
+ * there are leading zeros in at least one hextet ("0db8")
+ *
+ * #4 - all characters in an IPv6 address have to be lower case
+ *
+ * #6 - same as two but included in brackets
+ *
+ * If you follow RFC, the above addresses are not converted and an
+ * error is returned. If you turn RFC off, you will get the expected
+ * representation of the address.
+ *
+ * It's a nice way to convert "weird" addresses to rfc compliant addresses
+ *
+ */
+
+
+/**
+ * Parses any string and tests if it is an IPv6 Address
+ *
+ * This function should NOT be used directly. If you do, note
+ * that no security checks are done at the moment. This can change.
+ *
+ * @returns iprt sstatus code, yeah, right... This function most certainly DOES
+ * NOT RETURN ANY IPRT STATUS CODES. It's also a unreadable mess.
+ * @param pszAddress The strin that holds the IPv6 address
+ * @param addressLength The length of pszAddress
+ * @param pszAddressOut Returns a plain, full blown IPv6 address
+ * as a char array
+ * @param addressOutSize The size of pszAddressOut (length)
+ * @param pPortOut 32 bit unsigned integer, holding the port
+ * If pszAddress doesn't contain a port, it's 0
+ * @param pszScopeOut Returns the scope of the address, if none it's 0
+ * @param scopeOutSize sizeof(pszScopeOut)
+ * @param pBrackets returns true if the address was enclosed in brackets
+ * @param pEmbeddedV4 returns true if the address is an embedded IPv4 address
+ * @param followRfc if set to true, the function follows RFC (default)
+ */
+static int rtStrParseAddrStr6(const char *pszAddress, size_t addressLength, char *pszAddressOut, size_t addressOutSize, uint32_t *pPortOut, char *pszIfIdOut, size_t ifIdOutSize, bool *pBrackets, bool *pEmbeddedV4, bool followRfc)
+{
+ /************************\
+ * Pointer Hell Ahead *
+ \************************/
+
+ const char szIpV6AddressChars[] = "ABCDEF01234567890abcdef.:[]%"; // order IMPORTANT
+ const char szIpV4AddressChars[] = "01234567890.:[]"; // order IMPORTANT
+ const char szLinkLocalPrefix[] = "FfEe8800"; //
+ const char *pszIpV6AddressChars = NULL, *pszIpV4AddressChars = NULL, *pszLinkLocalPrefix = NULL;
+
+ char *pszSourceAddress = NULL, *pszSourceAddressStart = NULL;
+ char *pszResultAddress = NULL, *pszResultAddressStart = NULL;
+ char *pszResultAddress4 = NULL, *pszResultAddress4Start = NULL;
+ char *pszResultPort = NULL, *pszResultPortStart = NULL;
+ char *pszInternalAddress = NULL, *pszInternalAddressStart = NULL;
+ char *pszInternalPort = NULL, *pszInternalPortStart = NULL;
+
+ char *pStart = NULL, *pNow = NULL, *pNext = NULL, *pNowChar = NULL, *pIfId = NULL, *pIfIdEnd = NULL;
+ char *pNowDigit = NULL, *pFrom = NULL, *pTo = NULL, *pLast = NULL;
+ char *pGap = NULL, *pMisc = NULL, *pDotStart = NULL, *pFieldStart = NULL, *pFieldEnd = NULL;
+ char *pFieldStartLongest = NULL, *pBracketOpen = NULL, *pBracketClose = NULL;
+ char *pszRc = NULL;
+
+ bool isLinkLocal = false;
+ char szDummy[4];
+
+ uint8_t *pByte = NULL;
+ uint32_t byteOut = 0;
+ uint16_t returnValue = 0;
+ uint32_t colons = 0;
+ uint32_t colonsOverAll = 0;
+ uint32_t fieldLength = 0;
+ uint32_t dots = 0;
+ size_t gapSize = 0;
+ uint32_t intPortOut = 0;
+
+ pszIpV4AddressChars = &szIpV4AddressChars[0];
+ pszIpV6AddressChars = &szIpV6AddressChars[6];
+ pszLinkLocalPrefix = &szLinkLocalPrefix[6];
+
+ if (!followRfc)
+ pszIpV6AddressChars = &szIpV6AddressChars[0];
+
+ if (addressLength<2)
+ returnValue = 711;
+
+ pszResultAddressStart = (char *)RTMemTmpAlloc(34);
+ pszInternalAddressStart = (char *)RTMemTmpAlloc(34);
+ pszInternalPortStart = (char * )RTMemTmpAlloc(10);
+
+ if (! (pszResultAddressStart && pszInternalAddressStart && pszInternalPortStart))
+ {
+ if (pszResultAddressStart)
+ RTMemTmpFree(pszResultAddressStart);
+
+ if (pszInternalAddressStart)
+ RTMemTmpFree(pszInternalAddressStart);
+
+ if (pszInternalPortStart)
+ RTMemTmpFree(pszInternalPortStart);
+
+ return -701;
+ }
+
+ memset(szDummy, '\0', 4);
+
+ pszResultAddress = pszResultAddressStart;
+ memset(pszResultAddressStart, '\0', 34);
+
+ pszInternalAddress = pszInternalAddressStart;
+ memset(pszInternalAddressStart, '\0' , 34);
+
+ pszInternalPort = pszInternalPortStart;
+ memset(pszInternalPortStart, '\0', 10);
+
+ pszSourceAddress = pszSourceAddressStart = (char *)pszAddress;
+
+ pFrom = pTo = pStart = pLast = pszSourceAddressStart;
+
+ while (*pszSourceAddress != '\0' && !returnValue)
+ {
+ pNow = NULL;
+ pNext = NULL;
+ pNowChar = NULL;
+ pNowDigit = NULL;
+
+ pNow = pszSourceAddress;
+ pNext = pszSourceAddress + 1;
+
+ if (!pFrom)
+ pFrom = pTo = pNow;
+
+ pNowChar = (char *)memchr(pszIpV6AddressChars, *pNow, strlen(pszIpV6AddressChars));
+ pNowDigit = (char *)memchr(pszIpV6AddressChars, *pNow, strlen(pszIpV6AddressChars) - 5);
+
+ if (pszResultPort)
+ {
+ if (pLast && (pszResultPort == pszSourceAddressStart))
+ {
+ if (*pLast == '\0')
+ returnValue = 721;
+
+ pszResultPortStart = (char *)RTMemTmpAlloc(10);
+
+ if (!pszResultPortStart)
+ returnValue = 702;
+
+ memset(pszResultPortStart, '\0', 10);
+ pszResultPort = pszResultPortStart;
+ pszSourceAddress = pLast;
+ pMisc = pLast;
+ pLast = NULL;
+ continue;
+ }
+
+ pNowDigit = NULL;
+ pNowDigit = (char *)memchr(pszIpV4AddressChars, *pNow, strlen(pszIpV4AddressChars) - 4);
+
+ if (strlen(pszResultPortStart) == 5)
+ returnValue = 11;
+
+ if (*pNow == '0' && pszResultPort == pszResultPortStart && *pNext != '\0' && (pNow - pMisc) < 5 )
+ {
+ pszSourceAddress++;
+ continue;
+ }
+
+ if (pNowDigit)
+ {
+ *pszResultPort = *pNowDigit;
+ pszResultPort++;
+ pszSourceAddress++;
+ continue;
+ }
+ else
+ returnValue = 12;
+ }
+
+ if (pszResultAddress4)
+ {
+ if (pszResultAddress4 == pszSourceAddressStart && pLast)
+ {
+ dots = 0;
+ pszResultAddress4 = NULL;
+ pszResultAddress4Start = NULL;
+ pszResultAddress4Start = (char *)RTMemTmpAlloc(20);
+
+ if (!pszResultAddress4Start)
+ {
+ returnValue = 401;
+ break;
+ }
+
+ memset(pszResultAddress4Start, '\0', 20);
+ pszResultAddress4 = pszResultAddress4Start;
+ pszSourceAddress = pLast;
+ pFrom = pLast;
+ pTo = pLast;
+ pLast = NULL;
+ continue;
+ }
+
+ pTo = pNow;
+ pNowDigit = NULL;
+ pNowDigit = (char *)memchr(pszIpV4AddressChars, *pNow, strlen(pszIpV4AddressChars) - 4);
+
+ if (!pNowDigit && *pNow != '.' && *pNow != ']' && *pNow != ':' && *pNow != '%')
+ returnValue = 412;
+
+ if ((pNow - pFrom) > 3)
+ {
+ returnValue = 402;
+ break;
+ }
+
+ if (pNowDigit && *pNext != '\0')
+ {
+ pszSourceAddress++;
+ continue;
+ }
+
+ if (!pNowDigit && !pBracketOpen && (*pNext == '.' || *pNext == ']' || *pNext == ':'))
+ returnValue = 411;
+
+ memset(pszResultAddress4, '0', 3);
+ pMisc = pszResultAddress4 + 2;
+ pszResultAddress4 = pszResultAddress4 + 3;
+
+ if (*pNow != '.' && !pNowDigit && strlen(pszResultAddress4Start) < 9)
+ returnValue = 403;
+
+ if ((pTo - pFrom) > 0)
+ pTo--;
+
+ dots++;
+
+ while (pTo >= pFrom)
+ {
+ *pMisc = *pTo;
+ pMisc--;
+ pTo--;
+ }
+
+ if (dots == 4 && *pNow == '.')
+ {
+ if (!pBracketOpen)
+ {
+ pszResultPort = pszSourceAddressStart;
+ pLast = pNext;
+ }
+ else
+ {
+ returnValue = 409;
+ }
+ }
+
+ dots = 0;
+
+ pFrom = pNext;
+ pTo = pNext;
+
+ if (strlen(pszResultAddress4Start) > 11)
+ pszResultAddress4 = NULL;
+
+ if ((*pNow == ':' || *pNow == '.') && strlen(pszResultAddress4Start) == 12)
+ {
+ pLast = pNext;
+ pszResultPort = pszSourceAddressStart;
+ }
+
+ if (*pNow == '%')
+ {
+ pIfId = pNow;
+ pLast = pNow;
+ continue;
+ }
+ pszSourceAddress = pNext;
+
+ if (*pNow != ']')
+ continue;
+
+ pFrom = pNow;
+ pTo = pNow;
+ }
+
+ if (pIfId && (!pIfIdEnd))
+ {
+ if (*pIfId == '%' && pIfId == pLast && *pNext != '\0')
+ {
+ pFrom = pNext;
+ pIfId = pNext;
+ pLast = NULL;
+
+ pszSourceAddress++;
+ continue;
+ }
+
+ if (*pNow == '%' && pIfId <= pNow)
+ {
+ returnValue = 442;
+ break;
+ }
+
+ if (*pNow != ']' && *pNext != '\0')
+ {
+ pTo = pNow;
+ pszSourceAddress++;
+ continue;
+ }
+
+ if (*pNow == ']')
+ {
+ pIfIdEnd = pNow - 1;
+ pFrom = pNow;
+ pTo = pNow;
+ continue;
+ }
+ else
+ {
+ pIfIdEnd = pNow;
+ pFrom = NULL;
+ pTo = NULL;
+ pszSourceAddress++;
+ continue;
+ }
+ }
+
+ if (!pNowChar)
+ {
+ returnValue = 254;
+
+ if (followRfc)
+ {
+ pMisc = (char *)memchr(&szIpV6AddressChars[0], *pNow, strlen(&szIpV6AddressChars[0]));
+
+ if (pMisc)
+ returnValue = 253;
+ }
+ }
+
+ if (strlen(pszResultAddressStart) > 32 && !pszResultAddress4Start)
+ returnValue = 255;
+
+ if (pNowDigit && *pNext != '\0' && colons == 0)
+ {
+ pTo = pNow;
+ pszSourceAddress++;
+ continue;
+ }
+
+ if (*pNow == ':' && *pNext != '\0')
+ {
+ colonsOverAll++;
+ colons++;
+ pszSourceAddress++;
+ continue;
+ }
+
+ if (*pNow == ':' )
+ {
+ colons++;
+ colonsOverAll++;
+ }
+
+ if (*pNow == '.')
+ {
+ pMisc = pNow;
+
+ while (*pMisc != '\0' && *pMisc != ']')
+ {
+ if (*pMisc == '.')
+ dots++;
+
+ pMisc++;
+ }
+ }
+
+ if (*pNow == ']')
+ {
+ if (pBracketClose)
+ returnValue = 77;
+
+ if (!pBracketOpen)
+ returnValue = 22;
+
+ if (*pNext == ':' || *pNext == '.')
+ {
+ pszResultPort = pszSourceAddressStart;
+ pLast = pNext + 1;
+ }
+
+ if (pFrom == pNow)
+ pFrom = NULL;
+
+ pBracketClose = pNow;
+ }
+
+ if (*pNow == '[')
+ {
+ if (pBracketOpen)
+ returnValue = 23;
+
+ if (pStart != pNow)
+ returnValue = 24;
+
+ pBracketOpen = pNow;
+ pStart++;
+ pFrom++;
+ pszSourceAddress++;
+ continue;
+ }
+
+ if (*pNow == '%')
+ {
+ if (pIfId)
+ returnValue = 441;
+
+ pLast = pNext;
+ pIfId = pNext;
+ }
+
+ if (colons > 0)
+ {
+ if (colons == 1)
+ {
+ if (pStart + 1 == pNow )
+ returnValue = 31;
+
+ if (*pNext == '\0' && !pNowDigit)
+ returnValue = 32;
+
+ pLast = pNow;
+ }
+
+ if (colons == 2)
+ {
+ if (pGap)
+ returnValue = 33;
+
+ pGap = pszResultAddress + 4;
+
+ if (pStart + 1 == pNow || pStart + 2 == pNow)
+ {
+ pGap = pszResultAddressStart;
+ pFrom = pNow;
+ }
+
+ if (*pNext == '\0' && !pNowDigit)
+ pszSourceAddress++;
+
+ if (*pNext != ':' && *pNext != '.')
+ pLast = pNow;
+ }
+
+ if (colons == 3)
+ {
+ pFrom = pLast;
+ pLast = pNow;
+
+ if (*pNext == '\0' && !pNowDigit)
+ returnValue = 34;
+
+ if (pBracketOpen)
+ returnValue = 35;
+
+ if (pGap && followRfc)
+ returnValue = 36;
+
+ if (!pGap)
+ pGap = pszResultAddress + 4;
+
+ if (pStart + 3 == pNow)
+ {
+ pszResultPort = pszSourceAddressStart;
+ pGap = pszResultAddress;
+ pFrom = NULL;
+ }
+
+ if (pNowDigit)
+ {
+ pszResultPort = pszSourceAddressStart;
+ }
+ }
+ }
+ if (*pNext == '\0' && colons == 0 && !pIfIdEnd)
+ {
+ pFrom = pLast;
+
+ if (pNowDigit)
+ pTo = pNow;
+
+ pLast = NULL;
+ }
+
+ if (dots > 0)
+ {
+ if (dots == 1)
+ {
+ pszResultPort = pszSourceAddressStart;
+ pLast = pNext;
+ }
+
+ if (dots == 4 && pBracketOpen)
+ returnValue = 601;
+
+ if (dots == 3 || dots == 4)
+ {
+ pszResultAddress4 = pszSourceAddressStart;
+ pLast = pFrom;
+ pFrom = NULL;
+ }
+
+ if (dots > 4)
+ returnValue = 603;
+
+ dots = 0;
+ }
+
+ if (pFrom && pTo)
+ {
+ if (pTo - pFrom > 3)
+ {
+ returnValue = 51;
+ break;
+ }
+
+ if (followRfc)
+ {
+ if ((pTo - pFrom > 0) && *pFrom == '0')
+ returnValue = 101;
+
+ if ((pTo - pFrom) == 0 && *pFrom == '0' && colons == 2)
+ returnValue = 102;
+
+ if ((pTo - pFrom) == 0 && *pFrom == '0' && pszResultAddress == pGap)
+ returnValue = 103;
+
+ if ((pTo - pFrom) == 0 && *pFrom == '0')
+ {
+ if (!pFieldStart)
+ {
+ pFieldStart = pszResultAddress;
+ pFieldEnd = pszResultAddress + 4;
+ }
+ else
+ {
+ pFieldEnd = pFieldEnd + 4;
+ }
+ }
+ else
+ {
+ if ((size_t)(pFieldEnd - pFieldStart) > fieldLength)
+ {
+ fieldLength = pFieldEnd - pFieldStart;
+ pFieldStartLongest = pFieldStart;
+ }
+
+ pFieldStart = NULL;
+ pFieldEnd = NULL;
+ }
+ }
+ if (!(pGap == pszResultAddressStart && (size_t)(pNow - pStart) == colons))
+ {
+ memset(pszResultAddress, '0', 4);
+ pMisc = pszResultAddress + 3;
+ pszResultAddress = pszResultAddress + 4;
+
+ if (pFrom == pStart && (pTo - pFrom) == 3)
+ {
+ isLinkLocal = true;
+
+ while (pTo >= pFrom)
+ {
+ *pMisc = *pTo;
+
+ if (*pTo != *pszLinkLocalPrefix && *pTo != *(pszLinkLocalPrefix + 1))
+ isLinkLocal = false;
+
+ pTo--;
+ pMisc--;
+ pszLinkLocalPrefix = pszLinkLocalPrefix - 2;
+ }
+ }
+ else
+ {
+ while (pTo >= pFrom)
+ {
+ *pMisc = *pTo;
+ pMisc--;
+ pTo--;
+ }
+ }
+ }
+
+ pFrom = pNow;
+ pTo = pNow;
+
+ }
+ if (*pNext == '\0' && colons == 0)
+ pszSourceAddress++;
+
+ if (*pNext == '\0' && !pBracketClose && !pszResultPort)
+ pTo = pNext;
+
+ colons = 0;
+ } // end of loop
+
+ if (!returnValue && colonsOverAll < 2)
+ returnValue = 252;
+
+ if (!returnValue && (pBracketOpen && !pBracketClose))
+ returnValue = 25;
+
+ if (!returnValue && pGap)
+ {
+ gapSize = 32 - strlen(pszResultAddressStart);
+
+ if (followRfc)
+ {
+ if (gapSize < 5)
+ returnValue = 104;
+
+ if (fieldLength > gapSize)
+ returnValue = 105;
+
+ if (fieldLength == gapSize && pFieldStartLongest < pGap)
+ returnValue = 106;
+ }
+
+ pszResultAddress = pszResultAddressStart;
+ pszInternalAddress = pszInternalAddressStart;
+
+ if (!returnValue && pszResultAddress4Start)
+ {
+ if (strlen(pszResultAddressStart) > 4)
+ returnValue = 405;
+
+ pszResultAddress = pszResultAddressStart;
+
+ if (pGap != pszResultAddressStart)
+ returnValue = 407;
+
+ memset(pszInternalAddressStart, '0', 20);
+ pszInternalAddress = pszInternalAddressStart + 20;
+
+ for (int i = 0; i < 4; i++)
+ {
+ if (*pszResultAddress != 'f' && *pszResultAddress != 'F')
+ {
+ returnValue = 406;
+ break;
+ }
+
+ *pszInternalAddress = *pszResultAddress;
+ pszResultAddress++;
+ pszInternalAddress++;
+ }
+ pszResultAddress4 = pszResultAddress4Start;
+
+ for (int i = 0; i<4; i++)
+ {
+ memcpy(szDummy, pszResultAddress4, 3);
+
+ int rc = RTStrToUInt32Ex((const char *)&szDummy[0], NULL, 16, &byteOut);
+
+ if (rc == 0 && byteOut < 256)
+ {
+ RTStrPrintf(szDummy, 3, "%02x", byteOut);
+ memcpy(pszInternalAddress, szDummy, 2);
+ pszInternalAddress = pszInternalAddress + 2;
+ pszResultAddress4 = pszResultAddress4 + 3;
+ memset(szDummy, '\0', 4);
+ }
+ else
+ {
+ returnValue = 499;
+ }
+ }
+ }
+ else
+ {
+ while (!returnValue && pszResultAddress != pGap)
+ {
+ *pszInternalAddress = *pszResultAddress;
+ pszResultAddress++;
+ pszInternalAddress++;
+ }
+
+ memset(pszInternalAddress, '0', gapSize);
+ pszInternalAddress = pszInternalAddress + gapSize;
+
+ while (!returnValue && *pszResultAddress != '\0')
+ {
+ *pszInternalAddress = *pszResultAddress;
+ pszResultAddress++;
+ pszInternalAddress++;
+ }
+ }
+ }
+ else
+ {
+ if (!returnValue)
+ {
+ if (strlen(pszResultAddressStart) != 32)
+ returnValue = 111;
+
+ if (followRfc)
+ {
+ if (fieldLength > 4)
+ returnValue = 112;
+ }
+
+ memcpy(pszInternalAddressStart, pszResultAddressStart, strlen(pszResultAddressStart));
+ }
+ }
+
+ if (pszResultPortStart)
+ {
+ if (strlen(pszResultPortStart) > 0 && strlen(pszResultPortStart) < 6)
+ {
+ memcpy(pszInternalPortStart, pszResultPortStart, strlen(pszResultPortStart));
+
+ intPortOut = 0;
+ int rc = RTStrToUInt32Ex(pszInternalPortStart, NULL, 10, &intPortOut);
+
+ if (rc == 0)
+ {
+ if (!(intPortOut > 0 && intPortOut < 65536))
+ intPortOut = 0;
+ }
+ else
+ {
+ returnValue = 888;
+ }
+ }
+ else
+ {
+ returnValue = 889;
+ }
+ }
+
+ /*
+ full blown address 32 bytes, no colons -> pszInternalAddressStart
+ port as string -> pszResultPortStart
+ port as binary integer -> intPortOut
+ interface id in pIfId and pIfIdEnd
+
+ Now fill the out parameters.
+
+ */
+
+ if (!returnValue && pszAddressOut)
+ {
+ if (strlen(pszInternalAddressStart) < addressOutSize)
+ {
+ pszRc = NULL;
+ pszRc = (char *)memset(pszAddressOut, '\0', addressOutSize);
+
+ if (!pszRc)
+ returnValue = 910;
+
+ pszRc = NULL;
+
+ pszRc = (char *)memcpy(pszAddressOut, pszInternalAddressStart, strlen(pszInternalAddressStart));
+
+ if (!pszRc)
+ returnValue = 911;
+ }
+ else
+ {
+ returnValue = 912;
+ }
+ }
+
+ if (!returnValue && pPortOut)
+ {
+ *pPortOut = intPortOut;
+ }
+
+ if (!returnValue && pszIfIdOut)
+ {
+ if (pIfIdEnd && pIfId)
+ {
+ if ((size_t)(pIfIdEnd - pIfId) + 1 < ifIdOutSize)
+ {
+ pszRc = NULL;
+ pszRc = (char *)memset(pszIfIdOut, '\0', ifIdOutSize);
+
+ if (!pszRc)
+ returnValue = 913;
+
+ pszRc = NULL;
+ pszRc = (char *)memcpy(pszIfIdOut, pIfId, (pIfIdEnd - pIfId) + 1);
+
+ if (!pszRc)
+ returnValue = 914;
+ }
+ else
+ {
+ returnValue = 915;
+ }
+ }
+ else
+ {
+ pszRc = NULL;
+ pszRc = (char *)memset(pszIfIdOut, '\0', ifIdOutSize);
+
+ if (!pszRc)
+ returnValue = 916;
+ }
+ // temporary hack
+ if (isLinkLocal && (strlen(pszIfIdOut) < 1))
+ {
+ memset(pszIfIdOut, '\0', ifIdOutSize);
+ *pszIfIdOut = '%';
+ pszIfIdOut++;
+ *pszIfIdOut = '0';
+ pszIfIdOut++;
+ }
+ }
+
+ if (pBracketOpen && pBracketClose && pBrackets)
+ *pBrackets = true;
+
+ if (pEmbeddedV4 && pszResultAddress4Start)
+ *pEmbeddedV4 = true;
+
+ if (pszResultAddressStart)
+ RTMemTmpFree(pszResultAddressStart);
+
+ if (pszResultPortStart)
+ RTMemTmpFree(pszResultPortStart);
+
+ if (pszResultAddress4Start)
+ RTMemTmpFree(pszResultAddress4Start);
+
+ if (pszInternalAddressStart)
+ RTMemTmpFree(pszInternalAddressStart);
+
+ if (pszInternalPortStart)
+ RTMemTmpFree(pszInternalPortStart);
+
+ return (uint32_t)(returnValue - (returnValue * 2)); // make it negative...
+}
+
+/**
+ * Takes a string and returns a RFC compliant string of the address
+ * This function SHOULD NOT be used directly. It expects a 33 byte
+ * char array with a full blown IPv6 address without separators.
+ *
+ * @returns iprt status code.
+ * @param psz The string to convert
+ * @param pszAddrOut The char[] that will hold the result
+ * @param addOutSize The size of the char[] from above.
+ * @param pszPortOut char[] for the text representation of the port
+ * @param portOutSize sizeof(pszPortOut);
+ */
+DECLHIDDEN(int) rtStrToIpAddr6Str(const char *psz, char *pszAddrOut, size_t addrOutSize, char *pszPortOut, size_t portOutSize, bool followRfc)
+{
+ char *pStart = NULL;
+ char *pGapStart = NULL;
+ char *pGapEnd = NULL;
+ char *pGapTStart = NULL;
+ char *pGapTEnd = NULL;
+ char *pCurrent = NULL;
+ char *pOut = NULL;
+
+ if (!psz || !pszAddrOut)
+ return VERR_NOT_SUPPORTED;
+
+ if (addrOutSize < 40)
+ return VERR_NOT_SUPPORTED;
+
+ pStart = (char *)psz;
+ pCurrent = (char *)psz;
+ pGapStart = (char *)psz;
+ pGapEnd = (char *)psz;
+
+ while (*pCurrent != '\0')
+ {
+ if (*pCurrent != '0')
+ pGapTStart = NULL;
+
+ if ((pCurrent - pStart) % 4 == 0) // ok, start of a hextet
+ {
+ if (*pCurrent == '0' && !pGapTStart)
+ pGapTStart = pCurrent;
+ }
+
+ if ((pCurrent - pStart) % 4 == 3)
+ {
+ if (*pCurrent == '0' && pGapTStart)
+ pGapTEnd = pCurrent;
+
+ if (pGapTStart && pGapTEnd)
+ {
+ pGapTEnd = pCurrent;
+
+ if ((pGapTEnd - pGapTStart) > (pGapEnd - pGapStart))
+ {
+ pGapEnd = pGapTEnd;
+ pGapStart = pGapTStart;
+ }
+ }
+ }
+
+ pCurrent++;
+ }
+
+ pCurrent = (char *)psz;
+ pStart = (char *)psz;
+ pOut = (char *)pszAddrOut;
+
+ while (*pCurrent != '\0')
+ {
+ if (*pCurrent != '0')
+ pGapTStart = NULL;
+
+ if (!pGapTStart)
+ {
+ *pOut = *pCurrent;
+ pOut++;
+ }
+
+ if ((pCurrent - pStart) % 4 == 3)
+ {
+ if (pGapTStart && *pCurrent == '0')
+ {
+ *pOut = *pCurrent;
+ pOut++;
+ }
+
+ if (*(pCurrent + 1) != '\0')
+ {
+ *pOut = ':';
+ pOut++;
+ }
+
+ pGapTStart = pCurrent + 1;
+ }
+
+ if ((pCurrent + 1) == pGapStart && (pGapEnd - pGapStart) > 3)
+ {
+ *pOut = ':';
+ pOut++;
+ pCurrent = pGapEnd;
+ }
+
+ pCurrent++;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tests if the given string is a valid IPv6 address.
+ *
+ * @returns 0 if valid, some random number if not. THIS IS NOT AN IPRT STATUS!
+ * @param psz The string to test
+ * @param pszResultAddress plain address, optional read "valid addresses
+ * and strings" above.
+ * @param resultAddressSize size of pszResultAddress
+ * @param addressOnly return only the plain address (no scope)
+ * Ignored, and will always return the if id
+ */
+static int rtNetIpv6CheckAddrStr(const char *psz, char *pszResultAddress, size_t resultAddressSize, bool addressOnly, bool followRfc)
+{
+ int rc;
+ int rc2;
+ int returnValue = VERR_NOT_SUPPORTED; /* gcc want's this initialized, I understand its confusion. */
+
+ char *p = NULL, *pl = NULL;
+
+ size_t memAllocMaxSize = RT_MAX(strlen(psz), resultAddressSize) + 40;
+
+ char *pszAddressOutLocal = (char *)RTMemTmpAlloc(memAllocMaxSize);
+ char *pszIfIdOutLocal = (char *)RTMemTmpAlloc(memAllocMaxSize);
+ char *pszAddressRfcOutLocal = (char *)RTMemTmpAlloc(memAllocMaxSize);
+
+ if (!pszAddressOutLocal || !pszIfIdOutLocal || !pszAddressRfcOutLocal)
+ return VERR_NO_TMP_MEMORY;
+
+ memset(pszAddressOutLocal, '\0', memAllocMaxSize);
+ memset(pszIfIdOutLocal, '\0', memAllocMaxSize);
+ memset(pszAddressRfcOutLocal, '\0', memAllocMaxSize);
+
+ rc = rtStrParseAddrStr6(psz, strlen(psz), pszAddressOutLocal, memAllocMaxSize, NULL, pszIfIdOutLocal, memAllocMaxSize, NULL, NULL, followRfc);
+
+ if (rc == 0)
+ returnValue = VINF_SUCCESS;
+
+ if (rc == 0 && pszResultAddress)
+ {
+ // convert the 32 characters to a valid, shortened ipv6 address
+
+ rc2 = rtStrToIpAddr6Str((const char *)pszAddressOutLocal, pszAddressRfcOutLocal, memAllocMaxSize, NULL, 0, followRfc);
+
+ if (rc2 != 0)
+ returnValue = 951;
+
+ // this is a temporary solution
+ if (!returnValue && strlen(pszIfIdOutLocal) > 0) // the if identifier is copied over _ALWAYS_ && !addressOnly)
+ {
+ p = pszAddressRfcOutLocal + strlen(pszAddressRfcOutLocal);
+
+ *p = '%';
+
+ p++;
+
+ pl = (char *)memcpy(p, pszIfIdOutLocal, strlen(pszIfIdOutLocal));
+
+ if (!pl)
+ returnValue = VERR_NOT_SUPPORTED;
+ }
+
+ pl = NULL;
+
+ pl = (char *)memcpy(pszResultAddress, pszAddressRfcOutLocal, strlen(pszAddressRfcOutLocal));
+
+ if (!pl)
+ returnValue = VERR_NOT_SUPPORTED;
+ }
+
+ if (rc != 0)
+ returnValue = VERR_NOT_SUPPORTED;
+
+ if (pszAddressOutLocal)
+ RTMemTmpFree(pszAddressOutLocal);
+
+ if (pszAddressRfcOutLocal)
+ RTMemTmpFree(pszAddressRfcOutLocal);
+
+ if (pszIfIdOutLocal)
+ RTMemTmpFree(pszIfIdOutLocal);
+
+ return returnValue;
+
+}
diff --git a/src/VBox/Runtime/common/net/netaddrstr2.cpp b/src/VBox/Runtime/common/net/netaddrstr2.cpp
new file mode 100644
index 00000000..5c848a63
--- /dev/null
+++ b/src/VBox/Runtime/common/net/netaddrstr2.cpp
@@ -0,0 +1,737 @@
+/* $Id: netaddrstr2.cpp $ */
+/** @file
+ * IPRT - Network Address String Handling.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/net.h>
+
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/stream.h>
+#include "internal/string.h"
+
+
+DECLHIDDEN(int) rtNetStrToIPv4AddrEx(const char *pcszAddr, PRTNETADDRIPV4 pAddr,
+ char **ppszNext)
+{
+ char *pszNext;
+ int rc;
+
+ AssertPtrReturn(pcszAddr, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pAddr, VERR_INVALID_PARAMETER);
+
+ rc = RTStrToUInt8Ex(pcszAddr, &pszNext, 10, &pAddr->au8[0]);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
+ return VERR_INVALID_PARAMETER;
+ if (*pszNext++ != '.')
+ return VERR_INVALID_PARAMETER;
+
+ rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[1]);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
+ return VERR_INVALID_PARAMETER;
+ if (*pszNext++ != '.')
+ return VERR_INVALID_PARAMETER;
+
+ rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[2]);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS)
+ return VERR_INVALID_PARAMETER;
+ if (*pszNext++ != '.')
+ return VERR_INVALID_PARAMETER;
+
+ rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &pAddr->au8[3]);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES && rc != VWRN_TRAILING_CHARS)
+ return VERR_INVALID_PARAMETER;
+
+ if (ppszNext != NULL)
+ *ppszNext = pszNext;
+ return rc;
+}
+
+
+RTDECL(int) RTNetStrToIPv4AddrEx(const char *pcszAddr, PRTNETADDRIPV4 pAddr,
+ char **ppszNext)
+{
+ return rtNetStrToIPv4AddrEx(pcszAddr, pAddr, ppszNext);
+}
+RT_EXPORT_SYMBOL(RTNetStrToIPv4AddrEx);
+
+
+RTDECL(int) RTNetStrToIPv4Addr(const char *pcszAddr, PRTNETADDRIPV4 pAddr)
+{
+ char *pszNext;
+ int rc;
+
+ AssertPtrReturn(pcszAddr, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pAddr, VERR_INVALID_PARAMETER);
+
+ pcszAddr = RTStrStripL(pcszAddr);
+ rc = rtNetStrToIPv4AddrEx(pcszAddr, pAddr, &pszNext);
+ if (RT_FAILURE(rc) || rc == VWRN_TRAILING_CHARS)
+ return VERR_INVALID_PARAMETER;
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTNetStrToIPv4Addr);
+
+
+RTDECL(bool) RTNetIsIPv4AddrStr(const char *pcszAddr)
+{
+ RTNETADDRIPV4 addrIPv4;
+ char *pszNext;
+ int rc;
+
+ if (pcszAddr == NULL)
+ return false;
+
+ rc = rtNetStrToIPv4AddrEx(pcszAddr, &addrIPv4, &pszNext);
+ if (rc != VINF_SUCCESS)
+ return false;
+
+ if (*pszNext != '\0')
+ return false;
+
+ return true;
+}
+RT_EXPORT_SYMBOL(RTNetIsIPv4AddrStr);
+
+
+RTDECL(bool) RTNetStrIsIPv4AddrAny(const char *pcszAddr)
+{
+ RTNETADDRIPV4 addrIPv4;
+ char *pszNext;
+ int rc;
+
+ if (pcszAddr == NULL)
+ return false;
+
+ pcszAddr = RTStrStripL(pcszAddr);
+ rc = rtNetStrToIPv4AddrEx(pcszAddr, &addrIPv4, &pszNext);
+ if (RT_FAILURE(rc) || rc == VWRN_TRAILING_CHARS)
+ return false;
+
+ if (addrIPv4.u != 0u) /* INADDR_ANY? */
+ return false;
+
+ return true;
+}
+RT_EXPORT_SYMBOL(RTNetStrIsIPv4AddrAny);
+
+
+RTDECL(int) RTNetMaskToPrefixIPv4(PCRTNETADDRIPV4 pMask, int *piPrefix)
+{
+ AssertReturn(pMask != NULL, VERR_INVALID_PARAMETER);
+
+ if (pMask->u == 0)
+ {
+ if (piPrefix != NULL)
+ *piPrefix = 0;
+ return VINF_SUCCESS;
+ }
+
+ const uint32_t uMask = RT_N2H_U32(pMask->u);
+
+ uint32_t uPrefixMask = UINT32_C(0xffffffff);
+ int iPrefixLen = 32;
+
+ while (iPrefixLen > 0)
+ {
+ if (uMask == uPrefixMask)
+ {
+ if (piPrefix != NULL)
+ *piPrefix = iPrefixLen;
+ return VINF_SUCCESS;
+ }
+
+ --iPrefixLen;
+ uPrefixMask <<= 1;
+ }
+
+ return VERR_INVALID_PARAMETER;
+}
+RT_EXPORT_SYMBOL(RTNetMaskToPrefixIPv4);
+
+
+RTDECL(int) RTNetPrefixToMaskIPv4(int iPrefix, PRTNETADDRIPV4 pMask)
+{
+ AssertReturn(pMask != NULL, VERR_INVALID_PARAMETER);
+
+ if (RT_UNLIKELY(iPrefix < 0 || 32 < iPrefix))
+ return VERR_INVALID_PARAMETER;
+
+ if (RT_LIKELY(iPrefix != 0))
+ pMask->u = RT_H2N_U32(UINT32_C(0xffffffff) << (32 - iPrefix));
+ else /* avoid UB in the shift */
+ pMask->u = 0;
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTNetPrefixToMaskIPv4);
+
+
+RTDECL(int) RTNetStrToIPv4Cidr(const char *pcszAddr, PRTNETADDRIPV4 pAddr, int *piPrefix)
+{
+ RTNETADDRIPV4 Addr, Mask;
+ uint8_t u8Prefix;
+ char *pszNext;
+ int rc;
+
+ AssertPtrReturn(pcszAddr, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pAddr, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(piPrefix, VERR_INVALID_PARAMETER);
+
+ pcszAddr = RTStrStripL(pcszAddr);
+ rc = rtNetStrToIPv4AddrEx(pcszAddr, &Addr, &pszNext);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * If the prefix is missing, treat is as exact (/32) address
+ * specification.
+ */
+ if (*pszNext == '\0' || rc == VWRN_TRAILING_SPACES)
+ {
+ *pAddr = Addr;
+ *piPrefix = 32;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Be flexible about the way the prefix is specified after the
+ * slash: accept both the prefix length and the netmask, and for
+ * the latter accept both dotted-decimal and hex. The inputs we
+ * convert here are likely coming from a user and people have
+ * different preferences. Sometimes they just remember specific
+ * different networks in specific formats!
+ */
+ if (*pszNext == '/')
+ ++pszNext;
+ else
+ return VERR_INVALID_PARAMETER;
+
+ /* .../0x... is a hex mask */
+ if (pszNext[0] == '0' && (pszNext[1] == 'x' || pszNext[1] == 'X'))
+ {
+ rc = RTStrToUInt32Ex(pszNext, &pszNext, 16, &Mask.u);
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_SPACES)
+ Mask.u = RT_H2N_U32(Mask.u);
+ else
+ return VERR_INVALID_PARAMETER;
+
+ int iPrefix;
+ rc = RTNetMaskToPrefixIPv4(&Mask, &iPrefix);
+ if (RT_SUCCESS(rc))
+ u8Prefix = (uint8_t)iPrefix;
+ else
+ return VERR_INVALID_PARAMETER;
+ }
+ else
+ {
+ char *pszLookAhead;
+ uint32_t u32;
+ rc = RTStrToUInt32Ex(pszNext, &pszLookAhead, 10, &u32);
+
+ /* single number after the slash is prefix length */
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_SPACES)
+ {
+ if (u32 <= 32)
+ u8Prefix = (uint8_t)u32;
+ else
+ return VERR_INVALID_PARAMETER;
+ }
+ /* a number followed by more stuff, may be a dotted-decimal */
+ else if (rc == VWRN_TRAILING_CHARS)
+ {
+ if (*pszLookAhead != '.') /* don't even bother checking */
+ return VERR_INVALID_PARAMETER;
+
+ rc = rtNetStrToIPv4AddrEx(pszNext, &Mask, &pszNext);
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_SPACES)
+ {
+ int iPrefix;
+ rc = RTNetMaskToPrefixIPv4(&Mask, &iPrefix);
+ if (RT_SUCCESS(rc))
+ u8Prefix = (uint8_t)iPrefix;
+ else
+ return VERR_INVALID_PARAMETER;
+ }
+ else
+ return VERR_INVALID_PARAMETER;
+ }
+ /* failed to convert to number */
+ else
+ return VERR_INVALID_PARAMETER;
+ }
+
+ if (u8Prefix > 32)
+ return VERR_INVALID_PARAMETER;
+
+ *pAddr = Addr;
+ *piPrefix = u8Prefix;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTNetStrToIPv4Cidr);
+
+
+static int rtNetStrToHexGroup(const char *pcszValue, char **ppszNext,
+ uint16_t *pu16)
+{
+ char *pszNext;
+ int rc;
+
+ rc = RTStrToUInt16Ex(pcszValue, &pszNext, 16, pu16);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if ( rc != VINF_SUCCESS
+ && rc != VWRN_TRAILING_CHARS
+ && rc != VWRN_TRAILING_SPACES)
+ {
+ return -rc; /* convert warning to error */
+ }
+
+ /* parser always accepts 0x prefix */
+ if (pcszValue[0] == '0' && (pcszValue[1] == 'x' || pcszValue[1] == 'X'))
+ {
+ if (pu16)
+ *pu16 = 0;
+ if (ppszNext)
+ *ppszNext = (/* UNCONST */ char *)pcszValue + 1; /* to 'x' */
+ return VWRN_TRAILING_CHARS;
+ }
+
+ /* parser accepts leading zeroes "000000f" */
+ if (pszNext - pcszValue > 4)
+ return VERR_PARSE_ERROR;
+
+ if (ppszNext)
+ *ppszNext = pszNext;
+ return rc;
+}
+
+
+/*
+ * This function deals only with the hex-group IPv6 address syntax
+ * proper (with possible embedded IPv4).
+ */
+DECLHIDDEN(int) rtNetStrToIPv6AddrBase(const char *pcszAddr, PRTNETADDRIPV6 pAddrResult,
+ char **ppszNext)
+{
+ RTNETADDRIPV6 ipv6;
+ RTNETADDRIPV4 ipv4;
+ const char *pcszPos;
+ char *pszNext;
+ int iGroup;
+ uint16_t u16;
+ int rc;
+
+ RT_ZERO(ipv6);
+
+ pcszPos = pcszAddr;
+
+ if (pcszPos[0] == ':') /* compressed zero run at the beginning? */
+ {
+ if (pcszPos[1] != ':')
+ return VERR_PARSE_ERROR;
+
+ pcszPos += 2; /* skip over "::" */
+ pszNext = (/* UNCONST */ char *)pcszPos;
+ iGroup = 1;
+ }
+ else
+ {
+ /*
+ * Scan forward until we either get complete address or find
+ * "::" compressed zero run.
+ */
+ pszNext = NULL; /* (MSC incorrectly thinks it may be used unitialized) */
+ for (iGroup = 0; iGroup < 8; ++iGroup)
+ {
+ /* check for embedded IPv4 at the end */
+ if (iGroup == 6)
+ {
+ rc = rtNetStrToIPv4AddrEx(pcszPos, &ipv4, &pszNext);
+ if (rc == VINF_SUCCESS)
+ {
+ ipv6.au32[3] = ipv4.au32[0];
+ iGroup = 8; /* filled 6 and 7 */
+ break; /* we are done */
+ }
+ }
+
+ rc = rtNetStrToHexGroup(pcszPos, &pszNext, &u16);
+ if (RT_FAILURE(rc))
+ return VERR_PARSE_ERROR;
+
+ ipv6.au16[iGroup] = RT_H2N_U16(u16);
+
+ if (iGroup == 7)
+ pcszPos = pszNext;
+ else
+ {
+ /* skip the colon that delimits this group */
+ if (*pszNext != ':')
+ return VERR_PARSE_ERROR;
+ pcszPos = pszNext + 1;
+
+ /* compressed zero run? */
+ if (*pcszPos == ':')
+ {
+ ++pcszPos; /* skip over :: */
+ pszNext += 2; /* skip over :: (in case we are done) */
+ iGroup += 2; /* current field and the zero in the next */
+ break;
+ }
+ }
+ }
+ }
+
+ if (iGroup != 8)
+ {
+ /*
+ * iGroup is the first group that can be filled by the part of
+ * the address after "::".
+ */
+ RTNETADDRIPV6 ipv6Tail;
+ const int iMaybeStart = iGroup;
+ int j;
+
+ RT_ZERO(ipv6Tail);
+
+ /*
+ * We try to accept longest match; we'll shift if necessary.
+ * Unlike the first loop, a failure to parse a group doesn't
+ * mean invalid address.
+ */
+ for (; iGroup < 8; ++iGroup)
+ {
+ /* check for embedded IPv4 at the end */
+ if (iGroup <= 6)
+ {
+ rc = rtNetStrToIPv4AddrEx(pcszPos, &ipv4, &pszNext);
+ if (rc == VINF_SUCCESS)
+ {
+ ipv6Tail.au16[iGroup] = ipv4.au16[0];
+ ipv6Tail.au16[iGroup + 1] = ipv4.au16[1];
+ iGroup = iGroup + 2; /* these two are done */
+ break; /* the rest is trailer */
+ }
+ }
+
+ rc = rtNetStrToHexGroup(pcszPos, &pszNext, &u16);
+ if (RT_FAILURE(rc))
+ break;
+
+ ipv6Tail.au16[iGroup] = RT_H2N_U16(u16);
+
+ if (iGroup == 7)
+ pcszPos = pszNext;
+ else
+ {
+ if (*pszNext != ':')
+ {
+ ++iGroup; /* this one is done */
+ break; /* the rest is trailer */
+ }
+
+ pcszPos = pszNext + 1;
+ }
+ }
+
+ for (j = 7, --iGroup; iGroup >= iMaybeStart; --j, --iGroup)
+ ipv6.au16[j] = ipv6Tail.au16[iGroup];
+ }
+
+ if (pAddrResult != NULL)
+ memcpy(pAddrResult, &ipv6, sizeof(ipv6));
+ if (ppszNext != NULL)
+ *ppszNext = pszNext;
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(int) rtNetStrToIPv6AddrEx(const char *pcszAddr, PRTNETADDRIPV6 pAddr,
+ char **ppszZone, char **ppszNext)
+{
+ char *pszNext, *pszZone;
+ int rc;
+
+ rc = rtNetStrToIPv6AddrBase(pcszAddr, pAddr, &pszNext);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (*pszNext != '%') /* is there a zone id? */
+ {
+ pszZone = NULL;
+ }
+ else
+ {
+ pszZone = pszNext + 1; /* skip '%' zone id delimiter */
+ if (*pszZone == '\0')
+ return VERR_PARSE_ERROR; /* empty zone id */
+
+ /*
+ * XXX: this is speculative as zone id syntax is
+ * implementation dependent, so we kinda guess here (accepting
+ * unreserved characters from URI syntax).
+ */
+ for (pszNext = pszZone; *pszNext != '\0'; ++pszNext)
+ {
+ const char c = *pszNext;
+ if ( !('0' <= c && c <= '9')
+ && !('a' <= c && c <= 'z')
+ && !('A' <= c && c <= 'Z')
+ && c != '_'
+ && c != '.'
+ && c != '-'
+ && c != '~')
+ {
+ break;
+ }
+ }
+ }
+
+ if (ppszZone != NULL)
+ *ppszZone = pszZone;
+ if (ppszNext != NULL)
+ *ppszNext = pszNext;
+
+ if (*pszNext == '\0') /* all input string consumed */
+ return VINF_SUCCESS;
+ else
+ {
+ while (*pszNext == ' ' || *pszNext == '\t')
+ ++pszNext;
+ if (*pszNext == '\0')
+ return VWRN_TRAILING_SPACES;
+ else
+ return VWRN_TRAILING_CHARS;
+ }
+}
+
+
+RTDECL(int) RTNetStrToIPv6AddrEx(const char *pcszAddr, PRTNETADDRIPV6 pAddr,
+ char **ppszNext)
+{
+ AssertPtrReturn(pcszAddr, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pAddr, VERR_INVALID_PARAMETER);
+
+ return rtNetStrToIPv6AddrBase(pcszAddr, pAddr, ppszNext);
+}
+RT_EXPORT_SYMBOL(RTNetStrToIPv6AddrEx);
+
+
+RTDECL(int) RTNetStrToIPv6Addr(const char *pcszAddr, PRTNETADDRIPV6 pAddr,
+ char **ppszZone)
+{
+ int rc;
+
+ AssertPtrReturn(pcszAddr, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pAddr, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(ppszZone, VERR_INVALID_PARAMETER);
+
+ pcszAddr = RTStrStripL(pcszAddr);
+ rc = rtNetStrToIPv6AddrEx(pcszAddr, pAddr, ppszZone, NULL);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES)
+ return VERR_INVALID_PARAMETER;
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTNetStrToIPv6Addr);
+
+
+RTDECL(bool) RTNetIsIPv6AddrStr(const char *pcszAddr)
+{
+ RTNETADDRIPV6 addrIPv6;
+ int rc;
+
+ if (pcszAddr == NULL)
+ return false;
+
+ rc = rtNetStrToIPv6AddrEx(pcszAddr, &addrIPv6, NULL, NULL);
+ if (rc != VINF_SUCCESS)
+ return false;
+
+ return true;
+}
+RT_EXPORT_SYMBOL(RTNetIsIPv6AddrStr);
+
+
+RTDECL(bool) RTNetStrIsIPv6AddrAny(const char *pcszAddr)
+{
+ RTNETADDRIPV6 addrIPv6;
+ char *pszZone, *pszNext;
+ int rc;
+
+ if (pcszAddr == NULL)
+ return false;
+
+ pcszAddr = RTStrStripL(pcszAddr);
+ rc = rtNetStrToIPv6AddrEx(pcszAddr, &addrIPv6, &pszZone, &pszNext);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES)
+ return false;
+
+ if (pszZone != NULL)
+ return false;
+
+ if (addrIPv6.s.Lo != 0 || addrIPv6.s.Hi != 0) /* in6addr_any? */
+ return false;
+
+ return true;
+}
+RT_EXPORT_SYMBOL(RTNetStrIsIPv6AddrAny);
+
+
+RTDECL(int) RTNetMaskToPrefixIPv6(PCRTNETADDRIPV6 pMask, int *piPrefix)
+{
+ AssertReturn(pMask != NULL, VERR_INVALID_PARAMETER);
+
+ int iPrefix = 0;
+ unsigned int i;
+
+ for (i = 0; i < RT_ELEMENTS(pMask->au8); ++i)
+ {
+ int iBits;
+ switch (pMask->au8[i])
+ {
+ case 0x00: iBits = 0; break;
+ case 0x80: iBits = 1; break;
+ case 0xc0: iBits = 2; break;
+ case 0xe0: iBits = 3; break;
+ case 0xf0: iBits = 4; break;
+ case 0xf8: iBits = 5; break;
+ case 0xfc: iBits = 6; break;
+ case 0xfe: iBits = 7; break;
+ case 0xff: iBits = 8; break;
+ default: /* non-contiguous mask */
+ return VERR_INVALID_PARAMETER;
+ }
+
+ iPrefix += iBits;
+ if (iBits != 8)
+ break;
+ }
+
+ for (++i; i < RT_ELEMENTS(pMask->au8); ++i)
+ if (pMask->au8[i] != 0)
+ return VERR_INVALID_PARAMETER;
+
+ if (piPrefix != NULL)
+ *piPrefix = iPrefix;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTNetMaskToPrefixIPv6);
+
+
+RTDECL(int) RTNetPrefixToMaskIPv6(int iPrefix, PRTNETADDRIPV6 pMask)
+{
+ AssertReturn(pMask != NULL, VERR_INVALID_PARAMETER);
+
+ if (RT_UNLIKELY(iPrefix < 0 || 128 < iPrefix))
+ return VERR_INVALID_PARAMETER;
+
+ for (unsigned int i = 0; i < RT_ELEMENTS(pMask->au32); ++i)
+ {
+ if (iPrefix == 0)
+ {
+ pMask->au32[i] = 0;
+ }
+ else if (iPrefix >= 32)
+ {
+ pMask->au32[i] = UINT32_C(0xffffffff);
+ iPrefix -= 32;
+ }
+ else
+ {
+ pMask->au32[i] = RT_H2N_U32(UINT32_C(0xffffffff) << (32 - iPrefix));
+ iPrefix = 0;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTNetPrefixToMaskIPv6);
+
+
+RTDECL(int) RTNetStrToIPv6Cidr(const char *pcszAddr, PRTNETADDRIPV6 pAddr, int *piPrefix)
+{
+ RTNETADDRIPV6 Addr;
+ uint8_t u8Prefix;
+ char *pszZone, *pszNext;
+ int rc;
+
+ AssertPtrReturn(pcszAddr, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pAddr, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(piPrefix, VERR_INVALID_PARAMETER);
+
+ pcszAddr = RTStrStripL(pcszAddr);
+ rc = rtNetStrToIPv6AddrEx(pcszAddr, &Addr, &pszZone, &pszNext);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ RT_NOREF(pszZone);
+
+ /*
+ * If the prefix is missing, treat is as exact (/128) address
+ * specification.
+ */
+ if (*pszNext == '\0' || rc == VWRN_TRAILING_SPACES)
+ {
+ *pAddr = Addr;
+ *piPrefix = 128;
+ return VINF_SUCCESS;
+ }
+
+ if (*pszNext != '/')
+ return VERR_INVALID_PARAMETER;
+
+ ++pszNext;
+ rc = RTStrToUInt8Ex(pszNext, &pszNext, 10, &u8Prefix);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES)
+ return VERR_INVALID_PARAMETER;
+
+ if (u8Prefix > 128)
+ return VERR_INVALID_PARAMETER;
+
+ *pAddr = Addr;
+ *piPrefix = u8Prefix;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTNetStrToIPv6Cidr);
diff --git a/src/VBox/Runtime/common/path/Makefile.kup b/src/VBox/Runtime/common/path/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/path/Makefile.kup
diff --git a/src/VBox/Runtime/common/path/RTPathAbsDup.cpp b/src/VBox/Runtime/common/path/RTPathAbsDup.cpp
new file mode 100644
index 00000000..80a79733
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAbsDup.cpp
@@ -0,0 +1,49 @@
+/* $Id: RTPathAbsDup.cpp $ */
+/** @file
+ * IPRT - RTPathAbsDup
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+RTDECL(char *) RTPathAbsDup(const char *pszPath)
+{
+ return RTPathAbsExDup(NULL, pszPath, RTPATH_STR_F_STYLE_HOST);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAbsEx.cpp b/src/VBox/Runtime/common/path/RTPathAbsEx.cpp
new file mode 100644
index 00000000..a93e244f
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAbsEx.cpp
@@ -0,0 +1,699 @@
+/* $Id: RTPathAbsEx.cpp $ */
+/** @file
+ * IPRT - RTPathAbsEx and RTPathAbs.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_PATH
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include "internal/path.h"
+
+
+
+/**
+ * Ensures that the drive letter is capitalized (prereq: RTPATH_PROP_VOLUME).
+ */
+DECLINLINE(void) rtPathAbsExUpperCaseDriveLetter(char *pszAbsPath)
+{
+ AssertReturnVoid(pszAbsPath[1] == ':');
+ char ch = *pszAbsPath;
+ AssertReturnVoid(RT_C_IS_ALPHA(ch));
+ *pszAbsPath = RT_C_TO_UPPER(ch);
+}
+
+
+/**
+ * Common worker for relative paths.
+ *
+ * Uses RTPATHABS_F_STOP_AT_BASE for RTPATHABS_F_STOP_AT_CWD.
+ */
+static int rtPathAbsExWithCwdOrBaseCommon(const char *pszBase, size_t cchBaseInPlace, PRTPATHPARSED pBaseParsed,
+ const char *pszPath, PRTPATHPARSED pParsed, uint32_t fFlags,
+ char *pszAbsPath, size_t *pcbAbsPath)
+{
+ AssertReturn(pBaseParsed->cComps > 0, VERR_INVALID_PARAMETER);
+
+ /*
+ * Clean up the base path first if necessary.
+ *
+ * Note! UNC tries to preserve the first two elements in the base path,
+ * unless it's a \\.\ or \\?\ prefix.
+ */
+ uint32_t const iBaseStop = (pBaseParsed->fProps & (RTPATH_PROP_UNC | RTPATH_PROP_SPECIAL_UNC)) != RTPATH_PROP_UNC
+ || pBaseParsed->cComps < 2 ? 0 : 1;
+ uint32_t iBaseLast = iBaseStop;
+ if (pBaseParsed->fProps & (RTPATH_PROP_DOT_REFS | RTPATH_PROP_DOTDOT_REFS))
+ {
+ uint32_t const cComps = pBaseParsed->cComps;
+ uint32_t i = iBaseStop + 1;
+ while (i < cComps)
+ {
+ uint32_t const cchComp = pBaseParsed->aComps[i].cch;
+ if ( cchComp > 2
+ || pszPath[pBaseParsed->aComps[i].off] != '.'
+ || (cchComp == 2 && pszPath[pBaseParsed->aComps[i].off + 1] != '.') )
+ iBaseLast = i;
+ else
+ {
+ Assert(cchComp == 1 || cchComp == 2);
+ pBaseParsed->aComps[i].cch = 0;
+ if (cchComp == 2)
+ {
+ while (iBaseLast > 0 && pBaseParsed->aComps[iBaseLast].cch == 0)
+ iBaseLast--;
+ if (iBaseLast > iBaseStop)
+ {
+ Assert(pBaseParsed->aComps[iBaseLast].cch != 0);
+ pBaseParsed->aComps[iBaseLast].cch = 0;
+ iBaseLast--;
+ }
+ }
+ }
+ i++;
+ }
+ Assert(iBaseLast < cComps);
+ }
+ else
+ iBaseLast = pBaseParsed->cComps - 1;
+
+ /*
+ * Clean up the path next if needed.
+ */
+ int32_t iLast = -1; /* Is signed here! */
+ if (pParsed->fProps & (RTPATH_PROP_DOT_REFS | RTPATH_PROP_DOTDOT_REFS))
+ {
+ uint32_t const cComps = pParsed->cComps;
+ uint32_t i = 0;
+
+ /* If we have a volume specifier, take it from the base path. */
+ if (pParsed->fProps & RTPATH_PROP_VOLUME)
+ pParsed->aComps[i++].cch = 0;
+
+ while (i < cComps)
+ {
+ uint32_t const cchComp = pParsed->aComps[i].cch;
+ if ( cchComp > 2
+ || pszPath[pParsed->aComps[i].off] != '.'
+ || (cchComp == 2 && pszPath[pParsed->aComps[i].off + 1] != '.') )
+ iLast = i;
+ else
+ {
+ Assert(cchComp == 1 || cchComp == 2);
+ pParsed->aComps[i].cch = 0;
+ if (cchComp == 2)
+ {
+ while (iLast >= 0 && pParsed->aComps[iLast].cch == 0)
+ iLast--;
+ if (iLast >= 0)
+ {
+ Assert(pParsed->aComps[iLast].cch != 0);
+ pParsed->aComps[iLast].cch = 0;
+ iLast--;
+ }
+ else if ( iBaseLast > iBaseStop
+ && !(fFlags & RTPATHABS_F_STOP_AT_BASE))
+ {
+ while (iBaseLast > iBaseStop && pBaseParsed->aComps[iBaseLast].cch == 0)
+ iBaseLast--;
+ if (iBaseLast > iBaseStop)
+ {
+ Assert(pBaseParsed->aComps[iBaseLast].cch != 0);
+ pBaseParsed->aComps[iBaseLast].cch = 0;
+ iBaseLast--;
+ }
+ }
+ }
+ }
+ i++;
+ }
+ Assert(iLast < (int32_t)cComps);
+ }
+ else
+ {
+ /* If we have a volume specifier, take it from the base path. */
+ iLast = pParsed->cComps - 1;
+ if (pParsed->fProps & RTPATH_PROP_VOLUME)
+ {
+ pParsed->aComps[0].cch = 0;
+ if (iLast == 0)
+ iLast = -1;
+ }
+ }
+
+ /*
+ * Do we need a trailing slash in the base?
+ * If nothing is taken from pszPath, preserve its trailing slash,
+ * otherwise make sure there is a slash for joining the two.
+ */
+ Assert(!(pParsed->fProps & RTPATH_PROP_ROOT_SLASH));
+ if (pBaseParsed->cComps == 1)
+ {
+ AssertReturn(pBaseParsed->fProps & RTPATH_PROP_ROOT_SLASH, VERR_PATH_DOES_NOT_START_WITH_ROOT);
+ Assert(!(pBaseParsed->fProps & RTPATH_PROP_DIR_SLASH));
+ }
+ else
+ {
+ Assert(pBaseParsed->cComps > 1);
+ if ( iLast >= 0
+ || (pParsed->fProps & RTPATH_PROP_DIR_SLASH)
+ || (fFlags & RTPATHABS_F_ENSURE_TRAILING_SLASH) )
+ pBaseParsed->fProps |= RTPATH_PROP_DIR_SLASH;
+ else
+ pBaseParsed->fProps &= ~RTPATH_PROP_DIR_SLASH;
+ }
+
+ /* Apply the trailing flash flag to the input path: */
+ if ( iLast >= 0
+ && (fFlags & RTPATHABS_F_ENSURE_TRAILING_SLASH))
+ pParsed->fProps |= RTPATH_PROP_DIR_SLASH;
+
+ /*
+ * Combine the two. RTPathParsedReassemble can handle in place stuff, as
+ * long as the path doesn't grow.
+ */
+ int rc = RTPathParsedReassemble(pszBase, pBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, *pcbAbsPath);
+ if (RT_SUCCESS(rc))
+ {
+ if (pBaseParsed->fProps & RTPATH_PROP_VOLUME)
+ rtPathAbsExUpperCaseDriveLetter(pszAbsPath);
+
+ cchBaseInPlace = pBaseParsed->cchPath;
+ Assert(cchBaseInPlace == strlen(pszAbsPath));
+ if (iLast >= 0)
+ {
+ rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK,
+ &pszAbsPath[cchBaseInPlace], *pcbAbsPath - cchBaseInPlace);
+ if (RT_SUCCESS(rc))
+ {
+ *pcbAbsPath = cchBaseInPlace + pParsed->cchPath;
+ Assert(*pcbAbsPath == strlen(pszAbsPath));
+ }
+ else
+ *pcbAbsPath = cchBaseInPlace + pParsed->cchPath + 1;
+ }
+ else
+ *pcbAbsPath = cchBaseInPlace;
+ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ if (iLast >= 0)
+ {
+ RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, 0);
+ *pcbAbsPath = pBaseParsed->cchPath + pParsed->cchPath + 1;
+ }
+ else
+ *pcbAbsPath = pBaseParsed->cchPath + 1;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Handles the no-root-path scenario where we do CWD prefixing.
+ */
+static int rtPathAbsExWithCwd(const char *pszPath, PRTPATHPARSED pParsed, uint32_t fFlags, char *pszAbsPath, size_t *pcbAbsPath)
+{
+ /*
+ * Get the current directory and place it in the output buffer.
+ */
+ size_t cchInPlace;
+ size_t cbCwd = *pcbAbsPath;
+ char *pszCwdFree = NULL;
+ char *pszCwd = pszAbsPath;
+ int rc;
+ if ( !(fFlags & RTPATH_STR_F_STYLE_DOS)
+ || (pParsed->fProps & (RTPATH_PROP_VOLUME | RTPATH_PROP_ROOT_SLASH)) != RTPATH_PROP_VOLUME )
+ rc = RTPathGetCurrent(pszCwd, cbCwd);
+ else
+ rc = RTPathGetCurrentOnDrive(pszPath[0], pszCwd, cbCwd);
+ if (RT_SUCCESS(rc))
+ cchInPlace = strlen(pszCwd);
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ /* Allocate a big temporary buffer so we can return the correct length
+ (the destination buffer might even be big enough if pszPath includes
+ sufficient '..' entries). */
+ cchInPlace = 0;
+ cbCwd = RT_MAX(cbCwd * 4, RTPATH_BIG_MAX);
+ pszCwdFree = pszCwd = (char *)RTMemTmpAlloc(cbCwd);
+ if (pszCwdFree)
+ {
+ if ( !(fFlags & RTPATH_STR_F_STYLE_DOS)
+ || (pParsed->fProps & (RTPATH_PROP_VOLUME | RTPATH_PROP_ROOT_SLASH)) != RTPATH_PROP_VOLUME )
+ rc = RTPathGetCurrent(pszCwd, cbCwd);
+ else
+ rc = RTPathGetCurrentOnDrive(pszPath[0], pszCwd, cbCwd);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ rc = VERR_FILENAME_TOO_LONG;
+ RTMemTmpFree(pszCwdFree);
+ return rc;
+ }
+ }
+ else
+ {
+ *pcbAbsPath = cbCwd + 1 + pParsed->cchPath + 1;
+ return rc;
+ }
+ }
+ else
+ return rc;
+
+ /*
+ * Parse the path.
+ */
+ union
+ {
+ RTPATHPARSED Parsed;
+ uint8_t abPadding[1024];
+ } uCwd;
+ PRTPATHPARSED pCwdParsedFree = NULL;
+ PRTPATHPARSED pCwdParsed = &uCwd.Parsed;
+ size_t cbCwdParsed = sizeof(uCwd);
+ rc = RTPathParse(pszCwd, pCwdParsed, cbCwdParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbCwdParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pCwdParsed->cComps + 2]);
+ pCwdParsedFree = pCwdParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbCwdParsed);
+ AssertReturnStmt(pCwdParsed, RTMemTmpFree(pszCwdFree), VERR_NO_TMP_MEMORY);
+ rc = RTPathParse(pszCwd, pCwdParsed, cbCwdParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pCwdParsedFree); RTMemTmpFree(pszCwdFree), rc);
+ }
+ else
+ AssertMsgFailedReturn(("rc=%Rrc '%s'\n", rc, pszPath), rc);
+
+ /*
+ * Join paths with the base-path code.
+ */
+ if (fFlags & RTPATHABS_F_STOP_AT_CWD)
+ fFlags |= RTPATHABS_F_STOP_AT_BASE;
+ else
+ fFlags &= ~RTPATHABS_F_STOP_AT_BASE;
+ rc = rtPathAbsExWithCwdOrBaseCommon(pszCwd, cchInPlace, pCwdParsed, pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ if (pCwdParsedFree)
+ RTMemTmpFree(pCwdParsedFree);
+ if (pszCwdFree)
+ RTMemTmpFree(pszCwdFree);
+ return rc;
+}
+
+
+/**
+ * Handles the no-root-path scenario where we've got a base path.
+ */
+static int rtPathAbsExWithBase(const char *pszBase, const char *pszPath, PRTPATHPARSED pParsed, uint32_t fFlags,
+ char *pszAbsPath, size_t *pcbAbsPath)
+{
+ /*
+ * Parse the base path.
+ */
+ union
+ {
+ RTPATHPARSED Parsed;
+ uint8_t abPadding[1024];
+ } uBase;
+ PRTPATHPARSED pBaseParsedFree = NULL;
+ PRTPATHPARSED pBaseParsed = &uBase.Parsed;
+ size_t cbBaseParsed = sizeof(uBase);
+ int rc = RTPathParse(pszBase, pBaseParsed, cbBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbBaseParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pBaseParsed->cComps + 2]);
+ pBaseParsedFree = pBaseParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbBaseParsed);
+ AssertReturn(pBaseParsed, VERR_NO_TMP_MEMORY);
+ rc = RTPathParse(pszBase, pBaseParsed, cbBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pBaseParsedFree), rc);
+ }
+ else
+ AssertMsgFailedReturn(("rc=%Rrc '%s'\n", rc, pszPath), rc);
+
+ /*
+ * If the base path isn't absolute, we need to deal with that.
+ */
+ size_t cchInPlace = 0;
+ if ((pBaseParsed->fProps & (RTPATH_PROP_ABSOLUTE | RTPATH_PROP_EXTRA_SLASHES | RTPATH_PROP_DOT_REFS)) == RTPATH_PROP_ABSOLUTE)
+ { /* likely */ }
+ else
+ {
+ cchInPlace = *pcbAbsPath;
+ rc = RTPathAbsEx(NULL, pszBase, fFlags, pszAbsPath, &cchInPlace);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(strlen(pszAbsPath) == cchInPlace);
+ Assert(cchInPlace > 0);
+ }
+ else
+ {
+/** @todo Allocate temp buffer like we do for CWD? */
+ /* This is over generious, but don't want to put too much effort into it yet. */
+ if (rc == VERR_BUFFER_OVERFLOW)
+ *pcbAbsPath = cchInPlace + 1 + pParsed->cchPath + 1;
+ return rc;
+ }
+
+ /*
+ * Reparse it.
+ */
+ rc = RTPathParse(pszAbsPath, pBaseParsed, cbBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ if (pBaseParsedFree)
+ RTMemTmpFree(pBaseParsedFree);
+ cbBaseParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pBaseParsed->cComps + 2]);
+ pBaseParsedFree = pBaseParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbBaseParsed);
+ AssertReturn(pBaseParsed, VERR_NO_TMP_MEMORY);
+ rc = RTPathParse(pszAbsPath, pBaseParsed, cbBaseParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pBaseParsedFree), rc);
+ }
+ else
+ AssertMsgFailedReturn(("rc=%Rrc '%s'\n", rc, pszPath), rc);
+ }
+
+ /*
+ * Join paths with the CWD code.
+ */
+ rc = rtPathAbsExWithCwdOrBaseCommon(cchInPlace ? pszAbsPath : pszBase, cchInPlace, pBaseParsed,
+ pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ if (pBaseParsedFree)
+ RTMemTmpFree(pBaseParsedFree);
+ return rc;
+}
+
+
+/**
+ * Handles the RTPATH_PROP_ROOT_SLASH case.
+ */
+static int rtPathAbsExRootSlash(const char *pszBase, const char *pszPath, PRTPATHPARSED pParsed,
+ uint32_t fFlags, char *pszAbsPath, size_t *pcbAbsPath)
+{
+ /*
+ * Eliminate dot and dot-dot components.
+ * Note! aComp[0] is the root stuff and must never be dropped.
+ */
+ uint32_t const cComps = pParsed->cComps;
+ uint32_t const iStop = (pParsed->fProps & (RTPATH_PROP_UNC | RTPATH_PROP_SPECIAL_UNC)) != RTPATH_PROP_UNC
+ || pParsed->cComps < 2 ? 0 : 1;
+ uint32_t iLast = iStop;
+ uint32_t i = iStop + 1;
+ while (i < cComps)
+ {
+ uint32_t const cchComp = pParsed->aComps[i].cch;
+ if ( cchComp > 2
+ || pszPath[pParsed->aComps[i].off] != '.'
+ || (cchComp == 2 && pszPath[pParsed->aComps[i].off + 1] != '.') )
+ iLast = i;
+ else
+ {
+ Assert(cchComp == 1 || cchComp == 2);
+ pParsed->aComps[i].cch = 0;
+ if (cchComp == 2)
+ {
+ while (iLast > iStop && pParsed->aComps[iLast].cch == 0)
+ iLast--;
+ if (iLast > iStop)
+ {
+ Assert(pParsed->aComps[iLast].cch > 0);
+ pParsed->aComps[iLast].cch = 0;
+ iLast--;
+ }
+ }
+ }
+ i++;
+ }
+
+ /*
+ * Before we continue, ensure trailing slash if requested.
+ */
+ if ( (fFlags & RTPATHABS_F_ENSURE_TRAILING_SLASH)
+ && iLast > 0)
+ pParsed->fProps |= RTPATH_PROP_DIR_SLASH;
+
+ /*
+ * DOS-style: Do we need to supply a drive letter or UNC root?
+ */
+ size_t cchRootPrefix = 0;
+ if ( (fFlags & RTPATH_STR_F_STYLE_DOS)
+ && !(pParsed->fProps & (RTPATH_PROP_VOLUME | RTPATH_PROP_UNC)) )
+ {
+ /* Use the drive/UNC from the base path if we have one and it has such a component: */
+ if (pszBase)
+ {
+ union
+ {
+ RTPATHPARSED Parsed;
+ uint8_t abPadding[sizeof(RTPATHPARSED) + sizeof(pParsed->aComps[0]) * 2];
+ } uBase;
+ int rc = RTPathParse(pszBase, &uBase.Parsed, sizeof(uBase), fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertMsgReturn(RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW, ("%Rrc - '%s'\n", rc, pszBase), rc);
+
+ if (uBase.Parsed.fProps & RTPATH_PROP_VOLUME)
+ {
+ /* get the drive letter. */
+ Assert(uBase.Parsed.aComps[0].cch == 2 || uBase.Parsed.aComps[0].cch == 3);
+ cchRootPrefix = RT_MIN(uBase.Parsed.aComps[0].cch, 2);
+ if (cchRootPrefix < *pcbAbsPath)
+ memcpy(pszAbsPath, &pszBase[uBase.Parsed.aComps[0].off], cchRootPrefix);
+ else
+ {
+ rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, 0);
+ Assert(rc == VERR_BUFFER_OVERFLOW);
+
+ *pcbAbsPath = cchRootPrefix + pParsed->cchPath + 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ rtPathAbsExUpperCaseDriveLetter(pszAbsPath);
+ }
+ else if (uBase.Parsed.fProps & RTPATH_PROP_UNC)
+ {
+ /* Include the share if we've got one. */
+ cchRootPrefix = uBase.Parsed.aComps[0].cch;
+ if (uBase.Parsed.cComps >= 2 && !(uBase.Parsed.fProps & RTPATH_PROP_SPECIAL_UNC))
+ cchRootPrefix += uBase.Parsed.aComps[1].cch;
+ else if (uBase.Parsed.fProps & RTPATH_PROP_ROOT_SLASH)
+ cchRootPrefix--;
+ if (cchRootPrefix < *pcbAbsPath)
+ {
+ if (uBase.Parsed.cComps < 2 || (uBase.Parsed.fProps & RTPATH_PROP_SPECIAL_UNC))
+ memcpy(pszAbsPath, &pszBase[uBase.Parsed.aComps[0].off], cchRootPrefix);
+ else
+ {
+ size_t cchFirst = uBase.Parsed.aComps[0].cch;
+ memcpy(pszAbsPath, &pszBase[uBase.Parsed.aComps[0].off], cchFirst);
+ memcpy(&pszAbsPath[cchFirst], &pszBase[uBase.Parsed.aComps[1].off], uBase.Parsed.aComps[1].cch);
+ }
+ }
+ else
+ {
+ rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, 0);
+ Assert(rc == VERR_BUFFER_OVERFLOW);
+
+ *pcbAbsPath = cchRootPrefix + pParsed->cchPath + 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ pszBase = NULL;
+ }
+
+ /* Otherwise, query the current drive: */
+ if (!pszBase)
+ {
+ int rc = RTPathGetCurrentDrive(pszAbsPath, *pcbAbsPath);
+ if (RT_SUCCESS(rc))
+ cchRootPrefix = strlen(pszAbsPath);
+ else
+ {
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ int rc2 = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, 0);
+ Assert(rc2 == VERR_BUFFER_OVERFLOW);
+
+ char *pszTmp = (char *)RTMemTmpAlloc(RTPATH_BIG_MAX);
+ if (pszTmp)
+ {
+ rc2 = RTPathGetCurrentDrive(pszTmp, RTPATH_BIG_MAX);
+ if (RT_SUCCESS(rc2))
+ *pcbAbsPath = strlen(pszTmp) + pParsed->cchPath + 1;
+ else
+ *pcbAbsPath = RT_MAX(*pcbAbsPath * 2, (size_t)RTPATH_BIG_MAX * 3 + pParsed->cchPath + 1);
+ RTMemTmpFree(pszTmp);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ return rc;
+ }
+ }
+ }
+
+ /*
+ * Reassemble the path and return.
+ */
+ int rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK,
+ pszAbsPath + cchRootPrefix, *pcbAbsPath - cchRootPrefix);
+ *pcbAbsPath = cchRootPrefix + pParsed->cchPath + (rc == VERR_BUFFER_OVERFLOW);
+ return rc;
+}
+
+
+/**
+ * Handles the RTPATH_PROP_ABSOLUTE case.
+ */
+static int rtPathAbsExAbsolute(const char *pszPath, PRTPATHPARSED pParsed, uint32_t fFlags, char *pszAbsPath, size_t *pcbAbsPath)
+{
+ if (pParsed->fProps & RTPATH_PROP_DOT_REFS)
+ {
+ uint32_t i = pParsed->cComps;
+ while (i-- > 0)
+ if ( pParsed->aComps[i].cch == 1
+ && pszPath[pParsed->aComps[i].off] == '.')
+ pParsed->aComps[i].cch = 0;
+ }
+
+ if ( (fFlags & RTPATHABS_F_ENSURE_TRAILING_SLASH)
+ && pParsed->cComps > 1)
+ pParsed->fProps |= RTPATH_PROP_DIR_SLASH;
+
+ int rc = RTPathParsedReassemble(pszPath, pParsed, fFlags & RTPATH_STR_F_STYLE_MASK, pszAbsPath, *pcbAbsPath);
+ *pcbAbsPath = pParsed->cchPath + (rc == VERR_BUFFER_OVERFLOW);
+ if (RT_SUCCESS(rc) && (pParsed->fProps & RTPATH_PROP_VOLUME))
+ rtPathAbsExUpperCaseDriveLetter(pszAbsPath);
+ return rc;
+}
+
+
+RTDECL(int) RTPathAbsEx(const char *pszBase, const char *pszPath, uint32_t fFlags, char *pszAbsPath, size_t *pcbAbsPath)
+{
+ LogFlow(("RTPathAbsEx: pszBase=%s pszPath=%s fFlags=%#x\n", pszBase, pszPath, fFlags));
+
+ /*
+ * Some input validation.
+ */
+ AssertPtr(pszPath);
+ AssertPtr(pszAbsPath);
+ AssertPtr(pcbAbsPath);
+ AssertReturn(*pszPath != '\0', VERR_PATH_ZERO_LENGTH);
+
+ AssertCompile(RTPATH_STR_F_STYLE_HOST == 0);
+ AssertReturn( RTPATH_STR_F_IS_VALID(fFlags, RTPATHABS_F_STOP_AT_BASE | RTPATHABS_F_STOP_AT_CWD | RTPATHABS_F_ENSURE_TRAILING_SLASH)
+ && !(fFlags & RTPATH_STR_F_MIDDLE), VERR_INVALID_FLAGS);
+ if ((fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_HOST)
+ fFlags |= RTPATH_STYLE;
+
+ /*
+ * Parse the path we're to straigthen out.
+ */
+ union
+ {
+ RTPATHPARSED Parsed;
+ uint8_t abPadding[1024];
+ } uBuf;
+ PRTPATHPARSED pParsedFree = NULL;
+ PRTPATHPARSED pParsed = &uBuf.Parsed;
+ size_t cbParsed = sizeof(uBuf);
+ int rc = RTPathParse(pszPath, pParsed, cbParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pParsed->cComps + 2]);
+ pParsedFree = pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
+ AssertReturn(pParsed, VERR_NO_TMP_MEMORY);
+ rc = RTPathParse(pszPath, pParsed, cbParsed, fFlags & RTPATH_STR_F_STYLE_MASK);
+ AssertRCReturnStmt(rc, RTMemTmpFree(pParsedFree), rc);
+ }
+ else
+ AssertMsgFailedReturn(("rc=%Rrc '%s'\n", rc, pszPath), rc);
+
+ /*
+ * Check if the input is more or less perfect as it is.
+ */
+ if (pParsed->fProps & RTPATH_PROP_ABSOLUTE)
+ rc = rtPathAbsExAbsolute(pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ /*
+ * What about relative but with a root slash.
+ */
+ else if (pParsed->fProps & RTPATH_PROP_ROOT_SLASH)
+ rc = rtPathAbsExRootSlash(pszBase, pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ /*
+ * Not exactly perfect. No root slash.
+ *
+ * If we have a base path, we use it unless we're into drive letters and
+ * pszPath refers to a different drive letter.
+ */
+ else if ( pszBase
+ && ( !(fFlags & RTPATH_STR_F_STYLE_DOS)
+ /** @todo add flag for skipping this and always using the base path? */
+ || !(pParsed->fProps & RTPATH_PROP_VOLUME)
+ || ( RT_C_IS_ALPHA(pszBase[0])
+ && pszBase[1] == ':'
+ && RT_C_TO_UPPER(pszBase[0]) == RT_C_TO_UPPER(pszPath[0])
+ )
+ )
+ )
+ rc = rtPathAbsExWithBase(pszBase, pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+ else
+ rc = rtPathAbsExWithCwd(pszPath, pParsed, fFlags, pszAbsPath, pcbAbsPath);
+
+ if (pParsedFree)
+ RTMemTmpFree(pParsedFree);
+ LogFlow(("RTPathAbsEx: returns %Rrc *pcbAbsPath=%#zx\n", rc, *pcbAbsPath));
+ return rc;
+}
+
+
+RTDECL(int) RTPathAbs(const char *pszPath, char *pszAbsPath, size_t cbAbsPath)
+{
+ return RTPathAbsEx(NULL, pszPath, RTPATH_STR_F_STYLE_HOST, pszAbsPath, &cbAbsPath);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAbsExDup.cpp b/src/VBox/Runtime/common/path/RTPathAbsExDup.cpp
new file mode 100644
index 00000000..6d658092
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAbsExDup.cpp
@@ -0,0 +1,82 @@
+/* $Id: RTPathAbsExDup.cpp $ */
+/** @file
+ * IPRT - RTPathAbsExDup
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/errcore.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(char *) RTPathAbsExDup(const char *pszBase, const char *pszPath, uint32_t fFlags)
+{
+ unsigned cTries = 16;
+ size_t cbAbsPath = RTPATH_MAX / 2;
+ for (;;)
+ {
+ char *pszAbsPath = RTStrAlloc(cbAbsPath);
+ if (pszAbsPath)
+ {
+ size_t cbActual = cbAbsPath;
+ int rc = RTPathAbsEx(pszBase, pszPath, fFlags, pszAbsPath, &cbActual);
+ if (RT_SUCCESS(rc))
+ {
+ if (cbActual < cbAbsPath / 2)
+ RTStrRealloc(&pszAbsPath, cbActual + 1);
+ return pszAbsPath;
+ }
+
+ RTStrFree(pszAbsPath);
+
+ if (rc != VERR_BUFFER_OVERFLOW)
+ break;
+
+ if (--cTries == 0)
+ break;
+
+ cbAbsPath = RT_MAX(RT_ALIGN_Z(cbActual + 16, 64), cbAbsPath + 256);
+ }
+ else
+ break;
+ }
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAppend.cpp b/src/VBox/Runtime/common/path/RTPathAppend.cpp
new file mode 100644
index 00000000..18297225
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAppend.cpp
@@ -0,0 +1,51 @@
+/* $Id: RTPathAppend.cpp $ */
+/** @file
+ * IPRT - RTPathAppend
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/string.h>
+
+
+RTDECL(int) RTPathAppend(char *pszPath, size_t cbPathDst, const char *pszAppend)
+{
+ return RTPathAppendEx(pszPath, cbPathDst, pszAppend, RTSTR_MAX, RTPATH_STR_F_STYLE_HOST);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAppendEx.cpp b/src/VBox/Runtime/common/path/RTPathAppendEx.cpp
new file mode 100644
index 00000000..2d936f5c
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAppendEx.cpp
@@ -0,0 +1,97 @@
+/* $Id: RTPathAppendEx.cpp $ */
+/** @file
+ * IPRT - RTPathAppendEx
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+#define RTPATH_TEMPLATE_CPP_H "RTPathAppendEx.cpp.h"
+#include "rtpath-expand-template.cpp.h"
+
+
+RTDECL(int) RTPathAppendEx(char *pszPath, size_t cbPathDst, const char *pszAppend, size_t cchAppendMax, uint32_t fFlags)
+{
+ char *pszPathEnd = RTStrEnd(pszPath, cbPathDst);
+ AssertReturn(pszPathEnd, VERR_INVALID_PARAMETER);
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0));
+
+ /*
+ * Special cases.
+ */
+ if (!pszAppend)
+ return VINF_SUCCESS;
+ size_t cchAppend = RTStrNLen(pszAppend, cchAppendMax);
+ if (!cchAppend)
+ return VINF_SUCCESS;
+ if (pszPathEnd == pszPath)
+ {
+ if (cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pszPath, pszAppend, cchAppend);
+ pszPath[cchAppend] = '\0';
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Go to path style specific code now.
+ */
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_DOS:
+ return rtPathAppendExStyleDos(pszPath, cbPathDst, pszPathEnd, pszAppend, cchAppend);
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_UNIX:
+ return rtPathAppendExStyleUnix(pszPath, cbPathDst, pszPathEnd, pszAppend, cchAppend);
+
+ default:
+ AssertFailedReturn(VERR_INVALID_FLAGS); /* impossible */
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h b/src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h
new file mode 100644
index 00000000..f51f335d
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathAppendEx.cpp.h
@@ -0,0 +1,166 @@
+/* $Id: RTPathAppendEx.cpp.h $ */
+/** @file
+ * IPRT - rtPathAppendEx - Code Template.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/**
+ * Figures the length of the root part of the path.
+ *
+ * @returns length of the root specifier.
+ * @retval 0 if none.
+ *
+ * @param pszPath The path to investigate.
+ *
+ * @remarks Unnecessary root slashes will not be counted. The caller will have
+ * to deal with it where it matters. (Unlike rtPathRootSpecLen which
+ * counts them.)
+ */
+DECLINLINE(size_t) RTPATH_STYLE_FN(rtPathRootSpecLen2)(const char *pszPath)
+{
+ /* fend of wildlife. */
+ if (!pszPath)
+ return 0;
+
+ /* Root slash? */
+ if (RTPATH_IS_SLASH(pszPath[0]))
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ /* UNC? */
+ if ( RTPATH_IS_SLASH(pszPath[1])
+ && pszPath[2] != '\0'
+ && !RTPATH_IS_SLASH(pszPath[2]))
+ {
+ /* Find the end of the server name. */
+ const char *pszEnd = pszPath + 2;
+ pszEnd += 2;
+ while ( *pszEnd != '\0'
+ && !RTPATH_IS_SLASH(*pszEnd))
+ pszEnd++;
+ if (RTPATH_IS_SLASH(*pszEnd))
+ {
+ pszEnd++;
+ while (RTPATH_IS_SLASH(*pszEnd))
+ pszEnd++;
+
+ /* Find the end of the share name */
+ while ( *pszEnd != '\0'
+ && !RTPATH_IS_SLASH(*pszEnd))
+ pszEnd++;
+ if (RTPATH_IS_SLASH(*pszEnd))
+ pszEnd++;
+ return pszPath - pszEnd;
+ }
+ }
+#endif
+ return 1;
+ }
+
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ /* Drive specifier? */
+ if ( pszPath[0] != '\0'
+ && pszPath[1] == ':'
+ && RT_C_IS_ALPHA(pszPath[0]))
+ {
+ if (RTPATH_IS_SLASH(pszPath[2]))
+ return 3;
+ return 2;
+ }
+#endif
+ return 0;
+}
+
+
+/** Internal worker for RTPathAppendEx. */
+DECLINLINE(int) RTPATH_STYLE_FN(rtPathAppendEx)(char *pszPath, size_t cbPathDst, char *pszPathEnd,
+ const char *pszAppend, size_t cchAppend)
+{
+ /*
+ * Balance slashes and check for buffer overflow.
+ */
+ if (!RTPATH_IS_SLASH(pszPathEnd[-1]))
+ {
+ if (!RTPATH_IS_SLASH(pszAppend[0]))
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ if ( (size_t)(pszPathEnd - pszPath) == 2
+ && pszPath[1] == ':'
+ && RT_C_IS_ALPHA(pszPath[0]))
+ {
+ if ((size_t)(pszPathEnd - pszPath) + cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ }
+ else
+#endif
+ {
+ if ((size_t)(pszPathEnd - pszPath) + 1 + cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ *pszPathEnd++ = RTPATH_SLASH;
+ }
+ }
+ else
+ {
+ /* One slash is sufficient at this point. */
+ while (cchAppend > 1 && RTPATH_IS_SLASH(pszAppend[1]))
+ pszAppend++, cchAppend--;
+
+ if ((size_t)(pszPathEnd - pszPath) + cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ {
+ /* No slashes needed in the appended bit. */
+ while (cchAppend && RTPATH_IS_SLASH(*pszAppend))
+ pszAppend++, cchAppend--;
+
+ /* In the leading path we can skip unnecessary trailing slashes, but
+ be sure to leave one. */
+ size_t const cchRoot = RTPATH_STYLE_FN(rtPathRootSpecLen2)(pszPath);
+ while ( (size_t)(pszPathEnd - pszPath) > RT_MAX(1, cchRoot)
+ && RTPATH_IS_SLASH(pszPathEnd[-2]))
+ pszPathEnd--;
+
+ if ((size_t)(pszPathEnd - pszPath) + cchAppend >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /*
+ * What remains now is the just the copying.
+ */
+ memcpy(pszPathEnd, pszAppend, cchAppend);
+ pszPathEnd[cchAppend] = '\0';
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathCalcRelative.cpp b/src/VBox/Runtime/common/path/RTPathCalcRelative.cpp
new file mode 100644
index 00000000..a1666957
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathCalcRelative.cpp
@@ -0,0 +1,248 @@
+/* $Id: RTPathCalcRelative.cpp $ */
+/** @file
+ * IPRT - RTPathCreateRelative.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+# include <iprt/uni.h>
+#endif
+#include "internal/path.h"
+
+
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+/** Helper for doing case insensitive comparison of a mismatching codepoint. */
+DECLINLINE(bool) rtPathCalcRelativeEqualICaseCodepoint(const char *pszPathFromStart, const char *pszPathFrom,
+ const char *pszPathToStart, const char *pszPathTo)
+{
+ RTUNICP ucFrom = RTStrGetCp(RTStrPrevCp(pszPathFromStart, pszPathFrom));
+ RTUNICP ucTo = RTStrGetCp(RTStrPrevCp(pszPathToStart, pszPathTo));
+ return ucFrom == ucTo
+ || RTUniCpToLower(ucFrom) == RTUniCpToLower(ucTo)
+ || RTUniCpToUpper(ucFrom) == RTUniCpToUpper(ucTo);
+}
+#endif
+
+
+RTDECL(int) RTPathCalcRelative(char *pszPathDst, size_t cbPathDst,
+ const char *pszPathFrom, bool fFromFile,
+ const char *pszPathTo)
+{
+ AssertPtrReturn(pszPathDst, VERR_INVALID_POINTER);
+ AssertReturn(cbPathDst, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pszPathFrom, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPathTo, VERR_INVALID_POINTER);
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ const char * const pszPathFromStart = pszPathFrom;
+ const char * const pszPathToStart = pszPathTo;
+#endif
+
+ /*
+ * Check for different root specifiers (drive letters), creating a relative path doesn't work here.
+ */
+ size_t offRootFrom = rtPathRootSpecLen(pszPathFrom);
+ AssertReturn(offRootFrom > 0, VERR_INVALID_PARAMETER);
+
+ size_t offRootTo = rtPathRootSpecLen(pszPathTo);
+ AssertReturn(offRootTo > 0, VERR_INVALID_PARAMETER);
+
+
+ /** @todo correctly deal with extra root slashes! */
+ if (offRootFrom != offRootTo)
+ return VERR_NOT_SUPPORTED;
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_DOS
+ if (RTStrNCmp(pszPathFrom, pszPathTo, offRootFrom))
+ return VERR_NOT_SUPPORTED;
+#else
+ if (RTStrNICmp(pszPathFrom, pszPathTo, offRootFrom))
+ {
+ const char *pszFromCursor = pszPathFrom;
+ const char *pszToCursor = pszPathTo;
+ while ((size_t)(pszFromCursor - pszPathFrom) < offRootFrom)
+ {
+ RTUNICP ucFrom;
+ int rc = RTStrGetCpEx(&pszFromCursor, &ucFrom);
+ AssertRCReturn(rc, rc);
+
+ RTUNICP ucTo;
+ rc = RTStrGetCpEx(&pszToCursor, &ucTo);
+ AssertRCReturn(rc, rc);
+ if ( ucFrom != ucTo
+ && RTUniCpToLower(ucFrom) != RTUniCpToLower(ucTo)
+ && RTUniCpToUpper(ucFrom) != RTUniCpToUpper(ucTo)
+ && (!RTPATH_IS_SLASH(ucFrom) || !RTPATH_IS_SLASH(ucTo)) )
+ return VERR_NOT_SUPPORTED;
+ }
+ }
+#endif
+
+ pszPathFrom += offRootFrom;
+ pszPathTo += offRootTo;
+
+ /*
+ * Skip out the part of the path which is equal to both.
+ */
+ const char *pszStartOfFromComp = pszPathFrom;
+ for (;;)
+ {
+ char const chFrom = *pszPathFrom;
+ char const chTo = *pszPathTo;
+ if (!RTPATH_IS_SLASH(chFrom))
+ {
+ if (chFrom == chTo)
+ {
+ if (chFrom)
+ { /* likely */ }
+ else
+ {
+ /* Special case: The two paths are equal. */
+ if (fFromFile)
+ {
+ size_t cchComp = pszPathFrom - pszStartOfFromComp;
+ if (cchComp < cbPathDst)
+ {
+ memcpy(pszPathDst, pszStartOfFromComp, cchComp);
+ pszPathDst[cchComp] = '\0';
+ return VINF_SUCCESS;
+ }
+ }
+ else if (sizeof(".") <= cbPathDst)
+ {
+ memcpy(pszPathDst, ".", sizeof("."));
+ return VINF_SUCCESS;
+ }
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if (rtPathCalcRelativeEqualICaseCodepoint(pszPathFromStart, pszPathFrom + 1, pszPathToStart, pszPathTo + 1))
+ { /* if not likely, then simpler code structure wise. */ }
+#endif
+ else if (chFrom != '\0' || !RTPATH_IS_SLASH(chTo) || fFromFile)
+ break;
+ else
+ {
+ pszStartOfFromComp = pszPathFrom;
+ do
+ pszPathTo++;
+ while (RTPATH_IS_SLASH(*pszPathTo));
+ break;
+ }
+ pszPathFrom++;
+ pszPathTo++;
+ }
+ else if (RTPATH_IS_SLASH(chTo))
+ {
+ /* Both have slashes. Skip any additional ones before taking down
+ the start of the component for rewinding purposes. */
+ do
+ pszPathTo++;
+ while (RTPATH_IS_SLASH(*pszPathTo));
+ do
+ pszPathFrom++;
+ while (RTPATH_IS_SLASH(*pszPathFrom));
+ pszStartOfFromComp = pszPathFrom;
+ }
+ else
+ break;
+ }
+
+ /* Rewind to the start of the current component. */
+ pszPathTo -= pszPathFrom - pszStartOfFromComp;
+ pszPathFrom = pszStartOfFromComp;
+
+ /* Paths point to the first non equal component now. */
+
+ /*
+ * Constructure the relative path.
+ */
+
+ /* Create the part to go up from pszPathFrom. */
+ unsigned offDst = 0;
+
+ if (!fFromFile && *pszPathFrom != '\0')
+ {
+ if (offDst + 3 < cbPathDst)
+ {
+ pszPathDst[offDst++] = '.';
+ pszPathDst[offDst++] = '.';
+ pszPathDst[offDst++] = RTPATH_SLASH;
+ }
+ else
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ while (*pszPathFrom != '\0')
+ {
+ char ch;
+ while ( (ch = *pszPathFrom) != '\0'
+ && !RTPATH_IS_SLASH(*pszPathFrom))
+ pszPathFrom++;
+ while ( (ch = *pszPathFrom) != '\0'
+ && RTPATH_IS_SLASH(ch))
+ pszPathFrom++;
+ if (!ch)
+ break;
+
+ if (offDst + 3 < cbPathDst)
+ {
+ pszPathDst[offDst++] = '.';
+ pszPathDst[offDst++] = '.';
+ pszPathDst[offDst++] = RTPATH_SLASH;
+ }
+ else
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /* Now append the rest of pszPathTo to the final path. */
+ size_t cchTo = strlen(pszPathTo);
+ if (offDst + cchTo <= cbPathDst)
+ {
+ memcpy(&pszPathDst[offDst], pszPathTo, cchTo);
+ pszPathDst[offDst + cchTo] = '\0';
+ return VINF_SUCCESS;
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathChangeToDosSlashes.cpp b/src/VBox/Runtime/common/path/RTPathChangeToDosSlashes.cpp
new file mode 100644
index 00000000..770ee4b5
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathChangeToDosSlashes.cpp
@@ -0,0 +1,74 @@
+/* $Id: RTPathChangeToDosSlashes.cpp $ */
+/** @file
+ * IPRT - RTPathChangeToDosSlashes
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+/**
+ * Changes all the slashes in the specified path to DOS style.
+ *
+ * Unless @a fForce is set, nothing will be done when on a UNIX flavored system
+ * since paths wont work with DOS style slashes there.
+ *
+ * @returns @a pszPath.
+ * @param pszPath The path to modify.
+ * @param fForce Whether to force the conversion on non-DOS OSes.
+ */
+RTDECL(char *) RTPathChangeToDosSlashes(char *pszPath, bool fForce)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ RT_NOREF_PV(fForce);
+#else
+ if (fForce)
+#endif
+ {
+ char ch;
+ char *psz = pszPath;
+ while ((ch = *psz) != '\0')
+ {
+ if (ch == '/')
+ *psz = '\\';
+ psz++;
+ }
+ }
+ return pszPath;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathChangeToUnixSlashes.cpp b/src/VBox/Runtime/common/path/RTPathChangeToUnixSlashes.cpp
new file mode 100644
index 00000000..8649b792
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathChangeToUnixSlashes.cpp
@@ -0,0 +1,74 @@
+/* $Id: RTPathChangeToUnixSlashes.cpp $ */
+/** @file
+ * IPRT - RTPathChangeToUnixSlashes
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+/**
+ * Changes all the slashes in the specified path to unix style.
+ *
+ * Unless @a fForce is set, nothing will be done when on a UNIX flavored system
+ * since paths wont work with DOS style slashes there.
+ *
+ * @returns @a pszPath.
+ * @param pszPath The path to modify.
+ * @param fForce Whether to force the conversion on non-DOS OSes.
+ */
+RTDECL(char *) RTPathChangeToUnixSlashes(char *pszPath, bool fForce)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ RT_NOREF_PV(fForce);
+#else
+ if (fForce)
+#endif
+ {
+ char ch;
+ char *psz = pszPath;
+ while ((ch = *psz) != '\0')
+ {
+ if (ch == '\\')
+ *psz = '/';
+ psz++;
+ }
+ }
+ return pszPath;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathCopyComponents.cpp b/src/VBox/Runtime/common/path/RTPathCopyComponents.cpp
new file mode 100644
index 00000000..ea202507
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathCopyComponents.cpp
@@ -0,0 +1,94 @@
+/* $Id: RTPathCopyComponents.cpp $ */
+/** @file
+ * IPRT - RTPathCountComponents
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include "internal/path.h"
+
+
+RTDECL(int) RTPathCopyComponents(char *pszDst, size_t cbDst, const char *pszSrc, size_t cComponents)
+{
+ /*
+ * Quick input validation.
+ */
+ AssertPtr(pszDst);
+ AssertPtr(pszSrc);
+ if (cbDst == 0)
+ return VERR_BUFFER_OVERFLOW;
+
+ /*
+ * Fend of the simple case where nothing is wanted.
+ */
+ if (cComponents == 0)
+ {
+ *pszDst = '\0';
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Parse into the path until we've counted the desired number of objects
+ * or hit the end.
+ */
+ size_t off = rtPathRootSpecLen(pszSrc);
+ size_t c = off != 0;
+ while (c < cComponents && pszSrc[off])
+ {
+ c++;
+ while (!RTPATH_IS_SLASH(pszSrc[off]) && pszSrc[off])
+ off++;
+ while (RTPATH_IS_SLASH(pszSrc[off]))
+ off++;
+ }
+
+ /*
+ * Copy up to but not including 'off'.
+ */
+ if (off >= cbDst)
+ return VERR_BUFFER_OVERFLOW;
+
+ memcpy(pszDst, pszSrc, off);
+ pszDst[off] = '\0';
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathCountComponents.cpp b/src/VBox/Runtime/common/path/RTPathCountComponents.cpp
new file mode 100644
index 00000000..27b4a298
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathCountComponents.cpp
@@ -0,0 +1,62 @@
+/* $Id: RTPathCountComponents.cpp $ */
+/** @file
+ * IPRT - RTPathCountComponents
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include "internal/path.h"
+
+
+RTDECL(size_t) RTPathCountComponents(const char *pszPath)
+{
+ size_t off = rtPathRootSpecLen(pszPath);
+ size_t c = off != 0;
+ while (pszPath[off])
+ {
+ c++;
+ while (!RTPATH_IS_SLASH(pszPath[off]) && pszPath[off])
+ off++;
+ while (RTPATH_IS_SLASH(pszPath[off]))
+ off++;
+ }
+ return c;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathEnsureTrailingSeparator.cpp b/src/VBox/Runtime/common/path/RTPathEnsureTrailingSeparator.cpp
new file mode 100644
index 00000000..b957234a
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathEnsureTrailingSeparator.cpp
@@ -0,0 +1,108 @@
+/* $Id: RTPathEnsureTrailingSeparator.cpp $ */
+/** @file
+ * IPRT - RTPathEnsureTrailingSeparator & RTPathEnsureTrailingSeparatorEx
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Slash character indexed by path style. */
+static char g_achSlashes[] =
+{
+ /*[RTPATH_STR_F_STYLE_HOST] =*/ RTPATH_SLASH,
+ /*[RTPATH_STR_F_STYLE_DOS] =*/ '\\',
+ /*[RTPATH_STR_F_STYLE_UNIX] =*/ '/',
+ /*[RTPATH_STR_F_STYLE_RESERVED] =*/ '!',
+};
+AssertCompile(RTPATH_STR_F_STYLE_HOST == 0);
+AssertCompile(RTPATH_STR_F_STYLE_DOS == 1);
+AssertCompile(RTPATH_STR_F_STYLE_UNIX == 2);
+
+
+RTDECL(size_t) RTPathEnsureTrailingSeparatorEx(char *pszPath, size_t cbPath, uint32_t fFlags)
+{
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0));
+
+ size_t off = strlen(pszPath);
+ if (off > 0)
+ {
+ char ch = pszPath[off - 1];
+ if (ch == '/')
+ return off;
+ if ( (ch == ':' || ch == '\\')
+ && ( (fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_DOS
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ || (fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_HOST
+#endif
+ ))
+ return off;
+
+ if (off + 2 <= cbPath)
+ {
+ pszPath[off++] = g_achSlashes[fFlags & RTPATH_STR_F_STYLE_MASK];
+ pszPath[off] = '\0';
+ return off;
+ }
+ }
+ else if (off + 3 <= cbPath)
+ {
+ pszPath[off++] = '.';
+ pszPath[off++] = g_achSlashes[fFlags & RTPATH_STR_F_STYLE_MASK];
+ pszPath[off] = '\0';
+ return off;
+ }
+
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTPathEnsureTrailingSeparatorEx);
+
+
+RTDECL(size_t) RTPathEnsureTrailingSeparator(char *pszPath, size_t cbPath)
+{
+ return RTPathEnsureTrailingSeparatorEx(pszPath, cbPath, RTPATH_STR_F_STYLE_HOST);
+}
+RT_EXPORT_SYMBOL(RTPathEnsureTrailingSeparator);
+
diff --git a/src/VBox/Runtime/common/path/RTPathExt.cpp b/src/VBox/Runtime/common/path/RTPathExt.cpp
new file mode 100644
index 00000000..54669270
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathExt.cpp
@@ -0,0 +1,79 @@
+/* $Id: RTPathExt.cpp $ */
+/** @file
+ * IPRT - RTPathExt
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+RTDECL(char *) RTPathSuffix(const char *pszPath)
+{
+ const char *psz = pszPath;
+ const char *pszExt = NULL;
+
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ /* handle separators. */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ case ':':
+ pszExt = NULL;
+ break;
+
+ case '\\':
+#endif
+ case '/':
+ pszExt = NULL;
+ break;
+ case '.':
+ pszExt = psz;
+ break;
+
+ /* the end */
+ case '\0':
+ if (pszExt && pszExt != pszPath && pszExt[1])
+ return (char *)(void *)pszExt;
+ return NULL;
+ }
+ }
+
+ /* not reached */
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathFilename.cpp b/src/VBox/Runtime/common/path/RTPathFilename.cpp
new file mode 100644
index 00000000..699ff8ec
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathFilename.cpp
@@ -0,0 +1,109 @@
+/* $Id: RTPathFilename.cpp $ */
+/** @file
+ * IPRT - RTPathFilename
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+
+
+
+RTDECL(char *) RTPathFilename(const char *pszPath)
+{
+ return RTPathFilenameEx(pszPath, RTPATH_STYLE);
+}
+RT_EXPORT_SYMBOL(RTPathFilename);
+
+
+RTDECL(char *) RTPathFilenameEx(const char *pszPath, uint32_t fFlags)
+{
+ const char *psz = pszPath;
+ const char *pszName = pszPath;
+
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0 /*no extra flags*/));
+ fFlags &= RTPATH_STR_F_STYLE_MASK;
+ if (fFlags == RTPATH_STR_F_STYLE_HOST)
+ fFlags = RTPATH_STYLE;
+ if (fFlags == RTPATH_STR_F_STYLE_DOS)
+ {
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ /* handle separators. */
+ case ':':
+ case '\\':
+ case '/':
+ pszName = psz + 1;
+ break;
+
+ /* the end */
+ case '\0':
+ if (*pszName)
+ return (char *)(void *)pszName;
+ return NULL;
+ }
+ }
+ }
+ else
+ {
+ Assert(fFlags == RTPATH_STR_F_STYLE_UNIX);
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ /* handle separators. */
+ case '/':
+ pszName = psz + 1;
+ break;
+
+ /* the end */
+ case '\0':
+ if (*pszName)
+ return (char *)(void *)pszName;
+ return NULL;
+ }
+ }
+ }
+
+ /* not reached */
+}
+RT_EXPORT_SYMBOL(RTPathFilenameEx);
+
diff --git a/src/VBox/Runtime/common/path/RTPathFilenameUtf16.cpp b/src/VBox/Runtime/common/path/RTPathFilenameUtf16.cpp
new file mode 100644
index 00000000..711ba7b4
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathFilenameUtf16.cpp
@@ -0,0 +1,109 @@
+/* $Id: RTPathFilenameUtf16.cpp $ */
+/** @file
+ * IPRT - RTPathFilenameUtf16
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+
+
+
+RTDECL(PRTUTF16) RTPathFilenameUtf16(PCRTUTF16 pwszPath)
+{
+ return RTPathFilenameExUtf16(pwszPath, RTPATH_STYLE);
+}
+RT_EXPORT_SYMBOL(RTPathFilenameUtf16);
+
+
+RTDECL(PRTUTF16) RTPathFilenameExUtf16(PCRTUTF16 pwszPath, uint32_t fFlags)
+{
+ PCRTUTF16 pwsz = pwszPath;
+ PCRTUTF16 pwszName = pwszPath;
+
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0 /*no extra flags*/));
+ fFlags &= RTPATH_STR_F_STYLE_MASK;
+ if (fFlags == RTPATH_STR_F_STYLE_HOST)
+ fFlags = RTPATH_STYLE;
+ if (fFlags == RTPATH_STR_F_STYLE_DOS)
+ {
+ for (;; pwsz++)
+ {
+ switch (*pwsz)
+ {
+ /* handle separators. */
+ case ':':
+ case '\\':
+ case '/':
+ pwszName = pwsz + 1;
+ break;
+
+ /* the end */
+ case '\0':
+ if (*pwszName)
+ return (PRTUTF16)(void *)pwszName;
+ return NULL;
+ }
+ }
+ }
+ else
+ {
+ Assert(fFlags == RTPATH_STR_F_STYLE_UNIX);
+ for (;; pwsz++)
+ {
+ switch (*pwsz)
+ {
+ /* handle separators. */
+ case '/':
+ pwszName = pwsz + 1;
+ break;
+
+ /* the end */
+ case '\0':
+ if (*pwszName)
+ return (PRTUTF16)(void *)pwszName;
+ return NULL;
+ }
+ }
+ }
+
+ /* not reached */
+}
+RT_EXPORT_SYMBOL(RTPathFilenameExUtf16);
+
diff --git a/src/VBox/Runtime/common/path/RTPathFindCommon.cpp b/src/VBox/Runtime/common/path/RTPathFindCommon.cpp
new file mode 100644
index 00000000..626ac181
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathFindCommon.cpp
@@ -0,0 +1,127 @@
+/* $Id: RTPathFindCommon.cpp $ */
+/** @file
+ * IPRT - RTPathFindCommon implementations.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+
+#define RTPATH_TEMPLATE_CPP_H "RTPathFindCommon.cpp.h"
+#include "rtpath-expand-template.cpp.h"
+
+
+RTDECL(size_t) RTPathFindCommonEx(size_t cPaths, const char * const *papszPaths, uint32_t fFlags)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, RTPATHFINDCOMMON_F_IGNORE_DOTDOT), 0);
+ AssertReturn(cPaths > 0, 0);
+ AssertPtrReturn(papszPaths, 0);
+ size_t i = cPaths;
+ while (i-- > 0)
+ AssertPtrReturn(papszPaths[i], 0);
+
+ /*
+ * Duplicate papszPaths so we can have individual positions in each path.
+ * Use the stack if we haven't got too many paths.
+ */
+ void *pvFree;
+ const char **papszCopy;
+ size_t cbNeeded = cPaths * sizeof(papszCopy[0]);
+ if (cbNeeded <= _2K)
+ {
+ pvFree = NULL;
+ papszCopy = (const char **)alloca(cbNeeded);
+ }
+ else
+ {
+ pvFree = RTMemTmpAlloc(cbNeeded);
+ papszCopy = (const char **)pvFree;
+ }
+ AssertReturn(papszCopy, 0);
+ memcpy(papszCopy, papszPaths, cbNeeded);
+
+ /*
+ * Invoke the worker for the selected path style.
+ */
+ size_t cchRet;
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_DOS:
+ cchRet= rtPathFindCommonStyleDos(cPaths, papszCopy, fFlags);
+ break;
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_UNIX:
+ cchRet = rtPathFindCommonStyleUnix(cPaths, papszCopy, fFlags);
+ break;
+
+ default:
+ AssertFailedStmt(cchRet = 0); /* impossible */
+ }
+
+ /*
+ * Clean up and return.
+ */
+ if (pvFree)
+ RTMemTmpFree(pvFree);
+ return cchRet;
+}
+RT_EXPORT_SYMBOL(RTPathFindCommonEx);
+
+
+RTDECL(size_t) RTPathFindCommon(size_t cPaths, const char * const *papszPaths)
+{
+ return RTPathFindCommonEx(cPaths, papszPaths, RTPATH_STR_F_STYLE_HOST);
+}
+RT_EXPORT_SYMBOL(RTPathFindCommon);
+
diff --git a/src/VBox/Runtime/common/path/RTPathFindCommon.cpp.h b/src/VBox/Runtime/common/path/RTPathFindCommon.cpp.h
new file mode 100644
index 00000000..196a26a0
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathFindCommon.cpp.h
@@ -0,0 +1,267 @@
+/* $Id: RTPathFindCommon.cpp.h $ */
+/** @file
+ * IPRT - RTPathFindCommon - Code Template.
+ *
+ * This file included multiple times with different path style macros.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include "rtpath-root-length-template.cpp.h"
+
+
+/** Helper for skipping slashes, given a pointer to the first one. */
+DECLINLINE(const char *) RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(const char *pszSlash)
+{
+ for (;;)
+ {
+ char ch;
+ do
+ ch = *++pszSlash;
+ while (RTPATH_IS_SLASH(ch));
+
+ /* Also skip '/./' sequences. */
+ if ( ch != '.'
+ || !RTPATH_IS_SLASH(pszSlash[1]))
+ break;
+ pszSlash++;
+ }
+ return pszSlash;
+}
+
+
+static size_t RTPATH_STYLE_FN(rtPathFindCommon)(size_t cPaths, const char **papszPaths, uint32_t fFlags)
+{
+ /*
+ * Check for '..' elements before we start doing anything.
+ *
+ * They are currently not supported at all (lazy) and we shun them for
+ * security reasons. Iff we want to support them properly, we'd have to:
+ * 1. Note down exactly where the root specification ends for each of
+ * the paths so we can prevent '..' from messing with it.
+ * 2. When encountering '..', we'd have to ascend all paths.
+ * 3. When encountering a difference, we'd have to see if it's eliminated
+ * by a following '..' sequence.
+ * 4. When returning anything, we'd have to see if it could be affected by
+ * a '..' sequence later in any of the paths.
+ *
+ * We could kind of RTAbsPath the secondary paths, however it wouldn't work
+ * for the primary path we use as reference.
+ *
+ * Summa summarum: Annoyingly tedious, so just forget it.
+ */
+ if (!(fFlags & RTPATHFINDCOMMON_F_IGNORE_DOTDOT))
+ for (size_t i = 0; i < cPaths; i++)
+ {
+ const char * const psz = papszPaths[i];
+ const char *pszDot = strchr(psz, '.');
+ while (pszDot)
+ {
+ if ( pszDot[1] == '.'
+ && (RTPATH_IS_SLASH(pszDot[2]) || pszDot[2] == '\0')
+ && ( pszDot == psz
+ || RTPATH_IS_SLASH(pszDot[-1])
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ || (pszDot[-1] == ':' && psz + 2 == pszDot && !(fFlags & RTPATH_STR_F_NO_START))
+#endif
+ )
+ )
+ return 0;
+ pszDot = strchr(pszDot + 1, '.');
+ }
+ }
+
+ /*
+ * We use the first path as the reference for the return length.
+ */
+ const char * pszPath0 = papszPaths[0];
+ const char * pszPath0EndLastComp = pszPath0;
+ const char * const pszPath0Start = pszPath0;
+
+ /*
+ * Deal with root stuff as appropriate.
+ */
+ if (fFlags & RTPATH_STR_F_NO_START)
+ {
+ /* We ignore leading slashes when RTPATH_STR_F_NO_START is specified: */
+ for (size_t i = 0; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ papszPaths[i] = RTPATH_IS_SLASH(*psz) ? RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(psz) : psz;
+ }
+ pszPath0EndLastComp = pszPath0 = papszPaths[0];
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if (RTPATH_IS_SLASH(pszPath0[0]))
+ {
+ /* UNC requires a little bit of special magic to make sure we have
+ exactly two slashes in each path and don't mix things up. */
+ char ch;
+ if ( RTPATH_IS_SLASH(pszPath0[1])
+ && (ch = pszPath0[2]) != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ {
+ pszPath0 += 2;
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ if ( RTPATH_IS_SLASH(psz[0])
+ && RTPATH_IS_SLASH(psz[1])
+ && (ch = psz[2]) != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ papszPaths[i] = psz + 2;
+ else
+ return 0;
+ }
+ }
+ else
+ {
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ if ( RTPATH_IS_SLASH(psz[0])
+ && RTPATH_IS_SLASH(psz[1])
+ && (ch = psz[2]) != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ return 0;
+ if (!RTPATH_IS_SLASH(psz[0]))
+ return 0;
+ papszPaths[i] = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(psz);
+ }
+ pszPath0EndLastComp = pszPath0 = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(pszPath0);
+ }
+ }
+ /* Skip past the drive letter if there is one, as that eliminates the need
+ to handle ':' in the main loop below. */
+ else if ( RT_C_IS_ALPHA(pszPath0[0])
+ && pszPath0[1] == ':')
+ {
+ /* Drive letter part first: */
+ char const chDrv = RT_C_TO_UPPER(pszPath0[0]);
+ pszPath0 += 2;
+ pszPath0EndLastComp = pszPath0;
+
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ if ( ( psz[0] != chDrv
+ && RT_C_TO_UPPER(psz[0]) != chDrv)
+ || psz[1] != ':')
+ return 0;
+ papszPaths[i] = psz + 2;
+ }
+
+ /* Subsequent slashes or lack thereof. */
+ if (RTPATH_IS_SLASH(*pszPath0))
+ {
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ const char *psz = papszPaths[i];
+ if (!RTPATH_IS_SLASH(*psz))
+ return pszPath0EndLastComp - pszPath0Start;
+ papszPaths[i] = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(psz);
+ }
+ pszPath0EndLastComp = pszPath0 = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(pszPath0);
+ }
+ else
+ for (size_t i = 1; i < cPaths; i++)
+ if (RTPATH_IS_SLASH(*papszPaths[i]))
+ return pszPath0EndLastComp - pszPath0Start;
+ }
+#endif
+
+ /*
+ * Main compare loop.
+ */
+ for (;;)
+ {
+ RTUNICP uc0;
+ int rc = RTStrGetCpEx(&pszPath0, &uc0);
+ AssertRCReturn(rc, 0);
+ if (!RTPATH_IS_SLASH(uc0))
+ {
+ if (uc0 != 0)
+ {
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ RTUNICP uc;
+ rc = RTStrGetCpEx(&papszPaths[i], &uc);
+ AssertRCReturn(rc, 0);
+ if (uc == uc0)
+ { /* likely */}
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if ( RTUniCpToUpper(uc) == RTUniCpToUpper(uc0)
+ || RTUniCpToLower(uc) == RTUniCpToLower(uc0))
+ { /* less likely */}
+#endif
+ else
+ return pszPath0EndLastComp - pszPath0Start;
+ }
+ }
+ else
+ {
+ /* pszPath0 is at an end. Check the state of the others as we must
+ return the whole pszPath0 length if their are also at the end of
+ at a slash. */
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ char ch = *papszPaths[i];
+ if ( ch != '\0'
+ && !RTPATH_IS_SLASH(ch))
+ return pszPath0EndLastComp - pszPath0Start;
+ }
+ return pszPath0 - 1 - pszPath0Start;
+ }
+ }
+ else
+ {
+ /* pszPath0 is at a slash. Check whether all the other are too or are at
+ the end of the string. If any other string ends here, we can return
+ the length up to but not including the slash. */
+ bool fDone = false;
+ for (size_t i = 1; i < cPaths; i++)
+ {
+ char ch = *papszPaths[i];
+ if (ch == '\0')
+ fDone = true;
+ else if (RTPATH_IS_SLASH(ch))
+ papszPaths[i] = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(papszPaths[i]);
+ else
+ return pszPath0EndLastComp - pszPath0Start;
+ }
+ if (fDone)
+ return pszPath0 - pszPath0Start;
+ pszPath0EndLastComp = pszPath0 = RTPATH_STYLE_FN(rtPathHlpSkipSlashes)(pszPath0 - 1);
+ }
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathGlob.cpp b/src/VBox/Runtime/common/path/RTPathGlob.cpp
new file mode 100644
index 00000000..46a634b2
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathGlob.cpp
@@ -0,0 +1,2177 @@
+/* $Id: RTPathGlob.cpp $ */
+/** @file
+ * IPRT - RTPathGlob
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/env.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+#if defined(RT_OS_WINDOWS)
+# include <iprt/utf16.h>
+# include <iprt/win/windows.h>
+# include "../../r3/win/internal-r3-win.h"
+
+#elif defined(RT_OS_OS2)
+# define INCL_BASE
+# include <os2.h>
+# undef RT_MAX /* collision */
+
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Maximum number of results. */
+#define RTPATHGLOB_MAX_RESULTS _32K
+/** Maximum number of zero-or-more wildcards in a pattern.
+ * This limits stack usage and recursion depth, as well as execution time. */
+#define RTPATHMATCH_MAX_ZERO_OR_MORE 24
+/** Maximum number of variable items. */
+#define RTPATHMATCH_MAX_VAR_ITEMS _4K
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Matching operation.
+ */
+typedef enum RTPATHMATCHOP
+{
+ RTPATHMATCHOP_INVALID = 0,
+ /** EOS: Returns a match if at end of string. */
+ RTPATHMATCHOP_RETURN_MATCH_IF_AT_END,
+ /** Asterisk: Returns a match (trailing asterisk). */
+ RTPATHMATCHOP_RETURN_MATCH,
+ /** Asterisk: Returns a match (just asterisk), unless it's '.' or '..'. */
+ RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT,
+ /** Plain text: Case sensitive string compare. */
+ RTPATHMATCHOP_STRCMP,
+ /** Plain text: Case insensitive string compare. */
+ RTPATHMATCHOP_STRICMP,
+ /** Question marks: Skips exactly one code point. */
+ RTPATHMATCHOP_SKIP_ONE_CODEPOINT,
+ /** Question marks: Skips exactly RTPATHMATCHCORE::cch code points. */
+ RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS,
+ /** Char set: Requires the next codepoint to be in the ASCII-7 set defined by
+ * RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. No ranges. */
+ RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7,
+ /** Char set: Requires the next codepoint to not be in the ASCII-7 set defined
+ * by RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. No ranges. */
+ RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7,
+ /** Char set: Requires the next codepoint to be in the extended set defined by
+ * RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. Ranges, UTF-8. */
+ RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED,
+ /** Char set: Requires the next codepoint to not be in the extended set defined
+ * by RTPATHMATCHCORE::pch & RTPATHMATCHCORE::cch. Ranges, UTF-8. */
+ RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED,
+ /** Variable: Case sensitive variable value compare, RTPATHMATCHCORE::uOp2 is
+ * the variable table index. */
+ RTPATHMATCHOP_VARIABLE_VALUE_CMP,
+ /** Variable: Case insensitive variable value compare, RTPATHMATCHCORE::uOp2 is
+ * the variable table index. */
+ RTPATHMATCHOP_VARIABLE_VALUE_ICMP,
+ /** Asterisk: Match zero or more code points, there must be at least
+ * RTPATHMATCHCORE::cch code points after it. */
+ RTPATHMATCHOP_ZERO_OR_MORE,
+ /** Asterisk: Match zero or more code points, there must be at least
+ * RTPATHMATCHCORE::cch code points after it, unless it's '.' or '..'. */
+ RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT,
+ /** End of valid operations. */
+ RTPATHMATCHOP_END
+} RTPATHMATCHOP;
+
+/**
+ * Matching instruction.
+ */
+typedef struct RTPATHMATCHCORE
+{
+ /** The action to take. */
+ RTPATHMATCHOP enmOpCode;
+ /** Generic value operand. */
+ uint16_t uOp2;
+ /** Generic length operand. */
+ uint16_t cch;
+ /** Generic string pointer operand. */
+ const char *pch;
+} RTPATHMATCHCORE;
+/** Pointer to a matching instruction. */
+typedef RTPATHMATCHCORE *PRTPATHMATCHCORE;
+/** Pointer to a const matching instruction. */
+typedef RTPATHMATCHCORE const *PCRTPATHMATCHCORE;
+
+/**
+ * Path matching instruction allocator.
+ */
+typedef struct RTPATHMATCHALLOC
+{
+ /** Allocated array of instructions. */
+ PRTPATHMATCHCORE paInstructions;
+ /** Index of the next free entry in paScratch. */
+ uint32_t iNext;
+ /** Number of instructions allocated. */
+ uint32_t cAllocated;
+} RTPATHMATCHALLOC;
+/** Pointer to a matching instruction allocator. */
+typedef RTPATHMATCHALLOC *PRTPATHMATCHALLOC;
+
+/**
+ * Path matching cache, mainly intended for variables like the PATH.
+ */
+typedef struct RTPATHMATCHCACHE
+{
+ /** @todo optimize later. */
+ uint32_t iNothingYet;
+} RTPATHMATCHCACHE;
+/** Pointer to a path matching cache. */
+typedef RTPATHMATCHCACHE *PRTPATHMATCHCACHE;
+
+
+
+/** Parsed path entry.*/
+typedef struct RTPATHGLOBPPE
+{
+ /** Normal: Index into RTPATHGLOB::MatchInstrAlloc.paInstructions. */
+ uint32_t iMatchProg : 16;
+ /** Set if this is a normal entry which is matched using iMatchProg. */
+ uint32_t fNormal : 1;
+ /** !fNormal: Plain name that can be dealt with using without
+ * enumerating the whole directory, unless of course the file system is case
+ * sensitive and the globbing isn't (that needs figuring out on a per
+ * directory basis). */
+ uint32_t fPlain : 1;
+ /** !fNormal: Match zero or more subdirectories. */
+ uint32_t fStarStar : 1;
+ /** !fNormal: The whole component is a variable expansion. */
+ uint32_t fExpVariable : 1;
+
+ /** Filter: Set if it only matches directories. */
+ uint32_t fDir : 1;
+ /** Set if it's the final component. */
+ uint32_t fFinal : 1;
+
+ /** Unused bits. */
+ uint32_t fReserved : 2+8;
+} RTPATHGLOBPPE;
+
+
+typedef struct RTPATHGLOB
+{
+ /** Path buffer. */
+ char szPath[RTPATH_MAX];
+ /** Temporary buffers. */
+ union
+ {
+ /** File system object info structure. */
+ RTFSOBJINFO ObjInfo;
+ /** Directory entry buffer. */
+ RTDIRENTRY DirEntry;
+ /** Padding the buffer to an unreasonably large size. */
+ uint8_t abPadding[RTPATH_MAX + sizeof(RTDIRENTRY)];
+ } u;
+
+
+ /** Where to insert the next one.*/
+ PRTPATHGLOBENTRY *ppNext;
+ /** The head pointer. */
+ PRTPATHGLOBENTRY pHead;
+ /** Result count. */
+ uint32_t cResults;
+ /** Counts path overflows. */
+ uint32_t cPathOverflows;
+ /** The input flags. */
+ uint32_t fFlags;
+ /** Matching instruction allocator. */
+ RTPATHMATCHALLOC MatchInstrAlloc;
+ /** Matching state. */
+ RTPATHMATCHCACHE MatchCache;
+
+ /** The pattern string. */
+ const char *pszPattern;
+ /** The parsed path. */
+ PRTPATHPARSED pParsed;
+ /** The component to start with. */
+ uint16_t iFirstComp;
+ /** The corresponding path offset (previous components already present). */
+ uint16_t offFirstPath;
+ /** Path component information we need. */
+ RTPATHGLOBPPE aComps[1];
+} RTPATHGLOB;
+typedef RTPATHGLOB *PRTPATHGLOB;
+
+
+/**
+ * Matching variable lookup table.
+ * Currently so small we don't bother sorting it and doing binary lookups.
+ */
+typedef struct RTPATHMATCHVAR
+{
+ /** The variable name. */
+ const char *pszName;
+ /** The variable name length. */
+ uint16_t cchName;
+ /** Only available as the verify first component. */
+ bool fFirstOnly;
+
+ /**
+ * Queries a given variable value.
+ *
+ * @returns IPRT status code.
+ * @retval VERR_BUFFER_OVERFLOW
+ * @retval VERR_TRY_AGAIN if the caller should skip this value item and try the
+ * next one instead (e.g. env var not present).
+ * @retval VINF_EOF when retrieving the last one, if possible.
+ * @retval VERR_EOF when @a iItem is past the item space.
+ *
+ * @param iItem The variable value item to retrieve. (A variable may
+ * have more than one value, e.g. 'BothProgramFile' on a
+ * 64-bit system or 'Path'.)
+ * @param pszBuf Where to return the value.
+ * @param cbBuf The buffer size.
+ * @param pcchValue Where to return the length of the return string.
+ * @param pCache Pointer to the path matching cache. May speed up
+ * enumerating PATH items and similar.
+ */
+ DECLCALLBACKMEMBER(int, pfnQuery,(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, PRTPATHMATCHCACHE pCache));
+
+ /**
+ * Matching method, optional.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS on match.
+ * @retval VERR_MISMATCH on mismatch.
+ *
+ * @param pszMatch String to match with (not terminated).
+ * @param cchMatch The length of what we match with.
+ * @param fIgnoreCase Whether to ignore case or not when comparing.
+ * @param pcchMatched Where to return the length of the match (value length).
+ */
+ DECLCALLBACKMEMBER(int, pfnMatch,(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, size_t *pcchMatched));
+
+} RTPATHMATCHVAR;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp, size_t offStarStarPath);
+static int rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
+static int rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
+static int rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp);
+
+
+/**
+ * Implements the two variable access functions for a simple one value variable.
+ */
+#define RTPATHMATCHVAR_SIMPLE(a_Name, a_GetStrExpr) \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
+ PRTPATHMATCHCACHE pCache) \
+ { \
+ if (iItem == 0) \
+ { \
+ const char *pszValue = a_GetStrExpr; \
+ size_t cchValue = strlen(pszValue); \
+ if (cchValue + 1 <= cbBuf) \
+ { \
+ memcpy(pszBuf, pszValue, cchValue + 1); \
+ *pcchValue = cchValue; \
+ return VINF_EOF; \
+ } \
+ return VERR_BUFFER_OVERFLOW; \
+ } \
+ NOREF(pCache);\
+ return VERR_EOF; \
+ } \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
+ size_t *pcchMatched) \
+ { \
+ const char *pszValue = a_GetStrExpr; \
+ size_t cchValue = strlen(pszValue); \
+ if ( cchValue >= cchMatch \
+ && ( !fIgnoreCase \
+ ? memcmp(pszValue, pchMatch, cchValue) == 0 \
+ : RTStrNICmp(pszValue, pchMatch, cchValue) == 0) ) \
+ { \
+ *pcchMatched = cchValue; \
+ return VINF_SUCCESS; \
+ } \
+ return VERR_MISMATCH; \
+ } \
+ typedef int RT_CONCAT(DummyColonType_,a_Name)
+
+/**
+ * Implements mapping a glob variable to an environment variable.
+ */
+#define RTPATHMATCHVAR_SIMPLE_ENVVAR(a_Name, a_pszEnvVar, a_cbMaxValue) \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
+ PRTPATHMATCHCACHE pCache) \
+ { \
+ if (iItem == 0) \
+ { \
+ int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, pszBuf, cbBuf, pcchValue); \
+ if (RT_SUCCESS(rc)) \
+ return VINF_EOF; \
+ if (rc != VERR_ENV_VAR_NOT_FOUND) \
+ return rc; \
+ } \
+ NOREF(pCache);\
+ return VERR_EOF; \
+ } \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
+ size_t *pcchMatched) \
+ { \
+ char szValue[a_cbMaxValue]; \
+ size_t cchValue; \
+ int rc = RTEnvGetEx(RTENV_DEFAULT, a_pszEnvVar, szValue, sizeof(szValue), &cchValue); \
+ if ( RT_SUCCESS(rc) \
+ && cchValue >= cchMatch \
+ && ( !fIgnoreCase \
+ ? memcmp(szValue, pchMatch, cchValue) == 0 \
+ : RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
+ { \
+ *pcchMatched = cchValue; \
+ return VINF_SUCCESS; \
+ } \
+ return VERR_MISMATCH; \
+ } \
+ typedef int RT_CONCAT(DummyColonType_,a_Name)
+
+/**
+ * Implements mapping a glob variable to multiple environment variable values.
+ *
+ * @param a_Name The variable name.
+ * @param a_apszVarNames Assumes to be a global variable that RT_ELEMENTS
+ * works correctly on.
+ * @param a_cbMaxValue The max expected value size.
+ */
+#define RTPATHMATCHVAR_MULTIPLE_ENVVARS(a_Name, a_apszVarNames, a_cbMaxValue) \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarQuery_,a_Name)(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue, \
+ PRTPATHMATCHCACHE pCache) \
+ { \
+ if (iItem < RT_ELEMENTS(a_apszVarNames)) \
+ { \
+ int rc = RTEnvGetEx(RTENV_DEFAULT, a_apszVarNames[iItem], pszBuf, cbBuf, pcchValue); \
+ if (RT_SUCCESS(rc)) \
+ return iItem + 1 == RT_ELEMENTS(a_apszVarNames) ? VINF_EOF : VINF_SUCCESS; \
+ if (rc == VERR_ENV_VAR_NOT_FOUND) \
+ rc = VERR_TRY_AGAIN; \
+ return rc; \
+ } \
+ NOREF(pCache);\
+ return VERR_EOF; \
+ } \
+ static DECLCALLBACK(int) RT_CONCAT(rtPathVarMatch_,a_Name)(const char *pchMatch, size_t cchMatch, bool fIgnoreCase, \
+ size_t *pcchMatched) \
+ { \
+ for (uint32_t iItem = 0; iItem < RT_ELEMENTS(a_apszVarNames); iItem++) \
+ { \
+ char szValue[a_cbMaxValue]; \
+ size_t cchValue; \
+ int rc = RTEnvGetEx(RTENV_DEFAULT, a_apszVarNames[iItem], szValue, sizeof(szValue), &cchValue);\
+ if ( RT_SUCCESS(rc) \
+ && cchValue >= cchMatch \
+ && ( !fIgnoreCase \
+ ? memcmp(szValue, pchMatch, cchValue) == 0 \
+ : RTStrNICmp(szValue, pchMatch, cchValue) == 0) ) \
+ { \
+ *pcchMatched = cchValue; \
+ return VINF_SUCCESS; \
+ } \
+ } \
+ return VERR_MISMATCH; \
+ } \
+ typedef int RT_CONCAT(DummyColonType_,a_Name)
+
+
+RTPATHMATCHVAR_SIMPLE(Arch, RTBldCfgTargetArch());
+RTPATHMATCHVAR_SIMPLE(Bits, RT_XSTR(ARCH_BITS));
+#ifdef RT_OS_WINDOWS
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinAppData, "AppData", RTPATH_MAX);
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinProgramData, "ProgramData", RTPATH_MAX);
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinProgramFiles, "ProgramFiles", RTPATH_MAX);
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinCommonProgramFiles, "CommonProgramFiles", RTPATH_MAX);
+# if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherProgramFiles, "ProgramFiles(x86)", RTPATH_MAX);
+RTPATHMATCHVAR_SIMPLE_ENVVAR(WinOtherCommonProgramFiles, "CommonProgramFiles(x86)", RTPATH_MAX);
+# else
+# error "Port ME!"
+# endif
+static const char * const a_apszWinProgramFilesVars[] =
+{
+ "ProgramFiles",
+# ifdef RT_ARCH_AMD64
+ "ProgramFiles(x86)",
+# endif
+};
+RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllProgramFiles, a_apszWinProgramFilesVars, RTPATH_MAX);
+static const char * const a_apszWinCommonProgramFilesVars[] =
+{
+ "CommonProgramFiles",
+# ifdef RT_ARCH_AMD64
+ "CommonProgramFiles(x86)",
+# endif
+};
+RTPATHMATCHVAR_MULTIPLE_ENVVARS(WinAllCommonProgramFiles, a_apszWinCommonProgramFilesVars, RTPATH_MAX);
+#endif
+
+
+/**
+ * @interface_method_impl{RTPATHMATCHVAR,pfnQuery, Enumerates the PATH}
+ */
+static DECLCALLBACK(int) rtPathVarQuery_Path(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
+ PRTPATHMATCHCACHE pCache)
+{
+ RT_NOREF_PV(pCache);
+
+ /*
+ * Query the PATH value.
+ */
+/** @todo cache this in pCache with iItem and offset. */
+ char *pszPathFree = NULL;
+ char *pszPath = pszBuf;
+ size_t cchActual;
+ const char *pszVarNm = "PATH";
+ int rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPath, cbBuf, &cchActual);
+#ifdef RT_OS_WINDOWS
+ if (rc == VERR_ENV_VAR_NOT_FOUND)
+ rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm = "Path", pszPath, cbBuf, &cchActual);
+#endif
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ for (uint32_t iTry = 0; iTry < 10; iTry++)
+ {
+ size_t cbPathBuf = RT_ALIGN_Z(cchActual + 1 + 64 * iTry, 64);
+ pszPathFree = (char *)RTMemTmpAlloc(cbPathBuf);
+ rc = RTEnvGetEx(RTENV_DEFAULT, pszVarNm, pszPathFree, cbPathBuf, &cchActual);
+ if (RT_SUCCESS(rc))
+ break;
+ RTMemTmpFree(pszPathFree);
+ AssertReturn(cchActual >= cbPathBuf, VERR_INTERNAL_ERROR_3);
+ }
+ pszPath = pszPathFree;
+ }
+
+ /*
+ * Spool forward to the given PATH item.
+ */
+ rc = VERR_EOF;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ const char chSep = ';';
+#else
+ const char chSep = ':';
+#endif
+ while (*pszPath != '\0')
+ {
+ char *pchSep = strchr(pszPath, chSep);
+
+ /* We ignore empty strings, which is probably not entirely correct,
+ but works better on DOS based system with many entries added
+ without checking whether there is a trailing separator or not.
+ Thus, the current directory is only searched if a '.' is present
+ in the PATH. */
+ if (pchSep == pszPath)
+ pszPath++;
+ else if (iItem > 0)
+ {
+ /* If we didn't find a separator, the item doesn't exists. Quit. */
+ if (!pchSep)
+ break;
+
+ pszPath = pchSep + 1;
+ iItem--;
+ }
+ else
+ {
+ /* We've reached the item we wanted. */
+ size_t cchComp = pchSep ? pchSep - pszPath : strlen(pszPath);
+ if (cchComp < cbBuf)
+ {
+ if (pszBuf != pszPath)
+ memmove(pszBuf, pszPath, cchComp);
+ pszBuf[cchComp] = '\0';
+ rc = pchSep ? VINF_SUCCESS : VINF_EOF;
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ *pcchValue = cchComp;
+ break;
+ }
+ }
+
+ if (pszPathFree)
+ RTMemTmpFree(pszPathFree);
+ return rc;
+}
+
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+/**
+ * @interface_method_impl{RTPATHMATCHVAR,pfnQuery,
+ * The system drive letter + colon.}.
+ */
+static DECLCALLBACK(int) rtPathVarQuery_DosSystemDrive(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
+ PRTPATHMATCHCACHE pCache)
+{
+ RT_NOREF_PV(pCache);
+
+ if (iItem == 0)
+ {
+ AssertReturn(cbBuf >= 3, VERR_BUFFER_OVERFLOW);
+
+# ifdef RT_OS_WINDOWS
+ /* Since this is used at the start of a pattern, we assume
+ we've got more than enough buffer space. */
+ AssertReturn(g_pfnGetSystemWindowsDirectoryW, VERR_SYMBOL_NOT_FOUND);
+ PRTUTF16 pwszTmp = (PRTUTF16)pszBuf;
+ UINT cch = g_pfnGetSystemWindowsDirectoryW(pwszTmp, (UINT)(cbBuf / sizeof(WCHAR)));
+ if (cch >= 2)
+ {
+ RTUTF16 wcDrive = pwszTmp[0];
+ if ( RT_C_IS_ALPHA(wcDrive)
+ && pwszTmp[1] == ':')
+ {
+ pszBuf[0] = wcDrive;
+ pszBuf[1] = ':';
+ pszBuf[2] = '\0';
+ *pcchValue = 2;
+ return VINF_EOF;
+ }
+ }
+# else
+ ULONG ulDrive = ~(ULONG)0;
+ APIRET rc = DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE, &ulDrive, sizeof(ulDrive));
+ ulDrive--; /* 1 = 'A' */
+ if ( rc == NO_ERROR
+ && ulDrive <= (ULONG)'Z')
+ {
+ pszBuf[0] = (char)ulDrive + 'A';
+ pszBuf[1] = ':';
+ pszBuf[2] = '\0';
+ *pcchValue = 2;
+ return VINF_EOF;
+ }
+# endif
+ return VERR_INTERNAL_ERROR_4;
+ }
+ return VERR_EOF;
+}
+#endif
+
+
+#ifdef RT_OS_WINDOWS
+/**
+ * @interface_method_impl{RTPATHMATCHVAR,pfnQuery,
+ * The system root directory (C:\Windows).}.
+ */
+static DECLCALLBACK(int) rtPathVarQuery_WinSystemRoot(uint32_t iItem, char *pszBuf, size_t cbBuf, size_t *pcchValue,
+ PRTPATHMATCHCACHE pCache)
+{
+ RT_NOREF_PV(pCache);
+
+ if (iItem == 0)
+ {
+ Assert(pszBuf); Assert(cbBuf);
+ AssertReturn(g_pfnGetSystemWindowsDirectoryW, VERR_SYMBOL_NOT_FOUND);
+ RTUTF16 wszSystemRoot[MAX_PATH];
+ UINT cchSystemRoot = g_pfnGetSystemWindowsDirectoryW(wszSystemRoot, MAX_PATH);
+ if (cchSystemRoot > 0)
+ return RTUtf16ToUtf8Ex(wszSystemRoot, cchSystemRoot, &pszBuf, cbBuf, pcchValue);
+ return RTErrConvertFromWin32(GetLastError());
+ }
+ return VERR_EOF;
+}
+#endif
+
+#undef RTPATHMATCHVAR_SIMPLE
+#undef RTPATHMATCHVAR_SIMPLE_ENVVAR
+#undef RTPATHMATCHVAR_DOUBLE_ENVVAR
+
+/**
+ * Variables.
+ */
+static RTPATHMATCHVAR const g_aVariables[] =
+{
+ { RT_STR_TUPLE("Arch"), false, rtPathVarQuery_Arch, rtPathVarMatch_Arch },
+ { RT_STR_TUPLE("Bits"), false, rtPathVarQuery_Bits, rtPathVarMatch_Bits },
+ { RT_STR_TUPLE("Path"), true, rtPathVarQuery_Path, NULL },
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ { RT_STR_TUPLE("SystemDrive"), true, rtPathVarQuery_DosSystemDrive, NULL },
+#endif
+#ifdef RT_OS_WINDOWS
+ { RT_STR_TUPLE("SystemRoot"), true, rtPathVarQuery_WinSystemRoot, NULL },
+ { RT_STR_TUPLE("AppData"), true, rtPathVarQuery_WinAppData, rtPathVarMatch_WinAppData },
+ { RT_STR_TUPLE("ProgramData"), true, rtPathVarQuery_WinProgramData, rtPathVarMatch_WinProgramData },
+ { RT_STR_TUPLE("ProgramFiles"), true, rtPathVarQuery_WinProgramFiles, rtPathVarMatch_WinProgramFiles },
+ { RT_STR_TUPLE("OtherProgramFiles"), true, rtPathVarQuery_WinOtherProgramFiles, rtPathVarMatch_WinOtherProgramFiles },
+ { RT_STR_TUPLE("AllProgramFiles"), true, rtPathVarQuery_WinAllProgramFiles, rtPathVarMatch_WinAllProgramFiles },
+ { RT_STR_TUPLE("CommonProgramFiles"), true, rtPathVarQuery_WinCommonProgramFiles, rtPathVarMatch_WinCommonProgramFiles },
+ { RT_STR_TUPLE("OtherCommonProgramFiles"), true, rtPathVarQuery_WinOtherCommonProgramFiles, rtPathVarMatch_WinOtherCommonProgramFiles },
+ { RT_STR_TUPLE("AllCommonProgramFiles"), true, rtPathVarQuery_WinAllCommonProgramFiles, rtPathVarMatch_WinAllCommonProgramFiles },
+#endif
+};
+
+
+
+/**
+ * Handles a complicated set.
+ *
+ * A complicated set is either using ranges, character classes or code points
+ * outside the ASCII-7 range.
+ *
+ * @returns VINF_SUCCESS or VERR_MISMATCH. May also return UTF-8 decoding
+ * errors as well as VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED.
+ *
+ * @param ucInput The input code point to match with.
+ * @param pchSet The start of the set specification (after caret).
+ * @param cchSet The length of the set specification.
+ */
+static int rtPathMatchExecExtendedSet(RTUNICP ucInput, const char *pchSet, size_t cchSet)
+{
+ while (cchSet > 0)
+ {
+ RTUNICP ucSet;
+ int rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Check for character class, collating symbol and equvalence class.
+ */
+ if (ucSet == '[' && cchSet > 0)
+ {
+ char chNext = *pchSet;
+ if (chNext == ':')
+ {
+#define CHECK_CHAR_CLASS(a_szClassNm, a_BoolTestExpr) \
+ if ( cchSet >= sizeof(a_szClassNm) \
+ && memcmp(pchSet, a_szClassNm "]", sizeof(a_szClassNm)) == 0) \
+ { \
+ if (a_BoolTestExpr) \
+ return VINF_SUCCESS; \
+ pchSet += sizeof(a_szClassNm); \
+ cchSet -= sizeof(a_szClassNm); \
+ continue; \
+ } do { } while (0)
+
+ CHECK_CHAR_CLASS(":alpha:", RTUniCpIsAlphabetic(ucInput));
+ CHECK_CHAR_CLASS(":alnum:", RTUniCpIsAlphabetic(ucInput) || RTUniCpIsDecDigit(ucInput)); /** @todo figure what's correct here and fix uni.h */
+ CHECK_CHAR_CLASS(":blank:", ucInput == ' ' || ucInput == '\t');
+ CHECK_CHAR_CLASS(":cntrl:", ucInput < 31 || ucInput == 127);
+ CHECK_CHAR_CLASS(":digit:", RTUniCpIsDecDigit(ucInput));
+ CHECK_CHAR_CLASS(":lower:", RTUniCpIsLower(ucInput));
+ CHECK_CHAR_CLASS(":print:", RTUniCpIsAlphabetic(ucInput) || (RT_C_IS_PRINT(ucInput) && ucInput < 127)); /** @todo fixme*/
+ CHECK_CHAR_CLASS(":punct:", RT_C_IS_PRINT(ucInput) && ucInput < 127); /** @todo fixme*/
+ CHECK_CHAR_CLASS(":space:", RTUniCpIsSpace(ucInput));
+ CHECK_CHAR_CLASS(":upper:", RTUniCpIsUpper(ucInput));
+ CHECK_CHAR_CLASS(":xdigit:", RTUniCpIsHexDigit(ucInput));
+ AssertMsgFailedReturn(("Unknown or malformed char class: '%.*s'\n", cchSet + 1, pchSet - 1),
+ VERR_PATH_GLOB_UNKNOWN_CHAR_CLASS);
+#undef CHECK_CHAR_CLASS
+ }
+ /** @todo implement collating symbol and equvalence class. */
+ else if (chNext == '=' || chNext == '.')
+ AssertFailedReturn(VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
+ }
+
+ /*
+ * Check for range (leading or final dash does not constitute a range).
+ */
+ if (cchSet > 1 && *pchSet == '-')
+ {
+ pchSet++; /* skip dash */
+ cchSet--;
+
+ RTUNICP ucSet2;
+ rc = RTStrGetCpNEx(&pchSet, &cchSet, &ucSet2);
+ AssertRCReturn(rc, rc);
+ Assert(ucSet < ucSet2);
+ if (ucInput >= ucSet && ucInput <= ucSet2)
+ return VINF_SUCCESS;
+ }
+ /*
+ * Single char comparison.
+ */
+ else if (ucInput == ucSet)
+ return VINF_SUCCESS;
+ }
+ return VERR_MISMATCH;
+}
+
+
+/**
+ * Variable matching fallback using the query function.
+ *
+ * This must not be inlined as it consuming a lot of stack! Which is why it's
+ * placed a couple of functions away from the recursive rtPathExecMatch.
+ *
+ * @returns VINF_SUCCESS or VERR_MISMATCH.
+ * @param pchInput The current input position.
+ * @param cchInput The amount of input left..
+ * @param idxVar The variable table index.
+ * @param fIgnoreCase Whether to ignore case when comparing.
+ * @param pcchMatched Where to return how much we actually matched up.
+ * @param pCache Pointer to the path matching cache.
+ */
+DECL_NO_INLINE(static, int) rtPathMatchExecVariableFallback(const char *pchInput, size_t cchInput, uint16_t idxVar,
+ bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
+{
+ for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
+ {
+ char szValue[RTPATH_MAX];
+ size_t cchValue;
+ int rc = g_aVariables[idxVar].pfnQuery(iItem, szValue, sizeof(szValue), &cchValue, pCache);
+ if (RT_SUCCESS(rc))
+ {
+ if (cchValue <= cchInput)
+ {
+ if ( !fIgnoreCase
+ ? memcmp(pchInput, szValue, cchValue) == 0
+ : RTStrNICmp(pchInput, szValue, cchValue) == 0)
+ {
+ *pcchMatched = cchValue;
+ return VINF_SUCCESS;
+ }
+ }
+ if (rc == VINF_EOF)
+ return VERR_MISMATCH;
+ }
+ else if (rc == VERR_EOF)
+ return VERR_MISMATCH;
+ else
+ Assert(rc == VERR_BUFFER_OVERFLOW || rc == VERR_TRY_AGAIN);
+ }
+ AssertFailed();
+ return VERR_MISMATCH;
+}
+
+
+/**
+ * Variable matching worker.
+ *
+ * @returns VINF_SUCCESS or VERR_MISMATCH.
+ * @param pchInput The current input position.
+ * @param cchInput The amount of input left..
+ * @param idxVar The variable table index.
+ * @param fIgnoreCase Whether to ignore case when comparing.
+ * @param pcchMatched Where to return how much we actually matched up.
+ * @param pCache Pointer to the path matching cache.
+ */
+static int rtPathMatchExecVariable(const char *pchInput, size_t cchInput, uint16_t idxVar,
+ bool fIgnoreCase, size_t *pcchMatched, PRTPATHMATCHCACHE pCache)
+{
+ Assert(idxVar < RT_ELEMENTS(g_aVariables));
+ if (g_aVariables[idxVar].pfnMatch)
+ return g_aVariables[idxVar].pfnMatch(pchInput, cchInput, fIgnoreCase, pcchMatched);
+ return rtPathMatchExecVariableFallback(pchInput, cchInput, idxVar, fIgnoreCase, pcchMatched, pCache);
+}
+
+
+/**
+ * Variable matching worker.
+ *
+ * @returns VINF_SUCCESS or VERR_MISMATCH.
+ * @param pchInput The current input position.
+ * @param cchInput The amount of input left..
+ * @param pProg The first matching program instruction.
+ * @param pCache Pointer to the path matching cache.
+ */
+static int rtPathMatchExec(const char *pchInput, size_t cchInput, PCRTPATHMATCHCORE pProg, PRTPATHMATCHCACHE pCache)
+{
+ for (;;)
+ {
+ switch (pProg->enmOpCode)
+ {
+ case RTPATHMATCHOP_RETURN_MATCH_IF_AT_END:
+ return cchInput == 0 ? VINF_SUCCESS : VERR_MISMATCH;
+
+ case RTPATHMATCHOP_RETURN_MATCH:
+ return VINF_SUCCESS;
+
+ case RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT:
+ if ( cchInput > 2
+ || cchInput < 1
+ || pchInput[0] != '.'
+ || (cchInput == 2 && pchInput[1] != '.') )
+ return VINF_SUCCESS;
+ return VERR_MISMATCH;
+
+ case RTPATHMATCHOP_STRCMP:
+ if (pProg->cch > cchInput)
+ return VERR_MISMATCH;
+ if (memcmp(pchInput, pProg->pch, pProg->cch) != 0)
+ return VERR_MISMATCH;
+ cchInput -= pProg->cch;
+ pchInput += pProg->cch;
+ break;
+
+ case RTPATHMATCHOP_STRICMP:
+ if (pProg->cch > cchInput)
+ return VERR_MISMATCH;
+ if (RTStrNICmp(pchInput, pProg->pch, pProg->cch) != 0)
+ return VERR_MISMATCH;
+ cchInput -= pProg->cch;
+ pchInput += pProg->cch;
+ break;
+
+ case RTPATHMATCHOP_SKIP_ONE_CODEPOINT:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInputIgnore;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
+ AssertRCReturn(rc, rc);
+ break;
+ }
+
+ case RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS:
+ {
+ uint16_t cCpsLeft = pProg->cch;
+ Assert(cCpsLeft > 1);
+ if (cCpsLeft > cchInput)
+ return VERR_MISMATCH;
+ while (cCpsLeft-- > 0)
+ {
+ RTUNICP ucInputIgnore;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInputIgnore);
+ if (RT_FAILURE(rc))
+ return rc == VERR_END_OF_STRING ? VERR_MISMATCH : rc;
+ }
+ break;
+ }
+
+ case RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInput;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
+ AssertRCReturn(rc, rc);
+ if (ucInput >= 0x80)
+ return VERR_MISMATCH;
+ if (memchr(pProg->pch, (char)ucInput, pProg->cch) == NULL)
+ return VERR_MISMATCH;
+ break;
+ }
+
+ case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInput;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
+ AssertRCReturn(rc, rc);
+ if (ucInput >= 0x80)
+ break;
+ if (memchr(pProg->pch, (char)ucInput, pProg->cch) != NULL)
+ return VERR_MISMATCH;
+ break;
+ }
+
+ case RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInput;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
+ AssertRCReturn(rc, rc);
+ rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
+ if (rc == VINF_SUCCESS)
+ break;
+ return rc;
+ }
+
+ case RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED:
+ {
+ if (cchInput == 0)
+ return VERR_MISMATCH;
+ RTUNICP ucInput;
+ int rc = RTStrGetCpNEx(&pchInput, &cchInput, &ucInput);
+ AssertRCReturn(rc, rc);
+ rc = rtPathMatchExecExtendedSet(ucInput, pProg->pch, pProg->cch);
+ if (rc == VERR_MISMATCH)
+ break;
+ if (rc == VINF_SUCCESS)
+ rc = VERR_MISMATCH;
+ return rc;
+ }
+
+ case RTPATHMATCHOP_VARIABLE_VALUE_CMP:
+ case RTPATHMATCHOP_VARIABLE_VALUE_ICMP:
+ {
+ size_t cchMatched = 0;
+ int rc = rtPathMatchExecVariable(pchInput, cchInput, pProg->uOp2,
+ pProg->enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP, &cchMatched, pCache);
+ if (rc == VINF_SUCCESS)
+ {
+ pchInput += cchMatched;
+ cchInput -= cchMatched;
+ break;
+ }
+ return rc;
+ }
+
+ /*
+ * This is the expensive one. It always completes the program.
+ */
+ case RTPATHMATCHOP_ZERO_OR_MORE:
+ {
+ if (cchInput < pProg->cch)
+ return VERR_MISMATCH;
+ size_t cchMatched = cchInput - pProg->cch;
+ do
+ {
+ int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
+ if (RT_SUCCESS(rc))
+ return rc;
+ } while (cchMatched-- > 0);
+ return VERR_MISMATCH;
+ }
+
+ /*
+ * Variant of the above that doesn't match '.' and '..' entries.
+ */
+ case RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT:
+ {
+ if (cchInput < pProg->cch)
+ return VERR_MISMATCH;
+ if ( cchInput <= 2
+ && cchInput > 0
+ && pchInput[0] == '.'
+ && (cchInput == 1 || pchInput[1] == '.') )
+ return VERR_MISMATCH;
+ size_t cchMatched = cchInput - pProg->cch;
+ do
+ {
+ int rc = rtPathMatchExec(&pchInput[cchMatched], cchInput - cchMatched, pProg + 1, pCache);
+ if (RT_SUCCESS(rc))
+ return rc;
+ } while (cchMatched-- > 0);
+ return VERR_MISMATCH;
+ }
+
+ default:
+ AssertMsgFailedReturn(("enmOpCode=%d\n", pProg->enmOpCode), VERR_INTERNAL_ERROR_3);
+ }
+
+ pProg++;
+ }
+}
+
+
+
+
+/**
+ * Compiles a path matching program.
+ *
+ * @returns IPRT status code.
+ * @param pchPattern The pattern to compile.
+ * @param cchPattern The length of the pattern.
+ * @param fIgnoreCase Whether to ignore case or not when doing the
+ * actual matching later on.
+ * @param pAllocator Pointer to the instruction allocator & result
+ * array. The compiled "program" starts at
+ * PRTPATHMATCHALLOC::paInstructions[PRTPATHMATCHALLOC::iNext]
+ * (input iNext value).
+ *
+ * @todo Expose this matching code and also use it for RTDirOpenFiltered
+ */
+static int rtPathMatchCompile(const char *pchPattern, size_t cchPattern, bool fIgnoreCase, PRTPATHMATCHALLOC pAllocator)
+{
+ /** @todo PORTME: big endian. */
+ static const uint8_t s_bmMetaChars[256/8] =
+ {
+ 0x00, 0x00, 0x00, 0x00, /* 0 thru 31 */
+ 0x10, 0x04, 0x00, 0x80, /* 32 thru 63 */
+ 0x00, 0x00, 0x00, 0x08, /* 64 thru 95 */
+ 0x00, 0x00, 0x00, 0x00, /* 96 thru 127 */
+ /* UTF-8 multibyte: */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ };
+ Assert(ASMBitTest(s_bmMetaChars, '$')); AssertCompile('$' == 0x24 /*36*/);
+ Assert(ASMBitTest(s_bmMetaChars, '*')); AssertCompile('*' == 0x2a /*42*/);
+ Assert(ASMBitTest(s_bmMetaChars, '?')); AssertCompile('?' == 0x3f /*63*/);
+ Assert(ASMBitTest(s_bmMetaChars, '[')); AssertCompile('[' == 0x5b /*91*/);
+
+ /*
+ * For checking for the first instruction.
+ */
+ uint16_t const iFirst = pAllocator->iNext;
+
+ /*
+ * This is for tracking zero-or-more instructions and for calculating
+ * the minimum amount of input required for it to be considered.
+ */
+ uint16_t aiZeroOrMore[RTPATHMATCH_MAX_ZERO_OR_MORE];
+ uint8_t cZeroOrMore = 0;
+ size_t offInput = 0;
+
+ /*
+ * Loop thru the pattern and translate it into string matching instructions.
+ */
+ for (;;)
+ {
+ /*
+ * Allocate the next instruction.
+ */
+ if (pAllocator->iNext >= pAllocator->cAllocated)
+ {
+ uint32_t cNew = pAllocator->cAllocated ? pAllocator->cAllocated * 2 : 2;
+ void *pvNew = RTMemRealloc(pAllocator->paInstructions, cNew * sizeof(pAllocator->paInstructions[0]));
+ AssertReturn(pvNew, VERR_NO_MEMORY);
+ pAllocator->paInstructions = (PRTPATHMATCHCORE)pvNew;
+ pAllocator->cAllocated = cNew;
+ }
+ PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[pAllocator->iNext++];
+ pInstr->pch = pchPattern;
+ pInstr->cch = 0;
+ pInstr->uOp2 = 0;
+
+ /*
+ * Special case: End of pattern.
+ */
+ if (!cchPattern)
+ {
+ pInstr->enmOpCode = RTPATHMATCHOP_RETURN_MATCH_IF_AT_END;
+ break;
+ }
+
+ /*
+ * Parse the next bit of the pattern.
+ */
+ char ch = *pchPattern;
+ if (ASMBitTest(s_bmMetaChars, (uint8_t)ch))
+ {
+ /*
+ * Zero or more characters wildcard.
+ */
+ if (ch == '*')
+ {
+ /* Skip extra asterisks. */
+ do
+ {
+ cchPattern--;
+ pchPattern++;
+ } while (cchPattern > 0 && *pchPattern == '*');
+
+ /* There is a special optimization for trailing '*'. */
+ pInstr->cch = 1;
+ if (cchPattern == 0)
+ {
+ pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
+ ? RTPATHMATCHOP_RETURN_MATCH_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_RETURN_MATCH;
+ break;
+ }
+
+ pInstr->enmOpCode = iFirst + 1U == pAllocator->iNext
+ ? RTPATHMATCHOP_ZERO_OR_MORE_EXCEPT_DOT_AND_DOTDOT : RTPATHMATCHOP_ZERO_OR_MORE;
+ pInstr->uOp2 = (uint16_t)offInput;
+ AssertReturn(cZeroOrMore < RT_ELEMENTS(aiZeroOrMore), VERR_OUT_OF_RANGE);
+ aiZeroOrMore[cZeroOrMore] = (uint16_t)(pInstr - pAllocator->paInstructions);
+
+ /* cchInput unchanged, zero-or-more matches. */
+ continue;
+ }
+
+ /*
+ * Single character wildcard.
+ */
+ if (ch == '?')
+ {
+ /* Count them if more. */
+ uint16_t cchQms = 1;
+ while (cchQms < cchPattern && pchPattern[cchQms] == '?')
+ cchQms++;
+
+ pInstr->cch = cchQms;
+ pInstr->enmOpCode = cchQms == 1 ? RTPATHMATCHOP_SKIP_ONE_CODEPOINT : RTPATHMATCHOP_SKIP_MULTIPLE_CODEPOINTS;
+
+ cchPattern -= cchQms;
+ pchPattern += cchQms;
+ offInput += cchQms;
+ continue;
+ }
+
+ /*
+ * Character in set.
+ *
+ * Note that we skip the first char in the set as that is the only place
+ * ']' can be placed if one desires to explicitly include it in the set.
+ * To make life a bit more interesting, [:class:] is allowed inside the
+ * set, so we have to do the counting game to find the end.
+ */
+ if (ch == '[')
+ {
+ if ( cchPattern > 2
+ && (const char *)memchr(pchPattern + 2, ']', cchPattern) != NULL)
+ {
+
+ /* Check for not-in. */
+ bool fInverted = false;
+ size_t offStart = 1;
+ if (pchPattern[offStart] == '^')
+ {
+ fInverted = true;
+ offStart++;
+ }
+
+ /* Special case for ']' as the first char, it doesn't indicate closing then. */
+ size_t off = offStart;
+ if (pchPattern[off] == ']')
+ off++;
+
+ bool fExtended = false;
+ while (off < cchPattern)
+ {
+ ch = pchPattern[off++];
+ if (ch == '[')
+ {
+ if (off < cchPattern)
+ {
+ char chOpen = pchPattern[off];
+ if ( chOpen == ':'
+ || chOpen == '='
+ || chOpen == '.')
+ {
+ off++;
+ const char *pchFound = (const char *)memchr(&pchPattern[off], ']', cchPattern - off);
+ if ( pchFound
+ && pchFound[-1] == chOpen)
+ {
+ fExtended = true;
+ off = pchFound - pchPattern + 1;
+ }
+ else
+ AssertFailed();
+ }
+ }
+ }
+ /* Check for closing. */
+ else if (ch == ']')
+ break;
+ /* Check for range expression, promote to extended if this happens. */
+ else if ( ch == '-'
+ && off != offStart + 1
+ && off < cchPattern
+ && pchPattern[off] != ']')
+ fExtended = true;
+ /* UTF-8 multibyte chars forces us to use the extended version too. */
+ else if ((uint8_t)ch >= 0x80)
+ fExtended = true;
+ }
+
+ if (ch == ']')
+ {
+ pInstr->pch = &pchPattern[offStart];
+ pInstr->cch = (uint16_t)(off - offStart - 1);
+ if (!fExtended)
+ pInstr->enmOpCode = !fInverted
+ ? RTPATHMATCHOP_CODEPOINT_IN_SET_ASCII7 : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_ASCII7;
+ else
+ pInstr->enmOpCode = !fInverted
+ ? RTPATHMATCHOP_CODEPOINT_IN_SET_EXTENDED
+ : RTPATHMATCHOP_CODEPOINT_NOT_IN_SET_EXTENDED;
+ pchPattern += off;
+ cchPattern -= off;
+ offInput += 1;
+ continue;
+ }
+
+ /* else: invalid, treat it as */
+ AssertFailed();
+ }
+ }
+ /*
+ * Variable matching.
+ */
+ else if (ch == '$')
+ {
+ const char *pchFound;
+ if ( cchPattern > 3
+ && pchPattern[1] == '{'
+ && (pchFound = (const char *)memchr(pchPattern + 2, '}', cchPattern)) != NULL
+ && pchFound != &pchPattern[2])
+ {
+ /* skip to the variable name. */
+ pchPattern += 2;
+ cchPattern -= 2;
+ size_t cchVarNm = pchFound - pchPattern;
+
+ /* Look it up. */
+ uint32_t iVar;
+ for (iVar = 0; iVar < RT_ELEMENTS(g_aVariables); iVar++)
+ if ( g_aVariables[iVar].cchName == cchVarNm
+ && memcmp(g_aVariables[iVar].pszName, pchPattern, cchVarNm) == 0)
+ break;
+ if (iVar < RT_ELEMENTS(g_aVariables))
+ {
+ pInstr->uOp2 = (uint16_t)iVar;
+ pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_VARIABLE_VALUE_CMP : RTPATHMATCHOP_VARIABLE_VALUE_ICMP;
+ pInstr->pch = pchPattern; /* not necessary */
+ pInstr->cch = (uint16_t)cchPattern; /* ditto */
+ pchPattern += cchVarNm + 1;
+ cchPattern -= cchVarNm + 1;
+ AssertMsgReturn(!g_aVariables[iVar].fFirstOnly || iFirst + 1U == pAllocator->iNext,
+ ("Glob variable '%s' should be first\n", g_aVariables[iVar].pszName),
+ VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
+ /* cchInput unchanged, value can be empty. */
+ continue;
+ }
+ AssertMsgFailedReturn(("Unknown path matching variable '%.*s'\n", cchVarNm, pchPattern),
+ VERR_PATH_MATCH_UNKNOWN_VARIABLE);
+ }
+ }
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
+ }
+
+ /*
+ * Plain text. Look for the next meta char.
+ */
+ uint32_t cchPlain = 1;
+ while (cchPlain < cchPattern)
+ {
+ ch = pchPattern[cchPlain];
+ if (!ASMBitTest(s_bmMetaChars, (uint8_t)ch))
+ { /* probable */ }
+ else if ( ch == '?'
+ || ch == '*')
+ break;
+ else if (ch == '$')
+ {
+ const char *pchFound;
+ if ( cchPattern > cchPlain + 3
+ && pchPattern[cchPlain + 1] == '{'
+ && (pchFound = (const char *)memchr(&pchPattern[cchPlain + 2], '}', cchPattern - cchPlain - 2)) != NULL
+ && pchFound != &pchPattern[cchPlain + 2])
+ break;
+ }
+ else if (ch == '[')
+ {
+ /* We don't put a lot of effort into getting this 100% right here,
+ no point it complicating things for malformed expressions. */
+ if ( cchPattern > cchPlain + 2
+ && memchr(&pchPattern[cchPlain + 2], ']', cchPattern - cchPlain - 1) != NULL)
+ break;
+ }
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR_2); /* broken bitmap / compiler codeset */
+ cchPlain++;
+ }
+ pInstr->enmOpCode = !fIgnoreCase ? RTPATHMATCHOP_STRCMP : RTPATHMATCHOP_STRICMP;
+ pInstr->cch = cchPlain;
+ Assert(pInstr->pch == pchPattern);
+ Assert(pInstr->uOp2 == 0);
+ pchPattern += cchPlain;
+ cchPattern -= cchPlain;
+ offInput += cchPlain;
+ }
+
+ /*
+ * Optimize zero-or-more matching.
+ */
+ while (cZeroOrMore-- > 0)
+ {
+ PRTPATHMATCHCORE pInstr = &pAllocator->paInstructions[aiZeroOrMore[cZeroOrMore]];
+ pInstr->uOp2 = (uint16_t)(offInput - pInstr->uOp2);
+ }
+
+ /** @todo It's possible to use offInput to inject a instruction for checking
+ * minimum input length at the start of the program. Not sure it's
+ * worth it though, unless it's long a complicated expression... */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses the glob pattern.
+ *
+ * This compiles filename matching programs for each component and determins the
+ * optimal search strategy for them.
+ *
+ * @returns IPRT status code.
+ * @param pGlob The glob instance data.
+ * @param pszPattern The pattern to parse.
+ * @param pParsed The RTPathParse output for the pattern.
+ * @param fFlags The glob flags (same as pGlob->fFlags).
+ */
+static int rtPathGlobParse(PRTPATHGLOB pGlob, const char *pszPattern, PRTPATHPARSED pParsed, uint32_t fFlags)
+{
+ AssertReturn(pParsed->cComps > 0, VERR_INVALID_PARAMETER); /* shouldn't happen */
+ uint32_t iComp = 0;
+
+ /*
+ * If we've got a rootspec, mark it as plain. On platforms with
+ * drive letter and/or UNC we don't allow wildcards or such in
+ * the drive letter spec or UNC server name. (At least not yet.)
+ */
+ if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
+ {
+ AssertReturn(pParsed->aComps[0].cch < sizeof(pGlob->szPath) - 1, VERR_FILENAME_TOO_LONG);
+ memcpy(pGlob->szPath, &pszPattern[pParsed->aComps[0].off], pParsed->aComps[0].cch);
+ pGlob->offFirstPath = pParsed->aComps[0].cch;
+ pGlob->iFirstComp = iComp = 1;
+ }
+ else
+ {
+ const char * const pszComp = &pszPattern[pParsed->aComps[0].off];
+
+ /*
+ * The tilde is only applicable to the first component, expand it
+ * immediately.
+ */
+ if ( *pszComp == '~'
+ && !(fFlags & RTPATHGLOB_F_NO_TILDE))
+ {
+ if (pParsed->aComps[0].cch == 1)
+ {
+ int rc = RTPathUserHome(pGlob->szPath, sizeof(pGlob->szPath) - 1);
+ AssertRCReturn(rc, rc);
+ }
+ else
+ AssertMsgFailedReturn(("'%.*s' is not supported yet\n", pszComp, pParsed->aComps[0].cch),
+ VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED);
+ pGlob->offFirstPath = (uint16_t)RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
+ pGlob->iFirstComp = iComp = 1;
+ }
+ }
+
+ /*
+ * Process the other components.
+ */
+ bool fStarStar = false;
+ for (; iComp < pParsed->cComps; iComp++)
+ {
+ const char *pszComp = &pszPattern[pParsed->aComps[iComp].off];
+ uint16_t cchComp = pParsed->aComps[iComp].cch;
+ Assert(pGlob->aComps[iComp].fNormal == false);
+
+ pGlob->aComps[iComp].fDir = iComp + 1 < pParsed->cComps || (fFlags & RTPATHGLOB_F_ONLY_DIRS);
+ if ( cchComp != 2
+ || pszComp[0] != '*'
+ || pszComp[1] != '*'
+ || (fFlags & RTPATHGLOB_F_NO_STARSTAR) )
+ {
+ /* Compile the pattern. */
+ uint16_t const iMatchProg = pGlob->MatchInstrAlloc.iNext;
+ pGlob->aComps[iComp].iMatchProg = iMatchProg;
+ int rc = rtPathMatchCompile(pszComp, cchComp, RT_BOOL(fFlags & RTPATHGLOB_F_IGNORE_CASE),
+ &pGlob->MatchInstrAlloc);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Check for plain text as well as full variable matching (not applicable after '**'). */
+ uint16_t const cInstructions = pGlob->MatchInstrAlloc.iNext - iMatchProg;
+ if ( cInstructions == 2
+ && !fStarStar
+ && pGlob->MatchInstrAlloc.paInstructions[iMatchProg + 1].enmOpCode == RTPATHMATCHOP_RETURN_MATCH_IF_AT_END)
+ {
+ if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRCMP
+ || pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_STRICMP)
+ pGlob->aComps[iComp].fPlain = true;
+ else if ( pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_CMP
+ || pGlob->MatchInstrAlloc.paInstructions[iMatchProg].enmOpCode == RTPATHMATCHOP_VARIABLE_VALUE_ICMP)
+ {
+ pGlob->aComps[iComp].fExpVariable = true;
+ AssertMsgReturn( iComp == 0
+ || !g_aVariables[pGlob->MatchInstrAlloc.paInstructions[iMatchProg].uOp2].fFirstOnly,
+ ("Glob variable '%.*s' can only be used as the path component.\n", cchComp, pszComp),
+ VERR_PATH_MATCH_VARIABLE_MUST_BE_FIRST);
+ }
+ else
+ pGlob->aComps[iComp].fNormal = true;
+ }
+ else
+ pGlob->aComps[iComp].fNormal = true;
+ }
+ else
+ {
+ /* Recursive "**" matching. */
+ pGlob->aComps[iComp].fNormal = false;
+ pGlob->aComps[iComp].fStarStar = true;
+ AssertReturn(!fStarStar, VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED); /** @todo implement multiple '**' sequences in a pattern. */
+ fStarStar = true;
+ }
+ }
+ pGlob->aComps[pParsed->cComps - 1].fFinal = true;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * This is for skipping overly long directories entries.
+ *
+ * Since our directory entry buffer can hold filenames of RTPATH_MAX bytes, we
+ * can safely skip filenames that are longer. There are very few file systems
+ * that can actually store filenames longer than 255 bytes at time of coding
+ * (2015-09), and extremely few which can exceed 4096 (RTPATH_MAX) bytes.
+ *
+ * @returns IPRT status code.
+ * @param hDir The directory handle.
+ * @param cbNeeded The required entry size.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobSkipDirEntry(RTDIR hDir, size_t cbNeeded)
+{
+ int rc = VERR_BUFFER_OVERFLOW;
+ cbNeeded = RT_ALIGN_Z(cbNeeded, 16);
+ PRTDIRENTRY pDirEntry = (PRTDIRENTRY)RTMemTmpAlloc(cbNeeded);
+ if (pDirEntry)
+ {
+ rc = RTDirRead(hDir, pDirEntry, &cbNeeded);
+ RTMemTmpFree(pDirEntry);
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a result.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN if we can stop searching.
+ *
+ * @param pGlob The glob instance data.
+ * @param cchPath The number of bytes to add from pGlob->szPath.
+ * @param uType The RTDIRENTRYTYPE value.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobAddResult(PRTPATHGLOB pGlob, size_t cchPath, uint8_t uType)
+{
+ if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
+ {
+ PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + 1]));
+ if (pEntry)
+ {
+ pEntry->uType = uType;
+ pEntry->cchPath = (uint16_t)cchPath;
+ memcpy(pEntry->szPath, pGlob->szPath, cchPath);
+ pEntry->szPath[cchPath] = '\0';
+
+ pEntry->pNext = NULL;
+ *pGlob->ppNext = pEntry;
+ pGlob->ppNext = &pEntry->pNext;
+ pGlob->cResults++;
+
+ if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
+ return VINF_SUCCESS;
+ return VINF_CALLBACK_RETURN;
+ }
+ return VERR_NO_MEMORY;
+ }
+ return VERR_TOO_MUCH_DATA;
+}
+
+
+/**
+ * Adds a result, constructing the path from two string.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN if we can stop searching.
+ *
+ * @param pGlob The glob instance data.
+ * @param cchPath The number of bytes to add from pGlob->szPath.
+ * @param pchName The string (usual filename) to append to the szPath.
+ * @param cchName The length of the string to append.
+ * @param uType The RTDIRENTRYTYPE value.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobAddResult2(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
+ uint8_t uType)
+{
+ if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
+ {
+ PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
+ if (pEntry)
+ {
+ pEntry->uType = uType;
+ pEntry->cchPath = (uint16_t)(cchPath + cchName);
+ memcpy(pEntry->szPath, pGlob->szPath, cchPath);
+ memcpy(&pEntry->szPath[cchPath], pchName, cchName);
+ pEntry->szPath[cchPath + cchName] = '\0';
+
+ pEntry->pNext = NULL;
+ *pGlob->ppNext = pEntry;
+ pGlob->ppNext = &pEntry->pNext;
+ pGlob->cResults++;
+
+ if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
+ return VINF_SUCCESS;
+ return VINF_CALLBACK_RETURN;
+ }
+ return VERR_NO_MEMORY;
+ }
+ return VERR_TOO_MUCH_DATA;
+}
+
+
+/**
+ * Prepares a result, constructing the path from two string.
+ *
+ * The caller must call either rtPathGlobCommitResult or
+ * rtPathGlobRollbackResult to complete the operation.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN if we can stop searching.
+ *
+ * @param pGlob The glob instance data.
+ * @param cchPath The number of bytes to add from pGlob->szPath.
+ * @param pchName The string (usual filename) to append to the szPath.
+ * @param cchName The length of the string to append.
+ * @param uType The RTDIRENTRYTYPE value.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobAlmostAddResult(PRTPATHGLOB pGlob, size_t cchPath, const char *pchName, size_t cchName,
+ uint8_t uType)
+{
+ if (pGlob->cResults < RTPATHGLOB_MAX_RESULTS)
+ {
+ PRTPATHGLOBENTRY pEntry = (PRTPATHGLOBENTRY)RTMemAlloc(RT_UOFFSETOF_DYN(RTPATHGLOBENTRY, szPath[cchPath + cchName + 1]));
+ if (pEntry)
+ {
+ pEntry->uType = uType;
+ pEntry->cchPath = (uint16_t)(cchPath + cchName);
+ memcpy(pEntry->szPath, pGlob->szPath, cchPath);
+ memcpy(&pEntry->szPath[cchPath], pchName, cchName);
+ pEntry->szPath[cchPath + cchName] = '\0';
+
+ pEntry->pNext = NULL;
+ *pGlob->ppNext = pEntry;
+ /* Note! We don't update ppNext here, that is done in rtPathGlobCommitResult. */
+
+ if (!(pGlob->fFlags & RTPATHGLOB_F_FIRST_ONLY))
+ return VINF_SUCCESS;
+ return VINF_CALLBACK_RETURN;
+ }
+ return VERR_NO_MEMORY;
+ }
+ return VERR_TOO_MUCH_DATA;
+}
+
+
+/**
+ * Commits a pending result from rtPathGlobAlmostAddResult.
+ *
+ * @param pGlob The glob instance data.
+ * @param uType The RTDIRENTRYTYPE value.
+ */
+static void rtPathGlobCommitResult(PRTPATHGLOB pGlob, uint8_t uType)
+{
+ PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
+ AssertPtr(pEntry);
+ pEntry->uType = uType;
+ pGlob->ppNext = &pEntry->pNext;
+ pGlob->cResults++;
+}
+
+
+/**
+ * Rolls back a pending result from rtPathGlobAlmostAddResult.
+ *
+ * @param pGlob The glob instance data.
+ */
+static void rtPathGlobRollbackResult(PRTPATHGLOB pGlob)
+{
+ PRTPATHGLOBENTRY pEntry = *pGlob->ppNext;
+ AssertPtr(pEntry);
+ RTMemFree(pEntry);
+ *pGlob->ppNext = NULL;
+}
+
+
+
+/**
+ * Whether to call rtPathGlobExecRecursiveVarExp for the next component.
+ *
+ * @returns true / false.
+ * @param pGlob The glob instance data.
+ * @param offPath The next path offset/length.
+ * @param iComp The next component.
+ */
+DECLINLINE(bool) rtPathGlobExecIsExpVar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ return pGlob->aComps[iComp].fExpVariable
+ && ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
+ || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
+}
+
+/**
+ * Whether to call rtPathGlobExecRecursivePlainText for the next component.
+ *
+ * @returns true / false.
+ * @param pGlob The glob instance data.
+ * @param offPath The next path offset/length.
+ * @param iComp The next component.
+ */
+DECLINLINE(bool) rtPathGlobExecIsPlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ return pGlob->aComps[iComp].fPlain
+ && ( !(pGlob->fFlags & RTPATHGLOB_F_IGNORE_CASE)
+ || (offPath ? !RTFsIsCaseSensitive(pGlob->szPath) : !RTFsIsCaseSensitive(".")) );
+}
+
+
+/**
+ * Helper for rtPathGlobExecRecursiveVarExp and rtPathGlobExecRecursivePlainText
+ * that compares a file mode mask with dir/no-dir wishes of the caller.
+ *
+ * @returns true if match, false if not.
+ * @param pGlob The glob instance data.
+ * @param fMode The file mode (only the type is used).
+ */
+DECLINLINE(bool) rtPathGlobExecIsMatchFinalWithFileMode(PRTPATHGLOB pGlob, RTFMODE fMode)
+{
+ if (!(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)))
+ return true;
+ return RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS) == RTFS_IS_DIRECTORY(fMode);
+}
+
+
+/**
+ * Recursive globbing - star-star mode.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
+ *
+ * @param pGlob The glob instance data.
+ * @param offPath The current path offset/length.
+ * @param iStarStarComp The star-star component index.
+ * @param offStarStarPath The offset of the star-star component in the
+ * pattern path.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveStarStar(PRTPATHGLOB pGlob, size_t offPath, uint32_t iStarStarComp,
+ size_t offStarStarPath)
+{
+ /** @todo implement multi subdir matching. */
+ RT_NOREF_PV(pGlob);
+ RT_NOREF_PV(offPath);
+ RT_NOREF_PV(iStarStarComp);
+ RT_NOREF_PV(offStarStarPath);
+ return VERR_PATH_MATCH_FEATURE_NOT_IMPLEMENTED;
+}
+
+
+
+/**
+ * Recursive globbing - variable expansion optimization.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
+ *
+ * @param pGlob The glob instance data.
+ * @param offPath The current path offset/length.
+ * @param iComp The current component.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveVarExp(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ Assert(iComp < pGlob->pParsed->cComps);
+ Assert(pGlob->szPath[offPath] == '\0');
+ Assert(pGlob->aComps[iComp].fExpVariable);
+ Assert(!pGlob->aComps[iComp].fPlain);
+ Assert(!pGlob->aComps[iComp].fStarStar);
+ Assert(rtPathGlobExecIsExpVar(pGlob, offPath, iComp));
+
+ /*
+ * Fish the variable index out of the first matching instruction.
+ */
+ Assert( pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
+ == RTPATHMATCHOP_VARIABLE_VALUE_CMP
+ || pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
+ == RTPATHMATCHOP_VARIABLE_VALUE_ICMP);
+ uint16_t const iVar = pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].uOp2;
+
+ /*
+ * Enumerate all the variable, giving them the plain text treatment.
+ */
+ for (uint32_t iItem = 0; iItem < RTPATHMATCH_MAX_VAR_ITEMS; iItem++)
+ {
+ size_t cch;
+ int rcVar = g_aVariables[iVar].pfnQuery(iItem, &pGlob->szPath[offPath], sizeof(pGlob->szPath) - offPath, &cch,
+ &pGlob->MatchCache);
+ if (RT_SUCCESS(rcVar))
+ {
+ Assert(pGlob->szPath[offPath + cch] == '\0');
+
+ int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ if (pGlob->aComps[iComp].fFinal)
+ {
+ if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
+ {
+ rc = rtPathGlobAddResult(pGlob, cch,
+ (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ }
+ else if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
+ {
+ Assert(pGlob->aComps[iComp].fDir);
+ cch = RTPathEnsureTrailingSeparator(pGlob->szPath, sizeof(pGlob->szPath));
+ if (cch > 0)
+ {
+ if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
+ rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
+ else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
+ rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
+ else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
+ rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
+ else
+ rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ else
+ pGlob->cPathOverflows++;
+ }
+ }
+ /* else: file doesn't exist or something else is wrong, ignore this. */
+ if (rcVar == VINF_EOF)
+ return VINF_SUCCESS;
+ }
+ else if (rcVar == VERR_EOF)
+ return VINF_SUCCESS;
+ else if (rcVar != VERR_TRY_AGAIN)
+ {
+ Assert(rcVar == VERR_BUFFER_OVERFLOW);
+ pGlob->cPathOverflows++;
+ }
+ }
+ AssertFailedReturn(VINF_SUCCESS); /* Too many items returned, probably buggy query method. */
+}
+
+
+/**
+ * Recursive globbing - plain text optimization.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
+ *
+ * @param pGlob The glob instance data.
+ * @param offPath The current path offset/length.
+ * @param iComp The current component.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobExecRecursivePlainText(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ /*
+ * Instead of recursing, we loop thru adjacent plain text components.
+ */
+ for (;;)
+ {
+ /*
+ * Preconditions.
+ */
+ Assert(iComp < pGlob->pParsed->cComps);
+ Assert(pGlob->szPath[offPath] == '\0');
+ Assert(pGlob->aComps[iComp].fPlain);
+ Assert(!pGlob->aComps[iComp].fExpVariable);
+ Assert(!pGlob->aComps[iComp].fStarStar);
+ Assert(rtPathGlobExecIsPlainText(pGlob, offPath, iComp));
+ Assert(pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
+ == RTPATHMATCHOP_STRCMP
+ || pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg].enmOpCode
+ == RTPATHMATCHOP_STRICMP);
+
+ /*
+ * Add the plain text component to the path.
+ */
+ size_t const cch = pGlob->pParsed->aComps[iComp].cch;
+ if (cch + pGlob->aComps[iComp].fDir < sizeof(pGlob->szPath) - offPath)
+ {
+ memcpy(&pGlob->szPath[offPath], &pGlob->pszPattern[pGlob->pParsed->aComps[iComp].off], cch);
+ offPath += cch;
+ pGlob->szPath[offPath] = '\0';
+
+ /*
+ * Check if it exists.
+ */
+ int rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ if (pGlob->aComps[iComp].fFinal)
+ {
+ if (rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
+ return rtPathGlobAddResult(pGlob, offPath,
+ (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
+ break;
+ }
+
+ if (RTFS_IS_DIRECTORY(pGlob->u.ObjInfo.Attr.fMode))
+ {
+ Assert(pGlob->aComps[iComp].fDir);
+ pGlob->szPath[offPath++] = RTPATH_SLASH;
+ pGlob->szPath[offPath] = '\0';
+
+ iComp++;
+ if (rtPathGlobExecIsExpVar(pGlob, offPath, iComp))
+ return rtPathGlobExecRecursiveVarExp(pGlob, offPath, iComp);
+ if (!rtPathGlobExecIsPlainText(pGlob, offPath, iComp))
+ return rtPathGlobExecRecursiveGeneric(pGlob, offPath, iComp);
+ if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
+ return rtPathGlobExecRecursiveStarStar(pGlob, offPath, iComp, offPath);
+
+ /* Continue with the next plain text component. */
+ continue;
+ }
+ }
+ /* else: file doesn't exist or something else is wrong, ignore this. */
+ }
+ else
+ pGlob->cPathOverflows++;
+ break;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Recursive globbing - generic.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_CALLBACK_RETURN is used to implement RTPATHGLOB_F_FIRST_ONLY.
+ *
+ * @param pGlob The glob instance data.
+ * @param offPath The current path offset/length.
+ * @param iComp The current component.
+ */
+DECL_NO_INLINE(static, int) rtPathGlobExecRecursiveGeneric(PRTPATHGLOB pGlob, size_t offPath, uint32_t iComp)
+{
+ /*
+ * Enumerate entire directory and match each entry.
+ */
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, offPath ? pGlob->szPath : ".");
+ if (RT_SUCCESS(rc))
+ {
+ for (;;)
+ {
+ size_t cch = sizeof(pGlob->u);
+ rc = RTDirRead(hDir, &pGlob->u.DirEntry, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ if (pGlob->aComps[iComp].fFinal)
+ {
+ /*
+ * Final component: Check if it matches the current pattern.
+ */
+ if ( !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS))
+ || RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
+ == (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY)
+ || pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
+ {
+ rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
+ &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
+ &pGlob->MatchCache);
+ if (RT_SUCCESS(rc))
+ {
+ /* Construct the result. */
+ if ( pGlob->u.DirEntry.enmType != RTDIRENTRYTYPE_UNKNOWN
+ || !(pGlob->fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) )
+ rc = rtPathGlobAddResult2(pGlob, offPath, pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
+ (uint8_t)pGlob->u.DirEntry.enmType);
+ else
+ {
+ rc = rtPathGlobAlmostAddResult(pGlob, offPath,
+ pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
+ (uint8_t)RTDIRENTRYTYPE_UNKNOWN);
+ if (RT_SUCCESS(rc))
+ {
+ RTDirQueryUnknownType((*pGlob->ppNext)->szPath, false /*fFollowSymlinks*/,
+ &pGlob->u.DirEntry.enmType);
+ if ( RT_BOOL(pGlob->fFlags & RTPATHGLOB_F_ONLY_DIRS)
+ == (pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY))
+ rtPathGlobCommitResult(pGlob, (uint8_t)pGlob->u.DirEntry.enmType);
+ else
+ rtPathGlobRollbackResult(pGlob);
+ }
+ }
+ if (rc != VINF_SUCCESS)
+ break;
+ }
+ else
+ {
+ AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+ /*
+ * Intermediate component: Directories only.
+ */
+ else if ( pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_DIRECTORY
+ || pGlob->u.DirEntry.enmType == RTDIRENTRYTYPE_UNKNOWN)
+ {
+ rc = rtPathMatchExec(pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName,
+ &pGlob->MatchInstrAlloc.paInstructions[pGlob->aComps[iComp].iMatchProg],
+ &pGlob->MatchCache);
+ if (RT_SUCCESS(rc))
+ {
+ /* Recurse down into the alleged directory. */
+ cch = offPath + pGlob->u.DirEntry.cbName;
+ if (cch + 1 < sizeof(pGlob->szPath))
+ {
+ memcpy(&pGlob->szPath[offPath], pGlob->u.DirEntry.szName, pGlob->u.DirEntry.cbName);
+ pGlob->szPath[cch++] = RTPATH_SLASH;
+ pGlob->szPath[cch] = '\0';
+
+ if (rtPathGlobExecIsExpVar(pGlob, cch, iComp + 1))
+ rc = rtPathGlobExecRecursiveVarExp(pGlob, cch, iComp + 1);
+ else if (rtPathGlobExecIsPlainText(pGlob, cch, iComp + 1))
+ rc = rtPathGlobExecRecursivePlainText(pGlob, cch, iComp + 1);
+ else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
+ rc = rtPathGlobExecRecursiveStarStar(pGlob, cch, iComp + 1, cch);
+ else
+ rc = rtPathGlobExecRecursiveGeneric(pGlob, cch, iComp + 1);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ else
+ pGlob->cPathOverflows++;
+ }
+ else
+ {
+ AssertMsgBreak(rc == VERR_MISMATCH, ("%Rrc\n", rc));
+ rc = VINF_SUCCESS;
+ }
+ }
+ }
+ /*
+ * RTDirRead failure.
+ */
+ else
+ {
+ /* The end? */
+ if (rc == VERR_NO_MORE_FILES)
+ rc = VINF_SUCCESS;
+ /* Try skip the entry if we end up with an overflow (szPath can't hold it either then). */
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ pGlob->cPathOverflows++;
+ rc = rtPathGlobSkipDirEntry(hDir, cch);
+ if (RT_SUCCESS(rc))
+ continue;
+ }
+ /* else: Any other error is unexpected and should be reported. */
+ break;
+ }
+ }
+
+ RTDirClose(hDir);
+ }
+ /* Directory doesn't exist or something else is wrong, ignore this. */
+ else
+ rc = VINF_SUCCESS;
+ return rc;
+}
+
+
+/**
+ * Executes a glob search.
+ *
+ * @returns IPRT status code.
+ * @param pGlob The glob instance data.
+ */
+static int rtPathGlobExec(PRTPATHGLOB pGlob)
+{
+ Assert(pGlob->offFirstPath < sizeof(pGlob->szPath));
+ Assert(pGlob->szPath[pGlob->offFirstPath] == '\0');
+
+ int rc;
+ if (RT_LIKELY(pGlob->iFirstComp < pGlob->pParsed->cComps))
+ {
+ /*
+ * Call the appropriate function.
+ */
+ if (rtPathGlobExecIsExpVar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
+ rc = rtPathGlobExecRecursiveVarExp(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
+ else if (rtPathGlobExecIsPlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp))
+ rc = rtPathGlobExecRecursivePlainText(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
+ else if (pGlob->aComps[pGlob->iFirstComp].fStarStar)
+ rc = rtPathGlobExecRecursiveStarStar(pGlob, pGlob->offFirstPath, pGlob->iFirstComp, pGlob->offFirstPath);
+ else
+ rc = rtPathGlobExecRecursiveGeneric(pGlob, pGlob->offFirstPath, pGlob->iFirstComp);
+ }
+ else
+ {
+ /*
+ * Special case where we only have a root component or tilde expansion.
+ */
+ Assert(pGlob->offFirstPath > 0);
+ rc = RTPathQueryInfoEx(pGlob->szPath, &pGlob->u.ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_FOLLOW_LINK);
+ if ( RT_SUCCESS(rc)
+ && rtPathGlobExecIsMatchFinalWithFileMode(pGlob, pGlob->u.ObjInfo.Attr.fMode))
+ rc = rtPathGlobAddResult(pGlob, pGlob->offFirstPath,
+ (pGlob->u.ObjInfo.Attr.fMode & RTFS_TYPE_MASK) >> RTFS_TYPE_DIRENTRYTYPE_SHIFT);
+ else
+ rc = VINF_SUCCESS;
+ }
+
+ /*
+ * Adjust the status code. Check for results, hide RTPATHGLOB_F_FIRST_ONLY
+ * status code, and add warning if necessary.
+ */
+ if (pGlob->cResults > 0)
+ {
+ if (rc == VINF_CALLBACK_RETURN)
+ rc = VINF_SUCCESS;
+ if (rc == VINF_SUCCESS)
+ {
+ if (pGlob->cPathOverflows > 0)
+ rc = VINF_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+
+ return rc;
+}
+
+
+RTDECL(int) RTPathGlob(const char *pszPattern, uint32_t fFlags, PPCRTPATHGLOBENTRY ppHead, uint32_t *pcResults)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(ppHead, VERR_INVALID_POINTER);
+ *ppHead = NULL;
+ if (pcResults)
+ {
+ AssertPtrReturn(pcResults, VERR_INVALID_POINTER);
+ *pcResults = 0;
+ }
+ AssertPtrReturn(pszPattern, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTPATHGLOB_F_MASK), VERR_INVALID_FLAGS);
+ AssertReturn((fFlags & (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS)) != (RTPATHGLOB_F_NO_DIRS | RTPATHGLOB_F_ONLY_DIRS),
+ VERR_INVALID_FLAGS);
+
+ /*
+ * Parse the path.
+ */
+ size_t cbParsed = RT_UOFFSETOF(RTPATHPARSED, aComps[1]); /** @todo 16 after testing */
+ PRTPATHPARSED pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
+ AssertReturn(pParsed, VERR_NO_MEMORY);
+ int rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbParsed = RT_UOFFSETOF_DYN(RTPATHPARSED, aComps[pParsed->cComps + 1]);
+ RTMemTmpFree(pParsed);
+ pParsed = (PRTPATHPARSED)RTMemTmpAlloc(cbParsed);
+ AssertReturn(pParsed, VERR_NO_MEMORY);
+
+ rc = RTPathParse(pszPattern, pParsed, cbParsed, RTPATH_STR_F_STYLE_HOST);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Check dir slash vs. only/not dir flag.
+ */
+ if ( !(fFlags & RTPATHGLOB_F_NO_DIRS)
+ || ( !(pParsed->fProps & RTPATH_PROP_DIR_SLASH)
+ && ( !(pParsed->fProps & (RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_UNC))
+ || pParsed->cComps > 1) ) )
+ {
+ if (pParsed->fProps & RTPATH_PROP_DIR_SLASH)
+ fFlags |= RTPATHGLOB_F_ONLY_DIRS;
+
+ /*
+ * Allocate and initialize the glob state data structure.
+ */
+ size_t cbGlob = RT_UOFFSETOF_DYN(RTPATHGLOB, aComps[pParsed->cComps + 1]);
+ PRTPATHGLOB pGlob = (PRTPATHGLOB)RTMemTmpAllocZ(cbGlob);
+ if (pGlob)
+ {
+ pGlob->pszPattern = pszPattern;
+ pGlob->fFlags = fFlags;
+ pGlob->pParsed = pParsed;
+ pGlob->ppNext = &pGlob->pHead;
+ rc = rtPathGlobParse(pGlob, pszPattern, pParsed, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Execute the search.
+ */
+ rc = rtPathGlobExec(pGlob);
+ if (RT_SUCCESS(rc))
+ {
+ *ppHead = pGlob->pHead;
+ if (pcResults)
+ *pcResults = pGlob->cResults;
+ }
+ else
+ RTPathGlobFree(pGlob->pHead);
+ }
+
+ RTMemTmpFree(pGlob->MatchInstrAlloc.paInstructions);
+ RTMemTmpFree(pGlob);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_NOT_FOUND;
+ }
+ RTMemTmpFree(pParsed);
+ return rc;
+
+
+}
+
+
+RTDECL(void) RTPathGlobFree(PCRTPATHGLOBENTRY pHead)
+{
+ PRTPATHGLOBENTRY pCur = (PRTPATHGLOBENTRY)pHead;
+ while (pCur)
+ {
+ PRTPATHGLOBENTRY pNext = pCur->pNext;
+ pCur->pNext = NULL;
+ RTMemFree(pCur);
+ pCur = pNext;
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathHasExt.cpp b/src/VBox/Runtime/common/path/RTPathHasExt.cpp
new file mode 100644
index 00000000..049f7d24
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathHasExt.cpp
@@ -0,0 +1,50 @@
+/* $Id: RTPathHasExt.cpp $ */
+/** @file
+ * IPRT - RTPathHasExt
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+
+
+RTDECL(bool) RTPathHasSuffix(const char *pszPath)
+{
+ return RTPathSuffix(pszPath) != NULL;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathHasPath.cpp b/src/VBox/Runtime/common/path/RTPathHasPath.cpp
new file mode 100644
index 00000000..77c1923e
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathHasPath.cpp
@@ -0,0 +1,61 @@
+/* $Id: RTPathHasPath.cpp $ */
+/** @file
+ * IPRT - RTPathHasPath
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+/**
+ * Checks if a path includes more than a filename.
+ *
+ * @returns true if path present.
+ * @returns false if no path.
+ * @param pszPath Path to check.
+ */
+RTDECL(bool) RTPathHasPath(const char *pszPath)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ return strpbrk(pszPath, "/\\:") != NULL;
+#else
+ return strpbrk(pszPath, "/") != NULL;
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathJoin.cpp b/src/VBox/Runtime/common/path/RTPathJoin.cpp
new file mode 100644
index 00000000..a7b30ba8
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathJoin.cpp
@@ -0,0 +1,67 @@
+/* $Id: RTPathJoin.cpp $ */
+/** @file
+ * IPRT - RTPathJoin.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+
+
+RTDECL(int) RTPathJoin(char *pszPathDst, size_t cbPathDst, const char *pszPathSrc,
+ const char *pszAppend)
+{
+ AssertPtr(pszPathDst);
+ AssertPtr(pszPathSrc);
+ AssertPtr(pszAppend);
+
+ /*
+ * The easy way: Copy the path into the buffer and call RTPathAppend.
+ */
+ size_t cchPathSrc = strlen(pszPathSrc);
+ if (cchPathSrc >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pszPathDst, pszPathSrc, cchPathSrc + 1);
+
+ return RTPathAppend(pszPathDst, cbPathDst, pszAppend);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathJoinA.cpp b/src/VBox/Runtime/common/path/RTPathJoinA.cpp
new file mode 100644
index 00000000..04fe6787
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathJoinA.cpp
@@ -0,0 +1,83 @@
+/* $Id: RTPathJoinA.cpp $ */
+/** @file
+ * IPRT - RTPathJoinA.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+
+
+RTDECL(char *) RTPathJoinA(const char *pszPathSrc, const char *pszAppend)
+{
+ AssertPtr(pszAppend);
+ AssertPtr(pszPathSrc);
+
+ /*
+ * The easy way: Allocate a buffer and call RTPathAppend till it succeeds.
+ */
+ size_t cchPathSrc = strlen(pszPathSrc);
+ size_t cchAppend = strlen(pszAppend);
+ size_t cbPathDst = cchPathSrc + cchAppend + 4;
+ char *pszPathDst = RTStrAlloc(cbPathDst);
+ if (pszPathDst)
+ {
+ memcpy(pszPathDst, pszPathSrc, cchPathSrc + 1);
+ int rc = RTPathAppend(pszPathDst, cbPathDst, pszAppend);
+ if (RT_FAILURE(rc))
+ {
+ /* This shouldn't happen, but if it does try again with a larger buffer... */
+ AssertRC(rc);
+
+ rc = RTStrRealloc(&pszPathDst, cbPathDst * 2);
+ if (RT_SUCCESS(rc))
+ rc = RTPathAppend(pszPathDst, cbPathDst, pszAppend);
+ if (RT_FAILURE(rc))
+ {
+ RTStrFree(pszPathDst);
+ pszPathDst = NULL;
+ }
+ }
+ }
+ return pszPathDst;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathJoinEx.cpp b/src/VBox/Runtime/common/path/RTPathJoinEx.cpp
new file mode 100644
index 00000000..c10c5934
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathJoinEx.cpp
@@ -0,0 +1,70 @@
+/* $Id: RTPathJoinEx.cpp $ */
+/** @file
+ * IPRT - RTPathJoinEx.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+
+
+RTDECL(int) RTPathJoinEx(char *pszPathDst, size_t cbPathDst,
+ const char *pszPathSrc, size_t cchPathSrcMax,
+ const char *pszAppend, size_t cchAppendMax, uint32_t fFlags)
+{
+ AssertPtr(pszPathDst);
+ AssertPtr(pszPathSrc);
+ AssertPtr(pszAppend);
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0));
+
+ /*
+ * The easy way: Copy the path into the buffer and call RTPathAppend.
+ */
+ size_t cchPathSrc = RTStrNLen(pszPathSrc, cchPathSrcMax);
+ if (cchPathSrc >= cbPathDst)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pszPathDst, pszPathSrc, cchPathSrc);
+ pszPathDst[cchPathSrc] = '\0';
+
+ return RTPathAppendEx(pszPathDst, cbPathDst, pszAppend, cchAppendMax, fFlags);
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParentLength.cpp b/src/VBox/Runtime/common/path/RTPathParentLength.cpp
new file mode 100644
index 00000000..813e779e
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParentLength.cpp
@@ -0,0 +1,95 @@
+/* $Id: RTPathParentLength.cpp $ */
+/** @file
+ * IPRT - RTPathParentLength
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#define RTPATH_TEMPLATE_CPP_H "RTPathParentLength.cpp.h"
+#include "rtpath-expand-template.cpp.h"
+
+
+
+RTDECL(size_t) RTPathParentLengthEx(const char *pszPath, uint32_t fFlags)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(pszPath, 0);
+ AssertReturn(*pszPath, 0);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0), 0);
+ Assert(!(fFlags & RTPATH_STR_F_NO_END));
+
+ /*
+ * Invoke the worker for the selected path style.
+ */
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_DOS:
+ return rtPathParentLengthStyleDos(pszPath, fFlags);
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_DOS
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_UNIX:
+ return rtPathParentLengthStyleUnix(pszPath, fFlags);
+
+ default:
+ AssertFailedReturn(0); /* impossible */
+ }
+}
+
+
+RTDECL(size_t) RTPathParentLength(const char *pszPath)
+{
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ return rtPathParentLengthStyleDos(pszPath, 0);
+#else
+ return rtPathParentLengthStyleUnix(pszPath, 0);
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParentLength.cpp.h b/src/VBox/Runtime/common/path/RTPathParentLength.cpp.h
new file mode 100644
index 00000000..aebf823b
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParentLength.cpp.h
@@ -0,0 +1,68 @@
+/* $Id: RTPathParentLength.cpp.h $ */
+/** @file
+ * IPRT - RTPathParentLength - Code Template.
+ *
+ * This file included multiple times with different path style macros.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include "rtpath-root-length-template.cpp.h"
+
+/**
+ * @copydoc RTPathParentLengthEx
+ */
+static size_t RTPATH_STYLE_FN(rtPathParentLength)(const char *pszPath, uint32_t fFlags)
+{
+ /*
+ * Determin the length of the root component so we can make sure
+ * we don't try ascend higher than it.
+ */
+ size_t const cchRoot = RTPATH_STYLE_FN(rtPathRootLengthEx)(pszPath, fFlags);
+
+ /*
+ * Rewind to the start of the final component.
+ */
+ size_t cch = strlen(pszPath);
+
+ /* Trailing slashes: */
+ while (cch > cchRoot && RTPATH_IS_SLASH(pszPath[cch - 1]))
+ cch--;
+
+ /* The component: */
+ while (cch > cchRoot && !RTPATH_IS_SEP(pszPath[cch - 1]))
+ cch--;
+
+ /* Done! */
+ return cch;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParse.cpp b/src/VBox/Runtime/common/path/RTPathParse.cpp
new file mode 100644
index 00000000..cb400033
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParse.cpp
@@ -0,0 +1,85 @@
+/* $Id: RTPathParse.cpp $ */
+/** @file
+ * IPRT - RTPathParse
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+#define RTPATH_TEMPLATE_CPP_H "RTPathParse.cpp.h"
+#include "rtpath-expand-template.cpp.h"
+
+
+RTDECL(int) RTPathParse(const char *pszPath, PRTPATHPARSED pParsed, size_t cbParsed, uint32_t fFlags)
+{
+ /*
+ * Input validation.
+ */
+ AssertReturn(cbParsed >= RT_UOFFSETOF(RTPATHPARSED, aComps), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pParsed, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(*pszPath, VERR_PATH_ZERO_LENGTH);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0), VERR_INVALID_FLAGS);
+
+ /*
+ * Invoke the worker for the selected path style.
+ */
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_DOS:
+ return rtPathParseStyleDos(pszPath, pParsed, cbParsed, fFlags);
+
+#if !defined(RT_OS_OS2) && !defined(RT_OS_WINDOWS)
+ case RTPATH_STR_F_STYLE_HOST:
+#endif
+ case RTPATH_STR_F_STYLE_UNIX:
+ return rtPathParseStyleUnix(pszPath, pParsed, cbParsed, fFlags);
+
+ default:
+ AssertFailedReturn(VERR_INVALID_FLAGS); /* impossible */
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParse.cpp.h b/src/VBox/Runtime/common/path/RTPathParse.cpp.h
new file mode 100644
index 00000000..083a2c98
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParse.cpp.h
@@ -0,0 +1,256 @@
+/* $Id: RTPathParse.cpp.h $ */
+/** @file
+ * IPRT - RTPathParse - Code Template.
+ *
+ * This file included multiple times with different path style macros.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+
+/**
+ * @copydoc RTPathParse
+ */
+static int RTPATH_STYLE_FN(rtPathParse)(const char *pszPath, PRTPATHPARSED pParsed, size_t cbParsed, uint32_t fFlags)
+{
+ /*
+ * Parse the root specification if present and initialize the parser state
+ * (keep it on the stack for speed).
+ */
+ uint32_t const cMaxComps = cbParsed < RT_UOFFSETOF(RTPATHPARSED, aComps[0xfff0])
+ ? (uint32_t)((cbParsed - RT_UOFFSETOF(RTPATHPARSED, aComps)) / sizeof(pParsed->aComps[0]))
+ : 0xfff0;
+ uint32_t idxComp = 0;
+ uint32_t cchPath;
+ uint32_t offCur;
+ uint16_t fProps;
+
+ if (RTPATH_IS_SLASH(pszPath[0]))
+ {
+ if (fFlags & RTPATH_STR_F_NO_START)
+ {
+ offCur = 1;
+ while (RTPATH_IS_SLASH(pszPath[offCur]))
+ offCur++;
+ if (!pszPath[offCur])
+ return VERR_PATH_ZERO_LENGTH;
+ fProps = RTPATH_PROP_RELATIVE | RTPATH_PROP_EXTRA_SLASHES;
+ cchPath = 0;
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if (RTPATH_IS_SLASH(pszPath[1]))
+ {
+ /* UNC - there are exactly two prefix slashes followed by a namespace
+ or computer name, which can be empty on windows. */
+ offCur = 2;
+ while (!RTPATH_IS_SLASH(pszPath[offCur]) && pszPath[offCur])
+ offCur++;
+
+ /* Special fun for windows. */
+ fProps = RTPATH_PROP_UNC | RTPATH_PROP_ABSOLUTE;
+ if ( offCur == 3
+ && (pszPath[2] == '.' || pszPath[2] == '?'))
+ fProps |= RTPATH_PROP_SPECIAL_UNC;
+
+ if (RTPATH_IS_SLASH(pszPath[offCur]))
+ {
+ fProps |= RTPATH_PROP_ROOT_SLASH;
+ offCur++;
+ }
+ cchPath = offCur;
+ }
+#endif
+ else
+ {
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ fProps = RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_RELATIVE;
+#else
+ fProps = RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_ABSOLUTE;
+#endif
+ offCur = 1;
+ cchPath = 1;
+ }
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ else if (RT_C_IS_ALPHA(pszPath[0]) && pszPath[1] == ':')
+ {
+ if (!RTPATH_IS_SLASH(pszPath[2]))
+ {
+ fProps = RTPATH_PROP_VOLUME | RTPATH_PROP_RELATIVE;
+ offCur = 2;
+ }
+ else
+ {
+ fProps = RTPATH_PROP_VOLUME | RTPATH_PROP_ROOT_SLASH | RTPATH_PROP_ABSOLUTE;
+ offCur = 3;
+ }
+ cchPath = offCur;
+ }
+#endif
+ else
+ {
+ fProps = RTPATH_PROP_RELATIVE;
+ offCur = 0;
+ cchPath = 0;
+ }
+
+ /* Add it to the component array . */
+ if (offCur && !(fFlags & RTPATH_STR_F_NO_START))
+ {
+ cchPath = offCur;
+ if (idxComp < cMaxComps)
+ {
+ pParsed->aComps[idxComp].off = 0;
+ pParsed->aComps[idxComp].cch = offCur;
+ }
+ idxComp++;
+
+ /* Skip unnecessary slashes following the root-spec. */
+ if (RTPATH_IS_SLASH(pszPath[offCur]))
+ {
+ fProps |= RTPATH_PROP_EXTRA_SLASHES;
+ do
+ offCur++;
+ while (RTPATH_IS_SLASH(pszPath[offCur]));
+ }
+ }
+
+ /*
+ * Parse the rest.
+ */
+ if (pszPath[offCur])
+ {
+ for (;;)
+ {
+ Assert(!RTPATH_IS_SLASH(pszPath[offCur]));
+
+ /* Find the end of the component. */
+ uint32_t offStart = offCur;
+ char ch;
+ while ((ch = pszPath[offCur]) != '\0' && !RTPATH_IS_SLASH(ch))
+ offCur++;
+ if (offCur >= _64K)
+ return VERR_FILENAME_TOO_LONG;
+
+ /* Add it. */
+ uint32_t cchComp = offCur - offStart;
+ if (idxComp < cMaxComps)
+ {
+ pParsed->aComps[idxComp].off = offStart;
+ pParsed->aComps[idxComp].cch = cchComp;
+ }
+ idxComp++;
+ cchPath += cchComp;
+
+ /* Look for '.' and '..' references. */
+ if (cchComp == 1 && pszPath[offCur - 1] == '.')
+ fProps |= RTPATH_PROP_DOT_REFS;
+ else if (cchComp == 2 && pszPath[offCur - 1] == '.' && pszPath[offCur - 2] == '.')
+ {
+ fProps &= ~RTPATH_PROP_ABSOLUTE;
+ fProps |= RTPATH_PROP_DOTDOT_REFS | RTPATH_PROP_RELATIVE;
+ }
+
+ /* Skip unnecessary slashes. Leave ch unchanged! */
+ char ch2 = ch;
+ if (ch2)
+ {
+ ch2 = pszPath[++offCur];
+ if (RTPATH_IS_SLASH(ch2))
+ {
+ fProps |= RTPATH_PROP_EXTRA_SLASHES;
+ do
+ ch2 = pszPath[++offCur];
+ while (RTPATH_IS_SLASH(ch2));
+ }
+ }
+
+ /* The end? */
+ if (ch2 == '\0')
+ {
+ pParsed->offSuffix = offCur;
+ pParsed->cchSuffix = 0;
+ if (ch)
+ {
+ if (!(fFlags & RTPATH_STR_F_NO_END))
+ {
+ fProps |= RTPATH_PROP_DIR_SLASH; /* (not counted in component, but in cchPath) */
+ cchPath++;
+ }
+ else
+ fProps |= RTPATH_PROP_EXTRA_SLASHES;
+ }
+ else if (!(fFlags & RTPATH_STR_F_NO_END))
+ {
+ fProps |= RTPATH_PROP_FILENAME;
+
+ /* Look for a suffix: */
+ uint32_t offSuffix = offStart + cchComp;
+ while (--offSuffix > offStart)
+ if (pszPath[offSuffix] == '.')
+ {
+ uint32_t cchSuffix = offStart + cchComp - offSuffix;
+ if (cchSuffix > 1)
+ {
+ pParsed->cchSuffix = cchSuffix;
+ pParsed->offSuffix = offSuffix;
+ fProps |= RTPATH_PROP_SUFFIX;
+ }
+ break;
+ }
+ }
+ break;
+ }
+
+ /* No, not the end. Account for an separator before we restart the loop. */
+ cchPath += sizeof(RTPATH_SLASH_STR) - 1;
+ }
+ }
+ else
+ {
+ pParsed->offSuffix = offCur;
+ pParsed->cchSuffix = 0;
+ }
+ if (offCur >= _64K)
+ return VERR_FILENAME_TOO_LONG;
+
+ /*
+ * Store the remainder of the state and we're done.
+ */
+ pParsed->fProps = fProps;
+ pParsed->cchPath = cchPath;
+ pParsed->cComps = idxComp;
+
+ return idxComp <= cMaxComps ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParseSimple.cpp b/src/VBox/Runtime/common/path/RTPathParseSimple.cpp
new file mode 100644
index 00000000..ba3cd17d
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParseSimple.cpp
@@ -0,0 +1,144 @@
+/* $Id: RTPathParseSimple.cpp $ */
+/** @file
+ * IPRT - RTPathParseSimple
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+
+
+RTDECL(size_t) RTPathParseSimple(const char *pszPath, size_t *pcchDir, ssize_t *poffName, ssize_t *poffSuff)
+{
+ /*
+ * First deal with the root as it is always more fun that you'd think.
+ */
+ const char *psz = pszPath;
+ size_t cchRoot = 0;
+
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ if (RT_C_IS_ALPHA(*psz) && RTPATH_IS_VOLSEP(psz[1]))
+ {
+ /* Volume specifier. */
+ cchRoot = 2;
+ psz += 2;
+ }
+ else if (RTPATH_IS_SLASH(*psz) && RTPATH_IS_SLASH(psz[1]))
+ {
+ /* UNC - there are exactly two prefix slashes followed by a namespace
+ or computer name, which can be empty on windows. */
+ cchRoot = 2;
+ psz += 2;
+ while (!RTPATH_IS_SLASH(*psz) && *psz)
+ {
+ cchRoot++;
+ psz++;
+ }
+ }
+#endif
+ while (RTPATH_IS_SLASH(*psz))
+ {
+ cchRoot++;
+ psz++;
+ }
+
+ /*
+ * Do the remainder.
+ */
+ const char *pszName = psz;
+ const char *pszLastDot = NULL;
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ default:
+ break;
+
+ /* handle separators. */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ case '\\':
+#endif
+ case '/':
+ pszName = psz + 1;
+ pszLastDot = NULL;
+ break;
+
+ case '.':
+ pszLastDot = psz;
+ break;
+
+ /*
+ * The end. Complete the results.
+ */
+ case '\0':
+ {
+ ssize_t offName = *pszName != '\0' ? pszName - pszPath : -1;
+ if (poffName)
+ *poffName = offName;
+
+ if (poffSuff)
+ {
+ ssize_t offSuff = -1;
+ if ( pszLastDot
+ && pszLastDot != pszName
+ && pszLastDot[1] != '\0')
+ {
+ offSuff = pszLastDot - pszPath;
+ Assert(offSuff > offName);
+ }
+ *poffSuff = offSuff;
+ }
+
+ if (pcchDir)
+ {
+ size_t cch = offName < 0 ? psz - pszPath : offName - 1 < (ssize_t)cchRoot ? cchRoot : offName - 1;
+ while (cch > cchRoot && RTPATH_IS_SLASH(pszPath[cch - 1]))
+ cch--;
+ *pcchDir = cch;
+ }
+
+ return psz - pszPath;
+ }
+ }
+ }
+
+ /* will never get here */
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathParsedReassemble.cpp b/src/VBox/Runtime/common/path/RTPathParsedReassemble.cpp
new file mode 100644
index 00000000..4a814943
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathParsedReassemble.cpp
@@ -0,0 +1,161 @@
+/* $Id: RTPathParsedReassemble.cpp $ */
+/** @file
+ * IPRT - RTPathParsedReassemble.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTPathParsedReassemble(const char *pszSrcPath, PRTPATHPARSED pParsed, uint32_t fFlags,
+ char *pszDstPath, size_t cbDstPath)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(pszSrcPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(pParsed, VERR_INVALID_POINTER);
+ AssertReturn(pParsed->cComps > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0) && !(fFlags & RTPATH_STR_F_MIDDLE), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszDstPath, VERR_INVALID_POINTER);
+
+ /*
+ * Recalculate the length.
+ */
+ uint32_t const cComps = pParsed->cComps;
+ uint32_t idxComp = 0;
+ uint32_t cchPath = 0;
+ if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
+ {
+ cchPath = pParsed->aComps[0].cch;
+ idxComp++;
+ }
+ bool fNeedSlash = false;
+ while (idxComp < cComps)
+ {
+ uint32_t const cchComp = pParsed->aComps[idxComp].cch;
+ idxComp++;
+ if (cchComp > 0)
+ {
+ cchPath += cchComp + fNeedSlash;
+ fNeedSlash = true;
+ }
+ }
+ if ((pParsed->fProps & RTPATH_PROP_DIR_SLASH) && fNeedSlash)
+ cchPath += 1;
+ pParsed->cchPath = cchPath;
+ if (cbDstPath > cchPath)
+ { /* likely */ }
+ else
+ {
+ if (cbDstPath)
+ *pszDstPath = '\0';
+ return VERR_BUFFER_OVERFLOW;
+ }
+
+ /*
+ * Figure which slash to use.
+ */
+ char chSlash;
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+ case RTPATH_STR_F_STYLE_HOST:
+ chSlash = RTPATH_SLASH;
+ break;
+
+ case RTPATH_STR_F_STYLE_DOS:
+ chSlash = '\\';
+ break;
+
+ case RTPATH_STR_F_STYLE_UNIX:
+ chSlash = '/';
+ break;
+
+ default:
+ AssertFailedReturn(VERR_INVALID_FLAGS); /* impossible */
+ }
+
+ /*
+ * Do the joining.
+ * Note! Using memmove here as we want to support pszSrcPath == pszDstPath.
+ */
+ char *pszDst = pszDstPath;
+ idxComp = 0;
+ fNeedSlash = false;
+
+ if (RTPATH_PROP_HAS_ROOT_SPEC(pParsed->fProps))
+ {
+ uint32_t cchComp = pParsed->aComps[0].cch;
+ memmove(pszDst, &pszSrcPath[pParsed->aComps[0].off], cchComp);
+
+ /* fix the slashes (harmless for unix style) */
+ char chOtherSlash = chSlash == '\\' ? '/' : '\\';
+ while (cchComp-- > 0)
+ {
+ if (*pszDst == chOtherSlash)
+ *pszDst = chSlash;
+ pszDst++;
+ }
+ idxComp = 1;
+ }
+
+ while (idxComp < cComps)
+ {
+ uint32_t const cchComp = pParsed->aComps[idxComp].cch;
+ if (cchComp > 0)
+ {
+ if (fNeedSlash)
+ *pszDst++ = chSlash;
+ fNeedSlash = true;
+ memmove(pszDst, &pszSrcPath[pParsed->aComps[idxComp].off], cchComp);
+ pszDst += cchComp;
+ }
+ idxComp++;
+ }
+
+ if ((pParsed->fProps & RTPATH_PROP_DIR_SLASH) && fNeedSlash)
+ *pszDst++ = chSlash;
+ *pszDst = '\0';
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathPurgeFilename.cpp b/src/VBox/Runtime/common/path/RTPathPurgeFilename.cpp
new file mode 100644
index 00000000..9f343c9a
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathPurgeFilename.cpp
@@ -0,0 +1,131 @@
+/* $Id: RTPathPurgeFilename.cpp $ */
+/** @file
+ * IPRT - RTPathPurgeFilename
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Character mappings for translating a string into a valid Windows or OS/2 filename. */
+static const unsigned char g_auchWinOs2Map[256] =
+{
+ 0, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 32, 33, 95, 35, 36, 37, 38, 39, 40, 41, 95, 43, 44, 45, 46, 95, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 95, 59, 95, 61, 95, 95,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 95, 93, 94, 95,
+ 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123, 95,125,126, 95,
+ 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
+ 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,
+ 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,
+ 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255
+};
+
+/* Program for generating the table:
+#include <stdio.h>
+#include <string.h>
+int main()
+{
+ int i;
+ printf("static const unsigned char g_auchWinOs2Map[256] =\n"
+ "{");
+ for (i = 0; i < 256; i++)
+ {
+ int ch = i;
+ if ( i <= 31
+ || i == 127
+ || (i < 127 && strchr("/\\*:<>?|\"", (char)i) != NULL))
+ ch = i > 0 ? '_' : 0;
+ if (i == 0)
+ printf("\n %3d", ch);
+ else if ((i % 32) == 0)
+ printf(",\n %3d", ch);
+ else
+ printf(",%3d", ch);
+ }
+ printf("\n"
+ "};\n");
+ return 0;
+}
+*/
+
+
+RTDECL(char *) RTPathPurgeFilename(char *pszString, uint32_t fFlags)
+{
+ AssertPtrReturn(pszString, NULL);
+ Assert(RTPATH_STR_F_IS_VALID(fFlags, 0));
+
+ /*
+ * Take action according to the style after first resolving the host style.
+ */
+ if ((fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_HOST)
+ fFlags = (fFlags & ~RTPATH_STR_F_STYLE_MASK) | RTPATH_STYLE;
+ if ((fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_DOS)
+ {
+ /*
+ * Produce a filename valid on Windows and OS/2.
+ * Here all control characters (including tab) and path separators needs to
+ * be replaced, in addition to a scattering of other ones.
+ */
+ unsigned char *puch = (unsigned char *)pszString;
+ uintptr_t uch;
+ while ((uch = *puch))
+ *puch++ = g_auchWinOs2Map[uch];
+ }
+ else
+ {
+ /*
+ * Produce a filename valid on a (typical) Unix system.
+ * Here only the '/' needs to be replaced.
+ */
+ Assert((fFlags & RTPATH_STR_F_STYLE_MASK) == RTPATH_STR_F_STYLE_UNIX);
+ char *pszSlash = strchr(pszString, '/');
+ while (pszSlash != NULL)
+ {
+ *pszSlash = '_';
+ pszSlash = strchr(pszSlash + 1, '/');
+ }
+ }
+ return pszString;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathRealDup.cpp b/src/VBox/Runtime/common/path/RTPathRealDup.cpp
new file mode 100644
index 00000000..66964c39
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathRealDup.cpp
@@ -0,0 +1,57 @@
+/* $Id: RTPathRealDup.cpp $ */
+/** @file
+ * IPRT - RTPathRealDup
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/errcore.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(char *) RTPathRealDup(const char *pszPath)
+{
+ char szPath[RTPATH_MAX];
+ int rc = RTPathReal(pszPath, szPath, sizeof(szPath));
+ if (RT_SUCCESS(rc))
+ return RTStrDup(szPath);
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathRmCmd.cpp b/src/VBox/Runtime/common/path/RTPathRmCmd.cpp
new file mode 100644
index 00000000..c5184b1e
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathRmCmd.cpp
@@ -0,0 +1,658 @@
+/* $Id: RTPathRmCmd.cpp $ */
+/** @file
+ * IPRT - RM Command.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/path.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/dir.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/symlink.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTPATHRMCMD_OPT_INTERACTIVE 1000
+#define RTPATHRMCMD_OPT_ONE_FILE_SYSTEM 1001
+#define RTPATHRMCMD_OPT_PRESERVE_ROOT 1002
+#define RTPATHRMCMD_OPT_NO_PRESERVE_ROOT 1003
+#define RTPATHRMCMD_OPT_MACHINE_READABLE 1004
+
+/** The max directory entry size. */
+#define RTPATHRM_DIR_MAX_ENTRY_SIZE (sizeof(RTDIRENTRYEX) + 4096)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Interactive option. */
+typedef enum
+{
+ RTPATHRMCMDINTERACTIVE_NONE = 1,
+ RTPATHRMCMDINTERACTIVE_ALL,
+ RTPATHRMCMDINTERACTIVE_ONCE
+ /** @todo possible that we should by default prompt if removing read-only
+ * files or files owned by someone else. We currently don't. */
+} RTPATHRMCMDINTERACTIVE;
+
+/**
+ * IPRT rm option structure.
+ */
+typedef struct RTPATHRMCMDOPTS
+{
+ /** Whether to delete recursively. */
+ bool fRecursive;
+ /** Whether to delete directories as well as other kinds of files. */
+ bool fDirsAndOther;
+ /** Whether to remove files without prompting and ignoring non-existing
+ * files. */
+ bool fForce;
+ /** Machine readable output. */
+ bool fMachineReadable;
+ /** Don't try remove root ('/') if set, otherwise don't treat root specially. */
+ bool fPreserveRoot;
+ /** Whether to keep to one file system. */
+ bool fOneFileSystem;
+ /** Whether to safely delete files (overwrite 3x before unlinking). */
+ bool fSafeDelete;
+ /** Whether to be verbose about the operation. */
+ bool fVerbose;
+ /** The interactive setting. */
+ RTPATHRMCMDINTERACTIVE enmInteractive;
+} RTPATHRMCMDOPTS;
+/** Pointer to the IPRT rm options. */
+typedef RTPATHRMCMDOPTS *PRTPATHRMCMDOPTS;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** A bunch of zeros. */
+static uint8_t const g_abZeros[16384] = { 0 };
+/** A bunch of 0xFF bytes. (lazy init) */
+static uint8_t g_ab0xFF[16384];
+
+
+static void rtPathRmVerbose(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ if (!pOpts->fMachineReadable)
+ RTPrintf("%s\n", pszPath);
+}
+
+
+static int rtPathRmError(PRTPATHRMCMDOPTS pOpts, const char *pszPath, int rc,
+ const char *pszFormat, ...)
+{
+ if (pOpts->fMachineReadable)
+ RTPrintf("fname=%s%crc=%d%c", pszPath, 0, rc, 0);
+ else
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ RTMsgErrorV(pszFormat, va);
+ va_end(va);
+ }
+ return rc;
+}
+
+
+/**
+ * Worker that removes a symbolic link.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the symbolic link.
+ */
+static int rtPathRmOneSymlink(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ if (pOpts->fVerbose)
+ rtPathRmVerbose(pOpts, pszPath);
+ int rc = RTSymlinkDelete(pszPath, 0);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Error removing symbolic link '%s': %Rrc\n", pszPath, rc);
+ return rc;
+}
+
+
+/**
+ * Worker that removes a file.
+ *
+ * Currently used to delete both regular and special files.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the file.
+ * @param pObjInfo The FS object info for the file.
+ */
+static int rtPathRmOneFile(PRTPATHRMCMDOPTS pOpts, const char *pszPath, PRTFSOBJINFO pObjInfo)
+{
+ int rc;
+ if (pOpts->fVerbose)
+ rtPathRmVerbose(pOpts, pszPath);
+
+ /*
+ * Wipe the file if requested and possible.
+ */
+ if (pOpts->fSafeDelete && RTFS_IS_FILE(pObjInfo->Attr.fMode))
+ {
+ /* Lazy init of the 0xff buffer. */
+ if (g_ab0xFF[0] != 0xff || g_ab0xFF[sizeof(g_ab0xFF) - 1] != 0xff)
+ memset(g_ab0xFF, 0xff, sizeof(g_ab0xFF));
+
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszPath, RTFILE_O_WRITE);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Opening '%s' for overwriting: %Rrc\n", pszPath, rc);
+
+ for (unsigned iPass = 0; iPass < 3; iPass++)
+ {
+ uint8_t const *pabFiller = iPass == 1 ? g_abZeros : g_ab0xFF;
+ size_t const cbFiller = iPass == 1 ? sizeof(g_abZeros) : sizeof(g_ab0xFF);
+
+ rc = RTFileSeek(hFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = rtPathRmError(pOpts, pszPath, rc, "Error seeking to start of '%s': %Rrc\n", pszPath, rc);
+ break;
+ }
+ for (RTFOFF cbLeft = pObjInfo->cbObject; cbLeft > 0; cbLeft -= cbFiller)
+ {
+ size_t cbToWrite = cbFiller;
+ if (cbLeft < (RTFOFF)cbToWrite)
+ cbToWrite = (size_t)cbLeft;
+ rc = RTFileWrite(hFile, pabFiller, cbToWrite, NULL);
+ if (RT_FAILURE(rc))
+ {
+ rc = rtPathRmError(pOpts, pszPath, rc, "Error writing to '%s': %Rrc\n", pszPath, rc);
+ break;
+ }
+ }
+ }
+
+ int rc2 = RTFileClose(hFile);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ return rtPathRmError(pOpts, pszPath, rc2, "Closing '%s' failed: %Rrc\n", pszPath, rc);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Remove the file.
+ */
+ rc = RTFileDelete(pszPath);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc,
+ RTFS_IS_FILE(pObjInfo->Attr.fMode)
+ ? "Error removing regular file '%s': %Rrc\n"
+ : "Error removing special file '%s': %Rrc\n",
+ pszPath, rc);
+ return rc;
+}
+
+
+/**
+ * Deletes one directory (if it's empty).
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the directory.
+ */
+static int rtPathRmOneDir(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ if (pOpts->fVerbose)
+ rtPathRmVerbose(pOpts, pszPath);
+
+ int rc = RTDirRemove(pszPath);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Error removing directory '%s': %Rrc", pszPath, rc);
+ return rc;
+}
+
+
+/**
+ * Recursively delete a directory.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath Pointer to a writable buffer holding the path to
+ * the directory.
+ * @param cchPath The length of the path (avoid strlen).
+ * @param pDirEntry Pointer to a directory entry buffer that is
+ * RTPATHRM_DIR_MAX_ENTRY_SIZE bytes big.
+ */
+static int rtPathRmRecursive(PRTPATHRMCMDOPTS pOpts, char *pszPath, size_t cchPath, PRTDIRENTRYEX pDirEntry)
+{
+ /*
+ * Make sure the path ends with a slash.
+ */
+ if (!cchPath || !RTPATH_IS_SLASH(pszPath[cchPath - 1]))
+ {
+ if (cchPath + 1 >= RTPATH_MAX)
+ return rtPathRmError(pOpts, pszPath, VERR_BUFFER_OVERFLOW, "Buffer overflow fixing up '%s'.\n", pszPath);
+ pszPath[cchPath++] = RTPATH_SLASH;
+ pszPath[cchPath] = '\0';
+ }
+
+ /*
+ * Traverse the directory.
+ */
+ RTDIR hDir;
+ int rc = RTDirOpen(&hDir, pszPath);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Error opening directory '%s': %Rrc", pszPath, rc);
+ int rcRet = VINF_SUCCESS;
+ for (;;)
+ {
+ /*
+ * Read the next entry, constructing an full path for it.
+ */
+ size_t cbEntry = RTPATHRM_DIR_MAX_ENTRY_SIZE;
+ rc = RTDirReadEx(hDir, pDirEntry, &cbEntry, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (rc == VERR_NO_MORE_FILES)
+ {
+ /*
+ * Reached the end of the directory.
+ */
+ pszPath[cchPath] = '\0';
+ rc = RTDirClose(hDir);
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "Error closing directory '%s': %Rrc", pszPath, rc);
+
+ /* Delete the directory. */
+ int rc2 = rtPathRmOneDir(pOpts, pszPath);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rcRet))
+ return rc2;
+ return rcRet;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ rc = rtPathRmError(pOpts, pszPath, rc, "Error reading directory '%s': %Rrc", pszPath, rc);
+ break;
+ }
+
+ /* Skip '.' and '..'. */
+ if ( pDirEntry->szName[0] == '.'
+ && ( pDirEntry->cbName == 1
+ || ( pDirEntry->cbName == 2
+ && pDirEntry->szName[1] == '.')))
+ continue;
+
+ /* Construct full path. */
+ if (cchPath + pDirEntry->cbName >= RTPATH_MAX)
+ {
+ pszPath[cchPath] = '\0';
+ rc = rtPathRmError(pOpts, pszPath, VERR_BUFFER_OVERFLOW, "Path buffer overflow in directory '%s'.", pszPath);
+ break;
+ }
+ memcpy(pszPath + cchPath, pDirEntry->szName, pDirEntry->cbName + 1);
+
+ /*
+ * Take action according to the type.
+ */
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ rc = rtPathRmOneFile(pOpts, pszPath, &pDirEntry->Info);
+ break;
+
+ case RTFS_TYPE_DIRECTORY:
+ rc = rtPathRmRecursive(pOpts, pszPath, cchPath + pDirEntry->cbName, pDirEntry);
+ break;
+
+ case RTFS_TYPE_SYMLINK:
+ rc = rtPathRmOneSymlink(pOpts, pszPath);
+ break;
+
+ case RTFS_TYPE_FIFO:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_SOCKET:
+ rc = rtPathRmOneFile(pOpts, pszPath, &pDirEntry->Info);
+ break;
+
+ case RTFS_TYPE_WHITEOUT:
+ default:
+ rc = rtPathRmError(pOpts, pszPath, VERR_UNEXPECTED_FS_OBJ_TYPE,
+ "Object '%s' has an unknown file type: %o\n",
+ pszPath, pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK);
+ break;
+ }
+ if (RT_FAILURE(rc) && RT_SUCCESS(rcRet))
+ rcRet = rc;
+ }
+
+ /*
+ * Some error occured, close and return.
+ */
+ RTDirClose(hDir);
+ return rc;
+}
+
+/**
+ * Validates the specified file or directory.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the file, directory, whatever.
+ */
+static int rtPathRmOneValidate(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ /*
+ * RTPathFilename doesn't do the trailing slash thing the way we need it to.
+ * E.g. both '..' and '../' should be rejected.
+ */
+ size_t cchPath = strlen(pszPath);
+ while (cchPath > 0 && RTPATH_IS_SLASH(pszPath[cchPath - 1]))
+ cchPath--;
+
+ if ( ( cchPath == 0
+ || 0 /** @todo drive letter + UNC crap */)
+ && pOpts->fPreserveRoot)
+ return rtPathRmError(pOpts, pszPath, VERR_CANT_DELETE_DIRECTORY, "Cannot remove root directory ('%s').\n", pszPath);
+
+ size_t offLast = cchPath - 1;
+ while (offLast > 0 && !RTPATH_IS_SEP(pszPath[offLast - 1]))
+ offLast--;
+
+ size_t cchLast = cchPath - offLast;
+ if ( pszPath[offLast] == '.'
+ && ( cchLast == 1
+ || (cchLast == 2 && pszPath[offLast + 1] == '.')))
+ return rtPathRmError(pOpts, pszPath, VERR_CANT_DELETE_DIRECTORY, "Cannot remove special directory '%s'.\n", pszPath);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Remove one user specified file or directory.
+ *
+ * @returns IPRT status code, errors go via rtPathRmError.
+ * @param pOpts The RM options.
+ * @param pszPath The path to the file, directory, whatever.
+ */
+static int rtPathRmOne(PRTPATHRMCMDOPTS pOpts, const char *pszPath)
+{
+ /*
+ * RM refuses to delete some directories.
+ */
+ int rc = rtPathRmOneValidate(pOpts, pszPath);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Query file system object info.
+ */
+ RTFSOBJINFO ObjInfo;
+ rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ {
+ if (pOpts->fForce && (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND))
+ return VINF_SUCCESS;
+ return rtPathRmError(pOpts, pszPath, rc, "Error deleting '%s': %Rrc", pszPath, rc);
+ }
+
+ /*
+ * Take type specific action.
+ */
+ switch (ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ return rtPathRmOneFile(pOpts, pszPath, &ObjInfo);
+
+ case RTFS_TYPE_DIRECTORY:
+ if (pOpts->fRecursive)
+ {
+ char szPath[RTPATH_MAX];
+ rc = RTPathAbs(pszPath, szPath, sizeof(szPath));
+ if (RT_FAILURE(rc))
+ return rtPathRmError(pOpts, pszPath, rc, "RTPathAbs failed on '%s': %Rrc\n", pszPath, rc);
+
+ union
+ {
+ RTDIRENTRYEX Core;
+ uint8_t abPadding[RTPATHRM_DIR_MAX_ENTRY_SIZE];
+ } DirEntry;
+
+ return rtPathRmRecursive(pOpts, szPath, strlen(szPath), &DirEntry.Core);
+ }
+ if (pOpts->fDirsAndOther)
+ return rtPathRmOneDir(pOpts, pszPath);
+ return rtPathRmError(pOpts, pszPath, VERR_IS_A_DIRECTORY, "Cannot remove '%s': %Rrc\n", pszPath, VERR_IS_A_DIRECTORY);
+
+ case RTFS_TYPE_SYMLINK:
+ return rtPathRmOneSymlink(pOpts, pszPath);
+
+ case RTFS_TYPE_FIFO:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_SOCKET:
+ return rtPathRmOneFile(pOpts, pszPath, &ObjInfo);
+
+ case RTFS_TYPE_WHITEOUT:
+ default:
+ return rtPathRmError(pOpts, pszPath, VERR_UNEXPECTED_FS_OBJ_TYPE,
+ "Object '%s' has an unknown file type: %o\n", pszPath, ObjInfo.Attr.fMode & RTFS_TYPE_MASK);
+
+ }
+}
+
+
+RTDECL(RTEXITCODE) RTPathRmCmd(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--dirs-and-more", 'd', RTGETOPT_REQ_NOTHING },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ { "--prompt", 'i', RTGETOPT_REQ_NOTHING },
+ { "--prompt-once", 'I', RTGETOPT_REQ_NOTHING },
+ { "--interactive", RTPATHRMCMD_OPT_INTERACTIVE, RTGETOPT_REQ_STRING },
+ { "--one-file-system", RTPATHRMCMD_OPT_ONE_FILE_SYSTEM, RTGETOPT_REQ_NOTHING },
+ { "--preserve-root", RTPATHRMCMD_OPT_PRESERVE_ROOT, RTGETOPT_REQ_NOTHING },
+ { "--no-preserve-root", RTPATHRMCMD_OPT_NO_PRESERVE_ROOT, RTGETOPT_REQ_NOTHING },
+ { "--recursive", 'R', RTGETOPT_REQ_NOTHING },
+ { "--recursive", 'r', RTGETOPT_REQ_NOTHING },
+ { "--safe-delete", 'P', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+
+ /* IPRT extensions */
+ { "--machine-readable", RTPATHRMCMD_OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING },
+ { "--machinereadable", RTPATHRMCMD_OPT_MACHINE_READABLE, RTGETOPT_REQ_NOTHING }, /* bad long option style */
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTPATHRMCMDOPTS Opts;
+ RT_ZERO(Opts);
+ Opts.fPreserveRoot = true;
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_NONE;
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'd':
+ Opts.fDirsAndOther = true;
+ break;
+
+ case 'f':
+ Opts.fForce = true;
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_NONE;
+ break;
+
+ case 'i':
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_ALL;
+ break;
+
+ case 'I':
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_ONCE;
+ break;
+
+ case RTPATHRMCMD_OPT_INTERACTIVE:
+ if (!strcmp(ValueUnion.psz, "always"))
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_ALL;
+ else if (!strcmp(ValueUnion.psz, "once"))
+ Opts.enmInteractive = RTPATHRMCMDINTERACTIVE_ONCE;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown --interactive option value: '%s'\n", ValueUnion.psz);
+ break;
+
+ case RTPATHRMCMD_OPT_ONE_FILE_SYSTEM:
+ Opts.fOneFileSystem = true;
+ break;
+
+ case RTPATHRMCMD_OPT_PRESERVE_ROOT:
+ Opts.fPreserveRoot = true;
+ break;
+
+ case RTPATHRMCMD_OPT_NO_PRESERVE_ROOT:
+ Opts.fPreserveRoot = false;
+ break;
+
+ case 'R':
+ case 'r':
+ Opts.fRecursive = true;
+ Opts.fDirsAndOther = true;
+ break;
+
+ case 'P':
+ Opts.fSafeDelete = true;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case RTPATHRMCMD_OPT_MACHINE_READABLE:
+ Opts.fMachineReadable = true;
+ break;
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ if (RT_C_IS_PRINT(s_aOptions[i].iShort))
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ else
+ RTPrintf(" %s\n", s_aOptions[i].pszLong);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ /*
+ * Options we don't support.
+ */
+ if (Opts.fOneFileSystem)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The --one-file-system option is not yet implemented.\n");
+ if (Opts.enmInteractive != RTPATHRMCMDINTERACTIVE_NONE)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "The -i, -I and --interactive options are not implemented yet.\n");
+
+ /*
+ * No files means error.
+ */
+ if (rc != VINF_GETOPT_NOT_OPTION && !Opts.fForce)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No files or directories specified.\n");
+
+ /*
+ * Machine readable init + header.
+ */
+ if (Opts.fMachineReadable)
+ {
+ int rc2 = RTStrmSetMode(g_pStdOut, true /*fBinary*/, false /*fCurrentCodeSet*/);
+ if (RT_FAILURE(rc2))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTStrmSetMode failed: %Rrc.\n", rc2);
+ static const char s_achHeader[] = "hdr_id=rm\0hdr_ver=1";
+ RTStrmWrite(g_pStdOut, s_achHeader, sizeof(s_achHeader));
+ }
+
+ /*
+ * Delete the specified files/dirs/whatever.
+ */
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ while (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ rc = rtPathRmOne(&Opts, ValueUnion.psz);
+ if (RT_FAILURE(rc))
+ rcExit = RTEXITCODE_FAILURE;
+
+ /* next */
+ rc = RTGetOpt(&GetState, &ValueUnion);
+ }
+ if (rc != 0)
+ rcExit = RTGetOptPrintError(rc, &ValueUnion);
+
+ /*
+ * Terminate the machine readable stuff.
+ */
+ if (Opts.fMachineReadable)
+ {
+ RTStrmWrite(g_pStdOut, "\0\0\0", 4);
+ rc = RTStrmFlush(g_pStdOut);
+ if (RT_FAILURE(rc) && rcExit == RTEXITCODE_SUCCESS)
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ return rcExit;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathSkipRootSpec.cpp b/src/VBox/Runtime/common/path/RTPathSkipRootSpec.cpp
new file mode 100644
index 00000000..999ec147
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathSkipRootSpec.cpp
@@ -0,0 +1,51 @@
+/* $Id: RTPathSkipRootSpec.cpp $ */
+/** @file
+ * IPRT - RTPathSkipRootSpec
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include "internal/path.h"
+
+
+RTDECL(char *) RTPathSkipRootSpec(const char *pszPath)
+{
+ return (char *)&pszPath[rtPathRootSpecLen(pszPath)];
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathSplit.cpp b/src/VBox/Runtime/common/path/RTPathSplit.cpp
new file mode 100644
index 00000000..a3253ed3
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathSplit.cpp
@@ -0,0 +1,143 @@
+/* $Id: RTPathSplit.cpp $ */
+/** @file
+ * IPRT - RTPathSplit
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(int) RTPathSplit(const char *pszPath, PRTPATHSPLIT pSplit, size_t cbSplit, uint32_t fFlags)
+{
+ /*
+ * Input validation.
+ */
+ AssertReturn(cbSplit >= RT_UOFFSETOF(RTPATHSPLIT, apszComps), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pSplit, VERR_INVALID_POINTER);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(*pszPath, VERR_PATH_ZERO_LENGTH);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0), VERR_INVALID_FLAGS);
+
+ /*
+ * Use RTPathParse to do the parsing.
+ * - This makes the ASSUMPTION that the output of this function is greater
+ * or equal to that of RTPathParsed.
+ * - We're aliasing the buffer here, so use volatile to avoid issues due to
+ * compiler optimizations.
+ */
+ RTPATHPARSED volatile *pParsedVolatile = (RTPATHPARSED volatile *)pSplit;
+ RTPATHSPLIT volatile *pSplitVolatile = (RTPATHSPLIT volatile *)pSplit;
+
+ AssertCompile(sizeof(*pParsedVolatile) <= sizeof(*pSplitVolatile));
+ AssertCompile(sizeof(pParsedVolatile->aComps[0]) <= sizeof(pSplitVolatile->apszComps[0]));
+
+ int rc = RTPathParse(pszPath, (PRTPATHPARSED)pParsedVolatile, cbSplit, fFlags);
+ if (RT_FAILURE(rc) && rc != VERR_BUFFER_OVERFLOW)
+ return rc;
+
+ /*
+ * Calculate the required buffer space.
+ */
+ uint16_t const cComps = pParsedVolatile->cComps;
+ uint16_t const fProps = pParsedVolatile->fProps;
+ uint16_t const cchPath = pParsedVolatile->cchPath;
+ uint16_t const offSuffix = pParsedVolatile->offSuffix;
+ uint32_t cbNeeded = RT_UOFFSETOF_DYN(RTPATHSPLIT, apszComps[cComps])
+ + cchPath
+ + RTPATH_PROP_FIRST_NEEDS_NO_SLASH(fProps) /* zero terminator for root spec. */
+ - RT_BOOL(fProps & RTPATH_PROP_DIR_SLASH) /* counted by cchPath, not included in the comp str. */
+ + 1; /* zero terminator. */
+ if (cbNeeded > cbSplit)
+ {
+ pSplitVolatile->cbNeeded = cbNeeded;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ Assert(RT_SUCCESS(rc));
+
+ /*
+ * Convert the array and copy the strings, both backwards.
+ */
+ char *psz = (char *)pSplit + cbNeeded;
+ uint32_t idxComp = cComps - 1;
+
+ /* the final component first (because of suffix handling). */
+ uint16_t offComp = pParsedVolatile->aComps[idxComp].off;
+ uint16_t cchComp = pParsedVolatile->aComps[idxComp].cch;
+
+ *--psz = '\0';
+ psz -= cchComp;
+ memcpy(psz, &pszPath[offComp], cchComp);
+ pSplitVolatile->apszComps[idxComp] = psz;
+
+ char *pszSuffix;
+ if (offSuffix >= offComp + cchComp)
+ pszSuffix = &psz[cchComp];
+ else
+ pszSuffix = &psz[offSuffix - offComp];
+
+ /* the remainder */
+ while (idxComp-- > 0)
+ {
+ offComp = pParsedVolatile->aComps[idxComp].off;
+ cchComp = pParsedVolatile->aComps[idxComp].cch;
+ *--psz = '\0';
+ psz -= cchComp;
+ memcpy(psz, &pszPath[offComp], cchComp);
+ pSplitVolatile->apszComps[idxComp] = psz;
+ }
+
+ /*
+ * Store / reshuffle the non-array bits. This MUST be done after finishing
+ * the array processing because there may be members in RTPATHSPLIT
+ * overlapping the array of RTPATHPARSED.
+ */
+ AssertCompileMembersSameSizeAndOffset(RTPATHPARSED, cComps, RTPATHSPLIT, cComps); Assert(pSplitVolatile->cComps == cComps);
+ AssertCompileMembersSameSizeAndOffset(RTPATHPARSED, fProps, RTPATHSPLIT, fProps); Assert(pSplitVolatile->fProps == fProps);
+ AssertCompileMembersSameSizeAndOffset(RTPATHPARSED, cchPath, RTPATHSPLIT, cchPath); Assert(pSplitVolatile->cchPath == cchPath);
+ pSplitVolatile->u16Reserved = 0;
+ pSplitVolatile->cbNeeded = cbNeeded;
+ pSplitVolatile->pszSuffix = pszSuffix;
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathSplitA.cpp b/src/VBox/Runtime/common/path/RTPathSplitA.cpp
new file mode 100644
index 00000000..ec82cfea
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathSplitA.cpp
@@ -0,0 +1,101 @@
+/* $Id: RTPathSplitA.cpp $ */
+/** @file
+ * IPRT - RTPathSplitA and RTPathSplitFree.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(int) RTPathSplitATag(const char *pszPath, PRTPATHSPLIT *ppSplit, uint32_t fFlags, const char *pszTag)
+{
+ AssertPtrReturn(ppSplit, VERR_INVALID_POINTER);
+ *ppSplit = NULL;
+
+ /*
+ * Try estimate a reasonable buffer size based on the path length.
+ * Note! No point in trying very hard to get it right.
+ */
+ size_t cbSplit = strlen(pszPath);
+ cbSplit += RT_UOFFSETOF_DYN(RTPATHSPLIT, apszComps[cbSplit / 8]) + cbSplit / 8 + 8;
+ cbSplit = RT_ALIGN(cbSplit, 64);
+ PRTPATHSPLIT pSplit = (PRTPATHSPLIT)RTMemAllocTag(cbSplit, pszTag);
+ if (pSplit == NULL)
+ return VERR_NO_MEMORY;
+
+ /*
+ * First try. If it fails due to buffer, reallocate the buffer and try again.
+ */
+ int rc = RTPathSplit(pszPath, pSplit, cbSplit, fFlags);
+ if (rc == VERR_BUFFER_OVERFLOW)
+ {
+ cbSplit = RT_ALIGN(pSplit->cbNeeded, 64);
+ RTMemFree(pSplit);
+
+ pSplit = (PRTPATHSPLIT)RTMemAllocTag(cbSplit, pszTag);
+ if (pSplit == NULL)
+ return VERR_NO_MEMORY;
+ rc = RTPathSplit(pszPath, pSplit, cbSplit, fFlags);
+ }
+
+ /*
+ * Done (one way or the other).
+ */
+ if (RT_SUCCESS(rc))
+ *ppSplit = pSplit;
+ else
+ RTMemFree(pSplit);
+ return rc;
+}
+
+
+RTDECL(void) RTPathSplitFree(PRTPATHSPLIT pSplit)
+{
+ if (pSplit)
+ {
+ Assert(pSplit->u16Reserved = UINT16_C(0xbeef));
+ RTMemFree(pSplit);
+ }
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathSplitReassemble.cpp b/src/VBox/Runtime/common/path/RTPathSplitReassemble.cpp
new file mode 100644
index 00000000..9987d2b9
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathSplitReassemble.cpp
@@ -0,0 +1,130 @@
+/* $Id: RTPathSplitReassemble.cpp $ */
+/** @file
+ * IPRT - RTPathSplitReassemble.
+ */
+
+/*
+ * Copyright (C) 2013-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTPathSplitReassemble(PRTPATHSPLIT pSplit, uint32_t fFlags, char *pszDstPath, size_t cbDstPath)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(pSplit, VERR_INVALID_POINTER);
+ AssertReturn(pSplit->cComps > 0, VERR_INVALID_PARAMETER);
+ AssertReturn(RTPATH_STR_F_IS_VALID(fFlags, 0) && !(fFlags & RTPATH_STR_F_MIDDLE), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszDstPath, VERR_INVALID_POINTER);
+ AssertReturn(cbDstPath > pSplit->cchPath, VERR_BUFFER_OVERFLOW);
+
+ /*
+ * Figure which slash to use.
+ */
+ char chSlash;
+ switch (fFlags & RTPATH_STR_F_STYLE_MASK)
+ {
+ case RTPATH_STR_F_STYLE_HOST:
+ chSlash = RTPATH_SLASH;
+ break;
+
+ case RTPATH_STR_F_STYLE_DOS:
+ chSlash = '\\';
+ break;
+
+ case RTPATH_STR_F_STYLE_UNIX:
+ chSlash = '/';
+ break;
+
+ default:
+ AssertFailedReturn(VERR_INVALID_FLAGS); /* impossible */
+ }
+
+ /*
+ * Do the joining.
+ */
+ uint32_t const cchOrgPath = pSplit->cchPath;
+ size_t cchDstPath = 0;
+ uint32_t const cComps = pSplit->cComps;
+ uint32_t idxComp = 0;
+ char *pszDst = pszDstPath;
+ size_t cchComp;
+
+ if (RTPATH_PROP_HAS_ROOT_SPEC(pSplit->fProps))
+ {
+ cchComp = strlen(pSplit->apszComps[0]);
+ cchDstPath += cchComp;
+ AssertReturn(cchDstPath <= cchOrgPath, VERR_INVALID_PARAMETER);
+ memcpy(pszDst, pSplit->apszComps[0], cchComp);
+
+ /* fix the slashes */
+ char chOtherSlash = chSlash == '\\' ? '/' : '\\';
+ while (cchComp-- > 0)
+ {
+ if (*pszDst == chOtherSlash)
+ *pszDst = chSlash;
+ pszDst++;
+ }
+ idxComp = 1;
+ }
+
+ while (idxComp < cComps)
+ {
+ cchComp = strlen(pSplit->apszComps[idxComp]);
+ cchDstPath += cchComp;
+ AssertReturn(cchDstPath <= cchOrgPath, VERR_INVALID_PARAMETER);
+ memcpy(pszDst, pSplit->apszComps[idxComp], cchComp);
+ pszDst += cchComp;
+ idxComp++;
+ if (idxComp != cComps || (pSplit->fProps & RTPATH_PROP_DIR_SLASH))
+ {
+ cchDstPath++;
+ AssertReturn(cchDstPath <= cchOrgPath, VERR_INVALID_PARAMETER);
+ *pszDst++ = chSlash;
+ }
+ }
+
+ *pszDst = '\0';
+
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathStartsWithRoot.cpp b/src/VBox/Runtime/common/path/RTPathStartsWithRoot.cpp
new file mode 100644
index 00000000..3ac7c426
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathStartsWithRoot.cpp
@@ -0,0 +1,51 @@
+/* $Id: RTPathStartsWithRoot.cpp $ */
+/** @file
+ * IPRT - RTPathStartsWithRoot
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include "internal/path.h"
+
+
+RTDECL(bool) RTPathStartsWithRoot(const char *pszPath)
+{
+ return rtPathRootSpecLen(pszPath) > 0;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathStripExt.cpp b/src/VBox/Runtime/common/path/RTPathStripExt.cpp
new file mode 100644
index 00000000..08421f28
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathStripExt.cpp
@@ -0,0 +1,53 @@
+/* $Id: RTPathStripExt.cpp $ */
+/** @file
+ * IPRT - RTPathStripSuffix
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(void) RTPathStripSuffix(char *pszPath)
+{
+ char *pszSuffix = RTPathSuffix(pszPath);
+ if (pszSuffix)
+ *pszSuffix = '\0';
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathStripFilename.cpp b/src/VBox/Runtime/common/path/RTPathStripFilename.cpp
new file mode 100644
index 00000000..6d6aa332
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathStripFilename.cpp
@@ -0,0 +1,99 @@
+/* $Id: RTPathStripFilename.cpp $ */
+/** @file
+ * IPRT - RTPathStripFilename
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/ctype.h>
+
+
+/**
+ * Strips the filename from a path. Truncates the given string in-place by overwriting the
+ * last path separator character with a null byte in a platform-neutral way.
+ *
+ * @param pszPath Path from which filename should be extracted, will be truncated.
+ * If the string contains no path separator, it will be changed to a "." string.
+ */
+RTDECL(void) RTPathStripFilename(char *pszPath)
+{
+ char *psz = pszPath;
+ char *pszLastSep = NULL;
+
+
+ for (;; psz++)
+ {
+ switch (*psz)
+ {
+ /* handle separators. */
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ case ':':
+ pszLastSep = psz + 1;
+ if (RTPATH_IS_SLASH(psz[1]))
+ pszPath = psz + 1;
+ else
+ pszPath = psz;
+ break;
+
+ case '\\':
+#endif
+ case '/':
+ pszLastSep = psz;
+ break;
+
+ /* the end */
+ case '\0':
+ if (!pszLastSep)
+ {
+ /* no directory component */
+ pszPath[0] = '.';
+ pszPath[1] = '\0';
+ }
+ else if (pszLastSep == pszPath)
+ {
+ /* only root. */
+ pszLastSep[1] = '\0';
+ }
+ else
+ pszLastSep[0] = '\0';
+ return;
+ }
+ }
+ /* will never get here */
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathStripTrailingSlash.cpp b/src/VBox/Runtime/common/path/RTPathStripTrailingSlash.cpp
new file mode 100644
index 00000000..c75c5103
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathStripTrailingSlash.cpp
@@ -0,0 +1,74 @@
+/* $Id: RTPathStripTrailingSlash.cpp $ */
+/** @file
+ * IPRT - RTPathSTripTrailingSlash
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+
+
+
+RTDECL(size_t) RTPathStripTrailingSlash(char *pszPath)
+{
+ size_t off = strlen(pszPath);
+ while (off > 1)
+ {
+ off--;
+ switch (pszPath[off])
+ {
+ case '/':
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ case '\\':
+ if ( off == 2
+ && pszPath[1] == ':'
+ && RT_C_IS_ALPHA(pszPath[0]))
+ return off + 1;
+#endif
+ pszPath[off] = '\0';
+ break;
+
+ default:
+ return off + 1;
+ }
+ }
+
+ return 1;
+}
+
diff --git a/src/VBox/Runtime/common/path/RTPathTraverseList.cpp b/src/VBox/Runtime/common/path/RTPathTraverseList.cpp
new file mode 100644
index 00000000..25439c01
--- /dev/null
+++ b/src/VBox/Runtime/common/path/RTPathTraverseList.cpp
@@ -0,0 +1,90 @@
+/* $Id: RTPathTraverseList.cpp $ */
+/** @file
+ * IPRT - RTPathTraverseList
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTDECL(int) RTPathTraverseList(const char *pszPathList, char chSep, PFNRTPATHTRAVERSER pfnCallback, void *pvUser1, void *pvUser2)
+{
+ AssertPtrNull(pszPathList);
+ Assert((unsigned int)chSep <= 127);
+
+ if (!pszPathList)
+ return VERR_END_OF_STRING;
+
+ /*
+ * Walk the path list.
+ */
+ const char *psz = pszPathList;
+ while (*psz)
+ {
+ /* Skip leading blanks - no directories with leading spaces, thank you. */
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+
+ /* Find the end of this element. */
+ const char *pszNext;
+ const char *pszEnd = strchr(psz, chSep);
+ if (!pszEnd)
+ pszEnd = pszNext = strchr(psz, '\0');
+ else
+ pszNext = pszEnd + 1;
+ if (pszEnd != psz)
+ {
+ size_t const cch = pszEnd - psz;
+ int rc = pfnCallback(psz, cch, pvUser1, pvUser2);
+ if (rc != VERR_TRY_AGAIN)
+ return rc;
+ }
+
+ /* advance */
+ psz = pszNext;
+ }
+
+ return VERR_END_OF_STRING;
+}
+RT_EXPORT_SYMBOL(RTPathTraverseList);
+
diff --git a/src/VBox/Runtime/common/path/comparepaths.cpp b/src/VBox/Runtime/common/path/comparepaths.cpp
new file mode 100644
index 00000000..159f60d2
--- /dev/null
+++ b/src/VBox/Runtime/common/path/comparepaths.cpp
@@ -0,0 +1,146 @@
+/* $Id: comparepaths.cpp $ */
+/** @file
+ * IPRT - Path Comparison.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+
+/**
+ * Helper for RTPathCompare() and RTPathStartsWith().
+ *
+ * @returns similar to strcmp.
+ * @param pszPath1 Path to compare.
+ * @param pszPath2 Path to compare.
+ * @param fLimit Limit the comparison to the length of \a pszPath2
+ * @internal
+ */
+static int rtPathCompare(const char *pszPath1, const char *pszPath2, bool fLimit)
+{
+ if (pszPath1 == pszPath2)
+ return 0;
+ if (!pszPath1)
+ return -1;
+ if (!pszPath2)
+ return 1;
+
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ for (;;)
+ {
+ RTUNICP uc1;
+ int rc = RTStrGetCpEx(&pszPath1, &uc1);
+ if (RT_SUCCESS(rc))
+ {
+ RTUNICP uc2;
+ rc = RTStrGetCpEx(&pszPath2, &uc2);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc1 == uc2)
+ {
+ if (uc1)
+ { /* likely */ }
+ else
+ return 0;
+ }
+ else
+ {
+ if (uc1 == '\\')
+ uc1 = '/';
+ else
+ uc1 = RTUniCpToUpper(uc1);
+ if (uc2 == '\\')
+ uc2 = '/';
+ else
+ uc2 = RTUniCpToUpper(uc2);
+ if (uc1 != uc2)
+ {
+ if (fLimit && uc2 == '\0')
+ return 0;
+ return uc1 > uc2 ? 1 : -1; /* (overflow/underflow paranoia) */
+ }
+ }
+ }
+ else
+ return 1;
+ }
+ else
+ return -1;
+ }
+#else
+ if (!fLimit)
+ return strcmp(pszPath1, pszPath2);
+ return strncmp(pszPath1, pszPath2, strlen(pszPath2));
+#endif
+}
+
+
+RTDECL(int) RTPathCompare(const char *pszPath1, const char *pszPath2)
+{
+ return rtPathCompare(pszPath1, pszPath2, false /* full path lengths */);
+}
+
+
+RTDECL(bool) RTPathStartsWith(const char *pszPath, const char *pszParentPath)
+{
+ if (pszPath == pszParentPath)
+ return true;
+ if (!pszPath || !pszParentPath)
+ return false;
+
+ if (rtPathCompare(pszPath, pszParentPath, true /* limited by path 2 */) != 0)
+ return false;
+
+ const size_t cchParentPath = strlen(pszParentPath);
+ if (RTPATH_IS_SLASH(pszPath[cchParentPath]))
+ return true;
+ if (pszPath[cchParentPath] == '\0')
+ return true;
+
+ /* Deal with pszParentPath = root (or having a trailing slash). */
+ if ( cchParentPath > 0
+ && RTPATH_IS_SLASH(pszParentPath[cchParentPath - 1])
+ && RTPATH_IS_SLASH(pszPath[cchParentPath - 1]))
+ return true;
+
+ return false;
+}
+
diff --git a/src/VBox/Runtime/common/path/nocrt-access.cpp b/src/VBox/Runtime/common/path/nocrt-access.cpp
new file mode 100644
index 00000000..16c554e5
--- /dev/null
+++ b/src/VBox/Runtime/common/path/nocrt-access.cpp
@@ -0,0 +1,78 @@
+/* $Id: nocrt-access.cpp $ */
+/** @file
+ * IPRT - No-CRT - access().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/unistd.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+
+
+#undef access
+int RT_NOCRT(access)(const char *pszPath, int fFlags) RT_NOEXCEPT
+{
+ RTFSOBJINFO ObjInfo;
+ int rc = RTPathQueryInfo(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(rc))
+ {
+ if (fFlags == F_OK)
+ return 0;
+ rc = 0;
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ /** @todo A proper answer on windows would require reading the security
+ * attributes and stuff. Faking it for now. */
+ if ((fFlags & W_OK) && (ObjInfo.Attr.fMode & RTFS_DOS_READONLY) && !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ rc = -1;
+ else if ((fFlags & X_OK) && !(ObjInfo.Attr.fMode & (RTFS_UNIX_IXOTH | RTFS_UNIX_IXGRP | RTFS_UNIX_IXUSR)))
+ rc = -1;
+#else
+# error "port me"
+#endif
+ if (!rc)
+ return rc;
+ errno = EACCES;
+ }
+ else
+ errno = RTErrConvertToErrno(rc);
+ return -1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(access);
+
diff --git a/src/VBox/Runtime/common/path/nocrt-unlink.cpp b/src/VBox/Runtime/common/path/nocrt-unlink.cpp
new file mode 100644
index 00000000..df2fb8b3
--- /dev/null
+++ b/src/VBox/Runtime/common/path/nocrt-unlink.cpp
@@ -0,0 +1,59 @@
+/* $Id: nocrt-unlink.cpp $ */
+/** @file
+ * IPRT - No-CRT - unlink().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/unistd.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+
+
+#undef unlink
+int RT_NOCRT(unlink)(const char *pszPath) RT_NOEXCEPT
+{
+ int rc = RTPathUnlink(pszPath, 0 /*fUnlink*/);
+ if (RT_SUCCESS(rc))
+ return 0;
+ errno = RTErrConvertToErrno(rc);
+ return -1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(unlink);
+
diff --git a/src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp b/src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp
new file mode 100644
index 00000000..47c140f3
--- /dev/null
+++ b/src/VBox/Runtime/common/path/rtPathRootSpecLen.cpp
@@ -0,0 +1,103 @@
+/* $Id: rtPathRootSpecLen.cpp $ */
+/** @file
+ * IPRT - rtPathRootSpecLen (internal).
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/path.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include "internal/path.h"
+
+/**
+ * Figures out the length of the root (or drive) specifier in @a pszPath.
+ *
+ * For UNC names, we consider the root specifier to include both the server and
+ * share names.
+ *
+ * @returns The length including all slashes. 0 if relative path.
+ *
+ * @param pszPath The path to examine.
+ */
+DECLHIDDEN(size_t) rtPathRootSpecLen(const char *pszPath)
+{
+ /*
+ * If it's an absolute path, threat the root or volume specification as
+ * component 0. UNC is making this extra fun on OS/2 and Windows as usual.
+ */
+ size_t off = 0;
+ if (RTPATH_IS_SLASH(pszPath[0]))
+ {
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ if ( RTPATH_IS_SLASH(pszPath[1])
+ && !RTPATH_IS_SLASH(pszPath[2])
+ && pszPath[2])
+ {
+ /* UNC server name */
+ off = 2;
+ while (!RTPATH_IS_SLASH(pszPath[off]) && pszPath[off])
+ off++;
+ while (RTPATH_IS_SLASH(pszPath[off]))
+ off++;
+
+ /* UNC share */
+ while (!RTPATH_IS_SLASH(pszPath[off]) && pszPath[off])
+ off++;
+ }
+ else
+#endif
+ {
+ off = 1;
+ }
+ while (RTPATH_IS_SLASH(pszPath[off]))
+ off++;
+ }
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+ else if (RT_C_IS_ALPHA(pszPath[0]) && pszPath[1] == ':')
+ {
+ off = 2;
+ while (RTPATH_IS_SLASH(pszPath[off]))
+ off++;
+ }
+#endif
+ Assert(!RTPATH_IS_SLASH(pszPath[off]));
+
+ return off;
+}
+
diff --git a/src/VBox/Runtime/common/path/rtPathVolumeSpecLen.cpp b/src/VBox/Runtime/common/path/rtPathVolumeSpecLen.cpp
new file mode 100644
index 00000000..8c8000f7
--- /dev/null
+++ b/src/VBox/Runtime/common/path/rtPathVolumeSpecLen.cpp
@@ -0,0 +1,80 @@
+/* $Id: rtPathVolumeSpecLen.cpp $ */
+/** @file
+ * IPRT - rtPathVolumeSpecLen
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+#include <iprt/ctype.h>
+#include "internal/path.h"
+
+
+
+/**
+ * Returns the length of the volume name specifier of the given path.
+ * If no such specifier zero is returned.
+ */
+DECLHIDDEN(size_t) rtPathVolumeSpecLen(const char *pszPath)
+{
+#if defined (RT_OS_OS2) || defined (RT_OS_WINDOWS)
+ if (pszPath && *pszPath)
+ {
+ /* UNC path. */
+ /** @todo r=bird: it's UNC and we have to check that the next char isn't a
+ * slash, then skip both the server and the share name. */
+ if ( (pszPath[0] == '\\' || pszPath[0] == '/')
+ && (pszPath[1] == '\\' || pszPath[1] == '/'))
+ return strcspn(pszPath + 2, "\\/") + 2;
+
+ /* Drive letter. */
+ if ( pszPath[1] == ':'
+ && RT_C_IS_ALPHA(pszPath[0]))
+ return 2;
+ }
+ return 0;
+
+#else
+ /* This isn't quite right when looking at the above stuff, but it works assuming that '//' does not mean UNC. */
+ /// @todo (dmik) well, it's better to consider there's no volume name
+ // at all on *nix systems
+ NOREF(pszPath);
+ return 0;
+// return pszPath && pszPath[0] == '/';
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/path/rtpath-expand-template.cpp.h b/src/VBox/Runtime/common/path/rtpath-expand-template.cpp.h
new file mode 100644
index 00000000..68fb1399
--- /dev/null
+++ b/src/VBox/Runtime/common/path/rtpath-expand-template.cpp.h
@@ -0,0 +1,92 @@
+/* $Id: rtpath-expand-template.cpp.h $ */
+/** @file
+ * IPRT - RTPath - Internal header that includes RTPATH_TEMPLATE_CPP_H multiple
+ * times to expand the code for different path styles.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#undef RTPATH_DELIMITER
+
+/*
+ * DOS style
+ */
+#undef RTPATH_STYLE
+#undef RTPATH_SLASH
+#undef RTPATH_SLASH_STR
+#undef RTPATH_IS_SLASH
+#undef RTPATH_IS_VOLSEP
+#undef RTPATH_STYLE_FN
+
+#define RTPATH_STYLE RTPATH_STR_F_STYLE_DOS
+#define RTPATH_SLASH '\\'
+#define RTPATH_SLASH_STR "\\"
+#define RTPATH_IS_SLASH(a_ch) ( (a_ch) == '\\' || (a_ch) == '/' )
+#define RTPATH_IS_VOLSEP(a_ch) ( (a_ch) == ':' )
+#define RTPATH_STYLE_FN(a_Name) a_Name ## StyleDos
+#include RTPATH_TEMPLATE_CPP_H
+
+/*
+ * Unix style.
+ */
+#undef RTPATH_STYLE
+#undef RTPATH_SLASH
+#undef RTPATH_SLASH_STR
+#undef RTPATH_IS_SLASH
+#undef RTPATH_IS_VOLSEP
+#undef RTPATH_STYLE_FN
+
+#define RTPATH_STYLE RTPATH_STR_F_STYLE_UNIX
+#define RTPATH_SLASH '/'
+#define RTPATH_SLASH_STR "/"
+#define RTPATH_IS_SLASH(a_ch) ( (a_ch) == '/' )
+#define RTPATH_IS_VOLSEP(a_ch) ( false )
+#define RTPATH_STYLE_FN(a_Name) a_Name ## StyleUnix
+#include RTPATH_TEMPLATE_CPP_H
+
+/*
+ * Clean up and restore the host style.
+ */
+#undef RTPATH_STYLE_FN
+#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS)
+# undef RTPATH_STYLE
+# undef RTPATH_SLASH
+# undef RTPATH_SLASH_STR
+# undef RTPATH_IS_SLASH
+# undef RTPATH_IS_VOLSEP
+# define RTPATH_STYLE RTPATH_STR_F_STYLE_DOS
+# define RTPATH_SLASH '\\'
+# define RTPATH_SLASH_STR "\\"
+# define RTPATH_IS_SLASH(a_ch) ( (a_ch) == '\\' || (a_ch) == '/' )
+# define RTPATH_IS_VOLSEP(a_ch) ( (a_ch) == ':' )
+#endif
+
diff --git a/src/VBox/Runtime/common/path/rtpath-root-length-template.cpp.h b/src/VBox/Runtime/common/path/rtpath-root-length-template.cpp.h
new file mode 100644
index 00000000..f223b857
--- /dev/null
+++ b/src/VBox/Runtime/common/path/rtpath-root-length-template.cpp.h
@@ -0,0 +1,80 @@
+/* $Id: rtpath-root-length-template.cpp.h $ */
+/** @file
+ * IPRT - rtPathRootLengthEx - Code Template.
+ *
+ * Include this from a path-style template file.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/ctype.h>
+
+
+/**
+ * Inlined helper for determining the length of the root component.
+ */
+DECLINLINE(size_t) RTPATH_STYLE_FN(rtPathRootLengthEx)(const char *pszPath, uint32_t fFlags)
+{
+ if (RTPATH_IS_SLASH(pszPath[0]))
+ {
+ if (fFlags & RTPATH_STR_F_NO_START)
+ return 0;
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ if (RTPATH_IS_SLASH(pszPath[1]))
+ {
+ /* UNC - there are exactly two prefix slashes followed by a namespace
+ or computer name, which can be empty on windows. */
+ size_t cchRoot = 2;
+ while (!RTPATH_IS_SLASH(pszPath[cchRoot]) && pszPath[cchRoot])
+ cchRoot++;
+ if (RTPATH_IS_SLASH(pszPath[cchRoot]))
+ cchRoot++;
+ return cchRoot;
+ }
+#endif
+ return 1;
+ }
+#if RTPATH_STYLE == RTPATH_STR_F_STYLE_DOS
+ if (RT_C_IS_ALPHA(pszPath[0]) && pszPath[1] == ':')
+ {
+ if (!RTPATH_IS_SLASH(pszPath[2]))
+ return 2;
+ return 3;
+ }
+#endif
+ return 0;
+}
+
diff --git a/src/VBox/Runtime/common/rand/Makefile.kup b/src/VBox/Runtime/common/rand/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/rand/Makefile.kup
diff --git a/src/VBox/Runtime/common/rand/nocrt-rand.cpp b/src/VBox/Runtime/common/rand/nocrt-rand.cpp
new file mode 100644
index 00000000..333ca454
--- /dev/null
+++ b/src/VBox/Runtime/common/rand/nocrt-rand.cpp
@@ -0,0 +1,56 @@
+/* $Id: nocrt-rand.cpp $ */
+/** @file
+ * IPRT - No-CRT - rand().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+#include <iprt/assertcompile.h>
+#include <iprt/rand.h>
+
+
+#undef rand
+int RT_NOCRT(rand)(void)
+{
+ /** @todo this isn't actually pseudo random, it mixes TSC into it. */
+ AssertCompile(sizeof(int) <= sizeof(uint32_t));
+ return (int)RTRandU32();
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(rand);
+
diff --git a/src/VBox/Runtime/common/rand/rand.cpp b/src/VBox/Runtime/common/rand/rand.cpp
new file mode 100644
index 00000000..ec540157
--- /dev/null
+++ b/src/VBox/Runtime/common/rand/rand.cpp
@@ -0,0 +1,185 @@
+/* $Id: rand.cpp $ */
+/** @file
+ * IPRT - Random Numbers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/rand.h>
+#include "internal/iprt.h"
+
+#include <iprt/time.h>
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+# include <iprt/asm-amd64-x86.h>
+#endif
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+#include <iprt/thread.h>
+#include <iprt/once.h>
+#include "internal/rand.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** For lazily initializing of the random generator. */
+static RTONCE g_rtRandOnce = RTONCE_INITIALIZER;
+/** The default random generator. */
+static RTRAND g_hRand = NIL_RTRAND;
+
+
+/**
+ * Perform lazy initialization.
+ *
+ * @returns IPRT status code.
+ * @param pvUser Ignored.
+ */
+static DECLCALLBACK(int) rtRandInitOnce(void *pvUser)
+{
+ RTRAND hRand;
+ int rc = RTRandAdvCreateSystemFaster(&hRand);
+ if (RT_FAILURE(rc))
+ rc = RTRandAdvCreateParkMiller(&hRand);
+ if (RT_SUCCESS(rc))
+ {
+#if defined(RT_ARCH_AMD64) || defined(RT_ARCH_X86)
+ RTRandAdvSeed(hRand, ASMReadTSC() >> 8);
+#else
+ RTRandAdvSeed(hRand, RTTimeNanoTS() >> 8);
+#endif
+ g_hRand = hRand;
+ }
+ else
+ AssertRC(rc);
+
+ NOREF(pvUser);
+ return rc;
+}
+
+
+/**
+ * Termination counterpart to rtRandInitOnce.
+ *
+ * @param pvUser Ignored.
+ * @param fLazyCleanUpOk Set if we're terminating the process.
+ */
+static DECLCALLBACK(void) rtRandTermOnce(void *pvUser, bool fLazyCleanUpOk)
+{
+ if (!fLazyCleanUpOk)
+ {
+ RTRAND hRand = g_hRand;
+ g_hRand = NIL_RTRAND;
+ if (hRand != NIL_RTRAND)
+ {
+ int rc = RTRandAdvDestroy(hRand);
+ AssertRC(rc);
+ }
+ }
+ NOREF(pvUser);
+}
+
+
+RTDECL(void) RTRandBytes(void *pv, size_t cb) RT_NO_THROW_DEF
+{
+ RTOnceEx(&g_rtRandOnce, rtRandInitOnce, rtRandTermOnce, NULL);
+ RTRandAdvBytes(g_hRand, pv, cb);
+}
+RT_EXPORT_SYMBOL(RTRandBytes);
+
+
+RTDECL(uint32_t) RTRandU32Ex(uint32_t u32First, uint32_t u32Last) RT_NO_THROW_DEF
+{
+ RTOnceEx(&g_rtRandOnce, rtRandInitOnce, rtRandTermOnce, NULL);
+ return RTRandAdvU32Ex(g_hRand, u32First, u32Last);
+}
+RT_EXPORT_SYMBOL(RTRandU32Ex);
+
+
+RTDECL(uint32_t) RTRandU32(void) RT_NO_THROW_DEF
+{
+ RTOnceEx(&g_rtRandOnce, rtRandInitOnce, rtRandTermOnce, NULL);
+ return RTRandAdvU32(g_hRand);
+}
+RT_EXPORT_SYMBOL(RTRandU32);
+
+
+RTDECL(int32_t) RTRandS32Ex(int32_t i32First, int32_t i32Last) RT_NO_THROW_DEF
+{
+ RTOnceEx(&g_rtRandOnce, rtRandInitOnce, rtRandTermOnce, NULL);
+ return RTRandAdvS32Ex(g_hRand, i32First, i32Last);
+}
+RT_EXPORT_SYMBOL(RTRandS32Ex);
+
+
+RTDECL(int32_t) RTRandS32(void) RT_NO_THROW_DEF
+{
+ RTOnceEx(&g_rtRandOnce, rtRandInitOnce, rtRandTermOnce, NULL);
+ return RTRandAdvS32(g_hRand);
+}
+RT_EXPORT_SYMBOL(RTRandS32);
+
+
+RTDECL(uint64_t) RTRandU64Ex(uint64_t u64First, uint64_t u64Last) RT_NO_THROW_DEF
+{
+ RTOnceEx(&g_rtRandOnce, rtRandInitOnce, rtRandTermOnce, NULL);
+ return RTRandAdvU64Ex(g_hRand, u64First, u64Last);
+}
+RT_EXPORT_SYMBOL(RTRandU64Ex);
+
+
+RTDECL(uint64_t) RTRandU64(void) RT_NO_THROW_DEF
+{
+ RTOnceEx(&g_rtRandOnce, rtRandInitOnce, rtRandTermOnce, NULL);
+ return RTRandAdvU64(g_hRand);
+}
+RT_EXPORT_SYMBOL(RTRandU64);
+
+
+RTDECL(int64_t) RTRandS64Ex(int64_t i64First, int64_t i64Last) RT_NO_THROW_DEF
+{
+ RTOnceEx(&g_rtRandOnce, rtRandInitOnce, rtRandTermOnce, NULL);
+ return RTRandAdvS64Ex(g_hRand, i64First, i64Last);
+}
+RT_EXPORT_SYMBOL(RTRandS64Ex);
+
+
+RTDECL(int64_t) RTRandS64(void) RT_NO_THROW_DEF
+{
+ RTOnceEx(&g_rtRandOnce, rtRandInitOnce, rtRandTermOnce, NULL);
+ return RTRandAdvS32(g_hRand);
+}
+RT_EXPORT_SYMBOL(RTRandS64);
+
diff --git a/src/VBox/Runtime/common/rand/randadv.cpp b/src/VBox/Runtime/common/rand/randadv.cpp
new file mode 100644
index 00000000..57355556
--- /dev/null
+++ b/src/VBox/Runtime/common/rand/randadv.cpp
@@ -0,0 +1,424 @@
+/* $Id: randadv.cpp $ */
+/** @file
+ * IPRT - Random Numbers, Generic Glue.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/rand.h>
+#include "internal/iprt.h"
+
+#include <iprt/mem.h>
+#include <iprt/errcore.h>
+#include <iprt/assert.h>
+#include "internal/magics.h"
+#include "internal/rand.h"
+
+
+RTDECL(int) RTRandAdvDestroy(RTRAND hRand) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ if (hRand == NIL_RTRAND)
+ return VINF_SUCCESS;
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, VERR_INVALID_HANDLE);
+
+ /* forward the call */
+ return pThis->pfnDestroy(pThis);
+}
+RT_EXPORT_SYMBOL(RTRandAdvDestroy);
+
+
+RTDECL(int) RTRandAdvSeed(RTRAND hRand, uint64_t u64Seed) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, VERR_INVALID_HANDLE);
+
+ /* forward the call */
+ return pThis->pfnSeed(pThis, u64Seed);
+}
+RT_EXPORT_SYMBOL(RTRandAdvSeed);
+
+
+RTDECL(int) RTRandAdvSaveState(RTRAND hRand, char *pszState, size_t *pcbState) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNull(pszState);
+ AssertPtr(pcbState);
+
+ /* forward the call */
+ return pThis->pfnSaveState(pThis, pszState, pcbState);
+}
+RT_EXPORT_SYMBOL(RTRandAdvSaveState);
+
+
+RTDECL(int) RTRandAdvRestoreState(RTRAND hRand, char const *pszState) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pszState);
+
+ /* forward the call */
+ return pThis->pfnRestoreState(pThis, pszState);
+}
+RT_EXPORT_SYMBOL(RTRandAdvRestoreState);
+
+
+RTDECL(void) RTRandAdvBytes(RTRAND hRand, void *pv, size_t cb) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturnVoid(pThis);
+ AssertReturnVoid(pThis->u32Magic == RTRANDINT_MAGIC);
+ AssertPtr(pv);
+
+ /* forward the call */
+ return pThis->pfnGetBytes(pThis, (uint8_t *)pv, cb);
+}
+RT_EXPORT_SYMBOL(RTRandAdvBytes);
+
+
+RTDECL(int32_t) RTRandAdvS32Ex(RTRAND hRand, int32_t i32First, int32_t i32Last) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, INT32_MAX);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, INT32_MAX);
+
+ /* wrap the call */
+ return pThis->pfnGetU32(pThis, 0, i32Last - i32First) + i32First;
+}
+RT_EXPORT_SYMBOL(RTRandAdvS32Ex);
+
+
+RTDECL(int32_t) RTRandAdvS32(RTRAND hRand) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, INT32_MAX);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, INT32_MAX);
+
+ /* wrap the call */
+ return pThis->pfnGetU32(pThis, 0, UINT32_MAX) + INT32_MAX;
+}
+RT_EXPORT_SYMBOL(RTRandAdvS32);
+
+
+RTDECL(uint32_t) RTRandAdvU32Ex(RTRAND hRand, uint32_t u32First, uint32_t u32Last) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, UINT32_MAX);
+
+ /* forward the call */
+ return pThis->pfnGetU32(pThis, u32First, u32Last);
+}
+RT_EXPORT_SYMBOL(RTRandAdvU32Ex);
+
+
+RTDECL(uint32_t) RTRandAdvU32(RTRAND hRand) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, UINT32_MAX);
+
+ /* forward the call */
+ return pThis->pfnGetU32(pThis, 0, UINT32_MAX);
+}
+RT_EXPORT_SYMBOL(RTRandAdvU32);
+
+
+RTDECL(int64_t) RTRandAdvS64Ex(RTRAND hRand, int64_t i64First, int64_t i64Last) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, INT64_MAX);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, INT64_MAX);
+
+ /* wrap the call */
+ return pThis->pfnGetU64(pThis, 0, i64Last - i64First) + i64First;
+}
+RT_EXPORT_SYMBOL(RTRandAdvS64Ex);
+
+
+RTDECL(int64_t) RTRandAdvS64(RTRAND hRand) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, INT64_MAX);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, INT64_MAX);
+
+ /* wrap the call */
+ return pThis->pfnGetU64(pThis, 0, UINT64_MAX) + INT64_MAX;
+}
+RT_EXPORT_SYMBOL(RTRandAdvS64);
+
+
+RTDECL(uint64_t) RTRandAdvU64Ex(RTRAND hRand, uint64_t u64First, uint64_t u64Last) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, UINT64_MAX);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, UINT64_MAX);
+
+ /* forward the call */
+ return pThis->pfnGetU64(pThis, u64First, u64Last);
+}
+RT_EXPORT_SYMBOL(RTRandAdvU64Ex);
+
+
+RTDECL(uint64_t) RTRandAdvU64(RTRAND hRand) RT_NO_THROW_DEF
+{
+ /* Validate. */
+ PRTRANDINT pThis = hRand;
+ AssertPtrReturn(pThis, UINT64_MAX);
+ AssertReturn(pThis->u32Magic == RTRANDINT_MAGIC, UINT64_MAX);
+
+ /* forward the call */
+ return pThis->pfnGetU64(pThis, 0, UINT64_MAX);
+}
+RT_EXPORT_SYMBOL(RTRandAdvU64);
+
+
+DECL_HIDDEN_CALLBACK(void) rtRandAdvSynthesizeBytesFromU32(PRTRANDINT pThis, uint8_t *pb, size_t cb)
+{
+ while (cb > 0)
+ {
+ uint32_t u32 = pThis->pfnGetU32(pThis, 0, UINT32_MAX);
+ switch (cb)
+ {
+ case 4:
+ pb[3] = (uint8_t)(u32 >> 24); RT_FALL_THRU();
+ case 3:
+ pb[2] = (uint8_t)(u32 >> 16); RT_FALL_THRU();
+ case 2:
+ pb[1] = (uint8_t)(u32 >> 8); RT_FALL_THRU();
+ case 1:
+ pb[0] = (uint8_t)u32;
+ return; /* done */
+
+ default:
+ pb[0] = (uint8_t)u32;
+ pb[1] = (uint8_t)(u32 >> 8);
+ pb[2] = (uint8_t)(u32 >> 16);
+ pb[3] = (uint8_t)(u32 >> 24);
+ break;
+ }
+
+ /* advance */
+ cb -= 4;
+ pb += 4;
+ }
+}
+
+
+DECL_HIDDEN_CALLBACK(void) rtRandAdvSynthesizeBytesFromU64(PRTRANDINT pThis, uint8_t *pb, size_t cb)
+{
+ while (cb > 0)
+ {
+ uint64_t u64 = pThis->pfnGetU64(pThis, 0, UINT64_MAX);
+ switch (cb)
+ {
+ case 8:
+ pb[7] = (uint8_t)(u64 >> 56); RT_FALL_THRU();
+ case 7:
+ pb[6] = (uint8_t)(u64 >> 48); RT_FALL_THRU();
+ case 6:
+ pb[5] = (uint8_t)(u64 >> 40); RT_FALL_THRU();
+ case 5:
+ pb[4] = (uint8_t)(u64 >> 32); RT_FALL_THRU();
+ case 4:
+ pb[3] = (uint8_t)(u64 >> 24); RT_FALL_THRU();
+ case 3:
+ pb[2] = (uint8_t)(u64 >> 16); RT_FALL_THRU();
+ case 2:
+ pb[1] = (uint8_t)(u64 >> 8); RT_FALL_THRU();
+ case 1:
+ pb[0] = (uint8_t)u64;
+ return; /* done */
+
+ default:
+ pb[0] = (uint8_t)u64;
+ pb[1] = (uint8_t)(u64 >> 8);
+ pb[2] = (uint8_t)(u64 >> 16);
+ pb[3] = (uint8_t)(u64 >> 24);
+ pb[4] = (uint8_t)(u64 >> 32);
+ pb[5] = (uint8_t)(u64 >> 40);
+ pb[6] = (uint8_t)(u64 >> 48);
+ pb[7] = (uint8_t)(u64 >> 56);
+ break;
+ }
+
+ /* advance */
+ cb -= 8;
+ pb += 8;
+ }
+}
+
+
+DECL_HIDDEN_CALLBACK(uint32_t) rtRandAdvSynthesizeU32FromBytes(PRTRANDINT pThis, uint32_t u32First, uint32_t u32Last)
+{
+ union
+ {
+ uint32_t off;
+ uint8_t ab[5];
+ } u;
+
+ const uint32_t offLast = u32Last - u32First;
+ if (offLast == UINT32_MAX)
+ /* get 4 random bytes and return them raw. */
+ pThis->pfnGetBytes(pThis, &u.ab[0], sizeof(u.off));
+ else if (!(offLast & UINT32_C(0xf0000000)))
+ {
+ /* get 4 random bytes and do simple squeeze. */
+ pThis->pfnGetBytes(pThis, &u.ab[0], sizeof(u.off));
+ u.off %= offLast + 1;
+ u.off += u32First;
+ }
+ else
+ {
+ /* get 5 random bytes and do shifted squeeze. (this ain't perfect) */
+ pThis->pfnGetBytes(pThis, &u.ab[0], sizeof(u.ab));
+ u.off %= (offLast >> 4) + 1;
+ u.off <<= 4;
+ u.off |= u.ab[4] & 0xf;
+ if (u.off > offLast)
+ u.off = offLast;
+ u.off += u32First;
+ }
+ return u.off;
+}
+
+
+DECL_HIDDEN_CALLBACK(uint32_t) rtRandAdvSynthesizeU32FromU64(PRTRANDINT pThis, uint32_t u32First, uint32_t u32Last)
+{
+ return (uint32_t)pThis->pfnGetU64(pThis, u32First, u32Last);
+}
+
+
+DECL_HIDDEN_CALLBACK(uint64_t) rtRandAdvSynthesizeU64FromBytes(PRTRANDINT pThis, uint64_t u64First, uint64_t u64Last)
+{
+ union
+ {
+ uint64_t off;
+ uint32_t off32;
+ uint8_t ab[9];
+ } u;
+
+ const uint64_t offLast = u64Last - u64First;
+ if (offLast == UINT64_MAX)
+ /* get 8 random bytes and return them raw. */
+ pThis->pfnGetBytes(pThis, &u.ab[0], sizeof(u.off));
+ else if (!(offLast & UINT64_C(0xf000000000000000)))
+ {
+ /* get 8 random bytes and do simple squeeze. */
+ pThis->pfnGetBytes(pThis, &u.ab[0], sizeof(u.off));
+ u.off %= offLast + 1;
+ u.off += u64First;
+ }
+ else
+ {
+ /* get 9 random bytes and do shifted squeeze. (this ain't perfect) */
+ pThis->pfnGetBytes(pThis, &u.ab[0], sizeof(u.ab));
+ u.off %= (offLast >> 4) + 1;
+ u.off <<= 4;
+ u.off |= u.ab[8] & 0xf;
+ if (u.off > offLast)
+ u.off = offLast;
+ u.off += u64First;
+ }
+ return u.off;
+}
+
+
+DECL_HIDDEN_CALLBACK(uint64_t) rtRandAdvSynthesizeU64FromU32(PRTRANDINT pThis, uint64_t u64First, uint64_t u64Last)
+{
+ uint64_t off = u64Last - u64First;
+ if (off <= UINT32_MAX)
+ return (uint64_t)pThis->pfnGetU32(pThis, 0, (uint32_t)off) + u64First;
+
+ return ( (uint64_t)pThis->pfnGetU32(pThis, 0, UINT32_MAX)
+ | ((uint64_t)pThis->pfnGetU32(pThis, 0, (uint32_t)(off >> 32)) << 32))
+ + u64First;
+}
+
+
+/** @copydoc RTRANDINT::pfnSeed */
+DECL_HIDDEN_CALLBACK(int) rtRandAdvStubSeed(PRTRANDINT pThis, uint64_t u64Seed)
+{
+ NOREF(pThis);
+ NOREF(u64Seed);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/** @copydoc RTRANDINT::pfnSaveState */
+DECL_HIDDEN_CALLBACK(int) rtRandAdvStubSaveState(PRTRANDINT pThis, char *pszState, size_t *pcbState)
+{
+ NOREF(pThis);
+ NOREF(pszState);
+ NOREF(pcbState);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/** @copydoc RTRANDINT::pfnRestoreState */
+DECL_HIDDEN_CALLBACK(int) rtRandAdvStubRestoreState(PRTRANDINT pThis, char const *pszState)
+{
+ NOREF(pThis);
+ NOREF(pszState);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/** @copydoc RTRANDINT::pfnDestroy */
+DECL_HIDDEN_CALLBACK(int) rtRandAdvDefaultDestroy(PRTRANDINT pThis)
+{
+ pThis->u32Magic = ~RTRANDINT_MAGIC;
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/rand/randparkmiller.cpp b/src/VBox/Runtime/common/rand/randparkmiller.cpp
new file mode 100644
index 00000000..44811820
--- /dev/null
+++ b/src/VBox/Runtime/common/rand/randparkmiller.cpp
@@ -0,0 +1,220 @@
+/* $Id: randparkmiller.cpp $ */
+/** @file
+ * IPRT - Random Numbers, Park-Miller Pseudo Random.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/rand.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm-math.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/errcore.h>
+#include "internal/rand.h"
+#include "internal/magics.h"
+
+
+
+DECLINLINE(uint32_t) rtRandParkMillerU31(uint32_t *pu32Ctx)
+{
+ /*
+ * Park-Miller random number generator:
+ * X2 = X1 * g mod n.
+ *
+ * We use the constants suggested by Park and Miller:
+ * n = 2^31 - 1 = INT32_MAX
+ * g = 7^5 = 16807
+ *
+ * This will produce numbers in the range [0..INT32_MAX-1], which is
+ * almost 31-bits. We'll ignore the missing number for now and settle
+ * for just filling in the missing bit instead (the caller does this).
+ */
+ uint32_t x1 = *pu32Ctx;
+ if (!x1)
+ x1 = 20080806;
+ /*uint32_t x2 = ((uint64_t)x1 * 16807) % INT32_MAX;*/
+ uint32_t x2 = ASMModU64ByU32RetU32(ASMMult2xU32RetU64(x1, 16807), INT32_MAX);
+ return *pu32Ctx = x2;
+}
+
+
+/** @copydoc RTRANDINT::pfnGetU32 */
+static DECLCALLBACK(uint32_t) rtRandParkMillerGetU32(PRTRANDINT pThis, uint32_t u32First, uint32_t u32Last)
+{
+ uint32_t off;
+ uint32_t offLast = u32Last - u32First;
+ if (offLast == UINT32_MAX)
+ {
+ /* 30 + 2 bit (make up for the missing INT32_MAX value) */
+ off = rtRandParkMillerU31(&pThis->u.ParkMiller.u32Ctx);
+ if (pThis->u.ParkMiller.cBits < 2)
+ {
+ pThis->u.ParkMiller.u32Bits = rtRandParkMillerU31(&pThis->u.ParkMiller.u32Ctx);
+ pThis->u.ParkMiller.cBits = 30;
+ }
+ off >>= 1;
+ off |= (pThis->u.ParkMiller.u32Bits & 3) << 30;
+ pThis->u.ParkMiller.u32Bits >>= 2;
+ pThis->u.ParkMiller.cBits -= 2;
+ }
+ else if (offLast == (uint32_t)INT32_MAX - 1)
+ /* The exact range. */
+ off = rtRandParkMillerU31(&pThis->u.ParkMiller.u32Ctx);
+ else if (offLast < UINT32_C(0x07ffffff))
+ {
+ /* Requested 23 or fewer bits, just lose the lower bit. */
+ off = rtRandParkMillerU31(&pThis->u.ParkMiller.u32Ctx);
+ off >>= 1;
+ off %= (offLast + 1);
+ }
+ else
+ {
+ /*
+ * 30 + 6 bits.
+ */
+ uint64_t off64 = rtRandParkMillerU31(&pThis->u.ParkMiller.u32Ctx);
+ if (pThis->u.ParkMiller.cBits < 6)
+ {
+ pThis->u.ParkMiller.u32Bits = rtRandParkMillerU31(&pThis->u.ParkMiller.u32Ctx);
+ pThis->u.ParkMiller.cBits = 30;
+ }
+ off64 >>= 1;
+ off64 |= (uint64_t)(pThis->u.ParkMiller.u32Bits & 0x3f) << 30;
+ pThis->u.ParkMiller.u32Bits >>= 6;
+ pThis->u.ParkMiller.cBits -= 6;
+ off = ASMModU64ByU32RetU32(off64, offLast + 1);
+ }
+ return off + u32First;
+}
+
+
+/** @copydoc RTRANDINT::pfnSeed */
+static DECLCALLBACK(int) rtRandParkMillerSeed(PRTRANDINT pThis, uint64_t u64Seed)
+{
+ pThis->u.ParkMiller.u32Ctx = (uint32_t)u64Seed;
+ pThis->u.ParkMiller.u32Bits = 0;
+ pThis->u.ParkMiller.cBits = 0;
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTRANDINT::pfnSaveState */
+static DECLCALLBACK(int) rtRandParkMillerSaveState(PRTRANDINT pThis, char *pszState, size_t *pcbState)
+{
+#define RTRAND_PARKMILLER_STATE_SIZE (3+8+1+8+1+2+1+1)
+
+ if (*pcbState < RTRAND_PARKMILLER_STATE_SIZE)
+ {
+ *pcbState = RTRAND_PARKMILLER_STATE_SIZE;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ RTStrPrintf(pszState, *pcbState, "PM:%08RX32,%08RX32,%02x;",
+ pThis->u.ParkMiller.u32Ctx,
+ pThis->u.ParkMiller.u32Bits,
+ pThis->u.ParkMiller.cBits);
+ return VINF_SUCCESS;
+}
+
+
+/** @copydoc RTRANDINT::pfnRestoreState */
+static DECLCALLBACK(int) rtRandParkMillerRestoreState(PRTRANDINT pThis, char const *pszState)
+{
+ /* marker */
+ if ( pszState[0] != 'P'
+ || pszState[1] != 'M'
+ || pszState[2] != ':')
+ return VERR_PARSE_ERROR;
+ pszState += 3;
+
+ /* u32Ctx */
+ char *pszNext = NULL;
+ uint32_t u32Ctx;
+ int rc = RTStrToUInt32Ex(pszState, &pszNext, 16, &u32Ctx);
+ if ( rc != VWRN_TRAILING_CHARS
+ || pszNext != pszState + 8
+ || *pszNext != ',')
+ return VERR_PARSE_ERROR;
+ pszState += 8 + 1;
+
+ /* u32Bits */
+ uint32_t u32Bits;
+ rc = RTStrToUInt32Ex(pszState, &pszNext, 16, &u32Bits);
+ if ( rc != VWRN_TRAILING_CHARS
+ || pszNext != pszState + 8
+ || *pszNext != ',')
+ return VERR_PARSE_ERROR;
+ pszState += 8 + 1;
+
+ /* cBits */
+ uint32_t cBits;
+ rc = RTStrToUInt32Ex(pszState, &pszNext, 16, &cBits);
+ if ( rc != VWRN_TRAILING_CHARS
+ || pszNext != pszState + 2
+ || *pszNext != ';'
+ || pszNext[1] != '\0')
+ return VERR_PARSE_ERROR;
+
+ /* commit */
+ pThis->u.ParkMiller.u32Ctx = u32Ctx;
+ pThis->u.ParkMiller.u32Bits = u32Bits;
+ pThis->u.ParkMiller.cBits = cBits;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTRandAdvCreateParkMiller(PRTRAND phRand) RT_NO_THROW_DEF
+{
+ PRTRANDINT pThis = (PRTRANDINT)RTMemAlloc(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+ pThis->u32Magic = RTRANDINT_MAGIC;
+ pThis->pfnGetBytes= rtRandAdvSynthesizeBytesFromU32;
+ pThis->pfnGetU32 = rtRandParkMillerGetU32;
+ pThis->pfnGetU64 = rtRandAdvSynthesizeU64FromU32;
+ pThis->pfnSeed = rtRandParkMillerSeed;
+ pThis->pfnSaveState = rtRandParkMillerSaveState;
+ pThis->pfnRestoreState = rtRandParkMillerRestoreState;
+ pThis->pfnDestroy = rtRandAdvDefaultDestroy;
+ pThis->u.ParkMiller.u32Ctx = 0x20080806;
+ pThis->u.ParkMiller.u32Bits = 0;
+ pThis->u.ParkMiller.cBits = 0;
+ *phRand = pThis;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTRandAdvCreateParkMiller);
+
diff --git a/src/VBox/Runtime/common/rest/Makefile.kup b/src/VBox/Runtime/common/rest/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/Makefile.kup
diff --git a/src/VBox/Runtime/common/rest/RTCRestAnyObject.cpp b/src/VBox/Runtime/common/rest/RTCRestAnyObject.cpp
new file mode 100644
index 00000000..cc202015
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestAnyObject.cpp
@@ -0,0 +1,606 @@
+/* $Id: RTCRestAnyObject.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestAnyObject implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restanyobject.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/cpp/restoutput.h>
+
+
+
+/**
+ * Default constructor.
+ */
+RTCRestAnyObject::RTCRestAnyObject() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_pData(NULL)
+{
+ m_fNullIndicator = true;
+}
+
+
+/**
+ * Destructor.
+ */
+RTCRestAnyObject::~RTCRestAnyObject()
+{
+ if (m_pData)
+ {
+ delete m_pData;
+ m_pData = NULL;
+ }
+}
+
+
+/**
+ * Copy constructor.
+ */
+RTCRestAnyObject::RTCRestAnyObject(RTCRestAnyObject const &a_rThat)
+ : RTCRestObjectBase()
+ , m_pData(NULL)
+{
+ int rc = assignCopy(a_rThat);
+ if (RT_FAILURE(rc))
+ throw std::bad_alloc();
+}
+
+
+/**
+ * Copy assignment operator.
+ */
+RTCRestAnyObject &RTCRestAnyObject::operator=(RTCRestAnyObject const &a_rThat)
+{
+ int rc = assignCopy(a_rThat);
+ if (RT_FAILURE(rc))
+ throw std::bad_alloc();
+ return *this;
+}
+
+
+/**
+ * Safe copy assignment method.
+ */
+int RTCRestAnyObject::assignCopy(RTCRestAnyObject const &a_rThat) RT_NOEXCEPT
+{
+ setNull();
+ if ( !a_rThat.m_fNullIndicator
+ && a_rThat.m_pData != NULL)
+ {
+ kTypeClass enmType = a_rThat.m_pData->typeClass();
+ switch (enmType)
+ {
+ case kTypeClass_Bool: return assignCopy(*(RTCRestBool const *)a_rThat.m_pData);
+ case kTypeClass_Int64: return assignCopy(*(RTCRestInt64 const *)a_rThat.m_pData);
+ case kTypeClass_Int32: return assignCopy(*(RTCRestInt32 const *)a_rThat.m_pData);
+ case kTypeClass_Int16: return assignCopy(*(RTCRestInt16 const *)a_rThat.m_pData);
+ case kTypeClass_Double: return assignCopy(*(RTCRestDouble const *)a_rThat.m_pData);
+ case kTypeClass_String: return assignCopy(*(RTCRestString const *)a_rThat.m_pData);
+ case kTypeClass_Array: return assignCopy(*(RTCRestArray<RTCRestAnyObject> const *)a_rThat.m_pData);
+ case kTypeClass_StringMap: return assignCopy(*(RTCRestStringMap<RTCRestAnyObject> const *)a_rThat.m_pData);
+
+ /* Currently unused of invalid: */
+ case kTypeClass_Date:
+ case kTypeClass_Uuid:
+ case kTypeClass_Binary:
+ case kTypeClass_StringEnum:
+ case kTypeClass_AnyObject:
+ case kTypeClass_DataObject:
+ case kTypeClass_Invalid:
+ AssertFailedReturn(VERR_REST_INTERNAL_ERROR_7);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Safe copy assignment method, boolean variant.
+ */
+int RTCRestAnyObject::assignCopy(RTCRestBool const &a_rThat) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestBool *pData = new (std::nothrow) RTCRestBool();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignCopy(a_rThat);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe copy assignment method, int64_t variant.
+ */
+int RTCRestAnyObject::assignCopy(RTCRestInt64 const &a_rThat) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestInt64 *pData = new (std::nothrow) RTCRestInt64();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignCopy(a_rThat);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe copy assignment method, int32_t variant.
+ */
+int RTCRestAnyObject::assignCopy(RTCRestInt32 const &a_rThat) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestInt32 *pData = new (std::nothrow) RTCRestInt32();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignCopy(a_rThat);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe copy assignment method, int16_t variant.
+ */
+int RTCRestAnyObject::assignCopy(RTCRestInt16 const &a_rThat) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestInt16 *pData = new (std::nothrow) RTCRestInt16();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignCopy(a_rThat);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe copy assignment method, double variant.
+ */
+int RTCRestAnyObject::assignCopy(RTCRestDouble const &a_rThat) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestDouble *pData = new (std::nothrow) RTCRestDouble();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignCopy(a_rThat);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe copy assignment method, string variant.
+ */
+int RTCRestAnyObject::assignCopy(RTCRestString const &a_rThat) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestString *pData = new (std::nothrow) RTCRestString();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignCopy(a_rThat);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe copy assignment method, array variant.
+ */
+int RTCRestAnyObject::assignCopy(RTCRestArray<RTCRestAnyObject> const &a_rThat) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestArray<RTCRestAnyObject> *pData = new (std::nothrow) RTCRestArray<RTCRestAnyObject>();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignCopy(a_rThat);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe copy assignment method, string map variant.
+ */
+int RTCRestAnyObject::assignCopy(RTCRestStringMap<RTCRestAnyObject> const &a_rThat) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestStringMap<RTCRestAnyObject> *pData = new (std::nothrow) RTCRestStringMap<RTCRestAnyObject>();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignCopy(a_rThat);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe value assignment method, boolean variant.
+ */
+int RTCRestAnyObject::assignValue(bool a_fValue) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestBool *pData = new (std::nothrow) RTCRestBool();
+ if (pData)
+ {
+ m_pData = pData;
+ pData->assignValue(a_fValue);
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe value assignment method, int64_t variant.
+ */
+int RTCRestAnyObject::assignValue(int64_t a_iValue) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestInt64 *pData = new (std::nothrow) RTCRestInt64();
+ if (pData)
+ {
+ m_pData = pData;
+ pData->assignValue(a_iValue);
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe value assignment method, int32_t variant.
+ */
+int RTCRestAnyObject::assignValue(int32_t a_iValue) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestInt32 *pData = new (std::nothrow) RTCRestInt32();
+ if (pData)
+ {
+ m_pData = pData;
+ pData->assignValue(a_iValue);
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe value assignment method, int16_t variant.
+ */
+int RTCRestAnyObject::assignValue(int16_t a_iValue) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestInt16 *pData = new (std::nothrow) RTCRestInt16();
+ if (pData)
+ {
+ m_pData = pData;
+ pData->assignValue(a_iValue);
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe value assignment method, double variant.
+ */
+int RTCRestAnyObject::assignValue(double a_iValue) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestDouble *pData = new (std::nothrow) RTCRestDouble();
+ if (pData)
+ {
+ m_pData = pData;
+ pData->assignValue(a_iValue);
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe value assignment method, string variant.
+ */
+int RTCRestAnyObject::assignValue(RTCString const &a_rValue) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestString *pData = new (std::nothrow) RTCRestString();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignNoThrow(a_rValue);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+/**
+ * Safe value assignment method, C-string variant.
+ */
+int RTCRestAnyObject::assignValue(const char *a_pszValue) RT_NOEXCEPT
+{
+ setNull();
+ RTCRestString *pData = new (std::nothrow) RTCRestString();
+ if (pData)
+ {
+ m_pData = pData;
+ m_fNullIndicator = false;
+ return pData->assignNoThrow(a_pszValue);
+ }
+ return VERR_NO_MEMORY;
+}
+
+
+RTCRestObjectBase *RTCRestAnyObject::baseClone() const RT_NOEXCEPT
+{
+ RTCRestAnyObject *pClone = new (std::nothrow) RTCRestAnyObject();
+ if (pClone)
+ {
+ int rc = pClone->assignCopy(*this);
+ if (RT_SUCCESS(rc))
+ return pClone;
+ delete pClone;
+ }
+ return NULL;
+}
+
+
+int RTCRestAnyObject::setNull(void) RT_NOEXCEPT
+{
+ if (m_pData)
+ {
+ delete m_pData;
+ m_pData = NULL;
+ }
+ return RTCRestObjectBase::setNull();
+}
+
+
+int RTCRestAnyObject::resetToDefault() RT_NOEXCEPT
+{
+ if (m_pData)
+ return m_pData->resetToDefault();
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestAnyObject::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (m_pData)
+ return m_pData->serializeAsJson(a_rDst);
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestAnyObject::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ setNull();
+
+ RTJSONVALTYPE enmType = RTJsonValueGetType(a_rCursor.m_hValue);
+ switch (enmType)
+ {
+ case RTJSONVALTYPE_OBJECT:
+ {
+ RTCRestStringMap<RTCRestAnyObject> *pMap = new (std::nothrow) RTCRestStringMap<RTCRestAnyObject>();
+ if (pMap)
+ {
+ m_pData = pMap;
+ m_fNullIndicator = false;
+ return pMap->deserializeFromJson(a_rCursor);
+ }
+ break;
+ }
+
+ case RTJSONVALTYPE_ARRAY:
+ {
+ RTCRestArray<RTCRestAnyObject> *pArray = new (std::nothrow) RTCRestArray<RTCRestAnyObject>();
+ if (pArray)
+ {
+ m_pData = pArray;
+ m_fNullIndicator = false;
+ return pArray->deserializeFromJson(a_rCursor);
+ }
+ break;
+ }
+
+ case RTJSONVALTYPE_STRING:
+ {
+ RTCRestString *pString = new (std::nothrow) RTCRestString();
+ if (pString)
+ {
+ m_pData = pString;
+ m_fNullIndicator = false;
+ return pString->deserializeFromJson(a_rCursor);
+ }
+ break;
+ }
+
+ case RTJSONVALTYPE_INTEGER:
+ {
+ RTCRestInt64 *pInt64 = new (std::nothrow) RTCRestInt64();
+ if (pInt64)
+ {
+ m_pData = pInt64;
+ m_fNullIndicator = false;
+ return pInt64->deserializeFromJson(a_rCursor);
+ }
+ break;
+ }
+
+ case RTJSONVALTYPE_NUMBER:
+ {
+ RTCRestDouble *pDouble = new (std::nothrow) RTCRestDouble();
+ if (pDouble)
+ {
+ m_pData = pDouble;
+ m_fNullIndicator = false;
+ return pDouble->deserializeFromJson(a_rCursor);
+ }
+ break;
+ }
+
+ case RTJSONVALTYPE_NULL:
+ return VINF_SUCCESS;
+
+ case RTJSONVALTYPE_TRUE:
+ case RTJSONVALTYPE_FALSE:
+ {
+ RTCRestBool *pBool = new (std::nothrow) RTCRestBool();
+ if (pBool)
+ {
+ m_pData = pBool;
+ m_fNullIndicator = false;
+ pBool->assignValue(enmType == RTJSONVALTYPE_TRUE);
+ return VINF_SUCCESS;
+ }
+ break;
+ }
+
+ /* no break. */
+ case RTJSONVALTYPE_INVALID:
+ case RTJSONVALTYPE_32BIT_HACK:
+ break;
+ }
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_WRONG_TYPE, "RTCRestAnyObject found %d (%s)",
+ enmType, RTJsonValueTypeName(enmType));
+}
+
+
+int RTCRestAnyObject::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ if (m_pData)
+ return m_pData->toString(a_pDst, a_fFlags);
+ if (a_fFlags & kToString_Append)
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("null"));
+}
+
+
+int RTCRestAnyObject::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ return RTCRestObjectBase::fromString(a_rValue, a_pszName, a_pErrInfo, a_fFlags);
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestAnyObject::typeClass(void) const RT_NOEXCEPT
+{
+ return kTypeClass_AnyObject;
+}
+
+
+const char *RTCRestAnyObject::typeName(void) const RT_NOEXCEPT
+{
+ if (m_pData)
+ {
+ kTypeClass enmType = m_pData->typeClass();
+ switch (enmType)
+ {
+ case kTypeClass_Bool: return "RTCRestAnyObject[Bool]";
+ case kTypeClass_Int64: return "RTCRestAnyObject[Int64]";
+ case kTypeClass_Int32: return "RTCRestAnyObject[Int32]";
+ case kTypeClass_Int16: return "RTCRestAnyObject[Int16]";
+ case kTypeClass_Double: return "RTCRestAnyObject[Double]";
+ case kTypeClass_String: return "RTCRestAnyObject[String]";
+ case kTypeClass_Array: return "RTCRestAnyObject[Array]";
+ case kTypeClass_StringMap: return "RTCRestAnyObject[StringMap]";
+
+ /* Currently unused of invalid: */
+ case kTypeClass_Date:
+ case kTypeClass_Uuid:
+ case kTypeClass_Binary:
+ case kTypeClass_StringEnum:
+ case kTypeClass_DataObject:
+ case kTypeClass_AnyObject:
+ case kTypeClass_Invalid:
+ AssertFailed();
+ }
+ }
+ return "RTCRestAnyObject";
+}
+
+
+/**
+ * Factory method.
+ */
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestAnyObject::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestAnyObject();
+}
+
+
+/**
+ * @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON
+ */
+/*static*/ DECLCALLBACK(int)
+RTCRestAnyObject::deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance)
+{
+ RTCRestObjectBase *pObj = createInstance();
+ *a_ppInstance = pObj;
+ if (pObj)
+ return pObj->deserializeFromJson(a_rCursor);
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory");
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestArrayBase.cpp b/src/VBox/Runtime/common/rest/RTCRestArrayBase.cpp
new file mode 100644
index 00000000..5bd22717
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestArrayBase.cpp
@@ -0,0 +1,496 @@
+/* $Id: RTCRestArrayBase.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestArrayBase implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restarray.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/cpp/restoutput.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Separator characters. */
+static char const g_szSep[RTCRestObjectBase::kCollectionFormat_Mask + 1] = ",, \t|,,";
+
+
+/**
+ * Default destructor.
+ */
+RTCRestArrayBase::RTCRestArrayBase() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_papElements(NULL)
+ , m_cElements(0)
+ , m_cCapacity(0)
+{
+}
+
+
+#if 0 /* should not be used */
+/**
+ * Copy constructor.
+ */
+RTCRestArrayBase::RTCRestArrayBase(RTCRestArrayBase const &a_rThat);
+#endif
+
+/**
+ * Destructor.
+ */
+RTCRestArrayBase::~RTCRestArrayBase()
+{
+ clear();
+
+ if (m_papElements)
+ {
+ RTMemFree(m_papElements);
+ m_papElements = NULL;
+ m_cCapacity = 0;
+ }
+}
+
+
+#if 0 /* should not be used */
+/**
+ * Copy assignment operator.
+ */
+RTCRestArrayBase &RTCRestArrayBase::operator=(RTCRestArrayBase const &a_rThat);
+#endif
+
+
+/*********************************************************************************************************************************
+* Overridden methods *
+*********************************************************************************************************************************/
+
+RTCRestObjectBase *RTCRestArrayBase::baseClone() const RT_NOEXCEPT
+{
+ RTCRestArrayBase *pClone = createClone();
+ if (pClone)
+ {
+ int rc = pClone->copyArrayWorkerNoThrow(*this);
+ if (RT_SUCCESS(rc))
+ return pClone;
+ delete pClone;
+ }
+ return NULL;
+}
+
+
+int RTCRestArrayBase::resetToDefault() RT_NOEXCEPT
+{
+ /* The default state of an array is empty. At least for now. */
+ clear();
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestArrayBase::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ {
+ uint32_t const uOldState = a_rDst.beginArray();
+ for (size_t i = 0; i < m_cElements; i++)
+ {
+ a_rDst.valueSeparator();
+ m_papElements[i]->serializeAsJson(a_rDst);
+ }
+ a_rDst.endArray(uOldState);
+ }
+ else
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestArrayBase::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ /*
+ * Make sure the object starts out with an empty map.
+ */
+ if (m_cElements > 0)
+ clear();
+ m_fNullIndicator = false;
+
+ /*
+ * Iterate the array values.
+ */
+ RTJSONIT hIterator;
+ int rcRet = RTJsonIteratorBeginArray(a_rCursor.m_hValue, &hIterator);
+ if (RT_SUCCESS(rcRet))
+ {
+ for (size_t idxName = 0;; idxName++)
+ {
+ /* Setup sub-cursor. */
+ RTCRestJsonCursor SubCursor(a_rCursor);
+ int rc = RTJsonIteratorQueryValue(hIterator, &SubCursor.m_hValue, &SubCursor.m_pszName);
+ if (RT_SUCCESS(rc))
+ {
+ char szName[32];
+ RTStrPrintf(szName, sizeof(szName), "[%u]", idxName);
+ SubCursor.m_pszName = szName;
+
+ /* Call the static deserializeInstanceFromJson method of the value class. */
+ RTCRestObjectBase *pObj = NULL;
+ rc = deserializeValueInstanceFromJson(SubCursor, &pObj);
+ if (RT_SUCCESS(rc))
+ Assert(pObj);
+ else if (RT_SUCCESS(rcRet))
+ rcRet = rc;
+ if (pObj)
+ {
+ rc = insertWorker(~(size_t)0, pObj, false /*a_fReplace*/);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ {
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rc, "Array insert failed (index %zu): %Rrc",
+ idxName, rc);
+ delete pObj;
+ }
+ }
+ }
+ else
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonIteratorQueryValue failed: %Rrc", rc);
+
+ /*
+ * Advance.
+ */
+ rc = RTJsonIteratorNext(hIterator);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_JSON_ITERATOR_END)
+ break;
+ else
+ {
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonIteratorNext failed: %Rrc", rc);
+ break;
+ }
+ }
+
+ RTJsonIteratorFree(hIterator);
+ }
+ else if (rcRet == VERR_JSON_IS_EMPTY)
+ rcRet = VINF_SUCCESS;
+ else if ( rcRet == VERR_JSON_VALUE_INVALID_TYPE
+ && RTJsonValueGetType(a_rCursor.m_hValue) == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ rcRet = VINF_SUCCESS;
+ }
+ else
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rcRet,
+ "RTJsonIteratorBeginrray failed: %Rrc (type %s)",
+ rcRet, RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+ return rcRet;
+
+}
+
+
+int RTCRestArrayBase::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ int rc;
+ if (!m_fNullIndicator)
+ {
+ if (m_cElements)
+ {
+ char const chSep = g_szSep[a_fFlags & kCollectionFormat_Mask];
+
+ rc = m_papElements[0]->toString(a_pDst, a_fFlags);
+ for (size_t i = 1; RT_SUCCESS(rc) && i < m_cElements; i++)
+ {
+ rc = a_pDst->appendNoThrow(chSep);
+ if (RT_SUCCESS(rc))
+ rc = m_papElements[i]->toString(a_pDst, a_fFlags | kToString_Append);
+ }
+ }
+ else
+ {
+ if (!(a_fFlags & kToString_Append))
+ a_pDst->setNull();
+ rc = VINF_SUCCESS;
+ }
+ }
+ else if (a_fFlags & kToString_Append)
+ rc = a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+ else
+ rc = a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+
+ return rc;
+}
+
+
+int RTCRestArrayBase::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ /*
+ * Clear the array. If the string is empty, we have an empty array and is done.
+ */
+ if (!(a_fFlags & kToString_Append))
+ clear();
+ if (a_rValue.isEmpty())
+ return VINF_SUCCESS;
+
+ /*
+ * Look for a separator so we don't mistake a initial null element for a null array.
+ */
+ char const chSep = g_szSep[a_fFlags & kCollectionFormat_Mask];
+ size_t offSep = a_rValue.find(chSep);
+ if ( offSep != RTCString::npos
+ || !a_rValue.startsWithWord("null", RTCString::CaseInsensitive))
+ {
+ RTCString strTmp;
+ size_t offStart = 0;
+ int rcRet = VINF_SUCCESS;
+ for (;;)
+ {
+ /* Copy the element value into its own string buffer. */
+ int rc = strTmp.assignNoThrow(a_rValue, offStart, (offSep == RTCString::npos ? a_rValue.length() : offSep) - offStart);
+ AssertRCReturn(rc, rc);
+
+ /* Create a new element, insert it and pass it the value string. */
+ RTCRestObjectBase *pObj = createValue();
+ AssertPtrReturn(pObj, VERR_NO_MEMORY);
+
+ rc = insertWorker(~(size_t)0, pObj, false);
+ AssertRCReturnStmt(rc, delete pObj, rc);
+
+ char szName[128];
+ RTStrPrintf(szName, sizeof(szName), "%.*s[%zu]", 116, a_pszName ? a_pszName : "", size());
+ rc = pObj->fromString(strTmp, a_pszName, a_pErrInfo, 0);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (RT_SUCCESS(rcRet))
+ rcRet = rc;
+
+ /*
+ * Done? Otherwise advance.
+ */
+ if (offSep == RTCString::npos)
+ break;
+ offStart = offSep + 1;
+ offSep = a_rValue.find(chSep, offStart);
+ }
+ return rcRet;
+ }
+
+ /*
+ * Consider this a null array even if it could also be an array with a single
+ * null element. This is just an artifact of an imperfect serialization format.
+ */
+ setNull();
+ return VINF_SUCCESS;
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestArrayBase::typeClass(void) const RT_NOEXCEPT
+{
+ return kTypeClass_Array;
+}
+
+
+const char *RTCRestArrayBase::typeName(void) const RT_NOEXCEPT
+{
+ return "RTCRestArray<ElementType>";
+}
+
+
+
+/*********************************************************************************************************************************
+* Array methods *
+*********************************************************************************************************************************/
+
+void RTCRestArrayBase::clear() RT_NOEXCEPT
+{
+ size_t i = m_cElements;
+ while (i-- > 0)
+ {
+ delete m_papElements[i];
+ m_papElements[i] = NULL;
+ }
+ m_cElements = 0;
+ m_fNullIndicator = false;
+}
+
+
+bool RTCRestArrayBase::removeAt(size_t a_idx) RT_NOEXCEPT
+{
+ if (a_idx == ~(size_t)0)
+ a_idx = m_cElements - 1;
+ if (a_idx < m_cElements)
+ {
+ delete m_papElements[a_idx];
+ m_papElements[a_idx] = NULL;
+
+ m_cElements--;
+ if (a_idx < m_cElements)
+ memmove(&m_papElements[a_idx], &m_papElements[a_idx + 1], (m_cElements - a_idx) * sizeof(m_papElements[0]));
+ }
+ return false;
+}
+
+
+int RTCRestArrayBase::ensureCapacity(size_t a_cEnsureCapacity) RT_NOEXCEPT
+{
+ if (m_cCapacity < a_cEnsureCapacity)
+ {
+ if (a_cEnsureCapacity < 512)
+ a_cEnsureCapacity = RT_ALIGN_Z(a_cEnsureCapacity, 16);
+ else if (a_cEnsureCapacity < 16384)
+ a_cEnsureCapacity = RT_ALIGN_Z(a_cEnsureCapacity, 128);
+ else
+ a_cEnsureCapacity = RT_ALIGN_Z(a_cEnsureCapacity, 512);
+
+ void *pvNew = RTMemRealloc(m_papElements, sizeof(m_papElements[0]) * a_cEnsureCapacity);
+ if (pvNew)
+ {
+ m_papElements = (RTCRestObjectBase **)pvNew;
+ memset(&m_papElements[m_cCapacity], 0, (a_cEnsureCapacity - m_cCapacity) * sizeof(sizeof(m_papElements[0])));
+ m_cCapacity = a_cEnsureCapacity;
+ }
+ else
+ return VERR_NO_MEMORY;
+ }
+ return VINF_SUCCESS;
+}
+
+
+int RTCRestArrayBase::copyArrayWorkerNoThrow(RTCRestArrayBase const &a_rThat) RT_NOEXCEPT
+{
+ int rc;
+ clear();
+ if (a_rThat.m_cElements == 0)
+ {
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ Assert(!a_rThat.m_fNullIndicator);
+ rc = ensureCapacity(a_rThat.m_cElements);
+ if (RT_SUCCESS(rc))
+ {
+ for (size_t i = 0; i < a_rThat.m_cElements; i++)
+ {
+ AssertPtr(a_rThat.m_papElements[i]);
+ rc = insertCopyWorker(i, *a_rThat.m_papElements[i], false);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ return rc;
+ }
+ }
+ }
+ return rc;
+}
+
+void RTCRestArrayBase::copyArrayWorkerMayThrow(RTCRestArrayBase const &a_rThat)
+{
+ int rc = copyArrayWorkerNoThrow(a_rThat);
+ if (RT_SUCCESS(rc))
+ return;
+ throw std::bad_alloc();
+}
+
+
+int RTCRestArrayBase::insertWorker(size_t a_idx, RTCRestObjectBase *a_pValue, bool a_fReplace) RT_NOEXCEPT
+{
+ AssertPtrReturn(a_pValue, VERR_INVALID_POINTER);
+
+ if (a_idx == ~(size_t)0)
+ a_idx = m_cElements;
+
+ if (a_idx <= m_cElements)
+ {
+ if (a_idx == m_cElements || !a_fReplace)
+ {
+ /* Make sure we've got array space. */
+ if (m_cElements + 1 < m_cCapacity)
+ { /* kind of likely */ }
+ else
+ {
+ int rc = ensureCapacity(m_cElements + 1);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ return rc;
+ }
+
+ /* Shift following elements before inserting. */
+ if (a_idx < m_cElements)
+ memmove(&m_papElements[a_idx + 1], &m_papElements[a_idx], (m_cElements - a_idx) * sizeof(m_papElements[0]));
+ m_papElements[a_idx] = a_pValue;
+ m_cElements++;
+#ifdef RT_STRICT
+ for (size_t i = 0; i < m_cElements; i++)
+ AssertPtr(m_papElements[i]);
+#endif
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+ }
+
+ /* Replace element. */
+ delete m_papElements[a_idx];
+ m_papElements[a_idx] = a_pValue;
+ m_fNullIndicator = false;
+ return VWRN_ALREADY_EXISTS;
+ }
+ return VERR_OUT_OF_RANGE;
+}
+
+
+int RTCRestArrayBase::insertCopyWorker(size_t a_idx, RTCRestObjectBase const &a_rValue, bool a_fReplace) RT_NOEXCEPT
+{
+ int rc;
+ RTCRestObjectBase *pValueCopy = a_rValue.baseClone();
+ if (pValueCopy)
+ {
+ rc = insertWorker(a_idx, pValueCopy, a_fReplace);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ delete pValueCopy;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestClientApiBase.cpp b/src/VBox/Runtime/common/rest/RTCRestClientApiBase.cpp
new file mode 100644
index 00000000..e865f97a
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestClientApiBase.cpp
@@ -0,0 +1,326 @@
+/* $Id: RTCRestClientApiBase.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestClientApiBase implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restclient.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/http.h>
+#include <iprt/log.h>
+#include <iprt/uri.h>
+
+
+/**
+ * Default constructor.
+ */
+RTCRestClientApiBase::RTCRestClientApiBase() RT_NOEXCEPT
+ : m_hHttp(NIL_RTHTTP)
+{
+}
+
+
+/**
+ * The destructor.
+ */
+RTCRestClientApiBase::~RTCRestClientApiBase()
+{
+ if (m_hHttp != NIL_RTHTTP)
+ {
+ int rc = RTHttpDestroy(m_hHttp);
+ AssertRC(rc);
+ m_hHttp = NIL_RTHTTP;
+ }
+}
+
+
+int RTCRestClientApiBase::setCAFile(const char *pcszCAFile) RT_NOEXCEPT
+{
+ return m_strCAFile.assignNoThrow(pcszCAFile);
+}
+
+
+int RTCRestClientApiBase::setCAFile(const RTCString &strCAFile) RT_NOEXCEPT
+{
+ return m_strCAFile.assignNoThrow(strCAFile);
+}
+
+
+const char *RTCRestClientApiBase::getServerUrl(void) const RT_NOEXCEPT
+{
+ if (m_strServerUrl.isEmpty())
+ return getDefaultServerUrl();
+ return m_strServerUrl.c_str();
+}
+
+
+int RTCRestClientApiBase::setServerUrl(const char *a_pszUrl) RT_NOEXCEPT
+{
+#ifdef RT_STRICT
+ if (a_pszUrl)
+ {
+ RTURIPARSED Parsed;
+ int rc = RTUriParse(a_pszUrl, &Parsed);
+ AssertRC(rc);
+ }
+#endif
+
+ return m_strServerUrl.assignNoThrow(a_pszUrl);
+}
+
+
+int RTCRestClientApiBase::setServerUrlPart(const char *a_pszServerUrl, size_t a_offDst, size_t a_cchDst,
+ const char *a_pszSrc, size_t a_cchSrc) RT_NOEXCEPT
+{
+ if ( a_cchDst == a_cchSrc
+ && memcmp(&a_pszServerUrl[0], a_pszSrc, a_cchSrc) == 0)
+ return VINF_SUCCESS;
+
+ if (m_strServerUrl.isEmpty())
+ {
+ int rc = m_strServerUrl.assignNoThrow(a_pszServerUrl);
+ AssertRCReturn(rc, rc);
+ }
+ return m_strServerUrl.replaceNoThrow(a_offDst, a_cchDst, a_pszSrc, a_cchSrc);
+}
+
+
+int RTCRestClientApiBase::setServerScheme(const char *a_pszScheme) RT_NOEXCEPT
+{
+ /*
+ * Validate.
+ */
+ AssertReturn(a_pszScheme, VERR_INVALID_POINTER);
+ size_t const cchScheme = strlen(a_pszScheme);
+ AssertReturn(cchScheme > 0, VERR_INVALID_PARAMETER);
+ Assert(cchScheme < 16);
+#ifdef RT_STRICT
+ for (size_t i = 0; i < cchScheme; i++)
+ Assert(RT_C_IS_ALNUM(a_pszScheme[i]));
+#endif
+
+ /*
+ * Parse, compare & replace.
+ */
+ RTURIPARSED Parsed;
+ const char *pszUrl = getServerUrl();
+ int rc = RTUriParse(pszUrl, &Parsed);
+ AssertRCReturn(rc, rc);
+ return setServerUrlPart(pszUrl, 0, Parsed.cchScheme, a_pszScheme, cchScheme);
+}
+
+
+int RTCRestClientApiBase::setServerAuthority(const char *a_pszAuthority) RT_NOEXCEPT
+{
+ /*
+ * Validate.
+ */
+ AssertReturn(a_pszAuthority, VERR_INVALID_POINTER);
+ size_t const cchAuthority = strlen(a_pszAuthority);
+ AssertReturn(cchAuthority > 0, VERR_INVALID_PARAMETER);
+ Assert(memchr(a_pszAuthority, '/', cchAuthority) == NULL);
+ Assert(memchr(a_pszAuthority, '\\', cchAuthority) == NULL);
+ Assert(memchr(a_pszAuthority, '#', cchAuthority) == NULL);
+ Assert(memchr(a_pszAuthority, '?', cchAuthority) == NULL);
+
+ /*
+ * Parse, compare & replace.
+ */
+ RTURIPARSED Parsed;
+ const char *pszUrl = getServerUrl();
+ int rc = RTUriParse(pszUrl, &Parsed);
+ AssertRCReturn(rc, rc);
+ return setServerUrlPart(pszUrl, Parsed.offAuthority, Parsed.cchAuthority, a_pszAuthority, cchAuthority);
+}
+
+
+int RTCRestClientApiBase::setServerBasePath(const char *a_pszBasePath) RT_NOEXCEPT
+{
+ /*
+ * Validate.
+ */
+ AssertReturn(a_pszBasePath, VERR_INVALID_POINTER);
+ size_t const cchBasePath = strlen(a_pszBasePath);
+ AssertReturn(cchBasePath > 0, VERR_INVALID_PARAMETER);
+ Assert(memchr(a_pszBasePath, '?', cchBasePath) == NULL);
+ Assert(memchr(a_pszBasePath, '#', cchBasePath) == NULL);
+
+ /*
+ * Parse, compare & replace.
+ */
+ RTURIPARSED Parsed;
+ const char *pszUrl = getServerUrl();
+ int rc = RTUriParse(pszUrl, &Parsed);
+ AssertRCReturn(rc, rc);
+ return setServerUrlPart(pszUrl, Parsed.offPath, Parsed.cchPath, a_pszBasePath, cchBasePath);
+}
+
+
+int RTCRestClientApiBase::reinitHttpInstance() RT_NOEXCEPT
+{
+ if (m_hHttp != NIL_RTHTTP)
+ return RTHttpReset(m_hHttp, 0 /*fFlags*/);
+
+ int rc = RTHttpCreate(&m_hHttp);
+ if (RT_SUCCESS(rc) && m_strCAFile.isNotEmpty())
+ rc = RTHttpSetCAFile(m_hHttp, m_strCAFile.c_str());
+
+ if (RT_FAILURE(rc) && m_hHttp != NIL_RTHTTP)
+ {
+ RTHttpDestroy(m_hHttp);
+ m_hHttp = NIL_RTHTTP;
+ }
+ return rc;
+}
+
+
+int RTCRestClientApiBase::xmitReady(RTHTTP a_hHttp, RTCString const &a_rStrFullUrl, RTHTTPMETHOD a_enmHttpMethod,
+ RTCString const &a_rStrXmitBody, uint32_t a_fFlags) RT_NOEXCEPT
+{
+ RT_NOREF(a_hHttp, a_rStrFullUrl, a_enmHttpMethod, a_rStrXmitBody, a_fFlags);
+ return VINF_SUCCESS;
+}
+
+
+int RTCRestClientApiBase::doCall(RTCRestClientRequestBase const &a_rRequest, RTHTTPMETHOD a_enmHttpMethod,
+ RTCRestClientResponseBase *a_pResponse, const char *a_pszMethod, uint32_t a_fFlags) RT_NOEXCEPT
+{
+ LogFlow(("doCall: %s %s\n", a_pszMethod, RTHttpMethodToStr(a_enmHttpMethod)));
+
+
+ /*
+ * Reset the response object (allowing reuse of such) and check the request
+ * object for assignment errors.
+ */
+ int rc;
+ RTHTTP hHttp = NIL_RTHTTP;
+
+ a_pResponse->reset();
+ if (!a_rRequest.hasAssignmentErrors())
+ {
+ /*
+ * Initialize the HTTP instance.
+ */
+ rc = reinitHttpInstance();
+ if (RT_SUCCESS(rc))
+ {
+ hHttp = m_hHttp;
+ Assert(hHttp != NIL_RTHTTP);
+
+ /*
+ * Prepare the response side.
+ */
+ rc = a_pResponse->receivePrepare(hHttp);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Prepare the request for the transmission.
+ */
+ RTCString strExtraPath;
+ RTCString strQuery;
+ RTCString strXmitBody;
+ rc = a_rRequest.xmitPrepare(&strExtraPath, &strQuery, hHttp, &strXmitBody);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Construct the full URL.
+ */
+ RTCString strFullUrl;
+ rc = strFullUrl.assignNoThrow(getServerUrl());
+ if (strExtraPath.isNotEmpty())
+ {
+ if (!strExtraPath.startsWith("/") && !strFullUrl.endsWith("/") && RT_SUCCESS(rc))
+ rc = strFullUrl.appendNoThrow('/');
+ if (RT_SUCCESS(rc))
+ rc = strFullUrl.appendNoThrow(strExtraPath);
+ strExtraPath.setNull();
+ }
+ if (strQuery.isNotEmpty())
+ {
+ Assert(strQuery.startsWith("?"));
+ rc = strFullUrl.appendNoThrow(strQuery);
+ strQuery.setNull();
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = xmitReady(hHttp, strFullUrl, a_enmHttpMethod, strXmitBody, a_fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Perform HTTP request.
+ */
+ uint32_t uHttpStatus = 0;
+ size_t cbBody = 0;
+ void *pvBody = NULL;
+ rc = RTHttpPerform(hHttp, strFullUrl.c_str(), a_enmHttpMethod,
+ strXmitBody.c_str(), strXmitBody.length(),
+ &uHttpStatus, NULL /*ppvHdrs*/, NULL /*pcbHdrs*/, &pvBody, &cbBody);
+ if (RT_SUCCESS(rc))
+ {
+ a_rRequest.xmitComplete(uHttpStatus, hHttp);
+
+ /*
+ * Do response processing.
+ */
+ a_pResponse->receiveComplete(uHttpStatus, hHttp);
+ a_pResponse->consumeBody((const char *)pvBody, cbBody);
+ if (pvBody)
+ RTHttpFreeResponse(pvBody);
+ a_pResponse->receiveFinal();
+
+ return a_pResponse->getStatus();
+ }
+ }
+ }
+ }
+ a_rRequest.xmitComplete(rc, hHttp);
+ }
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ a_pResponse->receiveComplete(rc, hHttp);
+ RT_NOREF_PV(a_pszMethod);
+
+ return a_pResponse->getStatus();
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestClientApiBaseOci.cpp b/src/VBox/Runtime/common/rest/RTCRestClientApiBaseOci.cpp
new file mode 100644
index 00000000..6cfb2815
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestClientApiBaseOci.cpp
@@ -0,0 +1,185 @@
+/* $Id: RTCRestClientApiBaseOci.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestClientApiBase implementation, OCI specific bits.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restclient.h>
+
+#include <iprt/assert.h>
+#include <iprt/base64.h>
+#include <iprt/errcore.h>
+#include <iprt/http.h>
+#include <iprt/log.h>
+#include <iprt/sha.h>
+#include <iprt/time.h>
+#include <iprt/uri.h>
+
+
+
+/**
+ * Ensures that we've got an 'X-Date' or 'Date' header.
+ *
+ * @returns IPRT status code.
+ * @param hHttp The HTTP client handle.
+ * @param pvContent
+ */
+static int ociSignRequestEnsureDateOrXDate(RTHTTP hHttp) RT_NOEXCEPT
+{
+ if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("x-date")))
+ return VINF_SUCCESS;
+ if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("date")))
+ return VINF_SUCCESS;
+
+ RTTIMESPEC NowSpec;
+ RTTIME Now;
+ char szDate[RTTIME_RFC2822_LEN];
+ ssize_t cch = RTTimeToRfc2822(RTTimeExplode(&Now, RTTimeNow(&NowSpec)), szDate, sizeof(szDate), RTTIME_RFC2822_F_GMT);
+ AssertRCReturn((int)cch, (int)cch);
+
+ return RTHttpAddHeader(hHttp, "x-date", szDate, cch, RTHTTPADDHDR_F_BACK);
+}
+
+
+/**
+ * Ensures that we've got a 'x-content-sha256' header.
+ *
+ * @returns IPRT status code.
+ * @param hHttp The HTTP client handle.
+ * @param pvContent
+ */
+static int ociSignRequestEnsureXContentSha256(RTHTTP hHttp, void const *pvContent, size_t cbContent) RT_NOEXCEPT
+{
+ if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("x-content-sha256")))
+ return VINF_SUCCESS;
+
+#ifdef RT_STRICT
+ if (cbContent != 0)
+ {
+ const char *pszContentLength = RTHttpGetHeader(hHttp, RT_STR_TUPLE("Content-Length"));
+ Assert(pszContentLength);
+ AssertMsg(!pszContentLength || RTStrToUInt64(pszContentLength) == cbContent,
+ ("'%s' vs %RU64\n", pszContentLength, cbContent));
+ }
+#endif
+
+ uint8_t abHash[RTSHA256_HASH_SIZE];
+ RTSha256(pvContent, cbContent, abHash);
+
+ char szBase64[RTSHA256_DIGEST_LEN + 1]; /* (base64 should be shorter) */
+ int rc = RTBase64EncodeEx(abHash, sizeof(abHash), RTBASE64_FLAGS_NO_LINE_BREAKS, szBase64, sizeof(szBase64), NULL);
+ AssertRCReturn(rc, rc);
+
+ return RTHttpAddHeader(hHttp, "x-content-sha256", szBase64, RTSTR_MAX, RTHTTPADDHDR_F_BACK);
+}
+
+
+/**
+ * Ensures that we've got a 'Content-Length' header.
+ *
+ * @returns IPRT status code.
+ * @param hHttp The HTTP client handle.
+ * @param cbContent The content length.
+ */
+static int ociSignRequestEnsureContentLength(RTHTTP hHttp, uint64_t cbContent) RT_NOEXCEPT
+{
+ if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("Content-Length")))
+ return VINF_SUCCESS;
+ char szValue[64];
+ ssize_t cchValue = RTStrFormatU64(szValue, sizeof(szValue), cbContent, 10, 0, 0, 0);
+ AssertRCReturn((int)cchValue, (int)cchValue);
+ return RTHttpAddHeader(hHttp, "Content-Length", szValue, cchValue, RTHTTPADDHDR_F_BACK);
+}
+
+
+/**
+ * Ensures that we've got a host header.
+ *
+ * @returns IPRT status code.
+ * @param hHttp The HTTP client handle.
+ * @param pszUrl The URL.
+ */
+static int ociSignRequestEnsureHost(RTHTTP hHttp, const char *pszUrl) RT_NOEXCEPT
+{
+ if (RTHttpGetHeader(hHttp, RT_STR_TUPLE("host")))
+ return VINF_SUCCESS;
+
+ RTURIPARSED ParsedUrl;
+ int rc = RTUriParse(pszUrl, &ParsedUrl);
+ AssertRCReturn(rc, rc);
+
+ return RTHttpAddHeader(hHttp, "host", &pszUrl[ParsedUrl.offAuthorityHost], ParsedUrl.cchAuthorityHost, RTHTTPADDHDR_F_BACK);
+}
+
+
+int RTCRestClientApiBase::ociSignRequest(RTHTTP a_hHttp, RTCString const &a_rStrFullUrl, RTHTTPMETHOD a_enmHttpMethod,
+ RTCString const &a_rStrXmitBody, uint32_t a_fFlags,
+ RTCRKEY a_hKey, RTCString const &a_rStrKeyId) RT_NOEXCEPT
+{
+ /*
+ * First make sure required headers are present, adding them as needed.
+ */
+ int rc = ociSignRequestEnsureHost(a_hHttp, a_rStrFullUrl.c_str());
+ if (RT_SUCCESS(rc))
+ {
+ bool fHasBody
+ = a_rStrXmitBody.isNotEmpty()
+ /* but sometimes we need an empty body signed too */
+ || (a_fFlags & kDoCall_RequireBody)
+ || a_enmHttpMethod == RTHTTPMETHOD_POST
+ || a_enmHttpMethod == RTHTTPMETHOD_PUT;
+
+ if (fHasBody)
+ {
+ rc = ociSignRequestEnsureContentLength(a_hHttp, a_rStrXmitBody.length());
+ if (RT_SUCCESS(rc))
+ rc = ociSignRequestEnsureXContentSha256(a_hHttp, a_rStrXmitBody.c_str(), a_rStrXmitBody.length());
+ }
+ if (RT_SUCCESS(rc))
+ rc = ociSignRequestEnsureDateOrXDate(a_hHttp);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the signing.
+ */
+ rc = RTHttpSignHeaders(a_hHttp, a_enmHttpMethod, a_rStrFullUrl.c_str(), a_hKey, a_rStrKeyId.c_str(), 0 /*fFlags*/);
+ }
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestClientRequestBase.cpp b/src/VBox/Runtime/common/rest/RTCRestClientRequestBase.cpp
new file mode 100644
index 00000000..c8211c79
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestClientRequestBase.cpp
@@ -0,0 +1,280 @@
+/* $Id: RTCRestClientRequestBase.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestClientRequestBase implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restclient.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/cpp/restarray.h>
+#include <iprt/cpp/reststringmap.h>
+
+
+/**
+ * Default constructor.
+ */
+RTCRestClientRequestBase::RTCRestClientRequestBase() RT_NOEXCEPT
+ : m_fIsSet(0)
+ , m_fErrorSet(0)
+{
+}
+
+
+/**
+ * Copy constructor.
+ */
+RTCRestClientRequestBase::RTCRestClientRequestBase(RTCRestClientRequestBase const &a_rThat) RT_NOEXCEPT
+ : m_fIsSet(a_rThat.m_fIsSet)
+ , m_fErrorSet(a_rThat.m_fErrorSet)
+{
+}
+
+
+/**
+ * Destructor
+ */
+RTCRestClientRequestBase::~RTCRestClientRequestBase()
+{
+ /* nothing to do */
+}
+
+
+/**
+ * Copy assignment operator.
+ */
+RTCRestClientRequestBase &RTCRestClientRequestBase::operator=(RTCRestClientRequestBase const &a_rThat) RT_NOEXCEPT
+{
+ m_fIsSet = a_rThat.m_fIsSet;
+ m_fErrorSet = a_rThat.m_fErrorSet;
+ return *this;
+}
+
+
+int RTCRestClientRequestBase::doPathParameters(RTCString *a_pStrPath, const char *a_pszPathTemplate, size_t a_cchPathTemplate,
+ PATHPARAMDESC const *a_paPathParams, PATHPARAMSTATE *a_paPathParamStates,
+ size_t a_cPathParams) const RT_NOEXCEPT
+{
+ int rc = a_pStrPath->assignNoThrow(a_pszPathTemplate, a_cchPathTemplate);
+ AssertRCReturn(rc, rc);
+
+ /* Locate the sub-string to replace with values first: */
+ for (size_t i = 0; i < a_cPathParams; i++)
+ {
+ char const *psz = strstr(a_pszPathTemplate, a_paPathParams[i].pszName);
+ AssertReturn(psz, VERR_INTERNAL_ERROR_5);
+ a_paPathParamStates[i].offName = psz - a_pszPathTemplate;
+ }
+
+ /* Replace with actual values: */
+ for (size_t i = 0; i < a_cPathParams; i++)
+ {
+ AssertReturn( (a_paPathParams[i].fFlags & RTCRestObjectBase::kCollectionFormat_Mask)
+ != RTCRestObjectBase::kCollectionFormat_multi,
+ VERR_INTERNAL_ERROR_3);
+ AssertMsgReturn(a_paPathParamStates[i].pObj != NULL,
+ ("%s: Path parameter '%s' is not set!\n",
+ getOperationName(), a_paPathParams[i].pszName),
+ VERR_REST_PATH_PARAMETER_NOT_SET);
+ AssertMsgReturn(m_fIsSet & RT_BIT_64(a_paPathParams[i].iBitNo),
+ ("%s: Path parameter '%s' is not set!\n",
+ getOperationName(), a_paPathParams[i].pszName),
+ VERR_REST_PATH_PARAMETER_NOT_SET);
+
+ RTCString strPathParam;
+ rc = a_paPathParamStates[i].pObj->toString(&strPathParam, a_paPathParams[i].fFlags);
+ AssertRCReturn(rc, rc);
+
+ LogRel5(("> %s: /%s = %s\n",
+ getOperationName(), a_paPathParams[i].pszName, strPathParam.c_str()));
+
+ RTCString strTmpVal;
+ rc = strTmpVal.printfNoThrow("%RMpa", strPathParam.c_str()); /* urlencode */
+ AssertRCReturn(rc, rc);
+
+ /* Replace. */
+ ssize_t cchAdjust = strTmpVal.length() - a_paPathParams[i].cchName;
+ rc = a_pStrPath->replaceNoThrow(a_paPathParamStates[i].offName, a_paPathParams[i].cchName, strTmpVal);
+ AssertRCReturn(rc, rc);
+
+ /* Adjust subsequent fields. */
+ if (cchAdjust != 0)
+ for (size_t j = i + 1; j < a_cPathParams; j++)
+ if (a_paPathParamStates[j].offName > a_paPathParamStates[i].offName)
+ a_paPathParamStates[j].offName += cchAdjust;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+int RTCRestClientRequestBase::doQueryParameters(RTCString *a_pStrQuery, QUERYPARAMDESC const *a_paQueryParams,
+ RTCRestObjectBase const **a_papQueryParamObjs, size_t a_cQueryParams) const RT_NOEXCEPT
+{
+ RTCString strTmpVal;
+ char chSep = a_pStrQuery->isEmpty() ? '?' : '&';
+ for (size_t i = 0; i < a_cQueryParams; i++)
+ {
+ if ( a_paQueryParams[i].fRequired
+ || (m_fIsSet & RT_BIT_64(a_paQueryParams[i].iBitNo)) )
+ {
+ AssertMsgReturn(a_papQueryParamObjs[i] != NULL,
+ ("%s: Required query parameter '%s' is not set!\n",
+ getOperationName(), a_paQueryParams[i].pszName),
+ VERR_REST_REQUIRED_QUERY_PARAMETER_NOT_SET);
+ AssertMsgReturn(m_fIsSet & RT_BIT_64(a_paQueryParams[i].iBitNo),
+ ("%s: Required query parameter '%s' is not set!\n",
+ getOperationName(), a_paQueryParams[i].pszName),
+ VERR_REST_REQUIRED_QUERY_PARAMETER_NOT_SET);
+
+ if ( (a_paQueryParams[i].fFlags & RTCRestObjectBase::kCollectionFormat_Mask)
+ != RTCRestObjectBase::kCollectionFormat_multi)
+ {
+ int rc = a_papQueryParamObjs[i]->toString(&strTmpVal, a_paQueryParams[i].fFlags);
+ AssertRCReturn(rc, rc);
+
+ rc = a_pStrQuery->appendPrintfNoThrow("%c%RMpa=%RMpa", chSep, a_paQueryParams[i].pszName, strTmpVal.c_str());
+ AssertRCReturn(rc, rc);
+
+ LogRel5(("> %s: ?%s = %s\n",
+ getOperationName(), a_paQueryParams[i].pszName, strTmpVal.c_str()));
+
+ chSep = '&';
+ }
+ else
+ {
+ /*
+ * Enumerate array and add 'name=element' for each element in it.
+ */
+ AssertReturn(a_papQueryParamObjs[i]->typeClass() == RTCRestObjectBase::kTypeClass_Array,
+ VERR_REST_INTERNAL_ERROR_2);
+ RTCRestArrayBase const *pArray = (RTCRestArrayBase const *)a_papQueryParamObjs[i];
+ for (size_t j = 0; j < pArray->size(); j++)
+ {
+ RTCRestObjectBase const *pObj = pArray->atBase(j);
+ int rc = pObj->toString(&strTmpVal, a_paQueryParams[i].fFlags & ~RTCRestObjectBase::kCollectionFormat_Mask);
+ AssertRCReturn(rc, rc);
+
+ rc = a_pStrQuery->appendPrintfNoThrow("%c%RMpa=%RMpa", chSep, a_paQueryParams[i].pszName, strTmpVal.c_str());
+ AssertRCReturn(rc, rc);
+
+ LogRel5(("> %s: ?%s[%d] = %s\n",
+ getOperationName(), a_paQueryParams[i].pszName, j, strTmpVal.c_str()));
+
+ chSep = '&';
+ }
+ }
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+int RTCRestClientRequestBase::doHeaderParameters(RTHTTP a_hHttp, HEADERPARAMDESC const *a_paHeaderParams,
+ RTCRestObjectBase const **a_papHeaderParamObjs, size_t a_cHeaderParams) const RT_NOEXCEPT
+{
+ RTCString strTmpVal;
+ for (size_t i = 0; i < a_cHeaderParams; i++)
+ {
+ AssertReturn( (a_paHeaderParams[i].fFlags & RTCRestObjectBase::kCollectionFormat_Mask)
+ != RTCRestObjectBase::kCollectionFormat_multi,
+ VERR_INTERNAL_ERROR_3);
+
+ if ( a_paHeaderParams[i].fRequired
+ || (m_fIsSet & RT_BIT_64(a_paHeaderParams[i].iBitNo)) )
+ {
+ AssertMsgReturn(m_fIsSet & RT_BIT_64(a_paHeaderParams[i].iBitNo),
+ ("%s: Required header parameter '%s' is not set!\n",
+ getOperationName(), a_paHeaderParams[i].pszName),
+ VERR_REST_REQUIRED_HEADER_PARAMETER_NOT_SET);
+ AssertMsgReturn(a_papHeaderParamObjs[i] != NULL,
+ ("%s: Required header parameter '%s' is not set!\n",
+ getOperationName(), a_paHeaderParams[i].pszName),
+ VERR_REST_REQUIRED_HEADER_PARAMETER_NOT_SET);
+
+ if (!a_paHeaderParams[i].fMapCollection)
+ {
+ int rc = a_papHeaderParamObjs[i]->toString(&strTmpVal, a_paHeaderParams[i].fFlags);
+ AssertRCReturn(rc, rc);
+
+ rc = RTHttpAddHeader(a_hHttp, a_paHeaderParams[i].pszName, strTmpVal.c_str(), strTmpVal.length(),
+ RTHTTPADDHDR_F_BACK);
+ AssertRCReturn(rc, rc);
+
+ LogRel5(("> %s: :%s = %s\n",
+ getOperationName(), a_paHeaderParams[i].pszName, strTmpVal.c_str()));
+ }
+ else if (!a_papHeaderParamObjs[i]->isNull())
+ {
+ /*
+ * Enumerate the map and produce a series of head fields on the form:
+ * (a_paHeaderParams[i].pszName + key): value.toString()
+ */
+ AssertReturn(a_papHeaderParamObjs[i]->typeClass() == RTCRestObjectBase::kTypeClass_StringMap,
+ VERR_REST_INTERNAL_ERROR_1);
+ RTCRestStringMapBase const *pMap = (RTCRestStringMapBase const *)a_papHeaderParamObjs[i];
+ const size_t cchName = strlen(a_paHeaderParams[i].pszName);
+ Assert(a_paHeaderParams[i].pszName[cchName - 1] != '*');
+ RTCString strTmpName;
+ for (RTCRestStringMapBase::ConstIterator it = pMap->begin(); it != pMap->end(); ++it)
+ {
+ int rc = strTmpName.assignNoThrow(a_paHeaderParams[i].pszName, cchName);
+ AssertRCReturn(rc, rc);
+ rc = strTmpName.appendNoThrow(it.getKey());
+ AssertRCReturn(rc, rc);
+
+ rc = it.getValue()->toString(&strTmpVal, a_paHeaderParams[i].fFlags);
+ AssertRCReturn(rc, rc);
+
+ rc = RTHttpAddHeader(a_hHttp, strTmpName.c_str(), strTmpVal.c_str(), strTmpVal.length(),
+ RTHTTPADDHDR_F_BACK);
+ AssertRCReturn(rc, rc);
+
+ LogRel5(("> %s: :%s = %s\n",
+ getOperationName(), strTmpName.c_str(), strTmpVal.c_str()));
+ }
+ }
+ else
+ Assert(!a_paHeaderParams[i].fRequired);
+ }
+ }
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp b/src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp
new file mode 100644
index 00000000..90bc84b5
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestClientResponseBase.cpp
@@ -0,0 +1,413 @@
+/* $Id: RTCRestClientResponseBase.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestClientResponseBase implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restclient.h>
+
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/cpp/reststringmap.h>
+
+
+/**
+ * Default constructor.
+ */
+RTCRestClientResponseBase::RTCRestClientResponseBase() RT_NOEXCEPT
+ : m_rcStatus(VERR_WRONG_ORDER)
+ , m_rcHttp(VERR_NOT_AVAILABLE)
+ , m_pErrInfo(NULL)
+{
+}
+
+
+/**
+ * Destructor.
+ */
+RTCRestClientResponseBase::~RTCRestClientResponseBase()
+{
+ deleteErrInfo();
+}
+
+
+/**
+ * Copy constructor.
+ */
+RTCRestClientResponseBase::RTCRestClientResponseBase(RTCRestClientResponseBase const &a_rThat)
+ : m_rcStatus(a_rThat.m_rcStatus)
+ , m_rcHttp(a_rThat.m_rcHttp)
+ , m_pErrInfo(NULL)
+ , m_strContentType(a_rThat.m_strContentType)
+{
+ if (a_rThat.m_pErrInfo)
+ copyErrInfo(a_rThat.m_pErrInfo);
+}
+
+
+/**
+ * Copy assignment operator.
+ */
+RTCRestClientResponseBase &RTCRestClientResponseBase::operator=(RTCRestClientResponseBase const &a_rThat)
+{
+ m_rcStatus = a_rThat.m_rcStatus;
+ m_rcHttp = a_rThat.m_rcHttp;
+ m_strContentType = a_rThat.m_strContentType;
+ if (a_rThat.m_pErrInfo)
+ copyErrInfo(a_rThat.m_pErrInfo);
+ else if (m_pErrInfo)
+ deleteErrInfo();
+
+ return *this;
+}
+
+
+void RTCRestClientResponseBase::reset() RT_NOEXCEPT
+{
+ /* Return to default constructor state. */
+ m_rcStatus = VERR_WRONG_ORDER;
+ m_rcHttp = VERR_NOT_AVAILABLE;
+ if (m_pErrInfo)
+ deleteErrInfo();
+ m_strContentType.setNull();
+}
+
+
+int RTCRestClientResponseBase::receivePrepare(RTHTTP a_hHttp) RT_NOEXCEPT
+{
+ int rc = RTHttpSetHeaderCallback(a_hHttp, receiveHttpHeaderCallback, this);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestClientResponseBase::receiveComplete(int a_rcStatus, RTHTTP a_hHttp) RT_NOEXCEPT
+{
+ RT_NOREF_PV(a_hHttp);
+ m_rcStatus = a_rcStatus;
+ if (a_rcStatus >= 0)
+ m_rcHttp = a_rcStatus;
+
+ int rc = RTHttpSetHeaderCallback(a_hHttp, NULL, NULL);
+ AssertRC(rc);
+}
+
+
+int RTCRestClientResponseBase::consumeHeader(uint32_t a_uMatchWord, const char *a_pchField, size_t a_cchField,
+ const char *a_pchValue, size_t a_cchValue) RT_NOEXCEPT
+{
+ if ( a_uMatchWord == RTHTTP_MAKE_HDR_MATCH_WORD(sizeof("Content-Type") - 1, 'c', 'o', 'n')
+ && RTStrNICmpAscii(a_pchField, RT_STR_TUPLE("Content-Type")) == 0)
+ {
+ int rc = RTStrValidateEncodingEx(a_pchValue, a_cchValue, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ return m_strContentType.assignNoThrow(a_pchValue, a_cchValue);
+ }
+ RT_NOREF(a_cchField);
+ return VINF_SUCCESS;
+}
+
+
+/*static*/ DECLCALLBACK(int)
+RTCRestClientResponseBase::receiveHttpHeaderCallback(RTHTTP hHttp, uint32_t uMatchWord, const char *pchField, size_t cchField,
+ const char *pchValue, size_t cchValue, void *pvUser) RT_NOEXCEPT
+{
+ RTCRestClientResponseBase *pThis = (RTCRestClientResponseBase *)pvUser;
+ RT_NOREF(hHttp);
+ return pThis->consumeHeader(uMatchWord, pchField, cchField, pchValue, cchValue);
+}
+
+
+void RTCRestClientResponseBase::consumeBody(const char *a_pchData, size_t a_cbData) RT_NOEXCEPT
+{
+ RT_NOREF(a_pchData, a_cbData);
+}
+
+
+void RTCRestClientResponseBase::receiveFinal() RT_NOEXCEPT
+{
+}
+
+
+PRTERRINFO RTCRestClientResponseBase::getErrInfoInternal(void) RT_NOEXCEPT
+{
+ if (m_pErrInfo)
+ return m_pErrInfo;
+ size_t cbMsg = _4K;
+ m_pErrInfo = (PRTERRINFO)RTMemAllocZ(sizeof(*m_pErrInfo) + cbMsg);
+ if (m_pErrInfo)
+ return RTErrInfoInit(m_pErrInfo, (char *)(m_pErrInfo + 1), cbMsg);
+ return NULL;
+}
+
+
+void RTCRestClientResponseBase::deleteErrInfo(void) RT_NOEXCEPT
+{
+ if (m_pErrInfo)
+ {
+ RTMemFree(m_pErrInfo);
+ m_pErrInfo = NULL;
+ }
+}
+
+
+void RTCRestClientResponseBase::copyErrInfo(PCRTERRINFO pErrInfo) RT_NOEXCEPT
+{
+ deleteErrInfo();
+ m_pErrInfo = (PRTERRINFO)RTMemDup(pErrInfo, pErrInfo->cbMsg + sizeof(*pErrInfo));
+ if (m_pErrInfo)
+ {
+ m_pErrInfo->pszMsg = (char *)(m_pErrInfo + 1);
+ m_pErrInfo->apvReserved[0] = NULL;
+ m_pErrInfo->apvReserved[1] = NULL;
+ }
+}
+
+
+int RTCRestClientResponseBase::addError(int rc, const char *pszFormat, ...) RT_NOEXCEPT
+{
+ PRTERRINFO pErrInfo = getErrInfoInternal();
+ if (pErrInfo)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ if ( !RTErrInfoIsSet(pErrInfo)
+ || pErrInfo->cbMsg == 0
+ || pErrInfo->pszMsg[pErrInfo->cbMsg - 1] == '\n')
+ RTErrInfoAddV(pErrInfo, rc, pszFormat, va);
+ else
+ RTErrInfoAddF(pErrInfo, rc, "\n%N", pszFormat, &va);
+ va_end(va);
+ }
+ if (RT_SUCCESS(m_rcStatus) && RT_FAILURE_NP(rc))
+ m_rcStatus = rc;
+ return rc;
+}
+
+
+RTCRestClientResponseBase::PrimaryJsonCursorForBody::PrimaryJsonCursorForBody(RTJSONVAL hValue, const char *pszName,
+ RTCRestClientResponseBase *a_pThat) RT_NOEXCEPT
+ : RTCRestJsonPrimaryCursor(hValue, pszName, a_pThat->getErrInfoInternal())
+ , m_pThat(a_pThat)
+{
+}
+
+
+int RTCRestClientResponseBase::PrimaryJsonCursorForBody::addError(RTCRestJsonCursor const &a_rCursor, int a_rc,
+ const char *a_pszFormat, ...) RT_NOEXCEPT
+{
+ va_list va;
+ va_start(va, a_pszFormat);
+ char szPath[256];
+ m_pThat->addError(a_rc, "response body/%s: %N", getPath(a_rCursor, szPath, sizeof(szPath)), a_pszFormat, &va);
+ va_end(va);
+ return a_rc;
+}
+
+
+int RTCRestClientResponseBase::PrimaryJsonCursorForBody::unknownField(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ char szPath[256];
+ m_pThat->addError(VWRN_NOT_FOUND, "response body/%s: unknown field (type %s)",
+ getPath(a_rCursor, szPath, sizeof(szPath)), RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+ return VWRN_NOT_FOUND;
+}
+
+
+int RTCRestClientResponseBase::deserializeHeader(RTCRestObjectBase *a_pObj, const char *a_pchValue, size_t a_cchValue,
+ uint32_t a_fFlags, const char *a_pszErrorTag) RT_NOEXCEPT
+{
+ /*
+ * Start by checking the encoding and transfering the value to a RTCString object.
+ */
+ int rc = RTStrValidateEncodingEx(a_pchValue, a_cchValue, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ RTCString strValue;
+ rc = strValue.assignNoThrow(a_pchValue, a_cchValue);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel7(("< %s: :%s = %s\n",
+ getOperationName(), a_pszErrorTag, strValue.c_str()));
+
+ /*
+ * Try deserialize it.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ rc = a_pObj->fromString(strValue, a_pszErrorTag, RTErrInfoInitStatic(&ErrInfo), a_fFlags);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ addError(rc, "Error %Rrc parsing header field '%s': %s", rc, a_pszErrorTag, ErrInfo.Core.pszMsg);
+ else
+ addError(rc, "Error %Rrc parsing header field '%s'", rc, a_pszErrorTag);
+ }
+ }
+ else
+ {
+ addError(rc, "Error %Rrc validating value encoding of header field '%s': %.*Rhxs",
+ rc, a_pszErrorTag, a_cchValue, a_pchValue);
+ rc = VINF_SUCCESS; /* ignore */
+ }
+ return rc;
+}
+
+
+int RTCRestClientResponseBase::deserializeHeaderIntoMap(RTCRestStringMapBase *a_pMap, const char *a_pchField, size_t a_cchField,
+ const char *a_pchValue, size_t a_cchValue, uint32_t a_fFlags,
+ const char *a_pszErrorTag) RT_NOEXCEPT
+{
+ /*
+ * Start by checking the encoding of both the field and value,
+ * then transfering the value to a RTCString object.
+ */
+ int rc = RTStrValidateEncodingEx(a_pchField, a_cchField, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrValidateEncodingEx(a_pchValue, a_cchValue, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ RTCString strValue;
+ rc = strValue.assignNoThrow(a_pchValue, a_cchValue);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a value object and put it into the map.
+ */
+ RTCRestObjectBase *pValue;
+ rc = a_pMap->putNewValue(&pValue, a_pchField, a_cchField);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel7(("< %s: :%s%.*s = %s\n",
+ getOperationName(), a_pszErrorTag, a_cchField, a_pchField, strValue.c_str()));
+
+ /*
+ * Try deserialize the value.
+ */
+ RTERRINFOSTATIC ErrInfo;
+ rc = pValue->fromString(strValue, a_pszErrorTag, RTErrInfoInitStatic(&ErrInfo), a_fFlags);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ addError(rc, "Error %Rrc parsing header field '%s' subfield '%.*s': %s",
+ rc, a_pszErrorTag, a_cchField, a_pchField, ErrInfo.Core.pszMsg);
+ else
+ addError(rc, "Error %Rrc parsing header field '%s' subfield '%.*s'",
+ rc, a_pszErrorTag, a_cchField, a_pchField);
+ }
+ }
+ }
+ else
+ {
+ addError(rc, "Error %Rrc validating value encoding of header field '%s': %.*Rhxs",
+ rc, a_pszErrorTag, a_cchValue, a_pchValue);
+ rc = VINF_SUCCESS; /* ignore */
+ }
+ }
+ else
+ {
+ addError(rc, "Error %Rrc validating sub-field encoding of header field '%s*': %.*Rhxs",
+ rc, a_pszErrorTag, a_cchField, a_pchField);
+ rc = VINF_SUCCESS; /* ignore */
+ }
+ return rc;
+}
+
+
+void RTCRestClientResponseBase::deserializeBody(const char *a_pchData, size_t a_cbData, const char *a_pszBodyName) RT_NOEXCEPT
+{
+ if (m_strContentType.startsWith("application/json"))
+ {
+ int rc = RTStrValidateEncodingEx(a_pchData, a_cbData, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH);
+ if (RT_SUCCESS(rc))
+ {
+ if (LogRelIs7Enabled())
+ {
+ /* skip m_ or m_p prefix */
+ const char *pszName = a_pszBodyName;
+ if (pszName[0] == 'm' && pszName[1] == '_')
+ {
+ if (pszName[2] == 'p')
+ pszName += 3;
+ else
+ pszName += 2;
+ }
+
+ LogRel7(("< %s: %d: %s = %.*s\n",
+ getOperationName(), m_rcHttp, pszName, a_cbData, a_pchData));
+ }
+
+ RTERRINFOSTATIC ErrInfo;
+ RTJSONVAL hValue;
+ rc = RTJsonParseFromBuf(&hValue, (const uint8_t *)a_pchData, a_cbData, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ PrimaryJsonCursorForBody PrimaryCursor(hValue, a_pszBodyName, this); /* note: consumes hValue */
+ deserializeBodyFromJsonCursor(PrimaryCursor.m_Cursor);
+ }
+ else if (RTErrInfoIsSet(&ErrInfo.Core))
+ addError(rc, "Error %Rrc parsing server response as JSON (type %s): %s",
+ rc, a_pszBodyName, ErrInfo.Core.pszMsg);
+ else
+ addError(rc, "Error %Rrc parsing server response as JSON (type %s)", rc, a_pszBodyName);
+ }
+ else if (rc == VERR_INVALID_UTF8_ENCODING)
+ addError(VERR_REST_RESPONSE_INVALID_UTF8_ENCODING, "Invalid UTF-8 body encoding (object type %s; Content-Type: %s)",
+ a_pszBodyName, m_strContentType.c_str());
+ else if (rc == VERR_BUFFER_UNDERFLOW)
+ addError(VERR_REST_RESPONSE_EMBEDDED_ZERO_CHAR, "Embedded zero character in response (object type %s; Content-Type: %s)",
+ a_pszBodyName, m_strContentType.c_str());
+ else
+ addError(rc, "Unexpected body validation error (object type %s; Content-Type: %s): %Rrc",
+ a_pszBodyName, m_strContentType.c_str(), rc);
+ }
+ else
+ addError(VERR_REST_RESPONSE_CONTENT_TYPE_NOT_SUPPORTED, "Unsupported content type for '%s': %s",
+ a_pszBodyName, m_strContentType.c_str());
+}
+
+
+void RTCRestClientResponseBase::deserializeBodyFromJsonCursor(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_INTERNAL_ERROR_8, "deserializeBodyFromJsonCursor must be overridden!");
+ AssertFailed();
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestJsonPrimaryCursor.cpp b/src/VBox/Runtime/common/rest/RTCRestJsonPrimaryCursor.cpp
new file mode 100644
index 00000000..7de9f356
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestJsonPrimaryCursor.cpp
@@ -0,0 +1,120 @@
+/* $Id: RTCRestJsonPrimaryCursor.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestJsonPrimaryCursor implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restbase.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+char *RTCRestJsonPrimaryCursor::getPath(RTCRestJsonCursor const &a_rCursor, char *pszDst, size_t cbDst) const RT_NOEXCEPT
+{
+ AssertReturn(cbDst > 0, NULL);
+
+ /*
+ * To avoid using recursion, we need to first do a pass to figure sizes
+ * and depth. To keep things simple, with the exception of the top name
+ * we only copy out full names.
+ */
+ /* Special case: Insufficient space for the top name. */
+ size_t const cchTopName = strlen(a_rCursor.m_pszName);
+ if (cchTopName >= cbDst)
+ {
+ memcpy(pszDst, a_rCursor.m_pszName, cbDst - 1);
+ pszDst[cbDst - 1] = '\0';
+ }
+ else
+ {
+ /* Determin how deep we should go and the resulting length. */
+ size_t iMaxDepth = 0;
+ size_t cchTotal = cchTopName;
+ for (RTCRestJsonCursor const *pCur = a_rCursor.m_pParent; pCur; pCur = pCur->m_pParent)
+ {
+ size_t const cchName = strlen(pCur->m_pszName);
+ size_t cchNewTotal = cchName + 1 + cchTotal;
+ if (cchNewTotal < cbDst)
+ cchTotal = cchNewTotal;
+ else
+ break;
+ iMaxDepth++;
+ }
+
+ /* Produce the string, in reverse. */
+ char *psz = &pszDst[cchTotal];
+ *psz = '\0';
+ psz -= cchTopName;
+ memcpy(psz, a_rCursor.m_pszName, cchTopName);
+ for (RTCRestJsonCursor const *pCur = a_rCursor.m_pParent; pCur && iMaxDepth > 0; pCur = pCur->m_pParent)
+ {
+ psz -= 1;
+ *psz = '.';
+
+ size_t const cchName = strlen(pCur->m_pszName);
+ psz -= cchName;
+ memcpy(psz, pCur->m_pszName, cchName);
+
+ iMaxDepth--;
+ }
+ Assert(psz == pszDst);
+ }
+ return pszDst;
+}
+
+
+int RTCRestJsonPrimaryCursor::addError(RTCRestJsonCursor const &a_rCursor, int a_rc, const char *a_pszFormat, ...) RT_NOEXCEPT
+{
+ va_list va;
+ va_start(va, a_pszFormat);
+ char szPath[128];
+ a_rc = RTErrInfoAddF(m_pErrInfo, a_rc, "%s: %N\n", getPath(a_rCursor, szPath, sizeof(szPath)), a_pszFormat, &va);
+ va_end(va);
+ return a_rc;
+}
+
+
+int RTCRestJsonPrimaryCursor::unknownField(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ char szPath[128];
+ return RTErrInfoAddF(m_pErrInfo, VWRN_NOT_FOUND, "%s: unknown field (type %s)\n",
+ getPath(a_rCursor, szPath, sizeof(szPath)),
+ RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestOutputBase.cpp b/src/VBox/Runtime/common/rest/RTCRestOutputBase.cpp
new file mode 100644
index 00000000..56bbce04
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestOutputBase.cpp
@@ -0,0 +1,129 @@
+/* $Id: RTCRestOutputBase.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestOutputBase implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restoutput.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTCRestOutputBase::RTCRestOutputBase() RT_NOEXCEPT
+ : m_uState(0)
+{
+}
+
+
+RTCRestOutputBase::~RTCRestOutputBase()
+{
+}
+
+
+size_t RTCRestOutputBase::vprintf(const char *pszFormat, va_list va) RT_NOEXCEPT
+{
+ return RTStrFormatV(printfOutputCallback, this, NULL, NULL, pszFormat, va);
+}
+
+
+/*static*/ DECLCALLBACK(size_t) RTCRestOutputBase::printfOutputCallback(void *pvArg, const char *pachChars, size_t cbChars) RT_NOEXCEPT
+{
+ return ((RTCRestOutputBase *)pvArg)->output(pachChars, cbChars);
+}
+
+
+uint32_t RTCRestOutputBase::beginArray() RT_NOEXCEPT
+{
+ output(RT_STR_TUPLE("["));
+ uint32_t const uOldState = m_uState;
+ m_uState = (uOldState & 0xffff) + 1;
+ return uOldState;
+}
+
+
+void RTCRestOutputBase::endArray(uint32_t a_uOldState) RT_NOEXCEPT
+{
+ m_uState = a_uOldState;
+ output(RT_STR_TUPLE("]"));
+}
+
+
+uint32_t RTCRestOutputBase::beginObject() RT_NOEXCEPT
+{
+ output(RT_STR_TUPLE("{"));
+ uint32_t const uOldState = m_uState;
+ m_uState = (uOldState & 0xffff) + 1;
+ return uOldState;
+}
+
+
+void RTCRestOutputBase::endObject(uint32_t a_uOldState) RT_NOEXCEPT
+{
+ m_uState = a_uOldState;
+ output(RT_STR_TUPLE("}"));
+}
+
+
+void RTCRestOutputBase::valueSeparator() RT_NOEXCEPT
+{
+ if (m_uState & RT_BIT_32(31))
+ output(RT_STR_TUPLE(","));
+ else
+ m_uState |= RT_BIT_32(31);
+}
+
+
+void RTCRestOutputBase::valueSeparatorAndName(const char *a_pszName, size_t a_cchName) RT_NOEXCEPT
+{
+ RT_NOREF(a_cchName);
+ if (m_uState & RT_BIT_32(31))
+ printf(",%RMjs:", a_pszName);
+ else
+ {
+ m_uState |= RT_BIT_32(31);
+ printf("%RMjs:", a_pszName);
+ }
+}
+
+
+void RTCRestOutputBase::nullValue() RT_NOEXCEPT
+{
+ output(RT_STR_TUPLE("null"));
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestOutputPrettyBase.cpp b/src/VBox/Runtime/common/rest/RTCRestOutputPrettyBase.cpp
new file mode 100644
index 00000000..c0aed42f
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestOutputPrettyBase.cpp
@@ -0,0 +1,134 @@
+/* $Id: RTCRestOutputPrettyBase.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestOutputPrettyBase implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restoutput.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTCRestOutputPrettyBase::RTCRestOutputPrettyBase() RT_NOEXCEPT
+ : RTCRestOutputBase()
+{
+}
+
+
+RTCRestOutputPrettyBase::~RTCRestOutputPrettyBase()
+{
+}
+
+
+uint32_t RTCRestOutputPrettyBase::beginArray() RT_NOEXCEPT
+{
+ output(RT_STR_TUPLE("["));
+ uint32_t const uOldState = m_uState;
+ m_uState = (uOldState & 0xffff) + 1;
+ return uOldState;
+}
+
+
+void RTCRestOutputPrettyBase::endArray(uint32_t a_uOldState) RT_NOEXCEPT
+{
+ m_uState = a_uOldState;
+ output(RT_STR_TUPLE("\n"));
+ outputIndentation();
+ output(RT_STR_TUPLE("]"));
+}
+
+
+uint32_t RTCRestOutputPrettyBase::beginObject() RT_NOEXCEPT
+{
+ output(RT_STR_TUPLE("{"));
+ uint32_t const uOldState = m_uState;
+ m_uState = (uOldState & 0xffff) + 1;
+ return uOldState;
+}
+
+
+void RTCRestOutputPrettyBase::endObject(uint32_t a_uOldState) RT_NOEXCEPT
+{
+ m_uState = a_uOldState;
+ output(RT_STR_TUPLE("\n"));
+ outputIndentation();
+ output(RT_STR_TUPLE("}"));
+}
+
+
+void RTCRestOutputPrettyBase::valueSeparator() RT_NOEXCEPT
+{
+ if (m_uState & RT_BIT_32(31))
+ output(RT_STR_TUPLE(",\n"));
+ else
+ {
+ m_uState |= RT_BIT_32(31);
+ output(RT_STR_TUPLE("\n"));
+ }
+ outputIndentation();
+}
+
+
+void RTCRestOutputPrettyBase::valueSeparatorAndName(const char *a_pszName, size_t a_cchName) RT_NOEXCEPT
+{
+ RT_NOREF(a_cchName);
+ if (m_uState & RT_BIT_32(31))
+ output(RT_STR_TUPLE(",\n"));
+ else
+ {
+ m_uState |= RT_BIT_32(31);
+ output(RT_STR_TUPLE("\n"));
+ }
+ outputIndentation();
+ printf("%RMjs: ", a_pszName);
+}
+
+
+void RTCRestOutputPrettyBase::outputIndentation() RT_NOEXCEPT
+{
+ static char const s_szSpaces[] = " ";
+ size_t cchIndent = (m_uState & 0xffff) << 1;
+ while (cchIndent > 0)
+ {
+ size_t cbToWrite = RT_MIN(cchIndent, sizeof(s_szSpaces) - 1);
+ output(s_szSpaces, cbToWrite);
+ cchIndent -= cbToWrite;
+ }
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestOutputPrettyToString.cpp b/src/VBox/Runtime/common/rest/RTCRestOutputPrettyToString.cpp
new file mode 100644
index 00000000..8258e7fa
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestOutputPrettyToString.cpp
@@ -0,0 +1,130 @@
+/* $Id: RTCRestOutputPrettyToString.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestOutputPrettyToString implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restoutput.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTCRestOutputPrettyToString::RTCRestOutputPrettyToString(RTCString *a_pDst, bool a_fAppend /*= false*/) RT_NOEXCEPT
+ : RTCRestOutputPrettyBase()
+ , m_pDst(a_pDst)
+ , m_fOutOfMemory(false)
+{
+ if (!a_fAppend)
+ m_pDst->setNull();
+}
+
+
+RTCRestOutputPrettyToString::~RTCRestOutputPrettyToString()
+{
+ /* We don't own the string, so we don't delete it! */
+ m_pDst = NULL;
+}
+
+
+size_t RTCRestOutputPrettyToString::output(const char *a_pchString, size_t a_cchToWrite) RT_NOEXCEPT
+{
+ if (a_cchToWrite)
+ {
+ RTCString *pDst = m_pDst;
+ if (pDst && !m_fOutOfMemory)
+ {
+ /*
+ * Make sure we've got sufficient space available before we append.
+ */
+ size_t cchCurrent = pDst->length();
+ size_t cbCapacity = pDst->capacity();
+ size_t cbNeeded = cchCurrent + a_cchToWrite + 1;
+ if (cbNeeded <= cbCapacity)
+ { /* likely */ }
+ else
+ {
+ /* Grow it. */
+ if (cbNeeded < _16M)
+ {
+ if (cbCapacity <= _1K)
+ cbCapacity = _1K;
+ else
+ cbCapacity = RT_ALIGN_Z(cbCapacity, _1K);
+ while (cbCapacity < cbNeeded)
+ cbCapacity <<= 1;
+ }
+ else
+ {
+ cbCapacity = RT_ALIGN_Z(cbCapacity, _2M);
+ while (cbCapacity < cbNeeded)
+ cbCapacity += _2M;
+ }
+ int rc = pDst->reserveNoThrow(cbCapacity);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pDst->reserveNoThrow(cbNeeded);
+ if (RT_FAILURE(rc))
+ {
+ m_fOutOfMemory = true;
+ return a_cchToWrite;
+ }
+ }
+ }
+
+ /*
+ * Do the appending.
+ */
+ pDst->append(a_pchString, a_cchToWrite);
+ }
+ }
+ return a_cchToWrite;
+}
+
+
+RTCString *RTCRestOutputPrettyToString::finalize() RT_NOEXCEPT
+{
+ RTCString *pRet;
+ if (!m_fOutOfMemory)
+ pRet = m_pDst;
+ else
+ pRet = NULL;
+ m_pDst = NULL;
+ return pRet;
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestOutputToString.cpp b/src/VBox/Runtime/common/rest/RTCRestOutputToString.cpp
new file mode 100644
index 00000000..372ddf81
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestOutputToString.cpp
@@ -0,0 +1,130 @@
+/* $Id: RTCRestOutputToString.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestOutputToString implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restoutput.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+RTCRestOutputToString::RTCRestOutputToString(RTCString *a_pDst, bool a_fAppend /*= false*/) RT_NOEXCEPT
+ : RTCRestOutputBase()
+ , m_pDst(a_pDst)
+ , m_fOutOfMemory(false)
+{
+ if (!a_fAppend)
+ m_pDst->setNull();
+}
+
+
+RTCRestOutputToString::~RTCRestOutputToString()
+{
+ /* We don't own the string, so we don't delete it! */
+ m_pDst = NULL;
+}
+
+
+size_t RTCRestOutputToString::output(const char *a_pchString, size_t a_cchToWrite) RT_NOEXCEPT
+{
+ if (a_cchToWrite)
+ {
+ RTCString *pDst = m_pDst;
+ if (pDst && !m_fOutOfMemory)
+ {
+ /*
+ * Make sure we've got sufficient space available before we append.
+ */
+ size_t cchCurrent = pDst->length();
+ size_t cbCapacity = pDst->capacity();
+ size_t cbNeeded = cchCurrent + a_cchToWrite + 1;
+ if (cbNeeded <= cbCapacity)
+ { /* likely */ }
+ else
+ {
+ /* Grow it. */
+ if (cbNeeded < _16M)
+ {
+ if (cbCapacity <= _1K)
+ cbCapacity = _1K;
+ else
+ cbCapacity = RT_ALIGN_Z(cbCapacity, _1K);
+ while (cbCapacity < cbNeeded)
+ cbCapacity <<= 1;
+ }
+ else
+ {
+ cbCapacity = RT_ALIGN_Z(cbCapacity, _2M);
+ while (cbCapacity < cbNeeded)
+ cbCapacity += _2M;
+ }
+ int rc = pDst->reserveNoThrow(cbCapacity);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pDst->reserveNoThrow(cbNeeded);
+ if (RT_FAILURE(rc))
+ {
+ m_fOutOfMemory = true;
+ return a_cchToWrite;
+ }
+ }
+ }
+
+ /*
+ * Do the appending.
+ */
+ pDst->append(a_pchString, a_cchToWrite);
+ }
+ }
+ return a_cchToWrite;
+}
+
+
+RTCString *RTCRestOutputToString::finalize() RT_NOEXCEPT
+{
+ RTCString *pRet;
+ if (!m_fOutOfMemory)
+ pRet = m_pDst;
+ else
+ pRet = NULL;
+ m_pDst = NULL;
+ return pRet;
+}
+
diff --git a/src/VBox/Runtime/common/rest/RTCRestStringMapBase.cpp b/src/VBox/Runtime/common/rest/RTCRestStringMapBase.cpp
new file mode 100644
index 00000000..35ec9805
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/RTCRestStringMapBase.cpp
@@ -0,0 +1,467 @@
+/* $Id: RTCRestStringMapBase.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestStringMapBase implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/reststringmap.h>
+
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/cpp/restoutput.h>
+
+
+/**
+ * Default destructor.
+ */
+RTCRestStringMapBase::RTCRestStringMapBase() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_Map(NULL)
+ , m_cEntries(0)
+{
+ RTListInit(&m_ListHead);
+}
+
+
+#if 0 /* trigger link error for now. */
+/** Copy constructor. */
+RTCRestStringMapBase::RTCRestStringMapBase(RTCRestStringMapBase const &a_rThat);
+#endif
+
+
+/**
+ * Destructor.
+ */
+RTCRestStringMapBase::~RTCRestStringMapBase()
+{
+ clear();
+}
+
+
+
+#if 0 /* trigger link error for now. */
+/** Copy assignment operator. */
+RTCRestStringMapBase &RTCRestStringMapBase::operator=(RTCRestStringMapBase const &a_rThat);
+#endif
+
+
+/*********************************************************************************************************************************
+* Overridden base object methods *
+*********************************************************************************************************************************/
+
+RTCRestObjectBase *RTCRestStringMapBase::baseClone() const RT_NOEXCEPT
+{
+ RTCRestStringMapBase *pClone = createClone();
+ if (pClone)
+ {
+ int rc = pClone->copyMapWorkerNoThrow(*this);
+ if (RT_SUCCESS(rc))
+ return pClone;
+ delete pClone;
+ }
+ return NULL;
+}
+
+
+int RTCRestStringMapBase::resetToDefault() RT_NOEXCEPT
+{
+ /* Default is an empty map. */
+ clear();
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestStringMapBase::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ {
+ uint32_t const uOldState = a_rDst.beginObject();
+ MapEntry const *pCur;
+ RTListForEachCpp(&m_ListHead, pCur, MapEntry, ListEntry)
+ {
+ a_rDst.valueSeparatorAndName(pCur->strKey.c_str(), pCur->strKey.length());
+ pCur->pValue->serializeAsJson(a_rDst);
+ }
+ a_rDst.endObject(uOldState);
+ }
+ else
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestStringMapBase::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ if (RTJsonValueGetType(a_rCursor.m_hValue) == RTJSONVALTYPE_NULL)
+ {
+ setNull();
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Make sure the object starts out with an empty map.
+ */
+ if (m_cEntries > 0)
+ clear();
+ m_fNullIndicator = false;
+
+ /*
+ * Iterate the object values.
+ */
+ RTJSONIT hIterator;
+ int rcRet = RTJsonIteratorBeginObject(a_rCursor.m_hValue, &hIterator);
+ if (RT_SUCCESS(rcRet))
+ {
+ for (;;)
+ {
+ /* Set up the sub-cursor. */
+ RTCRestJsonCursor SubCursor(a_rCursor);
+ int rc = RTJsonIteratorQueryValue(hIterator, &SubCursor.m_hValue, &SubCursor.m_pszName);
+ if (RT_SUCCESS(rc))
+ {
+ /* Call the static deserializeInstanceFromJson method of the value class. */
+ RTCRestObjectBase *pObj = NULL;
+ rc = deserializeValueInstanceFromJson(SubCursor, &pObj);
+ if (RT_SUCCESS(rc))
+ Assert(pObj);
+ else if (RT_SUCCESS(rcRet))
+ rcRet = rc;
+ if (pObj)
+ {
+ /* Insert the value. */
+ rc = putWorker(SubCursor.m_pszName, pObj, true /*a_fReplace*/);
+ if (rc == VINF_SUCCESS)
+ { /* likely */ }
+ else if (RT_SUCCESS(rc))
+ {
+ a_rCursor.m_pPrimary->addError(a_rCursor, rc, "warning %Rrc inserting '%s' into map",
+ rc, SubCursor.m_pszName);
+ if (rcRet == VINF_SUCCESS)
+ rcRet = rc;
+ }
+ else
+ {
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rc, "Failed to insert '%s' into map: %Rrc",
+ SubCursor.m_pszName, rc);
+ delete pObj;
+ }
+ }
+ }
+ else
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonIteratorQueryValue failed: %Rrc", rc);
+
+ /*
+ * Advance.
+ */
+ rc = RTJsonIteratorNext(hIterator);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_JSON_ITERATOR_END)
+ break;
+ else
+ {
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonIteratorNext failed: %Rrc", rc);
+ break;
+ }
+ }
+
+ RTJsonIteratorFree(hIterator);
+ }
+ else if (rcRet == VERR_JSON_IS_EMPTY)
+ rcRet = VINF_SUCCESS;
+ else if ( rcRet == VERR_JSON_VALUE_INVALID_TYPE
+ && RTJsonValueGetType(a_rCursor.m_hValue) == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ rcRet = VINF_SUCCESS;
+ }
+ else
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rcRet, "RTJsonIteratorBegin failed: %Rrc (type %s)",
+ rcRet, RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+ return rcRet;
+}
+
+// later?
+// virtual int RTCRestStringMapBase::toString(RTCString *a_pDst, uint32_t a_fFlags = kCollectionFormat_Unspecified) const ;
+// virtual int RTCRestStringMapBase::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL,
+// uint32_t a_fFlags = kCollectionFormat_Unspecified) ;
+//
+
+
+RTCRestObjectBase::kTypeClass RTCRestStringMapBase::typeClass(void) const RT_NOEXCEPT
+{
+ return kTypeClass_StringMap;
+}
+
+
+const char *RTCRestStringMapBase::typeName(void) const RT_NOEXCEPT
+{
+ return "RTCRestStringMap<ValueType>";
+}
+
+
+/*********************************************************************************************************************************
+* Generic map methods *
+*********************************************************************************************************************************/
+
+/**
+ * @callback_method_impl{FNRTSTRSPACECALLBACK}
+ */
+/*static*/ DECLCALLBACK(int) RTCRestStringMapBase::stringSpaceDestructorCallback(PRTSTRSPACECORE pStr, void *pvUser) RT_NOEXCEPT
+{
+ MapEntry *pNode = (MapEntry *)pStr;
+ if (pNode->pValue)
+ {
+ delete pNode->pValue;
+ pNode->pValue = NULL;
+ }
+ pNode->strKey.setNull();
+ delete pNode;
+
+ RT_NOREF(pvUser);
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestStringMapBase::clear() RT_NOEXCEPT
+{
+ RTStrSpaceDestroy(&m_Map, stringSpaceDestructorCallback, NULL);
+ RTListInit(&m_ListHead);
+ m_cEntries = 0;
+ m_fNullIndicator = false;
+}
+
+
+size_t RTCRestStringMapBase::size() const RT_NOEXCEPT
+{
+ return m_cEntries;
+}
+
+
+bool RTCRestStringMapBase::containsKey(const char *a_pszKey) const RT_NOEXCEPT
+{
+ if (isNull())
+ return false;
+
+ return RTStrSpaceGet((PRTSTRSPACE)&m_Map, a_pszKey) != NULL;
+}
+
+
+bool RTCRestStringMapBase::containsKey(RTCString const &a_rStrKey) const RT_NOEXCEPT
+{
+ return containsKey(a_rStrKey.c_str());
+}
+
+
+bool RTCRestStringMapBase::remove(const char *a_pszKey) RT_NOEXCEPT
+{
+ if (isNull())
+ return false;
+
+ MapEntry *pRemoved = (MapEntry *)RTStrSpaceRemove(&m_Map, a_pszKey);
+ if (pRemoved)
+ {
+ m_cEntries--;
+ RTListNodeRemove(&pRemoved->ListEntry);
+ stringSpaceDestructorCallback(&pRemoved->Core, NULL);
+ return true;
+ }
+ return false;
+}
+
+
+bool RTCRestStringMapBase::remove(RTCString const &a_rStrKey) RT_NOEXCEPT
+{
+ return remove(a_rStrKey.c_str());
+}
+
+
+int RTCRestStringMapBase::putNewValue(RTCRestObjectBase **a_ppValue, const char *a_pszKey, size_t a_cchKey /*= RTSTR_MAX*/,
+ bool a_fReplace /*= false*/) RT_NOEXCEPT
+{
+ RTCRestObjectBase *pValue = createValue();
+ if (pValue)
+ {
+ int rc = putWorker(a_pszKey, pValue, a_fReplace, a_cchKey);
+ if (RT_SUCCESS(rc))
+ *a_ppValue = pValue;
+ else
+ {
+ delete pValue;
+ *a_ppValue = NULL;
+ }
+ return rc;
+ }
+ *a_ppValue = NULL;
+ return VERR_NO_MEMORY;
+}
+
+
+int RTCRestStringMapBase::putNewValue(RTCRestObjectBase **a_ppValue, RTCString const &a_rStrKey, bool a_fReplace /*= false*/) RT_NOEXCEPT
+{
+ return putNewValue(a_ppValue, a_rStrKey.c_str(), a_rStrKey.length(), a_fReplace);
+}
+
+
+/*********************************************************************************************************************************
+* Protected methods *
+*********************************************************************************************************************************/
+
+int RTCRestStringMapBase::copyMapWorkerNoThrow(RTCRestStringMapBase const &a_rThat) RT_NOEXCEPT
+{
+ Assert(this != &a_rThat);
+ clear();
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+
+ if (!a_rThat.m_fNullIndicator)
+ {
+ MapEntry const *pCur;
+ RTListForEachCpp(&a_rThat.m_ListHead, pCur, MapEntry, ListEntry)
+ {
+ int rc = putCopyWorker(pCur->strKey.c_str(), *pCur->pValue, true /*a_fReplace*/);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ return rc;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestStringMapBase::copyMapWorkerMayThrow(RTCRestStringMapBase const &a_rThat)
+{
+ int rc = copyMapWorkerNoThrow(a_rThat);
+ if (RT_SUCCESS(rc))
+ return;
+ throw std::bad_alloc();
+}
+
+
+int RTCRestStringMapBase::putWorker(const char *a_pszKey, RTCRestObjectBase *a_pValue, bool a_fReplace,
+ size_t a_cchKey /*= RTSTR_MAX*/) RT_NOEXCEPT
+{
+ int rc;
+ MapEntry *pEntry = new (std::nothrow) MapEntry;
+ if (pEntry)
+ {
+ rc = pEntry->strKey.assignNoThrow(a_pszKey, a_cchKey);
+ if (RT_SUCCESS(rc))
+ {
+ pEntry->Core.pszString = pEntry->strKey.c_str();
+ pEntry->Core.cchString = pEntry->strKey.length();
+ pEntry->pValue = a_pValue;
+ if (RTStrSpaceInsert(&m_Map, &pEntry->Core))
+ {
+ RTListAppend(&m_ListHead, &pEntry->ListEntry);
+ m_cEntries++;
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+ }
+
+ Assert(!m_fNullIndicator);
+ if (!a_fReplace)
+ rc = VERR_ALREADY_EXISTS;
+ else
+ {
+ /* Just replace the pValue in the existing entry. */
+ MapEntry *pCollision = (MapEntry *)RTStrSpaceGet(&m_Map, a_pszKey);
+ if (pCollision)
+ {
+ if (pCollision->pValue)
+ delete pCollision->pValue;
+ pCollision->pValue = a_pValue;
+ pEntry->pValue = NULL; /* paranoia */
+ rc = VWRN_ALREADY_EXISTS;
+ }
+ else
+ rc = VERR_INTERNAL_ERROR;
+ }
+ }
+ delete pEntry;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+int RTCRestStringMapBase::putCopyWorker(const char *a_pszKey, RTCRestObjectBase const &a_rValue, bool a_fReplace,
+ size_t a_cchKey /*= RTSTR_MAX*/) RT_NOEXCEPT
+{
+ int rc;
+ RTCRestObjectBase *pValueCopy = a_rValue.baseClone();
+ if (pValueCopy)
+ {
+ rc = putWorker(a_pszKey, pValueCopy, a_fReplace, a_cchKey);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ delete pValueCopy;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ return rc;
+}
+
+
+RTCRestObjectBase *RTCRestStringMapBase::getWorker(const char *a_pszKey) RT_NOEXCEPT
+{
+ if (isNull())
+ return NULL;
+
+ MapEntry *pHit = (MapEntry *)RTStrSpaceGet(&m_Map, a_pszKey);
+ if (pHit)
+ return pHit->pValue;
+ return NULL;
+}
+
+
+RTCRestObjectBase const *RTCRestStringMapBase::getWorker(const char *a_pszKey) const RT_NOEXCEPT
+{
+ if (isNull())
+ return NULL;
+
+ MapEntry const *pHit = (MapEntry const *)RTStrSpaceGet((PRTSTRSPACE)&m_Map, a_pszKey);
+ if (pHit)
+ return pHit->pValue;
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/rest/rest-binary.cpp b/src/VBox/Runtime/common/rest/rest-binary.cpp
new file mode 100644
index 00000000..f1308516
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/rest-binary.cpp
@@ -0,0 +1,708 @@
+/* $Id: rest-binary.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestBinary and Descendants.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restbase.h>
+#include <iprt/cpp/restclient.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/cpp/restoutput.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The default maximum download size. */
+#if ARCH_BITS == 32
+# define RTCREST_MAX_DOWNLOAD_SIZE_DEFAULT _32M
+#else
+# define RTCREST_MAX_DOWNLOAD_SIZE_DEFAULT _128M
+#endif
+
+
+
+/*********************************************************************************************************************************
+* RTCRestBinary Implementation. *
+*********************************************************************************************************************************/
+/**
+ * Default constructor.
+ */
+RTCRestBinary::RTCRestBinary() RT_NOEXCEPT
+ : m_pbData(NULL)
+ , m_cbData(0)
+ , m_cbAllocated(0)
+ , m_fFreeable(true)
+ , m_fReadOnly(false)
+{
+}
+
+
+/**
+ * Destructor.
+ */
+RTCRestBinary::~RTCRestBinary()
+{
+ freeData();
+}
+
+/**
+ * Safe copy assignment method.
+ */
+int RTCRestBinary::assignCopy(RTCRestBinary const &a_rThat) RT_NOEXCEPT
+{
+ freeData();
+ if (a_rThat.m_pbData)
+ {
+ m_pbData = (uint8_t *)RTMemDup(a_rThat.m_pbData, a_rThat.m_cbAllocated);
+ AssertReturn(m_pbData, VERR_NO_MEMORY);
+ m_cbData = a_rThat.m_cbData;
+ m_cbAllocated = a_rThat.m_cbAllocated;
+ m_fFreeable = true;
+ m_fReadOnly = false;
+ m_fNullIndicator = false;
+ }
+ else
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Safe buffer copy method.
+ */
+int RTCRestBinary::assignCopy(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT
+{
+ if ( m_pbData == NULL
+ || m_fReadOnly
+ || a_cbData > m_cbAllocated)
+ {
+ freeData();
+ m_pbData = (uint8_t *)RTMemDup(a_pvData, a_cbData);
+ AssertReturn(m_pbData, VERR_NO_MEMORY);
+ m_cbData = a_cbData;
+ m_cbAllocated = a_cbData;
+ m_fFreeable = true;
+ m_fReadOnly = false;
+ }
+ else
+ {
+ m_cbData = a_cbData;
+ memcpy(m_pbData, a_pvData, a_cbData);
+ }
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Use the specified data buffer directly.
+ */
+int RTCRestBinary::assignReadOnly(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT
+{
+ freeData();
+ if (a_pvData)
+ {
+ m_pbData = (uint8_t *)a_pvData;
+ m_cbData = a_cbData;
+ m_cbAllocated = 0;
+ m_fFreeable = false;
+ m_fReadOnly = true;
+ m_fNullIndicator = false;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Use the specified data buffer directly.
+ */
+int RTCRestBinary::assignWriteable(void *a_pvBuf, size_t a_cbBuf) RT_NOEXCEPT
+{
+ freeData();
+ if (a_pvBuf)
+ {
+ m_pbData = (uint8_t *)a_pvBuf;
+ m_cbData = a_cbBuf;
+ m_cbAllocated = a_cbBuf;
+ m_fFreeable = false;
+ m_fReadOnly = false;
+ m_fNullIndicator = false;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Frees the data held by the object and resets it default state.
+ */
+void RTCRestBinary::freeData() RT_NOEXCEPT
+{
+ if (m_fFreeable)
+ RTMemFree(m_pbData);
+ m_pbData = NULL;
+ m_cbData = 0;
+ m_cbAllocated = 0;
+ m_fFreeable = true;
+ m_fReadOnly = false;
+}
+
+
+/* Overridden methods: */
+
+RTCRestObjectBase *RTCRestBinary::baseClone() const RT_NOEXCEPT
+{
+ RTCRestBinary *pClone = new (std::nothrow) RTCRestBinary();
+ if (pClone)
+ {
+ int rc = pClone->assignCopy(*this);
+ if (RT_SUCCESS(rc))
+ return pClone;
+ delete pClone;
+ }
+ return NULL;
+}
+
+
+int RTCRestBinary::setNull(void) RT_NOEXCEPT
+{
+ freeData();
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+}
+
+
+int RTCRestBinary::resetToDefault(void) RT_NOEXCEPT
+{
+ freeData();
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestBinary::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ AssertMsgFailed(("We should never get here!\n"));
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestBinary::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NOT_SUPPORTED, "RTCRestBinary does not support deserialization!");
+}
+
+
+int RTCRestBinary::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ RT_NOREF(a_pDst, a_fFlags);
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+}
+
+
+int RTCRestBinary::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo/*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ RT_NOREF(a_rValue, a_pszName, a_fFlags);
+ AssertFailedReturn(RTErrInfoSet(a_pErrInfo, VERR_NOT_SUPPORTED, "RTCRestBinary does not support fromString()!"));
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestBinary::typeClass(void) const RT_NOEXCEPT
+{
+ return kTypeClass_Binary;
+}
+
+
+const char *RTCRestBinary::typeName(void) const RT_NOEXCEPT
+{
+ return "RTCRestBinary";
+}
+
+
+/** Factory method. */
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestBinary::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestBinary();
+}
+
+
+/**
+ * @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON
+ */
+/*static*/ DECLCALLBACK(int)
+RTCRestBinary::deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT
+{
+ RTCRestObjectBase *pObj;
+ *a_ppInstance = pObj = createInstance();
+ if (pObj)
+ return pObj->deserializeFromJson(a_rCursor);
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory");
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestBinaryParameter Implementation. *
+*********************************************************************************************************************************/
+
+/**
+ * Default constructor.
+ */
+RTCRestBinaryParameter::RTCRestBinaryParameter() RT_NOEXCEPT
+ : RTCRestBinary()
+ , m_cbContentLength(UINT64_MAX)
+ , m_strContentType()
+ , m_pfnProducer(NULL)
+ , m_pvCallbackData(NULL)
+{
+}
+
+
+int RTCRestBinaryParameter::assignCopy(RTCRestBinaryParameter const &a_rThat) RT_NOEXCEPT
+{
+ AssertReturn(a_rThat.m_pfnProducer, VERR_INVALID_STATE);
+ int rc = assignCopy(*(RTCRestBinary const *)&a_rThat);
+ if (RT_SUCCESS(rc))
+ rc = m_strContentType.assignNoThrow(a_rThat.m_strContentType);
+ m_cbContentLength = a_rThat.m_cbContentLength;
+ m_pfnProducer = a_rThat.m_pfnProducer;
+ m_pvCallbackData = a_rThat.m_pvCallbackData;
+ return VINF_SUCCESS;
+}
+
+
+int RTCRestBinaryParameter::assignCopy(RTCRestBinary const &a_rThat) RT_NOEXCEPT
+{
+ m_cbContentLength = a_rThat.getSize();
+ m_strContentType.setNull();
+ m_pfnProducer = NULL;
+ m_pvCallbackData = NULL;
+ return RTCRestBinary::assignCopy(a_rThat);
+}
+
+
+int RTCRestBinaryParameter::assignCopy(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT
+{
+ m_cbContentLength = a_cbData;
+ m_pfnProducer = NULL;
+ m_pvCallbackData = NULL;
+ return RTCRestBinary::assignCopy(a_pvData, a_cbData);
+}
+
+
+int RTCRestBinaryParameter::assignReadOnly(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT
+{
+ m_cbContentLength = a_cbData;
+ m_pfnProducer = NULL;
+ m_pvCallbackData = NULL;
+ return RTCRestBinary::assignReadOnly(a_pvData, a_cbData);
+}
+
+
+int RTCRestBinaryParameter::assignWriteable(void *a_pvBuf, size_t a_cbBuf) RT_NOEXCEPT
+{
+ AssertMsgFailed(("Please use assignReadOnly!\n"));
+ return assignReadOnly(a_pvBuf, a_cbBuf);
+}
+
+
+RTCRestObjectBase *RTCRestBinaryParameter::baseClone() const RT_NOEXCEPT
+{
+ RTCRestBinaryParameter *pClone = new (std::nothrow) RTCRestBinaryParameter();
+ if (pClone)
+ {
+ int rc = pClone->assignCopy(*this);
+ if (RT_SUCCESS(rc))
+ return pClone;
+ delete pClone;
+ }
+ return NULL;
+}
+
+
+int RTCRestBinaryParameter::resetToDefault() RT_NOEXCEPT
+{
+ m_cbContentLength = UINT64_MAX;
+ m_pfnProducer = NULL;
+ m_pvCallbackData = NULL;
+ return RTCRestBinary::resetToDefault();
+}
+
+
+const char *RTCRestBinaryParameter::typeName(void) const RT_NOEXCEPT
+{
+ return "RTCRestBinaryParameter";
+}
+
+
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestBinaryParameter::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestBinaryParameter();
+}
+
+
+int RTCRestBinaryParameter::setContentType(const char *a_pszContentType) RT_NOEXCEPT
+{
+ return m_strContentType.assignNoThrow(a_pszContentType);
+}
+
+
+void RTCRestBinaryParameter::setProducerCallback(PFNPRODUCER a_pfnProducer, void *a_pvCallbackData /*= NULL*/,
+ uint64_t a_cbContentLength /*= UINT64_MAX*/) RT_NOEXCEPT
+{
+ freeData();
+
+ m_pfnProducer = a_pfnProducer;
+ m_pvCallbackData = a_pvCallbackData;
+ m_cbContentLength = a_cbContentLength;
+}
+
+
+int RTCRestBinaryParameter::xmitPrepare(RTHTTP a_hHttp) const RT_NOEXCEPT
+{
+ AssertReturn(m_pbData != NULL || m_pfnProducer != NULL || m_cbContentLength == 0, VERR_INVALID_STATE);
+
+
+ /*
+ * Set the content type if given.
+ */
+ if (m_strContentType.isNotEmpty())
+ {
+ Assert(!RTHttpGetHeader(a_hHttp, RT_STR_TUPLE("Content-Type")));
+ int rc = RTHttpAddHeader(a_hHttp, "Content-Type", m_strContentType.c_str(), m_strContentType.length(),
+ RTHTTPADDHDR_F_BACK);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Set the content length if given.
+ */
+ if (m_cbContentLength != UINT64_MAX)
+ {
+ const char *pszContentLength = RTHttpGetHeader(a_hHttp, RT_STR_TUPLE("Content-Length"));
+ AssertMsgReturn(!pszContentLength || RTStrToUInt64(pszContentLength) == m_cbContentLength,
+ ("pszContentLength=%s does not match m_cbContentLength=%RU64\n", pszContentLength, m_cbContentLength),
+ VERR_MISMATCH);
+ if (!pszContentLength)
+ {
+ char szValue[64];
+ ssize_t cchValue = RTStrFormatU64(szValue, sizeof(szValue), m_cbContentLength, 10, 0, 0, 0);
+ int rc = RTHttpAddHeader(a_hHttp, "Content-Length", szValue, cchValue, RTHTTPADDHDR_F_BACK);
+ AssertRCReturn(rc, rc);
+ }
+ }
+
+ /*
+ * Register an upload callback.
+ */
+ int rc = RTHttpSetUploadCallback(a_hHttp, m_cbContentLength, xmitHttpCallback, (RTCRestBinaryParameter *)this);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+
+}
+
+
+/*static*/ DECLCALLBACK(int)
+RTCRestBinaryParameter::xmitHttpCallback(RTHTTP hHttp, void *pvBuf, size_t cbBuf,
+ uint64_t offContent, size_t *pcbActual, void *pvUser) RT_NOEXCEPT
+{
+ RTCRestBinaryParameter *pThis = (RTCRestBinaryParameter *)pvUser;
+
+ /*
+ * Call the user upload callback if we've got one.
+ */
+ if (pThis->m_pfnProducer)
+ return pThis->m_pfnProducer(pThis, pvBuf, cbBuf, offContent, pcbActual);
+
+ /*
+ * Feed from the memory buffer.
+ */
+ if (offContent < pThis->m_cbContentLength)
+ {
+ uint64_t const cbLeft = pThis->m_cbContentLength - offContent;
+ size_t const cbToCopy = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft;
+ memcpy(pvBuf, &pThis->m_pbData[(size_t)offContent], cbToCopy);
+ *pcbActual = cbToCopy;
+ }
+ else
+ *pcbActual = 0;
+
+ RT_NOREF(hHttp);
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestBinaryParameter::xmitComplete(RTHTTP a_hHttp) const RT_NOEXCEPT
+{
+ /* Unset the callback. */
+ int rc = RTHttpSetUploadCallback(a_hHttp, UINT64_MAX, NULL, NULL);
+ AssertRC(rc);
+}
+
+
+/*********************************************************************************************************************************
+* RTCRestBinaryResponse Implementation. *
+*********************************************************************************************************************************/
+
+/**
+ * Default constructor.
+ */
+RTCRestBinaryResponse::RTCRestBinaryResponse() RT_NOEXCEPT
+ : RTCRestBinary()
+ , m_cbContentLength(UINT64_MAX)
+ , m_cbDownloaded(0)
+ , m_pfnConsumer(NULL)
+ , m_pvCallbackData(NULL)
+ , m_cbMaxDownload(RTCREST_MAX_DOWNLOAD_SIZE_DEFAULT)
+{
+}
+
+
+int RTCRestBinaryResponse::assignCopy(RTCRestBinaryResponse const &a_rThat) RT_NOEXCEPT
+{
+ AssertReturn(a_rThat.m_pfnConsumer, VERR_INVALID_STATE);
+ int rc = assignCopy(*(RTCRestBinary const *)&a_rThat);
+ m_cbContentLength = a_rThat.m_cbContentLength;
+ m_cbDownloaded = a_rThat.m_cbDownloaded;
+ m_cbMaxDownload = a_rThat.m_cbMaxDownload;
+ return rc;
+}
+
+
+int RTCRestBinaryResponse::assignCopy(RTCRestBinary const &a_rThat) RT_NOEXCEPT
+{
+ m_cbContentLength = UINT64_MAX;
+ m_cbDownloaded = 0;
+ m_pfnConsumer = NULL;
+ m_pvCallbackData = NULL;
+ return RTCRestBinary::assignCopy(a_rThat);
+}
+
+
+int RTCRestBinaryResponse::assignCopy(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT
+{
+ RT_NOREF(a_pvData, a_cbData);
+ AssertMsgFailedReturn(("Makes no sense for downloads.\n"), VERR_INVALID_STATE);
+}
+
+
+int RTCRestBinaryResponse::assignReadOnly(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT
+{
+ RT_NOREF(a_pvData, a_cbData);
+ AssertMsgFailedReturn(("Makes no sense for downloads.\n"), VERR_INVALID_STATE);
+}
+
+
+int RTCRestBinaryResponse::assignWriteable(void *a_pvBuf, size_t a_cbBuf) RT_NOEXCEPT
+{
+ m_cbContentLength = UINT64_MAX;
+ m_cbDownloaded = 0;
+ m_pfnConsumer = NULL;
+ m_pvCallbackData = NULL;
+ AssertStmt(a_cbBuf <= m_cbMaxDownload, m_cbMaxDownload = a_cbBuf);
+ return RTCRestBinary::assignWriteable(a_pvBuf, a_cbBuf);
+}
+
+
+RTCRestObjectBase *RTCRestBinaryResponse::baseClone() const RT_NOEXCEPT
+{
+ RTCRestBinaryResponse *pClone = new (std::nothrow) RTCRestBinaryResponse();
+ if (pClone)
+ {
+ int rc = pClone->assignCopy(*this);
+ if (RT_SUCCESS(rc))
+ return pClone;
+ delete pClone;
+ }
+ return NULL;
+}
+
+
+int RTCRestBinaryResponse::resetToDefault() RT_NOEXCEPT
+{
+ m_cbContentLength = UINT64_MAX;
+ m_cbDownloaded = 0;
+ m_pfnConsumer = NULL;
+ m_pvCallbackData = NULL;
+ m_cbMaxDownload = RTCREST_MAX_DOWNLOAD_SIZE_DEFAULT;
+ return RTCRestBinary::resetToDefault();
+}
+
+
+const char *RTCRestBinaryResponse::typeName(void) const RT_NOEXCEPT
+{
+ return "RTCRestBinaryResponse";
+}
+
+
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestBinaryResponse::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestBinaryResponse();
+}
+
+
+void RTCRestBinaryResponse::setMaxDownloadSize(size_t a_cbMaxDownload) RT_NOEXCEPT
+{
+ if (a_cbMaxDownload == 0)
+ m_cbMaxDownload = RTCREST_MAX_DOWNLOAD_SIZE_DEFAULT;
+ else
+ m_cbMaxDownload = a_cbMaxDownload;
+}
+
+
+void RTCRestBinaryResponse::setConsumerCallback(PFNCONSUMER a_pfnConsumer, void *a_pvCallbackData /*= NULL*/) RT_NOEXCEPT
+{
+ freeData();
+
+ m_pfnConsumer = a_pfnConsumer;
+ m_pvCallbackData = a_pvCallbackData;
+ m_cbDownloaded = 0;
+ m_cbContentLength = UINT64_MAX;
+}
+
+
+int RTCRestBinaryResponse::receivePrepare(RTHTTP a_hHttp, uint32_t a_fCallbackFlags) RT_NOEXCEPT
+{
+ AssertReturn(!m_fReadOnly, VERR_INVALID_STATE);
+
+ /*
+ * Register the download callback.
+ */
+ int rc = RTHttpSetDownloadCallback(a_hHttp, a_fCallbackFlags, receiveHttpCallback, this);
+ AssertRC(rc);
+ return rc;
+}
+
+
+/*static*/ DECLCALLBACK(int)
+RTCRestBinaryResponse::receiveHttpCallback(RTHTTP hHttp, void const *pvBuf, size_t cbBuf, uint32_t uHttpStatus,
+ uint64_t offContent, uint64_t cbContent, void *pvUser) RT_NOEXCEPT
+{
+ RTCRestBinaryResponse *pThis = (RTCRestBinaryResponse *)pvUser;
+ Assert(offContent == pThis->m_cbDownloaded);
+ pThis->m_cbContentLength = cbContent;
+
+ /*
+ * Call the user download callback if we've got one.
+ */
+ if (pThis->m_pfnConsumer)
+ {
+ int rc = pThis->m_pfnConsumer(pThis, pvBuf, cbBuf, uHttpStatus, offContent, cbContent);
+ if (RT_SUCCESS(rc))
+ pThis->m_cbDownloaded = offContent + cbBuf;
+ return rc;
+ }
+
+ /*
+ * Check download limit before adding more data.
+ */
+ AssertMsgReturn(offContent + cbBuf <= pThis->m_cbMaxDownload,
+ ("%RU64 + %zu = %RU64; max=%RU64", offContent, cbBuf, offContent + cbBuf, pThis->m_cbMaxDownload),
+ VERR_TOO_MUCH_DATA);
+
+ /*
+ * Make sure we've got sufficient writable buffer space before we copy in the data.
+ */
+ AssertReturn(!pThis->m_fReadOnly, VERR_INVALID_STATE);
+ if (offContent + cbBuf <= pThis->m_cbAllocated)
+ { /* likely, except for the first time. */ }
+ else
+ {
+ AssertMsgReturn(pThis->m_fFreeable,
+ ("offContent=%RU64 cbBuf=%zu m_cbAllocated=%zu", offContent, cbBuf, pThis->m_cbAllocated),
+ VERR_TOO_MUCH_DATA);
+ AssertMsgReturn(cbContent <= pThis->m_cbMaxDownload || cbContent == UINT64_MAX,
+ ("cbContent: %RU64; max=%RU64", cbContent, pThis->m_cbMaxDownload),
+ VERR_TOO_MUCH_DATA);
+
+ if (offContent == 0 && cbContent != UINT64_MAX)
+ {
+ void *pvNew = RTMemRealloc(pThis->m_pbData, (size_t)cbContent);
+ if (!pvNew)
+ return VERR_NO_MEMORY;
+ pThis->m_pbData = (uint8_t *)pvNew;
+ pThis->m_cbAllocated = (size_t)cbContent;
+ }
+ else
+ {
+ size_t cbNeeded = offContent + cbBuf;
+ size_t cbNew;
+ if (pThis->m_cbAllocated == 0)
+ cbNew = RT_MAX(_64K, RT_ALIGN_Z(cbNeeded, _64K));
+ else if (pThis->m_cbAllocated < _64M && cbNeeded <= _64M)
+ {
+ cbNew = pThis->m_cbAllocated * 2;
+ while (cbNew < cbNeeded)
+ cbNew *= 2;
+ }
+ else
+ cbNew = RT_ALIGN_Z(cbNeeded, _32M);
+
+ void *pvNew = RTMemRealloc(pThis->m_pbData, cbNew);
+ if (!pvNew)
+ return VERR_NO_MEMORY;
+ pThis->m_pbData = (uint8_t *)pvNew;
+ pThis->m_cbAllocated = cbNew;
+ }
+ }
+
+ /*
+ * Do the copying.
+ */
+ memcpy(&pThis->m_pbData[(size_t)offContent], pvBuf, cbBuf);
+ pThis->m_cbDownloaded = offContent + cbBuf;
+
+ /* we cap it at m_cbMaxDownload which is size_t so this cast is safe */
+ pThis->m_cbData = (size_t)pThis->m_cbDownloaded;
+
+ RT_NOREF(hHttp);
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestBinaryResponse::receiveComplete(RTHTTP a_hHttp) RT_NOEXCEPT
+{
+ /* Unset the callback. */
+ int rc = RTHttpSetDownloadCallback(a_hHttp, RTHTTPDOWNLOAD_F_ANY_STATUS, NULL, NULL);
+ AssertRC(rc);
+}
+
diff --git a/src/VBox/Runtime/common/rest/rest-primary-object-types.cpp b/src/VBox/Runtime/common/rest/rest-primary-object-types.cpp
new file mode 100644
index 00000000..9657a018
--- /dev/null
+++ b/src/VBox/Runtime/common/rest/rest-primary-object-types.cpp
@@ -0,0 +1,2403 @@
+/* $Id: rest-primary-object-types.cpp $ */
+/** @file
+ * IPRT - C++ REST, RTCRestObjectBase implementation.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_REST
+#include <iprt/cpp/restbase.h>
+
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/cpp/restoutput.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+
+
+/*********************************************************************************************************************************
+* RTCRestObjectBase implementation *
+*********************************************************************************************************************************/
+
+/** Default constructor. */
+RTCRestObjectBase::RTCRestObjectBase() RT_NOEXCEPT
+ : m_fNullIndicator(false)
+{
+}
+
+
+/** Copy constructor. */
+RTCRestObjectBase::RTCRestObjectBase(RTCRestObjectBase const &a_rThat) RT_NOEXCEPT
+ : m_fNullIndicator(a_rThat.m_fNullIndicator)
+{
+}
+
+
+/** Destructor. */
+RTCRestObjectBase::~RTCRestObjectBase()
+{
+ /* nothing to do */
+}
+
+
+/** Copy assignment operator. */
+RTCRestObjectBase &RTCRestObjectBase::operator=(RTCRestObjectBase const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ return *this;
+}
+
+
+int RTCRestObjectBase::setNull() RT_NOEXCEPT
+{
+ int rc = resetToDefault();
+ m_fNullIndicator = true;
+ return rc;
+}
+
+
+void RTCRestObjectBase::setNotNull() RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+}
+
+
+int RTCRestObjectBase::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ /*
+ * Just wrap the JSON serialization method.
+ */
+ RTCRestOutputToString Tmp(a_pDst, RT_BOOL(a_fFlags & kToString_Append));
+ serializeAsJson(Tmp);
+ return Tmp.finalize() ? VINF_SUCCESS : VERR_NO_MEMORY;
+}
+
+
+RTCString RTCRestObjectBase::toString() const
+{
+ RTCString strRet;
+ toString(&strRet, 0);
+ return strRet;
+}
+
+
+int RTCRestObjectBase::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ RT_NOREF(a_fFlags);
+
+ /*
+ * Just wrap the JSON serialization method.
+ */
+ RTJSONVAL hValue = NIL_RTJSONVAL;
+ int rc = RTJsonParseFromString(&hValue, a_rValue.c_str(), a_pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ RTCRestJsonPrimaryCursor PrimaryCursor(hValue, a_pszName, a_pErrInfo);
+ rc = deserializeFromJson(PrimaryCursor.m_Cursor);
+ }
+ return rc;
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestBool implementation *
+*********************************************************************************************************************************/
+
+/** Factory method. */
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestBool::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestBool();
+}
+
+
+/**
+ * @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON
+ */
+/*static*/ DECLCALLBACK(int)
+RTCRestBool::deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT
+{
+ RTCRestObjectBase *pObj = createInstance();
+ *a_ppInstance = pObj;
+ if (pObj)
+ return pObj->deserializeFromJson(a_rCursor);
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory");
+}
+
+
+/** Default constructor. */
+RTCRestBool::RTCRestBool() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_fValue(false)
+{
+}
+
+
+/** Copy constructor. */
+RTCRestBool::RTCRestBool(RTCRestBool const &a_rThat) RT_NOEXCEPT
+ : RTCRestObjectBase(a_rThat)
+ , m_fValue(a_rThat.m_fValue)
+{
+}
+
+
+/** From value constructor. */
+RTCRestBool::RTCRestBool(bool fValue) RT_NOEXCEPT
+ : m_fValue(fValue)
+{
+}
+
+
+/** Destructor. */
+RTCRestBool::~RTCRestBool()
+{
+ /* nothing to do */
+}
+
+
+/** Copy assignment operator. */
+RTCRestBool &RTCRestBool::operator=(RTCRestBool const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_fValue = a_rThat.m_fValue;
+ return *this;
+}
+
+
+int RTCRestBool::assignCopy(RTCRestBool const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_fValue = a_rThat.m_fValue;
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestBool::assignValue(bool a_fValue) RT_NOEXCEPT
+{
+ m_fValue = a_fValue;
+ m_fNullIndicator = false;
+}
+
+
+RTCRestObjectBase *RTCRestBool::baseClone() const RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestBool(*this);
+}
+
+
+int RTCRestBool::resetToDefault() RT_NOEXCEPT
+{
+ m_fValue = false;
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestBool::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ a_rDst.printf(!m_fNullIndicator ? m_fValue ? "true" : "false" : "null");
+ return a_rDst;
+}
+
+
+int RTCRestBool::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ m_fValue = false;
+ m_fNullIndicator = false;
+
+ RTJSONVALTYPE enmType = RTJsonValueGetType(a_rCursor.m_hValue);
+
+ if (enmType == RTJSONVALTYPE_TRUE)
+ {
+ m_fValue = true;
+ return VINF_SUCCESS;
+ }
+
+ if (enmType == RTJSONVALTYPE_FALSE)
+ return VINF_SUCCESS;
+
+ if (enmType == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_WRONG_JSON_TYPE_FOR_BOOL, "wrong JSON type %s for boolean",
+ RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+}
+
+
+int RTCRestBool::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ if (!(a_fFlags & kToString_Append))
+ {
+ if (!m_fNullIndicator)
+ {
+ if (m_fValue)
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("true"));
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("false"));
+ }
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("null"));
+ }
+
+ if (!m_fNullIndicator)
+ {
+ if (m_fValue)
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("true"));
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("false"));
+ }
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+}
+
+
+int RTCRestBool::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ RT_NOREF(a_fFlags);
+
+ if (a_rValue.startsWithWord("true", RTCString::CaseInsensitive))
+ {
+ m_fValue = true;
+ m_fNullIndicator = false;
+ }
+ else if (a_rValue.startsWithWord("false", RTCString::CaseInsensitive))
+ {
+ m_fValue = false;
+ m_fNullIndicator = false;
+ }
+ else if (a_rValue.startsWithWord("null", RTCString::CaseInsensitive))
+ {
+ m_fValue = false;
+ m_fNullIndicator = true;
+ }
+ else
+ return RTErrInfoSetF(a_pErrInfo, VERR_REST_UNABLE_TO_PARSE_STRING_AS_BOOL,
+ "%s: unable to parse '%s' as bool", a_pszName, a_rValue.c_str());
+ return VINF_SUCCESS;
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestBool::typeClass() const RT_NOEXCEPT
+{
+ return kTypeClass_Bool;
+}
+
+
+const char *RTCRestBool::typeName() const RT_NOEXCEPT
+{
+ return "bool";
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestInt64 implementation *
+*********************************************************************************************************************************/
+
+/** Factory method. */
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestInt64::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestInt64();
+}
+
+
+/**
+ * @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON
+ */
+/*static*/ DECLCALLBACK(int)
+RTCRestInt64::deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT
+{
+ RTCRestObjectBase *pObj = createInstance();
+ *a_ppInstance = pObj;
+ if (pObj)
+ return pObj->deserializeFromJson(a_rCursor);
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory");
+}
+
+
+/** Default constructor. */
+RTCRestInt64::RTCRestInt64() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_iValue(0)
+{
+}
+
+
+/** Copy constructor. */
+RTCRestInt64::RTCRestInt64(RTCRestInt64 const &a_rThat) RT_NOEXCEPT
+ : RTCRestObjectBase(a_rThat)
+ , m_iValue(a_rThat.m_iValue)
+{
+}
+
+
+/** From value constructor. */
+RTCRestInt64::RTCRestInt64(int64_t iValue) RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_iValue(iValue)
+{
+}
+
+
+/** Destructor. */
+RTCRestInt64::~RTCRestInt64()
+{
+ /* nothing to do */
+}
+
+
+/** Copy assignment operator. */
+RTCRestInt64 &RTCRestInt64::operator=(RTCRestInt64 const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_iValue = a_rThat.m_iValue;
+ return *this;
+}
+
+
+int RTCRestInt64::assignCopy(RTCRestInt64 const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_iValue = a_rThat.m_iValue;
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestInt64::assignValue(int64_t a_iValue) RT_NOEXCEPT
+{
+ m_iValue = a_iValue;
+ m_fNullIndicator = false;
+}
+
+
+RTCRestObjectBase *RTCRestInt64::baseClone() const RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestInt64(*this);
+}
+
+
+int RTCRestInt64::resetToDefault() RT_NOEXCEPT
+{
+ m_iValue = 0;
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestInt64::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ a_rDst.printf("%RI64", m_iValue);
+ else
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestInt64::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ m_iValue = 0;
+ m_fNullIndicator = false;
+
+ RTJSONVALTYPE enmType = RTJsonValueGetType(a_rCursor.m_hValue);
+ if (enmType == RTJSONVALTYPE_INTEGER)
+ {
+ int rc = RTJsonValueQueryInteger(a_rCursor.m_hValue, &m_iValue);
+ if (RT_SUCCESS(rc))
+ return rc;
+ return a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonValueQueryInteger failed with %Rrc", rc);
+ }
+
+ if (enmType == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ /* This is probably non-sense... */
+ if (enmType == RTJSONVALTYPE_TRUE)
+ m_iValue = 1;
+
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_WRONG_JSON_TYPE_FOR_INTEGER, "wrong JSON type %s for 64-bit integer",
+ RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+}
+
+
+int RTCRestInt64::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ if (!(a_fFlags & kToString_Append))
+ {
+ if (!m_fNullIndicator)
+ return a_pDst->printfNoThrow("%RI64", m_iValue);
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("null"));
+ }
+ if (!m_fNullIndicator)
+ return a_pDst->appendPrintfNoThrow("%RI64", m_iValue);
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+}
+
+
+int RTCRestInt64::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ RT_NOREF(a_fFlags);
+
+ m_iValue = 0;
+ m_fNullIndicator = false;
+
+/** @todo RTStrStripL and RTStrToInt64Full has a different idea what consitutes spaces... */
+ int rc = RTStrToInt64Full(RTStrStripL(a_rValue.c_str()), 0, &m_iValue);
+ if (rc == VINF_SUCCESS || rc == VERR_TRAILING_SPACES)
+ return VINF_SUCCESS;
+
+ if (a_rValue.startsWithWord("null", RTCString::CaseInsensitive))
+ {
+ m_iValue = 0;
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ return RTErrInfoSetF(a_pErrInfo, rc, "%s: error %Rrc parsing '%s' as int64_t", a_pszName, rc, a_rValue.c_str());
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestInt64::typeClass() const RT_NOEXCEPT
+{
+ return kTypeClass_Int64;
+}
+
+
+const char *RTCRestInt64::typeName() const RT_NOEXCEPT
+{
+ return "int64_t";
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestInt32 implementation *
+*********************************************************************************************************************************/
+
+/** Factory method. */
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestInt32::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestInt32();
+}
+
+
+/**
+ * @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON
+ */
+/*static*/ DECLCALLBACK(int)
+RTCRestInt32::deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT
+{
+ RTCRestObjectBase *pObj = createInstance();
+ *a_ppInstance = pObj;
+ if (pObj)
+ return pObj->deserializeFromJson(a_rCursor);
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory");
+}
+
+
+/** Default constructor. */
+RTCRestInt32::RTCRestInt32() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_iValue(0)
+{
+}
+
+
+/** Copy constructor. */
+RTCRestInt32::RTCRestInt32(RTCRestInt32 const &a_rThat) RT_NOEXCEPT
+ : RTCRestObjectBase(a_rThat)
+ , m_iValue(a_rThat.m_iValue)
+{
+}
+
+
+/** From value constructor. */
+RTCRestInt32::RTCRestInt32(int32_t iValue) RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_iValue(iValue)
+{
+}
+
+
+/** Destructor. */
+RTCRestInt32::~RTCRestInt32() RT_NOEXCEPT
+{
+ /* nothing to do */
+}
+
+
+/** Copy assignment operator. */
+RTCRestInt32 &RTCRestInt32::operator=(RTCRestInt32 const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_iValue = a_rThat.m_iValue;
+ return *this;
+}
+
+
+int RTCRestInt32::assignCopy(RTCRestInt32 const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_iValue = a_rThat.m_iValue;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestObjectBase *RTCRestInt32::baseClone() const RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestInt32(*this);
+}
+
+
+int RTCRestInt32::resetToDefault() RT_NOEXCEPT
+{
+ m_iValue = 0;
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestInt32::assignValue(int32_t a_iValue) RT_NOEXCEPT
+{
+ m_iValue = a_iValue;
+ m_fNullIndicator = false;
+}
+
+
+RTCRestOutputBase &RTCRestInt32::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ a_rDst.printf("%RI32", m_iValue);
+ else
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestInt32::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ m_iValue = 0;
+ m_fNullIndicator = false;
+
+ RTJSONVALTYPE enmType = RTJsonValueGetType(a_rCursor.m_hValue);
+ if (enmType == RTJSONVALTYPE_INTEGER)
+ {
+ int64_t iTmp = m_iValue;
+ int rc = RTJsonValueQueryInteger(a_rCursor.m_hValue, &iTmp);
+ if (RT_SUCCESS(rc))
+ {
+ m_iValue = (int32_t)iTmp;
+ if (m_iValue == iTmp)
+ return rc;
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_OUT_OF_RANGE, "value %RI64 does not fit in 32 bits", iTmp);
+ }
+ return a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonValueQueryInteger failed with %Rrc", rc);
+ }
+
+ if (enmType == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ /* This is probably non-sense... */
+ if (enmType == RTJSONVALTYPE_TRUE)
+ m_iValue = 1;
+
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_WRONG_JSON_TYPE_FOR_INTEGER, "wrong JSON type %s for 32-bit integer",
+ RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+}
+
+
+int RTCRestInt32::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ if (!(a_fFlags & kToString_Append))
+ {
+ if (!m_fNullIndicator)
+ return a_pDst->printfNoThrow("%RI32", m_iValue);
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("null"));
+ }
+ if (!m_fNullIndicator)
+ return a_pDst->appendPrintfNoThrow("%RI32", m_iValue);
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+}
+
+
+int RTCRestInt32::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ RT_NOREF(a_fFlags);
+
+ m_iValue = 0;
+ m_fNullIndicator = false;
+
+/** @todo RTStrStripL and RTStrToInt32Full has a different idea what consitutes spaces... */
+ int rc = RTStrToInt32Full(RTStrStripL(a_rValue.c_str()), 0, &m_iValue);
+ if (rc == VINF_SUCCESS || rc == VERR_TRAILING_SPACES)
+ return VINF_SUCCESS;
+
+ if (a_rValue.startsWithWord("null", RTCString::CaseInsensitive))
+ {
+ m_iValue = 0;
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ return RTErrInfoSetF(a_pErrInfo, rc, "%s: error %Rrc parsing '%s' as int32_t", a_pszName, rc, a_rValue.c_str());
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestInt32::typeClass() const RT_NOEXCEPT
+{
+ return kTypeClass_Int32;
+}
+
+
+const char *RTCRestInt32::typeName() const RT_NOEXCEPT
+{
+ return "int32_t";
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestInt16 implementation *
+*********************************************************************************************************************************/
+
+/** Factory method. */
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestInt16::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestInt16();
+}
+
+
+/**
+ * @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON
+ */
+/*static*/ DECLCALLBACK(int)
+RTCRestInt16::deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT
+{
+ RTCRestObjectBase *pObj = createInstance();
+ *a_ppInstance = pObj;
+ if (pObj)
+ return pObj->deserializeFromJson(a_rCursor);
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory");
+}
+
+
+/** Default constructor. */
+RTCRestInt16::RTCRestInt16() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_iValue(0)
+{
+}
+
+
+/** Copy constructor. */
+RTCRestInt16::RTCRestInt16(RTCRestInt16 const &a_rThat) RT_NOEXCEPT
+ : RTCRestObjectBase(a_rThat)
+ , m_iValue(a_rThat.m_iValue)
+{
+}
+
+
+/** From value constructor. */
+RTCRestInt16::RTCRestInt16(int16_t iValue) RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_iValue(iValue)
+{
+}
+
+
+/** Destructor. */
+RTCRestInt16::~RTCRestInt16()
+{
+ /* nothing to do */
+}
+
+
+/** Copy assignment operator. */
+RTCRestInt16 &RTCRestInt16::operator=(RTCRestInt16 const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_iValue = a_rThat.m_iValue;
+ return *this;
+}
+
+
+int RTCRestInt16::assignCopy(RTCRestInt16 const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_iValue = a_rThat.m_iValue;
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestInt16::assignValue(int16_t a_iValue) RT_NOEXCEPT
+{
+ m_iValue = a_iValue;
+ m_fNullIndicator = false;
+}
+
+
+RTCRestObjectBase *RTCRestInt16::baseClone() const RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestInt16(*this);
+}
+
+
+int RTCRestInt16::resetToDefault() RT_NOEXCEPT
+{
+ m_iValue = 0;
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestInt16::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ a_rDst.printf("%RI16", m_iValue);
+ else
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestInt16::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ m_iValue = 0;
+ m_fNullIndicator = false;
+
+ RTJSONVALTYPE enmType = RTJsonValueGetType(a_rCursor.m_hValue);
+ if (enmType == RTJSONVALTYPE_INTEGER)
+ {
+ int64_t iTmp = m_iValue;
+ int rc = RTJsonValueQueryInteger(a_rCursor.m_hValue, &iTmp);
+ if (RT_SUCCESS(rc))
+ {
+ m_iValue = (int16_t)iTmp;
+ if (m_iValue == iTmp)
+ return rc;
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_OUT_OF_RANGE, "value %RI64 does not fit in 16 bits", iTmp);
+ }
+ return a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonValueQueryInteger failed with %Rrc", rc);
+ }
+
+ if (enmType == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ /* This is probably non-sense... */
+ if (enmType == RTJSONVALTYPE_TRUE)
+ m_iValue = 1;
+
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_WRONG_JSON_TYPE_FOR_INTEGER, "wrong JSON type %s for 16-bit integer",
+ RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+}
+
+
+int RTCRestInt16::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ if (!(a_fFlags & kToString_Append))
+ {
+ if (!m_fNullIndicator)
+ return a_pDst->printfNoThrow("%RI16", m_iValue);
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("null"));
+ }
+ if (!m_fNullIndicator)
+ return a_pDst->appendPrintfNoThrow("%RI16", m_iValue);
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+}
+
+
+int RTCRestInt16::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ RT_NOREF(a_fFlags);
+
+ m_iValue = 0;
+ m_fNullIndicator = false;
+
+/** @todo RTStrStripL and RTStrToInt16Full has a different idea what consitutes spaces... */
+ int rc = RTStrToInt16Full(RTStrStripL(a_rValue.c_str()), 0, &m_iValue);
+ if (rc == VINF_SUCCESS || rc == VERR_TRAILING_SPACES)
+ return VINF_SUCCESS;
+
+ if (a_rValue.startsWithWord("null", RTCString::CaseInsensitive))
+ {
+ m_iValue = 0;
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ return RTErrInfoSetF(a_pErrInfo, rc, "%s: error %Rrc parsing '%s' as int16_t", a_pszName, rc, a_rValue.c_str());
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestInt16::typeClass() const RT_NOEXCEPT
+{
+ return kTypeClass_Int16;
+}
+
+
+const char *RTCRestInt16::typeName() const RT_NOEXCEPT
+{
+ return "int16_t";
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestDouble implementation *
+*********************************************************************************************************************************/
+
+/** Factory method. */
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestDouble::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestDouble();
+}
+
+
+/**
+ * @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON
+ */
+/*static*/ DECLCALLBACK(int)
+RTCRestDouble::deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT
+{
+ RTCRestObjectBase *pObj = createInstance();
+ *a_ppInstance = pObj;
+ if (pObj)
+ return pObj->deserializeFromJson(a_rCursor);
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory");
+}
+
+
+/** Default constructor. */
+RTCRestDouble::RTCRestDouble() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_rdValue(0.0)
+{
+}
+
+
+/** Copy constructor. */
+RTCRestDouble::RTCRestDouble(RTCRestDouble const &a_rThat) RT_NOEXCEPT
+ : RTCRestObjectBase(a_rThat)
+ , m_rdValue(a_rThat.m_rdValue)
+{
+}
+
+
+/** From value constructor. */
+RTCRestDouble::RTCRestDouble(double rdValue) RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_rdValue(rdValue)
+{
+}
+
+
+/** Destructor. */
+RTCRestDouble::~RTCRestDouble()
+{
+ /* nothing to do */
+}
+
+
+/** Copy assignment operator. */
+RTCRestDouble &RTCRestDouble::operator=(RTCRestDouble const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_rdValue = a_rThat.m_rdValue;
+ return *this;
+}
+
+
+int RTCRestDouble::assignCopy(RTCRestDouble const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_rdValue = a_rThat.m_rdValue;
+ return VINF_SUCCESS;
+}
+
+
+void RTCRestDouble::assignValue(double a_rdValue) RT_NOEXCEPT
+{
+ m_rdValue = a_rdValue;
+ m_fNullIndicator = false;
+}
+
+
+RTCRestObjectBase *RTCRestDouble::baseClone() const RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestDouble(*this);
+}
+
+
+int RTCRestDouble::resetToDefault() RT_NOEXCEPT
+{
+ m_rdValue = 0.0;
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestDouble::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ {
+
+ /* Just a simple approximation here. */
+ /** @todo Not 100% sure printf %g produces the right result for JSON floating point, but it'll have to do for now... */
+ char szValue[128];
+#ifdef _MSC_VER
+ _snprintf(szValue, sizeof(szValue), "%.18g", m_rdValue);
+#else
+ snprintf(szValue, sizeof(szValue), "%.18g", m_rdValue);
+#endif
+ size_t cchValue = strlen(szValue);
+ while (cchValue > 0 && szValue[cchValue - 1] == '0')
+ cchValue--;
+ szValue[cchValue] = '\0';
+
+ a_rDst.printf("%s", szValue);
+ }
+ else
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestDouble::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ m_rdValue = 0.0;
+ m_fNullIndicator = false;
+
+ RTJSONVALTYPE enmType = RTJsonValueGetType(a_rCursor.m_hValue);
+ if (enmType == RTJSONVALTYPE_NUMBER)
+ {
+ int rc = RTJsonValueQueryNumber(a_rCursor.m_hValue, &m_rdValue);
+ if (RT_SUCCESS(rc))
+ return rc;
+ return a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonValueQueryNumber failed with %Rrc", rc);
+ }
+
+ if (enmType == RTJSONVALTYPE_INTEGER)
+ {
+ int64_t iTmp = 0;
+ int rc = RTJsonValueQueryInteger(a_rCursor.m_hValue, &iTmp);
+ if (RT_SUCCESS(rc))
+ {
+ m_rdValue = iTmp;
+ if ((int64_t)m_rdValue == iTmp)
+ return rc;
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_OUT_OF_RANGE, "value %RI64 does not fit in a double", iTmp);
+ }
+ return a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonValueQueryInteger failed with %Rrc", rc);
+ }
+
+ if (enmType == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ /* This is probably non-sense... */
+ if (enmType == RTJSONVALTYPE_TRUE)
+ m_rdValue = 1.0;
+
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_WRONG_JSON_TYPE_FOR_DOUBLE, "wrong JSON type %s for a double",
+ RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+}
+
+
+int RTCRestDouble::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ {
+ /* Just a simple approximation here. */
+ /** @todo Not 100% sure printf %g produces the right result for JSON floating point, but it'll have to do for now... */
+ char szValue[128];
+#ifdef _MSC_VER
+ _snprintf(szValue, sizeof(szValue), "%.18g", m_rdValue);
+#else
+ snprintf(szValue, sizeof(szValue), "%.18g", m_rdValue);
+#endif
+ size_t cchValue = strlen(szValue);
+ while (cchValue > 0 && szValue[cchValue - 1] == '0')
+ cchValue--;
+ szValue[cchValue] = '\0';
+
+ if (!(a_fFlags & kToString_Append))
+ return a_pDst->assignNoThrow(szValue, cchValue);
+ return a_pDst->appendNoThrow(szValue, cchValue);
+ }
+
+ if (!(a_fFlags & kToString_Append))
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("null"));
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+}
+
+
+int RTCRestDouble::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ RT_NOREF(a_fFlags);
+
+ if (a_rValue.startsWithWord("null", RTCString::CaseInsensitive))
+ {
+ m_rdValue = 0.0;
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ m_fNullIndicator = false;
+
+ const char *pszValue = RTStrStripL(a_rValue.c_str());
+ errno = 0;
+ char *pszNext = NULL;
+ m_rdValue = strtod(pszValue, &pszNext);
+ if (errno == 0 && pszNext != pszValue)
+ {
+ if (!pszNext || *pszNext == '\0')
+ return VINF_SUCCESS;
+
+ while (RT_C_IS_SPACE(*pszNext))
+ pszNext++;
+ if (*pszNext == '\0')
+ return VINF_SUCCESS;
+
+ return RTErrInfoSetF(a_pErrInfo, VERR_TRAILING_CHARS, "%s: error VERR_TRAILING_CHARS parsing '%s' as double",
+ a_pszName, a_rValue.c_str());
+ }
+
+ if (!RT_C_IS_DIGIT(*pszValue) && *pszValue != '.')
+ return RTErrInfoSetF(a_pErrInfo, VERR_NO_DIGITS, "%s: error VERR_NO_DIGITS parsing '%s' as double",
+ a_pszName, a_rValue.c_str());
+ int rc = RTErrConvertFromErrno(errno);
+ return RTErrInfoSetF(a_pErrInfo, rc, "%s: error %Rrc parsing '%s' as double", a_pszName, rc, a_rValue.c_str());
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestDouble::typeClass() const RT_NOEXCEPT
+{
+ return kTypeClass_Double;
+}
+
+
+const char *RTCRestDouble::typeName() const RT_NOEXCEPT
+{
+ return "double";
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestString implementation *
+*********************************************************************************************************************************/
+
+/** Factory method. */
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestString::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestString();
+}
+
+
+/**
+ * @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON
+ */
+/*static*/ DECLCALLBACK(int)
+RTCRestString::deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT
+{
+ RTCRestObjectBase *pObj = createInstance();
+ *a_ppInstance = pObj;
+ if (pObj)
+ return pObj->deserializeFromJson(a_rCursor);
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory");
+}
+
+
+/** Default constructor. */
+RTCRestString::RTCRestString() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , RTCString()
+{
+}
+
+
+/** Copy constructor. */
+RTCRestString::RTCRestString(RTCRestString const &a_rThat)
+ : RTCRestObjectBase(a_rThat)
+ , RTCString(a_rThat)
+{
+}
+
+
+/** From value constructor. */
+RTCRestString::RTCRestString(RTCString const &a_rThat)
+ : RTCString(a_rThat)
+{
+}
+
+
+/** From value constructor. */
+RTCRestString::RTCRestString(const char *a_pszSrc)
+ : RTCRestObjectBase()
+ , RTCString(a_pszSrc)
+{
+}
+
+
+/** Destructor. */
+RTCRestString::~RTCRestString()
+{
+ /* nothing to do */
+}
+
+
+int RTCRestString::assignCopy(RTCRestString const &a_rThat) RT_NOEXCEPT
+{
+ int rc = assignNoThrow(a_rThat);
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ return rc;
+}
+
+
+int RTCRestString::assignCopy(RTCString const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ return assignNoThrow(a_rThat);
+}
+
+
+int RTCRestString::assignCopy(const char *a_pszThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ return assignNoThrow(a_pszThat);
+}
+
+
+int RTCRestString::setNull() RT_NOEXCEPT
+{
+ RTCString::setNull();
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestObjectBase *RTCRestString::baseClone() const RT_NOEXCEPT
+{
+ RTCRestString *pClone = new (std::nothrow) RTCRestString();
+ if (pClone)
+ {
+ int rc = pClone->assignCopy(*this);
+ if (RT_SUCCESS(rc))
+ return pClone;
+ delete pClone;
+ }
+ return NULL;
+}
+
+
+int RTCRestString::resetToDefault() RT_NOEXCEPT
+{
+ RTCString::setNull();
+ m_fNullIndicator = false;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestString::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ a_rDst.printf("%RMjs", m_psz ? m_psz : "");
+ else
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestString::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+
+ RTJSONVALTYPE enmType = RTJsonValueGetType(a_rCursor.m_hValue);
+ if (enmType == RTJSONVALTYPE_STRING)
+ {
+ const char *pszValue = RTJsonValueGetString(a_rCursor.m_hValue);
+ const size_t cchValue = strlen(pszValue);
+ int rc = assignNoThrow(pszValue, cchValue);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ return a_rCursor.m_pPrimary->addError(a_rCursor, rc, "no memory for %zu char long string", cchValue);
+ }
+
+ RTCString::setNull();
+
+ if (enmType == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_WRONG_JSON_TYPE_FOR_STRING, "wrong JSON type %s for string",
+ RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+}
+
+
+int RTCRestString::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ /* Note! m_fNullIndicator == true: empty string. */
+ if (!(a_fFlags & kToString_Append))
+ return a_pDst->assignNoThrow(*this);
+ return a_pDst->appendNoThrow(*this);
+}
+
+
+int RTCRestString::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ RT_NOREF(a_fFlags); RT_NOREF(a_pszName); RT_NOREF(a_pErrInfo);
+
+ /* Note! Unable to set m_fNullIndicator = true here. */
+ m_fNullIndicator = false;
+ return assignNoThrow(a_rValue);
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestString::typeClass() const RT_NOEXCEPT
+{
+ return kTypeClass_String;
+}
+
+
+const char *RTCRestString::typeName() const RT_NOEXCEPT
+{
+ return "RTCString";
+}
+
+
+int RTCRestString::assignNoThrow(const RTCString &a_rSrc) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ return RTCString::assignNoThrow(a_rSrc);
+}
+
+
+int RTCRestString::assignNoThrow(const char *a_pszSrc) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ return RTCString::assignNoThrow(a_pszSrc);
+}
+
+
+int RTCRestString::assignNoThrow(const RTCString &a_rSrc, size_t a_offSrc, size_t a_cchSrc /*= npos*/) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ return RTCString::assignNoThrow(a_rSrc, a_offSrc, a_cchSrc);
+}
+
+
+int RTCRestString::assignNoThrow(const char *a_pszSrc, size_t a_cchSrc) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ return RTCString::assignNoThrow(a_pszSrc, a_cchSrc);
+}
+
+
+int RTCRestString::assignNoThrow(size_t a_cTimes, char a_ch) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ return RTCString::assignNoThrow(a_cTimes, a_ch);
+}
+
+
+int RTCRestString::printfNoThrow(const char *pszFormat, ...) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = RTCString::printfVNoThrow(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+
+int RTCRestString::printfVNoThrow(const char *pszFormat, va_list va) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ return RTCString::printfVNoThrow(pszFormat, va);
+}
+
+
+RTCRestString &RTCRestString::operator=(const char *a_pcsz)
+{
+ m_fNullIndicator = false;
+ RTCString::operator=(a_pcsz);
+ return *this;
+}
+
+
+RTCRestString &RTCRestString::operator=(const RTCString &a_rThat)
+{
+ m_fNullIndicator = false;
+ RTCString::operator=(a_rThat);
+ return *this;
+}
+
+
+RTCRestString &RTCRestString::operator=(const RTCRestString &a_rThat)
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ RTCString::operator=(a_rThat);
+ return *this;
+}
+
+
+RTCRestString &RTCRestString::assign(const RTCString &a_rSrc)
+{
+ m_fNullIndicator = false;
+ RTCString::assign(a_rSrc);
+ return *this;
+}
+
+
+RTCRestString &RTCRestString::assign(const char *a_pszSrc)
+{
+ m_fNullIndicator = false;
+ RTCString::assign(a_pszSrc);
+ return *this;
+}
+
+
+RTCRestString &RTCRestString::assign(const RTCString &a_rSrc, size_t a_offSrc, size_t a_cchSrc /*= npos*/)
+{
+ m_fNullIndicator = false;
+ RTCString::assign(a_rSrc, a_offSrc, a_cchSrc);
+ return *this;
+}
+
+
+RTCRestString &RTCRestString::assign(const char *a_pszSrc, size_t a_cchSrc)
+{
+ m_fNullIndicator = false;
+ RTCString::assign(a_pszSrc, a_cchSrc);
+ return *this;
+}
+
+
+RTCRestString &RTCRestString::assign(size_t a_cTimes, char a_ch)
+{
+ m_fNullIndicator = false;
+ RTCString::assign(a_cTimes, a_ch);
+ return *this;
+}
+
+
+RTCRestString &RTCRestString::printf(const char *pszFormat, ...)
+{
+ m_fNullIndicator = false;
+ va_list va;
+ va_start(va, pszFormat);
+ RTCString::printfV(pszFormat, va);
+ va_end(va);
+ return *this;
+}
+
+
+RTCRestString &RTCRestString::printfV(const char *pszFormat, va_list va)
+{
+ m_fNullIndicator = false;
+ RTCString::printfV(pszFormat, va);
+ return *this;
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestDate implementation *
+*********************************************************************************************************************************/
+/*static*/ DECLCALLBACK(RTCRestObjectBase *) RTCRestDate::createInstance(void) RT_NOEXCEPT
+{
+ return new (std::nothrow) RTCRestDate();
+}
+
+
+/**
+ * @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON
+ */
+/*static*/ DECLCALLBACK(int)
+RTCRestDate::deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT
+{
+ RTCRestObjectBase *pObj = createInstance();
+ *a_ppInstance = pObj;
+ if (pObj)
+ return pObj->deserializeFromJson(a_rCursor);
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory");
+}
+
+
+RTCRestDate::RTCRestDate() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_fTimeSpecOkay(false)
+ , m_enmFormat(kFormat_Invalid)
+ , m_strFormatted()
+{
+ RTTimeSpecSetNano(&m_TimeSpec, 0);
+ RT_ZERO(m_Exploded);
+
+ /* Since we need to know the format, all date-time values default to 'null'. */
+ m_fNullIndicator = true;
+}
+
+
+RTCRestDate::RTCRestDate(RTCRestDate const &a_rThat)
+ : RTCRestObjectBase(a_rThat)
+ , m_fTimeSpecOkay(a_rThat.m_fTimeSpecOkay)
+ , m_enmFormat(a_rThat.m_enmFormat)
+ , m_strFormatted(a_rThat.m_strFormatted)
+{
+ m_TimeSpec = a_rThat.m_TimeSpec;
+ m_Exploded = a_rThat.m_Exploded;
+}
+
+
+RTCRestDate::~RTCRestDate()
+{
+ /* nothing to do */
+}
+
+
+RTCRestDate &RTCRestDate::operator=(RTCRestDate const &a_rThat)
+{
+ RTCRestObjectBase::operator=(a_rThat);
+ m_TimeSpec = a_rThat.m_TimeSpec;
+ m_Exploded = a_rThat.m_Exploded;
+ m_fTimeSpecOkay = a_rThat.m_fTimeSpecOkay;
+ m_enmFormat = a_rThat.m_enmFormat;
+ m_strFormatted = a_rThat.m_strFormatted;
+ return *this;
+}
+
+
+int RTCRestDate::assignCopy(RTCRestDate const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_TimeSpec = a_rThat.m_TimeSpec;
+ m_Exploded = a_rThat.m_Exploded;
+ m_fTimeSpecOkay = a_rThat.m_fTimeSpecOkay;
+ m_enmFormat = a_rThat.m_enmFormat;
+ return m_strFormatted.assignNoThrow(a_rThat.m_strFormatted);
+}
+
+
+RTCRestObjectBase *RTCRestDate::baseClone() const RT_NOEXCEPT
+{
+ RTCRestDate *pClone = new (std::nothrow) RTCRestDate();
+ if (pClone)
+ {
+ int rc = pClone->assignCopy(*this);
+ if (RT_SUCCESS(rc))
+ return pClone;
+ delete pClone;
+ }
+ return NULL;
+}
+
+
+int RTCRestDate::resetToDefault() RT_NOEXCEPT
+{
+ m_fNullIndicator = true;
+ RTTimeSpecSetNano(&m_TimeSpec, 0);
+ RT_ZERO(m_Exploded);
+ m_fTimeSpecOkay = false;
+ m_strFormatted.setNull();
+ /*m_enmFormat - leave as hint. */
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestDate::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (m_fNullIndicator)
+ a_rDst.nullValue();
+ else
+ a_rDst.printf("%RMjs", m_strFormatted.c_str());
+ return a_rDst;
+}
+
+
+int RTCRestDate::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ setNull();
+
+ RTJSONVALTYPE enmType = RTJsonValueGetType(a_rCursor.m_hValue);
+ if (enmType == RTJSONVALTYPE_STRING)
+ {
+ int rc = m_strFormatted.assignNoThrow(RTJsonValueGetString(a_rCursor.m_hValue));
+ AssertRCReturn(rc, rc);
+
+ m_fNullIndicator = false;
+ rc = decodeFormattedString(m_enmFormat);
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (m_enmFormat != kFormat_Invalid)
+ {
+ rc = decodeFormattedString();
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VWRN_REST_UNABLE_TO_DECODE_DATE,
+ "Unable to decode date value: %s", m_strFormatted.c_str());
+ }
+
+ if (enmType == RTJSONVALTYPE_NULL)
+ return VINF_SUCCESS;
+
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_WRONG_JSON_TYPE_FOR_DATE, "wrong JSON type for date: %s",
+ RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+}
+
+
+int RTCRestDate::toString(RTCString *a_pDst, uint32_t a_fFlags /*= 0*/) const RT_NOEXCEPT
+{
+ if (m_fNullIndicator)
+ {
+ if (a_fFlags & kToString_Append)
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("null"));
+ }
+ if (a_fFlags & kToString_Append)
+ return a_pDst->appendNoThrow(m_strFormatted);
+ return a_pDst->assignNoThrow(m_strFormatted);
+}
+
+
+int RTCRestDate::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ setNull();
+ if (a_rValue.startsWithWord("null", RTCString::CaseInsensitive))
+ return VINF_SUCCESS;
+
+ int rc = m_strFormatted.assignNoThrow(a_rValue);
+ AssertRCReturn(rc, rc);
+
+ m_fNullIndicator = false;
+ rc = decodeFormattedString(m_enmFormat);
+ if (RT_SUCCESS(rc))
+ return rc;
+ if (m_enmFormat != kFormat_Invalid)
+ {
+ rc = decodeFormattedString();
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ RT_NOREF(a_fFlags);
+ return RTErrInfoSetF(a_pErrInfo, VERR_REST_UNABLE_TO_DECODE_DATE,
+ "Unable to decode date value (%s): %s", a_pszName, m_strFormatted.c_str());
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestDate::typeClass(void) const RT_NOEXCEPT
+{
+ return kTypeClass_Date;
+}
+
+
+const char *RTCRestDate::typeName(void) const RT_NOEXCEPT
+{
+ return "RTCRestDate";
+}
+
+
+int RTCRestDate::assignValue(PCRTTIMESPEC a_pTimeSpec, kFormat a_enmFormat) RT_NOEXCEPT
+{
+ AssertPtrReturn(a_pTimeSpec, VERR_INVALID_PARAMETER);
+ AssertReturn(a_enmFormat > kFormat_Invalid && a_enmFormat < kFormat_End, VERR_INVALID_PARAMETER);
+
+ m_TimeSpec = *a_pTimeSpec;
+ return explodeAndFormat(a_enmFormat);
+}
+
+
+int RTCRestDate::assignValueRfc2822(PCRTTIMESPEC a_pTimeSpec) RT_NOEXCEPT
+{
+ AssertPtrReturn(a_pTimeSpec, VERR_INVALID_PARAMETER);
+ m_TimeSpec = *a_pTimeSpec;
+ return explodeAndFormat(kFormat_Rfc2822);
+}
+
+
+int RTCRestDate::assignValueRfc7131(PCRTTIMESPEC a_pTimeSpec) RT_NOEXCEPT
+{
+ AssertPtrReturn(a_pTimeSpec, VERR_INVALID_PARAMETER);
+ m_TimeSpec = *a_pTimeSpec;
+ return explodeAndFormat(kFormat_Rfc7131);
+}
+
+
+int RTCRestDate::assignValueRfc3339(PCRTTIMESPEC a_pTimeSpec) RT_NOEXCEPT
+{
+ AssertPtrReturn(a_pTimeSpec, VERR_INVALID_PARAMETER);
+ m_TimeSpec = *a_pTimeSpec;
+ return explodeAndFormat(kFormat_Rfc3339);
+}
+
+
+int RTCRestDate::assignNow(kFormat a_enmFormat) RT_NOEXCEPT
+{
+ RTTIMESPEC Now;
+ return assignValue(RTTimeNow(&Now), a_enmFormat);
+}
+
+
+int RTCRestDate::assignNowRfc2822() RT_NOEXCEPT
+{
+ RTTIMESPEC Now;
+ return assignValueRfc2822(RTTimeNow(&Now));
+}
+
+
+int RTCRestDate::assignNowRfc7131() RT_NOEXCEPT
+{
+ RTTIMESPEC Now;
+ return assignValueRfc7131(RTTimeNow(&Now));
+}
+
+
+int RTCRestDate::assignNowRfc3339() RT_NOEXCEPT
+{
+ RTTIMESPEC Now;
+ return assignValueRfc3339(RTTimeNow(&Now));
+}
+
+
+int RTCRestDate::setFormat(kFormat a_enmFormat) RT_NOEXCEPT
+{
+ /*
+ * If this is a null object, just set the format as a hint for upcoming deserialization.
+ */
+ if (m_fNullIndicator)
+ {
+ AssertReturn(a_enmFormat >= kFormat_Invalid && a_enmFormat < kFormat_End, VERR_INVALID_PARAMETER);
+ m_enmFormat = a_enmFormat;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * If the tiem spec is okay, just reformat the string value accordingly.
+ */
+ if (m_fTimeSpecOkay)
+ {
+ AssertReturn(a_enmFormat > kFormat_Invalid && a_enmFormat < kFormat_End, VERR_INVALID_PARAMETER);
+ if (a_enmFormat == m_enmFormat)
+ return VINF_SUCCESS;
+ return format(a_enmFormat);
+ }
+
+ /*
+ * Try decode
+ */
+ AssertReturn(a_enmFormat > kFormat_Invalid && a_enmFormat < kFormat_End, VERR_INVALID_PARAMETER);
+ return decodeFormattedString(a_enmFormat);
+}
+
+
+int RTCRestDate::explodeAndFormat(kFormat a_enmFormat) RT_NOEXCEPT
+{
+ RTTimeExplode(&m_Exploded, &m_TimeSpec);
+ return format(a_enmFormat);
+}
+
+
+/**
+ * Formats the m_Exploded value.
+ *
+ * Sets m_strFormatted, m_fTimeSpecOkay, and m_enmFormat, clears m_fNullIndicator.
+ *
+ * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY.
+ * @param a_enmFormat The format to use.
+ */
+int RTCRestDate::format(kFormat a_enmFormat) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ m_fTimeSpecOkay = true;
+ m_enmFormat = a_enmFormat;
+ int rc;
+ switch (a_enmFormat)
+ {
+ case kFormat_Rfc2822:
+ case kFormat_Rfc7131:
+ rc = m_strFormatted.reserveNoThrow(RTTIME_RFC2822_LEN);
+ AssertRCReturn(rc, rc);
+ RTTimeToRfc2822(&m_Exploded, m_strFormatted.mutableRaw(), m_strFormatted.capacity(),
+ a_enmFormat == kFormat_Rfc7131 ? RTTIME_RFC2822_F_GMT : 0);
+ m_strFormatted.jolt();
+ return VINF_SUCCESS;
+
+ case kFormat_Rfc3339:
+ case kFormat_Rfc3339_Fraction_2:
+ case kFormat_Rfc3339_Fraction_3:
+ case kFormat_Rfc3339_Fraction_6:
+ case kFormat_Rfc3339_Fraction_9:
+ rc = m_strFormatted.reserveNoThrow(RTTIME_STR_LEN);
+ AssertRCReturn(rc, rc);
+ RTTimeToStringEx(&m_Exploded, m_strFormatted.mutableRaw(), m_strFormatted.capacity(),
+ a_enmFormat == kFormat_Rfc3339 ? 0
+ : a_enmFormat == kFormat_Rfc3339_Fraction_2 ? 2
+ : a_enmFormat == kFormat_Rfc3339_Fraction_3 ? 3
+ : a_enmFormat == kFormat_Rfc3339_Fraction_6 ? 6 : 9);
+ m_strFormatted.jolt();
+ return VINF_SUCCESS;
+
+ /* no default */
+ case kFormat_Invalid:
+ case kFormat_End:
+ break;
+ }
+ AssertFailedReturn(VERR_REST_INTERNAL_ERROR_7);
+}
+
+
+/**
+ * Internal worker that attempts to decode m_strFormatted.
+ *
+ * Sets m_fTimeSpecOkay.
+ *
+ * @returns IPRT status code.
+ * @param enmFormat Specific format to try, kFormat_Invalid (default) to try guess it.
+ */
+int RTCRestDate::decodeFormattedString(kFormat enmFormat /*= kFormat_Invalid*/) RT_NOEXCEPT
+{
+ /*
+ * Take empty string to mean null.
+ */
+ const char *pszTmp = RTStrStripL(m_strFormatted.c_str());
+ if (*pszTmp == '\0')
+ {
+ setNull();
+ return VINF_SUCCESS;
+ }
+
+ switch (enmFormat)
+ {
+ case kFormat_Invalid:
+ {
+ size_t cch = strlen(pszTmp);
+ if (cch >= 6)
+ {
+ if ( !RT_C_IS_DIGIT(pszTmp[0])
+ || RT_C_IS_SPACE(pszTmp[5])
+ || RT_C_IS_SPACE(pszTmp[2])
+ || RT_C_IS_SPACE(pszTmp[1])
+ || RT_C_IS_SPACE(pszTmp[3])
+ || RT_C_IS_SPACE(pszTmp[4]))
+ return decodeFormattedString(kFormat_Rfc2822);
+ return decodeFormattedString(kFormat_Rfc3339);
+ }
+ return VERR_REST_UNABLE_TO_DECODE_DATE;
+ }
+
+ /*
+ * Examples:
+ * Fri, 31 Aug 2018 00:00:00 +0200
+ * Mon, 3 Sep 2018 00:00:00 GMT
+ * Mon, 3 Sep 2018 00:00:00 -0000
+ * 3 Sep 2018 00:00:00 -0000 (?)
+ * 3 Sep 2018 00:00:00 GMT (?)
+ */
+ case kFormat_Rfc2822:
+ case kFormat_Rfc7131:
+ if (RTTimeFromRfc2822(&m_Exploded, pszTmp))
+ {
+ RTTimeImplode(&m_TimeSpec, &m_Exploded);
+
+ pszTmp = strchr(pszTmp, '\0');
+ if (pszTmp[-1] == 'T' || pszTmp[-1] == 't')
+ m_enmFormat = kFormat_Rfc7131;
+ else
+ m_enmFormat = kFormat_Rfc2822;
+ m_fTimeSpecOkay = true;
+ return VINF_SUCCESS;
+ }
+ return VERR_REST_UNABLE_TO_DECODE_DATE;
+
+ /*
+ * Examples:
+ * 2018-08-31T00:00:00+0200
+ * 2018-09-03T00:00:00Z
+ * 2018-09-03T00:00:00+0000
+ * 2018-09-03T00:00:00.123456789Z
+ */
+ case kFormat_Rfc3339:
+ case kFormat_Rfc3339_Fraction_2:
+ case kFormat_Rfc3339_Fraction_3:
+ case kFormat_Rfc3339_Fraction_6:
+ case kFormat_Rfc3339_Fraction_9:
+ if (RTTimeFromString(&m_Exploded, pszTmp))
+ {
+ RTTimeImplode(&m_TimeSpec, &m_Exploded);
+
+ pszTmp = strchr(pszTmp, '.');
+ if (!pszTmp)
+ m_enmFormat = kFormat_Rfc3339;
+ else
+ {
+ size_t cchFraction = 0;
+ pszTmp++;
+ while (RT_C_IS_DIGIT(pszTmp[cchFraction]))
+ cchFraction++;
+ if (cchFraction == 0)
+ m_enmFormat = kFormat_Rfc3339;
+ else if (cchFraction <= 2)
+ m_enmFormat = kFormat_Rfc3339_Fraction_2;
+ else if (cchFraction <= 3)
+ m_enmFormat = kFormat_Rfc3339_Fraction_3;
+ else if (cchFraction <= 6)
+ m_enmFormat = kFormat_Rfc3339_Fraction_6;
+ else
+ m_enmFormat = kFormat_Rfc3339_Fraction_9;
+ }
+ m_fTimeSpecOkay = true;
+ return VINF_SUCCESS;
+ }
+ return VERR_REST_UNABLE_TO_DECODE_DATE;
+
+ /* no default */
+ case kFormat_End:
+ break;
+ }
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+}
+
+
+/*********************************************************************************************************************************
+* RTCRestStringEnumBase implementation *
+*********************************************************************************************************************************/
+
+/** Default constructor. */
+RTCRestStringEnumBase::RTCRestStringEnumBase() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_iEnumValue(0 /*invalid*/)
+ , m_strValue()
+{
+}
+
+
+/** Destructor. */
+RTCRestStringEnumBase::~RTCRestStringEnumBase()
+{
+ /* nothing to do */
+}
+
+
+/** Copy constructor. */
+RTCRestStringEnumBase::RTCRestStringEnumBase(RTCRestStringEnumBase const &a_rThat)
+ : RTCRestObjectBase(a_rThat)
+ , m_iEnumValue(a_rThat.m_iEnumValue)
+ , m_strValue(a_rThat.m_strValue)
+{
+}
+
+
+/** Copy assignment operator. */
+RTCRestStringEnumBase &RTCRestStringEnumBase::operator=(RTCRestStringEnumBase const &a_rThat)
+{
+ RTCRestObjectBase::operator=(a_rThat);
+ m_iEnumValue = a_rThat.m_iEnumValue;
+ m_strValue = a_rThat.m_strValue;
+ return *this;
+}
+
+
+int RTCRestStringEnumBase::assignCopy(RTCRestStringEnumBase const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_iEnumValue = a_rThat.m_iEnumValue;
+ return m_strValue.assignNoThrow(a_rThat.m_strValue);
+}
+
+
+int RTCRestStringEnumBase::resetToDefault() RT_NOEXCEPT
+{
+ m_iEnumValue = 0; /*invalid*/
+ m_strValue.setNull();
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestStringEnumBase::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ a_rDst.printf("%RMjs", getString());
+ else
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestStringEnumBase::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ m_iEnumValue = 0;
+
+ RTJSONVALTYPE enmType = RTJsonValueGetType(a_rCursor.m_hValue);
+ if (enmType == RTJSONVALTYPE_STRING)
+ {
+ const char *pszValue = RTJsonValueGetString(a_rCursor.m_hValue);
+ const size_t cchValue = strlen(pszValue);
+ int rc = setByString(pszValue, cchValue);
+ if (RT_SUCCESS(rc))
+ return rc;
+ return a_rCursor.m_pPrimary->addError(a_rCursor, rc, "no memory for %zu char long string", cchValue);
+ }
+
+ m_strValue.setNull();
+ if (enmType == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ return VINF_SUCCESS;
+ }
+
+ return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_REST_WRONG_JSON_TYPE_FOR_STRING, "wrong JSON type %s for string/enum",
+ RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+}
+
+
+int RTCRestStringEnumBase::toString(RTCString *a_pDst, uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ {
+ if (m_iEnumValue > 0)
+ {
+ size_t cEntries = 0;
+ ENUMMAPENTRY const *paEntries = getMappingTable(&cEntries);
+ AssertReturn((unsigned)(m_iEnumValue - 1) < cEntries, VERR_REST_INTERNAL_ERROR_3);
+ Assert(paEntries[m_iEnumValue - 1].iValue == m_iEnumValue);
+
+ if (a_fFlags & kToString_Append)
+ return a_pDst->appendNoThrow(paEntries[m_iEnumValue - 1].pszName, paEntries[m_iEnumValue - 1].cchName);
+ return a_pDst->assignNoThrow(paEntries[m_iEnumValue - 1].pszName, paEntries[m_iEnumValue - 1].cchName);
+ }
+ if (a_fFlags & kToString_Append)
+ return a_pDst->appendNoThrow(m_strValue);
+ return a_pDst->assignNoThrow(m_strValue);
+ }
+ if (a_fFlags & kToString_Append)
+ return a_pDst->appendNoThrow(RT_STR_TUPLE("null"));
+ return a_pDst->assignNoThrow(RT_STR_TUPLE("null"));
+}
+
+
+int RTCRestStringEnumBase::fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo /*= NULL*/,
+ uint32_t a_fFlags /*= kCollectionFormat_Unspecified*/) RT_NOEXCEPT
+{
+ int iEnumValue = stringToEnum(a_rValue);
+ if (iEnumValue > 0)
+ {
+ m_iEnumValue = iEnumValue;
+ m_strValue.setNull();
+ return VINF_SUCCESS;
+ }
+
+ /* No translation. Check for null... */
+ m_iEnumValue = 0;
+ if (a_rValue.startsWithWord("null", RTCString::CaseInsensitive))
+ {
+ m_strValue.setNull();
+ setNull();
+ return VINF_SUCCESS;
+ }
+
+ /* Try copy the string. */
+ int rc = m_strValue.assignNoThrow(a_rValue);
+ if (RT_SUCCESS(rc))
+ return VWRN_NOT_FOUND;
+
+ RT_NOREF(a_pszName, a_pErrInfo, a_fFlags);
+ return rc;
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestStringEnumBase::typeClass(void) const RT_NOEXCEPT
+{
+ return kTypeClass_StringEnum;
+}
+
+
+int RTCRestStringEnumBase::setByString(const char *a_pszValue, size_t a_cchValue /*= RTSTR_MAX*/) RT_NOEXCEPT
+{
+ if (a_cchValue == RTSTR_MAX)
+ a_cchValue = strlen(a_pszValue);
+ int iEnumValue = stringToEnum(a_pszValue, a_cchValue);
+ if (iEnumValue > 0)
+ {
+ m_iEnumValue = iEnumValue;
+ m_strValue.setNull();
+ return VINF_SUCCESS;
+ }
+
+ /* No translation. */
+ m_iEnumValue = 0;
+ int rc = m_strValue.assignNoThrow(a_pszValue, a_cchValue);
+ if (RT_SUCCESS(rc))
+ return VWRN_NOT_FOUND;
+ return rc;
+}
+
+
+int RTCRestStringEnumBase::setByString(RTCString const &a_rValue) RT_NOEXCEPT
+{
+ return setByString(a_rValue.c_str(), a_rValue.length());
+}
+
+
+const char *RTCRestStringEnumBase::getString() const RT_NOEXCEPT
+{
+ /* We ASSUME a certain mapping table layout here. */
+ if (m_iEnumValue > 0)
+ {
+ size_t cEntries = 0;
+ ENUMMAPENTRY const *paEntries = getMappingTable(&cEntries);
+ AssertReturn((unsigned)(m_iEnumValue - 1) < cEntries, "<internal-error-#1>");
+ Assert(paEntries[m_iEnumValue - 1].iValue == m_iEnumValue);
+ return paEntries[m_iEnumValue - 1].pszName;
+ }
+
+ AssertReturn(m_iEnumValue == 0, "<internal-error-#2>");
+ if (m_strValue.isEmpty())
+ return "invalid";
+
+ return m_strValue.c_str();
+}
+
+
+int RTCRestStringEnumBase::stringToEnum(const char *a_pszValue, size_t a_cchValue) RT_NOEXCEPT
+{
+ if (a_cchValue == RTSTR_MAX)
+ a_cchValue = strlen(a_pszValue);
+
+ size_t cEntries = 0;
+ ENUMMAPENTRY const *paEntries = getMappingTable(&cEntries);
+ for (size_t i = 0; i < cEntries; i++)
+ if ( paEntries[i].cchName == a_cchValue
+ && memcmp(paEntries[i].pszName, a_pszValue, a_cchValue) == 0)
+ return paEntries[i].iValue;
+ return 0;
+}
+
+
+int RTCRestStringEnumBase::stringToEnum(RTCString const &a_rStrValue) RT_NOEXCEPT
+{
+ return stringToEnum(a_rStrValue.c_str(), a_rStrValue.length());
+}
+
+
+const char *RTCRestStringEnumBase::enumToString(int a_iEnumValue, size_t *a_pcchString) RT_NOEXCEPT
+{
+ /* We ASSUME a certain mapping table layout here. */
+ if (a_iEnumValue > 0)
+ {
+ size_t cEntries = 0;
+ ENUMMAPENTRY const *paEntries = getMappingTable(&cEntries);
+ if ((unsigned)(a_iEnumValue - 1) < cEntries)
+ {
+ Assert(paEntries[a_iEnumValue - 1].iValue == a_iEnumValue);
+ if (a_pcchString)
+ *a_pcchString = paEntries[a_iEnumValue - 1].cchName;
+ return paEntries[a_iEnumValue - 1].pszName;
+ }
+ }
+ /* Zero is the special invalid value. */
+ else if (a_iEnumValue == 0)
+ {
+ if (a_pcchString)
+ *a_pcchString = 7;
+ return "invalid";
+ }
+ return NULL;
+}
+
+
+bool RTCRestStringEnumBase::setWorker(int a_iEnumValue) RT_NOEXCEPT
+{
+ /* We ASSUME a certain mapping table layout here. */
+ if (a_iEnumValue > 0)
+ {
+ size_t cEntries = 0;
+ ENUMMAPENTRY const *paEntries = getMappingTable(&cEntries);
+ AssertReturn((unsigned)(a_iEnumValue - 1) < cEntries, false);
+ Assert(paEntries[a_iEnumValue - 1].iValue == a_iEnumValue);
+ RT_NOREF(paEntries);
+ }
+ /* Zero is the special invalid value. */
+ else if (a_iEnumValue != 0)
+ AssertFailedReturn(false);
+
+ m_iEnumValue = a_iEnumValue;
+ m_strValue.setNull();
+ return true;
+}
+
+
+RTCRestObjectBase *RTCRestStringEnumBase::cloneWorker(RTCRestStringEnumBase *a_pDst) const RT_NOEXCEPT
+{
+ if (a_pDst)
+ {
+ int rc = a_pDst->assignCopy(*this);
+ if (RT_SUCCESS(rc))
+ return a_pDst;
+ delete a_pDst;
+ }
+ return NULL;
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestDataObject *
+*********************************************************************************************************************************/
+
+RTCRestDataObject::RTCRestDataObject() RT_NOEXCEPT
+ : RTCRestObjectBase()
+ , m_fIsSet(0)
+{
+}
+
+
+RTCRestDataObject::~RTCRestDataObject()
+{
+}
+
+
+RTCRestDataObject::RTCRestDataObject(RTCRestDataObject const &a_rThat) RT_NOEXCEPT
+ : RTCRestObjectBase(a_rThat)
+ , m_fIsSet(a_rThat.m_fIsSet)
+{
+}
+
+
+RTCRestDataObject &RTCRestDataObject::operator=(RTCRestDataObject const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_fIsSet = a_rThat.m_fIsSet;
+ return *this;
+}
+
+
+int RTCRestDataObject::assignCopy(RTCRestDataObject const &a_rThat) RT_NOEXCEPT
+{
+ m_fNullIndicator = a_rThat.m_fNullIndicator;
+ m_fIsSet = a_rThat.m_fIsSet;
+ return VINF_SUCCESS;
+}
+
+
+int RTCRestDataObject::resetToDefault() RT_NOEXCEPT
+{
+ m_fNullIndicator = false;
+ m_fIsSet = 0;
+ return VINF_SUCCESS;
+}
+
+
+RTCRestOutputBase &RTCRestDataObject::serializeMembersAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ RT_NOREF(a_rDst);
+ return a_rDst;
+}
+
+
+RTCRestOutputBase &RTCRestDataObject::serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT
+{
+ if (!m_fNullIndicator)
+ {
+ uint32_t const uOldState = a_rDst.beginObject();
+ serializeMembersAsJson(a_rDst);
+ a_rDst.endObject(uOldState);
+ }
+ else
+ a_rDst.nullValue();
+ return a_rDst;
+}
+
+
+int RTCRestDataObject::deserializeMemberFromJson(RTCRestJsonCursor const &a_rCursor, size_t a_cchName) RT_NOEXCEPT
+{
+ RT_NOREF(a_rCursor, a_cchName);
+ return VERR_NOT_FOUND;
+}
+
+
+int RTCRestDataObject::deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT
+{
+ if (RTJsonValueGetType(a_rCursor.m_hValue) == RTJSONVALTYPE_NULL)
+ {
+ setNull();
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Make sure the object starts out with default values.
+ */
+ if (m_fIsSet == 0)
+ m_fNullIndicator = false;
+ else
+ resetToDefault();
+
+ /*
+ * Iterate the object values.
+ */
+ RTJSONIT hIterator;
+ int rcRet = RTJsonIteratorBeginObject(a_rCursor.m_hValue, &hIterator);
+ if (RT_SUCCESS(rcRet))
+ {
+ for (;;)
+ {
+ RTCRestJsonCursor SubCursor(a_rCursor);
+ int rc = RTJsonIteratorQueryValue(hIterator, &SubCursor.m_hValue, &SubCursor.m_pszName);
+ if (RT_SUCCESS(rc))
+ {
+ size_t const cchName = strlen(SubCursor.m_pszName);
+
+ rc = deserializeMemberFromJson(SubCursor, cchName);
+ if (rc == VINF_SUCCESS)
+ { /* likely */ }
+ else if (rc == VERR_NOT_FOUND)
+ {
+ rc = SubCursor.m_pPrimary->unknownField(SubCursor);
+ if (rcRet == VINF_SUCCESS)
+ rcRet = rc;
+ }
+ else if (RT_SUCCESS(rc))
+ {
+ if (rcRet == VINF_SUCCESS)
+ rcRet = rc;
+ }
+ else if (RT_SUCCESS(rcRet))
+ rcRet = rc;
+ }
+ else
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonIteratorQueryValue failed: %Rrc", rc);
+
+ /*
+ * Advance.
+ */
+ rc = RTJsonIteratorNext(hIterator);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else if (rc == VERR_JSON_ITERATOR_END)
+ break;
+ else
+ {
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rc, "RTJsonIteratorNext failed: %Rrc", rc);
+ break;
+ }
+ }
+
+ RTJsonIteratorFree(hIterator);
+ }
+ else if ( rcRet == VERR_JSON_VALUE_INVALID_TYPE
+ && RTJsonValueGetType(a_rCursor.m_hValue) == RTJSONVALTYPE_NULL)
+ {
+ m_fNullIndicator = true;
+ rcRet = VINF_SUCCESS;
+ }
+ else
+ rcRet = a_rCursor.m_pPrimary->addError(a_rCursor, rcRet, "RTJsonIteratorBeginObject failed: %Rrc (type %s)",
+ rcRet, RTJsonValueTypeName(RTJsonValueGetType(a_rCursor.m_hValue)));
+ return rcRet;
+}
+
+
+RTCRestObjectBase::kTypeClass RTCRestDataObject::typeClass(void) const RT_NOEXCEPT
+{
+ return kTypeClass_DataObject;
+}
+
+
+
+/*********************************************************************************************************************************
+* RTCRestPolyDataObject *
+*********************************************************************************************************************************/
+
+RTCRestPolyDataObject::RTCRestPolyDataObject() RT_NOEXCEPT
+ : RTCRestDataObject()
+{
+}
+
+
+RTCRestPolyDataObject::RTCRestPolyDataObject(RTCRestPolyDataObject const &a_rThat) RT_NOEXCEPT
+ : RTCRestDataObject(a_rThat)
+{
+}
+
+
+RTCRestPolyDataObject::~RTCRestPolyDataObject()
+{
+}
+
+
+int RTCRestPolyDataObject::resetToDefault() RT_NOEXCEPT
+{
+ return RTCRestDataObject::resetToDefault();
+}
+
+
+bool RTCRestPolyDataObject::isChild() const RT_NOEXCEPT
+{
+ return false;
+}
+
+
+RTCRestPolyDataObject &RTCRestPolyDataObject::operator=(RTCRestPolyDataObject const &a_rThat) RT_NOEXCEPT
+{
+ RTCRestDataObject::operator=(a_rThat);
+ return *this;
+}
+
diff --git a/src/VBox/Runtime/common/sort/Makefile.kup b/src/VBox/Runtime/common/sort/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/sort/Makefile.kup
diff --git a/src/VBox/Runtime/common/sort/RTSortApvIsSorted.cpp b/src/VBox/Runtime/common/sort/RTSortApvIsSorted.cpp
new file mode 100644
index 00000000..e20044c2
--- /dev/null
+++ b/src/VBox/Runtime/common/sort/RTSortApvIsSorted.cpp
@@ -0,0 +1,58 @@
+/* $Id: RTSortApvIsSorted.cpp $ */
+/** @file
+ * IPRT - RTSortApvIsSorted.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/sort.h>
+#include "internal/iprt.h"
+
+
+RTDECL(bool) RTSortApvIsSorted(void const * const *papvArray, size_t cElements, PFNRTSORTCMP pfnCmp, void *pvUser)
+{
+ if (cElements >= 2)
+ {
+ for (size_t i = 0; i < cElements - 1; i++)
+ {
+ if (pfnCmp(papvArray[i], papvArray[i + 1], pvUser) > 0)
+ return false;
+ }
+ }
+ return true;
+}
+RT_EXPORT_SYMBOL(RTSortIsSorted);
+
diff --git a/src/VBox/Runtime/common/sort/RTSortIsSorted.cpp b/src/VBox/Runtime/common/sort/RTSortIsSorted.cpp
new file mode 100644
index 00000000..84ea7319
--- /dev/null
+++ b/src/VBox/Runtime/common/sort/RTSortIsSorted.cpp
@@ -0,0 +1,60 @@
+/* $Id: RTSortIsSorted.cpp $ */
+/** @file
+ * IPRT - RTSortIsSorted.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/sort.h>
+#include "internal/iprt.h"
+
+
+RTDECL(bool) RTSortIsSorted(void const *pvArray, size_t cElements, size_t cbElement, PFNRTSORTCMP pfnCmp, void *pvUser)
+{
+ if (cElements >= 2)
+ {
+ const char *pbArray = (const char *)pvArray;
+ for (size_t i = 0; i < cElements - 1; i++)
+ {
+ const char *pbElement1 = pbArray + i * cbElement;
+ if (pfnCmp(pbElement1, pbElement1 + cbElement, pvUser) > 0)
+ return false;
+ }
+ }
+ return true;
+}
+RT_EXPORT_SYMBOL(RTSortIsSorted);
+
diff --git a/src/VBox/Runtime/common/sort/nocrt-bsearch.cpp b/src/VBox/Runtime/common/sort/nocrt-bsearch.cpp
new file mode 100644
index 00000000..746764d1
--- /dev/null
+++ b/src/VBox/Runtime/common/sort/nocrt-bsearch.cpp
@@ -0,0 +1,80 @@
+/* $Id: nocrt-bsearch.cpp $ */
+/** @file
+ * IPRT - No-CRT - bsearch().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+
+
+void *RT_NOCRT(bsearch)(const void *pvKey, const void *pvBase, size_t cEntries, size_t cbEntry,
+ int (*pfnCompare)(const void *pvKey, const void *pvEntry))
+{
+ if (cEntries > 0)
+ { /* likely */ }
+ else
+ return NULL;
+
+ size_t iStart = 0;
+ size_t iEnd = cEntries;
+ for (;;)
+ {
+ size_t const i = (iEnd - iStart) / 2 + iStart;
+ const void * const pvEntry = (const char *)pvBase + cbEntry * i;
+ int const iDiff = pfnCompare(pvKey, pvEntry);
+ if (iDiff > 0) /* target is before i */
+ {
+ if (i > iStart)
+ iEnd = i;
+ else
+ return NULL;
+ }
+ else if (iDiff) /* target is after i */
+ {
+ if (i + 1 < iEnd)
+ iStart = i + 1;
+ else
+ return NULL;
+ }
+ else /* match */
+ return (void *)pvEntry;
+ }
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(bsearch);
+
diff --git a/src/VBox/Runtime/common/sort/nocrt-qsort.cpp b/src/VBox/Runtime/common/sort/nocrt-qsort.cpp
new file mode 100644
index 00000000..cfe66fa0
--- /dev/null
+++ b/src/VBox/Runtime/common/sort/nocrt-qsort.cpp
@@ -0,0 +1,70 @@
+/* $Id: nocrt-qsort.cpp $ */
+/** @file
+ * IPRT - No-CRT - qsort().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+#include <iprt/sort.h>
+
+
+#if !defined(RT_ARCH_AMD64) /* ASSUMING the regular calling convention */ \
+ && !defined(RT_ARCH_X86) /* ASSUMING __cdecl, won't work for __stdcall or __fastcall. */
+# define NEED_COMPARE_WRAPPER
+/** @callback_method_impl{FNRTSORTCMP} */
+static int DECLCALLBACK(int) CompareWrapper(void const *pvElement1, void const *pvElement2, void *pvUser))
+{
+ int (*pfnCompare)(const void *pv1, const void *pv2) = (int (*)(const void *pv1, const void *pv2))(uintptr_t)pvUser;
+ return pfnCompare(pvElement1, pvElement2);
+}
+#endif
+
+#undef qsort
+void RT_NOCRT(qsort)(void *pvBase, size_t cEntries, size_t cbEntry,
+ int (*pfnCompare)(const void *pv1, const void *pv2))
+{
+ /** @todo Implement and use RTSortQuick! */
+#ifdef NEED_COMPARE_WRAPPER
+ RTSortShell(pvBase, cEntries, cbEntry, CompareWrapper, (void *)(uintptr_t)pfnCompare);
+#else
+ RTSortShell(pvBase, cEntries, cbEntry, (PFNRTSORTCMP)pfnCompare, NULL);
+#endif
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(qsort);
+
diff --git a/src/VBox/Runtime/common/sort/nocrt-qsort_r.cpp b/src/VBox/Runtime/common/sort/nocrt-qsort_r.cpp
new file mode 100644
index 00000000..5d40e960
--- /dev/null
+++ b/src/VBox/Runtime/common/sort/nocrt-qsort_r.cpp
@@ -0,0 +1,56 @@
+/* $Id: nocrt-qsort_r.cpp $ */
+/** @file
+ * IPRT - No-CRT - qsort_r().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+#include <iprt/sort.h>
+
+
+#undef qsort_r
+void RT_NOCRT(qsort_r)(void *pvBase, size_t cEntries, size_t cbEntry,
+ int (*pfnCompare)(const void *pv1, const void *pv2, void *pvUser), void *pvUser)
+{
+ /** @todo Implement and use RTSortQuick! */
+ /* ASSUMES that we're using compatible calling conventions (x86: __cdecl, amd64: default). */
+ RTSortShell(pvBase, cEntries, cbEntry, (PFNRTSORTCMP)pfnCompare, pvUser);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(qsort_r);
+
diff --git a/src/VBox/Runtime/common/sort/shellsort.cpp b/src/VBox/Runtime/common/sort/shellsort.cpp
new file mode 100644
index 00000000..f0b067de
--- /dev/null
+++ b/src/VBox/Runtime/common/sort/shellsort.cpp
@@ -0,0 +1,111 @@
+/* $Id: shellsort.cpp $ */
+/** @file
+ * IPRT - RTSortShell and RTSortApvShell.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/sort.h>
+
+#include <iprt/alloca.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(void) RTSortShell(void *pvArray, size_t cElements, size_t cbElement, PFNRTSORTCMP pfnCmp, void *pvUser)
+{
+ Assert(cbElement <= 128);
+
+ /* Anything worth sorting? */
+ if (cElements < 2)
+ return;
+
+ uint8_t *pbArray = (uint8_t *)pvArray;
+ void *pvTmp = alloca(cbElement);
+ size_t cGap = (cElements + 1) / 2;
+ while (cGap > 0)
+ {
+ size_t i;
+ for (i = cGap; i < cElements; i++)
+ {
+ memcpy(pvTmp, &pbArray[i * cbElement], cbElement);
+ size_t j = i;
+ while ( j >= cGap
+ && pfnCmp(&pbArray[(j - cGap) * cbElement], pvTmp, pvUser) > 0)
+ {
+ memmove(&pbArray[j * cbElement], &pbArray[(j - cGap) * cbElement], cbElement);
+ j -= cGap;
+ }
+ memcpy(&pbArray[j * cbElement], pvTmp, cbElement);
+ }
+
+ /* This does not generate the most optimal gap sequence, but it has the
+ advantage of being simple and avoid floating point. */
+ cGap /= 2;
+ }
+}
+
+
+RTDECL(void) RTSortApvShell(void **papvArray, size_t cElements, PFNRTSORTCMP pfnCmp, void *pvUser)
+{
+ /* Anything worth sorting? */
+ if (cElements < 2)
+ return;
+
+ size_t cGap = (cElements + 1) / 2;
+ while (cGap > 0)
+ {
+ size_t i;
+ for (i = cGap; i < cElements; i++)
+ {
+ void *pvTmp = papvArray[i];
+ size_t j = i;
+ while ( j >= cGap
+ && pfnCmp(papvArray[j - cGap], pvTmp, pvUser) > 0)
+ {
+ papvArray[j] = papvArray[j - cGap];
+ j -= cGap;
+ }
+ papvArray[j] = pvTmp;
+ }
+
+ /* This does not generate the most optimal gap sequence, but it has the
+ advantage of being simple and avoid floating point. */
+ cGap /= 2;
+ }
+}
+
diff --git a/src/VBox/Runtime/common/string/Makefile.kup b/src/VBox/Runtime/common/string/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/string/Makefile.kup
diff --git a/src/VBox/Runtime/common/string/RTStrCat.cpp b/src/VBox/Runtime/common/string/RTStrCat.cpp
new file mode 100644
index 00000000..e43cdf31
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrCat.cpp
@@ -0,0 +1,68 @@
+/* $Id: RTStrCat.cpp $ */
+/** @file
+ * IPRT - RTStrCat.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrCat(char *pszDst, size_t cbDst, const char *pszSrc)
+{
+ char *pszDst2 = RTStrEnd(pszDst, cbDst);
+ AssertReturn(pszDst2, VERR_INVALID_PARAMETER);
+ cbDst -= pszDst2 - pszDst;
+
+ size_t cchSrc = strlen(pszSrc);
+ if (RT_LIKELY(cchSrc < cbDst))
+ {
+ memcpy(pszDst2, pszSrc, cchSrc + 1);
+ return VINF_SUCCESS;
+ }
+
+ if (cbDst != 0)
+ {
+ memcpy(pszDst2, pszSrc, cbDst - 1);
+ pszDst2[cbDst - 1] = '\0';
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTStrCat);
+
diff --git a/src/VBox/Runtime/common/string/RTStrCatEx.cpp b/src/VBox/Runtime/common/string/RTStrCatEx.cpp
new file mode 100644
index 00000000..03aefd2a
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrCatEx.cpp
@@ -0,0 +1,70 @@
+/* $Id: RTStrCatEx.cpp $ */
+/** @file
+ * IPRT - RTStrCatEx
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrCatEx(char *pszDst, size_t cbDst, const char *pszSrc, size_t cchMaxSrc)
+{
+ char *pszDst2 = RTStrEnd(pszDst, cbDst);
+ AssertReturn(pszDst2, VERR_INVALID_PARAMETER);
+ cbDst -= pszDst2 - pszDst;
+
+ const char *pszSrcEol = RTStrEnd(pszSrc, cchMaxSrc);
+ size_t cchSrc = pszSrcEol ? (size_t)(pszSrcEol - pszSrc) : cchMaxSrc;
+ if (RT_LIKELY(cchSrc < cbDst))
+ {
+ memcpy(pszDst2, pszSrc, cchSrc);
+ pszDst2[cchSrc] = '\0';
+ return VINF_SUCCESS;
+ }
+
+ if (cbDst != 0)
+ {
+ memcpy(pszDst2, pszSrc, cbDst - 1);
+ pszDst2[cbDst - 1] = '\0';
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTStrCatEx);
+
diff --git a/src/VBox/Runtime/common/string/RTStrCatP.cpp b/src/VBox/Runtime/common/string/RTStrCatP.cpp
new file mode 100644
index 00000000..081e1b72
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrCatP.cpp
@@ -0,0 +1,63 @@
+/* $Id: RTStrCatP.cpp $ */
+/** @file
+ * IPRT - RTStrCat.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrCatP(char **ppszDst, size_t *pcbDst, const char *pszSrc)
+{
+ /*
+ * Advance past the current string in the output buffer and turn this into
+ * a copy operation.
+ */
+ char *pszDstOrg = *ppszDst;
+ size_t cbDst = *pcbDst;
+ char *pszDst = RTStrEnd(pszDstOrg, cbDst);
+ AssertReturn(pszDst, VERR_INVALID_PARAMETER);
+ *ppszDst = pszDst;
+ *pcbDst = cbDst - (pszDst - pszDstOrg);
+
+ return RTStrCopyP(ppszDst, pcbDst, pszSrc);
+}
+RT_EXPORT_SYMBOL(RTStrCatP);
+
diff --git a/src/VBox/Runtime/common/string/RTStrCatPEx.cpp b/src/VBox/Runtime/common/string/RTStrCatPEx.cpp
new file mode 100644
index 00000000..656e1fe6
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrCatPEx.cpp
@@ -0,0 +1,62 @@
+/* $Id: RTStrCatPEx.cpp $ */
+/** @file
+ * IPRT - RTStrCatPEx
+ */
+
+/*
+ * Copyright (C) 2011-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrCatPEx(char **ppszDst, size_t *pcbDst, const char *pszSrc, size_t cchMaxSrc)
+{
+ /*
+ * Advance past the current string in the output buffer and turn this into
+ * a copy operation.
+ */
+ size_t cbDst = *pcbDst;
+ char *pszDst = RTStrEnd(*ppszDst, cbDst);
+ AssertReturn(pszDst, VERR_INVALID_PARAMETER);
+ *pcbDst -= pszDst - *ppszDst;
+ *ppszDst = pszDst;
+
+ return RTStrCopyPEx(ppszDst, pcbDst, pszSrc, cchMaxSrc);
+}
+RT_EXPORT_SYMBOL(RTStrCatPEx);
+
diff --git a/src/VBox/Runtime/common/string/RTStrCmp.cpp b/src/VBox/Runtime/common/string/RTStrCmp.cpp
new file mode 100644
index 00000000..8b5d4018
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrCmp.cpp
@@ -0,0 +1,70 @@
+/* $Id: RTStrCmp.cpp $ */
+/** @file
+ * IPRT - RTStrCmp.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+/**
+ * Performs a case sensitive string compare between two UTF-8 strings.
+ *
+ * Encoding errors are ignored by the current implementation. So, the only
+ * difference between this and the CRT strcmp function is the handling of
+ * NULL arguments.
+ *
+ * @returns < 0 if the first string less than the second string.
+ * @returns 0 if the first string identical to the second string.
+ * @returns > 0 if the first string greater than the second string.
+ * @param psz1 First UTF-8 string. Null is allowed.
+ * @param psz2 Second UTF-8 string. Null is allowed.
+ */
+RTDECL(int) RTStrCmp(const char *psz1, const char *psz2)
+{
+ if (psz1 == psz2)
+ return 0;
+ if (!psz1)
+ return -1;
+ if (!psz2)
+ return 1;
+
+ return strcmp(psz1, psz2);
+}
+RT_EXPORT_SYMBOL(RTStrCmp);
+
diff --git a/src/VBox/Runtime/common/string/RTStrCopy.cpp b/src/VBox/Runtime/common/string/RTStrCopy.cpp
new file mode 100644
index 00000000..0dc9e940
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrCopy.cpp
@@ -0,0 +1,64 @@
+/* $Id: RTStrCopy.cpp $ */
+/** @file
+ * IPRT - RTStrCopy.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrCopy(char *pszDst, size_t cbDst, const char *pszSrc)
+{
+ size_t cchSrc = strlen(pszSrc);
+ if (RT_LIKELY(cchSrc < cbDst))
+ {
+ memcpy(pszDst, pszSrc, cchSrc + 1);
+ return VINF_SUCCESS;
+ }
+
+ if (cbDst != 0)
+ {
+ memcpy(pszDst, pszSrc, cbDst - 1);
+ pszDst[cbDst - 1] = '\0';
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTStrCopy);
+
diff --git a/src/VBox/Runtime/common/string/RTStrCopyEx.cpp b/src/VBox/Runtime/common/string/RTStrCopyEx.cpp
new file mode 100644
index 00000000..b364784e
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrCopyEx.cpp
@@ -0,0 +1,66 @@
+/* $Id: RTStrCopyEx.cpp $ */
+/** @file
+ * IPRT - RTStrCopyEx.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrCopyEx(char *pszDst, size_t cbDst, const char *pszSrc, size_t cchMaxSrc)
+{
+ const char *pszSrcEol = RTStrEnd(pszSrc, cchMaxSrc);
+ size_t cchSrc = pszSrcEol ? (size_t)(pszSrcEol - pszSrc) : cchMaxSrc;
+ if (RT_LIKELY(cchSrc < cbDst))
+ {
+ memcpy(pszDst, pszSrc, cchSrc);
+ pszDst[cchSrc] = '\0';
+ return VINF_SUCCESS;
+ }
+
+ if (cbDst != 0)
+ {
+ memcpy(pszDst, pszSrc, cbDst - 1);
+ pszDst[cbDst - 1] = '\0';
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTStrCopyEx);
+
diff --git a/src/VBox/Runtime/common/string/RTStrCopyP.cpp b/src/VBox/Runtime/common/string/RTStrCopyP.cpp
new file mode 100644
index 00000000..bbfb1778
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrCopyP.cpp
@@ -0,0 +1,70 @@
+/* $Id: RTStrCopyP.cpp $ */
+/** @file
+ * IPRT - RTStrCopyP.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrCopyP(char **ppszDst, size_t *pcbDst, const char *pszSrc)
+{
+ size_t const cchSrc = strlen(pszSrc);
+ size_t const cbDst = *pcbDst;
+ char *pszDst = *ppszDst;
+ if (RT_LIKELY(cchSrc < cbDst))
+ {
+ memcpy(pszDst, pszSrc, cchSrc + 1);
+ *ppszDst = pszDst += cchSrc;
+ *pcbDst -= cchSrc;
+ return VINF_SUCCESS;
+ }
+
+ if (cbDst != 0)
+ {
+ memcpy(*ppszDst, pszSrc, cbDst - 1);
+ *ppszDst = pszDst += cbDst - 1;
+ *pszDst = '\0';
+ *pcbDst = 1;
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTStrCopyP);
+
diff --git a/src/VBox/Runtime/common/string/RTStrCopyPEx.cpp b/src/VBox/Runtime/common/string/RTStrCopyPEx.cpp
new file mode 100644
index 00000000..3034fc62
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrCopyPEx.cpp
@@ -0,0 +1,72 @@
+/* $Id: RTStrCopyPEx.cpp $ */
+/** @file
+ * IPRT - RTStrCopyPEx.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrCopyPEx(char **ppszDst, size_t *pcbDst, const char *pszSrc, size_t cchMaxSrc)
+{
+ const char *pszSrcEol = RTStrEnd(pszSrc, cchMaxSrc);
+ size_t cchSrc = pszSrcEol ? (size_t)(pszSrcEol - pszSrc) : cchMaxSrc;
+ size_t const cbDst = *pcbDst;
+ char *pszDst = *ppszDst;
+ if (RT_LIKELY(cchSrc < cbDst))
+ {
+ memcpy(pszDst, pszSrc, cchSrc);
+ *ppszDst = pszDst += cchSrc;
+ *pszDst = '\0';
+ *pcbDst -= cchSrc;
+ return VINF_SUCCESS;
+ }
+
+ if (cbDst != 0)
+ {
+ memcpy(*ppszDst, pszSrc, cbDst - 1);
+ *ppszDst = pszDst += cbDst - 1;
+ *pszDst = '\0';
+ *pcbDst = 1;
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTStrCopyPEx);
+
diff --git a/src/VBox/Runtime/common/string/RTStrEnd.asm b/src/VBox/Runtime/common/string/RTStrEnd.asm
new file mode 100644
index 00000000..3c7bf744
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrEnd.asm
@@ -0,0 +1,100 @@
+; $Id: RTStrEnd.asm $
+;; @file
+; IPRT - RTStrEnd - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pszString gcc: rdi msc: rcx x86:[esp+4] wcall: eax
+; @param cchMax gcc: rsi msc: rdx x86:[esp+8] wcall: edx
+;
+RT_BEGINPROC RTStrEnd
+ cld
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ or rdx, rdx
+ jz .not_found_early
+
+ mov r9, rdi ; save rdi
+ mov rdi, rcx
+ mov rcx, rdx
+ %else
+ mov rcx, rsi
+ jrcxz .not_found_early
+ %endif
+
+%else
+ %ifdef ASM_CALL32_WATCOM
+ mov ecx, edx
+ jecxz .not_found_early
+ mov edx, edi ; save rdi
+ mov edi, eax
+ %else
+ mov ecx, [esp + 8]
+ jecxz .not_found_early
+ mov edx, edi ; save edi
+ mov edi, [esp + 4]
+ %endif
+%endif
+ xor eax, eax ; we're searching for zero
+
+ ; do the search
+ repne scasb
+ jne .not_found
+
+ ; found it
+ lea xAX, [xDI - 1]
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+ ret
+
+.not_found:
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+.not_found_early:
+ xor eax, eax
+ ret
+ENDPROC RTStrEnd
+
diff --git a/src/VBox/Runtime/common/string/RTStrEnd.cpp b/src/VBox/Runtime/common/string/RTStrEnd.cpp
new file mode 100644
index 00000000..e2d499e7
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrEnd.cpp
@@ -0,0 +1,57 @@
+/* $Id: RTStrEnd.cpp $ */
+/** @file
+ * IPRT - RTStrEnd, C++ version.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+RTDECL(char *) RTStrEnd(const char *pszString, size_t cchMax)
+{
+ while (cchMax-- > 0)
+ {
+ if (*pszString)
+ { }
+ else
+ return (char *)pszString;
+ pszString++;
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTStrEnd);
diff --git a/src/VBox/Runtime/common/string/RTStrFormat.cpp b/src/VBox/Runtime/common/string/RTStrFormat.cpp
new file mode 100644
index 00000000..ffbf47ea
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrFormat.cpp
@@ -0,0 +1,59 @@
+/* $Id: RTStrFormat.cpp $ */
+/** @file
+ * IPRT - String Formatter, RTStrFormat.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_STRING
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/stdarg.h>
+
+
+RTDECL(size_t) RTStrFormat(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, PFNSTRFORMAT pfnFormat, void *pvArgFormat,
+ const char *pszFormat, ...)
+{
+ size_t cch;
+ va_list va;
+ va_start(va, pszFormat);
+ cch = RTStrFormatV(pfnOutput, pvArgOutput, pfnFormat, pvArgFormat, pszFormat, va);
+ va_end(va);
+ return cch;
+}
+RT_EXPORT_SYMBOL(RTStrFormat);
+
diff --git a/src/VBox/Runtime/common/string/RTStrICmpAscii.cpp b/src/VBox/Runtime/common/string/RTStrICmpAscii.cpp
new file mode 100644
index 00000000..2382474d
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrICmpAscii.cpp
@@ -0,0 +1,88 @@
+/* $Id: RTStrICmpAscii.cpp $ */
+/** @file
+ * IPRT - RTStrICmpAscii.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+
+
+RTDECL(int) RTStrICmpAscii(const char *psz1, const char *psz2)
+{
+ if (psz1 == psz2)
+ return 0;
+ if (!psz1)
+ return -1;
+ if (!psz2)
+ return 1;
+
+ for (;;)
+ {
+ RTUNICP uc1;
+ int rc = RTStrGetCpEx(&psz1, &uc1);
+ if (RT_SUCCESS(rc))
+ {
+ unsigned char uch2 = *psz2++; Assert(uch2 < 0x80);
+
+ /* compare */
+ int iDiff = uc1 - uch2;
+ if (iDiff)
+ {
+ if (uc1 >= 0x80)
+ return 1;
+
+ iDiff = RT_C_TO_LOWER(uc1) - RT_C_TO_LOWER(uch2); /* Return lower cased diff! */
+ if (iDiff)
+ return iDiff;
+ }
+
+ if (uch2)
+ { /* likely */ }
+ else
+ return 0;
+ }
+ /* Hit some bad encoding, continue in case sensitive mode. */
+ else
+ return RTStrCmp(psz1 - 1, psz2);
+ }
+}
+RT_EXPORT_SYMBOL(RTStrICmpAscii);
+
diff --git a/src/VBox/Runtime/common/string/RTStrIStartsWith.cpp b/src/VBox/Runtime/common/string/RTStrIStartsWith.cpp
new file mode 100644
index 00000000..d417737a
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrIStartsWith.cpp
@@ -0,0 +1,56 @@
+/* $Id: RTStrIStartsWith.cpp $ */
+/** @file
+ * IPRT - String starts with predicate, case insensitive.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_STRING
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTDECL(bool) RTStrIStartsWith(const char *pszString, const char *pszStart)
+{
+ if (pszString)
+ {
+ size_t cchStart = strlen(pszStart);
+ if (RTStrNICmp(pszString, pszStart, cchStart) == 0)
+ return true;
+ }
+ return false;
+}
+
diff --git a/src/VBox/Runtime/common/string/RTStrMemFind32.asm b/src/VBox/Runtime/common/string/RTStrMemFind32.asm
new file mode 100644
index 00000000..55cb202e
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrMemFind32.asm
@@ -0,0 +1,99 @@
+; $Id: RTStrMemFind32.asm $
+;; @file
+; IPRT - RTStrMemFind32 - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2019-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+;
+; This is just a 32-bit memchr.
+;
+; @param pvHaystack gcc: rdi msc: ecx x86:[esp+4] wcall: eax
+; @param uNeedle gcc: esi msc: edx x86:[esp+8] wcall: edx
+; @param cbHaystack gcc: rdx msc: r8 x86:[esp+0ch] wcall: ebx
+RT_BEGINPROC RTStrMemFind32
+ cld
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov r9, rdi ; save rdi
+ mov eax, edx
+ mov rdi, rcx
+ mov rcx, r8
+ %else
+ mov rcx, rdx
+ mov eax, esi
+ %endif
+%else
+ %ifdef ASM_CALL32_WATCOM
+ mov ecx, ebx
+ xchg eax, edx
+ xchg edi, edx ; load and save edi.
+ %else
+ mov ecx, [esp + 0ch]
+ mov edx, edi ; save edi
+ mov eax, [esp + 8]
+ mov edi, [esp + 4]
+ %endif
+%endif
+ cmp xCX, 4
+ jb .not_found
+
+ ; do the search
+ repne scasd
+ jne .not_found
+
+ ; found it
+ lea xAX, [xDI - 4]
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+ ret
+
+.not_found:
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+ xor eax, eax
+ ret
+ENDPROC RTStrMemFind32
+
diff --git a/src/VBox/Runtime/common/string/RTStrMemFind32.cpp b/src/VBox/Runtime/common/string/RTStrMemFind32.cpp
new file mode 100644
index 00000000..76659adc
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrMemFind32.cpp
@@ -0,0 +1,60 @@
+/* $Id: RTStrMemFind32.cpp $ */
+/** @file
+ * IPRT - RTMemFindU32.
+ */
+
+/*
+ * Copyright (C) 2019-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(uint32_t *) RTStrMemFind32(const void *pvHaystack, uint32_t uNeedle, size_t cbHaystack)
+{
+ uint32_t const *puHaystack = (uint32_t const *)pvHaystack;
+ while (cbHaystack >= sizeof(uNeedle))
+ {
+ if (*puHaystack == uNeedle)
+ return (uint32_t *)puHaystack;
+ cbHaystack -= sizeof(uNeedle);
+ puHaystack += 1;
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTStrMemFind32);
+
diff --git a/src/VBox/Runtime/common/string/RTStrNCmp.cpp b/src/VBox/Runtime/common/string/RTStrNCmp.cpp
new file mode 100644
index 00000000..68092c06
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrNCmp.cpp
@@ -0,0 +1,72 @@
+/* $Id: RTStrNCmp.cpp $ */
+/** @file
+ * IPRT - RTStrNCmp.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTDECL(int) RTStrNCmp(const char *psz1, const char *psz2, size_t cchMax)
+{
+ if (psz1 == psz2)
+ return 0;
+ if (!psz1)
+ return -1;
+ if (!psz2)
+ return 1;
+
+#ifdef RT_OS_SOLARIS
+ /* Solaris: tstUtf8 found to fail for some RTSTR_MAX on testboxsh1:
+ solaris.amd64 v5.10 (Generic_142901-12 (Assembled 30 March 2009)). */
+ while (cchMax-- > 0)
+ {
+ char ch1 = *psz1++;
+ char ch2 = *psz2++;
+ if (ch1 != ch2)
+ return ch1 > ch2 ? 1 : -1;
+ else if (ch1 == 0)
+ break;
+ }
+ return 0;
+#else
+ return strncmp(psz1, psz2, cchMax);
+#endif
+}
+RT_EXPORT_SYMBOL(RTStrNCmp);
+
diff --git a/src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp b/src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp
new file mode 100644
index 00000000..42ab6763
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrNICmpAscii.cpp
@@ -0,0 +1,90 @@
+/* $Id: RTStrNICmpAscii.cpp $ */
+/** @file
+ * IPRT - RTStrNICmpAscii.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+
+
+RTDECL(int) RTStrNICmpAscii(const char *psz1, const char *psz2, size_t cchMax)
+{
+ if (cchMax == 0)
+ return 0;
+ if (psz1 == psz2)
+ return 0;
+ if (!psz1)
+ return -1;
+ if (!psz2)
+ return 1;
+
+ for (;;)
+ {
+ RTUNICP uc1;
+ int rc = RTStrGetCpNEx(&psz1, &cchMax, &uc1);
+ if (RT_SUCCESS(rc))
+ {
+ unsigned char uch2 = *psz2++; Assert(uch2 < 0x80);
+
+ /* compare */
+ int iDiff = uc1 - uch2;
+ if (iDiff)
+ {
+ if (uc1 >= 0x80)
+ return 1;
+
+ iDiff = RT_C_TO_LOWER(uc1) - RT_C_TO_LOWER(uch2); /* Return lower cased diff! */
+ if (iDiff)
+ return iDiff;
+ }
+
+ if (uch2 && cchMax)
+ { /* likely */ }
+ else
+ return 0;
+ }
+ /* Hit some bad encoding, continue in case sensitive mode. */
+ else
+ return RTStrNCmp(psz1 - 1, psz2, cchMax + 1);
+ }
+}
+RT_EXPORT_SYMBOL(RTStrNICmpAscii);
+
diff --git a/src/VBox/Runtime/common/string/RTStrNLen.cpp b/src/VBox/Runtime/common/string/RTStrNLen.cpp
new file mode 100644
index 00000000..4299c724
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrNLen.cpp
@@ -0,0 +1,51 @@
+/* $Id: RTStrNLen.cpp $ */
+/** @file
+ * IPRT - RTStrNLen.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTDECL(size_t) RTStrNLen(const char *pszString, size_t cchMax)
+{
+ const char *pchEnd = RTStrEnd(pszString, cchMax);
+ return pchEnd ? pchEnd - pszString : cchMax;
+}
+RT_EXPORT_SYMBOL(RTStrNLen);
+
diff --git a/src/VBox/Runtime/common/string/RTStrNLenEx.cpp b/src/VBox/Runtime/common/string/RTStrNLenEx.cpp
new file mode 100644
index 00000000..67480006
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrNLenEx.cpp
@@ -0,0 +1,59 @@
+/* $Id: RTStrNLenEx.cpp $ */
+/** @file
+ * IPRT - RTStrNLenEx.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrNLenEx(const char *pszString, size_t cchMax, size_t *pcch)
+{
+ const char *pchEnd = RTStrEnd(pszString, cchMax);
+ if (!pchEnd)
+ {
+ *pcch = cchMax;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ *pcch = pchEnd - pszString;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTStrNLenEx);
+
diff --git a/src/VBox/Runtime/common/string/RTStrPrintHexBytes.cpp b/src/VBox/Runtime/common/string/RTStrPrintHexBytes.cpp
new file mode 100644
index 00000000..f82b8791
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrPrintHexBytes.cpp
@@ -0,0 +1,95 @@
+/* $Id: RTStrPrintHexBytes.cpp $ */
+/** @file
+ * IPRT - RTStrPrintHexBytes.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTStrPrintHexBytes(char *pszBuf, size_t cbBuf, void const *pv, size_t cb, uint32_t fFlags)
+{
+ AssertReturn( !(fFlags & ~(RTSTRPRINTHEXBYTES_F_UPPER | RTSTRPRINTHEXBYTES_F_SEP_SPACE | RTSTRPRINTHEXBYTES_F_SEP_COLON))
+ && (fFlags & (RTSTRPRINTHEXBYTES_F_SEP_SPACE | RTSTRPRINTHEXBYTES_F_SEP_COLON))
+ != (RTSTRPRINTHEXBYTES_F_SEP_SPACE | RTSTRPRINTHEXBYTES_F_SEP_COLON),
+ VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszBuf, VERR_INVALID_POINTER);
+ AssertReturn(cb * 2 >= cb, VERR_BUFFER_OVERFLOW);
+ char const chSep = fFlags & RTSTRPRINTHEXBYTES_F_SEP_SPACE ? ' '
+ : fFlags & RTSTRPRINTHEXBYTES_F_SEP_COLON ? ':' : '\0';
+ AssertReturn(cbBuf >= cb * (2 + (chSep != '\0')) - (chSep != '\0') + 1, VERR_BUFFER_OVERFLOW);
+ if (cb)
+ AssertPtrReturn(pv, VERR_INVALID_POINTER);
+
+ static char const s_szHexDigitsLower[17] = "0123456789abcdef";
+ static char const s_szHexDigitsUpper[17] = "0123456789ABCDEF";
+ const char *pszHexDigits = !(fFlags & RTSTRPRINTHEXBYTES_F_UPPER) ? s_szHexDigitsLower : s_szHexDigitsUpper;
+
+ uint8_t const *pb = (uint8_t const *)pv;
+
+ if (!chSep)
+ {
+ while (cb-- > 0)
+ {
+ uint8_t b = *pb++;
+ *pszBuf++ = pszHexDigits[b >> 4];
+ *pszBuf++ = pszHexDigits[b & 0xf];
+ }
+ }
+ else if (cb-- > 0)
+ {
+ uint8_t b = *pb++;
+ *pszBuf++ = pszHexDigits[b >> 4];
+ *pszBuf++ = pszHexDigits[b & 0xf];
+
+ while (cb-- > 0)
+ {
+ b = *pb++;
+ *pszBuf++ = chSep;
+ *pszBuf++ = pszHexDigits[b >> 4];
+ *pszBuf++ = pszHexDigits[b & 0xf];
+ }
+ }
+
+ *pszBuf = '\0';
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/string/RTStrSplit.cpp b/src/VBox/Runtime/common/string/RTStrSplit.cpp
new file mode 100644
index 00000000..80d8837a
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrSplit.cpp
@@ -0,0 +1,133 @@
+/* $Id: RTStrSplit.cpp $ */
+/** @file
+ * IPRT - RTStrSplit.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTDECL(int) RTStrSplit(const char *pcszStrings, size_t cbStrings,
+ const char *pcszSeparator, char ***ppapszStrings, size_t *pcStrings)
+{
+ AssertPtrReturn(pcszStrings, VERR_INVALID_POINTER);
+ AssertReturn(cbStrings, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pcszSeparator, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppapszStrings, VERR_INVALID_POINTER);
+ AssertPtrReturn(pcStrings, VERR_INVALID_POINTER);
+
+ size_t cStrings = 0;
+
+ /* Determine the number of paths in buffer first. */
+ size_t cch = cbStrings - 1;
+ char const *pcszTmp = pcszStrings;
+ const char *pcszEnd = RTStrEnd(pcszTmp, RTSTR_MAX);
+ char const *pcszNext;
+ const size_t cchSep = strlen(pcszSeparator);
+ size_t cchNext;
+ while (cch > 0)
+ {
+ pcszNext = RTStrStr(pcszTmp, pcszSeparator);
+ if (!pcszNext)
+ break;
+ cchNext = pcszNext - pcszTmp;
+ if (cchNext + cchSep > cch)
+ break;
+ pcszNext += cchSep;
+ pcszTmp += cchNext + cchSep;
+ cch -= cchNext + cchSep;
+ if (cchNext)
+ ++cStrings;
+ }
+
+ if (pcszTmp != pcszEnd) /* Do we need to take a trailing string without separator into account? */
+ cStrings++;
+
+ if (!cStrings)
+ {
+ *ppapszStrings = NULL;
+ *pcStrings = 0;
+ return VINF_SUCCESS;
+ }
+
+ char **papszStrings = (char **)RTMemAllocZ(cStrings * sizeof(char *));
+ if (!papszStrings)
+ return VERR_NO_MEMORY;
+
+ int rc = VINF_SUCCESS;
+
+ cch = cbStrings - 1;
+ pcszTmp = pcszStrings;
+
+ for (size_t i = 0; i < cStrings;)
+ {
+ pcszNext = RTStrStr(pcszTmp, pcszSeparator);
+ if (!pcszNext)
+ pcszNext = pcszEnd;
+ cchNext = pcszNext - pcszTmp;
+ if (cchNext)
+ {
+ papszStrings[i] = RTStrDupN(pcszTmp, cchNext);
+ if (!papszStrings[i])
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ i++;
+ }
+ pcszTmp += cchNext + cchSep;
+ cch -= cchNext + cchSep;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *ppapszStrings = papszStrings;
+ *pcStrings = cStrings;
+
+ return VINF_SUCCESS;
+ }
+
+ for (size_t i = 0; i < cStrings; ++i)
+ RTStrFree(papszStrings[i]);
+ RTMemFree(papszStrings);
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrSplit);
+
diff --git a/src/VBox/Runtime/common/string/RTStrStartsWith.cpp b/src/VBox/Runtime/common/string/RTStrStartsWith.cpp
new file mode 100644
index 00000000..076e3822
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrStartsWith.cpp
@@ -0,0 +1,59 @@
+/* $Id: RTStrStartsWith.cpp $ */
+/** @file
+ * IPRT - String starts with predicate.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_STRING
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTDECL(bool) RTStrStartsWith(const char *pszString, const char *pszStart)
+{
+ if (pszString)
+ {
+ if (*pszString == *pszStart)
+ {
+ size_t cchStart = strlen(pszStart);
+ if (strncmp(pszString, pszStart, cchStart) == 0)
+ return true;
+ }
+ }
+ return false;
+}
+
diff --git a/src/VBox/Runtime/common/string/RTStrStr.cpp b/src/VBox/Runtime/common/string/RTStrStr.cpp
new file mode 100644
index 00000000..4ee7aa07
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTStrStr.cpp
@@ -0,0 +1,57 @@
+/* $Id: RTStrStr.cpp $ */
+/** @file
+ * IPRT - RTStrStr.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTDECL(char *) RTStrStr(const char *pszHaystack, const char *pszNeedle)
+{
+ /* Any NULL strings means NULL return. (In the RTStrCmp tradition.) */
+ if (!pszHaystack)
+ return NULL;
+ if (!pszNeedle)
+ return NULL;
+
+ /* The rest is CRT. */
+ return (char *)strstr(pszHaystack, pszNeedle);
+}
+RT_EXPORT_SYMBOL(RTStrStr);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16Cat.cpp b/src/VBox/Runtime/common/string/RTUtf16Cat.cpp
new file mode 100644
index 00000000..6467e232
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16Cat.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTUtf16Cat.cpp $ */
+/** @file
+ * IPRT - RTUtf16Cat.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTUtf16Cat(PRTUTF16 pwszDst, size_t cwcDst, PCRTUTF16 pwszSrc)
+{
+ PRTUTF16 pwszDst2 = (PRTUTF16)RTUtf16End(pwszDst, cwcDst);
+ AssertReturn(pwszDst2, VERR_INVALID_PARAMETER);
+ return RTUtf16Copy(pwszDst2, cwcDst - (pwszDst2 - pwszDst), pwszSrc);
+}
+RT_EXPORT_SYMBOL(RTUtf16Cat);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16CatAscii.cpp b/src/VBox/Runtime/common/string/RTUtf16CatAscii.cpp
new file mode 100644
index 00000000..d706dc3b
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16CatAscii.cpp
@@ -0,0 +1,54 @@
+/* $Id: RTUtf16CatAscii.cpp $ */
+/** @file
+ * IPRT - RTUtf16CatAscii.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTUtf16CatAscii(PRTUTF16 pwszDst, size_t cwcDst, const char *pszSrc)
+{
+ PRTUTF16 pwszDst2 = (PRTUTF16)RTUtf16End(pwszDst, cwcDst);
+ AssertReturn(pwszDst2, VERR_INVALID_PARAMETER);
+ return RTUtf16CopyAscii(pwszDst2, cwcDst - (pwszDst2 - pwszDst), pszSrc);
+}
+RT_EXPORT_SYMBOL(RTUtf16CatAscii);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16Chr.cpp b/src/VBox/Runtime/common/string/RTUtf16Chr.cpp
new file mode 100644
index 00000000..507457ff
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16Chr.cpp
@@ -0,0 +1,62 @@
+/* $Id: RTUtf16Chr.cpp $ */
+/** @file
+ * IPRT - RTUtf16Chr.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+
+RTDECL(PRTUTF16) RTUtf16Chr(PCRTUTF16 pwszString, RTUTF16 wc)
+{
+ for (;;)
+ {
+ RTUTF16 wcSrc = *pwszString;
+ if (wcSrc != wc)
+ {
+ if (wcSrc != '\0')
+ pwszString++;
+ else
+ return NULL;
+ }
+ else
+ return (PRTUTF16)pwszString;
+ }
+}
+RT_EXPORT_SYMBOL(RTUtf16Chr);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16CmpAscii.cpp b/src/VBox/Runtime/common/string/RTUtf16CmpAscii.cpp
new file mode 100644
index 00000000..5666fe06
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16CmpAscii.cpp
@@ -0,0 +1,60 @@
+/* $Id: RTUtf16CmpAscii.cpp $ */
+/** @file
+ * IPRT - RTUtf16CmpAscii.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+
+
+RTDECL(int) RTUtf16CmpAscii(PCRTUTF16 pwsz1, const char *psz2)
+{
+ for (;;)
+ {
+ RTUTF16 wc1 = *pwsz1++;
+ unsigned char uch2 = *psz2++; Assert(uch2 < 0x80);
+ if (wc1 != uch2)
+ return wc1 < uch2 ? -1 : 1;
+ if (!uch2)
+ return 0;
+ }
+}
+RT_EXPORT_SYMBOL(RTUtf16ICmpAscii);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16Copy.cpp b/src/VBox/Runtime/common/string/RTUtf16Copy.cpp
new file mode 100644
index 00000000..15e40b39
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16Copy.cpp
@@ -0,0 +1,64 @@
+/* $Id: RTUtf16Copy.cpp $ */
+/** @file
+ * IPRT - RTUtf16Copy.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTUtf16Copy(PRTUTF16 pwszDst, size_t cwcDst, PCRTUTF16 pwszSrc)
+{
+ size_t wcwSrc = RTUtf16Len(pwszSrc);
+ if (RT_LIKELY(wcwSrc < cwcDst))
+ {
+ memcpy(pwszDst, pwszSrc, (wcwSrc + 1) * sizeof(RTUTF16));
+ return VINF_SUCCESS;
+ }
+
+ if (cwcDst != 0)
+ {
+ memcpy(pwszDst, pwszSrc, (cwcDst - 1) * sizeof(RTUTF16));
+ pwszDst[cwcDst - 1] = '\0';
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTUtf16Copy);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16CopyAscii.cpp b/src/VBox/Runtime/common/string/RTUtf16CopyAscii.cpp
new file mode 100644
index 00000000..016d9231
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16CopyAscii.cpp
@@ -0,0 +1,82 @@
+/* $Id: RTUtf16CopyAscii.cpp $ */
+/** @file
+ * IPRT - RTUtf16CopyAscii.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTUtf16CopyAscii(PRTUTF16 pwszDst, size_t cwcDst, const char *pszSrc)
+{
+ int rc;
+ size_t cchSrc = strlen(pszSrc);
+ size_t cchCopy;
+ if (RT_LIKELY(cchSrc < cwcDst))
+ {
+ rc = VINF_SUCCESS;
+ cchCopy = cchSrc;
+ }
+ else if (cwcDst != 0)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ cchCopy = cwcDst - 1;
+ }
+ else
+ return VERR_BUFFER_OVERFLOW;
+
+ pwszDst[cchCopy] = '\0';
+ while (cchCopy-- > 0)
+ {
+ unsigned char ch = pszSrc[cchCopy];
+ if (RT_LIKELY(ch < 0x80))
+ pwszDst[cchCopy] = ch;
+ else
+ {
+ AssertMsgFailed(("ch=%#x\n", ch));
+ pwszDst[cchCopy] = 0x7f;
+ if (rc == VINF_SUCCESS)
+ rc = VERR_OUT_OF_RANGE;
+ }
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16CopyAscii);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16CopyEx.cpp b/src/VBox/Runtime/common/string/RTUtf16CopyEx.cpp
new file mode 100644
index 00000000..7d6c42b1
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16CopyEx.cpp
@@ -0,0 +1,65 @@
+/* $Id: RTUtf16CopyEx.cpp $ */
+/** @file
+ * IPRT - RTUtf16CopyEx.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTUtf16CopyEx(PRTUTF16 pwszDst, size_t cwcDst, PCRTUTF16 pwszSrc, size_t cwcSrcMax)
+{
+ size_t cwcSrc = RTUtf16NLen(pwszSrc, cwcSrcMax);
+ if (RT_LIKELY(cwcSrc < cwcDst))
+ {
+ memcpy(pwszDst, pwszSrc, cwcSrc * sizeof(RTUTF16));
+ pwszDst[cwcSrc] = '\0';
+ return VINF_SUCCESS;
+ }
+
+ if (cwcDst != 0)
+ {
+ memcpy(pwszDst, pwszSrc, (cwcDst - 1) * sizeof(RTUTF16));
+ pwszDst[cwcDst - 1] = '\0';
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTUtf16CopyEx);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16End.cpp b/src/VBox/Runtime/common/string/RTUtf16End.cpp
new file mode 100644
index 00000000..4b28ad87
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16End.cpp
@@ -0,0 +1,56 @@
+/* $Id: RTUtf16End.cpp $ */
+/** @file
+ * IPRT - RTUtf16End.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+
+RTDECL(PCRTUTF16) RTUtf16End(PCRTUTF16 pwszString, size_t cwcMax)
+{
+ while (cwcMax-- > 0)
+ {
+ if (!*pwszString)
+ return pwszString;
+ pwszString++;
+ }
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTUtf16End);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16FindAscii.cpp b/src/VBox/Runtime/common/string/RTUtf16FindAscii.cpp
new file mode 100644
index 00000000..4d925c8d
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16FindAscii.cpp
@@ -0,0 +1,78 @@
+/* $Id: RTUtf16FindAscii.cpp $ */
+/** @file
+ * IPRT - RTUtf16FindAscii.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/string.h>
+
+
+RTDECL(ssize_t) RTUtf16FindAscii(PCRTUTF16 pwszString, const char *pszSubStr)
+{
+ if (pwszString)
+ {
+ if (pszSubStr)
+ {
+ const size_t cchSubStr = strlen(pszSubStr);
+ RTUTF16 const wcFirst = *pszSubStr;
+ Assert(wcFirst < 0x80);
+ if (wcFirst != 0)
+ {
+ PCRTUTF16 const pwszStart = pwszString;
+ for (;;)
+ {
+ RTUTF16 wc = *pwszString;
+ if (wc)
+ {
+ if ( wc == wcFirst
+ && RTUtf16NCmpAscii(pwszString, pszSubStr, cchSubStr) == 0)
+ return pwszString - pwszStart;
+ pwszString++;
+ }
+ else
+ break;
+ }
+ }
+ }
+ }
+ return -1;
+}
+RT_EXPORT_SYMBOL(RTUtf16FindAscii);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16ICmpAscii.cpp b/src/VBox/Runtime/common/string/RTUtf16ICmpAscii.cpp
new file mode 100644
index 00000000..c8bd5435
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16ICmpAscii.cpp
@@ -0,0 +1,66 @@
+/* $Id: RTUtf16ICmpAscii.cpp $ */
+/** @file
+ * IPRT - RTUtf16ICmpAscii.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+
+
+RTDECL(int) RTUtf16ICmpAscii(PCRTUTF16 pwsz1, const char *psz2)
+{
+ for (;;)
+ {
+ RTUTF16 wc1 = *pwsz1++;
+ unsigned char uch2 = *psz2++; Assert(uch2 < 0x80);
+ if (wc1 != uch2)
+ {
+ if (wc1 >= 0x80)
+ return 1;
+ if (RT_C_TO_LOWER(wc1) != RT_C_TO_LOWER(uch2))
+ return wc1 < uch2 ? -1 : 1;
+ }
+ if (!uch2)
+ return 0;
+ }
+}
+RT_EXPORT_SYMBOL(RTUtf16ICmpAscii);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16NCmp.cpp b/src/VBox/Runtime/common/string/RTUtf16NCmp.cpp
new file mode 100644
index 00000000..9aec6c82
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16NCmp.cpp
@@ -0,0 +1,66 @@
+/* $Id: RTUtf16NCmp.cpp $ */
+/** @file
+ * IPRT - RTUtf16NCmp.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+
+RTDECL(int) RTUtf16NCmp(PCRTUTF16 pwsz1, PCRTUTF16 pwsz2, size_t cwcMax)
+{
+ if (pwsz1 == pwsz2)
+ return 0;
+ if (!pwsz1)
+ return -1;
+ if (!pwsz2 || !cwcMax)
+ return 1;
+
+ while (cwcMax-- > 0)
+ {
+ RTUTF16 wcs = *pwsz1;
+ int iDiff = wcs - *pwsz2;
+ if (iDiff || !wcs)
+ return iDiff;
+ pwsz1++;
+ pwsz2++;
+ }
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTUtf16NCmp);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16NCmpAscii.cpp b/src/VBox/Runtime/common/string/RTUtf16NCmpAscii.cpp
new file mode 100644
index 00000000..de48e98a
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16NCmpAscii.cpp
@@ -0,0 +1,61 @@
+/* $Id: RTUtf16NCmpAscii.cpp $ */
+/** @file
+ * IPRT - RTUtf16CmpAscii.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+
+
+RTDECL(int) RTUtf16NCmpAscii(PCRTUTF16 pwsz1, const char *psz2, size_t cwcMax)
+{
+ while (cwcMax-- > 0)
+ {
+ RTUTF16 wc1 = *pwsz1++;
+ unsigned char uch2 = *psz2++; Assert(uch2 < 0x80);
+ if (wc1 != uch2)
+ return wc1 < uch2 ? -1 : 1;
+ if (!uch2)
+ break;
+ }
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTUtf16NCmpAscii);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16NCmpUtf8.cpp b/src/VBox/Runtime/common/string/RTUtf16NCmpUtf8.cpp
new file mode 100644
index 00000000..bd9bf4e0
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16NCmpUtf8.cpp
@@ -0,0 +1,74 @@
+/* $Id: RTUtf16NCmpUtf8.cpp $ */
+/** @file
+ * IPRT - RTUtf16NCmpUtf8.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+
+RTDECL(int) RTUtf16NCmpUtf8(PCRTUTF16 pwsz1, const char *psz2, size_t cwcMax1, size_t cchMax2)
+{
+ if (!pwsz1 || !cwcMax1)
+ return -1;
+ if (!psz2 || !cchMax2)
+ return 1;
+
+ while (cwcMax1 > 0 && cchMax2 > 0)
+ {
+ RTUNICP uc1;
+ int rc = RTUtf16GetCpNEx(&pwsz1, &cwcMax1, &uc1);
+ if (RT_SUCCESS(rc))
+ {
+ RTUNICP uc2;
+ rc = RTStrGetCpNEx(&psz2, &cchMax2, &uc2);
+ if (RT_SUCCESS(rc))
+ {
+ if (uc1 != uc2)
+ return uc1 < uc2 ? -1 : 1;
+ if (!uc1)
+ return 0;
+ }
+ else
+ return 1;
+ }
+ }
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTUtf16NCmpUtf8);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16NICmpAscii.cpp b/src/VBox/Runtime/common/string/RTUtf16NICmpAscii.cpp
new file mode 100644
index 00000000..60438053
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16NICmpAscii.cpp
@@ -0,0 +1,71 @@
+/* $Id: RTUtf16NICmpAscii.cpp $ */
+/** @file
+ * IPRT - RTUtf16ICmpAscii.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+
+
+RTDECL(int) RTUtf16NICmpAscii(PCRTUTF16 pwsz1, const char *psz2, size_t cwcMax)
+{
+ for (;;)
+ {
+ if (cwcMax == 0)
+ return 0;
+
+ RTUTF16 wc1 = *pwsz1++;
+ unsigned char uch2 = *psz2++; Assert(uch2 < 0x80);
+ if (wc1 != uch2)
+ {
+ if (wc1 >= 0x80)
+ return 1;
+ if (RT_C_TO_LOWER(wc1) != RT_C_TO_LOWER(uch2))
+ return wc1 < uch2 ? -1 : 1;
+ }
+
+ if (!uch2)
+ return 0;
+ cwcMax--;
+ }
+}
+RT_EXPORT_SYMBOL(RTUtf16NICmpAscii);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16NLen.cpp b/src/VBox/Runtime/common/string/RTUtf16NLen.cpp
new file mode 100644
index 00000000..37d2668f
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16NLen.cpp
@@ -0,0 +1,53 @@
+/* $Id: RTUtf16NLen.cpp $ */
+/** @file
+ * IPRT - RTUtf16NLen.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+
+RTDECL(size_t) RTUtf16NLen(PCRTUTF16 pwszString, size_t cwcMax)
+{
+ PCRTUTF16 pwszStart = pwszString;
+ while (cwcMax-- > 0 && *pwszString != '\0')
+ pwszString++;
+ return pwszString - pwszStart;
+}
+RT_EXPORT_SYMBOL(RTUtf16NLen);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16NLenEx.cpp b/src/VBox/Runtime/common/string/RTUtf16NLenEx.cpp
new file mode 100644
index 00000000..4bb8038f
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16NLenEx.cpp
@@ -0,0 +1,59 @@
+/* $Id: RTUtf16NLenEx.cpp $ */
+/** @file
+ * IPRT - RTUtf16NLenEx.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTUtf16NLenEx(PCRTUTF16 pwszString, size_t cwcMax, size_t *pcwc)
+{
+ PCRTUTF16 pwcEnd = RTUtf16End(pwszString, cwcMax);
+ if (!pwcEnd)
+ {
+ *pcwc = cwcMax;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ *pcwc = pwcEnd - pwszString;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTUtf16NLenEx);
+
diff --git a/src/VBox/Runtime/common/string/RTUtf16PrintHexBytes.cpp b/src/VBox/Runtime/common/string/RTUtf16PrintHexBytes.cpp
new file mode 100644
index 00000000..a18394a7
--- /dev/null
+++ b/src/VBox/Runtime/common/string/RTUtf16PrintHexBytes.cpp
@@ -0,0 +1,71 @@
+/* $Id: RTUtf16PrintHexBytes.cpp $ */
+/** @file
+ * IPRT - RTUtf16PrintHexBytes.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+
+RTDECL(int) RTUtf16PrintHexBytes(PRTUTF16 pwszBuf, size_t cwcBuf, void const *pv, size_t cb, uint32_t fFlags)
+{
+ AssertReturn(!(fFlags & ~RTSTRPRINTHEXBYTES_F_UPPER), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pwszBuf, VERR_INVALID_POINTER);
+ AssertReturn(cb * 2 >= cb, VERR_BUFFER_OVERFLOW);
+ AssertReturn(cwcBuf >= cb * 2 + 1, VERR_BUFFER_OVERFLOW);
+ if (cb)
+ AssertPtrReturn(pv, VERR_INVALID_POINTER);
+
+ static char const s_szHexDigitsLower[17] = "0123456789abcdef";
+ static char const s_szHexDigitsUpper[17] = "0123456789ABCDEF";
+ const char *pszHexDigits = !(fFlags & RTSTRPRINTHEXBYTES_F_UPPER) ? s_szHexDigitsLower : s_szHexDigitsUpper;
+
+ uint8_t const *pb = (uint8_t const *)pv;
+ while (cb-- > 0)
+ {
+ uint8_t b = *pb++;
+ *pwszBuf++ = pszHexDigits[b >> 4];
+ *pwszBuf++ = pszHexDigits[b & 0xf];
+ }
+ *pwszBuf = '\0';
+ return VINF_SUCCESS;
+}
+
diff --git a/src/VBox/Runtime/common/string/atoi.cpp b/src/VBox/Runtime/common/string/atoi.cpp
new file mode 100644
index 00000000..520e07b6
--- /dev/null
+++ b/src/VBox/Runtime/common/string/atoi.cpp
@@ -0,0 +1,64 @@
+/* $Id: atoi.cpp $ */
+/** @file
+ * IPRT - No-CRT - atoi.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/string.h>
+
+
+#undef atoi
+int RT_NOCRT(atoi)(const char *psz)
+{
+#if INT_MAX == INT32_MAX
+ int32_t iValue = 0;
+ int rc = RTStrToInt32Ex(RTStrStripL(psz), NULL, 10, &iValue);
+#else
+# error "Unsupported integer size"
+#endif
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES)
+ return iValue;
+ if (rc == VWRN_NUMBER_TOO_BIG)
+ return iValue < 0 ? INT_MIN : INT_MAX;
+ return 0;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(atoi);
+
diff --git a/src/VBox/Runtime/common/string/base64-utf16.cpp b/src/VBox/Runtime/common/string/base64-utf16.cpp
new file mode 100644
index 00000000..9406730c
--- /dev/null
+++ b/src/VBox/Runtime/common/string/base64-utf16.cpp
@@ -0,0 +1,446 @@
+/* $Id: base64-utf16.cpp $ */
+/** @file
+ * IPRT - Base64, MIME content transfer encoding.
+ *
+ * @note The base64.cpp file must be diffable with this one.
+ * Fixed typically applies to both files.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/base64.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+#ifdef RT_STRICT
+# include <iprt/asm.h>
+#endif
+
+#include "base64.h"
+
+
+/** Translates the given character. */
+DECL_FORCE_INLINE(uint8_t) rtBase64TranslateUtf16(RTUTF16 wc)
+{
+ if (wc < RT_ELEMENTS(g_au8rtBase64CharToVal))
+ return g_au8rtBase64CharToVal[wc];
+ if (RTUniCpIsSpace(wc))
+ return BASE64_SPACE;
+ return BASE64_INVALID;
+}
+
+
+/** Fetched the next character in the string and translates it. */
+DECL_FORCE_INLINE(uint8_t) rtBase64TranslateNextUtf16(PCRTUTF16 pwszString, size_t cwcStringMax)
+{
+ if (cwcStringMax > 0)
+ return rtBase64TranslateUtf16(*pwszString);
+ return BASE64_NULL;
+}
+
+
+/*
+ * Mostly the same as RTBase64DecodedSizeEx, except for the wider character
+ * type and therefore more careful handling of g_szrtBase64ValToChar and additional
+ * space characters. Fixes must be applied to both copies of the code.
+ */
+RTDECL(ssize_t) RTBase64DecodedUtf16SizeEx(PCRTUTF16 pwszString, size_t cwcStringMax, PRTUTF16 *ppwszEnd)
+{
+#ifdef RT_STRICT
+ rtBase64Sanity();
+#endif
+
+ /*
+ * Walk the string until a non-encoded or non-space character is encountered.
+ */
+ uint32_t c6Bits = 0;
+ uint8_t u8;
+
+ while ((u8 = rtBase64TranslateNextUtf16(pwszString, cwcStringMax)) != BASE64_NULL)
+ {
+ if (u8 < 64)
+ c6Bits++;
+ else if (RT_UNLIKELY(u8 != BASE64_SPACE))
+ break;
+
+ /* advance */
+ pwszString++;
+ cwcStringMax--;
+ }
+
+ /*
+ * Padding can only be found at the end and there is
+ * only 1 or 2 padding chars. Deal with it first.
+ */
+ unsigned cbPad = 0;
+ if (u8 == BASE64_PAD)
+ {
+ cbPad = 1;
+ c6Bits++;
+ pwszString++;
+ cwcStringMax--;
+ while ((u8 = rtBase64TranslateNextUtf16(pwszString, cwcStringMax)) != BASE64_NULL)
+ {
+ if (u8 != BASE64_SPACE)
+ {
+ if (u8 != BASE64_PAD)
+ break;
+ c6Bits++;
+ cbPad++;
+ }
+ pwszString++;
+ cwcStringMax--;
+ }
+ if (cbPad >= 3)
+ return -1;
+ }
+
+ /*
+ * Invalid char and no where to indicate where the
+ * Base64 text ends? Return failure.
+ */
+ if ( u8 == BASE64_INVALID
+ && !ppwszEnd)
+ return -1;
+
+ /*
+ * Recalc 6-bit to 8-bit and adjust for padding.
+ */
+ if (ppwszEnd)
+ *ppwszEnd = (PRTUTF16)pwszString;
+ return rtBase64DecodedSizeRecalc(c6Bits, cbPad);
+}
+RT_EXPORT_SYMBOL(RTBase64DecodedUtf16SizeEx);
+
+
+RTDECL(ssize_t) RTBase64DecodedUtf16Size(PCRTUTF16 pwszString, PRTUTF16 *ppwszEnd)
+{
+ return RTBase64DecodedUtf16SizeEx(pwszString, RTSTR_MAX, ppwszEnd);
+}
+RT_EXPORT_SYMBOL(RTBase64DecodedUtf16Size);
+
+
+RTDECL(int) RTBase64DecodeUtf16Ex(PCRTUTF16 pwszString, size_t cwcStringMax, void *pvData, size_t cbData,
+ size_t *pcbActual, PRTUTF16 *ppwszEnd)
+{
+#ifdef RT_STRICT
+ rtBase64Sanity();
+#endif
+
+ /*
+ * Process input in groups of 4 input / 3 output chars.
+ */
+ uint8_t u8Trio[3] = { 0, 0, 0 }; /* shuts up gcc */
+ uint8_t *pbData = (uint8_t *)pvData;
+ uint8_t u8;
+ unsigned c6Bits = 0;
+
+ for (;;)
+ {
+ /* The first 6-bit group. */
+ while ((u8 = rtBase64TranslateNextUtf16(pwszString, cwcStringMax)) == BASE64_SPACE)
+ pwszString++, cwcStringMax--;
+ if (u8 >= 64)
+ {
+ c6Bits = 0;
+ break;
+ }
+ u8Trio[0] = u8 << 2;
+ pwszString++;
+ cwcStringMax--;
+
+ /* The second 6-bit group. */
+ while ((u8 = rtBase64TranslateNextUtf16(pwszString, cwcStringMax)) == BASE64_SPACE)
+ pwszString++, cwcStringMax--;
+ if (u8 >= 64)
+ {
+ c6Bits = 1;
+ break;
+ }
+ u8Trio[0] |= u8 >> 4;
+ u8Trio[1] = u8 << 4;
+ pwszString++;
+ cwcStringMax--;
+
+ /* The third 6-bit group. */
+ u8 = BASE64_INVALID;
+ while ((u8 = rtBase64TranslateNextUtf16(pwszString, cwcStringMax)) == BASE64_SPACE)
+ pwszString++, cwcStringMax--;
+ if (u8 >= 64)
+ {
+ c6Bits = 2;
+ break;
+ }
+ u8Trio[1] |= u8 >> 2;
+ u8Trio[2] = u8 << 6;
+ pwszString++;
+ cwcStringMax--;
+
+ /* The fourth 6-bit group. */
+ u8 = BASE64_INVALID;
+ while ((u8 = rtBase64TranslateNextUtf16(pwszString, cwcStringMax)) == BASE64_SPACE)
+ pwszString++, cwcStringMax--;
+ if (u8 >= 64)
+ {
+ c6Bits = 3;
+ break;
+ }
+ u8Trio[2] |= u8;
+ pwszString++;
+ cwcStringMax--;
+
+ /* flush the trio */
+ if (cbData < 3)
+ return VERR_BUFFER_OVERFLOW;
+ cbData -= 3;
+ pbData[0] = u8Trio[0];
+ pbData[1] = u8Trio[1];
+ pbData[2] = u8Trio[2];
+ pbData += 3;
+ }
+
+ /*
+ * Padding can only be found at the end and there is
+ * only 1 or 2 padding chars. Deal with it first.
+ */
+ unsigned cbPad = 0;
+ if (u8 == BASE64_PAD)
+ {
+ cbPad = 1;
+ pwszString++;
+ cwcStringMax--;
+ while ((u8 = rtBase64TranslateNextUtf16(pwszString, cwcStringMax)) != BASE64_NULL)
+ {
+ if (u8 != BASE64_SPACE)
+ {
+ if (u8 != BASE64_PAD)
+ break;
+ cbPad++;
+ }
+ pwszString++;
+ cwcStringMax--;
+ }
+ if (cbPad >= 3)
+ return VERR_INVALID_BASE64_ENCODING;
+ }
+
+ /*
+ * Invalid char and no where to indicate where the
+ * Base64 text ends? Return failure.
+ */
+ if ( u8 == BASE64_INVALID
+ && !ppwszEnd)
+ return VERR_INVALID_BASE64_ENCODING;
+
+ /*
+ * Check padding vs. pending sextets, if anything left to do finish it off.
+ */
+ if (c6Bits || cbPad)
+ {
+ if (c6Bits + cbPad != 4)
+ return VERR_INVALID_BASE64_ENCODING;
+
+ switch (c6Bits)
+ {
+ case 1:
+ u8Trio[1] = u8Trio[2] = 0;
+ break;
+ case 2:
+ u8Trio[2] = 0;
+ break;
+ case 3:
+ default:
+ break;
+ }
+ switch (3 - cbPad)
+ {
+ case 1:
+ if (cbData < 1)
+ return VERR_BUFFER_OVERFLOW;
+ cbData--;
+ pbData[0] = u8Trio[0];
+ pbData++;
+ break;
+
+ case 2:
+ if (cbData < 2)
+ return VERR_BUFFER_OVERFLOW;
+ cbData -= 2;
+ pbData[0] = u8Trio[0];
+ pbData[1] = u8Trio[1];
+ pbData += 2;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Set optional return values and return successfully.
+ */
+ if (ppwszEnd)
+ *ppwszEnd = (PRTUTF16)pwszString;
+ if (pcbActual)
+ *pcbActual = pbData - (uint8_t *)pvData;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTBase64DecodeUtf16Ex);
+
+
+RTDECL(int) RTBase64DecodeUtf16(PCRTUTF16 pwszString, void *pvData, size_t cbData, size_t *pcbActual, PRTUTF16 *ppwszEnd)
+{
+ return RTBase64DecodeUtf16Ex(pwszString, RTSTR_MAX, pvData, cbData, pcbActual, ppwszEnd);
+}
+RT_EXPORT_SYMBOL(RTBase64DecodeUtf16);
+
+
+RTDECL(size_t) RTBase64EncodedUtf16Length(size_t cbData)
+{
+ return RTBase64EncodedLengthEx(cbData, 0);
+}
+RT_EXPORT_SYMBOL(RTBase64EncodedUtf16Length);
+
+
+RTDECL(size_t) RTBase64EncodedUtf16LengthEx(size_t cbData, uint32_t fFlags)
+{
+ return RTBase64EncodedLengthEx(cbData, fFlags);
+}
+RT_EXPORT_SYMBOL(RTBase64EncodedUtf16LengthEx);
+
+
+RTDECL(int) RTBase64EncodeUtf16(const void *pvData, size_t cbData, PRTUTF16 pwszBuf, size_t cwcBuf, size_t *pcwcActual)
+{
+ return RTBase64EncodeUtf16Ex(pvData, cbData, 0, pwszBuf, cwcBuf, pcwcActual);
+}
+RT_EXPORT_SYMBOL(RTBase64EncodeUtf16);
+
+
+/*
+ * Please note that RTBase64EncodeEx contains an almost exact copy of
+ * this code, just using different output character type and variable prefixes.
+ * So, all fixes must be applied to both versions of the code.
+ */
+RTDECL(int) RTBase64EncodeUtf16Ex(const void *pvData, size_t cbData, uint32_t fFlags,
+ PRTUTF16 pwszBuf, size_t cwcBuf, size_t *pcwcActual)
+{
+ /* Expand the EOL style flags: */
+ size_t const cchEol = g_acchrtBase64EolStyles[fFlags & RTBASE64_FLAGS_EOL_STYLE_MASK];
+ char const chEol0 = g_aachrtBase64EolStyles[fFlags & RTBASE64_FLAGS_EOL_STYLE_MASK][0];
+ char const chEol1 = g_aachrtBase64EolStyles[fFlags & RTBASE64_FLAGS_EOL_STYLE_MASK][1];
+ Assert(cchEol == (chEol0 != '\0' ? 1U : 0U) + (chEol1 != '\0' ? 1U : 0U));
+
+ /*
+ * Process whole "trios" of input data.
+ */
+ uint8_t u8A;
+ uint8_t u8B;
+ uint8_t u8C;
+ size_t cwcLineFeed = cchEol ? cwcBuf - RTBASE64_LINE_LEN : ~(size_t)0;
+ const uint8_t *pbSrc = (const uint8_t *)pvData;
+ PRTUTF16 pwcDst = pwszBuf;
+ while (cbData >= 3)
+ {
+ if (cwcBuf < 4 + 1)
+ return VERR_BUFFER_OVERFLOW;
+
+ /* encode */
+ u8A = pbSrc[0];
+ pwcDst[0] = g_szrtBase64ValToChar[u8A >> 2];
+ u8B = pbSrc[1];
+ pwcDst[1] = g_szrtBase64ValToChar[((u8A << 4) & 0x3f) | (u8B >> 4)];
+ u8C = pbSrc[2];
+ pwcDst[2] = g_szrtBase64ValToChar[((u8B << 2) & 0x3f) | (u8C >> 6)];
+ pwcDst[3] = g_szrtBase64ValToChar[u8C & 0x3f];
+
+ /* advance */
+ cwcBuf -= 4;
+ pwcDst += 4;
+ cbData -= 3;
+ pbSrc += 3;
+
+ /* deal out end-of-line */
+ if (cwcBuf == cwcLineFeed && cbData && cchEol)
+ {
+ if (cwcBuf < cchEol + 1)
+ return VERR_BUFFER_OVERFLOW;
+ cwcBuf -= cchEol;
+ *pwcDst++ = chEol0;
+ if (chEol1)
+ *pwcDst++ = chEol1;
+ cwcLineFeed = cwcBuf - RTBASE64_LINE_LEN;
+ }
+ }
+
+ /*
+ * Deal with the odd bytes and string termination.
+ */
+ if (cbData)
+ {
+ if (cwcBuf < 4 + 1)
+ return VERR_BUFFER_OVERFLOW;
+ switch (cbData)
+ {
+ case 1:
+ u8A = pbSrc[0];
+ pwcDst[0] = g_szrtBase64ValToChar[u8A >> 2];
+ pwcDst[1] = g_szrtBase64ValToChar[(u8A << 4) & 0x3f];
+ pwcDst[2] = '=';
+ pwcDst[3] = '=';
+ break;
+ case 2:
+ u8A = pbSrc[0];
+ pwcDst[0] = g_szrtBase64ValToChar[u8A >> 2];
+ u8B = pbSrc[1];
+ pwcDst[1] = g_szrtBase64ValToChar[((u8A << 4) & 0x3f) | (u8B >> 4)];
+ pwcDst[2] = g_szrtBase64ValToChar[(u8B << 2) & 0x3f];
+ pwcDst[3] = '=';
+ break;
+ }
+ pwcDst += 4;
+ }
+
+ *pwcDst = '\0';
+
+ if (pcwcActual)
+ *pcwcActual = pwcDst - pwszBuf;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTBase64EncodeUtf16Ex);
+
diff --git a/src/VBox/Runtime/common/string/base64.cpp b/src/VBox/Runtime/common/string/base64.cpp
new file mode 100644
index 00000000..cea46cd0
--- /dev/null
+++ b/src/VBox/Runtime/common/string/base64.cpp
@@ -0,0 +1,539 @@
+/* $Id: base64.cpp $ */
+/** @file
+ * IPRT - Base64, MIME content transfer encoding.
+ *
+ * @note The base64-utf16.cpp file must be diffable with this one.
+ * Fixed typically applies to both files.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/base64.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/ctype.h>
+#include <iprt/string.h>
+#ifdef RT_STRICT
+# include <iprt/asm.h>
+#endif
+
+#include "base64.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Base64 character to value. (RFC 2045)
+ * ASSUMES ASCII / UTF-8. */
+DECL_HIDDEN_CONST(const uint8_t) g_au8rtBase64CharToVal[256] =
+{
+ 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xff, 0xff, /* 0x00..0x0f */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x10..0x1f */
+ 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 62, 0xff, 0xff, 0xff, 63, /* 0x20..0x2f */
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0xff, 0xff, 0xff, 0xe0, 0xff, 0xff, /* 0x30..0x3f */
+ 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 0x40..0x4f */
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x50..0x5f */
+ 0xff, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 0x60..0x6f */
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x70..0x7f */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x80..0x8f */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0x90..0x9f */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0xa0..0xaf */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0xb0..0xbf */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0xc0..0xcf */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0xd0..0xdf */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 0xe0..0xef */
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff /* 0xf0..0xff */
+};
+
+/** Value to Base64 character. (RFC 2045) */
+DECL_HIDDEN_CONST(const char) g_szrtBase64ValToChar[64+1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/** The end-of-line lengths (indexed by style flag value). */
+DECL_HIDDEN_CONST(const size_t) g_acchrtBase64EolStyles[RTBASE64_FLAGS_EOL_STYLE_MASK + 1] =
+{
+ /*[RTBASE64_FLAGS_EOL_NATIVE ]:*/ RTBASE64_EOL_SIZE,
+ /*[RTBASE64_FLAGS_NO_LINE_BREAKS]:*/ 0,
+ /*[RTBASE64_FLAGS_EOL_LF ]:*/ 1,
+ /*[RTBASE64_FLAGS_EOL_CRLF ]:*/ 2
+};
+
+/** The end-of-line characters (zero, one or two). */
+DECL_HIDDEN_CONST(const char) g_aachrtBase64EolStyles[RTBASE64_FLAGS_EOL_STYLE_MASK + 1][2] =
+{
+ /*[RTBASE64_FLAGS_EOL_NATIVE ]:*/ { RTBASE64_EOL_SIZE == 1 ? '\n' : '\r', RTBASE64_EOL_SIZE == 1 ? '\0' : '\n', },
+ /*[RTBASE64_FLAGS_NO_LINE_BREAKS]:*/ { '\0', '\0' },
+ /*[RTBASE64_FLAGS_EOL_LF ]:*/ { '\n', '\0' },
+ /*[RTBASE64_FLAGS_EOL_CRLF ]:*/ { '\r', '\n' },
+};
+
+
+
+#ifdef RT_STRICT
+/**
+ * Perform table sanity checks on the first call.
+ */
+DECLHIDDEN(void) rtBase64Sanity(void)
+{
+ static bool s_fSane = false;
+ if (RT_UNLIKELY(!s_fSane))
+ {
+ for (unsigned i = 0; i < 64; i++)
+ {
+ unsigned ch = g_szrtBase64ValToChar[i];
+ Assert(ch);
+ Assert(g_au8rtBase64CharToVal[ch] == i);
+ }
+
+ for (unsigned i = 0; i < 256; i++)
+ {
+ uint8_t u8 = g_au8rtBase64CharToVal[i];
+ Assert( ( u8 == BASE64_INVALID
+ && !RT_C_IS_ALNUM(i)
+ && !RT_C_IS_SPACE(i))
+ || ( u8 == BASE64_PAD
+ && i == '=')
+ || ( u8 == BASE64_SPACE
+ && RT_C_IS_SPACE(i))
+ || ( u8 < 64
+ && (unsigned)g_szrtBase64ValToChar[u8] == i)
+ || ( u8 == BASE64_NULL
+ && i == 0) );
+ }
+ ASMAtomicWriteBool(&s_fSane, true);
+ }
+}
+#endif /* RT_STRICT */
+
+
+
+/** Fetched the next character in the string and translates it. */
+DECL_FORCE_INLINE(uint8_t) rtBase64TranslateNext(const char *pszString, size_t cchStringMax)
+{
+ AssertCompile(sizeof(unsigned char) == sizeof(uint8_t));
+ if (cchStringMax > 0)
+ return g_au8rtBase64CharToVal[(unsigned char)*pszString];
+ return BASE64_NULL;
+}
+
+
+/*
+ * Mostly the same as RTBase64DecodedUtf16SizeEx, except for the simpler
+ * character type. Fixes must be applied to both copies of the code.
+ */
+RTDECL(ssize_t) RTBase64DecodedSizeEx(const char *pszString, size_t cchStringMax, char **ppszEnd)
+{
+#ifdef RT_STRICT
+ rtBase64Sanity();
+#endif
+
+ /*
+ * Walk the string until a non-encoded or non-space character is encountered.
+ */
+ uint32_t c6Bits = 0;
+ uint8_t u8;
+
+ while ((u8 = rtBase64TranslateNext(pszString, cchStringMax)) != BASE64_NULL)
+ {
+ if (u8 < 64)
+ c6Bits++;
+ else if (RT_UNLIKELY(u8 != BASE64_SPACE))
+ break;
+
+ /* advance */
+ pszString++;
+ cchStringMax--;
+ }
+
+ /*
+ * Padding can only be found at the end and there is
+ * only 1 or 2 padding chars. Deal with it first.
+ */
+ unsigned cbPad = 0;
+ if (u8 == BASE64_PAD)
+ {
+ cbPad = 1;
+ c6Bits++;
+ pszString++;
+ cchStringMax--;
+ while ((u8 = rtBase64TranslateNext(pszString, cchStringMax)) != BASE64_NULL)
+ {
+ if (u8 != BASE64_SPACE)
+ {
+ if (u8 != BASE64_PAD)
+ break;
+ c6Bits++;
+ cbPad++;
+ }
+ pszString++;
+ cchStringMax--;
+ }
+ if (cbPad >= 3)
+ return -1;
+ }
+
+ /*
+ * Invalid char and no where to indicate where the
+ * Base64 text ends? Return failure.
+ */
+ if ( u8 == BASE64_INVALID
+ && !ppszEnd)
+ return -1;
+
+ /*
+ * Recalc 6-bit to 8-bit and adjust for padding.
+ */
+ if (ppszEnd)
+ *ppszEnd = (char *)pszString;
+ return rtBase64DecodedSizeRecalc(c6Bits, cbPad);
+}
+RT_EXPORT_SYMBOL(RTBase64DecodedSizeEx);
+
+
+RTDECL(ssize_t) RTBase64DecodedSize(const char *pszString, char **ppszEnd)
+{
+ return RTBase64DecodedSizeEx(pszString, RTSTR_MAX, ppszEnd);
+}
+RT_EXPORT_SYMBOL(RTBase64DecodedSize);
+
+
+RTDECL(int) RTBase64DecodeEx(const char *pszString, size_t cchStringMax, void *pvData, size_t cbData,
+ size_t *pcbActual, char **ppszEnd)
+{
+#ifdef RT_STRICT
+ rtBase64Sanity();
+#endif
+
+ /*
+ * Process input in groups of 4 input / 3 output chars.
+ */
+ uint8_t u8Trio[3] = { 0, 0, 0 }; /* shuts up gcc */
+ uint8_t *pbData = (uint8_t *)pvData;
+ uint8_t u8;
+ unsigned c6Bits = 0;
+
+ for (;;)
+ {
+ /* The first 6-bit group. */
+ while ((u8 = rtBase64TranslateNext(pszString, cchStringMax)) == BASE64_SPACE)
+ pszString++, cchStringMax--;
+ if (u8 >= 64)
+ {
+ c6Bits = 0;
+ break;
+ }
+ u8Trio[0] = u8 << 2;
+ pszString++;
+ cchStringMax--;
+
+ /* The second 6-bit group. */
+ while ((u8 = rtBase64TranslateNext(pszString, cchStringMax)) == BASE64_SPACE)
+ pszString++, cchStringMax--;
+ if (u8 >= 64)
+ {
+ c6Bits = 1;
+ break;
+ }
+ u8Trio[0] |= u8 >> 4;
+ u8Trio[1] = u8 << 4;
+ pszString++;
+ cchStringMax--;
+
+ /* The third 6-bit group. */
+ u8 = BASE64_INVALID;
+ while ((u8 = rtBase64TranslateNext(pszString, cchStringMax)) == BASE64_SPACE)
+ pszString++, cchStringMax--;
+ if (u8 >= 64)
+ {
+ c6Bits = 2;
+ break;
+ }
+ u8Trio[1] |= u8 >> 2;
+ u8Trio[2] = u8 << 6;
+ pszString++;
+ cchStringMax--;
+
+ /* The fourth 6-bit group. */
+ u8 = BASE64_INVALID;
+ while ((u8 = rtBase64TranslateNext(pszString, cchStringMax)) == BASE64_SPACE)
+ pszString++, cchStringMax--;
+ if (u8 >= 64)
+ {
+ c6Bits = 3;
+ break;
+ }
+ u8Trio[2] |= u8;
+ pszString++;
+ cchStringMax--;
+
+ /* flush the trio */
+ if (cbData < 3)
+ return VERR_BUFFER_OVERFLOW;
+ cbData -= 3;
+ pbData[0] = u8Trio[0];
+ pbData[1] = u8Trio[1];
+ pbData[2] = u8Trio[2];
+ pbData += 3;
+ }
+
+ /*
+ * Padding can only be found at the end and there is
+ * only 1 or 2 padding chars. Deal with it first.
+ */
+ unsigned cbPad = 0;
+ if (u8 == BASE64_PAD)
+ {
+ cbPad = 1;
+ pszString++;
+ cchStringMax--;
+ while ((u8 = rtBase64TranslateNext(pszString, cchStringMax)) != BASE64_NULL)
+ {
+ if (u8 != BASE64_SPACE)
+ {
+ if (u8 != BASE64_PAD)
+ break;
+ cbPad++;
+ }
+ pszString++;
+ cchStringMax--;
+ }
+ if (cbPad >= 3)
+ return VERR_INVALID_BASE64_ENCODING;
+ }
+
+ /*
+ * Invalid char and no where to indicate where the
+ * Base64 text ends? Return failure.
+ */
+ if ( u8 == BASE64_INVALID
+ && !ppszEnd)
+ return VERR_INVALID_BASE64_ENCODING;
+
+ /*
+ * Check padding vs. pending sextets, if anything left to do finish it off.
+ */
+ if (c6Bits || cbPad)
+ {
+ if (c6Bits + cbPad != 4)
+ return VERR_INVALID_BASE64_ENCODING;
+
+ switch (c6Bits)
+ {
+ case 1:
+ u8Trio[1] = u8Trio[2] = 0;
+ break;
+ case 2:
+ u8Trio[2] = 0;
+ break;
+ case 3:
+ default:
+ break;
+ }
+ switch (3 - cbPad)
+ {
+ case 1:
+ if (cbData < 1)
+ return VERR_BUFFER_OVERFLOW;
+ cbData--;
+ pbData[0] = u8Trio[0];
+ pbData++;
+ break;
+
+ case 2:
+ if (cbData < 2)
+ return VERR_BUFFER_OVERFLOW;
+ cbData -= 2;
+ pbData[0] = u8Trio[0];
+ pbData[1] = u8Trio[1];
+ pbData += 2;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Set optional return values and return successfully.
+ */
+ if (ppszEnd)
+ *ppszEnd = (char *)pszString;
+ if (pcbActual)
+ *pcbActual = pbData - (uint8_t *)pvData;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTBase64DecodeEx);
+
+
+RTDECL(int) RTBase64Decode(const char *pszString, void *pvData, size_t cbData, size_t *pcbActual, char **ppszEnd)
+{
+ return RTBase64DecodeEx(pszString, RTSTR_MAX, pvData, cbData, pcbActual, ppszEnd);
+}
+RT_EXPORT_SYMBOL(RTBase64Decode);
+
+
+RTDECL(size_t) RTBase64EncodedLength(size_t cbData)
+{
+ return RTBase64EncodedLengthEx(cbData, 0);
+}
+RT_EXPORT_SYMBOL(RTBase64EncodedLength);
+
+
+RTDECL(size_t) RTBase64EncodedLengthEx(size_t cbData, uint32_t fFlags)
+{
+ size_t const cchEol = g_acchrtBase64EolStyles[fFlags & RTBASE64_FLAGS_EOL_STYLE_MASK];
+
+ if (cbData * 8 / 8 != cbData)
+ {
+ AssertReturn(sizeof(size_t) == sizeof(uint64_t), ~(size_t)0);
+ uint64_t cch = cbData * (uint64_t)8;
+ while (cch % 24)
+ cch += 8;
+ cch /= 6;
+ cch += ((cch - 1) / RTBASE64_LINE_LEN) * cchEol;
+ return cch;
+ }
+
+ size_t cch = cbData * 8;
+ while (cch % 24)
+ cch += 8;
+ cch /= 6;
+ cch += ((cch - 1) / RTBASE64_LINE_LEN) * cchEol;
+ return cch;
+}
+RT_EXPORT_SYMBOL(RTBase64EncodedLengthEx);
+
+
+RTDECL(int) RTBase64Encode(const void *pvData, size_t cbData, char *pszBuf, size_t cbBuf, size_t *pcchActual)
+{
+ return RTBase64EncodeEx(pvData, cbData, 0, pszBuf, cbBuf, pcchActual);
+}
+RT_EXPORT_SYMBOL(RTBase64Encode);
+
+
+/*
+ * Please note that RTBase64EncodeUtf16Ex contains an almost exact copy of
+ * this code, just using different output character type and variable prefixes.
+ * So, all fixes must be applied to both versions of the code.
+ */
+RTDECL(int) RTBase64EncodeEx(const void *pvData, size_t cbData, uint32_t fFlags,
+ char *pszBuf, size_t cbBuf, size_t *pcchActual)
+{
+ /* Expand the EOL style flags: */
+ size_t const cchEol = g_acchrtBase64EolStyles[fFlags & RTBASE64_FLAGS_EOL_STYLE_MASK];
+ char const chEol0 = g_aachrtBase64EolStyles[fFlags & RTBASE64_FLAGS_EOL_STYLE_MASK][0];
+ char const chEol1 = g_aachrtBase64EolStyles[fFlags & RTBASE64_FLAGS_EOL_STYLE_MASK][1];
+ Assert(cchEol == (chEol0 != '\0' ? 1U : 0U) + (chEol1 != '\0' ? 1U : 0U));
+
+ /*
+ * Process whole "trios" of input data.
+ */
+ uint8_t u8A;
+ uint8_t u8B;
+ uint8_t u8C;
+ size_t cbLineFeed = cchEol ? cbBuf - RTBASE64_LINE_LEN : ~(size_t)0;
+ const uint8_t *pbSrc = (const uint8_t *)pvData;
+ char *pchDst = pszBuf;
+ while (cbData >= 3)
+ {
+ if (cbBuf < 4 + 1)
+ return VERR_BUFFER_OVERFLOW;
+
+ /* encode */
+ u8A = pbSrc[0];
+ pchDst[0] = g_szrtBase64ValToChar[u8A >> 2];
+ u8B = pbSrc[1];
+ pchDst[1] = g_szrtBase64ValToChar[((u8A << 4) & 0x3f) | (u8B >> 4)];
+ u8C = pbSrc[2];
+ pchDst[2] = g_szrtBase64ValToChar[((u8B << 2) & 0x3f) | (u8C >> 6)];
+ pchDst[3] = g_szrtBase64ValToChar[u8C & 0x3f];
+
+ /* advance */
+ cbBuf -= 4;
+ pchDst += 4;
+ cbData -= 3;
+ pbSrc += 3;
+
+ /* deal out end-of-line */
+ if (cbBuf == cbLineFeed && cbData && cchEol)
+ {
+ if (cbBuf < cchEol + 1)
+ return VERR_BUFFER_OVERFLOW;
+ cbBuf -= cchEol;
+ *pchDst++ = chEol0;
+ if (chEol1)
+ *pchDst++ = chEol1;
+ cbLineFeed = cbBuf - RTBASE64_LINE_LEN;
+ }
+ }
+
+ /*
+ * Deal with the odd bytes and string termination.
+ */
+ if (cbData)
+ {
+ if (cbBuf < 4 + 1)
+ return VERR_BUFFER_OVERFLOW;
+ switch (cbData)
+ {
+ case 1:
+ u8A = pbSrc[0];
+ pchDst[0] = g_szrtBase64ValToChar[u8A >> 2];
+ pchDst[1] = g_szrtBase64ValToChar[(u8A << 4) & 0x3f];
+ pchDst[2] = '=';
+ pchDst[3] = '=';
+ break;
+ case 2:
+ u8A = pbSrc[0];
+ pchDst[0] = g_szrtBase64ValToChar[u8A >> 2];
+ u8B = pbSrc[1];
+ pchDst[1] = g_szrtBase64ValToChar[((u8A << 4) & 0x3f) | (u8B >> 4)];
+ pchDst[2] = g_szrtBase64ValToChar[(u8B << 2) & 0x3f];
+ pchDst[3] = '=';
+ break;
+ }
+ pchDst += 4;
+ }
+
+ *pchDst = '\0';
+
+ if (pcchActual)
+ *pcchActual = pchDst - pszBuf;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTBase64EncodeEx);
+
diff --git a/src/VBox/Runtime/common/string/base64.h b/src/VBox/Runtime/common/string/base64.h
new file mode 100644
index 00000000..7406a803
--- /dev/null
+++ b/src/VBox/Runtime/common/string/base64.h
@@ -0,0 +1,101 @@
+/* $Id: base64.h $ */
+/** @file
+ * IPRT - Base64, MIME content transfer encoding, internal header.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_string_base64_h
+#define IPRT_INCLUDED_SRC_common_string_base64_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The line length used for encoding. */
+#define RTBASE64_LINE_LEN 64
+
+/** @name Special g_au8rtBase64CharToVal values
+ * @{ */
+#define BASE64_SPACE 0xc0
+#define BASE64_PAD 0xe0
+#define BASE64_NULL 0xfe
+#define BASE64_INVALID 0xff
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+extern DECL_HIDDEN_DATA(const uint8_t) g_au8rtBase64CharToVal[256];
+extern DECL_HIDDEN_DATA(const char) g_szrtBase64ValToChar[64+1];
+extern DECL_HIDDEN_DATA(const size_t) g_acchrtBase64EolStyles[RTBASE64_FLAGS_EOL_STYLE_MASK + 1];
+extern DECL_HIDDEN_DATA(const char) g_aachrtBase64EolStyles[RTBASE64_FLAGS_EOL_STYLE_MASK + 1][2];
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef RT_STRICT
+DECLHIDDEN(void) rtBase64Sanity(void);
+#endif
+
+
+/**
+ * Recalcs 6-bit to 8-bit and adjust for padding.
+ */
+DECLINLINE(ssize_t) rtBase64DecodedSizeRecalc(uint32_t c6Bits, unsigned cbPad)
+{
+ size_t cb;
+ if (c6Bits * 3 / 3 == c6Bits)
+ {
+ if ((c6Bits * 3 % 4) != 0)
+ return -1;
+ cb = c6Bits * 3 / 4;
+ }
+ else
+ {
+ if ((c6Bits * (uint64_t)3 % 4) != 0)
+ return -1;
+ cb = c6Bits * (uint64_t)3 / 4;
+ }
+
+ if (cb < cbPad)
+ return -1;
+ cb -= cbPad;
+ return cb;
+}
+
+#endif /* !IPRT_INCLUDED_SRC_common_string_base64_h */
diff --git a/src/VBox/Runtime/common/string/bzero.asm b/src/VBox/Runtime/common/string/bzero.asm
new file mode 100644
index 00000000..15ed85ab
--- /dev/null
+++ b/src/VBox/Runtime/common/string/bzero.asm
@@ -0,0 +1,137 @@
+; $Id: bzero.asm $
+;; @file
+; IPRT - No-CRT bzero - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pvDst gcc: rdi msc: rcx x86:[esp+4] wcall:eax
+; @param cb gcc: rsi msc: rdx x86:[esp+8] wcall:edx
+RT_NOCRT_BEGINPROC bzero
+%ifdef RT_OS_DARWIN
+GLOBALNAME __bzero
+%endif
+ cld
+%ifdef RT_ARCH_AMD64
+ xor eax, eax
+ %ifdef ASM_CALL64_MSC
+ mov r9, rdi ; save rdi in r9
+ mov rdi, rcx
+
+ ; todo: alignment?
+ mov rcx, rdx
+ shr rcx, 3
+ rep stosq
+
+ and rdx, 7
+ mov rcx, rdx
+ rep stosb
+
+ mov rdi, r9 ; restore rdi
+
+ %else ; GCC
+ ; todo: alignment?
+ mov rcx, rsi
+ shr rcx, 3
+ rep stosq
+
+ and rsi, 7
+ mov rcx, rsi
+ rep stosb
+
+ %endif ; GCC
+
+%elif ARCH_BITS == 32
+ push ebp
+ mov ebp, esp
+ push edi
+
+ %ifdef ASM_CALL32_WATCOM
+ mov ecx, edx
+ mov edi, eax
+ %else
+ mov ecx, [ebp + 0ch]
+ mov edi, [ebp + 08h]
+ %endif
+ xor eax, eax
+
+ mov edx, ecx
+ shr ecx, 2
+ rep stosd
+
+ and edx, 3
+ mov ecx, edx
+ rep stosb
+
+ pop edi
+ leave
+
+%elif ARCH_BITS == 16
+ push bp
+ mov bp, sp
+ push di
+
+ mov cx, [bp + 0ch]
+ mov di, [bp + 08h]
+ xor ax, ax
+
+ ; align di.
+ test di, 1
+ jz .aligned
+ jcxz .done
+ stosb
+ dec cx
+ jz .done
+
+.aligned:
+ mov dx, cx
+ shr cx, 1
+ rep stosw
+
+ test dl, 1
+ jz .done
+ stosb
+
+.done:
+ pop di
+ pop bp
+%else
+ %error ARCH_BITS
+%endif ; X86
+ ret
+ENDPROC RT_NOCRT(bzero)
+
diff --git a/src/VBox/Runtime/common/string/memchr.asm b/src/VBox/Runtime/common/string/memchr.asm
new file mode 100644
index 00000000..7bace6b6
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memchr.asm
@@ -0,0 +1,103 @@
+; $Id: memchr.asm $
+;; @file
+; IPRT - No-CRT memchr - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pv gcc: rdi msc: ecx x86:[esp+4] wcall: eax
+; @param ch gcc: esi msc: edx x86:[esp+8] wcall: edx
+; @param cb gcc: rdx msc: r8 x86:[esp+0ch] wcall: ebx
+RT_NOCRT_BEGINPROC memchr
+ cld
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ or r8, r8
+ jz .not_found_early
+
+ mov r9, rdi ; save rdi
+ mov eax, edx
+ mov rdi, rcx
+ mov rcx, r8
+ %else
+ mov rcx, rdx
+ jrcxz .not_found_early
+
+ mov eax, esi
+ %endif
+
+%else
+ %ifdef ASM_CALL32_WATCOM
+ mov ecx, ebx
+ jecxz .not_found_early
+ xchg eax, edx
+ xchg edi, edx ; load and save edi.
+ %else
+ mov ecx, [esp + 0ch]
+ jecxz .not_found_early
+ mov edx, edi ; save edi
+ mov eax, [esp + 8]
+ mov edi, [esp + 4]
+ %endif
+%endif
+
+ ; do the search
+ repne scasb
+ jne .not_found
+
+ ; found it
+ lea xAX, [xDI - 1]
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+ ret
+
+.not_found:
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+.not_found_early:
+ xor eax, eax
+ ret
+ENDPROC RT_NOCRT(memchr)
+
diff --git a/src/VBox/Runtime/common/string/memchr.cpp b/src/VBox/Runtime/common/string/memchr.cpp
new file mode 100644
index 00000000..6a5b4ebd
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memchr.cpp
@@ -0,0 +1,75 @@
+/* $Id: memchr.cpp $ */
+/** @file
+ * IPRT - CRT Strings, memcpy().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Search a memory block for a character.
+ *
+ * @returns Pointer to the first instance of ch in pv.
+ * @returns NULL if ch wasn't found.
+ * @param pv Pointer to the block to search.
+ * @param ch The character to search for.
+ * @param cb The size of the block.
+ */
+#ifdef IPRT_NO_CRT
+# undef memchr
+void *RT_NOCRT(memchr)(const void *pv, int ch, size_t cb)
+#elif RT_MSC_PREREQ(RT_MSC_VER_VS2005)
+_CRTIMP __checkReturn _CONST_RETURN void * __cdecl
+memchr(__in_bcount_opt(_MaxCount) const void *pv, __in int ch, __in size_t cb)
+#else
+void *memchr(const void *pv, int ch, size_t cb)
+#endif
+{
+ uint8_t const *pu8 = (uint8_t const *)pv;
+ size_t cb2 = cb;
+ while (cb2-- > 0)
+ {
+ if (*pu8 == ch)
+ return (void *)pu8;
+ pu8++;
+ }
+ return NULL;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(memchr);
+
diff --git a/src/VBox/Runtime/common/string/memcmp.asm b/src/VBox/Runtime/common/string/memcmp.asm
new file mode 100644
index 00000000..3c8b1974
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memcmp.asm
@@ -0,0 +1,155 @@
+; $Id: memcmp.asm $
+;; @file
+; IPRT - No-CRT memcmp - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pv1 gcc: rdi msc: rcx x86:[esp+4] wcall: eax
+; @param pv2 gcc: rsi msc: rdx x86:[esp+8] wcall: edx
+; @param cb gcc: rdx msc: r8 x86:[esp+0ch] wcall: ebx
+RT_NOCRT_BEGINPROC memcmp
+ cld
+
+ ; Do the bulk of the work.
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov r10, rdi ; save
+ mov r11, rsi ; save
+ mov rdi, rcx
+ mov rsi, rdx
+ mov rcx, r8
+ mov rdx, r8
+ %else
+ mov rcx, rdx
+ %endif
+ shr rcx, 3
+ xor eax, eax
+ repe cmpsq
+ jne .not_equal_qword
+%else
+ push edi
+ push esi
+
+ %ifdef ASM_CALL32_WATCOM
+ mov edi, eax
+ mov esi, edx
+ mov ecx, ebx
+ mov edx, ebx
+ %else
+ mov ecx, [esp + 0ch + 8]
+ mov edi, [esp + 04h + 8]
+ mov esi, [esp + 08h + 8]
+ mov edx, ecx
+ %endif
+ jecxz .done
+ shr ecx, 2
+ xor eax, eax
+ repe cmpsd
+ jne .not_equal_dword
+%endif
+
+ ; The remaining bytes.
+%ifdef RT_ARCH_AMD64
+ test dl, 4
+ jz .dont_cmp_dword
+ cmpsd
+ jne .not_equal_dword
+%endif
+.dont_cmp_dword:
+ test dl, 2
+ jz .dont_cmp_word
+ cmpsw
+ jne .not_equal_word
+.dont_cmp_word:
+ test dl, 1
+ jz .dont_cmp_byte
+ cmpsb
+ jne .not_equal_byte
+.dont_cmp_byte:
+
+.done:
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov rdi, r10
+ mov rsi, r11
+ %endif
+%else
+ pop esi
+ pop edi
+%endif
+ ret
+
+;
+; Mismatches.
+;
+%ifdef RT_ARCH_AMD64
+.not_equal_qword:
+ mov ecx, 8
+ sub rsi, 8
+ sub rdi, 8
+ repe cmpsb
+.not_equal_byte:
+ mov al, [xDI-1]
+ movzx ecx, byte [xSI-1]
+ sub eax, ecx
+ jmp .done
+%endif
+
+.not_equal_dword:
+ mov ecx, 4
+ sub xSI, 4
+ sub xDI, 4
+ repe cmpsb
+%ifdef RT_ARCH_AMD64
+ jmp .not_equal_byte
+%else
+.not_equal_byte:
+ mov al, [xDI-1]
+ movzx ecx, byte [xSI-1]
+ sub eax, ecx
+ jmp .done
+%endif
+
+.not_equal_word:
+ mov ecx, 2
+ sub xSI, 2
+ sub xDI, 2
+ repe cmpsb
+ jmp .not_equal_byte
+ENDPROC RT_NOCRT(memcmp)
+
diff --git a/src/VBox/Runtime/common/string/memcmp.cpp b/src/VBox/Runtime/common/string/memcmp.cpp
new file mode 100644
index 00000000..f54e2623
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memcmp.cpp
@@ -0,0 +1,97 @@
+/* $Id: memcmp.cpp $ */
+/** @file
+ * IPRT - CRT Strings, memcmp().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Copies a memory.
+ *
+ * @returns 0 if pvDst and pvSrc are equal
+ * @returns <0 if pvDst is 'smaller' than pvSrc.
+ * @returns >0 if pvDst is 'larger' than pvSrc.
+ *
+ * @param pvDst Pointer to the target block.
+ * @param pvSrc Pointer to the source block.
+ * @param cb The size of the block.
+ */
+#ifdef IPRT_NO_CRT
+# undef memcmp
+int RT_NOCRT(memcmp)(const void *pvDst, const void *pvSrc, size_t cb)
+#elif RT_MSC_PREREQ(RT_MSC_VER_VS2005)
+__checkReturn int __cdecl
+memcmp(__in_bcount_opt(_Size) const void *pvDst, __in_bcount_opt(_Size) const void *pvSrc, __in size_t cb)
+#else
+int memcmp(const void *pvDst, const void *pvSrc, size_t cb)
+#endif
+{
+ union
+ {
+ uint8_t const *pu8;
+ uint32_t const *pu32;
+ void const *pv;
+ } uDst, uSrc;
+ uDst.pv = pvDst;
+ uSrc.pv = pvSrc;
+
+ /* 32-bit word compare. */
+ size_t c = cb >> 2;
+ while (c-- > 0)
+ {
+ /* ASSUMES int is at least 32-bit! */
+ int32_t iDiff = *uDst.pu32++ - *uSrc.pu32++;
+ if (iDiff)
+ return iDiff;
+ }
+
+ /* Remaining byte moves. */
+ c = cb & 3;
+ while (c-- > 0)
+ {
+ int8_t iDiff = *uDst.pu8++ - *uSrc.pu8++;
+ if (iDiff)
+ return iDiff;
+ }
+
+ return 0;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(memcmp);
+
diff --git a/src/VBox/Runtime/common/string/memcpy.asm b/src/VBox/Runtime/common/string/memcpy.asm
new file mode 100644
index 00000000..737d168d
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memcpy.asm
@@ -0,0 +1,122 @@
+; $Id: memcpy.asm $
+;; @file
+; IPRT - No-CRT memcpy - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pvDst gcc: rdi msc: rcx x86:[esp+4] wcall: eax
+; @param pvSrc gcc: rsi msc: rdx x86:[esp+8] wcall: edx
+; @param cb gcc: rdx msc: r8 x86:[esp+0ch] wcall: ebx
+%ifdef IN_RING0_DRV_ON_DARWIN
+global NAME(memcpy):private_extern
+NAME(memcpy):
+%else
+RT_NOCRT_BEGINPROC memcpy
+%endif
+ cld
+
+ ; Do the bulk of the work.
+%ifdef RT_ARCH_AMD64
+ %ifdef DEBUG
+ push rbp
+ mov rbp, rsp
+ %endif
+ %ifdef ASM_CALL64_MSC
+ mov r10, rdi ; save
+ mov r11, rsi ; save
+ mov rdi, rcx
+ mov rsi, rdx
+ mov rcx, r8
+ mov rdx, r8
+ %else
+ mov rcx, rdx
+ %endif
+ mov rax, rdi ; save the return value
+ shr rcx, 3
+ rep movsq
+%else
+ push edi
+ push esi
+
+ %ifdef ASM_CALL32_WATCOM
+ mov edi, eax
+ mov esi, edx
+ mov ecx, ebx
+ mov edx, ebx
+ %else
+ mov ecx, [esp + 0ch + 8]
+ mov edi, [esp + 04h + 8]
+ mov esi, [esp + 08h + 8]
+ mov edx, ecx
+ mov eax, edi ; save the return value
+ %endif
+ shr ecx, 2
+ rep movsd
+%endif
+
+ ; The remaining bytes.
+%ifdef RT_ARCH_AMD64
+ test dl, 4
+ jz .dont_move_dword
+ movsd
+%endif
+.dont_move_dword:
+ test dl, 2
+ jz .dont_move_word
+ movsw
+.dont_move_word:
+ test dl, 1
+ jz .dont_move_byte
+ movsb
+.dont_move_byte:
+
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov rdi, r10
+ mov rsi, r11
+ %endif
+ %ifdef DEBUG
+ leave
+ %endif
+%else
+ pop esi
+ pop edi
+%endif
+ ret
+ENDPROC RT_NOCRT(memcpy)
+
diff --git a/src/VBox/Runtime/common/string/memcpy.cpp b/src/VBox/Runtime/common/string/memcpy.cpp
new file mode 100644
index 00000000..e587db5f
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memcpy.cpp
@@ -0,0 +1,92 @@
+/* $Id: memcpy.cpp $ */
+/** @file
+ * IPRT - CRT Strings, memcpy().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Copies a memory.
+ *
+ * @returns pvDst.
+ * @param pvDst Pointer to the target block.
+ * @param pvSrc Pointer to the source block.
+ * @param cb The size of the block.
+ */
+#ifdef IPRT_NO_CRT
+# undef memcpy
+void *RT_NOCRT(memcpy)(void *pvDst, const void *pvSrc, size_t cb)
+#elif RT_MSC_PREREQ(RT_MSC_VER_VS2005)
+_CRT_INSECURE_DEPRECATE_MEMORY(memcpy_s) void * __cdecl
+memcpy(__out_bcount_full_opt(_Size) void *pvDst, __in_bcount_opt(_Size) const void *pvSrc, __in size_t cb)
+#else
+void *memcpy(void *pvDst, const void *pvSrc, size_t cb)
+#endif
+{
+ union
+ {
+ uint8_t *pu8;
+ uint32_t *pu32;
+ void *pv;
+ } uTrg;
+ uTrg.pv = pvDst;
+
+ union
+ {
+ uint8_t const *pu8;
+ uint32_t const *pu32;
+ void const *pv;
+ } uSrc;
+ uSrc.pv = pvSrc;
+
+ /* 32-bit word moves. */
+ size_t c = cb >> 2;
+ while (c-- > 0)
+ *uTrg.pu32++ = *uSrc.pu32++;
+
+ /* Remaining byte moves. */
+ c = cb & 3;
+ while (c-- > 0)
+ *uTrg.pu8++ = *uSrc.pu8++;
+
+ return pvDst;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(memcpy);
+
diff --git a/src/VBox/Runtime/common/string/memmove.asm b/src/VBox/Runtime/common/string/memmove.asm
new file mode 100644
index 00000000..88342c16
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memmove.asm
@@ -0,0 +1,158 @@
+; $Id: memmove.asm $
+;; @file
+; IPRT - No-CRT memmove - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%define RT_ASM_WITH_SEH64
+%include "iprt/asmdefs.mac"
+
+
+BEGINCODE
+
+;;
+; @param pvDst gcc: rdi msc: rcx x86:[esp+4] wcall: eax
+; @param pvSrc gcc: rsi msc: rdx x86:[esp+8] wcall: edx
+; @param cb gcc: rdx msc: r8 x86:[esp+0ch] wcall: ebx
+RT_NOCRT_BEGINPROC memmove
+ ; Prolog.
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov r10, rdi ; save
+ mov r11, rsi ; save
+ mov rdi, rcx
+ mov rsi, rdx
+ mov rcx, r8
+ mov rdx, r8
+ %else
+ mov rcx, rdx
+ %endif
+ mov rax, rdi ; save the return value
+%else
+ push edi
+ push esi
+ %ifdef ASM_CALL32_WATCOM
+ mov edi, eax
+ mov esi, edx
+ mov ecx, ebx
+ mov edx, ebx
+ %else
+ mov edi, [esp + 04h + 8]
+ mov esi, [esp + 08h + 8]
+ mov ecx, [esp + 0ch + 8]
+ mov edx, ecx
+ mov eax, edi ; save the return value
+ %endif
+%endif
+ SEH64_END_PROLOGUE
+
+ ;
+ ; Decide which direction to perform the copy in.
+ ;
+%if 1 ; keep it simple for now.
+ cmp xDI, xSI
+ jnb .backward
+
+ ;
+ ; Slow/simple forward copy.
+ ;
+ cld
+ rep movsb
+ jmp .epilog
+
+%else ; disabled - it seems to work, but play safe for now.
+ ;sub xAX, xSI
+ ;jnb .backward
+ cmp xDI, xSI
+ jnb .backward
+
+ ;
+ ; Fast forward copy.
+ ;
+.fast_forward:
+ cld
+%ifdef RT_ARCH_AMD64
+ shr rcx, 3
+ rep movsq
+%else
+ shr ecx, 2
+ rep movsd
+%endif
+
+ ; The remaining bytes.
+%ifdef RT_ARCH_AMD64
+ test dl, 4
+ jz .forward_dont_move_dword
+ movsd
+%endif
+.forward_dont_move_dword:
+ test dl, 2
+ jz .forward_dont_move_word
+ movsw
+.forward_dont_move_word:
+ test dl, 1
+ jz .forward_dont_move_byte
+ movsb
+.forward_dont_move_byte:
+
+%endif ; disabled
+
+ ;
+ ; The epilog.
+ ;
+.epilog:
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov rdi, r10
+ mov rsi, r11
+ %endif
+%else
+ pop esi
+ pop edi
+%endif
+ ret
+
+ ;
+ ; Slow/simple backward copy.
+ ;
+ALIGNCODE(16)
+.backward:
+ ;; @todo check if they overlap.
+ lea xDI, [xDI + xCX - 1]
+ lea xSI, [xSI + xCX - 1]
+ std
+ rep movsb
+ cld
+ jmp .epilog
+ENDPROC RT_NOCRT(memmove)
+
diff --git a/src/VBox/Runtime/common/string/mempcpy.asm b/src/VBox/Runtime/common/string/mempcpy.asm
new file mode 100644
index 00000000..a6428391
--- /dev/null
+++ b/src/VBox/Runtime/common/string/mempcpy.asm
@@ -0,0 +1,110 @@
+; $Id: mempcpy.asm $
+;; @file
+; IPRT - No-CRT mempcpy - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pvDst gcc: rdi msc: rcx x86:[esp+4] wcall: eax
+; @param pvSrc gcc: rsi msc: rdx x86:[esp+8] wcall: edx
+; @param cb gcc: rdx msc: r8 x86:[esp+0ch] wcall: ebx
+RT_NOCRT_BEGINPROC mempcpy
+ cld ; paranoia
+
+ ; Do the bulk of the work.
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov r10, rdi ; save
+ mov r11, rsi ; save
+ mov rdi, rcx
+ mov rsi, rdx
+ mov rcx, r8
+ mov rdx, r8
+ %else
+ mov rcx, rdx
+ %endif
+ shr rcx, 3
+ rep movsq
+%else
+ %ifdef ASM_CALL32_WATCOM
+ xchg eax, edi ; saving edi in eax and loading it
+ push esi
+ mov esi, edx
+ mov ecx, ebx
+ mov edx, ebx
+ %else
+ mov eax, edi ; saving edi in eax
+ push esi
+ mov ecx, [esp + 0ch + 4]
+ mov edi, [esp + 04h + 4]
+ mov esi, [esp + 08h + 4]
+ mov edx, ecx
+ %endif
+ shr ecx, 2
+ rep movsd
+%endif
+
+ ; The remaining bytes.
+%ifdef RT_ARCH_AMD64
+ test dl, 4
+ jz .dont_move_dword
+ movsd
+%endif
+.dont_move_dword:
+ test dl, 2
+ jz .dont_move_word
+ movsw
+.dont_move_word:
+ test dl, 1
+ jz .dont_move_byte
+ movsb
+.dont_move_byte:
+
+ ; restore & return
+%ifdef RT_ARCH_AMD64
+ mov rax, rdi
+ %ifdef ASM_CALL64_MSC
+ mov rsi, r11
+ mov rdi, r10
+ %endif
+%else
+ pop esi
+ xchg eax, edi
+%endif
+ ret
+ENDPROC RT_NOCRT(mempcpy)
+
diff --git a/src/VBox/Runtime/common/string/mempcpy.cpp b/src/VBox/Runtime/common/string/mempcpy.cpp
new file mode 100644
index 00000000..6f4eb1e1
--- /dev/null
+++ b/src/VBox/Runtime/common/string/mempcpy.cpp
@@ -0,0 +1,55 @@
+/* $Id: mempcpy.cpp $ */
+/** @file
+ * IPRT - CRT Strings, mempcpy().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+#ifdef IPRT_NO_CRT
+# undef mempcpy
+void * RT_NOCRT(mempcpy)(void *pvDst, const void *pvSrc, size_t cb)
+#else
+RTDECL(void *) mempcpy(void *pvDst, const void *pvSrc, size_t cb)
+#endif
+{
+ return (char *)memcpy(pvDst, pvSrc, cb) + cb;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(mempcpy);
+
diff --git a/src/VBox/Runtime/common/string/memrchr.asm b/src/VBox/Runtime/common/string/memrchr.asm
new file mode 100644
index 00000000..c07dfa04
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memrchr.asm
@@ -0,0 +1,107 @@
+; $Id: memrchr.asm $
+;; @file
+; IPRT - No-CRT memrchr - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pv gcc: rdi msc: ecx x86:[esp+4] wcall: eax
+; @param ch gcc: esi msc: edx x86:[esp+8] wcall: edx
+; @param cb gcc: rdx msc: r8 x86:[esp+0ch] wcall: ebx
+RT_NOCRT_BEGINPROC memrchr
+ std
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ or r8, r8
+ jz .not_found_early
+
+ mov r9, rdi ; save rdi
+ mov eax, edx
+ lea rdi, [rcx + r8 - 1]
+ mov rcx, r8
+ %else
+ mov rcx, rdx
+ jrcxz .not_found_early
+
+ mov eax, esi
+ lea rdi, [rdi + rcx - 1]
+ %endif
+
+%else
+ %ifdef ASM_CALL32_WATCOM
+ mov ecx, ebx
+ jecxz .not_found_early
+ xchg eax, edx
+ xchg edi, edx ; load + save edi
+ %else
+ mov ecx, [esp + 0ch]
+ jecxz .not_found_early
+ mov edx, edi ; save edi
+ mov eax, [esp + 8]
+ mov edi, [esp + 4]
+ %endif
+ lea edi, [edi + ecx - 1]
+%endif
+
+ ; do the search
+ repne scasb
+ jne .not_found
+
+ ; found it
+ lea xAX, [xDI + 1]
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+ cld
+ ret
+
+.not_found:
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+.not_found_early:
+ xor eax, eax
+ cld
+ ret
+ENDPROC RT_NOCRT(memrchr)
+
diff --git a/src/VBox/Runtime/common/string/memrchr.cpp b/src/VBox/Runtime/common/string/memrchr.cpp
new file mode 100644
index 00000000..b8f20d28
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memrchr.cpp
@@ -0,0 +1,72 @@
+/* $Id: memrchr.cpp $ */
+/** @file
+ * IPRT - CRT Strings, memrchr().
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Search for a given byte starting at the end of the block.
+ *
+ * @returns Pointer on a match or NULL otherwise.
+ * @param pv Pointer to the block.
+ * @param ch The char to search for.
+ * @param cb The size of the block.
+ */
+#undef memrchr
+void *RT_NOCRT(memrchr)(const void *pv, int ch, size_t cb)
+{
+ if (cb)
+ {
+ const char *pbCur = (const char *)pv + cb - 1;
+
+ while (cb)
+ {
+ if (*pbCur == ch)
+ return (void *)pbCur;
+ pbCur--;
+ cb--;
+ }
+ }
+
+ return NULL;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(memrchr);
+
diff --git a/src/VBox/Runtime/common/string/memset.asm b/src/VBox/Runtime/common/string/memset.asm
new file mode 100644
index 00000000..a2523c84
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memset.asm
@@ -0,0 +1,141 @@
+; $Id: memset.asm $
+;; @file
+; IPRT - No-CRT memset - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pvDst gcc: rdi msc: ecx x86:[esp+4] wcall: eax
+; @param ch gcc: esi msc: edx x86:[esp+8] wcall: edx
+; @param cb gcc: rdx msc: r8 x86:[esp+0ch] wcall: ebx
+RT_NOCRT_BEGINPROC memset
+ cld
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov r9, rdi ; save rdi in r9
+ mov rdi, rcx
+ mov r10, rcx ; the return value.
+ movzx eax, dl
+ cmp r8, 32
+ jb .dobytes
+
+ ; eax = (al << 24) | (al << 16) | (al << 8) | al;
+ ; rdx = (eax << 32) | eax
+ movzx edx, dl
+ mov rax, qword 0101010101010101h
+ imul rax, rdx
+
+ ; todo: alignment.
+ mov rcx, r8
+ shr rcx, 3
+ rep stosq
+
+ and r8, 7
+.dobytes:
+ mov rcx, r8
+ rep stosb
+
+ mov rdi, r9 ; restore rdi
+ mov rax, r10
+
+ %else ; GCC
+ mov r10, rdi ; the return value.
+ movzx eax, sil
+ cmp rdx, 32
+ jb .dobytes
+
+ ; eax = (al << 24) | (al << 16) | (al << 8) | al;
+ ; rdx = (eax << 32) | eax
+ movzx esi, sil
+ mov rax, qword 0101010101010101h
+ imul rax, rsi
+
+ ; todo: alignment.
+ mov rcx, rdx
+ shr rcx, 3
+ rep stosq
+
+ and rdx, 7
+.dobytes:
+ mov rcx, rdx
+ rep stosb
+
+ mov rax, r10
+ %endif ; GCC
+
+%else ; X86
+ push edi
+
+ %ifdef ASM_CALL32_WATCOM
+ push eax
+ mov edi, eax
+ mov ecx, ebx
+ movzx eax, dl
+ %else
+ mov ecx, [esp + 0ch + 4]
+ movzx eax, byte [esp + 08h + 4]
+ mov edi, [esp + 04h + 4]
+ %endif
+ cmp ecx, 12
+ jb .dobytes
+
+ ; eax = (al << 24) | (al << 16) | (al << 8) | al;
+ mov ah, al
+ mov edx, eax
+ shl edx, 16
+ or eax, edx
+
+ mov edx, ecx
+ shr ecx, 2
+ rep stosd
+
+ and edx, 3
+ mov ecx, edx
+.dobytes:
+ rep stosb
+
+ %ifdef ASM_CALL32_WATCOM
+ pop eax
+ pop edi
+ %else
+ pop edi
+ mov eax, [esp + 4]
+ %endif
+%endif ; X86
+ ret
+ENDPROC RT_NOCRT(memset)
+
diff --git a/src/VBox/Runtime/common/string/memset.cpp b/src/VBox/Runtime/common/string/memset.cpp
new file mode 100644
index 00000000..2b94843a
--- /dev/null
+++ b/src/VBox/Runtime/common/string/memset.cpp
@@ -0,0 +1,86 @@
+/* $Id: memset.cpp $ */
+/** @file
+ * IPRT - CRT Strings, memset().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Fill a memory block with specific byte.
+ *
+ * @returns pvDst.
+ * @param pvDst Pointer to the block.
+ * @param ch The filler char.
+ * @param cb The size of the block.
+ */
+#undef memset
+#ifdef _MSC_VER
+# if _MSC_VER >= 1400
+void * __cdecl RT_NOCRT(memset)(__out_bcount_full_opt(_Size) void *pvDst, __in int ch, __in size_t cb)
+# else
+void *RT_NOCRT(memset)(void *pvDst, int ch, size_t cb)
+# endif
+#else
+void *RT_NOCRT(memset)(void *pvDst, int ch, size_t cb)
+#endif
+{
+ union
+ {
+ uint8_t *pu8;
+ uint32_t *pu32;
+ void *pvDst;
+ } u;
+ u.pvDst = pvDst;
+
+ /* 32-bit word moves. */
+ uint32_t u32 = ch | (ch << 8);
+ u32 |= u32 << 16;
+ size_t c = cb >> 2;
+ while (c-- > 0)
+ *u.pu32++ = u32;
+
+ /* Remaining byte moves. */
+ c = cb & 3;
+ while (c-- > 0)
+ *u.pu8++ = (uint8_t)u32;
+
+ return pvDst;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(memset);
diff --git a/src/VBox/Runtime/common/string/ministring.cpp b/src/VBox/Runtime/common/string/ministring.cpp
new file mode 100644
index 00000000..39fccb39
--- /dev/null
+++ b/src/VBox/Runtime/common/string/ministring.cpp
@@ -0,0 +1,1210 @@
+/* $Id: ministring.cpp $ */
+/** @file
+ * IPRT - Mini C++ string class.
+ *
+ * This is a base for both Utf8Str and other places where IPRT may want to use
+ * a lean C++ string class.
+ */
+
+/*
+ * Copyright (C) 2007-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cpp/ministring.h>
+#include "internal/iprt.h"
+
+#include <iprt/ctype.h>
+#include <iprt/uni.h>
+#include <iprt/err.h>
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+const size_t RTCString::npos = ~(size_t)0;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Allocation block alignment used when appending bytes to a string. */
+#define IPRT_MINISTRING_APPEND_ALIGNMENT 64
+
+
+RTCString &RTCString::assign(const RTCString &a_rSrc)
+{
+ Assert(&a_rSrc != this);
+ size_t const cchSrc = a_rSrc.length();
+ if (cchSrc > 0)
+ {
+ reserve(cchSrc + 1);
+ memcpy(m_psz, a_rSrc.c_str(), cchSrc);
+ m_psz[cchSrc] = '\0';
+ m_cch = cchSrc;
+ return *this;
+ }
+ setNull();
+ return *this;
+
+}
+
+int RTCString::assignNoThrow(const RTCString &a_rSrc) RT_NOEXCEPT
+{
+ AssertReturn(&a_rSrc != this, VINF_SUCCESS);
+ size_t const cchSrc = a_rSrc.length();
+ if (cchSrc > 0)
+ {
+ int rc = reserveNoThrow(cchSrc + 1);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(m_psz, a_rSrc.c_str(), cchSrc);
+ m_psz[cchSrc] = '\0';
+ m_cch = cchSrc;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ setNull();
+ return VINF_SUCCESS;
+
+}
+
+RTCString &RTCString::assign(const char *a_pszSrc)
+{
+ if (a_pszSrc)
+ {
+ size_t cchSrc = strlen(a_pszSrc);
+ if (cchSrc)
+ {
+ Assert((uintptr_t)&a_pszSrc - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated);
+
+ reserve(cchSrc + 1);
+ memcpy(m_psz, a_pszSrc, cchSrc);
+ m_psz[cchSrc] = '\0';
+ m_cch = cchSrc;
+ return *this;
+ }
+ }
+ setNull();
+ return *this;
+}
+
+int RTCString::assignNoThrow(const char *a_pszSrc) RT_NOEXCEPT
+{
+ if (a_pszSrc)
+ {
+ size_t cchSrc = strlen(a_pszSrc);
+ if (cchSrc)
+ {
+ Assert((uintptr_t)&a_pszSrc - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated);
+
+ int rc = reserveNoThrow(cchSrc + 1);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(m_psz, a_pszSrc, cchSrc);
+ m_psz[cchSrc] = '\0';
+ m_cch = cchSrc;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ }
+ setNull();
+ return VINF_SUCCESS;
+}
+
+RTCString &RTCString::assign(const RTCString &a_rSrc, size_t a_offSrc, size_t a_cchSrc /*= npos*/)
+{
+ AssertReturn(&a_rSrc != this, *this);
+ if (a_offSrc < a_rSrc.length())
+ {
+ size_t cchMax = a_rSrc.length() - a_offSrc;
+ if (a_cchSrc > cchMax)
+ a_cchSrc = cchMax;
+ reserve(a_cchSrc + 1);
+ memcpy(m_psz, a_rSrc.c_str() + a_offSrc, a_cchSrc);
+ m_psz[a_cchSrc] = '\0';
+ m_cch = a_cchSrc;
+ }
+ else
+ setNull();
+ return *this;
+}
+
+int RTCString::assignNoThrow(const RTCString &a_rSrc, size_t a_offSrc, size_t a_cchSrc /*= npos*/) RT_NOEXCEPT
+{
+ AssertReturn(&a_rSrc != this, VINF_SUCCESS);
+ if (a_offSrc < a_rSrc.length())
+ {
+ size_t cchMax = a_rSrc.length() - a_offSrc;
+ if (a_cchSrc > cchMax)
+ a_cchSrc = cchMax;
+ int rc = reserveNoThrow(a_cchSrc + 1);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(m_psz, a_rSrc.c_str() + a_offSrc, a_cchSrc);
+ m_psz[a_cchSrc] = '\0';
+ m_cch = a_cchSrc;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ setNull();
+ return VINF_SUCCESS;
+}
+
+RTCString &RTCString::assign(const char *a_pszSrc, size_t a_cchSrc)
+{
+ if (a_cchSrc)
+ {
+ a_cchSrc = RTStrNLen(a_pszSrc, a_cchSrc);
+ if (a_cchSrc)
+ {
+ Assert((uintptr_t)&a_pszSrc - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated);
+
+ reserve(a_cchSrc + 1);
+ memcpy(m_psz, a_pszSrc, a_cchSrc);
+ m_psz[a_cchSrc] = '\0';
+ m_cch = a_cchSrc;
+ return *this;
+ }
+ }
+ setNull();
+ return *this;
+}
+
+int RTCString::assignNoThrow(const char *a_pszSrc, size_t a_cchSrc) RT_NOEXCEPT
+{
+ if (a_cchSrc)
+ {
+ a_cchSrc = RTStrNLen(a_pszSrc, a_cchSrc);
+ if (a_cchSrc)
+ {
+ Assert((uintptr_t)&a_pszSrc - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated);
+
+ int rc = reserveNoThrow(a_cchSrc + 1);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(m_psz, a_pszSrc, a_cchSrc);
+ m_psz[a_cchSrc] = '\0';
+ m_cch = a_cchSrc;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ }
+ setNull();
+ return VINF_SUCCESS;
+}
+
+RTCString &RTCString::assign(size_t a_cTimes, char a_ch)
+{
+ reserve(a_cTimes + 1);
+ memset(m_psz, a_ch, a_cTimes);
+ m_psz[a_cTimes] = '\0';
+ m_cch = a_cTimes;
+ return *this;
+}
+
+
+int RTCString::assignNoThrow(size_t a_cTimes, char a_ch) RT_NOEXCEPT
+{
+ int rc = reserveNoThrow(a_cTimes + 1);
+ if (RT_SUCCESS(rc))
+ {
+ memset(m_psz, a_ch, a_cTimes);
+ m_psz[a_cTimes] = '\0';
+ m_cch = a_cTimes;
+ return VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+RTCString &RTCString::printf(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ printfV(pszFormat, va);
+ va_end(va);
+ return *this;
+}
+
+int RTCString::printfNoThrow(const char *pszFormat, ...) RT_NOEXCEPT
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = printfVNoThrow(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+/**
+ * Callback used with RTStrFormatV by RTCString::printfV.
+ *
+ * @returns The number of bytes added (not used).
+ *
+ * @param pvArg The string object.
+ * @param pachChars The characters to append.
+ * @param cbChars The number of characters. 0 on the final callback.
+ */
+/*static*/ DECLCALLBACK(size_t)
+RTCString::printfOutputCallback(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ RTCString *pThis = (RTCString *)pvArg;
+ if (cbChars)
+ {
+ size_t const cchBoth = pThis->m_cch + cbChars;
+ if (cchBoth >= pThis->m_cbAllocated)
+ {
+ /* Double the buffer size, if it's less that _4M. Align sizes like
+ for append. */
+ size_t cbAlloc = RT_ALIGN_Z(pThis->m_cbAllocated, IPRT_MINISTRING_APPEND_ALIGNMENT);
+ cbAlloc += RT_MIN(cbAlloc, _4M);
+ if (cbAlloc <= cchBoth)
+ cbAlloc = RT_ALIGN_Z(cchBoth + 1, IPRT_MINISTRING_APPEND_ALIGNMENT);
+ pThis->reserve(cbAlloc);
+#ifndef RT_EXCEPTIONS_ENABLED
+ AssertReleaseReturn(pThis->capacity() > cchBoth, 0);
+#endif
+ }
+
+ memcpy(&pThis->m_psz[pThis->m_cch], pachChars, cbChars);
+ pThis->m_cch = cchBoth;
+ pThis->m_psz[cchBoth] = '\0';
+ }
+ return cbChars;
+}
+
+RTCString &RTCString::printfV(const char *pszFormat, va_list va)
+{
+ cleanup();
+ RTStrFormatV(printfOutputCallback, this, NULL, NULL, pszFormat, va);
+ return *this;
+}
+
+RTCString &RTCString::appendPrintfV(const char *pszFormat, va_list va)
+{
+ RTStrFormatV(printfOutputCallback, this, NULL, NULL, pszFormat, va);
+ return *this;
+}
+
+struct RTCSTRINGOTHROW
+{
+ RTCString *pThis;
+ int rc;
+};
+
+/**
+ * Callback used with RTStrFormatV by RTCString::printfVNoThrow.
+ *
+ * @returns The number of bytes added (not used).
+ *
+ * @param pvArg Pointer to a RTCSTRINGOTHROW structure.
+ * @param pachChars The characters to append.
+ * @param cbChars The number of characters. 0 on the final callback.
+ */
+/*static*/ DECLCALLBACK(size_t)
+RTCString::printfOutputCallbackNoThrow(void *pvArg, const char *pachChars, size_t cbChars) RT_NOEXCEPT
+{
+ RTCString *pThis = ((RTCSTRINGOTHROW *)pvArg)->pThis;
+ if (cbChars)
+ {
+ size_t const cchBoth = pThis->m_cch + cbChars;
+ if (cchBoth >= pThis->m_cbAllocated)
+ {
+ /* Double the buffer size, if it's less that _4M. Align sizes like
+ for append. */
+ size_t cbAlloc = RT_ALIGN_Z(pThis->m_cbAllocated, IPRT_MINISTRING_APPEND_ALIGNMENT);
+ cbAlloc += RT_MIN(cbAlloc, _4M);
+ if (cbAlloc <= cchBoth)
+ cbAlloc = RT_ALIGN_Z(cchBoth + 1, IPRT_MINISTRING_APPEND_ALIGNMENT);
+ int rc = pThis->reserveNoThrow(cbAlloc);
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ {
+ ((RTCSTRINGOTHROW *)pvArg)->rc = rc;
+ return cbChars;
+ }
+ }
+
+ memcpy(&pThis->m_psz[pThis->m_cch], pachChars, cbChars);
+ pThis->m_cch = cchBoth;
+ pThis->m_psz[cchBoth] = '\0';
+ }
+ return cbChars;
+}
+
+int RTCString::printfVNoThrow(const char *pszFormat, va_list va) RT_NOEXCEPT
+{
+ cleanup();
+ RTCSTRINGOTHROW Args = { this, VINF_SUCCESS };
+ RTStrFormatV(printfOutputCallbackNoThrow, &Args, NULL, NULL, pszFormat, va);
+ return Args.rc;
+}
+
+int RTCString::appendPrintfVNoThrow(const char *pszFormat, va_list va) RT_NOEXCEPT
+{
+ RTCSTRINGOTHROW Args = { this, VINF_SUCCESS };
+ RTStrFormatV(printfOutputCallbackNoThrow, &Args, NULL, NULL, pszFormat, va);
+ return Args.rc;
+}
+
+RTCString &RTCString::appendPrintf(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ appendPrintfV(pszFormat, va);
+ va_end(va);
+ return *this;
+}
+
+int RTCString::appendPrintfNoThrow(const char *pszFormat, ...) RT_NOEXCEPT
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int rc = appendPrintfVNoThrow(pszFormat, va);
+ va_end(va);
+ return rc;
+}
+
+RTCString &RTCString::append(const RTCString &that)
+{
+ Assert(&that != this);
+ return appendWorker(that.c_str(), that.length());
+}
+
+int RTCString::appendNoThrow(const RTCString &that) RT_NOEXCEPT
+{
+ Assert(&that != this);
+ return appendWorkerNoThrow(that.c_str(), that.length());
+}
+
+RTCString &RTCString::append(const char *pszThat)
+{
+ return appendWorker(pszThat, strlen(pszThat));
+}
+
+int RTCString::appendNoThrow(const char *pszThat) RT_NOEXCEPT
+{
+ return appendWorkerNoThrow(pszThat, strlen(pszThat));
+}
+
+RTCString &RTCString::append(const RTCString &rThat, size_t offStart, size_t cchMax /*= RTSTR_MAX*/)
+{
+ if (offStart < rThat.length())
+ {
+ size_t cchLeft = rThat.length() - offStart;
+ return appendWorker(rThat.c_str() + offStart, RT_MIN(cchLeft, cchMax));
+ }
+ return *this;
+}
+
+int RTCString::appendNoThrow(const RTCString &rThat, size_t offStart, size_t cchMax /*= RTSTR_MAX*/) RT_NOEXCEPT
+{
+ if (offStart < rThat.length())
+ {
+ size_t cchLeft = rThat.length() - offStart;
+ return appendWorkerNoThrow(rThat.c_str() + offStart, RT_MIN(cchLeft, cchMax));
+ }
+ return VINF_SUCCESS;
+}
+
+RTCString &RTCString::append(const char *pszThat, size_t cchMax)
+{
+ return appendWorker(pszThat, RTStrNLen(pszThat, cchMax));
+}
+
+int RTCString::appendNoThrow(const char *pszThat, size_t cchMax) RT_NOEXCEPT
+{
+ return appendWorkerNoThrow(pszThat, RTStrNLen(pszThat, cchMax));
+}
+
+RTCString &RTCString::appendWorker(const char *pszSrc, size_t cchSrc)
+{
+ if (cchSrc)
+ {
+ Assert((uintptr_t)&pszSrc - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated);
+
+ size_t cchThis = length();
+ size_t cchBoth = cchThis + cchSrc;
+
+ if (cchBoth >= m_cbAllocated)
+ {
+ reserve(RT_ALIGN_Z(cchBoth + 1, IPRT_MINISTRING_APPEND_ALIGNMENT));
+ // calls realloc(cchBoth + 1) and sets m_cbAllocated; may throw bad_alloc.
+#ifndef RT_EXCEPTIONS_ENABLED
+ AssertRelease(capacity() > cchBoth);
+#endif
+ }
+
+ memcpy(&m_psz[cchThis], pszSrc, cchSrc);
+ m_psz[cchBoth] = '\0';
+ m_cch = cchBoth;
+ }
+ return *this;
+}
+
+int RTCString::appendWorkerNoThrow(const char *pszSrc, size_t cchSrc) RT_NOEXCEPT
+{
+ if (cchSrc)
+ {
+ Assert((uintptr_t)&pszSrc - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated);
+
+ size_t cchThis = length();
+ size_t cchBoth = cchThis + cchSrc;
+
+ if (cchBoth >= m_cbAllocated)
+ {
+ int rc = reserveNoThrow(RT_ALIGN_Z(cchBoth + 1, IPRT_MINISTRING_APPEND_ALIGNMENT));
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ return rc;
+ }
+
+ memcpy(&m_psz[cchThis], pszSrc, cchSrc);
+ m_psz[cchBoth] = '\0';
+ m_cch = cchBoth;
+ }
+ return VINF_SUCCESS;
+}
+
+RTCString &RTCString::append(char ch)
+{
+ Assert((unsigned char)ch < 0x80); /* Don't create invalid UTF-8. */
+ if (ch)
+ {
+ // allocate in chunks of 20 in case this gets called several times
+ if (m_cch + 1 >= m_cbAllocated)
+ {
+ reserve(RT_ALIGN_Z(m_cch + 2, IPRT_MINISTRING_APPEND_ALIGNMENT));
+ // calls realloc(cbBoth) and sets m_cbAllocated; may throw bad_alloc.
+#ifndef RT_EXCEPTIONS_ENABLED
+ AssertRelease(capacity() > m_cch + 1);
+#endif
+ }
+
+ m_psz[m_cch] = ch;
+ m_psz[++m_cch] = '\0';
+ }
+ return *this;
+}
+
+int RTCString::appendNoThrow(char ch) RT_NOEXCEPT
+{
+ Assert((unsigned char)ch < 0x80); /* Don't create invalid UTF-8. */
+ if (ch)
+ {
+ // allocate in chunks of 20 in case this gets called several times
+ if (m_cch + 1 >= m_cbAllocated)
+ {
+ int rc = reserveNoThrow(RT_ALIGN_Z(m_cch + 2, IPRT_MINISTRING_APPEND_ALIGNMENT));
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ return rc;
+ }
+
+ m_psz[m_cch] = ch;
+ m_psz[++m_cch] = '\0';
+ }
+ return VINF_SUCCESS;
+}
+
+RTCString &RTCString::appendCodePoint(RTUNICP uc)
+{
+ /*
+ * Single byte encoding.
+ */
+ if (uc < 0x80)
+ return RTCString::append((char)uc);
+
+ /*
+ * Multibyte encoding.
+ * Assume max encoding length when resizing the string, that's simpler.
+ */
+ AssertReturn(uc <= UINT32_C(0x7fffffff), *this);
+
+ if (m_cch + 6 >= m_cbAllocated)
+ {
+ reserve(RT_ALIGN_Z(m_cch + 6 + 1, IPRT_MINISTRING_APPEND_ALIGNMENT));
+ // calls realloc(cbBoth) and sets m_cbAllocated; may throw bad_alloc.
+#ifndef RT_EXCEPTIONS_ENABLED
+ AssertRelease(capacity() > m_cch + 6);
+#endif
+ }
+
+ char *pszNext = RTStrPutCp(&m_psz[m_cch], uc);
+ m_cch = pszNext - m_psz;
+ *pszNext = '\0';
+
+ return *this;
+}
+
+int RTCString::appendCodePointNoThrow(RTUNICP uc) RT_NOEXCEPT
+{
+ /*
+ * Single byte encoding.
+ */
+ if (uc < 0x80)
+ return RTCString::appendNoThrow((char)uc);
+
+ /*
+ * Multibyte encoding.
+ * Assume max encoding length when resizing the string, that's simpler.
+ */
+ AssertReturn(uc <= UINT32_C(0x7fffffff), VERR_INVALID_UTF8_ENCODING);
+
+ if (m_cch + 6 >= m_cbAllocated)
+ {
+ int rc = reserveNoThrow(RT_ALIGN_Z(m_cch + 6 + 1, IPRT_MINISTRING_APPEND_ALIGNMENT));
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ return rc;
+ }
+
+ char *pszNext = RTStrPutCp(&m_psz[m_cch], uc);
+ m_cch = pszNext - m_psz;
+ *pszNext = '\0';
+
+ return VINF_SUCCESS;
+}
+
+RTCString &RTCString::erase(size_t offStart /*= 0*/, size_t cchLength /*= npos*/) RT_NOEXCEPT
+{
+ size_t cch = length();
+ if (offStart < cch)
+ {
+ if (cchLength >= cch - offStart)
+ {
+ /* Trail removal, nothing to move. */
+ m_cch = offStart;
+ m_psz[offStart] = '\0';
+ }
+ else if (cchLength > 0)
+ {
+ /* Pull up the tail to offStart. */
+ size_t cchAfter = cch - offStart - cchLength;
+ memmove(&m_psz[offStart], &m_psz[offStart + cchLength], cchAfter);
+ m_cch = cch -= cchLength;
+ m_psz[cch] = '\0';
+ }
+ }
+ return *this;
+}
+
+RTCString &RTCString::replace(size_t offStart, size_t cchLength, const RTCString &rStrReplacement)
+{
+ return replaceWorker(offStart, cchLength, rStrReplacement.c_str(), rStrReplacement.length());
+}
+
+int RTCString::replaceNoThrow(size_t offStart, size_t cchLength, const RTCString &rStrReplacement) RT_NOEXCEPT
+{
+ return replaceWorkerNoThrow(offStart, cchLength, rStrReplacement.c_str(), rStrReplacement.length());
+}
+
+RTCString &RTCString::replace(size_t offStart, size_t cchLength, const RTCString &rStrReplacement,
+ size_t offReplacement, size_t cchReplacement)
+{
+ Assert(this != &rStrReplacement);
+ if (cchReplacement > 0)
+ {
+ if (offReplacement < rStrReplacement.length())
+ {
+ size_t cchMaxReplacement = rStrReplacement.length() - offReplacement;
+ return replaceWorker(offStart, cchLength, rStrReplacement.c_str() + offReplacement,
+ RT_MIN(cchReplacement, cchMaxReplacement));
+ }
+ /* Our non-standard handling of out_of_range situations. */
+ AssertMsgFailed(("offReplacement=%zu (cchReplacement=%zu) rStrReplacement.length()=%zu\n",
+ offReplacement, cchReplacement, rStrReplacement.length()));
+ }
+ return replaceWorker(offStart, cchLength, "", 0);
+}
+
+int RTCString::replaceNoThrow(size_t offStart, size_t cchLength, const RTCString &rStrReplacement,
+ size_t offReplacement, size_t cchReplacement) RT_NOEXCEPT
+{
+ Assert(this != &rStrReplacement);
+ if (cchReplacement > 0)
+ {
+ if (offReplacement < rStrReplacement.length())
+ {
+ size_t cchMaxReplacement = rStrReplacement.length() - offReplacement;
+ return replaceWorkerNoThrow(offStart, cchLength, rStrReplacement.c_str() + offReplacement,
+ RT_MIN(cchReplacement, cchMaxReplacement));
+ }
+ return VERR_OUT_OF_RANGE;
+ }
+ return replaceWorkerNoThrow(offStart, cchLength, "", 0);
+}
+
+RTCString &RTCString::replace(size_t offStart, size_t cchLength, const char *pszReplacement)
+{
+ return replaceWorker(offStart, cchLength, pszReplacement, strlen(pszReplacement));
+}
+
+int RTCString::replaceNoThrow(size_t offStart, size_t cchLength, const char *pszReplacement) RT_NOEXCEPT
+{
+ return replaceWorkerNoThrow(offStart, cchLength, pszReplacement, strlen(pszReplacement));
+}
+
+RTCString &RTCString::replace(size_t offStart, size_t cchLength, const char *pszReplacement, size_t cchReplacement)
+{
+ return replaceWorker(offStart, cchLength, pszReplacement, RTStrNLen(pszReplacement, cchReplacement));
+}
+
+int RTCString::replaceNoThrow(size_t offStart, size_t cchLength, const char *pszReplacement, size_t cchReplacement) RT_NOEXCEPT
+{
+ return replaceWorkerNoThrow(offStart, cchLength, pszReplacement, RTStrNLen(pszReplacement, cchReplacement));
+}
+
+RTCString &RTCString::replaceWorker(size_t offStart, size_t cchLength, const char *pszSrc, size_t cchSrc)
+{
+ Assert((uintptr_t)&pszSrc - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated || !cchSrc);
+
+ /*
+ * Our non-standard handling of out_of_range situations.
+ */
+ size_t const cchOldLength = length();
+ AssertMsgReturn(offStart < cchOldLength, ("offStart=%zu (cchLength=%zu); length()=%zu\n", offStart, cchLength, cchOldLength),
+ *this);
+
+ /*
+ * Correct the length parameter.
+ */
+ size_t cchMaxLength = cchOldLength - offStart;
+ if (cchMaxLength < cchLength)
+ cchLength = cchMaxLength;
+
+ /*
+ * Adjust string allocation if necessary.
+ */
+ size_t cchNew = cchOldLength - cchLength + cchSrc;
+ if (cchNew >= m_cbAllocated)
+ {
+ reserve(RT_ALIGN_Z(cchNew + 1, IPRT_MINISTRING_APPEND_ALIGNMENT));
+ // calls realloc(cchBoth + 1) and sets m_cbAllocated; may throw bad_alloc.
+#ifndef RT_EXCEPTIONS_ENABLED
+ AssertRelease(capacity() > cchNew);
+#endif
+ }
+
+ /*
+ * Make the change.
+ */
+ size_t cchAfter = cchOldLength - offStart - cchLength;
+ if (cchAfter > 0)
+ memmove(&m_psz[offStart + cchSrc], &m_psz[offStart + cchLength], cchAfter);
+ memcpy(&m_psz[offStart], pszSrc, cchSrc);
+ m_psz[cchNew] = '\0';
+ m_cch = cchNew;
+
+ return *this;
+}
+
+int RTCString::replaceWorkerNoThrow(size_t offStart, size_t cchLength, const char *pszSrc, size_t cchSrc) RT_NOEXCEPT
+{
+ Assert((uintptr_t)&pszSrc - (uintptr_t)m_psz >= (uintptr_t)m_cbAllocated || !cchSrc);
+
+ /*
+ * Our non-standard handling of out_of_range situations.
+ */
+ size_t const cchOldLength = length();
+ AssertMsgReturn(offStart < cchOldLength, ("offStart=%zu (cchLength=%zu); length()=%zu\n", offStart, cchLength, cchOldLength),
+ VERR_OUT_OF_RANGE);
+
+ /*
+ * Correct the length parameter.
+ */
+ size_t cchMaxLength = cchOldLength - offStart;
+ if (cchMaxLength < cchLength)
+ cchLength = cchMaxLength;
+
+ /*
+ * Adjust string allocation if necessary.
+ */
+ size_t cchNew = cchOldLength - cchLength + cchSrc;
+ if (cchNew >= m_cbAllocated)
+ {
+ int rc = reserveNoThrow(RT_ALIGN_Z(cchNew + 1, IPRT_MINISTRING_APPEND_ALIGNMENT));
+ if (RT_SUCCESS(rc))
+ { /* likely */ }
+ else
+ return rc;
+ }
+
+ /*
+ * Make the change.
+ */
+ size_t cchAfter = cchOldLength - offStart - cchLength;
+ if (cchAfter > 0)
+ memmove(&m_psz[offStart + cchSrc], &m_psz[offStart + cchLength], cchAfter);
+ memcpy(&m_psz[offStart], pszSrc, cchSrc);
+ m_psz[cchNew] = '\0';
+ m_cch = cchNew;
+
+ return VINF_SUCCESS;
+}
+
+
+RTCString &RTCString::truncate(size_t cchMax) RT_NOEXCEPT
+{
+ if (cchMax < m_cch)
+ {
+ /*
+ * Make sure the truncated string ends with a correctly encoded
+ * codepoint and is not missing a few bytes.
+ */
+ if (cchMax > 0)
+ {
+ char chTail = m_psz[cchMax];
+ if ( (chTail & 0x80) == 0 /* single byte codepoint */
+ || (chTail & 0xc0) == 0xc0) /* first byte of codepoint sequence. */
+ { /* likely */ }
+ else
+ {
+ /* We need to find the start of the codepoint sequence: */
+ do
+ cchMax -= 1;
+ while ( cchMax > 0
+ && (m_psz[cchMax] & 0xc0) != 0xc0);
+ }
+ }
+
+ /*
+ * Do the truncating.
+ */
+ m_psz[cchMax] = '\0';
+ m_cch = cchMax;
+ }
+ return *this;
+}
+
+
+size_t RTCString::find(const char *pszNeedle, size_t offStart /*= 0*/) const RT_NOEXCEPT
+{
+ if (offStart < length())
+ {
+ const char *pszThis = c_str();
+ if (pszThis)
+ {
+ if (pszNeedle && *pszNeedle != '\0')
+ {
+ const char *pszHit = strstr(pszThis + offStart, pszNeedle);
+ if (pszHit)
+ return pszHit - pszThis;
+ }
+ }
+ }
+
+ return npos;
+}
+
+size_t RTCString::find(const RTCString *pStrNeedle, size_t offStart /*= 0*/) const RT_NOEXCEPT
+{
+ if (offStart < length())
+ {
+ const char *pszThis = c_str();
+ if (pszThis)
+ {
+ if (pStrNeedle)
+ {
+ const char *pszNeedle = pStrNeedle->c_str();
+ if (pszNeedle && *pszNeedle != '\0')
+ {
+ const char *pszHit = strstr(pszThis + offStart, pszNeedle);
+ if (pszHit)
+ return pszHit - pszThis;
+ }
+ }
+ }
+ }
+
+ return npos;
+}
+
+
+size_t RTCString::find(const RTCString &rStrNeedle, size_t offStart /*= 0*/) const RT_NOEXCEPT
+{
+ return find(&rStrNeedle, offStart);
+}
+
+
+size_t RTCString::find(const char chNeedle, size_t offStart /*= 0*/) const RT_NOEXCEPT
+{
+ Assert((unsigned int)chNeedle < 128U);
+ if (offStart < length())
+ {
+ const char *pszThis = c_str();
+ if (pszThis)
+ {
+ const char *pszHit = (const char *)memchr(&pszThis[offStart], chNeedle, length() - offStart);
+ if (pszHit)
+ return pszHit - pszThis;
+ }
+ }
+ return npos;
+}
+
+
+void RTCString::findReplace(char chFind, char chReplace) RT_NOEXCEPT
+{
+ Assert((unsigned int)chFind < 128U);
+ Assert((unsigned int)chReplace < 128U);
+
+ for (size_t i = 0; i < length(); ++i)
+ {
+ char *p = &m_psz[i];
+ if (*p == chFind)
+ *p = chReplace;
+ }
+}
+
+size_t RTCString::count(char ch) const RT_NOEXCEPT
+{
+ Assert((unsigned int)ch < 128U);
+
+ size_t c = 0;
+ const char *psz = m_psz;
+ if (psz)
+ {
+ char chCur;
+ while ((chCur = *psz++) != '\0')
+ if (chCur == ch)
+ c++;
+ }
+ return c;
+}
+
+#if 0 /** @todo implement these when needed. */
+size_t RTCString::count(const char *psz, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT
+{
+}
+
+size_t RTCString::count(const RTCString *pStr, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT
+{
+
+}
+#endif
+
+
+RTCString &RTCString::strip() RT_NOEXCEPT
+{
+ stripRight();
+ return stripLeft();
+}
+
+
+RTCString &RTCString::stripLeft() RT_NOEXCEPT
+{
+ char *psz = m_psz;
+ size_t const cch = m_cch;
+ size_t off = 0;
+ while (off < cch && RT_C_IS_SPACE(psz[off]))
+ off++;
+ if (off > 0)
+ {
+ if (off != cch)
+ {
+ memmove(psz, &psz[off], cch - off + 1);
+ m_cch = cch - off;
+ }
+ else
+ setNull();
+ }
+ return *this;
+}
+
+
+RTCString &RTCString::stripRight() RT_NOEXCEPT
+{
+ char *psz = m_psz;
+ size_t cch = m_cch;
+ while (cch > 0 && RT_C_IS_SPACE(psz[cch - 1]))
+ cch--;
+ if (m_cch != cch)
+ {
+ m_cch = cch;
+ psz[cch] = '\0';
+ }
+ return *this;
+}
+
+
+
+RTCString RTCString::substrCP(size_t pos /*= 0*/, size_t n /*= npos*/) const
+{
+ RTCString ret;
+
+ if (n)
+ {
+ const char *psz;
+
+ if ((psz = c_str()))
+ {
+ RTUNICP cp;
+
+ // walk the UTF-8 characters until where the caller wants to start
+ size_t i = pos;
+ while (*psz && i--)
+ if (RT_FAILURE(RTStrGetCpEx(&psz, &cp)))
+ return ret; // return empty string on bad encoding
+
+ const char *pFirst = psz;
+
+ if (n == npos)
+ // all the rest:
+ ret = pFirst;
+ else
+ {
+ i = n;
+ while (*psz && i--)
+ if (RT_FAILURE(RTStrGetCpEx(&psz, &cp)))
+ return ret; // return empty string on bad encoding
+
+ size_t cbCopy = psz - pFirst;
+ if (cbCopy)
+ {
+ ret.reserve(cbCopy + 1); // may throw bad_alloc
+#ifndef RT_EXCEPTIONS_ENABLED
+ AssertRelease(capacity() >= cbCopy + 1);
+#endif
+ memcpy(ret.m_psz, pFirst, cbCopy);
+ ret.m_cch = cbCopy;
+ ret.m_psz[cbCopy] = '\0';
+ }
+ }
+ }
+ }
+
+ return ret;
+}
+
+bool RTCString::endsWith(const RTCString &that, CaseSensitivity cs /*= CaseSensitive*/) const RT_NOEXCEPT
+{
+ size_t l1 = length();
+ if (l1 == 0)
+ return false;
+
+ size_t l2 = that.length();
+ if (l1 < l2)
+ return false;
+
+ if (!m_psz) /* Don't crash when running against an empty string. */
+ return false;
+
+ /** @todo r=bird: See handling of l2 == in startsWith; inconsistent output (if l2 == 0, it matches anything). */
+
+ size_t l = l1 - l2;
+ if (cs == CaseSensitive)
+ return ::RTStrCmp(&m_psz[l], that.m_psz) == 0;
+ return ::RTStrICmp(&m_psz[l], that.m_psz) == 0;
+}
+
+bool RTCString::startsWith(const RTCString &that, CaseSensitivity cs /*= CaseSensitive*/) const RT_NOEXCEPT
+{
+ size_t l1 = length();
+ size_t l2 = that.length();
+ if (l1 == 0 || l2 == 0) /** @todo r=bird: this differs from endsWith, and I think other IPRT code. If l2 == 0, it matches anything. */
+ return false;
+
+ if (l1 < l2)
+ return false;
+
+ if (cs == CaseSensitive)
+ return ::RTStrNCmp(m_psz, that.m_psz, l2) == 0;
+ return ::RTStrNICmp(m_psz, that.m_psz, l2) == 0;
+}
+
+bool RTCString::startsWithWord(const char *pszWord, CaseSensitivity enmCase /*= CaseSensitive*/) const RT_NOEXCEPT
+{
+ const char *pszSrc = RTStrStripL(c_str()); /** @todo RTStrStripL doesn't use RTUniCpIsSpace (nbsp) */
+ size_t cchWord = strlen(pszWord);
+ if ( enmCase == CaseSensitive
+ ? RTStrNCmp(pszSrc, pszWord, cchWord) == 0
+ : RTStrNICmp(pszSrc, pszWord, cchWord) == 0)
+ {
+ if ( pszSrc[cchWord] == '\0'
+ || RT_C_IS_SPACE(pszSrc[cchWord])
+ || RT_C_IS_PUNCT(pszSrc[cchWord]) )
+ return true;
+ RTUNICP uc = RTStrGetCp(&pszSrc[cchWord]);
+ if (RTUniCpIsSpace(uc))
+ return true;
+ }
+ return false;
+}
+
+bool RTCString::startsWithWord(const RTCString &rThat, CaseSensitivity enmCase /*= CaseSensitive*/) const RT_NOEXCEPT
+{
+ return startsWithWord(rThat.c_str(), enmCase);
+}
+
+bool RTCString::contains(const RTCString &that, CaseSensitivity cs /*= CaseSensitive*/) const RT_NOEXCEPT
+{
+ /** @todo r-bird: Not checking for NULL strings like startsWith does (and
+ * endsWith only does half way). */
+ if (cs == CaseSensitive)
+ return ::RTStrStr(m_psz, that.m_psz) != NULL;
+ return ::RTStrIStr(m_psz, that.m_psz) != NULL;
+}
+
+bool RTCString::contains(const char *pszNeedle, CaseSensitivity cs /*= CaseSensitive*/) const RT_NOEXCEPT
+{
+ /** @todo r-bird: Not checking for NULL strings like startsWith does (and
+ * endsWith only does half way). */
+ if (cs == CaseSensitive)
+ return ::RTStrStr(m_psz, pszNeedle) != NULL;
+ return ::RTStrIStr(m_psz, pszNeedle) != NULL;
+}
+
+int RTCString::toInt(uint64_t &i) const RT_NOEXCEPT
+{
+ if (!m_psz)
+ return VERR_NO_DIGITS;
+ return RTStrToUInt64Ex(m_psz, NULL, 0, &i);
+}
+
+int RTCString::toInt(uint32_t &i) const RT_NOEXCEPT
+{
+ if (!m_psz)
+ return VERR_NO_DIGITS;
+ return RTStrToUInt32Ex(m_psz, NULL, 0, &i);
+}
+
+RTCList<RTCString, RTCString *>
+RTCString::split(const RTCString &a_rstrSep, SplitMode mode /* = RemoveEmptyParts */) const
+{
+ RTCList<RTCString> strRet;
+ if (!m_psz)
+ return strRet;
+ if (a_rstrSep.isEmpty())
+ {
+ strRet.append(RTCString(m_psz));
+ return strRet;
+ }
+
+ size_t cch = m_cch;
+ char const *pszTmp = m_psz;
+ while (cch > 0)
+ {
+ char const *pszNext = strstr(pszTmp, a_rstrSep.c_str());
+ if (!pszNext)
+ {
+ strRet.append(RTCString(pszTmp, cch));
+ break;
+ }
+ size_t cchNext = pszNext - pszTmp;
+ if ( cchNext > 0
+ || mode == KeepEmptyParts)
+ strRet.append(RTCString(pszTmp, cchNext));
+ pszTmp += cchNext + a_rstrSep.length();
+ cch -= cchNext + a_rstrSep.length();
+ }
+
+ return strRet;
+}
+
+/* static */
+RTCString
+RTCString::joinEx(const RTCList<RTCString, RTCString *> &a_rList,
+ const RTCString &a_rstrPrefix /* = "" */,
+ const RTCString &a_rstrSep /* = "" */)
+{
+ RTCString strRet;
+ if (a_rList.size() > 1)
+ {
+ /* calc the required size */
+ size_t cbNeeded = a_rstrSep.length() * (a_rList.size() - 1) + 1;
+ cbNeeded += a_rstrPrefix.length() * (a_rList.size() - 1) + 1;
+ for (size_t i = 0; i < a_rList.size(); ++i)
+ cbNeeded += a_rList.at(i).length();
+ strRet.reserve(cbNeeded);
+
+ /* do the appending. */
+ for (size_t i = 0; i < a_rList.size() - 1; ++i)
+ {
+ if (a_rstrPrefix.isNotEmpty())
+ strRet.append(a_rstrPrefix);
+ strRet.append(a_rList.at(i));
+ strRet.append(a_rstrSep);
+ }
+ strRet.append(a_rList.last());
+ }
+ /* special case: one list item. */
+ else if (a_rList.size() > 0)
+ {
+ if (a_rstrPrefix.isNotEmpty())
+ strRet.append(a_rstrPrefix);
+ strRet.append(a_rList.last());
+ }
+
+ return strRet;
+}
+
+/* static */
+RTCString
+RTCString::join(const RTCList<RTCString, RTCString *> &a_rList,
+ const RTCString &a_rstrSep /* = "" */)
+{
+ return RTCString::joinEx(a_rList,
+ "" /* a_rstrPrefix */, a_rstrSep);
+}
+
+RTDECL(const RTCString) operator+(const RTCString &a_rStr1, const RTCString &a_rStr2)
+{
+ RTCString strRet(a_rStr1);
+ strRet += a_rStr2;
+ return strRet;
+}
+
+RTDECL(const RTCString) operator+(const RTCString &a_rStr1, const char *a_pszStr2)
+{
+ RTCString strRet(a_rStr1);
+ strRet += a_pszStr2;
+ return strRet;
+}
+
+RTDECL(const RTCString) operator+(const char *a_psz1, const RTCString &a_rStr2)
+{
+ RTCString strRet(a_psz1);
+ strRet += a_rStr2;
+ return strRet;
+}
+
diff --git a/src/VBox/Runtime/common/string/nocrt-atof.cpp b/src/VBox/Runtime/common/string/nocrt-atof.cpp
new file mode 100644
index 00000000..850fe92a
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-atof.cpp
@@ -0,0 +1,52 @@
+/* $Id: nocrt-atof.cpp $ */
+/** @file
+ * IPRT - No-CRT - atof().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+
+
+#undef atof
+double RT_NOCRT(atof)(const char *psz)
+{
+ return RT_NOCRT(strtod)(psz, NULL);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(atof);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-scprintf.cpp b/src/VBox/Runtime/common/string/nocrt-scprintf.cpp
new file mode 100644
index 00000000..af998b41
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-scprintf.cpp
@@ -0,0 +1,58 @@
+/* $Id: nocrt-scprintf.cpp $ */
+/** @file
+ * IPRT - No-CRT Strings, scprintf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/iprt.h"
+#include <iprt/nocrt/stdio.h>
+#include <iprt/string.h>
+
+
+#undef scprintf
+int RT_NOCRT(scprintf)(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ ssize_t cchOutput = RT_NOCRT(vscprintf)(pszFormat, va);
+ va_end(va);
+ return cchOutput;
+
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(scprintf);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-snprintf.cpp b/src/VBox/Runtime/common/string/nocrt-snprintf.cpp
new file mode 100644
index 00000000..20cd0a3e
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-snprintf.cpp
@@ -0,0 +1,66 @@
+/* $Id: nocrt-snprintf.cpp $ */
+/** @file
+ * IPRT - No-CRT Strings, snprintf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/iprt.h"
+#include <iprt/nocrt/stdio.h>
+#include <iprt/string.h>
+
+
+#undef snprintf
+int RT_NOCRT(snprintf)(char *pszDst, size_t cbDst, const char *pszFormat, ...)
+{
+ char szNull[4];
+ if (pszDst && cbDst > 0)
+ { /* likely */ }
+ else
+ {
+ cbDst = sizeof(szNull);
+ pszDst = szNull;
+ }
+ va_list va;
+ va_start(va, pszFormat);
+ ssize_t cchOutput = RTStrPrintf2V(pszDst, cbDst, pszFormat, va);
+ va_end(va);
+ return cchOutput >= 0 ? cchOutput : -cchOutput;
+
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(snprintf);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-sscanf.cpp b/src/VBox/Runtime/common/string/nocrt-sscanf.cpp
new file mode 100644
index 00000000..a379b7b5
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-sscanf.cpp
@@ -0,0 +1,57 @@
+/* $Id: nocrt-sscanf.cpp $ */
+/** @file
+ * IPRT - No-CRT - sscanf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdio.h>
+#include <iprt/stdarg.h>
+
+
+#undef sscanf
+int RT_NOCRT(sscanf)(const char *pszString, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ int cMatches = RT_NOCRT(vsscanf)(pszString, pszFormat, va);
+ va_end(va);
+ return cMatches;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(sscanf);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-strdup.cpp b/src/VBox/Runtime/common/string/nocrt-strdup.cpp
new file mode 100644
index 00000000..09bd55db
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-strdup.cpp
@@ -0,0 +1,52 @@
+/* $Id: nocrt-strdup.cpp $ */
+/** @file
+ * IPRT - No-CRT Strings, strdup().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+#undef strdup
+char *RT_NOCRT(strdup)(const char *pszSrc)
+{
+ return RTStrDup(pszSrc);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strdup);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-stricmp.cpp b/src/VBox/Runtime/common/string/nocrt-stricmp.cpp
new file mode 100644
index 00000000..39b9fb0b
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-stricmp.cpp
@@ -0,0 +1,52 @@
+/* $Id: nocrt-stricmp.cpp $ */
+/** @file
+ * IPRT - No-CRT Strings, stricmp().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+#undef stricmp
+int RT_NOCRT(stricmp)(const char *psz1, const char *psz2)
+{
+ /* ASSUMES UTF-8 or plain ASCII input... The only alternative is to assume C locale. */
+ return RTStrICmp(psz1, psz2);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(stricmp);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-strtod.cpp b/src/VBox/Runtime/common/string/nocrt-strtod.cpp
new file mode 100644
index 00000000..b38d0cf3
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-strtod.cpp
@@ -0,0 +1,59 @@
+/* $Id: nocrt-strtod.cpp $ */
+/** @file
+ * IPRT - No-CRT - strtod().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+#undef strtod
+double RT_NOCRT(strtod)(const char *psz, char **ppszNext)
+{
+ double rd = 0.0;
+ int rc = RTStrToDoubleEx(RTStrStripL(psz), ppszNext, 0, &rd);
+ if (rc != VINF_SUCCESS)
+ errno = RTErrConvertToErrno(rc);
+ return rd;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strtod);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-strtok.cpp b/src/VBox/Runtime/common/string/nocrt-strtok.cpp
new file mode 100644
index 00000000..31dac67e
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-strtok.cpp
@@ -0,0 +1,56 @@
+/* $Id: nocrt-strtok.cpp $ */
+/** @file
+ * IPRT - No-CRT Strings, strtok().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/nocrt.h"
+#include <iprt/string.h>
+
+
+#undef strtok
+char *RT_NOCRT(strtok)(char *psz, const char *pszDelimiters)
+{
+ PRTNOCRTTHREADDATA pNoCrtData = rtNoCrtThreadDataGet();
+ if (pNoCrtData)
+ return RT_NOCRT(strtok_r)(psz, pszDelimiters, &pNoCrtData->pszStrToken);
+
+ static char *s_pszFallback = NULL;
+ return RT_NOCRT(strtok_r)(psz, pszDelimiters, &s_pszFallback);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strtok);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-strtol.cpp b/src/VBox/Runtime/common/string/nocrt-strtol.cpp
new file mode 100644
index 00000000..a554f09b
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-strtol.cpp
@@ -0,0 +1,72 @@
+/* $Id: nocrt-strtol.cpp $ */
+/** @file
+ * IPRT - No-CRT - strtol.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/string.h>
+
+
+#undef strtol
+long RT_NOCRT(strtol)(const char *psz, char **ppszNext, int iBase)
+{
+#if LONG_BIT == 64
+ int64_t iValue = 0;
+ int rc = RTStrToInt64Ex(RTStrStripL(psz), ppszNext, (unsigned)iBase, &iValue);
+#elif LONG_BIT == 32
+ int32_t iValue = 0;
+ int rc = RTStrToInt32Ex(RTStrStripL(psz), ppszNext, (unsigned)iBase, &iValue);
+#else
+# error "Unsupported LONG_BIT value"
+#endif
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES)
+ return iValue;
+ if (rc == VWRN_NUMBER_TOO_BIG)
+ {
+ errno = ERANGE;
+ return iValue < 0 ? LONG_MIN : LONG_MAX;
+ }
+ errno = EINVAL;
+ return 0;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strtol);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-strtoll.cpp b/src/VBox/Runtime/common/string/nocrt-strtoll.cpp
new file mode 100644
index 00000000..f2c5afb2
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-strtoll.cpp
@@ -0,0 +1,69 @@
+/* $Id: nocrt-strtoll.cpp $ */
+/** @file
+ * IPRT - No-CRT - strtoll.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/string.h>
+
+
+#undef strtoll
+long long RT_NOCRT(strtoll)(const char *psz, char **ppszNext, int iBase)
+{
+#if LLONG_BIT == 64
+ int64_t iValue = 0;
+ int rc = RTStrToInt64Ex(RTStrStripL(psz), ppszNext, (unsigned)iBase, &iValue);
+#else
+# error "Unsupported LLONG_BIT value"
+#endif
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES)
+ return iValue;
+ if (rc == VWRN_NUMBER_TOO_BIG)
+ {
+ errno = ERANGE;
+ return iValue < 0 ? LONG_MIN : LONG_MAX;
+ }
+ errno = EINVAL;
+ return 0;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strtoll);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-strtoul.cpp b/src/VBox/Runtime/common/string/nocrt-strtoul.cpp
new file mode 100644
index 00000000..68d2e7e8
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-strtoul.cpp
@@ -0,0 +1,73 @@
+/* $Id: nocrt-strtoul.cpp $ */
+/** @file
+ * IPRT - No-CRT - strtoul.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+
+#undef strtoul
+unsigned long RT_NOCRT(strtoul)(const char *psz, char **ppszNext, int iBase)
+{
+#if LONG_BIT == 64
+ uint64_t uValue = 0;
+ int rc = RTStrToUInt64Ex(RTStrStripL(psz), ppszNext, (unsigned)iBase, &uValue);
+#elif LONG_BIT == 32
+ uint32_t uValue = 0;
+ int rc = RTStrToUInt32Ex(RTStrStripL(psz), ppszNext, (unsigned)iBase, &uValue);
+#else
+# error "Unsupported LONG_BIT value"
+#endif
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES || rc == VWRN_NEGATIVE_UNSIGNED)
+ return uValue;
+ if (rc == VWRN_NUMBER_TOO_BIG)
+ {
+ errno = ERANGE;
+ return ULONG_MAX;
+ }
+ errno = EINVAL;
+ return 0;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strtoul);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-strtoull.cpp b/src/VBox/Runtime/common/string/nocrt-strtoull.cpp
new file mode 100644
index 00000000..8d21d866
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-strtoull.cpp
@@ -0,0 +1,70 @@
+/* $Id: nocrt-strtoull.cpp $ */
+/** @file
+ * IPRT - No-CRT - strtoull.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdlib.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/nocrt/errno.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+
+
+#undef strtoull
+unsigned long long RT_NOCRT(strtoull)(const char *psz, char **ppszNext, int iBase)
+{
+#if LLONG_BIT == 64
+ uint64_t uValue = 0;
+ int rc = RTStrToUInt64Ex(RTStrStripL(psz), ppszNext, (unsigned)iBase, &uValue);
+#else
+# error "Unsupported LLONG_BIT value"
+#endif
+ if (rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES || rc == VWRN_NEGATIVE_UNSIGNED)
+ return uValue;
+ if (rc == VWRN_NUMBER_TOO_BIG)
+ {
+ errno = ERANGE;
+ return ULONG_MAX;
+ }
+ errno = EINVAL;
+ return 0;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strtoull);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-vscprintf.cpp b/src/VBox/Runtime/common/string/nocrt-vscprintf.cpp
new file mode 100644
index 00000000..bb9e73e1
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-vscprintf.cpp
@@ -0,0 +1,65 @@
+/* $Id: nocrt-vscprintf.cpp $ */
+/** @file
+ * IPRT - No-CRT Strings, vscprintf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/iprt.h"
+#include <iprt/nocrt/stdio.h>
+#include <iprt/nocrt/limits.h>
+#include <iprt/string.h>
+
+
+/**
+ * @callback_method_impl{FNRTSTROUTPUT, Dummy that only return cbChars.}
+ */
+static DECLCALLBACK(size_t) rtStrDummyOutputter(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ RT_NOREF(pvArg, pachChars);
+ return cbChars;
+}
+
+
+#undef vscprintf
+int RT_NOCRT(vscprintf)(const char *pszFormat, va_list va)
+{
+ size_t cchOutput = RTStrFormatV(rtStrDummyOutputter, NULL, NULL, NULL, pszFormat, va);
+ return cchOutput < (size_t)INT_MAX ? (int)cchOutput : INT_MAX - 1;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(vscprintf);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-vsnprintf.cpp b/src/VBox/Runtime/common/string/nocrt-vsnprintf.cpp
new file mode 100644
index 00000000..26a77aa8
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-vsnprintf.cpp
@@ -0,0 +1,63 @@
+/* $Id: nocrt-vsnprintf.cpp $ */
+/** @file
+ * IPRT - No-CRT Strings, vsnprintf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/iprt.h"
+#include <iprt/nocrt/stdio.h>
+#include <iprt/string.h>
+
+
+#undef vsnprintf
+int RT_NOCRT(vsnprintf)(char *pszDst, size_t cbDst, const char *pszFormat, va_list va)
+{
+ char szNull[4];
+ if (pszDst && cbDst > 0)
+ { /* likely */ }
+ else
+ {
+ cbDst = sizeof(szNull);
+ pszDst = szNull;
+ }
+ ssize_t cchOutput = RTStrPrintf2V(pszDst, cbDst, pszFormat, va);
+ return cchOutput >= 0 ? cchOutput : -cchOutput;
+
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(vsnprintf);
+
diff --git a/src/VBox/Runtime/common/string/nocrt-vsscanf.cpp b/src/VBox/Runtime/common/string/nocrt-vsscanf.cpp
new file mode 100644
index 00000000..4f75cde7
--- /dev/null
+++ b/src/VBox/Runtime/common/string/nocrt-vsscanf.cpp
@@ -0,0 +1,403 @@
+/* $Id: nocrt-vsscanf.cpp $ */
+/** @file
+ * IPRT - No-CRT - Simplistic vsscanf().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define IPRT_NO_CRT_FOR_3RD_PARTY
+#include "internal/nocrt.h"
+#include <iprt/nocrt/stdio.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/stdarg.h>
+#include <iprt/string.h>
+
+
+static const char *rtNoCrtScanString(const char *pszString, size_t cchWidth, char *pszDst, int *pcMatches)
+{
+ if (pszDst)
+ *pcMatches += 1;
+ while (cchWidth-- > 0)
+ {
+ char ch = *pszString;
+ if (pszDst)
+ *pszDst++ = ch;
+ if (ch != '\0')
+ pszString++;
+ else
+ return pszString;
+ }
+ if (pszDst)
+ *pszDst = '\0';
+ return pszString;
+}
+
+
+static const char *rtNoCrtScanChars(const char *pszString, unsigned cchWidth, char *pchDst, int *pcMatches)
+{
+ if (pchDst)
+ *pcMatches += 1;
+ while (cchWidth-- > 0)
+ {
+ char ch = *pszString;
+ if (pchDst)
+ *pchDst++ = ch;
+ if (ch != '\0')
+ pszString++;
+ else
+ {
+ /** @todo how do we handle a too short strings? memset the remainder and
+ * count it as a match? */
+ if (pchDst && cchWidth > 0)
+ RT_BZERO(pchDst, cchWidth);
+ return pszString;
+ }
+ }
+ return pszString;
+}
+
+
+static void rtNoCrtStorInt(void *pvDst, char chPrefix, uint64_t uValue)
+{
+ switch (chPrefix)
+ {
+ default:
+ AssertFailed();
+ RT_FALL_THRU();
+ case '\0': *(unsigned int *)pvDst = (unsigned int)uValue; break;
+ case 'H': *(unsigned char *)pvDst = (unsigned char)uValue; break;
+ case 'h': *(unsigned short *)pvDst = (unsigned short)uValue; break;
+ case 'j': *(uint64_t *)pvDst = uValue; break;
+ case 'l': *(unsigned long *)pvDst = (unsigned long)uValue; break;
+ case 'L': *(unsigned long long *)pvDst = (unsigned long long)uValue; break;
+ case 't': *(ptrdiff_t *)pvDst = (ptrdiff_t)uValue; break;
+ case 'Z':
+ case 'z': *(size_t *)pvDst = (size_t)uValue; break;
+ }
+}
+
+
+static const char *rtNoCrtScanInt(const char *pszString, unsigned uBase, bool fSigned, char chPrefix, int cchWidth,
+ void *pvDst, int *pcMatches)
+{
+ if (cchWidth >= 0 && cchWidth < _16M)
+ uBase |= (unsigned)cchWidth << 8;
+
+ int rc;
+ if (fSigned)
+ {
+ int64_t iVal = 0;
+ rc = RTStrToInt64Ex(pszString, (char **)&pszString, uBase, &iVal);
+ if (RT_SUCCESS(rc))
+ {
+ if (pvDst)
+ {
+ rtNoCrtStorInt(pvDst, chPrefix, (uint64_t)iVal);
+ *pcMatches += 1;
+ }
+ }
+ else
+ pszString = NULL;
+ }
+ else
+ {
+ uint64_t uVal = 0;
+ rc = RTStrToUInt64Ex(pszString, (char **)&pszString, uBase, &uVal);
+ if (RT_SUCCESS(rc))
+ {
+ if (pvDst)
+ {
+ rtNoCrtStorInt(pvDst, chPrefix, uVal);
+ *pcMatches += 1;
+ }
+ }
+ else
+ pszString = NULL;
+ }
+
+ return pszString;
+}
+
+
+static const char *rtNoCrtScanFloat(const char *pszString, char chPrefix, int cchWidth, void *pvDst, int *pcMatches)
+{
+ size_t const cchMax = cchWidth > 0 ? (unsigned)cchWidth : 0;
+ int rc;
+ switch (chPrefix)
+ {
+ default:
+ case '\0':
+#ifndef RT_OS_WINDOWS /* Windows doesn't do float, only double and "long" double (same as double). */
+ rc = RTStrToFloatEx(pszString, (char **)&pszString, cchMax, (float *)pvDst);
+ break;
+#else
+ RT_FALL_THRU();
+#endif
+ case 'l':
+ rc = RTStrToDoubleEx(pszString, (char **)&pszString, cchMax, (double *)pvDst);
+ break;
+
+ case 'L':
+ rc = RTStrToLongDoubleEx(pszString, (char **)&pszString, cchMax, (long double *)pvDst);
+ break;
+ }
+ if (rc != VERR_NO_DIGITS)
+ *pcMatches += pvDst != NULL;
+ else
+ pszString = NULL;
+ return pszString;
+}
+
+
+#undef vsscanf
+int RT_NOCRT(vsscanf)(const char *pszString, const char *pszFormat, va_list va)
+{
+#ifdef RT_STRICT
+ const char * const pszFormatStart = pszFormat;
+#endif
+ const char * const pszStart = pszString;
+ int cMatches = 0;
+ char chFmt;
+ while ((chFmt = *pszFormat++) != '\0')
+ {
+ switch (chFmt)
+ {
+ default:
+ if (chFmt == *pszString)
+ pszString++;
+ else
+ return cMatches;
+ break;
+
+ /*
+ * White space will match zero or more whitespace character in the
+ * source string, no specific type. So, we advance to the next no-space
+ * character in each of the string.
+ */
+ case ' ': /* See RT_C_IS_SPACE */
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\v':
+ case '\f':
+ while (RT_C_IS_SPACE(*pszFormat))
+ pszFormat++;
+ while (RT_C_IS_SPACE(*pszString))
+ pszFormat++;
+ break;
+
+ /*
+ * %[*][width][h|l|ll|L|q|t|z|Z|]type
+ */
+ case '%':
+ {
+ chFmt = *pszFormat++;
+
+ /* Escaped '%s'? */
+ if (chFmt == '%')
+ {
+ if (*pszString == '%')
+ pszString++;
+ else
+ return cMatches;
+ break;
+ }
+
+ /* Assigning or non-assigning argument? */
+ bool const fAssign = chFmt != '*';
+ if (chFmt == '*')
+ chFmt = *pszFormat++;
+
+ /* Width specifier? */
+ int cchWidth = -1;
+ if (RT_C_IS_DIGIT(chFmt))
+ {
+ cchWidth = chFmt - '0';
+ for (;;)
+ {
+ chFmt = *pszFormat++;
+ if (!RT_C_IS_DIGIT(chFmt))
+ break;
+ cchWidth *= 10;
+ cchWidth += chFmt - '0';
+ }
+ }
+
+ /* Size prefix?
+ We convert 'hh' to 'H', 'll' to 'L', and 'I64' to 'L'. The
+ latter is for MSC compatibility, of course. */
+ char chPrefix = '\0';
+ switch (chFmt)
+ {
+ case 'q':
+ chPrefix = 'L';
+ RT_FALL_THRU();
+ case 'L':
+ case 'j':
+ case 'z':
+ case 'Z':
+ case 't':
+ chPrefix = chFmt;
+ chFmt = *pszFormat++;
+ break;
+ case 'l':
+ case 'h':
+ chPrefix = chFmt;
+ chFmt = *pszFormat++;
+ if (chPrefix != 'L' && chFmt == chPrefix)
+ {
+ chPrefix = chPrefix == 'l' ? 'L' : 'H';
+ chFmt = *pszFormat++;
+ }
+ break;
+ case 'I':
+ if (pszFormat[0] == '6' && pszFormat[1] == '4')
+ {
+ chPrefix = 'L';
+ pszFormat += 2;
+ }
+ break;
+ }
+
+ /* Now for the type. */
+ switch (chFmt)
+ {
+ case 'p':
+ chPrefix = 'j';
+ RT_FALL_THRU();
+ case 'd':
+ case 'i':
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X':
+ {
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+ void *pvDst = NULL;
+ if (fAssign)
+ pvDst = va_arg(va, void *); /* This ought to work most place... Probably not standard conforming. */
+ pszString = rtNoCrtScanInt(pszString,
+ chFmt == 'i' ? 0 : chFmt == 'd' || chFmt == 'u' ? 10 : chFmt == 'o' ? 8 : 16,
+ chFmt == 'd' || chFmt == 'i' /* fSigned */,
+ chPrefix, cchWidth, pvDst, &cMatches);
+ if (!pszString)
+ return cMatches;
+ break;
+ }
+
+ case 'a':
+ case 'A':
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'F':
+ case 'g':
+ case 'G':
+ {
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+ /* Note! We don't really give a hoot what input format type we're given,
+ we keep and open mind and acceept whatever we find that looks like
+ floating point. This is doubtfully standard compliant. */
+ void *pvDst = NULL;
+ if (fAssign)
+ pvDst = va_arg(va, void *); /* This ought to work most place... Probably not standard conforming. */
+ pszString = rtNoCrtScanFloat(pszString, chPrefix, cchWidth, pvDst, &cMatches);
+ if (!pszString)
+ return cMatches;
+ break;
+ }
+
+ case 'n':
+ {
+ if (fAssign)
+ {
+ void *pvDst = va_arg(va, void *);
+ rtNoCrtStorInt(pvDst, chPrefix, (size_t)(pszString - pszStart));
+ }
+ break;
+ }
+
+ case 'c':
+ if (chPrefix != 'l' && chPrefix != 'L')
+ {
+ /* no whitespace skipped for %c */
+ char *pchDst = NULL;
+ if (fAssign)
+ pchDst = va_arg(va, char *);
+ pszString = rtNoCrtScanChars(pszString, cchWidth < 0 ? 1U : (unsigned)cchWidth, pchDst, &cMatches);
+ break;
+ }
+ RT_FALL_THRU();
+ case 'C':
+ AssertMsgFailedReturn(("Unsupported sscanf type: C/Lc (%s)\n", pszFormatStart), cMatches);
+ break;
+
+ case 's':
+ if (chPrefix != 'l' && chPrefix != 'L')
+ {
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+ char *pszDst = NULL;
+ if (fAssign)
+ pszDst = va_arg(va, char *);
+ pszString = rtNoCrtScanString(pszString, cchWidth < 0 ? RTSTR_MAX : (unsigned)cchWidth,
+ pszDst, &cMatches);
+ }
+ RT_FALL_THRU();
+ case 'S':
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+ AssertMsgFailedReturn(("Unsupported sscanf type: S/Ls (%s)\n", pszFormatStart), cMatches);
+ break;
+
+ default:
+ AssertMsgFailedReturn(("Unsupported sscanf type: %c (%s)\n", chFmt, pszFormatStart), cMatches);
+ }
+ break;
+ }
+ }
+ }
+ return cMatches;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(vsscanf);
+
diff --git a/src/VBox/Runtime/common/string/simplepattern.cpp b/src/VBox/Runtime/common/string/simplepattern.cpp
new file mode 100644
index 00000000..cc08fe57
--- /dev/null
+++ b/src/VBox/Runtime/common/string/simplepattern.cpp
@@ -0,0 +1,204 @@
+/* $Id: simplepattern.cpp $ */
+/** @file
+ * IPRT - RTStrSimplePattern.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+
+
+RTDECL(bool) RTStrSimplePatternMatch(const char *pszPattern, const char *pszString)
+{
+#if 0
+ return RTStrSimplePatternNMatch(pszPattern, RTSTR_MAX, pszString, RTSTR_MAX);
+#else
+ /* ASSUMES ASCII / UTF-8 */
+ for (;;)
+ {
+ char chPat = *pszPattern;
+ switch (chPat)
+ {
+ default:
+ if (*pszString != chPat)
+ return false;
+ break;
+
+ case '*':
+ {
+ /* collapse '*' and '?', they are superfluous */
+ while ((chPat = *++pszPattern) == '*' || chPat == '?')
+ /* nothing */;
+
+ /* if no more pattern, we're done now. */
+ if (!chPat)
+ return true;
+
+ /* find chPat in the string and try get a match on the remaining pattern. */
+ for (;;)
+ {
+ char chStr = *pszString++;
+ if ( chStr == chPat
+ && RTStrSimplePatternMatch(pszPattern + 1, pszString))
+ return true;
+ if (!chStr)
+ return false;
+ }
+ /* won't ever get here */
+ break;
+ }
+
+ case '?':
+ if (!*pszString)
+ return false;
+ break;
+
+ case '\0':
+ return !*pszString;
+ }
+ pszString++;
+ pszPattern++;
+ }
+#endif
+}
+RT_EXPORT_SYMBOL(RTStrSimplePatternMatch);
+
+
+RTDECL(bool) RTStrSimplePatternNMatch(const char *pszPattern, size_t cchPattern,
+ const char *pszString, size_t cchString)
+{
+ /* ASSUMES ASCII / UTF-8 */
+ for (;;)
+ {
+ char chPat = cchPattern ? *pszPattern : '\0';
+ switch (chPat)
+ {
+ default:
+ {
+ char chStr = cchString ? *pszString : '\0';
+ if (chStr != chPat)
+ return false;
+ break;
+ }
+
+ case '*':
+ {
+ /* Collapse '*' and '?', they are superfluous. End of the pattern == match. */
+ do
+ {
+ if (!--cchPattern)
+ return true;
+ chPat = *++pszPattern;
+ } while (chPat == '*' || chPat == '?');
+ if (!chPat)
+ return true;
+
+ /* Find chPat in the string and try get a match on the remaining pattern. */
+ for (;;)
+ {
+ if (!cchString--)
+ return false;
+ char chStr = *pszString++;
+ if ( chStr == chPat
+ && RTStrSimplePatternNMatch(pszPattern + 1, cchPattern - 1, pszString, cchString))
+ return true;
+ if (!chStr)
+ return false;
+ }
+ /* won't ever get here */
+ break;
+ }
+
+ case '?':
+ if (!cchString || !*pszString)
+ return false;
+ break;
+
+ case '\0':
+ return cchString == 0 || !*pszString;
+ }
+
+ /* advance */
+ pszString++;
+ cchString--;
+ pszPattern++;
+ cchPattern--;
+ }
+}
+RT_EXPORT_SYMBOL(RTStrSimplePatternNMatch);
+
+
+RTDECL(bool) RTStrSimplePatternMultiMatch(const char *pszPatterns, size_t cchPatterns,
+ const char *pszString, size_t cchString,
+ size_t *poffMatchingPattern)
+{
+ const char *pszCur = pszPatterns;
+ while (*pszCur && cchPatterns)
+ {
+ /*
+ * Find the end of the current pattern.
+ */
+ unsigned char ch = '\0';
+ const char *pszEnd = pszCur;
+ while (cchPatterns && (ch = *pszEnd) != '\0' && ch != '|')
+ cchPatterns--, pszEnd++;
+
+ /*
+ * Try match it.
+ */
+ if (RTStrSimplePatternNMatch(pszCur, pszEnd - pszCur, pszString, cchString))
+ {
+ if (poffMatchingPattern)
+ *poffMatchingPattern = pszCur - pszPatterns;
+ return true;
+ }
+
+ /* advance */
+ if (!ch || !cchPatterns)
+ break;
+ cchPatterns--;
+ pszCur = pszEnd + 1;
+ }
+
+ if (poffMatchingPattern)
+ *poffMatchingPattern = RTSTR_MAX;
+ return false;
+}
+RT_EXPORT_SYMBOL(RTStrSimplePatternMultiMatch);
+
diff --git a/src/VBox/Runtime/common/string/straprintf.cpp b/src/VBox/Runtime/common/string/straprintf.cpp
new file mode 100644
index 00000000..cddb286d
--- /dev/null
+++ b/src/VBox/Runtime/common/string/straprintf.cpp
@@ -0,0 +1,207 @@
+/* $Id: straprintf.cpp $ */
+/** @file
+ * IPRT - Allocating String Formatters.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** strallocoutput() argument structure. */
+typedef struct STRALLOCARG
+{
+ /** Pointer to current buffer position. */
+ char *psz;
+ /** Number of bytes left in the buffer - not including the trailing zero. */
+ size_t cch;
+ /** Pointer to the start of the buffer. */
+ char *pszBuffer;
+ /** The number of bytes in the buffer. */
+ size_t cchBuffer;
+ /** Set if the buffer was allocated using RTMemRealloc(). If clear
+ * pszBuffer points to the initial stack buffer. */
+ bool fAllocated;
+ /** Allocation tag used for statistics and such. */
+ const char *pszTag;
+} STRALLOCARG;
+/** Pointer to a strallocoutput() argument structure. */
+typedef STRALLOCARG *PSTRALLOCARG;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(size_t) strallocoutput(void *pvArg, const char *pachChars, size_t cbChars);
+
+
+/**
+ * Output callback.
+ *
+ * @returns number of bytes written.
+ * @param pvArg Pointer to a STRBUFARG structure.
+ * @param pachChars Pointer to an array of utf-8 characters.
+ * @param cbChars Number of bytes in the character array pointed to by pachChars.
+ */
+static DECLCALLBACK(size_t) strallocoutput(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ PSTRALLOCARG pArg = (PSTRALLOCARG)pvArg;
+ if (pArg->psz)
+ {
+ /*
+ * The fast path
+ */
+ if (cbChars <= pArg->cch)
+ {
+ if (cbChars)
+ {
+ memcpy(pArg->psz, pachChars, cbChars);
+ pArg->cch -= cbChars;
+ pArg->psz += cbChars;
+ }
+ *pArg->psz = '\0';
+ return cbChars;
+ }
+
+ /*
+ * Need to (re)allocate the buffer.
+ */
+ size_t cbAdded = RT_MIN(pArg->cchBuffer, _1M);
+ if (cbAdded <= cbChars)
+ cbAdded = RT_ALIGN_Z(cbChars, _4K);
+ if (cbAdded <= _1G)
+ {
+ char *pszBuffer = (char *)RTMemReallocTag(pArg->fAllocated ? pArg->pszBuffer : NULL,
+ cbAdded + pArg->cchBuffer, pArg->pszTag);
+ if (pszBuffer)
+ {
+ size_t off = pArg->psz - pArg->pszBuffer;
+ if (!pArg->fAllocated)
+ {
+ memcpy(pszBuffer, pArg->pszBuffer, off);
+ pArg->fAllocated = true;
+ }
+
+ pArg->pszBuffer = pszBuffer;
+ pArg->cchBuffer += cbAdded;
+ pArg->psz = pszBuffer + off;
+ pArg->cch += cbAdded;
+
+ if (cbChars)
+ {
+ memcpy(pArg->psz, pachChars, cbChars);
+ pArg->cch -= cbChars;
+ pArg->psz += cbChars;
+ }
+ *pArg->psz = '\0';
+ return cbChars;
+ }
+ /* else allocation failure */
+ }
+ /* else wrap around */
+
+ /* failure */
+ pArg->psz = NULL;
+ }
+ return 0;
+}
+
+
+RTDECL(int) RTStrAPrintfVTag(char **ppszBuffer, const char *pszFormat, va_list args, const char *pszTag)
+{
+#ifdef IN_RING3
+ char szBuf[2048];
+#else
+ char szBuf[256];
+#endif
+ STRALLOCARG Arg;
+ Arg.fAllocated = false;
+ Arg.cchBuffer = sizeof(szBuf);
+ Arg.pszBuffer = szBuf;
+ Arg.cch = sizeof(szBuf) - 1;
+ Arg.psz = szBuf;
+ Arg.pszTag = pszTag;
+ szBuf[0] = '\0';
+ int cbRet = (int)RTStrFormatV(strallocoutput, &Arg, NULL, NULL, pszFormat, args);
+ if (Arg.psz)
+ {
+ if (!Arg.fAllocated)
+ {
+ /* duplicate the string in szBuf */
+ Assert(Arg.pszBuffer == szBuf);
+ char *psz = (char *)RTMemAllocTag(cbRet + 1, pszTag);
+ if (psz)
+ memcpy(psz, szBuf, cbRet + 1);
+ *ppszBuffer = psz;
+ }
+ else
+ {
+ /* adjust the allocated buffer */
+ char *psz = (char *)RTMemReallocTag(Arg.pszBuffer, cbRet + 1, pszTag);
+ *ppszBuffer = psz ? psz : Arg.pszBuffer;
+ }
+ }
+ else
+ {
+ /* allocation error */
+ *ppszBuffer = NULL;
+ cbRet = -1;
+
+ /* free any allocated buffer */
+ if (Arg.fAllocated)
+ RTMemFree(Arg.pszBuffer);
+ }
+
+ return cbRet;
+}
+RT_EXPORT_SYMBOL(RTStrAPrintfVTag);
+
+
+RTDECL(char *) RTStrAPrintf2VTag(const char *pszFormat, va_list args, const char *pszTag)
+{
+ char *pszBuffer;
+ RTStrAPrintfVTag(&pszBuffer, pszFormat, args, pszTag);
+ return pszBuffer;
+}
+RT_EXPORT_SYMBOL(RTStrAPrintf2VTag);
+
diff --git a/src/VBox/Runtime/common/string/strcache.cpp b/src/VBox/Runtime/common/string/strcache.cpp
new file mode 100644
index 00000000..a75515b8
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strcache.cpp
@@ -0,0 +1,1239 @@
+/* $Id: strcache.cpp $ */
+/** @file
+ * IPRT - String Cache.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/strcache.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloca.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/errcore.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+
+#include "internal/strhash.h"
+#include "internal/magics.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Special NIL pointer for the hash table. It differs from NULL in that it is
+ * a valid hash table entry when doing a lookup. */
+#define PRTSTRCACHEENTRY_NIL ((PRTSTRCACHEENTRY)~(uintptr_t)1)
+
+/** Calcuates the increment when handling a collision.
+ * The current formula makes sure it's always odd so we cannot possibly end
+ * up a cyclic loop with an even sized table. It also takes more bits from
+ * the length part. */
+#define RTSTRCACHE_COLLISION_INCR(uHashLen) ( ((uHashLen >> 8) | 1) )
+
+/** The initial hash table size. Must be power of two. */
+#define RTSTRCACHE_INITIAL_HASH_SIZE 512
+/** The hash table growth factor. */
+#define RTSTRCACHE_HASH_GROW_FACTOR 4
+
+/**
+ * The RTSTRCACHEENTRY size threshold at which we stop using our own allocator
+ * and switch to the application heap, expressed as a power of two.
+ *
+ * Using a 1KB as a reasonable limit here.
+ */
+#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+# define RTSTRCACHE_HEAP_THRESHOLD_BIT 10
+#else
+# define RTSTRCACHE_HEAP_THRESHOLD_BIT 9
+#endif
+/** The RTSTRCACHE_HEAP_THRESHOLD_BIT as a byte limit. */
+#define RTSTRCACHE_HEAP_THRESHOLD RT_BIT_32(RTSTRCACHE_HEAP_THRESHOLD_BIT)
+/** Big (heap) entry size alignment. */
+#define RTSTRCACHE_HEAP_ENTRY_SIZE_ALIGN 16
+
+#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+/**
+ * The RTSTRCACHEENTRY size threshold at which we start using the merge free
+ * list for allocations, expressed as a power of two.
+ */
+# define RTSTRCACHE_MERGED_THRESHOLD_BIT 6
+
+/** The number of bytes (power of two) that the merged allocation lists should
+ * be grown by. Must be much greater than RTSTRCACHE_MERGED_THRESHOLD. */
+# define RTSTRCACHE_MERGED_GROW_SIZE _32K
+#endif
+
+/** The number of bytes (power of two) that the fixed allocation lists should
+ * be grown by. */
+#define RTSTRCACHE_FIXED_GROW_SIZE _32K
+
+/** The number of fixed sized lists. */
+#define RTSTRCACHE_NUM_OF_FIXED_SIZES 12
+
+
+/** Validates a string cache handle, translating RTSTRCACHE_DEFAULT when found,
+ * and returns rc if not valid. */
+#define RTSTRCACHE_VALID_RETURN_RC(pStrCache, rc) \
+ do { \
+ if ((pStrCache) == RTSTRCACHE_DEFAULT) \
+ { \
+ int rcOnce = RTOnce(&g_rtStrCacheOnce, rtStrCacheInitDefault, NULL); \
+ if (RT_FAILURE(rcOnce)) \
+ return (rc); \
+ (pStrCache) = g_hrtStrCacheDefault; \
+ } \
+ else \
+ { \
+ AssertPtrReturn((pStrCache), (rc)); \
+ AssertReturn((pStrCache)->u32Magic == RTSTRCACHE_MAGIC, (rc)); \
+ } \
+ } while (0)
+
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * String cache entry.
+ */
+typedef struct RTSTRCACHEENTRY
+{
+ /** The number of references. */
+ uint32_t volatile cRefs;
+ /** The lower 16-bit hash value. */
+ uint16_t uHash;
+ /** The string length (excluding the terminator).
+ * If this is set to RTSTRCACHEENTRY_BIG_LEN, this is a BIG entry
+ * (RTSTRCACHEBIGENTRY). */
+ uint16_t cchString;
+ /** The string. */
+ char szString[8];
+} RTSTRCACHEENTRY;
+AssertCompileSize(RTSTRCACHEENTRY, 16);
+/** Pointer to a string cache entry. */
+typedef RTSTRCACHEENTRY *PRTSTRCACHEENTRY;
+/** Pointer to a const string cache entry. */
+typedef RTSTRCACHEENTRY *PCRTSTRCACHEENTRY;
+
+/** RTSTCACHEENTRY::cchString value for big cache entries. */
+#define RTSTRCACHEENTRY_BIG_LEN UINT16_MAX
+
+/**
+ * Big string cache entry.
+ *
+ * These are allocated individually from the application heap.
+ */
+typedef struct RTSTRCACHEBIGENTRY
+{
+ /** List entry. */
+ RTLISTNODE ListEntry;
+ /** The string length. */
+ uint32_t cchString;
+ /** The full hash value / padding. */
+ uint32_t uHash;
+ /** The core entry. */
+ RTSTRCACHEENTRY Core;
+} RTSTRCACHEBIGENTRY;
+AssertCompileSize(RTSTRCACHEENTRY, 16);
+/** Pointer to a big string cache entry. */
+typedef RTSTRCACHEBIGENTRY *PRTSTRCACHEBIGENTRY;
+/** Pointer to a const big string cache entry. */
+typedef RTSTRCACHEBIGENTRY *PCRTSTRCACHEBIGENTRY;
+
+
+/**
+ * A free string cache entry.
+ */
+typedef struct RTSTRCACHEFREE
+{
+ /** Zero value indicating that it's a free entry (no refs, no hash). */
+ uint32_t uZero;
+ /** Number of free bytes. Only used for > 32 byte allocations. */
+ uint32_t cbFree;
+ /** Pointer to the next free item. */
+ struct RTSTRCACHEFREE *pNext;
+} RTSTRCACHEFREE;
+AssertCompileSize(RTSTRCACHEENTRY, 16);
+AssertCompileMembersAtSameOffset(RTSTRCACHEENTRY, cRefs, RTSTRCACHEFREE, uZero);
+AssertCompileMembersAtSameOffset(RTSTRCACHEENTRY, szString, RTSTRCACHEFREE, pNext);
+/** Pointer to a free string cache entry. */
+typedef RTSTRCACHEFREE *PRTSTRCACHEFREE;
+
+#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+
+/**
+ * A free string cache entry with merging.
+ *
+ * This differs from RTSTRCACHEFREE only in having a back pointer for more
+ * efficient list management (doubly vs. singly linked lists).
+ */
+typedef struct RTSTRCACHEFREEMERGE
+{
+ /** Marker that indicates what kind of entry this is, either . */
+ uint32_t uMarker;
+ /** Number of free bytes. Only used for > 32 byte allocations. */
+ uint32_t cbFree;
+ /** Pointer to the main node. NULL for main nodes. */
+ struct RTSTRCACHEFREEMERGE *pMain;
+ /** The free list entry. */
+ RTLISTNODE ListEntry;
+ /** Pads the size up to the minimum allocation unit for the merge list.
+ * This both defines the minimum allocation unit and simplifies pointer
+ * manipulation during merging and splitting. */
+ uint8_t abPadding[ARCH_BITS == 32 ? 44 : 32];
+} RTSTRCACHEFREEMERGE;
+AssertCompileSize(RTSTRCACHEFREEMERGE, RT_BIT_32(RTSTRCACHE_MERGED_THRESHOLD_BIT));
+/** Pointer to a free cache string in the merge list. */
+typedef RTSTRCACHEFREEMERGE *PRTSTRCACHEFREEMERGE;
+
+/** RTSTRCACHEFREEMERGE::uMarker value indicating that it's the real free chunk
+ * header. Must be something that's invalid UTF-8 for both little and big
+ * endian system. */
+# define RTSTRCACHEFREEMERGE_MAIN UINT32_C(0xfffffff1)
+/** RTSTRCACHEFREEMERGE::uMarker value indicating that it's part of a larger
+ * chunk of free memory. Must be something that's invalid UTF-8 for both little
+ * and big endian system. */
+# define RTSTRCACHEFREEMERGE_PART UINT32_C(0xfffffff2)
+
+#endif /* RTSTRCACHE_WITH_MERGED_ALLOCATOR */
+
+/**
+ * Tracking structure chunk of memory used by the 16 byte or 32 byte
+ * allocations.
+ *
+ * This occupies the first entry in the chunk.
+ */
+typedef struct RTSTRCACHECHUNK
+{
+ /** The size of the chunk. */
+ size_t cb;
+ /** Pointer to the next chunk. */
+ struct RTSTRCACHECHUNK *pNext;
+} RTSTRCACHECHUNK;
+AssertCompile(sizeof(RTSTRCACHECHUNK) <= sizeof(RTSTRCACHEENTRY));
+/** Pointer to the chunk tracking structure. */
+typedef RTSTRCACHECHUNK *PRTSTRCACHECHUNK;
+
+
+/**
+ * Cache instance data.
+ */
+typedef struct RTSTRCACHEINT
+{
+ /** The string cache magic (RTSTRCACHE_MAGIC). */
+ uint32_t u32Magic;
+ /** Ref counter for the cache handle. */
+ uint32_t volatile cRefs;
+ /** The number of strings currently entered in the cache. */
+ uint32_t cStrings;
+ /** The size of the hash table. */
+ uint32_t cHashTab;
+ /** Pointer to the hash table. */
+ PRTSTRCACHEENTRY *papHashTab;
+ /** Free list for allocations of the sizes defined by g_acbFixedLists. */
+ PRTSTRCACHEFREE apFreeLists[RTSTRCACHE_NUM_OF_FIXED_SIZES];
+#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+ /** Free lists based on */
+ RTLISTANCHOR aMergedFreeLists[RTSTRCACHE_HEAP_THRESHOLD_BIT - RTSTRCACHE_MERGED_THRESHOLD_BIT + 1];
+#endif
+ /** List of allocated memory chunks. */
+ PRTSTRCACHECHUNK pChunkList;
+ /** List of big cache entries. */
+ RTLISTANCHOR BigEntryList;
+
+ /** @name Statistics
+ * @{ */
+ /** The total size of all chunks. */
+ size_t cbChunks;
+ /** The total length of all the strings, terminators included. */
+ size_t cbStrings;
+ /** The total size of all the big entries. */
+ size_t cbBigEntries;
+ /** Hash collisions. */
+ uint32_t cHashCollisions;
+ /** Secondary hash collisions. */
+ uint32_t cHashCollisions2;
+ /** The number of inserts to compare cHashCollisions to. */
+ uint32_t cHashInserts;
+ /** The number of rehashes. */
+ uint32_t cRehashes;
+ /** @} */
+
+ /** Critical section protecting the cache structures. */
+ RTCRITSECT CritSect;
+} RTSTRCACHEINT;
+/** Pointer to a cache instance. */
+typedef RTSTRCACHEINT *PRTSTRCACHEINT;
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The entry sizes of the fixed lists (RTSTRCACHEINT::apFreeLists). */
+static const uint32_t g_acbFixedLists[RTSTRCACHE_NUM_OF_FIXED_SIZES] =
+{
+ 16, 32, 48, 64, 96, 128, 192, 256, 320, 384, 448, 512
+};
+
+/** Init once for the default string cache. */
+static RTONCE g_rtStrCacheOnce = RTONCE_INITIALIZER;
+/** The default string cache. */
+static RTSTRCACHE g_hrtStrCacheDefault = NIL_RTSTRCACHE;
+
+
+/** @callback_method_impl{FNRTONCE, Initializes g_hrtStrCacheDefault} */
+static DECLCALLBACK(int) rtStrCacheInitDefault(void *pvUser)
+{
+ NOREF(pvUser);
+ return RTStrCacheCreate(&g_hrtStrCacheDefault, "Default");
+}
+
+
+RTDECL(int) RTStrCacheCreate(PRTSTRCACHE phStrCache, const char *pszName)
+{
+ int rc = VERR_NO_MEMORY;
+ PRTSTRCACHEINT pThis = (PRTSTRCACHEINT)RTMemAllocZ(sizeof(*pThis));
+ if (pThis)
+ {
+ pThis->cHashTab = RTSTRCACHE_INITIAL_HASH_SIZE;
+ pThis->papHashTab = (PRTSTRCACHEENTRY*)RTMemAllocZ(sizeof(pThis->papHashTab[0]) * pThis->cHashTab);
+ if (pThis->papHashTab)
+ {
+ rc = RTCritSectInit(&pThis->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pThis->BigEntryList);
+#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aMergedFreeLists); i++)
+ RTListInit(&pThis->aMergedFreeLists[i]);
+#endif
+ pThis->cRefs = 1;
+ pThis->u32Magic = RTSTRCACHE_MAGIC;
+
+ *phStrCache = pThis;
+ return VINF_SUCCESS;
+ }
+ RTMemFree(pThis->papHashTab);
+ }
+ RTMemFree(pThis);
+ }
+
+ RT_NOREF_PV(pszName);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrCacheCreate);
+
+
+RTDECL(int) RTStrCacheDestroy(RTSTRCACHE hStrCache)
+{
+ if ( hStrCache == NIL_RTSTRCACHE
+ || hStrCache == RTSTRCACHE_DEFAULT)
+ return VINF_SUCCESS;
+
+ PRTSTRCACHEINT pThis = hStrCache;
+ RTSTRCACHE_VALID_RETURN_RC(pThis, VERR_INVALID_HANDLE);
+
+ /*
+ * Invalidate it. Enter the crit sect just to be on the safe side.
+ */
+ AssertReturn(ASMAtomicCmpXchgU32(&pThis->u32Magic, RTSTRCACHE_MAGIC_DEAD, RTSTRCACHE_MAGIC), VERR_INVALID_HANDLE);
+ RTCritSectEnter(&pThis->CritSect);
+ Assert(pThis->cRefs == 1);
+
+ PRTSTRCACHECHUNK pChunk;
+ while ((pChunk = pThis->pChunkList) != NULL)
+ {
+ pThis->pChunkList = pChunk->pNext;
+ RTMemPageFree(pChunk, pChunk->cb);
+ }
+
+ RTMemFree(pThis->papHashTab);
+ pThis->papHashTab = NULL;
+ pThis->cHashTab = 0;
+
+ PRTSTRCACHEBIGENTRY pCur, pNext;
+ RTListForEachSafe(&pThis->BigEntryList, pCur, pNext, RTSTRCACHEBIGENTRY, ListEntry)
+ {
+ RTMemFree(pCur);
+ }
+
+ RTCritSectLeave(&pThis->CritSect);
+ RTCritSectDelete(&pThis->CritSect);
+
+ RTMemFree(pThis);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTStrCacheDestroy);
+
+
+/**
+ * Selects the fixed free list index for a given minimum entry size.
+ *
+ * @returns Free list index.
+ * @param cbMin Minimum entry size.
+ */
+DECLINLINE(uint32_t) rtStrCacheSelectFixedList(uint32_t cbMin)
+{
+ Assert(cbMin <= g_acbFixedLists[RT_ELEMENTS(g_acbFixedLists) - 1]);
+ unsigned i = 0;
+ while (cbMin > g_acbFixedLists[i])
+ i++;
+ return i;
+}
+
+
+#ifdef RT_STRICT
+# define RTSTRCACHE_CHECK(a_pThis) do { rtStrCacheCheck(pThis); } while (0)
+/**
+ * Internal cache check.
+ */
+static void rtStrCacheCheck(PRTSTRCACHEINT pThis)
+{
+# ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+ for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aMergedFreeLists); i++)
+ {
+ PRTSTRCACHEFREEMERGE pFree;
+ RTListForEach(&pThis->aMergedFreeLists[i], pFree, RTSTRCACHEFREEMERGE, ListEntry)
+ {
+ Assert(pFree->uMarker == RTSTRCACHEFREEMERGE_MAIN);
+ Assert(pFree->cbFree > 0);
+ Assert(RT_ALIGN_32(pFree->cbFree, sizeof(*pFree)) == pFree->cbFree);
+ }
+ }
+# endif
+ RT_NOREF_PV(pThis);
+}
+#else
+# define RTSTRCACHE_CHECK(a_pThis) do { } while (0)
+#endif
+
+
+/**
+ * Finds the first empty hash table entry given a hash+length value.
+ *
+ * ASSUMES that the hash table isn't full.
+ *
+ * @returns Hash table index.
+ * @param pThis The string cache instance.
+ * @param uHashLen The hash + length (not RTSTRCACHEENTRY_BIG_LEN).
+ */
+static uint32_t rtStrCacheFindEmptyHashTabEntry(PRTSTRCACHEINT pThis, uint32_t uHashLen)
+{
+ uint32_t iHash = uHashLen % pThis->cHashTab;
+ for (;;)
+ {
+ PRTSTRCACHEENTRY pEntry = pThis->papHashTab[iHash];
+ if (pEntry == NULL || pEntry == PRTSTRCACHEENTRY_NIL)
+ return iHash;
+
+ /* Advance. */
+ iHash += RTSTRCACHE_COLLISION_INCR(uHashLen);
+ iHash %= pThis->cHashTab;
+ }
+}
+
+/**
+ * Grows the hash table.
+ *
+ * @returns vINF_SUCCESS or VERR_NO_MEMORY.
+ * @param pThis The string cache instance.
+ */
+static int rtStrCacheGrowHashTab(PRTSTRCACHEINT pThis)
+{
+ /*
+ * Allocate a new hash table two times the size of the old one.
+ */
+ uint32_t cNew = pThis->cHashTab * RTSTRCACHE_HASH_GROW_FACTOR;
+ PRTSTRCACHEENTRY *papNew = (PRTSTRCACHEENTRY *)RTMemAllocZ(sizeof(papNew[0]) * cNew);
+ if (papNew == NULL)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Install the new table and move the items from the old table and into the new one.
+ */
+ PRTSTRCACHEENTRY *papOld = pThis->papHashTab;
+ uint32_t iOld = pThis->cHashTab;
+
+ pThis->papHashTab = papNew;
+ pThis->cHashTab = cNew;
+ pThis->cRehashes++;
+
+ while (iOld-- > 0)
+ {
+ PRTSTRCACHEENTRY pEntry = papOld[iOld];
+ if (pEntry != NULL && pEntry != PRTSTRCACHEENTRY_NIL)
+ {
+ uint32_t cchString = pEntry->cchString;
+ if (cchString == RTSTRCACHEENTRY_BIG_LEN)
+ cchString = RT_FROM_MEMBER(pEntry, RTSTRCACHEBIGENTRY, Core)->cchString;
+
+ uint32_t iHash = rtStrCacheFindEmptyHashTabEntry(pThis, RT_MAKE_U32(pEntry->uHash, cchString));
+ pThis->papHashTab[iHash] = pEntry;
+ }
+ }
+
+ /*
+ * Free the old hash table.
+ */
+ RTMemFree(papOld);
+ return VINF_SUCCESS;
+}
+
+#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+
+/**
+ * Link/Relink into the free right list.
+ *
+ * @param pThis The string cache instance.
+ * @param pFree The free string entry.
+ */
+static void rtStrCacheRelinkMerged(PRTSTRCACHEINT pThis, PRTSTRCACHEFREEMERGE pFree)
+{
+ Assert(pFree->uMarker == RTSTRCACHEFREEMERGE_MAIN);
+ Assert(pFree->cbFree > 0);
+ Assert(RT_ALIGN_32(pFree->cbFree, sizeof(*pFree)) == pFree->cbFree);
+
+ if (!RTListIsEmpty(&pFree->ListEntry))
+ RTListNodeRemove(&pFree->ListEntry);
+
+ uint32_t iList = (ASMBitLastSetU32(pFree->cbFree) - 1) - RTSTRCACHE_MERGED_THRESHOLD_BIT;
+ if (iList >= RT_ELEMENTS(pThis->aMergedFreeLists))
+ iList = RT_ELEMENTS(pThis->aMergedFreeLists) - 1;
+
+ RTListPrepend(&pThis->aMergedFreeLists[iList], &pFree->ListEntry);
+}
+
+
+/**
+ * Allocate a cache entry from the merged free lists.
+ *
+ * @returns Pointer to the cache entry on success, NULL on allocation error.
+ * @param pThis The string cache instance.
+ * @param uHash The full hash of the string.
+ * @param pchString The string.
+ * @param cchString The string length.
+ * @param cbEntry The required entry size.
+ */
+static PRTSTRCACHEENTRY rtStrCacheAllocMergedEntry(PRTSTRCACHEINT pThis, uint32_t uHash,
+ const char *pchString, uint32_t cchString, uint32_t cbEntry)
+{
+ cbEntry = RT_ALIGN_32(cbEntry, sizeof(RTSTRCACHEFREEMERGE));
+ Assert(cbEntry > cchString);
+
+ /*
+ * Search the list heads first.
+ */
+ PRTSTRCACHEFREEMERGE pFree = NULL;
+
+ uint32_t iList = ASMBitLastSetU32(cbEntry) - 1;
+ if (!RT_IS_POWER_OF_TWO(cbEntry))
+ iList++;
+ iList -= RTSTRCACHE_MERGED_THRESHOLD_BIT;
+
+ while (iList < RT_ELEMENTS(pThis->aMergedFreeLists))
+ {
+ pFree = RTListGetFirst(&pThis->aMergedFreeLists[iList], RTSTRCACHEFREEMERGE, ListEntry);
+ if (pFree)
+ {
+ /*
+ * Found something. Should we we split it? We split from the end
+ * to avoid having to update all the sub entries.
+ */
+ Assert(pFree->uMarker == RTSTRCACHEFREEMERGE_MAIN);
+ Assert(pFree->cbFree >= cbEntry);
+ Assert(RT_ALIGN_32(pFree->cbFree, sizeof(*pFree)) == pFree->cbFree);
+
+ if (pFree->cbFree == cbEntry)
+ RTListNodeRemove(&pFree->ListEntry);
+ else
+ {
+ uint32_t cRemainder = (pFree->cbFree - cbEntry) / sizeof(*pFree);
+ PRTSTRCACHEFREEMERGE pRemainder = pFree;
+ pFree += cRemainder;
+
+ Assert((pRemainder->cbFree - cbEntry) == cRemainder * sizeof(*pFree));
+ pRemainder->cbFree = cRemainder * sizeof(*pFree);
+
+ rtStrCacheRelinkMerged(pThis, pRemainder);
+ }
+ break;
+ }
+ iList++;
+ }
+ if (!pFree)
+ {
+ /*
+ * Allocate a new block. (We could search the list below in some
+ * cases, but it's too much effort to write and execute).
+ */
+ size_t const cbChunk = RTSTRCACHE_MERGED_GROW_SIZE; AssertReturn(cbChunk > cbEntry * 2, NULL);
+ PRTSTRCACHECHUNK pChunk = (PRTSTRCACHECHUNK)RTMemPageAlloc(cbChunk);
+ if (!pChunk)
+ return NULL;
+ pChunk->cb = cbChunk;
+ pChunk->pNext = pThis->pChunkList;
+ pThis->pChunkList = pChunk;
+ pThis->cbChunks += cbChunk;
+ AssertCompile(sizeof(*pChunk) <= sizeof(*pFree));
+
+ /*
+ * Get one node for the allocation at hand.
+ */
+ pFree = (PRTSTRCACHEFREEMERGE)((uintptr_t)pChunk + sizeof(*pFree));
+
+ /*
+ * Create a free block out of the remainder (always a reminder).
+ */
+ PRTSTRCACHEFREEMERGE pNewFree = (PRTSTRCACHEFREEMERGE)((uintptr_t)pFree + cbEntry);
+ pNewFree->uMarker = RTSTRCACHEFREEMERGE_MAIN;
+ pNewFree->cbFree = cbChunk - sizeof(*pNewFree) - cbEntry; Assert(pNewFree->cbFree < cbChunk && pNewFree->cbFree > 0);
+ pNewFree->pMain = NULL;
+ RTListInit(&pNewFree->ListEntry);
+
+ uint32_t iInternalBlock = pNewFree->cbFree / sizeof(*pNewFree);
+ while (iInternalBlock-- > 1)
+ {
+ pNewFree[iInternalBlock].uMarker = RTSTRCACHEFREEMERGE_PART;
+ pNewFree[iInternalBlock].cbFree = 0;
+ pNewFree[iInternalBlock].pMain = pNewFree;
+ }
+
+ rtStrCacheRelinkMerged(pThis, pNewFree);
+ }
+
+ /*
+ * Initialize the entry. We zero all bytes we don't use so they cannot
+ * accidentally be mistaken for a free entry.
+ */
+ ASMCompilerBarrier();
+ PRTSTRCACHEENTRY pEntry = (PRTSTRCACHEENTRY)pFree;
+ pEntry->cRefs = 1;
+ pEntry->uHash = (uint16_t)uHash;
+ pEntry->cchString = (uint16_t)cchString;
+ memcpy(pEntry->szString, pchString, cchString);
+ RT_BZERO(&pEntry->szString[cchString], cbEntry - RT_UOFFSETOF(RTSTRCACHEENTRY, szString) - cchString);
+
+ RTSTRCACHE_CHECK(pThis);
+
+ return pEntry;
+}
+
+#endif /* RTSTRCACHE_WITH_MERGED_ALLOCATOR */
+
+/**
+ * Allocate a cache entry from the heap.
+ *
+ * @returns Pointer to the cache entry on success, NULL on allocation error.
+ * @param pThis The string cache instance.
+ * @param uHash The full hash of the string.
+ * @param pchString The string.
+ * @param cchString The string length.
+ */
+static PRTSTRCACHEENTRY rtStrCacheAllocHeapEntry(PRTSTRCACHEINT pThis, uint32_t uHash,
+ const char *pchString, uint32_t cchString)
+{
+ /*
+ * Allocate a heap block for storing the string. We do some size aligning
+ * here to encourage the heap to give us optimal alignment.
+ */
+ size_t cbEntry = RT_UOFFSETOF_DYN(RTSTRCACHEBIGENTRY, Core.szString[cchString + 1]);
+ PRTSTRCACHEBIGENTRY pBigEntry = (PRTSTRCACHEBIGENTRY)RTMemAlloc(RT_ALIGN_Z(cbEntry, RTSTRCACHE_HEAP_ENTRY_SIZE_ALIGN));
+ if (!pBigEntry)
+ return NULL;
+
+ /*
+ * Initialize the block.
+ */
+ RTListAppend(&pThis->BigEntryList, &pBigEntry->ListEntry);
+ pThis->cbBigEntries += cbEntry;
+ pBigEntry->cchString = cchString;
+ pBigEntry->uHash = uHash;
+ pBigEntry->Core.cRefs = 1;
+ pBigEntry->Core.uHash = (uint16_t)uHash;
+ pBigEntry->Core.cchString = RTSTRCACHEENTRY_BIG_LEN;
+ /* The following is to try avoid gcc warnings/errors regarding array bounds: */
+ char *pszDst = (char *)memcpy(pBigEntry->Core.szString, pchString, cchString);
+ pszDst[cchString] = '\0';
+ ASMCompilerBarrier();
+
+ return &pBigEntry->Core;
+}
+
+
+/**
+ * Allocate a cache entry from a fixed size free list.
+ *
+ * @returns Pointer to the cache entry on success, NULL on allocation error.
+ * @param pThis The string cache instance.
+ * @param uHash The full hash of the string.
+ * @param pchString The string.
+ * @param cchString The string length.
+ * @param iFreeList Which free list.
+ */
+static PRTSTRCACHEENTRY rtStrCacheAllocFixedEntry(PRTSTRCACHEINT pThis, uint32_t uHash,
+ const char *pchString, uint32_t cchString, uint32_t iFreeList)
+{
+ /*
+ * Get an entry from the free list. If empty, allocate another chunk of
+ * memory and split it up into free entries of the desired size.
+ */
+ PRTSTRCACHEFREE pFree = pThis->apFreeLists[iFreeList];
+ if (!pFree)
+ {
+ PRTSTRCACHECHUNK pChunk = (PRTSTRCACHECHUNK)RTMemPageAlloc(RTSTRCACHE_FIXED_GROW_SIZE);
+ if (!pChunk)
+ return NULL;
+ pChunk->cb = RTSTRCACHE_FIXED_GROW_SIZE;
+ pChunk->pNext = pThis->pChunkList;
+ pThis->pChunkList = pChunk;
+ pThis->cbChunks += RTSTRCACHE_FIXED_GROW_SIZE;
+
+ PRTSTRCACHEFREE pPrev = NULL;
+ uint32_t const cbEntry = g_acbFixedLists[iFreeList];
+ uint32_t cLeft = RTSTRCACHE_FIXED_GROW_SIZE / cbEntry - 1;
+ pFree = (PRTSTRCACHEFREE)((uintptr_t)pChunk + cbEntry);
+
+ Assert(sizeof(*pChunk) <= cbEntry);
+ Assert(sizeof(*pFree) <= cbEntry);
+ Assert(cbEntry < RTSTRCACHE_FIXED_GROW_SIZE / 16);
+
+ while (cLeft-- > 0)
+ {
+ pFree->uZero = 0;
+ pFree->cbFree = cbEntry;
+ pFree->pNext = pPrev;
+ pPrev = pFree;
+ pFree = (PRTSTRCACHEFREE)((uintptr_t)pFree + cbEntry);
+ }
+
+ Assert(pPrev);
+ pThis->apFreeLists[iFreeList] = pFree = pPrev;
+ }
+
+ /*
+ * Unlink it.
+ */
+ pThis->apFreeLists[iFreeList] = pFree->pNext;
+ ASMCompilerBarrier();
+
+ /*
+ * Initialize the entry.
+ */
+ PRTSTRCACHEENTRY pEntry = (PRTSTRCACHEENTRY)pFree;
+ pEntry->cRefs = 1;
+ pEntry->uHash = (uint16_t)uHash;
+ pEntry->cchString = (uint16_t)cchString;
+ memcpy(pEntry->szString, pchString, cchString);
+ pEntry->szString[cchString] = '\0';
+
+ return pEntry;
+}
+
+
+/**
+ * Looks up a string in the hash table.
+ *
+ * @returns Pointer to the string cache entry, NULL + piFreeHashTabEntry if not
+ * found.
+ * @param pThis The string cache instance.
+ * @param uHashLen The hash + length (not RTSTRCACHEENTRY_BIG_LEN).
+ * @param cchString The real length.
+ * @param pchString The string.
+ * @param piFreeHashTabEntry Where to store the index insertion index if NULL
+ * is returned (same as what
+ * rtStrCacheFindEmptyHashTabEntry would return).
+ * @param pcCollisions Where to return a collision counter.
+ */
+static PRTSTRCACHEENTRY rtStrCacheLookUp(PRTSTRCACHEINT pThis, uint32_t uHashLen, uint32_t cchString, const char *pchString,
+ uint32_t *piFreeHashTabEntry, uint32_t *pcCollisions)
+{
+ *piFreeHashTabEntry = UINT32_MAX;
+ *pcCollisions = 0;
+
+ uint16_t cchStringFirst = RT_UOFFSETOF_DYN(RTSTRCACHEENTRY, szString[cchString + 1]) < RTSTRCACHE_HEAP_THRESHOLD
+ ? (uint16_t)cchString : RTSTRCACHEENTRY_BIG_LEN;
+ uint32_t iHash = uHashLen % pThis->cHashTab;
+ for (;;)
+ {
+ PRTSTRCACHEENTRY pEntry = pThis->papHashTab[iHash];
+
+ /* Give up if NULL, but record the index for insertion. */
+ if (pEntry == NULL)
+ {
+ if (*piFreeHashTabEntry == UINT32_MAX)
+ *piFreeHashTabEntry = iHash;
+ return NULL;
+ }
+
+ if (pEntry != PRTSTRCACHEENTRY_NIL)
+ {
+ /* Compare. */
+ if ( pEntry->uHash == (uint16_t)uHashLen
+ && pEntry->cchString == cchStringFirst)
+ {
+ if (pEntry->cchString != RTSTRCACHEENTRY_BIG_LEN)
+ {
+ if ( !memcmp(pEntry->szString, pchString, cchString)
+ && pEntry->szString[cchString] == '\0')
+ return pEntry;
+ }
+ else
+ {
+ PRTSTRCACHEBIGENTRY pBigEntry = RT_FROM_MEMBER(pEntry, RTSTRCACHEBIGENTRY, Core);
+ if ( pBigEntry->cchString == cchString
+ && !memcmp(pBigEntry->Core.szString, pchString, cchString))
+ return &pBigEntry->Core;
+ }
+ }
+
+ if (*piFreeHashTabEntry == UINT32_MAX)
+ *pcCollisions += 1;
+ }
+ /* Record the first NIL index for insertion in case we don't get a hit. */
+ else if (*piFreeHashTabEntry == UINT32_MAX)
+ *piFreeHashTabEntry = iHash;
+
+ /* Advance. */
+ iHash += RTSTRCACHE_COLLISION_INCR(uHashLen);
+ iHash %= pThis->cHashTab;
+ }
+}
+
+
+RTDECL(const char *) RTStrCacheEnterN(RTSTRCACHE hStrCache, const char *pchString, size_t cchString)
+{
+ PRTSTRCACHEINT pThis = hStrCache;
+ RTSTRCACHE_VALID_RETURN_RC(pThis, NULL);
+
+
+ /*
+ * Calculate the hash and figure the exact string length, then look for an existing entry.
+ */
+ uint32_t const uHash = sdbmN(pchString, cchString, &cchString);
+ uint32_t const uHashLen = RT_MAKE_U32(uHash, cchString);
+ AssertReturn(cchString < _1G, NULL);
+ uint32_t const cchString32 = (uint32_t)cchString;
+
+ RTCritSectEnter(&pThis->CritSect);
+ RTSTRCACHE_CHECK(pThis);
+
+ uint32_t cCollisions;
+ uint32_t iFreeHashTabEntry;
+ PRTSTRCACHEENTRY pEntry = rtStrCacheLookUp(pThis, uHashLen, cchString32, pchString, &iFreeHashTabEntry, &cCollisions);
+ if (pEntry)
+ {
+ uint32_t cRefs = ASMAtomicIncU32(&pEntry->cRefs);
+ Assert(cRefs < UINT32_MAX / 2); NOREF(cRefs);
+ }
+ else
+ {
+ /*
+ * Allocate a new entry.
+ */
+ uint32_t cbEntry = cchString32 + 1U + RT_UOFFSETOF(RTSTRCACHEENTRY, szString);
+ if (cbEntry >= RTSTRCACHE_HEAP_THRESHOLD)
+ pEntry = rtStrCacheAllocHeapEntry(pThis, uHash, pchString, cchString32);
+#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+ else if (cbEntry >= RT_BIT_32(RTSTRCACHE_MERGED_THRESHOLD_BIT))
+ pEntry = rtStrCacheAllocMergedEntry(pThis, uHash, pchString, cchString32, cbEntry);
+#endif
+ else
+ pEntry = rtStrCacheAllocFixedEntry(pThis, uHash, pchString, cchString32,
+ rtStrCacheSelectFixedList(cbEntry));
+ if (!pEntry)
+ {
+ RTSTRCACHE_CHECK(pThis);
+ RTCritSectLeave(&pThis->CritSect);
+ return NULL;
+ }
+
+ /*
+ * Insert it into the hash table.
+ */
+ if (pThis->cHashTab - pThis->cStrings < pThis->cHashTab / 2)
+ {
+ int rc = rtStrCacheGrowHashTab(pThis);
+ if (RT_SUCCESS(rc))
+ iFreeHashTabEntry = rtStrCacheFindEmptyHashTabEntry(pThis, uHashLen);
+ else if (pThis->cHashTab - pThis->cStrings <= pThis->cHashTab / 8) /* 12.5% full => error */
+ {
+ pThis->papHashTab[iFreeHashTabEntry] = pEntry;
+ pThis->cStrings++;
+ pThis->cHashInserts++;
+ pThis->cHashCollisions += cCollisions > 0;
+ pThis->cHashCollisions2 += cCollisions > 1;
+ pThis->cbStrings += cchString32 + 1;
+ RTStrCacheRelease(hStrCache, pEntry->szString);
+
+ RTSTRCACHE_CHECK(pThis);
+ RTCritSectLeave(&pThis->CritSect);
+ return NULL;
+ }
+ }
+
+ pThis->papHashTab[iFreeHashTabEntry] = pEntry;
+ pThis->cStrings++;
+ pThis->cHashInserts++;
+ pThis->cHashCollisions += cCollisions > 0;
+ pThis->cHashCollisions2 += cCollisions > 1;
+ pThis->cbStrings += cchString32 + 1;
+ Assert(pThis->cStrings < pThis->cHashTab && pThis->cStrings > 0);
+ }
+
+ RTSTRCACHE_CHECK(pThis);
+ RTCritSectLeave(&pThis->CritSect);
+ return pEntry->szString;
+}
+RT_EXPORT_SYMBOL(RTStrCacheEnterN);
+
+
+RTDECL(const char *) RTStrCacheEnter(RTSTRCACHE hStrCache, const char *psz)
+{
+ return RTStrCacheEnterN(hStrCache, psz, strlen(psz));
+}
+RT_EXPORT_SYMBOL(RTStrCacheEnter);
+
+
+static const char *rtStrCacheEnterLowerWorker(PRTSTRCACHEINT pThis, const char *pchString, size_t cchString)
+{
+ /*
+ * Try use a dynamic heap buffer first.
+ */
+ if (cchString < 512)
+ {
+ char *pszStackBuf = (char *)alloca(cchString + 1);
+ if (pszStackBuf)
+ {
+ memcpy(pszStackBuf, pchString, cchString);
+ pszStackBuf[cchString] = '\0';
+ RTStrToLower(pszStackBuf);
+ return RTStrCacheEnterN(pThis, pszStackBuf, cchString);
+ }
+ }
+
+ /*
+ * Fall back on heap.
+ */
+ char *pszHeapBuf = (char *)RTMemTmpAlloc(cchString + 1);
+ if (!pszHeapBuf)
+ return NULL;
+ memcpy(pszHeapBuf, pchString, cchString);
+ pszHeapBuf[cchString] = '\0';
+ RTStrToLower(pszHeapBuf);
+ const char *pszRet = RTStrCacheEnterN(pThis, pszHeapBuf, cchString);
+ RTMemTmpFree(pszHeapBuf);
+ return pszRet;
+}
+
+RTDECL(const char *) RTStrCacheEnterLowerN(RTSTRCACHE hStrCache, const char *pchString, size_t cchString)
+{
+ PRTSTRCACHEINT pThis = hStrCache;
+ RTSTRCACHE_VALID_RETURN_RC(pThis, NULL);
+ return rtStrCacheEnterLowerWorker(pThis, pchString, RTStrNLen(pchString, cchString));
+}
+RT_EXPORT_SYMBOL(RTStrCacheEnterLowerN);
+
+
+RTDECL(const char *) RTStrCacheEnterLower(RTSTRCACHE hStrCache, const char *psz)
+{
+ PRTSTRCACHEINT pThis = hStrCache;
+ RTSTRCACHE_VALID_RETURN_RC(pThis, NULL);
+ return rtStrCacheEnterLowerWorker(pThis, psz, strlen(psz));
+}
+RT_EXPORT_SYMBOL(RTStrCacheEnterLower);
+
+
+RTDECL(uint32_t) RTStrCacheRetain(const char *psz)
+{
+ AssertPtr(psz);
+
+ PRTSTRCACHEENTRY pStr = RT_FROM_MEMBER(psz, RTSTRCACHEENTRY, szString);
+ Assert(!((uintptr_t)pStr & 15) || pStr->cchString == RTSTRCACHEENTRY_BIG_LEN);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pStr->cRefs);
+ Assert(cRefs > 1);
+ Assert(cRefs < UINT32_MAX / 2);
+
+ return cRefs;
+}
+RT_EXPORT_SYMBOL(RTStrCacheRetain);
+
+
+static uint32_t rtStrCacheFreeEntry(PRTSTRCACHEINT pThis, PRTSTRCACHEENTRY pStr)
+{
+ RTCritSectEnter(&pThis->CritSect);
+ RTSTRCACHE_CHECK(pThis);
+
+ /* Remove it from the hash table. */
+ uint32_t cchString = pStr->cchString == RTSTRCACHEENTRY_BIG_LEN
+ ? RT_FROM_MEMBER(pStr, RTSTRCACHEBIGENTRY, Core)->cchString
+ : pStr->cchString;
+ uint32_t uHashLen = RT_MAKE_U32(pStr->uHash, cchString);
+ uint32_t iHash = uHashLen % pThis->cHashTab;
+ if (pThis->papHashTab[iHash] == pStr)
+ pThis->papHashTab[iHash] = PRTSTRCACHEENTRY_NIL;
+ else
+ {
+ do
+ {
+ AssertBreak(pThis->papHashTab[iHash] != NULL);
+ iHash += RTSTRCACHE_COLLISION_INCR(uHashLen);
+ iHash %= pThis->cHashTab;
+ } while (pThis->papHashTab[iHash] != pStr);
+ if (RT_LIKELY(pThis->papHashTab[iHash] == pStr))
+ pThis->papHashTab[iHash] = PRTSTRCACHEENTRY_NIL;
+ else
+ {
+ AssertFailed();
+ iHash = pThis->cHashTab;
+ while (iHash-- > 0)
+ if (pThis->papHashTab[iHash] == pStr)
+ break;
+ AssertMsgFailed(("iHash=%u cHashTab=%u\n", iHash, pThis->cHashTab));
+ }
+ }
+
+ pThis->cStrings--;
+ pThis->cbStrings -= cchString;
+ Assert(pThis->cStrings < pThis->cHashTab);
+
+ /* Free it. */
+ if (pStr->cchString != RTSTRCACHEENTRY_BIG_LEN)
+ {
+ uint32_t const cbMin = pStr->cchString + 1U + RT_UOFFSETOF(RTSTRCACHEENTRY, szString);
+#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+ if (cbMin <= RTSTRCACHE_MAX_FIXED)
+#endif
+ {
+ /*
+ * No merging, just add it to the list.
+ */
+ uint32_t const iFreeList = rtStrCacheSelectFixedList(cbMin);
+ ASMCompilerBarrier();
+ PRTSTRCACHEFREE pFreeStr = (PRTSTRCACHEFREE)pStr;
+ pFreeStr->cbFree = cbMin;
+ pFreeStr->uZero = 0;
+ pFreeStr->pNext = pThis->apFreeLists[iFreeList];
+ pThis->apFreeLists[iFreeList] = pFreeStr;
+ }
+#ifdef RTSTRCACHE_WITH_MERGED_ALLOCATOR
+ else
+ {
+ /*
+ * Complicated mode, we merge with adjecent nodes.
+ */
+ ASMCompilerBarrier();
+ PRTSTRCACHEFREEMERGE pFreeStr = (PRTSTRCACHEFREEMERGE)pStr;
+ pFreeStr->cbFree = RT_ALIGN_32(cbMin, sizeof(*pFreeStr));
+ pFreeStr->uMarker = RTSTRCACHEFREEMERGE_MAIN;
+ pFreeStr->pMain = NULL;
+ RTListInit(&pFreeStr->ListEntry);
+
+ /*
+ * Merge with previous?
+ * (Reading one block back is safe because there is always the
+ * RTSTRCACHECHUNK structure at the head of each memory chunk.)
+ */
+ uint32_t cInternalBlocks = pFreeStr->cbFree / sizeof(*pFreeStr);
+ PRTSTRCACHEFREEMERGE pMain = pFreeStr - 1;
+ if ( pMain->uMarker == RTSTRCACHEFREEMERGE_MAIN
+ || pMain->uMarker == RTSTRCACHEFREEMERGE_PART)
+ {
+ while (pMain->uMarker != RTSTRCACHEFREEMERGE_MAIN)
+ pMain--;
+ pMain->cbFree += pFreeStr->cbFree;
+ }
+ else
+ {
+ pMain = pFreeStr;
+ pFreeStr++;
+ cInternalBlocks--;
+ }
+
+ /*
+ * Mark internal blocks in the string we're freeing.
+ */
+ while (cInternalBlocks-- > 0)
+ {
+ pFreeStr->uMarker = RTSTRCACHEFREEMERGE_PART;
+ pFreeStr->cbFree = 0;
+ pFreeStr->pMain = pMain;
+ RTListInit(&pFreeStr->ListEntry);
+ pFreeStr++;
+ }
+
+ /*
+ * Merge with next? Limitation: We won't try cross page boundraries.
+ * (pFreeStr points to the next first free enter after the string now.)
+ */
+ if ( PAGE_ADDRESS(pFreeStr) == PAGE_ADDRESS(&pFreeStr[-1])
+ && pFreeStr->uMarker == RTSTRCACHEFREEMERGE_MAIN)
+ {
+ pMain->cbFree += pFreeStr->cbFree;
+ cInternalBlocks = pFreeStr->cbFree / sizeof(*pFreeStr);
+ Assert(cInternalBlocks > 0);
+
+ /* Update the main block we merge with. */
+ pFreeStr->cbFree = 0;
+ pFreeStr->uMarker = RTSTRCACHEFREEMERGE_PART;
+ RTListNodeRemove(&pFreeStr->ListEntry);
+ RTListInit(&pFreeStr->ListEntry);
+
+ /* Change the internal blocks we merged in. */
+ cInternalBlocks--;
+ while (cInternalBlocks-- > 0)
+ {
+ pFreeStr++;
+ pFreeStr->pMain = pMain;
+ Assert(pFreeStr->uMarker == RTSTRCACHEFREEMERGE_PART);
+ Assert(!pFreeStr->cbFree);
+ }
+ }
+
+ /*
+ * Add/relink into the appropriate free list.
+ */
+ rtStrCacheRelinkMerged(pThis, pMain);
+ }
+#endif /* RTSTRCACHE_WITH_MERGED_ALLOCATOR */
+ RTSTRCACHE_CHECK(pThis);
+ RTCritSectLeave(&pThis->CritSect);
+ }
+ else
+ {
+ /* Big string. */
+ PRTSTRCACHEBIGENTRY pBigStr = RT_FROM_MEMBER(pStr, RTSTRCACHEBIGENTRY, Core);
+ RTListNodeRemove(&pBigStr->ListEntry);
+ pThis->cbBigEntries -= RT_ALIGN_32(RT_UOFFSETOF_DYN(RTSTRCACHEBIGENTRY, Core.szString[cchString + 1]),
+ RTSTRCACHE_HEAP_ENTRY_SIZE_ALIGN);
+
+ RTSTRCACHE_CHECK(pThis);
+ RTCritSectLeave(&pThis->CritSect);
+
+ RTMemFree(pBigStr);
+ }
+
+ return 0;
+}
+
+RTDECL(uint32_t) RTStrCacheRelease(RTSTRCACHE hStrCache, const char *psz)
+{
+ if (!psz)
+ return 0;
+
+ PRTSTRCACHEINT pThis = hStrCache;
+ RTSTRCACHE_VALID_RETURN_RC(pThis, UINT32_MAX);
+
+ AssertPtr(psz);
+ PRTSTRCACHEENTRY pStr = RT_FROM_MEMBER(psz, RTSTRCACHEENTRY, szString);
+ Assert(!((uintptr_t)pStr & 15) || pStr->cchString == RTSTRCACHEENTRY_BIG_LEN);
+
+ /*
+ * Drop a reference and maybe free the entry.
+ */
+ uint32_t cRefs = ASMAtomicDecU32(&pStr->cRefs);
+ Assert(cRefs < UINT32_MAX / 2);
+ if (!cRefs)
+ return rtStrCacheFreeEntry(pThis, pStr);
+
+ return cRefs;
+}
+RT_EXPORT_SYMBOL(RTStrCacheRelease);
+
+
+RTDECL(size_t) RTStrCacheLength(const char *psz)
+{
+ if (!psz)
+ return 0;
+
+ AssertPtr(psz);
+ PRTSTRCACHEENTRY pStr = RT_FROM_MEMBER(psz, RTSTRCACHEENTRY, szString);
+ if (pStr->cchString == RTSTRCACHEENTRY_BIG_LEN)
+ {
+ PRTSTRCACHEBIGENTRY pBigStr = RT_FROM_MEMBER(psz, RTSTRCACHEBIGENTRY, Core.szString);
+ return pBigStr->cchString;
+ }
+ Assert(!((uintptr_t)pStr & 15));
+ return pStr->cchString;
+}
+RT_EXPORT_SYMBOL(RTStrCacheLength);
+
+
+RTDECL(bool) RTStrCacheIsRealImpl(void)
+{
+ return true;
+}
+RT_EXPORT_SYMBOL(RTStrCacheIsRealImpl);
+
+
+RTDECL(uint32_t) RTStrCacheGetStats(RTSTRCACHE hStrCache, size_t *pcbStrings, size_t *pcbChunks, size_t *pcbBigEntries,
+ uint32_t *pcHashCollisions, uint32_t *pcHashCollisions2, uint32_t *pcHashInserts,
+ uint32_t *pcRehashes)
+{
+ PRTSTRCACHEINT pThis = hStrCache;
+ RTSTRCACHE_VALID_RETURN_RC(pThis, UINT32_MAX);
+
+ RTCritSectEnter(&pThis->CritSect);
+
+ if (pcbStrings)
+ *pcbStrings = pThis->cbStrings;
+ if (pcbChunks)
+ *pcbChunks = pThis->cbChunks;
+ if (pcbBigEntries)
+ *pcbBigEntries = pThis->cbBigEntries;
+ if (pcHashCollisions)
+ *pcHashCollisions = pThis->cHashCollisions;
+ if (pcHashCollisions2)
+ *pcHashCollisions2 = pThis->cHashCollisions2;
+ if (pcHashInserts)
+ *pcHashInserts = pThis->cHashInserts;
+ if (pcRehashes)
+ *pcRehashes = pThis->cRehashes;
+ uint32_t cStrings = pThis->cStrings;
+
+ RTCritSectLeave(&pThis->CritSect);
+ return cStrings;
+}
+RT_EXPORT_SYMBOL(RTStrCacheRelease);
+
diff --git a/src/VBox/Runtime/common/string/strcat.cpp b/src/VBox/Runtime/common/string/strcat.cpp
new file mode 100644
index 00000000..057b69ce
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strcat.cpp
@@ -0,0 +1,63 @@
+/* $Id: strcat.cpp $ */
+/** @file
+ * IPRT - CRT Strings, strcat().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Append one string to another.
+ *
+ * @returns Pointer to destination string
+ * @param pszDst String to append @a pszSrc to.
+ * @param pszSrc Zero terminated string.
+ */
+#undef strcat
+char *RT_NOCRT(strcat)(char *pszDst, const char *pszSrc)
+{
+ char * const pszRet = pszDst;
+ pszDst = RTStrEnd(pszDst, RTSTR_MAX);
+ while ((*pszDst = *pszSrc++) != '\0')
+ pszDst++;
+
+ return pszRet;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strcat);
+
diff --git a/src/VBox/Runtime/common/string/strchr.asm b/src/VBox/Runtime/common/string/strchr.asm
new file mode 100644
index 00000000..fa5c3925
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strchr.asm
@@ -0,0 +1,152 @@
+; $Id: strchr.asm $
+;; @file
+; IPRT - No-CRT strchr - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param psz gcc: rdi msc: rcx x86:[esp+4] wcall: eax
+; @param ch gcc: esi msc: edx x86:[esp+8] wcall: edx
+RT_NOCRT_BEGINPROC strchr
+ cld
+
+ ; check for ch == 0 and setup normal strchr.
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ or dl, dl
+ jz near .strlen
+ mov r9, rsi ; save rsi
+ mov rsi, rcx
+ %else
+ or sil, sil
+ jz near .strlen
+ mov edx, esi
+ mov rsi, rdi
+ %endif
+%else
+ %ifndef ASM_CALL32_WATCOM
+ mov edx, [esp + 8]
+ %endif
+ or dl, dl
+ jz near .strlen
+ mov ecx, esi ; save esi
+ %ifdef ASM_CALL32_WATCOM
+ mov esi, eax
+ %else
+ mov esi, [esp + 4]
+ %endif
+%endif
+
+ ; do the search
+.next:
+ lodsb
+ cmp al, dl
+ je .found
+ test al, al
+ jz .not_found
+
+ lodsb
+ cmp al, dl
+ je .found
+ test al, al
+ jz .not_found
+
+ lodsb
+ cmp al, dl
+ je .found
+ test al, al
+ jz .not_found
+
+ lodsb
+ cmp al, dl
+ je .found
+ test al, al
+ jz .not_found
+ jmp .next
+
+.found:
+ lea xAX, [xSI - 1]
+%ifdef ASM_CALL64_MSC
+ mov rsi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov esi, ecx
+%endif
+ ret
+
+.not_found:
+%ifdef ASM_CALL64_MSC
+ mov rsi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov esi, ecx
+%endif
+ xor eax, eax
+ ret
+
+;
+; Special case: strchr(str, '\0');
+;
+align 16
+.strlen:
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov r9, rdi ; save rdi
+ mov rdi, rcx
+ %endif
+%else
+ mov edx, edi ; save edi
+ %ifdef ASM_CALL32_WATCOM
+ mov edi, eax
+ %else
+ mov edi, [esp + 4]
+ %endif
+%endif
+ mov xCX, -1
+ xor eax, eax
+ repne scasb
+
+ lea xAX, [xDI - 1]
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+ ret
+ENDPROC RT_NOCRT(strchr)
+
diff --git a/src/VBox/Runtime/common/string/strchr.cpp b/src/VBox/Runtime/common/string/strchr.cpp
new file mode 100644
index 00000000..c41f2d4e
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strchr.cpp
@@ -0,0 +1,67 @@
+/* $Id: strchr.cpp $ */
+/** @file
+ * IPRT - CRT Strings, strchr().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+#ifdef IPRT_NO_CRT
+# undef strchr
+char *RT_NOCRT(strchr)(const char *pszStr, int ch)
+#elif defined(_MSC_VER)
+_CRTIMP char * __cdecl strchr(const char *pszStr, int ch)
+#elif defined(__WATCOMC__) && !defined(IPRT_NO_CRT)
+_WCRTLINK char *std::strchr(const char *pszStr, int ch)
+#else
+char *strchr(const char *pszStr, int ch)
+# if defined(__THROW) && !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ __THROW
+# endif
+#endif
+{
+ char chCur;
+ while ((chCur = *pszStr) != '\0')
+ if (chCur != ch)
+ pszStr++;
+ else
+ return (char *)pszStr;
+ return NULL;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strchr);
+
diff --git a/src/VBox/Runtime/common/string/strcmp.asm b/src/VBox/Runtime/common/string/strcmp.asm
new file mode 100644
index 00000000..2721ce87
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strcmp.asm
@@ -0,0 +1,111 @@
+; $Id: strcmp.asm $
+;; @file
+; IPRT - No-CRT strcmp - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param psz1 gcc: rdi msc: rcx x86:[esp+4] wcall: eax
+; @param psz2 gcc: rsi msc: rdx x86:[esp+8] wcall: edx
+RT_NOCRT_BEGINPROC strcmp
+ ; input
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ %define psz1 rcx
+ %define psz2 rdx
+ %else
+ %define psz1 rdi
+ %define psz2 rsi
+ %endif
+%else
+ %ifdef ASM_CALL32_WATCOM
+ mov ecx, eax
+ %else
+ mov ecx, [esp + 4]
+ mov edx, [esp + 8]
+ %endif
+ %define psz1 ecx
+ %define psz2 edx
+%endif
+
+ ;
+ ; The loop.
+ ;
+.next:
+ mov al, [psz1]
+ mov ah, [psz2]
+ cmp al, ah
+ jne .not_equal
+ test al, al
+ jz .equal
+
+ mov al, [psz1 + 1]
+ mov ah, [psz2 + 1]
+ cmp al, ah
+ jne .not_equal
+ test al, al
+ jz .equal
+
+ mov al, [psz1 + 2]
+ mov ah, [psz2 + 2]
+ cmp al, ah
+ jne .not_equal
+ test al, al
+ jz .equal
+
+ mov al, [psz1 + 3]
+ mov ah, [psz2 + 3]
+ cmp al, ah
+ jne .not_equal
+ test al, al
+ jz .equal
+
+ add psz1, 4
+ add psz2, 4
+ jmp .next
+
+.equal:
+ xor eax, eax
+ ret
+
+.not_equal:
+ movzx ecx, ah
+ and eax, 0ffh
+ sub eax, ecx
+ ret
+ENDPROC RT_NOCRT(strcmp)
+
diff --git a/src/VBox/Runtime/common/string/strcpy.asm b/src/VBox/Runtime/common/string/strcpy.asm
new file mode 100644
index 00000000..e7021345
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strcpy.asm
@@ -0,0 +1,103 @@
+; $Id: strcpy.asm $
+;; @file
+; IPRT - No-CRT strcpy - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pszDst gcc: rdi msc: rcx x86:[esp+4] wcall:eax
+; @param pszSrc gcc: rsi msc: rdx x86:[esp+8] wcall:edx
+RT_NOCRT_BEGINPROC strcpy
+ ; input
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ %define pszDst rcx
+ %define pszSrc rdx
+ %else
+ %define pszDst rdi
+ %define pszSrc rsi
+ %endif
+ mov r8, pszDst
+%else
+ %ifdef ASM_CALL32_WATCOM
+ mov ecx, eax
+ %else
+ mov ecx, [esp + 4]
+ mov edx, [esp + 8]
+ %endif
+ %define pszDst ecx
+ %define pszSrc edx
+ push pszDst
+%endif
+
+ ;
+ ; The loop.
+ ;
+.next:
+ mov al, [pszSrc]
+ mov [pszDst], al
+ test al, al
+ jz .done
+
+ mov al, [pszSrc + 1]
+ mov [pszDst + 1], al
+ test al, al
+ jz .done
+
+ mov al, [pszSrc + 2]
+ mov [pszDst + 2], al
+ test al, al
+ jz .done
+
+ mov al, [pszSrc + 3]
+ mov [pszDst + 3], al
+ test al, al
+ jz .done
+
+ add pszDst, 4
+ add pszSrc, 4
+ jmp .next
+
+.done:
+%ifdef RT_ARCH_AMD64
+ mov rax, r8
+%else
+ pop eax
+%endif
+ ret
+ENDPROC RT_NOCRT(strcpy)
+
diff --git a/src/VBox/Runtime/common/string/strcpy.cpp b/src/VBox/Runtime/common/string/strcpy.cpp
new file mode 100644
index 00000000..c1995c00
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strcpy.cpp
@@ -0,0 +1,66 @@
+/* $Id: strcpy.cpp $ */
+/** @file
+ * IPRT - CRT Strings, strcpy().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Copy a string
+ *
+ * @returns Pointer to destination string
+ * @param pszDst Will contain a copy of pszSrc.
+ * @param pszSrc Zero terminated string.
+ */
+#ifdef IPRT_NO_CRT
+# undef strcpy
+char *RT_NOCRT(strcpy)(char *pszDst, const char *pszSrc)
+#else
+char *strcpy(char *pszDst, const char *pszSrc)
+#endif
+{
+ char * const pszRet = pszDst;
+ while ((*pszDst = *pszSrc++) != '\0')
+ pszDst++;
+
+ return pszRet;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strcpy);
+
diff --git a/src/VBox/Runtime/common/string/strcspn.cpp b/src/VBox/Runtime/common/string/strcspn.cpp
new file mode 100644
index 00000000..ca343f0b
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strcspn.cpp
@@ -0,0 +1,65 @@
+/* $Id: strcspn.cpp $ */
+/** @file
+ * IPRT - CRT Strings, strcspn().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * strpbrk with a offset return instead of a pointer.
+ */
+#undef strcspn
+size_t RT_NOCRT(strcspn)(const char *pszString, const char *pszBreakChars)
+{
+ const char * const pszStringStart = pszString;
+ int chCur;
+ while ((chCur = *pszString++) != '\0')
+ {
+ int chBreak;
+ const char *psz = pszBreakChars;
+ while ((chBreak = *psz++) != '\0')
+ if (chBreak == chCur)
+ return (size_t)(pszString - pszStringStart + 1);
+
+ }
+ return (size_t)(pszString - pszStringStart);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strcspn);
+
diff --git a/src/VBox/Runtime/common/string/strformat.cpp b/src/VBox/Runtime/common/string/strformat.cpp
new file mode 100644
index 00000000..86f23609
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strformat.cpp
@@ -0,0 +1,866 @@
+/* $Id: strformat.cpp $ */
+/** @file
+ * IPRT - String Formatter.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_STRING
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#ifdef IN_RING3
+# include <iprt/alloc.h>
+# include <iprt/errcore.h>
+# include <iprt/uni.h>
+# include <iprt/utf16.h>
+#endif
+#include <iprt/ctype.h>
+#include <iprt/string.h>
+#include <iprt/stdarg.h>
+#include "internal/string.h"
+
+
+/**
+ * Deals with bad pointers.
+ */
+DECLHIDDEN(size_t) rtStrFormatBadPointer(size_t cch, PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, int cchWidth,
+ unsigned fFlags, void const *pvStr, char szTmp[64], const char *pszTag, int cchTag)
+{
+ static char const s_szNull[] = "<NULL>";
+ int cchStr = !pvStr ? sizeof(s_szNull) - 1 : 1 + sizeof(void *) * 2 + cchTag + 1;
+
+ if (!(fFlags & RTSTR_F_LEFT))
+ while (--cchWidth >= cchStr)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+
+ cchWidth -= cchStr;
+ if (!pvStr)
+ cch += pfnOutput(pvArgOutput, s_szNull, sizeof(s_szNull) - 1);
+ else
+ {
+ cch += pfnOutput(pvArgOutput, "<", 1);
+ cchStr = RTStrFormatNumber(&szTmp[0], (uintptr_t)pvStr, 16, sizeof(char *) * 2, 0, RTSTR_F_ZEROPAD);
+ cch += pfnOutput(pvArgOutput, szTmp, cchStr);
+ cch += pfnOutput(pvArgOutput, pszTag, cchTag);
+ cch += pfnOutput(pvArgOutput, ">", 1);
+ }
+
+ while (--cchWidth >= 0)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+ return cch;
+}
+
+
+/**
+ * Finds the length of a string up to cchMax.
+ * @returns Length.
+ * @param psz Pointer to string.
+ * @param cchMax Max length.
+ */
+static unsigned rtStrFormatStrNLen(const char *psz, unsigned cchMax)
+{
+ const char *pszC = psz;
+
+ while (cchMax-- > 0 && *psz != '\0')
+ psz++;
+
+ return (unsigned)(psz - pszC);
+}
+
+
+/**
+ * Finds the length of a string up to cchMax.
+ * @returns Length.
+ * @param pwsz Pointer to string.
+ * @param cchMax Max length.
+ */
+static unsigned rtStrFormatStrNLenUtf16(PCRTUTF16 pwsz, unsigned cchMax)
+{
+#ifdef IN_RING3
+ unsigned cwc = 0;
+ while (cchMax-- > 0)
+ {
+ RTUNICP cp;
+ int rc = RTUtf16GetCpEx(&pwsz, &cp);
+ AssertRC(rc);
+ if (RT_FAILURE(rc) || !cp)
+ break;
+ cwc++;
+ }
+ return cwc;
+#else /* !IN_RING3 */
+ PCRTUTF16 pwszC = pwsz;
+
+ while (cchMax-- > 0 && *pwsz != '\0')
+ pwsz++;
+
+ return (unsigned)(pwsz - pwszC);
+#endif /* !IN_RING3 */
+}
+
+
+/**
+ * Finds the length of a string up to cchMax.
+ * @returns Length.
+ * @param pusz Pointer to string.
+ * @param cchMax Max length.
+ */
+static unsigned rtStrFormatStrNLenUni(PCRTUNICP pusz, unsigned cchMax)
+{
+ PCRTUNICP puszC = pusz;
+
+ while (cchMax-- > 0 && *pusz != '\0')
+ pusz++;
+
+ return (unsigned)(pusz - puszC);
+}
+
+
+RTDECL(int) RTStrFormatNumber(char *psz, uint64_t u64Value, unsigned int uiBase, signed int cchWidth, signed int cchPrecision,
+ unsigned int fFlags)
+{
+ const char *pachDigits = "0123456789abcdef";
+ char *pszStart = psz;
+ int cchMax;
+ int cchValue;
+ int i;
+ int j;
+ char chSign;
+
+ /*
+ * Validate and adjust input...
+ */
+ Assert(uiBase >= 2 && uiBase <= 16);
+ if (fFlags & RTSTR_F_CAPITAL)
+ pachDigits = "0123456789ABCDEF";
+ if (fFlags & RTSTR_F_LEFT)
+ fFlags &= ~RTSTR_F_ZEROPAD;
+ if ( (fFlags & RTSTR_F_THOUSAND_SEP)
+ && ( uiBase != 10
+ || (fFlags & RTSTR_F_ZEROPAD))) /** @todo implement RTSTR_F_ZEROPAD + RTSTR_F_THOUSAND_SEP. */
+ fFlags &= ~RTSTR_F_THOUSAND_SEP;
+
+ /*
+ * Determine value length and sign. Converts the u64Value to unsigned.
+ */
+ cchValue = 0;
+ chSign = '\0';
+ if ((fFlags & RTSTR_F_64BIT) || (u64Value & UINT64_C(0xffffffff00000000)))
+ {
+ uint64_t u64;
+ if (!(fFlags & RTSTR_F_VALSIGNED) || !(u64Value & RT_BIT_64(63)))
+ u64 = u64Value;
+ else if (u64Value != RT_BIT_64(63))
+ {
+ chSign = '-';
+ u64 = u64Value = -(int64_t)u64Value;
+ }
+ else
+ {
+ chSign = '-';
+ u64 = u64Value = RT_BIT_64(63);
+ }
+ do
+ {
+ cchValue++;
+ u64 /= uiBase;
+ } while (u64);
+ }
+ else
+ {
+ uint32_t u32 = (uint32_t)u64Value;
+ if (!(fFlags & RTSTR_F_VALSIGNED) || !(u32 & UINT32_C(0x80000000)))
+ { /* likley */ }
+ else if (u32 != UINT32_C(0x80000000))
+ {
+ chSign = '-';
+ u64Value = u32 = -(int32_t)u32;
+ }
+ else
+ {
+ chSign = '-';
+ u64Value = u32 = UINT32_C(0x80000000);
+ }
+ do
+ {
+ cchValue++;
+ u32 /= uiBase;
+ } while (u32);
+ }
+ if (fFlags & RTSTR_F_THOUSAND_SEP)
+ {
+ if (cchValue <= 3)
+ fFlags &= ~RTSTR_F_THOUSAND_SEP;
+ else
+ cchValue += cchValue / 3 - (cchValue % 3 == 0);
+ }
+
+ /*
+ * Sign (+/-).
+ */
+ i = 0;
+ if (fFlags & RTSTR_F_VALSIGNED)
+ {
+ if (chSign != '\0')
+ psz[i++] = chSign;
+ else if (fFlags & (RTSTR_F_PLUS | RTSTR_F_BLANK))
+ psz[i++] = (char)(fFlags & RTSTR_F_PLUS ? '+' : ' ');
+ }
+
+ /*
+ * Special (0/0x).
+ */
+ if ((fFlags & RTSTR_F_SPECIAL) && (uiBase % 8) == 0)
+ {
+ psz[i++] = '0';
+ if (uiBase == 16)
+ psz[i++] = (char)(fFlags & RTSTR_F_CAPITAL ? 'X' : 'x');
+ }
+
+ /*
+ * width - only if ZEROPAD
+ */
+ cchMax = 64 - (cchValue + i + 1); /* HACK! 64 bytes seems to be the usual buffer size... */
+ cchWidth -= i + cchValue;
+ if (fFlags & RTSTR_F_ZEROPAD)
+ while (--cchWidth >= 0 && i < cchMax)
+ {
+ AssertBreak(i < cchMax);
+ psz[i++] = '0';
+ cchPrecision--;
+ }
+ else if (!(fFlags & RTSTR_F_LEFT) && cchWidth > 0)
+ {
+ AssertStmt(cchWidth < cchMax, cchWidth = cchMax - 1);
+ for (j = i - 1; j >= 0; j--)
+ psz[cchWidth + j] = psz[j];
+ for (j = 0; j < cchWidth; j++)
+ psz[j] = ' ';
+ i += cchWidth;
+ }
+
+ /*
+ * precision
+ */
+ while (--cchPrecision >= cchValue)
+ {
+ AssertBreak(i < cchMax);
+ psz[i++] = '0';
+ }
+
+ psz += i;
+
+ /*
+ * write number - not good enough but it works
+ */
+ psz += cchValue;
+ i = -1;
+ if ((fFlags & RTSTR_F_64BIT) || (u64Value & UINT64_C(0xffffffff00000000)))
+ {
+ uint64_t u64 = u64Value;
+ if (fFlags & RTSTR_F_THOUSAND_SEP)
+ {
+ do
+ {
+ if ((-i - 1) % 4 == 3)
+ psz[i--] = ' ';
+ psz[i--] = pachDigits[u64 % uiBase];
+ u64 /= uiBase;
+ } while (u64);
+ }
+ else
+ {
+ do
+ {
+ psz[i--] = pachDigits[u64 % uiBase];
+ u64 /= uiBase;
+ } while (u64);
+ }
+ }
+ else
+ {
+ uint32_t u32 = (uint32_t)u64Value;
+ if (fFlags & RTSTR_F_THOUSAND_SEP)
+ {
+ do
+ {
+ if ((-i - 1) % 4 == 3)
+ psz[i--] = ' ';
+ psz[i--] = pachDigits[u32 % uiBase];
+ u32 /= uiBase;
+ } while (u32);
+ }
+ else
+ {
+ do
+ {
+ psz[i--] = pachDigits[u32 % uiBase];
+ u32 /= uiBase;
+ } while (u32);
+ }
+ }
+
+ /*
+ * width if RTSTR_F_LEFT
+ */
+ if (fFlags & RTSTR_F_LEFT)
+ while (--cchWidth >= 0)
+ *psz++ = ' ';
+
+ *psz = '\0';
+ return (unsigned)(psz - pszStart);
+}
+RT_EXPORT_SYMBOL(RTStrFormatNumber);
+
+
+RTDECL(size_t) RTStrFormatV(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, PFNSTRFORMAT pfnFormat, void *pvArgFormat,
+ const char *pszFormat, va_list InArgs)
+{
+ char szTmp[64]; /* Worker functions assumes 64 byte buffer! Ugly but faster. */
+ va_list args;
+ size_t cch = 0;
+ const char *pszStartOutput = pszFormat;
+
+ va_copy(args, InArgs); /* make a copy so we can reference it (AMD64 / gcc). */
+
+ while (*pszFormat != '\0')
+ {
+ if (*pszFormat == '%')
+ {
+ /* output pending string. */
+ if (pszStartOutput != pszFormat)
+ cch += pfnOutput(pvArgOutput, pszStartOutput, pszFormat - pszStartOutput);
+
+ /* skip '%' */
+ pszFormat++;
+ if (*pszFormat == '%') /* '%%'-> '%' */
+ pszStartOutput = pszFormat++;
+ else
+ {
+ unsigned int fFlags = 0;
+ int cchWidth = -1;
+ int cchPrecision = -1;
+ unsigned int uBase = 10;
+ char chArgSize;
+
+ /* flags */
+ for (;;)
+ {
+ switch (*pszFormat++)
+ {
+ case '#': fFlags |= RTSTR_F_SPECIAL; continue;
+ case '-': fFlags |= RTSTR_F_LEFT; continue;
+ case '+': fFlags |= RTSTR_F_PLUS; continue;
+ case ' ': fFlags |= RTSTR_F_BLANK; continue;
+ case '0': fFlags |= RTSTR_F_ZEROPAD; continue;
+ case '\'': fFlags |= RTSTR_F_THOUSAND_SEP; continue;
+ }
+ pszFormat--;
+ break;
+ }
+
+ /* width */
+ if (RT_C_IS_DIGIT(*pszFormat))
+ {
+ for (cchWidth = 0; RT_C_IS_DIGIT(*pszFormat); pszFormat++)
+ {
+ cchWidth *= 10;
+ cchWidth += *pszFormat - '0';
+ }
+ fFlags |= RTSTR_F_WIDTH;
+ }
+ else if (*pszFormat == '*')
+ {
+ pszFormat++;
+ cchWidth = va_arg(args, int);
+ if (cchWidth < 0)
+ {
+ cchWidth = -cchWidth;
+ fFlags |= RTSTR_F_LEFT;
+ }
+ fFlags |= RTSTR_F_WIDTH;
+ }
+
+ /* precision */
+ if (*pszFormat == '.')
+ {
+ pszFormat++;
+ if (RT_C_IS_DIGIT(*pszFormat))
+ {
+ for (cchPrecision = 0; RT_C_IS_DIGIT(*pszFormat); pszFormat++)
+ {
+ cchPrecision *= 10;
+ cchPrecision += *pszFormat - '0';
+ }
+
+ }
+ else if (*pszFormat == '*')
+ {
+ pszFormat++;
+ cchPrecision = va_arg(args, int);
+ }
+ if (cchPrecision < 0)
+ cchPrecision = 0;
+ fFlags |= RTSTR_F_PRECISION;
+ }
+
+ /*
+ * Argument size.
+ */
+ chArgSize = *pszFormat;
+ switch (chArgSize)
+ {
+ default:
+ chArgSize = 0;
+ break;
+
+ case 'z':
+ case 'L':
+ case 'j':
+ case 't':
+ pszFormat++;
+ break;
+
+ case 'l':
+ pszFormat++;
+ if (*pszFormat == 'l')
+ {
+ chArgSize = 'L';
+ pszFormat++;
+ }
+ break;
+
+ case 'h':
+ pszFormat++;
+ if (*pszFormat == 'h')
+ {
+ chArgSize = 'H';
+ pszFormat++;
+ }
+ break;
+
+ case 'I': /* Used by Win32/64 compilers. */
+ if ( pszFormat[1] == '6'
+ && pszFormat[2] == '4')
+ {
+ pszFormat += 3;
+ chArgSize = 'L';
+ }
+ else if ( pszFormat[1] == '3'
+ && pszFormat[2] == '2')
+ {
+ pszFormat += 3;
+ chArgSize = 0;
+ }
+ else
+ {
+ pszFormat += 1;
+ chArgSize = 'j';
+ }
+ break;
+
+ case 'q': /* Used on BSD platforms. */
+ pszFormat++;
+ chArgSize = 'L';
+ break;
+ }
+
+ /*
+ * The type.
+ */
+ switch (*pszFormat++)
+ {
+ /* char */
+ case 'c':
+ {
+ if (!(fFlags & RTSTR_F_LEFT))
+ while (--cchWidth > 0)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+
+ szTmp[0] = (char)va_arg(args, int);
+ szTmp[1] = '\0'; /* Some output functions wants terminated strings. */
+ cch += pfnOutput(pvArgOutput, &szTmp[0], 1);
+
+ while (--cchWidth > 0)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+ break;
+ }
+
+ case 'S': /* Legacy, conversion done by streams now. */
+ case 's':
+ {
+ if (chArgSize == 'l')
+ {
+ /* utf-16 -> utf-8 */
+ PCRTUTF16 pwszStr = va_arg(args, PCRTUTF16);
+ if (RT_VALID_PTR(pwszStr))
+ {
+ int cwcStr = rtStrFormatStrNLenUtf16(pwszStr, (unsigned)cchPrecision);
+ if (!(fFlags & RTSTR_F_LEFT))
+ while (--cchWidth >= cwcStr)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+ cchWidth -= cwcStr;
+ while (cwcStr-- > 0)
+ {
+/** @todo \#ifndef IN_RC*/
+#ifdef IN_RING3
+ RTUNICP Cp;
+ RTUtf16GetCpEx(&pwszStr, &Cp);
+ char *pszEnd = RTStrPutCp(szTmp, Cp);
+ *pszEnd = '\0';
+ cch += pfnOutput(pvArgOutput, szTmp, pszEnd - szTmp);
+#else
+ char ch = (char)*pwszStr++;
+ cch += pfnOutput(pvArgOutput, &ch, 1);
+#endif
+ }
+ while (--cchWidth >= 0)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+ }
+ else
+ cch = rtStrFormatBadPointer(cch, pfnOutput, pvArgOutput, cchWidth, fFlags,
+ pwszStr, szTmp, RT_STR_TUPLE("!BadStrW"));
+ }
+ else if (chArgSize == 'L')
+ {
+ /* unicp -> utf8 */
+ PCRTUNICP puszStr = va_arg(args, PCRTUNICP);
+ if (RT_VALID_PTR(puszStr))
+ {
+ int cchStr = rtStrFormatStrNLenUni(puszStr, (unsigned)cchPrecision);
+ if (!(fFlags & RTSTR_F_LEFT))
+ while (--cchWidth >= cchStr)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+
+ cchWidth -= cchStr;
+ while (cchStr-- > 0)
+ {
+/** @todo \#ifndef IN_RC*/
+#ifdef IN_RING3
+ char *pszEnd = RTStrPutCp(szTmp, *puszStr++);
+ cch += pfnOutput(pvArgOutput, szTmp, pszEnd - szTmp);
+#else
+ char ch = (char)*puszStr++;
+ cch += pfnOutput(pvArgOutput, &ch, 1);
+#endif
+ }
+ while (--cchWidth >= 0)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+ }
+ else
+ cch = rtStrFormatBadPointer(cch, pfnOutput, pvArgOutput, cchWidth, fFlags,
+ puszStr, szTmp, RT_STR_TUPLE("!BadStrU"));
+ }
+ else
+ {
+ const char *pszStr = va_arg(args, const char *);
+ if (RT_VALID_PTR(pszStr))
+ {
+ int cchStr = rtStrFormatStrNLen(pszStr, (unsigned)cchPrecision);
+ if (!(fFlags & RTSTR_F_LEFT))
+ while (--cchWidth >= cchStr)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+
+ cch += pfnOutput(pvArgOutput, pszStr, cchStr);
+
+ while (--cchWidth >= cchStr)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+ }
+ else
+ cch = rtStrFormatBadPointer(cch, pfnOutput, pvArgOutput, cchWidth, fFlags,
+ pszStr, szTmp, RT_STR_TUPLE("!BadStr"));
+ }
+ break;
+ }
+
+ /*-----------------*/
+ /* integer/pointer */
+ /*-----------------*/
+ case 'd':
+ case 'i':
+ case 'o':
+ case 'p':
+ case 'u':
+ case 'x':
+ case 'X':
+ {
+ int cchNum;
+ uint64_t u64Value;
+
+ switch (pszFormat[-1])
+ {
+ case 'd': /* signed decimal integer */
+ case 'i':
+ fFlags |= RTSTR_F_VALSIGNED;
+ break;
+
+ case 'o':
+ uBase = 8;
+ break;
+
+ case 'p':
+ fFlags |= RTSTR_F_ZEROPAD; /* Note not standard behaviour (but I like it this way!) */
+ uBase = 16;
+ if (cchWidth < 0)
+ cchWidth = sizeof(char *) * 2;
+ break;
+
+ case 'u':
+ uBase = 10;
+ break;
+
+ case 'X':
+ fFlags |= RTSTR_F_CAPITAL;
+ RT_FALL_THRU();
+ case 'x':
+ uBase = 16;
+ break;
+ }
+
+ if (pszFormat[-1] == 'p')
+ u64Value = va_arg(args, uintptr_t);
+ else if (fFlags & RTSTR_F_VALSIGNED)
+ {
+ if (chArgSize == 'L')
+ {
+ u64Value = va_arg(args, int64_t);
+ fFlags |= RTSTR_F_64BIT;
+ }
+ else if (chArgSize == 'l')
+ {
+ u64Value = va_arg(args, signed long);
+ fFlags |= RTSTR_GET_BIT_FLAG(unsigned long);
+ }
+ else if (chArgSize == 'h')
+ {
+ u64Value = va_arg(args, /* signed short */ int);
+ fFlags |= RTSTR_GET_BIT_FLAG(signed short);
+ }
+ else if (chArgSize == 'H')
+ {
+ u64Value = va_arg(args, /* int8_t */ int);
+ fFlags |= RTSTR_GET_BIT_FLAG(int8_t);
+ }
+ else if (chArgSize == 'j')
+ {
+ u64Value = va_arg(args, /*intmax_t*/ int64_t);
+ fFlags |= RTSTR_F_64BIT;
+ }
+ else if (chArgSize == 'z')
+ {
+ u64Value = va_arg(args, size_t);
+ fFlags |= RTSTR_GET_BIT_FLAG(size_t);
+ }
+ else if (chArgSize == 't')
+ {
+ u64Value = va_arg(args, ptrdiff_t);
+ fFlags |= RTSTR_GET_BIT_FLAG(ptrdiff_t);
+ }
+ else
+ {
+ u64Value = va_arg(args, signed int);
+ fFlags |= RTSTR_GET_BIT_FLAG(signed int);
+ }
+ }
+ else
+ {
+ if (chArgSize == 'L')
+ {
+ u64Value = va_arg(args, uint64_t);
+ fFlags |= RTSTR_F_64BIT;
+ }
+ else if (chArgSize == 'l')
+ {
+ u64Value = va_arg(args, unsigned long);
+ fFlags |= RTSTR_GET_BIT_FLAG(unsigned long);
+ }
+ else if (chArgSize == 'h')
+ {
+ u64Value = va_arg(args, /* unsigned short */ int);
+ fFlags |= RTSTR_GET_BIT_FLAG(unsigned short);
+ }
+ else if (chArgSize == 'H')
+ {
+ u64Value = va_arg(args, /* uint8_t */ int);
+ fFlags |= RTSTR_GET_BIT_FLAG(uint8_t);
+ }
+ else if (chArgSize == 'j')
+ {
+ u64Value = va_arg(args, /*uintmax_t*/ int64_t);
+ fFlags |= RTSTR_F_64BIT;
+ }
+ else if (chArgSize == 'z')
+ {
+ u64Value = va_arg(args, size_t);
+ fFlags |= RTSTR_GET_BIT_FLAG(size_t);
+ }
+ else if (chArgSize == 't')
+ {
+ u64Value = va_arg(args, ptrdiff_t);
+ fFlags |= RTSTR_GET_BIT_FLAG(ptrdiff_t);
+ }
+ else
+ {
+ u64Value = va_arg(args, unsigned int);
+ fFlags |= RTSTR_GET_BIT_FLAG(unsigned int);
+ }
+ }
+ cchNum = RTStrFormatNumber(&szTmp[0], u64Value, uBase, cchWidth, cchPrecision, fFlags);
+ cch += pfnOutput(pvArgOutput, &szTmp[0], cchNum);
+ break;
+ }
+
+ /*
+ * Floating point.
+ *
+ * We currently don't really implement these yet, there is just a very basic
+ * formatting regardless of the requested type.
+ */
+ case 'e': /* [-]d.dddddde+-dd[d] */
+ case 'E': /* [-]d.ddddddE+-dd[d] */
+ case 'f': /* [-]dddd.dddddd / inf / nan */
+ case 'F': /* [-]dddd.dddddd / INF / NAN */
+ case 'g': /* Either f or e, depending on the magnitue and precision. */
+ case 'G': /* Either f or E, depending on the magnitue and precision. */
+ case 'a': /* [-]0xh.hhhhhhp+-dd */
+ case 'A': /* [-]0Xh.hhhhhhP+-dd */
+ {
+#if defined(IN_RING3) && !defined(IN_SUP_HARDENED_R3) && !defined(IPRT_NO_FLOAT_FORMATTING)
+ size_t cchNum;
+# ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ if (chArgSize == 'L')
+ {
+ RTFLOAT80U2 r80;
+ r80.lrd = va_arg(args, long double);
+# ifndef IN_BLD_PROG
+ cchNum = RTStrFormatR80u2(&szTmp[0], sizeof(szTmp), &r80, cchWidth, cchPrecision, 0);
+# else
+ cch += pfnOutput(pvArgOutput, RT_STR_TUPLE("<long double>"));
+ RT_NOREF_PV(r80);
+ break;
+# endif
+ }
+ else
+# endif
+ {
+ RTFLOAT64U r64;
+ r64.rd = va_arg(args, double);
+# ifndef IN_BLD_PROG
+ cchNum = RTStrFormatR64(&szTmp[0], sizeof(szTmp), &r64, cchWidth, cchPrecision, 0);
+# else
+ cchNum = RTStrFormatNumber(&szTmp[0], r64.au64[0], 16, 0, 0, RTSTR_F_SPECIAL | RTSTR_F_64BIT);
+# endif
+ }
+ cch += pfnOutput(pvArgOutput, &szTmp[0], cchNum);
+#else /* !IN_RING3 */
+ AssertFailed();
+#endif /* !IN_RING3 */
+ break;
+ }
+
+ /*
+ * Nested extensions.
+ */
+ case 'M': /* replace the format string (not stacked yet). */
+ {
+ pszStartOutput = pszFormat = va_arg(args, const char *);
+ AssertPtr(pszStartOutput);
+ break;
+ }
+
+ case 'N': /* real nesting. */
+ {
+ const char *pszFormatNested = va_arg(args, const char *);
+ va_list *pArgsNested = va_arg(args, va_list *);
+ va_list ArgsNested;
+ va_copy(ArgsNested, *pArgsNested);
+ Assert(pszFormatNested);
+ cch += RTStrFormatV(pfnOutput, pvArgOutput, pfnFormat, pvArgFormat, pszFormatNested, ArgsNested);
+ va_end(ArgsNested);
+ break;
+ }
+
+ /*
+ * IPRT Extensions.
+ */
+ case 'R':
+ {
+ if (*pszFormat != '[')
+ {
+ pszFormat--;
+ cch += rtstrFormatRt(pfnOutput, pvArgOutput, &pszFormat, &args, cchWidth, cchPrecision, fFlags, chArgSize);
+ }
+ else
+ {
+ pszFormat--;
+ cch += rtstrFormatType(pfnOutput, pvArgOutput, &pszFormat, &args, cchWidth, cchPrecision, fFlags, chArgSize);
+ }
+ break;
+ }
+
+ /*
+ * Custom format.
+ */
+ default:
+ {
+ if (pfnFormat)
+ {
+ pszFormat--;
+ cch += pfnFormat(pvArgFormat, pfnOutput, pvArgOutput, &pszFormat, &args, cchWidth, cchPrecision, fFlags, chArgSize);
+ }
+ break;
+ }
+ }
+ pszStartOutput = pszFormat;
+ }
+ }
+ else
+ pszFormat++;
+ }
+
+ /* output pending string. */
+ if (pszStartOutput != pszFormat)
+ cch += pfnOutput(pvArgOutput, pszStartOutput, pszFormat - pszStartOutput);
+
+ /* terminate the output */
+ pfnOutput(pvArgOutput, NULL, 0);
+
+ return cch;
+}
+RT_EXPORT_SYMBOL(RTStrFormatV);
+
diff --git a/src/VBox/Runtime/common/string/strformatfloat.cpp b/src/VBox/Runtime/common/string/strformatfloat.cpp
new file mode 100644
index 00000000..bd240c0c
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strformatfloat.cpp
@@ -0,0 +1,353 @@
+/* $Id: strformatfloat.cpp $ */
+/** @file
+ * IPRT - String Formatter, Floating Point Numbers (simple approach).
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_STRING
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include "internal/string.h"
+
+
+/**
+ * Helper for rtStrFormatR80Worker that copies out the resulting string.
+ */
+static ssize_t rtStrFormatCopyOutStr(char *pszBuf, size_t cbBuf, const char *pszSrc, size_t cchSrc)
+{
+ if (cchSrc < cbBuf)
+ {
+ memcpy(pszBuf, pszSrc, cchSrc);
+ pszBuf[cchSrc] = '\0';
+ return cchSrc;
+ }
+ if (cbBuf)
+ {
+ memcpy(pszBuf, pszSrc, cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+
+
+RTDECL(ssize_t) RTStrFormatR32(char *pszBuf, size_t cbBuf, PCRTFLOAT32U pr32Value, signed int cchWidth,
+ signed int cchPrecision, uint32_t fFlags)
+{
+ RT_NOREF(cchWidth, cchPrecision);
+
+ /*
+ * Handle some special values that does require any value annotating.
+ */
+ bool const fSign = pr32Value->s.fSign;
+ if (RTFLOAT32U_IS_ZERO(pr32Value))
+ return fSign
+ ? rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("-0"))
+ : rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("+0"));
+ if (RTFLOAT32U_IS_INF(pr32Value))
+ return fSign
+ ? rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("-Inf"))
+ : rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("+Inf"));
+
+ /*
+ * Output sign first.
+ */
+ char szTmp[80];
+ char *pszTmp = szTmp;
+ if (fSign)
+ *pszTmp++ = '-';
+ else
+ *pszTmp++ = '+';
+
+ /*
+ * Normal?
+ */
+ uint16_t const uExponent = pr32Value->s.uExponent;
+ uint32_t const uFraction = pr32Value->s.uFraction;
+ if (RTFLOAT32U_IS_NORMAL(pr32Value))
+ {
+ *pszTmp++ = '1';
+ *pszTmp++ = 'm';
+ pszTmp += RTStrFormatNumber(pszTmp, uFraction, 16, 2 + (RTFLOAT32U_FRACTION_BITS + 3) / 4, 0,
+ RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_32BIT);
+
+ *pszTmp++ = '^';
+ pszTmp += RTStrFormatNumber(pszTmp, (int32_t)uExponent - RTFLOAT32U_EXP_BIAS, 10, 0, 0,
+ RTSTR_F_ZEROPAD | RTSTR_F_32BIT | RTSTR_F_VALSIGNED);
+ }
+ /*
+ * Subnormal?
+ */
+ else if (RTFLOAT32U_IS_SUBNORMAL(pr32Value))
+ {
+ *pszTmp++ = '0';
+ *pszTmp++ = 'm';
+ pszTmp += RTStrFormatNumber(pszTmp, uFraction, 16, 2 + (RTFLOAT32U_FRACTION_BITS + 3) / 4, 0,
+ RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_32BIT);
+ if (fFlags & RTSTR_F_SPECIAL)
+ pszTmp = (char *)memcpy(pszTmp, "[SubN]", 6) + 6;
+ }
+ /*
+ * NaN.
+ */
+ else
+ {
+ Assert(RTFLOAT32U_IS_NAN(pr32Value));
+ if (!(fFlags & RTSTR_F_SPECIAL))
+ return rtStrFormatCopyOutStr(pszBuf, cbBuf,
+ RTFLOAT32U_IS_SIGNALLING_NAN(pr32Value)
+ ? (fSign ? "-SNan[" : "+SNan[") : fSign ? "-QNan[" : "+QNan[", 5);
+ *pszTmp++ = RTFLOAT32U_IS_SIGNALLING_NAN(pr32Value) ? 'S' : 'Q';
+ *pszTmp++ = 'N';
+ *pszTmp++ = 'a';
+ *pszTmp++ = 'N';
+ *pszTmp++ = '[';
+ *pszTmp++ = '.';
+ pszTmp += RTStrFormatNumber(pszTmp, uFraction, 16, 2 + (RTFLOAT32U_FRACTION_BITS + 3) / 4, 0,
+ RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_32BIT);
+ *pszTmp++ = ']';
+ }
+ return rtStrFormatCopyOutStr(pszBuf, cbBuf, szTmp, pszTmp - &szTmp[0]);
+}
+
+
+
+
+RTDECL(ssize_t) RTStrFormatR64(char *pszBuf, size_t cbBuf, PCRTFLOAT64U pr64Value, signed int cchWidth,
+ signed int cchPrecision, uint32_t fFlags)
+{
+ RT_NOREF(cchWidth, cchPrecision);
+
+ /*
+ * Handle some special values that does require any value annotating.
+ */
+ bool const fSign = pr64Value->s.fSign;
+ if (RTFLOAT64U_IS_ZERO(pr64Value))
+ return fSign
+ ? rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("-0"))
+ : rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("+0"));
+ if (RTFLOAT64U_IS_INF(pr64Value))
+ return fSign
+ ? rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("-Inf"))
+ : rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("+Inf"));
+
+ /*
+ * Output sign first.
+ */
+ char szTmp[160];
+ char *pszTmp = szTmp;
+ if (fSign)
+ *pszTmp++ = '-';
+ else
+ *pszTmp++ = '+';
+
+ /*
+ * Normal?
+ */
+ uint16_t const uExponent = pr64Value->s.uExponent;
+ uint64_t const uFraction = RT_MAKE_U64(pr64Value->s.uFractionLow, pr64Value->s.uFractionHigh);
+ if (RTFLOAT64U_IS_NORMAL(pr64Value))
+ {
+ *pszTmp++ = '1';
+ *pszTmp++ = 'm';
+ pszTmp += RTStrFormatNumber(pszTmp, uFraction, 16, 2 + (RTFLOAT64U_FRACTION_BITS + 3) / 4, 0,
+ RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
+
+ *pszTmp++ = '^';
+ pszTmp += RTStrFormatNumber(pszTmp, (int32_t)uExponent - RTFLOAT64U_EXP_BIAS, 10, 0, 0,
+ RTSTR_F_ZEROPAD | RTSTR_F_32BIT | RTSTR_F_VALSIGNED);
+ }
+ /*
+ * Subnormal?
+ */
+ else if (RTFLOAT64U_IS_SUBNORMAL(pr64Value))
+ {
+ *pszTmp++ = '0';
+ *pszTmp++ = 'm';
+ pszTmp += RTStrFormatNumber(pszTmp, uFraction, 16, 2 + (RTFLOAT64U_FRACTION_BITS + 3) / 4, 0,
+ RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
+ if (fFlags & RTSTR_F_SPECIAL)
+ pszTmp = (char *)memcpy(pszTmp, "[SubN]", 6) + 6;
+ }
+ /*
+ * NaN.
+ */
+ else
+ {
+ Assert(RTFLOAT64U_IS_NAN(pr64Value));
+ if (!(fFlags & RTSTR_F_SPECIAL))
+ return rtStrFormatCopyOutStr(pszBuf, cbBuf,
+ RTFLOAT64U_IS_SIGNALLING_NAN(pr64Value)
+ ? (fSign ? "-SNan[" : "+SNan[") : fSign ? "-QNan[" : "+QNan[", 5);
+ *pszTmp++ = RTFLOAT64U_IS_SIGNALLING_NAN(pr64Value) ? 'S' : 'Q';
+ *pszTmp++ = 'N';
+ *pszTmp++ = 'a';
+ *pszTmp++ = 'N';
+ *pszTmp++ = '[';
+ *pszTmp++ = '.';
+ pszTmp += RTStrFormatNumber(pszTmp, uFraction, 16, 2 + RTFLOAT64U_FRACTION_BITS / 4, 0,
+ RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
+ *pszTmp++ = ']';
+ }
+ return rtStrFormatCopyOutStr(pszBuf, cbBuf, szTmp, pszTmp - &szTmp[0]);
+}
+
+
+/**
+ * Common worker for RTStrFormatR80 and RTStrFormatR80u2.
+ */
+static ssize_t rtStrFormatR80Worker(char *pszBuf, size_t cbBuf, bool const fSign, bool const fInteger,
+ uint64_t const uFraction, uint16_t uExponent, uint32_t fFlags)
+{
+ char szTmp[160];
+
+ /*
+ * Output sign first.
+ */
+ char *pszTmp = szTmp;
+ if (fSign)
+ *pszTmp++ = '-';
+ else
+ *pszTmp++ = '+';
+
+ /*
+ * Then check for special numbers (indicated by expontent).
+ */
+ bool fDenormal = false;
+ if (uExponent == 0)
+ {
+ /* Zero? */
+ if ( !uFraction
+ && !fInteger)
+ return fSign
+ ? rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("-0"))
+ : rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("+0"));
+ fDenormal = true;
+ uExponent = 1;
+ }
+ else if (uExponent == RTFLOAT80U_EXP_MAX)
+ {
+ if (!fInteger)
+ {
+ if (!uFraction)
+ return fSign
+ ? rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("-PseudoInf"))
+ : rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("+PseudoInf"));
+ if (!(fFlags & RTSTR_F_SPECIAL))
+ return fSign
+ ? rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("-PseudoNan"))
+ : rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("+PseudoNan"));
+ pszTmp = (char *)memcpy(pszTmp, "PseudoNan[", 10) + 10;
+ }
+ else if (!(uFraction & RT_BIT_64(62)))
+ {
+ if (!(uFraction & (RT_BIT_64(62) - 1)))
+ return fSign
+ ? rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("-Inf"))
+ : rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("+Inf"));
+ if (!(fFlags & RTSTR_F_SPECIAL))
+ return rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("SNan"));
+ pszTmp = (char *)memcpy(pszTmp, "SNan[", 5) + 5;
+ }
+ else
+ {
+ if (!(uFraction & (RT_BIT_64(62) - 1)))
+ return fSign
+ ? rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("-Ind"))
+ : rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("+Ind"));
+ if (!(fFlags & RTSTR_F_SPECIAL))
+ return rtStrFormatCopyOutStr(pszBuf, cbBuf, RT_STR_TUPLE("QNan"));
+ pszTmp = (char *)memcpy(pszTmp, "QNan[", 5) + 5;
+ }
+ pszTmp += RTStrFormatNumber(pszTmp, uFraction, 16, 2 + RTFLOAT80U_FRACTION_BITS / 4, 0,
+ RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
+ *pszTmp++ = ']';
+ return rtStrFormatCopyOutStr(pszBuf, cbBuf, szTmp, pszTmp - &szTmp[0]);
+ }
+
+ /*
+ * Format the mantissa and exponent.
+ */
+ *pszTmp++ = fInteger ? '1' : '0';
+ *pszTmp++ = 'm';
+ pszTmp += RTStrFormatNumber(pszTmp, uFraction, 16, 2 + (RTFLOAT80U_FRACTION_BITS + 3) / 4, 0,
+ RTSTR_F_SPECIAL | RTSTR_F_ZEROPAD | RTSTR_F_64BIT);
+
+ *pszTmp++ = '^';
+ pszTmp += RTStrFormatNumber(pszTmp, (int32_t)uExponent - RTFLOAT80U_EXP_BIAS, 10, 0, 0,
+ RTSTR_F_ZEROPAD | RTSTR_F_32BIT | RTSTR_F_VALSIGNED);
+ if (fFlags & RTSTR_F_SPECIAL)
+ {
+ if (fDenormal)
+ {
+ if (fInteger)
+ pszTmp = (char *)memcpy(pszTmp, "[PDn]", 5) + 5;
+ else
+ pszTmp = (char *)memcpy(pszTmp, "[Den]", 5) + 5;
+ }
+ else if (!fInteger)
+ pszTmp = (char *)memcpy(pszTmp, "[Unn]", 5) + 5;
+ }
+ return rtStrFormatCopyOutStr(pszBuf, cbBuf, szTmp, pszTmp - &szTmp[0]);
+}
+
+
+RTDECL(ssize_t) RTStrFormatR80u2(char *pszBuf, size_t cbBuf, PCRTFLOAT80U2 pr80Value, signed int cchWidth,
+ signed int cchPrecision, uint32_t fFlags)
+{
+ RT_NOREF(cchWidth, cchPrecision);
+#ifdef RT_COMPILER_GROKS_64BIT_BITFIELDS
+ return rtStrFormatR80Worker(pszBuf, cbBuf, pr80Value->sj64.fSign, pr80Value->sj64.fInteger,
+ pr80Value->sj64.uFraction, pr80Value->sj64.uExponent, fFlags);
+#else
+ return rtStrFormatR80Worker(pszBuf, cbBuf, pr80Value->sj.fSign, pr80Value->sj.fInteger,
+ RT_MAKE_U64(pr80Value->sj.u32FractionLow, pr80Value->sj.u31FractionHigh),
+ pr80Value->sj.uExponent, fFlags);
+#endif
+}
+
+
+RTDECL(ssize_t) RTStrFormatR80(char *pszBuf, size_t cbBuf, PCRTFLOAT80U pr80Value, signed int cchWidth,
+ signed int cchPrecision, uint32_t fFlags)
+{
+ RT_NOREF(cchWidth, cchPrecision);
+ return rtStrFormatR80Worker(pszBuf, cbBuf, pr80Value->s.fSign, pr80Value->s.uMantissa >> 63,
+ pr80Value->s.uMantissa & (RT_BIT_64(63) - 1), pr80Value->s.uExponent, fFlags);
+}
+
diff --git a/src/VBox/Runtime/common/string/strformatnum.cpp b/src/VBox/Runtime/common/string/strformatnum.cpp
new file mode 100644
index 00000000..74b262fc
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strformatnum.cpp
@@ -0,0 +1,245 @@
+/* $Id: strformatnum.cpp $ */
+/** @file
+ * IPRT - String Formatter, Single Numbers.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_STRING
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/errcore.h>
+#include "internal/string.h"
+
+
+RTDECL(ssize_t) RTStrFormatU8(char *pszBuf, size_t cbBuf, uint8_t u8Value, unsigned int uiBase,
+ signed int cchWidth, signed int cchPrecision, uint32_t fFlags)
+{
+ fFlags &= ~RTSTR_F_BIT_MASK;
+ fFlags |= RTSTR_F_8BIT;
+
+ ssize_t cchRet;
+ if (cbBuf >= 64)
+ cchRet = RTStrFormatNumber(pszBuf, u8Value, uiBase, cchWidth, cchPrecision, fFlags);
+ else
+ {
+ char szTmp[64];
+ cchRet = RTStrFormatNumber(szTmp, u8Value, uiBase, cchWidth, cchPrecision, fFlags);
+ if ((size_t)cchRet < cbBuf)
+ memcpy(pszBuf, szTmp, cchRet + 1);
+ else
+ {
+ if (cbBuf)
+ {
+ memcpy(pszBuf, szTmp, cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ cchRet = VERR_BUFFER_OVERFLOW;
+ }
+ }
+ return cchRet;
+}
+
+
+RTDECL(ssize_t) RTStrFormatU16(char *pszBuf, size_t cbBuf, uint16_t u16Value, unsigned int uiBase,
+ signed int cchWidth, signed int cchPrecision, uint32_t fFlags)
+{
+ fFlags &= ~RTSTR_F_BIT_MASK;
+ fFlags |= RTSTR_F_16BIT;
+
+ ssize_t cchRet;
+ if (cbBuf >= 64)
+ cchRet = RTStrFormatNumber(pszBuf, u16Value, uiBase, cchWidth, cchPrecision, fFlags);
+ else
+ {
+ char szTmp[64];
+ cchRet = RTStrFormatNumber(szTmp, u16Value, uiBase, cchWidth, cchPrecision, fFlags);
+ if ((size_t)cchRet < cbBuf)
+ memcpy(pszBuf, szTmp, cchRet + 1);
+ else
+ {
+ if (cbBuf)
+ {
+ memcpy(pszBuf, szTmp, cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ cchRet = VERR_BUFFER_OVERFLOW;
+ }
+ }
+ return cchRet;
+}
+
+
+RTDECL(ssize_t) RTStrFormatU32(char *pszBuf, size_t cbBuf, uint32_t u32Value, unsigned int uiBase,
+ signed int cchWidth, signed int cchPrecision, uint32_t fFlags)
+{
+ fFlags &= ~RTSTR_F_BIT_MASK;
+ fFlags |= RTSTR_F_32BIT;
+
+ ssize_t cchRet;
+ if (cbBuf >= 64)
+ cchRet = RTStrFormatNumber(pszBuf, u32Value, uiBase, cchWidth, cchPrecision, fFlags);
+ else
+ {
+ char szTmp[64];
+ cchRet = RTStrFormatNumber(szTmp, u32Value, uiBase, cchWidth, cchPrecision, fFlags);
+ if ((size_t)cchRet < cbBuf)
+ memcpy(pszBuf, szTmp, cchRet + 1);
+ else
+ {
+ if (cbBuf)
+ {
+ memcpy(pszBuf, szTmp, cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ cchRet = VERR_BUFFER_OVERFLOW;
+ }
+ }
+ return cchRet;
+}
+
+
+RTDECL(ssize_t) RTStrFormatU64(char *pszBuf, size_t cbBuf, uint64_t u64Value, unsigned int uiBase,
+ signed int cchWidth, signed int cchPrecision, uint32_t fFlags)
+{
+ fFlags &= ~RTSTR_F_BIT_MASK;
+ fFlags |= RTSTR_F_64BIT;
+
+ ssize_t cchRet;
+ if (cbBuf >= 64)
+ cchRet = RTStrFormatNumber(pszBuf, u64Value, uiBase, cchWidth, cchPrecision, fFlags);
+ else
+ {
+ char szTmp[64];
+ cchRet = RTStrFormatNumber(szTmp, u64Value, uiBase, cchWidth, cchPrecision, fFlags);
+ if ((size_t)cchRet < cbBuf)
+ memcpy(pszBuf, szTmp, cchRet + 1);
+ else
+ {
+ if (cbBuf)
+ {
+ memcpy(pszBuf, szTmp, cbBuf - 1);
+ pszBuf[cbBuf - 1] = '\0';
+ }
+ cchRet = VERR_BUFFER_OVERFLOW;
+ }
+ }
+ return cchRet;
+}
+
+
+RTDECL(ssize_t) RTStrFormatU128(char *pszBuf, size_t cbBuf, PCRTUINT128U pu128, unsigned int uiBase,
+ signed int cchWidth, signed int cchPrecision, uint32_t fFlags)
+{
+ NOREF(cchWidth); NOREF(cchPrecision);
+ if (uiBase != 16)
+ fFlags |= RTSTR_F_SPECIAL;
+ fFlags &= ~RTSTR_F_BIT_MASK;
+
+ char szTmp[64+32+32+32];
+ char *pszTmp = cbBuf >= sizeof(szTmp) ? pszBuf : szTmp;
+ size_t cchResult = RTStrFormatNumber(pszTmp, pu128->QWords.qw1, 16, 0, 0, fFlags | RTSTR_F_64BIT);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu128->QWords.qw0, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ if (pszTmp == pszBuf)
+ return cchResult;
+ int rc = RTStrCopy(pszBuf, cbBuf, pszTmp);
+ if (RT_SUCCESS(rc))
+ return cchResult;
+ return rc;
+}
+
+
+RTDECL(ssize_t) RTStrFormatU256(char *pszBuf, size_t cbBuf, PCRTUINT256U pu256, unsigned int uiBase,
+ signed int cchWidth, signed int cchPrecision, uint32_t fFlags)
+{
+ NOREF(cchWidth); NOREF(cchPrecision);
+ if (uiBase != 16)
+ fFlags |= RTSTR_F_SPECIAL;
+ fFlags &= ~RTSTR_F_BIT_MASK;
+
+ char szTmp[64+32+32+32];
+ char *pszTmp = cbBuf >= sizeof(szTmp) ? pszBuf : szTmp;
+ size_t cchResult = RTStrFormatNumber(pszTmp, pu256->QWords.qw3, 16, 0, 0, fFlags | RTSTR_F_64BIT);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu256->QWords.qw2, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu256->QWords.qw1, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu256->QWords.qw0, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ if (pszTmp == pszBuf)
+ return cchResult;
+ int rc = RTStrCopy(pszBuf, cbBuf, pszTmp);
+ if (RT_SUCCESS(rc))
+ return cchResult;
+ return rc;
+}
+
+
+RTDECL(ssize_t) RTStrFormatU512(char *pszBuf, size_t cbBuf, PCRTUINT512U pu512, unsigned int uiBase,
+ signed int cchWidth, signed int cchPrecision, uint32_t fFlags)
+{
+ NOREF(cchWidth); NOREF(cchPrecision);
+ if (uiBase != 16)
+ fFlags |= RTSTR_F_SPECIAL;
+ fFlags &= ~RTSTR_F_BIT_MASK;
+
+ char szTmp[64+32+32+32 + 32+32+32+32];
+ char *pszTmp = cbBuf >= sizeof(szTmp) ? pszBuf : szTmp;
+ size_t cchResult = RTStrFormatNumber(pszTmp, pu512->QWords.qw7, 16, 0, 0, fFlags | RTSTR_F_64BIT);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu512->QWords.qw6, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu512->QWords.qw5, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu512->QWords.qw4, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu512->QWords.qw3, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu512->QWords.qw2, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu512->QWords.qw1, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ cchResult += RTStrFormatNumber(&pszTmp[cchResult], pu512->QWords.qw0, 16, 8, 0,
+ (fFlags | RTSTR_F_64BIT | RTSTR_F_ZEROPAD) & ~RTSTR_F_SPECIAL);
+ if (pszTmp == pszBuf)
+ return cchResult;
+ int rc = RTStrCopy(pszBuf, cbBuf, pszTmp);
+ if (RT_SUCCESS(rc))
+ return cchResult;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/string/strformatrt.cpp b/src/VBox/Runtime/common/string/strformatrt.cpp
new file mode 100644
index 00000000..eaedae3f
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strformatrt.cpp
@@ -0,0 +1,1722 @@
+/* $Id: strformatrt.cpp $ */
+/** @file
+ * IPRT - IPRT String Formatter Extensions.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_STRING
+#include <iprt/string.h>
+#ifndef RT_NO_EXPORT_SYMBOL
+# define RT_NO_EXPORT_SYMBOL /* don't slurp <linux/module.h> which then again
+ slurps arch-specific headers defining symbols */
+#endif
+#include "internal/iprt.h"
+
+#include <iprt/log.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/stdarg.h>
+#ifdef IN_RING3
+# include <iprt/errcore.h>
+# include <iprt/thread.h>
+# include <iprt/utf16.h>
+#endif
+#include <iprt/ctype.h>
+#include <iprt/time.h>
+#include <iprt/net.h>
+#include <iprt/path.h>
+#include <iprt/asm.h>
+#define STRFORMAT_WITH_X86
+#ifdef STRFORMAT_WITH_X86
+# include <iprt/x86.h>
+#endif
+#include "internal/string.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static char g_szHexDigits[17] = "0123456789abcdef";
+#ifdef IN_RING3
+static char g_szHexDigitsUpper[17] = "0123456789ABCDEF";
+#endif
+
+
+/**
+ * Helper that formats a 16-bit hex word in a IPv6 address.
+ *
+ * @returns Length in chars.
+ * @param pszDst The output buffer. Written from the start.
+ * @param uWord The word to format as hex.
+ */
+static size_t rtstrFormatIPv6HexWord(char *pszDst, uint16_t uWord)
+{
+ size_t off;
+ uint16_t cDigits;
+
+ if (uWord & UINT16_C(0xff00))
+ cDigits = uWord & UINT16_C(0xf000) ? 4 : 3;
+ else
+ cDigits = uWord & UINT16_C(0x00f0) ? 2 : 1;
+
+ off = 0;
+ switch (cDigits)
+ {
+ case 4: pszDst[off++] = g_szHexDigits[(uWord >> 12) & 0xf]; RT_FALL_THRU();
+ case 3: pszDst[off++] = g_szHexDigits[(uWord >> 8) & 0xf]; RT_FALL_THRU();
+ case 2: pszDst[off++] = g_szHexDigits[(uWord >> 4) & 0xf]; RT_FALL_THRU();
+ case 1: pszDst[off++] = g_szHexDigits[(uWord >> 0) & 0xf];
+ break;
+ }
+ pszDst[off] = '\0';
+ return off;
+}
+
+
+/**
+ * Helper function to format IPv6 address according to RFC 5952.
+ *
+ * @returns The number of bytes formatted.
+ * @param pfnOutput Pointer to output function.
+ * @param pvArgOutput Argument for the output function.
+ * @param pIpv6Addr IPv6 address
+ */
+static size_t rtstrFormatIPv6(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, PCRTNETADDRIPV6 pIpv6Addr)
+{
+ size_t cch; /* result */
+ bool fEmbeddedIpv4;
+ size_t cwHexPart;
+ size_t cwLongestZeroRun;
+ size_t iLongestZeroStart;
+ size_t idx;
+ char szHexWord[8];
+
+ Assert(pIpv6Addr != NULL);
+
+ /*
+ * Check for embedded IPv4 address.
+ *
+ * IPv4-compatible - ::11.22.33.44 (obsolete)
+ * IPv4-mapped - ::ffff:11.22.33.44
+ * IPv4-translated - ::ffff:0:11.22.33.44 (RFC 2765)
+ */
+ fEmbeddedIpv4 = false;
+ cwHexPart = RT_ELEMENTS(pIpv6Addr->au16);
+ if ( pIpv6Addr->au64[0] == 0
+ && ( ( pIpv6Addr->au32[2] == 0
+ && pIpv6Addr->au32[3] != 0
+ && pIpv6Addr->au32[3] != RT_H2BE_U32_C(1) )
+ || pIpv6Addr->au32[2] == RT_H2BE_U32_C(0x0000ffff)
+ || pIpv6Addr->au32[2] == RT_H2BE_U32_C(0xffff0000) ) )
+ {
+ fEmbeddedIpv4 = true;
+ cwHexPart -= 2;
+ }
+
+ /*
+ * Find the longest sequences of two or more zero words.
+ */
+ cwLongestZeroRun = 0;
+ iLongestZeroStart = 0;
+ for (idx = 0; idx < cwHexPart; idx++)
+ if (pIpv6Addr->au16[idx] == 0)
+ {
+ size_t iZeroStart = idx;
+ size_t cwZeroRun;
+ do
+ idx++;
+ while (idx < cwHexPart && pIpv6Addr->au16[idx] == 0);
+ cwZeroRun = idx - iZeroStart;
+ if (cwZeroRun > 1 && cwZeroRun > cwLongestZeroRun)
+ {
+ cwLongestZeroRun = cwZeroRun;
+ iLongestZeroStart = iZeroStart;
+ if (cwZeroRun >= cwHexPart - idx)
+ break;
+ }
+ }
+
+ /*
+ * Do the formatting.
+ */
+ cch = 0;
+ if (cwLongestZeroRun == 0)
+ {
+ for (idx = 0; idx < cwHexPart; ++idx)
+ {
+ if (idx > 0)
+ cch += pfnOutput(pvArgOutput, ":", 1);
+ cch += pfnOutput(pvArgOutput, szHexWord, rtstrFormatIPv6HexWord(szHexWord, RT_BE2H_U16(pIpv6Addr->au16[idx])));
+ }
+
+ if (fEmbeddedIpv4)
+ cch += pfnOutput(pvArgOutput, ":", 1);
+ }
+ else
+ {
+ const size_t iLongestZeroEnd = iLongestZeroStart + cwLongestZeroRun;
+
+ if (iLongestZeroStart == 0)
+ cch += pfnOutput(pvArgOutput, ":", 1);
+ else
+ for (idx = 0; idx < iLongestZeroStart; ++idx)
+ {
+ cch += pfnOutput(pvArgOutput, szHexWord, rtstrFormatIPv6HexWord(szHexWord, RT_BE2H_U16(pIpv6Addr->au16[idx])));
+ cch += pfnOutput(pvArgOutput, ":", 1);
+ }
+
+ if (iLongestZeroEnd == cwHexPart)
+ cch += pfnOutput(pvArgOutput, ":", 1);
+ else
+ {
+ for (idx = iLongestZeroEnd; idx < cwHexPart; ++idx)
+ {
+ cch += pfnOutput(pvArgOutput, ":", 1);
+ cch += pfnOutput(pvArgOutput, szHexWord, rtstrFormatIPv6HexWord(szHexWord, RT_BE2H_U16(pIpv6Addr->au16[idx])));
+ }
+
+ if (fEmbeddedIpv4)
+ cch += pfnOutput(pvArgOutput, ":", 1);
+ }
+ }
+
+ if (fEmbeddedIpv4)
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "%u.%u.%u.%u",
+ pIpv6Addr->au8[12],
+ pIpv6Addr->au8[13],
+ pIpv6Addr->au8[14],
+ pIpv6Addr->au8[15]);
+
+ return cch;
+}
+
+
+/**
+ * Callback to format iprt formatting extentions.
+ * See @ref pg_rt_str_format for a reference on the format types.
+ *
+ * @returns The number of bytes formatted.
+ * @param pfnOutput Pointer to output function.
+ * @param pvArgOutput Argument for the output function.
+ * @param ppszFormat Pointer to the format string pointer. Advance this till the char
+ * after the format specifier.
+ * @param pArgs Pointer to the argument list. Use this to fetch the arguments.
+ * @param cchWidth Format Width. -1 if not specified.
+ * @param cchPrecision Format Precision. -1 if not specified.
+ * @param fFlags Flags (RTSTR_NTFS_*).
+ * @param chArgSize The argument size specifier, 'l' or 'L'.
+ */
+DECLHIDDEN(size_t) rtstrFormatRt(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, const char **ppszFormat, va_list *pArgs,
+ int cchWidth, int cchPrecision, unsigned fFlags, char chArgSize)
+{
+ const char *pszFormatOrg = *ppszFormat;
+ char ch = *(*ppszFormat)++;
+ size_t cch;
+ char szBuf[80];
+
+ if (ch == 'R')
+ {
+ ch = *(*ppszFormat)++;
+ switch (ch)
+ {
+ /*
+ * Groups 1 and 2.
+ */
+ case 'T':
+ case 'G':
+ case 'H':
+ case 'R':
+ case 'C':
+ case 'I':
+ case 'X':
+ case 'U':
+ case 'K':
+ {
+ /*
+ * Interpret the type.
+ */
+ typedef enum
+ {
+ RTSF_INT,
+ RTSF_INTW,
+ RTSF_BOOL,
+ RTSF_FP16,
+ RTSF_FP32,
+ RTSF_FP64,
+ RTSF_IPV4,
+ RTSF_IPV6,
+ RTSF_MAC,
+ RTSF_NETADDR,
+ RTSF_UUID,
+ RTSF_ERRINFO,
+ RTSF_ERRINFO_MSG_ONLY
+ } RTSF;
+ static const struct
+ {
+ uint8_t cch; /**< the length of the string. */
+ char sz[10]; /**< the part following 'R'. */
+ uint8_t cb; /**< the size of the type. */
+ uint8_t u8Base; /**< the size of the type. */
+ RTSF enmFormat; /**< The way to format it. */
+ uint16_t fFlags; /**< additional RTSTR_F_* flags. */
+ }
+ /** Sorted array of types, looked up using binary search! */
+ s_aTypes[] =
+ {
+#define STRMEM(str) sizeof(str) - 1, str
+ { STRMEM("Ci"), sizeof(RTINT), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("Cp"), sizeof(RTCCPHYS), 16, RTSF_INTW, 0 },
+ { STRMEM("Cr"), sizeof(RTCCUINTREG), 16, RTSF_INTW, 0 },
+ { STRMEM("Cu"), sizeof(RTUINT), 10, RTSF_INT, 0 },
+ { STRMEM("Cv"), sizeof(void *), 16, RTSF_INTW, 0 },
+ { STRMEM("Cx"), sizeof(RTUINT), 16, RTSF_INT, 0 },
+ { STRMEM("Gi"), sizeof(RTGCINT), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("Gp"), sizeof(RTGCPHYS), 16, RTSF_INTW, 0 },
+ { STRMEM("Gr"), sizeof(RTGCUINTREG), 16, RTSF_INTW, 0 },
+ { STRMEM("Gu"), sizeof(RTGCUINT), 10, RTSF_INT, 0 },
+ { STRMEM("Gv"), sizeof(RTGCPTR), 16, RTSF_INTW, 0 },
+ { STRMEM("Gx"), sizeof(RTGCUINT), 16, RTSF_INT, 0 },
+ { STRMEM("Hi"), sizeof(RTHCINT), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("Hp"), sizeof(RTHCPHYS), 16, RTSF_INTW, 0 },
+ { STRMEM("Hr"), sizeof(RTHCUINTREG), 16, RTSF_INTW, 0 },
+ { STRMEM("Hu"), sizeof(RTHCUINT), 10, RTSF_INT, 0 },
+ { STRMEM("Hv"), sizeof(RTHCPTR), 16, RTSF_INTW, 0 },
+ { STRMEM("Hx"), sizeof(RTHCUINT), 16, RTSF_INT, 0 },
+ { STRMEM("I16"), sizeof(int16_t), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("I32"), sizeof(int32_t), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("I64"), sizeof(int64_t), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("I8"), sizeof(int8_t), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("Kv"), sizeof(RTHCPTR), 16, RTSF_INT, RTSTR_F_OBFUSCATE_PTR },
+ { STRMEM("Rv"), sizeof(RTRCPTR), 16, RTSF_INTW, 0 },
+ { STRMEM("Tbool"), sizeof(bool), 10, RTSF_BOOL, 0 },
+ { STRMEM("Teic"), sizeof(PCRTERRINFO), 16, RTSF_ERRINFO, 0 },
+ { STRMEM("Teim"), sizeof(PCRTERRINFO), 16, RTSF_ERRINFO_MSG_ONLY, 0 },
+ { STRMEM("Tfile"), sizeof(RTFILE), 10, RTSF_INT, 0 },
+ { STRMEM("Tfmode"), sizeof(RTFMODE), 16, RTSF_INTW, 0 },
+ { STRMEM("Tfoff"), sizeof(RTFOFF), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("Tfp16"), sizeof(RTFAR16), 16, RTSF_FP16, RTSTR_F_ZEROPAD },
+ { STRMEM("Tfp32"), sizeof(RTFAR32), 16, RTSF_FP32, RTSTR_F_ZEROPAD },
+ { STRMEM("Tfp64"), sizeof(RTFAR64), 16, RTSF_FP64, RTSTR_F_ZEROPAD },
+ { STRMEM("Tgid"), sizeof(RTGID), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("Tino"), sizeof(RTINODE), 16, RTSF_INTW, 0 },
+ { STRMEM("Tint"), sizeof(RTINT), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("Tiop"), sizeof(RTIOPORT), 16, RTSF_INTW, 0 },
+ { STRMEM("Tldrm"), sizeof(RTLDRMOD), 16, RTSF_INTW, 0 },
+ { STRMEM("Tmac"), sizeof(PCRTMAC), 16, RTSF_MAC, 0 },
+ { STRMEM("Tnaddr"), sizeof(PCRTNETADDR), 10, RTSF_NETADDR,0 },
+ { STRMEM("Tnaipv4"), sizeof(RTNETADDRIPV4), 10, RTSF_IPV4, 0 },
+ { STRMEM("Tnaipv6"), sizeof(PCRTNETADDRIPV6),16, RTSF_IPV6, 0 },
+ { STRMEM("Tnthrd"), sizeof(RTNATIVETHREAD), 16, RTSF_INTW, 0 },
+ { STRMEM("Tproc"), sizeof(RTPROCESS), 16, RTSF_INTW, 0 },
+ { STRMEM("Tptr"), sizeof(RTUINTPTR), 16, RTSF_INTW, 0 },
+ { STRMEM("Treg"), sizeof(RTCCUINTREG), 16, RTSF_INTW, 0 },
+ { STRMEM("Tsel"), sizeof(RTSEL), 16, RTSF_INTW, 0 },
+ { STRMEM("Tsem"), sizeof(RTSEMEVENT), 16, RTSF_INTW, 0 },
+ { STRMEM("Tsock"), sizeof(RTSOCKET), 10, RTSF_INT, 0 },
+ { STRMEM("Tthrd"), sizeof(RTTHREAD), 16, RTSF_INTW, 0 },
+ { STRMEM("Tuid"), sizeof(RTUID), 10, RTSF_INT, RTSTR_F_VALSIGNED },
+ { STRMEM("Tuint"), sizeof(RTUINT), 10, RTSF_INT, 0 },
+ { STRMEM("Tunicp"), sizeof(RTUNICP), 16, RTSF_INTW, RTSTR_F_ZEROPAD },
+ { STRMEM("Tutf16"), sizeof(RTUTF16), 16, RTSF_INTW, RTSTR_F_ZEROPAD },
+ { STRMEM("Tuuid"), sizeof(PCRTUUID), 16, RTSF_UUID, 0 },
+ { STRMEM("Txint"), sizeof(RTUINT), 16, RTSF_INT, 0 },
+ { STRMEM("U16"), sizeof(uint16_t), 10, RTSF_INT, 0 },
+ { STRMEM("U32"), sizeof(uint32_t), 10, RTSF_INT, 0 },
+ { STRMEM("U64"), sizeof(uint64_t), 10, RTSF_INT, 0 },
+ { STRMEM("U8"), sizeof(uint8_t), 10, RTSF_INT, 0 },
+ { STRMEM("X16"), sizeof(uint16_t), 16, RTSF_INT, 0 },
+ { STRMEM("X32"), sizeof(uint32_t), 16, RTSF_INT, 0 },
+ { STRMEM("X64"), sizeof(uint64_t), 16, RTSF_INT, 0 },
+ { STRMEM("X8"), sizeof(uint8_t), 16, RTSF_INT, 0 },
+#undef STRMEM
+ };
+
+ const char *pszType = *ppszFormat - 1;
+ int iStart = 0;
+ int iEnd = RT_ELEMENTS(s_aTypes) - 1;
+ int i = RT_ELEMENTS(s_aTypes) / 2;
+
+ union
+ {
+ uint8_t u8;
+ uint16_t u16;
+ uint32_t u32;
+ uint64_t u64;
+ int8_t i8;
+ int16_t i16;
+ int32_t i32;
+ int64_t i64;
+ RTR0INTPTR uR0Ptr;
+ RTFAR16 fp16;
+ RTFAR32 fp32;
+ RTFAR64 fp64;
+ bool fBool;
+ PCRTMAC pMac;
+ RTNETADDRIPV4 Ipv4Addr;
+ PCRTNETADDRIPV6 pIpv6Addr;
+ PCRTNETADDR pNetAddr;
+ PCRTUUID pUuid;
+ PCRTERRINFO pErrInfo;
+ } u;
+
+ AssertMsg(!chArgSize, ("Not argument size '%c' for RT types! '%.10s'\n", chArgSize, pszFormatOrg));
+ RT_NOREF_PV(chArgSize);
+
+ /*
+ * Lookup the type - binary search.
+ */
+ for (;;)
+ {
+ int iDiff = strncmp(pszType, s_aTypes[i].sz, s_aTypes[i].cch);
+ if (!iDiff)
+ break;
+ if (iEnd == iStart)
+ {
+ AssertMsgFailed(("Invalid format type '%.10s'!\n", pszFormatOrg));
+ return 0;
+ }
+ if (iDiff < 0)
+ iEnd = i - 1;
+ else
+ iStart = i + 1;
+ if (iEnd < iStart)
+ {
+ AssertMsgFailed(("Invalid format type '%.10s'!\n", pszFormatOrg));
+ return 0;
+ }
+ i = iStart + (iEnd - iStart) / 2;
+ }
+
+ /*
+ * Advance the format string and merge flags.
+ */
+ *ppszFormat += s_aTypes[i].cch - 1;
+ fFlags |= s_aTypes[i].fFlags;
+
+ /*
+ * Fetch the argument.
+ * It's important that a signed value gets sign-extended up to 64-bit.
+ */
+ RT_ZERO(u);
+ if (fFlags & RTSTR_F_VALSIGNED)
+ {
+ switch (s_aTypes[i].cb)
+ {
+ case sizeof(int8_t):
+ u.i64 = va_arg(*pArgs, /*int8_t*/int);
+ fFlags |= RTSTR_F_8BIT;
+ break;
+ case sizeof(int16_t):
+ u.i64 = va_arg(*pArgs, /*int16_t*/int);
+ fFlags |= RTSTR_F_16BIT;
+ break;
+ case sizeof(int32_t):
+ u.i64 = va_arg(*pArgs, int32_t);
+ fFlags |= RTSTR_F_32BIT;
+ break;
+ case sizeof(int64_t):
+ u.i64 = va_arg(*pArgs, int64_t);
+ fFlags |= RTSTR_F_64BIT;
+ break;
+ default:
+ AssertMsgFailed(("Invalid format error, size %d'!\n", s_aTypes[i].cb));
+ break;
+ }
+ }
+ else
+ {
+ switch (s_aTypes[i].cb)
+ {
+ case sizeof(uint8_t):
+ u.u8 = va_arg(*pArgs, /*uint8_t*/unsigned);
+ fFlags |= RTSTR_F_8BIT;
+ break;
+ case sizeof(uint16_t):
+ u.u16 = va_arg(*pArgs, /*uint16_t*/unsigned);
+ fFlags |= RTSTR_F_16BIT;
+ break;
+ case sizeof(uint32_t):
+ u.u32 = va_arg(*pArgs, uint32_t);
+ fFlags |= RTSTR_F_32BIT;
+ break;
+ case sizeof(uint64_t):
+ u.u64 = va_arg(*pArgs, uint64_t);
+ fFlags |= RTSTR_F_64BIT;
+ break;
+ case sizeof(RTFAR32):
+ u.fp32 = va_arg(*pArgs, RTFAR32);
+ break;
+ case sizeof(RTFAR64):
+ u.fp64 = va_arg(*pArgs, RTFAR64);
+ break;
+ default:
+ AssertMsgFailed(("Invalid format error, size %d'!\n", s_aTypes[i].cb));
+ break;
+ }
+ }
+
+#ifndef DEBUG
+ /*
+ * For now don't show the address.
+ */
+ if (fFlags & RTSTR_F_OBFUSCATE_PTR)
+ {
+ cch = rtStrFormatKernelAddress(szBuf, sizeof(szBuf), u.uR0Ptr, cchWidth, cchPrecision, fFlags);
+ return pfnOutput(pvArgOutput, szBuf, cch);
+ }
+#endif
+
+ /*
+ * Format the output.
+ */
+ switch (s_aTypes[i].enmFormat)
+ {
+ case RTSF_INT:
+ {
+ cch = RTStrFormatNumber(szBuf, u.u64, s_aTypes[i].u8Base, cchWidth, cchPrecision, fFlags);
+ break;
+ }
+
+ /* hex which defaults to max width. */
+ case RTSF_INTW:
+ {
+ Assert(s_aTypes[i].u8Base == 16);
+ if (cchWidth < 0)
+ {
+ cchWidth = s_aTypes[i].cb * 2 + (fFlags & RTSTR_F_SPECIAL ? 2 : 0);
+ fFlags |= RTSTR_F_ZEROPAD;
+ }
+ cch = RTStrFormatNumber(szBuf, u.u64, s_aTypes[i].u8Base, cchWidth, cchPrecision, fFlags);
+ break;
+ }
+
+ case RTSF_BOOL:
+ {
+ static const char s_szTrue[] = "true ";
+ static const char s_szFalse[] = "false";
+ if (u.u64 == 1) /* 2021-03-19: Only trailing space for %#RTbool. */
+ return pfnOutput(pvArgOutput, s_szTrue, sizeof(s_szTrue) - (fFlags & RTSTR_F_SPECIAL ? 1 : 2));
+ if (u.u64 == 0)
+ return pfnOutput(pvArgOutput, s_szFalse, sizeof(s_szFalse) - 1);
+ /* invalid boolean value */
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "!%lld!", u.u64);
+ }
+
+ case RTSF_FP16:
+ {
+ fFlags &= ~(RTSTR_F_VALSIGNED | RTSTR_F_BIT_MASK | RTSTR_F_WIDTH | RTSTR_F_PRECISION | RTSTR_F_THOUSAND_SEP);
+ cch = RTStrFormatNumber(&szBuf[0], u.fp16.sel, 16, 4, -1, fFlags | RTSTR_F_16BIT);
+ Assert(cch == 4);
+ szBuf[4] = ':';
+ cch = RTStrFormatNumber(&szBuf[5], u.fp16.off, 16, 4, -1, fFlags | RTSTR_F_16BIT);
+ Assert(cch == 4);
+ cch = 4 + 1 + 4;
+ break;
+ }
+ case RTSF_FP32:
+ {
+ fFlags &= ~(RTSTR_F_VALSIGNED | RTSTR_F_BIT_MASK | RTSTR_F_WIDTH | RTSTR_F_PRECISION | RTSTR_F_THOUSAND_SEP);
+ cch = RTStrFormatNumber(&szBuf[0], u.fp32.sel, 16, 4, -1, fFlags | RTSTR_F_16BIT);
+ Assert(cch == 4);
+ szBuf[4] = ':';
+ cch = RTStrFormatNumber(&szBuf[5], u.fp32.off, 16, 8, -1, fFlags | RTSTR_F_32BIT);
+ Assert(cch == 8);
+ cch = 4 + 1 + 8;
+ break;
+ }
+ case RTSF_FP64:
+ {
+ fFlags &= ~(RTSTR_F_VALSIGNED | RTSTR_F_BIT_MASK | RTSTR_F_WIDTH | RTSTR_F_PRECISION | RTSTR_F_THOUSAND_SEP);
+ cch = RTStrFormatNumber(&szBuf[0], u.fp64.sel, 16, 4, -1, fFlags | RTSTR_F_16BIT);
+ Assert(cch == 4);
+ szBuf[4] = ':';
+ cch = RTStrFormatNumber(&szBuf[5], u.fp64.off, 16, 16, -1, fFlags | RTSTR_F_64BIT);
+ Assert(cch == 16);
+ cch = 4 + 1 + 16;
+ break;
+ }
+
+ case RTSF_IPV4:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "%u.%u.%u.%u",
+ u.Ipv4Addr.au8[0],
+ u.Ipv4Addr.au8[1],
+ u.Ipv4Addr.au8[2],
+ u.Ipv4Addr.au8[3]);
+
+ case RTSF_IPV6:
+ if (RT_VALID_PTR(u.pIpv6Addr))
+ return rtstrFormatIPv6(pfnOutput, pvArgOutput, u.pIpv6Addr);
+ return rtStrFormatBadPointer(0, pfnOutput, pvArgOutput, cchWidth, fFlags, u.pIpv6Addr,
+ szBuf, RT_STR_TUPLE("!BadIPv6"));
+
+ case RTSF_MAC:
+ if (RT_VALID_PTR(u.pMac))
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "%02x:%02x:%02x:%02x:%02x:%02x",
+ u.pMac->au8[0],
+ u.pMac->au8[1],
+ u.pMac->au8[2],
+ u.pMac->au8[3],
+ u.pMac->au8[4],
+ u.pMac->au8[5]);
+ return rtStrFormatBadPointer(0, pfnOutput, pvArgOutput, cchWidth, fFlags, u.pMac,
+ szBuf, RT_STR_TUPLE("!BadMac"));
+
+ case RTSF_NETADDR:
+ if (RT_VALID_PTR(u.pNetAddr))
+ {
+ switch (u.pNetAddr->enmType)
+ {
+ case RTNETADDRTYPE_IPV4:
+ if (u.pNetAddr->uPort == RTNETADDR_PORT_NA)
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "%u.%u.%u.%u",
+ u.pNetAddr->uAddr.IPv4.au8[0],
+ u.pNetAddr->uAddr.IPv4.au8[1],
+ u.pNetAddr->uAddr.IPv4.au8[2],
+ u.pNetAddr->uAddr.IPv4.au8[3]);
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "%u.%u.%u.%u:%u",
+ u.pNetAddr->uAddr.IPv4.au8[0],
+ u.pNetAddr->uAddr.IPv4.au8[1],
+ u.pNetAddr->uAddr.IPv4.au8[2],
+ u.pNetAddr->uAddr.IPv4.au8[3],
+ u.pNetAddr->uPort);
+
+ case RTNETADDRTYPE_IPV6:
+ if (u.pNetAddr->uPort == RTNETADDR_PORT_NA)
+ return rtstrFormatIPv6(pfnOutput, pvArgOutput, &u.pNetAddr->uAddr.IPv6);
+
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "[%RTnaipv6]:%u",
+ &u.pNetAddr->uAddr.IPv6,
+ u.pNetAddr->uPort);
+
+ case RTNETADDRTYPE_MAC:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "%02x:%02x:%02x:%02x:%02x:%02x",
+ u.pNetAddr->uAddr.Mac.au8[0],
+ u.pNetAddr->uAddr.Mac.au8[1],
+ u.pNetAddr->uAddr.Mac.au8[2],
+ u.pNetAddr->uAddr.Mac.au8[3],
+ u.pNetAddr->uAddr.Mac.au8[4],
+ u.pNetAddr->uAddr.Mac.au8[5]);
+
+ default:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "unsupported-netaddr-type=%u", u.pNetAddr->enmType);
+
+ }
+ }
+ return rtStrFormatBadPointer(0, pfnOutput, pvArgOutput, cchWidth, fFlags, u.pNetAddr,
+ szBuf, RT_STR_TUPLE("!BadNetAddr"));
+
+ case RTSF_UUID:
+ if (RT_VALID_PTR(u.pUuid))
+ /* cannot call RTUuidToStr because of GC/R0. */
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ RT_H2LE_U32(u.pUuid->Gen.u32TimeLow),
+ RT_H2LE_U16(u.pUuid->Gen.u16TimeMid),
+ RT_H2LE_U16(u.pUuid->Gen.u16TimeHiAndVersion),
+ u.pUuid->Gen.u8ClockSeqHiAndReserved,
+ u.pUuid->Gen.u8ClockSeqLow,
+ u.pUuid->Gen.au8Node[0],
+ u.pUuid->Gen.au8Node[1],
+ u.pUuid->Gen.au8Node[2],
+ u.pUuid->Gen.au8Node[3],
+ u.pUuid->Gen.au8Node[4],
+ u.pUuid->Gen.au8Node[5]);
+ return rtStrFormatBadPointer(0, pfnOutput, pvArgOutput, cchWidth, fFlags, u.pUuid,
+ szBuf, RT_STR_TUPLE("!BadUuid"));
+
+ case RTSF_ERRINFO:
+ case RTSF_ERRINFO_MSG_ONLY:
+ if (RT_VALID_PTR(u.pErrInfo) && RTErrInfoIsSet(u.pErrInfo))
+ {
+ cch = 0;
+ if (s_aTypes[i].enmFormat == RTSF_ERRINFO)
+ {
+#ifdef IN_RING3 /* we don't want this anywhere else yet. */
+ cch += RTErrFormatMsgShort(u.pErrInfo->rc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+#else
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%d", u.pErrInfo->rc);
+#endif
+ }
+
+ if (u.pErrInfo->cbMsg > 0)
+ {
+ if (fFlags & RTSTR_F_SPECIAL)
+ cch = pfnOutput(pvArgOutput, RT_STR_TUPLE(" - "));
+ else
+ cch = pfnOutput(pvArgOutput, RT_STR_TUPLE(": "));
+ cch += pfnOutput(pvArgOutput, u.pErrInfo->pszMsg,
+ RTStrNLen(u.pErrInfo->pszMsg, u.pErrInfo->cbMsg));
+ }
+ return cch;
+ }
+ return 0;
+
+ default:
+ AssertMsgFailed(("Internal error %d\n", s_aTypes[i].enmFormat));
+ return 0;
+ }
+
+ /*
+ * Finally, output the formatted string and return.
+ */
+ return pfnOutput(pvArgOutput, szBuf, cch);
+ }
+
+
+ /* Group 3 */
+
+ /*
+ * Base name printing, big endian UTF-16.
+ */
+ case 'b':
+ {
+ switch (*(*ppszFormat)++)
+ {
+ case 'n':
+ {
+ const char *pszLastSep;
+ const char *psz = pszLastSep = va_arg(*pArgs, const char *);
+ if (!RT_VALID_PTR(psz))
+ return rtStrFormatBadPointer(0, pfnOutput, pvArgOutput, cchWidth, fFlags, psz,
+ szBuf, RT_STR_TUPLE("!BadBaseName"));
+
+ while ((ch = *psz) != '\0')
+ {
+ if (RTPATH_IS_SEP(ch))
+ {
+ do
+ psz++;
+ while ((ch = *psz) != '\0' && RTPATH_IS_SEP(ch));
+ if (!ch)
+ break;
+ pszLastSep = psz;
+ }
+ psz++;
+ }
+
+ return pfnOutput(pvArgOutput, pszLastSep, psz - pszLastSep);
+ }
+
+ /* %lRbs */
+ case 's':
+ if (chArgSize == 'l')
+ {
+ /* utf-16BE -> utf-8 */
+ int cchStr;
+ PCRTUTF16 pwszStr = va_arg(*pArgs, PRTUTF16);
+
+ if (RT_VALID_PTR(pwszStr))
+ {
+ cchStr = 0;
+ while (cchStr < cchPrecision && pwszStr[cchStr] != '\0')
+ cchStr++;
+ }
+ else
+ {
+ static RTUTF16 s_wszBigNull[] =
+ {
+ RT_H2BE_U16_C((uint16_t)'<'), RT_H2BE_U16_C((uint16_t)'N'), RT_H2BE_U16_C((uint16_t)'U'),
+ RT_H2BE_U16_C((uint16_t)'L'), RT_H2BE_U16_C((uint16_t)'L'), RT_H2BE_U16_C((uint16_t)'>'), '\0'
+ };
+ pwszStr = s_wszBigNull;
+ cchStr = RT_ELEMENTS(s_wszBigNull) - 1;
+ }
+
+ cch = 0;
+ if (!(fFlags & RTSTR_F_LEFT))
+ while (--cchWidth >= cchStr)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+ cchWidth -= cchStr;
+ while (cchStr-- > 0)
+ {
+/** @todo \#ifndef IN_RC*/
+#ifdef IN_RING3
+ RTUNICP Cp = 0;
+ RTUtf16BigGetCpEx(&pwszStr, &Cp);
+ char *pszEnd = RTStrPutCp(szBuf, Cp);
+ *pszEnd = '\0';
+ cch += pfnOutput(pvArgOutput, szBuf, pszEnd - szBuf);
+#else
+ szBuf[0] = (char)(*pwszStr++ >> 8);
+ cch += pfnOutput(pvArgOutput, szBuf, 1);
+#endif
+ }
+ while (--cchWidth >= 0)
+ cch += pfnOutput(pvArgOutput, " ", 1);
+ return cch;
+ }
+ RT_FALL_THRU();
+
+ default:
+ AssertMsgFailed(("Invalid status code format type '%.10s'!\n", pszFormatOrg));
+ break;
+ }
+ break;
+ }
+
+
+ /*
+ * Pretty function / method name printing.
+ */
+ case 'f':
+ {
+ switch (*(*ppszFormat)++)
+ {
+ /*
+ * Pretty function / method name printing.
+ * This isn't 100% right (see classic signal prototype) and it assumes
+ * standardized names, but it'll do for today.
+ */
+ case 'n':
+ {
+ const char *pszStart;
+ const char *psz = pszStart = va_arg(*pArgs, const char *);
+ int cAngle = 0;
+
+ if (!RT_VALID_PTR(psz))
+ return rtStrFormatBadPointer(0, pfnOutput, pvArgOutput, cchWidth, fFlags, psz,
+ szBuf, RT_STR_TUPLE("!BadFnNm"));
+
+ while ((ch = *psz) != '\0' && ch != '(')
+ {
+ if (RT_C_IS_BLANK(ch))
+ {
+ psz++;
+ while ((ch = *psz) != '\0' && (RT_C_IS_BLANK(ch) || ch == '('))
+ psz++;
+ if (ch && cAngle == 0)
+ pszStart = psz;
+ }
+ else if (ch == '(')
+ break;
+ else if (ch == '<')
+ {
+ cAngle++;
+ psz++;
+ }
+ else if (ch == '>')
+ {
+ cAngle--;
+ psz++;
+ }
+ else
+ psz++;
+ }
+
+ return pfnOutput(pvArgOutput, pszStart, psz - pszStart);
+ }
+
+ default:
+ AssertMsgFailed(("Invalid status code format type '%.10s'!\n", pszFormatOrg));
+ break;
+ }
+ break;
+ }
+
+
+ /*
+ * hex dumping, COM/XPCOM, human readable sizes.
+ */
+ case 'h':
+ {
+ ch = *(*ppszFormat)++;
+ switch (ch)
+ {
+ /*
+ * Hex stuff.
+ */
+ case 'x':
+ case 'X':
+ {
+ uint8_t *pu8 = va_arg(*pArgs, uint8_t *);
+ uint64_t uMemAddr;
+ int cchMemAddrWidth;
+
+ if (cchPrecision < 0)
+ cchPrecision = 16;
+
+ if (ch == 'x')
+ {
+ uMemAddr = (uintptr_t)pu8;
+ cchMemAddrWidth = sizeof(pu8) * 2;
+ }
+ else
+ {
+ uMemAddr = va_arg(*pArgs, uint64_t);
+ cchMemAddrWidth = uMemAddr > UINT32_MAX || uMemAddr + cchPrecision > UINT32_MAX ? 16 : 8;
+ }
+
+ if (pu8)
+ {
+ switch (*(*ppszFormat)++)
+ {
+ /*
+ * Regular hex dump.
+ */
+ case 'd':
+ {
+ int off = 0;
+ cch = 0;
+
+ if (cchWidth <= 0)
+ cchWidth = 16;
+
+ while (off < cchPrecision)
+ {
+ int i;
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%s%0*llx/%04x:",
+ off ? "\n" : "", cchMemAddrWidth, uMemAddr + off, off);
+ for (i = 0; i < cchWidth && off + i < cchPrecision ; i++)
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ off + i < cchPrecision ? !(i & 7) && i ? "-%02x" : " %02x" : " ",
+ pu8[i]);
+ while (i++ < cchWidth)
+ cch += pfnOutput(pvArgOutput, " ", 3);
+
+ cch += pfnOutput(pvArgOutput, " ", 1);
+
+ for (i = 0; i < cchWidth && off + i < cchPrecision; i++)
+ {
+ uint8_t u8 = pu8[i];
+ cch += pfnOutput(pvArgOutput, u8 < 127 && u8 >= 32 ? (const char *)&u8 : ".", 1);
+ }
+
+ /* next */
+ pu8 += cchWidth;
+ off += cchWidth;
+ }
+ return cch;
+ }
+
+ /*
+ * Regular hex dump with dittoing.
+ */
+ case 'D':
+ {
+ int offEndDupCheck;
+ int cDuplicates = 0;
+ int off = 0;
+ cch = 0;
+
+ if (cchWidth <= 0)
+ cchWidth = 16;
+ offEndDupCheck = cchPrecision - cchWidth;
+
+ while (off < cchPrecision)
+ {
+ int i;
+ if ( off >= offEndDupCheck
+ || off <= 0
+ || memcmp(pu8, pu8 - cchWidth, cchWidth) != 0
+ || ( cDuplicates == 0
+ && ( off + cchWidth >= offEndDupCheck
+ || memcmp(pu8 + cchWidth, pu8, cchWidth) != 0)) )
+ {
+ if (cDuplicates > 0)
+ {
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "\n%.*s **** <ditto x %u>",
+ cchMemAddrWidth, "****************", cDuplicates);
+ cDuplicates = 0;
+ }
+
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%s%0*llx/%04x:",
+ off ? "\n" : "", cchMemAddrWidth, uMemAddr + off, off);
+ for (i = 0; i < cchWidth && off + i < cchPrecision ; i++)
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0,
+ off + i < cchPrecision ? !(i & 7) && i
+ ? "-%02x" : " %02x" : " ",
+ pu8[i]);
+ while (i++ < cchWidth)
+ cch += pfnOutput(pvArgOutput, " ", 3);
+
+ cch += pfnOutput(pvArgOutput, " ", 1);
+
+ for (i = 0; i < cchWidth && off + i < cchPrecision; i++)
+ {
+ uint8_t u8 = pu8[i];
+ cch += pfnOutput(pvArgOutput, u8 < 127 && u8 >= 32 ? (const char *)&u8 : ".", 1);
+ }
+ }
+ else
+ cDuplicates++;
+
+ /* next */
+ pu8 += cchWidth;
+ off += cchWidth;
+ }
+ return cch;
+ }
+
+ /*
+ * Hex string.
+ * The default separator is ' ', RTSTR_F_THOUSAND_SEP changes it to ':',
+ * and RTSTR_F_SPECIAL removes it.
+ */
+ case 's':
+ {
+ if (cchPrecision-- > 0)
+ {
+ if (ch == 'x')
+ cch = RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%02x", *pu8++);
+ else
+ cch = RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%0*llx: %02x",
+ cchMemAddrWidth, uMemAddr, *pu8++);
+ if (!(fFlags & (RTSTR_F_SPECIAL | RTSTR_F_THOUSAND_SEP)))
+ for (; cchPrecision > 0; cchPrecision--, pu8++)
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, " %02x", *pu8);
+ else if (fFlags & RTSTR_F_SPECIAL)
+ for (; cchPrecision > 0; cchPrecision--, pu8++)
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%02x", *pu8);
+ else
+ for (; cchPrecision > 0; cchPrecision--, pu8++)
+ cch += RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, ":%02x", *pu8);
+ return cch;
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("Invalid status code format type '%.10s'!\n", pszFormatOrg));
+ break;
+ }
+ }
+ else
+ return pfnOutput(pvArgOutput, RT_STR_TUPLE("<null>"));
+ break;
+ }
+
+
+#ifdef IN_RING3
+ /*
+ * XPCOM / COM status code: %Rhrc, %Rhrf, %Rhra
+ * ASSUMES: If Windows Then COM else XPCOM.
+ */
+ case 'r':
+ {
+ uint32_t hrc = va_arg(*pArgs, uint32_t);
+# ifndef RT_OS_WINDOWS
+ PCRTCOMERRMSG pMsg = RTErrCOMGet(hrc);
+# endif
+ switch (*(*ppszFormat)++)
+ {
+# ifdef RT_OS_WINDOWS
+ case 'c':
+ return RTErrWinFormatDefine(hrc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+ case 'f':
+ return RTErrWinFormatMsg(hrc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+ case 'a':
+ return RTErrWinFormatMsgAll(hrc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+# else /* !RT_OS_WINDOWS */
+ case 'c':
+ return pfnOutput(pvArgOutput, pMsg->pszDefine, strlen(pMsg->pszDefine));
+ case 'f':
+ return pfnOutput(pvArgOutput, pMsg->pszMsgFull, strlen(pMsg->pszMsgFull));
+ case 'a':
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%s (0x%08X) - %s", pMsg->pszDefine, hrc, pMsg->pszMsgFull);
+# endif /* !RT_OS_WINDOWS */
+ default:
+ AssertMsgFailed(("Invalid status code format type '%.10s'!\n", pszFormatOrg));
+ return 0;
+ }
+ break;
+ }
+#endif /* IN_RING3 */
+
+ /*
+ * Human readable sizes.
+ */
+ case 'c':
+ case 'u':
+ {
+ unsigned i;
+ ssize_t cchBuf;
+ uint64_t uValue;
+ uint64_t uFraction = 0;
+ const char *pszPrefix = NULL;
+ char ch2 = *(*ppszFormat)++;
+ AssertMsgReturn(ch2 == 'b' || ch2 == 'B' || ch2 == 'i', ("invalid type '%.10s'!\n", pszFormatOrg), 0);
+ uValue = va_arg(*pArgs, uint64_t);
+
+ if (!(fFlags & RTSTR_F_PRECISION))
+ cchPrecision = 1; /** @todo default to flexible decimal point. */
+ else if (cchPrecision > 3)
+ cchPrecision = 3;
+ else if (cchPrecision < 0)
+ cchPrecision = 0;
+
+ if (ch2 == 'b' || ch2 == 'B')
+ {
+ static const struct
+ {
+ const char *pszPrefix;
+ uint8_t cShift;
+ uint64_t cbMin;
+ uint64_t cbMinZeroPrecision;
+ } s_aUnits[] =
+ {
+ { "Ei", 60, _1E, _1E*2 },
+ { "Pi", 50, _1P, _1P*2 },
+ { "Ti", 40, _1T, _1T*2 },
+ { "Gi", 30, _1G, _1G64*2 },
+ { "Mi", 20, _1M, _1M*2 },
+ { "Ki", 10, _1K, _1K*2 },
+ };
+ for (i = 0; i < RT_ELEMENTS(s_aUnits); i++)
+ if ( uValue >= s_aUnits[i].cbMin
+ && (cchPrecision > 0 || uValue >= s_aUnits[i].cbMinZeroPrecision))
+ {
+ if (cchPrecision != 0)
+ {
+ uFraction = uValue & (RT_BIT_64(s_aUnits[i].cShift) - 1);
+ uFraction *= cchPrecision == 1 ? 10 : cchPrecision == 2 ? 100 : 1000;
+ uFraction >>= s_aUnits[i].cShift;
+ }
+ uValue >>= s_aUnits[i].cShift;
+ pszPrefix = s_aUnits[i].pszPrefix;
+ break;
+ }
+ }
+ else
+ {
+ static const struct
+ {
+ const char *pszPrefix;
+ uint64_t cbFactor;
+ uint64_t cbMinZeroPrecision;
+ } s_aUnits[] =
+ {
+ { "E", UINT64_C(1000000000000000000), UINT64_C(1010000000000000000), },
+ { "P", UINT64_C(1000000000000000), UINT64_C(1010000000000000), },
+ { "T", UINT64_C(1000000000000), UINT64_C(1010000000000), },
+ { "G", UINT64_C(1000000000), UINT64_C(1010000000), },
+ { "M", UINT64_C(1000000), UINT64_C(1010000), },
+ { "k", UINT64_C(1000), UINT64_C(1010), },
+ };
+ for (i = 0; i < RT_ELEMENTS(s_aUnits); i++)
+ if ( uValue >= s_aUnits[i].cbFactor
+ && (cchPrecision > 0 || uValue >= s_aUnits[i].cbMinZeroPrecision))
+ {
+ if (cchPrecision == 0)
+ uValue /= s_aUnits[i].cbFactor;
+ else
+ {
+ uFraction = uValue % s_aUnits[i].cbFactor;
+ uValue = uValue / s_aUnits[i].cbFactor;
+ uFraction *= cchPrecision == 1 ? 10 : cchPrecision == 2 ? 100 : 1000;
+ uFraction += s_aUnits[i].cbFactor >> 1;
+ uFraction /= s_aUnits[i].cbFactor;
+ }
+ pszPrefix = s_aUnits[i].pszPrefix;
+ break;
+ }
+ }
+
+ cchBuf = RTStrFormatU64(szBuf, sizeof(szBuf), uValue, 10, 0, 0, 0);
+ if (pszPrefix)
+ {
+ if (cchPrecision)
+ {
+ szBuf[cchBuf++] = '.';
+ cchBuf += RTStrFormatU64(&szBuf[cchBuf], sizeof(szBuf) - cchBuf, uFraction, 10, cchPrecision, 0,
+ RTSTR_F_ZEROPAD | RTSTR_F_WIDTH);
+ }
+ if (fFlags & RTSTR_F_BLANK)
+ szBuf[cchBuf++] = ' ';
+ szBuf[cchBuf++] = *pszPrefix++;
+ if (*pszPrefix && ch2 != 'B')
+ szBuf[cchBuf++] = *pszPrefix;
+ }
+ else if (fFlags & RTSTR_F_BLANK)
+ szBuf[cchBuf++] = ' ';
+ if (ch == 'c')
+ szBuf[cchBuf++] = 'B';
+ szBuf[cchBuf] = '\0';
+
+ cch = 0;
+ if ((fFlags & RTSTR_F_WIDTH) && !(fFlags & RTSTR_F_LEFT))
+ while (cchBuf < cchWidth)
+ {
+ cch += pfnOutput(pvArgOutput, fFlags & RTSTR_F_ZEROPAD ? "0" : " ", 1);
+ cchWidth--;
+ }
+ cch += pfnOutput(pvArgOutput, szBuf, cchBuf);
+ return cch;
+ }
+
+ default:
+ AssertMsgFailed(("Invalid status code format type '%.10s'!\n", pszFormatOrg));
+ return 0;
+
+ }
+ break;
+ }
+
+ /*
+ * iprt status code: %Rrc, %Rrs, %Rrf, %Rra.
+ */
+ case 'r':
+ {
+ int rc = va_arg(*pArgs, int);
+#ifdef IN_RING3 /* we don't want this anywhere else yet. */
+ switch (*(*ppszFormat)++)
+ {
+ case 'c':
+ return RTErrFormatDefine(rc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+ case 's':
+ return RTErrFormatMsgShort(rc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+ case 'f':
+ return RTErrFormatMsgFull(rc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+ case 'a':
+ return RTErrFormatMsgAll(rc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+ default:
+ AssertMsgFailed(("Invalid status code format type '%.10s'!\n", pszFormatOrg));
+ return 0;
+ }
+#else /* !IN_RING3 */
+ switch (*(*ppszFormat)++)
+ {
+ case 'c':
+ case 's':
+ case 'f':
+ case 'a':
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "%d", rc);
+ default:
+ AssertMsgFailed(("Invalid status code format type '%.10s'!\n", pszFormatOrg));
+ return 0;
+ }
+#endif /* !IN_RING3 */
+ break;
+ }
+
+#if defined(IN_RING3)
+ /*
+ * Windows status code: %Rwc, %Rwf, %Rwa
+ */
+ case 'w':
+ {
+ long rc = va_arg(*pArgs, long);
+ switch (*(*ppszFormat)++)
+ {
+# if defined(RT_OS_WINDOWS)
+ case 'c':
+ return RTErrWinFormatDefine(rc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+ case 'f':
+ return RTErrWinFormatMsg(rc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+ case 'a':
+ return RTErrWinFormatMsgAll(rc, pfnOutput, pvArgOutput, szBuf, sizeof(szBuf));
+# else /* !RT_OS_WINDOWS */
+ case 'c':
+ case 'f':
+ case 'a':
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, 0, "0x%08x", rc);
+# endif /* !RT_OS_WINDOWS */
+ default:
+ AssertMsgFailed(("Invalid status code format type '%.10s'!\n", pszFormatOrg));
+ return 0;
+ }
+ break;
+ }
+#endif /* IN_RING3 */
+
+ /*
+ * Group 4, structure dumpers.
+ */
+ case 'D':
+ {
+ /*
+ * Interpret the type.
+ */
+ typedef enum
+ {
+ RTST_TIMESPEC
+ } RTST;
+/** Set if it's a pointer */
+#define RTST_FLAGS_POINTER RT_BIT(0)
+ static const struct
+ {
+ uint8_t cch; /**< the length of the string. */
+ char sz[16-2]; /**< the part following 'R'. */
+ uint8_t cb; /**< the size of the argument. */
+ uint8_t fFlags; /**< RTST_FLAGS_* */
+ RTST enmType; /**< The structure type. */
+ }
+ /** Sorted array of types, looked up using binary search! */
+ s_aTypes[] =
+ {
+#define STRMEM(str) sizeof(str) - 1, str
+ { STRMEM("Dtimespec"), sizeof(PCRTTIMESPEC), RTST_FLAGS_POINTER, RTST_TIMESPEC},
+#undef STRMEM
+ };
+ const char *pszType = *ppszFormat - 1;
+ int iStart = 0;
+ int iEnd = RT_ELEMENTS(s_aTypes) - 1;
+ int i = RT_ELEMENTS(s_aTypes) / 2;
+
+ union
+ {
+ const void *pv;
+ uint64_t u64;
+ PCRTTIMESPEC pTimeSpec;
+ } u;
+
+ AssertMsg(!chArgSize, ("Not argument size '%c' for RT types! '%.10s'\n", chArgSize, pszFormatOrg));
+
+ /*
+ * Lookup the type - binary search.
+ */
+ for (;;)
+ {
+ int iDiff = strncmp(pszType, s_aTypes[i].sz, s_aTypes[i].cch);
+ if (!iDiff)
+ break;
+ if (iEnd == iStart)
+ {
+ AssertMsgFailed(("Invalid format type '%.10s'!\n", pszFormatOrg));
+ return 0;
+ }
+ if (iDiff < 0)
+ iEnd = i - 1;
+ else
+ iStart = i + 1;
+ if (iEnd < iStart)
+ {
+ AssertMsgFailed(("Invalid format type '%.10s'!\n", pszFormatOrg));
+ return 0;
+ }
+ i = iStart + (iEnd - iStart) / 2;
+ }
+ *ppszFormat += s_aTypes[i].cch - 1;
+
+ /*
+ * Fetch the argument.
+ */
+ u.u64 = 0;
+ switch (s_aTypes[i].cb)
+ {
+ case sizeof(const void *):
+ u.pv = va_arg(*pArgs, const void *);
+ break;
+ default:
+ AssertMsgFailed(("Invalid format error, size %d'!\n", s_aTypes[i].cb));
+ break;
+ }
+
+ /*
+ * If it's a pointer, we'll check if it's valid before going on.
+ */
+ if ((s_aTypes[i].fFlags & RTST_FLAGS_POINTER) && !RT_VALID_PTR(u.pv))
+ return rtStrFormatBadPointer(0, pfnOutput, pvArgOutput, cchWidth, fFlags, u.pv,
+ szBuf, RT_STR_TUPLE("!BadRD"));
+
+ /*
+ * Format the output.
+ */
+ switch (s_aTypes[i].enmType)
+ {
+ case RTST_TIMESPEC:
+ return RTStrFormat(pfnOutput, pvArgOutput, NULL, NULL, "%'lld ns", RTTimeSpecGetNano(u.pTimeSpec));
+
+ default:
+ AssertMsgFailed(("Invalid/unhandled enmType=%d\n", s_aTypes[i].enmType));
+ break;
+ }
+ break;
+ }
+
+#ifdef IN_RING3
+
+ /*
+ * Group 5, XML / HTML, JSON and URI escapers.
+ */
+ case 'M':
+ {
+ char chWhat = (*ppszFormat)[0];
+ if (chWhat == 'a' || chWhat == 'e')
+ {
+ /* XML attributes and element values. */
+ bool fAttr = chWhat == 'a';
+ char chType = (*ppszFormat)[1];
+ *ppszFormat += 2;
+ switch (chType)
+ {
+ case 's':
+ {
+ static const char s_szElemEscape[] = "<>&\"'";
+ static const char s_szAttrEscape[] = "<>&\"\n\r"; /* more? */
+ const char * const pszEscape = fAttr ? s_szAttrEscape : s_szElemEscape;
+ size_t const cchEscape = (fAttr ? RT_ELEMENTS(s_szAttrEscape) : RT_ELEMENTS(s_szElemEscape)) - 1;
+ size_t cchOutput = 0;
+ const char *pszStr = va_arg(*pArgs, char *);
+ ssize_t cchStr;
+ ssize_t offCur;
+ ssize_t offLast;
+
+ if (!RT_VALID_PTR(pszStr))
+ pszStr = "<NULL>";
+ cchStr = RTStrNLen(pszStr, (unsigned)cchPrecision);
+
+ if (fAttr)
+ cchOutput += pfnOutput(pvArgOutput, "\"", 1);
+ if (!(fFlags & RTSTR_F_LEFT))
+ while (--cchWidth >= cchStr)
+ cchOutput += pfnOutput(pvArgOutput, " ", 1);
+
+ offLast = offCur = 0;
+ while (offCur < cchStr)
+ {
+ if (memchr(pszEscape, pszStr[offCur], cchEscape))
+ {
+ if (offLast < offCur)
+ cchOutput += pfnOutput(pvArgOutput, &pszStr[offLast], offCur - offLast);
+ switch (pszStr[offCur])
+ {
+ case '<': cchOutput += pfnOutput(pvArgOutput, "&lt;", 4); break;
+ case '>': cchOutput += pfnOutput(pvArgOutput, "&gt;", 4); break;
+ case '&': cchOutput += pfnOutput(pvArgOutput, "&amp;", 5); break;
+ case '\'': cchOutput += pfnOutput(pvArgOutput, "&apos;", 6); break;
+ case '"': cchOutput += pfnOutput(pvArgOutput, "&quot;", 6); break;
+ case '\n': cchOutput += pfnOutput(pvArgOutput, "&#xA;", 5); break;
+ case '\r': cchOutput += pfnOutput(pvArgOutput, "&#xD;", 5); break;
+ default:
+ AssertFailed();
+ }
+ offLast = offCur + 1;
+ }
+ offCur++;
+ }
+ if (offLast < offCur)
+ cchOutput += pfnOutput(pvArgOutput, &pszStr[offLast], offCur - offLast);
+
+ while (--cchWidth >= cchStr)
+ cchOutput += pfnOutput(pvArgOutput, " ", 1);
+ if (fAttr)
+ cchOutput += pfnOutput(pvArgOutput, "\"", 1);
+ return cchOutput;
+ }
+
+ default:
+ AssertMsgFailed(("Invalid IPRT format type '%.10s'!\n", pszFormatOrg));
+ }
+ }
+ else if (chWhat == 'j')
+ {
+ /* JSON string escaping. */
+ char const chType = (*ppszFormat)[1];
+ *ppszFormat += 2;
+ switch (chType)
+ {
+ case 's':
+ {
+ const char *pszStr = va_arg(*pArgs, char *);
+ size_t cchOutput;
+ ssize_t cchStr;
+ ssize_t offCur;
+ ssize_t offLast;
+
+ if (!RT_VALID_PTR(pszStr))
+ pszStr = "<NULL>";
+ cchStr = RTStrNLen(pszStr, (unsigned)cchPrecision);
+
+ cchOutput = pfnOutput(pvArgOutput, "\"", 1);
+ if (!(fFlags & RTSTR_F_LEFT))
+ while (--cchWidth >= cchStr)
+ cchOutput += pfnOutput(pvArgOutput, " ", 1);
+
+ offLast = offCur = 0;
+ while (offCur < cchStr)
+ {
+ unsigned int const uch = pszStr[offCur];
+ if ( uch >= 0x5d
+ || (uch >= 0x20 && uch != 0x22 && uch != 0x5c))
+ offCur++;
+ else
+ {
+ if (offLast < offCur)
+ cchOutput += pfnOutput(pvArgOutput, &pszStr[offLast], offCur - offLast);
+ switch ((char)uch)
+ {
+ case '"': cchOutput += pfnOutput(pvArgOutput, "\\\"", 2); break;
+ case '\\': cchOutput += pfnOutput(pvArgOutput, "\\\\", 2); break;
+ case '/': cchOutput += pfnOutput(pvArgOutput, "\\/", 2); break;
+ case '\b': cchOutput += pfnOutput(pvArgOutput, "\\b", 2); break;
+ case '\f': cchOutput += pfnOutput(pvArgOutput, "\\f", 2); break;
+ case '\n': cchOutput += pfnOutput(pvArgOutput, "\\n", 2); break;
+ case '\t': cchOutput += pfnOutput(pvArgOutput, "\\t", 2); break;
+ default:
+ {
+ RTUNICP uc = 0xfffd; /* replacement character */
+ const char *pszCur = &pszStr[offCur];
+ int rc = RTStrGetCpEx(&pszCur, &uc);
+ if (RT_SUCCESS(rc))
+ offCur += pszCur - &pszStr[offCur] - 1;
+ if (uc >= 0xfffe)
+ uc = 0xfffd; /* replacement character */
+ szBuf[0] = '\\';
+ szBuf[1] = 'u';
+ szBuf[2] = g_szHexDigits[(uc >> 12) & 0xf];
+ szBuf[3] = g_szHexDigits[(uc >> 8) & 0xf];
+ szBuf[4] = g_szHexDigits[(uc >> 4) & 0xf];
+ szBuf[5] = g_szHexDigits[ uc & 0xf];
+ szBuf[6] = '\0';
+ cchOutput += pfnOutput(pvArgOutput, szBuf, 6);
+ break;
+ }
+ }
+ offLast = ++offCur;
+ }
+ }
+ if (offLast < offCur)
+ cchOutput += pfnOutput(pvArgOutput, &pszStr[offLast], offCur - offLast);
+
+ while (--cchWidth >= cchStr)
+ cchOutput += pfnOutput(pvArgOutput, " ", 1);
+ cchOutput += pfnOutput(pvArgOutput, "\"", 1);
+ return cchOutput;
+ }
+
+ default:
+ AssertMsgFailed(("Invalid IPRT format type '%.10s'!\n", pszFormatOrg));
+ }
+ }
+ else if (chWhat == 'p')
+ {
+ /* Percent encoded string (RTC-3986). */
+ char const chVariant = (*ppszFormat)[1];
+ char const chAddSafe = chVariant == 'p' ? '/'
+ : chVariant == 'q' ? '+' /* '+' in queries is problematic, so no escape. */
+ : '~' /* whatever */;
+ size_t cchOutput = 0;
+ const char *pszStr = va_arg(*pArgs, char *);
+ ssize_t cchStr;
+ ssize_t offCur;
+ ssize_t offLast;
+
+ *ppszFormat += 2;
+ AssertMsgBreak(chVariant == 'a' || chVariant == 'p' || chVariant == 'q' || chVariant == 'f',
+ ("Invalid IPRT format type '%.10s'!\n", pszFormatOrg));
+
+ if (!RT_VALID_PTR(pszStr))
+ pszStr = "<NULL>";
+ cchStr = RTStrNLen(pszStr, (unsigned)cchPrecision);
+
+ if (!(fFlags & RTSTR_F_LEFT))
+ while (--cchWidth >= cchStr)
+ cchOutput += pfnOutput(pvArgOutput, "%20", 3);
+
+ offLast = offCur = 0;
+ while (offCur < cchStr)
+ {
+ ch = pszStr[offCur];
+ if ( RT_C_IS_ALPHA(ch)
+ || RT_C_IS_DIGIT(ch)
+ || ch == '-'
+ || ch == '.'
+ || ch == '_'
+ || ch == '~'
+ || ch == chAddSafe)
+ offCur++;
+ else
+ {
+ if (offLast < offCur)
+ cchOutput += pfnOutput(pvArgOutput, &pszStr[offLast], offCur - offLast);
+ if (ch != ' ' || chVariant != 'f')
+ {
+ szBuf[0] = '%';
+ szBuf[1] = g_szHexDigitsUpper[((uint8_t)ch >> 4) & 0xf];
+ szBuf[2] = g_szHexDigitsUpper[(uint8_t)ch & 0xf];
+ szBuf[3] = '\0';
+ cchOutput += pfnOutput(pvArgOutput, szBuf, 3);
+ }
+ else
+ cchOutput += pfnOutput(pvArgOutput, "+", 1);
+ offLast = ++offCur;
+ }
+ }
+ if (offLast < offCur)
+ cchOutput += pfnOutput(pvArgOutput, &pszStr[offLast], offCur - offLast);
+
+ while (--cchWidth >= cchStr)
+ cchOutput += pfnOutput(pvArgOutput, "%20", 3);
+ }
+ else
+ AssertMsgFailed(("Invalid IPRT format type '%.10s'!\n", pszFormatOrg));
+ break;
+ }
+
+#endif /* IN_RING3 */
+
+ /*
+ * Groups 6 - CPU Architecture Register Formatters.
+ * "%RAarch[reg]"
+ */
+ case 'A':
+ {
+ char const * const pszArch = *ppszFormat;
+ const char *pszReg = pszArch;
+ size_t cchOutput = 0;
+ int cPrinted = 0;
+ size_t cchReg;
+
+ /* Parse out the */
+ while ((ch = *pszReg++) && ch != '[')
+ { /* nothing */ }
+ AssertMsgBreak(ch == '[', ("Malformed IPRT architecture register format type '%.10s'!\n", pszFormatOrg));
+
+ cchReg = 0;
+ while ((ch = pszReg[cchReg]) && ch != ']')
+ cchReg++;
+ AssertMsgBreak(ch == ']', ("Malformed IPRT architecture register format type '%.10s'!\n", pszFormatOrg));
+
+ *ppszFormat = &pszReg[cchReg + 1];
+
+
+#define REG_EQUALS(a_szReg) (sizeof(a_szReg) - 1 == cchReg && !strncmp(a_szReg, pszReg, sizeof(a_szReg) - 1))
+#define REG_OUT_BIT(a_uVal, a_fBitMask, a_szName) \
+ do { \
+ if ((a_uVal) & (a_fBitMask)) \
+ { \
+ if (!cPrinted++) \
+ cchOutput += pfnOutput(pvArgOutput, "{" a_szName, sizeof(a_szName)); \
+ else \
+ cchOutput += pfnOutput(pvArgOutput, "," a_szName, sizeof(a_szName)); \
+ (a_uVal) &= ~(a_fBitMask); \
+ } \
+ } while (0)
+#define REG_OUT_CLOSE(a_uVal) \
+ do { \
+ if ((a_uVal)) \
+ { \
+ cchOutput += pfnOutput(pvArgOutput, !cPrinted ? "{unkn=" : ",unkn=", 6); \
+ cch = RTStrFormatNumber(&szBuf[0], (a_uVal), 16, 1, -1, fFlags); \
+ cchOutput += pfnOutput(pvArgOutput, szBuf, cch); \
+ cPrinted++; \
+ } \
+ if (cPrinted) \
+ cchOutput += pfnOutput(pvArgOutput, "}", 1); \
+ } while (0)
+
+
+ if (0)
+ { /* dummy */ }
+#ifdef STRFORMAT_WITH_X86
+ /*
+ * X86 & AMD64.
+ */
+ else if ( pszReg - pszArch == 3 + 1
+ && pszArch[0] == 'x'
+ && pszArch[1] == '8'
+ && pszArch[2] == '6')
+ {
+ if (REG_EQUALS("cr0"))
+ {
+ uint64_t cr0 = va_arg(*pArgs, uint64_t);
+ fFlags |= RTSTR_F_64BIT;
+ cch = RTStrFormatNumber(&szBuf[0], cr0, 16, 8, -1, fFlags | RTSTR_F_ZEROPAD);
+ cchOutput += pfnOutput(pvArgOutput, szBuf, cch);
+ REG_OUT_BIT(cr0, X86_CR0_PE, "PE");
+ REG_OUT_BIT(cr0, X86_CR0_MP, "MP");
+ REG_OUT_BIT(cr0, X86_CR0_EM, "EM");
+ REG_OUT_BIT(cr0, X86_CR0_TS, "DE");
+ REG_OUT_BIT(cr0, X86_CR0_ET, "ET");
+ REG_OUT_BIT(cr0, X86_CR0_NE, "NE");
+ REG_OUT_BIT(cr0, X86_CR0_WP, "WP");
+ REG_OUT_BIT(cr0, X86_CR0_AM, "AM");
+ REG_OUT_BIT(cr0, X86_CR0_NW, "NW");
+ REG_OUT_BIT(cr0, X86_CR0_CD, "CD");
+ REG_OUT_BIT(cr0, X86_CR0_PG, "PG");
+ REG_OUT_CLOSE(cr0);
+ }
+ else if (REG_EQUALS("cr4"))
+ {
+ uint64_t cr4 = va_arg(*pArgs, uint64_t);
+ fFlags |= RTSTR_F_64BIT;
+ cch = RTStrFormatNumber(&szBuf[0], cr4, 16, 8, -1, fFlags | RTSTR_F_ZEROPAD);
+ cchOutput += pfnOutput(pvArgOutput, szBuf, cch);
+ REG_OUT_BIT(cr4, X86_CR4_VME, "VME");
+ REG_OUT_BIT(cr4, X86_CR4_PVI, "PVI");
+ REG_OUT_BIT(cr4, X86_CR4_TSD, "TSD");
+ REG_OUT_BIT(cr4, X86_CR4_DE, "DE");
+ REG_OUT_BIT(cr4, X86_CR4_PSE, "PSE");
+ REG_OUT_BIT(cr4, X86_CR4_PAE, "PAE");
+ REG_OUT_BIT(cr4, X86_CR4_MCE, "MCE");
+ REG_OUT_BIT(cr4, X86_CR4_PGE, "PGE");
+ REG_OUT_BIT(cr4, X86_CR4_PCE, "PCE");
+ REG_OUT_BIT(cr4, X86_CR4_OSFXSR, "OSFXSR");
+ REG_OUT_BIT(cr4, X86_CR4_OSXMMEEXCPT, "OSXMMEEXCPT");
+ REG_OUT_BIT(cr4, X86_CR4_VMXE, "VMXE");
+ REG_OUT_BIT(cr4, X86_CR4_SMXE, "SMXE");
+ REG_OUT_BIT(cr4, X86_CR4_PCIDE, "PCIDE");
+ REG_OUT_BIT(cr4, X86_CR4_OSXSAVE, "OSXSAVE");
+ REG_OUT_BIT(cr4, X86_CR4_SMEP, "SMEP");
+ REG_OUT_BIT(cr4, X86_CR4_SMAP, "SMAP");
+ REG_OUT_CLOSE(cr4);
+ }
+ else
+ AssertMsgFailed(("Unknown x86 register specified in '%.10s'!\n", pszFormatOrg));
+ }
+#endif
+ else
+ AssertMsgFailed(("Unknown architecture specified in '%.10s'!\n", pszFormatOrg));
+#undef REG_OUT_BIT
+#undef REG_OUT_CLOSE
+#undef REG_EQUALS
+ return cchOutput;
+ }
+
+ /*
+ * Invalid/Unknown. Bitch about it.
+ */
+ default:
+ AssertMsgFailed(("Invalid IPRT format type '%.10s'!\n", pszFormatOrg));
+ break;
+ }
+ }
+ else
+ AssertMsgFailed(("Invalid IPRT format type '%.10s'!\n", pszFormatOrg));
+
+ NOREF(pszFormatOrg);
+ return 0;
+}
+
diff --git a/src/VBox/Runtime/common/string/strformattype.cpp b/src/VBox/Runtime/common/string/strformattype.cpp
new file mode 100644
index 00000000..27f25c31
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strformattype.cpp
@@ -0,0 +1,487 @@
+/* $Id: strformattype.cpp $ */
+/** @file
+ * IPRT - IPRT String Formatter Extensions, Dynamic Types.
+ */
+
+/*
+ * Copyright (C) 2008-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_STRING
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/stdarg.h>
+#include "internal/string.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifdef RT_STRICT
+# define RTSTRFORMATTYPE_WITH_LOCKING
+#endif
+#ifdef RTSTRFORMATTYPE_WITH_LOCKING
+# define RTSTRFORMATTYPE_LOCK_OFFSET 0x7fff0000
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Description of a registered formatting type.
+ *
+ * In GC we'll be using offsets instead of pointers just to try avoid having to
+ * do the bothersome relocating. This of course assumes that all the relevant
+ * code stays within the same mapping.
+ */
+typedef struct RTSTRDYNFMT
+{
+ /** The length of the type. */
+ uint8_t cchType;
+ /** The type name. */
+ char szType[47];
+ /** The handler function.
+ * In GC the offset is relative to g_aTypes[0], so that &g_aTypes[0] + offHandler
+ * gives the actual address. */
+#ifdef IN_RC
+ int32_t offHandler;
+#else
+ PFNRTSTRFORMATTYPE pfnHandler;
+#endif
+ /** Callback argument. */
+ void * volatile pvUser;
+#if ARCH_BITS == 32
+ /** Size alignment padding. */
+ char abPadding[8];
+#endif
+} RTSTRDYNFMT;
+AssertCompileSizeAlignment(RTSTRDYNFMT, 32);
+typedef RTSTRDYNFMT *PRTSTRDYNFMT;
+typedef RTSTRDYNFMT const *PCRTSTRDYNFMT;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The registered types, sorted for binary lookup.
+ * We use a static array here because it avoids RTMemAlloc dependencies+leaks. */
+static RTSTRDYNFMT g_aTypes[64];
+/** The number of registered types. */
+static uint32_t g_cTypes = 0;
+#ifdef RTSTRFORMATTYPE_WITH_LOCKING
+/** This is just a thing we assert/spin on.
+ * Zero == unlocked, negative == write locked, positive == read locked.
+ *
+ * The user should do all the serialization and we'll smack his fingers in
+ * strict builds if he doesn't. */
+static int32_t volatile g_i32Spinlock = 0;
+#endif
+
+
+/**
+ * Locks the stuff for updating.
+ *
+ * Mostly for check that the caller is doing his job.
+ */
+DECLINLINE(void) rtstrFormatTypeWriteLock(void)
+{
+#if defined(RTSTRFORMATTYPE_WITH_LOCKING)
+ if (RT_UNLIKELY(!ASMAtomicCmpXchgS32(&g_i32Spinlock, -RTSTRFORMATTYPE_LOCK_OFFSET, 0)))
+ {
+ unsigned volatile i;
+
+ AssertFailed();
+ for (i = 0;; i++)
+ if ( !g_i32Spinlock
+ && ASMAtomicCmpXchgS32(&g_i32Spinlock, -RTSTRFORMATTYPE_LOCK_OFFSET, 0))
+ break;
+ }
+#endif
+}
+
+
+/**
+ * Undoing rtstrFormatTypeWriteLock.
+ */
+DECLINLINE(void) rtstrFormatTypeWriteUnlock(void)
+{
+#if defined(RTSTRFORMATTYPE_WITH_LOCKING)
+ Assert(g_i32Spinlock < 0);
+ ASMAtomicAddS32(&g_i32Spinlock, RTSTRFORMATTYPE_LOCK_OFFSET);
+#endif
+}
+
+
+/**
+ * Locks the stuff for reading.
+ *
+ * This is just cheap stuff to make sure the caller is doing the right thing.
+ */
+DECLINLINE(void) rtstrFormatTypeReadLock(void)
+{
+#if defined(RTSTRFORMATTYPE_WITH_LOCKING)
+ if (RT_UNLIKELY(ASMAtomicIncS32(&g_i32Spinlock) < 0))
+ {
+ unsigned volatile i;
+
+ AssertFailed();
+ for (i = 0;; i++)
+ if (ASMAtomicUoReadS32(&g_i32Spinlock) > 0)
+ break;
+ }
+#endif
+}
+
+
+/**
+ * Undoing rtstrFormatTypeReadLock.
+ */
+DECLINLINE(void) rtstrFormatTypeReadUnlock(void)
+{
+#if defined(RTSTRFORMATTYPE_WITH_LOCKING)
+ Assert(g_i32Spinlock > 0);
+ ASMAtomicDecS32(&g_i32Spinlock);
+#endif
+}
+
+
+/**
+ * Compares a type string with a type entry, the string doesn't need to be terminated.
+ *
+ * @returns Same as memcmp.
+ * @param pszType The type string, doesn't need to be terminated.
+ * @param cchType The number of chars in @a pszType to compare.
+ * @param pType The type entry to compare with.
+ */
+DECLINLINE(int) rtstrFormatTypeCompare(const char *pszType, size_t cchType, PCRTSTRDYNFMT pType)
+{
+ size_t cch = RT_MIN(cchType, pType->cchType);
+ int iDiff = memcmp(pszType, pType->szType, cch);
+ if (!iDiff)
+ {
+ if (cchType == pType->cchType)
+ return 0;
+ iDiff = cchType < pType->cchType ? -1 : 1;
+ }
+ return iDiff;
+}
+
+
+/**
+ * Looks up a type entry.
+ *
+ * @returns The type index, -1 on failure.
+ * @param pszType The type to look up. This doesn't have to be terminated.
+ * @param cchType The length of the type.
+ */
+DECLINLINE(int32_t) rtstrFormatTypeLookup(const char *pszType, size_t cchType)
+{
+ /*
+ * Lookup the type - binary search.
+ */
+ int32_t iStart = 0;
+ int32_t iEnd = g_cTypes - 1;
+ int32_t i = iEnd / 2;
+ for (;;)
+ {
+ int iDiff = rtstrFormatTypeCompare(pszType, cchType, &g_aTypes[i]);
+ if (!iDiff)
+ return i;
+ if (iEnd == iStart)
+ break;
+ if (iDiff < 0)
+ iEnd = i - 1;
+ else
+ iStart = i + 1;
+ if (iEnd < iStart)
+ break;
+ i = iStart + (iEnd - iStart) / 2;
+ }
+ return -1;
+}
+
+
+/**
+ * Register a format handler for a type.
+ *
+ * The format handler is used to handle '%R[type]' format types, where the argument
+ * in the vector is a pointer value (a bit restrictive, but keeps it simple).
+ *
+ * The caller must ensure that no other thread will be making use of any of
+ * the dynamic formatting type facilities simultaneously with this call.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_ALREADY_EXISTS if the type has already been registered.
+ * @retval VERR_TOO_MANY_OPEN_FILES if all the type slots has been allocated already.
+ *
+ * @param pszType The type name.
+ * @param pfnHandler The handler address. See FNRTSTRFORMATTYPE for details.
+ * @param pvUser The user argument to pass to the handler. See RTStrFormatTypeSetUser
+ * for how to update this later.
+ */
+RTDECL(int) RTStrFormatTypeRegister(const char *pszType, PFNRTSTRFORMATTYPE pfnHandler, void *pvUser)
+{
+ int rc;
+ size_t cchType;
+ uint32_t cTypes;
+
+ /*
+ * Validate input.
+ */
+ AssertPtr(pfnHandler);
+ AssertPtr(pszType);
+ cchType = strlen(pszType);
+ AssertReturn(cchType < RT_SIZEOFMEMB(RTSTRDYNFMT, szType), VERR_INVALID_PARAMETER);
+
+ /*
+ * Try add it.
+ */
+ rtstrFormatTypeWriteLock();
+
+ /* check that there are empty slots. */
+ cTypes = g_cTypes;
+ if (cTypes < RT_ELEMENTS(g_aTypes))
+ {
+ /* find where to insert it. */
+ uint32_t i = 0;
+ rc = VINF_SUCCESS;
+ while (i < cTypes)
+ {
+ int iDiff = rtstrFormatTypeCompare(pszType, cchType, &g_aTypes[i]);
+ if (!iDiff)
+ {
+ rc = VERR_ALREADY_EXISTS;
+ break;
+ }
+ if (iDiff < 0)
+ break;
+ i++;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /* make room. */
+ uint32_t cToMove = cTypes - i;
+ if (cToMove)
+ memmove(&g_aTypes[i + 1], &g_aTypes[i], cToMove * sizeof(g_aTypes[i]));
+
+ /* insert the new entry. */
+ memset(&g_aTypes[i], 0, sizeof(g_aTypes[i]));
+ memcpy(&g_aTypes[i].szType[0], pszType, cchType + 1);
+ g_aTypes[i].cchType = (uint8_t)cchType;
+ g_aTypes[i].pvUser = pvUser;
+#ifdef IN_RC
+ g_aTypes[i].offHandler = (intptr_t)pfnHandler - (intptr_t)&g_aTypes[0];
+#else
+ g_aTypes[i].pfnHandler = pfnHandler;
+#endif
+ ASMAtomicIncU32(&g_cTypes);
+ rc = VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_TOO_MANY_OPEN_FILES; /** @todo fix error code */
+
+ rtstrFormatTypeWriteUnlock();
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrFormatTypeRegister);
+
+
+/**
+ * Deregisters a format type.
+ *
+ * The caller must ensure that no other thread will be making use of any of
+ * the dynamic formatting type facilities simultaneously with this call.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_FILE_NOT_FOUND if not found.
+ *
+ * @param pszType The type to deregister.
+ */
+RTDECL(int) RTStrFormatTypeDeregister(const char *pszType)
+{
+ int32_t i;
+
+ /*
+ * Validate input.
+ */
+ AssertPtr(pszType);
+
+ /*
+ * Locate the entry and remove it.
+ */
+ rtstrFormatTypeWriteLock();
+ i = rtstrFormatTypeLookup(pszType, strlen(pszType));
+ if (i >= 0)
+ {
+ const uint32_t cTypes = g_cTypes;
+ int32_t cToMove = cTypes - i - 1;
+ if (cToMove > 0)
+ memmove(&g_aTypes[i], &g_aTypes[i + 1], cToMove * sizeof(g_aTypes[i]));
+ memset(&g_aTypes[cTypes - 1], 0, sizeof(g_aTypes[0]));
+ ASMAtomicDecU32(&g_cTypes);
+ }
+ rtstrFormatTypeWriteUnlock();
+
+ Assert(i >= 0);
+ return i >= 0
+ ? VINF_SUCCESS
+ : VERR_FILE_NOT_FOUND; /** @todo fix status code */
+}
+RT_EXPORT_SYMBOL(RTStrFormatTypeDeregister);
+
+
+/**
+ * Sets the user argument for a type.
+ *
+ * This can be used if a user argument needs relocating in GC.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS on success.
+ * @retval VERR_FILE_NOT_FOUND if not found.
+ *
+ * @param pszType The type to update.
+ * @param pvUser The new user argument value.
+ */
+RTDECL(int) RTStrFormatTypeSetUser(const char *pszType, void *pvUser)
+{
+ int32_t i;
+
+ /*
+ * Validate input.
+ */
+ AssertPtr(pszType);
+
+ /*
+ * Locate the entry and update it.
+ */
+ rtstrFormatTypeReadLock();
+
+ i = rtstrFormatTypeLookup(pszType, strlen(pszType));
+ if (i >= 0)
+ ASMAtomicWritePtr(&g_aTypes[i].pvUser, pvUser);
+
+ rtstrFormatTypeReadUnlock();
+
+ Assert(i >= 0);
+ return i >= 0
+ ? VINF_SUCCESS
+ : VERR_FILE_NOT_FOUND; /** @todo fix status code */
+}
+RT_EXPORT_SYMBOL(RTStrFormatTypeSetUser);
+
+
+/**
+ * Formats a type using a registered callback handler.
+ *
+ * This will handle %R[type].
+ *
+ * @returns The number of bytes formatted.
+ * @param pfnOutput Pointer to output function.
+ * @param pvArgOutput Argument for the output function.
+ * @param ppszFormat Pointer to the format string pointer. Advance this till the char
+ * after the format specifier.
+ * @param pArgs Pointer to the argument list. Use this to fetch the arguments.
+ * @param cchWidth Format Width. -1 if not specified.
+ * @param cchPrecision Format Precision. -1 if not specified.
+ * @param fFlags Flags (RTSTR_NTFS_*).
+ * @param chArgSize The argument size specifier, 'l' or 'L'.
+ */
+DECLHIDDEN(size_t) rtstrFormatType(PFNRTSTROUTPUT pfnOutput, void *pvArgOutput, const char **ppszFormat,
+ va_list *pArgs, int cchWidth, int cchPrecision, unsigned fFlags, char chArgSize)
+{
+ size_t cch;
+ int32_t i;
+ char const *pszTypeEnd;
+ char const *pszType;
+ char ch;
+ void *pvValue = va_arg(*pArgs, void *);
+ NOREF(chArgSize);
+
+ /*
+ * Parse out the type.
+ */
+ pszType = *ppszFormat + 2;
+ *ppszFormat = pszType;
+ Assert(pszType[-1] == '[');
+ Assert(pszType[-2] == 'R');
+ pszTypeEnd = pszType;
+ while ((ch = *pszTypeEnd) != ']')
+ {
+ AssertReturn(ch != '\0', 0);
+ AssertReturn(ch != '%', 0);
+ AssertReturn(ch != '[', 0);
+ pszTypeEnd++;
+ }
+ *ppszFormat = pszTypeEnd + 1;
+
+ /*
+ * Locate the entry and call the handler.
+ */
+ rtstrFormatTypeReadLock();
+
+ i = rtstrFormatTypeLookup(pszType, pszTypeEnd - pszType);
+ if (RT_LIKELY(i >= 0))
+ {
+#ifdef IN_RC
+ PFNRTSTRFORMATTYPE pfnHandler = (PFNRTSTRFORMATTYPE)((intptr_t)&g_aTypes[0] + g_aTypes[i].offHandler);
+#else
+ PFNRTSTRFORMATTYPE pfnHandler = g_aTypes[i].pfnHandler;
+#endif
+ void *pvUser = ASMAtomicReadPtr(&g_aTypes[i].pvUser);
+
+ rtstrFormatTypeReadUnlock();
+
+ cch = pfnHandler(pfnOutput, pvArgOutput, g_aTypes[i].szType, pvValue, cchWidth, cchPrecision, fFlags, pvUser);
+ }
+ else
+ {
+ rtstrFormatTypeReadUnlock();
+
+ cch = pfnOutput(pvArgOutput, RT_STR_TUPLE("<missing:%R["));
+ cch += pfnOutput(pvArgOutput, pszType, pszTypeEnd - pszType);
+ cch += pfnOutput(pvArgOutput, RT_STR_TUPLE("]>"));
+ }
+
+ return cch;
+}
+
diff --git a/src/VBox/Runtime/common/string/strhash1.cpp b/src/VBox/Runtime/common/string/strhash1.cpp
new file mode 100644
index 00000000..f56f23b2
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strhash1.cpp
@@ -0,0 +1,82 @@
+/* $Id: strhash1.cpp $ */
+/** @file
+ * IPRT - String Hashing by Algorithm \#1.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+#include "internal/strhash.h"
+
+
+RTDECL(uint32_t) RTStrHash1(const char *pszString)
+{
+ size_t cchIgnored;
+ return sdbm(pszString, &cchIgnored);
+}
+
+
+RTDECL(uint32_t) RTStrHash1N(const char *pszString, size_t cchString)
+{
+ size_t cchIgnored;
+ return sdbmN(pszString, cchString, &cchIgnored);
+}
+
+
+RTDECL(uint32_t) RTStrHash1ExN(size_t cPairs, ...)
+{
+ va_list va;
+ va_start(va, cPairs);
+ uint32_t uHash = RTStrHash1ExNV(cPairs, va);
+ va_end(va);
+ return uHash;
+}
+
+
+RTDECL(uint32_t) RTStrHash1ExNV(size_t cPairs, va_list va)
+{
+ uint32_t uHash = 0;
+ for (uint32_t i = 0; i < cPairs; i++)
+ {
+ const char *psz = va_arg(va, const char *);
+ size_t cch = va_arg(va, size_t);
+ uHash += sdbmIncN(psz, cch, uHash);
+ }
+ return uHash;
+}
+
diff --git a/src/VBox/Runtime/common/string/stringalloc.cpp b/src/VBox/Runtime/common/string/stringalloc.cpp
new file mode 100644
index 00000000..921e9bb9
--- /dev/null
+++ b/src/VBox/Runtime/common/string/stringalloc.cpp
@@ -0,0 +1,321 @@
+/* $Id: stringalloc.cpp $ */
+/** @file
+ * IPRT - String Manipulation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#ifndef IN_RING0
+# include <iprt/alloca.h>
+#endif
+#include <iprt/assert.h>
+#include <iprt/mem.h>
+#include <iprt/err.h>
+#include "internal/string.h"
+
+
+
+RTDECL(char *) RTStrAllocTag(size_t cb, const char *pszTag)
+{
+ char *psz = (char *)RTMemAllocTag(RT_MAX(cb, 1), pszTag);
+ if (psz)
+ *psz = '\0';
+ return psz;
+}
+RT_EXPORT_SYMBOL(RTStrAllocTag);
+
+
+RTDECL(int) RTStrAllocExTag(char **ppsz, size_t cb, const char *pszTag)
+{
+ char *psz = *ppsz = (char *)RTMemAllocTag(RT_MAX(cb, 1), pszTag);
+ if (psz)
+ {
+ *psz = '\0';
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_STR_MEMORY;
+}
+RT_EXPORT_SYMBOL(RTStrAllocExTag);
+
+
+RTDECL(int) RTStrReallocTag(char **ppsz, size_t cbNew, const char *pszTag)
+{
+ char *pszOld = *ppsz;
+ if (!cbNew)
+ {
+ RTMemFree(pszOld);
+ *ppsz = NULL;
+ }
+ else if (pszOld)
+ {
+ char *pszNew = (char *)RTMemReallocTag(pszOld, cbNew, pszTag);
+ if (!pszNew)
+ return VERR_NO_STR_MEMORY;
+ pszNew[cbNew - 1] = '\0';
+ *ppsz = pszNew;
+ }
+ else
+ {
+ char *pszNew = (char *)RTMemAllocTag(cbNew, pszTag);
+ if (!pszNew)
+ return VERR_NO_STR_MEMORY;
+ pszNew[0] = '\0';
+ pszNew[cbNew - 1] = '\0';
+ *ppsz = pszNew;
+ }
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTStrReallocTag);
+
+RTDECL(void) RTStrFree(char *pszString)
+{
+ if (pszString)
+ RTMemTmpFree(pszString);
+}
+RT_EXPORT_SYMBOL(RTStrFree);
+
+
+RTDECL(char *) RTStrDupTag(const char *pszString, const char *pszTag)
+{
+#if defined(__cplusplus)
+ AssertPtr(pszString);
+#endif
+ size_t cch = strlen(pszString) + 1;
+ char *psz = (char *)RTMemAllocTag(cch, pszTag);
+ if (psz)
+ memcpy(psz, pszString, cch);
+ return psz;
+}
+RT_EXPORT_SYMBOL(RTStrDupTag);
+
+
+RTDECL(int) RTStrDupExTag(char **ppszCopy, const char *pszString, const char *pszTag)
+{
+#if defined(__cplusplus)
+ AssertPtr(ppszCopy);
+ AssertPtr(pszString);
+#endif
+
+ size_t cch = strlen(pszString);
+ char *pszDst = (char *)RTMemAllocTag(cch + 1, pszTag);
+ if (pszDst)
+ {
+ memcpy(pszDst, pszString, cch);
+ pszDst[cch] = '\0';
+ *ppszCopy = pszDst;
+ return VINF_SUCCESS;
+ }
+ *ppszCopy = NULL;
+ return VERR_NO_STR_MEMORY;
+}
+RT_EXPORT_SYMBOL(RTStrDupExTag);
+
+
+RTDECL(char *) RTStrDupNTag(const char *pszString, size_t cchMax, const char *pszTag)
+{
+#if defined(__cplusplus)
+ AssertPtr(pszString);
+#endif
+ char const *pszEnd = RTStrEnd(pszString, cchMax);
+ size_t cch = pszEnd ? (uintptr_t)pszEnd - (uintptr_t)pszString : cchMax;
+ char *pszDst = (char *)RTMemAllocTag(cch + 1, pszTag);
+ if (pszDst)
+ {
+ memcpy(pszDst, pszString, cch);
+ pszDst[cch] = '\0';
+ }
+ return pszDst;
+}
+RT_EXPORT_SYMBOL(RTStrDupNTag);
+
+
+RTDECL(int) RTStrDupNExTag(char **ppszCopy, const char *pszString, size_t cchMax, const char *pszTag)
+{
+#if defined(__cplusplus)
+ AssertPtr(pszString);
+#endif
+ char const *pszEnd = RTStrEnd(pszString, cchMax);
+ size_t cch = pszEnd ? (uintptr_t)pszEnd - (uintptr_t)pszString : cchMax;
+ char *pszDst = (char *)RTMemAllocTag(cch + 1, pszTag);
+ if (pszDst)
+ {
+ memcpy(pszDst, pszString, cch);
+ pszDst[cch] = '\0';
+ *ppszCopy = pszDst;
+ return VINF_SUCCESS;
+ }
+ *ppszCopy = NULL;
+ return VERR_NO_STR_MEMORY;
+}
+RT_EXPORT_SYMBOL(RTStrDupNExTag);
+
+
+RTDECL(int) RTStrAAppendTag(char **ppsz, const char *pszAppend, const char *pszTag)
+{
+ if (!pszAppend)
+ return VINF_SUCCESS;
+ return RTStrAAppendNTag(ppsz, pszAppend, RTSTR_MAX, pszTag);
+}
+
+
+RTDECL(int) RTStrAAppendNTag(char **ppsz, const char *pszAppend, size_t cchAppend, const char *pszTag)
+{
+ size_t cchOrg;
+ char *pszNew;
+
+ if (!cchAppend)
+ return VINF_SUCCESS;
+ if (cchAppend == RTSTR_MAX)
+ cchAppend = strlen(pszAppend);
+ else
+ Assert(cchAppend == RTStrNLen(pszAppend, cchAppend));
+
+ cchOrg = *ppsz ? strlen(*ppsz) : 0;
+ pszNew = (char *)RTMemReallocTag(*ppsz, cchOrg + cchAppend + 1, pszTag);
+ if (!pszNew)
+ return VERR_NO_STR_MEMORY;
+
+ memcpy(&pszNew[cchOrg], pszAppend, cchAppend);
+ pszNew[cchOrg + cchAppend] = '\0';
+
+ *ppsz = pszNew;
+ return VINF_SUCCESS;
+}
+
+
+#if !defined(IN_RING0) && !defined(IPRT_NO_ALLOCA_TROUBLE)
+
+/* XXX Currently not needed anywhere. alloca() induces some linker problems for ring 0 code
+ * with newer versions of VCC */
+
+RTDECL(int) RTStrAAppendExNVTag(char **ppsz, size_t cPairs, va_list va, const char *pszTag)
+{
+ AssertPtr(ppsz);
+ if (!cPairs)
+ return VINF_SUCCESS;
+
+ /*
+ * Determine the length of each string and calc the new total.
+ */
+ struct RTStrAAppendExNVStruct
+ {
+ const char *psz;
+ size_t cch;
+ } *paPairs = (struct RTStrAAppendExNVStruct *)alloca(cPairs * sizeof(*paPairs));
+ AssertReturn(paPairs, VERR_NO_STR_MEMORY);
+
+ size_t cchOrg = *ppsz ? strlen(*ppsz) : 0;
+ size_t cchNewTotal = cchOrg;
+ for (size_t i = 0; i < cPairs; i++)
+ {
+ const char *psz = va_arg(va, const char *);
+ size_t cch = va_arg(va, size_t);
+ AssertPtrNull(psz);
+ Assert(cch == RTSTR_MAX || cch == RTStrNLen(psz, cch));
+
+ if (cch == RTSTR_MAX)
+ cch = psz ? strlen(psz) : 0;
+ cchNewTotal += cch;
+
+ paPairs[i].cch = cch;
+ paPairs[i].psz = psz;
+ }
+ cchNewTotal++; /* '\0' */
+
+ /*
+ * Try reallocate the string.
+ */
+ char *pszNew = (char *)RTMemReallocTag(*ppsz, cchNewTotal, pszTag);
+ if (!pszNew)
+ return VERR_NO_STR_MEMORY;
+
+ /*
+ * Do the appending.
+ */
+ size_t off = cchOrg;
+ for (size_t i = 0; i < cPairs; i++)
+ {
+ memcpy(&pszNew[off], paPairs[i].psz, paPairs[i].cch);
+ off += paPairs[i].cch;
+ }
+ Assert(off + 1 == cchNewTotal);
+ pszNew[off] = '\0';
+
+ /* done */
+ *ppsz = pszNew;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTStrAAppendExNVTag);
+
+#endif
+
+
+RTDECL(int) RTStrATruncateTag(char **ppsz, size_t cchNew, const char *pszTag)
+{
+ char *pszNew;
+ char *pszOld = *ppsz;
+ if (!cchNew)
+ {
+ if (pszOld && *pszOld)
+ {
+ *pszOld = '\0';
+ pszNew = (char *)RTMemReallocTag(pszOld, 1, pszTag);
+ if (pszNew)
+ *ppsz = pszNew;
+ }
+ }
+ else
+ {
+ char *pszZero;
+ AssertPtrReturn(pszOld, VERR_OUT_OF_RANGE);
+ AssertReturn(cchNew < ~(size_t)64, VERR_OUT_OF_RANGE);
+ pszZero = RTStrEnd(pszOld, cchNew + 63);
+ AssertReturn(!pszZero || (size_t)(pszZero - pszOld) >= cchNew, VERR_OUT_OF_RANGE);
+ pszOld[cchNew] = '\0';
+ if (!pszZero)
+ {
+ pszNew = (char *)RTMemReallocTag(pszOld, cchNew + 1, pszTag);
+ if (pszNew)
+ *ppsz = pszNew;
+ }
+ }
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTStrATruncateTag);
+
diff --git a/src/VBox/Runtime/common/string/strlen.asm b/src/VBox/Runtime/common/string/strlen.asm
new file mode 100644
index 00000000..6c2ebb54
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strlen.asm
@@ -0,0 +1,75 @@
+; $Id: strlen.asm $
+;; @file
+; IPRT - No-CRT strlen - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param psz gcc: rdi msc: rcx x86: [esp+4] wcall: eax
+RT_NOCRT_BEGINPROC strlen
+ cld
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov r9, rdi ; save rdi
+ mov rdi, rcx
+ %endif
+%else
+ mov edx, edi ; save edi
+ %ifdef ASM_CALL32_WATCOM
+ mov edi, eax
+ %else
+ mov edi, [esp + 4]
+ %endif
+%endif
+
+ ; do the search
+ mov xCX, -1
+ xor eax, eax
+ repne scasb
+
+ ; found it
+ neg xCX
+ lea xAX, [xCX - 2]
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%endif
+%ifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+ ret
+ENDPROC RT_NOCRT(strlen)
+
diff --git a/src/VBox/Runtime/common/string/strlen.cpp b/src/VBox/Runtime/common/string/strlen.cpp
new file mode 100644
index 00000000..c604950f
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strlen.cpp
@@ -0,0 +1,66 @@
+/* $Id: strlen.cpp $ */
+/** @file
+ * IPRT - CRT Strings, strlen().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Find the length of a zeroterminated byte string.
+ *
+ * @returns String length in bytes.
+ * @param pszString Zero terminated string.
+ */
+#ifdef IPRT_NO_CRT
+# undef strlen
+size_t RT_NOCRT(strlen)(const char *pszString)
+#elif RT_MSC_PREREQ(RT_MSC_VER_VS2005)
+__checkReturn size_t __cdecl strlen(__in_z const char *pszString)
+#else
+size_t strlen(const char *pszString)
+#endif
+{
+ const char *psz = pszString;
+ while (*psz)
+ psz++;
+ return psz - pszString;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strlen);
+
diff --git a/src/VBox/Runtime/common/string/strncat.cpp b/src/VBox/Runtime/common/string/strncat.cpp
new file mode 100644
index 00000000..b1b45df0
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strncat.cpp
@@ -0,0 +1,69 @@
+/* $Id: strncat.cpp $ */
+/** @file
+ * IPRT - No-CRT Strings, strncat().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Append a substring to an existing string.
+ *
+ * @returns Pointer to destination string
+ * @param pszDst String to append @a cchMaxSrc chars from @a pszSrc to,
+ * plus a zero terminator.
+ * @param pszSrc Zero terminated string.
+ * @param cchMaxSrc Maximum number of chars to copy from @a pszSrc.
+ */
+#undef strncat
+char *RT_NOCRT(strncat)(char *pszDst, const char *pszSrc, size_t cchMaxSrc)
+{
+ char * const pszRet = pszDst;
+
+ pszDst = RTStrEnd(pszDst, RTSTR_MAX);
+
+ char ch;
+ while (cchMaxSrc-- > 0 && (ch = *pszSrc++) != '\0')
+ *pszDst++ = ch;
+ *pszDst = '\0';
+
+ return pszRet;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strncat);
+
diff --git a/src/VBox/Runtime/common/string/strncmp.asm b/src/VBox/Runtime/common/string/strncmp.asm
new file mode 100644
index 00000000..4873a341
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strncmp.asm
@@ -0,0 +1,141 @@
+; $Id: strncmp.asm $
+;; @file
+; IPRT - No-CRT strncmp - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param psz1 gcc: rdi msc: rcx x86:[esp+4] wcall: eax
+; @param psz2 gcc: rsi msc: rdx x86:[esp+8] wcall: edx
+; @param cch gcc: rdx msc: r8 x86:[esp+12] wcall: ebx
+RT_NOCRT_BEGINPROC strncmp
+ ; input
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ %define psz1 rcx
+ %define psz2 rdx
+ %define cch r8
+ %else
+ %define psz1 rdi
+ %define psz2 rsi
+ %define cch rdx
+ %endif
+%elifdef ASM_CALL32_WATCOM
+ mov ecx, eax
+ %define psz1 ecx
+ %define psz2 edx
+ %define cch ebx
+
+%elifdef RT_ARCH_X86
+ mov ecx, [esp + 4]
+ mov edx, [esp + 8]
+ push ebx
+ mov ebx, [esp + 12+4]
+ %define psz1 ecx
+ %define psz2 edx
+ %define cch ebx
+%else
+ %error "Unknown arch"
+%endif
+
+ ;
+ ; The loop.
+ ;
+ test cch, cch
+ jz .equal
+.next:
+ mov al, [psz1]
+ mov ah, [psz2]
+ cmp al, ah
+ jne .not_equal
+ test al, al
+ jz .equal
+ dec cch
+ jz .equal
+
+ mov al, [psz1 + 1]
+ mov ah, [psz2 + 1]
+ cmp al, ah
+ jne .not_equal
+ test al, al
+ jz .equal
+ dec cch
+ jz .equal
+
+ mov al, [psz1 + 2]
+ mov ah, [psz2 + 2]
+ cmp al, ah
+ jne .not_equal
+ test al, al
+ jz .equal
+ dec cch
+ jz .equal
+
+ mov al, [psz1 + 3]
+ mov ah, [psz2 + 3]
+ cmp al, ah
+ jne .not_equal
+ test al, al
+ jz .equal
+ dec cch
+ jz .equal
+
+ add psz1, 4
+ add psz2, 4
+ jmp .next
+
+.equal:
+ xor eax, eax
+%ifndef ASM_CALL32_WATCOM
+ %ifdef RT_ARCH_X86
+ pop ebx
+ %endif
+%endif
+ ret
+
+.not_equal:
+ movzx ecx, ah
+ and eax, 0ffh
+ sub eax, ecx
+%ifndef ASM_CALL32_WATCOM
+ %ifdef RT_ARCH_X86
+ pop ebx
+ %endif
+%endif
+ ret
+ENDPROC RT_NOCRT(strncmp)
+
diff --git a/src/VBox/Runtime/common/string/strncmp.cpp b/src/VBox/Runtime/common/string/strncmp.cpp
new file mode 100644
index 00000000..502bc5a1
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strncmp.cpp
@@ -0,0 +1,70 @@
+/* $Id: strncmp.cpp $ */
+/** @file
+ * IPRT - CRT Strings, strncmp().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+#ifdef IPRT_NO_CRT
+# undef strncmp
+int RT_NOCRT(strncmp)(const char *pszStr1, const char *pszStr2, size_t cb)
+#elif defined( _MSC_VER)
+_CRTIMP int __cdecl strncmp(const char *pszStr1, const char *pszStr2, size_t cb)
+#elif defined(__WATCOMC__) && !defined(IPRT_NO_CRT)
+_WCRTLINK int std::strncmp(const char *pszStr1, const char *pszStr2, size_t cb)
+#else
+int strncmp(const char *pszStr1, const char *pszStr2, size_t cb)
+# if defined(__THROW) && !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ __THROW
+# endif
+#endif
+{
+ while (cb-- > 0)
+ {
+ char const ch1 = *pszStr1++;
+ int const iDiff = ch1 - *pszStr2++;
+ if (iDiff)
+ return iDiff;
+ if (!ch1)
+ break;
+ }
+ return 0;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strncmp);
+
diff --git a/src/VBox/Runtime/common/string/strncpy.asm b/src/VBox/Runtime/common/string/strncpy.asm
new file mode 100644
index 00000000..20a028dc
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strncpy.asm
@@ -0,0 +1,139 @@
+; $Id: strncpy.asm $
+;; @file
+; IPRT - No-CRT strncpy - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param pszDst gcc: rdi msc: rcx x86:[esp+4] wcall: eax
+; @param pszSrc gcc: rsi msc: rdx x86:[esp+8] wcall: edx
+; @param cbMax gcc: rdx msc: r8 x86:[esp+12] wcall: ebx
+RT_NOCRT_BEGINPROC strncpy
+ ; input
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ %define pszDst rcx
+ %define pszSrc rdx
+ %define cbMax r8
+ %else
+ %define pszDst rdi
+ %define pszSrc rsi
+ %define cbMax rdx
+ %endif
+ mov r9, pszDst
+%else
+ %ifdef ASM_CALL32_WATCOM
+ mov ecx, eax
+ %define pszDst ecx
+ %define pszSrc edx
+ %define cbMax ebx
+ %else
+ mov ecx, [esp + 4]
+ mov edx, [esp + 8]
+ push ebx
+ mov ebx, [esp + 12 + 4]
+ %define pszDst ecx
+ %define pszSrc edx
+ %define cbMax ebx
+ %endif
+ push pszDst
+%endif
+
+ ;
+ ; The rolled out loop.
+ ;
+.next:
+ cmp cbMax, 4
+ jb .simple_intro
+
+ mov al, [pszSrc]
+ mov [pszDst], al
+ test al, al
+ jz .done
+
+ mov al, [pszSrc + 1]
+ mov [pszDst + 1], al
+ test al, al
+ jz .done
+
+ mov al, [pszSrc + 2]
+ mov [pszDst + 2], al
+ test al, al
+ jz .done
+
+ mov al, [pszSrc + 3]
+ mov [pszDst + 3], al
+ test al, al
+ jz .done
+
+ add pszDst, 4
+ add pszSrc, 4
+ sub cbMax, 4
+ jmp .next
+
+ ;
+ ; Char by char.
+ ;
+.simple_intro:
+ test cbMax, cbMax
+ jz .done
+
+.simple_next:
+ mov al, [pszSrc]
+ mov [pszDst], al
+ test al, al
+ jz .done
+
+ dec cbMax
+ jz .done
+
+ inc pszSrc
+ inc pszDst
+ jmp .simple_next
+
+.done:
+%ifdef RT_ARCH_AMD64
+ mov rax, r9
+%else
+ %ifndef ASM_CALL32_WATCOM
+ pop ebx
+ %endif
+ pop eax
+%endif
+ ret
+ENDPROC RT_NOCRT(strncpy)
+
diff --git a/src/VBox/Runtime/common/string/strnlen.cpp b/src/VBox/Runtime/common/string/strnlen.cpp
new file mode 100644
index 00000000..edb9d909
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strnlen.cpp
@@ -0,0 +1,62 @@
+/* $Id: strnlen.cpp $ */
+/** @file
+ * IPRT - CRT Strings, strnlen().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Find the length of a zeroterminated byte string, but only up to a given
+ * limit.
+ *
+ * @returns String length in bytes, or cchMax if too long.
+ * @param pszString Zero terminated string.
+ * @param cchMax Max number of chars to search for the end.
+ */
+#undef strnlen
+size_t RT_NOCRT(strnlen)(const char *pszString, size_t cchMax)
+{
+ const char * const pszStart = pszString;
+ while (cchMax-- > 0 && *pszString)
+ pszString++;
+ return pszString - pszStart;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strnlen);
+
diff --git a/src/VBox/Runtime/common/string/strpbrk.cpp b/src/VBox/Runtime/common/string/strpbrk.cpp
new file mode 100644
index 00000000..93fe5e85
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strpbrk.cpp
@@ -0,0 +1,81 @@
+/* $Id: strpbrk.cpp $ */
+/** @file
+ * IPRT - strpbrk().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Find the first occurrence of a character in pszChars in pszStr.
+ *
+ * @returns
+ */
+#ifdef IPRT_NO_CRT
+# undef strpbrk
+char *RT_NOCRT(strpbrk)(const char *pszStr, const char *pszBreakChars)
+#elif defined(_MSC_VER)
+# if _MSC_VER >= 1400
+_CRTIMP __checkReturn _CONST_RETURN char * __cdecl strpbrk(__in_z const char *pszStr, __in_z const char *pszBreakChars)
+# else
+_CRTIMP char * __cdecl strpbrk(const char *pszStr, const char *pszBreakChars)
+# endif
+#elif defined(__WATCOMC__)
+_WCRTLINK char *std::strpbrk(const char *pszStr, const char *pszBreakChars)
+#else
+char *strpbrk(const char *pszStr, const char *pszBreakChars)
+# if defined(__THROW) && !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ __THROW
+# endif
+#endif
+{
+ int chCur;
+ while ((chCur = *pszStr++) != '\0')
+ {
+ int chBreak;
+ const char *psz = pszBreakChars;
+ while ((chBreak = *psz++) != '\0')
+ if (chBreak == chCur)
+ return (char *)(pszStr - 1);
+
+ }
+ return NULL;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strpbrk);
+
diff --git a/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp b/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp
new file mode 100644
index 00000000..60858715
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strprintf-ellipsis.cpp
@@ -0,0 +1,67 @@
+/* $Id: strprintf-ellipsis.cpp $ */
+/** @file
+ * IPRT - String Formatters, Ellipsis Functions.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTDECL(size_t) RTStrPrintf(char *pszBuffer, size_t cchBuffer, const char *pszFormat, ...)
+{
+ size_t cbRet;
+ va_list va;
+ va_start(va, pszFormat);
+ cbRet = RTStrPrintfV(pszBuffer, cchBuffer, pszFormat, va);
+ va_end(va);
+ return cbRet;
+}
+RT_EXPORT_SYMBOL(RTStrPrintf);
+
+
+RTDECL(size_t) RTStrPrintfEx(PFNSTRFORMAT pfnFormat, void *pvArg, char *pszBuffer, size_t cchBuffer, const char *pszFormat, ...)
+{
+ va_list args;
+ size_t cbRet;
+ va_start(args, pszFormat);
+ cbRet = RTStrPrintfExV(pfnFormat, pvArg, pszBuffer, cchBuffer, pszFormat, args);
+ va_end(args);
+ return cbRet;
+}
+RT_EXPORT_SYMBOL(RTStrPrintfEx);
+
diff --git a/src/VBox/Runtime/common/string/strprintf.cpp b/src/VBox/Runtime/common/string/strprintf.cpp
new file mode 100644
index 00000000..359d499d
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strprintf.cpp
@@ -0,0 +1,131 @@
+/* $Id: strprintf.cpp $ */
+/** @file
+ * IPRT - String Formatters.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** strbufoutput() argument structure. */
+typedef struct STRBUFARG
+{
+ /** Pointer to current buffer position. */
+ char *psz;
+ /** Number of bytes left in the buffer - not including the trailing zero. */
+ size_t cch;
+} STRBUFARG;
+/** Pointer to a strbufoutput() argument structure. */
+typedef STRBUFARG *PSTRBUFARG;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(size_t) strbufoutput(void *pvArg, const char *pachChars, size_t cbChars);
+
+
+/**
+ * Output callback.
+ *
+ * @returns number of bytes written.
+ * @param pvArg Pointer to a STRBUFARG structure.
+ * @param pachChars Pointer to an array of utf-8 characters.
+ * @param cbChars Number of bytes in the character array pointed to by pachChars.
+ */
+static DECLCALLBACK(size_t) strbufoutput(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ PSTRBUFARG pArg = (PSTRBUFARG)pvArg;
+ char *pszCur = pArg->psz; /* We actually have to spell this out for VS2010, or it will load for each case. */
+
+ cbChars = RT_MIN(pArg->cch, cbChars);
+ if (cbChars)
+ {
+ pArg->cch -= cbChars;
+
+ /* Note! For VS2010/64 we need at least 7 case statements before it generates a jump table. */
+ switch (cbChars)
+ {
+ default:
+ memcpy(pszCur, pachChars, cbChars);
+ break;
+ case 8: pszCur[7] = pachChars[7]; RT_FALL_THRU();
+ case 7: pszCur[6] = pachChars[6]; RT_FALL_THRU();
+ case 6: pszCur[5] = pachChars[5]; RT_FALL_THRU();
+ case 5: pszCur[4] = pachChars[4]; RT_FALL_THRU();
+ case 4: pszCur[3] = pachChars[3]; RT_FALL_THRU();
+ case 3: pszCur[2] = pachChars[2]; RT_FALL_THRU();
+ case 2: pszCur[1] = pachChars[1]; RT_FALL_THRU();
+ case 1: pszCur[0] = pachChars[0]; RT_FALL_THRU();
+ case 0:
+ break;
+ }
+ pArg->psz = pszCur += cbChars;
+ }
+ *pszCur = '\0';
+
+ return cbChars;
+}
+
+
+RTDECL(size_t) RTStrPrintfV(char *pszBuffer, size_t cchBuffer, const char *pszFormat, va_list args)
+{
+ STRBUFARG Arg;
+ AssertMsgReturn(cchBuffer, ("Excellent idea! Format a string with no space for the output!\n"), 0);
+ Arg.psz = pszBuffer;
+ Arg.cch = cchBuffer - 1;
+ return RTStrFormatV(strbufoutput, &Arg, NULL, NULL, pszFormat, args);
+}
+RT_EXPORT_SYMBOL(RTStrPrintfV);
+
+
+RTDECL(size_t) RTStrPrintfExV(PFNSTRFORMAT pfnFormat, void *pvArg, char *pszBuffer, size_t cchBuffer, const char *pszFormat, va_list args)
+{
+ STRBUFARG Arg;
+ AssertMsgReturn(cchBuffer, ("Excellent idea! Format a string with no space for the output!\n"), 0);
+ Arg.psz = pszBuffer;
+ Arg.cch = cchBuffer - 1;
+ return RTStrFormatV(strbufoutput, &Arg, pfnFormat, pvArg, pszFormat, args);
+}
+RT_EXPORT_SYMBOL(RTStrPrintfExV);
+
diff --git a/src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp b/src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp
new file mode 100644
index 00000000..eff64b95
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strprintf2-ellipsis.cpp
@@ -0,0 +1,67 @@
+/* $Id: strprintf2-ellipsis.cpp $ */
+/** @file
+ * IPRT - String Formatters, alternative, ellipsis.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+
+RTDECL(ssize_t) RTStrPrintf2(char *pszBuffer, size_t cchBuffer, const char *pszFormat, ...)
+{
+ va_list va;
+ ssize_t cbRet;
+ va_start(va, pszFormat);
+ cbRet = RTStrPrintf2V(pszBuffer, cchBuffer, pszFormat, va);
+ va_end(va);
+ return cbRet;
+}
+RT_EXPORT_SYMBOL(RTStrPrintf2);
+
+
+RTDECL(ssize_t) RTStrPrintf2Ex(PFNSTRFORMAT pfnFormat, void *pvArg, char *pszBuffer, size_t cchBuffer, const char *pszFormat, ...)
+{
+ va_list args;
+ ssize_t cbRet;
+ va_start(args, pszFormat);
+ cbRet = RTStrPrintf2ExV(pfnFormat, pvArg, pszBuffer, cchBuffer, pszFormat, args);
+ va_end(args);
+ return cbRet;
+}
+RT_EXPORT_SYMBOL(RTStrPrintf2Ex);
+
diff --git a/src/VBox/Runtime/common/string/strprintf2.cpp b/src/VBox/Runtime/common/string/strprintf2.cpp
new file mode 100644
index 00000000..7a087c68
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strprintf2.cpp
@@ -0,0 +1,150 @@
+/* $Id: strprintf2.cpp $ */
+/** @file
+ * IPRT - String Formatters, alternative.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** rtStrPrintf2Output() argument structure. */
+typedef struct STRPRINTF2OUTPUTARGS
+{
+ /** Pointer to current buffer position. */
+ char *pszCur;
+ /** Number of bytes left in the buffer (including the trailing zero). */
+ size_t cbLeft;
+ /** Set if we overflowed. */
+ bool fOverflowed;
+} STRPRINTF2OUTPUTARGS;
+/** Pointer to a rtStrPrintf2Output() argument structure. */
+typedef STRPRINTF2OUTPUTARGS *PSTRPRINTF2OUTPUTARGS;
+
+
+/**
+ * Output callback.
+ *
+ * @returns cbChars
+ *
+ * @param pvArg Pointer to a STRBUFARG structure.
+ * @param pachChars Pointer to an array of utf-8 characters.
+ * @param cbChars Number of bytes in the character array pointed to by pachChars.
+ */
+static DECLCALLBACK(size_t) rtStrPrintf2Output(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ PSTRPRINTF2OUTPUTARGS pArgs = (PSTRPRINTF2OUTPUTARGS)pvArg;
+ char *pszCur = pArgs->pszCur; /* We actually have to spell this out for VS2010, or it will load it for each case. */
+
+ if (cbChars < pArgs->cbLeft)
+ {
+ pArgs->cbLeft -= cbChars;
+
+ /* Note! For VS2010/64 we need at least 7 case statements before it generates a jump table. */
+ switch (cbChars)
+ {
+ default:
+ memcpy(pszCur, pachChars, cbChars);
+ break;
+ case 8: pszCur[7] = pachChars[7]; RT_FALL_THRU();
+ case 7: pszCur[6] = pachChars[6]; RT_FALL_THRU();
+ case 6: pszCur[5] = pachChars[5]; RT_FALL_THRU();
+ case 5: pszCur[4] = pachChars[4]; RT_FALL_THRU();
+ case 4: pszCur[3] = pachChars[3]; RT_FALL_THRU();
+ case 3: pszCur[2] = pachChars[2]; RT_FALL_THRU();
+ case 2: pszCur[1] = pachChars[1]; RT_FALL_THRU();
+ case 1: pszCur[0] = pachChars[0]; RT_FALL_THRU();
+ case 0:
+ break;
+ }
+ pArgs->pszCur = pszCur += cbChars;
+ *pszCur = '\0';
+ }
+ else
+ {
+ size_t cbLeft = pArgs->cbLeft;
+ if (cbLeft-- > 1)
+ {
+ memcpy(pszCur, pachChars, cbLeft);
+ pArgs->pszCur = pszCur += cbLeft;
+ *pszCur = '\0';
+ pArgs->cbLeft = 1;
+ }
+ pArgs->fOverflowed = true;
+ }
+
+ return cbChars;
+}
+
+
+RTDECL(ssize_t) RTStrPrintf2V(char *pszBuffer, size_t cchBuffer, const char *pszFormat, va_list args)
+{
+ STRPRINTF2OUTPUTARGS Args;
+ size_t cchRet;
+ AssertMsg(cchBuffer > 0, ("Excellent idea! Format a string with no space for the output!\n"));
+
+ Args.pszCur = pszBuffer;
+ Args.cbLeft = cchBuffer;
+ Args.fOverflowed = false;
+
+ cchRet = RTStrFormatV(rtStrPrintf2Output, &Args, NULL, NULL, pszFormat, args);
+
+ return !Args.fOverflowed ? (ssize_t)cchRet : -(ssize_t)cchRet - 1;
+}
+RT_EXPORT_SYMBOL(RTStrPrintf2V);
+
+
+RTDECL(ssize_t) RTStrPrintf2ExV(PFNSTRFORMAT pfnFormat, void *pvArg, char *pszBuffer, size_t cchBuffer,
+ const char *pszFormat, va_list args)
+{
+ STRPRINTF2OUTPUTARGS Args;
+ size_t cchRet;
+ AssertMsg(cchBuffer > 0, ("Excellent idea! Format a string with no space for the output!\n"));
+
+ Args.pszCur = pszBuffer;
+ Args.cbLeft = cchBuffer;
+ Args.fOverflowed = false;
+ cchRet = RTStrFormatV(rtStrPrintf2Output, &Args, pfnFormat, pvArg, pszFormat, args);
+ return !Args.fOverflowed ? (ssize_t)cchRet : -(ssize_t)cchRet - 1;
+}
+RT_EXPORT_SYMBOL(RTStrPrintf2ExV);
+
diff --git a/src/VBox/Runtime/common/string/strrchr.cpp b/src/VBox/Runtime/common/string/strrchr.cpp
new file mode 100644
index 00000000..8dbebc50
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strrchr.cpp
@@ -0,0 +1,52 @@
+/* $Id: strrchr.cpp $ */
+/** @file
+ * IPRT - CRT Strings, strrchr().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+#undef strrchr
+char *RT_NOCRT(strrchr)(const char *pszString, int ch)
+{
+ /* We must search the terminator too, so strrchr(str, '\0') returns a pointer to it. */
+ return (char *)memrchr(pszString, ch, strlen(pszString) + 1);
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strrchr);
+
diff --git a/src/VBox/Runtime/common/string/strspace.cpp b/src/VBox/Runtime/common/string/strspace.cpp
new file mode 100644
index 00000000..88ca83ae
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strspace.cpp
@@ -0,0 +1,195 @@
+/* $Id: strspace.cpp $ */
+/** @file
+ * IPRT - Unique String Spaces.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include "internal/strhash.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_DECL(a_Type) static a_Type
+#define KAVL_FN(a) rtstrspace##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_EQUAL_ALLOWED 1
+#define KAVLNODECORE RTSTRSPACECORE
+#define PKAVLNODECORE PRTSTRSPACECORE
+#define PPKAVLNODECORE PPRTSTRSPACECORE
+#define KAVLKEY uint32_t
+#define PKAVLKEY uint32_t *
+
+#define PKAVLCALLBACK PFNRTSTRSPACECALLBACK
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) (key1 > key2)
+#define KAVL_E(key1, key2) (key1 == key2)
+#define KAVL_NE(key1, key2) (key1 != key2)
+
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "../table/avl_Base.cpp.h"
+#include "../table/avl_Get.cpp.h"
+#include "../table/avl_DoWithAll.cpp.h"
+#include "../table/avl_Destroy.cpp.h"
+
+
+
+RTDECL(bool) RTStrSpaceInsert(PRTSTRSPACE pStrSpace, PRTSTRSPACECORE pStr)
+{
+ pStr->Key = sdbm(pStr->pszString, &pStr->cchString);
+ PRTSTRSPACECORE pMatch = KAVL_FN(Get)(pStrSpace, pStr->Key);
+ if (!pMatch)
+ return KAVL_FN(Insert)(pStrSpace, pStr);
+
+ /* Check for clashes. */
+ for (PRTSTRSPACECORE pCur = pMatch; pCur; pCur = pCur->pList)
+ if ( pCur->cchString == pStr->cchString
+ && !memcmp(pCur->pszString, pStr->pszString, pStr->cchString))
+ return false;
+ pStr->pList = pMatch->pList;
+ pMatch->pList = pStr;
+ return true;
+}
+RT_EXPORT_SYMBOL(RTStrSpaceInsert);
+
+
+RTDECL(PRTSTRSPACECORE) RTStrSpaceRemove(PRTSTRSPACE pStrSpace, const char *pszString)
+{
+ size_t cchString;
+ KAVLKEY Key = sdbm(pszString, &cchString);
+ PRTSTRSPACECORE pCur = KAVL_FN(Get)(pStrSpace, Key);
+ if (!pCur)
+ return NULL;
+
+ /* find the right one. */
+ PRTSTRSPACECORE pPrev = NULL;
+ for (; pCur; pPrev = pCur, pCur = pCur->pList)
+ if ( pCur->cchString == cchString
+ && !memcmp(pCur->pszString, pszString, cchString))
+ break;
+ if (pCur)
+ {
+ if (pPrev)
+ /* simple, it's in the linked list. */
+ pPrev->pList = pCur->pList;
+ else
+ {
+ /* in the tree. remove and reinsert list head. */
+ PRTSTRSPACECORE pInsert = pCur->pList;
+ pCur->pList = NULL;
+ pCur = KAVL_FN(Remove)(pStrSpace, Key);
+ Assert(pCur);
+ if (pInsert)
+ {
+ PRTSTRSPACECORE pList = pInsert->pList;
+ bool fRc = KAVL_FN(Insert)(pStrSpace, pInsert);
+ Assert(fRc); NOREF(fRc);
+ pInsert->pList = pList;
+ }
+ }
+ }
+
+ return pCur;
+}
+RT_EXPORT_SYMBOL(RTStrSpaceRemove);
+
+
+RTDECL(PRTSTRSPACECORE) RTStrSpaceGet(PRTSTRSPACE pStrSpace, const char *pszString)
+{
+ size_t cchString;
+ KAVLKEY Key = sdbm(pszString, &cchString);
+ PRTSTRSPACECORE pCur = KAVL_FN(Get)(pStrSpace, Key);
+ if (!pCur)
+ return NULL;
+
+ /* Linear search. */
+ for (; pCur; pCur = pCur->pList)
+ if ( pCur->cchString == cchString
+ && !memcmp(pCur->pszString, pszString, cchString))
+ return pCur;
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTStrSpaceGet);
+
+
+RTDECL(PRTSTRSPACECORE) RTStrSpaceGetN(PRTSTRSPACE pStrSpace, const char *pszString, size_t cchMax)
+{
+ size_t cchString;
+ KAVLKEY Key = sdbmN(pszString, cchMax, &cchString);
+ PRTSTRSPACECORE pCur = KAVL_FN(Get)(pStrSpace, Key);
+ if (!pCur)
+ return NULL;
+
+ /* Linear search. */
+ for (; pCur; pCur = pCur->pList)
+ if ( pCur->cchString == cchString
+ && !memcmp(pCur->pszString, pszString, cchString))
+ return pCur;
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTStrSpaceGetN);
+
+
+RTDECL(int) RTStrSpaceEnumerate(PRTSTRSPACE pStrSpace, PFNRTSTRSPACECALLBACK pfnCallback, void *pvUser)
+{
+ return KAVL_FN(DoWithAll)(pStrSpace, true, pfnCallback, pvUser);
+}
+RT_EXPORT_SYMBOL(RTStrSpaceEnumerate);
+
+
+RTDECL(int) RTStrSpaceDestroy(PRTSTRSPACE pStrSpace, PFNRTSTRSPACECALLBACK pfnCallback, void *pvUser)
+{
+ return KAVL_FN(Destroy)(pStrSpace, pfnCallback, pvUser);
+}
+RT_EXPORT_SYMBOL(RTStrSpaceDestroy);
+
diff --git a/src/VBox/Runtime/common/string/strstr.cpp b/src/VBox/Runtime/common/string/strstr.cpp
new file mode 100644
index 00000000..97825f37
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strstr.cpp
@@ -0,0 +1,78 @@
+/* $Id: strstr.cpp $ */
+/** @file
+ * IPRT - CRT Strings, strstr().
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+/**
+ * Find the location of @a pszSubStr in @a pszString.
+ *
+ * @returns Pointer to first occurence of the substring in @a pszString, NULL if
+ * not found.
+ * @param pszString Zero terminated string to search.
+ * @param pszSubStr The substring to search for.
+ */
+#undef strstr
+char *RT_NOCRT(strstr)(const char *pszString, const char *pszSubStr)
+{
+ char const ch0Sub = *pszSubStr;
+ if (ch0Sub != '\0')
+ {
+ pszString = strchr(pszString, ch0Sub);
+ if (pszString)
+ {
+ size_t const cchSubStr = strlen(pszSubStr);
+ do
+ {
+ if (strncmp(pszString, pszSubStr, cchSubStr) == 0)
+ return (char *)pszString;
+ if (ch0Sub)
+ pszString = strchr(pszString + 1, ch0Sub);
+ else
+ break;
+ } while (pszString != NULL);
+ }
+ return NULL;
+ }
+ return (char *)pszString;
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strstr);
+
diff --git a/src/VBox/Runtime/common/string/strstrip.cpp b/src/VBox/Runtime/common/string/strstrip.cpp
new file mode 100644
index 00000000..d49b8ba2
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strstrip.cpp
@@ -0,0 +1,103 @@
+/* $Id: strstrip.cpp $ */
+/** @file
+ * IPRT - String Stripping and Trimming.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/ctype.h>
+#include <iprt/string.h>
+
+
+/**
+ * Strips blankspaces from both ends of the string.
+ *
+ * @returns Pointer to first non-blank char in the string.
+ * @param psz The string to strip.
+ */
+RTDECL(char *) RTStrStrip(char *psz)
+{
+ /* left */
+ while (RT_C_IS_SPACE(*psz))
+ psz++;
+
+ /* right */
+ char *pszEnd = strchr(psz, '\0');
+ while (--pszEnd > psz && RT_C_IS_SPACE(*pszEnd))
+ *pszEnd = '\0';
+
+ return psz;
+}
+RT_EXPORT_SYMBOL(RTStrStrip);
+
+
+/**
+ * Strips blankspaces from the start of the string.
+ *
+ * @returns Pointer to first non-blank char in the string.
+ * @param psz The string to strip.
+ */
+RTDECL(char *) RTStrStripL(const char *psz)
+{
+ /* left */
+ while (RT_C_IS_SPACE(*psz))
+ psz++;
+
+ return (char *)psz;
+}
+RT_EXPORT_SYMBOL(RTStrStripL);
+
+
+/**
+ * Strips blankspaces from the end of the string.
+ *
+ * @returns psz.
+ * @param psz The string to strip.
+ */
+RTDECL(char *) RTStrStripR(char *psz)
+{
+ /* right */
+ char *pszEnd = strchr(psz, '\0');
+ while (--pszEnd > psz && RT_C_IS_SPACE(*pszEnd))
+ *pszEnd = '\0';
+
+ return psz;
+}
+RT_EXPORT_SYMBOL(RTStrStripR);
+
diff --git a/src/VBox/Runtime/common/string/strtofloat.cpp b/src/VBox/Runtime/common/string/strtofloat.cpp
new file mode 100644
index 00000000..44deafef
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strtofloat.cpp
@@ -0,0 +1,1400 @@
+/* $Id: strtofloat.cpp $ */
+/** @file
+ * IPRT - String To Floating Point Conversion.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h> /* needed for RT_C_IS_DIGIT */
+#include <iprt/err.h>
+
+#include <float.h>
+#include <math.h>
+#if !defined(_MSC_VER) || !defined(IPRT_NO_CRT) /** @todo fix*/
+# include <fenv.h>
+#endif
+#ifndef INFINITY /* Not defined on older Solaris (like the one in the add build VM). */
+# define INFINITY HUGE_VAL
+#endif
+
+#if defined(SOFTFLOAT_FAST_INT64) && !defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE) /** @todo better softfloat indicator? */
+# define USE_SOFTFLOAT /* for scaling by power of 10 */
+#endif
+#ifdef USE_SOFTFLOAT
+# include <softfloat.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef union FLOATUNION
+{
+#ifdef RT_COMPILER_WITH_128BIT_LONG_DOUBLE
+ RTFLOAT128U lrd;
+#elif defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+ RTFLOAT80U2 lrd;
+#else
+ RTFLOAT64U lrd;
+#endif
+ RTFLOAT64U rd;
+ RTFLOAT32U r;
+} FLOATUNION;
+
+#define RET_TYPE_FLOAT 0
+#define RET_TYPE_DOUBLE 1
+#define RET_TYPE_LONG_DOUBLE 2
+
+#ifdef RT_COMPILER_WITH_128BIT_LONG_DOUBLE
+typedef RTFLOAT128U LONG_DOUBLE_U_T;
+typedef __uint128_t UINT_MANTISSA_T;
+# define UINT_MANTISSA_T_BITS 128
+#elif defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+typedef RTFLOAT80U2 LONG_DOUBLE_U_T;
+typedef uint64_t UINT_MANTISSA_T;
+# define UINT_MANTISSA_T_BITS 64
+#else
+typedef RTFLOAT64U LONG_DOUBLE_U_T;
+typedef uint64_t UINT_MANTISSA_T;
+# define UINT_MANTISSA_T_BITS 64
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/* in strtonum.cpp */
+extern const unsigned char g_auchDigits[256];
+
+#define DIGITS_ZERO_TERM 254
+#define DIGITS_COLON 253
+#define DIGITS_SPACE 252
+#define DIGITS_DOT 251
+
+/** Pair of default float quiet NaN values (indexed by fPositive). */
+static RTFLOAT32U const g_ar32QNan[2] = { RTFLOAT32U_INIT_QNAN(1), RTFLOAT32U_INIT_QNAN(0) };
+
+/** Pair of default double quiet NaN values (indexed by fPositive). */
+static RTFLOAT64U const g_ardQNan[2] = { RTFLOAT64U_INIT_QNAN(1), RTFLOAT64U_INIT_QNAN(0) };
+
+/** Pair of default double quiet NaN values (indexed by fPositive). */
+#if defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+static RTFLOAT128U const g_alrdQNan[2] = { RTFLOAT128U_INIT_QNAN(1), RTFLOAT128U_INIT_QNAN(0) };
+#elif defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+static RTFLOAT80U2 const g_alrdQNan[2] = { RTFLOAT80U_INIT_QNAN(1), RTFLOAT80U_INIT_QNAN(0) };
+#else
+static RTFLOAT64U const g_alrdQNan[2] = { RTFLOAT64U_INIT_QNAN(1), RTFLOAT64U_INIT_QNAN(0) };
+#endif
+
+/** NaN fraction value masks. */
+static uint64_t const g_fNanMasks[3] =
+{
+ RT_BIT_64(RTFLOAT32U_FRACTION_BITS - 1) - 1, /* 22=quiet(1) / silent(0) */
+ RT_BIT_64(RTFLOAT64U_FRACTION_BITS - 1) - 1, /* 51=quiet(1) / silent(0) */
+#if defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ RT_BIT_64(RTFLOAT128U_FRACTION_BITS - 1 - 64) - 1, /* 111=quiet(1) / silent(0) */
+#elif defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+ RT_BIT_64(RTFLOAT80U_FRACTION_BITS - 1) - 1, /* bit 63=NaN; bit 62=quiet(1) / silent(0) */
+#else
+ RT_BIT_64(RTFLOAT64U_FRACTION_BITS - 1) - 1,
+#endif
+};
+
+#if 0
+/** Maximum exponent value in the binary representation for a RET_TYPE_XXX. */
+static const int32_t g_iMaxExp[3] =
+{
+ RTFLOAT32U_EXP_MAX - 1 - RTFLOAT32U_EXP_BIAS,
+ RTFLOAT64U_EXP_MAX - 1 - RTFLOAT64U_EXP_BIAS,
+#if defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ RTFLOAT128U_EXP_MAX - 1 - RTFLOAT128U_EXP_BIAS,
+#elif defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+ RTFLOAT80U_EXP_MAX - 1 - RTFLOAT80U_EXP_BIAS,
+#else
+ RTFLOAT64U_EXP_MAX - 1 - RTFLOAT64U_EXP_BIAS,
+#endif
+};
+
+/** Minimum exponent value in the binary representation for a RET_TYPE_XXX. */
+static const int32_t g_iMinExp[3] =
+{
+ 1 - RTFLOAT32U_EXP_BIAS,
+ 1 - RTFLOAT64U_EXP_BIAS,
+#if defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ 1 - RTFLOAT128U_EXP_BIAS,
+#elif defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+ 1 - RTFLOAT80U_EXP_BIAS,
+#else
+ 1 - RTFLOAT64U_EXP_BIAS,
+#endif
+};
+#endif
+
+#if 0 /* unused */
+# if defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE) || defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+static const long double g_lrdPowerMin10 = 1e4931L;
+static const long double g_lrdPowerMax10 = 1e4932L;
+# else
+static const long double g_lrdPowerMin10 = 1e307L;
+static const long double g_lrdPowerMax10 = 1e308L;
+# endif
+#endif
+
+#ifdef USE_SOFTFLOAT
+/** SoftFloat: Power of 10 table using 128-bit floating point.
+ *
+ * @code
+ softfloat_state_t SoftState = SOFTFLOAT_STATE_INIT_DEFAULTS();
+ float128_t Power10;
+ ui32_to_f128M(10, &Power10, &SoftState);
+ for (unsigned iBit = 0; iBit < 13; iBit++)
+ {
+ RTAssertMsg2(" { { UINT64_C(%#018RX64), UINT64_C(%#018RX64) } }, %c* 1e%u (%RU64) *%c\n", Power10.v[0], Power10.v[1],
+ '/', RT_BIT_32(iBit), f128M_to_ui64(&Power10, softfloat_round_near_even, false, &SoftState), '/');
+ f128M_mul(&Power10, &Power10, &Power10, &SoftState);
+ }
+ @endcode */
+static const float128_t g_ar128Power10[] =
+{
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4002400000000000) } }, /* 1e1 (10) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4005900000000000) } }, /* 1e2 (100) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x400c388000000000) } }, /* 1e4 (10000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x40197d7840000000) } }, /* 1e8 (100000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x40341c37937e0800) } }, /* 1e16 (10000000000000000) */
+ { { UINT64_C(0x6b3be04000000000), UINT64_C(0x40693b8b5b5056e1) } }, /* 1e32 (18446744073709551615) */
+ { { UINT64_C(0x4daa797ed6e38ed6), UINT64_C(0x40d384f03e93ff9f) } }, /* 1e64 (18446744073709551615) */
+ { { UINT64_C(0x19bf8cde66d86d61), UINT64_C(0x41a827748f9301d3) } }, /* 1e128 (18446744073709551615) */
+ { { UINT64_C(0xbd1bbb77203731fb), UINT64_C(0x435154fdd7f73bf3) } }, /* 1e256 (18446744073709551615) */
+ { { UINT64_C(0x238d98cab8a97899), UINT64_C(0x46a3c633415d4c1d) } }, /* 1e512 (18446744073709551615) */
+ { { UINT64_C(0x182eca1a7a51e308), UINT64_C(0x4d4892eceb0d02ea) } }, /* 1e1024 (18446744073709551615) */
+ { { UINT64_C(0xbbc94e9a519c651e), UINT64_C(0x5a923d1676bb8a7a) } }, /* 1e2048 (18446744073709551615) */
+ { { UINT64_C(0x2f3592982a7f005a), UINT64_C(0x752588c0a4051441) } }, /* 1e4096 (18446744073709551615) */
+ /* INF */
+};
+
+/** SoftFloat: Initial value for power of 10 scaling.
+ * This deals with the first 32 powers of 10, covering the a full 64-bit
+ * mantissa and a small exponent w/o needing to make use of g_ar128Power10.
+ *
+ * @code
+ softfloat_state_t SoftState = SOFTFLOAT_STATE_INIT_DEFAULTS();
+ float128_t Num10;
+ ui32_to_f128M(10, &Num10, &SoftState);
+ float128_t Power10;
+ ui32_to_f128M(1, &Power10, &SoftState);
+ for (unsigned cTimes = 0; cTimes < 32; cTimes++)
+ {
+ RTAssertMsg2(" { { UINT64_C(%#018RX64), UINT64_C(%#018RX64) } }, %c* 1e%u (%RU64) *%c\n", Power10.v[0], Power10.v[1],
+ '/', cTimes, f128M_to_ui64(&Power10, softfloat_round_near_even, false, &SoftState), '/');
+ f128M_mul(&Power10, &Num10, &Power10, &SoftState);
+ }
+ @endcode */
+static const float128_t g_ar128Power10Initial[] =
+{
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x3fff000000000000) } }, /* 1e0 (1) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4002400000000000) } }, /* 1e1 (10) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4005900000000000) } }, /* 1e2 (100) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4008f40000000000) } }, /* 1e3 (1000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x400c388000000000) } }, /* 1e4 (10000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x400f86a000000000) } }, /* 1e5 (100000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4012e84800000000) } }, /* 1e6 (1000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4016312d00000000) } }, /* 1e7 (10000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x40197d7840000000) } }, /* 1e8 (100000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x401cdcd650000000) } }, /* 1e9 (1000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x40202a05f2000000) } }, /* 1e10 (10000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x402374876e800000) } }, /* 1e11 (100000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4026d1a94a200000) } }, /* 1e12 (1000000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x402a2309ce540000) } }, /* 1e13 (10000000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x402d6bcc41e90000) } }, /* 1e14 (100000000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4030c6bf52634000) } }, /* 1e15 (1000000000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x40341c37937e0800) } }, /* 1e16 (10000000000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x40376345785d8a00) } }, /* 1e17 (100000000000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x403abc16d674ec80) } }, /* 1e18 (1000000000000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x403e158e460913d0) } }, /* 1e19 (10000000000000000000) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x40415af1d78b58c4) } }, /* 1e20 (18446744073709551615) */
+ { { UINT64_C(0x0000000000000000), UINT64_C(0x4044b1ae4d6e2ef5) } }, /* 1e21 (18446744073709551615) */
+ { { UINT64_C(0x2000000000000000), UINT64_C(0x40480f0cf064dd59) } }, /* 1e22 (18446744073709551615) */
+ { { UINT64_C(0x6800000000000000), UINT64_C(0x404b52d02c7e14af) } }, /* 1e23 (18446744073709551615) */
+ { { UINT64_C(0x4200000000000000), UINT64_C(0x404ea784379d99db) } }, /* 1e24 (18446744073709551615) */
+ { { UINT64_C(0x0940000000000000), UINT64_C(0x405208b2a2c28029) } }, /* 1e25 (18446744073709551615) */
+ { { UINT64_C(0x4b90000000000000), UINT64_C(0x40554adf4b732033) } }, /* 1e26 (18446744073709551615) */
+ { { UINT64_C(0x1e74000000000000), UINT64_C(0x40589d971e4fe840) } }, /* 1e27 (18446744073709551615) */
+ { { UINT64_C(0x1308800000000000), UINT64_C(0x405c027e72f1f128) } }, /* 1e28 (18446744073709551615) */
+ { { UINT64_C(0x17caa00000000000), UINT64_C(0x405f431e0fae6d72) } }, /* 1e29 (18446744073709551615) */
+ { { UINT64_C(0x9dbd480000000000), UINT64_C(0x406293e5939a08ce) } }, /* 1e30 (18446744073709551615) */
+ { { UINT64_C(0x452c9a0000000000), UINT64_C(0x4065f8def8808b02) } }, /* 1e31 (18446744073709551615) */
+};
+
+#else /* !USE_SOFTFLOAT */
+/** Long Double: Power of 10 table scaling table.
+ * @note LDBL_MAX_10_EXP is 4932 for 80-bit and 308 for 64-bit type. */
+static const long double a_lrdPower10[] =
+{
+ 1e1L,
+ 1e2L,
+ 1e4L,
+ 1e8L,
+ 1e16L,
+ 1e32L,
+ 1e64L,
+ 1e128L,
+ 1e256L,
+# if defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE) || defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ 1e512L,
+ 1e1024L,
+ 1e2048L,
+ 1e4096L,
+# endif
+};
+
+/** Long double: Initial value for power of 10 scaling.
+ * This deals with the first 32 powers of 10, covering the a full 64-bit
+ * mantissa and a small exponent w/o needing to make use of g_ar128Power10. */
+static const long double g_alrdPower10Initial[] =
+{
+ 1e0L,
+ 1e1L,
+ 1e2L,
+ 1e3L,
+ 1e4L,
+ 1e5L,
+ 1e6L,
+ 1e7L,
+ 1e8L,
+ 1e9L,
+ 1e10L,
+ 1e11L,
+ 1e12L,
+ 1e13L,
+ 1e14L,
+ 1e15L,
+ 1e16L,
+ 1e17L,
+ 1e18L,
+ 1e19L,
+ 1e20L,
+ 1e21L,
+ 1e22L,
+ 1e23L,
+ 1e24L,
+ 1e25L,
+ 1e26L,
+ 1e27L,
+ 1e28L,
+ 1e29L,
+ 1e30L,
+ 1e31L,
+};
+
+/* Tell the compiler that we'll mess with the FPU environment. */
+# ifdef _MSC_VER
+# pragma fenv_access(on)
+# endif
+#endif /*!USE_SOFTFLOAT */
+
+
+/**
+ * Multiply @a pVal by 10 to the power of @a iExponent10.
+ *
+ * This is currently a weak point where we might end up with rounding issues.
+ */
+static int rtStrToLongDoubleExp10(LONG_DOUBLE_U_T *pVal, int iExponent10)
+{
+ AssertReturn(iExponent10 != 0, VINF_SUCCESS);
+#ifdef USE_SOFTFLOAT
+ /* Use 128-bit precision floating point from softfloat to improve accuracy. */
+
+ softfloat_state_t SoftState = SOFTFLOAT_STATE_INIT_DEFAULTS();
+ float128_t Val;
+# ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ extFloat80M Tmp = EXTFLOAT80M_INIT(pVal->s2.uSignAndExponent, pVal->s2.uMantissa);
+ extF80M_to_f128M(&Tmp, &Val, &SoftState);
+# else
+ float64_t Tmp = { pVal->u };
+ f64_to_f128M(Tmp, &Val, &SoftState);
+# endif
+
+ /*
+ * Calculate the scaling factor. If we need to make use of the last table
+ * entry, we will do part of the scaling here to avoid overflowing Factor.
+ */
+ unsigned uAbsExp = (unsigned)RT_ABS(iExponent10);
+ AssertCompile(RT_ELEMENTS(g_ar128Power10Initial) == 32);
+ unsigned iBit = 5;
+ float128_t Factor = g_ar128Power10Initial[uAbsExp & 31];
+ uAbsExp >>= iBit;
+ while (uAbsExp != 0)
+ {
+ if (iBit < RT_ELEMENTS(g_ar128Power10))
+ {
+ if (uAbsExp & 1)
+ {
+ if (iBit < RT_ELEMENTS(g_ar128Power10) - 1)
+ f128M_mul(&Factor, &g_ar128Power10[iBit], &Factor, &SoftState);
+ else
+ {
+ /* Must do it in two steps to avoid prematurely overflowing the factor value. */
+ if (iExponent10 > 0)
+ f128M_mul(&Val, &Factor, &Val, &SoftState);
+ else
+ f128M_div(&Val, &Factor, &Val, &SoftState);
+ Factor = g_ar128Power10[iBit];
+ }
+ }
+ }
+ else if (iExponent10 < 0)
+ {
+ pVal->r = pVal->r < 0.0L ? -0.0L : +0.0L;
+ return VERR_FLOAT_UNDERFLOW;
+ }
+ else
+ {
+ pVal->r = pVal->r < 0.0L ? -INFINITY : +INFINITY;
+ return VERR_FLOAT_OVERFLOW;
+ }
+ iBit++;
+ uAbsExp >>= 1;
+ }
+
+ /*
+ * Do the scaling (or what remains).
+ */
+ if (iExponent10 > 0)
+ f128M_mul(&Val, &Factor, &Val, &SoftState);
+ else
+ f128M_div(&Val, &Factor, &Val, &SoftState);
+
+# ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ f128M_to_extF80M(&Val, &Tmp, &SoftState);
+ pVal->s2.uSignAndExponent = Tmp.signExp;
+ pVal->s2.uMantissa = Tmp.signif;
+# else
+ Tmp = f128M_to_f64(&Val, &SoftState);
+ pVal->u = Tmp.v;
+# endif
+
+ /*
+ * Check for under/overflow and return.
+ */
+ int rc;
+ if (!(SoftState.exceptionFlags & (softfloat_flag_underflow | softfloat_flag_overflow)))
+ rc = VINF_SUCCESS;
+ else if (SoftState.exceptionFlags & softfloat_flag_underflow)
+ rc = VERR_FLOAT_UNDERFLOW;
+ else
+ rc = VERR_FLOAT_OVERFLOW;
+
+#else /* !USE_SOFTFLOAT */
+# if 0
+ /*
+ * Use RTBigNum, falling back on the simple approach if we don't need the
+ * precision or run out of memory?
+ */
+ /** @todo implement RTBigNum approach */
+# endif
+
+ /*
+ * Simple approach.
+ */
+# if !defined(_MSC_VER) || !defined(IPRT_NO_CRT) /** @todo fix*/
+ fenv_t SavedFpuEnv;
+ feholdexcept(&SavedFpuEnv);
+# endif
+
+ /*
+ * Calculate the scaling factor. If we need to make use of the last table
+ * entry, we will do part of the scaling here to avoid overflowing lrdFactor.
+ */
+ AssertCompile(RT_ELEMENTS(g_alrdPower10Initial) == 32);
+ int rc = VINF_SUCCESS;
+ unsigned uAbsExp = (unsigned)RT_ABS(iExponent10);
+ long double lrdFactor = g_alrdPower10Initial[uAbsExp & 31];
+ unsigned iBit = 5;
+ uAbsExp >>= iBit;
+
+ while (uAbsExp != 0)
+ {
+ if (iBit < RT_ELEMENTS(a_lrdPower10))
+ {
+ if (uAbsExp & 1)
+ {
+ if (iBit < RT_ELEMENTS(a_lrdPower10) - 1)
+ lrdFactor *= a_lrdPower10[iBit];
+ else
+ {
+ /* Must do it in two steps to avoid prematurely overflowing the factor value. */
+ if (iExponent10 < 0)
+ pVal->r /= lrdFactor;
+ else
+ pVal->r *= lrdFactor;
+ lrdFactor = a_lrdPower10[iBit];
+ }
+ }
+ }
+ else if (iExponent10 < 0)
+ {
+ pVal->r = pVal->r < 0.0L ? -0.0L : +0.0L;
+ rc = VERR_FLOAT_UNDERFLOW;
+ break;
+ }
+ else
+ {
+ pVal->r = pVal->r < 0.0L ? -INFINITY : +INFINITY;
+ rc = VERR_FLOAT_OVERFLOW;
+ break;
+ }
+ iBit++;
+ uAbsExp >>= 1;
+ }
+
+ /*
+ * Do the scaling (or what remains).
+ */
+ if (iExponent10 < 0)
+ pVal->r /= lrdFactor;
+ else
+ pVal->r *= lrdFactor;
+
+# if !defined(_MSC_VER) || !defined(IPRT_NO_CRT) /** @todo fix*/
+ fesetenv(&SavedFpuEnv);
+# endif
+
+#endif /* !USE_SOFTFLOAT */
+ return rc;
+}
+
+
+
+/**
+ * Set @a ppszNext and check for trailing spaces & chars if @a rc is
+ * VINF_SUCCESS.
+ *
+ * @returns IPRT status code.
+ * @param psz The current input position.
+ * @param ppszNext Where to return the pointer to the end of the value.
+ * Optional.
+ * @param cchMax Number of bytes left in the string starting at @a psz.
+ * @param rc The status code to return.
+ */
+static int rtStrToLongDoubleReturnChecks(const char *psz, char **ppszNext, size_t cchMax, int rc)
+{
+ if (ppszNext)
+ *ppszNext = (char *)psz;
+
+ /* Trailing spaces/chars warning: */
+ if (rc == VINF_SUCCESS && cchMax > 0 && *psz)
+ {
+ do
+ {
+ char ch = *psz++;
+ if (ch == ' ' || ch == '\t')
+ cchMax--;
+ else
+ return ch == '\0' ? VWRN_TRAILING_SPACES : VWRN_TRAILING_CHARS;
+ } while (cchMax > 0);
+ rc = VWRN_TRAILING_SPACES;
+ }
+ return rc;
+}
+
+
+/**
+ * Set @a pRet to infinity, set @a ppszNext, and check for trailing spaces &
+ * chars if @a rc is VINF_SUCCESS.
+ *
+ * @returns IPRT status code.
+ * @param psz The current input position.
+ * @param ppszNext Where to return the pointer to the end of the value.
+ * Optional.
+ * @param cchMax Number of bytes left in the string starting at @a psz.
+ * @param fPositive Whether the infinity should be positive or negative.
+ * @param rc The status code to return.
+ * @param iRetType The target type.
+ * @param pRet Where to store the result.
+ */
+static int rtStrToLongDoubleReturnInf(const char *psz, char **ppszNext, size_t cchMax, bool fPositive,
+ int rc, unsigned iRetType, FLOATUNION *pRet)
+{
+ /*
+ * Skip to the end of long form?
+ */
+ char ch;
+ if ( cchMax >= 5
+ && ((ch = psz[0]) == 'i' || ch == 'I')
+ && ((ch = psz[1]) == 'n' || ch == 'N')
+ && ((ch = psz[2]) == 'i' || ch == 'I')
+ && ((ch = psz[3]) == 't' || ch == 'T')
+ && ((ch = psz[4]) == 'y' || ch == 'Y'))
+ {
+ psz += 5;
+ cchMax -= 5;
+ }
+
+ /*
+ * Set the return value:
+ */
+ switch (iRetType)
+ {
+ case RET_TYPE_FLOAT:
+ {
+ RTFLOAT32U const uRet = RTFLOAT32U_INIT_INF(!fPositive);
+ AssertCompile(sizeof(uRet) == sizeof(pRet->r.r));
+ pRet->r.r = uRet.r;
+ break;
+ }
+
+ case RET_TYPE_LONG_DOUBLE:
+#if defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE) || defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ {
+# if defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+ RTFLOAT80U2 const uRet = RTFLOAT80U_INIT_INF(!fPositive);
+# else
+ RTFLOAT128U const uRet = RTFLOAT128U_INIT_INF(!fPositive);
+# endif
+ pRet->lrd.lrd = uRet.lrd;
+ break;
+ }
+#else
+ AssertCompile(sizeof(long double) == sizeof(pRet->rd.rd));
+ RT_FALL_THRU();
+#endif
+ case RET_TYPE_DOUBLE:
+ {
+ RTFLOAT64U const uRet = RTFLOAT64U_INIT_INF(!fPositive);
+ AssertCompile(sizeof(uRet) == sizeof(pRet->rd.rd));
+ pRet->rd.rd = uRet.rd;
+ break;
+ }
+
+ default: AssertFailedBreak();
+ }
+
+ /*
+ * Deal with whatever follows and return:
+ */
+ return rtStrToLongDoubleReturnChecks(psz, ppszNext, cchMax, rc);
+}
+
+
+/**
+ * Parses the tag of a "NaN(tag)" value.
+ *
+ * We take the tag to be a number to be put in the mantissa of the NaN, possibly
+ * suffixed by '[_]quiet' or '[_]signaling' (all or part) to indicate the type
+ * of NaN.
+ *
+ * @param pchTag The tag string to parse. Not zero terminated.
+ * @param cchTag The length of the tag string value.
+ * @param fPositive Whether the NaN should be positive or negative.
+ * @param iRetType The target type.
+ * @param pRet Where to store the result.
+ */
+static void rtStrParseNanTag(const char *pchTag, size_t cchTag, bool fPositive, unsigned iRetType, FLOATUNION *pRet)
+{
+ /*
+ * Skip 0x - content is hexadecimal, so this is not necessary.
+ */
+ if (cchTag > 2 && pchTag[0] == '0' && (pchTag[1] == 'x' || pchTag[1] == 'X'))
+ {
+ pchTag += 2;
+ cchTag -= 2;
+ }
+
+ /*
+ * Parse the number, ignoring overflows and stopping on non-xdigit.
+ */
+ uint64_t uHiNum = 0;
+ uint64_t uLoNum = 0;
+ unsigned iXDigit = 0;
+ while (cchTag > 0)
+ {
+ unsigned char uch = (unsigned char)*pchTag;
+ unsigned char uchDigit = g_auchDigits[uch];
+ if (uchDigit >= 16)
+ break;
+ iXDigit++;
+ if (iXDigit >= 16)
+ uHiNum = (uHiNum << 4) | (uLoNum >> 60);
+ uLoNum <<= 4;
+ uLoNum += uchDigit;
+ pchTag++;
+ cchTag--;
+ }
+
+ /*
+ * Check for special "non-standard" quiet / signalling indicator.
+ */
+ while (cchTag > 0 && *pchTag == '_')
+ pchTag++, cchTag--;
+ bool fQuiet = true;
+ if (cchTag > 0)
+ {
+ //const char *pszSkip = NULL;
+ char ch = pchTag[0];
+ if (ch == 'q' || ch == 'Q')
+ {
+ fQuiet = true;
+ //pszSkip = "qQuUiIeEtT\0"; /* cchTag stop before '\0', so we put two at the end to break out of the loop below. */
+ }
+ else if (ch == 's' || ch == 'S')
+ {
+ fQuiet = false;
+ //pszSkip = "sSiIgGnNaAlLiInNgG\0";
+ }
+ //if (pszSkip)
+ // do
+ // {
+ // pchTag++;
+ // cchTag--;
+ // pszSkip += 2;
+ // } while (cchTag > 0 && ((ch = *pchTag) == pszSkip[0] || ch == pszSkip[1]));
+ }
+
+ /*
+ * Adjust the number according to the type.
+ */
+ Assert(iRetType < RT_ELEMENTS(g_fNanMasks));
+#if defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ if (iRetType == RET_TYPE_LONG_DOUBLE)
+ uHiNum &= g_fNanMasks[RET_TYPE_LONG_DOUBLE];
+ else
+#endif
+ {
+ uHiNum = 0;
+ uLoNum &= g_fNanMasks[iRetType];
+ }
+ if (!uLoNum && !uHiNum && !fQuiet)
+ uLoNum = 1; /* must not be zero, or it'll turn into an infinity */
+
+ /*
+ * Set the return value.
+ */
+ switch (iRetType)
+ {
+ case RET_TYPE_FLOAT:
+ {
+ RTFLOAT32U const uRet = RTFLOAT32U_INIT_NAN_EX(fQuiet, !fPositive, (uint32_t)uLoNum);
+ pRet->r = uRet;
+ break;
+ }
+
+ case RET_TYPE_LONG_DOUBLE:
+#if defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE) || defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ {
+# if defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+ RTFLOAT80U2 const uRet = RTFLOAT80U_INIT_NAN_EX(fQuiet, !fPositive, uLoNum);
+# else
+ RTFLOAT128U const uRet = RTFLOAT128U_INIT_NAN_EX(fQuiet, !fPositive, uHiNum, uLoNum);
+# endif
+ pRet->lrd = uRet;
+ break;
+ }
+#else
+ AssertCompile(sizeof(long double) == sizeof(pRet->rd.rd));
+ RT_FALL_THRU();
+#endif
+ case RET_TYPE_DOUBLE:
+ {
+ RTFLOAT64U const uRet = RTFLOAT64U_INIT_NAN_EX(fQuiet, !fPositive, uLoNum);
+ pRet->rd = uRet;
+ break;
+ }
+
+ default: AssertFailedBreak();
+ }
+
+ //return cchTag == 0;
+}
+
+
+/**
+ * Finish parsing NaN, set @a pRet to NaN, set @a ppszNext, and check for
+ * trailing spaces & chars if @a rc is VINF_SUCCESS.
+ *
+ * @returns IPRT status code.
+ * @param psz The current input position.
+ * @param ppszNext Where to return the pointer to the end of the value.
+ * Optional.
+ * @param cchMax Number of bytes left in the string starting at @a psz.
+ * @param fPositive Whether the NaN should be positive or negative.
+ * @param iRetType The target type.
+ * @param pRet Where to store the result.
+ */
+static int rtStrToLongDoubleReturnNan(const char *psz, char **ppszNext, size_t cchMax, bool fPositive,
+ unsigned iRetType, FLOATUNION *pRet)
+{
+ /*
+ * Any NaN sub-number? E.g. NaN(1) or Nan(0x42). We'll require a closing
+ * parenthesis or we'll just ignore it.
+ */
+ if (cchMax >= 2 && *psz == '(')
+ {
+ unsigned cchTag = 1;
+ char ch = '\0';
+ while (cchTag < cchMax && (RT_C_IS_ALNUM((ch = psz[cchTag])) || ch == '_'))
+ cchTag++;
+ if (ch == ')')
+ {
+ rtStrParseNanTag(psz + 1, cchTag - 1, fPositive, iRetType, pRet);
+ psz += cchTag + 1;
+ cchMax -= cchTag + 1;
+ return rtStrToLongDoubleReturnChecks(psz, ppszNext, cchMax, VINF_SUCCESS);
+ }
+ }
+
+ /*
+ * Set the return value to the default NaN value.
+ */
+ switch (iRetType)
+ {
+ case RET_TYPE_FLOAT:
+ pRet->r = g_ar32QNan[fPositive];
+ break;
+
+ case RET_TYPE_DOUBLE:
+ pRet->rd = g_ardQNan[fPositive];
+ break;
+
+ case RET_TYPE_LONG_DOUBLE:
+ pRet->lrd = g_alrdQNan[fPositive];
+ break;
+
+ default: AssertFailedBreak();
+ }
+
+ return rtStrToLongDoubleReturnChecks(psz, ppszNext, cchMax, VINF_SUCCESS);
+}
+
+
+RTDECL(long double) RTStrNanLongDouble(const char *pszTag, bool fPositive)
+{
+ if (pszTag)
+ {
+ size_t cchTag = strlen(pszTag);
+ if (cchTag > 0)
+ {
+ FLOATUNION u;
+ rtStrParseNanTag(pszTag, cchTag, fPositive, RET_TYPE_LONG_DOUBLE, &u);
+ return u.lrd.r;
+ }
+ }
+ return g_alrdQNan[fPositive].r;
+}
+
+
+RTDECL(double) RTStrNanDouble(const char *pszTag, bool fPositive)
+{
+ if (pszTag)
+ {
+ size_t cchTag = strlen(pszTag);
+ if (cchTag > 0)
+ {
+ FLOATUNION u;
+ rtStrParseNanTag(pszTag, cchTag, fPositive, RET_TYPE_DOUBLE, &u);
+ return u.rd.r;
+ }
+ }
+ return g_ardQNan[fPositive].r;
+}
+
+
+RTDECL(float) RTStrNanFloat(const char *pszTag, bool fPositive)
+{
+ if (pszTag)
+ {
+ size_t cchTag = strlen(pszTag);
+ if (cchTag > 0)
+ {
+ FLOATUNION u;
+ rtStrParseNanTag(pszTag, cchTag, fPositive, RET_TYPE_FLOAT, &u);
+ return u.r.r;
+ }
+ }
+ return g_ar32QNan[fPositive].r;
+}
+
+
+/**
+ * Set @a pRet to zero, set @a ppszNext, and check for trailing spaces &
+ * chars if @a rc is VINF_SUCCESS.
+ *
+ * @returns IPRT status code.
+ * @param psz The current input position.
+ * @param ppszNext Where to return the pointer to the end of the value.
+ * Optional.
+ * @param cchMax Number of bytes left in the string starting at @a psz.
+ * @param fPositive Whether the value should be positive or negative.
+ * @param rc The status code to return.
+ * @param iRetType The target type.
+ * @param pRet Where to store the result.
+ */
+static int rtStrToLongDoubleReturnZero(const char *psz, char **ppszNext, size_t cchMax, bool fPositive,
+ int rc, unsigned iRetType, FLOATUNION *pRet)
+{
+ switch (iRetType)
+ {
+ case RET_TYPE_FLOAT:
+ pRet->r.r = fPositive ? +0.0F : -0.0F;
+ break;
+
+ case RET_TYPE_LONG_DOUBLE:
+#if defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE) || defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ pRet->lrd.lrd = fPositive ? +0.0L : -0.0L;
+ break;
+#else
+ AssertCompile(sizeof(long double) == sizeof(pRet->rd.rd));
+ RT_FALL_THRU();
+#endif
+ case RET_TYPE_DOUBLE:
+ pRet->rd.rd = fPositive ? +0.0 : -0.0;
+ break;
+
+ default: AssertFailedBreak();
+ }
+
+ return rtStrToLongDoubleReturnChecks(psz, ppszNext, cchMax, rc);
+}
+
+
+/**
+ * Return overflow or underflow - setting @a pRet and @a ppszNext accordingly.
+ *
+ * @returns IPRT status code.
+ * @param psz The current input position.
+ * @param ppszNext Where to return the pointer to the end of the value.
+ * Optional.
+ * @param cchMax Number of bytes left in the string starting at @a psz.
+ * @param fPositive Whether the value should be positive or negative.
+ * @param iExponent Overflow/underflow indicator.
+ * @param iRetType The target type.
+ * @param pRet Where to store the result.
+ */
+static int rtStrToLongDoubleReturnOverflow(const char *psz, char **ppszNext, size_t cchMax, bool fPositive,
+ int32_t iExponent, unsigned iRetType, FLOATUNION *pRet)
+{
+ if (iExponent > 0)
+ return rtStrToLongDoubleReturnInf(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_OVERFLOW, iRetType, pRet);
+ return rtStrToLongDoubleReturnZero(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_UNDERFLOW, iRetType, pRet);
+}
+
+
+/**
+ * Returns a denormal/subnormal value.
+ *
+ * This implies that iRetType is long double, or double if they are the same,
+ * and that we should warn about underflowing.
+ */
+static int rtStrToLongDoubleReturnSubnormal(const char *psz, char **ppszNext, size_t cchMax, LONG_DOUBLE_U_T const *pVal,
+ unsigned iRetType, FLOATUNION *pRet)
+{
+#if defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE) || defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ Assert(iRetType == RET_TYPE_LONG_DOUBLE);
+ pRet->lrd = *pVal;
+#else
+ Assert(iRetType == RET_TYPE_LONG_DOUBLE || iRetType == RET_TYPE_DOUBLE);
+ pRet->rd = *pVal;
+#endif
+ RT_NOREF(iRetType);
+ return rtStrToLongDoubleReturnChecks(psz, ppszNext, cchMax, VWRN_FLOAT_UNDERFLOW);
+}
+
+
+/**
+ * Packs the given sign, mantissa, and (power of 2) exponent into the
+ * return value.
+ */
+static int rtStrToLongDoubleReturnValue(const char *psz, char **ppszNext, size_t cchMax,
+ bool fPositive, UINT_MANTISSA_T uMantissa, int32_t iExponent,
+ unsigned iRetType, FLOATUNION *pRet)
+{
+ int rc = VINF_SUCCESS;
+ switch (iRetType)
+ {
+ case RET_TYPE_FLOAT:
+ iExponent += RTFLOAT32U_EXP_BIAS;
+ if (iExponent <= 0)
+ {
+ /* Produce a subnormal value if it's within range, otherwise return zero. */
+ if (iExponent < -RTFLOAT32U_FRACTION_BITS)
+ return rtStrToLongDoubleReturnZero(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_UNDERFLOW, iRetType, pRet);
+ rc = VWRN_FLOAT_UNDERFLOW;
+ uMantissa >>= -iExponent + 1;
+ iExponent = 0;
+ }
+ else if (iExponent >= RTFLOAT32U_EXP_MAX)
+ return rtStrToLongDoubleReturnInf(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_OVERFLOW, iRetType, pRet);
+
+ pRet->r.s.uFraction = (uMantissa >> (UINT_MANTISSA_T_BITS - 1 - RTFLOAT32U_FRACTION_BITS))
+ & (RT_BIT_32(RTFLOAT32U_FRACTION_BITS) - 1);
+ pRet->r.s.uExponent = iExponent;
+ pRet->r.s.fSign = !fPositive;
+ break;
+
+ case RET_TYPE_LONG_DOUBLE:
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+# if UINT_MANTISSA_T_BITS != 64
+# error Unsupported UINT_MANTISSA_T_BITS count.
+# endif
+ iExponent += RTFLOAT80U_EXP_BIAS;
+ if (iExponent <= 0)
+ {
+ /* Produce a subnormal value if it's within range, otherwise return zero. */
+ if (iExponent < -RTFLOAT80U_FRACTION_BITS)
+ return rtStrToLongDoubleReturnZero(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_UNDERFLOW, iRetType, pRet);
+ rc = VWRN_FLOAT_UNDERFLOW;
+ uMantissa >>= -iExponent + 1;
+ iExponent = 0;
+ }
+ else if (iExponent >= RTFLOAT80U_EXP_MAX)
+ return rtStrToLongDoubleReturnInf(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_OVERFLOW, iRetType, pRet);
+
+ pRet->lrd.s.uMantissa = uMantissa;
+ pRet->lrd.s.uExponent = iExponent;
+ pRet->lrd.s.fSign = !fPositive;
+ break;
+#elif defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ iExponent += RTFLOAT128U_EXP_BIAS;
+ uMantissa >>= 128 - RTFLOAT128U_FRACTION_BITS;
+ if (iExponent <= 0)
+ {
+ /* Produce a subnormal value if it's within range, otherwise return zero. */
+ if (iExponent < -RTFLOAT128U_FRACTION_BITS)
+ return rtStrToLongDoubleReturnZero(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_UNDERFLOW, iRetType, pRet);
+ rc = VWRN_FLOAT_UNDERFLOW;
+ uMantissa >>= -iExponent + 1;
+ iExponent = 0;
+ }
+ else if (iExponent >= RTFLOAT80U_EXP_MAX)
+ return rtStrToLongDoubleReturnInf(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_OVERFLOW, iRetType, pRet);
+ pRet->lrd.s64.uFractionHi = (uint64_t)(uMantissa >> 64) & (RT_BIT_64(RTFLOAT128U_FRACTION_BITS - 64) - 1);
+ pRet->lrd.s64.uFractionLo = (uint64_t)uMantissa;
+ pRet->lrd.s64.uExponent = iExponent;
+ pRet->lrd.s64.fSign = !fPositive;
+ break;
+#else
+ AssertCompile(sizeof(long double) == sizeof(pRet->rd.rd));
+ RT_FALL_THRU();
+#endif
+ case RET_TYPE_DOUBLE:
+ iExponent += RTFLOAT64U_EXP_BIAS;
+ if (iExponent <= 0)
+ {
+ /* Produce a subnormal value if it's within range, otherwise return zero. */
+ if (iExponent < -RTFLOAT64U_FRACTION_BITS)
+ return rtStrToLongDoubleReturnZero(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_UNDERFLOW, iRetType, pRet);
+ rc = VWRN_FLOAT_UNDERFLOW;
+ uMantissa >>= -iExponent + 1;
+ iExponent = 0;
+ }
+ else if (iExponent >= RTFLOAT64U_EXP_MAX)
+ return rtStrToLongDoubleReturnInf(psz, ppszNext, cchMax, fPositive, VERR_FLOAT_OVERFLOW, iRetType, pRet);
+
+ pRet->rd.s64.uFraction = (uMantissa >> (UINT_MANTISSA_T_BITS - 1 - RTFLOAT64U_FRACTION_BITS))
+ & (RT_BIT_64(RTFLOAT64U_FRACTION_BITS) - 1);
+ pRet->rd.s64.uExponent = iExponent;
+ pRet->rd.s64.fSign = !fPositive;
+ break;
+
+ default:
+ AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+ return rtStrToLongDoubleReturnChecks(psz, ppszNext, cchMax, rc);
+}
+
+
+/**
+ * Worker for RTStrToLongDoubleEx, RTStrToDoubleEx and RTStrToFloatEx.
+ *
+ * @returns IPRT status code
+ * @param pszValue The string value to convert.
+ * @param ppszNext Where to return the pointer to the end of the value.
+ * Optional.
+ * @param cchMax Number of bytes left in the string starting at @a psz.
+ * @param iRetType The return type: float, double or long double.
+ * @param pRet The return value union.
+ */
+static int rtStrToLongDoubleWorker(const char *pszValue, char **ppszNext, size_t cchMax, unsigned iRetType, FLOATUNION *pRet)
+{
+ const char *psz = pszValue;
+ if (!cchMax)
+ cchMax = ~(size_t)cchMax;
+
+ /*
+ * Sign.
+ */
+ bool fPositive = true;
+ while (cchMax > 0)
+ {
+ if (*psz == '+')
+ fPositive = true;
+ else if (*psz == '-')
+ fPositive = !fPositive;
+ else
+ break;
+ psz++;
+ cchMax--;
+ }
+
+ /*
+ * Constant like "Inf", "Infinity", "NaN" or "NaN(hexstr)"?
+ */
+ /* "Inf" or "Infinity"? */
+ if (cchMax == 0)
+ return rtStrToLongDoubleReturnZero(pszValue, ppszNext, cchMax, fPositive, VERR_NO_DIGITS, iRetType, pRet);
+ if (cchMax >= 3)
+ {
+ char ch = *psz;
+ /* Inf: */
+ if (ch == 'i' || ch == 'I')
+ {
+ if ( ((ch = psz[1]) == 'n' || ch == 'N')
+ && ((ch = psz[2]) == 'f' || ch == 'F'))
+ return rtStrToLongDoubleReturnInf(psz + 3, ppszNext, cchMax - 3, fPositive, VINF_SUCCESS, iRetType, pRet);
+ }
+ /* Nan: */
+ else if (ch == 'n' || ch == 'N')
+ {
+ if ( ((ch = psz[1]) == 'a' || ch == 'A')
+ && ((ch = psz[2]) == 'n' || ch == 'N'))
+ return rtStrToLongDoubleReturnNan(psz + 3, ppszNext, cchMax - 3, fPositive, iRetType, pRet);
+ }
+ }
+
+ /*
+ * Check for hex prefix.
+ */
+#ifdef RT_COMPILER_WITH_128BIT_LONG_DOUBLE
+ unsigned cMaxDigits = 33;
+#elif defined(RT_COMPILER_WITH_80BIT_LONG_DOUBLE)
+ unsigned cMaxDigits = 19;
+#else
+ unsigned cMaxDigits = 18;
+#endif
+ unsigned uBase = 10;
+ unsigned uExpDigitFactor = 1;
+ if (cchMax >= 2 && psz[0] == '0' && (psz[1] == 'x' || psz[1] == 'X'))
+ {
+ cMaxDigits = 16;
+ uBase = 16;
+ uExpDigitFactor = 4;
+ cchMax -= 2;
+ psz += 2;
+ }
+
+ /*
+ * Now, parse the mantissa.
+ */
+#ifdef RT_COMPILER_WITH_128BIT_LONG_DOUBLE
+ uint8_t abDigits[36];
+#else
+ uint8_t abDigits[20];
+#endif
+ unsigned cDigits = 0;
+ unsigned cFractionDigits = 0;
+ uint8_t fSeenNonZeroDigit = 0;
+ bool fInFraction = false;
+ bool fSeenDigits = false;
+ while (cchMax > 0)
+ {
+ uint8_t b = g_auchDigits[(unsigned char)*psz];
+ if (b < uBase)
+ {
+ fSeenDigits = true;
+ fSeenNonZeroDigit |= b;
+ if (fSeenNonZeroDigit)
+ {
+ if (cDigits < RT_ELEMENTS(abDigits))
+ abDigits[cDigits] = b;
+ cDigits++;
+ cFractionDigits += fInFraction;
+ }
+ }
+ else if (b == DIGITS_DOT && !fInFraction)
+ fInFraction = true;
+ else
+ break;
+ psz++;
+ cchMax--;
+ }
+
+ /* If we've seen no digits, or just a dot, return zero already. */
+ if (!fSeenDigits)
+ {
+ if (fInFraction) /* '+.' => 0.0 ? */
+ return rtStrToLongDoubleReturnZero(psz, ppszNext, cchMax, fPositive, VINF_SUCCESS, iRetType, pRet);
+ if (uBase == 16) /* '+0x' => 0.0 & *=pszNext="x..." */
+ return rtStrToLongDoubleReturnZero(psz - 1, ppszNext, cchMax, fPositive, VINF_SUCCESS, iRetType, pRet);
+ /* '' and '+' -> no digits + 0.0. */
+ return rtStrToLongDoubleReturnZero(pszValue, ppszNext, cchMax, fPositive, VERR_NO_DIGITS, iRetType, pRet);
+ }
+
+ /*
+ * Parse the exponent.
+ * This is optional and we ignore incomplete ones like "e+".
+ */
+ int32_t iExponent = 0;
+ if (cchMax >= 2) /* min "e0" */
+ {
+ char ch = *psz;
+ if (uBase == 10 ? ch == 'e' || ch == 'E' : ch == 'p' || ch == 'P')
+ {
+ bool fExpOverflow = false;
+ bool fPositiveExp = true;
+ size_t off = 1;
+ ch = psz[off];
+ if (ch == '+' || ch == '-')
+ {
+ fPositiveExp = ch == '+';
+ off++;
+ }
+ uint8_t b;
+ if ( off < cchMax
+ && (b = g_auchDigits[(unsigned char)psz[off]]) < 10)
+ {
+ do
+ {
+ int32_t const iPreviousExponent = iExponent;
+ iExponent *= 10;
+ iExponent += b;
+ if (iExponent < iPreviousExponent)
+ fExpOverflow = true;
+ off++;
+ } while (off < cchMax && (b = g_auchDigits[(unsigned char)psz[off]]) < 10);
+ if (!fPositiveExp)
+ iExponent = -iExponent;
+ cchMax -= off;
+ psz += off;
+ }
+ if (fExpOverflow || iExponent <= -65536 || iExponent >= 65536)
+ return rtStrToLongDoubleReturnOverflow(pszValue, ppszNext, cchMax, fPositive, iExponent, iRetType, pRet);
+ }
+ }
+
+ /* If the mantissa was all zeros, we can return zero now that we're past the exponent. */
+ if (!fSeenNonZeroDigit)
+ return rtStrToLongDoubleReturnZero(psz, ppszNext, cchMax, fPositive, VINF_SUCCESS, iRetType, pRet);
+
+ /*
+ * Adjust the expontent so we've got all digits to the left of the decimal point.
+ */
+ iExponent -= cFractionDigits * uExpDigitFactor;
+
+ /*
+ * Drop digits we won't translate.
+ */
+ if (cDigits > cMaxDigits)
+ {
+ iExponent += (cDigits - cMaxDigits) * uExpDigitFactor;
+ cDigits = cMaxDigits;
+ }
+
+ /*
+ * Strip least significant zero digits.
+ */
+ while (cDigits > 0 && abDigits[cDigits - 1] == 0)
+ {
+ cDigits--;
+ iExponent += uExpDigitFactor;
+ }
+
+ /*
+ * The hexadecimal is relatively straight forward.
+ */
+ if (uBase == 16)
+ {
+ UINT_MANTISSA_T uMantissa = 0;
+ for (unsigned iDigit = 0; iDigit < cDigits; iDigit++)
+ {
+ uMantissa |= (UINT_MANTISSA_T)abDigits[iDigit] << (UINT_MANTISSA_T_BITS - 4 - iDigit * 4);
+ iExponent += 4;
+ }
+ Assert(uMantissa != 0);
+
+ /* Shift to the left till the most significant bit is 1. */
+ if (!((uMantissa >> (UINT_MANTISSA_T_BITS - 1)) & 1))
+ {
+#if UINT_MANTISSA_T_BITS == 64
+ unsigned cShift = 64 - ASMBitLastSetU64(uMantissa);
+ uMantissa <<= cShift;
+ iExponent -= cShift;
+ Assert(uMantissa & RT_BIT_64(63));
+#else
+ do
+ {
+ uMantissa <<= 1;
+ iExponent -= 1;
+ } while (!((uMantissa >> (UINT_MANTISSA_T_BITS - 1)) & 1));
+#endif
+ }
+
+ /* Account for the 1 left of the decimal point. */
+ iExponent--;
+
+ /*
+ * Produce the return value.
+ */
+ return rtStrToLongDoubleReturnValue(psz, ppszNext, cchMax, fPositive, uMantissa, iExponent, iRetType, pRet);
+ }
+
+ /*
+ * For the decimal format, we'll rely on the floating point conversion of
+ * the compiler/CPU for the mantissa.
+ */
+ UINT_MANTISSA_T uMantissa = 0;
+ for (unsigned iDigit = 0; iDigit < cDigits; iDigit++)
+ {
+ uMantissa *= 10;
+ uMantissa += abDigits[iDigit];
+ }
+ Assert(uMantissa != 0);
+
+ LONG_DOUBLE_U_T uTmp;
+ uTmp.r = fPositive ? (long double)uMantissa : -(long double)uMantissa;
+
+ /*
+ * Here comes the fun part, scaling it according to the power of 10 exponent.
+ * We only need to consider overflows and underflows when scaling, when
+ * iExponent is zero we can be sure the target type can handle the result.
+ */
+ if (iExponent != 0)
+ {
+ rtStrToLongDoubleExp10(&uTmp, iExponent);
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ if (!RTFLOAT80U_IS_NORMAL(&uTmp))
+#elif defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ if (!RTFLOAT128U_IS_NORMAL(&uTmp))
+#else
+ if (!RTFLOAT64U_IS_NORMAL(&uTmp))
+#endif
+ {
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ if (RTFLOAT80U_IS_DENORMAL(&uTmp) && iRetType == RET_TYPE_LONG_DOUBLE)
+#elif defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ if (RTFLOAT128U_IS_SUBNORMAL(&uTmp) && iRetType == RET_TYPE_LONG_DOUBLE)
+#else
+ if (RTFLOAT64U_IS_SUBNORMAL(&uTmp) && iRetType != RET_TYPE_FLOAT)
+#endif
+ return rtStrToLongDoubleReturnSubnormal(psz, ppszNext, cchMax, &uTmp, iRetType, pRet);
+ return rtStrToLongDoubleReturnOverflow(psz, ppszNext, cchMax, fPositive, iExponent, iRetType, pRet);
+ }
+ }
+
+ /*
+ * We've got a normal value in uTmp when we get here, just repack it in the
+ * target format and return.
+ */
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ Assert(RTFLOAT80U_IS_NORMAL(&uTmp));
+ if (iRetType == RET_TYPE_LONG_DOUBLE)
+ {
+ pRet->lrd = uTmp;
+ return rtStrToLongDoubleReturnChecks(psz, ppszNext, cchMax, VINF_SUCCESS);
+ }
+ fPositive = uTmp.s.fSign;
+ iExponent = uTmp.s.uExponent - RTFLOAT80U_EXP_BIAS;
+ uMantissa = uTmp.s.uMantissa;
+# if UINT_MANTISSA_T_BITS > 64
+ uMantissa <<= UINT_MANTISSA_T_BITS - 64;
+# endif
+#elif defined(RT_COMPILER_WITH_128BIT_LONG_DOUBLE)
+ Assert(RTFLOAT128U_IS_NORMAL(&uTmp));
+ if (iRetType == RET_TYPE_LONG_DOUBLE)
+ {
+ pRet->lrd = uTmp;
+ return rtStrToLongDoubleReturnChecks(psz, ppszNext, cchMax, VINF_SUCCESS);
+ }
+ fPositive = uTmp.s64.fSign;
+ iExponent = uTmp.s64.uExponent - RTFLOAT128U_EXP_BIAS;
+ uMantissa = (UINT_MANTISSA_T)uTmp.s64.uFractionHi << (UINT_MANTISSA_T_BITS - RTFLOAT128U_FRACTION_BITS - 1 + 64);
+ uMantissa |= (UINT_MANTISSA_T)uTmp.s64.uFractionLo << (UINT_MANTISSA_T_BITS - RTFLOAT128U_FRACTION_BITS - 1);
+ uMantissa |= (UINT_MANTISSA_T)1 << (UINT_MANTISSA_T_BITS - 1);
+#else
+ Assert(RTFLOAT64U_IS_NORMAL(&uTmp));
+ if ( iRetType == RET_TYPE_DOUBLE
+ || iRetType == RET_TYPE_LONG_DOUBLE)
+ {
+ pRet->rd = uTmp;
+ return rtStrToLongDoubleReturnChecks(psz, ppszNext, cchMax, VINF_SUCCESS);
+ }
+ fPositive = uTmp.s64.fSign;
+ iExponent = uTmp.s64.uExponent - RTFLOAT64U_EXP_BIAS;
+ uMantissa = uTmp.s64.uFraction | RT_BIT_64(RTFLOAT64U_FRACTION_BITS);
+# if UINT_MANTISSA_T_BITS > 64
+ uMantissa <<= UINT_MANTISSA_T_BITS - 64;
+# endif
+#endif
+ return rtStrToLongDoubleReturnValue(psz, ppszNext, cchMax, fPositive, uMantissa, iExponent, iRetType, pRet);
+}
+
+
+RTDECL(int) RTStrToLongDoubleEx(const char *pszValue, char **ppszNext, size_t cchMax, long double *plrd)
+{
+ FLOATUNION u;
+ int rc = rtStrToLongDoubleWorker(pszValue, ppszNext, cchMax, RET_TYPE_LONG_DOUBLE, &u);
+ if (plrd)
+#ifdef RT_COMPILER_WITH_80BIT_LONG_DOUBLE
+ *plrd = u.lrd.lrd;
+#else
+ *plrd = u.rd.rd;
+#endif
+ return rc;
+}
+
+
+RTDECL(int) RTStrToDoubleEx(const char *pszValue, char **ppszNext, size_t cchMax, double *prd)
+{
+ FLOATUNION u;
+ int rc = rtStrToLongDoubleWorker(pszValue, ppszNext, cchMax, RET_TYPE_DOUBLE, &u);
+ if (prd)
+ *prd = u.rd.rd;
+ return rc;
+}
+
+
+RTDECL(int) RTStrToFloatEx(const char *pszValue, char **ppszNext, size_t cchMax, float *pr)
+{
+ FLOATUNION u;
+ int rc = rtStrToLongDoubleWorker(pszValue, ppszNext, cchMax, RET_TYPE_FLOAT, &u);
+ if (pr)
+ *pr = u.r.r;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/string/strtok_r.cpp b/src/VBox/Runtime/common/string/strtok_r.cpp
new file mode 100644
index 00000000..349ee884
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strtok_r.cpp
@@ -0,0 +1,90 @@
+/* $Id: strtok_r.cpp $ */
+/** @file
+ * IPRT - No-CRT Strings, strtok_r().
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/string.h>
+
+
+#undef strtok_r
+char *RT_NOCRT(strtok_r)(char *psz, const char *pszDelimiters, char **ppszState)
+{
+ /*
+ * Load state.
+ */
+ if (!psz)
+ {
+ psz = *ppszState;
+ if (!psz)
+ return NULL;
+ }
+
+ /*
+ * Skip leading delimiters.
+ */
+ size_t const cchDelimiters = strlen(pszDelimiters);
+ for (;;)
+ {
+ char ch = *psz;
+ if (!ch)
+ return *ppszState = NULL;
+ if (memchr(pszDelimiters, ch, cchDelimiters) == NULL)
+ break;
+ psz++;
+ }
+
+ /*
+ * Find the end of the token.
+ */
+ char * const pszRet = psz;
+ for (;;)
+ {
+ char ch = *++psz;
+ if (memchr(pszDelimiters, ch, cchDelimiters + 1 /* '\0' */) == NULL)
+ { /* semi-likely */ }
+ else
+ {
+ *psz = '\0';
+ *ppszState = ch ? psz + 1 : NULL;
+ return pszRet;
+ }
+ }
+}
+RT_ALIAS_AND_EXPORT_NOCRT_SYMBOL(strtok_r);
+
diff --git a/src/VBox/Runtime/common/string/strtonum.cpp b/src/VBox/Runtime/common/string/strtonum.cpp
new file mode 100644
index 00000000..08c5393c
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strtonum.cpp
@@ -0,0 +1,934 @@
+/* $Id: strtonum.cpp $ */
+/** @file
+ * IPRT - String To Number Conversion.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h> /* needed for RT_C_IS_DIGIT */
+#include <iprt/err.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+extern const unsigned char g_auchDigits[256]; /* shared with strtofloat.cpp - add header? */
+
+/** 8-bit char -> digit.
+ * Non-digits have values 255 (most), 254 (zero), 253 (colon), 252 (space), 251 (dot).
+ *
+ * @note Also used by strtofloat.cpp
+ */
+const unsigned char g_auchDigits[256] =
+{
+ 254,255,255,255,255,255,255,255,255,252,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ 252,255,255,255,255,255,255,255,255,255,255,255,255,255,251,255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,253,255,255,255,255,255,
+ 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,255,255,255,255,255,
+ 255, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
+};
+
+#define DIGITS_ZERO_TERM 254
+#define DIGITS_COLON 253
+#define DIGITS_SPACE 252
+#define DIGITS_DOT 251
+
+/** Approximated overflow shift checks. */
+static const char g_auchShift[36] =
+{
+ /* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 */
+ 64, 64, 63, 63, 62, 62, 62, 62, 61, 61, 61, 61, 61, 61, 61, 61, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 59, 59, 59, 59
+};
+
+/*
+#include <stdio.h>
+int main()
+{
+ int i;
+ printf("static const unsigned char g_auchDigits[256] =\n"
+ "{");
+ for (i = 0; i < 256; i++)
+ {
+ int ch = 255;
+ if (i >= '0' && i <= '9')
+ ch = i - '0';
+ else if (i >= 'a' && i <= 'z')
+ ch = i - 'a' + 10;
+ else if (i >= 'A' && i <= 'Z')
+ ch = i - 'A' + 10;
+ else if (i == 0)
+ ch = 254;
+ else if (i == ':')
+ ch = 253;
+ else if (i == ' ' || i == '\t')
+ ch = 252;
+ else if (i == '.')
+ ch = 251;
+ if (i == 0)
+ printf("\n %3d", ch);
+ else if ((i % 32) == 0)
+ printf(",\n %3d", ch);
+ else
+ printf(",%3d", ch);
+ }
+ printf("\n"
+ "};\n");
+ return 0;
+}
+*/
+
+
+RTDECL(int) RTStrToUInt64Ex(const char *pszValue, char **ppszNext, unsigned uBaseAndMaxLen, uint64_t *pu64)
+{
+ const char *psz = pszValue;
+ int iShift;
+ int rc;
+ uint64_t u64;
+ unsigned char uch;
+ bool fPositive;
+
+ /*
+ * Split the base and length limit (latter is chiefly for sscanf).
+ */
+ unsigned uBase = uBaseAndMaxLen & 0xff;
+ unsigned cchMax = uBaseAndMaxLen >> 8;
+ if (cchMax == 0)
+ cchMax = ~0U;
+ AssertStmt(uBase < RT_ELEMENTS(g_auchShift), uBase = 0);
+
+ /*
+ * Positive/Negative stuff.
+ */
+ fPositive = true;
+ while (cchMax > 0)
+ {
+ if (*psz == '+')
+ fPositive = true;
+ else if (*psz == '-')
+ fPositive = !fPositive;
+ else
+ break;
+ psz++;
+ cchMax--;
+ }
+
+ /*
+ * Check for hex prefix.
+ */
+ if (!uBase)
+ {
+ uBase = 10;
+ if (psz[0] == '0')
+ {
+ if ( psz[0] == '0'
+ && cchMax > 1
+ && (psz[1] == 'x' || psz[1] == 'X')
+ && g_auchDigits[(unsigned char)psz[2]] < 16)
+ {
+ uBase = 16;
+ psz += 2;
+ cchMax -= 2;
+ }
+ else if ( psz[0] == '0'
+ && g_auchDigits[(unsigned char)psz[1]] < 8)
+ uBase = 8; /* don't skip the zero, in case it's alone. */
+ }
+ }
+ else if ( uBase == 16
+ && psz[0] == '0'
+ && cchMax > 1
+ && (psz[1] == 'x' || psz[1] == 'X')
+ && g_auchDigits[(unsigned char)psz[2]] < 16)
+ {
+ cchMax -= 2;
+ psz += 2;
+ }
+
+ /*
+ * Interpret the value.
+ * Note: We only support ascii digits at this time... :-)
+ */
+ pszValue = psz; /* (Prefix and sign doesn't count in the digit counting.) */
+ iShift = g_auchShift[uBase];
+ rc = VINF_SUCCESS;
+ u64 = 0;
+ while (cchMax > 0 && (uch = (unsigned char)*psz) != 0)
+ {
+ unsigned char chDigit = g_auchDigits[uch];
+ uint64_t u64Prev;
+
+ if (chDigit >= uBase)
+ break;
+
+ u64Prev = u64;
+ u64 *= uBase;
+ u64 += chDigit;
+ if (u64Prev > u64 || (u64Prev >> iShift))
+ rc = VWRN_NUMBER_TOO_BIG;
+ psz++;
+ cchMax--;
+ }
+
+ if (!fPositive)
+ {
+ if (rc == VINF_SUCCESS)
+ rc = VWRN_NEGATIVE_UNSIGNED;
+ u64 = -(int64_t)u64;
+ }
+
+ if (pu64)
+ *pu64 = u64;
+
+ if (psz == pszValue)
+ rc = VERR_NO_DIGITS;
+
+ if (ppszNext)
+ *ppszNext = (char *)psz;
+
+ /*
+ * Warn about trailing chars/spaces.
+ */
+ if ( rc == VINF_SUCCESS
+ && *psz
+ && cchMax > 0)
+ {
+ while (cchMax > 0 && (*psz == ' ' || *psz == '\t'))
+ psz++, cchMax--;
+ rc = cchMax > 0 && *psz ? VWRN_TRAILING_CHARS : VWRN_TRAILING_SPACES;
+ }
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt64Ex);
+
+
+RTDECL(int) RTStrToUInt64Full(const char *pszValue, unsigned uBaseAndMaxLen, uint64_t *pu64)
+{
+ char *psz;
+ int rc = RTStrToUInt64Ex(pszValue, &psz, uBaseAndMaxLen, pu64);
+ if (RT_SUCCESS(rc) && *psz)
+ {
+ if (rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES)
+ rc = -rc;
+ else if (rc != VINF_SUCCESS)
+ {
+ unsigned cchMax = uBaseAndMaxLen >> 8;
+ if (!cchMax)
+ cchMax = ~0U;
+ else
+ cchMax -= (unsigned)(psz - pszValue);
+ if (cchMax > 0)
+ {
+ while (cchMax > 0 && (*psz == ' ' || *psz == '\t'))
+ psz++, cchMax--;
+ rc = cchMax > 0 && *psz ? VERR_TRAILING_CHARS : VERR_TRAILING_SPACES;
+ }
+ }
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt64Full);
+
+
+RTDECL(uint64_t) RTStrToUInt64(const char *pszValue)
+{
+ uint64_t u64;
+ int rc = RTStrToUInt64Ex(pszValue, NULL, 0, &u64);
+ if (RT_SUCCESS(rc))
+ return u64;
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt64);
+
+
+RTDECL(int) RTStrToUInt32Ex(const char *pszValue, char **ppszNext, unsigned uBaseAndMaxLen, uint32_t *pu32)
+{
+ uint64_t u64;
+ int rc = RTStrToUInt64Ex(pszValue, ppszNext, uBaseAndMaxLen, &u64);
+ if (RT_SUCCESS(rc))
+ {
+ if (u64 & ~0xffffffffULL)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pu32)
+ *pu32 = (uint32_t)u64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt32Ex);
+
+
+RTDECL(int) RTStrToUInt32Full(const char *pszValue, unsigned uBaseAndMaxLen, uint32_t *pu32)
+{
+ uint64_t u64;
+ int rc = RTStrToUInt64Full(pszValue, uBaseAndMaxLen, &u64);
+ if (RT_SUCCESS(rc))
+ {
+ if (u64 & ~0xffffffffULL)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pu32)
+ *pu32 = (uint32_t)u64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt32Full);
+
+
+RTDECL(uint32_t) RTStrToUInt32(const char *pszValue)
+{
+ uint32_t u32;
+ int rc = RTStrToUInt32Ex(pszValue, NULL, 0, &u32);
+ if (RT_SUCCESS(rc))
+ return u32;
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt32);
+
+
+RTDECL(int) RTStrToUInt16Ex(const char *pszValue, char **ppszNext, unsigned uBaseAndMaxLen, uint16_t *pu16)
+{
+ uint64_t u64;
+ int rc = RTStrToUInt64Ex(pszValue, ppszNext, uBaseAndMaxLen, &u64);
+ if (RT_SUCCESS(rc))
+ {
+ if (u64 & ~0xffffULL)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pu16)
+ *pu16 = (uint16_t)u64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt16Ex);
+
+
+RTDECL(int) RTStrToUInt16Full(const char *pszValue, unsigned uBaseAndMaxLen, uint16_t *pu16)
+{
+ uint64_t u64;
+ int rc = RTStrToUInt64Full(pszValue, uBaseAndMaxLen, &u64);
+ if (RT_SUCCESS(rc))
+ {
+ if (u64 & ~0xffffULL)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pu16)
+ *pu16 = (uint16_t)u64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt16Full);
+
+
+RTDECL(uint16_t) RTStrToUInt16(const char *pszValue)
+{
+ uint16_t u16;
+ int rc = RTStrToUInt16Ex(pszValue, NULL, 0, &u16);
+ if (RT_SUCCESS(rc))
+ return u16;
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt16);
+
+
+RTDECL(int) RTStrToUInt8Ex(const char *pszValue, char **ppszNext, unsigned uBaseAndMaxLen, uint8_t *pu8)
+{
+ uint64_t u64;
+ int rc = RTStrToUInt64Ex(pszValue, ppszNext, uBaseAndMaxLen, &u64);
+ if (RT_SUCCESS(rc))
+ {
+ if (u64 & ~0xffULL)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pu8)
+ *pu8 = (uint8_t)u64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt8Ex);
+
+
+RTDECL(int) RTStrToUInt8Full(const char *pszValue, unsigned uBaseAndMaxLen, uint8_t *pu8)
+{
+ uint64_t u64;
+ int rc = RTStrToUInt64Full(pszValue, uBaseAndMaxLen, &u64);
+ if (RT_SUCCESS(rc))
+ {
+ if (u64 & ~0xffULL)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pu8)
+ *pu8 = (uint8_t)u64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt8Full);
+
+
+RTDECL(uint8_t) RTStrToUInt8(const char *pszValue)
+{
+ uint8_t u8;
+ int rc = RTStrToUInt8Ex(pszValue, NULL, 0, &u8);
+ if (RT_SUCCESS(rc))
+ return u8;
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTStrToUInt8);
+
+
+RTDECL(int) RTStrToInt64Ex(const char *pszValue, char **ppszNext, unsigned uBaseAndMaxLen, int64_t *pi64)
+{
+ const char *psz = pszValue;
+ int iShift;
+ int rc;
+ uint64_t u64;
+ unsigned char uch;
+ bool fPositive;
+
+ /*
+ * Split the base and length limit (latter is chiefly for sscanf).
+ */
+ unsigned uBase = uBaseAndMaxLen & 0xff;
+ unsigned cchMax = uBaseAndMaxLen >> 8;
+ if (cchMax == 0)
+ cchMax = ~0U;
+ AssertStmt(uBase < RT_ELEMENTS(g_auchShift), uBase = 0);
+
+ /*
+ * Positive/Negative stuff.
+ */
+ fPositive = true;
+ while (cchMax > 0)
+ {
+ if (*psz == '+')
+ fPositive = true;
+ else if (*psz == '-')
+ fPositive = !fPositive;
+ else
+ break;
+ psz++;
+ cchMax--;
+ }
+
+ /*
+ * Check for hex prefix.
+ */
+ if (!uBase)
+ {
+ uBase = 10;
+ if (psz[0] == '0')
+ {
+ if ( psz[0] == '0'
+ && cchMax > 1
+ && (psz[1] == 'x' || psz[1] == 'X')
+ && g_auchDigits[(unsigned char)psz[2]] < 16)
+ {
+ uBase = 16;
+ psz += 2;
+ cchMax -= 2;
+ }
+ else if ( psz[0] == '0'
+ && g_auchDigits[(unsigned char)psz[1]] < 8)
+ uBase = 8; /* don't skip the zero, in case it's alone. */
+ }
+ }
+ else if ( uBase == 16
+ && psz[0] == '0'
+ && cchMax > 1
+ && (psz[1] == 'x' || psz[1] == 'X')
+ && g_auchDigits[(unsigned char)psz[2]] < 16)
+ {
+ cchMax -= 2;
+ psz += 2;
+ }
+
+ /*
+ * Interpret the value.
+ * Note: We only support ascii digits at this time... :-)
+ */
+ pszValue = psz; /* (Prefix and sign doesn't count in the digit counting.) */
+ iShift = g_auchShift[uBase];
+ rc = VINF_SUCCESS;
+ u64 = 0;
+ while (cchMax > 0 && (uch = (unsigned char)*psz) != 0)
+ {
+ unsigned char chDigit = g_auchDigits[uch];
+ uint64_t u64Prev;
+
+ if (chDigit >= uBase)
+ break;
+
+ u64Prev = u64;
+ u64 *= uBase;
+ u64 += chDigit;
+ if (u64Prev > u64 || (u64Prev >> iShift))
+ rc = VWRN_NUMBER_TOO_BIG;
+ psz++;
+ cchMax--;
+ }
+
+ /* Mixing pi64 assigning and overflow checks is to pacify a tstRTCRest-1
+ asan overflow warning. */
+ if (!(u64 & RT_BIT_64(63)))
+ {
+ if (psz == pszValue)
+ rc = VERR_NO_DIGITS;
+ if (pi64)
+ *pi64 = fPositive ? u64 : -(int64_t)u64;
+ }
+ else if (!fPositive && u64 == RT_BIT_64(63))
+ {
+ if (pi64)
+ *pi64 = INT64_MIN;
+ }
+ else
+ {
+ rc = VWRN_NUMBER_TOO_BIG;
+ if (pi64)
+ *pi64 = fPositive ? u64 : -(int64_t)u64;
+ }
+
+ if (ppszNext)
+ *ppszNext = (char *)psz;
+
+ /*
+ * Warn about trailing chars/spaces.
+ */
+ if ( rc == VINF_SUCCESS
+ && cchMax > 0
+ && *psz)
+ {
+ while (cchMax > 0 && (*psz == ' ' || *psz == '\t'))
+ psz++, cchMax--;
+ rc = cchMax > 0 && *psz ? VWRN_TRAILING_CHARS : VWRN_TRAILING_SPACES;
+ }
+
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToInt64Ex);
+
+
+RTDECL(int) RTStrToInt64Full(const char *pszValue, unsigned uBaseAndMaxLen, int64_t *pi64)
+{
+ char *psz;
+ int rc = RTStrToInt64Ex(pszValue, &psz, uBaseAndMaxLen, pi64);
+ if (RT_SUCCESS(rc) && *psz)
+ {
+ if (rc == VWRN_TRAILING_CHARS || rc == VWRN_TRAILING_SPACES)
+ rc = -rc;
+ else if (rc != VINF_SUCCESS)
+ {
+ unsigned cchMax = uBaseAndMaxLen >> 8;
+ if (!cchMax)
+ cchMax = ~0U;
+ else
+ cchMax -= (unsigned)(psz - pszValue);
+ if (cchMax > 0)
+ {
+ while (cchMax > 0 && (*psz == ' ' || *psz == '\t'))
+ psz++, cchMax--;
+ rc = cchMax > 0 && *psz ? VERR_TRAILING_CHARS : VERR_TRAILING_SPACES;
+ }
+ }
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToInt64Full);
+
+
+RTDECL(int64_t) RTStrToInt64(const char *pszValue)
+{
+ int64_t i64;
+ int rc = RTStrToInt64Ex(pszValue, NULL, 0, &i64);
+ if (RT_SUCCESS(rc))
+ return i64;
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTStrToInt64);
+
+
+RTDECL(int) RTStrToInt32Ex(const char *pszValue, char **ppszNext, unsigned uBaseAndMaxLen, int32_t *pi32)
+{
+ int64_t i64;
+ int rc = RTStrToInt64Ex(pszValue, ppszNext, uBaseAndMaxLen, &i64);
+ if (RT_SUCCESS(rc))
+ {
+ int32_t i32 = (int32_t)i64;
+ if (i64 != (int64_t)i32)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pi32)
+ *pi32 = (int32_t)i64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToInt32Ex);
+
+
+RTDECL(int) RTStrToInt32Full(const char *pszValue, unsigned uBaseAndMaxLen, int32_t *pi32)
+{
+ int64_t i64;
+ int rc = RTStrToInt64Full(pszValue, uBaseAndMaxLen, &i64);
+ if (RT_SUCCESS(rc))
+ {
+ int32_t i32 = (int32_t)i64;
+ if (i64 != (int64_t)i32)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pi32)
+ *pi32 = (int32_t)i64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToInt32Full);
+
+
+RTDECL(int32_t) RTStrToInt32(const char *pszValue)
+{
+ int32_t i32;
+ int rc = RTStrToInt32Ex(pszValue, NULL, 0, &i32);
+ if (RT_SUCCESS(rc))
+ return i32;
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTStrToInt32);
+
+
+RTDECL(int) RTStrToInt16Ex(const char *pszValue, char **ppszNext, unsigned uBaseAndMaxLen, int16_t *pi16)
+{
+ int64_t i64;
+ int rc = RTStrToInt64Ex(pszValue, ppszNext, uBaseAndMaxLen, &i64);
+ if (RT_SUCCESS(rc))
+ {
+ int16_t i16 = (int16_t)i64;
+ if (i64 != (int64_t)i16)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pi16)
+ *pi16 = (int16_t)i64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToInt16Ex);
+
+
+RTDECL(int) RTStrToInt16Full(const char *pszValue, unsigned uBaseAndMaxLen, int16_t *pi16)
+{
+ int64_t i64;
+ int rc = RTStrToInt64Full(pszValue, uBaseAndMaxLen, &i64);
+ if (RT_SUCCESS(rc))
+ {
+ int16_t i16 = (int16_t)i64;
+ if (i64 != (int64_t)i16)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pi16)
+ *pi16 = (int16_t)i64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToInt16Full);
+
+
+RTDECL(int16_t) RTStrToInt16(const char *pszValue)
+{
+ int16_t i16;
+ int rc = RTStrToInt16Ex(pszValue, NULL, 0, &i16);
+ if (RT_SUCCESS(rc))
+ return i16;
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTStrToInt16);
+
+
+RTDECL(int) RTStrToInt8Ex(const char *pszValue, char **ppszNext, unsigned uBaseAndMaxLen, int8_t *pi8)
+{
+ int64_t i64;
+ int rc = RTStrToInt64Ex(pszValue, ppszNext, uBaseAndMaxLen, &i64);
+ if (RT_SUCCESS(rc))
+ {
+ int8_t i8 = (int8_t)i64;
+ if (i64 != (int64_t)i8)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pi8)
+ *pi8 = (int8_t)i64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToInt8Ex);
+
+
+RTDECL(int) RTStrToInt8Full(const char *pszValue, unsigned uBaseAndMaxLen, int8_t *pi8)
+{
+ int64_t i64;
+ int rc = RTStrToInt64Full(pszValue, uBaseAndMaxLen, &i64);
+ if (RT_SUCCESS(rc))
+ {
+ int8_t i8 = (int8_t)i64;
+ if (i64 != (int64_t)i8)
+ rc = VWRN_NUMBER_TOO_BIG;
+ }
+ if (pi8)
+ *pi8 = (int8_t)i64;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToInt8Full);
+
+
+RTDECL(int8_t) RTStrToInt8(const char *pszValue)
+{
+ int8_t i8;
+ int rc = RTStrToInt8Ex(pszValue, NULL, 0, &i8);
+ if (RT_SUCCESS(rc))
+ return i8;
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTStrToInt8);
+
+
+RTDECL(int) RTStrConvertHexBytesEx(char const *pszHex, void *pv, size_t cb, uint32_t fFlags,
+ const char **ppszNext, size_t *pcbReturned)
+{
+ size_t cbDst = cb;
+ uint8_t *pbDst = (uint8_t *)pv;
+ const unsigned char *pszSrc = (const unsigned char *)pszHex;
+ unsigned char uchDigit;
+
+ if (pcbReturned)
+ *pcbReturned = 0;
+ if (ppszNext)
+ *ppszNext = NULL;
+ AssertPtrReturn(pszHex, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTSTRCONVERTHEXBYTES_F_SEP_COLON), VERR_INVALID_FLAGS);
+
+ if (fFlags & RTSTRCONVERTHEXBYTES_F_SEP_COLON)
+ {
+ /*
+ * Optional colon separators.
+ */
+ bool fPrevColon = true; /* leading colon is taken to mean leading zero byte */
+ for (;;)
+ {
+ /* Pick the next two digit from the string. */
+ uchDigit = g_auchDigits[*pszSrc++];
+ if (uchDigit >= 16)
+ {
+ if (uchDigit == 253 /* colon */)
+ {
+ Assert(pszSrc[-1] == ':');
+ if (!fPrevColon)
+ fPrevColon = true;
+ /* Add zero byte if there is room. */
+ else if (cbDst > 0)
+ {
+ cbDst--;
+ *pbDst++ = 0;
+ }
+ else
+ {
+ if (pcbReturned)
+ *pcbReturned = pbDst - (uint8_t *)pv;
+ if (ppszNext)
+ *ppszNext = (const char *)pszSrc - 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ continue;
+ }
+ else
+ break;
+ }
+ else
+ {
+ /* Got one digit, check what comes next: */
+ unsigned char const uchDigit2 = g_auchDigits[*pszSrc++];
+ if (uchDigit2 < 16)
+ {
+ if (cbDst > 0)
+ {
+ *pbDst++ = (uchDigit << 4) | uchDigit2;
+ cbDst--;
+ fPrevColon = false;
+ }
+ else
+ {
+ if (pcbReturned)
+ *pcbReturned = pbDst - (uint8_t *)pv;
+ if (ppszNext)
+ *ppszNext = (const char *)pszSrc - 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ /* Lone digits are only allowed if following a colon or at the very start, because
+ if there is more than one byte it ambigious whether it is the lead or tail byte
+ that only has one digit in it.
+ Note! This also ensures better compatibility with the no-separator variant
+ (except for single digit strings, which are accepted here but not below). */
+ else if (fPrevColon)
+ {
+ if (cbDst > 0)
+ {
+ *pbDst++ = uchDigit;
+ cbDst--;
+ }
+ else
+ {
+ if (pcbReturned)
+ *pcbReturned = pbDst - (uint8_t *)pv;
+ if (ppszNext)
+ *ppszNext = (const char *)pszSrc - 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ if (uchDigit2 == 253 /* colon */)
+ {
+ Assert(pszSrc[-1] == ':');
+ fPrevColon = true;
+ }
+ else
+ {
+ fPrevColon = false;
+ uchDigit = uchDigit2;
+ break;
+ }
+ }
+ else
+ {
+ if (pcbReturned)
+ *pcbReturned = pbDst - (uint8_t *)pv;
+ if (ppszNext)
+ *ppszNext = (const char *)pszSrc - 2;
+ return VERR_UNEVEN_INPUT;
+ }
+ }
+ }
+
+ /* Trailing colon means trailing zero byte: */
+ if (fPrevColon)
+ {
+ if (cbDst > 0)
+ {
+ *pbDst++ = 0;
+ cbDst--;
+ }
+ else
+ {
+ if (pcbReturned)
+ *pcbReturned = pbDst - (uint8_t *)pv;
+ if (ppszNext)
+ *ppszNext = (const char *)pszSrc - 1;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * No separators.
+ */
+ for (;;)
+ {
+ /* Pick the next two digit from the string. */
+ uchDigit = g_auchDigits[*pszSrc++];
+ if (uchDigit < 16)
+ {
+ unsigned char const uchDigit2 = g_auchDigits[*pszSrc++];
+ if (uchDigit2 < 16)
+ {
+ /* Add the byte to the output buffer. */
+ if (cbDst)
+ {
+ cbDst--;
+ *pbDst++ = (uchDigit << 4) | uchDigit2;
+ }
+ else
+ {
+ if (pcbReturned)
+ *pcbReturned = pbDst - (uint8_t *)pv;
+ if (ppszNext)
+ *ppszNext = (const char *)pszSrc - 2;
+ return VERR_BUFFER_OVERFLOW;
+ }
+ }
+ else
+ {
+ if (pcbReturned)
+ *pcbReturned = pbDst - (uint8_t *)pv;
+ if (ppszNext)
+ *ppszNext = (const char *)pszSrc - 2;
+ return VERR_UNEVEN_INPUT;
+ }
+ }
+ else
+ break;
+ }
+ }
+
+ /*
+ * End of hex bytes, look what comes next and figure out what to return.
+ */
+ if (pcbReturned)
+ *pcbReturned = pbDst - (uint8_t *)pv;
+ if (ppszNext)
+ *ppszNext = (const char *)pszSrc - 1;
+
+ if (uchDigit == 254)
+ {
+ Assert(pszSrc[-1] == '\0');
+ if (cbDst == 0)
+ return VINF_SUCCESS;
+ return pcbReturned ? VINF_BUFFER_UNDERFLOW : VERR_BUFFER_UNDERFLOW;
+ }
+ Assert(pszSrc[-1] != '\0');
+
+ if (cbDst != 0 && !pcbReturned)
+ return VERR_BUFFER_UNDERFLOW;
+
+ while (uchDigit == 252)
+ {
+ Assert(pszSrc[-1] == ' ' || pszSrc[-1] == '\t');
+ uchDigit = g_auchDigits[*pszSrc++];
+ }
+
+ Assert(pszSrc[-1] == '\0' ? uchDigit == 254 : uchDigit != 254);
+ return uchDigit == 254 ? VWRN_TRAILING_CHARS : VWRN_TRAILING_SPACES;
+
+}
+RT_EXPORT_SYMBOL(RTStrConvertHexBytesEx);
+
+
+RTDECL(int) RTStrConvertHexBytes(char const *pszHex, void *pv, size_t cb, uint32_t fFlags)
+{
+ return RTStrConvertHexBytesEx(pszHex, pv, cb, fFlags, NULL /*ppszNext*/, NULL /*pcbReturned*/);
+
+}
+RT_EXPORT_SYMBOL(RTStrConvertHexBytes);
+
diff --git a/src/VBox/Runtime/common/string/strversion.cpp b/src/VBox/Runtime/common/string/strversion.cpp
new file mode 100644
index 00000000..fcb0b34d
--- /dev/null
+++ b/src/VBox/Runtime/common/string/strversion.cpp
@@ -0,0 +1,235 @@
+/* $Id: strversion.cpp $ */
+/** @file
+ * IPRT - Version String Parsing.
+ */
+
+/*
+ * Copyright (C) 2009-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTSTRVER_IS_PUNCTUACTION(ch) \
+ ( (ch) == '_' || (ch) == '-' || (ch) == '+' || RT_C_IS_PUNCT(ch) )
+
+
+/**
+ * Parses a out the next block from a version string.
+ *
+ * @returns true if numeric, false if not.
+ * @param ppszVer The string cursor, IN/OUT.
+ * @param pi32Value Where to return the value if numeric.
+ * @param pcchBlock Where to return the block length.
+ */
+static bool rtStrVersionParseBlock(const char **ppszVer, int32_t *pi32Value, size_t *pcchBlock)
+{
+ const char *psz = *ppszVer;
+
+ /*
+ * Check for end-of-string.
+ */
+ if (!*psz)
+ {
+ *pi32Value = 0;
+ *pcchBlock = 0;
+ return false;
+ }
+
+ /*
+ * Try convert the block to a number the simple way.
+ */
+ char ch;
+ bool fNumeric = RT_C_IS_DIGIT(*psz);
+ if (fNumeric)
+ {
+ do
+ ch = *++psz;
+ while (ch && RT_C_IS_DIGIT(ch));
+
+ int rc = RTStrToInt32Ex(*ppszVer, NULL, 10, pi32Value);
+ if (RT_FAILURE(rc) || rc == VWRN_NUMBER_TOO_BIG)
+ {
+ AssertRC(rc);
+ fNumeric = false;
+ *pi32Value = 0;
+ }
+ }
+ else
+ {
+ /*
+ * Find the end of the current string. Make a special case for SVN
+ * revision numbers that immediately follows a release tag string.
+ */
+ do
+ ch = *++psz;
+ while ( ch
+ && !RT_C_IS_DIGIT(ch)
+ && !RTSTRVER_IS_PUNCTUACTION(ch));
+
+ size_t cchBlock = psz - *ppszVer;
+ if ( cchBlock > 1
+ && psz[-1] == 'r'
+ && RT_C_IS_DIGIT(*psz))
+ {
+ psz--;
+ cchBlock--;
+ }
+
+
+ /*
+ * Translate standard pre release terms to negative values.
+ */
+ static const struct
+ {
+ size_t cch;
+ const char *psz;
+ int32_t iValue;
+ } s_aTerms[] =
+ {
+ { 2, "RC", -100000 },
+ { 3, "PRE", -200000 },
+ { 5, "GAMMA", -300000 },
+ { 4, "BETA", -400000 },
+ { 5, "ALPHA", -500000 }
+ };
+
+ int32_t iVal1 = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aTerms); i++)
+ if ( cchBlock == s_aTerms[i].cch
+ && !RTStrNCmp(s_aTerms[i].psz, *ppszVer, cchBlock))
+ {
+ iVal1 = s_aTerms[i].iValue;
+ break;
+ }
+ if (iVal1 != 0)
+ {
+ /*
+ * Does the prelease term have a trailing number?
+ * Add it assuming BETA == BETA1.
+ */
+ if (RT_C_IS_DIGIT(*psz))
+ {
+ const char *psz2 = psz;
+ do
+ ch = *++psz;
+ while ( ch
+ && RT_C_IS_DIGIT(ch)
+ && !RTSTRVER_IS_PUNCTUACTION(ch));
+
+ int rc = RTStrToInt32Ex(psz2, NULL, 10, pi32Value);
+ if (RT_SUCCESS(rc) && rc != VWRN_NUMBER_TOO_BIG && *pi32Value)
+ iVal1 += *pi32Value - 1;
+ else
+ {
+ AssertRC(rc);
+ psz = psz2;
+ }
+ }
+ fNumeric = true;
+ }
+ *pi32Value = iVal1;
+ }
+ *pcchBlock = psz - *ppszVer;
+
+ /*
+ * Skip trailing punctuation.
+ */
+ if (RTSTRVER_IS_PUNCTUACTION(*psz))
+ psz++;
+ *ppszVer = psz;
+
+ return fNumeric;
+}
+
+
+RTDECL(int) RTStrVersionCompare(const char *pszVer1, const char *pszVer2)
+{
+ AssertPtr(pszVer1);
+ AssertPtr(pszVer2);
+
+ /*
+ * Do a parallel parse of the strings.
+ */
+ while (*pszVer1 || *pszVer2)
+ {
+ const char *pszBlock1 = pszVer1;
+ size_t cchBlock1;
+ int32_t iVal1;
+ bool fNumeric1 = rtStrVersionParseBlock(&pszVer1, &iVal1, &cchBlock1);
+
+ const char *pszBlock2 = pszVer2;
+ size_t cchBlock2;
+ int32_t iVal2;
+ bool fNumeric2 = rtStrVersionParseBlock(&pszVer2, &iVal2, &cchBlock2);
+
+ if (fNumeric1 && fNumeric2)
+ {
+ if (iVal1 != iVal2)
+ return iVal1 < iVal2 ? -1 : 1;
+ }
+ else if ( fNumeric1 != fNumeric2
+ && ( fNumeric1
+ ? iVal1 == 0 && cchBlock2 == 0
+ : iVal2 == 0 && cchBlock1 == 0)
+ )
+ {
+ /*else: 1.0 == 1.0.0.0.0. */;
+ }
+ else if ( fNumeric1 != fNumeric2
+ && (fNumeric1 ? iVal1 : iVal2) < 0)
+ {
+ /* Pre-release indicators are smaller than all other strings. */
+ return fNumeric1 ? -1 : 1;
+ }
+ else
+ {
+ int iDiff = RTStrNICmp(pszBlock1, pszBlock2, RT_MIN(cchBlock1, cchBlock2));
+ if (!iDiff && cchBlock1 != cchBlock2)
+ iDiff = cchBlock1 < cchBlock2 ? -1 : 1;
+ if (iDiff)
+ return iDiff < 0 ? -1 : 1;
+ }
+ }
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTStrVersionCompare);
diff --git a/src/VBox/Runtime/common/string/uni.cpp b/src/VBox/Runtime/common/string/uni.cpp
new file mode 100644
index 00000000..c95184ba
--- /dev/null
+++ b/src/VBox/Runtime/common/string/uni.cpp
@@ -0,0 +1,53 @@
+/* $Id: uni.cpp $ */
+/** @file
+ * IPRT - Unicode.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/uni.h>
+#include "internal/iprt.h"
+
+#include <iprt/alloc.h>
+
+
+RTDECL(void) RTUniFree(PRTUNICP pusz)
+{
+ if (pusz)
+ RTMemFree(pusz);
+}
+RT_EXPORT_SYMBOL(RTUniFree);
+
diff --git a/src/VBox/Runtime/common/string/unidata-flags.cpp b/src/VBox/Runtime/common/string/unidata-flags.cpp
new file mode 100644
index 00000000..fdeaa21e
--- /dev/null
+++ b/src/VBox/Runtime/common/string/unidata-flags.cpp
@@ -0,0 +1,47017 @@
+/* $Id: unidata-flags.cpp $ */
+/** @file
+ * IPRT - Unicode Tables.
+ *
+ * Automatically Generated from d:\src\unicode\6.3.0\
+ * by ..\..\..\..\..\out\win.amd64\debug\obj\uniread\uniread.exe (May 22 2014 19:07:40)
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <iprt/uni.h>
+
+static const uint8_t g_afRTUniFlags0x000000[] =
+{
+ 0, /* U+000000: <control>*/
+ 0, /* U+000001: <control>*/
+ 0, /* U+000002: <control>*/
+ 0, /* U+000003: <control>*/
+ 0, /* U+000004: <control>*/
+ 0, /* U+000005: <control>*/
+ 0, /* U+000006: <control>*/
+ 0, /* U+000007: <control>*/
+ 0, /* U+000008: <control>*/
+ RTUNI_WSPACE, /* U+000009: <control>*/
+ RTUNI_WSPACE, /* U+00000a: <control>*/
+ RTUNI_WSPACE, /* U+00000b: <control>*/
+ RTUNI_WSPACE, /* U+00000c: <control>*/
+ RTUNI_WSPACE, /* U+00000d: <control>*/
+ 0, /* U+00000e: <control>*/
+ 0, /* U+00000f: <control>*/
+ 0, /* U+000010: <control>*/
+ 0, /* U+000011: <control>*/
+ 0, /* U+000012: <control>*/
+ 0, /* U+000013: <control>*/
+ 0, /* U+000014: <control>*/
+ 0, /* U+000015: <control>*/
+ 0, /* U+000016: <control>*/
+ 0, /* U+000017: <control>*/
+ 0, /* U+000018: <control>*/
+ 0, /* U+000019: <control>*/
+ 0, /* U+00001a: <control>*/
+ 0, /* U+00001b: <control>*/
+ 0, /* U+00001c: <control>*/
+ 0, /* U+00001d: <control>*/
+ 0, /* U+00001e: <control>*/
+ 0, /* U+00001f: <control>*/
+ RTUNI_WSPACE, /* U+000020: SPACE*/
+ 0, /* U+000021: EXCLAMATION MARK*/
+ 0, /* U+000022: QUOTATION MARK*/
+ 0, /* U+000023: NUMBER SIGN*/
+ 0, /* U+000024: DOLLAR SIGN*/
+ 0, /* U+000025: PERCENT SIGN*/
+ 0, /* U+000026: AMPERSAND*/
+ 0, /* U+000027: APOSTROPHE*/
+ 0, /* U+000028: LEFT PARENTHESIS*/
+ 0, /* U+000029: RIGHT PARENTHESIS*/
+ 0, /* U+00002a: ASTERISK*/
+ 0, /* U+00002b: PLUS SIGN*/
+ 0, /* U+00002c: COMMA*/
+ 0, /* U+00002d: HYPHEN-MINUS*/
+ 0, /* U+00002e: FULL STOP*/
+ 0, /* U+00002f: SOLIDUS*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000030: DIGIT ZERO*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000031: DIGIT ONE*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000032: DIGIT TWO*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000033: DIGIT THREE*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000034: DIGIT FOUR*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000035: DIGIT FIVE*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000036: DIGIT SIX*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000037: DIGIT SEVEN*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000038: DIGIT EIGHT*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+000039: DIGIT NINE*/
+ 0, /* U+00003a: COLON*/
+ 0, /* U+00003b: SEMICOLON*/
+ 0, /* U+00003c: LESS-THAN SIGN*/
+ 0, /* U+00003d: EQUALS SIGN*/
+ 0, /* U+00003e: GREATER-THAN SIGN*/
+ 0, /* U+00003f: QUESTION MARK*/
+ 0, /* U+000040: COMMERCIAL AT*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+000041: LATIN CAPITAL LETTER A*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+000042: LATIN CAPITAL LETTER B*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+000043: LATIN CAPITAL LETTER C*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+000044: LATIN CAPITAL LETTER D*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+000045: LATIN CAPITAL LETTER E*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+000046: LATIN CAPITAL LETTER F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000047: LATIN CAPITAL LETTER G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000048: LATIN CAPITAL LETTER H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000049: LATIN CAPITAL LETTER I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00004a: LATIN CAPITAL LETTER J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00004b: LATIN CAPITAL LETTER K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00004c: LATIN CAPITAL LETTER L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00004d: LATIN CAPITAL LETTER M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00004e: LATIN CAPITAL LETTER N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00004f: LATIN CAPITAL LETTER O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000050: LATIN CAPITAL LETTER P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000051: LATIN CAPITAL LETTER Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000052: LATIN CAPITAL LETTER R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000053: LATIN CAPITAL LETTER S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000054: LATIN CAPITAL LETTER T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000055: LATIN CAPITAL LETTER U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000056: LATIN CAPITAL LETTER V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000057: LATIN CAPITAL LETTER W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000058: LATIN CAPITAL LETTER X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000059: LATIN CAPITAL LETTER Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00005a: LATIN CAPITAL LETTER Z*/
+ 0, /* U+00005b: LEFT SQUARE BRACKET*/
+ 0, /* U+00005c: REVERSE SOLIDUS*/
+ 0, /* U+00005d: RIGHT SQUARE BRACKET*/
+ 0, /* U+00005e: CIRCUMFLEX ACCENT*/
+ 0, /* U+00005f: LOW LINE*/
+ 0, /* U+000060: GRAVE ACCENT*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+000061: LATIN SMALL LETTER A*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+000062: LATIN SMALL LETTER B*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+000063: LATIN SMALL LETTER C*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+000064: LATIN SMALL LETTER D*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+000065: LATIN SMALL LETTER E*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+000066: LATIN SMALL LETTER F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000067: LATIN SMALL LETTER G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000068: LATIN SMALL LETTER H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000069: LATIN SMALL LETTER I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00006a: LATIN SMALL LETTER J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00006b: LATIN SMALL LETTER K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00006c: LATIN SMALL LETTER L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00006d: LATIN SMALL LETTER M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00006e: LATIN SMALL LETTER N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00006f: LATIN SMALL LETTER O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000070: LATIN SMALL LETTER P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000071: LATIN SMALL LETTER Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000072: LATIN SMALL LETTER R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000073: LATIN SMALL LETTER S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000074: LATIN SMALL LETTER T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000075: LATIN SMALL LETTER U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000076: LATIN SMALL LETTER V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000077: LATIN SMALL LETTER W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000078: LATIN SMALL LETTER X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000079: LATIN SMALL LETTER Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00007a: LATIN SMALL LETTER Z*/
+ 0, /* U+00007b: LEFT CURLY BRACKET*/
+ 0, /* U+00007c: VERTICAL LINE*/
+ 0, /* U+00007d: RIGHT CURLY BRACKET*/
+ 0, /* U+00007e: TILDE*/
+ 0, /* U+00007f: <control>*/
+ 0, /* U+000080: <control>*/
+ 0, /* U+000081: <control>*/
+ 0, /* U+000082: <control>*/
+ 0, /* U+000083: <control>*/
+ 0, /* U+000084: <control>*/
+ RTUNI_WSPACE, /* U+000085: <control>*/
+ 0, /* U+000086: <control>*/
+ 0, /* U+000087: <control>*/
+ 0, /* U+000088: <control>*/
+ 0, /* U+000089: <control>*/
+ 0, /* U+00008a: <control>*/
+ 0, /* U+00008b: <control>*/
+ 0, /* U+00008c: <control>*/
+ 0, /* U+00008d: <control>*/
+ 0, /* U+00008e: <control>*/
+ 0, /* U+00008f: <control>*/
+ 0, /* U+000090: <control>*/
+ 0, /* U+000091: <control>*/
+ 0, /* U+000092: <control>*/
+ 0, /* U+000093: <control>*/
+ 0, /* U+000094: <control>*/
+ 0, /* U+000095: <control>*/
+ 0, /* U+000096: <control>*/
+ 0, /* U+000097: <control>*/
+ 0, /* U+000098: <control>*/
+ 0, /* U+000099: <control>*/
+ 0, /* U+00009a: <control>*/
+ 0, /* U+00009b: <control>*/
+ 0, /* U+00009c: <control>*/
+ 0, /* U+00009d: <control>*/
+ 0, /* U+00009e: <control>*/
+ 0, /* U+00009f: <control>*/
+ RTUNI_WSPACE, /* U+0000a0: NO-BREAK SPACE*/
+ 0, /* U+0000a1: INVERTED EXCLAMATION MARK*/
+ 0, /* U+0000a2: CENT SIGN*/
+ 0, /* U+0000a3: POUND SIGN*/
+ 0, /* U+0000a4: CURRENCY SIGN*/
+ 0, /* U+0000a5: YEN SIGN*/
+ 0, /* U+0000a6: BROKEN BAR*/
+ 0, /* U+0000a7: SECTION SIGN*/
+ 0, /* U+0000a8: DIAERESIS*/
+ 0, /* U+0000a9: COPYRIGHT SIGN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000aa: FEMININE ORDINAL INDICATOR*/
+ 0, /* U+0000ab: LEFT-POINTING DOUBLE ANGLE QUOTATION MARK*/
+ 0, /* U+0000ac: NOT SIGN*/
+ 0, /* U+0000ad: SOFT HYPHEN*/
+ 0, /* U+0000ae: REGISTERED SIGN*/
+ 0, /* U+0000af: MACRON*/
+ 0, /* U+0000b0: DEGREE SIGN*/
+ 0, /* U+0000b1: PLUS-MINUS SIGN*/
+ 0, /* U+0000b2: SUPERSCRIPT TWO*/
+ 0, /* U+0000b3: SUPERSCRIPT THREE*/
+ 0, /* U+0000b4: ACUTE ACCENT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000b5: MICRO SIGN*/
+ 0, /* U+0000b6: PILCROW SIGN*/
+ 0, /* U+0000b7: MIDDLE DOT*/
+ 0, /* U+0000b8: CEDILLA*/
+ 0, /* U+0000b9: SUPERSCRIPT ONE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000ba: MASCULINE ORDINAL INDICATOR*/
+ 0, /* U+0000bb: RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK*/
+ 0, /* U+0000bc: VULGAR FRACTION ONE QUARTER*/
+ 0, /* U+0000bd: VULGAR FRACTION ONE HALF*/
+ 0, /* U+0000be: VULGAR FRACTION THREE QUARTERS*/
+ 0, /* U+0000bf: INVERTED QUESTION MARK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c0: LATIN CAPITAL LETTER A WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c1: LATIN CAPITAL LETTER A WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c2: LATIN CAPITAL LETTER A WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c3: LATIN CAPITAL LETTER A WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c4: LATIN CAPITAL LETTER A WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c5: LATIN CAPITAL LETTER A WITH RING ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c6: LATIN CAPITAL LETTER AE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c7: LATIN CAPITAL LETTER C WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c8: LATIN CAPITAL LETTER E WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000c9: LATIN CAPITAL LETTER E WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000ca: LATIN CAPITAL LETTER E WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000cb: LATIN CAPITAL LETTER E WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000cc: LATIN CAPITAL LETTER I WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000cd: LATIN CAPITAL LETTER I WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000ce: LATIN CAPITAL LETTER I WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000cf: LATIN CAPITAL LETTER I WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000d0: LATIN CAPITAL LETTER ETH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000d1: LATIN CAPITAL LETTER N WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000d2: LATIN CAPITAL LETTER O WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000d3: LATIN CAPITAL LETTER O WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000d4: LATIN CAPITAL LETTER O WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000d5: LATIN CAPITAL LETTER O WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000d6: LATIN CAPITAL LETTER O WITH DIAERESIS*/
+ 0, /* U+0000d7: MULTIPLICATION SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000d8: LATIN CAPITAL LETTER O WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000d9: LATIN CAPITAL LETTER U WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000da: LATIN CAPITAL LETTER U WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000db: LATIN CAPITAL LETTER U WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000dc: LATIN CAPITAL LETTER U WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000dd: LATIN CAPITAL LETTER Y WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0000de: LATIN CAPITAL LETTER THORN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000df: LATIN SMALL LETTER SHARP S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e0: LATIN SMALL LETTER A WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e1: LATIN SMALL LETTER A WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e2: LATIN SMALL LETTER A WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e3: LATIN SMALL LETTER A WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e4: LATIN SMALL LETTER A WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e5: LATIN SMALL LETTER A WITH RING ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e6: LATIN SMALL LETTER AE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e7: LATIN SMALL LETTER C WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e8: LATIN SMALL LETTER E WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000e9: LATIN SMALL LETTER E WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000ea: LATIN SMALL LETTER E WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000eb: LATIN SMALL LETTER E WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000ec: LATIN SMALL LETTER I WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000ed: LATIN SMALL LETTER I WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000ee: LATIN SMALL LETTER I WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000ef: LATIN SMALL LETTER I WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000f0: LATIN SMALL LETTER ETH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000f1: LATIN SMALL LETTER N WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000f2: LATIN SMALL LETTER O WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000f3: LATIN SMALL LETTER O WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000f4: LATIN SMALL LETTER O WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000f5: LATIN SMALL LETTER O WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000f6: LATIN SMALL LETTER O WITH DIAERESIS*/
+ 0, /* U+0000f7: DIVISION SIGN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000f8: LATIN SMALL LETTER O WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000f9: LATIN SMALL LETTER U WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000fa: LATIN SMALL LETTER U WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000fb: LATIN SMALL LETTER U WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000fc: LATIN SMALL LETTER U WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000fd: LATIN SMALL LETTER Y WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000fe: LATIN SMALL LETTER THORN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0000ff: LATIN SMALL LETTER Y WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000100: LATIN CAPITAL LETTER A WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000101: LATIN SMALL LETTER A WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000102: LATIN CAPITAL LETTER A WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000103: LATIN SMALL LETTER A WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000104: LATIN CAPITAL LETTER A WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000105: LATIN SMALL LETTER A WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000106: LATIN CAPITAL LETTER C WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000107: LATIN SMALL LETTER C WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000108: LATIN CAPITAL LETTER C WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000109: LATIN SMALL LETTER C WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00010a: LATIN CAPITAL LETTER C WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00010b: LATIN SMALL LETTER C WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00010c: LATIN CAPITAL LETTER C WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00010d: LATIN SMALL LETTER C WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00010e: LATIN CAPITAL LETTER D WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00010f: LATIN SMALL LETTER D WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000110: LATIN CAPITAL LETTER D WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000111: LATIN SMALL LETTER D WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000112: LATIN CAPITAL LETTER E WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000113: LATIN SMALL LETTER E WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000114: LATIN CAPITAL LETTER E WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000115: LATIN SMALL LETTER E WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000116: LATIN CAPITAL LETTER E WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000117: LATIN SMALL LETTER E WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000118: LATIN CAPITAL LETTER E WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000119: LATIN SMALL LETTER E WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00011a: LATIN CAPITAL LETTER E WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00011b: LATIN SMALL LETTER E WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00011c: LATIN CAPITAL LETTER G WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00011d: LATIN SMALL LETTER G WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00011e: LATIN CAPITAL LETTER G WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00011f: LATIN SMALL LETTER G WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000120: LATIN CAPITAL LETTER G WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000121: LATIN SMALL LETTER G WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000122: LATIN CAPITAL LETTER G WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000123: LATIN SMALL LETTER G WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000124: LATIN CAPITAL LETTER H WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000125: LATIN SMALL LETTER H WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000126: LATIN CAPITAL LETTER H WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000127: LATIN SMALL LETTER H WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000128: LATIN CAPITAL LETTER I WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000129: LATIN SMALL LETTER I WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00012a: LATIN CAPITAL LETTER I WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00012b: LATIN SMALL LETTER I WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00012c: LATIN CAPITAL LETTER I WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00012d: LATIN SMALL LETTER I WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00012e: LATIN CAPITAL LETTER I WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00012f: LATIN SMALL LETTER I WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000130: LATIN CAPITAL LETTER I WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000131: LATIN SMALL LETTER DOTLESS I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000132: LATIN CAPITAL LIGATURE IJ*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000133: LATIN SMALL LIGATURE IJ*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000134: LATIN CAPITAL LETTER J WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000135: LATIN SMALL LETTER J WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000136: LATIN CAPITAL LETTER K WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000137: LATIN SMALL LETTER K WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000138: LATIN SMALL LETTER KRA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000139: LATIN CAPITAL LETTER L WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00013a: LATIN SMALL LETTER L WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00013b: LATIN CAPITAL LETTER L WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00013c: LATIN SMALL LETTER L WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00013d: LATIN CAPITAL LETTER L WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00013e: LATIN SMALL LETTER L WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00013f: LATIN CAPITAL LETTER L WITH MIDDLE DOT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000140: LATIN SMALL LETTER L WITH MIDDLE DOT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000141: LATIN CAPITAL LETTER L WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000142: LATIN SMALL LETTER L WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000143: LATIN CAPITAL LETTER N WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000144: LATIN SMALL LETTER N WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000145: LATIN CAPITAL LETTER N WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000146: LATIN SMALL LETTER N WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000147: LATIN CAPITAL LETTER N WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000148: LATIN SMALL LETTER N WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000149: LATIN SMALL LETTER N PRECEDED BY APOSTROPHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00014a: LATIN CAPITAL LETTER ENG*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00014b: LATIN SMALL LETTER ENG*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00014c: LATIN CAPITAL LETTER O WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00014d: LATIN SMALL LETTER O WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00014e: LATIN CAPITAL LETTER O WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00014f: LATIN SMALL LETTER O WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000150: LATIN CAPITAL LETTER O WITH DOUBLE ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000151: LATIN SMALL LETTER O WITH DOUBLE ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000152: LATIN CAPITAL LIGATURE OE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000153: LATIN SMALL LIGATURE OE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000154: LATIN CAPITAL LETTER R WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000155: LATIN SMALL LETTER R WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000156: LATIN CAPITAL LETTER R WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000157: LATIN SMALL LETTER R WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000158: LATIN CAPITAL LETTER R WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000159: LATIN SMALL LETTER R WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00015a: LATIN CAPITAL LETTER S WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00015b: LATIN SMALL LETTER S WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00015c: LATIN CAPITAL LETTER S WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00015d: LATIN SMALL LETTER S WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00015e: LATIN CAPITAL LETTER S WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00015f: LATIN SMALL LETTER S WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000160: LATIN CAPITAL LETTER S WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000161: LATIN SMALL LETTER S WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000162: LATIN CAPITAL LETTER T WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000163: LATIN SMALL LETTER T WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000164: LATIN CAPITAL LETTER T WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000165: LATIN SMALL LETTER T WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000166: LATIN CAPITAL LETTER T WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000167: LATIN SMALL LETTER T WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000168: LATIN CAPITAL LETTER U WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000169: LATIN SMALL LETTER U WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00016a: LATIN CAPITAL LETTER U WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00016b: LATIN SMALL LETTER U WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00016c: LATIN CAPITAL LETTER U WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00016d: LATIN SMALL LETTER U WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00016e: LATIN CAPITAL LETTER U WITH RING ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00016f: LATIN SMALL LETTER U WITH RING ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000170: LATIN CAPITAL LETTER U WITH DOUBLE ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000171: LATIN SMALL LETTER U WITH DOUBLE ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000172: LATIN CAPITAL LETTER U WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000173: LATIN SMALL LETTER U WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000174: LATIN CAPITAL LETTER W WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000175: LATIN SMALL LETTER W WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000176: LATIN CAPITAL LETTER Y WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000177: LATIN SMALL LETTER Y WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000178: LATIN CAPITAL LETTER Y WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000179: LATIN CAPITAL LETTER Z WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00017a: LATIN SMALL LETTER Z WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00017b: LATIN CAPITAL LETTER Z WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00017c: LATIN SMALL LETTER Z WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00017d: LATIN CAPITAL LETTER Z WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00017e: LATIN SMALL LETTER Z WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00017f: LATIN SMALL LETTER LONG S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000180: LATIN SMALL LETTER B WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000181: LATIN CAPITAL LETTER B WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000182: LATIN CAPITAL LETTER B WITH TOPBAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000183: LATIN SMALL LETTER B WITH TOPBAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000184: LATIN CAPITAL LETTER TONE SIX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000185: LATIN SMALL LETTER TONE SIX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000186: LATIN CAPITAL LETTER OPEN O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000187: LATIN CAPITAL LETTER C WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000188: LATIN SMALL LETTER C WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000189: LATIN CAPITAL LETTER AFRICAN D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00018a: LATIN CAPITAL LETTER D WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00018b: LATIN CAPITAL LETTER D WITH TOPBAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00018c: LATIN SMALL LETTER D WITH TOPBAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00018d: LATIN SMALL LETTER TURNED DELTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00018e: LATIN CAPITAL LETTER REVERSED E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00018f: LATIN CAPITAL LETTER SCHWA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000190: LATIN CAPITAL LETTER OPEN E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000191: LATIN CAPITAL LETTER F WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000192: LATIN SMALL LETTER F WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000193: LATIN CAPITAL LETTER G WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000194: LATIN CAPITAL LETTER GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000195: LATIN SMALL LETTER HV*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000196: LATIN CAPITAL LETTER IOTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000197: LATIN CAPITAL LETTER I WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000198: LATIN CAPITAL LETTER K WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000199: LATIN SMALL LETTER K WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00019a: LATIN SMALL LETTER L WITH BAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00019b: LATIN SMALL LETTER LAMBDA WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00019c: LATIN CAPITAL LETTER TURNED M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00019d: LATIN CAPITAL LETTER N WITH LEFT HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00019e: LATIN SMALL LETTER N WITH LONG RIGHT LEG*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00019f: LATIN CAPITAL LETTER O WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001a0: LATIN CAPITAL LETTER O WITH HORN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001a1: LATIN SMALL LETTER O WITH HORN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001a2: LATIN CAPITAL LETTER OI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001a3: LATIN SMALL LETTER OI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001a4: LATIN CAPITAL LETTER P WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001a5: LATIN SMALL LETTER P WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001a6: LATIN LETTER YR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001a7: LATIN CAPITAL LETTER TONE TWO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001a8: LATIN SMALL LETTER TONE TWO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001a9: LATIN CAPITAL LETTER ESH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001aa: LATIN LETTER REVERSED ESH LOOP*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001ab: LATIN SMALL LETTER T WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001ac: LATIN CAPITAL LETTER T WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001ad: LATIN SMALL LETTER T WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001ae: LATIN CAPITAL LETTER T WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001af: LATIN CAPITAL LETTER U WITH HORN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001b0: LATIN SMALL LETTER U WITH HORN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001b1: LATIN CAPITAL LETTER UPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001b2: LATIN CAPITAL LETTER V WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001b3: LATIN CAPITAL LETTER Y WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001b4: LATIN SMALL LETTER Y WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001b5: LATIN CAPITAL LETTER Z WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001b6: LATIN SMALL LETTER Z WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001b7: LATIN CAPITAL LETTER EZH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001b8: LATIN CAPITAL LETTER EZH REVERSED*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001b9: LATIN SMALL LETTER EZH REVERSED*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001ba: LATIN SMALL LETTER EZH WITH TAIL*/
+ RTUNI_ALPHA, /* U+0001bb: LATIN LETTER TWO WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001bc: LATIN CAPITAL LETTER TONE FIVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001bd: LATIN SMALL LETTER TONE FIVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001be: LATIN LETTER INVERTED GLOTTAL STOP WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001bf: LATIN LETTER WYNN*/
+ RTUNI_ALPHA, /* U+0001c0: LATIN LETTER DENTAL CLICK*/
+ RTUNI_ALPHA, /* U+0001c1: LATIN LETTER LATERAL CLICK*/
+ RTUNI_ALPHA, /* U+0001c2: LATIN LETTER ALVEOLAR CLICK*/
+ RTUNI_ALPHA, /* U+0001c3: LATIN LETTER RETROFLEX CLICK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001c4: LATIN CAPITAL LETTER DZ WITH CARON*/
+ RTUNI_ALPHA, /* U+0001c5: LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001c6: LATIN SMALL LETTER DZ WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001c7: LATIN CAPITAL LETTER LJ*/
+ RTUNI_ALPHA, /* U+0001c8: LATIN CAPITAL LETTER L WITH SMALL LETTER J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001c9: LATIN SMALL LETTER LJ*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001ca: LATIN CAPITAL LETTER NJ*/
+ RTUNI_ALPHA, /* U+0001cb: LATIN CAPITAL LETTER N WITH SMALL LETTER J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001cc: LATIN SMALL LETTER NJ*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001cd: LATIN CAPITAL LETTER A WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001ce: LATIN SMALL LETTER A WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001cf: LATIN CAPITAL LETTER I WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001d0: LATIN SMALL LETTER I WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001d1: LATIN CAPITAL LETTER O WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001d2: LATIN SMALL LETTER O WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001d3: LATIN CAPITAL LETTER U WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001d4: LATIN SMALL LETTER U WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001d5: LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001d6: LATIN SMALL LETTER U WITH DIAERESIS AND MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001d7: LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001d8: LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001d9: LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001da: LATIN SMALL LETTER U WITH DIAERESIS AND CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001db: LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001dc: LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001dd: LATIN SMALL LETTER TURNED E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001de: LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001df: LATIN SMALL LETTER A WITH DIAERESIS AND MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001e0: LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001e1: LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001e2: LATIN CAPITAL LETTER AE WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001e3: LATIN SMALL LETTER AE WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001e4: LATIN CAPITAL LETTER G WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001e5: LATIN SMALL LETTER G WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001e6: LATIN CAPITAL LETTER G WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001e7: LATIN SMALL LETTER G WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001e8: LATIN CAPITAL LETTER K WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001e9: LATIN SMALL LETTER K WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001ea: LATIN CAPITAL LETTER O WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001eb: LATIN SMALL LETTER O WITH OGONEK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001ec: LATIN CAPITAL LETTER O WITH OGONEK AND MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001ed: LATIN SMALL LETTER O WITH OGONEK AND MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001ee: LATIN CAPITAL LETTER EZH WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001ef: LATIN SMALL LETTER EZH WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001f0: LATIN SMALL LETTER J WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001f1: LATIN CAPITAL LETTER DZ*/
+ RTUNI_ALPHA, /* U+0001f2: LATIN CAPITAL LETTER D WITH SMALL LETTER Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001f3: LATIN SMALL LETTER DZ*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001f4: LATIN CAPITAL LETTER G WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001f5: LATIN SMALL LETTER G WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001f6: LATIN CAPITAL LETTER HWAIR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001f7: LATIN CAPITAL LETTER WYNN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001f8: LATIN CAPITAL LETTER N WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001f9: LATIN SMALL LETTER N WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001fa: LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001fb: LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001fc: LATIN CAPITAL LETTER AE WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001fd: LATIN SMALL LETTER AE WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0001fe: LATIN CAPITAL LETTER O WITH STROKE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0001ff: LATIN SMALL LETTER O WITH STROKE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000200: LATIN CAPITAL LETTER A WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000201: LATIN SMALL LETTER A WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000202: LATIN CAPITAL LETTER A WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000203: LATIN SMALL LETTER A WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000204: LATIN CAPITAL LETTER E WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000205: LATIN SMALL LETTER E WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000206: LATIN CAPITAL LETTER E WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000207: LATIN SMALL LETTER E WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000208: LATIN CAPITAL LETTER I WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000209: LATIN SMALL LETTER I WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00020a: LATIN CAPITAL LETTER I WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00020b: LATIN SMALL LETTER I WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00020c: LATIN CAPITAL LETTER O WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00020d: LATIN SMALL LETTER O WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00020e: LATIN CAPITAL LETTER O WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00020f: LATIN SMALL LETTER O WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000210: LATIN CAPITAL LETTER R WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000211: LATIN SMALL LETTER R WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000212: LATIN CAPITAL LETTER R WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000213: LATIN SMALL LETTER R WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000214: LATIN CAPITAL LETTER U WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000215: LATIN SMALL LETTER U WITH DOUBLE GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000216: LATIN CAPITAL LETTER U WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000217: LATIN SMALL LETTER U WITH INVERTED BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000218: LATIN CAPITAL LETTER S WITH COMMA BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000219: LATIN SMALL LETTER S WITH COMMA BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00021a: LATIN CAPITAL LETTER T WITH COMMA BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00021b: LATIN SMALL LETTER T WITH COMMA BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00021c: LATIN CAPITAL LETTER YOGH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00021d: LATIN SMALL LETTER YOGH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00021e: LATIN CAPITAL LETTER H WITH CARON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00021f: LATIN SMALL LETTER H WITH CARON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000220: LATIN CAPITAL LETTER N WITH LONG RIGHT LEG*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000221: LATIN SMALL LETTER D WITH CURL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000222: LATIN CAPITAL LETTER OU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000223: LATIN SMALL LETTER OU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000224: LATIN CAPITAL LETTER Z WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000225: LATIN SMALL LETTER Z WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000226: LATIN CAPITAL LETTER A WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000227: LATIN SMALL LETTER A WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000228: LATIN CAPITAL LETTER E WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000229: LATIN SMALL LETTER E WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00022a: LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00022b: LATIN SMALL LETTER O WITH DIAERESIS AND MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00022c: LATIN CAPITAL LETTER O WITH TILDE AND MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00022d: LATIN SMALL LETTER O WITH TILDE AND MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00022e: LATIN CAPITAL LETTER O WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00022f: LATIN SMALL LETTER O WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000230: LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000231: LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000232: LATIN CAPITAL LETTER Y WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000233: LATIN SMALL LETTER Y WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000234: LATIN SMALL LETTER L WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000235: LATIN SMALL LETTER N WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000236: LATIN SMALL LETTER T WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000237: LATIN SMALL LETTER DOTLESS J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000238: LATIN SMALL LETTER DB DIGRAPH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000239: LATIN SMALL LETTER QP DIGRAPH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00023a: LATIN CAPITAL LETTER A WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00023b: LATIN CAPITAL LETTER C WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00023c: LATIN SMALL LETTER C WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00023d: LATIN CAPITAL LETTER L WITH BAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00023e: LATIN CAPITAL LETTER T WITH DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00023f: LATIN SMALL LETTER S WITH SWASH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000240: LATIN SMALL LETTER Z WITH SWASH TAIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000241: LATIN CAPITAL LETTER GLOTTAL STOP*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000242: LATIN SMALL LETTER GLOTTAL STOP*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000243: LATIN CAPITAL LETTER B WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000244: LATIN CAPITAL LETTER U BAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000245: LATIN CAPITAL LETTER TURNED V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000246: LATIN CAPITAL LETTER E WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000247: LATIN SMALL LETTER E WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000248: LATIN CAPITAL LETTER J WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000249: LATIN SMALL LETTER J WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00024a: LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00024b: LATIN SMALL LETTER Q WITH HOOK TAIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00024c: LATIN CAPITAL LETTER R WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00024d: LATIN SMALL LETTER R WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00024e: LATIN CAPITAL LETTER Y WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00024f: LATIN SMALL LETTER Y WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000250: LATIN SMALL LETTER TURNED A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000251: LATIN SMALL LETTER ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000252: LATIN SMALL LETTER TURNED ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000253: LATIN SMALL LETTER B WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000254: LATIN SMALL LETTER OPEN O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000255: LATIN SMALL LETTER C WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000256: LATIN SMALL LETTER D WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000257: LATIN SMALL LETTER D WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000258: LATIN SMALL LETTER REVERSED E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000259: LATIN SMALL LETTER SCHWA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00025a: LATIN SMALL LETTER SCHWA WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00025b: LATIN SMALL LETTER OPEN E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00025c: LATIN SMALL LETTER REVERSED OPEN E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00025d: LATIN SMALL LETTER REVERSED OPEN E WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00025e: LATIN SMALL LETTER CLOSED REVERSED OPEN E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00025f: LATIN SMALL LETTER DOTLESS J WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000260: LATIN SMALL LETTER G WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000261: LATIN SMALL LETTER SCRIPT G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000262: LATIN LETTER SMALL CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000263: LATIN SMALL LETTER GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000264: LATIN SMALL LETTER RAMS HORN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000265: LATIN SMALL LETTER TURNED H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000266: LATIN SMALL LETTER H WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000267: LATIN SMALL LETTER HENG WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000268: LATIN SMALL LETTER I WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000269: LATIN SMALL LETTER IOTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00026a: LATIN LETTER SMALL CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00026b: LATIN SMALL LETTER L WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00026c: LATIN SMALL LETTER L WITH BELT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00026d: LATIN SMALL LETTER L WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00026e: LATIN SMALL LETTER LEZH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00026f: LATIN SMALL LETTER TURNED M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000270: LATIN SMALL LETTER TURNED M WITH LONG LEG*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000271: LATIN SMALL LETTER M WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000272: LATIN SMALL LETTER N WITH LEFT HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000273: LATIN SMALL LETTER N WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000274: LATIN LETTER SMALL CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000275: LATIN SMALL LETTER BARRED O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000276: LATIN LETTER SMALL CAPITAL OE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000277: LATIN SMALL LETTER CLOSED OMEGA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000278: LATIN SMALL LETTER PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000279: LATIN SMALL LETTER TURNED R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00027a: LATIN SMALL LETTER TURNED R WITH LONG LEG*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00027b: LATIN SMALL LETTER TURNED R WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00027c: LATIN SMALL LETTER R WITH LONG LEG*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00027d: LATIN SMALL LETTER R WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00027e: LATIN SMALL LETTER R WITH FISHHOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00027f: LATIN SMALL LETTER REVERSED R WITH FISHHOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000280: LATIN LETTER SMALL CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000281: LATIN LETTER SMALL CAPITAL INVERTED R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000282: LATIN SMALL LETTER S WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000283: LATIN SMALL LETTER ESH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000284: LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000285: LATIN SMALL LETTER SQUAT REVERSED ESH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000286: LATIN SMALL LETTER ESH WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000287: LATIN SMALL LETTER TURNED T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000288: LATIN SMALL LETTER T WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000289: LATIN SMALL LETTER U BAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00028a: LATIN SMALL LETTER UPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00028b: LATIN SMALL LETTER V WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00028c: LATIN SMALL LETTER TURNED V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00028d: LATIN SMALL LETTER TURNED W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00028e: LATIN SMALL LETTER TURNED Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00028f: LATIN LETTER SMALL CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000290: LATIN SMALL LETTER Z WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000291: LATIN SMALL LETTER Z WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000292: LATIN SMALL LETTER EZH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000293: LATIN SMALL LETTER EZH WITH CURL*/
+ RTUNI_ALPHA, /* U+000294: LATIN LETTER GLOTTAL STOP*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000295: LATIN LETTER PHARYNGEAL VOICED FRICATIVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000296: LATIN LETTER INVERTED GLOTTAL STOP*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000297: LATIN LETTER STRETCHED C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000298: LATIN LETTER BILABIAL CLICK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000299: LATIN LETTER SMALL CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00029a: LATIN SMALL LETTER CLOSED OPEN E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00029b: LATIN LETTER SMALL CAPITAL G WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00029c: LATIN LETTER SMALL CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00029d: LATIN SMALL LETTER J WITH CROSSED-TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00029e: LATIN SMALL LETTER TURNED K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00029f: LATIN LETTER SMALL CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a0: LATIN SMALL LETTER Q WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a1: LATIN LETTER GLOTTAL STOP WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a2: LATIN LETTER REVERSED GLOTTAL STOP WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a3: LATIN SMALL LETTER DZ DIGRAPH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a4: LATIN SMALL LETTER DEZH DIGRAPH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a5: LATIN SMALL LETTER DZ DIGRAPH WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a6: LATIN SMALL LETTER TS DIGRAPH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a7: LATIN SMALL LETTER TESH DIGRAPH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a8: LATIN SMALL LETTER TC DIGRAPH WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002a9: LATIN SMALL LETTER FENG DIGRAPH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002aa: LATIN SMALL LETTER LS DIGRAPH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002ab: LATIN SMALL LETTER LZ DIGRAPH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002ac: LATIN LETTER BILABIAL PERCUSSIVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002ad: LATIN LETTER BIDENTAL PERCUSSIVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002ae: LATIN SMALL LETTER TURNED H WITH FISHHOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002af: LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002b0: MODIFIER LETTER SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002b1: MODIFIER LETTER SMALL H WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002b2: MODIFIER LETTER SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002b3: MODIFIER LETTER SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002b4: MODIFIER LETTER SMALL TURNED R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002b5: MODIFIER LETTER SMALL TURNED R WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002b6: MODIFIER LETTER SMALL CAPITAL INVERTED R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002b7: MODIFIER LETTER SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002b8: MODIFIER LETTER SMALL Y*/
+ RTUNI_ALPHA, /* U+0002b9: MODIFIER LETTER PRIME*/
+ RTUNI_ALPHA, /* U+0002ba: MODIFIER LETTER DOUBLE PRIME*/
+ RTUNI_ALPHA, /* U+0002bb: MODIFIER LETTER TURNED COMMA*/
+ RTUNI_ALPHA, /* U+0002bc: MODIFIER LETTER APOSTROPHE*/
+ RTUNI_ALPHA, /* U+0002bd: MODIFIER LETTER REVERSED COMMA*/
+ RTUNI_ALPHA, /* U+0002be: MODIFIER LETTER RIGHT HALF RING*/
+ RTUNI_ALPHA, /* U+0002bf: MODIFIER LETTER LEFT HALF RING*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002c0: MODIFIER LETTER GLOTTAL STOP*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002c1: MODIFIER LETTER REVERSED GLOTTAL STOP*/
+ 0, /* U+0002c2: MODIFIER LETTER LEFT ARROWHEAD*/
+ 0, /* U+0002c3: MODIFIER LETTER RIGHT ARROWHEAD*/
+ 0, /* U+0002c4: MODIFIER LETTER UP ARROWHEAD*/
+ 0, /* U+0002c5: MODIFIER LETTER DOWN ARROWHEAD*/
+ RTUNI_ALPHA, /* U+0002c6: MODIFIER LETTER CIRCUMFLEX ACCENT*/
+ RTUNI_ALPHA, /* U+0002c7: CARON*/
+ RTUNI_ALPHA, /* U+0002c8: MODIFIER LETTER VERTICAL LINE*/
+ RTUNI_ALPHA, /* U+0002c9: MODIFIER LETTER MACRON*/
+ RTUNI_ALPHA, /* U+0002ca: MODIFIER LETTER ACUTE ACCENT*/
+ RTUNI_ALPHA, /* U+0002cb: MODIFIER LETTER GRAVE ACCENT*/
+ RTUNI_ALPHA, /* U+0002cc: MODIFIER LETTER LOW VERTICAL LINE*/
+ RTUNI_ALPHA, /* U+0002cd: MODIFIER LETTER LOW MACRON*/
+ RTUNI_ALPHA, /* U+0002ce: MODIFIER LETTER LOW GRAVE ACCENT*/
+ RTUNI_ALPHA, /* U+0002cf: MODIFIER LETTER LOW ACUTE ACCENT*/
+ RTUNI_ALPHA, /* U+0002d0: MODIFIER LETTER TRIANGULAR COLON*/
+ RTUNI_ALPHA, /* U+0002d1: MODIFIER LETTER HALF TRIANGULAR COLON*/
+ 0, /* U+0002d2: MODIFIER LETTER CENTRED RIGHT HALF RING*/
+ 0, /* U+0002d3: MODIFIER LETTER CENTRED LEFT HALF RING*/
+ 0, /* U+0002d4: MODIFIER LETTER UP TACK*/
+ 0, /* U+0002d5: MODIFIER LETTER DOWN TACK*/
+ 0, /* U+0002d6: MODIFIER LETTER PLUS SIGN*/
+ 0, /* U+0002d7: MODIFIER LETTER MINUS SIGN*/
+ 0, /* U+0002d8: BREVE*/
+ 0, /* U+0002d9: DOT ABOVE*/
+ 0, /* U+0002da: RING ABOVE*/
+ 0, /* U+0002db: OGONEK*/
+ 0, /* U+0002dc: SMALL TILDE*/
+ 0, /* U+0002dd: DOUBLE ACUTE ACCENT*/
+ 0, /* U+0002de: MODIFIER LETTER RHOTIC HOOK*/
+ 0, /* U+0002df: MODIFIER LETTER CROSS ACCENT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002e0: MODIFIER LETTER SMALL GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002e1: MODIFIER LETTER SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002e2: MODIFIER LETTER SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002e3: MODIFIER LETTER SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0002e4: MODIFIER LETTER SMALL REVERSED GLOTTAL STOP*/
+ 0, /* U+0002e5: MODIFIER LETTER EXTRA-HIGH TONE BAR*/
+ 0, /* U+0002e6: MODIFIER LETTER HIGH TONE BAR*/
+ 0, /* U+0002e7: MODIFIER LETTER MID TONE BAR*/
+ 0, /* U+0002e8: MODIFIER LETTER LOW TONE BAR*/
+ 0, /* U+0002e9: MODIFIER LETTER EXTRA-LOW TONE BAR*/
+ 0, /* U+0002ea: MODIFIER LETTER YIN DEPARTING TONE MARK*/
+ 0, /* U+0002eb: MODIFIER LETTER YANG DEPARTING TONE MARK*/
+ RTUNI_ALPHA, /* U+0002ec: MODIFIER LETTER VOICING*/
+ 0, /* U+0002ed: MODIFIER LETTER UNASPIRATED*/
+ RTUNI_ALPHA, /* U+0002ee: MODIFIER LETTER DOUBLE APOSTROPHE*/
+ 0, /* U+0002ef: MODIFIER LETTER LOW DOWN ARROWHEAD*/
+ 0, /* U+0002f0: MODIFIER LETTER LOW UP ARROWHEAD*/
+ 0, /* U+0002f1: MODIFIER LETTER LOW LEFT ARROWHEAD*/
+ 0, /* U+0002f2: MODIFIER LETTER LOW RIGHT ARROWHEAD*/
+ 0, /* U+0002f3: MODIFIER LETTER LOW RING*/
+ 0, /* U+0002f4: MODIFIER LETTER MIDDLE GRAVE ACCENT*/
+ 0, /* U+0002f5: MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT*/
+ 0, /* U+0002f6: MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT*/
+ 0, /* U+0002f7: MODIFIER LETTER LOW TILDE*/
+ 0, /* U+0002f8: MODIFIER LETTER RAISED COLON*/
+ 0, /* U+0002f9: MODIFIER LETTER BEGIN HIGH TONE*/
+ 0, /* U+0002fa: MODIFIER LETTER END HIGH TONE*/
+ 0, /* U+0002fb: MODIFIER LETTER BEGIN LOW TONE*/
+ 0, /* U+0002fc: MODIFIER LETTER END LOW TONE*/
+ 0, /* U+0002fd: MODIFIER LETTER SHELF*/
+ 0, /* U+0002fe: MODIFIER LETTER OPEN SHELF*/
+ 0, /* U+0002ff: MODIFIER LETTER LOW LEFT ARROW*/
+ 0, /* U+000300: COMBINING GRAVE ACCENT*/
+ 0, /* U+000301: COMBINING ACUTE ACCENT*/
+ 0, /* U+000302: COMBINING CIRCUMFLEX ACCENT*/
+ 0, /* U+000303: COMBINING TILDE*/
+ 0, /* U+000304: COMBINING MACRON*/
+ 0, /* U+000305: COMBINING OVERLINE*/
+ 0, /* U+000306: COMBINING BREVE*/
+ 0, /* U+000307: COMBINING DOT ABOVE*/
+ 0, /* U+000308: COMBINING DIAERESIS*/
+ 0, /* U+000309: COMBINING HOOK ABOVE*/
+ 0, /* U+00030a: COMBINING RING ABOVE*/
+ 0, /* U+00030b: COMBINING DOUBLE ACUTE ACCENT*/
+ 0, /* U+00030c: COMBINING CARON*/
+ 0, /* U+00030d: COMBINING VERTICAL LINE ABOVE*/
+ 0, /* U+00030e: COMBINING DOUBLE VERTICAL LINE ABOVE*/
+ 0, /* U+00030f: COMBINING DOUBLE GRAVE ACCENT*/
+ 0, /* U+000310: COMBINING CANDRABINDU*/
+ 0, /* U+000311: COMBINING INVERTED BREVE*/
+ 0, /* U+000312: COMBINING TURNED COMMA ABOVE*/
+ 0, /* U+000313: COMBINING COMMA ABOVE*/
+ 0, /* U+000314: COMBINING REVERSED COMMA ABOVE*/
+ 0, /* U+000315: COMBINING COMMA ABOVE RIGHT*/
+ 0, /* U+000316: COMBINING GRAVE ACCENT BELOW*/
+ 0, /* U+000317: COMBINING ACUTE ACCENT BELOW*/
+ 0, /* U+000318: COMBINING LEFT TACK BELOW*/
+ 0, /* U+000319: COMBINING RIGHT TACK BELOW*/
+ 0, /* U+00031a: COMBINING LEFT ANGLE ABOVE*/
+ 0, /* U+00031b: COMBINING HORN*/
+ 0, /* U+00031c: COMBINING LEFT HALF RING BELOW*/
+ 0, /* U+00031d: COMBINING UP TACK BELOW*/
+ 0, /* U+00031e: COMBINING DOWN TACK BELOW*/
+ 0, /* U+00031f: COMBINING PLUS SIGN BELOW*/
+ 0, /* U+000320: COMBINING MINUS SIGN BELOW*/
+ 0, /* U+000321: COMBINING PALATALIZED HOOK BELOW*/
+ 0, /* U+000322: COMBINING RETROFLEX HOOK BELOW*/
+ 0, /* U+000323: COMBINING DOT BELOW*/
+ 0, /* U+000324: COMBINING DIAERESIS BELOW*/
+ 0, /* U+000325: COMBINING RING BELOW*/
+ 0, /* U+000326: COMBINING COMMA BELOW*/
+ 0, /* U+000327: COMBINING CEDILLA*/
+ 0, /* U+000328: COMBINING OGONEK*/
+ 0, /* U+000329: COMBINING VERTICAL LINE BELOW*/
+ 0, /* U+00032a: COMBINING BRIDGE BELOW*/
+ 0, /* U+00032b: COMBINING INVERTED DOUBLE ARCH BELOW*/
+ 0, /* U+00032c: COMBINING CARON BELOW*/
+ 0, /* U+00032d: COMBINING CIRCUMFLEX ACCENT BELOW*/
+ 0, /* U+00032e: COMBINING BREVE BELOW*/
+ 0, /* U+00032f: COMBINING INVERTED BREVE BELOW*/
+ 0, /* U+000330: COMBINING TILDE BELOW*/
+ 0, /* U+000331: COMBINING MACRON BELOW*/
+ 0, /* U+000332: COMBINING LOW LINE*/
+ 0, /* U+000333: COMBINING DOUBLE LOW LINE*/
+ 0, /* U+000334: COMBINING TILDE OVERLAY*/
+ 0, /* U+000335: COMBINING SHORT STROKE OVERLAY*/
+ 0, /* U+000336: COMBINING LONG STROKE OVERLAY*/
+ 0, /* U+000337: COMBINING SHORT SOLIDUS OVERLAY*/
+ 0, /* U+000338: COMBINING LONG SOLIDUS OVERLAY*/
+ 0, /* U+000339: COMBINING RIGHT HALF RING BELOW*/
+ 0, /* U+00033a: COMBINING INVERTED BRIDGE BELOW*/
+ 0, /* U+00033b: COMBINING SQUARE BELOW*/
+ 0, /* U+00033c: COMBINING SEAGULL BELOW*/
+ 0, /* U+00033d: COMBINING X ABOVE*/
+ 0, /* U+00033e: COMBINING VERTICAL TILDE*/
+ 0, /* U+00033f: COMBINING DOUBLE OVERLINE*/
+ 0, /* U+000340: COMBINING GRAVE TONE MARK*/
+ 0, /* U+000341: COMBINING ACUTE TONE MARK*/
+ 0, /* U+000342: COMBINING GREEK PERISPOMENI*/
+ 0, /* U+000343: COMBINING GREEK KORONIS*/
+ 0, /* U+000344: COMBINING GREEK DIALYTIKA TONOS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000345: COMBINING GREEK YPOGEGRAMMENI*/
+ 0, /* U+000346: COMBINING BRIDGE ABOVE*/
+ 0, /* U+000347: COMBINING EQUALS SIGN BELOW*/
+ 0, /* U+000348: COMBINING DOUBLE VERTICAL LINE BELOW*/
+ 0, /* U+000349: COMBINING LEFT ANGLE BELOW*/
+ 0, /* U+00034a: COMBINING NOT TILDE ABOVE*/
+ 0, /* U+00034b: COMBINING HOMOTHETIC ABOVE*/
+ 0, /* U+00034c: COMBINING ALMOST EQUAL TO ABOVE*/
+ 0, /* U+00034d: COMBINING LEFT RIGHT ARROW BELOW*/
+ 0, /* U+00034e: COMBINING UPWARDS ARROW BELOW*/
+ 0, /* U+00034f: COMBINING GRAPHEME JOINER*/
+ 0, /* U+000350: COMBINING RIGHT ARROWHEAD ABOVE*/
+ 0, /* U+000351: COMBINING LEFT HALF RING ABOVE*/
+ 0, /* U+000352: COMBINING FERMATA*/
+ 0, /* U+000353: COMBINING X BELOW*/
+ 0, /* U+000354: COMBINING LEFT ARROWHEAD BELOW*/
+ 0, /* U+000355: COMBINING RIGHT ARROWHEAD BELOW*/
+ 0, /* U+000356: COMBINING RIGHT ARROWHEAD AND UP ARROWHEAD BELOW*/
+ 0, /* U+000357: COMBINING RIGHT HALF RING ABOVE*/
+ 0, /* U+000358: COMBINING DOT ABOVE RIGHT*/
+ 0, /* U+000359: COMBINING ASTERISK BELOW*/
+ 0, /* U+00035a: COMBINING DOUBLE RING BELOW*/
+ 0, /* U+00035b: COMBINING ZIGZAG ABOVE*/
+ 0, /* U+00035c: COMBINING DOUBLE BREVE BELOW*/
+ 0, /* U+00035d: COMBINING DOUBLE BREVE*/
+ 0, /* U+00035e: COMBINING DOUBLE MACRON*/
+ 0, /* U+00035f: COMBINING DOUBLE MACRON BELOW*/
+ 0, /* U+000360: COMBINING DOUBLE TILDE*/
+ 0, /* U+000361: COMBINING DOUBLE INVERTED BREVE*/
+ 0, /* U+000362: COMBINING DOUBLE RIGHTWARDS ARROW BELOW*/
+ 0, /* U+000363: COMBINING LATIN SMALL LETTER A*/
+ 0, /* U+000364: COMBINING LATIN SMALL LETTER E*/
+ 0, /* U+000365: COMBINING LATIN SMALL LETTER I*/
+ 0, /* U+000366: COMBINING LATIN SMALL LETTER O*/
+ 0, /* U+000367: COMBINING LATIN SMALL LETTER U*/
+ 0, /* U+000368: COMBINING LATIN SMALL LETTER C*/
+ 0, /* U+000369: COMBINING LATIN SMALL LETTER D*/
+ 0, /* U+00036a: COMBINING LATIN SMALL LETTER H*/
+ 0, /* U+00036b: COMBINING LATIN SMALL LETTER M*/
+ 0, /* U+00036c: COMBINING LATIN SMALL LETTER R*/
+ 0, /* U+00036d: COMBINING LATIN SMALL LETTER T*/
+ 0, /* U+00036e: COMBINING LATIN SMALL LETTER V*/
+ 0, /* U+00036f: COMBINING LATIN SMALL LETTER X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000370: GREEK CAPITAL LETTER HETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000371: GREEK SMALL LETTER HETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000372: GREEK CAPITAL LETTER ARCHAIC SAMPI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000373: GREEK SMALL LETTER ARCHAIC SAMPI*/
+ RTUNI_ALPHA, /* U+000374: GREEK NUMERAL SIGN*/
+ 0, /* U+000375: GREEK LOWER NUMERAL SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000376: GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000377: GREEK SMALL LETTER PAMPHYLIAN DIGAMMA*/
+ 0, /* U+000378: */
+ 0, /* U+000379: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00037a: GREEK YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00037b: GREEK SMALL REVERSED LUNATE SIGMA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00037c: GREEK SMALL DOTTED LUNATE SIGMA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00037d: GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL*/
+ 0, /* U+00037e: GREEK QUESTION MARK*/
+ 0, /* U+00037f: */
+ 0, /* U+000380: */
+ 0, /* U+000381: */
+ 0, /* U+000382: */
+ 0, /* U+000383: */
+ 0, /* U+000384: GREEK TONOS*/
+ 0, /* U+000385: GREEK DIALYTIKA TONOS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000386: GREEK CAPITAL LETTER ALPHA WITH TONOS*/
+ 0, /* U+000387: GREEK ANO TELEIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000388: GREEK CAPITAL LETTER EPSILON WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000389: GREEK CAPITAL LETTER ETA WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00038a: GREEK CAPITAL LETTER IOTA WITH TONOS*/
+ 0, /* U+00038b: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00038c: GREEK CAPITAL LETTER OMICRON WITH TONOS*/
+ 0, /* U+00038d: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00038e: GREEK CAPITAL LETTER UPSILON WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00038f: GREEK CAPITAL LETTER OMEGA WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000390: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000391: GREEK CAPITAL LETTER ALPHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000392: GREEK CAPITAL LETTER BETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000393: GREEK CAPITAL LETTER GAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000394: GREEK CAPITAL LETTER DELTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000395: GREEK CAPITAL LETTER EPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000396: GREEK CAPITAL LETTER ZETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000397: GREEK CAPITAL LETTER ETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000398: GREEK CAPITAL LETTER THETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000399: GREEK CAPITAL LETTER IOTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00039a: GREEK CAPITAL LETTER KAPPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00039b: GREEK CAPITAL LETTER LAMDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00039c: GREEK CAPITAL LETTER MU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00039d: GREEK CAPITAL LETTER NU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00039e: GREEK CAPITAL LETTER XI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00039f: GREEK CAPITAL LETTER OMICRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003a0: GREEK CAPITAL LETTER PI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003a1: GREEK CAPITAL LETTER RHO*/
+ 0, /* U+0003a2: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003a3: GREEK CAPITAL LETTER SIGMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003a4: GREEK CAPITAL LETTER TAU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003a5: GREEK CAPITAL LETTER UPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003a6: GREEK CAPITAL LETTER PHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003a7: GREEK CAPITAL LETTER CHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003a8: GREEK CAPITAL LETTER PSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003a9: GREEK CAPITAL LETTER OMEGA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003aa: GREEK CAPITAL LETTER IOTA WITH DIALYTIKA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003ab: GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003ac: GREEK SMALL LETTER ALPHA WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003ad: GREEK SMALL LETTER EPSILON WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003ae: GREEK SMALL LETTER ETA WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003af: GREEK SMALL LETTER IOTA WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b0: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b1: GREEK SMALL LETTER ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b2: GREEK SMALL LETTER BETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b3: GREEK SMALL LETTER GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b4: GREEK SMALL LETTER DELTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b5: GREEK SMALL LETTER EPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b6: GREEK SMALL LETTER ZETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b7: GREEK SMALL LETTER ETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b8: GREEK SMALL LETTER THETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003b9: GREEK SMALL LETTER IOTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003ba: GREEK SMALL LETTER KAPPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003bb: GREEK SMALL LETTER LAMDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003bc: GREEK SMALL LETTER MU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003bd: GREEK SMALL LETTER NU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003be: GREEK SMALL LETTER XI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003bf: GREEK SMALL LETTER OMICRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c0: GREEK SMALL LETTER PI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c1: GREEK SMALL LETTER RHO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c2: GREEK SMALL LETTER FINAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c3: GREEK SMALL LETTER SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c4: GREEK SMALL LETTER TAU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c5: GREEK SMALL LETTER UPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c6: GREEK SMALL LETTER PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c7: GREEK SMALL LETTER CHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c8: GREEK SMALL LETTER PSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003c9: GREEK SMALL LETTER OMEGA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003ca: GREEK SMALL LETTER IOTA WITH DIALYTIKA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003cb: GREEK SMALL LETTER UPSILON WITH DIALYTIKA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003cc: GREEK SMALL LETTER OMICRON WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003cd: GREEK SMALL LETTER UPSILON WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003ce: GREEK SMALL LETTER OMEGA WITH TONOS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003cf: GREEK CAPITAL KAI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003d0: GREEK BETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003d1: GREEK THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003d2: GREEK UPSILON WITH HOOK SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003d3: GREEK UPSILON WITH ACUTE AND HOOK SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003d4: GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003d5: GREEK PHI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003d6: GREEK PI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003d7: GREEK KAI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003d8: GREEK LETTER ARCHAIC KOPPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003d9: GREEK SMALL LETTER ARCHAIC KOPPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003da: GREEK LETTER STIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003db: GREEK SMALL LETTER STIGMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003dc: GREEK LETTER DIGAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003dd: GREEK SMALL LETTER DIGAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003de: GREEK LETTER KOPPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003df: GREEK SMALL LETTER KOPPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003e0: GREEK LETTER SAMPI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003e1: GREEK SMALL LETTER SAMPI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003e2: COPTIC CAPITAL LETTER SHEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003e3: COPTIC SMALL LETTER SHEI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003e4: COPTIC CAPITAL LETTER FEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003e5: COPTIC SMALL LETTER FEI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003e6: COPTIC CAPITAL LETTER KHEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003e7: COPTIC SMALL LETTER KHEI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003e8: COPTIC CAPITAL LETTER HORI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003e9: COPTIC SMALL LETTER HORI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003ea: COPTIC CAPITAL LETTER GANGIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003eb: COPTIC SMALL LETTER GANGIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003ec: COPTIC CAPITAL LETTER SHIMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003ed: COPTIC SMALL LETTER SHIMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003ee: COPTIC CAPITAL LETTER DEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003ef: COPTIC SMALL LETTER DEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003f0: GREEK KAPPA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003f1: GREEK RHO SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003f2: GREEK LUNATE SIGMA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003f3: GREEK LETTER YOT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003f4: GREEK CAPITAL THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003f5: GREEK LUNATE EPSILON SYMBOL*/
+ 0, /* U+0003f6: GREEK REVERSED LUNATE EPSILON SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003f7: GREEK CAPITAL LETTER SHO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003f8: GREEK SMALL LETTER SHO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003f9: GREEK CAPITAL LUNATE SIGMA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003fa: GREEK CAPITAL LETTER SAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003fb: GREEK SMALL LETTER SAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0003fc: GREEK RHO WITH STROKE SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003fd: GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003fe: GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0003ff: GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000400: CYRILLIC CAPITAL LETTER IE WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000401: CYRILLIC CAPITAL LETTER IO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000402: CYRILLIC CAPITAL LETTER DJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000403: CYRILLIC CAPITAL LETTER GJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000404: CYRILLIC CAPITAL LETTER UKRAINIAN IE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000405: CYRILLIC CAPITAL LETTER DZE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000406: CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000407: CYRILLIC CAPITAL LETTER YI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000408: CYRILLIC CAPITAL LETTER JE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000409: CYRILLIC CAPITAL LETTER LJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00040a: CYRILLIC CAPITAL LETTER NJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00040b: CYRILLIC CAPITAL LETTER TSHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00040c: CYRILLIC CAPITAL LETTER KJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00040d: CYRILLIC CAPITAL LETTER I WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00040e: CYRILLIC CAPITAL LETTER SHORT U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00040f: CYRILLIC CAPITAL LETTER DZHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000410: CYRILLIC CAPITAL LETTER A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000411: CYRILLIC CAPITAL LETTER BE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000412: CYRILLIC CAPITAL LETTER VE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000413: CYRILLIC CAPITAL LETTER GHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000414: CYRILLIC CAPITAL LETTER DE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000415: CYRILLIC CAPITAL LETTER IE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000416: CYRILLIC CAPITAL LETTER ZHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000417: CYRILLIC CAPITAL LETTER ZE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000418: CYRILLIC CAPITAL LETTER I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000419: CYRILLIC CAPITAL LETTER SHORT I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00041a: CYRILLIC CAPITAL LETTER KA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00041b: CYRILLIC CAPITAL LETTER EL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00041c: CYRILLIC CAPITAL LETTER EM*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00041d: CYRILLIC CAPITAL LETTER EN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00041e: CYRILLIC CAPITAL LETTER O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00041f: CYRILLIC CAPITAL LETTER PE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000420: CYRILLIC CAPITAL LETTER ER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000421: CYRILLIC CAPITAL LETTER ES*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000422: CYRILLIC CAPITAL LETTER TE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000423: CYRILLIC CAPITAL LETTER U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000424: CYRILLIC CAPITAL LETTER EF*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000425: CYRILLIC CAPITAL LETTER HA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000426: CYRILLIC CAPITAL LETTER TSE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000427: CYRILLIC CAPITAL LETTER CHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000428: CYRILLIC CAPITAL LETTER SHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000429: CYRILLIC CAPITAL LETTER SHCHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00042a: CYRILLIC CAPITAL LETTER HARD SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00042b: CYRILLIC CAPITAL LETTER YERU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00042c: CYRILLIC CAPITAL LETTER SOFT SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00042d: CYRILLIC CAPITAL LETTER E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00042e: CYRILLIC CAPITAL LETTER YU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00042f: CYRILLIC CAPITAL LETTER YA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000430: CYRILLIC SMALL LETTER A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000431: CYRILLIC SMALL LETTER BE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000432: CYRILLIC SMALL LETTER VE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000433: CYRILLIC SMALL LETTER GHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000434: CYRILLIC SMALL LETTER DE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000435: CYRILLIC SMALL LETTER IE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000436: CYRILLIC SMALL LETTER ZHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000437: CYRILLIC SMALL LETTER ZE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000438: CYRILLIC SMALL LETTER I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000439: CYRILLIC SMALL LETTER SHORT I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00043a: CYRILLIC SMALL LETTER KA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00043b: CYRILLIC SMALL LETTER EL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00043c: CYRILLIC SMALL LETTER EM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00043d: CYRILLIC SMALL LETTER EN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00043e: CYRILLIC SMALL LETTER O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00043f: CYRILLIC SMALL LETTER PE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000440: CYRILLIC SMALL LETTER ER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000441: CYRILLIC SMALL LETTER ES*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000442: CYRILLIC SMALL LETTER TE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000443: CYRILLIC SMALL LETTER U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000444: CYRILLIC SMALL LETTER EF*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000445: CYRILLIC SMALL LETTER HA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000446: CYRILLIC SMALL LETTER TSE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000447: CYRILLIC SMALL LETTER CHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000448: CYRILLIC SMALL LETTER SHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000449: CYRILLIC SMALL LETTER SHCHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00044a: CYRILLIC SMALL LETTER HARD SIGN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00044b: CYRILLIC SMALL LETTER YERU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00044c: CYRILLIC SMALL LETTER SOFT SIGN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00044d: CYRILLIC SMALL LETTER E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00044e: CYRILLIC SMALL LETTER YU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00044f: CYRILLIC SMALL LETTER YA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000450: CYRILLIC SMALL LETTER IE WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000451: CYRILLIC SMALL LETTER IO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000452: CYRILLIC SMALL LETTER DJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000453: CYRILLIC SMALL LETTER GJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000454: CYRILLIC SMALL LETTER UKRAINIAN IE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000455: CYRILLIC SMALL LETTER DZE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000456: CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000457: CYRILLIC SMALL LETTER YI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000458: CYRILLIC SMALL LETTER JE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000459: CYRILLIC SMALL LETTER LJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00045a: CYRILLIC SMALL LETTER NJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00045b: CYRILLIC SMALL LETTER TSHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00045c: CYRILLIC SMALL LETTER KJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00045d: CYRILLIC SMALL LETTER I WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00045e: CYRILLIC SMALL LETTER SHORT U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00045f: CYRILLIC SMALL LETTER DZHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000460: CYRILLIC CAPITAL LETTER OMEGA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000461: CYRILLIC SMALL LETTER OMEGA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000462: CYRILLIC CAPITAL LETTER YAT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000463: CYRILLIC SMALL LETTER YAT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000464: CYRILLIC CAPITAL LETTER IOTIFIED E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000465: CYRILLIC SMALL LETTER IOTIFIED E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000466: CYRILLIC CAPITAL LETTER LITTLE YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000467: CYRILLIC SMALL LETTER LITTLE YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000468: CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000469: CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00046a: CYRILLIC CAPITAL LETTER BIG YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00046b: CYRILLIC SMALL LETTER BIG YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00046c: CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00046d: CYRILLIC SMALL LETTER IOTIFIED BIG YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00046e: CYRILLIC CAPITAL LETTER KSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00046f: CYRILLIC SMALL LETTER KSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000470: CYRILLIC CAPITAL LETTER PSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000471: CYRILLIC SMALL LETTER PSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000472: CYRILLIC CAPITAL LETTER FITA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000473: CYRILLIC SMALL LETTER FITA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000474: CYRILLIC CAPITAL LETTER IZHITSA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000475: CYRILLIC SMALL LETTER IZHITSA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000476: CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000477: CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000478: CYRILLIC CAPITAL LETTER UK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000479: CYRILLIC SMALL LETTER UK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00047a: CYRILLIC CAPITAL LETTER ROUND OMEGA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00047b: CYRILLIC SMALL LETTER ROUND OMEGA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00047c: CYRILLIC CAPITAL LETTER OMEGA WITH TITLO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00047d: CYRILLIC SMALL LETTER OMEGA WITH TITLO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00047e: CYRILLIC CAPITAL LETTER OT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00047f: CYRILLIC SMALL LETTER OT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000480: CYRILLIC CAPITAL LETTER KOPPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000481: CYRILLIC SMALL LETTER KOPPA*/
+ 0, /* U+000482: CYRILLIC THOUSANDS SIGN*/
+ 0, /* U+000483: COMBINING CYRILLIC TITLO*/
+ 0, /* U+000484: COMBINING CYRILLIC PALATALIZATION*/
+ 0, /* U+000485: COMBINING CYRILLIC DASIA PNEUMATA*/
+ 0, /* U+000486: COMBINING CYRILLIC PSILI PNEUMATA*/
+ 0, /* U+000487: COMBINING CYRILLIC POKRYTIE*/
+ 0, /* U+000488: COMBINING CYRILLIC HUNDRED THOUSANDS SIGN*/
+ 0, /* U+000489: COMBINING CYRILLIC MILLIONS SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00048a: CYRILLIC CAPITAL LETTER SHORT I WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00048b: CYRILLIC SMALL LETTER SHORT I WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00048c: CYRILLIC CAPITAL LETTER SEMISOFT SIGN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00048d: CYRILLIC SMALL LETTER SEMISOFT SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00048e: CYRILLIC CAPITAL LETTER ER WITH TICK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00048f: CYRILLIC SMALL LETTER ER WITH TICK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000490: CYRILLIC CAPITAL LETTER GHE WITH UPTURN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000491: CYRILLIC SMALL LETTER GHE WITH UPTURN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000492: CYRILLIC CAPITAL LETTER GHE WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000493: CYRILLIC SMALL LETTER GHE WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000494: CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000495: CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000496: CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000497: CYRILLIC SMALL LETTER ZHE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000498: CYRILLIC CAPITAL LETTER ZE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000499: CYRILLIC SMALL LETTER ZE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00049a: CYRILLIC CAPITAL LETTER KA WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00049b: CYRILLIC SMALL LETTER KA WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00049c: CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00049d: CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00049e: CYRILLIC CAPITAL LETTER KA WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00049f: CYRILLIC SMALL LETTER KA WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004a0: CYRILLIC CAPITAL LETTER BASHKIR KA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004a1: CYRILLIC SMALL LETTER BASHKIR KA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004a2: CYRILLIC CAPITAL LETTER EN WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004a3: CYRILLIC SMALL LETTER EN WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004a4: CYRILLIC CAPITAL LIGATURE EN GHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004a5: CYRILLIC SMALL LIGATURE EN GHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004a6: CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004a7: CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004a8: CYRILLIC CAPITAL LETTER ABKHASIAN HA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004a9: CYRILLIC SMALL LETTER ABKHASIAN HA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004aa: CYRILLIC CAPITAL LETTER ES WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004ab: CYRILLIC SMALL LETTER ES WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004ac: CYRILLIC CAPITAL LETTER TE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004ad: CYRILLIC SMALL LETTER TE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004ae: CYRILLIC CAPITAL LETTER STRAIGHT U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004af: CYRILLIC SMALL LETTER STRAIGHT U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004b0: CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004b1: CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004b2: CYRILLIC CAPITAL LETTER HA WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004b3: CYRILLIC SMALL LETTER HA WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004b4: CYRILLIC CAPITAL LIGATURE TE TSE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004b5: CYRILLIC SMALL LIGATURE TE TSE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004b6: CYRILLIC CAPITAL LETTER CHE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004b7: CYRILLIC SMALL LETTER CHE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004b8: CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004b9: CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004ba: CYRILLIC CAPITAL LETTER SHHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004bb: CYRILLIC SMALL LETTER SHHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004bc: CYRILLIC CAPITAL LETTER ABKHASIAN CHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004bd: CYRILLIC SMALL LETTER ABKHASIAN CHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004be: CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004bf: CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004c0: CYRILLIC LETTER PALOCHKA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004c1: CYRILLIC CAPITAL LETTER ZHE WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004c2: CYRILLIC SMALL LETTER ZHE WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004c3: CYRILLIC CAPITAL LETTER KA WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004c4: CYRILLIC SMALL LETTER KA WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004c5: CYRILLIC CAPITAL LETTER EL WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004c6: CYRILLIC SMALL LETTER EL WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004c7: CYRILLIC CAPITAL LETTER EN WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004c8: CYRILLIC SMALL LETTER EN WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004c9: CYRILLIC CAPITAL LETTER EN WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004ca: CYRILLIC SMALL LETTER EN WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004cb: CYRILLIC CAPITAL LETTER KHAKASSIAN CHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004cc: CYRILLIC SMALL LETTER KHAKASSIAN CHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004cd: CYRILLIC CAPITAL LETTER EM WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004ce: CYRILLIC SMALL LETTER EM WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004cf: CYRILLIC SMALL LETTER PALOCHKA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004d0: CYRILLIC CAPITAL LETTER A WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004d1: CYRILLIC SMALL LETTER A WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004d2: CYRILLIC CAPITAL LETTER A WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004d3: CYRILLIC SMALL LETTER A WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004d4: CYRILLIC CAPITAL LIGATURE A IE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004d5: CYRILLIC SMALL LIGATURE A IE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004d6: CYRILLIC CAPITAL LETTER IE WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004d7: CYRILLIC SMALL LETTER IE WITH BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004d8: CYRILLIC CAPITAL LETTER SCHWA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004d9: CYRILLIC SMALL LETTER SCHWA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004da: CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004db: CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004dc: CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004dd: CYRILLIC SMALL LETTER ZHE WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004de: CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004df: CYRILLIC SMALL LETTER ZE WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004e0: CYRILLIC CAPITAL LETTER ABKHASIAN DZE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004e1: CYRILLIC SMALL LETTER ABKHASIAN DZE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004e2: CYRILLIC CAPITAL LETTER I WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004e3: CYRILLIC SMALL LETTER I WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004e4: CYRILLIC CAPITAL LETTER I WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004e5: CYRILLIC SMALL LETTER I WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004e6: CYRILLIC CAPITAL LETTER O WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004e7: CYRILLIC SMALL LETTER O WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004e8: CYRILLIC CAPITAL LETTER BARRED O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004e9: CYRILLIC SMALL LETTER BARRED O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004ea: CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004eb: CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004ec: CYRILLIC CAPITAL LETTER E WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004ed: CYRILLIC SMALL LETTER E WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004ee: CYRILLIC CAPITAL LETTER U WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004ef: CYRILLIC SMALL LETTER U WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004f0: CYRILLIC CAPITAL LETTER U WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004f1: CYRILLIC SMALL LETTER U WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004f2: CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004f3: CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004f4: CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004f5: CYRILLIC SMALL LETTER CHE WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004f6: CYRILLIC CAPITAL LETTER GHE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004f7: CYRILLIC SMALL LETTER GHE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004f8: CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004f9: CYRILLIC SMALL LETTER YERU WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004fa: CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004fb: CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004fc: CYRILLIC CAPITAL LETTER HA WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004fd: CYRILLIC SMALL LETTER HA WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0004fe: CYRILLIC CAPITAL LETTER HA WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0004ff: CYRILLIC SMALL LETTER HA WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000500: CYRILLIC CAPITAL LETTER KOMI DE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000501: CYRILLIC SMALL LETTER KOMI DE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000502: CYRILLIC CAPITAL LETTER KOMI DJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000503: CYRILLIC SMALL LETTER KOMI DJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000504: CYRILLIC CAPITAL LETTER KOMI ZJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000505: CYRILLIC SMALL LETTER KOMI ZJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000506: CYRILLIC CAPITAL LETTER KOMI DZJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000507: CYRILLIC SMALL LETTER KOMI DZJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000508: CYRILLIC CAPITAL LETTER KOMI LJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000509: CYRILLIC SMALL LETTER KOMI LJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00050a: CYRILLIC CAPITAL LETTER KOMI NJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00050b: CYRILLIC SMALL LETTER KOMI NJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00050c: CYRILLIC CAPITAL LETTER KOMI SJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00050d: CYRILLIC SMALL LETTER KOMI SJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00050e: CYRILLIC CAPITAL LETTER KOMI TJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00050f: CYRILLIC SMALL LETTER KOMI TJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000510: CYRILLIC CAPITAL LETTER REVERSED ZE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000511: CYRILLIC SMALL LETTER REVERSED ZE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000512: CYRILLIC CAPITAL LETTER EL WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000513: CYRILLIC SMALL LETTER EL WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000514: CYRILLIC CAPITAL LETTER LHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000515: CYRILLIC SMALL LETTER LHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000516: CYRILLIC CAPITAL LETTER RHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000517: CYRILLIC SMALL LETTER RHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000518: CYRILLIC CAPITAL LETTER YAE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000519: CYRILLIC SMALL LETTER YAE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00051a: CYRILLIC CAPITAL LETTER QA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00051b: CYRILLIC SMALL LETTER QA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00051c: CYRILLIC CAPITAL LETTER WE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00051d: CYRILLIC SMALL LETTER WE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00051e: CYRILLIC CAPITAL LETTER ALEUT KA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00051f: CYRILLIC SMALL LETTER ALEUT KA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000520: CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000521: CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000522: CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000523: CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000524: CYRILLIC CAPITAL LETTER PE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000525: CYRILLIC SMALL LETTER PE WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000526: CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000527: CYRILLIC SMALL LETTER SHHA WITH DESCENDER*/
+ 0, /* U+000528: */
+ 0, /* U+000529: */
+ 0, /* U+00052a: */
+ 0, /* U+00052b: */
+ 0, /* U+00052c: */
+ 0, /* U+00052d: */
+ 0, /* U+00052e: */
+ 0, /* U+00052f: */
+ 0, /* U+000530: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000531: ARMENIAN CAPITAL LETTER AYB*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000532: ARMENIAN CAPITAL LETTER BEN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000533: ARMENIAN CAPITAL LETTER GIM*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000534: ARMENIAN CAPITAL LETTER DA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000535: ARMENIAN CAPITAL LETTER ECH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000536: ARMENIAN CAPITAL LETTER ZA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000537: ARMENIAN CAPITAL LETTER EH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000538: ARMENIAN CAPITAL LETTER ET*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000539: ARMENIAN CAPITAL LETTER TO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00053a: ARMENIAN CAPITAL LETTER ZHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00053b: ARMENIAN CAPITAL LETTER INI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00053c: ARMENIAN CAPITAL LETTER LIWN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00053d: ARMENIAN CAPITAL LETTER XEH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00053e: ARMENIAN CAPITAL LETTER CA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00053f: ARMENIAN CAPITAL LETTER KEN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000540: ARMENIAN CAPITAL LETTER HO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000541: ARMENIAN CAPITAL LETTER JA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000542: ARMENIAN CAPITAL LETTER GHAD*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000543: ARMENIAN CAPITAL LETTER CHEH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000544: ARMENIAN CAPITAL LETTER MEN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000545: ARMENIAN CAPITAL LETTER YI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000546: ARMENIAN CAPITAL LETTER NOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000547: ARMENIAN CAPITAL LETTER SHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000548: ARMENIAN CAPITAL LETTER VO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000549: ARMENIAN CAPITAL LETTER CHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00054a: ARMENIAN CAPITAL LETTER PEH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00054b: ARMENIAN CAPITAL LETTER JHEH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00054c: ARMENIAN CAPITAL LETTER RA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00054d: ARMENIAN CAPITAL LETTER SEH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00054e: ARMENIAN CAPITAL LETTER VEW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00054f: ARMENIAN CAPITAL LETTER TIWN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000550: ARMENIAN CAPITAL LETTER REH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000551: ARMENIAN CAPITAL LETTER CO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000552: ARMENIAN CAPITAL LETTER YIWN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000553: ARMENIAN CAPITAL LETTER PIWR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000554: ARMENIAN CAPITAL LETTER KEH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000555: ARMENIAN CAPITAL LETTER OH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+000556: ARMENIAN CAPITAL LETTER FEH*/
+ 0, /* U+000557: */
+ 0, /* U+000558: */
+ RTUNI_ALPHA, /* U+000559: ARMENIAN MODIFIER LETTER LEFT HALF RING*/
+ 0, /* U+00055a: ARMENIAN APOSTROPHE*/
+ 0, /* U+00055b: ARMENIAN EMPHASIS MARK*/
+ 0, /* U+00055c: ARMENIAN EXCLAMATION MARK*/
+ 0, /* U+00055d: ARMENIAN COMMA*/
+ 0, /* U+00055e: ARMENIAN QUESTION MARK*/
+ 0, /* U+00055f: ARMENIAN ABBREVIATION MARK*/
+ 0, /* U+000560: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000561: ARMENIAN SMALL LETTER AYB*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000562: ARMENIAN SMALL LETTER BEN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000563: ARMENIAN SMALL LETTER GIM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000564: ARMENIAN SMALL LETTER DA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000565: ARMENIAN SMALL LETTER ECH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000566: ARMENIAN SMALL LETTER ZA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000567: ARMENIAN SMALL LETTER EH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000568: ARMENIAN SMALL LETTER ET*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000569: ARMENIAN SMALL LETTER TO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00056a: ARMENIAN SMALL LETTER ZHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00056b: ARMENIAN SMALL LETTER INI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00056c: ARMENIAN SMALL LETTER LIWN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00056d: ARMENIAN SMALL LETTER XEH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00056e: ARMENIAN SMALL LETTER CA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00056f: ARMENIAN SMALL LETTER KEN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000570: ARMENIAN SMALL LETTER HO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000571: ARMENIAN SMALL LETTER JA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000572: ARMENIAN SMALL LETTER GHAD*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000573: ARMENIAN SMALL LETTER CHEH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000574: ARMENIAN SMALL LETTER MEN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000575: ARMENIAN SMALL LETTER YI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000576: ARMENIAN SMALL LETTER NOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000577: ARMENIAN SMALL LETTER SHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000578: ARMENIAN SMALL LETTER VO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000579: ARMENIAN SMALL LETTER CHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00057a: ARMENIAN SMALL LETTER PEH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00057b: ARMENIAN SMALL LETTER JHEH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00057c: ARMENIAN SMALL LETTER RA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00057d: ARMENIAN SMALL LETTER SEH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00057e: ARMENIAN SMALL LETTER VEW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00057f: ARMENIAN SMALL LETTER TIWN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000580: ARMENIAN SMALL LETTER REH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000581: ARMENIAN SMALL LETTER CO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000582: ARMENIAN SMALL LETTER YIWN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000583: ARMENIAN SMALL LETTER PIWR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000584: ARMENIAN SMALL LETTER KEH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000585: ARMENIAN SMALL LETTER OH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000586: ARMENIAN SMALL LETTER FEH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+000587: ARMENIAN SMALL LIGATURE ECH YIWN*/
+ 0, /* U+000588: */
+ 0, /* U+000589: ARMENIAN FULL STOP*/
+ 0, /* U+00058a: ARMENIAN HYPHEN*/
+ 0, /* U+00058b: */
+ 0, /* U+00058c: */
+ 0, /* U+00058d: */
+ 0, /* U+00058e: */
+ 0, /* U+00058f: ARMENIAN DRAM SIGN*/
+ 0, /* U+000590: */
+ 0, /* U+000591: HEBREW ACCENT ETNAHTA*/
+ 0, /* U+000592: HEBREW ACCENT SEGOL*/
+ 0, /* U+000593: HEBREW ACCENT SHALSHELET*/
+ 0, /* U+000594: HEBREW ACCENT ZAQEF QATAN*/
+ 0, /* U+000595: HEBREW ACCENT ZAQEF GADOL*/
+ 0, /* U+000596: HEBREW ACCENT TIPEHA*/
+ 0, /* U+000597: HEBREW ACCENT REVIA*/
+ 0, /* U+000598: HEBREW ACCENT ZARQA*/
+ 0, /* U+000599: HEBREW ACCENT PASHTA*/
+ 0, /* U+00059a: HEBREW ACCENT YETIV*/
+ 0, /* U+00059b: HEBREW ACCENT TEVIR*/
+ 0, /* U+00059c: HEBREW ACCENT GERESH*/
+ 0, /* U+00059d: HEBREW ACCENT GERESH MUQDAM*/
+ 0, /* U+00059e: HEBREW ACCENT GERSHAYIM*/
+ 0, /* U+00059f: HEBREW ACCENT QARNEY PARA*/
+ 0, /* U+0005a0: HEBREW ACCENT TELISHA GEDOLA*/
+ 0, /* U+0005a1: HEBREW ACCENT PAZER*/
+ 0, /* U+0005a2: HEBREW ACCENT ATNAH HAFUKH*/
+ 0, /* U+0005a3: HEBREW ACCENT MUNAH*/
+ 0, /* U+0005a4: HEBREW ACCENT MAHAPAKH*/
+ 0, /* U+0005a5: HEBREW ACCENT MERKHA*/
+ 0, /* U+0005a6: HEBREW ACCENT MERKHA KEFULA*/
+ 0, /* U+0005a7: HEBREW ACCENT DARGA*/
+ 0, /* U+0005a8: HEBREW ACCENT QADMA*/
+ 0, /* U+0005a9: HEBREW ACCENT TELISHA QETANA*/
+ 0, /* U+0005aa: HEBREW ACCENT YERAH BEN YOMO*/
+ 0, /* U+0005ab: HEBREW ACCENT OLE*/
+ 0, /* U+0005ac: HEBREW ACCENT ILUY*/
+ 0, /* U+0005ad: HEBREW ACCENT DEHI*/
+ 0, /* U+0005ae: HEBREW ACCENT ZINOR*/
+ 0, /* U+0005af: HEBREW MARK MASORA CIRCLE*/
+ RTUNI_ALPHA, /* U+0005b0: HEBREW POINT SHEVA*/
+ RTUNI_ALPHA, /* U+0005b1: HEBREW POINT HATAF SEGOL*/
+ RTUNI_ALPHA, /* U+0005b2: HEBREW POINT HATAF PATAH*/
+ RTUNI_ALPHA, /* U+0005b3: HEBREW POINT HATAF QAMATS*/
+ RTUNI_ALPHA, /* U+0005b4: HEBREW POINT HIRIQ*/
+ RTUNI_ALPHA, /* U+0005b5: HEBREW POINT TSERE*/
+ RTUNI_ALPHA, /* U+0005b6: HEBREW POINT SEGOL*/
+ RTUNI_ALPHA, /* U+0005b7: HEBREW POINT PATAH*/
+ RTUNI_ALPHA, /* U+0005b8: HEBREW POINT QAMATS*/
+ RTUNI_ALPHA, /* U+0005b9: HEBREW POINT HOLAM*/
+ RTUNI_ALPHA, /* U+0005ba: HEBREW POINT HOLAM HASER FOR VAV*/
+ RTUNI_ALPHA, /* U+0005bb: HEBREW POINT QUBUTS*/
+ RTUNI_ALPHA, /* U+0005bc: HEBREW POINT DAGESH OR MAPIQ*/
+ RTUNI_ALPHA, /* U+0005bd: HEBREW POINT METEG*/
+ 0, /* U+0005be: HEBREW PUNCTUATION MAQAF*/
+ RTUNI_ALPHA, /* U+0005bf: HEBREW POINT RAFE*/
+ 0, /* U+0005c0: HEBREW PUNCTUATION PASEQ*/
+ RTUNI_ALPHA, /* U+0005c1: HEBREW POINT SHIN DOT*/
+ RTUNI_ALPHA, /* U+0005c2: HEBREW POINT SIN DOT*/
+ 0, /* U+0005c3: HEBREW PUNCTUATION SOF PASUQ*/
+ RTUNI_ALPHA, /* U+0005c4: HEBREW MARK UPPER DOT*/
+ RTUNI_ALPHA, /* U+0005c5: HEBREW MARK LOWER DOT*/
+ 0, /* U+0005c6: HEBREW PUNCTUATION NUN HAFUKHA*/
+ RTUNI_ALPHA, /* U+0005c7: HEBREW POINT QAMATS QATAN*/
+ 0, /* U+0005c8: */
+ 0, /* U+0005c9: */
+ 0, /* U+0005ca: */
+ 0, /* U+0005cb: */
+ 0, /* U+0005cc: */
+ 0, /* U+0005cd: */
+ 0, /* U+0005ce: */
+ 0, /* U+0005cf: */
+ RTUNI_ALPHA, /* U+0005d0: HEBREW LETTER ALEF*/
+ RTUNI_ALPHA, /* U+0005d1: HEBREW LETTER BET*/
+ RTUNI_ALPHA, /* U+0005d2: HEBREW LETTER GIMEL*/
+ RTUNI_ALPHA, /* U+0005d3: HEBREW LETTER DALET*/
+ RTUNI_ALPHA, /* U+0005d4: HEBREW LETTER HE*/
+ RTUNI_ALPHA, /* U+0005d5: HEBREW LETTER VAV*/
+ RTUNI_ALPHA, /* U+0005d6: HEBREW LETTER ZAYIN*/
+ RTUNI_ALPHA, /* U+0005d7: HEBREW LETTER HET*/
+ RTUNI_ALPHA, /* U+0005d8: HEBREW LETTER TET*/
+ RTUNI_ALPHA, /* U+0005d9: HEBREW LETTER YOD*/
+ RTUNI_ALPHA, /* U+0005da: HEBREW LETTER FINAL KAF*/
+ RTUNI_ALPHA, /* U+0005db: HEBREW LETTER KAF*/
+ RTUNI_ALPHA, /* U+0005dc: HEBREW LETTER LAMED*/
+ RTUNI_ALPHA, /* U+0005dd: HEBREW LETTER FINAL MEM*/
+ RTUNI_ALPHA, /* U+0005de: HEBREW LETTER MEM*/
+ RTUNI_ALPHA, /* U+0005df: HEBREW LETTER FINAL NUN*/
+ RTUNI_ALPHA, /* U+0005e0: HEBREW LETTER NUN*/
+ RTUNI_ALPHA, /* U+0005e1: HEBREW LETTER SAMEKH*/
+ RTUNI_ALPHA, /* U+0005e2: HEBREW LETTER AYIN*/
+ RTUNI_ALPHA, /* U+0005e3: HEBREW LETTER FINAL PE*/
+ RTUNI_ALPHA, /* U+0005e4: HEBREW LETTER PE*/
+ RTUNI_ALPHA, /* U+0005e5: HEBREW LETTER FINAL TSADI*/
+ RTUNI_ALPHA, /* U+0005e6: HEBREW LETTER TSADI*/
+ RTUNI_ALPHA, /* U+0005e7: HEBREW LETTER QOF*/
+ RTUNI_ALPHA, /* U+0005e8: HEBREW LETTER RESH*/
+ RTUNI_ALPHA, /* U+0005e9: HEBREW LETTER SHIN*/
+ RTUNI_ALPHA, /* U+0005ea: HEBREW LETTER TAV*/
+ 0, /* U+0005eb: */
+ 0, /* U+0005ec: */
+ 0, /* U+0005ed: */
+ 0, /* U+0005ee: */
+ 0, /* U+0005ef: */
+ RTUNI_ALPHA, /* U+0005f0: HEBREW LIGATURE YIDDISH DOUBLE VAV*/
+ RTUNI_ALPHA, /* U+0005f1: HEBREW LIGATURE YIDDISH VAV YOD*/
+ RTUNI_ALPHA, /* U+0005f2: HEBREW LIGATURE YIDDISH DOUBLE YOD*/
+ 0, /* U+0005f3: HEBREW PUNCTUATION GERESH*/
+ 0, /* U+0005f4: HEBREW PUNCTUATION GERSHAYIM*/
+ 0, /* U+0005f5: */
+ 0, /* U+0005f6: */
+ 0, /* U+0005f7: */
+ 0, /* U+0005f8: */
+ 0, /* U+0005f9: */
+ 0, /* U+0005fa: */
+ 0, /* U+0005fb: */
+ 0, /* U+0005fc: */
+ 0, /* U+0005fd: */
+ 0, /* U+0005fe: */
+ 0, /* U+0005ff: */
+ 0, /* U+000600: ARABIC NUMBER SIGN*/
+ 0, /* U+000601: ARABIC SIGN SANAH*/
+ 0, /* U+000602: ARABIC FOOTNOTE MARKER*/
+ 0, /* U+000603: ARABIC SIGN SAFHA*/
+ 0, /* U+000604: ARABIC SIGN SAMVAT*/
+ 0, /* U+000605: */
+ 0, /* U+000606: ARABIC-INDIC CUBE ROOT*/
+ 0, /* U+000607: ARABIC-INDIC FOURTH ROOT*/
+ 0, /* U+000608: ARABIC RAY*/
+ 0, /* U+000609: ARABIC-INDIC PER MILLE SIGN*/
+ 0, /* U+00060a: ARABIC-INDIC PER TEN THOUSAND SIGN*/
+ 0, /* U+00060b: AFGHANI SIGN*/
+ 0, /* U+00060c: ARABIC COMMA*/
+ 0, /* U+00060d: ARABIC DATE SEPARATOR*/
+ 0, /* U+00060e: ARABIC POETIC VERSE SIGN*/
+ 0, /* U+00060f: ARABIC SIGN MISRA*/
+ RTUNI_ALPHA, /* U+000610: ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM*/
+ RTUNI_ALPHA, /* U+000611: ARABIC SIGN ALAYHE ASSALLAM*/
+ RTUNI_ALPHA, /* U+000612: ARABIC SIGN RAHMATULLAH ALAYHE*/
+ RTUNI_ALPHA, /* U+000613: ARABIC SIGN RADI ALLAHOU ANHU*/
+ RTUNI_ALPHA, /* U+000614: ARABIC SIGN TAKHALLUS*/
+ RTUNI_ALPHA, /* U+000615: ARABIC SMALL HIGH TAH*/
+ RTUNI_ALPHA, /* U+000616: ARABIC SMALL HIGH LIGATURE ALEF WITH LAM WITH YEH*/
+ RTUNI_ALPHA, /* U+000617: ARABIC SMALL HIGH ZAIN*/
+ RTUNI_ALPHA, /* U+000618: ARABIC SMALL FATHA*/
+ RTUNI_ALPHA, /* U+000619: ARABIC SMALL DAMMA*/
+ RTUNI_ALPHA, /* U+00061a: ARABIC SMALL KASRA*/
+ 0, /* U+00061b: ARABIC SEMICOLON*/
+ 0, /* U+00061c: ARABIC LETTER MARK*/
+ 0, /* U+00061d: */
+ 0, /* U+00061e: ARABIC TRIPLE DOT PUNCTUATION MARK*/
+ 0, /* U+00061f: ARABIC QUESTION MARK*/
+ RTUNI_ALPHA, /* U+000620: ARABIC LETTER KASHMIRI YEH*/
+ RTUNI_ALPHA, /* U+000621: ARABIC LETTER HAMZA*/
+ RTUNI_ALPHA, /* U+000622: ARABIC LETTER ALEF WITH MADDA ABOVE*/
+ RTUNI_ALPHA, /* U+000623: ARABIC LETTER ALEF WITH HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+000624: ARABIC LETTER WAW WITH HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+000625: ARABIC LETTER ALEF WITH HAMZA BELOW*/
+ RTUNI_ALPHA, /* U+000626: ARABIC LETTER YEH WITH HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+000627: ARABIC LETTER ALEF*/
+ RTUNI_ALPHA, /* U+000628: ARABIC LETTER BEH*/
+ RTUNI_ALPHA, /* U+000629: ARABIC LETTER TEH MARBUTA*/
+ RTUNI_ALPHA, /* U+00062a: ARABIC LETTER TEH*/
+ RTUNI_ALPHA, /* U+00062b: ARABIC LETTER THEH*/
+ RTUNI_ALPHA, /* U+00062c: ARABIC LETTER JEEM*/
+ RTUNI_ALPHA, /* U+00062d: ARABIC LETTER HAH*/
+ RTUNI_ALPHA, /* U+00062e: ARABIC LETTER KHAH*/
+ RTUNI_ALPHA, /* U+00062f: ARABIC LETTER DAL*/
+ RTUNI_ALPHA, /* U+000630: ARABIC LETTER THAL*/
+ RTUNI_ALPHA, /* U+000631: ARABIC LETTER REH*/
+ RTUNI_ALPHA, /* U+000632: ARABIC LETTER ZAIN*/
+ RTUNI_ALPHA, /* U+000633: ARABIC LETTER SEEN*/
+ RTUNI_ALPHA, /* U+000634: ARABIC LETTER SHEEN*/
+ RTUNI_ALPHA, /* U+000635: ARABIC LETTER SAD*/
+ RTUNI_ALPHA, /* U+000636: ARABIC LETTER DAD*/
+ RTUNI_ALPHA, /* U+000637: ARABIC LETTER TAH*/
+ RTUNI_ALPHA, /* U+000638: ARABIC LETTER ZAH*/
+ RTUNI_ALPHA, /* U+000639: ARABIC LETTER AIN*/
+ RTUNI_ALPHA, /* U+00063a: ARABIC LETTER GHAIN*/
+ RTUNI_ALPHA, /* U+00063b: ARABIC LETTER KEHEH WITH TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+00063c: ARABIC LETTER KEHEH WITH THREE DOTS BELOW*/
+ RTUNI_ALPHA, /* U+00063d: ARABIC LETTER FARSI YEH WITH INVERTED V*/
+ RTUNI_ALPHA, /* U+00063e: ARABIC LETTER FARSI YEH WITH TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+00063f: ARABIC LETTER FARSI YEH WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+000640: ARABIC TATWEEL*/
+ RTUNI_ALPHA, /* U+000641: ARABIC LETTER FEH*/
+ RTUNI_ALPHA, /* U+000642: ARABIC LETTER QAF*/
+ RTUNI_ALPHA, /* U+000643: ARABIC LETTER KAF*/
+ RTUNI_ALPHA, /* U+000644: ARABIC LETTER LAM*/
+ RTUNI_ALPHA, /* U+000645: ARABIC LETTER MEEM*/
+ RTUNI_ALPHA, /* U+000646: ARABIC LETTER NOON*/
+ RTUNI_ALPHA, /* U+000647: ARABIC LETTER HEH*/
+ RTUNI_ALPHA, /* U+000648: ARABIC LETTER WAW*/
+ RTUNI_ALPHA, /* U+000649: ARABIC LETTER ALEF MAKSURA*/
+ RTUNI_ALPHA, /* U+00064a: ARABIC LETTER YEH*/
+ RTUNI_ALPHA, /* U+00064b: ARABIC FATHATAN*/
+ RTUNI_ALPHA, /* U+00064c: ARABIC DAMMATAN*/
+ RTUNI_ALPHA, /* U+00064d: ARABIC KASRATAN*/
+ RTUNI_ALPHA, /* U+00064e: ARABIC FATHA*/
+ RTUNI_ALPHA, /* U+00064f: ARABIC DAMMA*/
+ RTUNI_ALPHA, /* U+000650: ARABIC KASRA*/
+ RTUNI_ALPHA, /* U+000651: ARABIC SHADDA*/
+ RTUNI_ALPHA, /* U+000652: ARABIC SUKUN*/
+ RTUNI_ALPHA, /* U+000653: ARABIC MADDAH ABOVE*/
+ RTUNI_ALPHA, /* U+000654: ARABIC HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+000655: ARABIC HAMZA BELOW*/
+ RTUNI_ALPHA, /* U+000656: ARABIC SUBSCRIPT ALEF*/
+ RTUNI_ALPHA, /* U+000657: ARABIC INVERTED DAMMA*/
+ 0, /* U+000658: ARABIC MARK NOON GHUNNA*/
+ RTUNI_ALPHA, /* U+000659: ARABIC ZWARAKAY*/
+ RTUNI_ALPHA, /* U+00065a: ARABIC VOWEL SIGN SMALL V ABOVE*/
+ RTUNI_ALPHA, /* U+00065b: ARABIC VOWEL SIGN INVERTED SMALL V ABOVE*/
+ RTUNI_ALPHA, /* U+00065c: ARABIC VOWEL SIGN DOT BELOW*/
+ RTUNI_ALPHA, /* U+00065d: ARABIC REVERSED DAMMA*/
+ RTUNI_ALPHA, /* U+00065e: ARABIC FATHA WITH TWO DOTS*/
+ RTUNI_ALPHA, /* U+00065f: ARABIC WAVY HAMZA BELOW*/
+ RTUNI_DDIGIT, /* U+000660: ARABIC-INDIC DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000661: ARABIC-INDIC DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000662: ARABIC-INDIC DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000663: ARABIC-INDIC DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000664: ARABIC-INDIC DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000665: ARABIC-INDIC DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000666: ARABIC-INDIC DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000667: ARABIC-INDIC DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000668: ARABIC-INDIC DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000669: ARABIC-INDIC DIGIT NINE*/
+ 0, /* U+00066a: ARABIC PERCENT SIGN*/
+ 0, /* U+00066b: ARABIC DECIMAL SEPARATOR*/
+ 0, /* U+00066c: ARABIC THOUSANDS SEPARATOR*/
+ 0, /* U+00066d: ARABIC FIVE POINTED STAR*/
+ RTUNI_ALPHA, /* U+00066e: ARABIC LETTER DOTLESS BEH*/
+ RTUNI_ALPHA, /* U+00066f: ARABIC LETTER DOTLESS QAF*/
+ RTUNI_ALPHA, /* U+000670: ARABIC LETTER SUPERSCRIPT ALEF*/
+ RTUNI_ALPHA, /* U+000671: ARABIC LETTER ALEF WASLA*/
+ RTUNI_ALPHA, /* U+000672: ARABIC LETTER ALEF WITH WAVY HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+000673: ARABIC LETTER ALEF WITH WAVY HAMZA BELOW*/
+ RTUNI_ALPHA, /* U+000674: ARABIC LETTER HIGH HAMZA*/
+ RTUNI_ALPHA, /* U+000675: ARABIC LETTER HIGH HAMZA ALEF*/
+ RTUNI_ALPHA, /* U+000676: ARABIC LETTER HIGH HAMZA WAW*/
+ RTUNI_ALPHA, /* U+000677: ARABIC LETTER U WITH HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+000678: ARABIC LETTER HIGH HAMZA YEH*/
+ RTUNI_ALPHA, /* U+000679: ARABIC LETTER TTEH*/
+ RTUNI_ALPHA, /* U+00067a: ARABIC LETTER TTEHEH*/
+ RTUNI_ALPHA, /* U+00067b: ARABIC LETTER BEEH*/
+ RTUNI_ALPHA, /* U+00067c: ARABIC LETTER TEH WITH RING*/
+ RTUNI_ALPHA, /* U+00067d: ARABIC LETTER TEH WITH THREE DOTS ABOVE DOWNWARDS*/
+ RTUNI_ALPHA, /* U+00067e: ARABIC LETTER PEH*/
+ RTUNI_ALPHA, /* U+00067f: ARABIC LETTER TEHEH*/
+ RTUNI_ALPHA, /* U+000680: ARABIC LETTER BEHEH*/
+ RTUNI_ALPHA, /* U+000681: ARABIC LETTER HAH WITH HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+000682: ARABIC LETTER HAH WITH TWO DOTS VERTICAL ABOVE*/
+ RTUNI_ALPHA, /* U+000683: ARABIC LETTER NYEH*/
+ RTUNI_ALPHA, /* U+000684: ARABIC LETTER DYEH*/
+ RTUNI_ALPHA, /* U+000685: ARABIC LETTER HAH WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+000686: ARABIC LETTER TCHEH*/
+ RTUNI_ALPHA, /* U+000687: ARABIC LETTER TCHEHEH*/
+ RTUNI_ALPHA, /* U+000688: ARABIC LETTER DDAL*/
+ RTUNI_ALPHA, /* U+000689: ARABIC LETTER DAL WITH RING*/
+ RTUNI_ALPHA, /* U+00068a: ARABIC LETTER DAL WITH DOT BELOW*/
+ RTUNI_ALPHA, /* U+00068b: ARABIC LETTER DAL WITH DOT BELOW AND SMALL TAH*/
+ RTUNI_ALPHA, /* U+00068c: ARABIC LETTER DAHAL*/
+ RTUNI_ALPHA, /* U+00068d: ARABIC LETTER DDAHAL*/
+ RTUNI_ALPHA, /* U+00068e: ARABIC LETTER DUL*/
+ RTUNI_ALPHA, /* U+00068f: ARABIC LETTER DAL WITH THREE DOTS ABOVE DOWNWARDS*/
+ RTUNI_ALPHA, /* U+000690: ARABIC LETTER DAL WITH FOUR DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+000691: ARABIC LETTER RREH*/
+ RTUNI_ALPHA, /* U+000692: ARABIC LETTER REH WITH SMALL V*/
+ RTUNI_ALPHA, /* U+000693: ARABIC LETTER REH WITH RING*/
+ RTUNI_ALPHA, /* U+000694: ARABIC LETTER REH WITH DOT BELOW*/
+ RTUNI_ALPHA, /* U+000695: ARABIC LETTER REH WITH SMALL V BELOW*/
+ RTUNI_ALPHA, /* U+000696: ARABIC LETTER REH WITH DOT BELOW AND DOT ABOVE*/
+ RTUNI_ALPHA, /* U+000697: ARABIC LETTER REH WITH TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+000698: ARABIC LETTER JEH*/
+ RTUNI_ALPHA, /* U+000699: ARABIC LETTER REH WITH FOUR DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+00069a: ARABIC LETTER SEEN WITH DOT BELOW AND DOT ABOVE*/
+ RTUNI_ALPHA, /* U+00069b: ARABIC LETTER SEEN WITH THREE DOTS BELOW*/
+ RTUNI_ALPHA, /* U+00069c: ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+00069d: ARABIC LETTER SAD WITH TWO DOTS BELOW*/
+ RTUNI_ALPHA, /* U+00069e: ARABIC LETTER SAD WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+00069f: ARABIC LETTER TAH WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0006a0: ARABIC LETTER AIN WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0006a1: ARABIC LETTER DOTLESS FEH*/
+ RTUNI_ALPHA, /* U+0006a2: ARABIC LETTER FEH WITH DOT MOVED BELOW*/
+ RTUNI_ALPHA, /* U+0006a3: ARABIC LETTER FEH WITH DOT BELOW*/
+ RTUNI_ALPHA, /* U+0006a4: ARABIC LETTER VEH*/
+ RTUNI_ALPHA, /* U+0006a5: ARABIC LETTER FEH WITH THREE DOTS BELOW*/
+ RTUNI_ALPHA, /* U+0006a6: ARABIC LETTER PEHEH*/
+ RTUNI_ALPHA, /* U+0006a7: ARABIC LETTER QAF WITH DOT ABOVE*/
+ RTUNI_ALPHA, /* U+0006a8: ARABIC LETTER QAF WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0006a9: ARABIC LETTER KEHEH*/
+ RTUNI_ALPHA, /* U+0006aa: ARABIC LETTER SWASH KAF*/
+ RTUNI_ALPHA, /* U+0006ab: ARABIC LETTER KAF WITH RING*/
+ RTUNI_ALPHA, /* U+0006ac: ARABIC LETTER KAF WITH DOT ABOVE*/
+ RTUNI_ALPHA, /* U+0006ad: ARABIC LETTER NG*/
+ RTUNI_ALPHA, /* U+0006ae: ARABIC LETTER KAF WITH THREE DOTS BELOW*/
+ RTUNI_ALPHA, /* U+0006af: ARABIC LETTER GAF*/
+ RTUNI_ALPHA, /* U+0006b0: ARABIC LETTER GAF WITH RING*/
+ RTUNI_ALPHA, /* U+0006b1: ARABIC LETTER NGOEH*/
+ RTUNI_ALPHA, /* U+0006b2: ARABIC LETTER GAF WITH TWO DOTS BELOW*/
+ RTUNI_ALPHA, /* U+0006b3: ARABIC LETTER GUEH*/
+ RTUNI_ALPHA, /* U+0006b4: ARABIC LETTER GAF WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0006b5: ARABIC LETTER LAM WITH SMALL V*/
+ RTUNI_ALPHA, /* U+0006b6: ARABIC LETTER LAM WITH DOT ABOVE*/
+ RTUNI_ALPHA, /* U+0006b7: ARABIC LETTER LAM WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0006b8: ARABIC LETTER LAM WITH THREE DOTS BELOW*/
+ RTUNI_ALPHA, /* U+0006b9: ARABIC LETTER NOON WITH DOT BELOW*/
+ RTUNI_ALPHA, /* U+0006ba: ARABIC LETTER NOON GHUNNA*/
+ RTUNI_ALPHA, /* U+0006bb: ARABIC LETTER RNOON*/
+ RTUNI_ALPHA, /* U+0006bc: ARABIC LETTER NOON WITH RING*/
+ RTUNI_ALPHA, /* U+0006bd: ARABIC LETTER NOON WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0006be: ARABIC LETTER HEH DOACHASHMEE*/
+ RTUNI_ALPHA, /* U+0006bf: ARABIC LETTER TCHEH WITH DOT ABOVE*/
+ RTUNI_ALPHA, /* U+0006c0: ARABIC LETTER HEH WITH YEH ABOVE*/
+ RTUNI_ALPHA, /* U+0006c1: ARABIC LETTER HEH GOAL*/
+ RTUNI_ALPHA, /* U+0006c2: ARABIC LETTER HEH GOAL WITH HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+0006c3: ARABIC LETTER TEH MARBUTA GOAL*/
+ RTUNI_ALPHA, /* U+0006c4: ARABIC LETTER WAW WITH RING*/
+ RTUNI_ALPHA, /* U+0006c5: ARABIC LETTER KIRGHIZ OE*/
+ RTUNI_ALPHA, /* U+0006c6: ARABIC LETTER OE*/
+ RTUNI_ALPHA, /* U+0006c7: ARABIC LETTER U*/
+ RTUNI_ALPHA, /* U+0006c8: ARABIC LETTER YU*/
+ RTUNI_ALPHA, /* U+0006c9: ARABIC LETTER KIRGHIZ YU*/
+ RTUNI_ALPHA, /* U+0006ca: ARABIC LETTER WAW WITH TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0006cb: ARABIC LETTER VE*/
+ RTUNI_ALPHA, /* U+0006cc: ARABIC LETTER FARSI YEH*/
+ RTUNI_ALPHA, /* U+0006cd: ARABIC LETTER YEH WITH TAIL*/
+ RTUNI_ALPHA, /* U+0006ce: ARABIC LETTER YEH WITH SMALL V*/
+ RTUNI_ALPHA, /* U+0006cf: ARABIC LETTER WAW WITH DOT ABOVE*/
+ RTUNI_ALPHA, /* U+0006d0: ARABIC LETTER E*/
+ RTUNI_ALPHA, /* U+0006d1: ARABIC LETTER YEH WITH THREE DOTS BELOW*/
+ RTUNI_ALPHA, /* U+0006d2: ARABIC LETTER YEH BARREE*/
+ RTUNI_ALPHA, /* U+0006d3: ARABIC LETTER YEH BARREE WITH HAMZA ABOVE*/
+ 0, /* U+0006d4: ARABIC FULL STOP*/
+ RTUNI_ALPHA, /* U+0006d5: ARABIC LETTER AE*/
+ RTUNI_ALPHA, /* U+0006d6: ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA*/
+ RTUNI_ALPHA, /* U+0006d7: ARABIC SMALL HIGH LIGATURE QAF WITH LAM WITH ALEF MAKSURA*/
+ RTUNI_ALPHA, /* U+0006d8: ARABIC SMALL HIGH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+0006d9: ARABIC SMALL HIGH LAM ALEF*/
+ RTUNI_ALPHA, /* U+0006da: ARABIC SMALL HIGH JEEM*/
+ RTUNI_ALPHA, /* U+0006db: ARABIC SMALL HIGH THREE DOTS*/
+ RTUNI_ALPHA, /* U+0006dc: ARABIC SMALL HIGH SEEN*/
+ 0, /* U+0006dd: ARABIC END OF AYAH*/
+ 0, /* U+0006de: ARABIC START OF RUB EL HIZB*/
+ 0, /* U+0006df: ARABIC SMALL HIGH ROUNDED ZERO*/
+ 0, /* U+0006e0: ARABIC SMALL HIGH UPRIGHT RECTANGULAR ZERO*/
+ RTUNI_ALPHA, /* U+0006e1: ARABIC SMALL HIGH DOTLESS HEAD OF KHAH*/
+ RTUNI_ALPHA, /* U+0006e2: ARABIC SMALL HIGH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+0006e3: ARABIC SMALL LOW SEEN*/
+ RTUNI_ALPHA, /* U+0006e4: ARABIC SMALL HIGH MADDA*/
+ RTUNI_ALPHA, /* U+0006e5: ARABIC SMALL WAW*/
+ RTUNI_ALPHA, /* U+0006e6: ARABIC SMALL YEH*/
+ RTUNI_ALPHA, /* U+0006e7: ARABIC SMALL HIGH YEH*/
+ RTUNI_ALPHA, /* U+0006e8: ARABIC SMALL HIGH NOON*/
+ 0, /* U+0006e9: ARABIC PLACE OF SAJDAH*/
+ 0, /* U+0006ea: ARABIC EMPTY CENTRE LOW STOP*/
+ 0, /* U+0006eb: ARABIC EMPTY CENTRE HIGH STOP*/
+ 0, /* U+0006ec: ARABIC ROUNDED HIGH STOP WITH FILLED CENTRE*/
+ RTUNI_ALPHA, /* U+0006ed: ARABIC SMALL LOW MEEM*/
+ RTUNI_ALPHA, /* U+0006ee: ARABIC LETTER DAL WITH INVERTED V*/
+ RTUNI_ALPHA, /* U+0006ef: ARABIC LETTER REH WITH INVERTED V*/
+ RTUNI_DDIGIT, /* U+0006f0: EXTENDED ARABIC-INDIC DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+0006f1: EXTENDED ARABIC-INDIC DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+0006f2: EXTENDED ARABIC-INDIC DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+0006f3: EXTENDED ARABIC-INDIC DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+0006f4: EXTENDED ARABIC-INDIC DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+0006f5: EXTENDED ARABIC-INDIC DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+0006f6: EXTENDED ARABIC-INDIC DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+0006f7: EXTENDED ARABIC-INDIC DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+0006f8: EXTENDED ARABIC-INDIC DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+0006f9: EXTENDED ARABIC-INDIC DIGIT NINE*/
+ RTUNI_ALPHA, /* U+0006fa: ARABIC LETTER SHEEN WITH DOT BELOW*/
+ RTUNI_ALPHA, /* U+0006fb: ARABIC LETTER DAD WITH DOT BELOW*/
+ RTUNI_ALPHA, /* U+0006fc: ARABIC LETTER GHAIN WITH DOT BELOW*/
+ 0, /* U+0006fd: ARABIC SIGN SINDHI AMPERSAND*/
+ 0, /* U+0006fe: ARABIC SIGN SINDHI POSTPOSITION MEN*/
+ RTUNI_ALPHA, /* U+0006ff: ARABIC LETTER HEH WITH INVERTED V*/
+ 0, /* U+000700: SYRIAC END OF PARAGRAPH*/
+ 0, /* U+000701: SYRIAC SUPRALINEAR FULL STOP*/
+ 0, /* U+000702: SYRIAC SUBLINEAR FULL STOP*/
+ 0, /* U+000703: SYRIAC SUPRALINEAR COLON*/
+ 0, /* U+000704: SYRIAC SUBLINEAR COLON*/
+ 0, /* U+000705: SYRIAC HORIZONTAL COLON*/
+ 0, /* U+000706: SYRIAC COLON SKEWED LEFT*/
+ 0, /* U+000707: SYRIAC COLON SKEWED RIGHT*/
+ 0, /* U+000708: SYRIAC SUPRALINEAR COLON SKEWED LEFT*/
+ 0, /* U+000709: SYRIAC SUBLINEAR COLON SKEWED RIGHT*/
+ 0, /* U+00070a: SYRIAC CONTRACTION*/
+ 0, /* U+00070b: SYRIAC HARKLEAN OBELUS*/
+ 0, /* U+00070c: SYRIAC HARKLEAN METOBELUS*/
+ 0, /* U+00070d: SYRIAC HARKLEAN ASTERISCUS*/
+ 0, /* U+00070e: */
+ 0, /* U+00070f: SYRIAC ABBREVIATION MARK*/
+ RTUNI_ALPHA, /* U+000710: SYRIAC LETTER ALAPH*/
+ RTUNI_ALPHA, /* U+000711: SYRIAC LETTER SUPERSCRIPT ALAPH*/
+ RTUNI_ALPHA, /* U+000712: SYRIAC LETTER BETH*/
+ RTUNI_ALPHA, /* U+000713: SYRIAC LETTER GAMAL*/
+ RTUNI_ALPHA, /* U+000714: SYRIAC LETTER GAMAL GARSHUNI*/
+ RTUNI_ALPHA, /* U+000715: SYRIAC LETTER DALATH*/
+ RTUNI_ALPHA, /* U+000716: SYRIAC LETTER DOTLESS DALATH RISH*/
+ RTUNI_ALPHA, /* U+000717: SYRIAC LETTER HE*/
+ RTUNI_ALPHA, /* U+000718: SYRIAC LETTER WAW*/
+ RTUNI_ALPHA, /* U+000719: SYRIAC LETTER ZAIN*/
+ RTUNI_ALPHA, /* U+00071a: SYRIAC LETTER HETH*/
+ RTUNI_ALPHA, /* U+00071b: SYRIAC LETTER TETH*/
+ RTUNI_ALPHA, /* U+00071c: SYRIAC LETTER TETH GARSHUNI*/
+ RTUNI_ALPHA, /* U+00071d: SYRIAC LETTER YUDH*/
+ RTUNI_ALPHA, /* U+00071e: SYRIAC LETTER YUDH HE*/
+ RTUNI_ALPHA, /* U+00071f: SYRIAC LETTER KAPH*/
+ RTUNI_ALPHA, /* U+000720: SYRIAC LETTER LAMADH*/
+ RTUNI_ALPHA, /* U+000721: SYRIAC LETTER MIM*/
+ RTUNI_ALPHA, /* U+000722: SYRIAC LETTER NUN*/
+ RTUNI_ALPHA, /* U+000723: SYRIAC LETTER SEMKATH*/
+ RTUNI_ALPHA, /* U+000724: SYRIAC LETTER FINAL SEMKATH*/
+ RTUNI_ALPHA, /* U+000725: SYRIAC LETTER E*/
+ RTUNI_ALPHA, /* U+000726: SYRIAC LETTER PE*/
+ RTUNI_ALPHA, /* U+000727: SYRIAC LETTER REVERSED PE*/
+ RTUNI_ALPHA, /* U+000728: SYRIAC LETTER SADHE*/
+ RTUNI_ALPHA, /* U+000729: SYRIAC LETTER QAPH*/
+ RTUNI_ALPHA, /* U+00072a: SYRIAC LETTER RISH*/
+ RTUNI_ALPHA, /* U+00072b: SYRIAC LETTER SHIN*/
+ RTUNI_ALPHA, /* U+00072c: SYRIAC LETTER TAW*/
+ RTUNI_ALPHA, /* U+00072d: SYRIAC LETTER PERSIAN BHETH*/
+ RTUNI_ALPHA, /* U+00072e: SYRIAC LETTER PERSIAN GHAMAL*/
+ RTUNI_ALPHA, /* U+00072f: SYRIAC LETTER PERSIAN DHALATH*/
+ RTUNI_ALPHA, /* U+000730: SYRIAC PTHAHA ABOVE*/
+ RTUNI_ALPHA, /* U+000731: SYRIAC PTHAHA BELOW*/
+ RTUNI_ALPHA, /* U+000732: SYRIAC PTHAHA DOTTED*/
+ RTUNI_ALPHA, /* U+000733: SYRIAC ZQAPHA ABOVE*/
+ RTUNI_ALPHA, /* U+000734: SYRIAC ZQAPHA BELOW*/
+ RTUNI_ALPHA, /* U+000735: SYRIAC ZQAPHA DOTTED*/
+ RTUNI_ALPHA, /* U+000736: SYRIAC RBASA ABOVE*/
+ RTUNI_ALPHA, /* U+000737: SYRIAC RBASA BELOW*/
+ RTUNI_ALPHA, /* U+000738: SYRIAC DOTTED ZLAMA HORIZONTAL*/
+ RTUNI_ALPHA, /* U+000739: SYRIAC DOTTED ZLAMA ANGULAR*/
+ RTUNI_ALPHA, /* U+00073a: SYRIAC HBASA ABOVE*/
+ RTUNI_ALPHA, /* U+00073b: SYRIAC HBASA BELOW*/
+ RTUNI_ALPHA, /* U+00073c: SYRIAC HBASA-ESASA DOTTED*/
+ RTUNI_ALPHA, /* U+00073d: SYRIAC ESASA ABOVE*/
+ RTUNI_ALPHA, /* U+00073e: SYRIAC ESASA BELOW*/
+ RTUNI_ALPHA, /* U+00073f: SYRIAC RWAHA*/
+ 0, /* U+000740: SYRIAC FEMININE DOT*/
+ 0, /* U+000741: SYRIAC QUSHSHAYA*/
+ 0, /* U+000742: SYRIAC RUKKAKHA*/
+ 0, /* U+000743: SYRIAC TWO VERTICAL DOTS ABOVE*/
+ 0, /* U+000744: SYRIAC TWO VERTICAL DOTS BELOW*/
+ 0, /* U+000745: SYRIAC THREE DOTS ABOVE*/
+ 0, /* U+000746: SYRIAC THREE DOTS BELOW*/
+ 0, /* U+000747: SYRIAC OBLIQUE LINE ABOVE*/
+ 0, /* U+000748: SYRIAC OBLIQUE LINE BELOW*/
+ 0, /* U+000749: SYRIAC MUSIC*/
+ 0, /* U+00074a: SYRIAC BARREKH*/
+ 0, /* U+00074b: */
+ 0, /* U+00074c: */
+ RTUNI_ALPHA, /* U+00074d: SYRIAC LETTER SOGDIAN ZHAIN*/
+ RTUNI_ALPHA, /* U+00074e: SYRIAC LETTER SOGDIAN KHAPH*/
+ RTUNI_ALPHA, /* U+00074f: SYRIAC LETTER SOGDIAN FE*/
+ RTUNI_ALPHA, /* U+000750: ARABIC LETTER BEH WITH THREE DOTS HORIZONTALLY BELOW*/
+ RTUNI_ALPHA, /* U+000751: ARABIC LETTER BEH WITH DOT BELOW AND THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+000752: ARABIC LETTER BEH WITH THREE DOTS POINTING UPWARDS BELOW*/
+ RTUNI_ALPHA, /* U+000753: ARABIC LETTER BEH WITH THREE DOTS POINTING UPWARDS BELOW AND TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+000754: ARABIC LETTER BEH WITH TWO DOTS BELOW AND DOT ABOVE*/
+ RTUNI_ALPHA, /* U+000755: ARABIC LETTER BEH WITH INVERTED SMALL V BELOW*/
+ RTUNI_ALPHA, /* U+000756: ARABIC LETTER BEH WITH SMALL V*/
+ RTUNI_ALPHA, /* U+000757: ARABIC LETTER HAH WITH TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+000758: ARABIC LETTER HAH WITH THREE DOTS POINTING UPWARDS BELOW*/
+ RTUNI_ALPHA, /* U+000759: ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW AND SMALL TAH*/
+ RTUNI_ALPHA, /* U+00075a: ARABIC LETTER DAL WITH INVERTED SMALL V BELOW*/
+ RTUNI_ALPHA, /* U+00075b: ARABIC LETTER REH WITH STROKE*/
+ RTUNI_ALPHA, /* U+00075c: ARABIC LETTER SEEN WITH FOUR DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+00075d: ARABIC LETTER AIN WITH TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+00075e: ARABIC LETTER AIN WITH THREE DOTS POINTING DOWNWARDS ABOVE*/
+ RTUNI_ALPHA, /* U+00075f: ARABIC LETTER AIN WITH TWO DOTS VERTICALLY ABOVE*/
+ RTUNI_ALPHA, /* U+000760: ARABIC LETTER FEH WITH TWO DOTS BELOW*/
+ RTUNI_ALPHA, /* U+000761: ARABIC LETTER FEH WITH THREE DOTS POINTING UPWARDS BELOW*/
+ RTUNI_ALPHA, /* U+000762: ARABIC LETTER KEHEH WITH DOT ABOVE*/
+ RTUNI_ALPHA, /* U+000763: ARABIC LETTER KEHEH WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+000764: ARABIC LETTER KEHEH WITH THREE DOTS POINTING UPWARDS BELOW*/
+ RTUNI_ALPHA, /* U+000765: ARABIC LETTER MEEM WITH DOT ABOVE*/
+ RTUNI_ALPHA, /* U+000766: ARABIC LETTER MEEM WITH DOT BELOW*/
+ RTUNI_ALPHA, /* U+000767: ARABIC LETTER NOON WITH TWO DOTS BELOW*/
+ RTUNI_ALPHA, /* U+000768: ARABIC LETTER NOON WITH SMALL TAH*/
+ RTUNI_ALPHA, /* U+000769: ARABIC LETTER NOON WITH SMALL V*/
+ RTUNI_ALPHA, /* U+00076a: ARABIC LETTER LAM WITH BAR*/
+ RTUNI_ALPHA, /* U+00076b: ARABIC LETTER REH WITH TWO DOTS VERTICALLY ABOVE*/
+ RTUNI_ALPHA, /* U+00076c: ARABIC LETTER REH WITH HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+00076d: ARABIC LETTER SEEN WITH TWO DOTS VERTICALLY ABOVE*/
+ RTUNI_ALPHA, /* U+00076e: ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH BELOW*/
+ RTUNI_ALPHA, /* U+00076f: ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH AND TWO DOTS*/
+ RTUNI_ALPHA, /* U+000770: ARABIC LETTER SEEN WITH SMALL ARABIC LETTER TAH AND TWO DOTS*/
+ RTUNI_ALPHA, /* U+000771: ARABIC LETTER REH WITH SMALL ARABIC LETTER TAH AND TWO DOTS*/
+ RTUNI_ALPHA, /* U+000772: ARABIC LETTER HAH WITH SMALL ARABIC LETTER TAH ABOVE*/
+ RTUNI_ALPHA, /* U+000773: ARABIC LETTER ALEF WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE*/
+ RTUNI_ALPHA, /* U+000774: ARABIC LETTER ALEF WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE*/
+ RTUNI_ALPHA, /* U+000775: ARABIC LETTER FARSI YEH WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE*/
+ RTUNI_ALPHA, /* U+000776: ARABIC LETTER FARSI YEH WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE*/
+ RTUNI_ALPHA, /* U+000777: ARABIC LETTER FARSI YEH WITH EXTENDED ARABIC-INDIC DIGIT FOUR BELOW*/
+ RTUNI_ALPHA, /* U+000778: ARABIC LETTER WAW WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE*/
+ RTUNI_ALPHA, /* U+000779: ARABIC LETTER WAW WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE*/
+ RTUNI_ALPHA, /* U+00077a: ARABIC LETTER YEH BARREE WITH EXTENDED ARABIC-INDIC DIGIT TWO ABOVE*/
+ RTUNI_ALPHA, /* U+00077b: ARABIC LETTER YEH BARREE WITH EXTENDED ARABIC-INDIC DIGIT THREE ABOVE*/
+ RTUNI_ALPHA, /* U+00077c: ARABIC LETTER HAH WITH EXTENDED ARABIC-INDIC DIGIT FOUR BELOW*/
+ RTUNI_ALPHA, /* U+00077d: ARABIC LETTER SEEN WITH EXTENDED ARABIC-INDIC DIGIT FOUR ABOVE*/
+ RTUNI_ALPHA, /* U+00077e: ARABIC LETTER SEEN WITH INVERTED V*/
+ RTUNI_ALPHA, /* U+00077f: ARABIC LETTER KAF WITH TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+000780: THAANA LETTER HAA*/
+ RTUNI_ALPHA, /* U+000781: THAANA LETTER SHAVIYANI*/
+ RTUNI_ALPHA, /* U+000782: THAANA LETTER NOONU*/
+ RTUNI_ALPHA, /* U+000783: THAANA LETTER RAA*/
+ RTUNI_ALPHA, /* U+000784: THAANA LETTER BAA*/
+ RTUNI_ALPHA, /* U+000785: THAANA LETTER LHAVIYANI*/
+ RTUNI_ALPHA, /* U+000786: THAANA LETTER KAAFU*/
+ RTUNI_ALPHA, /* U+000787: THAANA LETTER ALIFU*/
+ RTUNI_ALPHA, /* U+000788: THAANA LETTER VAAVU*/
+ RTUNI_ALPHA, /* U+000789: THAANA LETTER MEEMU*/
+ RTUNI_ALPHA, /* U+00078a: THAANA LETTER FAAFU*/
+ RTUNI_ALPHA, /* U+00078b: THAANA LETTER DHAALU*/
+ RTUNI_ALPHA, /* U+00078c: THAANA LETTER THAA*/
+ RTUNI_ALPHA, /* U+00078d: THAANA LETTER LAAMU*/
+ RTUNI_ALPHA, /* U+00078e: THAANA LETTER GAAFU*/
+ RTUNI_ALPHA, /* U+00078f: THAANA LETTER GNAVIYANI*/
+ RTUNI_ALPHA, /* U+000790: THAANA LETTER SEENU*/
+ RTUNI_ALPHA, /* U+000791: THAANA LETTER DAVIYANI*/
+ RTUNI_ALPHA, /* U+000792: THAANA LETTER ZAVIYANI*/
+ RTUNI_ALPHA, /* U+000793: THAANA LETTER TAVIYANI*/
+ RTUNI_ALPHA, /* U+000794: THAANA LETTER YAA*/
+ RTUNI_ALPHA, /* U+000795: THAANA LETTER PAVIYANI*/
+ RTUNI_ALPHA, /* U+000796: THAANA LETTER JAVIYANI*/
+ RTUNI_ALPHA, /* U+000797: THAANA LETTER CHAVIYANI*/
+ RTUNI_ALPHA, /* U+000798: THAANA LETTER TTAA*/
+ RTUNI_ALPHA, /* U+000799: THAANA LETTER HHAA*/
+ RTUNI_ALPHA, /* U+00079a: THAANA LETTER KHAA*/
+ RTUNI_ALPHA, /* U+00079b: THAANA LETTER THAALU*/
+ RTUNI_ALPHA, /* U+00079c: THAANA LETTER ZAA*/
+ RTUNI_ALPHA, /* U+00079d: THAANA LETTER SHEENU*/
+ RTUNI_ALPHA, /* U+00079e: THAANA LETTER SAADHU*/
+ RTUNI_ALPHA, /* U+00079f: THAANA LETTER DAADHU*/
+ RTUNI_ALPHA, /* U+0007a0: THAANA LETTER TO*/
+ RTUNI_ALPHA, /* U+0007a1: THAANA LETTER ZO*/
+ RTUNI_ALPHA, /* U+0007a2: THAANA LETTER AINU*/
+ RTUNI_ALPHA, /* U+0007a3: THAANA LETTER GHAINU*/
+ RTUNI_ALPHA, /* U+0007a4: THAANA LETTER QAAFU*/
+ RTUNI_ALPHA, /* U+0007a5: THAANA LETTER WAAVU*/
+ RTUNI_ALPHA, /* U+0007a6: THAANA ABAFILI*/
+ RTUNI_ALPHA, /* U+0007a7: THAANA AABAAFILI*/
+ RTUNI_ALPHA, /* U+0007a8: THAANA IBIFILI*/
+ RTUNI_ALPHA, /* U+0007a9: THAANA EEBEEFILI*/
+ RTUNI_ALPHA, /* U+0007aa: THAANA UBUFILI*/
+ RTUNI_ALPHA, /* U+0007ab: THAANA OOBOOFILI*/
+ RTUNI_ALPHA, /* U+0007ac: THAANA EBEFILI*/
+ RTUNI_ALPHA, /* U+0007ad: THAANA EYBEYFILI*/
+ RTUNI_ALPHA, /* U+0007ae: THAANA OBOFILI*/
+ RTUNI_ALPHA, /* U+0007af: THAANA OABOAFILI*/
+ RTUNI_ALPHA, /* U+0007b0: THAANA SUKUN*/
+ RTUNI_ALPHA, /* U+0007b1: THAANA LETTER NAA*/
+ 0, /* U+0007b2: */
+ 0, /* U+0007b3: */
+ 0, /* U+0007b4: */
+ 0, /* U+0007b5: */
+ 0, /* U+0007b6: */
+ 0, /* U+0007b7: */
+ 0, /* U+0007b8: */
+ 0, /* U+0007b9: */
+ 0, /* U+0007ba: */
+ 0, /* U+0007bb: */
+ 0, /* U+0007bc: */
+ 0, /* U+0007bd: */
+ 0, /* U+0007be: */
+ 0, /* U+0007bf: */
+ RTUNI_DDIGIT, /* U+0007c0: NKO DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+0007c1: NKO DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+0007c2: NKO DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+0007c3: NKO DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+0007c4: NKO DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+0007c5: NKO DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+0007c6: NKO DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+0007c7: NKO DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+0007c8: NKO DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+0007c9: NKO DIGIT NINE*/
+ RTUNI_ALPHA, /* U+0007ca: NKO LETTER A*/
+ RTUNI_ALPHA, /* U+0007cb: NKO LETTER EE*/
+ RTUNI_ALPHA, /* U+0007cc: NKO LETTER I*/
+ RTUNI_ALPHA, /* U+0007cd: NKO LETTER E*/
+ RTUNI_ALPHA, /* U+0007ce: NKO LETTER U*/
+ RTUNI_ALPHA, /* U+0007cf: NKO LETTER OO*/
+ RTUNI_ALPHA, /* U+0007d0: NKO LETTER O*/
+ RTUNI_ALPHA, /* U+0007d1: NKO LETTER DAGBASINNA*/
+ RTUNI_ALPHA, /* U+0007d2: NKO LETTER N*/
+ RTUNI_ALPHA, /* U+0007d3: NKO LETTER BA*/
+ RTUNI_ALPHA, /* U+0007d4: NKO LETTER PA*/
+ RTUNI_ALPHA, /* U+0007d5: NKO LETTER TA*/
+ RTUNI_ALPHA, /* U+0007d6: NKO LETTER JA*/
+ RTUNI_ALPHA, /* U+0007d7: NKO LETTER CHA*/
+ RTUNI_ALPHA, /* U+0007d8: NKO LETTER DA*/
+ RTUNI_ALPHA, /* U+0007d9: NKO LETTER RA*/
+ RTUNI_ALPHA, /* U+0007da: NKO LETTER RRA*/
+ RTUNI_ALPHA, /* U+0007db: NKO LETTER SA*/
+ RTUNI_ALPHA, /* U+0007dc: NKO LETTER GBA*/
+ RTUNI_ALPHA, /* U+0007dd: NKO LETTER FA*/
+ RTUNI_ALPHA, /* U+0007de: NKO LETTER KA*/
+ RTUNI_ALPHA, /* U+0007df: NKO LETTER LA*/
+ RTUNI_ALPHA, /* U+0007e0: NKO LETTER NA WOLOSO*/
+ RTUNI_ALPHA, /* U+0007e1: NKO LETTER MA*/
+ RTUNI_ALPHA, /* U+0007e2: NKO LETTER NYA*/
+ RTUNI_ALPHA, /* U+0007e3: NKO LETTER NA*/
+ RTUNI_ALPHA, /* U+0007e4: NKO LETTER HA*/
+ RTUNI_ALPHA, /* U+0007e5: NKO LETTER WA*/
+ RTUNI_ALPHA, /* U+0007e6: NKO LETTER YA*/
+ RTUNI_ALPHA, /* U+0007e7: NKO LETTER NYA WOLOSO*/
+ RTUNI_ALPHA, /* U+0007e8: NKO LETTER JONA JA*/
+ RTUNI_ALPHA, /* U+0007e9: NKO LETTER JONA CHA*/
+ RTUNI_ALPHA, /* U+0007ea: NKO LETTER JONA RA*/
+ 0, /* U+0007eb: NKO COMBINING SHORT HIGH TONE*/
+ 0, /* U+0007ec: NKO COMBINING SHORT LOW TONE*/
+ 0, /* U+0007ed: NKO COMBINING SHORT RISING TONE*/
+ 0, /* U+0007ee: NKO COMBINING LONG DESCENDING TONE*/
+ 0, /* U+0007ef: NKO COMBINING LONG HIGH TONE*/
+ 0, /* U+0007f0: NKO COMBINING LONG LOW TONE*/
+ 0, /* U+0007f1: NKO COMBINING LONG RISING TONE*/
+ 0, /* U+0007f2: NKO COMBINING NASALIZATION MARK*/
+ 0, /* U+0007f3: NKO COMBINING DOUBLE DOT ABOVE*/
+ RTUNI_ALPHA, /* U+0007f4: NKO HIGH TONE APOSTROPHE*/
+ RTUNI_ALPHA, /* U+0007f5: NKO LOW TONE APOSTROPHE*/
+ 0, /* U+0007f6: NKO SYMBOL OO DENNEN*/
+ 0, /* U+0007f7: NKO SYMBOL GBAKURUNEN*/
+ 0, /* U+0007f8: NKO COMMA*/
+ 0, /* U+0007f9: NKO EXCLAMATION MARK*/
+ RTUNI_ALPHA, /* U+0007fa: NKO LAJANYALAN*/
+ 0, /* U+0007fb: */
+ 0, /* U+0007fc: */
+ 0, /* U+0007fd: */
+ 0, /* U+0007fe: */
+ 0, /* U+0007ff: */
+ RTUNI_ALPHA, /* U+000800: SAMARITAN LETTER ALAF*/
+ RTUNI_ALPHA, /* U+000801: SAMARITAN LETTER BIT*/
+ RTUNI_ALPHA, /* U+000802: SAMARITAN LETTER GAMAN*/
+ RTUNI_ALPHA, /* U+000803: SAMARITAN LETTER DALAT*/
+ RTUNI_ALPHA, /* U+000804: SAMARITAN LETTER IY*/
+ RTUNI_ALPHA, /* U+000805: SAMARITAN LETTER BAA*/
+ RTUNI_ALPHA, /* U+000806: SAMARITAN LETTER ZEN*/
+ RTUNI_ALPHA, /* U+000807: SAMARITAN LETTER IT*/
+ RTUNI_ALPHA, /* U+000808: SAMARITAN LETTER TIT*/
+ RTUNI_ALPHA, /* U+000809: SAMARITAN LETTER YUT*/
+ RTUNI_ALPHA, /* U+00080a: SAMARITAN LETTER KAAF*/
+ RTUNI_ALPHA, /* U+00080b: SAMARITAN LETTER LABAT*/
+ RTUNI_ALPHA, /* U+00080c: SAMARITAN LETTER MIM*/
+ RTUNI_ALPHA, /* U+00080d: SAMARITAN LETTER NUN*/
+ RTUNI_ALPHA, /* U+00080e: SAMARITAN LETTER SINGAAT*/
+ RTUNI_ALPHA, /* U+00080f: SAMARITAN LETTER IN*/
+ RTUNI_ALPHA, /* U+000810: SAMARITAN LETTER FI*/
+ RTUNI_ALPHA, /* U+000811: SAMARITAN LETTER TSAADIY*/
+ RTUNI_ALPHA, /* U+000812: SAMARITAN LETTER QUF*/
+ RTUNI_ALPHA, /* U+000813: SAMARITAN LETTER RISH*/
+ RTUNI_ALPHA, /* U+000814: SAMARITAN LETTER SHAN*/
+ RTUNI_ALPHA, /* U+000815: SAMARITAN LETTER TAAF*/
+ RTUNI_ALPHA, /* U+000816: SAMARITAN MARK IN*/
+ RTUNI_ALPHA, /* U+000817: SAMARITAN MARK IN-ALAF*/
+ 0, /* U+000818: SAMARITAN MARK OCCLUSION*/
+ 0, /* U+000819: SAMARITAN MARK DAGESH*/
+ RTUNI_ALPHA, /* U+00081a: SAMARITAN MODIFIER LETTER EPENTHETIC YUT*/
+ RTUNI_ALPHA, /* U+00081b: SAMARITAN MARK EPENTHETIC YUT*/
+ RTUNI_ALPHA, /* U+00081c: SAMARITAN VOWEL SIGN LONG E*/
+ RTUNI_ALPHA, /* U+00081d: SAMARITAN VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+00081e: SAMARITAN VOWEL SIGN OVERLONG AA*/
+ RTUNI_ALPHA, /* U+00081f: SAMARITAN VOWEL SIGN LONG AA*/
+ RTUNI_ALPHA, /* U+000820: SAMARITAN VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000821: SAMARITAN VOWEL SIGN OVERLONG A*/
+ RTUNI_ALPHA, /* U+000822: SAMARITAN VOWEL SIGN LONG A*/
+ RTUNI_ALPHA, /* U+000823: SAMARITAN VOWEL SIGN A*/
+ RTUNI_ALPHA, /* U+000824: SAMARITAN MODIFIER LETTER SHORT A*/
+ RTUNI_ALPHA, /* U+000825: SAMARITAN VOWEL SIGN SHORT A*/
+ RTUNI_ALPHA, /* U+000826: SAMARITAN VOWEL SIGN LONG U*/
+ RTUNI_ALPHA, /* U+000827: SAMARITAN VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000828: SAMARITAN MODIFIER LETTER I*/
+ RTUNI_ALPHA, /* U+000829: SAMARITAN VOWEL SIGN LONG I*/
+ RTUNI_ALPHA, /* U+00082a: SAMARITAN VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+00082b: SAMARITAN VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+00082c: SAMARITAN VOWEL SIGN SUKUN*/
+ 0, /* U+00082d: SAMARITAN MARK NEQUDAA*/
+ 0, /* U+00082e: */
+ 0, /* U+00082f: */
+ 0, /* U+000830: SAMARITAN PUNCTUATION NEQUDAA*/
+ 0, /* U+000831: SAMARITAN PUNCTUATION AFSAAQ*/
+ 0, /* U+000832: SAMARITAN PUNCTUATION ANGED*/
+ 0, /* U+000833: SAMARITAN PUNCTUATION BAU*/
+ 0, /* U+000834: SAMARITAN PUNCTUATION ATMAAU*/
+ 0, /* U+000835: SAMARITAN PUNCTUATION SHIYYAALAA*/
+ 0, /* U+000836: SAMARITAN ABBREVIATION MARK*/
+ 0, /* U+000837: SAMARITAN PUNCTUATION MELODIC QITSA*/
+ 0, /* U+000838: SAMARITAN PUNCTUATION ZIQAA*/
+ 0, /* U+000839: SAMARITAN PUNCTUATION QITSA*/
+ 0, /* U+00083a: SAMARITAN PUNCTUATION ZAEF*/
+ 0, /* U+00083b: SAMARITAN PUNCTUATION TURU*/
+ 0, /* U+00083c: SAMARITAN PUNCTUATION ARKAANU*/
+ 0, /* U+00083d: SAMARITAN PUNCTUATION SOF MASHFAAT*/
+ 0, /* U+00083e: SAMARITAN PUNCTUATION ANNAAU*/
+ 0, /* U+00083f: */
+ RTUNI_ALPHA, /* U+000840: MANDAIC LETTER HALQA*/
+ RTUNI_ALPHA, /* U+000841: MANDAIC LETTER AB*/
+ RTUNI_ALPHA, /* U+000842: MANDAIC LETTER AG*/
+ RTUNI_ALPHA, /* U+000843: MANDAIC LETTER AD*/
+ RTUNI_ALPHA, /* U+000844: MANDAIC LETTER AH*/
+ RTUNI_ALPHA, /* U+000845: MANDAIC LETTER USHENNA*/
+ RTUNI_ALPHA, /* U+000846: MANDAIC LETTER AZ*/
+ RTUNI_ALPHA, /* U+000847: MANDAIC LETTER IT*/
+ RTUNI_ALPHA, /* U+000848: MANDAIC LETTER ATT*/
+ RTUNI_ALPHA, /* U+000849: MANDAIC LETTER AKSA*/
+ RTUNI_ALPHA, /* U+00084a: MANDAIC LETTER AK*/
+ RTUNI_ALPHA, /* U+00084b: MANDAIC LETTER AL*/
+ RTUNI_ALPHA, /* U+00084c: MANDAIC LETTER AM*/
+ RTUNI_ALPHA, /* U+00084d: MANDAIC LETTER AN*/
+ RTUNI_ALPHA, /* U+00084e: MANDAIC LETTER AS*/
+ RTUNI_ALPHA, /* U+00084f: MANDAIC LETTER IN*/
+ RTUNI_ALPHA, /* U+000850: MANDAIC LETTER AP*/
+ RTUNI_ALPHA, /* U+000851: MANDAIC LETTER ASZ*/
+ RTUNI_ALPHA, /* U+000852: MANDAIC LETTER AQ*/
+ RTUNI_ALPHA, /* U+000853: MANDAIC LETTER AR*/
+ RTUNI_ALPHA, /* U+000854: MANDAIC LETTER ASH*/
+ RTUNI_ALPHA, /* U+000855: MANDAIC LETTER AT*/
+ RTUNI_ALPHA, /* U+000856: MANDAIC LETTER DUSHENNA*/
+ RTUNI_ALPHA, /* U+000857: MANDAIC LETTER KAD*/
+ RTUNI_ALPHA, /* U+000858: MANDAIC LETTER AIN*/
+ 0, /* U+000859: MANDAIC AFFRICATION MARK*/
+ 0, /* U+00085a: MANDAIC VOCALIZATION MARK*/
+ 0, /* U+00085b: MANDAIC GEMINATION MARK*/
+ 0, /* U+00085c: */
+ 0, /* U+00085d: */
+ 0, /* U+00085e: MANDAIC PUNCTUATION*/
+ 0, /* U+00085f: */
+ 0, /* U+000860: */
+ 0, /* U+000861: */
+ 0, /* U+000862: */
+ 0, /* U+000863: */
+ 0, /* U+000864: */
+ 0, /* U+000865: */
+ 0, /* U+000866: */
+ 0, /* U+000867: */
+ 0, /* U+000868: */
+ 0, /* U+000869: */
+ 0, /* U+00086a: */
+ 0, /* U+00086b: */
+ 0, /* U+00086c: */
+ 0, /* U+00086d: */
+ 0, /* U+00086e: */
+ 0, /* U+00086f: */
+ 0, /* U+000870: */
+ 0, /* U+000871: */
+ 0, /* U+000872: */
+ 0, /* U+000873: */
+ 0, /* U+000874: */
+ 0, /* U+000875: */
+ 0, /* U+000876: */
+ 0, /* U+000877: */
+ 0, /* U+000878: */
+ 0, /* U+000879: */
+ 0, /* U+00087a: */
+ 0, /* U+00087b: */
+ 0, /* U+00087c: */
+ 0, /* U+00087d: */
+ 0, /* U+00087e: */
+ 0, /* U+00087f: */
+ 0, /* U+000880: */
+ 0, /* U+000881: */
+ 0, /* U+000882: */
+ 0, /* U+000883: */
+ 0, /* U+000884: */
+ 0, /* U+000885: */
+ 0, /* U+000886: */
+ 0, /* U+000887: */
+ 0, /* U+000888: */
+ 0, /* U+000889: */
+ 0, /* U+00088a: */
+ 0, /* U+00088b: */
+ 0, /* U+00088c: */
+ 0, /* U+00088d: */
+ 0, /* U+00088e: */
+ 0, /* U+00088f: */
+ 0, /* U+000890: */
+ 0, /* U+000891: */
+ 0, /* U+000892: */
+ 0, /* U+000893: */
+ 0, /* U+000894: */
+ 0, /* U+000895: */
+ 0, /* U+000896: */
+ 0, /* U+000897: */
+ 0, /* U+000898: */
+ 0, /* U+000899: */
+ 0, /* U+00089a: */
+ 0, /* U+00089b: */
+ 0, /* U+00089c: */
+ 0, /* U+00089d: */
+ 0, /* U+00089e: */
+ 0, /* U+00089f: */
+ RTUNI_ALPHA, /* U+0008a0: ARABIC LETTER BEH WITH SMALL V BELOW*/
+ 0, /* U+0008a1: */
+ RTUNI_ALPHA, /* U+0008a2: ARABIC LETTER JEEM WITH TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0008a3: ARABIC LETTER TAH WITH TWO DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0008a4: ARABIC LETTER FEH WITH DOT BELOW AND THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0008a5: ARABIC LETTER QAF WITH DOT BELOW*/
+ RTUNI_ALPHA, /* U+0008a6: ARABIC LETTER LAM WITH DOUBLE BAR*/
+ RTUNI_ALPHA, /* U+0008a7: ARABIC LETTER MEEM WITH THREE DOTS ABOVE*/
+ RTUNI_ALPHA, /* U+0008a8: ARABIC LETTER YEH WITH TWO DOTS BELOW AND HAMZA ABOVE*/
+ RTUNI_ALPHA, /* U+0008a9: ARABIC LETTER YEH WITH TWO DOTS BELOW AND DOT ABOVE*/
+ RTUNI_ALPHA, /* U+0008aa: ARABIC LETTER REH WITH LOOP*/
+ RTUNI_ALPHA, /* U+0008ab: ARABIC LETTER WAW WITH DOT WITHIN*/
+ RTUNI_ALPHA, /* U+0008ac: ARABIC LETTER ROHINGYA YEH*/
+ 0, /* U+0008ad: */
+ 0, /* U+0008ae: */
+ 0, /* U+0008af: */
+ 0, /* U+0008b0: */
+ 0, /* U+0008b1: */
+ 0, /* U+0008b2: */
+ 0, /* U+0008b3: */
+ 0, /* U+0008b4: */
+ 0, /* U+0008b5: */
+ 0, /* U+0008b6: */
+ 0, /* U+0008b7: */
+ 0, /* U+0008b8: */
+ 0, /* U+0008b9: */
+ 0, /* U+0008ba: */
+ 0, /* U+0008bb: */
+ 0, /* U+0008bc: */
+ 0, /* U+0008bd: */
+ 0, /* U+0008be: */
+ 0, /* U+0008bf: */
+ 0, /* U+0008c0: */
+ 0, /* U+0008c1: */
+ 0, /* U+0008c2: */
+ 0, /* U+0008c3: */
+ 0, /* U+0008c4: */
+ 0, /* U+0008c5: */
+ 0, /* U+0008c6: */
+ 0, /* U+0008c7: */
+ 0, /* U+0008c8: */
+ 0, /* U+0008c9: */
+ 0, /* U+0008ca: */
+ 0, /* U+0008cb: */
+ 0, /* U+0008cc: */
+ 0, /* U+0008cd: */
+ 0, /* U+0008ce: */
+ 0, /* U+0008cf: */
+ 0, /* U+0008d0: */
+ 0, /* U+0008d1: */
+ 0, /* U+0008d2: */
+ 0, /* U+0008d3: */
+ 0, /* U+0008d4: */
+ 0, /* U+0008d5: */
+ 0, /* U+0008d6: */
+ 0, /* U+0008d7: */
+ 0, /* U+0008d8: */
+ 0, /* U+0008d9: */
+ 0, /* U+0008da: */
+ 0, /* U+0008db: */
+ 0, /* U+0008dc: */
+ 0, /* U+0008dd: */
+ 0, /* U+0008de: */
+ 0, /* U+0008df: */
+ 0, /* U+0008e0: */
+ 0, /* U+0008e1: */
+ 0, /* U+0008e2: */
+ 0, /* U+0008e3: */
+ RTUNI_ALPHA, /* U+0008e4: ARABIC CURLY FATHA*/
+ RTUNI_ALPHA, /* U+0008e5: ARABIC CURLY DAMMA*/
+ RTUNI_ALPHA, /* U+0008e6: ARABIC CURLY KASRA*/
+ RTUNI_ALPHA, /* U+0008e7: ARABIC CURLY FATHATAN*/
+ RTUNI_ALPHA, /* U+0008e8: ARABIC CURLY DAMMATAN*/
+ RTUNI_ALPHA, /* U+0008e9: ARABIC CURLY KASRATAN*/
+ 0, /* U+0008ea: ARABIC TONE ONE DOT ABOVE*/
+ 0, /* U+0008eb: ARABIC TONE TWO DOTS ABOVE*/
+ 0, /* U+0008ec: ARABIC TONE LOOP ABOVE*/
+ 0, /* U+0008ed: ARABIC TONE ONE DOT BELOW*/
+ 0, /* U+0008ee: ARABIC TONE TWO DOTS BELOW*/
+ 0, /* U+0008ef: ARABIC TONE LOOP BELOW*/
+ RTUNI_ALPHA, /* U+0008f0: ARABIC OPEN FATHATAN*/
+ RTUNI_ALPHA, /* U+0008f1: ARABIC OPEN DAMMATAN*/
+ RTUNI_ALPHA, /* U+0008f2: ARABIC OPEN KASRATAN*/
+ RTUNI_ALPHA, /* U+0008f3: ARABIC SMALL HIGH WAW*/
+ RTUNI_ALPHA, /* U+0008f4: ARABIC FATHA WITH RING*/
+ RTUNI_ALPHA, /* U+0008f5: ARABIC FATHA WITH DOT ABOVE*/
+ RTUNI_ALPHA, /* U+0008f6: ARABIC KASRA WITH DOT BELOW*/
+ RTUNI_ALPHA, /* U+0008f7: ARABIC LEFT ARROWHEAD ABOVE*/
+ RTUNI_ALPHA, /* U+0008f8: ARABIC RIGHT ARROWHEAD ABOVE*/
+ RTUNI_ALPHA, /* U+0008f9: ARABIC LEFT ARROWHEAD BELOW*/
+ RTUNI_ALPHA, /* U+0008fa: ARABIC RIGHT ARROWHEAD BELOW*/
+ RTUNI_ALPHA, /* U+0008fb: ARABIC DOUBLE RIGHT ARROWHEAD ABOVE*/
+ RTUNI_ALPHA, /* U+0008fc: ARABIC DOUBLE RIGHT ARROWHEAD ABOVE WITH DOT*/
+ RTUNI_ALPHA, /* U+0008fd: ARABIC RIGHT ARROWHEAD ABOVE WITH DOT*/
+ RTUNI_ALPHA, /* U+0008fe: ARABIC DAMMA WITH DOT*/
+ 0, /* U+0008ff: */
+ RTUNI_ALPHA, /* U+000900: DEVANAGARI SIGN INVERTED CANDRABINDU*/
+ RTUNI_ALPHA, /* U+000901: DEVANAGARI SIGN CANDRABINDU*/
+ RTUNI_ALPHA, /* U+000902: DEVANAGARI SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+000903: DEVANAGARI SIGN VISARGA*/
+ RTUNI_ALPHA, /* U+000904: DEVANAGARI LETTER SHORT A*/
+ RTUNI_ALPHA, /* U+000905: DEVANAGARI LETTER A*/
+ RTUNI_ALPHA, /* U+000906: DEVANAGARI LETTER AA*/
+ RTUNI_ALPHA, /* U+000907: DEVANAGARI LETTER I*/
+ RTUNI_ALPHA, /* U+000908: DEVANAGARI LETTER II*/
+ RTUNI_ALPHA, /* U+000909: DEVANAGARI LETTER U*/
+ RTUNI_ALPHA, /* U+00090a: DEVANAGARI LETTER UU*/
+ RTUNI_ALPHA, /* U+00090b: DEVANAGARI LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+00090c: DEVANAGARI LETTER VOCALIC L*/
+ RTUNI_ALPHA, /* U+00090d: DEVANAGARI LETTER CANDRA E*/
+ RTUNI_ALPHA, /* U+00090e: DEVANAGARI LETTER SHORT E*/
+ RTUNI_ALPHA, /* U+00090f: DEVANAGARI LETTER E*/
+ RTUNI_ALPHA, /* U+000910: DEVANAGARI LETTER AI*/
+ RTUNI_ALPHA, /* U+000911: DEVANAGARI LETTER CANDRA O*/
+ RTUNI_ALPHA, /* U+000912: DEVANAGARI LETTER SHORT O*/
+ RTUNI_ALPHA, /* U+000913: DEVANAGARI LETTER O*/
+ RTUNI_ALPHA, /* U+000914: DEVANAGARI LETTER AU*/
+ RTUNI_ALPHA, /* U+000915: DEVANAGARI LETTER KA*/
+ RTUNI_ALPHA, /* U+000916: DEVANAGARI LETTER KHA*/
+ RTUNI_ALPHA, /* U+000917: DEVANAGARI LETTER GA*/
+ RTUNI_ALPHA, /* U+000918: DEVANAGARI LETTER GHA*/
+ RTUNI_ALPHA, /* U+000919: DEVANAGARI LETTER NGA*/
+ RTUNI_ALPHA, /* U+00091a: DEVANAGARI LETTER CA*/
+ RTUNI_ALPHA, /* U+00091b: DEVANAGARI LETTER CHA*/
+ RTUNI_ALPHA, /* U+00091c: DEVANAGARI LETTER JA*/
+ RTUNI_ALPHA, /* U+00091d: DEVANAGARI LETTER JHA*/
+ RTUNI_ALPHA, /* U+00091e: DEVANAGARI LETTER NYA*/
+ RTUNI_ALPHA, /* U+00091f: DEVANAGARI LETTER TTA*/
+ RTUNI_ALPHA, /* U+000920: DEVANAGARI LETTER TTHA*/
+ RTUNI_ALPHA, /* U+000921: DEVANAGARI LETTER DDA*/
+ RTUNI_ALPHA, /* U+000922: DEVANAGARI LETTER DDHA*/
+ RTUNI_ALPHA, /* U+000923: DEVANAGARI LETTER NNA*/
+ RTUNI_ALPHA, /* U+000924: DEVANAGARI LETTER TA*/
+ RTUNI_ALPHA, /* U+000925: DEVANAGARI LETTER THA*/
+ RTUNI_ALPHA, /* U+000926: DEVANAGARI LETTER DA*/
+ RTUNI_ALPHA, /* U+000927: DEVANAGARI LETTER DHA*/
+ RTUNI_ALPHA, /* U+000928: DEVANAGARI LETTER NA*/
+ RTUNI_ALPHA, /* U+000929: DEVANAGARI LETTER NNNA*/
+ RTUNI_ALPHA, /* U+00092a: DEVANAGARI LETTER PA*/
+ RTUNI_ALPHA, /* U+00092b: DEVANAGARI LETTER PHA*/
+ RTUNI_ALPHA, /* U+00092c: DEVANAGARI LETTER BA*/
+ RTUNI_ALPHA, /* U+00092d: DEVANAGARI LETTER BHA*/
+ RTUNI_ALPHA, /* U+00092e: DEVANAGARI LETTER MA*/
+ RTUNI_ALPHA, /* U+00092f: DEVANAGARI LETTER YA*/
+ RTUNI_ALPHA, /* U+000930: DEVANAGARI LETTER RA*/
+ RTUNI_ALPHA, /* U+000931: DEVANAGARI LETTER RRA*/
+ RTUNI_ALPHA, /* U+000932: DEVANAGARI LETTER LA*/
+ RTUNI_ALPHA, /* U+000933: DEVANAGARI LETTER LLA*/
+ RTUNI_ALPHA, /* U+000934: DEVANAGARI LETTER LLLA*/
+ RTUNI_ALPHA, /* U+000935: DEVANAGARI LETTER VA*/
+ RTUNI_ALPHA, /* U+000936: DEVANAGARI LETTER SHA*/
+ RTUNI_ALPHA, /* U+000937: DEVANAGARI LETTER SSA*/
+ RTUNI_ALPHA, /* U+000938: DEVANAGARI LETTER SA*/
+ RTUNI_ALPHA, /* U+000939: DEVANAGARI LETTER HA*/
+ RTUNI_ALPHA, /* U+00093a: DEVANAGARI VOWEL SIGN OE*/
+ RTUNI_ALPHA, /* U+00093b: DEVANAGARI VOWEL SIGN OOE*/
+ 0, /* U+00093c: DEVANAGARI SIGN NUKTA*/
+ RTUNI_ALPHA, /* U+00093d: DEVANAGARI SIGN AVAGRAHA*/
+ RTUNI_ALPHA, /* U+00093e: DEVANAGARI VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+00093f: DEVANAGARI VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000940: DEVANAGARI VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000941: DEVANAGARI VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000942: DEVANAGARI VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+000943: DEVANAGARI VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+000944: DEVANAGARI VOWEL SIGN VOCALIC RR*/
+ RTUNI_ALPHA, /* U+000945: DEVANAGARI VOWEL SIGN CANDRA E*/
+ RTUNI_ALPHA, /* U+000946: DEVANAGARI VOWEL SIGN SHORT E*/
+ RTUNI_ALPHA, /* U+000947: DEVANAGARI VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+000948: DEVANAGARI VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+000949: DEVANAGARI VOWEL SIGN CANDRA O*/
+ RTUNI_ALPHA, /* U+00094a: DEVANAGARI VOWEL SIGN SHORT O*/
+ RTUNI_ALPHA, /* U+00094b: DEVANAGARI VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+00094c: DEVANAGARI VOWEL SIGN AU*/
+ 0, /* U+00094d: DEVANAGARI SIGN VIRAMA*/
+ RTUNI_ALPHA, /* U+00094e: DEVANAGARI VOWEL SIGN PRISHTHAMATRA E*/
+ RTUNI_ALPHA, /* U+00094f: DEVANAGARI VOWEL SIGN AW*/
+ RTUNI_ALPHA, /* U+000950: DEVANAGARI OM*/
+ 0, /* U+000951: DEVANAGARI STRESS SIGN UDATTA*/
+ 0, /* U+000952: DEVANAGARI STRESS SIGN ANUDATTA*/
+ 0, /* U+000953: DEVANAGARI GRAVE ACCENT*/
+ 0, /* U+000954: DEVANAGARI ACUTE ACCENT*/
+ RTUNI_ALPHA, /* U+000955: DEVANAGARI VOWEL SIGN CANDRA LONG E*/
+ RTUNI_ALPHA, /* U+000956: DEVANAGARI VOWEL SIGN UE*/
+ RTUNI_ALPHA, /* U+000957: DEVANAGARI VOWEL SIGN UUE*/
+ RTUNI_ALPHA, /* U+000958: DEVANAGARI LETTER QA*/
+ RTUNI_ALPHA, /* U+000959: DEVANAGARI LETTER KHHA*/
+ RTUNI_ALPHA, /* U+00095a: DEVANAGARI LETTER GHHA*/
+ RTUNI_ALPHA, /* U+00095b: DEVANAGARI LETTER ZA*/
+ RTUNI_ALPHA, /* U+00095c: DEVANAGARI LETTER DDDHA*/
+ RTUNI_ALPHA, /* U+00095d: DEVANAGARI LETTER RHA*/
+ RTUNI_ALPHA, /* U+00095e: DEVANAGARI LETTER FA*/
+ RTUNI_ALPHA, /* U+00095f: DEVANAGARI LETTER YYA*/
+ RTUNI_ALPHA, /* U+000960: DEVANAGARI LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+000961: DEVANAGARI LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+000962: DEVANAGARI VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+000963: DEVANAGARI VOWEL SIGN VOCALIC LL*/
+ 0, /* U+000964: DEVANAGARI DANDA*/
+ 0, /* U+000965: DEVANAGARI DOUBLE DANDA*/
+ RTUNI_DDIGIT, /* U+000966: DEVANAGARI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000967: DEVANAGARI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000968: DEVANAGARI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000969: DEVANAGARI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+00096a: DEVANAGARI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+00096b: DEVANAGARI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+00096c: DEVANAGARI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+00096d: DEVANAGARI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+00096e: DEVANAGARI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+00096f: DEVANAGARI DIGIT NINE*/
+ 0, /* U+000970: DEVANAGARI ABBREVIATION SIGN*/
+ RTUNI_ALPHA, /* U+000971: DEVANAGARI SIGN HIGH SPACING DOT*/
+ RTUNI_ALPHA, /* U+000972: DEVANAGARI LETTER CANDRA A*/
+ RTUNI_ALPHA, /* U+000973: DEVANAGARI LETTER OE*/
+ RTUNI_ALPHA, /* U+000974: DEVANAGARI LETTER OOE*/
+ RTUNI_ALPHA, /* U+000975: DEVANAGARI LETTER AW*/
+ RTUNI_ALPHA, /* U+000976: DEVANAGARI LETTER UE*/
+ RTUNI_ALPHA, /* U+000977: DEVANAGARI LETTER UUE*/
+ 0, /* U+000978: */
+ RTUNI_ALPHA, /* U+000979: DEVANAGARI LETTER ZHA*/
+ RTUNI_ALPHA, /* U+00097a: DEVANAGARI LETTER HEAVY YA*/
+ RTUNI_ALPHA, /* U+00097b: DEVANAGARI LETTER GGA*/
+ RTUNI_ALPHA, /* U+00097c: DEVANAGARI LETTER JJA*/
+ RTUNI_ALPHA, /* U+00097d: DEVANAGARI LETTER GLOTTAL STOP*/
+ RTUNI_ALPHA, /* U+00097e: DEVANAGARI LETTER DDDA*/
+ RTUNI_ALPHA, /* U+00097f: DEVANAGARI LETTER BBA*/
+ 0, /* U+000980: */
+ RTUNI_ALPHA, /* U+000981: BENGALI SIGN CANDRABINDU*/
+ RTUNI_ALPHA, /* U+000982: BENGALI SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+000983: BENGALI SIGN VISARGA*/
+ 0, /* U+000984: */
+ RTUNI_ALPHA, /* U+000985: BENGALI LETTER A*/
+ RTUNI_ALPHA, /* U+000986: BENGALI LETTER AA*/
+ RTUNI_ALPHA, /* U+000987: BENGALI LETTER I*/
+ RTUNI_ALPHA, /* U+000988: BENGALI LETTER II*/
+ RTUNI_ALPHA, /* U+000989: BENGALI LETTER U*/
+ RTUNI_ALPHA, /* U+00098a: BENGALI LETTER UU*/
+ RTUNI_ALPHA, /* U+00098b: BENGALI LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+00098c: BENGALI LETTER VOCALIC L*/
+ 0, /* U+00098d: */
+ 0, /* U+00098e: */
+ RTUNI_ALPHA, /* U+00098f: BENGALI LETTER E*/
+ RTUNI_ALPHA, /* U+000990: BENGALI LETTER AI*/
+ 0, /* U+000991: */
+ 0, /* U+000992: */
+ RTUNI_ALPHA, /* U+000993: BENGALI LETTER O*/
+ RTUNI_ALPHA, /* U+000994: BENGALI LETTER AU*/
+ RTUNI_ALPHA, /* U+000995: BENGALI LETTER KA*/
+ RTUNI_ALPHA, /* U+000996: BENGALI LETTER KHA*/
+ RTUNI_ALPHA, /* U+000997: BENGALI LETTER GA*/
+ RTUNI_ALPHA, /* U+000998: BENGALI LETTER GHA*/
+ RTUNI_ALPHA, /* U+000999: BENGALI LETTER NGA*/
+ RTUNI_ALPHA, /* U+00099a: BENGALI LETTER CA*/
+ RTUNI_ALPHA, /* U+00099b: BENGALI LETTER CHA*/
+ RTUNI_ALPHA, /* U+00099c: BENGALI LETTER JA*/
+ RTUNI_ALPHA, /* U+00099d: BENGALI LETTER JHA*/
+ RTUNI_ALPHA, /* U+00099e: BENGALI LETTER NYA*/
+ RTUNI_ALPHA, /* U+00099f: BENGALI LETTER TTA*/
+ RTUNI_ALPHA, /* U+0009a0: BENGALI LETTER TTHA*/
+ RTUNI_ALPHA, /* U+0009a1: BENGALI LETTER DDA*/
+ RTUNI_ALPHA, /* U+0009a2: BENGALI LETTER DDHA*/
+ RTUNI_ALPHA, /* U+0009a3: BENGALI LETTER NNA*/
+ RTUNI_ALPHA, /* U+0009a4: BENGALI LETTER TA*/
+ RTUNI_ALPHA, /* U+0009a5: BENGALI LETTER THA*/
+ RTUNI_ALPHA, /* U+0009a6: BENGALI LETTER DA*/
+ RTUNI_ALPHA, /* U+0009a7: BENGALI LETTER DHA*/
+ RTUNI_ALPHA, /* U+0009a8: BENGALI LETTER NA*/
+ 0, /* U+0009a9: */
+ RTUNI_ALPHA, /* U+0009aa: BENGALI LETTER PA*/
+ RTUNI_ALPHA, /* U+0009ab: BENGALI LETTER PHA*/
+ RTUNI_ALPHA, /* U+0009ac: BENGALI LETTER BA*/
+ RTUNI_ALPHA, /* U+0009ad: BENGALI LETTER BHA*/
+ RTUNI_ALPHA, /* U+0009ae: BENGALI LETTER MA*/
+ RTUNI_ALPHA, /* U+0009af: BENGALI LETTER YA*/
+ RTUNI_ALPHA, /* U+0009b0: BENGALI LETTER RA*/
+ 0, /* U+0009b1: */
+ RTUNI_ALPHA, /* U+0009b2: BENGALI LETTER LA*/
+ 0, /* U+0009b3: */
+ 0, /* U+0009b4: */
+ 0, /* U+0009b5: */
+ RTUNI_ALPHA, /* U+0009b6: BENGALI LETTER SHA*/
+ RTUNI_ALPHA, /* U+0009b7: BENGALI LETTER SSA*/
+ RTUNI_ALPHA, /* U+0009b8: BENGALI LETTER SA*/
+ RTUNI_ALPHA, /* U+0009b9: BENGALI LETTER HA*/
+ 0, /* U+0009ba: */
+ 0, /* U+0009bb: */
+ 0, /* U+0009bc: BENGALI SIGN NUKTA*/
+ RTUNI_ALPHA, /* U+0009bd: BENGALI SIGN AVAGRAHA*/
+ RTUNI_ALPHA, /* U+0009be: BENGALI VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+0009bf: BENGALI VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+0009c0: BENGALI VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+0009c1: BENGALI VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+0009c2: BENGALI VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+0009c3: BENGALI VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+0009c4: BENGALI VOWEL SIGN VOCALIC RR*/
+ 0, /* U+0009c5: */
+ 0, /* U+0009c6: */
+ RTUNI_ALPHA, /* U+0009c7: BENGALI VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+0009c8: BENGALI VOWEL SIGN AI*/
+ 0, /* U+0009c9: */
+ 0, /* U+0009ca: */
+ RTUNI_ALPHA, /* U+0009cb: BENGALI VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+0009cc: BENGALI VOWEL SIGN AU*/
+ 0, /* U+0009cd: BENGALI SIGN VIRAMA*/
+ RTUNI_ALPHA, /* U+0009ce: BENGALI LETTER KHANDA TA*/
+ 0, /* U+0009cf: */
+ 0, /* U+0009d0: */
+ 0, /* U+0009d1: */
+ 0, /* U+0009d2: */
+ 0, /* U+0009d3: */
+ 0, /* U+0009d4: */
+ 0, /* U+0009d5: */
+ 0, /* U+0009d6: */
+ RTUNI_ALPHA, /* U+0009d7: BENGALI AU LENGTH MARK*/
+ 0, /* U+0009d8: */
+ 0, /* U+0009d9: */
+ 0, /* U+0009da: */
+ 0, /* U+0009db: */
+ RTUNI_ALPHA, /* U+0009dc: BENGALI LETTER RRA*/
+ RTUNI_ALPHA, /* U+0009dd: BENGALI LETTER RHA*/
+ 0, /* U+0009de: */
+ RTUNI_ALPHA, /* U+0009df: BENGALI LETTER YYA*/
+ RTUNI_ALPHA, /* U+0009e0: BENGALI LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+0009e1: BENGALI LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+0009e2: BENGALI VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+0009e3: BENGALI VOWEL SIGN VOCALIC LL*/
+ 0, /* U+0009e4: */
+ 0, /* U+0009e5: */
+ RTUNI_DDIGIT, /* U+0009e6: BENGALI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+0009e7: BENGALI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+0009e8: BENGALI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+0009e9: BENGALI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+0009ea: BENGALI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+0009eb: BENGALI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+0009ec: BENGALI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+0009ed: BENGALI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+0009ee: BENGALI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+0009ef: BENGALI DIGIT NINE*/
+ RTUNI_ALPHA, /* U+0009f0: BENGALI LETTER RA WITH MIDDLE DIAGONAL*/
+ RTUNI_ALPHA, /* U+0009f1: BENGALI LETTER RA WITH LOWER DIAGONAL*/
+ 0, /* U+0009f2: BENGALI RUPEE MARK*/
+ 0, /* U+0009f3: BENGALI RUPEE SIGN*/
+ 0, /* U+0009f4: BENGALI CURRENCY NUMERATOR ONE*/
+ 0, /* U+0009f5: BENGALI CURRENCY NUMERATOR TWO*/
+ 0, /* U+0009f6: BENGALI CURRENCY NUMERATOR THREE*/
+ 0, /* U+0009f7: BENGALI CURRENCY NUMERATOR FOUR*/
+ 0, /* U+0009f8: BENGALI CURRENCY NUMERATOR ONE LESS THAN THE DENOMINATOR*/
+ 0, /* U+0009f9: BENGALI CURRENCY DENOMINATOR SIXTEEN*/
+ 0, /* U+0009fa: BENGALI ISSHAR*/
+ 0, /* U+0009fb: BENGALI GANDA MARK*/
+ 0, /* U+0009fc: */
+ 0, /* U+0009fd: */
+ 0, /* U+0009fe: */
+ 0, /* U+0009ff: */
+ 0, /* U+000a00: */
+ RTUNI_ALPHA, /* U+000a01: GURMUKHI SIGN ADAK BINDI*/
+ RTUNI_ALPHA, /* U+000a02: GURMUKHI SIGN BINDI*/
+ RTUNI_ALPHA, /* U+000a03: GURMUKHI SIGN VISARGA*/
+ 0, /* U+000a04: */
+ RTUNI_ALPHA, /* U+000a05: GURMUKHI LETTER A*/
+ RTUNI_ALPHA, /* U+000a06: GURMUKHI LETTER AA*/
+ RTUNI_ALPHA, /* U+000a07: GURMUKHI LETTER I*/
+ RTUNI_ALPHA, /* U+000a08: GURMUKHI LETTER II*/
+ RTUNI_ALPHA, /* U+000a09: GURMUKHI LETTER U*/
+ RTUNI_ALPHA, /* U+000a0a: GURMUKHI LETTER UU*/
+ 0, /* U+000a0b: */
+ 0, /* U+000a0c: */
+ 0, /* U+000a0d: */
+ 0, /* U+000a0e: */
+ RTUNI_ALPHA, /* U+000a0f: GURMUKHI LETTER EE*/
+ RTUNI_ALPHA, /* U+000a10: GURMUKHI LETTER AI*/
+ 0, /* U+000a11: */
+ 0, /* U+000a12: */
+ RTUNI_ALPHA, /* U+000a13: GURMUKHI LETTER OO*/
+ RTUNI_ALPHA, /* U+000a14: GURMUKHI LETTER AU*/
+ RTUNI_ALPHA, /* U+000a15: GURMUKHI LETTER KA*/
+ RTUNI_ALPHA, /* U+000a16: GURMUKHI LETTER KHA*/
+ RTUNI_ALPHA, /* U+000a17: GURMUKHI LETTER GA*/
+ RTUNI_ALPHA, /* U+000a18: GURMUKHI LETTER GHA*/
+ RTUNI_ALPHA, /* U+000a19: GURMUKHI LETTER NGA*/
+ RTUNI_ALPHA, /* U+000a1a: GURMUKHI LETTER CA*/
+ RTUNI_ALPHA, /* U+000a1b: GURMUKHI LETTER CHA*/
+ RTUNI_ALPHA, /* U+000a1c: GURMUKHI LETTER JA*/
+ RTUNI_ALPHA, /* U+000a1d: GURMUKHI LETTER JHA*/
+ RTUNI_ALPHA, /* U+000a1e: GURMUKHI LETTER NYA*/
+ RTUNI_ALPHA, /* U+000a1f: GURMUKHI LETTER TTA*/
+ RTUNI_ALPHA, /* U+000a20: GURMUKHI LETTER TTHA*/
+ RTUNI_ALPHA, /* U+000a21: GURMUKHI LETTER DDA*/
+ RTUNI_ALPHA, /* U+000a22: GURMUKHI LETTER DDHA*/
+ RTUNI_ALPHA, /* U+000a23: GURMUKHI LETTER NNA*/
+ RTUNI_ALPHA, /* U+000a24: GURMUKHI LETTER TA*/
+ RTUNI_ALPHA, /* U+000a25: GURMUKHI LETTER THA*/
+ RTUNI_ALPHA, /* U+000a26: GURMUKHI LETTER DA*/
+ RTUNI_ALPHA, /* U+000a27: GURMUKHI LETTER DHA*/
+ RTUNI_ALPHA, /* U+000a28: GURMUKHI LETTER NA*/
+ 0, /* U+000a29: */
+ RTUNI_ALPHA, /* U+000a2a: GURMUKHI LETTER PA*/
+ RTUNI_ALPHA, /* U+000a2b: GURMUKHI LETTER PHA*/
+ RTUNI_ALPHA, /* U+000a2c: GURMUKHI LETTER BA*/
+ RTUNI_ALPHA, /* U+000a2d: GURMUKHI LETTER BHA*/
+ RTUNI_ALPHA, /* U+000a2e: GURMUKHI LETTER MA*/
+ RTUNI_ALPHA, /* U+000a2f: GURMUKHI LETTER YA*/
+ RTUNI_ALPHA, /* U+000a30: GURMUKHI LETTER RA*/
+ 0, /* U+000a31: */
+ RTUNI_ALPHA, /* U+000a32: GURMUKHI LETTER LA*/
+ RTUNI_ALPHA, /* U+000a33: GURMUKHI LETTER LLA*/
+ 0, /* U+000a34: */
+ RTUNI_ALPHA, /* U+000a35: GURMUKHI LETTER VA*/
+ RTUNI_ALPHA, /* U+000a36: GURMUKHI LETTER SHA*/
+ 0, /* U+000a37: */
+ RTUNI_ALPHA, /* U+000a38: GURMUKHI LETTER SA*/
+ RTUNI_ALPHA, /* U+000a39: GURMUKHI LETTER HA*/
+ 0, /* U+000a3a: */
+ 0, /* U+000a3b: */
+ 0, /* U+000a3c: GURMUKHI SIGN NUKTA*/
+ 0, /* U+000a3d: */
+ RTUNI_ALPHA, /* U+000a3e: GURMUKHI VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000a3f: GURMUKHI VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000a40: GURMUKHI VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000a41: GURMUKHI VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000a42: GURMUKHI VOWEL SIGN UU*/
+ 0, /* U+000a43: */
+ 0, /* U+000a44: */
+ 0, /* U+000a45: */
+ 0, /* U+000a46: */
+ RTUNI_ALPHA, /* U+000a47: GURMUKHI VOWEL SIGN EE*/
+ RTUNI_ALPHA, /* U+000a48: GURMUKHI VOWEL SIGN AI*/
+ 0, /* U+000a49: */
+ 0, /* U+000a4a: */
+ RTUNI_ALPHA, /* U+000a4b: GURMUKHI VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+000a4c: GURMUKHI VOWEL SIGN AU*/
+ 0, /* U+000a4d: GURMUKHI SIGN VIRAMA*/
+ 0, /* U+000a4e: */
+ 0, /* U+000a4f: */
+ 0, /* U+000a50: */
+ RTUNI_ALPHA, /* U+000a51: GURMUKHI SIGN UDAAT*/
+ 0, /* U+000a52: */
+ 0, /* U+000a53: */
+ 0, /* U+000a54: */
+ 0, /* U+000a55: */
+ 0, /* U+000a56: */
+ 0, /* U+000a57: */
+ 0, /* U+000a58: */
+ RTUNI_ALPHA, /* U+000a59: GURMUKHI LETTER KHHA*/
+ RTUNI_ALPHA, /* U+000a5a: GURMUKHI LETTER GHHA*/
+ RTUNI_ALPHA, /* U+000a5b: GURMUKHI LETTER ZA*/
+ RTUNI_ALPHA, /* U+000a5c: GURMUKHI LETTER RRA*/
+ 0, /* U+000a5d: */
+ RTUNI_ALPHA, /* U+000a5e: GURMUKHI LETTER FA*/
+ 0, /* U+000a5f: */
+ 0, /* U+000a60: */
+ 0, /* U+000a61: */
+ 0, /* U+000a62: */
+ 0, /* U+000a63: */
+ 0, /* U+000a64: */
+ 0, /* U+000a65: */
+ RTUNI_DDIGIT, /* U+000a66: GURMUKHI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000a67: GURMUKHI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000a68: GURMUKHI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000a69: GURMUKHI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000a6a: GURMUKHI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000a6b: GURMUKHI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000a6c: GURMUKHI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000a6d: GURMUKHI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000a6e: GURMUKHI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000a6f: GURMUKHI DIGIT NINE*/
+ RTUNI_ALPHA, /* U+000a70: GURMUKHI TIPPI*/
+ RTUNI_ALPHA, /* U+000a71: GURMUKHI ADDAK*/
+ RTUNI_ALPHA, /* U+000a72: GURMUKHI IRI*/
+ RTUNI_ALPHA, /* U+000a73: GURMUKHI URA*/
+ RTUNI_ALPHA, /* U+000a74: GURMUKHI EK ONKAR*/
+ RTUNI_ALPHA, /* U+000a75: GURMUKHI SIGN YAKASH*/
+ 0, /* U+000a76: */
+ 0, /* U+000a77: */
+ 0, /* U+000a78: */
+ 0, /* U+000a79: */
+ 0, /* U+000a7a: */
+ 0, /* U+000a7b: */
+ 0, /* U+000a7c: */
+ 0, /* U+000a7d: */
+ 0, /* U+000a7e: */
+ 0, /* U+000a7f: */
+ 0, /* U+000a80: */
+ RTUNI_ALPHA, /* U+000a81: GUJARATI SIGN CANDRABINDU*/
+ RTUNI_ALPHA, /* U+000a82: GUJARATI SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+000a83: GUJARATI SIGN VISARGA*/
+ 0, /* U+000a84: */
+ RTUNI_ALPHA, /* U+000a85: GUJARATI LETTER A*/
+ RTUNI_ALPHA, /* U+000a86: GUJARATI LETTER AA*/
+ RTUNI_ALPHA, /* U+000a87: GUJARATI LETTER I*/
+ RTUNI_ALPHA, /* U+000a88: GUJARATI LETTER II*/
+ RTUNI_ALPHA, /* U+000a89: GUJARATI LETTER U*/
+ RTUNI_ALPHA, /* U+000a8a: GUJARATI LETTER UU*/
+ RTUNI_ALPHA, /* U+000a8b: GUJARATI LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+000a8c: GUJARATI LETTER VOCALIC L*/
+ RTUNI_ALPHA, /* U+000a8d: GUJARATI VOWEL CANDRA E*/
+ 0, /* U+000a8e: */
+ RTUNI_ALPHA, /* U+000a8f: GUJARATI LETTER E*/
+ RTUNI_ALPHA, /* U+000a90: GUJARATI LETTER AI*/
+ RTUNI_ALPHA, /* U+000a91: GUJARATI VOWEL CANDRA O*/
+ 0, /* U+000a92: */
+ RTUNI_ALPHA, /* U+000a93: GUJARATI LETTER O*/
+ RTUNI_ALPHA, /* U+000a94: GUJARATI LETTER AU*/
+ RTUNI_ALPHA, /* U+000a95: GUJARATI LETTER KA*/
+ RTUNI_ALPHA, /* U+000a96: GUJARATI LETTER KHA*/
+ RTUNI_ALPHA, /* U+000a97: GUJARATI LETTER GA*/
+ RTUNI_ALPHA, /* U+000a98: GUJARATI LETTER GHA*/
+ RTUNI_ALPHA, /* U+000a99: GUJARATI LETTER NGA*/
+ RTUNI_ALPHA, /* U+000a9a: GUJARATI LETTER CA*/
+ RTUNI_ALPHA, /* U+000a9b: GUJARATI LETTER CHA*/
+ RTUNI_ALPHA, /* U+000a9c: GUJARATI LETTER JA*/
+ RTUNI_ALPHA, /* U+000a9d: GUJARATI LETTER JHA*/
+ RTUNI_ALPHA, /* U+000a9e: GUJARATI LETTER NYA*/
+ RTUNI_ALPHA, /* U+000a9f: GUJARATI LETTER TTA*/
+ RTUNI_ALPHA, /* U+000aa0: GUJARATI LETTER TTHA*/
+ RTUNI_ALPHA, /* U+000aa1: GUJARATI LETTER DDA*/
+ RTUNI_ALPHA, /* U+000aa2: GUJARATI LETTER DDHA*/
+ RTUNI_ALPHA, /* U+000aa3: GUJARATI LETTER NNA*/
+ RTUNI_ALPHA, /* U+000aa4: GUJARATI LETTER TA*/
+ RTUNI_ALPHA, /* U+000aa5: GUJARATI LETTER THA*/
+ RTUNI_ALPHA, /* U+000aa6: GUJARATI LETTER DA*/
+ RTUNI_ALPHA, /* U+000aa7: GUJARATI LETTER DHA*/
+ RTUNI_ALPHA, /* U+000aa8: GUJARATI LETTER NA*/
+ 0, /* U+000aa9: */
+ RTUNI_ALPHA, /* U+000aaa: GUJARATI LETTER PA*/
+ RTUNI_ALPHA, /* U+000aab: GUJARATI LETTER PHA*/
+ RTUNI_ALPHA, /* U+000aac: GUJARATI LETTER BA*/
+ RTUNI_ALPHA, /* U+000aad: GUJARATI LETTER BHA*/
+ RTUNI_ALPHA, /* U+000aae: GUJARATI LETTER MA*/
+ RTUNI_ALPHA, /* U+000aaf: GUJARATI LETTER YA*/
+ RTUNI_ALPHA, /* U+000ab0: GUJARATI LETTER RA*/
+ 0, /* U+000ab1: */
+ RTUNI_ALPHA, /* U+000ab2: GUJARATI LETTER LA*/
+ RTUNI_ALPHA, /* U+000ab3: GUJARATI LETTER LLA*/
+ 0, /* U+000ab4: */
+ RTUNI_ALPHA, /* U+000ab5: GUJARATI LETTER VA*/
+ RTUNI_ALPHA, /* U+000ab6: GUJARATI LETTER SHA*/
+ RTUNI_ALPHA, /* U+000ab7: GUJARATI LETTER SSA*/
+ RTUNI_ALPHA, /* U+000ab8: GUJARATI LETTER SA*/
+ RTUNI_ALPHA, /* U+000ab9: GUJARATI LETTER HA*/
+ 0, /* U+000aba: */
+ 0, /* U+000abb: */
+ 0, /* U+000abc: GUJARATI SIGN NUKTA*/
+ RTUNI_ALPHA, /* U+000abd: GUJARATI SIGN AVAGRAHA*/
+ RTUNI_ALPHA, /* U+000abe: GUJARATI VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000abf: GUJARATI VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000ac0: GUJARATI VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000ac1: GUJARATI VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000ac2: GUJARATI VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+000ac3: GUJARATI VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+000ac4: GUJARATI VOWEL SIGN VOCALIC RR*/
+ RTUNI_ALPHA, /* U+000ac5: GUJARATI VOWEL SIGN CANDRA E*/
+ 0, /* U+000ac6: */
+ RTUNI_ALPHA, /* U+000ac7: GUJARATI VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+000ac8: GUJARATI VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+000ac9: GUJARATI VOWEL SIGN CANDRA O*/
+ 0, /* U+000aca: */
+ RTUNI_ALPHA, /* U+000acb: GUJARATI VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+000acc: GUJARATI VOWEL SIGN AU*/
+ 0, /* U+000acd: GUJARATI SIGN VIRAMA*/
+ 0, /* U+000ace: */
+ 0, /* U+000acf: */
+ RTUNI_ALPHA, /* U+000ad0: GUJARATI OM*/
+ 0, /* U+000ad1: */
+ 0, /* U+000ad2: */
+ 0, /* U+000ad3: */
+ 0, /* U+000ad4: */
+ 0, /* U+000ad5: */
+ 0, /* U+000ad6: */
+ 0, /* U+000ad7: */
+ 0, /* U+000ad8: */
+ 0, /* U+000ad9: */
+ 0, /* U+000ada: */
+ 0, /* U+000adb: */
+ 0, /* U+000adc: */
+ 0, /* U+000add: */
+ 0, /* U+000ade: */
+ 0, /* U+000adf: */
+ RTUNI_ALPHA, /* U+000ae0: GUJARATI LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+000ae1: GUJARATI LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+000ae2: GUJARATI VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+000ae3: GUJARATI VOWEL SIGN VOCALIC LL*/
+ 0, /* U+000ae4: */
+ 0, /* U+000ae5: */
+ RTUNI_DDIGIT, /* U+000ae6: GUJARATI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000ae7: GUJARATI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000ae8: GUJARATI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000ae9: GUJARATI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000aea: GUJARATI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000aeb: GUJARATI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000aec: GUJARATI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000aed: GUJARATI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000aee: GUJARATI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000aef: GUJARATI DIGIT NINE*/
+ 0, /* U+000af0: GUJARATI ABBREVIATION SIGN*/
+ 0, /* U+000af1: GUJARATI RUPEE SIGN*/
+ 0, /* U+000af2: */
+ 0, /* U+000af3: */
+ 0, /* U+000af4: */
+ 0, /* U+000af5: */
+ 0, /* U+000af6: */
+ 0, /* U+000af7: */
+ 0, /* U+000af8: */
+ 0, /* U+000af9: */
+ 0, /* U+000afa: */
+ 0, /* U+000afb: */
+ 0, /* U+000afc: */
+ 0, /* U+000afd: */
+ 0, /* U+000afe: */
+ 0, /* U+000aff: */
+ 0, /* U+000b00: */
+ RTUNI_ALPHA, /* U+000b01: ORIYA SIGN CANDRABINDU*/
+ RTUNI_ALPHA, /* U+000b02: ORIYA SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+000b03: ORIYA SIGN VISARGA*/
+ 0, /* U+000b04: */
+ RTUNI_ALPHA, /* U+000b05: ORIYA LETTER A*/
+ RTUNI_ALPHA, /* U+000b06: ORIYA LETTER AA*/
+ RTUNI_ALPHA, /* U+000b07: ORIYA LETTER I*/
+ RTUNI_ALPHA, /* U+000b08: ORIYA LETTER II*/
+ RTUNI_ALPHA, /* U+000b09: ORIYA LETTER U*/
+ RTUNI_ALPHA, /* U+000b0a: ORIYA LETTER UU*/
+ RTUNI_ALPHA, /* U+000b0b: ORIYA LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+000b0c: ORIYA LETTER VOCALIC L*/
+ 0, /* U+000b0d: */
+ 0, /* U+000b0e: */
+ RTUNI_ALPHA, /* U+000b0f: ORIYA LETTER E*/
+ RTUNI_ALPHA, /* U+000b10: ORIYA LETTER AI*/
+ 0, /* U+000b11: */
+ 0, /* U+000b12: */
+ RTUNI_ALPHA, /* U+000b13: ORIYA LETTER O*/
+ RTUNI_ALPHA, /* U+000b14: ORIYA LETTER AU*/
+ RTUNI_ALPHA, /* U+000b15: ORIYA LETTER KA*/
+ RTUNI_ALPHA, /* U+000b16: ORIYA LETTER KHA*/
+ RTUNI_ALPHA, /* U+000b17: ORIYA LETTER GA*/
+ RTUNI_ALPHA, /* U+000b18: ORIYA LETTER GHA*/
+ RTUNI_ALPHA, /* U+000b19: ORIYA LETTER NGA*/
+ RTUNI_ALPHA, /* U+000b1a: ORIYA LETTER CA*/
+ RTUNI_ALPHA, /* U+000b1b: ORIYA LETTER CHA*/
+ RTUNI_ALPHA, /* U+000b1c: ORIYA LETTER JA*/
+ RTUNI_ALPHA, /* U+000b1d: ORIYA LETTER JHA*/
+ RTUNI_ALPHA, /* U+000b1e: ORIYA LETTER NYA*/
+ RTUNI_ALPHA, /* U+000b1f: ORIYA LETTER TTA*/
+ RTUNI_ALPHA, /* U+000b20: ORIYA LETTER TTHA*/
+ RTUNI_ALPHA, /* U+000b21: ORIYA LETTER DDA*/
+ RTUNI_ALPHA, /* U+000b22: ORIYA LETTER DDHA*/
+ RTUNI_ALPHA, /* U+000b23: ORIYA LETTER NNA*/
+ RTUNI_ALPHA, /* U+000b24: ORIYA LETTER TA*/
+ RTUNI_ALPHA, /* U+000b25: ORIYA LETTER THA*/
+ RTUNI_ALPHA, /* U+000b26: ORIYA LETTER DA*/
+ RTUNI_ALPHA, /* U+000b27: ORIYA LETTER DHA*/
+ RTUNI_ALPHA, /* U+000b28: ORIYA LETTER NA*/
+ 0, /* U+000b29: */
+ RTUNI_ALPHA, /* U+000b2a: ORIYA LETTER PA*/
+ RTUNI_ALPHA, /* U+000b2b: ORIYA LETTER PHA*/
+ RTUNI_ALPHA, /* U+000b2c: ORIYA LETTER BA*/
+ RTUNI_ALPHA, /* U+000b2d: ORIYA LETTER BHA*/
+ RTUNI_ALPHA, /* U+000b2e: ORIYA LETTER MA*/
+ RTUNI_ALPHA, /* U+000b2f: ORIYA LETTER YA*/
+ RTUNI_ALPHA, /* U+000b30: ORIYA LETTER RA*/
+ 0, /* U+000b31: */
+ RTUNI_ALPHA, /* U+000b32: ORIYA LETTER LA*/
+ RTUNI_ALPHA, /* U+000b33: ORIYA LETTER LLA*/
+ 0, /* U+000b34: */
+ RTUNI_ALPHA, /* U+000b35: ORIYA LETTER VA*/
+ RTUNI_ALPHA, /* U+000b36: ORIYA LETTER SHA*/
+ RTUNI_ALPHA, /* U+000b37: ORIYA LETTER SSA*/
+ RTUNI_ALPHA, /* U+000b38: ORIYA LETTER SA*/
+ RTUNI_ALPHA, /* U+000b39: ORIYA LETTER HA*/
+ 0, /* U+000b3a: */
+ 0, /* U+000b3b: */
+ 0, /* U+000b3c: ORIYA SIGN NUKTA*/
+ RTUNI_ALPHA, /* U+000b3d: ORIYA SIGN AVAGRAHA*/
+ RTUNI_ALPHA, /* U+000b3e: ORIYA VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000b3f: ORIYA VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000b40: ORIYA VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000b41: ORIYA VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000b42: ORIYA VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+000b43: ORIYA VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+000b44: ORIYA VOWEL SIGN VOCALIC RR*/
+ 0, /* U+000b45: */
+ 0, /* U+000b46: */
+ RTUNI_ALPHA, /* U+000b47: ORIYA VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+000b48: ORIYA VOWEL SIGN AI*/
+ 0, /* U+000b49: */
+ 0, /* U+000b4a: */
+ RTUNI_ALPHA, /* U+000b4b: ORIYA VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+000b4c: ORIYA VOWEL SIGN AU*/
+ 0, /* U+000b4d: ORIYA SIGN VIRAMA*/
+ 0, /* U+000b4e: */
+ 0, /* U+000b4f: */
+ 0, /* U+000b50: */
+ 0, /* U+000b51: */
+ 0, /* U+000b52: */
+ 0, /* U+000b53: */
+ 0, /* U+000b54: */
+ 0, /* U+000b55: */
+ RTUNI_ALPHA, /* U+000b56: ORIYA AI LENGTH MARK*/
+ RTUNI_ALPHA, /* U+000b57: ORIYA AU LENGTH MARK*/
+ 0, /* U+000b58: */
+ 0, /* U+000b59: */
+ 0, /* U+000b5a: */
+ 0, /* U+000b5b: */
+ RTUNI_ALPHA, /* U+000b5c: ORIYA LETTER RRA*/
+ RTUNI_ALPHA, /* U+000b5d: ORIYA LETTER RHA*/
+ 0, /* U+000b5e: */
+ RTUNI_ALPHA, /* U+000b5f: ORIYA LETTER YYA*/
+ RTUNI_ALPHA, /* U+000b60: ORIYA LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+000b61: ORIYA LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+000b62: ORIYA VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+000b63: ORIYA VOWEL SIGN VOCALIC LL*/
+ 0, /* U+000b64: */
+ 0, /* U+000b65: */
+ RTUNI_DDIGIT, /* U+000b66: ORIYA DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000b67: ORIYA DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000b68: ORIYA DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000b69: ORIYA DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000b6a: ORIYA DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000b6b: ORIYA DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000b6c: ORIYA DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000b6d: ORIYA DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000b6e: ORIYA DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000b6f: ORIYA DIGIT NINE*/
+ 0, /* U+000b70: ORIYA ISSHAR*/
+ RTUNI_ALPHA, /* U+000b71: ORIYA LETTER WA*/
+ 0, /* U+000b72: ORIYA FRACTION ONE QUARTER*/
+ 0, /* U+000b73: ORIYA FRACTION ONE HALF*/
+ 0, /* U+000b74: ORIYA FRACTION THREE QUARTERS*/
+ 0, /* U+000b75: ORIYA FRACTION ONE SIXTEENTH*/
+ 0, /* U+000b76: ORIYA FRACTION ONE EIGHTH*/
+ 0, /* U+000b77: ORIYA FRACTION THREE SIXTEENTHS*/
+ 0, /* U+000b78: */
+ 0, /* U+000b79: */
+ 0, /* U+000b7a: */
+ 0, /* U+000b7b: */
+ 0, /* U+000b7c: */
+ 0, /* U+000b7d: */
+ 0, /* U+000b7e: */
+ 0, /* U+000b7f: */
+ 0, /* U+000b80: */
+ 0, /* U+000b81: */
+ RTUNI_ALPHA, /* U+000b82: TAMIL SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+000b83: TAMIL SIGN VISARGA*/
+ 0, /* U+000b84: */
+ RTUNI_ALPHA, /* U+000b85: TAMIL LETTER A*/
+ RTUNI_ALPHA, /* U+000b86: TAMIL LETTER AA*/
+ RTUNI_ALPHA, /* U+000b87: TAMIL LETTER I*/
+ RTUNI_ALPHA, /* U+000b88: TAMIL LETTER II*/
+ RTUNI_ALPHA, /* U+000b89: TAMIL LETTER U*/
+ RTUNI_ALPHA, /* U+000b8a: TAMIL LETTER UU*/
+ 0, /* U+000b8b: */
+ 0, /* U+000b8c: */
+ 0, /* U+000b8d: */
+ RTUNI_ALPHA, /* U+000b8e: TAMIL LETTER E*/
+ RTUNI_ALPHA, /* U+000b8f: TAMIL LETTER EE*/
+ RTUNI_ALPHA, /* U+000b90: TAMIL LETTER AI*/
+ 0, /* U+000b91: */
+ RTUNI_ALPHA, /* U+000b92: TAMIL LETTER O*/
+ RTUNI_ALPHA, /* U+000b93: TAMIL LETTER OO*/
+ RTUNI_ALPHA, /* U+000b94: TAMIL LETTER AU*/
+ RTUNI_ALPHA, /* U+000b95: TAMIL LETTER KA*/
+ 0, /* U+000b96: */
+ 0, /* U+000b97: */
+ 0, /* U+000b98: */
+ RTUNI_ALPHA, /* U+000b99: TAMIL LETTER NGA*/
+ RTUNI_ALPHA, /* U+000b9a: TAMIL LETTER CA*/
+ 0, /* U+000b9b: */
+ RTUNI_ALPHA, /* U+000b9c: TAMIL LETTER JA*/
+ 0, /* U+000b9d: */
+ RTUNI_ALPHA, /* U+000b9e: TAMIL LETTER NYA*/
+ RTUNI_ALPHA, /* U+000b9f: TAMIL LETTER TTA*/
+ 0, /* U+000ba0: */
+ 0, /* U+000ba1: */
+ 0, /* U+000ba2: */
+ RTUNI_ALPHA, /* U+000ba3: TAMIL LETTER NNA*/
+ RTUNI_ALPHA, /* U+000ba4: TAMIL LETTER TA*/
+ 0, /* U+000ba5: */
+ 0, /* U+000ba6: */
+ 0, /* U+000ba7: */
+ RTUNI_ALPHA, /* U+000ba8: TAMIL LETTER NA*/
+ RTUNI_ALPHA, /* U+000ba9: TAMIL LETTER NNNA*/
+ RTUNI_ALPHA, /* U+000baa: TAMIL LETTER PA*/
+ 0, /* U+000bab: */
+ 0, /* U+000bac: */
+ 0, /* U+000bad: */
+ RTUNI_ALPHA, /* U+000bae: TAMIL LETTER MA*/
+ RTUNI_ALPHA, /* U+000baf: TAMIL LETTER YA*/
+ RTUNI_ALPHA, /* U+000bb0: TAMIL LETTER RA*/
+ RTUNI_ALPHA, /* U+000bb1: TAMIL LETTER RRA*/
+ RTUNI_ALPHA, /* U+000bb2: TAMIL LETTER LA*/
+ RTUNI_ALPHA, /* U+000bb3: TAMIL LETTER LLA*/
+ RTUNI_ALPHA, /* U+000bb4: TAMIL LETTER LLLA*/
+ RTUNI_ALPHA, /* U+000bb5: TAMIL LETTER VA*/
+ RTUNI_ALPHA, /* U+000bb6: TAMIL LETTER SHA*/
+ RTUNI_ALPHA, /* U+000bb7: TAMIL LETTER SSA*/
+ RTUNI_ALPHA, /* U+000bb8: TAMIL LETTER SA*/
+ RTUNI_ALPHA, /* U+000bb9: TAMIL LETTER HA*/
+ 0, /* U+000bba: */
+ 0, /* U+000bbb: */
+ 0, /* U+000bbc: */
+ 0, /* U+000bbd: */
+ RTUNI_ALPHA, /* U+000bbe: TAMIL VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000bbf: TAMIL VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000bc0: TAMIL VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000bc1: TAMIL VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000bc2: TAMIL VOWEL SIGN UU*/
+ 0, /* U+000bc3: */
+ 0, /* U+000bc4: */
+ 0, /* U+000bc5: */
+ RTUNI_ALPHA, /* U+000bc6: TAMIL VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+000bc7: TAMIL VOWEL SIGN EE*/
+ RTUNI_ALPHA, /* U+000bc8: TAMIL VOWEL SIGN AI*/
+ 0, /* U+000bc9: */
+ RTUNI_ALPHA, /* U+000bca: TAMIL VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+000bcb: TAMIL VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+000bcc: TAMIL VOWEL SIGN AU*/
+ 0, /* U+000bcd: TAMIL SIGN VIRAMA*/
+ 0, /* U+000bce: */
+ 0, /* U+000bcf: */
+ RTUNI_ALPHA, /* U+000bd0: TAMIL OM*/
+ 0, /* U+000bd1: */
+ 0, /* U+000bd2: */
+ 0, /* U+000bd3: */
+ 0, /* U+000bd4: */
+ 0, /* U+000bd5: */
+ 0, /* U+000bd6: */
+ RTUNI_ALPHA, /* U+000bd7: TAMIL AU LENGTH MARK*/
+ 0, /* U+000bd8: */
+ 0, /* U+000bd9: */
+ 0, /* U+000bda: */
+ 0, /* U+000bdb: */
+ 0, /* U+000bdc: */
+ 0, /* U+000bdd: */
+ 0, /* U+000bde: */
+ 0, /* U+000bdf: */
+ 0, /* U+000be0: */
+ 0, /* U+000be1: */
+ 0, /* U+000be2: */
+ 0, /* U+000be3: */
+ 0, /* U+000be4: */
+ 0, /* U+000be5: */
+ RTUNI_DDIGIT, /* U+000be6: TAMIL DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000be7: TAMIL DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000be8: TAMIL DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000be9: TAMIL DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000bea: TAMIL DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000beb: TAMIL DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000bec: TAMIL DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000bed: TAMIL DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000bee: TAMIL DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000bef: TAMIL DIGIT NINE*/
+ 0, /* U+000bf0: TAMIL NUMBER TEN*/
+ 0, /* U+000bf1: TAMIL NUMBER ONE HUNDRED*/
+ 0, /* U+000bf2: TAMIL NUMBER ONE THOUSAND*/
+ 0, /* U+000bf3: TAMIL DAY SIGN*/
+ 0, /* U+000bf4: TAMIL MONTH SIGN*/
+ 0, /* U+000bf5: TAMIL YEAR SIGN*/
+ 0, /* U+000bf6: TAMIL DEBIT SIGN*/
+ 0, /* U+000bf7: TAMIL CREDIT SIGN*/
+ 0, /* U+000bf8: TAMIL AS ABOVE SIGN*/
+ 0, /* U+000bf9: TAMIL RUPEE SIGN*/
+ 0, /* U+000bfa: TAMIL NUMBER SIGN*/
+ 0, /* U+000bfb: */
+ 0, /* U+000bfc: */
+ 0, /* U+000bfd: */
+ 0, /* U+000bfe: */
+ 0, /* U+000bff: */
+ 0, /* U+000c00: */
+ RTUNI_ALPHA, /* U+000c01: TELUGU SIGN CANDRABINDU*/
+ RTUNI_ALPHA, /* U+000c02: TELUGU SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+000c03: TELUGU SIGN VISARGA*/
+ 0, /* U+000c04: */
+ RTUNI_ALPHA, /* U+000c05: TELUGU LETTER A*/
+ RTUNI_ALPHA, /* U+000c06: TELUGU LETTER AA*/
+ RTUNI_ALPHA, /* U+000c07: TELUGU LETTER I*/
+ RTUNI_ALPHA, /* U+000c08: TELUGU LETTER II*/
+ RTUNI_ALPHA, /* U+000c09: TELUGU LETTER U*/
+ RTUNI_ALPHA, /* U+000c0a: TELUGU LETTER UU*/
+ RTUNI_ALPHA, /* U+000c0b: TELUGU LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+000c0c: TELUGU LETTER VOCALIC L*/
+ 0, /* U+000c0d: */
+ RTUNI_ALPHA, /* U+000c0e: TELUGU LETTER E*/
+ RTUNI_ALPHA, /* U+000c0f: TELUGU LETTER EE*/
+ RTUNI_ALPHA, /* U+000c10: TELUGU LETTER AI*/
+ 0, /* U+000c11: */
+ RTUNI_ALPHA, /* U+000c12: TELUGU LETTER O*/
+ RTUNI_ALPHA, /* U+000c13: TELUGU LETTER OO*/
+ RTUNI_ALPHA, /* U+000c14: TELUGU LETTER AU*/
+ RTUNI_ALPHA, /* U+000c15: TELUGU LETTER KA*/
+ RTUNI_ALPHA, /* U+000c16: TELUGU LETTER KHA*/
+ RTUNI_ALPHA, /* U+000c17: TELUGU LETTER GA*/
+ RTUNI_ALPHA, /* U+000c18: TELUGU LETTER GHA*/
+ RTUNI_ALPHA, /* U+000c19: TELUGU LETTER NGA*/
+ RTUNI_ALPHA, /* U+000c1a: TELUGU LETTER CA*/
+ RTUNI_ALPHA, /* U+000c1b: TELUGU LETTER CHA*/
+ RTUNI_ALPHA, /* U+000c1c: TELUGU LETTER JA*/
+ RTUNI_ALPHA, /* U+000c1d: TELUGU LETTER JHA*/
+ RTUNI_ALPHA, /* U+000c1e: TELUGU LETTER NYA*/
+ RTUNI_ALPHA, /* U+000c1f: TELUGU LETTER TTA*/
+ RTUNI_ALPHA, /* U+000c20: TELUGU LETTER TTHA*/
+ RTUNI_ALPHA, /* U+000c21: TELUGU LETTER DDA*/
+ RTUNI_ALPHA, /* U+000c22: TELUGU LETTER DDHA*/
+ RTUNI_ALPHA, /* U+000c23: TELUGU LETTER NNA*/
+ RTUNI_ALPHA, /* U+000c24: TELUGU LETTER TA*/
+ RTUNI_ALPHA, /* U+000c25: TELUGU LETTER THA*/
+ RTUNI_ALPHA, /* U+000c26: TELUGU LETTER DA*/
+ RTUNI_ALPHA, /* U+000c27: TELUGU LETTER DHA*/
+ RTUNI_ALPHA, /* U+000c28: TELUGU LETTER NA*/
+ 0, /* U+000c29: */
+ RTUNI_ALPHA, /* U+000c2a: TELUGU LETTER PA*/
+ RTUNI_ALPHA, /* U+000c2b: TELUGU LETTER PHA*/
+ RTUNI_ALPHA, /* U+000c2c: TELUGU LETTER BA*/
+ RTUNI_ALPHA, /* U+000c2d: TELUGU LETTER BHA*/
+ RTUNI_ALPHA, /* U+000c2e: TELUGU LETTER MA*/
+ RTUNI_ALPHA, /* U+000c2f: TELUGU LETTER YA*/
+ RTUNI_ALPHA, /* U+000c30: TELUGU LETTER RA*/
+ RTUNI_ALPHA, /* U+000c31: TELUGU LETTER RRA*/
+ RTUNI_ALPHA, /* U+000c32: TELUGU LETTER LA*/
+ RTUNI_ALPHA, /* U+000c33: TELUGU LETTER LLA*/
+ 0, /* U+000c34: */
+ RTUNI_ALPHA, /* U+000c35: TELUGU LETTER VA*/
+ RTUNI_ALPHA, /* U+000c36: TELUGU LETTER SHA*/
+ RTUNI_ALPHA, /* U+000c37: TELUGU LETTER SSA*/
+ RTUNI_ALPHA, /* U+000c38: TELUGU LETTER SA*/
+ RTUNI_ALPHA, /* U+000c39: TELUGU LETTER HA*/
+ 0, /* U+000c3a: */
+ 0, /* U+000c3b: */
+ 0, /* U+000c3c: */
+ RTUNI_ALPHA, /* U+000c3d: TELUGU SIGN AVAGRAHA*/
+ RTUNI_ALPHA, /* U+000c3e: TELUGU VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000c3f: TELUGU VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000c40: TELUGU VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000c41: TELUGU VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000c42: TELUGU VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+000c43: TELUGU VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+000c44: TELUGU VOWEL SIGN VOCALIC RR*/
+ 0, /* U+000c45: */
+ RTUNI_ALPHA, /* U+000c46: TELUGU VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+000c47: TELUGU VOWEL SIGN EE*/
+ RTUNI_ALPHA, /* U+000c48: TELUGU VOWEL SIGN AI*/
+ 0, /* U+000c49: */
+ RTUNI_ALPHA, /* U+000c4a: TELUGU VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+000c4b: TELUGU VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+000c4c: TELUGU VOWEL SIGN AU*/
+ 0, /* U+000c4d: TELUGU SIGN VIRAMA*/
+ 0, /* U+000c4e: */
+ 0, /* U+000c4f: */
+ 0, /* U+000c50: */
+ 0, /* U+000c51: */
+ 0, /* U+000c52: */
+ 0, /* U+000c53: */
+ 0, /* U+000c54: */
+ RTUNI_ALPHA, /* U+000c55: TELUGU LENGTH MARK*/
+ RTUNI_ALPHA, /* U+000c56: TELUGU AI LENGTH MARK*/
+ 0, /* U+000c57: */
+ RTUNI_ALPHA, /* U+000c58: TELUGU LETTER TSA*/
+ RTUNI_ALPHA, /* U+000c59: TELUGU LETTER DZA*/
+ 0, /* U+000c5a: */
+ 0, /* U+000c5b: */
+ 0, /* U+000c5c: */
+ 0, /* U+000c5d: */
+ 0, /* U+000c5e: */
+ 0, /* U+000c5f: */
+ RTUNI_ALPHA, /* U+000c60: TELUGU LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+000c61: TELUGU LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+000c62: TELUGU VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+000c63: TELUGU VOWEL SIGN VOCALIC LL*/
+ 0, /* U+000c64: */
+ 0, /* U+000c65: */
+ RTUNI_DDIGIT, /* U+000c66: TELUGU DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000c67: TELUGU DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000c68: TELUGU DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000c69: TELUGU DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000c6a: TELUGU DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000c6b: TELUGU DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000c6c: TELUGU DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000c6d: TELUGU DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000c6e: TELUGU DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000c6f: TELUGU DIGIT NINE*/
+ 0, /* U+000c70: */
+ 0, /* U+000c71: */
+ 0, /* U+000c72: */
+ 0, /* U+000c73: */
+ 0, /* U+000c74: */
+ 0, /* U+000c75: */
+ 0, /* U+000c76: */
+ 0, /* U+000c77: */
+ 0, /* U+000c78: TELUGU FRACTION DIGIT ZERO FOR ODD POWERS OF FOUR*/
+ 0, /* U+000c79: TELUGU FRACTION DIGIT ONE FOR ODD POWERS OF FOUR*/
+ 0, /* U+000c7a: TELUGU FRACTION DIGIT TWO FOR ODD POWERS OF FOUR*/
+ 0, /* U+000c7b: TELUGU FRACTION DIGIT THREE FOR ODD POWERS OF FOUR*/
+ 0, /* U+000c7c: TELUGU FRACTION DIGIT ONE FOR EVEN POWERS OF FOUR*/
+ 0, /* U+000c7d: TELUGU FRACTION DIGIT TWO FOR EVEN POWERS OF FOUR*/
+ 0, /* U+000c7e: TELUGU FRACTION DIGIT THREE FOR EVEN POWERS OF FOUR*/
+ 0, /* U+000c7f: TELUGU SIGN TUUMU*/
+ 0, /* U+000c80: */
+ 0, /* U+000c81: */
+ RTUNI_ALPHA, /* U+000c82: KANNADA SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+000c83: KANNADA SIGN VISARGA*/
+ 0, /* U+000c84: */
+ RTUNI_ALPHA, /* U+000c85: KANNADA LETTER A*/
+ RTUNI_ALPHA, /* U+000c86: KANNADA LETTER AA*/
+ RTUNI_ALPHA, /* U+000c87: KANNADA LETTER I*/
+ RTUNI_ALPHA, /* U+000c88: KANNADA LETTER II*/
+ RTUNI_ALPHA, /* U+000c89: KANNADA LETTER U*/
+ RTUNI_ALPHA, /* U+000c8a: KANNADA LETTER UU*/
+ RTUNI_ALPHA, /* U+000c8b: KANNADA LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+000c8c: KANNADA LETTER VOCALIC L*/
+ 0, /* U+000c8d: */
+ RTUNI_ALPHA, /* U+000c8e: KANNADA LETTER E*/
+ RTUNI_ALPHA, /* U+000c8f: KANNADA LETTER EE*/
+ RTUNI_ALPHA, /* U+000c90: KANNADA LETTER AI*/
+ 0, /* U+000c91: */
+ RTUNI_ALPHA, /* U+000c92: KANNADA LETTER O*/
+ RTUNI_ALPHA, /* U+000c93: KANNADA LETTER OO*/
+ RTUNI_ALPHA, /* U+000c94: KANNADA LETTER AU*/
+ RTUNI_ALPHA, /* U+000c95: KANNADA LETTER KA*/
+ RTUNI_ALPHA, /* U+000c96: KANNADA LETTER KHA*/
+ RTUNI_ALPHA, /* U+000c97: KANNADA LETTER GA*/
+ RTUNI_ALPHA, /* U+000c98: KANNADA LETTER GHA*/
+ RTUNI_ALPHA, /* U+000c99: KANNADA LETTER NGA*/
+ RTUNI_ALPHA, /* U+000c9a: KANNADA LETTER CA*/
+ RTUNI_ALPHA, /* U+000c9b: KANNADA LETTER CHA*/
+ RTUNI_ALPHA, /* U+000c9c: KANNADA LETTER JA*/
+ RTUNI_ALPHA, /* U+000c9d: KANNADA LETTER JHA*/
+ RTUNI_ALPHA, /* U+000c9e: KANNADA LETTER NYA*/
+ RTUNI_ALPHA, /* U+000c9f: KANNADA LETTER TTA*/
+ RTUNI_ALPHA, /* U+000ca0: KANNADA LETTER TTHA*/
+ RTUNI_ALPHA, /* U+000ca1: KANNADA LETTER DDA*/
+ RTUNI_ALPHA, /* U+000ca2: KANNADA LETTER DDHA*/
+ RTUNI_ALPHA, /* U+000ca3: KANNADA LETTER NNA*/
+ RTUNI_ALPHA, /* U+000ca4: KANNADA LETTER TA*/
+ RTUNI_ALPHA, /* U+000ca5: KANNADA LETTER THA*/
+ RTUNI_ALPHA, /* U+000ca6: KANNADA LETTER DA*/
+ RTUNI_ALPHA, /* U+000ca7: KANNADA LETTER DHA*/
+ RTUNI_ALPHA, /* U+000ca8: KANNADA LETTER NA*/
+ 0, /* U+000ca9: */
+ RTUNI_ALPHA, /* U+000caa: KANNADA LETTER PA*/
+ RTUNI_ALPHA, /* U+000cab: KANNADA LETTER PHA*/
+ RTUNI_ALPHA, /* U+000cac: KANNADA LETTER BA*/
+ RTUNI_ALPHA, /* U+000cad: KANNADA LETTER BHA*/
+ RTUNI_ALPHA, /* U+000cae: KANNADA LETTER MA*/
+ RTUNI_ALPHA, /* U+000caf: KANNADA LETTER YA*/
+ RTUNI_ALPHA, /* U+000cb0: KANNADA LETTER RA*/
+ RTUNI_ALPHA, /* U+000cb1: KANNADA LETTER RRA*/
+ RTUNI_ALPHA, /* U+000cb2: KANNADA LETTER LA*/
+ RTUNI_ALPHA, /* U+000cb3: KANNADA LETTER LLA*/
+ 0, /* U+000cb4: */
+ RTUNI_ALPHA, /* U+000cb5: KANNADA LETTER VA*/
+ RTUNI_ALPHA, /* U+000cb6: KANNADA LETTER SHA*/
+ RTUNI_ALPHA, /* U+000cb7: KANNADA LETTER SSA*/
+ RTUNI_ALPHA, /* U+000cb8: KANNADA LETTER SA*/
+ RTUNI_ALPHA, /* U+000cb9: KANNADA LETTER HA*/
+ 0, /* U+000cba: */
+ 0, /* U+000cbb: */
+ 0, /* U+000cbc: KANNADA SIGN NUKTA*/
+ RTUNI_ALPHA, /* U+000cbd: KANNADA SIGN AVAGRAHA*/
+ RTUNI_ALPHA, /* U+000cbe: KANNADA VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000cbf: KANNADA VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000cc0: KANNADA VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000cc1: KANNADA VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000cc2: KANNADA VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+000cc3: KANNADA VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+000cc4: KANNADA VOWEL SIGN VOCALIC RR*/
+ 0, /* U+000cc5: */
+ RTUNI_ALPHA, /* U+000cc6: KANNADA VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+000cc7: KANNADA VOWEL SIGN EE*/
+ RTUNI_ALPHA, /* U+000cc8: KANNADA VOWEL SIGN AI*/
+ 0, /* U+000cc9: */
+ RTUNI_ALPHA, /* U+000cca: KANNADA VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+000ccb: KANNADA VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+000ccc: KANNADA VOWEL SIGN AU*/
+ 0, /* U+000ccd: KANNADA SIGN VIRAMA*/
+ 0, /* U+000cce: */
+ 0, /* U+000ccf: */
+ 0, /* U+000cd0: */
+ 0, /* U+000cd1: */
+ 0, /* U+000cd2: */
+ 0, /* U+000cd3: */
+ 0, /* U+000cd4: */
+ RTUNI_ALPHA, /* U+000cd5: KANNADA LENGTH MARK*/
+ RTUNI_ALPHA, /* U+000cd6: KANNADA AI LENGTH MARK*/
+ 0, /* U+000cd7: */
+ 0, /* U+000cd8: */
+ 0, /* U+000cd9: */
+ 0, /* U+000cda: */
+ 0, /* U+000cdb: */
+ 0, /* U+000cdc: */
+ 0, /* U+000cdd: */
+ RTUNI_ALPHA, /* U+000cde: KANNADA LETTER FA*/
+ 0, /* U+000cdf: */
+ RTUNI_ALPHA, /* U+000ce0: KANNADA LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+000ce1: KANNADA LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+000ce2: KANNADA VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+000ce3: KANNADA VOWEL SIGN VOCALIC LL*/
+ 0, /* U+000ce4: */
+ 0, /* U+000ce5: */
+ RTUNI_DDIGIT, /* U+000ce6: KANNADA DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000ce7: KANNADA DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000ce8: KANNADA DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000ce9: KANNADA DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000cea: KANNADA DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000ceb: KANNADA DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000cec: KANNADA DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000ced: KANNADA DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000cee: KANNADA DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000cef: KANNADA DIGIT NINE*/
+ 0, /* U+000cf0: */
+ RTUNI_ALPHA, /* U+000cf1: KANNADA SIGN JIHVAMULIYA*/
+ RTUNI_ALPHA, /* U+000cf2: KANNADA SIGN UPADHMANIYA*/
+ 0, /* U+000cf3: */
+ 0, /* U+000cf4: */
+ 0, /* U+000cf5: */
+ 0, /* U+000cf6: */
+ 0, /* U+000cf7: */
+ 0, /* U+000cf8: */
+ 0, /* U+000cf9: */
+ 0, /* U+000cfa: */
+ 0, /* U+000cfb: */
+ 0, /* U+000cfc: */
+ 0, /* U+000cfd: */
+ 0, /* U+000cfe: */
+ 0, /* U+000cff: */
+ 0, /* U+000d00: */
+ 0, /* U+000d01: */
+ RTUNI_ALPHA, /* U+000d02: MALAYALAM SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+000d03: MALAYALAM SIGN VISARGA*/
+ 0, /* U+000d04: */
+ RTUNI_ALPHA, /* U+000d05: MALAYALAM LETTER A*/
+ RTUNI_ALPHA, /* U+000d06: MALAYALAM LETTER AA*/
+ RTUNI_ALPHA, /* U+000d07: MALAYALAM LETTER I*/
+ RTUNI_ALPHA, /* U+000d08: MALAYALAM LETTER II*/
+ RTUNI_ALPHA, /* U+000d09: MALAYALAM LETTER U*/
+ RTUNI_ALPHA, /* U+000d0a: MALAYALAM LETTER UU*/
+ RTUNI_ALPHA, /* U+000d0b: MALAYALAM LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+000d0c: MALAYALAM LETTER VOCALIC L*/
+ 0, /* U+000d0d: */
+ RTUNI_ALPHA, /* U+000d0e: MALAYALAM LETTER E*/
+ RTUNI_ALPHA, /* U+000d0f: MALAYALAM LETTER EE*/
+ RTUNI_ALPHA, /* U+000d10: MALAYALAM LETTER AI*/
+ 0, /* U+000d11: */
+ RTUNI_ALPHA, /* U+000d12: MALAYALAM LETTER O*/
+ RTUNI_ALPHA, /* U+000d13: MALAYALAM LETTER OO*/
+ RTUNI_ALPHA, /* U+000d14: MALAYALAM LETTER AU*/
+ RTUNI_ALPHA, /* U+000d15: MALAYALAM LETTER KA*/
+ RTUNI_ALPHA, /* U+000d16: MALAYALAM LETTER KHA*/
+ RTUNI_ALPHA, /* U+000d17: MALAYALAM LETTER GA*/
+ RTUNI_ALPHA, /* U+000d18: MALAYALAM LETTER GHA*/
+ RTUNI_ALPHA, /* U+000d19: MALAYALAM LETTER NGA*/
+ RTUNI_ALPHA, /* U+000d1a: MALAYALAM LETTER CA*/
+ RTUNI_ALPHA, /* U+000d1b: MALAYALAM LETTER CHA*/
+ RTUNI_ALPHA, /* U+000d1c: MALAYALAM LETTER JA*/
+ RTUNI_ALPHA, /* U+000d1d: MALAYALAM LETTER JHA*/
+ RTUNI_ALPHA, /* U+000d1e: MALAYALAM LETTER NYA*/
+ RTUNI_ALPHA, /* U+000d1f: MALAYALAM LETTER TTA*/
+ RTUNI_ALPHA, /* U+000d20: MALAYALAM LETTER TTHA*/
+ RTUNI_ALPHA, /* U+000d21: MALAYALAM LETTER DDA*/
+ RTUNI_ALPHA, /* U+000d22: MALAYALAM LETTER DDHA*/
+ RTUNI_ALPHA, /* U+000d23: MALAYALAM LETTER NNA*/
+ RTUNI_ALPHA, /* U+000d24: MALAYALAM LETTER TA*/
+ RTUNI_ALPHA, /* U+000d25: MALAYALAM LETTER THA*/
+ RTUNI_ALPHA, /* U+000d26: MALAYALAM LETTER DA*/
+ RTUNI_ALPHA, /* U+000d27: MALAYALAM LETTER DHA*/
+ RTUNI_ALPHA, /* U+000d28: MALAYALAM LETTER NA*/
+ RTUNI_ALPHA, /* U+000d29: MALAYALAM LETTER NNNA*/
+ RTUNI_ALPHA, /* U+000d2a: MALAYALAM LETTER PA*/
+ RTUNI_ALPHA, /* U+000d2b: MALAYALAM LETTER PHA*/
+ RTUNI_ALPHA, /* U+000d2c: MALAYALAM LETTER BA*/
+ RTUNI_ALPHA, /* U+000d2d: MALAYALAM LETTER BHA*/
+ RTUNI_ALPHA, /* U+000d2e: MALAYALAM LETTER MA*/
+ RTUNI_ALPHA, /* U+000d2f: MALAYALAM LETTER YA*/
+ RTUNI_ALPHA, /* U+000d30: MALAYALAM LETTER RA*/
+ RTUNI_ALPHA, /* U+000d31: MALAYALAM LETTER RRA*/
+ RTUNI_ALPHA, /* U+000d32: MALAYALAM LETTER LA*/
+ RTUNI_ALPHA, /* U+000d33: MALAYALAM LETTER LLA*/
+ RTUNI_ALPHA, /* U+000d34: MALAYALAM LETTER LLLA*/
+ RTUNI_ALPHA, /* U+000d35: MALAYALAM LETTER VA*/
+ RTUNI_ALPHA, /* U+000d36: MALAYALAM LETTER SHA*/
+ RTUNI_ALPHA, /* U+000d37: MALAYALAM LETTER SSA*/
+ RTUNI_ALPHA, /* U+000d38: MALAYALAM LETTER SA*/
+ RTUNI_ALPHA, /* U+000d39: MALAYALAM LETTER HA*/
+ RTUNI_ALPHA, /* U+000d3a: MALAYALAM LETTER TTTA*/
+ 0, /* U+000d3b: */
+ 0, /* U+000d3c: */
+ RTUNI_ALPHA, /* U+000d3d: MALAYALAM SIGN AVAGRAHA*/
+ RTUNI_ALPHA, /* U+000d3e: MALAYALAM VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000d3f: MALAYALAM VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000d40: MALAYALAM VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000d41: MALAYALAM VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000d42: MALAYALAM VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+000d43: MALAYALAM VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+000d44: MALAYALAM VOWEL SIGN VOCALIC RR*/
+ 0, /* U+000d45: */
+ RTUNI_ALPHA, /* U+000d46: MALAYALAM VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+000d47: MALAYALAM VOWEL SIGN EE*/
+ RTUNI_ALPHA, /* U+000d48: MALAYALAM VOWEL SIGN AI*/
+ 0, /* U+000d49: */
+ RTUNI_ALPHA, /* U+000d4a: MALAYALAM VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+000d4b: MALAYALAM VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+000d4c: MALAYALAM VOWEL SIGN AU*/
+ 0, /* U+000d4d: MALAYALAM SIGN VIRAMA*/
+ RTUNI_ALPHA, /* U+000d4e: MALAYALAM LETTER DOT REPH*/
+ 0, /* U+000d4f: */
+ 0, /* U+000d50: */
+ 0, /* U+000d51: */
+ 0, /* U+000d52: */
+ 0, /* U+000d53: */
+ 0, /* U+000d54: */
+ 0, /* U+000d55: */
+ 0, /* U+000d56: */
+ RTUNI_ALPHA, /* U+000d57: MALAYALAM AU LENGTH MARK*/
+ 0, /* U+000d58: */
+ 0, /* U+000d59: */
+ 0, /* U+000d5a: */
+ 0, /* U+000d5b: */
+ 0, /* U+000d5c: */
+ 0, /* U+000d5d: */
+ 0, /* U+000d5e: */
+ 0, /* U+000d5f: */
+ RTUNI_ALPHA, /* U+000d60: MALAYALAM LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+000d61: MALAYALAM LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+000d62: MALAYALAM VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+000d63: MALAYALAM VOWEL SIGN VOCALIC LL*/
+ 0, /* U+000d64: */
+ 0, /* U+000d65: */
+ RTUNI_DDIGIT, /* U+000d66: MALAYALAM DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000d67: MALAYALAM DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000d68: MALAYALAM DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000d69: MALAYALAM DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000d6a: MALAYALAM DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000d6b: MALAYALAM DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000d6c: MALAYALAM DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000d6d: MALAYALAM DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000d6e: MALAYALAM DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000d6f: MALAYALAM DIGIT NINE*/
+ 0, /* U+000d70: MALAYALAM NUMBER TEN*/
+ 0, /* U+000d71: MALAYALAM NUMBER ONE HUNDRED*/
+ 0, /* U+000d72: MALAYALAM NUMBER ONE THOUSAND*/
+ 0, /* U+000d73: MALAYALAM FRACTION ONE QUARTER*/
+ 0, /* U+000d74: MALAYALAM FRACTION ONE HALF*/
+ 0, /* U+000d75: MALAYALAM FRACTION THREE QUARTERS*/
+ 0, /* U+000d76: */
+ 0, /* U+000d77: */
+ 0, /* U+000d78: */
+ 0, /* U+000d79: MALAYALAM DATE MARK*/
+ RTUNI_ALPHA, /* U+000d7a: MALAYALAM LETTER CHILLU NN*/
+ RTUNI_ALPHA, /* U+000d7b: MALAYALAM LETTER CHILLU N*/
+ RTUNI_ALPHA, /* U+000d7c: MALAYALAM LETTER CHILLU RR*/
+ RTUNI_ALPHA, /* U+000d7d: MALAYALAM LETTER CHILLU L*/
+ RTUNI_ALPHA, /* U+000d7e: MALAYALAM LETTER CHILLU LL*/
+ RTUNI_ALPHA, /* U+000d7f: MALAYALAM LETTER CHILLU K*/
+ 0, /* U+000d80: */
+ 0, /* U+000d81: */
+ RTUNI_ALPHA, /* U+000d82: SINHALA SIGN ANUSVARAYA*/
+ RTUNI_ALPHA, /* U+000d83: SINHALA SIGN VISARGAYA*/
+ 0, /* U+000d84: */
+ RTUNI_ALPHA, /* U+000d85: SINHALA LETTER AYANNA*/
+ RTUNI_ALPHA, /* U+000d86: SINHALA LETTER AAYANNA*/
+ RTUNI_ALPHA, /* U+000d87: SINHALA LETTER AEYANNA*/
+ RTUNI_ALPHA, /* U+000d88: SINHALA LETTER AEEYANNA*/
+ RTUNI_ALPHA, /* U+000d89: SINHALA LETTER IYANNA*/
+ RTUNI_ALPHA, /* U+000d8a: SINHALA LETTER IIYANNA*/
+ RTUNI_ALPHA, /* U+000d8b: SINHALA LETTER UYANNA*/
+ RTUNI_ALPHA, /* U+000d8c: SINHALA LETTER UUYANNA*/
+ RTUNI_ALPHA, /* U+000d8d: SINHALA LETTER IRUYANNA*/
+ RTUNI_ALPHA, /* U+000d8e: SINHALA LETTER IRUUYANNA*/
+ RTUNI_ALPHA, /* U+000d8f: SINHALA LETTER ILUYANNA*/
+ RTUNI_ALPHA, /* U+000d90: SINHALA LETTER ILUUYANNA*/
+ RTUNI_ALPHA, /* U+000d91: SINHALA LETTER EYANNA*/
+ RTUNI_ALPHA, /* U+000d92: SINHALA LETTER EEYANNA*/
+ RTUNI_ALPHA, /* U+000d93: SINHALA LETTER AIYANNA*/
+ RTUNI_ALPHA, /* U+000d94: SINHALA LETTER OYANNA*/
+ RTUNI_ALPHA, /* U+000d95: SINHALA LETTER OOYANNA*/
+ RTUNI_ALPHA, /* U+000d96: SINHALA LETTER AUYANNA*/
+ 0, /* U+000d97: */
+ 0, /* U+000d98: */
+ 0, /* U+000d99: */
+ RTUNI_ALPHA, /* U+000d9a: SINHALA LETTER ALPAPRAANA KAYANNA*/
+ RTUNI_ALPHA, /* U+000d9b: SINHALA LETTER MAHAAPRAANA KAYANNA*/
+ RTUNI_ALPHA, /* U+000d9c: SINHALA LETTER ALPAPRAANA GAYANNA*/
+ RTUNI_ALPHA, /* U+000d9d: SINHALA LETTER MAHAAPRAANA GAYANNA*/
+ RTUNI_ALPHA, /* U+000d9e: SINHALA LETTER KANTAJA NAASIKYAYA*/
+ RTUNI_ALPHA, /* U+000d9f: SINHALA LETTER SANYAKA GAYANNA*/
+ RTUNI_ALPHA, /* U+000da0: SINHALA LETTER ALPAPRAANA CAYANNA*/
+ RTUNI_ALPHA, /* U+000da1: SINHALA LETTER MAHAAPRAANA CAYANNA*/
+ RTUNI_ALPHA, /* U+000da2: SINHALA LETTER ALPAPRAANA JAYANNA*/
+ RTUNI_ALPHA, /* U+000da3: SINHALA LETTER MAHAAPRAANA JAYANNA*/
+ RTUNI_ALPHA, /* U+000da4: SINHALA LETTER TAALUJA NAASIKYAYA*/
+ RTUNI_ALPHA, /* U+000da5: SINHALA LETTER TAALUJA SANYOOGA NAAKSIKYAYA*/
+ RTUNI_ALPHA, /* U+000da6: SINHALA LETTER SANYAKA JAYANNA*/
+ RTUNI_ALPHA, /* U+000da7: SINHALA LETTER ALPAPRAANA TTAYANNA*/
+ RTUNI_ALPHA, /* U+000da8: SINHALA LETTER MAHAAPRAANA TTAYANNA*/
+ RTUNI_ALPHA, /* U+000da9: SINHALA LETTER ALPAPRAANA DDAYANNA*/
+ RTUNI_ALPHA, /* U+000daa: SINHALA LETTER MAHAAPRAANA DDAYANNA*/
+ RTUNI_ALPHA, /* U+000dab: SINHALA LETTER MUURDHAJA NAYANNA*/
+ RTUNI_ALPHA, /* U+000dac: SINHALA LETTER SANYAKA DDAYANNA*/
+ RTUNI_ALPHA, /* U+000dad: SINHALA LETTER ALPAPRAANA TAYANNA*/
+ RTUNI_ALPHA, /* U+000dae: SINHALA LETTER MAHAAPRAANA TAYANNA*/
+ RTUNI_ALPHA, /* U+000daf: SINHALA LETTER ALPAPRAANA DAYANNA*/
+ RTUNI_ALPHA, /* U+000db0: SINHALA LETTER MAHAAPRAANA DAYANNA*/
+ RTUNI_ALPHA, /* U+000db1: SINHALA LETTER DANTAJA NAYANNA*/
+ 0, /* U+000db2: */
+ RTUNI_ALPHA, /* U+000db3: SINHALA LETTER SANYAKA DAYANNA*/
+ RTUNI_ALPHA, /* U+000db4: SINHALA LETTER ALPAPRAANA PAYANNA*/
+ RTUNI_ALPHA, /* U+000db5: SINHALA LETTER MAHAAPRAANA PAYANNA*/
+ RTUNI_ALPHA, /* U+000db6: SINHALA LETTER ALPAPRAANA BAYANNA*/
+ RTUNI_ALPHA, /* U+000db7: SINHALA LETTER MAHAAPRAANA BAYANNA*/
+ RTUNI_ALPHA, /* U+000db8: SINHALA LETTER MAYANNA*/
+ RTUNI_ALPHA, /* U+000db9: SINHALA LETTER AMBA BAYANNA*/
+ RTUNI_ALPHA, /* U+000dba: SINHALA LETTER YAYANNA*/
+ RTUNI_ALPHA, /* U+000dbb: SINHALA LETTER RAYANNA*/
+ 0, /* U+000dbc: */
+ RTUNI_ALPHA, /* U+000dbd: SINHALA LETTER DANTAJA LAYANNA*/
+ 0, /* U+000dbe: */
+ 0, /* U+000dbf: */
+ RTUNI_ALPHA, /* U+000dc0: SINHALA LETTER VAYANNA*/
+ RTUNI_ALPHA, /* U+000dc1: SINHALA LETTER TAALUJA SAYANNA*/
+ RTUNI_ALPHA, /* U+000dc2: SINHALA LETTER MUURDHAJA SAYANNA*/
+ RTUNI_ALPHA, /* U+000dc3: SINHALA LETTER DANTAJA SAYANNA*/
+ RTUNI_ALPHA, /* U+000dc4: SINHALA LETTER HAYANNA*/
+ RTUNI_ALPHA, /* U+000dc5: SINHALA LETTER MUURDHAJA LAYANNA*/
+ RTUNI_ALPHA, /* U+000dc6: SINHALA LETTER FAYANNA*/
+ 0, /* U+000dc7: */
+ 0, /* U+000dc8: */
+ 0, /* U+000dc9: */
+ 0, /* U+000dca: SINHALA SIGN AL-LAKUNA*/
+ 0, /* U+000dcb: */
+ 0, /* U+000dcc: */
+ 0, /* U+000dcd: */
+ 0, /* U+000dce: */
+ RTUNI_ALPHA, /* U+000dcf: SINHALA VOWEL SIGN AELA-PILLA*/
+ RTUNI_ALPHA, /* U+000dd0: SINHALA VOWEL SIGN KETTI AEDA-PILLA*/
+ RTUNI_ALPHA, /* U+000dd1: SINHALA VOWEL SIGN DIGA AEDA-PILLA*/
+ RTUNI_ALPHA, /* U+000dd2: SINHALA VOWEL SIGN KETTI IS-PILLA*/
+ RTUNI_ALPHA, /* U+000dd3: SINHALA VOWEL SIGN DIGA IS-PILLA*/
+ RTUNI_ALPHA, /* U+000dd4: SINHALA VOWEL SIGN KETTI PAA-PILLA*/
+ 0, /* U+000dd5: */
+ RTUNI_ALPHA, /* U+000dd6: SINHALA VOWEL SIGN DIGA PAA-PILLA*/
+ 0, /* U+000dd7: */
+ RTUNI_ALPHA, /* U+000dd8: SINHALA VOWEL SIGN GAETTA-PILLA*/
+ RTUNI_ALPHA, /* U+000dd9: SINHALA VOWEL SIGN KOMBUVA*/
+ RTUNI_ALPHA, /* U+000dda: SINHALA VOWEL SIGN DIGA KOMBUVA*/
+ RTUNI_ALPHA, /* U+000ddb: SINHALA VOWEL SIGN KOMBU DEKA*/
+ RTUNI_ALPHA, /* U+000ddc: SINHALA VOWEL SIGN KOMBUVA HAA AELA-PILLA*/
+ RTUNI_ALPHA, /* U+000ddd: SINHALA VOWEL SIGN KOMBUVA HAA DIGA AELA-PILLA*/
+ RTUNI_ALPHA, /* U+000dde: SINHALA VOWEL SIGN KOMBUVA HAA GAYANUKITTA*/
+ RTUNI_ALPHA, /* U+000ddf: SINHALA VOWEL SIGN GAYANUKITTA*/
+ 0, /* U+000de0: */
+ 0, /* U+000de1: */
+ 0, /* U+000de2: */
+ 0, /* U+000de3: */
+ 0, /* U+000de4: */
+ 0, /* U+000de5: */
+ 0, /* U+000de6: */
+ 0, /* U+000de7: */
+ 0, /* U+000de8: */
+ 0, /* U+000de9: */
+ 0, /* U+000dea: */
+ 0, /* U+000deb: */
+ 0, /* U+000dec: */
+ 0, /* U+000ded: */
+ 0, /* U+000dee: */
+ 0, /* U+000def: */
+ 0, /* U+000df0: */
+ 0, /* U+000df1: */
+ RTUNI_ALPHA, /* U+000df2: SINHALA VOWEL SIGN DIGA GAETTA-PILLA*/
+ RTUNI_ALPHA, /* U+000df3: SINHALA VOWEL SIGN DIGA GAYANUKITTA*/
+ 0, /* U+000df4: SINHALA PUNCTUATION KUNDDALIYA*/
+ 0, /* U+000df5: */
+ 0, /* U+000df6: */
+ 0, /* U+000df7: */
+ 0, /* U+000df8: */
+ 0, /* U+000df9: */
+ 0, /* U+000dfa: */
+ 0, /* U+000dfb: */
+ 0, /* U+000dfc: */
+ 0, /* U+000dfd: */
+ 0, /* U+000dfe: */
+ 0, /* U+000dff: */
+ 0, /* U+000e00: */
+ RTUNI_ALPHA, /* U+000e01: THAI CHARACTER KO KAI*/
+ RTUNI_ALPHA, /* U+000e02: THAI CHARACTER KHO KHAI*/
+ RTUNI_ALPHA, /* U+000e03: THAI CHARACTER KHO KHUAT*/
+ RTUNI_ALPHA, /* U+000e04: THAI CHARACTER KHO KHWAI*/
+ RTUNI_ALPHA, /* U+000e05: THAI CHARACTER KHO KHON*/
+ RTUNI_ALPHA, /* U+000e06: THAI CHARACTER KHO RAKHANG*/
+ RTUNI_ALPHA, /* U+000e07: THAI CHARACTER NGO NGU*/
+ RTUNI_ALPHA, /* U+000e08: THAI CHARACTER CHO CHAN*/
+ RTUNI_ALPHA, /* U+000e09: THAI CHARACTER CHO CHING*/
+ RTUNI_ALPHA, /* U+000e0a: THAI CHARACTER CHO CHANG*/
+ RTUNI_ALPHA, /* U+000e0b: THAI CHARACTER SO SO*/
+ RTUNI_ALPHA, /* U+000e0c: THAI CHARACTER CHO CHOE*/
+ RTUNI_ALPHA, /* U+000e0d: THAI CHARACTER YO YING*/
+ RTUNI_ALPHA, /* U+000e0e: THAI CHARACTER DO CHADA*/
+ RTUNI_ALPHA, /* U+000e0f: THAI CHARACTER TO PATAK*/
+ RTUNI_ALPHA, /* U+000e10: THAI CHARACTER THO THAN*/
+ RTUNI_ALPHA, /* U+000e11: THAI CHARACTER THO NANGMONTHO*/
+ RTUNI_ALPHA, /* U+000e12: THAI CHARACTER THO PHUTHAO*/
+ RTUNI_ALPHA, /* U+000e13: THAI CHARACTER NO NEN*/
+ RTUNI_ALPHA, /* U+000e14: THAI CHARACTER DO DEK*/
+ RTUNI_ALPHA, /* U+000e15: THAI CHARACTER TO TAO*/
+ RTUNI_ALPHA, /* U+000e16: THAI CHARACTER THO THUNG*/
+ RTUNI_ALPHA, /* U+000e17: THAI CHARACTER THO THAHAN*/
+ RTUNI_ALPHA, /* U+000e18: THAI CHARACTER THO THONG*/
+ RTUNI_ALPHA, /* U+000e19: THAI CHARACTER NO NU*/
+ RTUNI_ALPHA, /* U+000e1a: THAI CHARACTER BO BAIMAI*/
+ RTUNI_ALPHA, /* U+000e1b: THAI CHARACTER PO PLA*/
+ RTUNI_ALPHA, /* U+000e1c: THAI CHARACTER PHO PHUNG*/
+ RTUNI_ALPHA, /* U+000e1d: THAI CHARACTER FO FA*/
+ RTUNI_ALPHA, /* U+000e1e: THAI CHARACTER PHO PHAN*/
+ RTUNI_ALPHA, /* U+000e1f: THAI CHARACTER FO FAN*/
+ RTUNI_ALPHA, /* U+000e20: THAI CHARACTER PHO SAMPHAO*/
+ RTUNI_ALPHA, /* U+000e21: THAI CHARACTER MO MA*/
+ RTUNI_ALPHA, /* U+000e22: THAI CHARACTER YO YAK*/
+ RTUNI_ALPHA, /* U+000e23: THAI CHARACTER RO RUA*/
+ RTUNI_ALPHA, /* U+000e24: THAI CHARACTER RU*/
+ RTUNI_ALPHA, /* U+000e25: THAI CHARACTER LO LING*/
+ RTUNI_ALPHA, /* U+000e26: THAI CHARACTER LU*/
+ RTUNI_ALPHA, /* U+000e27: THAI CHARACTER WO WAEN*/
+ RTUNI_ALPHA, /* U+000e28: THAI CHARACTER SO SALA*/
+ RTUNI_ALPHA, /* U+000e29: THAI CHARACTER SO RUSI*/
+ RTUNI_ALPHA, /* U+000e2a: THAI CHARACTER SO SUA*/
+ RTUNI_ALPHA, /* U+000e2b: THAI CHARACTER HO HIP*/
+ RTUNI_ALPHA, /* U+000e2c: THAI CHARACTER LO CHULA*/
+ RTUNI_ALPHA, /* U+000e2d: THAI CHARACTER O ANG*/
+ RTUNI_ALPHA, /* U+000e2e: THAI CHARACTER HO NOKHUK*/
+ RTUNI_ALPHA, /* U+000e2f: THAI CHARACTER PAIYANNOI*/
+ RTUNI_ALPHA, /* U+000e30: THAI CHARACTER SARA A*/
+ RTUNI_ALPHA, /* U+000e31: THAI CHARACTER MAI HAN-AKAT*/
+ RTUNI_ALPHA, /* U+000e32: THAI CHARACTER SARA AA*/
+ RTUNI_ALPHA, /* U+000e33: THAI CHARACTER SARA AM*/
+ RTUNI_ALPHA, /* U+000e34: THAI CHARACTER SARA I*/
+ RTUNI_ALPHA, /* U+000e35: THAI CHARACTER SARA II*/
+ RTUNI_ALPHA, /* U+000e36: THAI CHARACTER SARA UE*/
+ RTUNI_ALPHA, /* U+000e37: THAI CHARACTER SARA UEE*/
+ RTUNI_ALPHA, /* U+000e38: THAI CHARACTER SARA U*/
+ RTUNI_ALPHA, /* U+000e39: THAI CHARACTER SARA UU*/
+ RTUNI_ALPHA, /* U+000e3a: THAI CHARACTER PHINTHU*/
+ 0, /* U+000e3b: */
+ 0, /* U+000e3c: */
+ 0, /* U+000e3d: */
+ 0, /* U+000e3e: */
+ 0, /* U+000e3f: THAI CURRENCY SYMBOL BAHT*/
+ RTUNI_ALPHA, /* U+000e40: THAI CHARACTER SARA E*/
+ RTUNI_ALPHA, /* U+000e41: THAI CHARACTER SARA AE*/
+ RTUNI_ALPHA, /* U+000e42: THAI CHARACTER SARA O*/
+ RTUNI_ALPHA, /* U+000e43: THAI CHARACTER SARA AI MAIMUAN*/
+ RTUNI_ALPHA, /* U+000e44: THAI CHARACTER SARA AI MAIMALAI*/
+ RTUNI_ALPHA, /* U+000e45: THAI CHARACTER LAKKHANGYAO*/
+ RTUNI_ALPHA, /* U+000e46: THAI CHARACTER MAIYAMOK*/
+ 0, /* U+000e47: THAI CHARACTER MAITAIKHU*/
+ 0, /* U+000e48: THAI CHARACTER MAI EK*/
+ 0, /* U+000e49: THAI CHARACTER MAI THO*/
+ 0, /* U+000e4a: THAI CHARACTER MAI TRI*/
+ 0, /* U+000e4b: THAI CHARACTER MAI CHATTAWA*/
+ 0, /* U+000e4c: THAI CHARACTER THANTHAKHAT*/
+ RTUNI_ALPHA, /* U+000e4d: THAI CHARACTER NIKHAHIT*/
+ 0, /* U+000e4e: THAI CHARACTER YAMAKKAN*/
+ 0, /* U+000e4f: THAI CHARACTER FONGMAN*/
+ RTUNI_DDIGIT, /* U+000e50: THAI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000e51: THAI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000e52: THAI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000e53: THAI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000e54: THAI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000e55: THAI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000e56: THAI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000e57: THAI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000e58: THAI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000e59: THAI DIGIT NINE*/
+ 0, /* U+000e5a: THAI CHARACTER ANGKHANKHU*/
+ 0, /* U+000e5b: THAI CHARACTER KHOMUT*/
+ 0, /* U+000e5c: */
+ 0, /* U+000e5d: */
+ 0, /* U+000e5e: */
+ 0, /* U+000e5f: */
+ 0, /* U+000e60: */
+ 0, /* U+000e61: */
+ 0, /* U+000e62: */
+ 0, /* U+000e63: */
+ 0, /* U+000e64: */
+ 0, /* U+000e65: */
+ 0, /* U+000e66: */
+ 0, /* U+000e67: */
+ 0, /* U+000e68: */
+ 0, /* U+000e69: */
+ 0, /* U+000e6a: */
+ 0, /* U+000e6b: */
+ 0, /* U+000e6c: */
+ 0, /* U+000e6d: */
+ 0, /* U+000e6e: */
+ 0, /* U+000e6f: */
+ 0, /* U+000e70: */
+ 0, /* U+000e71: */
+ 0, /* U+000e72: */
+ 0, /* U+000e73: */
+ 0, /* U+000e74: */
+ 0, /* U+000e75: */
+ 0, /* U+000e76: */
+ 0, /* U+000e77: */
+ 0, /* U+000e78: */
+ 0, /* U+000e79: */
+ 0, /* U+000e7a: */
+ 0, /* U+000e7b: */
+ 0, /* U+000e7c: */
+ 0, /* U+000e7d: */
+ 0, /* U+000e7e: */
+ 0, /* U+000e7f: */
+ 0, /* U+000e80: */
+ RTUNI_ALPHA, /* U+000e81: LAO LETTER KO*/
+ RTUNI_ALPHA, /* U+000e82: LAO LETTER KHO SUNG*/
+ 0, /* U+000e83: */
+ RTUNI_ALPHA, /* U+000e84: LAO LETTER KHO TAM*/
+ 0, /* U+000e85: */
+ 0, /* U+000e86: */
+ RTUNI_ALPHA, /* U+000e87: LAO LETTER NGO*/
+ RTUNI_ALPHA, /* U+000e88: LAO LETTER CO*/
+ 0, /* U+000e89: */
+ RTUNI_ALPHA, /* U+000e8a: LAO LETTER SO TAM*/
+ 0, /* U+000e8b: */
+ 0, /* U+000e8c: */
+ RTUNI_ALPHA, /* U+000e8d: LAO LETTER NYO*/
+ 0, /* U+000e8e: */
+ 0, /* U+000e8f: */
+ 0, /* U+000e90: */
+ 0, /* U+000e91: */
+ 0, /* U+000e92: */
+ 0, /* U+000e93: */
+ RTUNI_ALPHA, /* U+000e94: LAO LETTER DO*/
+ RTUNI_ALPHA, /* U+000e95: LAO LETTER TO*/
+ RTUNI_ALPHA, /* U+000e96: LAO LETTER THO SUNG*/
+ RTUNI_ALPHA, /* U+000e97: LAO LETTER THO TAM*/
+ 0, /* U+000e98: */
+ RTUNI_ALPHA, /* U+000e99: LAO LETTER NO*/
+ RTUNI_ALPHA, /* U+000e9a: LAO LETTER BO*/
+ RTUNI_ALPHA, /* U+000e9b: LAO LETTER PO*/
+ RTUNI_ALPHA, /* U+000e9c: LAO LETTER PHO SUNG*/
+ RTUNI_ALPHA, /* U+000e9d: LAO LETTER FO TAM*/
+ RTUNI_ALPHA, /* U+000e9e: LAO LETTER PHO TAM*/
+ RTUNI_ALPHA, /* U+000e9f: LAO LETTER FO SUNG*/
+ 0, /* U+000ea0: */
+ RTUNI_ALPHA, /* U+000ea1: LAO LETTER MO*/
+ RTUNI_ALPHA, /* U+000ea2: LAO LETTER YO*/
+ RTUNI_ALPHA, /* U+000ea3: LAO LETTER LO LING*/
+ 0, /* U+000ea4: */
+ RTUNI_ALPHA, /* U+000ea5: LAO LETTER LO LOOT*/
+ 0, /* U+000ea6: */
+ RTUNI_ALPHA, /* U+000ea7: LAO LETTER WO*/
+ 0, /* U+000ea8: */
+ 0, /* U+000ea9: */
+ RTUNI_ALPHA, /* U+000eaa: LAO LETTER SO SUNG*/
+ RTUNI_ALPHA, /* U+000eab: LAO LETTER HO SUNG*/
+ 0, /* U+000eac: */
+ RTUNI_ALPHA, /* U+000ead: LAO LETTER O*/
+ RTUNI_ALPHA, /* U+000eae: LAO LETTER HO TAM*/
+ RTUNI_ALPHA, /* U+000eaf: LAO ELLIPSIS*/
+ RTUNI_ALPHA, /* U+000eb0: LAO VOWEL SIGN A*/
+ RTUNI_ALPHA, /* U+000eb1: LAO VOWEL SIGN MAI KAN*/
+ RTUNI_ALPHA, /* U+000eb2: LAO VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000eb3: LAO VOWEL SIGN AM*/
+ RTUNI_ALPHA, /* U+000eb4: LAO VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000eb5: LAO VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000eb6: LAO VOWEL SIGN Y*/
+ RTUNI_ALPHA, /* U+000eb7: LAO VOWEL SIGN YY*/
+ RTUNI_ALPHA, /* U+000eb8: LAO VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000eb9: LAO VOWEL SIGN UU*/
+ 0, /* U+000eba: */
+ RTUNI_ALPHA, /* U+000ebb: LAO VOWEL SIGN MAI KON*/
+ RTUNI_ALPHA, /* U+000ebc: LAO SEMIVOWEL SIGN LO*/
+ RTUNI_ALPHA, /* U+000ebd: LAO SEMIVOWEL SIGN NYO*/
+ 0, /* U+000ebe: */
+ 0, /* U+000ebf: */
+ RTUNI_ALPHA, /* U+000ec0: LAO VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+000ec1: LAO VOWEL SIGN EI*/
+ RTUNI_ALPHA, /* U+000ec2: LAO VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+000ec3: LAO VOWEL SIGN AY*/
+ RTUNI_ALPHA, /* U+000ec4: LAO VOWEL SIGN AI*/
+ 0, /* U+000ec5: */
+ RTUNI_ALPHA, /* U+000ec6: LAO KO LA*/
+ 0, /* U+000ec7: */
+ 0, /* U+000ec8: LAO TONE MAI EK*/
+ 0, /* U+000ec9: LAO TONE MAI THO*/
+ 0, /* U+000eca: LAO TONE MAI TI*/
+ 0, /* U+000ecb: LAO TONE MAI CATAWA*/
+ 0, /* U+000ecc: LAO CANCELLATION MARK*/
+ RTUNI_ALPHA, /* U+000ecd: LAO NIGGAHITA*/
+ 0, /* U+000ece: */
+ 0, /* U+000ecf: */
+ RTUNI_DDIGIT, /* U+000ed0: LAO DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000ed1: LAO DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000ed2: LAO DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000ed3: LAO DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000ed4: LAO DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000ed5: LAO DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000ed6: LAO DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000ed7: LAO DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000ed8: LAO DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000ed9: LAO DIGIT NINE*/
+ 0, /* U+000eda: */
+ 0, /* U+000edb: */
+ RTUNI_ALPHA, /* U+000edc: LAO HO NO*/
+ RTUNI_ALPHA, /* U+000edd: LAO HO MO*/
+ RTUNI_ALPHA, /* U+000ede: LAO LETTER KHMU GO*/
+ RTUNI_ALPHA, /* U+000edf: LAO LETTER KHMU NYO*/
+ 0, /* U+000ee0: */
+ 0, /* U+000ee1: */
+ 0, /* U+000ee2: */
+ 0, /* U+000ee3: */
+ 0, /* U+000ee4: */
+ 0, /* U+000ee5: */
+ 0, /* U+000ee6: */
+ 0, /* U+000ee7: */
+ 0, /* U+000ee8: */
+ 0, /* U+000ee9: */
+ 0, /* U+000eea: */
+ 0, /* U+000eeb: */
+ 0, /* U+000eec: */
+ 0, /* U+000eed: */
+ 0, /* U+000eee: */
+ 0, /* U+000eef: */
+ 0, /* U+000ef0: */
+ 0, /* U+000ef1: */
+ 0, /* U+000ef2: */
+ 0, /* U+000ef3: */
+ 0, /* U+000ef4: */
+ 0, /* U+000ef5: */
+ 0, /* U+000ef6: */
+ 0, /* U+000ef7: */
+ 0, /* U+000ef8: */
+ 0, /* U+000ef9: */
+ 0, /* U+000efa: */
+ 0, /* U+000efb: */
+ 0, /* U+000efc: */
+ 0, /* U+000efd: */
+ 0, /* U+000efe: */
+ 0, /* U+000eff: */
+ RTUNI_ALPHA, /* U+000f00: TIBETAN SYLLABLE OM*/
+ 0, /* U+000f01: TIBETAN MARK GTER YIG MGO TRUNCATED A*/
+ 0, /* U+000f02: TIBETAN MARK GTER YIG MGO -UM RNAM BCAD MA*/
+ 0, /* U+000f03: TIBETAN MARK GTER YIG MGO -UM GTER TSHEG MA*/
+ 0, /* U+000f04: TIBETAN MARK INITIAL YIG MGO MDUN MA*/
+ 0, /* U+000f05: TIBETAN MARK CLOSING YIG MGO SGAB MA*/
+ 0, /* U+000f06: TIBETAN MARK CARET YIG MGO PHUR SHAD MA*/
+ 0, /* U+000f07: TIBETAN MARK YIG MGO TSHEG SHAD MA*/
+ 0, /* U+000f08: TIBETAN MARK SBRUL SHAD*/
+ 0, /* U+000f09: TIBETAN MARK BSKUR YIG MGO*/
+ 0, /* U+000f0a: TIBETAN MARK BKA- SHOG YIG MGO*/
+ 0, /* U+000f0b: TIBETAN MARK INTERSYLLABIC TSHEG*/
+ 0, /* U+000f0c: TIBETAN MARK DELIMITER TSHEG BSTAR*/
+ 0, /* U+000f0d: TIBETAN MARK SHAD*/
+ 0, /* U+000f0e: TIBETAN MARK NYIS SHAD*/
+ 0, /* U+000f0f: TIBETAN MARK TSHEG SHAD*/
+ 0, /* U+000f10: TIBETAN MARK NYIS TSHEG SHAD*/
+ 0, /* U+000f11: TIBETAN MARK RIN CHEN SPUNGS SHAD*/
+ 0, /* U+000f12: TIBETAN MARK RGYA GRAM SHAD*/
+ 0, /* U+000f13: TIBETAN MARK CARET -DZUD RTAGS ME LONG CAN*/
+ 0, /* U+000f14: TIBETAN MARK GTER TSHEG*/
+ 0, /* U+000f15: TIBETAN LOGOTYPE SIGN CHAD RTAGS*/
+ 0, /* U+000f16: TIBETAN LOGOTYPE SIGN LHAG RTAGS*/
+ 0, /* U+000f17: TIBETAN ASTROLOGICAL SIGN SGRA GCAN -CHAR RTAGS*/
+ 0, /* U+000f18: TIBETAN ASTROLOGICAL SIGN -KHYUD PA*/
+ 0, /* U+000f19: TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS*/
+ 0, /* U+000f1a: TIBETAN SIGN RDEL DKAR GCIG*/
+ 0, /* U+000f1b: TIBETAN SIGN RDEL DKAR GNYIS*/
+ 0, /* U+000f1c: TIBETAN SIGN RDEL DKAR GSUM*/
+ 0, /* U+000f1d: TIBETAN SIGN RDEL NAG GCIG*/
+ 0, /* U+000f1e: TIBETAN SIGN RDEL NAG GNYIS*/
+ 0, /* U+000f1f: TIBETAN SIGN RDEL DKAR RDEL NAG*/
+ RTUNI_DDIGIT, /* U+000f20: TIBETAN DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+000f21: TIBETAN DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+000f22: TIBETAN DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+000f23: TIBETAN DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+000f24: TIBETAN DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+000f25: TIBETAN DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+000f26: TIBETAN DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+000f27: TIBETAN DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+000f28: TIBETAN DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+000f29: TIBETAN DIGIT NINE*/
+ 0, /* U+000f2a: TIBETAN DIGIT HALF ONE*/
+ 0, /* U+000f2b: TIBETAN DIGIT HALF TWO*/
+ 0, /* U+000f2c: TIBETAN DIGIT HALF THREE*/
+ 0, /* U+000f2d: TIBETAN DIGIT HALF FOUR*/
+ 0, /* U+000f2e: TIBETAN DIGIT HALF FIVE*/
+ 0, /* U+000f2f: TIBETAN DIGIT HALF SIX*/
+ 0, /* U+000f30: TIBETAN DIGIT HALF SEVEN*/
+ 0, /* U+000f31: TIBETAN DIGIT HALF EIGHT*/
+ 0, /* U+000f32: TIBETAN DIGIT HALF NINE*/
+ 0, /* U+000f33: TIBETAN DIGIT HALF ZERO*/
+ 0, /* U+000f34: TIBETAN MARK BSDUS RTAGS*/
+ 0, /* U+000f35: TIBETAN MARK NGAS BZUNG NYI ZLA*/
+ 0, /* U+000f36: TIBETAN MARK CARET -DZUD RTAGS BZHI MIG CAN*/
+ 0, /* U+000f37: TIBETAN MARK NGAS BZUNG SGOR RTAGS*/
+ 0, /* U+000f38: TIBETAN MARK CHE MGO*/
+ 0, /* U+000f39: TIBETAN MARK TSA -PHRU*/
+ 0, /* U+000f3a: TIBETAN MARK GUG RTAGS GYON*/
+ 0, /* U+000f3b: TIBETAN MARK GUG RTAGS GYAS*/
+ 0, /* U+000f3c: TIBETAN MARK ANG KHANG GYON*/
+ 0, /* U+000f3d: TIBETAN MARK ANG KHANG GYAS*/
+ 0, /* U+000f3e: TIBETAN SIGN YAR TSHES*/
+ 0, /* U+000f3f: TIBETAN SIGN MAR TSHES*/
+ RTUNI_ALPHA, /* U+000f40: TIBETAN LETTER KA*/
+ RTUNI_ALPHA, /* U+000f41: TIBETAN LETTER KHA*/
+ RTUNI_ALPHA, /* U+000f42: TIBETAN LETTER GA*/
+ RTUNI_ALPHA, /* U+000f43: TIBETAN LETTER GHA*/
+ RTUNI_ALPHA, /* U+000f44: TIBETAN LETTER NGA*/
+ RTUNI_ALPHA, /* U+000f45: TIBETAN LETTER CA*/
+ RTUNI_ALPHA, /* U+000f46: TIBETAN LETTER CHA*/
+ RTUNI_ALPHA, /* U+000f47: TIBETAN LETTER JA*/
+ 0, /* U+000f48: */
+ RTUNI_ALPHA, /* U+000f49: TIBETAN LETTER NYA*/
+ RTUNI_ALPHA, /* U+000f4a: TIBETAN LETTER TTA*/
+ RTUNI_ALPHA, /* U+000f4b: TIBETAN LETTER TTHA*/
+ RTUNI_ALPHA, /* U+000f4c: TIBETAN LETTER DDA*/
+ RTUNI_ALPHA, /* U+000f4d: TIBETAN LETTER DDHA*/
+ RTUNI_ALPHA, /* U+000f4e: TIBETAN LETTER NNA*/
+ RTUNI_ALPHA, /* U+000f4f: TIBETAN LETTER TA*/
+ RTUNI_ALPHA, /* U+000f50: TIBETAN LETTER THA*/
+ RTUNI_ALPHA, /* U+000f51: TIBETAN LETTER DA*/
+ RTUNI_ALPHA, /* U+000f52: TIBETAN LETTER DHA*/
+ RTUNI_ALPHA, /* U+000f53: TIBETAN LETTER NA*/
+ RTUNI_ALPHA, /* U+000f54: TIBETAN LETTER PA*/
+ RTUNI_ALPHA, /* U+000f55: TIBETAN LETTER PHA*/
+ RTUNI_ALPHA, /* U+000f56: TIBETAN LETTER BA*/
+ RTUNI_ALPHA, /* U+000f57: TIBETAN LETTER BHA*/
+ RTUNI_ALPHA, /* U+000f58: TIBETAN LETTER MA*/
+ RTUNI_ALPHA, /* U+000f59: TIBETAN LETTER TSA*/
+ RTUNI_ALPHA, /* U+000f5a: TIBETAN LETTER TSHA*/
+ RTUNI_ALPHA, /* U+000f5b: TIBETAN LETTER DZA*/
+ RTUNI_ALPHA, /* U+000f5c: TIBETAN LETTER DZHA*/
+ RTUNI_ALPHA, /* U+000f5d: TIBETAN LETTER WA*/
+ RTUNI_ALPHA, /* U+000f5e: TIBETAN LETTER ZHA*/
+ RTUNI_ALPHA, /* U+000f5f: TIBETAN LETTER ZA*/
+ RTUNI_ALPHA, /* U+000f60: TIBETAN LETTER -A*/
+ RTUNI_ALPHA, /* U+000f61: TIBETAN LETTER YA*/
+ RTUNI_ALPHA, /* U+000f62: TIBETAN LETTER RA*/
+ RTUNI_ALPHA, /* U+000f63: TIBETAN LETTER LA*/
+ RTUNI_ALPHA, /* U+000f64: TIBETAN LETTER SHA*/
+ RTUNI_ALPHA, /* U+000f65: TIBETAN LETTER SSA*/
+ RTUNI_ALPHA, /* U+000f66: TIBETAN LETTER SA*/
+ RTUNI_ALPHA, /* U+000f67: TIBETAN LETTER HA*/
+ RTUNI_ALPHA, /* U+000f68: TIBETAN LETTER A*/
+ RTUNI_ALPHA, /* U+000f69: TIBETAN LETTER KSSA*/
+ RTUNI_ALPHA, /* U+000f6a: TIBETAN LETTER FIXED-FORM RA*/
+ RTUNI_ALPHA, /* U+000f6b: TIBETAN LETTER KKA*/
+ RTUNI_ALPHA, /* U+000f6c: TIBETAN LETTER RRA*/
+ 0, /* U+000f6d: */
+ 0, /* U+000f6e: */
+ 0, /* U+000f6f: */
+ 0, /* U+000f70: */
+ RTUNI_ALPHA, /* U+000f71: TIBETAN VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+000f72: TIBETAN VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+000f73: TIBETAN VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+000f74: TIBETAN VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+000f75: TIBETAN VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+000f76: TIBETAN VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+000f77: TIBETAN VOWEL SIGN VOCALIC RR*/
+ RTUNI_ALPHA, /* U+000f78: TIBETAN VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+000f79: TIBETAN VOWEL SIGN VOCALIC LL*/
+ RTUNI_ALPHA, /* U+000f7a: TIBETAN VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+000f7b: TIBETAN VOWEL SIGN EE*/
+ RTUNI_ALPHA, /* U+000f7c: TIBETAN VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+000f7d: TIBETAN VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+000f7e: TIBETAN SIGN RJES SU NGA RO*/
+ RTUNI_ALPHA, /* U+000f7f: TIBETAN SIGN RNAM BCAD*/
+ RTUNI_ALPHA, /* U+000f80: TIBETAN VOWEL SIGN REVERSED I*/
+ RTUNI_ALPHA, /* U+000f81: TIBETAN VOWEL SIGN REVERSED II*/
+ 0, /* U+000f82: TIBETAN SIGN NYI ZLA NAA DA*/
+ 0, /* U+000f83: TIBETAN SIGN SNA LDAN*/
+ 0, /* U+000f84: TIBETAN MARK HALANTA*/
+ 0, /* U+000f85: TIBETAN MARK PALUTA*/
+ 0, /* U+000f86: TIBETAN SIGN LCI RTAGS*/
+ 0, /* U+000f87: TIBETAN SIGN YANG RTAGS*/
+ RTUNI_ALPHA, /* U+000f88: TIBETAN SIGN LCE TSA CAN*/
+ RTUNI_ALPHA, /* U+000f89: TIBETAN SIGN MCHU CAN*/
+ RTUNI_ALPHA, /* U+000f8a: TIBETAN SIGN GRU CAN RGYINGS*/
+ RTUNI_ALPHA, /* U+000f8b: TIBETAN SIGN GRU MED RGYINGS*/
+ RTUNI_ALPHA, /* U+000f8c: TIBETAN SIGN INVERTED MCHU CAN*/
+ RTUNI_ALPHA, /* U+000f8d: TIBETAN SUBJOINED SIGN LCE TSA CAN*/
+ RTUNI_ALPHA, /* U+000f8e: TIBETAN SUBJOINED SIGN MCHU CAN*/
+ RTUNI_ALPHA, /* U+000f8f: TIBETAN SUBJOINED SIGN INVERTED MCHU CAN*/
+ RTUNI_ALPHA, /* U+000f90: TIBETAN SUBJOINED LETTER KA*/
+ RTUNI_ALPHA, /* U+000f91: TIBETAN SUBJOINED LETTER KHA*/
+ RTUNI_ALPHA, /* U+000f92: TIBETAN SUBJOINED LETTER GA*/
+ RTUNI_ALPHA, /* U+000f93: TIBETAN SUBJOINED LETTER GHA*/
+ RTUNI_ALPHA, /* U+000f94: TIBETAN SUBJOINED LETTER NGA*/
+ RTUNI_ALPHA, /* U+000f95: TIBETAN SUBJOINED LETTER CA*/
+ RTUNI_ALPHA, /* U+000f96: TIBETAN SUBJOINED LETTER CHA*/
+ RTUNI_ALPHA, /* U+000f97: TIBETAN SUBJOINED LETTER JA*/
+ 0, /* U+000f98: */
+ RTUNI_ALPHA, /* U+000f99: TIBETAN SUBJOINED LETTER NYA*/
+ RTUNI_ALPHA, /* U+000f9a: TIBETAN SUBJOINED LETTER TTA*/
+ RTUNI_ALPHA, /* U+000f9b: TIBETAN SUBJOINED LETTER TTHA*/
+ RTUNI_ALPHA, /* U+000f9c: TIBETAN SUBJOINED LETTER DDA*/
+ RTUNI_ALPHA, /* U+000f9d: TIBETAN SUBJOINED LETTER DDHA*/
+ RTUNI_ALPHA, /* U+000f9e: TIBETAN SUBJOINED LETTER NNA*/
+ RTUNI_ALPHA, /* U+000f9f: TIBETAN SUBJOINED LETTER TA*/
+ RTUNI_ALPHA, /* U+000fa0: TIBETAN SUBJOINED LETTER THA*/
+ RTUNI_ALPHA, /* U+000fa1: TIBETAN SUBJOINED LETTER DA*/
+ RTUNI_ALPHA, /* U+000fa2: TIBETAN SUBJOINED LETTER DHA*/
+ RTUNI_ALPHA, /* U+000fa3: TIBETAN SUBJOINED LETTER NA*/
+ RTUNI_ALPHA, /* U+000fa4: TIBETAN SUBJOINED LETTER PA*/
+ RTUNI_ALPHA, /* U+000fa5: TIBETAN SUBJOINED LETTER PHA*/
+ RTUNI_ALPHA, /* U+000fa6: TIBETAN SUBJOINED LETTER BA*/
+ RTUNI_ALPHA, /* U+000fa7: TIBETAN SUBJOINED LETTER BHA*/
+ RTUNI_ALPHA, /* U+000fa8: TIBETAN SUBJOINED LETTER MA*/
+ RTUNI_ALPHA, /* U+000fa9: TIBETAN SUBJOINED LETTER TSA*/
+ RTUNI_ALPHA, /* U+000faa: TIBETAN SUBJOINED LETTER TSHA*/
+ RTUNI_ALPHA, /* U+000fab: TIBETAN SUBJOINED LETTER DZA*/
+ RTUNI_ALPHA, /* U+000fac: TIBETAN SUBJOINED LETTER DZHA*/
+ RTUNI_ALPHA, /* U+000fad: TIBETAN SUBJOINED LETTER WA*/
+ RTUNI_ALPHA, /* U+000fae: TIBETAN SUBJOINED LETTER ZHA*/
+ RTUNI_ALPHA, /* U+000faf: TIBETAN SUBJOINED LETTER ZA*/
+ RTUNI_ALPHA, /* U+000fb0: TIBETAN SUBJOINED LETTER -A*/
+ RTUNI_ALPHA, /* U+000fb1: TIBETAN SUBJOINED LETTER YA*/
+ RTUNI_ALPHA, /* U+000fb2: TIBETAN SUBJOINED LETTER RA*/
+ RTUNI_ALPHA, /* U+000fb3: TIBETAN SUBJOINED LETTER LA*/
+ RTUNI_ALPHA, /* U+000fb4: TIBETAN SUBJOINED LETTER SHA*/
+ RTUNI_ALPHA, /* U+000fb5: TIBETAN SUBJOINED LETTER SSA*/
+ RTUNI_ALPHA, /* U+000fb6: TIBETAN SUBJOINED LETTER SA*/
+ RTUNI_ALPHA, /* U+000fb7: TIBETAN SUBJOINED LETTER HA*/
+ RTUNI_ALPHA, /* U+000fb8: TIBETAN SUBJOINED LETTER A*/
+ RTUNI_ALPHA, /* U+000fb9: TIBETAN SUBJOINED LETTER KSSA*/
+ RTUNI_ALPHA, /* U+000fba: TIBETAN SUBJOINED LETTER FIXED-FORM WA*/
+ RTUNI_ALPHA, /* U+000fbb: TIBETAN SUBJOINED LETTER FIXED-FORM YA*/
+ RTUNI_ALPHA, /* U+000fbc: TIBETAN SUBJOINED LETTER FIXED-FORM RA*/
+ 0, /* U+000fbd: */
+ 0, /* U+000fbe: TIBETAN KU RU KHA*/
+ 0, /* U+000fbf: TIBETAN KU RU KHA BZHI MIG CAN*/
+ 0, /* U+000fc0: TIBETAN CANTILLATION SIGN HEAVY BEAT*/
+ 0, /* U+000fc1: TIBETAN CANTILLATION SIGN LIGHT BEAT*/
+ 0, /* U+000fc2: TIBETAN CANTILLATION SIGN CANG TE-U*/
+ 0, /* U+000fc3: TIBETAN CANTILLATION SIGN SBUB -CHAL*/
+ 0, /* U+000fc4: TIBETAN SYMBOL DRIL BU*/
+ 0, /* U+000fc5: TIBETAN SYMBOL RDO RJE*/
+ 0, /* U+000fc6: TIBETAN SYMBOL PADMA GDAN*/
+ 0, /* U+000fc7: TIBETAN SYMBOL RDO RJE RGYA GRAM*/
+ 0, /* U+000fc8: TIBETAN SYMBOL PHUR PA*/
+ 0, /* U+000fc9: TIBETAN SYMBOL NOR BU*/
+ 0, /* U+000fca: TIBETAN SYMBOL NOR BU NYIS -KHYIL*/
+ 0, /* U+000fcb: TIBETAN SYMBOL NOR BU GSUM -KHYIL*/
+ 0, /* U+000fcc: TIBETAN SYMBOL NOR BU BZHI -KHYIL*/
+ 0, /* U+000fcd: */
+ 0, /* U+000fce: TIBETAN SIGN RDEL NAG RDEL DKAR*/
+ 0, /* U+000fcf: TIBETAN SIGN RDEL NAG GSUM*/
+ 0, /* U+000fd0: TIBETAN MARK BSKA- SHOG GI MGO RGYAN*/
+ 0, /* U+000fd1: TIBETAN MARK MNYAM YIG GI MGO RGYAN*/
+ 0, /* U+000fd2: TIBETAN MARK NYIS TSHEG*/
+ 0, /* U+000fd3: TIBETAN MARK INITIAL BRDA RNYING YIG MGO MDUN MA*/
+ 0, /* U+000fd4: TIBETAN MARK CLOSING BRDA RNYING YIG MGO SGAB MA*/
+ 0, /* U+000fd5: RIGHT-FACING SVASTI SIGN*/
+ 0, /* U+000fd6: LEFT-FACING SVASTI SIGN*/
+ 0, /* U+000fd7: RIGHT-FACING SVASTI SIGN WITH DOTS*/
+ 0, /* U+000fd8: LEFT-FACING SVASTI SIGN WITH DOTS*/
+ 0, /* U+000fd9: TIBETAN MARK LEADING MCHAN RTAGS*/
+ 0, /* U+000fda: TIBETAN MARK TRAILING MCHAN RTAGS*/
+ 0, /* U+000fdb: */
+ 0, /* U+000fdc: */
+ 0, /* U+000fdd: */
+ 0, /* U+000fde: */
+ 0, /* U+000fdf: */
+ 0, /* U+000fe0: */
+ 0, /* U+000fe1: */
+ 0, /* U+000fe2: */
+ 0, /* U+000fe3: */
+ 0, /* U+000fe4: */
+ 0, /* U+000fe5: */
+ 0, /* U+000fe6: */
+ 0, /* U+000fe7: */
+ 0, /* U+000fe8: */
+ 0, /* U+000fe9: */
+ 0, /* U+000fea: */
+ 0, /* U+000feb: */
+ 0, /* U+000fec: */
+ 0, /* U+000fed: */
+ 0, /* U+000fee: */
+ 0, /* U+000fef: */
+ 0, /* U+000ff0: */
+ 0, /* U+000ff1: */
+ 0, /* U+000ff2: */
+ 0, /* U+000ff3: */
+ 0, /* U+000ff4: */
+ 0, /* U+000ff5: */
+ 0, /* U+000ff6: */
+ 0, /* U+000ff7: */
+ 0, /* U+000ff8: */
+ 0, /* U+000ff9: */
+ 0, /* U+000ffa: */
+ 0, /* U+000ffb: */
+ 0, /* U+000ffc: */
+ 0, /* U+000ffd: */
+ 0, /* U+000ffe: */
+ 0, /* U+000fff: */
+ RTUNI_ALPHA, /* U+001000: MYANMAR LETTER KA*/
+ RTUNI_ALPHA, /* U+001001: MYANMAR LETTER KHA*/
+ RTUNI_ALPHA, /* U+001002: MYANMAR LETTER GA*/
+ RTUNI_ALPHA, /* U+001003: MYANMAR LETTER GHA*/
+ RTUNI_ALPHA, /* U+001004: MYANMAR LETTER NGA*/
+ RTUNI_ALPHA, /* U+001005: MYANMAR LETTER CA*/
+ RTUNI_ALPHA, /* U+001006: MYANMAR LETTER CHA*/
+ RTUNI_ALPHA, /* U+001007: MYANMAR LETTER JA*/
+ RTUNI_ALPHA, /* U+001008: MYANMAR LETTER JHA*/
+ RTUNI_ALPHA, /* U+001009: MYANMAR LETTER NYA*/
+ RTUNI_ALPHA, /* U+00100a: MYANMAR LETTER NNYA*/
+ RTUNI_ALPHA, /* U+00100b: MYANMAR LETTER TTA*/
+ RTUNI_ALPHA, /* U+00100c: MYANMAR LETTER TTHA*/
+ RTUNI_ALPHA, /* U+00100d: MYANMAR LETTER DDA*/
+ RTUNI_ALPHA, /* U+00100e: MYANMAR LETTER DDHA*/
+ RTUNI_ALPHA, /* U+00100f: MYANMAR LETTER NNA*/
+ RTUNI_ALPHA, /* U+001010: MYANMAR LETTER TA*/
+ RTUNI_ALPHA, /* U+001011: MYANMAR LETTER THA*/
+ RTUNI_ALPHA, /* U+001012: MYANMAR LETTER DA*/
+ RTUNI_ALPHA, /* U+001013: MYANMAR LETTER DHA*/
+ RTUNI_ALPHA, /* U+001014: MYANMAR LETTER NA*/
+ RTUNI_ALPHA, /* U+001015: MYANMAR LETTER PA*/
+ RTUNI_ALPHA, /* U+001016: MYANMAR LETTER PHA*/
+ RTUNI_ALPHA, /* U+001017: MYANMAR LETTER BA*/
+ RTUNI_ALPHA, /* U+001018: MYANMAR LETTER BHA*/
+ RTUNI_ALPHA, /* U+001019: MYANMAR LETTER MA*/
+ RTUNI_ALPHA, /* U+00101a: MYANMAR LETTER YA*/
+ RTUNI_ALPHA, /* U+00101b: MYANMAR LETTER RA*/
+ RTUNI_ALPHA, /* U+00101c: MYANMAR LETTER LA*/
+ RTUNI_ALPHA, /* U+00101d: MYANMAR LETTER WA*/
+ RTUNI_ALPHA, /* U+00101e: MYANMAR LETTER SA*/
+ RTUNI_ALPHA, /* U+00101f: MYANMAR LETTER HA*/
+ RTUNI_ALPHA, /* U+001020: MYANMAR LETTER LLA*/
+ RTUNI_ALPHA, /* U+001021: MYANMAR LETTER A*/
+ RTUNI_ALPHA, /* U+001022: MYANMAR LETTER SHAN A*/
+ RTUNI_ALPHA, /* U+001023: MYANMAR LETTER I*/
+ RTUNI_ALPHA, /* U+001024: MYANMAR LETTER II*/
+ RTUNI_ALPHA, /* U+001025: MYANMAR LETTER U*/
+ RTUNI_ALPHA, /* U+001026: MYANMAR LETTER UU*/
+ RTUNI_ALPHA, /* U+001027: MYANMAR LETTER E*/
+ RTUNI_ALPHA, /* U+001028: MYANMAR LETTER MON E*/
+ RTUNI_ALPHA, /* U+001029: MYANMAR LETTER O*/
+ RTUNI_ALPHA, /* U+00102a: MYANMAR LETTER AU*/
+ RTUNI_ALPHA, /* U+00102b: MYANMAR VOWEL SIGN TALL AA*/
+ RTUNI_ALPHA, /* U+00102c: MYANMAR VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+00102d: MYANMAR VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+00102e: MYANMAR VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+00102f: MYANMAR VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+001030: MYANMAR VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+001031: MYANMAR VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+001032: MYANMAR VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+001033: MYANMAR VOWEL SIGN MON II*/
+ RTUNI_ALPHA, /* U+001034: MYANMAR VOWEL SIGN MON O*/
+ RTUNI_ALPHA, /* U+001035: MYANMAR VOWEL SIGN E ABOVE*/
+ RTUNI_ALPHA, /* U+001036: MYANMAR SIGN ANUSVARA*/
+ 0, /* U+001037: MYANMAR SIGN DOT BELOW*/
+ RTUNI_ALPHA, /* U+001038: MYANMAR SIGN VISARGA*/
+ 0, /* U+001039: MYANMAR SIGN VIRAMA*/
+ 0, /* U+00103a: MYANMAR SIGN ASAT*/
+ RTUNI_ALPHA, /* U+00103b: MYANMAR CONSONANT SIGN MEDIAL YA*/
+ RTUNI_ALPHA, /* U+00103c: MYANMAR CONSONANT SIGN MEDIAL RA*/
+ RTUNI_ALPHA, /* U+00103d: MYANMAR CONSONANT SIGN MEDIAL WA*/
+ RTUNI_ALPHA, /* U+00103e: MYANMAR CONSONANT SIGN MEDIAL HA*/
+ RTUNI_ALPHA, /* U+00103f: MYANMAR LETTER GREAT SA*/
+ RTUNI_DDIGIT, /* U+001040: MYANMAR DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001041: MYANMAR DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001042: MYANMAR DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001043: MYANMAR DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+001044: MYANMAR DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+001045: MYANMAR DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+001046: MYANMAR DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+001047: MYANMAR DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+001048: MYANMAR DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+001049: MYANMAR DIGIT NINE*/
+ 0, /* U+00104a: MYANMAR SIGN LITTLE SECTION*/
+ 0, /* U+00104b: MYANMAR SIGN SECTION*/
+ 0, /* U+00104c: MYANMAR SYMBOL LOCATIVE*/
+ 0, /* U+00104d: MYANMAR SYMBOL COMPLETED*/
+ 0, /* U+00104e: MYANMAR SYMBOL AFOREMENTIONED*/
+ 0, /* U+00104f: MYANMAR SYMBOL GENITIVE*/
+ RTUNI_ALPHA, /* U+001050: MYANMAR LETTER SHA*/
+ RTUNI_ALPHA, /* U+001051: MYANMAR LETTER SSA*/
+ RTUNI_ALPHA, /* U+001052: MYANMAR LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+001053: MYANMAR LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+001054: MYANMAR LETTER VOCALIC L*/
+ RTUNI_ALPHA, /* U+001055: MYANMAR LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+001056: MYANMAR VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+001057: MYANMAR VOWEL SIGN VOCALIC RR*/
+ RTUNI_ALPHA, /* U+001058: MYANMAR VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+001059: MYANMAR VOWEL SIGN VOCALIC LL*/
+ RTUNI_ALPHA, /* U+00105a: MYANMAR LETTER MON NGA*/
+ RTUNI_ALPHA, /* U+00105b: MYANMAR LETTER MON JHA*/
+ RTUNI_ALPHA, /* U+00105c: MYANMAR LETTER MON BBA*/
+ RTUNI_ALPHA, /* U+00105d: MYANMAR LETTER MON BBE*/
+ RTUNI_ALPHA, /* U+00105e: MYANMAR CONSONANT SIGN MON MEDIAL NA*/
+ RTUNI_ALPHA, /* U+00105f: MYANMAR CONSONANT SIGN MON MEDIAL MA*/
+ RTUNI_ALPHA, /* U+001060: MYANMAR CONSONANT SIGN MON MEDIAL LA*/
+ RTUNI_ALPHA, /* U+001061: MYANMAR LETTER SGAW KAREN SHA*/
+ RTUNI_ALPHA, /* U+001062: MYANMAR VOWEL SIGN SGAW KAREN EU*/
+ 0, /* U+001063: MYANMAR TONE MARK SGAW KAREN HATHI*/
+ 0, /* U+001064: MYANMAR TONE MARK SGAW KAREN KE PHO*/
+ RTUNI_ALPHA, /* U+001065: MYANMAR LETTER WESTERN PWO KAREN THA*/
+ RTUNI_ALPHA, /* U+001066: MYANMAR LETTER WESTERN PWO KAREN PWA*/
+ RTUNI_ALPHA, /* U+001067: MYANMAR VOWEL SIGN WESTERN PWO KAREN EU*/
+ RTUNI_ALPHA, /* U+001068: MYANMAR VOWEL SIGN WESTERN PWO KAREN UE*/
+ 0, /* U+001069: MYANMAR SIGN WESTERN PWO KAREN TONE-1*/
+ 0, /* U+00106a: MYANMAR SIGN WESTERN PWO KAREN TONE-2*/
+ 0, /* U+00106b: MYANMAR SIGN WESTERN PWO KAREN TONE-3*/
+ 0, /* U+00106c: MYANMAR SIGN WESTERN PWO KAREN TONE-4*/
+ 0, /* U+00106d: MYANMAR SIGN WESTERN PWO KAREN TONE-5*/
+ RTUNI_ALPHA, /* U+00106e: MYANMAR LETTER EASTERN PWO KAREN NNA*/
+ RTUNI_ALPHA, /* U+00106f: MYANMAR LETTER EASTERN PWO KAREN YWA*/
+ RTUNI_ALPHA, /* U+001070: MYANMAR LETTER EASTERN PWO KAREN GHWA*/
+ RTUNI_ALPHA, /* U+001071: MYANMAR VOWEL SIGN GEBA KAREN I*/
+ RTUNI_ALPHA, /* U+001072: MYANMAR VOWEL SIGN KAYAH OE*/
+ RTUNI_ALPHA, /* U+001073: MYANMAR VOWEL SIGN KAYAH U*/
+ RTUNI_ALPHA, /* U+001074: MYANMAR VOWEL SIGN KAYAH EE*/
+ RTUNI_ALPHA, /* U+001075: MYANMAR LETTER SHAN KA*/
+ RTUNI_ALPHA, /* U+001076: MYANMAR LETTER SHAN KHA*/
+ RTUNI_ALPHA, /* U+001077: MYANMAR LETTER SHAN GA*/
+ RTUNI_ALPHA, /* U+001078: MYANMAR LETTER SHAN CA*/
+ RTUNI_ALPHA, /* U+001079: MYANMAR LETTER SHAN ZA*/
+ RTUNI_ALPHA, /* U+00107a: MYANMAR LETTER SHAN NYA*/
+ RTUNI_ALPHA, /* U+00107b: MYANMAR LETTER SHAN DA*/
+ RTUNI_ALPHA, /* U+00107c: MYANMAR LETTER SHAN NA*/
+ RTUNI_ALPHA, /* U+00107d: MYANMAR LETTER SHAN PHA*/
+ RTUNI_ALPHA, /* U+00107e: MYANMAR LETTER SHAN FA*/
+ RTUNI_ALPHA, /* U+00107f: MYANMAR LETTER SHAN BA*/
+ RTUNI_ALPHA, /* U+001080: MYANMAR LETTER SHAN THA*/
+ RTUNI_ALPHA, /* U+001081: MYANMAR LETTER SHAN HA*/
+ RTUNI_ALPHA, /* U+001082: MYANMAR CONSONANT SIGN SHAN MEDIAL WA*/
+ RTUNI_ALPHA, /* U+001083: MYANMAR VOWEL SIGN SHAN AA*/
+ RTUNI_ALPHA, /* U+001084: MYANMAR VOWEL SIGN SHAN E*/
+ RTUNI_ALPHA, /* U+001085: MYANMAR VOWEL SIGN SHAN E ABOVE*/
+ RTUNI_ALPHA, /* U+001086: MYANMAR VOWEL SIGN SHAN FINAL Y*/
+ 0, /* U+001087: MYANMAR SIGN SHAN TONE-2*/
+ 0, /* U+001088: MYANMAR SIGN SHAN TONE-3*/
+ 0, /* U+001089: MYANMAR SIGN SHAN TONE-5*/
+ 0, /* U+00108a: MYANMAR SIGN SHAN TONE-6*/
+ 0, /* U+00108b: MYANMAR SIGN SHAN COUNCIL TONE-2*/
+ 0, /* U+00108c: MYANMAR SIGN SHAN COUNCIL TONE-3*/
+ 0, /* U+00108d: MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE*/
+ RTUNI_ALPHA, /* U+00108e: MYANMAR LETTER RUMAI PALAUNG FA*/
+ 0, /* U+00108f: MYANMAR SIGN RUMAI PALAUNG TONE-5*/
+ RTUNI_DDIGIT, /* U+001090: MYANMAR SHAN DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001091: MYANMAR SHAN DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001092: MYANMAR SHAN DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001093: MYANMAR SHAN DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+001094: MYANMAR SHAN DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+001095: MYANMAR SHAN DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+001096: MYANMAR SHAN DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+001097: MYANMAR SHAN DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+001098: MYANMAR SHAN DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+001099: MYANMAR SHAN DIGIT NINE*/
+ 0, /* U+00109a: MYANMAR SIGN KHAMTI TONE-1*/
+ 0, /* U+00109b: MYANMAR SIGN KHAMTI TONE-3*/
+ RTUNI_ALPHA, /* U+00109c: MYANMAR VOWEL SIGN AITON A*/
+ RTUNI_ALPHA, /* U+00109d: MYANMAR VOWEL SIGN AITON AI*/
+ 0, /* U+00109e: MYANMAR SYMBOL SHAN ONE*/
+ 0, /* U+00109f: MYANMAR SYMBOL SHAN EXCLAMATION*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a0: GEORGIAN CAPITAL LETTER AN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a1: GEORGIAN CAPITAL LETTER BAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a2: GEORGIAN CAPITAL LETTER GAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a3: GEORGIAN CAPITAL LETTER DON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a4: GEORGIAN CAPITAL LETTER EN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a5: GEORGIAN CAPITAL LETTER VIN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a6: GEORGIAN CAPITAL LETTER ZEN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a7: GEORGIAN CAPITAL LETTER TAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a8: GEORGIAN CAPITAL LETTER IN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010a9: GEORGIAN CAPITAL LETTER KAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010aa: GEORGIAN CAPITAL LETTER LAS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010ab: GEORGIAN CAPITAL LETTER MAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010ac: GEORGIAN CAPITAL LETTER NAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010ad: GEORGIAN CAPITAL LETTER ON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010ae: GEORGIAN CAPITAL LETTER PAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010af: GEORGIAN CAPITAL LETTER ZHAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b0: GEORGIAN CAPITAL LETTER RAE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b1: GEORGIAN CAPITAL LETTER SAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b2: GEORGIAN CAPITAL LETTER TAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b3: GEORGIAN CAPITAL LETTER UN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b4: GEORGIAN CAPITAL LETTER PHAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b5: GEORGIAN CAPITAL LETTER KHAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b6: GEORGIAN CAPITAL LETTER GHAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b7: GEORGIAN CAPITAL LETTER QAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b8: GEORGIAN CAPITAL LETTER SHIN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010b9: GEORGIAN CAPITAL LETTER CHIN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010ba: GEORGIAN CAPITAL LETTER CAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010bb: GEORGIAN CAPITAL LETTER JIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010bc: GEORGIAN CAPITAL LETTER CIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010bd: GEORGIAN CAPITAL LETTER CHAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010be: GEORGIAN CAPITAL LETTER XAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010bf: GEORGIAN CAPITAL LETTER JHAN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010c0: GEORGIAN CAPITAL LETTER HAE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010c1: GEORGIAN CAPITAL LETTER HE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010c2: GEORGIAN CAPITAL LETTER HIE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010c3: GEORGIAN CAPITAL LETTER WE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010c4: GEORGIAN CAPITAL LETTER HAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010c5: GEORGIAN CAPITAL LETTER HOE*/
+ 0, /* U+0010c6: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010c7: GEORGIAN CAPITAL LETTER YN*/
+ 0, /* U+0010c8: */
+ 0, /* U+0010c9: */
+ 0, /* U+0010ca: */
+ 0, /* U+0010cb: */
+ 0, /* U+0010cc: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0010cd: GEORGIAN CAPITAL LETTER AEN*/
+ 0, /* U+0010ce: */
+ 0, /* U+0010cf: */
+ RTUNI_ALPHA, /* U+0010d0: GEORGIAN LETTER AN*/
+ RTUNI_ALPHA, /* U+0010d1: GEORGIAN LETTER BAN*/
+ RTUNI_ALPHA, /* U+0010d2: GEORGIAN LETTER GAN*/
+ RTUNI_ALPHA, /* U+0010d3: GEORGIAN LETTER DON*/
+ RTUNI_ALPHA, /* U+0010d4: GEORGIAN LETTER EN*/
+ RTUNI_ALPHA, /* U+0010d5: GEORGIAN LETTER VIN*/
+ RTUNI_ALPHA, /* U+0010d6: GEORGIAN LETTER ZEN*/
+ RTUNI_ALPHA, /* U+0010d7: GEORGIAN LETTER TAN*/
+ RTUNI_ALPHA, /* U+0010d8: GEORGIAN LETTER IN*/
+ RTUNI_ALPHA, /* U+0010d9: GEORGIAN LETTER KAN*/
+ RTUNI_ALPHA, /* U+0010da: GEORGIAN LETTER LAS*/
+ RTUNI_ALPHA, /* U+0010db: GEORGIAN LETTER MAN*/
+ RTUNI_ALPHA, /* U+0010dc: GEORGIAN LETTER NAR*/
+ RTUNI_ALPHA, /* U+0010dd: GEORGIAN LETTER ON*/
+ RTUNI_ALPHA, /* U+0010de: GEORGIAN LETTER PAR*/
+ RTUNI_ALPHA, /* U+0010df: GEORGIAN LETTER ZHAR*/
+ RTUNI_ALPHA, /* U+0010e0: GEORGIAN LETTER RAE*/
+ RTUNI_ALPHA, /* U+0010e1: GEORGIAN LETTER SAN*/
+ RTUNI_ALPHA, /* U+0010e2: GEORGIAN LETTER TAR*/
+ RTUNI_ALPHA, /* U+0010e3: GEORGIAN LETTER UN*/
+ RTUNI_ALPHA, /* U+0010e4: GEORGIAN LETTER PHAR*/
+ RTUNI_ALPHA, /* U+0010e5: GEORGIAN LETTER KHAR*/
+ RTUNI_ALPHA, /* U+0010e6: GEORGIAN LETTER GHAN*/
+ RTUNI_ALPHA, /* U+0010e7: GEORGIAN LETTER QAR*/
+ RTUNI_ALPHA, /* U+0010e8: GEORGIAN LETTER SHIN*/
+ RTUNI_ALPHA, /* U+0010e9: GEORGIAN LETTER CHIN*/
+ RTUNI_ALPHA, /* U+0010ea: GEORGIAN LETTER CAN*/
+ RTUNI_ALPHA, /* U+0010eb: GEORGIAN LETTER JIL*/
+ RTUNI_ALPHA, /* U+0010ec: GEORGIAN LETTER CIL*/
+ RTUNI_ALPHA, /* U+0010ed: GEORGIAN LETTER CHAR*/
+ RTUNI_ALPHA, /* U+0010ee: GEORGIAN LETTER XAN*/
+ RTUNI_ALPHA, /* U+0010ef: GEORGIAN LETTER JHAN*/
+ RTUNI_ALPHA, /* U+0010f0: GEORGIAN LETTER HAE*/
+ RTUNI_ALPHA, /* U+0010f1: GEORGIAN LETTER HE*/
+ RTUNI_ALPHA, /* U+0010f2: GEORGIAN LETTER HIE*/
+ RTUNI_ALPHA, /* U+0010f3: GEORGIAN LETTER WE*/
+ RTUNI_ALPHA, /* U+0010f4: GEORGIAN LETTER HAR*/
+ RTUNI_ALPHA, /* U+0010f5: GEORGIAN LETTER HOE*/
+ RTUNI_ALPHA, /* U+0010f6: GEORGIAN LETTER FI*/
+ RTUNI_ALPHA, /* U+0010f7: GEORGIAN LETTER YN*/
+ RTUNI_ALPHA, /* U+0010f8: GEORGIAN LETTER ELIFI*/
+ RTUNI_ALPHA, /* U+0010f9: GEORGIAN LETTER TURNED GAN*/
+ RTUNI_ALPHA, /* U+0010fa: GEORGIAN LETTER AIN*/
+ 0, /* U+0010fb: GEORGIAN PARAGRAPH SEPARATOR*/
+ RTUNI_ALPHA, /* U+0010fc: MODIFIER LETTER GEORGIAN NAR*/
+ RTUNI_ALPHA, /* U+0010fd: GEORGIAN LETTER AEN*/
+ RTUNI_ALPHA, /* U+0010fe: GEORGIAN LETTER HARD SIGN*/
+ RTUNI_ALPHA, /* U+0010ff: GEORGIAN LETTER LABIAL SIGN*/
+ RTUNI_ALPHA, /* U+001100: HANGUL CHOSEONG KIYEOK*/
+ RTUNI_ALPHA, /* U+001101: HANGUL CHOSEONG SSANGKIYEOK*/
+ RTUNI_ALPHA, /* U+001102: HANGUL CHOSEONG NIEUN*/
+ RTUNI_ALPHA, /* U+001103: HANGUL CHOSEONG TIKEUT*/
+ RTUNI_ALPHA, /* U+001104: HANGUL CHOSEONG SSANGTIKEUT*/
+ RTUNI_ALPHA, /* U+001105: HANGUL CHOSEONG RIEUL*/
+ RTUNI_ALPHA, /* U+001106: HANGUL CHOSEONG MIEUM*/
+ RTUNI_ALPHA, /* U+001107: HANGUL CHOSEONG PIEUP*/
+ RTUNI_ALPHA, /* U+001108: HANGUL CHOSEONG SSANGPIEUP*/
+ RTUNI_ALPHA, /* U+001109: HANGUL CHOSEONG SIOS*/
+ RTUNI_ALPHA, /* U+00110a: HANGUL CHOSEONG SSANGSIOS*/
+ RTUNI_ALPHA, /* U+00110b: HANGUL CHOSEONG IEUNG*/
+ RTUNI_ALPHA, /* U+00110c: HANGUL CHOSEONG CIEUC*/
+ RTUNI_ALPHA, /* U+00110d: HANGUL CHOSEONG SSANGCIEUC*/
+ RTUNI_ALPHA, /* U+00110e: HANGUL CHOSEONG CHIEUCH*/
+ RTUNI_ALPHA, /* U+00110f: HANGUL CHOSEONG KHIEUKH*/
+ RTUNI_ALPHA, /* U+001110: HANGUL CHOSEONG THIEUTH*/
+ RTUNI_ALPHA, /* U+001111: HANGUL CHOSEONG PHIEUPH*/
+ RTUNI_ALPHA, /* U+001112: HANGUL CHOSEONG HIEUH*/
+ RTUNI_ALPHA, /* U+001113: HANGUL CHOSEONG NIEUN-KIYEOK*/
+ RTUNI_ALPHA, /* U+001114: HANGUL CHOSEONG SSANGNIEUN*/
+ RTUNI_ALPHA, /* U+001115: HANGUL CHOSEONG NIEUN-TIKEUT*/
+ RTUNI_ALPHA, /* U+001116: HANGUL CHOSEONG NIEUN-PIEUP*/
+ RTUNI_ALPHA, /* U+001117: HANGUL CHOSEONG TIKEUT-KIYEOK*/
+ RTUNI_ALPHA, /* U+001118: HANGUL CHOSEONG RIEUL-NIEUN*/
+ RTUNI_ALPHA, /* U+001119: HANGUL CHOSEONG SSANGRIEUL*/
+ RTUNI_ALPHA, /* U+00111a: HANGUL CHOSEONG RIEUL-HIEUH*/
+ RTUNI_ALPHA, /* U+00111b: HANGUL CHOSEONG KAPYEOUNRIEUL*/
+ RTUNI_ALPHA, /* U+00111c: HANGUL CHOSEONG MIEUM-PIEUP*/
+ RTUNI_ALPHA, /* U+00111d: HANGUL CHOSEONG KAPYEOUNMIEUM*/
+ RTUNI_ALPHA, /* U+00111e: HANGUL CHOSEONG PIEUP-KIYEOK*/
+ RTUNI_ALPHA, /* U+00111f: HANGUL CHOSEONG PIEUP-NIEUN*/
+ RTUNI_ALPHA, /* U+001120: HANGUL CHOSEONG PIEUP-TIKEUT*/
+ RTUNI_ALPHA, /* U+001121: HANGUL CHOSEONG PIEUP-SIOS*/
+ RTUNI_ALPHA, /* U+001122: HANGUL CHOSEONG PIEUP-SIOS-KIYEOK*/
+ RTUNI_ALPHA, /* U+001123: HANGUL CHOSEONG PIEUP-SIOS-TIKEUT*/
+ RTUNI_ALPHA, /* U+001124: HANGUL CHOSEONG PIEUP-SIOS-PIEUP*/
+ RTUNI_ALPHA, /* U+001125: HANGUL CHOSEONG PIEUP-SSANGSIOS*/
+ RTUNI_ALPHA, /* U+001126: HANGUL CHOSEONG PIEUP-SIOS-CIEUC*/
+ RTUNI_ALPHA, /* U+001127: HANGUL CHOSEONG PIEUP-CIEUC*/
+ RTUNI_ALPHA, /* U+001128: HANGUL CHOSEONG PIEUP-CHIEUCH*/
+ RTUNI_ALPHA, /* U+001129: HANGUL CHOSEONG PIEUP-THIEUTH*/
+ RTUNI_ALPHA, /* U+00112a: HANGUL CHOSEONG PIEUP-PHIEUPH*/
+ RTUNI_ALPHA, /* U+00112b: HANGUL CHOSEONG KAPYEOUNPIEUP*/
+ RTUNI_ALPHA, /* U+00112c: HANGUL CHOSEONG KAPYEOUNSSANGPIEUP*/
+ RTUNI_ALPHA, /* U+00112d: HANGUL CHOSEONG SIOS-KIYEOK*/
+ RTUNI_ALPHA, /* U+00112e: HANGUL CHOSEONG SIOS-NIEUN*/
+ RTUNI_ALPHA, /* U+00112f: HANGUL CHOSEONG SIOS-TIKEUT*/
+ RTUNI_ALPHA, /* U+001130: HANGUL CHOSEONG SIOS-RIEUL*/
+ RTUNI_ALPHA, /* U+001131: HANGUL CHOSEONG SIOS-MIEUM*/
+ RTUNI_ALPHA, /* U+001132: HANGUL CHOSEONG SIOS-PIEUP*/
+ RTUNI_ALPHA, /* U+001133: HANGUL CHOSEONG SIOS-PIEUP-KIYEOK*/
+ RTUNI_ALPHA, /* U+001134: HANGUL CHOSEONG SIOS-SSANGSIOS*/
+ RTUNI_ALPHA, /* U+001135: HANGUL CHOSEONG SIOS-IEUNG*/
+ RTUNI_ALPHA, /* U+001136: HANGUL CHOSEONG SIOS-CIEUC*/
+ RTUNI_ALPHA, /* U+001137: HANGUL CHOSEONG SIOS-CHIEUCH*/
+ RTUNI_ALPHA, /* U+001138: HANGUL CHOSEONG SIOS-KHIEUKH*/
+ RTUNI_ALPHA, /* U+001139: HANGUL CHOSEONG SIOS-THIEUTH*/
+ RTUNI_ALPHA, /* U+00113a: HANGUL CHOSEONG SIOS-PHIEUPH*/
+ RTUNI_ALPHA, /* U+00113b: HANGUL CHOSEONG SIOS-HIEUH*/
+ RTUNI_ALPHA, /* U+00113c: HANGUL CHOSEONG CHITUEUMSIOS*/
+ RTUNI_ALPHA, /* U+00113d: HANGUL CHOSEONG CHITUEUMSSANGSIOS*/
+ RTUNI_ALPHA, /* U+00113e: HANGUL CHOSEONG CEONGCHIEUMSIOS*/
+ RTUNI_ALPHA, /* U+00113f: HANGUL CHOSEONG CEONGCHIEUMSSANGSIOS*/
+ RTUNI_ALPHA, /* U+001140: HANGUL CHOSEONG PANSIOS*/
+ RTUNI_ALPHA, /* U+001141: HANGUL CHOSEONG IEUNG-KIYEOK*/
+ RTUNI_ALPHA, /* U+001142: HANGUL CHOSEONG IEUNG-TIKEUT*/
+ RTUNI_ALPHA, /* U+001143: HANGUL CHOSEONG IEUNG-MIEUM*/
+ RTUNI_ALPHA, /* U+001144: HANGUL CHOSEONG IEUNG-PIEUP*/
+ RTUNI_ALPHA, /* U+001145: HANGUL CHOSEONG IEUNG-SIOS*/
+ RTUNI_ALPHA, /* U+001146: HANGUL CHOSEONG IEUNG-PANSIOS*/
+ RTUNI_ALPHA, /* U+001147: HANGUL CHOSEONG SSANGIEUNG*/
+ RTUNI_ALPHA, /* U+001148: HANGUL CHOSEONG IEUNG-CIEUC*/
+ RTUNI_ALPHA, /* U+001149: HANGUL CHOSEONG IEUNG-CHIEUCH*/
+ RTUNI_ALPHA, /* U+00114a: HANGUL CHOSEONG IEUNG-THIEUTH*/
+ RTUNI_ALPHA, /* U+00114b: HANGUL CHOSEONG IEUNG-PHIEUPH*/
+ RTUNI_ALPHA, /* U+00114c: HANGUL CHOSEONG YESIEUNG*/
+ RTUNI_ALPHA, /* U+00114d: HANGUL CHOSEONG CIEUC-IEUNG*/
+ RTUNI_ALPHA, /* U+00114e: HANGUL CHOSEONG CHITUEUMCIEUC*/
+ RTUNI_ALPHA, /* U+00114f: HANGUL CHOSEONG CHITUEUMSSANGCIEUC*/
+ RTUNI_ALPHA, /* U+001150: HANGUL CHOSEONG CEONGCHIEUMCIEUC*/
+ RTUNI_ALPHA, /* U+001151: HANGUL CHOSEONG CEONGCHIEUMSSANGCIEUC*/
+ RTUNI_ALPHA, /* U+001152: HANGUL CHOSEONG CHIEUCH-KHIEUKH*/
+ RTUNI_ALPHA, /* U+001153: HANGUL CHOSEONG CHIEUCH-HIEUH*/
+ RTUNI_ALPHA, /* U+001154: HANGUL CHOSEONG CHITUEUMCHIEUCH*/
+ RTUNI_ALPHA, /* U+001155: HANGUL CHOSEONG CEONGCHIEUMCHIEUCH*/
+ RTUNI_ALPHA, /* U+001156: HANGUL CHOSEONG PHIEUPH-PIEUP*/
+ RTUNI_ALPHA, /* U+001157: HANGUL CHOSEONG KAPYEOUNPHIEUPH*/
+ RTUNI_ALPHA, /* U+001158: HANGUL CHOSEONG SSANGHIEUH*/
+ RTUNI_ALPHA, /* U+001159: HANGUL CHOSEONG YEORINHIEUH*/
+ RTUNI_ALPHA, /* U+00115a: HANGUL CHOSEONG KIYEOK-TIKEUT*/
+ RTUNI_ALPHA, /* U+00115b: HANGUL CHOSEONG NIEUN-SIOS*/
+ RTUNI_ALPHA, /* U+00115c: HANGUL CHOSEONG NIEUN-CIEUC*/
+ RTUNI_ALPHA, /* U+00115d: HANGUL CHOSEONG NIEUN-HIEUH*/
+ RTUNI_ALPHA, /* U+00115e: HANGUL CHOSEONG TIKEUT-RIEUL*/
+ RTUNI_ALPHA, /* U+00115f: HANGUL CHOSEONG FILLER*/
+ RTUNI_ALPHA, /* U+001160: HANGUL JUNGSEONG FILLER*/
+ RTUNI_ALPHA, /* U+001161: HANGUL JUNGSEONG A*/
+ RTUNI_ALPHA, /* U+001162: HANGUL JUNGSEONG AE*/
+ RTUNI_ALPHA, /* U+001163: HANGUL JUNGSEONG YA*/
+ RTUNI_ALPHA, /* U+001164: HANGUL JUNGSEONG YAE*/
+ RTUNI_ALPHA, /* U+001165: HANGUL JUNGSEONG EO*/
+ RTUNI_ALPHA, /* U+001166: HANGUL JUNGSEONG E*/
+ RTUNI_ALPHA, /* U+001167: HANGUL JUNGSEONG YEO*/
+ RTUNI_ALPHA, /* U+001168: HANGUL JUNGSEONG YE*/
+ RTUNI_ALPHA, /* U+001169: HANGUL JUNGSEONG O*/
+ RTUNI_ALPHA, /* U+00116a: HANGUL JUNGSEONG WA*/
+ RTUNI_ALPHA, /* U+00116b: HANGUL JUNGSEONG WAE*/
+ RTUNI_ALPHA, /* U+00116c: HANGUL JUNGSEONG OE*/
+ RTUNI_ALPHA, /* U+00116d: HANGUL JUNGSEONG YO*/
+ RTUNI_ALPHA, /* U+00116e: HANGUL JUNGSEONG U*/
+ RTUNI_ALPHA, /* U+00116f: HANGUL JUNGSEONG WEO*/
+ RTUNI_ALPHA, /* U+001170: HANGUL JUNGSEONG WE*/
+ RTUNI_ALPHA, /* U+001171: HANGUL JUNGSEONG WI*/
+ RTUNI_ALPHA, /* U+001172: HANGUL JUNGSEONG YU*/
+ RTUNI_ALPHA, /* U+001173: HANGUL JUNGSEONG EU*/
+ RTUNI_ALPHA, /* U+001174: HANGUL JUNGSEONG YI*/
+ RTUNI_ALPHA, /* U+001175: HANGUL JUNGSEONG I*/
+ RTUNI_ALPHA, /* U+001176: HANGUL JUNGSEONG A-O*/
+ RTUNI_ALPHA, /* U+001177: HANGUL JUNGSEONG A-U*/
+ RTUNI_ALPHA, /* U+001178: HANGUL JUNGSEONG YA-O*/
+ RTUNI_ALPHA, /* U+001179: HANGUL JUNGSEONG YA-YO*/
+ RTUNI_ALPHA, /* U+00117a: HANGUL JUNGSEONG EO-O*/
+ RTUNI_ALPHA, /* U+00117b: HANGUL JUNGSEONG EO-U*/
+ RTUNI_ALPHA, /* U+00117c: HANGUL JUNGSEONG EO-EU*/
+ RTUNI_ALPHA, /* U+00117d: HANGUL JUNGSEONG YEO-O*/
+ RTUNI_ALPHA, /* U+00117e: HANGUL JUNGSEONG YEO-U*/
+ RTUNI_ALPHA, /* U+00117f: HANGUL JUNGSEONG O-EO*/
+ RTUNI_ALPHA, /* U+001180: HANGUL JUNGSEONG O-E*/
+ RTUNI_ALPHA, /* U+001181: HANGUL JUNGSEONG O-YE*/
+ RTUNI_ALPHA, /* U+001182: HANGUL JUNGSEONG O-O*/
+ RTUNI_ALPHA, /* U+001183: HANGUL JUNGSEONG O-U*/
+ RTUNI_ALPHA, /* U+001184: HANGUL JUNGSEONG YO-YA*/
+ RTUNI_ALPHA, /* U+001185: HANGUL JUNGSEONG YO-YAE*/
+ RTUNI_ALPHA, /* U+001186: HANGUL JUNGSEONG YO-YEO*/
+ RTUNI_ALPHA, /* U+001187: HANGUL JUNGSEONG YO-O*/
+ RTUNI_ALPHA, /* U+001188: HANGUL JUNGSEONG YO-I*/
+ RTUNI_ALPHA, /* U+001189: HANGUL JUNGSEONG U-A*/
+ RTUNI_ALPHA, /* U+00118a: HANGUL JUNGSEONG U-AE*/
+ RTUNI_ALPHA, /* U+00118b: HANGUL JUNGSEONG U-EO-EU*/
+ RTUNI_ALPHA, /* U+00118c: HANGUL JUNGSEONG U-YE*/
+ RTUNI_ALPHA, /* U+00118d: HANGUL JUNGSEONG U-U*/
+ RTUNI_ALPHA, /* U+00118e: HANGUL JUNGSEONG YU-A*/
+ RTUNI_ALPHA, /* U+00118f: HANGUL JUNGSEONG YU-EO*/
+ RTUNI_ALPHA, /* U+001190: HANGUL JUNGSEONG YU-E*/
+ RTUNI_ALPHA, /* U+001191: HANGUL JUNGSEONG YU-YEO*/
+ RTUNI_ALPHA, /* U+001192: HANGUL JUNGSEONG YU-YE*/
+ RTUNI_ALPHA, /* U+001193: HANGUL JUNGSEONG YU-U*/
+ RTUNI_ALPHA, /* U+001194: HANGUL JUNGSEONG YU-I*/
+ RTUNI_ALPHA, /* U+001195: HANGUL JUNGSEONG EU-U*/
+ RTUNI_ALPHA, /* U+001196: HANGUL JUNGSEONG EU-EU*/
+ RTUNI_ALPHA, /* U+001197: HANGUL JUNGSEONG YI-U*/
+ RTUNI_ALPHA, /* U+001198: HANGUL JUNGSEONG I-A*/
+ RTUNI_ALPHA, /* U+001199: HANGUL JUNGSEONG I-YA*/
+ RTUNI_ALPHA, /* U+00119a: HANGUL JUNGSEONG I-O*/
+ RTUNI_ALPHA, /* U+00119b: HANGUL JUNGSEONG I-U*/
+ RTUNI_ALPHA, /* U+00119c: HANGUL JUNGSEONG I-EU*/
+ RTUNI_ALPHA, /* U+00119d: HANGUL JUNGSEONG I-ARAEA*/
+ RTUNI_ALPHA, /* U+00119e: HANGUL JUNGSEONG ARAEA*/
+ RTUNI_ALPHA, /* U+00119f: HANGUL JUNGSEONG ARAEA-EO*/
+ RTUNI_ALPHA, /* U+0011a0: HANGUL JUNGSEONG ARAEA-U*/
+ RTUNI_ALPHA, /* U+0011a1: HANGUL JUNGSEONG ARAEA-I*/
+ RTUNI_ALPHA, /* U+0011a2: HANGUL JUNGSEONG SSANGARAEA*/
+ RTUNI_ALPHA, /* U+0011a3: HANGUL JUNGSEONG A-EU*/
+ RTUNI_ALPHA, /* U+0011a4: HANGUL JUNGSEONG YA-U*/
+ RTUNI_ALPHA, /* U+0011a5: HANGUL JUNGSEONG YEO-YA*/
+ RTUNI_ALPHA, /* U+0011a6: HANGUL JUNGSEONG O-YA*/
+ RTUNI_ALPHA, /* U+0011a7: HANGUL JUNGSEONG O-YAE*/
+ RTUNI_ALPHA, /* U+0011a8: HANGUL JONGSEONG KIYEOK*/
+ RTUNI_ALPHA, /* U+0011a9: HANGUL JONGSEONG SSANGKIYEOK*/
+ RTUNI_ALPHA, /* U+0011aa: HANGUL JONGSEONG KIYEOK-SIOS*/
+ RTUNI_ALPHA, /* U+0011ab: HANGUL JONGSEONG NIEUN*/
+ RTUNI_ALPHA, /* U+0011ac: HANGUL JONGSEONG NIEUN-CIEUC*/
+ RTUNI_ALPHA, /* U+0011ad: HANGUL JONGSEONG NIEUN-HIEUH*/
+ RTUNI_ALPHA, /* U+0011ae: HANGUL JONGSEONG TIKEUT*/
+ RTUNI_ALPHA, /* U+0011af: HANGUL JONGSEONG RIEUL*/
+ RTUNI_ALPHA, /* U+0011b0: HANGUL JONGSEONG RIEUL-KIYEOK*/
+ RTUNI_ALPHA, /* U+0011b1: HANGUL JONGSEONG RIEUL-MIEUM*/
+ RTUNI_ALPHA, /* U+0011b2: HANGUL JONGSEONG RIEUL-PIEUP*/
+ RTUNI_ALPHA, /* U+0011b3: HANGUL JONGSEONG RIEUL-SIOS*/
+ RTUNI_ALPHA, /* U+0011b4: HANGUL JONGSEONG RIEUL-THIEUTH*/
+ RTUNI_ALPHA, /* U+0011b5: HANGUL JONGSEONG RIEUL-PHIEUPH*/
+ RTUNI_ALPHA, /* U+0011b6: HANGUL JONGSEONG RIEUL-HIEUH*/
+ RTUNI_ALPHA, /* U+0011b7: HANGUL JONGSEONG MIEUM*/
+ RTUNI_ALPHA, /* U+0011b8: HANGUL JONGSEONG PIEUP*/
+ RTUNI_ALPHA, /* U+0011b9: HANGUL JONGSEONG PIEUP-SIOS*/
+ RTUNI_ALPHA, /* U+0011ba: HANGUL JONGSEONG SIOS*/
+ RTUNI_ALPHA, /* U+0011bb: HANGUL JONGSEONG SSANGSIOS*/
+ RTUNI_ALPHA, /* U+0011bc: HANGUL JONGSEONG IEUNG*/
+ RTUNI_ALPHA, /* U+0011bd: HANGUL JONGSEONG CIEUC*/
+ RTUNI_ALPHA, /* U+0011be: HANGUL JONGSEONG CHIEUCH*/
+ RTUNI_ALPHA, /* U+0011bf: HANGUL JONGSEONG KHIEUKH*/
+ RTUNI_ALPHA, /* U+0011c0: HANGUL JONGSEONG THIEUTH*/
+ RTUNI_ALPHA, /* U+0011c1: HANGUL JONGSEONG PHIEUPH*/
+ RTUNI_ALPHA, /* U+0011c2: HANGUL JONGSEONG HIEUH*/
+ RTUNI_ALPHA, /* U+0011c3: HANGUL JONGSEONG KIYEOK-RIEUL*/
+ RTUNI_ALPHA, /* U+0011c4: HANGUL JONGSEONG KIYEOK-SIOS-KIYEOK*/
+ RTUNI_ALPHA, /* U+0011c5: HANGUL JONGSEONG NIEUN-KIYEOK*/
+ RTUNI_ALPHA, /* U+0011c6: HANGUL JONGSEONG NIEUN-TIKEUT*/
+ RTUNI_ALPHA, /* U+0011c7: HANGUL JONGSEONG NIEUN-SIOS*/
+ RTUNI_ALPHA, /* U+0011c8: HANGUL JONGSEONG NIEUN-PANSIOS*/
+ RTUNI_ALPHA, /* U+0011c9: HANGUL JONGSEONG NIEUN-THIEUTH*/
+ RTUNI_ALPHA, /* U+0011ca: HANGUL JONGSEONG TIKEUT-KIYEOK*/
+ RTUNI_ALPHA, /* U+0011cb: HANGUL JONGSEONG TIKEUT-RIEUL*/
+ RTUNI_ALPHA, /* U+0011cc: HANGUL JONGSEONG RIEUL-KIYEOK-SIOS*/
+ RTUNI_ALPHA, /* U+0011cd: HANGUL JONGSEONG RIEUL-NIEUN*/
+ RTUNI_ALPHA, /* U+0011ce: HANGUL JONGSEONG RIEUL-TIKEUT*/
+ RTUNI_ALPHA, /* U+0011cf: HANGUL JONGSEONG RIEUL-TIKEUT-HIEUH*/
+ RTUNI_ALPHA, /* U+0011d0: HANGUL JONGSEONG SSANGRIEUL*/
+ RTUNI_ALPHA, /* U+0011d1: HANGUL JONGSEONG RIEUL-MIEUM-KIYEOK*/
+ RTUNI_ALPHA, /* U+0011d2: HANGUL JONGSEONG RIEUL-MIEUM-SIOS*/
+ RTUNI_ALPHA, /* U+0011d3: HANGUL JONGSEONG RIEUL-PIEUP-SIOS*/
+ RTUNI_ALPHA, /* U+0011d4: HANGUL JONGSEONG RIEUL-PIEUP-HIEUH*/
+ RTUNI_ALPHA, /* U+0011d5: HANGUL JONGSEONG RIEUL-KAPYEOUNPIEUP*/
+ RTUNI_ALPHA, /* U+0011d6: HANGUL JONGSEONG RIEUL-SSANGSIOS*/
+ RTUNI_ALPHA, /* U+0011d7: HANGUL JONGSEONG RIEUL-PANSIOS*/
+ RTUNI_ALPHA, /* U+0011d8: HANGUL JONGSEONG RIEUL-KHIEUKH*/
+ RTUNI_ALPHA, /* U+0011d9: HANGUL JONGSEONG RIEUL-YEORINHIEUH*/
+ RTUNI_ALPHA, /* U+0011da: HANGUL JONGSEONG MIEUM-KIYEOK*/
+ RTUNI_ALPHA, /* U+0011db: HANGUL JONGSEONG MIEUM-RIEUL*/
+ RTUNI_ALPHA, /* U+0011dc: HANGUL JONGSEONG MIEUM-PIEUP*/
+ RTUNI_ALPHA, /* U+0011dd: HANGUL JONGSEONG MIEUM-SIOS*/
+ RTUNI_ALPHA, /* U+0011de: HANGUL JONGSEONG MIEUM-SSANGSIOS*/
+ RTUNI_ALPHA, /* U+0011df: HANGUL JONGSEONG MIEUM-PANSIOS*/
+ RTUNI_ALPHA, /* U+0011e0: HANGUL JONGSEONG MIEUM-CHIEUCH*/
+ RTUNI_ALPHA, /* U+0011e1: HANGUL JONGSEONG MIEUM-HIEUH*/
+ RTUNI_ALPHA, /* U+0011e2: HANGUL JONGSEONG KAPYEOUNMIEUM*/
+ RTUNI_ALPHA, /* U+0011e3: HANGUL JONGSEONG PIEUP-RIEUL*/
+ RTUNI_ALPHA, /* U+0011e4: HANGUL JONGSEONG PIEUP-PHIEUPH*/
+ RTUNI_ALPHA, /* U+0011e5: HANGUL JONGSEONG PIEUP-HIEUH*/
+ RTUNI_ALPHA, /* U+0011e6: HANGUL JONGSEONG KAPYEOUNPIEUP*/
+ RTUNI_ALPHA, /* U+0011e7: HANGUL JONGSEONG SIOS-KIYEOK*/
+ RTUNI_ALPHA, /* U+0011e8: HANGUL JONGSEONG SIOS-TIKEUT*/
+ RTUNI_ALPHA, /* U+0011e9: HANGUL JONGSEONG SIOS-RIEUL*/
+ RTUNI_ALPHA, /* U+0011ea: HANGUL JONGSEONG SIOS-PIEUP*/
+ RTUNI_ALPHA, /* U+0011eb: HANGUL JONGSEONG PANSIOS*/
+ RTUNI_ALPHA, /* U+0011ec: HANGUL JONGSEONG IEUNG-KIYEOK*/
+ RTUNI_ALPHA, /* U+0011ed: HANGUL JONGSEONG IEUNG-SSANGKIYEOK*/
+ RTUNI_ALPHA, /* U+0011ee: HANGUL JONGSEONG SSANGIEUNG*/
+ RTUNI_ALPHA, /* U+0011ef: HANGUL JONGSEONG IEUNG-KHIEUKH*/
+ RTUNI_ALPHA, /* U+0011f0: HANGUL JONGSEONG YESIEUNG*/
+ RTUNI_ALPHA, /* U+0011f1: HANGUL JONGSEONG YESIEUNG-SIOS*/
+ RTUNI_ALPHA, /* U+0011f2: HANGUL JONGSEONG YESIEUNG-PANSIOS*/
+ RTUNI_ALPHA, /* U+0011f3: HANGUL JONGSEONG PHIEUPH-PIEUP*/
+ RTUNI_ALPHA, /* U+0011f4: HANGUL JONGSEONG KAPYEOUNPHIEUPH*/
+ RTUNI_ALPHA, /* U+0011f5: HANGUL JONGSEONG HIEUH-NIEUN*/
+ RTUNI_ALPHA, /* U+0011f6: HANGUL JONGSEONG HIEUH-RIEUL*/
+ RTUNI_ALPHA, /* U+0011f7: HANGUL JONGSEONG HIEUH-MIEUM*/
+ RTUNI_ALPHA, /* U+0011f8: HANGUL JONGSEONG HIEUH-PIEUP*/
+ RTUNI_ALPHA, /* U+0011f9: HANGUL JONGSEONG YEORINHIEUH*/
+ RTUNI_ALPHA, /* U+0011fa: HANGUL JONGSEONG KIYEOK-NIEUN*/
+ RTUNI_ALPHA, /* U+0011fb: HANGUL JONGSEONG KIYEOK-PIEUP*/
+ RTUNI_ALPHA, /* U+0011fc: HANGUL JONGSEONG KIYEOK-CHIEUCH*/
+ RTUNI_ALPHA, /* U+0011fd: HANGUL JONGSEONG KIYEOK-KHIEUKH*/
+ RTUNI_ALPHA, /* U+0011fe: HANGUL JONGSEONG KIYEOK-HIEUH*/
+ RTUNI_ALPHA, /* U+0011ff: HANGUL JONGSEONG SSANGNIEUN*/
+ RTUNI_ALPHA, /* U+001200: ETHIOPIC SYLLABLE HA*/
+ RTUNI_ALPHA, /* U+001201: ETHIOPIC SYLLABLE HU*/
+ RTUNI_ALPHA, /* U+001202: ETHIOPIC SYLLABLE HI*/
+ RTUNI_ALPHA, /* U+001203: ETHIOPIC SYLLABLE HAA*/
+ RTUNI_ALPHA, /* U+001204: ETHIOPIC SYLLABLE HEE*/
+ RTUNI_ALPHA, /* U+001205: ETHIOPIC SYLLABLE HE*/
+ RTUNI_ALPHA, /* U+001206: ETHIOPIC SYLLABLE HO*/
+ RTUNI_ALPHA, /* U+001207: ETHIOPIC SYLLABLE HOA*/
+ RTUNI_ALPHA, /* U+001208: ETHIOPIC SYLLABLE LA*/
+ RTUNI_ALPHA, /* U+001209: ETHIOPIC SYLLABLE LU*/
+ RTUNI_ALPHA, /* U+00120a: ETHIOPIC SYLLABLE LI*/
+ RTUNI_ALPHA, /* U+00120b: ETHIOPIC SYLLABLE LAA*/
+ RTUNI_ALPHA, /* U+00120c: ETHIOPIC SYLLABLE LEE*/
+ RTUNI_ALPHA, /* U+00120d: ETHIOPIC SYLLABLE LE*/
+ RTUNI_ALPHA, /* U+00120e: ETHIOPIC SYLLABLE LO*/
+ RTUNI_ALPHA, /* U+00120f: ETHIOPIC SYLLABLE LWA*/
+ RTUNI_ALPHA, /* U+001210: ETHIOPIC SYLLABLE HHA*/
+ RTUNI_ALPHA, /* U+001211: ETHIOPIC SYLLABLE HHU*/
+ RTUNI_ALPHA, /* U+001212: ETHIOPIC SYLLABLE HHI*/
+ RTUNI_ALPHA, /* U+001213: ETHIOPIC SYLLABLE HHAA*/
+ RTUNI_ALPHA, /* U+001214: ETHIOPIC SYLLABLE HHEE*/
+ RTUNI_ALPHA, /* U+001215: ETHIOPIC SYLLABLE HHE*/
+ RTUNI_ALPHA, /* U+001216: ETHIOPIC SYLLABLE HHO*/
+ RTUNI_ALPHA, /* U+001217: ETHIOPIC SYLLABLE HHWA*/
+ RTUNI_ALPHA, /* U+001218: ETHIOPIC SYLLABLE MA*/
+ RTUNI_ALPHA, /* U+001219: ETHIOPIC SYLLABLE MU*/
+ RTUNI_ALPHA, /* U+00121a: ETHIOPIC SYLLABLE MI*/
+ RTUNI_ALPHA, /* U+00121b: ETHIOPIC SYLLABLE MAA*/
+ RTUNI_ALPHA, /* U+00121c: ETHIOPIC SYLLABLE MEE*/
+ RTUNI_ALPHA, /* U+00121d: ETHIOPIC SYLLABLE ME*/
+ RTUNI_ALPHA, /* U+00121e: ETHIOPIC SYLLABLE MO*/
+ RTUNI_ALPHA, /* U+00121f: ETHIOPIC SYLLABLE MWA*/
+ RTUNI_ALPHA, /* U+001220: ETHIOPIC SYLLABLE SZA*/
+ RTUNI_ALPHA, /* U+001221: ETHIOPIC SYLLABLE SZU*/
+ RTUNI_ALPHA, /* U+001222: ETHIOPIC SYLLABLE SZI*/
+ RTUNI_ALPHA, /* U+001223: ETHIOPIC SYLLABLE SZAA*/
+ RTUNI_ALPHA, /* U+001224: ETHIOPIC SYLLABLE SZEE*/
+ RTUNI_ALPHA, /* U+001225: ETHIOPIC SYLLABLE SZE*/
+ RTUNI_ALPHA, /* U+001226: ETHIOPIC SYLLABLE SZO*/
+ RTUNI_ALPHA, /* U+001227: ETHIOPIC SYLLABLE SZWA*/
+ RTUNI_ALPHA, /* U+001228: ETHIOPIC SYLLABLE RA*/
+ RTUNI_ALPHA, /* U+001229: ETHIOPIC SYLLABLE RU*/
+ RTUNI_ALPHA, /* U+00122a: ETHIOPIC SYLLABLE RI*/
+ RTUNI_ALPHA, /* U+00122b: ETHIOPIC SYLLABLE RAA*/
+ RTUNI_ALPHA, /* U+00122c: ETHIOPIC SYLLABLE REE*/
+ RTUNI_ALPHA, /* U+00122d: ETHIOPIC SYLLABLE RE*/
+ RTUNI_ALPHA, /* U+00122e: ETHIOPIC SYLLABLE RO*/
+ RTUNI_ALPHA, /* U+00122f: ETHIOPIC SYLLABLE RWA*/
+ RTUNI_ALPHA, /* U+001230: ETHIOPIC SYLLABLE SA*/
+ RTUNI_ALPHA, /* U+001231: ETHIOPIC SYLLABLE SU*/
+ RTUNI_ALPHA, /* U+001232: ETHIOPIC SYLLABLE SI*/
+ RTUNI_ALPHA, /* U+001233: ETHIOPIC SYLLABLE SAA*/
+ RTUNI_ALPHA, /* U+001234: ETHIOPIC SYLLABLE SEE*/
+ RTUNI_ALPHA, /* U+001235: ETHIOPIC SYLLABLE SE*/
+ RTUNI_ALPHA, /* U+001236: ETHIOPIC SYLLABLE SO*/
+ RTUNI_ALPHA, /* U+001237: ETHIOPIC SYLLABLE SWA*/
+ RTUNI_ALPHA, /* U+001238: ETHIOPIC SYLLABLE SHA*/
+ RTUNI_ALPHA, /* U+001239: ETHIOPIC SYLLABLE SHU*/
+ RTUNI_ALPHA, /* U+00123a: ETHIOPIC SYLLABLE SHI*/
+ RTUNI_ALPHA, /* U+00123b: ETHIOPIC SYLLABLE SHAA*/
+ RTUNI_ALPHA, /* U+00123c: ETHIOPIC SYLLABLE SHEE*/
+ RTUNI_ALPHA, /* U+00123d: ETHIOPIC SYLLABLE SHE*/
+ RTUNI_ALPHA, /* U+00123e: ETHIOPIC SYLLABLE SHO*/
+ RTUNI_ALPHA, /* U+00123f: ETHIOPIC SYLLABLE SHWA*/
+ RTUNI_ALPHA, /* U+001240: ETHIOPIC SYLLABLE QA*/
+ RTUNI_ALPHA, /* U+001241: ETHIOPIC SYLLABLE QU*/
+ RTUNI_ALPHA, /* U+001242: ETHIOPIC SYLLABLE QI*/
+ RTUNI_ALPHA, /* U+001243: ETHIOPIC SYLLABLE QAA*/
+ RTUNI_ALPHA, /* U+001244: ETHIOPIC SYLLABLE QEE*/
+ RTUNI_ALPHA, /* U+001245: ETHIOPIC SYLLABLE QE*/
+ RTUNI_ALPHA, /* U+001246: ETHIOPIC SYLLABLE QO*/
+ RTUNI_ALPHA, /* U+001247: ETHIOPIC SYLLABLE QOA*/
+ RTUNI_ALPHA, /* U+001248: ETHIOPIC SYLLABLE QWA*/
+ 0, /* U+001249: */
+ RTUNI_ALPHA, /* U+00124a: ETHIOPIC SYLLABLE QWI*/
+ RTUNI_ALPHA, /* U+00124b: ETHIOPIC SYLLABLE QWAA*/
+ RTUNI_ALPHA, /* U+00124c: ETHIOPIC SYLLABLE QWEE*/
+ RTUNI_ALPHA, /* U+00124d: ETHIOPIC SYLLABLE QWE*/
+ 0, /* U+00124e: */
+ 0, /* U+00124f: */
+ RTUNI_ALPHA, /* U+001250: ETHIOPIC SYLLABLE QHA*/
+ RTUNI_ALPHA, /* U+001251: ETHIOPIC SYLLABLE QHU*/
+ RTUNI_ALPHA, /* U+001252: ETHIOPIC SYLLABLE QHI*/
+ RTUNI_ALPHA, /* U+001253: ETHIOPIC SYLLABLE QHAA*/
+ RTUNI_ALPHA, /* U+001254: ETHIOPIC SYLLABLE QHEE*/
+ RTUNI_ALPHA, /* U+001255: ETHIOPIC SYLLABLE QHE*/
+ RTUNI_ALPHA, /* U+001256: ETHIOPIC SYLLABLE QHO*/
+ 0, /* U+001257: */
+ RTUNI_ALPHA, /* U+001258: ETHIOPIC SYLLABLE QHWA*/
+ 0, /* U+001259: */
+ RTUNI_ALPHA, /* U+00125a: ETHIOPIC SYLLABLE QHWI*/
+ RTUNI_ALPHA, /* U+00125b: ETHIOPIC SYLLABLE QHWAA*/
+ RTUNI_ALPHA, /* U+00125c: ETHIOPIC SYLLABLE QHWEE*/
+ RTUNI_ALPHA, /* U+00125d: ETHIOPIC SYLLABLE QHWE*/
+ 0, /* U+00125e: */
+ 0, /* U+00125f: */
+ RTUNI_ALPHA, /* U+001260: ETHIOPIC SYLLABLE BA*/
+ RTUNI_ALPHA, /* U+001261: ETHIOPIC SYLLABLE BU*/
+ RTUNI_ALPHA, /* U+001262: ETHIOPIC SYLLABLE BI*/
+ RTUNI_ALPHA, /* U+001263: ETHIOPIC SYLLABLE BAA*/
+ RTUNI_ALPHA, /* U+001264: ETHIOPIC SYLLABLE BEE*/
+ RTUNI_ALPHA, /* U+001265: ETHIOPIC SYLLABLE BE*/
+ RTUNI_ALPHA, /* U+001266: ETHIOPIC SYLLABLE BO*/
+ RTUNI_ALPHA, /* U+001267: ETHIOPIC SYLLABLE BWA*/
+ RTUNI_ALPHA, /* U+001268: ETHIOPIC SYLLABLE VA*/
+ RTUNI_ALPHA, /* U+001269: ETHIOPIC SYLLABLE VU*/
+ RTUNI_ALPHA, /* U+00126a: ETHIOPIC SYLLABLE VI*/
+ RTUNI_ALPHA, /* U+00126b: ETHIOPIC SYLLABLE VAA*/
+ RTUNI_ALPHA, /* U+00126c: ETHIOPIC SYLLABLE VEE*/
+ RTUNI_ALPHA, /* U+00126d: ETHIOPIC SYLLABLE VE*/
+ RTUNI_ALPHA, /* U+00126e: ETHIOPIC SYLLABLE VO*/
+ RTUNI_ALPHA, /* U+00126f: ETHIOPIC SYLLABLE VWA*/
+ RTUNI_ALPHA, /* U+001270: ETHIOPIC SYLLABLE TA*/
+ RTUNI_ALPHA, /* U+001271: ETHIOPIC SYLLABLE TU*/
+ RTUNI_ALPHA, /* U+001272: ETHIOPIC SYLLABLE TI*/
+ RTUNI_ALPHA, /* U+001273: ETHIOPIC SYLLABLE TAA*/
+ RTUNI_ALPHA, /* U+001274: ETHIOPIC SYLLABLE TEE*/
+ RTUNI_ALPHA, /* U+001275: ETHIOPIC SYLLABLE TE*/
+ RTUNI_ALPHA, /* U+001276: ETHIOPIC SYLLABLE TO*/
+ RTUNI_ALPHA, /* U+001277: ETHIOPIC SYLLABLE TWA*/
+ RTUNI_ALPHA, /* U+001278: ETHIOPIC SYLLABLE CA*/
+ RTUNI_ALPHA, /* U+001279: ETHIOPIC SYLLABLE CU*/
+ RTUNI_ALPHA, /* U+00127a: ETHIOPIC SYLLABLE CI*/
+ RTUNI_ALPHA, /* U+00127b: ETHIOPIC SYLLABLE CAA*/
+ RTUNI_ALPHA, /* U+00127c: ETHIOPIC SYLLABLE CEE*/
+ RTUNI_ALPHA, /* U+00127d: ETHIOPIC SYLLABLE CE*/
+ RTUNI_ALPHA, /* U+00127e: ETHIOPIC SYLLABLE CO*/
+ RTUNI_ALPHA, /* U+00127f: ETHIOPIC SYLLABLE CWA*/
+ RTUNI_ALPHA, /* U+001280: ETHIOPIC SYLLABLE XA*/
+ RTUNI_ALPHA, /* U+001281: ETHIOPIC SYLLABLE XU*/
+ RTUNI_ALPHA, /* U+001282: ETHIOPIC SYLLABLE XI*/
+ RTUNI_ALPHA, /* U+001283: ETHIOPIC SYLLABLE XAA*/
+ RTUNI_ALPHA, /* U+001284: ETHIOPIC SYLLABLE XEE*/
+ RTUNI_ALPHA, /* U+001285: ETHIOPIC SYLLABLE XE*/
+ RTUNI_ALPHA, /* U+001286: ETHIOPIC SYLLABLE XO*/
+ RTUNI_ALPHA, /* U+001287: ETHIOPIC SYLLABLE XOA*/
+ RTUNI_ALPHA, /* U+001288: ETHIOPIC SYLLABLE XWA*/
+ 0, /* U+001289: */
+ RTUNI_ALPHA, /* U+00128a: ETHIOPIC SYLLABLE XWI*/
+ RTUNI_ALPHA, /* U+00128b: ETHIOPIC SYLLABLE XWAA*/
+ RTUNI_ALPHA, /* U+00128c: ETHIOPIC SYLLABLE XWEE*/
+ RTUNI_ALPHA, /* U+00128d: ETHIOPIC SYLLABLE XWE*/
+ 0, /* U+00128e: */
+ 0, /* U+00128f: */
+ RTUNI_ALPHA, /* U+001290: ETHIOPIC SYLLABLE NA*/
+ RTUNI_ALPHA, /* U+001291: ETHIOPIC SYLLABLE NU*/
+ RTUNI_ALPHA, /* U+001292: ETHIOPIC SYLLABLE NI*/
+ RTUNI_ALPHA, /* U+001293: ETHIOPIC SYLLABLE NAA*/
+ RTUNI_ALPHA, /* U+001294: ETHIOPIC SYLLABLE NEE*/
+ RTUNI_ALPHA, /* U+001295: ETHIOPIC SYLLABLE NE*/
+ RTUNI_ALPHA, /* U+001296: ETHIOPIC SYLLABLE NO*/
+ RTUNI_ALPHA, /* U+001297: ETHIOPIC SYLLABLE NWA*/
+ RTUNI_ALPHA, /* U+001298: ETHIOPIC SYLLABLE NYA*/
+ RTUNI_ALPHA, /* U+001299: ETHIOPIC SYLLABLE NYU*/
+ RTUNI_ALPHA, /* U+00129a: ETHIOPIC SYLLABLE NYI*/
+ RTUNI_ALPHA, /* U+00129b: ETHIOPIC SYLLABLE NYAA*/
+ RTUNI_ALPHA, /* U+00129c: ETHIOPIC SYLLABLE NYEE*/
+ RTUNI_ALPHA, /* U+00129d: ETHIOPIC SYLLABLE NYE*/
+ RTUNI_ALPHA, /* U+00129e: ETHIOPIC SYLLABLE NYO*/
+ RTUNI_ALPHA, /* U+00129f: ETHIOPIC SYLLABLE NYWA*/
+ RTUNI_ALPHA, /* U+0012a0: ETHIOPIC SYLLABLE GLOTTAL A*/
+ RTUNI_ALPHA, /* U+0012a1: ETHIOPIC SYLLABLE GLOTTAL U*/
+ RTUNI_ALPHA, /* U+0012a2: ETHIOPIC SYLLABLE GLOTTAL I*/
+ RTUNI_ALPHA, /* U+0012a3: ETHIOPIC SYLLABLE GLOTTAL AA*/
+ RTUNI_ALPHA, /* U+0012a4: ETHIOPIC SYLLABLE GLOTTAL EE*/
+ RTUNI_ALPHA, /* U+0012a5: ETHIOPIC SYLLABLE GLOTTAL E*/
+ RTUNI_ALPHA, /* U+0012a6: ETHIOPIC SYLLABLE GLOTTAL O*/
+ RTUNI_ALPHA, /* U+0012a7: ETHIOPIC SYLLABLE GLOTTAL WA*/
+ RTUNI_ALPHA, /* U+0012a8: ETHIOPIC SYLLABLE KA*/
+ RTUNI_ALPHA, /* U+0012a9: ETHIOPIC SYLLABLE KU*/
+ RTUNI_ALPHA, /* U+0012aa: ETHIOPIC SYLLABLE KI*/
+ RTUNI_ALPHA, /* U+0012ab: ETHIOPIC SYLLABLE KAA*/
+ RTUNI_ALPHA, /* U+0012ac: ETHIOPIC SYLLABLE KEE*/
+ RTUNI_ALPHA, /* U+0012ad: ETHIOPIC SYLLABLE KE*/
+ RTUNI_ALPHA, /* U+0012ae: ETHIOPIC SYLLABLE KO*/
+ RTUNI_ALPHA, /* U+0012af: ETHIOPIC SYLLABLE KOA*/
+ RTUNI_ALPHA, /* U+0012b0: ETHIOPIC SYLLABLE KWA*/
+ 0, /* U+0012b1: */
+ RTUNI_ALPHA, /* U+0012b2: ETHIOPIC SYLLABLE KWI*/
+ RTUNI_ALPHA, /* U+0012b3: ETHIOPIC SYLLABLE KWAA*/
+ RTUNI_ALPHA, /* U+0012b4: ETHIOPIC SYLLABLE KWEE*/
+ RTUNI_ALPHA, /* U+0012b5: ETHIOPIC SYLLABLE KWE*/
+ 0, /* U+0012b6: */
+ 0, /* U+0012b7: */
+ RTUNI_ALPHA, /* U+0012b8: ETHIOPIC SYLLABLE KXA*/
+ RTUNI_ALPHA, /* U+0012b9: ETHIOPIC SYLLABLE KXU*/
+ RTUNI_ALPHA, /* U+0012ba: ETHIOPIC SYLLABLE KXI*/
+ RTUNI_ALPHA, /* U+0012bb: ETHIOPIC SYLLABLE KXAA*/
+ RTUNI_ALPHA, /* U+0012bc: ETHIOPIC SYLLABLE KXEE*/
+ RTUNI_ALPHA, /* U+0012bd: ETHIOPIC SYLLABLE KXE*/
+ RTUNI_ALPHA, /* U+0012be: ETHIOPIC SYLLABLE KXO*/
+ 0, /* U+0012bf: */
+ RTUNI_ALPHA, /* U+0012c0: ETHIOPIC SYLLABLE KXWA*/
+ 0, /* U+0012c1: */
+ RTUNI_ALPHA, /* U+0012c2: ETHIOPIC SYLLABLE KXWI*/
+ RTUNI_ALPHA, /* U+0012c3: ETHIOPIC SYLLABLE KXWAA*/
+ RTUNI_ALPHA, /* U+0012c4: ETHIOPIC SYLLABLE KXWEE*/
+ RTUNI_ALPHA, /* U+0012c5: ETHIOPIC SYLLABLE KXWE*/
+ 0, /* U+0012c6: */
+ 0, /* U+0012c7: */
+ RTUNI_ALPHA, /* U+0012c8: ETHIOPIC SYLLABLE WA*/
+ RTUNI_ALPHA, /* U+0012c9: ETHIOPIC SYLLABLE WU*/
+ RTUNI_ALPHA, /* U+0012ca: ETHIOPIC SYLLABLE WI*/
+ RTUNI_ALPHA, /* U+0012cb: ETHIOPIC SYLLABLE WAA*/
+ RTUNI_ALPHA, /* U+0012cc: ETHIOPIC SYLLABLE WEE*/
+ RTUNI_ALPHA, /* U+0012cd: ETHIOPIC SYLLABLE WE*/
+ RTUNI_ALPHA, /* U+0012ce: ETHIOPIC SYLLABLE WO*/
+ RTUNI_ALPHA, /* U+0012cf: ETHIOPIC SYLLABLE WOA*/
+ RTUNI_ALPHA, /* U+0012d0: ETHIOPIC SYLLABLE PHARYNGEAL A*/
+ RTUNI_ALPHA, /* U+0012d1: ETHIOPIC SYLLABLE PHARYNGEAL U*/
+ RTUNI_ALPHA, /* U+0012d2: ETHIOPIC SYLLABLE PHARYNGEAL I*/
+ RTUNI_ALPHA, /* U+0012d3: ETHIOPIC SYLLABLE PHARYNGEAL AA*/
+ RTUNI_ALPHA, /* U+0012d4: ETHIOPIC SYLLABLE PHARYNGEAL EE*/
+ RTUNI_ALPHA, /* U+0012d5: ETHIOPIC SYLLABLE PHARYNGEAL E*/
+ RTUNI_ALPHA, /* U+0012d6: ETHIOPIC SYLLABLE PHARYNGEAL O*/
+ 0, /* U+0012d7: */
+ RTUNI_ALPHA, /* U+0012d8: ETHIOPIC SYLLABLE ZA*/
+ RTUNI_ALPHA, /* U+0012d9: ETHIOPIC SYLLABLE ZU*/
+ RTUNI_ALPHA, /* U+0012da: ETHIOPIC SYLLABLE ZI*/
+ RTUNI_ALPHA, /* U+0012db: ETHIOPIC SYLLABLE ZAA*/
+ RTUNI_ALPHA, /* U+0012dc: ETHIOPIC SYLLABLE ZEE*/
+ RTUNI_ALPHA, /* U+0012dd: ETHIOPIC SYLLABLE ZE*/
+ RTUNI_ALPHA, /* U+0012de: ETHIOPIC SYLLABLE ZO*/
+ RTUNI_ALPHA, /* U+0012df: ETHIOPIC SYLLABLE ZWA*/
+ RTUNI_ALPHA, /* U+0012e0: ETHIOPIC SYLLABLE ZHA*/
+ RTUNI_ALPHA, /* U+0012e1: ETHIOPIC SYLLABLE ZHU*/
+ RTUNI_ALPHA, /* U+0012e2: ETHIOPIC SYLLABLE ZHI*/
+ RTUNI_ALPHA, /* U+0012e3: ETHIOPIC SYLLABLE ZHAA*/
+ RTUNI_ALPHA, /* U+0012e4: ETHIOPIC SYLLABLE ZHEE*/
+ RTUNI_ALPHA, /* U+0012e5: ETHIOPIC SYLLABLE ZHE*/
+ RTUNI_ALPHA, /* U+0012e6: ETHIOPIC SYLLABLE ZHO*/
+ RTUNI_ALPHA, /* U+0012e7: ETHIOPIC SYLLABLE ZHWA*/
+ RTUNI_ALPHA, /* U+0012e8: ETHIOPIC SYLLABLE YA*/
+ RTUNI_ALPHA, /* U+0012e9: ETHIOPIC SYLLABLE YU*/
+ RTUNI_ALPHA, /* U+0012ea: ETHIOPIC SYLLABLE YI*/
+ RTUNI_ALPHA, /* U+0012eb: ETHIOPIC SYLLABLE YAA*/
+ RTUNI_ALPHA, /* U+0012ec: ETHIOPIC SYLLABLE YEE*/
+ RTUNI_ALPHA, /* U+0012ed: ETHIOPIC SYLLABLE YE*/
+ RTUNI_ALPHA, /* U+0012ee: ETHIOPIC SYLLABLE YO*/
+ RTUNI_ALPHA, /* U+0012ef: ETHIOPIC SYLLABLE YOA*/
+ RTUNI_ALPHA, /* U+0012f0: ETHIOPIC SYLLABLE DA*/
+ RTUNI_ALPHA, /* U+0012f1: ETHIOPIC SYLLABLE DU*/
+ RTUNI_ALPHA, /* U+0012f2: ETHIOPIC SYLLABLE DI*/
+ RTUNI_ALPHA, /* U+0012f3: ETHIOPIC SYLLABLE DAA*/
+ RTUNI_ALPHA, /* U+0012f4: ETHIOPIC SYLLABLE DEE*/
+ RTUNI_ALPHA, /* U+0012f5: ETHIOPIC SYLLABLE DE*/
+ RTUNI_ALPHA, /* U+0012f6: ETHIOPIC SYLLABLE DO*/
+ RTUNI_ALPHA, /* U+0012f7: ETHIOPIC SYLLABLE DWA*/
+ RTUNI_ALPHA, /* U+0012f8: ETHIOPIC SYLLABLE DDA*/
+ RTUNI_ALPHA, /* U+0012f9: ETHIOPIC SYLLABLE DDU*/
+ RTUNI_ALPHA, /* U+0012fa: ETHIOPIC SYLLABLE DDI*/
+ RTUNI_ALPHA, /* U+0012fb: ETHIOPIC SYLLABLE DDAA*/
+ RTUNI_ALPHA, /* U+0012fc: ETHIOPIC SYLLABLE DDEE*/
+ RTUNI_ALPHA, /* U+0012fd: ETHIOPIC SYLLABLE DDE*/
+ RTUNI_ALPHA, /* U+0012fe: ETHIOPIC SYLLABLE DDO*/
+ RTUNI_ALPHA, /* U+0012ff: ETHIOPIC SYLLABLE DDWA*/
+ RTUNI_ALPHA, /* U+001300: ETHIOPIC SYLLABLE JA*/
+ RTUNI_ALPHA, /* U+001301: ETHIOPIC SYLLABLE JU*/
+ RTUNI_ALPHA, /* U+001302: ETHIOPIC SYLLABLE JI*/
+ RTUNI_ALPHA, /* U+001303: ETHIOPIC SYLLABLE JAA*/
+ RTUNI_ALPHA, /* U+001304: ETHIOPIC SYLLABLE JEE*/
+ RTUNI_ALPHA, /* U+001305: ETHIOPIC SYLLABLE JE*/
+ RTUNI_ALPHA, /* U+001306: ETHIOPIC SYLLABLE JO*/
+ RTUNI_ALPHA, /* U+001307: ETHIOPIC SYLLABLE JWA*/
+ RTUNI_ALPHA, /* U+001308: ETHIOPIC SYLLABLE GA*/
+ RTUNI_ALPHA, /* U+001309: ETHIOPIC SYLLABLE GU*/
+ RTUNI_ALPHA, /* U+00130a: ETHIOPIC SYLLABLE GI*/
+ RTUNI_ALPHA, /* U+00130b: ETHIOPIC SYLLABLE GAA*/
+ RTUNI_ALPHA, /* U+00130c: ETHIOPIC SYLLABLE GEE*/
+ RTUNI_ALPHA, /* U+00130d: ETHIOPIC SYLLABLE GE*/
+ RTUNI_ALPHA, /* U+00130e: ETHIOPIC SYLLABLE GO*/
+ RTUNI_ALPHA, /* U+00130f: ETHIOPIC SYLLABLE GOA*/
+ RTUNI_ALPHA, /* U+001310: ETHIOPIC SYLLABLE GWA*/
+ 0, /* U+001311: */
+ RTUNI_ALPHA, /* U+001312: ETHIOPIC SYLLABLE GWI*/
+ RTUNI_ALPHA, /* U+001313: ETHIOPIC SYLLABLE GWAA*/
+ RTUNI_ALPHA, /* U+001314: ETHIOPIC SYLLABLE GWEE*/
+ RTUNI_ALPHA, /* U+001315: ETHIOPIC SYLLABLE GWE*/
+ 0, /* U+001316: */
+ 0, /* U+001317: */
+ RTUNI_ALPHA, /* U+001318: ETHIOPIC SYLLABLE GGA*/
+ RTUNI_ALPHA, /* U+001319: ETHIOPIC SYLLABLE GGU*/
+ RTUNI_ALPHA, /* U+00131a: ETHIOPIC SYLLABLE GGI*/
+ RTUNI_ALPHA, /* U+00131b: ETHIOPIC SYLLABLE GGAA*/
+ RTUNI_ALPHA, /* U+00131c: ETHIOPIC SYLLABLE GGEE*/
+ RTUNI_ALPHA, /* U+00131d: ETHIOPIC SYLLABLE GGE*/
+ RTUNI_ALPHA, /* U+00131e: ETHIOPIC SYLLABLE GGO*/
+ RTUNI_ALPHA, /* U+00131f: ETHIOPIC SYLLABLE GGWAA*/
+ RTUNI_ALPHA, /* U+001320: ETHIOPIC SYLLABLE THA*/
+ RTUNI_ALPHA, /* U+001321: ETHIOPIC SYLLABLE THU*/
+ RTUNI_ALPHA, /* U+001322: ETHIOPIC SYLLABLE THI*/
+ RTUNI_ALPHA, /* U+001323: ETHIOPIC SYLLABLE THAA*/
+ RTUNI_ALPHA, /* U+001324: ETHIOPIC SYLLABLE THEE*/
+ RTUNI_ALPHA, /* U+001325: ETHIOPIC SYLLABLE THE*/
+ RTUNI_ALPHA, /* U+001326: ETHIOPIC SYLLABLE THO*/
+ RTUNI_ALPHA, /* U+001327: ETHIOPIC SYLLABLE THWA*/
+ RTUNI_ALPHA, /* U+001328: ETHIOPIC SYLLABLE CHA*/
+ RTUNI_ALPHA, /* U+001329: ETHIOPIC SYLLABLE CHU*/
+ RTUNI_ALPHA, /* U+00132a: ETHIOPIC SYLLABLE CHI*/
+ RTUNI_ALPHA, /* U+00132b: ETHIOPIC SYLLABLE CHAA*/
+ RTUNI_ALPHA, /* U+00132c: ETHIOPIC SYLLABLE CHEE*/
+ RTUNI_ALPHA, /* U+00132d: ETHIOPIC SYLLABLE CHE*/
+ RTUNI_ALPHA, /* U+00132e: ETHIOPIC SYLLABLE CHO*/
+ RTUNI_ALPHA, /* U+00132f: ETHIOPIC SYLLABLE CHWA*/
+ RTUNI_ALPHA, /* U+001330: ETHIOPIC SYLLABLE PHA*/
+ RTUNI_ALPHA, /* U+001331: ETHIOPIC SYLLABLE PHU*/
+ RTUNI_ALPHA, /* U+001332: ETHIOPIC SYLLABLE PHI*/
+ RTUNI_ALPHA, /* U+001333: ETHIOPIC SYLLABLE PHAA*/
+ RTUNI_ALPHA, /* U+001334: ETHIOPIC SYLLABLE PHEE*/
+ RTUNI_ALPHA, /* U+001335: ETHIOPIC SYLLABLE PHE*/
+ RTUNI_ALPHA, /* U+001336: ETHIOPIC SYLLABLE PHO*/
+ RTUNI_ALPHA, /* U+001337: ETHIOPIC SYLLABLE PHWA*/
+ RTUNI_ALPHA, /* U+001338: ETHIOPIC SYLLABLE TSA*/
+ RTUNI_ALPHA, /* U+001339: ETHIOPIC SYLLABLE TSU*/
+ RTUNI_ALPHA, /* U+00133a: ETHIOPIC SYLLABLE TSI*/
+ RTUNI_ALPHA, /* U+00133b: ETHIOPIC SYLLABLE TSAA*/
+ RTUNI_ALPHA, /* U+00133c: ETHIOPIC SYLLABLE TSEE*/
+ RTUNI_ALPHA, /* U+00133d: ETHIOPIC SYLLABLE TSE*/
+ RTUNI_ALPHA, /* U+00133e: ETHIOPIC SYLLABLE TSO*/
+ RTUNI_ALPHA, /* U+00133f: ETHIOPIC SYLLABLE TSWA*/
+ RTUNI_ALPHA, /* U+001340: ETHIOPIC SYLLABLE TZA*/
+ RTUNI_ALPHA, /* U+001341: ETHIOPIC SYLLABLE TZU*/
+ RTUNI_ALPHA, /* U+001342: ETHIOPIC SYLLABLE TZI*/
+ RTUNI_ALPHA, /* U+001343: ETHIOPIC SYLLABLE TZAA*/
+ RTUNI_ALPHA, /* U+001344: ETHIOPIC SYLLABLE TZEE*/
+ RTUNI_ALPHA, /* U+001345: ETHIOPIC SYLLABLE TZE*/
+ RTUNI_ALPHA, /* U+001346: ETHIOPIC SYLLABLE TZO*/
+ RTUNI_ALPHA, /* U+001347: ETHIOPIC SYLLABLE TZOA*/
+ RTUNI_ALPHA, /* U+001348: ETHIOPIC SYLLABLE FA*/
+ RTUNI_ALPHA, /* U+001349: ETHIOPIC SYLLABLE FU*/
+ RTUNI_ALPHA, /* U+00134a: ETHIOPIC SYLLABLE FI*/
+ RTUNI_ALPHA, /* U+00134b: ETHIOPIC SYLLABLE FAA*/
+ RTUNI_ALPHA, /* U+00134c: ETHIOPIC SYLLABLE FEE*/
+ RTUNI_ALPHA, /* U+00134d: ETHIOPIC SYLLABLE FE*/
+ RTUNI_ALPHA, /* U+00134e: ETHIOPIC SYLLABLE FO*/
+ RTUNI_ALPHA, /* U+00134f: ETHIOPIC SYLLABLE FWA*/
+ RTUNI_ALPHA, /* U+001350: ETHIOPIC SYLLABLE PA*/
+ RTUNI_ALPHA, /* U+001351: ETHIOPIC SYLLABLE PU*/
+ RTUNI_ALPHA, /* U+001352: ETHIOPIC SYLLABLE PI*/
+ RTUNI_ALPHA, /* U+001353: ETHIOPIC SYLLABLE PAA*/
+ RTUNI_ALPHA, /* U+001354: ETHIOPIC SYLLABLE PEE*/
+ RTUNI_ALPHA, /* U+001355: ETHIOPIC SYLLABLE PE*/
+ RTUNI_ALPHA, /* U+001356: ETHIOPIC SYLLABLE PO*/
+ RTUNI_ALPHA, /* U+001357: ETHIOPIC SYLLABLE PWA*/
+ RTUNI_ALPHA, /* U+001358: ETHIOPIC SYLLABLE RYA*/
+ RTUNI_ALPHA, /* U+001359: ETHIOPIC SYLLABLE MYA*/
+ RTUNI_ALPHA, /* U+00135a: ETHIOPIC SYLLABLE FYA*/
+ 0, /* U+00135b: */
+ 0, /* U+00135c: */
+ 0, /* U+00135d: ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK*/
+ 0, /* U+00135e: ETHIOPIC COMBINING VOWEL LENGTH MARK*/
+ RTUNI_ALPHA, /* U+00135f: ETHIOPIC COMBINING GEMINATION MARK*/
+ 0, /* U+001360: ETHIOPIC SECTION MARK*/
+ 0, /* U+001361: ETHIOPIC WORDSPACE*/
+ 0, /* U+001362: ETHIOPIC FULL STOP*/
+ 0, /* U+001363: ETHIOPIC COMMA*/
+ 0, /* U+001364: ETHIOPIC SEMICOLON*/
+ 0, /* U+001365: ETHIOPIC COLON*/
+ 0, /* U+001366: ETHIOPIC PREFACE COLON*/
+ 0, /* U+001367: ETHIOPIC QUESTION MARK*/
+ 0, /* U+001368: ETHIOPIC PARAGRAPH SEPARATOR*/
+ 0, /* U+001369: ETHIOPIC DIGIT ONE*/
+ 0, /* U+00136a: ETHIOPIC DIGIT TWO*/
+ 0, /* U+00136b: ETHIOPIC DIGIT THREE*/
+ 0, /* U+00136c: ETHIOPIC DIGIT FOUR*/
+ 0, /* U+00136d: ETHIOPIC DIGIT FIVE*/
+ 0, /* U+00136e: ETHIOPIC DIGIT SIX*/
+ 0, /* U+00136f: ETHIOPIC DIGIT SEVEN*/
+ 0, /* U+001370: ETHIOPIC DIGIT EIGHT*/
+ 0, /* U+001371: ETHIOPIC DIGIT NINE*/
+ 0, /* U+001372: ETHIOPIC NUMBER TEN*/
+ 0, /* U+001373: ETHIOPIC NUMBER TWENTY*/
+ 0, /* U+001374: ETHIOPIC NUMBER THIRTY*/
+ 0, /* U+001375: ETHIOPIC NUMBER FORTY*/
+ 0, /* U+001376: ETHIOPIC NUMBER FIFTY*/
+ 0, /* U+001377: ETHIOPIC NUMBER SIXTY*/
+ 0, /* U+001378: ETHIOPIC NUMBER SEVENTY*/
+ 0, /* U+001379: ETHIOPIC NUMBER EIGHTY*/
+ 0, /* U+00137a: ETHIOPIC NUMBER NINETY*/
+ 0, /* U+00137b: ETHIOPIC NUMBER HUNDRED*/
+ 0, /* U+00137c: ETHIOPIC NUMBER TEN THOUSAND*/
+ 0, /* U+00137d: */
+ 0, /* U+00137e: */
+ 0, /* U+00137f: */
+ RTUNI_ALPHA, /* U+001380: ETHIOPIC SYLLABLE SEBATBEIT MWA*/
+ RTUNI_ALPHA, /* U+001381: ETHIOPIC SYLLABLE MWI*/
+ RTUNI_ALPHA, /* U+001382: ETHIOPIC SYLLABLE MWEE*/
+ RTUNI_ALPHA, /* U+001383: ETHIOPIC SYLLABLE MWE*/
+ RTUNI_ALPHA, /* U+001384: ETHIOPIC SYLLABLE SEBATBEIT BWA*/
+ RTUNI_ALPHA, /* U+001385: ETHIOPIC SYLLABLE BWI*/
+ RTUNI_ALPHA, /* U+001386: ETHIOPIC SYLLABLE BWEE*/
+ RTUNI_ALPHA, /* U+001387: ETHIOPIC SYLLABLE BWE*/
+ RTUNI_ALPHA, /* U+001388: ETHIOPIC SYLLABLE SEBATBEIT FWA*/
+ RTUNI_ALPHA, /* U+001389: ETHIOPIC SYLLABLE FWI*/
+ RTUNI_ALPHA, /* U+00138a: ETHIOPIC SYLLABLE FWEE*/
+ RTUNI_ALPHA, /* U+00138b: ETHIOPIC SYLLABLE FWE*/
+ RTUNI_ALPHA, /* U+00138c: ETHIOPIC SYLLABLE SEBATBEIT PWA*/
+ RTUNI_ALPHA, /* U+00138d: ETHIOPIC SYLLABLE PWI*/
+ RTUNI_ALPHA, /* U+00138e: ETHIOPIC SYLLABLE PWEE*/
+ RTUNI_ALPHA, /* U+00138f: ETHIOPIC SYLLABLE PWE*/
+ 0, /* U+001390: ETHIOPIC TONAL MARK YIZET*/
+ 0, /* U+001391: ETHIOPIC TONAL MARK DERET*/
+ 0, /* U+001392: ETHIOPIC TONAL MARK RIKRIK*/
+ 0, /* U+001393: ETHIOPIC TONAL MARK SHORT RIKRIK*/
+ 0, /* U+001394: ETHIOPIC TONAL MARK DIFAT*/
+ 0, /* U+001395: ETHIOPIC TONAL MARK KENAT*/
+ 0, /* U+001396: ETHIOPIC TONAL MARK CHIRET*/
+ 0, /* U+001397: ETHIOPIC TONAL MARK HIDET*/
+ 0, /* U+001398: ETHIOPIC TONAL MARK DERET-HIDET*/
+ 0, /* U+001399: ETHIOPIC TONAL MARK KURT*/
+ 0, /* U+00139a: */
+ 0, /* U+00139b: */
+ 0, /* U+00139c: */
+ 0, /* U+00139d: */
+ 0, /* U+00139e: */
+ 0, /* U+00139f: */
+ RTUNI_ALPHA, /* U+0013a0: CHEROKEE LETTER A*/
+ RTUNI_ALPHA, /* U+0013a1: CHEROKEE LETTER E*/
+ RTUNI_ALPHA, /* U+0013a2: CHEROKEE LETTER I*/
+ RTUNI_ALPHA, /* U+0013a3: CHEROKEE LETTER O*/
+ RTUNI_ALPHA, /* U+0013a4: CHEROKEE LETTER U*/
+ RTUNI_ALPHA, /* U+0013a5: CHEROKEE LETTER V*/
+ RTUNI_ALPHA, /* U+0013a6: CHEROKEE LETTER GA*/
+ RTUNI_ALPHA, /* U+0013a7: CHEROKEE LETTER KA*/
+ RTUNI_ALPHA, /* U+0013a8: CHEROKEE LETTER GE*/
+ RTUNI_ALPHA, /* U+0013a9: CHEROKEE LETTER GI*/
+ RTUNI_ALPHA, /* U+0013aa: CHEROKEE LETTER GO*/
+ RTUNI_ALPHA, /* U+0013ab: CHEROKEE LETTER GU*/
+ RTUNI_ALPHA, /* U+0013ac: CHEROKEE LETTER GV*/
+ RTUNI_ALPHA, /* U+0013ad: CHEROKEE LETTER HA*/
+ RTUNI_ALPHA, /* U+0013ae: CHEROKEE LETTER HE*/
+ RTUNI_ALPHA, /* U+0013af: CHEROKEE LETTER HI*/
+ RTUNI_ALPHA, /* U+0013b0: CHEROKEE LETTER HO*/
+ RTUNI_ALPHA, /* U+0013b1: CHEROKEE LETTER HU*/
+ RTUNI_ALPHA, /* U+0013b2: CHEROKEE LETTER HV*/
+ RTUNI_ALPHA, /* U+0013b3: CHEROKEE LETTER LA*/
+ RTUNI_ALPHA, /* U+0013b4: CHEROKEE LETTER LE*/
+ RTUNI_ALPHA, /* U+0013b5: CHEROKEE LETTER LI*/
+ RTUNI_ALPHA, /* U+0013b6: CHEROKEE LETTER LO*/
+ RTUNI_ALPHA, /* U+0013b7: CHEROKEE LETTER LU*/
+ RTUNI_ALPHA, /* U+0013b8: CHEROKEE LETTER LV*/
+ RTUNI_ALPHA, /* U+0013b9: CHEROKEE LETTER MA*/
+ RTUNI_ALPHA, /* U+0013ba: CHEROKEE LETTER ME*/
+ RTUNI_ALPHA, /* U+0013bb: CHEROKEE LETTER MI*/
+ RTUNI_ALPHA, /* U+0013bc: CHEROKEE LETTER MO*/
+ RTUNI_ALPHA, /* U+0013bd: CHEROKEE LETTER MU*/
+ RTUNI_ALPHA, /* U+0013be: CHEROKEE LETTER NA*/
+ RTUNI_ALPHA, /* U+0013bf: CHEROKEE LETTER HNA*/
+ RTUNI_ALPHA, /* U+0013c0: CHEROKEE LETTER NAH*/
+ RTUNI_ALPHA, /* U+0013c1: CHEROKEE LETTER NE*/
+ RTUNI_ALPHA, /* U+0013c2: CHEROKEE LETTER NI*/
+ RTUNI_ALPHA, /* U+0013c3: CHEROKEE LETTER NO*/
+ RTUNI_ALPHA, /* U+0013c4: CHEROKEE LETTER NU*/
+ RTUNI_ALPHA, /* U+0013c5: CHEROKEE LETTER NV*/
+ RTUNI_ALPHA, /* U+0013c6: CHEROKEE LETTER QUA*/
+ RTUNI_ALPHA, /* U+0013c7: CHEROKEE LETTER QUE*/
+ RTUNI_ALPHA, /* U+0013c8: CHEROKEE LETTER QUI*/
+ RTUNI_ALPHA, /* U+0013c9: CHEROKEE LETTER QUO*/
+ RTUNI_ALPHA, /* U+0013ca: CHEROKEE LETTER QUU*/
+ RTUNI_ALPHA, /* U+0013cb: CHEROKEE LETTER QUV*/
+ RTUNI_ALPHA, /* U+0013cc: CHEROKEE LETTER SA*/
+ RTUNI_ALPHA, /* U+0013cd: CHEROKEE LETTER S*/
+ RTUNI_ALPHA, /* U+0013ce: CHEROKEE LETTER SE*/
+ RTUNI_ALPHA, /* U+0013cf: CHEROKEE LETTER SI*/
+ RTUNI_ALPHA, /* U+0013d0: CHEROKEE LETTER SO*/
+ RTUNI_ALPHA, /* U+0013d1: CHEROKEE LETTER SU*/
+ RTUNI_ALPHA, /* U+0013d2: CHEROKEE LETTER SV*/
+ RTUNI_ALPHA, /* U+0013d3: CHEROKEE LETTER DA*/
+ RTUNI_ALPHA, /* U+0013d4: CHEROKEE LETTER TA*/
+ RTUNI_ALPHA, /* U+0013d5: CHEROKEE LETTER DE*/
+ RTUNI_ALPHA, /* U+0013d6: CHEROKEE LETTER TE*/
+ RTUNI_ALPHA, /* U+0013d7: CHEROKEE LETTER DI*/
+ RTUNI_ALPHA, /* U+0013d8: CHEROKEE LETTER TI*/
+ RTUNI_ALPHA, /* U+0013d9: CHEROKEE LETTER DO*/
+ RTUNI_ALPHA, /* U+0013da: CHEROKEE LETTER DU*/
+ RTUNI_ALPHA, /* U+0013db: CHEROKEE LETTER DV*/
+ RTUNI_ALPHA, /* U+0013dc: CHEROKEE LETTER DLA*/
+ RTUNI_ALPHA, /* U+0013dd: CHEROKEE LETTER TLA*/
+ RTUNI_ALPHA, /* U+0013de: CHEROKEE LETTER TLE*/
+ RTUNI_ALPHA, /* U+0013df: CHEROKEE LETTER TLI*/
+ RTUNI_ALPHA, /* U+0013e0: CHEROKEE LETTER TLO*/
+ RTUNI_ALPHA, /* U+0013e1: CHEROKEE LETTER TLU*/
+ RTUNI_ALPHA, /* U+0013e2: CHEROKEE LETTER TLV*/
+ RTUNI_ALPHA, /* U+0013e3: CHEROKEE LETTER TSA*/
+ RTUNI_ALPHA, /* U+0013e4: CHEROKEE LETTER TSE*/
+ RTUNI_ALPHA, /* U+0013e5: CHEROKEE LETTER TSI*/
+ RTUNI_ALPHA, /* U+0013e6: CHEROKEE LETTER TSO*/
+ RTUNI_ALPHA, /* U+0013e7: CHEROKEE LETTER TSU*/
+ RTUNI_ALPHA, /* U+0013e8: CHEROKEE LETTER TSV*/
+ RTUNI_ALPHA, /* U+0013e9: CHEROKEE LETTER WA*/
+ RTUNI_ALPHA, /* U+0013ea: CHEROKEE LETTER WE*/
+ RTUNI_ALPHA, /* U+0013eb: CHEROKEE LETTER WI*/
+ RTUNI_ALPHA, /* U+0013ec: CHEROKEE LETTER WO*/
+ RTUNI_ALPHA, /* U+0013ed: CHEROKEE LETTER WU*/
+ RTUNI_ALPHA, /* U+0013ee: CHEROKEE LETTER WV*/
+ RTUNI_ALPHA, /* U+0013ef: CHEROKEE LETTER YA*/
+ RTUNI_ALPHA, /* U+0013f0: CHEROKEE LETTER YE*/
+ RTUNI_ALPHA, /* U+0013f1: CHEROKEE LETTER YI*/
+ RTUNI_ALPHA, /* U+0013f2: CHEROKEE LETTER YO*/
+ RTUNI_ALPHA, /* U+0013f3: CHEROKEE LETTER YU*/
+ RTUNI_ALPHA, /* U+0013f4: CHEROKEE LETTER YV*/
+ 0, /* U+0013f5: */
+ 0, /* U+0013f6: */
+ 0, /* U+0013f7: */
+ 0, /* U+0013f8: */
+ 0, /* U+0013f9: */
+ 0, /* U+0013fa: */
+ 0, /* U+0013fb: */
+ 0, /* U+0013fc: */
+ 0, /* U+0013fd: */
+ 0, /* U+0013fe: */
+ 0, /* U+0013ff: */
+ 0, /* U+001400: CANADIAN SYLLABICS HYPHEN*/
+ RTUNI_ALPHA, /* U+001401: CANADIAN SYLLABICS E*/
+ RTUNI_ALPHA, /* U+001402: CANADIAN SYLLABICS AAI*/
+ RTUNI_ALPHA, /* U+001403: CANADIAN SYLLABICS I*/
+ RTUNI_ALPHA, /* U+001404: CANADIAN SYLLABICS II*/
+ RTUNI_ALPHA, /* U+001405: CANADIAN SYLLABICS O*/
+ RTUNI_ALPHA, /* U+001406: CANADIAN SYLLABICS OO*/
+ RTUNI_ALPHA, /* U+001407: CANADIAN SYLLABICS Y-CREE OO*/
+ RTUNI_ALPHA, /* U+001408: CANADIAN SYLLABICS CARRIER EE*/
+ RTUNI_ALPHA, /* U+001409: CANADIAN SYLLABICS CARRIER I*/
+ RTUNI_ALPHA, /* U+00140a: CANADIAN SYLLABICS A*/
+ RTUNI_ALPHA, /* U+00140b: CANADIAN SYLLABICS AA*/
+ RTUNI_ALPHA, /* U+00140c: CANADIAN SYLLABICS WE*/
+ RTUNI_ALPHA, /* U+00140d: CANADIAN SYLLABICS WEST-CREE WE*/
+ RTUNI_ALPHA, /* U+00140e: CANADIAN SYLLABICS WI*/
+ RTUNI_ALPHA, /* U+00140f: CANADIAN SYLLABICS WEST-CREE WI*/
+ RTUNI_ALPHA, /* U+001410: CANADIAN SYLLABICS WII*/
+ RTUNI_ALPHA, /* U+001411: CANADIAN SYLLABICS WEST-CREE WII*/
+ RTUNI_ALPHA, /* U+001412: CANADIAN SYLLABICS WO*/
+ RTUNI_ALPHA, /* U+001413: CANADIAN SYLLABICS WEST-CREE WO*/
+ RTUNI_ALPHA, /* U+001414: CANADIAN SYLLABICS WOO*/
+ RTUNI_ALPHA, /* U+001415: CANADIAN SYLLABICS WEST-CREE WOO*/
+ RTUNI_ALPHA, /* U+001416: CANADIAN SYLLABICS NASKAPI WOO*/
+ RTUNI_ALPHA, /* U+001417: CANADIAN SYLLABICS WA*/
+ RTUNI_ALPHA, /* U+001418: CANADIAN SYLLABICS WEST-CREE WA*/
+ RTUNI_ALPHA, /* U+001419: CANADIAN SYLLABICS WAA*/
+ RTUNI_ALPHA, /* U+00141a: CANADIAN SYLLABICS WEST-CREE WAA*/
+ RTUNI_ALPHA, /* U+00141b: CANADIAN SYLLABICS NASKAPI WAA*/
+ RTUNI_ALPHA, /* U+00141c: CANADIAN SYLLABICS AI*/
+ RTUNI_ALPHA, /* U+00141d: CANADIAN SYLLABICS Y-CREE W*/
+ RTUNI_ALPHA, /* U+00141e: CANADIAN SYLLABICS GLOTTAL STOP*/
+ RTUNI_ALPHA, /* U+00141f: CANADIAN SYLLABICS FINAL ACUTE*/
+ RTUNI_ALPHA, /* U+001420: CANADIAN SYLLABICS FINAL GRAVE*/
+ RTUNI_ALPHA, /* U+001421: CANADIAN SYLLABICS FINAL BOTTOM HALF RING*/
+ RTUNI_ALPHA, /* U+001422: CANADIAN SYLLABICS FINAL TOP HALF RING*/
+ RTUNI_ALPHA, /* U+001423: CANADIAN SYLLABICS FINAL RIGHT HALF RING*/
+ RTUNI_ALPHA, /* U+001424: CANADIAN SYLLABICS FINAL RING*/
+ RTUNI_ALPHA, /* U+001425: CANADIAN SYLLABICS FINAL DOUBLE ACUTE*/
+ RTUNI_ALPHA, /* U+001426: CANADIAN SYLLABICS FINAL DOUBLE SHORT VERTICAL STROKES*/
+ RTUNI_ALPHA, /* U+001427: CANADIAN SYLLABICS FINAL MIDDLE DOT*/
+ RTUNI_ALPHA, /* U+001428: CANADIAN SYLLABICS FINAL SHORT HORIZONTAL STROKE*/
+ RTUNI_ALPHA, /* U+001429: CANADIAN SYLLABICS FINAL PLUS*/
+ RTUNI_ALPHA, /* U+00142a: CANADIAN SYLLABICS FINAL DOWN TACK*/
+ RTUNI_ALPHA, /* U+00142b: CANADIAN SYLLABICS EN*/
+ RTUNI_ALPHA, /* U+00142c: CANADIAN SYLLABICS IN*/
+ RTUNI_ALPHA, /* U+00142d: CANADIAN SYLLABICS ON*/
+ RTUNI_ALPHA, /* U+00142e: CANADIAN SYLLABICS AN*/
+ RTUNI_ALPHA, /* U+00142f: CANADIAN SYLLABICS PE*/
+ RTUNI_ALPHA, /* U+001430: CANADIAN SYLLABICS PAAI*/
+ RTUNI_ALPHA, /* U+001431: CANADIAN SYLLABICS PI*/
+ RTUNI_ALPHA, /* U+001432: CANADIAN SYLLABICS PII*/
+ RTUNI_ALPHA, /* U+001433: CANADIAN SYLLABICS PO*/
+ RTUNI_ALPHA, /* U+001434: CANADIAN SYLLABICS POO*/
+ RTUNI_ALPHA, /* U+001435: CANADIAN SYLLABICS Y-CREE POO*/
+ RTUNI_ALPHA, /* U+001436: CANADIAN SYLLABICS CARRIER HEE*/
+ RTUNI_ALPHA, /* U+001437: CANADIAN SYLLABICS CARRIER HI*/
+ RTUNI_ALPHA, /* U+001438: CANADIAN SYLLABICS PA*/
+ RTUNI_ALPHA, /* U+001439: CANADIAN SYLLABICS PAA*/
+ RTUNI_ALPHA, /* U+00143a: CANADIAN SYLLABICS PWE*/
+ RTUNI_ALPHA, /* U+00143b: CANADIAN SYLLABICS WEST-CREE PWE*/
+ RTUNI_ALPHA, /* U+00143c: CANADIAN SYLLABICS PWI*/
+ RTUNI_ALPHA, /* U+00143d: CANADIAN SYLLABICS WEST-CREE PWI*/
+ RTUNI_ALPHA, /* U+00143e: CANADIAN SYLLABICS PWII*/
+ RTUNI_ALPHA, /* U+00143f: CANADIAN SYLLABICS WEST-CREE PWII*/
+ RTUNI_ALPHA, /* U+001440: CANADIAN SYLLABICS PWO*/
+ RTUNI_ALPHA, /* U+001441: CANADIAN SYLLABICS WEST-CREE PWO*/
+ RTUNI_ALPHA, /* U+001442: CANADIAN SYLLABICS PWOO*/
+ RTUNI_ALPHA, /* U+001443: CANADIAN SYLLABICS WEST-CREE PWOO*/
+ RTUNI_ALPHA, /* U+001444: CANADIAN SYLLABICS PWA*/
+ RTUNI_ALPHA, /* U+001445: CANADIAN SYLLABICS WEST-CREE PWA*/
+ RTUNI_ALPHA, /* U+001446: CANADIAN SYLLABICS PWAA*/
+ RTUNI_ALPHA, /* U+001447: CANADIAN SYLLABICS WEST-CREE PWAA*/
+ RTUNI_ALPHA, /* U+001448: CANADIAN SYLLABICS Y-CREE PWAA*/
+ RTUNI_ALPHA, /* U+001449: CANADIAN SYLLABICS P*/
+ RTUNI_ALPHA, /* U+00144a: CANADIAN SYLLABICS WEST-CREE P*/
+ RTUNI_ALPHA, /* U+00144b: CANADIAN SYLLABICS CARRIER H*/
+ RTUNI_ALPHA, /* U+00144c: CANADIAN SYLLABICS TE*/
+ RTUNI_ALPHA, /* U+00144d: CANADIAN SYLLABICS TAAI*/
+ RTUNI_ALPHA, /* U+00144e: CANADIAN SYLLABICS TI*/
+ RTUNI_ALPHA, /* U+00144f: CANADIAN SYLLABICS TII*/
+ RTUNI_ALPHA, /* U+001450: CANADIAN SYLLABICS TO*/
+ RTUNI_ALPHA, /* U+001451: CANADIAN SYLLABICS TOO*/
+ RTUNI_ALPHA, /* U+001452: CANADIAN SYLLABICS Y-CREE TOO*/
+ RTUNI_ALPHA, /* U+001453: CANADIAN SYLLABICS CARRIER DEE*/
+ RTUNI_ALPHA, /* U+001454: CANADIAN SYLLABICS CARRIER DI*/
+ RTUNI_ALPHA, /* U+001455: CANADIAN SYLLABICS TA*/
+ RTUNI_ALPHA, /* U+001456: CANADIAN SYLLABICS TAA*/
+ RTUNI_ALPHA, /* U+001457: CANADIAN SYLLABICS TWE*/
+ RTUNI_ALPHA, /* U+001458: CANADIAN SYLLABICS WEST-CREE TWE*/
+ RTUNI_ALPHA, /* U+001459: CANADIAN SYLLABICS TWI*/
+ RTUNI_ALPHA, /* U+00145a: CANADIAN SYLLABICS WEST-CREE TWI*/
+ RTUNI_ALPHA, /* U+00145b: CANADIAN SYLLABICS TWII*/
+ RTUNI_ALPHA, /* U+00145c: CANADIAN SYLLABICS WEST-CREE TWII*/
+ RTUNI_ALPHA, /* U+00145d: CANADIAN SYLLABICS TWO*/
+ RTUNI_ALPHA, /* U+00145e: CANADIAN SYLLABICS WEST-CREE TWO*/
+ RTUNI_ALPHA, /* U+00145f: CANADIAN SYLLABICS TWOO*/
+ RTUNI_ALPHA, /* U+001460: CANADIAN SYLLABICS WEST-CREE TWOO*/
+ RTUNI_ALPHA, /* U+001461: CANADIAN SYLLABICS TWA*/
+ RTUNI_ALPHA, /* U+001462: CANADIAN SYLLABICS WEST-CREE TWA*/
+ RTUNI_ALPHA, /* U+001463: CANADIAN SYLLABICS TWAA*/
+ RTUNI_ALPHA, /* U+001464: CANADIAN SYLLABICS WEST-CREE TWAA*/
+ RTUNI_ALPHA, /* U+001465: CANADIAN SYLLABICS NASKAPI TWAA*/
+ RTUNI_ALPHA, /* U+001466: CANADIAN SYLLABICS T*/
+ RTUNI_ALPHA, /* U+001467: CANADIAN SYLLABICS TTE*/
+ RTUNI_ALPHA, /* U+001468: CANADIAN SYLLABICS TTI*/
+ RTUNI_ALPHA, /* U+001469: CANADIAN SYLLABICS TTO*/
+ RTUNI_ALPHA, /* U+00146a: CANADIAN SYLLABICS TTA*/
+ RTUNI_ALPHA, /* U+00146b: CANADIAN SYLLABICS KE*/
+ RTUNI_ALPHA, /* U+00146c: CANADIAN SYLLABICS KAAI*/
+ RTUNI_ALPHA, /* U+00146d: CANADIAN SYLLABICS KI*/
+ RTUNI_ALPHA, /* U+00146e: CANADIAN SYLLABICS KII*/
+ RTUNI_ALPHA, /* U+00146f: CANADIAN SYLLABICS KO*/
+ RTUNI_ALPHA, /* U+001470: CANADIAN SYLLABICS KOO*/
+ RTUNI_ALPHA, /* U+001471: CANADIAN SYLLABICS Y-CREE KOO*/
+ RTUNI_ALPHA, /* U+001472: CANADIAN SYLLABICS KA*/
+ RTUNI_ALPHA, /* U+001473: CANADIAN SYLLABICS KAA*/
+ RTUNI_ALPHA, /* U+001474: CANADIAN SYLLABICS KWE*/
+ RTUNI_ALPHA, /* U+001475: CANADIAN SYLLABICS WEST-CREE KWE*/
+ RTUNI_ALPHA, /* U+001476: CANADIAN SYLLABICS KWI*/
+ RTUNI_ALPHA, /* U+001477: CANADIAN SYLLABICS WEST-CREE KWI*/
+ RTUNI_ALPHA, /* U+001478: CANADIAN SYLLABICS KWII*/
+ RTUNI_ALPHA, /* U+001479: CANADIAN SYLLABICS WEST-CREE KWII*/
+ RTUNI_ALPHA, /* U+00147a: CANADIAN SYLLABICS KWO*/
+ RTUNI_ALPHA, /* U+00147b: CANADIAN SYLLABICS WEST-CREE KWO*/
+ RTUNI_ALPHA, /* U+00147c: CANADIAN SYLLABICS KWOO*/
+ RTUNI_ALPHA, /* U+00147d: CANADIAN SYLLABICS WEST-CREE KWOO*/
+ RTUNI_ALPHA, /* U+00147e: CANADIAN SYLLABICS KWA*/
+ RTUNI_ALPHA, /* U+00147f: CANADIAN SYLLABICS WEST-CREE KWA*/
+ RTUNI_ALPHA, /* U+001480: CANADIAN SYLLABICS KWAA*/
+ RTUNI_ALPHA, /* U+001481: CANADIAN SYLLABICS WEST-CREE KWAA*/
+ RTUNI_ALPHA, /* U+001482: CANADIAN SYLLABICS NASKAPI KWAA*/
+ RTUNI_ALPHA, /* U+001483: CANADIAN SYLLABICS K*/
+ RTUNI_ALPHA, /* U+001484: CANADIAN SYLLABICS KW*/
+ RTUNI_ALPHA, /* U+001485: CANADIAN SYLLABICS SOUTH-SLAVEY KEH*/
+ RTUNI_ALPHA, /* U+001486: CANADIAN SYLLABICS SOUTH-SLAVEY KIH*/
+ RTUNI_ALPHA, /* U+001487: CANADIAN SYLLABICS SOUTH-SLAVEY KOH*/
+ RTUNI_ALPHA, /* U+001488: CANADIAN SYLLABICS SOUTH-SLAVEY KAH*/
+ RTUNI_ALPHA, /* U+001489: CANADIAN SYLLABICS CE*/
+ RTUNI_ALPHA, /* U+00148a: CANADIAN SYLLABICS CAAI*/
+ RTUNI_ALPHA, /* U+00148b: CANADIAN SYLLABICS CI*/
+ RTUNI_ALPHA, /* U+00148c: CANADIAN SYLLABICS CII*/
+ RTUNI_ALPHA, /* U+00148d: CANADIAN SYLLABICS CO*/
+ RTUNI_ALPHA, /* U+00148e: CANADIAN SYLLABICS COO*/
+ RTUNI_ALPHA, /* U+00148f: CANADIAN SYLLABICS Y-CREE COO*/
+ RTUNI_ALPHA, /* U+001490: CANADIAN SYLLABICS CA*/
+ RTUNI_ALPHA, /* U+001491: CANADIAN SYLLABICS CAA*/
+ RTUNI_ALPHA, /* U+001492: CANADIAN SYLLABICS CWE*/
+ RTUNI_ALPHA, /* U+001493: CANADIAN SYLLABICS WEST-CREE CWE*/
+ RTUNI_ALPHA, /* U+001494: CANADIAN SYLLABICS CWI*/
+ RTUNI_ALPHA, /* U+001495: CANADIAN SYLLABICS WEST-CREE CWI*/
+ RTUNI_ALPHA, /* U+001496: CANADIAN SYLLABICS CWII*/
+ RTUNI_ALPHA, /* U+001497: CANADIAN SYLLABICS WEST-CREE CWII*/
+ RTUNI_ALPHA, /* U+001498: CANADIAN SYLLABICS CWO*/
+ RTUNI_ALPHA, /* U+001499: CANADIAN SYLLABICS WEST-CREE CWO*/
+ RTUNI_ALPHA, /* U+00149a: CANADIAN SYLLABICS CWOO*/
+ RTUNI_ALPHA, /* U+00149b: CANADIAN SYLLABICS WEST-CREE CWOO*/
+ RTUNI_ALPHA, /* U+00149c: CANADIAN SYLLABICS CWA*/
+ RTUNI_ALPHA, /* U+00149d: CANADIAN SYLLABICS WEST-CREE CWA*/
+ RTUNI_ALPHA, /* U+00149e: CANADIAN SYLLABICS CWAA*/
+ RTUNI_ALPHA, /* U+00149f: CANADIAN SYLLABICS WEST-CREE CWAA*/
+ RTUNI_ALPHA, /* U+0014a0: CANADIAN SYLLABICS NASKAPI CWAA*/
+ RTUNI_ALPHA, /* U+0014a1: CANADIAN SYLLABICS C*/
+ RTUNI_ALPHA, /* U+0014a2: CANADIAN SYLLABICS SAYISI TH*/
+ RTUNI_ALPHA, /* U+0014a3: CANADIAN SYLLABICS ME*/
+ RTUNI_ALPHA, /* U+0014a4: CANADIAN SYLLABICS MAAI*/
+ RTUNI_ALPHA, /* U+0014a5: CANADIAN SYLLABICS MI*/
+ RTUNI_ALPHA, /* U+0014a6: CANADIAN SYLLABICS MII*/
+ RTUNI_ALPHA, /* U+0014a7: CANADIAN SYLLABICS MO*/
+ RTUNI_ALPHA, /* U+0014a8: CANADIAN SYLLABICS MOO*/
+ RTUNI_ALPHA, /* U+0014a9: CANADIAN SYLLABICS Y-CREE MOO*/
+ RTUNI_ALPHA, /* U+0014aa: CANADIAN SYLLABICS MA*/
+ RTUNI_ALPHA, /* U+0014ab: CANADIAN SYLLABICS MAA*/
+ RTUNI_ALPHA, /* U+0014ac: CANADIAN SYLLABICS MWE*/
+ RTUNI_ALPHA, /* U+0014ad: CANADIAN SYLLABICS WEST-CREE MWE*/
+ RTUNI_ALPHA, /* U+0014ae: CANADIAN SYLLABICS MWI*/
+ RTUNI_ALPHA, /* U+0014af: CANADIAN SYLLABICS WEST-CREE MWI*/
+ RTUNI_ALPHA, /* U+0014b0: CANADIAN SYLLABICS MWII*/
+ RTUNI_ALPHA, /* U+0014b1: CANADIAN SYLLABICS WEST-CREE MWII*/
+ RTUNI_ALPHA, /* U+0014b2: CANADIAN SYLLABICS MWO*/
+ RTUNI_ALPHA, /* U+0014b3: CANADIAN SYLLABICS WEST-CREE MWO*/
+ RTUNI_ALPHA, /* U+0014b4: CANADIAN SYLLABICS MWOO*/
+ RTUNI_ALPHA, /* U+0014b5: CANADIAN SYLLABICS WEST-CREE MWOO*/
+ RTUNI_ALPHA, /* U+0014b6: CANADIAN SYLLABICS MWA*/
+ RTUNI_ALPHA, /* U+0014b7: CANADIAN SYLLABICS WEST-CREE MWA*/
+ RTUNI_ALPHA, /* U+0014b8: CANADIAN SYLLABICS MWAA*/
+ RTUNI_ALPHA, /* U+0014b9: CANADIAN SYLLABICS WEST-CREE MWAA*/
+ RTUNI_ALPHA, /* U+0014ba: CANADIAN SYLLABICS NASKAPI MWAA*/
+ RTUNI_ALPHA, /* U+0014bb: CANADIAN SYLLABICS M*/
+ RTUNI_ALPHA, /* U+0014bc: CANADIAN SYLLABICS WEST-CREE M*/
+ RTUNI_ALPHA, /* U+0014bd: CANADIAN SYLLABICS MH*/
+ RTUNI_ALPHA, /* U+0014be: CANADIAN SYLLABICS ATHAPASCAN M*/
+ RTUNI_ALPHA, /* U+0014bf: CANADIAN SYLLABICS SAYISI M*/
+ RTUNI_ALPHA, /* U+0014c0: CANADIAN SYLLABICS NE*/
+ RTUNI_ALPHA, /* U+0014c1: CANADIAN SYLLABICS NAAI*/
+ RTUNI_ALPHA, /* U+0014c2: CANADIAN SYLLABICS NI*/
+ RTUNI_ALPHA, /* U+0014c3: CANADIAN SYLLABICS NII*/
+ RTUNI_ALPHA, /* U+0014c4: CANADIAN SYLLABICS NO*/
+ RTUNI_ALPHA, /* U+0014c5: CANADIAN SYLLABICS NOO*/
+ RTUNI_ALPHA, /* U+0014c6: CANADIAN SYLLABICS Y-CREE NOO*/
+ RTUNI_ALPHA, /* U+0014c7: CANADIAN SYLLABICS NA*/
+ RTUNI_ALPHA, /* U+0014c8: CANADIAN SYLLABICS NAA*/
+ RTUNI_ALPHA, /* U+0014c9: CANADIAN SYLLABICS NWE*/
+ RTUNI_ALPHA, /* U+0014ca: CANADIAN SYLLABICS WEST-CREE NWE*/
+ RTUNI_ALPHA, /* U+0014cb: CANADIAN SYLLABICS NWA*/
+ RTUNI_ALPHA, /* U+0014cc: CANADIAN SYLLABICS WEST-CREE NWA*/
+ RTUNI_ALPHA, /* U+0014cd: CANADIAN SYLLABICS NWAA*/
+ RTUNI_ALPHA, /* U+0014ce: CANADIAN SYLLABICS WEST-CREE NWAA*/
+ RTUNI_ALPHA, /* U+0014cf: CANADIAN SYLLABICS NASKAPI NWAA*/
+ RTUNI_ALPHA, /* U+0014d0: CANADIAN SYLLABICS N*/
+ RTUNI_ALPHA, /* U+0014d1: CANADIAN SYLLABICS CARRIER NG*/
+ RTUNI_ALPHA, /* U+0014d2: CANADIAN SYLLABICS NH*/
+ RTUNI_ALPHA, /* U+0014d3: CANADIAN SYLLABICS LE*/
+ RTUNI_ALPHA, /* U+0014d4: CANADIAN SYLLABICS LAAI*/
+ RTUNI_ALPHA, /* U+0014d5: CANADIAN SYLLABICS LI*/
+ RTUNI_ALPHA, /* U+0014d6: CANADIAN SYLLABICS LII*/
+ RTUNI_ALPHA, /* U+0014d7: CANADIAN SYLLABICS LO*/
+ RTUNI_ALPHA, /* U+0014d8: CANADIAN SYLLABICS LOO*/
+ RTUNI_ALPHA, /* U+0014d9: CANADIAN SYLLABICS Y-CREE LOO*/
+ RTUNI_ALPHA, /* U+0014da: CANADIAN SYLLABICS LA*/
+ RTUNI_ALPHA, /* U+0014db: CANADIAN SYLLABICS LAA*/
+ RTUNI_ALPHA, /* U+0014dc: CANADIAN SYLLABICS LWE*/
+ RTUNI_ALPHA, /* U+0014dd: CANADIAN SYLLABICS WEST-CREE LWE*/
+ RTUNI_ALPHA, /* U+0014de: CANADIAN SYLLABICS LWI*/
+ RTUNI_ALPHA, /* U+0014df: CANADIAN SYLLABICS WEST-CREE LWI*/
+ RTUNI_ALPHA, /* U+0014e0: CANADIAN SYLLABICS LWII*/
+ RTUNI_ALPHA, /* U+0014e1: CANADIAN SYLLABICS WEST-CREE LWII*/
+ RTUNI_ALPHA, /* U+0014e2: CANADIAN SYLLABICS LWO*/
+ RTUNI_ALPHA, /* U+0014e3: CANADIAN SYLLABICS WEST-CREE LWO*/
+ RTUNI_ALPHA, /* U+0014e4: CANADIAN SYLLABICS LWOO*/
+ RTUNI_ALPHA, /* U+0014e5: CANADIAN SYLLABICS WEST-CREE LWOO*/
+ RTUNI_ALPHA, /* U+0014e6: CANADIAN SYLLABICS LWA*/
+ RTUNI_ALPHA, /* U+0014e7: CANADIAN SYLLABICS WEST-CREE LWA*/
+ RTUNI_ALPHA, /* U+0014e8: CANADIAN SYLLABICS LWAA*/
+ RTUNI_ALPHA, /* U+0014e9: CANADIAN SYLLABICS WEST-CREE LWAA*/
+ RTUNI_ALPHA, /* U+0014ea: CANADIAN SYLLABICS L*/
+ RTUNI_ALPHA, /* U+0014eb: CANADIAN SYLLABICS WEST-CREE L*/
+ RTUNI_ALPHA, /* U+0014ec: CANADIAN SYLLABICS MEDIAL L*/
+ RTUNI_ALPHA, /* U+0014ed: CANADIAN SYLLABICS SE*/
+ RTUNI_ALPHA, /* U+0014ee: CANADIAN SYLLABICS SAAI*/
+ RTUNI_ALPHA, /* U+0014ef: CANADIAN SYLLABICS SI*/
+ RTUNI_ALPHA, /* U+0014f0: CANADIAN SYLLABICS SII*/
+ RTUNI_ALPHA, /* U+0014f1: CANADIAN SYLLABICS SO*/
+ RTUNI_ALPHA, /* U+0014f2: CANADIAN SYLLABICS SOO*/
+ RTUNI_ALPHA, /* U+0014f3: CANADIAN SYLLABICS Y-CREE SOO*/
+ RTUNI_ALPHA, /* U+0014f4: CANADIAN SYLLABICS SA*/
+ RTUNI_ALPHA, /* U+0014f5: CANADIAN SYLLABICS SAA*/
+ RTUNI_ALPHA, /* U+0014f6: CANADIAN SYLLABICS SWE*/
+ RTUNI_ALPHA, /* U+0014f7: CANADIAN SYLLABICS WEST-CREE SWE*/
+ RTUNI_ALPHA, /* U+0014f8: CANADIAN SYLLABICS SWI*/
+ RTUNI_ALPHA, /* U+0014f9: CANADIAN SYLLABICS WEST-CREE SWI*/
+ RTUNI_ALPHA, /* U+0014fa: CANADIAN SYLLABICS SWII*/
+ RTUNI_ALPHA, /* U+0014fb: CANADIAN SYLLABICS WEST-CREE SWII*/
+ RTUNI_ALPHA, /* U+0014fc: CANADIAN SYLLABICS SWO*/
+ RTUNI_ALPHA, /* U+0014fd: CANADIAN SYLLABICS WEST-CREE SWO*/
+ RTUNI_ALPHA, /* U+0014fe: CANADIAN SYLLABICS SWOO*/
+ RTUNI_ALPHA, /* U+0014ff: CANADIAN SYLLABICS WEST-CREE SWOO*/
+ RTUNI_ALPHA, /* U+001500: CANADIAN SYLLABICS SWA*/
+ RTUNI_ALPHA, /* U+001501: CANADIAN SYLLABICS WEST-CREE SWA*/
+ RTUNI_ALPHA, /* U+001502: CANADIAN SYLLABICS SWAA*/
+ RTUNI_ALPHA, /* U+001503: CANADIAN SYLLABICS WEST-CREE SWAA*/
+ RTUNI_ALPHA, /* U+001504: CANADIAN SYLLABICS NASKAPI SWAA*/
+ RTUNI_ALPHA, /* U+001505: CANADIAN SYLLABICS S*/
+ RTUNI_ALPHA, /* U+001506: CANADIAN SYLLABICS ATHAPASCAN S*/
+ RTUNI_ALPHA, /* U+001507: CANADIAN SYLLABICS SW*/
+ RTUNI_ALPHA, /* U+001508: CANADIAN SYLLABICS BLACKFOOT S*/
+ RTUNI_ALPHA, /* U+001509: CANADIAN SYLLABICS MOOSE-CREE SK*/
+ RTUNI_ALPHA, /* U+00150a: CANADIAN SYLLABICS NASKAPI SKW*/
+ RTUNI_ALPHA, /* U+00150b: CANADIAN SYLLABICS NASKAPI S-W*/
+ RTUNI_ALPHA, /* U+00150c: CANADIAN SYLLABICS NASKAPI SPWA*/
+ RTUNI_ALPHA, /* U+00150d: CANADIAN SYLLABICS NASKAPI STWA*/
+ RTUNI_ALPHA, /* U+00150e: CANADIAN SYLLABICS NASKAPI SKWA*/
+ RTUNI_ALPHA, /* U+00150f: CANADIAN SYLLABICS NASKAPI SCWA*/
+ RTUNI_ALPHA, /* U+001510: CANADIAN SYLLABICS SHE*/
+ RTUNI_ALPHA, /* U+001511: CANADIAN SYLLABICS SHI*/
+ RTUNI_ALPHA, /* U+001512: CANADIAN SYLLABICS SHII*/
+ RTUNI_ALPHA, /* U+001513: CANADIAN SYLLABICS SHO*/
+ RTUNI_ALPHA, /* U+001514: CANADIAN SYLLABICS SHOO*/
+ RTUNI_ALPHA, /* U+001515: CANADIAN SYLLABICS SHA*/
+ RTUNI_ALPHA, /* U+001516: CANADIAN SYLLABICS SHAA*/
+ RTUNI_ALPHA, /* U+001517: CANADIAN SYLLABICS SHWE*/
+ RTUNI_ALPHA, /* U+001518: CANADIAN SYLLABICS WEST-CREE SHWE*/
+ RTUNI_ALPHA, /* U+001519: CANADIAN SYLLABICS SHWI*/
+ RTUNI_ALPHA, /* U+00151a: CANADIAN SYLLABICS WEST-CREE SHWI*/
+ RTUNI_ALPHA, /* U+00151b: CANADIAN SYLLABICS SHWII*/
+ RTUNI_ALPHA, /* U+00151c: CANADIAN SYLLABICS WEST-CREE SHWII*/
+ RTUNI_ALPHA, /* U+00151d: CANADIAN SYLLABICS SHWO*/
+ RTUNI_ALPHA, /* U+00151e: CANADIAN SYLLABICS WEST-CREE SHWO*/
+ RTUNI_ALPHA, /* U+00151f: CANADIAN SYLLABICS SHWOO*/
+ RTUNI_ALPHA, /* U+001520: CANADIAN SYLLABICS WEST-CREE SHWOO*/
+ RTUNI_ALPHA, /* U+001521: CANADIAN SYLLABICS SHWA*/
+ RTUNI_ALPHA, /* U+001522: CANADIAN SYLLABICS WEST-CREE SHWA*/
+ RTUNI_ALPHA, /* U+001523: CANADIAN SYLLABICS SHWAA*/
+ RTUNI_ALPHA, /* U+001524: CANADIAN SYLLABICS WEST-CREE SHWAA*/
+ RTUNI_ALPHA, /* U+001525: CANADIAN SYLLABICS SH*/
+ RTUNI_ALPHA, /* U+001526: CANADIAN SYLLABICS YE*/
+ RTUNI_ALPHA, /* U+001527: CANADIAN SYLLABICS YAAI*/
+ RTUNI_ALPHA, /* U+001528: CANADIAN SYLLABICS YI*/
+ RTUNI_ALPHA, /* U+001529: CANADIAN SYLLABICS YII*/
+ RTUNI_ALPHA, /* U+00152a: CANADIAN SYLLABICS YO*/
+ RTUNI_ALPHA, /* U+00152b: CANADIAN SYLLABICS YOO*/
+ RTUNI_ALPHA, /* U+00152c: CANADIAN SYLLABICS Y-CREE YOO*/
+ RTUNI_ALPHA, /* U+00152d: CANADIAN SYLLABICS YA*/
+ RTUNI_ALPHA, /* U+00152e: CANADIAN SYLLABICS YAA*/
+ RTUNI_ALPHA, /* U+00152f: CANADIAN SYLLABICS YWE*/
+ RTUNI_ALPHA, /* U+001530: CANADIAN SYLLABICS WEST-CREE YWE*/
+ RTUNI_ALPHA, /* U+001531: CANADIAN SYLLABICS YWI*/
+ RTUNI_ALPHA, /* U+001532: CANADIAN SYLLABICS WEST-CREE YWI*/
+ RTUNI_ALPHA, /* U+001533: CANADIAN SYLLABICS YWII*/
+ RTUNI_ALPHA, /* U+001534: CANADIAN SYLLABICS WEST-CREE YWII*/
+ RTUNI_ALPHA, /* U+001535: CANADIAN SYLLABICS YWO*/
+ RTUNI_ALPHA, /* U+001536: CANADIAN SYLLABICS WEST-CREE YWO*/
+ RTUNI_ALPHA, /* U+001537: CANADIAN SYLLABICS YWOO*/
+ RTUNI_ALPHA, /* U+001538: CANADIAN SYLLABICS WEST-CREE YWOO*/
+ RTUNI_ALPHA, /* U+001539: CANADIAN SYLLABICS YWA*/
+ RTUNI_ALPHA, /* U+00153a: CANADIAN SYLLABICS WEST-CREE YWA*/
+ RTUNI_ALPHA, /* U+00153b: CANADIAN SYLLABICS YWAA*/
+ RTUNI_ALPHA, /* U+00153c: CANADIAN SYLLABICS WEST-CREE YWAA*/
+ RTUNI_ALPHA, /* U+00153d: CANADIAN SYLLABICS NASKAPI YWAA*/
+ RTUNI_ALPHA, /* U+00153e: CANADIAN SYLLABICS Y*/
+ RTUNI_ALPHA, /* U+00153f: CANADIAN SYLLABICS BIBLE-CREE Y*/
+ RTUNI_ALPHA, /* U+001540: CANADIAN SYLLABICS WEST-CREE Y*/
+ RTUNI_ALPHA, /* U+001541: CANADIAN SYLLABICS SAYISI YI*/
+ RTUNI_ALPHA, /* U+001542: CANADIAN SYLLABICS RE*/
+ RTUNI_ALPHA, /* U+001543: CANADIAN SYLLABICS R-CREE RE*/
+ RTUNI_ALPHA, /* U+001544: CANADIAN SYLLABICS WEST-CREE LE*/
+ RTUNI_ALPHA, /* U+001545: CANADIAN SYLLABICS RAAI*/
+ RTUNI_ALPHA, /* U+001546: CANADIAN SYLLABICS RI*/
+ RTUNI_ALPHA, /* U+001547: CANADIAN SYLLABICS RII*/
+ RTUNI_ALPHA, /* U+001548: CANADIAN SYLLABICS RO*/
+ RTUNI_ALPHA, /* U+001549: CANADIAN SYLLABICS ROO*/
+ RTUNI_ALPHA, /* U+00154a: CANADIAN SYLLABICS WEST-CREE LO*/
+ RTUNI_ALPHA, /* U+00154b: CANADIAN SYLLABICS RA*/
+ RTUNI_ALPHA, /* U+00154c: CANADIAN SYLLABICS RAA*/
+ RTUNI_ALPHA, /* U+00154d: CANADIAN SYLLABICS WEST-CREE LA*/
+ RTUNI_ALPHA, /* U+00154e: CANADIAN SYLLABICS RWAA*/
+ RTUNI_ALPHA, /* U+00154f: CANADIAN SYLLABICS WEST-CREE RWAA*/
+ RTUNI_ALPHA, /* U+001550: CANADIAN SYLLABICS R*/
+ RTUNI_ALPHA, /* U+001551: CANADIAN SYLLABICS WEST-CREE R*/
+ RTUNI_ALPHA, /* U+001552: CANADIAN SYLLABICS MEDIAL R*/
+ RTUNI_ALPHA, /* U+001553: CANADIAN SYLLABICS FE*/
+ RTUNI_ALPHA, /* U+001554: CANADIAN SYLLABICS FAAI*/
+ RTUNI_ALPHA, /* U+001555: CANADIAN SYLLABICS FI*/
+ RTUNI_ALPHA, /* U+001556: CANADIAN SYLLABICS FII*/
+ RTUNI_ALPHA, /* U+001557: CANADIAN SYLLABICS FO*/
+ RTUNI_ALPHA, /* U+001558: CANADIAN SYLLABICS FOO*/
+ RTUNI_ALPHA, /* U+001559: CANADIAN SYLLABICS FA*/
+ RTUNI_ALPHA, /* U+00155a: CANADIAN SYLLABICS FAA*/
+ RTUNI_ALPHA, /* U+00155b: CANADIAN SYLLABICS FWAA*/
+ RTUNI_ALPHA, /* U+00155c: CANADIAN SYLLABICS WEST-CREE FWAA*/
+ RTUNI_ALPHA, /* U+00155d: CANADIAN SYLLABICS F*/
+ RTUNI_ALPHA, /* U+00155e: CANADIAN SYLLABICS THE*/
+ RTUNI_ALPHA, /* U+00155f: CANADIAN SYLLABICS N-CREE THE*/
+ RTUNI_ALPHA, /* U+001560: CANADIAN SYLLABICS THI*/
+ RTUNI_ALPHA, /* U+001561: CANADIAN SYLLABICS N-CREE THI*/
+ RTUNI_ALPHA, /* U+001562: CANADIAN SYLLABICS THII*/
+ RTUNI_ALPHA, /* U+001563: CANADIAN SYLLABICS N-CREE THII*/
+ RTUNI_ALPHA, /* U+001564: CANADIAN SYLLABICS THO*/
+ RTUNI_ALPHA, /* U+001565: CANADIAN SYLLABICS THOO*/
+ RTUNI_ALPHA, /* U+001566: CANADIAN SYLLABICS THA*/
+ RTUNI_ALPHA, /* U+001567: CANADIAN SYLLABICS THAA*/
+ RTUNI_ALPHA, /* U+001568: CANADIAN SYLLABICS THWAA*/
+ RTUNI_ALPHA, /* U+001569: CANADIAN SYLLABICS WEST-CREE THWAA*/
+ RTUNI_ALPHA, /* U+00156a: CANADIAN SYLLABICS TH*/
+ RTUNI_ALPHA, /* U+00156b: CANADIAN SYLLABICS TTHE*/
+ RTUNI_ALPHA, /* U+00156c: CANADIAN SYLLABICS TTHI*/
+ RTUNI_ALPHA, /* U+00156d: CANADIAN SYLLABICS TTHO*/
+ RTUNI_ALPHA, /* U+00156e: CANADIAN SYLLABICS TTHA*/
+ RTUNI_ALPHA, /* U+00156f: CANADIAN SYLLABICS TTH*/
+ RTUNI_ALPHA, /* U+001570: CANADIAN SYLLABICS TYE*/
+ RTUNI_ALPHA, /* U+001571: CANADIAN SYLLABICS TYI*/
+ RTUNI_ALPHA, /* U+001572: CANADIAN SYLLABICS TYO*/
+ RTUNI_ALPHA, /* U+001573: CANADIAN SYLLABICS TYA*/
+ RTUNI_ALPHA, /* U+001574: CANADIAN SYLLABICS NUNAVIK HE*/
+ RTUNI_ALPHA, /* U+001575: CANADIAN SYLLABICS NUNAVIK HI*/
+ RTUNI_ALPHA, /* U+001576: CANADIAN SYLLABICS NUNAVIK HII*/
+ RTUNI_ALPHA, /* U+001577: CANADIAN SYLLABICS NUNAVIK HO*/
+ RTUNI_ALPHA, /* U+001578: CANADIAN SYLLABICS NUNAVIK HOO*/
+ RTUNI_ALPHA, /* U+001579: CANADIAN SYLLABICS NUNAVIK HA*/
+ RTUNI_ALPHA, /* U+00157a: CANADIAN SYLLABICS NUNAVIK HAA*/
+ RTUNI_ALPHA, /* U+00157b: CANADIAN SYLLABICS NUNAVIK H*/
+ RTUNI_ALPHA, /* U+00157c: CANADIAN SYLLABICS NUNAVUT H*/
+ RTUNI_ALPHA, /* U+00157d: CANADIAN SYLLABICS HK*/
+ RTUNI_ALPHA, /* U+00157e: CANADIAN SYLLABICS QAAI*/
+ RTUNI_ALPHA, /* U+00157f: CANADIAN SYLLABICS QI*/
+ RTUNI_ALPHA, /* U+001580: CANADIAN SYLLABICS QII*/
+ RTUNI_ALPHA, /* U+001581: CANADIAN SYLLABICS QO*/
+ RTUNI_ALPHA, /* U+001582: CANADIAN SYLLABICS QOO*/
+ RTUNI_ALPHA, /* U+001583: CANADIAN SYLLABICS QA*/
+ RTUNI_ALPHA, /* U+001584: CANADIAN SYLLABICS QAA*/
+ RTUNI_ALPHA, /* U+001585: CANADIAN SYLLABICS Q*/
+ RTUNI_ALPHA, /* U+001586: CANADIAN SYLLABICS TLHE*/
+ RTUNI_ALPHA, /* U+001587: CANADIAN SYLLABICS TLHI*/
+ RTUNI_ALPHA, /* U+001588: CANADIAN SYLLABICS TLHO*/
+ RTUNI_ALPHA, /* U+001589: CANADIAN SYLLABICS TLHA*/
+ RTUNI_ALPHA, /* U+00158a: CANADIAN SYLLABICS WEST-CREE RE*/
+ RTUNI_ALPHA, /* U+00158b: CANADIAN SYLLABICS WEST-CREE RI*/
+ RTUNI_ALPHA, /* U+00158c: CANADIAN SYLLABICS WEST-CREE RO*/
+ RTUNI_ALPHA, /* U+00158d: CANADIAN SYLLABICS WEST-CREE RA*/
+ RTUNI_ALPHA, /* U+00158e: CANADIAN SYLLABICS NGAAI*/
+ RTUNI_ALPHA, /* U+00158f: CANADIAN SYLLABICS NGI*/
+ RTUNI_ALPHA, /* U+001590: CANADIAN SYLLABICS NGII*/
+ RTUNI_ALPHA, /* U+001591: CANADIAN SYLLABICS NGO*/
+ RTUNI_ALPHA, /* U+001592: CANADIAN SYLLABICS NGOO*/
+ RTUNI_ALPHA, /* U+001593: CANADIAN SYLLABICS NGA*/
+ RTUNI_ALPHA, /* U+001594: CANADIAN SYLLABICS NGAA*/
+ RTUNI_ALPHA, /* U+001595: CANADIAN SYLLABICS NG*/
+ RTUNI_ALPHA, /* U+001596: CANADIAN SYLLABICS NNG*/
+ RTUNI_ALPHA, /* U+001597: CANADIAN SYLLABICS SAYISI SHE*/
+ RTUNI_ALPHA, /* U+001598: CANADIAN SYLLABICS SAYISI SHI*/
+ RTUNI_ALPHA, /* U+001599: CANADIAN SYLLABICS SAYISI SHO*/
+ RTUNI_ALPHA, /* U+00159a: CANADIAN SYLLABICS SAYISI SHA*/
+ RTUNI_ALPHA, /* U+00159b: CANADIAN SYLLABICS WOODS-CREE THE*/
+ RTUNI_ALPHA, /* U+00159c: CANADIAN SYLLABICS WOODS-CREE THI*/
+ RTUNI_ALPHA, /* U+00159d: CANADIAN SYLLABICS WOODS-CREE THO*/
+ RTUNI_ALPHA, /* U+00159e: CANADIAN SYLLABICS WOODS-CREE THA*/
+ RTUNI_ALPHA, /* U+00159f: CANADIAN SYLLABICS WOODS-CREE TH*/
+ RTUNI_ALPHA, /* U+0015a0: CANADIAN SYLLABICS LHI*/
+ RTUNI_ALPHA, /* U+0015a1: CANADIAN SYLLABICS LHII*/
+ RTUNI_ALPHA, /* U+0015a2: CANADIAN SYLLABICS LHO*/
+ RTUNI_ALPHA, /* U+0015a3: CANADIAN SYLLABICS LHOO*/
+ RTUNI_ALPHA, /* U+0015a4: CANADIAN SYLLABICS LHA*/
+ RTUNI_ALPHA, /* U+0015a5: CANADIAN SYLLABICS LHAA*/
+ RTUNI_ALPHA, /* U+0015a6: CANADIAN SYLLABICS LH*/
+ RTUNI_ALPHA, /* U+0015a7: CANADIAN SYLLABICS TH-CREE THE*/
+ RTUNI_ALPHA, /* U+0015a8: CANADIAN SYLLABICS TH-CREE THI*/
+ RTUNI_ALPHA, /* U+0015a9: CANADIAN SYLLABICS TH-CREE THII*/
+ RTUNI_ALPHA, /* U+0015aa: CANADIAN SYLLABICS TH-CREE THO*/
+ RTUNI_ALPHA, /* U+0015ab: CANADIAN SYLLABICS TH-CREE THOO*/
+ RTUNI_ALPHA, /* U+0015ac: CANADIAN SYLLABICS TH-CREE THA*/
+ RTUNI_ALPHA, /* U+0015ad: CANADIAN SYLLABICS TH-CREE THAA*/
+ RTUNI_ALPHA, /* U+0015ae: CANADIAN SYLLABICS TH-CREE TH*/
+ RTUNI_ALPHA, /* U+0015af: CANADIAN SYLLABICS AIVILIK B*/
+ RTUNI_ALPHA, /* U+0015b0: CANADIAN SYLLABICS BLACKFOOT E*/
+ RTUNI_ALPHA, /* U+0015b1: CANADIAN SYLLABICS BLACKFOOT I*/
+ RTUNI_ALPHA, /* U+0015b2: CANADIAN SYLLABICS BLACKFOOT O*/
+ RTUNI_ALPHA, /* U+0015b3: CANADIAN SYLLABICS BLACKFOOT A*/
+ RTUNI_ALPHA, /* U+0015b4: CANADIAN SYLLABICS BLACKFOOT WE*/
+ RTUNI_ALPHA, /* U+0015b5: CANADIAN SYLLABICS BLACKFOOT WI*/
+ RTUNI_ALPHA, /* U+0015b6: CANADIAN SYLLABICS BLACKFOOT WO*/
+ RTUNI_ALPHA, /* U+0015b7: CANADIAN SYLLABICS BLACKFOOT WA*/
+ RTUNI_ALPHA, /* U+0015b8: CANADIAN SYLLABICS BLACKFOOT NE*/
+ RTUNI_ALPHA, /* U+0015b9: CANADIAN SYLLABICS BLACKFOOT NI*/
+ RTUNI_ALPHA, /* U+0015ba: CANADIAN SYLLABICS BLACKFOOT NO*/
+ RTUNI_ALPHA, /* U+0015bb: CANADIAN SYLLABICS BLACKFOOT NA*/
+ RTUNI_ALPHA, /* U+0015bc: CANADIAN SYLLABICS BLACKFOOT KE*/
+ RTUNI_ALPHA, /* U+0015bd: CANADIAN SYLLABICS BLACKFOOT KI*/
+ RTUNI_ALPHA, /* U+0015be: CANADIAN SYLLABICS BLACKFOOT KO*/
+ RTUNI_ALPHA, /* U+0015bf: CANADIAN SYLLABICS BLACKFOOT KA*/
+ RTUNI_ALPHA, /* U+0015c0: CANADIAN SYLLABICS SAYISI HE*/
+ RTUNI_ALPHA, /* U+0015c1: CANADIAN SYLLABICS SAYISI HI*/
+ RTUNI_ALPHA, /* U+0015c2: CANADIAN SYLLABICS SAYISI HO*/
+ RTUNI_ALPHA, /* U+0015c3: CANADIAN SYLLABICS SAYISI HA*/
+ RTUNI_ALPHA, /* U+0015c4: CANADIAN SYLLABICS CARRIER GHU*/
+ RTUNI_ALPHA, /* U+0015c5: CANADIAN SYLLABICS CARRIER GHO*/
+ RTUNI_ALPHA, /* U+0015c6: CANADIAN SYLLABICS CARRIER GHE*/
+ RTUNI_ALPHA, /* U+0015c7: CANADIAN SYLLABICS CARRIER GHEE*/
+ RTUNI_ALPHA, /* U+0015c8: CANADIAN SYLLABICS CARRIER GHI*/
+ RTUNI_ALPHA, /* U+0015c9: CANADIAN SYLLABICS CARRIER GHA*/
+ RTUNI_ALPHA, /* U+0015ca: CANADIAN SYLLABICS CARRIER RU*/
+ RTUNI_ALPHA, /* U+0015cb: CANADIAN SYLLABICS CARRIER RO*/
+ RTUNI_ALPHA, /* U+0015cc: CANADIAN SYLLABICS CARRIER RE*/
+ RTUNI_ALPHA, /* U+0015cd: CANADIAN SYLLABICS CARRIER REE*/
+ RTUNI_ALPHA, /* U+0015ce: CANADIAN SYLLABICS CARRIER RI*/
+ RTUNI_ALPHA, /* U+0015cf: CANADIAN SYLLABICS CARRIER RA*/
+ RTUNI_ALPHA, /* U+0015d0: CANADIAN SYLLABICS CARRIER WU*/
+ RTUNI_ALPHA, /* U+0015d1: CANADIAN SYLLABICS CARRIER WO*/
+ RTUNI_ALPHA, /* U+0015d2: CANADIAN SYLLABICS CARRIER WE*/
+ RTUNI_ALPHA, /* U+0015d3: CANADIAN SYLLABICS CARRIER WEE*/
+ RTUNI_ALPHA, /* U+0015d4: CANADIAN SYLLABICS CARRIER WI*/
+ RTUNI_ALPHA, /* U+0015d5: CANADIAN SYLLABICS CARRIER WA*/
+ RTUNI_ALPHA, /* U+0015d6: CANADIAN SYLLABICS CARRIER HWU*/
+ RTUNI_ALPHA, /* U+0015d7: CANADIAN SYLLABICS CARRIER HWO*/
+ RTUNI_ALPHA, /* U+0015d8: CANADIAN SYLLABICS CARRIER HWE*/
+ RTUNI_ALPHA, /* U+0015d9: CANADIAN SYLLABICS CARRIER HWEE*/
+ RTUNI_ALPHA, /* U+0015da: CANADIAN SYLLABICS CARRIER HWI*/
+ RTUNI_ALPHA, /* U+0015db: CANADIAN SYLLABICS CARRIER HWA*/
+ RTUNI_ALPHA, /* U+0015dc: CANADIAN SYLLABICS CARRIER THU*/
+ RTUNI_ALPHA, /* U+0015dd: CANADIAN SYLLABICS CARRIER THO*/
+ RTUNI_ALPHA, /* U+0015de: CANADIAN SYLLABICS CARRIER THE*/
+ RTUNI_ALPHA, /* U+0015df: CANADIAN SYLLABICS CARRIER THEE*/
+ RTUNI_ALPHA, /* U+0015e0: CANADIAN SYLLABICS CARRIER THI*/
+ RTUNI_ALPHA, /* U+0015e1: CANADIAN SYLLABICS CARRIER THA*/
+ RTUNI_ALPHA, /* U+0015e2: CANADIAN SYLLABICS CARRIER TTU*/
+ RTUNI_ALPHA, /* U+0015e3: CANADIAN SYLLABICS CARRIER TTO*/
+ RTUNI_ALPHA, /* U+0015e4: CANADIAN SYLLABICS CARRIER TTE*/
+ RTUNI_ALPHA, /* U+0015e5: CANADIAN SYLLABICS CARRIER TTEE*/
+ RTUNI_ALPHA, /* U+0015e6: CANADIAN SYLLABICS CARRIER TTI*/
+ RTUNI_ALPHA, /* U+0015e7: CANADIAN SYLLABICS CARRIER TTA*/
+ RTUNI_ALPHA, /* U+0015e8: CANADIAN SYLLABICS CARRIER PU*/
+ RTUNI_ALPHA, /* U+0015e9: CANADIAN SYLLABICS CARRIER PO*/
+ RTUNI_ALPHA, /* U+0015ea: CANADIAN SYLLABICS CARRIER PE*/
+ RTUNI_ALPHA, /* U+0015eb: CANADIAN SYLLABICS CARRIER PEE*/
+ RTUNI_ALPHA, /* U+0015ec: CANADIAN SYLLABICS CARRIER PI*/
+ RTUNI_ALPHA, /* U+0015ed: CANADIAN SYLLABICS CARRIER PA*/
+ RTUNI_ALPHA, /* U+0015ee: CANADIAN SYLLABICS CARRIER P*/
+ RTUNI_ALPHA, /* U+0015ef: CANADIAN SYLLABICS CARRIER GU*/
+ RTUNI_ALPHA, /* U+0015f0: CANADIAN SYLLABICS CARRIER GO*/
+ RTUNI_ALPHA, /* U+0015f1: CANADIAN SYLLABICS CARRIER GE*/
+ RTUNI_ALPHA, /* U+0015f2: CANADIAN SYLLABICS CARRIER GEE*/
+ RTUNI_ALPHA, /* U+0015f3: CANADIAN SYLLABICS CARRIER GI*/
+ RTUNI_ALPHA, /* U+0015f4: CANADIAN SYLLABICS CARRIER GA*/
+ RTUNI_ALPHA, /* U+0015f5: CANADIAN SYLLABICS CARRIER KHU*/
+ RTUNI_ALPHA, /* U+0015f6: CANADIAN SYLLABICS CARRIER KHO*/
+ RTUNI_ALPHA, /* U+0015f7: CANADIAN SYLLABICS CARRIER KHE*/
+ RTUNI_ALPHA, /* U+0015f8: CANADIAN SYLLABICS CARRIER KHEE*/
+ RTUNI_ALPHA, /* U+0015f9: CANADIAN SYLLABICS CARRIER KHI*/
+ RTUNI_ALPHA, /* U+0015fa: CANADIAN SYLLABICS CARRIER KHA*/
+ RTUNI_ALPHA, /* U+0015fb: CANADIAN SYLLABICS CARRIER KKU*/
+ RTUNI_ALPHA, /* U+0015fc: CANADIAN SYLLABICS CARRIER KKO*/
+ RTUNI_ALPHA, /* U+0015fd: CANADIAN SYLLABICS CARRIER KKE*/
+ RTUNI_ALPHA, /* U+0015fe: CANADIAN SYLLABICS CARRIER KKEE*/
+ RTUNI_ALPHA, /* U+0015ff: CANADIAN SYLLABICS CARRIER KKI*/
+ RTUNI_ALPHA, /* U+001600: CANADIAN SYLLABICS CARRIER KKA*/
+ RTUNI_ALPHA, /* U+001601: CANADIAN SYLLABICS CARRIER KK*/
+ RTUNI_ALPHA, /* U+001602: CANADIAN SYLLABICS CARRIER NU*/
+ RTUNI_ALPHA, /* U+001603: CANADIAN SYLLABICS CARRIER NO*/
+ RTUNI_ALPHA, /* U+001604: CANADIAN SYLLABICS CARRIER NE*/
+ RTUNI_ALPHA, /* U+001605: CANADIAN SYLLABICS CARRIER NEE*/
+ RTUNI_ALPHA, /* U+001606: CANADIAN SYLLABICS CARRIER NI*/
+ RTUNI_ALPHA, /* U+001607: CANADIAN SYLLABICS CARRIER NA*/
+ RTUNI_ALPHA, /* U+001608: CANADIAN SYLLABICS CARRIER MU*/
+ RTUNI_ALPHA, /* U+001609: CANADIAN SYLLABICS CARRIER MO*/
+ RTUNI_ALPHA, /* U+00160a: CANADIAN SYLLABICS CARRIER ME*/
+ RTUNI_ALPHA, /* U+00160b: CANADIAN SYLLABICS CARRIER MEE*/
+ RTUNI_ALPHA, /* U+00160c: CANADIAN SYLLABICS CARRIER MI*/
+ RTUNI_ALPHA, /* U+00160d: CANADIAN SYLLABICS CARRIER MA*/
+ RTUNI_ALPHA, /* U+00160e: CANADIAN SYLLABICS CARRIER YU*/
+ RTUNI_ALPHA, /* U+00160f: CANADIAN SYLLABICS CARRIER YO*/
+ RTUNI_ALPHA, /* U+001610: CANADIAN SYLLABICS CARRIER YE*/
+ RTUNI_ALPHA, /* U+001611: CANADIAN SYLLABICS CARRIER YEE*/
+ RTUNI_ALPHA, /* U+001612: CANADIAN SYLLABICS CARRIER YI*/
+ RTUNI_ALPHA, /* U+001613: CANADIAN SYLLABICS CARRIER YA*/
+ RTUNI_ALPHA, /* U+001614: CANADIAN SYLLABICS CARRIER JU*/
+ RTUNI_ALPHA, /* U+001615: CANADIAN SYLLABICS SAYISI JU*/
+ RTUNI_ALPHA, /* U+001616: CANADIAN SYLLABICS CARRIER JO*/
+ RTUNI_ALPHA, /* U+001617: CANADIAN SYLLABICS CARRIER JE*/
+ RTUNI_ALPHA, /* U+001618: CANADIAN SYLLABICS CARRIER JEE*/
+ RTUNI_ALPHA, /* U+001619: CANADIAN SYLLABICS CARRIER JI*/
+ RTUNI_ALPHA, /* U+00161a: CANADIAN SYLLABICS SAYISI JI*/
+ RTUNI_ALPHA, /* U+00161b: CANADIAN SYLLABICS CARRIER JA*/
+ RTUNI_ALPHA, /* U+00161c: CANADIAN SYLLABICS CARRIER JJU*/
+ RTUNI_ALPHA, /* U+00161d: CANADIAN SYLLABICS CARRIER JJO*/
+ RTUNI_ALPHA, /* U+00161e: CANADIAN SYLLABICS CARRIER JJE*/
+ RTUNI_ALPHA, /* U+00161f: CANADIAN SYLLABICS CARRIER JJEE*/
+ RTUNI_ALPHA, /* U+001620: CANADIAN SYLLABICS CARRIER JJI*/
+ RTUNI_ALPHA, /* U+001621: CANADIAN SYLLABICS CARRIER JJA*/
+ RTUNI_ALPHA, /* U+001622: CANADIAN SYLLABICS CARRIER LU*/
+ RTUNI_ALPHA, /* U+001623: CANADIAN SYLLABICS CARRIER LO*/
+ RTUNI_ALPHA, /* U+001624: CANADIAN SYLLABICS CARRIER LE*/
+ RTUNI_ALPHA, /* U+001625: CANADIAN SYLLABICS CARRIER LEE*/
+ RTUNI_ALPHA, /* U+001626: CANADIAN SYLLABICS CARRIER LI*/
+ RTUNI_ALPHA, /* U+001627: CANADIAN SYLLABICS CARRIER LA*/
+ RTUNI_ALPHA, /* U+001628: CANADIAN SYLLABICS CARRIER DLU*/
+ RTUNI_ALPHA, /* U+001629: CANADIAN SYLLABICS CARRIER DLO*/
+ RTUNI_ALPHA, /* U+00162a: CANADIAN SYLLABICS CARRIER DLE*/
+ RTUNI_ALPHA, /* U+00162b: CANADIAN SYLLABICS CARRIER DLEE*/
+ RTUNI_ALPHA, /* U+00162c: CANADIAN SYLLABICS CARRIER DLI*/
+ RTUNI_ALPHA, /* U+00162d: CANADIAN SYLLABICS CARRIER DLA*/
+ RTUNI_ALPHA, /* U+00162e: CANADIAN SYLLABICS CARRIER LHU*/
+ RTUNI_ALPHA, /* U+00162f: CANADIAN SYLLABICS CARRIER LHO*/
+ RTUNI_ALPHA, /* U+001630: CANADIAN SYLLABICS CARRIER LHE*/
+ RTUNI_ALPHA, /* U+001631: CANADIAN SYLLABICS CARRIER LHEE*/
+ RTUNI_ALPHA, /* U+001632: CANADIAN SYLLABICS CARRIER LHI*/
+ RTUNI_ALPHA, /* U+001633: CANADIAN SYLLABICS CARRIER LHA*/
+ RTUNI_ALPHA, /* U+001634: CANADIAN SYLLABICS CARRIER TLHU*/
+ RTUNI_ALPHA, /* U+001635: CANADIAN SYLLABICS CARRIER TLHO*/
+ RTUNI_ALPHA, /* U+001636: CANADIAN SYLLABICS CARRIER TLHE*/
+ RTUNI_ALPHA, /* U+001637: CANADIAN SYLLABICS CARRIER TLHEE*/
+ RTUNI_ALPHA, /* U+001638: CANADIAN SYLLABICS CARRIER TLHI*/
+ RTUNI_ALPHA, /* U+001639: CANADIAN SYLLABICS CARRIER TLHA*/
+ RTUNI_ALPHA, /* U+00163a: CANADIAN SYLLABICS CARRIER TLU*/
+ RTUNI_ALPHA, /* U+00163b: CANADIAN SYLLABICS CARRIER TLO*/
+ RTUNI_ALPHA, /* U+00163c: CANADIAN SYLLABICS CARRIER TLE*/
+ RTUNI_ALPHA, /* U+00163d: CANADIAN SYLLABICS CARRIER TLEE*/
+ RTUNI_ALPHA, /* U+00163e: CANADIAN SYLLABICS CARRIER TLI*/
+ RTUNI_ALPHA, /* U+00163f: CANADIAN SYLLABICS CARRIER TLA*/
+ RTUNI_ALPHA, /* U+001640: CANADIAN SYLLABICS CARRIER ZU*/
+ RTUNI_ALPHA, /* U+001641: CANADIAN SYLLABICS CARRIER ZO*/
+ RTUNI_ALPHA, /* U+001642: CANADIAN SYLLABICS CARRIER ZE*/
+ RTUNI_ALPHA, /* U+001643: CANADIAN SYLLABICS CARRIER ZEE*/
+ RTUNI_ALPHA, /* U+001644: CANADIAN SYLLABICS CARRIER ZI*/
+ RTUNI_ALPHA, /* U+001645: CANADIAN SYLLABICS CARRIER ZA*/
+ RTUNI_ALPHA, /* U+001646: CANADIAN SYLLABICS CARRIER Z*/
+ RTUNI_ALPHA, /* U+001647: CANADIAN SYLLABICS CARRIER INITIAL Z*/
+ RTUNI_ALPHA, /* U+001648: CANADIAN SYLLABICS CARRIER DZU*/
+ RTUNI_ALPHA, /* U+001649: CANADIAN SYLLABICS CARRIER DZO*/
+ RTUNI_ALPHA, /* U+00164a: CANADIAN SYLLABICS CARRIER DZE*/
+ RTUNI_ALPHA, /* U+00164b: CANADIAN SYLLABICS CARRIER DZEE*/
+ RTUNI_ALPHA, /* U+00164c: CANADIAN SYLLABICS CARRIER DZI*/
+ RTUNI_ALPHA, /* U+00164d: CANADIAN SYLLABICS CARRIER DZA*/
+ RTUNI_ALPHA, /* U+00164e: CANADIAN SYLLABICS CARRIER SU*/
+ RTUNI_ALPHA, /* U+00164f: CANADIAN SYLLABICS CARRIER SO*/
+ RTUNI_ALPHA, /* U+001650: CANADIAN SYLLABICS CARRIER SE*/
+ RTUNI_ALPHA, /* U+001651: CANADIAN SYLLABICS CARRIER SEE*/
+ RTUNI_ALPHA, /* U+001652: CANADIAN SYLLABICS CARRIER SI*/
+ RTUNI_ALPHA, /* U+001653: CANADIAN SYLLABICS CARRIER SA*/
+ RTUNI_ALPHA, /* U+001654: CANADIAN SYLLABICS CARRIER SHU*/
+ RTUNI_ALPHA, /* U+001655: CANADIAN SYLLABICS CARRIER SHO*/
+ RTUNI_ALPHA, /* U+001656: CANADIAN SYLLABICS CARRIER SHE*/
+ RTUNI_ALPHA, /* U+001657: CANADIAN SYLLABICS CARRIER SHEE*/
+ RTUNI_ALPHA, /* U+001658: CANADIAN SYLLABICS CARRIER SHI*/
+ RTUNI_ALPHA, /* U+001659: CANADIAN SYLLABICS CARRIER SHA*/
+ RTUNI_ALPHA, /* U+00165a: CANADIAN SYLLABICS CARRIER SH*/
+ RTUNI_ALPHA, /* U+00165b: CANADIAN SYLLABICS CARRIER TSU*/
+ RTUNI_ALPHA, /* U+00165c: CANADIAN SYLLABICS CARRIER TSO*/
+ RTUNI_ALPHA, /* U+00165d: CANADIAN SYLLABICS CARRIER TSE*/
+ RTUNI_ALPHA, /* U+00165e: CANADIAN SYLLABICS CARRIER TSEE*/
+ RTUNI_ALPHA, /* U+00165f: CANADIAN SYLLABICS CARRIER TSI*/
+ RTUNI_ALPHA, /* U+001660: CANADIAN SYLLABICS CARRIER TSA*/
+ RTUNI_ALPHA, /* U+001661: CANADIAN SYLLABICS CARRIER CHU*/
+ RTUNI_ALPHA, /* U+001662: CANADIAN SYLLABICS CARRIER CHO*/
+ RTUNI_ALPHA, /* U+001663: CANADIAN SYLLABICS CARRIER CHE*/
+ RTUNI_ALPHA, /* U+001664: CANADIAN SYLLABICS CARRIER CHEE*/
+ RTUNI_ALPHA, /* U+001665: CANADIAN SYLLABICS CARRIER CHI*/
+ RTUNI_ALPHA, /* U+001666: CANADIAN SYLLABICS CARRIER CHA*/
+ RTUNI_ALPHA, /* U+001667: CANADIAN SYLLABICS CARRIER TTSU*/
+ RTUNI_ALPHA, /* U+001668: CANADIAN SYLLABICS CARRIER TTSO*/
+ RTUNI_ALPHA, /* U+001669: CANADIAN SYLLABICS CARRIER TTSE*/
+ RTUNI_ALPHA, /* U+00166a: CANADIAN SYLLABICS CARRIER TTSEE*/
+ RTUNI_ALPHA, /* U+00166b: CANADIAN SYLLABICS CARRIER TTSI*/
+ RTUNI_ALPHA, /* U+00166c: CANADIAN SYLLABICS CARRIER TTSA*/
+ 0, /* U+00166d: CANADIAN SYLLABICS CHI SIGN*/
+ 0, /* U+00166e: CANADIAN SYLLABICS FULL STOP*/
+ RTUNI_ALPHA, /* U+00166f: CANADIAN SYLLABICS QAI*/
+ RTUNI_ALPHA, /* U+001670: CANADIAN SYLLABICS NGAI*/
+ RTUNI_ALPHA, /* U+001671: CANADIAN SYLLABICS NNGI*/
+ RTUNI_ALPHA, /* U+001672: CANADIAN SYLLABICS NNGII*/
+ RTUNI_ALPHA, /* U+001673: CANADIAN SYLLABICS NNGO*/
+ RTUNI_ALPHA, /* U+001674: CANADIAN SYLLABICS NNGOO*/
+ RTUNI_ALPHA, /* U+001675: CANADIAN SYLLABICS NNGA*/
+ RTUNI_ALPHA, /* U+001676: CANADIAN SYLLABICS NNGAA*/
+ RTUNI_ALPHA, /* U+001677: CANADIAN SYLLABICS WOODS-CREE THWEE*/
+ RTUNI_ALPHA, /* U+001678: CANADIAN SYLLABICS WOODS-CREE THWI*/
+ RTUNI_ALPHA, /* U+001679: CANADIAN SYLLABICS WOODS-CREE THWII*/
+ RTUNI_ALPHA, /* U+00167a: CANADIAN SYLLABICS WOODS-CREE THWO*/
+ RTUNI_ALPHA, /* U+00167b: CANADIAN SYLLABICS WOODS-CREE THWOO*/
+ RTUNI_ALPHA, /* U+00167c: CANADIAN SYLLABICS WOODS-CREE THWA*/
+ RTUNI_ALPHA, /* U+00167d: CANADIAN SYLLABICS WOODS-CREE THWAA*/
+ RTUNI_ALPHA, /* U+00167e: CANADIAN SYLLABICS WOODS-CREE FINAL TH*/
+ RTUNI_ALPHA, /* U+00167f: CANADIAN SYLLABICS BLACKFOOT W*/
+ RTUNI_WSPACE, /* U+001680: OGHAM SPACE MARK*/
+ RTUNI_ALPHA, /* U+001681: OGHAM LETTER BEITH*/
+ RTUNI_ALPHA, /* U+001682: OGHAM LETTER LUIS*/
+ RTUNI_ALPHA, /* U+001683: OGHAM LETTER FEARN*/
+ RTUNI_ALPHA, /* U+001684: OGHAM LETTER SAIL*/
+ RTUNI_ALPHA, /* U+001685: OGHAM LETTER NION*/
+ RTUNI_ALPHA, /* U+001686: OGHAM LETTER UATH*/
+ RTUNI_ALPHA, /* U+001687: OGHAM LETTER DAIR*/
+ RTUNI_ALPHA, /* U+001688: OGHAM LETTER TINNE*/
+ RTUNI_ALPHA, /* U+001689: OGHAM LETTER COLL*/
+ RTUNI_ALPHA, /* U+00168a: OGHAM LETTER CEIRT*/
+ RTUNI_ALPHA, /* U+00168b: OGHAM LETTER MUIN*/
+ RTUNI_ALPHA, /* U+00168c: OGHAM LETTER GORT*/
+ RTUNI_ALPHA, /* U+00168d: OGHAM LETTER NGEADAL*/
+ RTUNI_ALPHA, /* U+00168e: OGHAM LETTER STRAIF*/
+ RTUNI_ALPHA, /* U+00168f: OGHAM LETTER RUIS*/
+ RTUNI_ALPHA, /* U+001690: OGHAM LETTER AILM*/
+ RTUNI_ALPHA, /* U+001691: OGHAM LETTER ONN*/
+ RTUNI_ALPHA, /* U+001692: OGHAM LETTER UR*/
+ RTUNI_ALPHA, /* U+001693: OGHAM LETTER EADHADH*/
+ RTUNI_ALPHA, /* U+001694: OGHAM LETTER IODHADH*/
+ RTUNI_ALPHA, /* U+001695: OGHAM LETTER EABHADH*/
+ RTUNI_ALPHA, /* U+001696: OGHAM LETTER OR*/
+ RTUNI_ALPHA, /* U+001697: OGHAM LETTER UILLEANN*/
+ RTUNI_ALPHA, /* U+001698: OGHAM LETTER IFIN*/
+ RTUNI_ALPHA, /* U+001699: OGHAM LETTER EAMHANCHOLL*/
+ RTUNI_ALPHA, /* U+00169a: OGHAM LETTER PEITH*/
+ 0, /* U+00169b: OGHAM FEATHER MARK*/
+ 0, /* U+00169c: OGHAM REVERSED FEATHER MARK*/
+ 0, /* U+00169d: */
+ 0, /* U+00169e: */
+ 0, /* U+00169f: */
+ RTUNI_ALPHA, /* U+0016a0: RUNIC LETTER FEHU FEOH FE F*/
+ RTUNI_ALPHA, /* U+0016a1: RUNIC LETTER V*/
+ RTUNI_ALPHA, /* U+0016a2: RUNIC LETTER URUZ UR U*/
+ RTUNI_ALPHA, /* U+0016a3: RUNIC LETTER YR*/
+ RTUNI_ALPHA, /* U+0016a4: RUNIC LETTER Y*/
+ RTUNI_ALPHA, /* U+0016a5: RUNIC LETTER W*/
+ RTUNI_ALPHA, /* U+0016a6: RUNIC LETTER THURISAZ THURS THORN*/
+ RTUNI_ALPHA, /* U+0016a7: RUNIC LETTER ETH*/
+ RTUNI_ALPHA, /* U+0016a8: RUNIC LETTER ANSUZ A*/
+ RTUNI_ALPHA, /* U+0016a9: RUNIC LETTER OS O*/
+ RTUNI_ALPHA, /* U+0016aa: RUNIC LETTER AC A*/
+ RTUNI_ALPHA, /* U+0016ab: RUNIC LETTER AESC*/
+ RTUNI_ALPHA, /* U+0016ac: RUNIC LETTER LONG-BRANCH-OSS O*/
+ RTUNI_ALPHA, /* U+0016ad: RUNIC LETTER SHORT-TWIG-OSS O*/
+ RTUNI_ALPHA, /* U+0016ae: RUNIC LETTER O*/
+ RTUNI_ALPHA, /* U+0016af: RUNIC LETTER OE*/
+ RTUNI_ALPHA, /* U+0016b0: RUNIC LETTER ON*/
+ RTUNI_ALPHA, /* U+0016b1: RUNIC LETTER RAIDO RAD REID R*/
+ RTUNI_ALPHA, /* U+0016b2: RUNIC LETTER KAUNA*/
+ RTUNI_ALPHA, /* U+0016b3: RUNIC LETTER CEN*/
+ RTUNI_ALPHA, /* U+0016b4: RUNIC LETTER KAUN K*/
+ RTUNI_ALPHA, /* U+0016b5: RUNIC LETTER G*/
+ RTUNI_ALPHA, /* U+0016b6: RUNIC LETTER ENG*/
+ RTUNI_ALPHA, /* U+0016b7: RUNIC LETTER GEBO GYFU G*/
+ RTUNI_ALPHA, /* U+0016b8: RUNIC LETTER GAR*/
+ RTUNI_ALPHA, /* U+0016b9: RUNIC LETTER WUNJO WYNN W*/
+ RTUNI_ALPHA, /* U+0016ba: RUNIC LETTER HAGLAZ H*/
+ RTUNI_ALPHA, /* U+0016bb: RUNIC LETTER HAEGL H*/
+ RTUNI_ALPHA, /* U+0016bc: RUNIC LETTER LONG-BRANCH-HAGALL H*/
+ RTUNI_ALPHA, /* U+0016bd: RUNIC LETTER SHORT-TWIG-HAGALL H*/
+ RTUNI_ALPHA, /* U+0016be: RUNIC LETTER NAUDIZ NYD NAUD N*/
+ RTUNI_ALPHA, /* U+0016bf: RUNIC LETTER SHORT-TWIG-NAUD N*/
+ RTUNI_ALPHA, /* U+0016c0: RUNIC LETTER DOTTED-N*/
+ RTUNI_ALPHA, /* U+0016c1: RUNIC LETTER ISAZ IS ISS I*/
+ RTUNI_ALPHA, /* U+0016c2: RUNIC LETTER E*/
+ RTUNI_ALPHA, /* U+0016c3: RUNIC LETTER JERAN J*/
+ RTUNI_ALPHA, /* U+0016c4: RUNIC LETTER GER*/
+ RTUNI_ALPHA, /* U+0016c5: RUNIC LETTER LONG-BRANCH-AR AE*/
+ RTUNI_ALPHA, /* U+0016c6: RUNIC LETTER SHORT-TWIG-AR A*/
+ RTUNI_ALPHA, /* U+0016c7: RUNIC LETTER IWAZ EOH*/
+ RTUNI_ALPHA, /* U+0016c8: RUNIC LETTER PERTHO PEORTH P*/
+ RTUNI_ALPHA, /* U+0016c9: RUNIC LETTER ALGIZ EOLHX*/
+ RTUNI_ALPHA, /* U+0016ca: RUNIC LETTER SOWILO S*/
+ RTUNI_ALPHA, /* U+0016cb: RUNIC LETTER SIGEL LONG-BRANCH-SOL S*/
+ RTUNI_ALPHA, /* U+0016cc: RUNIC LETTER SHORT-TWIG-SOL S*/
+ RTUNI_ALPHA, /* U+0016cd: RUNIC LETTER C*/
+ RTUNI_ALPHA, /* U+0016ce: RUNIC LETTER Z*/
+ RTUNI_ALPHA, /* U+0016cf: RUNIC LETTER TIWAZ TIR TYR T*/
+ RTUNI_ALPHA, /* U+0016d0: RUNIC LETTER SHORT-TWIG-TYR T*/
+ RTUNI_ALPHA, /* U+0016d1: RUNIC LETTER D*/
+ RTUNI_ALPHA, /* U+0016d2: RUNIC LETTER BERKANAN BEORC BJARKAN B*/
+ RTUNI_ALPHA, /* U+0016d3: RUNIC LETTER SHORT-TWIG-BJARKAN B*/
+ RTUNI_ALPHA, /* U+0016d4: RUNIC LETTER DOTTED-P*/
+ RTUNI_ALPHA, /* U+0016d5: RUNIC LETTER OPEN-P*/
+ RTUNI_ALPHA, /* U+0016d6: RUNIC LETTER EHWAZ EH E*/
+ RTUNI_ALPHA, /* U+0016d7: RUNIC LETTER MANNAZ MAN M*/
+ RTUNI_ALPHA, /* U+0016d8: RUNIC LETTER LONG-BRANCH-MADR M*/
+ RTUNI_ALPHA, /* U+0016d9: RUNIC LETTER SHORT-TWIG-MADR M*/
+ RTUNI_ALPHA, /* U+0016da: RUNIC LETTER LAUKAZ LAGU LOGR L*/
+ RTUNI_ALPHA, /* U+0016db: RUNIC LETTER DOTTED-L*/
+ RTUNI_ALPHA, /* U+0016dc: RUNIC LETTER INGWAZ*/
+ RTUNI_ALPHA, /* U+0016dd: RUNIC LETTER ING*/
+ RTUNI_ALPHA, /* U+0016de: RUNIC LETTER DAGAZ DAEG D*/
+ RTUNI_ALPHA, /* U+0016df: RUNIC LETTER OTHALAN ETHEL O*/
+ RTUNI_ALPHA, /* U+0016e0: RUNIC LETTER EAR*/
+ RTUNI_ALPHA, /* U+0016e1: RUNIC LETTER IOR*/
+ RTUNI_ALPHA, /* U+0016e2: RUNIC LETTER CWEORTH*/
+ RTUNI_ALPHA, /* U+0016e3: RUNIC LETTER CALC*/
+ RTUNI_ALPHA, /* U+0016e4: RUNIC LETTER CEALC*/
+ RTUNI_ALPHA, /* U+0016e5: RUNIC LETTER STAN*/
+ RTUNI_ALPHA, /* U+0016e6: RUNIC LETTER LONG-BRANCH-YR*/
+ RTUNI_ALPHA, /* U+0016e7: RUNIC LETTER SHORT-TWIG-YR*/
+ RTUNI_ALPHA, /* U+0016e8: RUNIC LETTER ICELANDIC-YR*/
+ RTUNI_ALPHA, /* U+0016e9: RUNIC LETTER Q*/
+ RTUNI_ALPHA, /* U+0016ea: RUNIC LETTER X*/
+ 0, /* U+0016eb: RUNIC SINGLE PUNCTUATION*/
+ 0, /* U+0016ec: RUNIC MULTIPLE PUNCTUATION*/
+ 0, /* U+0016ed: RUNIC CROSS PUNCTUATION*/
+ RTUNI_ALPHA, /* U+0016ee: RUNIC ARLAUG SYMBOL*/
+ RTUNI_ALPHA, /* U+0016ef: RUNIC TVIMADUR SYMBOL*/
+ RTUNI_ALPHA, /* U+0016f0: RUNIC BELGTHOR SYMBOL*/
+ 0, /* U+0016f1: */
+ 0, /* U+0016f2: */
+ 0, /* U+0016f3: */
+ 0, /* U+0016f4: */
+ 0, /* U+0016f5: */
+ 0, /* U+0016f6: */
+ 0, /* U+0016f7: */
+ 0, /* U+0016f8: */
+ 0, /* U+0016f9: */
+ 0, /* U+0016fa: */
+ 0, /* U+0016fb: */
+ 0, /* U+0016fc: */
+ 0, /* U+0016fd: */
+ 0, /* U+0016fe: */
+ 0, /* U+0016ff: */
+ RTUNI_ALPHA, /* U+001700: TAGALOG LETTER A*/
+ RTUNI_ALPHA, /* U+001701: TAGALOG LETTER I*/
+ RTUNI_ALPHA, /* U+001702: TAGALOG LETTER U*/
+ RTUNI_ALPHA, /* U+001703: TAGALOG LETTER KA*/
+ RTUNI_ALPHA, /* U+001704: TAGALOG LETTER GA*/
+ RTUNI_ALPHA, /* U+001705: TAGALOG LETTER NGA*/
+ RTUNI_ALPHA, /* U+001706: TAGALOG LETTER TA*/
+ RTUNI_ALPHA, /* U+001707: TAGALOG LETTER DA*/
+ RTUNI_ALPHA, /* U+001708: TAGALOG LETTER NA*/
+ RTUNI_ALPHA, /* U+001709: TAGALOG LETTER PA*/
+ RTUNI_ALPHA, /* U+00170a: TAGALOG LETTER BA*/
+ RTUNI_ALPHA, /* U+00170b: TAGALOG LETTER MA*/
+ RTUNI_ALPHA, /* U+00170c: TAGALOG LETTER YA*/
+ 0, /* U+00170d: */
+ RTUNI_ALPHA, /* U+00170e: TAGALOG LETTER LA*/
+ RTUNI_ALPHA, /* U+00170f: TAGALOG LETTER WA*/
+ RTUNI_ALPHA, /* U+001710: TAGALOG LETTER SA*/
+ RTUNI_ALPHA, /* U+001711: TAGALOG LETTER HA*/
+ RTUNI_ALPHA, /* U+001712: TAGALOG VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+001713: TAGALOG VOWEL SIGN U*/
+ 0, /* U+001714: TAGALOG SIGN VIRAMA*/
+ 0, /* U+001715: */
+ 0, /* U+001716: */
+ 0, /* U+001717: */
+ 0, /* U+001718: */
+ 0, /* U+001719: */
+ 0, /* U+00171a: */
+ 0, /* U+00171b: */
+ 0, /* U+00171c: */
+ 0, /* U+00171d: */
+ 0, /* U+00171e: */
+ 0, /* U+00171f: */
+ RTUNI_ALPHA, /* U+001720: HANUNOO LETTER A*/
+ RTUNI_ALPHA, /* U+001721: HANUNOO LETTER I*/
+ RTUNI_ALPHA, /* U+001722: HANUNOO LETTER U*/
+ RTUNI_ALPHA, /* U+001723: HANUNOO LETTER KA*/
+ RTUNI_ALPHA, /* U+001724: HANUNOO LETTER GA*/
+ RTUNI_ALPHA, /* U+001725: HANUNOO LETTER NGA*/
+ RTUNI_ALPHA, /* U+001726: HANUNOO LETTER TA*/
+ RTUNI_ALPHA, /* U+001727: HANUNOO LETTER DA*/
+ RTUNI_ALPHA, /* U+001728: HANUNOO LETTER NA*/
+ RTUNI_ALPHA, /* U+001729: HANUNOO LETTER PA*/
+ RTUNI_ALPHA, /* U+00172a: HANUNOO LETTER BA*/
+ RTUNI_ALPHA, /* U+00172b: HANUNOO LETTER MA*/
+ RTUNI_ALPHA, /* U+00172c: HANUNOO LETTER YA*/
+ RTUNI_ALPHA, /* U+00172d: HANUNOO LETTER RA*/
+ RTUNI_ALPHA, /* U+00172e: HANUNOO LETTER LA*/
+ RTUNI_ALPHA, /* U+00172f: HANUNOO LETTER WA*/
+ RTUNI_ALPHA, /* U+001730: HANUNOO LETTER SA*/
+ RTUNI_ALPHA, /* U+001731: HANUNOO LETTER HA*/
+ RTUNI_ALPHA, /* U+001732: HANUNOO VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+001733: HANUNOO VOWEL SIGN U*/
+ 0, /* U+001734: HANUNOO SIGN PAMUDPOD*/
+ 0, /* U+001735: PHILIPPINE SINGLE PUNCTUATION*/
+ 0, /* U+001736: PHILIPPINE DOUBLE PUNCTUATION*/
+ 0, /* U+001737: */
+ 0, /* U+001738: */
+ 0, /* U+001739: */
+ 0, /* U+00173a: */
+ 0, /* U+00173b: */
+ 0, /* U+00173c: */
+ 0, /* U+00173d: */
+ 0, /* U+00173e: */
+ 0, /* U+00173f: */
+ RTUNI_ALPHA, /* U+001740: BUHID LETTER A*/
+ RTUNI_ALPHA, /* U+001741: BUHID LETTER I*/
+ RTUNI_ALPHA, /* U+001742: BUHID LETTER U*/
+ RTUNI_ALPHA, /* U+001743: BUHID LETTER KA*/
+ RTUNI_ALPHA, /* U+001744: BUHID LETTER GA*/
+ RTUNI_ALPHA, /* U+001745: BUHID LETTER NGA*/
+ RTUNI_ALPHA, /* U+001746: BUHID LETTER TA*/
+ RTUNI_ALPHA, /* U+001747: BUHID LETTER DA*/
+ RTUNI_ALPHA, /* U+001748: BUHID LETTER NA*/
+ RTUNI_ALPHA, /* U+001749: BUHID LETTER PA*/
+ RTUNI_ALPHA, /* U+00174a: BUHID LETTER BA*/
+ RTUNI_ALPHA, /* U+00174b: BUHID LETTER MA*/
+ RTUNI_ALPHA, /* U+00174c: BUHID LETTER YA*/
+ RTUNI_ALPHA, /* U+00174d: BUHID LETTER RA*/
+ RTUNI_ALPHA, /* U+00174e: BUHID LETTER LA*/
+ RTUNI_ALPHA, /* U+00174f: BUHID LETTER WA*/
+ RTUNI_ALPHA, /* U+001750: BUHID LETTER SA*/
+ RTUNI_ALPHA, /* U+001751: BUHID LETTER HA*/
+ RTUNI_ALPHA, /* U+001752: BUHID VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+001753: BUHID VOWEL SIGN U*/
+ 0, /* U+001754: */
+ 0, /* U+001755: */
+ 0, /* U+001756: */
+ 0, /* U+001757: */
+ 0, /* U+001758: */
+ 0, /* U+001759: */
+ 0, /* U+00175a: */
+ 0, /* U+00175b: */
+ 0, /* U+00175c: */
+ 0, /* U+00175d: */
+ 0, /* U+00175e: */
+ 0, /* U+00175f: */
+ RTUNI_ALPHA, /* U+001760: TAGBANWA LETTER A*/
+ RTUNI_ALPHA, /* U+001761: TAGBANWA LETTER I*/
+ RTUNI_ALPHA, /* U+001762: TAGBANWA LETTER U*/
+ RTUNI_ALPHA, /* U+001763: TAGBANWA LETTER KA*/
+ RTUNI_ALPHA, /* U+001764: TAGBANWA LETTER GA*/
+ RTUNI_ALPHA, /* U+001765: TAGBANWA LETTER NGA*/
+ RTUNI_ALPHA, /* U+001766: TAGBANWA LETTER TA*/
+ RTUNI_ALPHA, /* U+001767: TAGBANWA LETTER DA*/
+ RTUNI_ALPHA, /* U+001768: TAGBANWA LETTER NA*/
+ RTUNI_ALPHA, /* U+001769: TAGBANWA LETTER PA*/
+ RTUNI_ALPHA, /* U+00176a: TAGBANWA LETTER BA*/
+ RTUNI_ALPHA, /* U+00176b: TAGBANWA LETTER MA*/
+ RTUNI_ALPHA, /* U+00176c: TAGBANWA LETTER YA*/
+ 0, /* U+00176d: */
+ RTUNI_ALPHA, /* U+00176e: TAGBANWA LETTER LA*/
+ RTUNI_ALPHA, /* U+00176f: TAGBANWA LETTER WA*/
+ RTUNI_ALPHA, /* U+001770: TAGBANWA LETTER SA*/
+ 0, /* U+001771: */
+ RTUNI_ALPHA, /* U+001772: TAGBANWA VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+001773: TAGBANWA VOWEL SIGN U*/
+ 0, /* U+001774: */
+ 0, /* U+001775: */
+ 0, /* U+001776: */
+ 0, /* U+001777: */
+ 0, /* U+001778: */
+ 0, /* U+001779: */
+ 0, /* U+00177a: */
+ 0, /* U+00177b: */
+ 0, /* U+00177c: */
+ 0, /* U+00177d: */
+ 0, /* U+00177e: */
+ 0, /* U+00177f: */
+ RTUNI_ALPHA, /* U+001780: KHMER LETTER KA*/
+ RTUNI_ALPHA, /* U+001781: KHMER LETTER KHA*/
+ RTUNI_ALPHA, /* U+001782: KHMER LETTER KO*/
+ RTUNI_ALPHA, /* U+001783: KHMER LETTER KHO*/
+ RTUNI_ALPHA, /* U+001784: KHMER LETTER NGO*/
+ RTUNI_ALPHA, /* U+001785: KHMER LETTER CA*/
+ RTUNI_ALPHA, /* U+001786: KHMER LETTER CHA*/
+ RTUNI_ALPHA, /* U+001787: KHMER LETTER CO*/
+ RTUNI_ALPHA, /* U+001788: KHMER LETTER CHO*/
+ RTUNI_ALPHA, /* U+001789: KHMER LETTER NYO*/
+ RTUNI_ALPHA, /* U+00178a: KHMER LETTER DA*/
+ RTUNI_ALPHA, /* U+00178b: KHMER LETTER TTHA*/
+ RTUNI_ALPHA, /* U+00178c: KHMER LETTER DO*/
+ RTUNI_ALPHA, /* U+00178d: KHMER LETTER TTHO*/
+ RTUNI_ALPHA, /* U+00178e: KHMER LETTER NNO*/
+ RTUNI_ALPHA, /* U+00178f: KHMER LETTER TA*/
+ RTUNI_ALPHA, /* U+001790: KHMER LETTER THA*/
+ RTUNI_ALPHA, /* U+001791: KHMER LETTER TO*/
+ RTUNI_ALPHA, /* U+001792: KHMER LETTER THO*/
+ RTUNI_ALPHA, /* U+001793: KHMER LETTER NO*/
+ RTUNI_ALPHA, /* U+001794: KHMER LETTER BA*/
+ RTUNI_ALPHA, /* U+001795: KHMER LETTER PHA*/
+ RTUNI_ALPHA, /* U+001796: KHMER LETTER PO*/
+ RTUNI_ALPHA, /* U+001797: KHMER LETTER PHO*/
+ RTUNI_ALPHA, /* U+001798: KHMER LETTER MO*/
+ RTUNI_ALPHA, /* U+001799: KHMER LETTER YO*/
+ RTUNI_ALPHA, /* U+00179a: KHMER LETTER RO*/
+ RTUNI_ALPHA, /* U+00179b: KHMER LETTER LO*/
+ RTUNI_ALPHA, /* U+00179c: KHMER LETTER VO*/
+ RTUNI_ALPHA, /* U+00179d: KHMER LETTER SHA*/
+ RTUNI_ALPHA, /* U+00179e: KHMER LETTER SSO*/
+ RTUNI_ALPHA, /* U+00179f: KHMER LETTER SA*/
+ RTUNI_ALPHA, /* U+0017a0: KHMER LETTER HA*/
+ RTUNI_ALPHA, /* U+0017a1: KHMER LETTER LA*/
+ RTUNI_ALPHA, /* U+0017a2: KHMER LETTER QA*/
+ RTUNI_ALPHA, /* U+0017a3: KHMER INDEPENDENT VOWEL QAQ*/
+ RTUNI_ALPHA, /* U+0017a4: KHMER INDEPENDENT VOWEL QAA*/
+ RTUNI_ALPHA, /* U+0017a5: KHMER INDEPENDENT VOWEL QI*/
+ RTUNI_ALPHA, /* U+0017a6: KHMER INDEPENDENT VOWEL QII*/
+ RTUNI_ALPHA, /* U+0017a7: KHMER INDEPENDENT VOWEL QU*/
+ RTUNI_ALPHA, /* U+0017a8: KHMER INDEPENDENT VOWEL QUK*/
+ RTUNI_ALPHA, /* U+0017a9: KHMER INDEPENDENT VOWEL QUU*/
+ RTUNI_ALPHA, /* U+0017aa: KHMER INDEPENDENT VOWEL QUUV*/
+ RTUNI_ALPHA, /* U+0017ab: KHMER INDEPENDENT VOWEL RY*/
+ RTUNI_ALPHA, /* U+0017ac: KHMER INDEPENDENT VOWEL RYY*/
+ RTUNI_ALPHA, /* U+0017ad: KHMER INDEPENDENT VOWEL LY*/
+ RTUNI_ALPHA, /* U+0017ae: KHMER INDEPENDENT VOWEL LYY*/
+ RTUNI_ALPHA, /* U+0017af: KHMER INDEPENDENT VOWEL QE*/
+ RTUNI_ALPHA, /* U+0017b0: KHMER INDEPENDENT VOWEL QAI*/
+ RTUNI_ALPHA, /* U+0017b1: KHMER INDEPENDENT VOWEL QOO TYPE ONE*/
+ RTUNI_ALPHA, /* U+0017b2: KHMER INDEPENDENT VOWEL QOO TYPE TWO*/
+ RTUNI_ALPHA, /* U+0017b3: KHMER INDEPENDENT VOWEL QAU*/
+ 0, /* U+0017b4: KHMER VOWEL INHERENT AQ*/
+ 0, /* U+0017b5: KHMER VOWEL INHERENT AA*/
+ RTUNI_ALPHA, /* U+0017b6: KHMER VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+0017b7: KHMER VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+0017b8: KHMER VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+0017b9: KHMER VOWEL SIGN Y*/
+ RTUNI_ALPHA, /* U+0017ba: KHMER VOWEL SIGN YY*/
+ RTUNI_ALPHA, /* U+0017bb: KHMER VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+0017bc: KHMER VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+0017bd: KHMER VOWEL SIGN UA*/
+ RTUNI_ALPHA, /* U+0017be: KHMER VOWEL SIGN OE*/
+ RTUNI_ALPHA, /* U+0017bf: KHMER VOWEL SIGN YA*/
+ RTUNI_ALPHA, /* U+0017c0: KHMER VOWEL SIGN IE*/
+ RTUNI_ALPHA, /* U+0017c1: KHMER VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+0017c2: KHMER VOWEL SIGN AE*/
+ RTUNI_ALPHA, /* U+0017c3: KHMER VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+0017c4: KHMER VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+0017c5: KHMER VOWEL SIGN AU*/
+ RTUNI_ALPHA, /* U+0017c6: KHMER SIGN NIKAHIT*/
+ RTUNI_ALPHA, /* U+0017c7: KHMER SIGN REAHMUK*/
+ RTUNI_ALPHA, /* U+0017c8: KHMER SIGN YUUKALEAPINTU*/
+ 0, /* U+0017c9: KHMER SIGN MUUSIKATOAN*/
+ 0, /* U+0017ca: KHMER SIGN TRIISAP*/
+ 0, /* U+0017cb: KHMER SIGN BANTOC*/
+ 0, /* U+0017cc: KHMER SIGN ROBAT*/
+ 0, /* U+0017cd: KHMER SIGN TOANDAKHIAT*/
+ 0, /* U+0017ce: KHMER SIGN KAKABAT*/
+ 0, /* U+0017cf: KHMER SIGN AHSDA*/
+ 0, /* U+0017d0: KHMER SIGN SAMYOK SANNYA*/
+ 0, /* U+0017d1: KHMER SIGN VIRIAM*/
+ 0, /* U+0017d2: KHMER SIGN COENG*/
+ 0, /* U+0017d3: KHMER SIGN BATHAMASAT*/
+ 0, /* U+0017d4: KHMER SIGN KHAN*/
+ 0, /* U+0017d5: KHMER SIGN BARIYOOSAN*/
+ 0, /* U+0017d6: KHMER SIGN CAMNUC PII KUUH*/
+ RTUNI_ALPHA, /* U+0017d7: KHMER SIGN LEK TOO*/
+ 0, /* U+0017d8: KHMER SIGN BEYYAL*/
+ 0, /* U+0017d9: KHMER SIGN PHNAEK MUAN*/
+ 0, /* U+0017da: KHMER SIGN KOOMUUT*/
+ 0, /* U+0017db: KHMER CURRENCY SYMBOL RIEL*/
+ RTUNI_ALPHA, /* U+0017dc: KHMER SIGN AVAKRAHASANYA*/
+ 0, /* U+0017dd: KHMER SIGN ATTHACAN*/
+ 0, /* U+0017de: */
+ 0, /* U+0017df: */
+ RTUNI_DDIGIT, /* U+0017e0: KHMER DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+0017e1: KHMER DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+0017e2: KHMER DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+0017e3: KHMER DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+0017e4: KHMER DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+0017e5: KHMER DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+0017e6: KHMER DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+0017e7: KHMER DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+0017e8: KHMER DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+0017e9: KHMER DIGIT NINE*/
+ 0, /* U+0017ea: */
+ 0, /* U+0017eb: */
+ 0, /* U+0017ec: */
+ 0, /* U+0017ed: */
+ 0, /* U+0017ee: */
+ 0, /* U+0017ef: */
+ 0, /* U+0017f0: KHMER SYMBOL LEK ATTAK SON*/
+ 0, /* U+0017f1: KHMER SYMBOL LEK ATTAK MUOY*/
+ 0, /* U+0017f2: KHMER SYMBOL LEK ATTAK PII*/
+ 0, /* U+0017f3: KHMER SYMBOL LEK ATTAK BEI*/
+ 0, /* U+0017f4: KHMER SYMBOL LEK ATTAK BUON*/
+ 0, /* U+0017f5: KHMER SYMBOL LEK ATTAK PRAM*/
+ 0, /* U+0017f6: KHMER SYMBOL LEK ATTAK PRAM-MUOY*/
+ 0, /* U+0017f7: KHMER SYMBOL LEK ATTAK PRAM-PII*/
+ 0, /* U+0017f8: KHMER SYMBOL LEK ATTAK PRAM-BEI*/
+ 0, /* U+0017f9: KHMER SYMBOL LEK ATTAK PRAM-BUON*/
+ 0, /* U+0017fa: */
+ 0, /* U+0017fb: */
+ 0, /* U+0017fc: */
+ 0, /* U+0017fd: */
+ 0, /* U+0017fe: */
+ 0, /* U+0017ff: */
+ 0, /* U+001800: MONGOLIAN BIRGA*/
+ 0, /* U+001801: MONGOLIAN ELLIPSIS*/
+ 0, /* U+001802: MONGOLIAN COMMA*/
+ 0, /* U+001803: MONGOLIAN FULL STOP*/
+ 0, /* U+001804: MONGOLIAN COLON*/
+ 0, /* U+001805: MONGOLIAN FOUR DOTS*/
+ 0, /* U+001806: MONGOLIAN TODO SOFT HYPHEN*/
+ 0, /* U+001807: MONGOLIAN SIBE SYLLABLE BOUNDARY MARKER*/
+ 0, /* U+001808: MONGOLIAN MANCHU COMMA*/
+ 0, /* U+001809: MONGOLIAN MANCHU FULL STOP*/
+ 0, /* U+00180a: MONGOLIAN NIRUGU*/
+ 0, /* U+00180b: MONGOLIAN FREE VARIATION SELECTOR ONE*/
+ 0, /* U+00180c: MONGOLIAN FREE VARIATION SELECTOR TWO*/
+ 0, /* U+00180d: MONGOLIAN FREE VARIATION SELECTOR THREE*/
+ 0, /* U+00180e: MONGOLIAN VOWEL SEPARATOR*/
+ 0, /* U+00180f: */
+ RTUNI_DDIGIT, /* U+001810: MONGOLIAN DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001811: MONGOLIAN DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001812: MONGOLIAN DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001813: MONGOLIAN DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+001814: MONGOLIAN DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+001815: MONGOLIAN DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+001816: MONGOLIAN DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+001817: MONGOLIAN DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+001818: MONGOLIAN DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+001819: MONGOLIAN DIGIT NINE*/
+ 0, /* U+00181a: */
+ 0, /* U+00181b: */
+ 0, /* U+00181c: */
+ 0, /* U+00181d: */
+ 0, /* U+00181e: */
+ 0, /* U+00181f: */
+ RTUNI_ALPHA, /* U+001820: MONGOLIAN LETTER A*/
+ RTUNI_ALPHA, /* U+001821: MONGOLIAN LETTER E*/
+ RTUNI_ALPHA, /* U+001822: MONGOLIAN LETTER I*/
+ RTUNI_ALPHA, /* U+001823: MONGOLIAN LETTER O*/
+ RTUNI_ALPHA, /* U+001824: MONGOLIAN LETTER U*/
+ RTUNI_ALPHA, /* U+001825: MONGOLIAN LETTER OE*/
+ RTUNI_ALPHA, /* U+001826: MONGOLIAN LETTER UE*/
+ RTUNI_ALPHA, /* U+001827: MONGOLIAN LETTER EE*/
+ RTUNI_ALPHA, /* U+001828: MONGOLIAN LETTER NA*/
+ RTUNI_ALPHA, /* U+001829: MONGOLIAN LETTER ANG*/
+ RTUNI_ALPHA, /* U+00182a: MONGOLIAN LETTER BA*/
+ RTUNI_ALPHA, /* U+00182b: MONGOLIAN LETTER PA*/
+ RTUNI_ALPHA, /* U+00182c: MONGOLIAN LETTER QA*/
+ RTUNI_ALPHA, /* U+00182d: MONGOLIAN LETTER GA*/
+ RTUNI_ALPHA, /* U+00182e: MONGOLIAN LETTER MA*/
+ RTUNI_ALPHA, /* U+00182f: MONGOLIAN LETTER LA*/
+ RTUNI_ALPHA, /* U+001830: MONGOLIAN LETTER SA*/
+ RTUNI_ALPHA, /* U+001831: MONGOLIAN LETTER SHA*/
+ RTUNI_ALPHA, /* U+001832: MONGOLIAN LETTER TA*/
+ RTUNI_ALPHA, /* U+001833: MONGOLIAN LETTER DA*/
+ RTUNI_ALPHA, /* U+001834: MONGOLIAN LETTER CHA*/
+ RTUNI_ALPHA, /* U+001835: MONGOLIAN LETTER JA*/
+ RTUNI_ALPHA, /* U+001836: MONGOLIAN LETTER YA*/
+ RTUNI_ALPHA, /* U+001837: MONGOLIAN LETTER RA*/
+ RTUNI_ALPHA, /* U+001838: MONGOLIAN LETTER WA*/
+ RTUNI_ALPHA, /* U+001839: MONGOLIAN LETTER FA*/
+ RTUNI_ALPHA, /* U+00183a: MONGOLIAN LETTER KA*/
+ RTUNI_ALPHA, /* U+00183b: MONGOLIAN LETTER KHA*/
+ RTUNI_ALPHA, /* U+00183c: MONGOLIAN LETTER TSA*/
+ RTUNI_ALPHA, /* U+00183d: MONGOLIAN LETTER ZA*/
+ RTUNI_ALPHA, /* U+00183e: MONGOLIAN LETTER HAA*/
+ RTUNI_ALPHA, /* U+00183f: MONGOLIAN LETTER ZRA*/
+ RTUNI_ALPHA, /* U+001840: MONGOLIAN LETTER LHA*/
+ RTUNI_ALPHA, /* U+001841: MONGOLIAN LETTER ZHI*/
+ RTUNI_ALPHA, /* U+001842: MONGOLIAN LETTER CHI*/
+ RTUNI_ALPHA, /* U+001843: MONGOLIAN LETTER TODO LONG VOWEL SIGN*/
+ RTUNI_ALPHA, /* U+001844: MONGOLIAN LETTER TODO E*/
+ RTUNI_ALPHA, /* U+001845: MONGOLIAN LETTER TODO I*/
+ RTUNI_ALPHA, /* U+001846: MONGOLIAN LETTER TODO O*/
+ RTUNI_ALPHA, /* U+001847: MONGOLIAN LETTER TODO U*/
+ RTUNI_ALPHA, /* U+001848: MONGOLIAN LETTER TODO OE*/
+ RTUNI_ALPHA, /* U+001849: MONGOLIAN LETTER TODO UE*/
+ RTUNI_ALPHA, /* U+00184a: MONGOLIAN LETTER TODO ANG*/
+ RTUNI_ALPHA, /* U+00184b: MONGOLIAN LETTER TODO BA*/
+ RTUNI_ALPHA, /* U+00184c: MONGOLIAN LETTER TODO PA*/
+ RTUNI_ALPHA, /* U+00184d: MONGOLIAN LETTER TODO QA*/
+ RTUNI_ALPHA, /* U+00184e: MONGOLIAN LETTER TODO GA*/
+ RTUNI_ALPHA, /* U+00184f: MONGOLIAN LETTER TODO MA*/
+ RTUNI_ALPHA, /* U+001850: MONGOLIAN LETTER TODO TA*/
+ RTUNI_ALPHA, /* U+001851: MONGOLIAN LETTER TODO DA*/
+ RTUNI_ALPHA, /* U+001852: MONGOLIAN LETTER TODO CHA*/
+ RTUNI_ALPHA, /* U+001853: MONGOLIAN LETTER TODO JA*/
+ RTUNI_ALPHA, /* U+001854: MONGOLIAN LETTER TODO TSA*/
+ RTUNI_ALPHA, /* U+001855: MONGOLIAN LETTER TODO YA*/
+ RTUNI_ALPHA, /* U+001856: MONGOLIAN LETTER TODO WA*/
+ RTUNI_ALPHA, /* U+001857: MONGOLIAN LETTER TODO KA*/
+ RTUNI_ALPHA, /* U+001858: MONGOLIAN LETTER TODO GAA*/
+ RTUNI_ALPHA, /* U+001859: MONGOLIAN LETTER TODO HAA*/
+ RTUNI_ALPHA, /* U+00185a: MONGOLIAN LETTER TODO JIA*/
+ RTUNI_ALPHA, /* U+00185b: MONGOLIAN LETTER TODO NIA*/
+ RTUNI_ALPHA, /* U+00185c: MONGOLIAN LETTER TODO DZA*/
+ RTUNI_ALPHA, /* U+00185d: MONGOLIAN LETTER SIBE E*/
+ RTUNI_ALPHA, /* U+00185e: MONGOLIAN LETTER SIBE I*/
+ RTUNI_ALPHA, /* U+00185f: MONGOLIAN LETTER SIBE IY*/
+ RTUNI_ALPHA, /* U+001860: MONGOLIAN LETTER SIBE UE*/
+ RTUNI_ALPHA, /* U+001861: MONGOLIAN LETTER SIBE U*/
+ RTUNI_ALPHA, /* U+001862: MONGOLIAN LETTER SIBE ANG*/
+ RTUNI_ALPHA, /* U+001863: MONGOLIAN LETTER SIBE KA*/
+ RTUNI_ALPHA, /* U+001864: MONGOLIAN LETTER SIBE GA*/
+ RTUNI_ALPHA, /* U+001865: MONGOLIAN LETTER SIBE HA*/
+ RTUNI_ALPHA, /* U+001866: MONGOLIAN LETTER SIBE PA*/
+ RTUNI_ALPHA, /* U+001867: MONGOLIAN LETTER SIBE SHA*/
+ RTUNI_ALPHA, /* U+001868: MONGOLIAN LETTER SIBE TA*/
+ RTUNI_ALPHA, /* U+001869: MONGOLIAN LETTER SIBE DA*/
+ RTUNI_ALPHA, /* U+00186a: MONGOLIAN LETTER SIBE JA*/
+ RTUNI_ALPHA, /* U+00186b: MONGOLIAN LETTER SIBE FA*/
+ RTUNI_ALPHA, /* U+00186c: MONGOLIAN LETTER SIBE GAA*/
+ RTUNI_ALPHA, /* U+00186d: MONGOLIAN LETTER SIBE HAA*/
+ RTUNI_ALPHA, /* U+00186e: MONGOLIAN LETTER SIBE TSA*/
+ RTUNI_ALPHA, /* U+00186f: MONGOLIAN LETTER SIBE ZA*/
+ RTUNI_ALPHA, /* U+001870: MONGOLIAN LETTER SIBE RAA*/
+ RTUNI_ALPHA, /* U+001871: MONGOLIAN LETTER SIBE CHA*/
+ RTUNI_ALPHA, /* U+001872: MONGOLIAN LETTER SIBE ZHA*/
+ RTUNI_ALPHA, /* U+001873: MONGOLIAN LETTER MANCHU I*/
+ RTUNI_ALPHA, /* U+001874: MONGOLIAN LETTER MANCHU KA*/
+ RTUNI_ALPHA, /* U+001875: MONGOLIAN LETTER MANCHU RA*/
+ RTUNI_ALPHA, /* U+001876: MONGOLIAN LETTER MANCHU FA*/
+ RTUNI_ALPHA, /* U+001877: MONGOLIAN LETTER MANCHU ZHA*/
+ 0, /* U+001878: */
+ 0, /* U+001879: */
+ 0, /* U+00187a: */
+ 0, /* U+00187b: */
+ 0, /* U+00187c: */
+ 0, /* U+00187d: */
+ 0, /* U+00187e: */
+ 0, /* U+00187f: */
+ RTUNI_ALPHA, /* U+001880: MONGOLIAN LETTER ALI GALI ANUSVARA ONE*/
+ RTUNI_ALPHA, /* U+001881: MONGOLIAN LETTER ALI GALI VISARGA ONE*/
+ RTUNI_ALPHA, /* U+001882: MONGOLIAN LETTER ALI GALI DAMARU*/
+ RTUNI_ALPHA, /* U+001883: MONGOLIAN LETTER ALI GALI UBADAMA*/
+ RTUNI_ALPHA, /* U+001884: MONGOLIAN LETTER ALI GALI INVERTED UBADAMA*/
+ RTUNI_ALPHA, /* U+001885: MONGOLIAN LETTER ALI GALI BALUDA*/
+ RTUNI_ALPHA, /* U+001886: MONGOLIAN LETTER ALI GALI THREE BALUDA*/
+ RTUNI_ALPHA, /* U+001887: MONGOLIAN LETTER ALI GALI A*/
+ RTUNI_ALPHA, /* U+001888: MONGOLIAN LETTER ALI GALI I*/
+ RTUNI_ALPHA, /* U+001889: MONGOLIAN LETTER ALI GALI KA*/
+ RTUNI_ALPHA, /* U+00188a: MONGOLIAN LETTER ALI GALI NGA*/
+ RTUNI_ALPHA, /* U+00188b: MONGOLIAN LETTER ALI GALI CA*/
+ RTUNI_ALPHA, /* U+00188c: MONGOLIAN LETTER ALI GALI TTA*/
+ RTUNI_ALPHA, /* U+00188d: MONGOLIAN LETTER ALI GALI TTHA*/
+ RTUNI_ALPHA, /* U+00188e: MONGOLIAN LETTER ALI GALI DDA*/
+ RTUNI_ALPHA, /* U+00188f: MONGOLIAN LETTER ALI GALI NNA*/
+ RTUNI_ALPHA, /* U+001890: MONGOLIAN LETTER ALI GALI TA*/
+ RTUNI_ALPHA, /* U+001891: MONGOLIAN LETTER ALI GALI DA*/
+ RTUNI_ALPHA, /* U+001892: MONGOLIAN LETTER ALI GALI PA*/
+ RTUNI_ALPHA, /* U+001893: MONGOLIAN LETTER ALI GALI PHA*/
+ RTUNI_ALPHA, /* U+001894: MONGOLIAN LETTER ALI GALI SSA*/
+ RTUNI_ALPHA, /* U+001895: MONGOLIAN LETTER ALI GALI ZHA*/
+ RTUNI_ALPHA, /* U+001896: MONGOLIAN LETTER ALI GALI ZA*/
+ RTUNI_ALPHA, /* U+001897: MONGOLIAN LETTER ALI GALI AH*/
+ RTUNI_ALPHA, /* U+001898: MONGOLIAN LETTER TODO ALI GALI TA*/
+ RTUNI_ALPHA, /* U+001899: MONGOLIAN LETTER TODO ALI GALI ZHA*/
+ RTUNI_ALPHA, /* U+00189a: MONGOLIAN LETTER MANCHU ALI GALI GHA*/
+ RTUNI_ALPHA, /* U+00189b: MONGOLIAN LETTER MANCHU ALI GALI NGA*/
+ RTUNI_ALPHA, /* U+00189c: MONGOLIAN LETTER MANCHU ALI GALI CA*/
+ RTUNI_ALPHA, /* U+00189d: MONGOLIAN LETTER MANCHU ALI GALI JHA*/
+ RTUNI_ALPHA, /* U+00189e: MONGOLIAN LETTER MANCHU ALI GALI TTA*/
+ RTUNI_ALPHA, /* U+00189f: MONGOLIAN LETTER MANCHU ALI GALI DDHA*/
+ RTUNI_ALPHA, /* U+0018a0: MONGOLIAN LETTER MANCHU ALI GALI TA*/
+ RTUNI_ALPHA, /* U+0018a1: MONGOLIAN LETTER MANCHU ALI GALI DHA*/
+ RTUNI_ALPHA, /* U+0018a2: MONGOLIAN LETTER MANCHU ALI GALI SSA*/
+ RTUNI_ALPHA, /* U+0018a3: MONGOLIAN LETTER MANCHU ALI GALI CYA*/
+ RTUNI_ALPHA, /* U+0018a4: MONGOLIAN LETTER MANCHU ALI GALI ZHA*/
+ RTUNI_ALPHA, /* U+0018a5: MONGOLIAN LETTER MANCHU ALI GALI ZA*/
+ RTUNI_ALPHA, /* U+0018a6: MONGOLIAN LETTER ALI GALI HALF U*/
+ RTUNI_ALPHA, /* U+0018a7: MONGOLIAN LETTER ALI GALI HALF YA*/
+ RTUNI_ALPHA, /* U+0018a8: MONGOLIAN LETTER MANCHU ALI GALI BHA*/
+ RTUNI_ALPHA, /* U+0018a9: MONGOLIAN LETTER ALI GALI DAGALGA*/
+ RTUNI_ALPHA, /* U+0018aa: MONGOLIAN LETTER MANCHU ALI GALI LHA*/
+ 0, /* U+0018ab: */
+ 0, /* U+0018ac: */
+ 0, /* U+0018ad: */
+ 0, /* U+0018ae: */
+ 0, /* U+0018af: */
+ RTUNI_ALPHA, /* U+0018b0: CANADIAN SYLLABICS OY*/
+ RTUNI_ALPHA, /* U+0018b1: CANADIAN SYLLABICS AY*/
+ RTUNI_ALPHA, /* U+0018b2: CANADIAN SYLLABICS AAY*/
+ RTUNI_ALPHA, /* U+0018b3: CANADIAN SYLLABICS WAY*/
+ RTUNI_ALPHA, /* U+0018b4: CANADIAN SYLLABICS POY*/
+ RTUNI_ALPHA, /* U+0018b5: CANADIAN SYLLABICS PAY*/
+ RTUNI_ALPHA, /* U+0018b6: CANADIAN SYLLABICS PWOY*/
+ RTUNI_ALPHA, /* U+0018b7: CANADIAN SYLLABICS TAY*/
+ RTUNI_ALPHA, /* U+0018b8: CANADIAN SYLLABICS KAY*/
+ RTUNI_ALPHA, /* U+0018b9: CANADIAN SYLLABICS KWAY*/
+ RTUNI_ALPHA, /* U+0018ba: CANADIAN SYLLABICS MAY*/
+ RTUNI_ALPHA, /* U+0018bb: CANADIAN SYLLABICS NOY*/
+ RTUNI_ALPHA, /* U+0018bc: CANADIAN SYLLABICS NAY*/
+ RTUNI_ALPHA, /* U+0018bd: CANADIAN SYLLABICS LAY*/
+ RTUNI_ALPHA, /* U+0018be: CANADIAN SYLLABICS SOY*/
+ RTUNI_ALPHA, /* U+0018bf: CANADIAN SYLLABICS SAY*/
+ RTUNI_ALPHA, /* U+0018c0: CANADIAN SYLLABICS SHOY*/
+ RTUNI_ALPHA, /* U+0018c1: CANADIAN SYLLABICS SHAY*/
+ RTUNI_ALPHA, /* U+0018c2: CANADIAN SYLLABICS SHWOY*/
+ RTUNI_ALPHA, /* U+0018c3: CANADIAN SYLLABICS YOY*/
+ RTUNI_ALPHA, /* U+0018c4: CANADIAN SYLLABICS YAY*/
+ RTUNI_ALPHA, /* U+0018c5: CANADIAN SYLLABICS RAY*/
+ RTUNI_ALPHA, /* U+0018c6: CANADIAN SYLLABICS NWI*/
+ RTUNI_ALPHA, /* U+0018c7: CANADIAN SYLLABICS OJIBWAY NWI*/
+ RTUNI_ALPHA, /* U+0018c8: CANADIAN SYLLABICS NWII*/
+ RTUNI_ALPHA, /* U+0018c9: CANADIAN SYLLABICS OJIBWAY NWII*/
+ RTUNI_ALPHA, /* U+0018ca: CANADIAN SYLLABICS NWO*/
+ RTUNI_ALPHA, /* U+0018cb: CANADIAN SYLLABICS OJIBWAY NWO*/
+ RTUNI_ALPHA, /* U+0018cc: CANADIAN SYLLABICS NWOO*/
+ RTUNI_ALPHA, /* U+0018cd: CANADIAN SYLLABICS OJIBWAY NWOO*/
+ RTUNI_ALPHA, /* U+0018ce: CANADIAN SYLLABICS RWEE*/
+ RTUNI_ALPHA, /* U+0018cf: CANADIAN SYLLABICS RWI*/
+ RTUNI_ALPHA, /* U+0018d0: CANADIAN SYLLABICS RWII*/
+ RTUNI_ALPHA, /* U+0018d1: CANADIAN SYLLABICS RWO*/
+ RTUNI_ALPHA, /* U+0018d2: CANADIAN SYLLABICS RWOO*/
+ RTUNI_ALPHA, /* U+0018d3: CANADIAN SYLLABICS RWA*/
+ RTUNI_ALPHA, /* U+0018d4: CANADIAN SYLLABICS OJIBWAY P*/
+ RTUNI_ALPHA, /* U+0018d5: CANADIAN SYLLABICS OJIBWAY T*/
+ RTUNI_ALPHA, /* U+0018d6: CANADIAN SYLLABICS OJIBWAY K*/
+ RTUNI_ALPHA, /* U+0018d7: CANADIAN SYLLABICS OJIBWAY C*/
+ RTUNI_ALPHA, /* U+0018d8: CANADIAN SYLLABICS OJIBWAY M*/
+ RTUNI_ALPHA, /* U+0018d9: CANADIAN SYLLABICS OJIBWAY N*/
+ RTUNI_ALPHA, /* U+0018da: CANADIAN SYLLABICS OJIBWAY S*/
+ RTUNI_ALPHA, /* U+0018db: CANADIAN SYLLABICS OJIBWAY SH*/
+ RTUNI_ALPHA, /* U+0018dc: CANADIAN SYLLABICS EASTERN W*/
+ RTUNI_ALPHA, /* U+0018dd: CANADIAN SYLLABICS WESTERN W*/
+ RTUNI_ALPHA, /* U+0018de: CANADIAN SYLLABICS FINAL SMALL RING*/
+ RTUNI_ALPHA, /* U+0018df: CANADIAN SYLLABICS FINAL RAISED DOT*/
+ RTUNI_ALPHA, /* U+0018e0: CANADIAN SYLLABICS R-CREE RWE*/
+ RTUNI_ALPHA, /* U+0018e1: CANADIAN SYLLABICS WEST-CREE LOO*/
+ RTUNI_ALPHA, /* U+0018e2: CANADIAN SYLLABICS WEST-CREE LAA*/
+ RTUNI_ALPHA, /* U+0018e3: CANADIAN SYLLABICS THWE*/
+ RTUNI_ALPHA, /* U+0018e4: CANADIAN SYLLABICS THWA*/
+ RTUNI_ALPHA, /* U+0018e5: CANADIAN SYLLABICS TTHWE*/
+ RTUNI_ALPHA, /* U+0018e6: CANADIAN SYLLABICS TTHOO*/
+ RTUNI_ALPHA, /* U+0018e7: CANADIAN SYLLABICS TTHAA*/
+ RTUNI_ALPHA, /* U+0018e8: CANADIAN SYLLABICS TLHWE*/
+ RTUNI_ALPHA, /* U+0018e9: CANADIAN SYLLABICS TLHOO*/
+ RTUNI_ALPHA, /* U+0018ea: CANADIAN SYLLABICS SAYISI SHWE*/
+ RTUNI_ALPHA, /* U+0018eb: CANADIAN SYLLABICS SAYISI SHOO*/
+ RTUNI_ALPHA, /* U+0018ec: CANADIAN SYLLABICS SAYISI HOO*/
+ RTUNI_ALPHA, /* U+0018ed: CANADIAN SYLLABICS CARRIER GWU*/
+ RTUNI_ALPHA, /* U+0018ee: CANADIAN SYLLABICS CARRIER DENE GEE*/
+ RTUNI_ALPHA, /* U+0018ef: CANADIAN SYLLABICS CARRIER GAA*/
+ RTUNI_ALPHA, /* U+0018f0: CANADIAN SYLLABICS CARRIER GWA*/
+ RTUNI_ALPHA, /* U+0018f1: CANADIAN SYLLABICS SAYISI JUU*/
+ RTUNI_ALPHA, /* U+0018f2: CANADIAN SYLLABICS CARRIER JWA*/
+ RTUNI_ALPHA, /* U+0018f3: CANADIAN SYLLABICS BEAVER DENE L*/
+ RTUNI_ALPHA, /* U+0018f4: CANADIAN SYLLABICS BEAVER DENE R*/
+ RTUNI_ALPHA, /* U+0018f5: CANADIAN SYLLABICS CARRIER DENTAL S*/
+ 0, /* U+0018f6: */
+ 0, /* U+0018f7: */
+ 0, /* U+0018f8: */
+ 0, /* U+0018f9: */
+ 0, /* U+0018fa: */
+ 0, /* U+0018fb: */
+ 0, /* U+0018fc: */
+ 0, /* U+0018fd: */
+ 0, /* U+0018fe: */
+ 0, /* U+0018ff: */
+ RTUNI_ALPHA, /* U+001900: LIMBU VOWEL-CARRIER LETTER*/
+ RTUNI_ALPHA, /* U+001901: LIMBU LETTER KA*/
+ RTUNI_ALPHA, /* U+001902: LIMBU LETTER KHA*/
+ RTUNI_ALPHA, /* U+001903: LIMBU LETTER GA*/
+ RTUNI_ALPHA, /* U+001904: LIMBU LETTER GHA*/
+ RTUNI_ALPHA, /* U+001905: LIMBU LETTER NGA*/
+ RTUNI_ALPHA, /* U+001906: LIMBU LETTER CA*/
+ RTUNI_ALPHA, /* U+001907: LIMBU LETTER CHA*/
+ RTUNI_ALPHA, /* U+001908: LIMBU LETTER JA*/
+ RTUNI_ALPHA, /* U+001909: LIMBU LETTER JHA*/
+ RTUNI_ALPHA, /* U+00190a: LIMBU LETTER YAN*/
+ RTUNI_ALPHA, /* U+00190b: LIMBU LETTER TA*/
+ RTUNI_ALPHA, /* U+00190c: LIMBU LETTER THA*/
+ RTUNI_ALPHA, /* U+00190d: LIMBU LETTER DA*/
+ RTUNI_ALPHA, /* U+00190e: LIMBU LETTER DHA*/
+ RTUNI_ALPHA, /* U+00190f: LIMBU LETTER NA*/
+ RTUNI_ALPHA, /* U+001910: LIMBU LETTER PA*/
+ RTUNI_ALPHA, /* U+001911: LIMBU LETTER PHA*/
+ RTUNI_ALPHA, /* U+001912: LIMBU LETTER BA*/
+ RTUNI_ALPHA, /* U+001913: LIMBU LETTER BHA*/
+ RTUNI_ALPHA, /* U+001914: LIMBU LETTER MA*/
+ RTUNI_ALPHA, /* U+001915: LIMBU LETTER YA*/
+ RTUNI_ALPHA, /* U+001916: LIMBU LETTER RA*/
+ RTUNI_ALPHA, /* U+001917: LIMBU LETTER LA*/
+ RTUNI_ALPHA, /* U+001918: LIMBU LETTER WA*/
+ RTUNI_ALPHA, /* U+001919: LIMBU LETTER SHA*/
+ RTUNI_ALPHA, /* U+00191a: LIMBU LETTER SSA*/
+ RTUNI_ALPHA, /* U+00191b: LIMBU LETTER SA*/
+ RTUNI_ALPHA, /* U+00191c: LIMBU LETTER HA*/
+ 0, /* U+00191d: */
+ 0, /* U+00191e: */
+ 0, /* U+00191f: */
+ RTUNI_ALPHA, /* U+001920: LIMBU VOWEL SIGN A*/
+ RTUNI_ALPHA, /* U+001921: LIMBU VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+001922: LIMBU VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+001923: LIMBU VOWEL SIGN EE*/
+ RTUNI_ALPHA, /* U+001924: LIMBU VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+001925: LIMBU VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+001926: LIMBU VOWEL SIGN AU*/
+ RTUNI_ALPHA, /* U+001927: LIMBU VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+001928: LIMBU VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+001929: LIMBU SUBJOINED LETTER YA*/
+ RTUNI_ALPHA, /* U+00192a: LIMBU SUBJOINED LETTER RA*/
+ RTUNI_ALPHA, /* U+00192b: LIMBU SUBJOINED LETTER WA*/
+ 0, /* U+00192c: */
+ 0, /* U+00192d: */
+ 0, /* U+00192e: */
+ 0, /* U+00192f: */
+ RTUNI_ALPHA, /* U+001930: LIMBU SMALL LETTER KA*/
+ RTUNI_ALPHA, /* U+001931: LIMBU SMALL LETTER NGA*/
+ RTUNI_ALPHA, /* U+001932: LIMBU SMALL LETTER ANUSVARA*/
+ RTUNI_ALPHA, /* U+001933: LIMBU SMALL LETTER TA*/
+ RTUNI_ALPHA, /* U+001934: LIMBU SMALL LETTER NA*/
+ RTUNI_ALPHA, /* U+001935: LIMBU SMALL LETTER PA*/
+ RTUNI_ALPHA, /* U+001936: LIMBU SMALL LETTER MA*/
+ RTUNI_ALPHA, /* U+001937: LIMBU SMALL LETTER RA*/
+ RTUNI_ALPHA, /* U+001938: LIMBU SMALL LETTER LA*/
+ 0, /* U+001939: LIMBU SIGN MUKPHRENG*/
+ 0, /* U+00193a: LIMBU SIGN KEMPHRENG*/
+ 0, /* U+00193b: LIMBU SIGN SA-I*/
+ 0, /* U+00193c: */
+ 0, /* U+00193d: */
+ 0, /* U+00193e: */
+ 0, /* U+00193f: */
+ 0, /* U+001940: LIMBU SIGN LOO*/
+ 0, /* U+001941: */
+ 0, /* U+001942: */
+ 0, /* U+001943: */
+ 0, /* U+001944: LIMBU EXCLAMATION MARK*/
+ 0, /* U+001945: LIMBU QUESTION MARK*/
+ RTUNI_DDIGIT, /* U+001946: LIMBU DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001947: LIMBU DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001948: LIMBU DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001949: LIMBU DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+00194a: LIMBU DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+00194b: LIMBU DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+00194c: LIMBU DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+00194d: LIMBU DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+00194e: LIMBU DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+00194f: LIMBU DIGIT NINE*/
+ RTUNI_ALPHA, /* U+001950: TAI LE LETTER KA*/
+ RTUNI_ALPHA, /* U+001951: TAI LE LETTER XA*/
+ RTUNI_ALPHA, /* U+001952: TAI LE LETTER NGA*/
+ RTUNI_ALPHA, /* U+001953: TAI LE LETTER TSA*/
+ RTUNI_ALPHA, /* U+001954: TAI LE LETTER SA*/
+ RTUNI_ALPHA, /* U+001955: TAI LE LETTER YA*/
+ RTUNI_ALPHA, /* U+001956: TAI LE LETTER TA*/
+ RTUNI_ALPHA, /* U+001957: TAI LE LETTER THA*/
+ RTUNI_ALPHA, /* U+001958: TAI LE LETTER LA*/
+ RTUNI_ALPHA, /* U+001959: TAI LE LETTER PA*/
+ RTUNI_ALPHA, /* U+00195a: TAI LE LETTER PHA*/
+ RTUNI_ALPHA, /* U+00195b: TAI LE LETTER MA*/
+ RTUNI_ALPHA, /* U+00195c: TAI LE LETTER FA*/
+ RTUNI_ALPHA, /* U+00195d: TAI LE LETTER VA*/
+ RTUNI_ALPHA, /* U+00195e: TAI LE LETTER HA*/
+ RTUNI_ALPHA, /* U+00195f: TAI LE LETTER QA*/
+ RTUNI_ALPHA, /* U+001960: TAI LE LETTER KHA*/
+ RTUNI_ALPHA, /* U+001961: TAI LE LETTER TSHA*/
+ RTUNI_ALPHA, /* U+001962: TAI LE LETTER NA*/
+ RTUNI_ALPHA, /* U+001963: TAI LE LETTER A*/
+ RTUNI_ALPHA, /* U+001964: TAI LE LETTER I*/
+ RTUNI_ALPHA, /* U+001965: TAI LE LETTER EE*/
+ RTUNI_ALPHA, /* U+001966: TAI LE LETTER EH*/
+ RTUNI_ALPHA, /* U+001967: TAI LE LETTER U*/
+ RTUNI_ALPHA, /* U+001968: TAI LE LETTER OO*/
+ RTUNI_ALPHA, /* U+001969: TAI LE LETTER O*/
+ RTUNI_ALPHA, /* U+00196a: TAI LE LETTER UE*/
+ RTUNI_ALPHA, /* U+00196b: TAI LE LETTER E*/
+ RTUNI_ALPHA, /* U+00196c: TAI LE LETTER AUE*/
+ RTUNI_ALPHA, /* U+00196d: TAI LE LETTER AI*/
+ 0, /* U+00196e: */
+ 0, /* U+00196f: */
+ RTUNI_ALPHA, /* U+001970: TAI LE LETTER TONE-2*/
+ RTUNI_ALPHA, /* U+001971: TAI LE LETTER TONE-3*/
+ RTUNI_ALPHA, /* U+001972: TAI LE LETTER TONE-4*/
+ RTUNI_ALPHA, /* U+001973: TAI LE LETTER TONE-5*/
+ RTUNI_ALPHA, /* U+001974: TAI LE LETTER TONE-6*/
+ 0, /* U+001975: */
+ 0, /* U+001976: */
+ 0, /* U+001977: */
+ 0, /* U+001978: */
+ 0, /* U+001979: */
+ 0, /* U+00197a: */
+ 0, /* U+00197b: */
+ 0, /* U+00197c: */
+ 0, /* U+00197d: */
+ 0, /* U+00197e: */
+ 0, /* U+00197f: */
+ RTUNI_ALPHA, /* U+001980: NEW TAI LUE LETTER HIGH QA*/
+ RTUNI_ALPHA, /* U+001981: NEW TAI LUE LETTER LOW QA*/
+ RTUNI_ALPHA, /* U+001982: NEW TAI LUE LETTER HIGH KA*/
+ RTUNI_ALPHA, /* U+001983: NEW TAI LUE LETTER HIGH XA*/
+ RTUNI_ALPHA, /* U+001984: NEW TAI LUE LETTER HIGH NGA*/
+ RTUNI_ALPHA, /* U+001985: NEW TAI LUE LETTER LOW KA*/
+ RTUNI_ALPHA, /* U+001986: NEW TAI LUE LETTER LOW XA*/
+ RTUNI_ALPHA, /* U+001987: NEW TAI LUE LETTER LOW NGA*/
+ RTUNI_ALPHA, /* U+001988: NEW TAI LUE LETTER HIGH TSA*/
+ RTUNI_ALPHA, /* U+001989: NEW TAI LUE LETTER HIGH SA*/
+ RTUNI_ALPHA, /* U+00198a: NEW TAI LUE LETTER HIGH YA*/
+ RTUNI_ALPHA, /* U+00198b: NEW TAI LUE LETTER LOW TSA*/
+ RTUNI_ALPHA, /* U+00198c: NEW TAI LUE LETTER LOW SA*/
+ RTUNI_ALPHA, /* U+00198d: NEW TAI LUE LETTER LOW YA*/
+ RTUNI_ALPHA, /* U+00198e: NEW TAI LUE LETTER HIGH TA*/
+ RTUNI_ALPHA, /* U+00198f: NEW TAI LUE LETTER HIGH THA*/
+ RTUNI_ALPHA, /* U+001990: NEW TAI LUE LETTER HIGH NA*/
+ RTUNI_ALPHA, /* U+001991: NEW TAI LUE LETTER LOW TA*/
+ RTUNI_ALPHA, /* U+001992: NEW TAI LUE LETTER LOW THA*/
+ RTUNI_ALPHA, /* U+001993: NEW TAI LUE LETTER LOW NA*/
+ RTUNI_ALPHA, /* U+001994: NEW TAI LUE LETTER HIGH PA*/
+ RTUNI_ALPHA, /* U+001995: NEW TAI LUE LETTER HIGH PHA*/
+ RTUNI_ALPHA, /* U+001996: NEW TAI LUE LETTER HIGH MA*/
+ RTUNI_ALPHA, /* U+001997: NEW TAI LUE LETTER LOW PA*/
+ RTUNI_ALPHA, /* U+001998: NEW TAI LUE LETTER LOW PHA*/
+ RTUNI_ALPHA, /* U+001999: NEW TAI LUE LETTER LOW MA*/
+ RTUNI_ALPHA, /* U+00199a: NEW TAI LUE LETTER HIGH FA*/
+ RTUNI_ALPHA, /* U+00199b: NEW TAI LUE LETTER HIGH VA*/
+ RTUNI_ALPHA, /* U+00199c: NEW TAI LUE LETTER HIGH LA*/
+ RTUNI_ALPHA, /* U+00199d: NEW TAI LUE LETTER LOW FA*/
+ RTUNI_ALPHA, /* U+00199e: NEW TAI LUE LETTER LOW VA*/
+ RTUNI_ALPHA, /* U+00199f: NEW TAI LUE LETTER LOW LA*/
+ RTUNI_ALPHA, /* U+0019a0: NEW TAI LUE LETTER HIGH HA*/
+ RTUNI_ALPHA, /* U+0019a1: NEW TAI LUE LETTER HIGH DA*/
+ RTUNI_ALPHA, /* U+0019a2: NEW TAI LUE LETTER HIGH BA*/
+ RTUNI_ALPHA, /* U+0019a3: NEW TAI LUE LETTER LOW HA*/
+ RTUNI_ALPHA, /* U+0019a4: NEW TAI LUE LETTER LOW DA*/
+ RTUNI_ALPHA, /* U+0019a5: NEW TAI LUE LETTER LOW BA*/
+ RTUNI_ALPHA, /* U+0019a6: NEW TAI LUE LETTER HIGH KVA*/
+ RTUNI_ALPHA, /* U+0019a7: NEW TAI LUE LETTER HIGH XVA*/
+ RTUNI_ALPHA, /* U+0019a8: NEW TAI LUE LETTER LOW KVA*/
+ RTUNI_ALPHA, /* U+0019a9: NEW TAI LUE LETTER LOW XVA*/
+ RTUNI_ALPHA, /* U+0019aa: NEW TAI LUE LETTER HIGH SUA*/
+ RTUNI_ALPHA, /* U+0019ab: NEW TAI LUE LETTER LOW SUA*/
+ 0, /* U+0019ac: */
+ 0, /* U+0019ad: */
+ 0, /* U+0019ae: */
+ 0, /* U+0019af: */
+ RTUNI_ALPHA, /* U+0019b0: NEW TAI LUE VOWEL SIGN VOWEL SHORTENER*/
+ RTUNI_ALPHA, /* U+0019b1: NEW TAI LUE VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+0019b2: NEW TAI LUE VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+0019b3: NEW TAI LUE VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+0019b4: NEW TAI LUE VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+0019b5: NEW TAI LUE VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+0019b6: NEW TAI LUE VOWEL SIGN AE*/
+ RTUNI_ALPHA, /* U+0019b7: NEW TAI LUE VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+0019b8: NEW TAI LUE VOWEL SIGN OA*/
+ RTUNI_ALPHA, /* U+0019b9: NEW TAI LUE VOWEL SIGN UE*/
+ RTUNI_ALPHA, /* U+0019ba: NEW TAI LUE VOWEL SIGN AY*/
+ RTUNI_ALPHA, /* U+0019bb: NEW TAI LUE VOWEL SIGN AAY*/
+ RTUNI_ALPHA, /* U+0019bc: NEW TAI LUE VOWEL SIGN UY*/
+ RTUNI_ALPHA, /* U+0019bd: NEW TAI LUE VOWEL SIGN OY*/
+ RTUNI_ALPHA, /* U+0019be: NEW TAI LUE VOWEL SIGN OAY*/
+ RTUNI_ALPHA, /* U+0019bf: NEW TAI LUE VOWEL SIGN UEY*/
+ RTUNI_ALPHA, /* U+0019c0: NEW TAI LUE VOWEL SIGN IY*/
+ RTUNI_ALPHA, /* U+0019c1: NEW TAI LUE LETTER FINAL V*/
+ RTUNI_ALPHA, /* U+0019c2: NEW TAI LUE LETTER FINAL NG*/
+ RTUNI_ALPHA, /* U+0019c3: NEW TAI LUE LETTER FINAL N*/
+ RTUNI_ALPHA, /* U+0019c4: NEW TAI LUE LETTER FINAL M*/
+ RTUNI_ALPHA, /* U+0019c5: NEW TAI LUE LETTER FINAL K*/
+ RTUNI_ALPHA, /* U+0019c6: NEW TAI LUE LETTER FINAL D*/
+ RTUNI_ALPHA, /* U+0019c7: NEW TAI LUE LETTER FINAL B*/
+ RTUNI_ALPHA, /* U+0019c8: NEW TAI LUE TONE MARK-1*/
+ RTUNI_ALPHA, /* U+0019c9: NEW TAI LUE TONE MARK-2*/
+ 0, /* U+0019ca: */
+ 0, /* U+0019cb: */
+ 0, /* U+0019cc: */
+ 0, /* U+0019cd: */
+ 0, /* U+0019ce: */
+ 0, /* U+0019cf: */
+ RTUNI_DDIGIT, /* U+0019d0: NEW TAI LUE DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+0019d1: NEW TAI LUE DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+0019d2: NEW TAI LUE DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+0019d3: NEW TAI LUE DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+0019d4: NEW TAI LUE DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+0019d5: NEW TAI LUE DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+0019d6: NEW TAI LUE DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+0019d7: NEW TAI LUE DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+0019d8: NEW TAI LUE DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+0019d9: NEW TAI LUE DIGIT NINE*/
+ 0, /* U+0019da: NEW TAI LUE THAM DIGIT ONE*/
+ 0, /* U+0019db: */
+ 0, /* U+0019dc: */
+ 0, /* U+0019dd: */
+ 0, /* U+0019de: NEW TAI LUE SIGN LAE*/
+ 0, /* U+0019df: NEW TAI LUE SIGN LAEV*/
+ 0, /* U+0019e0: KHMER SYMBOL PATHAMASAT*/
+ 0, /* U+0019e1: KHMER SYMBOL MUOY KOET*/
+ 0, /* U+0019e2: KHMER SYMBOL PII KOET*/
+ 0, /* U+0019e3: KHMER SYMBOL BEI KOET*/
+ 0, /* U+0019e4: KHMER SYMBOL BUON KOET*/
+ 0, /* U+0019e5: KHMER SYMBOL PRAM KOET*/
+ 0, /* U+0019e6: KHMER SYMBOL PRAM-MUOY KOET*/
+ 0, /* U+0019e7: KHMER SYMBOL PRAM-PII KOET*/
+ 0, /* U+0019e8: KHMER SYMBOL PRAM-BEI KOET*/
+ 0, /* U+0019e9: KHMER SYMBOL PRAM-BUON KOET*/
+ 0, /* U+0019ea: KHMER SYMBOL DAP KOET*/
+ 0, /* U+0019eb: KHMER SYMBOL DAP-MUOY KOET*/
+ 0, /* U+0019ec: KHMER SYMBOL DAP-PII KOET*/
+ 0, /* U+0019ed: KHMER SYMBOL DAP-BEI KOET*/
+ 0, /* U+0019ee: KHMER SYMBOL DAP-BUON KOET*/
+ 0, /* U+0019ef: KHMER SYMBOL DAP-PRAM KOET*/
+ 0, /* U+0019f0: KHMER SYMBOL TUTEYASAT*/
+ 0, /* U+0019f1: KHMER SYMBOL MUOY ROC*/
+ 0, /* U+0019f2: KHMER SYMBOL PII ROC*/
+ 0, /* U+0019f3: KHMER SYMBOL BEI ROC*/
+ 0, /* U+0019f4: KHMER SYMBOL BUON ROC*/
+ 0, /* U+0019f5: KHMER SYMBOL PRAM ROC*/
+ 0, /* U+0019f6: KHMER SYMBOL PRAM-MUOY ROC*/
+ 0, /* U+0019f7: KHMER SYMBOL PRAM-PII ROC*/
+ 0, /* U+0019f8: KHMER SYMBOL PRAM-BEI ROC*/
+ 0, /* U+0019f9: KHMER SYMBOL PRAM-BUON ROC*/
+ 0, /* U+0019fa: KHMER SYMBOL DAP ROC*/
+ 0, /* U+0019fb: KHMER SYMBOL DAP-MUOY ROC*/
+ 0, /* U+0019fc: KHMER SYMBOL DAP-PII ROC*/
+ 0, /* U+0019fd: KHMER SYMBOL DAP-BEI ROC*/
+ 0, /* U+0019fe: KHMER SYMBOL DAP-BUON ROC*/
+ 0, /* U+0019ff: KHMER SYMBOL DAP-PRAM ROC*/
+ RTUNI_ALPHA, /* U+001a00: BUGINESE LETTER KA*/
+ RTUNI_ALPHA, /* U+001a01: BUGINESE LETTER GA*/
+ RTUNI_ALPHA, /* U+001a02: BUGINESE LETTER NGA*/
+ RTUNI_ALPHA, /* U+001a03: BUGINESE LETTER NGKA*/
+ RTUNI_ALPHA, /* U+001a04: BUGINESE LETTER PA*/
+ RTUNI_ALPHA, /* U+001a05: BUGINESE LETTER BA*/
+ RTUNI_ALPHA, /* U+001a06: BUGINESE LETTER MA*/
+ RTUNI_ALPHA, /* U+001a07: BUGINESE LETTER MPA*/
+ RTUNI_ALPHA, /* U+001a08: BUGINESE LETTER TA*/
+ RTUNI_ALPHA, /* U+001a09: BUGINESE LETTER DA*/
+ RTUNI_ALPHA, /* U+001a0a: BUGINESE LETTER NA*/
+ RTUNI_ALPHA, /* U+001a0b: BUGINESE LETTER NRA*/
+ RTUNI_ALPHA, /* U+001a0c: BUGINESE LETTER CA*/
+ RTUNI_ALPHA, /* U+001a0d: BUGINESE LETTER JA*/
+ RTUNI_ALPHA, /* U+001a0e: BUGINESE LETTER NYA*/
+ RTUNI_ALPHA, /* U+001a0f: BUGINESE LETTER NYCA*/
+ RTUNI_ALPHA, /* U+001a10: BUGINESE LETTER YA*/
+ RTUNI_ALPHA, /* U+001a11: BUGINESE LETTER RA*/
+ RTUNI_ALPHA, /* U+001a12: BUGINESE LETTER LA*/
+ RTUNI_ALPHA, /* U+001a13: BUGINESE LETTER VA*/
+ RTUNI_ALPHA, /* U+001a14: BUGINESE LETTER SA*/
+ RTUNI_ALPHA, /* U+001a15: BUGINESE LETTER A*/
+ RTUNI_ALPHA, /* U+001a16: BUGINESE LETTER HA*/
+ RTUNI_ALPHA, /* U+001a17: BUGINESE VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+001a18: BUGINESE VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+001a19: BUGINESE VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+001a1a: BUGINESE VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+001a1b: BUGINESE VOWEL SIGN AE*/
+ 0, /* U+001a1c: */
+ 0, /* U+001a1d: */
+ 0, /* U+001a1e: BUGINESE PALLAWA*/
+ 0, /* U+001a1f: BUGINESE END OF SECTION*/
+ RTUNI_ALPHA, /* U+001a20: TAI THAM LETTER HIGH KA*/
+ RTUNI_ALPHA, /* U+001a21: TAI THAM LETTER HIGH KHA*/
+ RTUNI_ALPHA, /* U+001a22: TAI THAM LETTER HIGH KXA*/
+ RTUNI_ALPHA, /* U+001a23: TAI THAM LETTER LOW KA*/
+ RTUNI_ALPHA, /* U+001a24: TAI THAM LETTER LOW KXA*/
+ RTUNI_ALPHA, /* U+001a25: TAI THAM LETTER LOW KHA*/
+ RTUNI_ALPHA, /* U+001a26: TAI THAM LETTER NGA*/
+ RTUNI_ALPHA, /* U+001a27: TAI THAM LETTER HIGH CA*/
+ RTUNI_ALPHA, /* U+001a28: TAI THAM LETTER HIGH CHA*/
+ RTUNI_ALPHA, /* U+001a29: TAI THAM LETTER LOW CA*/
+ RTUNI_ALPHA, /* U+001a2a: TAI THAM LETTER LOW SA*/
+ RTUNI_ALPHA, /* U+001a2b: TAI THAM LETTER LOW CHA*/
+ RTUNI_ALPHA, /* U+001a2c: TAI THAM LETTER NYA*/
+ RTUNI_ALPHA, /* U+001a2d: TAI THAM LETTER RATA*/
+ RTUNI_ALPHA, /* U+001a2e: TAI THAM LETTER HIGH RATHA*/
+ RTUNI_ALPHA, /* U+001a2f: TAI THAM LETTER DA*/
+ RTUNI_ALPHA, /* U+001a30: TAI THAM LETTER LOW RATHA*/
+ RTUNI_ALPHA, /* U+001a31: TAI THAM LETTER RANA*/
+ RTUNI_ALPHA, /* U+001a32: TAI THAM LETTER HIGH TA*/
+ RTUNI_ALPHA, /* U+001a33: TAI THAM LETTER HIGH THA*/
+ RTUNI_ALPHA, /* U+001a34: TAI THAM LETTER LOW TA*/
+ RTUNI_ALPHA, /* U+001a35: TAI THAM LETTER LOW THA*/
+ RTUNI_ALPHA, /* U+001a36: TAI THAM LETTER NA*/
+ RTUNI_ALPHA, /* U+001a37: TAI THAM LETTER BA*/
+ RTUNI_ALPHA, /* U+001a38: TAI THAM LETTER HIGH PA*/
+ RTUNI_ALPHA, /* U+001a39: TAI THAM LETTER HIGH PHA*/
+ RTUNI_ALPHA, /* U+001a3a: TAI THAM LETTER HIGH FA*/
+ RTUNI_ALPHA, /* U+001a3b: TAI THAM LETTER LOW PA*/
+ RTUNI_ALPHA, /* U+001a3c: TAI THAM LETTER LOW FA*/
+ RTUNI_ALPHA, /* U+001a3d: TAI THAM LETTER LOW PHA*/
+ RTUNI_ALPHA, /* U+001a3e: TAI THAM LETTER MA*/
+ RTUNI_ALPHA, /* U+001a3f: TAI THAM LETTER LOW YA*/
+ RTUNI_ALPHA, /* U+001a40: TAI THAM LETTER HIGH YA*/
+ RTUNI_ALPHA, /* U+001a41: TAI THAM LETTER RA*/
+ RTUNI_ALPHA, /* U+001a42: TAI THAM LETTER RUE*/
+ RTUNI_ALPHA, /* U+001a43: TAI THAM LETTER LA*/
+ RTUNI_ALPHA, /* U+001a44: TAI THAM LETTER LUE*/
+ RTUNI_ALPHA, /* U+001a45: TAI THAM LETTER WA*/
+ RTUNI_ALPHA, /* U+001a46: TAI THAM LETTER HIGH SHA*/
+ RTUNI_ALPHA, /* U+001a47: TAI THAM LETTER HIGH SSA*/
+ RTUNI_ALPHA, /* U+001a48: TAI THAM LETTER HIGH SA*/
+ RTUNI_ALPHA, /* U+001a49: TAI THAM LETTER HIGH HA*/
+ RTUNI_ALPHA, /* U+001a4a: TAI THAM LETTER LLA*/
+ RTUNI_ALPHA, /* U+001a4b: TAI THAM LETTER A*/
+ RTUNI_ALPHA, /* U+001a4c: TAI THAM LETTER LOW HA*/
+ RTUNI_ALPHA, /* U+001a4d: TAI THAM LETTER I*/
+ RTUNI_ALPHA, /* U+001a4e: TAI THAM LETTER II*/
+ RTUNI_ALPHA, /* U+001a4f: TAI THAM LETTER U*/
+ RTUNI_ALPHA, /* U+001a50: TAI THAM LETTER UU*/
+ RTUNI_ALPHA, /* U+001a51: TAI THAM LETTER EE*/
+ RTUNI_ALPHA, /* U+001a52: TAI THAM LETTER OO*/
+ RTUNI_ALPHA, /* U+001a53: TAI THAM LETTER LAE*/
+ RTUNI_ALPHA, /* U+001a54: TAI THAM LETTER GREAT SA*/
+ RTUNI_ALPHA, /* U+001a55: TAI THAM CONSONANT SIGN MEDIAL RA*/
+ RTUNI_ALPHA, /* U+001a56: TAI THAM CONSONANT SIGN MEDIAL LA*/
+ RTUNI_ALPHA, /* U+001a57: TAI THAM CONSONANT SIGN LA TANG LAI*/
+ RTUNI_ALPHA, /* U+001a58: TAI THAM SIGN MAI KANG LAI*/
+ RTUNI_ALPHA, /* U+001a59: TAI THAM CONSONANT SIGN FINAL NGA*/
+ RTUNI_ALPHA, /* U+001a5a: TAI THAM CONSONANT SIGN LOW PA*/
+ RTUNI_ALPHA, /* U+001a5b: TAI THAM CONSONANT SIGN HIGH RATHA OR LOW PA*/
+ RTUNI_ALPHA, /* U+001a5c: TAI THAM CONSONANT SIGN MA*/
+ RTUNI_ALPHA, /* U+001a5d: TAI THAM CONSONANT SIGN BA*/
+ RTUNI_ALPHA, /* U+001a5e: TAI THAM CONSONANT SIGN SA*/
+ 0, /* U+001a5f: */
+ 0, /* U+001a60: TAI THAM SIGN SAKOT*/
+ RTUNI_ALPHA, /* U+001a61: TAI THAM VOWEL SIGN A*/
+ RTUNI_ALPHA, /* U+001a62: TAI THAM VOWEL SIGN MAI SAT*/
+ RTUNI_ALPHA, /* U+001a63: TAI THAM VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+001a64: TAI THAM VOWEL SIGN TALL AA*/
+ RTUNI_ALPHA, /* U+001a65: TAI THAM VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+001a66: TAI THAM VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+001a67: TAI THAM VOWEL SIGN UE*/
+ RTUNI_ALPHA, /* U+001a68: TAI THAM VOWEL SIGN UUE*/
+ RTUNI_ALPHA, /* U+001a69: TAI THAM VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+001a6a: TAI THAM VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+001a6b: TAI THAM VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+001a6c: TAI THAM VOWEL SIGN OA BELOW*/
+ RTUNI_ALPHA, /* U+001a6d: TAI THAM VOWEL SIGN OY*/
+ RTUNI_ALPHA, /* U+001a6e: TAI THAM VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+001a6f: TAI THAM VOWEL SIGN AE*/
+ RTUNI_ALPHA, /* U+001a70: TAI THAM VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+001a71: TAI THAM VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+001a72: TAI THAM VOWEL SIGN THAM AI*/
+ RTUNI_ALPHA, /* U+001a73: TAI THAM VOWEL SIGN OA ABOVE*/
+ RTUNI_ALPHA, /* U+001a74: TAI THAM SIGN MAI KANG*/
+ 0, /* U+001a75: TAI THAM SIGN TONE-1*/
+ 0, /* U+001a76: TAI THAM SIGN TONE-2*/
+ 0, /* U+001a77: TAI THAM SIGN KHUEN TONE-3*/
+ 0, /* U+001a78: TAI THAM SIGN KHUEN TONE-4*/
+ 0, /* U+001a79: TAI THAM SIGN KHUEN TONE-5*/
+ 0, /* U+001a7a: TAI THAM SIGN RA HAAM*/
+ 0, /* U+001a7b: TAI THAM SIGN MAI SAM*/
+ 0, /* U+001a7c: TAI THAM SIGN KHUEN-LUE KARAN*/
+ 0, /* U+001a7d: */
+ 0, /* U+001a7e: */
+ 0, /* U+001a7f: TAI THAM COMBINING CRYPTOGRAMMIC DOT*/
+ RTUNI_DDIGIT, /* U+001a80: TAI THAM HORA DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001a81: TAI THAM HORA DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001a82: TAI THAM HORA DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001a83: TAI THAM HORA DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+001a84: TAI THAM HORA DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+001a85: TAI THAM HORA DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+001a86: TAI THAM HORA DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+001a87: TAI THAM HORA DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+001a88: TAI THAM HORA DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+001a89: TAI THAM HORA DIGIT NINE*/
+ 0, /* U+001a8a: */
+ 0, /* U+001a8b: */
+ 0, /* U+001a8c: */
+ 0, /* U+001a8d: */
+ 0, /* U+001a8e: */
+ 0, /* U+001a8f: */
+ RTUNI_DDIGIT, /* U+001a90: TAI THAM THAM DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001a91: TAI THAM THAM DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001a92: TAI THAM THAM DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001a93: TAI THAM THAM DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+001a94: TAI THAM THAM DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+001a95: TAI THAM THAM DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+001a96: TAI THAM THAM DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+001a97: TAI THAM THAM DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+001a98: TAI THAM THAM DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+001a99: TAI THAM THAM DIGIT NINE*/
+ 0, /* U+001a9a: */
+ 0, /* U+001a9b: */
+ 0, /* U+001a9c: */
+ 0, /* U+001a9d: */
+ 0, /* U+001a9e: */
+ 0, /* U+001a9f: */
+ 0, /* U+001aa0: TAI THAM SIGN WIANG*/
+ 0, /* U+001aa1: TAI THAM SIGN WIANGWAAK*/
+ 0, /* U+001aa2: TAI THAM SIGN SAWAN*/
+ 0, /* U+001aa3: TAI THAM SIGN KEOW*/
+ 0, /* U+001aa4: TAI THAM SIGN HOY*/
+ 0, /* U+001aa5: TAI THAM SIGN DOKMAI*/
+ 0, /* U+001aa6: TAI THAM SIGN REVERSED ROTATED RANA*/
+ RTUNI_ALPHA, /* U+001aa7: TAI THAM SIGN MAI YAMOK*/
+ 0, /* U+001aa8: TAI THAM SIGN KAAN*/
+ 0, /* U+001aa9: TAI THAM SIGN KAANKUU*/
+ 0, /* U+001aaa: TAI THAM SIGN SATKAAN*/
+ 0, /* U+001aab: TAI THAM SIGN SATKAANKUU*/
+ 0, /* U+001aac: TAI THAM SIGN HANG*/
+ 0, /* U+001aad: TAI THAM SIGN CAANG*/
+ 0, /* U+001aae: */
+ 0, /* U+001aaf: */
+ 0, /* U+001ab0: */
+ 0, /* U+001ab1: */
+ 0, /* U+001ab2: */
+ 0, /* U+001ab3: */
+ 0, /* U+001ab4: */
+ 0, /* U+001ab5: */
+ 0, /* U+001ab6: */
+ 0, /* U+001ab7: */
+ 0, /* U+001ab8: */
+ 0, /* U+001ab9: */
+ 0, /* U+001aba: */
+ 0, /* U+001abb: */
+ 0, /* U+001abc: */
+ 0, /* U+001abd: */
+ 0, /* U+001abe: */
+ 0, /* U+001abf: */
+ 0, /* U+001ac0: */
+ 0, /* U+001ac1: */
+ 0, /* U+001ac2: */
+ 0, /* U+001ac3: */
+ 0, /* U+001ac4: */
+ 0, /* U+001ac5: */
+ 0, /* U+001ac6: */
+ 0, /* U+001ac7: */
+ 0, /* U+001ac8: */
+ 0, /* U+001ac9: */
+ 0, /* U+001aca: */
+ 0, /* U+001acb: */
+ 0, /* U+001acc: */
+ 0, /* U+001acd: */
+ 0, /* U+001ace: */
+ 0, /* U+001acf: */
+ 0, /* U+001ad0: */
+ 0, /* U+001ad1: */
+ 0, /* U+001ad2: */
+ 0, /* U+001ad3: */
+ 0, /* U+001ad4: */
+ 0, /* U+001ad5: */
+ 0, /* U+001ad6: */
+ 0, /* U+001ad7: */
+ 0, /* U+001ad8: */
+ 0, /* U+001ad9: */
+ 0, /* U+001ada: */
+ 0, /* U+001adb: */
+ 0, /* U+001adc: */
+ 0, /* U+001add: */
+ 0, /* U+001ade: */
+ 0, /* U+001adf: */
+ 0, /* U+001ae0: */
+ 0, /* U+001ae1: */
+ 0, /* U+001ae2: */
+ 0, /* U+001ae3: */
+ 0, /* U+001ae4: */
+ 0, /* U+001ae5: */
+ 0, /* U+001ae6: */
+ 0, /* U+001ae7: */
+ 0, /* U+001ae8: */
+ 0, /* U+001ae9: */
+ 0, /* U+001aea: */
+ 0, /* U+001aeb: */
+ 0, /* U+001aec: */
+ 0, /* U+001aed: */
+ 0, /* U+001aee: */
+ 0, /* U+001aef: */
+ 0, /* U+001af0: */
+ 0, /* U+001af1: */
+ 0, /* U+001af2: */
+ 0, /* U+001af3: */
+ 0, /* U+001af4: */
+ 0, /* U+001af5: */
+ 0, /* U+001af6: */
+ 0, /* U+001af7: */
+ 0, /* U+001af8: */
+ 0, /* U+001af9: */
+ 0, /* U+001afa: */
+ 0, /* U+001afb: */
+ 0, /* U+001afc: */
+ 0, /* U+001afd: */
+ 0, /* U+001afe: */
+ 0, /* U+001aff: */
+ RTUNI_ALPHA, /* U+001b00: BALINESE SIGN ULU RICEM*/
+ RTUNI_ALPHA, /* U+001b01: BALINESE SIGN ULU CANDRA*/
+ RTUNI_ALPHA, /* U+001b02: BALINESE SIGN CECEK*/
+ RTUNI_ALPHA, /* U+001b03: BALINESE SIGN SURANG*/
+ RTUNI_ALPHA, /* U+001b04: BALINESE SIGN BISAH*/
+ RTUNI_ALPHA, /* U+001b05: BALINESE LETTER AKARA*/
+ RTUNI_ALPHA, /* U+001b06: BALINESE LETTER AKARA TEDUNG*/
+ RTUNI_ALPHA, /* U+001b07: BALINESE LETTER IKARA*/
+ RTUNI_ALPHA, /* U+001b08: BALINESE LETTER IKARA TEDUNG*/
+ RTUNI_ALPHA, /* U+001b09: BALINESE LETTER UKARA*/
+ RTUNI_ALPHA, /* U+001b0a: BALINESE LETTER UKARA TEDUNG*/
+ RTUNI_ALPHA, /* U+001b0b: BALINESE LETTER RA REPA*/
+ RTUNI_ALPHA, /* U+001b0c: BALINESE LETTER RA REPA TEDUNG*/
+ RTUNI_ALPHA, /* U+001b0d: BALINESE LETTER LA LENGA*/
+ RTUNI_ALPHA, /* U+001b0e: BALINESE LETTER LA LENGA TEDUNG*/
+ RTUNI_ALPHA, /* U+001b0f: BALINESE LETTER EKARA*/
+ RTUNI_ALPHA, /* U+001b10: BALINESE LETTER AIKARA*/
+ RTUNI_ALPHA, /* U+001b11: BALINESE LETTER OKARA*/
+ RTUNI_ALPHA, /* U+001b12: BALINESE LETTER OKARA TEDUNG*/
+ RTUNI_ALPHA, /* U+001b13: BALINESE LETTER KA*/
+ RTUNI_ALPHA, /* U+001b14: BALINESE LETTER KA MAHAPRANA*/
+ RTUNI_ALPHA, /* U+001b15: BALINESE LETTER GA*/
+ RTUNI_ALPHA, /* U+001b16: BALINESE LETTER GA GORA*/
+ RTUNI_ALPHA, /* U+001b17: BALINESE LETTER NGA*/
+ RTUNI_ALPHA, /* U+001b18: BALINESE LETTER CA*/
+ RTUNI_ALPHA, /* U+001b19: BALINESE LETTER CA LACA*/
+ RTUNI_ALPHA, /* U+001b1a: BALINESE LETTER JA*/
+ RTUNI_ALPHA, /* U+001b1b: BALINESE LETTER JA JERA*/
+ RTUNI_ALPHA, /* U+001b1c: BALINESE LETTER NYA*/
+ RTUNI_ALPHA, /* U+001b1d: BALINESE LETTER TA LATIK*/
+ RTUNI_ALPHA, /* U+001b1e: BALINESE LETTER TA MURDA MAHAPRANA*/
+ RTUNI_ALPHA, /* U+001b1f: BALINESE LETTER DA MURDA ALPAPRANA*/
+ RTUNI_ALPHA, /* U+001b20: BALINESE LETTER DA MURDA MAHAPRANA*/
+ RTUNI_ALPHA, /* U+001b21: BALINESE LETTER NA RAMBAT*/
+ RTUNI_ALPHA, /* U+001b22: BALINESE LETTER TA*/
+ RTUNI_ALPHA, /* U+001b23: BALINESE LETTER TA TAWA*/
+ RTUNI_ALPHA, /* U+001b24: BALINESE LETTER DA*/
+ RTUNI_ALPHA, /* U+001b25: BALINESE LETTER DA MADU*/
+ RTUNI_ALPHA, /* U+001b26: BALINESE LETTER NA*/
+ RTUNI_ALPHA, /* U+001b27: BALINESE LETTER PA*/
+ RTUNI_ALPHA, /* U+001b28: BALINESE LETTER PA KAPAL*/
+ RTUNI_ALPHA, /* U+001b29: BALINESE LETTER BA*/
+ RTUNI_ALPHA, /* U+001b2a: BALINESE LETTER BA KEMBANG*/
+ RTUNI_ALPHA, /* U+001b2b: BALINESE LETTER MA*/
+ RTUNI_ALPHA, /* U+001b2c: BALINESE LETTER YA*/
+ RTUNI_ALPHA, /* U+001b2d: BALINESE LETTER RA*/
+ RTUNI_ALPHA, /* U+001b2e: BALINESE LETTER LA*/
+ RTUNI_ALPHA, /* U+001b2f: BALINESE LETTER WA*/
+ RTUNI_ALPHA, /* U+001b30: BALINESE LETTER SA SAGA*/
+ RTUNI_ALPHA, /* U+001b31: BALINESE LETTER SA SAPA*/
+ RTUNI_ALPHA, /* U+001b32: BALINESE LETTER SA*/
+ RTUNI_ALPHA, /* U+001b33: BALINESE LETTER HA*/
+ 0, /* U+001b34: BALINESE SIGN REREKAN*/
+ RTUNI_ALPHA, /* U+001b35: BALINESE VOWEL SIGN TEDUNG*/
+ RTUNI_ALPHA, /* U+001b36: BALINESE VOWEL SIGN ULU*/
+ RTUNI_ALPHA, /* U+001b37: BALINESE VOWEL SIGN ULU SARI*/
+ RTUNI_ALPHA, /* U+001b38: BALINESE VOWEL SIGN SUKU*/
+ RTUNI_ALPHA, /* U+001b39: BALINESE VOWEL SIGN SUKU ILUT*/
+ RTUNI_ALPHA, /* U+001b3a: BALINESE VOWEL SIGN RA REPA*/
+ RTUNI_ALPHA, /* U+001b3b: BALINESE VOWEL SIGN RA REPA TEDUNG*/
+ RTUNI_ALPHA, /* U+001b3c: BALINESE VOWEL SIGN LA LENGA*/
+ RTUNI_ALPHA, /* U+001b3d: BALINESE VOWEL SIGN LA LENGA TEDUNG*/
+ RTUNI_ALPHA, /* U+001b3e: BALINESE VOWEL SIGN TALING*/
+ RTUNI_ALPHA, /* U+001b3f: BALINESE VOWEL SIGN TALING REPA*/
+ RTUNI_ALPHA, /* U+001b40: BALINESE VOWEL SIGN TALING TEDUNG*/
+ RTUNI_ALPHA, /* U+001b41: BALINESE VOWEL SIGN TALING REPA TEDUNG*/
+ RTUNI_ALPHA, /* U+001b42: BALINESE VOWEL SIGN PEPET*/
+ RTUNI_ALPHA, /* U+001b43: BALINESE VOWEL SIGN PEPET TEDUNG*/
+ 0, /* U+001b44: BALINESE ADEG ADEG*/
+ RTUNI_ALPHA, /* U+001b45: BALINESE LETTER KAF SASAK*/
+ RTUNI_ALPHA, /* U+001b46: BALINESE LETTER KHOT SASAK*/
+ RTUNI_ALPHA, /* U+001b47: BALINESE LETTER TZIR SASAK*/
+ RTUNI_ALPHA, /* U+001b48: BALINESE LETTER EF SASAK*/
+ RTUNI_ALPHA, /* U+001b49: BALINESE LETTER VE SASAK*/
+ RTUNI_ALPHA, /* U+001b4a: BALINESE LETTER ZAL SASAK*/
+ RTUNI_ALPHA, /* U+001b4b: BALINESE LETTER ASYURA SASAK*/
+ 0, /* U+001b4c: */
+ 0, /* U+001b4d: */
+ 0, /* U+001b4e: */
+ 0, /* U+001b4f: */
+ RTUNI_DDIGIT, /* U+001b50: BALINESE DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001b51: BALINESE DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001b52: BALINESE DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001b53: BALINESE DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+001b54: BALINESE DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+001b55: BALINESE DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+001b56: BALINESE DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+001b57: BALINESE DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+001b58: BALINESE DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+001b59: BALINESE DIGIT NINE*/
+ 0, /* U+001b5a: BALINESE PANTI*/
+ 0, /* U+001b5b: BALINESE PAMADA*/
+ 0, /* U+001b5c: BALINESE WINDU*/
+ 0, /* U+001b5d: BALINESE CARIK PAMUNGKAH*/
+ 0, /* U+001b5e: BALINESE CARIK SIKI*/
+ 0, /* U+001b5f: BALINESE CARIK PAREREN*/
+ 0, /* U+001b60: BALINESE PAMENENG*/
+ 0, /* U+001b61: BALINESE MUSICAL SYMBOL DONG*/
+ 0, /* U+001b62: BALINESE MUSICAL SYMBOL DENG*/
+ 0, /* U+001b63: BALINESE MUSICAL SYMBOL DUNG*/
+ 0, /* U+001b64: BALINESE MUSICAL SYMBOL DANG*/
+ 0, /* U+001b65: BALINESE MUSICAL SYMBOL DANG SURANG*/
+ 0, /* U+001b66: BALINESE MUSICAL SYMBOL DING*/
+ 0, /* U+001b67: BALINESE MUSICAL SYMBOL DAENG*/
+ 0, /* U+001b68: BALINESE MUSICAL SYMBOL DEUNG*/
+ 0, /* U+001b69: BALINESE MUSICAL SYMBOL DAING*/
+ 0, /* U+001b6a: BALINESE MUSICAL SYMBOL DANG GEDE*/
+ 0, /* U+001b6b: BALINESE MUSICAL SYMBOL COMBINING TEGEH*/
+ 0, /* U+001b6c: BALINESE MUSICAL SYMBOL COMBINING ENDEP*/
+ 0, /* U+001b6d: BALINESE MUSICAL SYMBOL COMBINING KEMPUL*/
+ 0, /* U+001b6e: BALINESE MUSICAL SYMBOL COMBINING KEMPLI*/
+ 0, /* U+001b6f: BALINESE MUSICAL SYMBOL COMBINING JEGOGAN*/
+ 0, /* U+001b70: BALINESE MUSICAL SYMBOL COMBINING KEMPUL WITH JEGOGAN*/
+ 0, /* U+001b71: BALINESE MUSICAL SYMBOL COMBINING KEMPLI WITH JEGOGAN*/
+ 0, /* U+001b72: BALINESE MUSICAL SYMBOL COMBINING BENDE*/
+ 0, /* U+001b73: BALINESE MUSICAL SYMBOL COMBINING GONG*/
+ 0, /* U+001b74: BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DUG*/
+ 0, /* U+001b75: BALINESE MUSICAL SYMBOL RIGHT-HAND OPEN DAG*/
+ 0, /* U+001b76: BALINESE MUSICAL SYMBOL RIGHT-HAND CLOSED TUK*/
+ 0, /* U+001b77: BALINESE MUSICAL SYMBOL RIGHT-HAND CLOSED TAK*/
+ 0, /* U+001b78: BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PANG*/
+ 0, /* U+001b79: BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PUNG*/
+ 0, /* U+001b7a: BALINESE MUSICAL SYMBOL LEFT-HAND CLOSED PLAK*/
+ 0, /* U+001b7b: BALINESE MUSICAL SYMBOL LEFT-HAND CLOSED PLUK*/
+ 0, /* U+001b7c: BALINESE MUSICAL SYMBOL LEFT-HAND OPEN PING*/
+ 0, /* U+001b7d: */
+ 0, /* U+001b7e: */
+ 0, /* U+001b7f: */
+ RTUNI_ALPHA, /* U+001b80: SUNDANESE SIGN PANYECEK*/
+ RTUNI_ALPHA, /* U+001b81: SUNDANESE SIGN PANGLAYAR*/
+ RTUNI_ALPHA, /* U+001b82: SUNDANESE SIGN PANGWISAD*/
+ RTUNI_ALPHA, /* U+001b83: SUNDANESE LETTER A*/
+ RTUNI_ALPHA, /* U+001b84: SUNDANESE LETTER I*/
+ RTUNI_ALPHA, /* U+001b85: SUNDANESE LETTER U*/
+ RTUNI_ALPHA, /* U+001b86: SUNDANESE LETTER AE*/
+ RTUNI_ALPHA, /* U+001b87: SUNDANESE LETTER O*/
+ RTUNI_ALPHA, /* U+001b88: SUNDANESE LETTER E*/
+ RTUNI_ALPHA, /* U+001b89: SUNDANESE LETTER EU*/
+ RTUNI_ALPHA, /* U+001b8a: SUNDANESE LETTER KA*/
+ RTUNI_ALPHA, /* U+001b8b: SUNDANESE LETTER QA*/
+ RTUNI_ALPHA, /* U+001b8c: SUNDANESE LETTER GA*/
+ RTUNI_ALPHA, /* U+001b8d: SUNDANESE LETTER NGA*/
+ RTUNI_ALPHA, /* U+001b8e: SUNDANESE LETTER CA*/
+ RTUNI_ALPHA, /* U+001b8f: SUNDANESE LETTER JA*/
+ RTUNI_ALPHA, /* U+001b90: SUNDANESE LETTER ZA*/
+ RTUNI_ALPHA, /* U+001b91: SUNDANESE LETTER NYA*/
+ RTUNI_ALPHA, /* U+001b92: SUNDANESE LETTER TA*/
+ RTUNI_ALPHA, /* U+001b93: SUNDANESE LETTER DA*/
+ RTUNI_ALPHA, /* U+001b94: SUNDANESE LETTER NA*/
+ RTUNI_ALPHA, /* U+001b95: SUNDANESE LETTER PA*/
+ RTUNI_ALPHA, /* U+001b96: SUNDANESE LETTER FA*/
+ RTUNI_ALPHA, /* U+001b97: SUNDANESE LETTER VA*/
+ RTUNI_ALPHA, /* U+001b98: SUNDANESE LETTER BA*/
+ RTUNI_ALPHA, /* U+001b99: SUNDANESE LETTER MA*/
+ RTUNI_ALPHA, /* U+001b9a: SUNDANESE LETTER YA*/
+ RTUNI_ALPHA, /* U+001b9b: SUNDANESE LETTER RA*/
+ RTUNI_ALPHA, /* U+001b9c: SUNDANESE LETTER LA*/
+ RTUNI_ALPHA, /* U+001b9d: SUNDANESE LETTER WA*/
+ RTUNI_ALPHA, /* U+001b9e: SUNDANESE LETTER SA*/
+ RTUNI_ALPHA, /* U+001b9f: SUNDANESE LETTER XA*/
+ RTUNI_ALPHA, /* U+001ba0: SUNDANESE LETTER HA*/
+ RTUNI_ALPHA, /* U+001ba1: SUNDANESE CONSONANT SIGN PAMINGKAL*/
+ RTUNI_ALPHA, /* U+001ba2: SUNDANESE CONSONANT SIGN PANYAKRA*/
+ RTUNI_ALPHA, /* U+001ba3: SUNDANESE CONSONANT SIGN PANYIKU*/
+ RTUNI_ALPHA, /* U+001ba4: SUNDANESE VOWEL SIGN PANGHULU*/
+ RTUNI_ALPHA, /* U+001ba5: SUNDANESE VOWEL SIGN PANYUKU*/
+ RTUNI_ALPHA, /* U+001ba6: SUNDANESE VOWEL SIGN PANAELAENG*/
+ RTUNI_ALPHA, /* U+001ba7: SUNDANESE VOWEL SIGN PANOLONG*/
+ RTUNI_ALPHA, /* U+001ba8: SUNDANESE VOWEL SIGN PAMEPET*/
+ RTUNI_ALPHA, /* U+001ba9: SUNDANESE VOWEL SIGN PANEULEUNG*/
+ 0, /* U+001baa: SUNDANESE SIGN PAMAAEH*/
+ 0, /* U+001bab: SUNDANESE SIGN VIRAMA*/
+ RTUNI_ALPHA, /* U+001bac: SUNDANESE CONSONANT SIGN PASANGAN MA*/
+ RTUNI_ALPHA, /* U+001bad: SUNDANESE CONSONANT SIGN PASANGAN WA*/
+ RTUNI_ALPHA, /* U+001bae: SUNDANESE LETTER KHA*/
+ RTUNI_ALPHA, /* U+001baf: SUNDANESE LETTER SYA*/
+ RTUNI_DDIGIT, /* U+001bb0: SUNDANESE DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001bb1: SUNDANESE DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001bb2: SUNDANESE DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001bb3: SUNDANESE DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+001bb4: SUNDANESE DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+001bb5: SUNDANESE DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+001bb6: SUNDANESE DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+001bb7: SUNDANESE DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+001bb8: SUNDANESE DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+001bb9: SUNDANESE DIGIT NINE*/
+ RTUNI_ALPHA, /* U+001bba: SUNDANESE AVAGRAHA*/
+ RTUNI_ALPHA, /* U+001bbb: SUNDANESE LETTER REU*/
+ RTUNI_ALPHA, /* U+001bbc: SUNDANESE LETTER LEU*/
+ RTUNI_ALPHA, /* U+001bbd: SUNDANESE LETTER BHA*/
+ RTUNI_ALPHA, /* U+001bbe: SUNDANESE LETTER FINAL K*/
+ RTUNI_ALPHA, /* U+001bbf: SUNDANESE LETTER FINAL M*/
+ RTUNI_ALPHA, /* U+001bc0: BATAK LETTER A*/
+ RTUNI_ALPHA, /* U+001bc1: BATAK LETTER SIMALUNGUN A*/
+ RTUNI_ALPHA, /* U+001bc2: BATAK LETTER HA*/
+ RTUNI_ALPHA, /* U+001bc3: BATAK LETTER SIMALUNGUN HA*/
+ RTUNI_ALPHA, /* U+001bc4: BATAK LETTER MANDAILING HA*/
+ RTUNI_ALPHA, /* U+001bc5: BATAK LETTER BA*/
+ RTUNI_ALPHA, /* U+001bc6: BATAK LETTER KARO BA*/
+ RTUNI_ALPHA, /* U+001bc7: BATAK LETTER PA*/
+ RTUNI_ALPHA, /* U+001bc8: BATAK LETTER SIMALUNGUN PA*/
+ RTUNI_ALPHA, /* U+001bc9: BATAK LETTER NA*/
+ RTUNI_ALPHA, /* U+001bca: BATAK LETTER MANDAILING NA*/
+ RTUNI_ALPHA, /* U+001bcb: BATAK LETTER WA*/
+ RTUNI_ALPHA, /* U+001bcc: BATAK LETTER SIMALUNGUN WA*/
+ RTUNI_ALPHA, /* U+001bcd: BATAK LETTER PAKPAK WA*/
+ RTUNI_ALPHA, /* U+001bce: BATAK LETTER GA*/
+ RTUNI_ALPHA, /* U+001bcf: BATAK LETTER SIMALUNGUN GA*/
+ RTUNI_ALPHA, /* U+001bd0: BATAK LETTER JA*/
+ RTUNI_ALPHA, /* U+001bd1: BATAK LETTER DA*/
+ RTUNI_ALPHA, /* U+001bd2: BATAK LETTER RA*/
+ RTUNI_ALPHA, /* U+001bd3: BATAK LETTER SIMALUNGUN RA*/
+ RTUNI_ALPHA, /* U+001bd4: BATAK LETTER MA*/
+ RTUNI_ALPHA, /* U+001bd5: BATAK LETTER SIMALUNGUN MA*/
+ RTUNI_ALPHA, /* U+001bd6: BATAK LETTER SOUTHERN TA*/
+ RTUNI_ALPHA, /* U+001bd7: BATAK LETTER NORTHERN TA*/
+ RTUNI_ALPHA, /* U+001bd8: BATAK LETTER SA*/
+ RTUNI_ALPHA, /* U+001bd9: BATAK LETTER SIMALUNGUN SA*/
+ RTUNI_ALPHA, /* U+001bda: BATAK LETTER MANDAILING SA*/
+ RTUNI_ALPHA, /* U+001bdb: BATAK LETTER YA*/
+ RTUNI_ALPHA, /* U+001bdc: BATAK LETTER SIMALUNGUN YA*/
+ RTUNI_ALPHA, /* U+001bdd: BATAK LETTER NGA*/
+ RTUNI_ALPHA, /* U+001bde: BATAK LETTER LA*/
+ RTUNI_ALPHA, /* U+001bdf: BATAK LETTER SIMALUNGUN LA*/
+ RTUNI_ALPHA, /* U+001be0: BATAK LETTER NYA*/
+ RTUNI_ALPHA, /* U+001be1: BATAK LETTER CA*/
+ RTUNI_ALPHA, /* U+001be2: BATAK LETTER NDA*/
+ RTUNI_ALPHA, /* U+001be3: BATAK LETTER MBA*/
+ RTUNI_ALPHA, /* U+001be4: BATAK LETTER I*/
+ RTUNI_ALPHA, /* U+001be5: BATAK LETTER U*/
+ 0, /* U+001be6: BATAK SIGN TOMPI*/
+ RTUNI_ALPHA, /* U+001be7: BATAK VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+001be8: BATAK VOWEL SIGN PAKPAK E*/
+ RTUNI_ALPHA, /* U+001be9: BATAK VOWEL SIGN EE*/
+ RTUNI_ALPHA, /* U+001bea: BATAK VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+001beb: BATAK VOWEL SIGN KARO I*/
+ RTUNI_ALPHA, /* U+001bec: BATAK VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+001bed: BATAK VOWEL SIGN KARO O*/
+ RTUNI_ALPHA, /* U+001bee: BATAK VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+001bef: BATAK VOWEL SIGN U FOR SIMALUNGUN SA*/
+ RTUNI_ALPHA, /* U+001bf0: BATAK CONSONANT SIGN NG*/
+ RTUNI_ALPHA, /* U+001bf1: BATAK CONSONANT SIGN H*/
+ 0, /* U+001bf2: BATAK PANGOLAT*/
+ 0, /* U+001bf3: BATAK PANONGONAN*/
+ 0, /* U+001bf4: */
+ 0, /* U+001bf5: */
+ 0, /* U+001bf6: */
+ 0, /* U+001bf7: */
+ 0, /* U+001bf8: */
+ 0, /* U+001bf9: */
+ 0, /* U+001bfa: */
+ 0, /* U+001bfb: */
+ 0, /* U+001bfc: BATAK SYMBOL BINDU NA METEK*/
+ 0, /* U+001bfd: BATAK SYMBOL BINDU PINARBORAS*/
+ 0, /* U+001bfe: BATAK SYMBOL BINDU JUDUL*/
+ 0, /* U+001bff: BATAK SYMBOL BINDU PANGOLAT*/
+ RTUNI_ALPHA, /* U+001c00: LEPCHA LETTER KA*/
+ RTUNI_ALPHA, /* U+001c01: LEPCHA LETTER KLA*/
+ RTUNI_ALPHA, /* U+001c02: LEPCHA LETTER KHA*/
+ RTUNI_ALPHA, /* U+001c03: LEPCHA LETTER GA*/
+ RTUNI_ALPHA, /* U+001c04: LEPCHA LETTER GLA*/
+ RTUNI_ALPHA, /* U+001c05: LEPCHA LETTER NGA*/
+ RTUNI_ALPHA, /* U+001c06: LEPCHA LETTER CA*/
+ RTUNI_ALPHA, /* U+001c07: LEPCHA LETTER CHA*/
+ RTUNI_ALPHA, /* U+001c08: LEPCHA LETTER JA*/
+ RTUNI_ALPHA, /* U+001c09: LEPCHA LETTER NYA*/
+ RTUNI_ALPHA, /* U+001c0a: LEPCHA LETTER TA*/
+ RTUNI_ALPHA, /* U+001c0b: LEPCHA LETTER THA*/
+ RTUNI_ALPHA, /* U+001c0c: LEPCHA LETTER DA*/
+ RTUNI_ALPHA, /* U+001c0d: LEPCHA LETTER NA*/
+ RTUNI_ALPHA, /* U+001c0e: LEPCHA LETTER PA*/
+ RTUNI_ALPHA, /* U+001c0f: LEPCHA LETTER PLA*/
+ RTUNI_ALPHA, /* U+001c10: LEPCHA LETTER PHA*/
+ RTUNI_ALPHA, /* U+001c11: LEPCHA LETTER FA*/
+ RTUNI_ALPHA, /* U+001c12: LEPCHA LETTER FLA*/
+ RTUNI_ALPHA, /* U+001c13: LEPCHA LETTER BA*/
+ RTUNI_ALPHA, /* U+001c14: LEPCHA LETTER BLA*/
+ RTUNI_ALPHA, /* U+001c15: LEPCHA LETTER MA*/
+ RTUNI_ALPHA, /* U+001c16: LEPCHA LETTER MLA*/
+ RTUNI_ALPHA, /* U+001c17: LEPCHA LETTER TSA*/
+ RTUNI_ALPHA, /* U+001c18: LEPCHA LETTER TSHA*/
+ RTUNI_ALPHA, /* U+001c19: LEPCHA LETTER DZA*/
+ RTUNI_ALPHA, /* U+001c1a: LEPCHA LETTER YA*/
+ RTUNI_ALPHA, /* U+001c1b: LEPCHA LETTER RA*/
+ RTUNI_ALPHA, /* U+001c1c: LEPCHA LETTER LA*/
+ RTUNI_ALPHA, /* U+001c1d: LEPCHA LETTER HA*/
+ RTUNI_ALPHA, /* U+001c1e: LEPCHA LETTER HLA*/
+ RTUNI_ALPHA, /* U+001c1f: LEPCHA LETTER VA*/
+ RTUNI_ALPHA, /* U+001c20: LEPCHA LETTER SA*/
+ RTUNI_ALPHA, /* U+001c21: LEPCHA LETTER SHA*/
+ RTUNI_ALPHA, /* U+001c22: LEPCHA LETTER WA*/
+ RTUNI_ALPHA, /* U+001c23: LEPCHA LETTER A*/
+ RTUNI_ALPHA, /* U+001c24: LEPCHA SUBJOINED LETTER YA*/
+ RTUNI_ALPHA, /* U+001c25: LEPCHA SUBJOINED LETTER RA*/
+ RTUNI_ALPHA, /* U+001c26: LEPCHA VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+001c27: LEPCHA VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+001c28: LEPCHA VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+001c29: LEPCHA VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+001c2a: LEPCHA VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+001c2b: LEPCHA VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+001c2c: LEPCHA VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+001c2d: LEPCHA CONSONANT SIGN K*/
+ RTUNI_ALPHA, /* U+001c2e: LEPCHA CONSONANT SIGN M*/
+ RTUNI_ALPHA, /* U+001c2f: LEPCHA CONSONANT SIGN L*/
+ RTUNI_ALPHA, /* U+001c30: LEPCHA CONSONANT SIGN N*/
+ RTUNI_ALPHA, /* U+001c31: LEPCHA CONSONANT SIGN P*/
+ RTUNI_ALPHA, /* U+001c32: LEPCHA CONSONANT SIGN R*/
+ RTUNI_ALPHA, /* U+001c33: LEPCHA CONSONANT SIGN T*/
+ RTUNI_ALPHA, /* U+001c34: LEPCHA CONSONANT SIGN NYIN-DO*/
+ RTUNI_ALPHA, /* U+001c35: LEPCHA CONSONANT SIGN KANG*/
+ 0, /* U+001c36: LEPCHA SIGN RAN*/
+ 0, /* U+001c37: LEPCHA SIGN NUKTA*/
+ 0, /* U+001c38: */
+ 0, /* U+001c39: */
+ 0, /* U+001c3a: */
+ 0, /* U+001c3b: LEPCHA PUNCTUATION TA-ROL*/
+ 0, /* U+001c3c: LEPCHA PUNCTUATION NYET THYOOM TA-ROL*/
+ 0, /* U+001c3d: LEPCHA PUNCTUATION CER-WA*/
+ 0, /* U+001c3e: LEPCHA PUNCTUATION TSHOOK CER-WA*/
+ 0, /* U+001c3f: LEPCHA PUNCTUATION TSHOOK*/
+ RTUNI_DDIGIT, /* U+001c40: LEPCHA DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001c41: LEPCHA DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001c42: LEPCHA DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001c43: LEPCHA DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+001c44: LEPCHA DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+001c45: LEPCHA DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+001c46: LEPCHA DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+001c47: LEPCHA DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+001c48: LEPCHA DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+001c49: LEPCHA DIGIT NINE*/
+ 0, /* U+001c4a: */
+ 0, /* U+001c4b: */
+ 0, /* U+001c4c: */
+ RTUNI_ALPHA, /* U+001c4d: LEPCHA LETTER TTA*/
+ RTUNI_ALPHA, /* U+001c4e: LEPCHA LETTER TTHA*/
+ RTUNI_ALPHA, /* U+001c4f: LEPCHA LETTER DDA*/
+ RTUNI_DDIGIT, /* U+001c50: OL CHIKI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+001c51: OL CHIKI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+001c52: OL CHIKI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+001c53: OL CHIKI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+001c54: OL CHIKI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+001c55: OL CHIKI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+001c56: OL CHIKI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+001c57: OL CHIKI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+001c58: OL CHIKI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+001c59: OL CHIKI DIGIT NINE*/
+ RTUNI_ALPHA, /* U+001c5a: OL CHIKI LETTER LA*/
+ RTUNI_ALPHA, /* U+001c5b: OL CHIKI LETTER AT*/
+ RTUNI_ALPHA, /* U+001c5c: OL CHIKI LETTER AG*/
+ RTUNI_ALPHA, /* U+001c5d: OL CHIKI LETTER ANG*/
+ RTUNI_ALPHA, /* U+001c5e: OL CHIKI LETTER AL*/
+ RTUNI_ALPHA, /* U+001c5f: OL CHIKI LETTER LAA*/
+ RTUNI_ALPHA, /* U+001c60: OL CHIKI LETTER AAK*/
+ RTUNI_ALPHA, /* U+001c61: OL CHIKI LETTER AAJ*/
+ RTUNI_ALPHA, /* U+001c62: OL CHIKI LETTER AAM*/
+ RTUNI_ALPHA, /* U+001c63: OL CHIKI LETTER AAW*/
+ RTUNI_ALPHA, /* U+001c64: OL CHIKI LETTER LI*/
+ RTUNI_ALPHA, /* U+001c65: OL CHIKI LETTER IS*/
+ RTUNI_ALPHA, /* U+001c66: OL CHIKI LETTER IH*/
+ RTUNI_ALPHA, /* U+001c67: OL CHIKI LETTER INY*/
+ RTUNI_ALPHA, /* U+001c68: OL CHIKI LETTER IR*/
+ RTUNI_ALPHA, /* U+001c69: OL CHIKI LETTER LU*/
+ RTUNI_ALPHA, /* U+001c6a: OL CHIKI LETTER UC*/
+ RTUNI_ALPHA, /* U+001c6b: OL CHIKI LETTER UD*/
+ RTUNI_ALPHA, /* U+001c6c: OL CHIKI LETTER UNN*/
+ RTUNI_ALPHA, /* U+001c6d: OL CHIKI LETTER UY*/
+ RTUNI_ALPHA, /* U+001c6e: OL CHIKI LETTER LE*/
+ RTUNI_ALPHA, /* U+001c6f: OL CHIKI LETTER EP*/
+ RTUNI_ALPHA, /* U+001c70: OL CHIKI LETTER EDD*/
+ RTUNI_ALPHA, /* U+001c71: OL CHIKI LETTER EN*/
+ RTUNI_ALPHA, /* U+001c72: OL CHIKI LETTER ERR*/
+ RTUNI_ALPHA, /* U+001c73: OL CHIKI LETTER LO*/
+ RTUNI_ALPHA, /* U+001c74: OL CHIKI LETTER OTT*/
+ RTUNI_ALPHA, /* U+001c75: OL CHIKI LETTER OB*/
+ RTUNI_ALPHA, /* U+001c76: OL CHIKI LETTER OV*/
+ RTUNI_ALPHA, /* U+001c77: OL CHIKI LETTER OH*/
+ RTUNI_ALPHA, /* U+001c78: OL CHIKI MU TTUDDAG*/
+ RTUNI_ALPHA, /* U+001c79: OL CHIKI GAAHLAA TTUDDAAG*/
+ RTUNI_ALPHA, /* U+001c7a: OL CHIKI MU-GAAHLAA TTUDDAAG*/
+ RTUNI_ALPHA, /* U+001c7b: OL CHIKI RELAA*/
+ RTUNI_ALPHA, /* U+001c7c: OL CHIKI PHAARKAA*/
+ RTUNI_ALPHA, /* U+001c7d: OL CHIKI AHAD*/
+ 0, /* U+001c7e: OL CHIKI PUNCTUATION MUCAAD*/
+ 0, /* U+001c7f: OL CHIKI PUNCTUATION DOUBLE MUCAAD*/
+ 0, /* U+001c80: */
+ 0, /* U+001c81: */
+ 0, /* U+001c82: */
+ 0, /* U+001c83: */
+ 0, /* U+001c84: */
+ 0, /* U+001c85: */
+ 0, /* U+001c86: */
+ 0, /* U+001c87: */
+ 0, /* U+001c88: */
+ 0, /* U+001c89: */
+ 0, /* U+001c8a: */
+ 0, /* U+001c8b: */
+ 0, /* U+001c8c: */
+ 0, /* U+001c8d: */
+ 0, /* U+001c8e: */
+ 0, /* U+001c8f: */
+ 0, /* U+001c90: */
+ 0, /* U+001c91: */
+ 0, /* U+001c92: */
+ 0, /* U+001c93: */
+ 0, /* U+001c94: */
+ 0, /* U+001c95: */
+ 0, /* U+001c96: */
+ 0, /* U+001c97: */
+ 0, /* U+001c98: */
+ 0, /* U+001c99: */
+ 0, /* U+001c9a: */
+ 0, /* U+001c9b: */
+ 0, /* U+001c9c: */
+ 0, /* U+001c9d: */
+ 0, /* U+001c9e: */
+ 0, /* U+001c9f: */
+ 0, /* U+001ca0: */
+ 0, /* U+001ca1: */
+ 0, /* U+001ca2: */
+ 0, /* U+001ca3: */
+ 0, /* U+001ca4: */
+ 0, /* U+001ca5: */
+ 0, /* U+001ca6: */
+ 0, /* U+001ca7: */
+ 0, /* U+001ca8: */
+ 0, /* U+001ca9: */
+ 0, /* U+001caa: */
+ 0, /* U+001cab: */
+ 0, /* U+001cac: */
+ 0, /* U+001cad: */
+ 0, /* U+001cae: */
+ 0, /* U+001caf: */
+ 0, /* U+001cb0: */
+ 0, /* U+001cb1: */
+ 0, /* U+001cb2: */
+ 0, /* U+001cb3: */
+ 0, /* U+001cb4: */
+ 0, /* U+001cb5: */
+ 0, /* U+001cb6: */
+ 0, /* U+001cb7: */
+ 0, /* U+001cb8: */
+ 0, /* U+001cb9: */
+ 0, /* U+001cba: */
+ 0, /* U+001cbb: */
+ 0, /* U+001cbc: */
+ 0, /* U+001cbd: */
+ 0, /* U+001cbe: */
+ 0, /* U+001cbf: */
+ 0, /* U+001cc0: SUNDANESE PUNCTUATION BINDU SURYA*/
+ 0, /* U+001cc1: SUNDANESE PUNCTUATION BINDU PANGLONG*/
+ 0, /* U+001cc2: SUNDANESE PUNCTUATION BINDU PURNAMA*/
+ 0, /* U+001cc3: SUNDANESE PUNCTUATION BINDU CAKRA*/
+ 0, /* U+001cc4: SUNDANESE PUNCTUATION BINDU LEU SATANGA*/
+ 0, /* U+001cc5: SUNDANESE PUNCTUATION BINDU KA SATANGA*/
+ 0, /* U+001cc6: SUNDANESE PUNCTUATION BINDU DA SATANGA*/
+ 0, /* U+001cc7: SUNDANESE PUNCTUATION BINDU BA SATANGA*/
+ 0, /* U+001cc8: */
+ 0, /* U+001cc9: */
+ 0, /* U+001cca: */
+ 0, /* U+001ccb: */
+ 0, /* U+001ccc: */
+ 0, /* U+001ccd: */
+ 0, /* U+001cce: */
+ 0, /* U+001ccf: */
+ 0, /* U+001cd0: VEDIC TONE KARSHANA*/
+ 0, /* U+001cd1: VEDIC TONE SHARA*/
+ 0, /* U+001cd2: VEDIC TONE PRENKHA*/
+ 0, /* U+001cd3: VEDIC SIGN NIHSHVASA*/
+ 0, /* U+001cd4: VEDIC SIGN YAJURVEDIC MIDLINE SVARITA*/
+ 0, /* U+001cd5: VEDIC TONE YAJURVEDIC AGGRAVATED INDEPENDENT SVARITA*/
+ 0, /* U+001cd6: VEDIC TONE YAJURVEDIC INDEPENDENT SVARITA*/
+ 0, /* U+001cd7: VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA*/
+ 0, /* U+001cd8: VEDIC TONE CANDRA BELOW*/
+ 0, /* U+001cd9: VEDIC TONE YAJURVEDIC KATHAKA INDEPENDENT SVARITA SCHROEDER*/
+ 0, /* U+001cda: VEDIC TONE DOUBLE SVARITA*/
+ 0, /* U+001cdb: VEDIC TONE TRIPLE SVARITA*/
+ 0, /* U+001cdc: VEDIC TONE KATHAKA ANUDATTA*/
+ 0, /* U+001cdd: VEDIC TONE DOT BELOW*/
+ 0, /* U+001cde: VEDIC TONE TWO DOTS BELOW*/
+ 0, /* U+001cdf: VEDIC TONE THREE DOTS BELOW*/
+ 0, /* U+001ce0: VEDIC TONE RIGVEDIC KASHMIRI INDEPENDENT SVARITA*/
+ 0, /* U+001ce1: VEDIC TONE ATHARVAVEDIC INDEPENDENT SVARITA*/
+ 0, /* U+001ce2: VEDIC SIGN VISARGA SVARITA*/
+ 0, /* U+001ce3: VEDIC SIGN VISARGA UDATTA*/
+ 0, /* U+001ce4: VEDIC SIGN REVERSED VISARGA UDATTA*/
+ 0, /* U+001ce5: VEDIC SIGN VISARGA ANUDATTA*/
+ 0, /* U+001ce6: VEDIC SIGN REVERSED VISARGA ANUDATTA*/
+ 0, /* U+001ce7: VEDIC SIGN VISARGA UDATTA WITH TAIL*/
+ 0, /* U+001ce8: VEDIC SIGN VISARGA ANUDATTA WITH TAIL*/
+ RTUNI_ALPHA, /* U+001ce9: VEDIC SIGN ANUSVARA ANTARGOMUKHA*/
+ RTUNI_ALPHA, /* U+001cea: VEDIC SIGN ANUSVARA BAHIRGOMUKHA*/
+ RTUNI_ALPHA, /* U+001ceb: VEDIC SIGN ANUSVARA VAMAGOMUKHA*/
+ RTUNI_ALPHA, /* U+001cec: VEDIC SIGN ANUSVARA VAMAGOMUKHA WITH TAIL*/
+ 0, /* U+001ced: VEDIC SIGN TIRYAK*/
+ RTUNI_ALPHA, /* U+001cee: VEDIC SIGN HEXIFORM LONG ANUSVARA*/
+ RTUNI_ALPHA, /* U+001cef: VEDIC SIGN LONG ANUSVARA*/
+ RTUNI_ALPHA, /* U+001cf0: VEDIC SIGN RTHANG LONG ANUSVARA*/
+ RTUNI_ALPHA, /* U+001cf1: VEDIC SIGN ANUSVARA UBHAYATO MUKHA*/
+ RTUNI_ALPHA, /* U+001cf2: VEDIC SIGN ARDHAVISARGA*/
+ RTUNI_ALPHA, /* U+001cf3: VEDIC SIGN ROTATED ARDHAVISARGA*/
+ 0, /* U+001cf4: VEDIC TONE CANDRA ABOVE*/
+ RTUNI_ALPHA, /* U+001cf5: VEDIC SIGN JIHVAMULIYA*/
+ RTUNI_ALPHA, /* U+001cf6: VEDIC SIGN UPADHMANIYA*/
+ 0, /* U+001cf7: */
+ 0, /* U+001cf8: */
+ 0, /* U+001cf9: */
+ 0, /* U+001cfa: */
+ 0, /* U+001cfb: */
+ 0, /* U+001cfc: */
+ 0, /* U+001cfd: */
+ 0, /* U+001cfe: */
+ 0, /* U+001cff: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d00: LATIN LETTER SMALL CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d01: LATIN LETTER SMALL CAPITAL AE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d02: LATIN SMALL LETTER TURNED AE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d03: LATIN LETTER SMALL CAPITAL BARRED B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d04: LATIN LETTER SMALL CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d05: LATIN LETTER SMALL CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d06: LATIN LETTER SMALL CAPITAL ETH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d07: LATIN LETTER SMALL CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d08: LATIN SMALL LETTER TURNED OPEN E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d09: LATIN SMALL LETTER TURNED I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d0a: LATIN LETTER SMALL CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d0b: LATIN LETTER SMALL CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d0c: LATIN LETTER SMALL CAPITAL L WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d0d: LATIN LETTER SMALL CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d0e: LATIN LETTER SMALL CAPITAL REVERSED N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d0f: LATIN LETTER SMALL CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d10: LATIN LETTER SMALL CAPITAL OPEN O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d11: LATIN SMALL LETTER SIDEWAYS O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d12: LATIN SMALL LETTER SIDEWAYS OPEN O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d13: LATIN SMALL LETTER SIDEWAYS O WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d14: LATIN SMALL LETTER TURNED OE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d15: LATIN LETTER SMALL CAPITAL OU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d16: LATIN SMALL LETTER TOP HALF O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d17: LATIN SMALL LETTER BOTTOM HALF O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d18: LATIN LETTER SMALL CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d19: LATIN LETTER SMALL CAPITAL REVERSED R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d1a: LATIN LETTER SMALL CAPITAL TURNED R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d1b: LATIN LETTER SMALL CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d1c: LATIN LETTER SMALL CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d1d: LATIN SMALL LETTER SIDEWAYS U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d1e: LATIN SMALL LETTER SIDEWAYS DIAERESIZED U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d1f: LATIN SMALL LETTER SIDEWAYS TURNED M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d20: LATIN LETTER SMALL CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d21: LATIN LETTER SMALL CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d22: LATIN LETTER SMALL CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d23: LATIN LETTER SMALL CAPITAL EZH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d24: LATIN LETTER VOICED LARYNGEAL SPIRANT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d25: LATIN LETTER AIN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d26: GREEK LETTER SMALL CAPITAL GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d27: GREEK LETTER SMALL CAPITAL LAMDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d28: GREEK LETTER SMALL CAPITAL PI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d29: GREEK LETTER SMALL CAPITAL RHO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d2a: GREEK LETTER SMALL CAPITAL PSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d2b: CYRILLIC LETTER SMALL CAPITAL EL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d2c: MODIFIER LETTER CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d2d: MODIFIER LETTER CAPITAL AE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d2e: MODIFIER LETTER CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d2f: MODIFIER LETTER CAPITAL BARRED B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d30: MODIFIER LETTER CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d31: MODIFIER LETTER CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d32: MODIFIER LETTER CAPITAL REVERSED E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d33: MODIFIER LETTER CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d34: MODIFIER LETTER CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d35: MODIFIER LETTER CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d36: MODIFIER LETTER CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d37: MODIFIER LETTER CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d38: MODIFIER LETTER CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d39: MODIFIER LETTER CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d3a: MODIFIER LETTER CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d3b: MODIFIER LETTER CAPITAL REVERSED N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d3c: MODIFIER LETTER CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d3d: MODIFIER LETTER CAPITAL OU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d3e: MODIFIER LETTER CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d3f: MODIFIER LETTER CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d40: MODIFIER LETTER CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d41: MODIFIER LETTER CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d42: MODIFIER LETTER CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d43: MODIFIER LETTER SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d44: MODIFIER LETTER SMALL TURNED A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d45: MODIFIER LETTER SMALL ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d46: MODIFIER LETTER SMALL TURNED AE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d47: MODIFIER LETTER SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d48: MODIFIER LETTER SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d49: MODIFIER LETTER SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d4a: MODIFIER LETTER SMALL SCHWA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d4b: MODIFIER LETTER SMALL OPEN E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d4c: MODIFIER LETTER SMALL TURNED OPEN E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d4d: MODIFIER LETTER SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d4e: MODIFIER LETTER SMALL TURNED I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d4f: MODIFIER LETTER SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d50: MODIFIER LETTER SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d51: MODIFIER LETTER SMALL ENG*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d52: MODIFIER LETTER SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d53: MODIFIER LETTER SMALL OPEN O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d54: MODIFIER LETTER SMALL TOP HALF O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d55: MODIFIER LETTER SMALL BOTTOM HALF O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d56: MODIFIER LETTER SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d57: MODIFIER LETTER SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d58: MODIFIER LETTER SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d59: MODIFIER LETTER SMALL SIDEWAYS U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d5a: MODIFIER LETTER SMALL TURNED M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d5b: MODIFIER LETTER SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d5c: MODIFIER LETTER SMALL AIN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d5d: MODIFIER LETTER SMALL BETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d5e: MODIFIER LETTER SMALL GREEK GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d5f: MODIFIER LETTER SMALL DELTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d60: MODIFIER LETTER SMALL GREEK PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d61: MODIFIER LETTER SMALL CHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d62: LATIN SUBSCRIPT SMALL LETTER I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d63: LATIN SUBSCRIPT SMALL LETTER R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d64: LATIN SUBSCRIPT SMALL LETTER U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d65: LATIN SUBSCRIPT SMALL LETTER V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d66: GREEK SUBSCRIPT SMALL LETTER BETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d67: GREEK SUBSCRIPT SMALL LETTER GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d68: GREEK SUBSCRIPT SMALL LETTER RHO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d69: GREEK SUBSCRIPT SMALL LETTER PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d6a: GREEK SUBSCRIPT SMALL LETTER CHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d6b: LATIN SMALL LETTER UE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d6c: LATIN SMALL LETTER B WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d6d: LATIN SMALL LETTER D WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d6e: LATIN SMALL LETTER F WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d6f: LATIN SMALL LETTER M WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d70: LATIN SMALL LETTER N WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d71: LATIN SMALL LETTER P WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d72: LATIN SMALL LETTER R WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d73: LATIN SMALL LETTER R WITH FISHHOOK AND MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d74: LATIN SMALL LETTER S WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d75: LATIN SMALL LETTER T WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d76: LATIN SMALL LETTER Z WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d77: LATIN SMALL LETTER TURNED G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d78: MODIFIER LETTER CYRILLIC EN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d79: LATIN SMALL LETTER INSULAR G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d7a: LATIN SMALL LETTER TH WITH STRIKETHROUGH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d7b: LATIN SMALL CAPITAL LETTER I WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d7c: LATIN SMALL LETTER IOTA WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d7d: LATIN SMALL LETTER P WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d7e: LATIN SMALL CAPITAL LETTER U WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d7f: LATIN SMALL LETTER UPSILON WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d80: LATIN SMALL LETTER B WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d81: LATIN SMALL LETTER D WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d82: LATIN SMALL LETTER F WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d83: LATIN SMALL LETTER G WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d84: LATIN SMALL LETTER K WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d85: LATIN SMALL LETTER L WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d86: LATIN SMALL LETTER M WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d87: LATIN SMALL LETTER N WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d88: LATIN SMALL LETTER P WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d89: LATIN SMALL LETTER R WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d8a: LATIN SMALL LETTER S WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d8b: LATIN SMALL LETTER ESH WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d8c: LATIN SMALL LETTER V WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d8d: LATIN SMALL LETTER X WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d8e: LATIN SMALL LETTER Z WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d8f: LATIN SMALL LETTER A WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d90: LATIN SMALL LETTER ALPHA WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d91: LATIN SMALL LETTER D WITH HOOK AND TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d92: LATIN SMALL LETTER E WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d93: LATIN SMALL LETTER OPEN E WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d94: LATIN SMALL LETTER REVERSED OPEN E WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d95: LATIN SMALL LETTER SCHWA WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d96: LATIN SMALL LETTER I WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d97: LATIN SMALL LETTER OPEN O WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d98: LATIN SMALL LETTER ESH WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d99: LATIN SMALL LETTER U WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d9a: LATIN SMALL LETTER EZH WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d9b: MODIFIER LETTER SMALL TURNED ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d9c: MODIFIER LETTER SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d9d: MODIFIER LETTER SMALL C WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d9e: MODIFIER LETTER SMALL ETH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001d9f: MODIFIER LETTER SMALL REVERSED OPEN E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da0: MODIFIER LETTER SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da1: MODIFIER LETTER SMALL DOTLESS J WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da2: MODIFIER LETTER SMALL SCRIPT G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da3: MODIFIER LETTER SMALL TURNED H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da4: MODIFIER LETTER SMALL I WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da5: MODIFIER LETTER SMALL IOTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da6: MODIFIER LETTER SMALL CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da7: MODIFIER LETTER SMALL CAPITAL I WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da8: MODIFIER LETTER SMALL J WITH CROSSED-TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001da9: MODIFIER LETTER SMALL L WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001daa: MODIFIER LETTER SMALL L WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dab: MODIFIER LETTER SMALL CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dac: MODIFIER LETTER SMALL M WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dad: MODIFIER LETTER SMALL TURNED M WITH LONG LEG*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dae: MODIFIER LETTER SMALL N WITH LEFT HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001daf: MODIFIER LETTER SMALL N WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db0: MODIFIER LETTER SMALL CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db1: MODIFIER LETTER SMALL BARRED O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db2: MODIFIER LETTER SMALL PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db3: MODIFIER LETTER SMALL S WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db4: MODIFIER LETTER SMALL ESH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db5: MODIFIER LETTER SMALL T WITH PALATAL HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db6: MODIFIER LETTER SMALL U BAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db7: MODIFIER LETTER SMALL UPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db8: MODIFIER LETTER SMALL CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001db9: MODIFIER LETTER SMALL V WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dba: MODIFIER LETTER SMALL TURNED V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dbb: MODIFIER LETTER SMALL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dbc: MODIFIER LETTER SMALL Z WITH RETROFLEX HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dbd: MODIFIER LETTER SMALL Z WITH CURL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dbe: MODIFIER LETTER SMALL EZH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001dbf: MODIFIER LETTER SMALL THETA*/
+ 0, /* U+001dc0: COMBINING DOTTED GRAVE ACCENT*/
+ 0, /* U+001dc1: COMBINING DOTTED ACUTE ACCENT*/
+ 0, /* U+001dc2: COMBINING SNAKE BELOW*/
+ 0, /* U+001dc3: COMBINING SUSPENSION MARK*/
+ 0, /* U+001dc4: COMBINING MACRON-ACUTE*/
+ 0, /* U+001dc5: COMBINING GRAVE-MACRON*/
+ 0, /* U+001dc6: COMBINING MACRON-GRAVE*/
+ 0, /* U+001dc7: COMBINING ACUTE-MACRON*/
+ 0, /* U+001dc8: COMBINING GRAVE-ACUTE-GRAVE*/
+ 0, /* U+001dc9: COMBINING ACUTE-GRAVE-ACUTE*/
+ 0, /* U+001dca: COMBINING LATIN SMALL LETTER R BELOW*/
+ 0, /* U+001dcb: COMBINING BREVE-MACRON*/
+ 0, /* U+001dcc: COMBINING MACRON-BREVE*/
+ 0, /* U+001dcd: COMBINING DOUBLE CIRCUMFLEX ABOVE*/
+ 0, /* U+001dce: COMBINING OGONEK ABOVE*/
+ 0, /* U+001dcf: COMBINING ZIGZAG BELOW*/
+ 0, /* U+001dd0: COMBINING IS BELOW*/
+ 0, /* U+001dd1: COMBINING UR ABOVE*/
+ 0, /* U+001dd2: COMBINING US ABOVE*/
+ 0, /* U+001dd3: COMBINING LATIN SMALL LETTER FLATTENED OPEN A ABOVE*/
+ 0, /* U+001dd4: COMBINING LATIN SMALL LETTER AE*/
+ 0, /* U+001dd5: COMBINING LATIN SMALL LETTER AO*/
+ 0, /* U+001dd6: COMBINING LATIN SMALL LETTER AV*/
+ 0, /* U+001dd7: COMBINING LATIN SMALL LETTER C CEDILLA*/
+ 0, /* U+001dd8: COMBINING LATIN SMALL LETTER INSULAR D*/
+ 0, /* U+001dd9: COMBINING LATIN SMALL LETTER ETH*/
+ 0, /* U+001dda: COMBINING LATIN SMALL LETTER G*/
+ 0, /* U+001ddb: COMBINING LATIN LETTER SMALL CAPITAL G*/
+ 0, /* U+001ddc: COMBINING LATIN SMALL LETTER K*/
+ 0, /* U+001ddd: COMBINING LATIN SMALL LETTER L*/
+ 0, /* U+001dde: COMBINING LATIN LETTER SMALL CAPITAL L*/
+ 0, /* U+001ddf: COMBINING LATIN LETTER SMALL CAPITAL M*/
+ 0, /* U+001de0: COMBINING LATIN SMALL LETTER N*/
+ 0, /* U+001de1: COMBINING LATIN LETTER SMALL CAPITAL N*/
+ 0, /* U+001de2: COMBINING LATIN LETTER SMALL CAPITAL R*/
+ 0, /* U+001de3: COMBINING LATIN SMALL LETTER R ROTUNDA*/
+ 0, /* U+001de4: COMBINING LATIN SMALL LETTER S*/
+ 0, /* U+001de5: COMBINING LATIN SMALL LETTER LONG S*/
+ 0, /* U+001de6: COMBINING LATIN SMALL LETTER Z*/
+ 0, /* U+001de7: */
+ 0, /* U+001de8: */
+ 0, /* U+001de9: */
+ 0, /* U+001dea: */
+ 0, /* U+001deb: */
+ 0, /* U+001dec: */
+ 0, /* U+001ded: */
+ 0, /* U+001dee: */
+ 0, /* U+001def: */
+ 0, /* U+001df0: */
+ 0, /* U+001df1: */
+ 0, /* U+001df2: */
+ 0, /* U+001df3: */
+ 0, /* U+001df4: */
+ 0, /* U+001df5: */
+ 0, /* U+001df6: */
+ 0, /* U+001df7: */
+ 0, /* U+001df8: */
+ 0, /* U+001df9: */
+ 0, /* U+001dfa: */
+ 0, /* U+001dfb: */
+ 0, /* U+001dfc: COMBINING DOUBLE INVERTED BREVE BELOW*/
+ 0, /* U+001dfd: COMBINING ALMOST EQUAL TO BELOW*/
+ 0, /* U+001dfe: COMBINING LEFT ARROWHEAD ABOVE*/
+ 0, /* U+001dff: COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e00: LATIN CAPITAL LETTER A WITH RING BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e01: LATIN SMALL LETTER A WITH RING BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e02: LATIN CAPITAL LETTER B WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e03: LATIN SMALL LETTER B WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e04: LATIN CAPITAL LETTER B WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e05: LATIN SMALL LETTER B WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e06: LATIN CAPITAL LETTER B WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e07: LATIN SMALL LETTER B WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e08: LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e09: LATIN SMALL LETTER C WITH CEDILLA AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e0a: LATIN CAPITAL LETTER D WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e0b: LATIN SMALL LETTER D WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e0c: LATIN CAPITAL LETTER D WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e0d: LATIN SMALL LETTER D WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e0e: LATIN CAPITAL LETTER D WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e0f: LATIN SMALL LETTER D WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e10: LATIN CAPITAL LETTER D WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e11: LATIN SMALL LETTER D WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e12: LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e13: LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e14: LATIN CAPITAL LETTER E WITH MACRON AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e15: LATIN SMALL LETTER E WITH MACRON AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e16: LATIN CAPITAL LETTER E WITH MACRON AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e17: LATIN SMALL LETTER E WITH MACRON AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e18: LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e19: LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e1a: LATIN CAPITAL LETTER E WITH TILDE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e1b: LATIN SMALL LETTER E WITH TILDE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e1c: LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e1d: LATIN SMALL LETTER E WITH CEDILLA AND BREVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e1e: LATIN CAPITAL LETTER F WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e1f: LATIN SMALL LETTER F WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e20: LATIN CAPITAL LETTER G WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e21: LATIN SMALL LETTER G WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e22: LATIN CAPITAL LETTER H WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e23: LATIN SMALL LETTER H WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e24: LATIN CAPITAL LETTER H WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e25: LATIN SMALL LETTER H WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e26: LATIN CAPITAL LETTER H WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e27: LATIN SMALL LETTER H WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e28: LATIN CAPITAL LETTER H WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e29: LATIN SMALL LETTER H WITH CEDILLA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e2a: LATIN CAPITAL LETTER H WITH BREVE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e2b: LATIN SMALL LETTER H WITH BREVE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e2c: LATIN CAPITAL LETTER I WITH TILDE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e2d: LATIN SMALL LETTER I WITH TILDE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e2e: LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e2f: LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e30: LATIN CAPITAL LETTER K WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e31: LATIN SMALL LETTER K WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e32: LATIN CAPITAL LETTER K WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e33: LATIN SMALL LETTER K WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e34: LATIN CAPITAL LETTER K WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e35: LATIN SMALL LETTER K WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e36: LATIN CAPITAL LETTER L WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e37: LATIN SMALL LETTER L WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e38: LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e39: LATIN SMALL LETTER L WITH DOT BELOW AND MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e3a: LATIN CAPITAL LETTER L WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e3b: LATIN SMALL LETTER L WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e3c: LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e3d: LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e3e: LATIN CAPITAL LETTER M WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e3f: LATIN SMALL LETTER M WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e40: LATIN CAPITAL LETTER M WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e41: LATIN SMALL LETTER M WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e42: LATIN CAPITAL LETTER M WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e43: LATIN SMALL LETTER M WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e44: LATIN CAPITAL LETTER N WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e45: LATIN SMALL LETTER N WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e46: LATIN CAPITAL LETTER N WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e47: LATIN SMALL LETTER N WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e48: LATIN CAPITAL LETTER N WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e49: LATIN SMALL LETTER N WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e4a: LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e4b: LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e4c: LATIN CAPITAL LETTER O WITH TILDE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e4d: LATIN SMALL LETTER O WITH TILDE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e4e: LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e4f: LATIN SMALL LETTER O WITH TILDE AND DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e50: LATIN CAPITAL LETTER O WITH MACRON AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e51: LATIN SMALL LETTER O WITH MACRON AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e52: LATIN CAPITAL LETTER O WITH MACRON AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e53: LATIN SMALL LETTER O WITH MACRON AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e54: LATIN CAPITAL LETTER P WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e55: LATIN SMALL LETTER P WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e56: LATIN CAPITAL LETTER P WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e57: LATIN SMALL LETTER P WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e58: LATIN CAPITAL LETTER R WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e59: LATIN SMALL LETTER R WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e5a: LATIN CAPITAL LETTER R WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e5b: LATIN SMALL LETTER R WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e5c: LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e5d: LATIN SMALL LETTER R WITH DOT BELOW AND MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e5e: LATIN CAPITAL LETTER R WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e5f: LATIN SMALL LETTER R WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e60: LATIN CAPITAL LETTER S WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e61: LATIN SMALL LETTER S WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e62: LATIN CAPITAL LETTER S WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e63: LATIN SMALL LETTER S WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e64: LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e65: LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e66: LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e67: LATIN SMALL LETTER S WITH CARON AND DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e68: LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e69: LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e6a: LATIN CAPITAL LETTER T WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e6b: LATIN SMALL LETTER T WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e6c: LATIN CAPITAL LETTER T WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e6d: LATIN SMALL LETTER T WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e6e: LATIN CAPITAL LETTER T WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e6f: LATIN SMALL LETTER T WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e70: LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e71: LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e72: LATIN CAPITAL LETTER U WITH DIAERESIS BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e73: LATIN SMALL LETTER U WITH DIAERESIS BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e74: LATIN CAPITAL LETTER U WITH TILDE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e75: LATIN SMALL LETTER U WITH TILDE BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e76: LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e77: LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e78: LATIN CAPITAL LETTER U WITH TILDE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e79: LATIN SMALL LETTER U WITH TILDE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e7a: LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e7b: LATIN SMALL LETTER U WITH MACRON AND DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e7c: LATIN CAPITAL LETTER V WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e7d: LATIN SMALL LETTER V WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e7e: LATIN CAPITAL LETTER V WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e7f: LATIN SMALL LETTER V WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e80: LATIN CAPITAL LETTER W WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e81: LATIN SMALL LETTER W WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e82: LATIN CAPITAL LETTER W WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e83: LATIN SMALL LETTER W WITH ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e84: LATIN CAPITAL LETTER W WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e85: LATIN SMALL LETTER W WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e86: LATIN CAPITAL LETTER W WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e87: LATIN SMALL LETTER W WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e88: LATIN CAPITAL LETTER W WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e89: LATIN SMALL LETTER W WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e8a: LATIN CAPITAL LETTER X WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e8b: LATIN SMALL LETTER X WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e8c: LATIN CAPITAL LETTER X WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e8d: LATIN SMALL LETTER X WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e8e: LATIN CAPITAL LETTER Y WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e8f: LATIN SMALL LETTER Y WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e90: LATIN CAPITAL LETTER Z WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e91: LATIN SMALL LETTER Z WITH CIRCUMFLEX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e92: LATIN CAPITAL LETTER Z WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e93: LATIN SMALL LETTER Z WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e94: LATIN CAPITAL LETTER Z WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e95: LATIN SMALL LETTER Z WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e96: LATIN SMALL LETTER H WITH LINE BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e97: LATIN SMALL LETTER T WITH DIAERESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e98: LATIN SMALL LETTER W WITH RING ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e99: LATIN SMALL LETTER Y WITH RING ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e9a: LATIN SMALL LETTER A WITH RIGHT HALF RING*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e9b: LATIN SMALL LETTER LONG S WITH DOT ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e9c: LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e9d: LATIN SMALL LETTER LONG S WITH HIGH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001e9e: LATIN CAPITAL LETTER SHARP S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001e9f: LATIN SMALL LETTER DELTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ea0: LATIN CAPITAL LETTER A WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ea1: LATIN SMALL LETTER A WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ea2: LATIN CAPITAL LETTER A WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ea3: LATIN SMALL LETTER A WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ea4: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ea5: LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ea6: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ea7: LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ea8: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ea9: LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eaa: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eab: LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eac: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ead: LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eae: LATIN CAPITAL LETTER A WITH BREVE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eaf: LATIN SMALL LETTER A WITH BREVE AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eb0: LATIN CAPITAL LETTER A WITH BREVE AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eb1: LATIN SMALL LETTER A WITH BREVE AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eb2: LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eb3: LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eb4: LATIN CAPITAL LETTER A WITH BREVE AND TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eb5: LATIN SMALL LETTER A WITH BREVE AND TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eb6: LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eb7: LATIN SMALL LETTER A WITH BREVE AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eb8: LATIN CAPITAL LETTER E WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eb9: LATIN SMALL LETTER E WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eba: LATIN CAPITAL LETTER E WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ebb: LATIN SMALL LETTER E WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ebc: LATIN CAPITAL LETTER E WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ebd: LATIN SMALL LETTER E WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ebe: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ebf: LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ec0: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ec1: LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ec2: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ec3: LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ec4: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ec5: LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ec6: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ec7: LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ec8: LATIN CAPITAL LETTER I WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ec9: LATIN SMALL LETTER I WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eca: LATIN CAPITAL LETTER I WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ecb: LATIN SMALL LETTER I WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ecc: LATIN CAPITAL LETTER O WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ecd: LATIN SMALL LETTER O WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ece: LATIN CAPITAL LETTER O WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ecf: LATIN SMALL LETTER O WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ed0: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ed1: LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ed2: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ed3: LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ed4: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ed5: LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ed6: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ed7: LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ed8: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ed9: LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eda: LATIN CAPITAL LETTER O WITH HORN AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001edb: LATIN SMALL LETTER O WITH HORN AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001edc: LATIN CAPITAL LETTER O WITH HORN AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001edd: LATIN SMALL LETTER O WITH HORN AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ede: LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001edf: LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ee0: LATIN CAPITAL LETTER O WITH HORN AND TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ee1: LATIN SMALL LETTER O WITH HORN AND TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ee2: LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ee3: LATIN SMALL LETTER O WITH HORN AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ee4: LATIN CAPITAL LETTER U WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ee5: LATIN SMALL LETTER U WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ee6: LATIN CAPITAL LETTER U WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ee7: LATIN SMALL LETTER U WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ee8: LATIN CAPITAL LETTER U WITH HORN AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ee9: LATIN SMALL LETTER U WITH HORN AND ACUTE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eea: LATIN CAPITAL LETTER U WITH HORN AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eeb: LATIN SMALL LETTER U WITH HORN AND GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eec: LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eed: LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001eee: LATIN CAPITAL LETTER U WITH HORN AND TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eef: LATIN SMALL LETTER U WITH HORN AND TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ef0: LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ef1: LATIN SMALL LETTER U WITH HORN AND DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ef2: LATIN CAPITAL LETTER Y WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ef3: LATIN SMALL LETTER Y WITH GRAVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ef4: LATIN CAPITAL LETTER Y WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ef5: LATIN SMALL LETTER Y WITH DOT BELOW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ef6: LATIN CAPITAL LETTER Y WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ef7: LATIN SMALL LETTER Y WITH HOOK ABOVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ef8: LATIN CAPITAL LETTER Y WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ef9: LATIN SMALL LETTER Y WITH TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001efa: LATIN CAPITAL LETTER MIDDLE-WELSH LL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001efb: LATIN SMALL LETTER MIDDLE-WELSH LL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001efc: LATIN CAPITAL LETTER MIDDLE-WELSH V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001efd: LATIN SMALL LETTER MIDDLE-WELSH V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001efe: LATIN CAPITAL LETTER Y WITH LOOP*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001eff: LATIN SMALL LETTER Y WITH LOOP*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f00: GREEK SMALL LETTER ALPHA WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f01: GREEK SMALL LETTER ALPHA WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f02: GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f03: GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f04: GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f05: GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f06: GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f07: GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f08: GREEK CAPITAL LETTER ALPHA WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f09: GREEK CAPITAL LETTER ALPHA WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f0a: GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f0b: GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f0c: GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f0d: GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f0e: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f0f: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f10: GREEK SMALL LETTER EPSILON WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f11: GREEK SMALL LETTER EPSILON WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f12: GREEK SMALL LETTER EPSILON WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f13: GREEK SMALL LETTER EPSILON WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f14: GREEK SMALL LETTER EPSILON WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f15: GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA*/
+ 0, /* U+001f16: */
+ 0, /* U+001f17: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f18: GREEK CAPITAL LETTER EPSILON WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f19: GREEK CAPITAL LETTER EPSILON WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f1a: GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f1b: GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f1c: GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f1d: GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA*/
+ 0, /* U+001f1e: */
+ 0, /* U+001f1f: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f20: GREEK SMALL LETTER ETA WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f21: GREEK SMALL LETTER ETA WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f22: GREEK SMALL LETTER ETA WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f23: GREEK SMALL LETTER ETA WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f24: GREEK SMALL LETTER ETA WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f25: GREEK SMALL LETTER ETA WITH DASIA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f26: GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f27: GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f28: GREEK CAPITAL LETTER ETA WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f29: GREEK CAPITAL LETTER ETA WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f2a: GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f2b: GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f2c: GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f2d: GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f2e: GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f2f: GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f30: GREEK SMALL LETTER IOTA WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f31: GREEK SMALL LETTER IOTA WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f32: GREEK SMALL LETTER IOTA WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f33: GREEK SMALL LETTER IOTA WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f34: GREEK SMALL LETTER IOTA WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f35: GREEK SMALL LETTER IOTA WITH DASIA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f36: GREEK SMALL LETTER IOTA WITH PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f37: GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f38: GREEK CAPITAL LETTER IOTA WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f39: GREEK CAPITAL LETTER IOTA WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f3a: GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f3b: GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f3c: GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f3d: GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f3e: GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f3f: GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f40: GREEK SMALL LETTER OMICRON WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f41: GREEK SMALL LETTER OMICRON WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f42: GREEK SMALL LETTER OMICRON WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f43: GREEK SMALL LETTER OMICRON WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f44: GREEK SMALL LETTER OMICRON WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f45: GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA*/
+ 0, /* U+001f46: */
+ 0, /* U+001f47: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f48: GREEK CAPITAL LETTER OMICRON WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f49: GREEK CAPITAL LETTER OMICRON WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f4a: GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f4b: GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f4c: GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f4d: GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA*/
+ 0, /* U+001f4e: */
+ 0, /* U+001f4f: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f50: GREEK SMALL LETTER UPSILON WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f51: GREEK SMALL LETTER UPSILON WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f52: GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f53: GREEK SMALL LETTER UPSILON WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f54: GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f55: GREEK SMALL LETTER UPSILON WITH DASIA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f56: GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f57: GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI*/
+ 0, /* U+001f58: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f59: GREEK CAPITAL LETTER UPSILON WITH DASIA*/
+ 0, /* U+001f5a: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f5b: GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA*/
+ 0, /* U+001f5c: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f5d: GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA*/
+ 0, /* U+001f5e: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f5f: GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f60: GREEK SMALL LETTER OMEGA WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f61: GREEK SMALL LETTER OMEGA WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f62: GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f63: GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f64: GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f65: GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f66: GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f67: GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f68: GREEK CAPITAL LETTER OMEGA WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f69: GREEK CAPITAL LETTER OMEGA WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f6a: GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f6b: GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f6c: GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f6d: GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f6e: GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001f6f: GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f70: GREEK SMALL LETTER ALPHA WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f71: GREEK SMALL LETTER ALPHA WITH OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f72: GREEK SMALL LETTER EPSILON WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f73: GREEK SMALL LETTER EPSILON WITH OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f74: GREEK SMALL LETTER ETA WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f75: GREEK SMALL LETTER ETA WITH OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f76: GREEK SMALL LETTER IOTA WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f77: GREEK SMALL LETTER IOTA WITH OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f78: GREEK SMALL LETTER OMICRON WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f79: GREEK SMALL LETTER OMICRON WITH OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f7a: GREEK SMALL LETTER UPSILON WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f7b: GREEK SMALL LETTER UPSILON WITH OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f7c: GREEK SMALL LETTER OMEGA WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f7d: GREEK SMALL LETTER OMEGA WITH OXIA*/
+ 0, /* U+001f7e: */
+ 0, /* U+001f7f: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f80: GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f81: GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f82: GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f83: GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f84: GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f85: GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f86: GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f87: GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f88: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f89: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f8a: GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f8b: GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f8c: GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f8d: GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f8e: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f8f: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f90: GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f91: GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f92: GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f93: GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f94: GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f95: GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f96: GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001f97: GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f98: GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f99: GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f9a: GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f9b: GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f9c: GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f9d: GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f9e: GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001f9f: GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fa0: GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fa1: GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fa2: GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fa3: GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fa4: GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fa5: GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fa6: GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fa7: GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001fa8: GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001fa9: GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001faa: GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001fab: GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001fac: GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001fad: GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001fae: GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA, /* U+001faf: GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fb0: GREEK SMALL LETTER ALPHA WITH VRACHY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fb1: GREEK SMALL LETTER ALPHA WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fb2: GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fb3: GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fb4: GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI*/
+ 0, /* U+001fb5: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fb6: GREEK SMALL LETTER ALPHA WITH PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fb7: GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fb8: GREEK CAPITAL LETTER ALPHA WITH VRACHY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fb9: GREEK CAPITAL LETTER ALPHA WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fba: GREEK CAPITAL LETTER ALPHA WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fbb: GREEK CAPITAL LETTER ALPHA WITH OXIA*/
+ RTUNI_ALPHA, /* U+001fbc: GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI*/
+ 0, /* U+001fbd: GREEK KORONIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fbe: GREEK PROSGEGRAMMENI*/
+ 0, /* U+001fbf: GREEK PSILI*/
+ 0, /* U+001fc0: GREEK PERISPOMENI*/
+ 0, /* U+001fc1: GREEK DIALYTIKA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fc2: GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fc3: GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fc4: GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI*/
+ 0, /* U+001fc5: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fc6: GREEK SMALL LETTER ETA WITH PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fc7: GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fc8: GREEK CAPITAL LETTER EPSILON WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fc9: GREEK CAPITAL LETTER EPSILON WITH OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fca: GREEK CAPITAL LETTER ETA WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fcb: GREEK CAPITAL LETTER ETA WITH OXIA*/
+ RTUNI_ALPHA, /* U+001fcc: GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI*/
+ 0, /* U+001fcd: GREEK PSILI AND VARIA*/
+ 0, /* U+001fce: GREEK PSILI AND OXIA*/
+ 0, /* U+001fcf: GREEK PSILI AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fd0: GREEK SMALL LETTER IOTA WITH VRACHY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fd1: GREEK SMALL LETTER IOTA WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fd2: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fd3: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA*/
+ 0, /* U+001fd4: */
+ 0, /* U+001fd5: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fd6: GREEK SMALL LETTER IOTA WITH PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fd7: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fd8: GREEK CAPITAL LETTER IOTA WITH VRACHY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fd9: GREEK CAPITAL LETTER IOTA WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fda: GREEK CAPITAL LETTER IOTA WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fdb: GREEK CAPITAL LETTER IOTA WITH OXIA*/
+ 0, /* U+001fdc: */
+ 0, /* U+001fdd: GREEK DASIA AND VARIA*/
+ 0, /* U+001fde: GREEK DASIA AND OXIA*/
+ 0, /* U+001fdf: GREEK DASIA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fe0: GREEK SMALL LETTER UPSILON WITH VRACHY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fe1: GREEK SMALL LETTER UPSILON WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fe2: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fe3: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fe4: GREEK SMALL LETTER RHO WITH PSILI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fe5: GREEK SMALL LETTER RHO WITH DASIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fe6: GREEK SMALL LETTER UPSILON WITH PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001fe7: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fe8: GREEK CAPITAL LETTER UPSILON WITH VRACHY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fe9: GREEK CAPITAL LETTER UPSILON WITH MACRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fea: GREEK CAPITAL LETTER UPSILON WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001feb: GREEK CAPITAL LETTER UPSILON WITH OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001fec: GREEK CAPITAL LETTER RHO WITH DASIA*/
+ 0, /* U+001fed: GREEK DIALYTIKA AND VARIA*/
+ 0, /* U+001fee: GREEK DIALYTIKA AND OXIA*/
+ 0, /* U+001fef: GREEK VARIA*/
+ 0, /* U+001ff0: */
+ 0, /* U+001ff1: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ff2: GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ff3: GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ff4: GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI*/
+ 0, /* U+001ff5: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ff6: GREEK SMALL LETTER OMEGA WITH PERISPOMENI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+001ff7: GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ff8: GREEK CAPITAL LETTER OMICRON WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ff9: GREEK CAPITAL LETTER OMICRON WITH OXIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ffa: GREEK CAPITAL LETTER OMEGA WITH VARIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+001ffb: GREEK CAPITAL LETTER OMEGA WITH OXIA*/
+ RTUNI_ALPHA, /* U+001ffc: GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI*/
+ 0, /* U+001ffd: GREEK OXIA*/
+ 0, /* U+001ffe: GREEK DASIA*/
+ 0, /* U+001fff: */
+ RTUNI_WSPACE, /* U+002000: EN QUAD*/
+ RTUNI_WSPACE, /* U+002001: EM QUAD*/
+ RTUNI_WSPACE, /* U+002002: EN SPACE*/
+ RTUNI_WSPACE, /* U+002003: EM SPACE*/
+ RTUNI_WSPACE, /* U+002004: THREE-PER-EM SPACE*/
+ RTUNI_WSPACE, /* U+002005: FOUR-PER-EM SPACE*/
+ RTUNI_WSPACE, /* U+002006: SIX-PER-EM SPACE*/
+ RTUNI_WSPACE, /* U+002007: FIGURE SPACE*/
+ RTUNI_WSPACE, /* U+002008: PUNCTUATION SPACE*/
+ RTUNI_WSPACE, /* U+002009: THIN SPACE*/
+ RTUNI_WSPACE, /* U+00200a: HAIR SPACE*/
+ 0, /* U+00200b: ZERO WIDTH SPACE*/
+ 0, /* U+00200c: ZERO WIDTH NON-JOINER*/
+ 0, /* U+00200d: ZERO WIDTH JOINER*/
+ 0, /* U+00200e: LEFT-TO-RIGHT MARK*/
+ 0, /* U+00200f: RIGHT-TO-LEFT MARK*/
+ 0, /* U+002010: HYPHEN*/
+ 0, /* U+002011: NON-BREAKING HYPHEN*/
+ 0, /* U+002012: FIGURE DASH*/
+ 0, /* U+002013: EN DASH*/
+ 0, /* U+002014: EM DASH*/
+ 0, /* U+002015: HORIZONTAL BAR*/
+ 0, /* U+002016: DOUBLE VERTICAL LINE*/
+ 0, /* U+002017: DOUBLE LOW LINE*/
+ 0, /* U+002018: LEFT SINGLE QUOTATION MARK*/
+ 0, /* U+002019: RIGHT SINGLE QUOTATION MARK*/
+ 0, /* U+00201a: SINGLE LOW-9 QUOTATION MARK*/
+ 0, /* U+00201b: SINGLE HIGH-REVERSED-9 QUOTATION MARK*/
+ 0, /* U+00201c: LEFT DOUBLE QUOTATION MARK*/
+ 0, /* U+00201d: RIGHT DOUBLE QUOTATION MARK*/
+ 0, /* U+00201e: DOUBLE LOW-9 QUOTATION MARK*/
+ 0, /* U+00201f: DOUBLE HIGH-REVERSED-9 QUOTATION MARK*/
+ 0, /* U+002020: DAGGER*/
+ 0, /* U+002021: DOUBLE DAGGER*/
+ 0, /* U+002022: BULLET*/
+ 0, /* U+002023: TRIANGULAR BULLET*/
+ 0, /* U+002024: ONE DOT LEADER*/
+ 0, /* U+002025: TWO DOT LEADER*/
+ 0, /* U+002026: HORIZONTAL ELLIPSIS*/
+ 0, /* U+002027: HYPHENATION POINT*/
+ RTUNI_WSPACE, /* U+002028: LINE SEPARATOR*/
+ RTUNI_WSPACE, /* U+002029: PARAGRAPH SEPARATOR*/
+ 0, /* U+00202a: LEFT-TO-RIGHT EMBEDDING*/
+ 0, /* U+00202b: RIGHT-TO-LEFT EMBEDDING*/
+ 0, /* U+00202c: POP DIRECTIONAL FORMATTING*/
+ 0, /* U+00202d: LEFT-TO-RIGHT OVERRIDE*/
+ 0, /* U+00202e: RIGHT-TO-LEFT OVERRIDE*/
+ RTUNI_WSPACE, /* U+00202f: NARROW NO-BREAK SPACE*/
+ 0, /* U+002030: PER MILLE SIGN*/
+ 0, /* U+002031: PER TEN THOUSAND SIGN*/
+ 0, /* U+002032: PRIME*/
+ 0, /* U+002033: DOUBLE PRIME*/
+ 0, /* U+002034: TRIPLE PRIME*/
+ 0, /* U+002035: REVERSED PRIME*/
+ 0, /* U+002036: REVERSED DOUBLE PRIME*/
+ 0, /* U+002037: REVERSED TRIPLE PRIME*/
+ 0, /* U+002038: CARET*/
+ 0, /* U+002039: SINGLE LEFT-POINTING ANGLE QUOTATION MARK*/
+ 0, /* U+00203a: SINGLE RIGHT-POINTING ANGLE QUOTATION MARK*/
+ 0, /* U+00203b: REFERENCE MARK*/
+ 0, /* U+00203c: DOUBLE EXCLAMATION MARK*/
+ 0, /* U+00203d: INTERROBANG*/
+ 0, /* U+00203e: OVERLINE*/
+ 0, /* U+00203f: UNDERTIE*/
+ 0, /* U+002040: CHARACTER TIE*/
+ 0, /* U+002041: CARET INSERTION POINT*/
+ 0, /* U+002042: ASTERISM*/
+ 0, /* U+002043: HYPHEN BULLET*/
+ 0, /* U+002044: FRACTION SLASH*/
+ 0, /* U+002045: LEFT SQUARE BRACKET WITH QUILL*/
+ 0, /* U+002046: RIGHT SQUARE BRACKET WITH QUILL*/
+ 0, /* U+002047: DOUBLE QUESTION MARK*/
+ 0, /* U+002048: QUESTION EXCLAMATION MARK*/
+ 0, /* U+002049: EXCLAMATION QUESTION MARK*/
+ 0, /* U+00204a: TIRONIAN SIGN ET*/
+ 0, /* U+00204b: REVERSED PILCROW SIGN*/
+ 0, /* U+00204c: BLACK LEFTWARDS BULLET*/
+ 0, /* U+00204d: BLACK RIGHTWARDS BULLET*/
+ 0, /* U+00204e: LOW ASTERISK*/
+ 0, /* U+00204f: REVERSED SEMICOLON*/
+ 0, /* U+002050: CLOSE UP*/
+ 0, /* U+002051: TWO ASTERISKS ALIGNED VERTICALLY*/
+ 0, /* U+002052: COMMERCIAL MINUS SIGN*/
+ 0, /* U+002053: SWUNG DASH*/
+ 0, /* U+002054: INVERTED UNDERTIE*/
+ 0, /* U+002055: FLOWER PUNCTUATION MARK*/
+ 0, /* U+002056: THREE DOT PUNCTUATION*/
+ 0, /* U+002057: QUADRUPLE PRIME*/
+ 0, /* U+002058: FOUR DOT PUNCTUATION*/
+ 0, /* U+002059: FIVE DOT PUNCTUATION*/
+ 0, /* U+00205a: TWO DOT PUNCTUATION*/
+ 0, /* U+00205b: FOUR DOT MARK*/
+ 0, /* U+00205c: DOTTED CROSS*/
+ 0, /* U+00205d: TRICOLON*/
+ 0, /* U+00205e: VERTICAL FOUR DOTS*/
+ RTUNI_WSPACE, /* U+00205f: MEDIUM MATHEMATICAL SPACE*/
+ 0, /* U+002060: WORD JOINER*/
+ 0, /* U+002061: FUNCTION APPLICATION*/
+ 0, /* U+002062: INVISIBLE TIMES*/
+ 0, /* U+002063: INVISIBLE SEPARATOR*/
+ 0, /* U+002064: INVISIBLE PLUS*/
+ 0, /* U+002065: */
+ 0, /* U+002066: LEFT-TO-RIGHT ISOLATE*/
+ 0, /* U+002067: RIGHT-TO-LEFT ISOLATE*/
+ 0, /* U+002068: FIRST STRONG ISOLATE*/
+ 0, /* U+002069: POP DIRECTIONAL ISOLATE*/
+ 0, /* U+00206a: INHIBIT SYMMETRIC SWAPPING*/
+ 0, /* U+00206b: ACTIVATE SYMMETRIC SWAPPING*/
+ 0, /* U+00206c: INHIBIT ARABIC FORM SHAPING*/
+ 0, /* U+00206d: ACTIVATE ARABIC FORM SHAPING*/
+ 0, /* U+00206e: NATIONAL DIGIT SHAPES*/
+ 0, /* U+00206f: NOMINAL DIGIT SHAPES*/
+ 0, /* U+002070: SUPERSCRIPT ZERO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002071: SUPERSCRIPT LATIN SMALL LETTER I*/
+ 0, /* U+002072: */
+ 0, /* U+002073: */
+ 0, /* U+002074: SUPERSCRIPT FOUR*/
+ 0, /* U+002075: SUPERSCRIPT FIVE*/
+ 0, /* U+002076: SUPERSCRIPT SIX*/
+ 0, /* U+002077: SUPERSCRIPT SEVEN*/
+ 0, /* U+002078: SUPERSCRIPT EIGHT*/
+ 0, /* U+002079: SUPERSCRIPT NINE*/
+ 0, /* U+00207a: SUPERSCRIPT PLUS SIGN*/
+ 0, /* U+00207b: SUPERSCRIPT MINUS*/
+ 0, /* U+00207c: SUPERSCRIPT EQUALS SIGN*/
+ 0, /* U+00207d: SUPERSCRIPT LEFT PARENTHESIS*/
+ 0, /* U+00207e: SUPERSCRIPT RIGHT PARENTHESIS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00207f: SUPERSCRIPT LATIN SMALL LETTER N*/
+ 0, /* U+002080: SUBSCRIPT ZERO*/
+ 0, /* U+002081: SUBSCRIPT ONE*/
+ 0, /* U+002082: SUBSCRIPT TWO*/
+ 0, /* U+002083: SUBSCRIPT THREE*/
+ 0, /* U+002084: SUBSCRIPT FOUR*/
+ 0, /* U+002085: SUBSCRIPT FIVE*/
+ 0, /* U+002086: SUBSCRIPT SIX*/
+ 0, /* U+002087: SUBSCRIPT SEVEN*/
+ 0, /* U+002088: SUBSCRIPT EIGHT*/
+ 0, /* U+002089: SUBSCRIPT NINE*/
+ 0, /* U+00208a: SUBSCRIPT PLUS SIGN*/
+ 0, /* U+00208b: SUBSCRIPT MINUS*/
+ 0, /* U+00208c: SUBSCRIPT EQUALS SIGN*/
+ 0, /* U+00208d: SUBSCRIPT LEFT PARENTHESIS*/
+ 0, /* U+00208e: SUBSCRIPT RIGHT PARENTHESIS*/
+ 0, /* U+00208f: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002090: LATIN SUBSCRIPT SMALL LETTER A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002091: LATIN SUBSCRIPT SMALL LETTER E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002092: LATIN SUBSCRIPT SMALL LETTER O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002093: LATIN SUBSCRIPT SMALL LETTER X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002094: LATIN SUBSCRIPT SMALL LETTER SCHWA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002095: LATIN SUBSCRIPT SMALL LETTER H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002096: LATIN SUBSCRIPT SMALL LETTER K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002097: LATIN SUBSCRIPT SMALL LETTER L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002098: LATIN SUBSCRIPT SMALL LETTER M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002099: LATIN SUBSCRIPT SMALL LETTER N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00209a: LATIN SUBSCRIPT SMALL LETTER P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00209b: LATIN SUBSCRIPT SMALL LETTER S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00209c: LATIN SUBSCRIPT SMALL LETTER T*/
+ 0, /* U+00209d: */
+ 0, /* U+00209e: */
+ 0, /* U+00209f: */
+ 0, /* U+0020a0: EURO-CURRENCY SIGN*/
+ 0, /* U+0020a1: COLON SIGN*/
+ 0, /* U+0020a2: CRUZEIRO SIGN*/
+ 0, /* U+0020a3: FRENCH FRANC SIGN*/
+ 0, /* U+0020a4: LIRA SIGN*/
+ 0, /* U+0020a5: MILL SIGN*/
+ 0, /* U+0020a6: NAIRA SIGN*/
+ 0, /* U+0020a7: PESETA SIGN*/
+ 0, /* U+0020a8: RUPEE SIGN*/
+ 0, /* U+0020a9: WON SIGN*/
+ 0, /* U+0020aa: NEW SHEQEL SIGN*/
+ 0, /* U+0020ab: DONG SIGN*/
+ 0, /* U+0020ac: EURO SIGN*/
+ 0, /* U+0020ad: KIP SIGN*/
+ 0, /* U+0020ae: TUGRIK SIGN*/
+ 0, /* U+0020af: DRACHMA SIGN*/
+ 0, /* U+0020b0: GERMAN PENNY SIGN*/
+ 0, /* U+0020b1: PESO SIGN*/
+ 0, /* U+0020b2: GUARANI SIGN*/
+ 0, /* U+0020b3: AUSTRAL SIGN*/
+ 0, /* U+0020b4: HRYVNIA SIGN*/
+ 0, /* U+0020b5: CEDI SIGN*/
+ 0, /* U+0020b6: LIVRE TOURNOIS SIGN*/
+ 0, /* U+0020b7: SPESMILO SIGN*/
+ 0, /* U+0020b8: TENGE SIGN*/
+ 0, /* U+0020b9: INDIAN RUPEE SIGN*/
+ 0, /* U+0020ba: TURKISH LIRA SIGN*/
+ 0, /* U+0020bb: */
+ 0, /* U+0020bc: */
+ 0, /* U+0020bd: */
+ 0, /* U+0020be: */
+ 0, /* U+0020bf: */
+ 0, /* U+0020c0: */
+ 0, /* U+0020c1: */
+ 0, /* U+0020c2: */
+ 0, /* U+0020c3: */
+ 0, /* U+0020c4: */
+ 0, /* U+0020c5: */
+ 0, /* U+0020c6: */
+ 0, /* U+0020c7: */
+ 0, /* U+0020c8: */
+ 0, /* U+0020c9: */
+ 0, /* U+0020ca: */
+ 0, /* U+0020cb: */
+ 0, /* U+0020cc: */
+ 0, /* U+0020cd: */
+ 0, /* U+0020ce: */
+ 0, /* U+0020cf: */
+ 0, /* U+0020d0: COMBINING LEFT HARPOON ABOVE*/
+ 0, /* U+0020d1: COMBINING RIGHT HARPOON ABOVE*/
+ 0, /* U+0020d2: COMBINING LONG VERTICAL LINE OVERLAY*/
+ 0, /* U+0020d3: COMBINING SHORT VERTICAL LINE OVERLAY*/
+ 0, /* U+0020d4: COMBINING ANTICLOCKWISE ARROW ABOVE*/
+ 0, /* U+0020d5: COMBINING CLOCKWISE ARROW ABOVE*/
+ 0, /* U+0020d6: COMBINING LEFT ARROW ABOVE*/
+ 0, /* U+0020d7: COMBINING RIGHT ARROW ABOVE*/
+ 0, /* U+0020d8: COMBINING RING OVERLAY*/
+ 0, /* U+0020d9: COMBINING CLOCKWISE RING OVERLAY*/
+ 0, /* U+0020da: COMBINING ANTICLOCKWISE RING OVERLAY*/
+ 0, /* U+0020db: COMBINING THREE DOTS ABOVE*/
+ 0, /* U+0020dc: COMBINING FOUR DOTS ABOVE*/
+ 0, /* U+0020dd: COMBINING ENCLOSING CIRCLE*/
+ 0, /* U+0020de: COMBINING ENCLOSING SQUARE*/
+ 0, /* U+0020df: COMBINING ENCLOSING DIAMOND*/
+ 0, /* U+0020e0: COMBINING ENCLOSING CIRCLE BACKSLASH*/
+ 0, /* U+0020e1: COMBINING LEFT RIGHT ARROW ABOVE*/
+ 0, /* U+0020e2: COMBINING ENCLOSING SCREEN*/
+ 0, /* U+0020e3: COMBINING ENCLOSING KEYCAP*/
+ 0, /* U+0020e4: COMBINING ENCLOSING UPWARD POINTING TRIANGLE*/
+ 0, /* U+0020e5: COMBINING REVERSE SOLIDUS OVERLAY*/
+ 0, /* U+0020e6: COMBINING DOUBLE VERTICAL STROKE OVERLAY*/
+ 0, /* U+0020e7: COMBINING ANNUITY SYMBOL*/
+ 0, /* U+0020e8: COMBINING TRIPLE UNDERDOT*/
+ 0, /* U+0020e9: COMBINING WIDE BRIDGE ABOVE*/
+ 0, /* U+0020ea: COMBINING LEFTWARDS ARROW OVERLAY*/
+ 0, /* U+0020eb: COMBINING LONG DOUBLE SOLIDUS OVERLAY*/
+ 0, /* U+0020ec: COMBINING RIGHTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0, /* U+0020ed: COMBINING LEFTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0, /* U+0020ee: COMBINING LEFT ARROW BELOW*/
+ 0, /* U+0020ef: COMBINING RIGHT ARROW BELOW*/
+ 0, /* U+0020f0: COMBINING ASTERISK ABOVE*/
+ 0, /* U+0020f1: */
+ 0, /* U+0020f2: */
+ 0, /* U+0020f3: */
+ 0, /* U+0020f4: */
+ 0, /* U+0020f5: */
+ 0, /* U+0020f6: */
+ 0, /* U+0020f7: */
+ 0, /* U+0020f8: */
+ 0, /* U+0020f9: */
+ 0, /* U+0020fa: */
+ 0, /* U+0020fb: */
+ 0, /* U+0020fc: */
+ 0, /* U+0020fd: */
+ 0, /* U+0020fe: */
+ 0, /* U+0020ff: */
+ 0, /* U+002100: ACCOUNT OF*/
+ 0, /* U+002101: ADDRESSED TO THE SUBJECT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002102: DOUBLE-STRUCK CAPITAL C*/
+ 0, /* U+002103: DEGREE CELSIUS*/
+ 0, /* U+002104: CENTRE LINE SYMBOL*/
+ 0, /* U+002105: CARE OF*/
+ 0, /* U+002106: CADA UNA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002107: EULER CONSTANT*/
+ 0, /* U+002108: SCRUPLE*/
+ 0, /* U+002109: DEGREE FAHRENHEIT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00210a: SCRIPT SMALL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00210b: SCRIPT CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00210c: BLACK-LETTER CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00210d: DOUBLE-STRUCK CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00210e: PLANCK CONSTANT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00210f: PLANCK CONSTANT OVER TWO PI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002110: SCRIPT CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002111: BLACK-LETTER CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002112: SCRIPT CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002113: SCRIPT SMALL L*/
+ 0, /* U+002114: L B BAR SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002115: DOUBLE-STRUCK CAPITAL N*/
+ 0, /* U+002116: NUMERO SIGN*/
+ 0, /* U+002117: SOUND RECORDING COPYRIGHT*/
+ 0, /* U+002118: SCRIPT CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002119: DOUBLE-STRUCK CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00211a: DOUBLE-STRUCK CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00211b: SCRIPT CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00211c: BLACK-LETTER CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00211d: DOUBLE-STRUCK CAPITAL R*/
+ 0, /* U+00211e: PRESCRIPTION TAKE*/
+ 0, /* U+00211f: RESPONSE*/
+ 0, /* U+002120: SERVICE MARK*/
+ 0, /* U+002121: TELEPHONE SIGN*/
+ 0, /* U+002122: TRADE MARK SIGN*/
+ 0, /* U+002123: VERSICLE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002124: DOUBLE-STRUCK CAPITAL Z*/
+ 0, /* U+002125: OUNCE SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002126: OHM SIGN*/
+ 0, /* U+002127: INVERTED OHM SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002128: BLACK-LETTER CAPITAL Z*/
+ 0, /* U+002129: TURNED GREEK SMALL LETTER IOTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00212a: KELVIN SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00212b: ANGSTROM SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00212c: SCRIPT CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00212d: BLACK-LETTER CAPITAL C*/
+ 0, /* U+00212e: ESTIMATED SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00212f: SCRIPT SMALL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002130: SCRIPT CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002131: SCRIPT CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002132: TURNED CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002133: SCRIPT CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002134: SCRIPT SMALL O*/
+ RTUNI_ALPHA, /* U+002135: ALEF SYMBOL*/
+ RTUNI_ALPHA, /* U+002136: BET SYMBOL*/
+ RTUNI_ALPHA, /* U+002137: GIMEL SYMBOL*/
+ RTUNI_ALPHA, /* U+002138: DALET SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002139: INFORMATION SOURCE*/
+ 0, /* U+00213a: ROTATED CAPITAL Q*/
+ 0, /* U+00213b: FACSIMILE SIGN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00213c: DOUBLE-STRUCK SMALL PI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00213d: DOUBLE-STRUCK SMALL GAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00213e: DOUBLE-STRUCK CAPITAL GAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00213f: DOUBLE-STRUCK CAPITAL PI*/
+ 0, /* U+002140: DOUBLE-STRUCK N-ARY SUMMATION*/
+ 0, /* U+002141: TURNED SANS-SERIF CAPITAL G*/
+ 0, /* U+002142: TURNED SANS-SERIF CAPITAL L*/
+ 0, /* U+002143: REVERSED SANS-SERIF CAPITAL L*/
+ 0, /* U+002144: TURNED SANS-SERIF CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002145: DOUBLE-STRUCK ITALIC CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002146: DOUBLE-STRUCK ITALIC SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002147: DOUBLE-STRUCK ITALIC SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002148: DOUBLE-STRUCK ITALIC SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002149: DOUBLE-STRUCK ITALIC SMALL J*/
+ 0, /* U+00214a: PROPERTY LINE*/
+ 0, /* U+00214b: TURNED AMPERSAND*/
+ 0, /* U+00214c: PER SIGN*/
+ 0, /* U+00214d: AKTIESELSKAB*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00214e: TURNED SMALL F*/
+ 0, /* U+00214f: SYMBOL FOR SAMARITAN SOURCE*/
+ 0, /* U+002150: VULGAR FRACTION ONE SEVENTH*/
+ 0, /* U+002151: VULGAR FRACTION ONE NINTH*/
+ 0, /* U+002152: VULGAR FRACTION ONE TENTH*/
+ 0, /* U+002153: VULGAR FRACTION ONE THIRD*/
+ 0, /* U+002154: VULGAR FRACTION TWO THIRDS*/
+ 0, /* U+002155: VULGAR FRACTION ONE FIFTH*/
+ 0, /* U+002156: VULGAR FRACTION TWO FIFTHS*/
+ 0, /* U+002157: VULGAR FRACTION THREE FIFTHS*/
+ 0, /* U+002158: VULGAR FRACTION FOUR FIFTHS*/
+ 0, /* U+002159: VULGAR FRACTION ONE SIXTH*/
+ 0, /* U+00215a: VULGAR FRACTION FIVE SIXTHS*/
+ 0, /* U+00215b: VULGAR FRACTION ONE EIGHTH*/
+ 0, /* U+00215c: VULGAR FRACTION THREE EIGHTHS*/
+ 0, /* U+00215d: VULGAR FRACTION FIVE EIGHTHS*/
+ 0, /* U+00215e: VULGAR FRACTION SEVEN EIGHTHS*/
+ 0, /* U+00215f: FRACTION NUMERATOR ONE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002160: ROMAN NUMERAL ONE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002161: ROMAN NUMERAL TWO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002162: ROMAN NUMERAL THREE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002163: ROMAN NUMERAL FOUR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002164: ROMAN NUMERAL FIVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002165: ROMAN NUMERAL SIX*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002166: ROMAN NUMERAL SEVEN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002167: ROMAN NUMERAL EIGHT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002168: ROMAN NUMERAL NINE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002169: ROMAN NUMERAL TEN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00216a: ROMAN NUMERAL ELEVEN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00216b: ROMAN NUMERAL TWELVE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00216c: ROMAN NUMERAL FIFTY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00216d: ROMAN NUMERAL ONE HUNDRED*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00216e: ROMAN NUMERAL FIVE HUNDRED*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00216f: ROMAN NUMERAL ONE THOUSAND*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002170: SMALL ROMAN NUMERAL ONE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002171: SMALL ROMAN NUMERAL TWO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002172: SMALL ROMAN NUMERAL THREE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002173: SMALL ROMAN NUMERAL FOUR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002174: SMALL ROMAN NUMERAL FIVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002175: SMALL ROMAN NUMERAL SIX*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002176: SMALL ROMAN NUMERAL SEVEN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002177: SMALL ROMAN NUMERAL EIGHT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002178: SMALL ROMAN NUMERAL NINE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002179: SMALL ROMAN NUMERAL TEN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00217a: SMALL ROMAN NUMERAL ELEVEN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00217b: SMALL ROMAN NUMERAL TWELVE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00217c: SMALL ROMAN NUMERAL FIFTY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00217d: SMALL ROMAN NUMERAL ONE HUNDRED*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00217e: SMALL ROMAN NUMERAL FIVE HUNDRED*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00217f: SMALL ROMAN NUMERAL ONE THOUSAND*/
+ RTUNI_ALPHA, /* U+002180: ROMAN NUMERAL ONE THOUSAND C D*/
+ RTUNI_ALPHA, /* U+002181: ROMAN NUMERAL FIVE THOUSAND*/
+ RTUNI_ALPHA, /* U+002182: ROMAN NUMERAL TEN THOUSAND*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002183: ROMAN NUMERAL REVERSED ONE HUNDRED*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002184: LATIN SMALL LETTER REVERSED C*/
+ RTUNI_ALPHA, /* U+002185: ROMAN NUMERAL SIX LATE FORM*/
+ RTUNI_ALPHA, /* U+002186: ROMAN NUMERAL FIFTY EARLY FORM*/
+ RTUNI_ALPHA, /* U+002187: ROMAN NUMERAL FIFTY THOUSAND*/
+ RTUNI_ALPHA, /* U+002188: ROMAN NUMERAL ONE HUNDRED THOUSAND*/
+ 0, /* U+002189: VULGAR FRACTION ZERO THIRDS*/
+ 0, /* U+00218a: */
+ 0, /* U+00218b: */
+ 0, /* U+00218c: */
+ 0, /* U+00218d: */
+ 0, /* U+00218e: */
+ 0, /* U+00218f: */
+ 0, /* U+002190: LEFTWARDS ARROW*/
+ 0, /* U+002191: UPWARDS ARROW*/
+ 0, /* U+002192: RIGHTWARDS ARROW*/
+ 0, /* U+002193: DOWNWARDS ARROW*/
+ 0, /* U+002194: LEFT RIGHT ARROW*/
+ 0, /* U+002195: UP DOWN ARROW*/
+ 0, /* U+002196: NORTH WEST ARROW*/
+ 0, /* U+002197: NORTH EAST ARROW*/
+ 0, /* U+002198: SOUTH EAST ARROW*/
+ 0, /* U+002199: SOUTH WEST ARROW*/
+ 0, /* U+00219a: LEFTWARDS ARROW WITH STROKE*/
+ 0, /* U+00219b: RIGHTWARDS ARROW WITH STROKE*/
+ 0, /* U+00219c: LEFTWARDS WAVE ARROW*/
+ 0, /* U+00219d: RIGHTWARDS WAVE ARROW*/
+ 0, /* U+00219e: LEFTWARDS TWO HEADED ARROW*/
+ 0, /* U+00219f: UPWARDS TWO HEADED ARROW*/
+ 0, /* U+0021a0: RIGHTWARDS TWO HEADED ARROW*/
+ 0, /* U+0021a1: DOWNWARDS TWO HEADED ARROW*/
+ 0, /* U+0021a2: LEFTWARDS ARROW WITH TAIL*/
+ 0, /* U+0021a3: RIGHTWARDS ARROW WITH TAIL*/
+ 0, /* U+0021a4: LEFTWARDS ARROW FROM BAR*/
+ 0, /* U+0021a5: UPWARDS ARROW FROM BAR*/
+ 0, /* U+0021a6: RIGHTWARDS ARROW FROM BAR*/
+ 0, /* U+0021a7: DOWNWARDS ARROW FROM BAR*/
+ 0, /* U+0021a8: UP DOWN ARROW WITH BASE*/
+ 0, /* U+0021a9: LEFTWARDS ARROW WITH HOOK*/
+ 0, /* U+0021aa: RIGHTWARDS ARROW WITH HOOK*/
+ 0, /* U+0021ab: LEFTWARDS ARROW WITH LOOP*/
+ 0, /* U+0021ac: RIGHTWARDS ARROW WITH LOOP*/
+ 0, /* U+0021ad: LEFT RIGHT WAVE ARROW*/
+ 0, /* U+0021ae: LEFT RIGHT ARROW WITH STROKE*/
+ 0, /* U+0021af: DOWNWARDS ZIGZAG ARROW*/
+ 0, /* U+0021b0: UPWARDS ARROW WITH TIP LEFTWARDS*/
+ 0, /* U+0021b1: UPWARDS ARROW WITH TIP RIGHTWARDS*/
+ 0, /* U+0021b2: DOWNWARDS ARROW WITH TIP LEFTWARDS*/
+ 0, /* U+0021b3: DOWNWARDS ARROW WITH TIP RIGHTWARDS*/
+ 0, /* U+0021b4: RIGHTWARDS ARROW WITH CORNER DOWNWARDS*/
+ 0, /* U+0021b5: DOWNWARDS ARROW WITH CORNER LEFTWARDS*/
+ 0, /* U+0021b6: ANTICLOCKWISE TOP SEMICIRCLE ARROW*/
+ 0, /* U+0021b7: CLOCKWISE TOP SEMICIRCLE ARROW*/
+ 0, /* U+0021b8: NORTH WEST ARROW TO LONG BAR*/
+ 0, /* U+0021b9: LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR*/
+ 0, /* U+0021ba: ANTICLOCKWISE OPEN CIRCLE ARROW*/
+ 0, /* U+0021bb: CLOCKWISE OPEN CIRCLE ARROW*/
+ 0, /* U+0021bc: LEFTWARDS HARPOON WITH BARB UPWARDS*/
+ 0, /* U+0021bd: LEFTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0, /* U+0021be: UPWARDS HARPOON WITH BARB RIGHTWARDS*/
+ 0, /* U+0021bf: UPWARDS HARPOON WITH BARB LEFTWARDS*/
+ 0, /* U+0021c0: RIGHTWARDS HARPOON WITH BARB UPWARDS*/
+ 0, /* U+0021c1: RIGHTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0, /* U+0021c2: DOWNWARDS HARPOON WITH BARB RIGHTWARDS*/
+ 0, /* U+0021c3: DOWNWARDS HARPOON WITH BARB LEFTWARDS*/
+ 0, /* U+0021c4: RIGHTWARDS ARROW OVER LEFTWARDS ARROW*/
+ 0, /* U+0021c5: UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW*/
+ 0, /* U+0021c6: LEFTWARDS ARROW OVER RIGHTWARDS ARROW*/
+ 0, /* U+0021c7: LEFTWARDS PAIRED ARROWS*/
+ 0, /* U+0021c8: UPWARDS PAIRED ARROWS*/
+ 0, /* U+0021c9: RIGHTWARDS PAIRED ARROWS*/
+ 0, /* U+0021ca: DOWNWARDS PAIRED ARROWS*/
+ 0, /* U+0021cb: LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON*/
+ 0, /* U+0021cc: RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON*/
+ 0, /* U+0021cd: LEFTWARDS DOUBLE ARROW WITH STROKE*/
+ 0, /* U+0021ce: LEFT RIGHT DOUBLE ARROW WITH STROKE*/
+ 0, /* U+0021cf: RIGHTWARDS DOUBLE ARROW WITH STROKE*/
+ 0, /* U+0021d0: LEFTWARDS DOUBLE ARROW*/
+ 0, /* U+0021d1: UPWARDS DOUBLE ARROW*/
+ 0, /* U+0021d2: RIGHTWARDS DOUBLE ARROW*/
+ 0, /* U+0021d3: DOWNWARDS DOUBLE ARROW*/
+ 0, /* U+0021d4: LEFT RIGHT DOUBLE ARROW*/
+ 0, /* U+0021d5: UP DOWN DOUBLE ARROW*/
+ 0, /* U+0021d6: NORTH WEST DOUBLE ARROW*/
+ 0, /* U+0021d7: NORTH EAST DOUBLE ARROW*/
+ 0, /* U+0021d8: SOUTH EAST DOUBLE ARROW*/
+ 0, /* U+0021d9: SOUTH WEST DOUBLE ARROW*/
+ 0, /* U+0021da: LEFTWARDS TRIPLE ARROW*/
+ 0, /* U+0021db: RIGHTWARDS TRIPLE ARROW*/
+ 0, /* U+0021dc: LEFTWARDS SQUIGGLE ARROW*/
+ 0, /* U+0021dd: RIGHTWARDS SQUIGGLE ARROW*/
+ 0, /* U+0021de: UPWARDS ARROW WITH DOUBLE STROKE*/
+ 0, /* U+0021df: DOWNWARDS ARROW WITH DOUBLE STROKE*/
+ 0, /* U+0021e0: LEFTWARDS DASHED ARROW*/
+ 0, /* U+0021e1: UPWARDS DASHED ARROW*/
+ 0, /* U+0021e2: RIGHTWARDS DASHED ARROW*/
+ 0, /* U+0021e3: DOWNWARDS DASHED ARROW*/
+ 0, /* U+0021e4: LEFTWARDS ARROW TO BAR*/
+ 0, /* U+0021e5: RIGHTWARDS ARROW TO BAR*/
+ 0, /* U+0021e6: LEFTWARDS WHITE ARROW*/
+ 0, /* U+0021e7: UPWARDS WHITE ARROW*/
+ 0, /* U+0021e8: RIGHTWARDS WHITE ARROW*/
+ 0, /* U+0021e9: DOWNWARDS WHITE ARROW*/
+ 0, /* U+0021ea: UPWARDS WHITE ARROW FROM BAR*/
+ 0, /* U+0021eb: UPWARDS WHITE ARROW ON PEDESTAL*/
+ 0, /* U+0021ec: UPWARDS WHITE ARROW ON PEDESTAL WITH HORIZONTAL BAR*/
+ 0, /* U+0021ed: UPWARDS WHITE ARROW ON PEDESTAL WITH VERTICAL BAR*/
+ 0, /* U+0021ee: UPWARDS WHITE DOUBLE ARROW*/
+ 0, /* U+0021ef: UPWARDS WHITE DOUBLE ARROW ON PEDESTAL*/
+ 0, /* U+0021f0: RIGHTWARDS WHITE ARROW FROM WALL*/
+ 0, /* U+0021f1: NORTH WEST ARROW TO CORNER*/
+ 0, /* U+0021f2: SOUTH EAST ARROW TO CORNER*/
+ 0, /* U+0021f3: UP DOWN WHITE ARROW*/
+ 0, /* U+0021f4: RIGHT ARROW WITH SMALL CIRCLE*/
+ 0, /* U+0021f5: DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW*/
+ 0, /* U+0021f6: THREE RIGHTWARDS ARROWS*/
+ 0, /* U+0021f7: LEFTWARDS ARROW WITH VERTICAL STROKE*/
+ 0, /* U+0021f8: RIGHTWARDS ARROW WITH VERTICAL STROKE*/
+ 0, /* U+0021f9: LEFT RIGHT ARROW WITH VERTICAL STROKE*/
+ 0, /* U+0021fa: LEFTWARDS ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+0021fb: RIGHTWARDS ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+0021fc: LEFT RIGHT ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+0021fd: LEFTWARDS OPEN-HEADED ARROW*/
+ 0, /* U+0021fe: RIGHTWARDS OPEN-HEADED ARROW*/
+ 0, /* U+0021ff: LEFT RIGHT OPEN-HEADED ARROW*/
+ 0, /* U+002200: FOR ALL*/
+ 0, /* U+002201: COMPLEMENT*/
+ 0, /* U+002202: PARTIAL DIFFERENTIAL*/
+ 0, /* U+002203: THERE EXISTS*/
+ 0, /* U+002204: THERE DOES NOT EXIST*/
+ 0, /* U+002205: EMPTY SET*/
+ 0, /* U+002206: INCREMENT*/
+ 0, /* U+002207: NABLA*/
+ 0, /* U+002208: ELEMENT OF*/
+ 0, /* U+002209: NOT AN ELEMENT OF*/
+ 0, /* U+00220a: SMALL ELEMENT OF*/
+ 0, /* U+00220b: CONTAINS AS MEMBER*/
+ 0, /* U+00220c: DOES NOT CONTAIN AS MEMBER*/
+ 0, /* U+00220d: SMALL CONTAINS AS MEMBER*/
+ 0, /* U+00220e: END OF PROOF*/
+ 0, /* U+00220f: N-ARY PRODUCT*/
+ 0, /* U+002210: N-ARY COPRODUCT*/
+ 0, /* U+002211: N-ARY SUMMATION*/
+ 0, /* U+002212: MINUS SIGN*/
+ 0, /* U+002213: MINUS-OR-PLUS SIGN*/
+ 0, /* U+002214: DOT PLUS*/
+ 0, /* U+002215: DIVISION SLASH*/
+ 0, /* U+002216: SET MINUS*/
+ 0, /* U+002217: ASTERISK OPERATOR*/
+ 0, /* U+002218: RING OPERATOR*/
+ 0, /* U+002219: BULLET OPERATOR*/
+ 0, /* U+00221a: SQUARE ROOT*/
+ 0, /* U+00221b: CUBE ROOT*/
+ 0, /* U+00221c: FOURTH ROOT*/
+ 0, /* U+00221d: PROPORTIONAL TO*/
+ 0, /* U+00221e: INFINITY*/
+ 0, /* U+00221f: RIGHT ANGLE*/
+ 0, /* U+002220: ANGLE*/
+ 0, /* U+002221: MEASURED ANGLE*/
+ 0, /* U+002222: SPHERICAL ANGLE*/
+ 0, /* U+002223: DIVIDES*/
+ 0, /* U+002224: DOES NOT DIVIDE*/
+ 0, /* U+002225: PARALLEL TO*/
+ 0, /* U+002226: NOT PARALLEL TO*/
+ 0, /* U+002227: LOGICAL AND*/
+ 0, /* U+002228: LOGICAL OR*/
+ 0, /* U+002229: INTERSECTION*/
+ 0, /* U+00222a: UNION*/
+ 0, /* U+00222b: INTEGRAL*/
+ 0, /* U+00222c: DOUBLE INTEGRAL*/
+ 0, /* U+00222d: TRIPLE INTEGRAL*/
+ 0, /* U+00222e: CONTOUR INTEGRAL*/
+ 0, /* U+00222f: SURFACE INTEGRAL*/
+ 0, /* U+002230: VOLUME INTEGRAL*/
+ 0, /* U+002231: CLOCKWISE INTEGRAL*/
+ 0, /* U+002232: CLOCKWISE CONTOUR INTEGRAL*/
+ 0, /* U+002233: ANTICLOCKWISE CONTOUR INTEGRAL*/
+ 0, /* U+002234: THEREFORE*/
+ 0, /* U+002235: BECAUSE*/
+ 0, /* U+002236: RATIO*/
+ 0, /* U+002237: PROPORTION*/
+ 0, /* U+002238: DOT MINUS*/
+ 0, /* U+002239: EXCESS*/
+ 0, /* U+00223a: GEOMETRIC PROPORTION*/
+ 0, /* U+00223b: HOMOTHETIC*/
+ 0, /* U+00223c: TILDE OPERATOR*/
+ 0, /* U+00223d: REVERSED TILDE*/
+ 0, /* U+00223e: INVERTED LAZY S*/
+ 0, /* U+00223f: SINE WAVE*/
+ 0, /* U+002240: WREATH PRODUCT*/
+ 0, /* U+002241: NOT TILDE*/
+ 0, /* U+002242: MINUS TILDE*/
+ 0, /* U+002243: ASYMPTOTICALLY EQUAL TO*/
+ 0, /* U+002244: NOT ASYMPTOTICALLY EQUAL TO*/
+ 0, /* U+002245: APPROXIMATELY EQUAL TO*/
+ 0, /* U+002246: APPROXIMATELY BUT NOT ACTUALLY EQUAL TO*/
+ 0, /* U+002247: NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO*/
+ 0, /* U+002248: ALMOST EQUAL TO*/
+ 0, /* U+002249: NOT ALMOST EQUAL TO*/
+ 0, /* U+00224a: ALMOST EQUAL OR EQUAL TO*/
+ 0, /* U+00224b: TRIPLE TILDE*/
+ 0, /* U+00224c: ALL EQUAL TO*/
+ 0, /* U+00224d: EQUIVALENT TO*/
+ 0, /* U+00224e: GEOMETRICALLY EQUIVALENT TO*/
+ 0, /* U+00224f: DIFFERENCE BETWEEN*/
+ 0, /* U+002250: APPROACHES THE LIMIT*/
+ 0, /* U+002251: GEOMETRICALLY EQUAL TO*/
+ 0, /* U+002252: APPROXIMATELY EQUAL TO OR THE IMAGE OF*/
+ 0, /* U+002253: IMAGE OF OR APPROXIMATELY EQUAL TO*/
+ 0, /* U+002254: COLON EQUALS*/
+ 0, /* U+002255: EQUALS COLON*/
+ 0, /* U+002256: RING IN EQUAL TO*/
+ 0, /* U+002257: RING EQUAL TO*/
+ 0, /* U+002258: CORRESPONDS TO*/
+ 0, /* U+002259: ESTIMATES*/
+ 0, /* U+00225a: EQUIANGULAR TO*/
+ 0, /* U+00225b: STAR EQUALS*/
+ 0, /* U+00225c: DELTA EQUAL TO*/
+ 0, /* U+00225d: EQUAL TO BY DEFINITION*/
+ 0, /* U+00225e: MEASURED BY*/
+ 0, /* U+00225f: QUESTIONED EQUAL TO*/
+ 0, /* U+002260: NOT EQUAL TO*/
+ 0, /* U+002261: IDENTICAL TO*/
+ 0, /* U+002262: NOT IDENTICAL TO*/
+ 0, /* U+002263: STRICTLY EQUIVALENT TO*/
+ 0, /* U+002264: LESS-THAN OR EQUAL TO*/
+ 0, /* U+002265: GREATER-THAN OR EQUAL TO*/
+ 0, /* U+002266: LESS-THAN OVER EQUAL TO*/
+ 0, /* U+002267: GREATER-THAN OVER EQUAL TO*/
+ 0, /* U+002268: LESS-THAN BUT NOT EQUAL TO*/
+ 0, /* U+002269: GREATER-THAN BUT NOT EQUAL TO*/
+ 0, /* U+00226a: MUCH LESS-THAN*/
+ 0, /* U+00226b: MUCH GREATER-THAN*/
+ 0, /* U+00226c: BETWEEN*/
+ 0, /* U+00226d: NOT EQUIVALENT TO*/
+ 0, /* U+00226e: NOT LESS-THAN*/
+ 0, /* U+00226f: NOT GREATER-THAN*/
+ 0, /* U+002270: NEITHER LESS-THAN NOR EQUAL TO*/
+ 0, /* U+002271: NEITHER GREATER-THAN NOR EQUAL TO*/
+ 0, /* U+002272: LESS-THAN OR EQUIVALENT TO*/
+ 0, /* U+002273: GREATER-THAN OR EQUIVALENT TO*/
+ 0, /* U+002274: NEITHER LESS-THAN NOR EQUIVALENT TO*/
+ 0, /* U+002275: NEITHER GREATER-THAN NOR EQUIVALENT TO*/
+ 0, /* U+002276: LESS-THAN OR GREATER-THAN*/
+ 0, /* U+002277: GREATER-THAN OR LESS-THAN*/
+ 0, /* U+002278: NEITHER LESS-THAN NOR GREATER-THAN*/
+ 0, /* U+002279: NEITHER GREATER-THAN NOR LESS-THAN*/
+ 0, /* U+00227a: PRECEDES*/
+ 0, /* U+00227b: SUCCEEDS*/
+ 0, /* U+00227c: PRECEDES OR EQUAL TO*/
+ 0, /* U+00227d: SUCCEEDS OR EQUAL TO*/
+ 0, /* U+00227e: PRECEDES OR EQUIVALENT TO*/
+ 0, /* U+00227f: SUCCEEDS OR EQUIVALENT TO*/
+ 0, /* U+002280: DOES NOT PRECEDE*/
+ 0, /* U+002281: DOES NOT SUCCEED*/
+ 0, /* U+002282: SUBSET OF*/
+ 0, /* U+002283: SUPERSET OF*/
+ 0, /* U+002284: NOT A SUBSET OF*/
+ 0, /* U+002285: NOT A SUPERSET OF*/
+ 0, /* U+002286: SUBSET OF OR EQUAL TO*/
+ 0, /* U+002287: SUPERSET OF OR EQUAL TO*/
+ 0, /* U+002288: NEITHER A SUBSET OF NOR EQUAL TO*/
+ 0, /* U+002289: NEITHER A SUPERSET OF NOR EQUAL TO*/
+ 0, /* U+00228a: SUBSET OF WITH NOT EQUAL TO*/
+ 0, /* U+00228b: SUPERSET OF WITH NOT EQUAL TO*/
+ 0, /* U+00228c: MULTISET*/
+ 0, /* U+00228d: MULTISET MULTIPLICATION*/
+ 0, /* U+00228e: MULTISET UNION*/
+ 0, /* U+00228f: SQUARE IMAGE OF*/
+ 0, /* U+002290: SQUARE ORIGINAL OF*/
+ 0, /* U+002291: SQUARE IMAGE OF OR EQUAL TO*/
+ 0, /* U+002292: SQUARE ORIGINAL OF OR EQUAL TO*/
+ 0, /* U+002293: SQUARE CAP*/
+ 0, /* U+002294: SQUARE CUP*/
+ 0, /* U+002295: CIRCLED PLUS*/
+ 0, /* U+002296: CIRCLED MINUS*/
+ 0, /* U+002297: CIRCLED TIMES*/
+ 0, /* U+002298: CIRCLED DIVISION SLASH*/
+ 0, /* U+002299: CIRCLED DOT OPERATOR*/
+ 0, /* U+00229a: CIRCLED RING OPERATOR*/
+ 0, /* U+00229b: CIRCLED ASTERISK OPERATOR*/
+ 0, /* U+00229c: CIRCLED EQUALS*/
+ 0, /* U+00229d: CIRCLED DASH*/
+ 0, /* U+00229e: SQUARED PLUS*/
+ 0, /* U+00229f: SQUARED MINUS*/
+ 0, /* U+0022a0: SQUARED TIMES*/
+ 0, /* U+0022a1: SQUARED DOT OPERATOR*/
+ 0, /* U+0022a2: RIGHT TACK*/
+ 0, /* U+0022a3: LEFT TACK*/
+ 0, /* U+0022a4: DOWN TACK*/
+ 0, /* U+0022a5: UP TACK*/
+ 0, /* U+0022a6: ASSERTION*/
+ 0, /* U+0022a7: MODELS*/
+ 0, /* U+0022a8: TRUE*/
+ 0, /* U+0022a9: FORCES*/
+ 0, /* U+0022aa: TRIPLE VERTICAL BAR RIGHT TURNSTILE*/
+ 0, /* U+0022ab: DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE*/
+ 0, /* U+0022ac: DOES NOT PROVE*/
+ 0, /* U+0022ad: NOT TRUE*/
+ 0, /* U+0022ae: DOES NOT FORCE*/
+ 0, /* U+0022af: NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE*/
+ 0, /* U+0022b0: PRECEDES UNDER RELATION*/
+ 0, /* U+0022b1: SUCCEEDS UNDER RELATION*/
+ 0, /* U+0022b2: NORMAL SUBGROUP OF*/
+ 0, /* U+0022b3: CONTAINS AS NORMAL SUBGROUP*/
+ 0, /* U+0022b4: NORMAL SUBGROUP OF OR EQUAL TO*/
+ 0, /* U+0022b5: CONTAINS AS NORMAL SUBGROUP OR EQUAL TO*/
+ 0, /* U+0022b6: ORIGINAL OF*/
+ 0, /* U+0022b7: IMAGE OF*/
+ 0, /* U+0022b8: MULTIMAP*/
+ 0, /* U+0022b9: HERMITIAN CONJUGATE MATRIX*/
+ 0, /* U+0022ba: INTERCALATE*/
+ 0, /* U+0022bb: XOR*/
+ 0, /* U+0022bc: NAND*/
+ 0, /* U+0022bd: NOR*/
+ 0, /* U+0022be: RIGHT ANGLE WITH ARC*/
+ 0, /* U+0022bf: RIGHT TRIANGLE*/
+ 0, /* U+0022c0: N-ARY LOGICAL AND*/
+ 0, /* U+0022c1: N-ARY LOGICAL OR*/
+ 0, /* U+0022c2: N-ARY INTERSECTION*/
+ 0, /* U+0022c3: N-ARY UNION*/
+ 0, /* U+0022c4: DIAMOND OPERATOR*/
+ 0, /* U+0022c5: DOT OPERATOR*/
+ 0, /* U+0022c6: STAR OPERATOR*/
+ 0, /* U+0022c7: DIVISION TIMES*/
+ 0, /* U+0022c8: BOWTIE*/
+ 0, /* U+0022c9: LEFT NORMAL FACTOR SEMIDIRECT PRODUCT*/
+ 0, /* U+0022ca: RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT*/
+ 0, /* U+0022cb: LEFT SEMIDIRECT PRODUCT*/
+ 0, /* U+0022cc: RIGHT SEMIDIRECT PRODUCT*/
+ 0, /* U+0022cd: REVERSED TILDE EQUALS*/
+ 0, /* U+0022ce: CURLY LOGICAL OR*/
+ 0, /* U+0022cf: CURLY LOGICAL AND*/
+ 0, /* U+0022d0: DOUBLE SUBSET*/
+ 0, /* U+0022d1: DOUBLE SUPERSET*/
+ 0, /* U+0022d2: DOUBLE INTERSECTION*/
+ 0, /* U+0022d3: DOUBLE UNION*/
+ 0, /* U+0022d4: PITCHFORK*/
+ 0, /* U+0022d5: EQUAL AND PARALLEL TO*/
+ 0, /* U+0022d6: LESS-THAN WITH DOT*/
+ 0, /* U+0022d7: GREATER-THAN WITH DOT*/
+ 0, /* U+0022d8: VERY MUCH LESS-THAN*/
+ 0, /* U+0022d9: VERY MUCH GREATER-THAN*/
+ 0, /* U+0022da: LESS-THAN EQUAL TO OR GREATER-THAN*/
+ 0, /* U+0022db: GREATER-THAN EQUAL TO OR LESS-THAN*/
+ 0, /* U+0022dc: EQUAL TO OR LESS-THAN*/
+ 0, /* U+0022dd: EQUAL TO OR GREATER-THAN*/
+ 0, /* U+0022de: EQUAL TO OR PRECEDES*/
+ 0, /* U+0022df: EQUAL TO OR SUCCEEDS*/
+ 0, /* U+0022e0: DOES NOT PRECEDE OR EQUAL*/
+ 0, /* U+0022e1: DOES NOT SUCCEED OR EQUAL*/
+ 0, /* U+0022e2: NOT SQUARE IMAGE OF OR EQUAL TO*/
+ 0, /* U+0022e3: NOT SQUARE ORIGINAL OF OR EQUAL TO*/
+ 0, /* U+0022e4: SQUARE IMAGE OF OR NOT EQUAL TO*/
+ 0, /* U+0022e5: SQUARE ORIGINAL OF OR NOT EQUAL TO*/
+ 0, /* U+0022e6: LESS-THAN BUT NOT EQUIVALENT TO*/
+ 0, /* U+0022e7: GREATER-THAN BUT NOT EQUIVALENT TO*/
+ 0, /* U+0022e8: PRECEDES BUT NOT EQUIVALENT TO*/
+ 0, /* U+0022e9: SUCCEEDS BUT NOT EQUIVALENT TO*/
+ 0, /* U+0022ea: NOT NORMAL SUBGROUP OF*/
+ 0, /* U+0022eb: DOES NOT CONTAIN AS NORMAL SUBGROUP*/
+ 0, /* U+0022ec: NOT NORMAL SUBGROUP OF OR EQUAL TO*/
+ 0, /* U+0022ed: DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL*/
+ 0, /* U+0022ee: VERTICAL ELLIPSIS*/
+ 0, /* U+0022ef: MIDLINE HORIZONTAL ELLIPSIS*/
+ 0, /* U+0022f0: UP RIGHT DIAGONAL ELLIPSIS*/
+ 0, /* U+0022f1: DOWN RIGHT DIAGONAL ELLIPSIS*/
+ 0, /* U+0022f2: ELEMENT OF WITH LONG HORIZONTAL STROKE*/
+ 0, /* U+0022f3: ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0, /* U+0022f4: SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0, /* U+0022f5: ELEMENT OF WITH DOT ABOVE*/
+ 0, /* U+0022f6: ELEMENT OF WITH OVERBAR*/
+ 0, /* U+0022f7: SMALL ELEMENT OF WITH OVERBAR*/
+ 0, /* U+0022f8: ELEMENT OF WITH UNDERBAR*/
+ 0, /* U+0022f9: ELEMENT OF WITH TWO HORIZONTAL STROKES*/
+ 0, /* U+0022fa: CONTAINS WITH LONG HORIZONTAL STROKE*/
+ 0, /* U+0022fb: CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0, /* U+0022fc: SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0, /* U+0022fd: CONTAINS WITH OVERBAR*/
+ 0, /* U+0022fe: SMALL CONTAINS WITH OVERBAR*/
+ 0, /* U+0022ff: Z NOTATION BAG MEMBERSHIP*/
+ 0, /* U+002300: DIAMETER SIGN*/
+ 0, /* U+002301: ELECTRIC ARROW*/
+ 0, /* U+002302: HOUSE*/
+ 0, /* U+002303: UP ARROWHEAD*/
+ 0, /* U+002304: DOWN ARROWHEAD*/
+ 0, /* U+002305: PROJECTIVE*/
+ 0, /* U+002306: PERSPECTIVE*/
+ 0, /* U+002307: WAVY LINE*/
+ 0, /* U+002308: LEFT CEILING*/
+ 0, /* U+002309: RIGHT CEILING*/
+ 0, /* U+00230a: LEFT FLOOR*/
+ 0, /* U+00230b: RIGHT FLOOR*/
+ 0, /* U+00230c: BOTTOM RIGHT CROP*/
+ 0, /* U+00230d: BOTTOM LEFT CROP*/
+ 0, /* U+00230e: TOP RIGHT CROP*/
+ 0, /* U+00230f: TOP LEFT CROP*/
+ 0, /* U+002310: REVERSED NOT SIGN*/
+ 0, /* U+002311: SQUARE LOZENGE*/
+ 0, /* U+002312: ARC*/
+ 0, /* U+002313: SEGMENT*/
+ 0, /* U+002314: SECTOR*/
+ 0, /* U+002315: TELEPHONE RECORDER*/
+ 0, /* U+002316: POSITION INDICATOR*/
+ 0, /* U+002317: VIEWDATA SQUARE*/
+ 0, /* U+002318: PLACE OF INTEREST SIGN*/
+ 0, /* U+002319: TURNED NOT SIGN*/
+ 0, /* U+00231a: WATCH*/
+ 0, /* U+00231b: HOURGLASS*/
+ 0, /* U+00231c: TOP LEFT CORNER*/
+ 0, /* U+00231d: TOP RIGHT CORNER*/
+ 0, /* U+00231e: BOTTOM LEFT CORNER*/
+ 0, /* U+00231f: BOTTOM RIGHT CORNER*/
+ 0, /* U+002320: TOP HALF INTEGRAL*/
+ 0, /* U+002321: BOTTOM HALF INTEGRAL*/
+ 0, /* U+002322: FROWN*/
+ 0, /* U+002323: SMILE*/
+ 0, /* U+002324: UP ARROWHEAD BETWEEN TWO HORIZONTAL BARS*/
+ 0, /* U+002325: OPTION KEY*/
+ 0, /* U+002326: ERASE TO THE RIGHT*/
+ 0, /* U+002327: X IN A RECTANGLE BOX*/
+ 0, /* U+002328: KEYBOARD*/
+ 0, /* U+002329: LEFT-POINTING ANGLE BRACKET*/
+ 0, /* U+00232a: RIGHT-POINTING ANGLE BRACKET*/
+ 0, /* U+00232b: ERASE TO THE LEFT*/
+ 0, /* U+00232c: BENZENE RING*/
+ 0, /* U+00232d: CYLINDRICITY*/
+ 0, /* U+00232e: ALL AROUND-PROFILE*/
+ 0, /* U+00232f: SYMMETRY*/
+ 0, /* U+002330: TOTAL RUNOUT*/
+ 0, /* U+002331: DIMENSION ORIGIN*/
+ 0, /* U+002332: CONICAL TAPER*/
+ 0, /* U+002333: SLOPE*/
+ 0, /* U+002334: COUNTERBORE*/
+ 0, /* U+002335: COUNTERSINK*/
+ 0, /* U+002336: APL FUNCTIONAL SYMBOL I-BEAM*/
+ 0, /* U+002337: APL FUNCTIONAL SYMBOL SQUISH QUAD*/
+ 0, /* U+002338: APL FUNCTIONAL SYMBOL QUAD EQUAL*/
+ 0, /* U+002339: APL FUNCTIONAL SYMBOL QUAD DIVIDE*/
+ 0, /* U+00233a: APL FUNCTIONAL SYMBOL QUAD DIAMOND*/
+ 0, /* U+00233b: APL FUNCTIONAL SYMBOL QUAD JOT*/
+ 0, /* U+00233c: APL FUNCTIONAL SYMBOL QUAD CIRCLE*/
+ 0, /* U+00233d: APL FUNCTIONAL SYMBOL CIRCLE STILE*/
+ 0, /* U+00233e: APL FUNCTIONAL SYMBOL CIRCLE JOT*/
+ 0, /* U+00233f: APL FUNCTIONAL SYMBOL SLASH BAR*/
+ 0, /* U+002340: APL FUNCTIONAL SYMBOL BACKSLASH BAR*/
+ 0, /* U+002341: APL FUNCTIONAL SYMBOL QUAD SLASH*/
+ 0, /* U+002342: APL FUNCTIONAL SYMBOL QUAD BACKSLASH*/
+ 0, /* U+002343: APL FUNCTIONAL SYMBOL QUAD LESS-THAN*/
+ 0, /* U+002344: APL FUNCTIONAL SYMBOL QUAD GREATER-THAN*/
+ 0, /* U+002345: APL FUNCTIONAL SYMBOL LEFTWARDS VANE*/
+ 0, /* U+002346: APL FUNCTIONAL SYMBOL RIGHTWARDS VANE*/
+ 0, /* U+002347: APL FUNCTIONAL SYMBOL QUAD LEFTWARDS ARROW*/
+ 0, /* U+002348: APL FUNCTIONAL SYMBOL QUAD RIGHTWARDS ARROW*/
+ 0, /* U+002349: APL FUNCTIONAL SYMBOL CIRCLE BACKSLASH*/
+ 0, /* U+00234a: APL FUNCTIONAL SYMBOL DOWN TACK UNDERBAR*/
+ 0, /* U+00234b: APL FUNCTIONAL SYMBOL DELTA STILE*/
+ 0, /* U+00234c: APL FUNCTIONAL SYMBOL QUAD DOWN CARET*/
+ 0, /* U+00234d: APL FUNCTIONAL SYMBOL QUAD DELTA*/
+ 0, /* U+00234e: APL FUNCTIONAL SYMBOL DOWN TACK JOT*/
+ 0, /* U+00234f: APL FUNCTIONAL SYMBOL UPWARDS VANE*/
+ 0, /* U+002350: APL FUNCTIONAL SYMBOL QUAD UPWARDS ARROW*/
+ 0, /* U+002351: APL FUNCTIONAL SYMBOL UP TACK OVERBAR*/
+ 0, /* U+002352: APL FUNCTIONAL SYMBOL DEL STILE*/
+ 0, /* U+002353: APL FUNCTIONAL SYMBOL QUAD UP CARET*/
+ 0, /* U+002354: APL FUNCTIONAL SYMBOL QUAD DEL*/
+ 0, /* U+002355: APL FUNCTIONAL SYMBOL UP TACK JOT*/
+ 0, /* U+002356: APL FUNCTIONAL SYMBOL DOWNWARDS VANE*/
+ 0, /* U+002357: APL FUNCTIONAL SYMBOL QUAD DOWNWARDS ARROW*/
+ 0, /* U+002358: APL FUNCTIONAL SYMBOL QUOTE UNDERBAR*/
+ 0, /* U+002359: APL FUNCTIONAL SYMBOL DELTA UNDERBAR*/
+ 0, /* U+00235a: APL FUNCTIONAL SYMBOL DIAMOND UNDERBAR*/
+ 0, /* U+00235b: APL FUNCTIONAL SYMBOL JOT UNDERBAR*/
+ 0, /* U+00235c: APL FUNCTIONAL SYMBOL CIRCLE UNDERBAR*/
+ 0, /* U+00235d: APL FUNCTIONAL SYMBOL UP SHOE JOT*/
+ 0, /* U+00235e: APL FUNCTIONAL SYMBOL QUOTE QUAD*/
+ 0, /* U+00235f: APL FUNCTIONAL SYMBOL CIRCLE STAR*/
+ 0, /* U+002360: APL FUNCTIONAL SYMBOL QUAD COLON*/
+ 0, /* U+002361: APL FUNCTIONAL SYMBOL UP TACK DIAERESIS*/
+ 0, /* U+002362: APL FUNCTIONAL SYMBOL DEL DIAERESIS*/
+ 0, /* U+002363: APL FUNCTIONAL SYMBOL STAR DIAERESIS*/
+ 0, /* U+002364: APL FUNCTIONAL SYMBOL JOT DIAERESIS*/
+ 0, /* U+002365: APL FUNCTIONAL SYMBOL CIRCLE DIAERESIS*/
+ 0, /* U+002366: APL FUNCTIONAL SYMBOL DOWN SHOE STILE*/
+ 0, /* U+002367: APL FUNCTIONAL SYMBOL LEFT SHOE STILE*/
+ 0, /* U+002368: APL FUNCTIONAL SYMBOL TILDE DIAERESIS*/
+ 0, /* U+002369: APL FUNCTIONAL SYMBOL GREATER-THAN DIAERESIS*/
+ 0, /* U+00236a: APL FUNCTIONAL SYMBOL COMMA BAR*/
+ 0, /* U+00236b: APL FUNCTIONAL SYMBOL DEL TILDE*/
+ 0, /* U+00236c: APL FUNCTIONAL SYMBOL ZILDE*/
+ 0, /* U+00236d: APL FUNCTIONAL SYMBOL STILE TILDE*/
+ 0, /* U+00236e: APL FUNCTIONAL SYMBOL SEMICOLON UNDERBAR*/
+ 0, /* U+00236f: APL FUNCTIONAL SYMBOL QUAD NOT EQUAL*/
+ 0, /* U+002370: APL FUNCTIONAL SYMBOL QUAD QUESTION*/
+ 0, /* U+002371: APL FUNCTIONAL SYMBOL DOWN CARET TILDE*/
+ 0, /* U+002372: APL FUNCTIONAL SYMBOL UP CARET TILDE*/
+ 0, /* U+002373: APL FUNCTIONAL SYMBOL IOTA*/
+ 0, /* U+002374: APL FUNCTIONAL SYMBOL RHO*/
+ 0, /* U+002375: APL FUNCTIONAL SYMBOL OMEGA*/
+ 0, /* U+002376: APL FUNCTIONAL SYMBOL ALPHA UNDERBAR*/
+ 0, /* U+002377: APL FUNCTIONAL SYMBOL EPSILON UNDERBAR*/
+ 0, /* U+002378: APL FUNCTIONAL SYMBOL IOTA UNDERBAR*/
+ 0, /* U+002379: APL FUNCTIONAL SYMBOL OMEGA UNDERBAR*/
+ 0, /* U+00237a: APL FUNCTIONAL SYMBOL ALPHA*/
+ 0, /* U+00237b: NOT CHECK MARK*/
+ 0, /* U+00237c: RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW*/
+ 0, /* U+00237d: SHOULDERED OPEN BOX*/
+ 0, /* U+00237e: BELL SYMBOL*/
+ 0, /* U+00237f: VERTICAL LINE WITH MIDDLE DOT*/
+ 0, /* U+002380: INSERTION SYMBOL*/
+ 0, /* U+002381: CONTINUOUS UNDERLINE SYMBOL*/
+ 0, /* U+002382: DISCONTINUOUS UNDERLINE SYMBOL*/
+ 0, /* U+002383: EMPHASIS SYMBOL*/
+ 0, /* U+002384: COMPOSITION SYMBOL*/
+ 0, /* U+002385: WHITE SQUARE WITH CENTRE VERTICAL LINE*/
+ 0, /* U+002386: ENTER SYMBOL*/
+ 0, /* U+002387: ALTERNATIVE KEY SYMBOL*/
+ 0, /* U+002388: HELM SYMBOL*/
+ 0, /* U+002389: CIRCLED HORIZONTAL BAR WITH NOTCH*/
+ 0, /* U+00238a: CIRCLED TRIANGLE DOWN*/
+ 0, /* U+00238b: BROKEN CIRCLE WITH NORTHWEST ARROW*/
+ 0, /* U+00238c: UNDO SYMBOL*/
+ 0, /* U+00238d: MONOSTABLE SYMBOL*/
+ 0, /* U+00238e: HYSTERESIS SYMBOL*/
+ 0, /* U+00238f: OPEN-CIRCUIT-OUTPUT H-TYPE SYMBOL*/
+ 0, /* U+002390: OPEN-CIRCUIT-OUTPUT L-TYPE SYMBOL*/
+ 0, /* U+002391: PASSIVE-PULL-DOWN-OUTPUT SYMBOL*/
+ 0, /* U+002392: PASSIVE-PULL-UP-OUTPUT SYMBOL*/
+ 0, /* U+002393: DIRECT CURRENT SYMBOL FORM TWO*/
+ 0, /* U+002394: SOFTWARE-FUNCTION SYMBOL*/
+ 0, /* U+002395: APL FUNCTIONAL SYMBOL QUAD*/
+ 0, /* U+002396: DECIMAL SEPARATOR KEY SYMBOL*/
+ 0, /* U+002397: PREVIOUS PAGE*/
+ 0, /* U+002398: NEXT PAGE*/
+ 0, /* U+002399: PRINT SCREEN SYMBOL*/
+ 0, /* U+00239a: CLEAR SCREEN SYMBOL*/
+ 0, /* U+00239b: LEFT PARENTHESIS UPPER HOOK*/
+ 0, /* U+00239c: LEFT PARENTHESIS EXTENSION*/
+ 0, /* U+00239d: LEFT PARENTHESIS LOWER HOOK*/
+ 0, /* U+00239e: RIGHT PARENTHESIS UPPER HOOK*/
+ 0, /* U+00239f: RIGHT PARENTHESIS EXTENSION*/
+ 0, /* U+0023a0: RIGHT PARENTHESIS LOWER HOOK*/
+ 0, /* U+0023a1: LEFT SQUARE BRACKET UPPER CORNER*/
+ 0, /* U+0023a2: LEFT SQUARE BRACKET EXTENSION*/
+ 0, /* U+0023a3: LEFT SQUARE BRACKET LOWER CORNER*/
+ 0, /* U+0023a4: RIGHT SQUARE BRACKET UPPER CORNER*/
+ 0, /* U+0023a5: RIGHT SQUARE BRACKET EXTENSION*/
+ 0, /* U+0023a6: RIGHT SQUARE BRACKET LOWER CORNER*/
+ 0, /* U+0023a7: LEFT CURLY BRACKET UPPER HOOK*/
+ 0, /* U+0023a8: LEFT CURLY BRACKET MIDDLE PIECE*/
+ 0, /* U+0023a9: LEFT CURLY BRACKET LOWER HOOK*/
+ 0, /* U+0023aa: CURLY BRACKET EXTENSION*/
+ 0, /* U+0023ab: RIGHT CURLY BRACKET UPPER HOOK*/
+ 0, /* U+0023ac: RIGHT CURLY BRACKET MIDDLE PIECE*/
+ 0, /* U+0023ad: RIGHT CURLY BRACKET LOWER HOOK*/
+ 0, /* U+0023ae: INTEGRAL EXTENSION*/
+ 0, /* U+0023af: HORIZONTAL LINE EXTENSION*/
+ 0, /* U+0023b0: UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION*/
+ 0, /* U+0023b1: UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION*/
+ 0, /* U+0023b2: SUMMATION TOP*/
+ 0, /* U+0023b3: SUMMATION BOTTOM*/
+ 0, /* U+0023b4: TOP SQUARE BRACKET*/
+ 0, /* U+0023b5: BOTTOM SQUARE BRACKET*/
+ 0, /* U+0023b6: BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET*/
+ 0, /* U+0023b7: RADICAL SYMBOL BOTTOM*/
+ 0, /* U+0023b8: LEFT VERTICAL BOX LINE*/
+ 0, /* U+0023b9: RIGHT VERTICAL BOX LINE*/
+ 0, /* U+0023ba: HORIZONTAL SCAN LINE-1*/
+ 0, /* U+0023bb: HORIZONTAL SCAN LINE-3*/
+ 0, /* U+0023bc: HORIZONTAL SCAN LINE-7*/
+ 0, /* U+0023bd: HORIZONTAL SCAN LINE-9*/
+ 0, /* U+0023be: DENTISTRY SYMBOL LIGHT VERTICAL AND TOP RIGHT*/
+ 0, /* U+0023bf: DENTISTRY SYMBOL LIGHT VERTICAL AND BOTTOM RIGHT*/
+ 0, /* U+0023c0: DENTISTRY SYMBOL LIGHT VERTICAL WITH CIRCLE*/
+ 0, /* U+0023c1: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH CIRCLE*/
+ 0, /* U+0023c2: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH CIRCLE*/
+ 0, /* U+0023c3: DENTISTRY SYMBOL LIGHT VERTICAL WITH TRIANGLE*/
+ 0, /* U+0023c4: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH TRIANGLE*/
+ 0, /* U+0023c5: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH TRIANGLE*/
+ 0, /* U+0023c6: DENTISTRY SYMBOL LIGHT VERTICAL AND WAVE*/
+ 0, /* U+0023c7: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH WAVE*/
+ 0, /* U+0023c8: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH WAVE*/
+ 0, /* U+0023c9: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL*/
+ 0, /* U+0023ca: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL*/
+ 0, /* U+0023cb: DENTISTRY SYMBOL LIGHT VERTICAL AND TOP LEFT*/
+ 0, /* U+0023cc: DENTISTRY SYMBOL LIGHT VERTICAL AND BOTTOM LEFT*/
+ 0, /* U+0023cd: SQUARE FOOT*/
+ 0, /* U+0023ce: RETURN SYMBOL*/
+ 0, /* U+0023cf: EJECT SYMBOL*/
+ 0, /* U+0023d0: VERTICAL LINE EXTENSION*/
+ 0, /* U+0023d1: METRICAL BREVE*/
+ 0, /* U+0023d2: METRICAL LONG OVER SHORT*/
+ 0, /* U+0023d3: METRICAL SHORT OVER LONG*/
+ 0, /* U+0023d4: METRICAL LONG OVER TWO SHORTS*/
+ 0, /* U+0023d5: METRICAL TWO SHORTS OVER LONG*/
+ 0, /* U+0023d6: METRICAL TWO SHORTS JOINED*/
+ 0, /* U+0023d7: METRICAL TRISEME*/
+ 0, /* U+0023d8: METRICAL TETRASEME*/
+ 0, /* U+0023d9: METRICAL PENTASEME*/
+ 0, /* U+0023da: EARTH GROUND*/
+ 0, /* U+0023db: FUSE*/
+ 0, /* U+0023dc: TOP PARENTHESIS*/
+ 0, /* U+0023dd: BOTTOM PARENTHESIS*/
+ 0, /* U+0023de: TOP CURLY BRACKET*/
+ 0, /* U+0023df: BOTTOM CURLY BRACKET*/
+ 0, /* U+0023e0: TOP TORTOISE SHELL BRACKET*/
+ 0, /* U+0023e1: BOTTOM TORTOISE SHELL BRACKET*/
+ 0, /* U+0023e2: WHITE TRAPEZIUM*/
+ 0, /* U+0023e3: BENZENE RING WITH CIRCLE*/
+ 0, /* U+0023e4: STRAIGHTNESS*/
+ 0, /* U+0023e5: FLATNESS*/
+ 0, /* U+0023e6: AC CURRENT*/
+ 0, /* U+0023e7: ELECTRICAL INTERSECTION*/
+ 0, /* U+0023e8: DECIMAL EXPONENT SYMBOL*/
+ 0, /* U+0023e9: BLACK RIGHT-POINTING DOUBLE TRIANGLE*/
+ 0, /* U+0023ea: BLACK LEFT-POINTING DOUBLE TRIANGLE*/
+ 0, /* U+0023eb: BLACK UP-POINTING DOUBLE TRIANGLE*/
+ 0, /* U+0023ec: BLACK DOWN-POINTING DOUBLE TRIANGLE*/
+ 0, /* U+0023ed: BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR*/
+ 0, /* U+0023ee: BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR*/
+ 0, /* U+0023ef: BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR*/
+ 0, /* U+0023f0: ALARM CLOCK*/
+ 0, /* U+0023f1: STOPWATCH*/
+ 0, /* U+0023f2: TIMER CLOCK*/
+ 0, /* U+0023f3: HOURGLASS WITH FLOWING SAND*/
+ 0, /* U+0023f4: */
+ 0, /* U+0023f5: */
+ 0, /* U+0023f6: */
+ 0, /* U+0023f7: */
+ 0, /* U+0023f8: */
+ 0, /* U+0023f9: */
+ 0, /* U+0023fa: */
+ 0, /* U+0023fb: */
+ 0, /* U+0023fc: */
+ 0, /* U+0023fd: */
+ 0, /* U+0023fe: */
+ 0, /* U+0023ff: */
+ 0, /* U+002400: SYMBOL FOR NULL*/
+ 0, /* U+002401: SYMBOL FOR START OF HEADING*/
+ 0, /* U+002402: SYMBOL FOR START OF TEXT*/
+ 0, /* U+002403: SYMBOL FOR END OF TEXT*/
+ 0, /* U+002404: SYMBOL FOR END OF TRANSMISSION*/
+ 0, /* U+002405: SYMBOL FOR ENQUIRY*/
+ 0, /* U+002406: SYMBOL FOR ACKNOWLEDGE*/
+ 0, /* U+002407: SYMBOL FOR BELL*/
+ 0, /* U+002408: SYMBOL FOR BACKSPACE*/
+ 0, /* U+002409: SYMBOL FOR HORIZONTAL TABULATION*/
+ 0, /* U+00240a: SYMBOL FOR LINE FEED*/
+ 0, /* U+00240b: SYMBOL FOR VERTICAL TABULATION*/
+ 0, /* U+00240c: SYMBOL FOR FORM FEED*/
+ 0, /* U+00240d: SYMBOL FOR CARRIAGE RETURN*/
+ 0, /* U+00240e: SYMBOL FOR SHIFT OUT*/
+ 0, /* U+00240f: SYMBOL FOR SHIFT IN*/
+ 0, /* U+002410: SYMBOL FOR DATA LINK ESCAPE*/
+ 0, /* U+002411: SYMBOL FOR DEVICE CONTROL ONE*/
+ 0, /* U+002412: SYMBOL FOR DEVICE CONTROL TWO*/
+ 0, /* U+002413: SYMBOL FOR DEVICE CONTROL THREE*/
+ 0, /* U+002414: SYMBOL FOR DEVICE CONTROL FOUR*/
+ 0, /* U+002415: SYMBOL FOR NEGATIVE ACKNOWLEDGE*/
+ 0, /* U+002416: SYMBOL FOR SYNCHRONOUS IDLE*/
+ 0, /* U+002417: SYMBOL FOR END OF TRANSMISSION BLOCK*/
+ 0, /* U+002418: SYMBOL FOR CANCEL*/
+ 0, /* U+002419: SYMBOL FOR END OF MEDIUM*/
+ 0, /* U+00241a: SYMBOL FOR SUBSTITUTE*/
+ 0, /* U+00241b: SYMBOL FOR ESCAPE*/
+ 0, /* U+00241c: SYMBOL FOR FILE SEPARATOR*/
+ 0, /* U+00241d: SYMBOL FOR GROUP SEPARATOR*/
+ 0, /* U+00241e: SYMBOL FOR RECORD SEPARATOR*/
+ 0, /* U+00241f: SYMBOL FOR UNIT SEPARATOR*/
+ 0, /* U+002420: SYMBOL FOR SPACE*/
+ 0, /* U+002421: SYMBOL FOR DELETE*/
+ 0, /* U+002422: BLANK SYMBOL*/
+ 0, /* U+002423: OPEN BOX*/
+ 0, /* U+002424: SYMBOL FOR NEWLINE*/
+ 0, /* U+002425: SYMBOL FOR DELETE FORM TWO*/
+ 0, /* U+002426: SYMBOL FOR SUBSTITUTE FORM TWO*/
+ 0, /* U+002427: */
+ 0, /* U+002428: */
+ 0, /* U+002429: */
+ 0, /* U+00242a: */
+ 0, /* U+00242b: */
+ 0, /* U+00242c: */
+ 0, /* U+00242d: */
+ 0, /* U+00242e: */
+ 0, /* U+00242f: */
+ 0, /* U+002430: */
+ 0, /* U+002431: */
+ 0, /* U+002432: */
+ 0, /* U+002433: */
+ 0, /* U+002434: */
+ 0, /* U+002435: */
+ 0, /* U+002436: */
+ 0, /* U+002437: */
+ 0, /* U+002438: */
+ 0, /* U+002439: */
+ 0, /* U+00243a: */
+ 0, /* U+00243b: */
+ 0, /* U+00243c: */
+ 0, /* U+00243d: */
+ 0, /* U+00243e: */
+ 0, /* U+00243f: */
+ 0, /* U+002440: OCR HOOK*/
+ 0, /* U+002441: OCR CHAIR*/
+ 0, /* U+002442: OCR FORK*/
+ 0, /* U+002443: OCR INVERTED FORK*/
+ 0, /* U+002444: OCR BELT BUCKLE*/
+ 0, /* U+002445: OCR BOW TIE*/
+ 0, /* U+002446: OCR BRANCH BANK IDENTIFICATION*/
+ 0, /* U+002447: OCR AMOUNT OF CHECK*/
+ 0, /* U+002448: OCR DASH*/
+ 0, /* U+002449: OCR CUSTOMER ACCOUNT NUMBER*/
+ 0, /* U+00244a: OCR DOUBLE BACKSLASH*/
+ 0, /* U+00244b: */
+ 0, /* U+00244c: */
+ 0, /* U+00244d: */
+ 0, /* U+00244e: */
+ 0, /* U+00244f: */
+ 0, /* U+002450: */
+ 0, /* U+002451: */
+ 0, /* U+002452: */
+ 0, /* U+002453: */
+ 0, /* U+002454: */
+ 0, /* U+002455: */
+ 0, /* U+002456: */
+ 0, /* U+002457: */
+ 0, /* U+002458: */
+ 0, /* U+002459: */
+ 0, /* U+00245a: */
+ 0, /* U+00245b: */
+ 0, /* U+00245c: */
+ 0, /* U+00245d: */
+ 0, /* U+00245e: */
+ 0, /* U+00245f: */
+ 0, /* U+002460: CIRCLED DIGIT ONE*/
+ 0, /* U+002461: CIRCLED DIGIT TWO*/
+ 0, /* U+002462: CIRCLED DIGIT THREE*/
+ 0, /* U+002463: CIRCLED DIGIT FOUR*/
+ 0, /* U+002464: CIRCLED DIGIT FIVE*/
+ 0, /* U+002465: CIRCLED DIGIT SIX*/
+ 0, /* U+002466: CIRCLED DIGIT SEVEN*/
+ 0, /* U+002467: CIRCLED DIGIT EIGHT*/
+ 0, /* U+002468: CIRCLED DIGIT NINE*/
+ 0, /* U+002469: CIRCLED NUMBER TEN*/
+ 0, /* U+00246a: CIRCLED NUMBER ELEVEN*/
+ 0, /* U+00246b: CIRCLED NUMBER TWELVE*/
+ 0, /* U+00246c: CIRCLED NUMBER THIRTEEN*/
+ 0, /* U+00246d: CIRCLED NUMBER FOURTEEN*/
+ 0, /* U+00246e: CIRCLED NUMBER FIFTEEN*/
+ 0, /* U+00246f: CIRCLED NUMBER SIXTEEN*/
+ 0, /* U+002470: CIRCLED NUMBER SEVENTEEN*/
+ 0, /* U+002471: CIRCLED NUMBER EIGHTEEN*/
+ 0, /* U+002472: CIRCLED NUMBER NINETEEN*/
+ 0, /* U+002473: CIRCLED NUMBER TWENTY*/
+ 0, /* U+002474: PARENTHESIZED DIGIT ONE*/
+ 0, /* U+002475: PARENTHESIZED DIGIT TWO*/
+ 0, /* U+002476: PARENTHESIZED DIGIT THREE*/
+ 0, /* U+002477: PARENTHESIZED DIGIT FOUR*/
+ 0, /* U+002478: PARENTHESIZED DIGIT FIVE*/
+ 0, /* U+002479: PARENTHESIZED DIGIT SIX*/
+ 0, /* U+00247a: PARENTHESIZED DIGIT SEVEN*/
+ 0, /* U+00247b: PARENTHESIZED DIGIT EIGHT*/
+ 0, /* U+00247c: PARENTHESIZED DIGIT NINE*/
+ 0, /* U+00247d: PARENTHESIZED NUMBER TEN*/
+ 0, /* U+00247e: PARENTHESIZED NUMBER ELEVEN*/
+ 0, /* U+00247f: PARENTHESIZED NUMBER TWELVE*/
+ 0, /* U+002480: PARENTHESIZED NUMBER THIRTEEN*/
+ 0, /* U+002481: PARENTHESIZED NUMBER FOURTEEN*/
+ 0, /* U+002482: PARENTHESIZED NUMBER FIFTEEN*/
+ 0, /* U+002483: PARENTHESIZED NUMBER SIXTEEN*/
+ 0, /* U+002484: PARENTHESIZED NUMBER SEVENTEEN*/
+ 0, /* U+002485: PARENTHESIZED NUMBER EIGHTEEN*/
+ 0, /* U+002486: PARENTHESIZED NUMBER NINETEEN*/
+ 0, /* U+002487: PARENTHESIZED NUMBER TWENTY*/
+ 0, /* U+002488: DIGIT ONE FULL STOP*/
+ 0, /* U+002489: DIGIT TWO FULL STOP*/
+ 0, /* U+00248a: DIGIT THREE FULL STOP*/
+ 0, /* U+00248b: DIGIT FOUR FULL STOP*/
+ 0, /* U+00248c: DIGIT FIVE FULL STOP*/
+ 0, /* U+00248d: DIGIT SIX FULL STOP*/
+ 0, /* U+00248e: DIGIT SEVEN FULL STOP*/
+ 0, /* U+00248f: DIGIT EIGHT FULL STOP*/
+ 0, /* U+002490: DIGIT NINE FULL STOP*/
+ 0, /* U+002491: NUMBER TEN FULL STOP*/
+ 0, /* U+002492: NUMBER ELEVEN FULL STOP*/
+ 0, /* U+002493: NUMBER TWELVE FULL STOP*/
+ 0, /* U+002494: NUMBER THIRTEEN FULL STOP*/
+ 0, /* U+002495: NUMBER FOURTEEN FULL STOP*/
+ 0, /* U+002496: NUMBER FIFTEEN FULL STOP*/
+ 0, /* U+002497: NUMBER SIXTEEN FULL STOP*/
+ 0, /* U+002498: NUMBER SEVENTEEN FULL STOP*/
+ 0, /* U+002499: NUMBER EIGHTEEN FULL STOP*/
+ 0, /* U+00249a: NUMBER NINETEEN FULL STOP*/
+ 0, /* U+00249b: NUMBER TWENTY FULL STOP*/
+ 0, /* U+00249c: PARENTHESIZED LATIN SMALL LETTER A*/
+ 0, /* U+00249d: PARENTHESIZED LATIN SMALL LETTER B*/
+ 0, /* U+00249e: PARENTHESIZED LATIN SMALL LETTER C*/
+ 0, /* U+00249f: PARENTHESIZED LATIN SMALL LETTER D*/
+ 0, /* U+0024a0: PARENTHESIZED LATIN SMALL LETTER E*/
+ 0, /* U+0024a1: PARENTHESIZED LATIN SMALL LETTER F*/
+ 0, /* U+0024a2: PARENTHESIZED LATIN SMALL LETTER G*/
+ 0, /* U+0024a3: PARENTHESIZED LATIN SMALL LETTER H*/
+ 0, /* U+0024a4: PARENTHESIZED LATIN SMALL LETTER I*/
+ 0, /* U+0024a5: PARENTHESIZED LATIN SMALL LETTER J*/
+ 0, /* U+0024a6: PARENTHESIZED LATIN SMALL LETTER K*/
+ 0, /* U+0024a7: PARENTHESIZED LATIN SMALL LETTER L*/
+ 0, /* U+0024a8: PARENTHESIZED LATIN SMALL LETTER M*/
+ 0, /* U+0024a9: PARENTHESIZED LATIN SMALL LETTER N*/
+ 0, /* U+0024aa: PARENTHESIZED LATIN SMALL LETTER O*/
+ 0, /* U+0024ab: PARENTHESIZED LATIN SMALL LETTER P*/
+ 0, /* U+0024ac: PARENTHESIZED LATIN SMALL LETTER Q*/
+ 0, /* U+0024ad: PARENTHESIZED LATIN SMALL LETTER R*/
+ 0, /* U+0024ae: PARENTHESIZED LATIN SMALL LETTER S*/
+ 0, /* U+0024af: PARENTHESIZED LATIN SMALL LETTER T*/
+ 0, /* U+0024b0: PARENTHESIZED LATIN SMALL LETTER U*/
+ 0, /* U+0024b1: PARENTHESIZED LATIN SMALL LETTER V*/
+ 0, /* U+0024b2: PARENTHESIZED LATIN SMALL LETTER W*/
+ 0, /* U+0024b3: PARENTHESIZED LATIN SMALL LETTER X*/
+ 0, /* U+0024b4: PARENTHESIZED LATIN SMALL LETTER Y*/
+ 0, /* U+0024b5: PARENTHESIZED LATIN SMALL LETTER Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024b6: CIRCLED LATIN CAPITAL LETTER A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024b7: CIRCLED LATIN CAPITAL LETTER B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024b8: CIRCLED LATIN CAPITAL LETTER C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024b9: CIRCLED LATIN CAPITAL LETTER D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024ba: CIRCLED LATIN CAPITAL LETTER E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024bb: CIRCLED LATIN CAPITAL LETTER F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024bc: CIRCLED LATIN CAPITAL LETTER G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024bd: CIRCLED LATIN CAPITAL LETTER H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024be: CIRCLED LATIN CAPITAL LETTER I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024bf: CIRCLED LATIN CAPITAL LETTER J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c0: CIRCLED LATIN CAPITAL LETTER K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c1: CIRCLED LATIN CAPITAL LETTER L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c2: CIRCLED LATIN CAPITAL LETTER M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c3: CIRCLED LATIN CAPITAL LETTER N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c4: CIRCLED LATIN CAPITAL LETTER O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c5: CIRCLED LATIN CAPITAL LETTER P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c6: CIRCLED LATIN CAPITAL LETTER Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c7: CIRCLED LATIN CAPITAL LETTER R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c8: CIRCLED LATIN CAPITAL LETTER S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024c9: CIRCLED LATIN CAPITAL LETTER T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024ca: CIRCLED LATIN CAPITAL LETTER U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024cb: CIRCLED LATIN CAPITAL LETTER V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024cc: CIRCLED LATIN CAPITAL LETTER W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024cd: CIRCLED LATIN CAPITAL LETTER X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024ce: CIRCLED LATIN CAPITAL LETTER Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+0024cf: CIRCLED LATIN CAPITAL LETTER Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d0: CIRCLED LATIN SMALL LETTER A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d1: CIRCLED LATIN SMALL LETTER B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d2: CIRCLED LATIN SMALL LETTER C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d3: CIRCLED LATIN SMALL LETTER D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d4: CIRCLED LATIN SMALL LETTER E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d5: CIRCLED LATIN SMALL LETTER F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d6: CIRCLED LATIN SMALL LETTER G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d7: CIRCLED LATIN SMALL LETTER H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d8: CIRCLED LATIN SMALL LETTER I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024d9: CIRCLED LATIN SMALL LETTER J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024da: CIRCLED LATIN SMALL LETTER K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024db: CIRCLED LATIN SMALL LETTER L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024dc: CIRCLED LATIN SMALL LETTER M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024dd: CIRCLED LATIN SMALL LETTER N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024de: CIRCLED LATIN SMALL LETTER O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024df: CIRCLED LATIN SMALL LETTER P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e0: CIRCLED LATIN SMALL LETTER Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e1: CIRCLED LATIN SMALL LETTER R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e2: CIRCLED LATIN SMALL LETTER S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e3: CIRCLED LATIN SMALL LETTER T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e4: CIRCLED LATIN SMALL LETTER U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e5: CIRCLED LATIN SMALL LETTER V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e6: CIRCLED LATIN SMALL LETTER W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e7: CIRCLED LATIN SMALL LETTER X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e8: CIRCLED LATIN SMALL LETTER Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+0024e9: CIRCLED LATIN SMALL LETTER Z*/
+ 0, /* U+0024ea: CIRCLED DIGIT ZERO*/
+ 0, /* U+0024eb: NEGATIVE CIRCLED NUMBER ELEVEN*/
+ 0, /* U+0024ec: NEGATIVE CIRCLED NUMBER TWELVE*/
+ 0, /* U+0024ed: NEGATIVE CIRCLED NUMBER THIRTEEN*/
+ 0, /* U+0024ee: NEGATIVE CIRCLED NUMBER FOURTEEN*/
+ 0, /* U+0024ef: NEGATIVE CIRCLED NUMBER FIFTEEN*/
+ 0, /* U+0024f0: NEGATIVE CIRCLED NUMBER SIXTEEN*/
+ 0, /* U+0024f1: NEGATIVE CIRCLED NUMBER SEVENTEEN*/
+ 0, /* U+0024f2: NEGATIVE CIRCLED NUMBER EIGHTEEN*/
+ 0, /* U+0024f3: NEGATIVE CIRCLED NUMBER NINETEEN*/
+ 0, /* U+0024f4: NEGATIVE CIRCLED NUMBER TWENTY*/
+ 0, /* U+0024f5: DOUBLE CIRCLED DIGIT ONE*/
+ 0, /* U+0024f6: DOUBLE CIRCLED DIGIT TWO*/
+ 0, /* U+0024f7: DOUBLE CIRCLED DIGIT THREE*/
+ 0, /* U+0024f8: DOUBLE CIRCLED DIGIT FOUR*/
+ 0, /* U+0024f9: DOUBLE CIRCLED DIGIT FIVE*/
+ 0, /* U+0024fa: DOUBLE CIRCLED DIGIT SIX*/
+ 0, /* U+0024fb: DOUBLE CIRCLED DIGIT SEVEN*/
+ 0, /* U+0024fc: DOUBLE CIRCLED DIGIT EIGHT*/
+ 0, /* U+0024fd: DOUBLE CIRCLED DIGIT NINE*/
+ 0, /* U+0024fe: DOUBLE CIRCLED NUMBER TEN*/
+ 0, /* U+0024ff: NEGATIVE CIRCLED DIGIT ZERO*/
+ 0, /* U+002500: BOX DRAWINGS LIGHT HORIZONTAL*/
+ 0, /* U+002501: BOX DRAWINGS HEAVY HORIZONTAL*/
+ 0, /* U+002502: BOX DRAWINGS LIGHT VERTICAL*/
+ 0, /* U+002503: BOX DRAWINGS HEAVY VERTICAL*/
+ 0, /* U+002504: BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL*/
+ 0, /* U+002505: BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL*/
+ 0, /* U+002506: BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL*/
+ 0, /* U+002507: BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL*/
+ 0, /* U+002508: BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL*/
+ 0, /* U+002509: BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL*/
+ 0, /* U+00250a: BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL*/
+ 0, /* U+00250b: BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL*/
+ 0, /* U+00250c: BOX DRAWINGS LIGHT DOWN AND RIGHT*/
+ 0, /* U+00250d: BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY*/
+ 0, /* U+00250e: BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT*/
+ 0, /* U+00250f: BOX DRAWINGS HEAVY DOWN AND RIGHT*/
+ 0, /* U+002510: BOX DRAWINGS LIGHT DOWN AND LEFT*/
+ 0, /* U+002511: BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY*/
+ 0, /* U+002512: BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT*/
+ 0, /* U+002513: BOX DRAWINGS HEAVY DOWN AND LEFT*/
+ 0, /* U+002514: BOX DRAWINGS LIGHT UP AND RIGHT*/
+ 0, /* U+002515: BOX DRAWINGS UP LIGHT AND RIGHT HEAVY*/
+ 0, /* U+002516: BOX DRAWINGS UP HEAVY AND RIGHT LIGHT*/
+ 0, /* U+002517: BOX DRAWINGS HEAVY UP AND RIGHT*/
+ 0, /* U+002518: BOX DRAWINGS LIGHT UP AND LEFT*/
+ 0, /* U+002519: BOX DRAWINGS UP LIGHT AND LEFT HEAVY*/
+ 0, /* U+00251a: BOX DRAWINGS UP HEAVY AND LEFT LIGHT*/
+ 0, /* U+00251b: BOX DRAWINGS HEAVY UP AND LEFT*/
+ 0, /* U+00251c: BOX DRAWINGS LIGHT VERTICAL AND RIGHT*/
+ 0, /* U+00251d: BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY*/
+ 0, /* U+00251e: BOX DRAWINGS UP HEAVY AND RIGHT DOWN LIGHT*/
+ 0, /* U+00251f: BOX DRAWINGS DOWN HEAVY AND RIGHT UP LIGHT*/
+ 0, /* U+002520: BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT*/
+ 0, /* U+002521: BOX DRAWINGS DOWN LIGHT AND RIGHT UP HEAVY*/
+ 0, /* U+002522: BOX DRAWINGS UP LIGHT AND RIGHT DOWN HEAVY*/
+ 0, /* U+002523: BOX DRAWINGS HEAVY VERTICAL AND RIGHT*/
+ 0, /* U+002524: BOX DRAWINGS LIGHT VERTICAL AND LEFT*/
+ 0, /* U+002525: BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY*/
+ 0, /* U+002526: BOX DRAWINGS UP HEAVY AND LEFT DOWN LIGHT*/
+ 0, /* U+002527: BOX DRAWINGS DOWN HEAVY AND LEFT UP LIGHT*/
+ 0, /* U+002528: BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT*/
+ 0, /* U+002529: BOX DRAWINGS DOWN LIGHT AND LEFT UP HEAVY*/
+ 0, /* U+00252a: BOX DRAWINGS UP LIGHT AND LEFT DOWN HEAVY*/
+ 0, /* U+00252b: BOX DRAWINGS HEAVY VERTICAL AND LEFT*/
+ 0, /* U+00252c: BOX DRAWINGS LIGHT DOWN AND HORIZONTAL*/
+ 0, /* U+00252d: BOX DRAWINGS LEFT HEAVY AND RIGHT DOWN LIGHT*/
+ 0, /* U+00252e: BOX DRAWINGS RIGHT HEAVY AND LEFT DOWN LIGHT*/
+ 0, /* U+00252f: BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY*/
+ 0, /* U+002530: BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT*/
+ 0, /* U+002531: BOX DRAWINGS RIGHT LIGHT AND LEFT DOWN HEAVY*/
+ 0, /* U+002532: BOX DRAWINGS LEFT LIGHT AND RIGHT DOWN HEAVY*/
+ 0, /* U+002533: BOX DRAWINGS HEAVY DOWN AND HORIZONTAL*/
+ 0, /* U+002534: BOX DRAWINGS LIGHT UP AND HORIZONTAL*/
+ 0, /* U+002535: BOX DRAWINGS LEFT HEAVY AND RIGHT UP LIGHT*/
+ 0, /* U+002536: BOX DRAWINGS RIGHT HEAVY AND LEFT UP LIGHT*/
+ 0, /* U+002537: BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY*/
+ 0, /* U+002538: BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT*/
+ 0, /* U+002539: BOX DRAWINGS RIGHT LIGHT AND LEFT UP HEAVY*/
+ 0, /* U+00253a: BOX DRAWINGS LEFT LIGHT AND RIGHT UP HEAVY*/
+ 0, /* U+00253b: BOX DRAWINGS HEAVY UP AND HORIZONTAL*/
+ 0, /* U+00253c: BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL*/
+ 0, /* U+00253d: BOX DRAWINGS LEFT HEAVY AND RIGHT VERTICAL LIGHT*/
+ 0, /* U+00253e: BOX DRAWINGS RIGHT HEAVY AND LEFT VERTICAL LIGHT*/
+ 0, /* U+00253f: BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY*/
+ 0, /* U+002540: BOX DRAWINGS UP HEAVY AND DOWN HORIZONTAL LIGHT*/
+ 0, /* U+002541: BOX DRAWINGS DOWN HEAVY AND UP HORIZONTAL LIGHT*/
+ 0, /* U+002542: BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT*/
+ 0, /* U+002543: BOX DRAWINGS LEFT UP HEAVY AND RIGHT DOWN LIGHT*/
+ 0, /* U+002544: BOX DRAWINGS RIGHT UP HEAVY AND LEFT DOWN LIGHT*/
+ 0, /* U+002545: BOX DRAWINGS LEFT DOWN HEAVY AND RIGHT UP LIGHT*/
+ 0, /* U+002546: BOX DRAWINGS RIGHT DOWN HEAVY AND LEFT UP LIGHT*/
+ 0, /* U+002547: BOX DRAWINGS DOWN LIGHT AND UP HORIZONTAL HEAVY*/
+ 0, /* U+002548: BOX DRAWINGS UP LIGHT AND DOWN HORIZONTAL HEAVY*/
+ 0, /* U+002549: BOX DRAWINGS RIGHT LIGHT AND LEFT VERTICAL HEAVY*/
+ 0, /* U+00254a: BOX DRAWINGS LEFT LIGHT AND RIGHT VERTICAL HEAVY*/
+ 0, /* U+00254b: BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL*/
+ 0, /* U+00254c: BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL*/
+ 0, /* U+00254d: BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL*/
+ 0, /* U+00254e: BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL*/
+ 0, /* U+00254f: BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL*/
+ 0, /* U+002550: BOX DRAWINGS DOUBLE HORIZONTAL*/
+ 0, /* U+002551: BOX DRAWINGS DOUBLE VERTICAL*/
+ 0, /* U+002552: BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE*/
+ 0, /* U+002553: BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE*/
+ 0, /* U+002554: BOX DRAWINGS DOUBLE DOWN AND RIGHT*/
+ 0, /* U+002555: BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE*/
+ 0, /* U+002556: BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE*/
+ 0, /* U+002557: BOX DRAWINGS DOUBLE DOWN AND LEFT*/
+ 0, /* U+002558: BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE*/
+ 0, /* U+002559: BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE*/
+ 0, /* U+00255a: BOX DRAWINGS DOUBLE UP AND RIGHT*/
+ 0, /* U+00255b: BOX DRAWINGS UP SINGLE AND LEFT DOUBLE*/
+ 0, /* U+00255c: BOX DRAWINGS UP DOUBLE AND LEFT SINGLE*/
+ 0, /* U+00255d: BOX DRAWINGS DOUBLE UP AND LEFT*/
+ 0, /* U+00255e: BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE*/
+ 0, /* U+00255f: BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE*/
+ 0, /* U+002560: BOX DRAWINGS DOUBLE VERTICAL AND RIGHT*/
+ 0, /* U+002561: BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE*/
+ 0, /* U+002562: BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE*/
+ 0, /* U+002563: BOX DRAWINGS DOUBLE VERTICAL AND LEFT*/
+ 0, /* U+002564: BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE*/
+ 0, /* U+002565: BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE*/
+ 0, /* U+002566: BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL*/
+ 0, /* U+002567: BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE*/
+ 0, /* U+002568: BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE*/
+ 0, /* U+002569: BOX DRAWINGS DOUBLE UP AND HORIZONTAL*/
+ 0, /* U+00256a: BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE*/
+ 0, /* U+00256b: BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE*/
+ 0, /* U+00256c: BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL*/
+ 0, /* U+00256d: BOX DRAWINGS LIGHT ARC DOWN AND RIGHT*/
+ 0, /* U+00256e: BOX DRAWINGS LIGHT ARC DOWN AND LEFT*/
+ 0, /* U+00256f: BOX DRAWINGS LIGHT ARC UP AND LEFT*/
+ 0, /* U+002570: BOX DRAWINGS LIGHT ARC UP AND RIGHT*/
+ 0, /* U+002571: BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT*/
+ 0, /* U+002572: BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT*/
+ 0, /* U+002573: BOX DRAWINGS LIGHT DIAGONAL CROSS*/
+ 0, /* U+002574: BOX DRAWINGS LIGHT LEFT*/
+ 0, /* U+002575: BOX DRAWINGS LIGHT UP*/
+ 0, /* U+002576: BOX DRAWINGS LIGHT RIGHT*/
+ 0, /* U+002577: BOX DRAWINGS LIGHT DOWN*/
+ 0, /* U+002578: BOX DRAWINGS HEAVY LEFT*/
+ 0, /* U+002579: BOX DRAWINGS HEAVY UP*/
+ 0, /* U+00257a: BOX DRAWINGS HEAVY RIGHT*/
+ 0, /* U+00257b: BOX DRAWINGS HEAVY DOWN*/
+ 0, /* U+00257c: BOX DRAWINGS LIGHT LEFT AND HEAVY RIGHT*/
+ 0, /* U+00257d: BOX DRAWINGS LIGHT UP AND HEAVY DOWN*/
+ 0, /* U+00257e: BOX DRAWINGS HEAVY LEFT AND LIGHT RIGHT*/
+ 0, /* U+00257f: BOX DRAWINGS HEAVY UP AND LIGHT DOWN*/
+ 0, /* U+002580: UPPER HALF BLOCK*/
+ 0, /* U+002581: LOWER ONE EIGHTH BLOCK*/
+ 0, /* U+002582: LOWER ONE QUARTER BLOCK*/
+ 0, /* U+002583: LOWER THREE EIGHTHS BLOCK*/
+ 0, /* U+002584: LOWER HALF BLOCK*/
+ 0, /* U+002585: LOWER FIVE EIGHTHS BLOCK*/
+ 0, /* U+002586: LOWER THREE QUARTERS BLOCK*/
+ 0, /* U+002587: LOWER SEVEN EIGHTHS BLOCK*/
+ 0, /* U+002588: FULL BLOCK*/
+ 0, /* U+002589: LEFT SEVEN EIGHTHS BLOCK*/
+ 0, /* U+00258a: LEFT THREE QUARTERS BLOCK*/
+ 0, /* U+00258b: LEFT FIVE EIGHTHS BLOCK*/
+ 0, /* U+00258c: LEFT HALF BLOCK*/
+ 0, /* U+00258d: LEFT THREE EIGHTHS BLOCK*/
+ 0, /* U+00258e: LEFT ONE QUARTER BLOCK*/
+ 0, /* U+00258f: LEFT ONE EIGHTH BLOCK*/
+ 0, /* U+002590: RIGHT HALF BLOCK*/
+ 0, /* U+002591: LIGHT SHADE*/
+ 0, /* U+002592: MEDIUM SHADE*/
+ 0, /* U+002593: DARK SHADE*/
+ 0, /* U+002594: UPPER ONE EIGHTH BLOCK*/
+ 0, /* U+002595: RIGHT ONE EIGHTH BLOCK*/
+ 0, /* U+002596: QUADRANT LOWER LEFT*/
+ 0, /* U+002597: QUADRANT LOWER RIGHT*/
+ 0, /* U+002598: QUADRANT UPPER LEFT*/
+ 0, /* U+002599: QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT*/
+ 0, /* U+00259a: QUADRANT UPPER LEFT AND LOWER RIGHT*/
+ 0, /* U+00259b: QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT*/
+ 0, /* U+00259c: QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT*/
+ 0, /* U+00259d: QUADRANT UPPER RIGHT*/
+ 0, /* U+00259e: QUADRANT UPPER RIGHT AND LOWER LEFT*/
+ 0, /* U+00259f: QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT*/
+ 0, /* U+0025a0: BLACK SQUARE*/
+ 0, /* U+0025a1: WHITE SQUARE*/
+ 0, /* U+0025a2: WHITE SQUARE WITH ROUNDED CORNERS*/
+ 0, /* U+0025a3: WHITE SQUARE CONTAINING BLACK SMALL SQUARE*/
+ 0, /* U+0025a4: SQUARE WITH HORIZONTAL FILL*/
+ 0, /* U+0025a5: SQUARE WITH VERTICAL FILL*/
+ 0, /* U+0025a6: SQUARE WITH ORTHOGONAL CROSSHATCH FILL*/
+ 0, /* U+0025a7: SQUARE WITH UPPER LEFT TO LOWER RIGHT FILL*/
+ 0, /* U+0025a8: SQUARE WITH UPPER RIGHT TO LOWER LEFT FILL*/
+ 0, /* U+0025a9: SQUARE WITH DIAGONAL CROSSHATCH FILL*/
+ 0, /* U+0025aa: BLACK SMALL SQUARE*/
+ 0, /* U+0025ab: WHITE SMALL SQUARE*/
+ 0, /* U+0025ac: BLACK RECTANGLE*/
+ 0, /* U+0025ad: WHITE RECTANGLE*/
+ 0, /* U+0025ae: BLACK VERTICAL RECTANGLE*/
+ 0, /* U+0025af: WHITE VERTICAL RECTANGLE*/
+ 0, /* U+0025b0: BLACK PARALLELOGRAM*/
+ 0, /* U+0025b1: WHITE PARALLELOGRAM*/
+ 0, /* U+0025b2: BLACK UP-POINTING TRIANGLE*/
+ 0, /* U+0025b3: WHITE UP-POINTING TRIANGLE*/
+ 0, /* U+0025b4: BLACK UP-POINTING SMALL TRIANGLE*/
+ 0, /* U+0025b5: WHITE UP-POINTING SMALL TRIANGLE*/
+ 0, /* U+0025b6: BLACK RIGHT-POINTING TRIANGLE*/
+ 0, /* U+0025b7: WHITE RIGHT-POINTING TRIANGLE*/
+ 0, /* U+0025b8: BLACK RIGHT-POINTING SMALL TRIANGLE*/
+ 0, /* U+0025b9: WHITE RIGHT-POINTING SMALL TRIANGLE*/
+ 0, /* U+0025ba: BLACK RIGHT-POINTING POINTER*/
+ 0, /* U+0025bb: WHITE RIGHT-POINTING POINTER*/
+ 0, /* U+0025bc: BLACK DOWN-POINTING TRIANGLE*/
+ 0, /* U+0025bd: WHITE DOWN-POINTING TRIANGLE*/
+ 0, /* U+0025be: BLACK DOWN-POINTING SMALL TRIANGLE*/
+ 0, /* U+0025bf: WHITE DOWN-POINTING SMALL TRIANGLE*/
+ 0, /* U+0025c0: BLACK LEFT-POINTING TRIANGLE*/
+ 0, /* U+0025c1: WHITE LEFT-POINTING TRIANGLE*/
+ 0, /* U+0025c2: BLACK LEFT-POINTING SMALL TRIANGLE*/
+ 0, /* U+0025c3: WHITE LEFT-POINTING SMALL TRIANGLE*/
+ 0, /* U+0025c4: BLACK LEFT-POINTING POINTER*/
+ 0, /* U+0025c5: WHITE LEFT-POINTING POINTER*/
+ 0, /* U+0025c6: BLACK DIAMOND*/
+ 0, /* U+0025c7: WHITE DIAMOND*/
+ 0, /* U+0025c8: WHITE DIAMOND CONTAINING BLACK SMALL DIAMOND*/
+ 0, /* U+0025c9: FISHEYE*/
+ 0, /* U+0025ca: LOZENGE*/
+ 0, /* U+0025cb: WHITE CIRCLE*/
+ 0, /* U+0025cc: DOTTED CIRCLE*/
+ 0, /* U+0025cd: CIRCLE WITH VERTICAL FILL*/
+ 0, /* U+0025ce: BULLSEYE*/
+ 0, /* U+0025cf: BLACK CIRCLE*/
+ 0, /* U+0025d0: CIRCLE WITH LEFT HALF BLACK*/
+ 0, /* U+0025d1: CIRCLE WITH RIGHT HALF BLACK*/
+ 0, /* U+0025d2: CIRCLE WITH LOWER HALF BLACK*/
+ 0, /* U+0025d3: CIRCLE WITH UPPER HALF BLACK*/
+ 0, /* U+0025d4: CIRCLE WITH UPPER RIGHT QUADRANT BLACK*/
+ 0, /* U+0025d5: CIRCLE WITH ALL BUT UPPER LEFT QUADRANT BLACK*/
+ 0, /* U+0025d6: LEFT HALF BLACK CIRCLE*/
+ 0, /* U+0025d7: RIGHT HALF BLACK CIRCLE*/
+ 0, /* U+0025d8: INVERSE BULLET*/
+ 0, /* U+0025d9: INVERSE WHITE CIRCLE*/
+ 0, /* U+0025da: UPPER HALF INVERSE WHITE CIRCLE*/
+ 0, /* U+0025db: LOWER HALF INVERSE WHITE CIRCLE*/
+ 0, /* U+0025dc: UPPER LEFT QUADRANT CIRCULAR ARC*/
+ 0, /* U+0025dd: UPPER RIGHT QUADRANT CIRCULAR ARC*/
+ 0, /* U+0025de: LOWER RIGHT QUADRANT CIRCULAR ARC*/
+ 0, /* U+0025df: LOWER LEFT QUADRANT CIRCULAR ARC*/
+ 0, /* U+0025e0: UPPER HALF CIRCLE*/
+ 0, /* U+0025e1: LOWER HALF CIRCLE*/
+ 0, /* U+0025e2: BLACK LOWER RIGHT TRIANGLE*/
+ 0, /* U+0025e3: BLACK LOWER LEFT TRIANGLE*/
+ 0, /* U+0025e4: BLACK UPPER LEFT TRIANGLE*/
+ 0, /* U+0025e5: BLACK UPPER RIGHT TRIANGLE*/
+ 0, /* U+0025e6: WHITE BULLET*/
+ 0, /* U+0025e7: SQUARE WITH LEFT HALF BLACK*/
+ 0, /* U+0025e8: SQUARE WITH RIGHT HALF BLACK*/
+ 0, /* U+0025e9: SQUARE WITH UPPER LEFT DIAGONAL HALF BLACK*/
+ 0, /* U+0025ea: SQUARE WITH LOWER RIGHT DIAGONAL HALF BLACK*/
+ 0, /* U+0025eb: WHITE SQUARE WITH VERTICAL BISECTING LINE*/
+ 0, /* U+0025ec: WHITE UP-POINTING TRIANGLE WITH DOT*/
+ 0, /* U+0025ed: UP-POINTING TRIANGLE WITH LEFT HALF BLACK*/
+ 0, /* U+0025ee: UP-POINTING TRIANGLE WITH RIGHT HALF BLACK*/
+ 0, /* U+0025ef: LARGE CIRCLE*/
+ 0, /* U+0025f0: WHITE SQUARE WITH UPPER LEFT QUADRANT*/
+ 0, /* U+0025f1: WHITE SQUARE WITH LOWER LEFT QUADRANT*/
+ 0, /* U+0025f2: WHITE SQUARE WITH LOWER RIGHT QUADRANT*/
+ 0, /* U+0025f3: WHITE SQUARE WITH UPPER RIGHT QUADRANT*/
+ 0, /* U+0025f4: WHITE CIRCLE WITH UPPER LEFT QUADRANT*/
+ 0, /* U+0025f5: WHITE CIRCLE WITH LOWER LEFT QUADRANT*/
+ 0, /* U+0025f6: WHITE CIRCLE WITH LOWER RIGHT QUADRANT*/
+ 0, /* U+0025f7: WHITE CIRCLE WITH UPPER RIGHT QUADRANT*/
+ 0, /* U+0025f8: UPPER LEFT TRIANGLE*/
+ 0, /* U+0025f9: UPPER RIGHT TRIANGLE*/
+ 0, /* U+0025fa: LOWER LEFT TRIANGLE*/
+ 0, /* U+0025fb: WHITE MEDIUM SQUARE*/
+ 0, /* U+0025fc: BLACK MEDIUM SQUARE*/
+ 0, /* U+0025fd: WHITE MEDIUM SMALL SQUARE*/
+ 0, /* U+0025fe: BLACK MEDIUM SMALL SQUARE*/
+ 0, /* U+0025ff: LOWER RIGHT TRIANGLE*/
+ 0, /* U+002600: BLACK SUN WITH RAYS*/
+ 0, /* U+002601: CLOUD*/
+ 0, /* U+002602: UMBRELLA*/
+ 0, /* U+002603: SNOWMAN*/
+ 0, /* U+002604: COMET*/
+ 0, /* U+002605: BLACK STAR*/
+ 0, /* U+002606: WHITE STAR*/
+ 0, /* U+002607: LIGHTNING*/
+ 0, /* U+002608: THUNDERSTORM*/
+ 0, /* U+002609: SUN*/
+ 0, /* U+00260a: ASCENDING NODE*/
+ 0, /* U+00260b: DESCENDING NODE*/
+ 0, /* U+00260c: CONJUNCTION*/
+ 0, /* U+00260d: OPPOSITION*/
+ 0, /* U+00260e: BLACK TELEPHONE*/
+ 0, /* U+00260f: WHITE TELEPHONE*/
+ 0, /* U+002610: BALLOT BOX*/
+ 0, /* U+002611: BALLOT BOX WITH CHECK*/
+ 0, /* U+002612: BALLOT BOX WITH X*/
+ 0, /* U+002613: SALTIRE*/
+ 0, /* U+002614: UMBRELLA WITH RAIN DROPS*/
+ 0, /* U+002615: HOT BEVERAGE*/
+ 0, /* U+002616: WHITE SHOGI PIECE*/
+ 0, /* U+002617: BLACK SHOGI PIECE*/
+ 0, /* U+002618: SHAMROCK*/
+ 0, /* U+002619: REVERSED ROTATED FLORAL HEART BULLET*/
+ 0, /* U+00261a: BLACK LEFT POINTING INDEX*/
+ 0, /* U+00261b: BLACK RIGHT POINTING INDEX*/
+ 0, /* U+00261c: WHITE LEFT POINTING INDEX*/
+ 0, /* U+00261d: WHITE UP POINTING INDEX*/
+ 0, /* U+00261e: WHITE RIGHT POINTING INDEX*/
+ 0, /* U+00261f: WHITE DOWN POINTING INDEX*/
+ 0, /* U+002620: SKULL AND CROSSBONES*/
+ 0, /* U+002621: CAUTION SIGN*/
+ 0, /* U+002622: RADIOACTIVE SIGN*/
+ 0, /* U+002623: BIOHAZARD SIGN*/
+ 0, /* U+002624: CADUCEUS*/
+ 0, /* U+002625: ANKH*/
+ 0, /* U+002626: ORTHODOX CROSS*/
+ 0, /* U+002627: CHI RHO*/
+ 0, /* U+002628: CROSS OF LORRAINE*/
+ 0, /* U+002629: CROSS OF JERUSALEM*/
+ 0, /* U+00262a: STAR AND CRESCENT*/
+ 0, /* U+00262b: FARSI SYMBOL*/
+ 0, /* U+00262c: ADI SHAKTI*/
+ 0, /* U+00262d: HAMMER AND SICKLE*/
+ 0, /* U+00262e: PEACE SYMBOL*/
+ 0, /* U+00262f: YIN YANG*/
+ 0, /* U+002630: TRIGRAM FOR HEAVEN*/
+ 0, /* U+002631: TRIGRAM FOR LAKE*/
+ 0, /* U+002632: TRIGRAM FOR FIRE*/
+ 0, /* U+002633: TRIGRAM FOR THUNDER*/
+ 0, /* U+002634: TRIGRAM FOR WIND*/
+ 0, /* U+002635: TRIGRAM FOR WATER*/
+ 0, /* U+002636: TRIGRAM FOR MOUNTAIN*/
+ 0, /* U+002637: TRIGRAM FOR EARTH*/
+ 0, /* U+002638: WHEEL OF DHARMA*/
+ 0, /* U+002639: WHITE FROWNING FACE*/
+ 0, /* U+00263a: WHITE SMILING FACE*/
+ 0, /* U+00263b: BLACK SMILING FACE*/
+ 0, /* U+00263c: WHITE SUN WITH RAYS*/
+ 0, /* U+00263d: FIRST QUARTER MOON*/
+ 0, /* U+00263e: LAST QUARTER MOON*/
+ 0, /* U+00263f: MERCURY*/
+ 0, /* U+002640: FEMALE SIGN*/
+ 0, /* U+002641: EARTH*/
+ 0, /* U+002642: MALE SIGN*/
+ 0, /* U+002643: JUPITER*/
+ 0, /* U+002644: SATURN*/
+ 0, /* U+002645: URANUS*/
+ 0, /* U+002646: NEPTUNE*/
+ 0, /* U+002647: PLUTO*/
+ 0, /* U+002648: ARIES*/
+ 0, /* U+002649: TAURUS*/
+ 0, /* U+00264a: GEMINI*/
+ 0, /* U+00264b: CANCER*/
+ 0, /* U+00264c: LEO*/
+ 0, /* U+00264d: VIRGO*/
+ 0, /* U+00264e: LIBRA*/
+ 0, /* U+00264f: SCORPIUS*/
+ 0, /* U+002650: SAGITTARIUS*/
+ 0, /* U+002651: CAPRICORN*/
+ 0, /* U+002652: AQUARIUS*/
+ 0, /* U+002653: PISCES*/
+ 0, /* U+002654: WHITE CHESS KING*/
+ 0, /* U+002655: WHITE CHESS QUEEN*/
+ 0, /* U+002656: WHITE CHESS ROOK*/
+ 0, /* U+002657: WHITE CHESS BISHOP*/
+ 0, /* U+002658: WHITE CHESS KNIGHT*/
+ 0, /* U+002659: WHITE CHESS PAWN*/
+ 0, /* U+00265a: BLACK CHESS KING*/
+ 0, /* U+00265b: BLACK CHESS QUEEN*/
+ 0, /* U+00265c: BLACK CHESS ROOK*/
+ 0, /* U+00265d: BLACK CHESS BISHOP*/
+ 0, /* U+00265e: BLACK CHESS KNIGHT*/
+ 0, /* U+00265f: BLACK CHESS PAWN*/
+ 0, /* U+002660: BLACK SPADE SUIT*/
+ 0, /* U+002661: WHITE HEART SUIT*/
+ 0, /* U+002662: WHITE DIAMOND SUIT*/
+ 0, /* U+002663: BLACK CLUB SUIT*/
+ 0, /* U+002664: WHITE SPADE SUIT*/
+ 0, /* U+002665: BLACK HEART SUIT*/
+ 0, /* U+002666: BLACK DIAMOND SUIT*/
+ 0, /* U+002667: WHITE CLUB SUIT*/
+ 0, /* U+002668: HOT SPRINGS*/
+ 0, /* U+002669: QUARTER NOTE*/
+ 0, /* U+00266a: EIGHTH NOTE*/
+ 0, /* U+00266b: BEAMED EIGHTH NOTES*/
+ 0, /* U+00266c: BEAMED SIXTEENTH NOTES*/
+ 0, /* U+00266d: MUSIC FLAT SIGN*/
+ 0, /* U+00266e: MUSIC NATURAL SIGN*/
+ 0, /* U+00266f: MUSIC SHARP SIGN*/
+ 0, /* U+002670: WEST SYRIAC CROSS*/
+ 0, /* U+002671: EAST SYRIAC CROSS*/
+ 0, /* U+002672: UNIVERSAL RECYCLING SYMBOL*/
+ 0, /* U+002673: RECYCLING SYMBOL FOR TYPE-1 PLASTICS*/
+ 0, /* U+002674: RECYCLING SYMBOL FOR TYPE-2 PLASTICS*/
+ 0, /* U+002675: RECYCLING SYMBOL FOR TYPE-3 PLASTICS*/
+ 0, /* U+002676: RECYCLING SYMBOL FOR TYPE-4 PLASTICS*/
+ 0, /* U+002677: RECYCLING SYMBOL FOR TYPE-5 PLASTICS*/
+ 0, /* U+002678: RECYCLING SYMBOL FOR TYPE-6 PLASTICS*/
+ 0, /* U+002679: RECYCLING SYMBOL FOR TYPE-7 PLASTICS*/
+ 0, /* U+00267a: RECYCLING SYMBOL FOR GENERIC MATERIALS*/
+ 0, /* U+00267b: BLACK UNIVERSAL RECYCLING SYMBOL*/
+ 0, /* U+00267c: RECYCLED PAPER SYMBOL*/
+ 0, /* U+00267d: PARTIALLY-RECYCLED PAPER SYMBOL*/
+ 0, /* U+00267e: PERMANENT PAPER SIGN*/
+ 0, /* U+00267f: WHEELCHAIR SYMBOL*/
+ 0, /* U+002680: DIE FACE-1*/
+ 0, /* U+002681: DIE FACE-2*/
+ 0, /* U+002682: DIE FACE-3*/
+ 0, /* U+002683: DIE FACE-4*/
+ 0, /* U+002684: DIE FACE-5*/
+ 0, /* U+002685: DIE FACE-6*/
+ 0, /* U+002686: WHITE CIRCLE WITH DOT RIGHT*/
+ 0, /* U+002687: WHITE CIRCLE WITH TWO DOTS*/
+ 0, /* U+002688: BLACK CIRCLE WITH WHITE DOT RIGHT*/
+ 0, /* U+002689: BLACK CIRCLE WITH TWO WHITE DOTS*/
+ 0, /* U+00268a: MONOGRAM FOR YANG*/
+ 0, /* U+00268b: MONOGRAM FOR YIN*/
+ 0, /* U+00268c: DIGRAM FOR GREATER YANG*/
+ 0, /* U+00268d: DIGRAM FOR LESSER YIN*/
+ 0, /* U+00268e: DIGRAM FOR LESSER YANG*/
+ 0, /* U+00268f: DIGRAM FOR GREATER YIN*/
+ 0, /* U+002690: WHITE FLAG*/
+ 0, /* U+002691: BLACK FLAG*/
+ 0, /* U+002692: HAMMER AND PICK*/
+ 0, /* U+002693: ANCHOR*/
+ 0, /* U+002694: CROSSED SWORDS*/
+ 0, /* U+002695: STAFF OF AESCULAPIUS*/
+ 0, /* U+002696: SCALES*/
+ 0, /* U+002697: ALEMBIC*/
+ 0, /* U+002698: FLOWER*/
+ 0, /* U+002699: GEAR*/
+ 0, /* U+00269a: STAFF OF HERMES*/
+ 0, /* U+00269b: ATOM SYMBOL*/
+ 0, /* U+00269c: FLEUR-DE-LIS*/
+ 0, /* U+00269d: OUTLINED WHITE STAR*/
+ 0, /* U+00269e: THREE LINES CONVERGING RIGHT*/
+ 0, /* U+00269f: THREE LINES CONVERGING LEFT*/
+ 0, /* U+0026a0: WARNING SIGN*/
+ 0, /* U+0026a1: HIGH VOLTAGE SIGN*/
+ 0, /* U+0026a2: DOUBLED FEMALE SIGN*/
+ 0, /* U+0026a3: DOUBLED MALE SIGN*/
+ 0, /* U+0026a4: INTERLOCKED FEMALE AND MALE SIGN*/
+ 0, /* U+0026a5: MALE AND FEMALE SIGN*/
+ 0, /* U+0026a6: MALE WITH STROKE SIGN*/
+ 0, /* U+0026a7: MALE WITH STROKE AND MALE AND FEMALE SIGN*/
+ 0, /* U+0026a8: VERTICAL MALE WITH STROKE SIGN*/
+ 0, /* U+0026a9: HORIZONTAL MALE WITH STROKE SIGN*/
+ 0, /* U+0026aa: MEDIUM WHITE CIRCLE*/
+ 0, /* U+0026ab: MEDIUM BLACK CIRCLE*/
+ 0, /* U+0026ac: MEDIUM SMALL WHITE CIRCLE*/
+ 0, /* U+0026ad: MARRIAGE SYMBOL*/
+ 0, /* U+0026ae: DIVORCE SYMBOL*/
+ 0, /* U+0026af: UNMARRIED PARTNERSHIP SYMBOL*/
+ 0, /* U+0026b0: COFFIN*/
+ 0, /* U+0026b1: FUNERAL URN*/
+ 0, /* U+0026b2: NEUTER*/
+ 0, /* U+0026b3: CERES*/
+ 0, /* U+0026b4: PALLAS*/
+ 0, /* U+0026b5: JUNO*/
+ 0, /* U+0026b6: VESTA*/
+ 0, /* U+0026b7: CHIRON*/
+ 0, /* U+0026b8: BLACK MOON LILITH*/
+ 0, /* U+0026b9: SEXTILE*/
+ 0, /* U+0026ba: SEMISEXTILE*/
+ 0, /* U+0026bb: QUINCUNX*/
+ 0, /* U+0026bc: SESQUIQUADRATE*/
+ 0, /* U+0026bd: SOCCER BALL*/
+ 0, /* U+0026be: BASEBALL*/
+ 0, /* U+0026bf: SQUARED KEY*/
+ 0, /* U+0026c0: WHITE DRAUGHTS MAN*/
+ 0, /* U+0026c1: WHITE DRAUGHTS KING*/
+ 0, /* U+0026c2: BLACK DRAUGHTS MAN*/
+ 0, /* U+0026c3: BLACK DRAUGHTS KING*/
+ 0, /* U+0026c4: SNOWMAN WITHOUT SNOW*/
+ 0, /* U+0026c5: SUN BEHIND CLOUD*/
+ 0, /* U+0026c6: RAIN*/
+ 0, /* U+0026c7: BLACK SNOWMAN*/
+ 0, /* U+0026c8: THUNDER CLOUD AND RAIN*/
+ 0, /* U+0026c9: TURNED WHITE SHOGI PIECE*/
+ 0, /* U+0026ca: TURNED BLACK SHOGI PIECE*/
+ 0, /* U+0026cb: WHITE DIAMOND IN SQUARE*/
+ 0, /* U+0026cc: CROSSING LANES*/
+ 0, /* U+0026cd: DISABLED CAR*/
+ 0, /* U+0026ce: OPHIUCHUS*/
+ 0, /* U+0026cf: PICK*/
+ 0, /* U+0026d0: CAR SLIDING*/
+ 0, /* U+0026d1: HELMET WITH WHITE CROSS*/
+ 0, /* U+0026d2: CIRCLED CROSSING LANES*/
+ 0, /* U+0026d3: CHAINS*/
+ 0, /* U+0026d4: NO ENTRY*/
+ 0, /* U+0026d5: ALTERNATE ONE-WAY LEFT WAY TRAFFIC*/
+ 0, /* U+0026d6: BLACK TWO-WAY LEFT WAY TRAFFIC*/
+ 0, /* U+0026d7: WHITE TWO-WAY LEFT WAY TRAFFIC*/
+ 0, /* U+0026d8: BLACK LEFT LANE MERGE*/
+ 0, /* U+0026d9: WHITE LEFT LANE MERGE*/
+ 0, /* U+0026da: DRIVE SLOW SIGN*/
+ 0, /* U+0026db: HEAVY WHITE DOWN-POINTING TRIANGLE*/
+ 0, /* U+0026dc: LEFT CLOSED ENTRY*/
+ 0, /* U+0026dd: SQUARED SALTIRE*/
+ 0, /* U+0026de: FALLING DIAGONAL IN WHITE CIRCLE IN BLACK SQUARE*/
+ 0, /* U+0026df: BLACK TRUCK*/
+ 0, /* U+0026e0: RESTRICTED LEFT ENTRY-1*/
+ 0, /* U+0026e1: RESTRICTED LEFT ENTRY-2*/
+ 0, /* U+0026e2: ASTRONOMICAL SYMBOL FOR URANUS*/
+ 0, /* U+0026e3: HEAVY CIRCLE WITH STROKE AND TWO DOTS ABOVE*/
+ 0, /* U+0026e4: PENTAGRAM*/
+ 0, /* U+0026e5: RIGHT-HANDED INTERLACED PENTAGRAM*/
+ 0, /* U+0026e6: LEFT-HANDED INTERLACED PENTAGRAM*/
+ 0, /* U+0026e7: INVERTED PENTAGRAM*/
+ 0, /* U+0026e8: BLACK CROSS ON SHIELD*/
+ 0, /* U+0026e9: SHINTO SHRINE*/
+ 0, /* U+0026ea: CHURCH*/
+ 0, /* U+0026eb: CASTLE*/
+ 0, /* U+0026ec: HISTORIC SITE*/
+ 0, /* U+0026ed: GEAR WITHOUT HUB*/
+ 0, /* U+0026ee: GEAR WITH HANDLES*/
+ 0, /* U+0026ef: MAP SYMBOL FOR LIGHTHOUSE*/
+ 0, /* U+0026f0: MOUNTAIN*/
+ 0, /* U+0026f1: UMBRELLA ON GROUND*/
+ 0, /* U+0026f2: FOUNTAIN*/
+ 0, /* U+0026f3: FLAG IN HOLE*/
+ 0, /* U+0026f4: FERRY*/
+ 0, /* U+0026f5: SAILBOAT*/
+ 0, /* U+0026f6: SQUARE FOUR CORNERS*/
+ 0, /* U+0026f7: SKIER*/
+ 0, /* U+0026f8: ICE SKATE*/
+ 0, /* U+0026f9: PERSON WITH BALL*/
+ 0, /* U+0026fa: TENT*/
+ 0, /* U+0026fb: JAPANESE BANK SYMBOL*/
+ 0, /* U+0026fc: HEADSTONE GRAVEYARD SYMBOL*/
+ 0, /* U+0026fd: FUEL PUMP*/
+ 0, /* U+0026fe: CUP ON BLACK SQUARE*/
+ 0, /* U+0026ff: WHITE FLAG WITH HORIZONTAL MIDDLE BLACK STRIPE*/
+ 0, /* U+002700: */
+ 0, /* U+002701: UPPER BLADE SCISSORS*/
+ 0, /* U+002702: BLACK SCISSORS*/
+ 0, /* U+002703: LOWER BLADE SCISSORS*/
+ 0, /* U+002704: WHITE SCISSORS*/
+ 0, /* U+002705: WHITE HEAVY CHECK MARK*/
+ 0, /* U+002706: TELEPHONE LOCATION SIGN*/
+ 0, /* U+002707: TAPE DRIVE*/
+ 0, /* U+002708: AIRPLANE*/
+ 0, /* U+002709: ENVELOPE*/
+ 0, /* U+00270a: RAISED FIST*/
+ 0, /* U+00270b: RAISED HAND*/
+ 0, /* U+00270c: VICTORY HAND*/
+ 0, /* U+00270d: WRITING HAND*/
+ 0, /* U+00270e: LOWER RIGHT PENCIL*/
+ 0, /* U+00270f: PENCIL*/
+ 0, /* U+002710: UPPER RIGHT PENCIL*/
+ 0, /* U+002711: WHITE NIB*/
+ 0, /* U+002712: BLACK NIB*/
+ 0, /* U+002713: CHECK MARK*/
+ 0, /* U+002714: HEAVY CHECK MARK*/
+ 0, /* U+002715: MULTIPLICATION X*/
+ 0, /* U+002716: HEAVY MULTIPLICATION X*/
+ 0, /* U+002717: BALLOT X*/
+ 0, /* U+002718: HEAVY BALLOT X*/
+ 0, /* U+002719: OUTLINED GREEK CROSS*/
+ 0, /* U+00271a: HEAVY GREEK CROSS*/
+ 0, /* U+00271b: OPEN CENTRE CROSS*/
+ 0, /* U+00271c: HEAVY OPEN CENTRE CROSS*/
+ 0, /* U+00271d: LATIN CROSS*/
+ 0, /* U+00271e: SHADOWED WHITE LATIN CROSS*/
+ 0, /* U+00271f: OUTLINED LATIN CROSS*/
+ 0, /* U+002720: MALTESE CROSS*/
+ 0, /* U+002721: STAR OF DAVID*/
+ 0, /* U+002722: FOUR TEARDROP-SPOKED ASTERISK*/
+ 0, /* U+002723: FOUR BALLOON-SPOKED ASTERISK*/
+ 0, /* U+002724: HEAVY FOUR BALLOON-SPOKED ASTERISK*/
+ 0, /* U+002725: FOUR CLUB-SPOKED ASTERISK*/
+ 0, /* U+002726: BLACK FOUR POINTED STAR*/
+ 0, /* U+002727: WHITE FOUR POINTED STAR*/
+ 0, /* U+002728: SPARKLES*/
+ 0, /* U+002729: STRESS OUTLINED WHITE STAR*/
+ 0, /* U+00272a: CIRCLED WHITE STAR*/
+ 0, /* U+00272b: OPEN CENTRE BLACK STAR*/
+ 0, /* U+00272c: BLACK CENTRE WHITE STAR*/
+ 0, /* U+00272d: OUTLINED BLACK STAR*/
+ 0, /* U+00272e: HEAVY OUTLINED BLACK STAR*/
+ 0, /* U+00272f: PINWHEEL STAR*/
+ 0, /* U+002730: SHADOWED WHITE STAR*/
+ 0, /* U+002731: HEAVY ASTERISK*/
+ 0, /* U+002732: OPEN CENTRE ASTERISK*/
+ 0, /* U+002733: EIGHT SPOKED ASTERISK*/
+ 0, /* U+002734: EIGHT POINTED BLACK STAR*/
+ 0, /* U+002735: EIGHT POINTED PINWHEEL STAR*/
+ 0, /* U+002736: SIX POINTED BLACK STAR*/
+ 0, /* U+002737: EIGHT POINTED RECTILINEAR BLACK STAR*/
+ 0, /* U+002738: HEAVY EIGHT POINTED RECTILINEAR BLACK STAR*/
+ 0, /* U+002739: TWELVE POINTED BLACK STAR*/
+ 0, /* U+00273a: SIXTEEN POINTED ASTERISK*/
+ 0, /* U+00273b: TEARDROP-SPOKED ASTERISK*/
+ 0, /* U+00273c: OPEN CENTRE TEARDROP-SPOKED ASTERISK*/
+ 0, /* U+00273d: HEAVY TEARDROP-SPOKED ASTERISK*/
+ 0, /* U+00273e: SIX PETALLED BLACK AND WHITE FLORETTE*/
+ 0, /* U+00273f: BLACK FLORETTE*/
+ 0, /* U+002740: WHITE FLORETTE*/
+ 0, /* U+002741: EIGHT PETALLED OUTLINED BLACK FLORETTE*/
+ 0, /* U+002742: CIRCLED OPEN CENTRE EIGHT POINTED STAR*/
+ 0, /* U+002743: HEAVY TEARDROP-SPOKED PINWHEEL ASTERISK*/
+ 0, /* U+002744: SNOWFLAKE*/
+ 0, /* U+002745: TIGHT TRIFOLIATE SNOWFLAKE*/
+ 0, /* U+002746: HEAVY CHEVRON SNOWFLAKE*/
+ 0, /* U+002747: SPARKLE*/
+ 0, /* U+002748: HEAVY SPARKLE*/
+ 0, /* U+002749: BALLOON-SPOKED ASTERISK*/
+ 0, /* U+00274a: EIGHT TEARDROP-SPOKED PROPELLER ASTERISK*/
+ 0, /* U+00274b: HEAVY EIGHT TEARDROP-SPOKED PROPELLER ASTERISK*/
+ 0, /* U+00274c: CROSS MARK*/
+ 0, /* U+00274d: SHADOWED WHITE CIRCLE*/
+ 0, /* U+00274e: NEGATIVE SQUARED CROSS MARK*/
+ 0, /* U+00274f: LOWER RIGHT DROP-SHADOWED WHITE SQUARE*/
+ 0, /* U+002750: UPPER RIGHT DROP-SHADOWED WHITE SQUARE*/
+ 0, /* U+002751: LOWER RIGHT SHADOWED WHITE SQUARE*/
+ 0, /* U+002752: UPPER RIGHT SHADOWED WHITE SQUARE*/
+ 0, /* U+002753: BLACK QUESTION MARK ORNAMENT*/
+ 0, /* U+002754: WHITE QUESTION MARK ORNAMENT*/
+ 0, /* U+002755: WHITE EXCLAMATION MARK ORNAMENT*/
+ 0, /* U+002756: BLACK DIAMOND MINUS WHITE X*/
+ 0, /* U+002757: HEAVY EXCLAMATION MARK SYMBOL*/
+ 0, /* U+002758: LIGHT VERTICAL BAR*/
+ 0, /* U+002759: MEDIUM VERTICAL BAR*/
+ 0, /* U+00275a: HEAVY VERTICAL BAR*/
+ 0, /* U+00275b: HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT*/
+ 0, /* U+00275c: HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT*/
+ 0, /* U+00275d: HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT*/
+ 0, /* U+00275e: HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT*/
+ 0, /* U+00275f: HEAVY LOW SINGLE COMMA QUOTATION MARK ORNAMENT*/
+ 0, /* U+002760: HEAVY LOW DOUBLE COMMA QUOTATION MARK ORNAMENT*/
+ 0, /* U+002761: CURVED STEM PARAGRAPH SIGN ORNAMENT*/
+ 0, /* U+002762: HEAVY EXCLAMATION MARK ORNAMENT*/
+ 0, /* U+002763: HEAVY HEART EXCLAMATION MARK ORNAMENT*/
+ 0, /* U+002764: HEAVY BLACK HEART*/
+ 0, /* U+002765: ROTATED HEAVY BLACK HEART BULLET*/
+ 0, /* U+002766: FLORAL HEART*/
+ 0, /* U+002767: ROTATED FLORAL HEART BULLET*/
+ 0, /* U+002768: MEDIUM LEFT PARENTHESIS ORNAMENT*/
+ 0, /* U+002769: MEDIUM RIGHT PARENTHESIS ORNAMENT*/
+ 0, /* U+00276a: MEDIUM FLATTENED LEFT PARENTHESIS ORNAMENT*/
+ 0, /* U+00276b: MEDIUM FLATTENED RIGHT PARENTHESIS ORNAMENT*/
+ 0, /* U+00276c: MEDIUM LEFT-POINTING ANGLE BRACKET ORNAMENT*/
+ 0, /* U+00276d: MEDIUM RIGHT-POINTING ANGLE BRACKET ORNAMENT*/
+ 0, /* U+00276e: HEAVY LEFT-POINTING ANGLE QUOTATION MARK ORNAMENT*/
+ 0, /* U+00276f: HEAVY RIGHT-POINTING ANGLE QUOTATION MARK ORNAMENT*/
+ 0, /* U+002770: HEAVY LEFT-POINTING ANGLE BRACKET ORNAMENT*/
+ 0, /* U+002771: HEAVY RIGHT-POINTING ANGLE BRACKET ORNAMENT*/
+ 0, /* U+002772: LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT*/
+ 0, /* U+002773: LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT*/
+ 0, /* U+002774: MEDIUM LEFT CURLY BRACKET ORNAMENT*/
+ 0, /* U+002775: MEDIUM RIGHT CURLY BRACKET ORNAMENT*/
+ 0, /* U+002776: DINGBAT NEGATIVE CIRCLED DIGIT ONE*/
+ 0, /* U+002777: DINGBAT NEGATIVE CIRCLED DIGIT TWO*/
+ 0, /* U+002778: DINGBAT NEGATIVE CIRCLED DIGIT THREE*/
+ 0, /* U+002779: DINGBAT NEGATIVE CIRCLED DIGIT FOUR*/
+ 0, /* U+00277a: DINGBAT NEGATIVE CIRCLED DIGIT FIVE*/
+ 0, /* U+00277b: DINGBAT NEGATIVE CIRCLED DIGIT SIX*/
+ 0, /* U+00277c: DINGBAT NEGATIVE CIRCLED DIGIT SEVEN*/
+ 0, /* U+00277d: DINGBAT NEGATIVE CIRCLED DIGIT EIGHT*/
+ 0, /* U+00277e: DINGBAT NEGATIVE CIRCLED DIGIT NINE*/
+ 0, /* U+00277f: DINGBAT NEGATIVE CIRCLED NUMBER TEN*/
+ 0, /* U+002780: DINGBAT CIRCLED SANS-SERIF DIGIT ONE*/
+ 0, /* U+002781: DINGBAT CIRCLED SANS-SERIF DIGIT TWO*/
+ 0, /* U+002782: DINGBAT CIRCLED SANS-SERIF DIGIT THREE*/
+ 0, /* U+002783: DINGBAT CIRCLED SANS-SERIF DIGIT FOUR*/
+ 0, /* U+002784: DINGBAT CIRCLED SANS-SERIF DIGIT FIVE*/
+ 0, /* U+002785: DINGBAT CIRCLED SANS-SERIF DIGIT SIX*/
+ 0, /* U+002786: DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN*/
+ 0, /* U+002787: DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT*/
+ 0, /* U+002788: DINGBAT CIRCLED SANS-SERIF DIGIT NINE*/
+ 0, /* U+002789: DINGBAT CIRCLED SANS-SERIF NUMBER TEN*/
+ 0, /* U+00278a: DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE*/
+ 0, /* U+00278b: DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO*/
+ 0, /* U+00278c: DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE*/
+ 0, /* U+00278d: DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR*/
+ 0, /* U+00278e: DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE*/
+ 0, /* U+00278f: DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX*/
+ 0, /* U+002790: DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN*/
+ 0, /* U+002791: DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT*/
+ 0, /* U+002792: DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE*/
+ 0, /* U+002793: DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN*/
+ 0, /* U+002794: HEAVY WIDE-HEADED RIGHTWARDS ARROW*/
+ 0, /* U+002795: HEAVY PLUS SIGN*/
+ 0, /* U+002796: HEAVY MINUS SIGN*/
+ 0, /* U+002797: HEAVY DIVISION SIGN*/
+ 0, /* U+002798: HEAVY SOUTH EAST ARROW*/
+ 0, /* U+002799: HEAVY RIGHTWARDS ARROW*/
+ 0, /* U+00279a: HEAVY NORTH EAST ARROW*/
+ 0, /* U+00279b: DRAFTING POINT RIGHTWARDS ARROW*/
+ 0, /* U+00279c: HEAVY ROUND-TIPPED RIGHTWARDS ARROW*/
+ 0, /* U+00279d: TRIANGLE-HEADED RIGHTWARDS ARROW*/
+ 0, /* U+00279e: HEAVY TRIANGLE-HEADED RIGHTWARDS ARROW*/
+ 0, /* U+00279f: DASHED TRIANGLE-HEADED RIGHTWARDS ARROW*/
+ 0, /* U+0027a0: HEAVY DASHED TRIANGLE-HEADED RIGHTWARDS ARROW*/
+ 0, /* U+0027a1: BLACK RIGHTWARDS ARROW*/
+ 0, /* U+0027a2: THREE-D TOP-LIGHTED RIGHTWARDS ARROWHEAD*/
+ 0, /* U+0027a3: THREE-D BOTTOM-LIGHTED RIGHTWARDS ARROWHEAD*/
+ 0, /* U+0027a4: BLACK RIGHTWARDS ARROWHEAD*/
+ 0, /* U+0027a5: HEAVY BLACK CURVED DOWNWARDS AND RIGHTWARDS ARROW*/
+ 0, /* U+0027a6: HEAVY BLACK CURVED UPWARDS AND RIGHTWARDS ARROW*/
+ 0, /* U+0027a7: SQUAT BLACK RIGHTWARDS ARROW*/
+ 0, /* U+0027a8: HEAVY CONCAVE-POINTED BLACK RIGHTWARDS ARROW*/
+ 0, /* U+0027a9: RIGHT-SHADED WHITE RIGHTWARDS ARROW*/
+ 0, /* U+0027aa: LEFT-SHADED WHITE RIGHTWARDS ARROW*/
+ 0, /* U+0027ab: BACK-TILTED SHADOWED WHITE RIGHTWARDS ARROW*/
+ 0, /* U+0027ac: FRONT-TILTED SHADOWED WHITE RIGHTWARDS ARROW*/
+ 0, /* U+0027ad: HEAVY LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW*/
+ 0, /* U+0027ae: HEAVY UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW*/
+ 0, /* U+0027af: NOTCHED LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW*/
+ 0, /* U+0027b0: CURLY LOOP*/
+ 0, /* U+0027b1: NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW*/
+ 0, /* U+0027b2: CIRCLED HEAVY WHITE RIGHTWARDS ARROW*/
+ 0, /* U+0027b3: WHITE-FEATHERED RIGHTWARDS ARROW*/
+ 0, /* U+0027b4: BLACK-FEATHERED SOUTH EAST ARROW*/
+ 0, /* U+0027b5: BLACK-FEATHERED RIGHTWARDS ARROW*/
+ 0, /* U+0027b6: BLACK-FEATHERED NORTH EAST ARROW*/
+ 0, /* U+0027b7: HEAVY BLACK-FEATHERED SOUTH EAST ARROW*/
+ 0, /* U+0027b8: HEAVY BLACK-FEATHERED RIGHTWARDS ARROW*/
+ 0, /* U+0027b9: HEAVY BLACK-FEATHERED NORTH EAST ARROW*/
+ 0, /* U+0027ba: TEARDROP-BARBED RIGHTWARDS ARROW*/
+ 0, /* U+0027bb: HEAVY TEARDROP-SHANKED RIGHTWARDS ARROW*/
+ 0, /* U+0027bc: WEDGE-TAILED RIGHTWARDS ARROW*/
+ 0, /* U+0027bd: HEAVY WEDGE-TAILED RIGHTWARDS ARROW*/
+ 0, /* U+0027be: OPEN-OUTLINED RIGHTWARDS ARROW*/
+ 0, /* U+0027bf: DOUBLE CURLY LOOP*/
+ 0, /* U+0027c0: THREE DIMENSIONAL ANGLE*/
+ 0, /* U+0027c1: WHITE TRIANGLE CONTAINING SMALL WHITE TRIANGLE*/
+ 0, /* U+0027c2: PERPENDICULAR*/
+ 0, /* U+0027c3: OPEN SUBSET*/
+ 0, /* U+0027c4: OPEN SUPERSET*/
+ 0, /* U+0027c5: LEFT S-SHAPED BAG DELIMITER*/
+ 0, /* U+0027c6: RIGHT S-SHAPED BAG DELIMITER*/
+ 0, /* U+0027c7: OR WITH DOT INSIDE*/
+ 0, /* U+0027c8: REVERSE SOLIDUS PRECEDING SUBSET*/
+ 0, /* U+0027c9: SUPERSET PRECEDING SOLIDUS*/
+ 0, /* U+0027ca: VERTICAL BAR WITH HORIZONTAL STROKE*/
+ 0, /* U+0027cb: MATHEMATICAL RISING DIAGONAL*/
+ 0, /* U+0027cc: LONG DIVISION*/
+ 0, /* U+0027cd: MATHEMATICAL FALLING DIAGONAL*/
+ 0, /* U+0027ce: SQUARED LOGICAL AND*/
+ 0, /* U+0027cf: SQUARED LOGICAL OR*/
+ 0, /* U+0027d0: WHITE DIAMOND WITH CENTRED DOT*/
+ 0, /* U+0027d1: AND WITH DOT*/
+ 0, /* U+0027d2: ELEMENT OF OPENING UPWARDS*/
+ 0, /* U+0027d3: LOWER RIGHT CORNER WITH DOT*/
+ 0, /* U+0027d4: UPPER LEFT CORNER WITH DOT*/
+ 0, /* U+0027d5: LEFT OUTER JOIN*/
+ 0, /* U+0027d6: RIGHT OUTER JOIN*/
+ 0, /* U+0027d7: FULL OUTER JOIN*/
+ 0, /* U+0027d8: LARGE UP TACK*/
+ 0, /* U+0027d9: LARGE DOWN TACK*/
+ 0, /* U+0027da: LEFT AND RIGHT DOUBLE TURNSTILE*/
+ 0, /* U+0027db: LEFT AND RIGHT TACK*/
+ 0, /* U+0027dc: LEFT MULTIMAP*/
+ 0, /* U+0027dd: LONG RIGHT TACK*/
+ 0, /* U+0027de: LONG LEFT TACK*/
+ 0, /* U+0027df: UP TACK WITH CIRCLE ABOVE*/
+ 0, /* U+0027e0: LOZENGE DIVIDED BY HORIZONTAL RULE*/
+ 0, /* U+0027e1: WHITE CONCAVE-SIDED DIAMOND*/
+ 0, /* U+0027e2: WHITE CONCAVE-SIDED DIAMOND WITH LEFTWARDS TICK*/
+ 0, /* U+0027e3: WHITE CONCAVE-SIDED DIAMOND WITH RIGHTWARDS TICK*/
+ 0, /* U+0027e4: WHITE SQUARE WITH LEFTWARDS TICK*/
+ 0, /* U+0027e5: WHITE SQUARE WITH RIGHTWARDS TICK*/
+ 0, /* U+0027e6: MATHEMATICAL LEFT WHITE SQUARE BRACKET*/
+ 0, /* U+0027e7: MATHEMATICAL RIGHT WHITE SQUARE BRACKET*/
+ 0, /* U+0027e8: MATHEMATICAL LEFT ANGLE BRACKET*/
+ 0, /* U+0027e9: MATHEMATICAL RIGHT ANGLE BRACKET*/
+ 0, /* U+0027ea: MATHEMATICAL LEFT DOUBLE ANGLE BRACKET*/
+ 0, /* U+0027eb: MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET*/
+ 0, /* U+0027ec: MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET*/
+ 0, /* U+0027ed: MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET*/
+ 0, /* U+0027ee: MATHEMATICAL LEFT FLATTENED PARENTHESIS*/
+ 0, /* U+0027ef: MATHEMATICAL RIGHT FLATTENED PARENTHESIS*/
+ 0, /* U+0027f0: UPWARDS QUADRUPLE ARROW*/
+ 0, /* U+0027f1: DOWNWARDS QUADRUPLE ARROW*/
+ 0, /* U+0027f2: ANTICLOCKWISE GAPPED CIRCLE ARROW*/
+ 0, /* U+0027f3: CLOCKWISE GAPPED CIRCLE ARROW*/
+ 0, /* U+0027f4: RIGHT ARROW WITH CIRCLED PLUS*/
+ 0, /* U+0027f5: LONG LEFTWARDS ARROW*/
+ 0, /* U+0027f6: LONG RIGHTWARDS ARROW*/
+ 0, /* U+0027f7: LONG LEFT RIGHT ARROW*/
+ 0, /* U+0027f8: LONG LEFTWARDS DOUBLE ARROW*/
+ 0, /* U+0027f9: LONG RIGHTWARDS DOUBLE ARROW*/
+ 0, /* U+0027fa: LONG LEFT RIGHT DOUBLE ARROW*/
+ 0, /* U+0027fb: LONG LEFTWARDS ARROW FROM BAR*/
+ 0, /* U+0027fc: LONG RIGHTWARDS ARROW FROM BAR*/
+ 0, /* U+0027fd: LONG LEFTWARDS DOUBLE ARROW FROM BAR*/
+ 0, /* U+0027fe: LONG RIGHTWARDS DOUBLE ARROW FROM BAR*/
+ 0, /* U+0027ff: LONG RIGHTWARDS SQUIGGLE ARROW*/
+ 0, /* U+002800: BRAILLE PATTERN BLANK*/
+ 0, /* U+002801: BRAILLE PATTERN DOTS-1*/
+ 0, /* U+002802: BRAILLE PATTERN DOTS-2*/
+ 0, /* U+002803: BRAILLE PATTERN DOTS-12*/
+ 0, /* U+002804: BRAILLE PATTERN DOTS-3*/
+ 0, /* U+002805: BRAILLE PATTERN DOTS-13*/
+ 0, /* U+002806: BRAILLE PATTERN DOTS-23*/
+ 0, /* U+002807: BRAILLE PATTERN DOTS-123*/
+ 0, /* U+002808: BRAILLE PATTERN DOTS-4*/
+ 0, /* U+002809: BRAILLE PATTERN DOTS-14*/
+ 0, /* U+00280a: BRAILLE PATTERN DOTS-24*/
+ 0, /* U+00280b: BRAILLE PATTERN DOTS-124*/
+ 0, /* U+00280c: BRAILLE PATTERN DOTS-34*/
+ 0, /* U+00280d: BRAILLE PATTERN DOTS-134*/
+ 0, /* U+00280e: BRAILLE PATTERN DOTS-234*/
+ 0, /* U+00280f: BRAILLE PATTERN DOTS-1234*/
+ 0, /* U+002810: BRAILLE PATTERN DOTS-5*/
+ 0, /* U+002811: BRAILLE PATTERN DOTS-15*/
+ 0, /* U+002812: BRAILLE PATTERN DOTS-25*/
+ 0, /* U+002813: BRAILLE PATTERN DOTS-125*/
+ 0, /* U+002814: BRAILLE PATTERN DOTS-35*/
+ 0, /* U+002815: BRAILLE PATTERN DOTS-135*/
+ 0, /* U+002816: BRAILLE PATTERN DOTS-235*/
+ 0, /* U+002817: BRAILLE PATTERN DOTS-1235*/
+ 0, /* U+002818: BRAILLE PATTERN DOTS-45*/
+ 0, /* U+002819: BRAILLE PATTERN DOTS-145*/
+ 0, /* U+00281a: BRAILLE PATTERN DOTS-245*/
+ 0, /* U+00281b: BRAILLE PATTERN DOTS-1245*/
+ 0, /* U+00281c: BRAILLE PATTERN DOTS-345*/
+ 0, /* U+00281d: BRAILLE PATTERN DOTS-1345*/
+ 0, /* U+00281e: BRAILLE PATTERN DOTS-2345*/
+ 0, /* U+00281f: BRAILLE PATTERN DOTS-12345*/
+ 0, /* U+002820: BRAILLE PATTERN DOTS-6*/
+ 0, /* U+002821: BRAILLE PATTERN DOTS-16*/
+ 0, /* U+002822: BRAILLE PATTERN DOTS-26*/
+ 0, /* U+002823: BRAILLE PATTERN DOTS-126*/
+ 0, /* U+002824: BRAILLE PATTERN DOTS-36*/
+ 0, /* U+002825: BRAILLE PATTERN DOTS-136*/
+ 0, /* U+002826: BRAILLE PATTERN DOTS-236*/
+ 0, /* U+002827: BRAILLE PATTERN DOTS-1236*/
+ 0, /* U+002828: BRAILLE PATTERN DOTS-46*/
+ 0, /* U+002829: BRAILLE PATTERN DOTS-146*/
+ 0, /* U+00282a: BRAILLE PATTERN DOTS-246*/
+ 0, /* U+00282b: BRAILLE PATTERN DOTS-1246*/
+ 0, /* U+00282c: BRAILLE PATTERN DOTS-346*/
+ 0, /* U+00282d: BRAILLE PATTERN DOTS-1346*/
+ 0, /* U+00282e: BRAILLE PATTERN DOTS-2346*/
+ 0, /* U+00282f: BRAILLE PATTERN DOTS-12346*/
+ 0, /* U+002830: BRAILLE PATTERN DOTS-56*/
+ 0, /* U+002831: BRAILLE PATTERN DOTS-156*/
+ 0, /* U+002832: BRAILLE PATTERN DOTS-256*/
+ 0, /* U+002833: BRAILLE PATTERN DOTS-1256*/
+ 0, /* U+002834: BRAILLE PATTERN DOTS-356*/
+ 0, /* U+002835: BRAILLE PATTERN DOTS-1356*/
+ 0, /* U+002836: BRAILLE PATTERN DOTS-2356*/
+ 0, /* U+002837: BRAILLE PATTERN DOTS-12356*/
+ 0, /* U+002838: BRAILLE PATTERN DOTS-456*/
+ 0, /* U+002839: BRAILLE PATTERN DOTS-1456*/
+ 0, /* U+00283a: BRAILLE PATTERN DOTS-2456*/
+ 0, /* U+00283b: BRAILLE PATTERN DOTS-12456*/
+ 0, /* U+00283c: BRAILLE PATTERN DOTS-3456*/
+ 0, /* U+00283d: BRAILLE PATTERN DOTS-13456*/
+ 0, /* U+00283e: BRAILLE PATTERN DOTS-23456*/
+ 0, /* U+00283f: BRAILLE PATTERN DOTS-123456*/
+ 0, /* U+002840: BRAILLE PATTERN DOTS-7*/
+ 0, /* U+002841: BRAILLE PATTERN DOTS-17*/
+ 0, /* U+002842: BRAILLE PATTERN DOTS-27*/
+ 0, /* U+002843: BRAILLE PATTERN DOTS-127*/
+ 0, /* U+002844: BRAILLE PATTERN DOTS-37*/
+ 0, /* U+002845: BRAILLE PATTERN DOTS-137*/
+ 0, /* U+002846: BRAILLE PATTERN DOTS-237*/
+ 0, /* U+002847: BRAILLE PATTERN DOTS-1237*/
+ 0, /* U+002848: BRAILLE PATTERN DOTS-47*/
+ 0, /* U+002849: BRAILLE PATTERN DOTS-147*/
+ 0, /* U+00284a: BRAILLE PATTERN DOTS-247*/
+ 0, /* U+00284b: BRAILLE PATTERN DOTS-1247*/
+ 0, /* U+00284c: BRAILLE PATTERN DOTS-347*/
+ 0, /* U+00284d: BRAILLE PATTERN DOTS-1347*/
+ 0, /* U+00284e: BRAILLE PATTERN DOTS-2347*/
+ 0, /* U+00284f: BRAILLE PATTERN DOTS-12347*/
+ 0, /* U+002850: BRAILLE PATTERN DOTS-57*/
+ 0, /* U+002851: BRAILLE PATTERN DOTS-157*/
+ 0, /* U+002852: BRAILLE PATTERN DOTS-257*/
+ 0, /* U+002853: BRAILLE PATTERN DOTS-1257*/
+ 0, /* U+002854: BRAILLE PATTERN DOTS-357*/
+ 0, /* U+002855: BRAILLE PATTERN DOTS-1357*/
+ 0, /* U+002856: BRAILLE PATTERN DOTS-2357*/
+ 0, /* U+002857: BRAILLE PATTERN DOTS-12357*/
+ 0, /* U+002858: BRAILLE PATTERN DOTS-457*/
+ 0, /* U+002859: BRAILLE PATTERN DOTS-1457*/
+ 0, /* U+00285a: BRAILLE PATTERN DOTS-2457*/
+ 0, /* U+00285b: BRAILLE PATTERN DOTS-12457*/
+ 0, /* U+00285c: BRAILLE PATTERN DOTS-3457*/
+ 0, /* U+00285d: BRAILLE PATTERN DOTS-13457*/
+ 0, /* U+00285e: BRAILLE PATTERN DOTS-23457*/
+ 0, /* U+00285f: BRAILLE PATTERN DOTS-123457*/
+ 0, /* U+002860: BRAILLE PATTERN DOTS-67*/
+ 0, /* U+002861: BRAILLE PATTERN DOTS-167*/
+ 0, /* U+002862: BRAILLE PATTERN DOTS-267*/
+ 0, /* U+002863: BRAILLE PATTERN DOTS-1267*/
+ 0, /* U+002864: BRAILLE PATTERN DOTS-367*/
+ 0, /* U+002865: BRAILLE PATTERN DOTS-1367*/
+ 0, /* U+002866: BRAILLE PATTERN DOTS-2367*/
+ 0, /* U+002867: BRAILLE PATTERN DOTS-12367*/
+ 0, /* U+002868: BRAILLE PATTERN DOTS-467*/
+ 0, /* U+002869: BRAILLE PATTERN DOTS-1467*/
+ 0, /* U+00286a: BRAILLE PATTERN DOTS-2467*/
+ 0, /* U+00286b: BRAILLE PATTERN DOTS-12467*/
+ 0, /* U+00286c: BRAILLE PATTERN DOTS-3467*/
+ 0, /* U+00286d: BRAILLE PATTERN DOTS-13467*/
+ 0, /* U+00286e: BRAILLE PATTERN DOTS-23467*/
+ 0, /* U+00286f: BRAILLE PATTERN DOTS-123467*/
+ 0, /* U+002870: BRAILLE PATTERN DOTS-567*/
+ 0, /* U+002871: BRAILLE PATTERN DOTS-1567*/
+ 0, /* U+002872: BRAILLE PATTERN DOTS-2567*/
+ 0, /* U+002873: BRAILLE PATTERN DOTS-12567*/
+ 0, /* U+002874: BRAILLE PATTERN DOTS-3567*/
+ 0, /* U+002875: BRAILLE PATTERN DOTS-13567*/
+ 0, /* U+002876: BRAILLE PATTERN DOTS-23567*/
+ 0, /* U+002877: BRAILLE PATTERN DOTS-123567*/
+ 0, /* U+002878: BRAILLE PATTERN DOTS-4567*/
+ 0, /* U+002879: BRAILLE PATTERN DOTS-14567*/
+ 0, /* U+00287a: BRAILLE PATTERN DOTS-24567*/
+ 0, /* U+00287b: BRAILLE PATTERN DOTS-124567*/
+ 0, /* U+00287c: BRAILLE PATTERN DOTS-34567*/
+ 0, /* U+00287d: BRAILLE PATTERN DOTS-134567*/
+ 0, /* U+00287e: BRAILLE PATTERN DOTS-234567*/
+ 0, /* U+00287f: BRAILLE PATTERN DOTS-1234567*/
+ 0, /* U+002880: BRAILLE PATTERN DOTS-8*/
+ 0, /* U+002881: BRAILLE PATTERN DOTS-18*/
+ 0, /* U+002882: BRAILLE PATTERN DOTS-28*/
+ 0, /* U+002883: BRAILLE PATTERN DOTS-128*/
+ 0, /* U+002884: BRAILLE PATTERN DOTS-38*/
+ 0, /* U+002885: BRAILLE PATTERN DOTS-138*/
+ 0, /* U+002886: BRAILLE PATTERN DOTS-238*/
+ 0, /* U+002887: BRAILLE PATTERN DOTS-1238*/
+ 0, /* U+002888: BRAILLE PATTERN DOTS-48*/
+ 0, /* U+002889: BRAILLE PATTERN DOTS-148*/
+ 0, /* U+00288a: BRAILLE PATTERN DOTS-248*/
+ 0, /* U+00288b: BRAILLE PATTERN DOTS-1248*/
+ 0, /* U+00288c: BRAILLE PATTERN DOTS-348*/
+ 0, /* U+00288d: BRAILLE PATTERN DOTS-1348*/
+ 0, /* U+00288e: BRAILLE PATTERN DOTS-2348*/
+ 0, /* U+00288f: BRAILLE PATTERN DOTS-12348*/
+ 0, /* U+002890: BRAILLE PATTERN DOTS-58*/
+ 0, /* U+002891: BRAILLE PATTERN DOTS-158*/
+ 0, /* U+002892: BRAILLE PATTERN DOTS-258*/
+ 0, /* U+002893: BRAILLE PATTERN DOTS-1258*/
+ 0, /* U+002894: BRAILLE PATTERN DOTS-358*/
+ 0, /* U+002895: BRAILLE PATTERN DOTS-1358*/
+ 0, /* U+002896: BRAILLE PATTERN DOTS-2358*/
+ 0, /* U+002897: BRAILLE PATTERN DOTS-12358*/
+ 0, /* U+002898: BRAILLE PATTERN DOTS-458*/
+ 0, /* U+002899: BRAILLE PATTERN DOTS-1458*/
+ 0, /* U+00289a: BRAILLE PATTERN DOTS-2458*/
+ 0, /* U+00289b: BRAILLE PATTERN DOTS-12458*/
+ 0, /* U+00289c: BRAILLE PATTERN DOTS-3458*/
+ 0, /* U+00289d: BRAILLE PATTERN DOTS-13458*/
+ 0, /* U+00289e: BRAILLE PATTERN DOTS-23458*/
+ 0, /* U+00289f: BRAILLE PATTERN DOTS-123458*/
+ 0, /* U+0028a0: BRAILLE PATTERN DOTS-68*/
+ 0, /* U+0028a1: BRAILLE PATTERN DOTS-168*/
+ 0, /* U+0028a2: BRAILLE PATTERN DOTS-268*/
+ 0, /* U+0028a3: BRAILLE PATTERN DOTS-1268*/
+ 0, /* U+0028a4: BRAILLE PATTERN DOTS-368*/
+ 0, /* U+0028a5: BRAILLE PATTERN DOTS-1368*/
+ 0, /* U+0028a6: BRAILLE PATTERN DOTS-2368*/
+ 0, /* U+0028a7: BRAILLE PATTERN DOTS-12368*/
+ 0, /* U+0028a8: BRAILLE PATTERN DOTS-468*/
+ 0, /* U+0028a9: BRAILLE PATTERN DOTS-1468*/
+ 0, /* U+0028aa: BRAILLE PATTERN DOTS-2468*/
+ 0, /* U+0028ab: BRAILLE PATTERN DOTS-12468*/
+ 0, /* U+0028ac: BRAILLE PATTERN DOTS-3468*/
+ 0, /* U+0028ad: BRAILLE PATTERN DOTS-13468*/
+ 0, /* U+0028ae: BRAILLE PATTERN DOTS-23468*/
+ 0, /* U+0028af: BRAILLE PATTERN DOTS-123468*/
+ 0, /* U+0028b0: BRAILLE PATTERN DOTS-568*/
+ 0, /* U+0028b1: BRAILLE PATTERN DOTS-1568*/
+ 0, /* U+0028b2: BRAILLE PATTERN DOTS-2568*/
+ 0, /* U+0028b3: BRAILLE PATTERN DOTS-12568*/
+ 0, /* U+0028b4: BRAILLE PATTERN DOTS-3568*/
+ 0, /* U+0028b5: BRAILLE PATTERN DOTS-13568*/
+ 0, /* U+0028b6: BRAILLE PATTERN DOTS-23568*/
+ 0, /* U+0028b7: BRAILLE PATTERN DOTS-123568*/
+ 0, /* U+0028b8: BRAILLE PATTERN DOTS-4568*/
+ 0, /* U+0028b9: BRAILLE PATTERN DOTS-14568*/
+ 0, /* U+0028ba: BRAILLE PATTERN DOTS-24568*/
+ 0, /* U+0028bb: BRAILLE PATTERN DOTS-124568*/
+ 0, /* U+0028bc: BRAILLE PATTERN DOTS-34568*/
+ 0, /* U+0028bd: BRAILLE PATTERN DOTS-134568*/
+ 0, /* U+0028be: BRAILLE PATTERN DOTS-234568*/
+ 0, /* U+0028bf: BRAILLE PATTERN DOTS-1234568*/
+ 0, /* U+0028c0: BRAILLE PATTERN DOTS-78*/
+ 0, /* U+0028c1: BRAILLE PATTERN DOTS-178*/
+ 0, /* U+0028c2: BRAILLE PATTERN DOTS-278*/
+ 0, /* U+0028c3: BRAILLE PATTERN DOTS-1278*/
+ 0, /* U+0028c4: BRAILLE PATTERN DOTS-378*/
+ 0, /* U+0028c5: BRAILLE PATTERN DOTS-1378*/
+ 0, /* U+0028c6: BRAILLE PATTERN DOTS-2378*/
+ 0, /* U+0028c7: BRAILLE PATTERN DOTS-12378*/
+ 0, /* U+0028c8: BRAILLE PATTERN DOTS-478*/
+ 0, /* U+0028c9: BRAILLE PATTERN DOTS-1478*/
+ 0, /* U+0028ca: BRAILLE PATTERN DOTS-2478*/
+ 0, /* U+0028cb: BRAILLE PATTERN DOTS-12478*/
+ 0, /* U+0028cc: BRAILLE PATTERN DOTS-3478*/
+ 0, /* U+0028cd: BRAILLE PATTERN DOTS-13478*/
+ 0, /* U+0028ce: BRAILLE PATTERN DOTS-23478*/
+ 0, /* U+0028cf: BRAILLE PATTERN DOTS-123478*/
+ 0, /* U+0028d0: BRAILLE PATTERN DOTS-578*/
+ 0, /* U+0028d1: BRAILLE PATTERN DOTS-1578*/
+ 0, /* U+0028d2: BRAILLE PATTERN DOTS-2578*/
+ 0, /* U+0028d3: BRAILLE PATTERN DOTS-12578*/
+ 0, /* U+0028d4: BRAILLE PATTERN DOTS-3578*/
+ 0, /* U+0028d5: BRAILLE PATTERN DOTS-13578*/
+ 0, /* U+0028d6: BRAILLE PATTERN DOTS-23578*/
+ 0, /* U+0028d7: BRAILLE PATTERN DOTS-123578*/
+ 0, /* U+0028d8: BRAILLE PATTERN DOTS-4578*/
+ 0, /* U+0028d9: BRAILLE PATTERN DOTS-14578*/
+ 0, /* U+0028da: BRAILLE PATTERN DOTS-24578*/
+ 0, /* U+0028db: BRAILLE PATTERN DOTS-124578*/
+ 0, /* U+0028dc: BRAILLE PATTERN DOTS-34578*/
+ 0, /* U+0028dd: BRAILLE PATTERN DOTS-134578*/
+ 0, /* U+0028de: BRAILLE PATTERN DOTS-234578*/
+ 0, /* U+0028df: BRAILLE PATTERN DOTS-1234578*/
+ 0, /* U+0028e0: BRAILLE PATTERN DOTS-678*/
+ 0, /* U+0028e1: BRAILLE PATTERN DOTS-1678*/
+ 0, /* U+0028e2: BRAILLE PATTERN DOTS-2678*/
+ 0, /* U+0028e3: BRAILLE PATTERN DOTS-12678*/
+ 0, /* U+0028e4: BRAILLE PATTERN DOTS-3678*/
+ 0, /* U+0028e5: BRAILLE PATTERN DOTS-13678*/
+ 0, /* U+0028e6: BRAILLE PATTERN DOTS-23678*/
+ 0, /* U+0028e7: BRAILLE PATTERN DOTS-123678*/
+ 0, /* U+0028e8: BRAILLE PATTERN DOTS-4678*/
+ 0, /* U+0028e9: BRAILLE PATTERN DOTS-14678*/
+ 0, /* U+0028ea: BRAILLE PATTERN DOTS-24678*/
+ 0, /* U+0028eb: BRAILLE PATTERN DOTS-124678*/
+ 0, /* U+0028ec: BRAILLE PATTERN DOTS-34678*/
+ 0, /* U+0028ed: BRAILLE PATTERN DOTS-134678*/
+ 0, /* U+0028ee: BRAILLE PATTERN DOTS-234678*/
+ 0, /* U+0028ef: BRAILLE PATTERN DOTS-1234678*/
+ 0, /* U+0028f0: BRAILLE PATTERN DOTS-5678*/
+ 0, /* U+0028f1: BRAILLE PATTERN DOTS-15678*/
+ 0, /* U+0028f2: BRAILLE PATTERN DOTS-25678*/
+ 0, /* U+0028f3: BRAILLE PATTERN DOTS-125678*/
+ 0, /* U+0028f4: BRAILLE PATTERN DOTS-35678*/
+ 0, /* U+0028f5: BRAILLE PATTERN DOTS-135678*/
+ 0, /* U+0028f6: BRAILLE PATTERN DOTS-235678*/
+ 0, /* U+0028f7: BRAILLE PATTERN DOTS-1235678*/
+ 0, /* U+0028f8: BRAILLE PATTERN DOTS-45678*/
+ 0, /* U+0028f9: BRAILLE PATTERN DOTS-145678*/
+ 0, /* U+0028fa: BRAILLE PATTERN DOTS-245678*/
+ 0, /* U+0028fb: BRAILLE PATTERN DOTS-1245678*/
+ 0, /* U+0028fc: BRAILLE PATTERN DOTS-345678*/
+ 0, /* U+0028fd: BRAILLE PATTERN DOTS-1345678*/
+ 0, /* U+0028fe: BRAILLE PATTERN DOTS-2345678*/
+ 0, /* U+0028ff: BRAILLE PATTERN DOTS-12345678*/
+ 0, /* U+002900: RIGHTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE*/
+ 0, /* U+002901: RIGHTWARDS TWO-HEADED ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+002902: LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE*/
+ 0, /* U+002903: RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE*/
+ 0, /* U+002904: LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE*/
+ 0, /* U+002905: RIGHTWARDS TWO-HEADED ARROW FROM BAR*/
+ 0, /* U+002906: LEFTWARDS DOUBLE ARROW FROM BAR*/
+ 0, /* U+002907: RIGHTWARDS DOUBLE ARROW FROM BAR*/
+ 0, /* U+002908: DOWNWARDS ARROW WITH HORIZONTAL STROKE*/
+ 0, /* U+002909: UPWARDS ARROW WITH HORIZONTAL STROKE*/
+ 0, /* U+00290a: UPWARDS TRIPLE ARROW*/
+ 0, /* U+00290b: DOWNWARDS TRIPLE ARROW*/
+ 0, /* U+00290c: LEFTWARDS DOUBLE DASH ARROW*/
+ 0, /* U+00290d: RIGHTWARDS DOUBLE DASH ARROW*/
+ 0, /* U+00290e: LEFTWARDS TRIPLE DASH ARROW*/
+ 0, /* U+00290f: RIGHTWARDS TRIPLE DASH ARROW*/
+ 0, /* U+002910: RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW*/
+ 0, /* U+002911: RIGHTWARDS ARROW WITH DOTTED STEM*/
+ 0, /* U+002912: UPWARDS ARROW TO BAR*/
+ 0, /* U+002913: DOWNWARDS ARROW TO BAR*/
+ 0, /* U+002914: RIGHTWARDS ARROW WITH TAIL WITH VERTICAL STROKE*/
+ 0, /* U+002915: RIGHTWARDS ARROW WITH TAIL WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+002916: RIGHTWARDS TWO-HEADED ARROW WITH TAIL*/
+ 0, /* U+002917: RIGHTWARDS TWO-HEADED ARROW WITH TAIL WITH VERTICAL STROKE*/
+ 0, /* U+002918: RIGHTWARDS TWO-HEADED ARROW WITH TAIL WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+002919: LEFTWARDS ARROW-TAIL*/
+ 0, /* U+00291a: RIGHTWARDS ARROW-TAIL*/
+ 0, /* U+00291b: LEFTWARDS DOUBLE ARROW-TAIL*/
+ 0, /* U+00291c: RIGHTWARDS DOUBLE ARROW-TAIL*/
+ 0, /* U+00291d: LEFTWARDS ARROW TO BLACK DIAMOND*/
+ 0, /* U+00291e: RIGHTWARDS ARROW TO BLACK DIAMOND*/
+ 0, /* U+00291f: LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND*/
+ 0, /* U+002920: RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND*/
+ 0, /* U+002921: NORTH WEST AND SOUTH EAST ARROW*/
+ 0, /* U+002922: NORTH EAST AND SOUTH WEST ARROW*/
+ 0, /* U+002923: NORTH WEST ARROW WITH HOOK*/
+ 0, /* U+002924: NORTH EAST ARROW WITH HOOK*/
+ 0, /* U+002925: SOUTH EAST ARROW WITH HOOK*/
+ 0, /* U+002926: SOUTH WEST ARROW WITH HOOK*/
+ 0, /* U+002927: NORTH WEST ARROW AND NORTH EAST ARROW*/
+ 0, /* U+002928: NORTH EAST ARROW AND SOUTH EAST ARROW*/
+ 0, /* U+002929: SOUTH EAST ARROW AND SOUTH WEST ARROW*/
+ 0, /* U+00292a: SOUTH WEST ARROW AND NORTH WEST ARROW*/
+ 0, /* U+00292b: RISING DIAGONAL CROSSING FALLING DIAGONAL*/
+ 0, /* U+00292c: FALLING DIAGONAL CROSSING RISING DIAGONAL*/
+ 0, /* U+00292d: SOUTH EAST ARROW CROSSING NORTH EAST ARROW*/
+ 0, /* U+00292e: NORTH EAST ARROW CROSSING SOUTH EAST ARROW*/
+ 0, /* U+00292f: FALLING DIAGONAL CROSSING NORTH EAST ARROW*/
+ 0, /* U+002930: RISING DIAGONAL CROSSING SOUTH EAST ARROW*/
+ 0, /* U+002931: NORTH EAST ARROW CROSSING NORTH WEST ARROW*/
+ 0, /* U+002932: NORTH WEST ARROW CROSSING NORTH EAST ARROW*/
+ 0, /* U+002933: WAVE ARROW POINTING DIRECTLY RIGHT*/
+ 0, /* U+002934: ARROW POINTING RIGHTWARDS THEN CURVING UPWARDS*/
+ 0, /* U+002935: ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS*/
+ 0, /* U+002936: ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS*/
+ 0, /* U+002937: ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS*/
+ 0, /* U+002938: RIGHT-SIDE ARC CLOCKWISE ARROW*/
+ 0, /* U+002939: LEFT-SIDE ARC ANTICLOCKWISE ARROW*/
+ 0, /* U+00293a: TOP ARC ANTICLOCKWISE ARROW*/
+ 0, /* U+00293b: BOTTOM ARC ANTICLOCKWISE ARROW*/
+ 0, /* U+00293c: TOP ARC CLOCKWISE ARROW WITH MINUS*/
+ 0, /* U+00293d: TOP ARC ANTICLOCKWISE ARROW WITH PLUS*/
+ 0, /* U+00293e: LOWER RIGHT SEMICIRCULAR CLOCKWISE ARROW*/
+ 0, /* U+00293f: LOWER LEFT SEMICIRCULAR ANTICLOCKWISE ARROW*/
+ 0, /* U+002940: ANTICLOCKWISE CLOSED CIRCLE ARROW*/
+ 0, /* U+002941: CLOCKWISE CLOSED CIRCLE ARROW*/
+ 0, /* U+002942: RIGHTWARDS ARROW ABOVE SHORT LEFTWARDS ARROW*/
+ 0, /* U+002943: LEFTWARDS ARROW ABOVE SHORT RIGHTWARDS ARROW*/
+ 0, /* U+002944: SHORT RIGHTWARDS ARROW ABOVE LEFTWARDS ARROW*/
+ 0, /* U+002945: RIGHTWARDS ARROW WITH PLUS BELOW*/
+ 0, /* U+002946: LEFTWARDS ARROW WITH PLUS BELOW*/
+ 0, /* U+002947: RIGHTWARDS ARROW THROUGH X*/
+ 0, /* U+002948: LEFT RIGHT ARROW THROUGH SMALL CIRCLE*/
+ 0, /* U+002949: UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE*/
+ 0, /* U+00294a: LEFT BARB UP RIGHT BARB DOWN HARPOON*/
+ 0, /* U+00294b: LEFT BARB DOWN RIGHT BARB UP HARPOON*/
+ 0, /* U+00294c: UP BARB RIGHT DOWN BARB LEFT HARPOON*/
+ 0, /* U+00294d: UP BARB LEFT DOWN BARB RIGHT HARPOON*/
+ 0, /* U+00294e: LEFT BARB UP RIGHT BARB UP HARPOON*/
+ 0, /* U+00294f: UP BARB RIGHT DOWN BARB RIGHT HARPOON*/
+ 0, /* U+002950: LEFT BARB DOWN RIGHT BARB DOWN HARPOON*/
+ 0, /* U+002951: UP BARB LEFT DOWN BARB LEFT HARPOON*/
+ 0, /* U+002952: LEFTWARDS HARPOON WITH BARB UP TO BAR*/
+ 0, /* U+002953: RIGHTWARDS HARPOON WITH BARB UP TO BAR*/
+ 0, /* U+002954: UPWARDS HARPOON WITH BARB RIGHT TO BAR*/
+ 0, /* U+002955: DOWNWARDS HARPOON WITH BARB RIGHT TO BAR*/
+ 0, /* U+002956: LEFTWARDS HARPOON WITH BARB DOWN TO BAR*/
+ 0, /* U+002957: RIGHTWARDS HARPOON WITH BARB DOWN TO BAR*/
+ 0, /* U+002958: UPWARDS HARPOON WITH BARB LEFT TO BAR*/
+ 0, /* U+002959: DOWNWARDS HARPOON WITH BARB LEFT TO BAR*/
+ 0, /* U+00295a: LEFTWARDS HARPOON WITH BARB UP FROM BAR*/
+ 0, /* U+00295b: RIGHTWARDS HARPOON WITH BARB UP FROM BAR*/
+ 0, /* U+00295c: UPWARDS HARPOON WITH BARB RIGHT FROM BAR*/
+ 0, /* U+00295d: DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR*/
+ 0, /* U+00295e: LEFTWARDS HARPOON WITH BARB DOWN FROM BAR*/
+ 0, /* U+00295f: RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR*/
+ 0, /* U+002960: UPWARDS HARPOON WITH BARB LEFT FROM BAR*/
+ 0, /* U+002961: DOWNWARDS HARPOON WITH BARB LEFT FROM BAR*/
+ 0, /* U+002962: LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN*/
+ 0, /* U+002963: UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT*/
+ 0, /* U+002964: RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN*/
+ 0, /* U+002965: DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT*/
+ 0, /* U+002966: LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP*/
+ 0, /* U+002967: LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN*/
+ 0, /* U+002968: RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP*/
+ 0, /* U+002969: RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN*/
+ 0, /* U+00296a: LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH*/
+ 0, /* U+00296b: LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH*/
+ 0, /* U+00296c: RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH*/
+ 0, /* U+00296d: RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH*/
+ 0, /* U+00296e: UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT*/
+ 0, /* U+00296f: DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT*/
+ 0, /* U+002970: RIGHT DOUBLE ARROW WITH ROUNDED HEAD*/
+ 0, /* U+002971: EQUALS SIGN ABOVE RIGHTWARDS ARROW*/
+ 0, /* U+002972: TILDE OPERATOR ABOVE RIGHTWARDS ARROW*/
+ 0, /* U+002973: LEFTWARDS ARROW ABOVE TILDE OPERATOR*/
+ 0, /* U+002974: RIGHTWARDS ARROW ABOVE TILDE OPERATOR*/
+ 0, /* U+002975: RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO*/
+ 0, /* U+002976: LESS-THAN ABOVE LEFTWARDS ARROW*/
+ 0, /* U+002977: LEFTWARDS ARROW THROUGH LESS-THAN*/
+ 0, /* U+002978: GREATER-THAN ABOVE RIGHTWARDS ARROW*/
+ 0, /* U+002979: SUBSET ABOVE RIGHTWARDS ARROW*/
+ 0, /* U+00297a: LEFTWARDS ARROW THROUGH SUBSET*/
+ 0, /* U+00297b: SUPERSET ABOVE LEFTWARDS ARROW*/
+ 0, /* U+00297c: LEFT FISH TAIL*/
+ 0, /* U+00297d: RIGHT FISH TAIL*/
+ 0, /* U+00297e: UP FISH TAIL*/
+ 0, /* U+00297f: DOWN FISH TAIL*/
+ 0, /* U+002980: TRIPLE VERTICAL BAR DELIMITER*/
+ 0, /* U+002981: Z NOTATION SPOT*/
+ 0, /* U+002982: Z NOTATION TYPE COLON*/
+ 0, /* U+002983: LEFT WHITE CURLY BRACKET*/
+ 0, /* U+002984: RIGHT WHITE CURLY BRACKET*/
+ 0, /* U+002985: LEFT WHITE PARENTHESIS*/
+ 0, /* U+002986: RIGHT WHITE PARENTHESIS*/
+ 0, /* U+002987: Z NOTATION LEFT IMAGE BRACKET*/
+ 0, /* U+002988: Z NOTATION RIGHT IMAGE BRACKET*/
+ 0, /* U+002989: Z NOTATION LEFT BINDING BRACKET*/
+ 0, /* U+00298a: Z NOTATION RIGHT BINDING BRACKET*/
+ 0, /* U+00298b: LEFT SQUARE BRACKET WITH UNDERBAR*/
+ 0, /* U+00298c: RIGHT SQUARE BRACKET WITH UNDERBAR*/
+ 0, /* U+00298d: LEFT SQUARE BRACKET WITH TICK IN TOP CORNER*/
+ 0, /* U+00298e: RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER*/
+ 0, /* U+00298f: LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER*/
+ 0, /* U+002990: RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER*/
+ 0, /* U+002991: LEFT ANGLE BRACKET WITH DOT*/
+ 0, /* U+002992: RIGHT ANGLE BRACKET WITH DOT*/
+ 0, /* U+002993: LEFT ARC LESS-THAN BRACKET*/
+ 0, /* U+002994: RIGHT ARC GREATER-THAN BRACKET*/
+ 0, /* U+002995: DOUBLE LEFT ARC GREATER-THAN BRACKET*/
+ 0, /* U+002996: DOUBLE RIGHT ARC LESS-THAN BRACKET*/
+ 0, /* U+002997: LEFT BLACK TORTOISE SHELL BRACKET*/
+ 0, /* U+002998: RIGHT BLACK TORTOISE SHELL BRACKET*/
+ 0, /* U+002999: DOTTED FENCE*/
+ 0, /* U+00299a: VERTICAL ZIGZAG LINE*/
+ 0, /* U+00299b: MEASURED ANGLE OPENING LEFT*/
+ 0, /* U+00299c: RIGHT ANGLE VARIANT WITH SQUARE*/
+ 0, /* U+00299d: MEASURED RIGHT ANGLE WITH DOT*/
+ 0, /* U+00299e: ANGLE WITH S INSIDE*/
+ 0, /* U+00299f: ACUTE ANGLE*/
+ 0, /* U+0029a0: SPHERICAL ANGLE OPENING LEFT*/
+ 0, /* U+0029a1: SPHERICAL ANGLE OPENING UP*/
+ 0, /* U+0029a2: TURNED ANGLE*/
+ 0, /* U+0029a3: REVERSED ANGLE*/
+ 0, /* U+0029a4: ANGLE WITH UNDERBAR*/
+ 0, /* U+0029a5: REVERSED ANGLE WITH UNDERBAR*/
+ 0, /* U+0029a6: OBLIQUE ANGLE OPENING UP*/
+ 0, /* U+0029a7: OBLIQUE ANGLE OPENING DOWN*/
+ 0, /* U+0029a8: MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT*/
+ 0, /* U+0029a9: MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT*/
+ 0, /* U+0029aa: MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT*/
+ 0, /* U+0029ab: MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT*/
+ 0, /* U+0029ac: MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP*/
+ 0, /* U+0029ad: MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP*/
+ 0, /* U+0029ae: MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN*/
+ 0, /* U+0029af: MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN*/
+ 0, /* U+0029b0: REVERSED EMPTY SET*/
+ 0, /* U+0029b1: EMPTY SET WITH OVERBAR*/
+ 0, /* U+0029b2: EMPTY SET WITH SMALL CIRCLE ABOVE*/
+ 0, /* U+0029b3: EMPTY SET WITH RIGHT ARROW ABOVE*/
+ 0, /* U+0029b4: EMPTY SET WITH LEFT ARROW ABOVE*/
+ 0, /* U+0029b5: CIRCLE WITH HORIZONTAL BAR*/
+ 0, /* U+0029b6: CIRCLED VERTICAL BAR*/
+ 0, /* U+0029b7: CIRCLED PARALLEL*/
+ 0, /* U+0029b8: CIRCLED REVERSE SOLIDUS*/
+ 0, /* U+0029b9: CIRCLED PERPENDICULAR*/
+ 0, /* U+0029ba: CIRCLE DIVIDED BY HORIZONTAL BAR AND TOP HALF DIVIDED BY VERTICAL BAR*/
+ 0, /* U+0029bb: CIRCLE WITH SUPERIMPOSED X*/
+ 0, /* U+0029bc: CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN*/
+ 0, /* U+0029bd: UP ARROW THROUGH CIRCLE*/
+ 0, /* U+0029be: CIRCLED WHITE BULLET*/
+ 0, /* U+0029bf: CIRCLED BULLET*/
+ 0, /* U+0029c0: CIRCLED LESS-THAN*/
+ 0, /* U+0029c1: CIRCLED GREATER-THAN*/
+ 0, /* U+0029c2: CIRCLE WITH SMALL CIRCLE TO THE RIGHT*/
+ 0, /* U+0029c3: CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT*/
+ 0, /* U+0029c4: SQUARED RISING DIAGONAL SLASH*/
+ 0, /* U+0029c5: SQUARED FALLING DIAGONAL SLASH*/
+ 0, /* U+0029c6: SQUARED ASTERISK*/
+ 0, /* U+0029c7: SQUARED SMALL CIRCLE*/
+ 0, /* U+0029c8: SQUARED SQUARE*/
+ 0, /* U+0029c9: TWO JOINED SQUARES*/
+ 0, /* U+0029ca: TRIANGLE WITH DOT ABOVE*/
+ 0, /* U+0029cb: TRIANGLE WITH UNDERBAR*/
+ 0, /* U+0029cc: S IN TRIANGLE*/
+ 0, /* U+0029cd: TRIANGLE WITH SERIFS AT BOTTOM*/
+ 0, /* U+0029ce: RIGHT TRIANGLE ABOVE LEFT TRIANGLE*/
+ 0, /* U+0029cf: LEFT TRIANGLE BESIDE VERTICAL BAR*/
+ 0, /* U+0029d0: VERTICAL BAR BESIDE RIGHT TRIANGLE*/
+ 0, /* U+0029d1: BOWTIE WITH LEFT HALF BLACK*/
+ 0, /* U+0029d2: BOWTIE WITH RIGHT HALF BLACK*/
+ 0, /* U+0029d3: BLACK BOWTIE*/
+ 0, /* U+0029d4: TIMES WITH LEFT HALF BLACK*/
+ 0, /* U+0029d5: TIMES WITH RIGHT HALF BLACK*/
+ 0, /* U+0029d6: WHITE HOURGLASS*/
+ 0, /* U+0029d7: BLACK HOURGLASS*/
+ 0, /* U+0029d8: LEFT WIGGLY FENCE*/
+ 0, /* U+0029d9: RIGHT WIGGLY FENCE*/
+ 0, /* U+0029da: LEFT DOUBLE WIGGLY FENCE*/
+ 0, /* U+0029db: RIGHT DOUBLE WIGGLY FENCE*/
+ 0, /* U+0029dc: INCOMPLETE INFINITY*/
+ 0, /* U+0029dd: TIE OVER INFINITY*/
+ 0, /* U+0029de: INFINITY NEGATED WITH VERTICAL BAR*/
+ 0, /* U+0029df: DOUBLE-ENDED MULTIMAP*/
+ 0, /* U+0029e0: SQUARE WITH CONTOURED OUTLINE*/
+ 0, /* U+0029e1: INCREASES AS*/
+ 0, /* U+0029e2: SHUFFLE PRODUCT*/
+ 0, /* U+0029e3: EQUALS SIGN AND SLANTED PARALLEL*/
+ 0, /* U+0029e4: EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE*/
+ 0, /* U+0029e5: IDENTICAL TO AND SLANTED PARALLEL*/
+ 0, /* U+0029e6: GLEICH STARK*/
+ 0, /* U+0029e7: THERMODYNAMIC*/
+ 0, /* U+0029e8: DOWN-POINTING TRIANGLE WITH LEFT HALF BLACK*/
+ 0, /* U+0029e9: DOWN-POINTING TRIANGLE WITH RIGHT HALF BLACK*/
+ 0, /* U+0029ea: BLACK DIAMOND WITH DOWN ARROW*/
+ 0, /* U+0029eb: BLACK LOZENGE*/
+ 0, /* U+0029ec: WHITE CIRCLE WITH DOWN ARROW*/
+ 0, /* U+0029ed: BLACK CIRCLE WITH DOWN ARROW*/
+ 0, /* U+0029ee: ERROR-BARRED WHITE SQUARE*/
+ 0, /* U+0029ef: ERROR-BARRED BLACK SQUARE*/
+ 0, /* U+0029f0: ERROR-BARRED WHITE DIAMOND*/
+ 0, /* U+0029f1: ERROR-BARRED BLACK DIAMOND*/
+ 0, /* U+0029f2: ERROR-BARRED WHITE CIRCLE*/
+ 0, /* U+0029f3: ERROR-BARRED BLACK CIRCLE*/
+ 0, /* U+0029f4: RULE-DELAYED*/
+ 0, /* U+0029f5: REVERSE SOLIDUS OPERATOR*/
+ 0, /* U+0029f6: SOLIDUS WITH OVERBAR*/
+ 0, /* U+0029f7: REVERSE SOLIDUS WITH HORIZONTAL STROKE*/
+ 0, /* U+0029f8: BIG SOLIDUS*/
+ 0, /* U+0029f9: BIG REVERSE SOLIDUS*/
+ 0, /* U+0029fa: DOUBLE PLUS*/
+ 0, /* U+0029fb: TRIPLE PLUS*/
+ 0, /* U+0029fc: LEFT-POINTING CURVED ANGLE BRACKET*/
+ 0, /* U+0029fd: RIGHT-POINTING CURVED ANGLE BRACKET*/
+ 0, /* U+0029fe: TINY*/
+ 0, /* U+0029ff: MINY*/
+ 0, /* U+002a00: N-ARY CIRCLED DOT OPERATOR*/
+ 0, /* U+002a01: N-ARY CIRCLED PLUS OPERATOR*/
+ 0, /* U+002a02: N-ARY CIRCLED TIMES OPERATOR*/
+ 0, /* U+002a03: N-ARY UNION OPERATOR WITH DOT*/
+ 0, /* U+002a04: N-ARY UNION OPERATOR WITH PLUS*/
+ 0, /* U+002a05: N-ARY SQUARE INTERSECTION OPERATOR*/
+ 0, /* U+002a06: N-ARY SQUARE UNION OPERATOR*/
+ 0, /* U+002a07: TWO LOGICAL AND OPERATOR*/
+ 0, /* U+002a08: TWO LOGICAL OR OPERATOR*/
+ 0, /* U+002a09: N-ARY TIMES OPERATOR*/
+ 0, /* U+002a0a: MODULO TWO SUM*/
+ 0, /* U+002a0b: SUMMATION WITH INTEGRAL*/
+ 0, /* U+002a0c: QUADRUPLE INTEGRAL OPERATOR*/
+ 0, /* U+002a0d: FINITE PART INTEGRAL*/
+ 0, /* U+002a0e: INTEGRAL WITH DOUBLE STROKE*/
+ 0, /* U+002a0f: INTEGRAL AVERAGE WITH SLASH*/
+ 0, /* U+002a10: CIRCULATION FUNCTION*/
+ 0, /* U+002a11: ANTICLOCKWISE INTEGRATION*/
+ 0, /* U+002a12: LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE*/
+ 0, /* U+002a13: LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE*/
+ 0, /* U+002a14: LINE INTEGRATION NOT INCLUDING THE POLE*/
+ 0, /* U+002a15: INTEGRAL AROUND A POINT OPERATOR*/
+ 0, /* U+002a16: QUATERNION INTEGRAL OPERATOR*/
+ 0, /* U+002a17: INTEGRAL WITH LEFTWARDS ARROW WITH HOOK*/
+ 0, /* U+002a18: INTEGRAL WITH TIMES SIGN*/
+ 0, /* U+002a19: INTEGRAL WITH INTERSECTION*/
+ 0, /* U+002a1a: INTEGRAL WITH UNION*/
+ 0, /* U+002a1b: INTEGRAL WITH OVERBAR*/
+ 0, /* U+002a1c: INTEGRAL WITH UNDERBAR*/
+ 0, /* U+002a1d: JOIN*/
+ 0, /* U+002a1e: LARGE LEFT TRIANGLE OPERATOR*/
+ 0, /* U+002a1f: Z NOTATION SCHEMA COMPOSITION*/
+ 0, /* U+002a20: Z NOTATION SCHEMA PIPING*/
+ 0, /* U+002a21: Z NOTATION SCHEMA PROJECTION*/
+ 0, /* U+002a22: PLUS SIGN WITH SMALL CIRCLE ABOVE*/
+ 0, /* U+002a23: PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE*/
+ 0, /* U+002a24: PLUS SIGN WITH TILDE ABOVE*/
+ 0, /* U+002a25: PLUS SIGN WITH DOT BELOW*/
+ 0, /* U+002a26: PLUS SIGN WITH TILDE BELOW*/
+ 0, /* U+002a27: PLUS SIGN WITH SUBSCRIPT TWO*/
+ 0, /* U+002a28: PLUS SIGN WITH BLACK TRIANGLE*/
+ 0, /* U+002a29: MINUS SIGN WITH COMMA ABOVE*/
+ 0, /* U+002a2a: MINUS SIGN WITH DOT BELOW*/
+ 0, /* U+002a2b: MINUS SIGN WITH FALLING DOTS*/
+ 0, /* U+002a2c: MINUS SIGN WITH RISING DOTS*/
+ 0, /* U+002a2d: PLUS SIGN IN LEFT HALF CIRCLE*/
+ 0, /* U+002a2e: PLUS SIGN IN RIGHT HALF CIRCLE*/
+ 0, /* U+002a2f: VECTOR OR CROSS PRODUCT*/
+ 0, /* U+002a30: MULTIPLICATION SIGN WITH DOT ABOVE*/
+ 0, /* U+002a31: MULTIPLICATION SIGN WITH UNDERBAR*/
+ 0, /* U+002a32: SEMIDIRECT PRODUCT WITH BOTTOM CLOSED*/
+ 0, /* U+002a33: SMASH PRODUCT*/
+ 0, /* U+002a34: MULTIPLICATION SIGN IN LEFT HALF CIRCLE*/
+ 0, /* U+002a35: MULTIPLICATION SIGN IN RIGHT HALF CIRCLE*/
+ 0, /* U+002a36: CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT*/
+ 0, /* U+002a37: MULTIPLICATION SIGN IN DOUBLE CIRCLE*/
+ 0, /* U+002a38: CIRCLED DIVISION SIGN*/
+ 0, /* U+002a39: PLUS SIGN IN TRIANGLE*/
+ 0, /* U+002a3a: MINUS SIGN IN TRIANGLE*/
+ 0, /* U+002a3b: MULTIPLICATION SIGN IN TRIANGLE*/
+ 0, /* U+002a3c: INTERIOR PRODUCT*/
+ 0, /* U+002a3d: RIGHTHAND INTERIOR PRODUCT*/
+ 0, /* U+002a3e: Z NOTATION RELATIONAL COMPOSITION*/
+ 0, /* U+002a3f: AMALGAMATION OR COPRODUCT*/
+ 0, /* U+002a40: INTERSECTION WITH DOT*/
+ 0, /* U+002a41: UNION WITH MINUS SIGN*/
+ 0, /* U+002a42: UNION WITH OVERBAR*/
+ 0, /* U+002a43: INTERSECTION WITH OVERBAR*/
+ 0, /* U+002a44: INTERSECTION WITH LOGICAL AND*/
+ 0, /* U+002a45: UNION WITH LOGICAL OR*/
+ 0, /* U+002a46: UNION ABOVE INTERSECTION*/
+ 0, /* U+002a47: INTERSECTION ABOVE UNION*/
+ 0, /* U+002a48: UNION ABOVE BAR ABOVE INTERSECTION*/
+ 0, /* U+002a49: INTERSECTION ABOVE BAR ABOVE UNION*/
+ 0, /* U+002a4a: UNION BESIDE AND JOINED WITH UNION*/
+ 0, /* U+002a4b: INTERSECTION BESIDE AND JOINED WITH INTERSECTION*/
+ 0, /* U+002a4c: CLOSED UNION WITH SERIFS*/
+ 0, /* U+002a4d: CLOSED INTERSECTION WITH SERIFS*/
+ 0, /* U+002a4e: DOUBLE SQUARE INTERSECTION*/
+ 0, /* U+002a4f: DOUBLE SQUARE UNION*/
+ 0, /* U+002a50: CLOSED UNION WITH SERIFS AND SMASH PRODUCT*/
+ 0, /* U+002a51: LOGICAL AND WITH DOT ABOVE*/
+ 0, /* U+002a52: LOGICAL OR WITH DOT ABOVE*/
+ 0, /* U+002a53: DOUBLE LOGICAL AND*/
+ 0, /* U+002a54: DOUBLE LOGICAL OR*/
+ 0, /* U+002a55: TWO INTERSECTING LOGICAL AND*/
+ 0, /* U+002a56: TWO INTERSECTING LOGICAL OR*/
+ 0, /* U+002a57: SLOPING LARGE OR*/
+ 0, /* U+002a58: SLOPING LARGE AND*/
+ 0, /* U+002a59: LOGICAL OR OVERLAPPING LOGICAL AND*/
+ 0, /* U+002a5a: LOGICAL AND WITH MIDDLE STEM*/
+ 0, /* U+002a5b: LOGICAL OR WITH MIDDLE STEM*/
+ 0, /* U+002a5c: LOGICAL AND WITH HORIZONTAL DASH*/
+ 0, /* U+002a5d: LOGICAL OR WITH HORIZONTAL DASH*/
+ 0, /* U+002a5e: LOGICAL AND WITH DOUBLE OVERBAR*/
+ 0, /* U+002a5f: LOGICAL AND WITH UNDERBAR*/
+ 0, /* U+002a60: LOGICAL AND WITH DOUBLE UNDERBAR*/
+ 0, /* U+002a61: SMALL VEE WITH UNDERBAR*/
+ 0, /* U+002a62: LOGICAL OR WITH DOUBLE OVERBAR*/
+ 0, /* U+002a63: LOGICAL OR WITH DOUBLE UNDERBAR*/
+ 0, /* U+002a64: Z NOTATION DOMAIN ANTIRESTRICTION*/
+ 0, /* U+002a65: Z NOTATION RANGE ANTIRESTRICTION*/
+ 0, /* U+002a66: EQUALS SIGN WITH DOT BELOW*/
+ 0, /* U+002a67: IDENTICAL WITH DOT ABOVE*/
+ 0, /* U+002a68: TRIPLE HORIZONTAL BAR WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+002a69: TRIPLE HORIZONTAL BAR WITH TRIPLE VERTICAL STROKE*/
+ 0, /* U+002a6a: TILDE OPERATOR WITH DOT ABOVE*/
+ 0, /* U+002a6b: TILDE OPERATOR WITH RISING DOTS*/
+ 0, /* U+002a6c: SIMILAR MINUS SIMILAR*/
+ 0, /* U+002a6d: CONGRUENT WITH DOT ABOVE*/
+ 0, /* U+002a6e: EQUALS WITH ASTERISK*/
+ 0, /* U+002a6f: ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT*/
+ 0, /* U+002a70: APPROXIMATELY EQUAL OR EQUAL TO*/
+ 0, /* U+002a71: EQUALS SIGN ABOVE PLUS SIGN*/
+ 0, /* U+002a72: PLUS SIGN ABOVE EQUALS SIGN*/
+ 0, /* U+002a73: EQUALS SIGN ABOVE TILDE OPERATOR*/
+ 0, /* U+002a74: DOUBLE COLON EQUAL*/
+ 0, /* U+002a75: TWO CONSECUTIVE EQUALS SIGNS*/
+ 0, /* U+002a76: THREE CONSECUTIVE EQUALS SIGNS*/
+ 0, /* U+002a77: EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW*/
+ 0, /* U+002a78: EQUIVALENT WITH FOUR DOTS ABOVE*/
+ 0, /* U+002a79: LESS-THAN WITH CIRCLE INSIDE*/
+ 0, /* U+002a7a: GREATER-THAN WITH CIRCLE INSIDE*/
+ 0, /* U+002a7b: LESS-THAN WITH QUESTION MARK ABOVE*/
+ 0, /* U+002a7c: GREATER-THAN WITH QUESTION MARK ABOVE*/
+ 0, /* U+002a7d: LESS-THAN OR SLANTED EQUAL TO*/
+ 0, /* U+002a7e: GREATER-THAN OR SLANTED EQUAL TO*/
+ 0, /* U+002a7f: LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE*/
+ 0, /* U+002a80: GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE*/
+ 0, /* U+002a81: LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE*/
+ 0, /* U+002a82: GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE*/
+ 0, /* U+002a83: LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT*/
+ 0, /* U+002a84: GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT*/
+ 0, /* U+002a85: LESS-THAN OR APPROXIMATE*/
+ 0, /* U+002a86: GREATER-THAN OR APPROXIMATE*/
+ 0, /* U+002a87: LESS-THAN AND SINGLE-LINE NOT EQUAL TO*/
+ 0, /* U+002a88: GREATER-THAN AND SINGLE-LINE NOT EQUAL TO*/
+ 0, /* U+002a89: LESS-THAN AND NOT APPROXIMATE*/
+ 0, /* U+002a8a: GREATER-THAN AND NOT APPROXIMATE*/
+ 0, /* U+002a8b: LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN*/
+ 0, /* U+002a8c: GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN*/
+ 0, /* U+002a8d: LESS-THAN ABOVE SIMILAR OR EQUAL*/
+ 0, /* U+002a8e: GREATER-THAN ABOVE SIMILAR OR EQUAL*/
+ 0, /* U+002a8f: LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN*/
+ 0, /* U+002a90: GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN*/
+ 0, /* U+002a91: LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL*/
+ 0, /* U+002a92: GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL*/
+ 0, /* U+002a93: LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL*/
+ 0, /* U+002a94: GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL*/
+ 0, /* U+002a95: SLANTED EQUAL TO OR LESS-THAN*/
+ 0, /* U+002a96: SLANTED EQUAL TO OR GREATER-THAN*/
+ 0, /* U+002a97: SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE*/
+ 0, /* U+002a98: SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE*/
+ 0, /* U+002a99: DOUBLE-LINE EQUAL TO OR LESS-THAN*/
+ 0, /* U+002a9a: DOUBLE-LINE EQUAL TO OR GREATER-THAN*/
+ 0, /* U+002a9b: DOUBLE-LINE SLANTED EQUAL TO OR LESS-THAN*/
+ 0, /* U+002a9c: DOUBLE-LINE SLANTED EQUAL TO OR GREATER-THAN*/
+ 0, /* U+002a9d: SIMILAR OR LESS-THAN*/
+ 0, /* U+002a9e: SIMILAR OR GREATER-THAN*/
+ 0, /* U+002a9f: SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN*/
+ 0, /* U+002aa0: SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN*/
+ 0, /* U+002aa1: DOUBLE NESTED LESS-THAN*/
+ 0, /* U+002aa2: DOUBLE NESTED GREATER-THAN*/
+ 0, /* U+002aa3: DOUBLE NESTED LESS-THAN WITH UNDERBAR*/
+ 0, /* U+002aa4: GREATER-THAN OVERLAPPING LESS-THAN*/
+ 0, /* U+002aa5: GREATER-THAN BESIDE LESS-THAN*/
+ 0, /* U+002aa6: LESS-THAN CLOSED BY CURVE*/
+ 0, /* U+002aa7: GREATER-THAN CLOSED BY CURVE*/
+ 0, /* U+002aa8: LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL*/
+ 0, /* U+002aa9: GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL*/
+ 0, /* U+002aaa: SMALLER THAN*/
+ 0, /* U+002aab: LARGER THAN*/
+ 0, /* U+002aac: SMALLER THAN OR EQUAL TO*/
+ 0, /* U+002aad: LARGER THAN OR EQUAL TO*/
+ 0, /* U+002aae: EQUALS SIGN WITH BUMPY ABOVE*/
+ 0, /* U+002aaf: PRECEDES ABOVE SINGLE-LINE EQUALS SIGN*/
+ 0, /* U+002ab0: SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN*/
+ 0, /* U+002ab1: PRECEDES ABOVE SINGLE-LINE NOT EQUAL TO*/
+ 0, /* U+002ab2: SUCCEEDS ABOVE SINGLE-LINE NOT EQUAL TO*/
+ 0, /* U+002ab3: PRECEDES ABOVE EQUALS SIGN*/
+ 0, /* U+002ab4: SUCCEEDS ABOVE EQUALS SIGN*/
+ 0, /* U+002ab5: PRECEDES ABOVE NOT EQUAL TO*/
+ 0, /* U+002ab6: SUCCEEDS ABOVE NOT EQUAL TO*/
+ 0, /* U+002ab7: PRECEDES ABOVE ALMOST EQUAL TO*/
+ 0, /* U+002ab8: SUCCEEDS ABOVE ALMOST EQUAL TO*/
+ 0, /* U+002ab9: PRECEDES ABOVE NOT ALMOST EQUAL TO*/
+ 0, /* U+002aba: SUCCEEDS ABOVE NOT ALMOST EQUAL TO*/
+ 0, /* U+002abb: DOUBLE PRECEDES*/
+ 0, /* U+002abc: DOUBLE SUCCEEDS*/
+ 0, /* U+002abd: SUBSET WITH DOT*/
+ 0, /* U+002abe: SUPERSET WITH DOT*/
+ 0, /* U+002abf: SUBSET WITH PLUS SIGN BELOW*/
+ 0, /* U+002ac0: SUPERSET WITH PLUS SIGN BELOW*/
+ 0, /* U+002ac1: SUBSET WITH MULTIPLICATION SIGN BELOW*/
+ 0, /* U+002ac2: SUPERSET WITH MULTIPLICATION SIGN BELOW*/
+ 0, /* U+002ac3: SUBSET OF OR EQUAL TO WITH DOT ABOVE*/
+ 0, /* U+002ac4: SUPERSET OF OR EQUAL TO WITH DOT ABOVE*/
+ 0, /* U+002ac5: SUBSET OF ABOVE EQUALS SIGN*/
+ 0, /* U+002ac6: SUPERSET OF ABOVE EQUALS SIGN*/
+ 0, /* U+002ac7: SUBSET OF ABOVE TILDE OPERATOR*/
+ 0, /* U+002ac8: SUPERSET OF ABOVE TILDE OPERATOR*/
+ 0, /* U+002ac9: SUBSET OF ABOVE ALMOST EQUAL TO*/
+ 0, /* U+002aca: SUPERSET OF ABOVE ALMOST EQUAL TO*/
+ 0, /* U+002acb: SUBSET OF ABOVE NOT EQUAL TO*/
+ 0, /* U+002acc: SUPERSET OF ABOVE NOT EQUAL TO*/
+ 0, /* U+002acd: SQUARE LEFT OPEN BOX OPERATOR*/
+ 0, /* U+002ace: SQUARE RIGHT OPEN BOX OPERATOR*/
+ 0, /* U+002acf: CLOSED SUBSET*/
+ 0, /* U+002ad0: CLOSED SUPERSET*/
+ 0, /* U+002ad1: CLOSED SUBSET OR EQUAL TO*/
+ 0, /* U+002ad2: CLOSED SUPERSET OR EQUAL TO*/
+ 0, /* U+002ad3: SUBSET ABOVE SUPERSET*/
+ 0, /* U+002ad4: SUPERSET ABOVE SUBSET*/
+ 0, /* U+002ad5: SUBSET ABOVE SUBSET*/
+ 0, /* U+002ad6: SUPERSET ABOVE SUPERSET*/
+ 0, /* U+002ad7: SUPERSET BESIDE SUBSET*/
+ 0, /* U+002ad8: SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET*/
+ 0, /* U+002ad9: ELEMENT OF OPENING DOWNWARDS*/
+ 0, /* U+002ada: PITCHFORK WITH TEE TOP*/
+ 0, /* U+002adb: TRANSVERSAL INTERSECTION*/
+ 0, /* U+002adc: FORKING*/
+ 0, /* U+002add: NONFORKING*/
+ 0, /* U+002ade: SHORT LEFT TACK*/
+ 0, /* U+002adf: SHORT DOWN TACK*/
+ 0, /* U+002ae0: SHORT UP TACK*/
+ 0, /* U+002ae1: PERPENDICULAR WITH S*/
+ 0, /* U+002ae2: VERTICAL BAR TRIPLE RIGHT TURNSTILE*/
+ 0, /* U+002ae3: DOUBLE VERTICAL BAR LEFT TURNSTILE*/
+ 0, /* U+002ae4: VERTICAL BAR DOUBLE LEFT TURNSTILE*/
+ 0, /* U+002ae5: DOUBLE VERTICAL BAR DOUBLE LEFT TURNSTILE*/
+ 0, /* U+002ae6: LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL*/
+ 0, /* U+002ae7: SHORT DOWN TACK WITH OVERBAR*/
+ 0, /* U+002ae8: SHORT UP TACK WITH UNDERBAR*/
+ 0, /* U+002ae9: SHORT UP TACK ABOVE SHORT DOWN TACK*/
+ 0, /* U+002aea: DOUBLE DOWN TACK*/
+ 0, /* U+002aeb: DOUBLE UP TACK*/
+ 0, /* U+002aec: DOUBLE STROKE NOT SIGN*/
+ 0, /* U+002aed: REVERSED DOUBLE STROKE NOT SIGN*/
+ 0, /* U+002aee: DOES NOT DIVIDE WITH REVERSED NEGATION SLASH*/
+ 0, /* U+002aef: VERTICAL LINE WITH CIRCLE ABOVE*/
+ 0, /* U+002af0: VERTICAL LINE WITH CIRCLE BELOW*/
+ 0, /* U+002af1: DOWN TACK WITH CIRCLE BELOW*/
+ 0, /* U+002af2: PARALLEL WITH HORIZONTAL STROKE*/
+ 0, /* U+002af3: PARALLEL WITH TILDE OPERATOR*/
+ 0, /* U+002af4: TRIPLE VERTICAL BAR BINARY RELATION*/
+ 0, /* U+002af5: TRIPLE VERTICAL BAR WITH HORIZONTAL STROKE*/
+ 0, /* U+002af6: TRIPLE COLON OPERATOR*/
+ 0, /* U+002af7: TRIPLE NESTED LESS-THAN*/
+ 0, /* U+002af8: TRIPLE NESTED GREATER-THAN*/
+ 0, /* U+002af9: DOUBLE-LINE SLANTED LESS-THAN OR EQUAL TO*/
+ 0, /* U+002afa: DOUBLE-LINE SLANTED GREATER-THAN OR EQUAL TO*/
+ 0, /* U+002afb: TRIPLE SOLIDUS BINARY RELATION*/
+ 0, /* U+002afc: LARGE TRIPLE VERTICAL BAR OPERATOR*/
+ 0, /* U+002afd: DOUBLE SOLIDUS OPERATOR*/
+ 0, /* U+002afe: WHITE VERTICAL BAR*/
+ 0, /* U+002aff: N-ARY WHITE VERTICAL BAR*/
+ 0, /* U+002b00: NORTH EAST WHITE ARROW*/
+ 0, /* U+002b01: NORTH WEST WHITE ARROW*/
+ 0, /* U+002b02: SOUTH EAST WHITE ARROW*/
+ 0, /* U+002b03: SOUTH WEST WHITE ARROW*/
+ 0, /* U+002b04: LEFT RIGHT WHITE ARROW*/
+ 0, /* U+002b05: LEFTWARDS BLACK ARROW*/
+ 0, /* U+002b06: UPWARDS BLACK ARROW*/
+ 0, /* U+002b07: DOWNWARDS BLACK ARROW*/
+ 0, /* U+002b08: NORTH EAST BLACK ARROW*/
+ 0, /* U+002b09: NORTH WEST BLACK ARROW*/
+ 0, /* U+002b0a: SOUTH EAST BLACK ARROW*/
+ 0, /* U+002b0b: SOUTH WEST BLACK ARROW*/
+ 0, /* U+002b0c: LEFT RIGHT BLACK ARROW*/
+ 0, /* U+002b0d: UP DOWN BLACK ARROW*/
+ 0, /* U+002b0e: RIGHTWARDS ARROW WITH TIP DOWNWARDS*/
+ 0, /* U+002b0f: RIGHTWARDS ARROW WITH TIP UPWARDS*/
+ 0, /* U+002b10: LEFTWARDS ARROW WITH TIP DOWNWARDS*/
+ 0, /* U+002b11: LEFTWARDS ARROW WITH TIP UPWARDS*/
+ 0, /* U+002b12: SQUARE WITH TOP HALF BLACK*/
+ 0, /* U+002b13: SQUARE WITH BOTTOM HALF BLACK*/
+ 0, /* U+002b14: SQUARE WITH UPPER RIGHT DIAGONAL HALF BLACK*/
+ 0, /* U+002b15: SQUARE WITH LOWER LEFT DIAGONAL HALF BLACK*/
+ 0, /* U+002b16: DIAMOND WITH LEFT HALF BLACK*/
+ 0, /* U+002b17: DIAMOND WITH RIGHT HALF BLACK*/
+ 0, /* U+002b18: DIAMOND WITH TOP HALF BLACK*/
+ 0, /* U+002b19: DIAMOND WITH BOTTOM HALF BLACK*/
+ 0, /* U+002b1a: DOTTED SQUARE*/
+ 0, /* U+002b1b: BLACK LARGE SQUARE*/
+ 0, /* U+002b1c: WHITE LARGE SQUARE*/
+ 0, /* U+002b1d: BLACK VERY SMALL SQUARE*/
+ 0, /* U+002b1e: WHITE VERY SMALL SQUARE*/
+ 0, /* U+002b1f: BLACK PENTAGON*/
+ 0, /* U+002b20: WHITE PENTAGON*/
+ 0, /* U+002b21: WHITE HEXAGON*/
+ 0, /* U+002b22: BLACK HEXAGON*/
+ 0, /* U+002b23: HORIZONTAL BLACK HEXAGON*/
+ 0, /* U+002b24: BLACK LARGE CIRCLE*/
+ 0, /* U+002b25: BLACK MEDIUM DIAMOND*/
+ 0, /* U+002b26: WHITE MEDIUM DIAMOND*/
+ 0, /* U+002b27: BLACK MEDIUM LOZENGE*/
+ 0, /* U+002b28: WHITE MEDIUM LOZENGE*/
+ 0, /* U+002b29: BLACK SMALL DIAMOND*/
+ 0, /* U+002b2a: BLACK SMALL LOZENGE*/
+ 0, /* U+002b2b: WHITE SMALL LOZENGE*/
+ 0, /* U+002b2c: BLACK HORIZONTAL ELLIPSE*/
+ 0, /* U+002b2d: WHITE HORIZONTAL ELLIPSE*/
+ 0, /* U+002b2e: BLACK VERTICAL ELLIPSE*/
+ 0, /* U+002b2f: WHITE VERTICAL ELLIPSE*/
+ 0, /* U+002b30: LEFT ARROW WITH SMALL CIRCLE*/
+ 0, /* U+002b31: THREE LEFTWARDS ARROWS*/
+ 0, /* U+002b32: LEFT ARROW WITH CIRCLED PLUS*/
+ 0, /* U+002b33: LONG LEFTWARDS SQUIGGLE ARROW*/
+ 0, /* U+002b34: LEFTWARDS TWO-HEADED ARROW WITH VERTICAL STROKE*/
+ 0, /* U+002b35: LEFTWARDS TWO-HEADED ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+002b36: LEFTWARDS TWO-HEADED ARROW FROM BAR*/
+ 0, /* U+002b37: LEFTWARDS TWO-HEADED TRIPLE DASH ARROW*/
+ 0, /* U+002b38: LEFTWARDS ARROW WITH DOTTED STEM*/
+ 0, /* U+002b39: LEFTWARDS ARROW WITH TAIL WITH VERTICAL STROKE*/
+ 0, /* U+002b3a: LEFTWARDS ARROW WITH TAIL WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+002b3b: LEFTWARDS TWO-HEADED ARROW WITH TAIL*/
+ 0, /* U+002b3c: LEFTWARDS TWO-HEADED ARROW WITH TAIL WITH VERTICAL STROKE*/
+ 0, /* U+002b3d: LEFTWARDS TWO-HEADED ARROW WITH TAIL WITH DOUBLE VERTICAL STROKE*/
+ 0, /* U+002b3e: LEFTWARDS ARROW THROUGH X*/
+ 0, /* U+002b3f: WAVE ARROW POINTING DIRECTLY LEFT*/
+ 0, /* U+002b40: EQUALS SIGN ABOVE LEFTWARDS ARROW*/
+ 0, /* U+002b41: REVERSE TILDE OPERATOR ABOVE LEFTWARDS ARROW*/
+ 0, /* U+002b42: LEFTWARDS ARROW ABOVE REVERSE ALMOST EQUAL TO*/
+ 0, /* U+002b43: RIGHTWARDS ARROW THROUGH GREATER-THAN*/
+ 0, /* U+002b44: RIGHTWARDS ARROW THROUGH SUPERSET*/
+ 0, /* U+002b45: LEFTWARDS QUADRUPLE ARROW*/
+ 0, /* U+002b46: RIGHTWARDS QUADRUPLE ARROW*/
+ 0, /* U+002b47: REVERSE TILDE OPERATOR ABOVE RIGHTWARDS ARROW*/
+ 0, /* U+002b48: RIGHTWARDS ARROW ABOVE REVERSE ALMOST EQUAL TO*/
+ 0, /* U+002b49: TILDE OPERATOR ABOVE LEFTWARDS ARROW*/
+ 0, /* U+002b4a: LEFTWARDS ARROW ABOVE ALMOST EQUAL TO*/
+ 0, /* U+002b4b: LEFTWARDS ARROW ABOVE REVERSE TILDE OPERATOR*/
+ 0, /* U+002b4c: RIGHTWARDS ARROW ABOVE REVERSE TILDE OPERATOR*/
+ 0, /* U+002b4d: */
+ 0, /* U+002b4e: */
+ 0, /* U+002b4f: */
+ 0, /* U+002b50: WHITE MEDIUM STAR*/
+ 0, /* U+002b51: BLACK SMALL STAR*/
+ 0, /* U+002b52: WHITE SMALL STAR*/
+ 0, /* U+002b53: BLACK RIGHT-POINTING PENTAGON*/
+ 0, /* U+002b54: WHITE RIGHT-POINTING PENTAGON*/
+ 0, /* U+002b55: HEAVY LARGE CIRCLE*/
+ 0, /* U+002b56: HEAVY OVAL WITH OVAL INSIDE*/
+ 0, /* U+002b57: HEAVY CIRCLE WITH CIRCLE INSIDE*/
+ 0, /* U+002b58: HEAVY CIRCLE*/
+ 0, /* U+002b59: HEAVY CIRCLED SALTIRE*/
+ 0, /* U+002b5a: */
+ 0, /* U+002b5b: */
+ 0, /* U+002b5c: */
+ 0, /* U+002b5d: */
+ 0, /* U+002b5e: */
+ 0, /* U+002b5f: */
+ 0, /* U+002b60: */
+ 0, /* U+002b61: */
+ 0, /* U+002b62: */
+ 0, /* U+002b63: */
+ 0, /* U+002b64: */
+ 0, /* U+002b65: */
+ 0, /* U+002b66: */
+ 0, /* U+002b67: */
+ 0, /* U+002b68: */
+ 0, /* U+002b69: */
+ 0, /* U+002b6a: */
+ 0, /* U+002b6b: */
+ 0, /* U+002b6c: */
+ 0, /* U+002b6d: */
+ 0, /* U+002b6e: */
+ 0, /* U+002b6f: */
+ 0, /* U+002b70: */
+ 0, /* U+002b71: */
+ 0, /* U+002b72: */
+ 0, /* U+002b73: */
+ 0, /* U+002b74: */
+ 0, /* U+002b75: */
+ 0, /* U+002b76: */
+ 0, /* U+002b77: */
+ 0, /* U+002b78: */
+ 0, /* U+002b79: */
+ 0, /* U+002b7a: */
+ 0, /* U+002b7b: */
+ 0, /* U+002b7c: */
+ 0, /* U+002b7d: */
+ 0, /* U+002b7e: */
+ 0, /* U+002b7f: */
+ 0, /* U+002b80: */
+ 0, /* U+002b81: */
+ 0, /* U+002b82: */
+ 0, /* U+002b83: */
+ 0, /* U+002b84: */
+ 0, /* U+002b85: */
+ 0, /* U+002b86: */
+ 0, /* U+002b87: */
+ 0, /* U+002b88: */
+ 0, /* U+002b89: */
+ 0, /* U+002b8a: */
+ 0, /* U+002b8b: */
+ 0, /* U+002b8c: */
+ 0, /* U+002b8d: */
+ 0, /* U+002b8e: */
+ 0, /* U+002b8f: */
+ 0, /* U+002b90: */
+ 0, /* U+002b91: */
+ 0, /* U+002b92: */
+ 0, /* U+002b93: */
+ 0, /* U+002b94: */
+ 0, /* U+002b95: */
+ 0, /* U+002b96: */
+ 0, /* U+002b97: */
+ 0, /* U+002b98: */
+ 0, /* U+002b99: */
+ 0, /* U+002b9a: */
+ 0, /* U+002b9b: */
+ 0, /* U+002b9c: */
+ 0, /* U+002b9d: */
+ 0, /* U+002b9e: */
+ 0, /* U+002b9f: */
+ 0, /* U+002ba0: */
+ 0, /* U+002ba1: */
+ 0, /* U+002ba2: */
+ 0, /* U+002ba3: */
+ 0, /* U+002ba4: */
+ 0, /* U+002ba5: */
+ 0, /* U+002ba6: */
+ 0, /* U+002ba7: */
+ 0, /* U+002ba8: */
+ 0, /* U+002ba9: */
+ 0, /* U+002baa: */
+ 0, /* U+002bab: */
+ 0, /* U+002bac: */
+ 0, /* U+002bad: */
+ 0, /* U+002bae: */
+ 0, /* U+002baf: */
+ 0, /* U+002bb0: */
+ 0, /* U+002bb1: */
+ 0, /* U+002bb2: */
+ 0, /* U+002bb3: */
+ 0, /* U+002bb4: */
+ 0, /* U+002bb5: */
+ 0, /* U+002bb6: */
+ 0, /* U+002bb7: */
+ 0, /* U+002bb8: */
+ 0, /* U+002bb9: */
+ 0, /* U+002bba: */
+ 0, /* U+002bbb: */
+ 0, /* U+002bbc: */
+ 0, /* U+002bbd: */
+ 0, /* U+002bbe: */
+ 0, /* U+002bbf: */
+ 0, /* U+002bc0: */
+ 0, /* U+002bc1: */
+ 0, /* U+002bc2: */
+ 0, /* U+002bc3: */
+ 0, /* U+002bc4: */
+ 0, /* U+002bc5: */
+ 0, /* U+002bc6: */
+ 0, /* U+002bc7: */
+ 0, /* U+002bc8: */
+ 0, /* U+002bc9: */
+ 0, /* U+002bca: */
+ 0, /* U+002bcb: */
+ 0, /* U+002bcc: */
+ 0, /* U+002bcd: */
+ 0, /* U+002bce: */
+ 0, /* U+002bcf: */
+ 0, /* U+002bd0: */
+ 0, /* U+002bd1: */
+ 0, /* U+002bd2: */
+ 0, /* U+002bd3: */
+ 0, /* U+002bd4: */
+ 0, /* U+002bd5: */
+ 0, /* U+002bd6: */
+ 0, /* U+002bd7: */
+ 0, /* U+002bd8: */
+ 0, /* U+002bd9: */
+ 0, /* U+002bda: */
+ 0, /* U+002bdb: */
+ 0, /* U+002bdc: */
+ 0, /* U+002bdd: */
+ 0, /* U+002bde: */
+ 0, /* U+002bdf: */
+ 0, /* U+002be0: */
+ 0, /* U+002be1: */
+ 0, /* U+002be2: */
+ 0, /* U+002be3: */
+ 0, /* U+002be4: */
+ 0, /* U+002be5: */
+ 0, /* U+002be6: */
+ 0, /* U+002be7: */
+ 0, /* U+002be8: */
+ 0, /* U+002be9: */
+ 0, /* U+002bea: */
+ 0, /* U+002beb: */
+ 0, /* U+002bec: */
+ 0, /* U+002bed: */
+ 0, /* U+002bee: */
+ 0, /* U+002bef: */
+ 0, /* U+002bf0: */
+ 0, /* U+002bf1: */
+ 0, /* U+002bf2: */
+ 0, /* U+002bf3: */
+ 0, /* U+002bf4: */
+ 0, /* U+002bf5: */
+ 0, /* U+002bf6: */
+ 0, /* U+002bf7: */
+ 0, /* U+002bf8: */
+ 0, /* U+002bf9: */
+ 0, /* U+002bfa: */
+ 0, /* U+002bfb: */
+ 0, /* U+002bfc: */
+ 0, /* U+002bfd: */
+ 0, /* U+002bfe: */
+ 0, /* U+002bff: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c00: GLAGOLITIC CAPITAL LETTER AZU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c01: GLAGOLITIC CAPITAL LETTER BUKY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c02: GLAGOLITIC CAPITAL LETTER VEDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c03: GLAGOLITIC CAPITAL LETTER GLAGOLI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c04: GLAGOLITIC CAPITAL LETTER DOBRO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c05: GLAGOLITIC CAPITAL LETTER YESTU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c06: GLAGOLITIC CAPITAL LETTER ZHIVETE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c07: GLAGOLITIC CAPITAL LETTER DZELO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c08: GLAGOLITIC CAPITAL LETTER ZEMLJA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c09: GLAGOLITIC CAPITAL LETTER IZHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c0a: GLAGOLITIC CAPITAL LETTER INITIAL IZHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c0b: GLAGOLITIC CAPITAL LETTER I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c0c: GLAGOLITIC CAPITAL LETTER DJERVI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c0d: GLAGOLITIC CAPITAL LETTER KAKO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c0e: GLAGOLITIC CAPITAL LETTER LJUDIJE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c0f: GLAGOLITIC CAPITAL LETTER MYSLITE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c10: GLAGOLITIC CAPITAL LETTER NASHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c11: GLAGOLITIC CAPITAL LETTER ONU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c12: GLAGOLITIC CAPITAL LETTER POKOJI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c13: GLAGOLITIC CAPITAL LETTER RITSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c14: GLAGOLITIC CAPITAL LETTER SLOVO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c15: GLAGOLITIC CAPITAL LETTER TVRIDO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c16: GLAGOLITIC CAPITAL LETTER UKU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c17: GLAGOLITIC CAPITAL LETTER FRITU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c18: GLAGOLITIC CAPITAL LETTER HERU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c19: GLAGOLITIC CAPITAL LETTER OTU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c1a: GLAGOLITIC CAPITAL LETTER PE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c1b: GLAGOLITIC CAPITAL LETTER SHTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c1c: GLAGOLITIC CAPITAL LETTER TSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c1d: GLAGOLITIC CAPITAL LETTER CHRIVI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c1e: GLAGOLITIC CAPITAL LETTER SHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c1f: GLAGOLITIC CAPITAL LETTER YERU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c20: GLAGOLITIC CAPITAL LETTER YERI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c21: GLAGOLITIC CAPITAL LETTER YATI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c22: GLAGOLITIC CAPITAL LETTER SPIDERY HA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c23: GLAGOLITIC CAPITAL LETTER YU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c24: GLAGOLITIC CAPITAL LETTER SMALL YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c25: GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c26: GLAGOLITIC CAPITAL LETTER YO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c27: GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c28: GLAGOLITIC CAPITAL LETTER BIG YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c29: GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c2a: GLAGOLITIC CAPITAL LETTER FITA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c2b: GLAGOLITIC CAPITAL LETTER IZHITSA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c2c: GLAGOLITIC CAPITAL LETTER SHTAPIC*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c2d: GLAGOLITIC CAPITAL LETTER TROKUTASTI A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c2e: GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE*/
+ 0, /* U+002c2f: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c30: GLAGOLITIC SMALL LETTER AZU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c31: GLAGOLITIC SMALL LETTER BUKY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c32: GLAGOLITIC SMALL LETTER VEDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c33: GLAGOLITIC SMALL LETTER GLAGOLI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c34: GLAGOLITIC SMALL LETTER DOBRO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c35: GLAGOLITIC SMALL LETTER YESTU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c36: GLAGOLITIC SMALL LETTER ZHIVETE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c37: GLAGOLITIC SMALL LETTER DZELO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c38: GLAGOLITIC SMALL LETTER ZEMLJA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c39: GLAGOLITIC SMALL LETTER IZHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c3a: GLAGOLITIC SMALL LETTER INITIAL IZHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c3b: GLAGOLITIC SMALL LETTER I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c3c: GLAGOLITIC SMALL LETTER DJERVI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c3d: GLAGOLITIC SMALL LETTER KAKO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c3e: GLAGOLITIC SMALL LETTER LJUDIJE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c3f: GLAGOLITIC SMALL LETTER MYSLITE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c40: GLAGOLITIC SMALL LETTER NASHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c41: GLAGOLITIC SMALL LETTER ONU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c42: GLAGOLITIC SMALL LETTER POKOJI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c43: GLAGOLITIC SMALL LETTER RITSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c44: GLAGOLITIC SMALL LETTER SLOVO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c45: GLAGOLITIC SMALL LETTER TVRIDO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c46: GLAGOLITIC SMALL LETTER UKU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c47: GLAGOLITIC SMALL LETTER FRITU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c48: GLAGOLITIC SMALL LETTER HERU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c49: GLAGOLITIC SMALL LETTER OTU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c4a: GLAGOLITIC SMALL LETTER PE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c4b: GLAGOLITIC SMALL LETTER SHTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c4c: GLAGOLITIC SMALL LETTER TSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c4d: GLAGOLITIC SMALL LETTER CHRIVI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c4e: GLAGOLITIC SMALL LETTER SHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c4f: GLAGOLITIC SMALL LETTER YERU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c50: GLAGOLITIC SMALL LETTER YERI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c51: GLAGOLITIC SMALL LETTER YATI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c52: GLAGOLITIC SMALL LETTER SPIDERY HA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c53: GLAGOLITIC SMALL LETTER YU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c54: GLAGOLITIC SMALL LETTER SMALL YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c55: GLAGOLITIC SMALL LETTER SMALL YUS WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c56: GLAGOLITIC SMALL LETTER YO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c57: GLAGOLITIC SMALL LETTER IOTATED SMALL YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c58: GLAGOLITIC SMALL LETTER BIG YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c59: GLAGOLITIC SMALL LETTER IOTATED BIG YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c5a: GLAGOLITIC SMALL LETTER FITA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c5b: GLAGOLITIC SMALL LETTER IZHITSA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c5c: GLAGOLITIC SMALL LETTER SHTAPIC*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c5d: GLAGOLITIC SMALL LETTER TROKUTASTI A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c5e: GLAGOLITIC SMALL LETTER LATINATE MYSLITE*/
+ 0, /* U+002c5f: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c60: LATIN CAPITAL LETTER L WITH DOUBLE BAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c61: LATIN SMALL LETTER L WITH DOUBLE BAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c62: LATIN CAPITAL LETTER L WITH MIDDLE TILDE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c63: LATIN CAPITAL LETTER P WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c64: LATIN CAPITAL LETTER R WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c65: LATIN SMALL LETTER A WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c66: LATIN SMALL LETTER T WITH DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c67: LATIN CAPITAL LETTER H WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c68: LATIN SMALL LETTER H WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c69: LATIN CAPITAL LETTER K WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c6a: LATIN SMALL LETTER K WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c6b: LATIN CAPITAL LETTER Z WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c6c: LATIN SMALL LETTER Z WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c6d: LATIN CAPITAL LETTER ALPHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c6e: LATIN CAPITAL LETTER M WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c6f: LATIN CAPITAL LETTER TURNED A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c70: LATIN CAPITAL LETTER TURNED ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c71: LATIN SMALL LETTER V WITH RIGHT HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c72: LATIN CAPITAL LETTER W WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c73: LATIN SMALL LETTER W WITH HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c74: LATIN SMALL LETTER V WITH CURL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c75: LATIN CAPITAL LETTER HALF H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c76: LATIN SMALL LETTER HALF H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c77: LATIN SMALL LETTER TAILLESS PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c78: LATIN SMALL LETTER E WITH NOTCH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c79: LATIN SMALL LETTER TURNED R WITH TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c7a: LATIN SMALL LETTER O WITH LOW RING INSIDE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c7b: LATIN LETTER SMALL CAPITAL TURNED E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c7c: LATIN SUBSCRIPT SMALL LETTER J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c7d: MODIFIER LETTER CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c7e: LATIN CAPITAL LETTER S WITH SWASH TAIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c7f: LATIN CAPITAL LETTER Z WITH SWASH TAIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c80: COPTIC CAPITAL LETTER ALFA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c81: COPTIC SMALL LETTER ALFA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c82: COPTIC CAPITAL LETTER VIDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c83: COPTIC SMALL LETTER VIDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c84: COPTIC CAPITAL LETTER GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c85: COPTIC SMALL LETTER GAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c86: COPTIC CAPITAL LETTER DALDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c87: COPTIC SMALL LETTER DALDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c88: COPTIC CAPITAL LETTER EIE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c89: COPTIC SMALL LETTER EIE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c8a: COPTIC CAPITAL LETTER SOU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c8b: COPTIC SMALL LETTER SOU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c8c: COPTIC CAPITAL LETTER ZATA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c8d: COPTIC SMALL LETTER ZATA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c8e: COPTIC CAPITAL LETTER HATE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c8f: COPTIC SMALL LETTER HATE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c90: COPTIC CAPITAL LETTER THETHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c91: COPTIC SMALL LETTER THETHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c92: COPTIC CAPITAL LETTER IAUDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c93: COPTIC SMALL LETTER IAUDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c94: COPTIC CAPITAL LETTER KAPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c95: COPTIC SMALL LETTER KAPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c96: COPTIC CAPITAL LETTER LAULA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c97: COPTIC SMALL LETTER LAULA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c98: COPTIC CAPITAL LETTER MI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c99: COPTIC SMALL LETTER MI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c9a: COPTIC CAPITAL LETTER NI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c9b: COPTIC SMALL LETTER NI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c9c: COPTIC CAPITAL LETTER KSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c9d: COPTIC SMALL LETTER KSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002c9e: COPTIC CAPITAL LETTER O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002c9f: COPTIC SMALL LETTER O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ca0: COPTIC CAPITAL LETTER PI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ca1: COPTIC SMALL LETTER PI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ca2: COPTIC CAPITAL LETTER RO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ca3: COPTIC SMALL LETTER RO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ca4: COPTIC CAPITAL LETTER SIMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ca5: COPTIC SMALL LETTER SIMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ca6: COPTIC CAPITAL LETTER TAU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ca7: COPTIC SMALL LETTER TAU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ca8: COPTIC CAPITAL LETTER UA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ca9: COPTIC SMALL LETTER UA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002caa: COPTIC CAPITAL LETTER FI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cab: COPTIC SMALL LETTER FI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cac: COPTIC CAPITAL LETTER KHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cad: COPTIC SMALL LETTER KHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cae: COPTIC CAPITAL LETTER PSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002caf: COPTIC SMALL LETTER PSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cb0: COPTIC CAPITAL LETTER OOU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cb1: COPTIC SMALL LETTER OOU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cb2: COPTIC CAPITAL LETTER DIALECT-P ALEF*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cb3: COPTIC SMALL LETTER DIALECT-P ALEF*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cb4: COPTIC CAPITAL LETTER OLD COPTIC AIN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cb5: COPTIC SMALL LETTER OLD COPTIC AIN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cb6: COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cb7: COPTIC SMALL LETTER CRYPTOGRAMMIC EIE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cb8: COPTIC CAPITAL LETTER DIALECT-P KAPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cb9: COPTIC SMALL LETTER DIALECT-P KAPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cba: COPTIC CAPITAL LETTER DIALECT-P NI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cbb: COPTIC SMALL LETTER DIALECT-P NI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cbc: COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cbd: COPTIC SMALL LETTER CRYPTOGRAMMIC NI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cbe: COPTIC CAPITAL LETTER OLD COPTIC OOU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cbf: COPTIC SMALL LETTER OLD COPTIC OOU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cc0: COPTIC CAPITAL LETTER SAMPI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cc1: COPTIC SMALL LETTER SAMPI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cc2: COPTIC CAPITAL LETTER CROSSED SHEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cc3: COPTIC SMALL LETTER CROSSED SHEI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cc4: COPTIC CAPITAL LETTER OLD COPTIC SHEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cc5: COPTIC SMALL LETTER OLD COPTIC SHEI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cc6: COPTIC CAPITAL LETTER OLD COPTIC ESH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cc7: COPTIC SMALL LETTER OLD COPTIC ESH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cc8: COPTIC CAPITAL LETTER AKHMIMIC KHEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cc9: COPTIC SMALL LETTER AKHMIMIC KHEI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cca: COPTIC CAPITAL LETTER DIALECT-P HORI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ccb: COPTIC SMALL LETTER DIALECT-P HORI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ccc: COPTIC CAPITAL LETTER OLD COPTIC HORI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ccd: COPTIC SMALL LETTER OLD COPTIC HORI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cce: COPTIC CAPITAL LETTER OLD COPTIC HA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ccf: COPTIC SMALL LETTER OLD COPTIC HA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cd0: COPTIC CAPITAL LETTER L-SHAPED HA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cd1: COPTIC SMALL LETTER L-SHAPED HA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cd2: COPTIC CAPITAL LETTER OLD COPTIC HEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cd3: COPTIC SMALL LETTER OLD COPTIC HEI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cd4: COPTIC CAPITAL LETTER OLD COPTIC HAT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cd5: COPTIC SMALL LETTER OLD COPTIC HAT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cd6: COPTIC CAPITAL LETTER OLD COPTIC GANGIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cd7: COPTIC SMALL LETTER OLD COPTIC GANGIA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cd8: COPTIC CAPITAL LETTER OLD COPTIC DJA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cd9: COPTIC SMALL LETTER OLD COPTIC DJA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cda: COPTIC CAPITAL LETTER OLD COPTIC SHIMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cdb: COPTIC SMALL LETTER OLD COPTIC SHIMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cdc: COPTIC CAPITAL LETTER OLD NUBIAN SHIMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cdd: COPTIC SMALL LETTER OLD NUBIAN SHIMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cde: COPTIC CAPITAL LETTER OLD NUBIAN NGI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cdf: COPTIC SMALL LETTER OLD NUBIAN NGI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ce0: COPTIC CAPITAL LETTER OLD NUBIAN NYI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ce1: COPTIC SMALL LETTER OLD NUBIAN NYI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ce2: COPTIC CAPITAL LETTER OLD NUBIAN WAU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ce3: COPTIC SMALL LETTER OLD NUBIAN WAU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002ce4: COPTIC SYMBOL KAI*/
+ 0, /* U+002ce5: COPTIC SYMBOL MI RO*/
+ 0, /* U+002ce6: COPTIC SYMBOL PI RO*/
+ 0, /* U+002ce7: COPTIC SYMBOL STAUROS*/
+ 0, /* U+002ce8: COPTIC SYMBOL TAU RO*/
+ 0, /* U+002ce9: COPTIC SYMBOL KHI RO*/
+ 0, /* U+002cea: COPTIC SYMBOL SHIMA SIMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ceb: COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cec: COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002ced: COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cee: COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA*/
+ 0, /* U+002cef: COPTIC COMBINING NI ABOVE*/
+ 0, /* U+002cf0: COPTIC COMBINING SPIRITUS ASPER*/
+ 0, /* U+002cf1: COPTIC COMBINING SPIRITUS LENIS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+002cf2: COPTIC CAPITAL LETTER BOHAIRIC KHEI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002cf3: COPTIC SMALL LETTER BOHAIRIC KHEI*/
+ 0, /* U+002cf4: */
+ 0, /* U+002cf5: */
+ 0, /* U+002cf6: */
+ 0, /* U+002cf7: */
+ 0, /* U+002cf8: */
+ 0, /* U+002cf9: COPTIC OLD NUBIAN FULL STOP*/
+ 0, /* U+002cfa: COPTIC OLD NUBIAN DIRECT QUESTION MARK*/
+ 0, /* U+002cfb: COPTIC OLD NUBIAN INDIRECT QUESTION MARK*/
+ 0, /* U+002cfc: COPTIC OLD NUBIAN VERSE DIVIDER*/
+ 0, /* U+002cfd: COPTIC FRACTION ONE HALF*/
+ 0, /* U+002cfe: COPTIC FULL STOP*/
+ 0, /* U+002cff: COPTIC MORPHOLOGICAL DIVIDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d00: GEORGIAN SMALL LETTER AN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d01: GEORGIAN SMALL LETTER BAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d02: GEORGIAN SMALL LETTER GAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d03: GEORGIAN SMALL LETTER DON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d04: GEORGIAN SMALL LETTER EN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d05: GEORGIAN SMALL LETTER VIN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d06: GEORGIAN SMALL LETTER ZEN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d07: GEORGIAN SMALL LETTER TAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d08: GEORGIAN SMALL LETTER IN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d09: GEORGIAN SMALL LETTER KAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d0a: GEORGIAN SMALL LETTER LAS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d0b: GEORGIAN SMALL LETTER MAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d0c: GEORGIAN SMALL LETTER NAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d0d: GEORGIAN SMALL LETTER ON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d0e: GEORGIAN SMALL LETTER PAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d0f: GEORGIAN SMALL LETTER ZHAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d10: GEORGIAN SMALL LETTER RAE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d11: GEORGIAN SMALL LETTER SAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d12: GEORGIAN SMALL LETTER TAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d13: GEORGIAN SMALL LETTER UN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d14: GEORGIAN SMALL LETTER PHAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d15: GEORGIAN SMALL LETTER KHAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d16: GEORGIAN SMALL LETTER GHAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d17: GEORGIAN SMALL LETTER QAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d18: GEORGIAN SMALL LETTER SHIN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d19: GEORGIAN SMALL LETTER CHIN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d1a: GEORGIAN SMALL LETTER CAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d1b: GEORGIAN SMALL LETTER JIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d1c: GEORGIAN SMALL LETTER CIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d1d: GEORGIAN SMALL LETTER CHAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d1e: GEORGIAN SMALL LETTER XAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d1f: GEORGIAN SMALL LETTER JHAN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d20: GEORGIAN SMALL LETTER HAE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d21: GEORGIAN SMALL LETTER HE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d22: GEORGIAN SMALL LETTER HIE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d23: GEORGIAN SMALL LETTER WE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d24: GEORGIAN SMALL LETTER HAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d25: GEORGIAN SMALL LETTER HOE*/
+ 0, /* U+002d26: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d27: GEORGIAN SMALL LETTER YN*/
+ 0, /* U+002d28: */
+ 0, /* U+002d29: */
+ 0, /* U+002d2a: */
+ 0, /* U+002d2b: */
+ 0, /* U+002d2c: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+002d2d: GEORGIAN SMALL LETTER AEN*/
+ 0, /* U+002d2e: */
+ 0, /* U+002d2f: */
+ RTUNI_ALPHA, /* U+002d30: TIFINAGH LETTER YA*/
+ RTUNI_ALPHA, /* U+002d31: TIFINAGH LETTER YAB*/
+ RTUNI_ALPHA, /* U+002d32: TIFINAGH LETTER YABH*/
+ RTUNI_ALPHA, /* U+002d33: TIFINAGH LETTER YAG*/
+ RTUNI_ALPHA, /* U+002d34: TIFINAGH LETTER YAGHH*/
+ RTUNI_ALPHA, /* U+002d35: TIFINAGH LETTER BERBER ACADEMY YAJ*/
+ RTUNI_ALPHA, /* U+002d36: TIFINAGH LETTER YAJ*/
+ RTUNI_ALPHA, /* U+002d37: TIFINAGH LETTER YAD*/
+ RTUNI_ALPHA, /* U+002d38: TIFINAGH LETTER YADH*/
+ RTUNI_ALPHA, /* U+002d39: TIFINAGH LETTER YADD*/
+ RTUNI_ALPHA, /* U+002d3a: TIFINAGH LETTER YADDH*/
+ RTUNI_ALPHA, /* U+002d3b: TIFINAGH LETTER YEY*/
+ RTUNI_ALPHA, /* U+002d3c: TIFINAGH LETTER YAF*/
+ RTUNI_ALPHA, /* U+002d3d: TIFINAGH LETTER YAK*/
+ RTUNI_ALPHA, /* U+002d3e: TIFINAGH LETTER TUAREG YAK*/
+ RTUNI_ALPHA, /* U+002d3f: TIFINAGH LETTER YAKHH*/
+ RTUNI_ALPHA, /* U+002d40: TIFINAGH LETTER YAH*/
+ RTUNI_ALPHA, /* U+002d41: TIFINAGH LETTER BERBER ACADEMY YAH*/
+ RTUNI_ALPHA, /* U+002d42: TIFINAGH LETTER TUAREG YAH*/
+ RTUNI_ALPHA, /* U+002d43: TIFINAGH LETTER YAHH*/
+ RTUNI_ALPHA, /* U+002d44: TIFINAGH LETTER YAA*/
+ RTUNI_ALPHA, /* U+002d45: TIFINAGH LETTER YAKH*/
+ RTUNI_ALPHA, /* U+002d46: TIFINAGH LETTER TUAREG YAKH*/
+ RTUNI_ALPHA, /* U+002d47: TIFINAGH LETTER YAQ*/
+ RTUNI_ALPHA, /* U+002d48: TIFINAGH LETTER TUAREG YAQ*/
+ RTUNI_ALPHA, /* U+002d49: TIFINAGH LETTER YI*/
+ RTUNI_ALPHA, /* U+002d4a: TIFINAGH LETTER YAZH*/
+ RTUNI_ALPHA, /* U+002d4b: TIFINAGH LETTER AHAGGAR YAZH*/
+ RTUNI_ALPHA, /* U+002d4c: TIFINAGH LETTER TUAREG YAZH*/
+ RTUNI_ALPHA, /* U+002d4d: TIFINAGH LETTER YAL*/
+ RTUNI_ALPHA, /* U+002d4e: TIFINAGH LETTER YAM*/
+ RTUNI_ALPHA, /* U+002d4f: TIFINAGH LETTER YAN*/
+ RTUNI_ALPHA, /* U+002d50: TIFINAGH LETTER TUAREG YAGN*/
+ RTUNI_ALPHA, /* U+002d51: TIFINAGH LETTER TUAREG YANG*/
+ RTUNI_ALPHA, /* U+002d52: TIFINAGH LETTER YAP*/
+ RTUNI_ALPHA, /* U+002d53: TIFINAGH LETTER YU*/
+ RTUNI_ALPHA, /* U+002d54: TIFINAGH LETTER YAR*/
+ RTUNI_ALPHA, /* U+002d55: TIFINAGH LETTER YARR*/
+ RTUNI_ALPHA, /* U+002d56: TIFINAGH LETTER YAGH*/
+ RTUNI_ALPHA, /* U+002d57: TIFINAGH LETTER TUAREG YAGH*/
+ RTUNI_ALPHA, /* U+002d58: TIFINAGH LETTER AYER YAGH*/
+ RTUNI_ALPHA, /* U+002d59: TIFINAGH LETTER YAS*/
+ RTUNI_ALPHA, /* U+002d5a: TIFINAGH LETTER YASS*/
+ RTUNI_ALPHA, /* U+002d5b: TIFINAGH LETTER YASH*/
+ RTUNI_ALPHA, /* U+002d5c: TIFINAGH LETTER YAT*/
+ RTUNI_ALPHA, /* U+002d5d: TIFINAGH LETTER YATH*/
+ RTUNI_ALPHA, /* U+002d5e: TIFINAGH LETTER YACH*/
+ RTUNI_ALPHA, /* U+002d5f: TIFINAGH LETTER YATT*/
+ RTUNI_ALPHA, /* U+002d60: TIFINAGH LETTER YAV*/
+ RTUNI_ALPHA, /* U+002d61: TIFINAGH LETTER YAW*/
+ RTUNI_ALPHA, /* U+002d62: TIFINAGH LETTER YAY*/
+ RTUNI_ALPHA, /* U+002d63: TIFINAGH LETTER YAZ*/
+ RTUNI_ALPHA, /* U+002d64: TIFINAGH LETTER TAWELLEMET YAZ*/
+ RTUNI_ALPHA, /* U+002d65: TIFINAGH LETTER YAZZ*/
+ RTUNI_ALPHA, /* U+002d66: TIFINAGH LETTER YE*/
+ RTUNI_ALPHA, /* U+002d67: TIFINAGH LETTER YO*/
+ 0, /* U+002d68: */
+ 0, /* U+002d69: */
+ 0, /* U+002d6a: */
+ 0, /* U+002d6b: */
+ 0, /* U+002d6c: */
+ 0, /* U+002d6d: */
+ 0, /* U+002d6e: */
+ RTUNI_ALPHA, /* U+002d6f: TIFINAGH MODIFIER LETTER LABIALIZATION MARK*/
+ 0, /* U+002d70: TIFINAGH SEPARATOR MARK*/
+ 0, /* U+002d71: */
+ 0, /* U+002d72: */
+ 0, /* U+002d73: */
+ 0, /* U+002d74: */
+ 0, /* U+002d75: */
+ 0, /* U+002d76: */
+ 0, /* U+002d77: */
+ 0, /* U+002d78: */
+ 0, /* U+002d79: */
+ 0, /* U+002d7a: */
+ 0, /* U+002d7b: */
+ 0, /* U+002d7c: */
+ 0, /* U+002d7d: */
+ 0, /* U+002d7e: */
+ 0, /* U+002d7f: TIFINAGH CONSONANT JOINER*/
+ RTUNI_ALPHA, /* U+002d80: ETHIOPIC SYLLABLE LOA*/
+ RTUNI_ALPHA, /* U+002d81: ETHIOPIC SYLLABLE MOA*/
+ RTUNI_ALPHA, /* U+002d82: ETHIOPIC SYLLABLE ROA*/
+ RTUNI_ALPHA, /* U+002d83: ETHIOPIC SYLLABLE SOA*/
+ RTUNI_ALPHA, /* U+002d84: ETHIOPIC SYLLABLE SHOA*/
+ RTUNI_ALPHA, /* U+002d85: ETHIOPIC SYLLABLE BOA*/
+ RTUNI_ALPHA, /* U+002d86: ETHIOPIC SYLLABLE TOA*/
+ RTUNI_ALPHA, /* U+002d87: ETHIOPIC SYLLABLE COA*/
+ RTUNI_ALPHA, /* U+002d88: ETHIOPIC SYLLABLE NOA*/
+ RTUNI_ALPHA, /* U+002d89: ETHIOPIC SYLLABLE NYOA*/
+ RTUNI_ALPHA, /* U+002d8a: ETHIOPIC SYLLABLE GLOTTAL OA*/
+ RTUNI_ALPHA, /* U+002d8b: ETHIOPIC SYLLABLE ZOA*/
+ RTUNI_ALPHA, /* U+002d8c: ETHIOPIC SYLLABLE DOA*/
+ RTUNI_ALPHA, /* U+002d8d: ETHIOPIC SYLLABLE DDOA*/
+ RTUNI_ALPHA, /* U+002d8e: ETHIOPIC SYLLABLE JOA*/
+ RTUNI_ALPHA, /* U+002d8f: ETHIOPIC SYLLABLE THOA*/
+ RTUNI_ALPHA, /* U+002d90: ETHIOPIC SYLLABLE CHOA*/
+ RTUNI_ALPHA, /* U+002d91: ETHIOPIC SYLLABLE PHOA*/
+ RTUNI_ALPHA, /* U+002d92: ETHIOPIC SYLLABLE POA*/
+ RTUNI_ALPHA, /* U+002d93: ETHIOPIC SYLLABLE GGWA*/
+ RTUNI_ALPHA, /* U+002d94: ETHIOPIC SYLLABLE GGWI*/
+ RTUNI_ALPHA, /* U+002d95: ETHIOPIC SYLLABLE GGWEE*/
+ RTUNI_ALPHA, /* U+002d96: ETHIOPIC SYLLABLE GGWE*/
+ 0, /* U+002d97: */
+ 0, /* U+002d98: */
+ 0, /* U+002d99: */
+ 0, /* U+002d9a: */
+ 0, /* U+002d9b: */
+ 0, /* U+002d9c: */
+ 0, /* U+002d9d: */
+ 0, /* U+002d9e: */
+ 0, /* U+002d9f: */
+ RTUNI_ALPHA, /* U+002da0: ETHIOPIC SYLLABLE SSA*/
+ RTUNI_ALPHA, /* U+002da1: ETHIOPIC SYLLABLE SSU*/
+ RTUNI_ALPHA, /* U+002da2: ETHIOPIC SYLLABLE SSI*/
+ RTUNI_ALPHA, /* U+002da3: ETHIOPIC SYLLABLE SSAA*/
+ RTUNI_ALPHA, /* U+002da4: ETHIOPIC SYLLABLE SSEE*/
+ RTUNI_ALPHA, /* U+002da5: ETHIOPIC SYLLABLE SSE*/
+ RTUNI_ALPHA, /* U+002da6: ETHIOPIC SYLLABLE SSO*/
+ 0, /* U+002da7: */
+ RTUNI_ALPHA, /* U+002da8: ETHIOPIC SYLLABLE CCA*/
+ RTUNI_ALPHA, /* U+002da9: ETHIOPIC SYLLABLE CCU*/
+ RTUNI_ALPHA, /* U+002daa: ETHIOPIC SYLLABLE CCI*/
+ RTUNI_ALPHA, /* U+002dab: ETHIOPIC SYLLABLE CCAA*/
+ RTUNI_ALPHA, /* U+002dac: ETHIOPIC SYLLABLE CCEE*/
+ RTUNI_ALPHA, /* U+002dad: ETHIOPIC SYLLABLE CCE*/
+ RTUNI_ALPHA, /* U+002dae: ETHIOPIC SYLLABLE CCO*/
+ 0, /* U+002daf: */
+ RTUNI_ALPHA, /* U+002db0: ETHIOPIC SYLLABLE ZZA*/
+ RTUNI_ALPHA, /* U+002db1: ETHIOPIC SYLLABLE ZZU*/
+ RTUNI_ALPHA, /* U+002db2: ETHIOPIC SYLLABLE ZZI*/
+ RTUNI_ALPHA, /* U+002db3: ETHIOPIC SYLLABLE ZZAA*/
+ RTUNI_ALPHA, /* U+002db4: ETHIOPIC SYLLABLE ZZEE*/
+ RTUNI_ALPHA, /* U+002db5: ETHIOPIC SYLLABLE ZZE*/
+ RTUNI_ALPHA, /* U+002db6: ETHIOPIC SYLLABLE ZZO*/
+ 0, /* U+002db7: */
+ RTUNI_ALPHA, /* U+002db8: ETHIOPIC SYLLABLE CCHA*/
+ RTUNI_ALPHA, /* U+002db9: ETHIOPIC SYLLABLE CCHU*/
+ RTUNI_ALPHA, /* U+002dba: ETHIOPIC SYLLABLE CCHI*/
+ RTUNI_ALPHA, /* U+002dbb: ETHIOPIC SYLLABLE CCHAA*/
+ RTUNI_ALPHA, /* U+002dbc: ETHIOPIC SYLLABLE CCHEE*/
+ RTUNI_ALPHA, /* U+002dbd: ETHIOPIC SYLLABLE CCHE*/
+ RTUNI_ALPHA, /* U+002dbe: ETHIOPIC SYLLABLE CCHO*/
+ 0, /* U+002dbf: */
+ RTUNI_ALPHA, /* U+002dc0: ETHIOPIC SYLLABLE QYA*/
+ RTUNI_ALPHA, /* U+002dc1: ETHIOPIC SYLLABLE QYU*/
+ RTUNI_ALPHA, /* U+002dc2: ETHIOPIC SYLLABLE QYI*/
+ RTUNI_ALPHA, /* U+002dc3: ETHIOPIC SYLLABLE QYAA*/
+ RTUNI_ALPHA, /* U+002dc4: ETHIOPIC SYLLABLE QYEE*/
+ RTUNI_ALPHA, /* U+002dc5: ETHIOPIC SYLLABLE QYE*/
+ RTUNI_ALPHA, /* U+002dc6: ETHIOPIC SYLLABLE QYO*/
+ 0, /* U+002dc7: */
+ RTUNI_ALPHA, /* U+002dc8: ETHIOPIC SYLLABLE KYA*/
+ RTUNI_ALPHA, /* U+002dc9: ETHIOPIC SYLLABLE KYU*/
+ RTUNI_ALPHA, /* U+002dca: ETHIOPIC SYLLABLE KYI*/
+ RTUNI_ALPHA, /* U+002dcb: ETHIOPIC SYLLABLE KYAA*/
+ RTUNI_ALPHA, /* U+002dcc: ETHIOPIC SYLLABLE KYEE*/
+ RTUNI_ALPHA, /* U+002dcd: ETHIOPIC SYLLABLE KYE*/
+ RTUNI_ALPHA, /* U+002dce: ETHIOPIC SYLLABLE KYO*/
+ 0, /* U+002dcf: */
+ RTUNI_ALPHA, /* U+002dd0: ETHIOPIC SYLLABLE XYA*/
+ RTUNI_ALPHA, /* U+002dd1: ETHIOPIC SYLLABLE XYU*/
+ RTUNI_ALPHA, /* U+002dd2: ETHIOPIC SYLLABLE XYI*/
+ RTUNI_ALPHA, /* U+002dd3: ETHIOPIC SYLLABLE XYAA*/
+ RTUNI_ALPHA, /* U+002dd4: ETHIOPIC SYLLABLE XYEE*/
+ RTUNI_ALPHA, /* U+002dd5: ETHIOPIC SYLLABLE XYE*/
+ RTUNI_ALPHA, /* U+002dd6: ETHIOPIC SYLLABLE XYO*/
+ 0, /* U+002dd7: */
+ RTUNI_ALPHA, /* U+002dd8: ETHIOPIC SYLLABLE GYA*/
+ RTUNI_ALPHA, /* U+002dd9: ETHIOPIC SYLLABLE GYU*/
+ RTUNI_ALPHA, /* U+002dda: ETHIOPIC SYLLABLE GYI*/
+ RTUNI_ALPHA, /* U+002ddb: ETHIOPIC SYLLABLE GYAA*/
+ RTUNI_ALPHA, /* U+002ddc: ETHIOPIC SYLLABLE GYEE*/
+ RTUNI_ALPHA, /* U+002ddd: ETHIOPIC SYLLABLE GYE*/
+ RTUNI_ALPHA, /* U+002dde: ETHIOPIC SYLLABLE GYO*/
+ 0, /* U+002ddf: */
+ RTUNI_ALPHA, /* U+002de0: COMBINING CYRILLIC LETTER BE*/
+ RTUNI_ALPHA, /* U+002de1: COMBINING CYRILLIC LETTER VE*/
+ RTUNI_ALPHA, /* U+002de2: COMBINING CYRILLIC LETTER GHE*/
+ RTUNI_ALPHA, /* U+002de3: COMBINING CYRILLIC LETTER DE*/
+ RTUNI_ALPHA, /* U+002de4: COMBINING CYRILLIC LETTER ZHE*/
+ RTUNI_ALPHA, /* U+002de5: COMBINING CYRILLIC LETTER ZE*/
+ RTUNI_ALPHA, /* U+002de6: COMBINING CYRILLIC LETTER KA*/
+ RTUNI_ALPHA, /* U+002de7: COMBINING CYRILLIC LETTER EL*/
+ RTUNI_ALPHA, /* U+002de8: COMBINING CYRILLIC LETTER EM*/
+ RTUNI_ALPHA, /* U+002de9: COMBINING CYRILLIC LETTER EN*/
+ RTUNI_ALPHA, /* U+002dea: COMBINING CYRILLIC LETTER O*/
+ RTUNI_ALPHA, /* U+002deb: COMBINING CYRILLIC LETTER PE*/
+ RTUNI_ALPHA, /* U+002dec: COMBINING CYRILLIC LETTER ER*/
+ RTUNI_ALPHA, /* U+002ded: COMBINING CYRILLIC LETTER ES*/
+ RTUNI_ALPHA, /* U+002dee: COMBINING CYRILLIC LETTER TE*/
+ RTUNI_ALPHA, /* U+002def: COMBINING CYRILLIC LETTER HA*/
+ RTUNI_ALPHA, /* U+002df0: COMBINING CYRILLIC LETTER TSE*/
+ RTUNI_ALPHA, /* U+002df1: COMBINING CYRILLIC LETTER CHE*/
+ RTUNI_ALPHA, /* U+002df2: COMBINING CYRILLIC LETTER SHA*/
+ RTUNI_ALPHA, /* U+002df3: COMBINING CYRILLIC LETTER SHCHA*/
+ RTUNI_ALPHA, /* U+002df4: COMBINING CYRILLIC LETTER FITA*/
+ RTUNI_ALPHA, /* U+002df5: COMBINING CYRILLIC LETTER ES-TE*/
+ RTUNI_ALPHA, /* U+002df6: COMBINING CYRILLIC LETTER A*/
+ RTUNI_ALPHA, /* U+002df7: COMBINING CYRILLIC LETTER IE*/
+ RTUNI_ALPHA, /* U+002df8: COMBINING CYRILLIC LETTER DJERV*/
+ RTUNI_ALPHA, /* U+002df9: COMBINING CYRILLIC LETTER MONOGRAPH UK*/
+ RTUNI_ALPHA, /* U+002dfa: COMBINING CYRILLIC LETTER YAT*/
+ RTUNI_ALPHA, /* U+002dfb: COMBINING CYRILLIC LETTER YU*/
+ RTUNI_ALPHA, /* U+002dfc: COMBINING CYRILLIC LETTER IOTIFIED A*/
+ RTUNI_ALPHA, /* U+002dfd: COMBINING CYRILLIC LETTER LITTLE YUS*/
+ RTUNI_ALPHA, /* U+002dfe: COMBINING CYRILLIC LETTER BIG YUS*/
+ RTUNI_ALPHA, /* U+002dff: COMBINING CYRILLIC LETTER IOTIFIED BIG YUS*/
+ 0, /* U+002e00: RIGHT ANGLE SUBSTITUTION MARKER*/
+ 0, /* U+002e01: RIGHT ANGLE DOTTED SUBSTITUTION MARKER*/
+ 0, /* U+002e02: LEFT SUBSTITUTION BRACKET*/
+ 0, /* U+002e03: RIGHT SUBSTITUTION BRACKET*/
+ 0, /* U+002e04: LEFT DOTTED SUBSTITUTION BRACKET*/
+ 0, /* U+002e05: RIGHT DOTTED SUBSTITUTION BRACKET*/
+ 0, /* U+002e06: RAISED INTERPOLATION MARKER*/
+ 0, /* U+002e07: RAISED DOTTED INTERPOLATION MARKER*/
+ 0, /* U+002e08: DOTTED TRANSPOSITION MARKER*/
+ 0, /* U+002e09: LEFT TRANSPOSITION BRACKET*/
+ 0, /* U+002e0a: RIGHT TRANSPOSITION BRACKET*/
+ 0, /* U+002e0b: RAISED SQUARE*/
+ 0, /* U+002e0c: LEFT RAISED OMISSION BRACKET*/
+ 0, /* U+002e0d: RIGHT RAISED OMISSION BRACKET*/
+ 0, /* U+002e0e: EDITORIAL CORONIS*/
+ 0, /* U+002e0f: PARAGRAPHOS*/
+ 0, /* U+002e10: FORKED PARAGRAPHOS*/
+ 0, /* U+002e11: REVERSED FORKED PARAGRAPHOS*/
+ 0, /* U+002e12: HYPODIASTOLE*/
+ 0, /* U+002e13: DOTTED OBELOS*/
+ 0, /* U+002e14: DOWNWARDS ANCORA*/
+ 0, /* U+002e15: UPWARDS ANCORA*/
+ 0, /* U+002e16: DOTTED RIGHT-POINTING ANGLE*/
+ 0, /* U+002e17: DOUBLE OBLIQUE HYPHEN*/
+ 0, /* U+002e18: INVERTED INTERROBANG*/
+ 0, /* U+002e19: PALM BRANCH*/
+ 0, /* U+002e1a: HYPHEN WITH DIAERESIS*/
+ 0, /* U+002e1b: TILDE WITH RING ABOVE*/
+ 0, /* U+002e1c: LEFT LOW PARAPHRASE BRACKET*/
+ 0, /* U+002e1d: RIGHT LOW PARAPHRASE BRACKET*/
+ 0, /* U+002e1e: TILDE WITH DOT ABOVE*/
+ 0, /* U+002e1f: TILDE WITH DOT BELOW*/
+ 0, /* U+002e20: LEFT VERTICAL BAR WITH QUILL*/
+ 0, /* U+002e21: RIGHT VERTICAL BAR WITH QUILL*/
+ 0, /* U+002e22: TOP LEFT HALF BRACKET*/
+ 0, /* U+002e23: TOP RIGHT HALF BRACKET*/
+ 0, /* U+002e24: BOTTOM LEFT HALF BRACKET*/
+ 0, /* U+002e25: BOTTOM RIGHT HALF BRACKET*/
+ 0, /* U+002e26: LEFT SIDEWAYS U BRACKET*/
+ 0, /* U+002e27: RIGHT SIDEWAYS U BRACKET*/
+ 0, /* U+002e28: LEFT DOUBLE PARENTHESIS*/
+ 0, /* U+002e29: RIGHT DOUBLE PARENTHESIS*/
+ 0, /* U+002e2a: TWO DOTS OVER ONE DOT PUNCTUATION*/
+ 0, /* U+002e2b: ONE DOT OVER TWO DOTS PUNCTUATION*/
+ 0, /* U+002e2c: SQUARED FOUR DOT PUNCTUATION*/
+ 0, /* U+002e2d: FIVE DOT MARK*/
+ 0, /* U+002e2e: REVERSED QUESTION MARK*/
+ RTUNI_ALPHA, /* U+002e2f: VERTICAL TILDE*/
+ 0, /* U+002e30: RING POINT*/
+ 0, /* U+002e31: WORD SEPARATOR MIDDLE DOT*/
+ 0, /* U+002e32: TURNED COMMA*/
+ 0, /* U+002e33: RAISED DOT*/
+ 0, /* U+002e34: RAISED COMMA*/
+ 0, /* U+002e35: TURNED SEMICOLON*/
+ 0, /* U+002e36: DAGGER WITH LEFT GUARD*/
+ 0, /* U+002e37: DAGGER WITH RIGHT GUARD*/
+ 0, /* U+002e38: TURNED DAGGER*/
+ 0, /* U+002e39: TOP HALF SECTION SIGN*/
+ 0, /* U+002e3a: TWO-EM DASH*/
+ 0, /* U+002e3b: THREE-EM DASH*/
+ 0, /* U+002e3c: */
+ 0, /* U+002e3d: */
+ 0, /* U+002e3e: */
+ 0, /* U+002e3f: */
+ 0, /* U+002e40: */
+ 0, /* U+002e41: */
+ 0, /* U+002e42: */
+ 0, /* U+002e43: */
+ 0, /* U+002e44: */
+ 0, /* U+002e45: */
+ 0, /* U+002e46: */
+ 0, /* U+002e47: */
+ 0, /* U+002e48: */
+ 0, /* U+002e49: */
+ 0, /* U+002e4a: */
+ 0, /* U+002e4b: */
+ 0, /* U+002e4c: */
+ 0, /* U+002e4d: */
+ 0, /* U+002e4e: */
+ 0, /* U+002e4f: */
+ 0, /* U+002e50: */
+ 0, /* U+002e51: */
+ 0, /* U+002e52: */
+ 0, /* U+002e53: */
+ 0, /* U+002e54: */
+ 0, /* U+002e55: */
+ 0, /* U+002e56: */
+ 0, /* U+002e57: */
+ 0, /* U+002e58: */
+ 0, /* U+002e59: */
+ 0, /* U+002e5a: */
+ 0, /* U+002e5b: */
+ 0, /* U+002e5c: */
+ 0, /* U+002e5d: */
+ 0, /* U+002e5e: */
+ 0, /* U+002e5f: */
+ 0, /* U+002e60: */
+ 0, /* U+002e61: */
+ 0, /* U+002e62: */
+ 0, /* U+002e63: */
+ 0, /* U+002e64: */
+ 0, /* U+002e65: */
+ 0, /* U+002e66: */
+ 0, /* U+002e67: */
+ 0, /* U+002e68: */
+ 0, /* U+002e69: */
+ 0, /* U+002e6a: */
+ 0, /* U+002e6b: */
+ 0, /* U+002e6c: */
+ 0, /* U+002e6d: */
+ 0, /* U+002e6e: */
+ 0, /* U+002e6f: */
+ 0, /* U+002e70: */
+ 0, /* U+002e71: */
+ 0, /* U+002e72: */
+ 0, /* U+002e73: */
+ 0, /* U+002e74: */
+ 0, /* U+002e75: */
+ 0, /* U+002e76: */
+ 0, /* U+002e77: */
+ 0, /* U+002e78: */
+ 0, /* U+002e79: */
+ 0, /* U+002e7a: */
+ 0, /* U+002e7b: */
+ 0, /* U+002e7c: */
+ 0, /* U+002e7d: */
+ 0, /* U+002e7e: */
+ 0, /* U+002e7f: */
+ 0, /* U+002e80: CJK RADICAL REPEAT*/
+ 0, /* U+002e81: CJK RADICAL CLIFF*/
+ 0, /* U+002e82: CJK RADICAL SECOND ONE*/
+ 0, /* U+002e83: CJK RADICAL SECOND TWO*/
+ 0, /* U+002e84: CJK RADICAL SECOND THREE*/
+ 0, /* U+002e85: CJK RADICAL PERSON*/
+ 0, /* U+002e86: CJK RADICAL BOX*/
+ 0, /* U+002e87: CJK RADICAL TABLE*/
+ 0, /* U+002e88: CJK RADICAL KNIFE ONE*/
+ 0, /* U+002e89: CJK RADICAL KNIFE TWO*/
+ 0, /* U+002e8a: CJK RADICAL DIVINATION*/
+ 0, /* U+002e8b: CJK RADICAL SEAL*/
+ 0, /* U+002e8c: CJK RADICAL SMALL ONE*/
+ 0, /* U+002e8d: CJK RADICAL SMALL TWO*/
+ 0, /* U+002e8e: CJK RADICAL LAME ONE*/
+ 0, /* U+002e8f: CJK RADICAL LAME TWO*/
+ 0, /* U+002e90: CJK RADICAL LAME THREE*/
+ 0, /* U+002e91: CJK RADICAL LAME FOUR*/
+ 0, /* U+002e92: CJK RADICAL SNAKE*/
+ 0, /* U+002e93: CJK RADICAL THREAD*/
+ 0, /* U+002e94: CJK RADICAL SNOUT ONE*/
+ 0, /* U+002e95: CJK RADICAL SNOUT TWO*/
+ 0, /* U+002e96: CJK RADICAL HEART ONE*/
+ 0, /* U+002e97: CJK RADICAL HEART TWO*/
+ 0, /* U+002e98: CJK RADICAL HAND*/
+ 0, /* U+002e99: CJK RADICAL RAP*/
+ 0, /* U+002e9a: */
+ 0, /* U+002e9b: CJK RADICAL CHOKE*/
+ 0, /* U+002e9c: CJK RADICAL SUN*/
+ 0, /* U+002e9d: CJK RADICAL MOON*/
+ 0, /* U+002e9e: CJK RADICAL DEATH*/
+ 0, /* U+002e9f: CJK RADICAL MOTHER*/
+ 0, /* U+002ea0: CJK RADICAL CIVILIAN*/
+ 0, /* U+002ea1: CJK RADICAL WATER ONE*/
+ 0, /* U+002ea2: CJK RADICAL WATER TWO*/
+ 0, /* U+002ea3: CJK RADICAL FIRE*/
+ 0, /* U+002ea4: CJK RADICAL PAW ONE*/
+ 0, /* U+002ea5: CJK RADICAL PAW TWO*/
+ 0, /* U+002ea6: CJK RADICAL SIMPLIFIED HALF TREE TRUNK*/
+ 0, /* U+002ea7: CJK RADICAL COW*/
+ 0, /* U+002ea8: CJK RADICAL DOG*/
+ 0, /* U+002ea9: CJK RADICAL JADE*/
+ 0, /* U+002eaa: CJK RADICAL BOLT OF CLOTH*/
+ 0, /* U+002eab: CJK RADICAL EYE*/
+ 0, /* U+002eac: CJK RADICAL SPIRIT ONE*/
+ 0, /* U+002ead: CJK RADICAL SPIRIT TWO*/
+ 0, /* U+002eae: CJK RADICAL BAMBOO*/
+ 0, /* U+002eaf: CJK RADICAL SILK*/
+ 0, /* U+002eb0: CJK RADICAL C-SIMPLIFIED SILK*/
+ 0, /* U+002eb1: CJK RADICAL NET ONE*/
+ 0, /* U+002eb2: CJK RADICAL NET TWO*/
+ 0, /* U+002eb3: CJK RADICAL NET THREE*/
+ 0, /* U+002eb4: CJK RADICAL NET FOUR*/
+ 0, /* U+002eb5: CJK RADICAL MESH*/
+ 0, /* U+002eb6: CJK RADICAL SHEEP*/
+ 0, /* U+002eb7: CJK RADICAL RAM*/
+ 0, /* U+002eb8: CJK RADICAL EWE*/
+ 0, /* U+002eb9: CJK RADICAL OLD*/
+ 0, /* U+002eba: CJK RADICAL BRUSH ONE*/
+ 0, /* U+002ebb: CJK RADICAL BRUSH TWO*/
+ 0, /* U+002ebc: CJK RADICAL MEAT*/
+ 0, /* U+002ebd: CJK RADICAL MORTAR*/
+ 0, /* U+002ebe: CJK RADICAL GRASS ONE*/
+ 0, /* U+002ebf: CJK RADICAL GRASS TWO*/
+ 0, /* U+002ec0: CJK RADICAL GRASS THREE*/
+ 0, /* U+002ec1: CJK RADICAL TIGER*/
+ 0, /* U+002ec2: CJK RADICAL CLOTHES*/
+ 0, /* U+002ec3: CJK RADICAL WEST ONE*/
+ 0, /* U+002ec4: CJK RADICAL WEST TWO*/
+ 0, /* U+002ec5: CJK RADICAL C-SIMPLIFIED SEE*/
+ 0, /* U+002ec6: CJK RADICAL SIMPLIFIED HORN*/
+ 0, /* U+002ec7: CJK RADICAL HORN*/
+ 0, /* U+002ec8: CJK RADICAL C-SIMPLIFIED SPEECH*/
+ 0, /* U+002ec9: CJK RADICAL C-SIMPLIFIED SHELL*/
+ 0, /* U+002eca: CJK RADICAL FOOT*/
+ 0, /* U+002ecb: CJK RADICAL C-SIMPLIFIED CART*/
+ 0, /* U+002ecc: CJK RADICAL SIMPLIFIED WALK*/
+ 0, /* U+002ecd: CJK RADICAL WALK ONE*/
+ 0, /* U+002ece: CJK RADICAL WALK TWO*/
+ 0, /* U+002ecf: CJK RADICAL CITY*/
+ 0, /* U+002ed0: CJK RADICAL C-SIMPLIFIED GOLD*/
+ 0, /* U+002ed1: CJK RADICAL LONG ONE*/
+ 0, /* U+002ed2: CJK RADICAL LONG TWO*/
+ 0, /* U+002ed3: CJK RADICAL C-SIMPLIFIED LONG*/
+ 0, /* U+002ed4: CJK RADICAL C-SIMPLIFIED GATE*/
+ 0, /* U+002ed5: CJK RADICAL MOUND ONE*/
+ 0, /* U+002ed6: CJK RADICAL MOUND TWO*/
+ 0, /* U+002ed7: CJK RADICAL RAIN*/
+ 0, /* U+002ed8: CJK RADICAL BLUE*/
+ 0, /* U+002ed9: CJK RADICAL C-SIMPLIFIED TANNED LEATHER*/
+ 0, /* U+002eda: CJK RADICAL C-SIMPLIFIED LEAF*/
+ 0, /* U+002edb: CJK RADICAL C-SIMPLIFIED WIND*/
+ 0, /* U+002edc: CJK RADICAL C-SIMPLIFIED FLY*/
+ 0, /* U+002edd: CJK RADICAL EAT ONE*/
+ 0, /* U+002ede: CJK RADICAL EAT TWO*/
+ 0, /* U+002edf: CJK RADICAL EAT THREE*/
+ 0, /* U+002ee0: CJK RADICAL C-SIMPLIFIED EAT*/
+ 0, /* U+002ee1: CJK RADICAL HEAD*/
+ 0, /* U+002ee2: CJK RADICAL C-SIMPLIFIED HORSE*/
+ 0, /* U+002ee3: CJK RADICAL BONE*/
+ 0, /* U+002ee4: CJK RADICAL GHOST*/
+ 0, /* U+002ee5: CJK RADICAL C-SIMPLIFIED FISH*/
+ 0, /* U+002ee6: CJK RADICAL C-SIMPLIFIED BIRD*/
+ 0, /* U+002ee7: CJK RADICAL C-SIMPLIFIED SALT*/
+ 0, /* U+002ee8: CJK RADICAL SIMPLIFIED WHEAT*/
+ 0, /* U+002ee9: CJK RADICAL SIMPLIFIED YELLOW*/
+ 0, /* U+002eea: CJK RADICAL C-SIMPLIFIED FROG*/
+ 0, /* U+002eeb: CJK RADICAL J-SIMPLIFIED EVEN*/
+ 0, /* U+002eec: CJK RADICAL C-SIMPLIFIED EVEN*/
+ 0, /* U+002eed: CJK RADICAL J-SIMPLIFIED TOOTH*/
+ 0, /* U+002eee: CJK RADICAL C-SIMPLIFIED TOOTH*/
+ 0, /* U+002eef: CJK RADICAL J-SIMPLIFIED DRAGON*/
+ 0, /* U+002ef0: CJK RADICAL C-SIMPLIFIED DRAGON*/
+ 0, /* U+002ef1: CJK RADICAL TURTLE*/
+ 0, /* U+002ef2: CJK RADICAL J-SIMPLIFIED TURTLE*/
+ 0, /* U+002ef3: CJK RADICAL C-SIMPLIFIED TURTLE*/
+ 0, /* U+002ef4: */
+ 0, /* U+002ef5: */
+ 0, /* U+002ef6: */
+ 0, /* U+002ef7: */
+ 0, /* U+002ef8: */
+ 0, /* U+002ef9: */
+ 0, /* U+002efa: */
+ 0, /* U+002efb: */
+ 0, /* U+002efc: */
+ 0, /* U+002efd: */
+ 0, /* U+002efe: */
+ 0, /* U+002eff: */
+ 0, /* U+002f00: KANGXI RADICAL ONE*/
+ 0, /* U+002f01: KANGXI RADICAL LINE*/
+ 0, /* U+002f02: KANGXI RADICAL DOT*/
+ 0, /* U+002f03: KANGXI RADICAL SLASH*/
+ 0, /* U+002f04: KANGXI RADICAL SECOND*/
+ 0, /* U+002f05: KANGXI RADICAL HOOK*/
+ 0, /* U+002f06: KANGXI RADICAL TWO*/
+ 0, /* U+002f07: KANGXI RADICAL LID*/
+ 0, /* U+002f08: KANGXI RADICAL MAN*/
+ 0, /* U+002f09: KANGXI RADICAL LEGS*/
+ 0, /* U+002f0a: KANGXI RADICAL ENTER*/
+ 0, /* U+002f0b: KANGXI RADICAL EIGHT*/
+ 0, /* U+002f0c: KANGXI RADICAL DOWN BOX*/
+ 0, /* U+002f0d: KANGXI RADICAL COVER*/
+ 0, /* U+002f0e: KANGXI RADICAL ICE*/
+ 0, /* U+002f0f: KANGXI RADICAL TABLE*/
+ 0, /* U+002f10: KANGXI RADICAL OPEN BOX*/
+ 0, /* U+002f11: KANGXI RADICAL KNIFE*/
+ 0, /* U+002f12: KANGXI RADICAL POWER*/
+ 0, /* U+002f13: KANGXI RADICAL WRAP*/
+ 0, /* U+002f14: KANGXI RADICAL SPOON*/
+ 0, /* U+002f15: KANGXI RADICAL RIGHT OPEN BOX*/
+ 0, /* U+002f16: KANGXI RADICAL HIDING ENCLOSURE*/
+ 0, /* U+002f17: KANGXI RADICAL TEN*/
+ 0, /* U+002f18: KANGXI RADICAL DIVINATION*/
+ 0, /* U+002f19: KANGXI RADICAL SEAL*/
+ 0, /* U+002f1a: KANGXI RADICAL CLIFF*/
+ 0, /* U+002f1b: KANGXI RADICAL PRIVATE*/
+ 0, /* U+002f1c: KANGXI RADICAL AGAIN*/
+ 0, /* U+002f1d: KANGXI RADICAL MOUTH*/
+ 0, /* U+002f1e: KANGXI RADICAL ENCLOSURE*/
+ 0, /* U+002f1f: KANGXI RADICAL EARTH*/
+ 0, /* U+002f20: KANGXI RADICAL SCHOLAR*/
+ 0, /* U+002f21: KANGXI RADICAL GO*/
+ 0, /* U+002f22: KANGXI RADICAL GO SLOWLY*/
+ 0, /* U+002f23: KANGXI RADICAL EVENING*/
+ 0, /* U+002f24: KANGXI RADICAL BIG*/
+ 0, /* U+002f25: KANGXI RADICAL WOMAN*/
+ 0, /* U+002f26: KANGXI RADICAL CHILD*/
+ 0, /* U+002f27: KANGXI RADICAL ROOF*/
+ 0, /* U+002f28: KANGXI RADICAL INCH*/
+ 0, /* U+002f29: KANGXI RADICAL SMALL*/
+ 0, /* U+002f2a: KANGXI RADICAL LAME*/
+ 0, /* U+002f2b: KANGXI RADICAL CORPSE*/
+ 0, /* U+002f2c: KANGXI RADICAL SPROUT*/
+ 0, /* U+002f2d: KANGXI RADICAL MOUNTAIN*/
+ 0, /* U+002f2e: KANGXI RADICAL RIVER*/
+ 0, /* U+002f2f: KANGXI RADICAL WORK*/
+ 0, /* U+002f30: KANGXI RADICAL ONESELF*/
+ 0, /* U+002f31: KANGXI RADICAL TURBAN*/
+ 0, /* U+002f32: KANGXI RADICAL DRY*/
+ 0, /* U+002f33: KANGXI RADICAL SHORT THREAD*/
+ 0, /* U+002f34: KANGXI RADICAL DOTTED CLIFF*/
+ 0, /* U+002f35: KANGXI RADICAL LONG STRIDE*/
+ 0, /* U+002f36: KANGXI RADICAL TWO HANDS*/
+ 0, /* U+002f37: KANGXI RADICAL SHOOT*/
+ 0, /* U+002f38: KANGXI RADICAL BOW*/
+ 0, /* U+002f39: KANGXI RADICAL SNOUT*/
+ 0, /* U+002f3a: KANGXI RADICAL BRISTLE*/
+ 0, /* U+002f3b: KANGXI RADICAL STEP*/
+ 0, /* U+002f3c: KANGXI RADICAL HEART*/
+ 0, /* U+002f3d: KANGXI RADICAL HALBERD*/
+ 0, /* U+002f3e: KANGXI RADICAL DOOR*/
+ 0, /* U+002f3f: KANGXI RADICAL HAND*/
+ 0, /* U+002f40: KANGXI RADICAL BRANCH*/
+ 0, /* U+002f41: KANGXI RADICAL RAP*/
+ 0, /* U+002f42: KANGXI RADICAL SCRIPT*/
+ 0, /* U+002f43: KANGXI RADICAL DIPPER*/
+ 0, /* U+002f44: KANGXI RADICAL AXE*/
+ 0, /* U+002f45: KANGXI RADICAL SQUARE*/
+ 0, /* U+002f46: KANGXI RADICAL NOT*/
+ 0, /* U+002f47: KANGXI RADICAL SUN*/
+ 0, /* U+002f48: KANGXI RADICAL SAY*/
+ 0, /* U+002f49: KANGXI RADICAL MOON*/
+ 0, /* U+002f4a: KANGXI RADICAL TREE*/
+ 0, /* U+002f4b: KANGXI RADICAL LACK*/
+ 0, /* U+002f4c: KANGXI RADICAL STOP*/
+ 0, /* U+002f4d: KANGXI RADICAL DEATH*/
+ 0, /* U+002f4e: KANGXI RADICAL WEAPON*/
+ 0, /* U+002f4f: KANGXI RADICAL DO NOT*/
+ 0, /* U+002f50: KANGXI RADICAL COMPARE*/
+ 0, /* U+002f51: KANGXI RADICAL FUR*/
+ 0, /* U+002f52: KANGXI RADICAL CLAN*/
+ 0, /* U+002f53: KANGXI RADICAL STEAM*/
+ 0, /* U+002f54: KANGXI RADICAL WATER*/
+ 0, /* U+002f55: KANGXI RADICAL FIRE*/
+ 0, /* U+002f56: KANGXI RADICAL CLAW*/
+ 0, /* U+002f57: KANGXI RADICAL FATHER*/
+ 0, /* U+002f58: KANGXI RADICAL DOUBLE X*/
+ 0, /* U+002f59: KANGXI RADICAL HALF TREE TRUNK*/
+ 0, /* U+002f5a: KANGXI RADICAL SLICE*/
+ 0, /* U+002f5b: KANGXI RADICAL FANG*/
+ 0, /* U+002f5c: KANGXI RADICAL COW*/
+ 0, /* U+002f5d: KANGXI RADICAL DOG*/
+ 0, /* U+002f5e: KANGXI RADICAL PROFOUND*/
+ 0, /* U+002f5f: KANGXI RADICAL JADE*/
+ 0, /* U+002f60: KANGXI RADICAL MELON*/
+ 0, /* U+002f61: KANGXI RADICAL TILE*/
+ 0, /* U+002f62: KANGXI RADICAL SWEET*/
+ 0, /* U+002f63: KANGXI RADICAL LIFE*/
+ 0, /* U+002f64: KANGXI RADICAL USE*/
+ 0, /* U+002f65: KANGXI RADICAL FIELD*/
+ 0, /* U+002f66: KANGXI RADICAL BOLT OF CLOTH*/
+ 0, /* U+002f67: KANGXI RADICAL SICKNESS*/
+ 0, /* U+002f68: KANGXI RADICAL DOTTED TENT*/
+ 0, /* U+002f69: KANGXI RADICAL WHITE*/
+ 0, /* U+002f6a: KANGXI RADICAL SKIN*/
+ 0, /* U+002f6b: KANGXI RADICAL DISH*/
+ 0, /* U+002f6c: KANGXI RADICAL EYE*/
+ 0, /* U+002f6d: KANGXI RADICAL SPEAR*/
+ 0, /* U+002f6e: KANGXI RADICAL ARROW*/
+ 0, /* U+002f6f: KANGXI RADICAL STONE*/
+ 0, /* U+002f70: KANGXI RADICAL SPIRIT*/
+ 0, /* U+002f71: KANGXI RADICAL TRACK*/
+ 0, /* U+002f72: KANGXI RADICAL GRAIN*/
+ 0, /* U+002f73: KANGXI RADICAL CAVE*/
+ 0, /* U+002f74: KANGXI RADICAL STAND*/
+ 0, /* U+002f75: KANGXI RADICAL BAMBOO*/
+ 0, /* U+002f76: KANGXI RADICAL RICE*/
+ 0, /* U+002f77: KANGXI RADICAL SILK*/
+ 0, /* U+002f78: KANGXI RADICAL JAR*/
+ 0, /* U+002f79: KANGXI RADICAL NET*/
+ 0, /* U+002f7a: KANGXI RADICAL SHEEP*/
+ 0, /* U+002f7b: KANGXI RADICAL FEATHER*/
+ 0, /* U+002f7c: KANGXI RADICAL OLD*/
+ 0, /* U+002f7d: KANGXI RADICAL AND*/
+ 0, /* U+002f7e: KANGXI RADICAL PLOW*/
+ 0, /* U+002f7f: KANGXI RADICAL EAR*/
+ 0, /* U+002f80: KANGXI RADICAL BRUSH*/
+ 0, /* U+002f81: KANGXI RADICAL MEAT*/
+ 0, /* U+002f82: KANGXI RADICAL MINISTER*/
+ 0, /* U+002f83: KANGXI RADICAL SELF*/
+ 0, /* U+002f84: KANGXI RADICAL ARRIVE*/
+ 0, /* U+002f85: KANGXI RADICAL MORTAR*/
+ 0, /* U+002f86: KANGXI RADICAL TONGUE*/
+ 0, /* U+002f87: KANGXI RADICAL OPPOSE*/
+ 0, /* U+002f88: KANGXI RADICAL BOAT*/
+ 0, /* U+002f89: KANGXI RADICAL STOPPING*/
+ 0, /* U+002f8a: KANGXI RADICAL COLOR*/
+ 0, /* U+002f8b: KANGXI RADICAL GRASS*/
+ 0, /* U+002f8c: KANGXI RADICAL TIGER*/
+ 0, /* U+002f8d: KANGXI RADICAL INSECT*/
+ 0, /* U+002f8e: KANGXI RADICAL BLOOD*/
+ 0, /* U+002f8f: KANGXI RADICAL WALK ENCLOSURE*/
+ 0, /* U+002f90: KANGXI RADICAL CLOTHES*/
+ 0, /* U+002f91: KANGXI RADICAL WEST*/
+ 0, /* U+002f92: KANGXI RADICAL SEE*/
+ 0, /* U+002f93: KANGXI RADICAL HORN*/
+ 0, /* U+002f94: KANGXI RADICAL SPEECH*/
+ 0, /* U+002f95: KANGXI RADICAL VALLEY*/
+ 0, /* U+002f96: KANGXI RADICAL BEAN*/
+ 0, /* U+002f97: KANGXI RADICAL PIG*/
+ 0, /* U+002f98: KANGXI RADICAL BADGER*/
+ 0, /* U+002f99: KANGXI RADICAL SHELL*/
+ 0, /* U+002f9a: KANGXI RADICAL RED*/
+ 0, /* U+002f9b: KANGXI RADICAL RUN*/
+ 0, /* U+002f9c: KANGXI RADICAL FOOT*/
+ 0, /* U+002f9d: KANGXI RADICAL BODY*/
+ 0, /* U+002f9e: KANGXI RADICAL CART*/
+ 0, /* U+002f9f: KANGXI RADICAL BITTER*/
+ 0, /* U+002fa0: KANGXI RADICAL MORNING*/
+ 0, /* U+002fa1: KANGXI RADICAL WALK*/
+ 0, /* U+002fa2: KANGXI RADICAL CITY*/
+ 0, /* U+002fa3: KANGXI RADICAL WINE*/
+ 0, /* U+002fa4: KANGXI RADICAL DISTINGUISH*/
+ 0, /* U+002fa5: KANGXI RADICAL VILLAGE*/
+ 0, /* U+002fa6: KANGXI RADICAL GOLD*/
+ 0, /* U+002fa7: KANGXI RADICAL LONG*/
+ 0, /* U+002fa8: KANGXI RADICAL GATE*/
+ 0, /* U+002fa9: KANGXI RADICAL MOUND*/
+ 0, /* U+002faa: KANGXI RADICAL SLAVE*/
+ 0, /* U+002fab: KANGXI RADICAL SHORT TAILED BIRD*/
+ 0, /* U+002fac: KANGXI RADICAL RAIN*/
+ 0, /* U+002fad: KANGXI RADICAL BLUE*/
+ 0, /* U+002fae: KANGXI RADICAL WRONG*/
+ 0, /* U+002faf: KANGXI RADICAL FACE*/
+ 0, /* U+002fb0: KANGXI RADICAL LEATHER*/
+ 0, /* U+002fb1: KANGXI RADICAL TANNED LEATHER*/
+ 0, /* U+002fb2: KANGXI RADICAL LEEK*/
+ 0, /* U+002fb3: KANGXI RADICAL SOUND*/
+ 0, /* U+002fb4: KANGXI RADICAL LEAF*/
+ 0, /* U+002fb5: KANGXI RADICAL WIND*/
+ 0, /* U+002fb6: KANGXI RADICAL FLY*/
+ 0, /* U+002fb7: KANGXI RADICAL EAT*/
+ 0, /* U+002fb8: KANGXI RADICAL HEAD*/
+ 0, /* U+002fb9: KANGXI RADICAL FRAGRANT*/
+ 0, /* U+002fba: KANGXI RADICAL HORSE*/
+ 0, /* U+002fbb: KANGXI RADICAL BONE*/
+ 0, /* U+002fbc: KANGXI RADICAL TALL*/
+ 0, /* U+002fbd: KANGXI RADICAL HAIR*/
+ 0, /* U+002fbe: KANGXI RADICAL FIGHT*/
+ 0, /* U+002fbf: KANGXI RADICAL SACRIFICIAL WINE*/
+ 0, /* U+002fc0: KANGXI RADICAL CAULDRON*/
+ 0, /* U+002fc1: KANGXI RADICAL GHOST*/
+ 0, /* U+002fc2: KANGXI RADICAL FISH*/
+ 0, /* U+002fc3: KANGXI RADICAL BIRD*/
+ 0, /* U+002fc4: KANGXI RADICAL SALT*/
+ 0, /* U+002fc5: KANGXI RADICAL DEER*/
+ 0, /* U+002fc6: KANGXI RADICAL WHEAT*/
+ 0, /* U+002fc7: KANGXI RADICAL HEMP*/
+ 0, /* U+002fc8: KANGXI RADICAL YELLOW*/
+ 0, /* U+002fc9: KANGXI RADICAL MILLET*/
+ 0, /* U+002fca: KANGXI RADICAL BLACK*/
+ 0, /* U+002fcb: KANGXI RADICAL EMBROIDERY*/
+ 0, /* U+002fcc: KANGXI RADICAL FROG*/
+ 0, /* U+002fcd: KANGXI RADICAL TRIPOD*/
+ 0, /* U+002fce: KANGXI RADICAL DRUM*/
+ 0, /* U+002fcf: KANGXI RADICAL RAT*/
+ 0, /* U+002fd0: KANGXI RADICAL NOSE*/
+ 0, /* U+002fd1: KANGXI RADICAL EVEN*/
+ 0, /* U+002fd2: KANGXI RADICAL TOOTH*/
+ 0, /* U+002fd3: KANGXI RADICAL DRAGON*/
+ 0, /* U+002fd4: KANGXI RADICAL TURTLE*/
+ 0, /* U+002fd5: KANGXI RADICAL FLUTE*/
+ 0, /* U+002fd6: */
+ 0, /* U+002fd7: */
+ 0, /* U+002fd8: */
+ 0, /* U+002fd9: */
+ 0, /* U+002fda: */
+ 0, /* U+002fdb: */
+ 0, /* U+002fdc: */
+ 0, /* U+002fdd: */
+ 0, /* U+002fde: */
+ 0, /* U+002fdf: */
+ 0, /* U+002fe0: */
+ 0, /* U+002fe1: */
+ 0, /* U+002fe2: */
+ 0, /* U+002fe3: */
+ 0, /* U+002fe4: */
+ 0, /* U+002fe5: */
+ 0, /* U+002fe6: */
+ 0, /* U+002fe7: */
+ 0, /* U+002fe8: */
+ 0, /* U+002fe9: */
+ 0, /* U+002fea: */
+ 0, /* U+002feb: */
+ 0, /* U+002fec: */
+ 0, /* U+002fed: */
+ 0, /* U+002fee: */
+ 0, /* U+002fef: */
+ 0, /* U+002ff0: IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT*/
+ 0, /* U+002ff1: IDEOGRAPHIC DESCRIPTION CHARACTER ABOVE TO BELOW*/
+ 0, /* U+002ff2: IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO MIDDLE AND RIGHT*/
+ 0, /* U+002ff3: IDEOGRAPHIC DESCRIPTION CHARACTER ABOVE TO MIDDLE AND BELOW*/
+ 0, /* U+002ff4: IDEOGRAPHIC DESCRIPTION CHARACTER FULL SURROUND*/
+ 0, /* U+002ff5: IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM ABOVE*/
+ 0, /* U+002ff6: IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM BELOW*/
+ 0, /* U+002ff7: IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM LEFT*/
+ 0, /* U+002ff8: IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM UPPER LEFT*/
+ 0, /* U+002ff9: IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM UPPER RIGHT*/
+ 0, /* U+002ffa: IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM LOWER LEFT*/
+ 0, /* U+002ffb: IDEOGRAPHIC DESCRIPTION CHARACTER OVERLAID*/
+ 0, /* U+002ffc: */
+ 0, /* U+002ffd: */
+ 0, /* U+002ffe: */
+ 0, /* U+002fff: */
+ RTUNI_WSPACE, /* U+003000: IDEOGRAPHIC SPACE*/
+ 0, /* U+003001: IDEOGRAPHIC COMMA*/
+ 0, /* U+003002: IDEOGRAPHIC FULL STOP*/
+ 0, /* U+003003: DITTO MARK*/
+ 0, /* U+003004: JAPANESE INDUSTRIAL STANDARD SYMBOL*/
+ RTUNI_ALPHA, /* U+003005: IDEOGRAPHIC ITERATION MARK*/
+ RTUNI_ALPHA, /* U+003006: IDEOGRAPHIC CLOSING MARK*/
+ RTUNI_ALPHA, /* U+003007: IDEOGRAPHIC NUMBER ZERO*/
+ 0, /* U+003008: LEFT ANGLE BRACKET*/
+ 0, /* U+003009: RIGHT ANGLE BRACKET*/
+ 0, /* U+00300a: LEFT DOUBLE ANGLE BRACKET*/
+ 0, /* U+00300b: RIGHT DOUBLE ANGLE BRACKET*/
+ 0, /* U+00300c: LEFT CORNER BRACKET*/
+ 0, /* U+00300d: RIGHT CORNER BRACKET*/
+ 0, /* U+00300e: LEFT WHITE CORNER BRACKET*/
+ 0, /* U+00300f: RIGHT WHITE CORNER BRACKET*/
+ 0, /* U+003010: LEFT BLACK LENTICULAR BRACKET*/
+ 0, /* U+003011: RIGHT BLACK LENTICULAR BRACKET*/
+ 0, /* U+003012: POSTAL MARK*/
+ 0, /* U+003013: GETA MARK*/
+ 0, /* U+003014: LEFT TORTOISE SHELL BRACKET*/
+ 0, /* U+003015: RIGHT TORTOISE SHELL BRACKET*/
+ 0, /* U+003016: LEFT WHITE LENTICULAR BRACKET*/
+ 0, /* U+003017: RIGHT WHITE LENTICULAR BRACKET*/
+ 0, /* U+003018: LEFT WHITE TORTOISE SHELL BRACKET*/
+ 0, /* U+003019: RIGHT WHITE TORTOISE SHELL BRACKET*/
+ 0, /* U+00301a: LEFT WHITE SQUARE BRACKET*/
+ 0, /* U+00301b: RIGHT WHITE SQUARE BRACKET*/
+ 0, /* U+00301c: WAVE DASH*/
+ 0, /* U+00301d: REVERSED DOUBLE PRIME QUOTATION MARK*/
+ 0, /* U+00301e: DOUBLE PRIME QUOTATION MARK*/
+ 0, /* U+00301f: LOW DOUBLE PRIME QUOTATION MARK*/
+ 0, /* U+003020: POSTAL MARK FACE*/
+ RTUNI_ALPHA, /* U+003021: HANGZHOU NUMERAL ONE*/
+ RTUNI_ALPHA, /* U+003022: HANGZHOU NUMERAL TWO*/
+ RTUNI_ALPHA, /* U+003023: HANGZHOU NUMERAL THREE*/
+ RTUNI_ALPHA, /* U+003024: HANGZHOU NUMERAL FOUR*/
+ RTUNI_ALPHA, /* U+003025: HANGZHOU NUMERAL FIVE*/
+ RTUNI_ALPHA, /* U+003026: HANGZHOU NUMERAL SIX*/
+ RTUNI_ALPHA, /* U+003027: HANGZHOU NUMERAL SEVEN*/
+ RTUNI_ALPHA, /* U+003028: HANGZHOU NUMERAL EIGHT*/
+ RTUNI_ALPHA, /* U+003029: HANGZHOU NUMERAL NINE*/
+ 0, /* U+00302a: IDEOGRAPHIC LEVEL TONE MARK*/
+ 0, /* U+00302b: IDEOGRAPHIC RISING TONE MARK*/
+ 0, /* U+00302c: IDEOGRAPHIC DEPARTING TONE MARK*/
+ 0, /* U+00302d: IDEOGRAPHIC ENTERING TONE MARK*/
+ 0, /* U+00302e: HANGUL SINGLE DOT TONE MARK*/
+ 0, /* U+00302f: HANGUL DOUBLE DOT TONE MARK*/
+ 0, /* U+003030: WAVY DASH*/
+ RTUNI_ALPHA, /* U+003031: VERTICAL KANA REPEAT MARK*/
+ RTUNI_ALPHA, /* U+003032: VERTICAL KANA REPEAT WITH VOICED SOUND MARK*/
+ RTUNI_ALPHA, /* U+003033: VERTICAL KANA REPEAT MARK UPPER HALF*/
+ RTUNI_ALPHA, /* U+003034: VERTICAL KANA REPEAT WITH VOICED SOUND MARK UPPER HALF*/
+ RTUNI_ALPHA, /* U+003035: VERTICAL KANA REPEAT MARK LOWER HALF*/
+ 0, /* U+003036: CIRCLED POSTAL MARK*/
+ 0, /* U+003037: IDEOGRAPHIC TELEGRAPH LINE FEED SEPARATOR SYMBOL*/
+ RTUNI_ALPHA, /* U+003038: HANGZHOU NUMERAL TEN*/
+ RTUNI_ALPHA, /* U+003039: HANGZHOU NUMERAL TWENTY*/
+ RTUNI_ALPHA, /* U+00303a: HANGZHOU NUMERAL THIRTY*/
+ RTUNI_ALPHA, /* U+00303b: VERTICAL IDEOGRAPHIC ITERATION MARK*/
+ RTUNI_ALPHA, /* U+00303c: MASU MARK*/
+ 0, /* U+00303d: PART ALTERNATION MARK*/
+ 0, /* U+00303e: IDEOGRAPHIC VARIATION INDICATOR*/
+ 0, /* U+00303f: IDEOGRAPHIC HALF FILL SPACE*/
+ 0, /* U+003040: */
+ RTUNI_ALPHA, /* U+003041: HIRAGANA LETTER SMALL A*/
+ RTUNI_ALPHA, /* U+003042: HIRAGANA LETTER A*/
+ RTUNI_ALPHA, /* U+003043: HIRAGANA LETTER SMALL I*/
+ RTUNI_ALPHA, /* U+003044: HIRAGANA LETTER I*/
+ RTUNI_ALPHA, /* U+003045: HIRAGANA LETTER SMALL U*/
+ RTUNI_ALPHA, /* U+003046: HIRAGANA LETTER U*/
+ RTUNI_ALPHA, /* U+003047: HIRAGANA LETTER SMALL E*/
+ RTUNI_ALPHA, /* U+003048: HIRAGANA LETTER E*/
+ RTUNI_ALPHA, /* U+003049: HIRAGANA LETTER SMALL O*/
+ RTUNI_ALPHA, /* U+00304a: HIRAGANA LETTER O*/
+ RTUNI_ALPHA, /* U+00304b: HIRAGANA LETTER KA*/
+ RTUNI_ALPHA, /* U+00304c: HIRAGANA LETTER GA*/
+ RTUNI_ALPHA, /* U+00304d: HIRAGANA LETTER KI*/
+ RTUNI_ALPHA, /* U+00304e: HIRAGANA LETTER GI*/
+ RTUNI_ALPHA, /* U+00304f: HIRAGANA LETTER KU*/
+ RTUNI_ALPHA, /* U+003050: HIRAGANA LETTER GU*/
+ RTUNI_ALPHA, /* U+003051: HIRAGANA LETTER KE*/
+ RTUNI_ALPHA, /* U+003052: HIRAGANA LETTER GE*/
+ RTUNI_ALPHA, /* U+003053: HIRAGANA LETTER KO*/
+ RTUNI_ALPHA, /* U+003054: HIRAGANA LETTER GO*/
+ RTUNI_ALPHA, /* U+003055: HIRAGANA LETTER SA*/
+ RTUNI_ALPHA, /* U+003056: HIRAGANA LETTER ZA*/
+ RTUNI_ALPHA, /* U+003057: HIRAGANA LETTER SI*/
+ RTUNI_ALPHA, /* U+003058: HIRAGANA LETTER ZI*/
+ RTUNI_ALPHA, /* U+003059: HIRAGANA LETTER SU*/
+ RTUNI_ALPHA, /* U+00305a: HIRAGANA LETTER ZU*/
+ RTUNI_ALPHA, /* U+00305b: HIRAGANA LETTER SE*/
+ RTUNI_ALPHA, /* U+00305c: HIRAGANA LETTER ZE*/
+ RTUNI_ALPHA, /* U+00305d: HIRAGANA LETTER SO*/
+ RTUNI_ALPHA, /* U+00305e: HIRAGANA LETTER ZO*/
+ RTUNI_ALPHA, /* U+00305f: HIRAGANA LETTER TA*/
+ RTUNI_ALPHA, /* U+003060: HIRAGANA LETTER DA*/
+ RTUNI_ALPHA, /* U+003061: HIRAGANA LETTER TI*/
+ RTUNI_ALPHA, /* U+003062: HIRAGANA LETTER DI*/
+ RTUNI_ALPHA, /* U+003063: HIRAGANA LETTER SMALL TU*/
+ RTUNI_ALPHA, /* U+003064: HIRAGANA LETTER TU*/
+ RTUNI_ALPHA, /* U+003065: HIRAGANA LETTER DU*/
+ RTUNI_ALPHA, /* U+003066: HIRAGANA LETTER TE*/
+ RTUNI_ALPHA, /* U+003067: HIRAGANA LETTER DE*/
+ RTUNI_ALPHA, /* U+003068: HIRAGANA LETTER TO*/
+ RTUNI_ALPHA, /* U+003069: HIRAGANA LETTER DO*/
+ RTUNI_ALPHA, /* U+00306a: HIRAGANA LETTER NA*/
+ RTUNI_ALPHA, /* U+00306b: HIRAGANA LETTER NI*/
+ RTUNI_ALPHA, /* U+00306c: HIRAGANA LETTER NU*/
+ RTUNI_ALPHA, /* U+00306d: HIRAGANA LETTER NE*/
+ RTUNI_ALPHA, /* U+00306e: HIRAGANA LETTER NO*/
+ RTUNI_ALPHA, /* U+00306f: HIRAGANA LETTER HA*/
+ RTUNI_ALPHA, /* U+003070: HIRAGANA LETTER BA*/
+ RTUNI_ALPHA, /* U+003071: HIRAGANA LETTER PA*/
+ RTUNI_ALPHA, /* U+003072: HIRAGANA LETTER HI*/
+ RTUNI_ALPHA, /* U+003073: HIRAGANA LETTER BI*/
+ RTUNI_ALPHA, /* U+003074: HIRAGANA LETTER PI*/
+ RTUNI_ALPHA, /* U+003075: HIRAGANA LETTER HU*/
+ RTUNI_ALPHA, /* U+003076: HIRAGANA LETTER BU*/
+ RTUNI_ALPHA, /* U+003077: HIRAGANA LETTER PU*/
+ RTUNI_ALPHA, /* U+003078: HIRAGANA LETTER HE*/
+ RTUNI_ALPHA, /* U+003079: HIRAGANA LETTER BE*/
+ RTUNI_ALPHA, /* U+00307a: HIRAGANA LETTER PE*/
+ RTUNI_ALPHA, /* U+00307b: HIRAGANA LETTER HO*/
+ RTUNI_ALPHA, /* U+00307c: HIRAGANA LETTER BO*/
+ RTUNI_ALPHA, /* U+00307d: HIRAGANA LETTER PO*/
+ RTUNI_ALPHA, /* U+00307e: HIRAGANA LETTER MA*/
+ RTUNI_ALPHA, /* U+00307f: HIRAGANA LETTER MI*/
+ RTUNI_ALPHA, /* U+003080: HIRAGANA LETTER MU*/
+ RTUNI_ALPHA, /* U+003081: HIRAGANA LETTER ME*/
+ RTUNI_ALPHA, /* U+003082: HIRAGANA LETTER MO*/
+ RTUNI_ALPHA, /* U+003083: HIRAGANA LETTER SMALL YA*/
+ RTUNI_ALPHA, /* U+003084: HIRAGANA LETTER YA*/
+ RTUNI_ALPHA, /* U+003085: HIRAGANA LETTER SMALL YU*/
+ RTUNI_ALPHA, /* U+003086: HIRAGANA LETTER YU*/
+ RTUNI_ALPHA, /* U+003087: HIRAGANA LETTER SMALL YO*/
+ RTUNI_ALPHA, /* U+003088: HIRAGANA LETTER YO*/
+ RTUNI_ALPHA, /* U+003089: HIRAGANA LETTER RA*/
+ RTUNI_ALPHA, /* U+00308a: HIRAGANA LETTER RI*/
+ RTUNI_ALPHA, /* U+00308b: HIRAGANA LETTER RU*/
+ RTUNI_ALPHA, /* U+00308c: HIRAGANA LETTER RE*/
+ RTUNI_ALPHA, /* U+00308d: HIRAGANA LETTER RO*/
+ RTUNI_ALPHA, /* U+00308e: HIRAGANA LETTER SMALL WA*/
+ RTUNI_ALPHA, /* U+00308f: HIRAGANA LETTER WA*/
+ RTUNI_ALPHA, /* U+003090: HIRAGANA LETTER WI*/
+ RTUNI_ALPHA, /* U+003091: HIRAGANA LETTER WE*/
+ RTUNI_ALPHA, /* U+003092: HIRAGANA LETTER WO*/
+ RTUNI_ALPHA, /* U+003093: HIRAGANA LETTER N*/
+ RTUNI_ALPHA, /* U+003094: HIRAGANA LETTER VU*/
+ RTUNI_ALPHA, /* U+003095: HIRAGANA LETTER SMALL KA*/
+ RTUNI_ALPHA, /* U+003096: HIRAGANA LETTER SMALL KE*/
+ 0, /* U+003097: */
+ 0, /* U+003098: */
+ 0, /* U+003099: COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK*/
+ 0, /* U+00309a: COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK*/
+ 0, /* U+00309b: KATAKANA-HIRAGANA VOICED SOUND MARK*/
+ 0, /* U+00309c: KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK*/
+ RTUNI_ALPHA, /* U+00309d: HIRAGANA ITERATION MARK*/
+ RTUNI_ALPHA, /* U+00309e: HIRAGANA VOICED ITERATION MARK*/
+ RTUNI_ALPHA, /* U+00309f: HIRAGANA DIGRAPH YORI*/
+ 0, /* U+0030a0: KATAKANA-HIRAGANA DOUBLE HYPHEN*/
+ RTUNI_ALPHA, /* U+0030a1: KATAKANA LETTER SMALL A*/
+ RTUNI_ALPHA, /* U+0030a2: KATAKANA LETTER A*/
+ RTUNI_ALPHA, /* U+0030a3: KATAKANA LETTER SMALL I*/
+ RTUNI_ALPHA, /* U+0030a4: KATAKANA LETTER I*/
+ RTUNI_ALPHA, /* U+0030a5: KATAKANA LETTER SMALL U*/
+ RTUNI_ALPHA, /* U+0030a6: KATAKANA LETTER U*/
+ RTUNI_ALPHA, /* U+0030a7: KATAKANA LETTER SMALL E*/
+ RTUNI_ALPHA, /* U+0030a8: KATAKANA LETTER E*/
+ RTUNI_ALPHA, /* U+0030a9: KATAKANA LETTER SMALL O*/
+ RTUNI_ALPHA, /* U+0030aa: KATAKANA LETTER O*/
+ RTUNI_ALPHA, /* U+0030ab: KATAKANA LETTER KA*/
+ RTUNI_ALPHA, /* U+0030ac: KATAKANA LETTER GA*/
+ RTUNI_ALPHA, /* U+0030ad: KATAKANA LETTER KI*/
+ RTUNI_ALPHA, /* U+0030ae: KATAKANA LETTER GI*/
+ RTUNI_ALPHA, /* U+0030af: KATAKANA LETTER KU*/
+ RTUNI_ALPHA, /* U+0030b0: KATAKANA LETTER GU*/
+ RTUNI_ALPHA, /* U+0030b1: KATAKANA LETTER KE*/
+ RTUNI_ALPHA, /* U+0030b2: KATAKANA LETTER GE*/
+ RTUNI_ALPHA, /* U+0030b3: KATAKANA LETTER KO*/
+ RTUNI_ALPHA, /* U+0030b4: KATAKANA LETTER GO*/
+ RTUNI_ALPHA, /* U+0030b5: KATAKANA LETTER SA*/
+ RTUNI_ALPHA, /* U+0030b6: KATAKANA LETTER ZA*/
+ RTUNI_ALPHA, /* U+0030b7: KATAKANA LETTER SI*/
+ RTUNI_ALPHA, /* U+0030b8: KATAKANA LETTER ZI*/
+ RTUNI_ALPHA, /* U+0030b9: KATAKANA LETTER SU*/
+ RTUNI_ALPHA, /* U+0030ba: KATAKANA LETTER ZU*/
+ RTUNI_ALPHA, /* U+0030bb: KATAKANA LETTER SE*/
+ RTUNI_ALPHA, /* U+0030bc: KATAKANA LETTER ZE*/
+ RTUNI_ALPHA, /* U+0030bd: KATAKANA LETTER SO*/
+ RTUNI_ALPHA, /* U+0030be: KATAKANA LETTER ZO*/
+ RTUNI_ALPHA, /* U+0030bf: KATAKANA LETTER TA*/
+ RTUNI_ALPHA, /* U+0030c0: KATAKANA LETTER DA*/
+ RTUNI_ALPHA, /* U+0030c1: KATAKANA LETTER TI*/
+ RTUNI_ALPHA, /* U+0030c2: KATAKANA LETTER DI*/
+ RTUNI_ALPHA, /* U+0030c3: KATAKANA LETTER SMALL TU*/
+ RTUNI_ALPHA, /* U+0030c4: KATAKANA LETTER TU*/
+ RTUNI_ALPHA, /* U+0030c5: KATAKANA LETTER DU*/
+ RTUNI_ALPHA, /* U+0030c6: KATAKANA LETTER TE*/
+ RTUNI_ALPHA, /* U+0030c7: KATAKANA LETTER DE*/
+ RTUNI_ALPHA, /* U+0030c8: KATAKANA LETTER TO*/
+ RTUNI_ALPHA, /* U+0030c9: KATAKANA LETTER DO*/
+ RTUNI_ALPHA, /* U+0030ca: KATAKANA LETTER NA*/
+ RTUNI_ALPHA, /* U+0030cb: KATAKANA LETTER NI*/
+ RTUNI_ALPHA, /* U+0030cc: KATAKANA LETTER NU*/
+ RTUNI_ALPHA, /* U+0030cd: KATAKANA LETTER NE*/
+ RTUNI_ALPHA, /* U+0030ce: KATAKANA LETTER NO*/
+ RTUNI_ALPHA, /* U+0030cf: KATAKANA LETTER HA*/
+ RTUNI_ALPHA, /* U+0030d0: KATAKANA LETTER BA*/
+ RTUNI_ALPHA, /* U+0030d1: KATAKANA LETTER PA*/
+ RTUNI_ALPHA, /* U+0030d2: KATAKANA LETTER HI*/
+ RTUNI_ALPHA, /* U+0030d3: KATAKANA LETTER BI*/
+ RTUNI_ALPHA, /* U+0030d4: KATAKANA LETTER PI*/
+ RTUNI_ALPHA, /* U+0030d5: KATAKANA LETTER HU*/
+ RTUNI_ALPHA, /* U+0030d6: KATAKANA LETTER BU*/
+ RTUNI_ALPHA, /* U+0030d7: KATAKANA LETTER PU*/
+ RTUNI_ALPHA, /* U+0030d8: KATAKANA LETTER HE*/
+ RTUNI_ALPHA, /* U+0030d9: KATAKANA LETTER BE*/
+ RTUNI_ALPHA, /* U+0030da: KATAKANA LETTER PE*/
+ RTUNI_ALPHA, /* U+0030db: KATAKANA LETTER HO*/
+ RTUNI_ALPHA, /* U+0030dc: KATAKANA LETTER BO*/
+ RTUNI_ALPHA, /* U+0030dd: KATAKANA LETTER PO*/
+ RTUNI_ALPHA, /* U+0030de: KATAKANA LETTER MA*/
+ RTUNI_ALPHA, /* U+0030df: KATAKANA LETTER MI*/
+ RTUNI_ALPHA, /* U+0030e0: KATAKANA LETTER MU*/
+ RTUNI_ALPHA, /* U+0030e1: KATAKANA LETTER ME*/
+ RTUNI_ALPHA, /* U+0030e2: KATAKANA LETTER MO*/
+ RTUNI_ALPHA, /* U+0030e3: KATAKANA LETTER SMALL YA*/
+ RTUNI_ALPHA, /* U+0030e4: KATAKANA LETTER YA*/
+ RTUNI_ALPHA, /* U+0030e5: KATAKANA LETTER SMALL YU*/
+ RTUNI_ALPHA, /* U+0030e6: KATAKANA LETTER YU*/
+ RTUNI_ALPHA, /* U+0030e7: KATAKANA LETTER SMALL YO*/
+ RTUNI_ALPHA, /* U+0030e8: KATAKANA LETTER YO*/
+ RTUNI_ALPHA, /* U+0030e9: KATAKANA LETTER RA*/
+ RTUNI_ALPHA, /* U+0030ea: KATAKANA LETTER RI*/
+ RTUNI_ALPHA, /* U+0030eb: KATAKANA LETTER RU*/
+ RTUNI_ALPHA, /* U+0030ec: KATAKANA LETTER RE*/
+ RTUNI_ALPHA, /* U+0030ed: KATAKANA LETTER RO*/
+ RTUNI_ALPHA, /* U+0030ee: KATAKANA LETTER SMALL WA*/
+ RTUNI_ALPHA, /* U+0030ef: KATAKANA LETTER WA*/
+ RTUNI_ALPHA, /* U+0030f0: KATAKANA LETTER WI*/
+ RTUNI_ALPHA, /* U+0030f1: KATAKANA LETTER WE*/
+ RTUNI_ALPHA, /* U+0030f2: KATAKANA LETTER WO*/
+ RTUNI_ALPHA, /* U+0030f3: KATAKANA LETTER N*/
+ RTUNI_ALPHA, /* U+0030f4: KATAKANA LETTER VU*/
+ RTUNI_ALPHA, /* U+0030f5: KATAKANA LETTER SMALL KA*/
+ RTUNI_ALPHA, /* U+0030f6: KATAKANA LETTER SMALL KE*/
+ RTUNI_ALPHA, /* U+0030f7: KATAKANA LETTER VA*/
+ RTUNI_ALPHA, /* U+0030f8: KATAKANA LETTER VI*/
+ RTUNI_ALPHA, /* U+0030f9: KATAKANA LETTER VE*/
+ RTUNI_ALPHA, /* U+0030fa: KATAKANA LETTER VO*/
+ 0, /* U+0030fb: KATAKANA MIDDLE DOT*/
+ RTUNI_ALPHA, /* U+0030fc: KATAKANA-HIRAGANA PROLONGED SOUND MARK*/
+ RTUNI_ALPHA, /* U+0030fd: KATAKANA ITERATION MARK*/
+ RTUNI_ALPHA, /* U+0030fe: KATAKANA VOICED ITERATION MARK*/
+ RTUNI_ALPHA, /* U+0030ff: KATAKANA DIGRAPH KOTO*/
+ 0, /* U+003100: */
+ 0, /* U+003101: */
+ 0, /* U+003102: */
+ 0, /* U+003103: */
+ 0, /* U+003104: */
+ RTUNI_ALPHA, /* U+003105: BOPOMOFO LETTER B*/
+ RTUNI_ALPHA, /* U+003106: BOPOMOFO LETTER P*/
+ RTUNI_ALPHA, /* U+003107: BOPOMOFO LETTER M*/
+ RTUNI_ALPHA, /* U+003108: BOPOMOFO LETTER F*/
+ RTUNI_ALPHA, /* U+003109: BOPOMOFO LETTER D*/
+ RTUNI_ALPHA, /* U+00310a: BOPOMOFO LETTER T*/
+ RTUNI_ALPHA, /* U+00310b: BOPOMOFO LETTER N*/
+ RTUNI_ALPHA, /* U+00310c: BOPOMOFO LETTER L*/
+ RTUNI_ALPHA, /* U+00310d: BOPOMOFO LETTER G*/
+ RTUNI_ALPHA, /* U+00310e: BOPOMOFO LETTER K*/
+ RTUNI_ALPHA, /* U+00310f: BOPOMOFO LETTER H*/
+ RTUNI_ALPHA, /* U+003110: BOPOMOFO LETTER J*/
+ RTUNI_ALPHA, /* U+003111: BOPOMOFO LETTER Q*/
+ RTUNI_ALPHA, /* U+003112: BOPOMOFO LETTER X*/
+ RTUNI_ALPHA, /* U+003113: BOPOMOFO LETTER ZH*/
+ RTUNI_ALPHA, /* U+003114: BOPOMOFO LETTER CH*/
+ RTUNI_ALPHA, /* U+003115: BOPOMOFO LETTER SH*/
+ RTUNI_ALPHA, /* U+003116: BOPOMOFO LETTER R*/
+ RTUNI_ALPHA, /* U+003117: BOPOMOFO LETTER Z*/
+ RTUNI_ALPHA, /* U+003118: BOPOMOFO LETTER C*/
+ RTUNI_ALPHA, /* U+003119: BOPOMOFO LETTER S*/
+ RTUNI_ALPHA, /* U+00311a: BOPOMOFO LETTER A*/
+ RTUNI_ALPHA, /* U+00311b: BOPOMOFO LETTER O*/
+ RTUNI_ALPHA, /* U+00311c: BOPOMOFO LETTER E*/
+ RTUNI_ALPHA, /* U+00311d: BOPOMOFO LETTER EH*/
+ RTUNI_ALPHA, /* U+00311e: BOPOMOFO LETTER AI*/
+ RTUNI_ALPHA, /* U+00311f: BOPOMOFO LETTER EI*/
+ RTUNI_ALPHA, /* U+003120: BOPOMOFO LETTER AU*/
+ RTUNI_ALPHA, /* U+003121: BOPOMOFO LETTER OU*/
+ RTUNI_ALPHA, /* U+003122: BOPOMOFO LETTER AN*/
+ RTUNI_ALPHA, /* U+003123: BOPOMOFO LETTER EN*/
+ RTUNI_ALPHA, /* U+003124: BOPOMOFO LETTER ANG*/
+ RTUNI_ALPHA, /* U+003125: BOPOMOFO LETTER ENG*/
+ RTUNI_ALPHA, /* U+003126: BOPOMOFO LETTER ER*/
+ RTUNI_ALPHA, /* U+003127: BOPOMOFO LETTER I*/
+ RTUNI_ALPHA, /* U+003128: BOPOMOFO LETTER U*/
+ RTUNI_ALPHA, /* U+003129: BOPOMOFO LETTER IU*/
+ RTUNI_ALPHA, /* U+00312a: BOPOMOFO LETTER V*/
+ RTUNI_ALPHA, /* U+00312b: BOPOMOFO LETTER NG*/
+ RTUNI_ALPHA, /* U+00312c: BOPOMOFO LETTER GN*/
+ RTUNI_ALPHA, /* U+00312d: BOPOMOFO LETTER IH*/
+ 0, /* U+00312e: */
+ 0, /* U+00312f: */
+ 0, /* U+003130: */
+ RTUNI_ALPHA, /* U+003131: HANGUL LETTER KIYEOK*/
+ RTUNI_ALPHA, /* U+003132: HANGUL LETTER SSANGKIYEOK*/
+ RTUNI_ALPHA, /* U+003133: HANGUL LETTER KIYEOK-SIOS*/
+ RTUNI_ALPHA, /* U+003134: HANGUL LETTER NIEUN*/
+ RTUNI_ALPHA, /* U+003135: HANGUL LETTER NIEUN-CIEUC*/
+ RTUNI_ALPHA, /* U+003136: HANGUL LETTER NIEUN-HIEUH*/
+ RTUNI_ALPHA, /* U+003137: HANGUL LETTER TIKEUT*/
+ RTUNI_ALPHA, /* U+003138: HANGUL LETTER SSANGTIKEUT*/
+ RTUNI_ALPHA, /* U+003139: HANGUL LETTER RIEUL*/
+ RTUNI_ALPHA, /* U+00313a: HANGUL LETTER RIEUL-KIYEOK*/
+ RTUNI_ALPHA, /* U+00313b: HANGUL LETTER RIEUL-MIEUM*/
+ RTUNI_ALPHA, /* U+00313c: HANGUL LETTER RIEUL-PIEUP*/
+ RTUNI_ALPHA, /* U+00313d: HANGUL LETTER RIEUL-SIOS*/
+ RTUNI_ALPHA, /* U+00313e: HANGUL LETTER RIEUL-THIEUTH*/
+ RTUNI_ALPHA, /* U+00313f: HANGUL LETTER RIEUL-PHIEUPH*/
+ RTUNI_ALPHA, /* U+003140: HANGUL LETTER RIEUL-HIEUH*/
+ RTUNI_ALPHA, /* U+003141: HANGUL LETTER MIEUM*/
+ RTUNI_ALPHA, /* U+003142: HANGUL LETTER PIEUP*/
+ RTUNI_ALPHA, /* U+003143: HANGUL LETTER SSANGPIEUP*/
+ RTUNI_ALPHA, /* U+003144: HANGUL LETTER PIEUP-SIOS*/
+ RTUNI_ALPHA, /* U+003145: HANGUL LETTER SIOS*/
+ RTUNI_ALPHA, /* U+003146: HANGUL LETTER SSANGSIOS*/
+ RTUNI_ALPHA, /* U+003147: HANGUL LETTER IEUNG*/
+ RTUNI_ALPHA, /* U+003148: HANGUL LETTER CIEUC*/
+ RTUNI_ALPHA, /* U+003149: HANGUL LETTER SSANGCIEUC*/
+ RTUNI_ALPHA, /* U+00314a: HANGUL LETTER CHIEUCH*/
+ RTUNI_ALPHA, /* U+00314b: HANGUL LETTER KHIEUKH*/
+ RTUNI_ALPHA, /* U+00314c: HANGUL LETTER THIEUTH*/
+ RTUNI_ALPHA, /* U+00314d: HANGUL LETTER PHIEUPH*/
+ RTUNI_ALPHA, /* U+00314e: HANGUL LETTER HIEUH*/
+ RTUNI_ALPHA, /* U+00314f: HANGUL LETTER A*/
+ RTUNI_ALPHA, /* U+003150: HANGUL LETTER AE*/
+ RTUNI_ALPHA, /* U+003151: HANGUL LETTER YA*/
+ RTUNI_ALPHA, /* U+003152: HANGUL LETTER YAE*/
+ RTUNI_ALPHA, /* U+003153: HANGUL LETTER EO*/
+ RTUNI_ALPHA, /* U+003154: HANGUL LETTER E*/
+ RTUNI_ALPHA, /* U+003155: HANGUL LETTER YEO*/
+ RTUNI_ALPHA, /* U+003156: HANGUL LETTER YE*/
+ RTUNI_ALPHA, /* U+003157: HANGUL LETTER O*/
+ RTUNI_ALPHA, /* U+003158: HANGUL LETTER WA*/
+ RTUNI_ALPHA, /* U+003159: HANGUL LETTER WAE*/
+ RTUNI_ALPHA, /* U+00315a: HANGUL LETTER OE*/
+ RTUNI_ALPHA, /* U+00315b: HANGUL LETTER YO*/
+ RTUNI_ALPHA, /* U+00315c: HANGUL LETTER U*/
+ RTUNI_ALPHA, /* U+00315d: HANGUL LETTER WEO*/
+ RTUNI_ALPHA, /* U+00315e: HANGUL LETTER WE*/
+ RTUNI_ALPHA, /* U+00315f: HANGUL LETTER WI*/
+ RTUNI_ALPHA, /* U+003160: HANGUL LETTER YU*/
+ RTUNI_ALPHA, /* U+003161: HANGUL LETTER EU*/
+ RTUNI_ALPHA, /* U+003162: HANGUL LETTER YI*/
+ RTUNI_ALPHA, /* U+003163: HANGUL LETTER I*/
+ RTUNI_ALPHA, /* U+003164: HANGUL FILLER*/
+ RTUNI_ALPHA, /* U+003165: HANGUL LETTER SSANGNIEUN*/
+ RTUNI_ALPHA, /* U+003166: HANGUL LETTER NIEUN-TIKEUT*/
+ RTUNI_ALPHA, /* U+003167: HANGUL LETTER NIEUN-SIOS*/
+ RTUNI_ALPHA, /* U+003168: HANGUL LETTER NIEUN-PANSIOS*/
+ RTUNI_ALPHA, /* U+003169: HANGUL LETTER RIEUL-KIYEOK-SIOS*/
+ RTUNI_ALPHA, /* U+00316a: HANGUL LETTER RIEUL-TIKEUT*/
+ RTUNI_ALPHA, /* U+00316b: HANGUL LETTER RIEUL-PIEUP-SIOS*/
+ RTUNI_ALPHA, /* U+00316c: HANGUL LETTER RIEUL-PANSIOS*/
+ RTUNI_ALPHA, /* U+00316d: HANGUL LETTER RIEUL-YEORINHIEUH*/
+ RTUNI_ALPHA, /* U+00316e: HANGUL LETTER MIEUM-PIEUP*/
+ RTUNI_ALPHA, /* U+00316f: HANGUL LETTER MIEUM-SIOS*/
+ RTUNI_ALPHA, /* U+003170: HANGUL LETTER MIEUM-PANSIOS*/
+ RTUNI_ALPHA, /* U+003171: HANGUL LETTER KAPYEOUNMIEUM*/
+ RTUNI_ALPHA, /* U+003172: HANGUL LETTER PIEUP-KIYEOK*/
+ RTUNI_ALPHA, /* U+003173: HANGUL LETTER PIEUP-TIKEUT*/
+ RTUNI_ALPHA, /* U+003174: HANGUL LETTER PIEUP-SIOS-KIYEOK*/
+ RTUNI_ALPHA, /* U+003175: HANGUL LETTER PIEUP-SIOS-TIKEUT*/
+ RTUNI_ALPHA, /* U+003176: HANGUL LETTER PIEUP-CIEUC*/
+ RTUNI_ALPHA, /* U+003177: HANGUL LETTER PIEUP-THIEUTH*/
+ RTUNI_ALPHA, /* U+003178: HANGUL LETTER KAPYEOUNPIEUP*/
+ RTUNI_ALPHA, /* U+003179: HANGUL LETTER KAPYEOUNSSANGPIEUP*/
+ RTUNI_ALPHA, /* U+00317a: HANGUL LETTER SIOS-KIYEOK*/
+ RTUNI_ALPHA, /* U+00317b: HANGUL LETTER SIOS-NIEUN*/
+ RTUNI_ALPHA, /* U+00317c: HANGUL LETTER SIOS-TIKEUT*/
+ RTUNI_ALPHA, /* U+00317d: HANGUL LETTER SIOS-PIEUP*/
+ RTUNI_ALPHA, /* U+00317e: HANGUL LETTER SIOS-CIEUC*/
+ RTUNI_ALPHA, /* U+00317f: HANGUL LETTER PANSIOS*/
+ RTUNI_ALPHA, /* U+003180: HANGUL LETTER SSANGIEUNG*/
+ RTUNI_ALPHA, /* U+003181: HANGUL LETTER YESIEUNG*/
+ RTUNI_ALPHA, /* U+003182: HANGUL LETTER YESIEUNG-SIOS*/
+ RTUNI_ALPHA, /* U+003183: HANGUL LETTER YESIEUNG-PANSIOS*/
+ RTUNI_ALPHA, /* U+003184: HANGUL LETTER KAPYEOUNPHIEUPH*/
+ RTUNI_ALPHA, /* U+003185: HANGUL LETTER SSANGHIEUH*/
+ RTUNI_ALPHA, /* U+003186: HANGUL LETTER YEORINHIEUH*/
+ RTUNI_ALPHA, /* U+003187: HANGUL LETTER YO-YA*/
+ RTUNI_ALPHA, /* U+003188: HANGUL LETTER YO-YAE*/
+ RTUNI_ALPHA, /* U+003189: HANGUL LETTER YO-I*/
+ RTUNI_ALPHA, /* U+00318a: HANGUL LETTER YU-YEO*/
+ RTUNI_ALPHA, /* U+00318b: HANGUL LETTER YU-YE*/
+ RTUNI_ALPHA, /* U+00318c: HANGUL LETTER YU-I*/
+ RTUNI_ALPHA, /* U+00318d: HANGUL LETTER ARAEA*/
+ RTUNI_ALPHA, /* U+00318e: HANGUL LETTER ARAEAE*/
+ 0, /* U+00318f: */
+ 0, /* U+003190: IDEOGRAPHIC ANNOTATION LINKING MARK*/
+ 0, /* U+003191: IDEOGRAPHIC ANNOTATION REVERSE MARK*/
+ 0, /* U+003192: IDEOGRAPHIC ANNOTATION ONE MARK*/
+ 0, /* U+003193: IDEOGRAPHIC ANNOTATION TWO MARK*/
+ 0, /* U+003194: IDEOGRAPHIC ANNOTATION THREE MARK*/
+ 0, /* U+003195: IDEOGRAPHIC ANNOTATION FOUR MARK*/
+ 0, /* U+003196: IDEOGRAPHIC ANNOTATION TOP MARK*/
+ 0, /* U+003197: IDEOGRAPHIC ANNOTATION MIDDLE MARK*/
+ 0, /* U+003198: IDEOGRAPHIC ANNOTATION BOTTOM MARK*/
+ 0, /* U+003199: IDEOGRAPHIC ANNOTATION FIRST MARK*/
+ 0, /* U+00319a: IDEOGRAPHIC ANNOTATION SECOND MARK*/
+ 0, /* U+00319b: IDEOGRAPHIC ANNOTATION THIRD MARK*/
+ 0, /* U+00319c: IDEOGRAPHIC ANNOTATION FOURTH MARK*/
+ 0, /* U+00319d: IDEOGRAPHIC ANNOTATION HEAVEN MARK*/
+ 0, /* U+00319e: IDEOGRAPHIC ANNOTATION EARTH MARK*/
+ 0, /* U+00319f: IDEOGRAPHIC ANNOTATION MAN MARK*/
+ RTUNI_ALPHA, /* U+0031a0: BOPOMOFO LETTER BU*/
+ RTUNI_ALPHA, /* U+0031a1: BOPOMOFO LETTER ZI*/
+ RTUNI_ALPHA, /* U+0031a2: BOPOMOFO LETTER JI*/
+ RTUNI_ALPHA, /* U+0031a3: BOPOMOFO LETTER GU*/
+ RTUNI_ALPHA, /* U+0031a4: BOPOMOFO LETTER EE*/
+ RTUNI_ALPHA, /* U+0031a5: BOPOMOFO LETTER ENN*/
+ RTUNI_ALPHA, /* U+0031a6: BOPOMOFO LETTER OO*/
+ RTUNI_ALPHA, /* U+0031a7: BOPOMOFO LETTER ONN*/
+ RTUNI_ALPHA, /* U+0031a8: BOPOMOFO LETTER IR*/
+ RTUNI_ALPHA, /* U+0031a9: BOPOMOFO LETTER ANN*/
+ RTUNI_ALPHA, /* U+0031aa: BOPOMOFO LETTER INN*/
+ RTUNI_ALPHA, /* U+0031ab: BOPOMOFO LETTER UNN*/
+ RTUNI_ALPHA, /* U+0031ac: BOPOMOFO LETTER IM*/
+ RTUNI_ALPHA, /* U+0031ad: BOPOMOFO LETTER NGG*/
+ RTUNI_ALPHA, /* U+0031ae: BOPOMOFO LETTER AINN*/
+ RTUNI_ALPHA, /* U+0031af: BOPOMOFO LETTER AUNN*/
+ RTUNI_ALPHA, /* U+0031b0: BOPOMOFO LETTER AM*/
+ RTUNI_ALPHA, /* U+0031b1: BOPOMOFO LETTER OM*/
+ RTUNI_ALPHA, /* U+0031b2: BOPOMOFO LETTER ONG*/
+ RTUNI_ALPHA, /* U+0031b3: BOPOMOFO LETTER INNN*/
+ RTUNI_ALPHA, /* U+0031b4: BOPOMOFO FINAL LETTER P*/
+ RTUNI_ALPHA, /* U+0031b5: BOPOMOFO FINAL LETTER T*/
+ RTUNI_ALPHA, /* U+0031b6: BOPOMOFO FINAL LETTER K*/
+ RTUNI_ALPHA, /* U+0031b7: BOPOMOFO FINAL LETTER H*/
+ RTUNI_ALPHA, /* U+0031b8: BOPOMOFO LETTER GH*/
+ RTUNI_ALPHA, /* U+0031b9: BOPOMOFO LETTER LH*/
+ RTUNI_ALPHA, /* U+0031ba: BOPOMOFO LETTER ZY*/
+ 0, /* U+0031bb: */
+ 0, /* U+0031bc: */
+ 0, /* U+0031bd: */
+ 0, /* U+0031be: */
+ 0, /* U+0031bf: */
+ 0, /* U+0031c0: CJK STROKE T*/
+ 0, /* U+0031c1: CJK STROKE WG*/
+ 0, /* U+0031c2: CJK STROKE XG*/
+ 0, /* U+0031c3: CJK STROKE BXG*/
+ 0, /* U+0031c4: CJK STROKE SW*/
+ 0, /* U+0031c5: CJK STROKE HZZ*/
+ 0, /* U+0031c6: CJK STROKE HZG*/
+ 0, /* U+0031c7: CJK STROKE HP*/
+ 0, /* U+0031c8: CJK STROKE HZWG*/
+ 0, /* U+0031c9: CJK STROKE SZWG*/
+ 0, /* U+0031ca: CJK STROKE HZT*/
+ 0, /* U+0031cb: CJK STROKE HZZP*/
+ 0, /* U+0031cc: CJK STROKE HPWG*/
+ 0, /* U+0031cd: CJK STROKE HZW*/
+ 0, /* U+0031ce: CJK STROKE HZZZ*/
+ 0, /* U+0031cf: CJK STROKE N*/
+ 0, /* U+0031d0: CJK STROKE H*/
+ 0, /* U+0031d1: CJK STROKE S*/
+ 0, /* U+0031d2: CJK STROKE P*/
+ 0, /* U+0031d3: CJK STROKE SP*/
+ 0, /* U+0031d4: CJK STROKE D*/
+ 0, /* U+0031d5: CJK STROKE HZ*/
+ 0, /* U+0031d6: CJK STROKE HG*/
+ 0, /* U+0031d7: CJK STROKE SZ*/
+ 0, /* U+0031d8: CJK STROKE SWZ*/
+ 0, /* U+0031d9: CJK STROKE ST*/
+ 0, /* U+0031da: CJK STROKE SG*/
+ 0, /* U+0031db: CJK STROKE PD*/
+ 0, /* U+0031dc: CJK STROKE PZ*/
+ 0, /* U+0031dd: CJK STROKE TN*/
+ 0, /* U+0031de: CJK STROKE SZZ*/
+ 0, /* U+0031df: CJK STROKE SWG*/
+ 0, /* U+0031e0: CJK STROKE HXWG*/
+ 0, /* U+0031e1: CJK STROKE HZZZG*/
+ 0, /* U+0031e2: CJK STROKE PG*/
+ 0, /* U+0031e3: CJK STROKE Q*/
+ 0, /* U+0031e4: */
+ 0, /* U+0031e5: */
+ 0, /* U+0031e6: */
+ 0, /* U+0031e7: */
+ 0, /* U+0031e8: */
+ 0, /* U+0031e9: */
+ 0, /* U+0031ea: */
+ 0, /* U+0031eb: */
+ 0, /* U+0031ec: */
+ 0, /* U+0031ed: */
+ 0, /* U+0031ee: */
+ 0, /* U+0031ef: */
+ RTUNI_ALPHA, /* U+0031f0: KATAKANA LETTER SMALL KU*/
+ RTUNI_ALPHA, /* U+0031f1: KATAKANA LETTER SMALL SI*/
+ RTUNI_ALPHA, /* U+0031f2: KATAKANA LETTER SMALL SU*/
+ RTUNI_ALPHA, /* U+0031f3: KATAKANA LETTER SMALL TO*/
+ RTUNI_ALPHA, /* U+0031f4: KATAKANA LETTER SMALL NU*/
+ RTUNI_ALPHA, /* U+0031f5: KATAKANA LETTER SMALL HA*/
+ RTUNI_ALPHA, /* U+0031f6: KATAKANA LETTER SMALL HI*/
+ RTUNI_ALPHA, /* U+0031f7: KATAKANA LETTER SMALL HU*/
+ RTUNI_ALPHA, /* U+0031f8: KATAKANA LETTER SMALL HE*/
+ RTUNI_ALPHA, /* U+0031f9: KATAKANA LETTER SMALL HO*/
+ RTUNI_ALPHA, /* U+0031fa: KATAKANA LETTER SMALL MU*/
+ RTUNI_ALPHA, /* U+0031fb: KATAKANA LETTER SMALL RA*/
+ RTUNI_ALPHA, /* U+0031fc: KATAKANA LETTER SMALL RI*/
+ RTUNI_ALPHA, /* U+0031fd: KATAKANA LETTER SMALL RU*/
+ RTUNI_ALPHA, /* U+0031fe: KATAKANA LETTER SMALL RE*/
+ RTUNI_ALPHA, /* U+0031ff: KATAKANA LETTER SMALL RO*/
+ 0, /* U+003200: PARENTHESIZED HANGUL KIYEOK*/
+ 0, /* U+003201: PARENTHESIZED HANGUL NIEUN*/
+ 0, /* U+003202: PARENTHESIZED HANGUL TIKEUT*/
+ 0, /* U+003203: PARENTHESIZED HANGUL RIEUL*/
+ 0, /* U+003204: PARENTHESIZED HANGUL MIEUM*/
+ 0, /* U+003205: PARENTHESIZED HANGUL PIEUP*/
+ 0, /* U+003206: PARENTHESIZED HANGUL SIOS*/
+ 0, /* U+003207: PARENTHESIZED HANGUL IEUNG*/
+ 0, /* U+003208: PARENTHESIZED HANGUL CIEUC*/
+ 0, /* U+003209: PARENTHESIZED HANGUL CHIEUCH*/
+ 0, /* U+00320a: PARENTHESIZED HANGUL KHIEUKH*/
+ 0, /* U+00320b: PARENTHESIZED HANGUL THIEUTH*/
+ 0, /* U+00320c: PARENTHESIZED HANGUL PHIEUPH*/
+ 0, /* U+00320d: PARENTHESIZED HANGUL HIEUH*/
+ 0, /* U+00320e: PARENTHESIZED HANGUL KIYEOK A*/
+ 0, /* U+00320f: PARENTHESIZED HANGUL NIEUN A*/
+ 0, /* U+003210: PARENTHESIZED HANGUL TIKEUT A*/
+ 0, /* U+003211: PARENTHESIZED HANGUL RIEUL A*/
+ 0, /* U+003212: PARENTHESIZED HANGUL MIEUM A*/
+ 0, /* U+003213: PARENTHESIZED HANGUL PIEUP A*/
+ 0, /* U+003214: PARENTHESIZED HANGUL SIOS A*/
+ 0, /* U+003215: PARENTHESIZED HANGUL IEUNG A*/
+ 0, /* U+003216: PARENTHESIZED HANGUL CIEUC A*/
+ 0, /* U+003217: PARENTHESIZED HANGUL CHIEUCH A*/
+ 0, /* U+003218: PARENTHESIZED HANGUL KHIEUKH A*/
+ 0, /* U+003219: PARENTHESIZED HANGUL THIEUTH A*/
+ 0, /* U+00321a: PARENTHESIZED HANGUL PHIEUPH A*/
+ 0, /* U+00321b: PARENTHESIZED HANGUL HIEUH A*/
+ 0, /* U+00321c: PARENTHESIZED HANGUL CIEUC U*/
+ 0, /* U+00321d: PARENTHESIZED KOREAN CHARACTER OJEON*/
+ 0, /* U+00321e: PARENTHESIZED KOREAN CHARACTER O HU*/
+ 0, /* U+00321f: */
+ 0, /* U+003220: PARENTHESIZED IDEOGRAPH ONE*/
+ 0, /* U+003221: PARENTHESIZED IDEOGRAPH TWO*/
+ 0, /* U+003222: PARENTHESIZED IDEOGRAPH THREE*/
+ 0, /* U+003223: PARENTHESIZED IDEOGRAPH FOUR*/
+ 0, /* U+003224: PARENTHESIZED IDEOGRAPH FIVE*/
+ 0, /* U+003225: PARENTHESIZED IDEOGRAPH SIX*/
+ 0, /* U+003226: PARENTHESIZED IDEOGRAPH SEVEN*/
+ 0, /* U+003227: PARENTHESIZED IDEOGRAPH EIGHT*/
+ 0, /* U+003228: PARENTHESIZED IDEOGRAPH NINE*/
+ 0, /* U+003229: PARENTHESIZED IDEOGRAPH TEN*/
+ 0, /* U+00322a: PARENTHESIZED IDEOGRAPH MOON*/
+ 0, /* U+00322b: PARENTHESIZED IDEOGRAPH FIRE*/
+ 0, /* U+00322c: PARENTHESIZED IDEOGRAPH WATER*/
+ 0, /* U+00322d: PARENTHESIZED IDEOGRAPH WOOD*/
+ 0, /* U+00322e: PARENTHESIZED IDEOGRAPH METAL*/
+ 0, /* U+00322f: PARENTHESIZED IDEOGRAPH EARTH*/
+ 0, /* U+003230: PARENTHESIZED IDEOGRAPH SUN*/
+ 0, /* U+003231: PARENTHESIZED IDEOGRAPH STOCK*/
+ 0, /* U+003232: PARENTHESIZED IDEOGRAPH HAVE*/
+ 0, /* U+003233: PARENTHESIZED IDEOGRAPH SOCIETY*/
+ 0, /* U+003234: PARENTHESIZED IDEOGRAPH NAME*/
+ 0, /* U+003235: PARENTHESIZED IDEOGRAPH SPECIAL*/
+ 0, /* U+003236: PARENTHESIZED IDEOGRAPH FINANCIAL*/
+ 0, /* U+003237: PARENTHESIZED IDEOGRAPH CONGRATULATION*/
+ 0, /* U+003238: PARENTHESIZED IDEOGRAPH LABOR*/
+ 0, /* U+003239: PARENTHESIZED IDEOGRAPH REPRESENT*/
+ 0, /* U+00323a: PARENTHESIZED IDEOGRAPH CALL*/
+ 0, /* U+00323b: PARENTHESIZED IDEOGRAPH STUDY*/
+ 0, /* U+00323c: PARENTHESIZED IDEOGRAPH SUPERVISE*/
+ 0, /* U+00323d: PARENTHESIZED IDEOGRAPH ENTERPRISE*/
+ 0, /* U+00323e: PARENTHESIZED IDEOGRAPH RESOURCE*/
+ 0, /* U+00323f: PARENTHESIZED IDEOGRAPH ALLIANCE*/
+ 0, /* U+003240: PARENTHESIZED IDEOGRAPH FESTIVAL*/
+ 0, /* U+003241: PARENTHESIZED IDEOGRAPH REST*/
+ 0, /* U+003242: PARENTHESIZED IDEOGRAPH SELF*/
+ 0, /* U+003243: PARENTHESIZED IDEOGRAPH REACH*/
+ 0, /* U+003244: CIRCLED IDEOGRAPH QUESTION*/
+ 0, /* U+003245: CIRCLED IDEOGRAPH KINDERGARTEN*/
+ 0, /* U+003246: CIRCLED IDEOGRAPH SCHOOL*/
+ 0, /* U+003247: CIRCLED IDEOGRAPH KOTO*/
+ 0, /* U+003248: CIRCLED NUMBER TEN ON BLACK SQUARE*/
+ 0, /* U+003249: CIRCLED NUMBER TWENTY ON BLACK SQUARE*/
+ 0, /* U+00324a: CIRCLED NUMBER THIRTY ON BLACK SQUARE*/
+ 0, /* U+00324b: CIRCLED NUMBER FORTY ON BLACK SQUARE*/
+ 0, /* U+00324c: CIRCLED NUMBER FIFTY ON BLACK SQUARE*/
+ 0, /* U+00324d: CIRCLED NUMBER SIXTY ON BLACK SQUARE*/
+ 0, /* U+00324e: CIRCLED NUMBER SEVENTY ON BLACK SQUARE*/
+ 0, /* U+00324f: CIRCLED NUMBER EIGHTY ON BLACK SQUARE*/
+ 0, /* U+003250: PARTNERSHIP SIGN*/
+ 0, /* U+003251: CIRCLED NUMBER TWENTY ONE*/
+ 0, /* U+003252: CIRCLED NUMBER TWENTY TWO*/
+ 0, /* U+003253: CIRCLED NUMBER TWENTY THREE*/
+ 0, /* U+003254: CIRCLED NUMBER TWENTY FOUR*/
+ 0, /* U+003255: CIRCLED NUMBER TWENTY FIVE*/
+ 0, /* U+003256: CIRCLED NUMBER TWENTY SIX*/
+ 0, /* U+003257: CIRCLED NUMBER TWENTY SEVEN*/
+ 0, /* U+003258: CIRCLED NUMBER TWENTY EIGHT*/
+ 0, /* U+003259: CIRCLED NUMBER TWENTY NINE*/
+ 0, /* U+00325a: CIRCLED NUMBER THIRTY*/
+ 0, /* U+00325b: CIRCLED NUMBER THIRTY ONE*/
+ 0, /* U+00325c: CIRCLED NUMBER THIRTY TWO*/
+ 0, /* U+00325d: CIRCLED NUMBER THIRTY THREE*/
+ 0, /* U+00325e: CIRCLED NUMBER THIRTY FOUR*/
+ 0, /* U+00325f: CIRCLED NUMBER THIRTY FIVE*/
+ 0, /* U+003260: CIRCLED HANGUL KIYEOK*/
+ 0, /* U+003261: CIRCLED HANGUL NIEUN*/
+ 0, /* U+003262: CIRCLED HANGUL TIKEUT*/
+ 0, /* U+003263: CIRCLED HANGUL RIEUL*/
+ 0, /* U+003264: CIRCLED HANGUL MIEUM*/
+ 0, /* U+003265: CIRCLED HANGUL PIEUP*/
+ 0, /* U+003266: CIRCLED HANGUL SIOS*/
+ 0, /* U+003267: CIRCLED HANGUL IEUNG*/
+ 0, /* U+003268: CIRCLED HANGUL CIEUC*/
+ 0, /* U+003269: CIRCLED HANGUL CHIEUCH*/
+ 0, /* U+00326a: CIRCLED HANGUL KHIEUKH*/
+ 0, /* U+00326b: CIRCLED HANGUL THIEUTH*/
+ 0, /* U+00326c: CIRCLED HANGUL PHIEUPH*/
+ 0, /* U+00326d: CIRCLED HANGUL HIEUH*/
+ 0, /* U+00326e: CIRCLED HANGUL KIYEOK A*/
+ 0, /* U+00326f: CIRCLED HANGUL NIEUN A*/
+ 0, /* U+003270: CIRCLED HANGUL TIKEUT A*/
+ 0, /* U+003271: CIRCLED HANGUL RIEUL A*/
+ 0, /* U+003272: CIRCLED HANGUL MIEUM A*/
+ 0, /* U+003273: CIRCLED HANGUL PIEUP A*/
+ 0, /* U+003274: CIRCLED HANGUL SIOS A*/
+ 0, /* U+003275: CIRCLED HANGUL IEUNG A*/
+ 0, /* U+003276: CIRCLED HANGUL CIEUC A*/
+ 0, /* U+003277: CIRCLED HANGUL CHIEUCH A*/
+ 0, /* U+003278: CIRCLED HANGUL KHIEUKH A*/
+ 0, /* U+003279: CIRCLED HANGUL THIEUTH A*/
+ 0, /* U+00327a: CIRCLED HANGUL PHIEUPH A*/
+ 0, /* U+00327b: CIRCLED HANGUL HIEUH A*/
+ 0, /* U+00327c: CIRCLED KOREAN CHARACTER CHAMKO*/
+ 0, /* U+00327d: CIRCLED KOREAN CHARACTER JUEUI*/
+ 0, /* U+00327e: CIRCLED HANGUL IEUNG U*/
+ 0, /* U+00327f: KOREAN STANDARD SYMBOL*/
+ 0, /* U+003280: CIRCLED IDEOGRAPH ONE*/
+ 0, /* U+003281: CIRCLED IDEOGRAPH TWO*/
+ 0, /* U+003282: CIRCLED IDEOGRAPH THREE*/
+ 0, /* U+003283: CIRCLED IDEOGRAPH FOUR*/
+ 0, /* U+003284: CIRCLED IDEOGRAPH FIVE*/
+ 0, /* U+003285: CIRCLED IDEOGRAPH SIX*/
+ 0, /* U+003286: CIRCLED IDEOGRAPH SEVEN*/
+ 0, /* U+003287: CIRCLED IDEOGRAPH EIGHT*/
+ 0, /* U+003288: CIRCLED IDEOGRAPH NINE*/
+ 0, /* U+003289: CIRCLED IDEOGRAPH TEN*/
+ 0, /* U+00328a: CIRCLED IDEOGRAPH MOON*/
+ 0, /* U+00328b: CIRCLED IDEOGRAPH FIRE*/
+ 0, /* U+00328c: CIRCLED IDEOGRAPH WATER*/
+ 0, /* U+00328d: CIRCLED IDEOGRAPH WOOD*/
+ 0, /* U+00328e: CIRCLED IDEOGRAPH METAL*/
+ 0, /* U+00328f: CIRCLED IDEOGRAPH EARTH*/
+ 0, /* U+003290: CIRCLED IDEOGRAPH SUN*/
+ 0, /* U+003291: CIRCLED IDEOGRAPH STOCK*/
+ 0, /* U+003292: CIRCLED IDEOGRAPH HAVE*/
+ 0, /* U+003293: CIRCLED IDEOGRAPH SOCIETY*/
+ 0, /* U+003294: CIRCLED IDEOGRAPH NAME*/
+ 0, /* U+003295: CIRCLED IDEOGRAPH SPECIAL*/
+ 0, /* U+003296: CIRCLED IDEOGRAPH FINANCIAL*/
+ 0, /* U+003297: CIRCLED IDEOGRAPH CONGRATULATION*/
+ 0, /* U+003298: CIRCLED IDEOGRAPH LABOR*/
+ 0, /* U+003299: CIRCLED IDEOGRAPH SECRET*/
+ 0, /* U+00329a: CIRCLED IDEOGRAPH MALE*/
+ 0, /* U+00329b: CIRCLED IDEOGRAPH FEMALE*/
+ 0, /* U+00329c: CIRCLED IDEOGRAPH SUITABLE*/
+ 0, /* U+00329d: CIRCLED IDEOGRAPH EXCELLENT*/
+ 0, /* U+00329e: CIRCLED IDEOGRAPH PRINT*/
+ 0, /* U+00329f: CIRCLED IDEOGRAPH ATTENTION*/
+ 0, /* U+0032a0: CIRCLED IDEOGRAPH ITEM*/
+ 0, /* U+0032a1: CIRCLED IDEOGRAPH REST*/
+ 0, /* U+0032a2: CIRCLED IDEOGRAPH COPY*/
+ 0, /* U+0032a3: CIRCLED IDEOGRAPH CORRECT*/
+ 0, /* U+0032a4: CIRCLED IDEOGRAPH HIGH*/
+ 0, /* U+0032a5: CIRCLED IDEOGRAPH CENTRE*/
+ 0, /* U+0032a6: CIRCLED IDEOGRAPH LOW*/
+ 0, /* U+0032a7: CIRCLED IDEOGRAPH LEFT*/
+ 0, /* U+0032a8: CIRCLED IDEOGRAPH RIGHT*/
+ 0, /* U+0032a9: CIRCLED IDEOGRAPH MEDICINE*/
+ 0, /* U+0032aa: CIRCLED IDEOGRAPH RELIGION*/
+ 0, /* U+0032ab: CIRCLED IDEOGRAPH STUDY*/
+ 0, /* U+0032ac: CIRCLED IDEOGRAPH SUPERVISE*/
+ 0, /* U+0032ad: CIRCLED IDEOGRAPH ENTERPRISE*/
+ 0, /* U+0032ae: CIRCLED IDEOGRAPH RESOURCE*/
+ 0, /* U+0032af: CIRCLED IDEOGRAPH ALLIANCE*/
+ 0, /* U+0032b0: CIRCLED IDEOGRAPH NIGHT*/
+ 0, /* U+0032b1: CIRCLED NUMBER THIRTY SIX*/
+ 0, /* U+0032b2: CIRCLED NUMBER THIRTY SEVEN*/
+ 0, /* U+0032b3: CIRCLED NUMBER THIRTY EIGHT*/
+ 0, /* U+0032b4: CIRCLED NUMBER THIRTY NINE*/
+ 0, /* U+0032b5: CIRCLED NUMBER FORTY*/
+ 0, /* U+0032b6: CIRCLED NUMBER FORTY ONE*/
+ 0, /* U+0032b7: CIRCLED NUMBER FORTY TWO*/
+ 0, /* U+0032b8: CIRCLED NUMBER FORTY THREE*/
+ 0, /* U+0032b9: CIRCLED NUMBER FORTY FOUR*/
+ 0, /* U+0032ba: CIRCLED NUMBER FORTY FIVE*/
+ 0, /* U+0032bb: CIRCLED NUMBER FORTY SIX*/
+ 0, /* U+0032bc: CIRCLED NUMBER FORTY SEVEN*/
+ 0, /* U+0032bd: CIRCLED NUMBER FORTY EIGHT*/
+ 0, /* U+0032be: CIRCLED NUMBER FORTY NINE*/
+ 0, /* U+0032bf: CIRCLED NUMBER FIFTY*/
+ 0, /* U+0032c0: IDEOGRAPHIC TELEGRAPH SYMBOL FOR JANUARY*/
+ 0, /* U+0032c1: IDEOGRAPHIC TELEGRAPH SYMBOL FOR FEBRUARY*/
+ 0, /* U+0032c2: IDEOGRAPHIC TELEGRAPH SYMBOL FOR MARCH*/
+ 0, /* U+0032c3: IDEOGRAPHIC TELEGRAPH SYMBOL FOR APRIL*/
+ 0, /* U+0032c4: IDEOGRAPHIC TELEGRAPH SYMBOL FOR MAY*/
+ 0, /* U+0032c5: IDEOGRAPHIC TELEGRAPH SYMBOL FOR JUNE*/
+ 0, /* U+0032c6: IDEOGRAPHIC TELEGRAPH SYMBOL FOR JULY*/
+ 0, /* U+0032c7: IDEOGRAPHIC TELEGRAPH SYMBOL FOR AUGUST*/
+ 0, /* U+0032c8: IDEOGRAPHIC TELEGRAPH SYMBOL FOR SEPTEMBER*/
+ 0, /* U+0032c9: IDEOGRAPHIC TELEGRAPH SYMBOL FOR OCTOBER*/
+ 0, /* U+0032ca: IDEOGRAPHIC TELEGRAPH SYMBOL FOR NOVEMBER*/
+ 0, /* U+0032cb: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DECEMBER*/
+ 0, /* U+0032cc: SQUARE HG*/
+ 0, /* U+0032cd: SQUARE ERG*/
+ 0, /* U+0032ce: SQUARE EV*/
+ 0, /* U+0032cf: LIMITED LIABILITY SIGN*/
+ 0, /* U+0032d0: CIRCLED KATAKANA A*/
+ 0, /* U+0032d1: CIRCLED KATAKANA I*/
+ 0, /* U+0032d2: CIRCLED KATAKANA U*/
+ 0, /* U+0032d3: CIRCLED KATAKANA E*/
+ 0, /* U+0032d4: CIRCLED KATAKANA O*/
+ 0, /* U+0032d5: CIRCLED KATAKANA KA*/
+ 0, /* U+0032d6: CIRCLED KATAKANA KI*/
+ 0, /* U+0032d7: CIRCLED KATAKANA KU*/
+ 0, /* U+0032d8: CIRCLED KATAKANA KE*/
+ 0, /* U+0032d9: CIRCLED KATAKANA KO*/
+ 0, /* U+0032da: CIRCLED KATAKANA SA*/
+ 0, /* U+0032db: CIRCLED KATAKANA SI*/
+ 0, /* U+0032dc: CIRCLED KATAKANA SU*/
+ 0, /* U+0032dd: CIRCLED KATAKANA SE*/
+ 0, /* U+0032de: CIRCLED KATAKANA SO*/
+ 0, /* U+0032df: CIRCLED KATAKANA TA*/
+ 0, /* U+0032e0: CIRCLED KATAKANA TI*/
+ 0, /* U+0032e1: CIRCLED KATAKANA TU*/
+ 0, /* U+0032e2: CIRCLED KATAKANA TE*/
+ 0, /* U+0032e3: CIRCLED KATAKANA TO*/
+ 0, /* U+0032e4: CIRCLED KATAKANA NA*/
+ 0, /* U+0032e5: CIRCLED KATAKANA NI*/
+ 0, /* U+0032e6: CIRCLED KATAKANA NU*/
+ 0, /* U+0032e7: CIRCLED KATAKANA NE*/
+ 0, /* U+0032e8: CIRCLED KATAKANA NO*/
+ 0, /* U+0032e9: CIRCLED KATAKANA HA*/
+ 0, /* U+0032ea: CIRCLED KATAKANA HI*/
+ 0, /* U+0032eb: CIRCLED KATAKANA HU*/
+ 0, /* U+0032ec: CIRCLED KATAKANA HE*/
+ 0, /* U+0032ed: CIRCLED KATAKANA HO*/
+ 0, /* U+0032ee: CIRCLED KATAKANA MA*/
+ 0, /* U+0032ef: CIRCLED KATAKANA MI*/
+ 0, /* U+0032f0: CIRCLED KATAKANA MU*/
+ 0, /* U+0032f1: CIRCLED KATAKANA ME*/
+ 0, /* U+0032f2: CIRCLED KATAKANA MO*/
+ 0, /* U+0032f3: CIRCLED KATAKANA YA*/
+ 0, /* U+0032f4: CIRCLED KATAKANA YU*/
+ 0, /* U+0032f5: CIRCLED KATAKANA YO*/
+ 0, /* U+0032f6: CIRCLED KATAKANA RA*/
+ 0, /* U+0032f7: CIRCLED KATAKANA RI*/
+ 0, /* U+0032f8: CIRCLED KATAKANA RU*/
+ 0, /* U+0032f9: CIRCLED KATAKANA RE*/
+ 0, /* U+0032fa: CIRCLED KATAKANA RO*/
+ 0, /* U+0032fb: CIRCLED KATAKANA WA*/
+ 0, /* U+0032fc: CIRCLED KATAKANA WI*/
+ 0, /* U+0032fd: CIRCLED KATAKANA WE*/
+ 0, /* U+0032fe: CIRCLED KATAKANA WO*/
+ 0, /* U+0032ff: */
+ 0, /* U+003300: SQUARE APAATO*/
+ 0, /* U+003301: SQUARE ARUHUA*/
+ 0, /* U+003302: SQUARE ANPEA*/
+ 0, /* U+003303: SQUARE AARU*/
+ 0, /* U+003304: SQUARE ININGU*/
+ 0, /* U+003305: SQUARE INTI*/
+ 0, /* U+003306: SQUARE UON*/
+ 0, /* U+003307: SQUARE ESUKUUDO*/
+ 0, /* U+003308: SQUARE EEKAA*/
+ 0, /* U+003309: SQUARE ONSU*/
+ 0, /* U+00330a: SQUARE OOMU*/
+ 0, /* U+00330b: SQUARE KAIRI*/
+ 0, /* U+00330c: SQUARE KARATTO*/
+ 0, /* U+00330d: SQUARE KARORII*/
+ 0, /* U+00330e: SQUARE GARON*/
+ 0, /* U+00330f: SQUARE GANMA*/
+ 0, /* U+003310: SQUARE GIGA*/
+ 0, /* U+003311: SQUARE GINII*/
+ 0, /* U+003312: SQUARE KYURII*/
+ 0, /* U+003313: SQUARE GIRUDAA*/
+ 0, /* U+003314: SQUARE KIRO*/
+ 0, /* U+003315: SQUARE KIROGURAMU*/
+ 0, /* U+003316: SQUARE KIROMEETORU*/
+ 0, /* U+003317: SQUARE KIROWATTO*/
+ 0, /* U+003318: SQUARE GURAMU*/
+ 0, /* U+003319: SQUARE GURAMUTON*/
+ 0, /* U+00331a: SQUARE KURUZEIRO*/
+ 0, /* U+00331b: SQUARE KUROONE*/
+ 0, /* U+00331c: SQUARE KEESU*/
+ 0, /* U+00331d: SQUARE KORUNA*/
+ 0, /* U+00331e: SQUARE KOOPO*/
+ 0, /* U+00331f: SQUARE SAIKURU*/
+ 0, /* U+003320: SQUARE SANTIIMU*/
+ 0, /* U+003321: SQUARE SIRINGU*/
+ 0, /* U+003322: SQUARE SENTI*/
+ 0, /* U+003323: SQUARE SENTO*/
+ 0, /* U+003324: SQUARE DAASU*/
+ 0, /* U+003325: SQUARE DESI*/
+ 0, /* U+003326: SQUARE DORU*/
+ 0, /* U+003327: SQUARE TON*/
+ 0, /* U+003328: SQUARE NANO*/
+ 0, /* U+003329: SQUARE NOTTO*/
+ 0, /* U+00332a: SQUARE HAITU*/
+ 0, /* U+00332b: SQUARE PAASENTO*/
+ 0, /* U+00332c: SQUARE PAATU*/
+ 0, /* U+00332d: SQUARE BAARERU*/
+ 0, /* U+00332e: SQUARE PIASUTORU*/
+ 0, /* U+00332f: SQUARE PIKURU*/
+ 0, /* U+003330: SQUARE PIKO*/
+ 0, /* U+003331: SQUARE BIRU*/
+ 0, /* U+003332: SQUARE HUARADDO*/
+ 0, /* U+003333: SQUARE HUIITO*/
+ 0, /* U+003334: SQUARE BUSSYERU*/
+ 0, /* U+003335: SQUARE HURAN*/
+ 0, /* U+003336: SQUARE HEKUTAARU*/
+ 0, /* U+003337: SQUARE PESO*/
+ 0, /* U+003338: SQUARE PENIHI*/
+ 0, /* U+003339: SQUARE HERUTU*/
+ 0, /* U+00333a: SQUARE PENSU*/
+ 0, /* U+00333b: SQUARE PEEZI*/
+ 0, /* U+00333c: SQUARE BEETA*/
+ 0, /* U+00333d: SQUARE POINTO*/
+ 0, /* U+00333e: SQUARE BORUTO*/
+ 0, /* U+00333f: SQUARE HON*/
+ 0, /* U+003340: SQUARE PONDO*/
+ 0, /* U+003341: SQUARE HOORU*/
+ 0, /* U+003342: SQUARE HOON*/
+ 0, /* U+003343: SQUARE MAIKURO*/
+ 0, /* U+003344: SQUARE MAIRU*/
+ 0, /* U+003345: SQUARE MAHHA*/
+ 0, /* U+003346: SQUARE MARUKU*/
+ 0, /* U+003347: SQUARE MANSYON*/
+ 0, /* U+003348: SQUARE MIKURON*/
+ 0, /* U+003349: SQUARE MIRI*/
+ 0, /* U+00334a: SQUARE MIRIBAARU*/
+ 0, /* U+00334b: SQUARE MEGA*/
+ 0, /* U+00334c: SQUARE MEGATON*/
+ 0, /* U+00334d: SQUARE MEETORU*/
+ 0, /* U+00334e: SQUARE YAADO*/
+ 0, /* U+00334f: SQUARE YAARU*/
+ 0, /* U+003350: SQUARE YUAN*/
+ 0, /* U+003351: SQUARE RITTORU*/
+ 0, /* U+003352: SQUARE RIRA*/
+ 0, /* U+003353: SQUARE RUPII*/
+ 0, /* U+003354: SQUARE RUUBURU*/
+ 0, /* U+003355: SQUARE REMU*/
+ 0, /* U+003356: SQUARE RENTOGEN*/
+ 0, /* U+003357: SQUARE WATTO*/
+ 0, /* U+003358: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ZERO*/
+ 0, /* U+003359: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ONE*/
+ 0, /* U+00335a: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWO*/
+ 0, /* U+00335b: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR THREE*/
+ 0, /* U+00335c: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FOUR*/
+ 0, /* U+00335d: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FIVE*/
+ 0, /* U+00335e: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SIX*/
+ 0, /* U+00335f: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SEVEN*/
+ 0, /* U+003360: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR EIGHT*/
+ 0, /* U+003361: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR NINE*/
+ 0, /* U+003362: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TEN*/
+ 0, /* U+003363: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR ELEVEN*/
+ 0, /* U+003364: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWELVE*/
+ 0, /* U+003365: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR THIRTEEN*/
+ 0, /* U+003366: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FOURTEEN*/
+ 0, /* U+003367: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR FIFTEEN*/
+ 0, /* U+003368: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SIXTEEN*/
+ 0, /* U+003369: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR SEVENTEEN*/
+ 0, /* U+00336a: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR EIGHTEEN*/
+ 0, /* U+00336b: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR NINETEEN*/
+ 0, /* U+00336c: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY*/
+ 0, /* U+00336d: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-ONE*/
+ 0, /* U+00336e: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-TWO*/
+ 0, /* U+00336f: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-THREE*/
+ 0, /* U+003370: IDEOGRAPHIC TELEGRAPH SYMBOL FOR HOUR TWENTY-FOUR*/
+ 0, /* U+003371: SQUARE HPA*/
+ 0, /* U+003372: SQUARE DA*/
+ 0, /* U+003373: SQUARE AU*/
+ 0, /* U+003374: SQUARE BAR*/
+ 0, /* U+003375: SQUARE OV*/
+ 0, /* U+003376: SQUARE PC*/
+ 0, /* U+003377: SQUARE DM*/
+ 0, /* U+003378: SQUARE DM SQUARED*/
+ 0, /* U+003379: SQUARE DM CUBED*/
+ 0, /* U+00337a: SQUARE IU*/
+ 0, /* U+00337b: SQUARE ERA NAME HEISEI*/
+ 0, /* U+00337c: SQUARE ERA NAME SYOUWA*/
+ 0, /* U+00337d: SQUARE ERA NAME TAISYOU*/
+ 0, /* U+00337e: SQUARE ERA NAME MEIZI*/
+ 0, /* U+00337f: SQUARE CORPORATION*/
+ 0, /* U+003380: SQUARE PA AMPS*/
+ 0, /* U+003381: SQUARE NA*/
+ 0, /* U+003382: SQUARE MU A*/
+ 0, /* U+003383: SQUARE MA*/
+ 0, /* U+003384: SQUARE KA*/
+ 0, /* U+003385: SQUARE KB*/
+ 0, /* U+003386: SQUARE MB*/
+ 0, /* U+003387: SQUARE GB*/
+ 0, /* U+003388: SQUARE CAL*/
+ 0, /* U+003389: SQUARE KCAL*/
+ 0, /* U+00338a: SQUARE PF*/
+ 0, /* U+00338b: SQUARE NF*/
+ 0, /* U+00338c: SQUARE MU F*/
+ 0, /* U+00338d: SQUARE MU G*/
+ 0, /* U+00338e: SQUARE MG*/
+ 0, /* U+00338f: SQUARE KG*/
+ 0, /* U+003390: SQUARE HZ*/
+ 0, /* U+003391: SQUARE KHZ*/
+ 0, /* U+003392: SQUARE MHZ*/
+ 0, /* U+003393: SQUARE GHZ*/
+ 0, /* U+003394: SQUARE THZ*/
+ 0, /* U+003395: SQUARE MU L*/
+ 0, /* U+003396: SQUARE ML*/
+ 0, /* U+003397: SQUARE DL*/
+ 0, /* U+003398: SQUARE KL*/
+ 0, /* U+003399: SQUARE FM*/
+ 0, /* U+00339a: SQUARE NM*/
+ 0, /* U+00339b: SQUARE MU M*/
+ 0, /* U+00339c: SQUARE MM*/
+ 0, /* U+00339d: SQUARE CM*/
+ 0, /* U+00339e: SQUARE KM*/
+ 0, /* U+00339f: SQUARE MM SQUARED*/
+ 0, /* U+0033a0: SQUARE CM SQUARED*/
+ 0, /* U+0033a1: SQUARE M SQUARED*/
+ 0, /* U+0033a2: SQUARE KM SQUARED*/
+ 0, /* U+0033a3: SQUARE MM CUBED*/
+ 0, /* U+0033a4: SQUARE CM CUBED*/
+ 0, /* U+0033a5: SQUARE M CUBED*/
+ 0, /* U+0033a6: SQUARE KM CUBED*/
+ 0, /* U+0033a7: SQUARE M OVER S*/
+ 0, /* U+0033a8: SQUARE M OVER S SQUARED*/
+ 0, /* U+0033a9: SQUARE PA*/
+ 0, /* U+0033aa: SQUARE KPA*/
+ 0, /* U+0033ab: SQUARE MPA*/
+ 0, /* U+0033ac: SQUARE GPA*/
+ 0, /* U+0033ad: SQUARE RAD*/
+ 0, /* U+0033ae: SQUARE RAD OVER S*/
+ 0, /* U+0033af: SQUARE RAD OVER S SQUARED*/
+ 0, /* U+0033b0: SQUARE PS*/
+ 0, /* U+0033b1: SQUARE NS*/
+ 0, /* U+0033b2: SQUARE MU S*/
+ 0, /* U+0033b3: SQUARE MS*/
+ 0, /* U+0033b4: SQUARE PV*/
+ 0, /* U+0033b5: SQUARE NV*/
+ 0, /* U+0033b6: SQUARE MU V*/
+ 0, /* U+0033b7: SQUARE MV*/
+ 0, /* U+0033b8: SQUARE KV*/
+ 0, /* U+0033b9: SQUARE MV MEGA*/
+ 0, /* U+0033ba: SQUARE PW*/
+ 0, /* U+0033bb: SQUARE NW*/
+ 0, /* U+0033bc: SQUARE MU W*/
+ 0, /* U+0033bd: SQUARE MW*/
+ 0, /* U+0033be: SQUARE KW*/
+ 0, /* U+0033bf: SQUARE MW MEGA*/
+ 0, /* U+0033c0: SQUARE K OHM*/
+ 0, /* U+0033c1: SQUARE M OHM*/
+ 0, /* U+0033c2: SQUARE AM*/
+ 0, /* U+0033c3: SQUARE BQ*/
+ 0, /* U+0033c4: SQUARE CC*/
+ 0, /* U+0033c5: SQUARE CD*/
+ 0, /* U+0033c6: SQUARE C OVER KG*/
+ 0, /* U+0033c7: SQUARE CO*/
+ 0, /* U+0033c8: SQUARE DB*/
+ 0, /* U+0033c9: SQUARE GY*/
+ 0, /* U+0033ca: SQUARE HA*/
+ 0, /* U+0033cb: SQUARE HP*/
+ 0, /* U+0033cc: SQUARE IN*/
+ 0, /* U+0033cd: SQUARE KK*/
+ 0, /* U+0033ce: SQUARE KM CAPITAL*/
+ 0, /* U+0033cf: SQUARE KT*/
+ 0, /* U+0033d0: SQUARE LM*/
+ 0, /* U+0033d1: SQUARE LN*/
+ 0, /* U+0033d2: SQUARE LOG*/
+ 0, /* U+0033d3: SQUARE LX*/
+ 0, /* U+0033d4: SQUARE MB SMALL*/
+ 0, /* U+0033d5: SQUARE MIL*/
+ 0, /* U+0033d6: SQUARE MOL*/
+ 0, /* U+0033d7: SQUARE PH*/
+ 0, /* U+0033d8: SQUARE PM*/
+ 0, /* U+0033d9: SQUARE PPM*/
+ 0, /* U+0033da: SQUARE PR*/
+ 0, /* U+0033db: SQUARE SR*/
+ 0, /* U+0033dc: SQUARE SV*/
+ 0, /* U+0033dd: SQUARE WB*/
+ 0, /* U+0033de: SQUARE V OVER M*/
+ 0, /* U+0033df: SQUARE A OVER M*/
+ 0, /* U+0033e0: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY ONE*/
+ 0, /* U+0033e1: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWO*/
+ 0, /* U+0033e2: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THREE*/
+ 0, /* U+0033e3: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FOUR*/
+ 0, /* U+0033e4: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FIVE*/
+ 0, /* U+0033e5: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SIX*/
+ 0, /* U+0033e6: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SEVEN*/
+ 0, /* U+0033e7: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY EIGHT*/
+ 0, /* U+0033e8: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY NINE*/
+ 0, /* U+0033e9: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TEN*/
+ 0, /* U+0033ea: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY ELEVEN*/
+ 0, /* U+0033eb: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWELVE*/
+ 0, /* U+0033ec: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTEEN*/
+ 0, /* U+0033ed: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FOURTEEN*/
+ 0, /* U+0033ee: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY FIFTEEN*/
+ 0, /* U+0033ef: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SIXTEEN*/
+ 0, /* U+0033f0: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY SEVENTEEN*/
+ 0, /* U+0033f1: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY EIGHTEEN*/
+ 0, /* U+0033f2: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY NINETEEN*/
+ 0, /* U+0033f3: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY*/
+ 0, /* U+0033f4: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-ONE*/
+ 0, /* U+0033f5: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-TWO*/
+ 0, /* U+0033f6: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-THREE*/
+ 0, /* U+0033f7: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-FOUR*/
+ 0, /* U+0033f8: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-FIVE*/
+ 0, /* U+0033f9: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-SIX*/
+ 0, /* U+0033fa: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-SEVEN*/
+ 0, /* U+0033fb: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-EIGHT*/
+ 0, /* U+0033fc: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY TWENTY-NINE*/
+ 0, /* U+0033fd: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTY*/
+ 0, /* U+0033fe: IDEOGRAPHIC TELEGRAPH SYMBOL FOR DAY THIRTY-ONE*/
+ 0, /* U+0033ff: SQUARE GAL*/
+ RTUNI_ALPHA, /* U+003400: <CJK Ideograph Extension A, First>*/
+};
+
+static const uint8_t g_afRTUniFlags0x004db5[] =
+{
+ RTUNI_ALPHA, /* U+004db5: <CJK Ideograph Extension A, Last>*/
+ 0, /* U+004db6: */
+ 0, /* U+004db7: */
+ 0, /* U+004db8: */
+ 0, /* U+004db9: */
+ 0, /* U+004dba: */
+ 0, /* U+004dbb: */
+ 0, /* U+004dbc: */
+ 0, /* U+004dbd: */
+ 0, /* U+004dbe: */
+ 0, /* U+004dbf: */
+ 0, /* U+004dc0: HEXAGRAM FOR THE CREATIVE HEAVEN*/
+ 0, /* U+004dc1: HEXAGRAM FOR THE RECEPTIVE EARTH*/
+ 0, /* U+004dc2: HEXAGRAM FOR DIFFICULTY AT THE BEGINNING*/
+ 0, /* U+004dc3: HEXAGRAM FOR YOUTHFUL FOLLY*/
+ 0, /* U+004dc4: HEXAGRAM FOR WAITING*/
+ 0, /* U+004dc5: HEXAGRAM FOR CONFLICT*/
+ 0, /* U+004dc6: HEXAGRAM FOR THE ARMY*/
+ 0, /* U+004dc7: HEXAGRAM FOR HOLDING TOGETHER*/
+ 0, /* U+004dc8: HEXAGRAM FOR SMALL TAMING*/
+ 0, /* U+004dc9: HEXAGRAM FOR TREADING*/
+ 0, /* U+004dca: HEXAGRAM FOR PEACE*/
+ 0, /* U+004dcb: HEXAGRAM FOR STANDSTILL*/
+ 0, /* U+004dcc: HEXAGRAM FOR FELLOWSHIP*/
+ 0, /* U+004dcd: HEXAGRAM FOR GREAT POSSESSION*/
+ 0, /* U+004dce: HEXAGRAM FOR MODESTY*/
+ 0, /* U+004dcf: HEXAGRAM FOR ENTHUSIASM*/
+ 0, /* U+004dd0: HEXAGRAM FOR FOLLOWING*/
+ 0, /* U+004dd1: HEXAGRAM FOR WORK ON THE DECAYED*/
+ 0, /* U+004dd2: HEXAGRAM FOR APPROACH*/
+ 0, /* U+004dd3: HEXAGRAM FOR CONTEMPLATION*/
+ 0, /* U+004dd4: HEXAGRAM FOR BITING THROUGH*/
+ 0, /* U+004dd5: HEXAGRAM FOR GRACE*/
+ 0, /* U+004dd6: HEXAGRAM FOR SPLITTING APART*/
+ 0, /* U+004dd7: HEXAGRAM FOR RETURN*/
+ 0, /* U+004dd8: HEXAGRAM FOR INNOCENCE*/
+ 0, /* U+004dd9: HEXAGRAM FOR GREAT TAMING*/
+ 0, /* U+004dda: HEXAGRAM FOR MOUTH CORNERS*/
+ 0, /* U+004ddb: HEXAGRAM FOR GREAT PREPONDERANCE*/
+ 0, /* U+004ddc: HEXAGRAM FOR THE ABYSMAL WATER*/
+ 0, /* U+004ddd: HEXAGRAM FOR THE CLINGING FIRE*/
+ 0, /* U+004dde: HEXAGRAM FOR INFLUENCE*/
+ 0, /* U+004ddf: HEXAGRAM FOR DURATION*/
+ 0, /* U+004de0: HEXAGRAM FOR RETREAT*/
+ 0, /* U+004de1: HEXAGRAM FOR GREAT POWER*/
+ 0, /* U+004de2: HEXAGRAM FOR PROGRESS*/
+ 0, /* U+004de3: HEXAGRAM FOR DARKENING OF THE LIGHT*/
+ 0, /* U+004de4: HEXAGRAM FOR THE FAMILY*/
+ 0, /* U+004de5: HEXAGRAM FOR OPPOSITION*/
+ 0, /* U+004de6: HEXAGRAM FOR OBSTRUCTION*/
+ 0, /* U+004de7: HEXAGRAM FOR DELIVERANCE*/
+ 0, /* U+004de8: HEXAGRAM FOR DECREASE*/
+ 0, /* U+004de9: HEXAGRAM FOR INCREASE*/
+ 0, /* U+004dea: HEXAGRAM FOR BREAKTHROUGH*/
+ 0, /* U+004deb: HEXAGRAM FOR COMING TO MEET*/
+ 0, /* U+004dec: HEXAGRAM FOR GATHERING TOGETHER*/
+ 0, /* U+004ded: HEXAGRAM FOR PUSHING UPWARD*/
+ 0, /* U+004dee: HEXAGRAM FOR OPPRESSION*/
+ 0, /* U+004def: HEXAGRAM FOR THE WELL*/
+ 0, /* U+004df0: HEXAGRAM FOR REVOLUTION*/
+ 0, /* U+004df1: HEXAGRAM FOR THE CAULDRON*/
+ 0, /* U+004df2: HEXAGRAM FOR THE AROUSING THUNDER*/
+ 0, /* U+004df3: HEXAGRAM FOR THE KEEPING STILL MOUNTAIN*/
+ 0, /* U+004df4: HEXAGRAM FOR DEVELOPMENT*/
+ 0, /* U+004df5: HEXAGRAM FOR THE MARRYING MAIDEN*/
+ 0, /* U+004df6: HEXAGRAM FOR ABUNDANCE*/
+ 0, /* U+004df7: HEXAGRAM FOR THE WANDERER*/
+ 0, /* U+004df8: HEXAGRAM FOR THE GENTLE WIND*/
+ 0, /* U+004df9: HEXAGRAM FOR THE JOYOUS LAKE*/
+ 0, /* U+004dfa: HEXAGRAM FOR DISPERSION*/
+ 0, /* U+004dfb: HEXAGRAM FOR LIMITATION*/
+ 0, /* U+004dfc: HEXAGRAM FOR INNER TRUTH*/
+ 0, /* U+004dfd: HEXAGRAM FOR SMALL PREPONDERANCE*/
+ 0, /* U+004dfe: HEXAGRAM FOR AFTER COMPLETION*/
+ 0, /* U+004dff: HEXAGRAM FOR BEFORE COMPLETION*/
+ RTUNI_ALPHA, /* U+004e00: <CJK Ideograph, First>*/
+};
+
+static const uint8_t g_afRTUniFlags0x009fcc[] =
+{
+ RTUNI_ALPHA, /* U+009fcc: <CJK Ideograph, Last>*/
+ 0, /* U+009fcd: */
+ 0, /* U+009fce: */
+ 0, /* U+009fcf: */
+ 0, /* U+009fd0: */
+ 0, /* U+009fd1: */
+ 0, /* U+009fd2: */
+ 0, /* U+009fd3: */
+ 0, /* U+009fd4: */
+ 0, /* U+009fd5: */
+ 0, /* U+009fd6: */
+ 0, /* U+009fd7: */
+ 0, /* U+009fd8: */
+ 0, /* U+009fd9: */
+ 0, /* U+009fda: */
+ 0, /* U+009fdb: */
+ 0, /* U+009fdc: */
+ 0, /* U+009fdd: */
+ 0, /* U+009fde: */
+ 0, /* U+009fdf: */
+ 0, /* U+009fe0: */
+ 0, /* U+009fe1: */
+ 0, /* U+009fe2: */
+ 0, /* U+009fe3: */
+ 0, /* U+009fe4: */
+ 0, /* U+009fe5: */
+ 0, /* U+009fe6: */
+ 0, /* U+009fe7: */
+ 0, /* U+009fe8: */
+ 0, /* U+009fe9: */
+ 0, /* U+009fea: */
+ 0, /* U+009feb: */
+ 0, /* U+009fec: */
+ 0, /* U+009fed: */
+ 0, /* U+009fee: */
+ 0, /* U+009fef: */
+ 0, /* U+009ff0: */
+ 0, /* U+009ff1: */
+ 0, /* U+009ff2: */
+ 0, /* U+009ff3: */
+ 0, /* U+009ff4: */
+ 0, /* U+009ff5: */
+ 0, /* U+009ff6: */
+ 0, /* U+009ff7: */
+ 0, /* U+009ff8: */
+ 0, /* U+009ff9: */
+ 0, /* U+009ffa: */
+ 0, /* U+009ffb: */
+ 0, /* U+009ffc: */
+ 0, /* U+009ffd: */
+ 0, /* U+009ffe: */
+ 0, /* U+009fff: */
+ RTUNI_ALPHA, /* U+00a000: YI SYLLABLE IT*/
+ RTUNI_ALPHA, /* U+00a001: YI SYLLABLE IX*/
+ RTUNI_ALPHA, /* U+00a002: YI SYLLABLE I*/
+ RTUNI_ALPHA, /* U+00a003: YI SYLLABLE IP*/
+ RTUNI_ALPHA, /* U+00a004: YI SYLLABLE IET*/
+ RTUNI_ALPHA, /* U+00a005: YI SYLLABLE IEX*/
+ RTUNI_ALPHA, /* U+00a006: YI SYLLABLE IE*/
+ RTUNI_ALPHA, /* U+00a007: YI SYLLABLE IEP*/
+ RTUNI_ALPHA, /* U+00a008: YI SYLLABLE AT*/
+ RTUNI_ALPHA, /* U+00a009: YI SYLLABLE AX*/
+ RTUNI_ALPHA, /* U+00a00a: YI SYLLABLE A*/
+ RTUNI_ALPHA, /* U+00a00b: YI SYLLABLE AP*/
+ RTUNI_ALPHA, /* U+00a00c: YI SYLLABLE UOX*/
+ RTUNI_ALPHA, /* U+00a00d: YI SYLLABLE UO*/
+ RTUNI_ALPHA, /* U+00a00e: YI SYLLABLE UOP*/
+ RTUNI_ALPHA, /* U+00a00f: YI SYLLABLE OT*/
+ RTUNI_ALPHA, /* U+00a010: YI SYLLABLE OX*/
+ RTUNI_ALPHA, /* U+00a011: YI SYLLABLE O*/
+ RTUNI_ALPHA, /* U+00a012: YI SYLLABLE OP*/
+ RTUNI_ALPHA, /* U+00a013: YI SYLLABLE EX*/
+ RTUNI_ALPHA, /* U+00a014: YI SYLLABLE E*/
+ RTUNI_ALPHA, /* U+00a015: YI SYLLABLE WU*/
+ RTUNI_ALPHA, /* U+00a016: YI SYLLABLE BIT*/
+ RTUNI_ALPHA, /* U+00a017: YI SYLLABLE BIX*/
+ RTUNI_ALPHA, /* U+00a018: YI SYLLABLE BI*/
+ RTUNI_ALPHA, /* U+00a019: YI SYLLABLE BIP*/
+ RTUNI_ALPHA, /* U+00a01a: YI SYLLABLE BIET*/
+ RTUNI_ALPHA, /* U+00a01b: YI SYLLABLE BIEX*/
+ RTUNI_ALPHA, /* U+00a01c: YI SYLLABLE BIE*/
+ RTUNI_ALPHA, /* U+00a01d: YI SYLLABLE BIEP*/
+ RTUNI_ALPHA, /* U+00a01e: YI SYLLABLE BAT*/
+ RTUNI_ALPHA, /* U+00a01f: YI SYLLABLE BAX*/
+ RTUNI_ALPHA, /* U+00a020: YI SYLLABLE BA*/
+ RTUNI_ALPHA, /* U+00a021: YI SYLLABLE BAP*/
+ RTUNI_ALPHA, /* U+00a022: YI SYLLABLE BUOX*/
+ RTUNI_ALPHA, /* U+00a023: YI SYLLABLE BUO*/
+ RTUNI_ALPHA, /* U+00a024: YI SYLLABLE BUOP*/
+ RTUNI_ALPHA, /* U+00a025: YI SYLLABLE BOT*/
+ RTUNI_ALPHA, /* U+00a026: YI SYLLABLE BOX*/
+ RTUNI_ALPHA, /* U+00a027: YI SYLLABLE BO*/
+ RTUNI_ALPHA, /* U+00a028: YI SYLLABLE BOP*/
+ RTUNI_ALPHA, /* U+00a029: YI SYLLABLE BEX*/
+ RTUNI_ALPHA, /* U+00a02a: YI SYLLABLE BE*/
+ RTUNI_ALPHA, /* U+00a02b: YI SYLLABLE BEP*/
+ RTUNI_ALPHA, /* U+00a02c: YI SYLLABLE BUT*/
+ RTUNI_ALPHA, /* U+00a02d: YI SYLLABLE BUX*/
+ RTUNI_ALPHA, /* U+00a02e: YI SYLLABLE BU*/
+ RTUNI_ALPHA, /* U+00a02f: YI SYLLABLE BUP*/
+ RTUNI_ALPHA, /* U+00a030: YI SYLLABLE BURX*/
+ RTUNI_ALPHA, /* U+00a031: YI SYLLABLE BUR*/
+ RTUNI_ALPHA, /* U+00a032: YI SYLLABLE BYT*/
+ RTUNI_ALPHA, /* U+00a033: YI SYLLABLE BYX*/
+ RTUNI_ALPHA, /* U+00a034: YI SYLLABLE BY*/
+ RTUNI_ALPHA, /* U+00a035: YI SYLLABLE BYP*/
+ RTUNI_ALPHA, /* U+00a036: YI SYLLABLE BYRX*/
+ RTUNI_ALPHA, /* U+00a037: YI SYLLABLE BYR*/
+ RTUNI_ALPHA, /* U+00a038: YI SYLLABLE PIT*/
+ RTUNI_ALPHA, /* U+00a039: YI SYLLABLE PIX*/
+ RTUNI_ALPHA, /* U+00a03a: YI SYLLABLE PI*/
+ RTUNI_ALPHA, /* U+00a03b: YI SYLLABLE PIP*/
+ RTUNI_ALPHA, /* U+00a03c: YI SYLLABLE PIEX*/
+ RTUNI_ALPHA, /* U+00a03d: YI SYLLABLE PIE*/
+ RTUNI_ALPHA, /* U+00a03e: YI SYLLABLE PIEP*/
+ RTUNI_ALPHA, /* U+00a03f: YI SYLLABLE PAT*/
+ RTUNI_ALPHA, /* U+00a040: YI SYLLABLE PAX*/
+ RTUNI_ALPHA, /* U+00a041: YI SYLLABLE PA*/
+ RTUNI_ALPHA, /* U+00a042: YI SYLLABLE PAP*/
+ RTUNI_ALPHA, /* U+00a043: YI SYLLABLE PUOX*/
+ RTUNI_ALPHA, /* U+00a044: YI SYLLABLE PUO*/
+ RTUNI_ALPHA, /* U+00a045: YI SYLLABLE PUOP*/
+ RTUNI_ALPHA, /* U+00a046: YI SYLLABLE POT*/
+ RTUNI_ALPHA, /* U+00a047: YI SYLLABLE POX*/
+ RTUNI_ALPHA, /* U+00a048: YI SYLLABLE PO*/
+ RTUNI_ALPHA, /* U+00a049: YI SYLLABLE POP*/
+ RTUNI_ALPHA, /* U+00a04a: YI SYLLABLE PUT*/
+ RTUNI_ALPHA, /* U+00a04b: YI SYLLABLE PUX*/
+ RTUNI_ALPHA, /* U+00a04c: YI SYLLABLE PU*/
+ RTUNI_ALPHA, /* U+00a04d: YI SYLLABLE PUP*/
+ RTUNI_ALPHA, /* U+00a04e: YI SYLLABLE PURX*/
+ RTUNI_ALPHA, /* U+00a04f: YI SYLLABLE PUR*/
+ RTUNI_ALPHA, /* U+00a050: YI SYLLABLE PYT*/
+ RTUNI_ALPHA, /* U+00a051: YI SYLLABLE PYX*/
+ RTUNI_ALPHA, /* U+00a052: YI SYLLABLE PY*/
+ RTUNI_ALPHA, /* U+00a053: YI SYLLABLE PYP*/
+ RTUNI_ALPHA, /* U+00a054: YI SYLLABLE PYRX*/
+ RTUNI_ALPHA, /* U+00a055: YI SYLLABLE PYR*/
+ RTUNI_ALPHA, /* U+00a056: YI SYLLABLE BBIT*/
+ RTUNI_ALPHA, /* U+00a057: YI SYLLABLE BBIX*/
+ RTUNI_ALPHA, /* U+00a058: YI SYLLABLE BBI*/
+ RTUNI_ALPHA, /* U+00a059: YI SYLLABLE BBIP*/
+ RTUNI_ALPHA, /* U+00a05a: YI SYLLABLE BBIET*/
+ RTUNI_ALPHA, /* U+00a05b: YI SYLLABLE BBIEX*/
+ RTUNI_ALPHA, /* U+00a05c: YI SYLLABLE BBIE*/
+ RTUNI_ALPHA, /* U+00a05d: YI SYLLABLE BBIEP*/
+ RTUNI_ALPHA, /* U+00a05e: YI SYLLABLE BBAT*/
+ RTUNI_ALPHA, /* U+00a05f: YI SYLLABLE BBAX*/
+ RTUNI_ALPHA, /* U+00a060: YI SYLLABLE BBA*/
+ RTUNI_ALPHA, /* U+00a061: YI SYLLABLE BBAP*/
+ RTUNI_ALPHA, /* U+00a062: YI SYLLABLE BBUOX*/
+ RTUNI_ALPHA, /* U+00a063: YI SYLLABLE BBUO*/
+ RTUNI_ALPHA, /* U+00a064: YI SYLLABLE BBUOP*/
+ RTUNI_ALPHA, /* U+00a065: YI SYLLABLE BBOT*/
+ RTUNI_ALPHA, /* U+00a066: YI SYLLABLE BBOX*/
+ RTUNI_ALPHA, /* U+00a067: YI SYLLABLE BBO*/
+ RTUNI_ALPHA, /* U+00a068: YI SYLLABLE BBOP*/
+ RTUNI_ALPHA, /* U+00a069: YI SYLLABLE BBEX*/
+ RTUNI_ALPHA, /* U+00a06a: YI SYLLABLE BBE*/
+ RTUNI_ALPHA, /* U+00a06b: YI SYLLABLE BBEP*/
+ RTUNI_ALPHA, /* U+00a06c: YI SYLLABLE BBUT*/
+ RTUNI_ALPHA, /* U+00a06d: YI SYLLABLE BBUX*/
+ RTUNI_ALPHA, /* U+00a06e: YI SYLLABLE BBU*/
+ RTUNI_ALPHA, /* U+00a06f: YI SYLLABLE BBUP*/
+ RTUNI_ALPHA, /* U+00a070: YI SYLLABLE BBURX*/
+ RTUNI_ALPHA, /* U+00a071: YI SYLLABLE BBUR*/
+ RTUNI_ALPHA, /* U+00a072: YI SYLLABLE BBYT*/
+ RTUNI_ALPHA, /* U+00a073: YI SYLLABLE BBYX*/
+ RTUNI_ALPHA, /* U+00a074: YI SYLLABLE BBY*/
+ RTUNI_ALPHA, /* U+00a075: YI SYLLABLE BBYP*/
+ RTUNI_ALPHA, /* U+00a076: YI SYLLABLE NBIT*/
+ RTUNI_ALPHA, /* U+00a077: YI SYLLABLE NBIX*/
+ RTUNI_ALPHA, /* U+00a078: YI SYLLABLE NBI*/
+ RTUNI_ALPHA, /* U+00a079: YI SYLLABLE NBIP*/
+ RTUNI_ALPHA, /* U+00a07a: YI SYLLABLE NBIEX*/
+ RTUNI_ALPHA, /* U+00a07b: YI SYLLABLE NBIE*/
+ RTUNI_ALPHA, /* U+00a07c: YI SYLLABLE NBIEP*/
+ RTUNI_ALPHA, /* U+00a07d: YI SYLLABLE NBAT*/
+ RTUNI_ALPHA, /* U+00a07e: YI SYLLABLE NBAX*/
+ RTUNI_ALPHA, /* U+00a07f: YI SYLLABLE NBA*/
+ RTUNI_ALPHA, /* U+00a080: YI SYLLABLE NBAP*/
+ RTUNI_ALPHA, /* U+00a081: YI SYLLABLE NBOT*/
+ RTUNI_ALPHA, /* U+00a082: YI SYLLABLE NBOX*/
+ RTUNI_ALPHA, /* U+00a083: YI SYLLABLE NBO*/
+ RTUNI_ALPHA, /* U+00a084: YI SYLLABLE NBOP*/
+ RTUNI_ALPHA, /* U+00a085: YI SYLLABLE NBUT*/
+ RTUNI_ALPHA, /* U+00a086: YI SYLLABLE NBUX*/
+ RTUNI_ALPHA, /* U+00a087: YI SYLLABLE NBU*/
+ RTUNI_ALPHA, /* U+00a088: YI SYLLABLE NBUP*/
+ RTUNI_ALPHA, /* U+00a089: YI SYLLABLE NBURX*/
+ RTUNI_ALPHA, /* U+00a08a: YI SYLLABLE NBUR*/
+ RTUNI_ALPHA, /* U+00a08b: YI SYLLABLE NBYT*/
+ RTUNI_ALPHA, /* U+00a08c: YI SYLLABLE NBYX*/
+ RTUNI_ALPHA, /* U+00a08d: YI SYLLABLE NBY*/
+ RTUNI_ALPHA, /* U+00a08e: YI SYLLABLE NBYP*/
+ RTUNI_ALPHA, /* U+00a08f: YI SYLLABLE NBYRX*/
+ RTUNI_ALPHA, /* U+00a090: YI SYLLABLE NBYR*/
+ RTUNI_ALPHA, /* U+00a091: YI SYLLABLE HMIT*/
+ RTUNI_ALPHA, /* U+00a092: YI SYLLABLE HMIX*/
+ RTUNI_ALPHA, /* U+00a093: YI SYLLABLE HMI*/
+ RTUNI_ALPHA, /* U+00a094: YI SYLLABLE HMIP*/
+ RTUNI_ALPHA, /* U+00a095: YI SYLLABLE HMIEX*/
+ RTUNI_ALPHA, /* U+00a096: YI SYLLABLE HMIE*/
+ RTUNI_ALPHA, /* U+00a097: YI SYLLABLE HMIEP*/
+ RTUNI_ALPHA, /* U+00a098: YI SYLLABLE HMAT*/
+ RTUNI_ALPHA, /* U+00a099: YI SYLLABLE HMAX*/
+ RTUNI_ALPHA, /* U+00a09a: YI SYLLABLE HMA*/
+ RTUNI_ALPHA, /* U+00a09b: YI SYLLABLE HMAP*/
+ RTUNI_ALPHA, /* U+00a09c: YI SYLLABLE HMUOX*/
+ RTUNI_ALPHA, /* U+00a09d: YI SYLLABLE HMUO*/
+ RTUNI_ALPHA, /* U+00a09e: YI SYLLABLE HMUOP*/
+ RTUNI_ALPHA, /* U+00a09f: YI SYLLABLE HMOT*/
+ RTUNI_ALPHA, /* U+00a0a0: YI SYLLABLE HMOX*/
+ RTUNI_ALPHA, /* U+00a0a1: YI SYLLABLE HMO*/
+ RTUNI_ALPHA, /* U+00a0a2: YI SYLLABLE HMOP*/
+ RTUNI_ALPHA, /* U+00a0a3: YI SYLLABLE HMUT*/
+ RTUNI_ALPHA, /* U+00a0a4: YI SYLLABLE HMUX*/
+ RTUNI_ALPHA, /* U+00a0a5: YI SYLLABLE HMU*/
+ RTUNI_ALPHA, /* U+00a0a6: YI SYLLABLE HMUP*/
+ RTUNI_ALPHA, /* U+00a0a7: YI SYLLABLE HMURX*/
+ RTUNI_ALPHA, /* U+00a0a8: YI SYLLABLE HMUR*/
+ RTUNI_ALPHA, /* U+00a0a9: YI SYLLABLE HMYX*/
+ RTUNI_ALPHA, /* U+00a0aa: YI SYLLABLE HMY*/
+ RTUNI_ALPHA, /* U+00a0ab: YI SYLLABLE HMYP*/
+ RTUNI_ALPHA, /* U+00a0ac: YI SYLLABLE HMYRX*/
+ RTUNI_ALPHA, /* U+00a0ad: YI SYLLABLE HMYR*/
+ RTUNI_ALPHA, /* U+00a0ae: YI SYLLABLE MIT*/
+ RTUNI_ALPHA, /* U+00a0af: YI SYLLABLE MIX*/
+ RTUNI_ALPHA, /* U+00a0b0: YI SYLLABLE MI*/
+ RTUNI_ALPHA, /* U+00a0b1: YI SYLLABLE MIP*/
+ RTUNI_ALPHA, /* U+00a0b2: YI SYLLABLE MIEX*/
+ RTUNI_ALPHA, /* U+00a0b3: YI SYLLABLE MIE*/
+ RTUNI_ALPHA, /* U+00a0b4: YI SYLLABLE MIEP*/
+ RTUNI_ALPHA, /* U+00a0b5: YI SYLLABLE MAT*/
+ RTUNI_ALPHA, /* U+00a0b6: YI SYLLABLE MAX*/
+ RTUNI_ALPHA, /* U+00a0b7: YI SYLLABLE MA*/
+ RTUNI_ALPHA, /* U+00a0b8: YI SYLLABLE MAP*/
+ RTUNI_ALPHA, /* U+00a0b9: YI SYLLABLE MUOT*/
+ RTUNI_ALPHA, /* U+00a0ba: YI SYLLABLE MUOX*/
+ RTUNI_ALPHA, /* U+00a0bb: YI SYLLABLE MUO*/
+ RTUNI_ALPHA, /* U+00a0bc: YI SYLLABLE MUOP*/
+ RTUNI_ALPHA, /* U+00a0bd: YI SYLLABLE MOT*/
+ RTUNI_ALPHA, /* U+00a0be: YI SYLLABLE MOX*/
+ RTUNI_ALPHA, /* U+00a0bf: YI SYLLABLE MO*/
+ RTUNI_ALPHA, /* U+00a0c0: YI SYLLABLE MOP*/
+ RTUNI_ALPHA, /* U+00a0c1: YI SYLLABLE MEX*/
+ RTUNI_ALPHA, /* U+00a0c2: YI SYLLABLE ME*/
+ RTUNI_ALPHA, /* U+00a0c3: YI SYLLABLE MUT*/
+ RTUNI_ALPHA, /* U+00a0c4: YI SYLLABLE MUX*/
+ RTUNI_ALPHA, /* U+00a0c5: YI SYLLABLE MU*/
+ RTUNI_ALPHA, /* U+00a0c6: YI SYLLABLE MUP*/
+ RTUNI_ALPHA, /* U+00a0c7: YI SYLLABLE MURX*/
+ RTUNI_ALPHA, /* U+00a0c8: YI SYLLABLE MUR*/
+ RTUNI_ALPHA, /* U+00a0c9: YI SYLLABLE MYT*/
+ RTUNI_ALPHA, /* U+00a0ca: YI SYLLABLE MYX*/
+ RTUNI_ALPHA, /* U+00a0cb: YI SYLLABLE MY*/
+ RTUNI_ALPHA, /* U+00a0cc: YI SYLLABLE MYP*/
+ RTUNI_ALPHA, /* U+00a0cd: YI SYLLABLE FIT*/
+ RTUNI_ALPHA, /* U+00a0ce: YI SYLLABLE FIX*/
+ RTUNI_ALPHA, /* U+00a0cf: YI SYLLABLE FI*/
+ RTUNI_ALPHA, /* U+00a0d0: YI SYLLABLE FIP*/
+ RTUNI_ALPHA, /* U+00a0d1: YI SYLLABLE FAT*/
+ RTUNI_ALPHA, /* U+00a0d2: YI SYLLABLE FAX*/
+ RTUNI_ALPHA, /* U+00a0d3: YI SYLLABLE FA*/
+ RTUNI_ALPHA, /* U+00a0d4: YI SYLLABLE FAP*/
+ RTUNI_ALPHA, /* U+00a0d5: YI SYLLABLE FOX*/
+ RTUNI_ALPHA, /* U+00a0d6: YI SYLLABLE FO*/
+ RTUNI_ALPHA, /* U+00a0d7: YI SYLLABLE FOP*/
+ RTUNI_ALPHA, /* U+00a0d8: YI SYLLABLE FUT*/
+ RTUNI_ALPHA, /* U+00a0d9: YI SYLLABLE FUX*/
+ RTUNI_ALPHA, /* U+00a0da: YI SYLLABLE FU*/
+ RTUNI_ALPHA, /* U+00a0db: YI SYLLABLE FUP*/
+ RTUNI_ALPHA, /* U+00a0dc: YI SYLLABLE FURX*/
+ RTUNI_ALPHA, /* U+00a0dd: YI SYLLABLE FUR*/
+ RTUNI_ALPHA, /* U+00a0de: YI SYLLABLE FYT*/
+ RTUNI_ALPHA, /* U+00a0df: YI SYLLABLE FYX*/
+ RTUNI_ALPHA, /* U+00a0e0: YI SYLLABLE FY*/
+ RTUNI_ALPHA, /* U+00a0e1: YI SYLLABLE FYP*/
+ RTUNI_ALPHA, /* U+00a0e2: YI SYLLABLE VIT*/
+ RTUNI_ALPHA, /* U+00a0e3: YI SYLLABLE VIX*/
+ RTUNI_ALPHA, /* U+00a0e4: YI SYLLABLE VI*/
+ RTUNI_ALPHA, /* U+00a0e5: YI SYLLABLE VIP*/
+ RTUNI_ALPHA, /* U+00a0e6: YI SYLLABLE VIET*/
+ RTUNI_ALPHA, /* U+00a0e7: YI SYLLABLE VIEX*/
+ RTUNI_ALPHA, /* U+00a0e8: YI SYLLABLE VIE*/
+ RTUNI_ALPHA, /* U+00a0e9: YI SYLLABLE VIEP*/
+ RTUNI_ALPHA, /* U+00a0ea: YI SYLLABLE VAT*/
+ RTUNI_ALPHA, /* U+00a0eb: YI SYLLABLE VAX*/
+ RTUNI_ALPHA, /* U+00a0ec: YI SYLLABLE VA*/
+ RTUNI_ALPHA, /* U+00a0ed: YI SYLLABLE VAP*/
+ RTUNI_ALPHA, /* U+00a0ee: YI SYLLABLE VOT*/
+ RTUNI_ALPHA, /* U+00a0ef: YI SYLLABLE VOX*/
+ RTUNI_ALPHA, /* U+00a0f0: YI SYLLABLE VO*/
+ RTUNI_ALPHA, /* U+00a0f1: YI SYLLABLE VOP*/
+ RTUNI_ALPHA, /* U+00a0f2: YI SYLLABLE VEX*/
+ RTUNI_ALPHA, /* U+00a0f3: YI SYLLABLE VEP*/
+ RTUNI_ALPHA, /* U+00a0f4: YI SYLLABLE VUT*/
+ RTUNI_ALPHA, /* U+00a0f5: YI SYLLABLE VUX*/
+ RTUNI_ALPHA, /* U+00a0f6: YI SYLLABLE VU*/
+ RTUNI_ALPHA, /* U+00a0f7: YI SYLLABLE VUP*/
+ RTUNI_ALPHA, /* U+00a0f8: YI SYLLABLE VURX*/
+ RTUNI_ALPHA, /* U+00a0f9: YI SYLLABLE VUR*/
+ RTUNI_ALPHA, /* U+00a0fa: YI SYLLABLE VYT*/
+ RTUNI_ALPHA, /* U+00a0fb: YI SYLLABLE VYX*/
+ RTUNI_ALPHA, /* U+00a0fc: YI SYLLABLE VY*/
+ RTUNI_ALPHA, /* U+00a0fd: YI SYLLABLE VYP*/
+ RTUNI_ALPHA, /* U+00a0fe: YI SYLLABLE VYRX*/
+ RTUNI_ALPHA, /* U+00a0ff: YI SYLLABLE VYR*/
+ RTUNI_ALPHA, /* U+00a100: YI SYLLABLE DIT*/
+ RTUNI_ALPHA, /* U+00a101: YI SYLLABLE DIX*/
+ RTUNI_ALPHA, /* U+00a102: YI SYLLABLE DI*/
+ RTUNI_ALPHA, /* U+00a103: YI SYLLABLE DIP*/
+ RTUNI_ALPHA, /* U+00a104: YI SYLLABLE DIEX*/
+ RTUNI_ALPHA, /* U+00a105: YI SYLLABLE DIE*/
+ RTUNI_ALPHA, /* U+00a106: YI SYLLABLE DIEP*/
+ RTUNI_ALPHA, /* U+00a107: YI SYLLABLE DAT*/
+ RTUNI_ALPHA, /* U+00a108: YI SYLLABLE DAX*/
+ RTUNI_ALPHA, /* U+00a109: YI SYLLABLE DA*/
+ RTUNI_ALPHA, /* U+00a10a: YI SYLLABLE DAP*/
+ RTUNI_ALPHA, /* U+00a10b: YI SYLLABLE DUOX*/
+ RTUNI_ALPHA, /* U+00a10c: YI SYLLABLE DUO*/
+ RTUNI_ALPHA, /* U+00a10d: YI SYLLABLE DOT*/
+ RTUNI_ALPHA, /* U+00a10e: YI SYLLABLE DOX*/
+ RTUNI_ALPHA, /* U+00a10f: YI SYLLABLE DO*/
+ RTUNI_ALPHA, /* U+00a110: YI SYLLABLE DOP*/
+ RTUNI_ALPHA, /* U+00a111: YI SYLLABLE DEX*/
+ RTUNI_ALPHA, /* U+00a112: YI SYLLABLE DE*/
+ RTUNI_ALPHA, /* U+00a113: YI SYLLABLE DEP*/
+ RTUNI_ALPHA, /* U+00a114: YI SYLLABLE DUT*/
+ RTUNI_ALPHA, /* U+00a115: YI SYLLABLE DUX*/
+ RTUNI_ALPHA, /* U+00a116: YI SYLLABLE DU*/
+ RTUNI_ALPHA, /* U+00a117: YI SYLLABLE DUP*/
+ RTUNI_ALPHA, /* U+00a118: YI SYLLABLE DURX*/
+ RTUNI_ALPHA, /* U+00a119: YI SYLLABLE DUR*/
+ RTUNI_ALPHA, /* U+00a11a: YI SYLLABLE TIT*/
+ RTUNI_ALPHA, /* U+00a11b: YI SYLLABLE TIX*/
+ RTUNI_ALPHA, /* U+00a11c: YI SYLLABLE TI*/
+ RTUNI_ALPHA, /* U+00a11d: YI SYLLABLE TIP*/
+ RTUNI_ALPHA, /* U+00a11e: YI SYLLABLE TIEX*/
+ RTUNI_ALPHA, /* U+00a11f: YI SYLLABLE TIE*/
+ RTUNI_ALPHA, /* U+00a120: YI SYLLABLE TIEP*/
+ RTUNI_ALPHA, /* U+00a121: YI SYLLABLE TAT*/
+ RTUNI_ALPHA, /* U+00a122: YI SYLLABLE TAX*/
+ RTUNI_ALPHA, /* U+00a123: YI SYLLABLE TA*/
+ RTUNI_ALPHA, /* U+00a124: YI SYLLABLE TAP*/
+ RTUNI_ALPHA, /* U+00a125: YI SYLLABLE TUOT*/
+ RTUNI_ALPHA, /* U+00a126: YI SYLLABLE TUOX*/
+ RTUNI_ALPHA, /* U+00a127: YI SYLLABLE TUO*/
+ RTUNI_ALPHA, /* U+00a128: YI SYLLABLE TUOP*/
+ RTUNI_ALPHA, /* U+00a129: YI SYLLABLE TOT*/
+ RTUNI_ALPHA, /* U+00a12a: YI SYLLABLE TOX*/
+ RTUNI_ALPHA, /* U+00a12b: YI SYLLABLE TO*/
+ RTUNI_ALPHA, /* U+00a12c: YI SYLLABLE TOP*/
+ RTUNI_ALPHA, /* U+00a12d: YI SYLLABLE TEX*/
+ RTUNI_ALPHA, /* U+00a12e: YI SYLLABLE TE*/
+ RTUNI_ALPHA, /* U+00a12f: YI SYLLABLE TEP*/
+ RTUNI_ALPHA, /* U+00a130: YI SYLLABLE TUT*/
+ RTUNI_ALPHA, /* U+00a131: YI SYLLABLE TUX*/
+ RTUNI_ALPHA, /* U+00a132: YI SYLLABLE TU*/
+ RTUNI_ALPHA, /* U+00a133: YI SYLLABLE TUP*/
+ RTUNI_ALPHA, /* U+00a134: YI SYLLABLE TURX*/
+ RTUNI_ALPHA, /* U+00a135: YI SYLLABLE TUR*/
+ RTUNI_ALPHA, /* U+00a136: YI SYLLABLE DDIT*/
+ RTUNI_ALPHA, /* U+00a137: YI SYLLABLE DDIX*/
+ RTUNI_ALPHA, /* U+00a138: YI SYLLABLE DDI*/
+ RTUNI_ALPHA, /* U+00a139: YI SYLLABLE DDIP*/
+ RTUNI_ALPHA, /* U+00a13a: YI SYLLABLE DDIEX*/
+ RTUNI_ALPHA, /* U+00a13b: YI SYLLABLE DDIE*/
+ RTUNI_ALPHA, /* U+00a13c: YI SYLLABLE DDIEP*/
+ RTUNI_ALPHA, /* U+00a13d: YI SYLLABLE DDAT*/
+ RTUNI_ALPHA, /* U+00a13e: YI SYLLABLE DDAX*/
+ RTUNI_ALPHA, /* U+00a13f: YI SYLLABLE DDA*/
+ RTUNI_ALPHA, /* U+00a140: YI SYLLABLE DDAP*/
+ RTUNI_ALPHA, /* U+00a141: YI SYLLABLE DDUOX*/
+ RTUNI_ALPHA, /* U+00a142: YI SYLLABLE DDUO*/
+ RTUNI_ALPHA, /* U+00a143: YI SYLLABLE DDUOP*/
+ RTUNI_ALPHA, /* U+00a144: YI SYLLABLE DDOT*/
+ RTUNI_ALPHA, /* U+00a145: YI SYLLABLE DDOX*/
+ RTUNI_ALPHA, /* U+00a146: YI SYLLABLE DDO*/
+ RTUNI_ALPHA, /* U+00a147: YI SYLLABLE DDOP*/
+ RTUNI_ALPHA, /* U+00a148: YI SYLLABLE DDEX*/
+ RTUNI_ALPHA, /* U+00a149: YI SYLLABLE DDE*/
+ RTUNI_ALPHA, /* U+00a14a: YI SYLLABLE DDEP*/
+ RTUNI_ALPHA, /* U+00a14b: YI SYLLABLE DDUT*/
+ RTUNI_ALPHA, /* U+00a14c: YI SYLLABLE DDUX*/
+ RTUNI_ALPHA, /* U+00a14d: YI SYLLABLE DDU*/
+ RTUNI_ALPHA, /* U+00a14e: YI SYLLABLE DDUP*/
+ RTUNI_ALPHA, /* U+00a14f: YI SYLLABLE DDURX*/
+ RTUNI_ALPHA, /* U+00a150: YI SYLLABLE DDUR*/
+ RTUNI_ALPHA, /* U+00a151: YI SYLLABLE NDIT*/
+ RTUNI_ALPHA, /* U+00a152: YI SYLLABLE NDIX*/
+ RTUNI_ALPHA, /* U+00a153: YI SYLLABLE NDI*/
+ RTUNI_ALPHA, /* U+00a154: YI SYLLABLE NDIP*/
+ RTUNI_ALPHA, /* U+00a155: YI SYLLABLE NDIEX*/
+ RTUNI_ALPHA, /* U+00a156: YI SYLLABLE NDIE*/
+ RTUNI_ALPHA, /* U+00a157: YI SYLLABLE NDAT*/
+ RTUNI_ALPHA, /* U+00a158: YI SYLLABLE NDAX*/
+ RTUNI_ALPHA, /* U+00a159: YI SYLLABLE NDA*/
+ RTUNI_ALPHA, /* U+00a15a: YI SYLLABLE NDAP*/
+ RTUNI_ALPHA, /* U+00a15b: YI SYLLABLE NDOT*/
+ RTUNI_ALPHA, /* U+00a15c: YI SYLLABLE NDOX*/
+ RTUNI_ALPHA, /* U+00a15d: YI SYLLABLE NDO*/
+ RTUNI_ALPHA, /* U+00a15e: YI SYLLABLE NDOP*/
+ RTUNI_ALPHA, /* U+00a15f: YI SYLLABLE NDEX*/
+ RTUNI_ALPHA, /* U+00a160: YI SYLLABLE NDE*/
+ RTUNI_ALPHA, /* U+00a161: YI SYLLABLE NDEP*/
+ RTUNI_ALPHA, /* U+00a162: YI SYLLABLE NDUT*/
+ RTUNI_ALPHA, /* U+00a163: YI SYLLABLE NDUX*/
+ RTUNI_ALPHA, /* U+00a164: YI SYLLABLE NDU*/
+ RTUNI_ALPHA, /* U+00a165: YI SYLLABLE NDUP*/
+ RTUNI_ALPHA, /* U+00a166: YI SYLLABLE NDURX*/
+ RTUNI_ALPHA, /* U+00a167: YI SYLLABLE NDUR*/
+ RTUNI_ALPHA, /* U+00a168: YI SYLLABLE HNIT*/
+ RTUNI_ALPHA, /* U+00a169: YI SYLLABLE HNIX*/
+ RTUNI_ALPHA, /* U+00a16a: YI SYLLABLE HNI*/
+ RTUNI_ALPHA, /* U+00a16b: YI SYLLABLE HNIP*/
+ RTUNI_ALPHA, /* U+00a16c: YI SYLLABLE HNIET*/
+ RTUNI_ALPHA, /* U+00a16d: YI SYLLABLE HNIEX*/
+ RTUNI_ALPHA, /* U+00a16e: YI SYLLABLE HNIE*/
+ RTUNI_ALPHA, /* U+00a16f: YI SYLLABLE HNIEP*/
+ RTUNI_ALPHA, /* U+00a170: YI SYLLABLE HNAT*/
+ RTUNI_ALPHA, /* U+00a171: YI SYLLABLE HNAX*/
+ RTUNI_ALPHA, /* U+00a172: YI SYLLABLE HNA*/
+ RTUNI_ALPHA, /* U+00a173: YI SYLLABLE HNAP*/
+ RTUNI_ALPHA, /* U+00a174: YI SYLLABLE HNUOX*/
+ RTUNI_ALPHA, /* U+00a175: YI SYLLABLE HNUO*/
+ RTUNI_ALPHA, /* U+00a176: YI SYLLABLE HNOT*/
+ RTUNI_ALPHA, /* U+00a177: YI SYLLABLE HNOX*/
+ RTUNI_ALPHA, /* U+00a178: YI SYLLABLE HNOP*/
+ RTUNI_ALPHA, /* U+00a179: YI SYLLABLE HNEX*/
+ RTUNI_ALPHA, /* U+00a17a: YI SYLLABLE HNE*/
+ RTUNI_ALPHA, /* U+00a17b: YI SYLLABLE HNEP*/
+ RTUNI_ALPHA, /* U+00a17c: YI SYLLABLE HNUT*/
+ RTUNI_ALPHA, /* U+00a17d: YI SYLLABLE NIT*/
+ RTUNI_ALPHA, /* U+00a17e: YI SYLLABLE NIX*/
+ RTUNI_ALPHA, /* U+00a17f: YI SYLLABLE NI*/
+ RTUNI_ALPHA, /* U+00a180: YI SYLLABLE NIP*/
+ RTUNI_ALPHA, /* U+00a181: YI SYLLABLE NIEX*/
+ RTUNI_ALPHA, /* U+00a182: YI SYLLABLE NIE*/
+ RTUNI_ALPHA, /* U+00a183: YI SYLLABLE NIEP*/
+ RTUNI_ALPHA, /* U+00a184: YI SYLLABLE NAX*/
+ RTUNI_ALPHA, /* U+00a185: YI SYLLABLE NA*/
+ RTUNI_ALPHA, /* U+00a186: YI SYLLABLE NAP*/
+ RTUNI_ALPHA, /* U+00a187: YI SYLLABLE NUOX*/
+ RTUNI_ALPHA, /* U+00a188: YI SYLLABLE NUO*/
+ RTUNI_ALPHA, /* U+00a189: YI SYLLABLE NUOP*/
+ RTUNI_ALPHA, /* U+00a18a: YI SYLLABLE NOT*/
+ RTUNI_ALPHA, /* U+00a18b: YI SYLLABLE NOX*/
+ RTUNI_ALPHA, /* U+00a18c: YI SYLLABLE NO*/
+ RTUNI_ALPHA, /* U+00a18d: YI SYLLABLE NOP*/
+ RTUNI_ALPHA, /* U+00a18e: YI SYLLABLE NEX*/
+ RTUNI_ALPHA, /* U+00a18f: YI SYLLABLE NE*/
+ RTUNI_ALPHA, /* U+00a190: YI SYLLABLE NEP*/
+ RTUNI_ALPHA, /* U+00a191: YI SYLLABLE NUT*/
+ RTUNI_ALPHA, /* U+00a192: YI SYLLABLE NUX*/
+ RTUNI_ALPHA, /* U+00a193: YI SYLLABLE NU*/
+ RTUNI_ALPHA, /* U+00a194: YI SYLLABLE NUP*/
+ RTUNI_ALPHA, /* U+00a195: YI SYLLABLE NURX*/
+ RTUNI_ALPHA, /* U+00a196: YI SYLLABLE NUR*/
+ RTUNI_ALPHA, /* U+00a197: YI SYLLABLE HLIT*/
+ RTUNI_ALPHA, /* U+00a198: YI SYLLABLE HLIX*/
+ RTUNI_ALPHA, /* U+00a199: YI SYLLABLE HLI*/
+ RTUNI_ALPHA, /* U+00a19a: YI SYLLABLE HLIP*/
+ RTUNI_ALPHA, /* U+00a19b: YI SYLLABLE HLIEX*/
+ RTUNI_ALPHA, /* U+00a19c: YI SYLLABLE HLIE*/
+ RTUNI_ALPHA, /* U+00a19d: YI SYLLABLE HLIEP*/
+ RTUNI_ALPHA, /* U+00a19e: YI SYLLABLE HLAT*/
+ RTUNI_ALPHA, /* U+00a19f: YI SYLLABLE HLAX*/
+ RTUNI_ALPHA, /* U+00a1a0: YI SYLLABLE HLA*/
+ RTUNI_ALPHA, /* U+00a1a1: YI SYLLABLE HLAP*/
+ RTUNI_ALPHA, /* U+00a1a2: YI SYLLABLE HLUOX*/
+ RTUNI_ALPHA, /* U+00a1a3: YI SYLLABLE HLUO*/
+ RTUNI_ALPHA, /* U+00a1a4: YI SYLLABLE HLUOP*/
+ RTUNI_ALPHA, /* U+00a1a5: YI SYLLABLE HLOX*/
+ RTUNI_ALPHA, /* U+00a1a6: YI SYLLABLE HLO*/
+ RTUNI_ALPHA, /* U+00a1a7: YI SYLLABLE HLOP*/
+ RTUNI_ALPHA, /* U+00a1a8: YI SYLLABLE HLEX*/
+ RTUNI_ALPHA, /* U+00a1a9: YI SYLLABLE HLE*/
+ RTUNI_ALPHA, /* U+00a1aa: YI SYLLABLE HLEP*/
+ RTUNI_ALPHA, /* U+00a1ab: YI SYLLABLE HLUT*/
+ RTUNI_ALPHA, /* U+00a1ac: YI SYLLABLE HLUX*/
+ RTUNI_ALPHA, /* U+00a1ad: YI SYLLABLE HLU*/
+ RTUNI_ALPHA, /* U+00a1ae: YI SYLLABLE HLUP*/
+ RTUNI_ALPHA, /* U+00a1af: YI SYLLABLE HLURX*/
+ RTUNI_ALPHA, /* U+00a1b0: YI SYLLABLE HLUR*/
+ RTUNI_ALPHA, /* U+00a1b1: YI SYLLABLE HLYT*/
+ RTUNI_ALPHA, /* U+00a1b2: YI SYLLABLE HLYX*/
+ RTUNI_ALPHA, /* U+00a1b3: YI SYLLABLE HLY*/
+ RTUNI_ALPHA, /* U+00a1b4: YI SYLLABLE HLYP*/
+ RTUNI_ALPHA, /* U+00a1b5: YI SYLLABLE HLYRX*/
+ RTUNI_ALPHA, /* U+00a1b6: YI SYLLABLE HLYR*/
+ RTUNI_ALPHA, /* U+00a1b7: YI SYLLABLE LIT*/
+ RTUNI_ALPHA, /* U+00a1b8: YI SYLLABLE LIX*/
+ RTUNI_ALPHA, /* U+00a1b9: YI SYLLABLE LI*/
+ RTUNI_ALPHA, /* U+00a1ba: YI SYLLABLE LIP*/
+ RTUNI_ALPHA, /* U+00a1bb: YI SYLLABLE LIET*/
+ RTUNI_ALPHA, /* U+00a1bc: YI SYLLABLE LIEX*/
+ RTUNI_ALPHA, /* U+00a1bd: YI SYLLABLE LIE*/
+ RTUNI_ALPHA, /* U+00a1be: YI SYLLABLE LIEP*/
+ RTUNI_ALPHA, /* U+00a1bf: YI SYLLABLE LAT*/
+ RTUNI_ALPHA, /* U+00a1c0: YI SYLLABLE LAX*/
+ RTUNI_ALPHA, /* U+00a1c1: YI SYLLABLE LA*/
+ RTUNI_ALPHA, /* U+00a1c2: YI SYLLABLE LAP*/
+ RTUNI_ALPHA, /* U+00a1c3: YI SYLLABLE LUOT*/
+ RTUNI_ALPHA, /* U+00a1c4: YI SYLLABLE LUOX*/
+ RTUNI_ALPHA, /* U+00a1c5: YI SYLLABLE LUO*/
+ RTUNI_ALPHA, /* U+00a1c6: YI SYLLABLE LUOP*/
+ RTUNI_ALPHA, /* U+00a1c7: YI SYLLABLE LOT*/
+ RTUNI_ALPHA, /* U+00a1c8: YI SYLLABLE LOX*/
+ RTUNI_ALPHA, /* U+00a1c9: YI SYLLABLE LO*/
+ RTUNI_ALPHA, /* U+00a1ca: YI SYLLABLE LOP*/
+ RTUNI_ALPHA, /* U+00a1cb: YI SYLLABLE LEX*/
+ RTUNI_ALPHA, /* U+00a1cc: YI SYLLABLE LE*/
+ RTUNI_ALPHA, /* U+00a1cd: YI SYLLABLE LEP*/
+ RTUNI_ALPHA, /* U+00a1ce: YI SYLLABLE LUT*/
+ RTUNI_ALPHA, /* U+00a1cf: YI SYLLABLE LUX*/
+ RTUNI_ALPHA, /* U+00a1d0: YI SYLLABLE LU*/
+ RTUNI_ALPHA, /* U+00a1d1: YI SYLLABLE LUP*/
+ RTUNI_ALPHA, /* U+00a1d2: YI SYLLABLE LURX*/
+ RTUNI_ALPHA, /* U+00a1d3: YI SYLLABLE LUR*/
+ RTUNI_ALPHA, /* U+00a1d4: YI SYLLABLE LYT*/
+ RTUNI_ALPHA, /* U+00a1d5: YI SYLLABLE LYX*/
+ RTUNI_ALPHA, /* U+00a1d6: YI SYLLABLE LY*/
+ RTUNI_ALPHA, /* U+00a1d7: YI SYLLABLE LYP*/
+ RTUNI_ALPHA, /* U+00a1d8: YI SYLLABLE LYRX*/
+ RTUNI_ALPHA, /* U+00a1d9: YI SYLLABLE LYR*/
+ RTUNI_ALPHA, /* U+00a1da: YI SYLLABLE GIT*/
+ RTUNI_ALPHA, /* U+00a1db: YI SYLLABLE GIX*/
+ RTUNI_ALPHA, /* U+00a1dc: YI SYLLABLE GI*/
+ RTUNI_ALPHA, /* U+00a1dd: YI SYLLABLE GIP*/
+ RTUNI_ALPHA, /* U+00a1de: YI SYLLABLE GIET*/
+ RTUNI_ALPHA, /* U+00a1df: YI SYLLABLE GIEX*/
+ RTUNI_ALPHA, /* U+00a1e0: YI SYLLABLE GIE*/
+ RTUNI_ALPHA, /* U+00a1e1: YI SYLLABLE GIEP*/
+ RTUNI_ALPHA, /* U+00a1e2: YI SYLLABLE GAT*/
+ RTUNI_ALPHA, /* U+00a1e3: YI SYLLABLE GAX*/
+ RTUNI_ALPHA, /* U+00a1e4: YI SYLLABLE GA*/
+ RTUNI_ALPHA, /* U+00a1e5: YI SYLLABLE GAP*/
+ RTUNI_ALPHA, /* U+00a1e6: YI SYLLABLE GUOT*/
+ RTUNI_ALPHA, /* U+00a1e7: YI SYLLABLE GUOX*/
+ RTUNI_ALPHA, /* U+00a1e8: YI SYLLABLE GUO*/
+ RTUNI_ALPHA, /* U+00a1e9: YI SYLLABLE GUOP*/
+ RTUNI_ALPHA, /* U+00a1ea: YI SYLLABLE GOT*/
+ RTUNI_ALPHA, /* U+00a1eb: YI SYLLABLE GOX*/
+ RTUNI_ALPHA, /* U+00a1ec: YI SYLLABLE GO*/
+ RTUNI_ALPHA, /* U+00a1ed: YI SYLLABLE GOP*/
+ RTUNI_ALPHA, /* U+00a1ee: YI SYLLABLE GET*/
+ RTUNI_ALPHA, /* U+00a1ef: YI SYLLABLE GEX*/
+ RTUNI_ALPHA, /* U+00a1f0: YI SYLLABLE GE*/
+ RTUNI_ALPHA, /* U+00a1f1: YI SYLLABLE GEP*/
+ RTUNI_ALPHA, /* U+00a1f2: YI SYLLABLE GUT*/
+ RTUNI_ALPHA, /* U+00a1f3: YI SYLLABLE GUX*/
+ RTUNI_ALPHA, /* U+00a1f4: YI SYLLABLE GU*/
+ RTUNI_ALPHA, /* U+00a1f5: YI SYLLABLE GUP*/
+ RTUNI_ALPHA, /* U+00a1f6: YI SYLLABLE GURX*/
+ RTUNI_ALPHA, /* U+00a1f7: YI SYLLABLE GUR*/
+ RTUNI_ALPHA, /* U+00a1f8: YI SYLLABLE KIT*/
+ RTUNI_ALPHA, /* U+00a1f9: YI SYLLABLE KIX*/
+ RTUNI_ALPHA, /* U+00a1fa: YI SYLLABLE KI*/
+ RTUNI_ALPHA, /* U+00a1fb: YI SYLLABLE KIP*/
+ RTUNI_ALPHA, /* U+00a1fc: YI SYLLABLE KIEX*/
+ RTUNI_ALPHA, /* U+00a1fd: YI SYLLABLE KIE*/
+ RTUNI_ALPHA, /* U+00a1fe: YI SYLLABLE KIEP*/
+ RTUNI_ALPHA, /* U+00a1ff: YI SYLLABLE KAT*/
+ RTUNI_ALPHA, /* U+00a200: YI SYLLABLE KAX*/
+ RTUNI_ALPHA, /* U+00a201: YI SYLLABLE KA*/
+ RTUNI_ALPHA, /* U+00a202: YI SYLLABLE KAP*/
+ RTUNI_ALPHA, /* U+00a203: YI SYLLABLE KUOX*/
+ RTUNI_ALPHA, /* U+00a204: YI SYLLABLE KUO*/
+ RTUNI_ALPHA, /* U+00a205: YI SYLLABLE KUOP*/
+ RTUNI_ALPHA, /* U+00a206: YI SYLLABLE KOT*/
+ RTUNI_ALPHA, /* U+00a207: YI SYLLABLE KOX*/
+ RTUNI_ALPHA, /* U+00a208: YI SYLLABLE KO*/
+ RTUNI_ALPHA, /* U+00a209: YI SYLLABLE KOP*/
+ RTUNI_ALPHA, /* U+00a20a: YI SYLLABLE KET*/
+ RTUNI_ALPHA, /* U+00a20b: YI SYLLABLE KEX*/
+ RTUNI_ALPHA, /* U+00a20c: YI SYLLABLE KE*/
+ RTUNI_ALPHA, /* U+00a20d: YI SYLLABLE KEP*/
+ RTUNI_ALPHA, /* U+00a20e: YI SYLLABLE KUT*/
+ RTUNI_ALPHA, /* U+00a20f: YI SYLLABLE KUX*/
+ RTUNI_ALPHA, /* U+00a210: YI SYLLABLE KU*/
+ RTUNI_ALPHA, /* U+00a211: YI SYLLABLE KUP*/
+ RTUNI_ALPHA, /* U+00a212: YI SYLLABLE KURX*/
+ RTUNI_ALPHA, /* U+00a213: YI SYLLABLE KUR*/
+ RTUNI_ALPHA, /* U+00a214: YI SYLLABLE GGIT*/
+ RTUNI_ALPHA, /* U+00a215: YI SYLLABLE GGIX*/
+ RTUNI_ALPHA, /* U+00a216: YI SYLLABLE GGI*/
+ RTUNI_ALPHA, /* U+00a217: YI SYLLABLE GGIEX*/
+ RTUNI_ALPHA, /* U+00a218: YI SYLLABLE GGIE*/
+ RTUNI_ALPHA, /* U+00a219: YI SYLLABLE GGIEP*/
+ RTUNI_ALPHA, /* U+00a21a: YI SYLLABLE GGAT*/
+ RTUNI_ALPHA, /* U+00a21b: YI SYLLABLE GGAX*/
+ RTUNI_ALPHA, /* U+00a21c: YI SYLLABLE GGA*/
+ RTUNI_ALPHA, /* U+00a21d: YI SYLLABLE GGAP*/
+ RTUNI_ALPHA, /* U+00a21e: YI SYLLABLE GGUOT*/
+ RTUNI_ALPHA, /* U+00a21f: YI SYLLABLE GGUOX*/
+ RTUNI_ALPHA, /* U+00a220: YI SYLLABLE GGUO*/
+ RTUNI_ALPHA, /* U+00a221: YI SYLLABLE GGUOP*/
+ RTUNI_ALPHA, /* U+00a222: YI SYLLABLE GGOT*/
+ RTUNI_ALPHA, /* U+00a223: YI SYLLABLE GGOX*/
+ RTUNI_ALPHA, /* U+00a224: YI SYLLABLE GGO*/
+ RTUNI_ALPHA, /* U+00a225: YI SYLLABLE GGOP*/
+ RTUNI_ALPHA, /* U+00a226: YI SYLLABLE GGET*/
+ RTUNI_ALPHA, /* U+00a227: YI SYLLABLE GGEX*/
+ RTUNI_ALPHA, /* U+00a228: YI SYLLABLE GGE*/
+ RTUNI_ALPHA, /* U+00a229: YI SYLLABLE GGEP*/
+ RTUNI_ALPHA, /* U+00a22a: YI SYLLABLE GGUT*/
+ RTUNI_ALPHA, /* U+00a22b: YI SYLLABLE GGUX*/
+ RTUNI_ALPHA, /* U+00a22c: YI SYLLABLE GGU*/
+ RTUNI_ALPHA, /* U+00a22d: YI SYLLABLE GGUP*/
+ RTUNI_ALPHA, /* U+00a22e: YI SYLLABLE GGURX*/
+ RTUNI_ALPHA, /* U+00a22f: YI SYLLABLE GGUR*/
+ RTUNI_ALPHA, /* U+00a230: YI SYLLABLE MGIEX*/
+ RTUNI_ALPHA, /* U+00a231: YI SYLLABLE MGIE*/
+ RTUNI_ALPHA, /* U+00a232: YI SYLLABLE MGAT*/
+ RTUNI_ALPHA, /* U+00a233: YI SYLLABLE MGAX*/
+ RTUNI_ALPHA, /* U+00a234: YI SYLLABLE MGA*/
+ RTUNI_ALPHA, /* U+00a235: YI SYLLABLE MGAP*/
+ RTUNI_ALPHA, /* U+00a236: YI SYLLABLE MGUOX*/
+ RTUNI_ALPHA, /* U+00a237: YI SYLLABLE MGUO*/
+ RTUNI_ALPHA, /* U+00a238: YI SYLLABLE MGUOP*/
+ RTUNI_ALPHA, /* U+00a239: YI SYLLABLE MGOT*/
+ RTUNI_ALPHA, /* U+00a23a: YI SYLLABLE MGOX*/
+ RTUNI_ALPHA, /* U+00a23b: YI SYLLABLE MGO*/
+ RTUNI_ALPHA, /* U+00a23c: YI SYLLABLE MGOP*/
+ RTUNI_ALPHA, /* U+00a23d: YI SYLLABLE MGEX*/
+ RTUNI_ALPHA, /* U+00a23e: YI SYLLABLE MGE*/
+ RTUNI_ALPHA, /* U+00a23f: YI SYLLABLE MGEP*/
+ RTUNI_ALPHA, /* U+00a240: YI SYLLABLE MGUT*/
+ RTUNI_ALPHA, /* U+00a241: YI SYLLABLE MGUX*/
+ RTUNI_ALPHA, /* U+00a242: YI SYLLABLE MGU*/
+ RTUNI_ALPHA, /* U+00a243: YI SYLLABLE MGUP*/
+ RTUNI_ALPHA, /* U+00a244: YI SYLLABLE MGURX*/
+ RTUNI_ALPHA, /* U+00a245: YI SYLLABLE MGUR*/
+ RTUNI_ALPHA, /* U+00a246: YI SYLLABLE HXIT*/
+ RTUNI_ALPHA, /* U+00a247: YI SYLLABLE HXIX*/
+ RTUNI_ALPHA, /* U+00a248: YI SYLLABLE HXI*/
+ RTUNI_ALPHA, /* U+00a249: YI SYLLABLE HXIP*/
+ RTUNI_ALPHA, /* U+00a24a: YI SYLLABLE HXIET*/
+ RTUNI_ALPHA, /* U+00a24b: YI SYLLABLE HXIEX*/
+ RTUNI_ALPHA, /* U+00a24c: YI SYLLABLE HXIE*/
+ RTUNI_ALPHA, /* U+00a24d: YI SYLLABLE HXIEP*/
+ RTUNI_ALPHA, /* U+00a24e: YI SYLLABLE HXAT*/
+ RTUNI_ALPHA, /* U+00a24f: YI SYLLABLE HXAX*/
+ RTUNI_ALPHA, /* U+00a250: YI SYLLABLE HXA*/
+ RTUNI_ALPHA, /* U+00a251: YI SYLLABLE HXAP*/
+ RTUNI_ALPHA, /* U+00a252: YI SYLLABLE HXUOT*/
+ RTUNI_ALPHA, /* U+00a253: YI SYLLABLE HXUOX*/
+ RTUNI_ALPHA, /* U+00a254: YI SYLLABLE HXUO*/
+ RTUNI_ALPHA, /* U+00a255: YI SYLLABLE HXUOP*/
+ RTUNI_ALPHA, /* U+00a256: YI SYLLABLE HXOT*/
+ RTUNI_ALPHA, /* U+00a257: YI SYLLABLE HXOX*/
+ RTUNI_ALPHA, /* U+00a258: YI SYLLABLE HXO*/
+ RTUNI_ALPHA, /* U+00a259: YI SYLLABLE HXOP*/
+ RTUNI_ALPHA, /* U+00a25a: YI SYLLABLE HXEX*/
+ RTUNI_ALPHA, /* U+00a25b: YI SYLLABLE HXE*/
+ RTUNI_ALPHA, /* U+00a25c: YI SYLLABLE HXEP*/
+ RTUNI_ALPHA, /* U+00a25d: YI SYLLABLE NGIEX*/
+ RTUNI_ALPHA, /* U+00a25e: YI SYLLABLE NGIE*/
+ RTUNI_ALPHA, /* U+00a25f: YI SYLLABLE NGIEP*/
+ RTUNI_ALPHA, /* U+00a260: YI SYLLABLE NGAT*/
+ RTUNI_ALPHA, /* U+00a261: YI SYLLABLE NGAX*/
+ RTUNI_ALPHA, /* U+00a262: YI SYLLABLE NGA*/
+ RTUNI_ALPHA, /* U+00a263: YI SYLLABLE NGAP*/
+ RTUNI_ALPHA, /* U+00a264: YI SYLLABLE NGUOT*/
+ RTUNI_ALPHA, /* U+00a265: YI SYLLABLE NGUOX*/
+ RTUNI_ALPHA, /* U+00a266: YI SYLLABLE NGUO*/
+ RTUNI_ALPHA, /* U+00a267: YI SYLLABLE NGOT*/
+ RTUNI_ALPHA, /* U+00a268: YI SYLLABLE NGOX*/
+ RTUNI_ALPHA, /* U+00a269: YI SYLLABLE NGO*/
+ RTUNI_ALPHA, /* U+00a26a: YI SYLLABLE NGOP*/
+ RTUNI_ALPHA, /* U+00a26b: YI SYLLABLE NGEX*/
+ RTUNI_ALPHA, /* U+00a26c: YI SYLLABLE NGE*/
+ RTUNI_ALPHA, /* U+00a26d: YI SYLLABLE NGEP*/
+ RTUNI_ALPHA, /* U+00a26e: YI SYLLABLE HIT*/
+ RTUNI_ALPHA, /* U+00a26f: YI SYLLABLE HIEX*/
+ RTUNI_ALPHA, /* U+00a270: YI SYLLABLE HIE*/
+ RTUNI_ALPHA, /* U+00a271: YI SYLLABLE HAT*/
+ RTUNI_ALPHA, /* U+00a272: YI SYLLABLE HAX*/
+ RTUNI_ALPHA, /* U+00a273: YI SYLLABLE HA*/
+ RTUNI_ALPHA, /* U+00a274: YI SYLLABLE HAP*/
+ RTUNI_ALPHA, /* U+00a275: YI SYLLABLE HUOT*/
+ RTUNI_ALPHA, /* U+00a276: YI SYLLABLE HUOX*/
+ RTUNI_ALPHA, /* U+00a277: YI SYLLABLE HUO*/
+ RTUNI_ALPHA, /* U+00a278: YI SYLLABLE HUOP*/
+ RTUNI_ALPHA, /* U+00a279: YI SYLLABLE HOT*/
+ RTUNI_ALPHA, /* U+00a27a: YI SYLLABLE HOX*/
+ RTUNI_ALPHA, /* U+00a27b: YI SYLLABLE HO*/
+ RTUNI_ALPHA, /* U+00a27c: YI SYLLABLE HOP*/
+ RTUNI_ALPHA, /* U+00a27d: YI SYLLABLE HEX*/
+ RTUNI_ALPHA, /* U+00a27e: YI SYLLABLE HE*/
+ RTUNI_ALPHA, /* U+00a27f: YI SYLLABLE HEP*/
+ RTUNI_ALPHA, /* U+00a280: YI SYLLABLE WAT*/
+ RTUNI_ALPHA, /* U+00a281: YI SYLLABLE WAX*/
+ RTUNI_ALPHA, /* U+00a282: YI SYLLABLE WA*/
+ RTUNI_ALPHA, /* U+00a283: YI SYLLABLE WAP*/
+ RTUNI_ALPHA, /* U+00a284: YI SYLLABLE WUOX*/
+ RTUNI_ALPHA, /* U+00a285: YI SYLLABLE WUO*/
+ RTUNI_ALPHA, /* U+00a286: YI SYLLABLE WUOP*/
+ RTUNI_ALPHA, /* U+00a287: YI SYLLABLE WOX*/
+ RTUNI_ALPHA, /* U+00a288: YI SYLLABLE WO*/
+ RTUNI_ALPHA, /* U+00a289: YI SYLLABLE WOP*/
+ RTUNI_ALPHA, /* U+00a28a: YI SYLLABLE WEX*/
+ RTUNI_ALPHA, /* U+00a28b: YI SYLLABLE WE*/
+ RTUNI_ALPHA, /* U+00a28c: YI SYLLABLE WEP*/
+ RTUNI_ALPHA, /* U+00a28d: YI SYLLABLE ZIT*/
+ RTUNI_ALPHA, /* U+00a28e: YI SYLLABLE ZIX*/
+ RTUNI_ALPHA, /* U+00a28f: YI SYLLABLE ZI*/
+ RTUNI_ALPHA, /* U+00a290: YI SYLLABLE ZIP*/
+ RTUNI_ALPHA, /* U+00a291: YI SYLLABLE ZIEX*/
+ RTUNI_ALPHA, /* U+00a292: YI SYLLABLE ZIE*/
+ RTUNI_ALPHA, /* U+00a293: YI SYLLABLE ZIEP*/
+ RTUNI_ALPHA, /* U+00a294: YI SYLLABLE ZAT*/
+ RTUNI_ALPHA, /* U+00a295: YI SYLLABLE ZAX*/
+ RTUNI_ALPHA, /* U+00a296: YI SYLLABLE ZA*/
+ RTUNI_ALPHA, /* U+00a297: YI SYLLABLE ZAP*/
+ RTUNI_ALPHA, /* U+00a298: YI SYLLABLE ZUOX*/
+ RTUNI_ALPHA, /* U+00a299: YI SYLLABLE ZUO*/
+ RTUNI_ALPHA, /* U+00a29a: YI SYLLABLE ZUOP*/
+ RTUNI_ALPHA, /* U+00a29b: YI SYLLABLE ZOT*/
+ RTUNI_ALPHA, /* U+00a29c: YI SYLLABLE ZOX*/
+ RTUNI_ALPHA, /* U+00a29d: YI SYLLABLE ZO*/
+ RTUNI_ALPHA, /* U+00a29e: YI SYLLABLE ZOP*/
+ RTUNI_ALPHA, /* U+00a29f: YI SYLLABLE ZEX*/
+ RTUNI_ALPHA, /* U+00a2a0: YI SYLLABLE ZE*/
+ RTUNI_ALPHA, /* U+00a2a1: YI SYLLABLE ZEP*/
+ RTUNI_ALPHA, /* U+00a2a2: YI SYLLABLE ZUT*/
+ RTUNI_ALPHA, /* U+00a2a3: YI SYLLABLE ZUX*/
+ RTUNI_ALPHA, /* U+00a2a4: YI SYLLABLE ZU*/
+ RTUNI_ALPHA, /* U+00a2a5: YI SYLLABLE ZUP*/
+ RTUNI_ALPHA, /* U+00a2a6: YI SYLLABLE ZURX*/
+ RTUNI_ALPHA, /* U+00a2a7: YI SYLLABLE ZUR*/
+ RTUNI_ALPHA, /* U+00a2a8: YI SYLLABLE ZYT*/
+ RTUNI_ALPHA, /* U+00a2a9: YI SYLLABLE ZYX*/
+ RTUNI_ALPHA, /* U+00a2aa: YI SYLLABLE ZY*/
+ RTUNI_ALPHA, /* U+00a2ab: YI SYLLABLE ZYP*/
+ RTUNI_ALPHA, /* U+00a2ac: YI SYLLABLE ZYRX*/
+ RTUNI_ALPHA, /* U+00a2ad: YI SYLLABLE ZYR*/
+ RTUNI_ALPHA, /* U+00a2ae: YI SYLLABLE CIT*/
+ RTUNI_ALPHA, /* U+00a2af: YI SYLLABLE CIX*/
+ RTUNI_ALPHA, /* U+00a2b0: YI SYLLABLE CI*/
+ RTUNI_ALPHA, /* U+00a2b1: YI SYLLABLE CIP*/
+ RTUNI_ALPHA, /* U+00a2b2: YI SYLLABLE CIET*/
+ RTUNI_ALPHA, /* U+00a2b3: YI SYLLABLE CIEX*/
+ RTUNI_ALPHA, /* U+00a2b4: YI SYLLABLE CIE*/
+ RTUNI_ALPHA, /* U+00a2b5: YI SYLLABLE CIEP*/
+ RTUNI_ALPHA, /* U+00a2b6: YI SYLLABLE CAT*/
+ RTUNI_ALPHA, /* U+00a2b7: YI SYLLABLE CAX*/
+ RTUNI_ALPHA, /* U+00a2b8: YI SYLLABLE CA*/
+ RTUNI_ALPHA, /* U+00a2b9: YI SYLLABLE CAP*/
+ RTUNI_ALPHA, /* U+00a2ba: YI SYLLABLE CUOX*/
+ RTUNI_ALPHA, /* U+00a2bb: YI SYLLABLE CUO*/
+ RTUNI_ALPHA, /* U+00a2bc: YI SYLLABLE CUOP*/
+ RTUNI_ALPHA, /* U+00a2bd: YI SYLLABLE COT*/
+ RTUNI_ALPHA, /* U+00a2be: YI SYLLABLE COX*/
+ RTUNI_ALPHA, /* U+00a2bf: YI SYLLABLE CO*/
+ RTUNI_ALPHA, /* U+00a2c0: YI SYLLABLE COP*/
+ RTUNI_ALPHA, /* U+00a2c1: YI SYLLABLE CEX*/
+ RTUNI_ALPHA, /* U+00a2c2: YI SYLLABLE CE*/
+ RTUNI_ALPHA, /* U+00a2c3: YI SYLLABLE CEP*/
+ RTUNI_ALPHA, /* U+00a2c4: YI SYLLABLE CUT*/
+ RTUNI_ALPHA, /* U+00a2c5: YI SYLLABLE CUX*/
+ RTUNI_ALPHA, /* U+00a2c6: YI SYLLABLE CU*/
+ RTUNI_ALPHA, /* U+00a2c7: YI SYLLABLE CUP*/
+ RTUNI_ALPHA, /* U+00a2c8: YI SYLLABLE CURX*/
+ RTUNI_ALPHA, /* U+00a2c9: YI SYLLABLE CUR*/
+ RTUNI_ALPHA, /* U+00a2ca: YI SYLLABLE CYT*/
+ RTUNI_ALPHA, /* U+00a2cb: YI SYLLABLE CYX*/
+ RTUNI_ALPHA, /* U+00a2cc: YI SYLLABLE CY*/
+ RTUNI_ALPHA, /* U+00a2cd: YI SYLLABLE CYP*/
+ RTUNI_ALPHA, /* U+00a2ce: YI SYLLABLE CYRX*/
+ RTUNI_ALPHA, /* U+00a2cf: YI SYLLABLE CYR*/
+ RTUNI_ALPHA, /* U+00a2d0: YI SYLLABLE ZZIT*/
+ RTUNI_ALPHA, /* U+00a2d1: YI SYLLABLE ZZIX*/
+ RTUNI_ALPHA, /* U+00a2d2: YI SYLLABLE ZZI*/
+ RTUNI_ALPHA, /* U+00a2d3: YI SYLLABLE ZZIP*/
+ RTUNI_ALPHA, /* U+00a2d4: YI SYLLABLE ZZIET*/
+ RTUNI_ALPHA, /* U+00a2d5: YI SYLLABLE ZZIEX*/
+ RTUNI_ALPHA, /* U+00a2d6: YI SYLLABLE ZZIE*/
+ RTUNI_ALPHA, /* U+00a2d7: YI SYLLABLE ZZIEP*/
+ RTUNI_ALPHA, /* U+00a2d8: YI SYLLABLE ZZAT*/
+ RTUNI_ALPHA, /* U+00a2d9: YI SYLLABLE ZZAX*/
+ RTUNI_ALPHA, /* U+00a2da: YI SYLLABLE ZZA*/
+ RTUNI_ALPHA, /* U+00a2db: YI SYLLABLE ZZAP*/
+ RTUNI_ALPHA, /* U+00a2dc: YI SYLLABLE ZZOX*/
+ RTUNI_ALPHA, /* U+00a2dd: YI SYLLABLE ZZO*/
+ RTUNI_ALPHA, /* U+00a2de: YI SYLLABLE ZZOP*/
+ RTUNI_ALPHA, /* U+00a2df: YI SYLLABLE ZZEX*/
+ RTUNI_ALPHA, /* U+00a2e0: YI SYLLABLE ZZE*/
+ RTUNI_ALPHA, /* U+00a2e1: YI SYLLABLE ZZEP*/
+ RTUNI_ALPHA, /* U+00a2e2: YI SYLLABLE ZZUX*/
+ RTUNI_ALPHA, /* U+00a2e3: YI SYLLABLE ZZU*/
+ RTUNI_ALPHA, /* U+00a2e4: YI SYLLABLE ZZUP*/
+ RTUNI_ALPHA, /* U+00a2e5: YI SYLLABLE ZZURX*/
+ RTUNI_ALPHA, /* U+00a2e6: YI SYLLABLE ZZUR*/
+ RTUNI_ALPHA, /* U+00a2e7: YI SYLLABLE ZZYT*/
+ RTUNI_ALPHA, /* U+00a2e8: YI SYLLABLE ZZYX*/
+ RTUNI_ALPHA, /* U+00a2e9: YI SYLLABLE ZZY*/
+ RTUNI_ALPHA, /* U+00a2ea: YI SYLLABLE ZZYP*/
+ RTUNI_ALPHA, /* U+00a2eb: YI SYLLABLE ZZYRX*/
+ RTUNI_ALPHA, /* U+00a2ec: YI SYLLABLE ZZYR*/
+ RTUNI_ALPHA, /* U+00a2ed: YI SYLLABLE NZIT*/
+ RTUNI_ALPHA, /* U+00a2ee: YI SYLLABLE NZIX*/
+ RTUNI_ALPHA, /* U+00a2ef: YI SYLLABLE NZI*/
+ RTUNI_ALPHA, /* U+00a2f0: YI SYLLABLE NZIP*/
+ RTUNI_ALPHA, /* U+00a2f1: YI SYLLABLE NZIEX*/
+ RTUNI_ALPHA, /* U+00a2f2: YI SYLLABLE NZIE*/
+ RTUNI_ALPHA, /* U+00a2f3: YI SYLLABLE NZIEP*/
+ RTUNI_ALPHA, /* U+00a2f4: YI SYLLABLE NZAT*/
+ RTUNI_ALPHA, /* U+00a2f5: YI SYLLABLE NZAX*/
+ RTUNI_ALPHA, /* U+00a2f6: YI SYLLABLE NZA*/
+ RTUNI_ALPHA, /* U+00a2f7: YI SYLLABLE NZAP*/
+ RTUNI_ALPHA, /* U+00a2f8: YI SYLLABLE NZUOX*/
+ RTUNI_ALPHA, /* U+00a2f9: YI SYLLABLE NZUO*/
+ RTUNI_ALPHA, /* U+00a2fa: YI SYLLABLE NZOX*/
+ RTUNI_ALPHA, /* U+00a2fb: YI SYLLABLE NZOP*/
+ RTUNI_ALPHA, /* U+00a2fc: YI SYLLABLE NZEX*/
+ RTUNI_ALPHA, /* U+00a2fd: YI SYLLABLE NZE*/
+ RTUNI_ALPHA, /* U+00a2fe: YI SYLLABLE NZUX*/
+ RTUNI_ALPHA, /* U+00a2ff: YI SYLLABLE NZU*/
+ RTUNI_ALPHA, /* U+00a300: YI SYLLABLE NZUP*/
+ RTUNI_ALPHA, /* U+00a301: YI SYLLABLE NZURX*/
+ RTUNI_ALPHA, /* U+00a302: YI SYLLABLE NZUR*/
+ RTUNI_ALPHA, /* U+00a303: YI SYLLABLE NZYT*/
+ RTUNI_ALPHA, /* U+00a304: YI SYLLABLE NZYX*/
+ RTUNI_ALPHA, /* U+00a305: YI SYLLABLE NZY*/
+ RTUNI_ALPHA, /* U+00a306: YI SYLLABLE NZYP*/
+ RTUNI_ALPHA, /* U+00a307: YI SYLLABLE NZYRX*/
+ RTUNI_ALPHA, /* U+00a308: YI SYLLABLE NZYR*/
+ RTUNI_ALPHA, /* U+00a309: YI SYLLABLE SIT*/
+ RTUNI_ALPHA, /* U+00a30a: YI SYLLABLE SIX*/
+ RTUNI_ALPHA, /* U+00a30b: YI SYLLABLE SI*/
+ RTUNI_ALPHA, /* U+00a30c: YI SYLLABLE SIP*/
+ RTUNI_ALPHA, /* U+00a30d: YI SYLLABLE SIEX*/
+ RTUNI_ALPHA, /* U+00a30e: YI SYLLABLE SIE*/
+ RTUNI_ALPHA, /* U+00a30f: YI SYLLABLE SIEP*/
+ RTUNI_ALPHA, /* U+00a310: YI SYLLABLE SAT*/
+ RTUNI_ALPHA, /* U+00a311: YI SYLLABLE SAX*/
+ RTUNI_ALPHA, /* U+00a312: YI SYLLABLE SA*/
+ RTUNI_ALPHA, /* U+00a313: YI SYLLABLE SAP*/
+ RTUNI_ALPHA, /* U+00a314: YI SYLLABLE SUOX*/
+ RTUNI_ALPHA, /* U+00a315: YI SYLLABLE SUO*/
+ RTUNI_ALPHA, /* U+00a316: YI SYLLABLE SUOP*/
+ RTUNI_ALPHA, /* U+00a317: YI SYLLABLE SOT*/
+ RTUNI_ALPHA, /* U+00a318: YI SYLLABLE SOX*/
+ RTUNI_ALPHA, /* U+00a319: YI SYLLABLE SO*/
+ RTUNI_ALPHA, /* U+00a31a: YI SYLLABLE SOP*/
+ RTUNI_ALPHA, /* U+00a31b: YI SYLLABLE SEX*/
+ RTUNI_ALPHA, /* U+00a31c: YI SYLLABLE SE*/
+ RTUNI_ALPHA, /* U+00a31d: YI SYLLABLE SEP*/
+ RTUNI_ALPHA, /* U+00a31e: YI SYLLABLE SUT*/
+ RTUNI_ALPHA, /* U+00a31f: YI SYLLABLE SUX*/
+ RTUNI_ALPHA, /* U+00a320: YI SYLLABLE SU*/
+ RTUNI_ALPHA, /* U+00a321: YI SYLLABLE SUP*/
+ RTUNI_ALPHA, /* U+00a322: YI SYLLABLE SURX*/
+ RTUNI_ALPHA, /* U+00a323: YI SYLLABLE SUR*/
+ RTUNI_ALPHA, /* U+00a324: YI SYLLABLE SYT*/
+ RTUNI_ALPHA, /* U+00a325: YI SYLLABLE SYX*/
+ RTUNI_ALPHA, /* U+00a326: YI SYLLABLE SY*/
+ RTUNI_ALPHA, /* U+00a327: YI SYLLABLE SYP*/
+ RTUNI_ALPHA, /* U+00a328: YI SYLLABLE SYRX*/
+ RTUNI_ALPHA, /* U+00a329: YI SYLLABLE SYR*/
+ RTUNI_ALPHA, /* U+00a32a: YI SYLLABLE SSIT*/
+ RTUNI_ALPHA, /* U+00a32b: YI SYLLABLE SSIX*/
+ RTUNI_ALPHA, /* U+00a32c: YI SYLLABLE SSI*/
+ RTUNI_ALPHA, /* U+00a32d: YI SYLLABLE SSIP*/
+ RTUNI_ALPHA, /* U+00a32e: YI SYLLABLE SSIEX*/
+ RTUNI_ALPHA, /* U+00a32f: YI SYLLABLE SSIE*/
+ RTUNI_ALPHA, /* U+00a330: YI SYLLABLE SSIEP*/
+ RTUNI_ALPHA, /* U+00a331: YI SYLLABLE SSAT*/
+ RTUNI_ALPHA, /* U+00a332: YI SYLLABLE SSAX*/
+ RTUNI_ALPHA, /* U+00a333: YI SYLLABLE SSA*/
+ RTUNI_ALPHA, /* U+00a334: YI SYLLABLE SSAP*/
+ RTUNI_ALPHA, /* U+00a335: YI SYLLABLE SSOT*/
+ RTUNI_ALPHA, /* U+00a336: YI SYLLABLE SSOX*/
+ RTUNI_ALPHA, /* U+00a337: YI SYLLABLE SSO*/
+ RTUNI_ALPHA, /* U+00a338: YI SYLLABLE SSOP*/
+ RTUNI_ALPHA, /* U+00a339: YI SYLLABLE SSEX*/
+ RTUNI_ALPHA, /* U+00a33a: YI SYLLABLE SSE*/
+ RTUNI_ALPHA, /* U+00a33b: YI SYLLABLE SSEP*/
+ RTUNI_ALPHA, /* U+00a33c: YI SYLLABLE SSUT*/
+ RTUNI_ALPHA, /* U+00a33d: YI SYLLABLE SSUX*/
+ RTUNI_ALPHA, /* U+00a33e: YI SYLLABLE SSU*/
+ RTUNI_ALPHA, /* U+00a33f: YI SYLLABLE SSUP*/
+ RTUNI_ALPHA, /* U+00a340: YI SYLLABLE SSYT*/
+ RTUNI_ALPHA, /* U+00a341: YI SYLLABLE SSYX*/
+ RTUNI_ALPHA, /* U+00a342: YI SYLLABLE SSY*/
+ RTUNI_ALPHA, /* U+00a343: YI SYLLABLE SSYP*/
+ RTUNI_ALPHA, /* U+00a344: YI SYLLABLE SSYRX*/
+ RTUNI_ALPHA, /* U+00a345: YI SYLLABLE SSYR*/
+ RTUNI_ALPHA, /* U+00a346: YI SYLLABLE ZHAT*/
+ RTUNI_ALPHA, /* U+00a347: YI SYLLABLE ZHAX*/
+ RTUNI_ALPHA, /* U+00a348: YI SYLLABLE ZHA*/
+ RTUNI_ALPHA, /* U+00a349: YI SYLLABLE ZHAP*/
+ RTUNI_ALPHA, /* U+00a34a: YI SYLLABLE ZHUOX*/
+ RTUNI_ALPHA, /* U+00a34b: YI SYLLABLE ZHUO*/
+ RTUNI_ALPHA, /* U+00a34c: YI SYLLABLE ZHUOP*/
+ RTUNI_ALPHA, /* U+00a34d: YI SYLLABLE ZHOT*/
+ RTUNI_ALPHA, /* U+00a34e: YI SYLLABLE ZHOX*/
+ RTUNI_ALPHA, /* U+00a34f: YI SYLLABLE ZHO*/
+ RTUNI_ALPHA, /* U+00a350: YI SYLLABLE ZHOP*/
+ RTUNI_ALPHA, /* U+00a351: YI SYLLABLE ZHET*/
+ RTUNI_ALPHA, /* U+00a352: YI SYLLABLE ZHEX*/
+ RTUNI_ALPHA, /* U+00a353: YI SYLLABLE ZHE*/
+ RTUNI_ALPHA, /* U+00a354: YI SYLLABLE ZHEP*/
+ RTUNI_ALPHA, /* U+00a355: YI SYLLABLE ZHUT*/
+ RTUNI_ALPHA, /* U+00a356: YI SYLLABLE ZHUX*/
+ RTUNI_ALPHA, /* U+00a357: YI SYLLABLE ZHU*/
+ RTUNI_ALPHA, /* U+00a358: YI SYLLABLE ZHUP*/
+ RTUNI_ALPHA, /* U+00a359: YI SYLLABLE ZHURX*/
+ RTUNI_ALPHA, /* U+00a35a: YI SYLLABLE ZHUR*/
+ RTUNI_ALPHA, /* U+00a35b: YI SYLLABLE ZHYT*/
+ RTUNI_ALPHA, /* U+00a35c: YI SYLLABLE ZHYX*/
+ RTUNI_ALPHA, /* U+00a35d: YI SYLLABLE ZHY*/
+ RTUNI_ALPHA, /* U+00a35e: YI SYLLABLE ZHYP*/
+ RTUNI_ALPHA, /* U+00a35f: YI SYLLABLE ZHYRX*/
+ RTUNI_ALPHA, /* U+00a360: YI SYLLABLE ZHYR*/
+ RTUNI_ALPHA, /* U+00a361: YI SYLLABLE CHAT*/
+ RTUNI_ALPHA, /* U+00a362: YI SYLLABLE CHAX*/
+ RTUNI_ALPHA, /* U+00a363: YI SYLLABLE CHA*/
+ RTUNI_ALPHA, /* U+00a364: YI SYLLABLE CHAP*/
+ RTUNI_ALPHA, /* U+00a365: YI SYLLABLE CHUOT*/
+ RTUNI_ALPHA, /* U+00a366: YI SYLLABLE CHUOX*/
+ RTUNI_ALPHA, /* U+00a367: YI SYLLABLE CHUO*/
+ RTUNI_ALPHA, /* U+00a368: YI SYLLABLE CHUOP*/
+ RTUNI_ALPHA, /* U+00a369: YI SYLLABLE CHOT*/
+ RTUNI_ALPHA, /* U+00a36a: YI SYLLABLE CHOX*/
+ RTUNI_ALPHA, /* U+00a36b: YI SYLLABLE CHO*/
+ RTUNI_ALPHA, /* U+00a36c: YI SYLLABLE CHOP*/
+ RTUNI_ALPHA, /* U+00a36d: YI SYLLABLE CHET*/
+ RTUNI_ALPHA, /* U+00a36e: YI SYLLABLE CHEX*/
+ RTUNI_ALPHA, /* U+00a36f: YI SYLLABLE CHE*/
+ RTUNI_ALPHA, /* U+00a370: YI SYLLABLE CHEP*/
+ RTUNI_ALPHA, /* U+00a371: YI SYLLABLE CHUX*/
+ RTUNI_ALPHA, /* U+00a372: YI SYLLABLE CHU*/
+ RTUNI_ALPHA, /* U+00a373: YI SYLLABLE CHUP*/
+ RTUNI_ALPHA, /* U+00a374: YI SYLLABLE CHURX*/
+ RTUNI_ALPHA, /* U+00a375: YI SYLLABLE CHUR*/
+ RTUNI_ALPHA, /* U+00a376: YI SYLLABLE CHYT*/
+ RTUNI_ALPHA, /* U+00a377: YI SYLLABLE CHYX*/
+ RTUNI_ALPHA, /* U+00a378: YI SYLLABLE CHY*/
+ RTUNI_ALPHA, /* U+00a379: YI SYLLABLE CHYP*/
+ RTUNI_ALPHA, /* U+00a37a: YI SYLLABLE CHYRX*/
+ RTUNI_ALPHA, /* U+00a37b: YI SYLLABLE CHYR*/
+ RTUNI_ALPHA, /* U+00a37c: YI SYLLABLE RRAX*/
+ RTUNI_ALPHA, /* U+00a37d: YI SYLLABLE RRA*/
+ RTUNI_ALPHA, /* U+00a37e: YI SYLLABLE RRUOX*/
+ RTUNI_ALPHA, /* U+00a37f: YI SYLLABLE RRUO*/
+ RTUNI_ALPHA, /* U+00a380: YI SYLLABLE RROT*/
+ RTUNI_ALPHA, /* U+00a381: YI SYLLABLE RROX*/
+ RTUNI_ALPHA, /* U+00a382: YI SYLLABLE RRO*/
+ RTUNI_ALPHA, /* U+00a383: YI SYLLABLE RROP*/
+ RTUNI_ALPHA, /* U+00a384: YI SYLLABLE RRET*/
+ RTUNI_ALPHA, /* U+00a385: YI SYLLABLE RREX*/
+ RTUNI_ALPHA, /* U+00a386: YI SYLLABLE RRE*/
+ RTUNI_ALPHA, /* U+00a387: YI SYLLABLE RREP*/
+ RTUNI_ALPHA, /* U+00a388: YI SYLLABLE RRUT*/
+ RTUNI_ALPHA, /* U+00a389: YI SYLLABLE RRUX*/
+ RTUNI_ALPHA, /* U+00a38a: YI SYLLABLE RRU*/
+ RTUNI_ALPHA, /* U+00a38b: YI SYLLABLE RRUP*/
+ RTUNI_ALPHA, /* U+00a38c: YI SYLLABLE RRURX*/
+ RTUNI_ALPHA, /* U+00a38d: YI SYLLABLE RRUR*/
+ RTUNI_ALPHA, /* U+00a38e: YI SYLLABLE RRYT*/
+ RTUNI_ALPHA, /* U+00a38f: YI SYLLABLE RRYX*/
+ RTUNI_ALPHA, /* U+00a390: YI SYLLABLE RRY*/
+ RTUNI_ALPHA, /* U+00a391: YI SYLLABLE RRYP*/
+ RTUNI_ALPHA, /* U+00a392: YI SYLLABLE RRYRX*/
+ RTUNI_ALPHA, /* U+00a393: YI SYLLABLE RRYR*/
+ RTUNI_ALPHA, /* U+00a394: YI SYLLABLE NRAT*/
+ RTUNI_ALPHA, /* U+00a395: YI SYLLABLE NRAX*/
+ RTUNI_ALPHA, /* U+00a396: YI SYLLABLE NRA*/
+ RTUNI_ALPHA, /* U+00a397: YI SYLLABLE NRAP*/
+ RTUNI_ALPHA, /* U+00a398: YI SYLLABLE NROX*/
+ RTUNI_ALPHA, /* U+00a399: YI SYLLABLE NRO*/
+ RTUNI_ALPHA, /* U+00a39a: YI SYLLABLE NROP*/
+ RTUNI_ALPHA, /* U+00a39b: YI SYLLABLE NRET*/
+ RTUNI_ALPHA, /* U+00a39c: YI SYLLABLE NREX*/
+ RTUNI_ALPHA, /* U+00a39d: YI SYLLABLE NRE*/
+ RTUNI_ALPHA, /* U+00a39e: YI SYLLABLE NREP*/
+ RTUNI_ALPHA, /* U+00a39f: YI SYLLABLE NRUT*/
+ RTUNI_ALPHA, /* U+00a3a0: YI SYLLABLE NRUX*/
+ RTUNI_ALPHA, /* U+00a3a1: YI SYLLABLE NRU*/
+ RTUNI_ALPHA, /* U+00a3a2: YI SYLLABLE NRUP*/
+ RTUNI_ALPHA, /* U+00a3a3: YI SYLLABLE NRURX*/
+ RTUNI_ALPHA, /* U+00a3a4: YI SYLLABLE NRUR*/
+ RTUNI_ALPHA, /* U+00a3a5: YI SYLLABLE NRYT*/
+ RTUNI_ALPHA, /* U+00a3a6: YI SYLLABLE NRYX*/
+ RTUNI_ALPHA, /* U+00a3a7: YI SYLLABLE NRY*/
+ RTUNI_ALPHA, /* U+00a3a8: YI SYLLABLE NRYP*/
+ RTUNI_ALPHA, /* U+00a3a9: YI SYLLABLE NRYRX*/
+ RTUNI_ALPHA, /* U+00a3aa: YI SYLLABLE NRYR*/
+ RTUNI_ALPHA, /* U+00a3ab: YI SYLLABLE SHAT*/
+ RTUNI_ALPHA, /* U+00a3ac: YI SYLLABLE SHAX*/
+ RTUNI_ALPHA, /* U+00a3ad: YI SYLLABLE SHA*/
+ RTUNI_ALPHA, /* U+00a3ae: YI SYLLABLE SHAP*/
+ RTUNI_ALPHA, /* U+00a3af: YI SYLLABLE SHUOX*/
+ RTUNI_ALPHA, /* U+00a3b0: YI SYLLABLE SHUO*/
+ RTUNI_ALPHA, /* U+00a3b1: YI SYLLABLE SHUOP*/
+ RTUNI_ALPHA, /* U+00a3b2: YI SYLLABLE SHOT*/
+ RTUNI_ALPHA, /* U+00a3b3: YI SYLLABLE SHOX*/
+ RTUNI_ALPHA, /* U+00a3b4: YI SYLLABLE SHO*/
+ RTUNI_ALPHA, /* U+00a3b5: YI SYLLABLE SHOP*/
+ RTUNI_ALPHA, /* U+00a3b6: YI SYLLABLE SHET*/
+ RTUNI_ALPHA, /* U+00a3b7: YI SYLLABLE SHEX*/
+ RTUNI_ALPHA, /* U+00a3b8: YI SYLLABLE SHE*/
+ RTUNI_ALPHA, /* U+00a3b9: YI SYLLABLE SHEP*/
+ RTUNI_ALPHA, /* U+00a3ba: YI SYLLABLE SHUT*/
+ RTUNI_ALPHA, /* U+00a3bb: YI SYLLABLE SHUX*/
+ RTUNI_ALPHA, /* U+00a3bc: YI SYLLABLE SHU*/
+ RTUNI_ALPHA, /* U+00a3bd: YI SYLLABLE SHUP*/
+ RTUNI_ALPHA, /* U+00a3be: YI SYLLABLE SHURX*/
+ RTUNI_ALPHA, /* U+00a3bf: YI SYLLABLE SHUR*/
+ RTUNI_ALPHA, /* U+00a3c0: YI SYLLABLE SHYT*/
+ RTUNI_ALPHA, /* U+00a3c1: YI SYLLABLE SHYX*/
+ RTUNI_ALPHA, /* U+00a3c2: YI SYLLABLE SHY*/
+ RTUNI_ALPHA, /* U+00a3c3: YI SYLLABLE SHYP*/
+ RTUNI_ALPHA, /* U+00a3c4: YI SYLLABLE SHYRX*/
+ RTUNI_ALPHA, /* U+00a3c5: YI SYLLABLE SHYR*/
+ RTUNI_ALPHA, /* U+00a3c6: YI SYLLABLE RAT*/
+ RTUNI_ALPHA, /* U+00a3c7: YI SYLLABLE RAX*/
+ RTUNI_ALPHA, /* U+00a3c8: YI SYLLABLE RA*/
+ RTUNI_ALPHA, /* U+00a3c9: YI SYLLABLE RAP*/
+ RTUNI_ALPHA, /* U+00a3ca: YI SYLLABLE RUOX*/
+ RTUNI_ALPHA, /* U+00a3cb: YI SYLLABLE RUO*/
+ RTUNI_ALPHA, /* U+00a3cc: YI SYLLABLE RUOP*/
+ RTUNI_ALPHA, /* U+00a3cd: YI SYLLABLE ROT*/
+ RTUNI_ALPHA, /* U+00a3ce: YI SYLLABLE ROX*/
+ RTUNI_ALPHA, /* U+00a3cf: YI SYLLABLE RO*/
+ RTUNI_ALPHA, /* U+00a3d0: YI SYLLABLE ROP*/
+ RTUNI_ALPHA, /* U+00a3d1: YI SYLLABLE REX*/
+ RTUNI_ALPHA, /* U+00a3d2: YI SYLLABLE RE*/
+ RTUNI_ALPHA, /* U+00a3d3: YI SYLLABLE REP*/
+ RTUNI_ALPHA, /* U+00a3d4: YI SYLLABLE RUT*/
+ RTUNI_ALPHA, /* U+00a3d5: YI SYLLABLE RUX*/
+ RTUNI_ALPHA, /* U+00a3d6: YI SYLLABLE RU*/
+ RTUNI_ALPHA, /* U+00a3d7: YI SYLLABLE RUP*/
+ RTUNI_ALPHA, /* U+00a3d8: YI SYLLABLE RURX*/
+ RTUNI_ALPHA, /* U+00a3d9: YI SYLLABLE RUR*/
+ RTUNI_ALPHA, /* U+00a3da: YI SYLLABLE RYT*/
+ RTUNI_ALPHA, /* U+00a3db: YI SYLLABLE RYX*/
+ RTUNI_ALPHA, /* U+00a3dc: YI SYLLABLE RY*/
+ RTUNI_ALPHA, /* U+00a3dd: YI SYLLABLE RYP*/
+ RTUNI_ALPHA, /* U+00a3de: YI SYLLABLE RYRX*/
+ RTUNI_ALPHA, /* U+00a3df: YI SYLLABLE RYR*/
+ RTUNI_ALPHA, /* U+00a3e0: YI SYLLABLE JIT*/
+ RTUNI_ALPHA, /* U+00a3e1: YI SYLLABLE JIX*/
+ RTUNI_ALPHA, /* U+00a3e2: YI SYLLABLE JI*/
+ RTUNI_ALPHA, /* U+00a3e3: YI SYLLABLE JIP*/
+ RTUNI_ALPHA, /* U+00a3e4: YI SYLLABLE JIET*/
+ RTUNI_ALPHA, /* U+00a3e5: YI SYLLABLE JIEX*/
+ RTUNI_ALPHA, /* U+00a3e6: YI SYLLABLE JIE*/
+ RTUNI_ALPHA, /* U+00a3e7: YI SYLLABLE JIEP*/
+ RTUNI_ALPHA, /* U+00a3e8: YI SYLLABLE JUOT*/
+ RTUNI_ALPHA, /* U+00a3e9: YI SYLLABLE JUOX*/
+ RTUNI_ALPHA, /* U+00a3ea: YI SYLLABLE JUO*/
+ RTUNI_ALPHA, /* U+00a3eb: YI SYLLABLE JUOP*/
+ RTUNI_ALPHA, /* U+00a3ec: YI SYLLABLE JOT*/
+ RTUNI_ALPHA, /* U+00a3ed: YI SYLLABLE JOX*/
+ RTUNI_ALPHA, /* U+00a3ee: YI SYLLABLE JO*/
+ RTUNI_ALPHA, /* U+00a3ef: YI SYLLABLE JOP*/
+ RTUNI_ALPHA, /* U+00a3f0: YI SYLLABLE JUT*/
+ RTUNI_ALPHA, /* U+00a3f1: YI SYLLABLE JUX*/
+ RTUNI_ALPHA, /* U+00a3f2: YI SYLLABLE JU*/
+ RTUNI_ALPHA, /* U+00a3f3: YI SYLLABLE JUP*/
+ RTUNI_ALPHA, /* U+00a3f4: YI SYLLABLE JURX*/
+ RTUNI_ALPHA, /* U+00a3f5: YI SYLLABLE JUR*/
+ RTUNI_ALPHA, /* U+00a3f6: YI SYLLABLE JYT*/
+ RTUNI_ALPHA, /* U+00a3f7: YI SYLLABLE JYX*/
+ RTUNI_ALPHA, /* U+00a3f8: YI SYLLABLE JY*/
+ RTUNI_ALPHA, /* U+00a3f9: YI SYLLABLE JYP*/
+ RTUNI_ALPHA, /* U+00a3fa: YI SYLLABLE JYRX*/
+ RTUNI_ALPHA, /* U+00a3fb: YI SYLLABLE JYR*/
+ RTUNI_ALPHA, /* U+00a3fc: YI SYLLABLE QIT*/
+ RTUNI_ALPHA, /* U+00a3fd: YI SYLLABLE QIX*/
+ RTUNI_ALPHA, /* U+00a3fe: YI SYLLABLE QI*/
+ RTUNI_ALPHA, /* U+00a3ff: YI SYLLABLE QIP*/
+ RTUNI_ALPHA, /* U+00a400: YI SYLLABLE QIET*/
+ RTUNI_ALPHA, /* U+00a401: YI SYLLABLE QIEX*/
+ RTUNI_ALPHA, /* U+00a402: YI SYLLABLE QIE*/
+ RTUNI_ALPHA, /* U+00a403: YI SYLLABLE QIEP*/
+ RTUNI_ALPHA, /* U+00a404: YI SYLLABLE QUOT*/
+ RTUNI_ALPHA, /* U+00a405: YI SYLLABLE QUOX*/
+ RTUNI_ALPHA, /* U+00a406: YI SYLLABLE QUO*/
+ RTUNI_ALPHA, /* U+00a407: YI SYLLABLE QUOP*/
+ RTUNI_ALPHA, /* U+00a408: YI SYLLABLE QOT*/
+ RTUNI_ALPHA, /* U+00a409: YI SYLLABLE QOX*/
+ RTUNI_ALPHA, /* U+00a40a: YI SYLLABLE QO*/
+ RTUNI_ALPHA, /* U+00a40b: YI SYLLABLE QOP*/
+ RTUNI_ALPHA, /* U+00a40c: YI SYLLABLE QUT*/
+ RTUNI_ALPHA, /* U+00a40d: YI SYLLABLE QUX*/
+ RTUNI_ALPHA, /* U+00a40e: YI SYLLABLE QU*/
+ RTUNI_ALPHA, /* U+00a40f: YI SYLLABLE QUP*/
+ RTUNI_ALPHA, /* U+00a410: YI SYLLABLE QURX*/
+ RTUNI_ALPHA, /* U+00a411: YI SYLLABLE QUR*/
+ RTUNI_ALPHA, /* U+00a412: YI SYLLABLE QYT*/
+ RTUNI_ALPHA, /* U+00a413: YI SYLLABLE QYX*/
+ RTUNI_ALPHA, /* U+00a414: YI SYLLABLE QY*/
+ RTUNI_ALPHA, /* U+00a415: YI SYLLABLE QYP*/
+ RTUNI_ALPHA, /* U+00a416: YI SYLLABLE QYRX*/
+ RTUNI_ALPHA, /* U+00a417: YI SYLLABLE QYR*/
+ RTUNI_ALPHA, /* U+00a418: YI SYLLABLE JJIT*/
+ RTUNI_ALPHA, /* U+00a419: YI SYLLABLE JJIX*/
+ RTUNI_ALPHA, /* U+00a41a: YI SYLLABLE JJI*/
+ RTUNI_ALPHA, /* U+00a41b: YI SYLLABLE JJIP*/
+ RTUNI_ALPHA, /* U+00a41c: YI SYLLABLE JJIET*/
+ RTUNI_ALPHA, /* U+00a41d: YI SYLLABLE JJIEX*/
+ RTUNI_ALPHA, /* U+00a41e: YI SYLLABLE JJIE*/
+ RTUNI_ALPHA, /* U+00a41f: YI SYLLABLE JJIEP*/
+ RTUNI_ALPHA, /* U+00a420: YI SYLLABLE JJUOX*/
+ RTUNI_ALPHA, /* U+00a421: YI SYLLABLE JJUO*/
+ RTUNI_ALPHA, /* U+00a422: YI SYLLABLE JJUOP*/
+ RTUNI_ALPHA, /* U+00a423: YI SYLLABLE JJOT*/
+ RTUNI_ALPHA, /* U+00a424: YI SYLLABLE JJOX*/
+ RTUNI_ALPHA, /* U+00a425: YI SYLLABLE JJO*/
+ RTUNI_ALPHA, /* U+00a426: YI SYLLABLE JJOP*/
+ RTUNI_ALPHA, /* U+00a427: YI SYLLABLE JJUT*/
+ RTUNI_ALPHA, /* U+00a428: YI SYLLABLE JJUX*/
+ RTUNI_ALPHA, /* U+00a429: YI SYLLABLE JJU*/
+ RTUNI_ALPHA, /* U+00a42a: YI SYLLABLE JJUP*/
+ RTUNI_ALPHA, /* U+00a42b: YI SYLLABLE JJURX*/
+ RTUNI_ALPHA, /* U+00a42c: YI SYLLABLE JJUR*/
+ RTUNI_ALPHA, /* U+00a42d: YI SYLLABLE JJYT*/
+ RTUNI_ALPHA, /* U+00a42e: YI SYLLABLE JJYX*/
+ RTUNI_ALPHA, /* U+00a42f: YI SYLLABLE JJY*/
+ RTUNI_ALPHA, /* U+00a430: YI SYLLABLE JJYP*/
+ RTUNI_ALPHA, /* U+00a431: YI SYLLABLE NJIT*/
+ RTUNI_ALPHA, /* U+00a432: YI SYLLABLE NJIX*/
+ RTUNI_ALPHA, /* U+00a433: YI SYLLABLE NJI*/
+ RTUNI_ALPHA, /* U+00a434: YI SYLLABLE NJIP*/
+ RTUNI_ALPHA, /* U+00a435: YI SYLLABLE NJIET*/
+ RTUNI_ALPHA, /* U+00a436: YI SYLLABLE NJIEX*/
+ RTUNI_ALPHA, /* U+00a437: YI SYLLABLE NJIE*/
+ RTUNI_ALPHA, /* U+00a438: YI SYLLABLE NJIEP*/
+ RTUNI_ALPHA, /* U+00a439: YI SYLLABLE NJUOX*/
+ RTUNI_ALPHA, /* U+00a43a: YI SYLLABLE NJUO*/
+ RTUNI_ALPHA, /* U+00a43b: YI SYLLABLE NJOT*/
+ RTUNI_ALPHA, /* U+00a43c: YI SYLLABLE NJOX*/
+ RTUNI_ALPHA, /* U+00a43d: YI SYLLABLE NJO*/
+ RTUNI_ALPHA, /* U+00a43e: YI SYLLABLE NJOP*/
+ RTUNI_ALPHA, /* U+00a43f: YI SYLLABLE NJUX*/
+ RTUNI_ALPHA, /* U+00a440: YI SYLLABLE NJU*/
+ RTUNI_ALPHA, /* U+00a441: YI SYLLABLE NJUP*/
+ RTUNI_ALPHA, /* U+00a442: YI SYLLABLE NJURX*/
+ RTUNI_ALPHA, /* U+00a443: YI SYLLABLE NJUR*/
+ RTUNI_ALPHA, /* U+00a444: YI SYLLABLE NJYT*/
+ RTUNI_ALPHA, /* U+00a445: YI SYLLABLE NJYX*/
+ RTUNI_ALPHA, /* U+00a446: YI SYLLABLE NJY*/
+ RTUNI_ALPHA, /* U+00a447: YI SYLLABLE NJYP*/
+ RTUNI_ALPHA, /* U+00a448: YI SYLLABLE NJYRX*/
+ RTUNI_ALPHA, /* U+00a449: YI SYLLABLE NJYR*/
+ RTUNI_ALPHA, /* U+00a44a: YI SYLLABLE NYIT*/
+ RTUNI_ALPHA, /* U+00a44b: YI SYLLABLE NYIX*/
+ RTUNI_ALPHA, /* U+00a44c: YI SYLLABLE NYI*/
+ RTUNI_ALPHA, /* U+00a44d: YI SYLLABLE NYIP*/
+ RTUNI_ALPHA, /* U+00a44e: YI SYLLABLE NYIET*/
+ RTUNI_ALPHA, /* U+00a44f: YI SYLLABLE NYIEX*/
+ RTUNI_ALPHA, /* U+00a450: YI SYLLABLE NYIE*/
+ RTUNI_ALPHA, /* U+00a451: YI SYLLABLE NYIEP*/
+ RTUNI_ALPHA, /* U+00a452: YI SYLLABLE NYUOX*/
+ RTUNI_ALPHA, /* U+00a453: YI SYLLABLE NYUO*/
+ RTUNI_ALPHA, /* U+00a454: YI SYLLABLE NYUOP*/
+ RTUNI_ALPHA, /* U+00a455: YI SYLLABLE NYOT*/
+ RTUNI_ALPHA, /* U+00a456: YI SYLLABLE NYOX*/
+ RTUNI_ALPHA, /* U+00a457: YI SYLLABLE NYO*/
+ RTUNI_ALPHA, /* U+00a458: YI SYLLABLE NYOP*/
+ RTUNI_ALPHA, /* U+00a459: YI SYLLABLE NYUT*/
+ RTUNI_ALPHA, /* U+00a45a: YI SYLLABLE NYUX*/
+ RTUNI_ALPHA, /* U+00a45b: YI SYLLABLE NYU*/
+ RTUNI_ALPHA, /* U+00a45c: YI SYLLABLE NYUP*/
+ RTUNI_ALPHA, /* U+00a45d: YI SYLLABLE XIT*/
+ RTUNI_ALPHA, /* U+00a45e: YI SYLLABLE XIX*/
+ RTUNI_ALPHA, /* U+00a45f: YI SYLLABLE XI*/
+ RTUNI_ALPHA, /* U+00a460: YI SYLLABLE XIP*/
+ RTUNI_ALPHA, /* U+00a461: YI SYLLABLE XIET*/
+ RTUNI_ALPHA, /* U+00a462: YI SYLLABLE XIEX*/
+ RTUNI_ALPHA, /* U+00a463: YI SYLLABLE XIE*/
+ RTUNI_ALPHA, /* U+00a464: YI SYLLABLE XIEP*/
+ RTUNI_ALPHA, /* U+00a465: YI SYLLABLE XUOX*/
+ RTUNI_ALPHA, /* U+00a466: YI SYLLABLE XUO*/
+ RTUNI_ALPHA, /* U+00a467: YI SYLLABLE XOT*/
+ RTUNI_ALPHA, /* U+00a468: YI SYLLABLE XOX*/
+ RTUNI_ALPHA, /* U+00a469: YI SYLLABLE XO*/
+ RTUNI_ALPHA, /* U+00a46a: YI SYLLABLE XOP*/
+ RTUNI_ALPHA, /* U+00a46b: YI SYLLABLE XYT*/
+ RTUNI_ALPHA, /* U+00a46c: YI SYLLABLE XYX*/
+ RTUNI_ALPHA, /* U+00a46d: YI SYLLABLE XY*/
+ RTUNI_ALPHA, /* U+00a46e: YI SYLLABLE XYP*/
+ RTUNI_ALPHA, /* U+00a46f: YI SYLLABLE XYRX*/
+ RTUNI_ALPHA, /* U+00a470: YI SYLLABLE XYR*/
+ RTUNI_ALPHA, /* U+00a471: YI SYLLABLE YIT*/
+ RTUNI_ALPHA, /* U+00a472: YI SYLLABLE YIX*/
+ RTUNI_ALPHA, /* U+00a473: YI SYLLABLE YI*/
+ RTUNI_ALPHA, /* U+00a474: YI SYLLABLE YIP*/
+ RTUNI_ALPHA, /* U+00a475: YI SYLLABLE YIET*/
+ RTUNI_ALPHA, /* U+00a476: YI SYLLABLE YIEX*/
+ RTUNI_ALPHA, /* U+00a477: YI SYLLABLE YIE*/
+ RTUNI_ALPHA, /* U+00a478: YI SYLLABLE YIEP*/
+ RTUNI_ALPHA, /* U+00a479: YI SYLLABLE YUOT*/
+ RTUNI_ALPHA, /* U+00a47a: YI SYLLABLE YUOX*/
+ RTUNI_ALPHA, /* U+00a47b: YI SYLLABLE YUO*/
+ RTUNI_ALPHA, /* U+00a47c: YI SYLLABLE YUOP*/
+ RTUNI_ALPHA, /* U+00a47d: YI SYLLABLE YOT*/
+ RTUNI_ALPHA, /* U+00a47e: YI SYLLABLE YOX*/
+ RTUNI_ALPHA, /* U+00a47f: YI SYLLABLE YO*/
+ RTUNI_ALPHA, /* U+00a480: YI SYLLABLE YOP*/
+ RTUNI_ALPHA, /* U+00a481: YI SYLLABLE YUT*/
+ RTUNI_ALPHA, /* U+00a482: YI SYLLABLE YUX*/
+ RTUNI_ALPHA, /* U+00a483: YI SYLLABLE YU*/
+ RTUNI_ALPHA, /* U+00a484: YI SYLLABLE YUP*/
+ RTUNI_ALPHA, /* U+00a485: YI SYLLABLE YURX*/
+ RTUNI_ALPHA, /* U+00a486: YI SYLLABLE YUR*/
+ RTUNI_ALPHA, /* U+00a487: YI SYLLABLE YYT*/
+ RTUNI_ALPHA, /* U+00a488: YI SYLLABLE YYX*/
+ RTUNI_ALPHA, /* U+00a489: YI SYLLABLE YY*/
+ RTUNI_ALPHA, /* U+00a48a: YI SYLLABLE YYP*/
+ RTUNI_ALPHA, /* U+00a48b: YI SYLLABLE YYRX*/
+ RTUNI_ALPHA, /* U+00a48c: YI SYLLABLE YYR*/
+ 0, /* U+00a48d: */
+ 0, /* U+00a48e: */
+ 0, /* U+00a48f: */
+ 0, /* U+00a490: YI RADICAL QOT*/
+ 0, /* U+00a491: YI RADICAL LI*/
+ 0, /* U+00a492: YI RADICAL KIT*/
+ 0, /* U+00a493: YI RADICAL NYIP*/
+ 0, /* U+00a494: YI RADICAL CYP*/
+ 0, /* U+00a495: YI RADICAL SSI*/
+ 0, /* U+00a496: YI RADICAL GGOP*/
+ 0, /* U+00a497: YI RADICAL GEP*/
+ 0, /* U+00a498: YI RADICAL MI*/
+ 0, /* U+00a499: YI RADICAL HXIT*/
+ 0, /* U+00a49a: YI RADICAL LYR*/
+ 0, /* U+00a49b: YI RADICAL BBUT*/
+ 0, /* U+00a49c: YI RADICAL MOP*/
+ 0, /* U+00a49d: YI RADICAL YO*/
+ 0, /* U+00a49e: YI RADICAL PUT*/
+ 0, /* U+00a49f: YI RADICAL HXUO*/
+ 0, /* U+00a4a0: YI RADICAL TAT*/
+ 0, /* U+00a4a1: YI RADICAL GA*/
+ 0, /* U+00a4a2: YI RADICAL ZUP*/
+ 0, /* U+00a4a3: YI RADICAL CYT*/
+ 0, /* U+00a4a4: YI RADICAL DDUR*/
+ 0, /* U+00a4a5: YI RADICAL BUR*/
+ 0, /* U+00a4a6: YI RADICAL GGUO*/
+ 0, /* U+00a4a7: YI RADICAL NYOP*/
+ 0, /* U+00a4a8: YI RADICAL TU*/
+ 0, /* U+00a4a9: YI RADICAL OP*/
+ 0, /* U+00a4aa: YI RADICAL JJUT*/
+ 0, /* U+00a4ab: YI RADICAL ZOT*/
+ 0, /* U+00a4ac: YI RADICAL PYT*/
+ 0, /* U+00a4ad: YI RADICAL HMO*/
+ 0, /* U+00a4ae: YI RADICAL YIT*/
+ 0, /* U+00a4af: YI RADICAL VUR*/
+ 0, /* U+00a4b0: YI RADICAL SHY*/
+ 0, /* U+00a4b1: YI RADICAL VEP*/
+ 0, /* U+00a4b2: YI RADICAL ZA*/
+ 0, /* U+00a4b3: YI RADICAL JO*/
+ 0, /* U+00a4b4: YI RADICAL NZUP*/
+ 0, /* U+00a4b5: YI RADICAL JJY*/
+ 0, /* U+00a4b6: YI RADICAL GOT*/
+ 0, /* U+00a4b7: YI RADICAL JJIE*/
+ 0, /* U+00a4b8: YI RADICAL WO*/
+ 0, /* U+00a4b9: YI RADICAL DU*/
+ 0, /* U+00a4ba: YI RADICAL SHUR*/
+ 0, /* U+00a4bb: YI RADICAL LIE*/
+ 0, /* U+00a4bc: YI RADICAL CY*/
+ 0, /* U+00a4bd: YI RADICAL CUOP*/
+ 0, /* U+00a4be: YI RADICAL CIP*/
+ 0, /* U+00a4bf: YI RADICAL HXOP*/
+ 0, /* U+00a4c0: YI RADICAL SHAT*/
+ 0, /* U+00a4c1: YI RADICAL ZUR*/
+ 0, /* U+00a4c2: YI RADICAL SHOP*/
+ 0, /* U+00a4c3: YI RADICAL CHE*/
+ 0, /* U+00a4c4: YI RADICAL ZZIET*/
+ 0, /* U+00a4c5: YI RADICAL NBIE*/
+ 0, /* U+00a4c6: YI RADICAL KE*/
+ 0, /* U+00a4c7: */
+ 0, /* U+00a4c8: */
+ 0, /* U+00a4c9: */
+ 0, /* U+00a4ca: */
+ 0, /* U+00a4cb: */
+ 0, /* U+00a4cc: */
+ 0, /* U+00a4cd: */
+ 0, /* U+00a4ce: */
+ 0, /* U+00a4cf: */
+ RTUNI_ALPHA, /* U+00a4d0: LISU LETTER BA*/
+ RTUNI_ALPHA, /* U+00a4d1: LISU LETTER PA*/
+ RTUNI_ALPHA, /* U+00a4d2: LISU LETTER PHA*/
+ RTUNI_ALPHA, /* U+00a4d3: LISU LETTER DA*/
+ RTUNI_ALPHA, /* U+00a4d4: LISU LETTER TA*/
+ RTUNI_ALPHA, /* U+00a4d5: LISU LETTER THA*/
+ RTUNI_ALPHA, /* U+00a4d6: LISU LETTER GA*/
+ RTUNI_ALPHA, /* U+00a4d7: LISU LETTER KA*/
+ RTUNI_ALPHA, /* U+00a4d8: LISU LETTER KHA*/
+ RTUNI_ALPHA, /* U+00a4d9: LISU LETTER JA*/
+ RTUNI_ALPHA, /* U+00a4da: LISU LETTER CA*/
+ RTUNI_ALPHA, /* U+00a4db: LISU LETTER CHA*/
+ RTUNI_ALPHA, /* U+00a4dc: LISU LETTER DZA*/
+ RTUNI_ALPHA, /* U+00a4dd: LISU LETTER TSA*/
+ RTUNI_ALPHA, /* U+00a4de: LISU LETTER TSHA*/
+ RTUNI_ALPHA, /* U+00a4df: LISU LETTER MA*/
+ RTUNI_ALPHA, /* U+00a4e0: LISU LETTER NA*/
+ RTUNI_ALPHA, /* U+00a4e1: LISU LETTER LA*/
+ RTUNI_ALPHA, /* U+00a4e2: LISU LETTER SA*/
+ RTUNI_ALPHA, /* U+00a4e3: LISU LETTER ZHA*/
+ RTUNI_ALPHA, /* U+00a4e4: LISU LETTER ZA*/
+ RTUNI_ALPHA, /* U+00a4e5: LISU LETTER NGA*/
+ RTUNI_ALPHA, /* U+00a4e6: LISU LETTER HA*/
+ RTUNI_ALPHA, /* U+00a4e7: LISU LETTER XA*/
+ RTUNI_ALPHA, /* U+00a4e8: LISU LETTER HHA*/
+ RTUNI_ALPHA, /* U+00a4e9: LISU LETTER FA*/
+ RTUNI_ALPHA, /* U+00a4ea: LISU LETTER WA*/
+ RTUNI_ALPHA, /* U+00a4eb: LISU LETTER SHA*/
+ RTUNI_ALPHA, /* U+00a4ec: LISU LETTER YA*/
+ RTUNI_ALPHA, /* U+00a4ed: LISU LETTER GHA*/
+ RTUNI_ALPHA, /* U+00a4ee: LISU LETTER A*/
+ RTUNI_ALPHA, /* U+00a4ef: LISU LETTER AE*/
+ RTUNI_ALPHA, /* U+00a4f0: LISU LETTER E*/
+ RTUNI_ALPHA, /* U+00a4f1: LISU LETTER EU*/
+ RTUNI_ALPHA, /* U+00a4f2: LISU LETTER I*/
+ RTUNI_ALPHA, /* U+00a4f3: LISU LETTER O*/
+ RTUNI_ALPHA, /* U+00a4f4: LISU LETTER U*/
+ RTUNI_ALPHA, /* U+00a4f5: LISU LETTER UE*/
+ RTUNI_ALPHA, /* U+00a4f6: LISU LETTER UH*/
+ RTUNI_ALPHA, /* U+00a4f7: LISU LETTER OE*/
+ RTUNI_ALPHA, /* U+00a4f8: LISU LETTER TONE MYA TI*/
+ RTUNI_ALPHA, /* U+00a4f9: LISU LETTER TONE NA PO*/
+ RTUNI_ALPHA, /* U+00a4fa: LISU LETTER TONE MYA CYA*/
+ RTUNI_ALPHA, /* U+00a4fb: LISU LETTER TONE MYA BO*/
+ RTUNI_ALPHA, /* U+00a4fc: LISU LETTER TONE MYA NA*/
+ RTUNI_ALPHA, /* U+00a4fd: LISU LETTER TONE MYA JEU*/
+ 0, /* U+00a4fe: LISU PUNCTUATION COMMA*/
+ 0, /* U+00a4ff: LISU PUNCTUATION FULL STOP*/
+ RTUNI_ALPHA, /* U+00a500: VAI SYLLABLE EE*/
+ RTUNI_ALPHA, /* U+00a501: VAI SYLLABLE EEN*/
+ RTUNI_ALPHA, /* U+00a502: VAI SYLLABLE HEE*/
+ RTUNI_ALPHA, /* U+00a503: VAI SYLLABLE WEE*/
+ RTUNI_ALPHA, /* U+00a504: VAI SYLLABLE WEEN*/
+ RTUNI_ALPHA, /* U+00a505: VAI SYLLABLE PEE*/
+ RTUNI_ALPHA, /* U+00a506: VAI SYLLABLE BHEE*/
+ RTUNI_ALPHA, /* U+00a507: VAI SYLLABLE BEE*/
+ RTUNI_ALPHA, /* U+00a508: VAI SYLLABLE MBEE*/
+ RTUNI_ALPHA, /* U+00a509: VAI SYLLABLE KPEE*/
+ RTUNI_ALPHA, /* U+00a50a: VAI SYLLABLE MGBEE*/
+ RTUNI_ALPHA, /* U+00a50b: VAI SYLLABLE GBEE*/
+ RTUNI_ALPHA, /* U+00a50c: VAI SYLLABLE FEE*/
+ RTUNI_ALPHA, /* U+00a50d: VAI SYLLABLE VEE*/
+ RTUNI_ALPHA, /* U+00a50e: VAI SYLLABLE TEE*/
+ RTUNI_ALPHA, /* U+00a50f: VAI SYLLABLE THEE*/
+ RTUNI_ALPHA, /* U+00a510: VAI SYLLABLE DHEE*/
+ RTUNI_ALPHA, /* U+00a511: VAI SYLLABLE DHHEE*/
+ RTUNI_ALPHA, /* U+00a512: VAI SYLLABLE LEE*/
+ RTUNI_ALPHA, /* U+00a513: VAI SYLLABLE REE*/
+ RTUNI_ALPHA, /* U+00a514: VAI SYLLABLE DEE*/
+ RTUNI_ALPHA, /* U+00a515: VAI SYLLABLE NDEE*/
+ RTUNI_ALPHA, /* U+00a516: VAI SYLLABLE SEE*/
+ RTUNI_ALPHA, /* U+00a517: VAI SYLLABLE SHEE*/
+ RTUNI_ALPHA, /* U+00a518: VAI SYLLABLE ZEE*/
+ RTUNI_ALPHA, /* U+00a519: VAI SYLLABLE ZHEE*/
+ RTUNI_ALPHA, /* U+00a51a: VAI SYLLABLE CEE*/
+ RTUNI_ALPHA, /* U+00a51b: VAI SYLLABLE JEE*/
+ RTUNI_ALPHA, /* U+00a51c: VAI SYLLABLE NJEE*/
+ RTUNI_ALPHA, /* U+00a51d: VAI SYLLABLE YEE*/
+ RTUNI_ALPHA, /* U+00a51e: VAI SYLLABLE KEE*/
+ RTUNI_ALPHA, /* U+00a51f: VAI SYLLABLE NGGEE*/
+ RTUNI_ALPHA, /* U+00a520: VAI SYLLABLE GEE*/
+ RTUNI_ALPHA, /* U+00a521: VAI SYLLABLE MEE*/
+ RTUNI_ALPHA, /* U+00a522: VAI SYLLABLE NEE*/
+ RTUNI_ALPHA, /* U+00a523: VAI SYLLABLE NYEE*/
+ RTUNI_ALPHA, /* U+00a524: VAI SYLLABLE I*/
+ RTUNI_ALPHA, /* U+00a525: VAI SYLLABLE IN*/
+ RTUNI_ALPHA, /* U+00a526: VAI SYLLABLE HI*/
+ RTUNI_ALPHA, /* U+00a527: VAI SYLLABLE HIN*/
+ RTUNI_ALPHA, /* U+00a528: VAI SYLLABLE WI*/
+ RTUNI_ALPHA, /* U+00a529: VAI SYLLABLE WIN*/
+ RTUNI_ALPHA, /* U+00a52a: VAI SYLLABLE PI*/
+ RTUNI_ALPHA, /* U+00a52b: VAI SYLLABLE BHI*/
+ RTUNI_ALPHA, /* U+00a52c: VAI SYLLABLE BI*/
+ RTUNI_ALPHA, /* U+00a52d: VAI SYLLABLE MBI*/
+ RTUNI_ALPHA, /* U+00a52e: VAI SYLLABLE KPI*/
+ RTUNI_ALPHA, /* U+00a52f: VAI SYLLABLE MGBI*/
+ RTUNI_ALPHA, /* U+00a530: VAI SYLLABLE GBI*/
+ RTUNI_ALPHA, /* U+00a531: VAI SYLLABLE FI*/
+ RTUNI_ALPHA, /* U+00a532: VAI SYLLABLE VI*/
+ RTUNI_ALPHA, /* U+00a533: VAI SYLLABLE TI*/
+ RTUNI_ALPHA, /* U+00a534: VAI SYLLABLE THI*/
+ RTUNI_ALPHA, /* U+00a535: VAI SYLLABLE DHI*/
+ RTUNI_ALPHA, /* U+00a536: VAI SYLLABLE DHHI*/
+ RTUNI_ALPHA, /* U+00a537: VAI SYLLABLE LI*/
+ RTUNI_ALPHA, /* U+00a538: VAI SYLLABLE RI*/
+ RTUNI_ALPHA, /* U+00a539: VAI SYLLABLE DI*/
+ RTUNI_ALPHA, /* U+00a53a: VAI SYLLABLE NDI*/
+ RTUNI_ALPHA, /* U+00a53b: VAI SYLLABLE SI*/
+ RTUNI_ALPHA, /* U+00a53c: VAI SYLLABLE SHI*/
+ RTUNI_ALPHA, /* U+00a53d: VAI SYLLABLE ZI*/
+ RTUNI_ALPHA, /* U+00a53e: VAI SYLLABLE ZHI*/
+ RTUNI_ALPHA, /* U+00a53f: VAI SYLLABLE CI*/
+ RTUNI_ALPHA, /* U+00a540: VAI SYLLABLE JI*/
+ RTUNI_ALPHA, /* U+00a541: VAI SYLLABLE NJI*/
+ RTUNI_ALPHA, /* U+00a542: VAI SYLLABLE YI*/
+ RTUNI_ALPHA, /* U+00a543: VAI SYLLABLE KI*/
+ RTUNI_ALPHA, /* U+00a544: VAI SYLLABLE NGGI*/
+ RTUNI_ALPHA, /* U+00a545: VAI SYLLABLE GI*/
+ RTUNI_ALPHA, /* U+00a546: VAI SYLLABLE MI*/
+ RTUNI_ALPHA, /* U+00a547: VAI SYLLABLE NI*/
+ RTUNI_ALPHA, /* U+00a548: VAI SYLLABLE NYI*/
+ RTUNI_ALPHA, /* U+00a549: VAI SYLLABLE A*/
+ RTUNI_ALPHA, /* U+00a54a: VAI SYLLABLE AN*/
+ RTUNI_ALPHA, /* U+00a54b: VAI SYLLABLE NGAN*/
+ RTUNI_ALPHA, /* U+00a54c: VAI SYLLABLE HA*/
+ RTUNI_ALPHA, /* U+00a54d: VAI SYLLABLE HAN*/
+ RTUNI_ALPHA, /* U+00a54e: VAI SYLLABLE WA*/
+ RTUNI_ALPHA, /* U+00a54f: VAI SYLLABLE WAN*/
+ RTUNI_ALPHA, /* U+00a550: VAI SYLLABLE PA*/
+ RTUNI_ALPHA, /* U+00a551: VAI SYLLABLE BHA*/
+ RTUNI_ALPHA, /* U+00a552: VAI SYLLABLE BA*/
+ RTUNI_ALPHA, /* U+00a553: VAI SYLLABLE MBA*/
+ RTUNI_ALPHA, /* U+00a554: VAI SYLLABLE KPA*/
+ RTUNI_ALPHA, /* U+00a555: VAI SYLLABLE KPAN*/
+ RTUNI_ALPHA, /* U+00a556: VAI SYLLABLE MGBA*/
+ RTUNI_ALPHA, /* U+00a557: VAI SYLLABLE GBA*/
+ RTUNI_ALPHA, /* U+00a558: VAI SYLLABLE FA*/
+ RTUNI_ALPHA, /* U+00a559: VAI SYLLABLE VA*/
+ RTUNI_ALPHA, /* U+00a55a: VAI SYLLABLE TA*/
+ RTUNI_ALPHA, /* U+00a55b: VAI SYLLABLE THA*/
+ RTUNI_ALPHA, /* U+00a55c: VAI SYLLABLE DHA*/
+ RTUNI_ALPHA, /* U+00a55d: VAI SYLLABLE DHHA*/
+ RTUNI_ALPHA, /* U+00a55e: VAI SYLLABLE LA*/
+ RTUNI_ALPHA, /* U+00a55f: VAI SYLLABLE RA*/
+ RTUNI_ALPHA, /* U+00a560: VAI SYLLABLE DA*/
+ RTUNI_ALPHA, /* U+00a561: VAI SYLLABLE NDA*/
+ RTUNI_ALPHA, /* U+00a562: VAI SYLLABLE SA*/
+ RTUNI_ALPHA, /* U+00a563: VAI SYLLABLE SHA*/
+ RTUNI_ALPHA, /* U+00a564: VAI SYLLABLE ZA*/
+ RTUNI_ALPHA, /* U+00a565: VAI SYLLABLE ZHA*/
+ RTUNI_ALPHA, /* U+00a566: VAI SYLLABLE CA*/
+ RTUNI_ALPHA, /* U+00a567: VAI SYLLABLE JA*/
+ RTUNI_ALPHA, /* U+00a568: VAI SYLLABLE NJA*/
+ RTUNI_ALPHA, /* U+00a569: VAI SYLLABLE YA*/
+ RTUNI_ALPHA, /* U+00a56a: VAI SYLLABLE KA*/
+ RTUNI_ALPHA, /* U+00a56b: VAI SYLLABLE KAN*/
+ RTUNI_ALPHA, /* U+00a56c: VAI SYLLABLE NGGA*/
+ RTUNI_ALPHA, /* U+00a56d: VAI SYLLABLE GA*/
+ RTUNI_ALPHA, /* U+00a56e: VAI SYLLABLE MA*/
+ RTUNI_ALPHA, /* U+00a56f: VAI SYLLABLE NA*/
+ RTUNI_ALPHA, /* U+00a570: VAI SYLLABLE NYA*/
+ RTUNI_ALPHA, /* U+00a571: VAI SYLLABLE OO*/
+ RTUNI_ALPHA, /* U+00a572: VAI SYLLABLE OON*/
+ RTUNI_ALPHA, /* U+00a573: VAI SYLLABLE HOO*/
+ RTUNI_ALPHA, /* U+00a574: VAI SYLLABLE WOO*/
+ RTUNI_ALPHA, /* U+00a575: VAI SYLLABLE WOON*/
+ RTUNI_ALPHA, /* U+00a576: VAI SYLLABLE POO*/
+ RTUNI_ALPHA, /* U+00a577: VAI SYLLABLE BHOO*/
+ RTUNI_ALPHA, /* U+00a578: VAI SYLLABLE BOO*/
+ RTUNI_ALPHA, /* U+00a579: VAI SYLLABLE MBOO*/
+ RTUNI_ALPHA, /* U+00a57a: VAI SYLLABLE KPOO*/
+ RTUNI_ALPHA, /* U+00a57b: VAI SYLLABLE MGBOO*/
+ RTUNI_ALPHA, /* U+00a57c: VAI SYLLABLE GBOO*/
+ RTUNI_ALPHA, /* U+00a57d: VAI SYLLABLE FOO*/
+ RTUNI_ALPHA, /* U+00a57e: VAI SYLLABLE VOO*/
+ RTUNI_ALPHA, /* U+00a57f: VAI SYLLABLE TOO*/
+ RTUNI_ALPHA, /* U+00a580: VAI SYLLABLE THOO*/
+ RTUNI_ALPHA, /* U+00a581: VAI SYLLABLE DHOO*/
+ RTUNI_ALPHA, /* U+00a582: VAI SYLLABLE DHHOO*/
+ RTUNI_ALPHA, /* U+00a583: VAI SYLLABLE LOO*/
+ RTUNI_ALPHA, /* U+00a584: VAI SYLLABLE ROO*/
+ RTUNI_ALPHA, /* U+00a585: VAI SYLLABLE DOO*/
+ RTUNI_ALPHA, /* U+00a586: VAI SYLLABLE NDOO*/
+ RTUNI_ALPHA, /* U+00a587: VAI SYLLABLE SOO*/
+ RTUNI_ALPHA, /* U+00a588: VAI SYLLABLE SHOO*/
+ RTUNI_ALPHA, /* U+00a589: VAI SYLLABLE ZOO*/
+ RTUNI_ALPHA, /* U+00a58a: VAI SYLLABLE ZHOO*/
+ RTUNI_ALPHA, /* U+00a58b: VAI SYLLABLE COO*/
+ RTUNI_ALPHA, /* U+00a58c: VAI SYLLABLE JOO*/
+ RTUNI_ALPHA, /* U+00a58d: VAI SYLLABLE NJOO*/
+ RTUNI_ALPHA, /* U+00a58e: VAI SYLLABLE YOO*/
+ RTUNI_ALPHA, /* U+00a58f: VAI SYLLABLE KOO*/
+ RTUNI_ALPHA, /* U+00a590: VAI SYLLABLE NGGOO*/
+ RTUNI_ALPHA, /* U+00a591: VAI SYLLABLE GOO*/
+ RTUNI_ALPHA, /* U+00a592: VAI SYLLABLE MOO*/
+ RTUNI_ALPHA, /* U+00a593: VAI SYLLABLE NOO*/
+ RTUNI_ALPHA, /* U+00a594: VAI SYLLABLE NYOO*/
+ RTUNI_ALPHA, /* U+00a595: VAI SYLLABLE U*/
+ RTUNI_ALPHA, /* U+00a596: VAI SYLLABLE UN*/
+ RTUNI_ALPHA, /* U+00a597: VAI SYLLABLE HU*/
+ RTUNI_ALPHA, /* U+00a598: VAI SYLLABLE HUN*/
+ RTUNI_ALPHA, /* U+00a599: VAI SYLLABLE WU*/
+ RTUNI_ALPHA, /* U+00a59a: VAI SYLLABLE WUN*/
+ RTUNI_ALPHA, /* U+00a59b: VAI SYLLABLE PU*/
+ RTUNI_ALPHA, /* U+00a59c: VAI SYLLABLE BHU*/
+ RTUNI_ALPHA, /* U+00a59d: VAI SYLLABLE BU*/
+ RTUNI_ALPHA, /* U+00a59e: VAI SYLLABLE MBU*/
+ RTUNI_ALPHA, /* U+00a59f: VAI SYLLABLE KPU*/
+ RTUNI_ALPHA, /* U+00a5a0: VAI SYLLABLE MGBU*/
+ RTUNI_ALPHA, /* U+00a5a1: VAI SYLLABLE GBU*/
+ RTUNI_ALPHA, /* U+00a5a2: VAI SYLLABLE FU*/
+ RTUNI_ALPHA, /* U+00a5a3: VAI SYLLABLE VU*/
+ RTUNI_ALPHA, /* U+00a5a4: VAI SYLLABLE TU*/
+ RTUNI_ALPHA, /* U+00a5a5: VAI SYLLABLE THU*/
+ RTUNI_ALPHA, /* U+00a5a6: VAI SYLLABLE DHU*/
+ RTUNI_ALPHA, /* U+00a5a7: VAI SYLLABLE DHHU*/
+ RTUNI_ALPHA, /* U+00a5a8: VAI SYLLABLE LU*/
+ RTUNI_ALPHA, /* U+00a5a9: VAI SYLLABLE RU*/
+ RTUNI_ALPHA, /* U+00a5aa: VAI SYLLABLE DU*/
+ RTUNI_ALPHA, /* U+00a5ab: VAI SYLLABLE NDU*/
+ RTUNI_ALPHA, /* U+00a5ac: VAI SYLLABLE SU*/
+ RTUNI_ALPHA, /* U+00a5ad: VAI SYLLABLE SHU*/
+ RTUNI_ALPHA, /* U+00a5ae: VAI SYLLABLE ZU*/
+ RTUNI_ALPHA, /* U+00a5af: VAI SYLLABLE ZHU*/
+ RTUNI_ALPHA, /* U+00a5b0: VAI SYLLABLE CU*/
+ RTUNI_ALPHA, /* U+00a5b1: VAI SYLLABLE JU*/
+ RTUNI_ALPHA, /* U+00a5b2: VAI SYLLABLE NJU*/
+ RTUNI_ALPHA, /* U+00a5b3: VAI SYLLABLE YU*/
+ RTUNI_ALPHA, /* U+00a5b4: VAI SYLLABLE KU*/
+ RTUNI_ALPHA, /* U+00a5b5: VAI SYLLABLE NGGU*/
+ RTUNI_ALPHA, /* U+00a5b6: VAI SYLLABLE GU*/
+ RTUNI_ALPHA, /* U+00a5b7: VAI SYLLABLE MU*/
+ RTUNI_ALPHA, /* U+00a5b8: VAI SYLLABLE NU*/
+ RTUNI_ALPHA, /* U+00a5b9: VAI SYLLABLE NYU*/
+ RTUNI_ALPHA, /* U+00a5ba: VAI SYLLABLE O*/
+ RTUNI_ALPHA, /* U+00a5bb: VAI SYLLABLE ON*/
+ RTUNI_ALPHA, /* U+00a5bc: VAI SYLLABLE NGON*/
+ RTUNI_ALPHA, /* U+00a5bd: VAI SYLLABLE HO*/
+ RTUNI_ALPHA, /* U+00a5be: VAI SYLLABLE HON*/
+ RTUNI_ALPHA, /* U+00a5bf: VAI SYLLABLE WO*/
+ RTUNI_ALPHA, /* U+00a5c0: VAI SYLLABLE WON*/
+ RTUNI_ALPHA, /* U+00a5c1: VAI SYLLABLE PO*/
+ RTUNI_ALPHA, /* U+00a5c2: VAI SYLLABLE BHO*/
+ RTUNI_ALPHA, /* U+00a5c3: VAI SYLLABLE BO*/
+ RTUNI_ALPHA, /* U+00a5c4: VAI SYLLABLE MBO*/
+ RTUNI_ALPHA, /* U+00a5c5: VAI SYLLABLE KPO*/
+ RTUNI_ALPHA, /* U+00a5c6: VAI SYLLABLE MGBO*/
+ RTUNI_ALPHA, /* U+00a5c7: VAI SYLLABLE GBO*/
+ RTUNI_ALPHA, /* U+00a5c8: VAI SYLLABLE GBON*/
+ RTUNI_ALPHA, /* U+00a5c9: VAI SYLLABLE FO*/
+ RTUNI_ALPHA, /* U+00a5ca: VAI SYLLABLE VO*/
+ RTUNI_ALPHA, /* U+00a5cb: VAI SYLLABLE TO*/
+ RTUNI_ALPHA, /* U+00a5cc: VAI SYLLABLE THO*/
+ RTUNI_ALPHA, /* U+00a5cd: VAI SYLLABLE DHO*/
+ RTUNI_ALPHA, /* U+00a5ce: VAI SYLLABLE DHHO*/
+ RTUNI_ALPHA, /* U+00a5cf: VAI SYLLABLE LO*/
+ RTUNI_ALPHA, /* U+00a5d0: VAI SYLLABLE RO*/
+ RTUNI_ALPHA, /* U+00a5d1: VAI SYLLABLE DO*/
+ RTUNI_ALPHA, /* U+00a5d2: VAI SYLLABLE NDO*/
+ RTUNI_ALPHA, /* U+00a5d3: VAI SYLLABLE SO*/
+ RTUNI_ALPHA, /* U+00a5d4: VAI SYLLABLE SHO*/
+ RTUNI_ALPHA, /* U+00a5d5: VAI SYLLABLE ZO*/
+ RTUNI_ALPHA, /* U+00a5d6: VAI SYLLABLE ZHO*/
+ RTUNI_ALPHA, /* U+00a5d7: VAI SYLLABLE CO*/
+ RTUNI_ALPHA, /* U+00a5d8: VAI SYLLABLE JO*/
+ RTUNI_ALPHA, /* U+00a5d9: VAI SYLLABLE NJO*/
+ RTUNI_ALPHA, /* U+00a5da: VAI SYLLABLE YO*/
+ RTUNI_ALPHA, /* U+00a5db: VAI SYLLABLE KO*/
+ RTUNI_ALPHA, /* U+00a5dc: VAI SYLLABLE NGGO*/
+ RTUNI_ALPHA, /* U+00a5dd: VAI SYLLABLE GO*/
+ RTUNI_ALPHA, /* U+00a5de: VAI SYLLABLE MO*/
+ RTUNI_ALPHA, /* U+00a5df: VAI SYLLABLE NO*/
+ RTUNI_ALPHA, /* U+00a5e0: VAI SYLLABLE NYO*/
+ RTUNI_ALPHA, /* U+00a5e1: VAI SYLLABLE E*/
+ RTUNI_ALPHA, /* U+00a5e2: VAI SYLLABLE EN*/
+ RTUNI_ALPHA, /* U+00a5e3: VAI SYLLABLE NGEN*/
+ RTUNI_ALPHA, /* U+00a5e4: VAI SYLLABLE HE*/
+ RTUNI_ALPHA, /* U+00a5e5: VAI SYLLABLE HEN*/
+ RTUNI_ALPHA, /* U+00a5e6: VAI SYLLABLE WE*/
+ RTUNI_ALPHA, /* U+00a5e7: VAI SYLLABLE WEN*/
+ RTUNI_ALPHA, /* U+00a5e8: VAI SYLLABLE PE*/
+ RTUNI_ALPHA, /* U+00a5e9: VAI SYLLABLE BHE*/
+ RTUNI_ALPHA, /* U+00a5ea: VAI SYLLABLE BE*/
+ RTUNI_ALPHA, /* U+00a5eb: VAI SYLLABLE MBE*/
+ RTUNI_ALPHA, /* U+00a5ec: VAI SYLLABLE KPE*/
+ RTUNI_ALPHA, /* U+00a5ed: VAI SYLLABLE KPEN*/
+ RTUNI_ALPHA, /* U+00a5ee: VAI SYLLABLE MGBE*/
+ RTUNI_ALPHA, /* U+00a5ef: VAI SYLLABLE GBE*/
+ RTUNI_ALPHA, /* U+00a5f0: VAI SYLLABLE GBEN*/
+ RTUNI_ALPHA, /* U+00a5f1: VAI SYLLABLE FE*/
+ RTUNI_ALPHA, /* U+00a5f2: VAI SYLLABLE VE*/
+ RTUNI_ALPHA, /* U+00a5f3: VAI SYLLABLE TE*/
+ RTUNI_ALPHA, /* U+00a5f4: VAI SYLLABLE THE*/
+ RTUNI_ALPHA, /* U+00a5f5: VAI SYLLABLE DHE*/
+ RTUNI_ALPHA, /* U+00a5f6: VAI SYLLABLE DHHE*/
+ RTUNI_ALPHA, /* U+00a5f7: VAI SYLLABLE LE*/
+ RTUNI_ALPHA, /* U+00a5f8: VAI SYLLABLE RE*/
+ RTUNI_ALPHA, /* U+00a5f9: VAI SYLLABLE DE*/
+ RTUNI_ALPHA, /* U+00a5fa: VAI SYLLABLE NDE*/
+ RTUNI_ALPHA, /* U+00a5fb: VAI SYLLABLE SE*/
+ RTUNI_ALPHA, /* U+00a5fc: VAI SYLLABLE SHE*/
+ RTUNI_ALPHA, /* U+00a5fd: VAI SYLLABLE ZE*/
+ RTUNI_ALPHA, /* U+00a5fe: VAI SYLLABLE ZHE*/
+ RTUNI_ALPHA, /* U+00a5ff: VAI SYLLABLE CE*/
+ RTUNI_ALPHA, /* U+00a600: VAI SYLLABLE JE*/
+ RTUNI_ALPHA, /* U+00a601: VAI SYLLABLE NJE*/
+ RTUNI_ALPHA, /* U+00a602: VAI SYLLABLE YE*/
+ RTUNI_ALPHA, /* U+00a603: VAI SYLLABLE KE*/
+ RTUNI_ALPHA, /* U+00a604: VAI SYLLABLE NGGE*/
+ RTUNI_ALPHA, /* U+00a605: VAI SYLLABLE NGGEN*/
+ RTUNI_ALPHA, /* U+00a606: VAI SYLLABLE GE*/
+ RTUNI_ALPHA, /* U+00a607: VAI SYLLABLE GEN*/
+ RTUNI_ALPHA, /* U+00a608: VAI SYLLABLE ME*/
+ RTUNI_ALPHA, /* U+00a609: VAI SYLLABLE NE*/
+ RTUNI_ALPHA, /* U+00a60a: VAI SYLLABLE NYE*/
+ RTUNI_ALPHA, /* U+00a60b: VAI SYLLABLE NG*/
+ RTUNI_ALPHA, /* U+00a60c: VAI SYLLABLE LENGTHENER*/
+ 0, /* U+00a60d: VAI COMMA*/
+ 0, /* U+00a60e: VAI FULL STOP*/
+ 0, /* U+00a60f: VAI QUESTION MARK*/
+ RTUNI_ALPHA, /* U+00a610: VAI SYLLABLE NDOLE FA*/
+ RTUNI_ALPHA, /* U+00a611: VAI SYLLABLE NDOLE KA*/
+ RTUNI_ALPHA, /* U+00a612: VAI SYLLABLE NDOLE SOO*/
+ RTUNI_ALPHA, /* U+00a613: VAI SYMBOL FEENG*/
+ RTUNI_ALPHA, /* U+00a614: VAI SYMBOL KEENG*/
+ RTUNI_ALPHA, /* U+00a615: VAI SYMBOL TING*/
+ RTUNI_ALPHA, /* U+00a616: VAI SYMBOL NII*/
+ RTUNI_ALPHA, /* U+00a617: VAI SYMBOL BANG*/
+ RTUNI_ALPHA, /* U+00a618: VAI SYMBOL FAA*/
+ RTUNI_ALPHA, /* U+00a619: VAI SYMBOL TAA*/
+ RTUNI_ALPHA, /* U+00a61a: VAI SYMBOL DANG*/
+ RTUNI_ALPHA, /* U+00a61b: VAI SYMBOL DOONG*/
+ RTUNI_ALPHA, /* U+00a61c: VAI SYMBOL KUNG*/
+ RTUNI_ALPHA, /* U+00a61d: VAI SYMBOL TONG*/
+ RTUNI_ALPHA, /* U+00a61e: VAI SYMBOL DO-O*/
+ RTUNI_ALPHA, /* U+00a61f: VAI SYMBOL JONG*/
+ RTUNI_DDIGIT, /* U+00a620: VAI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+00a621: VAI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+00a622: VAI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+00a623: VAI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+00a624: VAI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+00a625: VAI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+00a626: VAI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+00a627: VAI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+00a628: VAI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+00a629: VAI DIGIT NINE*/
+ RTUNI_ALPHA, /* U+00a62a: VAI SYLLABLE NDOLE MA*/
+ RTUNI_ALPHA, /* U+00a62b: VAI SYLLABLE NDOLE DO*/
+ 0, /* U+00a62c: */
+ 0, /* U+00a62d: */
+ 0, /* U+00a62e: */
+ 0, /* U+00a62f: */
+ 0, /* U+00a630: */
+ 0, /* U+00a631: */
+ 0, /* U+00a632: */
+ 0, /* U+00a633: */
+ 0, /* U+00a634: */
+ 0, /* U+00a635: */
+ 0, /* U+00a636: */
+ 0, /* U+00a637: */
+ 0, /* U+00a638: */
+ 0, /* U+00a639: */
+ 0, /* U+00a63a: */
+ 0, /* U+00a63b: */
+ 0, /* U+00a63c: */
+ 0, /* U+00a63d: */
+ 0, /* U+00a63e: */
+ 0, /* U+00a63f: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a640: CYRILLIC CAPITAL LETTER ZEMLYA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a641: CYRILLIC SMALL LETTER ZEMLYA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a642: CYRILLIC CAPITAL LETTER DZELO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a643: CYRILLIC SMALL LETTER DZELO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a644: CYRILLIC CAPITAL LETTER REVERSED DZE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a645: CYRILLIC SMALL LETTER REVERSED DZE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a646: CYRILLIC CAPITAL LETTER IOTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a647: CYRILLIC SMALL LETTER IOTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a648: CYRILLIC CAPITAL LETTER DJERV*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a649: CYRILLIC SMALL LETTER DJERV*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a64a: CYRILLIC CAPITAL LETTER MONOGRAPH UK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a64b: CYRILLIC SMALL LETTER MONOGRAPH UK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a64c: CYRILLIC CAPITAL LETTER BROAD OMEGA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a64d: CYRILLIC SMALL LETTER BROAD OMEGA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a64e: CYRILLIC CAPITAL LETTER NEUTRAL YER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a64f: CYRILLIC SMALL LETTER NEUTRAL YER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a650: CYRILLIC CAPITAL LETTER YERU WITH BACK YER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a651: CYRILLIC SMALL LETTER YERU WITH BACK YER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a652: CYRILLIC CAPITAL LETTER IOTIFIED YAT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a653: CYRILLIC SMALL LETTER IOTIFIED YAT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a654: CYRILLIC CAPITAL LETTER REVERSED YU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a655: CYRILLIC SMALL LETTER REVERSED YU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a656: CYRILLIC CAPITAL LETTER IOTIFIED A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a657: CYRILLIC SMALL LETTER IOTIFIED A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a658: CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a659: CYRILLIC SMALL LETTER CLOSED LITTLE YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a65a: CYRILLIC CAPITAL LETTER BLENDED YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a65b: CYRILLIC SMALL LETTER BLENDED YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a65c: CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a65d: CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a65e: CYRILLIC CAPITAL LETTER YN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a65f: CYRILLIC SMALL LETTER YN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a660: CYRILLIC CAPITAL LETTER REVERSED TSE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a661: CYRILLIC SMALL LETTER REVERSED TSE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a662: CYRILLIC CAPITAL LETTER SOFT DE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a663: CYRILLIC SMALL LETTER SOFT DE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a664: CYRILLIC CAPITAL LETTER SOFT EL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a665: CYRILLIC SMALL LETTER SOFT EL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a666: CYRILLIC CAPITAL LETTER SOFT EM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a667: CYRILLIC SMALL LETTER SOFT EM*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a668: CYRILLIC CAPITAL LETTER MONOCULAR O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a669: CYRILLIC SMALL LETTER MONOCULAR O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a66a: CYRILLIC CAPITAL LETTER BINOCULAR O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a66b: CYRILLIC SMALL LETTER BINOCULAR O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a66c: CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a66d: CYRILLIC SMALL LETTER DOUBLE MONOCULAR O*/
+ RTUNI_ALPHA, /* U+00a66e: CYRILLIC LETTER MULTIOCULAR O*/
+ 0, /* U+00a66f: COMBINING CYRILLIC VZMET*/
+ 0, /* U+00a670: COMBINING CYRILLIC TEN MILLIONS SIGN*/
+ 0, /* U+00a671: COMBINING CYRILLIC HUNDRED MILLIONS SIGN*/
+ 0, /* U+00a672: COMBINING CYRILLIC THOUSAND MILLIONS SIGN*/
+ 0, /* U+00a673: SLAVONIC ASTERISK*/
+ RTUNI_ALPHA, /* U+00a674: COMBINING CYRILLIC LETTER UKRAINIAN IE*/
+ RTUNI_ALPHA, /* U+00a675: COMBINING CYRILLIC LETTER I*/
+ RTUNI_ALPHA, /* U+00a676: COMBINING CYRILLIC LETTER YI*/
+ RTUNI_ALPHA, /* U+00a677: COMBINING CYRILLIC LETTER U*/
+ RTUNI_ALPHA, /* U+00a678: COMBINING CYRILLIC LETTER HARD SIGN*/
+ RTUNI_ALPHA, /* U+00a679: COMBINING CYRILLIC LETTER YERU*/
+ RTUNI_ALPHA, /* U+00a67a: COMBINING CYRILLIC LETTER SOFT SIGN*/
+ RTUNI_ALPHA, /* U+00a67b: COMBINING CYRILLIC LETTER OMEGA*/
+ 0, /* U+00a67c: COMBINING CYRILLIC KAVYKA*/
+ 0, /* U+00a67d: COMBINING CYRILLIC PAYEROK*/
+ 0, /* U+00a67e: CYRILLIC KAVYKA*/
+ RTUNI_ALPHA, /* U+00a67f: CYRILLIC PAYEROK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a680: CYRILLIC CAPITAL LETTER DWE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a681: CYRILLIC SMALL LETTER DWE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a682: CYRILLIC CAPITAL LETTER DZWE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a683: CYRILLIC SMALL LETTER DZWE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a684: CYRILLIC CAPITAL LETTER ZHWE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a685: CYRILLIC SMALL LETTER ZHWE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a686: CYRILLIC CAPITAL LETTER CCHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a687: CYRILLIC SMALL LETTER CCHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a688: CYRILLIC CAPITAL LETTER DZZE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a689: CYRILLIC SMALL LETTER DZZE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a68a: CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a68b: CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a68c: CYRILLIC CAPITAL LETTER TWE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a68d: CYRILLIC SMALL LETTER TWE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a68e: CYRILLIC CAPITAL LETTER TSWE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a68f: CYRILLIC SMALL LETTER TSWE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a690: CYRILLIC CAPITAL LETTER TSSE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a691: CYRILLIC SMALL LETTER TSSE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a692: CYRILLIC CAPITAL LETTER TCHE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a693: CYRILLIC SMALL LETTER TCHE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a694: CYRILLIC CAPITAL LETTER HWE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a695: CYRILLIC SMALL LETTER HWE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a696: CYRILLIC CAPITAL LETTER SHWE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a697: CYRILLIC SMALL LETTER SHWE*/
+ 0, /* U+00a698: */
+ 0, /* U+00a699: */
+ 0, /* U+00a69a: */
+ 0, /* U+00a69b: */
+ 0, /* U+00a69c: */
+ 0, /* U+00a69d: */
+ 0, /* U+00a69e: */
+ RTUNI_ALPHA, /* U+00a69f: COMBINING CYRILLIC LETTER IOTIFIED E*/
+ RTUNI_ALPHA, /* U+00a6a0: BAMUM LETTER A*/
+ RTUNI_ALPHA, /* U+00a6a1: BAMUM LETTER KA*/
+ RTUNI_ALPHA, /* U+00a6a2: BAMUM LETTER U*/
+ RTUNI_ALPHA, /* U+00a6a3: BAMUM LETTER KU*/
+ RTUNI_ALPHA, /* U+00a6a4: BAMUM LETTER EE*/
+ RTUNI_ALPHA, /* U+00a6a5: BAMUM LETTER REE*/
+ RTUNI_ALPHA, /* U+00a6a6: BAMUM LETTER TAE*/
+ RTUNI_ALPHA, /* U+00a6a7: BAMUM LETTER O*/
+ RTUNI_ALPHA, /* U+00a6a8: BAMUM LETTER NYI*/
+ RTUNI_ALPHA, /* U+00a6a9: BAMUM LETTER I*/
+ RTUNI_ALPHA, /* U+00a6aa: BAMUM LETTER LA*/
+ RTUNI_ALPHA, /* U+00a6ab: BAMUM LETTER PA*/
+ RTUNI_ALPHA, /* U+00a6ac: BAMUM LETTER RII*/
+ RTUNI_ALPHA, /* U+00a6ad: BAMUM LETTER RIEE*/
+ RTUNI_ALPHA, /* U+00a6ae: BAMUM LETTER LEEEE*/
+ RTUNI_ALPHA, /* U+00a6af: BAMUM LETTER MEEEE*/
+ RTUNI_ALPHA, /* U+00a6b0: BAMUM LETTER TAA*/
+ RTUNI_ALPHA, /* U+00a6b1: BAMUM LETTER NDAA*/
+ RTUNI_ALPHA, /* U+00a6b2: BAMUM LETTER NJAEM*/
+ RTUNI_ALPHA, /* U+00a6b3: BAMUM LETTER M*/
+ RTUNI_ALPHA, /* U+00a6b4: BAMUM LETTER SUU*/
+ RTUNI_ALPHA, /* U+00a6b5: BAMUM LETTER MU*/
+ RTUNI_ALPHA, /* U+00a6b6: BAMUM LETTER SHII*/
+ RTUNI_ALPHA, /* U+00a6b7: BAMUM LETTER SI*/
+ RTUNI_ALPHA, /* U+00a6b8: BAMUM LETTER SHEUX*/
+ RTUNI_ALPHA, /* U+00a6b9: BAMUM LETTER SEUX*/
+ RTUNI_ALPHA, /* U+00a6ba: BAMUM LETTER KYEE*/
+ RTUNI_ALPHA, /* U+00a6bb: BAMUM LETTER KET*/
+ RTUNI_ALPHA, /* U+00a6bc: BAMUM LETTER NUAE*/
+ RTUNI_ALPHA, /* U+00a6bd: BAMUM LETTER NU*/
+ RTUNI_ALPHA, /* U+00a6be: BAMUM LETTER NJUAE*/
+ RTUNI_ALPHA, /* U+00a6bf: BAMUM LETTER YOQ*/
+ RTUNI_ALPHA, /* U+00a6c0: BAMUM LETTER SHU*/
+ RTUNI_ALPHA, /* U+00a6c1: BAMUM LETTER YUQ*/
+ RTUNI_ALPHA, /* U+00a6c2: BAMUM LETTER YA*/
+ RTUNI_ALPHA, /* U+00a6c3: BAMUM LETTER NSHA*/
+ RTUNI_ALPHA, /* U+00a6c4: BAMUM LETTER KEUX*/
+ RTUNI_ALPHA, /* U+00a6c5: BAMUM LETTER PEUX*/
+ RTUNI_ALPHA, /* U+00a6c6: BAMUM LETTER NJEE*/
+ RTUNI_ALPHA, /* U+00a6c7: BAMUM LETTER NTEE*/
+ RTUNI_ALPHA, /* U+00a6c8: BAMUM LETTER PUE*/
+ RTUNI_ALPHA, /* U+00a6c9: BAMUM LETTER WUE*/
+ RTUNI_ALPHA, /* U+00a6ca: BAMUM LETTER PEE*/
+ RTUNI_ALPHA, /* U+00a6cb: BAMUM LETTER FEE*/
+ RTUNI_ALPHA, /* U+00a6cc: BAMUM LETTER RU*/
+ RTUNI_ALPHA, /* U+00a6cd: BAMUM LETTER LU*/
+ RTUNI_ALPHA, /* U+00a6ce: BAMUM LETTER MI*/
+ RTUNI_ALPHA, /* U+00a6cf: BAMUM LETTER NI*/
+ RTUNI_ALPHA, /* U+00a6d0: BAMUM LETTER REUX*/
+ RTUNI_ALPHA, /* U+00a6d1: BAMUM LETTER RAE*/
+ RTUNI_ALPHA, /* U+00a6d2: BAMUM LETTER KEN*/
+ RTUNI_ALPHA, /* U+00a6d3: BAMUM LETTER NGKWAEN*/
+ RTUNI_ALPHA, /* U+00a6d4: BAMUM LETTER NGGA*/
+ RTUNI_ALPHA, /* U+00a6d5: BAMUM LETTER NGA*/
+ RTUNI_ALPHA, /* U+00a6d6: BAMUM LETTER SHO*/
+ RTUNI_ALPHA, /* U+00a6d7: BAMUM LETTER PUAE*/
+ RTUNI_ALPHA, /* U+00a6d8: BAMUM LETTER FU*/
+ RTUNI_ALPHA, /* U+00a6d9: BAMUM LETTER FOM*/
+ RTUNI_ALPHA, /* U+00a6da: BAMUM LETTER WA*/
+ RTUNI_ALPHA, /* U+00a6db: BAMUM LETTER NA*/
+ RTUNI_ALPHA, /* U+00a6dc: BAMUM LETTER LI*/
+ RTUNI_ALPHA, /* U+00a6dd: BAMUM LETTER PI*/
+ RTUNI_ALPHA, /* U+00a6de: BAMUM LETTER LOQ*/
+ RTUNI_ALPHA, /* U+00a6df: BAMUM LETTER KO*/
+ RTUNI_ALPHA, /* U+00a6e0: BAMUM LETTER MBEN*/
+ RTUNI_ALPHA, /* U+00a6e1: BAMUM LETTER REN*/
+ RTUNI_ALPHA, /* U+00a6e2: BAMUM LETTER MEN*/
+ RTUNI_ALPHA, /* U+00a6e3: BAMUM LETTER MA*/
+ RTUNI_ALPHA, /* U+00a6e4: BAMUM LETTER TI*/
+ RTUNI_ALPHA, /* U+00a6e5: BAMUM LETTER KI*/
+ RTUNI_ALPHA, /* U+00a6e6: BAMUM LETTER MO*/
+ RTUNI_ALPHA, /* U+00a6e7: BAMUM LETTER MBAA*/
+ RTUNI_ALPHA, /* U+00a6e8: BAMUM LETTER TET*/
+ RTUNI_ALPHA, /* U+00a6e9: BAMUM LETTER KPA*/
+ RTUNI_ALPHA, /* U+00a6ea: BAMUM LETTER TEN*/
+ RTUNI_ALPHA, /* U+00a6eb: BAMUM LETTER NTUU*/
+ RTUNI_ALPHA, /* U+00a6ec: BAMUM LETTER SAMBA*/
+ RTUNI_ALPHA, /* U+00a6ed: BAMUM LETTER FAAMAE*/
+ RTUNI_ALPHA, /* U+00a6ee: BAMUM LETTER KOVUU*/
+ RTUNI_ALPHA, /* U+00a6ef: BAMUM LETTER KOGHOM*/
+ 0, /* U+00a6f0: BAMUM COMBINING MARK KOQNDON*/
+ 0, /* U+00a6f1: BAMUM COMBINING MARK TUKWENTIS*/
+ 0, /* U+00a6f2: BAMUM NJAEMLI*/
+ 0, /* U+00a6f3: BAMUM FULL STOP*/
+ 0, /* U+00a6f4: BAMUM COLON*/
+ 0, /* U+00a6f5: BAMUM COMMA*/
+ 0, /* U+00a6f6: BAMUM SEMICOLON*/
+ 0, /* U+00a6f7: BAMUM QUESTION MARK*/
+ 0, /* U+00a6f8: */
+ 0, /* U+00a6f9: */
+ 0, /* U+00a6fa: */
+ 0, /* U+00a6fb: */
+ 0, /* U+00a6fc: */
+ 0, /* U+00a6fd: */
+ 0, /* U+00a6fe: */
+ 0, /* U+00a6ff: */
+ 0, /* U+00a700: MODIFIER LETTER CHINESE TONE YIN PING*/
+ 0, /* U+00a701: MODIFIER LETTER CHINESE TONE YANG PING*/
+ 0, /* U+00a702: MODIFIER LETTER CHINESE TONE YIN SHANG*/
+ 0, /* U+00a703: MODIFIER LETTER CHINESE TONE YANG SHANG*/
+ 0, /* U+00a704: MODIFIER LETTER CHINESE TONE YIN QU*/
+ 0, /* U+00a705: MODIFIER LETTER CHINESE TONE YANG QU*/
+ 0, /* U+00a706: MODIFIER LETTER CHINESE TONE YIN RU*/
+ 0, /* U+00a707: MODIFIER LETTER CHINESE TONE YANG RU*/
+ 0, /* U+00a708: MODIFIER LETTER EXTRA-HIGH DOTTED TONE BAR*/
+ 0, /* U+00a709: MODIFIER LETTER HIGH DOTTED TONE BAR*/
+ 0, /* U+00a70a: MODIFIER LETTER MID DOTTED TONE BAR*/
+ 0, /* U+00a70b: MODIFIER LETTER LOW DOTTED TONE BAR*/
+ 0, /* U+00a70c: MODIFIER LETTER EXTRA-LOW DOTTED TONE BAR*/
+ 0, /* U+00a70d: MODIFIER LETTER EXTRA-HIGH DOTTED LEFT-STEM TONE BAR*/
+ 0, /* U+00a70e: MODIFIER LETTER HIGH DOTTED LEFT-STEM TONE BAR*/
+ 0, /* U+00a70f: MODIFIER LETTER MID DOTTED LEFT-STEM TONE BAR*/
+ 0, /* U+00a710: MODIFIER LETTER LOW DOTTED LEFT-STEM TONE BAR*/
+ 0, /* U+00a711: MODIFIER LETTER EXTRA-LOW DOTTED LEFT-STEM TONE BAR*/
+ 0, /* U+00a712: MODIFIER LETTER EXTRA-HIGH LEFT-STEM TONE BAR*/
+ 0, /* U+00a713: MODIFIER LETTER HIGH LEFT-STEM TONE BAR*/
+ 0, /* U+00a714: MODIFIER LETTER MID LEFT-STEM TONE BAR*/
+ 0, /* U+00a715: MODIFIER LETTER LOW LEFT-STEM TONE BAR*/
+ 0, /* U+00a716: MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR*/
+ RTUNI_ALPHA, /* U+00a717: MODIFIER LETTER DOT VERTICAL BAR*/
+ RTUNI_ALPHA, /* U+00a718: MODIFIER LETTER DOT SLASH*/
+ RTUNI_ALPHA, /* U+00a719: MODIFIER LETTER DOT HORIZONTAL BAR*/
+ RTUNI_ALPHA, /* U+00a71a: MODIFIER LETTER LOWER RIGHT CORNER ANGLE*/
+ RTUNI_ALPHA, /* U+00a71b: MODIFIER LETTER RAISED UP ARROW*/
+ RTUNI_ALPHA, /* U+00a71c: MODIFIER LETTER RAISED DOWN ARROW*/
+ RTUNI_ALPHA, /* U+00a71d: MODIFIER LETTER RAISED EXCLAMATION MARK*/
+ RTUNI_ALPHA, /* U+00a71e: MODIFIER LETTER RAISED INVERTED EXCLAMATION MARK*/
+ RTUNI_ALPHA, /* U+00a71f: MODIFIER LETTER LOW INVERTED EXCLAMATION MARK*/
+ 0, /* U+00a720: MODIFIER LETTER STRESS AND HIGH TONE*/
+ 0, /* U+00a721: MODIFIER LETTER STRESS AND LOW TONE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a722: LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a723: LATIN SMALL LETTER EGYPTOLOGICAL ALEF*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a724: LATIN CAPITAL LETTER EGYPTOLOGICAL AIN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a725: LATIN SMALL LETTER EGYPTOLOGICAL AIN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a726: LATIN CAPITAL LETTER HENG*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a727: LATIN SMALL LETTER HENG*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a728: LATIN CAPITAL LETTER TZ*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a729: LATIN SMALL LETTER TZ*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a72a: LATIN CAPITAL LETTER TRESILLO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a72b: LATIN SMALL LETTER TRESILLO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a72c: LATIN CAPITAL LETTER CUATRILLO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a72d: LATIN SMALL LETTER CUATRILLO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a72e: LATIN CAPITAL LETTER CUATRILLO WITH COMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a72f: LATIN SMALL LETTER CUATRILLO WITH COMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a730: LATIN LETTER SMALL CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a731: LATIN LETTER SMALL CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a732: LATIN CAPITAL LETTER AA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a733: LATIN SMALL LETTER AA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a734: LATIN CAPITAL LETTER AO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a735: LATIN SMALL LETTER AO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a736: LATIN CAPITAL LETTER AU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a737: LATIN SMALL LETTER AU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a738: LATIN CAPITAL LETTER AV*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a739: LATIN SMALL LETTER AV*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a73a: LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a73b: LATIN SMALL LETTER AV WITH HORIZONTAL BAR*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a73c: LATIN CAPITAL LETTER AY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a73d: LATIN SMALL LETTER AY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a73e: LATIN CAPITAL LETTER REVERSED C WITH DOT*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a73f: LATIN SMALL LETTER REVERSED C WITH DOT*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a740: LATIN CAPITAL LETTER K WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a741: LATIN SMALL LETTER K WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a742: LATIN CAPITAL LETTER K WITH DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a743: LATIN SMALL LETTER K WITH DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a744: LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a745: LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a746: LATIN CAPITAL LETTER BROKEN L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a747: LATIN SMALL LETTER BROKEN L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a748: LATIN CAPITAL LETTER L WITH HIGH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a749: LATIN SMALL LETTER L WITH HIGH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a74a: LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a74b: LATIN SMALL LETTER O WITH LONG STROKE OVERLAY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a74c: LATIN CAPITAL LETTER O WITH LOOP*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a74d: LATIN SMALL LETTER O WITH LOOP*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a74e: LATIN CAPITAL LETTER OO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a74f: LATIN SMALL LETTER OO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a750: LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a751: LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a752: LATIN CAPITAL LETTER P WITH FLOURISH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a753: LATIN SMALL LETTER P WITH FLOURISH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a754: LATIN CAPITAL LETTER P WITH SQUIRREL TAIL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a755: LATIN SMALL LETTER P WITH SQUIRREL TAIL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a756: LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a757: LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a758: LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a759: LATIN SMALL LETTER Q WITH DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a75a: LATIN CAPITAL LETTER R ROTUNDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a75b: LATIN SMALL LETTER R ROTUNDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a75c: LATIN CAPITAL LETTER RUM ROTUNDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a75d: LATIN SMALL LETTER RUM ROTUNDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a75e: LATIN CAPITAL LETTER V WITH DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a75f: LATIN SMALL LETTER V WITH DIAGONAL STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a760: LATIN CAPITAL LETTER VY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a761: LATIN SMALL LETTER VY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a762: LATIN CAPITAL LETTER VISIGOTHIC Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a763: LATIN SMALL LETTER VISIGOTHIC Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a764: LATIN CAPITAL LETTER THORN WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a765: LATIN SMALL LETTER THORN WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a766: LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a767: LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a768: LATIN CAPITAL LETTER VEND*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a769: LATIN SMALL LETTER VEND*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a76a: LATIN CAPITAL LETTER ET*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a76b: LATIN SMALL LETTER ET*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a76c: LATIN CAPITAL LETTER IS*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a76d: LATIN SMALL LETTER IS*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a76e: LATIN CAPITAL LETTER CON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a76f: LATIN SMALL LETTER CON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a770: MODIFIER LETTER US*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a771: LATIN SMALL LETTER DUM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a772: LATIN SMALL LETTER LUM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a773: LATIN SMALL LETTER MUM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a774: LATIN SMALL LETTER NUM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a775: LATIN SMALL LETTER RUM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a776: LATIN LETTER SMALL CAPITAL RUM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a777: LATIN SMALL LETTER TUM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a778: LATIN SMALL LETTER UM*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a779: LATIN CAPITAL LETTER INSULAR D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a77a: LATIN SMALL LETTER INSULAR D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a77b: LATIN CAPITAL LETTER INSULAR F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a77c: LATIN SMALL LETTER INSULAR F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a77d: LATIN CAPITAL LETTER INSULAR G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a77e: LATIN CAPITAL LETTER TURNED INSULAR G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a77f: LATIN SMALL LETTER TURNED INSULAR G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a780: LATIN CAPITAL LETTER TURNED L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a781: LATIN SMALL LETTER TURNED L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a782: LATIN CAPITAL LETTER INSULAR R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a783: LATIN SMALL LETTER INSULAR R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a784: LATIN CAPITAL LETTER INSULAR S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a785: LATIN SMALL LETTER INSULAR S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a786: LATIN CAPITAL LETTER INSULAR T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a787: LATIN SMALL LETTER INSULAR T*/
+ RTUNI_ALPHA, /* U+00a788: MODIFIER LETTER LOW CIRCUMFLEX ACCENT*/
+ 0, /* U+00a789: MODIFIER LETTER COLON*/
+ 0, /* U+00a78a: MODIFIER LETTER SHORT EQUALS SIGN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a78b: LATIN CAPITAL LETTER SALTILLO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a78c: LATIN SMALL LETTER SALTILLO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a78d: LATIN CAPITAL LETTER TURNED H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a78e: LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT*/
+ 0, /* U+00a78f: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a790: LATIN CAPITAL LETTER N WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a791: LATIN SMALL LETTER N WITH DESCENDER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a792: LATIN CAPITAL LETTER C WITH BAR*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a793: LATIN SMALL LETTER C WITH BAR*/
+ 0, /* U+00a794: */
+ 0, /* U+00a795: */
+ 0, /* U+00a796: */
+ 0, /* U+00a797: */
+ 0, /* U+00a798: */
+ 0, /* U+00a799: */
+ 0, /* U+00a79a: */
+ 0, /* U+00a79b: */
+ 0, /* U+00a79c: */
+ 0, /* U+00a79d: */
+ 0, /* U+00a79e: */
+ 0, /* U+00a79f: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a7a0: LATIN CAPITAL LETTER G WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a7a1: LATIN SMALL LETTER G WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a7a2: LATIN CAPITAL LETTER K WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a7a3: LATIN SMALL LETTER K WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a7a4: LATIN CAPITAL LETTER N WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a7a5: LATIN SMALL LETTER N WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a7a6: LATIN CAPITAL LETTER R WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a7a7: LATIN SMALL LETTER R WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a7a8: LATIN CAPITAL LETTER S WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a7a9: LATIN SMALL LETTER S WITH OBLIQUE STROKE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00a7aa: LATIN CAPITAL LETTER H WITH HOOK*/
+ 0, /* U+00a7ab: */
+ 0, /* U+00a7ac: */
+ 0, /* U+00a7ad: */
+ 0, /* U+00a7ae: */
+ 0, /* U+00a7af: */
+ 0, /* U+00a7b0: */
+ 0, /* U+00a7b1: */
+ 0, /* U+00a7b2: */
+ 0, /* U+00a7b3: */
+ 0, /* U+00a7b4: */
+ 0, /* U+00a7b5: */
+ 0, /* U+00a7b6: */
+ 0, /* U+00a7b7: */
+ 0, /* U+00a7b8: */
+ 0, /* U+00a7b9: */
+ 0, /* U+00a7ba: */
+ 0, /* U+00a7bb: */
+ 0, /* U+00a7bc: */
+ 0, /* U+00a7bd: */
+ 0, /* U+00a7be: */
+ 0, /* U+00a7bf: */
+ 0, /* U+00a7c0: */
+ 0, /* U+00a7c1: */
+ 0, /* U+00a7c2: */
+ 0, /* U+00a7c3: */
+ 0, /* U+00a7c4: */
+ 0, /* U+00a7c5: */
+ 0, /* U+00a7c6: */
+ 0, /* U+00a7c7: */
+ 0, /* U+00a7c8: */
+ 0, /* U+00a7c9: */
+ 0, /* U+00a7ca: */
+ 0, /* U+00a7cb: */
+ 0, /* U+00a7cc: */
+ 0, /* U+00a7cd: */
+ 0, /* U+00a7ce: */
+ 0, /* U+00a7cf: */
+ 0, /* U+00a7d0: */
+ 0, /* U+00a7d1: */
+ 0, /* U+00a7d2: */
+ 0, /* U+00a7d3: */
+ 0, /* U+00a7d4: */
+ 0, /* U+00a7d5: */
+ 0, /* U+00a7d6: */
+ 0, /* U+00a7d7: */
+ 0, /* U+00a7d8: */
+ 0, /* U+00a7d9: */
+ 0, /* U+00a7da: */
+ 0, /* U+00a7db: */
+ 0, /* U+00a7dc: */
+ 0, /* U+00a7dd: */
+ 0, /* U+00a7de: */
+ 0, /* U+00a7df: */
+ 0, /* U+00a7e0: */
+ 0, /* U+00a7e1: */
+ 0, /* U+00a7e2: */
+ 0, /* U+00a7e3: */
+ 0, /* U+00a7e4: */
+ 0, /* U+00a7e5: */
+ 0, /* U+00a7e6: */
+ 0, /* U+00a7e7: */
+ 0, /* U+00a7e8: */
+ 0, /* U+00a7e9: */
+ 0, /* U+00a7ea: */
+ 0, /* U+00a7eb: */
+ 0, /* U+00a7ec: */
+ 0, /* U+00a7ed: */
+ 0, /* U+00a7ee: */
+ 0, /* U+00a7ef: */
+ 0, /* U+00a7f0: */
+ 0, /* U+00a7f1: */
+ 0, /* U+00a7f2: */
+ 0, /* U+00a7f3: */
+ 0, /* U+00a7f4: */
+ 0, /* U+00a7f5: */
+ 0, /* U+00a7f6: */
+ 0, /* U+00a7f7: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a7f8: MODIFIER LETTER CAPITAL H WITH STROKE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a7f9: MODIFIER LETTER SMALL LIGATURE OE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00a7fa: LATIN LETTER SMALL CAPITAL TURNED M*/
+ RTUNI_ALPHA, /* U+00a7fb: LATIN EPIGRAPHIC LETTER REVERSED F*/
+ RTUNI_ALPHA, /* U+00a7fc: LATIN EPIGRAPHIC LETTER REVERSED P*/
+ RTUNI_ALPHA, /* U+00a7fd: LATIN EPIGRAPHIC LETTER INVERTED M*/
+ RTUNI_ALPHA, /* U+00a7fe: LATIN EPIGRAPHIC LETTER I LONGA*/
+ RTUNI_ALPHA, /* U+00a7ff: LATIN EPIGRAPHIC LETTER ARCHAIC M*/
+ RTUNI_ALPHA, /* U+00a800: SYLOTI NAGRI LETTER A*/
+ RTUNI_ALPHA, /* U+00a801: SYLOTI NAGRI LETTER I*/
+ 0, /* U+00a802: SYLOTI NAGRI SIGN DVISVARA*/
+ RTUNI_ALPHA, /* U+00a803: SYLOTI NAGRI LETTER U*/
+ RTUNI_ALPHA, /* U+00a804: SYLOTI NAGRI LETTER E*/
+ RTUNI_ALPHA, /* U+00a805: SYLOTI NAGRI LETTER O*/
+ 0, /* U+00a806: SYLOTI NAGRI SIGN HASANTA*/
+ RTUNI_ALPHA, /* U+00a807: SYLOTI NAGRI LETTER KO*/
+ RTUNI_ALPHA, /* U+00a808: SYLOTI NAGRI LETTER KHO*/
+ RTUNI_ALPHA, /* U+00a809: SYLOTI NAGRI LETTER GO*/
+ RTUNI_ALPHA, /* U+00a80a: SYLOTI NAGRI LETTER GHO*/
+ 0, /* U+00a80b: SYLOTI NAGRI SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+00a80c: SYLOTI NAGRI LETTER CO*/
+ RTUNI_ALPHA, /* U+00a80d: SYLOTI NAGRI LETTER CHO*/
+ RTUNI_ALPHA, /* U+00a80e: SYLOTI NAGRI LETTER JO*/
+ RTUNI_ALPHA, /* U+00a80f: SYLOTI NAGRI LETTER JHO*/
+ RTUNI_ALPHA, /* U+00a810: SYLOTI NAGRI LETTER TTO*/
+ RTUNI_ALPHA, /* U+00a811: SYLOTI NAGRI LETTER TTHO*/
+ RTUNI_ALPHA, /* U+00a812: SYLOTI NAGRI LETTER DDO*/
+ RTUNI_ALPHA, /* U+00a813: SYLOTI NAGRI LETTER DDHO*/
+ RTUNI_ALPHA, /* U+00a814: SYLOTI NAGRI LETTER TO*/
+ RTUNI_ALPHA, /* U+00a815: SYLOTI NAGRI LETTER THO*/
+ RTUNI_ALPHA, /* U+00a816: SYLOTI NAGRI LETTER DO*/
+ RTUNI_ALPHA, /* U+00a817: SYLOTI NAGRI LETTER DHO*/
+ RTUNI_ALPHA, /* U+00a818: SYLOTI NAGRI LETTER NO*/
+ RTUNI_ALPHA, /* U+00a819: SYLOTI NAGRI LETTER PO*/
+ RTUNI_ALPHA, /* U+00a81a: SYLOTI NAGRI LETTER PHO*/
+ RTUNI_ALPHA, /* U+00a81b: SYLOTI NAGRI LETTER BO*/
+ RTUNI_ALPHA, /* U+00a81c: SYLOTI NAGRI LETTER BHO*/
+ RTUNI_ALPHA, /* U+00a81d: SYLOTI NAGRI LETTER MO*/
+ RTUNI_ALPHA, /* U+00a81e: SYLOTI NAGRI LETTER RO*/
+ RTUNI_ALPHA, /* U+00a81f: SYLOTI NAGRI LETTER LO*/
+ RTUNI_ALPHA, /* U+00a820: SYLOTI NAGRI LETTER RRO*/
+ RTUNI_ALPHA, /* U+00a821: SYLOTI NAGRI LETTER SO*/
+ RTUNI_ALPHA, /* U+00a822: SYLOTI NAGRI LETTER HO*/
+ RTUNI_ALPHA, /* U+00a823: SYLOTI NAGRI VOWEL SIGN A*/
+ RTUNI_ALPHA, /* U+00a824: SYLOTI NAGRI VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+00a825: SYLOTI NAGRI VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+00a826: SYLOTI NAGRI VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+00a827: SYLOTI NAGRI VOWEL SIGN OO*/
+ 0, /* U+00a828: SYLOTI NAGRI POETRY MARK-1*/
+ 0, /* U+00a829: SYLOTI NAGRI POETRY MARK-2*/
+ 0, /* U+00a82a: SYLOTI NAGRI POETRY MARK-3*/
+ 0, /* U+00a82b: SYLOTI NAGRI POETRY MARK-4*/
+ 0, /* U+00a82c: */
+ 0, /* U+00a82d: */
+ 0, /* U+00a82e: */
+ 0, /* U+00a82f: */
+ 0, /* U+00a830: NORTH INDIC FRACTION ONE QUARTER*/
+ 0, /* U+00a831: NORTH INDIC FRACTION ONE HALF*/
+ 0, /* U+00a832: NORTH INDIC FRACTION THREE QUARTERS*/
+ 0, /* U+00a833: NORTH INDIC FRACTION ONE SIXTEENTH*/
+ 0, /* U+00a834: NORTH INDIC FRACTION ONE EIGHTH*/
+ 0, /* U+00a835: NORTH INDIC FRACTION THREE SIXTEENTHS*/
+ 0, /* U+00a836: NORTH INDIC QUARTER MARK*/
+ 0, /* U+00a837: NORTH INDIC PLACEHOLDER MARK*/
+ 0, /* U+00a838: NORTH INDIC RUPEE MARK*/
+ 0, /* U+00a839: NORTH INDIC QUANTITY MARK*/
+ 0, /* U+00a83a: */
+ 0, /* U+00a83b: */
+ 0, /* U+00a83c: */
+ 0, /* U+00a83d: */
+ 0, /* U+00a83e: */
+ 0, /* U+00a83f: */
+ RTUNI_ALPHA, /* U+00a840: PHAGS-PA LETTER KA*/
+ RTUNI_ALPHA, /* U+00a841: PHAGS-PA LETTER KHA*/
+ RTUNI_ALPHA, /* U+00a842: PHAGS-PA LETTER GA*/
+ RTUNI_ALPHA, /* U+00a843: PHAGS-PA LETTER NGA*/
+ RTUNI_ALPHA, /* U+00a844: PHAGS-PA LETTER CA*/
+ RTUNI_ALPHA, /* U+00a845: PHAGS-PA LETTER CHA*/
+ RTUNI_ALPHA, /* U+00a846: PHAGS-PA LETTER JA*/
+ RTUNI_ALPHA, /* U+00a847: PHAGS-PA LETTER NYA*/
+ RTUNI_ALPHA, /* U+00a848: PHAGS-PA LETTER TA*/
+ RTUNI_ALPHA, /* U+00a849: PHAGS-PA LETTER THA*/
+ RTUNI_ALPHA, /* U+00a84a: PHAGS-PA LETTER DA*/
+ RTUNI_ALPHA, /* U+00a84b: PHAGS-PA LETTER NA*/
+ RTUNI_ALPHA, /* U+00a84c: PHAGS-PA LETTER PA*/
+ RTUNI_ALPHA, /* U+00a84d: PHAGS-PA LETTER PHA*/
+ RTUNI_ALPHA, /* U+00a84e: PHAGS-PA LETTER BA*/
+ RTUNI_ALPHA, /* U+00a84f: PHAGS-PA LETTER MA*/
+ RTUNI_ALPHA, /* U+00a850: PHAGS-PA LETTER TSA*/
+ RTUNI_ALPHA, /* U+00a851: PHAGS-PA LETTER TSHA*/
+ RTUNI_ALPHA, /* U+00a852: PHAGS-PA LETTER DZA*/
+ RTUNI_ALPHA, /* U+00a853: PHAGS-PA LETTER WA*/
+ RTUNI_ALPHA, /* U+00a854: PHAGS-PA LETTER ZHA*/
+ RTUNI_ALPHA, /* U+00a855: PHAGS-PA LETTER ZA*/
+ RTUNI_ALPHA, /* U+00a856: PHAGS-PA LETTER SMALL A*/
+ RTUNI_ALPHA, /* U+00a857: PHAGS-PA LETTER YA*/
+ RTUNI_ALPHA, /* U+00a858: PHAGS-PA LETTER RA*/
+ RTUNI_ALPHA, /* U+00a859: PHAGS-PA LETTER LA*/
+ RTUNI_ALPHA, /* U+00a85a: PHAGS-PA LETTER SHA*/
+ RTUNI_ALPHA, /* U+00a85b: PHAGS-PA LETTER SA*/
+ RTUNI_ALPHA, /* U+00a85c: PHAGS-PA LETTER HA*/
+ RTUNI_ALPHA, /* U+00a85d: PHAGS-PA LETTER A*/
+ RTUNI_ALPHA, /* U+00a85e: PHAGS-PA LETTER I*/
+ RTUNI_ALPHA, /* U+00a85f: PHAGS-PA LETTER U*/
+ RTUNI_ALPHA, /* U+00a860: PHAGS-PA LETTER E*/
+ RTUNI_ALPHA, /* U+00a861: PHAGS-PA LETTER O*/
+ RTUNI_ALPHA, /* U+00a862: PHAGS-PA LETTER QA*/
+ RTUNI_ALPHA, /* U+00a863: PHAGS-PA LETTER XA*/
+ RTUNI_ALPHA, /* U+00a864: PHAGS-PA LETTER FA*/
+ RTUNI_ALPHA, /* U+00a865: PHAGS-PA LETTER GGA*/
+ RTUNI_ALPHA, /* U+00a866: PHAGS-PA LETTER EE*/
+ RTUNI_ALPHA, /* U+00a867: PHAGS-PA SUBJOINED LETTER WA*/
+ RTUNI_ALPHA, /* U+00a868: PHAGS-PA SUBJOINED LETTER YA*/
+ RTUNI_ALPHA, /* U+00a869: PHAGS-PA LETTER TTA*/
+ RTUNI_ALPHA, /* U+00a86a: PHAGS-PA LETTER TTHA*/
+ RTUNI_ALPHA, /* U+00a86b: PHAGS-PA LETTER DDA*/
+ RTUNI_ALPHA, /* U+00a86c: PHAGS-PA LETTER NNA*/
+ RTUNI_ALPHA, /* U+00a86d: PHAGS-PA LETTER ALTERNATE YA*/
+ RTUNI_ALPHA, /* U+00a86e: PHAGS-PA LETTER VOICELESS SHA*/
+ RTUNI_ALPHA, /* U+00a86f: PHAGS-PA LETTER VOICED HA*/
+ RTUNI_ALPHA, /* U+00a870: PHAGS-PA LETTER ASPIRATED FA*/
+ RTUNI_ALPHA, /* U+00a871: PHAGS-PA SUBJOINED LETTER RA*/
+ RTUNI_ALPHA, /* U+00a872: PHAGS-PA SUPERFIXED LETTER RA*/
+ RTUNI_ALPHA, /* U+00a873: PHAGS-PA LETTER CANDRABINDU*/
+ 0, /* U+00a874: PHAGS-PA SINGLE HEAD MARK*/
+ 0, /* U+00a875: PHAGS-PA DOUBLE HEAD MARK*/
+ 0, /* U+00a876: PHAGS-PA MARK SHAD*/
+ 0, /* U+00a877: PHAGS-PA MARK DOUBLE SHAD*/
+ 0, /* U+00a878: */
+ 0, /* U+00a879: */
+ 0, /* U+00a87a: */
+ 0, /* U+00a87b: */
+ 0, /* U+00a87c: */
+ 0, /* U+00a87d: */
+ 0, /* U+00a87e: */
+ 0, /* U+00a87f: */
+ RTUNI_ALPHA, /* U+00a880: SAURASHTRA SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+00a881: SAURASHTRA SIGN VISARGA*/
+ RTUNI_ALPHA, /* U+00a882: SAURASHTRA LETTER A*/
+ RTUNI_ALPHA, /* U+00a883: SAURASHTRA LETTER AA*/
+ RTUNI_ALPHA, /* U+00a884: SAURASHTRA LETTER I*/
+ RTUNI_ALPHA, /* U+00a885: SAURASHTRA LETTER II*/
+ RTUNI_ALPHA, /* U+00a886: SAURASHTRA LETTER U*/
+ RTUNI_ALPHA, /* U+00a887: SAURASHTRA LETTER UU*/
+ RTUNI_ALPHA, /* U+00a888: SAURASHTRA LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+00a889: SAURASHTRA LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+00a88a: SAURASHTRA LETTER VOCALIC L*/
+ RTUNI_ALPHA, /* U+00a88b: SAURASHTRA LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+00a88c: SAURASHTRA LETTER E*/
+ RTUNI_ALPHA, /* U+00a88d: SAURASHTRA LETTER EE*/
+ RTUNI_ALPHA, /* U+00a88e: SAURASHTRA LETTER AI*/
+ RTUNI_ALPHA, /* U+00a88f: SAURASHTRA LETTER O*/
+ RTUNI_ALPHA, /* U+00a890: SAURASHTRA LETTER OO*/
+ RTUNI_ALPHA, /* U+00a891: SAURASHTRA LETTER AU*/
+ RTUNI_ALPHA, /* U+00a892: SAURASHTRA LETTER KA*/
+ RTUNI_ALPHA, /* U+00a893: SAURASHTRA LETTER KHA*/
+ RTUNI_ALPHA, /* U+00a894: SAURASHTRA LETTER GA*/
+ RTUNI_ALPHA, /* U+00a895: SAURASHTRA LETTER GHA*/
+ RTUNI_ALPHA, /* U+00a896: SAURASHTRA LETTER NGA*/
+ RTUNI_ALPHA, /* U+00a897: SAURASHTRA LETTER CA*/
+ RTUNI_ALPHA, /* U+00a898: SAURASHTRA LETTER CHA*/
+ RTUNI_ALPHA, /* U+00a899: SAURASHTRA LETTER JA*/
+ RTUNI_ALPHA, /* U+00a89a: SAURASHTRA LETTER JHA*/
+ RTUNI_ALPHA, /* U+00a89b: SAURASHTRA LETTER NYA*/
+ RTUNI_ALPHA, /* U+00a89c: SAURASHTRA LETTER TTA*/
+ RTUNI_ALPHA, /* U+00a89d: SAURASHTRA LETTER TTHA*/
+ RTUNI_ALPHA, /* U+00a89e: SAURASHTRA LETTER DDA*/
+ RTUNI_ALPHA, /* U+00a89f: SAURASHTRA LETTER DDHA*/
+ RTUNI_ALPHA, /* U+00a8a0: SAURASHTRA LETTER NNA*/
+ RTUNI_ALPHA, /* U+00a8a1: SAURASHTRA LETTER TA*/
+ RTUNI_ALPHA, /* U+00a8a2: SAURASHTRA LETTER THA*/
+ RTUNI_ALPHA, /* U+00a8a3: SAURASHTRA LETTER DA*/
+ RTUNI_ALPHA, /* U+00a8a4: SAURASHTRA LETTER DHA*/
+ RTUNI_ALPHA, /* U+00a8a5: SAURASHTRA LETTER NA*/
+ RTUNI_ALPHA, /* U+00a8a6: SAURASHTRA LETTER PA*/
+ RTUNI_ALPHA, /* U+00a8a7: SAURASHTRA LETTER PHA*/
+ RTUNI_ALPHA, /* U+00a8a8: SAURASHTRA LETTER BA*/
+ RTUNI_ALPHA, /* U+00a8a9: SAURASHTRA LETTER BHA*/
+ RTUNI_ALPHA, /* U+00a8aa: SAURASHTRA LETTER MA*/
+ RTUNI_ALPHA, /* U+00a8ab: SAURASHTRA LETTER YA*/
+ RTUNI_ALPHA, /* U+00a8ac: SAURASHTRA LETTER RA*/
+ RTUNI_ALPHA, /* U+00a8ad: SAURASHTRA LETTER LA*/
+ RTUNI_ALPHA, /* U+00a8ae: SAURASHTRA LETTER VA*/
+ RTUNI_ALPHA, /* U+00a8af: SAURASHTRA LETTER SHA*/
+ RTUNI_ALPHA, /* U+00a8b0: SAURASHTRA LETTER SSA*/
+ RTUNI_ALPHA, /* U+00a8b1: SAURASHTRA LETTER SA*/
+ RTUNI_ALPHA, /* U+00a8b2: SAURASHTRA LETTER HA*/
+ RTUNI_ALPHA, /* U+00a8b3: SAURASHTRA LETTER LLA*/
+ RTUNI_ALPHA, /* U+00a8b4: SAURASHTRA CONSONANT SIGN HAARU*/
+ RTUNI_ALPHA, /* U+00a8b5: SAURASHTRA VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+00a8b6: SAURASHTRA VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+00a8b7: SAURASHTRA VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+00a8b8: SAURASHTRA VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+00a8b9: SAURASHTRA VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+00a8ba: SAURASHTRA VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+00a8bb: SAURASHTRA VOWEL SIGN VOCALIC RR*/
+ RTUNI_ALPHA, /* U+00a8bc: SAURASHTRA VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+00a8bd: SAURASHTRA VOWEL SIGN VOCALIC LL*/
+ RTUNI_ALPHA, /* U+00a8be: SAURASHTRA VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+00a8bf: SAURASHTRA VOWEL SIGN EE*/
+ RTUNI_ALPHA, /* U+00a8c0: SAURASHTRA VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+00a8c1: SAURASHTRA VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+00a8c2: SAURASHTRA VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+00a8c3: SAURASHTRA VOWEL SIGN AU*/
+ 0, /* U+00a8c4: SAURASHTRA SIGN VIRAMA*/
+ 0, /* U+00a8c5: */
+ 0, /* U+00a8c6: */
+ 0, /* U+00a8c7: */
+ 0, /* U+00a8c8: */
+ 0, /* U+00a8c9: */
+ 0, /* U+00a8ca: */
+ 0, /* U+00a8cb: */
+ 0, /* U+00a8cc: */
+ 0, /* U+00a8cd: */
+ 0, /* U+00a8ce: SAURASHTRA DANDA*/
+ 0, /* U+00a8cf: SAURASHTRA DOUBLE DANDA*/
+ RTUNI_DDIGIT, /* U+00a8d0: SAURASHTRA DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+00a8d1: SAURASHTRA DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+00a8d2: SAURASHTRA DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+00a8d3: SAURASHTRA DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+00a8d4: SAURASHTRA DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+00a8d5: SAURASHTRA DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+00a8d6: SAURASHTRA DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+00a8d7: SAURASHTRA DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+00a8d8: SAURASHTRA DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+00a8d9: SAURASHTRA DIGIT NINE*/
+ 0, /* U+00a8da: */
+ 0, /* U+00a8db: */
+ 0, /* U+00a8dc: */
+ 0, /* U+00a8dd: */
+ 0, /* U+00a8de: */
+ 0, /* U+00a8df: */
+ 0, /* U+00a8e0: COMBINING DEVANAGARI DIGIT ZERO*/
+ 0, /* U+00a8e1: COMBINING DEVANAGARI DIGIT ONE*/
+ 0, /* U+00a8e2: COMBINING DEVANAGARI DIGIT TWO*/
+ 0, /* U+00a8e3: COMBINING DEVANAGARI DIGIT THREE*/
+ 0, /* U+00a8e4: COMBINING DEVANAGARI DIGIT FOUR*/
+ 0, /* U+00a8e5: COMBINING DEVANAGARI DIGIT FIVE*/
+ 0, /* U+00a8e6: COMBINING DEVANAGARI DIGIT SIX*/
+ 0, /* U+00a8e7: COMBINING DEVANAGARI DIGIT SEVEN*/
+ 0, /* U+00a8e8: COMBINING DEVANAGARI DIGIT EIGHT*/
+ 0, /* U+00a8e9: COMBINING DEVANAGARI DIGIT NINE*/
+ 0, /* U+00a8ea: COMBINING DEVANAGARI LETTER A*/
+ 0, /* U+00a8eb: COMBINING DEVANAGARI LETTER U*/
+ 0, /* U+00a8ec: COMBINING DEVANAGARI LETTER KA*/
+ 0, /* U+00a8ed: COMBINING DEVANAGARI LETTER NA*/
+ 0, /* U+00a8ee: COMBINING DEVANAGARI LETTER PA*/
+ 0, /* U+00a8ef: COMBINING DEVANAGARI LETTER RA*/
+ 0, /* U+00a8f0: COMBINING DEVANAGARI LETTER VI*/
+ 0, /* U+00a8f1: COMBINING DEVANAGARI SIGN AVAGRAHA*/
+ RTUNI_ALPHA, /* U+00a8f2: DEVANAGARI SIGN SPACING CANDRABINDU*/
+ RTUNI_ALPHA, /* U+00a8f3: DEVANAGARI SIGN CANDRABINDU VIRAMA*/
+ RTUNI_ALPHA, /* U+00a8f4: DEVANAGARI SIGN DOUBLE CANDRABINDU VIRAMA*/
+ RTUNI_ALPHA, /* U+00a8f5: DEVANAGARI SIGN CANDRABINDU TWO*/
+ RTUNI_ALPHA, /* U+00a8f6: DEVANAGARI SIGN CANDRABINDU THREE*/
+ RTUNI_ALPHA, /* U+00a8f7: DEVANAGARI SIGN CANDRABINDU AVAGRAHA*/
+ 0, /* U+00a8f8: DEVANAGARI SIGN PUSHPIKA*/
+ 0, /* U+00a8f9: DEVANAGARI GAP FILLER*/
+ 0, /* U+00a8fa: DEVANAGARI CARET*/
+ RTUNI_ALPHA, /* U+00a8fb: DEVANAGARI HEADSTROKE*/
+ 0, /* U+00a8fc: */
+ 0, /* U+00a8fd: */
+ 0, /* U+00a8fe: */
+ 0, /* U+00a8ff: */
+ RTUNI_DDIGIT, /* U+00a900: KAYAH LI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+00a901: KAYAH LI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+00a902: KAYAH LI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+00a903: KAYAH LI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+00a904: KAYAH LI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+00a905: KAYAH LI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+00a906: KAYAH LI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+00a907: KAYAH LI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+00a908: KAYAH LI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+00a909: KAYAH LI DIGIT NINE*/
+ RTUNI_ALPHA, /* U+00a90a: KAYAH LI LETTER KA*/
+ RTUNI_ALPHA, /* U+00a90b: KAYAH LI LETTER KHA*/
+ RTUNI_ALPHA, /* U+00a90c: KAYAH LI LETTER GA*/
+ RTUNI_ALPHA, /* U+00a90d: KAYAH LI LETTER NGA*/
+ RTUNI_ALPHA, /* U+00a90e: KAYAH LI LETTER SA*/
+ RTUNI_ALPHA, /* U+00a90f: KAYAH LI LETTER SHA*/
+ RTUNI_ALPHA, /* U+00a910: KAYAH LI LETTER ZA*/
+ RTUNI_ALPHA, /* U+00a911: KAYAH LI LETTER NYA*/
+ RTUNI_ALPHA, /* U+00a912: KAYAH LI LETTER TA*/
+ RTUNI_ALPHA, /* U+00a913: KAYAH LI LETTER HTA*/
+ RTUNI_ALPHA, /* U+00a914: KAYAH LI LETTER NA*/
+ RTUNI_ALPHA, /* U+00a915: KAYAH LI LETTER PA*/
+ RTUNI_ALPHA, /* U+00a916: KAYAH LI LETTER PHA*/
+ RTUNI_ALPHA, /* U+00a917: KAYAH LI LETTER MA*/
+ RTUNI_ALPHA, /* U+00a918: KAYAH LI LETTER DA*/
+ RTUNI_ALPHA, /* U+00a919: KAYAH LI LETTER BA*/
+ RTUNI_ALPHA, /* U+00a91a: KAYAH LI LETTER RA*/
+ RTUNI_ALPHA, /* U+00a91b: KAYAH LI LETTER YA*/
+ RTUNI_ALPHA, /* U+00a91c: KAYAH LI LETTER LA*/
+ RTUNI_ALPHA, /* U+00a91d: KAYAH LI LETTER WA*/
+ RTUNI_ALPHA, /* U+00a91e: KAYAH LI LETTER THA*/
+ RTUNI_ALPHA, /* U+00a91f: KAYAH LI LETTER HA*/
+ RTUNI_ALPHA, /* U+00a920: KAYAH LI LETTER VA*/
+ RTUNI_ALPHA, /* U+00a921: KAYAH LI LETTER CA*/
+ RTUNI_ALPHA, /* U+00a922: KAYAH LI LETTER A*/
+ RTUNI_ALPHA, /* U+00a923: KAYAH LI LETTER OE*/
+ RTUNI_ALPHA, /* U+00a924: KAYAH LI LETTER I*/
+ RTUNI_ALPHA, /* U+00a925: KAYAH LI LETTER OO*/
+ RTUNI_ALPHA, /* U+00a926: KAYAH LI VOWEL UE*/
+ RTUNI_ALPHA, /* U+00a927: KAYAH LI VOWEL E*/
+ RTUNI_ALPHA, /* U+00a928: KAYAH LI VOWEL U*/
+ RTUNI_ALPHA, /* U+00a929: KAYAH LI VOWEL EE*/
+ RTUNI_ALPHA, /* U+00a92a: KAYAH LI VOWEL O*/
+ 0, /* U+00a92b: KAYAH LI TONE PLOPHU*/
+ 0, /* U+00a92c: KAYAH LI TONE CALYA*/
+ 0, /* U+00a92d: KAYAH LI TONE CALYA PLOPHU*/
+ 0, /* U+00a92e: KAYAH LI SIGN CWI*/
+ 0, /* U+00a92f: KAYAH LI SIGN SHYA*/
+ RTUNI_ALPHA, /* U+00a930: REJANG LETTER KA*/
+ RTUNI_ALPHA, /* U+00a931: REJANG LETTER GA*/
+ RTUNI_ALPHA, /* U+00a932: REJANG LETTER NGA*/
+ RTUNI_ALPHA, /* U+00a933: REJANG LETTER TA*/
+ RTUNI_ALPHA, /* U+00a934: REJANG LETTER DA*/
+ RTUNI_ALPHA, /* U+00a935: REJANG LETTER NA*/
+ RTUNI_ALPHA, /* U+00a936: REJANG LETTER PA*/
+ RTUNI_ALPHA, /* U+00a937: REJANG LETTER BA*/
+ RTUNI_ALPHA, /* U+00a938: REJANG LETTER MA*/
+ RTUNI_ALPHA, /* U+00a939: REJANG LETTER CA*/
+ RTUNI_ALPHA, /* U+00a93a: REJANG LETTER JA*/
+ RTUNI_ALPHA, /* U+00a93b: REJANG LETTER NYA*/
+ RTUNI_ALPHA, /* U+00a93c: REJANG LETTER SA*/
+ RTUNI_ALPHA, /* U+00a93d: REJANG LETTER RA*/
+ RTUNI_ALPHA, /* U+00a93e: REJANG LETTER LA*/
+ RTUNI_ALPHA, /* U+00a93f: REJANG LETTER YA*/
+ RTUNI_ALPHA, /* U+00a940: REJANG LETTER WA*/
+ RTUNI_ALPHA, /* U+00a941: REJANG LETTER HA*/
+ RTUNI_ALPHA, /* U+00a942: REJANG LETTER MBA*/
+ RTUNI_ALPHA, /* U+00a943: REJANG LETTER NGGA*/
+ RTUNI_ALPHA, /* U+00a944: REJANG LETTER NDA*/
+ RTUNI_ALPHA, /* U+00a945: REJANG LETTER NYJA*/
+ RTUNI_ALPHA, /* U+00a946: REJANG LETTER A*/
+ RTUNI_ALPHA, /* U+00a947: REJANG VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+00a948: REJANG VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+00a949: REJANG VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+00a94a: REJANG VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+00a94b: REJANG VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+00a94c: REJANG VOWEL SIGN AU*/
+ RTUNI_ALPHA, /* U+00a94d: REJANG VOWEL SIGN EU*/
+ RTUNI_ALPHA, /* U+00a94e: REJANG VOWEL SIGN EA*/
+ RTUNI_ALPHA, /* U+00a94f: REJANG CONSONANT SIGN NG*/
+ RTUNI_ALPHA, /* U+00a950: REJANG CONSONANT SIGN N*/
+ RTUNI_ALPHA, /* U+00a951: REJANG CONSONANT SIGN R*/
+ RTUNI_ALPHA, /* U+00a952: REJANG CONSONANT SIGN H*/
+ 0, /* U+00a953: REJANG VIRAMA*/
+ 0, /* U+00a954: */
+ 0, /* U+00a955: */
+ 0, /* U+00a956: */
+ 0, /* U+00a957: */
+ 0, /* U+00a958: */
+ 0, /* U+00a959: */
+ 0, /* U+00a95a: */
+ 0, /* U+00a95b: */
+ 0, /* U+00a95c: */
+ 0, /* U+00a95d: */
+ 0, /* U+00a95e: */
+ 0, /* U+00a95f: REJANG SECTION MARK*/
+ RTUNI_ALPHA, /* U+00a960: HANGUL CHOSEONG TIKEUT-MIEUM*/
+ RTUNI_ALPHA, /* U+00a961: HANGUL CHOSEONG TIKEUT-PIEUP*/
+ RTUNI_ALPHA, /* U+00a962: HANGUL CHOSEONG TIKEUT-SIOS*/
+ RTUNI_ALPHA, /* U+00a963: HANGUL CHOSEONG TIKEUT-CIEUC*/
+ RTUNI_ALPHA, /* U+00a964: HANGUL CHOSEONG RIEUL-KIYEOK*/
+ RTUNI_ALPHA, /* U+00a965: HANGUL CHOSEONG RIEUL-SSANGKIYEOK*/
+ RTUNI_ALPHA, /* U+00a966: HANGUL CHOSEONG RIEUL-TIKEUT*/
+ RTUNI_ALPHA, /* U+00a967: HANGUL CHOSEONG RIEUL-SSANGTIKEUT*/
+ RTUNI_ALPHA, /* U+00a968: HANGUL CHOSEONG RIEUL-MIEUM*/
+ RTUNI_ALPHA, /* U+00a969: HANGUL CHOSEONG RIEUL-PIEUP*/
+ RTUNI_ALPHA, /* U+00a96a: HANGUL CHOSEONG RIEUL-SSANGPIEUP*/
+ RTUNI_ALPHA, /* U+00a96b: HANGUL CHOSEONG RIEUL-KAPYEOUNPIEUP*/
+ RTUNI_ALPHA, /* U+00a96c: HANGUL CHOSEONG RIEUL-SIOS*/
+ RTUNI_ALPHA, /* U+00a96d: HANGUL CHOSEONG RIEUL-CIEUC*/
+ RTUNI_ALPHA, /* U+00a96e: HANGUL CHOSEONG RIEUL-KHIEUKH*/
+ RTUNI_ALPHA, /* U+00a96f: HANGUL CHOSEONG MIEUM-KIYEOK*/
+ RTUNI_ALPHA, /* U+00a970: HANGUL CHOSEONG MIEUM-TIKEUT*/
+ RTUNI_ALPHA, /* U+00a971: HANGUL CHOSEONG MIEUM-SIOS*/
+ RTUNI_ALPHA, /* U+00a972: HANGUL CHOSEONG PIEUP-SIOS-THIEUTH*/
+ RTUNI_ALPHA, /* U+00a973: HANGUL CHOSEONG PIEUP-KHIEUKH*/
+ RTUNI_ALPHA, /* U+00a974: HANGUL CHOSEONG PIEUP-HIEUH*/
+ RTUNI_ALPHA, /* U+00a975: HANGUL CHOSEONG SSANGSIOS-PIEUP*/
+ RTUNI_ALPHA, /* U+00a976: HANGUL CHOSEONG IEUNG-RIEUL*/
+ RTUNI_ALPHA, /* U+00a977: HANGUL CHOSEONG IEUNG-HIEUH*/
+ RTUNI_ALPHA, /* U+00a978: HANGUL CHOSEONG SSANGCIEUC-HIEUH*/
+ RTUNI_ALPHA, /* U+00a979: HANGUL CHOSEONG SSANGTHIEUTH*/
+ RTUNI_ALPHA, /* U+00a97a: HANGUL CHOSEONG PHIEUPH-HIEUH*/
+ RTUNI_ALPHA, /* U+00a97b: HANGUL CHOSEONG HIEUH-SIOS*/
+ RTUNI_ALPHA, /* U+00a97c: HANGUL CHOSEONG SSANGYEORINHIEUH*/
+ 0, /* U+00a97d: */
+ 0, /* U+00a97e: */
+ 0, /* U+00a97f: */
+ RTUNI_ALPHA, /* U+00a980: JAVANESE SIGN PANYANGGA*/
+ RTUNI_ALPHA, /* U+00a981: JAVANESE SIGN CECAK*/
+ RTUNI_ALPHA, /* U+00a982: JAVANESE SIGN LAYAR*/
+ RTUNI_ALPHA, /* U+00a983: JAVANESE SIGN WIGNYAN*/
+ RTUNI_ALPHA, /* U+00a984: JAVANESE LETTER A*/
+ RTUNI_ALPHA, /* U+00a985: JAVANESE LETTER I KAWI*/
+ RTUNI_ALPHA, /* U+00a986: JAVANESE LETTER I*/
+ RTUNI_ALPHA, /* U+00a987: JAVANESE LETTER II*/
+ RTUNI_ALPHA, /* U+00a988: JAVANESE LETTER U*/
+ RTUNI_ALPHA, /* U+00a989: JAVANESE LETTER PA CEREK*/
+ RTUNI_ALPHA, /* U+00a98a: JAVANESE LETTER NGA LELET*/
+ RTUNI_ALPHA, /* U+00a98b: JAVANESE LETTER NGA LELET RASWADI*/
+ RTUNI_ALPHA, /* U+00a98c: JAVANESE LETTER E*/
+ RTUNI_ALPHA, /* U+00a98d: JAVANESE LETTER AI*/
+ RTUNI_ALPHA, /* U+00a98e: JAVANESE LETTER O*/
+ RTUNI_ALPHA, /* U+00a98f: JAVANESE LETTER KA*/
+ RTUNI_ALPHA, /* U+00a990: JAVANESE LETTER KA SASAK*/
+ RTUNI_ALPHA, /* U+00a991: JAVANESE LETTER KA MURDA*/
+ RTUNI_ALPHA, /* U+00a992: JAVANESE LETTER GA*/
+ RTUNI_ALPHA, /* U+00a993: JAVANESE LETTER GA MURDA*/
+ RTUNI_ALPHA, /* U+00a994: JAVANESE LETTER NGA*/
+ RTUNI_ALPHA, /* U+00a995: JAVANESE LETTER CA*/
+ RTUNI_ALPHA, /* U+00a996: JAVANESE LETTER CA MURDA*/
+ RTUNI_ALPHA, /* U+00a997: JAVANESE LETTER JA*/
+ RTUNI_ALPHA, /* U+00a998: JAVANESE LETTER NYA MURDA*/
+ RTUNI_ALPHA, /* U+00a999: JAVANESE LETTER JA MAHAPRANA*/
+ RTUNI_ALPHA, /* U+00a99a: JAVANESE LETTER NYA*/
+ RTUNI_ALPHA, /* U+00a99b: JAVANESE LETTER TTA*/
+ RTUNI_ALPHA, /* U+00a99c: JAVANESE LETTER TTA MAHAPRANA*/
+ RTUNI_ALPHA, /* U+00a99d: JAVANESE LETTER DDA*/
+ RTUNI_ALPHA, /* U+00a99e: JAVANESE LETTER DDA MAHAPRANA*/
+ RTUNI_ALPHA, /* U+00a99f: JAVANESE LETTER NA MURDA*/
+ RTUNI_ALPHA, /* U+00a9a0: JAVANESE LETTER TA*/
+ RTUNI_ALPHA, /* U+00a9a1: JAVANESE LETTER TA MURDA*/
+ RTUNI_ALPHA, /* U+00a9a2: JAVANESE LETTER DA*/
+ RTUNI_ALPHA, /* U+00a9a3: JAVANESE LETTER DA MAHAPRANA*/
+ RTUNI_ALPHA, /* U+00a9a4: JAVANESE LETTER NA*/
+ RTUNI_ALPHA, /* U+00a9a5: JAVANESE LETTER PA*/
+ RTUNI_ALPHA, /* U+00a9a6: JAVANESE LETTER PA MURDA*/
+ RTUNI_ALPHA, /* U+00a9a7: JAVANESE LETTER BA*/
+ RTUNI_ALPHA, /* U+00a9a8: JAVANESE LETTER BA MURDA*/
+ RTUNI_ALPHA, /* U+00a9a9: JAVANESE LETTER MA*/
+ RTUNI_ALPHA, /* U+00a9aa: JAVANESE LETTER YA*/
+ RTUNI_ALPHA, /* U+00a9ab: JAVANESE LETTER RA*/
+ RTUNI_ALPHA, /* U+00a9ac: JAVANESE LETTER RA AGUNG*/
+ RTUNI_ALPHA, /* U+00a9ad: JAVANESE LETTER LA*/
+ RTUNI_ALPHA, /* U+00a9ae: JAVANESE LETTER WA*/
+ RTUNI_ALPHA, /* U+00a9af: JAVANESE LETTER SA MURDA*/
+ RTUNI_ALPHA, /* U+00a9b0: JAVANESE LETTER SA MAHAPRANA*/
+ RTUNI_ALPHA, /* U+00a9b1: JAVANESE LETTER SA*/
+ RTUNI_ALPHA, /* U+00a9b2: JAVANESE LETTER HA*/
+ 0, /* U+00a9b3: JAVANESE SIGN CECAK TELU*/
+ RTUNI_ALPHA, /* U+00a9b4: JAVANESE VOWEL SIGN TARUNG*/
+ RTUNI_ALPHA, /* U+00a9b5: JAVANESE VOWEL SIGN TOLONG*/
+ RTUNI_ALPHA, /* U+00a9b6: JAVANESE VOWEL SIGN WULU*/
+ RTUNI_ALPHA, /* U+00a9b7: JAVANESE VOWEL SIGN WULU MELIK*/
+ RTUNI_ALPHA, /* U+00a9b8: JAVANESE VOWEL SIGN SUKU*/
+ RTUNI_ALPHA, /* U+00a9b9: JAVANESE VOWEL SIGN SUKU MENDUT*/
+ RTUNI_ALPHA, /* U+00a9ba: JAVANESE VOWEL SIGN TALING*/
+ RTUNI_ALPHA, /* U+00a9bb: JAVANESE VOWEL SIGN DIRGA MURE*/
+ RTUNI_ALPHA, /* U+00a9bc: JAVANESE VOWEL SIGN PEPET*/
+ RTUNI_ALPHA, /* U+00a9bd: JAVANESE CONSONANT SIGN KERET*/
+ RTUNI_ALPHA, /* U+00a9be: JAVANESE CONSONANT SIGN PENGKAL*/
+ RTUNI_ALPHA, /* U+00a9bf: JAVANESE CONSONANT SIGN CAKRA*/
+ 0, /* U+00a9c0: JAVANESE PANGKON*/
+ 0, /* U+00a9c1: JAVANESE LEFT RERENGGAN*/
+ 0, /* U+00a9c2: JAVANESE RIGHT RERENGGAN*/
+ 0, /* U+00a9c3: JAVANESE PADA ANDAP*/
+ 0, /* U+00a9c4: JAVANESE PADA MADYA*/
+ 0, /* U+00a9c5: JAVANESE PADA LUHUR*/
+ 0, /* U+00a9c6: JAVANESE PADA WINDU*/
+ 0, /* U+00a9c7: JAVANESE PADA PANGKAT*/
+ 0, /* U+00a9c8: JAVANESE PADA LINGSA*/
+ 0, /* U+00a9c9: JAVANESE PADA LUNGSI*/
+ 0, /* U+00a9ca: JAVANESE PADA ADEG*/
+ 0, /* U+00a9cb: JAVANESE PADA ADEG ADEG*/
+ 0, /* U+00a9cc: JAVANESE PADA PISELEH*/
+ 0, /* U+00a9cd: JAVANESE TURNED PADA PISELEH*/
+ 0, /* U+00a9ce: */
+ RTUNI_ALPHA, /* U+00a9cf: JAVANESE PANGRANGKEP*/
+ RTUNI_DDIGIT, /* U+00a9d0: JAVANESE DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+00a9d1: JAVANESE DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+00a9d2: JAVANESE DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+00a9d3: JAVANESE DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+00a9d4: JAVANESE DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+00a9d5: JAVANESE DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+00a9d6: JAVANESE DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+00a9d7: JAVANESE DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+00a9d8: JAVANESE DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+00a9d9: JAVANESE DIGIT NINE*/
+ 0, /* U+00a9da: */
+ 0, /* U+00a9db: */
+ 0, /* U+00a9dc: */
+ 0, /* U+00a9dd: */
+ 0, /* U+00a9de: JAVANESE PADA TIRTA TUMETES*/
+ 0, /* U+00a9df: JAVANESE PADA ISEN-ISEN*/
+ 0, /* U+00a9e0: */
+ 0, /* U+00a9e1: */
+ 0, /* U+00a9e2: */
+ 0, /* U+00a9e3: */
+ 0, /* U+00a9e4: */
+ 0, /* U+00a9e5: */
+ 0, /* U+00a9e6: */
+ 0, /* U+00a9e7: */
+ 0, /* U+00a9e8: */
+ 0, /* U+00a9e9: */
+ 0, /* U+00a9ea: */
+ 0, /* U+00a9eb: */
+ 0, /* U+00a9ec: */
+ 0, /* U+00a9ed: */
+ 0, /* U+00a9ee: */
+ 0, /* U+00a9ef: */
+ 0, /* U+00a9f0: */
+ 0, /* U+00a9f1: */
+ 0, /* U+00a9f2: */
+ 0, /* U+00a9f3: */
+ 0, /* U+00a9f4: */
+ 0, /* U+00a9f5: */
+ 0, /* U+00a9f6: */
+ 0, /* U+00a9f7: */
+ 0, /* U+00a9f8: */
+ 0, /* U+00a9f9: */
+ 0, /* U+00a9fa: */
+ 0, /* U+00a9fb: */
+ 0, /* U+00a9fc: */
+ 0, /* U+00a9fd: */
+ 0, /* U+00a9fe: */
+ 0, /* U+00a9ff: */
+ RTUNI_ALPHA, /* U+00aa00: CHAM LETTER A*/
+ RTUNI_ALPHA, /* U+00aa01: CHAM LETTER I*/
+ RTUNI_ALPHA, /* U+00aa02: CHAM LETTER U*/
+ RTUNI_ALPHA, /* U+00aa03: CHAM LETTER E*/
+ RTUNI_ALPHA, /* U+00aa04: CHAM LETTER AI*/
+ RTUNI_ALPHA, /* U+00aa05: CHAM LETTER O*/
+ RTUNI_ALPHA, /* U+00aa06: CHAM LETTER KA*/
+ RTUNI_ALPHA, /* U+00aa07: CHAM LETTER KHA*/
+ RTUNI_ALPHA, /* U+00aa08: CHAM LETTER GA*/
+ RTUNI_ALPHA, /* U+00aa09: CHAM LETTER GHA*/
+ RTUNI_ALPHA, /* U+00aa0a: CHAM LETTER NGUE*/
+ RTUNI_ALPHA, /* U+00aa0b: CHAM LETTER NGA*/
+ RTUNI_ALPHA, /* U+00aa0c: CHAM LETTER CHA*/
+ RTUNI_ALPHA, /* U+00aa0d: CHAM LETTER CHHA*/
+ RTUNI_ALPHA, /* U+00aa0e: CHAM LETTER JA*/
+ RTUNI_ALPHA, /* U+00aa0f: CHAM LETTER JHA*/
+ RTUNI_ALPHA, /* U+00aa10: CHAM LETTER NHUE*/
+ RTUNI_ALPHA, /* U+00aa11: CHAM LETTER NHA*/
+ RTUNI_ALPHA, /* U+00aa12: CHAM LETTER NHJA*/
+ RTUNI_ALPHA, /* U+00aa13: CHAM LETTER TA*/
+ RTUNI_ALPHA, /* U+00aa14: CHAM LETTER THA*/
+ RTUNI_ALPHA, /* U+00aa15: CHAM LETTER DA*/
+ RTUNI_ALPHA, /* U+00aa16: CHAM LETTER DHA*/
+ RTUNI_ALPHA, /* U+00aa17: CHAM LETTER NUE*/
+ RTUNI_ALPHA, /* U+00aa18: CHAM LETTER NA*/
+ RTUNI_ALPHA, /* U+00aa19: CHAM LETTER DDA*/
+ RTUNI_ALPHA, /* U+00aa1a: CHAM LETTER PA*/
+ RTUNI_ALPHA, /* U+00aa1b: CHAM LETTER PPA*/
+ RTUNI_ALPHA, /* U+00aa1c: CHAM LETTER PHA*/
+ RTUNI_ALPHA, /* U+00aa1d: CHAM LETTER BA*/
+ RTUNI_ALPHA, /* U+00aa1e: CHAM LETTER BHA*/
+ RTUNI_ALPHA, /* U+00aa1f: CHAM LETTER MUE*/
+ RTUNI_ALPHA, /* U+00aa20: CHAM LETTER MA*/
+ RTUNI_ALPHA, /* U+00aa21: CHAM LETTER BBA*/
+ RTUNI_ALPHA, /* U+00aa22: CHAM LETTER YA*/
+ RTUNI_ALPHA, /* U+00aa23: CHAM LETTER RA*/
+ RTUNI_ALPHA, /* U+00aa24: CHAM LETTER LA*/
+ RTUNI_ALPHA, /* U+00aa25: CHAM LETTER VA*/
+ RTUNI_ALPHA, /* U+00aa26: CHAM LETTER SSA*/
+ RTUNI_ALPHA, /* U+00aa27: CHAM LETTER SA*/
+ RTUNI_ALPHA, /* U+00aa28: CHAM LETTER HA*/
+ RTUNI_ALPHA, /* U+00aa29: CHAM VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+00aa2a: CHAM VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+00aa2b: CHAM VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+00aa2c: CHAM VOWEL SIGN EI*/
+ RTUNI_ALPHA, /* U+00aa2d: CHAM VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+00aa2e: CHAM VOWEL SIGN OE*/
+ RTUNI_ALPHA, /* U+00aa2f: CHAM VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+00aa30: CHAM VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+00aa31: CHAM VOWEL SIGN AU*/
+ RTUNI_ALPHA, /* U+00aa32: CHAM VOWEL SIGN UE*/
+ RTUNI_ALPHA, /* U+00aa33: CHAM CONSONANT SIGN YA*/
+ RTUNI_ALPHA, /* U+00aa34: CHAM CONSONANT SIGN RA*/
+ RTUNI_ALPHA, /* U+00aa35: CHAM CONSONANT SIGN LA*/
+ RTUNI_ALPHA, /* U+00aa36: CHAM CONSONANT SIGN WA*/
+ 0, /* U+00aa37: */
+ 0, /* U+00aa38: */
+ 0, /* U+00aa39: */
+ 0, /* U+00aa3a: */
+ 0, /* U+00aa3b: */
+ 0, /* U+00aa3c: */
+ 0, /* U+00aa3d: */
+ 0, /* U+00aa3e: */
+ 0, /* U+00aa3f: */
+ RTUNI_ALPHA, /* U+00aa40: CHAM LETTER FINAL K*/
+ RTUNI_ALPHA, /* U+00aa41: CHAM LETTER FINAL G*/
+ RTUNI_ALPHA, /* U+00aa42: CHAM LETTER FINAL NG*/
+ RTUNI_ALPHA, /* U+00aa43: CHAM CONSONANT SIGN FINAL NG*/
+ RTUNI_ALPHA, /* U+00aa44: CHAM LETTER FINAL CH*/
+ RTUNI_ALPHA, /* U+00aa45: CHAM LETTER FINAL T*/
+ RTUNI_ALPHA, /* U+00aa46: CHAM LETTER FINAL N*/
+ RTUNI_ALPHA, /* U+00aa47: CHAM LETTER FINAL P*/
+ RTUNI_ALPHA, /* U+00aa48: CHAM LETTER FINAL Y*/
+ RTUNI_ALPHA, /* U+00aa49: CHAM LETTER FINAL R*/
+ RTUNI_ALPHA, /* U+00aa4a: CHAM LETTER FINAL L*/
+ RTUNI_ALPHA, /* U+00aa4b: CHAM LETTER FINAL SS*/
+ RTUNI_ALPHA, /* U+00aa4c: CHAM CONSONANT SIGN FINAL M*/
+ RTUNI_ALPHA, /* U+00aa4d: CHAM CONSONANT SIGN FINAL H*/
+ 0, /* U+00aa4e: */
+ 0, /* U+00aa4f: */
+ RTUNI_DDIGIT, /* U+00aa50: CHAM DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+00aa51: CHAM DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+00aa52: CHAM DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+00aa53: CHAM DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+00aa54: CHAM DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+00aa55: CHAM DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+00aa56: CHAM DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+00aa57: CHAM DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+00aa58: CHAM DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+00aa59: CHAM DIGIT NINE*/
+ 0, /* U+00aa5a: */
+ 0, /* U+00aa5b: */
+ 0, /* U+00aa5c: CHAM PUNCTUATION SPIRAL*/
+ 0, /* U+00aa5d: CHAM PUNCTUATION DANDA*/
+ 0, /* U+00aa5e: CHAM PUNCTUATION DOUBLE DANDA*/
+ 0, /* U+00aa5f: CHAM PUNCTUATION TRIPLE DANDA*/
+ RTUNI_ALPHA, /* U+00aa60: MYANMAR LETTER KHAMTI GA*/
+ RTUNI_ALPHA, /* U+00aa61: MYANMAR LETTER KHAMTI CA*/
+ RTUNI_ALPHA, /* U+00aa62: MYANMAR LETTER KHAMTI CHA*/
+ RTUNI_ALPHA, /* U+00aa63: MYANMAR LETTER KHAMTI JA*/
+ RTUNI_ALPHA, /* U+00aa64: MYANMAR LETTER KHAMTI JHA*/
+ RTUNI_ALPHA, /* U+00aa65: MYANMAR LETTER KHAMTI NYA*/
+ RTUNI_ALPHA, /* U+00aa66: MYANMAR LETTER KHAMTI TTA*/
+ RTUNI_ALPHA, /* U+00aa67: MYANMAR LETTER KHAMTI TTHA*/
+ RTUNI_ALPHA, /* U+00aa68: MYANMAR LETTER KHAMTI DDA*/
+ RTUNI_ALPHA, /* U+00aa69: MYANMAR LETTER KHAMTI DDHA*/
+ RTUNI_ALPHA, /* U+00aa6a: MYANMAR LETTER KHAMTI DHA*/
+ RTUNI_ALPHA, /* U+00aa6b: MYANMAR LETTER KHAMTI NA*/
+ RTUNI_ALPHA, /* U+00aa6c: MYANMAR LETTER KHAMTI SA*/
+ RTUNI_ALPHA, /* U+00aa6d: MYANMAR LETTER KHAMTI HA*/
+ RTUNI_ALPHA, /* U+00aa6e: MYANMAR LETTER KHAMTI HHA*/
+ RTUNI_ALPHA, /* U+00aa6f: MYANMAR LETTER KHAMTI FA*/
+ RTUNI_ALPHA, /* U+00aa70: MYANMAR MODIFIER LETTER KHAMTI REDUPLICATION*/
+ RTUNI_ALPHA, /* U+00aa71: MYANMAR LETTER KHAMTI XA*/
+ RTUNI_ALPHA, /* U+00aa72: MYANMAR LETTER KHAMTI ZA*/
+ RTUNI_ALPHA, /* U+00aa73: MYANMAR LETTER KHAMTI RA*/
+ RTUNI_ALPHA, /* U+00aa74: MYANMAR LOGOGRAM KHAMTI OAY*/
+ RTUNI_ALPHA, /* U+00aa75: MYANMAR LOGOGRAM KHAMTI QN*/
+ RTUNI_ALPHA, /* U+00aa76: MYANMAR LOGOGRAM KHAMTI HM*/
+ 0, /* U+00aa77: MYANMAR SYMBOL AITON EXCLAMATION*/
+ 0, /* U+00aa78: MYANMAR SYMBOL AITON ONE*/
+ 0, /* U+00aa79: MYANMAR SYMBOL AITON TWO*/
+ RTUNI_ALPHA, /* U+00aa7a: MYANMAR LETTER AITON RA*/
+ 0, /* U+00aa7b: MYANMAR SIGN PAO KAREN TONE*/
+ 0, /* U+00aa7c: */
+ 0, /* U+00aa7d: */
+ 0, /* U+00aa7e: */
+ 0, /* U+00aa7f: */
+ RTUNI_ALPHA, /* U+00aa80: TAI VIET LETTER LOW KO*/
+ RTUNI_ALPHA, /* U+00aa81: TAI VIET LETTER HIGH KO*/
+ RTUNI_ALPHA, /* U+00aa82: TAI VIET LETTER LOW KHO*/
+ RTUNI_ALPHA, /* U+00aa83: TAI VIET LETTER HIGH KHO*/
+ RTUNI_ALPHA, /* U+00aa84: TAI VIET LETTER LOW KHHO*/
+ RTUNI_ALPHA, /* U+00aa85: TAI VIET LETTER HIGH KHHO*/
+ RTUNI_ALPHA, /* U+00aa86: TAI VIET LETTER LOW GO*/
+ RTUNI_ALPHA, /* U+00aa87: TAI VIET LETTER HIGH GO*/
+ RTUNI_ALPHA, /* U+00aa88: TAI VIET LETTER LOW NGO*/
+ RTUNI_ALPHA, /* U+00aa89: TAI VIET LETTER HIGH NGO*/
+ RTUNI_ALPHA, /* U+00aa8a: TAI VIET LETTER LOW CO*/
+ RTUNI_ALPHA, /* U+00aa8b: TAI VIET LETTER HIGH CO*/
+ RTUNI_ALPHA, /* U+00aa8c: TAI VIET LETTER LOW CHO*/
+ RTUNI_ALPHA, /* U+00aa8d: TAI VIET LETTER HIGH CHO*/
+ RTUNI_ALPHA, /* U+00aa8e: TAI VIET LETTER LOW SO*/
+ RTUNI_ALPHA, /* U+00aa8f: TAI VIET LETTER HIGH SO*/
+ RTUNI_ALPHA, /* U+00aa90: TAI VIET LETTER LOW NYO*/
+ RTUNI_ALPHA, /* U+00aa91: TAI VIET LETTER HIGH NYO*/
+ RTUNI_ALPHA, /* U+00aa92: TAI VIET LETTER LOW DO*/
+ RTUNI_ALPHA, /* U+00aa93: TAI VIET LETTER HIGH DO*/
+ RTUNI_ALPHA, /* U+00aa94: TAI VIET LETTER LOW TO*/
+ RTUNI_ALPHA, /* U+00aa95: TAI VIET LETTER HIGH TO*/
+ RTUNI_ALPHA, /* U+00aa96: TAI VIET LETTER LOW THO*/
+ RTUNI_ALPHA, /* U+00aa97: TAI VIET LETTER HIGH THO*/
+ RTUNI_ALPHA, /* U+00aa98: TAI VIET LETTER LOW NO*/
+ RTUNI_ALPHA, /* U+00aa99: TAI VIET LETTER HIGH NO*/
+ RTUNI_ALPHA, /* U+00aa9a: TAI VIET LETTER LOW BO*/
+ RTUNI_ALPHA, /* U+00aa9b: TAI VIET LETTER HIGH BO*/
+ RTUNI_ALPHA, /* U+00aa9c: TAI VIET LETTER LOW PO*/
+ RTUNI_ALPHA, /* U+00aa9d: TAI VIET LETTER HIGH PO*/
+ RTUNI_ALPHA, /* U+00aa9e: TAI VIET LETTER LOW PHO*/
+ RTUNI_ALPHA, /* U+00aa9f: TAI VIET LETTER HIGH PHO*/
+ RTUNI_ALPHA, /* U+00aaa0: TAI VIET LETTER LOW FO*/
+ RTUNI_ALPHA, /* U+00aaa1: TAI VIET LETTER HIGH FO*/
+ RTUNI_ALPHA, /* U+00aaa2: TAI VIET LETTER LOW MO*/
+ RTUNI_ALPHA, /* U+00aaa3: TAI VIET LETTER HIGH MO*/
+ RTUNI_ALPHA, /* U+00aaa4: TAI VIET LETTER LOW YO*/
+ RTUNI_ALPHA, /* U+00aaa5: TAI VIET LETTER HIGH YO*/
+ RTUNI_ALPHA, /* U+00aaa6: TAI VIET LETTER LOW RO*/
+ RTUNI_ALPHA, /* U+00aaa7: TAI VIET LETTER HIGH RO*/
+ RTUNI_ALPHA, /* U+00aaa8: TAI VIET LETTER LOW LO*/
+ RTUNI_ALPHA, /* U+00aaa9: TAI VIET LETTER HIGH LO*/
+ RTUNI_ALPHA, /* U+00aaaa: TAI VIET LETTER LOW VO*/
+ RTUNI_ALPHA, /* U+00aaab: TAI VIET LETTER HIGH VO*/
+ RTUNI_ALPHA, /* U+00aaac: TAI VIET LETTER LOW HO*/
+ RTUNI_ALPHA, /* U+00aaad: TAI VIET LETTER HIGH HO*/
+ RTUNI_ALPHA, /* U+00aaae: TAI VIET LETTER LOW O*/
+ RTUNI_ALPHA, /* U+00aaaf: TAI VIET LETTER HIGH O*/
+ RTUNI_ALPHA, /* U+00aab0: TAI VIET MAI KANG*/
+ RTUNI_ALPHA, /* U+00aab1: TAI VIET VOWEL AA*/
+ RTUNI_ALPHA, /* U+00aab2: TAI VIET VOWEL I*/
+ RTUNI_ALPHA, /* U+00aab3: TAI VIET VOWEL UE*/
+ RTUNI_ALPHA, /* U+00aab4: TAI VIET VOWEL U*/
+ RTUNI_ALPHA, /* U+00aab5: TAI VIET VOWEL E*/
+ RTUNI_ALPHA, /* U+00aab6: TAI VIET VOWEL O*/
+ RTUNI_ALPHA, /* U+00aab7: TAI VIET MAI KHIT*/
+ RTUNI_ALPHA, /* U+00aab8: TAI VIET VOWEL IA*/
+ RTUNI_ALPHA, /* U+00aab9: TAI VIET VOWEL UEA*/
+ RTUNI_ALPHA, /* U+00aaba: TAI VIET VOWEL UA*/
+ RTUNI_ALPHA, /* U+00aabb: TAI VIET VOWEL AUE*/
+ RTUNI_ALPHA, /* U+00aabc: TAI VIET VOWEL AY*/
+ RTUNI_ALPHA, /* U+00aabd: TAI VIET VOWEL AN*/
+ RTUNI_ALPHA, /* U+00aabe: TAI VIET VOWEL AM*/
+ 0, /* U+00aabf: TAI VIET TONE MAI EK*/
+ RTUNI_ALPHA, /* U+00aac0: TAI VIET TONE MAI NUENG*/
+ 0, /* U+00aac1: TAI VIET TONE MAI THO*/
+ RTUNI_ALPHA, /* U+00aac2: TAI VIET TONE MAI SONG*/
+ 0, /* U+00aac3: */
+ 0, /* U+00aac4: */
+ 0, /* U+00aac5: */
+ 0, /* U+00aac6: */
+ 0, /* U+00aac7: */
+ 0, /* U+00aac8: */
+ 0, /* U+00aac9: */
+ 0, /* U+00aaca: */
+ 0, /* U+00aacb: */
+ 0, /* U+00aacc: */
+ 0, /* U+00aacd: */
+ 0, /* U+00aace: */
+ 0, /* U+00aacf: */
+ 0, /* U+00aad0: */
+ 0, /* U+00aad1: */
+ 0, /* U+00aad2: */
+ 0, /* U+00aad3: */
+ 0, /* U+00aad4: */
+ 0, /* U+00aad5: */
+ 0, /* U+00aad6: */
+ 0, /* U+00aad7: */
+ 0, /* U+00aad8: */
+ 0, /* U+00aad9: */
+ 0, /* U+00aada: */
+ RTUNI_ALPHA, /* U+00aadb: TAI VIET SYMBOL KON*/
+ RTUNI_ALPHA, /* U+00aadc: TAI VIET SYMBOL NUENG*/
+ RTUNI_ALPHA, /* U+00aadd: TAI VIET SYMBOL SAM*/
+ 0, /* U+00aade: TAI VIET SYMBOL HO HOI*/
+ 0, /* U+00aadf: TAI VIET SYMBOL KOI KOI*/
+ RTUNI_ALPHA, /* U+00aae0: MEETEI MAYEK LETTER E*/
+ RTUNI_ALPHA, /* U+00aae1: MEETEI MAYEK LETTER O*/
+ RTUNI_ALPHA, /* U+00aae2: MEETEI MAYEK LETTER CHA*/
+ RTUNI_ALPHA, /* U+00aae3: MEETEI MAYEK LETTER NYA*/
+ RTUNI_ALPHA, /* U+00aae4: MEETEI MAYEK LETTER TTA*/
+ RTUNI_ALPHA, /* U+00aae5: MEETEI MAYEK LETTER TTHA*/
+ RTUNI_ALPHA, /* U+00aae6: MEETEI MAYEK LETTER DDA*/
+ RTUNI_ALPHA, /* U+00aae7: MEETEI MAYEK LETTER DDHA*/
+ RTUNI_ALPHA, /* U+00aae8: MEETEI MAYEK LETTER NNA*/
+ RTUNI_ALPHA, /* U+00aae9: MEETEI MAYEK LETTER SHA*/
+ RTUNI_ALPHA, /* U+00aaea: MEETEI MAYEK LETTER SSA*/
+ RTUNI_ALPHA, /* U+00aaeb: MEETEI MAYEK VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+00aaec: MEETEI MAYEK VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+00aaed: MEETEI MAYEK VOWEL SIGN AAI*/
+ RTUNI_ALPHA, /* U+00aaee: MEETEI MAYEK VOWEL SIGN AU*/
+ RTUNI_ALPHA, /* U+00aaef: MEETEI MAYEK VOWEL SIGN AAU*/
+ 0, /* U+00aaf0: MEETEI MAYEK CHEIKHAN*/
+ 0, /* U+00aaf1: MEETEI MAYEK AHANG KHUDAM*/
+ RTUNI_ALPHA, /* U+00aaf2: MEETEI MAYEK ANJI*/
+ RTUNI_ALPHA, /* U+00aaf3: MEETEI MAYEK SYLLABLE REPETITION MARK*/
+ RTUNI_ALPHA, /* U+00aaf4: MEETEI MAYEK WORD REPETITION MARK*/
+ RTUNI_ALPHA, /* U+00aaf5: MEETEI MAYEK VOWEL SIGN VISARGA*/
+ 0, /* U+00aaf6: MEETEI MAYEK VIRAMA*/
+ 0, /* U+00aaf7: */
+ 0, /* U+00aaf8: */
+ 0, /* U+00aaf9: */
+ 0, /* U+00aafa: */
+ 0, /* U+00aafb: */
+ 0, /* U+00aafc: */
+ 0, /* U+00aafd: */
+ 0, /* U+00aafe: */
+ 0, /* U+00aaff: */
+ 0, /* U+00ab00: */
+ RTUNI_ALPHA, /* U+00ab01: ETHIOPIC SYLLABLE TTHU*/
+ RTUNI_ALPHA, /* U+00ab02: ETHIOPIC SYLLABLE TTHI*/
+ RTUNI_ALPHA, /* U+00ab03: ETHIOPIC SYLLABLE TTHAA*/
+ RTUNI_ALPHA, /* U+00ab04: ETHIOPIC SYLLABLE TTHEE*/
+ RTUNI_ALPHA, /* U+00ab05: ETHIOPIC SYLLABLE TTHE*/
+ RTUNI_ALPHA, /* U+00ab06: ETHIOPIC SYLLABLE TTHO*/
+ 0, /* U+00ab07: */
+ 0, /* U+00ab08: */
+ RTUNI_ALPHA, /* U+00ab09: ETHIOPIC SYLLABLE DDHU*/
+ RTUNI_ALPHA, /* U+00ab0a: ETHIOPIC SYLLABLE DDHI*/
+ RTUNI_ALPHA, /* U+00ab0b: ETHIOPIC SYLLABLE DDHAA*/
+ RTUNI_ALPHA, /* U+00ab0c: ETHIOPIC SYLLABLE DDHEE*/
+ RTUNI_ALPHA, /* U+00ab0d: ETHIOPIC SYLLABLE DDHE*/
+ RTUNI_ALPHA, /* U+00ab0e: ETHIOPIC SYLLABLE DDHO*/
+ 0, /* U+00ab0f: */
+ 0, /* U+00ab10: */
+ RTUNI_ALPHA, /* U+00ab11: ETHIOPIC SYLLABLE DZU*/
+ RTUNI_ALPHA, /* U+00ab12: ETHIOPIC SYLLABLE DZI*/
+ RTUNI_ALPHA, /* U+00ab13: ETHIOPIC SYLLABLE DZAA*/
+ RTUNI_ALPHA, /* U+00ab14: ETHIOPIC SYLLABLE DZEE*/
+ RTUNI_ALPHA, /* U+00ab15: ETHIOPIC SYLLABLE DZE*/
+ RTUNI_ALPHA, /* U+00ab16: ETHIOPIC SYLLABLE DZO*/
+ 0, /* U+00ab17: */
+ 0, /* U+00ab18: */
+ 0, /* U+00ab19: */
+ 0, /* U+00ab1a: */
+ 0, /* U+00ab1b: */
+ 0, /* U+00ab1c: */
+ 0, /* U+00ab1d: */
+ 0, /* U+00ab1e: */
+ 0, /* U+00ab1f: */
+ RTUNI_ALPHA, /* U+00ab20: ETHIOPIC SYLLABLE CCHHA*/
+ RTUNI_ALPHA, /* U+00ab21: ETHIOPIC SYLLABLE CCHHU*/
+ RTUNI_ALPHA, /* U+00ab22: ETHIOPIC SYLLABLE CCHHI*/
+ RTUNI_ALPHA, /* U+00ab23: ETHIOPIC SYLLABLE CCHHAA*/
+ RTUNI_ALPHA, /* U+00ab24: ETHIOPIC SYLLABLE CCHHEE*/
+ RTUNI_ALPHA, /* U+00ab25: ETHIOPIC SYLLABLE CCHHE*/
+ RTUNI_ALPHA, /* U+00ab26: ETHIOPIC SYLLABLE CCHHO*/
+ 0, /* U+00ab27: */
+ RTUNI_ALPHA, /* U+00ab28: ETHIOPIC SYLLABLE BBA*/
+ RTUNI_ALPHA, /* U+00ab29: ETHIOPIC SYLLABLE BBU*/
+ RTUNI_ALPHA, /* U+00ab2a: ETHIOPIC SYLLABLE BBI*/
+ RTUNI_ALPHA, /* U+00ab2b: ETHIOPIC SYLLABLE BBAA*/
+ RTUNI_ALPHA, /* U+00ab2c: ETHIOPIC SYLLABLE BBEE*/
+ RTUNI_ALPHA, /* U+00ab2d: ETHIOPIC SYLLABLE BBE*/
+ RTUNI_ALPHA, /* U+00ab2e: ETHIOPIC SYLLABLE BBO*/
+ 0, /* U+00ab2f: */
+ 0, /* U+00ab30: */
+ 0, /* U+00ab31: */
+ 0, /* U+00ab32: */
+ 0, /* U+00ab33: */
+ 0, /* U+00ab34: */
+ 0, /* U+00ab35: */
+ 0, /* U+00ab36: */
+ 0, /* U+00ab37: */
+ 0, /* U+00ab38: */
+ 0, /* U+00ab39: */
+ 0, /* U+00ab3a: */
+ 0, /* U+00ab3b: */
+ 0, /* U+00ab3c: */
+ 0, /* U+00ab3d: */
+ 0, /* U+00ab3e: */
+ 0, /* U+00ab3f: */
+ 0, /* U+00ab40: */
+ 0, /* U+00ab41: */
+ 0, /* U+00ab42: */
+ 0, /* U+00ab43: */
+ 0, /* U+00ab44: */
+ 0, /* U+00ab45: */
+ 0, /* U+00ab46: */
+ 0, /* U+00ab47: */
+ 0, /* U+00ab48: */
+ 0, /* U+00ab49: */
+ 0, /* U+00ab4a: */
+ 0, /* U+00ab4b: */
+ 0, /* U+00ab4c: */
+ 0, /* U+00ab4d: */
+ 0, /* U+00ab4e: */
+ 0, /* U+00ab4f: */
+ 0, /* U+00ab50: */
+ 0, /* U+00ab51: */
+ 0, /* U+00ab52: */
+ 0, /* U+00ab53: */
+ 0, /* U+00ab54: */
+ 0, /* U+00ab55: */
+ 0, /* U+00ab56: */
+ 0, /* U+00ab57: */
+ 0, /* U+00ab58: */
+ 0, /* U+00ab59: */
+ 0, /* U+00ab5a: */
+ 0, /* U+00ab5b: */
+ 0, /* U+00ab5c: */
+ 0, /* U+00ab5d: */
+ 0, /* U+00ab5e: */
+ 0, /* U+00ab5f: */
+ 0, /* U+00ab60: */
+ 0, /* U+00ab61: */
+ 0, /* U+00ab62: */
+ 0, /* U+00ab63: */
+ 0, /* U+00ab64: */
+ 0, /* U+00ab65: */
+ 0, /* U+00ab66: */
+ 0, /* U+00ab67: */
+ 0, /* U+00ab68: */
+ 0, /* U+00ab69: */
+ 0, /* U+00ab6a: */
+ 0, /* U+00ab6b: */
+ 0, /* U+00ab6c: */
+ 0, /* U+00ab6d: */
+ 0, /* U+00ab6e: */
+ 0, /* U+00ab6f: */
+ 0, /* U+00ab70: */
+ 0, /* U+00ab71: */
+ 0, /* U+00ab72: */
+ 0, /* U+00ab73: */
+ 0, /* U+00ab74: */
+ 0, /* U+00ab75: */
+ 0, /* U+00ab76: */
+ 0, /* U+00ab77: */
+ 0, /* U+00ab78: */
+ 0, /* U+00ab79: */
+ 0, /* U+00ab7a: */
+ 0, /* U+00ab7b: */
+ 0, /* U+00ab7c: */
+ 0, /* U+00ab7d: */
+ 0, /* U+00ab7e: */
+ 0, /* U+00ab7f: */
+ 0, /* U+00ab80: */
+ 0, /* U+00ab81: */
+ 0, /* U+00ab82: */
+ 0, /* U+00ab83: */
+ 0, /* U+00ab84: */
+ 0, /* U+00ab85: */
+ 0, /* U+00ab86: */
+ 0, /* U+00ab87: */
+ 0, /* U+00ab88: */
+ 0, /* U+00ab89: */
+ 0, /* U+00ab8a: */
+ 0, /* U+00ab8b: */
+ 0, /* U+00ab8c: */
+ 0, /* U+00ab8d: */
+ 0, /* U+00ab8e: */
+ 0, /* U+00ab8f: */
+ 0, /* U+00ab90: */
+ 0, /* U+00ab91: */
+ 0, /* U+00ab92: */
+ 0, /* U+00ab93: */
+ 0, /* U+00ab94: */
+ 0, /* U+00ab95: */
+ 0, /* U+00ab96: */
+ 0, /* U+00ab97: */
+ 0, /* U+00ab98: */
+ 0, /* U+00ab99: */
+ 0, /* U+00ab9a: */
+ 0, /* U+00ab9b: */
+ 0, /* U+00ab9c: */
+ 0, /* U+00ab9d: */
+ 0, /* U+00ab9e: */
+ 0, /* U+00ab9f: */
+ 0, /* U+00aba0: */
+ 0, /* U+00aba1: */
+ 0, /* U+00aba2: */
+ 0, /* U+00aba3: */
+ 0, /* U+00aba4: */
+ 0, /* U+00aba5: */
+ 0, /* U+00aba6: */
+ 0, /* U+00aba7: */
+ 0, /* U+00aba8: */
+ 0, /* U+00aba9: */
+ 0, /* U+00abaa: */
+ 0, /* U+00abab: */
+ 0, /* U+00abac: */
+ 0, /* U+00abad: */
+ 0, /* U+00abae: */
+ 0, /* U+00abaf: */
+ 0, /* U+00abb0: */
+ 0, /* U+00abb1: */
+ 0, /* U+00abb2: */
+ 0, /* U+00abb3: */
+ 0, /* U+00abb4: */
+ 0, /* U+00abb5: */
+ 0, /* U+00abb6: */
+ 0, /* U+00abb7: */
+ 0, /* U+00abb8: */
+ 0, /* U+00abb9: */
+ 0, /* U+00abba: */
+ 0, /* U+00abbb: */
+ 0, /* U+00abbc: */
+ 0, /* U+00abbd: */
+ 0, /* U+00abbe: */
+ 0, /* U+00abbf: */
+ RTUNI_ALPHA, /* U+00abc0: MEETEI MAYEK LETTER KOK*/
+ RTUNI_ALPHA, /* U+00abc1: MEETEI MAYEK LETTER SAM*/
+ RTUNI_ALPHA, /* U+00abc2: MEETEI MAYEK LETTER LAI*/
+ RTUNI_ALPHA, /* U+00abc3: MEETEI MAYEK LETTER MIT*/
+ RTUNI_ALPHA, /* U+00abc4: MEETEI MAYEK LETTER PA*/
+ RTUNI_ALPHA, /* U+00abc5: MEETEI MAYEK LETTER NA*/
+ RTUNI_ALPHA, /* U+00abc6: MEETEI MAYEK LETTER CHIL*/
+ RTUNI_ALPHA, /* U+00abc7: MEETEI MAYEK LETTER TIL*/
+ RTUNI_ALPHA, /* U+00abc8: MEETEI MAYEK LETTER KHOU*/
+ RTUNI_ALPHA, /* U+00abc9: MEETEI MAYEK LETTER NGOU*/
+ RTUNI_ALPHA, /* U+00abca: MEETEI MAYEK LETTER THOU*/
+ RTUNI_ALPHA, /* U+00abcb: MEETEI MAYEK LETTER WAI*/
+ RTUNI_ALPHA, /* U+00abcc: MEETEI MAYEK LETTER YANG*/
+ RTUNI_ALPHA, /* U+00abcd: MEETEI MAYEK LETTER HUK*/
+ RTUNI_ALPHA, /* U+00abce: MEETEI MAYEK LETTER UN*/
+ RTUNI_ALPHA, /* U+00abcf: MEETEI MAYEK LETTER I*/
+ RTUNI_ALPHA, /* U+00abd0: MEETEI MAYEK LETTER PHAM*/
+ RTUNI_ALPHA, /* U+00abd1: MEETEI MAYEK LETTER ATIYA*/
+ RTUNI_ALPHA, /* U+00abd2: MEETEI MAYEK LETTER GOK*/
+ RTUNI_ALPHA, /* U+00abd3: MEETEI MAYEK LETTER JHAM*/
+ RTUNI_ALPHA, /* U+00abd4: MEETEI MAYEK LETTER RAI*/
+ RTUNI_ALPHA, /* U+00abd5: MEETEI MAYEK LETTER BA*/
+ RTUNI_ALPHA, /* U+00abd6: MEETEI MAYEK LETTER JIL*/
+ RTUNI_ALPHA, /* U+00abd7: MEETEI MAYEK LETTER DIL*/
+ RTUNI_ALPHA, /* U+00abd8: MEETEI MAYEK LETTER GHOU*/
+ RTUNI_ALPHA, /* U+00abd9: MEETEI MAYEK LETTER DHOU*/
+ RTUNI_ALPHA, /* U+00abda: MEETEI MAYEK LETTER BHAM*/
+ RTUNI_ALPHA, /* U+00abdb: MEETEI MAYEK LETTER KOK LONSUM*/
+ RTUNI_ALPHA, /* U+00abdc: MEETEI MAYEK LETTER LAI LONSUM*/
+ RTUNI_ALPHA, /* U+00abdd: MEETEI MAYEK LETTER MIT LONSUM*/
+ RTUNI_ALPHA, /* U+00abde: MEETEI MAYEK LETTER PA LONSUM*/
+ RTUNI_ALPHA, /* U+00abdf: MEETEI MAYEK LETTER NA LONSUM*/
+ RTUNI_ALPHA, /* U+00abe0: MEETEI MAYEK LETTER TIL LONSUM*/
+ RTUNI_ALPHA, /* U+00abe1: MEETEI MAYEK LETTER NGOU LONSUM*/
+ RTUNI_ALPHA, /* U+00abe2: MEETEI MAYEK LETTER I LONSUM*/
+ RTUNI_ALPHA, /* U+00abe3: MEETEI MAYEK VOWEL SIGN ONAP*/
+ RTUNI_ALPHA, /* U+00abe4: MEETEI MAYEK VOWEL SIGN INAP*/
+ RTUNI_ALPHA, /* U+00abe5: MEETEI MAYEK VOWEL SIGN ANAP*/
+ RTUNI_ALPHA, /* U+00abe6: MEETEI MAYEK VOWEL SIGN YENAP*/
+ RTUNI_ALPHA, /* U+00abe7: MEETEI MAYEK VOWEL SIGN SOUNAP*/
+ RTUNI_ALPHA, /* U+00abe8: MEETEI MAYEK VOWEL SIGN UNAP*/
+ RTUNI_ALPHA, /* U+00abe9: MEETEI MAYEK VOWEL SIGN CHEINAP*/
+ RTUNI_ALPHA, /* U+00abea: MEETEI MAYEK VOWEL SIGN NUNG*/
+ 0, /* U+00abeb: MEETEI MAYEK CHEIKHEI*/
+ 0, /* U+00abec: MEETEI MAYEK LUM IYEK*/
+ 0, /* U+00abed: MEETEI MAYEK APUN IYEK*/
+ 0, /* U+00abee: */
+ 0, /* U+00abef: */
+ RTUNI_DDIGIT, /* U+00abf0: MEETEI MAYEK DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+00abf1: MEETEI MAYEK DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+00abf2: MEETEI MAYEK DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+00abf3: MEETEI MAYEK DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+00abf4: MEETEI MAYEK DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+00abf5: MEETEI MAYEK DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+00abf6: MEETEI MAYEK DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+00abf7: MEETEI MAYEK DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+00abf8: MEETEI MAYEK DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+00abf9: MEETEI MAYEK DIGIT NINE*/
+ 0, /* U+00abfa: */
+ 0, /* U+00abfb: */
+ 0, /* U+00abfc: */
+ 0, /* U+00abfd: */
+ 0, /* U+00abfe: */
+ 0, /* U+00abff: */
+ RTUNI_ALPHA, /* U+00ac00: <Hangul Syllable, First>*/
+ RTUNI_ALPHA, /* U+00ac01: */
+ RTUNI_ALPHA, /* U+00ac02: */
+ RTUNI_ALPHA, /* U+00ac03: */
+ RTUNI_ALPHA, /* U+00ac04: */
+ RTUNI_ALPHA, /* U+00ac05: */
+ RTUNI_ALPHA, /* U+00ac06: */
+ RTUNI_ALPHA, /* U+00ac07: */
+ RTUNI_ALPHA, /* U+00ac08: */
+ RTUNI_ALPHA, /* U+00ac09: */
+ RTUNI_ALPHA, /* U+00ac0a: */
+ RTUNI_ALPHA, /* U+00ac0b: */
+ RTUNI_ALPHA, /* U+00ac0c: */
+ RTUNI_ALPHA, /* U+00ac0d: */
+ RTUNI_ALPHA, /* U+00ac0e: */
+ RTUNI_ALPHA, /* U+00ac0f: */
+ RTUNI_ALPHA, /* U+00ac10: */
+ RTUNI_ALPHA, /* U+00ac11: */
+ RTUNI_ALPHA, /* U+00ac12: */
+ RTUNI_ALPHA, /* U+00ac13: */
+ RTUNI_ALPHA, /* U+00ac14: */
+ RTUNI_ALPHA, /* U+00ac15: */
+ RTUNI_ALPHA, /* U+00ac16: */
+ RTUNI_ALPHA, /* U+00ac17: */
+ RTUNI_ALPHA, /* U+00ac18: */
+ RTUNI_ALPHA, /* U+00ac19: */
+ RTUNI_ALPHA, /* U+00ac1a: */
+ RTUNI_ALPHA, /* U+00ac1b: */
+ RTUNI_ALPHA, /* U+00ac1c: */
+ RTUNI_ALPHA, /* U+00ac1d: */
+ RTUNI_ALPHA, /* U+00ac1e: */
+ RTUNI_ALPHA, /* U+00ac1f: */
+ RTUNI_ALPHA, /* U+00ac20: */
+ RTUNI_ALPHA, /* U+00ac21: */
+ RTUNI_ALPHA, /* U+00ac22: */
+ RTUNI_ALPHA, /* U+00ac23: */
+ RTUNI_ALPHA, /* U+00ac24: */
+ RTUNI_ALPHA, /* U+00ac25: */
+ RTUNI_ALPHA, /* U+00ac26: */
+ RTUNI_ALPHA, /* U+00ac27: */
+ RTUNI_ALPHA, /* U+00ac28: */
+ RTUNI_ALPHA, /* U+00ac29: */
+ RTUNI_ALPHA, /* U+00ac2a: */
+ RTUNI_ALPHA, /* U+00ac2b: */
+ RTUNI_ALPHA, /* U+00ac2c: */
+ RTUNI_ALPHA, /* U+00ac2d: */
+ RTUNI_ALPHA, /* U+00ac2e: */
+ RTUNI_ALPHA, /* U+00ac2f: */
+ RTUNI_ALPHA, /* U+00ac30: */
+ RTUNI_ALPHA, /* U+00ac31: */
+ RTUNI_ALPHA, /* U+00ac32: */
+ RTUNI_ALPHA, /* U+00ac33: */
+ RTUNI_ALPHA, /* U+00ac34: */
+ RTUNI_ALPHA, /* U+00ac35: */
+ RTUNI_ALPHA, /* U+00ac36: */
+ RTUNI_ALPHA, /* U+00ac37: */
+ RTUNI_ALPHA, /* U+00ac38: */
+ RTUNI_ALPHA, /* U+00ac39: */
+ RTUNI_ALPHA, /* U+00ac3a: */
+ RTUNI_ALPHA, /* U+00ac3b: */
+ RTUNI_ALPHA, /* U+00ac3c: */
+ RTUNI_ALPHA, /* U+00ac3d: */
+ RTUNI_ALPHA, /* U+00ac3e: */
+ RTUNI_ALPHA, /* U+00ac3f: */
+ RTUNI_ALPHA, /* U+00ac40: */
+ RTUNI_ALPHA, /* U+00ac41: */
+ RTUNI_ALPHA, /* U+00ac42: */
+ RTUNI_ALPHA, /* U+00ac43: */
+ RTUNI_ALPHA, /* U+00ac44: */
+ RTUNI_ALPHA, /* U+00ac45: */
+ RTUNI_ALPHA, /* U+00ac46: */
+ RTUNI_ALPHA, /* U+00ac47: */
+ RTUNI_ALPHA, /* U+00ac48: */
+ RTUNI_ALPHA, /* U+00ac49: */
+ RTUNI_ALPHA, /* U+00ac4a: */
+ RTUNI_ALPHA, /* U+00ac4b: */
+ RTUNI_ALPHA, /* U+00ac4c: */
+ RTUNI_ALPHA, /* U+00ac4d: */
+ RTUNI_ALPHA, /* U+00ac4e: */
+ RTUNI_ALPHA, /* U+00ac4f: */
+ RTUNI_ALPHA, /* U+00ac50: */
+ RTUNI_ALPHA, /* U+00ac51: */
+ RTUNI_ALPHA, /* U+00ac52: */
+ RTUNI_ALPHA, /* U+00ac53: */
+ RTUNI_ALPHA, /* U+00ac54: */
+ RTUNI_ALPHA, /* U+00ac55: */
+ RTUNI_ALPHA, /* U+00ac56: */
+ RTUNI_ALPHA, /* U+00ac57: */
+ RTUNI_ALPHA, /* U+00ac58: */
+ RTUNI_ALPHA, /* U+00ac59: */
+ RTUNI_ALPHA, /* U+00ac5a: */
+ RTUNI_ALPHA, /* U+00ac5b: */
+ RTUNI_ALPHA, /* U+00ac5c: */
+ RTUNI_ALPHA, /* U+00ac5d: */
+ RTUNI_ALPHA, /* U+00ac5e: */
+ RTUNI_ALPHA, /* U+00ac5f: */
+ RTUNI_ALPHA, /* U+00ac60: */
+ RTUNI_ALPHA, /* U+00ac61: */
+ RTUNI_ALPHA, /* U+00ac62: */
+ RTUNI_ALPHA, /* U+00ac63: */
+ RTUNI_ALPHA, /* U+00ac64: */
+ RTUNI_ALPHA, /* U+00ac65: */
+ RTUNI_ALPHA, /* U+00ac66: */
+ RTUNI_ALPHA, /* U+00ac67: */
+ RTUNI_ALPHA, /* U+00ac68: */
+ RTUNI_ALPHA, /* U+00ac69: */
+ RTUNI_ALPHA, /* U+00ac6a: */
+ RTUNI_ALPHA, /* U+00ac6b: */
+ RTUNI_ALPHA, /* U+00ac6c: */
+ RTUNI_ALPHA, /* U+00ac6d: */
+ RTUNI_ALPHA, /* U+00ac6e: */
+ RTUNI_ALPHA, /* U+00ac6f: */
+ RTUNI_ALPHA, /* U+00ac70: */
+ RTUNI_ALPHA, /* U+00ac71: */
+ RTUNI_ALPHA, /* U+00ac72: */
+ RTUNI_ALPHA, /* U+00ac73: */
+ RTUNI_ALPHA, /* U+00ac74: */
+ RTUNI_ALPHA, /* U+00ac75: */
+ RTUNI_ALPHA, /* U+00ac76: */
+ RTUNI_ALPHA, /* U+00ac77: */
+ RTUNI_ALPHA, /* U+00ac78: */
+ RTUNI_ALPHA, /* U+00ac79: */
+ RTUNI_ALPHA, /* U+00ac7a: */
+ RTUNI_ALPHA, /* U+00ac7b: */
+ RTUNI_ALPHA, /* U+00ac7c: */
+ RTUNI_ALPHA, /* U+00ac7d: */
+ RTUNI_ALPHA, /* U+00ac7e: */
+ RTUNI_ALPHA, /* U+00ac7f: */
+ RTUNI_ALPHA, /* U+00ac80: */
+ RTUNI_ALPHA, /* U+00ac81: */
+ RTUNI_ALPHA, /* U+00ac82: */
+ RTUNI_ALPHA, /* U+00ac83: */
+ RTUNI_ALPHA, /* U+00ac84: */
+ RTUNI_ALPHA, /* U+00ac85: */
+ RTUNI_ALPHA, /* U+00ac86: */
+ RTUNI_ALPHA, /* U+00ac87: */
+ RTUNI_ALPHA, /* U+00ac88: */
+ RTUNI_ALPHA, /* U+00ac89: */
+ RTUNI_ALPHA, /* U+00ac8a: */
+ RTUNI_ALPHA, /* U+00ac8b: */
+ RTUNI_ALPHA, /* U+00ac8c: */
+ RTUNI_ALPHA, /* U+00ac8d: */
+ RTUNI_ALPHA, /* U+00ac8e: */
+ RTUNI_ALPHA, /* U+00ac8f: */
+ RTUNI_ALPHA, /* U+00ac90: */
+ RTUNI_ALPHA, /* U+00ac91: */
+ RTUNI_ALPHA, /* U+00ac92: */
+ RTUNI_ALPHA, /* U+00ac93: */
+ RTUNI_ALPHA, /* U+00ac94: */
+ RTUNI_ALPHA, /* U+00ac95: */
+ RTUNI_ALPHA, /* U+00ac96: */
+ RTUNI_ALPHA, /* U+00ac97: */
+ RTUNI_ALPHA, /* U+00ac98: */
+ RTUNI_ALPHA, /* U+00ac99: */
+ RTUNI_ALPHA, /* U+00ac9a: */
+ RTUNI_ALPHA, /* U+00ac9b: */
+ RTUNI_ALPHA, /* U+00ac9c: */
+ RTUNI_ALPHA, /* U+00ac9d: */
+ RTUNI_ALPHA, /* U+00ac9e: */
+ RTUNI_ALPHA, /* U+00ac9f: */
+ RTUNI_ALPHA, /* U+00aca0: */
+ RTUNI_ALPHA, /* U+00aca1: */
+ RTUNI_ALPHA, /* U+00aca2: */
+ RTUNI_ALPHA, /* U+00aca3: */
+ RTUNI_ALPHA, /* U+00aca4: */
+ RTUNI_ALPHA, /* U+00aca5: */
+ RTUNI_ALPHA, /* U+00aca6: */
+ RTUNI_ALPHA, /* U+00aca7: */
+ RTUNI_ALPHA, /* U+00aca8: */
+ RTUNI_ALPHA, /* U+00aca9: */
+ RTUNI_ALPHA, /* U+00acaa: */
+ RTUNI_ALPHA, /* U+00acab: */
+ RTUNI_ALPHA, /* U+00acac: */
+ RTUNI_ALPHA, /* U+00acad: */
+ RTUNI_ALPHA, /* U+00acae: */
+ RTUNI_ALPHA, /* U+00acaf: */
+ RTUNI_ALPHA, /* U+00acb0: */
+ RTUNI_ALPHA, /* U+00acb1: */
+ RTUNI_ALPHA, /* U+00acb2: */
+ RTUNI_ALPHA, /* U+00acb3: */
+ RTUNI_ALPHA, /* U+00acb4: */
+ RTUNI_ALPHA, /* U+00acb5: */
+ RTUNI_ALPHA, /* U+00acb6: */
+ RTUNI_ALPHA, /* U+00acb7: */
+ RTUNI_ALPHA, /* U+00acb8: */
+ RTUNI_ALPHA, /* U+00acb9: */
+ RTUNI_ALPHA, /* U+00acba: */
+ RTUNI_ALPHA, /* U+00acbb: */
+ RTUNI_ALPHA, /* U+00acbc: */
+ RTUNI_ALPHA, /* U+00acbd: */
+ RTUNI_ALPHA, /* U+00acbe: */
+ RTUNI_ALPHA, /* U+00acbf: */
+ RTUNI_ALPHA, /* U+00acc0: */
+ RTUNI_ALPHA, /* U+00acc1: */
+ RTUNI_ALPHA, /* U+00acc2: */
+ RTUNI_ALPHA, /* U+00acc3: */
+ RTUNI_ALPHA, /* U+00acc4: */
+ RTUNI_ALPHA, /* U+00acc5: */
+ RTUNI_ALPHA, /* U+00acc6: */
+ RTUNI_ALPHA, /* U+00acc7: */
+ RTUNI_ALPHA, /* U+00acc8: */
+ RTUNI_ALPHA, /* U+00acc9: */
+ RTUNI_ALPHA, /* U+00acca: */
+ RTUNI_ALPHA, /* U+00accb: */
+ RTUNI_ALPHA, /* U+00accc: */
+ RTUNI_ALPHA, /* U+00accd: */
+ RTUNI_ALPHA, /* U+00acce: */
+ RTUNI_ALPHA, /* U+00accf: */
+ RTUNI_ALPHA, /* U+00acd0: */
+ RTUNI_ALPHA, /* U+00acd1: */
+ RTUNI_ALPHA, /* U+00acd2: */
+ RTUNI_ALPHA, /* U+00acd3: */
+ RTUNI_ALPHA, /* U+00acd4: */
+ RTUNI_ALPHA, /* U+00acd5: */
+ RTUNI_ALPHA, /* U+00acd6: */
+ RTUNI_ALPHA, /* U+00acd7: */
+ RTUNI_ALPHA, /* U+00acd8: */
+ RTUNI_ALPHA, /* U+00acd9: */
+ RTUNI_ALPHA, /* U+00acda: */
+ RTUNI_ALPHA, /* U+00acdb: */
+ RTUNI_ALPHA, /* U+00acdc: */
+ RTUNI_ALPHA, /* U+00acdd: */
+ RTUNI_ALPHA, /* U+00acde: */
+ RTUNI_ALPHA, /* U+00acdf: */
+ RTUNI_ALPHA, /* U+00ace0: */
+ RTUNI_ALPHA, /* U+00ace1: */
+ RTUNI_ALPHA, /* U+00ace2: */
+ RTUNI_ALPHA, /* U+00ace3: */
+ RTUNI_ALPHA, /* U+00ace4: */
+ RTUNI_ALPHA, /* U+00ace5: */
+ RTUNI_ALPHA, /* U+00ace6: */
+ RTUNI_ALPHA, /* U+00ace7: */
+ RTUNI_ALPHA, /* U+00ace8: */
+ RTUNI_ALPHA, /* U+00ace9: */
+ RTUNI_ALPHA, /* U+00acea: */
+ RTUNI_ALPHA, /* U+00aceb: */
+ RTUNI_ALPHA, /* U+00acec: */
+ RTUNI_ALPHA, /* U+00aced: */
+ RTUNI_ALPHA, /* U+00acee: */
+ RTUNI_ALPHA, /* U+00acef: */
+ RTUNI_ALPHA, /* U+00acf0: */
+ RTUNI_ALPHA, /* U+00acf1: */
+ RTUNI_ALPHA, /* U+00acf2: */
+ RTUNI_ALPHA, /* U+00acf3: */
+ RTUNI_ALPHA, /* U+00acf4: */
+ RTUNI_ALPHA, /* U+00acf5: */
+ RTUNI_ALPHA, /* U+00acf6: */
+ RTUNI_ALPHA, /* U+00acf7: */
+ RTUNI_ALPHA, /* U+00acf8: */
+ RTUNI_ALPHA, /* U+00acf9: */
+ RTUNI_ALPHA, /* U+00acfa: */
+ RTUNI_ALPHA, /* U+00acfb: */
+ RTUNI_ALPHA, /* U+00acfc: */
+ RTUNI_ALPHA, /* U+00acfd: */
+ RTUNI_ALPHA, /* U+00acfe: */
+ RTUNI_ALPHA, /* U+00acff: */
+ RTUNI_ALPHA, /* U+00ad00: */
+ RTUNI_ALPHA, /* U+00ad01: */
+ RTUNI_ALPHA, /* U+00ad02: */
+ RTUNI_ALPHA, /* U+00ad03: */
+ RTUNI_ALPHA, /* U+00ad04: */
+ RTUNI_ALPHA, /* U+00ad05: */
+ RTUNI_ALPHA, /* U+00ad06: */
+ RTUNI_ALPHA, /* U+00ad07: */
+ RTUNI_ALPHA, /* U+00ad08: */
+ RTUNI_ALPHA, /* U+00ad09: */
+ RTUNI_ALPHA, /* U+00ad0a: */
+ RTUNI_ALPHA, /* U+00ad0b: */
+ RTUNI_ALPHA, /* U+00ad0c: */
+ RTUNI_ALPHA, /* U+00ad0d: */
+ RTUNI_ALPHA, /* U+00ad0e: */
+ RTUNI_ALPHA, /* U+00ad0f: */
+ RTUNI_ALPHA, /* U+00ad10: */
+ RTUNI_ALPHA, /* U+00ad11: */
+ RTUNI_ALPHA, /* U+00ad12: */
+ RTUNI_ALPHA, /* U+00ad13: */
+ RTUNI_ALPHA, /* U+00ad14: */
+ RTUNI_ALPHA, /* U+00ad15: */
+ RTUNI_ALPHA, /* U+00ad16: */
+ RTUNI_ALPHA, /* U+00ad17: */
+ RTUNI_ALPHA, /* U+00ad18: */
+ RTUNI_ALPHA, /* U+00ad19: */
+ RTUNI_ALPHA, /* U+00ad1a: */
+ RTUNI_ALPHA, /* U+00ad1b: */
+ RTUNI_ALPHA, /* U+00ad1c: */
+ RTUNI_ALPHA, /* U+00ad1d: */
+ RTUNI_ALPHA, /* U+00ad1e: */
+ RTUNI_ALPHA, /* U+00ad1f: */
+ RTUNI_ALPHA, /* U+00ad20: */
+ RTUNI_ALPHA, /* U+00ad21: */
+ RTUNI_ALPHA, /* U+00ad22: */
+ RTUNI_ALPHA, /* U+00ad23: */
+ RTUNI_ALPHA, /* U+00ad24: */
+ RTUNI_ALPHA, /* U+00ad25: */
+ RTUNI_ALPHA, /* U+00ad26: */
+ RTUNI_ALPHA, /* U+00ad27: */
+ RTUNI_ALPHA, /* U+00ad28: */
+ RTUNI_ALPHA, /* U+00ad29: */
+ RTUNI_ALPHA, /* U+00ad2a: */
+ RTUNI_ALPHA, /* U+00ad2b: */
+ RTUNI_ALPHA, /* U+00ad2c: */
+ RTUNI_ALPHA, /* U+00ad2d: */
+ RTUNI_ALPHA, /* U+00ad2e: */
+ RTUNI_ALPHA, /* U+00ad2f: */
+ RTUNI_ALPHA, /* U+00ad30: */
+ RTUNI_ALPHA, /* U+00ad31: */
+ RTUNI_ALPHA, /* U+00ad32: */
+ RTUNI_ALPHA, /* U+00ad33: */
+ RTUNI_ALPHA, /* U+00ad34: */
+ RTUNI_ALPHA, /* U+00ad35: */
+ RTUNI_ALPHA, /* U+00ad36: */
+ RTUNI_ALPHA, /* U+00ad37: */
+ RTUNI_ALPHA, /* U+00ad38: */
+ RTUNI_ALPHA, /* U+00ad39: */
+ RTUNI_ALPHA, /* U+00ad3a: */
+ RTUNI_ALPHA, /* U+00ad3b: */
+ RTUNI_ALPHA, /* U+00ad3c: */
+ RTUNI_ALPHA, /* U+00ad3d: */
+ RTUNI_ALPHA, /* U+00ad3e: */
+ RTUNI_ALPHA, /* U+00ad3f: */
+ RTUNI_ALPHA, /* U+00ad40: */
+ RTUNI_ALPHA, /* U+00ad41: */
+ RTUNI_ALPHA, /* U+00ad42: */
+ RTUNI_ALPHA, /* U+00ad43: */
+ RTUNI_ALPHA, /* U+00ad44: */
+ RTUNI_ALPHA, /* U+00ad45: */
+ RTUNI_ALPHA, /* U+00ad46: */
+ RTUNI_ALPHA, /* U+00ad47: */
+ RTUNI_ALPHA, /* U+00ad48: */
+ RTUNI_ALPHA, /* U+00ad49: */
+ RTUNI_ALPHA, /* U+00ad4a: */
+ RTUNI_ALPHA, /* U+00ad4b: */
+ RTUNI_ALPHA, /* U+00ad4c: */
+ RTUNI_ALPHA, /* U+00ad4d: */
+ RTUNI_ALPHA, /* U+00ad4e: */
+ RTUNI_ALPHA, /* U+00ad4f: */
+ RTUNI_ALPHA, /* U+00ad50: */
+ RTUNI_ALPHA, /* U+00ad51: */
+ RTUNI_ALPHA, /* U+00ad52: */
+ RTUNI_ALPHA, /* U+00ad53: */
+ RTUNI_ALPHA, /* U+00ad54: */
+ RTUNI_ALPHA, /* U+00ad55: */
+ RTUNI_ALPHA, /* U+00ad56: */
+ RTUNI_ALPHA, /* U+00ad57: */
+ RTUNI_ALPHA, /* U+00ad58: */
+ RTUNI_ALPHA, /* U+00ad59: */
+ RTUNI_ALPHA, /* U+00ad5a: */
+ RTUNI_ALPHA, /* U+00ad5b: */
+ RTUNI_ALPHA, /* U+00ad5c: */
+ RTUNI_ALPHA, /* U+00ad5d: */
+ RTUNI_ALPHA, /* U+00ad5e: */
+ RTUNI_ALPHA, /* U+00ad5f: */
+ RTUNI_ALPHA, /* U+00ad60: */
+ RTUNI_ALPHA, /* U+00ad61: */
+ RTUNI_ALPHA, /* U+00ad62: */
+ RTUNI_ALPHA, /* U+00ad63: */
+ RTUNI_ALPHA, /* U+00ad64: */
+ RTUNI_ALPHA, /* U+00ad65: */
+ RTUNI_ALPHA, /* U+00ad66: */
+ RTUNI_ALPHA, /* U+00ad67: */
+ RTUNI_ALPHA, /* U+00ad68: */
+ RTUNI_ALPHA, /* U+00ad69: */
+ RTUNI_ALPHA, /* U+00ad6a: */
+ RTUNI_ALPHA, /* U+00ad6b: */
+ RTUNI_ALPHA, /* U+00ad6c: */
+ RTUNI_ALPHA, /* U+00ad6d: */
+ RTUNI_ALPHA, /* U+00ad6e: */
+ RTUNI_ALPHA, /* U+00ad6f: */
+ RTUNI_ALPHA, /* U+00ad70: */
+ RTUNI_ALPHA, /* U+00ad71: */
+ RTUNI_ALPHA, /* U+00ad72: */
+ RTUNI_ALPHA, /* U+00ad73: */
+ RTUNI_ALPHA, /* U+00ad74: */
+ RTUNI_ALPHA, /* U+00ad75: */
+ RTUNI_ALPHA, /* U+00ad76: */
+ RTUNI_ALPHA, /* U+00ad77: */
+ RTUNI_ALPHA, /* U+00ad78: */
+ RTUNI_ALPHA, /* U+00ad79: */
+ RTUNI_ALPHA, /* U+00ad7a: */
+ RTUNI_ALPHA, /* U+00ad7b: */
+ RTUNI_ALPHA, /* U+00ad7c: */
+ RTUNI_ALPHA, /* U+00ad7d: */
+ RTUNI_ALPHA, /* U+00ad7e: */
+ RTUNI_ALPHA, /* U+00ad7f: */
+ RTUNI_ALPHA, /* U+00ad80: */
+ RTUNI_ALPHA, /* U+00ad81: */
+ RTUNI_ALPHA, /* U+00ad82: */
+ RTUNI_ALPHA, /* U+00ad83: */
+ RTUNI_ALPHA, /* U+00ad84: */
+ RTUNI_ALPHA, /* U+00ad85: */
+ RTUNI_ALPHA, /* U+00ad86: */
+ RTUNI_ALPHA, /* U+00ad87: */
+ RTUNI_ALPHA, /* U+00ad88: */
+ RTUNI_ALPHA, /* U+00ad89: */
+ RTUNI_ALPHA, /* U+00ad8a: */
+ RTUNI_ALPHA, /* U+00ad8b: */
+ RTUNI_ALPHA, /* U+00ad8c: */
+ RTUNI_ALPHA, /* U+00ad8d: */
+ RTUNI_ALPHA, /* U+00ad8e: */
+ RTUNI_ALPHA, /* U+00ad8f: */
+ RTUNI_ALPHA, /* U+00ad90: */
+ RTUNI_ALPHA, /* U+00ad91: */
+ RTUNI_ALPHA, /* U+00ad92: */
+ RTUNI_ALPHA, /* U+00ad93: */
+ RTUNI_ALPHA, /* U+00ad94: */
+ RTUNI_ALPHA, /* U+00ad95: */
+ RTUNI_ALPHA, /* U+00ad96: */
+ RTUNI_ALPHA, /* U+00ad97: */
+ RTUNI_ALPHA, /* U+00ad98: */
+ RTUNI_ALPHA, /* U+00ad99: */
+ RTUNI_ALPHA, /* U+00ad9a: */
+ RTUNI_ALPHA, /* U+00ad9b: */
+ RTUNI_ALPHA, /* U+00ad9c: */
+ RTUNI_ALPHA, /* U+00ad9d: */
+ RTUNI_ALPHA, /* U+00ad9e: */
+ RTUNI_ALPHA, /* U+00ad9f: */
+ RTUNI_ALPHA, /* U+00ada0: */
+ RTUNI_ALPHA, /* U+00ada1: */
+ RTUNI_ALPHA, /* U+00ada2: */
+ RTUNI_ALPHA, /* U+00ada3: */
+ RTUNI_ALPHA, /* U+00ada4: */
+ RTUNI_ALPHA, /* U+00ada5: */
+ RTUNI_ALPHA, /* U+00ada6: */
+ RTUNI_ALPHA, /* U+00ada7: */
+ RTUNI_ALPHA, /* U+00ada8: */
+ RTUNI_ALPHA, /* U+00ada9: */
+ RTUNI_ALPHA, /* U+00adaa: */
+ RTUNI_ALPHA, /* U+00adab: */
+ RTUNI_ALPHA, /* U+00adac: */
+ RTUNI_ALPHA, /* U+00adad: */
+ RTUNI_ALPHA, /* U+00adae: */
+ RTUNI_ALPHA, /* U+00adaf: */
+ RTUNI_ALPHA, /* U+00adb0: */
+ RTUNI_ALPHA, /* U+00adb1: */
+ RTUNI_ALPHA, /* U+00adb2: */
+ RTUNI_ALPHA, /* U+00adb3: */
+ RTUNI_ALPHA, /* U+00adb4: */
+ RTUNI_ALPHA, /* U+00adb5: */
+ RTUNI_ALPHA, /* U+00adb6: */
+ RTUNI_ALPHA, /* U+00adb7: */
+ RTUNI_ALPHA, /* U+00adb8: */
+ RTUNI_ALPHA, /* U+00adb9: */
+ RTUNI_ALPHA, /* U+00adba: */
+ RTUNI_ALPHA, /* U+00adbb: */
+ RTUNI_ALPHA, /* U+00adbc: */
+ RTUNI_ALPHA, /* U+00adbd: */
+ RTUNI_ALPHA, /* U+00adbe: */
+ RTUNI_ALPHA, /* U+00adbf: */
+ RTUNI_ALPHA, /* U+00adc0: */
+ RTUNI_ALPHA, /* U+00adc1: */
+ RTUNI_ALPHA, /* U+00adc2: */
+ RTUNI_ALPHA, /* U+00adc3: */
+ RTUNI_ALPHA, /* U+00adc4: */
+ RTUNI_ALPHA, /* U+00adc5: */
+ RTUNI_ALPHA, /* U+00adc6: */
+ RTUNI_ALPHA, /* U+00adc7: */
+ RTUNI_ALPHA, /* U+00adc8: */
+ RTUNI_ALPHA, /* U+00adc9: */
+ RTUNI_ALPHA, /* U+00adca: */
+ RTUNI_ALPHA, /* U+00adcb: */
+ RTUNI_ALPHA, /* U+00adcc: */
+ RTUNI_ALPHA, /* U+00adcd: */
+ RTUNI_ALPHA, /* U+00adce: */
+ RTUNI_ALPHA, /* U+00adcf: */
+ RTUNI_ALPHA, /* U+00add0: */
+ RTUNI_ALPHA, /* U+00add1: */
+ RTUNI_ALPHA, /* U+00add2: */
+ RTUNI_ALPHA, /* U+00add3: */
+ RTUNI_ALPHA, /* U+00add4: */
+ RTUNI_ALPHA, /* U+00add5: */
+ RTUNI_ALPHA, /* U+00add6: */
+ RTUNI_ALPHA, /* U+00add7: */
+ RTUNI_ALPHA, /* U+00add8: */
+ RTUNI_ALPHA, /* U+00add9: */
+ RTUNI_ALPHA, /* U+00adda: */
+ RTUNI_ALPHA, /* U+00addb: */
+ RTUNI_ALPHA, /* U+00addc: */
+ RTUNI_ALPHA, /* U+00addd: */
+ RTUNI_ALPHA, /* U+00adde: */
+ RTUNI_ALPHA, /* U+00addf: */
+ RTUNI_ALPHA, /* U+00ade0: */
+ RTUNI_ALPHA, /* U+00ade1: */
+ RTUNI_ALPHA, /* U+00ade2: */
+ RTUNI_ALPHA, /* U+00ade3: */
+ RTUNI_ALPHA, /* U+00ade4: */
+ RTUNI_ALPHA, /* U+00ade5: */
+ RTUNI_ALPHA, /* U+00ade6: */
+ RTUNI_ALPHA, /* U+00ade7: */
+ RTUNI_ALPHA, /* U+00ade8: */
+ RTUNI_ALPHA, /* U+00ade9: */
+ RTUNI_ALPHA, /* U+00adea: */
+ RTUNI_ALPHA, /* U+00adeb: */
+ RTUNI_ALPHA, /* U+00adec: */
+ RTUNI_ALPHA, /* U+00aded: */
+ RTUNI_ALPHA, /* U+00adee: */
+ RTUNI_ALPHA, /* U+00adef: */
+ RTUNI_ALPHA, /* U+00adf0: */
+ RTUNI_ALPHA, /* U+00adf1: */
+ RTUNI_ALPHA, /* U+00adf2: */
+ RTUNI_ALPHA, /* U+00adf3: */
+ RTUNI_ALPHA, /* U+00adf4: */
+ RTUNI_ALPHA, /* U+00adf5: */
+ RTUNI_ALPHA, /* U+00adf6: */
+ RTUNI_ALPHA, /* U+00adf7: */
+ RTUNI_ALPHA, /* U+00adf8: */
+ RTUNI_ALPHA, /* U+00adf9: */
+ RTUNI_ALPHA, /* U+00adfa: */
+ RTUNI_ALPHA, /* U+00adfb: */
+ RTUNI_ALPHA, /* U+00adfc: */
+ RTUNI_ALPHA, /* U+00adfd: */
+ RTUNI_ALPHA, /* U+00adfe: */
+ RTUNI_ALPHA, /* U+00adff: */
+ RTUNI_ALPHA, /* U+00ae00: */
+ RTUNI_ALPHA, /* U+00ae01: */
+ RTUNI_ALPHA, /* U+00ae02: */
+ RTUNI_ALPHA, /* U+00ae03: */
+ RTUNI_ALPHA, /* U+00ae04: */
+ RTUNI_ALPHA, /* U+00ae05: */
+ RTUNI_ALPHA, /* U+00ae06: */
+ RTUNI_ALPHA, /* U+00ae07: */
+ RTUNI_ALPHA, /* U+00ae08: */
+ RTUNI_ALPHA, /* U+00ae09: */
+ RTUNI_ALPHA, /* U+00ae0a: */
+ RTUNI_ALPHA, /* U+00ae0b: */
+ RTUNI_ALPHA, /* U+00ae0c: */
+ RTUNI_ALPHA, /* U+00ae0d: */
+ RTUNI_ALPHA, /* U+00ae0e: */
+ RTUNI_ALPHA, /* U+00ae0f: */
+ RTUNI_ALPHA, /* U+00ae10: */
+ RTUNI_ALPHA, /* U+00ae11: */
+ RTUNI_ALPHA, /* U+00ae12: */
+ RTUNI_ALPHA, /* U+00ae13: */
+ RTUNI_ALPHA, /* U+00ae14: */
+ RTUNI_ALPHA, /* U+00ae15: */
+ RTUNI_ALPHA, /* U+00ae16: */
+ RTUNI_ALPHA, /* U+00ae17: */
+ RTUNI_ALPHA, /* U+00ae18: */
+ RTUNI_ALPHA, /* U+00ae19: */
+ RTUNI_ALPHA, /* U+00ae1a: */
+ RTUNI_ALPHA, /* U+00ae1b: */
+ RTUNI_ALPHA, /* U+00ae1c: */
+ RTUNI_ALPHA, /* U+00ae1d: */
+ RTUNI_ALPHA, /* U+00ae1e: */
+ RTUNI_ALPHA, /* U+00ae1f: */
+ RTUNI_ALPHA, /* U+00ae20: */
+ RTUNI_ALPHA, /* U+00ae21: */
+ RTUNI_ALPHA, /* U+00ae22: */
+ RTUNI_ALPHA, /* U+00ae23: */
+ RTUNI_ALPHA, /* U+00ae24: */
+ RTUNI_ALPHA, /* U+00ae25: */
+ RTUNI_ALPHA, /* U+00ae26: */
+ RTUNI_ALPHA, /* U+00ae27: */
+ RTUNI_ALPHA, /* U+00ae28: */
+ RTUNI_ALPHA, /* U+00ae29: */
+ RTUNI_ALPHA, /* U+00ae2a: */
+ RTUNI_ALPHA, /* U+00ae2b: */
+ RTUNI_ALPHA, /* U+00ae2c: */
+ RTUNI_ALPHA, /* U+00ae2d: */
+ RTUNI_ALPHA, /* U+00ae2e: */
+ RTUNI_ALPHA, /* U+00ae2f: */
+ RTUNI_ALPHA, /* U+00ae30: */
+ RTUNI_ALPHA, /* U+00ae31: */
+ RTUNI_ALPHA, /* U+00ae32: */
+ RTUNI_ALPHA, /* U+00ae33: */
+ RTUNI_ALPHA, /* U+00ae34: */
+ RTUNI_ALPHA, /* U+00ae35: */
+ RTUNI_ALPHA, /* U+00ae36: */
+ RTUNI_ALPHA, /* U+00ae37: */
+ RTUNI_ALPHA, /* U+00ae38: */
+ RTUNI_ALPHA, /* U+00ae39: */
+ RTUNI_ALPHA, /* U+00ae3a: */
+ RTUNI_ALPHA, /* U+00ae3b: */
+ RTUNI_ALPHA, /* U+00ae3c: */
+ RTUNI_ALPHA, /* U+00ae3d: */
+ RTUNI_ALPHA, /* U+00ae3e: */
+ RTUNI_ALPHA, /* U+00ae3f: */
+ RTUNI_ALPHA, /* U+00ae40: */
+ RTUNI_ALPHA, /* U+00ae41: */
+ RTUNI_ALPHA, /* U+00ae42: */
+ RTUNI_ALPHA, /* U+00ae43: */
+ RTUNI_ALPHA, /* U+00ae44: */
+ RTUNI_ALPHA, /* U+00ae45: */
+ RTUNI_ALPHA, /* U+00ae46: */
+ RTUNI_ALPHA, /* U+00ae47: */
+ RTUNI_ALPHA, /* U+00ae48: */
+ RTUNI_ALPHA, /* U+00ae49: */
+ RTUNI_ALPHA, /* U+00ae4a: */
+ RTUNI_ALPHA, /* U+00ae4b: */
+ RTUNI_ALPHA, /* U+00ae4c: */
+ RTUNI_ALPHA, /* U+00ae4d: */
+ RTUNI_ALPHA, /* U+00ae4e: */
+ RTUNI_ALPHA, /* U+00ae4f: */
+ RTUNI_ALPHA, /* U+00ae50: */
+ RTUNI_ALPHA, /* U+00ae51: */
+ RTUNI_ALPHA, /* U+00ae52: */
+ RTUNI_ALPHA, /* U+00ae53: */
+ RTUNI_ALPHA, /* U+00ae54: */
+ RTUNI_ALPHA, /* U+00ae55: */
+ RTUNI_ALPHA, /* U+00ae56: */
+ RTUNI_ALPHA, /* U+00ae57: */
+ RTUNI_ALPHA, /* U+00ae58: */
+ RTUNI_ALPHA, /* U+00ae59: */
+ RTUNI_ALPHA, /* U+00ae5a: */
+ RTUNI_ALPHA, /* U+00ae5b: */
+ RTUNI_ALPHA, /* U+00ae5c: */
+ RTUNI_ALPHA, /* U+00ae5d: */
+ RTUNI_ALPHA, /* U+00ae5e: */
+ RTUNI_ALPHA, /* U+00ae5f: */
+ RTUNI_ALPHA, /* U+00ae60: */
+ RTUNI_ALPHA, /* U+00ae61: */
+ RTUNI_ALPHA, /* U+00ae62: */
+ RTUNI_ALPHA, /* U+00ae63: */
+ RTUNI_ALPHA, /* U+00ae64: */
+ RTUNI_ALPHA, /* U+00ae65: */
+ RTUNI_ALPHA, /* U+00ae66: */
+ RTUNI_ALPHA, /* U+00ae67: */
+ RTUNI_ALPHA, /* U+00ae68: */
+ RTUNI_ALPHA, /* U+00ae69: */
+ RTUNI_ALPHA, /* U+00ae6a: */
+ RTUNI_ALPHA, /* U+00ae6b: */
+ RTUNI_ALPHA, /* U+00ae6c: */
+ RTUNI_ALPHA, /* U+00ae6d: */
+ RTUNI_ALPHA, /* U+00ae6e: */
+ RTUNI_ALPHA, /* U+00ae6f: */
+ RTUNI_ALPHA, /* U+00ae70: */
+ RTUNI_ALPHA, /* U+00ae71: */
+ RTUNI_ALPHA, /* U+00ae72: */
+ RTUNI_ALPHA, /* U+00ae73: */
+ RTUNI_ALPHA, /* U+00ae74: */
+ RTUNI_ALPHA, /* U+00ae75: */
+ RTUNI_ALPHA, /* U+00ae76: */
+ RTUNI_ALPHA, /* U+00ae77: */
+ RTUNI_ALPHA, /* U+00ae78: */
+ RTUNI_ALPHA, /* U+00ae79: */
+ RTUNI_ALPHA, /* U+00ae7a: */
+ RTUNI_ALPHA, /* U+00ae7b: */
+ RTUNI_ALPHA, /* U+00ae7c: */
+ RTUNI_ALPHA, /* U+00ae7d: */
+ RTUNI_ALPHA, /* U+00ae7e: */
+ RTUNI_ALPHA, /* U+00ae7f: */
+ RTUNI_ALPHA, /* U+00ae80: */
+ RTUNI_ALPHA, /* U+00ae81: */
+ RTUNI_ALPHA, /* U+00ae82: */
+ RTUNI_ALPHA, /* U+00ae83: */
+ RTUNI_ALPHA, /* U+00ae84: */
+ RTUNI_ALPHA, /* U+00ae85: */
+ RTUNI_ALPHA, /* U+00ae86: */
+ RTUNI_ALPHA, /* U+00ae87: */
+ RTUNI_ALPHA, /* U+00ae88: */
+ RTUNI_ALPHA, /* U+00ae89: */
+ RTUNI_ALPHA, /* U+00ae8a: */
+ RTUNI_ALPHA, /* U+00ae8b: */
+ RTUNI_ALPHA, /* U+00ae8c: */
+ RTUNI_ALPHA, /* U+00ae8d: */
+ RTUNI_ALPHA, /* U+00ae8e: */
+ RTUNI_ALPHA, /* U+00ae8f: */
+ RTUNI_ALPHA, /* U+00ae90: */
+ RTUNI_ALPHA, /* U+00ae91: */
+ RTUNI_ALPHA, /* U+00ae92: */
+ RTUNI_ALPHA, /* U+00ae93: */
+ RTUNI_ALPHA, /* U+00ae94: */
+ RTUNI_ALPHA, /* U+00ae95: */
+ RTUNI_ALPHA, /* U+00ae96: */
+ RTUNI_ALPHA, /* U+00ae97: */
+ RTUNI_ALPHA, /* U+00ae98: */
+ RTUNI_ALPHA, /* U+00ae99: */
+ RTUNI_ALPHA, /* U+00ae9a: */
+ RTUNI_ALPHA, /* U+00ae9b: */
+ RTUNI_ALPHA, /* U+00ae9c: */
+ RTUNI_ALPHA, /* U+00ae9d: */
+ RTUNI_ALPHA, /* U+00ae9e: */
+ RTUNI_ALPHA, /* U+00ae9f: */
+ RTUNI_ALPHA, /* U+00aea0: */
+ RTUNI_ALPHA, /* U+00aea1: */
+ RTUNI_ALPHA, /* U+00aea2: */
+ RTUNI_ALPHA, /* U+00aea3: */
+ RTUNI_ALPHA, /* U+00aea4: */
+ RTUNI_ALPHA, /* U+00aea5: */
+ RTUNI_ALPHA, /* U+00aea6: */
+ RTUNI_ALPHA, /* U+00aea7: */
+ RTUNI_ALPHA, /* U+00aea8: */
+ RTUNI_ALPHA, /* U+00aea9: */
+ RTUNI_ALPHA, /* U+00aeaa: */
+ RTUNI_ALPHA, /* U+00aeab: */
+ RTUNI_ALPHA, /* U+00aeac: */
+ RTUNI_ALPHA, /* U+00aead: */
+ RTUNI_ALPHA, /* U+00aeae: */
+ RTUNI_ALPHA, /* U+00aeaf: */
+ RTUNI_ALPHA, /* U+00aeb0: */
+ RTUNI_ALPHA, /* U+00aeb1: */
+ RTUNI_ALPHA, /* U+00aeb2: */
+ RTUNI_ALPHA, /* U+00aeb3: */
+ RTUNI_ALPHA, /* U+00aeb4: */
+ RTUNI_ALPHA, /* U+00aeb5: */
+ RTUNI_ALPHA, /* U+00aeb6: */
+ RTUNI_ALPHA, /* U+00aeb7: */
+ RTUNI_ALPHA, /* U+00aeb8: */
+ RTUNI_ALPHA, /* U+00aeb9: */
+ RTUNI_ALPHA, /* U+00aeba: */
+ RTUNI_ALPHA, /* U+00aebb: */
+ RTUNI_ALPHA, /* U+00aebc: */
+ RTUNI_ALPHA, /* U+00aebd: */
+ RTUNI_ALPHA, /* U+00aebe: */
+ RTUNI_ALPHA, /* U+00aebf: */
+ RTUNI_ALPHA, /* U+00aec0: */
+ RTUNI_ALPHA, /* U+00aec1: */
+ RTUNI_ALPHA, /* U+00aec2: */
+ RTUNI_ALPHA, /* U+00aec3: */
+ RTUNI_ALPHA, /* U+00aec4: */
+ RTUNI_ALPHA, /* U+00aec5: */
+ RTUNI_ALPHA, /* U+00aec6: */
+ RTUNI_ALPHA, /* U+00aec7: */
+ RTUNI_ALPHA, /* U+00aec8: */
+ RTUNI_ALPHA, /* U+00aec9: */
+ RTUNI_ALPHA, /* U+00aeca: */
+ RTUNI_ALPHA, /* U+00aecb: */
+ RTUNI_ALPHA, /* U+00aecc: */
+ RTUNI_ALPHA, /* U+00aecd: */
+ RTUNI_ALPHA, /* U+00aece: */
+ RTUNI_ALPHA, /* U+00aecf: */
+ RTUNI_ALPHA, /* U+00aed0: */
+ RTUNI_ALPHA, /* U+00aed1: */
+ RTUNI_ALPHA, /* U+00aed2: */
+ RTUNI_ALPHA, /* U+00aed3: */
+ RTUNI_ALPHA, /* U+00aed4: */
+ RTUNI_ALPHA, /* U+00aed5: */
+ RTUNI_ALPHA, /* U+00aed6: */
+ RTUNI_ALPHA, /* U+00aed7: */
+ RTUNI_ALPHA, /* U+00aed8: */
+ RTUNI_ALPHA, /* U+00aed9: */
+ RTUNI_ALPHA, /* U+00aeda: */
+ RTUNI_ALPHA, /* U+00aedb: */
+ RTUNI_ALPHA, /* U+00aedc: */
+ RTUNI_ALPHA, /* U+00aedd: */
+ RTUNI_ALPHA, /* U+00aede: */
+ RTUNI_ALPHA, /* U+00aedf: */
+ RTUNI_ALPHA, /* U+00aee0: */
+ RTUNI_ALPHA, /* U+00aee1: */
+ RTUNI_ALPHA, /* U+00aee2: */
+ RTUNI_ALPHA, /* U+00aee3: */
+ RTUNI_ALPHA, /* U+00aee4: */
+ RTUNI_ALPHA, /* U+00aee5: */
+ RTUNI_ALPHA, /* U+00aee6: */
+ RTUNI_ALPHA, /* U+00aee7: */
+ RTUNI_ALPHA, /* U+00aee8: */
+ RTUNI_ALPHA, /* U+00aee9: */
+ RTUNI_ALPHA, /* U+00aeea: */
+ RTUNI_ALPHA, /* U+00aeeb: */
+ RTUNI_ALPHA, /* U+00aeec: */
+ RTUNI_ALPHA, /* U+00aeed: */
+ RTUNI_ALPHA, /* U+00aeee: */
+ RTUNI_ALPHA, /* U+00aeef: */
+ RTUNI_ALPHA, /* U+00aef0: */
+ RTUNI_ALPHA, /* U+00aef1: */
+ RTUNI_ALPHA, /* U+00aef2: */
+ RTUNI_ALPHA, /* U+00aef3: */
+ RTUNI_ALPHA, /* U+00aef4: */
+ RTUNI_ALPHA, /* U+00aef5: */
+ RTUNI_ALPHA, /* U+00aef6: */
+ RTUNI_ALPHA, /* U+00aef7: */
+ RTUNI_ALPHA, /* U+00aef8: */
+ RTUNI_ALPHA, /* U+00aef9: */
+ RTUNI_ALPHA, /* U+00aefa: */
+ RTUNI_ALPHA, /* U+00aefb: */
+ RTUNI_ALPHA, /* U+00aefc: */
+ RTUNI_ALPHA, /* U+00aefd: */
+ RTUNI_ALPHA, /* U+00aefe: */
+ RTUNI_ALPHA, /* U+00aeff: */
+ RTUNI_ALPHA, /* U+00af00: */
+ RTUNI_ALPHA, /* U+00af01: */
+ RTUNI_ALPHA, /* U+00af02: */
+ RTUNI_ALPHA, /* U+00af03: */
+ RTUNI_ALPHA, /* U+00af04: */
+ RTUNI_ALPHA, /* U+00af05: */
+ RTUNI_ALPHA, /* U+00af06: */
+ RTUNI_ALPHA, /* U+00af07: */
+ RTUNI_ALPHA, /* U+00af08: */
+ RTUNI_ALPHA, /* U+00af09: */
+ RTUNI_ALPHA, /* U+00af0a: */
+ RTUNI_ALPHA, /* U+00af0b: */
+ RTUNI_ALPHA, /* U+00af0c: */
+ RTUNI_ALPHA, /* U+00af0d: */
+ RTUNI_ALPHA, /* U+00af0e: */
+ RTUNI_ALPHA, /* U+00af0f: */
+ RTUNI_ALPHA, /* U+00af10: */
+ RTUNI_ALPHA, /* U+00af11: */
+ RTUNI_ALPHA, /* U+00af12: */
+ RTUNI_ALPHA, /* U+00af13: */
+ RTUNI_ALPHA, /* U+00af14: */
+ RTUNI_ALPHA, /* U+00af15: */
+ RTUNI_ALPHA, /* U+00af16: */
+ RTUNI_ALPHA, /* U+00af17: */
+ RTUNI_ALPHA, /* U+00af18: */
+ RTUNI_ALPHA, /* U+00af19: */
+ RTUNI_ALPHA, /* U+00af1a: */
+ RTUNI_ALPHA, /* U+00af1b: */
+ RTUNI_ALPHA, /* U+00af1c: */
+ RTUNI_ALPHA, /* U+00af1d: */
+ RTUNI_ALPHA, /* U+00af1e: */
+ RTUNI_ALPHA, /* U+00af1f: */
+ RTUNI_ALPHA, /* U+00af20: */
+ RTUNI_ALPHA, /* U+00af21: */
+ RTUNI_ALPHA, /* U+00af22: */
+ RTUNI_ALPHA, /* U+00af23: */
+ RTUNI_ALPHA, /* U+00af24: */
+ RTUNI_ALPHA, /* U+00af25: */
+ RTUNI_ALPHA, /* U+00af26: */
+ RTUNI_ALPHA, /* U+00af27: */
+ RTUNI_ALPHA, /* U+00af28: */
+ RTUNI_ALPHA, /* U+00af29: */
+ RTUNI_ALPHA, /* U+00af2a: */
+ RTUNI_ALPHA, /* U+00af2b: */
+ RTUNI_ALPHA, /* U+00af2c: */
+ RTUNI_ALPHA, /* U+00af2d: */
+ RTUNI_ALPHA, /* U+00af2e: */
+ RTUNI_ALPHA, /* U+00af2f: */
+ RTUNI_ALPHA, /* U+00af30: */
+ RTUNI_ALPHA, /* U+00af31: */
+ RTUNI_ALPHA, /* U+00af32: */
+ RTUNI_ALPHA, /* U+00af33: */
+ RTUNI_ALPHA, /* U+00af34: */
+ RTUNI_ALPHA, /* U+00af35: */
+ RTUNI_ALPHA, /* U+00af36: */
+ RTUNI_ALPHA, /* U+00af37: */
+ RTUNI_ALPHA, /* U+00af38: */
+ RTUNI_ALPHA, /* U+00af39: */
+ RTUNI_ALPHA, /* U+00af3a: */
+ RTUNI_ALPHA, /* U+00af3b: */
+ RTUNI_ALPHA, /* U+00af3c: */
+ RTUNI_ALPHA, /* U+00af3d: */
+ RTUNI_ALPHA, /* U+00af3e: */
+ RTUNI_ALPHA, /* U+00af3f: */
+ RTUNI_ALPHA, /* U+00af40: */
+ RTUNI_ALPHA, /* U+00af41: */
+ RTUNI_ALPHA, /* U+00af42: */
+ RTUNI_ALPHA, /* U+00af43: */
+ RTUNI_ALPHA, /* U+00af44: */
+ RTUNI_ALPHA, /* U+00af45: */
+ RTUNI_ALPHA, /* U+00af46: */
+ RTUNI_ALPHA, /* U+00af47: */
+ RTUNI_ALPHA, /* U+00af48: */
+ RTUNI_ALPHA, /* U+00af49: */
+ RTUNI_ALPHA, /* U+00af4a: */
+ RTUNI_ALPHA, /* U+00af4b: */
+ RTUNI_ALPHA, /* U+00af4c: */
+ RTUNI_ALPHA, /* U+00af4d: */
+ RTUNI_ALPHA, /* U+00af4e: */
+ RTUNI_ALPHA, /* U+00af4f: */
+ RTUNI_ALPHA, /* U+00af50: */
+ RTUNI_ALPHA, /* U+00af51: */
+ RTUNI_ALPHA, /* U+00af52: */
+ RTUNI_ALPHA, /* U+00af53: */
+ RTUNI_ALPHA, /* U+00af54: */
+ RTUNI_ALPHA, /* U+00af55: */
+ RTUNI_ALPHA, /* U+00af56: */
+ RTUNI_ALPHA, /* U+00af57: */
+ RTUNI_ALPHA, /* U+00af58: */
+ RTUNI_ALPHA, /* U+00af59: */
+ RTUNI_ALPHA, /* U+00af5a: */
+ RTUNI_ALPHA, /* U+00af5b: */
+ RTUNI_ALPHA, /* U+00af5c: */
+ RTUNI_ALPHA, /* U+00af5d: */
+ RTUNI_ALPHA, /* U+00af5e: */
+ RTUNI_ALPHA, /* U+00af5f: */
+ RTUNI_ALPHA, /* U+00af60: */
+ RTUNI_ALPHA, /* U+00af61: */
+ RTUNI_ALPHA, /* U+00af62: */
+ RTUNI_ALPHA, /* U+00af63: */
+ RTUNI_ALPHA, /* U+00af64: */
+ RTUNI_ALPHA, /* U+00af65: */
+ RTUNI_ALPHA, /* U+00af66: */
+ RTUNI_ALPHA, /* U+00af67: */
+ RTUNI_ALPHA, /* U+00af68: */
+ RTUNI_ALPHA, /* U+00af69: */
+ RTUNI_ALPHA, /* U+00af6a: */
+ RTUNI_ALPHA, /* U+00af6b: */
+ RTUNI_ALPHA, /* U+00af6c: */
+ RTUNI_ALPHA, /* U+00af6d: */
+ RTUNI_ALPHA, /* U+00af6e: */
+ RTUNI_ALPHA, /* U+00af6f: */
+ RTUNI_ALPHA, /* U+00af70: */
+ RTUNI_ALPHA, /* U+00af71: */
+ RTUNI_ALPHA, /* U+00af72: */
+ RTUNI_ALPHA, /* U+00af73: */
+ RTUNI_ALPHA, /* U+00af74: */
+ RTUNI_ALPHA, /* U+00af75: */
+ RTUNI_ALPHA, /* U+00af76: */
+ RTUNI_ALPHA, /* U+00af77: */
+ RTUNI_ALPHA, /* U+00af78: */
+ RTUNI_ALPHA, /* U+00af79: */
+ RTUNI_ALPHA, /* U+00af7a: */
+ RTUNI_ALPHA, /* U+00af7b: */
+ RTUNI_ALPHA, /* U+00af7c: */
+ RTUNI_ALPHA, /* U+00af7d: */
+ RTUNI_ALPHA, /* U+00af7e: */
+ RTUNI_ALPHA, /* U+00af7f: */
+ RTUNI_ALPHA, /* U+00af80: */
+ RTUNI_ALPHA, /* U+00af81: */
+ RTUNI_ALPHA, /* U+00af82: */
+ RTUNI_ALPHA, /* U+00af83: */
+ RTUNI_ALPHA, /* U+00af84: */
+ RTUNI_ALPHA, /* U+00af85: */
+ RTUNI_ALPHA, /* U+00af86: */
+ RTUNI_ALPHA, /* U+00af87: */
+ RTUNI_ALPHA, /* U+00af88: */
+ RTUNI_ALPHA, /* U+00af89: */
+ RTUNI_ALPHA, /* U+00af8a: */
+ RTUNI_ALPHA, /* U+00af8b: */
+ RTUNI_ALPHA, /* U+00af8c: */
+ RTUNI_ALPHA, /* U+00af8d: */
+ RTUNI_ALPHA, /* U+00af8e: */
+ RTUNI_ALPHA, /* U+00af8f: */
+ RTUNI_ALPHA, /* U+00af90: */
+ RTUNI_ALPHA, /* U+00af91: */
+ RTUNI_ALPHA, /* U+00af92: */
+ RTUNI_ALPHA, /* U+00af93: */
+ RTUNI_ALPHA, /* U+00af94: */
+ RTUNI_ALPHA, /* U+00af95: */
+ RTUNI_ALPHA, /* U+00af96: */
+ RTUNI_ALPHA, /* U+00af97: */
+ RTUNI_ALPHA, /* U+00af98: */
+ RTUNI_ALPHA, /* U+00af99: */
+ RTUNI_ALPHA, /* U+00af9a: */
+ RTUNI_ALPHA, /* U+00af9b: */
+ RTUNI_ALPHA, /* U+00af9c: */
+ RTUNI_ALPHA, /* U+00af9d: */
+ RTUNI_ALPHA, /* U+00af9e: */
+ RTUNI_ALPHA, /* U+00af9f: */
+ RTUNI_ALPHA, /* U+00afa0: */
+ RTUNI_ALPHA, /* U+00afa1: */
+ RTUNI_ALPHA, /* U+00afa2: */
+ RTUNI_ALPHA, /* U+00afa3: */
+ RTUNI_ALPHA, /* U+00afa4: */
+ RTUNI_ALPHA, /* U+00afa5: */
+ RTUNI_ALPHA, /* U+00afa6: */
+ RTUNI_ALPHA, /* U+00afa7: */
+ RTUNI_ALPHA, /* U+00afa8: */
+ RTUNI_ALPHA, /* U+00afa9: */
+ RTUNI_ALPHA, /* U+00afaa: */
+ RTUNI_ALPHA, /* U+00afab: */
+ RTUNI_ALPHA, /* U+00afac: */
+ RTUNI_ALPHA, /* U+00afad: */
+ RTUNI_ALPHA, /* U+00afae: */
+ RTUNI_ALPHA, /* U+00afaf: */
+ RTUNI_ALPHA, /* U+00afb0: */
+ RTUNI_ALPHA, /* U+00afb1: */
+ RTUNI_ALPHA, /* U+00afb2: */
+ RTUNI_ALPHA, /* U+00afb3: */
+ RTUNI_ALPHA, /* U+00afb4: */
+ RTUNI_ALPHA, /* U+00afb5: */
+ RTUNI_ALPHA, /* U+00afb6: */
+ RTUNI_ALPHA, /* U+00afb7: */
+ RTUNI_ALPHA, /* U+00afb8: */
+ RTUNI_ALPHA, /* U+00afb9: */
+ RTUNI_ALPHA, /* U+00afba: */
+ RTUNI_ALPHA, /* U+00afbb: */
+ RTUNI_ALPHA, /* U+00afbc: */
+ RTUNI_ALPHA, /* U+00afbd: */
+ RTUNI_ALPHA, /* U+00afbe: */
+ RTUNI_ALPHA, /* U+00afbf: */
+ RTUNI_ALPHA, /* U+00afc0: */
+ RTUNI_ALPHA, /* U+00afc1: */
+ RTUNI_ALPHA, /* U+00afc2: */
+ RTUNI_ALPHA, /* U+00afc3: */
+ RTUNI_ALPHA, /* U+00afc4: */
+ RTUNI_ALPHA, /* U+00afc5: */
+ RTUNI_ALPHA, /* U+00afc6: */
+ RTUNI_ALPHA, /* U+00afc7: */
+ RTUNI_ALPHA, /* U+00afc8: */
+ RTUNI_ALPHA, /* U+00afc9: */
+ RTUNI_ALPHA, /* U+00afca: */
+ RTUNI_ALPHA, /* U+00afcb: */
+ RTUNI_ALPHA, /* U+00afcc: */
+ RTUNI_ALPHA, /* U+00afcd: */
+ RTUNI_ALPHA, /* U+00afce: */
+ RTUNI_ALPHA, /* U+00afcf: */
+ RTUNI_ALPHA, /* U+00afd0: */
+ RTUNI_ALPHA, /* U+00afd1: */
+ RTUNI_ALPHA, /* U+00afd2: */
+ RTUNI_ALPHA, /* U+00afd3: */
+ RTUNI_ALPHA, /* U+00afd4: */
+ RTUNI_ALPHA, /* U+00afd5: */
+ RTUNI_ALPHA, /* U+00afd6: */
+ RTUNI_ALPHA, /* U+00afd7: */
+ RTUNI_ALPHA, /* U+00afd8: */
+ RTUNI_ALPHA, /* U+00afd9: */
+ RTUNI_ALPHA, /* U+00afda: */
+ RTUNI_ALPHA, /* U+00afdb: */
+ RTUNI_ALPHA, /* U+00afdc: */
+ RTUNI_ALPHA, /* U+00afdd: */
+ RTUNI_ALPHA, /* U+00afde: */
+ RTUNI_ALPHA, /* U+00afdf: */
+ RTUNI_ALPHA, /* U+00afe0: */
+ RTUNI_ALPHA, /* U+00afe1: */
+ RTUNI_ALPHA, /* U+00afe2: */
+ RTUNI_ALPHA, /* U+00afe3: */
+ RTUNI_ALPHA, /* U+00afe4: */
+ RTUNI_ALPHA, /* U+00afe5: */
+ RTUNI_ALPHA, /* U+00afe6: */
+ RTUNI_ALPHA, /* U+00afe7: */
+ RTUNI_ALPHA, /* U+00afe8: */
+ RTUNI_ALPHA, /* U+00afe9: */
+ RTUNI_ALPHA, /* U+00afea: */
+ RTUNI_ALPHA, /* U+00afeb: */
+ RTUNI_ALPHA, /* U+00afec: */
+ RTUNI_ALPHA, /* U+00afed: */
+ RTUNI_ALPHA, /* U+00afee: */
+ RTUNI_ALPHA, /* U+00afef: */
+ RTUNI_ALPHA, /* U+00aff0: */
+ RTUNI_ALPHA, /* U+00aff1: */
+ RTUNI_ALPHA, /* U+00aff2: */
+ RTUNI_ALPHA, /* U+00aff3: */
+ RTUNI_ALPHA, /* U+00aff4: */
+ RTUNI_ALPHA, /* U+00aff5: */
+ RTUNI_ALPHA, /* U+00aff6: */
+ RTUNI_ALPHA, /* U+00aff7: */
+ RTUNI_ALPHA, /* U+00aff8: */
+ RTUNI_ALPHA, /* U+00aff9: */
+ RTUNI_ALPHA, /* U+00affa: */
+ RTUNI_ALPHA, /* U+00affb: */
+ RTUNI_ALPHA, /* U+00affc: */
+ RTUNI_ALPHA, /* U+00affd: */
+ RTUNI_ALPHA, /* U+00affe: */
+ RTUNI_ALPHA, /* U+00afff: */
+ RTUNI_ALPHA, /* U+00b000: */
+ RTUNI_ALPHA, /* U+00b001: */
+ RTUNI_ALPHA, /* U+00b002: */
+ RTUNI_ALPHA, /* U+00b003: */
+ RTUNI_ALPHA, /* U+00b004: */
+ RTUNI_ALPHA, /* U+00b005: */
+ RTUNI_ALPHA, /* U+00b006: */
+ RTUNI_ALPHA, /* U+00b007: */
+ RTUNI_ALPHA, /* U+00b008: */
+ RTUNI_ALPHA, /* U+00b009: */
+ RTUNI_ALPHA, /* U+00b00a: */
+ RTUNI_ALPHA, /* U+00b00b: */
+ RTUNI_ALPHA, /* U+00b00c: */
+ RTUNI_ALPHA, /* U+00b00d: */
+ RTUNI_ALPHA, /* U+00b00e: */
+ RTUNI_ALPHA, /* U+00b00f: */
+ RTUNI_ALPHA, /* U+00b010: */
+ RTUNI_ALPHA, /* U+00b011: */
+ RTUNI_ALPHA, /* U+00b012: */
+ RTUNI_ALPHA, /* U+00b013: */
+ RTUNI_ALPHA, /* U+00b014: */
+ RTUNI_ALPHA, /* U+00b015: */
+ RTUNI_ALPHA, /* U+00b016: */
+ RTUNI_ALPHA, /* U+00b017: */
+ RTUNI_ALPHA, /* U+00b018: */
+ RTUNI_ALPHA, /* U+00b019: */
+ RTUNI_ALPHA, /* U+00b01a: */
+ RTUNI_ALPHA, /* U+00b01b: */
+ RTUNI_ALPHA, /* U+00b01c: */
+ RTUNI_ALPHA, /* U+00b01d: */
+ RTUNI_ALPHA, /* U+00b01e: */
+ RTUNI_ALPHA, /* U+00b01f: */
+ RTUNI_ALPHA, /* U+00b020: */
+ RTUNI_ALPHA, /* U+00b021: */
+ RTUNI_ALPHA, /* U+00b022: */
+ RTUNI_ALPHA, /* U+00b023: */
+ RTUNI_ALPHA, /* U+00b024: */
+ RTUNI_ALPHA, /* U+00b025: */
+ RTUNI_ALPHA, /* U+00b026: */
+ RTUNI_ALPHA, /* U+00b027: */
+ RTUNI_ALPHA, /* U+00b028: */
+ RTUNI_ALPHA, /* U+00b029: */
+ RTUNI_ALPHA, /* U+00b02a: */
+ RTUNI_ALPHA, /* U+00b02b: */
+ RTUNI_ALPHA, /* U+00b02c: */
+ RTUNI_ALPHA, /* U+00b02d: */
+ RTUNI_ALPHA, /* U+00b02e: */
+ RTUNI_ALPHA, /* U+00b02f: */
+ RTUNI_ALPHA, /* U+00b030: */
+ RTUNI_ALPHA, /* U+00b031: */
+ RTUNI_ALPHA, /* U+00b032: */
+ RTUNI_ALPHA, /* U+00b033: */
+ RTUNI_ALPHA, /* U+00b034: */
+ RTUNI_ALPHA, /* U+00b035: */
+ RTUNI_ALPHA, /* U+00b036: */
+ RTUNI_ALPHA, /* U+00b037: */
+ RTUNI_ALPHA, /* U+00b038: */
+ RTUNI_ALPHA, /* U+00b039: */
+ RTUNI_ALPHA, /* U+00b03a: */
+ RTUNI_ALPHA, /* U+00b03b: */
+ RTUNI_ALPHA, /* U+00b03c: */
+ RTUNI_ALPHA, /* U+00b03d: */
+ RTUNI_ALPHA, /* U+00b03e: */
+ RTUNI_ALPHA, /* U+00b03f: */
+ RTUNI_ALPHA, /* U+00b040: */
+ RTUNI_ALPHA, /* U+00b041: */
+ RTUNI_ALPHA, /* U+00b042: */
+ RTUNI_ALPHA, /* U+00b043: */
+ RTUNI_ALPHA, /* U+00b044: */
+ RTUNI_ALPHA, /* U+00b045: */
+ RTUNI_ALPHA, /* U+00b046: */
+ RTUNI_ALPHA, /* U+00b047: */
+ RTUNI_ALPHA, /* U+00b048: */
+ RTUNI_ALPHA, /* U+00b049: */
+ RTUNI_ALPHA, /* U+00b04a: */
+ RTUNI_ALPHA, /* U+00b04b: */
+ RTUNI_ALPHA, /* U+00b04c: */
+ RTUNI_ALPHA, /* U+00b04d: */
+ RTUNI_ALPHA, /* U+00b04e: */
+ RTUNI_ALPHA, /* U+00b04f: */
+ RTUNI_ALPHA, /* U+00b050: */
+ RTUNI_ALPHA, /* U+00b051: */
+ RTUNI_ALPHA, /* U+00b052: */
+ RTUNI_ALPHA, /* U+00b053: */
+ RTUNI_ALPHA, /* U+00b054: */
+ RTUNI_ALPHA, /* U+00b055: */
+ RTUNI_ALPHA, /* U+00b056: */
+ RTUNI_ALPHA, /* U+00b057: */
+ RTUNI_ALPHA, /* U+00b058: */
+ RTUNI_ALPHA, /* U+00b059: */
+ RTUNI_ALPHA, /* U+00b05a: */
+ RTUNI_ALPHA, /* U+00b05b: */
+ RTUNI_ALPHA, /* U+00b05c: */
+ RTUNI_ALPHA, /* U+00b05d: */
+ RTUNI_ALPHA, /* U+00b05e: */
+ RTUNI_ALPHA, /* U+00b05f: */
+ RTUNI_ALPHA, /* U+00b060: */
+ RTUNI_ALPHA, /* U+00b061: */
+ RTUNI_ALPHA, /* U+00b062: */
+ RTUNI_ALPHA, /* U+00b063: */
+ RTUNI_ALPHA, /* U+00b064: */
+ RTUNI_ALPHA, /* U+00b065: */
+ RTUNI_ALPHA, /* U+00b066: */
+ RTUNI_ALPHA, /* U+00b067: */
+ RTUNI_ALPHA, /* U+00b068: */
+ RTUNI_ALPHA, /* U+00b069: */
+ RTUNI_ALPHA, /* U+00b06a: */
+ RTUNI_ALPHA, /* U+00b06b: */
+ RTUNI_ALPHA, /* U+00b06c: */
+ RTUNI_ALPHA, /* U+00b06d: */
+ RTUNI_ALPHA, /* U+00b06e: */
+ RTUNI_ALPHA, /* U+00b06f: */
+ RTUNI_ALPHA, /* U+00b070: */
+ RTUNI_ALPHA, /* U+00b071: */
+ RTUNI_ALPHA, /* U+00b072: */
+ RTUNI_ALPHA, /* U+00b073: */
+ RTUNI_ALPHA, /* U+00b074: */
+ RTUNI_ALPHA, /* U+00b075: */
+ RTUNI_ALPHA, /* U+00b076: */
+ RTUNI_ALPHA, /* U+00b077: */
+ RTUNI_ALPHA, /* U+00b078: */
+ RTUNI_ALPHA, /* U+00b079: */
+ RTUNI_ALPHA, /* U+00b07a: */
+ RTUNI_ALPHA, /* U+00b07b: */
+ RTUNI_ALPHA, /* U+00b07c: */
+ RTUNI_ALPHA, /* U+00b07d: */
+ RTUNI_ALPHA, /* U+00b07e: */
+ RTUNI_ALPHA, /* U+00b07f: */
+ RTUNI_ALPHA, /* U+00b080: */
+ RTUNI_ALPHA, /* U+00b081: */
+ RTUNI_ALPHA, /* U+00b082: */
+ RTUNI_ALPHA, /* U+00b083: */
+ RTUNI_ALPHA, /* U+00b084: */
+ RTUNI_ALPHA, /* U+00b085: */
+ RTUNI_ALPHA, /* U+00b086: */
+ RTUNI_ALPHA, /* U+00b087: */
+ RTUNI_ALPHA, /* U+00b088: */
+ RTUNI_ALPHA, /* U+00b089: */
+ RTUNI_ALPHA, /* U+00b08a: */
+ RTUNI_ALPHA, /* U+00b08b: */
+ RTUNI_ALPHA, /* U+00b08c: */
+ RTUNI_ALPHA, /* U+00b08d: */
+ RTUNI_ALPHA, /* U+00b08e: */
+ RTUNI_ALPHA, /* U+00b08f: */
+ RTUNI_ALPHA, /* U+00b090: */
+ RTUNI_ALPHA, /* U+00b091: */
+ RTUNI_ALPHA, /* U+00b092: */
+ RTUNI_ALPHA, /* U+00b093: */
+ RTUNI_ALPHA, /* U+00b094: */
+ RTUNI_ALPHA, /* U+00b095: */
+ RTUNI_ALPHA, /* U+00b096: */
+ RTUNI_ALPHA, /* U+00b097: */
+ RTUNI_ALPHA, /* U+00b098: */
+ RTUNI_ALPHA, /* U+00b099: */
+ RTUNI_ALPHA, /* U+00b09a: */
+ RTUNI_ALPHA, /* U+00b09b: */
+ RTUNI_ALPHA, /* U+00b09c: */
+ RTUNI_ALPHA, /* U+00b09d: */
+ RTUNI_ALPHA, /* U+00b09e: */
+ RTUNI_ALPHA, /* U+00b09f: */
+ RTUNI_ALPHA, /* U+00b0a0: */
+ RTUNI_ALPHA, /* U+00b0a1: */
+ RTUNI_ALPHA, /* U+00b0a2: */
+ RTUNI_ALPHA, /* U+00b0a3: */
+ RTUNI_ALPHA, /* U+00b0a4: */
+ RTUNI_ALPHA, /* U+00b0a5: */
+ RTUNI_ALPHA, /* U+00b0a6: */
+ RTUNI_ALPHA, /* U+00b0a7: */
+ RTUNI_ALPHA, /* U+00b0a8: */
+ RTUNI_ALPHA, /* U+00b0a9: */
+ RTUNI_ALPHA, /* U+00b0aa: */
+ RTUNI_ALPHA, /* U+00b0ab: */
+ RTUNI_ALPHA, /* U+00b0ac: */
+ RTUNI_ALPHA, /* U+00b0ad: */
+ RTUNI_ALPHA, /* U+00b0ae: */
+ RTUNI_ALPHA, /* U+00b0af: */
+ RTUNI_ALPHA, /* U+00b0b0: */
+ RTUNI_ALPHA, /* U+00b0b1: */
+ RTUNI_ALPHA, /* U+00b0b2: */
+ RTUNI_ALPHA, /* U+00b0b3: */
+ RTUNI_ALPHA, /* U+00b0b4: */
+ RTUNI_ALPHA, /* U+00b0b5: */
+ RTUNI_ALPHA, /* U+00b0b6: */
+ RTUNI_ALPHA, /* U+00b0b7: */
+ RTUNI_ALPHA, /* U+00b0b8: */
+ RTUNI_ALPHA, /* U+00b0b9: */
+ RTUNI_ALPHA, /* U+00b0ba: */
+ RTUNI_ALPHA, /* U+00b0bb: */
+ RTUNI_ALPHA, /* U+00b0bc: */
+ RTUNI_ALPHA, /* U+00b0bd: */
+ RTUNI_ALPHA, /* U+00b0be: */
+ RTUNI_ALPHA, /* U+00b0bf: */
+ RTUNI_ALPHA, /* U+00b0c0: */
+ RTUNI_ALPHA, /* U+00b0c1: */
+ RTUNI_ALPHA, /* U+00b0c2: */
+ RTUNI_ALPHA, /* U+00b0c3: */
+ RTUNI_ALPHA, /* U+00b0c4: */
+ RTUNI_ALPHA, /* U+00b0c5: */
+ RTUNI_ALPHA, /* U+00b0c6: */
+ RTUNI_ALPHA, /* U+00b0c7: */
+ RTUNI_ALPHA, /* U+00b0c8: */
+ RTUNI_ALPHA, /* U+00b0c9: */
+ RTUNI_ALPHA, /* U+00b0ca: */
+ RTUNI_ALPHA, /* U+00b0cb: */
+ RTUNI_ALPHA, /* U+00b0cc: */
+ RTUNI_ALPHA, /* U+00b0cd: */
+ RTUNI_ALPHA, /* U+00b0ce: */
+ RTUNI_ALPHA, /* U+00b0cf: */
+ RTUNI_ALPHA, /* U+00b0d0: */
+ RTUNI_ALPHA, /* U+00b0d1: */
+ RTUNI_ALPHA, /* U+00b0d2: */
+ RTUNI_ALPHA, /* U+00b0d3: */
+ RTUNI_ALPHA, /* U+00b0d4: */
+ RTUNI_ALPHA, /* U+00b0d5: */
+ RTUNI_ALPHA, /* U+00b0d6: */
+ RTUNI_ALPHA, /* U+00b0d7: */
+ RTUNI_ALPHA, /* U+00b0d8: */
+ RTUNI_ALPHA, /* U+00b0d9: */
+ RTUNI_ALPHA, /* U+00b0da: */
+ RTUNI_ALPHA, /* U+00b0db: */
+ RTUNI_ALPHA, /* U+00b0dc: */
+ RTUNI_ALPHA, /* U+00b0dd: */
+ RTUNI_ALPHA, /* U+00b0de: */
+ RTUNI_ALPHA, /* U+00b0df: */
+ RTUNI_ALPHA, /* U+00b0e0: */
+ RTUNI_ALPHA, /* U+00b0e1: */
+ RTUNI_ALPHA, /* U+00b0e2: */
+ RTUNI_ALPHA, /* U+00b0e3: */
+ RTUNI_ALPHA, /* U+00b0e4: */
+ RTUNI_ALPHA, /* U+00b0e5: */
+ RTUNI_ALPHA, /* U+00b0e6: */
+ RTUNI_ALPHA, /* U+00b0e7: */
+ RTUNI_ALPHA, /* U+00b0e8: */
+ RTUNI_ALPHA, /* U+00b0e9: */
+ RTUNI_ALPHA, /* U+00b0ea: */
+ RTUNI_ALPHA, /* U+00b0eb: */
+ RTUNI_ALPHA, /* U+00b0ec: */
+ RTUNI_ALPHA, /* U+00b0ed: */
+ RTUNI_ALPHA, /* U+00b0ee: */
+ RTUNI_ALPHA, /* U+00b0ef: */
+ RTUNI_ALPHA, /* U+00b0f0: */
+ RTUNI_ALPHA, /* U+00b0f1: */
+ RTUNI_ALPHA, /* U+00b0f2: */
+ RTUNI_ALPHA, /* U+00b0f3: */
+ RTUNI_ALPHA, /* U+00b0f4: */
+ RTUNI_ALPHA, /* U+00b0f5: */
+ RTUNI_ALPHA, /* U+00b0f6: */
+ RTUNI_ALPHA, /* U+00b0f7: */
+ RTUNI_ALPHA, /* U+00b0f8: */
+ RTUNI_ALPHA, /* U+00b0f9: */
+ RTUNI_ALPHA, /* U+00b0fa: */
+ RTUNI_ALPHA, /* U+00b0fb: */
+ RTUNI_ALPHA, /* U+00b0fc: */
+ RTUNI_ALPHA, /* U+00b0fd: */
+ RTUNI_ALPHA, /* U+00b0fe: */
+ RTUNI_ALPHA, /* U+00b0ff: */
+ RTUNI_ALPHA, /* U+00b100: */
+ RTUNI_ALPHA, /* U+00b101: */
+ RTUNI_ALPHA, /* U+00b102: */
+ RTUNI_ALPHA, /* U+00b103: */
+ RTUNI_ALPHA, /* U+00b104: */
+ RTUNI_ALPHA, /* U+00b105: */
+ RTUNI_ALPHA, /* U+00b106: */
+ RTUNI_ALPHA, /* U+00b107: */
+ RTUNI_ALPHA, /* U+00b108: */
+ RTUNI_ALPHA, /* U+00b109: */
+ RTUNI_ALPHA, /* U+00b10a: */
+ RTUNI_ALPHA, /* U+00b10b: */
+ RTUNI_ALPHA, /* U+00b10c: */
+ RTUNI_ALPHA, /* U+00b10d: */
+ RTUNI_ALPHA, /* U+00b10e: */
+ RTUNI_ALPHA, /* U+00b10f: */
+ RTUNI_ALPHA, /* U+00b110: */
+ RTUNI_ALPHA, /* U+00b111: */
+ RTUNI_ALPHA, /* U+00b112: */
+ RTUNI_ALPHA, /* U+00b113: */
+ RTUNI_ALPHA, /* U+00b114: */
+ RTUNI_ALPHA, /* U+00b115: */
+ RTUNI_ALPHA, /* U+00b116: */
+ RTUNI_ALPHA, /* U+00b117: */
+ RTUNI_ALPHA, /* U+00b118: */
+ RTUNI_ALPHA, /* U+00b119: */
+ RTUNI_ALPHA, /* U+00b11a: */
+ RTUNI_ALPHA, /* U+00b11b: */
+ RTUNI_ALPHA, /* U+00b11c: */
+ RTUNI_ALPHA, /* U+00b11d: */
+ RTUNI_ALPHA, /* U+00b11e: */
+ RTUNI_ALPHA, /* U+00b11f: */
+ RTUNI_ALPHA, /* U+00b120: */
+ RTUNI_ALPHA, /* U+00b121: */
+ RTUNI_ALPHA, /* U+00b122: */
+ RTUNI_ALPHA, /* U+00b123: */
+ RTUNI_ALPHA, /* U+00b124: */
+ RTUNI_ALPHA, /* U+00b125: */
+ RTUNI_ALPHA, /* U+00b126: */
+ RTUNI_ALPHA, /* U+00b127: */
+ RTUNI_ALPHA, /* U+00b128: */
+ RTUNI_ALPHA, /* U+00b129: */
+ RTUNI_ALPHA, /* U+00b12a: */
+ RTUNI_ALPHA, /* U+00b12b: */
+ RTUNI_ALPHA, /* U+00b12c: */
+ RTUNI_ALPHA, /* U+00b12d: */
+ RTUNI_ALPHA, /* U+00b12e: */
+ RTUNI_ALPHA, /* U+00b12f: */
+ RTUNI_ALPHA, /* U+00b130: */
+ RTUNI_ALPHA, /* U+00b131: */
+ RTUNI_ALPHA, /* U+00b132: */
+ RTUNI_ALPHA, /* U+00b133: */
+ RTUNI_ALPHA, /* U+00b134: */
+ RTUNI_ALPHA, /* U+00b135: */
+ RTUNI_ALPHA, /* U+00b136: */
+ RTUNI_ALPHA, /* U+00b137: */
+ RTUNI_ALPHA, /* U+00b138: */
+ RTUNI_ALPHA, /* U+00b139: */
+ RTUNI_ALPHA, /* U+00b13a: */
+ RTUNI_ALPHA, /* U+00b13b: */
+ RTUNI_ALPHA, /* U+00b13c: */
+ RTUNI_ALPHA, /* U+00b13d: */
+ RTUNI_ALPHA, /* U+00b13e: */
+ RTUNI_ALPHA, /* U+00b13f: */
+ RTUNI_ALPHA, /* U+00b140: */
+ RTUNI_ALPHA, /* U+00b141: */
+ RTUNI_ALPHA, /* U+00b142: */
+ RTUNI_ALPHA, /* U+00b143: */
+ RTUNI_ALPHA, /* U+00b144: */
+ RTUNI_ALPHA, /* U+00b145: */
+ RTUNI_ALPHA, /* U+00b146: */
+ RTUNI_ALPHA, /* U+00b147: */
+ RTUNI_ALPHA, /* U+00b148: */
+ RTUNI_ALPHA, /* U+00b149: */
+ RTUNI_ALPHA, /* U+00b14a: */
+ RTUNI_ALPHA, /* U+00b14b: */
+ RTUNI_ALPHA, /* U+00b14c: */
+ RTUNI_ALPHA, /* U+00b14d: */
+ RTUNI_ALPHA, /* U+00b14e: */
+ RTUNI_ALPHA, /* U+00b14f: */
+ RTUNI_ALPHA, /* U+00b150: */
+ RTUNI_ALPHA, /* U+00b151: */
+ RTUNI_ALPHA, /* U+00b152: */
+ RTUNI_ALPHA, /* U+00b153: */
+ RTUNI_ALPHA, /* U+00b154: */
+ RTUNI_ALPHA, /* U+00b155: */
+ RTUNI_ALPHA, /* U+00b156: */
+ RTUNI_ALPHA, /* U+00b157: */
+ RTUNI_ALPHA, /* U+00b158: */
+ RTUNI_ALPHA, /* U+00b159: */
+ RTUNI_ALPHA, /* U+00b15a: */
+ RTUNI_ALPHA, /* U+00b15b: */
+ RTUNI_ALPHA, /* U+00b15c: */
+ RTUNI_ALPHA, /* U+00b15d: */
+ RTUNI_ALPHA, /* U+00b15e: */
+ RTUNI_ALPHA, /* U+00b15f: */
+ RTUNI_ALPHA, /* U+00b160: */
+ RTUNI_ALPHA, /* U+00b161: */
+ RTUNI_ALPHA, /* U+00b162: */
+ RTUNI_ALPHA, /* U+00b163: */
+ RTUNI_ALPHA, /* U+00b164: */
+ RTUNI_ALPHA, /* U+00b165: */
+ RTUNI_ALPHA, /* U+00b166: */
+ RTUNI_ALPHA, /* U+00b167: */
+ RTUNI_ALPHA, /* U+00b168: */
+ RTUNI_ALPHA, /* U+00b169: */
+ RTUNI_ALPHA, /* U+00b16a: */
+ RTUNI_ALPHA, /* U+00b16b: */
+ RTUNI_ALPHA, /* U+00b16c: */
+ RTUNI_ALPHA, /* U+00b16d: */
+ RTUNI_ALPHA, /* U+00b16e: */
+ RTUNI_ALPHA, /* U+00b16f: */
+ RTUNI_ALPHA, /* U+00b170: */
+ RTUNI_ALPHA, /* U+00b171: */
+ RTUNI_ALPHA, /* U+00b172: */
+ RTUNI_ALPHA, /* U+00b173: */
+ RTUNI_ALPHA, /* U+00b174: */
+ RTUNI_ALPHA, /* U+00b175: */
+ RTUNI_ALPHA, /* U+00b176: */
+ RTUNI_ALPHA, /* U+00b177: */
+ RTUNI_ALPHA, /* U+00b178: */
+ RTUNI_ALPHA, /* U+00b179: */
+ RTUNI_ALPHA, /* U+00b17a: */
+ RTUNI_ALPHA, /* U+00b17b: */
+ RTUNI_ALPHA, /* U+00b17c: */
+ RTUNI_ALPHA, /* U+00b17d: */
+ RTUNI_ALPHA, /* U+00b17e: */
+ RTUNI_ALPHA, /* U+00b17f: */
+ RTUNI_ALPHA, /* U+00b180: */
+ RTUNI_ALPHA, /* U+00b181: */
+ RTUNI_ALPHA, /* U+00b182: */
+ RTUNI_ALPHA, /* U+00b183: */
+ RTUNI_ALPHA, /* U+00b184: */
+ RTUNI_ALPHA, /* U+00b185: */
+ RTUNI_ALPHA, /* U+00b186: */
+ RTUNI_ALPHA, /* U+00b187: */
+ RTUNI_ALPHA, /* U+00b188: */
+ RTUNI_ALPHA, /* U+00b189: */
+ RTUNI_ALPHA, /* U+00b18a: */
+ RTUNI_ALPHA, /* U+00b18b: */
+ RTUNI_ALPHA, /* U+00b18c: */
+ RTUNI_ALPHA, /* U+00b18d: */
+ RTUNI_ALPHA, /* U+00b18e: */
+ RTUNI_ALPHA, /* U+00b18f: */
+ RTUNI_ALPHA, /* U+00b190: */
+ RTUNI_ALPHA, /* U+00b191: */
+ RTUNI_ALPHA, /* U+00b192: */
+ RTUNI_ALPHA, /* U+00b193: */
+ RTUNI_ALPHA, /* U+00b194: */
+ RTUNI_ALPHA, /* U+00b195: */
+ RTUNI_ALPHA, /* U+00b196: */
+ RTUNI_ALPHA, /* U+00b197: */
+ RTUNI_ALPHA, /* U+00b198: */
+ RTUNI_ALPHA, /* U+00b199: */
+ RTUNI_ALPHA, /* U+00b19a: */
+ RTUNI_ALPHA, /* U+00b19b: */
+ RTUNI_ALPHA, /* U+00b19c: */
+ RTUNI_ALPHA, /* U+00b19d: */
+ RTUNI_ALPHA, /* U+00b19e: */
+ RTUNI_ALPHA, /* U+00b19f: */
+ RTUNI_ALPHA, /* U+00b1a0: */
+ RTUNI_ALPHA, /* U+00b1a1: */
+ RTUNI_ALPHA, /* U+00b1a2: */
+ RTUNI_ALPHA, /* U+00b1a3: */
+ RTUNI_ALPHA, /* U+00b1a4: */
+ RTUNI_ALPHA, /* U+00b1a5: */
+ RTUNI_ALPHA, /* U+00b1a6: */
+ RTUNI_ALPHA, /* U+00b1a7: */
+ RTUNI_ALPHA, /* U+00b1a8: */
+ RTUNI_ALPHA, /* U+00b1a9: */
+ RTUNI_ALPHA, /* U+00b1aa: */
+ RTUNI_ALPHA, /* U+00b1ab: */
+ RTUNI_ALPHA, /* U+00b1ac: */
+ RTUNI_ALPHA, /* U+00b1ad: */
+ RTUNI_ALPHA, /* U+00b1ae: */
+ RTUNI_ALPHA, /* U+00b1af: */
+ RTUNI_ALPHA, /* U+00b1b0: */
+ RTUNI_ALPHA, /* U+00b1b1: */
+ RTUNI_ALPHA, /* U+00b1b2: */
+ RTUNI_ALPHA, /* U+00b1b3: */
+ RTUNI_ALPHA, /* U+00b1b4: */
+ RTUNI_ALPHA, /* U+00b1b5: */
+ RTUNI_ALPHA, /* U+00b1b6: */
+ RTUNI_ALPHA, /* U+00b1b7: */
+ RTUNI_ALPHA, /* U+00b1b8: */
+ RTUNI_ALPHA, /* U+00b1b9: */
+ RTUNI_ALPHA, /* U+00b1ba: */
+ RTUNI_ALPHA, /* U+00b1bb: */
+ RTUNI_ALPHA, /* U+00b1bc: */
+ RTUNI_ALPHA, /* U+00b1bd: */
+ RTUNI_ALPHA, /* U+00b1be: */
+ RTUNI_ALPHA, /* U+00b1bf: */
+ RTUNI_ALPHA, /* U+00b1c0: */
+ RTUNI_ALPHA, /* U+00b1c1: */
+ RTUNI_ALPHA, /* U+00b1c2: */
+ RTUNI_ALPHA, /* U+00b1c3: */
+ RTUNI_ALPHA, /* U+00b1c4: */
+ RTUNI_ALPHA, /* U+00b1c5: */
+ RTUNI_ALPHA, /* U+00b1c6: */
+ RTUNI_ALPHA, /* U+00b1c7: */
+ RTUNI_ALPHA, /* U+00b1c8: */
+ RTUNI_ALPHA, /* U+00b1c9: */
+ RTUNI_ALPHA, /* U+00b1ca: */
+ RTUNI_ALPHA, /* U+00b1cb: */
+ RTUNI_ALPHA, /* U+00b1cc: */
+ RTUNI_ALPHA, /* U+00b1cd: */
+ RTUNI_ALPHA, /* U+00b1ce: */
+ RTUNI_ALPHA, /* U+00b1cf: */
+ RTUNI_ALPHA, /* U+00b1d0: */
+ RTUNI_ALPHA, /* U+00b1d1: */
+ RTUNI_ALPHA, /* U+00b1d2: */
+ RTUNI_ALPHA, /* U+00b1d3: */
+ RTUNI_ALPHA, /* U+00b1d4: */
+ RTUNI_ALPHA, /* U+00b1d5: */
+ RTUNI_ALPHA, /* U+00b1d6: */
+ RTUNI_ALPHA, /* U+00b1d7: */
+ RTUNI_ALPHA, /* U+00b1d8: */
+ RTUNI_ALPHA, /* U+00b1d9: */
+ RTUNI_ALPHA, /* U+00b1da: */
+ RTUNI_ALPHA, /* U+00b1db: */
+ RTUNI_ALPHA, /* U+00b1dc: */
+ RTUNI_ALPHA, /* U+00b1dd: */
+ RTUNI_ALPHA, /* U+00b1de: */
+ RTUNI_ALPHA, /* U+00b1df: */
+ RTUNI_ALPHA, /* U+00b1e0: */
+ RTUNI_ALPHA, /* U+00b1e1: */
+ RTUNI_ALPHA, /* U+00b1e2: */
+ RTUNI_ALPHA, /* U+00b1e3: */
+ RTUNI_ALPHA, /* U+00b1e4: */
+ RTUNI_ALPHA, /* U+00b1e5: */
+ RTUNI_ALPHA, /* U+00b1e6: */
+ RTUNI_ALPHA, /* U+00b1e7: */
+ RTUNI_ALPHA, /* U+00b1e8: */
+ RTUNI_ALPHA, /* U+00b1e9: */
+ RTUNI_ALPHA, /* U+00b1ea: */
+ RTUNI_ALPHA, /* U+00b1eb: */
+ RTUNI_ALPHA, /* U+00b1ec: */
+ RTUNI_ALPHA, /* U+00b1ed: */
+ RTUNI_ALPHA, /* U+00b1ee: */
+ RTUNI_ALPHA, /* U+00b1ef: */
+ RTUNI_ALPHA, /* U+00b1f0: */
+ RTUNI_ALPHA, /* U+00b1f1: */
+ RTUNI_ALPHA, /* U+00b1f2: */
+ RTUNI_ALPHA, /* U+00b1f3: */
+ RTUNI_ALPHA, /* U+00b1f4: */
+ RTUNI_ALPHA, /* U+00b1f5: */
+ RTUNI_ALPHA, /* U+00b1f6: */
+ RTUNI_ALPHA, /* U+00b1f7: */
+ RTUNI_ALPHA, /* U+00b1f8: */
+ RTUNI_ALPHA, /* U+00b1f9: */
+ RTUNI_ALPHA, /* U+00b1fa: */
+ RTUNI_ALPHA, /* U+00b1fb: */
+ RTUNI_ALPHA, /* U+00b1fc: */
+ RTUNI_ALPHA, /* U+00b1fd: */
+ RTUNI_ALPHA, /* U+00b1fe: */
+ RTUNI_ALPHA, /* U+00b1ff: */
+ RTUNI_ALPHA, /* U+00b200: */
+ RTUNI_ALPHA, /* U+00b201: */
+ RTUNI_ALPHA, /* U+00b202: */
+ RTUNI_ALPHA, /* U+00b203: */
+ RTUNI_ALPHA, /* U+00b204: */
+ RTUNI_ALPHA, /* U+00b205: */
+ RTUNI_ALPHA, /* U+00b206: */
+ RTUNI_ALPHA, /* U+00b207: */
+ RTUNI_ALPHA, /* U+00b208: */
+ RTUNI_ALPHA, /* U+00b209: */
+ RTUNI_ALPHA, /* U+00b20a: */
+ RTUNI_ALPHA, /* U+00b20b: */
+ RTUNI_ALPHA, /* U+00b20c: */
+ RTUNI_ALPHA, /* U+00b20d: */
+ RTUNI_ALPHA, /* U+00b20e: */
+ RTUNI_ALPHA, /* U+00b20f: */
+ RTUNI_ALPHA, /* U+00b210: */
+ RTUNI_ALPHA, /* U+00b211: */
+ RTUNI_ALPHA, /* U+00b212: */
+ RTUNI_ALPHA, /* U+00b213: */
+ RTUNI_ALPHA, /* U+00b214: */
+ RTUNI_ALPHA, /* U+00b215: */
+ RTUNI_ALPHA, /* U+00b216: */
+ RTUNI_ALPHA, /* U+00b217: */
+ RTUNI_ALPHA, /* U+00b218: */
+ RTUNI_ALPHA, /* U+00b219: */
+ RTUNI_ALPHA, /* U+00b21a: */
+ RTUNI_ALPHA, /* U+00b21b: */
+ RTUNI_ALPHA, /* U+00b21c: */
+ RTUNI_ALPHA, /* U+00b21d: */
+ RTUNI_ALPHA, /* U+00b21e: */
+ RTUNI_ALPHA, /* U+00b21f: */
+ RTUNI_ALPHA, /* U+00b220: */
+ RTUNI_ALPHA, /* U+00b221: */
+ RTUNI_ALPHA, /* U+00b222: */
+ RTUNI_ALPHA, /* U+00b223: */
+ RTUNI_ALPHA, /* U+00b224: */
+ RTUNI_ALPHA, /* U+00b225: */
+ RTUNI_ALPHA, /* U+00b226: */
+ RTUNI_ALPHA, /* U+00b227: */
+ RTUNI_ALPHA, /* U+00b228: */
+ RTUNI_ALPHA, /* U+00b229: */
+ RTUNI_ALPHA, /* U+00b22a: */
+ RTUNI_ALPHA, /* U+00b22b: */
+ RTUNI_ALPHA, /* U+00b22c: */
+ RTUNI_ALPHA, /* U+00b22d: */
+ RTUNI_ALPHA, /* U+00b22e: */
+ RTUNI_ALPHA, /* U+00b22f: */
+ RTUNI_ALPHA, /* U+00b230: */
+ RTUNI_ALPHA, /* U+00b231: */
+ RTUNI_ALPHA, /* U+00b232: */
+ RTUNI_ALPHA, /* U+00b233: */
+ RTUNI_ALPHA, /* U+00b234: */
+ RTUNI_ALPHA, /* U+00b235: */
+ RTUNI_ALPHA, /* U+00b236: */
+ RTUNI_ALPHA, /* U+00b237: */
+ RTUNI_ALPHA, /* U+00b238: */
+ RTUNI_ALPHA, /* U+00b239: */
+ RTUNI_ALPHA, /* U+00b23a: */
+ RTUNI_ALPHA, /* U+00b23b: */
+ RTUNI_ALPHA, /* U+00b23c: */
+ RTUNI_ALPHA, /* U+00b23d: */
+ RTUNI_ALPHA, /* U+00b23e: */
+ RTUNI_ALPHA, /* U+00b23f: */
+ RTUNI_ALPHA, /* U+00b240: */
+ RTUNI_ALPHA, /* U+00b241: */
+ RTUNI_ALPHA, /* U+00b242: */
+ RTUNI_ALPHA, /* U+00b243: */
+ RTUNI_ALPHA, /* U+00b244: */
+ RTUNI_ALPHA, /* U+00b245: */
+ RTUNI_ALPHA, /* U+00b246: */
+ RTUNI_ALPHA, /* U+00b247: */
+ RTUNI_ALPHA, /* U+00b248: */
+ RTUNI_ALPHA, /* U+00b249: */
+ RTUNI_ALPHA, /* U+00b24a: */
+ RTUNI_ALPHA, /* U+00b24b: */
+ RTUNI_ALPHA, /* U+00b24c: */
+ RTUNI_ALPHA, /* U+00b24d: */
+ RTUNI_ALPHA, /* U+00b24e: */
+ RTUNI_ALPHA, /* U+00b24f: */
+ RTUNI_ALPHA, /* U+00b250: */
+ RTUNI_ALPHA, /* U+00b251: */
+ RTUNI_ALPHA, /* U+00b252: */
+ RTUNI_ALPHA, /* U+00b253: */
+ RTUNI_ALPHA, /* U+00b254: */
+ RTUNI_ALPHA, /* U+00b255: */
+ RTUNI_ALPHA, /* U+00b256: */
+ RTUNI_ALPHA, /* U+00b257: */
+ RTUNI_ALPHA, /* U+00b258: */
+ RTUNI_ALPHA, /* U+00b259: */
+ RTUNI_ALPHA, /* U+00b25a: */
+ RTUNI_ALPHA, /* U+00b25b: */
+ RTUNI_ALPHA, /* U+00b25c: */
+ RTUNI_ALPHA, /* U+00b25d: */
+ RTUNI_ALPHA, /* U+00b25e: */
+ RTUNI_ALPHA, /* U+00b25f: */
+ RTUNI_ALPHA, /* U+00b260: */
+ RTUNI_ALPHA, /* U+00b261: */
+ RTUNI_ALPHA, /* U+00b262: */
+ RTUNI_ALPHA, /* U+00b263: */
+ RTUNI_ALPHA, /* U+00b264: */
+ RTUNI_ALPHA, /* U+00b265: */
+ RTUNI_ALPHA, /* U+00b266: */
+ RTUNI_ALPHA, /* U+00b267: */
+ RTUNI_ALPHA, /* U+00b268: */
+ RTUNI_ALPHA, /* U+00b269: */
+ RTUNI_ALPHA, /* U+00b26a: */
+ RTUNI_ALPHA, /* U+00b26b: */
+ RTUNI_ALPHA, /* U+00b26c: */
+ RTUNI_ALPHA, /* U+00b26d: */
+ RTUNI_ALPHA, /* U+00b26e: */
+ RTUNI_ALPHA, /* U+00b26f: */
+ RTUNI_ALPHA, /* U+00b270: */
+ RTUNI_ALPHA, /* U+00b271: */
+ RTUNI_ALPHA, /* U+00b272: */
+ RTUNI_ALPHA, /* U+00b273: */
+ RTUNI_ALPHA, /* U+00b274: */
+ RTUNI_ALPHA, /* U+00b275: */
+ RTUNI_ALPHA, /* U+00b276: */
+ RTUNI_ALPHA, /* U+00b277: */
+ RTUNI_ALPHA, /* U+00b278: */
+ RTUNI_ALPHA, /* U+00b279: */
+ RTUNI_ALPHA, /* U+00b27a: */
+ RTUNI_ALPHA, /* U+00b27b: */
+ RTUNI_ALPHA, /* U+00b27c: */
+ RTUNI_ALPHA, /* U+00b27d: */
+ RTUNI_ALPHA, /* U+00b27e: */
+ RTUNI_ALPHA, /* U+00b27f: */
+ RTUNI_ALPHA, /* U+00b280: */
+ RTUNI_ALPHA, /* U+00b281: */
+ RTUNI_ALPHA, /* U+00b282: */
+ RTUNI_ALPHA, /* U+00b283: */
+ RTUNI_ALPHA, /* U+00b284: */
+ RTUNI_ALPHA, /* U+00b285: */
+ RTUNI_ALPHA, /* U+00b286: */
+ RTUNI_ALPHA, /* U+00b287: */
+ RTUNI_ALPHA, /* U+00b288: */
+ RTUNI_ALPHA, /* U+00b289: */
+ RTUNI_ALPHA, /* U+00b28a: */
+ RTUNI_ALPHA, /* U+00b28b: */
+ RTUNI_ALPHA, /* U+00b28c: */
+ RTUNI_ALPHA, /* U+00b28d: */
+ RTUNI_ALPHA, /* U+00b28e: */
+ RTUNI_ALPHA, /* U+00b28f: */
+ RTUNI_ALPHA, /* U+00b290: */
+ RTUNI_ALPHA, /* U+00b291: */
+ RTUNI_ALPHA, /* U+00b292: */
+ RTUNI_ALPHA, /* U+00b293: */
+ RTUNI_ALPHA, /* U+00b294: */
+ RTUNI_ALPHA, /* U+00b295: */
+ RTUNI_ALPHA, /* U+00b296: */
+ RTUNI_ALPHA, /* U+00b297: */
+ RTUNI_ALPHA, /* U+00b298: */
+ RTUNI_ALPHA, /* U+00b299: */
+ RTUNI_ALPHA, /* U+00b29a: */
+ RTUNI_ALPHA, /* U+00b29b: */
+ RTUNI_ALPHA, /* U+00b29c: */
+ RTUNI_ALPHA, /* U+00b29d: */
+ RTUNI_ALPHA, /* U+00b29e: */
+ RTUNI_ALPHA, /* U+00b29f: */
+ RTUNI_ALPHA, /* U+00b2a0: */
+ RTUNI_ALPHA, /* U+00b2a1: */
+ RTUNI_ALPHA, /* U+00b2a2: */
+ RTUNI_ALPHA, /* U+00b2a3: */
+ RTUNI_ALPHA, /* U+00b2a4: */
+ RTUNI_ALPHA, /* U+00b2a5: */
+ RTUNI_ALPHA, /* U+00b2a6: */
+ RTUNI_ALPHA, /* U+00b2a7: */
+ RTUNI_ALPHA, /* U+00b2a8: */
+ RTUNI_ALPHA, /* U+00b2a9: */
+ RTUNI_ALPHA, /* U+00b2aa: */
+ RTUNI_ALPHA, /* U+00b2ab: */
+ RTUNI_ALPHA, /* U+00b2ac: */
+ RTUNI_ALPHA, /* U+00b2ad: */
+ RTUNI_ALPHA, /* U+00b2ae: */
+ RTUNI_ALPHA, /* U+00b2af: */
+ RTUNI_ALPHA, /* U+00b2b0: */
+ RTUNI_ALPHA, /* U+00b2b1: */
+ RTUNI_ALPHA, /* U+00b2b2: */
+ RTUNI_ALPHA, /* U+00b2b3: */
+ RTUNI_ALPHA, /* U+00b2b4: */
+ RTUNI_ALPHA, /* U+00b2b5: */
+ RTUNI_ALPHA, /* U+00b2b6: */
+ RTUNI_ALPHA, /* U+00b2b7: */
+ RTUNI_ALPHA, /* U+00b2b8: */
+ RTUNI_ALPHA, /* U+00b2b9: */
+ RTUNI_ALPHA, /* U+00b2ba: */
+ RTUNI_ALPHA, /* U+00b2bb: */
+ RTUNI_ALPHA, /* U+00b2bc: */
+ RTUNI_ALPHA, /* U+00b2bd: */
+ RTUNI_ALPHA, /* U+00b2be: */
+ RTUNI_ALPHA, /* U+00b2bf: */
+ RTUNI_ALPHA, /* U+00b2c0: */
+ RTUNI_ALPHA, /* U+00b2c1: */
+ RTUNI_ALPHA, /* U+00b2c2: */
+ RTUNI_ALPHA, /* U+00b2c3: */
+ RTUNI_ALPHA, /* U+00b2c4: */
+ RTUNI_ALPHA, /* U+00b2c5: */
+ RTUNI_ALPHA, /* U+00b2c6: */
+ RTUNI_ALPHA, /* U+00b2c7: */
+ RTUNI_ALPHA, /* U+00b2c8: */
+ RTUNI_ALPHA, /* U+00b2c9: */
+ RTUNI_ALPHA, /* U+00b2ca: */
+ RTUNI_ALPHA, /* U+00b2cb: */
+ RTUNI_ALPHA, /* U+00b2cc: */
+ RTUNI_ALPHA, /* U+00b2cd: */
+ RTUNI_ALPHA, /* U+00b2ce: */
+ RTUNI_ALPHA, /* U+00b2cf: */
+ RTUNI_ALPHA, /* U+00b2d0: */
+ RTUNI_ALPHA, /* U+00b2d1: */
+ RTUNI_ALPHA, /* U+00b2d2: */
+ RTUNI_ALPHA, /* U+00b2d3: */
+ RTUNI_ALPHA, /* U+00b2d4: */
+ RTUNI_ALPHA, /* U+00b2d5: */
+ RTUNI_ALPHA, /* U+00b2d6: */
+ RTUNI_ALPHA, /* U+00b2d7: */
+ RTUNI_ALPHA, /* U+00b2d8: */
+ RTUNI_ALPHA, /* U+00b2d9: */
+ RTUNI_ALPHA, /* U+00b2da: */
+ RTUNI_ALPHA, /* U+00b2db: */
+ RTUNI_ALPHA, /* U+00b2dc: */
+ RTUNI_ALPHA, /* U+00b2dd: */
+ RTUNI_ALPHA, /* U+00b2de: */
+ RTUNI_ALPHA, /* U+00b2df: */
+ RTUNI_ALPHA, /* U+00b2e0: */
+ RTUNI_ALPHA, /* U+00b2e1: */
+ RTUNI_ALPHA, /* U+00b2e2: */
+ RTUNI_ALPHA, /* U+00b2e3: */
+ RTUNI_ALPHA, /* U+00b2e4: */
+ RTUNI_ALPHA, /* U+00b2e5: */
+ RTUNI_ALPHA, /* U+00b2e6: */
+ RTUNI_ALPHA, /* U+00b2e7: */
+ RTUNI_ALPHA, /* U+00b2e8: */
+ RTUNI_ALPHA, /* U+00b2e9: */
+ RTUNI_ALPHA, /* U+00b2ea: */
+ RTUNI_ALPHA, /* U+00b2eb: */
+ RTUNI_ALPHA, /* U+00b2ec: */
+ RTUNI_ALPHA, /* U+00b2ed: */
+ RTUNI_ALPHA, /* U+00b2ee: */
+ RTUNI_ALPHA, /* U+00b2ef: */
+ RTUNI_ALPHA, /* U+00b2f0: */
+ RTUNI_ALPHA, /* U+00b2f1: */
+ RTUNI_ALPHA, /* U+00b2f2: */
+ RTUNI_ALPHA, /* U+00b2f3: */
+ RTUNI_ALPHA, /* U+00b2f4: */
+ RTUNI_ALPHA, /* U+00b2f5: */
+ RTUNI_ALPHA, /* U+00b2f6: */
+ RTUNI_ALPHA, /* U+00b2f7: */
+ RTUNI_ALPHA, /* U+00b2f8: */
+ RTUNI_ALPHA, /* U+00b2f9: */
+ RTUNI_ALPHA, /* U+00b2fa: */
+ RTUNI_ALPHA, /* U+00b2fb: */
+ RTUNI_ALPHA, /* U+00b2fc: */
+ RTUNI_ALPHA, /* U+00b2fd: */
+ RTUNI_ALPHA, /* U+00b2fe: */
+ RTUNI_ALPHA, /* U+00b2ff: */
+ RTUNI_ALPHA, /* U+00b300: */
+ RTUNI_ALPHA, /* U+00b301: */
+ RTUNI_ALPHA, /* U+00b302: */
+ RTUNI_ALPHA, /* U+00b303: */
+ RTUNI_ALPHA, /* U+00b304: */
+ RTUNI_ALPHA, /* U+00b305: */
+ RTUNI_ALPHA, /* U+00b306: */
+ RTUNI_ALPHA, /* U+00b307: */
+ RTUNI_ALPHA, /* U+00b308: */
+ RTUNI_ALPHA, /* U+00b309: */
+ RTUNI_ALPHA, /* U+00b30a: */
+ RTUNI_ALPHA, /* U+00b30b: */
+ RTUNI_ALPHA, /* U+00b30c: */
+ RTUNI_ALPHA, /* U+00b30d: */
+ RTUNI_ALPHA, /* U+00b30e: */
+ RTUNI_ALPHA, /* U+00b30f: */
+ RTUNI_ALPHA, /* U+00b310: */
+ RTUNI_ALPHA, /* U+00b311: */
+ RTUNI_ALPHA, /* U+00b312: */
+ RTUNI_ALPHA, /* U+00b313: */
+ RTUNI_ALPHA, /* U+00b314: */
+ RTUNI_ALPHA, /* U+00b315: */
+ RTUNI_ALPHA, /* U+00b316: */
+ RTUNI_ALPHA, /* U+00b317: */
+ RTUNI_ALPHA, /* U+00b318: */
+ RTUNI_ALPHA, /* U+00b319: */
+ RTUNI_ALPHA, /* U+00b31a: */
+ RTUNI_ALPHA, /* U+00b31b: */
+ RTUNI_ALPHA, /* U+00b31c: */
+ RTUNI_ALPHA, /* U+00b31d: */
+ RTUNI_ALPHA, /* U+00b31e: */
+ RTUNI_ALPHA, /* U+00b31f: */
+ RTUNI_ALPHA, /* U+00b320: */
+ RTUNI_ALPHA, /* U+00b321: */
+ RTUNI_ALPHA, /* U+00b322: */
+ RTUNI_ALPHA, /* U+00b323: */
+ RTUNI_ALPHA, /* U+00b324: */
+ RTUNI_ALPHA, /* U+00b325: */
+ RTUNI_ALPHA, /* U+00b326: */
+ RTUNI_ALPHA, /* U+00b327: */
+ RTUNI_ALPHA, /* U+00b328: */
+ RTUNI_ALPHA, /* U+00b329: */
+ RTUNI_ALPHA, /* U+00b32a: */
+ RTUNI_ALPHA, /* U+00b32b: */
+ RTUNI_ALPHA, /* U+00b32c: */
+ RTUNI_ALPHA, /* U+00b32d: */
+ RTUNI_ALPHA, /* U+00b32e: */
+ RTUNI_ALPHA, /* U+00b32f: */
+ RTUNI_ALPHA, /* U+00b330: */
+ RTUNI_ALPHA, /* U+00b331: */
+ RTUNI_ALPHA, /* U+00b332: */
+ RTUNI_ALPHA, /* U+00b333: */
+ RTUNI_ALPHA, /* U+00b334: */
+ RTUNI_ALPHA, /* U+00b335: */
+ RTUNI_ALPHA, /* U+00b336: */
+ RTUNI_ALPHA, /* U+00b337: */
+ RTUNI_ALPHA, /* U+00b338: */
+ RTUNI_ALPHA, /* U+00b339: */
+ RTUNI_ALPHA, /* U+00b33a: */
+ RTUNI_ALPHA, /* U+00b33b: */
+ RTUNI_ALPHA, /* U+00b33c: */
+ RTUNI_ALPHA, /* U+00b33d: */
+ RTUNI_ALPHA, /* U+00b33e: */
+ RTUNI_ALPHA, /* U+00b33f: */
+ RTUNI_ALPHA, /* U+00b340: */
+ RTUNI_ALPHA, /* U+00b341: */
+ RTUNI_ALPHA, /* U+00b342: */
+ RTUNI_ALPHA, /* U+00b343: */
+ RTUNI_ALPHA, /* U+00b344: */
+ RTUNI_ALPHA, /* U+00b345: */
+ RTUNI_ALPHA, /* U+00b346: */
+ RTUNI_ALPHA, /* U+00b347: */
+ RTUNI_ALPHA, /* U+00b348: */
+ RTUNI_ALPHA, /* U+00b349: */
+ RTUNI_ALPHA, /* U+00b34a: */
+ RTUNI_ALPHA, /* U+00b34b: */
+ RTUNI_ALPHA, /* U+00b34c: */
+ RTUNI_ALPHA, /* U+00b34d: */
+ RTUNI_ALPHA, /* U+00b34e: */
+ RTUNI_ALPHA, /* U+00b34f: */
+ RTUNI_ALPHA, /* U+00b350: */
+ RTUNI_ALPHA, /* U+00b351: */
+ RTUNI_ALPHA, /* U+00b352: */
+ RTUNI_ALPHA, /* U+00b353: */
+ RTUNI_ALPHA, /* U+00b354: */
+ RTUNI_ALPHA, /* U+00b355: */
+ RTUNI_ALPHA, /* U+00b356: */
+ RTUNI_ALPHA, /* U+00b357: */
+ RTUNI_ALPHA, /* U+00b358: */
+ RTUNI_ALPHA, /* U+00b359: */
+ RTUNI_ALPHA, /* U+00b35a: */
+ RTUNI_ALPHA, /* U+00b35b: */
+ RTUNI_ALPHA, /* U+00b35c: */
+ RTUNI_ALPHA, /* U+00b35d: */
+ RTUNI_ALPHA, /* U+00b35e: */
+ RTUNI_ALPHA, /* U+00b35f: */
+ RTUNI_ALPHA, /* U+00b360: */
+ RTUNI_ALPHA, /* U+00b361: */
+ RTUNI_ALPHA, /* U+00b362: */
+ RTUNI_ALPHA, /* U+00b363: */
+ RTUNI_ALPHA, /* U+00b364: */
+ RTUNI_ALPHA, /* U+00b365: */
+ RTUNI_ALPHA, /* U+00b366: */
+ RTUNI_ALPHA, /* U+00b367: */
+ RTUNI_ALPHA, /* U+00b368: */
+ RTUNI_ALPHA, /* U+00b369: */
+ RTUNI_ALPHA, /* U+00b36a: */
+ RTUNI_ALPHA, /* U+00b36b: */
+ RTUNI_ALPHA, /* U+00b36c: */
+ RTUNI_ALPHA, /* U+00b36d: */
+ RTUNI_ALPHA, /* U+00b36e: */
+ RTUNI_ALPHA, /* U+00b36f: */
+ RTUNI_ALPHA, /* U+00b370: */
+ RTUNI_ALPHA, /* U+00b371: */
+ RTUNI_ALPHA, /* U+00b372: */
+ RTUNI_ALPHA, /* U+00b373: */
+ RTUNI_ALPHA, /* U+00b374: */
+ RTUNI_ALPHA, /* U+00b375: */
+ RTUNI_ALPHA, /* U+00b376: */
+ RTUNI_ALPHA, /* U+00b377: */
+ RTUNI_ALPHA, /* U+00b378: */
+ RTUNI_ALPHA, /* U+00b379: */
+ RTUNI_ALPHA, /* U+00b37a: */
+ RTUNI_ALPHA, /* U+00b37b: */
+ RTUNI_ALPHA, /* U+00b37c: */
+ RTUNI_ALPHA, /* U+00b37d: */
+ RTUNI_ALPHA, /* U+00b37e: */
+ RTUNI_ALPHA, /* U+00b37f: */
+ RTUNI_ALPHA, /* U+00b380: */
+ RTUNI_ALPHA, /* U+00b381: */
+ RTUNI_ALPHA, /* U+00b382: */
+ RTUNI_ALPHA, /* U+00b383: */
+ RTUNI_ALPHA, /* U+00b384: */
+ RTUNI_ALPHA, /* U+00b385: */
+ RTUNI_ALPHA, /* U+00b386: */
+ RTUNI_ALPHA, /* U+00b387: */
+ RTUNI_ALPHA, /* U+00b388: */
+ RTUNI_ALPHA, /* U+00b389: */
+ RTUNI_ALPHA, /* U+00b38a: */
+ RTUNI_ALPHA, /* U+00b38b: */
+ RTUNI_ALPHA, /* U+00b38c: */
+ RTUNI_ALPHA, /* U+00b38d: */
+ RTUNI_ALPHA, /* U+00b38e: */
+ RTUNI_ALPHA, /* U+00b38f: */
+ RTUNI_ALPHA, /* U+00b390: */
+ RTUNI_ALPHA, /* U+00b391: */
+ RTUNI_ALPHA, /* U+00b392: */
+ RTUNI_ALPHA, /* U+00b393: */
+ RTUNI_ALPHA, /* U+00b394: */
+ RTUNI_ALPHA, /* U+00b395: */
+ RTUNI_ALPHA, /* U+00b396: */
+ RTUNI_ALPHA, /* U+00b397: */
+ RTUNI_ALPHA, /* U+00b398: */
+ RTUNI_ALPHA, /* U+00b399: */
+ RTUNI_ALPHA, /* U+00b39a: */
+ RTUNI_ALPHA, /* U+00b39b: */
+ RTUNI_ALPHA, /* U+00b39c: */
+ RTUNI_ALPHA, /* U+00b39d: */
+ RTUNI_ALPHA, /* U+00b39e: */
+ RTUNI_ALPHA, /* U+00b39f: */
+ RTUNI_ALPHA, /* U+00b3a0: */
+ RTUNI_ALPHA, /* U+00b3a1: */
+ RTUNI_ALPHA, /* U+00b3a2: */
+ RTUNI_ALPHA, /* U+00b3a3: */
+ RTUNI_ALPHA, /* U+00b3a4: */
+ RTUNI_ALPHA, /* U+00b3a5: */
+ RTUNI_ALPHA, /* U+00b3a6: */
+ RTUNI_ALPHA, /* U+00b3a7: */
+ RTUNI_ALPHA, /* U+00b3a8: */
+ RTUNI_ALPHA, /* U+00b3a9: */
+ RTUNI_ALPHA, /* U+00b3aa: */
+ RTUNI_ALPHA, /* U+00b3ab: */
+ RTUNI_ALPHA, /* U+00b3ac: */
+ RTUNI_ALPHA, /* U+00b3ad: */
+ RTUNI_ALPHA, /* U+00b3ae: */
+ RTUNI_ALPHA, /* U+00b3af: */
+ RTUNI_ALPHA, /* U+00b3b0: */
+ RTUNI_ALPHA, /* U+00b3b1: */
+ RTUNI_ALPHA, /* U+00b3b2: */
+ RTUNI_ALPHA, /* U+00b3b3: */
+ RTUNI_ALPHA, /* U+00b3b4: */
+ RTUNI_ALPHA, /* U+00b3b5: */
+ RTUNI_ALPHA, /* U+00b3b6: */
+ RTUNI_ALPHA, /* U+00b3b7: */
+ RTUNI_ALPHA, /* U+00b3b8: */
+ RTUNI_ALPHA, /* U+00b3b9: */
+ RTUNI_ALPHA, /* U+00b3ba: */
+ RTUNI_ALPHA, /* U+00b3bb: */
+ RTUNI_ALPHA, /* U+00b3bc: */
+ RTUNI_ALPHA, /* U+00b3bd: */
+ RTUNI_ALPHA, /* U+00b3be: */
+ RTUNI_ALPHA, /* U+00b3bf: */
+ RTUNI_ALPHA, /* U+00b3c0: */
+ RTUNI_ALPHA, /* U+00b3c1: */
+ RTUNI_ALPHA, /* U+00b3c2: */
+ RTUNI_ALPHA, /* U+00b3c3: */
+ RTUNI_ALPHA, /* U+00b3c4: */
+ RTUNI_ALPHA, /* U+00b3c5: */
+ RTUNI_ALPHA, /* U+00b3c6: */
+ RTUNI_ALPHA, /* U+00b3c7: */
+ RTUNI_ALPHA, /* U+00b3c8: */
+ RTUNI_ALPHA, /* U+00b3c9: */
+ RTUNI_ALPHA, /* U+00b3ca: */
+ RTUNI_ALPHA, /* U+00b3cb: */
+ RTUNI_ALPHA, /* U+00b3cc: */
+ RTUNI_ALPHA, /* U+00b3cd: */
+ RTUNI_ALPHA, /* U+00b3ce: */
+ RTUNI_ALPHA, /* U+00b3cf: */
+ RTUNI_ALPHA, /* U+00b3d0: */
+ RTUNI_ALPHA, /* U+00b3d1: */
+ RTUNI_ALPHA, /* U+00b3d2: */
+ RTUNI_ALPHA, /* U+00b3d3: */
+ RTUNI_ALPHA, /* U+00b3d4: */
+ RTUNI_ALPHA, /* U+00b3d5: */
+ RTUNI_ALPHA, /* U+00b3d6: */
+ RTUNI_ALPHA, /* U+00b3d7: */
+ RTUNI_ALPHA, /* U+00b3d8: */
+ RTUNI_ALPHA, /* U+00b3d9: */
+ RTUNI_ALPHA, /* U+00b3da: */
+ RTUNI_ALPHA, /* U+00b3db: */
+ RTUNI_ALPHA, /* U+00b3dc: */
+ RTUNI_ALPHA, /* U+00b3dd: */
+ RTUNI_ALPHA, /* U+00b3de: */
+ RTUNI_ALPHA, /* U+00b3df: */
+ RTUNI_ALPHA, /* U+00b3e0: */
+ RTUNI_ALPHA, /* U+00b3e1: */
+ RTUNI_ALPHA, /* U+00b3e2: */
+ RTUNI_ALPHA, /* U+00b3e3: */
+ RTUNI_ALPHA, /* U+00b3e4: */
+ RTUNI_ALPHA, /* U+00b3e5: */
+ RTUNI_ALPHA, /* U+00b3e6: */
+ RTUNI_ALPHA, /* U+00b3e7: */
+ RTUNI_ALPHA, /* U+00b3e8: */
+ RTUNI_ALPHA, /* U+00b3e9: */
+ RTUNI_ALPHA, /* U+00b3ea: */
+ RTUNI_ALPHA, /* U+00b3eb: */
+ RTUNI_ALPHA, /* U+00b3ec: */
+ RTUNI_ALPHA, /* U+00b3ed: */
+ RTUNI_ALPHA, /* U+00b3ee: */
+ RTUNI_ALPHA, /* U+00b3ef: */
+ RTUNI_ALPHA, /* U+00b3f0: */
+ RTUNI_ALPHA, /* U+00b3f1: */
+ RTUNI_ALPHA, /* U+00b3f2: */
+ RTUNI_ALPHA, /* U+00b3f3: */
+ RTUNI_ALPHA, /* U+00b3f4: */
+ RTUNI_ALPHA, /* U+00b3f5: */
+ RTUNI_ALPHA, /* U+00b3f6: */
+ RTUNI_ALPHA, /* U+00b3f7: */
+ RTUNI_ALPHA, /* U+00b3f8: */
+ RTUNI_ALPHA, /* U+00b3f9: */
+ RTUNI_ALPHA, /* U+00b3fa: */
+ RTUNI_ALPHA, /* U+00b3fb: */
+ RTUNI_ALPHA, /* U+00b3fc: */
+ RTUNI_ALPHA, /* U+00b3fd: */
+ RTUNI_ALPHA, /* U+00b3fe: */
+ RTUNI_ALPHA, /* U+00b3ff: */
+ RTUNI_ALPHA, /* U+00b400: */
+ RTUNI_ALPHA, /* U+00b401: */
+ RTUNI_ALPHA, /* U+00b402: */
+ RTUNI_ALPHA, /* U+00b403: */
+ RTUNI_ALPHA, /* U+00b404: */
+ RTUNI_ALPHA, /* U+00b405: */
+ RTUNI_ALPHA, /* U+00b406: */
+ RTUNI_ALPHA, /* U+00b407: */
+ RTUNI_ALPHA, /* U+00b408: */
+ RTUNI_ALPHA, /* U+00b409: */
+ RTUNI_ALPHA, /* U+00b40a: */
+ RTUNI_ALPHA, /* U+00b40b: */
+ RTUNI_ALPHA, /* U+00b40c: */
+ RTUNI_ALPHA, /* U+00b40d: */
+ RTUNI_ALPHA, /* U+00b40e: */
+ RTUNI_ALPHA, /* U+00b40f: */
+ RTUNI_ALPHA, /* U+00b410: */
+ RTUNI_ALPHA, /* U+00b411: */
+ RTUNI_ALPHA, /* U+00b412: */
+ RTUNI_ALPHA, /* U+00b413: */
+ RTUNI_ALPHA, /* U+00b414: */
+ RTUNI_ALPHA, /* U+00b415: */
+ RTUNI_ALPHA, /* U+00b416: */
+ RTUNI_ALPHA, /* U+00b417: */
+ RTUNI_ALPHA, /* U+00b418: */
+ RTUNI_ALPHA, /* U+00b419: */
+ RTUNI_ALPHA, /* U+00b41a: */
+ RTUNI_ALPHA, /* U+00b41b: */
+ RTUNI_ALPHA, /* U+00b41c: */
+ RTUNI_ALPHA, /* U+00b41d: */
+ RTUNI_ALPHA, /* U+00b41e: */
+ RTUNI_ALPHA, /* U+00b41f: */
+ RTUNI_ALPHA, /* U+00b420: */
+ RTUNI_ALPHA, /* U+00b421: */
+ RTUNI_ALPHA, /* U+00b422: */
+ RTUNI_ALPHA, /* U+00b423: */
+ RTUNI_ALPHA, /* U+00b424: */
+ RTUNI_ALPHA, /* U+00b425: */
+ RTUNI_ALPHA, /* U+00b426: */
+ RTUNI_ALPHA, /* U+00b427: */
+ RTUNI_ALPHA, /* U+00b428: */
+ RTUNI_ALPHA, /* U+00b429: */
+ RTUNI_ALPHA, /* U+00b42a: */
+ RTUNI_ALPHA, /* U+00b42b: */
+ RTUNI_ALPHA, /* U+00b42c: */
+ RTUNI_ALPHA, /* U+00b42d: */
+ RTUNI_ALPHA, /* U+00b42e: */
+ RTUNI_ALPHA, /* U+00b42f: */
+ RTUNI_ALPHA, /* U+00b430: */
+ RTUNI_ALPHA, /* U+00b431: */
+ RTUNI_ALPHA, /* U+00b432: */
+ RTUNI_ALPHA, /* U+00b433: */
+ RTUNI_ALPHA, /* U+00b434: */
+ RTUNI_ALPHA, /* U+00b435: */
+ RTUNI_ALPHA, /* U+00b436: */
+ RTUNI_ALPHA, /* U+00b437: */
+ RTUNI_ALPHA, /* U+00b438: */
+ RTUNI_ALPHA, /* U+00b439: */
+ RTUNI_ALPHA, /* U+00b43a: */
+ RTUNI_ALPHA, /* U+00b43b: */
+ RTUNI_ALPHA, /* U+00b43c: */
+ RTUNI_ALPHA, /* U+00b43d: */
+ RTUNI_ALPHA, /* U+00b43e: */
+ RTUNI_ALPHA, /* U+00b43f: */
+ RTUNI_ALPHA, /* U+00b440: */
+ RTUNI_ALPHA, /* U+00b441: */
+ RTUNI_ALPHA, /* U+00b442: */
+ RTUNI_ALPHA, /* U+00b443: */
+ RTUNI_ALPHA, /* U+00b444: */
+ RTUNI_ALPHA, /* U+00b445: */
+ RTUNI_ALPHA, /* U+00b446: */
+ RTUNI_ALPHA, /* U+00b447: */
+ RTUNI_ALPHA, /* U+00b448: */
+ RTUNI_ALPHA, /* U+00b449: */
+ RTUNI_ALPHA, /* U+00b44a: */
+ RTUNI_ALPHA, /* U+00b44b: */
+ RTUNI_ALPHA, /* U+00b44c: */
+ RTUNI_ALPHA, /* U+00b44d: */
+ RTUNI_ALPHA, /* U+00b44e: */
+ RTUNI_ALPHA, /* U+00b44f: */
+ RTUNI_ALPHA, /* U+00b450: */
+ RTUNI_ALPHA, /* U+00b451: */
+ RTUNI_ALPHA, /* U+00b452: */
+ RTUNI_ALPHA, /* U+00b453: */
+ RTUNI_ALPHA, /* U+00b454: */
+ RTUNI_ALPHA, /* U+00b455: */
+ RTUNI_ALPHA, /* U+00b456: */
+ RTUNI_ALPHA, /* U+00b457: */
+ RTUNI_ALPHA, /* U+00b458: */
+ RTUNI_ALPHA, /* U+00b459: */
+ RTUNI_ALPHA, /* U+00b45a: */
+ RTUNI_ALPHA, /* U+00b45b: */
+ RTUNI_ALPHA, /* U+00b45c: */
+ RTUNI_ALPHA, /* U+00b45d: */
+ RTUNI_ALPHA, /* U+00b45e: */
+ RTUNI_ALPHA, /* U+00b45f: */
+ RTUNI_ALPHA, /* U+00b460: */
+ RTUNI_ALPHA, /* U+00b461: */
+ RTUNI_ALPHA, /* U+00b462: */
+ RTUNI_ALPHA, /* U+00b463: */
+ RTUNI_ALPHA, /* U+00b464: */
+ RTUNI_ALPHA, /* U+00b465: */
+ RTUNI_ALPHA, /* U+00b466: */
+ RTUNI_ALPHA, /* U+00b467: */
+ RTUNI_ALPHA, /* U+00b468: */
+ RTUNI_ALPHA, /* U+00b469: */
+ RTUNI_ALPHA, /* U+00b46a: */
+ RTUNI_ALPHA, /* U+00b46b: */
+ RTUNI_ALPHA, /* U+00b46c: */
+ RTUNI_ALPHA, /* U+00b46d: */
+ RTUNI_ALPHA, /* U+00b46e: */
+ RTUNI_ALPHA, /* U+00b46f: */
+ RTUNI_ALPHA, /* U+00b470: */
+ RTUNI_ALPHA, /* U+00b471: */
+ RTUNI_ALPHA, /* U+00b472: */
+ RTUNI_ALPHA, /* U+00b473: */
+ RTUNI_ALPHA, /* U+00b474: */
+ RTUNI_ALPHA, /* U+00b475: */
+ RTUNI_ALPHA, /* U+00b476: */
+ RTUNI_ALPHA, /* U+00b477: */
+ RTUNI_ALPHA, /* U+00b478: */
+ RTUNI_ALPHA, /* U+00b479: */
+ RTUNI_ALPHA, /* U+00b47a: */
+ RTUNI_ALPHA, /* U+00b47b: */
+ RTUNI_ALPHA, /* U+00b47c: */
+ RTUNI_ALPHA, /* U+00b47d: */
+ RTUNI_ALPHA, /* U+00b47e: */
+ RTUNI_ALPHA, /* U+00b47f: */
+ RTUNI_ALPHA, /* U+00b480: */
+ RTUNI_ALPHA, /* U+00b481: */
+ RTUNI_ALPHA, /* U+00b482: */
+ RTUNI_ALPHA, /* U+00b483: */
+ RTUNI_ALPHA, /* U+00b484: */
+ RTUNI_ALPHA, /* U+00b485: */
+ RTUNI_ALPHA, /* U+00b486: */
+ RTUNI_ALPHA, /* U+00b487: */
+ RTUNI_ALPHA, /* U+00b488: */
+ RTUNI_ALPHA, /* U+00b489: */
+ RTUNI_ALPHA, /* U+00b48a: */
+ RTUNI_ALPHA, /* U+00b48b: */
+ RTUNI_ALPHA, /* U+00b48c: */
+ RTUNI_ALPHA, /* U+00b48d: */
+ RTUNI_ALPHA, /* U+00b48e: */
+ RTUNI_ALPHA, /* U+00b48f: */
+ RTUNI_ALPHA, /* U+00b490: */
+ RTUNI_ALPHA, /* U+00b491: */
+ RTUNI_ALPHA, /* U+00b492: */
+ RTUNI_ALPHA, /* U+00b493: */
+ RTUNI_ALPHA, /* U+00b494: */
+ RTUNI_ALPHA, /* U+00b495: */
+ RTUNI_ALPHA, /* U+00b496: */
+ RTUNI_ALPHA, /* U+00b497: */
+ RTUNI_ALPHA, /* U+00b498: */
+ RTUNI_ALPHA, /* U+00b499: */
+ RTUNI_ALPHA, /* U+00b49a: */
+ RTUNI_ALPHA, /* U+00b49b: */
+ RTUNI_ALPHA, /* U+00b49c: */
+ RTUNI_ALPHA, /* U+00b49d: */
+ RTUNI_ALPHA, /* U+00b49e: */
+ RTUNI_ALPHA, /* U+00b49f: */
+ RTUNI_ALPHA, /* U+00b4a0: */
+ RTUNI_ALPHA, /* U+00b4a1: */
+ RTUNI_ALPHA, /* U+00b4a2: */
+ RTUNI_ALPHA, /* U+00b4a3: */
+ RTUNI_ALPHA, /* U+00b4a4: */
+ RTUNI_ALPHA, /* U+00b4a5: */
+ RTUNI_ALPHA, /* U+00b4a6: */
+ RTUNI_ALPHA, /* U+00b4a7: */
+ RTUNI_ALPHA, /* U+00b4a8: */
+ RTUNI_ALPHA, /* U+00b4a9: */
+ RTUNI_ALPHA, /* U+00b4aa: */
+ RTUNI_ALPHA, /* U+00b4ab: */
+ RTUNI_ALPHA, /* U+00b4ac: */
+ RTUNI_ALPHA, /* U+00b4ad: */
+ RTUNI_ALPHA, /* U+00b4ae: */
+ RTUNI_ALPHA, /* U+00b4af: */
+ RTUNI_ALPHA, /* U+00b4b0: */
+ RTUNI_ALPHA, /* U+00b4b1: */
+ RTUNI_ALPHA, /* U+00b4b2: */
+ RTUNI_ALPHA, /* U+00b4b3: */
+ RTUNI_ALPHA, /* U+00b4b4: */
+ RTUNI_ALPHA, /* U+00b4b5: */
+ RTUNI_ALPHA, /* U+00b4b6: */
+ RTUNI_ALPHA, /* U+00b4b7: */
+ RTUNI_ALPHA, /* U+00b4b8: */
+ RTUNI_ALPHA, /* U+00b4b9: */
+ RTUNI_ALPHA, /* U+00b4ba: */
+ RTUNI_ALPHA, /* U+00b4bb: */
+ RTUNI_ALPHA, /* U+00b4bc: */
+ RTUNI_ALPHA, /* U+00b4bd: */
+ RTUNI_ALPHA, /* U+00b4be: */
+ RTUNI_ALPHA, /* U+00b4bf: */
+ RTUNI_ALPHA, /* U+00b4c0: */
+ RTUNI_ALPHA, /* U+00b4c1: */
+ RTUNI_ALPHA, /* U+00b4c2: */
+ RTUNI_ALPHA, /* U+00b4c3: */
+ RTUNI_ALPHA, /* U+00b4c4: */
+ RTUNI_ALPHA, /* U+00b4c5: */
+ RTUNI_ALPHA, /* U+00b4c6: */
+ RTUNI_ALPHA, /* U+00b4c7: */
+ RTUNI_ALPHA, /* U+00b4c8: */
+ RTUNI_ALPHA, /* U+00b4c9: */
+ RTUNI_ALPHA, /* U+00b4ca: */
+ RTUNI_ALPHA, /* U+00b4cb: */
+ RTUNI_ALPHA, /* U+00b4cc: */
+ RTUNI_ALPHA, /* U+00b4cd: */
+ RTUNI_ALPHA, /* U+00b4ce: */
+ RTUNI_ALPHA, /* U+00b4cf: */
+ RTUNI_ALPHA, /* U+00b4d0: */
+ RTUNI_ALPHA, /* U+00b4d1: */
+ RTUNI_ALPHA, /* U+00b4d2: */
+ RTUNI_ALPHA, /* U+00b4d3: */
+ RTUNI_ALPHA, /* U+00b4d4: */
+ RTUNI_ALPHA, /* U+00b4d5: */
+ RTUNI_ALPHA, /* U+00b4d6: */
+ RTUNI_ALPHA, /* U+00b4d7: */
+ RTUNI_ALPHA, /* U+00b4d8: */
+ RTUNI_ALPHA, /* U+00b4d9: */
+ RTUNI_ALPHA, /* U+00b4da: */
+ RTUNI_ALPHA, /* U+00b4db: */
+ RTUNI_ALPHA, /* U+00b4dc: */
+ RTUNI_ALPHA, /* U+00b4dd: */
+ RTUNI_ALPHA, /* U+00b4de: */
+ RTUNI_ALPHA, /* U+00b4df: */
+ RTUNI_ALPHA, /* U+00b4e0: */
+ RTUNI_ALPHA, /* U+00b4e1: */
+ RTUNI_ALPHA, /* U+00b4e2: */
+ RTUNI_ALPHA, /* U+00b4e3: */
+ RTUNI_ALPHA, /* U+00b4e4: */
+ RTUNI_ALPHA, /* U+00b4e5: */
+ RTUNI_ALPHA, /* U+00b4e6: */
+ RTUNI_ALPHA, /* U+00b4e7: */
+ RTUNI_ALPHA, /* U+00b4e8: */
+ RTUNI_ALPHA, /* U+00b4e9: */
+ RTUNI_ALPHA, /* U+00b4ea: */
+ RTUNI_ALPHA, /* U+00b4eb: */
+ RTUNI_ALPHA, /* U+00b4ec: */
+ RTUNI_ALPHA, /* U+00b4ed: */
+ RTUNI_ALPHA, /* U+00b4ee: */
+ RTUNI_ALPHA, /* U+00b4ef: */
+ RTUNI_ALPHA, /* U+00b4f0: */
+ RTUNI_ALPHA, /* U+00b4f1: */
+ RTUNI_ALPHA, /* U+00b4f2: */
+ RTUNI_ALPHA, /* U+00b4f3: */
+ RTUNI_ALPHA, /* U+00b4f4: */
+ RTUNI_ALPHA, /* U+00b4f5: */
+ RTUNI_ALPHA, /* U+00b4f6: */
+ RTUNI_ALPHA, /* U+00b4f7: */
+ RTUNI_ALPHA, /* U+00b4f8: */
+ RTUNI_ALPHA, /* U+00b4f9: */
+ RTUNI_ALPHA, /* U+00b4fa: */
+ RTUNI_ALPHA, /* U+00b4fb: */
+ RTUNI_ALPHA, /* U+00b4fc: */
+ RTUNI_ALPHA, /* U+00b4fd: */
+ RTUNI_ALPHA, /* U+00b4fe: */
+ RTUNI_ALPHA, /* U+00b4ff: */
+ RTUNI_ALPHA, /* U+00b500: */
+ RTUNI_ALPHA, /* U+00b501: */
+ RTUNI_ALPHA, /* U+00b502: */
+ RTUNI_ALPHA, /* U+00b503: */
+ RTUNI_ALPHA, /* U+00b504: */
+ RTUNI_ALPHA, /* U+00b505: */
+ RTUNI_ALPHA, /* U+00b506: */
+ RTUNI_ALPHA, /* U+00b507: */
+ RTUNI_ALPHA, /* U+00b508: */
+ RTUNI_ALPHA, /* U+00b509: */
+ RTUNI_ALPHA, /* U+00b50a: */
+ RTUNI_ALPHA, /* U+00b50b: */
+ RTUNI_ALPHA, /* U+00b50c: */
+ RTUNI_ALPHA, /* U+00b50d: */
+ RTUNI_ALPHA, /* U+00b50e: */
+ RTUNI_ALPHA, /* U+00b50f: */
+ RTUNI_ALPHA, /* U+00b510: */
+ RTUNI_ALPHA, /* U+00b511: */
+ RTUNI_ALPHA, /* U+00b512: */
+ RTUNI_ALPHA, /* U+00b513: */
+ RTUNI_ALPHA, /* U+00b514: */
+ RTUNI_ALPHA, /* U+00b515: */
+ RTUNI_ALPHA, /* U+00b516: */
+ RTUNI_ALPHA, /* U+00b517: */
+ RTUNI_ALPHA, /* U+00b518: */
+ RTUNI_ALPHA, /* U+00b519: */
+ RTUNI_ALPHA, /* U+00b51a: */
+ RTUNI_ALPHA, /* U+00b51b: */
+ RTUNI_ALPHA, /* U+00b51c: */
+ RTUNI_ALPHA, /* U+00b51d: */
+ RTUNI_ALPHA, /* U+00b51e: */
+ RTUNI_ALPHA, /* U+00b51f: */
+ RTUNI_ALPHA, /* U+00b520: */
+ RTUNI_ALPHA, /* U+00b521: */
+ RTUNI_ALPHA, /* U+00b522: */
+ RTUNI_ALPHA, /* U+00b523: */
+ RTUNI_ALPHA, /* U+00b524: */
+ RTUNI_ALPHA, /* U+00b525: */
+ RTUNI_ALPHA, /* U+00b526: */
+ RTUNI_ALPHA, /* U+00b527: */
+ RTUNI_ALPHA, /* U+00b528: */
+ RTUNI_ALPHA, /* U+00b529: */
+ RTUNI_ALPHA, /* U+00b52a: */
+ RTUNI_ALPHA, /* U+00b52b: */
+ RTUNI_ALPHA, /* U+00b52c: */
+ RTUNI_ALPHA, /* U+00b52d: */
+ RTUNI_ALPHA, /* U+00b52e: */
+ RTUNI_ALPHA, /* U+00b52f: */
+ RTUNI_ALPHA, /* U+00b530: */
+ RTUNI_ALPHA, /* U+00b531: */
+ RTUNI_ALPHA, /* U+00b532: */
+ RTUNI_ALPHA, /* U+00b533: */
+ RTUNI_ALPHA, /* U+00b534: */
+ RTUNI_ALPHA, /* U+00b535: */
+ RTUNI_ALPHA, /* U+00b536: */
+ RTUNI_ALPHA, /* U+00b537: */
+ RTUNI_ALPHA, /* U+00b538: */
+ RTUNI_ALPHA, /* U+00b539: */
+ RTUNI_ALPHA, /* U+00b53a: */
+ RTUNI_ALPHA, /* U+00b53b: */
+ RTUNI_ALPHA, /* U+00b53c: */
+ RTUNI_ALPHA, /* U+00b53d: */
+ RTUNI_ALPHA, /* U+00b53e: */
+ RTUNI_ALPHA, /* U+00b53f: */
+ RTUNI_ALPHA, /* U+00b540: */
+ RTUNI_ALPHA, /* U+00b541: */
+ RTUNI_ALPHA, /* U+00b542: */
+ RTUNI_ALPHA, /* U+00b543: */
+ RTUNI_ALPHA, /* U+00b544: */
+ RTUNI_ALPHA, /* U+00b545: */
+ RTUNI_ALPHA, /* U+00b546: */
+ RTUNI_ALPHA, /* U+00b547: */
+ RTUNI_ALPHA, /* U+00b548: */
+ RTUNI_ALPHA, /* U+00b549: */
+ RTUNI_ALPHA, /* U+00b54a: */
+ RTUNI_ALPHA, /* U+00b54b: */
+ RTUNI_ALPHA, /* U+00b54c: */
+ RTUNI_ALPHA, /* U+00b54d: */
+ RTUNI_ALPHA, /* U+00b54e: */
+ RTUNI_ALPHA, /* U+00b54f: */
+ RTUNI_ALPHA, /* U+00b550: */
+ RTUNI_ALPHA, /* U+00b551: */
+ RTUNI_ALPHA, /* U+00b552: */
+ RTUNI_ALPHA, /* U+00b553: */
+ RTUNI_ALPHA, /* U+00b554: */
+ RTUNI_ALPHA, /* U+00b555: */
+ RTUNI_ALPHA, /* U+00b556: */
+ RTUNI_ALPHA, /* U+00b557: */
+ RTUNI_ALPHA, /* U+00b558: */
+ RTUNI_ALPHA, /* U+00b559: */
+ RTUNI_ALPHA, /* U+00b55a: */
+ RTUNI_ALPHA, /* U+00b55b: */
+ RTUNI_ALPHA, /* U+00b55c: */
+ RTUNI_ALPHA, /* U+00b55d: */
+ RTUNI_ALPHA, /* U+00b55e: */
+ RTUNI_ALPHA, /* U+00b55f: */
+ RTUNI_ALPHA, /* U+00b560: */
+ RTUNI_ALPHA, /* U+00b561: */
+ RTUNI_ALPHA, /* U+00b562: */
+ RTUNI_ALPHA, /* U+00b563: */
+ RTUNI_ALPHA, /* U+00b564: */
+ RTUNI_ALPHA, /* U+00b565: */
+ RTUNI_ALPHA, /* U+00b566: */
+ RTUNI_ALPHA, /* U+00b567: */
+ RTUNI_ALPHA, /* U+00b568: */
+ RTUNI_ALPHA, /* U+00b569: */
+ RTUNI_ALPHA, /* U+00b56a: */
+ RTUNI_ALPHA, /* U+00b56b: */
+ RTUNI_ALPHA, /* U+00b56c: */
+ RTUNI_ALPHA, /* U+00b56d: */
+ RTUNI_ALPHA, /* U+00b56e: */
+ RTUNI_ALPHA, /* U+00b56f: */
+ RTUNI_ALPHA, /* U+00b570: */
+ RTUNI_ALPHA, /* U+00b571: */
+ RTUNI_ALPHA, /* U+00b572: */
+ RTUNI_ALPHA, /* U+00b573: */
+ RTUNI_ALPHA, /* U+00b574: */
+ RTUNI_ALPHA, /* U+00b575: */
+ RTUNI_ALPHA, /* U+00b576: */
+ RTUNI_ALPHA, /* U+00b577: */
+ RTUNI_ALPHA, /* U+00b578: */
+ RTUNI_ALPHA, /* U+00b579: */
+ RTUNI_ALPHA, /* U+00b57a: */
+ RTUNI_ALPHA, /* U+00b57b: */
+ RTUNI_ALPHA, /* U+00b57c: */
+ RTUNI_ALPHA, /* U+00b57d: */
+ RTUNI_ALPHA, /* U+00b57e: */
+ RTUNI_ALPHA, /* U+00b57f: */
+ RTUNI_ALPHA, /* U+00b580: */
+ RTUNI_ALPHA, /* U+00b581: */
+ RTUNI_ALPHA, /* U+00b582: */
+ RTUNI_ALPHA, /* U+00b583: */
+ RTUNI_ALPHA, /* U+00b584: */
+ RTUNI_ALPHA, /* U+00b585: */
+ RTUNI_ALPHA, /* U+00b586: */
+ RTUNI_ALPHA, /* U+00b587: */
+ RTUNI_ALPHA, /* U+00b588: */
+ RTUNI_ALPHA, /* U+00b589: */
+ RTUNI_ALPHA, /* U+00b58a: */
+ RTUNI_ALPHA, /* U+00b58b: */
+ RTUNI_ALPHA, /* U+00b58c: */
+ RTUNI_ALPHA, /* U+00b58d: */
+ RTUNI_ALPHA, /* U+00b58e: */
+ RTUNI_ALPHA, /* U+00b58f: */
+ RTUNI_ALPHA, /* U+00b590: */
+ RTUNI_ALPHA, /* U+00b591: */
+ RTUNI_ALPHA, /* U+00b592: */
+ RTUNI_ALPHA, /* U+00b593: */
+ RTUNI_ALPHA, /* U+00b594: */
+ RTUNI_ALPHA, /* U+00b595: */
+ RTUNI_ALPHA, /* U+00b596: */
+ RTUNI_ALPHA, /* U+00b597: */
+ RTUNI_ALPHA, /* U+00b598: */
+ RTUNI_ALPHA, /* U+00b599: */
+ RTUNI_ALPHA, /* U+00b59a: */
+ RTUNI_ALPHA, /* U+00b59b: */
+ RTUNI_ALPHA, /* U+00b59c: */
+ RTUNI_ALPHA, /* U+00b59d: */
+ RTUNI_ALPHA, /* U+00b59e: */
+ RTUNI_ALPHA, /* U+00b59f: */
+ RTUNI_ALPHA, /* U+00b5a0: */
+ RTUNI_ALPHA, /* U+00b5a1: */
+ RTUNI_ALPHA, /* U+00b5a2: */
+ RTUNI_ALPHA, /* U+00b5a3: */
+ RTUNI_ALPHA, /* U+00b5a4: */
+ RTUNI_ALPHA, /* U+00b5a5: */
+ RTUNI_ALPHA, /* U+00b5a6: */
+ RTUNI_ALPHA, /* U+00b5a7: */
+ RTUNI_ALPHA, /* U+00b5a8: */
+ RTUNI_ALPHA, /* U+00b5a9: */
+ RTUNI_ALPHA, /* U+00b5aa: */
+ RTUNI_ALPHA, /* U+00b5ab: */
+ RTUNI_ALPHA, /* U+00b5ac: */
+ RTUNI_ALPHA, /* U+00b5ad: */
+ RTUNI_ALPHA, /* U+00b5ae: */
+ RTUNI_ALPHA, /* U+00b5af: */
+ RTUNI_ALPHA, /* U+00b5b0: */
+ RTUNI_ALPHA, /* U+00b5b1: */
+ RTUNI_ALPHA, /* U+00b5b2: */
+ RTUNI_ALPHA, /* U+00b5b3: */
+ RTUNI_ALPHA, /* U+00b5b4: */
+ RTUNI_ALPHA, /* U+00b5b5: */
+ RTUNI_ALPHA, /* U+00b5b6: */
+ RTUNI_ALPHA, /* U+00b5b7: */
+ RTUNI_ALPHA, /* U+00b5b8: */
+ RTUNI_ALPHA, /* U+00b5b9: */
+ RTUNI_ALPHA, /* U+00b5ba: */
+ RTUNI_ALPHA, /* U+00b5bb: */
+ RTUNI_ALPHA, /* U+00b5bc: */
+ RTUNI_ALPHA, /* U+00b5bd: */
+ RTUNI_ALPHA, /* U+00b5be: */
+ RTUNI_ALPHA, /* U+00b5bf: */
+ RTUNI_ALPHA, /* U+00b5c0: */
+ RTUNI_ALPHA, /* U+00b5c1: */
+ RTUNI_ALPHA, /* U+00b5c2: */
+ RTUNI_ALPHA, /* U+00b5c3: */
+ RTUNI_ALPHA, /* U+00b5c4: */
+ RTUNI_ALPHA, /* U+00b5c5: */
+ RTUNI_ALPHA, /* U+00b5c6: */
+ RTUNI_ALPHA, /* U+00b5c7: */
+ RTUNI_ALPHA, /* U+00b5c8: */
+ RTUNI_ALPHA, /* U+00b5c9: */
+ RTUNI_ALPHA, /* U+00b5ca: */
+ RTUNI_ALPHA, /* U+00b5cb: */
+ RTUNI_ALPHA, /* U+00b5cc: */
+ RTUNI_ALPHA, /* U+00b5cd: */
+ RTUNI_ALPHA, /* U+00b5ce: */
+ RTUNI_ALPHA, /* U+00b5cf: */
+ RTUNI_ALPHA, /* U+00b5d0: */
+ RTUNI_ALPHA, /* U+00b5d1: */
+ RTUNI_ALPHA, /* U+00b5d2: */
+ RTUNI_ALPHA, /* U+00b5d3: */
+ RTUNI_ALPHA, /* U+00b5d4: */
+ RTUNI_ALPHA, /* U+00b5d5: */
+ RTUNI_ALPHA, /* U+00b5d6: */
+ RTUNI_ALPHA, /* U+00b5d7: */
+ RTUNI_ALPHA, /* U+00b5d8: */
+ RTUNI_ALPHA, /* U+00b5d9: */
+ RTUNI_ALPHA, /* U+00b5da: */
+ RTUNI_ALPHA, /* U+00b5db: */
+ RTUNI_ALPHA, /* U+00b5dc: */
+ RTUNI_ALPHA, /* U+00b5dd: */
+ RTUNI_ALPHA, /* U+00b5de: */
+ RTUNI_ALPHA, /* U+00b5df: */
+ RTUNI_ALPHA, /* U+00b5e0: */
+ RTUNI_ALPHA, /* U+00b5e1: */
+ RTUNI_ALPHA, /* U+00b5e2: */
+ RTUNI_ALPHA, /* U+00b5e3: */
+ RTUNI_ALPHA, /* U+00b5e4: */
+ RTUNI_ALPHA, /* U+00b5e5: */
+ RTUNI_ALPHA, /* U+00b5e6: */
+ RTUNI_ALPHA, /* U+00b5e7: */
+ RTUNI_ALPHA, /* U+00b5e8: */
+ RTUNI_ALPHA, /* U+00b5e9: */
+ RTUNI_ALPHA, /* U+00b5ea: */
+ RTUNI_ALPHA, /* U+00b5eb: */
+ RTUNI_ALPHA, /* U+00b5ec: */
+ RTUNI_ALPHA, /* U+00b5ed: */
+ RTUNI_ALPHA, /* U+00b5ee: */
+ RTUNI_ALPHA, /* U+00b5ef: */
+ RTUNI_ALPHA, /* U+00b5f0: */
+ RTUNI_ALPHA, /* U+00b5f1: */
+ RTUNI_ALPHA, /* U+00b5f2: */
+ RTUNI_ALPHA, /* U+00b5f3: */
+ RTUNI_ALPHA, /* U+00b5f4: */
+ RTUNI_ALPHA, /* U+00b5f5: */
+ RTUNI_ALPHA, /* U+00b5f6: */
+ RTUNI_ALPHA, /* U+00b5f7: */
+ RTUNI_ALPHA, /* U+00b5f8: */
+ RTUNI_ALPHA, /* U+00b5f9: */
+ RTUNI_ALPHA, /* U+00b5fa: */
+ RTUNI_ALPHA, /* U+00b5fb: */
+ RTUNI_ALPHA, /* U+00b5fc: */
+ RTUNI_ALPHA, /* U+00b5fd: */
+ RTUNI_ALPHA, /* U+00b5fe: */
+ RTUNI_ALPHA, /* U+00b5ff: */
+ RTUNI_ALPHA, /* U+00b600: */
+ RTUNI_ALPHA, /* U+00b601: */
+ RTUNI_ALPHA, /* U+00b602: */
+ RTUNI_ALPHA, /* U+00b603: */
+ RTUNI_ALPHA, /* U+00b604: */
+ RTUNI_ALPHA, /* U+00b605: */
+ RTUNI_ALPHA, /* U+00b606: */
+ RTUNI_ALPHA, /* U+00b607: */
+ RTUNI_ALPHA, /* U+00b608: */
+ RTUNI_ALPHA, /* U+00b609: */
+ RTUNI_ALPHA, /* U+00b60a: */
+ RTUNI_ALPHA, /* U+00b60b: */
+ RTUNI_ALPHA, /* U+00b60c: */
+ RTUNI_ALPHA, /* U+00b60d: */
+ RTUNI_ALPHA, /* U+00b60e: */
+ RTUNI_ALPHA, /* U+00b60f: */
+ RTUNI_ALPHA, /* U+00b610: */
+ RTUNI_ALPHA, /* U+00b611: */
+ RTUNI_ALPHA, /* U+00b612: */
+ RTUNI_ALPHA, /* U+00b613: */
+ RTUNI_ALPHA, /* U+00b614: */
+ RTUNI_ALPHA, /* U+00b615: */
+ RTUNI_ALPHA, /* U+00b616: */
+ RTUNI_ALPHA, /* U+00b617: */
+ RTUNI_ALPHA, /* U+00b618: */
+ RTUNI_ALPHA, /* U+00b619: */
+ RTUNI_ALPHA, /* U+00b61a: */
+ RTUNI_ALPHA, /* U+00b61b: */
+ RTUNI_ALPHA, /* U+00b61c: */
+ RTUNI_ALPHA, /* U+00b61d: */
+ RTUNI_ALPHA, /* U+00b61e: */
+ RTUNI_ALPHA, /* U+00b61f: */
+ RTUNI_ALPHA, /* U+00b620: */
+ RTUNI_ALPHA, /* U+00b621: */
+ RTUNI_ALPHA, /* U+00b622: */
+ RTUNI_ALPHA, /* U+00b623: */
+ RTUNI_ALPHA, /* U+00b624: */
+ RTUNI_ALPHA, /* U+00b625: */
+ RTUNI_ALPHA, /* U+00b626: */
+ RTUNI_ALPHA, /* U+00b627: */
+ RTUNI_ALPHA, /* U+00b628: */
+ RTUNI_ALPHA, /* U+00b629: */
+ RTUNI_ALPHA, /* U+00b62a: */
+ RTUNI_ALPHA, /* U+00b62b: */
+ RTUNI_ALPHA, /* U+00b62c: */
+ RTUNI_ALPHA, /* U+00b62d: */
+ RTUNI_ALPHA, /* U+00b62e: */
+ RTUNI_ALPHA, /* U+00b62f: */
+ RTUNI_ALPHA, /* U+00b630: */
+ RTUNI_ALPHA, /* U+00b631: */
+ RTUNI_ALPHA, /* U+00b632: */
+ RTUNI_ALPHA, /* U+00b633: */
+ RTUNI_ALPHA, /* U+00b634: */
+ RTUNI_ALPHA, /* U+00b635: */
+ RTUNI_ALPHA, /* U+00b636: */
+ RTUNI_ALPHA, /* U+00b637: */
+ RTUNI_ALPHA, /* U+00b638: */
+ RTUNI_ALPHA, /* U+00b639: */
+ RTUNI_ALPHA, /* U+00b63a: */
+ RTUNI_ALPHA, /* U+00b63b: */
+ RTUNI_ALPHA, /* U+00b63c: */
+ RTUNI_ALPHA, /* U+00b63d: */
+ RTUNI_ALPHA, /* U+00b63e: */
+ RTUNI_ALPHA, /* U+00b63f: */
+ RTUNI_ALPHA, /* U+00b640: */
+ RTUNI_ALPHA, /* U+00b641: */
+ RTUNI_ALPHA, /* U+00b642: */
+ RTUNI_ALPHA, /* U+00b643: */
+ RTUNI_ALPHA, /* U+00b644: */
+ RTUNI_ALPHA, /* U+00b645: */
+ RTUNI_ALPHA, /* U+00b646: */
+ RTUNI_ALPHA, /* U+00b647: */
+ RTUNI_ALPHA, /* U+00b648: */
+ RTUNI_ALPHA, /* U+00b649: */
+ RTUNI_ALPHA, /* U+00b64a: */
+ RTUNI_ALPHA, /* U+00b64b: */
+ RTUNI_ALPHA, /* U+00b64c: */
+ RTUNI_ALPHA, /* U+00b64d: */
+ RTUNI_ALPHA, /* U+00b64e: */
+ RTUNI_ALPHA, /* U+00b64f: */
+ RTUNI_ALPHA, /* U+00b650: */
+ RTUNI_ALPHA, /* U+00b651: */
+ RTUNI_ALPHA, /* U+00b652: */
+ RTUNI_ALPHA, /* U+00b653: */
+ RTUNI_ALPHA, /* U+00b654: */
+ RTUNI_ALPHA, /* U+00b655: */
+ RTUNI_ALPHA, /* U+00b656: */
+ RTUNI_ALPHA, /* U+00b657: */
+ RTUNI_ALPHA, /* U+00b658: */
+ RTUNI_ALPHA, /* U+00b659: */
+ RTUNI_ALPHA, /* U+00b65a: */
+ RTUNI_ALPHA, /* U+00b65b: */
+ RTUNI_ALPHA, /* U+00b65c: */
+ RTUNI_ALPHA, /* U+00b65d: */
+ RTUNI_ALPHA, /* U+00b65e: */
+ RTUNI_ALPHA, /* U+00b65f: */
+ RTUNI_ALPHA, /* U+00b660: */
+ RTUNI_ALPHA, /* U+00b661: */
+ RTUNI_ALPHA, /* U+00b662: */
+ RTUNI_ALPHA, /* U+00b663: */
+ RTUNI_ALPHA, /* U+00b664: */
+ RTUNI_ALPHA, /* U+00b665: */
+ RTUNI_ALPHA, /* U+00b666: */
+ RTUNI_ALPHA, /* U+00b667: */
+ RTUNI_ALPHA, /* U+00b668: */
+ RTUNI_ALPHA, /* U+00b669: */
+ RTUNI_ALPHA, /* U+00b66a: */
+ RTUNI_ALPHA, /* U+00b66b: */
+ RTUNI_ALPHA, /* U+00b66c: */
+ RTUNI_ALPHA, /* U+00b66d: */
+ RTUNI_ALPHA, /* U+00b66e: */
+ RTUNI_ALPHA, /* U+00b66f: */
+ RTUNI_ALPHA, /* U+00b670: */
+ RTUNI_ALPHA, /* U+00b671: */
+ RTUNI_ALPHA, /* U+00b672: */
+ RTUNI_ALPHA, /* U+00b673: */
+ RTUNI_ALPHA, /* U+00b674: */
+ RTUNI_ALPHA, /* U+00b675: */
+ RTUNI_ALPHA, /* U+00b676: */
+ RTUNI_ALPHA, /* U+00b677: */
+ RTUNI_ALPHA, /* U+00b678: */
+ RTUNI_ALPHA, /* U+00b679: */
+ RTUNI_ALPHA, /* U+00b67a: */
+ RTUNI_ALPHA, /* U+00b67b: */
+ RTUNI_ALPHA, /* U+00b67c: */
+ RTUNI_ALPHA, /* U+00b67d: */
+ RTUNI_ALPHA, /* U+00b67e: */
+ RTUNI_ALPHA, /* U+00b67f: */
+ RTUNI_ALPHA, /* U+00b680: */
+ RTUNI_ALPHA, /* U+00b681: */
+ RTUNI_ALPHA, /* U+00b682: */
+ RTUNI_ALPHA, /* U+00b683: */
+ RTUNI_ALPHA, /* U+00b684: */
+ RTUNI_ALPHA, /* U+00b685: */
+ RTUNI_ALPHA, /* U+00b686: */
+ RTUNI_ALPHA, /* U+00b687: */
+ RTUNI_ALPHA, /* U+00b688: */
+ RTUNI_ALPHA, /* U+00b689: */
+ RTUNI_ALPHA, /* U+00b68a: */
+ RTUNI_ALPHA, /* U+00b68b: */
+ RTUNI_ALPHA, /* U+00b68c: */
+ RTUNI_ALPHA, /* U+00b68d: */
+ RTUNI_ALPHA, /* U+00b68e: */
+ RTUNI_ALPHA, /* U+00b68f: */
+ RTUNI_ALPHA, /* U+00b690: */
+ RTUNI_ALPHA, /* U+00b691: */
+ RTUNI_ALPHA, /* U+00b692: */
+ RTUNI_ALPHA, /* U+00b693: */
+ RTUNI_ALPHA, /* U+00b694: */
+ RTUNI_ALPHA, /* U+00b695: */
+ RTUNI_ALPHA, /* U+00b696: */
+ RTUNI_ALPHA, /* U+00b697: */
+ RTUNI_ALPHA, /* U+00b698: */
+ RTUNI_ALPHA, /* U+00b699: */
+ RTUNI_ALPHA, /* U+00b69a: */
+ RTUNI_ALPHA, /* U+00b69b: */
+ RTUNI_ALPHA, /* U+00b69c: */
+ RTUNI_ALPHA, /* U+00b69d: */
+ RTUNI_ALPHA, /* U+00b69e: */
+ RTUNI_ALPHA, /* U+00b69f: */
+ RTUNI_ALPHA, /* U+00b6a0: */
+ RTUNI_ALPHA, /* U+00b6a1: */
+ RTUNI_ALPHA, /* U+00b6a2: */
+ RTUNI_ALPHA, /* U+00b6a3: */
+ RTUNI_ALPHA, /* U+00b6a4: */
+ RTUNI_ALPHA, /* U+00b6a5: */
+ RTUNI_ALPHA, /* U+00b6a6: */
+ RTUNI_ALPHA, /* U+00b6a7: */
+ RTUNI_ALPHA, /* U+00b6a8: */
+ RTUNI_ALPHA, /* U+00b6a9: */
+ RTUNI_ALPHA, /* U+00b6aa: */
+ RTUNI_ALPHA, /* U+00b6ab: */
+ RTUNI_ALPHA, /* U+00b6ac: */
+ RTUNI_ALPHA, /* U+00b6ad: */
+ RTUNI_ALPHA, /* U+00b6ae: */
+ RTUNI_ALPHA, /* U+00b6af: */
+ RTUNI_ALPHA, /* U+00b6b0: */
+ RTUNI_ALPHA, /* U+00b6b1: */
+ RTUNI_ALPHA, /* U+00b6b2: */
+ RTUNI_ALPHA, /* U+00b6b3: */
+ RTUNI_ALPHA, /* U+00b6b4: */
+ RTUNI_ALPHA, /* U+00b6b5: */
+ RTUNI_ALPHA, /* U+00b6b6: */
+ RTUNI_ALPHA, /* U+00b6b7: */
+ RTUNI_ALPHA, /* U+00b6b8: */
+ RTUNI_ALPHA, /* U+00b6b9: */
+ RTUNI_ALPHA, /* U+00b6ba: */
+ RTUNI_ALPHA, /* U+00b6bb: */
+ RTUNI_ALPHA, /* U+00b6bc: */
+ RTUNI_ALPHA, /* U+00b6bd: */
+ RTUNI_ALPHA, /* U+00b6be: */
+ RTUNI_ALPHA, /* U+00b6bf: */
+ RTUNI_ALPHA, /* U+00b6c0: */
+ RTUNI_ALPHA, /* U+00b6c1: */
+ RTUNI_ALPHA, /* U+00b6c2: */
+ RTUNI_ALPHA, /* U+00b6c3: */
+ RTUNI_ALPHA, /* U+00b6c4: */
+ RTUNI_ALPHA, /* U+00b6c5: */
+ RTUNI_ALPHA, /* U+00b6c6: */
+ RTUNI_ALPHA, /* U+00b6c7: */
+ RTUNI_ALPHA, /* U+00b6c8: */
+ RTUNI_ALPHA, /* U+00b6c9: */
+ RTUNI_ALPHA, /* U+00b6ca: */
+ RTUNI_ALPHA, /* U+00b6cb: */
+ RTUNI_ALPHA, /* U+00b6cc: */
+ RTUNI_ALPHA, /* U+00b6cd: */
+ RTUNI_ALPHA, /* U+00b6ce: */
+ RTUNI_ALPHA, /* U+00b6cf: */
+ RTUNI_ALPHA, /* U+00b6d0: */
+ RTUNI_ALPHA, /* U+00b6d1: */
+ RTUNI_ALPHA, /* U+00b6d2: */
+ RTUNI_ALPHA, /* U+00b6d3: */
+ RTUNI_ALPHA, /* U+00b6d4: */
+ RTUNI_ALPHA, /* U+00b6d5: */
+ RTUNI_ALPHA, /* U+00b6d6: */
+ RTUNI_ALPHA, /* U+00b6d7: */
+ RTUNI_ALPHA, /* U+00b6d8: */
+ RTUNI_ALPHA, /* U+00b6d9: */
+ RTUNI_ALPHA, /* U+00b6da: */
+ RTUNI_ALPHA, /* U+00b6db: */
+ RTUNI_ALPHA, /* U+00b6dc: */
+ RTUNI_ALPHA, /* U+00b6dd: */
+ RTUNI_ALPHA, /* U+00b6de: */
+ RTUNI_ALPHA, /* U+00b6df: */
+ RTUNI_ALPHA, /* U+00b6e0: */
+ RTUNI_ALPHA, /* U+00b6e1: */
+ RTUNI_ALPHA, /* U+00b6e2: */
+ RTUNI_ALPHA, /* U+00b6e3: */
+ RTUNI_ALPHA, /* U+00b6e4: */
+ RTUNI_ALPHA, /* U+00b6e5: */
+ RTUNI_ALPHA, /* U+00b6e6: */
+ RTUNI_ALPHA, /* U+00b6e7: */
+ RTUNI_ALPHA, /* U+00b6e8: */
+ RTUNI_ALPHA, /* U+00b6e9: */
+ RTUNI_ALPHA, /* U+00b6ea: */
+ RTUNI_ALPHA, /* U+00b6eb: */
+ RTUNI_ALPHA, /* U+00b6ec: */
+ RTUNI_ALPHA, /* U+00b6ed: */
+ RTUNI_ALPHA, /* U+00b6ee: */
+ RTUNI_ALPHA, /* U+00b6ef: */
+ RTUNI_ALPHA, /* U+00b6f0: */
+ RTUNI_ALPHA, /* U+00b6f1: */
+ RTUNI_ALPHA, /* U+00b6f2: */
+ RTUNI_ALPHA, /* U+00b6f3: */
+ RTUNI_ALPHA, /* U+00b6f4: */
+ RTUNI_ALPHA, /* U+00b6f5: */
+ RTUNI_ALPHA, /* U+00b6f6: */
+ RTUNI_ALPHA, /* U+00b6f7: */
+ RTUNI_ALPHA, /* U+00b6f8: */
+ RTUNI_ALPHA, /* U+00b6f9: */
+ RTUNI_ALPHA, /* U+00b6fa: */
+ RTUNI_ALPHA, /* U+00b6fb: */
+ RTUNI_ALPHA, /* U+00b6fc: */
+ RTUNI_ALPHA, /* U+00b6fd: */
+ RTUNI_ALPHA, /* U+00b6fe: */
+ RTUNI_ALPHA, /* U+00b6ff: */
+ RTUNI_ALPHA, /* U+00b700: */
+ RTUNI_ALPHA, /* U+00b701: */
+ RTUNI_ALPHA, /* U+00b702: */
+ RTUNI_ALPHA, /* U+00b703: */
+ RTUNI_ALPHA, /* U+00b704: */
+ RTUNI_ALPHA, /* U+00b705: */
+ RTUNI_ALPHA, /* U+00b706: */
+ RTUNI_ALPHA, /* U+00b707: */
+ RTUNI_ALPHA, /* U+00b708: */
+ RTUNI_ALPHA, /* U+00b709: */
+ RTUNI_ALPHA, /* U+00b70a: */
+ RTUNI_ALPHA, /* U+00b70b: */
+ RTUNI_ALPHA, /* U+00b70c: */
+ RTUNI_ALPHA, /* U+00b70d: */
+ RTUNI_ALPHA, /* U+00b70e: */
+ RTUNI_ALPHA, /* U+00b70f: */
+ RTUNI_ALPHA, /* U+00b710: */
+ RTUNI_ALPHA, /* U+00b711: */
+ RTUNI_ALPHA, /* U+00b712: */
+ RTUNI_ALPHA, /* U+00b713: */
+ RTUNI_ALPHA, /* U+00b714: */
+ RTUNI_ALPHA, /* U+00b715: */
+ RTUNI_ALPHA, /* U+00b716: */
+ RTUNI_ALPHA, /* U+00b717: */
+ RTUNI_ALPHA, /* U+00b718: */
+ RTUNI_ALPHA, /* U+00b719: */
+ RTUNI_ALPHA, /* U+00b71a: */
+ RTUNI_ALPHA, /* U+00b71b: */
+ RTUNI_ALPHA, /* U+00b71c: */
+ RTUNI_ALPHA, /* U+00b71d: */
+ RTUNI_ALPHA, /* U+00b71e: */
+ RTUNI_ALPHA, /* U+00b71f: */
+ RTUNI_ALPHA, /* U+00b720: */
+ RTUNI_ALPHA, /* U+00b721: */
+ RTUNI_ALPHA, /* U+00b722: */
+ RTUNI_ALPHA, /* U+00b723: */
+ RTUNI_ALPHA, /* U+00b724: */
+ RTUNI_ALPHA, /* U+00b725: */
+ RTUNI_ALPHA, /* U+00b726: */
+ RTUNI_ALPHA, /* U+00b727: */
+ RTUNI_ALPHA, /* U+00b728: */
+ RTUNI_ALPHA, /* U+00b729: */
+ RTUNI_ALPHA, /* U+00b72a: */
+ RTUNI_ALPHA, /* U+00b72b: */
+ RTUNI_ALPHA, /* U+00b72c: */
+ RTUNI_ALPHA, /* U+00b72d: */
+ RTUNI_ALPHA, /* U+00b72e: */
+ RTUNI_ALPHA, /* U+00b72f: */
+ RTUNI_ALPHA, /* U+00b730: */
+ RTUNI_ALPHA, /* U+00b731: */
+ RTUNI_ALPHA, /* U+00b732: */
+ RTUNI_ALPHA, /* U+00b733: */
+ RTUNI_ALPHA, /* U+00b734: */
+ RTUNI_ALPHA, /* U+00b735: */
+ RTUNI_ALPHA, /* U+00b736: */
+ RTUNI_ALPHA, /* U+00b737: */
+ RTUNI_ALPHA, /* U+00b738: */
+ RTUNI_ALPHA, /* U+00b739: */
+ RTUNI_ALPHA, /* U+00b73a: */
+ RTUNI_ALPHA, /* U+00b73b: */
+ RTUNI_ALPHA, /* U+00b73c: */
+ RTUNI_ALPHA, /* U+00b73d: */
+ RTUNI_ALPHA, /* U+00b73e: */
+ RTUNI_ALPHA, /* U+00b73f: */
+ RTUNI_ALPHA, /* U+00b740: */
+ RTUNI_ALPHA, /* U+00b741: */
+ RTUNI_ALPHA, /* U+00b742: */
+ RTUNI_ALPHA, /* U+00b743: */
+ RTUNI_ALPHA, /* U+00b744: */
+ RTUNI_ALPHA, /* U+00b745: */
+ RTUNI_ALPHA, /* U+00b746: */
+ RTUNI_ALPHA, /* U+00b747: */
+ RTUNI_ALPHA, /* U+00b748: */
+ RTUNI_ALPHA, /* U+00b749: */
+ RTUNI_ALPHA, /* U+00b74a: */
+ RTUNI_ALPHA, /* U+00b74b: */
+ RTUNI_ALPHA, /* U+00b74c: */
+ RTUNI_ALPHA, /* U+00b74d: */
+ RTUNI_ALPHA, /* U+00b74e: */
+ RTUNI_ALPHA, /* U+00b74f: */
+ RTUNI_ALPHA, /* U+00b750: */
+ RTUNI_ALPHA, /* U+00b751: */
+ RTUNI_ALPHA, /* U+00b752: */
+ RTUNI_ALPHA, /* U+00b753: */
+ RTUNI_ALPHA, /* U+00b754: */
+ RTUNI_ALPHA, /* U+00b755: */
+ RTUNI_ALPHA, /* U+00b756: */
+ RTUNI_ALPHA, /* U+00b757: */
+ RTUNI_ALPHA, /* U+00b758: */
+ RTUNI_ALPHA, /* U+00b759: */
+ RTUNI_ALPHA, /* U+00b75a: */
+ RTUNI_ALPHA, /* U+00b75b: */
+ RTUNI_ALPHA, /* U+00b75c: */
+ RTUNI_ALPHA, /* U+00b75d: */
+ RTUNI_ALPHA, /* U+00b75e: */
+ RTUNI_ALPHA, /* U+00b75f: */
+ RTUNI_ALPHA, /* U+00b760: */
+ RTUNI_ALPHA, /* U+00b761: */
+ RTUNI_ALPHA, /* U+00b762: */
+ RTUNI_ALPHA, /* U+00b763: */
+ RTUNI_ALPHA, /* U+00b764: */
+ RTUNI_ALPHA, /* U+00b765: */
+ RTUNI_ALPHA, /* U+00b766: */
+ RTUNI_ALPHA, /* U+00b767: */
+ RTUNI_ALPHA, /* U+00b768: */
+ RTUNI_ALPHA, /* U+00b769: */
+ RTUNI_ALPHA, /* U+00b76a: */
+ RTUNI_ALPHA, /* U+00b76b: */
+ RTUNI_ALPHA, /* U+00b76c: */
+ RTUNI_ALPHA, /* U+00b76d: */
+ RTUNI_ALPHA, /* U+00b76e: */
+ RTUNI_ALPHA, /* U+00b76f: */
+ RTUNI_ALPHA, /* U+00b770: */
+ RTUNI_ALPHA, /* U+00b771: */
+ RTUNI_ALPHA, /* U+00b772: */
+ RTUNI_ALPHA, /* U+00b773: */
+ RTUNI_ALPHA, /* U+00b774: */
+ RTUNI_ALPHA, /* U+00b775: */
+ RTUNI_ALPHA, /* U+00b776: */
+ RTUNI_ALPHA, /* U+00b777: */
+ RTUNI_ALPHA, /* U+00b778: */
+ RTUNI_ALPHA, /* U+00b779: */
+ RTUNI_ALPHA, /* U+00b77a: */
+ RTUNI_ALPHA, /* U+00b77b: */
+ RTUNI_ALPHA, /* U+00b77c: */
+ RTUNI_ALPHA, /* U+00b77d: */
+ RTUNI_ALPHA, /* U+00b77e: */
+ RTUNI_ALPHA, /* U+00b77f: */
+ RTUNI_ALPHA, /* U+00b780: */
+ RTUNI_ALPHA, /* U+00b781: */
+ RTUNI_ALPHA, /* U+00b782: */
+ RTUNI_ALPHA, /* U+00b783: */
+ RTUNI_ALPHA, /* U+00b784: */
+ RTUNI_ALPHA, /* U+00b785: */
+ RTUNI_ALPHA, /* U+00b786: */
+ RTUNI_ALPHA, /* U+00b787: */
+ RTUNI_ALPHA, /* U+00b788: */
+ RTUNI_ALPHA, /* U+00b789: */
+ RTUNI_ALPHA, /* U+00b78a: */
+ RTUNI_ALPHA, /* U+00b78b: */
+ RTUNI_ALPHA, /* U+00b78c: */
+ RTUNI_ALPHA, /* U+00b78d: */
+ RTUNI_ALPHA, /* U+00b78e: */
+ RTUNI_ALPHA, /* U+00b78f: */
+ RTUNI_ALPHA, /* U+00b790: */
+ RTUNI_ALPHA, /* U+00b791: */
+ RTUNI_ALPHA, /* U+00b792: */
+ RTUNI_ALPHA, /* U+00b793: */
+ RTUNI_ALPHA, /* U+00b794: */
+ RTUNI_ALPHA, /* U+00b795: */
+ RTUNI_ALPHA, /* U+00b796: */
+ RTUNI_ALPHA, /* U+00b797: */
+ RTUNI_ALPHA, /* U+00b798: */
+ RTUNI_ALPHA, /* U+00b799: */
+ RTUNI_ALPHA, /* U+00b79a: */
+ RTUNI_ALPHA, /* U+00b79b: */
+ RTUNI_ALPHA, /* U+00b79c: */
+ RTUNI_ALPHA, /* U+00b79d: */
+ RTUNI_ALPHA, /* U+00b79e: */
+ RTUNI_ALPHA, /* U+00b79f: */
+ RTUNI_ALPHA, /* U+00b7a0: */
+ RTUNI_ALPHA, /* U+00b7a1: */
+ RTUNI_ALPHA, /* U+00b7a2: */
+ RTUNI_ALPHA, /* U+00b7a3: */
+ RTUNI_ALPHA, /* U+00b7a4: */
+ RTUNI_ALPHA, /* U+00b7a5: */
+ RTUNI_ALPHA, /* U+00b7a6: */
+ RTUNI_ALPHA, /* U+00b7a7: */
+ RTUNI_ALPHA, /* U+00b7a8: */
+ RTUNI_ALPHA, /* U+00b7a9: */
+ RTUNI_ALPHA, /* U+00b7aa: */
+ RTUNI_ALPHA, /* U+00b7ab: */
+ RTUNI_ALPHA, /* U+00b7ac: */
+ RTUNI_ALPHA, /* U+00b7ad: */
+ RTUNI_ALPHA, /* U+00b7ae: */
+ RTUNI_ALPHA, /* U+00b7af: */
+ RTUNI_ALPHA, /* U+00b7b0: */
+ RTUNI_ALPHA, /* U+00b7b1: */
+ RTUNI_ALPHA, /* U+00b7b2: */
+ RTUNI_ALPHA, /* U+00b7b3: */
+ RTUNI_ALPHA, /* U+00b7b4: */
+ RTUNI_ALPHA, /* U+00b7b5: */
+ RTUNI_ALPHA, /* U+00b7b6: */
+ RTUNI_ALPHA, /* U+00b7b7: */
+ RTUNI_ALPHA, /* U+00b7b8: */
+ RTUNI_ALPHA, /* U+00b7b9: */
+ RTUNI_ALPHA, /* U+00b7ba: */
+ RTUNI_ALPHA, /* U+00b7bb: */
+ RTUNI_ALPHA, /* U+00b7bc: */
+ RTUNI_ALPHA, /* U+00b7bd: */
+ RTUNI_ALPHA, /* U+00b7be: */
+ RTUNI_ALPHA, /* U+00b7bf: */
+ RTUNI_ALPHA, /* U+00b7c0: */
+ RTUNI_ALPHA, /* U+00b7c1: */
+ RTUNI_ALPHA, /* U+00b7c2: */
+ RTUNI_ALPHA, /* U+00b7c3: */
+ RTUNI_ALPHA, /* U+00b7c4: */
+ RTUNI_ALPHA, /* U+00b7c5: */
+ RTUNI_ALPHA, /* U+00b7c6: */
+ RTUNI_ALPHA, /* U+00b7c7: */
+ RTUNI_ALPHA, /* U+00b7c8: */
+ RTUNI_ALPHA, /* U+00b7c9: */
+ RTUNI_ALPHA, /* U+00b7ca: */
+ RTUNI_ALPHA, /* U+00b7cb: */
+ RTUNI_ALPHA, /* U+00b7cc: */
+ RTUNI_ALPHA, /* U+00b7cd: */
+ RTUNI_ALPHA, /* U+00b7ce: */
+ RTUNI_ALPHA, /* U+00b7cf: */
+ RTUNI_ALPHA, /* U+00b7d0: */
+ RTUNI_ALPHA, /* U+00b7d1: */
+ RTUNI_ALPHA, /* U+00b7d2: */
+ RTUNI_ALPHA, /* U+00b7d3: */
+ RTUNI_ALPHA, /* U+00b7d4: */
+ RTUNI_ALPHA, /* U+00b7d5: */
+ RTUNI_ALPHA, /* U+00b7d6: */
+ RTUNI_ALPHA, /* U+00b7d7: */
+ RTUNI_ALPHA, /* U+00b7d8: */
+ RTUNI_ALPHA, /* U+00b7d9: */
+ RTUNI_ALPHA, /* U+00b7da: */
+ RTUNI_ALPHA, /* U+00b7db: */
+ RTUNI_ALPHA, /* U+00b7dc: */
+ RTUNI_ALPHA, /* U+00b7dd: */
+ RTUNI_ALPHA, /* U+00b7de: */
+ RTUNI_ALPHA, /* U+00b7df: */
+ RTUNI_ALPHA, /* U+00b7e0: */
+ RTUNI_ALPHA, /* U+00b7e1: */
+ RTUNI_ALPHA, /* U+00b7e2: */
+ RTUNI_ALPHA, /* U+00b7e3: */
+ RTUNI_ALPHA, /* U+00b7e4: */
+ RTUNI_ALPHA, /* U+00b7e5: */
+ RTUNI_ALPHA, /* U+00b7e6: */
+ RTUNI_ALPHA, /* U+00b7e7: */
+ RTUNI_ALPHA, /* U+00b7e8: */
+ RTUNI_ALPHA, /* U+00b7e9: */
+ RTUNI_ALPHA, /* U+00b7ea: */
+ RTUNI_ALPHA, /* U+00b7eb: */
+ RTUNI_ALPHA, /* U+00b7ec: */
+ RTUNI_ALPHA, /* U+00b7ed: */
+ RTUNI_ALPHA, /* U+00b7ee: */
+ RTUNI_ALPHA, /* U+00b7ef: */
+ RTUNI_ALPHA, /* U+00b7f0: */
+ RTUNI_ALPHA, /* U+00b7f1: */
+ RTUNI_ALPHA, /* U+00b7f2: */
+ RTUNI_ALPHA, /* U+00b7f3: */
+ RTUNI_ALPHA, /* U+00b7f4: */
+ RTUNI_ALPHA, /* U+00b7f5: */
+ RTUNI_ALPHA, /* U+00b7f6: */
+ RTUNI_ALPHA, /* U+00b7f7: */
+ RTUNI_ALPHA, /* U+00b7f8: */
+ RTUNI_ALPHA, /* U+00b7f9: */
+ RTUNI_ALPHA, /* U+00b7fa: */
+ RTUNI_ALPHA, /* U+00b7fb: */
+ RTUNI_ALPHA, /* U+00b7fc: */
+ RTUNI_ALPHA, /* U+00b7fd: */
+ RTUNI_ALPHA, /* U+00b7fe: */
+ RTUNI_ALPHA, /* U+00b7ff: */
+ RTUNI_ALPHA, /* U+00b800: */
+ RTUNI_ALPHA, /* U+00b801: */
+ RTUNI_ALPHA, /* U+00b802: */
+ RTUNI_ALPHA, /* U+00b803: */
+ RTUNI_ALPHA, /* U+00b804: */
+ RTUNI_ALPHA, /* U+00b805: */
+ RTUNI_ALPHA, /* U+00b806: */
+ RTUNI_ALPHA, /* U+00b807: */
+ RTUNI_ALPHA, /* U+00b808: */
+ RTUNI_ALPHA, /* U+00b809: */
+ RTUNI_ALPHA, /* U+00b80a: */
+ RTUNI_ALPHA, /* U+00b80b: */
+ RTUNI_ALPHA, /* U+00b80c: */
+ RTUNI_ALPHA, /* U+00b80d: */
+ RTUNI_ALPHA, /* U+00b80e: */
+ RTUNI_ALPHA, /* U+00b80f: */
+ RTUNI_ALPHA, /* U+00b810: */
+ RTUNI_ALPHA, /* U+00b811: */
+ RTUNI_ALPHA, /* U+00b812: */
+ RTUNI_ALPHA, /* U+00b813: */
+ RTUNI_ALPHA, /* U+00b814: */
+ RTUNI_ALPHA, /* U+00b815: */
+ RTUNI_ALPHA, /* U+00b816: */
+ RTUNI_ALPHA, /* U+00b817: */
+ RTUNI_ALPHA, /* U+00b818: */
+ RTUNI_ALPHA, /* U+00b819: */
+ RTUNI_ALPHA, /* U+00b81a: */
+ RTUNI_ALPHA, /* U+00b81b: */
+ RTUNI_ALPHA, /* U+00b81c: */
+ RTUNI_ALPHA, /* U+00b81d: */
+ RTUNI_ALPHA, /* U+00b81e: */
+ RTUNI_ALPHA, /* U+00b81f: */
+ RTUNI_ALPHA, /* U+00b820: */
+ RTUNI_ALPHA, /* U+00b821: */
+ RTUNI_ALPHA, /* U+00b822: */
+ RTUNI_ALPHA, /* U+00b823: */
+ RTUNI_ALPHA, /* U+00b824: */
+ RTUNI_ALPHA, /* U+00b825: */
+ RTUNI_ALPHA, /* U+00b826: */
+ RTUNI_ALPHA, /* U+00b827: */
+ RTUNI_ALPHA, /* U+00b828: */
+ RTUNI_ALPHA, /* U+00b829: */
+ RTUNI_ALPHA, /* U+00b82a: */
+ RTUNI_ALPHA, /* U+00b82b: */
+ RTUNI_ALPHA, /* U+00b82c: */
+ RTUNI_ALPHA, /* U+00b82d: */
+ RTUNI_ALPHA, /* U+00b82e: */
+ RTUNI_ALPHA, /* U+00b82f: */
+ RTUNI_ALPHA, /* U+00b830: */
+ RTUNI_ALPHA, /* U+00b831: */
+ RTUNI_ALPHA, /* U+00b832: */
+ RTUNI_ALPHA, /* U+00b833: */
+ RTUNI_ALPHA, /* U+00b834: */
+ RTUNI_ALPHA, /* U+00b835: */
+ RTUNI_ALPHA, /* U+00b836: */
+ RTUNI_ALPHA, /* U+00b837: */
+ RTUNI_ALPHA, /* U+00b838: */
+ RTUNI_ALPHA, /* U+00b839: */
+ RTUNI_ALPHA, /* U+00b83a: */
+ RTUNI_ALPHA, /* U+00b83b: */
+ RTUNI_ALPHA, /* U+00b83c: */
+ RTUNI_ALPHA, /* U+00b83d: */
+ RTUNI_ALPHA, /* U+00b83e: */
+ RTUNI_ALPHA, /* U+00b83f: */
+ RTUNI_ALPHA, /* U+00b840: */
+ RTUNI_ALPHA, /* U+00b841: */
+ RTUNI_ALPHA, /* U+00b842: */
+ RTUNI_ALPHA, /* U+00b843: */
+ RTUNI_ALPHA, /* U+00b844: */
+ RTUNI_ALPHA, /* U+00b845: */
+ RTUNI_ALPHA, /* U+00b846: */
+ RTUNI_ALPHA, /* U+00b847: */
+ RTUNI_ALPHA, /* U+00b848: */
+ RTUNI_ALPHA, /* U+00b849: */
+ RTUNI_ALPHA, /* U+00b84a: */
+ RTUNI_ALPHA, /* U+00b84b: */
+ RTUNI_ALPHA, /* U+00b84c: */
+ RTUNI_ALPHA, /* U+00b84d: */
+ RTUNI_ALPHA, /* U+00b84e: */
+ RTUNI_ALPHA, /* U+00b84f: */
+ RTUNI_ALPHA, /* U+00b850: */
+ RTUNI_ALPHA, /* U+00b851: */
+ RTUNI_ALPHA, /* U+00b852: */
+ RTUNI_ALPHA, /* U+00b853: */
+ RTUNI_ALPHA, /* U+00b854: */
+ RTUNI_ALPHA, /* U+00b855: */
+ RTUNI_ALPHA, /* U+00b856: */
+ RTUNI_ALPHA, /* U+00b857: */
+ RTUNI_ALPHA, /* U+00b858: */
+ RTUNI_ALPHA, /* U+00b859: */
+ RTUNI_ALPHA, /* U+00b85a: */
+ RTUNI_ALPHA, /* U+00b85b: */
+ RTUNI_ALPHA, /* U+00b85c: */
+ RTUNI_ALPHA, /* U+00b85d: */
+ RTUNI_ALPHA, /* U+00b85e: */
+ RTUNI_ALPHA, /* U+00b85f: */
+ RTUNI_ALPHA, /* U+00b860: */
+ RTUNI_ALPHA, /* U+00b861: */
+ RTUNI_ALPHA, /* U+00b862: */
+ RTUNI_ALPHA, /* U+00b863: */
+ RTUNI_ALPHA, /* U+00b864: */
+ RTUNI_ALPHA, /* U+00b865: */
+ RTUNI_ALPHA, /* U+00b866: */
+ RTUNI_ALPHA, /* U+00b867: */
+ RTUNI_ALPHA, /* U+00b868: */
+ RTUNI_ALPHA, /* U+00b869: */
+ RTUNI_ALPHA, /* U+00b86a: */
+ RTUNI_ALPHA, /* U+00b86b: */
+ RTUNI_ALPHA, /* U+00b86c: */
+ RTUNI_ALPHA, /* U+00b86d: */
+ RTUNI_ALPHA, /* U+00b86e: */
+ RTUNI_ALPHA, /* U+00b86f: */
+ RTUNI_ALPHA, /* U+00b870: */
+ RTUNI_ALPHA, /* U+00b871: */
+ RTUNI_ALPHA, /* U+00b872: */
+ RTUNI_ALPHA, /* U+00b873: */
+ RTUNI_ALPHA, /* U+00b874: */
+ RTUNI_ALPHA, /* U+00b875: */
+ RTUNI_ALPHA, /* U+00b876: */
+ RTUNI_ALPHA, /* U+00b877: */
+ RTUNI_ALPHA, /* U+00b878: */
+ RTUNI_ALPHA, /* U+00b879: */
+ RTUNI_ALPHA, /* U+00b87a: */
+ RTUNI_ALPHA, /* U+00b87b: */
+ RTUNI_ALPHA, /* U+00b87c: */
+ RTUNI_ALPHA, /* U+00b87d: */
+ RTUNI_ALPHA, /* U+00b87e: */
+ RTUNI_ALPHA, /* U+00b87f: */
+ RTUNI_ALPHA, /* U+00b880: */
+ RTUNI_ALPHA, /* U+00b881: */
+ RTUNI_ALPHA, /* U+00b882: */
+ RTUNI_ALPHA, /* U+00b883: */
+ RTUNI_ALPHA, /* U+00b884: */
+ RTUNI_ALPHA, /* U+00b885: */
+ RTUNI_ALPHA, /* U+00b886: */
+ RTUNI_ALPHA, /* U+00b887: */
+ RTUNI_ALPHA, /* U+00b888: */
+ RTUNI_ALPHA, /* U+00b889: */
+ RTUNI_ALPHA, /* U+00b88a: */
+ RTUNI_ALPHA, /* U+00b88b: */
+ RTUNI_ALPHA, /* U+00b88c: */
+ RTUNI_ALPHA, /* U+00b88d: */
+ RTUNI_ALPHA, /* U+00b88e: */
+ RTUNI_ALPHA, /* U+00b88f: */
+ RTUNI_ALPHA, /* U+00b890: */
+ RTUNI_ALPHA, /* U+00b891: */
+ RTUNI_ALPHA, /* U+00b892: */
+ RTUNI_ALPHA, /* U+00b893: */
+ RTUNI_ALPHA, /* U+00b894: */
+ RTUNI_ALPHA, /* U+00b895: */
+ RTUNI_ALPHA, /* U+00b896: */
+ RTUNI_ALPHA, /* U+00b897: */
+ RTUNI_ALPHA, /* U+00b898: */
+ RTUNI_ALPHA, /* U+00b899: */
+ RTUNI_ALPHA, /* U+00b89a: */
+ RTUNI_ALPHA, /* U+00b89b: */
+ RTUNI_ALPHA, /* U+00b89c: */
+ RTUNI_ALPHA, /* U+00b89d: */
+ RTUNI_ALPHA, /* U+00b89e: */
+ RTUNI_ALPHA, /* U+00b89f: */
+ RTUNI_ALPHA, /* U+00b8a0: */
+ RTUNI_ALPHA, /* U+00b8a1: */
+ RTUNI_ALPHA, /* U+00b8a2: */
+ RTUNI_ALPHA, /* U+00b8a3: */
+ RTUNI_ALPHA, /* U+00b8a4: */
+ RTUNI_ALPHA, /* U+00b8a5: */
+ RTUNI_ALPHA, /* U+00b8a6: */
+ RTUNI_ALPHA, /* U+00b8a7: */
+ RTUNI_ALPHA, /* U+00b8a8: */
+ RTUNI_ALPHA, /* U+00b8a9: */
+ RTUNI_ALPHA, /* U+00b8aa: */
+ RTUNI_ALPHA, /* U+00b8ab: */
+ RTUNI_ALPHA, /* U+00b8ac: */
+ RTUNI_ALPHA, /* U+00b8ad: */
+ RTUNI_ALPHA, /* U+00b8ae: */
+ RTUNI_ALPHA, /* U+00b8af: */
+ RTUNI_ALPHA, /* U+00b8b0: */
+ RTUNI_ALPHA, /* U+00b8b1: */
+ RTUNI_ALPHA, /* U+00b8b2: */
+ RTUNI_ALPHA, /* U+00b8b3: */
+ RTUNI_ALPHA, /* U+00b8b4: */
+ RTUNI_ALPHA, /* U+00b8b5: */
+ RTUNI_ALPHA, /* U+00b8b6: */
+ RTUNI_ALPHA, /* U+00b8b7: */
+ RTUNI_ALPHA, /* U+00b8b8: */
+ RTUNI_ALPHA, /* U+00b8b9: */
+ RTUNI_ALPHA, /* U+00b8ba: */
+ RTUNI_ALPHA, /* U+00b8bb: */
+ RTUNI_ALPHA, /* U+00b8bc: */
+ RTUNI_ALPHA, /* U+00b8bd: */
+ RTUNI_ALPHA, /* U+00b8be: */
+ RTUNI_ALPHA, /* U+00b8bf: */
+ RTUNI_ALPHA, /* U+00b8c0: */
+ RTUNI_ALPHA, /* U+00b8c1: */
+ RTUNI_ALPHA, /* U+00b8c2: */
+ RTUNI_ALPHA, /* U+00b8c3: */
+ RTUNI_ALPHA, /* U+00b8c4: */
+ RTUNI_ALPHA, /* U+00b8c5: */
+ RTUNI_ALPHA, /* U+00b8c6: */
+ RTUNI_ALPHA, /* U+00b8c7: */
+ RTUNI_ALPHA, /* U+00b8c8: */
+ RTUNI_ALPHA, /* U+00b8c9: */
+ RTUNI_ALPHA, /* U+00b8ca: */
+ RTUNI_ALPHA, /* U+00b8cb: */
+ RTUNI_ALPHA, /* U+00b8cc: */
+ RTUNI_ALPHA, /* U+00b8cd: */
+ RTUNI_ALPHA, /* U+00b8ce: */
+ RTUNI_ALPHA, /* U+00b8cf: */
+ RTUNI_ALPHA, /* U+00b8d0: */
+ RTUNI_ALPHA, /* U+00b8d1: */
+ RTUNI_ALPHA, /* U+00b8d2: */
+ RTUNI_ALPHA, /* U+00b8d3: */
+ RTUNI_ALPHA, /* U+00b8d4: */
+ RTUNI_ALPHA, /* U+00b8d5: */
+ RTUNI_ALPHA, /* U+00b8d6: */
+ RTUNI_ALPHA, /* U+00b8d7: */
+ RTUNI_ALPHA, /* U+00b8d8: */
+ RTUNI_ALPHA, /* U+00b8d9: */
+ RTUNI_ALPHA, /* U+00b8da: */
+ RTUNI_ALPHA, /* U+00b8db: */
+ RTUNI_ALPHA, /* U+00b8dc: */
+ RTUNI_ALPHA, /* U+00b8dd: */
+ RTUNI_ALPHA, /* U+00b8de: */
+ RTUNI_ALPHA, /* U+00b8df: */
+ RTUNI_ALPHA, /* U+00b8e0: */
+ RTUNI_ALPHA, /* U+00b8e1: */
+ RTUNI_ALPHA, /* U+00b8e2: */
+ RTUNI_ALPHA, /* U+00b8e3: */
+ RTUNI_ALPHA, /* U+00b8e4: */
+ RTUNI_ALPHA, /* U+00b8e5: */
+ RTUNI_ALPHA, /* U+00b8e6: */
+ RTUNI_ALPHA, /* U+00b8e7: */
+ RTUNI_ALPHA, /* U+00b8e8: */
+ RTUNI_ALPHA, /* U+00b8e9: */
+ RTUNI_ALPHA, /* U+00b8ea: */
+ RTUNI_ALPHA, /* U+00b8eb: */
+ RTUNI_ALPHA, /* U+00b8ec: */
+ RTUNI_ALPHA, /* U+00b8ed: */
+ RTUNI_ALPHA, /* U+00b8ee: */
+ RTUNI_ALPHA, /* U+00b8ef: */
+ RTUNI_ALPHA, /* U+00b8f0: */
+ RTUNI_ALPHA, /* U+00b8f1: */
+ RTUNI_ALPHA, /* U+00b8f2: */
+ RTUNI_ALPHA, /* U+00b8f3: */
+ RTUNI_ALPHA, /* U+00b8f4: */
+ RTUNI_ALPHA, /* U+00b8f5: */
+ RTUNI_ALPHA, /* U+00b8f6: */
+ RTUNI_ALPHA, /* U+00b8f7: */
+ RTUNI_ALPHA, /* U+00b8f8: */
+ RTUNI_ALPHA, /* U+00b8f9: */
+ RTUNI_ALPHA, /* U+00b8fa: */
+ RTUNI_ALPHA, /* U+00b8fb: */
+ RTUNI_ALPHA, /* U+00b8fc: */
+ RTUNI_ALPHA, /* U+00b8fd: */
+ RTUNI_ALPHA, /* U+00b8fe: */
+ RTUNI_ALPHA, /* U+00b8ff: */
+ RTUNI_ALPHA, /* U+00b900: */
+ RTUNI_ALPHA, /* U+00b901: */
+ RTUNI_ALPHA, /* U+00b902: */
+ RTUNI_ALPHA, /* U+00b903: */
+ RTUNI_ALPHA, /* U+00b904: */
+ RTUNI_ALPHA, /* U+00b905: */
+ RTUNI_ALPHA, /* U+00b906: */
+ RTUNI_ALPHA, /* U+00b907: */
+ RTUNI_ALPHA, /* U+00b908: */
+ RTUNI_ALPHA, /* U+00b909: */
+ RTUNI_ALPHA, /* U+00b90a: */
+ RTUNI_ALPHA, /* U+00b90b: */
+ RTUNI_ALPHA, /* U+00b90c: */
+ RTUNI_ALPHA, /* U+00b90d: */
+ RTUNI_ALPHA, /* U+00b90e: */
+ RTUNI_ALPHA, /* U+00b90f: */
+ RTUNI_ALPHA, /* U+00b910: */
+ RTUNI_ALPHA, /* U+00b911: */
+ RTUNI_ALPHA, /* U+00b912: */
+ RTUNI_ALPHA, /* U+00b913: */
+ RTUNI_ALPHA, /* U+00b914: */
+ RTUNI_ALPHA, /* U+00b915: */
+ RTUNI_ALPHA, /* U+00b916: */
+ RTUNI_ALPHA, /* U+00b917: */
+ RTUNI_ALPHA, /* U+00b918: */
+ RTUNI_ALPHA, /* U+00b919: */
+ RTUNI_ALPHA, /* U+00b91a: */
+ RTUNI_ALPHA, /* U+00b91b: */
+ RTUNI_ALPHA, /* U+00b91c: */
+ RTUNI_ALPHA, /* U+00b91d: */
+ RTUNI_ALPHA, /* U+00b91e: */
+ RTUNI_ALPHA, /* U+00b91f: */
+ RTUNI_ALPHA, /* U+00b920: */
+ RTUNI_ALPHA, /* U+00b921: */
+ RTUNI_ALPHA, /* U+00b922: */
+ RTUNI_ALPHA, /* U+00b923: */
+ RTUNI_ALPHA, /* U+00b924: */
+ RTUNI_ALPHA, /* U+00b925: */
+ RTUNI_ALPHA, /* U+00b926: */
+ RTUNI_ALPHA, /* U+00b927: */
+ RTUNI_ALPHA, /* U+00b928: */
+ RTUNI_ALPHA, /* U+00b929: */
+ RTUNI_ALPHA, /* U+00b92a: */
+ RTUNI_ALPHA, /* U+00b92b: */
+ RTUNI_ALPHA, /* U+00b92c: */
+ RTUNI_ALPHA, /* U+00b92d: */
+ RTUNI_ALPHA, /* U+00b92e: */
+ RTUNI_ALPHA, /* U+00b92f: */
+ RTUNI_ALPHA, /* U+00b930: */
+ RTUNI_ALPHA, /* U+00b931: */
+ RTUNI_ALPHA, /* U+00b932: */
+ RTUNI_ALPHA, /* U+00b933: */
+ RTUNI_ALPHA, /* U+00b934: */
+ RTUNI_ALPHA, /* U+00b935: */
+ RTUNI_ALPHA, /* U+00b936: */
+ RTUNI_ALPHA, /* U+00b937: */
+ RTUNI_ALPHA, /* U+00b938: */
+ RTUNI_ALPHA, /* U+00b939: */
+ RTUNI_ALPHA, /* U+00b93a: */
+ RTUNI_ALPHA, /* U+00b93b: */
+ RTUNI_ALPHA, /* U+00b93c: */
+ RTUNI_ALPHA, /* U+00b93d: */
+ RTUNI_ALPHA, /* U+00b93e: */
+ RTUNI_ALPHA, /* U+00b93f: */
+ RTUNI_ALPHA, /* U+00b940: */
+ RTUNI_ALPHA, /* U+00b941: */
+ RTUNI_ALPHA, /* U+00b942: */
+ RTUNI_ALPHA, /* U+00b943: */
+ RTUNI_ALPHA, /* U+00b944: */
+ RTUNI_ALPHA, /* U+00b945: */
+ RTUNI_ALPHA, /* U+00b946: */
+ RTUNI_ALPHA, /* U+00b947: */
+ RTUNI_ALPHA, /* U+00b948: */
+ RTUNI_ALPHA, /* U+00b949: */
+ RTUNI_ALPHA, /* U+00b94a: */
+ RTUNI_ALPHA, /* U+00b94b: */
+ RTUNI_ALPHA, /* U+00b94c: */
+ RTUNI_ALPHA, /* U+00b94d: */
+ RTUNI_ALPHA, /* U+00b94e: */
+ RTUNI_ALPHA, /* U+00b94f: */
+ RTUNI_ALPHA, /* U+00b950: */
+ RTUNI_ALPHA, /* U+00b951: */
+ RTUNI_ALPHA, /* U+00b952: */
+ RTUNI_ALPHA, /* U+00b953: */
+ RTUNI_ALPHA, /* U+00b954: */
+ RTUNI_ALPHA, /* U+00b955: */
+ RTUNI_ALPHA, /* U+00b956: */
+ RTUNI_ALPHA, /* U+00b957: */
+ RTUNI_ALPHA, /* U+00b958: */
+ RTUNI_ALPHA, /* U+00b959: */
+ RTUNI_ALPHA, /* U+00b95a: */
+ RTUNI_ALPHA, /* U+00b95b: */
+ RTUNI_ALPHA, /* U+00b95c: */
+ RTUNI_ALPHA, /* U+00b95d: */
+ RTUNI_ALPHA, /* U+00b95e: */
+ RTUNI_ALPHA, /* U+00b95f: */
+ RTUNI_ALPHA, /* U+00b960: */
+ RTUNI_ALPHA, /* U+00b961: */
+ RTUNI_ALPHA, /* U+00b962: */
+ RTUNI_ALPHA, /* U+00b963: */
+ RTUNI_ALPHA, /* U+00b964: */
+ RTUNI_ALPHA, /* U+00b965: */
+ RTUNI_ALPHA, /* U+00b966: */
+ RTUNI_ALPHA, /* U+00b967: */
+ RTUNI_ALPHA, /* U+00b968: */
+ RTUNI_ALPHA, /* U+00b969: */
+ RTUNI_ALPHA, /* U+00b96a: */
+ RTUNI_ALPHA, /* U+00b96b: */
+ RTUNI_ALPHA, /* U+00b96c: */
+ RTUNI_ALPHA, /* U+00b96d: */
+ RTUNI_ALPHA, /* U+00b96e: */
+ RTUNI_ALPHA, /* U+00b96f: */
+ RTUNI_ALPHA, /* U+00b970: */
+ RTUNI_ALPHA, /* U+00b971: */
+ RTUNI_ALPHA, /* U+00b972: */
+ RTUNI_ALPHA, /* U+00b973: */
+ RTUNI_ALPHA, /* U+00b974: */
+ RTUNI_ALPHA, /* U+00b975: */
+ RTUNI_ALPHA, /* U+00b976: */
+ RTUNI_ALPHA, /* U+00b977: */
+ RTUNI_ALPHA, /* U+00b978: */
+ RTUNI_ALPHA, /* U+00b979: */
+ RTUNI_ALPHA, /* U+00b97a: */
+ RTUNI_ALPHA, /* U+00b97b: */
+ RTUNI_ALPHA, /* U+00b97c: */
+ RTUNI_ALPHA, /* U+00b97d: */
+ RTUNI_ALPHA, /* U+00b97e: */
+ RTUNI_ALPHA, /* U+00b97f: */
+ RTUNI_ALPHA, /* U+00b980: */
+ RTUNI_ALPHA, /* U+00b981: */
+ RTUNI_ALPHA, /* U+00b982: */
+ RTUNI_ALPHA, /* U+00b983: */
+ RTUNI_ALPHA, /* U+00b984: */
+ RTUNI_ALPHA, /* U+00b985: */
+ RTUNI_ALPHA, /* U+00b986: */
+ RTUNI_ALPHA, /* U+00b987: */
+ RTUNI_ALPHA, /* U+00b988: */
+ RTUNI_ALPHA, /* U+00b989: */
+ RTUNI_ALPHA, /* U+00b98a: */
+ RTUNI_ALPHA, /* U+00b98b: */
+ RTUNI_ALPHA, /* U+00b98c: */
+ RTUNI_ALPHA, /* U+00b98d: */
+ RTUNI_ALPHA, /* U+00b98e: */
+ RTUNI_ALPHA, /* U+00b98f: */
+ RTUNI_ALPHA, /* U+00b990: */
+ RTUNI_ALPHA, /* U+00b991: */
+ RTUNI_ALPHA, /* U+00b992: */
+ RTUNI_ALPHA, /* U+00b993: */
+ RTUNI_ALPHA, /* U+00b994: */
+ RTUNI_ALPHA, /* U+00b995: */
+ RTUNI_ALPHA, /* U+00b996: */
+ RTUNI_ALPHA, /* U+00b997: */
+ RTUNI_ALPHA, /* U+00b998: */
+ RTUNI_ALPHA, /* U+00b999: */
+ RTUNI_ALPHA, /* U+00b99a: */
+ RTUNI_ALPHA, /* U+00b99b: */
+ RTUNI_ALPHA, /* U+00b99c: */
+ RTUNI_ALPHA, /* U+00b99d: */
+ RTUNI_ALPHA, /* U+00b99e: */
+ RTUNI_ALPHA, /* U+00b99f: */
+ RTUNI_ALPHA, /* U+00b9a0: */
+ RTUNI_ALPHA, /* U+00b9a1: */
+ RTUNI_ALPHA, /* U+00b9a2: */
+ RTUNI_ALPHA, /* U+00b9a3: */
+ RTUNI_ALPHA, /* U+00b9a4: */
+ RTUNI_ALPHA, /* U+00b9a5: */
+ RTUNI_ALPHA, /* U+00b9a6: */
+ RTUNI_ALPHA, /* U+00b9a7: */
+ RTUNI_ALPHA, /* U+00b9a8: */
+ RTUNI_ALPHA, /* U+00b9a9: */
+ RTUNI_ALPHA, /* U+00b9aa: */
+ RTUNI_ALPHA, /* U+00b9ab: */
+ RTUNI_ALPHA, /* U+00b9ac: */
+ RTUNI_ALPHA, /* U+00b9ad: */
+ RTUNI_ALPHA, /* U+00b9ae: */
+ RTUNI_ALPHA, /* U+00b9af: */
+ RTUNI_ALPHA, /* U+00b9b0: */
+ RTUNI_ALPHA, /* U+00b9b1: */
+ RTUNI_ALPHA, /* U+00b9b2: */
+ RTUNI_ALPHA, /* U+00b9b3: */
+ RTUNI_ALPHA, /* U+00b9b4: */
+ RTUNI_ALPHA, /* U+00b9b5: */
+ RTUNI_ALPHA, /* U+00b9b6: */
+ RTUNI_ALPHA, /* U+00b9b7: */
+ RTUNI_ALPHA, /* U+00b9b8: */
+ RTUNI_ALPHA, /* U+00b9b9: */
+ RTUNI_ALPHA, /* U+00b9ba: */
+ RTUNI_ALPHA, /* U+00b9bb: */
+ RTUNI_ALPHA, /* U+00b9bc: */
+ RTUNI_ALPHA, /* U+00b9bd: */
+ RTUNI_ALPHA, /* U+00b9be: */
+ RTUNI_ALPHA, /* U+00b9bf: */
+ RTUNI_ALPHA, /* U+00b9c0: */
+ RTUNI_ALPHA, /* U+00b9c1: */
+ RTUNI_ALPHA, /* U+00b9c2: */
+ RTUNI_ALPHA, /* U+00b9c3: */
+ RTUNI_ALPHA, /* U+00b9c4: */
+ RTUNI_ALPHA, /* U+00b9c5: */
+ RTUNI_ALPHA, /* U+00b9c6: */
+ RTUNI_ALPHA, /* U+00b9c7: */
+ RTUNI_ALPHA, /* U+00b9c8: */
+ RTUNI_ALPHA, /* U+00b9c9: */
+ RTUNI_ALPHA, /* U+00b9ca: */
+ RTUNI_ALPHA, /* U+00b9cb: */
+ RTUNI_ALPHA, /* U+00b9cc: */
+ RTUNI_ALPHA, /* U+00b9cd: */
+ RTUNI_ALPHA, /* U+00b9ce: */
+ RTUNI_ALPHA, /* U+00b9cf: */
+ RTUNI_ALPHA, /* U+00b9d0: */
+ RTUNI_ALPHA, /* U+00b9d1: */
+ RTUNI_ALPHA, /* U+00b9d2: */
+ RTUNI_ALPHA, /* U+00b9d3: */
+ RTUNI_ALPHA, /* U+00b9d4: */
+ RTUNI_ALPHA, /* U+00b9d5: */
+ RTUNI_ALPHA, /* U+00b9d6: */
+ RTUNI_ALPHA, /* U+00b9d7: */
+ RTUNI_ALPHA, /* U+00b9d8: */
+ RTUNI_ALPHA, /* U+00b9d9: */
+ RTUNI_ALPHA, /* U+00b9da: */
+ RTUNI_ALPHA, /* U+00b9db: */
+ RTUNI_ALPHA, /* U+00b9dc: */
+ RTUNI_ALPHA, /* U+00b9dd: */
+ RTUNI_ALPHA, /* U+00b9de: */
+ RTUNI_ALPHA, /* U+00b9df: */
+ RTUNI_ALPHA, /* U+00b9e0: */
+ RTUNI_ALPHA, /* U+00b9e1: */
+ RTUNI_ALPHA, /* U+00b9e2: */
+ RTUNI_ALPHA, /* U+00b9e3: */
+ RTUNI_ALPHA, /* U+00b9e4: */
+ RTUNI_ALPHA, /* U+00b9e5: */
+ RTUNI_ALPHA, /* U+00b9e6: */
+ RTUNI_ALPHA, /* U+00b9e7: */
+ RTUNI_ALPHA, /* U+00b9e8: */
+ RTUNI_ALPHA, /* U+00b9e9: */
+ RTUNI_ALPHA, /* U+00b9ea: */
+ RTUNI_ALPHA, /* U+00b9eb: */
+ RTUNI_ALPHA, /* U+00b9ec: */
+ RTUNI_ALPHA, /* U+00b9ed: */
+ RTUNI_ALPHA, /* U+00b9ee: */
+ RTUNI_ALPHA, /* U+00b9ef: */
+ RTUNI_ALPHA, /* U+00b9f0: */
+ RTUNI_ALPHA, /* U+00b9f1: */
+ RTUNI_ALPHA, /* U+00b9f2: */
+ RTUNI_ALPHA, /* U+00b9f3: */
+ RTUNI_ALPHA, /* U+00b9f4: */
+ RTUNI_ALPHA, /* U+00b9f5: */
+ RTUNI_ALPHA, /* U+00b9f6: */
+ RTUNI_ALPHA, /* U+00b9f7: */
+ RTUNI_ALPHA, /* U+00b9f8: */
+ RTUNI_ALPHA, /* U+00b9f9: */
+ RTUNI_ALPHA, /* U+00b9fa: */
+ RTUNI_ALPHA, /* U+00b9fb: */
+ RTUNI_ALPHA, /* U+00b9fc: */
+ RTUNI_ALPHA, /* U+00b9fd: */
+ RTUNI_ALPHA, /* U+00b9fe: */
+ RTUNI_ALPHA, /* U+00b9ff: */
+ RTUNI_ALPHA, /* U+00ba00: */
+ RTUNI_ALPHA, /* U+00ba01: */
+ RTUNI_ALPHA, /* U+00ba02: */
+ RTUNI_ALPHA, /* U+00ba03: */
+ RTUNI_ALPHA, /* U+00ba04: */
+ RTUNI_ALPHA, /* U+00ba05: */
+ RTUNI_ALPHA, /* U+00ba06: */
+ RTUNI_ALPHA, /* U+00ba07: */
+ RTUNI_ALPHA, /* U+00ba08: */
+ RTUNI_ALPHA, /* U+00ba09: */
+ RTUNI_ALPHA, /* U+00ba0a: */
+ RTUNI_ALPHA, /* U+00ba0b: */
+ RTUNI_ALPHA, /* U+00ba0c: */
+ RTUNI_ALPHA, /* U+00ba0d: */
+ RTUNI_ALPHA, /* U+00ba0e: */
+ RTUNI_ALPHA, /* U+00ba0f: */
+ RTUNI_ALPHA, /* U+00ba10: */
+ RTUNI_ALPHA, /* U+00ba11: */
+ RTUNI_ALPHA, /* U+00ba12: */
+ RTUNI_ALPHA, /* U+00ba13: */
+ RTUNI_ALPHA, /* U+00ba14: */
+ RTUNI_ALPHA, /* U+00ba15: */
+ RTUNI_ALPHA, /* U+00ba16: */
+ RTUNI_ALPHA, /* U+00ba17: */
+ RTUNI_ALPHA, /* U+00ba18: */
+ RTUNI_ALPHA, /* U+00ba19: */
+ RTUNI_ALPHA, /* U+00ba1a: */
+ RTUNI_ALPHA, /* U+00ba1b: */
+ RTUNI_ALPHA, /* U+00ba1c: */
+ RTUNI_ALPHA, /* U+00ba1d: */
+ RTUNI_ALPHA, /* U+00ba1e: */
+ RTUNI_ALPHA, /* U+00ba1f: */
+ RTUNI_ALPHA, /* U+00ba20: */
+ RTUNI_ALPHA, /* U+00ba21: */
+ RTUNI_ALPHA, /* U+00ba22: */
+ RTUNI_ALPHA, /* U+00ba23: */
+ RTUNI_ALPHA, /* U+00ba24: */
+ RTUNI_ALPHA, /* U+00ba25: */
+ RTUNI_ALPHA, /* U+00ba26: */
+ RTUNI_ALPHA, /* U+00ba27: */
+ RTUNI_ALPHA, /* U+00ba28: */
+ RTUNI_ALPHA, /* U+00ba29: */
+ RTUNI_ALPHA, /* U+00ba2a: */
+ RTUNI_ALPHA, /* U+00ba2b: */
+ RTUNI_ALPHA, /* U+00ba2c: */
+ RTUNI_ALPHA, /* U+00ba2d: */
+ RTUNI_ALPHA, /* U+00ba2e: */
+ RTUNI_ALPHA, /* U+00ba2f: */
+ RTUNI_ALPHA, /* U+00ba30: */
+ RTUNI_ALPHA, /* U+00ba31: */
+ RTUNI_ALPHA, /* U+00ba32: */
+ RTUNI_ALPHA, /* U+00ba33: */
+ RTUNI_ALPHA, /* U+00ba34: */
+ RTUNI_ALPHA, /* U+00ba35: */
+ RTUNI_ALPHA, /* U+00ba36: */
+ RTUNI_ALPHA, /* U+00ba37: */
+ RTUNI_ALPHA, /* U+00ba38: */
+ RTUNI_ALPHA, /* U+00ba39: */
+ RTUNI_ALPHA, /* U+00ba3a: */
+ RTUNI_ALPHA, /* U+00ba3b: */
+ RTUNI_ALPHA, /* U+00ba3c: */
+ RTUNI_ALPHA, /* U+00ba3d: */
+ RTUNI_ALPHA, /* U+00ba3e: */
+ RTUNI_ALPHA, /* U+00ba3f: */
+ RTUNI_ALPHA, /* U+00ba40: */
+ RTUNI_ALPHA, /* U+00ba41: */
+ RTUNI_ALPHA, /* U+00ba42: */
+ RTUNI_ALPHA, /* U+00ba43: */
+ RTUNI_ALPHA, /* U+00ba44: */
+ RTUNI_ALPHA, /* U+00ba45: */
+ RTUNI_ALPHA, /* U+00ba46: */
+ RTUNI_ALPHA, /* U+00ba47: */
+ RTUNI_ALPHA, /* U+00ba48: */
+ RTUNI_ALPHA, /* U+00ba49: */
+ RTUNI_ALPHA, /* U+00ba4a: */
+ RTUNI_ALPHA, /* U+00ba4b: */
+ RTUNI_ALPHA, /* U+00ba4c: */
+ RTUNI_ALPHA, /* U+00ba4d: */
+ RTUNI_ALPHA, /* U+00ba4e: */
+ RTUNI_ALPHA, /* U+00ba4f: */
+ RTUNI_ALPHA, /* U+00ba50: */
+ RTUNI_ALPHA, /* U+00ba51: */
+ RTUNI_ALPHA, /* U+00ba52: */
+ RTUNI_ALPHA, /* U+00ba53: */
+ RTUNI_ALPHA, /* U+00ba54: */
+ RTUNI_ALPHA, /* U+00ba55: */
+ RTUNI_ALPHA, /* U+00ba56: */
+ RTUNI_ALPHA, /* U+00ba57: */
+ RTUNI_ALPHA, /* U+00ba58: */
+ RTUNI_ALPHA, /* U+00ba59: */
+ RTUNI_ALPHA, /* U+00ba5a: */
+ RTUNI_ALPHA, /* U+00ba5b: */
+ RTUNI_ALPHA, /* U+00ba5c: */
+ RTUNI_ALPHA, /* U+00ba5d: */
+ RTUNI_ALPHA, /* U+00ba5e: */
+ RTUNI_ALPHA, /* U+00ba5f: */
+ RTUNI_ALPHA, /* U+00ba60: */
+ RTUNI_ALPHA, /* U+00ba61: */
+ RTUNI_ALPHA, /* U+00ba62: */
+ RTUNI_ALPHA, /* U+00ba63: */
+ RTUNI_ALPHA, /* U+00ba64: */
+ RTUNI_ALPHA, /* U+00ba65: */
+ RTUNI_ALPHA, /* U+00ba66: */
+ RTUNI_ALPHA, /* U+00ba67: */
+ RTUNI_ALPHA, /* U+00ba68: */
+ RTUNI_ALPHA, /* U+00ba69: */
+ RTUNI_ALPHA, /* U+00ba6a: */
+ RTUNI_ALPHA, /* U+00ba6b: */
+ RTUNI_ALPHA, /* U+00ba6c: */
+ RTUNI_ALPHA, /* U+00ba6d: */
+ RTUNI_ALPHA, /* U+00ba6e: */
+ RTUNI_ALPHA, /* U+00ba6f: */
+ RTUNI_ALPHA, /* U+00ba70: */
+ RTUNI_ALPHA, /* U+00ba71: */
+ RTUNI_ALPHA, /* U+00ba72: */
+ RTUNI_ALPHA, /* U+00ba73: */
+ RTUNI_ALPHA, /* U+00ba74: */
+ RTUNI_ALPHA, /* U+00ba75: */
+ RTUNI_ALPHA, /* U+00ba76: */
+ RTUNI_ALPHA, /* U+00ba77: */
+ RTUNI_ALPHA, /* U+00ba78: */
+ RTUNI_ALPHA, /* U+00ba79: */
+ RTUNI_ALPHA, /* U+00ba7a: */
+ RTUNI_ALPHA, /* U+00ba7b: */
+ RTUNI_ALPHA, /* U+00ba7c: */
+ RTUNI_ALPHA, /* U+00ba7d: */
+ RTUNI_ALPHA, /* U+00ba7e: */
+ RTUNI_ALPHA, /* U+00ba7f: */
+ RTUNI_ALPHA, /* U+00ba80: */
+ RTUNI_ALPHA, /* U+00ba81: */
+ RTUNI_ALPHA, /* U+00ba82: */
+ RTUNI_ALPHA, /* U+00ba83: */
+ RTUNI_ALPHA, /* U+00ba84: */
+ RTUNI_ALPHA, /* U+00ba85: */
+ RTUNI_ALPHA, /* U+00ba86: */
+ RTUNI_ALPHA, /* U+00ba87: */
+ RTUNI_ALPHA, /* U+00ba88: */
+ RTUNI_ALPHA, /* U+00ba89: */
+ RTUNI_ALPHA, /* U+00ba8a: */
+ RTUNI_ALPHA, /* U+00ba8b: */
+ RTUNI_ALPHA, /* U+00ba8c: */
+ RTUNI_ALPHA, /* U+00ba8d: */
+ RTUNI_ALPHA, /* U+00ba8e: */
+ RTUNI_ALPHA, /* U+00ba8f: */
+ RTUNI_ALPHA, /* U+00ba90: */
+ RTUNI_ALPHA, /* U+00ba91: */
+ RTUNI_ALPHA, /* U+00ba92: */
+ RTUNI_ALPHA, /* U+00ba93: */
+ RTUNI_ALPHA, /* U+00ba94: */
+ RTUNI_ALPHA, /* U+00ba95: */
+ RTUNI_ALPHA, /* U+00ba96: */
+ RTUNI_ALPHA, /* U+00ba97: */
+ RTUNI_ALPHA, /* U+00ba98: */
+ RTUNI_ALPHA, /* U+00ba99: */
+ RTUNI_ALPHA, /* U+00ba9a: */
+ RTUNI_ALPHA, /* U+00ba9b: */
+ RTUNI_ALPHA, /* U+00ba9c: */
+ RTUNI_ALPHA, /* U+00ba9d: */
+ RTUNI_ALPHA, /* U+00ba9e: */
+ RTUNI_ALPHA, /* U+00ba9f: */
+ RTUNI_ALPHA, /* U+00baa0: */
+ RTUNI_ALPHA, /* U+00baa1: */
+ RTUNI_ALPHA, /* U+00baa2: */
+ RTUNI_ALPHA, /* U+00baa3: */
+ RTUNI_ALPHA, /* U+00baa4: */
+ RTUNI_ALPHA, /* U+00baa5: */
+ RTUNI_ALPHA, /* U+00baa6: */
+ RTUNI_ALPHA, /* U+00baa7: */
+ RTUNI_ALPHA, /* U+00baa8: */
+ RTUNI_ALPHA, /* U+00baa9: */
+ RTUNI_ALPHA, /* U+00baaa: */
+ RTUNI_ALPHA, /* U+00baab: */
+ RTUNI_ALPHA, /* U+00baac: */
+ RTUNI_ALPHA, /* U+00baad: */
+ RTUNI_ALPHA, /* U+00baae: */
+ RTUNI_ALPHA, /* U+00baaf: */
+ RTUNI_ALPHA, /* U+00bab0: */
+ RTUNI_ALPHA, /* U+00bab1: */
+ RTUNI_ALPHA, /* U+00bab2: */
+ RTUNI_ALPHA, /* U+00bab3: */
+ RTUNI_ALPHA, /* U+00bab4: */
+ RTUNI_ALPHA, /* U+00bab5: */
+ RTUNI_ALPHA, /* U+00bab6: */
+ RTUNI_ALPHA, /* U+00bab7: */
+ RTUNI_ALPHA, /* U+00bab8: */
+ RTUNI_ALPHA, /* U+00bab9: */
+ RTUNI_ALPHA, /* U+00baba: */
+ RTUNI_ALPHA, /* U+00babb: */
+ RTUNI_ALPHA, /* U+00babc: */
+ RTUNI_ALPHA, /* U+00babd: */
+ RTUNI_ALPHA, /* U+00babe: */
+ RTUNI_ALPHA, /* U+00babf: */
+ RTUNI_ALPHA, /* U+00bac0: */
+ RTUNI_ALPHA, /* U+00bac1: */
+ RTUNI_ALPHA, /* U+00bac2: */
+ RTUNI_ALPHA, /* U+00bac3: */
+ RTUNI_ALPHA, /* U+00bac4: */
+ RTUNI_ALPHA, /* U+00bac5: */
+ RTUNI_ALPHA, /* U+00bac6: */
+ RTUNI_ALPHA, /* U+00bac7: */
+ RTUNI_ALPHA, /* U+00bac8: */
+ RTUNI_ALPHA, /* U+00bac9: */
+ RTUNI_ALPHA, /* U+00baca: */
+ RTUNI_ALPHA, /* U+00bacb: */
+ RTUNI_ALPHA, /* U+00bacc: */
+ RTUNI_ALPHA, /* U+00bacd: */
+ RTUNI_ALPHA, /* U+00bace: */
+ RTUNI_ALPHA, /* U+00bacf: */
+ RTUNI_ALPHA, /* U+00bad0: */
+ RTUNI_ALPHA, /* U+00bad1: */
+ RTUNI_ALPHA, /* U+00bad2: */
+ RTUNI_ALPHA, /* U+00bad3: */
+ RTUNI_ALPHA, /* U+00bad4: */
+ RTUNI_ALPHA, /* U+00bad5: */
+ RTUNI_ALPHA, /* U+00bad6: */
+ RTUNI_ALPHA, /* U+00bad7: */
+ RTUNI_ALPHA, /* U+00bad8: */
+ RTUNI_ALPHA, /* U+00bad9: */
+ RTUNI_ALPHA, /* U+00bada: */
+ RTUNI_ALPHA, /* U+00badb: */
+ RTUNI_ALPHA, /* U+00badc: */
+ RTUNI_ALPHA, /* U+00badd: */
+ RTUNI_ALPHA, /* U+00bade: */
+ RTUNI_ALPHA, /* U+00badf: */
+ RTUNI_ALPHA, /* U+00bae0: */
+ RTUNI_ALPHA, /* U+00bae1: */
+ RTUNI_ALPHA, /* U+00bae2: */
+ RTUNI_ALPHA, /* U+00bae3: */
+ RTUNI_ALPHA, /* U+00bae4: */
+ RTUNI_ALPHA, /* U+00bae5: */
+ RTUNI_ALPHA, /* U+00bae6: */
+ RTUNI_ALPHA, /* U+00bae7: */
+ RTUNI_ALPHA, /* U+00bae8: */
+ RTUNI_ALPHA, /* U+00bae9: */
+ RTUNI_ALPHA, /* U+00baea: */
+ RTUNI_ALPHA, /* U+00baeb: */
+ RTUNI_ALPHA, /* U+00baec: */
+ RTUNI_ALPHA, /* U+00baed: */
+ RTUNI_ALPHA, /* U+00baee: */
+ RTUNI_ALPHA, /* U+00baef: */
+ RTUNI_ALPHA, /* U+00baf0: */
+ RTUNI_ALPHA, /* U+00baf1: */
+ RTUNI_ALPHA, /* U+00baf2: */
+ RTUNI_ALPHA, /* U+00baf3: */
+ RTUNI_ALPHA, /* U+00baf4: */
+ RTUNI_ALPHA, /* U+00baf5: */
+ RTUNI_ALPHA, /* U+00baf6: */
+ RTUNI_ALPHA, /* U+00baf7: */
+ RTUNI_ALPHA, /* U+00baf8: */
+ RTUNI_ALPHA, /* U+00baf9: */
+ RTUNI_ALPHA, /* U+00bafa: */
+ RTUNI_ALPHA, /* U+00bafb: */
+ RTUNI_ALPHA, /* U+00bafc: */
+ RTUNI_ALPHA, /* U+00bafd: */
+ RTUNI_ALPHA, /* U+00bafe: */
+ RTUNI_ALPHA, /* U+00baff: */
+ RTUNI_ALPHA, /* U+00bb00: */
+ RTUNI_ALPHA, /* U+00bb01: */
+ RTUNI_ALPHA, /* U+00bb02: */
+ RTUNI_ALPHA, /* U+00bb03: */
+ RTUNI_ALPHA, /* U+00bb04: */
+ RTUNI_ALPHA, /* U+00bb05: */
+ RTUNI_ALPHA, /* U+00bb06: */
+ RTUNI_ALPHA, /* U+00bb07: */
+ RTUNI_ALPHA, /* U+00bb08: */
+ RTUNI_ALPHA, /* U+00bb09: */
+ RTUNI_ALPHA, /* U+00bb0a: */
+ RTUNI_ALPHA, /* U+00bb0b: */
+ RTUNI_ALPHA, /* U+00bb0c: */
+ RTUNI_ALPHA, /* U+00bb0d: */
+ RTUNI_ALPHA, /* U+00bb0e: */
+ RTUNI_ALPHA, /* U+00bb0f: */
+ RTUNI_ALPHA, /* U+00bb10: */
+ RTUNI_ALPHA, /* U+00bb11: */
+ RTUNI_ALPHA, /* U+00bb12: */
+ RTUNI_ALPHA, /* U+00bb13: */
+ RTUNI_ALPHA, /* U+00bb14: */
+ RTUNI_ALPHA, /* U+00bb15: */
+ RTUNI_ALPHA, /* U+00bb16: */
+ RTUNI_ALPHA, /* U+00bb17: */
+ RTUNI_ALPHA, /* U+00bb18: */
+ RTUNI_ALPHA, /* U+00bb19: */
+ RTUNI_ALPHA, /* U+00bb1a: */
+ RTUNI_ALPHA, /* U+00bb1b: */
+ RTUNI_ALPHA, /* U+00bb1c: */
+ RTUNI_ALPHA, /* U+00bb1d: */
+ RTUNI_ALPHA, /* U+00bb1e: */
+ RTUNI_ALPHA, /* U+00bb1f: */
+ RTUNI_ALPHA, /* U+00bb20: */
+ RTUNI_ALPHA, /* U+00bb21: */
+ RTUNI_ALPHA, /* U+00bb22: */
+ RTUNI_ALPHA, /* U+00bb23: */
+ RTUNI_ALPHA, /* U+00bb24: */
+ RTUNI_ALPHA, /* U+00bb25: */
+ RTUNI_ALPHA, /* U+00bb26: */
+ RTUNI_ALPHA, /* U+00bb27: */
+ RTUNI_ALPHA, /* U+00bb28: */
+ RTUNI_ALPHA, /* U+00bb29: */
+ RTUNI_ALPHA, /* U+00bb2a: */
+ RTUNI_ALPHA, /* U+00bb2b: */
+ RTUNI_ALPHA, /* U+00bb2c: */
+ RTUNI_ALPHA, /* U+00bb2d: */
+ RTUNI_ALPHA, /* U+00bb2e: */
+ RTUNI_ALPHA, /* U+00bb2f: */
+ RTUNI_ALPHA, /* U+00bb30: */
+ RTUNI_ALPHA, /* U+00bb31: */
+ RTUNI_ALPHA, /* U+00bb32: */
+ RTUNI_ALPHA, /* U+00bb33: */
+ RTUNI_ALPHA, /* U+00bb34: */
+ RTUNI_ALPHA, /* U+00bb35: */
+ RTUNI_ALPHA, /* U+00bb36: */
+ RTUNI_ALPHA, /* U+00bb37: */
+ RTUNI_ALPHA, /* U+00bb38: */
+ RTUNI_ALPHA, /* U+00bb39: */
+ RTUNI_ALPHA, /* U+00bb3a: */
+ RTUNI_ALPHA, /* U+00bb3b: */
+ RTUNI_ALPHA, /* U+00bb3c: */
+ RTUNI_ALPHA, /* U+00bb3d: */
+ RTUNI_ALPHA, /* U+00bb3e: */
+ RTUNI_ALPHA, /* U+00bb3f: */
+ RTUNI_ALPHA, /* U+00bb40: */
+ RTUNI_ALPHA, /* U+00bb41: */
+ RTUNI_ALPHA, /* U+00bb42: */
+ RTUNI_ALPHA, /* U+00bb43: */
+ RTUNI_ALPHA, /* U+00bb44: */
+ RTUNI_ALPHA, /* U+00bb45: */
+ RTUNI_ALPHA, /* U+00bb46: */
+ RTUNI_ALPHA, /* U+00bb47: */
+ RTUNI_ALPHA, /* U+00bb48: */
+ RTUNI_ALPHA, /* U+00bb49: */
+ RTUNI_ALPHA, /* U+00bb4a: */
+ RTUNI_ALPHA, /* U+00bb4b: */
+ RTUNI_ALPHA, /* U+00bb4c: */
+ RTUNI_ALPHA, /* U+00bb4d: */
+ RTUNI_ALPHA, /* U+00bb4e: */
+ RTUNI_ALPHA, /* U+00bb4f: */
+ RTUNI_ALPHA, /* U+00bb50: */
+ RTUNI_ALPHA, /* U+00bb51: */
+ RTUNI_ALPHA, /* U+00bb52: */
+ RTUNI_ALPHA, /* U+00bb53: */
+ RTUNI_ALPHA, /* U+00bb54: */
+ RTUNI_ALPHA, /* U+00bb55: */
+ RTUNI_ALPHA, /* U+00bb56: */
+ RTUNI_ALPHA, /* U+00bb57: */
+ RTUNI_ALPHA, /* U+00bb58: */
+ RTUNI_ALPHA, /* U+00bb59: */
+ RTUNI_ALPHA, /* U+00bb5a: */
+ RTUNI_ALPHA, /* U+00bb5b: */
+ RTUNI_ALPHA, /* U+00bb5c: */
+ RTUNI_ALPHA, /* U+00bb5d: */
+ RTUNI_ALPHA, /* U+00bb5e: */
+ RTUNI_ALPHA, /* U+00bb5f: */
+ RTUNI_ALPHA, /* U+00bb60: */
+ RTUNI_ALPHA, /* U+00bb61: */
+ RTUNI_ALPHA, /* U+00bb62: */
+ RTUNI_ALPHA, /* U+00bb63: */
+ RTUNI_ALPHA, /* U+00bb64: */
+ RTUNI_ALPHA, /* U+00bb65: */
+ RTUNI_ALPHA, /* U+00bb66: */
+ RTUNI_ALPHA, /* U+00bb67: */
+ RTUNI_ALPHA, /* U+00bb68: */
+ RTUNI_ALPHA, /* U+00bb69: */
+ RTUNI_ALPHA, /* U+00bb6a: */
+ RTUNI_ALPHA, /* U+00bb6b: */
+ RTUNI_ALPHA, /* U+00bb6c: */
+ RTUNI_ALPHA, /* U+00bb6d: */
+ RTUNI_ALPHA, /* U+00bb6e: */
+ RTUNI_ALPHA, /* U+00bb6f: */
+ RTUNI_ALPHA, /* U+00bb70: */
+ RTUNI_ALPHA, /* U+00bb71: */
+ RTUNI_ALPHA, /* U+00bb72: */
+ RTUNI_ALPHA, /* U+00bb73: */
+ RTUNI_ALPHA, /* U+00bb74: */
+ RTUNI_ALPHA, /* U+00bb75: */
+ RTUNI_ALPHA, /* U+00bb76: */
+ RTUNI_ALPHA, /* U+00bb77: */
+ RTUNI_ALPHA, /* U+00bb78: */
+ RTUNI_ALPHA, /* U+00bb79: */
+ RTUNI_ALPHA, /* U+00bb7a: */
+ RTUNI_ALPHA, /* U+00bb7b: */
+ RTUNI_ALPHA, /* U+00bb7c: */
+ RTUNI_ALPHA, /* U+00bb7d: */
+ RTUNI_ALPHA, /* U+00bb7e: */
+ RTUNI_ALPHA, /* U+00bb7f: */
+ RTUNI_ALPHA, /* U+00bb80: */
+ RTUNI_ALPHA, /* U+00bb81: */
+ RTUNI_ALPHA, /* U+00bb82: */
+ RTUNI_ALPHA, /* U+00bb83: */
+ RTUNI_ALPHA, /* U+00bb84: */
+ RTUNI_ALPHA, /* U+00bb85: */
+ RTUNI_ALPHA, /* U+00bb86: */
+ RTUNI_ALPHA, /* U+00bb87: */
+ RTUNI_ALPHA, /* U+00bb88: */
+ RTUNI_ALPHA, /* U+00bb89: */
+ RTUNI_ALPHA, /* U+00bb8a: */
+ RTUNI_ALPHA, /* U+00bb8b: */
+ RTUNI_ALPHA, /* U+00bb8c: */
+ RTUNI_ALPHA, /* U+00bb8d: */
+ RTUNI_ALPHA, /* U+00bb8e: */
+ RTUNI_ALPHA, /* U+00bb8f: */
+ RTUNI_ALPHA, /* U+00bb90: */
+ RTUNI_ALPHA, /* U+00bb91: */
+ RTUNI_ALPHA, /* U+00bb92: */
+ RTUNI_ALPHA, /* U+00bb93: */
+ RTUNI_ALPHA, /* U+00bb94: */
+ RTUNI_ALPHA, /* U+00bb95: */
+ RTUNI_ALPHA, /* U+00bb96: */
+ RTUNI_ALPHA, /* U+00bb97: */
+ RTUNI_ALPHA, /* U+00bb98: */
+ RTUNI_ALPHA, /* U+00bb99: */
+ RTUNI_ALPHA, /* U+00bb9a: */
+ RTUNI_ALPHA, /* U+00bb9b: */
+ RTUNI_ALPHA, /* U+00bb9c: */
+ RTUNI_ALPHA, /* U+00bb9d: */
+ RTUNI_ALPHA, /* U+00bb9e: */
+ RTUNI_ALPHA, /* U+00bb9f: */
+ RTUNI_ALPHA, /* U+00bba0: */
+ RTUNI_ALPHA, /* U+00bba1: */
+ RTUNI_ALPHA, /* U+00bba2: */
+ RTUNI_ALPHA, /* U+00bba3: */
+ RTUNI_ALPHA, /* U+00bba4: */
+ RTUNI_ALPHA, /* U+00bba5: */
+ RTUNI_ALPHA, /* U+00bba6: */
+ RTUNI_ALPHA, /* U+00bba7: */
+ RTUNI_ALPHA, /* U+00bba8: */
+ RTUNI_ALPHA, /* U+00bba9: */
+ RTUNI_ALPHA, /* U+00bbaa: */
+ RTUNI_ALPHA, /* U+00bbab: */
+ RTUNI_ALPHA, /* U+00bbac: */
+ RTUNI_ALPHA, /* U+00bbad: */
+ RTUNI_ALPHA, /* U+00bbae: */
+ RTUNI_ALPHA, /* U+00bbaf: */
+ RTUNI_ALPHA, /* U+00bbb0: */
+ RTUNI_ALPHA, /* U+00bbb1: */
+ RTUNI_ALPHA, /* U+00bbb2: */
+ RTUNI_ALPHA, /* U+00bbb3: */
+ RTUNI_ALPHA, /* U+00bbb4: */
+ RTUNI_ALPHA, /* U+00bbb5: */
+ RTUNI_ALPHA, /* U+00bbb6: */
+ RTUNI_ALPHA, /* U+00bbb7: */
+ RTUNI_ALPHA, /* U+00bbb8: */
+ RTUNI_ALPHA, /* U+00bbb9: */
+ RTUNI_ALPHA, /* U+00bbba: */
+ RTUNI_ALPHA, /* U+00bbbb: */
+ RTUNI_ALPHA, /* U+00bbbc: */
+ RTUNI_ALPHA, /* U+00bbbd: */
+ RTUNI_ALPHA, /* U+00bbbe: */
+ RTUNI_ALPHA, /* U+00bbbf: */
+ RTUNI_ALPHA, /* U+00bbc0: */
+ RTUNI_ALPHA, /* U+00bbc1: */
+ RTUNI_ALPHA, /* U+00bbc2: */
+ RTUNI_ALPHA, /* U+00bbc3: */
+ RTUNI_ALPHA, /* U+00bbc4: */
+ RTUNI_ALPHA, /* U+00bbc5: */
+ RTUNI_ALPHA, /* U+00bbc6: */
+ RTUNI_ALPHA, /* U+00bbc7: */
+ RTUNI_ALPHA, /* U+00bbc8: */
+ RTUNI_ALPHA, /* U+00bbc9: */
+ RTUNI_ALPHA, /* U+00bbca: */
+ RTUNI_ALPHA, /* U+00bbcb: */
+ RTUNI_ALPHA, /* U+00bbcc: */
+ RTUNI_ALPHA, /* U+00bbcd: */
+ RTUNI_ALPHA, /* U+00bbce: */
+ RTUNI_ALPHA, /* U+00bbcf: */
+ RTUNI_ALPHA, /* U+00bbd0: */
+ RTUNI_ALPHA, /* U+00bbd1: */
+ RTUNI_ALPHA, /* U+00bbd2: */
+ RTUNI_ALPHA, /* U+00bbd3: */
+ RTUNI_ALPHA, /* U+00bbd4: */
+ RTUNI_ALPHA, /* U+00bbd5: */
+ RTUNI_ALPHA, /* U+00bbd6: */
+ RTUNI_ALPHA, /* U+00bbd7: */
+ RTUNI_ALPHA, /* U+00bbd8: */
+ RTUNI_ALPHA, /* U+00bbd9: */
+ RTUNI_ALPHA, /* U+00bbda: */
+ RTUNI_ALPHA, /* U+00bbdb: */
+ RTUNI_ALPHA, /* U+00bbdc: */
+ RTUNI_ALPHA, /* U+00bbdd: */
+ RTUNI_ALPHA, /* U+00bbde: */
+ RTUNI_ALPHA, /* U+00bbdf: */
+ RTUNI_ALPHA, /* U+00bbe0: */
+ RTUNI_ALPHA, /* U+00bbe1: */
+ RTUNI_ALPHA, /* U+00bbe2: */
+ RTUNI_ALPHA, /* U+00bbe3: */
+ RTUNI_ALPHA, /* U+00bbe4: */
+ RTUNI_ALPHA, /* U+00bbe5: */
+ RTUNI_ALPHA, /* U+00bbe6: */
+ RTUNI_ALPHA, /* U+00bbe7: */
+ RTUNI_ALPHA, /* U+00bbe8: */
+ RTUNI_ALPHA, /* U+00bbe9: */
+ RTUNI_ALPHA, /* U+00bbea: */
+ RTUNI_ALPHA, /* U+00bbeb: */
+ RTUNI_ALPHA, /* U+00bbec: */
+ RTUNI_ALPHA, /* U+00bbed: */
+ RTUNI_ALPHA, /* U+00bbee: */
+ RTUNI_ALPHA, /* U+00bbef: */
+ RTUNI_ALPHA, /* U+00bbf0: */
+ RTUNI_ALPHA, /* U+00bbf1: */
+ RTUNI_ALPHA, /* U+00bbf2: */
+ RTUNI_ALPHA, /* U+00bbf3: */
+ RTUNI_ALPHA, /* U+00bbf4: */
+ RTUNI_ALPHA, /* U+00bbf5: */
+ RTUNI_ALPHA, /* U+00bbf6: */
+ RTUNI_ALPHA, /* U+00bbf7: */
+ RTUNI_ALPHA, /* U+00bbf8: */
+ RTUNI_ALPHA, /* U+00bbf9: */
+ RTUNI_ALPHA, /* U+00bbfa: */
+ RTUNI_ALPHA, /* U+00bbfb: */
+ RTUNI_ALPHA, /* U+00bbfc: */
+ RTUNI_ALPHA, /* U+00bbfd: */
+ RTUNI_ALPHA, /* U+00bbfe: */
+ RTUNI_ALPHA, /* U+00bbff: */
+ RTUNI_ALPHA, /* U+00bc00: */
+ RTUNI_ALPHA, /* U+00bc01: */
+ RTUNI_ALPHA, /* U+00bc02: */
+ RTUNI_ALPHA, /* U+00bc03: */
+ RTUNI_ALPHA, /* U+00bc04: */
+ RTUNI_ALPHA, /* U+00bc05: */
+ RTUNI_ALPHA, /* U+00bc06: */
+ RTUNI_ALPHA, /* U+00bc07: */
+ RTUNI_ALPHA, /* U+00bc08: */
+ RTUNI_ALPHA, /* U+00bc09: */
+ RTUNI_ALPHA, /* U+00bc0a: */
+ RTUNI_ALPHA, /* U+00bc0b: */
+ RTUNI_ALPHA, /* U+00bc0c: */
+ RTUNI_ALPHA, /* U+00bc0d: */
+ RTUNI_ALPHA, /* U+00bc0e: */
+ RTUNI_ALPHA, /* U+00bc0f: */
+ RTUNI_ALPHA, /* U+00bc10: */
+ RTUNI_ALPHA, /* U+00bc11: */
+ RTUNI_ALPHA, /* U+00bc12: */
+ RTUNI_ALPHA, /* U+00bc13: */
+ RTUNI_ALPHA, /* U+00bc14: */
+ RTUNI_ALPHA, /* U+00bc15: */
+ RTUNI_ALPHA, /* U+00bc16: */
+ RTUNI_ALPHA, /* U+00bc17: */
+ RTUNI_ALPHA, /* U+00bc18: */
+ RTUNI_ALPHA, /* U+00bc19: */
+ RTUNI_ALPHA, /* U+00bc1a: */
+ RTUNI_ALPHA, /* U+00bc1b: */
+ RTUNI_ALPHA, /* U+00bc1c: */
+ RTUNI_ALPHA, /* U+00bc1d: */
+ RTUNI_ALPHA, /* U+00bc1e: */
+ RTUNI_ALPHA, /* U+00bc1f: */
+ RTUNI_ALPHA, /* U+00bc20: */
+ RTUNI_ALPHA, /* U+00bc21: */
+ RTUNI_ALPHA, /* U+00bc22: */
+ RTUNI_ALPHA, /* U+00bc23: */
+ RTUNI_ALPHA, /* U+00bc24: */
+ RTUNI_ALPHA, /* U+00bc25: */
+ RTUNI_ALPHA, /* U+00bc26: */
+ RTUNI_ALPHA, /* U+00bc27: */
+ RTUNI_ALPHA, /* U+00bc28: */
+ RTUNI_ALPHA, /* U+00bc29: */
+ RTUNI_ALPHA, /* U+00bc2a: */
+ RTUNI_ALPHA, /* U+00bc2b: */
+ RTUNI_ALPHA, /* U+00bc2c: */
+ RTUNI_ALPHA, /* U+00bc2d: */
+ RTUNI_ALPHA, /* U+00bc2e: */
+ RTUNI_ALPHA, /* U+00bc2f: */
+ RTUNI_ALPHA, /* U+00bc30: */
+ RTUNI_ALPHA, /* U+00bc31: */
+ RTUNI_ALPHA, /* U+00bc32: */
+ RTUNI_ALPHA, /* U+00bc33: */
+ RTUNI_ALPHA, /* U+00bc34: */
+ RTUNI_ALPHA, /* U+00bc35: */
+ RTUNI_ALPHA, /* U+00bc36: */
+ RTUNI_ALPHA, /* U+00bc37: */
+ RTUNI_ALPHA, /* U+00bc38: */
+ RTUNI_ALPHA, /* U+00bc39: */
+ RTUNI_ALPHA, /* U+00bc3a: */
+ RTUNI_ALPHA, /* U+00bc3b: */
+ RTUNI_ALPHA, /* U+00bc3c: */
+ RTUNI_ALPHA, /* U+00bc3d: */
+ RTUNI_ALPHA, /* U+00bc3e: */
+ RTUNI_ALPHA, /* U+00bc3f: */
+ RTUNI_ALPHA, /* U+00bc40: */
+ RTUNI_ALPHA, /* U+00bc41: */
+ RTUNI_ALPHA, /* U+00bc42: */
+ RTUNI_ALPHA, /* U+00bc43: */
+ RTUNI_ALPHA, /* U+00bc44: */
+ RTUNI_ALPHA, /* U+00bc45: */
+ RTUNI_ALPHA, /* U+00bc46: */
+ RTUNI_ALPHA, /* U+00bc47: */
+ RTUNI_ALPHA, /* U+00bc48: */
+ RTUNI_ALPHA, /* U+00bc49: */
+ RTUNI_ALPHA, /* U+00bc4a: */
+ RTUNI_ALPHA, /* U+00bc4b: */
+ RTUNI_ALPHA, /* U+00bc4c: */
+ RTUNI_ALPHA, /* U+00bc4d: */
+ RTUNI_ALPHA, /* U+00bc4e: */
+ RTUNI_ALPHA, /* U+00bc4f: */
+ RTUNI_ALPHA, /* U+00bc50: */
+ RTUNI_ALPHA, /* U+00bc51: */
+ RTUNI_ALPHA, /* U+00bc52: */
+ RTUNI_ALPHA, /* U+00bc53: */
+ RTUNI_ALPHA, /* U+00bc54: */
+ RTUNI_ALPHA, /* U+00bc55: */
+ RTUNI_ALPHA, /* U+00bc56: */
+ RTUNI_ALPHA, /* U+00bc57: */
+ RTUNI_ALPHA, /* U+00bc58: */
+ RTUNI_ALPHA, /* U+00bc59: */
+ RTUNI_ALPHA, /* U+00bc5a: */
+ RTUNI_ALPHA, /* U+00bc5b: */
+ RTUNI_ALPHA, /* U+00bc5c: */
+ RTUNI_ALPHA, /* U+00bc5d: */
+ RTUNI_ALPHA, /* U+00bc5e: */
+ RTUNI_ALPHA, /* U+00bc5f: */
+ RTUNI_ALPHA, /* U+00bc60: */
+ RTUNI_ALPHA, /* U+00bc61: */
+ RTUNI_ALPHA, /* U+00bc62: */
+ RTUNI_ALPHA, /* U+00bc63: */
+ RTUNI_ALPHA, /* U+00bc64: */
+ RTUNI_ALPHA, /* U+00bc65: */
+ RTUNI_ALPHA, /* U+00bc66: */
+ RTUNI_ALPHA, /* U+00bc67: */
+ RTUNI_ALPHA, /* U+00bc68: */
+ RTUNI_ALPHA, /* U+00bc69: */
+ RTUNI_ALPHA, /* U+00bc6a: */
+ RTUNI_ALPHA, /* U+00bc6b: */
+ RTUNI_ALPHA, /* U+00bc6c: */
+ RTUNI_ALPHA, /* U+00bc6d: */
+ RTUNI_ALPHA, /* U+00bc6e: */
+ RTUNI_ALPHA, /* U+00bc6f: */
+ RTUNI_ALPHA, /* U+00bc70: */
+ RTUNI_ALPHA, /* U+00bc71: */
+ RTUNI_ALPHA, /* U+00bc72: */
+ RTUNI_ALPHA, /* U+00bc73: */
+ RTUNI_ALPHA, /* U+00bc74: */
+ RTUNI_ALPHA, /* U+00bc75: */
+ RTUNI_ALPHA, /* U+00bc76: */
+ RTUNI_ALPHA, /* U+00bc77: */
+ RTUNI_ALPHA, /* U+00bc78: */
+ RTUNI_ALPHA, /* U+00bc79: */
+ RTUNI_ALPHA, /* U+00bc7a: */
+ RTUNI_ALPHA, /* U+00bc7b: */
+ RTUNI_ALPHA, /* U+00bc7c: */
+ RTUNI_ALPHA, /* U+00bc7d: */
+ RTUNI_ALPHA, /* U+00bc7e: */
+ RTUNI_ALPHA, /* U+00bc7f: */
+ RTUNI_ALPHA, /* U+00bc80: */
+ RTUNI_ALPHA, /* U+00bc81: */
+ RTUNI_ALPHA, /* U+00bc82: */
+ RTUNI_ALPHA, /* U+00bc83: */
+ RTUNI_ALPHA, /* U+00bc84: */
+ RTUNI_ALPHA, /* U+00bc85: */
+ RTUNI_ALPHA, /* U+00bc86: */
+ RTUNI_ALPHA, /* U+00bc87: */
+ RTUNI_ALPHA, /* U+00bc88: */
+ RTUNI_ALPHA, /* U+00bc89: */
+ RTUNI_ALPHA, /* U+00bc8a: */
+ RTUNI_ALPHA, /* U+00bc8b: */
+ RTUNI_ALPHA, /* U+00bc8c: */
+ RTUNI_ALPHA, /* U+00bc8d: */
+ RTUNI_ALPHA, /* U+00bc8e: */
+ RTUNI_ALPHA, /* U+00bc8f: */
+ RTUNI_ALPHA, /* U+00bc90: */
+ RTUNI_ALPHA, /* U+00bc91: */
+ RTUNI_ALPHA, /* U+00bc92: */
+ RTUNI_ALPHA, /* U+00bc93: */
+ RTUNI_ALPHA, /* U+00bc94: */
+ RTUNI_ALPHA, /* U+00bc95: */
+ RTUNI_ALPHA, /* U+00bc96: */
+ RTUNI_ALPHA, /* U+00bc97: */
+ RTUNI_ALPHA, /* U+00bc98: */
+ RTUNI_ALPHA, /* U+00bc99: */
+ RTUNI_ALPHA, /* U+00bc9a: */
+ RTUNI_ALPHA, /* U+00bc9b: */
+ RTUNI_ALPHA, /* U+00bc9c: */
+ RTUNI_ALPHA, /* U+00bc9d: */
+ RTUNI_ALPHA, /* U+00bc9e: */
+ RTUNI_ALPHA, /* U+00bc9f: */
+ RTUNI_ALPHA, /* U+00bca0: */
+ RTUNI_ALPHA, /* U+00bca1: */
+ RTUNI_ALPHA, /* U+00bca2: */
+ RTUNI_ALPHA, /* U+00bca3: */
+ RTUNI_ALPHA, /* U+00bca4: */
+ RTUNI_ALPHA, /* U+00bca5: */
+ RTUNI_ALPHA, /* U+00bca6: */
+ RTUNI_ALPHA, /* U+00bca7: */
+ RTUNI_ALPHA, /* U+00bca8: */
+ RTUNI_ALPHA, /* U+00bca9: */
+ RTUNI_ALPHA, /* U+00bcaa: */
+ RTUNI_ALPHA, /* U+00bcab: */
+ RTUNI_ALPHA, /* U+00bcac: */
+ RTUNI_ALPHA, /* U+00bcad: */
+ RTUNI_ALPHA, /* U+00bcae: */
+ RTUNI_ALPHA, /* U+00bcaf: */
+ RTUNI_ALPHA, /* U+00bcb0: */
+ RTUNI_ALPHA, /* U+00bcb1: */
+ RTUNI_ALPHA, /* U+00bcb2: */
+ RTUNI_ALPHA, /* U+00bcb3: */
+ RTUNI_ALPHA, /* U+00bcb4: */
+ RTUNI_ALPHA, /* U+00bcb5: */
+ RTUNI_ALPHA, /* U+00bcb6: */
+ RTUNI_ALPHA, /* U+00bcb7: */
+ RTUNI_ALPHA, /* U+00bcb8: */
+ RTUNI_ALPHA, /* U+00bcb9: */
+ RTUNI_ALPHA, /* U+00bcba: */
+ RTUNI_ALPHA, /* U+00bcbb: */
+ RTUNI_ALPHA, /* U+00bcbc: */
+ RTUNI_ALPHA, /* U+00bcbd: */
+ RTUNI_ALPHA, /* U+00bcbe: */
+ RTUNI_ALPHA, /* U+00bcbf: */
+ RTUNI_ALPHA, /* U+00bcc0: */
+ RTUNI_ALPHA, /* U+00bcc1: */
+ RTUNI_ALPHA, /* U+00bcc2: */
+ RTUNI_ALPHA, /* U+00bcc3: */
+ RTUNI_ALPHA, /* U+00bcc4: */
+ RTUNI_ALPHA, /* U+00bcc5: */
+ RTUNI_ALPHA, /* U+00bcc6: */
+ RTUNI_ALPHA, /* U+00bcc7: */
+ RTUNI_ALPHA, /* U+00bcc8: */
+ RTUNI_ALPHA, /* U+00bcc9: */
+ RTUNI_ALPHA, /* U+00bcca: */
+ RTUNI_ALPHA, /* U+00bccb: */
+ RTUNI_ALPHA, /* U+00bccc: */
+ RTUNI_ALPHA, /* U+00bccd: */
+ RTUNI_ALPHA, /* U+00bcce: */
+ RTUNI_ALPHA, /* U+00bccf: */
+ RTUNI_ALPHA, /* U+00bcd0: */
+ RTUNI_ALPHA, /* U+00bcd1: */
+ RTUNI_ALPHA, /* U+00bcd2: */
+ RTUNI_ALPHA, /* U+00bcd3: */
+ RTUNI_ALPHA, /* U+00bcd4: */
+ RTUNI_ALPHA, /* U+00bcd5: */
+ RTUNI_ALPHA, /* U+00bcd6: */
+ RTUNI_ALPHA, /* U+00bcd7: */
+ RTUNI_ALPHA, /* U+00bcd8: */
+ RTUNI_ALPHA, /* U+00bcd9: */
+ RTUNI_ALPHA, /* U+00bcda: */
+ RTUNI_ALPHA, /* U+00bcdb: */
+ RTUNI_ALPHA, /* U+00bcdc: */
+ RTUNI_ALPHA, /* U+00bcdd: */
+ RTUNI_ALPHA, /* U+00bcde: */
+ RTUNI_ALPHA, /* U+00bcdf: */
+ RTUNI_ALPHA, /* U+00bce0: */
+ RTUNI_ALPHA, /* U+00bce1: */
+ RTUNI_ALPHA, /* U+00bce2: */
+ RTUNI_ALPHA, /* U+00bce3: */
+ RTUNI_ALPHA, /* U+00bce4: */
+ RTUNI_ALPHA, /* U+00bce5: */
+ RTUNI_ALPHA, /* U+00bce6: */
+ RTUNI_ALPHA, /* U+00bce7: */
+ RTUNI_ALPHA, /* U+00bce8: */
+ RTUNI_ALPHA, /* U+00bce9: */
+ RTUNI_ALPHA, /* U+00bcea: */
+ RTUNI_ALPHA, /* U+00bceb: */
+ RTUNI_ALPHA, /* U+00bcec: */
+ RTUNI_ALPHA, /* U+00bced: */
+ RTUNI_ALPHA, /* U+00bcee: */
+ RTUNI_ALPHA, /* U+00bcef: */
+ RTUNI_ALPHA, /* U+00bcf0: */
+ RTUNI_ALPHA, /* U+00bcf1: */
+ RTUNI_ALPHA, /* U+00bcf2: */
+ RTUNI_ALPHA, /* U+00bcf3: */
+ RTUNI_ALPHA, /* U+00bcf4: */
+ RTUNI_ALPHA, /* U+00bcf5: */
+ RTUNI_ALPHA, /* U+00bcf6: */
+ RTUNI_ALPHA, /* U+00bcf7: */
+ RTUNI_ALPHA, /* U+00bcf8: */
+ RTUNI_ALPHA, /* U+00bcf9: */
+ RTUNI_ALPHA, /* U+00bcfa: */
+ RTUNI_ALPHA, /* U+00bcfb: */
+ RTUNI_ALPHA, /* U+00bcfc: */
+ RTUNI_ALPHA, /* U+00bcfd: */
+ RTUNI_ALPHA, /* U+00bcfe: */
+ RTUNI_ALPHA, /* U+00bcff: */
+ RTUNI_ALPHA, /* U+00bd00: */
+ RTUNI_ALPHA, /* U+00bd01: */
+ RTUNI_ALPHA, /* U+00bd02: */
+ RTUNI_ALPHA, /* U+00bd03: */
+ RTUNI_ALPHA, /* U+00bd04: */
+ RTUNI_ALPHA, /* U+00bd05: */
+ RTUNI_ALPHA, /* U+00bd06: */
+ RTUNI_ALPHA, /* U+00bd07: */
+ RTUNI_ALPHA, /* U+00bd08: */
+ RTUNI_ALPHA, /* U+00bd09: */
+ RTUNI_ALPHA, /* U+00bd0a: */
+ RTUNI_ALPHA, /* U+00bd0b: */
+ RTUNI_ALPHA, /* U+00bd0c: */
+ RTUNI_ALPHA, /* U+00bd0d: */
+ RTUNI_ALPHA, /* U+00bd0e: */
+ RTUNI_ALPHA, /* U+00bd0f: */
+ RTUNI_ALPHA, /* U+00bd10: */
+ RTUNI_ALPHA, /* U+00bd11: */
+ RTUNI_ALPHA, /* U+00bd12: */
+ RTUNI_ALPHA, /* U+00bd13: */
+ RTUNI_ALPHA, /* U+00bd14: */
+ RTUNI_ALPHA, /* U+00bd15: */
+ RTUNI_ALPHA, /* U+00bd16: */
+ RTUNI_ALPHA, /* U+00bd17: */
+ RTUNI_ALPHA, /* U+00bd18: */
+ RTUNI_ALPHA, /* U+00bd19: */
+ RTUNI_ALPHA, /* U+00bd1a: */
+ RTUNI_ALPHA, /* U+00bd1b: */
+ RTUNI_ALPHA, /* U+00bd1c: */
+ RTUNI_ALPHA, /* U+00bd1d: */
+ RTUNI_ALPHA, /* U+00bd1e: */
+ RTUNI_ALPHA, /* U+00bd1f: */
+ RTUNI_ALPHA, /* U+00bd20: */
+ RTUNI_ALPHA, /* U+00bd21: */
+ RTUNI_ALPHA, /* U+00bd22: */
+ RTUNI_ALPHA, /* U+00bd23: */
+ RTUNI_ALPHA, /* U+00bd24: */
+ RTUNI_ALPHA, /* U+00bd25: */
+ RTUNI_ALPHA, /* U+00bd26: */
+ RTUNI_ALPHA, /* U+00bd27: */
+ RTUNI_ALPHA, /* U+00bd28: */
+ RTUNI_ALPHA, /* U+00bd29: */
+ RTUNI_ALPHA, /* U+00bd2a: */
+ RTUNI_ALPHA, /* U+00bd2b: */
+ RTUNI_ALPHA, /* U+00bd2c: */
+ RTUNI_ALPHA, /* U+00bd2d: */
+ RTUNI_ALPHA, /* U+00bd2e: */
+ RTUNI_ALPHA, /* U+00bd2f: */
+ RTUNI_ALPHA, /* U+00bd30: */
+ RTUNI_ALPHA, /* U+00bd31: */
+ RTUNI_ALPHA, /* U+00bd32: */
+ RTUNI_ALPHA, /* U+00bd33: */
+ RTUNI_ALPHA, /* U+00bd34: */
+ RTUNI_ALPHA, /* U+00bd35: */
+ RTUNI_ALPHA, /* U+00bd36: */
+ RTUNI_ALPHA, /* U+00bd37: */
+ RTUNI_ALPHA, /* U+00bd38: */
+ RTUNI_ALPHA, /* U+00bd39: */
+ RTUNI_ALPHA, /* U+00bd3a: */
+ RTUNI_ALPHA, /* U+00bd3b: */
+ RTUNI_ALPHA, /* U+00bd3c: */
+ RTUNI_ALPHA, /* U+00bd3d: */
+ RTUNI_ALPHA, /* U+00bd3e: */
+ RTUNI_ALPHA, /* U+00bd3f: */
+ RTUNI_ALPHA, /* U+00bd40: */
+ RTUNI_ALPHA, /* U+00bd41: */
+ RTUNI_ALPHA, /* U+00bd42: */
+ RTUNI_ALPHA, /* U+00bd43: */
+ RTUNI_ALPHA, /* U+00bd44: */
+ RTUNI_ALPHA, /* U+00bd45: */
+ RTUNI_ALPHA, /* U+00bd46: */
+ RTUNI_ALPHA, /* U+00bd47: */
+ RTUNI_ALPHA, /* U+00bd48: */
+ RTUNI_ALPHA, /* U+00bd49: */
+ RTUNI_ALPHA, /* U+00bd4a: */
+ RTUNI_ALPHA, /* U+00bd4b: */
+ RTUNI_ALPHA, /* U+00bd4c: */
+ RTUNI_ALPHA, /* U+00bd4d: */
+ RTUNI_ALPHA, /* U+00bd4e: */
+ RTUNI_ALPHA, /* U+00bd4f: */
+ RTUNI_ALPHA, /* U+00bd50: */
+ RTUNI_ALPHA, /* U+00bd51: */
+ RTUNI_ALPHA, /* U+00bd52: */
+ RTUNI_ALPHA, /* U+00bd53: */
+ RTUNI_ALPHA, /* U+00bd54: */
+ RTUNI_ALPHA, /* U+00bd55: */
+ RTUNI_ALPHA, /* U+00bd56: */
+ RTUNI_ALPHA, /* U+00bd57: */
+ RTUNI_ALPHA, /* U+00bd58: */
+ RTUNI_ALPHA, /* U+00bd59: */
+ RTUNI_ALPHA, /* U+00bd5a: */
+ RTUNI_ALPHA, /* U+00bd5b: */
+ RTUNI_ALPHA, /* U+00bd5c: */
+ RTUNI_ALPHA, /* U+00bd5d: */
+ RTUNI_ALPHA, /* U+00bd5e: */
+ RTUNI_ALPHA, /* U+00bd5f: */
+ RTUNI_ALPHA, /* U+00bd60: */
+ RTUNI_ALPHA, /* U+00bd61: */
+ RTUNI_ALPHA, /* U+00bd62: */
+ RTUNI_ALPHA, /* U+00bd63: */
+ RTUNI_ALPHA, /* U+00bd64: */
+ RTUNI_ALPHA, /* U+00bd65: */
+ RTUNI_ALPHA, /* U+00bd66: */
+ RTUNI_ALPHA, /* U+00bd67: */
+ RTUNI_ALPHA, /* U+00bd68: */
+ RTUNI_ALPHA, /* U+00bd69: */
+ RTUNI_ALPHA, /* U+00bd6a: */
+ RTUNI_ALPHA, /* U+00bd6b: */
+ RTUNI_ALPHA, /* U+00bd6c: */
+ RTUNI_ALPHA, /* U+00bd6d: */
+ RTUNI_ALPHA, /* U+00bd6e: */
+ RTUNI_ALPHA, /* U+00bd6f: */
+ RTUNI_ALPHA, /* U+00bd70: */
+ RTUNI_ALPHA, /* U+00bd71: */
+ RTUNI_ALPHA, /* U+00bd72: */
+ RTUNI_ALPHA, /* U+00bd73: */
+ RTUNI_ALPHA, /* U+00bd74: */
+ RTUNI_ALPHA, /* U+00bd75: */
+ RTUNI_ALPHA, /* U+00bd76: */
+ RTUNI_ALPHA, /* U+00bd77: */
+ RTUNI_ALPHA, /* U+00bd78: */
+ RTUNI_ALPHA, /* U+00bd79: */
+ RTUNI_ALPHA, /* U+00bd7a: */
+ RTUNI_ALPHA, /* U+00bd7b: */
+ RTUNI_ALPHA, /* U+00bd7c: */
+ RTUNI_ALPHA, /* U+00bd7d: */
+ RTUNI_ALPHA, /* U+00bd7e: */
+ RTUNI_ALPHA, /* U+00bd7f: */
+ RTUNI_ALPHA, /* U+00bd80: */
+ RTUNI_ALPHA, /* U+00bd81: */
+ RTUNI_ALPHA, /* U+00bd82: */
+ RTUNI_ALPHA, /* U+00bd83: */
+ RTUNI_ALPHA, /* U+00bd84: */
+ RTUNI_ALPHA, /* U+00bd85: */
+ RTUNI_ALPHA, /* U+00bd86: */
+ RTUNI_ALPHA, /* U+00bd87: */
+ RTUNI_ALPHA, /* U+00bd88: */
+ RTUNI_ALPHA, /* U+00bd89: */
+ RTUNI_ALPHA, /* U+00bd8a: */
+ RTUNI_ALPHA, /* U+00bd8b: */
+ RTUNI_ALPHA, /* U+00bd8c: */
+ RTUNI_ALPHA, /* U+00bd8d: */
+ RTUNI_ALPHA, /* U+00bd8e: */
+ RTUNI_ALPHA, /* U+00bd8f: */
+ RTUNI_ALPHA, /* U+00bd90: */
+ RTUNI_ALPHA, /* U+00bd91: */
+ RTUNI_ALPHA, /* U+00bd92: */
+ RTUNI_ALPHA, /* U+00bd93: */
+ RTUNI_ALPHA, /* U+00bd94: */
+ RTUNI_ALPHA, /* U+00bd95: */
+ RTUNI_ALPHA, /* U+00bd96: */
+ RTUNI_ALPHA, /* U+00bd97: */
+ RTUNI_ALPHA, /* U+00bd98: */
+ RTUNI_ALPHA, /* U+00bd99: */
+ RTUNI_ALPHA, /* U+00bd9a: */
+ RTUNI_ALPHA, /* U+00bd9b: */
+ RTUNI_ALPHA, /* U+00bd9c: */
+ RTUNI_ALPHA, /* U+00bd9d: */
+ RTUNI_ALPHA, /* U+00bd9e: */
+ RTUNI_ALPHA, /* U+00bd9f: */
+ RTUNI_ALPHA, /* U+00bda0: */
+ RTUNI_ALPHA, /* U+00bda1: */
+ RTUNI_ALPHA, /* U+00bda2: */
+ RTUNI_ALPHA, /* U+00bda3: */
+ RTUNI_ALPHA, /* U+00bda4: */
+ RTUNI_ALPHA, /* U+00bda5: */
+ RTUNI_ALPHA, /* U+00bda6: */
+ RTUNI_ALPHA, /* U+00bda7: */
+ RTUNI_ALPHA, /* U+00bda8: */
+ RTUNI_ALPHA, /* U+00bda9: */
+ RTUNI_ALPHA, /* U+00bdaa: */
+ RTUNI_ALPHA, /* U+00bdab: */
+ RTUNI_ALPHA, /* U+00bdac: */
+ RTUNI_ALPHA, /* U+00bdad: */
+ RTUNI_ALPHA, /* U+00bdae: */
+ RTUNI_ALPHA, /* U+00bdaf: */
+ RTUNI_ALPHA, /* U+00bdb0: */
+ RTUNI_ALPHA, /* U+00bdb1: */
+ RTUNI_ALPHA, /* U+00bdb2: */
+ RTUNI_ALPHA, /* U+00bdb3: */
+ RTUNI_ALPHA, /* U+00bdb4: */
+ RTUNI_ALPHA, /* U+00bdb5: */
+ RTUNI_ALPHA, /* U+00bdb6: */
+ RTUNI_ALPHA, /* U+00bdb7: */
+ RTUNI_ALPHA, /* U+00bdb8: */
+ RTUNI_ALPHA, /* U+00bdb9: */
+ RTUNI_ALPHA, /* U+00bdba: */
+ RTUNI_ALPHA, /* U+00bdbb: */
+ RTUNI_ALPHA, /* U+00bdbc: */
+ RTUNI_ALPHA, /* U+00bdbd: */
+ RTUNI_ALPHA, /* U+00bdbe: */
+ RTUNI_ALPHA, /* U+00bdbf: */
+ RTUNI_ALPHA, /* U+00bdc0: */
+ RTUNI_ALPHA, /* U+00bdc1: */
+ RTUNI_ALPHA, /* U+00bdc2: */
+ RTUNI_ALPHA, /* U+00bdc3: */
+ RTUNI_ALPHA, /* U+00bdc4: */
+ RTUNI_ALPHA, /* U+00bdc5: */
+ RTUNI_ALPHA, /* U+00bdc6: */
+ RTUNI_ALPHA, /* U+00bdc7: */
+ RTUNI_ALPHA, /* U+00bdc8: */
+ RTUNI_ALPHA, /* U+00bdc9: */
+ RTUNI_ALPHA, /* U+00bdca: */
+ RTUNI_ALPHA, /* U+00bdcb: */
+ RTUNI_ALPHA, /* U+00bdcc: */
+ RTUNI_ALPHA, /* U+00bdcd: */
+ RTUNI_ALPHA, /* U+00bdce: */
+ RTUNI_ALPHA, /* U+00bdcf: */
+ RTUNI_ALPHA, /* U+00bdd0: */
+ RTUNI_ALPHA, /* U+00bdd1: */
+ RTUNI_ALPHA, /* U+00bdd2: */
+ RTUNI_ALPHA, /* U+00bdd3: */
+ RTUNI_ALPHA, /* U+00bdd4: */
+ RTUNI_ALPHA, /* U+00bdd5: */
+ RTUNI_ALPHA, /* U+00bdd6: */
+ RTUNI_ALPHA, /* U+00bdd7: */
+ RTUNI_ALPHA, /* U+00bdd8: */
+ RTUNI_ALPHA, /* U+00bdd9: */
+ RTUNI_ALPHA, /* U+00bdda: */
+ RTUNI_ALPHA, /* U+00bddb: */
+ RTUNI_ALPHA, /* U+00bddc: */
+ RTUNI_ALPHA, /* U+00bddd: */
+ RTUNI_ALPHA, /* U+00bdde: */
+ RTUNI_ALPHA, /* U+00bddf: */
+ RTUNI_ALPHA, /* U+00bde0: */
+ RTUNI_ALPHA, /* U+00bde1: */
+ RTUNI_ALPHA, /* U+00bde2: */
+ RTUNI_ALPHA, /* U+00bde3: */
+ RTUNI_ALPHA, /* U+00bde4: */
+ RTUNI_ALPHA, /* U+00bde5: */
+ RTUNI_ALPHA, /* U+00bde6: */
+ RTUNI_ALPHA, /* U+00bde7: */
+ RTUNI_ALPHA, /* U+00bde8: */
+ RTUNI_ALPHA, /* U+00bde9: */
+ RTUNI_ALPHA, /* U+00bdea: */
+ RTUNI_ALPHA, /* U+00bdeb: */
+ RTUNI_ALPHA, /* U+00bdec: */
+ RTUNI_ALPHA, /* U+00bded: */
+ RTUNI_ALPHA, /* U+00bdee: */
+ RTUNI_ALPHA, /* U+00bdef: */
+ RTUNI_ALPHA, /* U+00bdf0: */
+ RTUNI_ALPHA, /* U+00bdf1: */
+ RTUNI_ALPHA, /* U+00bdf2: */
+ RTUNI_ALPHA, /* U+00bdf3: */
+ RTUNI_ALPHA, /* U+00bdf4: */
+ RTUNI_ALPHA, /* U+00bdf5: */
+ RTUNI_ALPHA, /* U+00bdf6: */
+ RTUNI_ALPHA, /* U+00bdf7: */
+ RTUNI_ALPHA, /* U+00bdf8: */
+ RTUNI_ALPHA, /* U+00bdf9: */
+ RTUNI_ALPHA, /* U+00bdfa: */
+ RTUNI_ALPHA, /* U+00bdfb: */
+ RTUNI_ALPHA, /* U+00bdfc: */
+ RTUNI_ALPHA, /* U+00bdfd: */
+ RTUNI_ALPHA, /* U+00bdfe: */
+ RTUNI_ALPHA, /* U+00bdff: */
+ RTUNI_ALPHA, /* U+00be00: */
+ RTUNI_ALPHA, /* U+00be01: */
+ RTUNI_ALPHA, /* U+00be02: */
+ RTUNI_ALPHA, /* U+00be03: */
+ RTUNI_ALPHA, /* U+00be04: */
+ RTUNI_ALPHA, /* U+00be05: */
+ RTUNI_ALPHA, /* U+00be06: */
+ RTUNI_ALPHA, /* U+00be07: */
+ RTUNI_ALPHA, /* U+00be08: */
+ RTUNI_ALPHA, /* U+00be09: */
+ RTUNI_ALPHA, /* U+00be0a: */
+ RTUNI_ALPHA, /* U+00be0b: */
+ RTUNI_ALPHA, /* U+00be0c: */
+ RTUNI_ALPHA, /* U+00be0d: */
+ RTUNI_ALPHA, /* U+00be0e: */
+ RTUNI_ALPHA, /* U+00be0f: */
+ RTUNI_ALPHA, /* U+00be10: */
+ RTUNI_ALPHA, /* U+00be11: */
+ RTUNI_ALPHA, /* U+00be12: */
+ RTUNI_ALPHA, /* U+00be13: */
+ RTUNI_ALPHA, /* U+00be14: */
+ RTUNI_ALPHA, /* U+00be15: */
+ RTUNI_ALPHA, /* U+00be16: */
+ RTUNI_ALPHA, /* U+00be17: */
+ RTUNI_ALPHA, /* U+00be18: */
+ RTUNI_ALPHA, /* U+00be19: */
+ RTUNI_ALPHA, /* U+00be1a: */
+ RTUNI_ALPHA, /* U+00be1b: */
+ RTUNI_ALPHA, /* U+00be1c: */
+ RTUNI_ALPHA, /* U+00be1d: */
+ RTUNI_ALPHA, /* U+00be1e: */
+ RTUNI_ALPHA, /* U+00be1f: */
+ RTUNI_ALPHA, /* U+00be20: */
+ RTUNI_ALPHA, /* U+00be21: */
+ RTUNI_ALPHA, /* U+00be22: */
+ RTUNI_ALPHA, /* U+00be23: */
+ RTUNI_ALPHA, /* U+00be24: */
+ RTUNI_ALPHA, /* U+00be25: */
+ RTUNI_ALPHA, /* U+00be26: */
+ RTUNI_ALPHA, /* U+00be27: */
+ RTUNI_ALPHA, /* U+00be28: */
+ RTUNI_ALPHA, /* U+00be29: */
+ RTUNI_ALPHA, /* U+00be2a: */
+ RTUNI_ALPHA, /* U+00be2b: */
+ RTUNI_ALPHA, /* U+00be2c: */
+ RTUNI_ALPHA, /* U+00be2d: */
+ RTUNI_ALPHA, /* U+00be2e: */
+ RTUNI_ALPHA, /* U+00be2f: */
+ RTUNI_ALPHA, /* U+00be30: */
+ RTUNI_ALPHA, /* U+00be31: */
+ RTUNI_ALPHA, /* U+00be32: */
+ RTUNI_ALPHA, /* U+00be33: */
+ RTUNI_ALPHA, /* U+00be34: */
+ RTUNI_ALPHA, /* U+00be35: */
+ RTUNI_ALPHA, /* U+00be36: */
+ RTUNI_ALPHA, /* U+00be37: */
+ RTUNI_ALPHA, /* U+00be38: */
+ RTUNI_ALPHA, /* U+00be39: */
+ RTUNI_ALPHA, /* U+00be3a: */
+ RTUNI_ALPHA, /* U+00be3b: */
+ RTUNI_ALPHA, /* U+00be3c: */
+ RTUNI_ALPHA, /* U+00be3d: */
+ RTUNI_ALPHA, /* U+00be3e: */
+ RTUNI_ALPHA, /* U+00be3f: */
+ RTUNI_ALPHA, /* U+00be40: */
+ RTUNI_ALPHA, /* U+00be41: */
+ RTUNI_ALPHA, /* U+00be42: */
+ RTUNI_ALPHA, /* U+00be43: */
+ RTUNI_ALPHA, /* U+00be44: */
+ RTUNI_ALPHA, /* U+00be45: */
+ RTUNI_ALPHA, /* U+00be46: */
+ RTUNI_ALPHA, /* U+00be47: */
+ RTUNI_ALPHA, /* U+00be48: */
+ RTUNI_ALPHA, /* U+00be49: */
+ RTUNI_ALPHA, /* U+00be4a: */
+ RTUNI_ALPHA, /* U+00be4b: */
+ RTUNI_ALPHA, /* U+00be4c: */
+ RTUNI_ALPHA, /* U+00be4d: */
+ RTUNI_ALPHA, /* U+00be4e: */
+ RTUNI_ALPHA, /* U+00be4f: */
+ RTUNI_ALPHA, /* U+00be50: */
+ RTUNI_ALPHA, /* U+00be51: */
+ RTUNI_ALPHA, /* U+00be52: */
+ RTUNI_ALPHA, /* U+00be53: */
+ RTUNI_ALPHA, /* U+00be54: */
+ RTUNI_ALPHA, /* U+00be55: */
+ RTUNI_ALPHA, /* U+00be56: */
+ RTUNI_ALPHA, /* U+00be57: */
+ RTUNI_ALPHA, /* U+00be58: */
+ RTUNI_ALPHA, /* U+00be59: */
+ RTUNI_ALPHA, /* U+00be5a: */
+ RTUNI_ALPHA, /* U+00be5b: */
+ RTUNI_ALPHA, /* U+00be5c: */
+ RTUNI_ALPHA, /* U+00be5d: */
+ RTUNI_ALPHA, /* U+00be5e: */
+ RTUNI_ALPHA, /* U+00be5f: */
+ RTUNI_ALPHA, /* U+00be60: */
+ RTUNI_ALPHA, /* U+00be61: */
+ RTUNI_ALPHA, /* U+00be62: */
+ RTUNI_ALPHA, /* U+00be63: */
+ RTUNI_ALPHA, /* U+00be64: */
+ RTUNI_ALPHA, /* U+00be65: */
+ RTUNI_ALPHA, /* U+00be66: */
+ RTUNI_ALPHA, /* U+00be67: */
+ RTUNI_ALPHA, /* U+00be68: */
+ RTUNI_ALPHA, /* U+00be69: */
+ RTUNI_ALPHA, /* U+00be6a: */
+ RTUNI_ALPHA, /* U+00be6b: */
+ RTUNI_ALPHA, /* U+00be6c: */
+ RTUNI_ALPHA, /* U+00be6d: */
+ RTUNI_ALPHA, /* U+00be6e: */
+ RTUNI_ALPHA, /* U+00be6f: */
+ RTUNI_ALPHA, /* U+00be70: */
+ RTUNI_ALPHA, /* U+00be71: */
+ RTUNI_ALPHA, /* U+00be72: */
+ RTUNI_ALPHA, /* U+00be73: */
+ RTUNI_ALPHA, /* U+00be74: */
+ RTUNI_ALPHA, /* U+00be75: */
+ RTUNI_ALPHA, /* U+00be76: */
+ RTUNI_ALPHA, /* U+00be77: */
+ RTUNI_ALPHA, /* U+00be78: */
+ RTUNI_ALPHA, /* U+00be79: */
+ RTUNI_ALPHA, /* U+00be7a: */
+ RTUNI_ALPHA, /* U+00be7b: */
+ RTUNI_ALPHA, /* U+00be7c: */
+ RTUNI_ALPHA, /* U+00be7d: */
+ RTUNI_ALPHA, /* U+00be7e: */
+ RTUNI_ALPHA, /* U+00be7f: */
+ RTUNI_ALPHA, /* U+00be80: */
+ RTUNI_ALPHA, /* U+00be81: */
+ RTUNI_ALPHA, /* U+00be82: */
+ RTUNI_ALPHA, /* U+00be83: */
+ RTUNI_ALPHA, /* U+00be84: */
+ RTUNI_ALPHA, /* U+00be85: */
+ RTUNI_ALPHA, /* U+00be86: */
+ RTUNI_ALPHA, /* U+00be87: */
+ RTUNI_ALPHA, /* U+00be88: */
+ RTUNI_ALPHA, /* U+00be89: */
+ RTUNI_ALPHA, /* U+00be8a: */
+ RTUNI_ALPHA, /* U+00be8b: */
+ RTUNI_ALPHA, /* U+00be8c: */
+ RTUNI_ALPHA, /* U+00be8d: */
+ RTUNI_ALPHA, /* U+00be8e: */
+ RTUNI_ALPHA, /* U+00be8f: */
+ RTUNI_ALPHA, /* U+00be90: */
+ RTUNI_ALPHA, /* U+00be91: */
+ RTUNI_ALPHA, /* U+00be92: */
+ RTUNI_ALPHA, /* U+00be93: */
+ RTUNI_ALPHA, /* U+00be94: */
+ RTUNI_ALPHA, /* U+00be95: */
+ RTUNI_ALPHA, /* U+00be96: */
+ RTUNI_ALPHA, /* U+00be97: */
+ RTUNI_ALPHA, /* U+00be98: */
+ RTUNI_ALPHA, /* U+00be99: */
+ RTUNI_ALPHA, /* U+00be9a: */
+ RTUNI_ALPHA, /* U+00be9b: */
+ RTUNI_ALPHA, /* U+00be9c: */
+ RTUNI_ALPHA, /* U+00be9d: */
+ RTUNI_ALPHA, /* U+00be9e: */
+ RTUNI_ALPHA, /* U+00be9f: */
+ RTUNI_ALPHA, /* U+00bea0: */
+ RTUNI_ALPHA, /* U+00bea1: */
+ RTUNI_ALPHA, /* U+00bea2: */
+ RTUNI_ALPHA, /* U+00bea3: */
+ RTUNI_ALPHA, /* U+00bea4: */
+ RTUNI_ALPHA, /* U+00bea5: */
+ RTUNI_ALPHA, /* U+00bea6: */
+ RTUNI_ALPHA, /* U+00bea7: */
+ RTUNI_ALPHA, /* U+00bea8: */
+ RTUNI_ALPHA, /* U+00bea9: */
+ RTUNI_ALPHA, /* U+00beaa: */
+ RTUNI_ALPHA, /* U+00beab: */
+ RTUNI_ALPHA, /* U+00beac: */
+ RTUNI_ALPHA, /* U+00bead: */
+ RTUNI_ALPHA, /* U+00beae: */
+ RTUNI_ALPHA, /* U+00beaf: */
+ RTUNI_ALPHA, /* U+00beb0: */
+ RTUNI_ALPHA, /* U+00beb1: */
+ RTUNI_ALPHA, /* U+00beb2: */
+ RTUNI_ALPHA, /* U+00beb3: */
+ RTUNI_ALPHA, /* U+00beb4: */
+ RTUNI_ALPHA, /* U+00beb5: */
+ RTUNI_ALPHA, /* U+00beb6: */
+ RTUNI_ALPHA, /* U+00beb7: */
+ RTUNI_ALPHA, /* U+00beb8: */
+ RTUNI_ALPHA, /* U+00beb9: */
+ RTUNI_ALPHA, /* U+00beba: */
+ RTUNI_ALPHA, /* U+00bebb: */
+ RTUNI_ALPHA, /* U+00bebc: */
+ RTUNI_ALPHA, /* U+00bebd: */
+ RTUNI_ALPHA, /* U+00bebe: */
+ RTUNI_ALPHA, /* U+00bebf: */
+ RTUNI_ALPHA, /* U+00bec0: */
+ RTUNI_ALPHA, /* U+00bec1: */
+ RTUNI_ALPHA, /* U+00bec2: */
+ RTUNI_ALPHA, /* U+00bec3: */
+ RTUNI_ALPHA, /* U+00bec4: */
+ RTUNI_ALPHA, /* U+00bec5: */
+ RTUNI_ALPHA, /* U+00bec6: */
+ RTUNI_ALPHA, /* U+00bec7: */
+ RTUNI_ALPHA, /* U+00bec8: */
+ RTUNI_ALPHA, /* U+00bec9: */
+ RTUNI_ALPHA, /* U+00beca: */
+ RTUNI_ALPHA, /* U+00becb: */
+ RTUNI_ALPHA, /* U+00becc: */
+ RTUNI_ALPHA, /* U+00becd: */
+ RTUNI_ALPHA, /* U+00bece: */
+ RTUNI_ALPHA, /* U+00becf: */
+ RTUNI_ALPHA, /* U+00bed0: */
+ RTUNI_ALPHA, /* U+00bed1: */
+ RTUNI_ALPHA, /* U+00bed2: */
+ RTUNI_ALPHA, /* U+00bed3: */
+ RTUNI_ALPHA, /* U+00bed4: */
+ RTUNI_ALPHA, /* U+00bed5: */
+ RTUNI_ALPHA, /* U+00bed6: */
+ RTUNI_ALPHA, /* U+00bed7: */
+ RTUNI_ALPHA, /* U+00bed8: */
+ RTUNI_ALPHA, /* U+00bed9: */
+ RTUNI_ALPHA, /* U+00beda: */
+ RTUNI_ALPHA, /* U+00bedb: */
+ RTUNI_ALPHA, /* U+00bedc: */
+ RTUNI_ALPHA, /* U+00bedd: */
+ RTUNI_ALPHA, /* U+00bede: */
+ RTUNI_ALPHA, /* U+00bedf: */
+ RTUNI_ALPHA, /* U+00bee0: */
+ RTUNI_ALPHA, /* U+00bee1: */
+ RTUNI_ALPHA, /* U+00bee2: */
+ RTUNI_ALPHA, /* U+00bee3: */
+ RTUNI_ALPHA, /* U+00bee4: */
+ RTUNI_ALPHA, /* U+00bee5: */
+ RTUNI_ALPHA, /* U+00bee6: */
+ RTUNI_ALPHA, /* U+00bee7: */
+ RTUNI_ALPHA, /* U+00bee8: */
+ RTUNI_ALPHA, /* U+00bee9: */
+ RTUNI_ALPHA, /* U+00beea: */
+ RTUNI_ALPHA, /* U+00beeb: */
+ RTUNI_ALPHA, /* U+00beec: */
+ RTUNI_ALPHA, /* U+00beed: */
+ RTUNI_ALPHA, /* U+00beee: */
+ RTUNI_ALPHA, /* U+00beef: */
+ RTUNI_ALPHA, /* U+00bef0: */
+ RTUNI_ALPHA, /* U+00bef1: */
+ RTUNI_ALPHA, /* U+00bef2: */
+ RTUNI_ALPHA, /* U+00bef3: */
+ RTUNI_ALPHA, /* U+00bef4: */
+ RTUNI_ALPHA, /* U+00bef5: */
+ RTUNI_ALPHA, /* U+00bef6: */
+ RTUNI_ALPHA, /* U+00bef7: */
+ RTUNI_ALPHA, /* U+00bef8: */
+ RTUNI_ALPHA, /* U+00bef9: */
+ RTUNI_ALPHA, /* U+00befa: */
+ RTUNI_ALPHA, /* U+00befb: */
+ RTUNI_ALPHA, /* U+00befc: */
+ RTUNI_ALPHA, /* U+00befd: */
+ RTUNI_ALPHA, /* U+00befe: */
+ RTUNI_ALPHA, /* U+00beff: */
+ RTUNI_ALPHA, /* U+00bf00: */
+ RTUNI_ALPHA, /* U+00bf01: */
+ RTUNI_ALPHA, /* U+00bf02: */
+ RTUNI_ALPHA, /* U+00bf03: */
+ RTUNI_ALPHA, /* U+00bf04: */
+ RTUNI_ALPHA, /* U+00bf05: */
+ RTUNI_ALPHA, /* U+00bf06: */
+ RTUNI_ALPHA, /* U+00bf07: */
+ RTUNI_ALPHA, /* U+00bf08: */
+ RTUNI_ALPHA, /* U+00bf09: */
+ RTUNI_ALPHA, /* U+00bf0a: */
+ RTUNI_ALPHA, /* U+00bf0b: */
+ RTUNI_ALPHA, /* U+00bf0c: */
+ RTUNI_ALPHA, /* U+00bf0d: */
+ RTUNI_ALPHA, /* U+00bf0e: */
+ RTUNI_ALPHA, /* U+00bf0f: */
+ RTUNI_ALPHA, /* U+00bf10: */
+ RTUNI_ALPHA, /* U+00bf11: */
+ RTUNI_ALPHA, /* U+00bf12: */
+ RTUNI_ALPHA, /* U+00bf13: */
+ RTUNI_ALPHA, /* U+00bf14: */
+ RTUNI_ALPHA, /* U+00bf15: */
+ RTUNI_ALPHA, /* U+00bf16: */
+ RTUNI_ALPHA, /* U+00bf17: */
+ RTUNI_ALPHA, /* U+00bf18: */
+ RTUNI_ALPHA, /* U+00bf19: */
+ RTUNI_ALPHA, /* U+00bf1a: */
+ RTUNI_ALPHA, /* U+00bf1b: */
+ RTUNI_ALPHA, /* U+00bf1c: */
+ RTUNI_ALPHA, /* U+00bf1d: */
+ RTUNI_ALPHA, /* U+00bf1e: */
+ RTUNI_ALPHA, /* U+00bf1f: */
+ RTUNI_ALPHA, /* U+00bf20: */
+ RTUNI_ALPHA, /* U+00bf21: */
+ RTUNI_ALPHA, /* U+00bf22: */
+ RTUNI_ALPHA, /* U+00bf23: */
+ RTUNI_ALPHA, /* U+00bf24: */
+ RTUNI_ALPHA, /* U+00bf25: */
+ RTUNI_ALPHA, /* U+00bf26: */
+ RTUNI_ALPHA, /* U+00bf27: */
+ RTUNI_ALPHA, /* U+00bf28: */
+ RTUNI_ALPHA, /* U+00bf29: */
+ RTUNI_ALPHA, /* U+00bf2a: */
+ RTUNI_ALPHA, /* U+00bf2b: */
+ RTUNI_ALPHA, /* U+00bf2c: */
+ RTUNI_ALPHA, /* U+00bf2d: */
+ RTUNI_ALPHA, /* U+00bf2e: */
+ RTUNI_ALPHA, /* U+00bf2f: */
+ RTUNI_ALPHA, /* U+00bf30: */
+ RTUNI_ALPHA, /* U+00bf31: */
+ RTUNI_ALPHA, /* U+00bf32: */
+ RTUNI_ALPHA, /* U+00bf33: */
+ RTUNI_ALPHA, /* U+00bf34: */
+ RTUNI_ALPHA, /* U+00bf35: */
+ RTUNI_ALPHA, /* U+00bf36: */
+ RTUNI_ALPHA, /* U+00bf37: */
+ RTUNI_ALPHA, /* U+00bf38: */
+ RTUNI_ALPHA, /* U+00bf39: */
+ RTUNI_ALPHA, /* U+00bf3a: */
+ RTUNI_ALPHA, /* U+00bf3b: */
+ RTUNI_ALPHA, /* U+00bf3c: */
+ RTUNI_ALPHA, /* U+00bf3d: */
+ RTUNI_ALPHA, /* U+00bf3e: */
+ RTUNI_ALPHA, /* U+00bf3f: */
+ RTUNI_ALPHA, /* U+00bf40: */
+ RTUNI_ALPHA, /* U+00bf41: */
+ RTUNI_ALPHA, /* U+00bf42: */
+ RTUNI_ALPHA, /* U+00bf43: */
+ RTUNI_ALPHA, /* U+00bf44: */
+ RTUNI_ALPHA, /* U+00bf45: */
+ RTUNI_ALPHA, /* U+00bf46: */
+ RTUNI_ALPHA, /* U+00bf47: */
+ RTUNI_ALPHA, /* U+00bf48: */
+ RTUNI_ALPHA, /* U+00bf49: */
+ RTUNI_ALPHA, /* U+00bf4a: */
+ RTUNI_ALPHA, /* U+00bf4b: */
+ RTUNI_ALPHA, /* U+00bf4c: */
+ RTUNI_ALPHA, /* U+00bf4d: */
+ RTUNI_ALPHA, /* U+00bf4e: */
+ RTUNI_ALPHA, /* U+00bf4f: */
+ RTUNI_ALPHA, /* U+00bf50: */
+ RTUNI_ALPHA, /* U+00bf51: */
+ RTUNI_ALPHA, /* U+00bf52: */
+ RTUNI_ALPHA, /* U+00bf53: */
+ RTUNI_ALPHA, /* U+00bf54: */
+ RTUNI_ALPHA, /* U+00bf55: */
+ RTUNI_ALPHA, /* U+00bf56: */
+ RTUNI_ALPHA, /* U+00bf57: */
+ RTUNI_ALPHA, /* U+00bf58: */
+ RTUNI_ALPHA, /* U+00bf59: */
+ RTUNI_ALPHA, /* U+00bf5a: */
+ RTUNI_ALPHA, /* U+00bf5b: */
+ RTUNI_ALPHA, /* U+00bf5c: */
+ RTUNI_ALPHA, /* U+00bf5d: */
+ RTUNI_ALPHA, /* U+00bf5e: */
+ RTUNI_ALPHA, /* U+00bf5f: */
+ RTUNI_ALPHA, /* U+00bf60: */
+ RTUNI_ALPHA, /* U+00bf61: */
+ RTUNI_ALPHA, /* U+00bf62: */
+ RTUNI_ALPHA, /* U+00bf63: */
+ RTUNI_ALPHA, /* U+00bf64: */
+ RTUNI_ALPHA, /* U+00bf65: */
+ RTUNI_ALPHA, /* U+00bf66: */
+ RTUNI_ALPHA, /* U+00bf67: */
+ RTUNI_ALPHA, /* U+00bf68: */
+ RTUNI_ALPHA, /* U+00bf69: */
+ RTUNI_ALPHA, /* U+00bf6a: */
+ RTUNI_ALPHA, /* U+00bf6b: */
+ RTUNI_ALPHA, /* U+00bf6c: */
+ RTUNI_ALPHA, /* U+00bf6d: */
+ RTUNI_ALPHA, /* U+00bf6e: */
+ RTUNI_ALPHA, /* U+00bf6f: */
+ RTUNI_ALPHA, /* U+00bf70: */
+ RTUNI_ALPHA, /* U+00bf71: */
+ RTUNI_ALPHA, /* U+00bf72: */
+ RTUNI_ALPHA, /* U+00bf73: */
+ RTUNI_ALPHA, /* U+00bf74: */
+ RTUNI_ALPHA, /* U+00bf75: */
+ RTUNI_ALPHA, /* U+00bf76: */
+ RTUNI_ALPHA, /* U+00bf77: */
+ RTUNI_ALPHA, /* U+00bf78: */
+ RTUNI_ALPHA, /* U+00bf79: */
+ RTUNI_ALPHA, /* U+00bf7a: */
+ RTUNI_ALPHA, /* U+00bf7b: */
+ RTUNI_ALPHA, /* U+00bf7c: */
+ RTUNI_ALPHA, /* U+00bf7d: */
+ RTUNI_ALPHA, /* U+00bf7e: */
+ RTUNI_ALPHA, /* U+00bf7f: */
+ RTUNI_ALPHA, /* U+00bf80: */
+ RTUNI_ALPHA, /* U+00bf81: */
+ RTUNI_ALPHA, /* U+00bf82: */
+ RTUNI_ALPHA, /* U+00bf83: */
+ RTUNI_ALPHA, /* U+00bf84: */
+ RTUNI_ALPHA, /* U+00bf85: */
+ RTUNI_ALPHA, /* U+00bf86: */
+ RTUNI_ALPHA, /* U+00bf87: */
+ RTUNI_ALPHA, /* U+00bf88: */
+ RTUNI_ALPHA, /* U+00bf89: */
+ RTUNI_ALPHA, /* U+00bf8a: */
+ RTUNI_ALPHA, /* U+00bf8b: */
+ RTUNI_ALPHA, /* U+00bf8c: */
+ RTUNI_ALPHA, /* U+00bf8d: */
+ RTUNI_ALPHA, /* U+00bf8e: */
+ RTUNI_ALPHA, /* U+00bf8f: */
+ RTUNI_ALPHA, /* U+00bf90: */
+ RTUNI_ALPHA, /* U+00bf91: */
+ RTUNI_ALPHA, /* U+00bf92: */
+ RTUNI_ALPHA, /* U+00bf93: */
+ RTUNI_ALPHA, /* U+00bf94: */
+ RTUNI_ALPHA, /* U+00bf95: */
+ RTUNI_ALPHA, /* U+00bf96: */
+ RTUNI_ALPHA, /* U+00bf97: */
+ RTUNI_ALPHA, /* U+00bf98: */
+ RTUNI_ALPHA, /* U+00bf99: */
+ RTUNI_ALPHA, /* U+00bf9a: */
+ RTUNI_ALPHA, /* U+00bf9b: */
+ RTUNI_ALPHA, /* U+00bf9c: */
+ RTUNI_ALPHA, /* U+00bf9d: */
+ RTUNI_ALPHA, /* U+00bf9e: */
+ RTUNI_ALPHA, /* U+00bf9f: */
+ RTUNI_ALPHA, /* U+00bfa0: */
+ RTUNI_ALPHA, /* U+00bfa1: */
+ RTUNI_ALPHA, /* U+00bfa2: */
+ RTUNI_ALPHA, /* U+00bfa3: */
+ RTUNI_ALPHA, /* U+00bfa4: */
+ RTUNI_ALPHA, /* U+00bfa5: */
+ RTUNI_ALPHA, /* U+00bfa6: */
+ RTUNI_ALPHA, /* U+00bfa7: */
+ RTUNI_ALPHA, /* U+00bfa8: */
+ RTUNI_ALPHA, /* U+00bfa9: */
+ RTUNI_ALPHA, /* U+00bfaa: */
+ RTUNI_ALPHA, /* U+00bfab: */
+ RTUNI_ALPHA, /* U+00bfac: */
+ RTUNI_ALPHA, /* U+00bfad: */
+ RTUNI_ALPHA, /* U+00bfae: */
+ RTUNI_ALPHA, /* U+00bfaf: */
+ RTUNI_ALPHA, /* U+00bfb0: */
+ RTUNI_ALPHA, /* U+00bfb1: */
+ RTUNI_ALPHA, /* U+00bfb2: */
+ RTUNI_ALPHA, /* U+00bfb3: */
+ RTUNI_ALPHA, /* U+00bfb4: */
+ RTUNI_ALPHA, /* U+00bfb5: */
+ RTUNI_ALPHA, /* U+00bfb6: */
+ RTUNI_ALPHA, /* U+00bfb7: */
+ RTUNI_ALPHA, /* U+00bfb8: */
+ RTUNI_ALPHA, /* U+00bfb9: */
+ RTUNI_ALPHA, /* U+00bfba: */
+ RTUNI_ALPHA, /* U+00bfbb: */
+ RTUNI_ALPHA, /* U+00bfbc: */
+ RTUNI_ALPHA, /* U+00bfbd: */
+ RTUNI_ALPHA, /* U+00bfbe: */
+ RTUNI_ALPHA, /* U+00bfbf: */
+ RTUNI_ALPHA, /* U+00bfc0: */
+ RTUNI_ALPHA, /* U+00bfc1: */
+ RTUNI_ALPHA, /* U+00bfc2: */
+ RTUNI_ALPHA, /* U+00bfc3: */
+ RTUNI_ALPHA, /* U+00bfc4: */
+ RTUNI_ALPHA, /* U+00bfc5: */
+ RTUNI_ALPHA, /* U+00bfc6: */
+ RTUNI_ALPHA, /* U+00bfc7: */
+ RTUNI_ALPHA, /* U+00bfc8: */
+ RTUNI_ALPHA, /* U+00bfc9: */
+ RTUNI_ALPHA, /* U+00bfca: */
+ RTUNI_ALPHA, /* U+00bfcb: */
+ RTUNI_ALPHA, /* U+00bfcc: */
+ RTUNI_ALPHA, /* U+00bfcd: */
+ RTUNI_ALPHA, /* U+00bfce: */
+ RTUNI_ALPHA, /* U+00bfcf: */
+ RTUNI_ALPHA, /* U+00bfd0: */
+ RTUNI_ALPHA, /* U+00bfd1: */
+ RTUNI_ALPHA, /* U+00bfd2: */
+ RTUNI_ALPHA, /* U+00bfd3: */
+ RTUNI_ALPHA, /* U+00bfd4: */
+ RTUNI_ALPHA, /* U+00bfd5: */
+ RTUNI_ALPHA, /* U+00bfd6: */
+ RTUNI_ALPHA, /* U+00bfd7: */
+ RTUNI_ALPHA, /* U+00bfd8: */
+ RTUNI_ALPHA, /* U+00bfd9: */
+ RTUNI_ALPHA, /* U+00bfda: */
+ RTUNI_ALPHA, /* U+00bfdb: */
+ RTUNI_ALPHA, /* U+00bfdc: */
+ RTUNI_ALPHA, /* U+00bfdd: */
+ RTUNI_ALPHA, /* U+00bfde: */
+ RTUNI_ALPHA, /* U+00bfdf: */
+ RTUNI_ALPHA, /* U+00bfe0: */
+ RTUNI_ALPHA, /* U+00bfe1: */
+ RTUNI_ALPHA, /* U+00bfe2: */
+ RTUNI_ALPHA, /* U+00bfe3: */
+ RTUNI_ALPHA, /* U+00bfe4: */
+ RTUNI_ALPHA, /* U+00bfe5: */
+ RTUNI_ALPHA, /* U+00bfe6: */
+ RTUNI_ALPHA, /* U+00bfe7: */
+ RTUNI_ALPHA, /* U+00bfe8: */
+ RTUNI_ALPHA, /* U+00bfe9: */
+ RTUNI_ALPHA, /* U+00bfea: */
+ RTUNI_ALPHA, /* U+00bfeb: */
+ RTUNI_ALPHA, /* U+00bfec: */
+ RTUNI_ALPHA, /* U+00bfed: */
+ RTUNI_ALPHA, /* U+00bfee: */
+ RTUNI_ALPHA, /* U+00bfef: */
+ RTUNI_ALPHA, /* U+00bff0: */
+ RTUNI_ALPHA, /* U+00bff1: */
+ RTUNI_ALPHA, /* U+00bff2: */
+ RTUNI_ALPHA, /* U+00bff3: */
+ RTUNI_ALPHA, /* U+00bff4: */
+ RTUNI_ALPHA, /* U+00bff5: */
+ RTUNI_ALPHA, /* U+00bff6: */
+ RTUNI_ALPHA, /* U+00bff7: */
+ RTUNI_ALPHA, /* U+00bff8: */
+ RTUNI_ALPHA, /* U+00bff9: */
+ RTUNI_ALPHA, /* U+00bffa: */
+ RTUNI_ALPHA, /* U+00bffb: */
+ RTUNI_ALPHA, /* U+00bffc: */
+ RTUNI_ALPHA, /* U+00bffd: */
+ RTUNI_ALPHA, /* U+00bffe: */
+ RTUNI_ALPHA, /* U+00bfff: */
+ RTUNI_ALPHA, /* U+00c000: */
+ RTUNI_ALPHA, /* U+00c001: */
+ RTUNI_ALPHA, /* U+00c002: */
+ RTUNI_ALPHA, /* U+00c003: */
+ RTUNI_ALPHA, /* U+00c004: */
+ RTUNI_ALPHA, /* U+00c005: */
+ RTUNI_ALPHA, /* U+00c006: */
+ RTUNI_ALPHA, /* U+00c007: */
+ RTUNI_ALPHA, /* U+00c008: */
+ RTUNI_ALPHA, /* U+00c009: */
+ RTUNI_ALPHA, /* U+00c00a: */
+ RTUNI_ALPHA, /* U+00c00b: */
+ RTUNI_ALPHA, /* U+00c00c: */
+ RTUNI_ALPHA, /* U+00c00d: */
+ RTUNI_ALPHA, /* U+00c00e: */
+ RTUNI_ALPHA, /* U+00c00f: */
+ RTUNI_ALPHA, /* U+00c010: */
+ RTUNI_ALPHA, /* U+00c011: */
+ RTUNI_ALPHA, /* U+00c012: */
+ RTUNI_ALPHA, /* U+00c013: */
+ RTUNI_ALPHA, /* U+00c014: */
+ RTUNI_ALPHA, /* U+00c015: */
+ RTUNI_ALPHA, /* U+00c016: */
+ RTUNI_ALPHA, /* U+00c017: */
+ RTUNI_ALPHA, /* U+00c018: */
+ RTUNI_ALPHA, /* U+00c019: */
+ RTUNI_ALPHA, /* U+00c01a: */
+ RTUNI_ALPHA, /* U+00c01b: */
+ RTUNI_ALPHA, /* U+00c01c: */
+ RTUNI_ALPHA, /* U+00c01d: */
+ RTUNI_ALPHA, /* U+00c01e: */
+ RTUNI_ALPHA, /* U+00c01f: */
+ RTUNI_ALPHA, /* U+00c020: */
+ RTUNI_ALPHA, /* U+00c021: */
+ RTUNI_ALPHA, /* U+00c022: */
+ RTUNI_ALPHA, /* U+00c023: */
+ RTUNI_ALPHA, /* U+00c024: */
+ RTUNI_ALPHA, /* U+00c025: */
+ RTUNI_ALPHA, /* U+00c026: */
+ RTUNI_ALPHA, /* U+00c027: */
+ RTUNI_ALPHA, /* U+00c028: */
+ RTUNI_ALPHA, /* U+00c029: */
+ RTUNI_ALPHA, /* U+00c02a: */
+ RTUNI_ALPHA, /* U+00c02b: */
+ RTUNI_ALPHA, /* U+00c02c: */
+ RTUNI_ALPHA, /* U+00c02d: */
+ RTUNI_ALPHA, /* U+00c02e: */
+ RTUNI_ALPHA, /* U+00c02f: */
+ RTUNI_ALPHA, /* U+00c030: */
+ RTUNI_ALPHA, /* U+00c031: */
+ RTUNI_ALPHA, /* U+00c032: */
+ RTUNI_ALPHA, /* U+00c033: */
+ RTUNI_ALPHA, /* U+00c034: */
+ RTUNI_ALPHA, /* U+00c035: */
+ RTUNI_ALPHA, /* U+00c036: */
+ RTUNI_ALPHA, /* U+00c037: */
+ RTUNI_ALPHA, /* U+00c038: */
+ RTUNI_ALPHA, /* U+00c039: */
+ RTUNI_ALPHA, /* U+00c03a: */
+ RTUNI_ALPHA, /* U+00c03b: */
+ RTUNI_ALPHA, /* U+00c03c: */
+ RTUNI_ALPHA, /* U+00c03d: */
+ RTUNI_ALPHA, /* U+00c03e: */
+ RTUNI_ALPHA, /* U+00c03f: */
+ RTUNI_ALPHA, /* U+00c040: */
+ RTUNI_ALPHA, /* U+00c041: */
+ RTUNI_ALPHA, /* U+00c042: */
+ RTUNI_ALPHA, /* U+00c043: */
+ RTUNI_ALPHA, /* U+00c044: */
+ RTUNI_ALPHA, /* U+00c045: */
+ RTUNI_ALPHA, /* U+00c046: */
+ RTUNI_ALPHA, /* U+00c047: */
+ RTUNI_ALPHA, /* U+00c048: */
+ RTUNI_ALPHA, /* U+00c049: */
+ RTUNI_ALPHA, /* U+00c04a: */
+ RTUNI_ALPHA, /* U+00c04b: */
+ RTUNI_ALPHA, /* U+00c04c: */
+ RTUNI_ALPHA, /* U+00c04d: */
+ RTUNI_ALPHA, /* U+00c04e: */
+ RTUNI_ALPHA, /* U+00c04f: */
+ RTUNI_ALPHA, /* U+00c050: */
+ RTUNI_ALPHA, /* U+00c051: */
+ RTUNI_ALPHA, /* U+00c052: */
+ RTUNI_ALPHA, /* U+00c053: */
+ RTUNI_ALPHA, /* U+00c054: */
+ RTUNI_ALPHA, /* U+00c055: */
+ RTUNI_ALPHA, /* U+00c056: */
+ RTUNI_ALPHA, /* U+00c057: */
+ RTUNI_ALPHA, /* U+00c058: */
+ RTUNI_ALPHA, /* U+00c059: */
+ RTUNI_ALPHA, /* U+00c05a: */
+ RTUNI_ALPHA, /* U+00c05b: */
+ RTUNI_ALPHA, /* U+00c05c: */
+ RTUNI_ALPHA, /* U+00c05d: */
+ RTUNI_ALPHA, /* U+00c05e: */
+ RTUNI_ALPHA, /* U+00c05f: */
+ RTUNI_ALPHA, /* U+00c060: */
+ RTUNI_ALPHA, /* U+00c061: */
+ RTUNI_ALPHA, /* U+00c062: */
+ RTUNI_ALPHA, /* U+00c063: */
+ RTUNI_ALPHA, /* U+00c064: */
+ RTUNI_ALPHA, /* U+00c065: */
+ RTUNI_ALPHA, /* U+00c066: */
+ RTUNI_ALPHA, /* U+00c067: */
+ RTUNI_ALPHA, /* U+00c068: */
+ RTUNI_ALPHA, /* U+00c069: */
+ RTUNI_ALPHA, /* U+00c06a: */
+ RTUNI_ALPHA, /* U+00c06b: */
+ RTUNI_ALPHA, /* U+00c06c: */
+ RTUNI_ALPHA, /* U+00c06d: */
+ RTUNI_ALPHA, /* U+00c06e: */
+ RTUNI_ALPHA, /* U+00c06f: */
+ RTUNI_ALPHA, /* U+00c070: */
+ RTUNI_ALPHA, /* U+00c071: */
+ RTUNI_ALPHA, /* U+00c072: */
+ RTUNI_ALPHA, /* U+00c073: */
+ RTUNI_ALPHA, /* U+00c074: */
+ RTUNI_ALPHA, /* U+00c075: */
+ RTUNI_ALPHA, /* U+00c076: */
+ RTUNI_ALPHA, /* U+00c077: */
+ RTUNI_ALPHA, /* U+00c078: */
+ RTUNI_ALPHA, /* U+00c079: */
+ RTUNI_ALPHA, /* U+00c07a: */
+ RTUNI_ALPHA, /* U+00c07b: */
+ RTUNI_ALPHA, /* U+00c07c: */
+ RTUNI_ALPHA, /* U+00c07d: */
+ RTUNI_ALPHA, /* U+00c07e: */
+ RTUNI_ALPHA, /* U+00c07f: */
+ RTUNI_ALPHA, /* U+00c080: */
+ RTUNI_ALPHA, /* U+00c081: */
+ RTUNI_ALPHA, /* U+00c082: */
+ RTUNI_ALPHA, /* U+00c083: */
+ RTUNI_ALPHA, /* U+00c084: */
+ RTUNI_ALPHA, /* U+00c085: */
+ RTUNI_ALPHA, /* U+00c086: */
+ RTUNI_ALPHA, /* U+00c087: */
+ RTUNI_ALPHA, /* U+00c088: */
+ RTUNI_ALPHA, /* U+00c089: */
+ RTUNI_ALPHA, /* U+00c08a: */
+ RTUNI_ALPHA, /* U+00c08b: */
+ RTUNI_ALPHA, /* U+00c08c: */
+ RTUNI_ALPHA, /* U+00c08d: */
+ RTUNI_ALPHA, /* U+00c08e: */
+ RTUNI_ALPHA, /* U+00c08f: */
+ RTUNI_ALPHA, /* U+00c090: */
+ RTUNI_ALPHA, /* U+00c091: */
+ RTUNI_ALPHA, /* U+00c092: */
+ RTUNI_ALPHA, /* U+00c093: */
+ RTUNI_ALPHA, /* U+00c094: */
+ RTUNI_ALPHA, /* U+00c095: */
+ RTUNI_ALPHA, /* U+00c096: */
+ RTUNI_ALPHA, /* U+00c097: */
+ RTUNI_ALPHA, /* U+00c098: */
+ RTUNI_ALPHA, /* U+00c099: */
+ RTUNI_ALPHA, /* U+00c09a: */
+ RTUNI_ALPHA, /* U+00c09b: */
+ RTUNI_ALPHA, /* U+00c09c: */
+ RTUNI_ALPHA, /* U+00c09d: */
+ RTUNI_ALPHA, /* U+00c09e: */
+ RTUNI_ALPHA, /* U+00c09f: */
+ RTUNI_ALPHA, /* U+00c0a0: */
+ RTUNI_ALPHA, /* U+00c0a1: */
+ RTUNI_ALPHA, /* U+00c0a2: */
+ RTUNI_ALPHA, /* U+00c0a3: */
+ RTUNI_ALPHA, /* U+00c0a4: */
+ RTUNI_ALPHA, /* U+00c0a5: */
+ RTUNI_ALPHA, /* U+00c0a6: */
+ RTUNI_ALPHA, /* U+00c0a7: */
+ RTUNI_ALPHA, /* U+00c0a8: */
+ RTUNI_ALPHA, /* U+00c0a9: */
+ RTUNI_ALPHA, /* U+00c0aa: */
+ RTUNI_ALPHA, /* U+00c0ab: */
+ RTUNI_ALPHA, /* U+00c0ac: */
+ RTUNI_ALPHA, /* U+00c0ad: */
+ RTUNI_ALPHA, /* U+00c0ae: */
+ RTUNI_ALPHA, /* U+00c0af: */
+ RTUNI_ALPHA, /* U+00c0b0: */
+ RTUNI_ALPHA, /* U+00c0b1: */
+ RTUNI_ALPHA, /* U+00c0b2: */
+ RTUNI_ALPHA, /* U+00c0b3: */
+ RTUNI_ALPHA, /* U+00c0b4: */
+ RTUNI_ALPHA, /* U+00c0b5: */
+ RTUNI_ALPHA, /* U+00c0b6: */
+ RTUNI_ALPHA, /* U+00c0b7: */
+ RTUNI_ALPHA, /* U+00c0b8: */
+ RTUNI_ALPHA, /* U+00c0b9: */
+ RTUNI_ALPHA, /* U+00c0ba: */
+ RTUNI_ALPHA, /* U+00c0bb: */
+ RTUNI_ALPHA, /* U+00c0bc: */
+ RTUNI_ALPHA, /* U+00c0bd: */
+ RTUNI_ALPHA, /* U+00c0be: */
+ RTUNI_ALPHA, /* U+00c0bf: */
+ RTUNI_ALPHA, /* U+00c0c0: */
+ RTUNI_ALPHA, /* U+00c0c1: */
+ RTUNI_ALPHA, /* U+00c0c2: */
+ RTUNI_ALPHA, /* U+00c0c3: */
+ RTUNI_ALPHA, /* U+00c0c4: */
+ RTUNI_ALPHA, /* U+00c0c5: */
+ RTUNI_ALPHA, /* U+00c0c6: */
+ RTUNI_ALPHA, /* U+00c0c7: */
+ RTUNI_ALPHA, /* U+00c0c8: */
+ RTUNI_ALPHA, /* U+00c0c9: */
+ RTUNI_ALPHA, /* U+00c0ca: */
+ RTUNI_ALPHA, /* U+00c0cb: */
+ RTUNI_ALPHA, /* U+00c0cc: */
+ RTUNI_ALPHA, /* U+00c0cd: */
+ RTUNI_ALPHA, /* U+00c0ce: */
+ RTUNI_ALPHA, /* U+00c0cf: */
+ RTUNI_ALPHA, /* U+00c0d0: */
+ RTUNI_ALPHA, /* U+00c0d1: */
+ RTUNI_ALPHA, /* U+00c0d2: */
+ RTUNI_ALPHA, /* U+00c0d3: */
+ RTUNI_ALPHA, /* U+00c0d4: */
+ RTUNI_ALPHA, /* U+00c0d5: */
+ RTUNI_ALPHA, /* U+00c0d6: */
+ RTUNI_ALPHA, /* U+00c0d7: */
+ RTUNI_ALPHA, /* U+00c0d8: */
+ RTUNI_ALPHA, /* U+00c0d9: */
+ RTUNI_ALPHA, /* U+00c0da: */
+ RTUNI_ALPHA, /* U+00c0db: */
+ RTUNI_ALPHA, /* U+00c0dc: */
+ RTUNI_ALPHA, /* U+00c0dd: */
+ RTUNI_ALPHA, /* U+00c0de: */
+ RTUNI_ALPHA, /* U+00c0df: */
+ RTUNI_ALPHA, /* U+00c0e0: */
+ RTUNI_ALPHA, /* U+00c0e1: */
+ RTUNI_ALPHA, /* U+00c0e2: */
+ RTUNI_ALPHA, /* U+00c0e3: */
+ RTUNI_ALPHA, /* U+00c0e4: */
+ RTUNI_ALPHA, /* U+00c0e5: */
+ RTUNI_ALPHA, /* U+00c0e6: */
+ RTUNI_ALPHA, /* U+00c0e7: */
+ RTUNI_ALPHA, /* U+00c0e8: */
+ RTUNI_ALPHA, /* U+00c0e9: */
+ RTUNI_ALPHA, /* U+00c0ea: */
+ RTUNI_ALPHA, /* U+00c0eb: */
+ RTUNI_ALPHA, /* U+00c0ec: */
+ RTUNI_ALPHA, /* U+00c0ed: */
+ RTUNI_ALPHA, /* U+00c0ee: */
+ RTUNI_ALPHA, /* U+00c0ef: */
+ RTUNI_ALPHA, /* U+00c0f0: */
+ RTUNI_ALPHA, /* U+00c0f1: */
+ RTUNI_ALPHA, /* U+00c0f2: */
+ RTUNI_ALPHA, /* U+00c0f3: */
+ RTUNI_ALPHA, /* U+00c0f4: */
+ RTUNI_ALPHA, /* U+00c0f5: */
+ RTUNI_ALPHA, /* U+00c0f6: */
+ RTUNI_ALPHA, /* U+00c0f7: */
+ RTUNI_ALPHA, /* U+00c0f8: */
+ RTUNI_ALPHA, /* U+00c0f9: */
+ RTUNI_ALPHA, /* U+00c0fa: */
+ RTUNI_ALPHA, /* U+00c0fb: */
+ RTUNI_ALPHA, /* U+00c0fc: */
+ RTUNI_ALPHA, /* U+00c0fd: */
+ RTUNI_ALPHA, /* U+00c0fe: */
+ RTUNI_ALPHA, /* U+00c0ff: */
+ RTUNI_ALPHA, /* U+00c100: */
+ RTUNI_ALPHA, /* U+00c101: */
+ RTUNI_ALPHA, /* U+00c102: */
+ RTUNI_ALPHA, /* U+00c103: */
+ RTUNI_ALPHA, /* U+00c104: */
+ RTUNI_ALPHA, /* U+00c105: */
+ RTUNI_ALPHA, /* U+00c106: */
+ RTUNI_ALPHA, /* U+00c107: */
+ RTUNI_ALPHA, /* U+00c108: */
+ RTUNI_ALPHA, /* U+00c109: */
+ RTUNI_ALPHA, /* U+00c10a: */
+ RTUNI_ALPHA, /* U+00c10b: */
+ RTUNI_ALPHA, /* U+00c10c: */
+ RTUNI_ALPHA, /* U+00c10d: */
+ RTUNI_ALPHA, /* U+00c10e: */
+ RTUNI_ALPHA, /* U+00c10f: */
+ RTUNI_ALPHA, /* U+00c110: */
+ RTUNI_ALPHA, /* U+00c111: */
+ RTUNI_ALPHA, /* U+00c112: */
+ RTUNI_ALPHA, /* U+00c113: */
+ RTUNI_ALPHA, /* U+00c114: */
+ RTUNI_ALPHA, /* U+00c115: */
+ RTUNI_ALPHA, /* U+00c116: */
+ RTUNI_ALPHA, /* U+00c117: */
+ RTUNI_ALPHA, /* U+00c118: */
+ RTUNI_ALPHA, /* U+00c119: */
+ RTUNI_ALPHA, /* U+00c11a: */
+ RTUNI_ALPHA, /* U+00c11b: */
+ RTUNI_ALPHA, /* U+00c11c: */
+ RTUNI_ALPHA, /* U+00c11d: */
+ RTUNI_ALPHA, /* U+00c11e: */
+ RTUNI_ALPHA, /* U+00c11f: */
+ RTUNI_ALPHA, /* U+00c120: */
+ RTUNI_ALPHA, /* U+00c121: */
+ RTUNI_ALPHA, /* U+00c122: */
+ RTUNI_ALPHA, /* U+00c123: */
+ RTUNI_ALPHA, /* U+00c124: */
+ RTUNI_ALPHA, /* U+00c125: */
+ RTUNI_ALPHA, /* U+00c126: */
+ RTUNI_ALPHA, /* U+00c127: */
+ RTUNI_ALPHA, /* U+00c128: */
+ RTUNI_ALPHA, /* U+00c129: */
+ RTUNI_ALPHA, /* U+00c12a: */
+ RTUNI_ALPHA, /* U+00c12b: */
+ RTUNI_ALPHA, /* U+00c12c: */
+ RTUNI_ALPHA, /* U+00c12d: */
+ RTUNI_ALPHA, /* U+00c12e: */
+ RTUNI_ALPHA, /* U+00c12f: */
+ RTUNI_ALPHA, /* U+00c130: */
+ RTUNI_ALPHA, /* U+00c131: */
+ RTUNI_ALPHA, /* U+00c132: */
+ RTUNI_ALPHA, /* U+00c133: */
+ RTUNI_ALPHA, /* U+00c134: */
+ RTUNI_ALPHA, /* U+00c135: */
+ RTUNI_ALPHA, /* U+00c136: */
+ RTUNI_ALPHA, /* U+00c137: */
+ RTUNI_ALPHA, /* U+00c138: */
+ RTUNI_ALPHA, /* U+00c139: */
+ RTUNI_ALPHA, /* U+00c13a: */
+ RTUNI_ALPHA, /* U+00c13b: */
+ RTUNI_ALPHA, /* U+00c13c: */
+ RTUNI_ALPHA, /* U+00c13d: */
+ RTUNI_ALPHA, /* U+00c13e: */
+ RTUNI_ALPHA, /* U+00c13f: */
+ RTUNI_ALPHA, /* U+00c140: */
+ RTUNI_ALPHA, /* U+00c141: */
+ RTUNI_ALPHA, /* U+00c142: */
+ RTUNI_ALPHA, /* U+00c143: */
+ RTUNI_ALPHA, /* U+00c144: */
+ RTUNI_ALPHA, /* U+00c145: */
+ RTUNI_ALPHA, /* U+00c146: */
+ RTUNI_ALPHA, /* U+00c147: */
+ RTUNI_ALPHA, /* U+00c148: */
+ RTUNI_ALPHA, /* U+00c149: */
+ RTUNI_ALPHA, /* U+00c14a: */
+ RTUNI_ALPHA, /* U+00c14b: */
+ RTUNI_ALPHA, /* U+00c14c: */
+ RTUNI_ALPHA, /* U+00c14d: */
+ RTUNI_ALPHA, /* U+00c14e: */
+ RTUNI_ALPHA, /* U+00c14f: */
+ RTUNI_ALPHA, /* U+00c150: */
+ RTUNI_ALPHA, /* U+00c151: */
+ RTUNI_ALPHA, /* U+00c152: */
+ RTUNI_ALPHA, /* U+00c153: */
+ RTUNI_ALPHA, /* U+00c154: */
+ RTUNI_ALPHA, /* U+00c155: */
+ RTUNI_ALPHA, /* U+00c156: */
+ RTUNI_ALPHA, /* U+00c157: */
+ RTUNI_ALPHA, /* U+00c158: */
+ RTUNI_ALPHA, /* U+00c159: */
+ RTUNI_ALPHA, /* U+00c15a: */
+ RTUNI_ALPHA, /* U+00c15b: */
+ RTUNI_ALPHA, /* U+00c15c: */
+ RTUNI_ALPHA, /* U+00c15d: */
+ RTUNI_ALPHA, /* U+00c15e: */
+ RTUNI_ALPHA, /* U+00c15f: */
+ RTUNI_ALPHA, /* U+00c160: */
+ RTUNI_ALPHA, /* U+00c161: */
+ RTUNI_ALPHA, /* U+00c162: */
+ RTUNI_ALPHA, /* U+00c163: */
+ RTUNI_ALPHA, /* U+00c164: */
+ RTUNI_ALPHA, /* U+00c165: */
+ RTUNI_ALPHA, /* U+00c166: */
+ RTUNI_ALPHA, /* U+00c167: */
+ RTUNI_ALPHA, /* U+00c168: */
+ RTUNI_ALPHA, /* U+00c169: */
+ RTUNI_ALPHA, /* U+00c16a: */
+ RTUNI_ALPHA, /* U+00c16b: */
+ RTUNI_ALPHA, /* U+00c16c: */
+ RTUNI_ALPHA, /* U+00c16d: */
+ RTUNI_ALPHA, /* U+00c16e: */
+ RTUNI_ALPHA, /* U+00c16f: */
+ RTUNI_ALPHA, /* U+00c170: */
+ RTUNI_ALPHA, /* U+00c171: */
+ RTUNI_ALPHA, /* U+00c172: */
+ RTUNI_ALPHA, /* U+00c173: */
+ RTUNI_ALPHA, /* U+00c174: */
+ RTUNI_ALPHA, /* U+00c175: */
+ RTUNI_ALPHA, /* U+00c176: */
+ RTUNI_ALPHA, /* U+00c177: */
+ RTUNI_ALPHA, /* U+00c178: */
+ RTUNI_ALPHA, /* U+00c179: */
+ RTUNI_ALPHA, /* U+00c17a: */
+ RTUNI_ALPHA, /* U+00c17b: */
+ RTUNI_ALPHA, /* U+00c17c: */
+ RTUNI_ALPHA, /* U+00c17d: */
+ RTUNI_ALPHA, /* U+00c17e: */
+ RTUNI_ALPHA, /* U+00c17f: */
+ RTUNI_ALPHA, /* U+00c180: */
+ RTUNI_ALPHA, /* U+00c181: */
+ RTUNI_ALPHA, /* U+00c182: */
+ RTUNI_ALPHA, /* U+00c183: */
+ RTUNI_ALPHA, /* U+00c184: */
+ RTUNI_ALPHA, /* U+00c185: */
+ RTUNI_ALPHA, /* U+00c186: */
+ RTUNI_ALPHA, /* U+00c187: */
+ RTUNI_ALPHA, /* U+00c188: */
+ RTUNI_ALPHA, /* U+00c189: */
+ RTUNI_ALPHA, /* U+00c18a: */
+ RTUNI_ALPHA, /* U+00c18b: */
+ RTUNI_ALPHA, /* U+00c18c: */
+ RTUNI_ALPHA, /* U+00c18d: */
+ RTUNI_ALPHA, /* U+00c18e: */
+ RTUNI_ALPHA, /* U+00c18f: */
+ RTUNI_ALPHA, /* U+00c190: */
+ RTUNI_ALPHA, /* U+00c191: */
+ RTUNI_ALPHA, /* U+00c192: */
+ RTUNI_ALPHA, /* U+00c193: */
+ RTUNI_ALPHA, /* U+00c194: */
+ RTUNI_ALPHA, /* U+00c195: */
+ RTUNI_ALPHA, /* U+00c196: */
+ RTUNI_ALPHA, /* U+00c197: */
+ RTUNI_ALPHA, /* U+00c198: */
+ RTUNI_ALPHA, /* U+00c199: */
+ RTUNI_ALPHA, /* U+00c19a: */
+ RTUNI_ALPHA, /* U+00c19b: */
+ RTUNI_ALPHA, /* U+00c19c: */
+ RTUNI_ALPHA, /* U+00c19d: */
+ RTUNI_ALPHA, /* U+00c19e: */
+ RTUNI_ALPHA, /* U+00c19f: */
+ RTUNI_ALPHA, /* U+00c1a0: */
+ RTUNI_ALPHA, /* U+00c1a1: */
+ RTUNI_ALPHA, /* U+00c1a2: */
+ RTUNI_ALPHA, /* U+00c1a3: */
+ RTUNI_ALPHA, /* U+00c1a4: */
+ RTUNI_ALPHA, /* U+00c1a5: */
+ RTUNI_ALPHA, /* U+00c1a6: */
+ RTUNI_ALPHA, /* U+00c1a7: */
+ RTUNI_ALPHA, /* U+00c1a8: */
+ RTUNI_ALPHA, /* U+00c1a9: */
+ RTUNI_ALPHA, /* U+00c1aa: */
+ RTUNI_ALPHA, /* U+00c1ab: */
+ RTUNI_ALPHA, /* U+00c1ac: */
+ RTUNI_ALPHA, /* U+00c1ad: */
+ RTUNI_ALPHA, /* U+00c1ae: */
+ RTUNI_ALPHA, /* U+00c1af: */
+ RTUNI_ALPHA, /* U+00c1b0: */
+ RTUNI_ALPHA, /* U+00c1b1: */
+ RTUNI_ALPHA, /* U+00c1b2: */
+ RTUNI_ALPHA, /* U+00c1b3: */
+ RTUNI_ALPHA, /* U+00c1b4: */
+ RTUNI_ALPHA, /* U+00c1b5: */
+ RTUNI_ALPHA, /* U+00c1b6: */
+ RTUNI_ALPHA, /* U+00c1b7: */
+ RTUNI_ALPHA, /* U+00c1b8: */
+ RTUNI_ALPHA, /* U+00c1b9: */
+ RTUNI_ALPHA, /* U+00c1ba: */
+ RTUNI_ALPHA, /* U+00c1bb: */
+ RTUNI_ALPHA, /* U+00c1bc: */
+ RTUNI_ALPHA, /* U+00c1bd: */
+ RTUNI_ALPHA, /* U+00c1be: */
+ RTUNI_ALPHA, /* U+00c1bf: */
+ RTUNI_ALPHA, /* U+00c1c0: */
+ RTUNI_ALPHA, /* U+00c1c1: */
+ RTUNI_ALPHA, /* U+00c1c2: */
+ RTUNI_ALPHA, /* U+00c1c3: */
+ RTUNI_ALPHA, /* U+00c1c4: */
+ RTUNI_ALPHA, /* U+00c1c5: */
+ RTUNI_ALPHA, /* U+00c1c6: */
+ RTUNI_ALPHA, /* U+00c1c7: */
+ RTUNI_ALPHA, /* U+00c1c8: */
+ RTUNI_ALPHA, /* U+00c1c9: */
+ RTUNI_ALPHA, /* U+00c1ca: */
+ RTUNI_ALPHA, /* U+00c1cb: */
+ RTUNI_ALPHA, /* U+00c1cc: */
+ RTUNI_ALPHA, /* U+00c1cd: */
+ RTUNI_ALPHA, /* U+00c1ce: */
+ RTUNI_ALPHA, /* U+00c1cf: */
+ RTUNI_ALPHA, /* U+00c1d0: */
+ RTUNI_ALPHA, /* U+00c1d1: */
+ RTUNI_ALPHA, /* U+00c1d2: */
+ RTUNI_ALPHA, /* U+00c1d3: */
+ RTUNI_ALPHA, /* U+00c1d4: */
+ RTUNI_ALPHA, /* U+00c1d5: */
+ RTUNI_ALPHA, /* U+00c1d6: */
+ RTUNI_ALPHA, /* U+00c1d7: */
+ RTUNI_ALPHA, /* U+00c1d8: */
+ RTUNI_ALPHA, /* U+00c1d9: */
+ RTUNI_ALPHA, /* U+00c1da: */
+ RTUNI_ALPHA, /* U+00c1db: */
+ RTUNI_ALPHA, /* U+00c1dc: */
+ RTUNI_ALPHA, /* U+00c1dd: */
+ RTUNI_ALPHA, /* U+00c1de: */
+ RTUNI_ALPHA, /* U+00c1df: */
+ RTUNI_ALPHA, /* U+00c1e0: */
+ RTUNI_ALPHA, /* U+00c1e1: */
+ RTUNI_ALPHA, /* U+00c1e2: */
+ RTUNI_ALPHA, /* U+00c1e3: */
+ RTUNI_ALPHA, /* U+00c1e4: */
+ RTUNI_ALPHA, /* U+00c1e5: */
+ RTUNI_ALPHA, /* U+00c1e6: */
+ RTUNI_ALPHA, /* U+00c1e7: */
+ RTUNI_ALPHA, /* U+00c1e8: */
+ RTUNI_ALPHA, /* U+00c1e9: */
+ RTUNI_ALPHA, /* U+00c1ea: */
+ RTUNI_ALPHA, /* U+00c1eb: */
+ RTUNI_ALPHA, /* U+00c1ec: */
+ RTUNI_ALPHA, /* U+00c1ed: */
+ RTUNI_ALPHA, /* U+00c1ee: */
+ RTUNI_ALPHA, /* U+00c1ef: */
+ RTUNI_ALPHA, /* U+00c1f0: */
+ RTUNI_ALPHA, /* U+00c1f1: */
+ RTUNI_ALPHA, /* U+00c1f2: */
+ RTUNI_ALPHA, /* U+00c1f3: */
+ RTUNI_ALPHA, /* U+00c1f4: */
+ RTUNI_ALPHA, /* U+00c1f5: */
+ RTUNI_ALPHA, /* U+00c1f6: */
+ RTUNI_ALPHA, /* U+00c1f7: */
+ RTUNI_ALPHA, /* U+00c1f8: */
+ RTUNI_ALPHA, /* U+00c1f9: */
+ RTUNI_ALPHA, /* U+00c1fa: */
+ RTUNI_ALPHA, /* U+00c1fb: */
+ RTUNI_ALPHA, /* U+00c1fc: */
+ RTUNI_ALPHA, /* U+00c1fd: */
+ RTUNI_ALPHA, /* U+00c1fe: */
+ RTUNI_ALPHA, /* U+00c1ff: */
+ RTUNI_ALPHA, /* U+00c200: */
+ RTUNI_ALPHA, /* U+00c201: */
+ RTUNI_ALPHA, /* U+00c202: */
+ RTUNI_ALPHA, /* U+00c203: */
+ RTUNI_ALPHA, /* U+00c204: */
+ RTUNI_ALPHA, /* U+00c205: */
+ RTUNI_ALPHA, /* U+00c206: */
+ RTUNI_ALPHA, /* U+00c207: */
+ RTUNI_ALPHA, /* U+00c208: */
+ RTUNI_ALPHA, /* U+00c209: */
+ RTUNI_ALPHA, /* U+00c20a: */
+ RTUNI_ALPHA, /* U+00c20b: */
+ RTUNI_ALPHA, /* U+00c20c: */
+ RTUNI_ALPHA, /* U+00c20d: */
+ RTUNI_ALPHA, /* U+00c20e: */
+ RTUNI_ALPHA, /* U+00c20f: */
+ RTUNI_ALPHA, /* U+00c210: */
+ RTUNI_ALPHA, /* U+00c211: */
+ RTUNI_ALPHA, /* U+00c212: */
+ RTUNI_ALPHA, /* U+00c213: */
+ RTUNI_ALPHA, /* U+00c214: */
+ RTUNI_ALPHA, /* U+00c215: */
+ RTUNI_ALPHA, /* U+00c216: */
+ RTUNI_ALPHA, /* U+00c217: */
+ RTUNI_ALPHA, /* U+00c218: */
+ RTUNI_ALPHA, /* U+00c219: */
+ RTUNI_ALPHA, /* U+00c21a: */
+ RTUNI_ALPHA, /* U+00c21b: */
+ RTUNI_ALPHA, /* U+00c21c: */
+ RTUNI_ALPHA, /* U+00c21d: */
+ RTUNI_ALPHA, /* U+00c21e: */
+ RTUNI_ALPHA, /* U+00c21f: */
+ RTUNI_ALPHA, /* U+00c220: */
+ RTUNI_ALPHA, /* U+00c221: */
+ RTUNI_ALPHA, /* U+00c222: */
+ RTUNI_ALPHA, /* U+00c223: */
+ RTUNI_ALPHA, /* U+00c224: */
+ RTUNI_ALPHA, /* U+00c225: */
+ RTUNI_ALPHA, /* U+00c226: */
+ RTUNI_ALPHA, /* U+00c227: */
+ RTUNI_ALPHA, /* U+00c228: */
+ RTUNI_ALPHA, /* U+00c229: */
+ RTUNI_ALPHA, /* U+00c22a: */
+ RTUNI_ALPHA, /* U+00c22b: */
+ RTUNI_ALPHA, /* U+00c22c: */
+ RTUNI_ALPHA, /* U+00c22d: */
+ RTUNI_ALPHA, /* U+00c22e: */
+ RTUNI_ALPHA, /* U+00c22f: */
+ RTUNI_ALPHA, /* U+00c230: */
+ RTUNI_ALPHA, /* U+00c231: */
+ RTUNI_ALPHA, /* U+00c232: */
+ RTUNI_ALPHA, /* U+00c233: */
+ RTUNI_ALPHA, /* U+00c234: */
+ RTUNI_ALPHA, /* U+00c235: */
+ RTUNI_ALPHA, /* U+00c236: */
+ RTUNI_ALPHA, /* U+00c237: */
+ RTUNI_ALPHA, /* U+00c238: */
+ RTUNI_ALPHA, /* U+00c239: */
+ RTUNI_ALPHA, /* U+00c23a: */
+ RTUNI_ALPHA, /* U+00c23b: */
+ RTUNI_ALPHA, /* U+00c23c: */
+ RTUNI_ALPHA, /* U+00c23d: */
+ RTUNI_ALPHA, /* U+00c23e: */
+ RTUNI_ALPHA, /* U+00c23f: */
+ RTUNI_ALPHA, /* U+00c240: */
+ RTUNI_ALPHA, /* U+00c241: */
+ RTUNI_ALPHA, /* U+00c242: */
+ RTUNI_ALPHA, /* U+00c243: */
+ RTUNI_ALPHA, /* U+00c244: */
+ RTUNI_ALPHA, /* U+00c245: */
+ RTUNI_ALPHA, /* U+00c246: */
+ RTUNI_ALPHA, /* U+00c247: */
+ RTUNI_ALPHA, /* U+00c248: */
+ RTUNI_ALPHA, /* U+00c249: */
+ RTUNI_ALPHA, /* U+00c24a: */
+ RTUNI_ALPHA, /* U+00c24b: */
+ RTUNI_ALPHA, /* U+00c24c: */
+ RTUNI_ALPHA, /* U+00c24d: */
+ RTUNI_ALPHA, /* U+00c24e: */
+ RTUNI_ALPHA, /* U+00c24f: */
+ RTUNI_ALPHA, /* U+00c250: */
+ RTUNI_ALPHA, /* U+00c251: */
+ RTUNI_ALPHA, /* U+00c252: */
+ RTUNI_ALPHA, /* U+00c253: */
+ RTUNI_ALPHA, /* U+00c254: */
+ RTUNI_ALPHA, /* U+00c255: */
+ RTUNI_ALPHA, /* U+00c256: */
+ RTUNI_ALPHA, /* U+00c257: */
+ RTUNI_ALPHA, /* U+00c258: */
+ RTUNI_ALPHA, /* U+00c259: */
+ RTUNI_ALPHA, /* U+00c25a: */
+ RTUNI_ALPHA, /* U+00c25b: */
+ RTUNI_ALPHA, /* U+00c25c: */
+ RTUNI_ALPHA, /* U+00c25d: */
+ RTUNI_ALPHA, /* U+00c25e: */
+ RTUNI_ALPHA, /* U+00c25f: */
+ RTUNI_ALPHA, /* U+00c260: */
+ RTUNI_ALPHA, /* U+00c261: */
+ RTUNI_ALPHA, /* U+00c262: */
+ RTUNI_ALPHA, /* U+00c263: */
+ RTUNI_ALPHA, /* U+00c264: */
+ RTUNI_ALPHA, /* U+00c265: */
+ RTUNI_ALPHA, /* U+00c266: */
+ RTUNI_ALPHA, /* U+00c267: */
+ RTUNI_ALPHA, /* U+00c268: */
+ RTUNI_ALPHA, /* U+00c269: */
+ RTUNI_ALPHA, /* U+00c26a: */
+ RTUNI_ALPHA, /* U+00c26b: */
+ RTUNI_ALPHA, /* U+00c26c: */
+ RTUNI_ALPHA, /* U+00c26d: */
+ RTUNI_ALPHA, /* U+00c26e: */
+ RTUNI_ALPHA, /* U+00c26f: */
+ RTUNI_ALPHA, /* U+00c270: */
+ RTUNI_ALPHA, /* U+00c271: */
+ RTUNI_ALPHA, /* U+00c272: */
+ RTUNI_ALPHA, /* U+00c273: */
+ RTUNI_ALPHA, /* U+00c274: */
+ RTUNI_ALPHA, /* U+00c275: */
+ RTUNI_ALPHA, /* U+00c276: */
+ RTUNI_ALPHA, /* U+00c277: */
+ RTUNI_ALPHA, /* U+00c278: */
+ RTUNI_ALPHA, /* U+00c279: */
+ RTUNI_ALPHA, /* U+00c27a: */
+ RTUNI_ALPHA, /* U+00c27b: */
+ RTUNI_ALPHA, /* U+00c27c: */
+ RTUNI_ALPHA, /* U+00c27d: */
+ RTUNI_ALPHA, /* U+00c27e: */
+ RTUNI_ALPHA, /* U+00c27f: */
+ RTUNI_ALPHA, /* U+00c280: */
+ RTUNI_ALPHA, /* U+00c281: */
+ RTUNI_ALPHA, /* U+00c282: */
+ RTUNI_ALPHA, /* U+00c283: */
+ RTUNI_ALPHA, /* U+00c284: */
+ RTUNI_ALPHA, /* U+00c285: */
+ RTUNI_ALPHA, /* U+00c286: */
+ RTUNI_ALPHA, /* U+00c287: */
+ RTUNI_ALPHA, /* U+00c288: */
+ RTUNI_ALPHA, /* U+00c289: */
+ RTUNI_ALPHA, /* U+00c28a: */
+ RTUNI_ALPHA, /* U+00c28b: */
+ RTUNI_ALPHA, /* U+00c28c: */
+ RTUNI_ALPHA, /* U+00c28d: */
+ RTUNI_ALPHA, /* U+00c28e: */
+ RTUNI_ALPHA, /* U+00c28f: */
+ RTUNI_ALPHA, /* U+00c290: */
+ RTUNI_ALPHA, /* U+00c291: */
+ RTUNI_ALPHA, /* U+00c292: */
+ RTUNI_ALPHA, /* U+00c293: */
+ RTUNI_ALPHA, /* U+00c294: */
+ RTUNI_ALPHA, /* U+00c295: */
+ RTUNI_ALPHA, /* U+00c296: */
+ RTUNI_ALPHA, /* U+00c297: */
+ RTUNI_ALPHA, /* U+00c298: */
+ RTUNI_ALPHA, /* U+00c299: */
+ RTUNI_ALPHA, /* U+00c29a: */
+ RTUNI_ALPHA, /* U+00c29b: */
+ RTUNI_ALPHA, /* U+00c29c: */
+ RTUNI_ALPHA, /* U+00c29d: */
+ RTUNI_ALPHA, /* U+00c29e: */
+ RTUNI_ALPHA, /* U+00c29f: */
+ RTUNI_ALPHA, /* U+00c2a0: */
+ RTUNI_ALPHA, /* U+00c2a1: */
+ RTUNI_ALPHA, /* U+00c2a2: */
+ RTUNI_ALPHA, /* U+00c2a3: */
+ RTUNI_ALPHA, /* U+00c2a4: */
+ RTUNI_ALPHA, /* U+00c2a5: */
+ RTUNI_ALPHA, /* U+00c2a6: */
+ RTUNI_ALPHA, /* U+00c2a7: */
+ RTUNI_ALPHA, /* U+00c2a8: */
+ RTUNI_ALPHA, /* U+00c2a9: */
+ RTUNI_ALPHA, /* U+00c2aa: */
+ RTUNI_ALPHA, /* U+00c2ab: */
+ RTUNI_ALPHA, /* U+00c2ac: */
+ RTUNI_ALPHA, /* U+00c2ad: */
+ RTUNI_ALPHA, /* U+00c2ae: */
+ RTUNI_ALPHA, /* U+00c2af: */
+ RTUNI_ALPHA, /* U+00c2b0: */
+ RTUNI_ALPHA, /* U+00c2b1: */
+ RTUNI_ALPHA, /* U+00c2b2: */
+ RTUNI_ALPHA, /* U+00c2b3: */
+ RTUNI_ALPHA, /* U+00c2b4: */
+ RTUNI_ALPHA, /* U+00c2b5: */
+ RTUNI_ALPHA, /* U+00c2b6: */
+ RTUNI_ALPHA, /* U+00c2b7: */
+ RTUNI_ALPHA, /* U+00c2b8: */
+ RTUNI_ALPHA, /* U+00c2b9: */
+ RTUNI_ALPHA, /* U+00c2ba: */
+ RTUNI_ALPHA, /* U+00c2bb: */
+ RTUNI_ALPHA, /* U+00c2bc: */
+ RTUNI_ALPHA, /* U+00c2bd: */
+ RTUNI_ALPHA, /* U+00c2be: */
+ RTUNI_ALPHA, /* U+00c2bf: */
+ RTUNI_ALPHA, /* U+00c2c0: */
+ RTUNI_ALPHA, /* U+00c2c1: */
+ RTUNI_ALPHA, /* U+00c2c2: */
+ RTUNI_ALPHA, /* U+00c2c3: */
+ RTUNI_ALPHA, /* U+00c2c4: */
+ RTUNI_ALPHA, /* U+00c2c5: */
+ RTUNI_ALPHA, /* U+00c2c6: */
+ RTUNI_ALPHA, /* U+00c2c7: */
+ RTUNI_ALPHA, /* U+00c2c8: */
+ RTUNI_ALPHA, /* U+00c2c9: */
+ RTUNI_ALPHA, /* U+00c2ca: */
+ RTUNI_ALPHA, /* U+00c2cb: */
+ RTUNI_ALPHA, /* U+00c2cc: */
+ RTUNI_ALPHA, /* U+00c2cd: */
+ RTUNI_ALPHA, /* U+00c2ce: */
+ RTUNI_ALPHA, /* U+00c2cf: */
+ RTUNI_ALPHA, /* U+00c2d0: */
+ RTUNI_ALPHA, /* U+00c2d1: */
+ RTUNI_ALPHA, /* U+00c2d2: */
+ RTUNI_ALPHA, /* U+00c2d3: */
+ RTUNI_ALPHA, /* U+00c2d4: */
+ RTUNI_ALPHA, /* U+00c2d5: */
+ RTUNI_ALPHA, /* U+00c2d6: */
+ RTUNI_ALPHA, /* U+00c2d7: */
+ RTUNI_ALPHA, /* U+00c2d8: */
+ RTUNI_ALPHA, /* U+00c2d9: */
+ RTUNI_ALPHA, /* U+00c2da: */
+ RTUNI_ALPHA, /* U+00c2db: */
+ RTUNI_ALPHA, /* U+00c2dc: */
+ RTUNI_ALPHA, /* U+00c2dd: */
+ RTUNI_ALPHA, /* U+00c2de: */
+ RTUNI_ALPHA, /* U+00c2df: */
+ RTUNI_ALPHA, /* U+00c2e0: */
+ RTUNI_ALPHA, /* U+00c2e1: */
+ RTUNI_ALPHA, /* U+00c2e2: */
+ RTUNI_ALPHA, /* U+00c2e3: */
+ RTUNI_ALPHA, /* U+00c2e4: */
+ RTUNI_ALPHA, /* U+00c2e5: */
+ RTUNI_ALPHA, /* U+00c2e6: */
+ RTUNI_ALPHA, /* U+00c2e7: */
+ RTUNI_ALPHA, /* U+00c2e8: */
+ RTUNI_ALPHA, /* U+00c2e9: */
+ RTUNI_ALPHA, /* U+00c2ea: */
+ RTUNI_ALPHA, /* U+00c2eb: */
+ RTUNI_ALPHA, /* U+00c2ec: */
+ RTUNI_ALPHA, /* U+00c2ed: */
+ RTUNI_ALPHA, /* U+00c2ee: */
+ RTUNI_ALPHA, /* U+00c2ef: */
+ RTUNI_ALPHA, /* U+00c2f0: */
+ RTUNI_ALPHA, /* U+00c2f1: */
+ RTUNI_ALPHA, /* U+00c2f2: */
+ RTUNI_ALPHA, /* U+00c2f3: */
+ RTUNI_ALPHA, /* U+00c2f4: */
+ RTUNI_ALPHA, /* U+00c2f5: */
+ RTUNI_ALPHA, /* U+00c2f6: */
+ RTUNI_ALPHA, /* U+00c2f7: */
+ RTUNI_ALPHA, /* U+00c2f8: */
+ RTUNI_ALPHA, /* U+00c2f9: */
+ RTUNI_ALPHA, /* U+00c2fa: */
+ RTUNI_ALPHA, /* U+00c2fb: */
+ RTUNI_ALPHA, /* U+00c2fc: */
+ RTUNI_ALPHA, /* U+00c2fd: */
+ RTUNI_ALPHA, /* U+00c2fe: */
+ RTUNI_ALPHA, /* U+00c2ff: */
+ RTUNI_ALPHA, /* U+00c300: */
+ RTUNI_ALPHA, /* U+00c301: */
+ RTUNI_ALPHA, /* U+00c302: */
+ RTUNI_ALPHA, /* U+00c303: */
+ RTUNI_ALPHA, /* U+00c304: */
+ RTUNI_ALPHA, /* U+00c305: */
+ RTUNI_ALPHA, /* U+00c306: */
+ RTUNI_ALPHA, /* U+00c307: */
+ RTUNI_ALPHA, /* U+00c308: */
+ RTUNI_ALPHA, /* U+00c309: */
+ RTUNI_ALPHA, /* U+00c30a: */
+ RTUNI_ALPHA, /* U+00c30b: */
+ RTUNI_ALPHA, /* U+00c30c: */
+ RTUNI_ALPHA, /* U+00c30d: */
+ RTUNI_ALPHA, /* U+00c30e: */
+ RTUNI_ALPHA, /* U+00c30f: */
+ RTUNI_ALPHA, /* U+00c310: */
+ RTUNI_ALPHA, /* U+00c311: */
+ RTUNI_ALPHA, /* U+00c312: */
+ RTUNI_ALPHA, /* U+00c313: */
+ RTUNI_ALPHA, /* U+00c314: */
+ RTUNI_ALPHA, /* U+00c315: */
+ RTUNI_ALPHA, /* U+00c316: */
+ RTUNI_ALPHA, /* U+00c317: */
+ RTUNI_ALPHA, /* U+00c318: */
+ RTUNI_ALPHA, /* U+00c319: */
+ RTUNI_ALPHA, /* U+00c31a: */
+ RTUNI_ALPHA, /* U+00c31b: */
+ RTUNI_ALPHA, /* U+00c31c: */
+ RTUNI_ALPHA, /* U+00c31d: */
+ RTUNI_ALPHA, /* U+00c31e: */
+ RTUNI_ALPHA, /* U+00c31f: */
+ RTUNI_ALPHA, /* U+00c320: */
+ RTUNI_ALPHA, /* U+00c321: */
+ RTUNI_ALPHA, /* U+00c322: */
+ RTUNI_ALPHA, /* U+00c323: */
+ RTUNI_ALPHA, /* U+00c324: */
+ RTUNI_ALPHA, /* U+00c325: */
+ RTUNI_ALPHA, /* U+00c326: */
+ RTUNI_ALPHA, /* U+00c327: */
+ RTUNI_ALPHA, /* U+00c328: */
+ RTUNI_ALPHA, /* U+00c329: */
+ RTUNI_ALPHA, /* U+00c32a: */
+ RTUNI_ALPHA, /* U+00c32b: */
+ RTUNI_ALPHA, /* U+00c32c: */
+ RTUNI_ALPHA, /* U+00c32d: */
+ RTUNI_ALPHA, /* U+00c32e: */
+ RTUNI_ALPHA, /* U+00c32f: */
+ RTUNI_ALPHA, /* U+00c330: */
+ RTUNI_ALPHA, /* U+00c331: */
+ RTUNI_ALPHA, /* U+00c332: */
+ RTUNI_ALPHA, /* U+00c333: */
+ RTUNI_ALPHA, /* U+00c334: */
+ RTUNI_ALPHA, /* U+00c335: */
+ RTUNI_ALPHA, /* U+00c336: */
+ RTUNI_ALPHA, /* U+00c337: */
+ RTUNI_ALPHA, /* U+00c338: */
+ RTUNI_ALPHA, /* U+00c339: */
+ RTUNI_ALPHA, /* U+00c33a: */
+ RTUNI_ALPHA, /* U+00c33b: */
+ RTUNI_ALPHA, /* U+00c33c: */
+ RTUNI_ALPHA, /* U+00c33d: */
+ RTUNI_ALPHA, /* U+00c33e: */
+ RTUNI_ALPHA, /* U+00c33f: */
+ RTUNI_ALPHA, /* U+00c340: */
+ RTUNI_ALPHA, /* U+00c341: */
+ RTUNI_ALPHA, /* U+00c342: */
+ RTUNI_ALPHA, /* U+00c343: */
+ RTUNI_ALPHA, /* U+00c344: */
+ RTUNI_ALPHA, /* U+00c345: */
+ RTUNI_ALPHA, /* U+00c346: */
+ RTUNI_ALPHA, /* U+00c347: */
+ RTUNI_ALPHA, /* U+00c348: */
+ RTUNI_ALPHA, /* U+00c349: */
+ RTUNI_ALPHA, /* U+00c34a: */
+ RTUNI_ALPHA, /* U+00c34b: */
+ RTUNI_ALPHA, /* U+00c34c: */
+ RTUNI_ALPHA, /* U+00c34d: */
+ RTUNI_ALPHA, /* U+00c34e: */
+ RTUNI_ALPHA, /* U+00c34f: */
+ RTUNI_ALPHA, /* U+00c350: */
+ RTUNI_ALPHA, /* U+00c351: */
+ RTUNI_ALPHA, /* U+00c352: */
+ RTUNI_ALPHA, /* U+00c353: */
+ RTUNI_ALPHA, /* U+00c354: */
+ RTUNI_ALPHA, /* U+00c355: */
+ RTUNI_ALPHA, /* U+00c356: */
+ RTUNI_ALPHA, /* U+00c357: */
+ RTUNI_ALPHA, /* U+00c358: */
+ RTUNI_ALPHA, /* U+00c359: */
+ RTUNI_ALPHA, /* U+00c35a: */
+ RTUNI_ALPHA, /* U+00c35b: */
+ RTUNI_ALPHA, /* U+00c35c: */
+ RTUNI_ALPHA, /* U+00c35d: */
+ RTUNI_ALPHA, /* U+00c35e: */
+ RTUNI_ALPHA, /* U+00c35f: */
+ RTUNI_ALPHA, /* U+00c360: */
+ RTUNI_ALPHA, /* U+00c361: */
+ RTUNI_ALPHA, /* U+00c362: */
+ RTUNI_ALPHA, /* U+00c363: */
+ RTUNI_ALPHA, /* U+00c364: */
+ RTUNI_ALPHA, /* U+00c365: */
+ RTUNI_ALPHA, /* U+00c366: */
+ RTUNI_ALPHA, /* U+00c367: */
+ RTUNI_ALPHA, /* U+00c368: */
+ RTUNI_ALPHA, /* U+00c369: */
+ RTUNI_ALPHA, /* U+00c36a: */
+ RTUNI_ALPHA, /* U+00c36b: */
+ RTUNI_ALPHA, /* U+00c36c: */
+ RTUNI_ALPHA, /* U+00c36d: */
+ RTUNI_ALPHA, /* U+00c36e: */
+ RTUNI_ALPHA, /* U+00c36f: */
+ RTUNI_ALPHA, /* U+00c370: */
+ RTUNI_ALPHA, /* U+00c371: */
+ RTUNI_ALPHA, /* U+00c372: */
+ RTUNI_ALPHA, /* U+00c373: */
+ RTUNI_ALPHA, /* U+00c374: */
+ RTUNI_ALPHA, /* U+00c375: */
+ RTUNI_ALPHA, /* U+00c376: */
+ RTUNI_ALPHA, /* U+00c377: */
+ RTUNI_ALPHA, /* U+00c378: */
+ RTUNI_ALPHA, /* U+00c379: */
+ RTUNI_ALPHA, /* U+00c37a: */
+ RTUNI_ALPHA, /* U+00c37b: */
+ RTUNI_ALPHA, /* U+00c37c: */
+ RTUNI_ALPHA, /* U+00c37d: */
+ RTUNI_ALPHA, /* U+00c37e: */
+ RTUNI_ALPHA, /* U+00c37f: */
+ RTUNI_ALPHA, /* U+00c380: */
+ RTUNI_ALPHA, /* U+00c381: */
+ RTUNI_ALPHA, /* U+00c382: */
+ RTUNI_ALPHA, /* U+00c383: */
+ RTUNI_ALPHA, /* U+00c384: */
+ RTUNI_ALPHA, /* U+00c385: */
+ RTUNI_ALPHA, /* U+00c386: */
+ RTUNI_ALPHA, /* U+00c387: */
+ RTUNI_ALPHA, /* U+00c388: */
+ RTUNI_ALPHA, /* U+00c389: */
+ RTUNI_ALPHA, /* U+00c38a: */
+ RTUNI_ALPHA, /* U+00c38b: */
+ RTUNI_ALPHA, /* U+00c38c: */
+ RTUNI_ALPHA, /* U+00c38d: */
+ RTUNI_ALPHA, /* U+00c38e: */
+ RTUNI_ALPHA, /* U+00c38f: */
+ RTUNI_ALPHA, /* U+00c390: */
+ RTUNI_ALPHA, /* U+00c391: */
+ RTUNI_ALPHA, /* U+00c392: */
+ RTUNI_ALPHA, /* U+00c393: */
+ RTUNI_ALPHA, /* U+00c394: */
+ RTUNI_ALPHA, /* U+00c395: */
+ RTUNI_ALPHA, /* U+00c396: */
+ RTUNI_ALPHA, /* U+00c397: */
+ RTUNI_ALPHA, /* U+00c398: */
+ RTUNI_ALPHA, /* U+00c399: */
+ RTUNI_ALPHA, /* U+00c39a: */
+ RTUNI_ALPHA, /* U+00c39b: */
+ RTUNI_ALPHA, /* U+00c39c: */
+ RTUNI_ALPHA, /* U+00c39d: */
+ RTUNI_ALPHA, /* U+00c39e: */
+ RTUNI_ALPHA, /* U+00c39f: */
+ RTUNI_ALPHA, /* U+00c3a0: */
+ RTUNI_ALPHA, /* U+00c3a1: */
+ RTUNI_ALPHA, /* U+00c3a2: */
+ RTUNI_ALPHA, /* U+00c3a3: */
+ RTUNI_ALPHA, /* U+00c3a4: */
+ RTUNI_ALPHA, /* U+00c3a5: */
+ RTUNI_ALPHA, /* U+00c3a6: */
+ RTUNI_ALPHA, /* U+00c3a7: */
+ RTUNI_ALPHA, /* U+00c3a8: */
+ RTUNI_ALPHA, /* U+00c3a9: */
+ RTUNI_ALPHA, /* U+00c3aa: */
+ RTUNI_ALPHA, /* U+00c3ab: */
+ RTUNI_ALPHA, /* U+00c3ac: */
+ RTUNI_ALPHA, /* U+00c3ad: */
+ RTUNI_ALPHA, /* U+00c3ae: */
+ RTUNI_ALPHA, /* U+00c3af: */
+ RTUNI_ALPHA, /* U+00c3b0: */
+ RTUNI_ALPHA, /* U+00c3b1: */
+ RTUNI_ALPHA, /* U+00c3b2: */
+ RTUNI_ALPHA, /* U+00c3b3: */
+ RTUNI_ALPHA, /* U+00c3b4: */
+ RTUNI_ALPHA, /* U+00c3b5: */
+ RTUNI_ALPHA, /* U+00c3b6: */
+ RTUNI_ALPHA, /* U+00c3b7: */
+ RTUNI_ALPHA, /* U+00c3b8: */
+ RTUNI_ALPHA, /* U+00c3b9: */
+ RTUNI_ALPHA, /* U+00c3ba: */
+ RTUNI_ALPHA, /* U+00c3bb: */
+ RTUNI_ALPHA, /* U+00c3bc: */
+ RTUNI_ALPHA, /* U+00c3bd: */
+ RTUNI_ALPHA, /* U+00c3be: */
+ RTUNI_ALPHA, /* U+00c3bf: */
+ RTUNI_ALPHA, /* U+00c3c0: */
+ RTUNI_ALPHA, /* U+00c3c1: */
+ RTUNI_ALPHA, /* U+00c3c2: */
+ RTUNI_ALPHA, /* U+00c3c3: */
+ RTUNI_ALPHA, /* U+00c3c4: */
+ RTUNI_ALPHA, /* U+00c3c5: */
+ RTUNI_ALPHA, /* U+00c3c6: */
+ RTUNI_ALPHA, /* U+00c3c7: */
+ RTUNI_ALPHA, /* U+00c3c8: */
+ RTUNI_ALPHA, /* U+00c3c9: */
+ RTUNI_ALPHA, /* U+00c3ca: */
+ RTUNI_ALPHA, /* U+00c3cb: */
+ RTUNI_ALPHA, /* U+00c3cc: */
+ RTUNI_ALPHA, /* U+00c3cd: */
+ RTUNI_ALPHA, /* U+00c3ce: */
+ RTUNI_ALPHA, /* U+00c3cf: */
+ RTUNI_ALPHA, /* U+00c3d0: */
+ RTUNI_ALPHA, /* U+00c3d1: */
+ RTUNI_ALPHA, /* U+00c3d2: */
+ RTUNI_ALPHA, /* U+00c3d3: */
+ RTUNI_ALPHA, /* U+00c3d4: */
+ RTUNI_ALPHA, /* U+00c3d5: */
+ RTUNI_ALPHA, /* U+00c3d6: */
+ RTUNI_ALPHA, /* U+00c3d7: */
+ RTUNI_ALPHA, /* U+00c3d8: */
+ RTUNI_ALPHA, /* U+00c3d9: */
+ RTUNI_ALPHA, /* U+00c3da: */
+ RTUNI_ALPHA, /* U+00c3db: */
+ RTUNI_ALPHA, /* U+00c3dc: */
+ RTUNI_ALPHA, /* U+00c3dd: */
+ RTUNI_ALPHA, /* U+00c3de: */
+ RTUNI_ALPHA, /* U+00c3df: */
+ RTUNI_ALPHA, /* U+00c3e0: */
+ RTUNI_ALPHA, /* U+00c3e1: */
+ RTUNI_ALPHA, /* U+00c3e2: */
+ RTUNI_ALPHA, /* U+00c3e3: */
+ RTUNI_ALPHA, /* U+00c3e4: */
+ RTUNI_ALPHA, /* U+00c3e5: */
+ RTUNI_ALPHA, /* U+00c3e6: */
+ RTUNI_ALPHA, /* U+00c3e7: */
+ RTUNI_ALPHA, /* U+00c3e8: */
+ RTUNI_ALPHA, /* U+00c3e9: */
+ RTUNI_ALPHA, /* U+00c3ea: */
+ RTUNI_ALPHA, /* U+00c3eb: */
+ RTUNI_ALPHA, /* U+00c3ec: */
+ RTUNI_ALPHA, /* U+00c3ed: */
+ RTUNI_ALPHA, /* U+00c3ee: */
+ RTUNI_ALPHA, /* U+00c3ef: */
+ RTUNI_ALPHA, /* U+00c3f0: */
+ RTUNI_ALPHA, /* U+00c3f1: */
+ RTUNI_ALPHA, /* U+00c3f2: */
+ RTUNI_ALPHA, /* U+00c3f3: */
+ RTUNI_ALPHA, /* U+00c3f4: */
+ RTUNI_ALPHA, /* U+00c3f5: */
+ RTUNI_ALPHA, /* U+00c3f6: */
+ RTUNI_ALPHA, /* U+00c3f7: */
+ RTUNI_ALPHA, /* U+00c3f8: */
+ RTUNI_ALPHA, /* U+00c3f9: */
+ RTUNI_ALPHA, /* U+00c3fa: */
+ RTUNI_ALPHA, /* U+00c3fb: */
+ RTUNI_ALPHA, /* U+00c3fc: */
+ RTUNI_ALPHA, /* U+00c3fd: */
+ RTUNI_ALPHA, /* U+00c3fe: */
+ RTUNI_ALPHA, /* U+00c3ff: */
+ RTUNI_ALPHA, /* U+00c400: */
+ RTUNI_ALPHA, /* U+00c401: */
+ RTUNI_ALPHA, /* U+00c402: */
+ RTUNI_ALPHA, /* U+00c403: */
+ RTUNI_ALPHA, /* U+00c404: */
+ RTUNI_ALPHA, /* U+00c405: */
+ RTUNI_ALPHA, /* U+00c406: */
+ RTUNI_ALPHA, /* U+00c407: */
+ RTUNI_ALPHA, /* U+00c408: */
+ RTUNI_ALPHA, /* U+00c409: */
+ RTUNI_ALPHA, /* U+00c40a: */
+ RTUNI_ALPHA, /* U+00c40b: */
+ RTUNI_ALPHA, /* U+00c40c: */
+ RTUNI_ALPHA, /* U+00c40d: */
+ RTUNI_ALPHA, /* U+00c40e: */
+ RTUNI_ALPHA, /* U+00c40f: */
+ RTUNI_ALPHA, /* U+00c410: */
+ RTUNI_ALPHA, /* U+00c411: */
+ RTUNI_ALPHA, /* U+00c412: */
+ RTUNI_ALPHA, /* U+00c413: */
+ RTUNI_ALPHA, /* U+00c414: */
+ RTUNI_ALPHA, /* U+00c415: */
+ RTUNI_ALPHA, /* U+00c416: */
+ RTUNI_ALPHA, /* U+00c417: */
+ RTUNI_ALPHA, /* U+00c418: */
+ RTUNI_ALPHA, /* U+00c419: */
+ RTUNI_ALPHA, /* U+00c41a: */
+ RTUNI_ALPHA, /* U+00c41b: */
+ RTUNI_ALPHA, /* U+00c41c: */
+ RTUNI_ALPHA, /* U+00c41d: */
+ RTUNI_ALPHA, /* U+00c41e: */
+ RTUNI_ALPHA, /* U+00c41f: */
+ RTUNI_ALPHA, /* U+00c420: */
+ RTUNI_ALPHA, /* U+00c421: */
+ RTUNI_ALPHA, /* U+00c422: */
+ RTUNI_ALPHA, /* U+00c423: */
+ RTUNI_ALPHA, /* U+00c424: */
+ RTUNI_ALPHA, /* U+00c425: */
+ RTUNI_ALPHA, /* U+00c426: */
+ RTUNI_ALPHA, /* U+00c427: */
+ RTUNI_ALPHA, /* U+00c428: */
+ RTUNI_ALPHA, /* U+00c429: */
+ RTUNI_ALPHA, /* U+00c42a: */
+ RTUNI_ALPHA, /* U+00c42b: */
+ RTUNI_ALPHA, /* U+00c42c: */
+ RTUNI_ALPHA, /* U+00c42d: */
+ RTUNI_ALPHA, /* U+00c42e: */
+ RTUNI_ALPHA, /* U+00c42f: */
+ RTUNI_ALPHA, /* U+00c430: */
+ RTUNI_ALPHA, /* U+00c431: */
+ RTUNI_ALPHA, /* U+00c432: */
+ RTUNI_ALPHA, /* U+00c433: */
+ RTUNI_ALPHA, /* U+00c434: */
+ RTUNI_ALPHA, /* U+00c435: */
+ RTUNI_ALPHA, /* U+00c436: */
+ RTUNI_ALPHA, /* U+00c437: */
+ RTUNI_ALPHA, /* U+00c438: */
+ RTUNI_ALPHA, /* U+00c439: */
+ RTUNI_ALPHA, /* U+00c43a: */
+ RTUNI_ALPHA, /* U+00c43b: */
+ RTUNI_ALPHA, /* U+00c43c: */
+ RTUNI_ALPHA, /* U+00c43d: */
+ RTUNI_ALPHA, /* U+00c43e: */
+ RTUNI_ALPHA, /* U+00c43f: */
+ RTUNI_ALPHA, /* U+00c440: */
+ RTUNI_ALPHA, /* U+00c441: */
+ RTUNI_ALPHA, /* U+00c442: */
+ RTUNI_ALPHA, /* U+00c443: */
+ RTUNI_ALPHA, /* U+00c444: */
+ RTUNI_ALPHA, /* U+00c445: */
+ RTUNI_ALPHA, /* U+00c446: */
+ RTUNI_ALPHA, /* U+00c447: */
+ RTUNI_ALPHA, /* U+00c448: */
+ RTUNI_ALPHA, /* U+00c449: */
+ RTUNI_ALPHA, /* U+00c44a: */
+ RTUNI_ALPHA, /* U+00c44b: */
+ RTUNI_ALPHA, /* U+00c44c: */
+ RTUNI_ALPHA, /* U+00c44d: */
+ RTUNI_ALPHA, /* U+00c44e: */
+ RTUNI_ALPHA, /* U+00c44f: */
+ RTUNI_ALPHA, /* U+00c450: */
+ RTUNI_ALPHA, /* U+00c451: */
+ RTUNI_ALPHA, /* U+00c452: */
+ RTUNI_ALPHA, /* U+00c453: */
+ RTUNI_ALPHA, /* U+00c454: */
+ RTUNI_ALPHA, /* U+00c455: */
+ RTUNI_ALPHA, /* U+00c456: */
+ RTUNI_ALPHA, /* U+00c457: */
+ RTUNI_ALPHA, /* U+00c458: */
+ RTUNI_ALPHA, /* U+00c459: */
+ RTUNI_ALPHA, /* U+00c45a: */
+ RTUNI_ALPHA, /* U+00c45b: */
+ RTUNI_ALPHA, /* U+00c45c: */
+ RTUNI_ALPHA, /* U+00c45d: */
+ RTUNI_ALPHA, /* U+00c45e: */
+ RTUNI_ALPHA, /* U+00c45f: */
+ RTUNI_ALPHA, /* U+00c460: */
+ RTUNI_ALPHA, /* U+00c461: */
+ RTUNI_ALPHA, /* U+00c462: */
+ RTUNI_ALPHA, /* U+00c463: */
+ RTUNI_ALPHA, /* U+00c464: */
+ RTUNI_ALPHA, /* U+00c465: */
+ RTUNI_ALPHA, /* U+00c466: */
+ RTUNI_ALPHA, /* U+00c467: */
+ RTUNI_ALPHA, /* U+00c468: */
+ RTUNI_ALPHA, /* U+00c469: */
+ RTUNI_ALPHA, /* U+00c46a: */
+ RTUNI_ALPHA, /* U+00c46b: */
+ RTUNI_ALPHA, /* U+00c46c: */
+ RTUNI_ALPHA, /* U+00c46d: */
+ RTUNI_ALPHA, /* U+00c46e: */
+ RTUNI_ALPHA, /* U+00c46f: */
+ RTUNI_ALPHA, /* U+00c470: */
+ RTUNI_ALPHA, /* U+00c471: */
+ RTUNI_ALPHA, /* U+00c472: */
+ RTUNI_ALPHA, /* U+00c473: */
+ RTUNI_ALPHA, /* U+00c474: */
+ RTUNI_ALPHA, /* U+00c475: */
+ RTUNI_ALPHA, /* U+00c476: */
+ RTUNI_ALPHA, /* U+00c477: */
+ RTUNI_ALPHA, /* U+00c478: */
+ RTUNI_ALPHA, /* U+00c479: */
+ RTUNI_ALPHA, /* U+00c47a: */
+ RTUNI_ALPHA, /* U+00c47b: */
+ RTUNI_ALPHA, /* U+00c47c: */
+ RTUNI_ALPHA, /* U+00c47d: */
+ RTUNI_ALPHA, /* U+00c47e: */
+ RTUNI_ALPHA, /* U+00c47f: */
+ RTUNI_ALPHA, /* U+00c480: */
+ RTUNI_ALPHA, /* U+00c481: */
+ RTUNI_ALPHA, /* U+00c482: */
+ RTUNI_ALPHA, /* U+00c483: */
+ RTUNI_ALPHA, /* U+00c484: */
+ RTUNI_ALPHA, /* U+00c485: */
+ RTUNI_ALPHA, /* U+00c486: */
+ RTUNI_ALPHA, /* U+00c487: */
+ RTUNI_ALPHA, /* U+00c488: */
+ RTUNI_ALPHA, /* U+00c489: */
+ RTUNI_ALPHA, /* U+00c48a: */
+ RTUNI_ALPHA, /* U+00c48b: */
+ RTUNI_ALPHA, /* U+00c48c: */
+ RTUNI_ALPHA, /* U+00c48d: */
+ RTUNI_ALPHA, /* U+00c48e: */
+ RTUNI_ALPHA, /* U+00c48f: */
+ RTUNI_ALPHA, /* U+00c490: */
+ RTUNI_ALPHA, /* U+00c491: */
+ RTUNI_ALPHA, /* U+00c492: */
+ RTUNI_ALPHA, /* U+00c493: */
+ RTUNI_ALPHA, /* U+00c494: */
+ RTUNI_ALPHA, /* U+00c495: */
+ RTUNI_ALPHA, /* U+00c496: */
+ RTUNI_ALPHA, /* U+00c497: */
+ RTUNI_ALPHA, /* U+00c498: */
+ RTUNI_ALPHA, /* U+00c499: */
+ RTUNI_ALPHA, /* U+00c49a: */
+ RTUNI_ALPHA, /* U+00c49b: */
+ RTUNI_ALPHA, /* U+00c49c: */
+ RTUNI_ALPHA, /* U+00c49d: */
+ RTUNI_ALPHA, /* U+00c49e: */
+ RTUNI_ALPHA, /* U+00c49f: */
+ RTUNI_ALPHA, /* U+00c4a0: */
+ RTUNI_ALPHA, /* U+00c4a1: */
+ RTUNI_ALPHA, /* U+00c4a2: */
+ RTUNI_ALPHA, /* U+00c4a3: */
+ RTUNI_ALPHA, /* U+00c4a4: */
+ RTUNI_ALPHA, /* U+00c4a5: */
+ RTUNI_ALPHA, /* U+00c4a6: */
+ RTUNI_ALPHA, /* U+00c4a7: */
+ RTUNI_ALPHA, /* U+00c4a8: */
+ RTUNI_ALPHA, /* U+00c4a9: */
+ RTUNI_ALPHA, /* U+00c4aa: */
+ RTUNI_ALPHA, /* U+00c4ab: */
+ RTUNI_ALPHA, /* U+00c4ac: */
+ RTUNI_ALPHA, /* U+00c4ad: */
+ RTUNI_ALPHA, /* U+00c4ae: */
+ RTUNI_ALPHA, /* U+00c4af: */
+ RTUNI_ALPHA, /* U+00c4b0: */
+ RTUNI_ALPHA, /* U+00c4b1: */
+ RTUNI_ALPHA, /* U+00c4b2: */
+ RTUNI_ALPHA, /* U+00c4b3: */
+ RTUNI_ALPHA, /* U+00c4b4: */
+ RTUNI_ALPHA, /* U+00c4b5: */
+ RTUNI_ALPHA, /* U+00c4b6: */
+ RTUNI_ALPHA, /* U+00c4b7: */
+ RTUNI_ALPHA, /* U+00c4b8: */
+ RTUNI_ALPHA, /* U+00c4b9: */
+ RTUNI_ALPHA, /* U+00c4ba: */
+ RTUNI_ALPHA, /* U+00c4bb: */
+ RTUNI_ALPHA, /* U+00c4bc: */
+ RTUNI_ALPHA, /* U+00c4bd: */
+ RTUNI_ALPHA, /* U+00c4be: */
+ RTUNI_ALPHA, /* U+00c4bf: */
+ RTUNI_ALPHA, /* U+00c4c0: */
+ RTUNI_ALPHA, /* U+00c4c1: */
+ RTUNI_ALPHA, /* U+00c4c2: */
+ RTUNI_ALPHA, /* U+00c4c3: */
+ RTUNI_ALPHA, /* U+00c4c4: */
+ RTUNI_ALPHA, /* U+00c4c5: */
+ RTUNI_ALPHA, /* U+00c4c6: */
+ RTUNI_ALPHA, /* U+00c4c7: */
+ RTUNI_ALPHA, /* U+00c4c8: */
+ RTUNI_ALPHA, /* U+00c4c9: */
+ RTUNI_ALPHA, /* U+00c4ca: */
+ RTUNI_ALPHA, /* U+00c4cb: */
+ RTUNI_ALPHA, /* U+00c4cc: */
+ RTUNI_ALPHA, /* U+00c4cd: */
+ RTUNI_ALPHA, /* U+00c4ce: */
+ RTUNI_ALPHA, /* U+00c4cf: */
+ RTUNI_ALPHA, /* U+00c4d0: */
+ RTUNI_ALPHA, /* U+00c4d1: */
+ RTUNI_ALPHA, /* U+00c4d2: */
+ RTUNI_ALPHA, /* U+00c4d3: */
+ RTUNI_ALPHA, /* U+00c4d4: */
+ RTUNI_ALPHA, /* U+00c4d5: */
+ RTUNI_ALPHA, /* U+00c4d6: */
+ RTUNI_ALPHA, /* U+00c4d7: */
+ RTUNI_ALPHA, /* U+00c4d8: */
+ RTUNI_ALPHA, /* U+00c4d9: */
+ RTUNI_ALPHA, /* U+00c4da: */
+ RTUNI_ALPHA, /* U+00c4db: */
+ RTUNI_ALPHA, /* U+00c4dc: */
+ RTUNI_ALPHA, /* U+00c4dd: */
+ RTUNI_ALPHA, /* U+00c4de: */
+ RTUNI_ALPHA, /* U+00c4df: */
+ RTUNI_ALPHA, /* U+00c4e0: */
+ RTUNI_ALPHA, /* U+00c4e1: */
+ RTUNI_ALPHA, /* U+00c4e2: */
+ RTUNI_ALPHA, /* U+00c4e3: */
+ RTUNI_ALPHA, /* U+00c4e4: */
+ RTUNI_ALPHA, /* U+00c4e5: */
+ RTUNI_ALPHA, /* U+00c4e6: */
+ RTUNI_ALPHA, /* U+00c4e7: */
+ RTUNI_ALPHA, /* U+00c4e8: */
+ RTUNI_ALPHA, /* U+00c4e9: */
+ RTUNI_ALPHA, /* U+00c4ea: */
+ RTUNI_ALPHA, /* U+00c4eb: */
+ RTUNI_ALPHA, /* U+00c4ec: */
+ RTUNI_ALPHA, /* U+00c4ed: */
+ RTUNI_ALPHA, /* U+00c4ee: */
+ RTUNI_ALPHA, /* U+00c4ef: */
+ RTUNI_ALPHA, /* U+00c4f0: */
+ RTUNI_ALPHA, /* U+00c4f1: */
+ RTUNI_ALPHA, /* U+00c4f2: */
+ RTUNI_ALPHA, /* U+00c4f3: */
+ RTUNI_ALPHA, /* U+00c4f4: */
+ RTUNI_ALPHA, /* U+00c4f5: */
+ RTUNI_ALPHA, /* U+00c4f6: */
+ RTUNI_ALPHA, /* U+00c4f7: */
+ RTUNI_ALPHA, /* U+00c4f8: */
+ RTUNI_ALPHA, /* U+00c4f9: */
+ RTUNI_ALPHA, /* U+00c4fa: */
+ RTUNI_ALPHA, /* U+00c4fb: */
+ RTUNI_ALPHA, /* U+00c4fc: */
+ RTUNI_ALPHA, /* U+00c4fd: */
+ RTUNI_ALPHA, /* U+00c4fe: */
+ RTUNI_ALPHA, /* U+00c4ff: */
+ RTUNI_ALPHA, /* U+00c500: */
+ RTUNI_ALPHA, /* U+00c501: */
+ RTUNI_ALPHA, /* U+00c502: */
+ RTUNI_ALPHA, /* U+00c503: */
+ RTUNI_ALPHA, /* U+00c504: */
+ RTUNI_ALPHA, /* U+00c505: */
+ RTUNI_ALPHA, /* U+00c506: */
+ RTUNI_ALPHA, /* U+00c507: */
+ RTUNI_ALPHA, /* U+00c508: */
+ RTUNI_ALPHA, /* U+00c509: */
+ RTUNI_ALPHA, /* U+00c50a: */
+ RTUNI_ALPHA, /* U+00c50b: */
+ RTUNI_ALPHA, /* U+00c50c: */
+ RTUNI_ALPHA, /* U+00c50d: */
+ RTUNI_ALPHA, /* U+00c50e: */
+ RTUNI_ALPHA, /* U+00c50f: */
+ RTUNI_ALPHA, /* U+00c510: */
+ RTUNI_ALPHA, /* U+00c511: */
+ RTUNI_ALPHA, /* U+00c512: */
+ RTUNI_ALPHA, /* U+00c513: */
+ RTUNI_ALPHA, /* U+00c514: */
+ RTUNI_ALPHA, /* U+00c515: */
+ RTUNI_ALPHA, /* U+00c516: */
+ RTUNI_ALPHA, /* U+00c517: */
+ RTUNI_ALPHA, /* U+00c518: */
+ RTUNI_ALPHA, /* U+00c519: */
+ RTUNI_ALPHA, /* U+00c51a: */
+ RTUNI_ALPHA, /* U+00c51b: */
+ RTUNI_ALPHA, /* U+00c51c: */
+ RTUNI_ALPHA, /* U+00c51d: */
+ RTUNI_ALPHA, /* U+00c51e: */
+ RTUNI_ALPHA, /* U+00c51f: */
+ RTUNI_ALPHA, /* U+00c520: */
+ RTUNI_ALPHA, /* U+00c521: */
+ RTUNI_ALPHA, /* U+00c522: */
+ RTUNI_ALPHA, /* U+00c523: */
+ RTUNI_ALPHA, /* U+00c524: */
+ RTUNI_ALPHA, /* U+00c525: */
+ RTUNI_ALPHA, /* U+00c526: */
+ RTUNI_ALPHA, /* U+00c527: */
+ RTUNI_ALPHA, /* U+00c528: */
+ RTUNI_ALPHA, /* U+00c529: */
+ RTUNI_ALPHA, /* U+00c52a: */
+ RTUNI_ALPHA, /* U+00c52b: */
+ RTUNI_ALPHA, /* U+00c52c: */
+ RTUNI_ALPHA, /* U+00c52d: */
+ RTUNI_ALPHA, /* U+00c52e: */
+ RTUNI_ALPHA, /* U+00c52f: */
+ RTUNI_ALPHA, /* U+00c530: */
+ RTUNI_ALPHA, /* U+00c531: */
+ RTUNI_ALPHA, /* U+00c532: */
+ RTUNI_ALPHA, /* U+00c533: */
+ RTUNI_ALPHA, /* U+00c534: */
+ RTUNI_ALPHA, /* U+00c535: */
+ RTUNI_ALPHA, /* U+00c536: */
+ RTUNI_ALPHA, /* U+00c537: */
+ RTUNI_ALPHA, /* U+00c538: */
+ RTUNI_ALPHA, /* U+00c539: */
+ RTUNI_ALPHA, /* U+00c53a: */
+ RTUNI_ALPHA, /* U+00c53b: */
+ RTUNI_ALPHA, /* U+00c53c: */
+ RTUNI_ALPHA, /* U+00c53d: */
+ RTUNI_ALPHA, /* U+00c53e: */
+ RTUNI_ALPHA, /* U+00c53f: */
+ RTUNI_ALPHA, /* U+00c540: */
+ RTUNI_ALPHA, /* U+00c541: */
+ RTUNI_ALPHA, /* U+00c542: */
+ RTUNI_ALPHA, /* U+00c543: */
+ RTUNI_ALPHA, /* U+00c544: */
+ RTUNI_ALPHA, /* U+00c545: */
+ RTUNI_ALPHA, /* U+00c546: */
+ RTUNI_ALPHA, /* U+00c547: */
+ RTUNI_ALPHA, /* U+00c548: */
+ RTUNI_ALPHA, /* U+00c549: */
+ RTUNI_ALPHA, /* U+00c54a: */
+ RTUNI_ALPHA, /* U+00c54b: */
+ RTUNI_ALPHA, /* U+00c54c: */
+ RTUNI_ALPHA, /* U+00c54d: */
+ RTUNI_ALPHA, /* U+00c54e: */
+ RTUNI_ALPHA, /* U+00c54f: */
+ RTUNI_ALPHA, /* U+00c550: */
+ RTUNI_ALPHA, /* U+00c551: */
+ RTUNI_ALPHA, /* U+00c552: */
+ RTUNI_ALPHA, /* U+00c553: */
+ RTUNI_ALPHA, /* U+00c554: */
+ RTUNI_ALPHA, /* U+00c555: */
+ RTUNI_ALPHA, /* U+00c556: */
+ RTUNI_ALPHA, /* U+00c557: */
+ RTUNI_ALPHA, /* U+00c558: */
+ RTUNI_ALPHA, /* U+00c559: */
+ RTUNI_ALPHA, /* U+00c55a: */
+ RTUNI_ALPHA, /* U+00c55b: */
+ RTUNI_ALPHA, /* U+00c55c: */
+ RTUNI_ALPHA, /* U+00c55d: */
+ RTUNI_ALPHA, /* U+00c55e: */
+ RTUNI_ALPHA, /* U+00c55f: */
+ RTUNI_ALPHA, /* U+00c560: */
+ RTUNI_ALPHA, /* U+00c561: */
+ RTUNI_ALPHA, /* U+00c562: */
+ RTUNI_ALPHA, /* U+00c563: */
+ RTUNI_ALPHA, /* U+00c564: */
+ RTUNI_ALPHA, /* U+00c565: */
+ RTUNI_ALPHA, /* U+00c566: */
+ RTUNI_ALPHA, /* U+00c567: */
+ RTUNI_ALPHA, /* U+00c568: */
+ RTUNI_ALPHA, /* U+00c569: */
+ RTUNI_ALPHA, /* U+00c56a: */
+ RTUNI_ALPHA, /* U+00c56b: */
+ RTUNI_ALPHA, /* U+00c56c: */
+ RTUNI_ALPHA, /* U+00c56d: */
+ RTUNI_ALPHA, /* U+00c56e: */
+ RTUNI_ALPHA, /* U+00c56f: */
+ RTUNI_ALPHA, /* U+00c570: */
+ RTUNI_ALPHA, /* U+00c571: */
+ RTUNI_ALPHA, /* U+00c572: */
+ RTUNI_ALPHA, /* U+00c573: */
+ RTUNI_ALPHA, /* U+00c574: */
+ RTUNI_ALPHA, /* U+00c575: */
+ RTUNI_ALPHA, /* U+00c576: */
+ RTUNI_ALPHA, /* U+00c577: */
+ RTUNI_ALPHA, /* U+00c578: */
+ RTUNI_ALPHA, /* U+00c579: */
+ RTUNI_ALPHA, /* U+00c57a: */
+ RTUNI_ALPHA, /* U+00c57b: */
+ RTUNI_ALPHA, /* U+00c57c: */
+ RTUNI_ALPHA, /* U+00c57d: */
+ RTUNI_ALPHA, /* U+00c57e: */
+ RTUNI_ALPHA, /* U+00c57f: */
+ RTUNI_ALPHA, /* U+00c580: */
+ RTUNI_ALPHA, /* U+00c581: */
+ RTUNI_ALPHA, /* U+00c582: */
+ RTUNI_ALPHA, /* U+00c583: */
+ RTUNI_ALPHA, /* U+00c584: */
+ RTUNI_ALPHA, /* U+00c585: */
+ RTUNI_ALPHA, /* U+00c586: */
+ RTUNI_ALPHA, /* U+00c587: */
+ RTUNI_ALPHA, /* U+00c588: */
+ RTUNI_ALPHA, /* U+00c589: */
+ RTUNI_ALPHA, /* U+00c58a: */
+ RTUNI_ALPHA, /* U+00c58b: */
+ RTUNI_ALPHA, /* U+00c58c: */
+ RTUNI_ALPHA, /* U+00c58d: */
+ RTUNI_ALPHA, /* U+00c58e: */
+ RTUNI_ALPHA, /* U+00c58f: */
+ RTUNI_ALPHA, /* U+00c590: */
+ RTUNI_ALPHA, /* U+00c591: */
+ RTUNI_ALPHA, /* U+00c592: */
+ RTUNI_ALPHA, /* U+00c593: */
+ RTUNI_ALPHA, /* U+00c594: */
+ RTUNI_ALPHA, /* U+00c595: */
+ RTUNI_ALPHA, /* U+00c596: */
+ RTUNI_ALPHA, /* U+00c597: */
+ RTUNI_ALPHA, /* U+00c598: */
+ RTUNI_ALPHA, /* U+00c599: */
+ RTUNI_ALPHA, /* U+00c59a: */
+ RTUNI_ALPHA, /* U+00c59b: */
+ RTUNI_ALPHA, /* U+00c59c: */
+ RTUNI_ALPHA, /* U+00c59d: */
+ RTUNI_ALPHA, /* U+00c59e: */
+ RTUNI_ALPHA, /* U+00c59f: */
+ RTUNI_ALPHA, /* U+00c5a0: */
+ RTUNI_ALPHA, /* U+00c5a1: */
+ RTUNI_ALPHA, /* U+00c5a2: */
+ RTUNI_ALPHA, /* U+00c5a3: */
+ RTUNI_ALPHA, /* U+00c5a4: */
+ RTUNI_ALPHA, /* U+00c5a5: */
+ RTUNI_ALPHA, /* U+00c5a6: */
+ RTUNI_ALPHA, /* U+00c5a7: */
+ RTUNI_ALPHA, /* U+00c5a8: */
+ RTUNI_ALPHA, /* U+00c5a9: */
+ RTUNI_ALPHA, /* U+00c5aa: */
+ RTUNI_ALPHA, /* U+00c5ab: */
+ RTUNI_ALPHA, /* U+00c5ac: */
+ RTUNI_ALPHA, /* U+00c5ad: */
+ RTUNI_ALPHA, /* U+00c5ae: */
+ RTUNI_ALPHA, /* U+00c5af: */
+ RTUNI_ALPHA, /* U+00c5b0: */
+ RTUNI_ALPHA, /* U+00c5b1: */
+ RTUNI_ALPHA, /* U+00c5b2: */
+ RTUNI_ALPHA, /* U+00c5b3: */
+ RTUNI_ALPHA, /* U+00c5b4: */
+ RTUNI_ALPHA, /* U+00c5b5: */
+ RTUNI_ALPHA, /* U+00c5b6: */
+ RTUNI_ALPHA, /* U+00c5b7: */
+ RTUNI_ALPHA, /* U+00c5b8: */
+ RTUNI_ALPHA, /* U+00c5b9: */
+ RTUNI_ALPHA, /* U+00c5ba: */
+ RTUNI_ALPHA, /* U+00c5bb: */
+ RTUNI_ALPHA, /* U+00c5bc: */
+ RTUNI_ALPHA, /* U+00c5bd: */
+ RTUNI_ALPHA, /* U+00c5be: */
+ RTUNI_ALPHA, /* U+00c5bf: */
+ RTUNI_ALPHA, /* U+00c5c0: */
+ RTUNI_ALPHA, /* U+00c5c1: */
+ RTUNI_ALPHA, /* U+00c5c2: */
+ RTUNI_ALPHA, /* U+00c5c3: */
+ RTUNI_ALPHA, /* U+00c5c4: */
+ RTUNI_ALPHA, /* U+00c5c5: */
+ RTUNI_ALPHA, /* U+00c5c6: */
+ RTUNI_ALPHA, /* U+00c5c7: */
+ RTUNI_ALPHA, /* U+00c5c8: */
+ RTUNI_ALPHA, /* U+00c5c9: */
+ RTUNI_ALPHA, /* U+00c5ca: */
+ RTUNI_ALPHA, /* U+00c5cb: */
+ RTUNI_ALPHA, /* U+00c5cc: */
+ RTUNI_ALPHA, /* U+00c5cd: */
+ RTUNI_ALPHA, /* U+00c5ce: */
+ RTUNI_ALPHA, /* U+00c5cf: */
+ RTUNI_ALPHA, /* U+00c5d0: */
+ RTUNI_ALPHA, /* U+00c5d1: */
+ RTUNI_ALPHA, /* U+00c5d2: */
+ RTUNI_ALPHA, /* U+00c5d3: */
+ RTUNI_ALPHA, /* U+00c5d4: */
+ RTUNI_ALPHA, /* U+00c5d5: */
+ RTUNI_ALPHA, /* U+00c5d6: */
+ RTUNI_ALPHA, /* U+00c5d7: */
+ RTUNI_ALPHA, /* U+00c5d8: */
+ RTUNI_ALPHA, /* U+00c5d9: */
+ RTUNI_ALPHA, /* U+00c5da: */
+ RTUNI_ALPHA, /* U+00c5db: */
+ RTUNI_ALPHA, /* U+00c5dc: */
+ RTUNI_ALPHA, /* U+00c5dd: */
+ RTUNI_ALPHA, /* U+00c5de: */
+ RTUNI_ALPHA, /* U+00c5df: */
+ RTUNI_ALPHA, /* U+00c5e0: */
+ RTUNI_ALPHA, /* U+00c5e1: */
+ RTUNI_ALPHA, /* U+00c5e2: */
+ RTUNI_ALPHA, /* U+00c5e3: */
+ RTUNI_ALPHA, /* U+00c5e4: */
+ RTUNI_ALPHA, /* U+00c5e5: */
+ RTUNI_ALPHA, /* U+00c5e6: */
+ RTUNI_ALPHA, /* U+00c5e7: */
+ RTUNI_ALPHA, /* U+00c5e8: */
+ RTUNI_ALPHA, /* U+00c5e9: */
+ RTUNI_ALPHA, /* U+00c5ea: */
+ RTUNI_ALPHA, /* U+00c5eb: */
+ RTUNI_ALPHA, /* U+00c5ec: */
+ RTUNI_ALPHA, /* U+00c5ed: */
+ RTUNI_ALPHA, /* U+00c5ee: */
+ RTUNI_ALPHA, /* U+00c5ef: */
+ RTUNI_ALPHA, /* U+00c5f0: */
+ RTUNI_ALPHA, /* U+00c5f1: */
+ RTUNI_ALPHA, /* U+00c5f2: */
+ RTUNI_ALPHA, /* U+00c5f3: */
+ RTUNI_ALPHA, /* U+00c5f4: */
+ RTUNI_ALPHA, /* U+00c5f5: */
+ RTUNI_ALPHA, /* U+00c5f6: */
+ RTUNI_ALPHA, /* U+00c5f7: */
+ RTUNI_ALPHA, /* U+00c5f8: */
+ RTUNI_ALPHA, /* U+00c5f9: */
+ RTUNI_ALPHA, /* U+00c5fa: */
+ RTUNI_ALPHA, /* U+00c5fb: */
+ RTUNI_ALPHA, /* U+00c5fc: */
+ RTUNI_ALPHA, /* U+00c5fd: */
+ RTUNI_ALPHA, /* U+00c5fe: */
+ RTUNI_ALPHA, /* U+00c5ff: */
+ RTUNI_ALPHA, /* U+00c600: */
+ RTUNI_ALPHA, /* U+00c601: */
+ RTUNI_ALPHA, /* U+00c602: */
+ RTUNI_ALPHA, /* U+00c603: */
+ RTUNI_ALPHA, /* U+00c604: */
+ RTUNI_ALPHA, /* U+00c605: */
+ RTUNI_ALPHA, /* U+00c606: */
+ RTUNI_ALPHA, /* U+00c607: */
+ RTUNI_ALPHA, /* U+00c608: */
+ RTUNI_ALPHA, /* U+00c609: */
+ RTUNI_ALPHA, /* U+00c60a: */
+ RTUNI_ALPHA, /* U+00c60b: */
+ RTUNI_ALPHA, /* U+00c60c: */
+ RTUNI_ALPHA, /* U+00c60d: */
+ RTUNI_ALPHA, /* U+00c60e: */
+ RTUNI_ALPHA, /* U+00c60f: */
+ RTUNI_ALPHA, /* U+00c610: */
+ RTUNI_ALPHA, /* U+00c611: */
+ RTUNI_ALPHA, /* U+00c612: */
+ RTUNI_ALPHA, /* U+00c613: */
+ RTUNI_ALPHA, /* U+00c614: */
+ RTUNI_ALPHA, /* U+00c615: */
+ RTUNI_ALPHA, /* U+00c616: */
+ RTUNI_ALPHA, /* U+00c617: */
+ RTUNI_ALPHA, /* U+00c618: */
+ RTUNI_ALPHA, /* U+00c619: */
+ RTUNI_ALPHA, /* U+00c61a: */
+ RTUNI_ALPHA, /* U+00c61b: */
+ RTUNI_ALPHA, /* U+00c61c: */
+ RTUNI_ALPHA, /* U+00c61d: */
+ RTUNI_ALPHA, /* U+00c61e: */
+ RTUNI_ALPHA, /* U+00c61f: */
+ RTUNI_ALPHA, /* U+00c620: */
+ RTUNI_ALPHA, /* U+00c621: */
+ RTUNI_ALPHA, /* U+00c622: */
+ RTUNI_ALPHA, /* U+00c623: */
+ RTUNI_ALPHA, /* U+00c624: */
+ RTUNI_ALPHA, /* U+00c625: */
+ RTUNI_ALPHA, /* U+00c626: */
+ RTUNI_ALPHA, /* U+00c627: */
+ RTUNI_ALPHA, /* U+00c628: */
+ RTUNI_ALPHA, /* U+00c629: */
+ RTUNI_ALPHA, /* U+00c62a: */
+ RTUNI_ALPHA, /* U+00c62b: */
+ RTUNI_ALPHA, /* U+00c62c: */
+ RTUNI_ALPHA, /* U+00c62d: */
+ RTUNI_ALPHA, /* U+00c62e: */
+ RTUNI_ALPHA, /* U+00c62f: */
+ RTUNI_ALPHA, /* U+00c630: */
+ RTUNI_ALPHA, /* U+00c631: */
+ RTUNI_ALPHA, /* U+00c632: */
+ RTUNI_ALPHA, /* U+00c633: */
+ RTUNI_ALPHA, /* U+00c634: */
+ RTUNI_ALPHA, /* U+00c635: */
+ RTUNI_ALPHA, /* U+00c636: */
+ RTUNI_ALPHA, /* U+00c637: */
+ RTUNI_ALPHA, /* U+00c638: */
+ RTUNI_ALPHA, /* U+00c639: */
+ RTUNI_ALPHA, /* U+00c63a: */
+ RTUNI_ALPHA, /* U+00c63b: */
+ RTUNI_ALPHA, /* U+00c63c: */
+ RTUNI_ALPHA, /* U+00c63d: */
+ RTUNI_ALPHA, /* U+00c63e: */
+ RTUNI_ALPHA, /* U+00c63f: */
+ RTUNI_ALPHA, /* U+00c640: */
+ RTUNI_ALPHA, /* U+00c641: */
+ RTUNI_ALPHA, /* U+00c642: */
+ RTUNI_ALPHA, /* U+00c643: */
+ RTUNI_ALPHA, /* U+00c644: */
+ RTUNI_ALPHA, /* U+00c645: */
+ RTUNI_ALPHA, /* U+00c646: */
+ RTUNI_ALPHA, /* U+00c647: */
+ RTUNI_ALPHA, /* U+00c648: */
+ RTUNI_ALPHA, /* U+00c649: */
+ RTUNI_ALPHA, /* U+00c64a: */
+ RTUNI_ALPHA, /* U+00c64b: */
+ RTUNI_ALPHA, /* U+00c64c: */
+ RTUNI_ALPHA, /* U+00c64d: */
+ RTUNI_ALPHA, /* U+00c64e: */
+ RTUNI_ALPHA, /* U+00c64f: */
+ RTUNI_ALPHA, /* U+00c650: */
+ RTUNI_ALPHA, /* U+00c651: */
+ RTUNI_ALPHA, /* U+00c652: */
+ RTUNI_ALPHA, /* U+00c653: */
+ RTUNI_ALPHA, /* U+00c654: */
+ RTUNI_ALPHA, /* U+00c655: */
+ RTUNI_ALPHA, /* U+00c656: */
+ RTUNI_ALPHA, /* U+00c657: */
+ RTUNI_ALPHA, /* U+00c658: */
+ RTUNI_ALPHA, /* U+00c659: */
+ RTUNI_ALPHA, /* U+00c65a: */
+ RTUNI_ALPHA, /* U+00c65b: */
+ RTUNI_ALPHA, /* U+00c65c: */
+ RTUNI_ALPHA, /* U+00c65d: */
+ RTUNI_ALPHA, /* U+00c65e: */
+ RTUNI_ALPHA, /* U+00c65f: */
+ RTUNI_ALPHA, /* U+00c660: */
+ RTUNI_ALPHA, /* U+00c661: */
+ RTUNI_ALPHA, /* U+00c662: */
+ RTUNI_ALPHA, /* U+00c663: */
+ RTUNI_ALPHA, /* U+00c664: */
+ RTUNI_ALPHA, /* U+00c665: */
+ RTUNI_ALPHA, /* U+00c666: */
+ RTUNI_ALPHA, /* U+00c667: */
+ RTUNI_ALPHA, /* U+00c668: */
+ RTUNI_ALPHA, /* U+00c669: */
+ RTUNI_ALPHA, /* U+00c66a: */
+ RTUNI_ALPHA, /* U+00c66b: */
+ RTUNI_ALPHA, /* U+00c66c: */
+ RTUNI_ALPHA, /* U+00c66d: */
+ RTUNI_ALPHA, /* U+00c66e: */
+ RTUNI_ALPHA, /* U+00c66f: */
+ RTUNI_ALPHA, /* U+00c670: */
+ RTUNI_ALPHA, /* U+00c671: */
+ RTUNI_ALPHA, /* U+00c672: */
+ RTUNI_ALPHA, /* U+00c673: */
+ RTUNI_ALPHA, /* U+00c674: */
+ RTUNI_ALPHA, /* U+00c675: */
+ RTUNI_ALPHA, /* U+00c676: */
+ RTUNI_ALPHA, /* U+00c677: */
+ RTUNI_ALPHA, /* U+00c678: */
+ RTUNI_ALPHA, /* U+00c679: */
+ RTUNI_ALPHA, /* U+00c67a: */
+ RTUNI_ALPHA, /* U+00c67b: */
+ RTUNI_ALPHA, /* U+00c67c: */
+ RTUNI_ALPHA, /* U+00c67d: */
+ RTUNI_ALPHA, /* U+00c67e: */
+ RTUNI_ALPHA, /* U+00c67f: */
+ RTUNI_ALPHA, /* U+00c680: */
+ RTUNI_ALPHA, /* U+00c681: */
+ RTUNI_ALPHA, /* U+00c682: */
+ RTUNI_ALPHA, /* U+00c683: */
+ RTUNI_ALPHA, /* U+00c684: */
+ RTUNI_ALPHA, /* U+00c685: */
+ RTUNI_ALPHA, /* U+00c686: */
+ RTUNI_ALPHA, /* U+00c687: */
+ RTUNI_ALPHA, /* U+00c688: */
+ RTUNI_ALPHA, /* U+00c689: */
+ RTUNI_ALPHA, /* U+00c68a: */
+ RTUNI_ALPHA, /* U+00c68b: */
+ RTUNI_ALPHA, /* U+00c68c: */
+ RTUNI_ALPHA, /* U+00c68d: */
+ RTUNI_ALPHA, /* U+00c68e: */
+ RTUNI_ALPHA, /* U+00c68f: */
+ RTUNI_ALPHA, /* U+00c690: */
+ RTUNI_ALPHA, /* U+00c691: */
+ RTUNI_ALPHA, /* U+00c692: */
+ RTUNI_ALPHA, /* U+00c693: */
+ RTUNI_ALPHA, /* U+00c694: */
+ RTUNI_ALPHA, /* U+00c695: */
+ RTUNI_ALPHA, /* U+00c696: */
+ RTUNI_ALPHA, /* U+00c697: */
+ RTUNI_ALPHA, /* U+00c698: */
+ RTUNI_ALPHA, /* U+00c699: */
+ RTUNI_ALPHA, /* U+00c69a: */
+ RTUNI_ALPHA, /* U+00c69b: */
+ RTUNI_ALPHA, /* U+00c69c: */
+ RTUNI_ALPHA, /* U+00c69d: */
+ RTUNI_ALPHA, /* U+00c69e: */
+ RTUNI_ALPHA, /* U+00c69f: */
+ RTUNI_ALPHA, /* U+00c6a0: */
+ RTUNI_ALPHA, /* U+00c6a1: */
+ RTUNI_ALPHA, /* U+00c6a2: */
+ RTUNI_ALPHA, /* U+00c6a3: */
+ RTUNI_ALPHA, /* U+00c6a4: */
+ RTUNI_ALPHA, /* U+00c6a5: */
+ RTUNI_ALPHA, /* U+00c6a6: */
+ RTUNI_ALPHA, /* U+00c6a7: */
+ RTUNI_ALPHA, /* U+00c6a8: */
+ RTUNI_ALPHA, /* U+00c6a9: */
+ RTUNI_ALPHA, /* U+00c6aa: */
+ RTUNI_ALPHA, /* U+00c6ab: */
+ RTUNI_ALPHA, /* U+00c6ac: */
+ RTUNI_ALPHA, /* U+00c6ad: */
+ RTUNI_ALPHA, /* U+00c6ae: */
+ RTUNI_ALPHA, /* U+00c6af: */
+ RTUNI_ALPHA, /* U+00c6b0: */
+ RTUNI_ALPHA, /* U+00c6b1: */
+ RTUNI_ALPHA, /* U+00c6b2: */
+ RTUNI_ALPHA, /* U+00c6b3: */
+ RTUNI_ALPHA, /* U+00c6b4: */
+ RTUNI_ALPHA, /* U+00c6b5: */
+ RTUNI_ALPHA, /* U+00c6b6: */
+ RTUNI_ALPHA, /* U+00c6b7: */
+ RTUNI_ALPHA, /* U+00c6b8: */
+ RTUNI_ALPHA, /* U+00c6b9: */
+ RTUNI_ALPHA, /* U+00c6ba: */
+ RTUNI_ALPHA, /* U+00c6bb: */
+ RTUNI_ALPHA, /* U+00c6bc: */
+ RTUNI_ALPHA, /* U+00c6bd: */
+ RTUNI_ALPHA, /* U+00c6be: */
+ RTUNI_ALPHA, /* U+00c6bf: */
+ RTUNI_ALPHA, /* U+00c6c0: */
+ RTUNI_ALPHA, /* U+00c6c1: */
+ RTUNI_ALPHA, /* U+00c6c2: */
+ RTUNI_ALPHA, /* U+00c6c3: */
+ RTUNI_ALPHA, /* U+00c6c4: */
+ RTUNI_ALPHA, /* U+00c6c5: */
+ RTUNI_ALPHA, /* U+00c6c6: */
+ RTUNI_ALPHA, /* U+00c6c7: */
+ RTUNI_ALPHA, /* U+00c6c8: */
+ RTUNI_ALPHA, /* U+00c6c9: */
+ RTUNI_ALPHA, /* U+00c6ca: */
+ RTUNI_ALPHA, /* U+00c6cb: */
+ RTUNI_ALPHA, /* U+00c6cc: */
+ RTUNI_ALPHA, /* U+00c6cd: */
+ RTUNI_ALPHA, /* U+00c6ce: */
+ RTUNI_ALPHA, /* U+00c6cf: */
+ RTUNI_ALPHA, /* U+00c6d0: */
+ RTUNI_ALPHA, /* U+00c6d1: */
+ RTUNI_ALPHA, /* U+00c6d2: */
+ RTUNI_ALPHA, /* U+00c6d3: */
+ RTUNI_ALPHA, /* U+00c6d4: */
+ RTUNI_ALPHA, /* U+00c6d5: */
+ RTUNI_ALPHA, /* U+00c6d6: */
+ RTUNI_ALPHA, /* U+00c6d7: */
+ RTUNI_ALPHA, /* U+00c6d8: */
+ RTUNI_ALPHA, /* U+00c6d9: */
+ RTUNI_ALPHA, /* U+00c6da: */
+ RTUNI_ALPHA, /* U+00c6db: */
+ RTUNI_ALPHA, /* U+00c6dc: */
+ RTUNI_ALPHA, /* U+00c6dd: */
+ RTUNI_ALPHA, /* U+00c6de: */
+ RTUNI_ALPHA, /* U+00c6df: */
+ RTUNI_ALPHA, /* U+00c6e0: */
+ RTUNI_ALPHA, /* U+00c6e1: */
+ RTUNI_ALPHA, /* U+00c6e2: */
+ RTUNI_ALPHA, /* U+00c6e3: */
+ RTUNI_ALPHA, /* U+00c6e4: */
+ RTUNI_ALPHA, /* U+00c6e5: */
+ RTUNI_ALPHA, /* U+00c6e6: */
+ RTUNI_ALPHA, /* U+00c6e7: */
+ RTUNI_ALPHA, /* U+00c6e8: */
+ RTUNI_ALPHA, /* U+00c6e9: */
+ RTUNI_ALPHA, /* U+00c6ea: */
+ RTUNI_ALPHA, /* U+00c6eb: */
+ RTUNI_ALPHA, /* U+00c6ec: */
+ RTUNI_ALPHA, /* U+00c6ed: */
+ RTUNI_ALPHA, /* U+00c6ee: */
+ RTUNI_ALPHA, /* U+00c6ef: */
+ RTUNI_ALPHA, /* U+00c6f0: */
+ RTUNI_ALPHA, /* U+00c6f1: */
+ RTUNI_ALPHA, /* U+00c6f2: */
+ RTUNI_ALPHA, /* U+00c6f3: */
+ RTUNI_ALPHA, /* U+00c6f4: */
+ RTUNI_ALPHA, /* U+00c6f5: */
+ RTUNI_ALPHA, /* U+00c6f6: */
+ RTUNI_ALPHA, /* U+00c6f7: */
+ RTUNI_ALPHA, /* U+00c6f8: */
+ RTUNI_ALPHA, /* U+00c6f9: */
+ RTUNI_ALPHA, /* U+00c6fa: */
+ RTUNI_ALPHA, /* U+00c6fb: */
+ RTUNI_ALPHA, /* U+00c6fc: */
+ RTUNI_ALPHA, /* U+00c6fd: */
+ RTUNI_ALPHA, /* U+00c6fe: */
+ RTUNI_ALPHA, /* U+00c6ff: */
+ RTUNI_ALPHA, /* U+00c700: */
+ RTUNI_ALPHA, /* U+00c701: */
+ RTUNI_ALPHA, /* U+00c702: */
+ RTUNI_ALPHA, /* U+00c703: */
+ RTUNI_ALPHA, /* U+00c704: */
+ RTUNI_ALPHA, /* U+00c705: */
+ RTUNI_ALPHA, /* U+00c706: */
+ RTUNI_ALPHA, /* U+00c707: */
+ RTUNI_ALPHA, /* U+00c708: */
+ RTUNI_ALPHA, /* U+00c709: */
+ RTUNI_ALPHA, /* U+00c70a: */
+ RTUNI_ALPHA, /* U+00c70b: */
+ RTUNI_ALPHA, /* U+00c70c: */
+ RTUNI_ALPHA, /* U+00c70d: */
+ RTUNI_ALPHA, /* U+00c70e: */
+ RTUNI_ALPHA, /* U+00c70f: */
+ RTUNI_ALPHA, /* U+00c710: */
+ RTUNI_ALPHA, /* U+00c711: */
+ RTUNI_ALPHA, /* U+00c712: */
+ RTUNI_ALPHA, /* U+00c713: */
+ RTUNI_ALPHA, /* U+00c714: */
+ RTUNI_ALPHA, /* U+00c715: */
+ RTUNI_ALPHA, /* U+00c716: */
+ RTUNI_ALPHA, /* U+00c717: */
+ RTUNI_ALPHA, /* U+00c718: */
+ RTUNI_ALPHA, /* U+00c719: */
+ RTUNI_ALPHA, /* U+00c71a: */
+ RTUNI_ALPHA, /* U+00c71b: */
+ RTUNI_ALPHA, /* U+00c71c: */
+ RTUNI_ALPHA, /* U+00c71d: */
+ RTUNI_ALPHA, /* U+00c71e: */
+ RTUNI_ALPHA, /* U+00c71f: */
+ RTUNI_ALPHA, /* U+00c720: */
+ RTUNI_ALPHA, /* U+00c721: */
+ RTUNI_ALPHA, /* U+00c722: */
+ RTUNI_ALPHA, /* U+00c723: */
+ RTUNI_ALPHA, /* U+00c724: */
+ RTUNI_ALPHA, /* U+00c725: */
+ RTUNI_ALPHA, /* U+00c726: */
+ RTUNI_ALPHA, /* U+00c727: */
+ RTUNI_ALPHA, /* U+00c728: */
+ RTUNI_ALPHA, /* U+00c729: */
+ RTUNI_ALPHA, /* U+00c72a: */
+ RTUNI_ALPHA, /* U+00c72b: */
+ RTUNI_ALPHA, /* U+00c72c: */
+ RTUNI_ALPHA, /* U+00c72d: */
+ RTUNI_ALPHA, /* U+00c72e: */
+ RTUNI_ALPHA, /* U+00c72f: */
+ RTUNI_ALPHA, /* U+00c730: */
+ RTUNI_ALPHA, /* U+00c731: */
+ RTUNI_ALPHA, /* U+00c732: */
+ RTUNI_ALPHA, /* U+00c733: */
+ RTUNI_ALPHA, /* U+00c734: */
+ RTUNI_ALPHA, /* U+00c735: */
+ RTUNI_ALPHA, /* U+00c736: */
+ RTUNI_ALPHA, /* U+00c737: */
+ RTUNI_ALPHA, /* U+00c738: */
+ RTUNI_ALPHA, /* U+00c739: */
+ RTUNI_ALPHA, /* U+00c73a: */
+ RTUNI_ALPHA, /* U+00c73b: */
+ RTUNI_ALPHA, /* U+00c73c: */
+ RTUNI_ALPHA, /* U+00c73d: */
+ RTUNI_ALPHA, /* U+00c73e: */
+ RTUNI_ALPHA, /* U+00c73f: */
+ RTUNI_ALPHA, /* U+00c740: */
+ RTUNI_ALPHA, /* U+00c741: */
+ RTUNI_ALPHA, /* U+00c742: */
+ RTUNI_ALPHA, /* U+00c743: */
+ RTUNI_ALPHA, /* U+00c744: */
+ RTUNI_ALPHA, /* U+00c745: */
+ RTUNI_ALPHA, /* U+00c746: */
+ RTUNI_ALPHA, /* U+00c747: */
+ RTUNI_ALPHA, /* U+00c748: */
+ RTUNI_ALPHA, /* U+00c749: */
+ RTUNI_ALPHA, /* U+00c74a: */
+ RTUNI_ALPHA, /* U+00c74b: */
+ RTUNI_ALPHA, /* U+00c74c: */
+ RTUNI_ALPHA, /* U+00c74d: */
+ RTUNI_ALPHA, /* U+00c74e: */
+ RTUNI_ALPHA, /* U+00c74f: */
+ RTUNI_ALPHA, /* U+00c750: */
+ RTUNI_ALPHA, /* U+00c751: */
+ RTUNI_ALPHA, /* U+00c752: */
+ RTUNI_ALPHA, /* U+00c753: */
+ RTUNI_ALPHA, /* U+00c754: */
+ RTUNI_ALPHA, /* U+00c755: */
+ RTUNI_ALPHA, /* U+00c756: */
+ RTUNI_ALPHA, /* U+00c757: */
+ RTUNI_ALPHA, /* U+00c758: */
+ RTUNI_ALPHA, /* U+00c759: */
+ RTUNI_ALPHA, /* U+00c75a: */
+ RTUNI_ALPHA, /* U+00c75b: */
+ RTUNI_ALPHA, /* U+00c75c: */
+ RTUNI_ALPHA, /* U+00c75d: */
+ RTUNI_ALPHA, /* U+00c75e: */
+ RTUNI_ALPHA, /* U+00c75f: */
+ RTUNI_ALPHA, /* U+00c760: */
+ RTUNI_ALPHA, /* U+00c761: */
+ RTUNI_ALPHA, /* U+00c762: */
+ RTUNI_ALPHA, /* U+00c763: */
+ RTUNI_ALPHA, /* U+00c764: */
+ RTUNI_ALPHA, /* U+00c765: */
+ RTUNI_ALPHA, /* U+00c766: */
+ RTUNI_ALPHA, /* U+00c767: */
+ RTUNI_ALPHA, /* U+00c768: */
+ RTUNI_ALPHA, /* U+00c769: */
+ RTUNI_ALPHA, /* U+00c76a: */
+ RTUNI_ALPHA, /* U+00c76b: */
+ RTUNI_ALPHA, /* U+00c76c: */
+ RTUNI_ALPHA, /* U+00c76d: */
+ RTUNI_ALPHA, /* U+00c76e: */
+ RTUNI_ALPHA, /* U+00c76f: */
+ RTUNI_ALPHA, /* U+00c770: */
+ RTUNI_ALPHA, /* U+00c771: */
+ RTUNI_ALPHA, /* U+00c772: */
+ RTUNI_ALPHA, /* U+00c773: */
+ RTUNI_ALPHA, /* U+00c774: */
+ RTUNI_ALPHA, /* U+00c775: */
+ RTUNI_ALPHA, /* U+00c776: */
+ RTUNI_ALPHA, /* U+00c777: */
+ RTUNI_ALPHA, /* U+00c778: */
+ RTUNI_ALPHA, /* U+00c779: */
+ RTUNI_ALPHA, /* U+00c77a: */
+ RTUNI_ALPHA, /* U+00c77b: */
+ RTUNI_ALPHA, /* U+00c77c: */
+ RTUNI_ALPHA, /* U+00c77d: */
+ RTUNI_ALPHA, /* U+00c77e: */
+ RTUNI_ALPHA, /* U+00c77f: */
+ RTUNI_ALPHA, /* U+00c780: */
+ RTUNI_ALPHA, /* U+00c781: */
+ RTUNI_ALPHA, /* U+00c782: */
+ RTUNI_ALPHA, /* U+00c783: */
+ RTUNI_ALPHA, /* U+00c784: */
+ RTUNI_ALPHA, /* U+00c785: */
+ RTUNI_ALPHA, /* U+00c786: */
+ RTUNI_ALPHA, /* U+00c787: */
+ RTUNI_ALPHA, /* U+00c788: */
+ RTUNI_ALPHA, /* U+00c789: */
+ RTUNI_ALPHA, /* U+00c78a: */
+ RTUNI_ALPHA, /* U+00c78b: */
+ RTUNI_ALPHA, /* U+00c78c: */
+ RTUNI_ALPHA, /* U+00c78d: */
+ RTUNI_ALPHA, /* U+00c78e: */
+ RTUNI_ALPHA, /* U+00c78f: */
+ RTUNI_ALPHA, /* U+00c790: */
+ RTUNI_ALPHA, /* U+00c791: */
+ RTUNI_ALPHA, /* U+00c792: */
+ RTUNI_ALPHA, /* U+00c793: */
+ RTUNI_ALPHA, /* U+00c794: */
+ RTUNI_ALPHA, /* U+00c795: */
+ RTUNI_ALPHA, /* U+00c796: */
+ RTUNI_ALPHA, /* U+00c797: */
+ RTUNI_ALPHA, /* U+00c798: */
+ RTUNI_ALPHA, /* U+00c799: */
+ RTUNI_ALPHA, /* U+00c79a: */
+ RTUNI_ALPHA, /* U+00c79b: */
+ RTUNI_ALPHA, /* U+00c79c: */
+ RTUNI_ALPHA, /* U+00c79d: */
+ RTUNI_ALPHA, /* U+00c79e: */
+ RTUNI_ALPHA, /* U+00c79f: */
+ RTUNI_ALPHA, /* U+00c7a0: */
+ RTUNI_ALPHA, /* U+00c7a1: */
+ RTUNI_ALPHA, /* U+00c7a2: */
+ RTUNI_ALPHA, /* U+00c7a3: */
+ RTUNI_ALPHA, /* U+00c7a4: */
+ RTUNI_ALPHA, /* U+00c7a5: */
+ RTUNI_ALPHA, /* U+00c7a6: */
+ RTUNI_ALPHA, /* U+00c7a7: */
+ RTUNI_ALPHA, /* U+00c7a8: */
+ RTUNI_ALPHA, /* U+00c7a9: */
+ RTUNI_ALPHA, /* U+00c7aa: */
+ RTUNI_ALPHA, /* U+00c7ab: */
+ RTUNI_ALPHA, /* U+00c7ac: */
+ RTUNI_ALPHA, /* U+00c7ad: */
+ RTUNI_ALPHA, /* U+00c7ae: */
+ RTUNI_ALPHA, /* U+00c7af: */
+ RTUNI_ALPHA, /* U+00c7b0: */
+ RTUNI_ALPHA, /* U+00c7b1: */
+ RTUNI_ALPHA, /* U+00c7b2: */
+ RTUNI_ALPHA, /* U+00c7b3: */
+ RTUNI_ALPHA, /* U+00c7b4: */
+ RTUNI_ALPHA, /* U+00c7b5: */
+ RTUNI_ALPHA, /* U+00c7b6: */
+ RTUNI_ALPHA, /* U+00c7b7: */
+ RTUNI_ALPHA, /* U+00c7b8: */
+ RTUNI_ALPHA, /* U+00c7b9: */
+ RTUNI_ALPHA, /* U+00c7ba: */
+ RTUNI_ALPHA, /* U+00c7bb: */
+ RTUNI_ALPHA, /* U+00c7bc: */
+ RTUNI_ALPHA, /* U+00c7bd: */
+ RTUNI_ALPHA, /* U+00c7be: */
+ RTUNI_ALPHA, /* U+00c7bf: */
+ RTUNI_ALPHA, /* U+00c7c0: */
+ RTUNI_ALPHA, /* U+00c7c1: */
+ RTUNI_ALPHA, /* U+00c7c2: */
+ RTUNI_ALPHA, /* U+00c7c3: */
+ RTUNI_ALPHA, /* U+00c7c4: */
+ RTUNI_ALPHA, /* U+00c7c5: */
+ RTUNI_ALPHA, /* U+00c7c6: */
+ RTUNI_ALPHA, /* U+00c7c7: */
+ RTUNI_ALPHA, /* U+00c7c8: */
+ RTUNI_ALPHA, /* U+00c7c9: */
+ RTUNI_ALPHA, /* U+00c7ca: */
+ RTUNI_ALPHA, /* U+00c7cb: */
+ RTUNI_ALPHA, /* U+00c7cc: */
+ RTUNI_ALPHA, /* U+00c7cd: */
+ RTUNI_ALPHA, /* U+00c7ce: */
+ RTUNI_ALPHA, /* U+00c7cf: */
+ RTUNI_ALPHA, /* U+00c7d0: */
+ RTUNI_ALPHA, /* U+00c7d1: */
+ RTUNI_ALPHA, /* U+00c7d2: */
+ RTUNI_ALPHA, /* U+00c7d3: */
+ RTUNI_ALPHA, /* U+00c7d4: */
+ RTUNI_ALPHA, /* U+00c7d5: */
+ RTUNI_ALPHA, /* U+00c7d6: */
+ RTUNI_ALPHA, /* U+00c7d7: */
+ RTUNI_ALPHA, /* U+00c7d8: */
+ RTUNI_ALPHA, /* U+00c7d9: */
+ RTUNI_ALPHA, /* U+00c7da: */
+ RTUNI_ALPHA, /* U+00c7db: */
+ RTUNI_ALPHA, /* U+00c7dc: */
+ RTUNI_ALPHA, /* U+00c7dd: */
+ RTUNI_ALPHA, /* U+00c7de: */
+ RTUNI_ALPHA, /* U+00c7df: */
+ RTUNI_ALPHA, /* U+00c7e0: */
+ RTUNI_ALPHA, /* U+00c7e1: */
+ RTUNI_ALPHA, /* U+00c7e2: */
+ RTUNI_ALPHA, /* U+00c7e3: */
+ RTUNI_ALPHA, /* U+00c7e4: */
+ RTUNI_ALPHA, /* U+00c7e5: */
+ RTUNI_ALPHA, /* U+00c7e6: */
+ RTUNI_ALPHA, /* U+00c7e7: */
+ RTUNI_ALPHA, /* U+00c7e8: */
+ RTUNI_ALPHA, /* U+00c7e9: */
+ RTUNI_ALPHA, /* U+00c7ea: */
+ RTUNI_ALPHA, /* U+00c7eb: */
+ RTUNI_ALPHA, /* U+00c7ec: */
+ RTUNI_ALPHA, /* U+00c7ed: */
+ RTUNI_ALPHA, /* U+00c7ee: */
+ RTUNI_ALPHA, /* U+00c7ef: */
+ RTUNI_ALPHA, /* U+00c7f0: */
+ RTUNI_ALPHA, /* U+00c7f1: */
+ RTUNI_ALPHA, /* U+00c7f2: */
+ RTUNI_ALPHA, /* U+00c7f3: */
+ RTUNI_ALPHA, /* U+00c7f4: */
+ RTUNI_ALPHA, /* U+00c7f5: */
+ RTUNI_ALPHA, /* U+00c7f6: */
+ RTUNI_ALPHA, /* U+00c7f7: */
+ RTUNI_ALPHA, /* U+00c7f8: */
+ RTUNI_ALPHA, /* U+00c7f9: */
+ RTUNI_ALPHA, /* U+00c7fa: */
+ RTUNI_ALPHA, /* U+00c7fb: */
+ RTUNI_ALPHA, /* U+00c7fc: */
+ RTUNI_ALPHA, /* U+00c7fd: */
+ RTUNI_ALPHA, /* U+00c7fe: */
+ RTUNI_ALPHA, /* U+00c7ff: */
+ RTUNI_ALPHA, /* U+00c800: */
+ RTUNI_ALPHA, /* U+00c801: */
+ RTUNI_ALPHA, /* U+00c802: */
+ RTUNI_ALPHA, /* U+00c803: */
+ RTUNI_ALPHA, /* U+00c804: */
+ RTUNI_ALPHA, /* U+00c805: */
+ RTUNI_ALPHA, /* U+00c806: */
+ RTUNI_ALPHA, /* U+00c807: */
+ RTUNI_ALPHA, /* U+00c808: */
+ RTUNI_ALPHA, /* U+00c809: */
+ RTUNI_ALPHA, /* U+00c80a: */
+ RTUNI_ALPHA, /* U+00c80b: */
+ RTUNI_ALPHA, /* U+00c80c: */
+ RTUNI_ALPHA, /* U+00c80d: */
+ RTUNI_ALPHA, /* U+00c80e: */
+ RTUNI_ALPHA, /* U+00c80f: */
+ RTUNI_ALPHA, /* U+00c810: */
+ RTUNI_ALPHA, /* U+00c811: */
+ RTUNI_ALPHA, /* U+00c812: */
+ RTUNI_ALPHA, /* U+00c813: */
+ RTUNI_ALPHA, /* U+00c814: */
+ RTUNI_ALPHA, /* U+00c815: */
+ RTUNI_ALPHA, /* U+00c816: */
+ RTUNI_ALPHA, /* U+00c817: */
+ RTUNI_ALPHA, /* U+00c818: */
+ RTUNI_ALPHA, /* U+00c819: */
+ RTUNI_ALPHA, /* U+00c81a: */
+ RTUNI_ALPHA, /* U+00c81b: */
+ RTUNI_ALPHA, /* U+00c81c: */
+ RTUNI_ALPHA, /* U+00c81d: */
+ RTUNI_ALPHA, /* U+00c81e: */
+ RTUNI_ALPHA, /* U+00c81f: */
+ RTUNI_ALPHA, /* U+00c820: */
+ RTUNI_ALPHA, /* U+00c821: */
+ RTUNI_ALPHA, /* U+00c822: */
+ RTUNI_ALPHA, /* U+00c823: */
+ RTUNI_ALPHA, /* U+00c824: */
+ RTUNI_ALPHA, /* U+00c825: */
+ RTUNI_ALPHA, /* U+00c826: */
+ RTUNI_ALPHA, /* U+00c827: */
+ RTUNI_ALPHA, /* U+00c828: */
+ RTUNI_ALPHA, /* U+00c829: */
+ RTUNI_ALPHA, /* U+00c82a: */
+ RTUNI_ALPHA, /* U+00c82b: */
+ RTUNI_ALPHA, /* U+00c82c: */
+ RTUNI_ALPHA, /* U+00c82d: */
+ RTUNI_ALPHA, /* U+00c82e: */
+ RTUNI_ALPHA, /* U+00c82f: */
+ RTUNI_ALPHA, /* U+00c830: */
+ RTUNI_ALPHA, /* U+00c831: */
+ RTUNI_ALPHA, /* U+00c832: */
+ RTUNI_ALPHA, /* U+00c833: */
+ RTUNI_ALPHA, /* U+00c834: */
+ RTUNI_ALPHA, /* U+00c835: */
+ RTUNI_ALPHA, /* U+00c836: */
+ RTUNI_ALPHA, /* U+00c837: */
+ RTUNI_ALPHA, /* U+00c838: */
+ RTUNI_ALPHA, /* U+00c839: */
+ RTUNI_ALPHA, /* U+00c83a: */
+ RTUNI_ALPHA, /* U+00c83b: */
+ RTUNI_ALPHA, /* U+00c83c: */
+ RTUNI_ALPHA, /* U+00c83d: */
+ RTUNI_ALPHA, /* U+00c83e: */
+ RTUNI_ALPHA, /* U+00c83f: */
+ RTUNI_ALPHA, /* U+00c840: */
+ RTUNI_ALPHA, /* U+00c841: */
+ RTUNI_ALPHA, /* U+00c842: */
+ RTUNI_ALPHA, /* U+00c843: */
+ RTUNI_ALPHA, /* U+00c844: */
+ RTUNI_ALPHA, /* U+00c845: */
+ RTUNI_ALPHA, /* U+00c846: */
+ RTUNI_ALPHA, /* U+00c847: */
+ RTUNI_ALPHA, /* U+00c848: */
+ RTUNI_ALPHA, /* U+00c849: */
+ RTUNI_ALPHA, /* U+00c84a: */
+ RTUNI_ALPHA, /* U+00c84b: */
+ RTUNI_ALPHA, /* U+00c84c: */
+ RTUNI_ALPHA, /* U+00c84d: */
+ RTUNI_ALPHA, /* U+00c84e: */
+ RTUNI_ALPHA, /* U+00c84f: */
+ RTUNI_ALPHA, /* U+00c850: */
+ RTUNI_ALPHA, /* U+00c851: */
+ RTUNI_ALPHA, /* U+00c852: */
+ RTUNI_ALPHA, /* U+00c853: */
+ RTUNI_ALPHA, /* U+00c854: */
+ RTUNI_ALPHA, /* U+00c855: */
+ RTUNI_ALPHA, /* U+00c856: */
+ RTUNI_ALPHA, /* U+00c857: */
+ RTUNI_ALPHA, /* U+00c858: */
+ RTUNI_ALPHA, /* U+00c859: */
+ RTUNI_ALPHA, /* U+00c85a: */
+ RTUNI_ALPHA, /* U+00c85b: */
+ RTUNI_ALPHA, /* U+00c85c: */
+ RTUNI_ALPHA, /* U+00c85d: */
+ RTUNI_ALPHA, /* U+00c85e: */
+ RTUNI_ALPHA, /* U+00c85f: */
+ RTUNI_ALPHA, /* U+00c860: */
+ RTUNI_ALPHA, /* U+00c861: */
+ RTUNI_ALPHA, /* U+00c862: */
+ RTUNI_ALPHA, /* U+00c863: */
+ RTUNI_ALPHA, /* U+00c864: */
+ RTUNI_ALPHA, /* U+00c865: */
+ RTUNI_ALPHA, /* U+00c866: */
+ RTUNI_ALPHA, /* U+00c867: */
+ RTUNI_ALPHA, /* U+00c868: */
+ RTUNI_ALPHA, /* U+00c869: */
+ RTUNI_ALPHA, /* U+00c86a: */
+ RTUNI_ALPHA, /* U+00c86b: */
+ RTUNI_ALPHA, /* U+00c86c: */
+ RTUNI_ALPHA, /* U+00c86d: */
+ RTUNI_ALPHA, /* U+00c86e: */
+ RTUNI_ALPHA, /* U+00c86f: */
+ RTUNI_ALPHA, /* U+00c870: */
+ RTUNI_ALPHA, /* U+00c871: */
+ RTUNI_ALPHA, /* U+00c872: */
+ RTUNI_ALPHA, /* U+00c873: */
+ RTUNI_ALPHA, /* U+00c874: */
+ RTUNI_ALPHA, /* U+00c875: */
+ RTUNI_ALPHA, /* U+00c876: */
+ RTUNI_ALPHA, /* U+00c877: */
+ RTUNI_ALPHA, /* U+00c878: */
+ RTUNI_ALPHA, /* U+00c879: */
+ RTUNI_ALPHA, /* U+00c87a: */
+ RTUNI_ALPHA, /* U+00c87b: */
+ RTUNI_ALPHA, /* U+00c87c: */
+ RTUNI_ALPHA, /* U+00c87d: */
+ RTUNI_ALPHA, /* U+00c87e: */
+ RTUNI_ALPHA, /* U+00c87f: */
+ RTUNI_ALPHA, /* U+00c880: */
+ RTUNI_ALPHA, /* U+00c881: */
+ RTUNI_ALPHA, /* U+00c882: */
+ RTUNI_ALPHA, /* U+00c883: */
+ RTUNI_ALPHA, /* U+00c884: */
+ RTUNI_ALPHA, /* U+00c885: */
+ RTUNI_ALPHA, /* U+00c886: */
+ RTUNI_ALPHA, /* U+00c887: */
+ RTUNI_ALPHA, /* U+00c888: */
+ RTUNI_ALPHA, /* U+00c889: */
+ RTUNI_ALPHA, /* U+00c88a: */
+ RTUNI_ALPHA, /* U+00c88b: */
+ RTUNI_ALPHA, /* U+00c88c: */
+ RTUNI_ALPHA, /* U+00c88d: */
+ RTUNI_ALPHA, /* U+00c88e: */
+ RTUNI_ALPHA, /* U+00c88f: */
+ RTUNI_ALPHA, /* U+00c890: */
+ RTUNI_ALPHA, /* U+00c891: */
+ RTUNI_ALPHA, /* U+00c892: */
+ RTUNI_ALPHA, /* U+00c893: */
+ RTUNI_ALPHA, /* U+00c894: */
+ RTUNI_ALPHA, /* U+00c895: */
+ RTUNI_ALPHA, /* U+00c896: */
+ RTUNI_ALPHA, /* U+00c897: */
+ RTUNI_ALPHA, /* U+00c898: */
+ RTUNI_ALPHA, /* U+00c899: */
+ RTUNI_ALPHA, /* U+00c89a: */
+ RTUNI_ALPHA, /* U+00c89b: */
+ RTUNI_ALPHA, /* U+00c89c: */
+ RTUNI_ALPHA, /* U+00c89d: */
+ RTUNI_ALPHA, /* U+00c89e: */
+ RTUNI_ALPHA, /* U+00c89f: */
+ RTUNI_ALPHA, /* U+00c8a0: */
+ RTUNI_ALPHA, /* U+00c8a1: */
+ RTUNI_ALPHA, /* U+00c8a2: */
+ RTUNI_ALPHA, /* U+00c8a3: */
+ RTUNI_ALPHA, /* U+00c8a4: */
+ RTUNI_ALPHA, /* U+00c8a5: */
+ RTUNI_ALPHA, /* U+00c8a6: */
+ RTUNI_ALPHA, /* U+00c8a7: */
+ RTUNI_ALPHA, /* U+00c8a8: */
+ RTUNI_ALPHA, /* U+00c8a9: */
+ RTUNI_ALPHA, /* U+00c8aa: */
+ RTUNI_ALPHA, /* U+00c8ab: */
+ RTUNI_ALPHA, /* U+00c8ac: */
+ RTUNI_ALPHA, /* U+00c8ad: */
+ RTUNI_ALPHA, /* U+00c8ae: */
+ RTUNI_ALPHA, /* U+00c8af: */
+ RTUNI_ALPHA, /* U+00c8b0: */
+ RTUNI_ALPHA, /* U+00c8b1: */
+ RTUNI_ALPHA, /* U+00c8b2: */
+ RTUNI_ALPHA, /* U+00c8b3: */
+ RTUNI_ALPHA, /* U+00c8b4: */
+ RTUNI_ALPHA, /* U+00c8b5: */
+ RTUNI_ALPHA, /* U+00c8b6: */
+ RTUNI_ALPHA, /* U+00c8b7: */
+ RTUNI_ALPHA, /* U+00c8b8: */
+ RTUNI_ALPHA, /* U+00c8b9: */
+ RTUNI_ALPHA, /* U+00c8ba: */
+ RTUNI_ALPHA, /* U+00c8bb: */
+ RTUNI_ALPHA, /* U+00c8bc: */
+ RTUNI_ALPHA, /* U+00c8bd: */
+ RTUNI_ALPHA, /* U+00c8be: */
+ RTUNI_ALPHA, /* U+00c8bf: */
+ RTUNI_ALPHA, /* U+00c8c0: */
+ RTUNI_ALPHA, /* U+00c8c1: */
+ RTUNI_ALPHA, /* U+00c8c2: */
+ RTUNI_ALPHA, /* U+00c8c3: */
+ RTUNI_ALPHA, /* U+00c8c4: */
+ RTUNI_ALPHA, /* U+00c8c5: */
+ RTUNI_ALPHA, /* U+00c8c6: */
+ RTUNI_ALPHA, /* U+00c8c7: */
+ RTUNI_ALPHA, /* U+00c8c8: */
+ RTUNI_ALPHA, /* U+00c8c9: */
+ RTUNI_ALPHA, /* U+00c8ca: */
+ RTUNI_ALPHA, /* U+00c8cb: */
+ RTUNI_ALPHA, /* U+00c8cc: */
+ RTUNI_ALPHA, /* U+00c8cd: */
+ RTUNI_ALPHA, /* U+00c8ce: */
+ RTUNI_ALPHA, /* U+00c8cf: */
+ RTUNI_ALPHA, /* U+00c8d0: */
+ RTUNI_ALPHA, /* U+00c8d1: */
+ RTUNI_ALPHA, /* U+00c8d2: */
+ RTUNI_ALPHA, /* U+00c8d3: */
+ RTUNI_ALPHA, /* U+00c8d4: */
+ RTUNI_ALPHA, /* U+00c8d5: */
+ RTUNI_ALPHA, /* U+00c8d6: */
+ RTUNI_ALPHA, /* U+00c8d7: */
+ RTUNI_ALPHA, /* U+00c8d8: */
+ RTUNI_ALPHA, /* U+00c8d9: */
+ RTUNI_ALPHA, /* U+00c8da: */
+ RTUNI_ALPHA, /* U+00c8db: */
+ RTUNI_ALPHA, /* U+00c8dc: */
+ RTUNI_ALPHA, /* U+00c8dd: */
+ RTUNI_ALPHA, /* U+00c8de: */
+ RTUNI_ALPHA, /* U+00c8df: */
+ RTUNI_ALPHA, /* U+00c8e0: */
+ RTUNI_ALPHA, /* U+00c8e1: */
+ RTUNI_ALPHA, /* U+00c8e2: */
+ RTUNI_ALPHA, /* U+00c8e3: */
+ RTUNI_ALPHA, /* U+00c8e4: */
+ RTUNI_ALPHA, /* U+00c8e5: */
+ RTUNI_ALPHA, /* U+00c8e6: */
+ RTUNI_ALPHA, /* U+00c8e7: */
+ RTUNI_ALPHA, /* U+00c8e8: */
+ RTUNI_ALPHA, /* U+00c8e9: */
+ RTUNI_ALPHA, /* U+00c8ea: */
+ RTUNI_ALPHA, /* U+00c8eb: */
+ RTUNI_ALPHA, /* U+00c8ec: */
+ RTUNI_ALPHA, /* U+00c8ed: */
+ RTUNI_ALPHA, /* U+00c8ee: */
+ RTUNI_ALPHA, /* U+00c8ef: */
+ RTUNI_ALPHA, /* U+00c8f0: */
+ RTUNI_ALPHA, /* U+00c8f1: */
+ RTUNI_ALPHA, /* U+00c8f2: */
+ RTUNI_ALPHA, /* U+00c8f3: */
+ RTUNI_ALPHA, /* U+00c8f4: */
+ RTUNI_ALPHA, /* U+00c8f5: */
+ RTUNI_ALPHA, /* U+00c8f6: */
+ RTUNI_ALPHA, /* U+00c8f7: */
+ RTUNI_ALPHA, /* U+00c8f8: */
+ RTUNI_ALPHA, /* U+00c8f9: */
+ RTUNI_ALPHA, /* U+00c8fa: */
+ RTUNI_ALPHA, /* U+00c8fb: */
+ RTUNI_ALPHA, /* U+00c8fc: */
+ RTUNI_ALPHA, /* U+00c8fd: */
+ RTUNI_ALPHA, /* U+00c8fe: */
+ RTUNI_ALPHA, /* U+00c8ff: */
+ RTUNI_ALPHA, /* U+00c900: */
+ RTUNI_ALPHA, /* U+00c901: */
+ RTUNI_ALPHA, /* U+00c902: */
+ RTUNI_ALPHA, /* U+00c903: */
+ RTUNI_ALPHA, /* U+00c904: */
+ RTUNI_ALPHA, /* U+00c905: */
+ RTUNI_ALPHA, /* U+00c906: */
+ RTUNI_ALPHA, /* U+00c907: */
+ RTUNI_ALPHA, /* U+00c908: */
+ RTUNI_ALPHA, /* U+00c909: */
+ RTUNI_ALPHA, /* U+00c90a: */
+ RTUNI_ALPHA, /* U+00c90b: */
+ RTUNI_ALPHA, /* U+00c90c: */
+ RTUNI_ALPHA, /* U+00c90d: */
+ RTUNI_ALPHA, /* U+00c90e: */
+ RTUNI_ALPHA, /* U+00c90f: */
+ RTUNI_ALPHA, /* U+00c910: */
+ RTUNI_ALPHA, /* U+00c911: */
+ RTUNI_ALPHA, /* U+00c912: */
+ RTUNI_ALPHA, /* U+00c913: */
+ RTUNI_ALPHA, /* U+00c914: */
+ RTUNI_ALPHA, /* U+00c915: */
+ RTUNI_ALPHA, /* U+00c916: */
+ RTUNI_ALPHA, /* U+00c917: */
+ RTUNI_ALPHA, /* U+00c918: */
+ RTUNI_ALPHA, /* U+00c919: */
+ RTUNI_ALPHA, /* U+00c91a: */
+ RTUNI_ALPHA, /* U+00c91b: */
+ RTUNI_ALPHA, /* U+00c91c: */
+ RTUNI_ALPHA, /* U+00c91d: */
+ RTUNI_ALPHA, /* U+00c91e: */
+ RTUNI_ALPHA, /* U+00c91f: */
+ RTUNI_ALPHA, /* U+00c920: */
+ RTUNI_ALPHA, /* U+00c921: */
+ RTUNI_ALPHA, /* U+00c922: */
+ RTUNI_ALPHA, /* U+00c923: */
+ RTUNI_ALPHA, /* U+00c924: */
+ RTUNI_ALPHA, /* U+00c925: */
+ RTUNI_ALPHA, /* U+00c926: */
+ RTUNI_ALPHA, /* U+00c927: */
+ RTUNI_ALPHA, /* U+00c928: */
+ RTUNI_ALPHA, /* U+00c929: */
+ RTUNI_ALPHA, /* U+00c92a: */
+ RTUNI_ALPHA, /* U+00c92b: */
+ RTUNI_ALPHA, /* U+00c92c: */
+ RTUNI_ALPHA, /* U+00c92d: */
+ RTUNI_ALPHA, /* U+00c92e: */
+ RTUNI_ALPHA, /* U+00c92f: */
+ RTUNI_ALPHA, /* U+00c930: */
+ RTUNI_ALPHA, /* U+00c931: */
+ RTUNI_ALPHA, /* U+00c932: */
+ RTUNI_ALPHA, /* U+00c933: */
+ RTUNI_ALPHA, /* U+00c934: */
+ RTUNI_ALPHA, /* U+00c935: */
+ RTUNI_ALPHA, /* U+00c936: */
+ RTUNI_ALPHA, /* U+00c937: */
+ RTUNI_ALPHA, /* U+00c938: */
+ RTUNI_ALPHA, /* U+00c939: */
+ RTUNI_ALPHA, /* U+00c93a: */
+ RTUNI_ALPHA, /* U+00c93b: */
+ RTUNI_ALPHA, /* U+00c93c: */
+ RTUNI_ALPHA, /* U+00c93d: */
+ RTUNI_ALPHA, /* U+00c93e: */
+ RTUNI_ALPHA, /* U+00c93f: */
+ RTUNI_ALPHA, /* U+00c940: */
+ RTUNI_ALPHA, /* U+00c941: */
+ RTUNI_ALPHA, /* U+00c942: */
+ RTUNI_ALPHA, /* U+00c943: */
+ RTUNI_ALPHA, /* U+00c944: */
+ RTUNI_ALPHA, /* U+00c945: */
+ RTUNI_ALPHA, /* U+00c946: */
+ RTUNI_ALPHA, /* U+00c947: */
+ RTUNI_ALPHA, /* U+00c948: */
+ RTUNI_ALPHA, /* U+00c949: */
+ RTUNI_ALPHA, /* U+00c94a: */
+ RTUNI_ALPHA, /* U+00c94b: */
+ RTUNI_ALPHA, /* U+00c94c: */
+ RTUNI_ALPHA, /* U+00c94d: */
+ RTUNI_ALPHA, /* U+00c94e: */
+ RTUNI_ALPHA, /* U+00c94f: */
+ RTUNI_ALPHA, /* U+00c950: */
+ RTUNI_ALPHA, /* U+00c951: */
+ RTUNI_ALPHA, /* U+00c952: */
+ RTUNI_ALPHA, /* U+00c953: */
+ RTUNI_ALPHA, /* U+00c954: */
+ RTUNI_ALPHA, /* U+00c955: */
+ RTUNI_ALPHA, /* U+00c956: */
+ RTUNI_ALPHA, /* U+00c957: */
+ RTUNI_ALPHA, /* U+00c958: */
+ RTUNI_ALPHA, /* U+00c959: */
+ RTUNI_ALPHA, /* U+00c95a: */
+ RTUNI_ALPHA, /* U+00c95b: */
+ RTUNI_ALPHA, /* U+00c95c: */
+ RTUNI_ALPHA, /* U+00c95d: */
+ RTUNI_ALPHA, /* U+00c95e: */
+ RTUNI_ALPHA, /* U+00c95f: */
+ RTUNI_ALPHA, /* U+00c960: */
+ RTUNI_ALPHA, /* U+00c961: */
+ RTUNI_ALPHA, /* U+00c962: */
+ RTUNI_ALPHA, /* U+00c963: */
+ RTUNI_ALPHA, /* U+00c964: */
+ RTUNI_ALPHA, /* U+00c965: */
+ RTUNI_ALPHA, /* U+00c966: */
+ RTUNI_ALPHA, /* U+00c967: */
+ RTUNI_ALPHA, /* U+00c968: */
+ RTUNI_ALPHA, /* U+00c969: */
+ RTUNI_ALPHA, /* U+00c96a: */
+ RTUNI_ALPHA, /* U+00c96b: */
+ RTUNI_ALPHA, /* U+00c96c: */
+ RTUNI_ALPHA, /* U+00c96d: */
+ RTUNI_ALPHA, /* U+00c96e: */
+ RTUNI_ALPHA, /* U+00c96f: */
+ RTUNI_ALPHA, /* U+00c970: */
+ RTUNI_ALPHA, /* U+00c971: */
+ RTUNI_ALPHA, /* U+00c972: */
+ RTUNI_ALPHA, /* U+00c973: */
+ RTUNI_ALPHA, /* U+00c974: */
+ RTUNI_ALPHA, /* U+00c975: */
+ RTUNI_ALPHA, /* U+00c976: */
+ RTUNI_ALPHA, /* U+00c977: */
+ RTUNI_ALPHA, /* U+00c978: */
+ RTUNI_ALPHA, /* U+00c979: */
+ RTUNI_ALPHA, /* U+00c97a: */
+ RTUNI_ALPHA, /* U+00c97b: */
+ RTUNI_ALPHA, /* U+00c97c: */
+ RTUNI_ALPHA, /* U+00c97d: */
+ RTUNI_ALPHA, /* U+00c97e: */
+ RTUNI_ALPHA, /* U+00c97f: */
+ RTUNI_ALPHA, /* U+00c980: */
+ RTUNI_ALPHA, /* U+00c981: */
+ RTUNI_ALPHA, /* U+00c982: */
+ RTUNI_ALPHA, /* U+00c983: */
+ RTUNI_ALPHA, /* U+00c984: */
+ RTUNI_ALPHA, /* U+00c985: */
+ RTUNI_ALPHA, /* U+00c986: */
+ RTUNI_ALPHA, /* U+00c987: */
+ RTUNI_ALPHA, /* U+00c988: */
+ RTUNI_ALPHA, /* U+00c989: */
+ RTUNI_ALPHA, /* U+00c98a: */
+ RTUNI_ALPHA, /* U+00c98b: */
+ RTUNI_ALPHA, /* U+00c98c: */
+ RTUNI_ALPHA, /* U+00c98d: */
+ RTUNI_ALPHA, /* U+00c98e: */
+ RTUNI_ALPHA, /* U+00c98f: */
+ RTUNI_ALPHA, /* U+00c990: */
+ RTUNI_ALPHA, /* U+00c991: */
+ RTUNI_ALPHA, /* U+00c992: */
+ RTUNI_ALPHA, /* U+00c993: */
+ RTUNI_ALPHA, /* U+00c994: */
+ RTUNI_ALPHA, /* U+00c995: */
+ RTUNI_ALPHA, /* U+00c996: */
+ RTUNI_ALPHA, /* U+00c997: */
+ RTUNI_ALPHA, /* U+00c998: */
+ RTUNI_ALPHA, /* U+00c999: */
+ RTUNI_ALPHA, /* U+00c99a: */
+ RTUNI_ALPHA, /* U+00c99b: */
+ RTUNI_ALPHA, /* U+00c99c: */
+ RTUNI_ALPHA, /* U+00c99d: */
+ RTUNI_ALPHA, /* U+00c99e: */
+ RTUNI_ALPHA, /* U+00c99f: */
+ RTUNI_ALPHA, /* U+00c9a0: */
+ RTUNI_ALPHA, /* U+00c9a1: */
+ RTUNI_ALPHA, /* U+00c9a2: */
+ RTUNI_ALPHA, /* U+00c9a3: */
+ RTUNI_ALPHA, /* U+00c9a4: */
+ RTUNI_ALPHA, /* U+00c9a5: */
+ RTUNI_ALPHA, /* U+00c9a6: */
+ RTUNI_ALPHA, /* U+00c9a7: */
+ RTUNI_ALPHA, /* U+00c9a8: */
+ RTUNI_ALPHA, /* U+00c9a9: */
+ RTUNI_ALPHA, /* U+00c9aa: */
+ RTUNI_ALPHA, /* U+00c9ab: */
+ RTUNI_ALPHA, /* U+00c9ac: */
+ RTUNI_ALPHA, /* U+00c9ad: */
+ RTUNI_ALPHA, /* U+00c9ae: */
+ RTUNI_ALPHA, /* U+00c9af: */
+ RTUNI_ALPHA, /* U+00c9b0: */
+ RTUNI_ALPHA, /* U+00c9b1: */
+ RTUNI_ALPHA, /* U+00c9b2: */
+ RTUNI_ALPHA, /* U+00c9b3: */
+ RTUNI_ALPHA, /* U+00c9b4: */
+ RTUNI_ALPHA, /* U+00c9b5: */
+ RTUNI_ALPHA, /* U+00c9b6: */
+ RTUNI_ALPHA, /* U+00c9b7: */
+ RTUNI_ALPHA, /* U+00c9b8: */
+ RTUNI_ALPHA, /* U+00c9b9: */
+ RTUNI_ALPHA, /* U+00c9ba: */
+ RTUNI_ALPHA, /* U+00c9bb: */
+ RTUNI_ALPHA, /* U+00c9bc: */
+ RTUNI_ALPHA, /* U+00c9bd: */
+ RTUNI_ALPHA, /* U+00c9be: */
+ RTUNI_ALPHA, /* U+00c9bf: */
+ RTUNI_ALPHA, /* U+00c9c0: */
+ RTUNI_ALPHA, /* U+00c9c1: */
+ RTUNI_ALPHA, /* U+00c9c2: */
+ RTUNI_ALPHA, /* U+00c9c3: */
+ RTUNI_ALPHA, /* U+00c9c4: */
+ RTUNI_ALPHA, /* U+00c9c5: */
+ RTUNI_ALPHA, /* U+00c9c6: */
+ RTUNI_ALPHA, /* U+00c9c7: */
+ RTUNI_ALPHA, /* U+00c9c8: */
+ RTUNI_ALPHA, /* U+00c9c9: */
+ RTUNI_ALPHA, /* U+00c9ca: */
+ RTUNI_ALPHA, /* U+00c9cb: */
+ RTUNI_ALPHA, /* U+00c9cc: */
+ RTUNI_ALPHA, /* U+00c9cd: */
+ RTUNI_ALPHA, /* U+00c9ce: */
+ RTUNI_ALPHA, /* U+00c9cf: */
+ RTUNI_ALPHA, /* U+00c9d0: */
+ RTUNI_ALPHA, /* U+00c9d1: */
+ RTUNI_ALPHA, /* U+00c9d2: */
+ RTUNI_ALPHA, /* U+00c9d3: */
+ RTUNI_ALPHA, /* U+00c9d4: */
+ RTUNI_ALPHA, /* U+00c9d5: */
+ RTUNI_ALPHA, /* U+00c9d6: */
+ RTUNI_ALPHA, /* U+00c9d7: */
+ RTUNI_ALPHA, /* U+00c9d8: */
+ RTUNI_ALPHA, /* U+00c9d9: */
+ RTUNI_ALPHA, /* U+00c9da: */
+ RTUNI_ALPHA, /* U+00c9db: */
+ RTUNI_ALPHA, /* U+00c9dc: */
+ RTUNI_ALPHA, /* U+00c9dd: */
+ RTUNI_ALPHA, /* U+00c9de: */
+ RTUNI_ALPHA, /* U+00c9df: */
+ RTUNI_ALPHA, /* U+00c9e0: */
+ RTUNI_ALPHA, /* U+00c9e1: */
+ RTUNI_ALPHA, /* U+00c9e2: */
+ RTUNI_ALPHA, /* U+00c9e3: */
+ RTUNI_ALPHA, /* U+00c9e4: */
+ RTUNI_ALPHA, /* U+00c9e5: */
+ RTUNI_ALPHA, /* U+00c9e6: */
+ RTUNI_ALPHA, /* U+00c9e7: */
+ RTUNI_ALPHA, /* U+00c9e8: */
+ RTUNI_ALPHA, /* U+00c9e9: */
+ RTUNI_ALPHA, /* U+00c9ea: */
+ RTUNI_ALPHA, /* U+00c9eb: */
+ RTUNI_ALPHA, /* U+00c9ec: */
+ RTUNI_ALPHA, /* U+00c9ed: */
+ RTUNI_ALPHA, /* U+00c9ee: */
+ RTUNI_ALPHA, /* U+00c9ef: */
+ RTUNI_ALPHA, /* U+00c9f0: */
+ RTUNI_ALPHA, /* U+00c9f1: */
+ RTUNI_ALPHA, /* U+00c9f2: */
+ RTUNI_ALPHA, /* U+00c9f3: */
+ RTUNI_ALPHA, /* U+00c9f4: */
+ RTUNI_ALPHA, /* U+00c9f5: */
+ RTUNI_ALPHA, /* U+00c9f6: */
+ RTUNI_ALPHA, /* U+00c9f7: */
+ RTUNI_ALPHA, /* U+00c9f8: */
+ RTUNI_ALPHA, /* U+00c9f9: */
+ RTUNI_ALPHA, /* U+00c9fa: */
+ RTUNI_ALPHA, /* U+00c9fb: */
+ RTUNI_ALPHA, /* U+00c9fc: */
+ RTUNI_ALPHA, /* U+00c9fd: */
+ RTUNI_ALPHA, /* U+00c9fe: */
+ RTUNI_ALPHA, /* U+00c9ff: */
+ RTUNI_ALPHA, /* U+00ca00: */
+ RTUNI_ALPHA, /* U+00ca01: */
+ RTUNI_ALPHA, /* U+00ca02: */
+ RTUNI_ALPHA, /* U+00ca03: */
+ RTUNI_ALPHA, /* U+00ca04: */
+ RTUNI_ALPHA, /* U+00ca05: */
+ RTUNI_ALPHA, /* U+00ca06: */
+ RTUNI_ALPHA, /* U+00ca07: */
+ RTUNI_ALPHA, /* U+00ca08: */
+ RTUNI_ALPHA, /* U+00ca09: */
+ RTUNI_ALPHA, /* U+00ca0a: */
+ RTUNI_ALPHA, /* U+00ca0b: */
+ RTUNI_ALPHA, /* U+00ca0c: */
+ RTUNI_ALPHA, /* U+00ca0d: */
+ RTUNI_ALPHA, /* U+00ca0e: */
+ RTUNI_ALPHA, /* U+00ca0f: */
+ RTUNI_ALPHA, /* U+00ca10: */
+ RTUNI_ALPHA, /* U+00ca11: */
+ RTUNI_ALPHA, /* U+00ca12: */
+ RTUNI_ALPHA, /* U+00ca13: */
+ RTUNI_ALPHA, /* U+00ca14: */
+ RTUNI_ALPHA, /* U+00ca15: */
+ RTUNI_ALPHA, /* U+00ca16: */
+ RTUNI_ALPHA, /* U+00ca17: */
+ RTUNI_ALPHA, /* U+00ca18: */
+ RTUNI_ALPHA, /* U+00ca19: */
+ RTUNI_ALPHA, /* U+00ca1a: */
+ RTUNI_ALPHA, /* U+00ca1b: */
+ RTUNI_ALPHA, /* U+00ca1c: */
+ RTUNI_ALPHA, /* U+00ca1d: */
+ RTUNI_ALPHA, /* U+00ca1e: */
+ RTUNI_ALPHA, /* U+00ca1f: */
+ RTUNI_ALPHA, /* U+00ca20: */
+ RTUNI_ALPHA, /* U+00ca21: */
+ RTUNI_ALPHA, /* U+00ca22: */
+ RTUNI_ALPHA, /* U+00ca23: */
+ RTUNI_ALPHA, /* U+00ca24: */
+ RTUNI_ALPHA, /* U+00ca25: */
+ RTUNI_ALPHA, /* U+00ca26: */
+ RTUNI_ALPHA, /* U+00ca27: */
+ RTUNI_ALPHA, /* U+00ca28: */
+ RTUNI_ALPHA, /* U+00ca29: */
+ RTUNI_ALPHA, /* U+00ca2a: */
+ RTUNI_ALPHA, /* U+00ca2b: */
+ RTUNI_ALPHA, /* U+00ca2c: */
+ RTUNI_ALPHA, /* U+00ca2d: */
+ RTUNI_ALPHA, /* U+00ca2e: */
+ RTUNI_ALPHA, /* U+00ca2f: */
+ RTUNI_ALPHA, /* U+00ca30: */
+ RTUNI_ALPHA, /* U+00ca31: */
+ RTUNI_ALPHA, /* U+00ca32: */
+ RTUNI_ALPHA, /* U+00ca33: */
+ RTUNI_ALPHA, /* U+00ca34: */
+ RTUNI_ALPHA, /* U+00ca35: */
+ RTUNI_ALPHA, /* U+00ca36: */
+ RTUNI_ALPHA, /* U+00ca37: */
+ RTUNI_ALPHA, /* U+00ca38: */
+ RTUNI_ALPHA, /* U+00ca39: */
+ RTUNI_ALPHA, /* U+00ca3a: */
+ RTUNI_ALPHA, /* U+00ca3b: */
+ RTUNI_ALPHA, /* U+00ca3c: */
+ RTUNI_ALPHA, /* U+00ca3d: */
+ RTUNI_ALPHA, /* U+00ca3e: */
+ RTUNI_ALPHA, /* U+00ca3f: */
+ RTUNI_ALPHA, /* U+00ca40: */
+ RTUNI_ALPHA, /* U+00ca41: */
+ RTUNI_ALPHA, /* U+00ca42: */
+ RTUNI_ALPHA, /* U+00ca43: */
+ RTUNI_ALPHA, /* U+00ca44: */
+ RTUNI_ALPHA, /* U+00ca45: */
+ RTUNI_ALPHA, /* U+00ca46: */
+ RTUNI_ALPHA, /* U+00ca47: */
+ RTUNI_ALPHA, /* U+00ca48: */
+ RTUNI_ALPHA, /* U+00ca49: */
+ RTUNI_ALPHA, /* U+00ca4a: */
+ RTUNI_ALPHA, /* U+00ca4b: */
+ RTUNI_ALPHA, /* U+00ca4c: */
+ RTUNI_ALPHA, /* U+00ca4d: */
+ RTUNI_ALPHA, /* U+00ca4e: */
+ RTUNI_ALPHA, /* U+00ca4f: */
+ RTUNI_ALPHA, /* U+00ca50: */
+ RTUNI_ALPHA, /* U+00ca51: */
+ RTUNI_ALPHA, /* U+00ca52: */
+ RTUNI_ALPHA, /* U+00ca53: */
+ RTUNI_ALPHA, /* U+00ca54: */
+ RTUNI_ALPHA, /* U+00ca55: */
+ RTUNI_ALPHA, /* U+00ca56: */
+ RTUNI_ALPHA, /* U+00ca57: */
+ RTUNI_ALPHA, /* U+00ca58: */
+ RTUNI_ALPHA, /* U+00ca59: */
+ RTUNI_ALPHA, /* U+00ca5a: */
+ RTUNI_ALPHA, /* U+00ca5b: */
+ RTUNI_ALPHA, /* U+00ca5c: */
+ RTUNI_ALPHA, /* U+00ca5d: */
+ RTUNI_ALPHA, /* U+00ca5e: */
+ RTUNI_ALPHA, /* U+00ca5f: */
+ RTUNI_ALPHA, /* U+00ca60: */
+ RTUNI_ALPHA, /* U+00ca61: */
+ RTUNI_ALPHA, /* U+00ca62: */
+ RTUNI_ALPHA, /* U+00ca63: */
+ RTUNI_ALPHA, /* U+00ca64: */
+ RTUNI_ALPHA, /* U+00ca65: */
+ RTUNI_ALPHA, /* U+00ca66: */
+ RTUNI_ALPHA, /* U+00ca67: */
+ RTUNI_ALPHA, /* U+00ca68: */
+ RTUNI_ALPHA, /* U+00ca69: */
+ RTUNI_ALPHA, /* U+00ca6a: */
+ RTUNI_ALPHA, /* U+00ca6b: */
+ RTUNI_ALPHA, /* U+00ca6c: */
+ RTUNI_ALPHA, /* U+00ca6d: */
+ RTUNI_ALPHA, /* U+00ca6e: */
+ RTUNI_ALPHA, /* U+00ca6f: */
+ RTUNI_ALPHA, /* U+00ca70: */
+ RTUNI_ALPHA, /* U+00ca71: */
+ RTUNI_ALPHA, /* U+00ca72: */
+ RTUNI_ALPHA, /* U+00ca73: */
+ RTUNI_ALPHA, /* U+00ca74: */
+ RTUNI_ALPHA, /* U+00ca75: */
+ RTUNI_ALPHA, /* U+00ca76: */
+ RTUNI_ALPHA, /* U+00ca77: */
+ RTUNI_ALPHA, /* U+00ca78: */
+ RTUNI_ALPHA, /* U+00ca79: */
+ RTUNI_ALPHA, /* U+00ca7a: */
+ RTUNI_ALPHA, /* U+00ca7b: */
+ RTUNI_ALPHA, /* U+00ca7c: */
+ RTUNI_ALPHA, /* U+00ca7d: */
+ RTUNI_ALPHA, /* U+00ca7e: */
+ RTUNI_ALPHA, /* U+00ca7f: */
+ RTUNI_ALPHA, /* U+00ca80: */
+ RTUNI_ALPHA, /* U+00ca81: */
+ RTUNI_ALPHA, /* U+00ca82: */
+ RTUNI_ALPHA, /* U+00ca83: */
+ RTUNI_ALPHA, /* U+00ca84: */
+ RTUNI_ALPHA, /* U+00ca85: */
+ RTUNI_ALPHA, /* U+00ca86: */
+ RTUNI_ALPHA, /* U+00ca87: */
+ RTUNI_ALPHA, /* U+00ca88: */
+ RTUNI_ALPHA, /* U+00ca89: */
+ RTUNI_ALPHA, /* U+00ca8a: */
+ RTUNI_ALPHA, /* U+00ca8b: */
+ RTUNI_ALPHA, /* U+00ca8c: */
+ RTUNI_ALPHA, /* U+00ca8d: */
+ RTUNI_ALPHA, /* U+00ca8e: */
+ RTUNI_ALPHA, /* U+00ca8f: */
+ RTUNI_ALPHA, /* U+00ca90: */
+ RTUNI_ALPHA, /* U+00ca91: */
+ RTUNI_ALPHA, /* U+00ca92: */
+ RTUNI_ALPHA, /* U+00ca93: */
+ RTUNI_ALPHA, /* U+00ca94: */
+ RTUNI_ALPHA, /* U+00ca95: */
+ RTUNI_ALPHA, /* U+00ca96: */
+ RTUNI_ALPHA, /* U+00ca97: */
+ RTUNI_ALPHA, /* U+00ca98: */
+ RTUNI_ALPHA, /* U+00ca99: */
+ RTUNI_ALPHA, /* U+00ca9a: */
+ RTUNI_ALPHA, /* U+00ca9b: */
+ RTUNI_ALPHA, /* U+00ca9c: */
+ RTUNI_ALPHA, /* U+00ca9d: */
+ RTUNI_ALPHA, /* U+00ca9e: */
+ RTUNI_ALPHA, /* U+00ca9f: */
+ RTUNI_ALPHA, /* U+00caa0: */
+ RTUNI_ALPHA, /* U+00caa1: */
+ RTUNI_ALPHA, /* U+00caa2: */
+ RTUNI_ALPHA, /* U+00caa3: */
+ RTUNI_ALPHA, /* U+00caa4: */
+ RTUNI_ALPHA, /* U+00caa5: */
+ RTUNI_ALPHA, /* U+00caa6: */
+ RTUNI_ALPHA, /* U+00caa7: */
+ RTUNI_ALPHA, /* U+00caa8: */
+ RTUNI_ALPHA, /* U+00caa9: */
+ RTUNI_ALPHA, /* U+00caaa: */
+ RTUNI_ALPHA, /* U+00caab: */
+ RTUNI_ALPHA, /* U+00caac: */
+ RTUNI_ALPHA, /* U+00caad: */
+ RTUNI_ALPHA, /* U+00caae: */
+ RTUNI_ALPHA, /* U+00caaf: */
+ RTUNI_ALPHA, /* U+00cab0: */
+ RTUNI_ALPHA, /* U+00cab1: */
+ RTUNI_ALPHA, /* U+00cab2: */
+ RTUNI_ALPHA, /* U+00cab3: */
+ RTUNI_ALPHA, /* U+00cab4: */
+ RTUNI_ALPHA, /* U+00cab5: */
+ RTUNI_ALPHA, /* U+00cab6: */
+ RTUNI_ALPHA, /* U+00cab7: */
+ RTUNI_ALPHA, /* U+00cab8: */
+ RTUNI_ALPHA, /* U+00cab9: */
+ RTUNI_ALPHA, /* U+00caba: */
+ RTUNI_ALPHA, /* U+00cabb: */
+ RTUNI_ALPHA, /* U+00cabc: */
+ RTUNI_ALPHA, /* U+00cabd: */
+ RTUNI_ALPHA, /* U+00cabe: */
+ RTUNI_ALPHA, /* U+00cabf: */
+ RTUNI_ALPHA, /* U+00cac0: */
+ RTUNI_ALPHA, /* U+00cac1: */
+ RTUNI_ALPHA, /* U+00cac2: */
+ RTUNI_ALPHA, /* U+00cac3: */
+ RTUNI_ALPHA, /* U+00cac4: */
+ RTUNI_ALPHA, /* U+00cac5: */
+ RTUNI_ALPHA, /* U+00cac6: */
+ RTUNI_ALPHA, /* U+00cac7: */
+ RTUNI_ALPHA, /* U+00cac8: */
+ RTUNI_ALPHA, /* U+00cac9: */
+ RTUNI_ALPHA, /* U+00caca: */
+ RTUNI_ALPHA, /* U+00cacb: */
+ RTUNI_ALPHA, /* U+00cacc: */
+ RTUNI_ALPHA, /* U+00cacd: */
+ RTUNI_ALPHA, /* U+00cace: */
+ RTUNI_ALPHA, /* U+00cacf: */
+ RTUNI_ALPHA, /* U+00cad0: */
+ RTUNI_ALPHA, /* U+00cad1: */
+ RTUNI_ALPHA, /* U+00cad2: */
+ RTUNI_ALPHA, /* U+00cad3: */
+ RTUNI_ALPHA, /* U+00cad4: */
+ RTUNI_ALPHA, /* U+00cad5: */
+ RTUNI_ALPHA, /* U+00cad6: */
+ RTUNI_ALPHA, /* U+00cad7: */
+ RTUNI_ALPHA, /* U+00cad8: */
+ RTUNI_ALPHA, /* U+00cad9: */
+ RTUNI_ALPHA, /* U+00cada: */
+ RTUNI_ALPHA, /* U+00cadb: */
+ RTUNI_ALPHA, /* U+00cadc: */
+ RTUNI_ALPHA, /* U+00cadd: */
+ RTUNI_ALPHA, /* U+00cade: */
+ RTUNI_ALPHA, /* U+00cadf: */
+ RTUNI_ALPHA, /* U+00cae0: */
+ RTUNI_ALPHA, /* U+00cae1: */
+ RTUNI_ALPHA, /* U+00cae2: */
+ RTUNI_ALPHA, /* U+00cae3: */
+ RTUNI_ALPHA, /* U+00cae4: */
+ RTUNI_ALPHA, /* U+00cae5: */
+ RTUNI_ALPHA, /* U+00cae6: */
+ RTUNI_ALPHA, /* U+00cae7: */
+ RTUNI_ALPHA, /* U+00cae8: */
+ RTUNI_ALPHA, /* U+00cae9: */
+ RTUNI_ALPHA, /* U+00caea: */
+ RTUNI_ALPHA, /* U+00caeb: */
+ RTUNI_ALPHA, /* U+00caec: */
+ RTUNI_ALPHA, /* U+00caed: */
+ RTUNI_ALPHA, /* U+00caee: */
+ RTUNI_ALPHA, /* U+00caef: */
+ RTUNI_ALPHA, /* U+00caf0: */
+ RTUNI_ALPHA, /* U+00caf1: */
+ RTUNI_ALPHA, /* U+00caf2: */
+ RTUNI_ALPHA, /* U+00caf3: */
+ RTUNI_ALPHA, /* U+00caf4: */
+ RTUNI_ALPHA, /* U+00caf5: */
+ RTUNI_ALPHA, /* U+00caf6: */
+ RTUNI_ALPHA, /* U+00caf7: */
+ RTUNI_ALPHA, /* U+00caf8: */
+ RTUNI_ALPHA, /* U+00caf9: */
+ RTUNI_ALPHA, /* U+00cafa: */
+ RTUNI_ALPHA, /* U+00cafb: */
+ RTUNI_ALPHA, /* U+00cafc: */
+ RTUNI_ALPHA, /* U+00cafd: */
+ RTUNI_ALPHA, /* U+00cafe: */
+ RTUNI_ALPHA, /* U+00caff: */
+ RTUNI_ALPHA, /* U+00cb00: */
+ RTUNI_ALPHA, /* U+00cb01: */
+ RTUNI_ALPHA, /* U+00cb02: */
+ RTUNI_ALPHA, /* U+00cb03: */
+ RTUNI_ALPHA, /* U+00cb04: */
+ RTUNI_ALPHA, /* U+00cb05: */
+ RTUNI_ALPHA, /* U+00cb06: */
+ RTUNI_ALPHA, /* U+00cb07: */
+ RTUNI_ALPHA, /* U+00cb08: */
+ RTUNI_ALPHA, /* U+00cb09: */
+ RTUNI_ALPHA, /* U+00cb0a: */
+ RTUNI_ALPHA, /* U+00cb0b: */
+ RTUNI_ALPHA, /* U+00cb0c: */
+ RTUNI_ALPHA, /* U+00cb0d: */
+ RTUNI_ALPHA, /* U+00cb0e: */
+ RTUNI_ALPHA, /* U+00cb0f: */
+ RTUNI_ALPHA, /* U+00cb10: */
+ RTUNI_ALPHA, /* U+00cb11: */
+ RTUNI_ALPHA, /* U+00cb12: */
+ RTUNI_ALPHA, /* U+00cb13: */
+ RTUNI_ALPHA, /* U+00cb14: */
+ RTUNI_ALPHA, /* U+00cb15: */
+ RTUNI_ALPHA, /* U+00cb16: */
+ RTUNI_ALPHA, /* U+00cb17: */
+ RTUNI_ALPHA, /* U+00cb18: */
+ RTUNI_ALPHA, /* U+00cb19: */
+ RTUNI_ALPHA, /* U+00cb1a: */
+ RTUNI_ALPHA, /* U+00cb1b: */
+ RTUNI_ALPHA, /* U+00cb1c: */
+ RTUNI_ALPHA, /* U+00cb1d: */
+ RTUNI_ALPHA, /* U+00cb1e: */
+ RTUNI_ALPHA, /* U+00cb1f: */
+ RTUNI_ALPHA, /* U+00cb20: */
+ RTUNI_ALPHA, /* U+00cb21: */
+ RTUNI_ALPHA, /* U+00cb22: */
+ RTUNI_ALPHA, /* U+00cb23: */
+ RTUNI_ALPHA, /* U+00cb24: */
+ RTUNI_ALPHA, /* U+00cb25: */
+ RTUNI_ALPHA, /* U+00cb26: */
+ RTUNI_ALPHA, /* U+00cb27: */
+ RTUNI_ALPHA, /* U+00cb28: */
+ RTUNI_ALPHA, /* U+00cb29: */
+ RTUNI_ALPHA, /* U+00cb2a: */
+ RTUNI_ALPHA, /* U+00cb2b: */
+ RTUNI_ALPHA, /* U+00cb2c: */
+ RTUNI_ALPHA, /* U+00cb2d: */
+ RTUNI_ALPHA, /* U+00cb2e: */
+ RTUNI_ALPHA, /* U+00cb2f: */
+ RTUNI_ALPHA, /* U+00cb30: */
+ RTUNI_ALPHA, /* U+00cb31: */
+ RTUNI_ALPHA, /* U+00cb32: */
+ RTUNI_ALPHA, /* U+00cb33: */
+ RTUNI_ALPHA, /* U+00cb34: */
+ RTUNI_ALPHA, /* U+00cb35: */
+ RTUNI_ALPHA, /* U+00cb36: */
+ RTUNI_ALPHA, /* U+00cb37: */
+ RTUNI_ALPHA, /* U+00cb38: */
+ RTUNI_ALPHA, /* U+00cb39: */
+ RTUNI_ALPHA, /* U+00cb3a: */
+ RTUNI_ALPHA, /* U+00cb3b: */
+ RTUNI_ALPHA, /* U+00cb3c: */
+ RTUNI_ALPHA, /* U+00cb3d: */
+ RTUNI_ALPHA, /* U+00cb3e: */
+ RTUNI_ALPHA, /* U+00cb3f: */
+ RTUNI_ALPHA, /* U+00cb40: */
+ RTUNI_ALPHA, /* U+00cb41: */
+ RTUNI_ALPHA, /* U+00cb42: */
+ RTUNI_ALPHA, /* U+00cb43: */
+ RTUNI_ALPHA, /* U+00cb44: */
+ RTUNI_ALPHA, /* U+00cb45: */
+ RTUNI_ALPHA, /* U+00cb46: */
+ RTUNI_ALPHA, /* U+00cb47: */
+ RTUNI_ALPHA, /* U+00cb48: */
+ RTUNI_ALPHA, /* U+00cb49: */
+ RTUNI_ALPHA, /* U+00cb4a: */
+ RTUNI_ALPHA, /* U+00cb4b: */
+ RTUNI_ALPHA, /* U+00cb4c: */
+ RTUNI_ALPHA, /* U+00cb4d: */
+ RTUNI_ALPHA, /* U+00cb4e: */
+ RTUNI_ALPHA, /* U+00cb4f: */
+ RTUNI_ALPHA, /* U+00cb50: */
+ RTUNI_ALPHA, /* U+00cb51: */
+ RTUNI_ALPHA, /* U+00cb52: */
+ RTUNI_ALPHA, /* U+00cb53: */
+ RTUNI_ALPHA, /* U+00cb54: */
+ RTUNI_ALPHA, /* U+00cb55: */
+ RTUNI_ALPHA, /* U+00cb56: */
+ RTUNI_ALPHA, /* U+00cb57: */
+ RTUNI_ALPHA, /* U+00cb58: */
+ RTUNI_ALPHA, /* U+00cb59: */
+ RTUNI_ALPHA, /* U+00cb5a: */
+ RTUNI_ALPHA, /* U+00cb5b: */
+ RTUNI_ALPHA, /* U+00cb5c: */
+ RTUNI_ALPHA, /* U+00cb5d: */
+ RTUNI_ALPHA, /* U+00cb5e: */
+ RTUNI_ALPHA, /* U+00cb5f: */
+ RTUNI_ALPHA, /* U+00cb60: */
+ RTUNI_ALPHA, /* U+00cb61: */
+ RTUNI_ALPHA, /* U+00cb62: */
+ RTUNI_ALPHA, /* U+00cb63: */
+ RTUNI_ALPHA, /* U+00cb64: */
+ RTUNI_ALPHA, /* U+00cb65: */
+ RTUNI_ALPHA, /* U+00cb66: */
+ RTUNI_ALPHA, /* U+00cb67: */
+ RTUNI_ALPHA, /* U+00cb68: */
+ RTUNI_ALPHA, /* U+00cb69: */
+ RTUNI_ALPHA, /* U+00cb6a: */
+ RTUNI_ALPHA, /* U+00cb6b: */
+ RTUNI_ALPHA, /* U+00cb6c: */
+ RTUNI_ALPHA, /* U+00cb6d: */
+ RTUNI_ALPHA, /* U+00cb6e: */
+ RTUNI_ALPHA, /* U+00cb6f: */
+ RTUNI_ALPHA, /* U+00cb70: */
+ RTUNI_ALPHA, /* U+00cb71: */
+ RTUNI_ALPHA, /* U+00cb72: */
+ RTUNI_ALPHA, /* U+00cb73: */
+ RTUNI_ALPHA, /* U+00cb74: */
+ RTUNI_ALPHA, /* U+00cb75: */
+ RTUNI_ALPHA, /* U+00cb76: */
+ RTUNI_ALPHA, /* U+00cb77: */
+ RTUNI_ALPHA, /* U+00cb78: */
+ RTUNI_ALPHA, /* U+00cb79: */
+ RTUNI_ALPHA, /* U+00cb7a: */
+ RTUNI_ALPHA, /* U+00cb7b: */
+ RTUNI_ALPHA, /* U+00cb7c: */
+ RTUNI_ALPHA, /* U+00cb7d: */
+ RTUNI_ALPHA, /* U+00cb7e: */
+ RTUNI_ALPHA, /* U+00cb7f: */
+ RTUNI_ALPHA, /* U+00cb80: */
+ RTUNI_ALPHA, /* U+00cb81: */
+ RTUNI_ALPHA, /* U+00cb82: */
+ RTUNI_ALPHA, /* U+00cb83: */
+ RTUNI_ALPHA, /* U+00cb84: */
+ RTUNI_ALPHA, /* U+00cb85: */
+ RTUNI_ALPHA, /* U+00cb86: */
+ RTUNI_ALPHA, /* U+00cb87: */
+ RTUNI_ALPHA, /* U+00cb88: */
+ RTUNI_ALPHA, /* U+00cb89: */
+ RTUNI_ALPHA, /* U+00cb8a: */
+ RTUNI_ALPHA, /* U+00cb8b: */
+ RTUNI_ALPHA, /* U+00cb8c: */
+ RTUNI_ALPHA, /* U+00cb8d: */
+ RTUNI_ALPHA, /* U+00cb8e: */
+ RTUNI_ALPHA, /* U+00cb8f: */
+ RTUNI_ALPHA, /* U+00cb90: */
+ RTUNI_ALPHA, /* U+00cb91: */
+ RTUNI_ALPHA, /* U+00cb92: */
+ RTUNI_ALPHA, /* U+00cb93: */
+ RTUNI_ALPHA, /* U+00cb94: */
+ RTUNI_ALPHA, /* U+00cb95: */
+ RTUNI_ALPHA, /* U+00cb96: */
+ RTUNI_ALPHA, /* U+00cb97: */
+ RTUNI_ALPHA, /* U+00cb98: */
+ RTUNI_ALPHA, /* U+00cb99: */
+ RTUNI_ALPHA, /* U+00cb9a: */
+ RTUNI_ALPHA, /* U+00cb9b: */
+ RTUNI_ALPHA, /* U+00cb9c: */
+ RTUNI_ALPHA, /* U+00cb9d: */
+ RTUNI_ALPHA, /* U+00cb9e: */
+ RTUNI_ALPHA, /* U+00cb9f: */
+ RTUNI_ALPHA, /* U+00cba0: */
+ RTUNI_ALPHA, /* U+00cba1: */
+ RTUNI_ALPHA, /* U+00cba2: */
+ RTUNI_ALPHA, /* U+00cba3: */
+ RTUNI_ALPHA, /* U+00cba4: */
+ RTUNI_ALPHA, /* U+00cba5: */
+ RTUNI_ALPHA, /* U+00cba6: */
+ RTUNI_ALPHA, /* U+00cba7: */
+ RTUNI_ALPHA, /* U+00cba8: */
+ RTUNI_ALPHA, /* U+00cba9: */
+ RTUNI_ALPHA, /* U+00cbaa: */
+ RTUNI_ALPHA, /* U+00cbab: */
+ RTUNI_ALPHA, /* U+00cbac: */
+ RTUNI_ALPHA, /* U+00cbad: */
+ RTUNI_ALPHA, /* U+00cbae: */
+ RTUNI_ALPHA, /* U+00cbaf: */
+ RTUNI_ALPHA, /* U+00cbb0: */
+ RTUNI_ALPHA, /* U+00cbb1: */
+ RTUNI_ALPHA, /* U+00cbb2: */
+ RTUNI_ALPHA, /* U+00cbb3: */
+ RTUNI_ALPHA, /* U+00cbb4: */
+ RTUNI_ALPHA, /* U+00cbb5: */
+ RTUNI_ALPHA, /* U+00cbb6: */
+ RTUNI_ALPHA, /* U+00cbb7: */
+ RTUNI_ALPHA, /* U+00cbb8: */
+ RTUNI_ALPHA, /* U+00cbb9: */
+ RTUNI_ALPHA, /* U+00cbba: */
+ RTUNI_ALPHA, /* U+00cbbb: */
+ RTUNI_ALPHA, /* U+00cbbc: */
+ RTUNI_ALPHA, /* U+00cbbd: */
+ RTUNI_ALPHA, /* U+00cbbe: */
+ RTUNI_ALPHA, /* U+00cbbf: */
+ RTUNI_ALPHA, /* U+00cbc0: */
+ RTUNI_ALPHA, /* U+00cbc1: */
+ RTUNI_ALPHA, /* U+00cbc2: */
+ RTUNI_ALPHA, /* U+00cbc3: */
+ RTUNI_ALPHA, /* U+00cbc4: */
+ RTUNI_ALPHA, /* U+00cbc5: */
+ RTUNI_ALPHA, /* U+00cbc6: */
+ RTUNI_ALPHA, /* U+00cbc7: */
+ RTUNI_ALPHA, /* U+00cbc8: */
+ RTUNI_ALPHA, /* U+00cbc9: */
+ RTUNI_ALPHA, /* U+00cbca: */
+ RTUNI_ALPHA, /* U+00cbcb: */
+ RTUNI_ALPHA, /* U+00cbcc: */
+ RTUNI_ALPHA, /* U+00cbcd: */
+ RTUNI_ALPHA, /* U+00cbce: */
+ RTUNI_ALPHA, /* U+00cbcf: */
+ RTUNI_ALPHA, /* U+00cbd0: */
+ RTUNI_ALPHA, /* U+00cbd1: */
+ RTUNI_ALPHA, /* U+00cbd2: */
+ RTUNI_ALPHA, /* U+00cbd3: */
+ RTUNI_ALPHA, /* U+00cbd4: */
+ RTUNI_ALPHA, /* U+00cbd5: */
+ RTUNI_ALPHA, /* U+00cbd6: */
+ RTUNI_ALPHA, /* U+00cbd7: */
+ RTUNI_ALPHA, /* U+00cbd8: */
+ RTUNI_ALPHA, /* U+00cbd9: */
+ RTUNI_ALPHA, /* U+00cbda: */
+ RTUNI_ALPHA, /* U+00cbdb: */
+ RTUNI_ALPHA, /* U+00cbdc: */
+ RTUNI_ALPHA, /* U+00cbdd: */
+ RTUNI_ALPHA, /* U+00cbde: */
+ RTUNI_ALPHA, /* U+00cbdf: */
+ RTUNI_ALPHA, /* U+00cbe0: */
+ RTUNI_ALPHA, /* U+00cbe1: */
+ RTUNI_ALPHA, /* U+00cbe2: */
+ RTUNI_ALPHA, /* U+00cbe3: */
+ RTUNI_ALPHA, /* U+00cbe4: */
+ RTUNI_ALPHA, /* U+00cbe5: */
+ RTUNI_ALPHA, /* U+00cbe6: */
+ RTUNI_ALPHA, /* U+00cbe7: */
+ RTUNI_ALPHA, /* U+00cbe8: */
+ RTUNI_ALPHA, /* U+00cbe9: */
+ RTUNI_ALPHA, /* U+00cbea: */
+ RTUNI_ALPHA, /* U+00cbeb: */
+ RTUNI_ALPHA, /* U+00cbec: */
+ RTUNI_ALPHA, /* U+00cbed: */
+ RTUNI_ALPHA, /* U+00cbee: */
+ RTUNI_ALPHA, /* U+00cbef: */
+ RTUNI_ALPHA, /* U+00cbf0: */
+ RTUNI_ALPHA, /* U+00cbf1: */
+ RTUNI_ALPHA, /* U+00cbf2: */
+ RTUNI_ALPHA, /* U+00cbf3: */
+ RTUNI_ALPHA, /* U+00cbf4: */
+ RTUNI_ALPHA, /* U+00cbf5: */
+ RTUNI_ALPHA, /* U+00cbf6: */
+ RTUNI_ALPHA, /* U+00cbf7: */
+ RTUNI_ALPHA, /* U+00cbf8: */
+ RTUNI_ALPHA, /* U+00cbf9: */
+ RTUNI_ALPHA, /* U+00cbfa: */
+ RTUNI_ALPHA, /* U+00cbfb: */
+ RTUNI_ALPHA, /* U+00cbfc: */
+ RTUNI_ALPHA, /* U+00cbfd: */
+ RTUNI_ALPHA, /* U+00cbfe: */
+ RTUNI_ALPHA, /* U+00cbff: */
+ RTUNI_ALPHA, /* U+00cc00: */
+ RTUNI_ALPHA, /* U+00cc01: */
+ RTUNI_ALPHA, /* U+00cc02: */
+ RTUNI_ALPHA, /* U+00cc03: */
+ RTUNI_ALPHA, /* U+00cc04: */
+ RTUNI_ALPHA, /* U+00cc05: */
+ RTUNI_ALPHA, /* U+00cc06: */
+ RTUNI_ALPHA, /* U+00cc07: */
+ RTUNI_ALPHA, /* U+00cc08: */
+ RTUNI_ALPHA, /* U+00cc09: */
+ RTUNI_ALPHA, /* U+00cc0a: */
+ RTUNI_ALPHA, /* U+00cc0b: */
+ RTUNI_ALPHA, /* U+00cc0c: */
+ RTUNI_ALPHA, /* U+00cc0d: */
+ RTUNI_ALPHA, /* U+00cc0e: */
+ RTUNI_ALPHA, /* U+00cc0f: */
+ RTUNI_ALPHA, /* U+00cc10: */
+ RTUNI_ALPHA, /* U+00cc11: */
+ RTUNI_ALPHA, /* U+00cc12: */
+ RTUNI_ALPHA, /* U+00cc13: */
+ RTUNI_ALPHA, /* U+00cc14: */
+ RTUNI_ALPHA, /* U+00cc15: */
+ RTUNI_ALPHA, /* U+00cc16: */
+ RTUNI_ALPHA, /* U+00cc17: */
+ RTUNI_ALPHA, /* U+00cc18: */
+ RTUNI_ALPHA, /* U+00cc19: */
+ RTUNI_ALPHA, /* U+00cc1a: */
+ RTUNI_ALPHA, /* U+00cc1b: */
+ RTUNI_ALPHA, /* U+00cc1c: */
+ RTUNI_ALPHA, /* U+00cc1d: */
+ RTUNI_ALPHA, /* U+00cc1e: */
+ RTUNI_ALPHA, /* U+00cc1f: */
+ RTUNI_ALPHA, /* U+00cc20: */
+ RTUNI_ALPHA, /* U+00cc21: */
+ RTUNI_ALPHA, /* U+00cc22: */
+ RTUNI_ALPHA, /* U+00cc23: */
+ RTUNI_ALPHA, /* U+00cc24: */
+ RTUNI_ALPHA, /* U+00cc25: */
+ RTUNI_ALPHA, /* U+00cc26: */
+ RTUNI_ALPHA, /* U+00cc27: */
+ RTUNI_ALPHA, /* U+00cc28: */
+ RTUNI_ALPHA, /* U+00cc29: */
+ RTUNI_ALPHA, /* U+00cc2a: */
+ RTUNI_ALPHA, /* U+00cc2b: */
+ RTUNI_ALPHA, /* U+00cc2c: */
+ RTUNI_ALPHA, /* U+00cc2d: */
+ RTUNI_ALPHA, /* U+00cc2e: */
+ RTUNI_ALPHA, /* U+00cc2f: */
+ RTUNI_ALPHA, /* U+00cc30: */
+ RTUNI_ALPHA, /* U+00cc31: */
+ RTUNI_ALPHA, /* U+00cc32: */
+ RTUNI_ALPHA, /* U+00cc33: */
+ RTUNI_ALPHA, /* U+00cc34: */
+ RTUNI_ALPHA, /* U+00cc35: */
+ RTUNI_ALPHA, /* U+00cc36: */
+ RTUNI_ALPHA, /* U+00cc37: */
+ RTUNI_ALPHA, /* U+00cc38: */
+ RTUNI_ALPHA, /* U+00cc39: */
+ RTUNI_ALPHA, /* U+00cc3a: */
+ RTUNI_ALPHA, /* U+00cc3b: */
+ RTUNI_ALPHA, /* U+00cc3c: */
+ RTUNI_ALPHA, /* U+00cc3d: */
+ RTUNI_ALPHA, /* U+00cc3e: */
+ RTUNI_ALPHA, /* U+00cc3f: */
+ RTUNI_ALPHA, /* U+00cc40: */
+ RTUNI_ALPHA, /* U+00cc41: */
+ RTUNI_ALPHA, /* U+00cc42: */
+ RTUNI_ALPHA, /* U+00cc43: */
+ RTUNI_ALPHA, /* U+00cc44: */
+ RTUNI_ALPHA, /* U+00cc45: */
+ RTUNI_ALPHA, /* U+00cc46: */
+ RTUNI_ALPHA, /* U+00cc47: */
+ RTUNI_ALPHA, /* U+00cc48: */
+ RTUNI_ALPHA, /* U+00cc49: */
+ RTUNI_ALPHA, /* U+00cc4a: */
+ RTUNI_ALPHA, /* U+00cc4b: */
+ RTUNI_ALPHA, /* U+00cc4c: */
+ RTUNI_ALPHA, /* U+00cc4d: */
+ RTUNI_ALPHA, /* U+00cc4e: */
+ RTUNI_ALPHA, /* U+00cc4f: */
+ RTUNI_ALPHA, /* U+00cc50: */
+ RTUNI_ALPHA, /* U+00cc51: */
+ RTUNI_ALPHA, /* U+00cc52: */
+ RTUNI_ALPHA, /* U+00cc53: */
+ RTUNI_ALPHA, /* U+00cc54: */
+ RTUNI_ALPHA, /* U+00cc55: */
+ RTUNI_ALPHA, /* U+00cc56: */
+ RTUNI_ALPHA, /* U+00cc57: */
+ RTUNI_ALPHA, /* U+00cc58: */
+ RTUNI_ALPHA, /* U+00cc59: */
+ RTUNI_ALPHA, /* U+00cc5a: */
+ RTUNI_ALPHA, /* U+00cc5b: */
+ RTUNI_ALPHA, /* U+00cc5c: */
+ RTUNI_ALPHA, /* U+00cc5d: */
+ RTUNI_ALPHA, /* U+00cc5e: */
+ RTUNI_ALPHA, /* U+00cc5f: */
+ RTUNI_ALPHA, /* U+00cc60: */
+ RTUNI_ALPHA, /* U+00cc61: */
+ RTUNI_ALPHA, /* U+00cc62: */
+ RTUNI_ALPHA, /* U+00cc63: */
+ RTUNI_ALPHA, /* U+00cc64: */
+ RTUNI_ALPHA, /* U+00cc65: */
+ RTUNI_ALPHA, /* U+00cc66: */
+ RTUNI_ALPHA, /* U+00cc67: */
+ RTUNI_ALPHA, /* U+00cc68: */
+ RTUNI_ALPHA, /* U+00cc69: */
+ RTUNI_ALPHA, /* U+00cc6a: */
+ RTUNI_ALPHA, /* U+00cc6b: */
+ RTUNI_ALPHA, /* U+00cc6c: */
+ RTUNI_ALPHA, /* U+00cc6d: */
+ RTUNI_ALPHA, /* U+00cc6e: */
+ RTUNI_ALPHA, /* U+00cc6f: */
+ RTUNI_ALPHA, /* U+00cc70: */
+ RTUNI_ALPHA, /* U+00cc71: */
+ RTUNI_ALPHA, /* U+00cc72: */
+ RTUNI_ALPHA, /* U+00cc73: */
+ RTUNI_ALPHA, /* U+00cc74: */
+ RTUNI_ALPHA, /* U+00cc75: */
+ RTUNI_ALPHA, /* U+00cc76: */
+ RTUNI_ALPHA, /* U+00cc77: */
+ RTUNI_ALPHA, /* U+00cc78: */
+ RTUNI_ALPHA, /* U+00cc79: */
+ RTUNI_ALPHA, /* U+00cc7a: */
+ RTUNI_ALPHA, /* U+00cc7b: */
+ RTUNI_ALPHA, /* U+00cc7c: */
+ RTUNI_ALPHA, /* U+00cc7d: */
+ RTUNI_ALPHA, /* U+00cc7e: */
+ RTUNI_ALPHA, /* U+00cc7f: */
+ RTUNI_ALPHA, /* U+00cc80: */
+ RTUNI_ALPHA, /* U+00cc81: */
+ RTUNI_ALPHA, /* U+00cc82: */
+ RTUNI_ALPHA, /* U+00cc83: */
+ RTUNI_ALPHA, /* U+00cc84: */
+ RTUNI_ALPHA, /* U+00cc85: */
+ RTUNI_ALPHA, /* U+00cc86: */
+ RTUNI_ALPHA, /* U+00cc87: */
+ RTUNI_ALPHA, /* U+00cc88: */
+ RTUNI_ALPHA, /* U+00cc89: */
+ RTUNI_ALPHA, /* U+00cc8a: */
+ RTUNI_ALPHA, /* U+00cc8b: */
+ RTUNI_ALPHA, /* U+00cc8c: */
+ RTUNI_ALPHA, /* U+00cc8d: */
+ RTUNI_ALPHA, /* U+00cc8e: */
+ RTUNI_ALPHA, /* U+00cc8f: */
+ RTUNI_ALPHA, /* U+00cc90: */
+ RTUNI_ALPHA, /* U+00cc91: */
+ RTUNI_ALPHA, /* U+00cc92: */
+ RTUNI_ALPHA, /* U+00cc93: */
+ RTUNI_ALPHA, /* U+00cc94: */
+ RTUNI_ALPHA, /* U+00cc95: */
+ RTUNI_ALPHA, /* U+00cc96: */
+ RTUNI_ALPHA, /* U+00cc97: */
+ RTUNI_ALPHA, /* U+00cc98: */
+ RTUNI_ALPHA, /* U+00cc99: */
+ RTUNI_ALPHA, /* U+00cc9a: */
+ RTUNI_ALPHA, /* U+00cc9b: */
+ RTUNI_ALPHA, /* U+00cc9c: */
+ RTUNI_ALPHA, /* U+00cc9d: */
+ RTUNI_ALPHA, /* U+00cc9e: */
+ RTUNI_ALPHA, /* U+00cc9f: */
+ RTUNI_ALPHA, /* U+00cca0: */
+ RTUNI_ALPHA, /* U+00cca1: */
+ RTUNI_ALPHA, /* U+00cca2: */
+ RTUNI_ALPHA, /* U+00cca3: */
+ RTUNI_ALPHA, /* U+00cca4: */
+ RTUNI_ALPHA, /* U+00cca5: */
+ RTUNI_ALPHA, /* U+00cca6: */
+ RTUNI_ALPHA, /* U+00cca7: */
+ RTUNI_ALPHA, /* U+00cca8: */
+ RTUNI_ALPHA, /* U+00cca9: */
+ RTUNI_ALPHA, /* U+00ccaa: */
+ RTUNI_ALPHA, /* U+00ccab: */
+ RTUNI_ALPHA, /* U+00ccac: */
+ RTUNI_ALPHA, /* U+00ccad: */
+ RTUNI_ALPHA, /* U+00ccae: */
+ RTUNI_ALPHA, /* U+00ccaf: */
+ RTUNI_ALPHA, /* U+00ccb0: */
+ RTUNI_ALPHA, /* U+00ccb1: */
+ RTUNI_ALPHA, /* U+00ccb2: */
+ RTUNI_ALPHA, /* U+00ccb3: */
+ RTUNI_ALPHA, /* U+00ccb4: */
+ RTUNI_ALPHA, /* U+00ccb5: */
+ RTUNI_ALPHA, /* U+00ccb6: */
+ RTUNI_ALPHA, /* U+00ccb7: */
+ RTUNI_ALPHA, /* U+00ccb8: */
+ RTUNI_ALPHA, /* U+00ccb9: */
+ RTUNI_ALPHA, /* U+00ccba: */
+ RTUNI_ALPHA, /* U+00ccbb: */
+ RTUNI_ALPHA, /* U+00ccbc: */
+ RTUNI_ALPHA, /* U+00ccbd: */
+ RTUNI_ALPHA, /* U+00ccbe: */
+ RTUNI_ALPHA, /* U+00ccbf: */
+ RTUNI_ALPHA, /* U+00ccc0: */
+ RTUNI_ALPHA, /* U+00ccc1: */
+ RTUNI_ALPHA, /* U+00ccc2: */
+ RTUNI_ALPHA, /* U+00ccc3: */
+ RTUNI_ALPHA, /* U+00ccc4: */
+ RTUNI_ALPHA, /* U+00ccc5: */
+ RTUNI_ALPHA, /* U+00ccc6: */
+ RTUNI_ALPHA, /* U+00ccc7: */
+ RTUNI_ALPHA, /* U+00ccc8: */
+ RTUNI_ALPHA, /* U+00ccc9: */
+ RTUNI_ALPHA, /* U+00ccca: */
+ RTUNI_ALPHA, /* U+00cccb: */
+ RTUNI_ALPHA, /* U+00cccc: */
+ RTUNI_ALPHA, /* U+00cccd: */
+ RTUNI_ALPHA, /* U+00ccce: */
+ RTUNI_ALPHA, /* U+00cccf: */
+ RTUNI_ALPHA, /* U+00ccd0: */
+ RTUNI_ALPHA, /* U+00ccd1: */
+ RTUNI_ALPHA, /* U+00ccd2: */
+ RTUNI_ALPHA, /* U+00ccd3: */
+ RTUNI_ALPHA, /* U+00ccd4: */
+ RTUNI_ALPHA, /* U+00ccd5: */
+ RTUNI_ALPHA, /* U+00ccd6: */
+ RTUNI_ALPHA, /* U+00ccd7: */
+ RTUNI_ALPHA, /* U+00ccd8: */
+ RTUNI_ALPHA, /* U+00ccd9: */
+ RTUNI_ALPHA, /* U+00ccda: */
+ RTUNI_ALPHA, /* U+00ccdb: */
+ RTUNI_ALPHA, /* U+00ccdc: */
+ RTUNI_ALPHA, /* U+00ccdd: */
+ RTUNI_ALPHA, /* U+00ccde: */
+ RTUNI_ALPHA, /* U+00ccdf: */
+ RTUNI_ALPHA, /* U+00cce0: */
+ RTUNI_ALPHA, /* U+00cce1: */
+ RTUNI_ALPHA, /* U+00cce2: */
+ RTUNI_ALPHA, /* U+00cce3: */
+ RTUNI_ALPHA, /* U+00cce4: */
+ RTUNI_ALPHA, /* U+00cce5: */
+ RTUNI_ALPHA, /* U+00cce6: */
+ RTUNI_ALPHA, /* U+00cce7: */
+ RTUNI_ALPHA, /* U+00cce8: */
+ RTUNI_ALPHA, /* U+00cce9: */
+ RTUNI_ALPHA, /* U+00ccea: */
+ RTUNI_ALPHA, /* U+00cceb: */
+ RTUNI_ALPHA, /* U+00ccec: */
+ RTUNI_ALPHA, /* U+00cced: */
+ RTUNI_ALPHA, /* U+00ccee: */
+ RTUNI_ALPHA, /* U+00ccef: */
+ RTUNI_ALPHA, /* U+00ccf0: */
+ RTUNI_ALPHA, /* U+00ccf1: */
+ RTUNI_ALPHA, /* U+00ccf2: */
+ RTUNI_ALPHA, /* U+00ccf3: */
+ RTUNI_ALPHA, /* U+00ccf4: */
+ RTUNI_ALPHA, /* U+00ccf5: */
+ RTUNI_ALPHA, /* U+00ccf6: */
+ RTUNI_ALPHA, /* U+00ccf7: */
+ RTUNI_ALPHA, /* U+00ccf8: */
+ RTUNI_ALPHA, /* U+00ccf9: */
+ RTUNI_ALPHA, /* U+00ccfa: */
+ RTUNI_ALPHA, /* U+00ccfb: */
+ RTUNI_ALPHA, /* U+00ccfc: */
+ RTUNI_ALPHA, /* U+00ccfd: */
+ RTUNI_ALPHA, /* U+00ccfe: */
+ RTUNI_ALPHA, /* U+00ccff: */
+ RTUNI_ALPHA, /* U+00cd00: */
+ RTUNI_ALPHA, /* U+00cd01: */
+ RTUNI_ALPHA, /* U+00cd02: */
+ RTUNI_ALPHA, /* U+00cd03: */
+ RTUNI_ALPHA, /* U+00cd04: */
+ RTUNI_ALPHA, /* U+00cd05: */
+ RTUNI_ALPHA, /* U+00cd06: */
+ RTUNI_ALPHA, /* U+00cd07: */
+ RTUNI_ALPHA, /* U+00cd08: */
+ RTUNI_ALPHA, /* U+00cd09: */
+ RTUNI_ALPHA, /* U+00cd0a: */
+ RTUNI_ALPHA, /* U+00cd0b: */
+ RTUNI_ALPHA, /* U+00cd0c: */
+ RTUNI_ALPHA, /* U+00cd0d: */
+ RTUNI_ALPHA, /* U+00cd0e: */
+ RTUNI_ALPHA, /* U+00cd0f: */
+ RTUNI_ALPHA, /* U+00cd10: */
+ RTUNI_ALPHA, /* U+00cd11: */
+ RTUNI_ALPHA, /* U+00cd12: */
+ RTUNI_ALPHA, /* U+00cd13: */
+ RTUNI_ALPHA, /* U+00cd14: */
+ RTUNI_ALPHA, /* U+00cd15: */
+ RTUNI_ALPHA, /* U+00cd16: */
+ RTUNI_ALPHA, /* U+00cd17: */
+ RTUNI_ALPHA, /* U+00cd18: */
+ RTUNI_ALPHA, /* U+00cd19: */
+ RTUNI_ALPHA, /* U+00cd1a: */
+ RTUNI_ALPHA, /* U+00cd1b: */
+ RTUNI_ALPHA, /* U+00cd1c: */
+ RTUNI_ALPHA, /* U+00cd1d: */
+ RTUNI_ALPHA, /* U+00cd1e: */
+ RTUNI_ALPHA, /* U+00cd1f: */
+ RTUNI_ALPHA, /* U+00cd20: */
+ RTUNI_ALPHA, /* U+00cd21: */
+ RTUNI_ALPHA, /* U+00cd22: */
+ RTUNI_ALPHA, /* U+00cd23: */
+ RTUNI_ALPHA, /* U+00cd24: */
+ RTUNI_ALPHA, /* U+00cd25: */
+ RTUNI_ALPHA, /* U+00cd26: */
+ RTUNI_ALPHA, /* U+00cd27: */
+ RTUNI_ALPHA, /* U+00cd28: */
+ RTUNI_ALPHA, /* U+00cd29: */
+ RTUNI_ALPHA, /* U+00cd2a: */
+ RTUNI_ALPHA, /* U+00cd2b: */
+ RTUNI_ALPHA, /* U+00cd2c: */
+ RTUNI_ALPHA, /* U+00cd2d: */
+ RTUNI_ALPHA, /* U+00cd2e: */
+ RTUNI_ALPHA, /* U+00cd2f: */
+ RTUNI_ALPHA, /* U+00cd30: */
+ RTUNI_ALPHA, /* U+00cd31: */
+ RTUNI_ALPHA, /* U+00cd32: */
+ RTUNI_ALPHA, /* U+00cd33: */
+ RTUNI_ALPHA, /* U+00cd34: */
+ RTUNI_ALPHA, /* U+00cd35: */
+ RTUNI_ALPHA, /* U+00cd36: */
+ RTUNI_ALPHA, /* U+00cd37: */
+ RTUNI_ALPHA, /* U+00cd38: */
+ RTUNI_ALPHA, /* U+00cd39: */
+ RTUNI_ALPHA, /* U+00cd3a: */
+ RTUNI_ALPHA, /* U+00cd3b: */
+ RTUNI_ALPHA, /* U+00cd3c: */
+ RTUNI_ALPHA, /* U+00cd3d: */
+ RTUNI_ALPHA, /* U+00cd3e: */
+ RTUNI_ALPHA, /* U+00cd3f: */
+ RTUNI_ALPHA, /* U+00cd40: */
+ RTUNI_ALPHA, /* U+00cd41: */
+ RTUNI_ALPHA, /* U+00cd42: */
+ RTUNI_ALPHA, /* U+00cd43: */
+ RTUNI_ALPHA, /* U+00cd44: */
+ RTUNI_ALPHA, /* U+00cd45: */
+ RTUNI_ALPHA, /* U+00cd46: */
+ RTUNI_ALPHA, /* U+00cd47: */
+ RTUNI_ALPHA, /* U+00cd48: */
+ RTUNI_ALPHA, /* U+00cd49: */
+ RTUNI_ALPHA, /* U+00cd4a: */
+ RTUNI_ALPHA, /* U+00cd4b: */
+ RTUNI_ALPHA, /* U+00cd4c: */
+ RTUNI_ALPHA, /* U+00cd4d: */
+ RTUNI_ALPHA, /* U+00cd4e: */
+ RTUNI_ALPHA, /* U+00cd4f: */
+ RTUNI_ALPHA, /* U+00cd50: */
+ RTUNI_ALPHA, /* U+00cd51: */
+ RTUNI_ALPHA, /* U+00cd52: */
+ RTUNI_ALPHA, /* U+00cd53: */
+ RTUNI_ALPHA, /* U+00cd54: */
+ RTUNI_ALPHA, /* U+00cd55: */
+ RTUNI_ALPHA, /* U+00cd56: */
+ RTUNI_ALPHA, /* U+00cd57: */
+ RTUNI_ALPHA, /* U+00cd58: */
+ RTUNI_ALPHA, /* U+00cd59: */
+ RTUNI_ALPHA, /* U+00cd5a: */
+ RTUNI_ALPHA, /* U+00cd5b: */
+ RTUNI_ALPHA, /* U+00cd5c: */
+ RTUNI_ALPHA, /* U+00cd5d: */
+ RTUNI_ALPHA, /* U+00cd5e: */
+ RTUNI_ALPHA, /* U+00cd5f: */
+ RTUNI_ALPHA, /* U+00cd60: */
+ RTUNI_ALPHA, /* U+00cd61: */
+ RTUNI_ALPHA, /* U+00cd62: */
+ RTUNI_ALPHA, /* U+00cd63: */
+ RTUNI_ALPHA, /* U+00cd64: */
+ RTUNI_ALPHA, /* U+00cd65: */
+ RTUNI_ALPHA, /* U+00cd66: */
+ RTUNI_ALPHA, /* U+00cd67: */
+ RTUNI_ALPHA, /* U+00cd68: */
+ RTUNI_ALPHA, /* U+00cd69: */
+ RTUNI_ALPHA, /* U+00cd6a: */
+ RTUNI_ALPHA, /* U+00cd6b: */
+ RTUNI_ALPHA, /* U+00cd6c: */
+ RTUNI_ALPHA, /* U+00cd6d: */
+ RTUNI_ALPHA, /* U+00cd6e: */
+ RTUNI_ALPHA, /* U+00cd6f: */
+ RTUNI_ALPHA, /* U+00cd70: */
+ RTUNI_ALPHA, /* U+00cd71: */
+ RTUNI_ALPHA, /* U+00cd72: */
+ RTUNI_ALPHA, /* U+00cd73: */
+ RTUNI_ALPHA, /* U+00cd74: */
+ RTUNI_ALPHA, /* U+00cd75: */
+ RTUNI_ALPHA, /* U+00cd76: */
+ RTUNI_ALPHA, /* U+00cd77: */
+ RTUNI_ALPHA, /* U+00cd78: */
+ RTUNI_ALPHA, /* U+00cd79: */
+ RTUNI_ALPHA, /* U+00cd7a: */
+ RTUNI_ALPHA, /* U+00cd7b: */
+ RTUNI_ALPHA, /* U+00cd7c: */
+ RTUNI_ALPHA, /* U+00cd7d: */
+ RTUNI_ALPHA, /* U+00cd7e: */
+ RTUNI_ALPHA, /* U+00cd7f: */
+ RTUNI_ALPHA, /* U+00cd80: */
+ RTUNI_ALPHA, /* U+00cd81: */
+ RTUNI_ALPHA, /* U+00cd82: */
+ RTUNI_ALPHA, /* U+00cd83: */
+ RTUNI_ALPHA, /* U+00cd84: */
+ RTUNI_ALPHA, /* U+00cd85: */
+ RTUNI_ALPHA, /* U+00cd86: */
+ RTUNI_ALPHA, /* U+00cd87: */
+ RTUNI_ALPHA, /* U+00cd88: */
+ RTUNI_ALPHA, /* U+00cd89: */
+ RTUNI_ALPHA, /* U+00cd8a: */
+ RTUNI_ALPHA, /* U+00cd8b: */
+ RTUNI_ALPHA, /* U+00cd8c: */
+ RTUNI_ALPHA, /* U+00cd8d: */
+ RTUNI_ALPHA, /* U+00cd8e: */
+ RTUNI_ALPHA, /* U+00cd8f: */
+ RTUNI_ALPHA, /* U+00cd90: */
+ RTUNI_ALPHA, /* U+00cd91: */
+ RTUNI_ALPHA, /* U+00cd92: */
+ RTUNI_ALPHA, /* U+00cd93: */
+ RTUNI_ALPHA, /* U+00cd94: */
+ RTUNI_ALPHA, /* U+00cd95: */
+ RTUNI_ALPHA, /* U+00cd96: */
+ RTUNI_ALPHA, /* U+00cd97: */
+ RTUNI_ALPHA, /* U+00cd98: */
+ RTUNI_ALPHA, /* U+00cd99: */
+ RTUNI_ALPHA, /* U+00cd9a: */
+ RTUNI_ALPHA, /* U+00cd9b: */
+ RTUNI_ALPHA, /* U+00cd9c: */
+ RTUNI_ALPHA, /* U+00cd9d: */
+ RTUNI_ALPHA, /* U+00cd9e: */
+ RTUNI_ALPHA, /* U+00cd9f: */
+ RTUNI_ALPHA, /* U+00cda0: */
+ RTUNI_ALPHA, /* U+00cda1: */
+ RTUNI_ALPHA, /* U+00cda2: */
+ RTUNI_ALPHA, /* U+00cda3: */
+ RTUNI_ALPHA, /* U+00cda4: */
+ RTUNI_ALPHA, /* U+00cda5: */
+ RTUNI_ALPHA, /* U+00cda6: */
+ RTUNI_ALPHA, /* U+00cda7: */
+ RTUNI_ALPHA, /* U+00cda8: */
+ RTUNI_ALPHA, /* U+00cda9: */
+ RTUNI_ALPHA, /* U+00cdaa: */
+ RTUNI_ALPHA, /* U+00cdab: */
+ RTUNI_ALPHA, /* U+00cdac: */
+ RTUNI_ALPHA, /* U+00cdad: */
+ RTUNI_ALPHA, /* U+00cdae: */
+ RTUNI_ALPHA, /* U+00cdaf: */
+ RTUNI_ALPHA, /* U+00cdb0: */
+ RTUNI_ALPHA, /* U+00cdb1: */
+ RTUNI_ALPHA, /* U+00cdb2: */
+ RTUNI_ALPHA, /* U+00cdb3: */
+ RTUNI_ALPHA, /* U+00cdb4: */
+ RTUNI_ALPHA, /* U+00cdb5: */
+ RTUNI_ALPHA, /* U+00cdb6: */
+ RTUNI_ALPHA, /* U+00cdb7: */
+ RTUNI_ALPHA, /* U+00cdb8: */
+ RTUNI_ALPHA, /* U+00cdb9: */
+ RTUNI_ALPHA, /* U+00cdba: */
+ RTUNI_ALPHA, /* U+00cdbb: */
+ RTUNI_ALPHA, /* U+00cdbc: */
+ RTUNI_ALPHA, /* U+00cdbd: */
+ RTUNI_ALPHA, /* U+00cdbe: */
+ RTUNI_ALPHA, /* U+00cdbf: */
+ RTUNI_ALPHA, /* U+00cdc0: */
+ RTUNI_ALPHA, /* U+00cdc1: */
+ RTUNI_ALPHA, /* U+00cdc2: */
+ RTUNI_ALPHA, /* U+00cdc3: */
+ RTUNI_ALPHA, /* U+00cdc4: */
+ RTUNI_ALPHA, /* U+00cdc5: */
+ RTUNI_ALPHA, /* U+00cdc6: */
+ RTUNI_ALPHA, /* U+00cdc7: */
+ RTUNI_ALPHA, /* U+00cdc8: */
+ RTUNI_ALPHA, /* U+00cdc9: */
+ RTUNI_ALPHA, /* U+00cdca: */
+ RTUNI_ALPHA, /* U+00cdcb: */
+ RTUNI_ALPHA, /* U+00cdcc: */
+ RTUNI_ALPHA, /* U+00cdcd: */
+ RTUNI_ALPHA, /* U+00cdce: */
+ RTUNI_ALPHA, /* U+00cdcf: */
+ RTUNI_ALPHA, /* U+00cdd0: */
+ RTUNI_ALPHA, /* U+00cdd1: */
+ RTUNI_ALPHA, /* U+00cdd2: */
+ RTUNI_ALPHA, /* U+00cdd3: */
+ RTUNI_ALPHA, /* U+00cdd4: */
+ RTUNI_ALPHA, /* U+00cdd5: */
+ RTUNI_ALPHA, /* U+00cdd6: */
+ RTUNI_ALPHA, /* U+00cdd7: */
+ RTUNI_ALPHA, /* U+00cdd8: */
+ RTUNI_ALPHA, /* U+00cdd9: */
+ RTUNI_ALPHA, /* U+00cdda: */
+ RTUNI_ALPHA, /* U+00cddb: */
+ RTUNI_ALPHA, /* U+00cddc: */
+ RTUNI_ALPHA, /* U+00cddd: */
+ RTUNI_ALPHA, /* U+00cdde: */
+ RTUNI_ALPHA, /* U+00cddf: */
+ RTUNI_ALPHA, /* U+00cde0: */
+ RTUNI_ALPHA, /* U+00cde1: */
+ RTUNI_ALPHA, /* U+00cde2: */
+ RTUNI_ALPHA, /* U+00cde3: */
+ RTUNI_ALPHA, /* U+00cde4: */
+ RTUNI_ALPHA, /* U+00cde5: */
+ RTUNI_ALPHA, /* U+00cde6: */
+ RTUNI_ALPHA, /* U+00cde7: */
+ RTUNI_ALPHA, /* U+00cde8: */
+ RTUNI_ALPHA, /* U+00cde9: */
+ RTUNI_ALPHA, /* U+00cdea: */
+ RTUNI_ALPHA, /* U+00cdeb: */
+ RTUNI_ALPHA, /* U+00cdec: */
+ RTUNI_ALPHA, /* U+00cded: */
+ RTUNI_ALPHA, /* U+00cdee: */
+ RTUNI_ALPHA, /* U+00cdef: */
+ RTUNI_ALPHA, /* U+00cdf0: */
+ RTUNI_ALPHA, /* U+00cdf1: */
+ RTUNI_ALPHA, /* U+00cdf2: */
+ RTUNI_ALPHA, /* U+00cdf3: */
+ RTUNI_ALPHA, /* U+00cdf4: */
+ RTUNI_ALPHA, /* U+00cdf5: */
+ RTUNI_ALPHA, /* U+00cdf6: */
+ RTUNI_ALPHA, /* U+00cdf7: */
+ RTUNI_ALPHA, /* U+00cdf8: */
+ RTUNI_ALPHA, /* U+00cdf9: */
+ RTUNI_ALPHA, /* U+00cdfa: */
+ RTUNI_ALPHA, /* U+00cdfb: */
+ RTUNI_ALPHA, /* U+00cdfc: */
+ RTUNI_ALPHA, /* U+00cdfd: */
+ RTUNI_ALPHA, /* U+00cdfe: */
+ RTUNI_ALPHA, /* U+00cdff: */
+ RTUNI_ALPHA, /* U+00ce00: */
+ RTUNI_ALPHA, /* U+00ce01: */
+ RTUNI_ALPHA, /* U+00ce02: */
+ RTUNI_ALPHA, /* U+00ce03: */
+ RTUNI_ALPHA, /* U+00ce04: */
+ RTUNI_ALPHA, /* U+00ce05: */
+ RTUNI_ALPHA, /* U+00ce06: */
+ RTUNI_ALPHA, /* U+00ce07: */
+ RTUNI_ALPHA, /* U+00ce08: */
+ RTUNI_ALPHA, /* U+00ce09: */
+ RTUNI_ALPHA, /* U+00ce0a: */
+ RTUNI_ALPHA, /* U+00ce0b: */
+ RTUNI_ALPHA, /* U+00ce0c: */
+ RTUNI_ALPHA, /* U+00ce0d: */
+ RTUNI_ALPHA, /* U+00ce0e: */
+ RTUNI_ALPHA, /* U+00ce0f: */
+ RTUNI_ALPHA, /* U+00ce10: */
+ RTUNI_ALPHA, /* U+00ce11: */
+ RTUNI_ALPHA, /* U+00ce12: */
+ RTUNI_ALPHA, /* U+00ce13: */
+ RTUNI_ALPHA, /* U+00ce14: */
+ RTUNI_ALPHA, /* U+00ce15: */
+ RTUNI_ALPHA, /* U+00ce16: */
+ RTUNI_ALPHA, /* U+00ce17: */
+ RTUNI_ALPHA, /* U+00ce18: */
+ RTUNI_ALPHA, /* U+00ce19: */
+ RTUNI_ALPHA, /* U+00ce1a: */
+ RTUNI_ALPHA, /* U+00ce1b: */
+ RTUNI_ALPHA, /* U+00ce1c: */
+ RTUNI_ALPHA, /* U+00ce1d: */
+ RTUNI_ALPHA, /* U+00ce1e: */
+ RTUNI_ALPHA, /* U+00ce1f: */
+ RTUNI_ALPHA, /* U+00ce20: */
+ RTUNI_ALPHA, /* U+00ce21: */
+ RTUNI_ALPHA, /* U+00ce22: */
+ RTUNI_ALPHA, /* U+00ce23: */
+ RTUNI_ALPHA, /* U+00ce24: */
+ RTUNI_ALPHA, /* U+00ce25: */
+ RTUNI_ALPHA, /* U+00ce26: */
+ RTUNI_ALPHA, /* U+00ce27: */
+ RTUNI_ALPHA, /* U+00ce28: */
+ RTUNI_ALPHA, /* U+00ce29: */
+ RTUNI_ALPHA, /* U+00ce2a: */
+ RTUNI_ALPHA, /* U+00ce2b: */
+ RTUNI_ALPHA, /* U+00ce2c: */
+ RTUNI_ALPHA, /* U+00ce2d: */
+ RTUNI_ALPHA, /* U+00ce2e: */
+ RTUNI_ALPHA, /* U+00ce2f: */
+ RTUNI_ALPHA, /* U+00ce30: */
+ RTUNI_ALPHA, /* U+00ce31: */
+ RTUNI_ALPHA, /* U+00ce32: */
+ RTUNI_ALPHA, /* U+00ce33: */
+ RTUNI_ALPHA, /* U+00ce34: */
+ RTUNI_ALPHA, /* U+00ce35: */
+ RTUNI_ALPHA, /* U+00ce36: */
+ RTUNI_ALPHA, /* U+00ce37: */
+ RTUNI_ALPHA, /* U+00ce38: */
+ RTUNI_ALPHA, /* U+00ce39: */
+ RTUNI_ALPHA, /* U+00ce3a: */
+ RTUNI_ALPHA, /* U+00ce3b: */
+ RTUNI_ALPHA, /* U+00ce3c: */
+ RTUNI_ALPHA, /* U+00ce3d: */
+ RTUNI_ALPHA, /* U+00ce3e: */
+ RTUNI_ALPHA, /* U+00ce3f: */
+ RTUNI_ALPHA, /* U+00ce40: */
+ RTUNI_ALPHA, /* U+00ce41: */
+ RTUNI_ALPHA, /* U+00ce42: */
+ RTUNI_ALPHA, /* U+00ce43: */
+ RTUNI_ALPHA, /* U+00ce44: */
+ RTUNI_ALPHA, /* U+00ce45: */
+ RTUNI_ALPHA, /* U+00ce46: */
+ RTUNI_ALPHA, /* U+00ce47: */
+ RTUNI_ALPHA, /* U+00ce48: */
+ RTUNI_ALPHA, /* U+00ce49: */
+ RTUNI_ALPHA, /* U+00ce4a: */
+ RTUNI_ALPHA, /* U+00ce4b: */
+ RTUNI_ALPHA, /* U+00ce4c: */
+ RTUNI_ALPHA, /* U+00ce4d: */
+ RTUNI_ALPHA, /* U+00ce4e: */
+ RTUNI_ALPHA, /* U+00ce4f: */
+ RTUNI_ALPHA, /* U+00ce50: */
+ RTUNI_ALPHA, /* U+00ce51: */
+ RTUNI_ALPHA, /* U+00ce52: */
+ RTUNI_ALPHA, /* U+00ce53: */
+ RTUNI_ALPHA, /* U+00ce54: */
+ RTUNI_ALPHA, /* U+00ce55: */
+ RTUNI_ALPHA, /* U+00ce56: */
+ RTUNI_ALPHA, /* U+00ce57: */
+ RTUNI_ALPHA, /* U+00ce58: */
+ RTUNI_ALPHA, /* U+00ce59: */
+ RTUNI_ALPHA, /* U+00ce5a: */
+ RTUNI_ALPHA, /* U+00ce5b: */
+ RTUNI_ALPHA, /* U+00ce5c: */
+ RTUNI_ALPHA, /* U+00ce5d: */
+ RTUNI_ALPHA, /* U+00ce5e: */
+ RTUNI_ALPHA, /* U+00ce5f: */
+ RTUNI_ALPHA, /* U+00ce60: */
+ RTUNI_ALPHA, /* U+00ce61: */
+ RTUNI_ALPHA, /* U+00ce62: */
+ RTUNI_ALPHA, /* U+00ce63: */
+ RTUNI_ALPHA, /* U+00ce64: */
+ RTUNI_ALPHA, /* U+00ce65: */
+ RTUNI_ALPHA, /* U+00ce66: */
+ RTUNI_ALPHA, /* U+00ce67: */
+ RTUNI_ALPHA, /* U+00ce68: */
+ RTUNI_ALPHA, /* U+00ce69: */
+ RTUNI_ALPHA, /* U+00ce6a: */
+ RTUNI_ALPHA, /* U+00ce6b: */
+ RTUNI_ALPHA, /* U+00ce6c: */
+ RTUNI_ALPHA, /* U+00ce6d: */
+ RTUNI_ALPHA, /* U+00ce6e: */
+ RTUNI_ALPHA, /* U+00ce6f: */
+ RTUNI_ALPHA, /* U+00ce70: */
+ RTUNI_ALPHA, /* U+00ce71: */
+ RTUNI_ALPHA, /* U+00ce72: */
+ RTUNI_ALPHA, /* U+00ce73: */
+ RTUNI_ALPHA, /* U+00ce74: */
+ RTUNI_ALPHA, /* U+00ce75: */
+ RTUNI_ALPHA, /* U+00ce76: */
+ RTUNI_ALPHA, /* U+00ce77: */
+ RTUNI_ALPHA, /* U+00ce78: */
+ RTUNI_ALPHA, /* U+00ce79: */
+ RTUNI_ALPHA, /* U+00ce7a: */
+ RTUNI_ALPHA, /* U+00ce7b: */
+ RTUNI_ALPHA, /* U+00ce7c: */
+ RTUNI_ALPHA, /* U+00ce7d: */
+ RTUNI_ALPHA, /* U+00ce7e: */
+ RTUNI_ALPHA, /* U+00ce7f: */
+ RTUNI_ALPHA, /* U+00ce80: */
+ RTUNI_ALPHA, /* U+00ce81: */
+ RTUNI_ALPHA, /* U+00ce82: */
+ RTUNI_ALPHA, /* U+00ce83: */
+ RTUNI_ALPHA, /* U+00ce84: */
+ RTUNI_ALPHA, /* U+00ce85: */
+ RTUNI_ALPHA, /* U+00ce86: */
+ RTUNI_ALPHA, /* U+00ce87: */
+ RTUNI_ALPHA, /* U+00ce88: */
+ RTUNI_ALPHA, /* U+00ce89: */
+ RTUNI_ALPHA, /* U+00ce8a: */
+ RTUNI_ALPHA, /* U+00ce8b: */
+ RTUNI_ALPHA, /* U+00ce8c: */
+ RTUNI_ALPHA, /* U+00ce8d: */
+ RTUNI_ALPHA, /* U+00ce8e: */
+ RTUNI_ALPHA, /* U+00ce8f: */
+ RTUNI_ALPHA, /* U+00ce90: */
+ RTUNI_ALPHA, /* U+00ce91: */
+ RTUNI_ALPHA, /* U+00ce92: */
+ RTUNI_ALPHA, /* U+00ce93: */
+ RTUNI_ALPHA, /* U+00ce94: */
+ RTUNI_ALPHA, /* U+00ce95: */
+ RTUNI_ALPHA, /* U+00ce96: */
+ RTUNI_ALPHA, /* U+00ce97: */
+ RTUNI_ALPHA, /* U+00ce98: */
+ RTUNI_ALPHA, /* U+00ce99: */
+ RTUNI_ALPHA, /* U+00ce9a: */
+ RTUNI_ALPHA, /* U+00ce9b: */
+ RTUNI_ALPHA, /* U+00ce9c: */
+ RTUNI_ALPHA, /* U+00ce9d: */
+ RTUNI_ALPHA, /* U+00ce9e: */
+ RTUNI_ALPHA, /* U+00ce9f: */
+ RTUNI_ALPHA, /* U+00cea0: */
+ RTUNI_ALPHA, /* U+00cea1: */
+ RTUNI_ALPHA, /* U+00cea2: */
+ RTUNI_ALPHA, /* U+00cea3: */
+ RTUNI_ALPHA, /* U+00cea4: */
+ RTUNI_ALPHA, /* U+00cea5: */
+ RTUNI_ALPHA, /* U+00cea6: */
+ RTUNI_ALPHA, /* U+00cea7: */
+ RTUNI_ALPHA, /* U+00cea8: */
+ RTUNI_ALPHA, /* U+00cea9: */
+ RTUNI_ALPHA, /* U+00ceaa: */
+ RTUNI_ALPHA, /* U+00ceab: */
+ RTUNI_ALPHA, /* U+00ceac: */
+ RTUNI_ALPHA, /* U+00cead: */
+ RTUNI_ALPHA, /* U+00ceae: */
+ RTUNI_ALPHA, /* U+00ceaf: */
+ RTUNI_ALPHA, /* U+00ceb0: */
+ RTUNI_ALPHA, /* U+00ceb1: */
+ RTUNI_ALPHA, /* U+00ceb2: */
+ RTUNI_ALPHA, /* U+00ceb3: */
+ RTUNI_ALPHA, /* U+00ceb4: */
+ RTUNI_ALPHA, /* U+00ceb5: */
+ RTUNI_ALPHA, /* U+00ceb6: */
+ RTUNI_ALPHA, /* U+00ceb7: */
+ RTUNI_ALPHA, /* U+00ceb8: */
+ RTUNI_ALPHA, /* U+00ceb9: */
+ RTUNI_ALPHA, /* U+00ceba: */
+ RTUNI_ALPHA, /* U+00cebb: */
+ RTUNI_ALPHA, /* U+00cebc: */
+ RTUNI_ALPHA, /* U+00cebd: */
+ RTUNI_ALPHA, /* U+00cebe: */
+ RTUNI_ALPHA, /* U+00cebf: */
+ RTUNI_ALPHA, /* U+00cec0: */
+ RTUNI_ALPHA, /* U+00cec1: */
+ RTUNI_ALPHA, /* U+00cec2: */
+ RTUNI_ALPHA, /* U+00cec3: */
+ RTUNI_ALPHA, /* U+00cec4: */
+ RTUNI_ALPHA, /* U+00cec5: */
+ RTUNI_ALPHA, /* U+00cec6: */
+ RTUNI_ALPHA, /* U+00cec7: */
+ RTUNI_ALPHA, /* U+00cec8: */
+ RTUNI_ALPHA, /* U+00cec9: */
+ RTUNI_ALPHA, /* U+00ceca: */
+ RTUNI_ALPHA, /* U+00cecb: */
+ RTUNI_ALPHA, /* U+00cecc: */
+ RTUNI_ALPHA, /* U+00cecd: */
+ RTUNI_ALPHA, /* U+00cece: */
+ RTUNI_ALPHA, /* U+00cecf: */
+ RTUNI_ALPHA, /* U+00ced0: */
+ RTUNI_ALPHA, /* U+00ced1: */
+ RTUNI_ALPHA, /* U+00ced2: */
+ RTUNI_ALPHA, /* U+00ced3: */
+ RTUNI_ALPHA, /* U+00ced4: */
+ RTUNI_ALPHA, /* U+00ced5: */
+ RTUNI_ALPHA, /* U+00ced6: */
+ RTUNI_ALPHA, /* U+00ced7: */
+ RTUNI_ALPHA, /* U+00ced8: */
+ RTUNI_ALPHA, /* U+00ced9: */
+ RTUNI_ALPHA, /* U+00ceda: */
+ RTUNI_ALPHA, /* U+00cedb: */
+ RTUNI_ALPHA, /* U+00cedc: */
+ RTUNI_ALPHA, /* U+00cedd: */
+ RTUNI_ALPHA, /* U+00cede: */
+ RTUNI_ALPHA, /* U+00cedf: */
+ RTUNI_ALPHA, /* U+00cee0: */
+ RTUNI_ALPHA, /* U+00cee1: */
+ RTUNI_ALPHA, /* U+00cee2: */
+ RTUNI_ALPHA, /* U+00cee3: */
+ RTUNI_ALPHA, /* U+00cee4: */
+ RTUNI_ALPHA, /* U+00cee5: */
+ RTUNI_ALPHA, /* U+00cee6: */
+ RTUNI_ALPHA, /* U+00cee7: */
+ RTUNI_ALPHA, /* U+00cee8: */
+ RTUNI_ALPHA, /* U+00cee9: */
+ RTUNI_ALPHA, /* U+00ceea: */
+ RTUNI_ALPHA, /* U+00ceeb: */
+ RTUNI_ALPHA, /* U+00ceec: */
+ RTUNI_ALPHA, /* U+00ceed: */
+ RTUNI_ALPHA, /* U+00ceee: */
+ RTUNI_ALPHA, /* U+00ceef: */
+ RTUNI_ALPHA, /* U+00cef0: */
+ RTUNI_ALPHA, /* U+00cef1: */
+ RTUNI_ALPHA, /* U+00cef2: */
+ RTUNI_ALPHA, /* U+00cef3: */
+ RTUNI_ALPHA, /* U+00cef4: */
+ RTUNI_ALPHA, /* U+00cef5: */
+ RTUNI_ALPHA, /* U+00cef6: */
+ RTUNI_ALPHA, /* U+00cef7: */
+ RTUNI_ALPHA, /* U+00cef8: */
+ RTUNI_ALPHA, /* U+00cef9: */
+ RTUNI_ALPHA, /* U+00cefa: */
+ RTUNI_ALPHA, /* U+00cefb: */
+ RTUNI_ALPHA, /* U+00cefc: */
+ RTUNI_ALPHA, /* U+00cefd: */
+ RTUNI_ALPHA, /* U+00cefe: */
+ RTUNI_ALPHA, /* U+00ceff: */
+ RTUNI_ALPHA, /* U+00cf00: */
+ RTUNI_ALPHA, /* U+00cf01: */
+ RTUNI_ALPHA, /* U+00cf02: */
+ RTUNI_ALPHA, /* U+00cf03: */
+ RTUNI_ALPHA, /* U+00cf04: */
+ RTUNI_ALPHA, /* U+00cf05: */
+ RTUNI_ALPHA, /* U+00cf06: */
+ RTUNI_ALPHA, /* U+00cf07: */
+ RTUNI_ALPHA, /* U+00cf08: */
+ RTUNI_ALPHA, /* U+00cf09: */
+ RTUNI_ALPHA, /* U+00cf0a: */
+ RTUNI_ALPHA, /* U+00cf0b: */
+ RTUNI_ALPHA, /* U+00cf0c: */
+ RTUNI_ALPHA, /* U+00cf0d: */
+ RTUNI_ALPHA, /* U+00cf0e: */
+ RTUNI_ALPHA, /* U+00cf0f: */
+ RTUNI_ALPHA, /* U+00cf10: */
+ RTUNI_ALPHA, /* U+00cf11: */
+ RTUNI_ALPHA, /* U+00cf12: */
+ RTUNI_ALPHA, /* U+00cf13: */
+ RTUNI_ALPHA, /* U+00cf14: */
+ RTUNI_ALPHA, /* U+00cf15: */
+ RTUNI_ALPHA, /* U+00cf16: */
+ RTUNI_ALPHA, /* U+00cf17: */
+ RTUNI_ALPHA, /* U+00cf18: */
+ RTUNI_ALPHA, /* U+00cf19: */
+ RTUNI_ALPHA, /* U+00cf1a: */
+ RTUNI_ALPHA, /* U+00cf1b: */
+ RTUNI_ALPHA, /* U+00cf1c: */
+ RTUNI_ALPHA, /* U+00cf1d: */
+ RTUNI_ALPHA, /* U+00cf1e: */
+ RTUNI_ALPHA, /* U+00cf1f: */
+ RTUNI_ALPHA, /* U+00cf20: */
+ RTUNI_ALPHA, /* U+00cf21: */
+ RTUNI_ALPHA, /* U+00cf22: */
+ RTUNI_ALPHA, /* U+00cf23: */
+ RTUNI_ALPHA, /* U+00cf24: */
+ RTUNI_ALPHA, /* U+00cf25: */
+ RTUNI_ALPHA, /* U+00cf26: */
+ RTUNI_ALPHA, /* U+00cf27: */
+ RTUNI_ALPHA, /* U+00cf28: */
+ RTUNI_ALPHA, /* U+00cf29: */
+ RTUNI_ALPHA, /* U+00cf2a: */
+ RTUNI_ALPHA, /* U+00cf2b: */
+ RTUNI_ALPHA, /* U+00cf2c: */
+ RTUNI_ALPHA, /* U+00cf2d: */
+ RTUNI_ALPHA, /* U+00cf2e: */
+ RTUNI_ALPHA, /* U+00cf2f: */
+ RTUNI_ALPHA, /* U+00cf30: */
+ RTUNI_ALPHA, /* U+00cf31: */
+ RTUNI_ALPHA, /* U+00cf32: */
+ RTUNI_ALPHA, /* U+00cf33: */
+ RTUNI_ALPHA, /* U+00cf34: */
+ RTUNI_ALPHA, /* U+00cf35: */
+ RTUNI_ALPHA, /* U+00cf36: */
+ RTUNI_ALPHA, /* U+00cf37: */
+ RTUNI_ALPHA, /* U+00cf38: */
+ RTUNI_ALPHA, /* U+00cf39: */
+ RTUNI_ALPHA, /* U+00cf3a: */
+ RTUNI_ALPHA, /* U+00cf3b: */
+ RTUNI_ALPHA, /* U+00cf3c: */
+ RTUNI_ALPHA, /* U+00cf3d: */
+ RTUNI_ALPHA, /* U+00cf3e: */
+ RTUNI_ALPHA, /* U+00cf3f: */
+ RTUNI_ALPHA, /* U+00cf40: */
+ RTUNI_ALPHA, /* U+00cf41: */
+ RTUNI_ALPHA, /* U+00cf42: */
+ RTUNI_ALPHA, /* U+00cf43: */
+ RTUNI_ALPHA, /* U+00cf44: */
+ RTUNI_ALPHA, /* U+00cf45: */
+ RTUNI_ALPHA, /* U+00cf46: */
+ RTUNI_ALPHA, /* U+00cf47: */
+ RTUNI_ALPHA, /* U+00cf48: */
+ RTUNI_ALPHA, /* U+00cf49: */
+ RTUNI_ALPHA, /* U+00cf4a: */
+ RTUNI_ALPHA, /* U+00cf4b: */
+ RTUNI_ALPHA, /* U+00cf4c: */
+ RTUNI_ALPHA, /* U+00cf4d: */
+ RTUNI_ALPHA, /* U+00cf4e: */
+ RTUNI_ALPHA, /* U+00cf4f: */
+ RTUNI_ALPHA, /* U+00cf50: */
+ RTUNI_ALPHA, /* U+00cf51: */
+ RTUNI_ALPHA, /* U+00cf52: */
+ RTUNI_ALPHA, /* U+00cf53: */
+ RTUNI_ALPHA, /* U+00cf54: */
+ RTUNI_ALPHA, /* U+00cf55: */
+ RTUNI_ALPHA, /* U+00cf56: */
+ RTUNI_ALPHA, /* U+00cf57: */
+ RTUNI_ALPHA, /* U+00cf58: */
+ RTUNI_ALPHA, /* U+00cf59: */
+ RTUNI_ALPHA, /* U+00cf5a: */
+ RTUNI_ALPHA, /* U+00cf5b: */
+ RTUNI_ALPHA, /* U+00cf5c: */
+ RTUNI_ALPHA, /* U+00cf5d: */
+ RTUNI_ALPHA, /* U+00cf5e: */
+ RTUNI_ALPHA, /* U+00cf5f: */
+ RTUNI_ALPHA, /* U+00cf60: */
+ RTUNI_ALPHA, /* U+00cf61: */
+ RTUNI_ALPHA, /* U+00cf62: */
+ RTUNI_ALPHA, /* U+00cf63: */
+ RTUNI_ALPHA, /* U+00cf64: */
+ RTUNI_ALPHA, /* U+00cf65: */
+ RTUNI_ALPHA, /* U+00cf66: */
+ RTUNI_ALPHA, /* U+00cf67: */
+ RTUNI_ALPHA, /* U+00cf68: */
+ RTUNI_ALPHA, /* U+00cf69: */
+ RTUNI_ALPHA, /* U+00cf6a: */
+ RTUNI_ALPHA, /* U+00cf6b: */
+ RTUNI_ALPHA, /* U+00cf6c: */
+ RTUNI_ALPHA, /* U+00cf6d: */
+ RTUNI_ALPHA, /* U+00cf6e: */
+ RTUNI_ALPHA, /* U+00cf6f: */
+ RTUNI_ALPHA, /* U+00cf70: */
+ RTUNI_ALPHA, /* U+00cf71: */
+ RTUNI_ALPHA, /* U+00cf72: */
+ RTUNI_ALPHA, /* U+00cf73: */
+ RTUNI_ALPHA, /* U+00cf74: */
+ RTUNI_ALPHA, /* U+00cf75: */
+ RTUNI_ALPHA, /* U+00cf76: */
+ RTUNI_ALPHA, /* U+00cf77: */
+ RTUNI_ALPHA, /* U+00cf78: */
+ RTUNI_ALPHA, /* U+00cf79: */
+ RTUNI_ALPHA, /* U+00cf7a: */
+ RTUNI_ALPHA, /* U+00cf7b: */
+ RTUNI_ALPHA, /* U+00cf7c: */
+ RTUNI_ALPHA, /* U+00cf7d: */
+ RTUNI_ALPHA, /* U+00cf7e: */
+ RTUNI_ALPHA, /* U+00cf7f: */
+ RTUNI_ALPHA, /* U+00cf80: */
+ RTUNI_ALPHA, /* U+00cf81: */
+ RTUNI_ALPHA, /* U+00cf82: */
+ RTUNI_ALPHA, /* U+00cf83: */
+ RTUNI_ALPHA, /* U+00cf84: */
+ RTUNI_ALPHA, /* U+00cf85: */
+ RTUNI_ALPHA, /* U+00cf86: */
+ RTUNI_ALPHA, /* U+00cf87: */
+ RTUNI_ALPHA, /* U+00cf88: */
+ RTUNI_ALPHA, /* U+00cf89: */
+ RTUNI_ALPHA, /* U+00cf8a: */
+ RTUNI_ALPHA, /* U+00cf8b: */
+ RTUNI_ALPHA, /* U+00cf8c: */
+ RTUNI_ALPHA, /* U+00cf8d: */
+ RTUNI_ALPHA, /* U+00cf8e: */
+ RTUNI_ALPHA, /* U+00cf8f: */
+ RTUNI_ALPHA, /* U+00cf90: */
+ RTUNI_ALPHA, /* U+00cf91: */
+ RTUNI_ALPHA, /* U+00cf92: */
+ RTUNI_ALPHA, /* U+00cf93: */
+ RTUNI_ALPHA, /* U+00cf94: */
+ RTUNI_ALPHA, /* U+00cf95: */
+ RTUNI_ALPHA, /* U+00cf96: */
+ RTUNI_ALPHA, /* U+00cf97: */
+ RTUNI_ALPHA, /* U+00cf98: */
+ RTUNI_ALPHA, /* U+00cf99: */
+ RTUNI_ALPHA, /* U+00cf9a: */
+ RTUNI_ALPHA, /* U+00cf9b: */
+ RTUNI_ALPHA, /* U+00cf9c: */
+ RTUNI_ALPHA, /* U+00cf9d: */
+ RTUNI_ALPHA, /* U+00cf9e: */
+ RTUNI_ALPHA, /* U+00cf9f: */
+ RTUNI_ALPHA, /* U+00cfa0: */
+ RTUNI_ALPHA, /* U+00cfa1: */
+ RTUNI_ALPHA, /* U+00cfa2: */
+ RTUNI_ALPHA, /* U+00cfa3: */
+ RTUNI_ALPHA, /* U+00cfa4: */
+ RTUNI_ALPHA, /* U+00cfa5: */
+ RTUNI_ALPHA, /* U+00cfa6: */
+ RTUNI_ALPHA, /* U+00cfa7: */
+ RTUNI_ALPHA, /* U+00cfa8: */
+ RTUNI_ALPHA, /* U+00cfa9: */
+ RTUNI_ALPHA, /* U+00cfaa: */
+ RTUNI_ALPHA, /* U+00cfab: */
+ RTUNI_ALPHA, /* U+00cfac: */
+ RTUNI_ALPHA, /* U+00cfad: */
+ RTUNI_ALPHA, /* U+00cfae: */
+ RTUNI_ALPHA, /* U+00cfaf: */
+ RTUNI_ALPHA, /* U+00cfb0: */
+ RTUNI_ALPHA, /* U+00cfb1: */
+ RTUNI_ALPHA, /* U+00cfb2: */
+ RTUNI_ALPHA, /* U+00cfb3: */
+ RTUNI_ALPHA, /* U+00cfb4: */
+ RTUNI_ALPHA, /* U+00cfb5: */
+ RTUNI_ALPHA, /* U+00cfb6: */
+ RTUNI_ALPHA, /* U+00cfb7: */
+ RTUNI_ALPHA, /* U+00cfb8: */
+ RTUNI_ALPHA, /* U+00cfb9: */
+ RTUNI_ALPHA, /* U+00cfba: */
+ RTUNI_ALPHA, /* U+00cfbb: */
+ RTUNI_ALPHA, /* U+00cfbc: */
+ RTUNI_ALPHA, /* U+00cfbd: */
+ RTUNI_ALPHA, /* U+00cfbe: */
+ RTUNI_ALPHA, /* U+00cfbf: */
+ RTUNI_ALPHA, /* U+00cfc0: */
+ RTUNI_ALPHA, /* U+00cfc1: */
+ RTUNI_ALPHA, /* U+00cfc2: */
+ RTUNI_ALPHA, /* U+00cfc3: */
+ RTUNI_ALPHA, /* U+00cfc4: */
+ RTUNI_ALPHA, /* U+00cfc5: */
+ RTUNI_ALPHA, /* U+00cfc6: */
+ RTUNI_ALPHA, /* U+00cfc7: */
+ RTUNI_ALPHA, /* U+00cfc8: */
+ RTUNI_ALPHA, /* U+00cfc9: */
+ RTUNI_ALPHA, /* U+00cfca: */
+ RTUNI_ALPHA, /* U+00cfcb: */
+ RTUNI_ALPHA, /* U+00cfcc: */
+ RTUNI_ALPHA, /* U+00cfcd: */
+ RTUNI_ALPHA, /* U+00cfce: */
+ RTUNI_ALPHA, /* U+00cfcf: */
+ RTUNI_ALPHA, /* U+00cfd0: */
+ RTUNI_ALPHA, /* U+00cfd1: */
+ RTUNI_ALPHA, /* U+00cfd2: */
+ RTUNI_ALPHA, /* U+00cfd3: */
+ RTUNI_ALPHA, /* U+00cfd4: */
+ RTUNI_ALPHA, /* U+00cfd5: */
+ RTUNI_ALPHA, /* U+00cfd6: */
+ RTUNI_ALPHA, /* U+00cfd7: */
+ RTUNI_ALPHA, /* U+00cfd8: */
+ RTUNI_ALPHA, /* U+00cfd9: */
+ RTUNI_ALPHA, /* U+00cfda: */
+ RTUNI_ALPHA, /* U+00cfdb: */
+ RTUNI_ALPHA, /* U+00cfdc: */
+ RTUNI_ALPHA, /* U+00cfdd: */
+ RTUNI_ALPHA, /* U+00cfde: */
+ RTUNI_ALPHA, /* U+00cfdf: */
+ RTUNI_ALPHA, /* U+00cfe0: */
+ RTUNI_ALPHA, /* U+00cfe1: */
+ RTUNI_ALPHA, /* U+00cfe2: */
+ RTUNI_ALPHA, /* U+00cfe3: */
+ RTUNI_ALPHA, /* U+00cfe4: */
+ RTUNI_ALPHA, /* U+00cfe5: */
+ RTUNI_ALPHA, /* U+00cfe6: */
+ RTUNI_ALPHA, /* U+00cfe7: */
+ RTUNI_ALPHA, /* U+00cfe8: */
+ RTUNI_ALPHA, /* U+00cfe9: */
+ RTUNI_ALPHA, /* U+00cfea: */
+ RTUNI_ALPHA, /* U+00cfeb: */
+ RTUNI_ALPHA, /* U+00cfec: */
+ RTUNI_ALPHA, /* U+00cfed: */
+ RTUNI_ALPHA, /* U+00cfee: */
+ RTUNI_ALPHA, /* U+00cfef: */
+ RTUNI_ALPHA, /* U+00cff0: */
+ RTUNI_ALPHA, /* U+00cff1: */
+ RTUNI_ALPHA, /* U+00cff2: */
+ RTUNI_ALPHA, /* U+00cff3: */
+ RTUNI_ALPHA, /* U+00cff4: */
+ RTUNI_ALPHA, /* U+00cff5: */
+ RTUNI_ALPHA, /* U+00cff6: */
+ RTUNI_ALPHA, /* U+00cff7: */
+ RTUNI_ALPHA, /* U+00cff8: */
+ RTUNI_ALPHA, /* U+00cff9: */
+ RTUNI_ALPHA, /* U+00cffa: */
+ RTUNI_ALPHA, /* U+00cffb: */
+ RTUNI_ALPHA, /* U+00cffc: */
+ RTUNI_ALPHA, /* U+00cffd: */
+ RTUNI_ALPHA, /* U+00cffe: */
+ RTUNI_ALPHA, /* U+00cfff: */
+ RTUNI_ALPHA, /* U+00d000: */
+ RTUNI_ALPHA, /* U+00d001: */
+ RTUNI_ALPHA, /* U+00d002: */
+ RTUNI_ALPHA, /* U+00d003: */
+ RTUNI_ALPHA, /* U+00d004: */
+ RTUNI_ALPHA, /* U+00d005: */
+ RTUNI_ALPHA, /* U+00d006: */
+ RTUNI_ALPHA, /* U+00d007: */
+ RTUNI_ALPHA, /* U+00d008: */
+ RTUNI_ALPHA, /* U+00d009: */
+ RTUNI_ALPHA, /* U+00d00a: */
+ RTUNI_ALPHA, /* U+00d00b: */
+ RTUNI_ALPHA, /* U+00d00c: */
+ RTUNI_ALPHA, /* U+00d00d: */
+ RTUNI_ALPHA, /* U+00d00e: */
+ RTUNI_ALPHA, /* U+00d00f: */
+ RTUNI_ALPHA, /* U+00d010: */
+ RTUNI_ALPHA, /* U+00d011: */
+ RTUNI_ALPHA, /* U+00d012: */
+ RTUNI_ALPHA, /* U+00d013: */
+ RTUNI_ALPHA, /* U+00d014: */
+ RTUNI_ALPHA, /* U+00d015: */
+ RTUNI_ALPHA, /* U+00d016: */
+ RTUNI_ALPHA, /* U+00d017: */
+ RTUNI_ALPHA, /* U+00d018: */
+ RTUNI_ALPHA, /* U+00d019: */
+ RTUNI_ALPHA, /* U+00d01a: */
+ RTUNI_ALPHA, /* U+00d01b: */
+ RTUNI_ALPHA, /* U+00d01c: */
+ RTUNI_ALPHA, /* U+00d01d: */
+ RTUNI_ALPHA, /* U+00d01e: */
+ RTUNI_ALPHA, /* U+00d01f: */
+ RTUNI_ALPHA, /* U+00d020: */
+ RTUNI_ALPHA, /* U+00d021: */
+ RTUNI_ALPHA, /* U+00d022: */
+ RTUNI_ALPHA, /* U+00d023: */
+ RTUNI_ALPHA, /* U+00d024: */
+ RTUNI_ALPHA, /* U+00d025: */
+ RTUNI_ALPHA, /* U+00d026: */
+ RTUNI_ALPHA, /* U+00d027: */
+ RTUNI_ALPHA, /* U+00d028: */
+ RTUNI_ALPHA, /* U+00d029: */
+ RTUNI_ALPHA, /* U+00d02a: */
+ RTUNI_ALPHA, /* U+00d02b: */
+ RTUNI_ALPHA, /* U+00d02c: */
+ RTUNI_ALPHA, /* U+00d02d: */
+ RTUNI_ALPHA, /* U+00d02e: */
+ RTUNI_ALPHA, /* U+00d02f: */
+ RTUNI_ALPHA, /* U+00d030: */
+ RTUNI_ALPHA, /* U+00d031: */
+ RTUNI_ALPHA, /* U+00d032: */
+ RTUNI_ALPHA, /* U+00d033: */
+ RTUNI_ALPHA, /* U+00d034: */
+ RTUNI_ALPHA, /* U+00d035: */
+ RTUNI_ALPHA, /* U+00d036: */
+ RTUNI_ALPHA, /* U+00d037: */
+ RTUNI_ALPHA, /* U+00d038: */
+ RTUNI_ALPHA, /* U+00d039: */
+ RTUNI_ALPHA, /* U+00d03a: */
+ RTUNI_ALPHA, /* U+00d03b: */
+ RTUNI_ALPHA, /* U+00d03c: */
+ RTUNI_ALPHA, /* U+00d03d: */
+ RTUNI_ALPHA, /* U+00d03e: */
+ RTUNI_ALPHA, /* U+00d03f: */
+ RTUNI_ALPHA, /* U+00d040: */
+ RTUNI_ALPHA, /* U+00d041: */
+ RTUNI_ALPHA, /* U+00d042: */
+ RTUNI_ALPHA, /* U+00d043: */
+ RTUNI_ALPHA, /* U+00d044: */
+ RTUNI_ALPHA, /* U+00d045: */
+ RTUNI_ALPHA, /* U+00d046: */
+ RTUNI_ALPHA, /* U+00d047: */
+ RTUNI_ALPHA, /* U+00d048: */
+ RTUNI_ALPHA, /* U+00d049: */
+ RTUNI_ALPHA, /* U+00d04a: */
+ RTUNI_ALPHA, /* U+00d04b: */
+ RTUNI_ALPHA, /* U+00d04c: */
+ RTUNI_ALPHA, /* U+00d04d: */
+ RTUNI_ALPHA, /* U+00d04e: */
+ RTUNI_ALPHA, /* U+00d04f: */
+ RTUNI_ALPHA, /* U+00d050: */
+ RTUNI_ALPHA, /* U+00d051: */
+ RTUNI_ALPHA, /* U+00d052: */
+ RTUNI_ALPHA, /* U+00d053: */
+ RTUNI_ALPHA, /* U+00d054: */
+ RTUNI_ALPHA, /* U+00d055: */
+ RTUNI_ALPHA, /* U+00d056: */
+ RTUNI_ALPHA, /* U+00d057: */
+ RTUNI_ALPHA, /* U+00d058: */
+ RTUNI_ALPHA, /* U+00d059: */
+ RTUNI_ALPHA, /* U+00d05a: */
+ RTUNI_ALPHA, /* U+00d05b: */
+ RTUNI_ALPHA, /* U+00d05c: */
+ RTUNI_ALPHA, /* U+00d05d: */
+ RTUNI_ALPHA, /* U+00d05e: */
+ RTUNI_ALPHA, /* U+00d05f: */
+ RTUNI_ALPHA, /* U+00d060: */
+ RTUNI_ALPHA, /* U+00d061: */
+ RTUNI_ALPHA, /* U+00d062: */
+ RTUNI_ALPHA, /* U+00d063: */
+ RTUNI_ALPHA, /* U+00d064: */
+ RTUNI_ALPHA, /* U+00d065: */
+ RTUNI_ALPHA, /* U+00d066: */
+ RTUNI_ALPHA, /* U+00d067: */
+ RTUNI_ALPHA, /* U+00d068: */
+ RTUNI_ALPHA, /* U+00d069: */
+ RTUNI_ALPHA, /* U+00d06a: */
+ RTUNI_ALPHA, /* U+00d06b: */
+ RTUNI_ALPHA, /* U+00d06c: */
+ RTUNI_ALPHA, /* U+00d06d: */
+ RTUNI_ALPHA, /* U+00d06e: */
+ RTUNI_ALPHA, /* U+00d06f: */
+ RTUNI_ALPHA, /* U+00d070: */
+ RTUNI_ALPHA, /* U+00d071: */
+ RTUNI_ALPHA, /* U+00d072: */
+ RTUNI_ALPHA, /* U+00d073: */
+ RTUNI_ALPHA, /* U+00d074: */
+ RTUNI_ALPHA, /* U+00d075: */
+ RTUNI_ALPHA, /* U+00d076: */
+ RTUNI_ALPHA, /* U+00d077: */
+ RTUNI_ALPHA, /* U+00d078: */
+ RTUNI_ALPHA, /* U+00d079: */
+ RTUNI_ALPHA, /* U+00d07a: */
+ RTUNI_ALPHA, /* U+00d07b: */
+ RTUNI_ALPHA, /* U+00d07c: */
+ RTUNI_ALPHA, /* U+00d07d: */
+ RTUNI_ALPHA, /* U+00d07e: */
+ RTUNI_ALPHA, /* U+00d07f: */
+ RTUNI_ALPHA, /* U+00d080: */
+ RTUNI_ALPHA, /* U+00d081: */
+ RTUNI_ALPHA, /* U+00d082: */
+ RTUNI_ALPHA, /* U+00d083: */
+ RTUNI_ALPHA, /* U+00d084: */
+ RTUNI_ALPHA, /* U+00d085: */
+ RTUNI_ALPHA, /* U+00d086: */
+ RTUNI_ALPHA, /* U+00d087: */
+ RTUNI_ALPHA, /* U+00d088: */
+ RTUNI_ALPHA, /* U+00d089: */
+ RTUNI_ALPHA, /* U+00d08a: */
+ RTUNI_ALPHA, /* U+00d08b: */
+ RTUNI_ALPHA, /* U+00d08c: */
+ RTUNI_ALPHA, /* U+00d08d: */
+ RTUNI_ALPHA, /* U+00d08e: */
+ RTUNI_ALPHA, /* U+00d08f: */
+ RTUNI_ALPHA, /* U+00d090: */
+ RTUNI_ALPHA, /* U+00d091: */
+ RTUNI_ALPHA, /* U+00d092: */
+ RTUNI_ALPHA, /* U+00d093: */
+ RTUNI_ALPHA, /* U+00d094: */
+ RTUNI_ALPHA, /* U+00d095: */
+ RTUNI_ALPHA, /* U+00d096: */
+ RTUNI_ALPHA, /* U+00d097: */
+ RTUNI_ALPHA, /* U+00d098: */
+ RTUNI_ALPHA, /* U+00d099: */
+ RTUNI_ALPHA, /* U+00d09a: */
+ RTUNI_ALPHA, /* U+00d09b: */
+ RTUNI_ALPHA, /* U+00d09c: */
+ RTUNI_ALPHA, /* U+00d09d: */
+ RTUNI_ALPHA, /* U+00d09e: */
+ RTUNI_ALPHA, /* U+00d09f: */
+ RTUNI_ALPHA, /* U+00d0a0: */
+ RTUNI_ALPHA, /* U+00d0a1: */
+ RTUNI_ALPHA, /* U+00d0a2: */
+ RTUNI_ALPHA, /* U+00d0a3: */
+ RTUNI_ALPHA, /* U+00d0a4: */
+ RTUNI_ALPHA, /* U+00d0a5: */
+ RTUNI_ALPHA, /* U+00d0a6: */
+ RTUNI_ALPHA, /* U+00d0a7: */
+ RTUNI_ALPHA, /* U+00d0a8: */
+ RTUNI_ALPHA, /* U+00d0a9: */
+ RTUNI_ALPHA, /* U+00d0aa: */
+ RTUNI_ALPHA, /* U+00d0ab: */
+ RTUNI_ALPHA, /* U+00d0ac: */
+ RTUNI_ALPHA, /* U+00d0ad: */
+ RTUNI_ALPHA, /* U+00d0ae: */
+ RTUNI_ALPHA, /* U+00d0af: */
+ RTUNI_ALPHA, /* U+00d0b0: */
+ RTUNI_ALPHA, /* U+00d0b1: */
+ RTUNI_ALPHA, /* U+00d0b2: */
+ RTUNI_ALPHA, /* U+00d0b3: */
+ RTUNI_ALPHA, /* U+00d0b4: */
+ RTUNI_ALPHA, /* U+00d0b5: */
+ RTUNI_ALPHA, /* U+00d0b6: */
+ RTUNI_ALPHA, /* U+00d0b7: */
+ RTUNI_ALPHA, /* U+00d0b8: */
+ RTUNI_ALPHA, /* U+00d0b9: */
+ RTUNI_ALPHA, /* U+00d0ba: */
+ RTUNI_ALPHA, /* U+00d0bb: */
+ RTUNI_ALPHA, /* U+00d0bc: */
+ RTUNI_ALPHA, /* U+00d0bd: */
+ RTUNI_ALPHA, /* U+00d0be: */
+ RTUNI_ALPHA, /* U+00d0bf: */
+ RTUNI_ALPHA, /* U+00d0c0: */
+ RTUNI_ALPHA, /* U+00d0c1: */
+ RTUNI_ALPHA, /* U+00d0c2: */
+ RTUNI_ALPHA, /* U+00d0c3: */
+ RTUNI_ALPHA, /* U+00d0c4: */
+ RTUNI_ALPHA, /* U+00d0c5: */
+ RTUNI_ALPHA, /* U+00d0c6: */
+ RTUNI_ALPHA, /* U+00d0c7: */
+ RTUNI_ALPHA, /* U+00d0c8: */
+ RTUNI_ALPHA, /* U+00d0c9: */
+ RTUNI_ALPHA, /* U+00d0ca: */
+ RTUNI_ALPHA, /* U+00d0cb: */
+ RTUNI_ALPHA, /* U+00d0cc: */
+ RTUNI_ALPHA, /* U+00d0cd: */
+ RTUNI_ALPHA, /* U+00d0ce: */
+ RTUNI_ALPHA, /* U+00d0cf: */
+ RTUNI_ALPHA, /* U+00d0d0: */
+ RTUNI_ALPHA, /* U+00d0d1: */
+ RTUNI_ALPHA, /* U+00d0d2: */
+ RTUNI_ALPHA, /* U+00d0d3: */
+ RTUNI_ALPHA, /* U+00d0d4: */
+ RTUNI_ALPHA, /* U+00d0d5: */
+ RTUNI_ALPHA, /* U+00d0d6: */
+ RTUNI_ALPHA, /* U+00d0d7: */
+ RTUNI_ALPHA, /* U+00d0d8: */
+ RTUNI_ALPHA, /* U+00d0d9: */
+ RTUNI_ALPHA, /* U+00d0da: */
+ RTUNI_ALPHA, /* U+00d0db: */
+ RTUNI_ALPHA, /* U+00d0dc: */
+ RTUNI_ALPHA, /* U+00d0dd: */
+ RTUNI_ALPHA, /* U+00d0de: */
+ RTUNI_ALPHA, /* U+00d0df: */
+ RTUNI_ALPHA, /* U+00d0e0: */
+ RTUNI_ALPHA, /* U+00d0e1: */
+ RTUNI_ALPHA, /* U+00d0e2: */
+ RTUNI_ALPHA, /* U+00d0e3: */
+ RTUNI_ALPHA, /* U+00d0e4: */
+ RTUNI_ALPHA, /* U+00d0e5: */
+ RTUNI_ALPHA, /* U+00d0e6: */
+ RTUNI_ALPHA, /* U+00d0e7: */
+ RTUNI_ALPHA, /* U+00d0e8: */
+ RTUNI_ALPHA, /* U+00d0e9: */
+ RTUNI_ALPHA, /* U+00d0ea: */
+ RTUNI_ALPHA, /* U+00d0eb: */
+ RTUNI_ALPHA, /* U+00d0ec: */
+ RTUNI_ALPHA, /* U+00d0ed: */
+ RTUNI_ALPHA, /* U+00d0ee: */
+ RTUNI_ALPHA, /* U+00d0ef: */
+ RTUNI_ALPHA, /* U+00d0f0: */
+ RTUNI_ALPHA, /* U+00d0f1: */
+ RTUNI_ALPHA, /* U+00d0f2: */
+ RTUNI_ALPHA, /* U+00d0f3: */
+ RTUNI_ALPHA, /* U+00d0f4: */
+ RTUNI_ALPHA, /* U+00d0f5: */
+ RTUNI_ALPHA, /* U+00d0f6: */
+ RTUNI_ALPHA, /* U+00d0f7: */
+ RTUNI_ALPHA, /* U+00d0f8: */
+ RTUNI_ALPHA, /* U+00d0f9: */
+ RTUNI_ALPHA, /* U+00d0fa: */
+ RTUNI_ALPHA, /* U+00d0fb: */
+ RTUNI_ALPHA, /* U+00d0fc: */
+ RTUNI_ALPHA, /* U+00d0fd: */
+ RTUNI_ALPHA, /* U+00d0fe: */
+ RTUNI_ALPHA, /* U+00d0ff: */
+ RTUNI_ALPHA, /* U+00d100: */
+ RTUNI_ALPHA, /* U+00d101: */
+ RTUNI_ALPHA, /* U+00d102: */
+ RTUNI_ALPHA, /* U+00d103: */
+ RTUNI_ALPHA, /* U+00d104: */
+ RTUNI_ALPHA, /* U+00d105: */
+ RTUNI_ALPHA, /* U+00d106: */
+ RTUNI_ALPHA, /* U+00d107: */
+ RTUNI_ALPHA, /* U+00d108: */
+ RTUNI_ALPHA, /* U+00d109: */
+ RTUNI_ALPHA, /* U+00d10a: */
+ RTUNI_ALPHA, /* U+00d10b: */
+ RTUNI_ALPHA, /* U+00d10c: */
+ RTUNI_ALPHA, /* U+00d10d: */
+ RTUNI_ALPHA, /* U+00d10e: */
+ RTUNI_ALPHA, /* U+00d10f: */
+ RTUNI_ALPHA, /* U+00d110: */
+ RTUNI_ALPHA, /* U+00d111: */
+ RTUNI_ALPHA, /* U+00d112: */
+ RTUNI_ALPHA, /* U+00d113: */
+ RTUNI_ALPHA, /* U+00d114: */
+ RTUNI_ALPHA, /* U+00d115: */
+ RTUNI_ALPHA, /* U+00d116: */
+ RTUNI_ALPHA, /* U+00d117: */
+ RTUNI_ALPHA, /* U+00d118: */
+ RTUNI_ALPHA, /* U+00d119: */
+ RTUNI_ALPHA, /* U+00d11a: */
+ RTUNI_ALPHA, /* U+00d11b: */
+ RTUNI_ALPHA, /* U+00d11c: */
+ RTUNI_ALPHA, /* U+00d11d: */
+ RTUNI_ALPHA, /* U+00d11e: */
+ RTUNI_ALPHA, /* U+00d11f: */
+ RTUNI_ALPHA, /* U+00d120: */
+ RTUNI_ALPHA, /* U+00d121: */
+ RTUNI_ALPHA, /* U+00d122: */
+ RTUNI_ALPHA, /* U+00d123: */
+ RTUNI_ALPHA, /* U+00d124: */
+ RTUNI_ALPHA, /* U+00d125: */
+ RTUNI_ALPHA, /* U+00d126: */
+ RTUNI_ALPHA, /* U+00d127: */
+ RTUNI_ALPHA, /* U+00d128: */
+ RTUNI_ALPHA, /* U+00d129: */
+ RTUNI_ALPHA, /* U+00d12a: */
+ RTUNI_ALPHA, /* U+00d12b: */
+ RTUNI_ALPHA, /* U+00d12c: */
+ RTUNI_ALPHA, /* U+00d12d: */
+ RTUNI_ALPHA, /* U+00d12e: */
+ RTUNI_ALPHA, /* U+00d12f: */
+ RTUNI_ALPHA, /* U+00d130: */
+ RTUNI_ALPHA, /* U+00d131: */
+ RTUNI_ALPHA, /* U+00d132: */
+ RTUNI_ALPHA, /* U+00d133: */
+ RTUNI_ALPHA, /* U+00d134: */
+ RTUNI_ALPHA, /* U+00d135: */
+ RTUNI_ALPHA, /* U+00d136: */
+ RTUNI_ALPHA, /* U+00d137: */
+ RTUNI_ALPHA, /* U+00d138: */
+ RTUNI_ALPHA, /* U+00d139: */
+ RTUNI_ALPHA, /* U+00d13a: */
+ RTUNI_ALPHA, /* U+00d13b: */
+ RTUNI_ALPHA, /* U+00d13c: */
+ RTUNI_ALPHA, /* U+00d13d: */
+ RTUNI_ALPHA, /* U+00d13e: */
+ RTUNI_ALPHA, /* U+00d13f: */
+ RTUNI_ALPHA, /* U+00d140: */
+ RTUNI_ALPHA, /* U+00d141: */
+ RTUNI_ALPHA, /* U+00d142: */
+ RTUNI_ALPHA, /* U+00d143: */
+ RTUNI_ALPHA, /* U+00d144: */
+ RTUNI_ALPHA, /* U+00d145: */
+ RTUNI_ALPHA, /* U+00d146: */
+ RTUNI_ALPHA, /* U+00d147: */
+ RTUNI_ALPHA, /* U+00d148: */
+ RTUNI_ALPHA, /* U+00d149: */
+ RTUNI_ALPHA, /* U+00d14a: */
+ RTUNI_ALPHA, /* U+00d14b: */
+ RTUNI_ALPHA, /* U+00d14c: */
+ RTUNI_ALPHA, /* U+00d14d: */
+ RTUNI_ALPHA, /* U+00d14e: */
+ RTUNI_ALPHA, /* U+00d14f: */
+ RTUNI_ALPHA, /* U+00d150: */
+ RTUNI_ALPHA, /* U+00d151: */
+ RTUNI_ALPHA, /* U+00d152: */
+ RTUNI_ALPHA, /* U+00d153: */
+ RTUNI_ALPHA, /* U+00d154: */
+ RTUNI_ALPHA, /* U+00d155: */
+ RTUNI_ALPHA, /* U+00d156: */
+ RTUNI_ALPHA, /* U+00d157: */
+ RTUNI_ALPHA, /* U+00d158: */
+ RTUNI_ALPHA, /* U+00d159: */
+ RTUNI_ALPHA, /* U+00d15a: */
+ RTUNI_ALPHA, /* U+00d15b: */
+ RTUNI_ALPHA, /* U+00d15c: */
+ RTUNI_ALPHA, /* U+00d15d: */
+ RTUNI_ALPHA, /* U+00d15e: */
+ RTUNI_ALPHA, /* U+00d15f: */
+ RTUNI_ALPHA, /* U+00d160: */
+ RTUNI_ALPHA, /* U+00d161: */
+ RTUNI_ALPHA, /* U+00d162: */
+ RTUNI_ALPHA, /* U+00d163: */
+ RTUNI_ALPHA, /* U+00d164: */
+ RTUNI_ALPHA, /* U+00d165: */
+ RTUNI_ALPHA, /* U+00d166: */
+ RTUNI_ALPHA, /* U+00d167: */
+ RTUNI_ALPHA, /* U+00d168: */
+ RTUNI_ALPHA, /* U+00d169: */
+ RTUNI_ALPHA, /* U+00d16a: */
+ RTUNI_ALPHA, /* U+00d16b: */
+ RTUNI_ALPHA, /* U+00d16c: */
+ RTUNI_ALPHA, /* U+00d16d: */
+ RTUNI_ALPHA, /* U+00d16e: */
+ RTUNI_ALPHA, /* U+00d16f: */
+ RTUNI_ALPHA, /* U+00d170: */
+ RTUNI_ALPHA, /* U+00d171: */
+ RTUNI_ALPHA, /* U+00d172: */
+ RTUNI_ALPHA, /* U+00d173: */
+ RTUNI_ALPHA, /* U+00d174: */
+ RTUNI_ALPHA, /* U+00d175: */
+ RTUNI_ALPHA, /* U+00d176: */
+ RTUNI_ALPHA, /* U+00d177: */
+ RTUNI_ALPHA, /* U+00d178: */
+ RTUNI_ALPHA, /* U+00d179: */
+ RTUNI_ALPHA, /* U+00d17a: */
+ RTUNI_ALPHA, /* U+00d17b: */
+ RTUNI_ALPHA, /* U+00d17c: */
+ RTUNI_ALPHA, /* U+00d17d: */
+ RTUNI_ALPHA, /* U+00d17e: */
+ RTUNI_ALPHA, /* U+00d17f: */
+ RTUNI_ALPHA, /* U+00d180: */
+ RTUNI_ALPHA, /* U+00d181: */
+ RTUNI_ALPHA, /* U+00d182: */
+ RTUNI_ALPHA, /* U+00d183: */
+ RTUNI_ALPHA, /* U+00d184: */
+ RTUNI_ALPHA, /* U+00d185: */
+ RTUNI_ALPHA, /* U+00d186: */
+ RTUNI_ALPHA, /* U+00d187: */
+ RTUNI_ALPHA, /* U+00d188: */
+ RTUNI_ALPHA, /* U+00d189: */
+ RTUNI_ALPHA, /* U+00d18a: */
+ RTUNI_ALPHA, /* U+00d18b: */
+ RTUNI_ALPHA, /* U+00d18c: */
+ RTUNI_ALPHA, /* U+00d18d: */
+ RTUNI_ALPHA, /* U+00d18e: */
+ RTUNI_ALPHA, /* U+00d18f: */
+ RTUNI_ALPHA, /* U+00d190: */
+ RTUNI_ALPHA, /* U+00d191: */
+ RTUNI_ALPHA, /* U+00d192: */
+ RTUNI_ALPHA, /* U+00d193: */
+ RTUNI_ALPHA, /* U+00d194: */
+ RTUNI_ALPHA, /* U+00d195: */
+ RTUNI_ALPHA, /* U+00d196: */
+ RTUNI_ALPHA, /* U+00d197: */
+ RTUNI_ALPHA, /* U+00d198: */
+ RTUNI_ALPHA, /* U+00d199: */
+ RTUNI_ALPHA, /* U+00d19a: */
+ RTUNI_ALPHA, /* U+00d19b: */
+ RTUNI_ALPHA, /* U+00d19c: */
+ RTUNI_ALPHA, /* U+00d19d: */
+ RTUNI_ALPHA, /* U+00d19e: */
+ RTUNI_ALPHA, /* U+00d19f: */
+ RTUNI_ALPHA, /* U+00d1a0: */
+ RTUNI_ALPHA, /* U+00d1a1: */
+ RTUNI_ALPHA, /* U+00d1a2: */
+ RTUNI_ALPHA, /* U+00d1a3: */
+ RTUNI_ALPHA, /* U+00d1a4: */
+ RTUNI_ALPHA, /* U+00d1a5: */
+ RTUNI_ALPHA, /* U+00d1a6: */
+ RTUNI_ALPHA, /* U+00d1a7: */
+ RTUNI_ALPHA, /* U+00d1a8: */
+ RTUNI_ALPHA, /* U+00d1a9: */
+ RTUNI_ALPHA, /* U+00d1aa: */
+ RTUNI_ALPHA, /* U+00d1ab: */
+ RTUNI_ALPHA, /* U+00d1ac: */
+ RTUNI_ALPHA, /* U+00d1ad: */
+ RTUNI_ALPHA, /* U+00d1ae: */
+ RTUNI_ALPHA, /* U+00d1af: */
+ RTUNI_ALPHA, /* U+00d1b0: */
+ RTUNI_ALPHA, /* U+00d1b1: */
+ RTUNI_ALPHA, /* U+00d1b2: */
+ RTUNI_ALPHA, /* U+00d1b3: */
+ RTUNI_ALPHA, /* U+00d1b4: */
+ RTUNI_ALPHA, /* U+00d1b5: */
+ RTUNI_ALPHA, /* U+00d1b6: */
+ RTUNI_ALPHA, /* U+00d1b7: */
+ RTUNI_ALPHA, /* U+00d1b8: */
+ RTUNI_ALPHA, /* U+00d1b9: */
+ RTUNI_ALPHA, /* U+00d1ba: */
+ RTUNI_ALPHA, /* U+00d1bb: */
+ RTUNI_ALPHA, /* U+00d1bc: */
+ RTUNI_ALPHA, /* U+00d1bd: */
+ RTUNI_ALPHA, /* U+00d1be: */
+ RTUNI_ALPHA, /* U+00d1bf: */
+ RTUNI_ALPHA, /* U+00d1c0: */
+ RTUNI_ALPHA, /* U+00d1c1: */
+ RTUNI_ALPHA, /* U+00d1c2: */
+ RTUNI_ALPHA, /* U+00d1c3: */
+ RTUNI_ALPHA, /* U+00d1c4: */
+ RTUNI_ALPHA, /* U+00d1c5: */
+ RTUNI_ALPHA, /* U+00d1c6: */
+ RTUNI_ALPHA, /* U+00d1c7: */
+ RTUNI_ALPHA, /* U+00d1c8: */
+ RTUNI_ALPHA, /* U+00d1c9: */
+ RTUNI_ALPHA, /* U+00d1ca: */
+ RTUNI_ALPHA, /* U+00d1cb: */
+ RTUNI_ALPHA, /* U+00d1cc: */
+ RTUNI_ALPHA, /* U+00d1cd: */
+ RTUNI_ALPHA, /* U+00d1ce: */
+ RTUNI_ALPHA, /* U+00d1cf: */
+ RTUNI_ALPHA, /* U+00d1d0: */
+ RTUNI_ALPHA, /* U+00d1d1: */
+ RTUNI_ALPHA, /* U+00d1d2: */
+ RTUNI_ALPHA, /* U+00d1d3: */
+ RTUNI_ALPHA, /* U+00d1d4: */
+ RTUNI_ALPHA, /* U+00d1d5: */
+ RTUNI_ALPHA, /* U+00d1d6: */
+ RTUNI_ALPHA, /* U+00d1d7: */
+ RTUNI_ALPHA, /* U+00d1d8: */
+ RTUNI_ALPHA, /* U+00d1d9: */
+ RTUNI_ALPHA, /* U+00d1da: */
+ RTUNI_ALPHA, /* U+00d1db: */
+ RTUNI_ALPHA, /* U+00d1dc: */
+ RTUNI_ALPHA, /* U+00d1dd: */
+ RTUNI_ALPHA, /* U+00d1de: */
+ RTUNI_ALPHA, /* U+00d1df: */
+ RTUNI_ALPHA, /* U+00d1e0: */
+ RTUNI_ALPHA, /* U+00d1e1: */
+ RTUNI_ALPHA, /* U+00d1e2: */
+ RTUNI_ALPHA, /* U+00d1e3: */
+ RTUNI_ALPHA, /* U+00d1e4: */
+ RTUNI_ALPHA, /* U+00d1e5: */
+ RTUNI_ALPHA, /* U+00d1e6: */
+ RTUNI_ALPHA, /* U+00d1e7: */
+ RTUNI_ALPHA, /* U+00d1e8: */
+ RTUNI_ALPHA, /* U+00d1e9: */
+ RTUNI_ALPHA, /* U+00d1ea: */
+ RTUNI_ALPHA, /* U+00d1eb: */
+ RTUNI_ALPHA, /* U+00d1ec: */
+ RTUNI_ALPHA, /* U+00d1ed: */
+ RTUNI_ALPHA, /* U+00d1ee: */
+ RTUNI_ALPHA, /* U+00d1ef: */
+ RTUNI_ALPHA, /* U+00d1f0: */
+ RTUNI_ALPHA, /* U+00d1f1: */
+ RTUNI_ALPHA, /* U+00d1f2: */
+ RTUNI_ALPHA, /* U+00d1f3: */
+ RTUNI_ALPHA, /* U+00d1f4: */
+ RTUNI_ALPHA, /* U+00d1f5: */
+ RTUNI_ALPHA, /* U+00d1f6: */
+ RTUNI_ALPHA, /* U+00d1f7: */
+ RTUNI_ALPHA, /* U+00d1f8: */
+ RTUNI_ALPHA, /* U+00d1f9: */
+ RTUNI_ALPHA, /* U+00d1fa: */
+ RTUNI_ALPHA, /* U+00d1fb: */
+ RTUNI_ALPHA, /* U+00d1fc: */
+ RTUNI_ALPHA, /* U+00d1fd: */
+ RTUNI_ALPHA, /* U+00d1fe: */
+ RTUNI_ALPHA, /* U+00d1ff: */
+ RTUNI_ALPHA, /* U+00d200: */
+ RTUNI_ALPHA, /* U+00d201: */
+ RTUNI_ALPHA, /* U+00d202: */
+ RTUNI_ALPHA, /* U+00d203: */
+ RTUNI_ALPHA, /* U+00d204: */
+ RTUNI_ALPHA, /* U+00d205: */
+ RTUNI_ALPHA, /* U+00d206: */
+ RTUNI_ALPHA, /* U+00d207: */
+ RTUNI_ALPHA, /* U+00d208: */
+ RTUNI_ALPHA, /* U+00d209: */
+ RTUNI_ALPHA, /* U+00d20a: */
+ RTUNI_ALPHA, /* U+00d20b: */
+ RTUNI_ALPHA, /* U+00d20c: */
+ RTUNI_ALPHA, /* U+00d20d: */
+ RTUNI_ALPHA, /* U+00d20e: */
+ RTUNI_ALPHA, /* U+00d20f: */
+ RTUNI_ALPHA, /* U+00d210: */
+ RTUNI_ALPHA, /* U+00d211: */
+ RTUNI_ALPHA, /* U+00d212: */
+ RTUNI_ALPHA, /* U+00d213: */
+ RTUNI_ALPHA, /* U+00d214: */
+ RTUNI_ALPHA, /* U+00d215: */
+ RTUNI_ALPHA, /* U+00d216: */
+ RTUNI_ALPHA, /* U+00d217: */
+ RTUNI_ALPHA, /* U+00d218: */
+ RTUNI_ALPHA, /* U+00d219: */
+ RTUNI_ALPHA, /* U+00d21a: */
+ RTUNI_ALPHA, /* U+00d21b: */
+ RTUNI_ALPHA, /* U+00d21c: */
+ RTUNI_ALPHA, /* U+00d21d: */
+ RTUNI_ALPHA, /* U+00d21e: */
+ RTUNI_ALPHA, /* U+00d21f: */
+ RTUNI_ALPHA, /* U+00d220: */
+ RTUNI_ALPHA, /* U+00d221: */
+ RTUNI_ALPHA, /* U+00d222: */
+ RTUNI_ALPHA, /* U+00d223: */
+ RTUNI_ALPHA, /* U+00d224: */
+ RTUNI_ALPHA, /* U+00d225: */
+ RTUNI_ALPHA, /* U+00d226: */
+ RTUNI_ALPHA, /* U+00d227: */
+ RTUNI_ALPHA, /* U+00d228: */
+ RTUNI_ALPHA, /* U+00d229: */
+ RTUNI_ALPHA, /* U+00d22a: */
+ RTUNI_ALPHA, /* U+00d22b: */
+ RTUNI_ALPHA, /* U+00d22c: */
+ RTUNI_ALPHA, /* U+00d22d: */
+ RTUNI_ALPHA, /* U+00d22e: */
+ RTUNI_ALPHA, /* U+00d22f: */
+ RTUNI_ALPHA, /* U+00d230: */
+ RTUNI_ALPHA, /* U+00d231: */
+ RTUNI_ALPHA, /* U+00d232: */
+ RTUNI_ALPHA, /* U+00d233: */
+ RTUNI_ALPHA, /* U+00d234: */
+ RTUNI_ALPHA, /* U+00d235: */
+ RTUNI_ALPHA, /* U+00d236: */
+ RTUNI_ALPHA, /* U+00d237: */
+ RTUNI_ALPHA, /* U+00d238: */
+ RTUNI_ALPHA, /* U+00d239: */
+ RTUNI_ALPHA, /* U+00d23a: */
+ RTUNI_ALPHA, /* U+00d23b: */
+ RTUNI_ALPHA, /* U+00d23c: */
+ RTUNI_ALPHA, /* U+00d23d: */
+ RTUNI_ALPHA, /* U+00d23e: */
+ RTUNI_ALPHA, /* U+00d23f: */
+ RTUNI_ALPHA, /* U+00d240: */
+ RTUNI_ALPHA, /* U+00d241: */
+ RTUNI_ALPHA, /* U+00d242: */
+ RTUNI_ALPHA, /* U+00d243: */
+ RTUNI_ALPHA, /* U+00d244: */
+ RTUNI_ALPHA, /* U+00d245: */
+ RTUNI_ALPHA, /* U+00d246: */
+ RTUNI_ALPHA, /* U+00d247: */
+ RTUNI_ALPHA, /* U+00d248: */
+ RTUNI_ALPHA, /* U+00d249: */
+ RTUNI_ALPHA, /* U+00d24a: */
+ RTUNI_ALPHA, /* U+00d24b: */
+ RTUNI_ALPHA, /* U+00d24c: */
+ RTUNI_ALPHA, /* U+00d24d: */
+ RTUNI_ALPHA, /* U+00d24e: */
+ RTUNI_ALPHA, /* U+00d24f: */
+ RTUNI_ALPHA, /* U+00d250: */
+ RTUNI_ALPHA, /* U+00d251: */
+ RTUNI_ALPHA, /* U+00d252: */
+ RTUNI_ALPHA, /* U+00d253: */
+ RTUNI_ALPHA, /* U+00d254: */
+ RTUNI_ALPHA, /* U+00d255: */
+ RTUNI_ALPHA, /* U+00d256: */
+ RTUNI_ALPHA, /* U+00d257: */
+ RTUNI_ALPHA, /* U+00d258: */
+ RTUNI_ALPHA, /* U+00d259: */
+ RTUNI_ALPHA, /* U+00d25a: */
+ RTUNI_ALPHA, /* U+00d25b: */
+ RTUNI_ALPHA, /* U+00d25c: */
+ RTUNI_ALPHA, /* U+00d25d: */
+ RTUNI_ALPHA, /* U+00d25e: */
+ RTUNI_ALPHA, /* U+00d25f: */
+ RTUNI_ALPHA, /* U+00d260: */
+ RTUNI_ALPHA, /* U+00d261: */
+ RTUNI_ALPHA, /* U+00d262: */
+ RTUNI_ALPHA, /* U+00d263: */
+ RTUNI_ALPHA, /* U+00d264: */
+ RTUNI_ALPHA, /* U+00d265: */
+ RTUNI_ALPHA, /* U+00d266: */
+ RTUNI_ALPHA, /* U+00d267: */
+ RTUNI_ALPHA, /* U+00d268: */
+ RTUNI_ALPHA, /* U+00d269: */
+ RTUNI_ALPHA, /* U+00d26a: */
+ RTUNI_ALPHA, /* U+00d26b: */
+ RTUNI_ALPHA, /* U+00d26c: */
+ RTUNI_ALPHA, /* U+00d26d: */
+ RTUNI_ALPHA, /* U+00d26e: */
+ RTUNI_ALPHA, /* U+00d26f: */
+ RTUNI_ALPHA, /* U+00d270: */
+ RTUNI_ALPHA, /* U+00d271: */
+ RTUNI_ALPHA, /* U+00d272: */
+ RTUNI_ALPHA, /* U+00d273: */
+ RTUNI_ALPHA, /* U+00d274: */
+ RTUNI_ALPHA, /* U+00d275: */
+ RTUNI_ALPHA, /* U+00d276: */
+ RTUNI_ALPHA, /* U+00d277: */
+ RTUNI_ALPHA, /* U+00d278: */
+ RTUNI_ALPHA, /* U+00d279: */
+ RTUNI_ALPHA, /* U+00d27a: */
+ RTUNI_ALPHA, /* U+00d27b: */
+ RTUNI_ALPHA, /* U+00d27c: */
+ RTUNI_ALPHA, /* U+00d27d: */
+ RTUNI_ALPHA, /* U+00d27e: */
+ RTUNI_ALPHA, /* U+00d27f: */
+ RTUNI_ALPHA, /* U+00d280: */
+ RTUNI_ALPHA, /* U+00d281: */
+ RTUNI_ALPHA, /* U+00d282: */
+ RTUNI_ALPHA, /* U+00d283: */
+ RTUNI_ALPHA, /* U+00d284: */
+ RTUNI_ALPHA, /* U+00d285: */
+ RTUNI_ALPHA, /* U+00d286: */
+ RTUNI_ALPHA, /* U+00d287: */
+ RTUNI_ALPHA, /* U+00d288: */
+ RTUNI_ALPHA, /* U+00d289: */
+ RTUNI_ALPHA, /* U+00d28a: */
+ RTUNI_ALPHA, /* U+00d28b: */
+ RTUNI_ALPHA, /* U+00d28c: */
+ RTUNI_ALPHA, /* U+00d28d: */
+ RTUNI_ALPHA, /* U+00d28e: */
+ RTUNI_ALPHA, /* U+00d28f: */
+ RTUNI_ALPHA, /* U+00d290: */
+ RTUNI_ALPHA, /* U+00d291: */
+ RTUNI_ALPHA, /* U+00d292: */
+ RTUNI_ALPHA, /* U+00d293: */
+ RTUNI_ALPHA, /* U+00d294: */
+ RTUNI_ALPHA, /* U+00d295: */
+ RTUNI_ALPHA, /* U+00d296: */
+ RTUNI_ALPHA, /* U+00d297: */
+ RTUNI_ALPHA, /* U+00d298: */
+ RTUNI_ALPHA, /* U+00d299: */
+ RTUNI_ALPHA, /* U+00d29a: */
+ RTUNI_ALPHA, /* U+00d29b: */
+ RTUNI_ALPHA, /* U+00d29c: */
+ RTUNI_ALPHA, /* U+00d29d: */
+ RTUNI_ALPHA, /* U+00d29e: */
+ RTUNI_ALPHA, /* U+00d29f: */
+ RTUNI_ALPHA, /* U+00d2a0: */
+ RTUNI_ALPHA, /* U+00d2a1: */
+ RTUNI_ALPHA, /* U+00d2a2: */
+ RTUNI_ALPHA, /* U+00d2a3: */
+ RTUNI_ALPHA, /* U+00d2a4: */
+ RTUNI_ALPHA, /* U+00d2a5: */
+ RTUNI_ALPHA, /* U+00d2a6: */
+ RTUNI_ALPHA, /* U+00d2a7: */
+ RTUNI_ALPHA, /* U+00d2a8: */
+ RTUNI_ALPHA, /* U+00d2a9: */
+ RTUNI_ALPHA, /* U+00d2aa: */
+ RTUNI_ALPHA, /* U+00d2ab: */
+ RTUNI_ALPHA, /* U+00d2ac: */
+ RTUNI_ALPHA, /* U+00d2ad: */
+ RTUNI_ALPHA, /* U+00d2ae: */
+ RTUNI_ALPHA, /* U+00d2af: */
+ RTUNI_ALPHA, /* U+00d2b0: */
+ RTUNI_ALPHA, /* U+00d2b1: */
+ RTUNI_ALPHA, /* U+00d2b2: */
+ RTUNI_ALPHA, /* U+00d2b3: */
+ RTUNI_ALPHA, /* U+00d2b4: */
+ RTUNI_ALPHA, /* U+00d2b5: */
+ RTUNI_ALPHA, /* U+00d2b6: */
+ RTUNI_ALPHA, /* U+00d2b7: */
+ RTUNI_ALPHA, /* U+00d2b8: */
+ RTUNI_ALPHA, /* U+00d2b9: */
+ RTUNI_ALPHA, /* U+00d2ba: */
+ RTUNI_ALPHA, /* U+00d2bb: */
+ RTUNI_ALPHA, /* U+00d2bc: */
+ RTUNI_ALPHA, /* U+00d2bd: */
+ RTUNI_ALPHA, /* U+00d2be: */
+ RTUNI_ALPHA, /* U+00d2bf: */
+ RTUNI_ALPHA, /* U+00d2c0: */
+ RTUNI_ALPHA, /* U+00d2c1: */
+ RTUNI_ALPHA, /* U+00d2c2: */
+ RTUNI_ALPHA, /* U+00d2c3: */
+ RTUNI_ALPHA, /* U+00d2c4: */
+ RTUNI_ALPHA, /* U+00d2c5: */
+ RTUNI_ALPHA, /* U+00d2c6: */
+ RTUNI_ALPHA, /* U+00d2c7: */
+ RTUNI_ALPHA, /* U+00d2c8: */
+ RTUNI_ALPHA, /* U+00d2c9: */
+ RTUNI_ALPHA, /* U+00d2ca: */
+ RTUNI_ALPHA, /* U+00d2cb: */
+ RTUNI_ALPHA, /* U+00d2cc: */
+ RTUNI_ALPHA, /* U+00d2cd: */
+ RTUNI_ALPHA, /* U+00d2ce: */
+ RTUNI_ALPHA, /* U+00d2cf: */
+ RTUNI_ALPHA, /* U+00d2d0: */
+ RTUNI_ALPHA, /* U+00d2d1: */
+ RTUNI_ALPHA, /* U+00d2d2: */
+ RTUNI_ALPHA, /* U+00d2d3: */
+ RTUNI_ALPHA, /* U+00d2d4: */
+ RTUNI_ALPHA, /* U+00d2d5: */
+ RTUNI_ALPHA, /* U+00d2d6: */
+ RTUNI_ALPHA, /* U+00d2d7: */
+ RTUNI_ALPHA, /* U+00d2d8: */
+ RTUNI_ALPHA, /* U+00d2d9: */
+ RTUNI_ALPHA, /* U+00d2da: */
+ RTUNI_ALPHA, /* U+00d2db: */
+ RTUNI_ALPHA, /* U+00d2dc: */
+ RTUNI_ALPHA, /* U+00d2dd: */
+ RTUNI_ALPHA, /* U+00d2de: */
+ RTUNI_ALPHA, /* U+00d2df: */
+ RTUNI_ALPHA, /* U+00d2e0: */
+ RTUNI_ALPHA, /* U+00d2e1: */
+ RTUNI_ALPHA, /* U+00d2e2: */
+ RTUNI_ALPHA, /* U+00d2e3: */
+ RTUNI_ALPHA, /* U+00d2e4: */
+ RTUNI_ALPHA, /* U+00d2e5: */
+ RTUNI_ALPHA, /* U+00d2e6: */
+ RTUNI_ALPHA, /* U+00d2e7: */
+ RTUNI_ALPHA, /* U+00d2e8: */
+ RTUNI_ALPHA, /* U+00d2e9: */
+ RTUNI_ALPHA, /* U+00d2ea: */
+ RTUNI_ALPHA, /* U+00d2eb: */
+ RTUNI_ALPHA, /* U+00d2ec: */
+ RTUNI_ALPHA, /* U+00d2ed: */
+ RTUNI_ALPHA, /* U+00d2ee: */
+ RTUNI_ALPHA, /* U+00d2ef: */
+ RTUNI_ALPHA, /* U+00d2f0: */
+ RTUNI_ALPHA, /* U+00d2f1: */
+ RTUNI_ALPHA, /* U+00d2f2: */
+ RTUNI_ALPHA, /* U+00d2f3: */
+ RTUNI_ALPHA, /* U+00d2f4: */
+ RTUNI_ALPHA, /* U+00d2f5: */
+ RTUNI_ALPHA, /* U+00d2f6: */
+ RTUNI_ALPHA, /* U+00d2f7: */
+ RTUNI_ALPHA, /* U+00d2f8: */
+ RTUNI_ALPHA, /* U+00d2f9: */
+ RTUNI_ALPHA, /* U+00d2fa: */
+ RTUNI_ALPHA, /* U+00d2fb: */
+ RTUNI_ALPHA, /* U+00d2fc: */
+ RTUNI_ALPHA, /* U+00d2fd: */
+ RTUNI_ALPHA, /* U+00d2fe: */
+ RTUNI_ALPHA, /* U+00d2ff: */
+ RTUNI_ALPHA, /* U+00d300: */
+ RTUNI_ALPHA, /* U+00d301: */
+ RTUNI_ALPHA, /* U+00d302: */
+ RTUNI_ALPHA, /* U+00d303: */
+ RTUNI_ALPHA, /* U+00d304: */
+ RTUNI_ALPHA, /* U+00d305: */
+ RTUNI_ALPHA, /* U+00d306: */
+ RTUNI_ALPHA, /* U+00d307: */
+ RTUNI_ALPHA, /* U+00d308: */
+ RTUNI_ALPHA, /* U+00d309: */
+ RTUNI_ALPHA, /* U+00d30a: */
+ RTUNI_ALPHA, /* U+00d30b: */
+ RTUNI_ALPHA, /* U+00d30c: */
+ RTUNI_ALPHA, /* U+00d30d: */
+ RTUNI_ALPHA, /* U+00d30e: */
+ RTUNI_ALPHA, /* U+00d30f: */
+ RTUNI_ALPHA, /* U+00d310: */
+ RTUNI_ALPHA, /* U+00d311: */
+ RTUNI_ALPHA, /* U+00d312: */
+ RTUNI_ALPHA, /* U+00d313: */
+ RTUNI_ALPHA, /* U+00d314: */
+ RTUNI_ALPHA, /* U+00d315: */
+ RTUNI_ALPHA, /* U+00d316: */
+ RTUNI_ALPHA, /* U+00d317: */
+ RTUNI_ALPHA, /* U+00d318: */
+ RTUNI_ALPHA, /* U+00d319: */
+ RTUNI_ALPHA, /* U+00d31a: */
+ RTUNI_ALPHA, /* U+00d31b: */
+ RTUNI_ALPHA, /* U+00d31c: */
+ RTUNI_ALPHA, /* U+00d31d: */
+ RTUNI_ALPHA, /* U+00d31e: */
+ RTUNI_ALPHA, /* U+00d31f: */
+ RTUNI_ALPHA, /* U+00d320: */
+ RTUNI_ALPHA, /* U+00d321: */
+ RTUNI_ALPHA, /* U+00d322: */
+ RTUNI_ALPHA, /* U+00d323: */
+ RTUNI_ALPHA, /* U+00d324: */
+ RTUNI_ALPHA, /* U+00d325: */
+ RTUNI_ALPHA, /* U+00d326: */
+ RTUNI_ALPHA, /* U+00d327: */
+ RTUNI_ALPHA, /* U+00d328: */
+ RTUNI_ALPHA, /* U+00d329: */
+ RTUNI_ALPHA, /* U+00d32a: */
+ RTUNI_ALPHA, /* U+00d32b: */
+ RTUNI_ALPHA, /* U+00d32c: */
+ RTUNI_ALPHA, /* U+00d32d: */
+ RTUNI_ALPHA, /* U+00d32e: */
+ RTUNI_ALPHA, /* U+00d32f: */
+ RTUNI_ALPHA, /* U+00d330: */
+ RTUNI_ALPHA, /* U+00d331: */
+ RTUNI_ALPHA, /* U+00d332: */
+ RTUNI_ALPHA, /* U+00d333: */
+ RTUNI_ALPHA, /* U+00d334: */
+ RTUNI_ALPHA, /* U+00d335: */
+ RTUNI_ALPHA, /* U+00d336: */
+ RTUNI_ALPHA, /* U+00d337: */
+ RTUNI_ALPHA, /* U+00d338: */
+ RTUNI_ALPHA, /* U+00d339: */
+ RTUNI_ALPHA, /* U+00d33a: */
+ RTUNI_ALPHA, /* U+00d33b: */
+ RTUNI_ALPHA, /* U+00d33c: */
+ RTUNI_ALPHA, /* U+00d33d: */
+ RTUNI_ALPHA, /* U+00d33e: */
+ RTUNI_ALPHA, /* U+00d33f: */
+ RTUNI_ALPHA, /* U+00d340: */
+ RTUNI_ALPHA, /* U+00d341: */
+ RTUNI_ALPHA, /* U+00d342: */
+ RTUNI_ALPHA, /* U+00d343: */
+ RTUNI_ALPHA, /* U+00d344: */
+ RTUNI_ALPHA, /* U+00d345: */
+ RTUNI_ALPHA, /* U+00d346: */
+ RTUNI_ALPHA, /* U+00d347: */
+ RTUNI_ALPHA, /* U+00d348: */
+ RTUNI_ALPHA, /* U+00d349: */
+ RTUNI_ALPHA, /* U+00d34a: */
+ RTUNI_ALPHA, /* U+00d34b: */
+ RTUNI_ALPHA, /* U+00d34c: */
+ RTUNI_ALPHA, /* U+00d34d: */
+ RTUNI_ALPHA, /* U+00d34e: */
+ RTUNI_ALPHA, /* U+00d34f: */
+ RTUNI_ALPHA, /* U+00d350: */
+ RTUNI_ALPHA, /* U+00d351: */
+ RTUNI_ALPHA, /* U+00d352: */
+ RTUNI_ALPHA, /* U+00d353: */
+ RTUNI_ALPHA, /* U+00d354: */
+ RTUNI_ALPHA, /* U+00d355: */
+ RTUNI_ALPHA, /* U+00d356: */
+ RTUNI_ALPHA, /* U+00d357: */
+ RTUNI_ALPHA, /* U+00d358: */
+ RTUNI_ALPHA, /* U+00d359: */
+ RTUNI_ALPHA, /* U+00d35a: */
+ RTUNI_ALPHA, /* U+00d35b: */
+ RTUNI_ALPHA, /* U+00d35c: */
+ RTUNI_ALPHA, /* U+00d35d: */
+ RTUNI_ALPHA, /* U+00d35e: */
+ RTUNI_ALPHA, /* U+00d35f: */
+ RTUNI_ALPHA, /* U+00d360: */
+ RTUNI_ALPHA, /* U+00d361: */
+ RTUNI_ALPHA, /* U+00d362: */
+ RTUNI_ALPHA, /* U+00d363: */
+ RTUNI_ALPHA, /* U+00d364: */
+ RTUNI_ALPHA, /* U+00d365: */
+ RTUNI_ALPHA, /* U+00d366: */
+ RTUNI_ALPHA, /* U+00d367: */
+ RTUNI_ALPHA, /* U+00d368: */
+ RTUNI_ALPHA, /* U+00d369: */
+ RTUNI_ALPHA, /* U+00d36a: */
+ RTUNI_ALPHA, /* U+00d36b: */
+ RTUNI_ALPHA, /* U+00d36c: */
+ RTUNI_ALPHA, /* U+00d36d: */
+ RTUNI_ALPHA, /* U+00d36e: */
+ RTUNI_ALPHA, /* U+00d36f: */
+ RTUNI_ALPHA, /* U+00d370: */
+ RTUNI_ALPHA, /* U+00d371: */
+ RTUNI_ALPHA, /* U+00d372: */
+ RTUNI_ALPHA, /* U+00d373: */
+ RTUNI_ALPHA, /* U+00d374: */
+ RTUNI_ALPHA, /* U+00d375: */
+ RTUNI_ALPHA, /* U+00d376: */
+ RTUNI_ALPHA, /* U+00d377: */
+ RTUNI_ALPHA, /* U+00d378: */
+ RTUNI_ALPHA, /* U+00d379: */
+ RTUNI_ALPHA, /* U+00d37a: */
+ RTUNI_ALPHA, /* U+00d37b: */
+ RTUNI_ALPHA, /* U+00d37c: */
+ RTUNI_ALPHA, /* U+00d37d: */
+ RTUNI_ALPHA, /* U+00d37e: */
+ RTUNI_ALPHA, /* U+00d37f: */
+ RTUNI_ALPHA, /* U+00d380: */
+ RTUNI_ALPHA, /* U+00d381: */
+ RTUNI_ALPHA, /* U+00d382: */
+ RTUNI_ALPHA, /* U+00d383: */
+ RTUNI_ALPHA, /* U+00d384: */
+ RTUNI_ALPHA, /* U+00d385: */
+ RTUNI_ALPHA, /* U+00d386: */
+ RTUNI_ALPHA, /* U+00d387: */
+ RTUNI_ALPHA, /* U+00d388: */
+ RTUNI_ALPHA, /* U+00d389: */
+ RTUNI_ALPHA, /* U+00d38a: */
+ RTUNI_ALPHA, /* U+00d38b: */
+ RTUNI_ALPHA, /* U+00d38c: */
+ RTUNI_ALPHA, /* U+00d38d: */
+ RTUNI_ALPHA, /* U+00d38e: */
+ RTUNI_ALPHA, /* U+00d38f: */
+ RTUNI_ALPHA, /* U+00d390: */
+ RTUNI_ALPHA, /* U+00d391: */
+ RTUNI_ALPHA, /* U+00d392: */
+ RTUNI_ALPHA, /* U+00d393: */
+ RTUNI_ALPHA, /* U+00d394: */
+ RTUNI_ALPHA, /* U+00d395: */
+ RTUNI_ALPHA, /* U+00d396: */
+ RTUNI_ALPHA, /* U+00d397: */
+ RTUNI_ALPHA, /* U+00d398: */
+ RTUNI_ALPHA, /* U+00d399: */
+ RTUNI_ALPHA, /* U+00d39a: */
+ RTUNI_ALPHA, /* U+00d39b: */
+ RTUNI_ALPHA, /* U+00d39c: */
+ RTUNI_ALPHA, /* U+00d39d: */
+ RTUNI_ALPHA, /* U+00d39e: */
+ RTUNI_ALPHA, /* U+00d39f: */
+ RTUNI_ALPHA, /* U+00d3a0: */
+ RTUNI_ALPHA, /* U+00d3a1: */
+ RTUNI_ALPHA, /* U+00d3a2: */
+ RTUNI_ALPHA, /* U+00d3a3: */
+ RTUNI_ALPHA, /* U+00d3a4: */
+ RTUNI_ALPHA, /* U+00d3a5: */
+ RTUNI_ALPHA, /* U+00d3a6: */
+ RTUNI_ALPHA, /* U+00d3a7: */
+ RTUNI_ALPHA, /* U+00d3a8: */
+ RTUNI_ALPHA, /* U+00d3a9: */
+ RTUNI_ALPHA, /* U+00d3aa: */
+ RTUNI_ALPHA, /* U+00d3ab: */
+ RTUNI_ALPHA, /* U+00d3ac: */
+ RTUNI_ALPHA, /* U+00d3ad: */
+ RTUNI_ALPHA, /* U+00d3ae: */
+ RTUNI_ALPHA, /* U+00d3af: */
+ RTUNI_ALPHA, /* U+00d3b0: */
+ RTUNI_ALPHA, /* U+00d3b1: */
+ RTUNI_ALPHA, /* U+00d3b2: */
+ RTUNI_ALPHA, /* U+00d3b3: */
+ RTUNI_ALPHA, /* U+00d3b4: */
+ RTUNI_ALPHA, /* U+00d3b5: */
+ RTUNI_ALPHA, /* U+00d3b6: */
+ RTUNI_ALPHA, /* U+00d3b7: */
+ RTUNI_ALPHA, /* U+00d3b8: */
+ RTUNI_ALPHA, /* U+00d3b9: */
+ RTUNI_ALPHA, /* U+00d3ba: */
+ RTUNI_ALPHA, /* U+00d3bb: */
+ RTUNI_ALPHA, /* U+00d3bc: */
+ RTUNI_ALPHA, /* U+00d3bd: */
+ RTUNI_ALPHA, /* U+00d3be: */
+ RTUNI_ALPHA, /* U+00d3bf: */
+ RTUNI_ALPHA, /* U+00d3c0: */
+ RTUNI_ALPHA, /* U+00d3c1: */
+ RTUNI_ALPHA, /* U+00d3c2: */
+ RTUNI_ALPHA, /* U+00d3c3: */
+ RTUNI_ALPHA, /* U+00d3c4: */
+ RTUNI_ALPHA, /* U+00d3c5: */
+ RTUNI_ALPHA, /* U+00d3c6: */
+ RTUNI_ALPHA, /* U+00d3c7: */
+ RTUNI_ALPHA, /* U+00d3c8: */
+ RTUNI_ALPHA, /* U+00d3c9: */
+ RTUNI_ALPHA, /* U+00d3ca: */
+ RTUNI_ALPHA, /* U+00d3cb: */
+ RTUNI_ALPHA, /* U+00d3cc: */
+ RTUNI_ALPHA, /* U+00d3cd: */
+ RTUNI_ALPHA, /* U+00d3ce: */
+ RTUNI_ALPHA, /* U+00d3cf: */
+ RTUNI_ALPHA, /* U+00d3d0: */
+ RTUNI_ALPHA, /* U+00d3d1: */
+ RTUNI_ALPHA, /* U+00d3d2: */
+ RTUNI_ALPHA, /* U+00d3d3: */
+ RTUNI_ALPHA, /* U+00d3d4: */
+ RTUNI_ALPHA, /* U+00d3d5: */
+ RTUNI_ALPHA, /* U+00d3d6: */
+ RTUNI_ALPHA, /* U+00d3d7: */
+ RTUNI_ALPHA, /* U+00d3d8: */
+ RTUNI_ALPHA, /* U+00d3d9: */
+ RTUNI_ALPHA, /* U+00d3da: */
+ RTUNI_ALPHA, /* U+00d3db: */
+ RTUNI_ALPHA, /* U+00d3dc: */
+ RTUNI_ALPHA, /* U+00d3dd: */
+ RTUNI_ALPHA, /* U+00d3de: */
+ RTUNI_ALPHA, /* U+00d3df: */
+ RTUNI_ALPHA, /* U+00d3e0: */
+ RTUNI_ALPHA, /* U+00d3e1: */
+ RTUNI_ALPHA, /* U+00d3e2: */
+ RTUNI_ALPHA, /* U+00d3e3: */
+ RTUNI_ALPHA, /* U+00d3e4: */
+ RTUNI_ALPHA, /* U+00d3e5: */
+ RTUNI_ALPHA, /* U+00d3e6: */
+ RTUNI_ALPHA, /* U+00d3e7: */
+ RTUNI_ALPHA, /* U+00d3e8: */
+ RTUNI_ALPHA, /* U+00d3e9: */
+ RTUNI_ALPHA, /* U+00d3ea: */
+ RTUNI_ALPHA, /* U+00d3eb: */
+ RTUNI_ALPHA, /* U+00d3ec: */
+ RTUNI_ALPHA, /* U+00d3ed: */
+ RTUNI_ALPHA, /* U+00d3ee: */
+ RTUNI_ALPHA, /* U+00d3ef: */
+ RTUNI_ALPHA, /* U+00d3f0: */
+ RTUNI_ALPHA, /* U+00d3f1: */
+ RTUNI_ALPHA, /* U+00d3f2: */
+ RTUNI_ALPHA, /* U+00d3f3: */
+ RTUNI_ALPHA, /* U+00d3f4: */
+ RTUNI_ALPHA, /* U+00d3f5: */
+ RTUNI_ALPHA, /* U+00d3f6: */
+ RTUNI_ALPHA, /* U+00d3f7: */
+ RTUNI_ALPHA, /* U+00d3f8: */
+ RTUNI_ALPHA, /* U+00d3f9: */
+ RTUNI_ALPHA, /* U+00d3fa: */
+ RTUNI_ALPHA, /* U+00d3fb: */
+ RTUNI_ALPHA, /* U+00d3fc: */
+ RTUNI_ALPHA, /* U+00d3fd: */
+ RTUNI_ALPHA, /* U+00d3fe: */
+ RTUNI_ALPHA, /* U+00d3ff: */
+ RTUNI_ALPHA, /* U+00d400: */
+ RTUNI_ALPHA, /* U+00d401: */
+ RTUNI_ALPHA, /* U+00d402: */
+ RTUNI_ALPHA, /* U+00d403: */
+ RTUNI_ALPHA, /* U+00d404: */
+ RTUNI_ALPHA, /* U+00d405: */
+ RTUNI_ALPHA, /* U+00d406: */
+ RTUNI_ALPHA, /* U+00d407: */
+ RTUNI_ALPHA, /* U+00d408: */
+ RTUNI_ALPHA, /* U+00d409: */
+ RTUNI_ALPHA, /* U+00d40a: */
+ RTUNI_ALPHA, /* U+00d40b: */
+ RTUNI_ALPHA, /* U+00d40c: */
+ RTUNI_ALPHA, /* U+00d40d: */
+ RTUNI_ALPHA, /* U+00d40e: */
+ RTUNI_ALPHA, /* U+00d40f: */
+ RTUNI_ALPHA, /* U+00d410: */
+ RTUNI_ALPHA, /* U+00d411: */
+ RTUNI_ALPHA, /* U+00d412: */
+ RTUNI_ALPHA, /* U+00d413: */
+ RTUNI_ALPHA, /* U+00d414: */
+ RTUNI_ALPHA, /* U+00d415: */
+ RTUNI_ALPHA, /* U+00d416: */
+ RTUNI_ALPHA, /* U+00d417: */
+ RTUNI_ALPHA, /* U+00d418: */
+ RTUNI_ALPHA, /* U+00d419: */
+ RTUNI_ALPHA, /* U+00d41a: */
+ RTUNI_ALPHA, /* U+00d41b: */
+ RTUNI_ALPHA, /* U+00d41c: */
+ RTUNI_ALPHA, /* U+00d41d: */
+ RTUNI_ALPHA, /* U+00d41e: */
+ RTUNI_ALPHA, /* U+00d41f: */
+ RTUNI_ALPHA, /* U+00d420: */
+ RTUNI_ALPHA, /* U+00d421: */
+ RTUNI_ALPHA, /* U+00d422: */
+ RTUNI_ALPHA, /* U+00d423: */
+ RTUNI_ALPHA, /* U+00d424: */
+ RTUNI_ALPHA, /* U+00d425: */
+ RTUNI_ALPHA, /* U+00d426: */
+ RTUNI_ALPHA, /* U+00d427: */
+ RTUNI_ALPHA, /* U+00d428: */
+ RTUNI_ALPHA, /* U+00d429: */
+ RTUNI_ALPHA, /* U+00d42a: */
+ RTUNI_ALPHA, /* U+00d42b: */
+ RTUNI_ALPHA, /* U+00d42c: */
+ RTUNI_ALPHA, /* U+00d42d: */
+ RTUNI_ALPHA, /* U+00d42e: */
+ RTUNI_ALPHA, /* U+00d42f: */
+ RTUNI_ALPHA, /* U+00d430: */
+ RTUNI_ALPHA, /* U+00d431: */
+ RTUNI_ALPHA, /* U+00d432: */
+ RTUNI_ALPHA, /* U+00d433: */
+ RTUNI_ALPHA, /* U+00d434: */
+ RTUNI_ALPHA, /* U+00d435: */
+ RTUNI_ALPHA, /* U+00d436: */
+ RTUNI_ALPHA, /* U+00d437: */
+ RTUNI_ALPHA, /* U+00d438: */
+ RTUNI_ALPHA, /* U+00d439: */
+ RTUNI_ALPHA, /* U+00d43a: */
+ RTUNI_ALPHA, /* U+00d43b: */
+ RTUNI_ALPHA, /* U+00d43c: */
+ RTUNI_ALPHA, /* U+00d43d: */
+ RTUNI_ALPHA, /* U+00d43e: */
+ RTUNI_ALPHA, /* U+00d43f: */
+ RTUNI_ALPHA, /* U+00d440: */
+ RTUNI_ALPHA, /* U+00d441: */
+ RTUNI_ALPHA, /* U+00d442: */
+ RTUNI_ALPHA, /* U+00d443: */
+ RTUNI_ALPHA, /* U+00d444: */
+ RTUNI_ALPHA, /* U+00d445: */
+ RTUNI_ALPHA, /* U+00d446: */
+ RTUNI_ALPHA, /* U+00d447: */
+ RTUNI_ALPHA, /* U+00d448: */
+ RTUNI_ALPHA, /* U+00d449: */
+ RTUNI_ALPHA, /* U+00d44a: */
+ RTUNI_ALPHA, /* U+00d44b: */
+ RTUNI_ALPHA, /* U+00d44c: */
+ RTUNI_ALPHA, /* U+00d44d: */
+ RTUNI_ALPHA, /* U+00d44e: */
+ RTUNI_ALPHA, /* U+00d44f: */
+ RTUNI_ALPHA, /* U+00d450: */
+ RTUNI_ALPHA, /* U+00d451: */
+ RTUNI_ALPHA, /* U+00d452: */
+ RTUNI_ALPHA, /* U+00d453: */
+ RTUNI_ALPHA, /* U+00d454: */
+ RTUNI_ALPHA, /* U+00d455: */
+ RTUNI_ALPHA, /* U+00d456: */
+ RTUNI_ALPHA, /* U+00d457: */
+ RTUNI_ALPHA, /* U+00d458: */
+ RTUNI_ALPHA, /* U+00d459: */
+ RTUNI_ALPHA, /* U+00d45a: */
+ RTUNI_ALPHA, /* U+00d45b: */
+ RTUNI_ALPHA, /* U+00d45c: */
+ RTUNI_ALPHA, /* U+00d45d: */
+ RTUNI_ALPHA, /* U+00d45e: */
+ RTUNI_ALPHA, /* U+00d45f: */
+ RTUNI_ALPHA, /* U+00d460: */
+ RTUNI_ALPHA, /* U+00d461: */
+ RTUNI_ALPHA, /* U+00d462: */
+ RTUNI_ALPHA, /* U+00d463: */
+ RTUNI_ALPHA, /* U+00d464: */
+ RTUNI_ALPHA, /* U+00d465: */
+ RTUNI_ALPHA, /* U+00d466: */
+ RTUNI_ALPHA, /* U+00d467: */
+ RTUNI_ALPHA, /* U+00d468: */
+ RTUNI_ALPHA, /* U+00d469: */
+ RTUNI_ALPHA, /* U+00d46a: */
+ RTUNI_ALPHA, /* U+00d46b: */
+ RTUNI_ALPHA, /* U+00d46c: */
+ RTUNI_ALPHA, /* U+00d46d: */
+ RTUNI_ALPHA, /* U+00d46e: */
+ RTUNI_ALPHA, /* U+00d46f: */
+ RTUNI_ALPHA, /* U+00d470: */
+ RTUNI_ALPHA, /* U+00d471: */
+ RTUNI_ALPHA, /* U+00d472: */
+ RTUNI_ALPHA, /* U+00d473: */
+ RTUNI_ALPHA, /* U+00d474: */
+ RTUNI_ALPHA, /* U+00d475: */
+ RTUNI_ALPHA, /* U+00d476: */
+ RTUNI_ALPHA, /* U+00d477: */
+ RTUNI_ALPHA, /* U+00d478: */
+ RTUNI_ALPHA, /* U+00d479: */
+ RTUNI_ALPHA, /* U+00d47a: */
+ RTUNI_ALPHA, /* U+00d47b: */
+ RTUNI_ALPHA, /* U+00d47c: */
+ RTUNI_ALPHA, /* U+00d47d: */
+ RTUNI_ALPHA, /* U+00d47e: */
+ RTUNI_ALPHA, /* U+00d47f: */
+ RTUNI_ALPHA, /* U+00d480: */
+ RTUNI_ALPHA, /* U+00d481: */
+ RTUNI_ALPHA, /* U+00d482: */
+ RTUNI_ALPHA, /* U+00d483: */
+ RTUNI_ALPHA, /* U+00d484: */
+ RTUNI_ALPHA, /* U+00d485: */
+ RTUNI_ALPHA, /* U+00d486: */
+ RTUNI_ALPHA, /* U+00d487: */
+ RTUNI_ALPHA, /* U+00d488: */
+ RTUNI_ALPHA, /* U+00d489: */
+ RTUNI_ALPHA, /* U+00d48a: */
+ RTUNI_ALPHA, /* U+00d48b: */
+ RTUNI_ALPHA, /* U+00d48c: */
+ RTUNI_ALPHA, /* U+00d48d: */
+ RTUNI_ALPHA, /* U+00d48e: */
+ RTUNI_ALPHA, /* U+00d48f: */
+ RTUNI_ALPHA, /* U+00d490: */
+ RTUNI_ALPHA, /* U+00d491: */
+ RTUNI_ALPHA, /* U+00d492: */
+ RTUNI_ALPHA, /* U+00d493: */
+ RTUNI_ALPHA, /* U+00d494: */
+ RTUNI_ALPHA, /* U+00d495: */
+ RTUNI_ALPHA, /* U+00d496: */
+ RTUNI_ALPHA, /* U+00d497: */
+ RTUNI_ALPHA, /* U+00d498: */
+ RTUNI_ALPHA, /* U+00d499: */
+ RTUNI_ALPHA, /* U+00d49a: */
+ RTUNI_ALPHA, /* U+00d49b: */
+ RTUNI_ALPHA, /* U+00d49c: */
+ RTUNI_ALPHA, /* U+00d49d: */
+ RTUNI_ALPHA, /* U+00d49e: */
+ RTUNI_ALPHA, /* U+00d49f: */
+ RTUNI_ALPHA, /* U+00d4a0: */
+ RTUNI_ALPHA, /* U+00d4a1: */
+ RTUNI_ALPHA, /* U+00d4a2: */
+ RTUNI_ALPHA, /* U+00d4a3: */
+ RTUNI_ALPHA, /* U+00d4a4: */
+ RTUNI_ALPHA, /* U+00d4a5: */
+ RTUNI_ALPHA, /* U+00d4a6: */
+ RTUNI_ALPHA, /* U+00d4a7: */
+ RTUNI_ALPHA, /* U+00d4a8: */
+ RTUNI_ALPHA, /* U+00d4a9: */
+ RTUNI_ALPHA, /* U+00d4aa: */
+ RTUNI_ALPHA, /* U+00d4ab: */
+ RTUNI_ALPHA, /* U+00d4ac: */
+ RTUNI_ALPHA, /* U+00d4ad: */
+ RTUNI_ALPHA, /* U+00d4ae: */
+ RTUNI_ALPHA, /* U+00d4af: */
+ RTUNI_ALPHA, /* U+00d4b0: */
+ RTUNI_ALPHA, /* U+00d4b1: */
+ RTUNI_ALPHA, /* U+00d4b2: */
+ RTUNI_ALPHA, /* U+00d4b3: */
+ RTUNI_ALPHA, /* U+00d4b4: */
+ RTUNI_ALPHA, /* U+00d4b5: */
+ RTUNI_ALPHA, /* U+00d4b6: */
+ RTUNI_ALPHA, /* U+00d4b7: */
+ RTUNI_ALPHA, /* U+00d4b8: */
+ RTUNI_ALPHA, /* U+00d4b9: */
+ RTUNI_ALPHA, /* U+00d4ba: */
+ RTUNI_ALPHA, /* U+00d4bb: */
+ RTUNI_ALPHA, /* U+00d4bc: */
+ RTUNI_ALPHA, /* U+00d4bd: */
+ RTUNI_ALPHA, /* U+00d4be: */
+ RTUNI_ALPHA, /* U+00d4bf: */
+ RTUNI_ALPHA, /* U+00d4c0: */
+ RTUNI_ALPHA, /* U+00d4c1: */
+ RTUNI_ALPHA, /* U+00d4c2: */
+ RTUNI_ALPHA, /* U+00d4c3: */
+ RTUNI_ALPHA, /* U+00d4c4: */
+ RTUNI_ALPHA, /* U+00d4c5: */
+ RTUNI_ALPHA, /* U+00d4c6: */
+ RTUNI_ALPHA, /* U+00d4c7: */
+ RTUNI_ALPHA, /* U+00d4c8: */
+ RTUNI_ALPHA, /* U+00d4c9: */
+ RTUNI_ALPHA, /* U+00d4ca: */
+ RTUNI_ALPHA, /* U+00d4cb: */
+ RTUNI_ALPHA, /* U+00d4cc: */
+ RTUNI_ALPHA, /* U+00d4cd: */
+ RTUNI_ALPHA, /* U+00d4ce: */
+ RTUNI_ALPHA, /* U+00d4cf: */
+ RTUNI_ALPHA, /* U+00d4d0: */
+ RTUNI_ALPHA, /* U+00d4d1: */
+ RTUNI_ALPHA, /* U+00d4d2: */
+ RTUNI_ALPHA, /* U+00d4d3: */
+ RTUNI_ALPHA, /* U+00d4d4: */
+ RTUNI_ALPHA, /* U+00d4d5: */
+ RTUNI_ALPHA, /* U+00d4d6: */
+ RTUNI_ALPHA, /* U+00d4d7: */
+ RTUNI_ALPHA, /* U+00d4d8: */
+ RTUNI_ALPHA, /* U+00d4d9: */
+ RTUNI_ALPHA, /* U+00d4da: */
+ RTUNI_ALPHA, /* U+00d4db: */
+ RTUNI_ALPHA, /* U+00d4dc: */
+ RTUNI_ALPHA, /* U+00d4dd: */
+ RTUNI_ALPHA, /* U+00d4de: */
+ RTUNI_ALPHA, /* U+00d4df: */
+ RTUNI_ALPHA, /* U+00d4e0: */
+ RTUNI_ALPHA, /* U+00d4e1: */
+ RTUNI_ALPHA, /* U+00d4e2: */
+ RTUNI_ALPHA, /* U+00d4e3: */
+ RTUNI_ALPHA, /* U+00d4e4: */
+ RTUNI_ALPHA, /* U+00d4e5: */
+ RTUNI_ALPHA, /* U+00d4e6: */
+ RTUNI_ALPHA, /* U+00d4e7: */
+ RTUNI_ALPHA, /* U+00d4e8: */
+ RTUNI_ALPHA, /* U+00d4e9: */
+ RTUNI_ALPHA, /* U+00d4ea: */
+ RTUNI_ALPHA, /* U+00d4eb: */
+ RTUNI_ALPHA, /* U+00d4ec: */
+ RTUNI_ALPHA, /* U+00d4ed: */
+ RTUNI_ALPHA, /* U+00d4ee: */
+ RTUNI_ALPHA, /* U+00d4ef: */
+ RTUNI_ALPHA, /* U+00d4f0: */
+ RTUNI_ALPHA, /* U+00d4f1: */
+ RTUNI_ALPHA, /* U+00d4f2: */
+ RTUNI_ALPHA, /* U+00d4f3: */
+ RTUNI_ALPHA, /* U+00d4f4: */
+ RTUNI_ALPHA, /* U+00d4f5: */
+ RTUNI_ALPHA, /* U+00d4f6: */
+ RTUNI_ALPHA, /* U+00d4f7: */
+ RTUNI_ALPHA, /* U+00d4f8: */
+ RTUNI_ALPHA, /* U+00d4f9: */
+ RTUNI_ALPHA, /* U+00d4fa: */
+ RTUNI_ALPHA, /* U+00d4fb: */
+ RTUNI_ALPHA, /* U+00d4fc: */
+ RTUNI_ALPHA, /* U+00d4fd: */
+ RTUNI_ALPHA, /* U+00d4fe: */
+ RTUNI_ALPHA, /* U+00d4ff: */
+ RTUNI_ALPHA, /* U+00d500: */
+ RTUNI_ALPHA, /* U+00d501: */
+ RTUNI_ALPHA, /* U+00d502: */
+ RTUNI_ALPHA, /* U+00d503: */
+ RTUNI_ALPHA, /* U+00d504: */
+ RTUNI_ALPHA, /* U+00d505: */
+ RTUNI_ALPHA, /* U+00d506: */
+ RTUNI_ALPHA, /* U+00d507: */
+ RTUNI_ALPHA, /* U+00d508: */
+ RTUNI_ALPHA, /* U+00d509: */
+ RTUNI_ALPHA, /* U+00d50a: */
+ RTUNI_ALPHA, /* U+00d50b: */
+ RTUNI_ALPHA, /* U+00d50c: */
+ RTUNI_ALPHA, /* U+00d50d: */
+ RTUNI_ALPHA, /* U+00d50e: */
+ RTUNI_ALPHA, /* U+00d50f: */
+ RTUNI_ALPHA, /* U+00d510: */
+ RTUNI_ALPHA, /* U+00d511: */
+ RTUNI_ALPHA, /* U+00d512: */
+ RTUNI_ALPHA, /* U+00d513: */
+ RTUNI_ALPHA, /* U+00d514: */
+ RTUNI_ALPHA, /* U+00d515: */
+ RTUNI_ALPHA, /* U+00d516: */
+ RTUNI_ALPHA, /* U+00d517: */
+ RTUNI_ALPHA, /* U+00d518: */
+ RTUNI_ALPHA, /* U+00d519: */
+ RTUNI_ALPHA, /* U+00d51a: */
+ RTUNI_ALPHA, /* U+00d51b: */
+ RTUNI_ALPHA, /* U+00d51c: */
+ RTUNI_ALPHA, /* U+00d51d: */
+ RTUNI_ALPHA, /* U+00d51e: */
+ RTUNI_ALPHA, /* U+00d51f: */
+ RTUNI_ALPHA, /* U+00d520: */
+ RTUNI_ALPHA, /* U+00d521: */
+ RTUNI_ALPHA, /* U+00d522: */
+ RTUNI_ALPHA, /* U+00d523: */
+ RTUNI_ALPHA, /* U+00d524: */
+ RTUNI_ALPHA, /* U+00d525: */
+ RTUNI_ALPHA, /* U+00d526: */
+ RTUNI_ALPHA, /* U+00d527: */
+ RTUNI_ALPHA, /* U+00d528: */
+ RTUNI_ALPHA, /* U+00d529: */
+ RTUNI_ALPHA, /* U+00d52a: */
+ RTUNI_ALPHA, /* U+00d52b: */
+ RTUNI_ALPHA, /* U+00d52c: */
+ RTUNI_ALPHA, /* U+00d52d: */
+ RTUNI_ALPHA, /* U+00d52e: */
+ RTUNI_ALPHA, /* U+00d52f: */
+ RTUNI_ALPHA, /* U+00d530: */
+ RTUNI_ALPHA, /* U+00d531: */
+ RTUNI_ALPHA, /* U+00d532: */
+ RTUNI_ALPHA, /* U+00d533: */
+ RTUNI_ALPHA, /* U+00d534: */
+ RTUNI_ALPHA, /* U+00d535: */
+ RTUNI_ALPHA, /* U+00d536: */
+ RTUNI_ALPHA, /* U+00d537: */
+ RTUNI_ALPHA, /* U+00d538: */
+ RTUNI_ALPHA, /* U+00d539: */
+ RTUNI_ALPHA, /* U+00d53a: */
+ RTUNI_ALPHA, /* U+00d53b: */
+ RTUNI_ALPHA, /* U+00d53c: */
+ RTUNI_ALPHA, /* U+00d53d: */
+ RTUNI_ALPHA, /* U+00d53e: */
+ RTUNI_ALPHA, /* U+00d53f: */
+ RTUNI_ALPHA, /* U+00d540: */
+ RTUNI_ALPHA, /* U+00d541: */
+ RTUNI_ALPHA, /* U+00d542: */
+ RTUNI_ALPHA, /* U+00d543: */
+ RTUNI_ALPHA, /* U+00d544: */
+ RTUNI_ALPHA, /* U+00d545: */
+ RTUNI_ALPHA, /* U+00d546: */
+ RTUNI_ALPHA, /* U+00d547: */
+ RTUNI_ALPHA, /* U+00d548: */
+ RTUNI_ALPHA, /* U+00d549: */
+ RTUNI_ALPHA, /* U+00d54a: */
+ RTUNI_ALPHA, /* U+00d54b: */
+ RTUNI_ALPHA, /* U+00d54c: */
+ RTUNI_ALPHA, /* U+00d54d: */
+ RTUNI_ALPHA, /* U+00d54e: */
+ RTUNI_ALPHA, /* U+00d54f: */
+ RTUNI_ALPHA, /* U+00d550: */
+ RTUNI_ALPHA, /* U+00d551: */
+ RTUNI_ALPHA, /* U+00d552: */
+ RTUNI_ALPHA, /* U+00d553: */
+ RTUNI_ALPHA, /* U+00d554: */
+ RTUNI_ALPHA, /* U+00d555: */
+ RTUNI_ALPHA, /* U+00d556: */
+ RTUNI_ALPHA, /* U+00d557: */
+ RTUNI_ALPHA, /* U+00d558: */
+ RTUNI_ALPHA, /* U+00d559: */
+ RTUNI_ALPHA, /* U+00d55a: */
+ RTUNI_ALPHA, /* U+00d55b: */
+ RTUNI_ALPHA, /* U+00d55c: */
+ RTUNI_ALPHA, /* U+00d55d: */
+ RTUNI_ALPHA, /* U+00d55e: */
+ RTUNI_ALPHA, /* U+00d55f: */
+ RTUNI_ALPHA, /* U+00d560: */
+ RTUNI_ALPHA, /* U+00d561: */
+ RTUNI_ALPHA, /* U+00d562: */
+ RTUNI_ALPHA, /* U+00d563: */
+ RTUNI_ALPHA, /* U+00d564: */
+ RTUNI_ALPHA, /* U+00d565: */
+ RTUNI_ALPHA, /* U+00d566: */
+ RTUNI_ALPHA, /* U+00d567: */
+ RTUNI_ALPHA, /* U+00d568: */
+ RTUNI_ALPHA, /* U+00d569: */
+ RTUNI_ALPHA, /* U+00d56a: */
+ RTUNI_ALPHA, /* U+00d56b: */
+ RTUNI_ALPHA, /* U+00d56c: */
+ RTUNI_ALPHA, /* U+00d56d: */
+ RTUNI_ALPHA, /* U+00d56e: */
+ RTUNI_ALPHA, /* U+00d56f: */
+ RTUNI_ALPHA, /* U+00d570: */
+ RTUNI_ALPHA, /* U+00d571: */
+ RTUNI_ALPHA, /* U+00d572: */
+ RTUNI_ALPHA, /* U+00d573: */
+ RTUNI_ALPHA, /* U+00d574: */
+ RTUNI_ALPHA, /* U+00d575: */
+ RTUNI_ALPHA, /* U+00d576: */
+ RTUNI_ALPHA, /* U+00d577: */
+ RTUNI_ALPHA, /* U+00d578: */
+ RTUNI_ALPHA, /* U+00d579: */
+ RTUNI_ALPHA, /* U+00d57a: */
+ RTUNI_ALPHA, /* U+00d57b: */
+ RTUNI_ALPHA, /* U+00d57c: */
+ RTUNI_ALPHA, /* U+00d57d: */
+ RTUNI_ALPHA, /* U+00d57e: */
+ RTUNI_ALPHA, /* U+00d57f: */
+ RTUNI_ALPHA, /* U+00d580: */
+ RTUNI_ALPHA, /* U+00d581: */
+ RTUNI_ALPHA, /* U+00d582: */
+ RTUNI_ALPHA, /* U+00d583: */
+ RTUNI_ALPHA, /* U+00d584: */
+ RTUNI_ALPHA, /* U+00d585: */
+ RTUNI_ALPHA, /* U+00d586: */
+ RTUNI_ALPHA, /* U+00d587: */
+ RTUNI_ALPHA, /* U+00d588: */
+ RTUNI_ALPHA, /* U+00d589: */
+ RTUNI_ALPHA, /* U+00d58a: */
+ RTUNI_ALPHA, /* U+00d58b: */
+ RTUNI_ALPHA, /* U+00d58c: */
+ RTUNI_ALPHA, /* U+00d58d: */
+ RTUNI_ALPHA, /* U+00d58e: */
+ RTUNI_ALPHA, /* U+00d58f: */
+ RTUNI_ALPHA, /* U+00d590: */
+ RTUNI_ALPHA, /* U+00d591: */
+ RTUNI_ALPHA, /* U+00d592: */
+ RTUNI_ALPHA, /* U+00d593: */
+ RTUNI_ALPHA, /* U+00d594: */
+ RTUNI_ALPHA, /* U+00d595: */
+ RTUNI_ALPHA, /* U+00d596: */
+ RTUNI_ALPHA, /* U+00d597: */
+ RTUNI_ALPHA, /* U+00d598: */
+ RTUNI_ALPHA, /* U+00d599: */
+ RTUNI_ALPHA, /* U+00d59a: */
+ RTUNI_ALPHA, /* U+00d59b: */
+ RTUNI_ALPHA, /* U+00d59c: */
+ RTUNI_ALPHA, /* U+00d59d: */
+ RTUNI_ALPHA, /* U+00d59e: */
+ RTUNI_ALPHA, /* U+00d59f: */
+ RTUNI_ALPHA, /* U+00d5a0: */
+ RTUNI_ALPHA, /* U+00d5a1: */
+ RTUNI_ALPHA, /* U+00d5a2: */
+ RTUNI_ALPHA, /* U+00d5a3: */
+ RTUNI_ALPHA, /* U+00d5a4: */
+ RTUNI_ALPHA, /* U+00d5a5: */
+ RTUNI_ALPHA, /* U+00d5a6: */
+ RTUNI_ALPHA, /* U+00d5a7: */
+ RTUNI_ALPHA, /* U+00d5a8: */
+ RTUNI_ALPHA, /* U+00d5a9: */
+ RTUNI_ALPHA, /* U+00d5aa: */
+ RTUNI_ALPHA, /* U+00d5ab: */
+ RTUNI_ALPHA, /* U+00d5ac: */
+ RTUNI_ALPHA, /* U+00d5ad: */
+ RTUNI_ALPHA, /* U+00d5ae: */
+ RTUNI_ALPHA, /* U+00d5af: */
+ RTUNI_ALPHA, /* U+00d5b0: */
+ RTUNI_ALPHA, /* U+00d5b1: */
+ RTUNI_ALPHA, /* U+00d5b2: */
+ RTUNI_ALPHA, /* U+00d5b3: */
+ RTUNI_ALPHA, /* U+00d5b4: */
+ RTUNI_ALPHA, /* U+00d5b5: */
+ RTUNI_ALPHA, /* U+00d5b6: */
+ RTUNI_ALPHA, /* U+00d5b7: */
+ RTUNI_ALPHA, /* U+00d5b8: */
+ RTUNI_ALPHA, /* U+00d5b9: */
+ RTUNI_ALPHA, /* U+00d5ba: */
+ RTUNI_ALPHA, /* U+00d5bb: */
+ RTUNI_ALPHA, /* U+00d5bc: */
+ RTUNI_ALPHA, /* U+00d5bd: */
+ RTUNI_ALPHA, /* U+00d5be: */
+ RTUNI_ALPHA, /* U+00d5bf: */
+ RTUNI_ALPHA, /* U+00d5c0: */
+ RTUNI_ALPHA, /* U+00d5c1: */
+ RTUNI_ALPHA, /* U+00d5c2: */
+ RTUNI_ALPHA, /* U+00d5c3: */
+ RTUNI_ALPHA, /* U+00d5c4: */
+ RTUNI_ALPHA, /* U+00d5c5: */
+ RTUNI_ALPHA, /* U+00d5c6: */
+ RTUNI_ALPHA, /* U+00d5c7: */
+ RTUNI_ALPHA, /* U+00d5c8: */
+ RTUNI_ALPHA, /* U+00d5c9: */
+ RTUNI_ALPHA, /* U+00d5ca: */
+ RTUNI_ALPHA, /* U+00d5cb: */
+ RTUNI_ALPHA, /* U+00d5cc: */
+ RTUNI_ALPHA, /* U+00d5cd: */
+ RTUNI_ALPHA, /* U+00d5ce: */
+ RTUNI_ALPHA, /* U+00d5cf: */
+ RTUNI_ALPHA, /* U+00d5d0: */
+ RTUNI_ALPHA, /* U+00d5d1: */
+ RTUNI_ALPHA, /* U+00d5d2: */
+ RTUNI_ALPHA, /* U+00d5d3: */
+ RTUNI_ALPHA, /* U+00d5d4: */
+ RTUNI_ALPHA, /* U+00d5d5: */
+ RTUNI_ALPHA, /* U+00d5d6: */
+ RTUNI_ALPHA, /* U+00d5d7: */
+ RTUNI_ALPHA, /* U+00d5d8: */
+ RTUNI_ALPHA, /* U+00d5d9: */
+ RTUNI_ALPHA, /* U+00d5da: */
+ RTUNI_ALPHA, /* U+00d5db: */
+ RTUNI_ALPHA, /* U+00d5dc: */
+ RTUNI_ALPHA, /* U+00d5dd: */
+ RTUNI_ALPHA, /* U+00d5de: */
+ RTUNI_ALPHA, /* U+00d5df: */
+ RTUNI_ALPHA, /* U+00d5e0: */
+ RTUNI_ALPHA, /* U+00d5e1: */
+ RTUNI_ALPHA, /* U+00d5e2: */
+ RTUNI_ALPHA, /* U+00d5e3: */
+ RTUNI_ALPHA, /* U+00d5e4: */
+ RTUNI_ALPHA, /* U+00d5e5: */
+ RTUNI_ALPHA, /* U+00d5e6: */
+ RTUNI_ALPHA, /* U+00d5e7: */
+ RTUNI_ALPHA, /* U+00d5e8: */
+ RTUNI_ALPHA, /* U+00d5e9: */
+ RTUNI_ALPHA, /* U+00d5ea: */
+ RTUNI_ALPHA, /* U+00d5eb: */
+ RTUNI_ALPHA, /* U+00d5ec: */
+ RTUNI_ALPHA, /* U+00d5ed: */
+ RTUNI_ALPHA, /* U+00d5ee: */
+ RTUNI_ALPHA, /* U+00d5ef: */
+ RTUNI_ALPHA, /* U+00d5f0: */
+ RTUNI_ALPHA, /* U+00d5f1: */
+ RTUNI_ALPHA, /* U+00d5f2: */
+ RTUNI_ALPHA, /* U+00d5f3: */
+ RTUNI_ALPHA, /* U+00d5f4: */
+ RTUNI_ALPHA, /* U+00d5f5: */
+ RTUNI_ALPHA, /* U+00d5f6: */
+ RTUNI_ALPHA, /* U+00d5f7: */
+ RTUNI_ALPHA, /* U+00d5f8: */
+ RTUNI_ALPHA, /* U+00d5f9: */
+ RTUNI_ALPHA, /* U+00d5fa: */
+ RTUNI_ALPHA, /* U+00d5fb: */
+ RTUNI_ALPHA, /* U+00d5fc: */
+ RTUNI_ALPHA, /* U+00d5fd: */
+ RTUNI_ALPHA, /* U+00d5fe: */
+ RTUNI_ALPHA, /* U+00d5ff: */
+ RTUNI_ALPHA, /* U+00d600: */
+ RTUNI_ALPHA, /* U+00d601: */
+ RTUNI_ALPHA, /* U+00d602: */
+ RTUNI_ALPHA, /* U+00d603: */
+ RTUNI_ALPHA, /* U+00d604: */
+ RTUNI_ALPHA, /* U+00d605: */
+ RTUNI_ALPHA, /* U+00d606: */
+ RTUNI_ALPHA, /* U+00d607: */
+ RTUNI_ALPHA, /* U+00d608: */
+ RTUNI_ALPHA, /* U+00d609: */
+ RTUNI_ALPHA, /* U+00d60a: */
+ RTUNI_ALPHA, /* U+00d60b: */
+ RTUNI_ALPHA, /* U+00d60c: */
+ RTUNI_ALPHA, /* U+00d60d: */
+ RTUNI_ALPHA, /* U+00d60e: */
+ RTUNI_ALPHA, /* U+00d60f: */
+ RTUNI_ALPHA, /* U+00d610: */
+ RTUNI_ALPHA, /* U+00d611: */
+ RTUNI_ALPHA, /* U+00d612: */
+ RTUNI_ALPHA, /* U+00d613: */
+ RTUNI_ALPHA, /* U+00d614: */
+ RTUNI_ALPHA, /* U+00d615: */
+ RTUNI_ALPHA, /* U+00d616: */
+ RTUNI_ALPHA, /* U+00d617: */
+ RTUNI_ALPHA, /* U+00d618: */
+ RTUNI_ALPHA, /* U+00d619: */
+ RTUNI_ALPHA, /* U+00d61a: */
+ RTUNI_ALPHA, /* U+00d61b: */
+ RTUNI_ALPHA, /* U+00d61c: */
+ RTUNI_ALPHA, /* U+00d61d: */
+ RTUNI_ALPHA, /* U+00d61e: */
+ RTUNI_ALPHA, /* U+00d61f: */
+ RTUNI_ALPHA, /* U+00d620: */
+ RTUNI_ALPHA, /* U+00d621: */
+ RTUNI_ALPHA, /* U+00d622: */
+ RTUNI_ALPHA, /* U+00d623: */
+ RTUNI_ALPHA, /* U+00d624: */
+ RTUNI_ALPHA, /* U+00d625: */
+ RTUNI_ALPHA, /* U+00d626: */
+ RTUNI_ALPHA, /* U+00d627: */
+ RTUNI_ALPHA, /* U+00d628: */
+ RTUNI_ALPHA, /* U+00d629: */
+ RTUNI_ALPHA, /* U+00d62a: */
+ RTUNI_ALPHA, /* U+00d62b: */
+ RTUNI_ALPHA, /* U+00d62c: */
+ RTUNI_ALPHA, /* U+00d62d: */
+ RTUNI_ALPHA, /* U+00d62e: */
+ RTUNI_ALPHA, /* U+00d62f: */
+ RTUNI_ALPHA, /* U+00d630: */
+ RTUNI_ALPHA, /* U+00d631: */
+ RTUNI_ALPHA, /* U+00d632: */
+ RTUNI_ALPHA, /* U+00d633: */
+ RTUNI_ALPHA, /* U+00d634: */
+ RTUNI_ALPHA, /* U+00d635: */
+ RTUNI_ALPHA, /* U+00d636: */
+ RTUNI_ALPHA, /* U+00d637: */
+ RTUNI_ALPHA, /* U+00d638: */
+ RTUNI_ALPHA, /* U+00d639: */
+ RTUNI_ALPHA, /* U+00d63a: */
+ RTUNI_ALPHA, /* U+00d63b: */
+ RTUNI_ALPHA, /* U+00d63c: */
+ RTUNI_ALPHA, /* U+00d63d: */
+ RTUNI_ALPHA, /* U+00d63e: */
+ RTUNI_ALPHA, /* U+00d63f: */
+ RTUNI_ALPHA, /* U+00d640: */
+ RTUNI_ALPHA, /* U+00d641: */
+ RTUNI_ALPHA, /* U+00d642: */
+ RTUNI_ALPHA, /* U+00d643: */
+ RTUNI_ALPHA, /* U+00d644: */
+ RTUNI_ALPHA, /* U+00d645: */
+ RTUNI_ALPHA, /* U+00d646: */
+ RTUNI_ALPHA, /* U+00d647: */
+ RTUNI_ALPHA, /* U+00d648: */
+ RTUNI_ALPHA, /* U+00d649: */
+ RTUNI_ALPHA, /* U+00d64a: */
+ RTUNI_ALPHA, /* U+00d64b: */
+ RTUNI_ALPHA, /* U+00d64c: */
+ RTUNI_ALPHA, /* U+00d64d: */
+ RTUNI_ALPHA, /* U+00d64e: */
+ RTUNI_ALPHA, /* U+00d64f: */
+ RTUNI_ALPHA, /* U+00d650: */
+ RTUNI_ALPHA, /* U+00d651: */
+ RTUNI_ALPHA, /* U+00d652: */
+ RTUNI_ALPHA, /* U+00d653: */
+ RTUNI_ALPHA, /* U+00d654: */
+ RTUNI_ALPHA, /* U+00d655: */
+ RTUNI_ALPHA, /* U+00d656: */
+ RTUNI_ALPHA, /* U+00d657: */
+ RTUNI_ALPHA, /* U+00d658: */
+ RTUNI_ALPHA, /* U+00d659: */
+ RTUNI_ALPHA, /* U+00d65a: */
+ RTUNI_ALPHA, /* U+00d65b: */
+ RTUNI_ALPHA, /* U+00d65c: */
+ RTUNI_ALPHA, /* U+00d65d: */
+ RTUNI_ALPHA, /* U+00d65e: */
+ RTUNI_ALPHA, /* U+00d65f: */
+ RTUNI_ALPHA, /* U+00d660: */
+ RTUNI_ALPHA, /* U+00d661: */
+ RTUNI_ALPHA, /* U+00d662: */
+ RTUNI_ALPHA, /* U+00d663: */
+ RTUNI_ALPHA, /* U+00d664: */
+ RTUNI_ALPHA, /* U+00d665: */
+ RTUNI_ALPHA, /* U+00d666: */
+ RTUNI_ALPHA, /* U+00d667: */
+ RTUNI_ALPHA, /* U+00d668: */
+ RTUNI_ALPHA, /* U+00d669: */
+ RTUNI_ALPHA, /* U+00d66a: */
+ RTUNI_ALPHA, /* U+00d66b: */
+ RTUNI_ALPHA, /* U+00d66c: */
+ RTUNI_ALPHA, /* U+00d66d: */
+ RTUNI_ALPHA, /* U+00d66e: */
+ RTUNI_ALPHA, /* U+00d66f: */
+ RTUNI_ALPHA, /* U+00d670: */
+ RTUNI_ALPHA, /* U+00d671: */
+ RTUNI_ALPHA, /* U+00d672: */
+ RTUNI_ALPHA, /* U+00d673: */
+ RTUNI_ALPHA, /* U+00d674: */
+ RTUNI_ALPHA, /* U+00d675: */
+ RTUNI_ALPHA, /* U+00d676: */
+ RTUNI_ALPHA, /* U+00d677: */
+ RTUNI_ALPHA, /* U+00d678: */
+ RTUNI_ALPHA, /* U+00d679: */
+ RTUNI_ALPHA, /* U+00d67a: */
+ RTUNI_ALPHA, /* U+00d67b: */
+ RTUNI_ALPHA, /* U+00d67c: */
+ RTUNI_ALPHA, /* U+00d67d: */
+ RTUNI_ALPHA, /* U+00d67e: */
+ RTUNI_ALPHA, /* U+00d67f: */
+ RTUNI_ALPHA, /* U+00d680: */
+ RTUNI_ALPHA, /* U+00d681: */
+ RTUNI_ALPHA, /* U+00d682: */
+ RTUNI_ALPHA, /* U+00d683: */
+ RTUNI_ALPHA, /* U+00d684: */
+ RTUNI_ALPHA, /* U+00d685: */
+ RTUNI_ALPHA, /* U+00d686: */
+ RTUNI_ALPHA, /* U+00d687: */
+ RTUNI_ALPHA, /* U+00d688: */
+ RTUNI_ALPHA, /* U+00d689: */
+ RTUNI_ALPHA, /* U+00d68a: */
+ RTUNI_ALPHA, /* U+00d68b: */
+ RTUNI_ALPHA, /* U+00d68c: */
+ RTUNI_ALPHA, /* U+00d68d: */
+ RTUNI_ALPHA, /* U+00d68e: */
+ RTUNI_ALPHA, /* U+00d68f: */
+ RTUNI_ALPHA, /* U+00d690: */
+ RTUNI_ALPHA, /* U+00d691: */
+ RTUNI_ALPHA, /* U+00d692: */
+ RTUNI_ALPHA, /* U+00d693: */
+ RTUNI_ALPHA, /* U+00d694: */
+ RTUNI_ALPHA, /* U+00d695: */
+ RTUNI_ALPHA, /* U+00d696: */
+ RTUNI_ALPHA, /* U+00d697: */
+ RTUNI_ALPHA, /* U+00d698: */
+ RTUNI_ALPHA, /* U+00d699: */
+ RTUNI_ALPHA, /* U+00d69a: */
+ RTUNI_ALPHA, /* U+00d69b: */
+ RTUNI_ALPHA, /* U+00d69c: */
+ RTUNI_ALPHA, /* U+00d69d: */
+ RTUNI_ALPHA, /* U+00d69e: */
+ RTUNI_ALPHA, /* U+00d69f: */
+ RTUNI_ALPHA, /* U+00d6a0: */
+ RTUNI_ALPHA, /* U+00d6a1: */
+ RTUNI_ALPHA, /* U+00d6a2: */
+ RTUNI_ALPHA, /* U+00d6a3: */
+ RTUNI_ALPHA, /* U+00d6a4: */
+ RTUNI_ALPHA, /* U+00d6a5: */
+ RTUNI_ALPHA, /* U+00d6a6: */
+ RTUNI_ALPHA, /* U+00d6a7: */
+ RTUNI_ALPHA, /* U+00d6a8: */
+ RTUNI_ALPHA, /* U+00d6a9: */
+ RTUNI_ALPHA, /* U+00d6aa: */
+ RTUNI_ALPHA, /* U+00d6ab: */
+ RTUNI_ALPHA, /* U+00d6ac: */
+ RTUNI_ALPHA, /* U+00d6ad: */
+ RTUNI_ALPHA, /* U+00d6ae: */
+ RTUNI_ALPHA, /* U+00d6af: */
+ RTUNI_ALPHA, /* U+00d6b0: */
+ RTUNI_ALPHA, /* U+00d6b1: */
+ RTUNI_ALPHA, /* U+00d6b2: */
+ RTUNI_ALPHA, /* U+00d6b3: */
+ RTUNI_ALPHA, /* U+00d6b4: */
+ RTUNI_ALPHA, /* U+00d6b5: */
+ RTUNI_ALPHA, /* U+00d6b6: */
+ RTUNI_ALPHA, /* U+00d6b7: */
+ RTUNI_ALPHA, /* U+00d6b8: */
+ RTUNI_ALPHA, /* U+00d6b9: */
+ RTUNI_ALPHA, /* U+00d6ba: */
+ RTUNI_ALPHA, /* U+00d6bb: */
+ RTUNI_ALPHA, /* U+00d6bc: */
+ RTUNI_ALPHA, /* U+00d6bd: */
+ RTUNI_ALPHA, /* U+00d6be: */
+ RTUNI_ALPHA, /* U+00d6bf: */
+ RTUNI_ALPHA, /* U+00d6c0: */
+ RTUNI_ALPHA, /* U+00d6c1: */
+ RTUNI_ALPHA, /* U+00d6c2: */
+ RTUNI_ALPHA, /* U+00d6c3: */
+ RTUNI_ALPHA, /* U+00d6c4: */
+ RTUNI_ALPHA, /* U+00d6c5: */
+ RTUNI_ALPHA, /* U+00d6c6: */
+ RTUNI_ALPHA, /* U+00d6c7: */
+ RTUNI_ALPHA, /* U+00d6c8: */
+ RTUNI_ALPHA, /* U+00d6c9: */
+ RTUNI_ALPHA, /* U+00d6ca: */
+ RTUNI_ALPHA, /* U+00d6cb: */
+ RTUNI_ALPHA, /* U+00d6cc: */
+ RTUNI_ALPHA, /* U+00d6cd: */
+ RTUNI_ALPHA, /* U+00d6ce: */
+ RTUNI_ALPHA, /* U+00d6cf: */
+ RTUNI_ALPHA, /* U+00d6d0: */
+ RTUNI_ALPHA, /* U+00d6d1: */
+ RTUNI_ALPHA, /* U+00d6d2: */
+ RTUNI_ALPHA, /* U+00d6d3: */
+ RTUNI_ALPHA, /* U+00d6d4: */
+ RTUNI_ALPHA, /* U+00d6d5: */
+ RTUNI_ALPHA, /* U+00d6d6: */
+ RTUNI_ALPHA, /* U+00d6d7: */
+ RTUNI_ALPHA, /* U+00d6d8: */
+ RTUNI_ALPHA, /* U+00d6d9: */
+ RTUNI_ALPHA, /* U+00d6da: */
+ RTUNI_ALPHA, /* U+00d6db: */
+ RTUNI_ALPHA, /* U+00d6dc: */
+ RTUNI_ALPHA, /* U+00d6dd: */
+ RTUNI_ALPHA, /* U+00d6de: */
+ RTUNI_ALPHA, /* U+00d6df: */
+ RTUNI_ALPHA, /* U+00d6e0: */
+ RTUNI_ALPHA, /* U+00d6e1: */
+ RTUNI_ALPHA, /* U+00d6e2: */
+ RTUNI_ALPHA, /* U+00d6e3: */
+ RTUNI_ALPHA, /* U+00d6e4: */
+ RTUNI_ALPHA, /* U+00d6e5: */
+ RTUNI_ALPHA, /* U+00d6e6: */
+ RTUNI_ALPHA, /* U+00d6e7: */
+ RTUNI_ALPHA, /* U+00d6e8: */
+ RTUNI_ALPHA, /* U+00d6e9: */
+ RTUNI_ALPHA, /* U+00d6ea: */
+ RTUNI_ALPHA, /* U+00d6eb: */
+ RTUNI_ALPHA, /* U+00d6ec: */
+ RTUNI_ALPHA, /* U+00d6ed: */
+ RTUNI_ALPHA, /* U+00d6ee: */
+ RTUNI_ALPHA, /* U+00d6ef: */
+ RTUNI_ALPHA, /* U+00d6f0: */
+ RTUNI_ALPHA, /* U+00d6f1: */
+ RTUNI_ALPHA, /* U+00d6f2: */
+ RTUNI_ALPHA, /* U+00d6f3: */
+ RTUNI_ALPHA, /* U+00d6f4: */
+ RTUNI_ALPHA, /* U+00d6f5: */
+ RTUNI_ALPHA, /* U+00d6f6: */
+ RTUNI_ALPHA, /* U+00d6f7: */
+ RTUNI_ALPHA, /* U+00d6f8: */
+ RTUNI_ALPHA, /* U+00d6f9: */
+ RTUNI_ALPHA, /* U+00d6fa: */
+ RTUNI_ALPHA, /* U+00d6fb: */
+ RTUNI_ALPHA, /* U+00d6fc: */
+ RTUNI_ALPHA, /* U+00d6fd: */
+ RTUNI_ALPHA, /* U+00d6fe: */
+ RTUNI_ALPHA, /* U+00d6ff: */
+ RTUNI_ALPHA, /* U+00d700: */
+ RTUNI_ALPHA, /* U+00d701: */
+ RTUNI_ALPHA, /* U+00d702: */
+ RTUNI_ALPHA, /* U+00d703: */
+ RTUNI_ALPHA, /* U+00d704: */
+ RTUNI_ALPHA, /* U+00d705: */
+ RTUNI_ALPHA, /* U+00d706: */
+ RTUNI_ALPHA, /* U+00d707: */
+ RTUNI_ALPHA, /* U+00d708: */
+ RTUNI_ALPHA, /* U+00d709: */
+ RTUNI_ALPHA, /* U+00d70a: */
+ RTUNI_ALPHA, /* U+00d70b: */
+ RTUNI_ALPHA, /* U+00d70c: */
+ RTUNI_ALPHA, /* U+00d70d: */
+ RTUNI_ALPHA, /* U+00d70e: */
+ RTUNI_ALPHA, /* U+00d70f: */
+ RTUNI_ALPHA, /* U+00d710: */
+ RTUNI_ALPHA, /* U+00d711: */
+ RTUNI_ALPHA, /* U+00d712: */
+ RTUNI_ALPHA, /* U+00d713: */
+ RTUNI_ALPHA, /* U+00d714: */
+ RTUNI_ALPHA, /* U+00d715: */
+ RTUNI_ALPHA, /* U+00d716: */
+ RTUNI_ALPHA, /* U+00d717: */
+ RTUNI_ALPHA, /* U+00d718: */
+ RTUNI_ALPHA, /* U+00d719: */
+ RTUNI_ALPHA, /* U+00d71a: */
+ RTUNI_ALPHA, /* U+00d71b: */
+ RTUNI_ALPHA, /* U+00d71c: */
+ RTUNI_ALPHA, /* U+00d71d: */
+ RTUNI_ALPHA, /* U+00d71e: */
+ RTUNI_ALPHA, /* U+00d71f: */
+ RTUNI_ALPHA, /* U+00d720: */
+ RTUNI_ALPHA, /* U+00d721: */
+ RTUNI_ALPHA, /* U+00d722: */
+ RTUNI_ALPHA, /* U+00d723: */
+ RTUNI_ALPHA, /* U+00d724: */
+ RTUNI_ALPHA, /* U+00d725: */
+ RTUNI_ALPHA, /* U+00d726: */
+ RTUNI_ALPHA, /* U+00d727: */
+ RTUNI_ALPHA, /* U+00d728: */
+ RTUNI_ALPHA, /* U+00d729: */
+ RTUNI_ALPHA, /* U+00d72a: */
+ RTUNI_ALPHA, /* U+00d72b: */
+ RTUNI_ALPHA, /* U+00d72c: */
+ RTUNI_ALPHA, /* U+00d72d: */
+ RTUNI_ALPHA, /* U+00d72e: */
+ RTUNI_ALPHA, /* U+00d72f: */
+ RTUNI_ALPHA, /* U+00d730: */
+ RTUNI_ALPHA, /* U+00d731: */
+ RTUNI_ALPHA, /* U+00d732: */
+ RTUNI_ALPHA, /* U+00d733: */
+ RTUNI_ALPHA, /* U+00d734: */
+ RTUNI_ALPHA, /* U+00d735: */
+ RTUNI_ALPHA, /* U+00d736: */
+ RTUNI_ALPHA, /* U+00d737: */
+ RTUNI_ALPHA, /* U+00d738: */
+ RTUNI_ALPHA, /* U+00d739: */
+ RTUNI_ALPHA, /* U+00d73a: */
+ RTUNI_ALPHA, /* U+00d73b: */
+ RTUNI_ALPHA, /* U+00d73c: */
+ RTUNI_ALPHA, /* U+00d73d: */
+ RTUNI_ALPHA, /* U+00d73e: */
+ RTUNI_ALPHA, /* U+00d73f: */
+ RTUNI_ALPHA, /* U+00d740: */
+ RTUNI_ALPHA, /* U+00d741: */
+ RTUNI_ALPHA, /* U+00d742: */
+ RTUNI_ALPHA, /* U+00d743: */
+ RTUNI_ALPHA, /* U+00d744: */
+ RTUNI_ALPHA, /* U+00d745: */
+ RTUNI_ALPHA, /* U+00d746: */
+ RTUNI_ALPHA, /* U+00d747: */
+ RTUNI_ALPHA, /* U+00d748: */
+ RTUNI_ALPHA, /* U+00d749: */
+ RTUNI_ALPHA, /* U+00d74a: */
+ RTUNI_ALPHA, /* U+00d74b: */
+ RTUNI_ALPHA, /* U+00d74c: */
+ RTUNI_ALPHA, /* U+00d74d: */
+ RTUNI_ALPHA, /* U+00d74e: */
+ RTUNI_ALPHA, /* U+00d74f: */
+ RTUNI_ALPHA, /* U+00d750: */
+ RTUNI_ALPHA, /* U+00d751: */
+ RTUNI_ALPHA, /* U+00d752: */
+ RTUNI_ALPHA, /* U+00d753: */
+ RTUNI_ALPHA, /* U+00d754: */
+ RTUNI_ALPHA, /* U+00d755: */
+ RTUNI_ALPHA, /* U+00d756: */
+ RTUNI_ALPHA, /* U+00d757: */
+ RTUNI_ALPHA, /* U+00d758: */
+ RTUNI_ALPHA, /* U+00d759: */
+ RTUNI_ALPHA, /* U+00d75a: */
+ RTUNI_ALPHA, /* U+00d75b: */
+ RTUNI_ALPHA, /* U+00d75c: */
+ RTUNI_ALPHA, /* U+00d75d: */
+ RTUNI_ALPHA, /* U+00d75e: */
+ RTUNI_ALPHA, /* U+00d75f: */
+ RTUNI_ALPHA, /* U+00d760: */
+ RTUNI_ALPHA, /* U+00d761: */
+ RTUNI_ALPHA, /* U+00d762: */
+ RTUNI_ALPHA, /* U+00d763: */
+ RTUNI_ALPHA, /* U+00d764: */
+ RTUNI_ALPHA, /* U+00d765: */
+ RTUNI_ALPHA, /* U+00d766: */
+ RTUNI_ALPHA, /* U+00d767: */
+ RTUNI_ALPHA, /* U+00d768: */
+ RTUNI_ALPHA, /* U+00d769: */
+ RTUNI_ALPHA, /* U+00d76a: */
+ RTUNI_ALPHA, /* U+00d76b: */
+ RTUNI_ALPHA, /* U+00d76c: */
+ RTUNI_ALPHA, /* U+00d76d: */
+ RTUNI_ALPHA, /* U+00d76e: */
+ RTUNI_ALPHA, /* U+00d76f: */
+ RTUNI_ALPHA, /* U+00d770: */
+ RTUNI_ALPHA, /* U+00d771: */
+ RTUNI_ALPHA, /* U+00d772: */
+ RTUNI_ALPHA, /* U+00d773: */
+ RTUNI_ALPHA, /* U+00d774: */
+ RTUNI_ALPHA, /* U+00d775: */
+ RTUNI_ALPHA, /* U+00d776: */
+ RTUNI_ALPHA, /* U+00d777: */
+ RTUNI_ALPHA, /* U+00d778: */
+ RTUNI_ALPHA, /* U+00d779: */
+ RTUNI_ALPHA, /* U+00d77a: */
+ RTUNI_ALPHA, /* U+00d77b: */
+ RTUNI_ALPHA, /* U+00d77c: */
+ RTUNI_ALPHA, /* U+00d77d: */
+ RTUNI_ALPHA, /* U+00d77e: */
+ RTUNI_ALPHA, /* U+00d77f: */
+ RTUNI_ALPHA, /* U+00d780: */
+ RTUNI_ALPHA, /* U+00d781: */
+ RTUNI_ALPHA, /* U+00d782: */
+ RTUNI_ALPHA, /* U+00d783: */
+ RTUNI_ALPHA, /* U+00d784: */
+ RTUNI_ALPHA, /* U+00d785: */
+ RTUNI_ALPHA, /* U+00d786: */
+ RTUNI_ALPHA, /* U+00d787: */
+ RTUNI_ALPHA, /* U+00d788: */
+ RTUNI_ALPHA, /* U+00d789: */
+ RTUNI_ALPHA, /* U+00d78a: */
+ RTUNI_ALPHA, /* U+00d78b: */
+ RTUNI_ALPHA, /* U+00d78c: */
+ RTUNI_ALPHA, /* U+00d78d: */
+ RTUNI_ALPHA, /* U+00d78e: */
+ RTUNI_ALPHA, /* U+00d78f: */
+ RTUNI_ALPHA, /* U+00d790: */
+ RTUNI_ALPHA, /* U+00d791: */
+ RTUNI_ALPHA, /* U+00d792: */
+ RTUNI_ALPHA, /* U+00d793: */
+ RTUNI_ALPHA, /* U+00d794: */
+ RTUNI_ALPHA, /* U+00d795: */
+ RTUNI_ALPHA, /* U+00d796: */
+ RTUNI_ALPHA, /* U+00d797: */
+ RTUNI_ALPHA, /* U+00d798: */
+ RTUNI_ALPHA, /* U+00d799: */
+ RTUNI_ALPHA, /* U+00d79a: */
+ RTUNI_ALPHA, /* U+00d79b: */
+ RTUNI_ALPHA, /* U+00d79c: */
+ RTUNI_ALPHA, /* U+00d79d: */
+ RTUNI_ALPHA, /* U+00d79e: */
+ RTUNI_ALPHA, /* U+00d79f: */
+ RTUNI_ALPHA, /* U+00d7a0: */
+ RTUNI_ALPHA, /* U+00d7a1: */
+ RTUNI_ALPHA, /* U+00d7a2: */
+ RTUNI_ALPHA, /* U+00d7a3: <Hangul Syllable, Last>*/
+ 0, /* U+00d7a4: */
+ 0, /* U+00d7a5: */
+ 0, /* U+00d7a6: */
+ 0, /* U+00d7a7: */
+ 0, /* U+00d7a8: */
+ 0, /* U+00d7a9: */
+ 0, /* U+00d7aa: */
+ 0, /* U+00d7ab: */
+ 0, /* U+00d7ac: */
+ 0, /* U+00d7ad: */
+ 0, /* U+00d7ae: */
+ 0, /* U+00d7af: */
+ RTUNI_ALPHA, /* U+00d7b0: HANGUL JUNGSEONG O-YEO*/
+ RTUNI_ALPHA, /* U+00d7b1: HANGUL JUNGSEONG O-O-I*/
+ RTUNI_ALPHA, /* U+00d7b2: HANGUL JUNGSEONG YO-A*/
+ RTUNI_ALPHA, /* U+00d7b3: HANGUL JUNGSEONG YO-AE*/
+ RTUNI_ALPHA, /* U+00d7b4: HANGUL JUNGSEONG YO-EO*/
+ RTUNI_ALPHA, /* U+00d7b5: HANGUL JUNGSEONG U-YEO*/
+ RTUNI_ALPHA, /* U+00d7b6: HANGUL JUNGSEONG U-I-I*/
+ RTUNI_ALPHA, /* U+00d7b7: HANGUL JUNGSEONG YU-AE*/
+ RTUNI_ALPHA, /* U+00d7b8: HANGUL JUNGSEONG YU-O*/
+ RTUNI_ALPHA, /* U+00d7b9: HANGUL JUNGSEONG EU-A*/
+ RTUNI_ALPHA, /* U+00d7ba: HANGUL JUNGSEONG EU-EO*/
+ RTUNI_ALPHA, /* U+00d7bb: HANGUL JUNGSEONG EU-E*/
+ RTUNI_ALPHA, /* U+00d7bc: HANGUL JUNGSEONG EU-O*/
+ RTUNI_ALPHA, /* U+00d7bd: HANGUL JUNGSEONG I-YA-O*/
+ RTUNI_ALPHA, /* U+00d7be: HANGUL JUNGSEONG I-YAE*/
+ RTUNI_ALPHA, /* U+00d7bf: HANGUL JUNGSEONG I-YEO*/
+ RTUNI_ALPHA, /* U+00d7c0: HANGUL JUNGSEONG I-YE*/
+ RTUNI_ALPHA, /* U+00d7c1: HANGUL JUNGSEONG I-O-I*/
+ RTUNI_ALPHA, /* U+00d7c2: HANGUL JUNGSEONG I-YO*/
+ RTUNI_ALPHA, /* U+00d7c3: HANGUL JUNGSEONG I-YU*/
+ RTUNI_ALPHA, /* U+00d7c4: HANGUL JUNGSEONG I-I*/
+ RTUNI_ALPHA, /* U+00d7c5: HANGUL JUNGSEONG ARAEA-A*/
+ RTUNI_ALPHA, /* U+00d7c6: HANGUL JUNGSEONG ARAEA-E*/
+ 0, /* U+00d7c7: */
+ 0, /* U+00d7c8: */
+ 0, /* U+00d7c9: */
+ 0, /* U+00d7ca: */
+ RTUNI_ALPHA, /* U+00d7cb: HANGUL JONGSEONG NIEUN-RIEUL*/
+ RTUNI_ALPHA, /* U+00d7cc: HANGUL JONGSEONG NIEUN-CHIEUCH*/
+ RTUNI_ALPHA, /* U+00d7cd: HANGUL JONGSEONG SSANGTIKEUT*/
+ RTUNI_ALPHA, /* U+00d7ce: HANGUL JONGSEONG SSANGTIKEUT-PIEUP*/
+ RTUNI_ALPHA, /* U+00d7cf: HANGUL JONGSEONG TIKEUT-PIEUP*/
+ RTUNI_ALPHA, /* U+00d7d0: HANGUL JONGSEONG TIKEUT-SIOS*/
+ RTUNI_ALPHA, /* U+00d7d1: HANGUL JONGSEONG TIKEUT-SIOS-KIYEOK*/
+ RTUNI_ALPHA, /* U+00d7d2: HANGUL JONGSEONG TIKEUT-CIEUC*/
+ RTUNI_ALPHA, /* U+00d7d3: HANGUL JONGSEONG TIKEUT-CHIEUCH*/
+ RTUNI_ALPHA, /* U+00d7d4: HANGUL JONGSEONG TIKEUT-THIEUTH*/
+ RTUNI_ALPHA, /* U+00d7d5: HANGUL JONGSEONG RIEUL-SSANGKIYEOK*/
+ RTUNI_ALPHA, /* U+00d7d6: HANGUL JONGSEONG RIEUL-KIYEOK-HIEUH*/
+ RTUNI_ALPHA, /* U+00d7d7: HANGUL JONGSEONG SSANGRIEUL-KHIEUKH*/
+ RTUNI_ALPHA, /* U+00d7d8: HANGUL JONGSEONG RIEUL-MIEUM-HIEUH*/
+ RTUNI_ALPHA, /* U+00d7d9: HANGUL JONGSEONG RIEUL-PIEUP-TIKEUT*/
+ RTUNI_ALPHA, /* U+00d7da: HANGUL JONGSEONG RIEUL-PIEUP-PHIEUPH*/
+ RTUNI_ALPHA, /* U+00d7db: HANGUL JONGSEONG RIEUL-YESIEUNG*/
+ RTUNI_ALPHA, /* U+00d7dc: HANGUL JONGSEONG RIEUL-YEORINHIEUH-HIEUH*/
+ RTUNI_ALPHA, /* U+00d7dd: HANGUL JONGSEONG KAPYEOUNRIEUL*/
+ RTUNI_ALPHA, /* U+00d7de: HANGUL JONGSEONG MIEUM-NIEUN*/
+ RTUNI_ALPHA, /* U+00d7df: HANGUL JONGSEONG MIEUM-SSANGNIEUN*/
+ RTUNI_ALPHA, /* U+00d7e0: HANGUL JONGSEONG SSANGMIEUM*/
+ RTUNI_ALPHA, /* U+00d7e1: HANGUL JONGSEONG MIEUM-PIEUP-SIOS*/
+ RTUNI_ALPHA, /* U+00d7e2: HANGUL JONGSEONG MIEUM-CIEUC*/
+ RTUNI_ALPHA, /* U+00d7e3: HANGUL JONGSEONG PIEUP-TIKEUT*/
+ RTUNI_ALPHA, /* U+00d7e4: HANGUL JONGSEONG PIEUP-RIEUL-PHIEUPH*/
+ RTUNI_ALPHA, /* U+00d7e5: HANGUL JONGSEONG PIEUP-MIEUM*/
+ RTUNI_ALPHA, /* U+00d7e6: HANGUL JONGSEONG SSANGPIEUP*/
+ RTUNI_ALPHA, /* U+00d7e7: HANGUL JONGSEONG PIEUP-SIOS-TIKEUT*/
+ RTUNI_ALPHA, /* U+00d7e8: HANGUL JONGSEONG PIEUP-CIEUC*/
+ RTUNI_ALPHA, /* U+00d7e9: HANGUL JONGSEONG PIEUP-CHIEUCH*/
+ RTUNI_ALPHA, /* U+00d7ea: HANGUL JONGSEONG SIOS-MIEUM*/
+ RTUNI_ALPHA, /* U+00d7eb: HANGUL JONGSEONG SIOS-KAPYEOUNPIEUP*/
+ RTUNI_ALPHA, /* U+00d7ec: HANGUL JONGSEONG SSANGSIOS-KIYEOK*/
+ RTUNI_ALPHA, /* U+00d7ed: HANGUL JONGSEONG SSANGSIOS-TIKEUT*/
+ RTUNI_ALPHA, /* U+00d7ee: HANGUL JONGSEONG SIOS-PANSIOS*/
+ RTUNI_ALPHA, /* U+00d7ef: HANGUL JONGSEONG SIOS-CIEUC*/
+ RTUNI_ALPHA, /* U+00d7f0: HANGUL JONGSEONG SIOS-CHIEUCH*/
+ RTUNI_ALPHA, /* U+00d7f1: HANGUL JONGSEONG SIOS-THIEUTH*/
+ RTUNI_ALPHA, /* U+00d7f2: HANGUL JONGSEONG SIOS-HIEUH*/
+ RTUNI_ALPHA, /* U+00d7f3: HANGUL JONGSEONG PANSIOS-PIEUP*/
+ RTUNI_ALPHA, /* U+00d7f4: HANGUL JONGSEONG PANSIOS-KAPYEOUNPIEUP*/
+ RTUNI_ALPHA, /* U+00d7f5: HANGUL JONGSEONG YESIEUNG-MIEUM*/
+ RTUNI_ALPHA, /* U+00d7f6: HANGUL JONGSEONG YESIEUNG-HIEUH*/
+ RTUNI_ALPHA, /* U+00d7f7: HANGUL JONGSEONG CIEUC-PIEUP*/
+ RTUNI_ALPHA, /* U+00d7f8: HANGUL JONGSEONG CIEUC-SSANGPIEUP*/
+ RTUNI_ALPHA, /* U+00d7f9: HANGUL JONGSEONG SSANGCIEUC*/
+ RTUNI_ALPHA, /* U+00d7fa: HANGUL JONGSEONG PHIEUPH-SIOS*/
+ RTUNI_ALPHA, /* U+00d7fb: HANGUL JONGSEONG PHIEUPH-THIEUTH*/
+};
+
+static const uint8_t g_afRTUniFlags0x00f900[] =
+{
+ RTUNI_ALPHA, /* U+00f900: CJK COMPATIBILITY IDEOGRAPH-F900*/
+ RTUNI_ALPHA, /* U+00f901: CJK COMPATIBILITY IDEOGRAPH-F901*/
+ RTUNI_ALPHA, /* U+00f902: CJK COMPATIBILITY IDEOGRAPH-F902*/
+ RTUNI_ALPHA, /* U+00f903: CJK COMPATIBILITY IDEOGRAPH-F903*/
+ RTUNI_ALPHA, /* U+00f904: CJK COMPATIBILITY IDEOGRAPH-F904*/
+ RTUNI_ALPHA, /* U+00f905: CJK COMPATIBILITY IDEOGRAPH-F905*/
+ RTUNI_ALPHA, /* U+00f906: CJK COMPATIBILITY IDEOGRAPH-F906*/
+ RTUNI_ALPHA, /* U+00f907: CJK COMPATIBILITY IDEOGRAPH-F907*/
+ RTUNI_ALPHA, /* U+00f908: CJK COMPATIBILITY IDEOGRAPH-F908*/
+ RTUNI_ALPHA, /* U+00f909: CJK COMPATIBILITY IDEOGRAPH-F909*/
+ RTUNI_ALPHA, /* U+00f90a: CJK COMPATIBILITY IDEOGRAPH-F90A*/
+ RTUNI_ALPHA, /* U+00f90b: CJK COMPATIBILITY IDEOGRAPH-F90B*/
+ RTUNI_ALPHA, /* U+00f90c: CJK COMPATIBILITY IDEOGRAPH-F90C*/
+ RTUNI_ALPHA, /* U+00f90d: CJK COMPATIBILITY IDEOGRAPH-F90D*/
+ RTUNI_ALPHA, /* U+00f90e: CJK COMPATIBILITY IDEOGRAPH-F90E*/
+ RTUNI_ALPHA, /* U+00f90f: CJK COMPATIBILITY IDEOGRAPH-F90F*/
+ RTUNI_ALPHA, /* U+00f910: CJK COMPATIBILITY IDEOGRAPH-F910*/
+ RTUNI_ALPHA, /* U+00f911: CJK COMPATIBILITY IDEOGRAPH-F911*/
+ RTUNI_ALPHA, /* U+00f912: CJK COMPATIBILITY IDEOGRAPH-F912*/
+ RTUNI_ALPHA, /* U+00f913: CJK COMPATIBILITY IDEOGRAPH-F913*/
+ RTUNI_ALPHA, /* U+00f914: CJK COMPATIBILITY IDEOGRAPH-F914*/
+ RTUNI_ALPHA, /* U+00f915: CJK COMPATIBILITY IDEOGRAPH-F915*/
+ RTUNI_ALPHA, /* U+00f916: CJK COMPATIBILITY IDEOGRAPH-F916*/
+ RTUNI_ALPHA, /* U+00f917: CJK COMPATIBILITY IDEOGRAPH-F917*/
+ RTUNI_ALPHA, /* U+00f918: CJK COMPATIBILITY IDEOGRAPH-F918*/
+ RTUNI_ALPHA, /* U+00f919: CJK COMPATIBILITY IDEOGRAPH-F919*/
+ RTUNI_ALPHA, /* U+00f91a: CJK COMPATIBILITY IDEOGRAPH-F91A*/
+ RTUNI_ALPHA, /* U+00f91b: CJK COMPATIBILITY IDEOGRAPH-F91B*/
+ RTUNI_ALPHA, /* U+00f91c: CJK COMPATIBILITY IDEOGRAPH-F91C*/
+ RTUNI_ALPHA, /* U+00f91d: CJK COMPATIBILITY IDEOGRAPH-F91D*/
+ RTUNI_ALPHA, /* U+00f91e: CJK COMPATIBILITY IDEOGRAPH-F91E*/
+ RTUNI_ALPHA, /* U+00f91f: CJK COMPATIBILITY IDEOGRAPH-F91F*/
+ RTUNI_ALPHA, /* U+00f920: CJK COMPATIBILITY IDEOGRAPH-F920*/
+ RTUNI_ALPHA, /* U+00f921: CJK COMPATIBILITY IDEOGRAPH-F921*/
+ RTUNI_ALPHA, /* U+00f922: CJK COMPATIBILITY IDEOGRAPH-F922*/
+ RTUNI_ALPHA, /* U+00f923: CJK COMPATIBILITY IDEOGRAPH-F923*/
+ RTUNI_ALPHA, /* U+00f924: CJK COMPATIBILITY IDEOGRAPH-F924*/
+ RTUNI_ALPHA, /* U+00f925: CJK COMPATIBILITY IDEOGRAPH-F925*/
+ RTUNI_ALPHA, /* U+00f926: CJK COMPATIBILITY IDEOGRAPH-F926*/
+ RTUNI_ALPHA, /* U+00f927: CJK COMPATIBILITY IDEOGRAPH-F927*/
+ RTUNI_ALPHA, /* U+00f928: CJK COMPATIBILITY IDEOGRAPH-F928*/
+ RTUNI_ALPHA, /* U+00f929: CJK COMPATIBILITY IDEOGRAPH-F929*/
+ RTUNI_ALPHA, /* U+00f92a: CJK COMPATIBILITY IDEOGRAPH-F92A*/
+ RTUNI_ALPHA, /* U+00f92b: CJK COMPATIBILITY IDEOGRAPH-F92B*/
+ RTUNI_ALPHA, /* U+00f92c: CJK COMPATIBILITY IDEOGRAPH-F92C*/
+ RTUNI_ALPHA, /* U+00f92d: CJK COMPATIBILITY IDEOGRAPH-F92D*/
+ RTUNI_ALPHA, /* U+00f92e: CJK COMPATIBILITY IDEOGRAPH-F92E*/
+ RTUNI_ALPHA, /* U+00f92f: CJK COMPATIBILITY IDEOGRAPH-F92F*/
+ RTUNI_ALPHA, /* U+00f930: CJK COMPATIBILITY IDEOGRAPH-F930*/
+ RTUNI_ALPHA, /* U+00f931: CJK COMPATIBILITY IDEOGRAPH-F931*/
+ RTUNI_ALPHA, /* U+00f932: CJK COMPATIBILITY IDEOGRAPH-F932*/
+ RTUNI_ALPHA, /* U+00f933: CJK COMPATIBILITY IDEOGRAPH-F933*/
+ RTUNI_ALPHA, /* U+00f934: CJK COMPATIBILITY IDEOGRAPH-F934*/
+ RTUNI_ALPHA, /* U+00f935: CJK COMPATIBILITY IDEOGRAPH-F935*/
+ RTUNI_ALPHA, /* U+00f936: CJK COMPATIBILITY IDEOGRAPH-F936*/
+ RTUNI_ALPHA, /* U+00f937: CJK COMPATIBILITY IDEOGRAPH-F937*/
+ RTUNI_ALPHA, /* U+00f938: CJK COMPATIBILITY IDEOGRAPH-F938*/
+ RTUNI_ALPHA, /* U+00f939: CJK COMPATIBILITY IDEOGRAPH-F939*/
+ RTUNI_ALPHA, /* U+00f93a: CJK COMPATIBILITY IDEOGRAPH-F93A*/
+ RTUNI_ALPHA, /* U+00f93b: CJK COMPATIBILITY IDEOGRAPH-F93B*/
+ RTUNI_ALPHA, /* U+00f93c: CJK COMPATIBILITY IDEOGRAPH-F93C*/
+ RTUNI_ALPHA, /* U+00f93d: CJK COMPATIBILITY IDEOGRAPH-F93D*/
+ RTUNI_ALPHA, /* U+00f93e: CJK COMPATIBILITY IDEOGRAPH-F93E*/
+ RTUNI_ALPHA, /* U+00f93f: CJK COMPATIBILITY IDEOGRAPH-F93F*/
+ RTUNI_ALPHA, /* U+00f940: CJK COMPATIBILITY IDEOGRAPH-F940*/
+ RTUNI_ALPHA, /* U+00f941: CJK COMPATIBILITY IDEOGRAPH-F941*/
+ RTUNI_ALPHA, /* U+00f942: CJK COMPATIBILITY IDEOGRAPH-F942*/
+ RTUNI_ALPHA, /* U+00f943: CJK COMPATIBILITY IDEOGRAPH-F943*/
+ RTUNI_ALPHA, /* U+00f944: CJK COMPATIBILITY IDEOGRAPH-F944*/
+ RTUNI_ALPHA, /* U+00f945: CJK COMPATIBILITY IDEOGRAPH-F945*/
+ RTUNI_ALPHA, /* U+00f946: CJK COMPATIBILITY IDEOGRAPH-F946*/
+ RTUNI_ALPHA, /* U+00f947: CJK COMPATIBILITY IDEOGRAPH-F947*/
+ RTUNI_ALPHA, /* U+00f948: CJK COMPATIBILITY IDEOGRAPH-F948*/
+ RTUNI_ALPHA, /* U+00f949: CJK COMPATIBILITY IDEOGRAPH-F949*/
+ RTUNI_ALPHA, /* U+00f94a: CJK COMPATIBILITY IDEOGRAPH-F94A*/
+ RTUNI_ALPHA, /* U+00f94b: CJK COMPATIBILITY IDEOGRAPH-F94B*/
+ RTUNI_ALPHA, /* U+00f94c: CJK COMPATIBILITY IDEOGRAPH-F94C*/
+ RTUNI_ALPHA, /* U+00f94d: CJK COMPATIBILITY IDEOGRAPH-F94D*/
+ RTUNI_ALPHA, /* U+00f94e: CJK COMPATIBILITY IDEOGRAPH-F94E*/
+ RTUNI_ALPHA, /* U+00f94f: CJK COMPATIBILITY IDEOGRAPH-F94F*/
+ RTUNI_ALPHA, /* U+00f950: CJK COMPATIBILITY IDEOGRAPH-F950*/
+ RTUNI_ALPHA, /* U+00f951: CJK COMPATIBILITY IDEOGRAPH-F951*/
+ RTUNI_ALPHA, /* U+00f952: CJK COMPATIBILITY IDEOGRAPH-F952*/
+ RTUNI_ALPHA, /* U+00f953: CJK COMPATIBILITY IDEOGRAPH-F953*/
+ RTUNI_ALPHA, /* U+00f954: CJK COMPATIBILITY IDEOGRAPH-F954*/
+ RTUNI_ALPHA, /* U+00f955: CJK COMPATIBILITY IDEOGRAPH-F955*/
+ RTUNI_ALPHA, /* U+00f956: CJK COMPATIBILITY IDEOGRAPH-F956*/
+ RTUNI_ALPHA, /* U+00f957: CJK COMPATIBILITY IDEOGRAPH-F957*/
+ RTUNI_ALPHA, /* U+00f958: CJK COMPATIBILITY IDEOGRAPH-F958*/
+ RTUNI_ALPHA, /* U+00f959: CJK COMPATIBILITY IDEOGRAPH-F959*/
+ RTUNI_ALPHA, /* U+00f95a: CJK COMPATIBILITY IDEOGRAPH-F95A*/
+ RTUNI_ALPHA, /* U+00f95b: CJK COMPATIBILITY IDEOGRAPH-F95B*/
+ RTUNI_ALPHA, /* U+00f95c: CJK COMPATIBILITY IDEOGRAPH-F95C*/
+ RTUNI_ALPHA, /* U+00f95d: CJK COMPATIBILITY IDEOGRAPH-F95D*/
+ RTUNI_ALPHA, /* U+00f95e: CJK COMPATIBILITY IDEOGRAPH-F95E*/
+ RTUNI_ALPHA, /* U+00f95f: CJK COMPATIBILITY IDEOGRAPH-F95F*/
+ RTUNI_ALPHA, /* U+00f960: CJK COMPATIBILITY IDEOGRAPH-F960*/
+ RTUNI_ALPHA, /* U+00f961: CJK COMPATIBILITY IDEOGRAPH-F961*/
+ RTUNI_ALPHA, /* U+00f962: CJK COMPATIBILITY IDEOGRAPH-F962*/
+ RTUNI_ALPHA, /* U+00f963: CJK COMPATIBILITY IDEOGRAPH-F963*/
+ RTUNI_ALPHA, /* U+00f964: CJK COMPATIBILITY IDEOGRAPH-F964*/
+ RTUNI_ALPHA, /* U+00f965: CJK COMPATIBILITY IDEOGRAPH-F965*/
+ RTUNI_ALPHA, /* U+00f966: CJK COMPATIBILITY IDEOGRAPH-F966*/
+ RTUNI_ALPHA, /* U+00f967: CJK COMPATIBILITY IDEOGRAPH-F967*/
+ RTUNI_ALPHA, /* U+00f968: CJK COMPATIBILITY IDEOGRAPH-F968*/
+ RTUNI_ALPHA, /* U+00f969: CJK COMPATIBILITY IDEOGRAPH-F969*/
+ RTUNI_ALPHA, /* U+00f96a: CJK COMPATIBILITY IDEOGRAPH-F96A*/
+ RTUNI_ALPHA, /* U+00f96b: CJK COMPATIBILITY IDEOGRAPH-F96B*/
+ RTUNI_ALPHA, /* U+00f96c: CJK COMPATIBILITY IDEOGRAPH-F96C*/
+ RTUNI_ALPHA, /* U+00f96d: CJK COMPATIBILITY IDEOGRAPH-F96D*/
+ RTUNI_ALPHA, /* U+00f96e: CJK COMPATIBILITY IDEOGRAPH-F96E*/
+ RTUNI_ALPHA, /* U+00f96f: CJK COMPATIBILITY IDEOGRAPH-F96F*/
+ RTUNI_ALPHA, /* U+00f970: CJK COMPATIBILITY IDEOGRAPH-F970*/
+ RTUNI_ALPHA, /* U+00f971: CJK COMPATIBILITY IDEOGRAPH-F971*/
+ RTUNI_ALPHA, /* U+00f972: CJK COMPATIBILITY IDEOGRAPH-F972*/
+ RTUNI_ALPHA, /* U+00f973: CJK COMPATIBILITY IDEOGRAPH-F973*/
+ RTUNI_ALPHA, /* U+00f974: CJK COMPATIBILITY IDEOGRAPH-F974*/
+ RTUNI_ALPHA, /* U+00f975: CJK COMPATIBILITY IDEOGRAPH-F975*/
+ RTUNI_ALPHA, /* U+00f976: CJK COMPATIBILITY IDEOGRAPH-F976*/
+ RTUNI_ALPHA, /* U+00f977: CJK COMPATIBILITY IDEOGRAPH-F977*/
+ RTUNI_ALPHA, /* U+00f978: CJK COMPATIBILITY IDEOGRAPH-F978*/
+ RTUNI_ALPHA, /* U+00f979: CJK COMPATIBILITY IDEOGRAPH-F979*/
+ RTUNI_ALPHA, /* U+00f97a: CJK COMPATIBILITY IDEOGRAPH-F97A*/
+ RTUNI_ALPHA, /* U+00f97b: CJK COMPATIBILITY IDEOGRAPH-F97B*/
+ RTUNI_ALPHA, /* U+00f97c: CJK COMPATIBILITY IDEOGRAPH-F97C*/
+ RTUNI_ALPHA, /* U+00f97d: CJK COMPATIBILITY IDEOGRAPH-F97D*/
+ RTUNI_ALPHA, /* U+00f97e: CJK COMPATIBILITY IDEOGRAPH-F97E*/
+ RTUNI_ALPHA, /* U+00f97f: CJK COMPATIBILITY IDEOGRAPH-F97F*/
+ RTUNI_ALPHA, /* U+00f980: CJK COMPATIBILITY IDEOGRAPH-F980*/
+ RTUNI_ALPHA, /* U+00f981: CJK COMPATIBILITY IDEOGRAPH-F981*/
+ RTUNI_ALPHA, /* U+00f982: CJK COMPATIBILITY IDEOGRAPH-F982*/
+ RTUNI_ALPHA, /* U+00f983: CJK COMPATIBILITY IDEOGRAPH-F983*/
+ RTUNI_ALPHA, /* U+00f984: CJK COMPATIBILITY IDEOGRAPH-F984*/
+ RTUNI_ALPHA, /* U+00f985: CJK COMPATIBILITY IDEOGRAPH-F985*/
+ RTUNI_ALPHA, /* U+00f986: CJK COMPATIBILITY IDEOGRAPH-F986*/
+ RTUNI_ALPHA, /* U+00f987: CJK COMPATIBILITY IDEOGRAPH-F987*/
+ RTUNI_ALPHA, /* U+00f988: CJK COMPATIBILITY IDEOGRAPH-F988*/
+ RTUNI_ALPHA, /* U+00f989: CJK COMPATIBILITY IDEOGRAPH-F989*/
+ RTUNI_ALPHA, /* U+00f98a: CJK COMPATIBILITY IDEOGRAPH-F98A*/
+ RTUNI_ALPHA, /* U+00f98b: CJK COMPATIBILITY IDEOGRAPH-F98B*/
+ RTUNI_ALPHA, /* U+00f98c: CJK COMPATIBILITY IDEOGRAPH-F98C*/
+ RTUNI_ALPHA, /* U+00f98d: CJK COMPATIBILITY IDEOGRAPH-F98D*/
+ RTUNI_ALPHA, /* U+00f98e: CJK COMPATIBILITY IDEOGRAPH-F98E*/
+ RTUNI_ALPHA, /* U+00f98f: CJK COMPATIBILITY IDEOGRAPH-F98F*/
+ RTUNI_ALPHA, /* U+00f990: CJK COMPATIBILITY IDEOGRAPH-F990*/
+ RTUNI_ALPHA, /* U+00f991: CJK COMPATIBILITY IDEOGRAPH-F991*/
+ RTUNI_ALPHA, /* U+00f992: CJK COMPATIBILITY IDEOGRAPH-F992*/
+ RTUNI_ALPHA, /* U+00f993: CJK COMPATIBILITY IDEOGRAPH-F993*/
+ RTUNI_ALPHA, /* U+00f994: CJK COMPATIBILITY IDEOGRAPH-F994*/
+ RTUNI_ALPHA, /* U+00f995: CJK COMPATIBILITY IDEOGRAPH-F995*/
+ RTUNI_ALPHA, /* U+00f996: CJK COMPATIBILITY IDEOGRAPH-F996*/
+ RTUNI_ALPHA, /* U+00f997: CJK COMPATIBILITY IDEOGRAPH-F997*/
+ RTUNI_ALPHA, /* U+00f998: CJK COMPATIBILITY IDEOGRAPH-F998*/
+ RTUNI_ALPHA, /* U+00f999: CJK COMPATIBILITY IDEOGRAPH-F999*/
+ RTUNI_ALPHA, /* U+00f99a: CJK COMPATIBILITY IDEOGRAPH-F99A*/
+ RTUNI_ALPHA, /* U+00f99b: CJK COMPATIBILITY IDEOGRAPH-F99B*/
+ RTUNI_ALPHA, /* U+00f99c: CJK COMPATIBILITY IDEOGRAPH-F99C*/
+ RTUNI_ALPHA, /* U+00f99d: CJK COMPATIBILITY IDEOGRAPH-F99D*/
+ RTUNI_ALPHA, /* U+00f99e: CJK COMPATIBILITY IDEOGRAPH-F99E*/
+ RTUNI_ALPHA, /* U+00f99f: CJK COMPATIBILITY IDEOGRAPH-F99F*/
+ RTUNI_ALPHA, /* U+00f9a0: CJK COMPATIBILITY IDEOGRAPH-F9A0*/
+ RTUNI_ALPHA, /* U+00f9a1: CJK COMPATIBILITY IDEOGRAPH-F9A1*/
+ RTUNI_ALPHA, /* U+00f9a2: CJK COMPATIBILITY IDEOGRAPH-F9A2*/
+ RTUNI_ALPHA, /* U+00f9a3: CJK COMPATIBILITY IDEOGRAPH-F9A3*/
+ RTUNI_ALPHA, /* U+00f9a4: CJK COMPATIBILITY IDEOGRAPH-F9A4*/
+ RTUNI_ALPHA, /* U+00f9a5: CJK COMPATIBILITY IDEOGRAPH-F9A5*/
+ RTUNI_ALPHA, /* U+00f9a6: CJK COMPATIBILITY IDEOGRAPH-F9A6*/
+ RTUNI_ALPHA, /* U+00f9a7: CJK COMPATIBILITY IDEOGRAPH-F9A7*/
+ RTUNI_ALPHA, /* U+00f9a8: CJK COMPATIBILITY IDEOGRAPH-F9A8*/
+ RTUNI_ALPHA, /* U+00f9a9: CJK COMPATIBILITY IDEOGRAPH-F9A9*/
+ RTUNI_ALPHA, /* U+00f9aa: CJK COMPATIBILITY IDEOGRAPH-F9AA*/
+ RTUNI_ALPHA, /* U+00f9ab: CJK COMPATIBILITY IDEOGRAPH-F9AB*/
+ RTUNI_ALPHA, /* U+00f9ac: CJK COMPATIBILITY IDEOGRAPH-F9AC*/
+ RTUNI_ALPHA, /* U+00f9ad: CJK COMPATIBILITY IDEOGRAPH-F9AD*/
+ RTUNI_ALPHA, /* U+00f9ae: CJK COMPATIBILITY IDEOGRAPH-F9AE*/
+ RTUNI_ALPHA, /* U+00f9af: CJK COMPATIBILITY IDEOGRAPH-F9AF*/
+ RTUNI_ALPHA, /* U+00f9b0: CJK COMPATIBILITY IDEOGRAPH-F9B0*/
+ RTUNI_ALPHA, /* U+00f9b1: CJK COMPATIBILITY IDEOGRAPH-F9B1*/
+ RTUNI_ALPHA, /* U+00f9b2: CJK COMPATIBILITY IDEOGRAPH-F9B2*/
+ RTUNI_ALPHA, /* U+00f9b3: CJK COMPATIBILITY IDEOGRAPH-F9B3*/
+ RTUNI_ALPHA, /* U+00f9b4: CJK COMPATIBILITY IDEOGRAPH-F9B4*/
+ RTUNI_ALPHA, /* U+00f9b5: CJK COMPATIBILITY IDEOGRAPH-F9B5*/
+ RTUNI_ALPHA, /* U+00f9b6: CJK COMPATIBILITY IDEOGRAPH-F9B6*/
+ RTUNI_ALPHA, /* U+00f9b7: CJK COMPATIBILITY IDEOGRAPH-F9B7*/
+ RTUNI_ALPHA, /* U+00f9b8: CJK COMPATIBILITY IDEOGRAPH-F9B8*/
+ RTUNI_ALPHA, /* U+00f9b9: CJK COMPATIBILITY IDEOGRAPH-F9B9*/
+ RTUNI_ALPHA, /* U+00f9ba: CJK COMPATIBILITY IDEOGRAPH-F9BA*/
+ RTUNI_ALPHA, /* U+00f9bb: CJK COMPATIBILITY IDEOGRAPH-F9BB*/
+ RTUNI_ALPHA, /* U+00f9bc: CJK COMPATIBILITY IDEOGRAPH-F9BC*/
+ RTUNI_ALPHA, /* U+00f9bd: CJK COMPATIBILITY IDEOGRAPH-F9BD*/
+ RTUNI_ALPHA, /* U+00f9be: CJK COMPATIBILITY IDEOGRAPH-F9BE*/
+ RTUNI_ALPHA, /* U+00f9bf: CJK COMPATIBILITY IDEOGRAPH-F9BF*/
+ RTUNI_ALPHA, /* U+00f9c0: CJK COMPATIBILITY IDEOGRAPH-F9C0*/
+ RTUNI_ALPHA, /* U+00f9c1: CJK COMPATIBILITY IDEOGRAPH-F9C1*/
+ RTUNI_ALPHA, /* U+00f9c2: CJK COMPATIBILITY IDEOGRAPH-F9C2*/
+ RTUNI_ALPHA, /* U+00f9c3: CJK COMPATIBILITY IDEOGRAPH-F9C3*/
+ RTUNI_ALPHA, /* U+00f9c4: CJK COMPATIBILITY IDEOGRAPH-F9C4*/
+ RTUNI_ALPHA, /* U+00f9c5: CJK COMPATIBILITY IDEOGRAPH-F9C5*/
+ RTUNI_ALPHA, /* U+00f9c6: CJK COMPATIBILITY IDEOGRAPH-F9C6*/
+ RTUNI_ALPHA, /* U+00f9c7: CJK COMPATIBILITY IDEOGRAPH-F9C7*/
+ RTUNI_ALPHA, /* U+00f9c8: CJK COMPATIBILITY IDEOGRAPH-F9C8*/
+ RTUNI_ALPHA, /* U+00f9c9: CJK COMPATIBILITY IDEOGRAPH-F9C9*/
+ RTUNI_ALPHA, /* U+00f9ca: CJK COMPATIBILITY IDEOGRAPH-F9CA*/
+ RTUNI_ALPHA, /* U+00f9cb: CJK COMPATIBILITY IDEOGRAPH-F9CB*/
+ RTUNI_ALPHA, /* U+00f9cc: CJK COMPATIBILITY IDEOGRAPH-F9CC*/
+ RTUNI_ALPHA, /* U+00f9cd: CJK COMPATIBILITY IDEOGRAPH-F9CD*/
+ RTUNI_ALPHA, /* U+00f9ce: CJK COMPATIBILITY IDEOGRAPH-F9CE*/
+ RTUNI_ALPHA, /* U+00f9cf: CJK COMPATIBILITY IDEOGRAPH-F9CF*/
+ RTUNI_ALPHA, /* U+00f9d0: CJK COMPATIBILITY IDEOGRAPH-F9D0*/
+ RTUNI_ALPHA, /* U+00f9d1: CJK COMPATIBILITY IDEOGRAPH-F9D1*/
+ RTUNI_ALPHA, /* U+00f9d2: CJK COMPATIBILITY IDEOGRAPH-F9D2*/
+ RTUNI_ALPHA, /* U+00f9d3: CJK COMPATIBILITY IDEOGRAPH-F9D3*/
+ RTUNI_ALPHA, /* U+00f9d4: CJK COMPATIBILITY IDEOGRAPH-F9D4*/
+ RTUNI_ALPHA, /* U+00f9d5: CJK COMPATIBILITY IDEOGRAPH-F9D5*/
+ RTUNI_ALPHA, /* U+00f9d6: CJK COMPATIBILITY IDEOGRAPH-F9D6*/
+ RTUNI_ALPHA, /* U+00f9d7: CJK COMPATIBILITY IDEOGRAPH-F9D7*/
+ RTUNI_ALPHA, /* U+00f9d8: CJK COMPATIBILITY IDEOGRAPH-F9D8*/
+ RTUNI_ALPHA, /* U+00f9d9: CJK COMPATIBILITY IDEOGRAPH-F9D9*/
+ RTUNI_ALPHA, /* U+00f9da: CJK COMPATIBILITY IDEOGRAPH-F9DA*/
+ RTUNI_ALPHA, /* U+00f9db: CJK COMPATIBILITY IDEOGRAPH-F9DB*/
+ RTUNI_ALPHA, /* U+00f9dc: CJK COMPATIBILITY IDEOGRAPH-F9DC*/
+ RTUNI_ALPHA, /* U+00f9dd: CJK COMPATIBILITY IDEOGRAPH-F9DD*/
+ RTUNI_ALPHA, /* U+00f9de: CJK COMPATIBILITY IDEOGRAPH-F9DE*/
+ RTUNI_ALPHA, /* U+00f9df: CJK COMPATIBILITY IDEOGRAPH-F9DF*/
+ RTUNI_ALPHA, /* U+00f9e0: CJK COMPATIBILITY IDEOGRAPH-F9E0*/
+ RTUNI_ALPHA, /* U+00f9e1: CJK COMPATIBILITY IDEOGRAPH-F9E1*/
+ RTUNI_ALPHA, /* U+00f9e2: CJK COMPATIBILITY IDEOGRAPH-F9E2*/
+ RTUNI_ALPHA, /* U+00f9e3: CJK COMPATIBILITY IDEOGRAPH-F9E3*/
+ RTUNI_ALPHA, /* U+00f9e4: CJK COMPATIBILITY IDEOGRAPH-F9E4*/
+ RTUNI_ALPHA, /* U+00f9e5: CJK COMPATIBILITY IDEOGRAPH-F9E5*/
+ RTUNI_ALPHA, /* U+00f9e6: CJK COMPATIBILITY IDEOGRAPH-F9E6*/
+ RTUNI_ALPHA, /* U+00f9e7: CJK COMPATIBILITY IDEOGRAPH-F9E7*/
+ RTUNI_ALPHA, /* U+00f9e8: CJK COMPATIBILITY IDEOGRAPH-F9E8*/
+ RTUNI_ALPHA, /* U+00f9e9: CJK COMPATIBILITY IDEOGRAPH-F9E9*/
+ RTUNI_ALPHA, /* U+00f9ea: CJK COMPATIBILITY IDEOGRAPH-F9EA*/
+ RTUNI_ALPHA, /* U+00f9eb: CJK COMPATIBILITY IDEOGRAPH-F9EB*/
+ RTUNI_ALPHA, /* U+00f9ec: CJK COMPATIBILITY IDEOGRAPH-F9EC*/
+ RTUNI_ALPHA, /* U+00f9ed: CJK COMPATIBILITY IDEOGRAPH-F9ED*/
+ RTUNI_ALPHA, /* U+00f9ee: CJK COMPATIBILITY IDEOGRAPH-F9EE*/
+ RTUNI_ALPHA, /* U+00f9ef: CJK COMPATIBILITY IDEOGRAPH-F9EF*/
+ RTUNI_ALPHA, /* U+00f9f0: CJK COMPATIBILITY IDEOGRAPH-F9F0*/
+ RTUNI_ALPHA, /* U+00f9f1: CJK COMPATIBILITY IDEOGRAPH-F9F1*/
+ RTUNI_ALPHA, /* U+00f9f2: CJK COMPATIBILITY IDEOGRAPH-F9F2*/
+ RTUNI_ALPHA, /* U+00f9f3: CJK COMPATIBILITY IDEOGRAPH-F9F3*/
+ RTUNI_ALPHA, /* U+00f9f4: CJK COMPATIBILITY IDEOGRAPH-F9F4*/
+ RTUNI_ALPHA, /* U+00f9f5: CJK COMPATIBILITY IDEOGRAPH-F9F5*/
+ RTUNI_ALPHA, /* U+00f9f6: CJK COMPATIBILITY IDEOGRAPH-F9F6*/
+ RTUNI_ALPHA, /* U+00f9f7: CJK COMPATIBILITY IDEOGRAPH-F9F7*/
+ RTUNI_ALPHA, /* U+00f9f8: CJK COMPATIBILITY IDEOGRAPH-F9F8*/
+ RTUNI_ALPHA, /* U+00f9f9: CJK COMPATIBILITY IDEOGRAPH-F9F9*/
+ RTUNI_ALPHA, /* U+00f9fa: CJK COMPATIBILITY IDEOGRAPH-F9FA*/
+ RTUNI_ALPHA, /* U+00f9fb: CJK COMPATIBILITY IDEOGRAPH-F9FB*/
+ RTUNI_ALPHA, /* U+00f9fc: CJK COMPATIBILITY IDEOGRAPH-F9FC*/
+ RTUNI_ALPHA, /* U+00f9fd: CJK COMPATIBILITY IDEOGRAPH-F9FD*/
+ RTUNI_ALPHA, /* U+00f9fe: CJK COMPATIBILITY IDEOGRAPH-F9FE*/
+ RTUNI_ALPHA, /* U+00f9ff: CJK COMPATIBILITY IDEOGRAPH-F9FF*/
+ RTUNI_ALPHA, /* U+00fa00: CJK COMPATIBILITY IDEOGRAPH-FA00*/
+ RTUNI_ALPHA, /* U+00fa01: CJK COMPATIBILITY IDEOGRAPH-FA01*/
+ RTUNI_ALPHA, /* U+00fa02: CJK COMPATIBILITY IDEOGRAPH-FA02*/
+ RTUNI_ALPHA, /* U+00fa03: CJK COMPATIBILITY IDEOGRAPH-FA03*/
+ RTUNI_ALPHA, /* U+00fa04: CJK COMPATIBILITY IDEOGRAPH-FA04*/
+ RTUNI_ALPHA, /* U+00fa05: CJK COMPATIBILITY IDEOGRAPH-FA05*/
+ RTUNI_ALPHA, /* U+00fa06: CJK COMPATIBILITY IDEOGRAPH-FA06*/
+ RTUNI_ALPHA, /* U+00fa07: CJK COMPATIBILITY IDEOGRAPH-FA07*/
+ RTUNI_ALPHA, /* U+00fa08: CJK COMPATIBILITY IDEOGRAPH-FA08*/
+ RTUNI_ALPHA, /* U+00fa09: CJK COMPATIBILITY IDEOGRAPH-FA09*/
+ RTUNI_ALPHA, /* U+00fa0a: CJK COMPATIBILITY IDEOGRAPH-FA0A*/
+ RTUNI_ALPHA, /* U+00fa0b: CJK COMPATIBILITY IDEOGRAPH-FA0B*/
+ RTUNI_ALPHA, /* U+00fa0c: CJK COMPATIBILITY IDEOGRAPH-FA0C*/
+ RTUNI_ALPHA, /* U+00fa0d: CJK COMPATIBILITY IDEOGRAPH-FA0D*/
+ RTUNI_ALPHA, /* U+00fa0e: CJK COMPATIBILITY IDEOGRAPH-FA0E*/
+ RTUNI_ALPHA, /* U+00fa0f: CJK COMPATIBILITY IDEOGRAPH-FA0F*/
+ RTUNI_ALPHA, /* U+00fa10: CJK COMPATIBILITY IDEOGRAPH-FA10*/
+ RTUNI_ALPHA, /* U+00fa11: CJK COMPATIBILITY IDEOGRAPH-FA11*/
+ RTUNI_ALPHA, /* U+00fa12: CJK COMPATIBILITY IDEOGRAPH-FA12*/
+ RTUNI_ALPHA, /* U+00fa13: CJK COMPATIBILITY IDEOGRAPH-FA13*/
+ RTUNI_ALPHA, /* U+00fa14: CJK COMPATIBILITY IDEOGRAPH-FA14*/
+ RTUNI_ALPHA, /* U+00fa15: CJK COMPATIBILITY IDEOGRAPH-FA15*/
+ RTUNI_ALPHA, /* U+00fa16: CJK COMPATIBILITY IDEOGRAPH-FA16*/
+ RTUNI_ALPHA, /* U+00fa17: CJK COMPATIBILITY IDEOGRAPH-FA17*/
+ RTUNI_ALPHA, /* U+00fa18: CJK COMPATIBILITY IDEOGRAPH-FA18*/
+ RTUNI_ALPHA, /* U+00fa19: CJK COMPATIBILITY IDEOGRAPH-FA19*/
+ RTUNI_ALPHA, /* U+00fa1a: CJK COMPATIBILITY IDEOGRAPH-FA1A*/
+ RTUNI_ALPHA, /* U+00fa1b: CJK COMPATIBILITY IDEOGRAPH-FA1B*/
+ RTUNI_ALPHA, /* U+00fa1c: CJK COMPATIBILITY IDEOGRAPH-FA1C*/
+ RTUNI_ALPHA, /* U+00fa1d: CJK COMPATIBILITY IDEOGRAPH-FA1D*/
+ RTUNI_ALPHA, /* U+00fa1e: CJK COMPATIBILITY IDEOGRAPH-FA1E*/
+ RTUNI_ALPHA, /* U+00fa1f: CJK COMPATIBILITY IDEOGRAPH-FA1F*/
+ RTUNI_ALPHA, /* U+00fa20: CJK COMPATIBILITY IDEOGRAPH-FA20*/
+ RTUNI_ALPHA, /* U+00fa21: CJK COMPATIBILITY IDEOGRAPH-FA21*/
+ RTUNI_ALPHA, /* U+00fa22: CJK COMPATIBILITY IDEOGRAPH-FA22*/
+ RTUNI_ALPHA, /* U+00fa23: CJK COMPATIBILITY IDEOGRAPH-FA23*/
+ RTUNI_ALPHA, /* U+00fa24: CJK COMPATIBILITY IDEOGRAPH-FA24*/
+ RTUNI_ALPHA, /* U+00fa25: CJK COMPATIBILITY IDEOGRAPH-FA25*/
+ RTUNI_ALPHA, /* U+00fa26: CJK COMPATIBILITY IDEOGRAPH-FA26*/
+ RTUNI_ALPHA, /* U+00fa27: CJK COMPATIBILITY IDEOGRAPH-FA27*/
+ RTUNI_ALPHA, /* U+00fa28: CJK COMPATIBILITY IDEOGRAPH-FA28*/
+ RTUNI_ALPHA, /* U+00fa29: CJK COMPATIBILITY IDEOGRAPH-FA29*/
+ RTUNI_ALPHA, /* U+00fa2a: CJK COMPATIBILITY IDEOGRAPH-FA2A*/
+ RTUNI_ALPHA, /* U+00fa2b: CJK COMPATIBILITY IDEOGRAPH-FA2B*/
+ RTUNI_ALPHA, /* U+00fa2c: CJK COMPATIBILITY IDEOGRAPH-FA2C*/
+ RTUNI_ALPHA, /* U+00fa2d: CJK COMPATIBILITY IDEOGRAPH-FA2D*/
+ RTUNI_ALPHA, /* U+00fa2e: CJK COMPATIBILITY IDEOGRAPH-FA2E*/
+ RTUNI_ALPHA, /* U+00fa2f: CJK COMPATIBILITY IDEOGRAPH-FA2F*/
+ RTUNI_ALPHA, /* U+00fa30: CJK COMPATIBILITY IDEOGRAPH-FA30*/
+ RTUNI_ALPHA, /* U+00fa31: CJK COMPATIBILITY IDEOGRAPH-FA31*/
+ RTUNI_ALPHA, /* U+00fa32: CJK COMPATIBILITY IDEOGRAPH-FA32*/
+ RTUNI_ALPHA, /* U+00fa33: CJK COMPATIBILITY IDEOGRAPH-FA33*/
+ RTUNI_ALPHA, /* U+00fa34: CJK COMPATIBILITY IDEOGRAPH-FA34*/
+ RTUNI_ALPHA, /* U+00fa35: CJK COMPATIBILITY IDEOGRAPH-FA35*/
+ RTUNI_ALPHA, /* U+00fa36: CJK COMPATIBILITY IDEOGRAPH-FA36*/
+ RTUNI_ALPHA, /* U+00fa37: CJK COMPATIBILITY IDEOGRAPH-FA37*/
+ RTUNI_ALPHA, /* U+00fa38: CJK COMPATIBILITY IDEOGRAPH-FA38*/
+ RTUNI_ALPHA, /* U+00fa39: CJK COMPATIBILITY IDEOGRAPH-FA39*/
+ RTUNI_ALPHA, /* U+00fa3a: CJK COMPATIBILITY IDEOGRAPH-FA3A*/
+ RTUNI_ALPHA, /* U+00fa3b: CJK COMPATIBILITY IDEOGRAPH-FA3B*/
+ RTUNI_ALPHA, /* U+00fa3c: CJK COMPATIBILITY IDEOGRAPH-FA3C*/
+ RTUNI_ALPHA, /* U+00fa3d: CJK COMPATIBILITY IDEOGRAPH-FA3D*/
+ RTUNI_ALPHA, /* U+00fa3e: CJK COMPATIBILITY IDEOGRAPH-FA3E*/
+ RTUNI_ALPHA, /* U+00fa3f: CJK COMPATIBILITY IDEOGRAPH-FA3F*/
+ RTUNI_ALPHA, /* U+00fa40: CJK COMPATIBILITY IDEOGRAPH-FA40*/
+ RTUNI_ALPHA, /* U+00fa41: CJK COMPATIBILITY IDEOGRAPH-FA41*/
+ RTUNI_ALPHA, /* U+00fa42: CJK COMPATIBILITY IDEOGRAPH-FA42*/
+ RTUNI_ALPHA, /* U+00fa43: CJK COMPATIBILITY IDEOGRAPH-FA43*/
+ RTUNI_ALPHA, /* U+00fa44: CJK COMPATIBILITY IDEOGRAPH-FA44*/
+ RTUNI_ALPHA, /* U+00fa45: CJK COMPATIBILITY IDEOGRAPH-FA45*/
+ RTUNI_ALPHA, /* U+00fa46: CJK COMPATIBILITY IDEOGRAPH-FA46*/
+ RTUNI_ALPHA, /* U+00fa47: CJK COMPATIBILITY IDEOGRAPH-FA47*/
+ RTUNI_ALPHA, /* U+00fa48: CJK COMPATIBILITY IDEOGRAPH-FA48*/
+ RTUNI_ALPHA, /* U+00fa49: CJK COMPATIBILITY IDEOGRAPH-FA49*/
+ RTUNI_ALPHA, /* U+00fa4a: CJK COMPATIBILITY IDEOGRAPH-FA4A*/
+ RTUNI_ALPHA, /* U+00fa4b: CJK COMPATIBILITY IDEOGRAPH-FA4B*/
+ RTUNI_ALPHA, /* U+00fa4c: CJK COMPATIBILITY IDEOGRAPH-FA4C*/
+ RTUNI_ALPHA, /* U+00fa4d: CJK COMPATIBILITY IDEOGRAPH-FA4D*/
+ RTUNI_ALPHA, /* U+00fa4e: CJK COMPATIBILITY IDEOGRAPH-FA4E*/
+ RTUNI_ALPHA, /* U+00fa4f: CJK COMPATIBILITY IDEOGRAPH-FA4F*/
+ RTUNI_ALPHA, /* U+00fa50: CJK COMPATIBILITY IDEOGRAPH-FA50*/
+ RTUNI_ALPHA, /* U+00fa51: CJK COMPATIBILITY IDEOGRAPH-FA51*/
+ RTUNI_ALPHA, /* U+00fa52: CJK COMPATIBILITY IDEOGRAPH-FA52*/
+ RTUNI_ALPHA, /* U+00fa53: CJK COMPATIBILITY IDEOGRAPH-FA53*/
+ RTUNI_ALPHA, /* U+00fa54: CJK COMPATIBILITY IDEOGRAPH-FA54*/
+ RTUNI_ALPHA, /* U+00fa55: CJK COMPATIBILITY IDEOGRAPH-FA55*/
+ RTUNI_ALPHA, /* U+00fa56: CJK COMPATIBILITY IDEOGRAPH-FA56*/
+ RTUNI_ALPHA, /* U+00fa57: CJK COMPATIBILITY IDEOGRAPH-FA57*/
+ RTUNI_ALPHA, /* U+00fa58: CJK COMPATIBILITY IDEOGRAPH-FA58*/
+ RTUNI_ALPHA, /* U+00fa59: CJK COMPATIBILITY IDEOGRAPH-FA59*/
+ RTUNI_ALPHA, /* U+00fa5a: CJK COMPATIBILITY IDEOGRAPH-FA5A*/
+ RTUNI_ALPHA, /* U+00fa5b: CJK COMPATIBILITY IDEOGRAPH-FA5B*/
+ RTUNI_ALPHA, /* U+00fa5c: CJK COMPATIBILITY IDEOGRAPH-FA5C*/
+ RTUNI_ALPHA, /* U+00fa5d: CJK COMPATIBILITY IDEOGRAPH-FA5D*/
+ RTUNI_ALPHA, /* U+00fa5e: CJK COMPATIBILITY IDEOGRAPH-FA5E*/
+ RTUNI_ALPHA, /* U+00fa5f: CJK COMPATIBILITY IDEOGRAPH-FA5F*/
+ RTUNI_ALPHA, /* U+00fa60: CJK COMPATIBILITY IDEOGRAPH-FA60*/
+ RTUNI_ALPHA, /* U+00fa61: CJK COMPATIBILITY IDEOGRAPH-FA61*/
+ RTUNI_ALPHA, /* U+00fa62: CJK COMPATIBILITY IDEOGRAPH-FA62*/
+ RTUNI_ALPHA, /* U+00fa63: CJK COMPATIBILITY IDEOGRAPH-FA63*/
+ RTUNI_ALPHA, /* U+00fa64: CJK COMPATIBILITY IDEOGRAPH-FA64*/
+ RTUNI_ALPHA, /* U+00fa65: CJK COMPATIBILITY IDEOGRAPH-FA65*/
+ RTUNI_ALPHA, /* U+00fa66: CJK COMPATIBILITY IDEOGRAPH-FA66*/
+ RTUNI_ALPHA, /* U+00fa67: CJK COMPATIBILITY IDEOGRAPH-FA67*/
+ RTUNI_ALPHA, /* U+00fa68: CJK COMPATIBILITY IDEOGRAPH-FA68*/
+ RTUNI_ALPHA, /* U+00fa69: CJK COMPATIBILITY IDEOGRAPH-FA69*/
+ RTUNI_ALPHA, /* U+00fa6a: CJK COMPATIBILITY IDEOGRAPH-FA6A*/
+ RTUNI_ALPHA, /* U+00fa6b: CJK COMPATIBILITY IDEOGRAPH-FA6B*/
+ RTUNI_ALPHA, /* U+00fa6c: CJK COMPATIBILITY IDEOGRAPH-FA6C*/
+ RTUNI_ALPHA, /* U+00fa6d: CJK COMPATIBILITY IDEOGRAPH-FA6D*/
+ 0, /* U+00fa6e: */
+ 0, /* U+00fa6f: */
+ RTUNI_ALPHA, /* U+00fa70: CJK COMPATIBILITY IDEOGRAPH-FA70*/
+ RTUNI_ALPHA, /* U+00fa71: CJK COMPATIBILITY IDEOGRAPH-FA71*/
+ RTUNI_ALPHA, /* U+00fa72: CJK COMPATIBILITY IDEOGRAPH-FA72*/
+ RTUNI_ALPHA, /* U+00fa73: CJK COMPATIBILITY IDEOGRAPH-FA73*/
+ RTUNI_ALPHA, /* U+00fa74: CJK COMPATIBILITY IDEOGRAPH-FA74*/
+ RTUNI_ALPHA, /* U+00fa75: CJK COMPATIBILITY IDEOGRAPH-FA75*/
+ RTUNI_ALPHA, /* U+00fa76: CJK COMPATIBILITY IDEOGRAPH-FA76*/
+ RTUNI_ALPHA, /* U+00fa77: CJK COMPATIBILITY IDEOGRAPH-FA77*/
+ RTUNI_ALPHA, /* U+00fa78: CJK COMPATIBILITY IDEOGRAPH-FA78*/
+ RTUNI_ALPHA, /* U+00fa79: CJK COMPATIBILITY IDEOGRAPH-FA79*/
+ RTUNI_ALPHA, /* U+00fa7a: CJK COMPATIBILITY IDEOGRAPH-FA7A*/
+ RTUNI_ALPHA, /* U+00fa7b: CJK COMPATIBILITY IDEOGRAPH-FA7B*/
+ RTUNI_ALPHA, /* U+00fa7c: CJK COMPATIBILITY IDEOGRAPH-FA7C*/
+ RTUNI_ALPHA, /* U+00fa7d: CJK COMPATIBILITY IDEOGRAPH-FA7D*/
+ RTUNI_ALPHA, /* U+00fa7e: CJK COMPATIBILITY IDEOGRAPH-FA7E*/
+ RTUNI_ALPHA, /* U+00fa7f: CJK COMPATIBILITY IDEOGRAPH-FA7F*/
+ RTUNI_ALPHA, /* U+00fa80: CJK COMPATIBILITY IDEOGRAPH-FA80*/
+ RTUNI_ALPHA, /* U+00fa81: CJK COMPATIBILITY IDEOGRAPH-FA81*/
+ RTUNI_ALPHA, /* U+00fa82: CJK COMPATIBILITY IDEOGRAPH-FA82*/
+ RTUNI_ALPHA, /* U+00fa83: CJK COMPATIBILITY IDEOGRAPH-FA83*/
+ RTUNI_ALPHA, /* U+00fa84: CJK COMPATIBILITY IDEOGRAPH-FA84*/
+ RTUNI_ALPHA, /* U+00fa85: CJK COMPATIBILITY IDEOGRAPH-FA85*/
+ RTUNI_ALPHA, /* U+00fa86: CJK COMPATIBILITY IDEOGRAPH-FA86*/
+ RTUNI_ALPHA, /* U+00fa87: CJK COMPATIBILITY IDEOGRAPH-FA87*/
+ RTUNI_ALPHA, /* U+00fa88: CJK COMPATIBILITY IDEOGRAPH-FA88*/
+ RTUNI_ALPHA, /* U+00fa89: CJK COMPATIBILITY IDEOGRAPH-FA89*/
+ RTUNI_ALPHA, /* U+00fa8a: CJK COMPATIBILITY IDEOGRAPH-FA8A*/
+ RTUNI_ALPHA, /* U+00fa8b: CJK COMPATIBILITY IDEOGRAPH-FA8B*/
+ RTUNI_ALPHA, /* U+00fa8c: CJK COMPATIBILITY IDEOGRAPH-FA8C*/
+ RTUNI_ALPHA, /* U+00fa8d: CJK COMPATIBILITY IDEOGRAPH-FA8D*/
+ RTUNI_ALPHA, /* U+00fa8e: CJK COMPATIBILITY IDEOGRAPH-FA8E*/
+ RTUNI_ALPHA, /* U+00fa8f: CJK COMPATIBILITY IDEOGRAPH-FA8F*/
+ RTUNI_ALPHA, /* U+00fa90: CJK COMPATIBILITY IDEOGRAPH-FA90*/
+ RTUNI_ALPHA, /* U+00fa91: CJK COMPATIBILITY IDEOGRAPH-FA91*/
+ RTUNI_ALPHA, /* U+00fa92: CJK COMPATIBILITY IDEOGRAPH-FA92*/
+ RTUNI_ALPHA, /* U+00fa93: CJK COMPATIBILITY IDEOGRAPH-FA93*/
+ RTUNI_ALPHA, /* U+00fa94: CJK COMPATIBILITY IDEOGRAPH-FA94*/
+ RTUNI_ALPHA, /* U+00fa95: CJK COMPATIBILITY IDEOGRAPH-FA95*/
+ RTUNI_ALPHA, /* U+00fa96: CJK COMPATIBILITY IDEOGRAPH-FA96*/
+ RTUNI_ALPHA, /* U+00fa97: CJK COMPATIBILITY IDEOGRAPH-FA97*/
+ RTUNI_ALPHA, /* U+00fa98: CJK COMPATIBILITY IDEOGRAPH-FA98*/
+ RTUNI_ALPHA, /* U+00fa99: CJK COMPATIBILITY IDEOGRAPH-FA99*/
+ RTUNI_ALPHA, /* U+00fa9a: CJK COMPATIBILITY IDEOGRAPH-FA9A*/
+ RTUNI_ALPHA, /* U+00fa9b: CJK COMPATIBILITY IDEOGRAPH-FA9B*/
+ RTUNI_ALPHA, /* U+00fa9c: CJK COMPATIBILITY IDEOGRAPH-FA9C*/
+ RTUNI_ALPHA, /* U+00fa9d: CJK COMPATIBILITY IDEOGRAPH-FA9D*/
+ RTUNI_ALPHA, /* U+00fa9e: CJK COMPATIBILITY IDEOGRAPH-FA9E*/
+ RTUNI_ALPHA, /* U+00fa9f: CJK COMPATIBILITY IDEOGRAPH-FA9F*/
+ RTUNI_ALPHA, /* U+00faa0: CJK COMPATIBILITY IDEOGRAPH-FAA0*/
+ RTUNI_ALPHA, /* U+00faa1: CJK COMPATIBILITY IDEOGRAPH-FAA1*/
+ RTUNI_ALPHA, /* U+00faa2: CJK COMPATIBILITY IDEOGRAPH-FAA2*/
+ RTUNI_ALPHA, /* U+00faa3: CJK COMPATIBILITY IDEOGRAPH-FAA3*/
+ RTUNI_ALPHA, /* U+00faa4: CJK COMPATIBILITY IDEOGRAPH-FAA4*/
+ RTUNI_ALPHA, /* U+00faa5: CJK COMPATIBILITY IDEOGRAPH-FAA5*/
+ RTUNI_ALPHA, /* U+00faa6: CJK COMPATIBILITY IDEOGRAPH-FAA6*/
+ RTUNI_ALPHA, /* U+00faa7: CJK COMPATIBILITY IDEOGRAPH-FAA7*/
+ RTUNI_ALPHA, /* U+00faa8: CJK COMPATIBILITY IDEOGRAPH-FAA8*/
+ RTUNI_ALPHA, /* U+00faa9: CJK COMPATIBILITY IDEOGRAPH-FAA9*/
+ RTUNI_ALPHA, /* U+00faaa: CJK COMPATIBILITY IDEOGRAPH-FAAA*/
+ RTUNI_ALPHA, /* U+00faab: CJK COMPATIBILITY IDEOGRAPH-FAAB*/
+ RTUNI_ALPHA, /* U+00faac: CJK COMPATIBILITY IDEOGRAPH-FAAC*/
+ RTUNI_ALPHA, /* U+00faad: CJK COMPATIBILITY IDEOGRAPH-FAAD*/
+ RTUNI_ALPHA, /* U+00faae: CJK COMPATIBILITY IDEOGRAPH-FAAE*/
+ RTUNI_ALPHA, /* U+00faaf: CJK COMPATIBILITY IDEOGRAPH-FAAF*/
+ RTUNI_ALPHA, /* U+00fab0: CJK COMPATIBILITY IDEOGRAPH-FAB0*/
+ RTUNI_ALPHA, /* U+00fab1: CJK COMPATIBILITY IDEOGRAPH-FAB1*/
+ RTUNI_ALPHA, /* U+00fab2: CJK COMPATIBILITY IDEOGRAPH-FAB2*/
+ RTUNI_ALPHA, /* U+00fab3: CJK COMPATIBILITY IDEOGRAPH-FAB3*/
+ RTUNI_ALPHA, /* U+00fab4: CJK COMPATIBILITY IDEOGRAPH-FAB4*/
+ RTUNI_ALPHA, /* U+00fab5: CJK COMPATIBILITY IDEOGRAPH-FAB5*/
+ RTUNI_ALPHA, /* U+00fab6: CJK COMPATIBILITY IDEOGRAPH-FAB6*/
+ RTUNI_ALPHA, /* U+00fab7: CJK COMPATIBILITY IDEOGRAPH-FAB7*/
+ RTUNI_ALPHA, /* U+00fab8: CJK COMPATIBILITY IDEOGRAPH-FAB8*/
+ RTUNI_ALPHA, /* U+00fab9: CJK COMPATIBILITY IDEOGRAPH-FAB9*/
+ RTUNI_ALPHA, /* U+00faba: CJK COMPATIBILITY IDEOGRAPH-FABA*/
+ RTUNI_ALPHA, /* U+00fabb: CJK COMPATIBILITY IDEOGRAPH-FABB*/
+ RTUNI_ALPHA, /* U+00fabc: CJK COMPATIBILITY IDEOGRAPH-FABC*/
+ RTUNI_ALPHA, /* U+00fabd: CJK COMPATIBILITY IDEOGRAPH-FABD*/
+ RTUNI_ALPHA, /* U+00fabe: CJK COMPATIBILITY IDEOGRAPH-FABE*/
+ RTUNI_ALPHA, /* U+00fabf: CJK COMPATIBILITY IDEOGRAPH-FABF*/
+ RTUNI_ALPHA, /* U+00fac0: CJK COMPATIBILITY IDEOGRAPH-FAC0*/
+ RTUNI_ALPHA, /* U+00fac1: CJK COMPATIBILITY IDEOGRAPH-FAC1*/
+ RTUNI_ALPHA, /* U+00fac2: CJK COMPATIBILITY IDEOGRAPH-FAC2*/
+ RTUNI_ALPHA, /* U+00fac3: CJK COMPATIBILITY IDEOGRAPH-FAC3*/
+ RTUNI_ALPHA, /* U+00fac4: CJK COMPATIBILITY IDEOGRAPH-FAC4*/
+ RTUNI_ALPHA, /* U+00fac5: CJK COMPATIBILITY IDEOGRAPH-FAC5*/
+ RTUNI_ALPHA, /* U+00fac6: CJK COMPATIBILITY IDEOGRAPH-FAC6*/
+ RTUNI_ALPHA, /* U+00fac7: CJK COMPATIBILITY IDEOGRAPH-FAC7*/
+ RTUNI_ALPHA, /* U+00fac8: CJK COMPATIBILITY IDEOGRAPH-FAC8*/
+ RTUNI_ALPHA, /* U+00fac9: CJK COMPATIBILITY IDEOGRAPH-FAC9*/
+ RTUNI_ALPHA, /* U+00faca: CJK COMPATIBILITY IDEOGRAPH-FACA*/
+ RTUNI_ALPHA, /* U+00facb: CJK COMPATIBILITY IDEOGRAPH-FACB*/
+ RTUNI_ALPHA, /* U+00facc: CJK COMPATIBILITY IDEOGRAPH-FACC*/
+ RTUNI_ALPHA, /* U+00facd: CJK COMPATIBILITY IDEOGRAPH-FACD*/
+ RTUNI_ALPHA, /* U+00face: CJK COMPATIBILITY IDEOGRAPH-FACE*/
+ RTUNI_ALPHA, /* U+00facf: CJK COMPATIBILITY IDEOGRAPH-FACF*/
+ RTUNI_ALPHA, /* U+00fad0: CJK COMPATIBILITY IDEOGRAPH-FAD0*/
+ RTUNI_ALPHA, /* U+00fad1: CJK COMPATIBILITY IDEOGRAPH-FAD1*/
+ RTUNI_ALPHA, /* U+00fad2: CJK COMPATIBILITY IDEOGRAPH-FAD2*/
+ RTUNI_ALPHA, /* U+00fad3: CJK COMPATIBILITY IDEOGRAPH-FAD3*/
+ RTUNI_ALPHA, /* U+00fad4: CJK COMPATIBILITY IDEOGRAPH-FAD4*/
+ RTUNI_ALPHA, /* U+00fad5: CJK COMPATIBILITY IDEOGRAPH-FAD5*/
+ RTUNI_ALPHA, /* U+00fad6: CJK COMPATIBILITY IDEOGRAPH-FAD6*/
+ RTUNI_ALPHA, /* U+00fad7: CJK COMPATIBILITY IDEOGRAPH-FAD7*/
+ RTUNI_ALPHA, /* U+00fad8: CJK COMPATIBILITY IDEOGRAPH-FAD8*/
+ RTUNI_ALPHA, /* U+00fad9: CJK COMPATIBILITY IDEOGRAPH-FAD9*/
+ 0, /* U+00fada: */
+ 0, /* U+00fadb: */
+ 0, /* U+00fadc: */
+ 0, /* U+00fadd: */
+ 0, /* U+00fade: */
+ 0, /* U+00fadf: */
+ 0, /* U+00fae0: */
+ 0, /* U+00fae1: */
+ 0, /* U+00fae2: */
+ 0, /* U+00fae3: */
+ 0, /* U+00fae4: */
+ 0, /* U+00fae5: */
+ 0, /* U+00fae6: */
+ 0, /* U+00fae7: */
+ 0, /* U+00fae8: */
+ 0, /* U+00fae9: */
+ 0, /* U+00faea: */
+ 0, /* U+00faeb: */
+ 0, /* U+00faec: */
+ 0, /* U+00faed: */
+ 0, /* U+00faee: */
+ 0, /* U+00faef: */
+ 0, /* U+00faf0: */
+ 0, /* U+00faf1: */
+ 0, /* U+00faf2: */
+ 0, /* U+00faf3: */
+ 0, /* U+00faf4: */
+ 0, /* U+00faf5: */
+ 0, /* U+00faf6: */
+ 0, /* U+00faf7: */
+ 0, /* U+00faf8: */
+ 0, /* U+00faf9: */
+ 0, /* U+00fafa: */
+ 0, /* U+00fafb: */
+ 0, /* U+00fafc: */
+ 0, /* U+00fafd: */
+ 0, /* U+00fafe: */
+ 0, /* U+00faff: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb00: LATIN SMALL LIGATURE FF*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb01: LATIN SMALL LIGATURE FI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb02: LATIN SMALL LIGATURE FL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb03: LATIN SMALL LIGATURE FFI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb04: LATIN SMALL LIGATURE FFL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb05: LATIN SMALL LIGATURE LONG S T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb06: LATIN SMALL LIGATURE ST*/
+ 0, /* U+00fb07: */
+ 0, /* U+00fb08: */
+ 0, /* U+00fb09: */
+ 0, /* U+00fb0a: */
+ 0, /* U+00fb0b: */
+ 0, /* U+00fb0c: */
+ 0, /* U+00fb0d: */
+ 0, /* U+00fb0e: */
+ 0, /* U+00fb0f: */
+ 0, /* U+00fb10: */
+ 0, /* U+00fb11: */
+ 0, /* U+00fb12: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb13: ARMENIAN SMALL LIGATURE MEN NOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb14: ARMENIAN SMALL LIGATURE MEN ECH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb15: ARMENIAN SMALL LIGATURE MEN INI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb16: ARMENIAN SMALL LIGATURE VEW NOW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00fb17: ARMENIAN SMALL LIGATURE MEN XEH*/
+ 0, /* U+00fb18: */
+ 0, /* U+00fb19: */
+ 0, /* U+00fb1a: */
+ 0, /* U+00fb1b: */
+ 0, /* U+00fb1c: */
+ RTUNI_ALPHA, /* U+00fb1d: HEBREW LETTER YOD WITH HIRIQ*/
+ RTUNI_ALPHA, /* U+00fb1e: HEBREW POINT JUDEO-SPANISH VARIKA*/
+ RTUNI_ALPHA, /* U+00fb1f: HEBREW LIGATURE YIDDISH YOD YOD PATAH*/
+ RTUNI_ALPHA, /* U+00fb20: HEBREW LETTER ALTERNATIVE AYIN*/
+ RTUNI_ALPHA, /* U+00fb21: HEBREW LETTER WIDE ALEF*/
+ RTUNI_ALPHA, /* U+00fb22: HEBREW LETTER WIDE DALET*/
+ RTUNI_ALPHA, /* U+00fb23: HEBREW LETTER WIDE HE*/
+ RTUNI_ALPHA, /* U+00fb24: HEBREW LETTER WIDE KAF*/
+ RTUNI_ALPHA, /* U+00fb25: HEBREW LETTER WIDE LAMED*/
+ RTUNI_ALPHA, /* U+00fb26: HEBREW LETTER WIDE FINAL MEM*/
+ RTUNI_ALPHA, /* U+00fb27: HEBREW LETTER WIDE RESH*/
+ RTUNI_ALPHA, /* U+00fb28: HEBREW LETTER WIDE TAV*/
+ 0, /* U+00fb29: HEBREW LETTER ALTERNATIVE PLUS SIGN*/
+ RTUNI_ALPHA, /* U+00fb2a: HEBREW LETTER SHIN WITH SHIN DOT*/
+ RTUNI_ALPHA, /* U+00fb2b: HEBREW LETTER SHIN WITH SIN DOT*/
+ RTUNI_ALPHA, /* U+00fb2c: HEBREW LETTER SHIN WITH DAGESH AND SHIN DOT*/
+ RTUNI_ALPHA, /* U+00fb2d: HEBREW LETTER SHIN WITH DAGESH AND SIN DOT*/
+ RTUNI_ALPHA, /* U+00fb2e: HEBREW LETTER ALEF WITH PATAH*/
+ RTUNI_ALPHA, /* U+00fb2f: HEBREW LETTER ALEF WITH QAMATS*/
+ RTUNI_ALPHA, /* U+00fb30: HEBREW LETTER ALEF WITH MAPIQ*/
+ RTUNI_ALPHA, /* U+00fb31: HEBREW LETTER BET WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb32: HEBREW LETTER GIMEL WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb33: HEBREW LETTER DALET WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb34: HEBREW LETTER HE WITH MAPIQ*/
+ RTUNI_ALPHA, /* U+00fb35: HEBREW LETTER VAV WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb36: HEBREW LETTER ZAYIN WITH DAGESH*/
+ 0, /* U+00fb37: */
+ RTUNI_ALPHA, /* U+00fb38: HEBREW LETTER TET WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb39: HEBREW LETTER YOD WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb3a: HEBREW LETTER FINAL KAF WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb3b: HEBREW LETTER KAF WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb3c: HEBREW LETTER LAMED WITH DAGESH*/
+ 0, /* U+00fb3d: */
+ RTUNI_ALPHA, /* U+00fb3e: HEBREW LETTER MEM WITH DAGESH*/
+ 0, /* U+00fb3f: */
+ RTUNI_ALPHA, /* U+00fb40: HEBREW LETTER NUN WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb41: HEBREW LETTER SAMEKH WITH DAGESH*/
+ 0, /* U+00fb42: */
+ RTUNI_ALPHA, /* U+00fb43: HEBREW LETTER FINAL PE WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb44: HEBREW LETTER PE WITH DAGESH*/
+ 0, /* U+00fb45: */
+ RTUNI_ALPHA, /* U+00fb46: HEBREW LETTER TSADI WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb47: HEBREW LETTER QOF WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb48: HEBREW LETTER RESH WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb49: HEBREW LETTER SHIN WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb4a: HEBREW LETTER TAV WITH DAGESH*/
+ RTUNI_ALPHA, /* U+00fb4b: HEBREW LETTER VAV WITH HOLAM*/
+ RTUNI_ALPHA, /* U+00fb4c: HEBREW LETTER BET WITH RAFE*/
+ RTUNI_ALPHA, /* U+00fb4d: HEBREW LETTER KAF WITH RAFE*/
+ RTUNI_ALPHA, /* U+00fb4e: HEBREW LETTER PE WITH RAFE*/
+ RTUNI_ALPHA, /* U+00fb4f: HEBREW LIGATURE ALEF LAMED*/
+ RTUNI_ALPHA, /* U+00fb50: ARABIC LETTER ALEF WASLA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb51: ARABIC LETTER ALEF WASLA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb52: ARABIC LETTER BEEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb53: ARABIC LETTER BEEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb54: ARABIC LETTER BEEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb55: ARABIC LETTER BEEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb56: ARABIC LETTER PEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb57: ARABIC LETTER PEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb58: ARABIC LETTER PEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb59: ARABIC LETTER PEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb5a: ARABIC LETTER BEHEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb5b: ARABIC LETTER BEHEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb5c: ARABIC LETTER BEHEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb5d: ARABIC LETTER BEHEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb5e: ARABIC LETTER TTEHEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb5f: ARABIC LETTER TTEHEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb60: ARABIC LETTER TTEHEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb61: ARABIC LETTER TTEHEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb62: ARABIC LETTER TEHEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb63: ARABIC LETTER TEHEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb64: ARABIC LETTER TEHEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb65: ARABIC LETTER TEHEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb66: ARABIC LETTER TTEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb67: ARABIC LETTER TTEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb68: ARABIC LETTER TTEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb69: ARABIC LETTER TTEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb6a: ARABIC LETTER VEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb6b: ARABIC LETTER VEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb6c: ARABIC LETTER VEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb6d: ARABIC LETTER VEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb6e: ARABIC LETTER PEHEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb6f: ARABIC LETTER PEHEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb70: ARABIC LETTER PEHEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb71: ARABIC LETTER PEHEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb72: ARABIC LETTER DYEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb73: ARABIC LETTER DYEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb74: ARABIC LETTER DYEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb75: ARABIC LETTER DYEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb76: ARABIC LETTER NYEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb77: ARABIC LETTER NYEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb78: ARABIC LETTER NYEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb79: ARABIC LETTER NYEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb7a: ARABIC LETTER TCHEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb7b: ARABIC LETTER TCHEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb7c: ARABIC LETTER TCHEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb7d: ARABIC LETTER TCHEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb7e: ARABIC LETTER TCHEHEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb7f: ARABIC LETTER TCHEHEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb80: ARABIC LETTER TCHEHEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb81: ARABIC LETTER TCHEHEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb82: ARABIC LETTER DDAHAL ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb83: ARABIC LETTER DDAHAL FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb84: ARABIC LETTER DAHAL ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb85: ARABIC LETTER DAHAL FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb86: ARABIC LETTER DUL ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb87: ARABIC LETTER DUL FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb88: ARABIC LETTER DDAL ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb89: ARABIC LETTER DDAL FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb8a: ARABIC LETTER JEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb8b: ARABIC LETTER JEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb8c: ARABIC LETTER RREH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb8d: ARABIC LETTER RREH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb8e: ARABIC LETTER KEHEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb8f: ARABIC LETTER KEHEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb90: ARABIC LETTER KEHEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb91: ARABIC LETTER KEHEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb92: ARABIC LETTER GAF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb93: ARABIC LETTER GAF FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb94: ARABIC LETTER GAF INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb95: ARABIC LETTER GAF MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb96: ARABIC LETTER GUEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb97: ARABIC LETTER GUEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb98: ARABIC LETTER GUEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb99: ARABIC LETTER GUEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb9a: ARABIC LETTER NGOEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb9b: ARABIC LETTER NGOEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fb9c: ARABIC LETTER NGOEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb9d: ARABIC LETTER NGOEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fb9e: ARABIC LETTER NOON GHUNNA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fb9f: ARABIC LETTER NOON GHUNNA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fba0: ARABIC LETTER RNOON ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fba1: ARABIC LETTER RNOON FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fba2: ARABIC LETTER RNOON INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fba3: ARABIC LETTER RNOON MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fba4: ARABIC LETTER HEH WITH YEH ABOVE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fba5: ARABIC LETTER HEH WITH YEH ABOVE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fba6: ARABIC LETTER HEH GOAL ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fba7: ARABIC LETTER HEH GOAL FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fba8: ARABIC LETTER HEH GOAL INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fba9: ARABIC LETTER HEH GOAL MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbaa: ARABIC LETTER HEH DOACHASHMEE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbab: ARABIC LETTER HEH DOACHASHMEE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbac: ARABIC LETTER HEH DOACHASHMEE INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbad: ARABIC LETTER HEH DOACHASHMEE MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbae: ARABIC LETTER YEH BARREE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbaf: ARABIC LETTER YEH BARREE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbb0: ARABIC LETTER YEH BARREE WITH HAMZA ABOVE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbb1: ARABIC LETTER YEH BARREE WITH HAMZA ABOVE FINAL FORM*/
+ 0, /* U+00fbb2: ARABIC SYMBOL DOT ABOVE*/
+ 0, /* U+00fbb3: ARABIC SYMBOL DOT BELOW*/
+ 0, /* U+00fbb4: ARABIC SYMBOL TWO DOTS ABOVE*/
+ 0, /* U+00fbb5: ARABIC SYMBOL TWO DOTS BELOW*/
+ 0, /* U+00fbb6: ARABIC SYMBOL THREE DOTS ABOVE*/
+ 0, /* U+00fbb7: ARABIC SYMBOL THREE DOTS BELOW*/
+ 0, /* U+00fbb8: ARABIC SYMBOL THREE DOTS POINTING DOWNWARDS ABOVE*/
+ 0, /* U+00fbb9: ARABIC SYMBOL THREE DOTS POINTING DOWNWARDS BELOW*/
+ 0, /* U+00fbba: ARABIC SYMBOL FOUR DOTS ABOVE*/
+ 0, /* U+00fbbb: ARABIC SYMBOL FOUR DOTS BELOW*/
+ 0, /* U+00fbbc: ARABIC SYMBOL DOUBLE VERTICAL BAR BELOW*/
+ 0, /* U+00fbbd: ARABIC SYMBOL TWO DOTS VERTICALLY ABOVE*/
+ 0, /* U+00fbbe: ARABIC SYMBOL TWO DOTS VERTICALLY BELOW*/
+ 0, /* U+00fbbf: ARABIC SYMBOL RING*/
+ 0, /* U+00fbc0: ARABIC SYMBOL SMALL TAH ABOVE*/
+ 0, /* U+00fbc1: ARABIC SYMBOL SMALL TAH BELOW*/
+ 0, /* U+00fbc2: */
+ 0, /* U+00fbc3: */
+ 0, /* U+00fbc4: */
+ 0, /* U+00fbc5: */
+ 0, /* U+00fbc6: */
+ 0, /* U+00fbc7: */
+ 0, /* U+00fbc8: */
+ 0, /* U+00fbc9: */
+ 0, /* U+00fbca: */
+ 0, /* U+00fbcb: */
+ 0, /* U+00fbcc: */
+ 0, /* U+00fbcd: */
+ 0, /* U+00fbce: */
+ 0, /* U+00fbcf: */
+ 0, /* U+00fbd0: */
+ 0, /* U+00fbd1: */
+ 0, /* U+00fbd2: */
+ RTUNI_ALPHA, /* U+00fbd3: ARABIC LETTER NG ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbd4: ARABIC LETTER NG FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbd5: ARABIC LETTER NG INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbd6: ARABIC LETTER NG MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbd7: ARABIC LETTER U ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbd8: ARABIC LETTER U FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbd9: ARABIC LETTER OE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbda: ARABIC LETTER OE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbdb: ARABIC LETTER YU ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbdc: ARABIC LETTER YU FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbdd: ARABIC LETTER U WITH HAMZA ABOVE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbde: ARABIC LETTER VE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbdf: ARABIC LETTER VE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbe0: ARABIC LETTER KIRGHIZ OE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbe1: ARABIC LETTER KIRGHIZ OE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbe2: ARABIC LETTER KIRGHIZ YU ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbe3: ARABIC LETTER KIRGHIZ YU FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbe4: ARABIC LETTER E ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbe5: ARABIC LETTER E FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbe6: ARABIC LETTER E INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbe7: ARABIC LETTER E MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbe8: ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbe9: ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbea: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbeb: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbec: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH AE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbed: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH AE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbee: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH WAW ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbef: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH WAW FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbf0: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH U ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbf1: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH U FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbf2: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH OE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbf3: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH OE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbf4: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YU ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbf5: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YU FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbf6: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbf7: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbf8: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH E INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbf9: ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbfa: ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbfb: ARABIC LIGATURE UIGHUR KIRGHIZ YEH WITH HAMZA ABOVE WITH ALEF MAKSURA INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbfc: ARABIC LETTER FARSI YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fbfd: ARABIC LETTER FARSI YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fbfe: ARABIC LETTER FARSI YEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fbff: ARABIC LETTER FARSI YEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fc00: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc01: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc02: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc03: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc04: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc05: ARABIC LIGATURE BEH WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc06: ARABIC LIGATURE BEH WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc07: ARABIC LIGATURE BEH WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc08: ARABIC LIGATURE BEH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc09: ARABIC LIGATURE BEH WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc0a: ARABIC LIGATURE BEH WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc0b: ARABIC LIGATURE TEH WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc0c: ARABIC LIGATURE TEH WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc0d: ARABIC LIGATURE TEH WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc0e: ARABIC LIGATURE TEH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc0f: ARABIC LIGATURE TEH WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc10: ARABIC LIGATURE TEH WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc11: ARABIC LIGATURE THEH WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc12: ARABIC LIGATURE THEH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc13: ARABIC LIGATURE THEH WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc14: ARABIC LIGATURE THEH WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc15: ARABIC LIGATURE JEEM WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc16: ARABIC LIGATURE JEEM WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc17: ARABIC LIGATURE HAH WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc18: ARABIC LIGATURE HAH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc19: ARABIC LIGATURE KHAH WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc1a: ARABIC LIGATURE KHAH WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc1b: ARABIC LIGATURE KHAH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc1c: ARABIC LIGATURE SEEN WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc1d: ARABIC LIGATURE SEEN WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc1e: ARABIC LIGATURE SEEN WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc1f: ARABIC LIGATURE SEEN WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc20: ARABIC LIGATURE SAD WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc21: ARABIC LIGATURE SAD WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc22: ARABIC LIGATURE DAD WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc23: ARABIC LIGATURE DAD WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc24: ARABIC LIGATURE DAD WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc25: ARABIC LIGATURE DAD WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc26: ARABIC LIGATURE TAH WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc27: ARABIC LIGATURE TAH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc28: ARABIC LIGATURE ZAH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc29: ARABIC LIGATURE AIN WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc2a: ARABIC LIGATURE AIN WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc2b: ARABIC LIGATURE GHAIN WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc2c: ARABIC LIGATURE GHAIN WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc2d: ARABIC LIGATURE FEH WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc2e: ARABIC LIGATURE FEH WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc2f: ARABIC LIGATURE FEH WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc30: ARABIC LIGATURE FEH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc31: ARABIC LIGATURE FEH WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc32: ARABIC LIGATURE FEH WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc33: ARABIC LIGATURE QAF WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc34: ARABIC LIGATURE QAF WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc35: ARABIC LIGATURE QAF WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc36: ARABIC LIGATURE QAF WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc37: ARABIC LIGATURE KAF WITH ALEF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc38: ARABIC LIGATURE KAF WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc39: ARABIC LIGATURE KAF WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc3a: ARABIC LIGATURE KAF WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc3b: ARABIC LIGATURE KAF WITH LAM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc3c: ARABIC LIGATURE KAF WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc3d: ARABIC LIGATURE KAF WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc3e: ARABIC LIGATURE KAF WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc3f: ARABIC LIGATURE LAM WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc40: ARABIC LIGATURE LAM WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc41: ARABIC LIGATURE LAM WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc42: ARABIC LIGATURE LAM WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc43: ARABIC LIGATURE LAM WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc44: ARABIC LIGATURE LAM WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc45: ARABIC LIGATURE MEEM WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc46: ARABIC LIGATURE MEEM WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc47: ARABIC LIGATURE MEEM WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc48: ARABIC LIGATURE MEEM WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc49: ARABIC LIGATURE MEEM WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc4a: ARABIC LIGATURE MEEM WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc4b: ARABIC LIGATURE NOON WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc4c: ARABIC LIGATURE NOON WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc4d: ARABIC LIGATURE NOON WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc4e: ARABIC LIGATURE NOON WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc4f: ARABIC LIGATURE NOON WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc50: ARABIC LIGATURE NOON WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc51: ARABIC LIGATURE HEH WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc52: ARABIC LIGATURE HEH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc53: ARABIC LIGATURE HEH WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc54: ARABIC LIGATURE HEH WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc55: ARABIC LIGATURE YEH WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc56: ARABIC LIGATURE YEH WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc57: ARABIC LIGATURE YEH WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc58: ARABIC LIGATURE YEH WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc59: ARABIC LIGATURE YEH WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc5a: ARABIC LIGATURE YEH WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc5b: ARABIC LIGATURE THAL WITH SUPERSCRIPT ALEF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc5c: ARABIC LIGATURE REH WITH SUPERSCRIPT ALEF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc5d: ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc5e: ARABIC LIGATURE SHADDA WITH DAMMATAN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc5f: ARABIC LIGATURE SHADDA WITH KASRATAN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc60: ARABIC LIGATURE SHADDA WITH FATHA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc61: ARABIC LIGATURE SHADDA WITH DAMMA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc62: ARABIC LIGATURE SHADDA WITH KASRA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc63: ARABIC LIGATURE SHADDA WITH SUPERSCRIPT ALEF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fc64: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc65: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ZAIN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc66: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc67: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH NOON FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc68: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc69: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc6a: ARABIC LIGATURE BEH WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc6b: ARABIC LIGATURE BEH WITH ZAIN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc6c: ARABIC LIGATURE BEH WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc6d: ARABIC LIGATURE BEH WITH NOON FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc6e: ARABIC LIGATURE BEH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc6f: ARABIC LIGATURE BEH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc70: ARABIC LIGATURE TEH WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc71: ARABIC LIGATURE TEH WITH ZAIN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc72: ARABIC LIGATURE TEH WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc73: ARABIC LIGATURE TEH WITH NOON FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc74: ARABIC LIGATURE TEH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc75: ARABIC LIGATURE TEH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc76: ARABIC LIGATURE THEH WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc77: ARABIC LIGATURE THEH WITH ZAIN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc78: ARABIC LIGATURE THEH WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc79: ARABIC LIGATURE THEH WITH NOON FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc7a: ARABIC LIGATURE THEH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc7b: ARABIC LIGATURE THEH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc7c: ARABIC LIGATURE FEH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc7d: ARABIC LIGATURE FEH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc7e: ARABIC LIGATURE QAF WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc7f: ARABIC LIGATURE QAF WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc80: ARABIC LIGATURE KAF WITH ALEF FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc81: ARABIC LIGATURE KAF WITH LAM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc82: ARABIC LIGATURE KAF WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc83: ARABIC LIGATURE KAF WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc84: ARABIC LIGATURE KAF WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc85: ARABIC LIGATURE LAM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc86: ARABIC LIGATURE LAM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc87: ARABIC LIGATURE LAM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc88: ARABIC LIGATURE MEEM WITH ALEF FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc89: ARABIC LIGATURE MEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc8a: ARABIC LIGATURE NOON WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc8b: ARABIC LIGATURE NOON WITH ZAIN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc8c: ARABIC LIGATURE NOON WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc8d: ARABIC LIGATURE NOON WITH NOON FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc8e: ARABIC LIGATURE NOON WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc8f: ARABIC LIGATURE NOON WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc90: ARABIC LIGATURE ALEF MAKSURA WITH SUPERSCRIPT ALEF FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc91: ARABIC LIGATURE YEH WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc92: ARABIC LIGATURE YEH WITH ZAIN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc93: ARABIC LIGATURE YEH WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc94: ARABIC LIGATURE YEH WITH NOON FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc95: ARABIC LIGATURE YEH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc96: ARABIC LIGATURE YEH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fc97: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fc98: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fc99: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fc9a: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fc9b: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fc9c: ARABIC LIGATURE BEH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fc9d: ARABIC LIGATURE BEH WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fc9e: ARABIC LIGATURE BEH WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fc9f: ARABIC LIGATURE BEH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca0: ARABIC LIGATURE BEH WITH HEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca1: ARABIC LIGATURE TEH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca2: ARABIC LIGATURE TEH WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca3: ARABIC LIGATURE TEH WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca4: ARABIC LIGATURE TEH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca5: ARABIC LIGATURE TEH WITH HEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca6: ARABIC LIGATURE THEH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca7: ARABIC LIGATURE JEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca8: ARABIC LIGATURE JEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fca9: ARABIC LIGATURE HAH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcaa: ARABIC LIGATURE HAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcab: ARABIC LIGATURE KHAH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcac: ARABIC LIGATURE KHAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcad: ARABIC LIGATURE SEEN WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcae: ARABIC LIGATURE SEEN WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcaf: ARABIC LIGATURE SEEN WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb0: ARABIC LIGATURE SEEN WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb1: ARABIC LIGATURE SAD WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb2: ARABIC LIGATURE SAD WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb3: ARABIC LIGATURE SAD WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb4: ARABIC LIGATURE DAD WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb5: ARABIC LIGATURE DAD WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb6: ARABIC LIGATURE DAD WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb7: ARABIC LIGATURE DAD WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb8: ARABIC LIGATURE TAH WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcb9: ARABIC LIGATURE ZAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcba: ARABIC LIGATURE AIN WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcbb: ARABIC LIGATURE AIN WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcbc: ARABIC LIGATURE GHAIN WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcbd: ARABIC LIGATURE GHAIN WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcbe: ARABIC LIGATURE FEH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcbf: ARABIC LIGATURE FEH WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc0: ARABIC LIGATURE FEH WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc1: ARABIC LIGATURE FEH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc2: ARABIC LIGATURE QAF WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc3: ARABIC LIGATURE QAF WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc4: ARABIC LIGATURE KAF WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc5: ARABIC LIGATURE KAF WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc6: ARABIC LIGATURE KAF WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc7: ARABIC LIGATURE KAF WITH LAM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc8: ARABIC LIGATURE KAF WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcc9: ARABIC LIGATURE LAM WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcca: ARABIC LIGATURE LAM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fccb: ARABIC LIGATURE LAM WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fccc: ARABIC LIGATURE LAM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fccd: ARABIC LIGATURE LAM WITH HEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcce: ARABIC LIGATURE MEEM WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fccf: ARABIC LIGATURE MEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd0: ARABIC LIGATURE MEEM WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd1: ARABIC LIGATURE MEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd2: ARABIC LIGATURE NOON WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd3: ARABIC LIGATURE NOON WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd4: ARABIC LIGATURE NOON WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd5: ARABIC LIGATURE NOON WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd6: ARABIC LIGATURE NOON WITH HEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd7: ARABIC LIGATURE HEH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd8: ARABIC LIGATURE HEH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcd9: ARABIC LIGATURE HEH WITH SUPERSCRIPT ALEF INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcda: ARABIC LIGATURE YEH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcdb: ARABIC LIGATURE YEH WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcdc: ARABIC LIGATURE YEH WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcdd: ARABIC LIGATURE YEH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcde: ARABIC LIGATURE YEH WITH HEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcdf: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce0: ARABIC LIGATURE YEH WITH HAMZA ABOVE WITH HEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce1: ARABIC LIGATURE BEH WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce2: ARABIC LIGATURE BEH WITH HEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce3: ARABIC LIGATURE TEH WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce4: ARABIC LIGATURE TEH WITH HEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce5: ARABIC LIGATURE THEH WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce6: ARABIC LIGATURE THEH WITH HEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce7: ARABIC LIGATURE SEEN WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce8: ARABIC LIGATURE SEEN WITH HEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fce9: ARABIC LIGATURE SHEEN WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcea: ARABIC LIGATURE SHEEN WITH HEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fceb: ARABIC LIGATURE KAF WITH LAM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcec: ARABIC LIGATURE KAF WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fced: ARABIC LIGATURE LAM WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcee: ARABIC LIGATURE NOON WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcef: ARABIC LIGATURE NOON WITH HEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcf0: ARABIC LIGATURE YEH WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcf1: ARABIC LIGATURE YEH WITH HEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcf2: ARABIC LIGATURE SHADDA WITH FATHA MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcf3: ARABIC LIGATURE SHADDA WITH DAMMA MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcf4: ARABIC LIGATURE SHADDA WITH KASRA MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fcf5: ARABIC LIGATURE TAH WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcf6: ARABIC LIGATURE TAH WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcf7: ARABIC LIGATURE AIN WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcf8: ARABIC LIGATURE AIN WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcf9: ARABIC LIGATURE GHAIN WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcfa: ARABIC LIGATURE GHAIN WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcfb: ARABIC LIGATURE SEEN WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcfc: ARABIC LIGATURE SEEN WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcfd: ARABIC LIGATURE SHEEN WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcfe: ARABIC LIGATURE SHEEN WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fcff: ARABIC LIGATURE HAH WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd00: ARABIC LIGATURE HAH WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd01: ARABIC LIGATURE JEEM WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd02: ARABIC LIGATURE JEEM WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd03: ARABIC LIGATURE KHAH WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd04: ARABIC LIGATURE KHAH WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd05: ARABIC LIGATURE SAD WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd06: ARABIC LIGATURE SAD WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd07: ARABIC LIGATURE DAD WITH ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd08: ARABIC LIGATURE DAD WITH YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd09: ARABIC LIGATURE SHEEN WITH JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd0a: ARABIC LIGATURE SHEEN WITH HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd0b: ARABIC LIGATURE SHEEN WITH KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd0c: ARABIC LIGATURE SHEEN WITH MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd0d: ARABIC LIGATURE SHEEN WITH REH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd0e: ARABIC LIGATURE SEEN WITH REH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd0f: ARABIC LIGATURE SAD WITH REH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd10: ARABIC LIGATURE DAD WITH REH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fd11: ARABIC LIGATURE TAH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd12: ARABIC LIGATURE TAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd13: ARABIC LIGATURE AIN WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd14: ARABIC LIGATURE AIN WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd15: ARABIC LIGATURE GHAIN WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd16: ARABIC LIGATURE GHAIN WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd17: ARABIC LIGATURE SEEN WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd18: ARABIC LIGATURE SEEN WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd19: ARABIC LIGATURE SHEEN WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd1a: ARABIC LIGATURE SHEEN WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd1b: ARABIC LIGATURE HAH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd1c: ARABIC LIGATURE HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd1d: ARABIC LIGATURE JEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd1e: ARABIC LIGATURE JEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd1f: ARABIC LIGATURE KHAH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd20: ARABIC LIGATURE KHAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd21: ARABIC LIGATURE SAD WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd22: ARABIC LIGATURE SAD WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd23: ARABIC LIGATURE DAD WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd24: ARABIC LIGATURE DAD WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd25: ARABIC LIGATURE SHEEN WITH JEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd26: ARABIC LIGATURE SHEEN WITH HAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd27: ARABIC LIGATURE SHEEN WITH KHAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd28: ARABIC LIGATURE SHEEN WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd29: ARABIC LIGATURE SHEEN WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd2a: ARABIC LIGATURE SEEN WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd2b: ARABIC LIGATURE SAD WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd2c: ARABIC LIGATURE DAD WITH REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd2d: ARABIC LIGATURE SHEEN WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd2e: ARABIC LIGATURE SHEEN WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd2f: ARABIC LIGATURE SHEEN WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd30: ARABIC LIGATURE SHEEN WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd31: ARABIC LIGATURE SEEN WITH HEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd32: ARABIC LIGATURE SHEEN WITH HEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd33: ARABIC LIGATURE TAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd34: ARABIC LIGATURE SEEN WITH JEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd35: ARABIC LIGATURE SEEN WITH HAH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd36: ARABIC LIGATURE SEEN WITH KHAH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd37: ARABIC LIGATURE SHEEN WITH JEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd38: ARABIC LIGATURE SHEEN WITH HAH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd39: ARABIC LIGATURE SHEEN WITH KHAH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd3a: ARABIC LIGATURE TAH WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd3b: ARABIC LIGATURE ZAH WITH MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd3c: ARABIC LIGATURE ALEF WITH FATHATAN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd3d: ARABIC LIGATURE ALEF WITH FATHATAN ISOLATED FORM*/
+ 0, /* U+00fd3e: ORNATE LEFT PARENTHESIS*/
+ 0, /* U+00fd3f: ORNATE RIGHT PARENTHESIS*/
+ 0, /* U+00fd40: */
+ 0, /* U+00fd41: */
+ 0, /* U+00fd42: */
+ 0, /* U+00fd43: */
+ 0, /* U+00fd44: */
+ 0, /* U+00fd45: */
+ 0, /* U+00fd46: */
+ 0, /* U+00fd47: */
+ 0, /* U+00fd48: */
+ 0, /* U+00fd49: */
+ 0, /* U+00fd4a: */
+ 0, /* U+00fd4b: */
+ 0, /* U+00fd4c: */
+ 0, /* U+00fd4d: */
+ 0, /* U+00fd4e: */
+ 0, /* U+00fd4f: */
+ RTUNI_ALPHA, /* U+00fd50: ARABIC LIGATURE TEH WITH JEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd51: ARABIC LIGATURE TEH WITH HAH WITH JEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd52: ARABIC LIGATURE TEH WITH HAH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd53: ARABIC LIGATURE TEH WITH HAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd54: ARABIC LIGATURE TEH WITH KHAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd55: ARABIC LIGATURE TEH WITH MEEM WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd56: ARABIC LIGATURE TEH WITH MEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd57: ARABIC LIGATURE TEH WITH MEEM WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd58: ARABIC LIGATURE JEEM WITH MEEM WITH HAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd59: ARABIC LIGATURE JEEM WITH MEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd5a: ARABIC LIGATURE HAH WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd5b: ARABIC LIGATURE HAH WITH MEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd5c: ARABIC LIGATURE SEEN WITH HAH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd5d: ARABIC LIGATURE SEEN WITH JEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd5e: ARABIC LIGATURE SEEN WITH JEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd5f: ARABIC LIGATURE SEEN WITH MEEM WITH HAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd60: ARABIC LIGATURE SEEN WITH MEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd61: ARABIC LIGATURE SEEN WITH MEEM WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd62: ARABIC LIGATURE SEEN WITH MEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd63: ARABIC LIGATURE SEEN WITH MEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd64: ARABIC LIGATURE SAD WITH HAH WITH HAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd65: ARABIC LIGATURE SAD WITH HAH WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd66: ARABIC LIGATURE SAD WITH MEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd67: ARABIC LIGATURE SHEEN WITH HAH WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd68: ARABIC LIGATURE SHEEN WITH HAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd69: ARABIC LIGATURE SHEEN WITH JEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd6a: ARABIC LIGATURE SHEEN WITH MEEM WITH KHAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd6b: ARABIC LIGATURE SHEEN WITH MEEM WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd6c: ARABIC LIGATURE SHEEN WITH MEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd6d: ARABIC LIGATURE SHEEN WITH MEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd6e: ARABIC LIGATURE DAD WITH HAH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd6f: ARABIC LIGATURE DAD WITH KHAH WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd70: ARABIC LIGATURE DAD WITH KHAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd71: ARABIC LIGATURE TAH WITH MEEM WITH HAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd72: ARABIC LIGATURE TAH WITH MEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd73: ARABIC LIGATURE TAH WITH MEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd74: ARABIC LIGATURE TAH WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd75: ARABIC LIGATURE AIN WITH JEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd76: ARABIC LIGATURE AIN WITH MEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd77: ARABIC LIGATURE AIN WITH MEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd78: ARABIC LIGATURE AIN WITH MEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd79: ARABIC LIGATURE GHAIN WITH MEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd7a: ARABIC LIGATURE GHAIN WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd7b: ARABIC LIGATURE GHAIN WITH MEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd7c: ARABIC LIGATURE FEH WITH KHAH WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd7d: ARABIC LIGATURE FEH WITH KHAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd7e: ARABIC LIGATURE QAF WITH MEEM WITH HAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd7f: ARABIC LIGATURE QAF WITH MEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd80: ARABIC LIGATURE LAM WITH HAH WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd81: ARABIC LIGATURE LAM WITH HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd82: ARABIC LIGATURE LAM WITH HAH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd83: ARABIC LIGATURE LAM WITH JEEM WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd84: ARABIC LIGATURE LAM WITH JEEM WITH JEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd85: ARABIC LIGATURE LAM WITH KHAH WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd86: ARABIC LIGATURE LAM WITH KHAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd87: ARABIC LIGATURE LAM WITH MEEM WITH HAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd88: ARABIC LIGATURE LAM WITH MEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd89: ARABIC LIGATURE MEEM WITH HAH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd8a: ARABIC LIGATURE MEEM WITH HAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd8b: ARABIC LIGATURE MEEM WITH HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd8c: ARABIC LIGATURE MEEM WITH JEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd8d: ARABIC LIGATURE MEEM WITH JEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd8e: ARABIC LIGATURE MEEM WITH KHAH WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd8f: ARABIC LIGATURE MEEM WITH KHAH WITH MEEM INITIAL FORM*/
+ 0, /* U+00fd90: */
+ 0, /* U+00fd91: */
+ RTUNI_ALPHA, /* U+00fd92: ARABIC LIGATURE MEEM WITH JEEM WITH KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd93: ARABIC LIGATURE HEH WITH MEEM WITH JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd94: ARABIC LIGATURE HEH WITH MEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd95: ARABIC LIGATURE NOON WITH HAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd96: ARABIC LIGATURE NOON WITH HAH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd97: ARABIC LIGATURE NOON WITH JEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd98: ARABIC LIGATURE NOON WITH JEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd99: ARABIC LIGATURE NOON WITH JEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd9a: ARABIC LIGATURE NOON WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd9b: ARABIC LIGATURE NOON WITH MEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd9c: ARABIC LIGATURE YEH WITH MEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd9d: ARABIC LIGATURE YEH WITH MEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fd9e: ARABIC LIGATURE BEH WITH KHAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fd9f: ARABIC LIGATURE TEH WITH JEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda0: ARABIC LIGATURE TEH WITH JEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda1: ARABIC LIGATURE TEH WITH KHAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda2: ARABIC LIGATURE TEH WITH KHAH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda3: ARABIC LIGATURE TEH WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda4: ARABIC LIGATURE TEH WITH MEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda5: ARABIC LIGATURE JEEM WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda6: ARABIC LIGATURE JEEM WITH HAH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda7: ARABIC LIGATURE JEEM WITH MEEM WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda8: ARABIC LIGATURE SEEN WITH KHAH WITH ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fda9: ARABIC LIGATURE SAD WITH HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdaa: ARABIC LIGATURE SHEEN WITH HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdab: ARABIC LIGATURE DAD WITH HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdac: ARABIC LIGATURE LAM WITH JEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdad: ARABIC LIGATURE LAM WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdae: ARABIC LIGATURE YEH WITH HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdaf: ARABIC LIGATURE YEH WITH JEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb0: ARABIC LIGATURE YEH WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb1: ARABIC LIGATURE MEEM WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb2: ARABIC LIGATURE QAF WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb3: ARABIC LIGATURE NOON WITH HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb4: ARABIC LIGATURE QAF WITH MEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb5: ARABIC LIGATURE LAM WITH HAH WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb6: ARABIC LIGATURE AIN WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb7: ARABIC LIGATURE KAF WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb8: ARABIC LIGATURE NOON WITH JEEM WITH HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fdb9: ARABIC LIGATURE MEEM WITH KHAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdba: ARABIC LIGATURE LAM WITH JEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fdbb: ARABIC LIGATURE KAF WITH MEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdbc: ARABIC LIGATURE LAM WITH JEEM WITH MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdbd: ARABIC LIGATURE NOON WITH JEEM WITH HAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdbe: ARABIC LIGATURE JEEM WITH HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdbf: ARABIC LIGATURE HAH WITH JEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdc0: ARABIC LIGATURE MEEM WITH JEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdc1: ARABIC LIGATURE FEH WITH MEEM WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdc2: ARABIC LIGATURE BEH WITH HAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdc3: ARABIC LIGATURE KAF WITH MEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fdc4: ARABIC LIGATURE AIN WITH JEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fdc5: ARABIC LIGATURE SAD WITH MEEM WITH MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fdc6: ARABIC LIGATURE SEEN WITH KHAH WITH YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fdc7: ARABIC LIGATURE NOON WITH JEEM WITH YEH FINAL FORM*/
+ 0, /* U+00fdc8: */
+ 0, /* U+00fdc9: */
+ 0, /* U+00fdca: */
+ 0, /* U+00fdcb: */
+ 0, /* U+00fdcc: */
+ 0, /* U+00fdcd: */
+ 0, /* U+00fdce: */
+ 0, /* U+00fdcf: */
+ 0, /* U+00fdd0: */
+ 0, /* U+00fdd1: */
+ 0, /* U+00fdd2: */
+ 0, /* U+00fdd3: */
+ 0, /* U+00fdd4: */
+ 0, /* U+00fdd5: */
+ 0, /* U+00fdd6: */
+ 0, /* U+00fdd7: */
+ 0, /* U+00fdd8: */
+ 0, /* U+00fdd9: */
+ 0, /* U+00fdda: */
+ 0, /* U+00fddb: */
+ 0, /* U+00fddc: */
+ 0, /* U+00fddd: */
+ 0, /* U+00fdde: */
+ 0, /* U+00fddf: */
+ 0, /* U+00fde0: */
+ 0, /* U+00fde1: */
+ 0, /* U+00fde2: */
+ 0, /* U+00fde3: */
+ 0, /* U+00fde4: */
+ 0, /* U+00fde5: */
+ 0, /* U+00fde6: */
+ 0, /* U+00fde7: */
+ 0, /* U+00fde8: */
+ 0, /* U+00fde9: */
+ 0, /* U+00fdea: */
+ 0, /* U+00fdeb: */
+ 0, /* U+00fdec: */
+ 0, /* U+00fded: */
+ 0, /* U+00fdee: */
+ 0, /* U+00fdef: */
+ RTUNI_ALPHA, /* U+00fdf0: ARABIC LIGATURE SALLA USED AS KORANIC STOP SIGN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdf1: ARABIC LIGATURE QALA USED AS KORANIC STOP SIGN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdf2: ARABIC LIGATURE ALLAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdf3: ARABIC LIGATURE AKBAR ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdf4: ARABIC LIGATURE MOHAMMAD ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdf5: ARABIC LIGATURE SALAM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdf6: ARABIC LIGATURE RASOUL ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdf7: ARABIC LIGATURE ALAYHE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdf8: ARABIC LIGATURE WASALLAM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdf9: ARABIC LIGATURE SALLA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fdfa: ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM*/
+ RTUNI_ALPHA, /* U+00fdfb: ARABIC LIGATURE JALLAJALALOUHOU*/
+ 0, /* U+00fdfc: RIAL SIGN*/
+ 0, /* U+00fdfd: ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM*/
+ 0, /* U+00fdfe: */
+ 0, /* U+00fdff: */
+ 0, /* U+00fe00: VARIATION SELECTOR-1*/
+ 0, /* U+00fe01: VARIATION SELECTOR-2*/
+ 0, /* U+00fe02: VARIATION SELECTOR-3*/
+ 0, /* U+00fe03: VARIATION SELECTOR-4*/
+ 0, /* U+00fe04: VARIATION SELECTOR-5*/
+ 0, /* U+00fe05: VARIATION SELECTOR-6*/
+ 0, /* U+00fe06: VARIATION SELECTOR-7*/
+ 0, /* U+00fe07: VARIATION SELECTOR-8*/
+ 0, /* U+00fe08: VARIATION SELECTOR-9*/
+ 0, /* U+00fe09: VARIATION SELECTOR-10*/
+ 0, /* U+00fe0a: VARIATION SELECTOR-11*/
+ 0, /* U+00fe0b: VARIATION SELECTOR-12*/
+ 0, /* U+00fe0c: VARIATION SELECTOR-13*/
+ 0, /* U+00fe0d: VARIATION SELECTOR-14*/
+ 0, /* U+00fe0e: VARIATION SELECTOR-15*/
+ 0, /* U+00fe0f: VARIATION SELECTOR-16*/
+ 0, /* U+00fe10: PRESENTATION FORM FOR VERTICAL COMMA*/
+ 0, /* U+00fe11: PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC COMMA*/
+ 0, /* U+00fe12: PRESENTATION FORM FOR VERTICAL IDEOGRAPHIC FULL STOP*/
+ 0, /* U+00fe13: PRESENTATION FORM FOR VERTICAL COLON*/
+ 0, /* U+00fe14: PRESENTATION FORM FOR VERTICAL SEMICOLON*/
+ 0, /* U+00fe15: PRESENTATION FORM FOR VERTICAL EXCLAMATION MARK*/
+ 0, /* U+00fe16: PRESENTATION FORM FOR VERTICAL QUESTION MARK*/
+ 0, /* U+00fe17: PRESENTATION FORM FOR VERTICAL LEFT WHITE LENTICULAR BRACKET*/
+ 0, /* U+00fe18: PRESENTATION FORM FOR VERTICAL RIGHT WHITE LENTICULAR BRAKCET*/
+ 0, /* U+00fe19: PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS*/
+ 0, /* U+00fe1a: */
+ 0, /* U+00fe1b: */
+ 0, /* U+00fe1c: */
+ 0, /* U+00fe1d: */
+ 0, /* U+00fe1e: */
+ 0, /* U+00fe1f: */
+ 0, /* U+00fe20: COMBINING LIGATURE LEFT HALF*/
+ 0, /* U+00fe21: COMBINING LIGATURE RIGHT HALF*/
+ 0, /* U+00fe22: COMBINING DOUBLE TILDE LEFT HALF*/
+ 0, /* U+00fe23: COMBINING DOUBLE TILDE RIGHT HALF*/
+ 0, /* U+00fe24: COMBINING MACRON LEFT HALF*/
+ 0, /* U+00fe25: COMBINING MACRON RIGHT HALF*/
+ 0, /* U+00fe26: COMBINING CONJOINING MACRON*/
+ 0, /* U+00fe27: */
+ 0, /* U+00fe28: */
+ 0, /* U+00fe29: */
+ 0, /* U+00fe2a: */
+ 0, /* U+00fe2b: */
+ 0, /* U+00fe2c: */
+ 0, /* U+00fe2d: */
+ 0, /* U+00fe2e: */
+ 0, /* U+00fe2f: */
+ 0, /* U+00fe30: PRESENTATION FORM FOR VERTICAL TWO DOT LEADER*/
+ 0, /* U+00fe31: PRESENTATION FORM FOR VERTICAL EM DASH*/
+ 0, /* U+00fe32: PRESENTATION FORM FOR VERTICAL EN DASH*/
+ 0, /* U+00fe33: PRESENTATION FORM FOR VERTICAL LOW LINE*/
+ 0, /* U+00fe34: PRESENTATION FORM FOR VERTICAL WAVY LOW LINE*/
+ 0, /* U+00fe35: PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS*/
+ 0, /* U+00fe36: PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS*/
+ 0, /* U+00fe37: PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET*/
+ 0, /* U+00fe38: PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET*/
+ 0, /* U+00fe39: PRESENTATION FORM FOR VERTICAL LEFT TORTOISE SHELL BRACKET*/
+ 0, /* U+00fe3a: PRESENTATION FORM FOR VERTICAL RIGHT TORTOISE SHELL BRACKET*/
+ 0, /* U+00fe3b: PRESENTATION FORM FOR VERTICAL LEFT BLACK LENTICULAR BRACKET*/
+ 0, /* U+00fe3c: PRESENTATION FORM FOR VERTICAL RIGHT BLACK LENTICULAR BRACKET*/
+ 0, /* U+00fe3d: PRESENTATION FORM FOR VERTICAL LEFT DOUBLE ANGLE BRACKET*/
+ 0, /* U+00fe3e: PRESENTATION FORM FOR VERTICAL RIGHT DOUBLE ANGLE BRACKET*/
+ 0, /* U+00fe3f: PRESENTATION FORM FOR VERTICAL LEFT ANGLE BRACKET*/
+ 0, /* U+00fe40: PRESENTATION FORM FOR VERTICAL RIGHT ANGLE BRACKET*/
+ 0, /* U+00fe41: PRESENTATION FORM FOR VERTICAL LEFT CORNER BRACKET*/
+ 0, /* U+00fe42: PRESENTATION FORM FOR VERTICAL RIGHT CORNER BRACKET*/
+ 0, /* U+00fe43: PRESENTATION FORM FOR VERTICAL LEFT WHITE CORNER BRACKET*/
+ 0, /* U+00fe44: PRESENTATION FORM FOR VERTICAL RIGHT WHITE CORNER BRACKET*/
+ 0, /* U+00fe45: SESAME DOT*/
+ 0, /* U+00fe46: WHITE SESAME DOT*/
+ 0, /* U+00fe47: PRESENTATION FORM FOR VERTICAL LEFT SQUARE BRACKET*/
+ 0, /* U+00fe48: PRESENTATION FORM FOR VERTICAL RIGHT SQUARE BRACKET*/
+ 0, /* U+00fe49: DASHED OVERLINE*/
+ 0, /* U+00fe4a: CENTRELINE OVERLINE*/
+ 0, /* U+00fe4b: WAVY OVERLINE*/
+ 0, /* U+00fe4c: DOUBLE WAVY OVERLINE*/
+ 0, /* U+00fe4d: DASHED LOW LINE*/
+ 0, /* U+00fe4e: CENTRELINE LOW LINE*/
+ 0, /* U+00fe4f: WAVY LOW LINE*/
+ 0, /* U+00fe50: SMALL COMMA*/
+ 0, /* U+00fe51: SMALL IDEOGRAPHIC COMMA*/
+ 0, /* U+00fe52: SMALL FULL STOP*/
+ 0, /* U+00fe53: */
+ 0, /* U+00fe54: SMALL SEMICOLON*/
+ 0, /* U+00fe55: SMALL COLON*/
+ 0, /* U+00fe56: SMALL QUESTION MARK*/
+ 0, /* U+00fe57: SMALL EXCLAMATION MARK*/
+ 0, /* U+00fe58: SMALL EM DASH*/
+ 0, /* U+00fe59: SMALL LEFT PARENTHESIS*/
+ 0, /* U+00fe5a: SMALL RIGHT PARENTHESIS*/
+ 0, /* U+00fe5b: SMALL LEFT CURLY BRACKET*/
+ 0, /* U+00fe5c: SMALL RIGHT CURLY BRACKET*/
+ 0, /* U+00fe5d: SMALL LEFT TORTOISE SHELL BRACKET*/
+ 0, /* U+00fe5e: SMALL RIGHT TORTOISE SHELL BRACKET*/
+ 0, /* U+00fe5f: SMALL NUMBER SIGN*/
+ 0, /* U+00fe60: SMALL AMPERSAND*/
+ 0, /* U+00fe61: SMALL ASTERISK*/
+ 0, /* U+00fe62: SMALL PLUS SIGN*/
+ 0, /* U+00fe63: SMALL HYPHEN-MINUS*/
+ 0, /* U+00fe64: SMALL LESS-THAN SIGN*/
+ 0, /* U+00fe65: SMALL GREATER-THAN SIGN*/
+ 0, /* U+00fe66: SMALL EQUALS SIGN*/
+ 0, /* U+00fe67: */
+ 0, /* U+00fe68: SMALL REVERSE SOLIDUS*/
+ 0, /* U+00fe69: SMALL DOLLAR SIGN*/
+ 0, /* U+00fe6a: SMALL PERCENT SIGN*/
+ 0, /* U+00fe6b: SMALL COMMERCIAL AT*/
+ 0, /* U+00fe6c: */
+ 0, /* U+00fe6d: */
+ 0, /* U+00fe6e: */
+ 0, /* U+00fe6f: */
+ RTUNI_ALPHA, /* U+00fe70: ARABIC FATHATAN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe71: ARABIC TATWEEL WITH FATHATAN ABOVE*/
+ RTUNI_ALPHA, /* U+00fe72: ARABIC DAMMATAN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe73: ARABIC TAIL FRAGMENT*/
+ RTUNI_ALPHA, /* U+00fe74: ARABIC KASRATAN ISOLATED FORM*/
+ 0, /* U+00fe75: */
+ RTUNI_ALPHA, /* U+00fe76: ARABIC FATHA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe77: ARABIC FATHA MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe78: ARABIC DAMMA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe79: ARABIC DAMMA MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe7a: ARABIC KASRA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe7b: ARABIC KASRA MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe7c: ARABIC SHADDA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe7d: ARABIC SHADDA MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe7e: ARABIC SUKUN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe7f: ARABIC SUKUN MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe80: ARABIC LETTER HAMZA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe81: ARABIC LETTER ALEF WITH MADDA ABOVE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe82: ARABIC LETTER ALEF WITH MADDA ABOVE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe83: ARABIC LETTER ALEF WITH HAMZA ABOVE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe84: ARABIC LETTER ALEF WITH HAMZA ABOVE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe85: ARABIC LETTER WAW WITH HAMZA ABOVE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe86: ARABIC LETTER WAW WITH HAMZA ABOVE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe87: ARABIC LETTER ALEF WITH HAMZA BELOW ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe88: ARABIC LETTER ALEF WITH HAMZA BELOW FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe89: ARABIC LETTER YEH WITH HAMZA ABOVE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe8a: ARABIC LETTER YEH WITH HAMZA ABOVE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe8b: ARABIC LETTER YEH WITH HAMZA ABOVE INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe8c: ARABIC LETTER YEH WITH HAMZA ABOVE MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe8d: ARABIC LETTER ALEF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe8e: ARABIC LETTER ALEF FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe8f: ARABIC LETTER BEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe90: ARABIC LETTER BEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe91: ARABIC LETTER BEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe92: ARABIC LETTER BEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe93: ARABIC LETTER TEH MARBUTA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe94: ARABIC LETTER TEH MARBUTA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe95: ARABIC LETTER TEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe96: ARABIC LETTER TEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe97: ARABIC LETTER TEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe98: ARABIC LETTER TEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe99: ARABIC LETTER THEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe9a: ARABIC LETTER THEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe9b: ARABIC LETTER THEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe9c: ARABIC LETTER THEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fe9d: ARABIC LETTER JEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fe9e: ARABIC LETTER JEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fe9f: ARABIC LETTER JEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fea0: ARABIC LETTER JEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fea1: ARABIC LETTER HAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fea2: ARABIC LETTER HAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fea3: ARABIC LETTER HAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fea4: ARABIC LETTER HAH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fea5: ARABIC LETTER KHAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fea6: ARABIC LETTER KHAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fea7: ARABIC LETTER KHAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fea8: ARABIC LETTER KHAH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fea9: ARABIC LETTER DAL ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feaa: ARABIC LETTER DAL FINAL FORM*/
+ RTUNI_ALPHA, /* U+00feab: ARABIC LETTER THAL ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feac: ARABIC LETTER THAL FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fead: ARABIC LETTER REH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feae: ARABIC LETTER REH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00feaf: ARABIC LETTER ZAIN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feb0: ARABIC LETTER ZAIN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00feb1: ARABIC LETTER SEEN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feb2: ARABIC LETTER SEEN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00feb3: ARABIC LETTER SEEN INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00feb4: ARABIC LETTER SEEN MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00feb5: ARABIC LETTER SHEEN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feb6: ARABIC LETTER SHEEN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00feb7: ARABIC LETTER SHEEN INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00feb8: ARABIC LETTER SHEEN MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00feb9: ARABIC LETTER SAD ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feba: ARABIC LETTER SAD FINAL FORM*/
+ RTUNI_ALPHA, /* U+00febb: ARABIC LETTER SAD INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00febc: ARABIC LETTER SAD MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00febd: ARABIC LETTER DAD ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00febe: ARABIC LETTER DAD FINAL FORM*/
+ RTUNI_ALPHA, /* U+00febf: ARABIC LETTER DAD INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fec0: ARABIC LETTER DAD MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fec1: ARABIC LETTER TAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fec2: ARABIC LETTER TAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fec3: ARABIC LETTER TAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fec4: ARABIC LETTER TAH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fec5: ARABIC LETTER ZAH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fec6: ARABIC LETTER ZAH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fec7: ARABIC LETTER ZAH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fec8: ARABIC LETTER ZAH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fec9: ARABIC LETTER AIN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feca: ARABIC LETTER AIN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fecb: ARABIC LETTER AIN INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fecc: ARABIC LETTER AIN MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fecd: ARABIC LETTER GHAIN ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fece: ARABIC LETTER GHAIN FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fecf: ARABIC LETTER GHAIN INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fed0: ARABIC LETTER GHAIN MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fed1: ARABIC LETTER FEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fed2: ARABIC LETTER FEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fed3: ARABIC LETTER FEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fed4: ARABIC LETTER FEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fed5: ARABIC LETTER QAF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fed6: ARABIC LETTER QAF FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fed7: ARABIC LETTER QAF INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fed8: ARABIC LETTER QAF MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fed9: ARABIC LETTER KAF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feda: ARABIC LETTER KAF FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fedb: ARABIC LETTER KAF INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fedc: ARABIC LETTER KAF MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fedd: ARABIC LETTER LAM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fede: ARABIC LETTER LAM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fedf: ARABIC LETTER LAM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fee0: ARABIC LETTER LAM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fee1: ARABIC LETTER MEEM ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fee2: ARABIC LETTER MEEM FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fee3: ARABIC LETTER MEEM INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fee4: ARABIC LETTER MEEM MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fee5: ARABIC LETTER NOON ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fee6: ARABIC LETTER NOON FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fee7: ARABIC LETTER NOON INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fee8: ARABIC LETTER NOON MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fee9: ARABIC LETTER HEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feea: ARABIC LETTER HEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00feeb: ARABIC LETTER HEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00feec: ARABIC LETTER HEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00feed: ARABIC LETTER WAW ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00feee: ARABIC LETTER WAW FINAL FORM*/
+ RTUNI_ALPHA, /* U+00feef: ARABIC LETTER ALEF MAKSURA ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fef0: ARABIC LETTER ALEF MAKSURA FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fef1: ARABIC LETTER YEH ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fef2: ARABIC LETTER YEH FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fef3: ARABIC LETTER YEH INITIAL FORM*/
+ RTUNI_ALPHA, /* U+00fef4: ARABIC LETTER YEH MEDIAL FORM*/
+ RTUNI_ALPHA, /* U+00fef5: ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fef6: ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fef7: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fef8: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fef9: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fefa: ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW FINAL FORM*/
+ RTUNI_ALPHA, /* U+00fefb: ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM*/
+ RTUNI_ALPHA, /* U+00fefc: ARABIC LIGATURE LAM WITH ALEF FINAL FORM*/
+ 0, /* U+00fefd: */
+ 0, /* U+00fefe: */
+ 0, /* U+00feff: ZERO WIDTH NO-BREAK SPACE*/
+ 0, /* U+00ff00: */
+ 0, /* U+00ff01: FULLWIDTH EXCLAMATION MARK*/
+ 0, /* U+00ff02: FULLWIDTH QUOTATION MARK*/
+ 0, /* U+00ff03: FULLWIDTH NUMBER SIGN*/
+ 0, /* U+00ff04: FULLWIDTH DOLLAR SIGN*/
+ 0, /* U+00ff05: FULLWIDTH PERCENT SIGN*/
+ 0, /* U+00ff06: FULLWIDTH AMPERSAND*/
+ 0, /* U+00ff07: FULLWIDTH APOSTROPHE*/
+ 0, /* U+00ff08: FULLWIDTH LEFT PARENTHESIS*/
+ 0, /* U+00ff09: FULLWIDTH RIGHT PARENTHESIS*/
+ 0, /* U+00ff0a: FULLWIDTH ASTERISK*/
+ 0, /* U+00ff0b: FULLWIDTH PLUS SIGN*/
+ 0, /* U+00ff0c: FULLWIDTH COMMA*/
+ 0, /* U+00ff0d: FULLWIDTH HYPHEN-MINUS*/
+ 0, /* U+00ff0e: FULLWIDTH FULL STOP*/
+ 0, /* U+00ff0f: FULLWIDTH SOLIDUS*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff10: FULLWIDTH DIGIT ZERO*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff11: FULLWIDTH DIGIT ONE*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff12: FULLWIDTH DIGIT TWO*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff13: FULLWIDTH DIGIT THREE*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff14: FULLWIDTH DIGIT FOUR*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff15: FULLWIDTH DIGIT FIVE*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff16: FULLWIDTH DIGIT SIX*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff17: FULLWIDTH DIGIT SEVEN*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff18: FULLWIDTH DIGIT EIGHT*/
+ RTUNI_XDIGIT | RTUNI_DDIGIT, /* U+00ff19: FULLWIDTH DIGIT NINE*/
+ 0, /* U+00ff1a: FULLWIDTH COLON*/
+ 0, /* U+00ff1b: FULLWIDTH SEMICOLON*/
+ 0, /* U+00ff1c: FULLWIDTH LESS-THAN SIGN*/
+ 0, /* U+00ff1d: FULLWIDTH EQUALS SIGN*/
+ 0, /* U+00ff1e: FULLWIDTH GREATER-THAN SIGN*/
+ 0, /* U+00ff1f: FULLWIDTH QUESTION MARK*/
+ 0, /* U+00ff20: FULLWIDTH COMMERCIAL AT*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+00ff21: FULLWIDTH LATIN CAPITAL LETTER A*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+00ff22: FULLWIDTH LATIN CAPITAL LETTER B*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+00ff23: FULLWIDTH LATIN CAPITAL LETTER C*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+00ff24: FULLWIDTH LATIN CAPITAL LETTER D*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+00ff25: FULLWIDTH LATIN CAPITAL LETTER E*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_UPPER, /* U+00ff26: FULLWIDTH LATIN CAPITAL LETTER F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff27: FULLWIDTH LATIN CAPITAL LETTER G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff28: FULLWIDTH LATIN CAPITAL LETTER H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff29: FULLWIDTH LATIN CAPITAL LETTER I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff2a: FULLWIDTH LATIN CAPITAL LETTER J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff2b: FULLWIDTH LATIN CAPITAL LETTER K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff2c: FULLWIDTH LATIN CAPITAL LETTER L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff2d: FULLWIDTH LATIN CAPITAL LETTER M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff2e: FULLWIDTH LATIN CAPITAL LETTER N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff2f: FULLWIDTH LATIN CAPITAL LETTER O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff30: FULLWIDTH LATIN CAPITAL LETTER P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff31: FULLWIDTH LATIN CAPITAL LETTER Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff32: FULLWIDTH LATIN CAPITAL LETTER R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff33: FULLWIDTH LATIN CAPITAL LETTER S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff34: FULLWIDTH LATIN CAPITAL LETTER T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff35: FULLWIDTH LATIN CAPITAL LETTER U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff36: FULLWIDTH LATIN CAPITAL LETTER V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff37: FULLWIDTH LATIN CAPITAL LETTER W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff38: FULLWIDTH LATIN CAPITAL LETTER X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff39: FULLWIDTH LATIN CAPITAL LETTER Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+00ff3a: FULLWIDTH LATIN CAPITAL LETTER Z*/
+ 0, /* U+00ff3b: FULLWIDTH LEFT SQUARE BRACKET*/
+ 0, /* U+00ff3c: FULLWIDTH REVERSE SOLIDUS*/
+ 0, /* U+00ff3d: FULLWIDTH RIGHT SQUARE BRACKET*/
+ 0, /* U+00ff3e: FULLWIDTH CIRCUMFLEX ACCENT*/
+ 0, /* U+00ff3f: FULLWIDTH LOW LINE*/
+ 0, /* U+00ff40: FULLWIDTH GRAVE ACCENT*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+00ff41: FULLWIDTH LATIN SMALL LETTER A*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+00ff42: FULLWIDTH LATIN SMALL LETTER B*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+00ff43: FULLWIDTH LATIN SMALL LETTER C*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+00ff44: FULLWIDTH LATIN SMALL LETTER D*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+00ff45: FULLWIDTH LATIN SMALL LETTER E*/
+ RTUNI_ALPHA | RTUNI_XDIGIT | RTUNI_LOWER, /* U+00ff46: FULLWIDTH LATIN SMALL LETTER F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff47: FULLWIDTH LATIN SMALL LETTER G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff48: FULLWIDTH LATIN SMALL LETTER H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff49: FULLWIDTH LATIN SMALL LETTER I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff4a: FULLWIDTH LATIN SMALL LETTER J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff4b: FULLWIDTH LATIN SMALL LETTER K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff4c: FULLWIDTH LATIN SMALL LETTER L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff4d: FULLWIDTH LATIN SMALL LETTER M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff4e: FULLWIDTH LATIN SMALL LETTER N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff4f: FULLWIDTH LATIN SMALL LETTER O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff50: FULLWIDTH LATIN SMALL LETTER P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff51: FULLWIDTH LATIN SMALL LETTER Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff52: FULLWIDTH LATIN SMALL LETTER R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff53: FULLWIDTH LATIN SMALL LETTER S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff54: FULLWIDTH LATIN SMALL LETTER T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff55: FULLWIDTH LATIN SMALL LETTER U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff56: FULLWIDTH LATIN SMALL LETTER V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff57: FULLWIDTH LATIN SMALL LETTER W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff58: FULLWIDTH LATIN SMALL LETTER X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff59: FULLWIDTH LATIN SMALL LETTER Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+00ff5a: FULLWIDTH LATIN SMALL LETTER Z*/
+ 0, /* U+00ff5b: FULLWIDTH LEFT CURLY BRACKET*/
+ 0, /* U+00ff5c: FULLWIDTH VERTICAL LINE*/
+ 0, /* U+00ff5d: FULLWIDTH RIGHT CURLY BRACKET*/
+ 0, /* U+00ff5e: FULLWIDTH TILDE*/
+ 0, /* U+00ff5f: FULLWIDTH LEFT WHITE PARENTHESIS*/
+ 0, /* U+00ff60: FULLWIDTH RIGHT WHITE PARENTHESIS*/
+ 0, /* U+00ff61: HALFWIDTH IDEOGRAPHIC FULL STOP*/
+ 0, /* U+00ff62: HALFWIDTH LEFT CORNER BRACKET*/
+ 0, /* U+00ff63: HALFWIDTH RIGHT CORNER BRACKET*/
+ 0, /* U+00ff64: HALFWIDTH IDEOGRAPHIC COMMA*/
+ 0, /* U+00ff65: HALFWIDTH KATAKANA MIDDLE DOT*/
+ RTUNI_ALPHA, /* U+00ff66: HALFWIDTH KATAKANA LETTER WO*/
+ RTUNI_ALPHA, /* U+00ff67: HALFWIDTH KATAKANA LETTER SMALL A*/
+ RTUNI_ALPHA, /* U+00ff68: HALFWIDTH KATAKANA LETTER SMALL I*/
+ RTUNI_ALPHA, /* U+00ff69: HALFWIDTH KATAKANA LETTER SMALL U*/
+ RTUNI_ALPHA, /* U+00ff6a: HALFWIDTH KATAKANA LETTER SMALL E*/
+ RTUNI_ALPHA, /* U+00ff6b: HALFWIDTH KATAKANA LETTER SMALL O*/
+ RTUNI_ALPHA, /* U+00ff6c: HALFWIDTH KATAKANA LETTER SMALL YA*/
+ RTUNI_ALPHA, /* U+00ff6d: HALFWIDTH KATAKANA LETTER SMALL YU*/
+ RTUNI_ALPHA, /* U+00ff6e: HALFWIDTH KATAKANA LETTER SMALL YO*/
+ RTUNI_ALPHA, /* U+00ff6f: HALFWIDTH KATAKANA LETTER SMALL TU*/
+ RTUNI_ALPHA, /* U+00ff70: HALFWIDTH KATAKANA-HIRAGANA PROLONGED SOUND MARK*/
+ RTUNI_ALPHA, /* U+00ff71: HALFWIDTH KATAKANA LETTER A*/
+ RTUNI_ALPHA, /* U+00ff72: HALFWIDTH KATAKANA LETTER I*/
+ RTUNI_ALPHA, /* U+00ff73: HALFWIDTH KATAKANA LETTER U*/
+ RTUNI_ALPHA, /* U+00ff74: HALFWIDTH KATAKANA LETTER E*/
+ RTUNI_ALPHA, /* U+00ff75: HALFWIDTH KATAKANA LETTER O*/
+ RTUNI_ALPHA, /* U+00ff76: HALFWIDTH KATAKANA LETTER KA*/
+ RTUNI_ALPHA, /* U+00ff77: HALFWIDTH KATAKANA LETTER KI*/
+ RTUNI_ALPHA, /* U+00ff78: HALFWIDTH KATAKANA LETTER KU*/
+ RTUNI_ALPHA, /* U+00ff79: HALFWIDTH KATAKANA LETTER KE*/
+ RTUNI_ALPHA, /* U+00ff7a: HALFWIDTH KATAKANA LETTER KO*/
+ RTUNI_ALPHA, /* U+00ff7b: HALFWIDTH KATAKANA LETTER SA*/
+ RTUNI_ALPHA, /* U+00ff7c: HALFWIDTH KATAKANA LETTER SI*/
+ RTUNI_ALPHA, /* U+00ff7d: HALFWIDTH KATAKANA LETTER SU*/
+ RTUNI_ALPHA, /* U+00ff7e: HALFWIDTH KATAKANA LETTER SE*/
+ RTUNI_ALPHA, /* U+00ff7f: HALFWIDTH KATAKANA LETTER SO*/
+ RTUNI_ALPHA, /* U+00ff80: HALFWIDTH KATAKANA LETTER TA*/
+ RTUNI_ALPHA, /* U+00ff81: HALFWIDTH KATAKANA LETTER TI*/
+ RTUNI_ALPHA, /* U+00ff82: HALFWIDTH KATAKANA LETTER TU*/
+ RTUNI_ALPHA, /* U+00ff83: HALFWIDTH KATAKANA LETTER TE*/
+ RTUNI_ALPHA, /* U+00ff84: HALFWIDTH KATAKANA LETTER TO*/
+ RTUNI_ALPHA, /* U+00ff85: HALFWIDTH KATAKANA LETTER NA*/
+ RTUNI_ALPHA, /* U+00ff86: HALFWIDTH KATAKANA LETTER NI*/
+ RTUNI_ALPHA, /* U+00ff87: HALFWIDTH KATAKANA LETTER NU*/
+ RTUNI_ALPHA, /* U+00ff88: HALFWIDTH KATAKANA LETTER NE*/
+ RTUNI_ALPHA, /* U+00ff89: HALFWIDTH KATAKANA LETTER NO*/
+ RTUNI_ALPHA, /* U+00ff8a: HALFWIDTH KATAKANA LETTER HA*/
+ RTUNI_ALPHA, /* U+00ff8b: HALFWIDTH KATAKANA LETTER HI*/
+ RTUNI_ALPHA, /* U+00ff8c: HALFWIDTH KATAKANA LETTER HU*/
+ RTUNI_ALPHA, /* U+00ff8d: HALFWIDTH KATAKANA LETTER HE*/
+ RTUNI_ALPHA, /* U+00ff8e: HALFWIDTH KATAKANA LETTER HO*/
+ RTUNI_ALPHA, /* U+00ff8f: HALFWIDTH KATAKANA LETTER MA*/
+ RTUNI_ALPHA, /* U+00ff90: HALFWIDTH KATAKANA LETTER MI*/
+ RTUNI_ALPHA, /* U+00ff91: HALFWIDTH KATAKANA LETTER MU*/
+ RTUNI_ALPHA, /* U+00ff92: HALFWIDTH KATAKANA LETTER ME*/
+ RTUNI_ALPHA, /* U+00ff93: HALFWIDTH KATAKANA LETTER MO*/
+ RTUNI_ALPHA, /* U+00ff94: HALFWIDTH KATAKANA LETTER YA*/
+ RTUNI_ALPHA, /* U+00ff95: HALFWIDTH KATAKANA LETTER YU*/
+ RTUNI_ALPHA, /* U+00ff96: HALFWIDTH KATAKANA LETTER YO*/
+ RTUNI_ALPHA, /* U+00ff97: HALFWIDTH KATAKANA LETTER RA*/
+ RTUNI_ALPHA, /* U+00ff98: HALFWIDTH KATAKANA LETTER RI*/
+ RTUNI_ALPHA, /* U+00ff99: HALFWIDTH KATAKANA LETTER RU*/
+ RTUNI_ALPHA, /* U+00ff9a: HALFWIDTH KATAKANA LETTER RE*/
+ RTUNI_ALPHA, /* U+00ff9b: HALFWIDTH KATAKANA LETTER RO*/
+ RTUNI_ALPHA, /* U+00ff9c: HALFWIDTH KATAKANA LETTER WA*/
+ RTUNI_ALPHA, /* U+00ff9d: HALFWIDTH KATAKANA LETTER N*/
+ RTUNI_ALPHA, /* U+00ff9e: HALFWIDTH KATAKANA VOICED SOUND MARK*/
+ RTUNI_ALPHA, /* U+00ff9f: HALFWIDTH KATAKANA SEMI-VOICED SOUND MARK*/
+ RTUNI_ALPHA, /* U+00ffa0: HALFWIDTH HANGUL FILLER*/
+ RTUNI_ALPHA, /* U+00ffa1: HALFWIDTH HANGUL LETTER KIYEOK*/
+ RTUNI_ALPHA, /* U+00ffa2: HALFWIDTH HANGUL LETTER SSANGKIYEOK*/
+ RTUNI_ALPHA, /* U+00ffa3: HALFWIDTH HANGUL LETTER KIYEOK-SIOS*/
+ RTUNI_ALPHA, /* U+00ffa4: HALFWIDTH HANGUL LETTER NIEUN*/
+ RTUNI_ALPHA, /* U+00ffa5: HALFWIDTH HANGUL LETTER NIEUN-CIEUC*/
+ RTUNI_ALPHA, /* U+00ffa6: HALFWIDTH HANGUL LETTER NIEUN-HIEUH*/
+ RTUNI_ALPHA, /* U+00ffa7: HALFWIDTH HANGUL LETTER TIKEUT*/
+ RTUNI_ALPHA, /* U+00ffa8: HALFWIDTH HANGUL LETTER SSANGTIKEUT*/
+ RTUNI_ALPHA, /* U+00ffa9: HALFWIDTH HANGUL LETTER RIEUL*/
+ RTUNI_ALPHA, /* U+00ffaa: HALFWIDTH HANGUL LETTER RIEUL-KIYEOK*/
+ RTUNI_ALPHA, /* U+00ffab: HALFWIDTH HANGUL LETTER RIEUL-MIEUM*/
+ RTUNI_ALPHA, /* U+00ffac: HALFWIDTH HANGUL LETTER RIEUL-PIEUP*/
+ RTUNI_ALPHA, /* U+00ffad: HALFWIDTH HANGUL LETTER RIEUL-SIOS*/
+ RTUNI_ALPHA, /* U+00ffae: HALFWIDTH HANGUL LETTER RIEUL-THIEUTH*/
+ RTUNI_ALPHA, /* U+00ffaf: HALFWIDTH HANGUL LETTER RIEUL-PHIEUPH*/
+ RTUNI_ALPHA, /* U+00ffb0: HALFWIDTH HANGUL LETTER RIEUL-HIEUH*/
+ RTUNI_ALPHA, /* U+00ffb1: HALFWIDTH HANGUL LETTER MIEUM*/
+ RTUNI_ALPHA, /* U+00ffb2: HALFWIDTH HANGUL LETTER PIEUP*/
+ RTUNI_ALPHA, /* U+00ffb3: HALFWIDTH HANGUL LETTER SSANGPIEUP*/
+ RTUNI_ALPHA, /* U+00ffb4: HALFWIDTH HANGUL LETTER PIEUP-SIOS*/
+ RTUNI_ALPHA, /* U+00ffb5: HALFWIDTH HANGUL LETTER SIOS*/
+ RTUNI_ALPHA, /* U+00ffb6: HALFWIDTH HANGUL LETTER SSANGSIOS*/
+ RTUNI_ALPHA, /* U+00ffb7: HALFWIDTH HANGUL LETTER IEUNG*/
+ RTUNI_ALPHA, /* U+00ffb8: HALFWIDTH HANGUL LETTER CIEUC*/
+ RTUNI_ALPHA, /* U+00ffb9: HALFWIDTH HANGUL LETTER SSANGCIEUC*/
+ RTUNI_ALPHA, /* U+00ffba: HALFWIDTH HANGUL LETTER CHIEUCH*/
+ RTUNI_ALPHA, /* U+00ffbb: HALFWIDTH HANGUL LETTER KHIEUKH*/
+ RTUNI_ALPHA, /* U+00ffbc: HALFWIDTH HANGUL LETTER THIEUTH*/
+ RTUNI_ALPHA, /* U+00ffbd: HALFWIDTH HANGUL LETTER PHIEUPH*/
+ RTUNI_ALPHA, /* U+00ffbe: HALFWIDTH HANGUL LETTER HIEUH*/
+ 0, /* U+00ffbf: */
+ 0, /* U+00ffc0: */
+ 0, /* U+00ffc1: */
+ RTUNI_ALPHA, /* U+00ffc2: HALFWIDTH HANGUL LETTER A*/
+ RTUNI_ALPHA, /* U+00ffc3: HALFWIDTH HANGUL LETTER AE*/
+ RTUNI_ALPHA, /* U+00ffc4: HALFWIDTH HANGUL LETTER YA*/
+ RTUNI_ALPHA, /* U+00ffc5: HALFWIDTH HANGUL LETTER YAE*/
+ RTUNI_ALPHA, /* U+00ffc6: HALFWIDTH HANGUL LETTER EO*/
+ RTUNI_ALPHA, /* U+00ffc7: HALFWIDTH HANGUL LETTER E*/
+ 0, /* U+00ffc8: */
+ 0, /* U+00ffc9: */
+ RTUNI_ALPHA, /* U+00ffca: HALFWIDTH HANGUL LETTER YEO*/
+ RTUNI_ALPHA, /* U+00ffcb: HALFWIDTH HANGUL LETTER YE*/
+ RTUNI_ALPHA, /* U+00ffcc: HALFWIDTH HANGUL LETTER O*/
+ RTUNI_ALPHA, /* U+00ffcd: HALFWIDTH HANGUL LETTER WA*/
+ RTUNI_ALPHA, /* U+00ffce: HALFWIDTH HANGUL LETTER WAE*/
+ RTUNI_ALPHA, /* U+00ffcf: HALFWIDTH HANGUL LETTER OE*/
+ 0, /* U+00ffd0: */
+ 0, /* U+00ffd1: */
+ RTUNI_ALPHA, /* U+00ffd2: HALFWIDTH HANGUL LETTER YO*/
+ RTUNI_ALPHA, /* U+00ffd3: HALFWIDTH HANGUL LETTER U*/
+ RTUNI_ALPHA, /* U+00ffd4: HALFWIDTH HANGUL LETTER WEO*/
+ RTUNI_ALPHA, /* U+00ffd5: HALFWIDTH HANGUL LETTER WE*/
+ RTUNI_ALPHA, /* U+00ffd6: HALFWIDTH HANGUL LETTER WI*/
+ RTUNI_ALPHA, /* U+00ffd7: HALFWIDTH HANGUL LETTER YU*/
+ 0, /* U+00ffd8: */
+ 0, /* U+00ffd9: */
+ RTUNI_ALPHA, /* U+00ffda: HALFWIDTH HANGUL LETTER EU*/
+ RTUNI_ALPHA, /* U+00ffdb: HALFWIDTH HANGUL LETTER YI*/
+ RTUNI_ALPHA, /* U+00ffdc: HALFWIDTH HANGUL LETTER I*/
+ 0, /* U+00ffdd: */
+ 0, /* U+00ffde: */
+ 0, /* U+00ffdf: */
+ 0, /* U+00ffe0: FULLWIDTH CENT SIGN*/
+ 0, /* U+00ffe1: FULLWIDTH POUND SIGN*/
+ 0, /* U+00ffe2: FULLWIDTH NOT SIGN*/
+ 0, /* U+00ffe3: FULLWIDTH MACRON*/
+ 0, /* U+00ffe4: FULLWIDTH BROKEN BAR*/
+ 0, /* U+00ffe5: FULLWIDTH YEN SIGN*/
+ 0, /* U+00ffe6: FULLWIDTH WON SIGN*/
+ 0, /* U+00ffe7: */
+ 0, /* U+00ffe8: HALFWIDTH FORMS LIGHT VERTICAL*/
+ 0, /* U+00ffe9: HALFWIDTH LEFTWARDS ARROW*/
+ 0, /* U+00ffea: HALFWIDTH UPWARDS ARROW*/
+ 0, /* U+00ffeb: HALFWIDTH RIGHTWARDS ARROW*/
+ 0, /* U+00ffec: HALFWIDTH DOWNWARDS ARROW*/
+ 0, /* U+00ffed: HALFWIDTH BLACK SQUARE*/
+ 0, /* U+00ffee: HALFWIDTH WHITE CIRCLE*/
+ 0, /* U+00ffef: */
+ 0, /* U+00fff0: */
+ 0, /* U+00fff1: */
+ 0, /* U+00fff2: */
+ 0, /* U+00fff3: */
+ 0, /* U+00fff4: */
+ 0, /* U+00fff5: */
+ 0, /* U+00fff6: */
+ 0, /* U+00fff7: */
+ 0, /* U+00fff8: */
+ 0, /* U+00fff9: INTERLINEAR ANNOTATION ANCHOR*/
+ 0, /* U+00fffa: INTERLINEAR ANNOTATION SEPARATOR*/
+ 0, /* U+00fffb: INTERLINEAR ANNOTATION TERMINATOR*/
+ 0, /* U+00fffc: OBJECT REPLACEMENT CHARACTER*/
+ 0, /* U+00fffd: REPLACEMENT CHARACTER*/
+ 0, /* U+00fffe: */
+ 0, /* U+00ffff: */
+ RTUNI_ALPHA, /* U+010000: LINEAR B SYLLABLE B008 A*/
+ RTUNI_ALPHA, /* U+010001: LINEAR B SYLLABLE B038 E*/
+ RTUNI_ALPHA, /* U+010002: LINEAR B SYLLABLE B028 I*/
+ RTUNI_ALPHA, /* U+010003: LINEAR B SYLLABLE B061 O*/
+ RTUNI_ALPHA, /* U+010004: LINEAR B SYLLABLE B010 U*/
+ RTUNI_ALPHA, /* U+010005: LINEAR B SYLLABLE B001 DA*/
+ RTUNI_ALPHA, /* U+010006: LINEAR B SYLLABLE B045 DE*/
+ RTUNI_ALPHA, /* U+010007: LINEAR B SYLLABLE B007 DI*/
+ RTUNI_ALPHA, /* U+010008: LINEAR B SYLLABLE B014 DO*/
+ RTUNI_ALPHA, /* U+010009: LINEAR B SYLLABLE B051 DU*/
+ RTUNI_ALPHA, /* U+01000a: LINEAR B SYLLABLE B057 JA*/
+ RTUNI_ALPHA, /* U+01000b: LINEAR B SYLLABLE B046 JE*/
+ 0, /* U+01000c: */
+ RTUNI_ALPHA, /* U+01000d: LINEAR B SYLLABLE B036 JO*/
+ RTUNI_ALPHA, /* U+01000e: LINEAR B SYLLABLE B065 JU*/
+ RTUNI_ALPHA, /* U+01000f: LINEAR B SYLLABLE B077 KA*/
+ RTUNI_ALPHA, /* U+010010: LINEAR B SYLLABLE B044 KE*/
+ RTUNI_ALPHA, /* U+010011: LINEAR B SYLLABLE B067 KI*/
+ RTUNI_ALPHA, /* U+010012: LINEAR B SYLLABLE B070 KO*/
+ RTUNI_ALPHA, /* U+010013: LINEAR B SYLLABLE B081 KU*/
+ RTUNI_ALPHA, /* U+010014: LINEAR B SYLLABLE B080 MA*/
+ RTUNI_ALPHA, /* U+010015: LINEAR B SYLLABLE B013 ME*/
+ RTUNI_ALPHA, /* U+010016: LINEAR B SYLLABLE B073 MI*/
+ RTUNI_ALPHA, /* U+010017: LINEAR B SYLLABLE B015 MO*/
+ RTUNI_ALPHA, /* U+010018: LINEAR B SYLLABLE B023 MU*/
+ RTUNI_ALPHA, /* U+010019: LINEAR B SYLLABLE B006 NA*/
+ RTUNI_ALPHA, /* U+01001a: LINEAR B SYLLABLE B024 NE*/
+ RTUNI_ALPHA, /* U+01001b: LINEAR B SYLLABLE B030 NI*/
+ RTUNI_ALPHA, /* U+01001c: LINEAR B SYLLABLE B052 NO*/
+ RTUNI_ALPHA, /* U+01001d: LINEAR B SYLLABLE B055 NU*/
+ RTUNI_ALPHA, /* U+01001e: LINEAR B SYLLABLE B003 PA*/
+ RTUNI_ALPHA, /* U+01001f: LINEAR B SYLLABLE B072 PE*/
+ RTUNI_ALPHA, /* U+010020: LINEAR B SYLLABLE B039 PI*/
+ RTUNI_ALPHA, /* U+010021: LINEAR B SYLLABLE B011 PO*/
+ RTUNI_ALPHA, /* U+010022: LINEAR B SYLLABLE B050 PU*/
+ RTUNI_ALPHA, /* U+010023: LINEAR B SYLLABLE B016 QA*/
+ RTUNI_ALPHA, /* U+010024: LINEAR B SYLLABLE B078 QE*/
+ RTUNI_ALPHA, /* U+010025: LINEAR B SYLLABLE B021 QI*/
+ RTUNI_ALPHA, /* U+010026: LINEAR B SYLLABLE B032 QO*/
+ 0, /* U+010027: */
+ RTUNI_ALPHA, /* U+010028: LINEAR B SYLLABLE B060 RA*/
+ RTUNI_ALPHA, /* U+010029: LINEAR B SYLLABLE B027 RE*/
+ RTUNI_ALPHA, /* U+01002a: LINEAR B SYLLABLE B053 RI*/
+ RTUNI_ALPHA, /* U+01002b: LINEAR B SYLLABLE B002 RO*/
+ RTUNI_ALPHA, /* U+01002c: LINEAR B SYLLABLE B026 RU*/
+ RTUNI_ALPHA, /* U+01002d: LINEAR B SYLLABLE B031 SA*/
+ RTUNI_ALPHA, /* U+01002e: LINEAR B SYLLABLE B009 SE*/
+ RTUNI_ALPHA, /* U+01002f: LINEAR B SYLLABLE B041 SI*/
+ RTUNI_ALPHA, /* U+010030: LINEAR B SYLLABLE B012 SO*/
+ RTUNI_ALPHA, /* U+010031: LINEAR B SYLLABLE B058 SU*/
+ RTUNI_ALPHA, /* U+010032: LINEAR B SYLLABLE B059 TA*/
+ RTUNI_ALPHA, /* U+010033: LINEAR B SYLLABLE B004 TE*/
+ RTUNI_ALPHA, /* U+010034: LINEAR B SYLLABLE B037 TI*/
+ RTUNI_ALPHA, /* U+010035: LINEAR B SYLLABLE B005 TO*/
+ RTUNI_ALPHA, /* U+010036: LINEAR B SYLLABLE B069 TU*/
+ RTUNI_ALPHA, /* U+010037: LINEAR B SYLLABLE B054 WA*/
+ RTUNI_ALPHA, /* U+010038: LINEAR B SYLLABLE B075 WE*/
+ RTUNI_ALPHA, /* U+010039: LINEAR B SYLLABLE B040 WI*/
+ RTUNI_ALPHA, /* U+01003a: LINEAR B SYLLABLE B042 WO*/
+ 0, /* U+01003b: */
+ RTUNI_ALPHA, /* U+01003c: LINEAR B SYLLABLE B017 ZA*/
+ RTUNI_ALPHA, /* U+01003d: LINEAR B SYLLABLE B074 ZE*/
+ 0, /* U+01003e: */
+ RTUNI_ALPHA, /* U+01003f: LINEAR B SYLLABLE B020 ZO*/
+ RTUNI_ALPHA, /* U+010040: LINEAR B SYLLABLE B025 A2*/
+ RTUNI_ALPHA, /* U+010041: LINEAR B SYLLABLE B043 A3*/
+ RTUNI_ALPHA, /* U+010042: LINEAR B SYLLABLE B085 AU*/
+ RTUNI_ALPHA, /* U+010043: LINEAR B SYLLABLE B071 DWE*/
+ RTUNI_ALPHA, /* U+010044: LINEAR B SYLLABLE B090 DWO*/
+ RTUNI_ALPHA, /* U+010045: LINEAR B SYLLABLE B048 NWA*/
+ RTUNI_ALPHA, /* U+010046: LINEAR B SYLLABLE B029 PU2*/
+ RTUNI_ALPHA, /* U+010047: LINEAR B SYLLABLE B062 PTE*/
+ RTUNI_ALPHA, /* U+010048: LINEAR B SYLLABLE B076 RA2*/
+ RTUNI_ALPHA, /* U+010049: LINEAR B SYLLABLE B033 RA3*/
+ RTUNI_ALPHA, /* U+01004a: LINEAR B SYLLABLE B068 RO2*/
+ RTUNI_ALPHA, /* U+01004b: LINEAR B SYLLABLE B066 TA2*/
+ RTUNI_ALPHA, /* U+01004c: LINEAR B SYLLABLE B087 TWE*/
+ RTUNI_ALPHA, /* U+01004d: LINEAR B SYLLABLE B091 TWO*/
+ 0, /* U+01004e: */
+ 0, /* U+01004f: */
+ RTUNI_ALPHA, /* U+010050: LINEAR B SYMBOL B018*/
+ RTUNI_ALPHA, /* U+010051: LINEAR B SYMBOL B019*/
+ RTUNI_ALPHA, /* U+010052: LINEAR B SYMBOL B022*/
+ RTUNI_ALPHA, /* U+010053: LINEAR B SYMBOL B034*/
+ RTUNI_ALPHA, /* U+010054: LINEAR B SYMBOL B047*/
+ RTUNI_ALPHA, /* U+010055: LINEAR B SYMBOL B049*/
+ RTUNI_ALPHA, /* U+010056: LINEAR B SYMBOL B056*/
+ RTUNI_ALPHA, /* U+010057: LINEAR B SYMBOL B063*/
+ RTUNI_ALPHA, /* U+010058: LINEAR B SYMBOL B064*/
+ RTUNI_ALPHA, /* U+010059: LINEAR B SYMBOL B079*/
+ RTUNI_ALPHA, /* U+01005a: LINEAR B SYMBOL B082*/
+ RTUNI_ALPHA, /* U+01005b: LINEAR B SYMBOL B083*/
+ RTUNI_ALPHA, /* U+01005c: LINEAR B SYMBOL B086*/
+ RTUNI_ALPHA, /* U+01005d: LINEAR B SYMBOL B089*/
+ 0, /* U+01005e: */
+ 0, /* U+01005f: */
+ 0, /* U+010060: */
+ 0, /* U+010061: */
+ 0, /* U+010062: */
+ 0, /* U+010063: */
+ 0, /* U+010064: */
+ 0, /* U+010065: */
+ 0, /* U+010066: */
+ 0, /* U+010067: */
+ 0, /* U+010068: */
+ 0, /* U+010069: */
+ 0, /* U+01006a: */
+ 0, /* U+01006b: */
+ 0, /* U+01006c: */
+ 0, /* U+01006d: */
+ 0, /* U+01006e: */
+ 0, /* U+01006f: */
+ 0, /* U+010070: */
+ 0, /* U+010071: */
+ 0, /* U+010072: */
+ 0, /* U+010073: */
+ 0, /* U+010074: */
+ 0, /* U+010075: */
+ 0, /* U+010076: */
+ 0, /* U+010077: */
+ 0, /* U+010078: */
+ 0, /* U+010079: */
+ 0, /* U+01007a: */
+ 0, /* U+01007b: */
+ 0, /* U+01007c: */
+ 0, /* U+01007d: */
+ 0, /* U+01007e: */
+ 0, /* U+01007f: */
+ RTUNI_ALPHA, /* U+010080: LINEAR B IDEOGRAM B100 MAN*/
+ RTUNI_ALPHA, /* U+010081: LINEAR B IDEOGRAM B102 WOMAN*/
+ RTUNI_ALPHA, /* U+010082: LINEAR B IDEOGRAM B104 DEER*/
+ RTUNI_ALPHA, /* U+010083: LINEAR B IDEOGRAM B105 EQUID*/
+ RTUNI_ALPHA, /* U+010084: LINEAR B IDEOGRAM B105F MARE*/
+ RTUNI_ALPHA, /* U+010085: LINEAR B IDEOGRAM B105M STALLION*/
+ RTUNI_ALPHA, /* U+010086: LINEAR B IDEOGRAM B106F EWE*/
+ RTUNI_ALPHA, /* U+010087: LINEAR B IDEOGRAM B106M RAM*/
+ RTUNI_ALPHA, /* U+010088: LINEAR B IDEOGRAM B107F SHE-GOAT*/
+ RTUNI_ALPHA, /* U+010089: LINEAR B IDEOGRAM B107M HE-GOAT*/
+ RTUNI_ALPHA, /* U+01008a: LINEAR B IDEOGRAM B108F SOW*/
+ RTUNI_ALPHA, /* U+01008b: LINEAR B IDEOGRAM B108M BOAR*/
+ RTUNI_ALPHA, /* U+01008c: LINEAR B IDEOGRAM B109F COW*/
+ RTUNI_ALPHA, /* U+01008d: LINEAR B IDEOGRAM B109M BULL*/
+ RTUNI_ALPHA, /* U+01008e: LINEAR B IDEOGRAM B120 WHEAT*/
+ RTUNI_ALPHA, /* U+01008f: LINEAR B IDEOGRAM B121 BARLEY*/
+ RTUNI_ALPHA, /* U+010090: LINEAR B IDEOGRAM B122 OLIVE*/
+ RTUNI_ALPHA, /* U+010091: LINEAR B IDEOGRAM B123 SPICE*/
+ RTUNI_ALPHA, /* U+010092: LINEAR B IDEOGRAM B125 CYPERUS*/
+ RTUNI_ALPHA, /* U+010093: LINEAR B MONOGRAM B127 KAPO*/
+ RTUNI_ALPHA, /* U+010094: LINEAR B MONOGRAM B128 KANAKO*/
+ RTUNI_ALPHA, /* U+010095: LINEAR B IDEOGRAM B130 OIL*/
+ RTUNI_ALPHA, /* U+010096: LINEAR B IDEOGRAM B131 WINE*/
+ RTUNI_ALPHA, /* U+010097: LINEAR B IDEOGRAM B132*/
+ RTUNI_ALPHA, /* U+010098: LINEAR B MONOGRAM B133 AREPA*/
+ RTUNI_ALPHA, /* U+010099: LINEAR B MONOGRAM B135 MERI*/
+ RTUNI_ALPHA, /* U+01009a: LINEAR B IDEOGRAM B140 BRONZE*/
+ RTUNI_ALPHA, /* U+01009b: LINEAR B IDEOGRAM B141 GOLD*/
+ RTUNI_ALPHA, /* U+01009c: LINEAR B IDEOGRAM B142*/
+ RTUNI_ALPHA, /* U+01009d: LINEAR B IDEOGRAM B145 WOOL*/
+ RTUNI_ALPHA, /* U+01009e: LINEAR B IDEOGRAM B146*/
+ RTUNI_ALPHA, /* U+01009f: LINEAR B IDEOGRAM B150*/
+ RTUNI_ALPHA, /* U+0100a0: LINEAR B IDEOGRAM B151 HORN*/
+ RTUNI_ALPHA, /* U+0100a1: LINEAR B IDEOGRAM B152*/
+ RTUNI_ALPHA, /* U+0100a2: LINEAR B IDEOGRAM B153*/
+ RTUNI_ALPHA, /* U+0100a3: LINEAR B IDEOGRAM B154*/
+ RTUNI_ALPHA, /* U+0100a4: LINEAR B MONOGRAM B156 TURO2*/
+ RTUNI_ALPHA, /* U+0100a5: LINEAR B IDEOGRAM B157*/
+ RTUNI_ALPHA, /* U+0100a6: LINEAR B IDEOGRAM B158*/
+ RTUNI_ALPHA, /* U+0100a7: LINEAR B IDEOGRAM B159 CLOTH*/
+ RTUNI_ALPHA, /* U+0100a8: LINEAR B IDEOGRAM B160*/
+ RTUNI_ALPHA, /* U+0100a9: LINEAR B IDEOGRAM B161*/
+ RTUNI_ALPHA, /* U+0100aa: LINEAR B IDEOGRAM B162 GARMENT*/
+ RTUNI_ALPHA, /* U+0100ab: LINEAR B IDEOGRAM B163 ARMOUR*/
+ RTUNI_ALPHA, /* U+0100ac: LINEAR B IDEOGRAM B164*/
+ RTUNI_ALPHA, /* U+0100ad: LINEAR B IDEOGRAM B165*/
+ RTUNI_ALPHA, /* U+0100ae: LINEAR B IDEOGRAM B166*/
+ RTUNI_ALPHA, /* U+0100af: LINEAR B IDEOGRAM B167*/
+ RTUNI_ALPHA, /* U+0100b0: LINEAR B IDEOGRAM B168*/
+ RTUNI_ALPHA, /* U+0100b1: LINEAR B IDEOGRAM B169*/
+ RTUNI_ALPHA, /* U+0100b2: LINEAR B IDEOGRAM B170*/
+ RTUNI_ALPHA, /* U+0100b3: LINEAR B IDEOGRAM B171*/
+ RTUNI_ALPHA, /* U+0100b4: LINEAR B IDEOGRAM B172*/
+ RTUNI_ALPHA, /* U+0100b5: LINEAR B IDEOGRAM B173 MONTH*/
+ RTUNI_ALPHA, /* U+0100b6: LINEAR B IDEOGRAM B174*/
+ RTUNI_ALPHA, /* U+0100b7: LINEAR B IDEOGRAM B176 TREE*/
+ RTUNI_ALPHA, /* U+0100b8: LINEAR B IDEOGRAM B177*/
+ RTUNI_ALPHA, /* U+0100b9: LINEAR B IDEOGRAM B178*/
+ RTUNI_ALPHA, /* U+0100ba: LINEAR B IDEOGRAM B179*/
+ RTUNI_ALPHA, /* U+0100bb: LINEAR B IDEOGRAM B180*/
+ RTUNI_ALPHA, /* U+0100bc: LINEAR B IDEOGRAM B181*/
+ RTUNI_ALPHA, /* U+0100bd: LINEAR B IDEOGRAM B182*/
+ RTUNI_ALPHA, /* U+0100be: LINEAR B IDEOGRAM B183*/
+ RTUNI_ALPHA, /* U+0100bf: LINEAR B IDEOGRAM B184*/
+ RTUNI_ALPHA, /* U+0100c0: LINEAR B IDEOGRAM B185*/
+ RTUNI_ALPHA, /* U+0100c1: LINEAR B IDEOGRAM B189*/
+ RTUNI_ALPHA, /* U+0100c2: LINEAR B IDEOGRAM B190*/
+ RTUNI_ALPHA, /* U+0100c3: LINEAR B IDEOGRAM B191 HELMET*/
+ RTUNI_ALPHA, /* U+0100c4: LINEAR B IDEOGRAM B220 FOOTSTOOL*/
+ RTUNI_ALPHA, /* U+0100c5: LINEAR B IDEOGRAM B225 BATHTUB*/
+ RTUNI_ALPHA, /* U+0100c6: LINEAR B IDEOGRAM B230 SPEAR*/
+ RTUNI_ALPHA, /* U+0100c7: LINEAR B IDEOGRAM B231 ARROW*/
+ RTUNI_ALPHA, /* U+0100c8: LINEAR B IDEOGRAM B232*/
+ RTUNI_ALPHA, /* U+0100c9: LINEAR B IDEOGRAM B233 SWORD*/
+ RTUNI_ALPHA, /* U+0100ca: LINEAR B IDEOGRAM B234*/
+ RTUNI_ALPHA, /* U+0100cb: LINEAR B IDEOGRAM B236*/
+ RTUNI_ALPHA, /* U+0100cc: LINEAR B IDEOGRAM B240 WHEELED CHARIOT*/
+ RTUNI_ALPHA, /* U+0100cd: LINEAR B IDEOGRAM B241 CHARIOT*/
+ RTUNI_ALPHA, /* U+0100ce: LINEAR B IDEOGRAM B242 CHARIOT FRAME*/
+ RTUNI_ALPHA, /* U+0100cf: LINEAR B IDEOGRAM B243 WHEEL*/
+ RTUNI_ALPHA, /* U+0100d0: LINEAR B IDEOGRAM B245*/
+ RTUNI_ALPHA, /* U+0100d1: LINEAR B IDEOGRAM B246*/
+ RTUNI_ALPHA, /* U+0100d2: LINEAR B MONOGRAM B247 DIPTE*/
+ RTUNI_ALPHA, /* U+0100d3: LINEAR B IDEOGRAM B248*/
+ RTUNI_ALPHA, /* U+0100d4: LINEAR B IDEOGRAM B249*/
+ RTUNI_ALPHA, /* U+0100d5: LINEAR B IDEOGRAM B251*/
+ RTUNI_ALPHA, /* U+0100d6: LINEAR B IDEOGRAM B252*/
+ RTUNI_ALPHA, /* U+0100d7: LINEAR B IDEOGRAM B253*/
+ RTUNI_ALPHA, /* U+0100d8: LINEAR B IDEOGRAM B254 DART*/
+ RTUNI_ALPHA, /* U+0100d9: LINEAR B IDEOGRAM B255*/
+ RTUNI_ALPHA, /* U+0100da: LINEAR B IDEOGRAM B256*/
+ RTUNI_ALPHA, /* U+0100db: LINEAR B IDEOGRAM B257*/
+ RTUNI_ALPHA, /* U+0100dc: LINEAR B IDEOGRAM B258*/
+ RTUNI_ALPHA, /* U+0100dd: LINEAR B IDEOGRAM B259*/
+ RTUNI_ALPHA, /* U+0100de: LINEAR B IDEOGRAM VESSEL B155*/
+ RTUNI_ALPHA, /* U+0100df: LINEAR B IDEOGRAM VESSEL B200*/
+ RTUNI_ALPHA, /* U+0100e0: LINEAR B IDEOGRAM VESSEL B201*/
+ RTUNI_ALPHA, /* U+0100e1: LINEAR B IDEOGRAM VESSEL B202*/
+ RTUNI_ALPHA, /* U+0100e2: LINEAR B IDEOGRAM VESSEL B203*/
+ RTUNI_ALPHA, /* U+0100e3: LINEAR B IDEOGRAM VESSEL B204*/
+ RTUNI_ALPHA, /* U+0100e4: LINEAR B IDEOGRAM VESSEL B205*/
+ RTUNI_ALPHA, /* U+0100e5: LINEAR B IDEOGRAM VESSEL B206*/
+ RTUNI_ALPHA, /* U+0100e6: LINEAR B IDEOGRAM VESSEL B207*/
+ RTUNI_ALPHA, /* U+0100e7: LINEAR B IDEOGRAM VESSEL B208*/
+ RTUNI_ALPHA, /* U+0100e8: LINEAR B IDEOGRAM VESSEL B209*/
+ RTUNI_ALPHA, /* U+0100e9: LINEAR B IDEOGRAM VESSEL B210*/
+ RTUNI_ALPHA, /* U+0100ea: LINEAR B IDEOGRAM VESSEL B211*/
+ RTUNI_ALPHA, /* U+0100eb: LINEAR B IDEOGRAM VESSEL B212*/
+ RTUNI_ALPHA, /* U+0100ec: LINEAR B IDEOGRAM VESSEL B213*/
+ RTUNI_ALPHA, /* U+0100ed: LINEAR B IDEOGRAM VESSEL B214*/
+ RTUNI_ALPHA, /* U+0100ee: LINEAR B IDEOGRAM VESSEL B215*/
+ RTUNI_ALPHA, /* U+0100ef: LINEAR B IDEOGRAM VESSEL B216*/
+ RTUNI_ALPHA, /* U+0100f0: LINEAR B IDEOGRAM VESSEL B217*/
+ RTUNI_ALPHA, /* U+0100f1: LINEAR B IDEOGRAM VESSEL B218*/
+ RTUNI_ALPHA, /* U+0100f2: LINEAR B IDEOGRAM VESSEL B219*/
+ RTUNI_ALPHA, /* U+0100f3: LINEAR B IDEOGRAM VESSEL B221*/
+ RTUNI_ALPHA, /* U+0100f4: LINEAR B IDEOGRAM VESSEL B222*/
+ RTUNI_ALPHA, /* U+0100f5: LINEAR B IDEOGRAM VESSEL B226*/
+ RTUNI_ALPHA, /* U+0100f6: LINEAR B IDEOGRAM VESSEL B227*/
+ RTUNI_ALPHA, /* U+0100f7: LINEAR B IDEOGRAM VESSEL B228*/
+ RTUNI_ALPHA, /* U+0100f8: LINEAR B IDEOGRAM VESSEL B229*/
+ RTUNI_ALPHA, /* U+0100f9: LINEAR B IDEOGRAM VESSEL B250*/
+ RTUNI_ALPHA, /* U+0100fa: LINEAR B IDEOGRAM VESSEL B305*/
+ 0, /* U+0100fb: */
+ 0, /* U+0100fc: */
+ 0, /* U+0100fd: */
+ 0, /* U+0100fe: */
+ 0, /* U+0100ff: */
+ 0, /* U+010100: AEGEAN WORD SEPARATOR LINE*/
+ 0, /* U+010101: AEGEAN WORD SEPARATOR DOT*/
+ 0, /* U+010102: AEGEAN CHECK MARK*/
+ 0, /* U+010103: */
+ 0, /* U+010104: */
+ 0, /* U+010105: */
+ 0, /* U+010106: */
+ 0, /* U+010107: AEGEAN NUMBER ONE*/
+ 0, /* U+010108: AEGEAN NUMBER TWO*/
+ 0, /* U+010109: AEGEAN NUMBER THREE*/
+ 0, /* U+01010a: AEGEAN NUMBER FOUR*/
+ 0, /* U+01010b: AEGEAN NUMBER FIVE*/
+ 0, /* U+01010c: AEGEAN NUMBER SIX*/
+ 0, /* U+01010d: AEGEAN NUMBER SEVEN*/
+ 0, /* U+01010e: AEGEAN NUMBER EIGHT*/
+ 0, /* U+01010f: AEGEAN NUMBER NINE*/
+ 0, /* U+010110: AEGEAN NUMBER TEN*/
+ 0, /* U+010111: AEGEAN NUMBER TWENTY*/
+ 0, /* U+010112: AEGEAN NUMBER THIRTY*/
+ 0, /* U+010113: AEGEAN NUMBER FORTY*/
+ 0, /* U+010114: AEGEAN NUMBER FIFTY*/
+ 0, /* U+010115: AEGEAN NUMBER SIXTY*/
+ 0, /* U+010116: AEGEAN NUMBER SEVENTY*/
+ 0, /* U+010117: AEGEAN NUMBER EIGHTY*/
+ 0, /* U+010118: AEGEAN NUMBER NINETY*/
+ 0, /* U+010119: AEGEAN NUMBER ONE HUNDRED*/
+ 0, /* U+01011a: AEGEAN NUMBER TWO HUNDRED*/
+ 0, /* U+01011b: AEGEAN NUMBER THREE HUNDRED*/
+ 0, /* U+01011c: AEGEAN NUMBER FOUR HUNDRED*/
+ 0, /* U+01011d: AEGEAN NUMBER FIVE HUNDRED*/
+ 0, /* U+01011e: AEGEAN NUMBER SIX HUNDRED*/
+ 0, /* U+01011f: AEGEAN NUMBER SEVEN HUNDRED*/
+ 0, /* U+010120: AEGEAN NUMBER EIGHT HUNDRED*/
+ 0, /* U+010121: AEGEAN NUMBER NINE HUNDRED*/
+ 0, /* U+010122: AEGEAN NUMBER ONE THOUSAND*/
+ 0, /* U+010123: AEGEAN NUMBER TWO THOUSAND*/
+ 0, /* U+010124: AEGEAN NUMBER THREE THOUSAND*/
+ 0, /* U+010125: AEGEAN NUMBER FOUR THOUSAND*/
+ 0, /* U+010126: AEGEAN NUMBER FIVE THOUSAND*/
+ 0, /* U+010127: AEGEAN NUMBER SIX THOUSAND*/
+ 0, /* U+010128: AEGEAN NUMBER SEVEN THOUSAND*/
+ 0, /* U+010129: AEGEAN NUMBER EIGHT THOUSAND*/
+ 0, /* U+01012a: AEGEAN NUMBER NINE THOUSAND*/
+ 0, /* U+01012b: AEGEAN NUMBER TEN THOUSAND*/
+ 0, /* U+01012c: AEGEAN NUMBER TWENTY THOUSAND*/
+ 0, /* U+01012d: AEGEAN NUMBER THIRTY THOUSAND*/
+ 0, /* U+01012e: AEGEAN NUMBER FORTY THOUSAND*/
+ 0, /* U+01012f: AEGEAN NUMBER FIFTY THOUSAND*/
+ 0, /* U+010130: AEGEAN NUMBER SIXTY THOUSAND*/
+ 0, /* U+010131: AEGEAN NUMBER SEVENTY THOUSAND*/
+ 0, /* U+010132: AEGEAN NUMBER EIGHTY THOUSAND*/
+ 0, /* U+010133: AEGEAN NUMBER NINETY THOUSAND*/
+ 0, /* U+010134: */
+ 0, /* U+010135: */
+ 0, /* U+010136: */
+ 0, /* U+010137: AEGEAN WEIGHT BASE UNIT*/
+ 0, /* U+010138: AEGEAN WEIGHT FIRST SUBUNIT*/
+ 0, /* U+010139: AEGEAN WEIGHT SECOND SUBUNIT*/
+ 0, /* U+01013a: AEGEAN WEIGHT THIRD SUBUNIT*/
+ 0, /* U+01013b: AEGEAN WEIGHT FOURTH SUBUNIT*/
+ 0, /* U+01013c: AEGEAN DRY MEASURE FIRST SUBUNIT*/
+ 0, /* U+01013d: AEGEAN LIQUID MEASURE FIRST SUBUNIT*/
+ 0, /* U+01013e: AEGEAN MEASURE SECOND SUBUNIT*/
+ 0, /* U+01013f: AEGEAN MEASURE THIRD SUBUNIT*/
+ RTUNI_ALPHA, /* U+010140: GREEK ACROPHONIC ATTIC ONE QUARTER*/
+ RTUNI_ALPHA, /* U+010141: GREEK ACROPHONIC ATTIC ONE HALF*/
+ RTUNI_ALPHA, /* U+010142: GREEK ACROPHONIC ATTIC ONE DRACHMA*/
+ RTUNI_ALPHA, /* U+010143: GREEK ACROPHONIC ATTIC FIVE*/
+ RTUNI_ALPHA, /* U+010144: GREEK ACROPHONIC ATTIC FIFTY*/
+ RTUNI_ALPHA, /* U+010145: GREEK ACROPHONIC ATTIC FIVE HUNDRED*/
+ RTUNI_ALPHA, /* U+010146: GREEK ACROPHONIC ATTIC FIVE THOUSAND*/
+ RTUNI_ALPHA, /* U+010147: GREEK ACROPHONIC ATTIC FIFTY THOUSAND*/
+ RTUNI_ALPHA, /* U+010148: GREEK ACROPHONIC ATTIC FIVE TALENTS*/
+ RTUNI_ALPHA, /* U+010149: GREEK ACROPHONIC ATTIC TEN TALENTS*/
+ RTUNI_ALPHA, /* U+01014a: GREEK ACROPHONIC ATTIC FIFTY TALENTS*/
+ RTUNI_ALPHA, /* U+01014b: GREEK ACROPHONIC ATTIC ONE HUNDRED TALENTS*/
+ RTUNI_ALPHA, /* U+01014c: GREEK ACROPHONIC ATTIC FIVE HUNDRED TALENTS*/
+ RTUNI_ALPHA, /* U+01014d: GREEK ACROPHONIC ATTIC ONE THOUSAND TALENTS*/
+ RTUNI_ALPHA, /* U+01014e: GREEK ACROPHONIC ATTIC FIVE THOUSAND TALENTS*/
+ RTUNI_ALPHA, /* U+01014f: GREEK ACROPHONIC ATTIC FIVE STATERS*/
+ RTUNI_ALPHA, /* U+010150: GREEK ACROPHONIC ATTIC TEN STATERS*/
+ RTUNI_ALPHA, /* U+010151: GREEK ACROPHONIC ATTIC FIFTY STATERS*/
+ RTUNI_ALPHA, /* U+010152: GREEK ACROPHONIC ATTIC ONE HUNDRED STATERS*/
+ RTUNI_ALPHA, /* U+010153: GREEK ACROPHONIC ATTIC FIVE HUNDRED STATERS*/
+ RTUNI_ALPHA, /* U+010154: GREEK ACROPHONIC ATTIC ONE THOUSAND STATERS*/
+ RTUNI_ALPHA, /* U+010155: GREEK ACROPHONIC ATTIC TEN THOUSAND STATERS*/
+ RTUNI_ALPHA, /* U+010156: GREEK ACROPHONIC ATTIC FIFTY THOUSAND STATERS*/
+ RTUNI_ALPHA, /* U+010157: GREEK ACROPHONIC ATTIC TEN MNAS*/
+ RTUNI_ALPHA, /* U+010158: GREEK ACROPHONIC HERAEUM ONE PLETHRON*/
+ RTUNI_ALPHA, /* U+010159: GREEK ACROPHONIC THESPIAN ONE*/
+ RTUNI_ALPHA, /* U+01015a: GREEK ACROPHONIC HERMIONIAN ONE*/
+ RTUNI_ALPHA, /* U+01015b: GREEK ACROPHONIC EPIDAUREAN TWO*/
+ RTUNI_ALPHA, /* U+01015c: GREEK ACROPHONIC THESPIAN TWO*/
+ RTUNI_ALPHA, /* U+01015d: GREEK ACROPHONIC CYRENAIC TWO DRACHMAS*/
+ RTUNI_ALPHA, /* U+01015e: GREEK ACROPHONIC EPIDAUREAN TWO DRACHMAS*/
+ RTUNI_ALPHA, /* U+01015f: GREEK ACROPHONIC TROEZENIAN FIVE*/
+ RTUNI_ALPHA, /* U+010160: GREEK ACROPHONIC TROEZENIAN TEN*/
+ RTUNI_ALPHA, /* U+010161: GREEK ACROPHONIC TROEZENIAN TEN ALTERNATE FORM*/
+ RTUNI_ALPHA, /* U+010162: GREEK ACROPHONIC HERMIONIAN TEN*/
+ RTUNI_ALPHA, /* U+010163: GREEK ACROPHONIC MESSENIAN TEN*/
+ RTUNI_ALPHA, /* U+010164: GREEK ACROPHONIC THESPIAN TEN*/
+ RTUNI_ALPHA, /* U+010165: GREEK ACROPHONIC THESPIAN THIRTY*/
+ RTUNI_ALPHA, /* U+010166: GREEK ACROPHONIC TROEZENIAN FIFTY*/
+ RTUNI_ALPHA, /* U+010167: GREEK ACROPHONIC TROEZENIAN FIFTY ALTERNATE FORM*/
+ RTUNI_ALPHA, /* U+010168: GREEK ACROPHONIC HERMIONIAN FIFTY*/
+ RTUNI_ALPHA, /* U+010169: GREEK ACROPHONIC THESPIAN FIFTY*/
+ RTUNI_ALPHA, /* U+01016a: GREEK ACROPHONIC THESPIAN ONE HUNDRED*/
+ RTUNI_ALPHA, /* U+01016b: GREEK ACROPHONIC THESPIAN THREE HUNDRED*/
+ RTUNI_ALPHA, /* U+01016c: GREEK ACROPHONIC EPIDAUREAN FIVE HUNDRED*/
+ RTUNI_ALPHA, /* U+01016d: GREEK ACROPHONIC TROEZENIAN FIVE HUNDRED*/
+ RTUNI_ALPHA, /* U+01016e: GREEK ACROPHONIC THESPIAN FIVE HUNDRED*/
+ RTUNI_ALPHA, /* U+01016f: GREEK ACROPHONIC CARYSTIAN FIVE HUNDRED*/
+ RTUNI_ALPHA, /* U+010170: GREEK ACROPHONIC NAXIAN FIVE HUNDRED*/
+ RTUNI_ALPHA, /* U+010171: GREEK ACROPHONIC THESPIAN ONE THOUSAND*/
+ RTUNI_ALPHA, /* U+010172: GREEK ACROPHONIC THESPIAN FIVE THOUSAND*/
+ RTUNI_ALPHA, /* U+010173: GREEK ACROPHONIC DELPHIC FIVE MNAS*/
+ RTUNI_ALPHA, /* U+010174: GREEK ACROPHONIC STRATIAN FIFTY MNAS*/
+ 0, /* U+010175: GREEK ONE HALF SIGN*/
+ 0, /* U+010176: GREEK ONE HALF SIGN ALTERNATE FORM*/
+ 0, /* U+010177: GREEK TWO THIRDS SIGN*/
+ 0, /* U+010178: GREEK THREE QUARTERS SIGN*/
+ 0, /* U+010179: GREEK YEAR SIGN*/
+ 0, /* U+01017a: GREEK TALENT SIGN*/
+ 0, /* U+01017b: GREEK DRACHMA SIGN*/
+ 0, /* U+01017c: GREEK OBOL SIGN*/
+ 0, /* U+01017d: GREEK TWO OBOLS SIGN*/
+ 0, /* U+01017e: GREEK THREE OBOLS SIGN*/
+ 0, /* U+01017f: GREEK FOUR OBOLS SIGN*/
+ 0, /* U+010180: GREEK FIVE OBOLS SIGN*/
+ 0, /* U+010181: GREEK METRETES SIGN*/
+ 0, /* U+010182: GREEK KYATHOS BASE SIGN*/
+ 0, /* U+010183: GREEK LITRA SIGN*/
+ 0, /* U+010184: GREEK OUNKIA SIGN*/
+ 0, /* U+010185: GREEK XESTES SIGN*/
+ 0, /* U+010186: GREEK ARTABE SIGN*/
+ 0, /* U+010187: GREEK AROURA SIGN*/
+ 0, /* U+010188: GREEK GRAMMA SIGN*/
+ 0, /* U+010189: GREEK TRYBLION BASE SIGN*/
+ 0, /* U+01018a: GREEK ZERO SIGN*/
+ 0, /* U+01018b: */
+ 0, /* U+01018c: */
+ 0, /* U+01018d: */
+ 0, /* U+01018e: */
+ 0, /* U+01018f: */
+ 0, /* U+010190: ROMAN SEXTANS SIGN*/
+ 0, /* U+010191: ROMAN UNCIA SIGN*/
+ 0, /* U+010192: ROMAN SEMUNCIA SIGN*/
+ 0, /* U+010193: ROMAN SEXTULA SIGN*/
+ 0, /* U+010194: ROMAN DIMIDIA SEXTULA SIGN*/
+ 0, /* U+010195: ROMAN SILIQUA SIGN*/
+ 0, /* U+010196: ROMAN DENARIUS SIGN*/
+ 0, /* U+010197: ROMAN QUINARIUS SIGN*/
+ 0, /* U+010198: ROMAN SESTERTIUS SIGN*/
+ 0, /* U+010199: ROMAN DUPONDIUS SIGN*/
+ 0, /* U+01019a: ROMAN AS SIGN*/
+ 0, /* U+01019b: ROMAN CENTURIAL SIGN*/
+ 0, /* U+01019c: */
+ 0, /* U+01019d: */
+ 0, /* U+01019e: */
+ 0, /* U+01019f: */
+ 0, /* U+0101a0: */
+ 0, /* U+0101a1: */
+ 0, /* U+0101a2: */
+ 0, /* U+0101a3: */
+ 0, /* U+0101a4: */
+ 0, /* U+0101a5: */
+ 0, /* U+0101a6: */
+ 0, /* U+0101a7: */
+ 0, /* U+0101a8: */
+ 0, /* U+0101a9: */
+ 0, /* U+0101aa: */
+ 0, /* U+0101ab: */
+ 0, /* U+0101ac: */
+ 0, /* U+0101ad: */
+ 0, /* U+0101ae: */
+ 0, /* U+0101af: */
+ 0, /* U+0101b0: */
+ 0, /* U+0101b1: */
+ 0, /* U+0101b2: */
+ 0, /* U+0101b3: */
+ 0, /* U+0101b4: */
+ 0, /* U+0101b5: */
+ 0, /* U+0101b6: */
+ 0, /* U+0101b7: */
+ 0, /* U+0101b8: */
+ 0, /* U+0101b9: */
+ 0, /* U+0101ba: */
+ 0, /* U+0101bb: */
+ 0, /* U+0101bc: */
+ 0, /* U+0101bd: */
+ 0, /* U+0101be: */
+ 0, /* U+0101bf: */
+ 0, /* U+0101c0: */
+ 0, /* U+0101c1: */
+ 0, /* U+0101c2: */
+ 0, /* U+0101c3: */
+ 0, /* U+0101c4: */
+ 0, /* U+0101c5: */
+ 0, /* U+0101c6: */
+ 0, /* U+0101c7: */
+ 0, /* U+0101c8: */
+ 0, /* U+0101c9: */
+ 0, /* U+0101ca: */
+ 0, /* U+0101cb: */
+ 0, /* U+0101cc: */
+ 0, /* U+0101cd: */
+ 0, /* U+0101ce: */
+ 0, /* U+0101cf: */
+ 0, /* U+0101d0: PHAISTOS DISC SIGN PEDESTRIAN*/
+ 0, /* U+0101d1: PHAISTOS DISC SIGN PLUMED HEAD*/
+ 0, /* U+0101d2: PHAISTOS DISC SIGN TATTOOED HEAD*/
+ 0, /* U+0101d3: PHAISTOS DISC SIGN CAPTIVE*/
+ 0, /* U+0101d4: PHAISTOS DISC SIGN CHILD*/
+ 0, /* U+0101d5: PHAISTOS DISC SIGN WOMAN*/
+ 0, /* U+0101d6: PHAISTOS DISC SIGN HELMET*/
+ 0, /* U+0101d7: PHAISTOS DISC SIGN GAUNTLET*/
+ 0, /* U+0101d8: PHAISTOS DISC SIGN TIARA*/
+ 0, /* U+0101d9: PHAISTOS DISC SIGN ARROW*/
+ 0, /* U+0101da: PHAISTOS DISC SIGN BOW*/
+ 0, /* U+0101db: PHAISTOS DISC SIGN SHIELD*/
+ 0, /* U+0101dc: PHAISTOS DISC SIGN CLUB*/
+ 0, /* U+0101dd: PHAISTOS DISC SIGN MANACLES*/
+ 0, /* U+0101de: PHAISTOS DISC SIGN MATTOCK*/
+ 0, /* U+0101df: PHAISTOS DISC SIGN SAW*/
+ 0, /* U+0101e0: PHAISTOS DISC SIGN LID*/
+ 0, /* U+0101e1: PHAISTOS DISC SIGN BOOMERANG*/
+ 0, /* U+0101e2: PHAISTOS DISC SIGN CARPENTRY PLANE*/
+ 0, /* U+0101e3: PHAISTOS DISC SIGN DOLIUM*/
+ 0, /* U+0101e4: PHAISTOS DISC SIGN COMB*/
+ 0, /* U+0101e5: PHAISTOS DISC SIGN SLING*/
+ 0, /* U+0101e6: PHAISTOS DISC SIGN COLUMN*/
+ 0, /* U+0101e7: PHAISTOS DISC SIGN BEEHIVE*/
+ 0, /* U+0101e8: PHAISTOS DISC SIGN SHIP*/
+ 0, /* U+0101e9: PHAISTOS DISC SIGN HORN*/
+ 0, /* U+0101ea: PHAISTOS DISC SIGN HIDE*/
+ 0, /* U+0101eb: PHAISTOS DISC SIGN BULLS LEG*/
+ 0, /* U+0101ec: PHAISTOS DISC SIGN CAT*/
+ 0, /* U+0101ed: PHAISTOS DISC SIGN RAM*/
+ 0, /* U+0101ee: PHAISTOS DISC SIGN EAGLE*/
+ 0, /* U+0101ef: PHAISTOS DISC SIGN DOVE*/
+ 0, /* U+0101f0: PHAISTOS DISC SIGN TUNNY*/
+ 0, /* U+0101f1: PHAISTOS DISC SIGN BEE*/
+ 0, /* U+0101f2: PHAISTOS DISC SIGN PLANE TREE*/
+ 0, /* U+0101f3: PHAISTOS DISC SIGN VINE*/
+ 0, /* U+0101f4: PHAISTOS DISC SIGN PAPYRUS*/
+ 0, /* U+0101f5: PHAISTOS DISC SIGN ROSETTE*/
+ 0, /* U+0101f6: PHAISTOS DISC SIGN LILY*/
+ 0, /* U+0101f7: PHAISTOS DISC SIGN OX BACK*/
+ 0, /* U+0101f8: PHAISTOS DISC SIGN FLUTE*/
+ 0, /* U+0101f9: PHAISTOS DISC SIGN GRATER*/
+ 0, /* U+0101fa: PHAISTOS DISC SIGN STRAINER*/
+ 0, /* U+0101fb: PHAISTOS DISC SIGN SMALL AXE*/
+ 0, /* U+0101fc: PHAISTOS DISC SIGN WAVY BAND*/
+ 0, /* U+0101fd: PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE*/
+ 0, /* U+0101fe: */
+ 0, /* U+0101ff: */
+ 0, /* U+010200: */
+ 0, /* U+010201: */
+ 0, /* U+010202: */
+ 0, /* U+010203: */
+ 0, /* U+010204: */
+ 0, /* U+010205: */
+ 0, /* U+010206: */
+ 0, /* U+010207: */
+ 0, /* U+010208: */
+ 0, /* U+010209: */
+ 0, /* U+01020a: */
+ 0, /* U+01020b: */
+ 0, /* U+01020c: */
+ 0, /* U+01020d: */
+ 0, /* U+01020e: */
+ 0, /* U+01020f: */
+ 0, /* U+010210: */
+ 0, /* U+010211: */
+ 0, /* U+010212: */
+ 0, /* U+010213: */
+ 0, /* U+010214: */
+ 0, /* U+010215: */
+ 0, /* U+010216: */
+ 0, /* U+010217: */
+ 0, /* U+010218: */
+ 0, /* U+010219: */
+ 0, /* U+01021a: */
+ 0, /* U+01021b: */
+ 0, /* U+01021c: */
+ 0, /* U+01021d: */
+ 0, /* U+01021e: */
+ 0, /* U+01021f: */
+ 0, /* U+010220: */
+ 0, /* U+010221: */
+ 0, /* U+010222: */
+ 0, /* U+010223: */
+ 0, /* U+010224: */
+ 0, /* U+010225: */
+ 0, /* U+010226: */
+ 0, /* U+010227: */
+ 0, /* U+010228: */
+ 0, /* U+010229: */
+ 0, /* U+01022a: */
+ 0, /* U+01022b: */
+ 0, /* U+01022c: */
+ 0, /* U+01022d: */
+ 0, /* U+01022e: */
+ 0, /* U+01022f: */
+ 0, /* U+010230: */
+ 0, /* U+010231: */
+ 0, /* U+010232: */
+ 0, /* U+010233: */
+ 0, /* U+010234: */
+ 0, /* U+010235: */
+ 0, /* U+010236: */
+ 0, /* U+010237: */
+ 0, /* U+010238: */
+ 0, /* U+010239: */
+ 0, /* U+01023a: */
+ 0, /* U+01023b: */
+ 0, /* U+01023c: */
+ 0, /* U+01023d: */
+ 0, /* U+01023e: */
+ 0, /* U+01023f: */
+ 0, /* U+010240: */
+ 0, /* U+010241: */
+ 0, /* U+010242: */
+ 0, /* U+010243: */
+ 0, /* U+010244: */
+ 0, /* U+010245: */
+ 0, /* U+010246: */
+ 0, /* U+010247: */
+ 0, /* U+010248: */
+ 0, /* U+010249: */
+ 0, /* U+01024a: */
+ 0, /* U+01024b: */
+ 0, /* U+01024c: */
+ 0, /* U+01024d: */
+ 0, /* U+01024e: */
+ 0, /* U+01024f: */
+ 0, /* U+010250: */
+ 0, /* U+010251: */
+ 0, /* U+010252: */
+ 0, /* U+010253: */
+ 0, /* U+010254: */
+ 0, /* U+010255: */
+ 0, /* U+010256: */
+ 0, /* U+010257: */
+ 0, /* U+010258: */
+ 0, /* U+010259: */
+ 0, /* U+01025a: */
+ 0, /* U+01025b: */
+ 0, /* U+01025c: */
+ 0, /* U+01025d: */
+ 0, /* U+01025e: */
+ 0, /* U+01025f: */
+ 0, /* U+010260: */
+ 0, /* U+010261: */
+ 0, /* U+010262: */
+ 0, /* U+010263: */
+ 0, /* U+010264: */
+ 0, /* U+010265: */
+ 0, /* U+010266: */
+ 0, /* U+010267: */
+ 0, /* U+010268: */
+ 0, /* U+010269: */
+ 0, /* U+01026a: */
+ 0, /* U+01026b: */
+ 0, /* U+01026c: */
+ 0, /* U+01026d: */
+ 0, /* U+01026e: */
+ 0, /* U+01026f: */
+ 0, /* U+010270: */
+ 0, /* U+010271: */
+ 0, /* U+010272: */
+ 0, /* U+010273: */
+ 0, /* U+010274: */
+ 0, /* U+010275: */
+ 0, /* U+010276: */
+ 0, /* U+010277: */
+ 0, /* U+010278: */
+ 0, /* U+010279: */
+ 0, /* U+01027a: */
+ 0, /* U+01027b: */
+ 0, /* U+01027c: */
+ 0, /* U+01027d: */
+ 0, /* U+01027e: */
+ 0, /* U+01027f: */
+ RTUNI_ALPHA, /* U+010280: LYCIAN LETTER A*/
+ RTUNI_ALPHA, /* U+010281: LYCIAN LETTER E*/
+ RTUNI_ALPHA, /* U+010282: LYCIAN LETTER B*/
+ RTUNI_ALPHA, /* U+010283: LYCIAN LETTER BH*/
+ RTUNI_ALPHA, /* U+010284: LYCIAN LETTER G*/
+ RTUNI_ALPHA, /* U+010285: LYCIAN LETTER D*/
+ RTUNI_ALPHA, /* U+010286: LYCIAN LETTER I*/
+ RTUNI_ALPHA, /* U+010287: LYCIAN LETTER W*/
+ RTUNI_ALPHA, /* U+010288: LYCIAN LETTER Z*/
+ RTUNI_ALPHA, /* U+010289: LYCIAN LETTER TH*/
+ RTUNI_ALPHA, /* U+01028a: LYCIAN LETTER J*/
+ RTUNI_ALPHA, /* U+01028b: LYCIAN LETTER K*/
+ RTUNI_ALPHA, /* U+01028c: LYCIAN LETTER Q*/
+ RTUNI_ALPHA, /* U+01028d: LYCIAN LETTER L*/
+ RTUNI_ALPHA, /* U+01028e: LYCIAN LETTER M*/
+ RTUNI_ALPHA, /* U+01028f: LYCIAN LETTER N*/
+ RTUNI_ALPHA, /* U+010290: LYCIAN LETTER MM*/
+ RTUNI_ALPHA, /* U+010291: LYCIAN LETTER NN*/
+ RTUNI_ALPHA, /* U+010292: LYCIAN LETTER U*/
+ RTUNI_ALPHA, /* U+010293: LYCIAN LETTER P*/
+ RTUNI_ALPHA, /* U+010294: LYCIAN LETTER KK*/
+ RTUNI_ALPHA, /* U+010295: LYCIAN LETTER R*/
+ RTUNI_ALPHA, /* U+010296: LYCIAN LETTER S*/
+ RTUNI_ALPHA, /* U+010297: LYCIAN LETTER T*/
+ RTUNI_ALPHA, /* U+010298: LYCIAN LETTER TT*/
+ RTUNI_ALPHA, /* U+010299: LYCIAN LETTER AN*/
+ RTUNI_ALPHA, /* U+01029a: LYCIAN LETTER EN*/
+ RTUNI_ALPHA, /* U+01029b: LYCIAN LETTER H*/
+ RTUNI_ALPHA, /* U+01029c: LYCIAN LETTER X*/
+ 0, /* U+01029d: */
+ 0, /* U+01029e: */
+ 0, /* U+01029f: */
+ RTUNI_ALPHA, /* U+0102a0: CARIAN LETTER A*/
+ RTUNI_ALPHA, /* U+0102a1: CARIAN LETTER P2*/
+ RTUNI_ALPHA, /* U+0102a2: CARIAN LETTER D*/
+ RTUNI_ALPHA, /* U+0102a3: CARIAN LETTER L*/
+ RTUNI_ALPHA, /* U+0102a4: CARIAN LETTER UUU*/
+ RTUNI_ALPHA, /* U+0102a5: CARIAN LETTER R*/
+ RTUNI_ALPHA, /* U+0102a6: CARIAN LETTER LD*/
+ RTUNI_ALPHA, /* U+0102a7: CARIAN LETTER A2*/
+ RTUNI_ALPHA, /* U+0102a8: CARIAN LETTER Q*/
+ RTUNI_ALPHA, /* U+0102a9: CARIAN LETTER B*/
+ RTUNI_ALPHA, /* U+0102aa: CARIAN LETTER M*/
+ RTUNI_ALPHA, /* U+0102ab: CARIAN LETTER O*/
+ RTUNI_ALPHA, /* U+0102ac: CARIAN LETTER D2*/
+ RTUNI_ALPHA, /* U+0102ad: CARIAN LETTER T*/
+ RTUNI_ALPHA, /* U+0102ae: CARIAN LETTER SH*/
+ RTUNI_ALPHA, /* U+0102af: CARIAN LETTER SH2*/
+ RTUNI_ALPHA, /* U+0102b0: CARIAN LETTER S*/
+ RTUNI_ALPHA, /* U+0102b1: CARIAN LETTER C-18*/
+ RTUNI_ALPHA, /* U+0102b2: CARIAN LETTER U*/
+ RTUNI_ALPHA, /* U+0102b3: CARIAN LETTER NN*/
+ RTUNI_ALPHA, /* U+0102b4: CARIAN LETTER X*/
+ RTUNI_ALPHA, /* U+0102b5: CARIAN LETTER N*/
+ RTUNI_ALPHA, /* U+0102b6: CARIAN LETTER TT2*/
+ RTUNI_ALPHA, /* U+0102b7: CARIAN LETTER P*/
+ RTUNI_ALPHA, /* U+0102b8: CARIAN LETTER SS*/
+ RTUNI_ALPHA, /* U+0102b9: CARIAN LETTER I*/
+ RTUNI_ALPHA, /* U+0102ba: CARIAN LETTER E*/
+ RTUNI_ALPHA, /* U+0102bb: CARIAN LETTER UUUU*/
+ RTUNI_ALPHA, /* U+0102bc: CARIAN LETTER K*/
+ RTUNI_ALPHA, /* U+0102bd: CARIAN LETTER K2*/
+ RTUNI_ALPHA, /* U+0102be: CARIAN LETTER ND*/
+ RTUNI_ALPHA, /* U+0102bf: CARIAN LETTER UU*/
+ RTUNI_ALPHA, /* U+0102c0: CARIAN LETTER G*/
+ RTUNI_ALPHA, /* U+0102c1: CARIAN LETTER G2*/
+ RTUNI_ALPHA, /* U+0102c2: CARIAN LETTER ST*/
+ RTUNI_ALPHA, /* U+0102c3: CARIAN LETTER ST2*/
+ RTUNI_ALPHA, /* U+0102c4: CARIAN LETTER NG*/
+ RTUNI_ALPHA, /* U+0102c5: CARIAN LETTER II*/
+ RTUNI_ALPHA, /* U+0102c6: CARIAN LETTER C-39*/
+ RTUNI_ALPHA, /* U+0102c7: CARIAN LETTER TT*/
+ RTUNI_ALPHA, /* U+0102c8: CARIAN LETTER UUU2*/
+ RTUNI_ALPHA, /* U+0102c9: CARIAN LETTER RR*/
+ RTUNI_ALPHA, /* U+0102ca: CARIAN LETTER MB*/
+ RTUNI_ALPHA, /* U+0102cb: CARIAN LETTER MB2*/
+ RTUNI_ALPHA, /* U+0102cc: CARIAN LETTER MB3*/
+ RTUNI_ALPHA, /* U+0102cd: CARIAN LETTER MB4*/
+ RTUNI_ALPHA, /* U+0102ce: CARIAN LETTER LD2*/
+ RTUNI_ALPHA, /* U+0102cf: CARIAN LETTER E2*/
+ RTUNI_ALPHA, /* U+0102d0: CARIAN LETTER UUU3*/
+ 0, /* U+0102d1: */
+ 0, /* U+0102d2: */
+ 0, /* U+0102d3: */
+ 0, /* U+0102d4: */
+ 0, /* U+0102d5: */
+ 0, /* U+0102d6: */
+ 0, /* U+0102d7: */
+ 0, /* U+0102d8: */
+ 0, /* U+0102d9: */
+ 0, /* U+0102da: */
+ 0, /* U+0102db: */
+ 0, /* U+0102dc: */
+ 0, /* U+0102dd: */
+ 0, /* U+0102de: */
+ 0, /* U+0102df: */
+ 0, /* U+0102e0: */
+ 0, /* U+0102e1: */
+ 0, /* U+0102e2: */
+ 0, /* U+0102e3: */
+ 0, /* U+0102e4: */
+ 0, /* U+0102e5: */
+ 0, /* U+0102e6: */
+ 0, /* U+0102e7: */
+ 0, /* U+0102e8: */
+ 0, /* U+0102e9: */
+ 0, /* U+0102ea: */
+ 0, /* U+0102eb: */
+ 0, /* U+0102ec: */
+ 0, /* U+0102ed: */
+ 0, /* U+0102ee: */
+ 0, /* U+0102ef: */
+ 0, /* U+0102f0: */
+ 0, /* U+0102f1: */
+ 0, /* U+0102f2: */
+ 0, /* U+0102f3: */
+ 0, /* U+0102f4: */
+ 0, /* U+0102f5: */
+ 0, /* U+0102f6: */
+ 0, /* U+0102f7: */
+ 0, /* U+0102f8: */
+ 0, /* U+0102f9: */
+ 0, /* U+0102fa: */
+ 0, /* U+0102fb: */
+ 0, /* U+0102fc: */
+ 0, /* U+0102fd: */
+ 0, /* U+0102fe: */
+ 0, /* U+0102ff: */
+ RTUNI_ALPHA, /* U+010300: OLD ITALIC LETTER A*/
+ RTUNI_ALPHA, /* U+010301: OLD ITALIC LETTER BE*/
+ RTUNI_ALPHA, /* U+010302: OLD ITALIC LETTER KE*/
+ RTUNI_ALPHA, /* U+010303: OLD ITALIC LETTER DE*/
+ RTUNI_ALPHA, /* U+010304: OLD ITALIC LETTER E*/
+ RTUNI_ALPHA, /* U+010305: OLD ITALIC LETTER VE*/
+ RTUNI_ALPHA, /* U+010306: OLD ITALIC LETTER ZE*/
+ RTUNI_ALPHA, /* U+010307: OLD ITALIC LETTER HE*/
+ RTUNI_ALPHA, /* U+010308: OLD ITALIC LETTER THE*/
+ RTUNI_ALPHA, /* U+010309: OLD ITALIC LETTER I*/
+ RTUNI_ALPHA, /* U+01030a: OLD ITALIC LETTER KA*/
+ RTUNI_ALPHA, /* U+01030b: OLD ITALIC LETTER EL*/
+ RTUNI_ALPHA, /* U+01030c: OLD ITALIC LETTER EM*/
+ RTUNI_ALPHA, /* U+01030d: OLD ITALIC LETTER EN*/
+ RTUNI_ALPHA, /* U+01030e: OLD ITALIC LETTER ESH*/
+ RTUNI_ALPHA, /* U+01030f: OLD ITALIC LETTER O*/
+ RTUNI_ALPHA, /* U+010310: OLD ITALIC LETTER PE*/
+ RTUNI_ALPHA, /* U+010311: OLD ITALIC LETTER SHE*/
+ RTUNI_ALPHA, /* U+010312: OLD ITALIC LETTER KU*/
+ RTUNI_ALPHA, /* U+010313: OLD ITALIC LETTER ER*/
+ RTUNI_ALPHA, /* U+010314: OLD ITALIC LETTER ES*/
+ RTUNI_ALPHA, /* U+010315: OLD ITALIC LETTER TE*/
+ RTUNI_ALPHA, /* U+010316: OLD ITALIC LETTER U*/
+ RTUNI_ALPHA, /* U+010317: OLD ITALIC LETTER EKS*/
+ RTUNI_ALPHA, /* U+010318: OLD ITALIC LETTER PHE*/
+ RTUNI_ALPHA, /* U+010319: OLD ITALIC LETTER KHE*/
+ RTUNI_ALPHA, /* U+01031a: OLD ITALIC LETTER EF*/
+ RTUNI_ALPHA, /* U+01031b: OLD ITALIC LETTER ERS*/
+ RTUNI_ALPHA, /* U+01031c: OLD ITALIC LETTER CHE*/
+ RTUNI_ALPHA, /* U+01031d: OLD ITALIC LETTER II*/
+ RTUNI_ALPHA, /* U+01031e: OLD ITALIC LETTER UU*/
+ 0, /* U+01031f: */
+ 0, /* U+010320: OLD ITALIC NUMERAL ONE*/
+ 0, /* U+010321: OLD ITALIC NUMERAL FIVE*/
+ 0, /* U+010322: OLD ITALIC NUMERAL TEN*/
+ 0, /* U+010323: OLD ITALIC NUMERAL FIFTY*/
+ 0, /* U+010324: */
+ 0, /* U+010325: */
+ 0, /* U+010326: */
+ 0, /* U+010327: */
+ 0, /* U+010328: */
+ 0, /* U+010329: */
+ 0, /* U+01032a: */
+ 0, /* U+01032b: */
+ 0, /* U+01032c: */
+ 0, /* U+01032d: */
+ 0, /* U+01032e: */
+ 0, /* U+01032f: */
+ RTUNI_ALPHA, /* U+010330: GOTHIC LETTER AHSA*/
+ RTUNI_ALPHA, /* U+010331: GOTHIC LETTER BAIRKAN*/
+ RTUNI_ALPHA, /* U+010332: GOTHIC LETTER GIBA*/
+ RTUNI_ALPHA, /* U+010333: GOTHIC LETTER DAGS*/
+ RTUNI_ALPHA, /* U+010334: GOTHIC LETTER AIHVUS*/
+ RTUNI_ALPHA, /* U+010335: GOTHIC LETTER QAIRTHRA*/
+ RTUNI_ALPHA, /* U+010336: GOTHIC LETTER IUJA*/
+ RTUNI_ALPHA, /* U+010337: GOTHIC LETTER HAGL*/
+ RTUNI_ALPHA, /* U+010338: GOTHIC LETTER THIUTH*/
+ RTUNI_ALPHA, /* U+010339: GOTHIC LETTER EIS*/
+ RTUNI_ALPHA, /* U+01033a: GOTHIC LETTER KUSMA*/
+ RTUNI_ALPHA, /* U+01033b: GOTHIC LETTER LAGUS*/
+ RTUNI_ALPHA, /* U+01033c: GOTHIC LETTER MANNA*/
+ RTUNI_ALPHA, /* U+01033d: GOTHIC LETTER NAUTHS*/
+ RTUNI_ALPHA, /* U+01033e: GOTHIC LETTER JER*/
+ RTUNI_ALPHA, /* U+01033f: GOTHIC LETTER URUS*/
+ RTUNI_ALPHA, /* U+010340: GOTHIC LETTER PAIRTHRA*/
+ RTUNI_ALPHA, /* U+010341: GOTHIC LETTER NINETY*/
+ RTUNI_ALPHA, /* U+010342: GOTHIC LETTER RAIDA*/
+ RTUNI_ALPHA, /* U+010343: GOTHIC LETTER SAUIL*/
+ RTUNI_ALPHA, /* U+010344: GOTHIC LETTER TEIWS*/
+ RTUNI_ALPHA, /* U+010345: GOTHIC LETTER WINJA*/
+ RTUNI_ALPHA, /* U+010346: GOTHIC LETTER FAIHU*/
+ RTUNI_ALPHA, /* U+010347: GOTHIC LETTER IGGWS*/
+ RTUNI_ALPHA, /* U+010348: GOTHIC LETTER HWAIR*/
+ RTUNI_ALPHA, /* U+010349: GOTHIC LETTER OTHAL*/
+ RTUNI_ALPHA, /* U+01034a: GOTHIC LETTER NINE HUNDRED*/
+ 0, /* U+01034b: */
+ 0, /* U+01034c: */
+ 0, /* U+01034d: */
+ 0, /* U+01034e: */
+ 0, /* U+01034f: */
+ 0, /* U+010350: */
+ 0, /* U+010351: */
+ 0, /* U+010352: */
+ 0, /* U+010353: */
+ 0, /* U+010354: */
+ 0, /* U+010355: */
+ 0, /* U+010356: */
+ 0, /* U+010357: */
+ 0, /* U+010358: */
+ 0, /* U+010359: */
+ 0, /* U+01035a: */
+ 0, /* U+01035b: */
+ 0, /* U+01035c: */
+ 0, /* U+01035d: */
+ 0, /* U+01035e: */
+ 0, /* U+01035f: */
+ 0, /* U+010360: */
+ 0, /* U+010361: */
+ 0, /* U+010362: */
+ 0, /* U+010363: */
+ 0, /* U+010364: */
+ 0, /* U+010365: */
+ 0, /* U+010366: */
+ 0, /* U+010367: */
+ 0, /* U+010368: */
+ 0, /* U+010369: */
+ 0, /* U+01036a: */
+ 0, /* U+01036b: */
+ 0, /* U+01036c: */
+ 0, /* U+01036d: */
+ 0, /* U+01036e: */
+ 0, /* U+01036f: */
+ 0, /* U+010370: */
+ 0, /* U+010371: */
+ 0, /* U+010372: */
+ 0, /* U+010373: */
+ 0, /* U+010374: */
+ 0, /* U+010375: */
+ 0, /* U+010376: */
+ 0, /* U+010377: */
+ 0, /* U+010378: */
+ 0, /* U+010379: */
+ 0, /* U+01037a: */
+ 0, /* U+01037b: */
+ 0, /* U+01037c: */
+ 0, /* U+01037d: */
+ 0, /* U+01037e: */
+ 0, /* U+01037f: */
+ RTUNI_ALPHA, /* U+010380: UGARITIC LETTER ALPA*/
+ RTUNI_ALPHA, /* U+010381: UGARITIC LETTER BETA*/
+ RTUNI_ALPHA, /* U+010382: UGARITIC LETTER GAMLA*/
+ RTUNI_ALPHA, /* U+010383: UGARITIC LETTER KHA*/
+ RTUNI_ALPHA, /* U+010384: UGARITIC LETTER DELTA*/
+ RTUNI_ALPHA, /* U+010385: UGARITIC LETTER HO*/
+ RTUNI_ALPHA, /* U+010386: UGARITIC LETTER WO*/
+ RTUNI_ALPHA, /* U+010387: UGARITIC LETTER ZETA*/
+ RTUNI_ALPHA, /* U+010388: UGARITIC LETTER HOTA*/
+ RTUNI_ALPHA, /* U+010389: UGARITIC LETTER TET*/
+ RTUNI_ALPHA, /* U+01038a: UGARITIC LETTER YOD*/
+ RTUNI_ALPHA, /* U+01038b: UGARITIC LETTER KAF*/
+ RTUNI_ALPHA, /* U+01038c: UGARITIC LETTER SHIN*/
+ RTUNI_ALPHA, /* U+01038d: UGARITIC LETTER LAMDA*/
+ RTUNI_ALPHA, /* U+01038e: UGARITIC LETTER MEM*/
+ RTUNI_ALPHA, /* U+01038f: UGARITIC LETTER DHAL*/
+ RTUNI_ALPHA, /* U+010390: UGARITIC LETTER NUN*/
+ RTUNI_ALPHA, /* U+010391: UGARITIC LETTER ZU*/
+ RTUNI_ALPHA, /* U+010392: UGARITIC LETTER SAMKA*/
+ RTUNI_ALPHA, /* U+010393: UGARITIC LETTER AIN*/
+ RTUNI_ALPHA, /* U+010394: UGARITIC LETTER PU*/
+ RTUNI_ALPHA, /* U+010395: UGARITIC LETTER SADE*/
+ RTUNI_ALPHA, /* U+010396: UGARITIC LETTER QOPA*/
+ RTUNI_ALPHA, /* U+010397: UGARITIC LETTER RASHA*/
+ RTUNI_ALPHA, /* U+010398: UGARITIC LETTER THANNA*/
+ RTUNI_ALPHA, /* U+010399: UGARITIC LETTER GHAIN*/
+ RTUNI_ALPHA, /* U+01039a: UGARITIC LETTER TO*/
+ RTUNI_ALPHA, /* U+01039b: UGARITIC LETTER I*/
+ RTUNI_ALPHA, /* U+01039c: UGARITIC LETTER U*/
+ RTUNI_ALPHA, /* U+01039d: UGARITIC LETTER SSU*/
+ 0, /* U+01039e: */
+ 0, /* U+01039f: UGARITIC WORD DIVIDER*/
+ RTUNI_ALPHA, /* U+0103a0: OLD PERSIAN SIGN A*/
+ RTUNI_ALPHA, /* U+0103a1: OLD PERSIAN SIGN I*/
+ RTUNI_ALPHA, /* U+0103a2: OLD PERSIAN SIGN U*/
+ RTUNI_ALPHA, /* U+0103a3: OLD PERSIAN SIGN KA*/
+ RTUNI_ALPHA, /* U+0103a4: OLD PERSIAN SIGN KU*/
+ RTUNI_ALPHA, /* U+0103a5: OLD PERSIAN SIGN GA*/
+ RTUNI_ALPHA, /* U+0103a6: OLD PERSIAN SIGN GU*/
+ RTUNI_ALPHA, /* U+0103a7: OLD PERSIAN SIGN XA*/
+ RTUNI_ALPHA, /* U+0103a8: OLD PERSIAN SIGN CA*/
+ RTUNI_ALPHA, /* U+0103a9: OLD PERSIAN SIGN JA*/
+ RTUNI_ALPHA, /* U+0103aa: OLD PERSIAN SIGN JI*/
+ RTUNI_ALPHA, /* U+0103ab: OLD PERSIAN SIGN TA*/
+ RTUNI_ALPHA, /* U+0103ac: OLD PERSIAN SIGN TU*/
+ RTUNI_ALPHA, /* U+0103ad: OLD PERSIAN SIGN DA*/
+ RTUNI_ALPHA, /* U+0103ae: OLD PERSIAN SIGN DI*/
+ RTUNI_ALPHA, /* U+0103af: OLD PERSIAN SIGN DU*/
+ RTUNI_ALPHA, /* U+0103b0: OLD PERSIAN SIGN THA*/
+ RTUNI_ALPHA, /* U+0103b1: OLD PERSIAN SIGN PA*/
+ RTUNI_ALPHA, /* U+0103b2: OLD PERSIAN SIGN BA*/
+ RTUNI_ALPHA, /* U+0103b3: OLD PERSIAN SIGN FA*/
+ RTUNI_ALPHA, /* U+0103b4: OLD PERSIAN SIGN NA*/
+ RTUNI_ALPHA, /* U+0103b5: OLD PERSIAN SIGN NU*/
+ RTUNI_ALPHA, /* U+0103b6: OLD PERSIAN SIGN MA*/
+ RTUNI_ALPHA, /* U+0103b7: OLD PERSIAN SIGN MI*/
+ RTUNI_ALPHA, /* U+0103b8: OLD PERSIAN SIGN MU*/
+ RTUNI_ALPHA, /* U+0103b9: OLD PERSIAN SIGN YA*/
+ RTUNI_ALPHA, /* U+0103ba: OLD PERSIAN SIGN VA*/
+ RTUNI_ALPHA, /* U+0103bb: OLD PERSIAN SIGN VI*/
+ RTUNI_ALPHA, /* U+0103bc: OLD PERSIAN SIGN RA*/
+ RTUNI_ALPHA, /* U+0103bd: OLD PERSIAN SIGN RU*/
+ RTUNI_ALPHA, /* U+0103be: OLD PERSIAN SIGN LA*/
+ RTUNI_ALPHA, /* U+0103bf: OLD PERSIAN SIGN SA*/
+ RTUNI_ALPHA, /* U+0103c0: OLD PERSIAN SIGN ZA*/
+ RTUNI_ALPHA, /* U+0103c1: OLD PERSIAN SIGN SHA*/
+ RTUNI_ALPHA, /* U+0103c2: OLD PERSIAN SIGN SSA*/
+ RTUNI_ALPHA, /* U+0103c3: OLD PERSIAN SIGN HA*/
+ 0, /* U+0103c4: */
+ 0, /* U+0103c5: */
+ 0, /* U+0103c6: */
+ 0, /* U+0103c7: */
+ RTUNI_ALPHA, /* U+0103c8: OLD PERSIAN SIGN AURAMAZDAA*/
+ RTUNI_ALPHA, /* U+0103c9: OLD PERSIAN SIGN AURAMAZDAA-2*/
+ RTUNI_ALPHA, /* U+0103ca: OLD PERSIAN SIGN AURAMAZDAAHA*/
+ RTUNI_ALPHA, /* U+0103cb: OLD PERSIAN SIGN XSHAAYATHIYA*/
+ RTUNI_ALPHA, /* U+0103cc: OLD PERSIAN SIGN DAHYAAUSH*/
+ RTUNI_ALPHA, /* U+0103cd: OLD PERSIAN SIGN DAHYAAUSH-2*/
+ RTUNI_ALPHA, /* U+0103ce: OLD PERSIAN SIGN BAGA*/
+ RTUNI_ALPHA, /* U+0103cf: OLD PERSIAN SIGN BUUMISH*/
+ 0, /* U+0103d0: OLD PERSIAN WORD DIVIDER*/
+ RTUNI_ALPHA, /* U+0103d1: OLD PERSIAN NUMBER ONE*/
+ RTUNI_ALPHA, /* U+0103d2: OLD PERSIAN NUMBER TWO*/
+ RTUNI_ALPHA, /* U+0103d3: OLD PERSIAN NUMBER TEN*/
+ RTUNI_ALPHA, /* U+0103d4: OLD PERSIAN NUMBER TWENTY*/
+ RTUNI_ALPHA, /* U+0103d5: OLD PERSIAN NUMBER HUNDRED*/
+ 0, /* U+0103d6: */
+ 0, /* U+0103d7: */
+ 0, /* U+0103d8: */
+ 0, /* U+0103d9: */
+ 0, /* U+0103da: */
+ 0, /* U+0103db: */
+ 0, /* U+0103dc: */
+ 0, /* U+0103dd: */
+ 0, /* U+0103de: */
+ 0, /* U+0103df: */
+ 0, /* U+0103e0: */
+ 0, /* U+0103e1: */
+ 0, /* U+0103e2: */
+ 0, /* U+0103e3: */
+ 0, /* U+0103e4: */
+ 0, /* U+0103e5: */
+ 0, /* U+0103e6: */
+ 0, /* U+0103e7: */
+ 0, /* U+0103e8: */
+ 0, /* U+0103e9: */
+ 0, /* U+0103ea: */
+ 0, /* U+0103eb: */
+ 0, /* U+0103ec: */
+ 0, /* U+0103ed: */
+ 0, /* U+0103ee: */
+ 0, /* U+0103ef: */
+ 0, /* U+0103f0: */
+ 0, /* U+0103f1: */
+ 0, /* U+0103f2: */
+ 0, /* U+0103f3: */
+ 0, /* U+0103f4: */
+ 0, /* U+0103f5: */
+ 0, /* U+0103f6: */
+ 0, /* U+0103f7: */
+ 0, /* U+0103f8: */
+ 0, /* U+0103f9: */
+ 0, /* U+0103fa: */
+ 0, /* U+0103fb: */
+ 0, /* U+0103fc: */
+ 0, /* U+0103fd: */
+ 0, /* U+0103fe: */
+ 0, /* U+0103ff: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010400: DESERET CAPITAL LETTER LONG I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010401: DESERET CAPITAL LETTER LONG E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010402: DESERET CAPITAL LETTER LONG A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010403: DESERET CAPITAL LETTER LONG AH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010404: DESERET CAPITAL LETTER LONG O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010405: DESERET CAPITAL LETTER LONG OO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010406: DESERET CAPITAL LETTER SHORT I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010407: DESERET CAPITAL LETTER SHORT E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010408: DESERET CAPITAL LETTER SHORT A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010409: DESERET CAPITAL LETTER SHORT AH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01040a: DESERET CAPITAL LETTER SHORT O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01040b: DESERET CAPITAL LETTER SHORT OO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01040c: DESERET CAPITAL LETTER AY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01040d: DESERET CAPITAL LETTER OW*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01040e: DESERET CAPITAL LETTER WU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01040f: DESERET CAPITAL LETTER YEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010410: DESERET CAPITAL LETTER H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010411: DESERET CAPITAL LETTER PEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010412: DESERET CAPITAL LETTER BEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010413: DESERET CAPITAL LETTER TEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010414: DESERET CAPITAL LETTER DEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010415: DESERET CAPITAL LETTER CHEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010416: DESERET CAPITAL LETTER JEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010417: DESERET CAPITAL LETTER KAY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010418: DESERET CAPITAL LETTER GAY*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010419: DESERET CAPITAL LETTER EF*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01041a: DESERET CAPITAL LETTER VEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01041b: DESERET CAPITAL LETTER ETH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01041c: DESERET CAPITAL LETTER THEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01041d: DESERET CAPITAL LETTER ES*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01041e: DESERET CAPITAL LETTER ZEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01041f: DESERET CAPITAL LETTER ESH*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010420: DESERET CAPITAL LETTER ZHEE*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010421: DESERET CAPITAL LETTER ER*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010422: DESERET CAPITAL LETTER EL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010423: DESERET CAPITAL LETTER EM*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010424: DESERET CAPITAL LETTER EN*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010425: DESERET CAPITAL LETTER ENG*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010426: DESERET CAPITAL LETTER OI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+010427: DESERET CAPITAL LETTER EW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010428: DESERET SMALL LETTER LONG I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010429: DESERET SMALL LETTER LONG E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01042a: DESERET SMALL LETTER LONG A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01042b: DESERET SMALL LETTER LONG AH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01042c: DESERET SMALL LETTER LONG O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01042d: DESERET SMALL LETTER LONG OO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01042e: DESERET SMALL LETTER SHORT I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01042f: DESERET SMALL LETTER SHORT E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010430: DESERET SMALL LETTER SHORT A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010431: DESERET SMALL LETTER SHORT AH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010432: DESERET SMALL LETTER SHORT O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010433: DESERET SMALL LETTER SHORT OO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010434: DESERET SMALL LETTER AY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010435: DESERET SMALL LETTER OW*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010436: DESERET SMALL LETTER WU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010437: DESERET SMALL LETTER YEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010438: DESERET SMALL LETTER H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010439: DESERET SMALL LETTER PEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01043a: DESERET SMALL LETTER BEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01043b: DESERET SMALL LETTER TEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01043c: DESERET SMALL LETTER DEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01043d: DESERET SMALL LETTER CHEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01043e: DESERET SMALL LETTER JEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01043f: DESERET SMALL LETTER KAY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010440: DESERET SMALL LETTER GAY*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010441: DESERET SMALL LETTER EF*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010442: DESERET SMALL LETTER VEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010443: DESERET SMALL LETTER ETH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010444: DESERET SMALL LETTER THEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010445: DESERET SMALL LETTER ES*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010446: DESERET SMALL LETTER ZEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010447: DESERET SMALL LETTER ESH*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010448: DESERET SMALL LETTER ZHEE*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+010449: DESERET SMALL LETTER ER*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01044a: DESERET SMALL LETTER EL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01044b: DESERET SMALL LETTER EM*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01044c: DESERET SMALL LETTER EN*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01044d: DESERET SMALL LETTER ENG*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01044e: DESERET SMALL LETTER OI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01044f: DESERET SMALL LETTER EW*/
+ RTUNI_ALPHA, /* U+010450: SHAVIAN LETTER PEEP*/
+ RTUNI_ALPHA, /* U+010451: SHAVIAN LETTER TOT*/
+ RTUNI_ALPHA, /* U+010452: SHAVIAN LETTER KICK*/
+ RTUNI_ALPHA, /* U+010453: SHAVIAN LETTER FEE*/
+ RTUNI_ALPHA, /* U+010454: SHAVIAN LETTER THIGH*/
+ RTUNI_ALPHA, /* U+010455: SHAVIAN LETTER SO*/
+ RTUNI_ALPHA, /* U+010456: SHAVIAN LETTER SURE*/
+ RTUNI_ALPHA, /* U+010457: SHAVIAN LETTER CHURCH*/
+ RTUNI_ALPHA, /* U+010458: SHAVIAN LETTER YEA*/
+ RTUNI_ALPHA, /* U+010459: SHAVIAN LETTER HUNG*/
+ RTUNI_ALPHA, /* U+01045a: SHAVIAN LETTER BIB*/
+ RTUNI_ALPHA, /* U+01045b: SHAVIAN LETTER DEAD*/
+ RTUNI_ALPHA, /* U+01045c: SHAVIAN LETTER GAG*/
+ RTUNI_ALPHA, /* U+01045d: SHAVIAN LETTER VOW*/
+ RTUNI_ALPHA, /* U+01045e: SHAVIAN LETTER THEY*/
+ RTUNI_ALPHA, /* U+01045f: SHAVIAN LETTER ZOO*/
+ RTUNI_ALPHA, /* U+010460: SHAVIAN LETTER MEASURE*/
+ RTUNI_ALPHA, /* U+010461: SHAVIAN LETTER JUDGE*/
+ RTUNI_ALPHA, /* U+010462: SHAVIAN LETTER WOE*/
+ RTUNI_ALPHA, /* U+010463: SHAVIAN LETTER HA-HA*/
+ RTUNI_ALPHA, /* U+010464: SHAVIAN LETTER LOLL*/
+ RTUNI_ALPHA, /* U+010465: SHAVIAN LETTER MIME*/
+ RTUNI_ALPHA, /* U+010466: SHAVIAN LETTER IF*/
+ RTUNI_ALPHA, /* U+010467: SHAVIAN LETTER EGG*/
+ RTUNI_ALPHA, /* U+010468: SHAVIAN LETTER ASH*/
+ RTUNI_ALPHA, /* U+010469: SHAVIAN LETTER ADO*/
+ RTUNI_ALPHA, /* U+01046a: SHAVIAN LETTER ON*/
+ RTUNI_ALPHA, /* U+01046b: SHAVIAN LETTER WOOL*/
+ RTUNI_ALPHA, /* U+01046c: SHAVIAN LETTER OUT*/
+ RTUNI_ALPHA, /* U+01046d: SHAVIAN LETTER AH*/
+ RTUNI_ALPHA, /* U+01046e: SHAVIAN LETTER ROAR*/
+ RTUNI_ALPHA, /* U+01046f: SHAVIAN LETTER NUN*/
+ RTUNI_ALPHA, /* U+010470: SHAVIAN LETTER EAT*/
+ RTUNI_ALPHA, /* U+010471: SHAVIAN LETTER AGE*/
+ RTUNI_ALPHA, /* U+010472: SHAVIAN LETTER ICE*/
+ RTUNI_ALPHA, /* U+010473: SHAVIAN LETTER UP*/
+ RTUNI_ALPHA, /* U+010474: SHAVIAN LETTER OAK*/
+ RTUNI_ALPHA, /* U+010475: SHAVIAN LETTER OOZE*/
+ RTUNI_ALPHA, /* U+010476: SHAVIAN LETTER OIL*/
+ RTUNI_ALPHA, /* U+010477: SHAVIAN LETTER AWE*/
+ RTUNI_ALPHA, /* U+010478: SHAVIAN LETTER ARE*/
+ RTUNI_ALPHA, /* U+010479: SHAVIAN LETTER OR*/
+ RTUNI_ALPHA, /* U+01047a: SHAVIAN LETTER AIR*/
+ RTUNI_ALPHA, /* U+01047b: SHAVIAN LETTER ERR*/
+ RTUNI_ALPHA, /* U+01047c: SHAVIAN LETTER ARRAY*/
+ RTUNI_ALPHA, /* U+01047d: SHAVIAN LETTER EAR*/
+ RTUNI_ALPHA, /* U+01047e: SHAVIAN LETTER IAN*/
+ RTUNI_ALPHA, /* U+01047f: SHAVIAN LETTER YEW*/
+ RTUNI_ALPHA, /* U+010480: OSMANYA LETTER ALEF*/
+ RTUNI_ALPHA, /* U+010481: OSMANYA LETTER BA*/
+ RTUNI_ALPHA, /* U+010482: OSMANYA LETTER TA*/
+ RTUNI_ALPHA, /* U+010483: OSMANYA LETTER JA*/
+ RTUNI_ALPHA, /* U+010484: OSMANYA LETTER XA*/
+ RTUNI_ALPHA, /* U+010485: OSMANYA LETTER KHA*/
+ RTUNI_ALPHA, /* U+010486: OSMANYA LETTER DEEL*/
+ RTUNI_ALPHA, /* U+010487: OSMANYA LETTER RA*/
+ RTUNI_ALPHA, /* U+010488: OSMANYA LETTER SA*/
+ RTUNI_ALPHA, /* U+010489: OSMANYA LETTER SHIIN*/
+ RTUNI_ALPHA, /* U+01048a: OSMANYA LETTER DHA*/
+ RTUNI_ALPHA, /* U+01048b: OSMANYA LETTER CAYN*/
+ RTUNI_ALPHA, /* U+01048c: OSMANYA LETTER GA*/
+ RTUNI_ALPHA, /* U+01048d: OSMANYA LETTER FA*/
+ RTUNI_ALPHA, /* U+01048e: OSMANYA LETTER QAAF*/
+ RTUNI_ALPHA, /* U+01048f: OSMANYA LETTER KAAF*/
+ RTUNI_ALPHA, /* U+010490: OSMANYA LETTER LAAN*/
+ RTUNI_ALPHA, /* U+010491: OSMANYA LETTER MIIN*/
+ RTUNI_ALPHA, /* U+010492: OSMANYA LETTER NUUN*/
+ RTUNI_ALPHA, /* U+010493: OSMANYA LETTER WAW*/
+ RTUNI_ALPHA, /* U+010494: OSMANYA LETTER HA*/
+ RTUNI_ALPHA, /* U+010495: OSMANYA LETTER YA*/
+ RTUNI_ALPHA, /* U+010496: OSMANYA LETTER A*/
+ RTUNI_ALPHA, /* U+010497: OSMANYA LETTER E*/
+ RTUNI_ALPHA, /* U+010498: OSMANYA LETTER I*/
+ RTUNI_ALPHA, /* U+010499: OSMANYA LETTER O*/
+ RTUNI_ALPHA, /* U+01049a: OSMANYA LETTER U*/
+ RTUNI_ALPHA, /* U+01049b: OSMANYA LETTER AA*/
+ RTUNI_ALPHA, /* U+01049c: OSMANYA LETTER EE*/
+ RTUNI_ALPHA, /* U+01049d: OSMANYA LETTER OO*/
+ 0, /* U+01049e: */
+ 0, /* U+01049f: */
+ RTUNI_DDIGIT, /* U+0104a0: OSMANYA DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+0104a1: OSMANYA DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+0104a2: OSMANYA DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+0104a3: OSMANYA DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+0104a4: OSMANYA DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+0104a5: OSMANYA DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+0104a6: OSMANYA DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+0104a7: OSMANYA DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+0104a8: OSMANYA DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+0104a9: OSMANYA DIGIT NINE*/
+ 0, /* U+0104aa: */
+ 0, /* U+0104ab: */
+ 0, /* U+0104ac: */
+ 0, /* U+0104ad: */
+ 0, /* U+0104ae: */
+ 0, /* U+0104af: */
+ 0, /* U+0104b0: */
+ 0, /* U+0104b1: */
+ 0, /* U+0104b2: */
+ 0, /* U+0104b3: */
+ 0, /* U+0104b4: */
+ 0, /* U+0104b5: */
+ 0, /* U+0104b6: */
+ 0, /* U+0104b7: */
+ 0, /* U+0104b8: */
+ 0, /* U+0104b9: */
+ 0, /* U+0104ba: */
+ 0, /* U+0104bb: */
+ 0, /* U+0104bc: */
+ 0, /* U+0104bd: */
+ 0, /* U+0104be: */
+ 0, /* U+0104bf: */
+ 0, /* U+0104c0: */
+ 0, /* U+0104c1: */
+ 0, /* U+0104c2: */
+ 0, /* U+0104c3: */
+ 0, /* U+0104c4: */
+ 0, /* U+0104c5: */
+ 0, /* U+0104c6: */
+ 0, /* U+0104c7: */
+ 0, /* U+0104c8: */
+ 0, /* U+0104c9: */
+ 0, /* U+0104ca: */
+ 0, /* U+0104cb: */
+ 0, /* U+0104cc: */
+ 0, /* U+0104cd: */
+ 0, /* U+0104ce: */
+ 0, /* U+0104cf: */
+ 0, /* U+0104d0: */
+ 0, /* U+0104d1: */
+ 0, /* U+0104d2: */
+ 0, /* U+0104d3: */
+ 0, /* U+0104d4: */
+ 0, /* U+0104d5: */
+ 0, /* U+0104d6: */
+ 0, /* U+0104d7: */
+ 0, /* U+0104d8: */
+ 0, /* U+0104d9: */
+ 0, /* U+0104da: */
+ 0, /* U+0104db: */
+ 0, /* U+0104dc: */
+ 0, /* U+0104dd: */
+ 0, /* U+0104de: */
+ 0, /* U+0104df: */
+ 0, /* U+0104e0: */
+ 0, /* U+0104e1: */
+ 0, /* U+0104e2: */
+ 0, /* U+0104e3: */
+ 0, /* U+0104e4: */
+ 0, /* U+0104e5: */
+ 0, /* U+0104e6: */
+ 0, /* U+0104e7: */
+ 0, /* U+0104e8: */
+ 0, /* U+0104e9: */
+ 0, /* U+0104ea: */
+ 0, /* U+0104eb: */
+ 0, /* U+0104ec: */
+ 0, /* U+0104ed: */
+ 0, /* U+0104ee: */
+ 0, /* U+0104ef: */
+ 0, /* U+0104f0: */
+ 0, /* U+0104f1: */
+ 0, /* U+0104f2: */
+ 0, /* U+0104f3: */
+ 0, /* U+0104f4: */
+ 0, /* U+0104f5: */
+ 0, /* U+0104f6: */
+ 0, /* U+0104f7: */
+ 0, /* U+0104f8: */
+ 0, /* U+0104f9: */
+ 0, /* U+0104fa: */
+ 0, /* U+0104fb: */
+ 0, /* U+0104fc: */
+ 0, /* U+0104fd: */
+ 0, /* U+0104fe: */
+ 0, /* U+0104ff: */
+ 0, /* U+010500: */
+ 0, /* U+010501: */
+ 0, /* U+010502: */
+ 0, /* U+010503: */
+ 0, /* U+010504: */
+ 0, /* U+010505: */
+ 0, /* U+010506: */
+ 0, /* U+010507: */
+ 0, /* U+010508: */
+ 0, /* U+010509: */
+ 0, /* U+01050a: */
+ 0, /* U+01050b: */
+ 0, /* U+01050c: */
+ 0, /* U+01050d: */
+ 0, /* U+01050e: */
+ 0, /* U+01050f: */
+ 0, /* U+010510: */
+ 0, /* U+010511: */
+ 0, /* U+010512: */
+ 0, /* U+010513: */
+ 0, /* U+010514: */
+ 0, /* U+010515: */
+ 0, /* U+010516: */
+ 0, /* U+010517: */
+ 0, /* U+010518: */
+ 0, /* U+010519: */
+ 0, /* U+01051a: */
+ 0, /* U+01051b: */
+ 0, /* U+01051c: */
+ 0, /* U+01051d: */
+ 0, /* U+01051e: */
+ 0, /* U+01051f: */
+ 0, /* U+010520: */
+ 0, /* U+010521: */
+ 0, /* U+010522: */
+ 0, /* U+010523: */
+ 0, /* U+010524: */
+ 0, /* U+010525: */
+ 0, /* U+010526: */
+ 0, /* U+010527: */
+ 0, /* U+010528: */
+ 0, /* U+010529: */
+ 0, /* U+01052a: */
+ 0, /* U+01052b: */
+ 0, /* U+01052c: */
+ 0, /* U+01052d: */
+ 0, /* U+01052e: */
+ 0, /* U+01052f: */
+ 0, /* U+010530: */
+ 0, /* U+010531: */
+ 0, /* U+010532: */
+ 0, /* U+010533: */
+ 0, /* U+010534: */
+ 0, /* U+010535: */
+ 0, /* U+010536: */
+ 0, /* U+010537: */
+ 0, /* U+010538: */
+ 0, /* U+010539: */
+ 0, /* U+01053a: */
+ 0, /* U+01053b: */
+ 0, /* U+01053c: */
+ 0, /* U+01053d: */
+ 0, /* U+01053e: */
+ 0, /* U+01053f: */
+ 0, /* U+010540: */
+ 0, /* U+010541: */
+ 0, /* U+010542: */
+ 0, /* U+010543: */
+ 0, /* U+010544: */
+ 0, /* U+010545: */
+ 0, /* U+010546: */
+ 0, /* U+010547: */
+ 0, /* U+010548: */
+ 0, /* U+010549: */
+ 0, /* U+01054a: */
+ 0, /* U+01054b: */
+ 0, /* U+01054c: */
+ 0, /* U+01054d: */
+ 0, /* U+01054e: */
+ 0, /* U+01054f: */
+ 0, /* U+010550: */
+ 0, /* U+010551: */
+ 0, /* U+010552: */
+ 0, /* U+010553: */
+ 0, /* U+010554: */
+ 0, /* U+010555: */
+ 0, /* U+010556: */
+ 0, /* U+010557: */
+ 0, /* U+010558: */
+ 0, /* U+010559: */
+ 0, /* U+01055a: */
+ 0, /* U+01055b: */
+ 0, /* U+01055c: */
+ 0, /* U+01055d: */
+ 0, /* U+01055e: */
+ 0, /* U+01055f: */
+ 0, /* U+010560: */
+ 0, /* U+010561: */
+ 0, /* U+010562: */
+ 0, /* U+010563: */
+ 0, /* U+010564: */
+ 0, /* U+010565: */
+ 0, /* U+010566: */
+ 0, /* U+010567: */
+ 0, /* U+010568: */
+ 0, /* U+010569: */
+ 0, /* U+01056a: */
+ 0, /* U+01056b: */
+ 0, /* U+01056c: */
+ 0, /* U+01056d: */
+ 0, /* U+01056e: */
+ 0, /* U+01056f: */
+ 0, /* U+010570: */
+ 0, /* U+010571: */
+ 0, /* U+010572: */
+ 0, /* U+010573: */
+ 0, /* U+010574: */
+ 0, /* U+010575: */
+ 0, /* U+010576: */
+ 0, /* U+010577: */
+ 0, /* U+010578: */
+ 0, /* U+010579: */
+ 0, /* U+01057a: */
+ 0, /* U+01057b: */
+ 0, /* U+01057c: */
+ 0, /* U+01057d: */
+ 0, /* U+01057e: */
+ 0, /* U+01057f: */
+ 0, /* U+010580: */
+ 0, /* U+010581: */
+ 0, /* U+010582: */
+ 0, /* U+010583: */
+ 0, /* U+010584: */
+ 0, /* U+010585: */
+ 0, /* U+010586: */
+ 0, /* U+010587: */
+ 0, /* U+010588: */
+ 0, /* U+010589: */
+ 0, /* U+01058a: */
+ 0, /* U+01058b: */
+ 0, /* U+01058c: */
+ 0, /* U+01058d: */
+ 0, /* U+01058e: */
+ 0, /* U+01058f: */
+ 0, /* U+010590: */
+ 0, /* U+010591: */
+ 0, /* U+010592: */
+ 0, /* U+010593: */
+ 0, /* U+010594: */
+ 0, /* U+010595: */
+ 0, /* U+010596: */
+ 0, /* U+010597: */
+ 0, /* U+010598: */
+ 0, /* U+010599: */
+ 0, /* U+01059a: */
+ 0, /* U+01059b: */
+ 0, /* U+01059c: */
+ 0, /* U+01059d: */
+ 0, /* U+01059e: */
+ 0, /* U+01059f: */
+ 0, /* U+0105a0: */
+ 0, /* U+0105a1: */
+ 0, /* U+0105a2: */
+ 0, /* U+0105a3: */
+ 0, /* U+0105a4: */
+ 0, /* U+0105a5: */
+ 0, /* U+0105a6: */
+ 0, /* U+0105a7: */
+ 0, /* U+0105a8: */
+ 0, /* U+0105a9: */
+ 0, /* U+0105aa: */
+ 0, /* U+0105ab: */
+ 0, /* U+0105ac: */
+ 0, /* U+0105ad: */
+ 0, /* U+0105ae: */
+ 0, /* U+0105af: */
+ 0, /* U+0105b0: */
+ 0, /* U+0105b1: */
+ 0, /* U+0105b2: */
+ 0, /* U+0105b3: */
+ 0, /* U+0105b4: */
+ 0, /* U+0105b5: */
+ 0, /* U+0105b6: */
+ 0, /* U+0105b7: */
+ 0, /* U+0105b8: */
+ 0, /* U+0105b9: */
+ 0, /* U+0105ba: */
+ 0, /* U+0105bb: */
+ 0, /* U+0105bc: */
+ 0, /* U+0105bd: */
+ 0, /* U+0105be: */
+ 0, /* U+0105bf: */
+ 0, /* U+0105c0: */
+ 0, /* U+0105c1: */
+ 0, /* U+0105c2: */
+ 0, /* U+0105c3: */
+ 0, /* U+0105c4: */
+ 0, /* U+0105c5: */
+ 0, /* U+0105c6: */
+ 0, /* U+0105c7: */
+ 0, /* U+0105c8: */
+ 0, /* U+0105c9: */
+ 0, /* U+0105ca: */
+ 0, /* U+0105cb: */
+ 0, /* U+0105cc: */
+ 0, /* U+0105cd: */
+ 0, /* U+0105ce: */
+ 0, /* U+0105cf: */
+ 0, /* U+0105d0: */
+ 0, /* U+0105d1: */
+ 0, /* U+0105d2: */
+ 0, /* U+0105d3: */
+ 0, /* U+0105d4: */
+ 0, /* U+0105d5: */
+ 0, /* U+0105d6: */
+ 0, /* U+0105d7: */
+ 0, /* U+0105d8: */
+ 0, /* U+0105d9: */
+ 0, /* U+0105da: */
+ 0, /* U+0105db: */
+ 0, /* U+0105dc: */
+ 0, /* U+0105dd: */
+ 0, /* U+0105de: */
+ 0, /* U+0105df: */
+ 0, /* U+0105e0: */
+ 0, /* U+0105e1: */
+ 0, /* U+0105e2: */
+ 0, /* U+0105e3: */
+ 0, /* U+0105e4: */
+ 0, /* U+0105e5: */
+ 0, /* U+0105e6: */
+ 0, /* U+0105e7: */
+ 0, /* U+0105e8: */
+ 0, /* U+0105e9: */
+ 0, /* U+0105ea: */
+ 0, /* U+0105eb: */
+ 0, /* U+0105ec: */
+ 0, /* U+0105ed: */
+ 0, /* U+0105ee: */
+ 0, /* U+0105ef: */
+ 0, /* U+0105f0: */
+ 0, /* U+0105f1: */
+ 0, /* U+0105f2: */
+ 0, /* U+0105f3: */
+ 0, /* U+0105f4: */
+ 0, /* U+0105f5: */
+ 0, /* U+0105f6: */
+ 0, /* U+0105f7: */
+ 0, /* U+0105f8: */
+ 0, /* U+0105f9: */
+ 0, /* U+0105fa: */
+ 0, /* U+0105fb: */
+ 0, /* U+0105fc: */
+ 0, /* U+0105fd: */
+ 0, /* U+0105fe: */
+ 0, /* U+0105ff: */
+ 0, /* U+010600: */
+ 0, /* U+010601: */
+ 0, /* U+010602: */
+ 0, /* U+010603: */
+ 0, /* U+010604: */
+ 0, /* U+010605: */
+ 0, /* U+010606: */
+ 0, /* U+010607: */
+ 0, /* U+010608: */
+ 0, /* U+010609: */
+ 0, /* U+01060a: */
+ 0, /* U+01060b: */
+ 0, /* U+01060c: */
+ 0, /* U+01060d: */
+ 0, /* U+01060e: */
+ 0, /* U+01060f: */
+ 0, /* U+010610: */
+ 0, /* U+010611: */
+ 0, /* U+010612: */
+ 0, /* U+010613: */
+ 0, /* U+010614: */
+ 0, /* U+010615: */
+ 0, /* U+010616: */
+ 0, /* U+010617: */
+ 0, /* U+010618: */
+ 0, /* U+010619: */
+ 0, /* U+01061a: */
+ 0, /* U+01061b: */
+ 0, /* U+01061c: */
+ 0, /* U+01061d: */
+ 0, /* U+01061e: */
+ 0, /* U+01061f: */
+ 0, /* U+010620: */
+ 0, /* U+010621: */
+ 0, /* U+010622: */
+ 0, /* U+010623: */
+ 0, /* U+010624: */
+ 0, /* U+010625: */
+ 0, /* U+010626: */
+ 0, /* U+010627: */
+ 0, /* U+010628: */
+ 0, /* U+010629: */
+ 0, /* U+01062a: */
+ 0, /* U+01062b: */
+ 0, /* U+01062c: */
+ 0, /* U+01062d: */
+ 0, /* U+01062e: */
+ 0, /* U+01062f: */
+ 0, /* U+010630: */
+ 0, /* U+010631: */
+ 0, /* U+010632: */
+ 0, /* U+010633: */
+ 0, /* U+010634: */
+ 0, /* U+010635: */
+ 0, /* U+010636: */
+ 0, /* U+010637: */
+ 0, /* U+010638: */
+ 0, /* U+010639: */
+ 0, /* U+01063a: */
+ 0, /* U+01063b: */
+ 0, /* U+01063c: */
+ 0, /* U+01063d: */
+ 0, /* U+01063e: */
+ 0, /* U+01063f: */
+ 0, /* U+010640: */
+ 0, /* U+010641: */
+ 0, /* U+010642: */
+ 0, /* U+010643: */
+ 0, /* U+010644: */
+ 0, /* U+010645: */
+ 0, /* U+010646: */
+ 0, /* U+010647: */
+ 0, /* U+010648: */
+ 0, /* U+010649: */
+ 0, /* U+01064a: */
+ 0, /* U+01064b: */
+ 0, /* U+01064c: */
+ 0, /* U+01064d: */
+ 0, /* U+01064e: */
+ 0, /* U+01064f: */
+ 0, /* U+010650: */
+ 0, /* U+010651: */
+ 0, /* U+010652: */
+ 0, /* U+010653: */
+ 0, /* U+010654: */
+ 0, /* U+010655: */
+ 0, /* U+010656: */
+ 0, /* U+010657: */
+ 0, /* U+010658: */
+ 0, /* U+010659: */
+ 0, /* U+01065a: */
+ 0, /* U+01065b: */
+ 0, /* U+01065c: */
+ 0, /* U+01065d: */
+ 0, /* U+01065e: */
+ 0, /* U+01065f: */
+ 0, /* U+010660: */
+ 0, /* U+010661: */
+ 0, /* U+010662: */
+ 0, /* U+010663: */
+ 0, /* U+010664: */
+ 0, /* U+010665: */
+ 0, /* U+010666: */
+ 0, /* U+010667: */
+ 0, /* U+010668: */
+ 0, /* U+010669: */
+ 0, /* U+01066a: */
+ 0, /* U+01066b: */
+ 0, /* U+01066c: */
+ 0, /* U+01066d: */
+ 0, /* U+01066e: */
+ 0, /* U+01066f: */
+ 0, /* U+010670: */
+ 0, /* U+010671: */
+ 0, /* U+010672: */
+ 0, /* U+010673: */
+ 0, /* U+010674: */
+ 0, /* U+010675: */
+ 0, /* U+010676: */
+ 0, /* U+010677: */
+ 0, /* U+010678: */
+ 0, /* U+010679: */
+ 0, /* U+01067a: */
+ 0, /* U+01067b: */
+ 0, /* U+01067c: */
+ 0, /* U+01067d: */
+ 0, /* U+01067e: */
+ 0, /* U+01067f: */
+ 0, /* U+010680: */
+ 0, /* U+010681: */
+ 0, /* U+010682: */
+ 0, /* U+010683: */
+ 0, /* U+010684: */
+ 0, /* U+010685: */
+ 0, /* U+010686: */
+ 0, /* U+010687: */
+ 0, /* U+010688: */
+ 0, /* U+010689: */
+ 0, /* U+01068a: */
+ 0, /* U+01068b: */
+ 0, /* U+01068c: */
+ 0, /* U+01068d: */
+ 0, /* U+01068e: */
+ 0, /* U+01068f: */
+ 0, /* U+010690: */
+ 0, /* U+010691: */
+ 0, /* U+010692: */
+ 0, /* U+010693: */
+ 0, /* U+010694: */
+ 0, /* U+010695: */
+ 0, /* U+010696: */
+ 0, /* U+010697: */
+ 0, /* U+010698: */
+ 0, /* U+010699: */
+ 0, /* U+01069a: */
+ 0, /* U+01069b: */
+ 0, /* U+01069c: */
+ 0, /* U+01069d: */
+ 0, /* U+01069e: */
+ 0, /* U+01069f: */
+ 0, /* U+0106a0: */
+ 0, /* U+0106a1: */
+ 0, /* U+0106a2: */
+ 0, /* U+0106a3: */
+ 0, /* U+0106a4: */
+ 0, /* U+0106a5: */
+ 0, /* U+0106a6: */
+ 0, /* U+0106a7: */
+ 0, /* U+0106a8: */
+ 0, /* U+0106a9: */
+ 0, /* U+0106aa: */
+ 0, /* U+0106ab: */
+ 0, /* U+0106ac: */
+ 0, /* U+0106ad: */
+ 0, /* U+0106ae: */
+ 0, /* U+0106af: */
+ 0, /* U+0106b0: */
+ 0, /* U+0106b1: */
+ 0, /* U+0106b2: */
+ 0, /* U+0106b3: */
+ 0, /* U+0106b4: */
+ 0, /* U+0106b5: */
+ 0, /* U+0106b6: */
+ 0, /* U+0106b7: */
+ 0, /* U+0106b8: */
+ 0, /* U+0106b9: */
+ 0, /* U+0106ba: */
+ 0, /* U+0106bb: */
+ 0, /* U+0106bc: */
+ 0, /* U+0106bd: */
+ 0, /* U+0106be: */
+ 0, /* U+0106bf: */
+ 0, /* U+0106c0: */
+ 0, /* U+0106c1: */
+ 0, /* U+0106c2: */
+ 0, /* U+0106c3: */
+ 0, /* U+0106c4: */
+ 0, /* U+0106c5: */
+ 0, /* U+0106c6: */
+ 0, /* U+0106c7: */
+ 0, /* U+0106c8: */
+ 0, /* U+0106c9: */
+ 0, /* U+0106ca: */
+ 0, /* U+0106cb: */
+ 0, /* U+0106cc: */
+ 0, /* U+0106cd: */
+ 0, /* U+0106ce: */
+ 0, /* U+0106cf: */
+ 0, /* U+0106d0: */
+ 0, /* U+0106d1: */
+ 0, /* U+0106d2: */
+ 0, /* U+0106d3: */
+ 0, /* U+0106d4: */
+ 0, /* U+0106d5: */
+ 0, /* U+0106d6: */
+ 0, /* U+0106d7: */
+ 0, /* U+0106d8: */
+ 0, /* U+0106d9: */
+ 0, /* U+0106da: */
+ 0, /* U+0106db: */
+ 0, /* U+0106dc: */
+ 0, /* U+0106dd: */
+ 0, /* U+0106de: */
+ 0, /* U+0106df: */
+ 0, /* U+0106e0: */
+ 0, /* U+0106e1: */
+ 0, /* U+0106e2: */
+ 0, /* U+0106e3: */
+ 0, /* U+0106e4: */
+ 0, /* U+0106e5: */
+ 0, /* U+0106e6: */
+ 0, /* U+0106e7: */
+ 0, /* U+0106e8: */
+ 0, /* U+0106e9: */
+ 0, /* U+0106ea: */
+ 0, /* U+0106eb: */
+ 0, /* U+0106ec: */
+ 0, /* U+0106ed: */
+ 0, /* U+0106ee: */
+ 0, /* U+0106ef: */
+ 0, /* U+0106f0: */
+ 0, /* U+0106f1: */
+ 0, /* U+0106f2: */
+ 0, /* U+0106f3: */
+ 0, /* U+0106f4: */
+ 0, /* U+0106f5: */
+ 0, /* U+0106f6: */
+ 0, /* U+0106f7: */
+ 0, /* U+0106f8: */
+ 0, /* U+0106f9: */
+ 0, /* U+0106fa: */
+ 0, /* U+0106fb: */
+ 0, /* U+0106fc: */
+ 0, /* U+0106fd: */
+ 0, /* U+0106fe: */
+ 0, /* U+0106ff: */
+ 0, /* U+010700: */
+ 0, /* U+010701: */
+ 0, /* U+010702: */
+ 0, /* U+010703: */
+ 0, /* U+010704: */
+ 0, /* U+010705: */
+ 0, /* U+010706: */
+ 0, /* U+010707: */
+ 0, /* U+010708: */
+ 0, /* U+010709: */
+ 0, /* U+01070a: */
+ 0, /* U+01070b: */
+ 0, /* U+01070c: */
+ 0, /* U+01070d: */
+ 0, /* U+01070e: */
+ 0, /* U+01070f: */
+ 0, /* U+010710: */
+ 0, /* U+010711: */
+ 0, /* U+010712: */
+ 0, /* U+010713: */
+ 0, /* U+010714: */
+ 0, /* U+010715: */
+ 0, /* U+010716: */
+ 0, /* U+010717: */
+ 0, /* U+010718: */
+ 0, /* U+010719: */
+ 0, /* U+01071a: */
+ 0, /* U+01071b: */
+ 0, /* U+01071c: */
+ 0, /* U+01071d: */
+ 0, /* U+01071e: */
+ 0, /* U+01071f: */
+ 0, /* U+010720: */
+ 0, /* U+010721: */
+ 0, /* U+010722: */
+ 0, /* U+010723: */
+ 0, /* U+010724: */
+ 0, /* U+010725: */
+ 0, /* U+010726: */
+ 0, /* U+010727: */
+ 0, /* U+010728: */
+ 0, /* U+010729: */
+ 0, /* U+01072a: */
+ 0, /* U+01072b: */
+ 0, /* U+01072c: */
+ 0, /* U+01072d: */
+ 0, /* U+01072e: */
+ 0, /* U+01072f: */
+ 0, /* U+010730: */
+ 0, /* U+010731: */
+ 0, /* U+010732: */
+ 0, /* U+010733: */
+ 0, /* U+010734: */
+ 0, /* U+010735: */
+ 0, /* U+010736: */
+ 0, /* U+010737: */
+ 0, /* U+010738: */
+ 0, /* U+010739: */
+ 0, /* U+01073a: */
+ 0, /* U+01073b: */
+ 0, /* U+01073c: */
+ 0, /* U+01073d: */
+ 0, /* U+01073e: */
+ 0, /* U+01073f: */
+ 0, /* U+010740: */
+ 0, /* U+010741: */
+ 0, /* U+010742: */
+ 0, /* U+010743: */
+ 0, /* U+010744: */
+ 0, /* U+010745: */
+ 0, /* U+010746: */
+ 0, /* U+010747: */
+ 0, /* U+010748: */
+ 0, /* U+010749: */
+ 0, /* U+01074a: */
+ 0, /* U+01074b: */
+ 0, /* U+01074c: */
+ 0, /* U+01074d: */
+ 0, /* U+01074e: */
+ 0, /* U+01074f: */
+ 0, /* U+010750: */
+ 0, /* U+010751: */
+ 0, /* U+010752: */
+ 0, /* U+010753: */
+ 0, /* U+010754: */
+ 0, /* U+010755: */
+ 0, /* U+010756: */
+ 0, /* U+010757: */
+ 0, /* U+010758: */
+ 0, /* U+010759: */
+ 0, /* U+01075a: */
+ 0, /* U+01075b: */
+ 0, /* U+01075c: */
+ 0, /* U+01075d: */
+ 0, /* U+01075e: */
+ 0, /* U+01075f: */
+ 0, /* U+010760: */
+ 0, /* U+010761: */
+ 0, /* U+010762: */
+ 0, /* U+010763: */
+ 0, /* U+010764: */
+ 0, /* U+010765: */
+ 0, /* U+010766: */
+ 0, /* U+010767: */
+ 0, /* U+010768: */
+ 0, /* U+010769: */
+ 0, /* U+01076a: */
+ 0, /* U+01076b: */
+ 0, /* U+01076c: */
+ 0, /* U+01076d: */
+ 0, /* U+01076e: */
+ 0, /* U+01076f: */
+ 0, /* U+010770: */
+ 0, /* U+010771: */
+ 0, /* U+010772: */
+ 0, /* U+010773: */
+ 0, /* U+010774: */
+ 0, /* U+010775: */
+ 0, /* U+010776: */
+ 0, /* U+010777: */
+ 0, /* U+010778: */
+ 0, /* U+010779: */
+ 0, /* U+01077a: */
+ 0, /* U+01077b: */
+ 0, /* U+01077c: */
+ 0, /* U+01077d: */
+ 0, /* U+01077e: */
+ 0, /* U+01077f: */
+ 0, /* U+010780: */
+ 0, /* U+010781: */
+ 0, /* U+010782: */
+ 0, /* U+010783: */
+ 0, /* U+010784: */
+ 0, /* U+010785: */
+ 0, /* U+010786: */
+ 0, /* U+010787: */
+ 0, /* U+010788: */
+ 0, /* U+010789: */
+ 0, /* U+01078a: */
+ 0, /* U+01078b: */
+ 0, /* U+01078c: */
+ 0, /* U+01078d: */
+ 0, /* U+01078e: */
+ 0, /* U+01078f: */
+ 0, /* U+010790: */
+ 0, /* U+010791: */
+ 0, /* U+010792: */
+ 0, /* U+010793: */
+ 0, /* U+010794: */
+ 0, /* U+010795: */
+ 0, /* U+010796: */
+ 0, /* U+010797: */
+ 0, /* U+010798: */
+ 0, /* U+010799: */
+ 0, /* U+01079a: */
+ 0, /* U+01079b: */
+ 0, /* U+01079c: */
+ 0, /* U+01079d: */
+ 0, /* U+01079e: */
+ 0, /* U+01079f: */
+ 0, /* U+0107a0: */
+ 0, /* U+0107a1: */
+ 0, /* U+0107a2: */
+ 0, /* U+0107a3: */
+ 0, /* U+0107a4: */
+ 0, /* U+0107a5: */
+ 0, /* U+0107a6: */
+ 0, /* U+0107a7: */
+ 0, /* U+0107a8: */
+ 0, /* U+0107a9: */
+ 0, /* U+0107aa: */
+ 0, /* U+0107ab: */
+ 0, /* U+0107ac: */
+ 0, /* U+0107ad: */
+ 0, /* U+0107ae: */
+ 0, /* U+0107af: */
+ 0, /* U+0107b0: */
+ 0, /* U+0107b1: */
+ 0, /* U+0107b2: */
+ 0, /* U+0107b3: */
+ 0, /* U+0107b4: */
+ 0, /* U+0107b5: */
+ 0, /* U+0107b6: */
+ 0, /* U+0107b7: */
+ 0, /* U+0107b8: */
+ 0, /* U+0107b9: */
+ 0, /* U+0107ba: */
+ 0, /* U+0107bb: */
+ 0, /* U+0107bc: */
+ 0, /* U+0107bd: */
+ 0, /* U+0107be: */
+ 0, /* U+0107bf: */
+ 0, /* U+0107c0: */
+ 0, /* U+0107c1: */
+ 0, /* U+0107c2: */
+ 0, /* U+0107c3: */
+ 0, /* U+0107c4: */
+ 0, /* U+0107c5: */
+ 0, /* U+0107c6: */
+ 0, /* U+0107c7: */
+ 0, /* U+0107c8: */
+ 0, /* U+0107c9: */
+ 0, /* U+0107ca: */
+ 0, /* U+0107cb: */
+ 0, /* U+0107cc: */
+ 0, /* U+0107cd: */
+ 0, /* U+0107ce: */
+ 0, /* U+0107cf: */
+ 0, /* U+0107d0: */
+ 0, /* U+0107d1: */
+ 0, /* U+0107d2: */
+ 0, /* U+0107d3: */
+ 0, /* U+0107d4: */
+ 0, /* U+0107d5: */
+ 0, /* U+0107d6: */
+ 0, /* U+0107d7: */
+ 0, /* U+0107d8: */
+ 0, /* U+0107d9: */
+ 0, /* U+0107da: */
+ 0, /* U+0107db: */
+ 0, /* U+0107dc: */
+ 0, /* U+0107dd: */
+ 0, /* U+0107de: */
+ 0, /* U+0107df: */
+ 0, /* U+0107e0: */
+ 0, /* U+0107e1: */
+ 0, /* U+0107e2: */
+ 0, /* U+0107e3: */
+ 0, /* U+0107e4: */
+ 0, /* U+0107e5: */
+ 0, /* U+0107e6: */
+ 0, /* U+0107e7: */
+ 0, /* U+0107e8: */
+ 0, /* U+0107e9: */
+ 0, /* U+0107ea: */
+ 0, /* U+0107eb: */
+ 0, /* U+0107ec: */
+ 0, /* U+0107ed: */
+ 0, /* U+0107ee: */
+ 0, /* U+0107ef: */
+ 0, /* U+0107f0: */
+ 0, /* U+0107f1: */
+ 0, /* U+0107f2: */
+ 0, /* U+0107f3: */
+ 0, /* U+0107f4: */
+ 0, /* U+0107f5: */
+ 0, /* U+0107f6: */
+ 0, /* U+0107f7: */
+ 0, /* U+0107f8: */
+ 0, /* U+0107f9: */
+ 0, /* U+0107fa: */
+ 0, /* U+0107fb: */
+ 0, /* U+0107fc: */
+ 0, /* U+0107fd: */
+ 0, /* U+0107fe: */
+ 0, /* U+0107ff: */
+ RTUNI_ALPHA, /* U+010800: CYPRIOT SYLLABLE A*/
+ RTUNI_ALPHA, /* U+010801: CYPRIOT SYLLABLE E*/
+ RTUNI_ALPHA, /* U+010802: CYPRIOT SYLLABLE I*/
+ RTUNI_ALPHA, /* U+010803: CYPRIOT SYLLABLE O*/
+ RTUNI_ALPHA, /* U+010804: CYPRIOT SYLLABLE U*/
+ RTUNI_ALPHA, /* U+010805: CYPRIOT SYLLABLE JA*/
+ 0, /* U+010806: */
+ 0, /* U+010807: */
+ RTUNI_ALPHA, /* U+010808: CYPRIOT SYLLABLE JO*/
+ 0, /* U+010809: */
+ RTUNI_ALPHA, /* U+01080a: CYPRIOT SYLLABLE KA*/
+ RTUNI_ALPHA, /* U+01080b: CYPRIOT SYLLABLE KE*/
+ RTUNI_ALPHA, /* U+01080c: CYPRIOT SYLLABLE KI*/
+ RTUNI_ALPHA, /* U+01080d: CYPRIOT SYLLABLE KO*/
+ RTUNI_ALPHA, /* U+01080e: CYPRIOT SYLLABLE KU*/
+ RTUNI_ALPHA, /* U+01080f: CYPRIOT SYLLABLE LA*/
+ RTUNI_ALPHA, /* U+010810: CYPRIOT SYLLABLE LE*/
+ RTUNI_ALPHA, /* U+010811: CYPRIOT SYLLABLE LI*/
+ RTUNI_ALPHA, /* U+010812: CYPRIOT SYLLABLE LO*/
+ RTUNI_ALPHA, /* U+010813: CYPRIOT SYLLABLE LU*/
+ RTUNI_ALPHA, /* U+010814: CYPRIOT SYLLABLE MA*/
+ RTUNI_ALPHA, /* U+010815: CYPRIOT SYLLABLE ME*/
+ RTUNI_ALPHA, /* U+010816: CYPRIOT SYLLABLE MI*/
+ RTUNI_ALPHA, /* U+010817: CYPRIOT SYLLABLE MO*/
+ RTUNI_ALPHA, /* U+010818: CYPRIOT SYLLABLE MU*/
+ RTUNI_ALPHA, /* U+010819: CYPRIOT SYLLABLE NA*/
+ RTUNI_ALPHA, /* U+01081a: CYPRIOT SYLLABLE NE*/
+ RTUNI_ALPHA, /* U+01081b: CYPRIOT SYLLABLE NI*/
+ RTUNI_ALPHA, /* U+01081c: CYPRIOT SYLLABLE NO*/
+ RTUNI_ALPHA, /* U+01081d: CYPRIOT SYLLABLE NU*/
+ RTUNI_ALPHA, /* U+01081e: CYPRIOT SYLLABLE PA*/
+ RTUNI_ALPHA, /* U+01081f: CYPRIOT SYLLABLE PE*/
+ RTUNI_ALPHA, /* U+010820: CYPRIOT SYLLABLE PI*/
+ RTUNI_ALPHA, /* U+010821: CYPRIOT SYLLABLE PO*/
+ RTUNI_ALPHA, /* U+010822: CYPRIOT SYLLABLE PU*/
+ RTUNI_ALPHA, /* U+010823: CYPRIOT SYLLABLE RA*/
+ RTUNI_ALPHA, /* U+010824: CYPRIOT SYLLABLE RE*/
+ RTUNI_ALPHA, /* U+010825: CYPRIOT SYLLABLE RI*/
+ RTUNI_ALPHA, /* U+010826: CYPRIOT SYLLABLE RO*/
+ RTUNI_ALPHA, /* U+010827: CYPRIOT SYLLABLE RU*/
+ RTUNI_ALPHA, /* U+010828: CYPRIOT SYLLABLE SA*/
+ RTUNI_ALPHA, /* U+010829: CYPRIOT SYLLABLE SE*/
+ RTUNI_ALPHA, /* U+01082a: CYPRIOT SYLLABLE SI*/
+ RTUNI_ALPHA, /* U+01082b: CYPRIOT SYLLABLE SO*/
+ RTUNI_ALPHA, /* U+01082c: CYPRIOT SYLLABLE SU*/
+ RTUNI_ALPHA, /* U+01082d: CYPRIOT SYLLABLE TA*/
+ RTUNI_ALPHA, /* U+01082e: CYPRIOT SYLLABLE TE*/
+ RTUNI_ALPHA, /* U+01082f: CYPRIOT SYLLABLE TI*/
+ RTUNI_ALPHA, /* U+010830: CYPRIOT SYLLABLE TO*/
+ RTUNI_ALPHA, /* U+010831: CYPRIOT SYLLABLE TU*/
+ RTUNI_ALPHA, /* U+010832: CYPRIOT SYLLABLE WA*/
+ RTUNI_ALPHA, /* U+010833: CYPRIOT SYLLABLE WE*/
+ RTUNI_ALPHA, /* U+010834: CYPRIOT SYLLABLE WI*/
+ RTUNI_ALPHA, /* U+010835: CYPRIOT SYLLABLE WO*/
+ 0, /* U+010836: */
+ RTUNI_ALPHA, /* U+010837: CYPRIOT SYLLABLE XA*/
+ RTUNI_ALPHA, /* U+010838: CYPRIOT SYLLABLE XE*/
+ 0, /* U+010839: */
+ 0, /* U+01083a: */
+ 0, /* U+01083b: */
+ RTUNI_ALPHA, /* U+01083c: CYPRIOT SYLLABLE ZA*/
+ 0, /* U+01083d: */
+ 0, /* U+01083e: */
+ RTUNI_ALPHA, /* U+01083f: CYPRIOT SYLLABLE ZO*/
+ RTUNI_ALPHA, /* U+010840: IMPERIAL ARAMAIC LETTER ALEPH*/
+ RTUNI_ALPHA, /* U+010841: IMPERIAL ARAMAIC LETTER BETH*/
+ RTUNI_ALPHA, /* U+010842: IMPERIAL ARAMAIC LETTER GIMEL*/
+ RTUNI_ALPHA, /* U+010843: IMPERIAL ARAMAIC LETTER DALETH*/
+ RTUNI_ALPHA, /* U+010844: IMPERIAL ARAMAIC LETTER HE*/
+ RTUNI_ALPHA, /* U+010845: IMPERIAL ARAMAIC LETTER WAW*/
+ RTUNI_ALPHA, /* U+010846: IMPERIAL ARAMAIC LETTER ZAYIN*/
+ RTUNI_ALPHA, /* U+010847: IMPERIAL ARAMAIC LETTER HETH*/
+ RTUNI_ALPHA, /* U+010848: IMPERIAL ARAMAIC LETTER TETH*/
+ RTUNI_ALPHA, /* U+010849: IMPERIAL ARAMAIC LETTER YODH*/
+ RTUNI_ALPHA, /* U+01084a: IMPERIAL ARAMAIC LETTER KAPH*/
+ RTUNI_ALPHA, /* U+01084b: IMPERIAL ARAMAIC LETTER LAMEDH*/
+ RTUNI_ALPHA, /* U+01084c: IMPERIAL ARAMAIC LETTER MEM*/
+ RTUNI_ALPHA, /* U+01084d: IMPERIAL ARAMAIC LETTER NUN*/
+ RTUNI_ALPHA, /* U+01084e: IMPERIAL ARAMAIC LETTER SAMEKH*/
+ RTUNI_ALPHA, /* U+01084f: IMPERIAL ARAMAIC LETTER AYIN*/
+ RTUNI_ALPHA, /* U+010850: IMPERIAL ARAMAIC LETTER PE*/
+ RTUNI_ALPHA, /* U+010851: IMPERIAL ARAMAIC LETTER SADHE*/
+ RTUNI_ALPHA, /* U+010852: IMPERIAL ARAMAIC LETTER QOPH*/
+ RTUNI_ALPHA, /* U+010853: IMPERIAL ARAMAIC LETTER RESH*/
+ RTUNI_ALPHA, /* U+010854: IMPERIAL ARAMAIC LETTER SHIN*/
+ RTUNI_ALPHA, /* U+010855: IMPERIAL ARAMAIC LETTER TAW*/
+ 0, /* U+010856: */
+ 0, /* U+010857: IMPERIAL ARAMAIC SECTION SIGN*/
+ 0, /* U+010858: IMPERIAL ARAMAIC NUMBER ONE*/
+ 0, /* U+010859: IMPERIAL ARAMAIC NUMBER TWO*/
+ 0, /* U+01085a: IMPERIAL ARAMAIC NUMBER THREE*/
+ 0, /* U+01085b: IMPERIAL ARAMAIC NUMBER TEN*/
+ 0, /* U+01085c: IMPERIAL ARAMAIC NUMBER TWENTY*/
+ 0, /* U+01085d: IMPERIAL ARAMAIC NUMBER ONE HUNDRED*/
+ 0, /* U+01085e: IMPERIAL ARAMAIC NUMBER ONE THOUSAND*/
+ 0, /* U+01085f: IMPERIAL ARAMAIC NUMBER TEN THOUSAND*/
+ 0, /* U+010860: */
+ 0, /* U+010861: */
+ 0, /* U+010862: */
+ 0, /* U+010863: */
+ 0, /* U+010864: */
+ 0, /* U+010865: */
+ 0, /* U+010866: */
+ 0, /* U+010867: */
+ 0, /* U+010868: */
+ 0, /* U+010869: */
+ 0, /* U+01086a: */
+ 0, /* U+01086b: */
+ 0, /* U+01086c: */
+ 0, /* U+01086d: */
+ 0, /* U+01086e: */
+ 0, /* U+01086f: */
+ 0, /* U+010870: */
+ 0, /* U+010871: */
+ 0, /* U+010872: */
+ 0, /* U+010873: */
+ 0, /* U+010874: */
+ 0, /* U+010875: */
+ 0, /* U+010876: */
+ 0, /* U+010877: */
+ 0, /* U+010878: */
+ 0, /* U+010879: */
+ 0, /* U+01087a: */
+ 0, /* U+01087b: */
+ 0, /* U+01087c: */
+ 0, /* U+01087d: */
+ 0, /* U+01087e: */
+ 0, /* U+01087f: */
+ 0, /* U+010880: */
+ 0, /* U+010881: */
+ 0, /* U+010882: */
+ 0, /* U+010883: */
+ 0, /* U+010884: */
+ 0, /* U+010885: */
+ 0, /* U+010886: */
+ 0, /* U+010887: */
+ 0, /* U+010888: */
+ 0, /* U+010889: */
+ 0, /* U+01088a: */
+ 0, /* U+01088b: */
+ 0, /* U+01088c: */
+ 0, /* U+01088d: */
+ 0, /* U+01088e: */
+ 0, /* U+01088f: */
+ 0, /* U+010890: */
+ 0, /* U+010891: */
+ 0, /* U+010892: */
+ 0, /* U+010893: */
+ 0, /* U+010894: */
+ 0, /* U+010895: */
+ 0, /* U+010896: */
+ 0, /* U+010897: */
+ 0, /* U+010898: */
+ 0, /* U+010899: */
+ 0, /* U+01089a: */
+ 0, /* U+01089b: */
+ 0, /* U+01089c: */
+ 0, /* U+01089d: */
+ 0, /* U+01089e: */
+ 0, /* U+01089f: */
+ 0, /* U+0108a0: */
+ 0, /* U+0108a1: */
+ 0, /* U+0108a2: */
+ 0, /* U+0108a3: */
+ 0, /* U+0108a4: */
+ 0, /* U+0108a5: */
+ 0, /* U+0108a6: */
+ 0, /* U+0108a7: */
+ 0, /* U+0108a8: */
+ 0, /* U+0108a9: */
+ 0, /* U+0108aa: */
+ 0, /* U+0108ab: */
+ 0, /* U+0108ac: */
+ 0, /* U+0108ad: */
+ 0, /* U+0108ae: */
+ 0, /* U+0108af: */
+ 0, /* U+0108b0: */
+ 0, /* U+0108b1: */
+ 0, /* U+0108b2: */
+ 0, /* U+0108b3: */
+ 0, /* U+0108b4: */
+ 0, /* U+0108b5: */
+ 0, /* U+0108b6: */
+ 0, /* U+0108b7: */
+ 0, /* U+0108b8: */
+ 0, /* U+0108b9: */
+ 0, /* U+0108ba: */
+ 0, /* U+0108bb: */
+ 0, /* U+0108bc: */
+ 0, /* U+0108bd: */
+ 0, /* U+0108be: */
+ 0, /* U+0108bf: */
+ 0, /* U+0108c0: */
+ 0, /* U+0108c1: */
+ 0, /* U+0108c2: */
+ 0, /* U+0108c3: */
+ 0, /* U+0108c4: */
+ 0, /* U+0108c5: */
+ 0, /* U+0108c6: */
+ 0, /* U+0108c7: */
+ 0, /* U+0108c8: */
+ 0, /* U+0108c9: */
+ 0, /* U+0108ca: */
+ 0, /* U+0108cb: */
+ 0, /* U+0108cc: */
+ 0, /* U+0108cd: */
+ 0, /* U+0108ce: */
+ 0, /* U+0108cf: */
+ 0, /* U+0108d0: */
+ 0, /* U+0108d1: */
+ 0, /* U+0108d2: */
+ 0, /* U+0108d3: */
+ 0, /* U+0108d4: */
+ 0, /* U+0108d5: */
+ 0, /* U+0108d6: */
+ 0, /* U+0108d7: */
+ 0, /* U+0108d8: */
+ 0, /* U+0108d9: */
+ 0, /* U+0108da: */
+ 0, /* U+0108db: */
+ 0, /* U+0108dc: */
+ 0, /* U+0108dd: */
+ 0, /* U+0108de: */
+ 0, /* U+0108df: */
+ 0, /* U+0108e0: */
+ 0, /* U+0108e1: */
+ 0, /* U+0108e2: */
+ 0, /* U+0108e3: */
+ 0, /* U+0108e4: */
+ 0, /* U+0108e5: */
+ 0, /* U+0108e6: */
+ 0, /* U+0108e7: */
+ 0, /* U+0108e8: */
+ 0, /* U+0108e9: */
+ 0, /* U+0108ea: */
+ 0, /* U+0108eb: */
+ 0, /* U+0108ec: */
+ 0, /* U+0108ed: */
+ 0, /* U+0108ee: */
+ 0, /* U+0108ef: */
+ 0, /* U+0108f0: */
+ 0, /* U+0108f1: */
+ 0, /* U+0108f2: */
+ 0, /* U+0108f3: */
+ 0, /* U+0108f4: */
+ 0, /* U+0108f5: */
+ 0, /* U+0108f6: */
+ 0, /* U+0108f7: */
+ 0, /* U+0108f8: */
+ 0, /* U+0108f9: */
+ 0, /* U+0108fa: */
+ 0, /* U+0108fb: */
+ 0, /* U+0108fc: */
+ 0, /* U+0108fd: */
+ 0, /* U+0108fe: */
+ 0, /* U+0108ff: */
+ RTUNI_ALPHA, /* U+010900: PHOENICIAN LETTER ALF*/
+ RTUNI_ALPHA, /* U+010901: PHOENICIAN LETTER BET*/
+ RTUNI_ALPHA, /* U+010902: PHOENICIAN LETTER GAML*/
+ RTUNI_ALPHA, /* U+010903: PHOENICIAN LETTER DELT*/
+ RTUNI_ALPHA, /* U+010904: PHOENICIAN LETTER HE*/
+ RTUNI_ALPHA, /* U+010905: PHOENICIAN LETTER WAU*/
+ RTUNI_ALPHA, /* U+010906: PHOENICIAN LETTER ZAI*/
+ RTUNI_ALPHA, /* U+010907: PHOENICIAN LETTER HET*/
+ RTUNI_ALPHA, /* U+010908: PHOENICIAN LETTER TET*/
+ RTUNI_ALPHA, /* U+010909: PHOENICIAN LETTER YOD*/
+ RTUNI_ALPHA, /* U+01090a: PHOENICIAN LETTER KAF*/
+ RTUNI_ALPHA, /* U+01090b: PHOENICIAN LETTER LAMD*/
+ RTUNI_ALPHA, /* U+01090c: PHOENICIAN LETTER MEM*/
+ RTUNI_ALPHA, /* U+01090d: PHOENICIAN LETTER NUN*/
+ RTUNI_ALPHA, /* U+01090e: PHOENICIAN LETTER SEMK*/
+ RTUNI_ALPHA, /* U+01090f: PHOENICIAN LETTER AIN*/
+ RTUNI_ALPHA, /* U+010910: PHOENICIAN LETTER PE*/
+ RTUNI_ALPHA, /* U+010911: PHOENICIAN LETTER SADE*/
+ RTUNI_ALPHA, /* U+010912: PHOENICIAN LETTER QOF*/
+ RTUNI_ALPHA, /* U+010913: PHOENICIAN LETTER ROSH*/
+ RTUNI_ALPHA, /* U+010914: PHOENICIAN LETTER SHIN*/
+ RTUNI_ALPHA, /* U+010915: PHOENICIAN LETTER TAU*/
+ 0, /* U+010916: PHOENICIAN NUMBER ONE*/
+ 0, /* U+010917: PHOENICIAN NUMBER TEN*/
+ 0, /* U+010918: PHOENICIAN NUMBER TWENTY*/
+ 0, /* U+010919: PHOENICIAN NUMBER ONE HUNDRED*/
+ 0, /* U+01091a: PHOENICIAN NUMBER TWO*/
+ 0, /* U+01091b: PHOENICIAN NUMBER THREE*/
+ 0, /* U+01091c: */
+ 0, /* U+01091d: */
+ 0, /* U+01091e: */
+ 0, /* U+01091f: PHOENICIAN WORD SEPARATOR*/
+ RTUNI_ALPHA, /* U+010920: LYDIAN LETTER A*/
+ RTUNI_ALPHA, /* U+010921: LYDIAN LETTER B*/
+ RTUNI_ALPHA, /* U+010922: LYDIAN LETTER G*/
+ RTUNI_ALPHA, /* U+010923: LYDIAN LETTER D*/
+ RTUNI_ALPHA, /* U+010924: LYDIAN LETTER E*/
+ RTUNI_ALPHA, /* U+010925: LYDIAN LETTER V*/
+ RTUNI_ALPHA, /* U+010926: LYDIAN LETTER I*/
+ RTUNI_ALPHA, /* U+010927: LYDIAN LETTER Y*/
+ RTUNI_ALPHA, /* U+010928: LYDIAN LETTER K*/
+ RTUNI_ALPHA, /* U+010929: LYDIAN LETTER L*/
+ RTUNI_ALPHA, /* U+01092a: LYDIAN LETTER M*/
+ RTUNI_ALPHA, /* U+01092b: LYDIAN LETTER N*/
+ RTUNI_ALPHA, /* U+01092c: LYDIAN LETTER O*/
+ RTUNI_ALPHA, /* U+01092d: LYDIAN LETTER R*/
+ RTUNI_ALPHA, /* U+01092e: LYDIAN LETTER SS*/
+ RTUNI_ALPHA, /* U+01092f: LYDIAN LETTER T*/
+ RTUNI_ALPHA, /* U+010930: LYDIAN LETTER U*/
+ RTUNI_ALPHA, /* U+010931: LYDIAN LETTER F*/
+ RTUNI_ALPHA, /* U+010932: LYDIAN LETTER Q*/
+ RTUNI_ALPHA, /* U+010933: LYDIAN LETTER S*/
+ RTUNI_ALPHA, /* U+010934: LYDIAN LETTER TT*/
+ RTUNI_ALPHA, /* U+010935: LYDIAN LETTER AN*/
+ RTUNI_ALPHA, /* U+010936: LYDIAN LETTER EN*/
+ RTUNI_ALPHA, /* U+010937: LYDIAN LETTER LY*/
+ RTUNI_ALPHA, /* U+010938: LYDIAN LETTER NN*/
+ RTUNI_ALPHA, /* U+010939: LYDIAN LETTER C*/
+ 0, /* U+01093a: */
+ 0, /* U+01093b: */
+ 0, /* U+01093c: */
+ 0, /* U+01093d: */
+ 0, /* U+01093e: */
+ 0, /* U+01093f: LYDIAN TRIANGULAR MARK*/
+ 0, /* U+010940: */
+ 0, /* U+010941: */
+ 0, /* U+010942: */
+ 0, /* U+010943: */
+ 0, /* U+010944: */
+ 0, /* U+010945: */
+ 0, /* U+010946: */
+ 0, /* U+010947: */
+ 0, /* U+010948: */
+ 0, /* U+010949: */
+ 0, /* U+01094a: */
+ 0, /* U+01094b: */
+ 0, /* U+01094c: */
+ 0, /* U+01094d: */
+ 0, /* U+01094e: */
+ 0, /* U+01094f: */
+ 0, /* U+010950: */
+ 0, /* U+010951: */
+ 0, /* U+010952: */
+ 0, /* U+010953: */
+ 0, /* U+010954: */
+ 0, /* U+010955: */
+ 0, /* U+010956: */
+ 0, /* U+010957: */
+ 0, /* U+010958: */
+ 0, /* U+010959: */
+ 0, /* U+01095a: */
+ 0, /* U+01095b: */
+ 0, /* U+01095c: */
+ 0, /* U+01095d: */
+ 0, /* U+01095e: */
+ 0, /* U+01095f: */
+ 0, /* U+010960: */
+ 0, /* U+010961: */
+ 0, /* U+010962: */
+ 0, /* U+010963: */
+ 0, /* U+010964: */
+ 0, /* U+010965: */
+ 0, /* U+010966: */
+ 0, /* U+010967: */
+ 0, /* U+010968: */
+ 0, /* U+010969: */
+ 0, /* U+01096a: */
+ 0, /* U+01096b: */
+ 0, /* U+01096c: */
+ 0, /* U+01096d: */
+ 0, /* U+01096e: */
+ 0, /* U+01096f: */
+ 0, /* U+010970: */
+ 0, /* U+010971: */
+ 0, /* U+010972: */
+ 0, /* U+010973: */
+ 0, /* U+010974: */
+ 0, /* U+010975: */
+ 0, /* U+010976: */
+ 0, /* U+010977: */
+ 0, /* U+010978: */
+ 0, /* U+010979: */
+ 0, /* U+01097a: */
+ 0, /* U+01097b: */
+ 0, /* U+01097c: */
+ 0, /* U+01097d: */
+ 0, /* U+01097e: */
+ 0, /* U+01097f: */
+ RTUNI_ALPHA, /* U+010980: MEROITIC HIEROGLYPHIC LETTER A*/
+ RTUNI_ALPHA, /* U+010981: MEROITIC HIEROGLYPHIC LETTER E*/
+ RTUNI_ALPHA, /* U+010982: MEROITIC HIEROGLYPHIC LETTER I*/
+ RTUNI_ALPHA, /* U+010983: MEROITIC HIEROGLYPHIC LETTER O*/
+ RTUNI_ALPHA, /* U+010984: MEROITIC HIEROGLYPHIC LETTER YA*/
+ RTUNI_ALPHA, /* U+010985: MEROITIC HIEROGLYPHIC LETTER WA*/
+ RTUNI_ALPHA, /* U+010986: MEROITIC HIEROGLYPHIC LETTER BA*/
+ RTUNI_ALPHA, /* U+010987: MEROITIC HIEROGLYPHIC LETTER BA-2*/
+ RTUNI_ALPHA, /* U+010988: MEROITIC HIEROGLYPHIC LETTER PA*/
+ RTUNI_ALPHA, /* U+010989: MEROITIC HIEROGLYPHIC LETTER MA*/
+ RTUNI_ALPHA, /* U+01098a: MEROITIC HIEROGLYPHIC LETTER NA*/
+ RTUNI_ALPHA, /* U+01098b: MEROITIC HIEROGLYPHIC LETTER NA-2*/
+ RTUNI_ALPHA, /* U+01098c: MEROITIC HIEROGLYPHIC LETTER NE*/
+ RTUNI_ALPHA, /* U+01098d: MEROITIC HIEROGLYPHIC LETTER NE-2*/
+ RTUNI_ALPHA, /* U+01098e: MEROITIC HIEROGLYPHIC LETTER RA*/
+ RTUNI_ALPHA, /* U+01098f: MEROITIC HIEROGLYPHIC LETTER RA-2*/
+ RTUNI_ALPHA, /* U+010990: MEROITIC HIEROGLYPHIC LETTER LA*/
+ RTUNI_ALPHA, /* U+010991: MEROITIC HIEROGLYPHIC LETTER KHA*/
+ RTUNI_ALPHA, /* U+010992: MEROITIC HIEROGLYPHIC LETTER HHA*/
+ RTUNI_ALPHA, /* U+010993: MEROITIC HIEROGLYPHIC LETTER SA*/
+ RTUNI_ALPHA, /* U+010994: MEROITIC HIEROGLYPHIC LETTER SA-2*/
+ RTUNI_ALPHA, /* U+010995: MEROITIC HIEROGLYPHIC LETTER SE*/
+ RTUNI_ALPHA, /* U+010996: MEROITIC HIEROGLYPHIC LETTER KA*/
+ RTUNI_ALPHA, /* U+010997: MEROITIC HIEROGLYPHIC LETTER QA*/
+ RTUNI_ALPHA, /* U+010998: MEROITIC HIEROGLYPHIC LETTER TA*/
+ RTUNI_ALPHA, /* U+010999: MEROITIC HIEROGLYPHIC LETTER TA-2*/
+ RTUNI_ALPHA, /* U+01099a: MEROITIC HIEROGLYPHIC LETTER TE*/
+ RTUNI_ALPHA, /* U+01099b: MEROITIC HIEROGLYPHIC LETTER TE-2*/
+ RTUNI_ALPHA, /* U+01099c: MEROITIC HIEROGLYPHIC LETTER TO*/
+ RTUNI_ALPHA, /* U+01099d: MEROITIC HIEROGLYPHIC LETTER DA*/
+ RTUNI_ALPHA, /* U+01099e: MEROITIC HIEROGLYPHIC SYMBOL VIDJ*/
+ RTUNI_ALPHA, /* U+01099f: MEROITIC HIEROGLYPHIC SYMBOL VIDJ-2*/
+ RTUNI_ALPHA, /* U+0109a0: MEROITIC CURSIVE LETTER A*/
+ RTUNI_ALPHA, /* U+0109a1: MEROITIC CURSIVE LETTER E*/
+ RTUNI_ALPHA, /* U+0109a2: MEROITIC CURSIVE LETTER I*/
+ RTUNI_ALPHA, /* U+0109a3: MEROITIC CURSIVE LETTER O*/
+ RTUNI_ALPHA, /* U+0109a4: MEROITIC CURSIVE LETTER YA*/
+ RTUNI_ALPHA, /* U+0109a5: MEROITIC CURSIVE LETTER WA*/
+ RTUNI_ALPHA, /* U+0109a6: MEROITIC CURSIVE LETTER BA*/
+ RTUNI_ALPHA, /* U+0109a7: MEROITIC CURSIVE LETTER PA*/
+ RTUNI_ALPHA, /* U+0109a8: MEROITIC CURSIVE LETTER MA*/
+ RTUNI_ALPHA, /* U+0109a9: MEROITIC CURSIVE LETTER NA*/
+ RTUNI_ALPHA, /* U+0109aa: MEROITIC CURSIVE LETTER NE*/
+ RTUNI_ALPHA, /* U+0109ab: MEROITIC CURSIVE LETTER RA*/
+ RTUNI_ALPHA, /* U+0109ac: MEROITIC CURSIVE LETTER LA*/
+ RTUNI_ALPHA, /* U+0109ad: MEROITIC CURSIVE LETTER KHA*/
+ RTUNI_ALPHA, /* U+0109ae: MEROITIC CURSIVE LETTER HHA*/
+ RTUNI_ALPHA, /* U+0109af: MEROITIC CURSIVE LETTER SA*/
+ RTUNI_ALPHA, /* U+0109b0: MEROITIC CURSIVE LETTER ARCHAIC SA*/
+ RTUNI_ALPHA, /* U+0109b1: MEROITIC CURSIVE LETTER SE*/
+ RTUNI_ALPHA, /* U+0109b2: MEROITIC CURSIVE LETTER KA*/
+ RTUNI_ALPHA, /* U+0109b3: MEROITIC CURSIVE LETTER QA*/
+ RTUNI_ALPHA, /* U+0109b4: MEROITIC CURSIVE LETTER TA*/
+ RTUNI_ALPHA, /* U+0109b5: MEROITIC CURSIVE LETTER TE*/
+ RTUNI_ALPHA, /* U+0109b6: MEROITIC CURSIVE LETTER TO*/
+ RTUNI_ALPHA, /* U+0109b7: MEROITIC CURSIVE LETTER DA*/
+ 0, /* U+0109b8: */
+ 0, /* U+0109b9: */
+ 0, /* U+0109ba: */
+ 0, /* U+0109bb: */
+ 0, /* U+0109bc: */
+ 0, /* U+0109bd: */
+ RTUNI_ALPHA, /* U+0109be: MEROITIC CURSIVE LOGOGRAM RMT*/
+ RTUNI_ALPHA, /* U+0109bf: MEROITIC CURSIVE LOGOGRAM IMN*/
+ 0, /* U+0109c0: */
+ 0, /* U+0109c1: */
+ 0, /* U+0109c2: */
+ 0, /* U+0109c3: */
+ 0, /* U+0109c4: */
+ 0, /* U+0109c5: */
+ 0, /* U+0109c6: */
+ 0, /* U+0109c7: */
+ 0, /* U+0109c8: */
+ 0, /* U+0109c9: */
+ 0, /* U+0109ca: */
+ 0, /* U+0109cb: */
+ 0, /* U+0109cc: */
+ 0, /* U+0109cd: */
+ 0, /* U+0109ce: */
+ 0, /* U+0109cf: */
+ 0, /* U+0109d0: */
+ 0, /* U+0109d1: */
+ 0, /* U+0109d2: */
+ 0, /* U+0109d3: */
+ 0, /* U+0109d4: */
+ 0, /* U+0109d5: */
+ 0, /* U+0109d6: */
+ 0, /* U+0109d7: */
+ 0, /* U+0109d8: */
+ 0, /* U+0109d9: */
+ 0, /* U+0109da: */
+ 0, /* U+0109db: */
+ 0, /* U+0109dc: */
+ 0, /* U+0109dd: */
+ 0, /* U+0109de: */
+ 0, /* U+0109df: */
+ 0, /* U+0109e0: */
+ 0, /* U+0109e1: */
+ 0, /* U+0109e2: */
+ 0, /* U+0109e3: */
+ 0, /* U+0109e4: */
+ 0, /* U+0109e5: */
+ 0, /* U+0109e6: */
+ 0, /* U+0109e7: */
+ 0, /* U+0109e8: */
+ 0, /* U+0109e9: */
+ 0, /* U+0109ea: */
+ 0, /* U+0109eb: */
+ 0, /* U+0109ec: */
+ 0, /* U+0109ed: */
+ 0, /* U+0109ee: */
+ 0, /* U+0109ef: */
+ 0, /* U+0109f0: */
+ 0, /* U+0109f1: */
+ 0, /* U+0109f2: */
+ 0, /* U+0109f3: */
+ 0, /* U+0109f4: */
+ 0, /* U+0109f5: */
+ 0, /* U+0109f6: */
+ 0, /* U+0109f7: */
+ 0, /* U+0109f8: */
+ 0, /* U+0109f9: */
+ 0, /* U+0109fa: */
+ 0, /* U+0109fb: */
+ 0, /* U+0109fc: */
+ 0, /* U+0109fd: */
+ 0, /* U+0109fe: */
+ 0, /* U+0109ff: */
+ RTUNI_ALPHA, /* U+010a00: KHAROSHTHI LETTER A*/
+ RTUNI_ALPHA, /* U+010a01: KHAROSHTHI VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+010a02: KHAROSHTHI VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+010a03: KHAROSHTHI VOWEL SIGN VOCALIC R*/
+ 0, /* U+010a04: */
+ RTUNI_ALPHA, /* U+010a05: KHAROSHTHI VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+010a06: KHAROSHTHI VOWEL SIGN O*/
+ 0, /* U+010a07: */
+ 0, /* U+010a08: */
+ 0, /* U+010a09: */
+ 0, /* U+010a0a: */
+ 0, /* U+010a0b: */
+ RTUNI_ALPHA, /* U+010a0c: KHAROSHTHI VOWEL LENGTH MARK*/
+ RTUNI_ALPHA, /* U+010a0d: KHAROSHTHI SIGN DOUBLE RING BELOW*/
+ RTUNI_ALPHA, /* U+010a0e: KHAROSHTHI SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+010a0f: KHAROSHTHI SIGN VISARGA*/
+ RTUNI_ALPHA, /* U+010a10: KHAROSHTHI LETTER KA*/
+ RTUNI_ALPHA, /* U+010a11: KHAROSHTHI LETTER KHA*/
+ RTUNI_ALPHA, /* U+010a12: KHAROSHTHI LETTER GA*/
+ RTUNI_ALPHA, /* U+010a13: KHAROSHTHI LETTER GHA*/
+ 0, /* U+010a14: */
+ RTUNI_ALPHA, /* U+010a15: KHAROSHTHI LETTER CA*/
+ RTUNI_ALPHA, /* U+010a16: KHAROSHTHI LETTER CHA*/
+ RTUNI_ALPHA, /* U+010a17: KHAROSHTHI LETTER JA*/
+ 0, /* U+010a18: */
+ RTUNI_ALPHA, /* U+010a19: KHAROSHTHI LETTER NYA*/
+ RTUNI_ALPHA, /* U+010a1a: KHAROSHTHI LETTER TTA*/
+ RTUNI_ALPHA, /* U+010a1b: KHAROSHTHI LETTER TTHA*/
+ RTUNI_ALPHA, /* U+010a1c: KHAROSHTHI LETTER DDA*/
+ RTUNI_ALPHA, /* U+010a1d: KHAROSHTHI LETTER DDHA*/
+ RTUNI_ALPHA, /* U+010a1e: KHAROSHTHI LETTER NNA*/
+ RTUNI_ALPHA, /* U+010a1f: KHAROSHTHI LETTER TA*/
+ RTUNI_ALPHA, /* U+010a20: KHAROSHTHI LETTER THA*/
+ RTUNI_ALPHA, /* U+010a21: KHAROSHTHI LETTER DA*/
+ RTUNI_ALPHA, /* U+010a22: KHAROSHTHI LETTER DHA*/
+ RTUNI_ALPHA, /* U+010a23: KHAROSHTHI LETTER NA*/
+ RTUNI_ALPHA, /* U+010a24: KHAROSHTHI LETTER PA*/
+ RTUNI_ALPHA, /* U+010a25: KHAROSHTHI LETTER PHA*/
+ RTUNI_ALPHA, /* U+010a26: KHAROSHTHI LETTER BA*/
+ RTUNI_ALPHA, /* U+010a27: KHAROSHTHI LETTER BHA*/
+ RTUNI_ALPHA, /* U+010a28: KHAROSHTHI LETTER MA*/
+ RTUNI_ALPHA, /* U+010a29: KHAROSHTHI LETTER YA*/
+ RTUNI_ALPHA, /* U+010a2a: KHAROSHTHI LETTER RA*/
+ RTUNI_ALPHA, /* U+010a2b: KHAROSHTHI LETTER LA*/
+ RTUNI_ALPHA, /* U+010a2c: KHAROSHTHI LETTER VA*/
+ RTUNI_ALPHA, /* U+010a2d: KHAROSHTHI LETTER SHA*/
+ RTUNI_ALPHA, /* U+010a2e: KHAROSHTHI LETTER SSA*/
+ RTUNI_ALPHA, /* U+010a2f: KHAROSHTHI LETTER SA*/
+ RTUNI_ALPHA, /* U+010a30: KHAROSHTHI LETTER ZA*/
+ RTUNI_ALPHA, /* U+010a31: KHAROSHTHI LETTER HA*/
+ RTUNI_ALPHA, /* U+010a32: KHAROSHTHI LETTER KKA*/
+ RTUNI_ALPHA, /* U+010a33: KHAROSHTHI LETTER TTTHA*/
+ 0, /* U+010a34: */
+ 0, /* U+010a35: */
+ 0, /* U+010a36: */
+ 0, /* U+010a37: */
+ 0, /* U+010a38: KHAROSHTHI SIGN BAR ABOVE*/
+ 0, /* U+010a39: KHAROSHTHI SIGN CAUDA*/
+ 0, /* U+010a3a: KHAROSHTHI SIGN DOT BELOW*/
+ 0, /* U+010a3b: */
+ 0, /* U+010a3c: */
+ 0, /* U+010a3d: */
+ 0, /* U+010a3e: */
+ 0, /* U+010a3f: KHAROSHTHI VIRAMA*/
+ 0, /* U+010a40: KHAROSHTHI DIGIT ONE*/
+ 0, /* U+010a41: KHAROSHTHI DIGIT TWO*/
+ 0, /* U+010a42: KHAROSHTHI DIGIT THREE*/
+ 0, /* U+010a43: KHAROSHTHI DIGIT FOUR*/
+ 0, /* U+010a44: KHAROSHTHI NUMBER TEN*/
+ 0, /* U+010a45: KHAROSHTHI NUMBER TWENTY*/
+ 0, /* U+010a46: KHAROSHTHI NUMBER ONE HUNDRED*/
+ 0, /* U+010a47: KHAROSHTHI NUMBER ONE THOUSAND*/
+ 0, /* U+010a48: */
+ 0, /* U+010a49: */
+ 0, /* U+010a4a: */
+ 0, /* U+010a4b: */
+ 0, /* U+010a4c: */
+ 0, /* U+010a4d: */
+ 0, /* U+010a4e: */
+ 0, /* U+010a4f: */
+ 0, /* U+010a50: KHAROSHTHI PUNCTUATION DOT*/
+ 0, /* U+010a51: KHAROSHTHI PUNCTUATION SMALL CIRCLE*/
+ 0, /* U+010a52: KHAROSHTHI PUNCTUATION CIRCLE*/
+ 0, /* U+010a53: KHAROSHTHI PUNCTUATION CRESCENT BAR*/
+ 0, /* U+010a54: KHAROSHTHI PUNCTUATION MANGALAM*/
+ 0, /* U+010a55: KHAROSHTHI PUNCTUATION LOTUS*/
+ 0, /* U+010a56: KHAROSHTHI PUNCTUATION DANDA*/
+ 0, /* U+010a57: KHAROSHTHI PUNCTUATION DOUBLE DANDA*/
+ 0, /* U+010a58: KHAROSHTHI PUNCTUATION LINES*/
+ 0, /* U+010a59: */
+ 0, /* U+010a5a: */
+ 0, /* U+010a5b: */
+ 0, /* U+010a5c: */
+ 0, /* U+010a5d: */
+ 0, /* U+010a5e: */
+ 0, /* U+010a5f: */
+ RTUNI_ALPHA, /* U+010a60: OLD SOUTH ARABIAN LETTER HE*/
+ RTUNI_ALPHA, /* U+010a61: OLD SOUTH ARABIAN LETTER LAMEDH*/
+ RTUNI_ALPHA, /* U+010a62: OLD SOUTH ARABIAN LETTER HETH*/
+ RTUNI_ALPHA, /* U+010a63: OLD SOUTH ARABIAN LETTER MEM*/
+ RTUNI_ALPHA, /* U+010a64: OLD SOUTH ARABIAN LETTER QOPH*/
+ RTUNI_ALPHA, /* U+010a65: OLD SOUTH ARABIAN LETTER WAW*/
+ RTUNI_ALPHA, /* U+010a66: OLD SOUTH ARABIAN LETTER SHIN*/
+ RTUNI_ALPHA, /* U+010a67: OLD SOUTH ARABIAN LETTER RESH*/
+ RTUNI_ALPHA, /* U+010a68: OLD SOUTH ARABIAN LETTER BETH*/
+ RTUNI_ALPHA, /* U+010a69: OLD SOUTH ARABIAN LETTER TAW*/
+ RTUNI_ALPHA, /* U+010a6a: OLD SOUTH ARABIAN LETTER SAT*/
+ RTUNI_ALPHA, /* U+010a6b: OLD SOUTH ARABIAN LETTER KAPH*/
+ RTUNI_ALPHA, /* U+010a6c: OLD SOUTH ARABIAN LETTER NUN*/
+ RTUNI_ALPHA, /* U+010a6d: OLD SOUTH ARABIAN LETTER KHETH*/
+ RTUNI_ALPHA, /* U+010a6e: OLD SOUTH ARABIAN LETTER SADHE*/
+ RTUNI_ALPHA, /* U+010a6f: OLD SOUTH ARABIAN LETTER SAMEKH*/
+ RTUNI_ALPHA, /* U+010a70: OLD SOUTH ARABIAN LETTER FE*/
+ RTUNI_ALPHA, /* U+010a71: OLD SOUTH ARABIAN LETTER ALEF*/
+ RTUNI_ALPHA, /* U+010a72: OLD SOUTH ARABIAN LETTER AYN*/
+ RTUNI_ALPHA, /* U+010a73: OLD SOUTH ARABIAN LETTER DHADHE*/
+ RTUNI_ALPHA, /* U+010a74: OLD SOUTH ARABIAN LETTER GIMEL*/
+ RTUNI_ALPHA, /* U+010a75: OLD SOUTH ARABIAN LETTER DALETH*/
+ RTUNI_ALPHA, /* U+010a76: OLD SOUTH ARABIAN LETTER GHAYN*/
+ RTUNI_ALPHA, /* U+010a77: OLD SOUTH ARABIAN LETTER TETH*/
+ RTUNI_ALPHA, /* U+010a78: OLD SOUTH ARABIAN LETTER ZAYN*/
+ RTUNI_ALPHA, /* U+010a79: OLD SOUTH ARABIAN LETTER DHALETH*/
+ RTUNI_ALPHA, /* U+010a7a: OLD SOUTH ARABIAN LETTER YODH*/
+ RTUNI_ALPHA, /* U+010a7b: OLD SOUTH ARABIAN LETTER THAW*/
+ RTUNI_ALPHA, /* U+010a7c: OLD SOUTH ARABIAN LETTER THETH*/
+ 0, /* U+010a7d: OLD SOUTH ARABIAN NUMBER ONE*/
+ 0, /* U+010a7e: OLD SOUTH ARABIAN NUMBER FIFTY*/
+ 0, /* U+010a7f: OLD SOUTH ARABIAN NUMERIC INDICATOR*/
+ 0, /* U+010a80: */
+ 0, /* U+010a81: */
+ 0, /* U+010a82: */
+ 0, /* U+010a83: */
+ 0, /* U+010a84: */
+ 0, /* U+010a85: */
+ 0, /* U+010a86: */
+ 0, /* U+010a87: */
+ 0, /* U+010a88: */
+ 0, /* U+010a89: */
+ 0, /* U+010a8a: */
+ 0, /* U+010a8b: */
+ 0, /* U+010a8c: */
+ 0, /* U+010a8d: */
+ 0, /* U+010a8e: */
+ 0, /* U+010a8f: */
+ 0, /* U+010a90: */
+ 0, /* U+010a91: */
+ 0, /* U+010a92: */
+ 0, /* U+010a93: */
+ 0, /* U+010a94: */
+ 0, /* U+010a95: */
+ 0, /* U+010a96: */
+ 0, /* U+010a97: */
+ 0, /* U+010a98: */
+ 0, /* U+010a99: */
+ 0, /* U+010a9a: */
+ 0, /* U+010a9b: */
+ 0, /* U+010a9c: */
+ 0, /* U+010a9d: */
+ 0, /* U+010a9e: */
+ 0, /* U+010a9f: */
+ 0, /* U+010aa0: */
+ 0, /* U+010aa1: */
+ 0, /* U+010aa2: */
+ 0, /* U+010aa3: */
+ 0, /* U+010aa4: */
+ 0, /* U+010aa5: */
+ 0, /* U+010aa6: */
+ 0, /* U+010aa7: */
+ 0, /* U+010aa8: */
+ 0, /* U+010aa9: */
+ 0, /* U+010aaa: */
+ 0, /* U+010aab: */
+ 0, /* U+010aac: */
+ 0, /* U+010aad: */
+ 0, /* U+010aae: */
+ 0, /* U+010aaf: */
+ 0, /* U+010ab0: */
+ 0, /* U+010ab1: */
+ 0, /* U+010ab2: */
+ 0, /* U+010ab3: */
+ 0, /* U+010ab4: */
+ 0, /* U+010ab5: */
+ 0, /* U+010ab6: */
+ 0, /* U+010ab7: */
+ 0, /* U+010ab8: */
+ 0, /* U+010ab9: */
+ 0, /* U+010aba: */
+ 0, /* U+010abb: */
+ 0, /* U+010abc: */
+ 0, /* U+010abd: */
+ 0, /* U+010abe: */
+ 0, /* U+010abf: */
+ 0, /* U+010ac0: */
+ 0, /* U+010ac1: */
+ 0, /* U+010ac2: */
+ 0, /* U+010ac3: */
+ 0, /* U+010ac4: */
+ 0, /* U+010ac5: */
+ 0, /* U+010ac6: */
+ 0, /* U+010ac7: */
+ 0, /* U+010ac8: */
+ 0, /* U+010ac9: */
+ 0, /* U+010aca: */
+ 0, /* U+010acb: */
+ 0, /* U+010acc: */
+ 0, /* U+010acd: */
+ 0, /* U+010ace: */
+ 0, /* U+010acf: */
+ 0, /* U+010ad0: */
+ 0, /* U+010ad1: */
+ 0, /* U+010ad2: */
+ 0, /* U+010ad3: */
+ 0, /* U+010ad4: */
+ 0, /* U+010ad5: */
+ 0, /* U+010ad6: */
+ 0, /* U+010ad7: */
+ 0, /* U+010ad8: */
+ 0, /* U+010ad9: */
+ 0, /* U+010ada: */
+ 0, /* U+010adb: */
+ 0, /* U+010adc: */
+ 0, /* U+010add: */
+ 0, /* U+010ade: */
+ 0, /* U+010adf: */
+ 0, /* U+010ae0: */
+ 0, /* U+010ae1: */
+ 0, /* U+010ae2: */
+ 0, /* U+010ae3: */
+ 0, /* U+010ae4: */
+ 0, /* U+010ae5: */
+ 0, /* U+010ae6: */
+ 0, /* U+010ae7: */
+ 0, /* U+010ae8: */
+ 0, /* U+010ae9: */
+ 0, /* U+010aea: */
+ 0, /* U+010aeb: */
+ 0, /* U+010aec: */
+ 0, /* U+010aed: */
+ 0, /* U+010aee: */
+ 0, /* U+010aef: */
+ 0, /* U+010af0: */
+ 0, /* U+010af1: */
+ 0, /* U+010af2: */
+ 0, /* U+010af3: */
+ 0, /* U+010af4: */
+ 0, /* U+010af5: */
+ 0, /* U+010af6: */
+ 0, /* U+010af7: */
+ 0, /* U+010af8: */
+ 0, /* U+010af9: */
+ 0, /* U+010afa: */
+ 0, /* U+010afb: */
+ 0, /* U+010afc: */
+ 0, /* U+010afd: */
+ 0, /* U+010afe: */
+ 0, /* U+010aff: */
+ RTUNI_ALPHA, /* U+010b00: AVESTAN LETTER A*/
+ RTUNI_ALPHA, /* U+010b01: AVESTAN LETTER AA*/
+ RTUNI_ALPHA, /* U+010b02: AVESTAN LETTER AO*/
+ RTUNI_ALPHA, /* U+010b03: AVESTAN LETTER AAO*/
+ RTUNI_ALPHA, /* U+010b04: AVESTAN LETTER AN*/
+ RTUNI_ALPHA, /* U+010b05: AVESTAN LETTER AAN*/
+ RTUNI_ALPHA, /* U+010b06: AVESTAN LETTER AE*/
+ RTUNI_ALPHA, /* U+010b07: AVESTAN LETTER AEE*/
+ RTUNI_ALPHA, /* U+010b08: AVESTAN LETTER E*/
+ RTUNI_ALPHA, /* U+010b09: AVESTAN LETTER EE*/
+ RTUNI_ALPHA, /* U+010b0a: AVESTAN LETTER O*/
+ RTUNI_ALPHA, /* U+010b0b: AVESTAN LETTER OO*/
+ RTUNI_ALPHA, /* U+010b0c: AVESTAN LETTER I*/
+ RTUNI_ALPHA, /* U+010b0d: AVESTAN LETTER II*/
+ RTUNI_ALPHA, /* U+010b0e: AVESTAN LETTER U*/
+ RTUNI_ALPHA, /* U+010b0f: AVESTAN LETTER UU*/
+ RTUNI_ALPHA, /* U+010b10: AVESTAN LETTER KE*/
+ RTUNI_ALPHA, /* U+010b11: AVESTAN LETTER XE*/
+ RTUNI_ALPHA, /* U+010b12: AVESTAN LETTER XYE*/
+ RTUNI_ALPHA, /* U+010b13: AVESTAN LETTER XVE*/
+ RTUNI_ALPHA, /* U+010b14: AVESTAN LETTER GE*/
+ RTUNI_ALPHA, /* U+010b15: AVESTAN LETTER GGE*/
+ RTUNI_ALPHA, /* U+010b16: AVESTAN LETTER GHE*/
+ RTUNI_ALPHA, /* U+010b17: AVESTAN LETTER CE*/
+ RTUNI_ALPHA, /* U+010b18: AVESTAN LETTER JE*/
+ RTUNI_ALPHA, /* U+010b19: AVESTAN LETTER TE*/
+ RTUNI_ALPHA, /* U+010b1a: AVESTAN LETTER THE*/
+ RTUNI_ALPHA, /* U+010b1b: AVESTAN LETTER DE*/
+ RTUNI_ALPHA, /* U+010b1c: AVESTAN LETTER DHE*/
+ RTUNI_ALPHA, /* U+010b1d: AVESTAN LETTER TTE*/
+ RTUNI_ALPHA, /* U+010b1e: AVESTAN LETTER PE*/
+ RTUNI_ALPHA, /* U+010b1f: AVESTAN LETTER FE*/
+ RTUNI_ALPHA, /* U+010b20: AVESTAN LETTER BE*/
+ RTUNI_ALPHA, /* U+010b21: AVESTAN LETTER BHE*/
+ RTUNI_ALPHA, /* U+010b22: AVESTAN LETTER NGE*/
+ RTUNI_ALPHA, /* U+010b23: AVESTAN LETTER NGYE*/
+ RTUNI_ALPHA, /* U+010b24: AVESTAN LETTER NGVE*/
+ RTUNI_ALPHA, /* U+010b25: AVESTAN LETTER NE*/
+ RTUNI_ALPHA, /* U+010b26: AVESTAN LETTER NYE*/
+ RTUNI_ALPHA, /* U+010b27: AVESTAN LETTER NNE*/
+ RTUNI_ALPHA, /* U+010b28: AVESTAN LETTER ME*/
+ RTUNI_ALPHA, /* U+010b29: AVESTAN LETTER HME*/
+ RTUNI_ALPHA, /* U+010b2a: AVESTAN LETTER YYE*/
+ RTUNI_ALPHA, /* U+010b2b: AVESTAN LETTER YE*/
+ RTUNI_ALPHA, /* U+010b2c: AVESTAN LETTER VE*/
+ RTUNI_ALPHA, /* U+010b2d: AVESTAN LETTER RE*/
+ RTUNI_ALPHA, /* U+010b2e: AVESTAN LETTER LE*/
+ RTUNI_ALPHA, /* U+010b2f: AVESTAN LETTER SE*/
+ RTUNI_ALPHA, /* U+010b30: AVESTAN LETTER ZE*/
+ RTUNI_ALPHA, /* U+010b31: AVESTAN LETTER SHE*/
+ RTUNI_ALPHA, /* U+010b32: AVESTAN LETTER ZHE*/
+ RTUNI_ALPHA, /* U+010b33: AVESTAN LETTER SHYE*/
+ RTUNI_ALPHA, /* U+010b34: AVESTAN LETTER SSHE*/
+ RTUNI_ALPHA, /* U+010b35: AVESTAN LETTER HE*/
+ 0, /* U+010b36: */
+ 0, /* U+010b37: */
+ 0, /* U+010b38: */
+ 0, /* U+010b39: AVESTAN ABBREVIATION MARK*/
+ 0, /* U+010b3a: TINY TWO DOTS OVER ONE DOT PUNCTUATION*/
+ 0, /* U+010b3b: SMALL TWO DOTS OVER ONE DOT PUNCTUATION*/
+ 0, /* U+010b3c: LARGE TWO DOTS OVER ONE DOT PUNCTUATION*/
+ 0, /* U+010b3d: LARGE ONE DOT OVER TWO DOTS PUNCTUATION*/
+ 0, /* U+010b3e: LARGE TWO RINGS OVER ONE RING PUNCTUATION*/
+ 0, /* U+010b3f: LARGE ONE RING OVER TWO RINGS PUNCTUATION*/
+ RTUNI_ALPHA, /* U+010b40: INSCRIPTIONAL PARTHIAN LETTER ALEPH*/
+ RTUNI_ALPHA, /* U+010b41: INSCRIPTIONAL PARTHIAN LETTER BETH*/
+ RTUNI_ALPHA, /* U+010b42: INSCRIPTIONAL PARTHIAN LETTER GIMEL*/
+ RTUNI_ALPHA, /* U+010b43: INSCRIPTIONAL PARTHIAN LETTER DALETH*/
+ RTUNI_ALPHA, /* U+010b44: INSCRIPTIONAL PARTHIAN LETTER HE*/
+ RTUNI_ALPHA, /* U+010b45: INSCRIPTIONAL PARTHIAN LETTER WAW*/
+ RTUNI_ALPHA, /* U+010b46: INSCRIPTIONAL PARTHIAN LETTER ZAYIN*/
+ RTUNI_ALPHA, /* U+010b47: INSCRIPTIONAL PARTHIAN LETTER HETH*/
+ RTUNI_ALPHA, /* U+010b48: INSCRIPTIONAL PARTHIAN LETTER TETH*/
+ RTUNI_ALPHA, /* U+010b49: INSCRIPTIONAL PARTHIAN LETTER YODH*/
+ RTUNI_ALPHA, /* U+010b4a: INSCRIPTIONAL PARTHIAN LETTER KAPH*/
+ RTUNI_ALPHA, /* U+010b4b: INSCRIPTIONAL PARTHIAN LETTER LAMEDH*/
+ RTUNI_ALPHA, /* U+010b4c: INSCRIPTIONAL PARTHIAN LETTER MEM*/
+ RTUNI_ALPHA, /* U+010b4d: INSCRIPTIONAL PARTHIAN LETTER NUN*/
+ RTUNI_ALPHA, /* U+010b4e: INSCRIPTIONAL PARTHIAN LETTER SAMEKH*/
+ RTUNI_ALPHA, /* U+010b4f: INSCRIPTIONAL PARTHIAN LETTER AYIN*/
+ RTUNI_ALPHA, /* U+010b50: INSCRIPTIONAL PARTHIAN LETTER PE*/
+ RTUNI_ALPHA, /* U+010b51: INSCRIPTIONAL PARTHIAN LETTER SADHE*/
+ RTUNI_ALPHA, /* U+010b52: INSCRIPTIONAL PARTHIAN LETTER QOPH*/
+ RTUNI_ALPHA, /* U+010b53: INSCRIPTIONAL PARTHIAN LETTER RESH*/
+ RTUNI_ALPHA, /* U+010b54: INSCRIPTIONAL PARTHIAN LETTER SHIN*/
+ RTUNI_ALPHA, /* U+010b55: INSCRIPTIONAL PARTHIAN LETTER TAW*/
+ 0, /* U+010b56: */
+ 0, /* U+010b57: */
+ 0, /* U+010b58: INSCRIPTIONAL PARTHIAN NUMBER ONE*/
+ 0, /* U+010b59: INSCRIPTIONAL PARTHIAN NUMBER TWO*/
+ 0, /* U+010b5a: INSCRIPTIONAL PARTHIAN NUMBER THREE*/
+ 0, /* U+010b5b: INSCRIPTIONAL PARTHIAN NUMBER FOUR*/
+ 0, /* U+010b5c: INSCRIPTIONAL PARTHIAN NUMBER TEN*/
+ 0, /* U+010b5d: INSCRIPTIONAL PARTHIAN NUMBER TWENTY*/
+ 0, /* U+010b5e: INSCRIPTIONAL PARTHIAN NUMBER ONE HUNDRED*/
+ 0, /* U+010b5f: INSCRIPTIONAL PARTHIAN NUMBER ONE THOUSAND*/
+ RTUNI_ALPHA, /* U+010b60: INSCRIPTIONAL PAHLAVI LETTER ALEPH*/
+ RTUNI_ALPHA, /* U+010b61: INSCRIPTIONAL PAHLAVI LETTER BETH*/
+ RTUNI_ALPHA, /* U+010b62: INSCRIPTIONAL PAHLAVI LETTER GIMEL*/
+ RTUNI_ALPHA, /* U+010b63: INSCRIPTIONAL PAHLAVI LETTER DALETH*/
+ RTUNI_ALPHA, /* U+010b64: INSCRIPTIONAL PAHLAVI LETTER HE*/
+ RTUNI_ALPHA, /* U+010b65: INSCRIPTIONAL PAHLAVI LETTER WAW-AYIN-RESH*/
+ RTUNI_ALPHA, /* U+010b66: INSCRIPTIONAL PAHLAVI LETTER ZAYIN*/
+ RTUNI_ALPHA, /* U+010b67: INSCRIPTIONAL PAHLAVI LETTER HETH*/
+ RTUNI_ALPHA, /* U+010b68: INSCRIPTIONAL PAHLAVI LETTER TETH*/
+ RTUNI_ALPHA, /* U+010b69: INSCRIPTIONAL PAHLAVI LETTER YODH*/
+ RTUNI_ALPHA, /* U+010b6a: INSCRIPTIONAL PAHLAVI LETTER KAPH*/
+ RTUNI_ALPHA, /* U+010b6b: INSCRIPTIONAL PAHLAVI LETTER LAMEDH*/
+ RTUNI_ALPHA, /* U+010b6c: INSCRIPTIONAL PAHLAVI LETTER MEM-QOPH*/
+ RTUNI_ALPHA, /* U+010b6d: INSCRIPTIONAL PAHLAVI LETTER NUN*/
+ RTUNI_ALPHA, /* U+010b6e: INSCRIPTIONAL PAHLAVI LETTER SAMEKH*/
+ RTUNI_ALPHA, /* U+010b6f: INSCRIPTIONAL PAHLAVI LETTER PE*/
+ RTUNI_ALPHA, /* U+010b70: INSCRIPTIONAL PAHLAVI LETTER SADHE*/
+ RTUNI_ALPHA, /* U+010b71: INSCRIPTIONAL PAHLAVI LETTER SHIN*/
+ RTUNI_ALPHA, /* U+010b72: INSCRIPTIONAL PAHLAVI LETTER TAW*/
+ 0, /* U+010b73: */
+ 0, /* U+010b74: */
+ 0, /* U+010b75: */
+ 0, /* U+010b76: */
+ 0, /* U+010b77: */
+ 0, /* U+010b78: INSCRIPTIONAL PAHLAVI NUMBER ONE*/
+ 0, /* U+010b79: INSCRIPTIONAL PAHLAVI NUMBER TWO*/
+ 0, /* U+010b7a: INSCRIPTIONAL PAHLAVI NUMBER THREE*/
+ 0, /* U+010b7b: INSCRIPTIONAL PAHLAVI NUMBER FOUR*/
+ 0, /* U+010b7c: INSCRIPTIONAL PAHLAVI NUMBER TEN*/
+ 0, /* U+010b7d: INSCRIPTIONAL PAHLAVI NUMBER TWENTY*/
+ 0, /* U+010b7e: INSCRIPTIONAL PAHLAVI NUMBER ONE HUNDRED*/
+ 0, /* U+010b7f: INSCRIPTIONAL PAHLAVI NUMBER ONE THOUSAND*/
+ 0, /* U+010b80: */
+ 0, /* U+010b81: */
+ 0, /* U+010b82: */
+ 0, /* U+010b83: */
+ 0, /* U+010b84: */
+ 0, /* U+010b85: */
+ 0, /* U+010b86: */
+ 0, /* U+010b87: */
+ 0, /* U+010b88: */
+ 0, /* U+010b89: */
+ 0, /* U+010b8a: */
+ 0, /* U+010b8b: */
+ 0, /* U+010b8c: */
+ 0, /* U+010b8d: */
+ 0, /* U+010b8e: */
+ 0, /* U+010b8f: */
+ 0, /* U+010b90: */
+ 0, /* U+010b91: */
+ 0, /* U+010b92: */
+ 0, /* U+010b93: */
+ 0, /* U+010b94: */
+ 0, /* U+010b95: */
+ 0, /* U+010b96: */
+ 0, /* U+010b97: */
+ 0, /* U+010b98: */
+ 0, /* U+010b99: */
+ 0, /* U+010b9a: */
+ 0, /* U+010b9b: */
+ 0, /* U+010b9c: */
+ 0, /* U+010b9d: */
+ 0, /* U+010b9e: */
+ 0, /* U+010b9f: */
+ 0, /* U+010ba0: */
+ 0, /* U+010ba1: */
+ 0, /* U+010ba2: */
+ 0, /* U+010ba3: */
+ 0, /* U+010ba4: */
+ 0, /* U+010ba5: */
+ 0, /* U+010ba6: */
+ 0, /* U+010ba7: */
+ 0, /* U+010ba8: */
+ 0, /* U+010ba9: */
+ 0, /* U+010baa: */
+ 0, /* U+010bab: */
+ 0, /* U+010bac: */
+ 0, /* U+010bad: */
+ 0, /* U+010bae: */
+ 0, /* U+010baf: */
+ 0, /* U+010bb0: */
+ 0, /* U+010bb1: */
+ 0, /* U+010bb2: */
+ 0, /* U+010bb3: */
+ 0, /* U+010bb4: */
+ 0, /* U+010bb5: */
+ 0, /* U+010bb6: */
+ 0, /* U+010bb7: */
+ 0, /* U+010bb8: */
+ 0, /* U+010bb9: */
+ 0, /* U+010bba: */
+ 0, /* U+010bbb: */
+ 0, /* U+010bbc: */
+ 0, /* U+010bbd: */
+ 0, /* U+010bbe: */
+ 0, /* U+010bbf: */
+ 0, /* U+010bc0: */
+ 0, /* U+010bc1: */
+ 0, /* U+010bc2: */
+ 0, /* U+010bc3: */
+ 0, /* U+010bc4: */
+ 0, /* U+010bc5: */
+ 0, /* U+010bc6: */
+ 0, /* U+010bc7: */
+ 0, /* U+010bc8: */
+ 0, /* U+010bc9: */
+ 0, /* U+010bca: */
+ 0, /* U+010bcb: */
+ 0, /* U+010bcc: */
+ 0, /* U+010bcd: */
+ 0, /* U+010bce: */
+ 0, /* U+010bcf: */
+ 0, /* U+010bd0: */
+ 0, /* U+010bd1: */
+ 0, /* U+010bd2: */
+ 0, /* U+010bd3: */
+ 0, /* U+010bd4: */
+ 0, /* U+010bd5: */
+ 0, /* U+010bd6: */
+ 0, /* U+010bd7: */
+ 0, /* U+010bd8: */
+ 0, /* U+010bd9: */
+ 0, /* U+010bda: */
+ 0, /* U+010bdb: */
+ 0, /* U+010bdc: */
+ 0, /* U+010bdd: */
+ 0, /* U+010bde: */
+ 0, /* U+010bdf: */
+ 0, /* U+010be0: */
+ 0, /* U+010be1: */
+ 0, /* U+010be2: */
+ 0, /* U+010be3: */
+ 0, /* U+010be4: */
+ 0, /* U+010be5: */
+ 0, /* U+010be6: */
+ 0, /* U+010be7: */
+ 0, /* U+010be8: */
+ 0, /* U+010be9: */
+ 0, /* U+010bea: */
+ 0, /* U+010beb: */
+ 0, /* U+010bec: */
+ 0, /* U+010bed: */
+ 0, /* U+010bee: */
+ 0, /* U+010bef: */
+ 0, /* U+010bf0: */
+ 0, /* U+010bf1: */
+ 0, /* U+010bf2: */
+ 0, /* U+010bf3: */
+ 0, /* U+010bf4: */
+ 0, /* U+010bf5: */
+ 0, /* U+010bf6: */
+ 0, /* U+010bf7: */
+ 0, /* U+010bf8: */
+ 0, /* U+010bf9: */
+ 0, /* U+010bfa: */
+ 0, /* U+010bfb: */
+ 0, /* U+010bfc: */
+ 0, /* U+010bfd: */
+ 0, /* U+010bfe: */
+ 0, /* U+010bff: */
+ RTUNI_ALPHA, /* U+010c00: OLD TURKIC LETTER ORKHON A*/
+ RTUNI_ALPHA, /* U+010c01: OLD TURKIC LETTER YENISEI A*/
+ RTUNI_ALPHA, /* U+010c02: OLD TURKIC LETTER YENISEI AE*/
+ RTUNI_ALPHA, /* U+010c03: OLD TURKIC LETTER ORKHON I*/
+ RTUNI_ALPHA, /* U+010c04: OLD TURKIC LETTER YENISEI I*/
+ RTUNI_ALPHA, /* U+010c05: OLD TURKIC LETTER YENISEI E*/
+ RTUNI_ALPHA, /* U+010c06: OLD TURKIC LETTER ORKHON O*/
+ RTUNI_ALPHA, /* U+010c07: OLD TURKIC LETTER ORKHON OE*/
+ RTUNI_ALPHA, /* U+010c08: OLD TURKIC LETTER YENISEI OE*/
+ RTUNI_ALPHA, /* U+010c09: OLD TURKIC LETTER ORKHON AB*/
+ RTUNI_ALPHA, /* U+010c0a: OLD TURKIC LETTER YENISEI AB*/
+ RTUNI_ALPHA, /* U+010c0b: OLD TURKIC LETTER ORKHON AEB*/
+ RTUNI_ALPHA, /* U+010c0c: OLD TURKIC LETTER YENISEI AEB*/
+ RTUNI_ALPHA, /* U+010c0d: OLD TURKIC LETTER ORKHON AG*/
+ RTUNI_ALPHA, /* U+010c0e: OLD TURKIC LETTER YENISEI AG*/
+ RTUNI_ALPHA, /* U+010c0f: OLD TURKIC LETTER ORKHON AEG*/
+ RTUNI_ALPHA, /* U+010c10: OLD TURKIC LETTER YENISEI AEG*/
+ RTUNI_ALPHA, /* U+010c11: OLD TURKIC LETTER ORKHON AD*/
+ RTUNI_ALPHA, /* U+010c12: OLD TURKIC LETTER YENISEI AD*/
+ RTUNI_ALPHA, /* U+010c13: OLD TURKIC LETTER ORKHON AED*/
+ RTUNI_ALPHA, /* U+010c14: OLD TURKIC LETTER ORKHON EZ*/
+ RTUNI_ALPHA, /* U+010c15: OLD TURKIC LETTER YENISEI EZ*/
+ RTUNI_ALPHA, /* U+010c16: OLD TURKIC LETTER ORKHON AY*/
+ RTUNI_ALPHA, /* U+010c17: OLD TURKIC LETTER YENISEI AY*/
+ RTUNI_ALPHA, /* U+010c18: OLD TURKIC LETTER ORKHON AEY*/
+ RTUNI_ALPHA, /* U+010c19: OLD TURKIC LETTER YENISEI AEY*/
+ RTUNI_ALPHA, /* U+010c1a: OLD TURKIC LETTER ORKHON AEK*/
+ RTUNI_ALPHA, /* U+010c1b: OLD TURKIC LETTER YENISEI AEK*/
+ RTUNI_ALPHA, /* U+010c1c: OLD TURKIC LETTER ORKHON OEK*/
+ RTUNI_ALPHA, /* U+010c1d: OLD TURKIC LETTER YENISEI OEK*/
+ RTUNI_ALPHA, /* U+010c1e: OLD TURKIC LETTER ORKHON AL*/
+ RTUNI_ALPHA, /* U+010c1f: OLD TURKIC LETTER YENISEI AL*/
+ RTUNI_ALPHA, /* U+010c20: OLD TURKIC LETTER ORKHON AEL*/
+ RTUNI_ALPHA, /* U+010c21: OLD TURKIC LETTER ORKHON ELT*/
+ RTUNI_ALPHA, /* U+010c22: OLD TURKIC LETTER ORKHON EM*/
+ RTUNI_ALPHA, /* U+010c23: OLD TURKIC LETTER ORKHON AN*/
+ RTUNI_ALPHA, /* U+010c24: OLD TURKIC LETTER ORKHON AEN*/
+ RTUNI_ALPHA, /* U+010c25: OLD TURKIC LETTER YENISEI AEN*/
+ RTUNI_ALPHA, /* U+010c26: OLD TURKIC LETTER ORKHON ENT*/
+ RTUNI_ALPHA, /* U+010c27: OLD TURKIC LETTER YENISEI ENT*/
+ RTUNI_ALPHA, /* U+010c28: OLD TURKIC LETTER ORKHON ENC*/
+ RTUNI_ALPHA, /* U+010c29: OLD TURKIC LETTER YENISEI ENC*/
+ RTUNI_ALPHA, /* U+010c2a: OLD TURKIC LETTER ORKHON ENY*/
+ RTUNI_ALPHA, /* U+010c2b: OLD TURKIC LETTER YENISEI ENY*/
+ RTUNI_ALPHA, /* U+010c2c: OLD TURKIC LETTER YENISEI ANG*/
+ RTUNI_ALPHA, /* U+010c2d: OLD TURKIC LETTER ORKHON ENG*/
+ RTUNI_ALPHA, /* U+010c2e: OLD TURKIC LETTER YENISEI AENG*/
+ RTUNI_ALPHA, /* U+010c2f: OLD TURKIC LETTER ORKHON EP*/
+ RTUNI_ALPHA, /* U+010c30: OLD TURKIC LETTER ORKHON OP*/
+ RTUNI_ALPHA, /* U+010c31: OLD TURKIC LETTER ORKHON IC*/
+ RTUNI_ALPHA, /* U+010c32: OLD TURKIC LETTER ORKHON EC*/
+ RTUNI_ALPHA, /* U+010c33: OLD TURKIC LETTER YENISEI EC*/
+ RTUNI_ALPHA, /* U+010c34: OLD TURKIC LETTER ORKHON AQ*/
+ RTUNI_ALPHA, /* U+010c35: OLD TURKIC LETTER YENISEI AQ*/
+ RTUNI_ALPHA, /* U+010c36: OLD TURKIC LETTER ORKHON IQ*/
+ RTUNI_ALPHA, /* U+010c37: OLD TURKIC LETTER YENISEI IQ*/
+ RTUNI_ALPHA, /* U+010c38: OLD TURKIC LETTER ORKHON OQ*/
+ RTUNI_ALPHA, /* U+010c39: OLD TURKIC LETTER YENISEI OQ*/
+ RTUNI_ALPHA, /* U+010c3a: OLD TURKIC LETTER ORKHON AR*/
+ RTUNI_ALPHA, /* U+010c3b: OLD TURKIC LETTER YENISEI AR*/
+ RTUNI_ALPHA, /* U+010c3c: OLD TURKIC LETTER ORKHON AER*/
+ RTUNI_ALPHA, /* U+010c3d: OLD TURKIC LETTER ORKHON AS*/
+ RTUNI_ALPHA, /* U+010c3e: OLD TURKIC LETTER ORKHON AES*/
+ RTUNI_ALPHA, /* U+010c3f: OLD TURKIC LETTER ORKHON ASH*/
+ RTUNI_ALPHA, /* U+010c40: OLD TURKIC LETTER YENISEI ASH*/
+ RTUNI_ALPHA, /* U+010c41: OLD TURKIC LETTER ORKHON ESH*/
+ RTUNI_ALPHA, /* U+010c42: OLD TURKIC LETTER YENISEI ESH*/
+ RTUNI_ALPHA, /* U+010c43: OLD TURKIC LETTER ORKHON AT*/
+ RTUNI_ALPHA, /* U+010c44: OLD TURKIC LETTER YENISEI AT*/
+ RTUNI_ALPHA, /* U+010c45: OLD TURKIC LETTER ORKHON AET*/
+ RTUNI_ALPHA, /* U+010c46: OLD TURKIC LETTER YENISEI AET*/
+ RTUNI_ALPHA, /* U+010c47: OLD TURKIC LETTER ORKHON OT*/
+ RTUNI_ALPHA, /* U+010c48: OLD TURKIC LETTER ORKHON BASH*/
+ 0, /* U+010c49: */
+ 0, /* U+010c4a: */
+ 0, /* U+010c4b: */
+ 0, /* U+010c4c: */
+ 0, /* U+010c4d: */
+ 0, /* U+010c4e: */
+ 0, /* U+010c4f: */
+ 0, /* U+010c50: */
+ 0, /* U+010c51: */
+ 0, /* U+010c52: */
+ 0, /* U+010c53: */
+ 0, /* U+010c54: */
+ 0, /* U+010c55: */
+ 0, /* U+010c56: */
+ 0, /* U+010c57: */
+ 0, /* U+010c58: */
+ 0, /* U+010c59: */
+ 0, /* U+010c5a: */
+ 0, /* U+010c5b: */
+ 0, /* U+010c5c: */
+ 0, /* U+010c5d: */
+ 0, /* U+010c5e: */
+ 0, /* U+010c5f: */
+ 0, /* U+010c60: */
+ 0, /* U+010c61: */
+ 0, /* U+010c62: */
+ 0, /* U+010c63: */
+ 0, /* U+010c64: */
+ 0, /* U+010c65: */
+ 0, /* U+010c66: */
+ 0, /* U+010c67: */
+ 0, /* U+010c68: */
+ 0, /* U+010c69: */
+ 0, /* U+010c6a: */
+ 0, /* U+010c6b: */
+ 0, /* U+010c6c: */
+ 0, /* U+010c6d: */
+ 0, /* U+010c6e: */
+ 0, /* U+010c6f: */
+ 0, /* U+010c70: */
+ 0, /* U+010c71: */
+ 0, /* U+010c72: */
+ 0, /* U+010c73: */
+ 0, /* U+010c74: */
+ 0, /* U+010c75: */
+ 0, /* U+010c76: */
+ 0, /* U+010c77: */
+ 0, /* U+010c78: */
+ 0, /* U+010c79: */
+ 0, /* U+010c7a: */
+ 0, /* U+010c7b: */
+ 0, /* U+010c7c: */
+ 0, /* U+010c7d: */
+ 0, /* U+010c7e: */
+ 0, /* U+010c7f: */
+ 0, /* U+010c80: */
+ 0, /* U+010c81: */
+ 0, /* U+010c82: */
+ 0, /* U+010c83: */
+ 0, /* U+010c84: */
+ 0, /* U+010c85: */
+ 0, /* U+010c86: */
+ 0, /* U+010c87: */
+ 0, /* U+010c88: */
+ 0, /* U+010c89: */
+ 0, /* U+010c8a: */
+ 0, /* U+010c8b: */
+ 0, /* U+010c8c: */
+ 0, /* U+010c8d: */
+ 0, /* U+010c8e: */
+ 0, /* U+010c8f: */
+ 0, /* U+010c90: */
+ 0, /* U+010c91: */
+ 0, /* U+010c92: */
+ 0, /* U+010c93: */
+ 0, /* U+010c94: */
+ 0, /* U+010c95: */
+ 0, /* U+010c96: */
+ 0, /* U+010c97: */
+ 0, /* U+010c98: */
+ 0, /* U+010c99: */
+ 0, /* U+010c9a: */
+ 0, /* U+010c9b: */
+ 0, /* U+010c9c: */
+ 0, /* U+010c9d: */
+ 0, /* U+010c9e: */
+ 0, /* U+010c9f: */
+ 0, /* U+010ca0: */
+ 0, /* U+010ca1: */
+ 0, /* U+010ca2: */
+ 0, /* U+010ca3: */
+ 0, /* U+010ca4: */
+ 0, /* U+010ca5: */
+ 0, /* U+010ca6: */
+ 0, /* U+010ca7: */
+ 0, /* U+010ca8: */
+ 0, /* U+010ca9: */
+ 0, /* U+010caa: */
+ 0, /* U+010cab: */
+ 0, /* U+010cac: */
+ 0, /* U+010cad: */
+ 0, /* U+010cae: */
+ 0, /* U+010caf: */
+ 0, /* U+010cb0: */
+ 0, /* U+010cb1: */
+ 0, /* U+010cb2: */
+ 0, /* U+010cb3: */
+ 0, /* U+010cb4: */
+ 0, /* U+010cb5: */
+ 0, /* U+010cb6: */
+ 0, /* U+010cb7: */
+ 0, /* U+010cb8: */
+ 0, /* U+010cb9: */
+ 0, /* U+010cba: */
+ 0, /* U+010cbb: */
+ 0, /* U+010cbc: */
+ 0, /* U+010cbd: */
+ 0, /* U+010cbe: */
+ 0, /* U+010cbf: */
+ 0, /* U+010cc0: */
+ 0, /* U+010cc1: */
+ 0, /* U+010cc2: */
+ 0, /* U+010cc3: */
+ 0, /* U+010cc4: */
+ 0, /* U+010cc5: */
+ 0, /* U+010cc6: */
+ 0, /* U+010cc7: */
+ 0, /* U+010cc8: */
+ 0, /* U+010cc9: */
+ 0, /* U+010cca: */
+ 0, /* U+010ccb: */
+ 0, /* U+010ccc: */
+ 0, /* U+010ccd: */
+ 0, /* U+010cce: */
+ 0, /* U+010ccf: */
+ 0, /* U+010cd0: */
+ 0, /* U+010cd1: */
+ 0, /* U+010cd2: */
+ 0, /* U+010cd3: */
+ 0, /* U+010cd4: */
+ 0, /* U+010cd5: */
+ 0, /* U+010cd6: */
+ 0, /* U+010cd7: */
+ 0, /* U+010cd8: */
+ 0, /* U+010cd9: */
+ 0, /* U+010cda: */
+ 0, /* U+010cdb: */
+ 0, /* U+010cdc: */
+ 0, /* U+010cdd: */
+ 0, /* U+010cde: */
+ 0, /* U+010cdf: */
+ 0, /* U+010ce0: */
+ 0, /* U+010ce1: */
+ 0, /* U+010ce2: */
+ 0, /* U+010ce3: */
+ 0, /* U+010ce4: */
+ 0, /* U+010ce5: */
+ 0, /* U+010ce6: */
+ 0, /* U+010ce7: */
+ 0, /* U+010ce8: */
+ 0, /* U+010ce9: */
+ 0, /* U+010cea: */
+ 0, /* U+010ceb: */
+ 0, /* U+010cec: */
+ 0, /* U+010ced: */
+ 0, /* U+010cee: */
+ 0, /* U+010cef: */
+ 0, /* U+010cf0: */
+ 0, /* U+010cf1: */
+ 0, /* U+010cf2: */
+ 0, /* U+010cf3: */
+ 0, /* U+010cf4: */
+ 0, /* U+010cf5: */
+ 0, /* U+010cf6: */
+ 0, /* U+010cf7: */
+ 0, /* U+010cf8: */
+ 0, /* U+010cf9: */
+ 0, /* U+010cfa: */
+ 0, /* U+010cfb: */
+ 0, /* U+010cfc: */
+ 0, /* U+010cfd: */
+ 0, /* U+010cfe: */
+ 0, /* U+010cff: */
+ 0, /* U+010d00: */
+ 0, /* U+010d01: */
+ 0, /* U+010d02: */
+ 0, /* U+010d03: */
+ 0, /* U+010d04: */
+ 0, /* U+010d05: */
+ 0, /* U+010d06: */
+ 0, /* U+010d07: */
+ 0, /* U+010d08: */
+ 0, /* U+010d09: */
+ 0, /* U+010d0a: */
+ 0, /* U+010d0b: */
+ 0, /* U+010d0c: */
+ 0, /* U+010d0d: */
+ 0, /* U+010d0e: */
+ 0, /* U+010d0f: */
+ 0, /* U+010d10: */
+ 0, /* U+010d11: */
+ 0, /* U+010d12: */
+ 0, /* U+010d13: */
+ 0, /* U+010d14: */
+ 0, /* U+010d15: */
+ 0, /* U+010d16: */
+ 0, /* U+010d17: */
+ 0, /* U+010d18: */
+ 0, /* U+010d19: */
+ 0, /* U+010d1a: */
+ 0, /* U+010d1b: */
+ 0, /* U+010d1c: */
+ 0, /* U+010d1d: */
+ 0, /* U+010d1e: */
+ 0, /* U+010d1f: */
+ 0, /* U+010d20: */
+ 0, /* U+010d21: */
+ 0, /* U+010d22: */
+ 0, /* U+010d23: */
+ 0, /* U+010d24: */
+ 0, /* U+010d25: */
+ 0, /* U+010d26: */
+ 0, /* U+010d27: */
+ 0, /* U+010d28: */
+ 0, /* U+010d29: */
+ 0, /* U+010d2a: */
+ 0, /* U+010d2b: */
+ 0, /* U+010d2c: */
+ 0, /* U+010d2d: */
+ 0, /* U+010d2e: */
+ 0, /* U+010d2f: */
+ 0, /* U+010d30: */
+ 0, /* U+010d31: */
+ 0, /* U+010d32: */
+ 0, /* U+010d33: */
+ 0, /* U+010d34: */
+ 0, /* U+010d35: */
+ 0, /* U+010d36: */
+ 0, /* U+010d37: */
+ 0, /* U+010d38: */
+ 0, /* U+010d39: */
+ 0, /* U+010d3a: */
+ 0, /* U+010d3b: */
+ 0, /* U+010d3c: */
+ 0, /* U+010d3d: */
+ 0, /* U+010d3e: */
+ 0, /* U+010d3f: */
+ 0, /* U+010d40: */
+ 0, /* U+010d41: */
+ 0, /* U+010d42: */
+ 0, /* U+010d43: */
+ 0, /* U+010d44: */
+ 0, /* U+010d45: */
+ 0, /* U+010d46: */
+ 0, /* U+010d47: */
+ 0, /* U+010d48: */
+ 0, /* U+010d49: */
+ 0, /* U+010d4a: */
+ 0, /* U+010d4b: */
+ 0, /* U+010d4c: */
+ 0, /* U+010d4d: */
+ 0, /* U+010d4e: */
+ 0, /* U+010d4f: */
+ 0, /* U+010d50: */
+ 0, /* U+010d51: */
+ 0, /* U+010d52: */
+ 0, /* U+010d53: */
+ 0, /* U+010d54: */
+ 0, /* U+010d55: */
+ 0, /* U+010d56: */
+ 0, /* U+010d57: */
+ 0, /* U+010d58: */
+ 0, /* U+010d59: */
+ 0, /* U+010d5a: */
+ 0, /* U+010d5b: */
+ 0, /* U+010d5c: */
+ 0, /* U+010d5d: */
+ 0, /* U+010d5e: */
+ 0, /* U+010d5f: */
+ 0, /* U+010d60: */
+ 0, /* U+010d61: */
+ 0, /* U+010d62: */
+ 0, /* U+010d63: */
+ 0, /* U+010d64: */
+ 0, /* U+010d65: */
+ 0, /* U+010d66: */
+ 0, /* U+010d67: */
+ 0, /* U+010d68: */
+ 0, /* U+010d69: */
+ 0, /* U+010d6a: */
+ 0, /* U+010d6b: */
+ 0, /* U+010d6c: */
+ 0, /* U+010d6d: */
+ 0, /* U+010d6e: */
+ 0, /* U+010d6f: */
+ 0, /* U+010d70: */
+ 0, /* U+010d71: */
+ 0, /* U+010d72: */
+ 0, /* U+010d73: */
+ 0, /* U+010d74: */
+ 0, /* U+010d75: */
+ 0, /* U+010d76: */
+ 0, /* U+010d77: */
+ 0, /* U+010d78: */
+ 0, /* U+010d79: */
+ 0, /* U+010d7a: */
+ 0, /* U+010d7b: */
+ 0, /* U+010d7c: */
+ 0, /* U+010d7d: */
+ 0, /* U+010d7e: */
+ 0, /* U+010d7f: */
+ 0, /* U+010d80: */
+ 0, /* U+010d81: */
+ 0, /* U+010d82: */
+ 0, /* U+010d83: */
+ 0, /* U+010d84: */
+ 0, /* U+010d85: */
+ 0, /* U+010d86: */
+ 0, /* U+010d87: */
+ 0, /* U+010d88: */
+ 0, /* U+010d89: */
+ 0, /* U+010d8a: */
+ 0, /* U+010d8b: */
+ 0, /* U+010d8c: */
+ 0, /* U+010d8d: */
+ 0, /* U+010d8e: */
+ 0, /* U+010d8f: */
+ 0, /* U+010d90: */
+ 0, /* U+010d91: */
+ 0, /* U+010d92: */
+ 0, /* U+010d93: */
+ 0, /* U+010d94: */
+ 0, /* U+010d95: */
+ 0, /* U+010d96: */
+ 0, /* U+010d97: */
+ 0, /* U+010d98: */
+ 0, /* U+010d99: */
+ 0, /* U+010d9a: */
+ 0, /* U+010d9b: */
+ 0, /* U+010d9c: */
+ 0, /* U+010d9d: */
+ 0, /* U+010d9e: */
+ 0, /* U+010d9f: */
+ 0, /* U+010da0: */
+ 0, /* U+010da1: */
+ 0, /* U+010da2: */
+ 0, /* U+010da3: */
+ 0, /* U+010da4: */
+ 0, /* U+010da5: */
+ 0, /* U+010da6: */
+ 0, /* U+010da7: */
+ 0, /* U+010da8: */
+ 0, /* U+010da9: */
+ 0, /* U+010daa: */
+ 0, /* U+010dab: */
+ 0, /* U+010dac: */
+ 0, /* U+010dad: */
+ 0, /* U+010dae: */
+ 0, /* U+010daf: */
+ 0, /* U+010db0: */
+ 0, /* U+010db1: */
+ 0, /* U+010db2: */
+ 0, /* U+010db3: */
+ 0, /* U+010db4: */
+ 0, /* U+010db5: */
+ 0, /* U+010db6: */
+ 0, /* U+010db7: */
+ 0, /* U+010db8: */
+ 0, /* U+010db9: */
+ 0, /* U+010dba: */
+ 0, /* U+010dbb: */
+ 0, /* U+010dbc: */
+ 0, /* U+010dbd: */
+ 0, /* U+010dbe: */
+ 0, /* U+010dbf: */
+ 0, /* U+010dc0: */
+ 0, /* U+010dc1: */
+ 0, /* U+010dc2: */
+ 0, /* U+010dc3: */
+ 0, /* U+010dc4: */
+ 0, /* U+010dc5: */
+ 0, /* U+010dc6: */
+ 0, /* U+010dc7: */
+ 0, /* U+010dc8: */
+ 0, /* U+010dc9: */
+ 0, /* U+010dca: */
+ 0, /* U+010dcb: */
+ 0, /* U+010dcc: */
+ 0, /* U+010dcd: */
+ 0, /* U+010dce: */
+ 0, /* U+010dcf: */
+ 0, /* U+010dd0: */
+ 0, /* U+010dd1: */
+ 0, /* U+010dd2: */
+ 0, /* U+010dd3: */
+ 0, /* U+010dd4: */
+ 0, /* U+010dd5: */
+ 0, /* U+010dd6: */
+ 0, /* U+010dd7: */
+ 0, /* U+010dd8: */
+ 0, /* U+010dd9: */
+ 0, /* U+010dda: */
+ 0, /* U+010ddb: */
+ 0, /* U+010ddc: */
+ 0, /* U+010ddd: */
+ 0, /* U+010dde: */
+ 0, /* U+010ddf: */
+ 0, /* U+010de0: */
+ 0, /* U+010de1: */
+ 0, /* U+010de2: */
+ 0, /* U+010de3: */
+ 0, /* U+010de4: */
+ 0, /* U+010de5: */
+ 0, /* U+010de6: */
+ 0, /* U+010de7: */
+ 0, /* U+010de8: */
+ 0, /* U+010de9: */
+ 0, /* U+010dea: */
+ 0, /* U+010deb: */
+ 0, /* U+010dec: */
+ 0, /* U+010ded: */
+ 0, /* U+010dee: */
+ 0, /* U+010def: */
+ 0, /* U+010df0: */
+ 0, /* U+010df1: */
+ 0, /* U+010df2: */
+ 0, /* U+010df3: */
+ 0, /* U+010df4: */
+ 0, /* U+010df5: */
+ 0, /* U+010df6: */
+ 0, /* U+010df7: */
+ 0, /* U+010df8: */
+ 0, /* U+010df9: */
+ 0, /* U+010dfa: */
+ 0, /* U+010dfb: */
+ 0, /* U+010dfc: */
+ 0, /* U+010dfd: */
+ 0, /* U+010dfe: */
+ 0, /* U+010dff: */
+ 0, /* U+010e00: */
+ 0, /* U+010e01: */
+ 0, /* U+010e02: */
+ 0, /* U+010e03: */
+ 0, /* U+010e04: */
+ 0, /* U+010e05: */
+ 0, /* U+010e06: */
+ 0, /* U+010e07: */
+ 0, /* U+010e08: */
+ 0, /* U+010e09: */
+ 0, /* U+010e0a: */
+ 0, /* U+010e0b: */
+ 0, /* U+010e0c: */
+ 0, /* U+010e0d: */
+ 0, /* U+010e0e: */
+ 0, /* U+010e0f: */
+ 0, /* U+010e10: */
+ 0, /* U+010e11: */
+ 0, /* U+010e12: */
+ 0, /* U+010e13: */
+ 0, /* U+010e14: */
+ 0, /* U+010e15: */
+ 0, /* U+010e16: */
+ 0, /* U+010e17: */
+ 0, /* U+010e18: */
+ 0, /* U+010e19: */
+ 0, /* U+010e1a: */
+ 0, /* U+010e1b: */
+ 0, /* U+010e1c: */
+ 0, /* U+010e1d: */
+ 0, /* U+010e1e: */
+ 0, /* U+010e1f: */
+ 0, /* U+010e20: */
+ 0, /* U+010e21: */
+ 0, /* U+010e22: */
+ 0, /* U+010e23: */
+ 0, /* U+010e24: */
+ 0, /* U+010e25: */
+ 0, /* U+010e26: */
+ 0, /* U+010e27: */
+ 0, /* U+010e28: */
+ 0, /* U+010e29: */
+ 0, /* U+010e2a: */
+ 0, /* U+010e2b: */
+ 0, /* U+010e2c: */
+ 0, /* U+010e2d: */
+ 0, /* U+010e2e: */
+ 0, /* U+010e2f: */
+ 0, /* U+010e30: */
+ 0, /* U+010e31: */
+ 0, /* U+010e32: */
+ 0, /* U+010e33: */
+ 0, /* U+010e34: */
+ 0, /* U+010e35: */
+ 0, /* U+010e36: */
+ 0, /* U+010e37: */
+ 0, /* U+010e38: */
+ 0, /* U+010e39: */
+ 0, /* U+010e3a: */
+ 0, /* U+010e3b: */
+ 0, /* U+010e3c: */
+ 0, /* U+010e3d: */
+ 0, /* U+010e3e: */
+ 0, /* U+010e3f: */
+ 0, /* U+010e40: */
+ 0, /* U+010e41: */
+ 0, /* U+010e42: */
+ 0, /* U+010e43: */
+ 0, /* U+010e44: */
+ 0, /* U+010e45: */
+ 0, /* U+010e46: */
+ 0, /* U+010e47: */
+ 0, /* U+010e48: */
+ 0, /* U+010e49: */
+ 0, /* U+010e4a: */
+ 0, /* U+010e4b: */
+ 0, /* U+010e4c: */
+ 0, /* U+010e4d: */
+ 0, /* U+010e4e: */
+ 0, /* U+010e4f: */
+ 0, /* U+010e50: */
+ 0, /* U+010e51: */
+ 0, /* U+010e52: */
+ 0, /* U+010e53: */
+ 0, /* U+010e54: */
+ 0, /* U+010e55: */
+ 0, /* U+010e56: */
+ 0, /* U+010e57: */
+ 0, /* U+010e58: */
+ 0, /* U+010e59: */
+ 0, /* U+010e5a: */
+ 0, /* U+010e5b: */
+ 0, /* U+010e5c: */
+ 0, /* U+010e5d: */
+ 0, /* U+010e5e: */
+ 0, /* U+010e5f: */
+ 0, /* U+010e60: RUMI DIGIT ONE*/
+ 0, /* U+010e61: RUMI DIGIT TWO*/
+ 0, /* U+010e62: RUMI DIGIT THREE*/
+ 0, /* U+010e63: RUMI DIGIT FOUR*/
+ 0, /* U+010e64: RUMI DIGIT FIVE*/
+ 0, /* U+010e65: RUMI DIGIT SIX*/
+ 0, /* U+010e66: RUMI DIGIT SEVEN*/
+ 0, /* U+010e67: RUMI DIGIT EIGHT*/
+ 0, /* U+010e68: RUMI DIGIT NINE*/
+ 0, /* U+010e69: RUMI NUMBER TEN*/
+ 0, /* U+010e6a: RUMI NUMBER TWENTY*/
+ 0, /* U+010e6b: RUMI NUMBER THIRTY*/
+ 0, /* U+010e6c: RUMI NUMBER FORTY*/
+ 0, /* U+010e6d: RUMI NUMBER FIFTY*/
+ 0, /* U+010e6e: RUMI NUMBER SIXTY*/
+ 0, /* U+010e6f: RUMI NUMBER SEVENTY*/
+ 0, /* U+010e70: RUMI NUMBER EIGHTY*/
+ 0, /* U+010e71: RUMI NUMBER NINETY*/
+ 0, /* U+010e72: RUMI NUMBER ONE HUNDRED*/
+ 0, /* U+010e73: RUMI NUMBER TWO HUNDRED*/
+ 0, /* U+010e74: RUMI NUMBER THREE HUNDRED*/
+ 0, /* U+010e75: RUMI NUMBER FOUR HUNDRED*/
+ 0, /* U+010e76: RUMI NUMBER FIVE HUNDRED*/
+ 0, /* U+010e77: RUMI NUMBER SIX HUNDRED*/
+ 0, /* U+010e78: RUMI NUMBER SEVEN HUNDRED*/
+ 0, /* U+010e79: RUMI NUMBER EIGHT HUNDRED*/
+ 0, /* U+010e7a: RUMI NUMBER NINE HUNDRED*/
+ 0, /* U+010e7b: RUMI FRACTION ONE HALF*/
+ 0, /* U+010e7c: RUMI FRACTION ONE QUARTER*/
+ 0, /* U+010e7d: RUMI FRACTION ONE THIRD*/
+ 0, /* U+010e7e: RUMI FRACTION TWO THIRDS*/
+ 0, /* U+010e7f: */
+ 0, /* U+010e80: */
+ 0, /* U+010e81: */
+ 0, /* U+010e82: */
+ 0, /* U+010e83: */
+ 0, /* U+010e84: */
+ 0, /* U+010e85: */
+ 0, /* U+010e86: */
+ 0, /* U+010e87: */
+ 0, /* U+010e88: */
+ 0, /* U+010e89: */
+ 0, /* U+010e8a: */
+ 0, /* U+010e8b: */
+ 0, /* U+010e8c: */
+ 0, /* U+010e8d: */
+ 0, /* U+010e8e: */
+ 0, /* U+010e8f: */
+ 0, /* U+010e90: */
+ 0, /* U+010e91: */
+ 0, /* U+010e92: */
+ 0, /* U+010e93: */
+ 0, /* U+010e94: */
+ 0, /* U+010e95: */
+ 0, /* U+010e96: */
+ 0, /* U+010e97: */
+ 0, /* U+010e98: */
+ 0, /* U+010e99: */
+ 0, /* U+010e9a: */
+ 0, /* U+010e9b: */
+ 0, /* U+010e9c: */
+ 0, /* U+010e9d: */
+ 0, /* U+010e9e: */
+ 0, /* U+010e9f: */
+ 0, /* U+010ea0: */
+ 0, /* U+010ea1: */
+ 0, /* U+010ea2: */
+ 0, /* U+010ea3: */
+ 0, /* U+010ea4: */
+ 0, /* U+010ea5: */
+ 0, /* U+010ea6: */
+ 0, /* U+010ea7: */
+ 0, /* U+010ea8: */
+ 0, /* U+010ea9: */
+ 0, /* U+010eaa: */
+ 0, /* U+010eab: */
+ 0, /* U+010eac: */
+ 0, /* U+010ead: */
+ 0, /* U+010eae: */
+ 0, /* U+010eaf: */
+ 0, /* U+010eb0: */
+ 0, /* U+010eb1: */
+ 0, /* U+010eb2: */
+ 0, /* U+010eb3: */
+ 0, /* U+010eb4: */
+ 0, /* U+010eb5: */
+ 0, /* U+010eb6: */
+ 0, /* U+010eb7: */
+ 0, /* U+010eb8: */
+ 0, /* U+010eb9: */
+ 0, /* U+010eba: */
+ 0, /* U+010ebb: */
+ 0, /* U+010ebc: */
+ 0, /* U+010ebd: */
+ 0, /* U+010ebe: */
+ 0, /* U+010ebf: */
+ 0, /* U+010ec0: */
+ 0, /* U+010ec1: */
+ 0, /* U+010ec2: */
+ 0, /* U+010ec3: */
+ 0, /* U+010ec4: */
+ 0, /* U+010ec5: */
+ 0, /* U+010ec6: */
+ 0, /* U+010ec7: */
+ 0, /* U+010ec8: */
+ 0, /* U+010ec9: */
+ 0, /* U+010eca: */
+ 0, /* U+010ecb: */
+ 0, /* U+010ecc: */
+ 0, /* U+010ecd: */
+ 0, /* U+010ece: */
+ 0, /* U+010ecf: */
+ 0, /* U+010ed0: */
+ 0, /* U+010ed1: */
+ 0, /* U+010ed2: */
+ 0, /* U+010ed3: */
+ 0, /* U+010ed4: */
+ 0, /* U+010ed5: */
+ 0, /* U+010ed6: */
+ 0, /* U+010ed7: */
+ 0, /* U+010ed8: */
+ 0, /* U+010ed9: */
+ 0, /* U+010eda: */
+ 0, /* U+010edb: */
+ 0, /* U+010edc: */
+ 0, /* U+010edd: */
+ 0, /* U+010ede: */
+ 0, /* U+010edf: */
+ 0, /* U+010ee0: */
+ 0, /* U+010ee1: */
+ 0, /* U+010ee2: */
+ 0, /* U+010ee3: */
+ 0, /* U+010ee4: */
+ 0, /* U+010ee5: */
+ 0, /* U+010ee6: */
+ 0, /* U+010ee7: */
+ 0, /* U+010ee8: */
+ 0, /* U+010ee9: */
+ 0, /* U+010eea: */
+ 0, /* U+010eeb: */
+ 0, /* U+010eec: */
+ 0, /* U+010eed: */
+ 0, /* U+010eee: */
+ 0, /* U+010eef: */
+ 0, /* U+010ef0: */
+ 0, /* U+010ef1: */
+ 0, /* U+010ef2: */
+ 0, /* U+010ef3: */
+ 0, /* U+010ef4: */
+ 0, /* U+010ef5: */
+ 0, /* U+010ef6: */
+ 0, /* U+010ef7: */
+ 0, /* U+010ef8: */
+ 0, /* U+010ef9: */
+ 0, /* U+010efa: */
+ 0, /* U+010efb: */
+ 0, /* U+010efc: */
+ 0, /* U+010efd: */
+ 0, /* U+010efe: */
+ 0, /* U+010eff: */
+ 0, /* U+010f00: */
+ 0, /* U+010f01: */
+ 0, /* U+010f02: */
+ 0, /* U+010f03: */
+ 0, /* U+010f04: */
+ 0, /* U+010f05: */
+ 0, /* U+010f06: */
+ 0, /* U+010f07: */
+ 0, /* U+010f08: */
+ 0, /* U+010f09: */
+ 0, /* U+010f0a: */
+ 0, /* U+010f0b: */
+ 0, /* U+010f0c: */
+ 0, /* U+010f0d: */
+ 0, /* U+010f0e: */
+ 0, /* U+010f0f: */
+ 0, /* U+010f10: */
+ 0, /* U+010f11: */
+ 0, /* U+010f12: */
+ 0, /* U+010f13: */
+ 0, /* U+010f14: */
+ 0, /* U+010f15: */
+ 0, /* U+010f16: */
+ 0, /* U+010f17: */
+ 0, /* U+010f18: */
+ 0, /* U+010f19: */
+ 0, /* U+010f1a: */
+ 0, /* U+010f1b: */
+ 0, /* U+010f1c: */
+ 0, /* U+010f1d: */
+ 0, /* U+010f1e: */
+ 0, /* U+010f1f: */
+ 0, /* U+010f20: */
+ 0, /* U+010f21: */
+ 0, /* U+010f22: */
+ 0, /* U+010f23: */
+ 0, /* U+010f24: */
+ 0, /* U+010f25: */
+ 0, /* U+010f26: */
+ 0, /* U+010f27: */
+ 0, /* U+010f28: */
+ 0, /* U+010f29: */
+ 0, /* U+010f2a: */
+ 0, /* U+010f2b: */
+ 0, /* U+010f2c: */
+ 0, /* U+010f2d: */
+ 0, /* U+010f2e: */
+ 0, /* U+010f2f: */
+ 0, /* U+010f30: */
+ 0, /* U+010f31: */
+ 0, /* U+010f32: */
+ 0, /* U+010f33: */
+ 0, /* U+010f34: */
+ 0, /* U+010f35: */
+ 0, /* U+010f36: */
+ 0, /* U+010f37: */
+ 0, /* U+010f38: */
+ 0, /* U+010f39: */
+ 0, /* U+010f3a: */
+ 0, /* U+010f3b: */
+ 0, /* U+010f3c: */
+ 0, /* U+010f3d: */
+ 0, /* U+010f3e: */
+ 0, /* U+010f3f: */
+ 0, /* U+010f40: */
+ 0, /* U+010f41: */
+ 0, /* U+010f42: */
+ 0, /* U+010f43: */
+ 0, /* U+010f44: */
+ 0, /* U+010f45: */
+ 0, /* U+010f46: */
+ 0, /* U+010f47: */
+ 0, /* U+010f48: */
+ 0, /* U+010f49: */
+ 0, /* U+010f4a: */
+ 0, /* U+010f4b: */
+ 0, /* U+010f4c: */
+ 0, /* U+010f4d: */
+ 0, /* U+010f4e: */
+ 0, /* U+010f4f: */
+ 0, /* U+010f50: */
+ 0, /* U+010f51: */
+ 0, /* U+010f52: */
+ 0, /* U+010f53: */
+ 0, /* U+010f54: */
+ 0, /* U+010f55: */
+ 0, /* U+010f56: */
+ 0, /* U+010f57: */
+ 0, /* U+010f58: */
+ 0, /* U+010f59: */
+ 0, /* U+010f5a: */
+ 0, /* U+010f5b: */
+ 0, /* U+010f5c: */
+ 0, /* U+010f5d: */
+ 0, /* U+010f5e: */
+ 0, /* U+010f5f: */
+ 0, /* U+010f60: */
+ 0, /* U+010f61: */
+ 0, /* U+010f62: */
+ 0, /* U+010f63: */
+ 0, /* U+010f64: */
+ 0, /* U+010f65: */
+ 0, /* U+010f66: */
+ 0, /* U+010f67: */
+ 0, /* U+010f68: */
+ 0, /* U+010f69: */
+ 0, /* U+010f6a: */
+ 0, /* U+010f6b: */
+ 0, /* U+010f6c: */
+ 0, /* U+010f6d: */
+ 0, /* U+010f6e: */
+ 0, /* U+010f6f: */
+ 0, /* U+010f70: */
+ 0, /* U+010f71: */
+ 0, /* U+010f72: */
+ 0, /* U+010f73: */
+ 0, /* U+010f74: */
+ 0, /* U+010f75: */
+ 0, /* U+010f76: */
+ 0, /* U+010f77: */
+ 0, /* U+010f78: */
+ 0, /* U+010f79: */
+ 0, /* U+010f7a: */
+ 0, /* U+010f7b: */
+ 0, /* U+010f7c: */
+ 0, /* U+010f7d: */
+ 0, /* U+010f7e: */
+ 0, /* U+010f7f: */
+ 0, /* U+010f80: */
+ 0, /* U+010f81: */
+ 0, /* U+010f82: */
+ 0, /* U+010f83: */
+ 0, /* U+010f84: */
+ 0, /* U+010f85: */
+ 0, /* U+010f86: */
+ 0, /* U+010f87: */
+ 0, /* U+010f88: */
+ 0, /* U+010f89: */
+ 0, /* U+010f8a: */
+ 0, /* U+010f8b: */
+ 0, /* U+010f8c: */
+ 0, /* U+010f8d: */
+ 0, /* U+010f8e: */
+ 0, /* U+010f8f: */
+ 0, /* U+010f90: */
+ 0, /* U+010f91: */
+ 0, /* U+010f92: */
+ 0, /* U+010f93: */
+ 0, /* U+010f94: */
+ 0, /* U+010f95: */
+ 0, /* U+010f96: */
+ 0, /* U+010f97: */
+ 0, /* U+010f98: */
+ 0, /* U+010f99: */
+ 0, /* U+010f9a: */
+ 0, /* U+010f9b: */
+ 0, /* U+010f9c: */
+ 0, /* U+010f9d: */
+ 0, /* U+010f9e: */
+ 0, /* U+010f9f: */
+ 0, /* U+010fa0: */
+ 0, /* U+010fa1: */
+ 0, /* U+010fa2: */
+ 0, /* U+010fa3: */
+ 0, /* U+010fa4: */
+ 0, /* U+010fa5: */
+ 0, /* U+010fa6: */
+ 0, /* U+010fa7: */
+ 0, /* U+010fa8: */
+ 0, /* U+010fa9: */
+ 0, /* U+010faa: */
+ 0, /* U+010fab: */
+ 0, /* U+010fac: */
+ 0, /* U+010fad: */
+ 0, /* U+010fae: */
+ 0, /* U+010faf: */
+ 0, /* U+010fb0: */
+ 0, /* U+010fb1: */
+ 0, /* U+010fb2: */
+ 0, /* U+010fb3: */
+ 0, /* U+010fb4: */
+ 0, /* U+010fb5: */
+ 0, /* U+010fb6: */
+ 0, /* U+010fb7: */
+ 0, /* U+010fb8: */
+ 0, /* U+010fb9: */
+ 0, /* U+010fba: */
+ 0, /* U+010fbb: */
+ 0, /* U+010fbc: */
+ 0, /* U+010fbd: */
+ 0, /* U+010fbe: */
+ 0, /* U+010fbf: */
+ 0, /* U+010fc0: */
+ 0, /* U+010fc1: */
+ 0, /* U+010fc2: */
+ 0, /* U+010fc3: */
+ 0, /* U+010fc4: */
+ 0, /* U+010fc5: */
+ 0, /* U+010fc6: */
+ 0, /* U+010fc7: */
+ 0, /* U+010fc8: */
+ 0, /* U+010fc9: */
+ 0, /* U+010fca: */
+ 0, /* U+010fcb: */
+ 0, /* U+010fcc: */
+ 0, /* U+010fcd: */
+ 0, /* U+010fce: */
+ 0, /* U+010fcf: */
+ 0, /* U+010fd0: */
+ 0, /* U+010fd1: */
+ 0, /* U+010fd2: */
+ 0, /* U+010fd3: */
+ 0, /* U+010fd4: */
+ 0, /* U+010fd5: */
+ 0, /* U+010fd6: */
+ 0, /* U+010fd7: */
+ 0, /* U+010fd8: */
+ 0, /* U+010fd9: */
+ 0, /* U+010fda: */
+ 0, /* U+010fdb: */
+ 0, /* U+010fdc: */
+ 0, /* U+010fdd: */
+ 0, /* U+010fde: */
+ 0, /* U+010fdf: */
+ 0, /* U+010fe0: */
+ 0, /* U+010fe1: */
+ 0, /* U+010fe2: */
+ 0, /* U+010fe3: */
+ 0, /* U+010fe4: */
+ 0, /* U+010fe5: */
+ 0, /* U+010fe6: */
+ 0, /* U+010fe7: */
+ 0, /* U+010fe8: */
+ 0, /* U+010fe9: */
+ 0, /* U+010fea: */
+ 0, /* U+010feb: */
+ 0, /* U+010fec: */
+ 0, /* U+010fed: */
+ 0, /* U+010fee: */
+ 0, /* U+010fef: */
+ 0, /* U+010ff0: */
+ 0, /* U+010ff1: */
+ 0, /* U+010ff2: */
+ 0, /* U+010ff3: */
+ 0, /* U+010ff4: */
+ 0, /* U+010ff5: */
+ 0, /* U+010ff6: */
+ 0, /* U+010ff7: */
+ 0, /* U+010ff8: */
+ 0, /* U+010ff9: */
+ 0, /* U+010ffa: */
+ 0, /* U+010ffb: */
+ 0, /* U+010ffc: */
+ 0, /* U+010ffd: */
+ 0, /* U+010ffe: */
+ 0, /* U+010fff: */
+ RTUNI_ALPHA, /* U+011000: BRAHMI SIGN CANDRABINDU*/
+ RTUNI_ALPHA, /* U+011001: BRAHMI SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+011002: BRAHMI SIGN VISARGA*/
+ RTUNI_ALPHA, /* U+011003: BRAHMI SIGN JIHVAMULIYA*/
+ RTUNI_ALPHA, /* U+011004: BRAHMI SIGN UPADHMANIYA*/
+ RTUNI_ALPHA, /* U+011005: BRAHMI LETTER A*/
+ RTUNI_ALPHA, /* U+011006: BRAHMI LETTER AA*/
+ RTUNI_ALPHA, /* U+011007: BRAHMI LETTER I*/
+ RTUNI_ALPHA, /* U+011008: BRAHMI LETTER II*/
+ RTUNI_ALPHA, /* U+011009: BRAHMI LETTER U*/
+ RTUNI_ALPHA, /* U+01100a: BRAHMI LETTER UU*/
+ RTUNI_ALPHA, /* U+01100b: BRAHMI LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+01100c: BRAHMI LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+01100d: BRAHMI LETTER VOCALIC L*/
+ RTUNI_ALPHA, /* U+01100e: BRAHMI LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+01100f: BRAHMI LETTER E*/
+ RTUNI_ALPHA, /* U+011010: BRAHMI LETTER AI*/
+ RTUNI_ALPHA, /* U+011011: BRAHMI LETTER O*/
+ RTUNI_ALPHA, /* U+011012: BRAHMI LETTER AU*/
+ RTUNI_ALPHA, /* U+011013: BRAHMI LETTER KA*/
+ RTUNI_ALPHA, /* U+011014: BRAHMI LETTER KHA*/
+ RTUNI_ALPHA, /* U+011015: BRAHMI LETTER GA*/
+ RTUNI_ALPHA, /* U+011016: BRAHMI LETTER GHA*/
+ RTUNI_ALPHA, /* U+011017: BRAHMI LETTER NGA*/
+ RTUNI_ALPHA, /* U+011018: BRAHMI LETTER CA*/
+ RTUNI_ALPHA, /* U+011019: BRAHMI LETTER CHA*/
+ RTUNI_ALPHA, /* U+01101a: BRAHMI LETTER JA*/
+ RTUNI_ALPHA, /* U+01101b: BRAHMI LETTER JHA*/
+ RTUNI_ALPHA, /* U+01101c: BRAHMI LETTER NYA*/
+ RTUNI_ALPHA, /* U+01101d: BRAHMI LETTER TTA*/
+ RTUNI_ALPHA, /* U+01101e: BRAHMI LETTER TTHA*/
+ RTUNI_ALPHA, /* U+01101f: BRAHMI LETTER DDA*/
+ RTUNI_ALPHA, /* U+011020: BRAHMI LETTER DDHA*/
+ RTUNI_ALPHA, /* U+011021: BRAHMI LETTER NNA*/
+ RTUNI_ALPHA, /* U+011022: BRAHMI LETTER TA*/
+ RTUNI_ALPHA, /* U+011023: BRAHMI LETTER THA*/
+ RTUNI_ALPHA, /* U+011024: BRAHMI LETTER DA*/
+ RTUNI_ALPHA, /* U+011025: BRAHMI LETTER DHA*/
+ RTUNI_ALPHA, /* U+011026: BRAHMI LETTER NA*/
+ RTUNI_ALPHA, /* U+011027: BRAHMI LETTER PA*/
+ RTUNI_ALPHA, /* U+011028: BRAHMI LETTER PHA*/
+ RTUNI_ALPHA, /* U+011029: BRAHMI LETTER BA*/
+ RTUNI_ALPHA, /* U+01102a: BRAHMI LETTER BHA*/
+ RTUNI_ALPHA, /* U+01102b: BRAHMI LETTER MA*/
+ RTUNI_ALPHA, /* U+01102c: BRAHMI LETTER YA*/
+ RTUNI_ALPHA, /* U+01102d: BRAHMI LETTER RA*/
+ RTUNI_ALPHA, /* U+01102e: BRAHMI LETTER LA*/
+ RTUNI_ALPHA, /* U+01102f: BRAHMI LETTER VA*/
+ RTUNI_ALPHA, /* U+011030: BRAHMI LETTER SHA*/
+ RTUNI_ALPHA, /* U+011031: BRAHMI LETTER SSA*/
+ RTUNI_ALPHA, /* U+011032: BRAHMI LETTER SA*/
+ RTUNI_ALPHA, /* U+011033: BRAHMI LETTER HA*/
+ RTUNI_ALPHA, /* U+011034: BRAHMI LETTER LLA*/
+ RTUNI_ALPHA, /* U+011035: BRAHMI LETTER OLD TAMIL LLLA*/
+ RTUNI_ALPHA, /* U+011036: BRAHMI LETTER OLD TAMIL RRA*/
+ RTUNI_ALPHA, /* U+011037: BRAHMI LETTER OLD TAMIL NNNA*/
+ RTUNI_ALPHA, /* U+011038: BRAHMI VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+011039: BRAHMI VOWEL SIGN BHATTIPROLU AA*/
+ RTUNI_ALPHA, /* U+01103a: BRAHMI VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+01103b: BRAHMI VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+01103c: BRAHMI VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+01103d: BRAHMI VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+01103e: BRAHMI VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+01103f: BRAHMI VOWEL SIGN VOCALIC RR*/
+ RTUNI_ALPHA, /* U+011040: BRAHMI VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+011041: BRAHMI VOWEL SIGN VOCALIC LL*/
+ RTUNI_ALPHA, /* U+011042: BRAHMI VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+011043: BRAHMI VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+011044: BRAHMI VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+011045: BRAHMI VOWEL SIGN AU*/
+ 0, /* U+011046: BRAHMI VIRAMA*/
+ 0, /* U+011047: BRAHMI DANDA*/
+ 0, /* U+011048: BRAHMI DOUBLE DANDA*/
+ 0, /* U+011049: BRAHMI PUNCTUATION DOT*/
+ 0, /* U+01104a: BRAHMI PUNCTUATION DOUBLE DOT*/
+ 0, /* U+01104b: BRAHMI PUNCTUATION LINE*/
+ 0, /* U+01104c: BRAHMI PUNCTUATION CRESCENT BAR*/
+ 0, /* U+01104d: BRAHMI PUNCTUATION LOTUS*/
+ 0, /* U+01104e: */
+ 0, /* U+01104f: */
+ 0, /* U+011050: */
+ 0, /* U+011051: */
+ 0, /* U+011052: BRAHMI NUMBER ONE*/
+ 0, /* U+011053: BRAHMI NUMBER TWO*/
+ 0, /* U+011054: BRAHMI NUMBER THREE*/
+ 0, /* U+011055: BRAHMI NUMBER FOUR*/
+ 0, /* U+011056: BRAHMI NUMBER FIVE*/
+ 0, /* U+011057: BRAHMI NUMBER SIX*/
+ 0, /* U+011058: BRAHMI NUMBER SEVEN*/
+ 0, /* U+011059: BRAHMI NUMBER EIGHT*/
+ 0, /* U+01105a: BRAHMI NUMBER NINE*/
+ 0, /* U+01105b: BRAHMI NUMBER TEN*/
+ 0, /* U+01105c: BRAHMI NUMBER TWENTY*/
+ 0, /* U+01105d: BRAHMI NUMBER THIRTY*/
+ 0, /* U+01105e: BRAHMI NUMBER FORTY*/
+ 0, /* U+01105f: BRAHMI NUMBER FIFTY*/
+ 0, /* U+011060: BRAHMI NUMBER SIXTY*/
+ 0, /* U+011061: BRAHMI NUMBER SEVENTY*/
+ 0, /* U+011062: BRAHMI NUMBER EIGHTY*/
+ 0, /* U+011063: BRAHMI NUMBER NINETY*/
+ 0, /* U+011064: BRAHMI NUMBER ONE HUNDRED*/
+ 0, /* U+011065: BRAHMI NUMBER ONE THOUSAND*/
+ RTUNI_DDIGIT, /* U+011066: BRAHMI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+011067: BRAHMI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+011068: BRAHMI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+011069: BRAHMI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+01106a: BRAHMI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+01106b: BRAHMI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+01106c: BRAHMI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+01106d: BRAHMI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+01106e: BRAHMI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+01106f: BRAHMI DIGIT NINE*/
+ 0, /* U+011070: */
+ 0, /* U+011071: */
+ 0, /* U+011072: */
+ 0, /* U+011073: */
+ 0, /* U+011074: */
+ 0, /* U+011075: */
+ 0, /* U+011076: */
+ 0, /* U+011077: */
+ 0, /* U+011078: */
+ 0, /* U+011079: */
+ 0, /* U+01107a: */
+ 0, /* U+01107b: */
+ 0, /* U+01107c: */
+ 0, /* U+01107d: */
+ 0, /* U+01107e: */
+ 0, /* U+01107f: */
+ 0, /* U+011080: KAITHI SIGN CANDRABINDU*/
+ 0, /* U+011081: KAITHI SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+011082: KAITHI SIGN VISARGA*/
+ RTUNI_ALPHA, /* U+011083: KAITHI LETTER A*/
+ RTUNI_ALPHA, /* U+011084: KAITHI LETTER AA*/
+ RTUNI_ALPHA, /* U+011085: KAITHI LETTER I*/
+ RTUNI_ALPHA, /* U+011086: KAITHI LETTER II*/
+ RTUNI_ALPHA, /* U+011087: KAITHI LETTER U*/
+ RTUNI_ALPHA, /* U+011088: KAITHI LETTER UU*/
+ RTUNI_ALPHA, /* U+011089: KAITHI LETTER E*/
+ RTUNI_ALPHA, /* U+01108a: KAITHI LETTER AI*/
+ RTUNI_ALPHA, /* U+01108b: KAITHI LETTER O*/
+ RTUNI_ALPHA, /* U+01108c: KAITHI LETTER AU*/
+ RTUNI_ALPHA, /* U+01108d: KAITHI LETTER KA*/
+ RTUNI_ALPHA, /* U+01108e: KAITHI LETTER KHA*/
+ RTUNI_ALPHA, /* U+01108f: KAITHI LETTER GA*/
+ RTUNI_ALPHA, /* U+011090: KAITHI LETTER GHA*/
+ RTUNI_ALPHA, /* U+011091: KAITHI LETTER NGA*/
+ RTUNI_ALPHA, /* U+011092: KAITHI LETTER CA*/
+ RTUNI_ALPHA, /* U+011093: KAITHI LETTER CHA*/
+ RTUNI_ALPHA, /* U+011094: KAITHI LETTER JA*/
+ RTUNI_ALPHA, /* U+011095: KAITHI LETTER JHA*/
+ RTUNI_ALPHA, /* U+011096: KAITHI LETTER NYA*/
+ RTUNI_ALPHA, /* U+011097: KAITHI LETTER TTA*/
+ RTUNI_ALPHA, /* U+011098: KAITHI LETTER TTHA*/
+ RTUNI_ALPHA, /* U+011099: KAITHI LETTER DDA*/
+ RTUNI_ALPHA, /* U+01109a: KAITHI LETTER DDDHA*/
+ RTUNI_ALPHA, /* U+01109b: KAITHI LETTER DDHA*/
+ RTUNI_ALPHA, /* U+01109c: KAITHI LETTER RHA*/
+ RTUNI_ALPHA, /* U+01109d: KAITHI LETTER NNA*/
+ RTUNI_ALPHA, /* U+01109e: KAITHI LETTER TA*/
+ RTUNI_ALPHA, /* U+01109f: KAITHI LETTER THA*/
+ RTUNI_ALPHA, /* U+0110a0: KAITHI LETTER DA*/
+ RTUNI_ALPHA, /* U+0110a1: KAITHI LETTER DHA*/
+ RTUNI_ALPHA, /* U+0110a2: KAITHI LETTER NA*/
+ RTUNI_ALPHA, /* U+0110a3: KAITHI LETTER PA*/
+ RTUNI_ALPHA, /* U+0110a4: KAITHI LETTER PHA*/
+ RTUNI_ALPHA, /* U+0110a5: KAITHI LETTER BA*/
+ RTUNI_ALPHA, /* U+0110a6: KAITHI LETTER BHA*/
+ RTUNI_ALPHA, /* U+0110a7: KAITHI LETTER MA*/
+ RTUNI_ALPHA, /* U+0110a8: KAITHI LETTER YA*/
+ RTUNI_ALPHA, /* U+0110a9: KAITHI LETTER RA*/
+ RTUNI_ALPHA, /* U+0110aa: KAITHI LETTER LA*/
+ RTUNI_ALPHA, /* U+0110ab: KAITHI LETTER VA*/
+ RTUNI_ALPHA, /* U+0110ac: KAITHI LETTER SHA*/
+ RTUNI_ALPHA, /* U+0110ad: KAITHI LETTER SSA*/
+ RTUNI_ALPHA, /* U+0110ae: KAITHI LETTER SA*/
+ RTUNI_ALPHA, /* U+0110af: KAITHI LETTER HA*/
+ RTUNI_ALPHA, /* U+0110b0: KAITHI VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+0110b1: KAITHI VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+0110b2: KAITHI VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+0110b3: KAITHI VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+0110b4: KAITHI VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+0110b5: KAITHI VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+0110b6: KAITHI VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+0110b7: KAITHI VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+0110b8: KAITHI VOWEL SIGN AU*/
+ 0, /* U+0110b9: KAITHI SIGN VIRAMA*/
+ 0, /* U+0110ba: KAITHI SIGN NUKTA*/
+ 0, /* U+0110bb: KAITHI ABBREVIATION SIGN*/
+ 0, /* U+0110bc: KAITHI ENUMERATION SIGN*/
+ 0, /* U+0110bd: KAITHI NUMBER SIGN*/
+ 0, /* U+0110be: KAITHI SECTION MARK*/
+ 0, /* U+0110bf: KAITHI DOUBLE SECTION MARK*/
+ 0, /* U+0110c0: KAITHI DANDA*/
+ 0, /* U+0110c1: KAITHI DOUBLE DANDA*/
+ 0, /* U+0110c2: */
+ 0, /* U+0110c3: */
+ 0, /* U+0110c4: */
+ 0, /* U+0110c5: */
+ 0, /* U+0110c6: */
+ 0, /* U+0110c7: */
+ 0, /* U+0110c8: */
+ 0, /* U+0110c9: */
+ 0, /* U+0110ca: */
+ 0, /* U+0110cb: */
+ 0, /* U+0110cc: */
+ 0, /* U+0110cd: */
+ 0, /* U+0110ce: */
+ 0, /* U+0110cf: */
+ RTUNI_ALPHA, /* U+0110d0: SORA SOMPENG LETTER SAH*/
+ RTUNI_ALPHA, /* U+0110d1: SORA SOMPENG LETTER TAH*/
+ RTUNI_ALPHA, /* U+0110d2: SORA SOMPENG LETTER BAH*/
+ RTUNI_ALPHA, /* U+0110d3: SORA SOMPENG LETTER CAH*/
+ RTUNI_ALPHA, /* U+0110d4: SORA SOMPENG LETTER DAH*/
+ RTUNI_ALPHA, /* U+0110d5: SORA SOMPENG LETTER GAH*/
+ RTUNI_ALPHA, /* U+0110d6: SORA SOMPENG LETTER MAH*/
+ RTUNI_ALPHA, /* U+0110d7: SORA SOMPENG LETTER NGAH*/
+ RTUNI_ALPHA, /* U+0110d8: SORA SOMPENG LETTER LAH*/
+ RTUNI_ALPHA, /* U+0110d9: SORA SOMPENG LETTER NAH*/
+ RTUNI_ALPHA, /* U+0110da: SORA SOMPENG LETTER VAH*/
+ RTUNI_ALPHA, /* U+0110db: SORA SOMPENG LETTER PAH*/
+ RTUNI_ALPHA, /* U+0110dc: SORA SOMPENG LETTER YAH*/
+ RTUNI_ALPHA, /* U+0110dd: SORA SOMPENG LETTER RAH*/
+ RTUNI_ALPHA, /* U+0110de: SORA SOMPENG LETTER HAH*/
+ RTUNI_ALPHA, /* U+0110df: SORA SOMPENG LETTER KAH*/
+ RTUNI_ALPHA, /* U+0110e0: SORA SOMPENG LETTER JAH*/
+ RTUNI_ALPHA, /* U+0110e1: SORA SOMPENG LETTER NYAH*/
+ RTUNI_ALPHA, /* U+0110e2: SORA SOMPENG LETTER AH*/
+ RTUNI_ALPHA, /* U+0110e3: SORA SOMPENG LETTER EEH*/
+ RTUNI_ALPHA, /* U+0110e4: SORA SOMPENG LETTER IH*/
+ RTUNI_ALPHA, /* U+0110e5: SORA SOMPENG LETTER UH*/
+ RTUNI_ALPHA, /* U+0110e6: SORA SOMPENG LETTER OH*/
+ RTUNI_ALPHA, /* U+0110e7: SORA SOMPENG LETTER EH*/
+ RTUNI_ALPHA, /* U+0110e8: SORA SOMPENG LETTER MAE*/
+ 0, /* U+0110e9: */
+ 0, /* U+0110ea: */
+ 0, /* U+0110eb: */
+ 0, /* U+0110ec: */
+ 0, /* U+0110ed: */
+ 0, /* U+0110ee: */
+ 0, /* U+0110ef: */
+ RTUNI_DDIGIT, /* U+0110f0: SORA SOMPENG DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+0110f1: SORA SOMPENG DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+0110f2: SORA SOMPENG DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+0110f3: SORA SOMPENG DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+0110f4: SORA SOMPENG DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+0110f5: SORA SOMPENG DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+0110f6: SORA SOMPENG DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+0110f7: SORA SOMPENG DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+0110f8: SORA SOMPENG DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+0110f9: SORA SOMPENG DIGIT NINE*/
+ 0, /* U+0110fa: */
+ 0, /* U+0110fb: */
+ 0, /* U+0110fc: */
+ 0, /* U+0110fd: */
+ 0, /* U+0110fe: */
+ 0, /* U+0110ff: */
+ RTUNI_ALPHA, /* U+011100: CHAKMA SIGN CANDRABINDU*/
+ RTUNI_ALPHA, /* U+011101: CHAKMA SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+011102: CHAKMA SIGN VISARGA*/
+ RTUNI_ALPHA, /* U+011103: CHAKMA LETTER AA*/
+ RTUNI_ALPHA, /* U+011104: CHAKMA LETTER I*/
+ RTUNI_ALPHA, /* U+011105: CHAKMA LETTER U*/
+ RTUNI_ALPHA, /* U+011106: CHAKMA LETTER E*/
+ RTUNI_ALPHA, /* U+011107: CHAKMA LETTER KAA*/
+ RTUNI_ALPHA, /* U+011108: CHAKMA LETTER KHAA*/
+ RTUNI_ALPHA, /* U+011109: CHAKMA LETTER GAA*/
+ RTUNI_ALPHA, /* U+01110a: CHAKMA LETTER GHAA*/
+ RTUNI_ALPHA, /* U+01110b: CHAKMA LETTER NGAA*/
+ RTUNI_ALPHA, /* U+01110c: CHAKMA LETTER CAA*/
+ RTUNI_ALPHA, /* U+01110d: CHAKMA LETTER CHAA*/
+ RTUNI_ALPHA, /* U+01110e: CHAKMA LETTER JAA*/
+ RTUNI_ALPHA, /* U+01110f: CHAKMA LETTER JHAA*/
+ RTUNI_ALPHA, /* U+011110: CHAKMA LETTER NYAA*/
+ RTUNI_ALPHA, /* U+011111: CHAKMA LETTER TTAA*/
+ RTUNI_ALPHA, /* U+011112: CHAKMA LETTER TTHAA*/
+ RTUNI_ALPHA, /* U+011113: CHAKMA LETTER DDAA*/
+ RTUNI_ALPHA, /* U+011114: CHAKMA LETTER DDHAA*/
+ RTUNI_ALPHA, /* U+011115: CHAKMA LETTER NNAA*/
+ RTUNI_ALPHA, /* U+011116: CHAKMA LETTER TAA*/
+ RTUNI_ALPHA, /* U+011117: CHAKMA LETTER THAA*/
+ RTUNI_ALPHA, /* U+011118: CHAKMA LETTER DAA*/
+ RTUNI_ALPHA, /* U+011119: CHAKMA LETTER DHAA*/
+ RTUNI_ALPHA, /* U+01111a: CHAKMA LETTER NAA*/
+ RTUNI_ALPHA, /* U+01111b: CHAKMA LETTER PAA*/
+ RTUNI_ALPHA, /* U+01111c: CHAKMA LETTER PHAA*/
+ RTUNI_ALPHA, /* U+01111d: CHAKMA LETTER BAA*/
+ RTUNI_ALPHA, /* U+01111e: CHAKMA LETTER BHAA*/
+ RTUNI_ALPHA, /* U+01111f: CHAKMA LETTER MAA*/
+ RTUNI_ALPHA, /* U+011120: CHAKMA LETTER YYAA*/
+ RTUNI_ALPHA, /* U+011121: CHAKMA LETTER YAA*/
+ RTUNI_ALPHA, /* U+011122: CHAKMA LETTER RAA*/
+ RTUNI_ALPHA, /* U+011123: CHAKMA LETTER LAA*/
+ RTUNI_ALPHA, /* U+011124: CHAKMA LETTER WAA*/
+ RTUNI_ALPHA, /* U+011125: CHAKMA LETTER SAA*/
+ RTUNI_ALPHA, /* U+011126: CHAKMA LETTER HAA*/
+ RTUNI_ALPHA, /* U+011127: CHAKMA VOWEL SIGN A*/
+ RTUNI_ALPHA, /* U+011128: CHAKMA VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+011129: CHAKMA VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+01112a: CHAKMA VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+01112b: CHAKMA VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+01112c: CHAKMA VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+01112d: CHAKMA VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+01112e: CHAKMA VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+01112f: CHAKMA VOWEL SIGN AU*/
+ RTUNI_ALPHA, /* U+011130: CHAKMA VOWEL SIGN OI*/
+ RTUNI_ALPHA, /* U+011131: CHAKMA O MARK*/
+ RTUNI_ALPHA, /* U+011132: CHAKMA AU MARK*/
+ 0, /* U+011133: CHAKMA VIRAMA*/
+ 0, /* U+011134: CHAKMA MAAYYAA*/
+ 0, /* U+011135: */
+ RTUNI_DDIGIT, /* U+011136: CHAKMA DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+011137: CHAKMA DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+011138: CHAKMA DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+011139: CHAKMA DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+01113a: CHAKMA DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+01113b: CHAKMA DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+01113c: CHAKMA DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+01113d: CHAKMA DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+01113e: CHAKMA DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+01113f: CHAKMA DIGIT NINE*/
+ 0, /* U+011140: CHAKMA SECTION MARK*/
+ 0, /* U+011141: CHAKMA DANDA*/
+ 0, /* U+011142: CHAKMA DOUBLE DANDA*/
+ 0, /* U+011143: CHAKMA QUESTION MARK*/
+ 0, /* U+011144: */
+ 0, /* U+011145: */
+ 0, /* U+011146: */
+ 0, /* U+011147: */
+ 0, /* U+011148: */
+ 0, /* U+011149: */
+ 0, /* U+01114a: */
+ 0, /* U+01114b: */
+ 0, /* U+01114c: */
+ 0, /* U+01114d: */
+ 0, /* U+01114e: */
+ 0, /* U+01114f: */
+ 0, /* U+011150: */
+ 0, /* U+011151: */
+ 0, /* U+011152: */
+ 0, /* U+011153: */
+ 0, /* U+011154: */
+ 0, /* U+011155: */
+ 0, /* U+011156: */
+ 0, /* U+011157: */
+ 0, /* U+011158: */
+ 0, /* U+011159: */
+ 0, /* U+01115a: */
+ 0, /* U+01115b: */
+ 0, /* U+01115c: */
+ 0, /* U+01115d: */
+ 0, /* U+01115e: */
+ 0, /* U+01115f: */
+ 0, /* U+011160: */
+ 0, /* U+011161: */
+ 0, /* U+011162: */
+ 0, /* U+011163: */
+ 0, /* U+011164: */
+ 0, /* U+011165: */
+ 0, /* U+011166: */
+ 0, /* U+011167: */
+ 0, /* U+011168: */
+ 0, /* U+011169: */
+ 0, /* U+01116a: */
+ 0, /* U+01116b: */
+ 0, /* U+01116c: */
+ 0, /* U+01116d: */
+ 0, /* U+01116e: */
+ 0, /* U+01116f: */
+ 0, /* U+011170: */
+ 0, /* U+011171: */
+ 0, /* U+011172: */
+ 0, /* U+011173: */
+ 0, /* U+011174: */
+ 0, /* U+011175: */
+ 0, /* U+011176: */
+ 0, /* U+011177: */
+ 0, /* U+011178: */
+ 0, /* U+011179: */
+ 0, /* U+01117a: */
+ 0, /* U+01117b: */
+ 0, /* U+01117c: */
+ 0, /* U+01117d: */
+ 0, /* U+01117e: */
+ 0, /* U+01117f: */
+ RTUNI_ALPHA, /* U+011180: SHARADA SIGN CANDRABINDU*/
+ RTUNI_ALPHA, /* U+011181: SHARADA SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+011182: SHARADA SIGN VISARGA*/
+ RTUNI_ALPHA, /* U+011183: SHARADA LETTER A*/
+ RTUNI_ALPHA, /* U+011184: SHARADA LETTER AA*/
+ RTUNI_ALPHA, /* U+011185: SHARADA LETTER I*/
+ RTUNI_ALPHA, /* U+011186: SHARADA LETTER II*/
+ RTUNI_ALPHA, /* U+011187: SHARADA LETTER U*/
+ RTUNI_ALPHA, /* U+011188: SHARADA LETTER UU*/
+ RTUNI_ALPHA, /* U+011189: SHARADA LETTER VOCALIC R*/
+ RTUNI_ALPHA, /* U+01118a: SHARADA LETTER VOCALIC RR*/
+ RTUNI_ALPHA, /* U+01118b: SHARADA LETTER VOCALIC L*/
+ RTUNI_ALPHA, /* U+01118c: SHARADA LETTER VOCALIC LL*/
+ RTUNI_ALPHA, /* U+01118d: SHARADA LETTER E*/
+ RTUNI_ALPHA, /* U+01118e: SHARADA LETTER AI*/
+ RTUNI_ALPHA, /* U+01118f: SHARADA LETTER O*/
+ RTUNI_ALPHA, /* U+011190: SHARADA LETTER AU*/
+ RTUNI_ALPHA, /* U+011191: SHARADA LETTER KA*/
+ RTUNI_ALPHA, /* U+011192: SHARADA LETTER KHA*/
+ RTUNI_ALPHA, /* U+011193: SHARADA LETTER GA*/
+ RTUNI_ALPHA, /* U+011194: SHARADA LETTER GHA*/
+ RTUNI_ALPHA, /* U+011195: SHARADA LETTER NGA*/
+ RTUNI_ALPHA, /* U+011196: SHARADA LETTER CA*/
+ RTUNI_ALPHA, /* U+011197: SHARADA LETTER CHA*/
+ RTUNI_ALPHA, /* U+011198: SHARADA LETTER JA*/
+ RTUNI_ALPHA, /* U+011199: SHARADA LETTER JHA*/
+ RTUNI_ALPHA, /* U+01119a: SHARADA LETTER NYA*/
+ RTUNI_ALPHA, /* U+01119b: SHARADA LETTER TTA*/
+ RTUNI_ALPHA, /* U+01119c: SHARADA LETTER TTHA*/
+ RTUNI_ALPHA, /* U+01119d: SHARADA LETTER DDA*/
+ RTUNI_ALPHA, /* U+01119e: SHARADA LETTER DDHA*/
+ RTUNI_ALPHA, /* U+01119f: SHARADA LETTER NNA*/
+ RTUNI_ALPHA, /* U+0111a0: SHARADA LETTER TA*/
+ RTUNI_ALPHA, /* U+0111a1: SHARADA LETTER THA*/
+ RTUNI_ALPHA, /* U+0111a2: SHARADA LETTER DA*/
+ RTUNI_ALPHA, /* U+0111a3: SHARADA LETTER DHA*/
+ RTUNI_ALPHA, /* U+0111a4: SHARADA LETTER NA*/
+ RTUNI_ALPHA, /* U+0111a5: SHARADA LETTER PA*/
+ RTUNI_ALPHA, /* U+0111a6: SHARADA LETTER PHA*/
+ RTUNI_ALPHA, /* U+0111a7: SHARADA LETTER BA*/
+ RTUNI_ALPHA, /* U+0111a8: SHARADA LETTER BHA*/
+ RTUNI_ALPHA, /* U+0111a9: SHARADA LETTER MA*/
+ RTUNI_ALPHA, /* U+0111aa: SHARADA LETTER YA*/
+ RTUNI_ALPHA, /* U+0111ab: SHARADA LETTER RA*/
+ RTUNI_ALPHA, /* U+0111ac: SHARADA LETTER LA*/
+ RTUNI_ALPHA, /* U+0111ad: SHARADA LETTER LLA*/
+ RTUNI_ALPHA, /* U+0111ae: SHARADA LETTER VA*/
+ RTUNI_ALPHA, /* U+0111af: SHARADA LETTER SHA*/
+ RTUNI_ALPHA, /* U+0111b0: SHARADA LETTER SSA*/
+ RTUNI_ALPHA, /* U+0111b1: SHARADA LETTER SA*/
+ RTUNI_ALPHA, /* U+0111b2: SHARADA LETTER HA*/
+ RTUNI_ALPHA, /* U+0111b3: SHARADA VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+0111b4: SHARADA VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+0111b5: SHARADA VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+0111b6: SHARADA VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+0111b7: SHARADA VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+0111b8: SHARADA VOWEL SIGN VOCALIC R*/
+ RTUNI_ALPHA, /* U+0111b9: SHARADA VOWEL SIGN VOCALIC RR*/
+ RTUNI_ALPHA, /* U+0111ba: SHARADA VOWEL SIGN VOCALIC L*/
+ RTUNI_ALPHA, /* U+0111bb: SHARADA VOWEL SIGN VOCALIC LL*/
+ RTUNI_ALPHA, /* U+0111bc: SHARADA VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+0111bd: SHARADA VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+0111be: SHARADA VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+0111bf: SHARADA VOWEL SIGN AU*/
+ 0, /* U+0111c0: SHARADA SIGN VIRAMA*/
+ RTUNI_ALPHA, /* U+0111c1: SHARADA SIGN AVAGRAHA*/
+ RTUNI_ALPHA, /* U+0111c2: SHARADA SIGN JIHVAMULIYA*/
+ RTUNI_ALPHA, /* U+0111c3: SHARADA SIGN UPADHMANIYA*/
+ RTUNI_ALPHA, /* U+0111c4: SHARADA OM*/
+ 0, /* U+0111c5: SHARADA DANDA*/
+ 0, /* U+0111c6: SHARADA DOUBLE DANDA*/
+ 0, /* U+0111c7: SHARADA ABBREVIATION SIGN*/
+ 0, /* U+0111c8: SHARADA SEPARATOR*/
+ 0, /* U+0111c9: */
+ 0, /* U+0111ca: */
+ 0, /* U+0111cb: */
+ 0, /* U+0111cc: */
+ 0, /* U+0111cd: */
+ 0, /* U+0111ce: */
+ 0, /* U+0111cf: */
+ RTUNI_DDIGIT, /* U+0111d0: SHARADA DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+0111d1: SHARADA DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+0111d2: SHARADA DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+0111d3: SHARADA DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+0111d4: SHARADA DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+0111d5: SHARADA DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+0111d6: SHARADA DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+0111d7: SHARADA DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+0111d8: SHARADA DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+0111d9: SHARADA DIGIT NINE*/
+ 0, /* U+0111da: */
+ 0, /* U+0111db: */
+ 0, /* U+0111dc: */
+ 0, /* U+0111dd: */
+ 0, /* U+0111de: */
+ 0, /* U+0111df: */
+ 0, /* U+0111e0: */
+ 0, /* U+0111e1: */
+ 0, /* U+0111e2: */
+ 0, /* U+0111e3: */
+ 0, /* U+0111e4: */
+ 0, /* U+0111e5: */
+ 0, /* U+0111e6: */
+ 0, /* U+0111e7: */
+ 0, /* U+0111e8: */
+ 0, /* U+0111e9: */
+ 0, /* U+0111ea: */
+ 0, /* U+0111eb: */
+ 0, /* U+0111ec: */
+ 0, /* U+0111ed: */
+ 0, /* U+0111ee: */
+ 0, /* U+0111ef: */
+ 0, /* U+0111f0: */
+ 0, /* U+0111f1: */
+ 0, /* U+0111f2: */
+ 0, /* U+0111f3: */
+ 0, /* U+0111f4: */
+ 0, /* U+0111f5: */
+ 0, /* U+0111f6: */
+ 0, /* U+0111f7: */
+ 0, /* U+0111f8: */
+ 0, /* U+0111f9: */
+ 0, /* U+0111fa: */
+ 0, /* U+0111fb: */
+ 0, /* U+0111fc: */
+ 0, /* U+0111fd: */
+ 0, /* U+0111fe: */
+ 0, /* U+0111ff: */
+ 0, /* U+011200: */
+ 0, /* U+011201: */
+ 0, /* U+011202: */
+ 0, /* U+011203: */
+ 0, /* U+011204: */
+ 0, /* U+011205: */
+ 0, /* U+011206: */
+ 0, /* U+011207: */
+ 0, /* U+011208: */
+ 0, /* U+011209: */
+ 0, /* U+01120a: */
+ 0, /* U+01120b: */
+ 0, /* U+01120c: */
+ 0, /* U+01120d: */
+ 0, /* U+01120e: */
+ 0, /* U+01120f: */
+ 0, /* U+011210: */
+ 0, /* U+011211: */
+ 0, /* U+011212: */
+ 0, /* U+011213: */
+ 0, /* U+011214: */
+ 0, /* U+011215: */
+ 0, /* U+011216: */
+ 0, /* U+011217: */
+ 0, /* U+011218: */
+ 0, /* U+011219: */
+ 0, /* U+01121a: */
+ 0, /* U+01121b: */
+ 0, /* U+01121c: */
+ 0, /* U+01121d: */
+ 0, /* U+01121e: */
+ 0, /* U+01121f: */
+ 0, /* U+011220: */
+ 0, /* U+011221: */
+ 0, /* U+011222: */
+ 0, /* U+011223: */
+ 0, /* U+011224: */
+ 0, /* U+011225: */
+ 0, /* U+011226: */
+ 0, /* U+011227: */
+ 0, /* U+011228: */
+ 0, /* U+011229: */
+ 0, /* U+01122a: */
+ 0, /* U+01122b: */
+ 0, /* U+01122c: */
+ 0, /* U+01122d: */
+ 0, /* U+01122e: */
+ 0, /* U+01122f: */
+ 0, /* U+011230: */
+ 0, /* U+011231: */
+ 0, /* U+011232: */
+ 0, /* U+011233: */
+ 0, /* U+011234: */
+ 0, /* U+011235: */
+ 0, /* U+011236: */
+ 0, /* U+011237: */
+ 0, /* U+011238: */
+ 0, /* U+011239: */
+ 0, /* U+01123a: */
+ 0, /* U+01123b: */
+ 0, /* U+01123c: */
+ 0, /* U+01123d: */
+ 0, /* U+01123e: */
+ 0, /* U+01123f: */
+ 0, /* U+011240: */
+ 0, /* U+011241: */
+ 0, /* U+011242: */
+ 0, /* U+011243: */
+ 0, /* U+011244: */
+ 0, /* U+011245: */
+ 0, /* U+011246: */
+ 0, /* U+011247: */
+ 0, /* U+011248: */
+ 0, /* U+011249: */
+ 0, /* U+01124a: */
+ 0, /* U+01124b: */
+ 0, /* U+01124c: */
+ 0, /* U+01124d: */
+ 0, /* U+01124e: */
+ 0, /* U+01124f: */
+ 0, /* U+011250: */
+ 0, /* U+011251: */
+ 0, /* U+011252: */
+ 0, /* U+011253: */
+ 0, /* U+011254: */
+ 0, /* U+011255: */
+ 0, /* U+011256: */
+ 0, /* U+011257: */
+ 0, /* U+011258: */
+ 0, /* U+011259: */
+ 0, /* U+01125a: */
+ 0, /* U+01125b: */
+ 0, /* U+01125c: */
+ 0, /* U+01125d: */
+ 0, /* U+01125e: */
+ 0, /* U+01125f: */
+ 0, /* U+011260: */
+ 0, /* U+011261: */
+ 0, /* U+011262: */
+ 0, /* U+011263: */
+ 0, /* U+011264: */
+ 0, /* U+011265: */
+ 0, /* U+011266: */
+ 0, /* U+011267: */
+ 0, /* U+011268: */
+ 0, /* U+011269: */
+ 0, /* U+01126a: */
+ 0, /* U+01126b: */
+ 0, /* U+01126c: */
+ 0, /* U+01126d: */
+ 0, /* U+01126e: */
+ 0, /* U+01126f: */
+ 0, /* U+011270: */
+ 0, /* U+011271: */
+ 0, /* U+011272: */
+ 0, /* U+011273: */
+ 0, /* U+011274: */
+ 0, /* U+011275: */
+ 0, /* U+011276: */
+ 0, /* U+011277: */
+ 0, /* U+011278: */
+ 0, /* U+011279: */
+ 0, /* U+01127a: */
+ 0, /* U+01127b: */
+ 0, /* U+01127c: */
+ 0, /* U+01127d: */
+ 0, /* U+01127e: */
+ 0, /* U+01127f: */
+ 0, /* U+011280: */
+ 0, /* U+011281: */
+ 0, /* U+011282: */
+ 0, /* U+011283: */
+ 0, /* U+011284: */
+ 0, /* U+011285: */
+ 0, /* U+011286: */
+ 0, /* U+011287: */
+ 0, /* U+011288: */
+ 0, /* U+011289: */
+ 0, /* U+01128a: */
+ 0, /* U+01128b: */
+ 0, /* U+01128c: */
+ 0, /* U+01128d: */
+ 0, /* U+01128e: */
+ 0, /* U+01128f: */
+ 0, /* U+011290: */
+ 0, /* U+011291: */
+ 0, /* U+011292: */
+ 0, /* U+011293: */
+ 0, /* U+011294: */
+ 0, /* U+011295: */
+ 0, /* U+011296: */
+ 0, /* U+011297: */
+ 0, /* U+011298: */
+ 0, /* U+011299: */
+ 0, /* U+01129a: */
+ 0, /* U+01129b: */
+ 0, /* U+01129c: */
+ 0, /* U+01129d: */
+ 0, /* U+01129e: */
+ 0, /* U+01129f: */
+ 0, /* U+0112a0: */
+ 0, /* U+0112a1: */
+ 0, /* U+0112a2: */
+ 0, /* U+0112a3: */
+ 0, /* U+0112a4: */
+ 0, /* U+0112a5: */
+ 0, /* U+0112a6: */
+ 0, /* U+0112a7: */
+ 0, /* U+0112a8: */
+ 0, /* U+0112a9: */
+ 0, /* U+0112aa: */
+ 0, /* U+0112ab: */
+ 0, /* U+0112ac: */
+ 0, /* U+0112ad: */
+ 0, /* U+0112ae: */
+ 0, /* U+0112af: */
+ 0, /* U+0112b0: */
+ 0, /* U+0112b1: */
+ 0, /* U+0112b2: */
+ 0, /* U+0112b3: */
+ 0, /* U+0112b4: */
+ 0, /* U+0112b5: */
+ 0, /* U+0112b6: */
+ 0, /* U+0112b7: */
+ 0, /* U+0112b8: */
+ 0, /* U+0112b9: */
+ 0, /* U+0112ba: */
+ 0, /* U+0112bb: */
+ 0, /* U+0112bc: */
+ 0, /* U+0112bd: */
+ 0, /* U+0112be: */
+ 0, /* U+0112bf: */
+ 0, /* U+0112c0: */
+ 0, /* U+0112c1: */
+ 0, /* U+0112c2: */
+ 0, /* U+0112c3: */
+ 0, /* U+0112c4: */
+ 0, /* U+0112c5: */
+ 0, /* U+0112c6: */
+ 0, /* U+0112c7: */
+ 0, /* U+0112c8: */
+ 0, /* U+0112c9: */
+ 0, /* U+0112ca: */
+ 0, /* U+0112cb: */
+ 0, /* U+0112cc: */
+ 0, /* U+0112cd: */
+ 0, /* U+0112ce: */
+ 0, /* U+0112cf: */
+ 0, /* U+0112d0: */
+ 0, /* U+0112d1: */
+ 0, /* U+0112d2: */
+ 0, /* U+0112d3: */
+ 0, /* U+0112d4: */
+ 0, /* U+0112d5: */
+ 0, /* U+0112d6: */
+ 0, /* U+0112d7: */
+ 0, /* U+0112d8: */
+ 0, /* U+0112d9: */
+ 0, /* U+0112da: */
+ 0, /* U+0112db: */
+ 0, /* U+0112dc: */
+ 0, /* U+0112dd: */
+ 0, /* U+0112de: */
+ 0, /* U+0112df: */
+ 0, /* U+0112e0: */
+ 0, /* U+0112e1: */
+ 0, /* U+0112e2: */
+ 0, /* U+0112e3: */
+ 0, /* U+0112e4: */
+ 0, /* U+0112e5: */
+ 0, /* U+0112e6: */
+ 0, /* U+0112e7: */
+ 0, /* U+0112e8: */
+ 0, /* U+0112e9: */
+ 0, /* U+0112ea: */
+ 0, /* U+0112eb: */
+ 0, /* U+0112ec: */
+ 0, /* U+0112ed: */
+ 0, /* U+0112ee: */
+ 0, /* U+0112ef: */
+ 0, /* U+0112f0: */
+ 0, /* U+0112f1: */
+ 0, /* U+0112f2: */
+ 0, /* U+0112f3: */
+ 0, /* U+0112f4: */
+ 0, /* U+0112f5: */
+ 0, /* U+0112f6: */
+ 0, /* U+0112f7: */
+ 0, /* U+0112f8: */
+ 0, /* U+0112f9: */
+ 0, /* U+0112fa: */
+ 0, /* U+0112fb: */
+ 0, /* U+0112fc: */
+ 0, /* U+0112fd: */
+ 0, /* U+0112fe: */
+ 0, /* U+0112ff: */
+ 0, /* U+011300: */
+ 0, /* U+011301: */
+ 0, /* U+011302: */
+ 0, /* U+011303: */
+ 0, /* U+011304: */
+ 0, /* U+011305: */
+ 0, /* U+011306: */
+ 0, /* U+011307: */
+ 0, /* U+011308: */
+ 0, /* U+011309: */
+ 0, /* U+01130a: */
+ 0, /* U+01130b: */
+ 0, /* U+01130c: */
+ 0, /* U+01130d: */
+ 0, /* U+01130e: */
+ 0, /* U+01130f: */
+ 0, /* U+011310: */
+ 0, /* U+011311: */
+ 0, /* U+011312: */
+ 0, /* U+011313: */
+ 0, /* U+011314: */
+ 0, /* U+011315: */
+ 0, /* U+011316: */
+ 0, /* U+011317: */
+ 0, /* U+011318: */
+ 0, /* U+011319: */
+ 0, /* U+01131a: */
+ 0, /* U+01131b: */
+ 0, /* U+01131c: */
+ 0, /* U+01131d: */
+ 0, /* U+01131e: */
+ 0, /* U+01131f: */
+ 0, /* U+011320: */
+ 0, /* U+011321: */
+ 0, /* U+011322: */
+ 0, /* U+011323: */
+ 0, /* U+011324: */
+ 0, /* U+011325: */
+ 0, /* U+011326: */
+ 0, /* U+011327: */
+ 0, /* U+011328: */
+ 0, /* U+011329: */
+ 0, /* U+01132a: */
+ 0, /* U+01132b: */
+ 0, /* U+01132c: */
+ 0, /* U+01132d: */
+ 0, /* U+01132e: */
+ 0, /* U+01132f: */
+ 0, /* U+011330: */
+ 0, /* U+011331: */
+ 0, /* U+011332: */
+ 0, /* U+011333: */
+ 0, /* U+011334: */
+ 0, /* U+011335: */
+ 0, /* U+011336: */
+ 0, /* U+011337: */
+ 0, /* U+011338: */
+ 0, /* U+011339: */
+ 0, /* U+01133a: */
+ 0, /* U+01133b: */
+ 0, /* U+01133c: */
+ 0, /* U+01133d: */
+ 0, /* U+01133e: */
+ 0, /* U+01133f: */
+ 0, /* U+011340: */
+ 0, /* U+011341: */
+ 0, /* U+011342: */
+ 0, /* U+011343: */
+ 0, /* U+011344: */
+ 0, /* U+011345: */
+ 0, /* U+011346: */
+ 0, /* U+011347: */
+ 0, /* U+011348: */
+ 0, /* U+011349: */
+ 0, /* U+01134a: */
+ 0, /* U+01134b: */
+ 0, /* U+01134c: */
+ 0, /* U+01134d: */
+ 0, /* U+01134e: */
+ 0, /* U+01134f: */
+ 0, /* U+011350: */
+ 0, /* U+011351: */
+ 0, /* U+011352: */
+ 0, /* U+011353: */
+ 0, /* U+011354: */
+ 0, /* U+011355: */
+ 0, /* U+011356: */
+ 0, /* U+011357: */
+ 0, /* U+011358: */
+ 0, /* U+011359: */
+ 0, /* U+01135a: */
+ 0, /* U+01135b: */
+ 0, /* U+01135c: */
+ 0, /* U+01135d: */
+ 0, /* U+01135e: */
+ 0, /* U+01135f: */
+ 0, /* U+011360: */
+ 0, /* U+011361: */
+ 0, /* U+011362: */
+ 0, /* U+011363: */
+ 0, /* U+011364: */
+ 0, /* U+011365: */
+ 0, /* U+011366: */
+ 0, /* U+011367: */
+ 0, /* U+011368: */
+ 0, /* U+011369: */
+ 0, /* U+01136a: */
+ 0, /* U+01136b: */
+ 0, /* U+01136c: */
+ 0, /* U+01136d: */
+ 0, /* U+01136e: */
+ 0, /* U+01136f: */
+ 0, /* U+011370: */
+ 0, /* U+011371: */
+ 0, /* U+011372: */
+ 0, /* U+011373: */
+ 0, /* U+011374: */
+ 0, /* U+011375: */
+ 0, /* U+011376: */
+ 0, /* U+011377: */
+ 0, /* U+011378: */
+ 0, /* U+011379: */
+ 0, /* U+01137a: */
+ 0, /* U+01137b: */
+ 0, /* U+01137c: */
+ 0, /* U+01137d: */
+ 0, /* U+01137e: */
+ 0, /* U+01137f: */
+ 0, /* U+011380: */
+ 0, /* U+011381: */
+ 0, /* U+011382: */
+ 0, /* U+011383: */
+ 0, /* U+011384: */
+ 0, /* U+011385: */
+ 0, /* U+011386: */
+ 0, /* U+011387: */
+ 0, /* U+011388: */
+ 0, /* U+011389: */
+ 0, /* U+01138a: */
+ 0, /* U+01138b: */
+ 0, /* U+01138c: */
+ 0, /* U+01138d: */
+ 0, /* U+01138e: */
+ 0, /* U+01138f: */
+ 0, /* U+011390: */
+ 0, /* U+011391: */
+ 0, /* U+011392: */
+ 0, /* U+011393: */
+ 0, /* U+011394: */
+ 0, /* U+011395: */
+ 0, /* U+011396: */
+ 0, /* U+011397: */
+ 0, /* U+011398: */
+ 0, /* U+011399: */
+ 0, /* U+01139a: */
+ 0, /* U+01139b: */
+ 0, /* U+01139c: */
+ 0, /* U+01139d: */
+ 0, /* U+01139e: */
+ 0, /* U+01139f: */
+ 0, /* U+0113a0: */
+ 0, /* U+0113a1: */
+ 0, /* U+0113a2: */
+ 0, /* U+0113a3: */
+ 0, /* U+0113a4: */
+ 0, /* U+0113a5: */
+ 0, /* U+0113a6: */
+ 0, /* U+0113a7: */
+ 0, /* U+0113a8: */
+ 0, /* U+0113a9: */
+ 0, /* U+0113aa: */
+ 0, /* U+0113ab: */
+ 0, /* U+0113ac: */
+ 0, /* U+0113ad: */
+ 0, /* U+0113ae: */
+ 0, /* U+0113af: */
+ 0, /* U+0113b0: */
+ 0, /* U+0113b1: */
+ 0, /* U+0113b2: */
+ 0, /* U+0113b3: */
+ 0, /* U+0113b4: */
+ 0, /* U+0113b5: */
+ 0, /* U+0113b6: */
+ 0, /* U+0113b7: */
+ 0, /* U+0113b8: */
+ 0, /* U+0113b9: */
+ 0, /* U+0113ba: */
+ 0, /* U+0113bb: */
+ 0, /* U+0113bc: */
+ 0, /* U+0113bd: */
+ 0, /* U+0113be: */
+ 0, /* U+0113bf: */
+ 0, /* U+0113c0: */
+ 0, /* U+0113c1: */
+ 0, /* U+0113c2: */
+ 0, /* U+0113c3: */
+ 0, /* U+0113c4: */
+ 0, /* U+0113c5: */
+ 0, /* U+0113c6: */
+ 0, /* U+0113c7: */
+ 0, /* U+0113c8: */
+ 0, /* U+0113c9: */
+ 0, /* U+0113ca: */
+ 0, /* U+0113cb: */
+ 0, /* U+0113cc: */
+ 0, /* U+0113cd: */
+ 0, /* U+0113ce: */
+ 0, /* U+0113cf: */
+ 0, /* U+0113d0: */
+ 0, /* U+0113d1: */
+ 0, /* U+0113d2: */
+ 0, /* U+0113d3: */
+ 0, /* U+0113d4: */
+ 0, /* U+0113d5: */
+ 0, /* U+0113d6: */
+ 0, /* U+0113d7: */
+ 0, /* U+0113d8: */
+ 0, /* U+0113d9: */
+ 0, /* U+0113da: */
+ 0, /* U+0113db: */
+ 0, /* U+0113dc: */
+ 0, /* U+0113dd: */
+ 0, /* U+0113de: */
+ 0, /* U+0113df: */
+ 0, /* U+0113e0: */
+ 0, /* U+0113e1: */
+ 0, /* U+0113e2: */
+ 0, /* U+0113e3: */
+ 0, /* U+0113e4: */
+ 0, /* U+0113e5: */
+ 0, /* U+0113e6: */
+ 0, /* U+0113e7: */
+ 0, /* U+0113e8: */
+ 0, /* U+0113e9: */
+ 0, /* U+0113ea: */
+ 0, /* U+0113eb: */
+ 0, /* U+0113ec: */
+ 0, /* U+0113ed: */
+ 0, /* U+0113ee: */
+ 0, /* U+0113ef: */
+ 0, /* U+0113f0: */
+ 0, /* U+0113f1: */
+ 0, /* U+0113f2: */
+ 0, /* U+0113f3: */
+ 0, /* U+0113f4: */
+ 0, /* U+0113f5: */
+ 0, /* U+0113f6: */
+ 0, /* U+0113f7: */
+ 0, /* U+0113f8: */
+ 0, /* U+0113f9: */
+ 0, /* U+0113fa: */
+ 0, /* U+0113fb: */
+ 0, /* U+0113fc: */
+ 0, /* U+0113fd: */
+ 0, /* U+0113fe: */
+ 0, /* U+0113ff: */
+ 0, /* U+011400: */
+ 0, /* U+011401: */
+ 0, /* U+011402: */
+ 0, /* U+011403: */
+ 0, /* U+011404: */
+ 0, /* U+011405: */
+ 0, /* U+011406: */
+ 0, /* U+011407: */
+ 0, /* U+011408: */
+ 0, /* U+011409: */
+ 0, /* U+01140a: */
+ 0, /* U+01140b: */
+ 0, /* U+01140c: */
+ 0, /* U+01140d: */
+ 0, /* U+01140e: */
+ 0, /* U+01140f: */
+ 0, /* U+011410: */
+ 0, /* U+011411: */
+ 0, /* U+011412: */
+ 0, /* U+011413: */
+ 0, /* U+011414: */
+ 0, /* U+011415: */
+ 0, /* U+011416: */
+ 0, /* U+011417: */
+ 0, /* U+011418: */
+ 0, /* U+011419: */
+ 0, /* U+01141a: */
+ 0, /* U+01141b: */
+ 0, /* U+01141c: */
+ 0, /* U+01141d: */
+ 0, /* U+01141e: */
+ 0, /* U+01141f: */
+ 0, /* U+011420: */
+ 0, /* U+011421: */
+ 0, /* U+011422: */
+ 0, /* U+011423: */
+ 0, /* U+011424: */
+ 0, /* U+011425: */
+ 0, /* U+011426: */
+ 0, /* U+011427: */
+ 0, /* U+011428: */
+ 0, /* U+011429: */
+ 0, /* U+01142a: */
+ 0, /* U+01142b: */
+ 0, /* U+01142c: */
+ 0, /* U+01142d: */
+ 0, /* U+01142e: */
+ 0, /* U+01142f: */
+ 0, /* U+011430: */
+ 0, /* U+011431: */
+ 0, /* U+011432: */
+ 0, /* U+011433: */
+ 0, /* U+011434: */
+ 0, /* U+011435: */
+ 0, /* U+011436: */
+ 0, /* U+011437: */
+ 0, /* U+011438: */
+ 0, /* U+011439: */
+ 0, /* U+01143a: */
+ 0, /* U+01143b: */
+ 0, /* U+01143c: */
+ 0, /* U+01143d: */
+ 0, /* U+01143e: */
+ 0, /* U+01143f: */
+ 0, /* U+011440: */
+ 0, /* U+011441: */
+ 0, /* U+011442: */
+ 0, /* U+011443: */
+ 0, /* U+011444: */
+ 0, /* U+011445: */
+ 0, /* U+011446: */
+ 0, /* U+011447: */
+ 0, /* U+011448: */
+ 0, /* U+011449: */
+ 0, /* U+01144a: */
+ 0, /* U+01144b: */
+ 0, /* U+01144c: */
+ 0, /* U+01144d: */
+ 0, /* U+01144e: */
+ 0, /* U+01144f: */
+ 0, /* U+011450: */
+ 0, /* U+011451: */
+ 0, /* U+011452: */
+ 0, /* U+011453: */
+ 0, /* U+011454: */
+ 0, /* U+011455: */
+ 0, /* U+011456: */
+ 0, /* U+011457: */
+ 0, /* U+011458: */
+ 0, /* U+011459: */
+ 0, /* U+01145a: */
+ 0, /* U+01145b: */
+ 0, /* U+01145c: */
+ 0, /* U+01145d: */
+ 0, /* U+01145e: */
+ 0, /* U+01145f: */
+ 0, /* U+011460: */
+ 0, /* U+011461: */
+ 0, /* U+011462: */
+ 0, /* U+011463: */
+ 0, /* U+011464: */
+ 0, /* U+011465: */
+ 0, /* U+011466: */
+ 0, /* U+011467: */
+ 0, /* U+011468: */
+ 0, /* U+011469: */
+ 0, /* U+01146a: */
+ 0, /* U+01146b: */
+ 0, /* U+01146c: */
+ 0, /* U+01146d: */
+ 0, /* U+01146e: */
+ 0, /* U+01146f: */
+ 0, /* U+011470: */
+ 0, /* U+011471: */
+ 0, /* U+011472: */
+ 0, /* U+011473: */
+ 0, /* U+011474: */
+ 0, /* U+011475: */
+ 0, /* U+011476: */
+ 0, /* U+011477: */
+ 0, /* U+011478: */
+ 0, /* U+011479: */
+ 0, /* U+01147a: */
+ 0, /* U+01147b: */
+ 0, /* U+01147c: */
+ 0, /* U+01147d: */
+ 0, /* U+01147e: */
+ 0, /* U+01147f: */
+ 0, /* U+011480: */
+ 0, /* U+011481: */
+ 0, /* U+011482: */
+ 0, /* U+011483: */
+ 0, /* U+011484: */
+ 0, /* U+011485: */
+ 0, /* U+011486: */
+ 0, /* U+011487: */
+ 0, /* U+011488: */
+ 0, /* U+011489: */
+ 0, /* U+01148a: */
+ 0, /* U+01148b: */
+ 0, /* U+01148c: */
+ 0, /* U+01148d: */
+ 0, /* U+01148e: */
+ 0, /* U+01148f: */
+ 0, /* U+011490: */
+ 0, /* U+011491: */
+ 0, /* U+011492: */
+ 0, /* U+011493: */
+ 0, /* U+011494: */
+ 0, /* U+011495: */
+ 0, /* U+011496: */
+ 0, /* U+011497: */
+ 0, /* U+011498: */
+ 0, /* U+011499: */
+ 0, /* U+01149a: */
+ 0, /* U+01149b: */
+ 0, /* U+01149c: */
+ 0, /* U+01149d: */
+ 0, /* U+01149e: */
+ 0, /* U+01149f: */
+ 0, /* U+0114a0: */
+ 0, /* U+0114a1: */
+ 0, /* U+0114a2: */
+ 0, /* U+0114a3: */
+ 0, /* U+0114a4: */
+ 0, /* U+0114a5: */
+ 0, /* U+0114a6: */
+ 0, /* U+0114a7: */
+ 0, /* U+0114a8: */
+ 0, /* U+0114a9: */
+ 0, /* U+0114aa: */
+ 0, /* U+0114ab: */
+ 0, /* U+0114ac: */
+ 0, /* U+0114ad: */
+ 0, /* U+0114ae: */
+ 0, /* U+0114af: */
+ 0, /* U+0114b0: */
+ 0, /* U+0114b1: */
+ 0, /* U+0114b2: */
+ 0, /* U+0114b3: */
+ 0, /* U+0114b4: */
+ 0, /* U+0114b5: */
+ 0, /* U+0114b6: */
+ 0, /* U+0114b7: */
+ 0, /* U+0114b8: */
+ 0, /* U+0114b9: */
+ 0, /* U+0114ba: */
+ 0, /* U+0114bb: */
+ 0, /* U+0114bc: */
+ 0, /* U+0114bd: */
+ 0, /* U+0114be: */
+ 0, /* U+0114bf: */
+ 0, /* U+0114c0: */
+ 0, /* U+0114c1: */
+ 0, /* U+0114c2: */
+ 0, /* U+0114c3: */
+ 0, /* U+0114c4: */
+ 0, /* U+0114c5: */
+ 0, /* U+0114c6: */
+ 0, /* U+0114c7: */
+ 0, /* U+0114c8: */
+ 0, /* U+0114c9: */
+ 0, /* U+0114ca: */
+ 0, /* U+0114cb: */
+ 0, /* U+0114cc: */
+ 0, /* U+0114cd: */
+ 0, /* U+0114ce: */
+ 0, /* U+0114cf: */
+ 0, /* U+0114d0: */
+ 0, /* U+0114d1: */
+ 0, /* U+0114d2: */
+ 0, /* U+0114d3: */
+ 0, /* U+0114d4: */
+ 0, /* U+0114d5: */
+ 0, /* U+0114d6: */
+ 0, /* U+0114d7: */
+ 0, /* U+0114d8: */
+ 0, /* U+0114d9: */
+ 0, /* U+0114da: */
+ 0, /* U+0114db: */
+ 0, /* U+0114dc: */
+ 0, /* U+0114dd: */
+ 0, /* U+0114de: */
+ 0, /* U+0114df: */
+ 0, /* U+0114e0: */
+ 0, /* U+0114e1: */
+ 0, /* U+0114e2: */
+ 0, /* U+0114e3: */
+ 0, /* U+0114e4: */
+ 0, /* U+0114e5: */
+ 0, /* U+0114e6: */
+ 0, /* U+0114e7: */
+ 0, /* U+0114e8: */
+ 0, /* U+0114e9: */
+ 0, /* U+0114ea: */
+ 0, /* U+0114eb: */
+ 0, /* U+0114ec: */
+ 0, /* U+0114ed: */
+ 0, /* U+0114ee: */
+ 0, /* U+0114ef: */
+ 0, /* U+0114f0: */
+ 0, /* U+0114f1: */
+ 0, /* U+0114f2: */
+ 0, /* U+0114f3: */
+ 0, /* U+0114f4: */
+ 0, /* U+0114f5: */
+ 0, /* U+0114f6: */
+ 0, /* U+0114f7: */
+ 0, /* U+0114f8: */
+ 0, /* U+0114f9: */
+ 0, /* U+0114fa: */
+ 0, /* U+0114fb: */
+ 0, /* U+0114fc: */
+ 0, /* U+0114fd: */
+ 0, /* U+0114fe: */
+ 0, /* U+0114ff: */
+ 0, /* U+011500: */
+ 0, /* U+011501: */
+ 0, /* U+011502: */
+ 0, /* U+011503: */
+ 0, /* U+011504: */
+ 0, /* U+011505: */
+ 0, /* U+011506: */
+ 0, /* U+011507: */
+ 0, /* U+011508: */
+ 0, /* U+011509: */
+ 0, /* U+01150a: */
+ 0, /* U+01150b: */
+ 0, /* U+01150c: */
+ 0, /* U+01150d: */
+ 0, /* U+01150e: */
+ 0, /* U+01150f: */
+ 0, /* U+011510: */
+ 0, /* U+011511: */
+ 0, /* U+011512: */
+ 0, /* U+011513: */
+ 0, /* U+011514: */
+ 0, /* U+011515: */
+ 0, /* U+011516: */
+ 0, /* U+011517: */
+ 0, /* U+011518: */
+ 0, /* U+011519: */
+ 0, /* U+01151a: */
+ 0, /* U+01151b: */
+ 0, /* U+01151c: */
+ 0, /* U+01151d: */
+ 0, /* U+01151e: */
+ 0, /* U+01151f: */
+ 0, /* U+011520: */
+ 0, /* U+011521: */
+ 0, /* U+011522: */
+ 0, /* U+011523: */
+ 0, /* U+011524: */
+ 0, /* U+011525: */
+ 0, /* U+011526: */
+ 0, /* U+011527: */
+ 0, /* U+011528: */
+ 0, /* U+011529: */
+ 0, /* U+01152a: */
+ 0, /* U+01152b: */
+ 0, /* U+01152c: */
+ 0, /* U+01152d: */
+ 0, /* U+01152e: */
+ 0, /* U+01152f: */
+ 0, /* U+011530: */
+ 0, /* U+011531: */
+ 0, /* U+011532: */
+ 0, /* U+011533: */
+ 0, /* U+011534: */
+ 0, /* U+011535: */
+ 0, /* U+011536: */
+ 0, /* U+011537: */
+ 0, /* U+011538: */
+ 0, /* U+011539: */
+ 0, /* U+01153a: */
+ 0, /* U+01153b: */
+ 0, /* U+01153c: */
+ 0, /* U+01153d: */
+ 0, /* U+01153e: */
+ 0, /* U+01153f: */
+ 0, /* U+011540: */
+ 0, /* U+011541: */
+ 0, /* U+011542: */
+ 0, /* U+011543: */
+ 0, /* U+011544: */
+ 0, /* U+011545: */
+ 0, /* U+011546: */
+ 0, /* U+011547: */
+ 0, /* U+011548: */
+ 0, /* U+011549: */
+ 0, /* U+01154a: */
+ 0, /* U+01154b: */
+ 0, /* U+01154c: */
+ 0, /* U+01154d: */
+ 0, /* U+01154e: */
+ 0, /* U+01154f: */
+ 0, /* U+011550: */
+ 0, /* U+011551: */
+ 0, /* U+011552: */
+ 0, /* U+011553: */
+ 0, /* U+011554: */
+ 0, /* U+011555: */
+ 0, /* U+011556: */
+ 0, /* U+011557: */
+ 0, /* U+011558: */
+ 0, /* U+011559: */
+ 0, /* U+01155a: */
+ 0, /* U+01155b: */
+ 0, /* U+01155c: */
+ 0, /* U+01155d: */
+ 0, /* U+01155e: */
+ 0, /* U+01155f: */
+ 0, /* U+011560: */
+ 0, /* U+011561: */
+ 0, /* U+011562: */
+ 0, /* U+011563: */
+ 0, /* U+011564: */
+ 0, /* U+011565: */
+ 0, /* U+011566: */
+ 0, /* U+011567: */
+ 0, /* U+011568: */
+ 0, /* U+011569: */
+ 0, /* U+01156a: */
+ 0, /* U+01156b: */
+ 0, /* U+01156c: */
+ 0, /* U+01156d: */
+ 0, /* U+01156e: */
+ 0, /* U+01156f: */
+ 0, /* U+011570: */
+ 0, /* U+011571: */
+ 0, /* U+011572: */
+ 0, /* U+011573: */
+ 0, /* U+011574: */
+ 0, /* U+011575: */
+ 0, /* U+011576: */
+ 0, /* U+011577: */
+ 0, /* U+011578: */
+ 0, /* U+011579: */
+ 0, /* U+01157a: */
+ 0, /* U+01157b: */
+ 0, /* U+01157c: */
+ 0, /* U+01157d: */
+ 0, /* U+01157e: */
+ 0, /* U+01157f: */
+ 0, /* U+011580: */
+ 0, /* U+011581: */
+ 0, /* U+011582: */
+ 0, /* U+011583: */
+ 0, /* U+011584: */
+ 0, /* U+011585: */
+ 0, /* U+011586: */
+ 0, /* U+011587: */
+ 0, /* U+011588: */
+ 0, /* U+011589: */
+ 0, /* U+01158a: */
+ 0, /* U+01158b: */
+ 0, /* U+01158c: */
+ 0, /* U+01158d: */
+ 0, /* U+01158e: */
+ 0, /* U+01158f: */
+ 0, /* U+011590: */
+ 0, /* U+011591: */
+ 0, /* U+011592: */
+ 0, /* U+011593: */
+ 0, /* U+011594: */
+ 0, /* U+011595: */
+ 0, /* U+011596: */
+ 0, /* U+011597: */
+ 0, /* U+011598: */
+ 0, /* U+011599: */
+ 0, /* U+01159a: */
+ 0, /* U+01159b: */
+ 0, /* U+01159c: */
+ 0, /* U+01159d: */
+ 0, /* U+01159e: */
+ 0, /* U+01159f: */
+ 0, /* U+0115a0: */
+ 0, /* U+0115a1: */
+ 0, /* U+0115a2: */
+ 0, /* U+0115a3: */
+ 0, /* U+0115a4: */
+ 0, /* U+0115a5: */
+ 0, /* U+0115a6: */
+ 0, /* U+0115a7: */
+ 0, /* U+0115a8: */
+ 0, /* U+0115a9: */
+ 0, /* U+0115aa: */
+ 0, /* U+0115ab: */
+ 0, /* U+0115ac: */
+ 0, /* U+0115ad: */
+ 0, /* U+0115ae: */
+ 0, /* U+0115af: */
+ 0, /* U+0115b0: */
+ 0, /* U+0115b1: */
+ 0, /* U+0115b2: */
+ 0, /* U+0115b3: */
+ 0, /* U+0115b4: */
+ 0, /* U+0115b5: */
+ 0, /* U+0115b6: */
+ 0, /* U+0115b7: */
+ 0, /* U+0115b8: */
+ 0, /* U+0115b9: */
+ 0, /* U+0115ba: */
+ 0, /* U+0115bb: */
+ 0, /* U+0115bc: */
+ 0, /* U+0115bd: */
+ 0, /* U+0115be: */
+ 0, /* U+0115bf: */
+ 0, /* U+0115c0: */
+ 0, /* U+0115c1: */
+ 0, /* U+0115c2: */
+ 0, /* U+0115c3: */
+ 0, /* U+0115c4: */
+ 0, /* U+0115c5: */
+ 0, /* U+0115c6: */
+ 0, /* U+0115c7: */
+ 0, /* U+0115c8: */
+ 0, /* U+0115c9: */
+ 0, /* U+0115ca: */
+ 0, /* U+0115cb: */
+ 0, /* U+0115cc: */
+ 0, /* U+0115cd: */
+ 0, /* U+0115ce: */
+ 0, /* U+0115cf: */
+ 0, /* U+0115d0: */
+ 0, /* U+0115d1: */
+ 0, /* U+0115d2: */
+ 0, /* U+0115d3: */
+ 0, /* U+0115d4: */
+ 0, /* U+0115d5: */
+ 0, /* U+0115d6: */
+ 0, /* U+0115d7: */
+ 0, /* U+0115d8: */
+ 0, /* U+0115d9: */
+ 0, /* U+0115da: */
+ 0, /* U+0115db: */
+ 0, /* U+0115dc: */
+ 0, /* U+0115dd: */
+ 0, /* U+0115de: */
+ 0, /* U+0115df: */
+ 0, /* U+0115e0: */
+ 0, /* U+0115e1: */
+ 0, /* U+0115e2: */
+ 0, /* U+0115e3: */
+ 0, /* U+0115e4: */
+ 0, /* U+0115e5: */
+ 0, /* U+0115e6: */
+ 0, /* U+0115e7: */
+ 0, /* U+0115e8: */
+ 0, /* U+0115e9: */
+ 0, /* U+0115ea: */
+ 0, /* U+0115eb: */
+ 0, /* U+0115ec: */
+ 0, /* U+0115ed: */
+ 0, /* U+0115ee: */
+ 0, /* U+0115ef: */
+ 0, /* U+0115f0: */
+ 0, /* U+0115f1: */
+ 0, /* U+0115f2: */
+ 0, /* U+0115f3: */
+ 0, /* U+0115f4: */
+ 0, /* U+0115f5: */
+ 0, /* U+0115f6: */
+ 0, /* U+0115f7: */
+ 0, /* U+0115f8: */
+ 0, /* U+0115f9: */
+ 0, /* U+0115fa: */
+ 0, /* U+0115fb: */
+ 0, /* U+0115fc: */
+ 0, /* U+0115fd: */
+ 0, /* U+0115fe: */
+ 0, /* U+0115ff: */
+ 0, /* U+011600: */
+ 0, /* U+011601: */
+ 0, /* U+011602: */
+ 0, /* U+011603: */
+ 0, /* U+011604: */
+ 0, /* U+011605: */
+ 0, /* U+011606: */
+ 0, /* U+011607: */
+ 0, /* U+011608: */
+ 0, /* U+011609: */
+ 0, /* U+01160a: */
+ 0, /* U+01160b: */
+ 0, /* U+01160c: */
+ 0, /* U+01160d: */
+ 0, /* U+01160e: */
+ 0, /* U+01160f: */
+ 0, /* U+011610: */
+ 0, /* U+011611: */
+ 0, /* U+011612: */
+ 0, /* U+011613: */
+ 0, /* U+011614: */
+ 0, /* U+011615: */
+ 0, /* U+011616: */
+ 0, /* U+011617: */
+ 0, /* U+011618: */
+ 0, /* U+011619: */
+ 0, /* U+01161a: */
+ 0, /* U+01161b: */
+ 0, /* U+01161c: */
+ 0, /* U+01161d: */
+ 0, /* U+01161e: */
+ 0, /* U+01161f: */
+ 0, /* U+011620: */
+ 0, /* U+011621: */
+ 0, /* U+011622: */
+ 0, /* U+011623: */
+ 0, /* U+011624: */
+ 0, /* U+011625: */
+ 0, /* U+011626: */
+ 0, /* U+011627: */
+ 0, /* U+011628: */
+ 0, /* U+011629: */
+ 0, /* U+01162a: */
+ 0, /* U+01162b: */
+ 0, /* U+01162c: */
+ 0, /* U+01162d: */
+ 0, /* U+01162e: */
+ 0, /* U+01162f: */
+ 0, /* U+011630: */
+ 0, /* U+011631: */
+ 0, /* U+011632: */
+ 0, /* U+011633: */
+ 0, /* U+011634: */
+ 0, /* U+011635: */
+ 0, /* U+011636: */
+ 0, /* U+011637: */
+ 0, /* U+011638: */
+ 0, /* U+011639: */
+ 0, /* U+01163a: */
+ 0, /* U+01163b: */
+ 0, /* U+01163c: */
+ 0, /* U+01163d: */
+ 0, /* U+01163e: */
+ 0, /* U+01163f: */
+ 0, /* U+011640: */
+ 0, /* U+011641: */
+ 0, /* U+011642: */
+ 0, /* U+011643: */
+ 0, /* U+011644: */
+ 0, /* U+011645: */
+ 0, /* U+011646: */
+ 0, /* U+011647: */
+ 0, /* U+011648: */
+ 0, /* U+011649: */
+ 0, /* U+01164a: */
+ 0, /* U+01164b: */
+ 0, /* U+01164c: */
+ 0, /* U+01164d: */
+ 0, /* U+01164e: */
+ 0, /* U+01164f: */
+ 0, /* U+011650: */
+ 0, /* U+011651: */
+ 0, /* U+011652: */
+ 0, /* U+011653: */
+ 0, /* U+011654: */
+ 0, /* U+011655: */
+ 0, /* U+011656: */
+ 0, /* U+011657: */
+ 0, /* U+011658: */
+ 0, /* U+011659: */
+ 0, /* U+01165a: */
+ 0, /* U+01165b: */
+ 0, /* U+01165c: */
+ 0, /* U+01165d: */
+ 0, /* U+01165e: */
+ 0, /* U+01165f: */
+ 0, /* U+011660: */
+ 0, /* U+011661: */
+ 0, /* U+011662: */
+ 0, /* U+011663: */
+ 0, /* U+011664: */
+ 0, /* U+011665: */
+ 0, /* U+011666: */
+ 0, /* U+011667: */
+ 0, /* U+011668: */
+ 0, /* U+011669: */
+ 0, /* U+01166a: */
+ 0, /* U+01166b: */
+ 0, /* U+01166c: */
+ 0, /* U+01166d: */
+ 0, /* U+01166e: */
+ 0, /* U+01166f: */
+ 0, /* U+011670: */
+ 0, /* U+011671: */
+ 0, /* U+011672: */
+ 0, /* U+011673: */
+ 0, /* U+011674: */
+ 0, /* U+011675: */
+ 0, /* U+011676: */
+ 0, /* U+011677: */
+ 0, /* U+011678: */
+ 0, /* U+011679: */
+ 0, /* U+01167a: */
+ 0, /* U+01167b: */
+ 0, /* U+01167c: */
+ 0, /* U+01167d: */
+ 0, /* U+01167e: */
+ 0, /* U+01167f: */
+ RTUNI_ALPHA, /* U+011680: TAKRI LETTER A*/
+ RTUNI_ALPHA, /* U+011681: TAKRI LETTER AA*/
+ RTUNI_ALPHA, /* U+011682: TAKRI LETTER I*/
+ RTUNI_ALPHA, /* U+011683: TAKRI LETTER II*/
+ RTUNI_ALPHA, /* U+011684: TAKRI LETTER U*/
+ RTUNI_ALPHA, /* U+011685: TAKRI LETTER UU*/
+ RTUNI_ALPHA, /* U+011686: TAKRI LETTER E*/
+ RTUNI_ALPHA, /* U+011687: TAKRI LETTER AI*/
+ RTUNI_ALPHA, /* U+011688: TAKRI LETTER O*/
+ RTUNI_ALPHA, /* U+011689: TAKRI LETTER AU*/
+ RTUNI_ALPHA, /* U+01168a: TAKRI LETTER KA*/
+ RTUNI_ALPHA, /* U+01168b: TAKRI LETTER KHA*/
+ RTUNI_ALPHA, /* U+01168c: TAKRI LETTER GA*/
+ RTUNI_ALPHA, /* U+01168d: TAKRI LETTER GHA*/
+ RTUNI_ALPHA, /* U+01168e: TAKRI LETTER NGA*/
+ RTUNI_ALPHA, /* U+01168f: TAKRI LETTER CA*/
+ RTUNI_ALPHA, /* U+011690: TAKRI LETTER CHA*/
+ RTUNI_ALPHA, /* U+011691: TAKRI LETTER JA*/
+ RTUNI_ALPHA, /* U+011692: TAKRI LETTER JHA*/
+ RTUNI_ALPHA, /* U+011693: TAKRI LETTER NYA*/
+ RTUNI_ALPHA, /* U+011694: TAKRI LETTER TTA*/
+ RTUNI_ALPHA, /* U+011695: TAKRI LETTER TTHA*/
+ RTUNI_ALPHA, /* U+011696: TAKRI LETTER DDA*/
+ RTUNI_ALPHA, /* U+011697: TAKRI LETTER DDHA*/
+ RTUNI_ALPHA, /* U+011698: TAKRI LETTER NNA*/
+ RTUNI_ALPHA, /* U+011699: TAKRI LETTER TA*/
+ RTUNI_ALPHA, /* U+01169a: TAKRI LETTER THA*/
+ RTUNI_ALPHA, /* U+01169b: TAKRI LETTER DA*/
+ RTUNI_ALPHA, /* U+01169c: TAKRI LETTER DHA*/
+ RTUNI_ALPHA, /* U+01169d: TAKRI LETTER NA*/
+ RTUNI_ALPHA, /* U+01169e: TAKRI LETTER PA*/
+ RTUNI_ALPHA, /* U+01169f: TAKRI LETTER PHA*/
+ RTUNI_ALPHA, /* U+0116a0: TAKRI LETTER BA*/
+ RTUNI_ALPHA, /* U+0116a1: TAKRI LETTER BHA*/
+ RTUNI_ALPHA, /* U+0116a2: TAKRI LETTER MA*/
+ RTUNI_ALPHA, /* U+0116a3: TAKRI LETTER YA*/
+ RTUNI_ALPHA, /* U+0116a4: TAKRI LETTER RA*/
+ RTUNI_ALPHA, /* U+0116a5: TAKRI LETTER LA*/
+ RTUNI_ALPHA, /* U+0116a6: TAKRI LETTER VA*/
+ RTUNI_ALPHA, /* U+0116a7: TAKRI LETTER SHA*/
+ RTUNI_ALPHA, /* U+0116a8: TAKRI LETTER SA*/
+ RTUNI_ALPHA, /* U+0116a9: TAKRI LETTER HA*/
+ RTUNI_ALPHA, /* U+0116aa: TAKRI LETTER RRA*/
+ RTUNI_ALPHA, /* U+0116ab: TAKRI SIGN ANUSVARA*/
+ RTUNI_ALPHA, /* U+0116ac: TAKRI SIGN VISARGA*/
+ RTUNI_ALPHA, /* U+0116ad: TAKRI VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+0116ae: TAKRI VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+0116af: TAKRI VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+0116b0: TAKRI VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+0116b1: TAKRI VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+0116b2: TAKRI VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+0116b3: TAKRI VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+0116b4: TAKRI VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+0116b5: TAKRI VOWEL SIGN AU*/
+ 0, /* U+0116b6: TAKRI SIGN VIRAMA*/
+ 0, /* U+0116b7: TAKRI SIGN NUKTA*/
+ 0, /* U+0116b8: */
+ 0, /* U+0116b9: */
+ 0, /* U+0116ba: */
+ 0, /* U+0116bb: */
+ 0, /* U+0116bc: */
+ 0, /* U+0116bd: */
+ 0, /* U+0116be: */
+ 0, /* U+0116bf: */
+ RTUNI_DDIGIT, /* U+0116c0: TAKRI DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+0116c1: TAKRI DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+0116c2: TAKRI DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+0116c3: TAKRI DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+0116c4: TAKRI DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+0116c5: TAKRI DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+0116c6: TAKRI DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+0116c7: TAKRI DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+0116c8: TAKRI DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+0116c9: TAKRI DIGIT NINE*/
+ 0, /* U+0116ca: */
+ 0, /* U+0116cb: */
+ 0, /* U+0116cc: */
+ 0, /* U+0116cd: */
+ 0, /* U+0116ce: */
+ 0, /* U+0116cf: */
+ 0, /* U+0116d0: */
+ 0, /* U+0116d1: */
+ 0, /* U+0116d2: */
+ 0, /* U+0116d3: */
+ 0, /* U+0116d4: */
+ 0, /* U+0116d5: */
+ 0, /* U+0116d6: */
+ 0, /* U+0116d7: */
+ 0, /* U+0116d8: */
+ 0, /* U+0116d9: */
+ 0, /* U+0116da: */
+ 0, /* U+0116db: */
+ 0, /* U+0116dc: */
+ 0, /* U+0116dd: */
+ 0, /* U+0116de: */
+ 0, /* U+0116df: */
+ 0, /* U+0116e0: */
+ 0, /* U+0116e1: */
+ 0, /* U+0116e2: */
+ 0, /* U+0116e3: */
+ 0, /* U+0116e4: */
+ 0, /* U+0116e5: */
+ 0, /* U+0116e6: */
+ 0, /* U+0116e7: */
+ 0, /* U+0116e8: */
+ 0, /* U+0116e9: */
+ 0, /* U+0116ea: */
+ 0, /* U+0116eb: */
+ 0, /* U+0116ec: */
+ 0, /* U+0116ed: */
+ 0, /* U+0116ee: */
+ 0, /* U+0116ef: */
+ 0, /* U+0116f0: */
+ 0, /* U+0116f1: */
+ 0, /* U+0116f2: */
+ 0, /* U+0116f3: */
+ 0, /* U+0116f4: */
+ 0, /* U+0116f5: */
+ 0, /* U+0116f6: */
+ 0, /* U+0116f7: */
+ 0, /* U+0116f8: */
+ 0, /* U+0116f9: */
+ 0, /* U+0116fa: */
+ 0, /* U+0116fb: */
+ 0, /* U+0116fc: */
+ 0, /* U+0116fd: */
+ 0, /* U+0116fe: */
+ 0, /* U+0116ff: */
+ 0, /* U+011700: */
+ 0, /* U+011701: */
+ 0, /* U+011702: */
+ 0, /* U+011703: */
+ 0, /* U+011704: */
+ 0, /* U+011705: */
+ 0, /* U+011706: */
+ 0, /* U+011707: */
+ 0, /* U+011708: */
+ 0, /* U+011709: */
+ 0, /* U+01170a: */
+ 0, /* U+01170b: */
+ 0, /* U+01170c: */
+ 0, /* U+01170d: */
+ 0, /* U+01170e: */
+ 0, /* U+01170f: */
+ 0, /* U+011710: */
+ 0, /* U+011711: */
+ 0, /* U+011712: */
+ 0, /* U+011713: */
+ 0, /* U+011714: */
+ 0, /* U+011715: */
+ 0, /* U+011716: */
+ 0, /* U+011717: */
+ 0, /* U+011718: */
+ 0, /* U+011719: */
+ 0, /* U+01171a: */
+ 0, /* U+01171b: */
+ 0, /* U+01171c: */
+ 0, /* U+01171d: */
+ 0, /* U+01171e: */
+ 0, /* U+01171f: */
+ 0, /* U+011720: */
+ 0, /* U+011721: */
+ 0, /* U+011722: */
+ 0, /* U+011723: */
+ 0, /* U+011724: */
+ 0, /* U+011725: */
+ 0, /* U+011726: */
+ 0, /* U+011727: */
+ 0, /* U+011728: */
+ 0, /* U+011729: */
+ 0, /* U+01172a: */
+ 0, /* U+01172b: */
+ 0, /* U+01172c: */
+ 0, /* U+01172d: */
+ 0, /* U+01172e: */
+ 0, /* U+01172f: */
+ 0, /* U+011730: */
+ 0, /* U+011731: */
+ 0, /* U+011732: */
+ 0, /* U+011733: */
+ 0, /* U+011734: */
+ 0, /* U+011735: */
+ 0, /* U+011736: */
+ 0, /* U+011737: */
+ 0, /* U+011738: */
+ 0, /* U+011739: */
+ 0, /* U+01173a: */
+ 0, /* U+01173b: */
+ 0, /* U+01173c: */
+ 0, /* U+01173d: */
+ 0, /* U+01173e: */
+ 0, /* U+01173f: */
+ 0, /* U+011740: */
+ 0, /* U+011741: */
+ 0, /* U+011742: */
+ 0, /* U+011743: */
+ 0, /* U+011744: */
+ 0, /* U+011745: */
+ 0, /* U+011746: */
+ 0, /* U+011747: */
+ 0, /* U+011748: */
+ 0, /* U+011749: */
+ 0, /* U+01174a: */
+ 0, /* U+01174b: */
+ 0, /* U+01174c: */
+ 0, /* U+01174d: */
+ 0, /* U+01174e: */
+ 0, /* U+01174f: */
+ 0, /* U+011750: */
+ 0, /* U+011751: */
+ 0, /* U+011752: */
+ 0, /* U+011753: */
+ 0, /* U+011754: */
+ 0, /* U+011755: */
+ 0, /* U+011756: */
+ 0, /* U+011757: */
+ 0, /* U+011758: */
+ 0, /* U+011759: */
+ 0, /* U+01175a: */
+ 0, /* U+01175b: */
+ 0, /* U+01175c: */
+ 0, /* U+01175d: */
+ 0, /* U+01175e: */
+ 0, /* U+01175f: */
+ 0, /* U+011760: */
+ 0, /* U+011761: */
+ 0, /* U+011762: */
+ 0, /* U+011763: */
+ 0, /* U+011764: */
+ 0, /* U+011765: */
+ 0, /* U+011766: */
+ 0, /* U+011767: */
+ 0, /* U+011768: */
+ 0, /* U+011769: */
+ 0, /* U+01176a: */
+ 0, /* U+01176b: */
+ 0, /* U+01176c: */
+ 0, /* U+01176d: */
+ 0, /* U+01176e: */
+ 0, /* U+01176f: */
+ 0, /* U+011770: */
+ 0, /* U+011771: */
+ 0, /* U+011772: */
+ 0, /* U+011773: */
+ 0, /* U+011774: */
+ 0, /* U+011775: */
+ 0, /* U+011776: */
+ 0, /* U+011777: */
+ 0, /* U+011778: */
+ 0, /* U+011779: */
+ 0, /* U+01177a: */
+ 0, /* U+01177b: */
+ 0, /* U+01177c: */
+ 0, /* U+01177d: */
+ 0, /* U+01177e: */
+ 0, /* U+01177f: */
+ 0, /* U+011780: */
+ 0, /* U+011781: */
+ 0, /* U+011782: */
+ 0, /* U+011783: */
+ 0, /* U+011784: */
+ 0, /* U+011785: */
+ 0, /* U+011786: */
+ 0, /* U+011787: */
+ 0, /* U+011788: */
+ 0, /* U+011789: */
+ 0, /* U+01178a: */
+ 0, /* U+01178b: */
+ 0, /* U+01178c: */
+ 0, /* U+01178d: */
+ 0, /* U+01178e: */
+ 0, /* U+01178f: */
+ 0, /* U+011790: */
+ 0, /* U+011791: */
+ 0, /* U+011792: */
+ 0, /* U+011793: */
+ 0, /* U+011794: */
+ 0, /* U+011795: */
+ 0, /* U+011796: */
+ 0, /* U+011797: */
+ 0, /* U+011798: */
+ 0, /* U+011799: */
+ 0, /* U+01179a: */
+ 0, /* U+01179b: */
+ 0, /* U+01179c: */
+ 0, /* U+01179d: */
+ 0, /* U+01179e: */
+ 0, /* U+01179f: */
+ 0, /* U+0117a0: */
+ 0, /* U+0117a1: */
+ 0, /* U+0117a2: */
+ 0, /* U+0117a3: */
+ 0, /* U+0117a4: */
+ 0, /* U+0117a5: */
+ 0, /* U+0117a6: */
+ 0, /* U+0117a7: */
+ 0, /* U+0117a8: */
+ 0, /* U+0117a9: */
+ 0, /* U+0117aa: */
+ 0, /* U+0117ab: */
+ 0, /* U+0117ac: */
+ 0, /* U+0117ad: */
+ 0, /* U+0117ae: */
+ 0, /* U+0117af: */
+ 0, /* U+0117b0: */
+ 0, /* U+0117b1: */
+ 0, /* U+0117b2: */
+ 0, /* U+0117b3: */
+ 0, /* U+0117b4: */
+ 0, /* U+0117b5: */
+ 0, /* U+0117b6: */
+ 0, /* U+0117b7: */
+ 0, /* U+0117b8: */
+ 0, /* U+0117b9: */
+ 0, /* U+0117ba: */
+ 0, /* U+0117bb: */
+ 0, /* U+0117bc: */
+ 0, /* U+0117bd: */
+ 0, /* U+0117be: */
+ 0, /* U+0117bf: */
+ 0, /* U+0117c0: */
+ 0, /* U+0117c1: */
+ 0, /* U+0117c2: */
+ 0, /* U+0117c3: */
+ 0, /* U+0117c4: */
+ 0, /* U+0117c5: */
+ 0, /* U+0117c6: */
+ 0, /* U+0117c7: */
+ 0, /* U+0117c8: */
+ 0, /* U+0117c9: */
+ 0, /* U+0117ca: */
+ 0, /* U+0117cb: */
+ 0, /* U+0117cc: */
+ 0, /* U+0117cd: */
+ 0, /* U+0117ce: */
+ 0, /* U+0117cf: */
+ 0, /* U+0117d0: */
+ 0, /* U+0117d1: */
+ 0, /* U+0117d2: */
+ 0, /* U+0117d3: */
+ 0, /* U+0117d4: */
+ 0, /* U+0117d5: */
+ 0, /* U+0117d6: */
+ 0, /* U+0117d7: */
+ 0, /* U+0117d8: */
+ 0, /* U+0117d9: */
+ 0, /* U+0117da: */
+ 0, /* U+0117db: */
+ 0, /* U+0117dc: */
+ 0, /* U+0117dd: */
+ 0, /* U+0117de: */
+ 0, /* U+0117df: */
+ 0, /* U+0117e0: */
+ 0, /* U+0117e1: */
+ 0, /* U+0117e2: */
+ 0, /* U+0117e3: */
+ 0, /* U+0117e4: */
+ 0, /* U+0117e5: */
+ 0, /* U+0117e6: */
+ 0, /* U+0117e7: */
+ 0, /* U+0117e8: */
+ 0, /* U+0117e9: */
+ 0, /* U+0117ea: */
+ 0, /* U+0117eb: */
+ 0, /* U+0117ec: */
+ 0, /* U+0117ed: */
+ 0, /* U+0117ee: */
+ 0, /* U+0117ef: */
+ 0, /* U+0117f0: */
+ 0, /* U+0117f1: */
+ 0, /* U+0117f2: */
+ 0, /* U+0117f3: */
+ 0, /* U+0117f4: */
+ 0, /* U+0117f5: */
+ 0, /* U+0117f6: */
+ 0, /* U+0117f7: */
+ 0, /* U+0117f8: */
+ 0, /* U+0117f9: */
+ 0, /* U+0117fa: */
+ 0, /* U+0117fb: */
+ 0, /* U+0117fc: */
+ 0, /* U+0117fd: */
+ 0, /* U+0117fe: */
+ 0, /* U+0117ff: */
+ 0, /* U+011800: */
+ 0, /* U+011801: */
+ 0, /* U+011802: */
+ 0, /* U+011803: */
+ 0, /* U+011804: */
+ 0, /* U+011805: */
+ 0, /* U+011806: */
+ 0, /* U+011807: */
+ 0, /* U+011808: */
+ 0, /* U+011809: */
+ 0, /* U+01180a: */
+ 0, /* U+01180b: */
+ 0, /* U+01180c: */
+ 0, /* U+01180d: */
+ 0, /* U+01180e: */
+ 0, /* U+01180f: */
+ 0, /* U+011810: */
+ 0, /* U+011811: */
+ 0, /* U+011812: */
+ 0, /* U+011813: */
+ 0, /* U+011814: */
+ 0, /* U+011815: */
+ 0, /* U+011816: */
+ 0, /* U+011817: */
+ 0, /* U+011818: */
+ 0, /* U+011819: */
+ 0, /* U+01181a: */
+ 0, /* U+01181b: */
+ 0, /* U+01181c: */
+ 0, /* U+01181d: */
+ 0, /* U+01181e: */
+ 0, /* U+01181f: */
+ 0, /* U+011820: */
+ 0, /* U+011821: */
+ 0, /* U+011822: */
+ 0, /* U+011823: */
+ 0, /* U+011824: */
+ 0, /* U+011825: */
+ 0, /* U+011826: */
+ 0, /* U+011827: */
+ 0, /* U+011828: */
+ 0, /* U+011829: */
+ 0, /* U+01182a: */
+ 0, /* U+01182b: */
+ 0, /* U+01182c: */
+ 0, /* U+01182d: */
+ 0, /* U+01182e: */
+ 0, /* U+01182f: */
+ 0, /* U+011830: */
+ 0, /* U+011831: */
+ 0, /* U+011832: */
+ 0, /* U+011833: */
+ 0, /* U+011834: */
+ 0, /* U+011835: */
+ 0, /* U+011836: */
+ 0, /* U+011837: */
+ 0, /* U+011838: */
+ 0, /* U+011839: */
+ 0, /* U+01183a: */
+ 0, /* U+01183b: */
+ 0, /* U+01183c: */
+ 0, /* U+01183d: */
+ 0, /* U+01183e: */
+ 0, /* U+01183f: */
+ 0, /* U+011840: */
+ 0, /* U+011841: */
+ 0, /* U+011842: */
+ 0, /* U+011843: */
+ 0, /* U+011844: */
+ 0, /* U+011845: */
+ 0, /* U+011846: */
+ 0, /* U+011847: */
+ 0, /* U+011848: */
+ 0, /* U+011849: */
+ 0, /* U+01184a: */
+ 0, /* U+01184b: */
+ 0, /* U+01184c: */
+ 0, /* U+01184d: */
+ 0, /* U+01184e: */
+ 0, /* U+01184f: */
+ 0, /* U+011850: */
+ 0, /* U+011851: */
+ 0, /* U+011852: */
+ 0, /* U+011853: */
+ 0, /* U+011854: */
+ 0, /* U+011855: */
+ 0, /* U+011856: */
+ 0, /* U+011857: */
+ 0, /* U+011858: */
+ 0, /* U+011859: */
+ 0, /* U+01185a: */
+ 0, /* U+01185b: */
+ 0, /* U+01185c: */
+ 0, /* U+01185d: */
+ 0, /* U+01185e: */
+ 0, /* U+01185f: */
+ 0, /* U+011860: */
+ 0, /* U+011861: */
+ 0, /* U+011862: */
+ 0, /* U+011863: */
+ 0, /* U+011864: */
+ 0, /* U+011865: */
+ 0, /* U+011866: */
+ 0, /* U+011867: */
+ 0, /* U+011868: */
+ 0, /* U+011869: */
+ 0, /* U+01186a: */
+ 0, /* U+01186b: */
+ 0, /* U+01186c: */
+ 0, /* U+01186d: */
+ 0, /* U+01186e: */
+ 0, /* U+01186f: */
+ 0, /* U+011870: */
+ 0, /* U+011871: */
+ 0, /* U+011872: */
+ 0, /* U+011873: */
+ 0, /* U+011874: */
+ 0, /* U+011875: */
+ 0, /* U+011876: */
+ 0, /* U+011877: */
+ 0, /* U+011878: */
+ 0, /* U+011879: */
+ 0, /* U+01187a: */
+ 0, /* U+01187b: */
+ 0, /* U+01187c: */
+ 0, /* U+01187d: */
+ 0, /* U+01187e: */
+ 0, /* U+01187f: */
+ 0, /* U+011880: */
+ 0, /* U+011881: */
+ 0, /* U+011882: */
+ 0, /* U+011883: */
+ 0, /* U+011884: */
+ 0, /* U+011885: */
+ 0, /* U+011886: */
+ 0, /* U+011887: */
+ 0, /* U+011888: */
+ 0, /* U+011889: */
+ 0, /* U+01188a: */
+ 0, /* U+01188b: */
+ 0, /* U+01188c: */
+ 0, /* U+01188d: */
+ 0, /* U+01188e: */
+ 0, /* U+01188f: */
+ 0, /* U+011890: */
+ 0, /* U+011891: */
+ 0, /* U+011892: */
+ 0, /* U+011893: */
+ 0, /* U+011894: */
+ 0, /* U+011895: */
+ 0, /* U+011896: */
+ 0, /* U+011897: */
+ 0, /* U+011898: */
+ 0, /* U+011899: */
+ 0, /* U+01189a: */
+ 0, /* U+01189b: */
+ 0, /* U+01189c: */
+ 0, /* U+01189d: */
+ 0, /* U+01189e: */
+ 0, /* U+01189f: */
+ 0, /* U+0118a0: */
+ 0, /* U+0118a1: */
+ 0, /* U+0118a2: */
+ 0, /* U+0118a3: */
+ 0, /* U+0118a4: */
+ 0, /* U+0118a5: */
+ 0, /* U+0118a6: */
+ 0, /* U+0118a7: */
+ 0, /* U+0118a8: */
+ 0, /* U+0118a9: */
+ 0, /* U+0118aa: */
+ 0, /* U+0118ab: */
+ 0, /* U+0118ac: */
+ 0, /* U+0118ad: */
+ 0, /* U+0118ae: */
+ 0, /* U+0118af: */
+ 0, /* U+0118b0: */
+ 0, /* U+0118b1: */
+ 0, /* U+0118b2: */
+ 0, /* U+0118b3: */
+ 0, /* U+0118b4: */
+ 0, /* U+0118b5: */
+ 0, /* U+0118b6: */
+ 0, /* U+0118b7: */
+ 0, /* U+0118b8: */
+ 0, /* U+0118b9: */
+ 0, /* U+0118ba: */
+ 0, /* U+0118bb: */
+ 0, /* U+0118bc: */
+ 0, /* U+0118bd: */
+ 0, /* U+0118be: */
+ 0, /* U+0118bf: */
+ 0, /* U+0118c0: */
+ 0, /* U+0118c1: */
+ 0, /* U+0118c2: */
+ 0, /* U+0118c3: */
+ 0, /* U+0118c4: */
+ 0, /* U+0118c5: */
+ 0, /* U+0118c6: */
+ 0, /* U+0118c7: */
+ 0, /* U+0118c8: */
+ 0, /* U+0118c9: */
+ 0, /* U+0118ca: */
+ 0, /* U+0118cb: */
+ 0, /* U+0118cc: */
+ 0, /* U+0118cd: */
+ 0, /* U+0118ce: */
+ 0, /* U+0118cf: */
+ 0, /* U+0118d0: */
+ 0, /* U+0118d1: */
+ 0, /* U+0118d2: */
+ 0, /* U+0118d3: */
+ 0, /* U+0118d4: */
+ 0, /* U+0118d5: */
+ 0, /* U+0118d6: */
+ 0, /* U+0118d7: */
+ 0, /* U+0118d8: */
+ 0, /* U+0118d9: */
+ 0, /* U+0118da: */
+ 0, /* U+0118db: */
+ 0, /* U+0118dc: */
+ 0, /* U+0118dd: */
+ 0, /* U+0118de: */
+ 0, /* U+0118df: */
+ 0, /* U+0118e0: */
+ 0, /* U+0118e1: */
+ 0, /* U+0118e2: */
+ 0, /* U+0118e3: */
+ 0, /* U+0118e4: */
+ 0, /* U+0118e5: */
+ 0, /* U+0118e6: */
+ 0, /* U+0118e7: */
+ 0, /* U+0118e8: */
+ 0, /* U+0118e9: */
+ 0, /* U+0118ea: */
+ 0, /* U+0118eb: */
+ 0, /* U+0118ec: */
+ 0, /* U+0118ed: */
+ 0, /* U+0118ee: */
+ 0, /* U+0118ef: */
+ 0, /* U+0118f0: */
+ 0, /* U+0118f1: */
+ 0, /* U+0118f2: */
+ 0, /* U+0118f3: */
+ 0, /* U+0118f4: */
+ 0, /* U+0118f5: */
+ 0, /* U+0118f6: */
+ 0, /* U+0118f7: */
+ 0, /* U+0118f8: */
+ 0, /* U+0118f9: */
+ 0, /* U+0118fa: */
+ 0, /* U+0118fb: */
+ 0, /* U+0118fc: */
+ 0, /* U+0118fd: */
+ 0, /* U+0118fe: */
+ 0, /* U+0118ff: */
+ 0, /* U+011900: */
+ 0, /* U+011901: */
+ 0, /* U+011902: */
+ 0, /* U+011903: */
+ 0, /* U+011904: */
+ 0, /* U+011905: */
+ 0, /* U+011906: */
+ 0, /* U+011907: */
+ 0, /* U+011908: */
+ 0, /* U+011909: */
+ 0, /* U+01190a: */
+ 0, /* U+01190b: */
+ 0, /* U+01190c: */
+ 0, /* U+01190d: */
+ 0, /* U+01190e: */
+ 0, /* U+01190f: */
+ 0, /* U+011910: */
+ 0, /* U+011911: */
+ 0, /* U+011912: */
+ 0, /* U+011913: */
+ 0, /* U+011914: */
+ 0, /* U+011915: */
+ 0, /* U+011916: */
+ 0, /* U+011917: */
+ 0, /* U+011918: */
+ 0, /* U+011919: */
+ 0, /* U+01191a: */
+ 0, /* U+01191b: */
+ 0, /* U+01191c: */
+ 0, /* U+01191d: */
+ 0, /* U+01191e: */
+ 0, /* U+01191f: */
+ 0, /* U+011920: */
+ 0, /* U+011921: */
+ 0, /* U+011922: */
+ 0, /* U+011923: */
+ 0, /* U+011924: */
+ 0, /* U+011925: */
+ 0, /* U+011926: */
+ 0, /* U+011927: */
+ 0, /* U+011928: */
+ 0, /* U+011929: */
+ 0, /* U+01192a: */
+ 0, /* U+01192b: */
+ 0, /* U+01192c: */
+ 0, /* U+01192d: */
+ 0, /* U+01192e: */
+ 0, /* U+01192f: */
+ 0, /* U+011930: */
+ 0, /* U+011931: */
+ 0, /* U+011932: */
+ 0, /* U+011933: */
+ 0, /* U+011934: */
+ 0, /* U+011935: */
+ 0, /* U+011936: */
+ 0, /* U+011937: */
+ 0, /* U+011938: */
+ 0, /* U+011939: */
+ 0, /* U+01193a: */
+ 0, /* U+01193b: */
+ 0, /* U+01193c: */
+ 0, /* U+01193d: */
+ 0, /* U+01193e: */
+ 0, /* U+01193f: */
+ 0, /* U+011940: */
+ 0, /* U+011941: */
+ 0, /* U+011942: */
+ 0, /* U+011943: */
+ 0, /* U+011944: */
+ 0, /* U+011945: */
+ 0, /* U+011946: */
+ 0, /* U+011947: */
+ 0, /* U+011948: */
+ 0, /* U+011949: */
+ 0, /* U+01194a: */
+ 0, /* U+01194b: */
+ 0, /* U+01194c: */
+ 0, /* U+01194d: */
+ 0, /* U+01194e: */
+ 0, /* U+01194f: */
+ 0, /* U+011950: */
+ 0, /* U+011951: */
+ 0, /* U+011952: */
+ 0, /* U+011953: */
+ 0, /* U+011954: */
+ 0, /* U+011955: */
+ 0, /* U+011956: */
+ 0, /* U+011957: */
+ 0, /* U+011958: */
+ 0, /* U+011959: */
+ 0, /* U+01195a: */
+ 0, /* U+01195b: */
+ 0, /* U+01195c: */
+ 0, /* U+01195d: */
+ 0, /* U+01195e: */
+ 0, /* U+01195f: */
+ 0, /* U+011960: */
+ 0, /* U+011961: */
+ 0, /* U+011962: */
+ 0, /* U+011963: */
+ 0, /* U+011964: */
+ 0, /* U+011965: */
+ 0, /* U+011966: */
+ 0, /* U+011967: */
+ 0, /* U+011968: */
+ 0, /* U+011969: */
+ 0, /* U+01196a: */
+ 0, /* U+01196b: */
+ 0, /* U+01196c: */
+ 0, /* U+01196d: */
+ 0, /* U+01196e: */
+ 0, /* U+01196f: */
+ 0, /* U+011970: */
+ 0, /* U+011971: */
+ 0, /* U+011972: */
+ 0, /* U+011973: */
+ 0, /* U+011974: */
+ 0, /* U+011975: */
+ 0, /* U+011976: */
+ 0, /* U+011977: */
+ 0, /* U+011978: */
+ 0, /* U+011979: */
+ 0, /* U+01197a: */
+ 0, /* U+01197b: */
+ 0, /* U+01197c: */
+ 0, /* U+01197d: */
+ 0, /* U+01197e: */
+ 0, /* U+01197f: */
+ 0, /* U+011980: */
+ 0, /* U+011981: */
+ 0, /* U+011982: */
+ 0, /* U+011983: */
+ 0, /* U+011984: */
+ 0, /* U+011985: */
+ 0, /* U+011986: */
+ 0, /* U+011987: */
+ 0, /* U+011988: */
+ 0, /* U+011989: */
+ 0, /* U+01198a: */
+ 0, /* U+01198b: */
+ 0, /* U+01198c: */
+ 0, /* U+01198d: */
+ 0, /* U+01198e: */
+ 0, /* U+01198f: */
+ 0, /* U+011990: */
+ 0, /* U+011991: */
+ 0, /* U+011992: */
+ 0, /* U+011993: */
+ 0, /* U+011994: */
+ 0, /* U+011995: */
+ 0, /* U+011996: */
+ 0, /* U+011997: */
+ 0, /* U+011998: */
+ 0, /* U+011999: */
+ 0, /* U+01199a: */
+ 0, /* U+01199b: */
+ 0, /* U+01199c: */
+ 0, /* U+01199d: */
+ 0, /* U+01199e: */
+ 0, /* U+01199f: */
+ 0, /* U+0119a0: */
+ 0, /* U+0119a1: */
+ 0, /* U+0119a2: */
+ 0, /* U+0119a3: */
+ 0, /* U+0119a4: */
+ 0, /* U+0119a5: */
+ 0, /* U+0119a6: */
+ 0, /* U+0119a7: */
+ 0, /* U+0119a8: */
+ 0, /* U+0119a9: */
+ 0, /* U+0119aa: */
+ 0, /* U+0119ab: */
+ 0, /* U+0119ac: */
+ 0, /* U+0119ad: */
+ 0, /* U+0119ae: */
+ 0, /* U+0119af: */
+ 0, /* U+0119b0: */
+ 0, /* U+0119b1: */
+ 0, /* U+0119b2: */
+ 0, /* U+0119b3: */
+ 0, /* U+0119b4: */
+ 0, /* U+0119b5: */
+ 0, /* U+0119b6: */
+ 0, /* U+0119b7: */
+ 0, /* U+0119b8: */
+ 0, /* U+0119b9: */
+ 0, /* U+0119ba: */
+ 0, /* U+0119bb: */
+ 0, /* U+0119bc: */
+ 0, /* U+0119bd: */
+ 0, /* U+0119be: */
+ 0, /* U+0119bf: */
+ 0, /* U+0119c0: */
+ 0, /* U+0119c1: */
+ 0, /* U+0119c2: */
+ 0, /* U+0119c3: */
+ 0, /* U+0119c4: */
+ 0, /* U+0119c5: */
+ 0, /* U+0119c6: */
+ 0, /* U+0119c7: */
+ 0, /* U+0119c8: */
+ 0, /* U+0119c9: */
+ 0, /* U+0119ca: */
+ 0, /* U+0119cb: */
+ 0, /* U+0119cc: */
+ 0, /* U+0119cd: */
+ 0, /* U+0119ce: */
+ 0, /* U+0119cf: */
+ 0, /* U+0119d0: */
+ 0, /* U+0119d1: */
+ 0, /* U+0119d2: */
+ 0, /* U+0119d3: */
+ 0, /* U+0119d4: */
+ 0, /* U+0119d5: */
+ 0, /* U+0119d6: */
+ 0, /* U+0119d7: */
+ 0, /* U+0119d8: */
+ 0, /* U+0119d9: */
+ 0, /* U+0119da: */
+ 0, /* U+0119db: */
+ 0, /* U+0119dc: */
+ 0, /* U+0119dd: */
+ 0, /* U+0119de: */
+ 0, /* U+0119df: */
+ 0, /* U+0119e0: */
+ 0, /* U+0119e1: */
+ 0, /* U+0119e2: */
+ 0, /* U+0119e3: */
+ 0, /* U+0119e4: */
+ 0, /* U+0119e5: */
+ 0, /* U+0119e6: */
+ 0, /* U+0119e7: */
+ 0, /* U+0119e8: */
+ 0, /* U+0119e9: */
+ 0, /* U+0119ea: */
+ 0, /* U+0119eb: */
+ 0, /* U+0119ec: */
+ 0, /* U+0119ed: */
+ 0, /* U+0119ee: */
+ 0, /* U+0119ef: */
+ 0, /* U+0119f0: */
+ 0, /* U+0119f1: */
+ 0, /* U+0119f2: */
+ 0, /* U+0119f3: */
+ 0, /* U+0119f4: */
+ 0, /* U+0119f5: */
+ 0, /* U+0119f6: */
+ 0, /* U+0119f7: */
+ 0, /* U+0119f8: */
+ 0, /* U+0119f9: */
+ 0, /* U+0119fa: */
+ 0, /* U+0119fb: */
+ 0, /* U+0119fc: */
+ 0, /* U+0119fd: */
+ 0, /* U+0119fe: */
+ 0, /* U+0119ff: */
+ 0, /* U+011a00: */
+ 0, /* U+011a01: */
+ 0, /* U+011a02: */
+ 0, /* U+011a03: */
+ 0, /* U+011a04: */
+ 0, /* U+011a05: */
+ 0, /* U+011a06: */
+ 0, /* U+011a07: */
+ 0, /* U+011a08: */
+ 0, /* U+011a09: */
+ 0, /* U+011a0a: */
+ 0, /* U+011a0b: */
+ 0, /* U+011a0c: */
+ 0, /* U+011a0d: */
+ 0, /* U+011a0e: */
+ 0, /* U+011a0f: */
+ 0, /* U+011a10: */
+ 0, /* U+011a11: */
+ 0, /* U+011a12: */
+ 0, /* U+011a13: */
+ 0, /* U+011a14: */
+ 0, /* U+011a15: */
+ 0, /* U+011a16: */
+ 0, /* U+011a17: */
+ 0, /* U+011a18: */
+ 0, /* U+011a19: */
+ 0, /* U+011a1a: */
+ 0, /* U+011a1b: */
+ 0, /* U+011a1c: */
+ 0, /* U+011a1d: */
+ 0, /* U+011a1e: */
+ 0, /* U+011a1f: */
+ 0, /* U+011a20: */
+ 0, /* U+011a21: */
+ 0, /* U+011a22: */
+ 0, /* U+011a23: */
+ 0, /* U+011a24: */
+ 0, /* U+011a25: */
+ 0, /* U+011a26: */
+ 0, /* U+011a27: */
+ 0, /* U+011a28: */
+ 0, /* U+011a29: */
+ 0, /* U+011a2a: */
+ 0, /* U+011a2b: */
+ 0, /* U+011a2c: */
+ 0, /* U+011a2d: */
+ 0, /* U+011a2e: */
+ 0, /* U+011a2f: */
+ 0, /* U+011a30: */
+ 0, /* U+011a31: */
+ 0, /* U+011a32: */
+ 0, /* U+011a33: */
+ 0, /* U+011a34: */
+ 0, /* U+011a35: */
+ 0, /* U+011a36: */
+ 0, /* U+011a37: */
+ 0, /* U+011a38: */
+ 0, /* U+011a39: */
+ 0, /* U+011a3a: */
+ 0, /* U+011a3b: */
+ 0, /* U+011a3c: */
+ 0, /* U+011a3d: */
+ 0, /* U+011a3e: */
+ 0, /* U+011a3f: */
+ 0, /* U+011a40: */
+ 0, /* U+011a41: */
+ 0, /* U+011a42: */
+ 0, /* U+011a43: */
+ 0, /* U+011a44: */
+ 0, /* U+011a45: */
+ 0, /* U+011a46: */
+ 0, /* U+011a47: */
+ 0, /* U+011a48: */
+ 0, /* U+011a49: */
+ 0, /* U+011a4a: */
+ 0, /* U+011a4b: */
+ 0, /* U+011a4c: */
+ 0, /* U+011a4d: */
+ 0, /* U+011a4e: */
+ 0, /* U+011a4f: */
+ 0, /* U+011a50: */
+ 0, /* U+011a51: */
+ 0, /* U+011a52: */
+ 0, /* U+011a53: */
+ 0, /* U+011a54: */
+ 0, /* U+011a55: */
+ 0, /* U+011a56: */
+ 0, /* U+011a57: */
+ 0, /* U+011a58: */
+ 0, /* U+011a59: */
+ 0, /* U+011a5a: */
+ 0, /* U+011a5b: */
+ 0, /* U+011a5c: */
+ 0, /* U+011a5d: */
+ 0, /* U+011a5e: */
+ 0, /* U+011a5f: */
+ 0, /* U+011a60: */
+ 0, /* U+011a61: */
+ 0, /* U+011a62: */
+ 0, /* U+011a63: */
+ 0, /* U+011a64: */
+ 0, /* U+011a65: */
+ 0, /* U+011a66: */
+ 0, /* U+011a67: */
+ 0, /* U+011a68: */
+ 0, /* U+011a69: */
+ 0, /* U+011a6a: */
+ 0, /* U+011a6b: */
+ 0, /* U+011a6c: */
+ 0, /* U+011a6d: */
+ 0, /* U+011a6e: */
+ 0, /* U+011a6f: */
+ 0, /* U+011a70: */
+ 0, /* U+011a71: */
+ 0, /* U+011a72: */
+ 0, /* U+011a73: */
+ 0, /* U+011a74: */
+ 0, /* U+011a75: */
+ 0, /* U+011a76: */
+ 0, /* U+011a77: */
+ 0, /* U+011a78: */
+ 0, /* U+011a79: */
+ 0, /* U+011a7a: */
+ 0, /* U+011a7b: */
+ 0, /* U+011a7c: */
+ 0, /* U+011a7d: */
+ 0, /* U+011a7e: */
+ 0, /* U+011a7f: */
+ 0, /* U+011a80: */
+ 0, /* U+011a81: */
+ 0, /* U+011a82: */
+ 0, /* U+011a83: */
+ 0, /* U+011a84: */
+ 0, /* U+011a85: */
+ 0, /* U+011a86: */
+ 0, /* U+011a87: */
+ 0, /* U+011a88: */
+ 0, /* U+011a89: */
+ 0, /* U+011a8a: */
+ 0, /* U+011a8b: */
+ 0, /* U+011a8c: */
+ 0, /* U+011a8d: */
+ 0, /* U+011a8e: */
+ 0, /* U+011a8f: */
+ 0, /* U+011a90: */
+ 0, /* U+011a91: */
+ 0, /* U+011a92: */
+ 0, /* U+011a93: */
+ 0, /* U+011a94: */
+ 0, /* U+011a95: */
+ 0, /* U+011a96: */
+ 0, /* U+011a97: */
+ 0, /* U+011a98: */
+ 0, /* U+011a99: */
+ 0, /* U+011a9a: */
+ 0, /* U+011a9b: */
+ 0, /* U+011a9c: */
+ 0, /* U+011a9d: */
+ 0, /* U+011a9e: */
+ 0, /* U+011a9f: */
+ 0, /* U+011aa0: */
+ 0, /* U+011aa1: */
+ 0, /* U+011aa2: */
+ 0, /* U+011aa3: */
+ 0, /* U+011aa4: */
+ 0, /* U+011aa5: */
+ 0, /* U+011aa6: */
+ 0, /* U+011aa7: */
+ 0, /* U+011aa8: */
+ 0, /* U+011aa9: */
+ 0, /* U+011aaa: */
+ 0, /* U+011aab: */
+ 0, /* U+011aac: */
+ 0, /* U+011aad: */
+ 0, /* U+011aae: */
+ 0, /* U+011aaf: */
+ 0, /* U+011ab0: */
+ 0, /* U+011ab1: */
+ 0, /* U+011ab2: */
+ 0, /* U+011ab3: */
+ 0, /* U+011ab4: */
+ 0, /* U+011ab5: */
+ 0, /* U+011ab6: */
+ 0, /* U+011ab7: */
+ 0, /* U+011ab8: */
+ 0, /* U+011ab9: */
+ 0, /* U+011aba: */
+ 0, /* U+011abb: */
+ 0, /* U+011abc: */
+ 0, /* U+011abd: */
+ 0, /* U+011abe: */
+ 0, /* U+011abf: */
+ 0, /* U+011ac0: */
+ 0, /* U+011ac1: */
+ 0, /* U+011ac2: */
+ 0, /* U+011ac3: */
+ 0, /* U+011ac4: */
+ 0, /* U+011ac5: */
+ 0, /* U+011ac6: */
+ 0, /* U+011ac7: */
+ 0, /* U+011ac8: */
+ 0, /* U+011ac9: */
+ 0, /* U+011aca: */
+ 0, /* U+011acb: */
+ 0, /* U+011acc: */
+ 0, /* U+011acd: */
+ 0, /* U+011ace: */
+ 0, /* U+011acf: */
+ 0, /* U+011ad0: */
+ 0, /* U+011ad1: */
+ 0, /* U+011ad2: */
+ 0, /* U+011ad3: */
+ 0, /* U+011ad4: */
+ 0, /* U+011ad5: */
+ 0, /* U+011ad6: */
+ 0, /* U+011ad7: */
+ 0, /* U+011ad8: */
+ 0, /* U+011ad9: */
+ 0, /* U+011ada: */
+ 0, /* U+011adb: */
+ 0, /* U+011adc: */
+ 0, /* U+011add: */
+ 0, /* U+011ade: */
+ 0, /* U+011adf: */
+ 0, /* U+011ae0: */
+ 0, /* U+011ae1: */
+ 0, /* U+011ae2: */
+ 0, /* U+011ae3: */
+ 0, /* U+011ae4: */
+ 0, /* U+011ae5: */
+ 0, /* U+011ae6: */
+ 0, /* U+011ae7: */
+ 0, /* U+011ae8: */
+ 0, /* U+011ae9: */
+ 0, /* U+011aea: */
+ 0, /* U+011aeb: */
+ 0, /* U+011aec: */
+ 0, /* U+011aed: */
+ 0, /* U+011aee: */
+ 0, /* U+011aef: */
+ 0, /* U+011af0: */
+ 0, /* U+011af1: */
+ 0, /* U+011af2: */
+ 0, /* U+011af3: */
+ 0, /* U+011af4: */
+ 0, /* U+011af5: */
+ 0, /* U+011af6: */
+ 0, /* U+011af7: */
+ 0, /* U+011af8: */
+ 0, /* U+011af9: */
+ 0, /* U+011afa: */
+ 0, /* U+011afb: */
+ 0, /* U+011afc: */
+ 0, /* U+011afd: */
+ 0, /* U+011afe: */
+ 0, /* U+011aff: */
+ 0, /* U+011b00: */
+ 0, /* U+011b01: */
+ 0, /* U+011b02: */
+ 0, /* U+011b03: */
+ 0, /* U+011b04: */
+ 0, /* U+011b05: */
+ 0, /* U+011b06: */
+ 0, /* U+011b07: */
+ 0, /* U+011b08: */
+ 0, /* U+011b09: */
+ 0, /* U+011b0a: */
+ 0, /* U+011b0b: */
+ 0, /* U+011b0c: */
+ 0, /* U+011b0d: */
+ 0, /* U+011b0e: */
+ 0, /* U+011b0f: */
+ 0, /* U+011b10: */
+ 0, /* U+011b11: */
+ 0, /* U+011b12: */
+ 0, /* U+011b13: */
+ 0, /* U+011b14: */
+ 0, /* U+011b15: */
+ 0, /* U+011b16: */
+ 0, /* U+011b17: */
+ 0, /* U+011b18: */
+ 0, /* U+011b19: */
+ 0, /* U+011b1a: */
+ 0, /* U+011b1b: */
+ 0, /* U+011b1c: */
+ 0, /* U+011b1d: */
+ 0, /* U+011b1e: */
+ 0, /* U+011b1f: */
+ 0, /* U+011b20: */
+ 0, /* U+011b21: */
+ 0, /* U+011b22: */
+ 0, /* U+011b23: */
+ 0, /* U+011b24: */
+ 0, /* U+011b25: */
+ 0, /* U+011b26: */
+ 0, /* U+011b27: */
+ 0, /* U+011b28: */
+ 0, /* U+011b29: */
+ 0, /* U+011b2a: */
+ 0, /* U+011b2b: */
+ 0, /* U+011b2c: */
+ 0, /* U+011b2d: */
+ 0, /* U+011b2e: */
+ 0, /* U+011b2f: */
+ 0, /* U+011b30: */
+ 0, /* U+011b31: */
+ 0, /* U+011b32: */
+ 0, /* U+011b33: */
+ 0, /* U+011b34: */
+ 0, /* U+011b35: */
+ 0, /* U+011b36: */
+ 0, /* U+011b37: */
+ 0, /* U+011b38: */
+ 0, /* U+011b39: */
+ 0, /* U+011b3a: */
+ 0, /* U+011b3b: */
+ 0, /* U+011b3c: */
+ 0, /* U+011b3d: */
+ 0, /* U+011b3e: */
+ 0, /* U+011b3f: */
+ 0, /* U+011b40: */
+ 0, /* U+011b41: */
+ 0, /* U+011b42: */
+ 0, /* U+011b43: */
+ 0, /* U+011b44: */
+ 0, /* U+011b45: */
+ 0, /* U+011b46: */
+ 0, /* U+011b47: */
+ 0, /* U+011b48: */
+ 0, /* U+011b49: */
+ 0, /* U+011b4a: */
+ 0, /* U+011b4b: */
+ 0, /* U+011b4c: */
+ 0, /* U+011b4d: */
+ 0, /* U+011b4e: */
+ 0, /* U+011b4f: */
+ 0, /* U+011b50: */
+ 0, /* U+011b51: */
+ 0, /* U+011b52: */
+ 0, /* U+011b53: */
+ 0, /* U+011b54: */
+ 0, /* U+011b55: */
+ 0, /* U+011b56: */
+ 0, /* U+011b57: */
+ 0, /* U+011b58: */
+ 0, /* U+011b59: */
+ 0, /* U+011b5a: */
+ 0, /* U+011b5b: */
+ 0, /* U+011b5c: */
+ 0, /* U+011b5d: */
+ 0, /* U+011b5e: */
+ 0, /* U+011b5f: */
+ 0, /* U+011b60: */
+ 0, /* U+011b61: */
+ 0, /* U+011b62: */
+ 0, /* U+011b63: */
+ 0, /* U+011b64: */
+ 0, /* U+011b65: */
+ 0, /* U+011b66: */
+ 0, /* U+011b67: */
+ 0, /* U+011b68: */
+ 0, /* U+011b69: */
+ 0, /* U+011b6a: */
+ 0, /* U+011b6b: */
+ 0, /* U+011b6c: */
+ 0, /* U+011b6d: */
+ 0, /* U+011b6e: */
+ 0, /* U+011b6f: */
+ 0, /* U+011b70: */
+ 0, /* U+011b71: */
+ 0, /* U+011b72: */
+ 0, /* U+011b73: */
+ 0, /* U+011b74: */
+ 0, /* U+011b75: */
+ 0, /* U+011b76: */
+ 0, /* U+011b77: */
+ 0, /* U+011b78: */
+ 0, /* U+011b79: */
+ 0, /* U+011b7a: */
+ 0, /* U+011b7b: */
+ 0, /* U+011b7c: */
+ 0, /* U+011b7d: */
+ 0, /* U+011b7e: */
+ 0, /* U+011b7f: */
+ 0, /* U+011b80: */
+ 0, /* U+011b81: */
+ 0, /* U+011b82: */
+ 0, /* U+011b83: */
+ 0, /* U+011b84: */
+ 0, /* U+011b85: */
+ 0, /* U+011b86: */
+ 0, /* U+011b87: */
+ 0, /* U+011b88: */
+ 0, /* U+011b89: */
+ 0, /* U+011b8a: */
+ 0, /* U+011b8b: */
+ 0, /* U+011b8c: */
+ 0, /* U+011b8d: */
+ 0, /* U+011b8e: */
+ 0, /* U+011b8f: */
+ 0, /* U+011b90: */
+ 0, /* U+011b91: */
+ 0, /* U+011b92: */
+ 0, /* U+011b93: */
+ 0, /* U+011b94: */
+ 0, /* U+011b95: */
+ 0, /* U+011b96: */
+ 0, /* U+011b97: */
+ 0, /* U+011b98: */
+ 0, /* U+011b99: */
+ 0, /* U+011b9a: */
+ 0, /* U+011b9b: */
+ 0, /* U+011b9c: */
+ 0, /* U+011b9d: */
+ 0, /* U+011b9e: */
+ 0, /* U+011b9f: */
+ 0, /* U+011ba0: */
+ 0, /* U+011ba1: */
+ 0, /* U+011ba2: */
+ 0, /* U+011ba3: */
+ 0, /* U+011ba4: */
+ 0, /* U+011ba5: */
+ 0, /* U+011ba6: */
+ 0, /* U+011ba7: */
+ 0, /* U+011ba8: */
+ 0, /* U+011ba9: */
+ 0, /* U+011baa: */
+ 0, /* U+011bab: */
+ 0, /* U+011bac: */
+ 0, /* U+011bad: */
+ 0, /* U+011bae: */
+ 0, /* U+011baf: */
+ 0, /* U+011bb0: */
+ 0, /* U+011bb1: */
+ 0, /* U+011bb2: */
+ 0, /* U+011bb3: */
+ 0, /* U+011bb4: */
+ 0, /* U+011bb5: */
+ 0, /* U+011bb6: */
+ 0, /* U+011bb7: */
+ 0, /* U+011bb8: */
+ 0, /* U+011bb9: */
+ 0, /* U+011bba: */
+ 0, /* U+011bbb: */
+ 0, /* U+011bbc: */
+ 0, /* U+011bbd: */
+ 0, /* U+011bbe: */
+ 0, /* U+011bbf: */
+ 0, /* U+011bc0: */
+ 0, /* U+011bc1: */
+ 0, /* U+011bc2: */
+ 0, /* U+011bc3: */
+ 0, /* U+011bc4: */
+ 0, /* U+011bc5: */
+ 0, /* U+011bc6: */
+ 0, /* U+011bc7: */
+ 0, /* U+011bc8: */
+ 0, /* U+011bc9: */
+ 0, /* U+011bca: */
+ 0, /* U+011bcb: */
+ 0, /* U+011bcc: */
+ 0, /* U+011bcd: */
+ 0, /* U+011bce: */
+ 0, /* U+011bcf: */
+ 0, /* U+011bd0: */
+ 0, /* U+011bd1: */
+ 0, /* U+011bd2: */
+ 0, /* U+011bd3: */
+ 0, /* U+011bd4: */
+ 0, /* U+011bd5: */
+ 0, /* U+011bd6: */
+ 0, /* U+011bd7: */
+ 0, /* U+011bd8: */
+ 0, /* U+011bd9: */
+ 0, /* U+011bda: */
+ 0, /* U+011bdb: */
+ 0, /* U+011bdc: */
+ 0, /* U+011bdd: */
+ 0, /* U+011bde: */
+ 0, /* U+011bdf: */
+ 0, /* U+011be0: */
+ 0, /* U+011be1: */
+ 0, /* U+011be2: */
+ 0, /* U+011be3: */
+ 0, /* U+011be4: */
+ 0, /* U+011be5: */
+ 0, /* U+011be6: */
+ 0, /* U+011be7: */
+ 0, /* U+011be8: */
+ 0, /* U+011be9: */
+ 0, /* U+011bea: */
+ 0, /* U+011beb: */
+ 0, /* U+011bec: */
+ 0, /* U+011bed: */
+ 0, /* U+011bee: */
+ 0, /* U+011bef: */
+ 0, /* U+011bf0: */
+ 0, /* U+011bf1: */
+ 0, /* U+011bf2: */
+ 0, /* U+011bf3: */
+ 0, /* U+011bf4: */
+ 0, /* U+011bf5: */
+ 0, /* U+011bf6: */
+ 0, /* U+011bf7: */
+ 0, /* U+011bf8: */
+ 0, /* U+011bf9: */
+ 0, /* U+011bfa: */
+ 0, /* U+011bfb: */
+ 0, /* U+011bfc: */
+ 0, /* U+011bfd: */
+ 0, /* U+011bfe: */
+ 0, /* U+011bff: */
+ 0, /* U+011c00: */
+ 0, /* U+011c01: */
+ 0, /* U+011c02: */
+ 0, /* U+011c03: */
+ 0, /* U+011c04: */
+ 0, /* U+011c05: */
+ 0, /* U+011c06: */
+ 0, /* U+011c07: */
+ 0, /* U+011c08: */
+ 0, /* U+011c09: */
+ 0, /* U+011c0a: */
+ 0, /* U+011c0b: */
+ 0, /* U+011c0c: */
+ 0, /* U+011c0d: */
+ 0, /* U+011c0e: */
+ 0, /* U+011c0f: */
+ 0, /* U+011c10: */
+ 0, /* U+011c11: */
+ 0, /* U+011c12: */
+ 0, /* U+011c13: */
+ 0, /* U+011c14: */
+ 0, /* U+011c15: */
+ 0, /* U+011c16: */
+ 0, /* U+011c17: */
+ 0, /* U+011c18: */
+ 0, /* U+011c19: */
+ 0, /* U+011c1a: */
+ 0, /* U+011c1b: */
+ 0, /* U+011c1c: */
+ 0, /* U+011c1d: */
+ 0, /* U+011c1e: */
+ 0, /* U+011c1f: */
+ 0, /* U+011c20: */
+ 0, /* U+011c21: */
+ 0, /* U+011c22: */
+ 0, /* U+011c23: */
+ 0, /* U+011c24: */
+ 0, /* U+011c25: */
+ 0, /* U+011c26: */
+ 0, /* U+011c27: */
+ 0, /* U+011c28: */
+ 0, /* U+011c29: */
+ 0, /* U+011c2a: */
+ 0, /* U+011c2b: */
+ 0, /* U+011c2c: */
+ 0, /* U+011c2d: */
+ 0, /* U+011c2e: */
+ 0, /* U+011c2f: */
+ 0, /* U+011c30: */
+ 0, /* U+011c31: */
+ 0, /* U+011c32: */
+ 0, /* U+011c33: */
+ 0, /* U+011c34: */
+ 0, /* U+011c35: */
+ 0, /* U+011c36: */
+ 0, /* U+011c37: */
+ 0, /* U+011c38: */
+ 0, /* U+011c39: */
+ 0, /* U+011c3a: */
+ 0, /* U+011c3b: */
+ 0, /* U+011c3c: */
+ 0, /* U+011c3d: */
+ 0, /* U+011c3e: */
+ 0, /* U+011c3f: */
+ 0, /* U+011c40: */
+ 0, /* U+011c41: */
+ 0, /* U+011c42: */
+ 0, /* U+011c43: */
+ 0, /* U+011c44: */
+ 0, /* U+011c45: */
+ 0, /* U+011c46: */
+ 0, /* U+011c47: */
+ 0, /* U+011c48: */
+ 0, /* U+011c49: */
+ 0, /* U+011c4a: */
+ 0, /* U+011c4b: */
+ 0, /* U+011c4c: */
+ 0, /* U+011c4d: */
+ 0, /* U+011c4e: */
+ 0, /* U+011c4f: */
+ 0, /* U+011c50: */
+ 0, /* U+011c51: */
+ 0, /* U+011c52: */
+ 0, /* U+011c53: */
+ 0, /* U+011c54: */
+ 0, /* U+011c55: */
+ 0, /* U+011c56: */
+ 0, /* U+011c57: */
+ 0, /* U+011c58: */
+ 0, /* U+011c59: */
+ 0, /* U+011c5a: */
+ 0, /* U+011c5b: */
+ 0, /* U+011c5c: */
+ 0, /* U+011c5d: */
+ 0, /* U+011c5e: */
+ 0, /* U+011c5f: */
+ 0, /* U+011c60: */
+ 0, /* U+011c61: */
+ 0, /* U+011c62: */
+ 0, /* U+011c63: */
+ 0, /* U+011c64: */
+ 0, /* U+011c65: */
+ 0, /* U+011c66: */
+ 0, /* U+011c67: */
+ 0, /* U+011c68: */
+ 0, /* U+011c69: */
+ 0, /* U+011c6a: */
+ 0, /* U+011c6b: */
+ 0, /* U+011c6c: */
+ 0, /* U+011c6d: */
+ 0, /* U+011c6e: */
+ 0, /* U+011c6f: */
+ 0, /* U+011c70: */
+ 0, /* U+011c71: */
+ 0, /* U+011c72: */
+ 0, /* U+011c73: */
+ 0, /* U+011c74: */
+ 0, /* U+011c75: */
+ 0, /* U+011c76: */
+ 0, /* U+011c77: */
+ 0, /* U+011c78: */
+ 0, /* U+011c79: */
+ 0, /* U+011c7a: */
+ 0, /* U+011c7b: */
+ 0, /* U+011c7c: */
+ 0, /* U+011c7d: */
+ 0, /* U+011c7e: */
+ 0, /* U+011c7f: */
+ 0, /* U+011c80: */
+ 0, /* U+011c81: */
+ 0, /* U+011c82: */
+ 0, /* U+011c83: */
+ 0, /* U+011c84: */
+ 0, /* U+011c85: */
+ 0, /* U+011c86: */
+ 0, /* U+011c87: */
+ 0, /* U+011c88: */
+ 0, /* U+011c89: */
+ 0, /* U+011c8a: */
+ 0, /* U+011c8b: */
+ 0, /* U+011c8c: */
+ 0, /* U+011c8d: */
+ 0, /* U+011c8e: */
+ 0, /* U+011c8f: */
+ 0, /* U+011c90: */
+ 0, /* U+011c91: */
+ 0, /* U+011c92: */
+ 0, /* U+011c93: */
+ 0, /* U+011c94: */
+ 0, /* U+011c95: */
+ 0, /* U+011c96: */
+ 0, /* U+011c97: */
+ 0, /* U+011c98: */
+ 0, /* U+011c99: */
+ 0, /* U+011c9a: */
+ 0, /* U+011c9b: */
+ 0, /* U+011c9c: */
+ 0, /* U+011c9d: */
+ 0, /* U+011c9e: */
+ 0, /* U+011c9f: */
+ 0, /* U+011ca0: */
+ 0, /* U+011ca1: */
+ 0, /* U+011ca2: */
+ 0, /* U+011ca3: */
+ 0, /* U+011ca4: */
+ 0, /* U+011ca5: */
+ 0, /* U+011ca6: */
+ 0, /* U+011ca7: */
+ 0, /* U+011ca8: */
+ 0, /* U+011ca9: */
+ 0, /* U+011caa: */
+ 0, /* U+011cab: */
+ 0, /* U+011cac: */
+ 0, /* U+011cad: */
+ 0, /* U+011cae: */
+ 0, /* U+011caf: */
+ 0, /* U+011cb0: */
+ 0, /* U+011cb1: */
+ 0, /* U+011cb2: */
+ 0, /* U+011cb3: */
+ 0, /* U+011cb4: */
+ 0, /* U+011cb5: */
+ 0, /* U+011cb6: */
+ 0, /* U+011cb7: */
+ 0, /* U+011cb8: */
+ 0, /* U+011cb9: */
+ 0, /* U+011cba: */
+ 0, /* U+011cbb: */
+ 0, /* U+011cbc: */
+ 0, /* U+011cbd: */
+ 0, /* U+011cbe: */
+ 0, /* U+011cbf: */
+ 0, /* U+011cc0: */
+ 0, /* U+011cc1: */
+ 0, /* U+011cc2: */
+ 0, /* U+011cc3: */
+ 0, /* U+011cc4: */
+ 0, /* U+011cc5: */
+ 0, /* U+011cc6: */
+ 0, /* U+011cc7: */
+ 0, /* U+011cc8: */
+ 0, /* U+011cc9: */
+ 0, /* U+011cca: */
+ 0, /* U+011ccb: */
+ 0, /* U+011ccc: */
+ 0, /* U+011ccd: */
+ 0, /* U+011cce: */
+ 0, /* U+011ccf: */
+ 0, /* U+011cd0: */
+ 0, /* U+011cd1: */
+ 0, /* U+011cd2: */
+ 0, /* U+011cd3: */
+ 0, /* U+011cd4: */
+ 0, /* U+011cd5: */
+ 0, /* U+011cd6: */
+ 0, /* U+011cd7: */
+ 0, /* U+011cd8: */
+ 0, /* U+011cd9: */
+ 0, /* U+011cda: */
+ 0, /* U+011cdb: */
+ 0, /* U+011cdc: */
+ 0, /* U+011cdd: */
+ 0, /* U+011cde: */
+ 0, /* U+011cdf: */
+ 0, /* U+011ce0: */
+ 0, /* U+011ce1: */
+ 0, /* U+011ce2: */
+ 0, /* U+011ce3: */
+ 0, /* U+011ce4: */
+ 0, /* U+011ce5: */
+ 0, /* U+011ce6: */
+ 0, /* U+011ce7: */
+ 0, /* U+011ce8: */
+ 0, /* U+011ce9: */
+ 0, /* U+011cea: */
+ 0, /* U+011ceb: */
+ 0, /* U+011cec: */
+ 0, /* U+011ced: */
+ 0, /* U+011cee: */
+ 0, /* U+011cef: */
+ 0, /* U+011cf0: */
+ 0, /* U+011cf1: */
+ 0, /* U+011cf2: */
+ 0, /* U+011cf3: */
+ 0, /* U+011cf4: */
+ 0, /* U+011cf5: */
+ 0, /* U+011cf6: */
+ 0, /* U+011cf7: */
+ 0, /* U+011cf8: */
+ 0, /* U+011cf9: */
+ 0, /* U+011cfa: */
+ 0, /* U+011cfb: */
+ 0, /* U+011cfc: */
+ 0, /* U+011cfd: */
+ 0, /* U+011cfe: */
+ 0, /* U+011cff: */
+ 0, /* U+011d00: */
+ 0, /* U+011d01: */
+ 0, /* U+011d02: */
+ 0, /* U+011d03: */
+ 0, /* U+011d04: */
+ 0, /* U+011d05: */
+ 0, /* U+011d06: */
+ 0, /* U+011d07: */
+ 0, /* U+011d08: */
+ 0, /* U+011d09: */
+ 0, /* U+011d0a: */
+ 0, /* U+011d0b: */
+ 0, /* U+011d0c: */
+ 0, /* U+011d0d: */
+ 0, /* U+011d0e: */
+ 0, /* U+011d0f: */
+ 0, /* U+011d10: */
+ 0, /* U+011d11: */
+ 0, /* U+011d12: */
+ 0, /* U+011d13: */
+ 0, /* U+011d14: */
+ 0, /* U+011d15: */
+ 0, /* U+011d16: */
+ 0, /* U+011d17: */
+ 0, /* U+011d18: */
+ 0, /* U+011d19: */
+ 0, /* U+011d1a: */
+ 0, /* U+011d1b: */
+ 0, /* U+011d1c: */
+ 0, /* U+011d1d: */
+ 0, /* U+011d1e: */
+ 0, /* U+011d1f: */
+ 0, /* U+011d20: */
+ 0, /* U+011d21: */
+ 0, /* U+011d22: */
+ 0, /* U+011d23: */
+ 0, /* U+011d24: */
+ 0, /* U+011d25: */
+ 0, /* U+011d26: */
+ 0, /* U+011d27: */
+ 0, /* U+011d28: */
+ 0, /* U+011d29: */
+ 0, /* U+011d2a: */
+ 0, /* U+011d2b: */
+ 0, /* U+011d2c: */
+ 0, /* U+011d2d: */
+ 0, /* U+011d2e: */
+ 0, /* U+011d2f: */
+ 0, /* U+011d30: */
+ 0, /* U+011d31: */
+ 0, /* U+011d32: */
+ 0, /* U+011d33: */
+ 0, /* U+011d34: */
+ 0, /* U+011d35: */
+ 0, /* U+011d36: */
+ 0, /* U+011d37: */
+ 0, /* U+011d38: */
+ 0, /* U+011d39: */
+ 0, /* U+011d3a: */
+ 0, /* U+011d3b: */
+ 0, /* U+011d3c: */
+ 0, /* U+011d3d: */
+ 0, /* U+011d3e: */
+ 0, /* U+011d3f: */
+ 0, /* U+011d40: */
+ 0, /* U+011d41: */
+ 0, /* U+011d42: */
+ 0, /* U+011d43: */
+ 0, /* U+011d44: */
+ 0, /* U+011d45: */
+ 0, /* U+011d46: */
+ 0, /* U+011d47: */
+ 0, /* U+011d48: */
+ 0, /* U+011d49: */
+ 0, /* U+011d4a: */
+ 0, /* U+011d4b: */
+ 0, /* U+011d4c: */
+ 0, /* U+011d4d: */
+ 0, /* U+011d4e: */
+ 0, /* U+011d4f: */
+ 0, /* U+011d50: */
+ 0, /* U+011d51: */
+ 0, /* U+011d52: */
+ 0, /* U+011d53: */
+ 0, /* U+011d54: */
+ 0, /* U+011d55: */
+ 0, /* U+011d56: */
+ 0, /* U+011d57: */
+ 0, /* U+011d58: */
+ 0, /* U+011d59: */
+ 0, /* U+011d5a: */
+ 0, /* U+011d5b: */
+ 0, /* U+011d5c: */
+ 0, /* U+011d5d: */
+ 0, /* U+011d5e: */
+ 0, /* U+011d5f: */
+ 0, /* U+011d60: */
+ 0, /* U+011d61: */
+ 0, /* U+011d62: */
+ 0, /* U+011d63: */
+ 0, /* U+011d64: */
+ 0, /* U+011d65: */
+ 0, /* U+011d66: */
+ 0, /* U+011d67: */
+ 0, /* U+011d68: */
+ 0, /* U+011d69: */
+ 0, /* U+011d6a: */
+ 0, /* U+011d6b: */
+ 0, /* U+011d6c: */
+ 0, /* U+011d6d: */
+ 0, /* U+011d6e: */
+ 0, /* U+011d6f: */
+ 0, /* U+011d70: */
+ 0, /* U+011d71: */
+ 0, /* U+011d72: */
+ 0, /* U+011d73: */
+ 0, /* U+011d74: */
+ 0, /* U+011d75: */
+ 0, /* U+011d76: */
+ 0, /* U+011d77: */
+ 0, /* U+011d78: */
+ 0, /* U+011d79: */
+ 0, /* U+011d7a: */
+ 0, /* U+011d7b: */
+ 0, /* U+011d7c: */
+ 0, /* U+011d7d: */
+ 0, /* U+011d7e: */
+ 0, /* U+011d7f: */
+ 0, /* U+011d80: */
+ 0, /* U+011d81: */
+ 0, /* U+011d82: */
+ 0, /* U+011d83: */
+ 0, /* U+011d84: */
+ 0, /* U+011d85: */
+ 0, /* U+011d86: */
+ 0, /* U+011d87: */
+ 0, /* U+011d88: */
+ 0, /* U+011d89: */
+ 0, /* U+011d8a: */
+ 0, /* U+011d8b: */
+ 0, /* U+011d8c: */
+ 0, /* U+011d8d: */
+ 0, /* U+011d8e: */
+ 0, /* U+011d8f: */
+ 0, /* U+011d90: */
+ 0, /* U+011d91: */
+ 0, /* U+011d92: */
+ 0, /* U+011d93: */
+ 0, /* U+011d94: */
+ 0, /* U+011d95: */
+ 0, /* U+011d96: */
+ 0, /* U+011d97: */
+ 0, /* U+011d98: */
+ 0, /* U+011d99: */
+ 0, /* U+011d9a: */
+ 0, /* U+011d9b: */
+ 0, /* U+011d9c: */
+ 0, /* U+011d9d: */
+ 0, /* U+011d9e: */
+ 0, /* U+011d9f: */
+ 0, /* U+011da0: */
+ 0, /* U+011da1: */
+ 0, /* U+011da2: */
+ 0, /* U+011da3: */
+ 0, /* U+011da4: */
+ 0, /* U+011da5: */
+ 0, /* U+011da6: */
+ 0, /* U+011da7: */
+ 0, /* U+011da8: */
+ 0, /* U+011da9: */
+ 0, /* U+011daa: */
+ 0, /* U+011dab: */
+ 0, /* U+011dac: */
+ 0, /* U+011dad: */
+ 0, /* U+011dae: */
+ 0, /* U+011daf: */
+ 0, /* U+011db0: */
+ 0, /* U+011db1: */
+ 0, /* U+011db2: */
+ 0, /* U+011db3: */
+ 0, /* U+011db4: */
+ 0, /* U+011db5: */
+ 0, /* U+011db6: */
+ 0, /* U+011db7: */
+ 0, /* U+011db8: */
+ 0, /* U+011db9: */
+ 0, /* U+011dba: */
+ 0, /* U+011dbb: */
+ 0, /* U+011dbc: */
+ 0, /* U+011dbd: */
+ 0, /* U+011dbe: */
+ 0, /* U+011dbf: */
+ 0, /* U+011dc0: */
+ 0, /* U+011dc1: */
+ 0, /* U+011dc2: */
+ 0, /* U+011dc3: */
+ 0, /* U+011dc4: */
+ 0, /* U+011dc5: */
+ 0, /* U+011dc6: */
+ 0, /* U+011dc7: */
+ 0, /* U+011dc8: */
+ 0, /* U+011dc9: */
+ 0, /* U+011dca: */
+ 0, /* U+011dcb: */
+ 0, /* U+011dcc: */
+ 0, /* U+011dcd: */
+ 0, /* U+011dce: */
+ 0, /* U+011dcf: */
+ 0, /* U+011dd0: */
+ 0, /* U+011dd1: */
+ 0, /* U+011dd2: */
+ 0, /* U+011dd3: */
+ 0, /* U+011dd4: */
+ 0, /* U+011dd5: */
+ 0, /* U+011dd6: */
+ 0, /* U+011dd7: */
+ 0, /* U+011dd8: */
+ 0, /* U+011dd9: */
+ 0, /* U+011dda: */
+ 0, /* U+011ddb: */
+ 0, /* U+011ddc: */
+ 0, /* U+011ddd: */
+ 0, /* U+011dde: */
+ 0, /* U+011ddf: */
+ 0, /* U+011de0: */
+ 0, /* U+011de1: */
+ 0, /* U+011de2: */
+ 0, /* U+011de3: */
+ 0, /* U+011de4: */
+ 0, /* U+011de5: */
+ 0, /* U+011de6: */
+ 0, /* U+011de7: */
+ 0, /* U+011de8: */
+ 0, /* U+011de9: */
+ 0, /* U+011dea: */
+ 0, /* U+011deb: */
+ 0, /* U+011dec: */
+ 0, /* U+011ded: */
+ 0, /* U+011dee: */
+ 0, /* U+011def: */
+ 0, /* U+011df0: */
+ 0, /* U+011df1: */
+ 0, /* U+011df2: */
+ 0, /* U+011df3: */
+ 0, /* U+011df4: */
+ 0, /* U+011df5: */
+ 0, /* U+011df6: */
+ 0, /* U+011df7: */
+ 0, /* U+011df8: */
+ 0, /* U+011df9: */
+ 0, /* U+011dfa: */
+ 0, /* U+011dfb: */
+ 0, /* U+011dfc: */
+ 0, /* U+011dfd: */
+ 0, /* U+011dfe: */
+ 0, /* U+011dff: */
+ 0, /* U+011e00: */
+ 0, /* U+011e01: */
+ 0, /* U+011e02: */
+ 0, /* U+011e03: */
+ 0, /* U+011e04: */
+ 0, /* U+011e05: */
+ 0, /* U+011e06: */
+ 0, /* U+011e07: */
+ 0, /* U+011e08: */
+ 0, /* U+011e09: */
+ 0, /* U+011e0a: */
+ 0, /* U+011e0b: */
+ 0, /* U+011e0c: */
+ 0, /* U+011e0d: */
+ 0, /* U+011e0e: */
+ 0, /* U+011e0f: */
+ 0, /* U+011e10: */
+ 0, /* U+011e11: */
+ 0, /* U+011e12: */
+ 0, /* U+011e13: */
+ 0, /* U+011e14: */
+ 0, /* U+011e15: */
+ 0, /* U+011e16: */
+ 0, /* U+011e17: */
+ 0, /* U+011e18: */
+ 0, /* U+011e19: */
+ 0, /* U+011e1a: */
+ 0, /* U+011e1b: */
+ 0, /* U+011e1c: */
+ 0, /* U+011e1d: */
+ 0, /* U+011e1e: */
+ 0, /* U+011e1f: */
+ 0, /* U+011e20: */
+ 0, /* U+011e21: */
+ 0, /* U+011e22: */
+ 0, /* U+011e23: */
+ 0, /* U+011e24: */
+ 0, /* U+011e25: */
+ 0, /* U+011e26: */
+ 0, /* U+011e27: */
+ 0, /* U+011e28: */
+ 0, /* U+011e29: */
+ 0, /* U+011e2a: */
+ 0, /* U+011e2b: */
+ 0, /* U+011e2c: */
+ 0, /* U+011e2d: */
+ 0, /* U+011e2e: */
+ 0, /* U+011e2f: */
+ 0, /* U+011e30: */
+ 0, /* U+011e31: */
+ 0, /* U+011e32: */
+ 0, /* U+011e33: */
+ 0, /* U+011e34: */
+ 0, /* U+011e35: */
+ 0, /* U+011e36: */
+ 0, /* U+011e37: */
+ 0, /* U+011e38: */
+ 0, /* U+011e39: */
+ 0, /* U+011e3a: */
+ 0, /* U+011e3b: */
+ 0, /* U+011e3c: */
+ 0, /* U+011e3d: */
+ 0, /* U+011e3e: */
+ 0, /* U+011e3f: */
+ 0, /* U+011e40: */
+ 0, /* U+011e41: */
+ 0, /* U+011e42: */
+ 0, /* U+011e43: */
+ 0, /* U+011e44: */
+ 0, /* U+011e45: */
+ 0, /* U+011e46: */
+ 0, /* U+011e47: */
+ 0, /* U+011e48: */
+ 0, /* U+011e49: */
+ 0, /* U+011e4a: */
+ 0, /* U+011e4b: */
+ 0, /* U+011e4c: */
+ 0, /* U+011e4d: */
+ 0, /* U+011e4e: */
+ 0, /* U+011e4f: */
+ 0, /* U+011e50: */
+ 0, /* U+011e51: */
+ 0, /* U+011e52: */
+ 0, /* U+011e53: */
+ 0, /* U+011e54: */
+ 0, /* U+011e55: */
+ 0, /* U+011e56: */
+ 0, /* U+011e57: */
+ 0, /* U+011e58: */
+ 0, /* U+011e59: */
+ 0, /* U+011e5a: */
+ 0, /* U+011e5b: */
+ 0, /* U+011e5c: */
+ 0, /* U+011e5d: */
+ 0, /* U+011e5e: */
+ 0, /* U+011e5f: */
+ 0, /* U+011e60: */
+ 0, /* U+011e61: */
+ 0, /* U+011e62: */
+ 0, /* U+011e63: */
+ 0, /* U+011e64: */
+ 0, /* U+011e65: */
+ 0, /* U+011e66: */
+ 0, /* U+011e67: */
+ 0, /* U+011e68: */
+ 0, /* U+011e69: */
+ 0, /* U+011e6a: */
+ 0, /* U+011e6b: */
+ 0, /* U+011e6c: */
+ 0, /* U+011e6d: */
+ 0, /* U+011e6e: */
+ 0, /* U+011e6f: */
+ 0, /* U+011e70: */
+ 0, /* U+011e71: */
+ 0, /* U+011e72: */
+ 0, /* U+011e73: */
+ 0, /* U+011e74: */
+ 0, /* U+011e75: */
+ 0, /* U+011e76: */
+ 0, /* U+011e77: */
+ 0, /* U+011e78: */
+ 0, /* U+011e79: */
+ 0, /* U+011e7a: */
+ 0, /* U+011e7b: */
+ 0, /* U+011e7c: */
+ 0, /* U+011e7d: */
+ 0, /* U+011e7e: */
+ 0, /* U+011e7f: */
+ 0, /* U+011e80: */
+ 0, /* U+011e81: */
+ 0, /* U+011e82: */
+ 0, /* U+011e83: */
+ 0, /* U+011e84: */
+ 0, /* U+011e85: */
+ 0, /* U+011e86: */
+ 0, /* U+011e87: */
+ 0, /* U+011e88: */
+ 0, /* U+011e89: */
+ 0, /* U+011e8a: */
+ 0, /* U+011e8b: */
+ 0, /* U+011e8c: */
+ 0, /* U+011e8d: */
+ 0, /* U+011e8e: */
+ 0, /* U+011e8f: */
+ 0, /* U+011e90: */
+ 0, /* U+011e91: */
+ 0, /* U+011e92: */
+ 0, /* U+011e93: */
+ 0, /* U+011e94: */
+ 0, /* U+011e95: */
+ 0, /* U+011e96: */
+ 0, /* U+011e97: */
+ 0, /* U+011e98: */
+ 0, /* U+011e99: */
+ 0, /* U+011e9a: */
+ 0, /* U+011e9b: */
+ 0, /* U+011e9c: */
+ 0, /* U+011e9d: */
+ 0, /* U+011e9e: */
+ 0, /* U+011e9f: */
+ 0, /* U+011ea0: */
+ 0, /* U+011ea1: */
+ 0, /* U+011ea2: */
+ 0, /* U+011ea3: */
+ 0, /* U+011ea4: */
+ 0, /* U+011ea5: */
+ 0, /* U+011ea6: */
+ 0, /* U+011ea7: */
+ 0, /* U+011ea8: */
+ 0, /* U+011ea9: */
+ 0, /* U+011eaa: */
+ 0, /* U+011eab: */
+ 0, /* U+011eac: */
+ 0, /* U+011ead: */
+ 0, /* U+011eae: */
+ 0, /* U+011eaf: */
+ 0, /* U+011eb0: */
+ 0, /* U+011eb1: */
+ 0, /* U+011eb2: */
+ 0, /* U+011eb3: */
+ 0, /* U+011eb4: */
+ 0, /* U+011eb5: */
+ 0, /* U+011eb6: */
+ 0, /* U+011eb7: */
+ 0, /* U+011eb8: */
+ 0, /* U+011eb9: */
+ 0, /* U+011eba: */
+ 0, /* U+011ebb: */
+ 0, /* U+011ebc: */
+ 0, /* U+011ebd: */
+ 0, /* U+011ebe: */
+ 0, /* U+011ebf: */
+ 0, /* U+011ec0: */
+ 0, /* U+011ec1: */
+ 0, /* U+011ec2: */
+ 0, /* U+011ec3: */
+ 0, /* U+011ec4: */
+ 0, /* U+011ec5: */
+ 0, /* U+011ec6: */
+ 0, /* U+011ec7: */
+ 0, /* U+011ec8: */
+ 0, /* U+011ec9: */
+ 0, /* U+011eca: */
+ 0, /* U+011ecb: */
+ 0, /* U+011ecc: */
+ 0, /* U+011ecd: */
+ 0, /* U+011ece: */
+ 0, /* U+011ecf: */
+ 0, /* U+011ed0: */
+ 0, /* U+011ed1: */
+ 0, /* U+011ed2: */
+ 0, /* U+011ed3: */
+ 0, /* U+011ed4: */
+ 0, /* U+011ed5: */
+ 0, /* U+011ed6: */
+ 0, /* U+011ed7: */
+ 0, /* U+011ed8: */
+ 0, /* U+011ed9: */
+ 0, /* U+011eda: */
+ 0, /* U+011edb: */
+ 0, /* U+011edc: */
+ 0, /* U+011edd: */
+ 0, /* U+011ede: */
+ 0, /* U+011edf: */
+ 0, /* U+011ee0: */
+ 0, /* U+011ee1: */
+ 0, /* U+011ee2: */
+ 0, /* U+011ee3: */
+ 0, /* U+011ee4: */
+ 0, /* U+011ee5: */
+ 0, /* U+011ee6: */
+ 0, /* U+011ee7: */
+ 0, /* U+011ee8: */
+ 0, /* U+011ee9: */
+ 0, /* U+011eea: */
+ 0, /* U+011eeb: */
+ 0, /* U+011eec: */
+ 0, /* U+011eed: */
+ 0, /* U+011eee: */
+ 0, /* U+011eef: */
+ 0, /* U+011ef0: */
+ 0, /* U+011ef1: */
+ 0, /* U+011ef2: */
+ 0, /* U+011ef3: */
+ 0, /* U+011ef4: */
+ 0, /* U+011ef5: */
+ 0, /* U+011ef6: */
+ 0, /* U+011ef7: */
+ 0, /* U+011ef8: */
+ 0, /* U+011ef9: */
+ 0, /* U+011efa: */
+ 0, /* U+011efb: */
+ 0, /* U+011efc: */
+ 0, /* U+011efd: */
+ 0, /* U+011efe: */
+ 0, /* U+011eff: */
+ 0, /* U+011f00: */
+ 0, /* U+011f01: */
+ 0, /* U+011f02: */
+ 0, /* U+011f03: */
+ 0, /* U+011f04: */
+ 0, /* U+011f05: */
+ 0, /* U+011f06: */
+ 0, /* U+011f07: */
+ 0, /* U+011f08: */
+ 0, /* U+011f09: */
+ 0, /* U+011f0a: */
+ 0, /* U+011f0b: */
+ 0, /* U+011f0c: */
+ 0, /* U+011f0d: */
+ 0, /* U+011f0e: */
+ 0, /* U+011f0f: */
+ 0, /* U+011f10: */
+ 0, /* U+011f11: */
+ 0, /* U+011f12: */
+ 0, /* U+011f13: */
+ 0, /* U+011f14: */
+ 0, /* U+011f15: */
+ 0, /* U+011f16: */
+ 0, /* U+011f17: */
+ 0, /* U+011f18: */
+ 0, /* U+011f19: */
+ 0, /* U+011f1a: */
+ 0, /* U+011f1b: */
+ 0, /* U+011f1c: */
+ 0, /* U+011f1d: */
+ 0, /* U+011f1e: */
+ 0, /* U+011f1f: */
+ 0, /* U+011f20: */
+ 0, /* U+011f21: */
+ 0, /* U+011f22: */
+ 0, /* U+011f23: */
+ 0, /* U+011f24: */
+ 0, /* U+011f25: */
+ 0, /* U+011f26: */
+ 0, /* U+011f27: */
+ 0, /* U+011f28: */
+ 0, /* U+011f29: */
+ 0, /* U+011f2a: */
+ 0, /* U+011f2b: */
+ 0, /* U+011f2c: */
+ 0, /* U+011f2d: */
+ 0, /* U+011f2e: */
+ 0, /* U+011f2f: */
+ 0, /* U+011f30: */
+ 0, /* U+011f31: */
+ 0, /* U+011f32: */
+ 0, /* U+011f33: */
+ 0, /* U+011f34: */
+ 0, /* U+011f35: */
+ 0, /* U+011f36: */
+ 0, /* U+011f37: */
+ 0, /* U+011f38: */
+ 0, /* U+011f39: */
+ 0, /* U+011f3a: */
+ 0, /* U+011f3b: */
+ 0, /* U+011f3c: */
+ 0, /* U+011f3d: */
+ 0, /* U+011f3e: */
+ 0, /* U+011f3f: */
+ 0, /* U+011f40: */
+ 0, /* U+011f41: */
+ 0, /* U+011f42: */
+ 0, /* U+011f43: */
+ 0, /* U+011f44: */
+ 0, /* U+011f45: */
+ 0, /* U+011f46: */
+ 0, /* U+011f47: */
+ 0, /* U+011f48: */
+ 0, /* U+011f49: */
+ 0, /* U+011f4a: */
+ 0, /* U+011f4b: */
+ 0, /* U+011f4c: */
+ 0, /* U+011f4d: */
+ 0, /* U+011f4e: */
+ 0, /* U+011f4f: */
+ 0, /* U+011f50: */
+ 0, /* U+011f51: */
+ 0, /* U+011f52: */
+ 0, /* U+011f53: */
+ 0, /* U+011f54: */
+ 0, /* U+011f55: */
+ 0, /* U+011f56: */
+ 0, /* U+011f57: */
+ 0, /* U+011f58: */
+ 0, /* U+011f59: */
+ 0, /* U+011f5a: */
+ 0, /* U+011f5b: */
+ 0, /* U+011f5c: */
+ 0, /* U+011f5d: */
+ 0, /* U+011f5e: */
+ 0, /* U+011f5f: */
+ 0, /* U+011f60: */
+ 0, /* U+011f61: */
+ 0, /* U+011f62: */
+ 0, /* U+011f63: */
+ 0, /* U+011f64: */
+ 0, /* U+011f65: */
+ 0, /* U+011f66: */
+ 0, /* U+011f67: */
+ 0, /* U+011f68: */
+ 0, /* U+011f69: */
+ 0, /* U+011f6a: */
+ 0, /* U+011f6b: */
+ 0, /* U+011f6c: */
+ 0, /* U+011f6d: */
+ 0, /* U+011f6e: */
+ 0, /* U+011f6f: */
+ 0, /* U+011f70: */
+ 0, /* U+011f71: */
+ 0, /* U+011f72: */
+ 0, /* U+011f73: */
+ 0, /* U+011f74: */
+ 0, /* U+011f75: */
+ 0, /* U+011f76: */
+ 0, /* U+011f77: */
+ 0, /* U+011f78: */
+ 0, /* U+011f79: */
+ 0, /* U+011f7a: */
+ 0, /* U+011f7b: */
+ 0, /* U+011f7c: */
+ 0, /* U+011f7d: */
+ 0, /* U+011f7e: */
+ 0, /* U+011f7f: */
+ 0, /* U+011f80: */
+ 0, /* U+011f81: */
+ 0, /* U+011f82: */
+ 0, /* U+011f83: */
+ 0, /* U+011f84: */
+ 0, /* U+011f85: */
+ 0, /* U+011f86: */
+ 0, /* U+011f87: */
+ 0, /* U+011f88: */
+ 0, /* U+011f89: */
+ 0, /* U+011f8a: */
+ 0, /* U+011f8b: */
+ 0, /* U+011f8c: */
+ 0, /* U+011f8d: */
+ 0, /* U+011f8e: */
+ 0, /* U+011f8f: */
+ 0, /* U+011f90: */
+ 0, /* U+011f91: */
+ 0, /* U+011f92: */
+ 0, /* U+011f93: */
+ 0, /* U+011f94: */
+ 0, /* U+011f95: */
+ 0, /* U+011f96: */
+ 0, /* U+011f97: */
+ 0, /* U+011f98: */
+ 0, /* U+011f99: */
+ 0, /* U+011f9a: */
+ 0, /* U+011f9b: */
+ 0, /* U+011f9c: */
+ 0, /* U+011f9d: */
+ 0, /* U+011f9e: */
+ 0, /* U+011f9f: */
+ 0, /* U+011fa0: */
+ 0, /* U+011fa1: */
+ 0, /* U+011fa2: */
+ 0, /* U+011fa3: */
+ 0, /* U+011fa4: */
+ 0, /* U+011fa5: */
+ 0, /* U+011fa6: */
+ 0, /* U+011fa7: */
+ 0, /* U+011fa8: */
+ 0, /* U+011fa9: */
+ 0, /* U+011faa: */
+ 0, /* U+011fab: */
+ 0, /* U+011fac: */
+ 0, /* U+011fad: */
+ 0, /* U+011fae: */
+ 0, /* U+011faf: */
+ 0, /* U+011fb0: */
+ 0, /* U+011fb1: */
+ 0, /* U+011fb2: */
+ 0, /* U+011fb3: */
+ 0, /* U+011fb4: */
+ 0, /* U+011fb5: */
+ 0, /* U+011fb6: */
+ 0, /* U+011fb7: */
+ 0, /* U+011fb8: */
+ 0, /* U+011fb9: */
+ 0, /* U+011fba: */
+ 0, /* U+011fbb: */
+ 0, /* U+011fbc: */
+ 0, /* U+011fbd: */
+ 0, /* U+011fbe: */
+ 0, /* U+011fbf: */
+ 0, /* U+011fc0: */
+ 0, /* U+011fc1: */
+ 0, /* U+011fc2: */
+ 0, /* U+011fc3: */
+ 0, /* U+011fc4: */
+ 0, /* U+011fc5: */
+ 0, /* U+011fc6: */
+ 0, /* U+011fc7: */
+ 0, /* U+011fc8: */
+ 0, /* U+011fc9: */
+ 0, /* U+011fca: */
+ 0, /* U+011fcb: */
+ 0, /* U+011fcc: */
+ 0, /* U+011fcd: */
+ 0, /* U+011fce: */
+ 0, /* U+011fcf: */
+ 0, /* U+011fd0: */
+ 0, /* U+011fd1: */
+ 0, /* U+011fd2: */
+ 0, /* U+011fd3: */
+ 0, /* U+011fd4: */
+ 0, /* U+011fd5: */
+ 0, /* U+011fd6: */
+ 0, /* U+011fd7: */
+ 0, /* U+011fd8: */
+ 0, /* U+011fd9: */
+ 0, /* U+011fda: */
+ 0, /* U+011fdb: */
+ 0, /* U+011fdc: */
+ 0, /* U+011fdd: */
+ 0, /* U+011fde: */
+ 0, /* U+011fdf: */
+ 0, /* U+011fe0: */
+ 0, /* U+011fe1: */
+ 0, /* U+011fe2: */
+ 0, /* U+011fe3: */
+ 0, /* U+011fe4: */
+ 0, /* U+011fe5: */
+ 0, /* U+011fe6: */
+ 0, /* U+011fe7: */
+ 0, /* U+011fe8: */
+ 0, /* U+011fe9: */
+ 0, /* U+011fea: */
+ 0, /* U+011feb: */
+ 0, /* U+011fec: */
+ 0, /* U+011fed: */
+ 0, /* U+011fee: */
+ 0, /* U+011fef: */
+ 0, /* U+011ff0: */
+ 0, /* U+011ff1: */
+ 0, /* U+011ff2: */
+ 0, /* U+011ff3: */
+ 0, /* U+011ff4: */
+ 0, /* U+011ff5: */
+ 0, /* U+011ff6: */
+ 0, /* U+011ff7: */
+ 0, /* U+011ff8: */
+ 0, /* U+011ff9: */
+ 0, /* U+011ffa: */
+ 0, /* U+011ffb: */
+ 0, /* U+011ffc: */
+ 0, /* U+011ffd: */
+ 0, /* U+011ffe: */
+ 0, /* U+011fff: */
+ RTUNI_ALPHA, /* U+012000: CUNEIFORM SIGN A*/
+ RTUNI_ALPHA, /* U+012001: CUNEIFORM SIGN A TIMES A*/
+ RTUNI_ALPHA, /* U+012002: CUNEIFORM SIGN A TIMES BAD*/
+ RTUNI_ALPHA, /* U+012003: CUNEIFORM SIGN A TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+012004: CUNEIFORM SIGN A TIMES HA*/
+ RTUNI_ALPHA, /* U+012005: CUNEIFORM SIGN A TIMES IGI*/
+ RTUNI_ALPHA, /* U+012006: CUNEIFORM SIGN A TIMES LAGAR GUNU*/
+ RTUNI_ALPHA, /* U+012007: CUNEIFORM SIGN A TIMES MUSH*/
+ RTUNI_ALPHA, /* U+012008: CUNEIFORM SIGN A TIMES SAG*/
+ RTUNI_ALPHA, /* U+012009: CUNEIFORM SIGN A2*/
+ RTUNI_ALPHA, /* U+01200a: CUNEIFORM SIGN AB*/
+ RTUNI_ALPHA, /* U+01200b: CUNEIFORM SIGN AB TIMES ASH2*/
+ RTUNI_ALPHA, /* U+01200c: CUNEIFORM SIGN AB TIMES DUN3 GUNU*/
+ RTUNI_ALPHA, /* U+01200d: CUNEIFORM SIGN AB TIMES GAL*/
+ RTUNI_ALPHA, /* U+01200e: CUNEIFORM SIGN AB TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+01200f: CUNEIFORM SIGN AB TIMES HA*/
+ RTUNI_ALPHA, /* U+012010: CUNEIFORM SIGN AB TIMES IGI GUNU*/
+ RTUNI_ALPHA, /* U+012011: CUNEIFORM SIGN AB TIMES IMIN*/
+ RTUNI_ALPHA, /* U+012012: CUNEIFORM SIGN AB TIMES LAGAB*/
+ RTUNI_ALPHA, /* U+012013: CUNEIFORM SIGN AB TIMES SHESH*/
+ RTUNI_ALPHA, /* U+012014: CUNEIFORM SIGN AB TIMES U PLUS U PLUS U*/
+ RTUNI_ALPHA, /* U+012015: CUNEIFORM SIGN AB GUNU*/
+ RTUNI_ALPHA, /* U+012016: CUNEIFORM SIGN AB2*/
+ RTUNI_ALPHA, /* U+012017: CUNEIFORM SIGN AB2 TIMES BALAG*/
+ RTUNI_ALPHA, /* U+012018: CUNEIFORM SIGN AB2 TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+012019: CUNEIFORM SIGN AB2 TIMES ME PLUS EN*/
+ RTUNI_ALPHA, /* U+01201a: CUNEIFORM SIGN AB2 TIMES SHA3*/
+ RTUNI_ALPHA, /* U+01201b: CUNEIFORM SIGN AB2 TIMES TAK4*/
+ RTUNI_ALPHA, /* U+01201c: CUNEIFORM SIGN AD*/
+ RTUNI_ALPHA, /* U+01201d: CUNEIFORM SIGN AK*/
+ RTUNI_ALPHA, /* U+01201e: CUNEIFORM SIGN AK TIMES ERIN2*/
+ RTUNI_ALPHA, /* U+01201f: CUNEIFORM SIGN AK TIMES SHITA PLUS GISH*/
+ RTUNI_ALPHA, /* U+012020: CUNEIFORM SIGN AL*/
+ RTUNI_ALPHA, /* U+012021: CUNEIFORM SIGN AL TIMES AL*/
+ RTUNI_ALPHA, /* U+012022: CUNEIFORM SIGN AL TIMES DIM2*/
+ RTUNI_ALPHA, /* U+012023: CUNEIFORM SIGN AL TIMES GISH*/
+ RTUNI_ALPHA, /* U+012024: CUNEIFORM SIGN AL TIMES HA*/
+ RTUNI_ALPHA, /* U+012025: CUNEIFORM SIGN AL TIMES KAD3*/
+ RTUNI_ALPHA, /* U+012026: CUNEIFORM SIGN AL TIMES KI*/
+ RTUNI_ALPHA, /* U+012027: CUNEIFORM SIGN AL TIMES SHE*/
+ RTUNI_ALPHA, /* U+012028: CUNEIFORM SIGN AL TIMES USH*/
+ RTUNI_ALPHA, /* U+012029: CUNEIFORM SIGN ALAN*/
+ RTUNI_ALPHA, /* U+01202a: CUNEIFORM SIGN ALEPH*/
+ RTUNI_ALPHA, /* U+01202b: CUNEIFORM SIGN AMAR*/
+ RTUNI_ALPHA, /* U+01202c: CUNEIFORM SIGN AMAR TIMES SHE*/
+ RTUNI_ALPHA, /* U+01202d: CUNEIFORM SIGN AN*/
+ RTUNI_ALPHA, /* U+01202e: CUNEIFORM SIGN AN OVER AN*/
+ RTUNI_ALPHA, /* U+01202f: CUNEIFORM SIGN AN THREE TIMES*/
+ RTUNI_ALPHA, /* U+012030: CUNEIFORM SIGN AN PLUS NAGA OPPOSING AN PLUS NAGA*/
+ RTUNI_ALPHA, /* U+012031: CUNEIFORM SIGN AN PLUS NAGA SQUARED*/
+ RTUNI_ALPHA, /* U+012032: CUNEIFORM SIGN ANSHE*/
+ RTUNI_ALPHA, /* U+012033: CUNEIFORM SIGN APIN*/
+ RTUNI_ALPHA, /* U+012034: CUNEIFORM SIGN ARAD*/
+ RTUNI_ALPHA, /* U+012035: CUNEIFORM SIGN ARAD TIMES KUR*/
+ RTUNI_ALPHA, /* U+012036: CUNEIFORM SIGN ARKAB*/
+ RTUNI_ALPHA, /* U+012037: CUNEIFORM SIGN ASAL2*/
+ RTUNI_ALPHA, /* U+012038: CUNEIFORM SIGN ASH*/
+ RTUNI_ALPHA, /* U+012039: CUNEIFORM SIGN ASH ZIDA TENU*/
+ RTUNI_ALPHA, /* U+01203a: CUNEIFORM SIGN ASH KABA TENU*/
+ RTUNI_ALPHA, /* U+01203b: CUNEIFORM SIGN ASH OVER ASH TUG2 OVER TUG2 TUG2 OVER TUG2 PAP*/
+ RTUNI_ALPHA, /* U+01203c: CUNEIFORM SIGN ASH OVER ASH OVER ASH*/
+ RTUNI_ALPHA, /* U+01203d: CUNEIFORM SIGN ASH OVER ASH OVER ASH CROSSING ASH OVER ASH OVER ASH*/
+ RTUNI_ALPHA, /* U+01203e: CUNEIFORM SIGN ASH2*/
+ RTUNI_ALPHA, /* U+01203f: CUNEIFORM SIGN ASHGAB*/
+ RTUNI_ALPHA, /* U+012040: CUNEIFORM SIGN BA*/
+ RTUNI_ALPHA, /* U+012041: CUNEIFORM SIGN BAD*/
+ RTUNI_ALPHA, /* U+012042: CUNEIFORM SIGN BAG3*/
+ RTUNI_ALPHA, /* U+012043: CUNEIFORM SIGN BAHAR2*/
+ RTUNI_ALPHA, /* U+012044: CUNEIFORM SIGN BAL*/
+ RTUNI_ALPHA, /* U+012045: CUNEIFORM SIGN BAL OVER BAL*/
+ RTUNI_ALPHA, /* U+012046: CUNEIFORM SIGN BALAG*/
+ RTUNI_ALPHA, /* U+012047: CUNEIFORM SIGN BAR*/
+ RTUNI_ALPHA, /* U+012048: CUNEIFORM SIGN BARA2*/
+ RTUNI_ALPHA, /* U+012049: CUNEIFORM SIGN BI*/
+ RTUNI_ALPHA, /* U+01204a: CUNEIFORM SIGN BI TIMES A*/
+ RTUNI_ALPHA, /* U+01204b: CUNEIFORM SIGN BI TIMES GAR*/
+ RTUNI_ALPHA, /* U+01204c: CUNEIFORM SIGN BI TIMES IGI GUNU*/
+ RTUNI_ALPHA, /* U+01204d: CUNEIFORM SIGN BU*/
+ RTUNI_ALPHA, /* U+01204e: CUNEIFORM SIGN BU OVER BU AB*/
+ RTUNI_ALPHA, /* U+01204f: CUNEIFORM SIGN BU OVER BU UN*/
+ RTUNI_ALPHA, /* U+012050: CUNEIFORM SIGN BU CROSSING BU*/
+ RTUNI_ALPHA, /* U+012051: CUNEIFORM SIGN BULUG*/
+ RTUNI_ALPHA, /* U+012052: CUNEIFORM SIGN BULUG OVER BULUG*/
+ RTUNI_ALPHA, /* U+012053: CUNEIFORM SIGN BUR*/
+ RTUNI_ALPHA, /* U+012054: CUNEIFORM SIGN BUR2*/
+ RTUNI_ALPHA, /* U+012055: CUNEIFORM SIGN DA*/
+ RTUNI_ALPHA, /* U+012056: CUNEIFORM SIGN DAG*/
+ RTUNI_ALPHA, /* U+012057: CUNEIFORM SIGN DAG KISIM5 TIMES A PLUS MASH*/
+ RTUNI_ALPHA, /* U+012058: CUNEIFORM SIGN DAG KISIM5 TIMES AMAR*/
+ RTUNI_ALPHA, /* U+012059: CUNEIFORM SIGN DAG KISIM5 TIMES BALAG*/
+ RTUNI_ALPHA, /* U+01205a: CUNEIFORM SIGN DAG KISIM5 TIMES BI*/
+ RTUNI_ALPHA, /* U+01205b: CUNEIFORM SIGN DAG KISIM5 TIMES GA*/
+ RTUNI_ALPHA, /* U+01205c: CUNEIFORM SIGN DAG KISIM5 TIMES GA PLUS MASH*/
+ RTUNI_ALPHA, /* U+01205d: CUNEIFORM SIGN DAG KISIM5 TIMES GI*/
+ RTUNI_ALPHA, /* U+01205e: CUNEIFORM SIGN DAG KISIM5 TIMES GIR2*/
+ RTUNI_ALPHA, /* U+01205f: CUNEIFORM SIGN DAG KISIM5 TIMES GUD*/
+ RTUNI_ALPHA, /* U+012060: CUNEIFORM SIGN DAG KISIM5 TIMES HA*/
+ RTUNI_ALPHA, /* U+012061: CUNEIFORM SIGN DAG KISIM5 TIMES IR*/
+ RTUNI_ALPHA, /* U+012062: CUNEIFORM SIGN DAG KISIM5 TIMES IR PLUS LU*/
+ RTUNI_ALPHA, /* U+012063: CUNEIFORM SIGN DAG KISIM5 TIMES KAK*/
+ RTUNI_ALPHA, /* U+012064: CUNEIFORM SIGN DAG KISIM5 TIMES LA*/
+ RTUNI_ALPHA, /* U+012065: CUNEIFORM SIGN DAG KISIM5 TIMES LU*/
+ RTUNI_ALPHA, /* U+012066: CUNEIFORM SIGN DAG KISIM5 TIMES LU PLUS MASH2*/
+ RTUNI_ALPHA, /* U+012067: CUNEIFORM SIGN DAG KISIM5 TIMES LUM*/
+ RTUNI_ALPHA, /* U+012068: CUNEIFORM SIGN DAG KISIM5 TIMES NE*/
+ RTUNI_ALPHA, /* U+012069: CUNEIFORM SIGN DAG KISIM5 TIMES PAP PLUS PAP*/
+ RTUNI_ALPHA, /* U+01206a: CUNEIFORM SIGN DAG KISIM5 TIMES SI*/
+ RTUNI_ALPHA, /* U+01206b: CUNEIFORM SIGN DAG KISIM5 TIMES TAK4*/
+ RTUNI_ALPHA, /* U+01206c: CUNEIFORM SIGN DAG KISIM5 TIMES U2 PLUS GIR2*/
+ RTUNI_ALPHA, /* U+01206d: CUNEIFORM SIGN DAG KISIM5 TIMES USH*/
+ RTUNI_ALPHA, /* U+01206e: CUNEIFORM SIGN DAM*/
+ RTUNI_ALPHA, /* U+01206f: CUNEIFORM SIGN DAR*/
+ RTUNI_ALPHA, /* U+012070: CUNEIFORM SIGN DARA3*/
+ RTUNI_ALPHA, /* U+012071: CUNEIFORM SIGN DARA4*/
+ RTUNI_ALPHA, /* U+012072: CUNEIFORM SIGN DI*/
+ RTUNI_ALPHA, /* U+012073: CUNEIFORM SIGN DIB*/
+ RTUNI_ALPHA, /* U+012074: CUNEIFORM SIGN DIM*/
+ RTUNI_ALPHA, /* U+012075: CUNEIFORM SIGN DIM TIMES SHE*/
+ RTUNI_ALPHA, /* U+012076: CUNEIFORM SIGN DIM2*/
+ RTUNI_ALPHA, /* U+012077: CUNEIFORM SIGN DIN*/
+ RTUNI_ALPHA, /* U+012078: CUNEIFORM SIGN DIN KASKAL U GUNU DISH*/
+ RTUNI_ALPHA, /* U+012079: CUNEIFORM SIGN DISH*/
+ RTUNI_ALPHA, /* U+01207a: CUNEIFORM SIGN DU*/
+ RTUNI_ALPHA, /* U+01207b: CUNEIFORM SIGN DU OVER DU*/
+ RTUNI_ALPHA, /* U+01207c: CUNEIFORM SIGN DU GUNU*/
+ RTUNI_ALPHA, /* U+01207d: CUNEIFORM SIGN DU SHESHIG*/
+ RTUNI_ALPHA, /* U+01207e: CUNEIFORM SIGN DUB*/
+ RTUNI_ALPHA, /* U+01207f: CUNEIFORM SIGN DUB TIMES ESH2*/
+ RTUNI_ALPHA, /* U+012080: CUNEIFORM SIGN DUB2*/
+ RTUNI_ALPHA, /* U+012081: CUNEIFORM SIGN DUG*/
+ RTUNI_ALPHA, /* U+012082: CUNEIFORM SIGN DUGUD*/
+ RTUNI_ALPHA, /* U+012083: CUNEIFORM SIGN DUH*/
+ RTUNI_ALPHA, /* U+012084: CUNEIFORM SIGN DUN*/
+ RTUNI_ALPHA, /* U+012085: CUNEIFORM SIGN DUN3*/
+ RTUNI_ALPHA, /* U+012086: CUNEIFORM SIGN DUN3 GUNU*/
+ RTUNI_ALPHA, /* U+012087: CUNEIFORM SIGN DUN3 GUNU GUNU*/
+ RTUNI_ALPHA, /* U+012088: CUNEIFORM SIGN DUN4*/
+ RTUNI_ALPHA, /* U+012089: CUNEIFORM SIGN DUR2*/
+ RTUNI_ALPHA, /* U+01208a: CUNEIFORM SIGN E*/
+ RTUNI_ALPHA, /* U+01208b: CUNEIFORM SIGN E TIMES PAP*/
+ RTUNI_ALPHA, /* U+01208c: CUNEIFORM SIGN E OVER E NUN OVER NUN*/
+ RTUNI_ALPHA, /* U+01208d: CUNEIFORM SIGN E2*/
+ RTUNI_ALPHA, /* U+01208e: CUNEIFORM SIGN E2 TIMES A PLUS HA PLUS DA*/
+ RTUNI_ALPHA, /* U+01208f: CUNEIFORM SIGN E2 TIMES GAR*/
+ RTUNI_ALPHA, /* U+012090: CUNEIFORM SIGN E2 TIMES MI*/
+ RTUNI_ALPHA, /* U+012091: CUNEIFORM SIGN E2 TIMES SAL*/
+ RTUNI_ALPHA, /* U+012092: CUNEIFORM SIGN E2 TIMES SHE*/
+ RTUNI_ALPHA, /* U+012093: CUNEIFORM SIGN E2 TIMES U*/
+ RTUNI_ALPHA, /* U+012094: CUNEIFORM SIGN EDIN*/
+ RTUNI_ALPHA, /* U+012095: CUNEIFORM SIGN EGIR*/
+ RTUNI_ALPHA, /* U+012096: CUNEIFORM SIGN EL*/
+ RTUNI_ALPHA, /* U+012097: CUNEIFORM SIGN EN*/
+ RTUNI_ALPHA, /* U+012098: CUNEIFORM SIGN EN TIMES GAN2*/
+ RTUNI_ALPHA, /* U+012099: CUNEIFORM SIGN EN TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+01209a: CUNEIFORM SIGN EN TIMES ME*/
+ RTUNI_ALPHA, /* U+01209b: CUNEIFORM SIGN EN CROSSING EN*/
+ RTUNI_ALPHA, /* U+01209c: CUNEIFORM SIGN EN OPPOSING EN*/
+ RTUNI_ALPHA, /* U+01209d: CUNEIFORM SIGN EN SQUARED*/
+ RTUNI_ALPHA, /* U+01209e: CUNEIFORM SIGN EREN*/
+ RTUNI_ALPHA, /* U+01209f: CUNEIFORM SIGN ERIN2*/
+ RTUNI_ALPHA, /* U+0120a0: CUNEIFORM SIGN ESH2*/
+ RTUNI_ALPHA, /* U+0120a1: CUNEIFORM SIGN EZEN*/
+ RTUNI_ALPHA, /* U+0120a2: CUNEIFORM SIGN EZEN TIMES A*/
+ RTUNI_ALPHA, /* U+0120a3: CUNEIFORM SIGN EZEN TIMES A PLUS LAL*/
+ RTUNI_ALPHA, /* U+0120a4: CUNEIFORM SIGN EZEN TIMES A PLUS LAL TIMES LAL*/
+ RTUNI_ALPHA, /* U+0120a5: CUNEIFORM SIGN EZEN TIMES AN*/
+ RTUNI_ALPHA, /* U+0120a6: CUNEIFORM SIGN EZEN TIMES BAD*/
+ RTUNI_ALPHA, /* U+0120a7: CUNEIFORM SIGN EZEN TIMES DUN3 GUNU*/
+ RTUNI_ALPHA, /* U+0120a8: CUNEIFORM SIGN EZEN TIMES DUN3 GUNU GUNU*/
+ RTUNI_ALPHA, /* U+0120a9: CUNEIFORM SIGN EZEN TIMES HA*/
+ RTUNI_ALPHA, /* U+0120aa: CUNEIFORM SIGN EZEN TIMES HA GUNU*/
+ RTUNI_ALPHA, /* U+0120ab: CUNEIFORM SIGN EZEN TIMES IGI GUNU*/
+ RTUNI_ALPHA, /* U+0120ac: CUNEIFORM SIGN EZEN TIMES KASKAL*/
+ RTUNI_ALPHA, /* U+0120ad: CUNEIFORM SIGN EZEN TIMES KASKAL SQUARED*/
+ RTUNI_ALPHA, /* U+0120ae: CUNEIFORM SIGN EZEN TIMES KU3*/
+ RTUNI_ALPHA, /* U+0120af: CUNEIFORM SIGN EZEN TIMES LA*/
+ RTUNI_ALPHA, /* U+0120b0: CUNEIFORM SIGN EZEN TIMES LAL TIMES LAL*/
+ RTUNI_ALPHA, /* U+0120b1: CUNEIFORM SIGN EZEN TIMES LI*/
+ RTUNI_ALPHA, /* U+0120b2: CUNEIFORM SIGN EZEN TIMES LU*/
+ RTUNI_ALPHA, /* U+0120b3: CUNEIFORM SIGN EZEN TIMES U2*/
+ RTUNI_ALPHA, /* U+0120b4: CUNEIFORM SIGN EZEN TIMES UD*/
+ RTUNI_ALPHA, /* U+0120b5: CUNEIFORM SIGN GA*/
+ RTUNI_ALPHA, /* U+0120b6: CUNEIFORM SIGN GA GUNU*/
+ RTUNI_ALPHA, /* U+0120b7: CUNEIFORM SIGN GA2*/
+ RTUNI_ALPHA, /* U+0120b8: CUNEIFORM SIGN GA2 TIMES A PLUS DA PLUS HA*/
+ RTUNI_ALPHA, /* U+0120b9: CUNEIFORM SIGN GA2 TIMES A PLUS HA*/
+ RTUNI_ALPHA, /* U+0120ba: CUNEIFORM SIGN GA2 TIMES A PLUS IGI*/
+ RTUNI_ALPHA, /* U+0120bb: CUNEIFORM SIGN GA2 TIMES AB2 TENU PLUS TAB*/
+ RTUNI_ALPHA, /* U+0120bc: CUNEIFORM SIGN GA2 TIMES AN*/
+ RTUNI_ALPHA, /* U+0120bd: CUNEIFORM SIGN GA2 TIMES ASH*/
+ RTUNI_ALPHA, /* U+0120be: CUNEIFORM SIGN GA2 TIMES ASH2 PLUS GAL*/
+ RTUNI_ALPHA, /* U+0120bf: CUNEIFORM SIGN GA2 TIMES BAD*/
+ RTUNI_ALPHA, /* U+0120c0: CUNEIFORM SIGN GA2 TIMES BAR PLUS RA*/
+ RTUNI_ALPHA, /* U+0120c1: CUNEIFORM SIGN GA2 TIMES BUR*/
+ RTUNI_ALPHA, /* U+0120c2: CUNEIFORM SIGN GA2 TIMES BUR PLUS RA*/
+ RTUNI_ALPHA, /* U+0120c3: CUNEIFORM SIGN GA2 TIMES DA*/
+ RTUNI_ALPHA, /* U+0120c4: CUNEIFORM SIGN GA2 TIMES DI*/
+ RTUNI_ALPHA, /* U+0120c5: CUNEIFORM SIGN GA2 TIMES DIM TIMES SHE*/
+ RTUNI_ALPHA, /* U+0120c6: CUNEIFORM SIGN GA2 TIMES DUB*/
+ RTUNI_ALPHA, /* U+0120c7: CUNEIFORM SIGN GA2 TIMES EL*/
+ RTUNI_ALPHA, /* U+0120c8: CUNEIFORM SIGN GA2 TIMES EL PLUS LA*/
+ RTUNI_ALPHA, /* U+0120c9: CUNEIFORM SIGN GA2 TIMES EN*/
+ RTUNI_ALPHA, /* U+0120ca: CUNEIFORM SIGN GA2 TIMES EN TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+0120cb: CUNEIFORM SIGN GA2 TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+0120cc: CUNEIFORM SIGN GA2 TIMES GAR*/
+ RTUNI_ALPHA, /* U+0120cd: CUNEIFORM SIGN GA2 TIMES GI*/
+ RTUNI_ALPHA, /* U+0120ce: CUNEIFORM SIGN GA2 TIMES GI4*/
+ RTUNI_ALPHA, /* U+0120cf: CUNEIFORM SIGN GA2 TIMES GI4 PLUS A*/
+ RTUNI_ALPHA, /* U+0120d0: CUNEIFORM SIGN GA2 TIMES GIR2 PLUS SU*/
+ RTUNI_ALPHA, /* U+0120d1: CUNEIFORM SIGN GA2 TIMES HA PLUS LU PLUS ESH2*/
+ RTUNI_ALPHA, /* U+0120d2: CUNEIFORM SIGN GA2 TIMES HAL*/
+ RTUNI_ALPHA, /* U+0120d3: CUNEIFORM SIGN GA2 TIMES HAL PLUS LA*/
+ RTUNI_ALPHA, /* U+0120d4: CUNEIFORM SIGN GA2 TIMES HI PLUS LI*/
+ RTUNI_ALPHA, /* U+0120d5: CUNEIFORM SIGN GA2 TIMES HUB2*/
+ RTUNI_ALPHA, /* U+0120d6: CUNEIFORM SIGN GA2 TIMES IGI GUNU*/
+ RTUNI_ALPHA, /* U+0120d7: CUNEIFORM SIGN GA2 TIMES ISH PLUS HU PLUS ASH*/
+ RTUNI_ALPHA, /* U+0120d8: CUNEIFORM SIGN GA2 TIMES KAK*/
+ RTUNI_ALPHA, /* U+0120d9: CUNEIFORM SIGN GA2 TIMES KASKAL*/
+ RTUNI_ALPHA, /* U+0120da: CUNEIFORM SIGN GA2 TIMES KID*/
+ RTUNI_ALPHA, /* U+0120db: CUNEIFORM SIGN GA2 TIMES KID PLUS LAL*/
+ RTUNI_ALPHA, /* U+0120dc: CUNEIFORM SIGN GA2 TIMES KU3 PLUS AN*/
+ RTUNI_ALPHA, /* U+0120dd: CUNEIFORM SIGN GA2 TIMES LA*/
+ RTUNI_ALPHA, /* U+0120de: CUNEIFORM SIGN GA2 TIMES ME PLUS EN*/
+ RTUNI_ALPHA, /* U+0120df: CUNEIFORM SIGN GA2 TIMES MI*/
+ RTUNI_ALPHA, /* U+0120e0: CUNEIFORM SIGN GA2 TIMES NUN*/
+ RTUNI_ALPHA, /* U+0120e1: CUNEIFORM SIGN GA2 TIMES NUN OVER NUN*/
+ RTUNI_ALPHA, /* U+0120e2: CUNEIFORM SIGN GA2 TIMES PA*/
+ RTUNI_ALPHA, /* U+0120e3: CUNEIFORM SIGN GA2 TIMES SAL*/
+ RTUNI_ALPHA, /* U+0120e4: CUNEIFORM SIGN GA2 TIMES SAR*/
+ RTUNI_ALPHA, /* U+0120e5: CUNEIFORM SIGN GA2 TIMES SHE*/
+ RTUNI_ALPHA, /* U+0120e6: CUNEIFORM SIGN GA2 TIMES SHE PLUS TUR*/
+ RTUNI_ALPHA, /* U+0120e7: CUNEIFORM SIGN GA2 TIMES SHID*/
+ RTUNI_ALPHA, /* U+0120e8: CUNEIFORM SIGN GA2 TIMES SUM*/
+ RTUNI_ALPHA, /* U+0120e9: CUNEIFORM SIGN GA2 TIMES TAK4*/
+ RTUNI_ALPHA, /* U+0120ea: CUNEIFORM SIGN GA2 TIMES U*/
+ RTUNI_ALPHA, /* U+0120eb: CUNEIFORM SIGN GA2 TIMES UD*/
+ RTUNI_ALPHA, /* U+0120ec: CUNEIFORM SIGN GA2 TIMES UD PLUS DU*/
+ RTUNI_ALPHA, /* U+0120ed: CUNEIFORM SIGN GA2 OVER GA2*/
+ RTUNI_ALPHA, /* U+0120ee: CUNEIFORM SIGN GABA*/
+ RTUNI_ALPHA, /* U+0120ef: CUNEIFORM SIGN GABA CROSSING GABA*/
+ RTUNI_ALPHA, /* U+0120f0: CUNEIFORM SIGN GAD*/
+ RTUNI_ALPHA, /* U+0120f1: CUNEIFORM SIGN GAD OVER GAD GAR OVER GAR*/
+ RTUNI_ALPHA, /* U+0120f2: CUNEIFORM SIGN GAL*/
+ RTUNI_ALPHA, /* U+0120f3: CUNEIFORM SIGN GAL GAD OVER GAD GAR OVER GAR*/
+ RTUNI_ALPHA, /* U+0120f4: CUNEIFORM SIGN GALAM*/
+ RTUNI_ALPHA, /* U+0120f5: CUNEIFORM SIGN GAM*/
+ RTUNI_ALPHA, /* U+0120f6: CUNEIFORM SIGN GAN*/
+ RTUNI_ALPHA, /* U+0120f7: CUNEIFORM SIGN GAN2*/
+ RTUNI_ALPHA, /* U+0120f8: CUNEIFORM SIGN GAN2 TENU*/
+ RTUNI_ALPHA, /* U+0120f9: CUNEIFORM SIGN GAN2 OVER GAN2*/
+ RTUNI_ALPHA, /* U+0120fa: CUNEIFORM SIGN GAN2 CROSSING GAN2*/
+ RTUNI_ALPHA, /* U+0120fb: CUNEIFORM SIGN GAR*/
+ RTUNI_ALPHA, /* U+0120fc: CUNEIFORM SIGN GAR3*/
+ RTUNI_ALPHA, /* U+0120fd: CUNEIFORM SIGN GASHAN*/
+ RTUNI_ALPHA, /* U+0120fe: CUNEIFORM SIGN GESHTIN*/
+ RTUNI_ALPHA, /* U+0120ff: CUNEIFORM SIGN GESHTIN TIMES KUR*/
+ RTUNI_ALPHA, /* U+012100: CUNEIFORM SIGN GI*/
+ RTUNI_ALPHA, /* U+012101: CUNEIFORM SIGN GI TIMES E*/
+ RTUNI_ALPHA, /* U+012102: CUNEIFORM SIGN GI TIMES U*/
+ RTUNI_ALPHA, /* U+012103: CUNEIFORM SIGN GI CROSSING GI*/
+ RTUNI_ALPHA, /* U+012104: CUNEIFORM SIGN GI4*/
+ RTUNI_ALPHA, /* U+012105: CUNEIFORM SIGN GI4 OVER GI4*/
+ RTUNI_ALPHA, /* U+012106: CUNEIFORM SIGN GI4 CROSSING GI4*/
+ RTUNI_ALPHA, /* U+012107: CUNEIFORM SIGN GIDIM*/
+ RTUNI_ALPHA, /* U+012108: CUNEIFORM SIGN GIR2*/
+ RTUNI_ALPHA, /* U+012109: CUNEIFORM SIGN GIR2 GUNU*/
+ RTUNI_ALPHA, /* U+01210a: CUNEIFORM SIGN GIR3*/
+ RTUNI_ALPHA, /* U+01210b: CUNEIFORM SIGN GIR3 TIMES A PLUS IGI*/
+ RTUNI_ALPHA, /* U+01210c: CUNEIFORM SIGN GIR3 TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+01210d: CUNEIFORM SIGN GIR3 TIMES IGI*/
+ RTUNI_ALPHA, /* U+01210e: CUNEIFORM SIGN GIR3 TIMES LU PLUS IGI*/
+ RTUNI_ALPHA, /* U+01210f: CUNEIFORM SIGN GIR3 TIMES PA*/
+ RTUNI_ALPHA, /* U+012110: CUNEIFORM SIGN GISAL*/
+ RTUNI_ALPHA, /* U+012111: CUNEIFORM SIGN GISH*/
+ RTUNI_ALPHA, /* U+012112: CUNEIFORM SIGN GISH CROSSING GISH*/
+ RTUNI_ALPHA, /* U+012113: CUNEIFORM SIGN GISH TIMES BAD*/
+ RTUNI_ALPHA, /* U+012114: CUNEIFORM SIGN GISH TIMES TAK4*/
+ RTUNI_ALPHA, /* U+012115: CUNEIFORM SIGN GISH TENU*/
+ RTUNI_ALPHA, /* U+012116: CUNEIFORM SIGN GU*/
+ RTUNI_ALPHA, /* U+012117: CUNEIFORM SIGN GU CROSSING GU*/
+ RTUNI_ALPHA, /* U+012118: CUNEIFORM SIGN GU2*/
+ RTUNI_ALPHA, /* U+012119: CUNEIFORM SIGN GU2 TIMES KAK*/
+ RTUNI_ALPHA, /* U+01211a: CUNEIFORM SIGN GU2 TIMES KAK TIMES IGI GUNU*/
+ RTUNI_ALPHA, /* U+01211b: CUNEIFORM SIGN GU2 TIMES NUN*/
+ RTUNI_ALPHA, /* U+01211c: CUNEIFORM SIGN GU2 TIMES SAL PLUS TUG2*/
+ RTUNI_ALPHA, /* U+01211d: CUNEIFORM SIGN GU2 GUNU*/
+ RTUNI_ALPHA, /* U+01211e: CUNEIFORM SIGN GUD*/
+ RTUNI_ALPHA, /* U+01211f: CUNEIFORM SIGN GUD TIMES A PLUS KUR*/
+ RTUNI_ALPHA, /* U+012120: CUNEIFORM SIGN GUD TIMES KUR*/
+ RTUNI_ALPHA, /* U+012121: CUNEIFORM SIGN GUD OVER GUD LUGAL*/
+ RTUNI_ALPHA, /* U+012122: CUNEIFORM SIGN GUL*/
+ RTUNI_ALPHA, /* U+012123: CUNEIFORM SIGN GUM*/
+ RTUNI_ALPHA, /* U+012124: CUNEIFORM SIGN GUM TIMES SHE*/
+ RTUNI_ALPHA, /* U+012125: CUNEIFORM SIGN GUR*/
+ RTUNI_ALPHA, /* U+012126: CUNEIFORM SIGN GUR7*/
+ RTUNI_ALPHA, /* U+012127: CUNEIFORM SIGN GURUN*/
+ RTUNI_ALPHA, /* U+012128: CUNEIFORM SIGN GURUSH*/
+ RTUNI_ALPHA, /* U+012129: CUNEIFORM SIGN HA*/
+ RTUNI_ALPHA, /* U+01212a: CUNEIFORM SIGN HA TENU*/
+ RTUNI_ALPHA, /* U+01212b: CUNEIFORM SIGN HA GUNU*/
+ RTUNI_ALPHA, /* U+01212c: CUNEIFORM SIGN HAL*/
+ RTUNI_ALPHA, /* U+01212d: CUNEIFORM SIGN HI*/
+ RTUNI_ALPHA, /* U+01212e: CUNEIFORM SIGN HI TIMES ASH*/
+ RTUNI_ALPHA, /* U+01212f: CUNEIFORM SIGN HI TIMES ASH2*/
+ RTUNI_ALPHA, /* U+012130: CUNEIFORM SIGN HI TIMES BAD*/
+ RTUNI_ALPHA, /* U+012131: CUNEIFORM SIGN HI TIMES DISH*/
+ RTUNI_ALPHA, /* U+012132: CUNEIFORM SIGN HI TIMES GAD*/
+ RTUNI_ALPHA, /* U+012133: CUNEIFORM SIGN HI TIMES KIN*/
+ RTUNI_ALPHA, /* U+012134: CUNEIFORM SIGN HI TIMES NUN*/
+ RTUNI_ALPHA, /* U+012135: CUNEIFORM SIGN HI TIMES SHE*/
+ RTUNI_ALPHA, /* U+012136: CUNEIFORM SIGN HI TIMES U*/
+ RTUNI_ALPHA, /* U+012137: CUNEIFORM SIGN HU*/
+ RTUNI_ALPHA, /* U+012138: CUNEIFORM SIGN HUB2*/
+ RTUNI_ALPHA, /* U+012139: CUNEIFORM SIGN HUB2 TIMES AN*/
+ RTUNI_ALPHA, /* U+01213a: CUNEIFORM SIGN HUB2 TIMES HAL*/
+ RTUNI_ALPHA, /* U+01213b: CUNEIFORM SIGN HUB2 TIMES KASKAL*/
+ RTUNI_ALPHA, /* U+01213c: CUNEIFORM SIGN HUB2 TIMES LISH*/
+ RTUNI_ALPHA, /* U+01213d: CUNEIFORM SIGN HUB2 TIMES UD*/
+ RTUNI_ALPHA, /* U+01213e: CUNEIFORM SIGN HUL2*/
+ RTUNI_ALPHA, /* U+01213f: CUNEIFORM SIGN I*/
+ RTUNI_ALPHA, /* U+012140: CUNEIFORM SIGN I A*/
+ RTUNI_ALPHA, /* U+012141: CUNEIFORM SIGN IB*/
+ RTUNI_ALPHA, /* U+012142: CUNEIFORM SIGN IDIM*/
+ RTUNI_ALPHA, /* U+012143: CUNEIFORM SIGN IDIM OVER IDIM BUR*/
+ RTUNI_ALPHA, /* U+012144: CUNEIFORM SIGN IDIM OVER IDIM SQUARED*/
+ RTUNI_ALPHA, /* U+012145: CUNEIFORM SIGN IG*/
+ RTUNI_ALPHA, /* U+012146: CUNEIFORM SIGN IGI*/
+ RTUNI_ALPHA, /* U+012147: CUNEIFORM SIGN IGI DIB*/
+ RTUNI_ALPHA, /* U+012148: CUNEIFORM SIGN IGI RI*/
+ RTUNI_ALPHA, /* U+012149: CUNEIFORM SIGN IGI OVER IGI SHIR OVER SHIR UD OVER UD*/
+ RTUNI_ALPHA, /* U+01214a: CUNEIFORM SIGN IGI GUNU*/
+ RTUNI_ALPHA, /* U+01214b: CUNEIFORM SIGN IL*/
+ RTUNI_ALPHA, /* U+01214c: CUNEIFORM SIGN IL TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+01214d: CUNEIFORM SIGN IL2*/
+ RTUNI_ALPHA, /* U+01214e: CUNEIFORM SIGN IM*/
+ RTUNI_ALPHA, /* U+01214f: CUNEIFORM SIGN IM TIMES TAK4*/
+ RTUNI_ALPHA, /* U+012150: CUNEIFORM SIGN IM CROSSING IM*/
+ RTUNI_ALPHA, /* U+012151: CUNEIFORM SIGN IM OPPOSING IM*/
+ RTUNI_ALPHA, /* U+012152: CUNEIFORM SIGN IM SQUARED*/
+ RTUNI_ALPHA, /* U+012153: CUNEIFORM SIGN IMIN*/
+ RTUNI_ALPHA, /* U+012154: CUNEIFORM SIGN IN*/
+ RTUNI_ALPHA, /* U+012155: CUNEIFORM SIGN IR*/
+ RTUNI_ALPHA, /* U+012156: CUNEIFORM SIGN ISH*/
+ RTUNI_ALPHA, /* U+012157: CUNEIFORM SIGN KA*/
+ RTUNI_ALPHA, /* U+012158: CUNEIFORM SIGN KA TIMES A*/
+ RTUNI_ALPHA, /* U+012159: CUNEIFORM SIGN KA TIMES AD*/
+ RTUNI_ALPHA, /* U+01215a: CUNEIFORM SIGN KA TIMES AD PLUS KU3*/
+ RTUNI_ALPHA, /* U+01215b: CUNEIFORM SIGN KA TIMES ASH2*/
+ RTUNI_ALPHA, /* U+01215c: CUNEIFORM SIGN KA TIMES BAD*/
+ RTUNI_ALPHA, /* U+01215d: CUNEIFORM SIGN KA TIMES BALAG*/
+ RTUNI_ALPHA, /* U+01215e: CUNEIFORM SIGN KA TIMES BAR*/
+ RTUNI_ALPHA, /* U+01215f: CUNEIFORM SIGN KA TIMES BI*/
+ RTUNI_ALPHA, /* U+012160: CUNEIFORM SIGN KA TIMES ERIN2*/
+ RTUNI_ALPHA, /* U+012161: CUNEIFORM SIGN KA TIMES ESH2*/
+ RTUNI_ALPHA, /* U+012162: CUNEIFORM SIGN KA TIMES GA*/
+ RTUNI_ALPHA, /* U+012163: CUNEIFORM SIGN KA TIMES GAL*/
+ RTUNI_ALPHA, /* U+012164: CUNEIFORM SIGN KA TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+012165: CUNEIFORM SIGN KA TIMES GAR*/
+ RTUNI_ALPHA, /* U+012166: CUNEIFORM SIGN KA TIMES GAR PLUS SHA3 PLUS A*/
+ RTUNI_ALPHA, /* U+012167: CUNEIFORM SIGN KA TIMES GI*/
+ RTUNI_ALPHA, /* U+012168: CUNEIFORM SIGN KA TIMES GIR2*/
+ RTUNI_ALPHA, /* U+012169: CUNEIFORM SIGN KA TIMES GISH PLUS SAR*/
+ RTUNI_ALPHA, /* U+01216a: CUNEIFORM SIGN KA TIMES GISH CROSSING GISH*/
+ RTUNI_ALPHA, /* U+01216b: CUNEIFORM SIGN KA TIMES GU*/
+ RTUNI_ALPHA, /* U+01216c: CUNEIFORM SIGN KA TIMES GUR7*/
+ RTUNI_ALPHA, /* U+01216d: CUNEIFORM SIGN KA TIMES IGI*/
+ RTUNI_ALPHA, /* U+01216e: CUNEIFORM SIGN KA TIMES IM*/
+ RTUNI_ALPHA, /* U+01216f: CUNEIFORM SIGN KA TIMES KAK*/
+ RTUNI_ALPHA, /* U+012170: CUNEIFORM SIGN KA TIMES KI*/
+ RTUNI_ALPHA, /* U+012171: CUNEIFORM SIGN KA TIMES KID*/
+ RTUNI_ALPHA, /* U+012172: CUNEIFORM SIGN KA TIMES LI*/
+ RTUNI_ALPHA, /* U+012173: CUNEIFORM SIGN KA TIMES LU*/
+ RTUNI_ALPHA, /* U+012174: CUNEIFORM SIGN KA TIMES ME*/
+ RTUNI_ALPHA, /* U+012175: CUNEIFORM SIGN KA TIMES ME PLUS DU*/
+ RTUNI_ALPHA, /* U+012176: CUNEIFORM SIGN KA TIMES ME PLUS GI*/
+ RTUNI_ALPHA, /* U+012177: CUNEIFORM SIGN KA TIMES ME PLUS TE*/
+ RTUNI_ALPHA, /* U+012178: CUNEIFORM SIGN KA TIMES MI*/
+ RTUNI_ALPHA, /* U+012179: CUNEIFORM SIGN KA TIMES MI PLUS NUNUZ*/
+ RTUNI_ALPHA, /* U+01217a: CUNEIFORM SIGN KA TIMES NE*/
+ RTUNI_ALPHA, /* U+01217b: CUNEIFORM SIGN KA TIMES NUN*/
+ RTUNI_ALPHA, /* U+01217c: CUNEIFORM SIGN KA TIMES PI*/
+ RTUNI_ALPHA, /* U+01217d: CUNEIFORM SIGN KA TIMES RU*/
+ RTUNI_ALPHA, /* U+01217e: CUNEIFORM SIGN KA TIMES SA*/
+ RTUNI_ALPHA, /* U+01217f: CUNEIFORM SIGN KA TIMES SAR*/
+ RTUNI_ALPHA, /* U+012180: CUNEIFORM SIGN KA TIMES SHA*/
+ RTUNI_ALPHA, /* U+012181: CUNEIFORM SIGN KA TIMES SHE*/
+ RTUNI_ALPHA, /* U+012182: CUNEIFORM SIGN KA TIMES SHID*/
+ RTUNI_ALPHA, /* U+012183: CUNEIFORM SIGN KA TIMES SHU*/
+ RTUNI_ALPHA, /* U+012184: CUNEIFORM SIGN KA TIMES SIG*/
+ RTUNI_ALPHA, /* U+012185: CUNEIFORM SIGN KA TIMES SUHUR*/
+ RTUNI_ALPHA, /* U+012186: CUNEIFORM SIGN KA TIMES TAR*/
+ RTUNI_ALPHA, /* U+012187: CUNEIFORM SIGN KA TIMES U*/
+ RTUNI_ALPHA, /* U+012188: CUNEIFORM SIGN KA TIMES U2*/
+ RTUNI_ALPHA, /* U+012189: CUNEIFORM SIGN KA TIMES UD*/
+ RTUNI_ALPHA, /* U+01218a: CUNEIFORM SIGN KA TIMES UMUM TIMES PA*/
+ RTUNI_ALPHA, /* U+01218b: CUNEIFORM SIGN KA TIMES USH*/
+ RTUNI_ALPHA, /* U+01218c: CUNEIFORM SIGN KA TIMES ZI*/
+ RTUNI_ALPHA, /* U+01218d: CUNEIFORM SIGN KA2*/
+ RTUNI_ALPHA, /* U+01218e: CUNEIFORM SIGN KA2 CROSSING KA2*/
+ RTUNI_ALPHA, /* U+01218f: CUNEIFORM SIGN KAB*/
+ RTUNI_ALPHA, /* U+012190: CUNEIFORM SIGN KAD2*/
+ RTUNI_ALPHA, /* U+012191: CUNEIFORM SIGN KAD3*/
+ RTUNI_ALPHA, /* U+012192: CUNEIFORM SIGN KAD4*/
+ RTUNI_ALPHA, /* U+012193: CUNEIFORM SIGN KAD5*/
+ RTUNI_ALPHA, /* U+012194: CUNEIFORM SIGN KAD5 OVER KAD5*/
+ RTUNI_ALPHA, /* U+012195: CUNEIFORM SIGN KAK*/
+ RTUNI_ALPHA, /* U+012196: CUNEIFORM SIGN KAK TIMES IGI GUNU*/
+ RTUNI_ALPHA, /* U+012197: CUNEIFORM SIGN KAL*/
+ RTUNI_ALPHA, /* U+012198: CUNEIFORM SIGN KAL TIMES BAD*/
+ RTUNI_ALPHA, /* U+012199: CUNEIFORM SIGN KAL CROSSING KAL*/
+ RTUNI_ALPHA, /* U+01219a: CUNEIFORM SIGN KAM2*/
+ RTUNI_ALPHA, /* U+01219b: CUNEIFORM SIGN KAM4*/
+ RTUNI_ALPHA, /* U+01219c: CUNEIFORM SIGN KASKAL*/
+ RTUNI_ALPHA, /* U+01219d: CUNEIFORM SIGN KASKAL LAGAB TIMES U OVER LAGAB TIMES U*/
+ RTUNI_ALPHA, /* U+01219e: CUNEIFORM SIGN KASKAL OVER KASKAL LAGAB TIMES U OVER LAGAB TIMES U*/
+ RTUNI_ALPHA, /* U+01219f: CUNEIFORM SIGN KESH2*/
+ RTUNI_ALPHA, /* U+0121a0: CUNEIFORM SIGN KI*/
+ RTUNI_ALPHA, /* U+0121a1: CUNEIFORM SIGN KI TIMES BAD*/
+ RTUNI_ALPHA, /* U+0121a2: CUNEIFORM SIGN KI TIMES U*/
+ RTUNI_ALPHA, /* U+0121a3: CUNEIFORM SIGN KI TIMES UD*/
+ RTUNI_ALPHA, /* U+0121a4: CUNEIFORM SIGN KID*/
+ RTUNI_ALPHA, /* U+0121a5: CUNEIFORM SIGN KIN*/
+ RTUNI_ALPHA, /* U+0121a6: CUNEIFORM SIGN KISAL*/
+ RTUNI_ALPHA, /* U+0121a7: CUNEIFORM SIGN KISH*/
+ RTUNI_ALPHA, /* U+0121a8: CUNEIFORM SIGN KISIM5*/
+ RTUNI_ALPHA, /* U+0121a9: CUNEIFORM SIGN KISIM5 OVER KISIM5*/
+ RTUNI_ALPHA, /* U+0121aa: CUNEIFORM SIGN KU*/
+ RTUNI_ALPHA, /* U+0121ab: CUNEIFORM SIGN KU OVER HI TIMES ASH2 KU OVER HI TIMES ASH2*/
+ RTUNI_ALPHA, /* U+0121ac: CUNEIFORM SIGN KU3*/
+ RTUNI_ALPHA, /* U+0121ad: CUNEIFORM SIGN KU4*/
+ RTUNI_ALPHA, /* U+0121ae: CUNEIFORM SIGN KU4 VARIANT FORM*/
+ RTUNI_ALPHA, /* U+0121af: CUNEIFORM SIGN KU7*/
+ RTUNI_ALPHA, /* U+0121b0: CUNEIFORM SIGN KUL*/
+ RTUNI_ALPHA, /* U+0121b1: CUNEIFORM SIGN KUL GUNU*/
+ RTUNI_ALPHA, /* U+0121b2: CUNEIFORM SIGN KUN*/
+ RTUNI_ALPHA, /* U+0121b3: CUNEIFORM SIGN KUR*/
+ RTUNI_ALPHA, /* U+0121b4: CUNEIFORM SIGN KUR OPPOSING KUR*/
+ RTUNI_ALPHA, /* U+0121b5: CUNEIFORM SIGN KUSHU2*/
+ RTUNI_ALPHA, /* U+0121b6: CUNEIFORM SIGN KWU318*/
+ RTUNI_ALPHA, /* U+0121b7: CUNEIFORM SIGN LA*/
+ RTUNI_ALPHA, /* U+0121b8: CUNEIFORM SIGN LAGAB*/
+ RTUNI_ALPHA, /* U+0121b9: CUNEIFORM SIGN LAGAB TIMES A*/
+ RTUNI_ALPHA, /* U+0121ba: CUNEIFORM SIGN LAGAB TIMES A PLUS DA PLUS HA*/
+ RTUNI_ALPHA, /* U+0121bb: CUNEIFORM SIGN LAGAB TIMES A PLUS GAR*/
+ RTUNI_ALPHA, /* U+0121bc: CUNEIFORM SIGN LAGAB TIMES A PLUS LAL*/
+ RTUNI_ALPHA, /* U+0121bd: CUNEIFORM SIGN LAGAB TIMES AL*/
+ RTUNI_ALPHA, /* U+0121be: CUNEIFORM SIGN LAGAB TIMES AN*/
+ RTUNI_ALPHA, /* U+0121bf: CUNEIFORM SIGN LAGAB TIMES ASH ZIDA TENU*/
+ RTUNI_ALPHA, /* U+0121c0: CUNEIFORM SIGN LAGAB TIMES BAD*/
+ RTUNI_ALPHA, /* U+0121c1: CUNEIFORM SIGN LAGAB TIMES BI*/
+ RTUNI_ALPHA, /* U+0121c2: CUNEIFORM SIGN LAGAB TIMES DAR*/
+ RTUNI_ALPHA, /* U+0121c3: CUNEIFORM SIGN LAGAB TIMES EN*/
+ RTUNI_ALPHA, /* U+0121c4: CUNEIFORM SIGN LAGAB TIMES GA*/
+ RTUNI_ALPHA, /* U+0121c5: CUNEIFORM SIGN LAGAB TIMES GAR*/
+ RTUNI_ALPHA, /* U+0121c6: CUNEIFORM SIGN LAGAB TIMES GUD*/
+ RTUNI_ALPHA, /* U+0121c7: CUNEIFORM SIGN LAGAB TIMES GUD PLUS GUD*/
+ RTUNI_ALPHA, /* U+0121c8: CUNEIFORM SIGN LAGAB TIMES HA*/
+ RTUNI_ALPHA, /* U+0121c9: CUNEIFORM SIGN LAGAB TIMES HAL*/
+ RTUNI_ALPHA, /* U+0121ca: CUNEIFORM SIGN LAGAB TIMES HI TIMES NUN*/
+ RTUNI_ALPHA, /* U+0121cb: CUNEIFORM SIGN LAGAB TIMES IGI GUNU*/
+ RTUNI_ALPHA, /* U+0121cc: CUNEIFORM SIGN LAGAB TIMES IM*/
+ RTUNI_ALPHA, /* U+0121cd: CUNEIFORM SIGN LAGAB TIMES IM PLUS HA*/
+ RTUNI_ALPHA, /* U+0121ce: CUNEIFORM SIGN LAGAB TIMES IM PLUS LU*/
+ RTUNI_ALPHA, /* U+0121cf: CUNEIFORM SIGN LAGAB TIMES KI*/
+ RTUNI_ALPHA, /* U+0121d0: CUNEIFORM SIGN LAGAB TIMES KIN*/
+ RTUNI_ALPHA, /* U+0121d1: CUNEIFORM SIGN LAGAB TIMES KU3*/
+ RTUNI_ALPHA, /* U+0121d2: CUNEIFORM SIGN LAGAB TIMES KUL*/
+ RTUNI_ALPHA, /* U+0121d3: CUNEIFORM SIGN LAGAB TIMES KUL PLUS HI PLUS A*/
+ RTUNI_ALPHA, /* U+0121d4: CUNEIFORM SIGN LAGAB TIMES LAGAB*/
+ RTUNI_ALPHA, /* U+0121d5: CUNEIFORM SIGN LAGAB TIMES LISH*/
+ RTUNI_ALPHA, /* U+0121d6: CUNEIFORM SIGN LAGAB TIMES LU*/
+ RTUNI_ALPHA, /* U+0121d7: CUNEIFORM SIGN LAGAB TIMES LUL*/
+ RTUNI_ALPHA, /* U+0121d8: CUNEIFORM SIGN LAGAB TIMES ME*/
+ RTUNI_ALPHA, /* U+0121d9: CUNEIFORM SIGN LAGAB TIMES ME PLUS EN*/
+ RTUNI_ALPHA, /* U+0121da: CUNEIFORM SIGN LAGAB TIMES MUSH*/
+ RTUNI_ALPHA, /* U+0121db: CUNEIFORM SIGN LAGAB TIMES NE*/
+ RTUNI_ALPHA, /* U+0121dc: CUNEIFORM SIGN LAGAB TIMES SHE PLUS SUM*/
+ RTUNI_ALPHA, /* U+0121dd: CUNEIFORM SIGN LAGAB TIMES SHITA PLUS GISH PLUS ERIN2*/
+ RTUNI_ALPHA, /* U+0121de: CUNEIFORM SIGN LAGAB TIMES SHITA PLUS GISH TENU*/
+ RTUNI_ALPHA, /* U+0121df: CUNEIFORM SIGN LAGAB TIMES SHU2*/
+ RTUNI_ALPHA, /* U+0121e0: CUNEIFORM SIGN LAGAB TIMES SHU2 PLUS SHU2*/
+ RTUNI_ALPHA, /* U+0121e1: CUNEIFORM SIGN LAGAB TIMES SUM*/
+ RTUNI_ALPHA, /* U+0121e2: CUNEIFORM SIGN LAGAB TIMES TAG*/
+ RTUNI_ALPHA, /* U+0121e3: CUNEIFORM SIGN LAGAB TIMES TAK4*/
+ RTUNI_ALPHA, /* U+0121e4: CUNEIFORM SIGN LAGAB TIMES TE PLUS A PLUS SU PLUS NA*/
+ RTUNI_ALPHA, /* U+0121e5: CUNEIFORM SIGN LAGAB TIMES U*/
+ RTUNI_ALPHA, /* U+0121e6: CUNEIFORM SIGN LAGAB TIMES U PLUS A*/
+ RTUNI_ALPHA, /* U+0121e7: CUNEIFORM SIGN LAGAB TIMES U PLUS U PLUS U*/
+ RTUNI_ALPHA, /* U+0121e8: CUNEIFORM SIGN LAGAB TIMES U2 PLUS ASH*/
+ RTUNI_ALPHA, /* U+0121e9: CUNEIFORM SIGN LAGAB TIMES UD*/
+ RTUNI_ALPHA, /* U+0121ea: CUNEIFORM SIGN LAGAB TIMES USH*/
+ RTUNI_ALPHA, /* U+0121eb: CUNEIFORM SIGN LAGAB SQUARED*/
+ RTUNI_ALPHA, /* U+0121ec: CUNEIFORM SIGN LAGAR*/
+ RTUNI_ALPHA, /* U+0121ed: CUNEIFORM SIGN LAGAR TIMES SHE*/
+ RTUNI_ALPHA, /* U+0121ee: CUNEIFORM SIGN LAGAR TIMES SHE PLUS SUM*/
+ RTUNI_ALPHA, /* U+0121ef: CUNEIFORM SIGN LAGAR GUNU*/
+ RTUNI_ALPHA, /* U+0121f0: CUNEIFORM SIGN LAGAR GUNU OVER LAGAR GUNU SHE*/
+ RTUNI_ALPHA, /* U+0121f1: CUNEIFORM SIGN LAHSHU*/
+ RTUNI_ALPHA, /* U+0121f2: CUNEIFORM SIGN LAL*/
+ RTUNI_ALPHA, /* U+0121f3: CUNEIFORM SIGN LAL TIMES LAL*/
+ RTUNI_ALPHA, /* U+0121f4: CUNEIFORM SIGN LAM*/
+ RTUNI_ALPHA, /* U+0121f5: CUNEIFORM SIGN LAM TIMES KUR*/
+ RTUNI_ALPHA, /* U+0121f6: CUNEIFORM SIGN LAM TIMES KUR PLUS RU*/
+ RTUNI_ALPHA, /* U+0121f7: CUNEIFORM SIGN LI*/
+ RTUNI_ALPHA, /* U+0121f8: CUNEIFORM SIGN LIL*/
+ RTUNI_ALPHA, /* U+0121f9: CUNEIFORM SIGN LIMMU2*/
+ RTUNI_ALPHA, /* U+0121fa: CUNEIFORM SIGN LISH*/
+ RTUNI_ALPHA, /* U+0121fb: CUNEIFORM SIGN LU*/
+ RTUNI_ALPHA, /* U+0121fc: CUNEIFORM SIGN LU TIMES BAD*/
+ RTUNI_ALPHA, /* U+0121fd: CUNEIFORM SIGN LU2*/
+ RTUNI_ALPHA, /* U+0121fe: CUNEIFORM SIGN LU2 TIMES AL*/
+ RTUNI_ALPHA, /* U+0121ff: CUNEIFORM SIGN LU2 TIMES BAD*/
+ RTUNI_ALPHA, /* U+012200: CUNEIFORM SIGN LU2 TIMES ESH2*/
+ RTUNI_ALPHA, /* U+012201: CUNEIFORM SIGN LU2 TIMES ESH2 TENU*/
+ RTUNI_ALPHA, /* U+012202: CUNEIFORM SIGN LU2 TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+012203: CUNEIFORM SIGN LU2 TIMES HI TIMES BAD*/
+ RTUNI_ALPHA, /* U+012204: CUNEIFORM SIGN LU2 TIMES IM*/
+ RTUNI_ALPHA, /* U+012205: CUNEIFORM SIGN LU2 TIMES KAD2*/
+ RTUNI_ALPHA, /* U+012206: CUNEIFORM SIGN LU2 TIMES KAD3*/
+ RTUNI_ALPHA, /* U+012207: CUNEIFORM SIGN LU2 TIMES KAD3 PLUS ASH*/
+ RTUNI_ALPHA, /* U+012208: CUNEIFORM SIGN LU2 TIMES KI*/
+ RTUNI_ALPHA, /* U+012209: CUNEIFORM SIGN LU2 TIMES LA PLUS ASH*/
+ RTUNI_ALPHA, /* U+01220a: CUNEIFORM SIGN LU2 TIMES LAGAB*/
+ RTUNI_ALPHA, /* U+01220b: CUNEIFORM SIGN LU2 TIMES ME PLUS EN*/
+ RTUNI_ALPHA, /* U+01220c: CUNEIFORM SIGN LU2 TIMES NE*/
+ RTUNI_ALPHA, /* U+01220d: CUNEIFORM SIGN LU2 TIMES NU*/
+ RTUNI_ALPHA, /* U+01220e: CUNEIFORM SIGN LU2 TIMES SI PLUS ASH*/
+ RTUNI_ALPHA, /* U+01220f: CUNEIFORM SIGN LU2 TIMES SIK2 PLUS BU*/
+ RTUNI_ALPHA, /* U+012210: CUNEIFORM SIGN LU2 TIMES TUG2*/
+ RTUNI_ALPHA, /* U+012211: CUNEIFORM SIGN LU2 TENU*/
+ RTUNI_ALPHA, /* U+012212: CUNEIFORM SIGN LU2 CROSSING LU2*/
+ RTUNI_ALPHA, /* U+012213: CUNEIFORM SIGN LU2 OPPOSING LU2*/
+ RTUNI_ALPHA, /* U+012214: CUNEIFORM SIGN LU2 SQUARED*/
+ RTUNI_ALPHA, /* U+012215: CUNEIFORM SIGN LU2 SHESHIG*/
+ RTUNI_ALPHA, /* U+012216: CUNEIFORM SIGN LU3*/
+ RTUNI_ALPHA, /* U+012217: CUNEIFORM SIGN LUGAL*/
+ RTUNI_ALPHA, /* U+012218: CUNEIFORM SIGN LUGAL OVER LUGAL*/
+ RTUNI_ALPHA, /* U+012219: CUNEIFORM SIGN LUGAL OPPOSING LUGAL*/
+ RTUNI_ALPHA, /* U+01221a: CUNEIFORM SIGN LUGAL SHESHIG*/
+ RTUNI_ALPHA, /* U+01221b: CUNEIFORM SIGN LUH*/
+ RTUNI_ALPHA, /* U+01221c: CUNEIFORM SIGN LUL*/
+ RTUNI_ALPHA, /* U+01221d: CUNEIFORM SIGN LUM*/
+ RTUNI_ALPHA, /* U+01221e: CUNEIFORM SIGN LUM OVER LUM*/
+ RTUNI_ALPHA, /* U+01221f: CUNEIFORM SIGN LUM OVER LUM GAR OVER GAR*/
+ RTUNI_ALPHA, /* U+012220: CUNEIFORM SIGN MA*/
+ RTUNI_ALPHA, /* U+012221: CUNEIFORM SIGN MA TIMES TAK4*/
+ RTUNI_ALPHA, /* U+012222: CUNEIFORM SIGN MA GUNU*/
+ RTUNI_ALPHA, /* U+012223: CUNEIFORM SIGN MA2*/
+ RTUNI_ALPHA, /* U+012224: CUNEIFORM SIGN MAH*/
+ RTUNI_ALPHA, /* U+012225: CUNEIFORM SIGN MAR*/
+ RTUNI_ALPHA, /* U+012226: CUNEIFORM SIGN MASH*/
+ RTUNI_ALPHA, /* U+012227: CUNEIFORM SIGN MASH2*/
+ RTUNI_ALPHA, /* U+012228: CUNEIFORM SIGN ME*/
+ RTUNI_ALPHA, /* U+012229: CUNEIFORM SIGN MES*/
+ RTUNI_ALPHA, /* U+01222a: CUNEIFORM SIGN MI*/
+ RTUNI_ALPHA, /* U+01222b: CUNEIFORM SIGN MIN*/
+ RTUNI_ALPHA, /* U+01222c: CUNEIFORM SIGN MU*/
+ RTUNI_ALPHA, /* U+01222d: CUNEIFORM SIGN MU OVER MU*/
+ RTUNI_ALPHA, /* U+01222e: CUNEIFORM SIGN MUG*/
+ RTUNI_ALPHA, /* U+01222f: CUNEIFORM SIGN MUG GUNU*/
+ RTUNI_ALPHA, /* U+012230: CUNEIFORM SIGN MUNSUB*/
+ RTUNI_ALPHA, /* U+012231: CUNEIFORM SIGN MURGU2*/
+ RTUNI_ALPHA, /* U+012232: CUNEIFORM SIGN MUSH*/
+ RTUNI_ALPHA, /* U+012233: CUNEIFORM SIGN MUSH TIMES A*/
+ RTUNI_ALPHA, /* U+012234: CUNEIFORM SIGN MUSH TIMES KUR*/
+ RTUNI_ALPHA, /* U+012235: CUNEIFORM SIGN MUSH TIMES ZA*/
+ RTUNI_ALPHA, /* U+012236: CUNEIFORM SIGN MUSH OVER MUSH*/
+ RTUNI_ALPHA, /* U+012237: CUNEIFORM SIGN MUSH OVER MUSH TIMES A PLUS NA*/
+ RTUNI_ALPHA, /* U+012238: CUNEIFORM SIGN MUSH CROSSING MUSH*/
+ RTUNI_ALPHA, /* U+012239: CUNEIFORM SIGN MUSH3*/
+ RTUNI_ALPHA, /* U+01223a: CUNEIFORM SIGN MUSH3 TIMES A*/
+ RTUNI_ALPHA, /* U+01223b: CUNEIFORM SIGN MUSH3 TIMES A PLUS DI*/
+ RTUNI_ALPHA, /* U+01223c: CUNEIFORM SIGN MUSH3 TIMES DI*/
+ RTUNI_ALPHA, /* U+01223d: CUNEIFORM SIGN MUSH3 GUNU*/
+ RTUNI_ALPHA, /* U+01223e: CUNEIFORM SIGN NA*/
+ RTUNI_ALPHA, /* U+01223f: CUNEIFORM SIGN NA2*/
+ RTUNI_ALPHA, /* U+012240: CUNEIFORM SIGN NAGA*/
+ RTUNI_ALPHA, /* U+012241: CUNEIFORM SIGN NAGA INVERTED*/
+ RTUNI_ALPHA, /* U+012242: CUNEIFORM SIGN NAGA TIMES SHU TENU*/
+ RTUNI_ALPHA, /* U+012243: CUNEIFORM SIGN NAGA OPPOSING NAGA*/
+ RTUNI_ALPHA, /* U+012244: CUNEIFORM SIGN NAGAR*/
+ RTUNI_ALPHA, /* U+012245: CUNEIFORM SIGN NAM NUTILLU*/
+ RTUNI_ALPHA, /* U+012246: CUNEIFORM SIGN NAM*/
+ RTUNI_ALPHA, /* U+012247: CUNEIFORM SIGN NAM2*/
+ RTUNI_ALPHA, /* U+012248: CUNEIFORM SIGN NE*/
+ RTUNI_ALPHA, /* U+012249: CUNEIFORM SIGN NE TIMES A*/
+ RTUNI_ALPHA, /* U+01224a: CUNEIFORM SIGN NE TIMES UD*/
+ RTUNI_ALPHA, /* U+01224b: CUNEIFORM SIGN NE SHESHIG*/
+ RTUNI_ALPHA, /* U+01224c: CUNEIFORM SIGN NI*/
+ RTUNI_ALPHA, /* U+01224d: CUNEIFORM SIGN NI TIMES E*/
+ RTUNI_ALPHA, /* U+01224e: CUNEIFORM SIGN NI2*/
+ RTUNI_ALPHA, /* U+01224f: CUNEIFORM SIGN NIM*/
+ RTUNI_ALPHA, /* U+012250: CUNEIFORM SIGN NIM TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+012251: CUNEIFORM SIGN NIM TIMES GAR PLUS GAN2 TENU*/
+ RTUNI_ALPHA, /* U+012252: CUNEIFORM SIGN NINDA2*/
+ RTUNI_ALPHA, /* U+012253: CUNEIFORM SIGN NINDA2 TIMES AN*/
+ RTUNI_ALPHA, /* U+012254: CUNEIFORM SIGN NINDA2 TIMES ASH*/
+ RTUNI_ALPHA, /* U+012255: CUNEIFORM SIGN NINDA2 TIMES ASH PLUS ASH*/
+ RTUNI_ALPHA, /* U+012256: CUNEIFORM SIGN NINDA2 TIMES GUD*/
+ RTUNI_ALPHA, /* U+012257: CUNEIFORM SIGN NINDA2 TIMES ME PLUS GAN2 TENU*/
+ RTUNI_ALPHA, /* U+012258: CUNEIFORM SIGN NINDA2 TIMES NE*/
+ RTUNI_ALPHA, /* U+012259: CUNEIFORM SIGN NINDA2 TIMES NUN*/
+ RTUNI_ALPHA, /* U+01225a: CUNEIFORM SIGN NINDA2 TIMES SHE*/
+ RTUNI_ALPHA, /* U+01225b: CUNEIFORM SIGN NINDA2 TIMES SHE PLUS A AN*/
+ RTUNI_ALPHA, /* U+01225c: CUNEIFORM SIGN NINDA2 TIMES SHE PLUS ASH*/
+ RTUNI_ALPHA, /* U+01225d: CUNEIFORM SIGN NINDA2 TIMES SHE PLUS ASH PLUS ASH*/
+ RTUNI_ALPHA, /* U+01225e: CUNEIFORM SIGN NINDA2 TIMES U2 PLUS ASH*/
+ RTUNI_ALPHA, /* U+01225f: CUNEIFORM SIGN NINDA2 TIMES USH*/
+ RTUNI_ALPHA, /* U+012260: CUNEIFORM SIGN NISAG*/
+ RTUNI_ALPHA, /* U+012261: CUNEIFORM SIGN NU*/
+ RTUNI_ALPHA, /* U+012262: CUNEIFORM SIGN NU11*/
+ RTUNI_ALPHA, /* U+012263: CUNEIFORM SIGN NUN*/
+ RTUNI_ALPHA, /* U+012264: CUNEIFORM SIGN NUN LAGAR TIMES GAR*/
+ RTUNI_ALPHA, /* U+012265: CUNEIFORM SIGN NUN LAGAR TIMES MASH*/
+ RTUNI_ALPHA, /* U+012266: CUNEIFORM SIGN NUN LAGAR TIMES SAL*/
+ RTUNI_ALPHA, /* U+012267: CUNEIFORM SIGN NUN LAGAR TIMES SAL OVER NUN LAGAR TIMES SAL*/
+ RTUNI_ALPHA, /* U+012268: CUNEIFORM SIGN NUN LAGAR TIMES USH*/
+ RTUNI_ALPHA, /* U+012269: CUNEIFORM SIGN NUN TENU*/
+ RTUNI_ALPHA, /* U+01226a: CUNEIFORM SIGN NUN OVER NUN*/
+ RTUNI_ALPHA, /* U+01226b: CUNEIFORM SIGN NUN CROSSING NUN*/
+ RTUNI_ALPHA, /* U+01226c: CUNEIFORM SIGN NUN CROSSING NUN LAGAR OVER LAGAR*/
+ RTUNI_ALPHA, /* U+01226d: CUNEIFORM SIGN NUNUZ*/
+ RTUNI_ALPHA, /* U+01226e: CUNEIFORM SIGN NUNUZ AB2 TIMES ASHGAB*/
+ RTUNI_ALPHA, /* U+01226f: CUNEIFORM SIGN NUNUZ AB2 TIMES BI*/
+ RTUNI_ALPHA, /* U+012270: CUNEIFORM SIGN NUNUZ AB2 TIMES DUG*/
+ RTUNI_ALPHA, /* U+012271: CUNEIFORM SIGN NUNUZ AB2 TIMES GUD*/
+ RTUNI_ALPHA, /* U+012272: CUNEIFORM SIGN NUNUZ AB2 TIMES IGI GUNU*/
+ RTUNI_ALPHA, /* U+012273: CUNEIFORM SIGN NUNUZ AB2 TIMES KAD3*/
+ RTUNI_ALPHA, /* U+012274: CUNEIFORM SIGN NUNUZ AB2 TIMES LA*/
+ RTUNI_ALPHA, /* U+012275: CUNEIFORM SIGN NUNUZ AB2 TIMES NE*/
+ RTUNI_ALPHA, /* U+012276: CUNEIFORM SIGN NUNUZ AB2 TIMES SILA3*/
+ RTUNI_ALPHA, /* U+012277: CUNEIFORM SIGN NUNUZ AB2 TIMES U2*/
+ RTUNI_ALPHA, /* U+012278: CUNEIFORM SIGN NUNUZ KISIM5 TIMES BI*/
+ RTUNI_ALPHA, /* U+012279: CUNEIFORM SIGN NUNUZ KISIM5 TIMES BI U*/
+ RTUNI_ALPHA, /* U+01227a: CUNEIFORM SIGN PA*/
+ RTUNI_ALPHA, /* U+01227b: CUNEIFORM SIGN PAD*/
+ RTUNI_ALPHA, /* U+01227c: CUNEIFORM SIGN PAN*/
+ RTUNI_ALPHA, /* U+01227d: CUNEIFORM SIGN PAP*/
+ RTUNI_ALPHA, /* U+01227e: CUNEIFORM SIGN PESH2*/
+ RTUNI_ALPHA, /* U+01227f: CUNEIFORM SIGN PI*/
+ RTUNI_ALPHA, /* U+012280: CUNEIFORM SIGN PI TIMES A*/
+ RTUNI_ALPHA, /* U+012281: CUNEIFORM SIGN PI TIMES AB*/
+ RTUNI_ALPHA, /* U+012282: CUNEIFORM SIGN PI TIMES BI*/
+ RTUNI_ALPHA, /* U+012283: CUNEIFORM SIGN PI TIMES BU*/
+ RTUNI_ALPHA, /* U+012284: CUNEIFORM SIGN PI TIMES E*/
+ RTUNI_ALPHA, /* U+012285: CUNEIFORM SIGN PI TIMES I*/
+ RTUNI_ALPHA, /* U+012286: CUNEIFORM SIGN PI TIMES IB*/
+ RTUNI_ALPHA, /* U+012287: CUNEIFORM SIGN PI TIMES U*/
+ RTUNI_ALPHA, /* U+012288: CUNEIFORM SIGN PI TIMES U2*/
+ RTUNI_ALPHA, /* U+012289: CUNEIFORM SIGN PI CROSSING PI*/
+ RTUNI_ALPHA, /* U+01228a: CUNEIFORM SIGN PIRIG*/
+ RTUNI_ALPHA, /* U+01228b: CUNEIFORM SIGN PIRIG TIMES KAL*/
+ RTUNI_ALPHA, /* U+01228c: CUNEIFORM SIGN PIRIG TIMES UD*/
+ RTUNI_ALPHA, /* U+01228d: CUNEIFORM SIGN PIRIG TIMES ZA*/
+ RTUNI_ALPHA, /* U+01228e: CUNEIFORM SIGN PIRIG OPPOSING PIRIG*/
+ RTUNI_ALPHA, /* U+01228f: CUNEIFORM SIGN RA*/
+ RTUNI_ALPHA, /* U+012290: CUNEIFORM SIGN RAB*/
+ RTUNI_ALPHA, /* U+012291: CUNEIFORM SIGN RI*/
+ RTUNI_ALPHA, /* U+012292: CUNEIFORM SIGN RU*/
+ RTUNI_ALPHA, /* U+012293: CUNEIFORM SIGN SA*/
+ RTUNI_ALPHA, /* U+012294: CUNEIFORM SIGN SAG NUTILLU*/
+ RTUNI_ALPHA, /* U+012295: CUNEIFORM SIGN SAG*/
+ RTUNI_ALPHA, /* U+012296: CUNEIFORM SIGN SAG TIMES A*/
+ RTUNI_ALPHA, /* U+012297: CUNEIFORM SIGN SAG TIMES DU*/
+ RTUNI_ALPHA, /* U+012298: CUNEIFORM SIGN SAG TIMES DUB*/
+ RTUNI_ALPHA, /* U+012299: CUNEIFORM SIGN SAG TIMES HA*/
+ RTUNI_ALPHA, /* U+01229a: CUNEIFORM SIGN SAG TIMES KAK*/
+ RTUNI_ALPHA, /* U+01229b: CUNEIFORM SIGN SAG TIMES KUR*/
+ RTUNI_ALPHA, /* U+01229c: CUNEIFORM SIGN SAG TIMES LUM*/
+ RTUNI_ALPHA, /* U+01229d: CUNEIFORM SIGN SAG TIMES MI*/
+ RTUNI_ALPHA, /* U+01229e: CUNEIFORM SIGN SAG TIMES NUN*/
+ RTUNI_ALPHA, /* U+01229f: CUNEIFORM SIGN SAG TIMES SAL*/
+ RTUNI_ALPHA, /* U+0122a0: CUNEIFORM SIGN SAG TIMES SHID*/
+ RTUNI_ALPHA, /* U+0122a1: CUNEIFORM SIGN SAG TIMES TAB*/
+ RTUNI_ALPHA, /* U+0122a2: CUNEIFORM SIGN SAG TIMES U2*/
+ RTUNI_ALPHA, /* U+0122a3: CUNEIFORM SIGN SAG TIMES UB*/
+ RTUNI_ALPHA, /* U+0122a4: CUNEIFORM SIGN SAG TIMES UM*/
+ RTUNI_ALPHA, /* U+0122a5: CUNEIFORM SIGN SAG TIMES UR*/
+ RTUNI_ALPHA, /* U+0122a6: CUNEIFORM SIGN SAG TIMES USH*/
+ RTUNI_ALPHA, /* U+0122a7: CUNEIFORM SIGN SAG OVER SAG*/
+ RTUNI_ALPHA, /* U+0122a8: CUNEIFORM SIGN SAG GUNU*/
+ RTUNI_ALPHA, /* U+0122a9: CUNEIFORM SIGN SAL*/
+ RTUNI_ALPHA, /* U+0122aa: CUNEIFORM SIGN SAL LAGAB TIMES ASH2*/
+ RTUNI_ALPHA, /* U+0122ab: CUNEIFORM SIGN SANGA2*/
+ RTUNI_ALPHA, /* U+0122ac: CUNEIFORM SIGN SAR*/
+ RTUNI_ALPHA, /* U+0122ad: CUNEIFORM SIGN SHA*/
+ RTUNI_ALPHA, /* U+0122ae: CUNEIFORM SIGN SHA3*/
+ RTUNI_ALPHA, /* U+0122af: CUNEIFORM SIGN SHA3 TIMES A*/
+ RTUNI_ALPHA, /* U+0122b0: CUNEIFORM SIGN SHA3 TIMES BAD*/
+ RTUNI_ALPHA, /* U+0122b1: CUNEIFORM SIGN SHA3 TIMES GISH*/
+ RTUNI_ALPHA, /* U+0122b2: CUNEIFORM SIGN SHA3 TIMES NE*/
+ RTUNI_ALPHA, /* U+0122b3: CUNEIFORM SIGN SHA3 TIMES SHU2*/
+ RTUNI_ALPHA, /* U+0122b4: CUNEIFORM SIGN SHA3 TIMES TUR*/
+ RTUNI_ALPHA, /* U+0122b5: CUNEIFORM SIGN SHA3 TIMES U*/
+ RTUNI_ALPHA, /* U+0122b6: CUNEIFORM SIGN SHA3 TIMES U PLUS A*/
+ RTUNI_ALPHA, /* U+0122b7: CUNEIFORM SIGN SHA6*/
+ RTUNI_ALPHA, /* U+0122b8: CUNEIFORM SIGN SHAB6*/
+ RTUNI_ALPHA, /* U+0122b9: CUNEIFORM SIGN SHAR2*/
+ RTUNI_ALPHA, /* U+0122ba: CUNEIFORM SIGN SHE*/
+ RTUNI_ALPHA, /* U+0122bb: CUNEIFORM SIGN SHE HU*/
+ RTUNI_ALPHA, /* U+0122bc: CUNEIFORM SIGN SHE OVER SHE GAD OVER GAD GAR OVER GAR*/
+ RTUNI_ALPHA, /* U+0122bd: CUNEIFORM SIGN SHE OVER SHE TAB OVER TAB GAR OVER GAR*/
+ RTUNI_ALPHA, /* U+0122be: CUNEIFORM SIGN SHEG9*/
+ RTUNI_ALPHA, /* U+0122bf: CUNEIFORM SIGN SHEN*/
+ RTUNI_ALPHA, /* U+0122c0: CUNEIFORM SIGN SHESH*/
+ RTUNI_ALPHA, /* U+0122c1: CUNEIFORM SIGN SHESH2*/
+ RTUNI_ALPHA, /* U+0122c2: CUNEIFORM SIGN SHESHLAM*/
+ RTUNI_ALPHA, /* U+0122c3: CUNEIFORM SIGN SHID*/
+ RTUNI_ALPHA, /* U+0122c4: CUNEIFORM SIGN SHID TIMES A*/
+ RTUNI_ALPHA, /* U+0122c5: CUNEIFORM SIGN SHID TIMES IM*/
+ RTUNI_ALPHA, /* U+0122c6: CUNEIFORM SIGN SHIM*/
+ RTUNI_ALPHA, /* U+0122c7: CUNEIFORM SIGN SHIM TIMES A*/
+ RTUNI_ALPHA, /* U+0122c8: CUNEIFORM SIGN SHIM TIMES BAL*/
+ RTUNI_ALPHA, /* U+0122c9: CUNEIFORM SIGN SHIM TIMES BULUG*/
+ RTUNI_ALPHA, /* U+0122ca: CUNEIFORM SIGN SHIM TIMES DIN*/
+ RTUNI_ALPHA, /* U+0122cb: CUNEIFORM SIGN SHIM TIMES GAR*/
+ RTUNI_ALPHA, /* U+0122cc: CUNEIFORM SIGN SHIM TIMES IGI*/
+ RTUNI_ALPHA, /* U+0122cd: CUNEIFORM SIGN SHIM TIMES IGI GUNU*/
+ RTUNI_ALPHA, /* U+0122ce: CUNEIFORM SIGN SHIM TIMES KUSHU2*/
+ RTUNI_ALPHA, /* U+0122cf: CUNEIFORM SIGN SHIM TIMES LUL*/
+ RTUNI_ALPHA, /* U+0122d0: CUNEIFORM SIGN SHIM TIMES MUG*/
+ RTUNI_ALPHA, /* U+0122d1: CUNEIFORM SIGN SHIM TIMES SAL*/
+ RTUNI_ALPHA, /* U+0122d2: CUNEIFORM SIGN SHINIG*/
+ RTUNI_ALPHA, /* U+0122d3: CUNEIFORM SIGN SHIR*/
+ RTUNI_ALPHA, /* U+0122d4: CUNEIFORM SIGN SHIR TENU*/
+ RTUNI_ALPHA, /* U+0122d5: CUNEIFORM SIGN SHIR OVER SHIR BUR OVER BUR*/
+ RTUNI_ALPHA, /* U+0122d6: CUNEIFORM SIGN SHITA*/
+ RTUNI_ALPHA, /* U+0122d7: CUNEIFORM SIGN SHU*/
+ RTUNI_ALPHA, /* U+0122d8: CUNEIFORM SIGN SHU OVER INVERTED SHU*/
+ RTUNI_ALPHA, /* U+0122d9: CUNEIFORM SIGN SHU2*/
+ RTUNI_ALPHA, /* U+0122da: CUNEIFORM SIGN SHUBUR*/
+ RTUNI_ALPHA, /* U+0122db: CUNEIFORM SIGN SI*/
+ RTUNI_ALPHA, /* U+0122dc: CUNEIFORM SIGN SI GUNU*/
+ RTUNI_ALPHA, /* U+0122dd: CUNEIFORM SIGN SIG*/
+ RTUNI_ALPHA, /* U+0122de: CUNEIFORM SIGN SIG4*/
+ RTUNI_ALPHA, /* U+0122df: CUNEIFORM SIGN SIG4 OVER SIG4 SHU2*/
+ RTUNI_ALPHA, /* U+0122e0: CUNEIFORM SIGN SIK2*/
+ RTUNI_ALPHA, /* U+0122e1: CUNEIFORM SIGN SILA3*/
+ RTUNI_ALPHA, /* U+0122e2: CUNEIFORM SIGN SU*/
+ RTUNI_ALPHA, /* U+0122e3: CUNEIFORM SIGN SU OVER SU*/
+ RTUNI_ALPHA, /* U+0122e4: CUNEIFORM SIGN SUD*/
+ RTUNI_ALPHA, /* U+0122e5: CUNEIFORM SIGN SUD2*/
+ RTUNI_ALPHA, /* U+0122e6: CUNEIFORM SIGN SUHUR*/
+ RTUNI_ALPHA, /* U+0122e7: CUNEIFORM SIGN SUM*/
+ RTUNI_ALPHA, /* U+0122e8: CUNEIFORM SIGN SUMASH*/
+ RTUNI_ALPHA, /* U+0122e9: CUNEIFORM SIGN SUR*/
+ RTUNI_ALPHA, /* U+0122ea: CUNEIFORM SIGN SUR9*/
+ RTUNI_ALPHA, /* U+0122eb: CUNEIFORM SIGN TA*/
+ RTUNI_ALPHA, /* U+0122ec: CUNEIFORM SIGN TA ASTERISK*/
+ RTUNI_ALPHA, /* U+0122ed: CUNEIFORM SIGN TA TIMES HI*/
+ RTUNI_ALPHA, /* U+0122ee: CUNEIFORM SIGN TA TIMES MI*/
+ RTUNI_ALPHA, /* U+0122ef: CUNEIFORM SIGN TA GUNU*/
+ RTUNI_ALPHA, /* U+0122f0: CUNEIFORM SIGN TAB*/
+ RTUNI_ALPHA, /* U+0122f1: CUNEIFORM SIGN TAB OVER TAB NI OVER NI DISH OVER DISH*/
+ RTUNI_ALPHA, /* U+0122f2: CUNEIFORM SIGN TAB SQUARED*/
+ RTUNI_ALPHA, /* U+0122f3: CUNEIFORM SIGN TAG*/
+ RTUNI_ALPHA, /* U+0122f4: CUNEIFORM SIGN TAG TIMES BI*/
+ RTUNI_ALPHA, /* U+0122f5: CUNEIFORM SIGN TAG TIMES GUD*/
+ RTUNI_ALPHA, /* U+0122f6: CUNEIFORM SIGN TAG TIMES SHE*/
+ RTUNI_ALPHA, /* U+0122f7: CUNEIFORM SIGN TAG TIMES SHU*/
+ RTUNI_ALPHA, /* U+0122f8: CUNEIFORM SIGN TAG TIMES TUG2*/
+ RTUNI_ALPHA, /* U+0122f9: CUNEIFORM SIGN TAG TIMES UD*/
+ RTUNI_ALPHA, /* U+0122fa: CUNEIFORM SIGN TAK4*/
+ RTUNI_ALPHA, /* U+0122fb: CUNEIFORM SIGN TAR*/
+ RTUNI_ALPHA, /* U+0122fc: CUNEIFORM SIGN TE*/
+ RTUNI_ALPHA, /* U+0122fd: CUNEIFORM SIGN TE GUNU*/
+ RTUNI_ALPHA, /* U+0122fe: CUNEIFORM SIGN TI*/
+ RTUNI_ALPHA, /* U+0122ff: CUNEIFORM SIGN TI TENU*/
+ RTUNI_ALPHA, /* U+012300: CUNEIFORM SIGN TIL*/
+ RTUNI_ALPHA, /* U+012301: CUNEIFORM SIGN TIR*/
+ RTUNI_ALPHA, /* U+012302: CUNEIFORM SIGN TIR TIMES TAK4*/
+ RTUNI_ALPHA, /* U+012303: CUNEIFORM SIGN TIR OVER TIR*/
+ RTUNI_ALPHA, /* U+012304: CUNEIFORM SIGN TIR OVER TIR GAD OVER GAD GAR OVER GAR*/
+ RTUNI_ALPHA, /* U+012305: CUNEIFORM SIGN TU*/
+ RTUNI_ALPHA, /* U+012306: CUNEIFORM SIGN TUG2*/
+ RTUNI_ALPHA, /* U+012307: CUNEIFORM SIGN TUK*/
+ RTUNI_ALPHA, /* U+012308: CUNEIFORM SIGN TUM*/
+ RTUNI_ALPHA, /* U+012309: CUNEIFORM SIGN TUR*/
+ RTUNI_ALPHA, /* U+01230a: CUNEIFORM SIGN TUR OVER TUR ZA OVER ZA*/
+ RTUNI_ALPHA, /* U+01230b: CUNEIFORM SIGN U*/
+ RTUNI_ALPHA, /* U+01230c: CUNEIFORM SIGN U GUD*/
+ RTUNI_ALPHA, /* U+01230d: CUNEIFORM SIGN U U U*/
+ RTUNI_ALPHA, /* U+01230e: CUNEIFORM SIGN U OVER U PA OVER PA GAR OVER GAR*/
+ RTUNI_ALPHA, /* U+01230f: CUNEIFORM SIGN U OVER U SUR OVER SUR*/
+ RTUNI_ALPHA, /* U+012310: CUNEIFORM SIGN U OVER U U REVERSED OVER U REVERSED*/
+ RTUNI_ALPHA, /* U+012311: CUNEIFORM SIGN U2*/
+ RTUNI_ALPHA, /* U+012312: CUNEIFORM SIGN UB*/
+ RTUNI_ALPHA, /* U+012313: CUNEIFORM SIGN UD*/
+ RTUNI_ALPHA, /* U+012314: CUNEIFORM SIGN UD KUSHU2*/
+ RTUNI_ALPHA, /* U+012315: CUNEIFORM SIGN UD TIMES BAD*/
+ RTUNI_ALPHA, /* U+012316: CUNEIFORM SIGN UD TIMES MI*/
+ RTUNI_ALPHA, /* U+012317: CUNEIFORM SIGN UD TIMES U PLUS U PLUS U*/
+ RTUNI_ALPHA, /* U+012318: CUNEIFORM SIGN UD TIMES U PLUS U PLUS U GUNU*/
+ RTUNI_ALPHA, /* U+012319: CUNEIFORM SIGN UD GUNU*/
+ RTUNI_ALPHA, /* U+01231a: CUNEIFORM SIGN UD SHESHIG*/
+ RTUNI_ALPHA, /* U+01231b: CUNEIFORM SIGN UD SHESHIG TIMES BAD*/
+ RTUNI_ALPHA, /* U+01231c: CUNEIFORM SIGN UDUG*/
+ RTUNI_ALPHA, /* U+01231d: CUNEIFORM SIGN UM*/
+ RTUNI_ALPHA, /* U+01231e: CUNEIFORM SIGN UM TIMES LAGAB*/
+ RTUNI_ALPHA, /* U+01231f: CUNEIFORM SIGN UM TIMES ME PLUS DA*/
+ RTUNI_ALPHA, /* U+012320: CUNEIFORM SIGN UM TIMES SHA3*/
+ RTUNI_ALPHA, /* U+012321: CUNEIFORM SIGN UM TIMES U*/
+ RTUNI_ALPHA, /* U+012322: CUNEIFORM SIGN UMBIN*/
+ RTUNI_ALPHA, /* U+012323: CUNEIFORM SIGN UMUM*/
+ RTUNI_ALPHA, /* U+012324: CUNEIFORM SIGN UMUM TIMES KASKAL*/
+ RTUNI_ALPHA, /* U+012325: CUNEIFORM SIGN UMUM TIMES PA*/
+ RTUNI_ALPHA, /* U+012326: CUNEIFORM SIGN UN*/
+ RTUNI_ALPHA, /* U+012327: CUNEIFORM SIGN UN GUNU*/
+ RTUNI_ALPHA, /* U+012328: CUNEIFORM SIGN UR*/
+ RTUNI_ALPHA, /* U+012329: CUNEIFORM SIGN UR CROSSING UR*/
+ RTUNI_ALPHA, /* U+01232a: CUNEIFORM SIGN UR SHESHIG*/
+ RTUNI_ALPHA, /* U+01232b: CUNEIFORM SIGN UR2*/
+ RTUNI_ALPHA, /* U+01232c: CUNEIFORM SIGN UR2 TIMES A PLUS HA*/
+ RTUNI_ALPHA, /* U+01232d: CUNEIFORM SIGN UR2 TIMES A PLUS NA*/
+ RTUNI_ALPHA, /* U+01232e: CUNEIFORM SIGN UR2 TIMES AL*/
+ RTUNI_ALPHA, /* U+01232f: CUNEIFORM SIGN UR2 TIMES HA*/
+ RTUNI_ALPHA, /* U+012330: CUNEIFORM SIGN UR2 TIMES NUN*/
+ RTUNI_ALPHA, /* U+012331: CUNEIFORM SIGN UR2 TIMES U2*/
+ RTUNI_ALPHA, /* U+012332: CUNEIFORM SIGN UR2 TIMES U2 PLUS ASH*/
+ RTUNI_ALPHA, /* U+012333: CUNEIFORM SIGN UR2 TIMES U2 PLUS BI*/
+ RTUNI_ALPHA, /* U+012334: CUNEIFORM SIGN UR4*/
+ RTUNI_ALPHA, /* U+012335: CUNEIFORM SIGN URI*/
+ RTUNI_ALPHA, /* U+012336: CUNEIFORM SIGN URI3*/
+ RTUNI_ALPHA, /* U+012337: CUNEIFORM SIGN URU*/
+ RTUNI_ALPHA, /* U+012338: CUNEIFORM SIGN URU TIMES A*/
+ RTUNI_ALPHA, /* U+012339: CUNEIFORM SIGN URU TIMES ASHGAB*/
+ RTUNI_ALPHA, /* U+01233a: CUNEIFORM SIGN URU TIMES BAR*/
+ RTUNI_ALPHA, /* U+01233b: CUNEIFORM SIGN URU TIMES DUN*/
+ RTUNI_ALPHA, /* U+01233c: CUNEIFORM SIGN URU TIMES GA*/
+ RTUNI_ALPHA, /* U+01233d: CUNEIFORM SIGN URU TIMES GAL*/
+ RTUNI_ALPHA, /* U+01233e: CUNEIFORM SIGN URU TIMES GAN2 TENU*/
+ RTUNI_ALPHA, /* U+01233f: CUNEIFORM SIGN URU TIMES GAR*/
+ RTUNI_ALPHA, /* U+012340: CUNEIFORM SIGN URU TIMES GU*/
+ RTUNI_ALPHA, /* U+012341: CUNEIFORM SIGN URU TIMES HA*/
+ RTUNI_ALPHA, /* U+012342: CUNEIFORM SIGN URU TIMES IGI*/
+ RTUNI_ALPHA, /* U+012343: CUNEIFORM SIGN URU TIMES IM*/
+ RTUNI_ALPHA, /* U+012344: CUNEIFORM SIGN URU TIMES ISH*/
+ RTUNI_ALPHA, /* U+012345: CUNEIFORM SIGN URU TIMES KI*/
+ RTUNI_ALPHA, /* U+012346: CUNEIFORM SIGN URU TIMES LUM*/
+ RTUNI_ALPHA, /* U+012347: CUNEIFORM SIGN URU TIMES MIN*/
+ RTUNI_ALPHA, /* U+012348: CUNEIFORM SIGN URU TIMES PA*/
+ RTUNI_ALPHA, /* U+012349: CUNEIFORM SIGN URU TIMES SHE*/
+ RTUNI_ALPHA, /* U+01234a: CUNEIFORM SIGN URU TIMES SIG4*/
+ RTUNI_ALPHA, /* U+01234b: CUNEIFORM SIGN URU TIMES TU*/
+ RTUNI_ALPHA, /* U+01234c: CUNEIFORM SIGN URU TIMES U PLUS GUD*/
+ RTUNI_ALPHA, /* U+01234d: CUNEIFORM SIGN URU TIMES UD*/
+ RTUNI_ALPHA, /* U+01234e: CUNEIFORM SIGN URU TIMES URUDA*/
+ RTUNI_ALPHA, /* U+01234f: CUNEIFORM SIGN URUDA*/
+ RTUNI_ALPHA, /* U+012350: CUNEIFORM SIGN URUDA TIMES U*/
+ RTUNI_ALPHA, /* U+012351: CUNEIFORM SIGN USH*/
+ RTUNI_ALPHA, /* U+012352: CUNEIFORM SIGN USH TIMES A*/
+ RTUNI_ALPHA, /* U+012353: CUNEIFORM SIGN USH TIMES KU*/
+ RTUNI_ALPHA, /* U+012354: CUNEIFORM SIGN USH TIMES KUR*/
+ RTUNI_ALPHA, /* U+012355: CUNEIFORM SIGN USH TIMES TAK4*/
+ RTUNI_ALPHA, /* U+012356: CUNEIFORM SIGN USHX*/
+ RTUNI_ALPHA, /* U+012357: CUNEIFORM SIGN USH2*/
+ RTUNI_ALPHA, /* U+012358: CUNEIFORM SIGN USHUMX*/
+ RTUNI_ALPHA, /* U+012359: CUNEIFORM SIGN UTUKI*/
+ RTUNI_ALPHA, /* U+01235a: CUNEIFORM SIGN UZ3*/
+ RTUNI_ALPHA, /* U+01235b: CUNEIFORM SIGN UZ3 TIMES KASKAL*/
+ RTUNI_ALPHA, /* U+01235c: CUNEIFORM SIGN UZU*/
+ RTUNI_ALPHA, /* U+01235d: CUNEIFORM SIGN ZA*/
+ RTUNI_ALPHA, /* U+01235e: CUNEIFORM SIGN ZA TENU*/
+ RTUNI_ALPHA, /* U+01235f: CUNEIFORM SIGN ZA SQUARED TIMES KUR*/
+ RTUNI_ALPHA, /* U+012360: CUNEIFORM SIGN ZAG*/
+ RTUNI_ALPHA, /* U+012361: CUNEIFORM SIGN ZAMX*/
+ RTUNI_ALPHA, /* U+012362: CUNEIFORM SIGN ZE2*/
+ RTUNI_ALPHA, /* U+012363: CUNEIFORM SIGN ZI*/
+ RTUNI_ALPHA, /* U+012364: CUNEIFORM SIGN ZI OVER ZI*/
+ RTUNI_ALPHA, /* U+012365: CUNEIFORM SIGN ZI3*/
+ RTUNI_ALPHA, /* U+012366: CUNEIFORM SIGN ZIB*/
+ RTUNI_ALPHA, /* U+012367: CUNEIFORM SIGN ZIB KABA TENU*/
+ RTUNI_ALPHA, /* U+012368: CUNEIFORM SIGN ZIG*/
+ RTUNI_ALPHA, /* U+012369: CUNEIFORM SIGN ZIZ2*/
+ RTUNI_ALPHA, /* U+01236a: CUNEIFORM SIGN ZU*/
+ RTUNI_ALPHA, /* U+01236b: CUNEIFORM SIGN ZU5*/
+ RTUNI_ALPHA, /* U+01236c: CUNEIFORM SIGN ZU5 TIMES A*/
+ RTUNI_ALPHA, /* U+01236d: CUNEIFORM SIGN ZUBUR*/
+ RTUNI_ALPHA, /* U+01236e: CUNEIFORM SIGN ZUM*/
+ 0, /* U+01236f: */
+ 0, /* U+012370: */
+ 0, /* U+012371: */
+ 0, /* U+012372: */
+ 0, /* U+012373: */
+ 0, /* U+012374: */
+ 0, /* U+012375: */
+ 0, /* U+012376: */
+ 0, /* U+012377: */
+ 0, /* U+012378: */
+ 0, /* U+012379: */
+ 0, /* U+01237a: */
+ 0, /* U+01237b: */
+ 0, /* U+01237c: */
+ 0, /* U+01237d: */
+ 0, /* U+01237e: */
+ 0, /* U+01237f: */
+ 0, /* U+012380: */
+ 0, /* U+012381: */
+ 0, /* U+012382: */
+ 0, /* U+012383: */
+ 0, /* U+012384: */
+ 0, /* U+012385: */
+ 0, /* U+012386: */
+ 0, /* U+012387: */
+ 0, /* U+012388: */
+ 0, /* U+012389: */
+ 0, /* U+01238a: */
+ 0, /* U+01238b: */
+ 0, /* U+01238c: */
+ 0, /* U+01238d: */
+ 0, /* U+01238e: */
+ 0, /* U+01238f: */
+ 0, /* U+012390: */
+ 0, /* U+012391: */
+ 0, /* U+012392: */
+ 0, /* U+012393: */
+ 0, /* U+012394: */
+ 0, /* U+012395: */
+ 0, /* U+012396: */
+ 0, /* U+012397: */
+ 0, /* U+012398: */
+ 0, /* U+012399: */
+ 0, /* U+01239a: */
+ 0, /* U+01239b: */
+ 0, /* U+01239c: */
+ 0, /* U+01239d: */
+ 0, /* U+01239e: */
+ 0, /* U+01239f: */
+ 0, /* U+0123a0: */
+ 0, /* U+0123a1: */
+ 0, /* U+0123a2: */
+ 0, /* U+0123a3: */
+ 0, /* U+0123a4: */
+ 0, /* U+0123a5: */
+ 0, /* U+0123a6: */
+ 0, /* U+0123a7: */
+ 0, /* U+0123a8: */
+ 0, /* U+0123a9: */
+ 0, /* U+0123aa: */
+ 0, /* U+0123ab: */
+ 0, /* U+0123ac: */
+ 0, /* U+0123ad: */
+ 0, /* U+0123ae: */
+ 0, /* U+0123af: */
+ 0, /* U+0123b0: */
+ 0, /* U+0123b1: */
+ 0, /* U+0123b2: */
+ 0, /* U+0123b3: */
+ 0, /* U+0123b4: */
+ 0, /* U+0123b5: */
+ 0, /* U+0123b6: */
+ 0, /* U+0123b7: */
+ 0, /* U+0123b8: */
+ 0, /* U+0123b9: */
+ 0, /* U+0123ba: */
+ 0, /* U+0123bb: */
+ 0, /* U+0123bc: */
+ 0, /* U+0123bd: */
+ 0, /* U+0123be: */
+ 0, /* U+0123bf: */
+ 0, /* U+0123c0: */
+ 0, /* U+0123c1: */
+ 0, /* U+0123c2: */
+ 0, /* U+0123c3: */
+ 0, /* U+0123c4: */
+ 0, /* U+0123c5: */
+ 0, /* U+0123c6: */
+ 0, /* U+0123c7: */
+ 0, /* U+0123c8: */
+ 0, /* U+0123c9: */
+ 0, /* U+0123ca: */
+ 0, /* U+0123cb: */
+ 0, /* U+0123cc: */
+ 0, /* U+0123cd: */
+ 0, /* U+0123ce: */
+ 0, /* U+0123cf: */
+ 0, /* U+0123d0: */
+ 0, /* U+0123d1: */
+ 0, /* U+0123d2: */
+ 0, /* U+0123d3: */
+ 0, /* U+0123d4: */
+ 0, /* U+0123d5: */
+ 0, /* U+0123d6: */
+ 0, /* U+0123d7: */
+ 0, /* U+0123d8: */
+ 0, /* U+0123d9: */
+ 0, /* U+0123da: */
+ 0, /* U+0123db: */
+ 0, /* U+0123dc: */
+ 0, /* U+0123dd: */
+ 0, /* U+0123de: */
+ 0, /* U+0123df: */
+ 0, /* U+0123e0: */
+ 0, /* U+0123e1: */
+ 0, /* U+0123e2: */
+ 0, /* U+0123e3: */
+ 0, /* U+0123e4: */
+ 0, /* U+0123e5: */
+ 0, /* U+0123e6: */
+ 0, /* U+0123e7: */
+ 0, /* U+0123e8: */
+ 0, /* U+0123e9: */
+ 0, /* U+0123ea: */
+ 0, /* U+0123eb: */
+ 0, /* U+0123ec: */
+ 0, /* U+0123ed: */
+ 0, /* U+0123ee: */
+ 0, /* U+0123ef: */
+ 0, /* U+0123f0: */
+ 0, /* U+0123f1: */
+ 0, /* U+0123f2: */
+ 0, /* U+0123f3: */
+ 0, /* U+0123f4: */
+ 0, /* U+0123f5: */
+ 0, /* U+0123f6: */
+ 0, /* U+0123f7: */
+ 0, /* U+0123f8: */
+ 0, /* U+0123f9: */
+ 0, /* U+0123fa: */
+ 0, /* U+0123fb: */
+ 0, /* U+0123fc: */
+ 0, /* U+0123fd: */
+ 0, /* U+0123fe: */
+ 0, /* U+0123ff: */
+ RTUNI_ALPHA, /* U+012400: CUNEIFORM NUMERIC SIGN TWO ASH*/
+ RTUNI_ALPHA, /* U+012401: CUNEIFORM NUMERIC SIGN THREE ASH*/
+ RTUNI_ALPHA, /* U+012402: CUNEIFORM NUMERIC SIGN FOUR ASH*/
+ RTUNI_ALPHA, /* U+012403: CUNEIFORM NUMERIC SIGN FIVE ASH*/
+ RTUNI_ALPHA, /* U+012404: CUNEIFORM NUMERIC SIGN SIX ASH*/
+ RTUNI_ALPHA, /* U+012405: CUNEIFORM NUMERIC SIGN SEVEN ASH*/
+ RTUNI_ALPHA, /* U+012406: CUNEIFORM NUMERIC SIGN EIGHT ASH*/
+ RTUNI_ALPHA, /* U+012407: CUNEIFORM NUMERIC SIGN NINE ASH*/
+ RTUNI_ALPHA, /* U+012408: CUNEIFORM NUMERIC SIGN THREE DISH*/
+ RTUNI_ALPHA, /* U+012409: CUNEIFORM NUMERIC SIGN FOUR DISH*/
+ RTUNI_ALPHA, /* U+01240a: CUNEIFORM NUMERIC SIGN FIVE DISH*/
+ RTUNI_ALPHA, /* U+01240b: CUNEIFORM NUMERIC SIGN SIX DISH*/
+ RTUNI_ALPHA, /* U+01240c: CUNEIFORM NUMERIC SIGN SEVEN DISH*/
+ RTUNI_ALPHA, /* U+01240d: CUNEIFORM NUMERIC SIGN EIGHT DISH*/
+ RTUNI_ALPHA, /* U+01240e: CUNEIFORM NUMERIC SIGN NINE DISH*/
+ RTUNI_ALPHA, /* U+01240f: CUNEIFORM NUMERIC SIGN FOUR U*/
+ RTUNI_ALPHA, /* U+012410: CUNEIFORM NUMERIC SIGN FIVE U*/
+ RTUNI_ALPHA, /* U+012411: CUNEIFORM NUMERIC SIGN SIX U*/
+ RTUNI_ALPHA, /* U+012412: CUNEIFORM NUMERIC SIGN SEVEN U*/
+ RTUNI_ALPHA, /* U+012413: CUNEIFORM NUMERIC SIGN EIGHT U*/
+ RTUNI_ALPHA, /* U+012414: CUNEIFORM NUMERIC SIGN NINE U*/
+ RTUNI_ALPHA, /* U+012415: CUNEIFORM NUMERIC SIGN ONE GESH2*/
+ RTUNI_ALPHA, /* U+012416: CUNEIFORM NUMERIC SIGN TWO GESH2*/
+ RTUNI_ALPHA, /* U+012417: CUNEIFORM NUMERIC SIGN THREE GESH2*/
+ RTUNI_ALPHA, /* U+012418: CUNEIFORM NUMERIC SIGN FOUR GESH2*/
+ RTUNI_ALPHA, /* U+012419: CUNEIFORM NUMERIC SIGN FIVE GESH2*/
+ RTUNI_ALPHA, /* U+01241a: CUNEIFORM NUMERIC SIGN SIX GESH2*/
+ RTUNI_ALPHA, /* U+01241b: CUNEIFORM NUMERIC SIGN SEVEN GESH2*/
+ RTUNI_ALPHA, /* U+01241c: CUNEIFORM NUMERIC SIGN EIGHT GESH2*/
+ RTUNI_ALPHA, /* U+01241d: CUNEIFORM NUMERIC SIGN NINE GESH2*/
+ RTUNI_ALPHA, /* U+01241e: CUNEIFORM NUMERIC SIGN ONE GESHU*/
+ RTUNI_ALPHA, /* U+01241f: CUNEIFORM NUMERIC SIGN TWO GESHU*/
+ RTUNI_ALPHA, /* U+012420: CUNEIFORM NUMERIC SIGN THREE GESHU*/
+ RTUNI_ALPHA, /* U+012421: CUNEIFORM NUMERIC SIGN FOUR GESHU*/
+ RTUNI_ALPHA, /* U+012422: CUNEIFORM NUMERIC SIGN FIVE GESHU*/
+ RTUNI_ALPHA, /* U+012423: CUNEIFORM NUMERIC SIGN TWO SHAR2*/
+ RTUNI_ALPHA, /* U+012424: CUNEIFORM NUMERIC SIGN THREE SHAR2*/
+ RTUNI_ALPHA, /* U+012425: CUNEIFORM NUMERIC SIGN THREE SHAR2 VARIANT FORM*/
+ RTUNI_ALPHA, /* U+012426: CUNEIFORM NUMERIC SIGN FOUR SHAR2*/
+ RTUNI_ALPHA, /* U+012427: CUNEIFORM NUMERIC SIGN FIVE SHAR2*/
+ RTUNI_ALPHA, /* U+012428: CUNEIFORM NUMERIC SIGN SIX SHAR2*/
+ RTUNI_ALPHA, /* U+012429: CUNEIFORM NUMERIC SIGN SEVEN SHAR2*/
+ RTUNI_ALPHA, /* U+01242a: CUNEIFORM NUMERIC SIGN EIGHT SHAR2*/
+ RTUNI_ALPHA, /* U+01242b: CUNEIFORM NUMERIC SIGN NINE SHAR2*/
+ RTUNI_ALPHA, /* U+01242c: CUNEIFORM NUMERIC SIGN ONE SHARU*/
+ RTUNI_ALPHA, /* U+01242d: CUNEIFORM NUMERIC SIGN TWO SHARU*/
+ RTUNI_ALPHA, /* U+01242e: CUNEIFORM NUMERIC SIGN THREE SHARU*/
+ RTUNI_ALPHA, /* U+01242f: CUNEIFORM NUMERIC SIGN THREE SHARU VARIANT FORM*/
+ RTUNI_ALPHA, /* U+012430: CUNEIFORM NUMERIC SIGN FOUR SHARU*/
+ RTUNI_ALPHA, /* U+012431: CUNEIFORM NUMERIC SIGN FIVE SHARU*/
+ RTUNI_ALPHA, /* U+012432: CUNEIFORM NUMERIC SIGN SHAR2 TIMES GAL PLUS DISH*/
+ RTUNI_ALPHA, /* U+012433: CUNEIFORM NUMERIC SIGN SHAR2 TIMES GAL PLUS MIN*/
+ RTUNI_ALPHA, /* U+012434: CUNEIFORM NUMERIC SIGN ONE BURU*/
+ RTUNI_ALPHA, /* U+012435: CUNEIFORM NUMERIC SIGN TWO BURU*/
+ RTUNI_ALPHA, /* U+012436: CUNEIFORM NUMERIC SIGN THREE BURU*/
+ RTUNI_ALPHA, /* U+012437: CUNEIFORM NUMERIC SIGN THREE BURU VARIANT FORM*/
+ RTUNI_ALPHA, /* U+012438: CUNEIFORM NUMERIC SIGN FOUR BURU*/
+ RTUNI_ALPHA, /* U+012439: CUNEIFORM NUMERIC SIGN FIVE BURU*/
+ RTUNI_ALPHA, /* U+01243a: CUNEIFORM NUMERIC SIGN THREE VARIANT FORM ESH16*/
+ RTUNI_ALPHA, /* U+01243b: CUNEIFORM NUMERIC SIGN THREE VARIANT FORM ESH21*/
+ RTUNI_ALPHA, /* U+01243c: CUNEIFORM NUMERIC SIGN FOUR VARIANT FORM LIMMU*/
+ RTUNI_ALPHA, /* U+01243d: CUNEIFORM NUMERIC SIGN FOUR VARIANT FORM LIMMU4*/
+ RTUNI_ALPHA, /* U+01243e: CUNEIFORM NUMERIC SIGN FOUR VARIANT FORM LIMMU A*/
+ RTUNI_ALPHA, /* U+01243f: CUNEIFORM NUMERIC SIGN FOUR VARIANT FORM LIMMU B*/
+ RTUNI_ALPHA, /* U+012440: CUNEIFORM NUMERIC SIGN SIX VARIANT FORM ASH9*/
+ RTUNI_ALPHA, /* U+012441: CUNEIFORM NUMERIC SIGN SEVEN VARIANT FORM IMIN3*/
+ RTUNI_ALPHA, /* U+012442: CUNEIFORM NUMERIC SIGN SEVEN VARIANT FORM IMIN A*/
+ RTUNI_ALPHA, /* U+012443: CUNEIFORM NUMERIC SIGN SEVEN VARIANT FORM IMIN B*/
+ RTUNI_ALPHA, /* U+012444: CUNEIFORM NUMERIC SIGN EIGHT VARIANT FORM USSU*/
+ RTUNI_ALPHA, /* U+012445: CUNEIFORM NUMERIC SIGN EIGHT VARIANT FORM USSU3*/
+ RTUNI_ALPHA, /* U+012446: CUNEIFORM NUMERIC SIGN NINE VARIANT FORM ILIMMU*/
+ RTUNI_ALPHA, /* U+012447: CUNEIFORM NUMERIC SIGN NINE VARIANT FORM ILIMMU3*/
+ RTUNI_ALPHA, /* U+012448: CUNEIFORM NUMERIC SIGN NINE VARIANT FORM ILIMMU4*/
+ RTUNI_ALPHA, /* U+012449: CUNEIFORM NUMERIC SIGN NINE VARIANT FORM ILIMMU A*/
+ RTUNI_ALPHA, /* U+01244a: CUNEIFORM NUMERIC SIGN TWO ASH TENU*/
+ RTUNI_ALPHA, /* U+01244b: CUNEIFORM NUMERIC SIGN THREE ASH TENU*/
+ RTUNI_ALPHA, /* U+01244c: CUNEIFORM NUMERIC SIGN FOUR ASH TENU*/
+ RTUNI_ALPHA, /* U+01244d: CUNEIFORM NUMERIC SIGN FIVE ASH TENU*/
+ RTUNI_ALPHA, /* U+01244e: CUNEIFORM NUMERIC SIGN SIX ASH TENU*/
+ RTUNI_ALPHA, /* U+01244f: CUNEIFORM NUMERIC SIGN ONE BAN2*/
+ RTUNI_ALPHA, /* U+012450: CUNEIFORM NUMERIC SIGN TWO BAN2*/
+ RTUNI_ALPHA, /* U+012451: CUNEIFORM NUMERIC SIGN THREE BAN2*/
+ RTUNI_ALPHA, /* U+012452: CUNEIFORM NUMERIC SIGN FOUR BAN2*/
+ RTUNI_ALPHA, /* U+012453: CUNEIFORM NUMERIC SIGN FOUR BAN2 VARIANT FORM*/
+ RTUNI_ALPHA, /* U+012454: CUNEIFORM NUMERIC SIGN FIVE BAN2*/
+ RTUNI_ALPHA, /* U+012455: CUNEIFORM NUMERIC SIGN FIVE BAN2 VARIANT FORM*/
+ RTUNI_ALPHA, /* U+012456: CUNEIFORM NUMERIC SIGN NIGIDAMIN*/
+ RTUNI_ALPHA, /* U+012457: CUNEIFORM NUMERIC SIGN NIGIDAESH*/
+ RTUNI_ALPHA, /* U+012458: CUNEIFORM NUMERIC SIGN ONE ESHE3*/
+ RTUNI_ALPHA, /* U+012459: CUNEIFORM NUMERIC SIGN TWO ESHE3*/
+ RTUNI_ALPHA, /* U+01245a: CUNEIFORM NUMERIC SIGN ONE THIRD DISH*/
+ RTUNI_ALPHA, /* U+01245b: CUNEIFORM NUMERIC SIGN TWO THIRDS DISH*/
+ RTUNI_ALPHA, /* U+01245c: CUNEIFORM NUMERIC SIGN FIVE SIXTHS DISH*/
+ RTUNI_ALPHA, /* U+01245d: CUNEIFORM NUMERIC SIGN ONE THIRD VARIANT FORM A*/
+ RTUNI_ALPHA, /* U+01245e: CUNEIFORM NUMERIC SIGN TWO THIRDS VARIANT FORM A*/
+ RTUNI_ALPHA, /* U+01245f: CUNEIFORM NUMERIC SIGN ONE EIGHTH ASH*/
+ RTUNI_ALPHA, /* U+012460: CUNEIFORM NUMERIC SIGN ONE QUARTER ASH*/
+ RTUNI_ALPHA, /* U+012461: CUNEIFORM NUMERIC SIGN OLD ASSYRIAN ONE SIXTH*/
+ RTUNI_ALPHA, /* U+012462: CUNEIFORM NUMERIC SIGN OLD ASSYRIAN ONE QUARTER*/
+ 0, /* U+012463: */
+ 0, /* U+012464: */
+ 0, /* U+012465: */
+ 0, /* U+012466: */
+ 0, /* U+012467: */
+ 0, /* U+012468: */
+ 0, /* U+012469: */
+ 0, /* U+01246a: */
+ 0, /* U+01246b: */
+ 0, /* U+01246c: */
+ 0, /* U+01246d: */
+ 0, /* U+01246e: */
+ 0, /* U+01246f: */
+ 0, /* U+012470: CUNEIFORM PUNCTUATION SIGN OLD ASSYRIAN WORD DIVIDER*/
+ 0, /* U+012471: CUNEIFORM PUNCTUATION SIGN VERTICAL COLON*/
+ 0, /* U+012472: CUNEIFORM PUNCTUATION SIGN DIAGONAL COLON*/
+ 0, /* U+012473: CUNEIFORM PUNCTUATION SIGN DIAGONAL TRICOLON*/
+ 0, /* U+012474: */
+ 0, /* U+012475: */
+ 0, /* U+012476: */
+ 0, /* U+012477: */
+ 0, /* U+012478: */
+ 0, /* U+012479: */
+ 0, /* U+01247a: */
+ 0, /* U+01247b: */
+ 0, /* U+01247c: */
+ 0, /* U+01247d: */
+ 0, /* U+01247e: */
+ 0, /* U+01247f: */
+ 0, /* U+012480: */
+ 0, /* U+012481: */
+ 0, /* U+012482: */
+ 0, /* U+012483: */
+ 0, /* U+012484: */
+ 0, /* U+012485: */
+ 0, /* U+012486: */
+ 0, /* U+012487: */
+ 0, /* U+012488: */
+ 0, /* U+012489: */
+ 0, /* U+01248a: */
+ 0, /* U+01248b: */
+ 0, /* U+01248c: */
+ 0, /* U+01248d: */
+ 0, /* U+01248e: */
+ 0, /* U+01248f: */
+ 0, /* U+012490: */
+ 0, /* U+012491: */
+ 0, /* U+012492: */
+ 0, /* U+012493: */
+ 0, /* U+012494: */
+ 0, /* U+012495: */
+ 0, /* U+012496: */
+ 0, /* U+012497: */
+ 0, /* U+012498: */
+ 0, /* U+012499: */
+ 0, /* U+01249a: */
+ 0, /* U+01249b: */
+ 0, /* U+01249c: */
+ 0, /* U+01249d: */
+ 0, /* U+01249e: */
+ 0, /* U+01249f: */
+ 0, /* U+0124a0: */
+ 0, /* U+0124a1: */
+ 0, /* U+0124a2: */
+ 0, /* U+0124a3: */
+ 0, /* U+0124a4: */
+ 0, /* U+0124a5: */
+ 0, /* U+0124a6: */
+ 0, /* U+0124a7: */
+ 0, /* U+0124a8: */
+ 0, /* U+0124a9: */
+ 0, /* U+0124aa: */
+ 0, /* U+0124ab: */
+ 0, /* U+0124ac: */
+ 0, /* U+0124ad: */
+ 0, /* U+0124ae: */
+ 0, /* U+0124af: */
+ 0, /* U+0124b0: */
+ 0, /* U+0124b1: */
+ 0, /* U+0124b2: */
+ 0, /* U+0124b3: */
+ 0, /* U+0124b4: */
+ 0, /* U+0124b5: */
+ 0, /* U+0124b6: */
+ 0, /* U+0124b7: */
+ 0, /* U+0124b8: */
+ 0, /* U+0124b9: */
+ 0, /* U+0124ba: */
+ 0, /* U+0124bb: */
+ 0, /* U+0124bc: */
+ 0, /* U+0124bd: */
+ 0, /* U+0124be: */
+ 0, /* U+0124bf: */
+ 0, /* U+0124c0: */
+ 0, /* U+0124c1: */
+ 0, /* U+0124c2: */
+ 0, /* U+0124c3: */
+ 0, /* U+0124c4: */
+ 0, /* U+0124c5: */
+ 0, /* U+0124c6: */
+ 0, /* U+0124c7: */
+ 0, /* U+0124c8: */
+ 0, /* U+0124c9: */
+ 0, /* U+0124ca: */
+ 0, /* U+0124cb: */
+ 0, /* U+0124cc: */
+ 0, /* U+0124cd: */
+ 0, /* U+0124ce: */
+ 0, /* U+0124cf: */
+ 0, /* U+0124d0: */
+ 0, /* U+0124d1: */
+ 0, /* U+0124d2: */
+ 0, /* U+0124d3: */
+ 0, /* U+0124d4: */
+ 0, /* U+0124d5: */
+ 0, /* U+0124d6: */
+ 0, /* U+0124d7: */
+ 0, /* U+0124d8: */
+ 0, /* U+0124d9: */
+ 0, /* U+0124da: */
+ 0, /* U+0124db: */
+ 0, /* U+0124dc: */
+ 0, /* U+0124dd: */
+ 0, /* U+0124de: */
+ 0, /* U+0124df: */
+ 0, /* U+0124e0: */
+ 0, /* U+0124e1: */
+ 0, /* U+0124e2: */
+ 0, /* U+0124e3: */
+ 0, /* U+0124e4: */
+ 0, /* U+0124e5: */
+ 0, /* U+0124e6: */
+ 0, /* U+0124e7: */
+ 0, /* U+0124e8: */
+ 0, /* U+0124e9: */
+ 0, /* U+0124ea: */
+ 0, /* U+0124eb: */
+ 0, /* U+0124ec: */
+ 0, /* U+0124ed: */
+ 0, /* U+0124ee: */
+ 0, /* U+0124ef: */
+ 0, /* U+0124f0: */
+ 0, /* U+0124f1: */
+ 0, /* U+0124f2: */
+ 0, /* U+0124f3: */
+ 0, /* U+0124f4: */
+ 0, /* U+0124f5: */
+ 0, /* U+0124f6: */
+ 0, /* U+0124f7: */
+ 0, /* U+0124f8: */
+ 0, /* U+0124f9: */
+ 0, /* U+0124fa: */
+ 0, /* U+0124fb: */
+ 0, /* U+0124fc: */
+ 0, /* U+0124fd: */
+ 0, /* U+0124fe: */
+ 0, /* U+0124ff: */
+ 0, /* U+012500: */
+ 0, /* U+012501: */
+ 0, /* U+012502: */
+ 0, /* U+012503: */
+ 0, /* U+012504: */
+ 0, /* U+012505: */
+ 0, /* U+012506: */
+ 0, /* U+012507: */
+ 0, /* U+012508: */
+ 0, /* U+012509: */
+ 0, /* U+01250a: */
+ 0, /* U+01250b: */
+ 0, /* U+01250c: */
+ 0, /* U+01250d: */
+ 0, /* U+01250e: */
+ 0, /* U+01250f: */
+ 0, /* U+012510: */
+ 0, /* U+012511: */
+ 0, /* U+012512: */
+ 0, /* U+012513: */
+ 0, /* U+012514: */
+ 0, /* U+012515: */
+ 0, /* U+012516: */
+ 0, /* U+012517: */
+ 0, /* U+012518: */
+ 0, /* U+012519: */
+ 0, /* U+01251a: */
+ 0, /* U+01251b: */
+ 0, /* U+01251c: */
+ 0, /* U+01251d: */
+ 0, /* U+01251e: */
+ 0, /* U+01251f: */
+ 0, /* U+012520: */
+ 0, /* U+012521: */
+ 0, /* U+012522: */
+ 0, /* U+012523: */
+ 0, /* U+012524: */
+ 0, /* U+012525: */
+ 0, /* U+012526: */
+ 0, /* U+012527: */
+ 0, /* U+012528: */
+ 0, /* U+012529: */
+ 0, /* U+01252a: */
+ 0, /* U+01252b: */
+ 0, /* U+01252c: */
+ 0, /* U+01252d: */
+ 0, /* U+01252e: */
+ 0, /* U+01252f: */
+ 0, /* U+012530: */
+ 0, /* U+012531: */
+ 0, /* U+012532: */
+ 0, /* U+012533: */
+ 0, /* U+012534: */
+ 0, /* U+012535: */
+ 0, /* U+012536: */
+ 0, /* U+012537: */
+ 0, /* U+012538: */
+ 0, /* U+012539: */
+ 0, /* U+01253a: */
+ 0, /* U+01253b: */
+ 0, /* U+01253c: */
+ 0, /* U+01253d: */
+ 0, /* U+01253e: */
+ 0, /* U+01253f: */
+ 0, /* U+012540: */
+ 0, /* U+012541: */
+ 0, /* U+012542: */
+ 0, /* U+012543: */
+ 0, /* U+012544: */
+ 0, /* U+012545: */
+ 0, /* U+012546: */
+ 0, /* U+012547: */
+ 0, /* U+012548: */
+ 0, /* U+012549: */
+ 0, /* U+01254a: */
+ 0, /* U+01254b: */
+ 0, /* U+01254c: */
+ 0, /* U+01254d: */
+ 0, /* U+01254e: */
+ 0, /* U+01254f: */
+ 0, /* U+012550: */
+ 0, /* U+012551: */
+ 0, /* U+012552: */
+ 0, /* U+012553: */
+ 0, /* U+012554: */
+ 0, /* U+012555: */
+ 0, /* U+012556: */
+ 0, /* U+012557: */
+ 0, /* U+012558: */
+ 0, /* U+012559: */
+ 0, /* U+01255a: */
+ 0, /* U+01255b: */
+ 0, /* U+01255c: */
+ 0, /* U+01255d: */
+ 0, /* U+01255e: */
+ 0, /* U+01255f: */
+ 0, /* U+012560: */
+ 0, /* U+012561: */
+ 0, /* U+012562: */
+ 0, /* U+012563: */
+ 0, /* U+012564: */
+ 0, /* U+012565: */
+ 0, /* U+012566: */
+ 0, /* U+012567: */
+ 0, /* U+012568: */
+ 0, /* U+012569: */
+ 0, /* U+01256a: */
+ 0, /* U+01256b: */
+ 0, /* U+01256c: */
+ 0, /* U+01256d: */
+ 0, /* U+01256e: */
+ 0, /* U+01256f: */
+ 0, /* U+012570: */
+ 0, /* U+012571: */
+ 0, /* U+012572: */
+ 0, /* U+012573: */
+ 0, /* U+012574: */
+ 0, /* U+012575: */
+ 0, /* U+012576: */
+ 0, /* U+012577: */
+ 0, /* U+012578: */
+ 0, /* U+012579: */
+ 0, /* U+01257a: */
+ 0, /* U+01257b: */
+ 0, /* U+01257c: */
+ 0, /* U+01257d: */
+ 0, /* U+01257e: */
+ 0, /* U+01257f: */
+ 0, /* U+012580: */
+ 0, /* U+012581: */
+ 0, /* U+012582: */
+ 0, /* U+012583: */
+ 0, /* U+012584: */
+ 0, /* U+012585: */
+ 0, /* U+012586: */
+ 0, /* U+012587: */
+ 0, /* U+012588: */
+ 0, /* U+012589: */
+ 0, /* U+01258a: */
+ 0, /* U+01258b: */
+ 0, /* U+01258c: */
+ 0, /* U+01258d: */
+ 0, /* U+01258e: */
+ 0, /* U+01258f: */
+ 0, /* U+012590: */
+ 0, /* U+012591: */
+ 0, /* U+012592: */
+ 0, /* U+012593: */
+ 0, /* U+012594: */
+ 0, /* U+012595: */
+ 0, /* U+012596: */
+ 0, /* U+012597: */
+ 0, /* U+012598: */
+ 0, /* U+012599: */
+ 0, /* U+01259a: */
+ 0, /* U+01259b: */
+ 0, /* U+01259c: */
+ 0, /* U+01259d: */
+ 0, /* U+01259e: */
+ 0, /* U+01259f: */
+ 0, /* U+0125a0: */
+ 0, /* U+0125a1: */
+ 0, /* U+0125a2: */
+ 0, /* U+0125a3: */
+ 0, /* U+0125a4: */
+ 0, /* U+0125a5: */
+ 0, /* U+0125a6: */
+ 0, /* U+0125a7: */
+ 0, /* U+0125a8: */
+ 0, /* U+0125a9: */
+ 0, /* U+0125aa: */
+ 0, /* U+0125ab: */
+ 0, /* U+0125ac: */
+ 0, /* U+0125ad: */
+ 0, /* U+0125ae: */
+ 0, /* U+0125af: */
+ 0, /* U+0125b0: */
+ 0, /* U+0125b1: */
+ 0, /* U+0125b2: */
+ 0, /* U+0125b3: */
+ 0, /* U+0125b4: */
+ 0, /* U+0125b5: */
+ 0, /* U+0125b6: */
+ 0, /* U+0125b7: */
+ 0, /* U+0125b8: */
+ 0, /* U+0125b9: */
+ 0, /* U+0125ba: */
+ 0, /* U+0125bb: */
+ 0, /* U+0125bc: */
+ 0, /* U+0125bd: */
+ 0, /* U+0125be: */
+ 0, /* U+0125bf: */
+ 0, /* U+0125c0: */
+ 0, /* U+0125c1: */
+ 0, /* U+0125c2: */
+ 0, /* U+0125c3: */
+ 0, /* U+0125c4: */
+ 0, /* U+0125c5: */
+ 0, /* U+0125c6: */
+ 0, /* U+0125c7: */
+ 0, /* U+0125c8: */
+ 0, /* U+0125c9: */
+ 0, /* U+0125ca: */
+ 0, /* U+0125cb: */
+ 0, /* U+0125cc: */
+ 0, /* U+0125cd: */
+ 0, /* U+0125ce: */
+ 0, /* U+0125cf: */
+ 0, /* U+0125d0: */
+ 0, /* U+0125d1: */
+ 0, /* U+0125d2: */
+ 0, /* U+0125d3: */
+ 0, /* U+0125d4: */
+ 0, /* U+0125d5: */
+ 0, /* U+0125d6: */
+ 0, /* U+0125d7: */
+ 0, /* U+0125d8: */
+ 0, /* U+0125d9: */
+ 0, /* U+0125da: */
+ 0, /* U+0125db: */
+ 0, /* U+0125dc: */
+ 0, /* U+0125dd: */
+ 0, /* U+0125de: */
+ 0, /* U+0125df: */
+ 0, /* U+0125e0: */
+ 0, /* U+0125e1: */
+ 0, /* U+0125e2: */
+ 0, /* U+0125e3: */
+ 0, /* U+0125e4: */
+ 0, /* U+0125e5: */
+ 0, /* U+0125e6: */
+ 0, /* U+0125e7: */
+ 0, /* U+0125e8: */
+ 0, /* U+0125e9: */
+ 0, /* U+0125ea: */
+ 0, /* U+0125eb: */
+ 0, /* U+0125ec: */
+ 0, /* U+0125ed: */
+ 0, /* U+0125ee: */
+ 0, /* U+0125ef: */
+ 0, /* U+0125f0: */
+ 0, /* U+0125f1: */
+ 0, /* U+0125f2: */
+ 0, /* U+0125f3: */
+ 0, /* U+0125f4: */
+ 0, /* U+0125f5: */
+ 0, /* U+0125f6: */
+ 0, /* U+0125f7: */
+ 0, /* U+0125f8: */
+ 0, /* U+0125f9: */
+ 0, /* U+0125fa: */
+ 0, /* U+0125fb: */
+ 0, /* U+0125fc: */
+ 0, /* U+0125fd: */
+ 0, /* U+0125fe: */
+ 0, /* U+0125ff: */
+ 0, /* U+012600: */
+ 0, /* U+012601: */
+ 0, /* U+012602: */
+ 0, /* U+012603: */
+ 0, /* U+012604: */
+ 0, /* U+012605: */
+ 0, /* U+012606: */
+ 0, /* U+012607: */
+ 0, /* U+012608: */
+ 0, /* U+012609: */
+ 0, /* U+01260a: */
+ 0, /* U+01260b: */
+ 0, /* U+01260c: */
+ 0, /* U+01260d: */
+ 0, /* U+01260e: */
+ 0, /* U+01260f: */
+ 0, /* U+012610: */
+ 0, /* U+012611: */
+ 0, /* U+012612: */
+ 0, /* U+012613: */
+ 0, /* U+012614: */
+ 0, /* U+012615: */
+ 0, /* U+012616: */
+ 0, /* U+012617: */
+ 0, /* U+012618: */
+ 0, /* U+012619: */
+ 0, /* U+01261a: */
+ 0, /* U+01261b: */
+ 0, /* U+01261c: */
+ 0, /* U+01261d: */
+ 0, /* U+01261e: */
+ 0, /* U+01261f: */
+ 0, /* U+012620: */
+ 0, /* U+012621: */
+ 0, /* U+012622: */
+ 0, /* U+012623: */
+ 0, /* U+012624: */
+ 0, /* U+012625: */
+ 0, /* U+012626: */
+ 0, /* U+012627: */
+ 0, /* U+012628: */
+ 0, /* U+012629: */
+ 0, /* U+01262a: */
+ 0, /* U+01262b: */
+ 0, /* U+01262c: */
+ 0, /* U+01262d: */
+ 0, /* U+01262e: */
+ 0, /* U+01262f: */
+ 0, /* U+012630: */
+ 0, /* U+012631: */
+ 0, /* U+012632: */
+ 0, /* U+012633: */
+ 0, /* U+012634: */
+ 0, /* U+012635: */
+ 0, /* U+012636: */
+ 0, /* U+012637: */
+ 0, /* U+012638: */
+ 0, /* U+012639: */
+ 0, /* U+01263a: */
+ 0, /* U+01263b: */
+ 0, /* U+01263c: */
+ 0, /* U+01263d: */
+ 0, /* U+01263e: */
+ 0, /* U+01263f: */
+ 0, /* U+012640: */
+ 0, /* U+012641: */
+ 0, /* U+012642: */
+ 0, /* U+012643: */
+ 0, /* U+012644: */
+ 0, /* U+012645: */
+ 0, /* U+012646: */
+ 0, /* U+012647: */
+ 0, /* U+012648: */
+ 0, /* U+012649: */
+ 0, /* U+01264a: */
+ 0, /* U+01264b: */
+ 0, /* U+01264c: */
+ 0, /* U+01264d: */
+ 0, /* U+01264e: */
+ 0, /* U+01264f: */
+ 0, /* U+012650: */
+ 0, /* U+012651: */
+ 0, /* U+012652: */
+ 0, /* U+012653: */
+ 0, /* U+012654: */
+ 0, /* U+012655: */
+ 0, /* U+012656: */
+ 0, /* U+012657: */
+ 0, /* U+012658: */
+ 0, /* U+012659: */
+ 0, /* U+01265a: */
+ 0, /* U+01265b: */
+ 0, /* U+01265c: */
+ 0, /* U+01265d: */
+ 0, /* U+01265e: */
+ 0, /* U+01265f: */
+ 0, /* U+012660: */
+ 0, /* U+012661: */
+ 0, /* U+012662: */
+ 0, /* U+012663: */
+ 0, /* U+012664: */
+ 0, /* U+012665: */
+ 0, /* U+012666: */
+ 0, /* U+012667: */
+ 0, /* U+012668: */
+ 0, /* U+012669: */
+ 0, /* U+01266a: */
+ 0, /* U+01266b: */
+ 0, /* U+01266c: */
+ 0, /* U+01266d: */
+ 0, /* U+01266e: */
+ 0, /* U+01266f: */
+ 0, /* U+012670: */
+ 0, /* U+012671: */
+ 0, /* U+012672: */
+ 0, /* U+012673: */
+ 0, /* U+012674: */
+ 0, /* U+012675: */
+ 0, /* U+012676: */
+ 0, /* U+012677: */
+ 0, /* U+012678: */
+ 0, /* U+012679: */
+ 0, /* U+01267a: */
+ 0, /* U+01267b: */
+ 0, /* U+01267c: */
+ 0, /* U+01267d: */
+ 0, /* U+01267e: */
+ 0, /* U+01267f: */
+ 0, /* U+012680: */
+ 0, /* U+012681: */
+ 0, /* U+012682: */
+ 0, /* U+012683: */
+ 0, /* U+012684: */
+ 0, /* U+012685: */
+ 0, /* U+012686: */
+ 0, /* U+012687: */
+ 0, /* U+012688: */
+ 0, /* U+012689: */
+ 0, /* U+01268a: */
+ 0, /* U+01268b: */
+ 0, /* U+01268c: */
+ 0, /* U+01268d: */
+ 0, /* U+01268e: */
+ 0, /* U+01268f: */
+ 0, /* U+012690: */
+ 0, /* U+012691: */
+ 0, /* U+012692: */
+ 0, /* U+012693: */
+ 0, /* U+012694: */
+ 0, /* U+012695: */
+ 0, /* U+012696: */
+ 0, /* U+012697: */
+ 0, /* U+012698: */
+ 0, /* U+012699: */
+ 0, /* U+01269a: */
+ 0, /* U+01269b: */
+ 0, /* U+01269c: */
+ 0, /* U+01269d: */
+ 0, /* U+01269e: */
+ 0, /* U+01269f: */
+ 0, /* U+0126a0: */
+ 0, /* U+0126a1: */
+ 0, /* U+0126a2: */
+ 0, /* U+0126a3: */
+ 0, /* U+0126a4: */
+ 0, /* U+0126a5: */
+ 0, /* U+0126a6: */
+ 0, /* U+0126a7: */
+ 0, /* U+0126a8: */
+ 0, /* U+0126a9: */
+ 0, /* U+0126aa: */
+ 0, /* U+0126ab: */
+ 0, /* U+0126ac: */
+ 0, /* U+0126ad: */
+ 0, /* U+0126ae: */
+ 0, /* U+0126af: */
+ 0, /* U+0126b0: */
+ 0, /* U+0126b1: */
+ 0, /* U+0126b2: */
+ 0, /* U+0126b3: */
+ 0, /* U+0126b4: */
+ 0, /* U+0126b5: */
+ 0, /* U+0126b6: */
+ 0, /* U+0126b7: */
+ 0, /* U+0126b8: */
+ 0, /* U+0126b9: */
+ 0, /* U+0126ba: */
+ 0, /* U+0126bb: */
+ 0, /* U+0126bc: */
+ 0, /* U+0126bd: */
+ 0, /* U+0126be: */
+ 0, /* U+0126bf: */
+ 0, /* U+0126c0: */
+ 0, /* U+0126c1: */
+ 0, /* U+0126c2: */
+ 0, /* U+0126c3: */
+ 0, /* U+0126c4: */
+ 0, /* U+0126c5: */
+ 0, /* U+0126c6: */
+ 0, /* U+0126c7: */
+ 0, /* U+0126c8: */
+ 0, /* U+0126c9: */
+ 0, /* U+0126ca: */
+ 0, /* U+0126cb: */
+ 0, /* U+0126cc: */
+ 0, /* U+0126cd: */
+ 0, /* U+0126ce: */
+ 0, /* U+0126cf: */
+ 0, /* U+0126d0: */
+ 0, /* U+0126d1: */
+ 0, /* U+0126d2: */
+ 0, /* U+0126d3: */
+ 0, /* U+0126d4: */
+ 0, /* U+0126d5: */
+ 0, /* U+0126d6: */
+ 0, /* U+0126d7: */
+ 0, /* U+0126d8: */
+ 0, /* U+0126d9: */
+ 0, /* U+0126da: */
+ 0, /* U+0126db: */
+ 0, /* U+0126dc: */
+ 0, /* U+0126dd: */
+ 0, /* U+0126de: */
+ 0, /* U+0126df: */
+ 0, /* U+0126e0: */
+ 0, /* U+0126e1: */
+ 0, /* U+0126e2: */
+ 0, /* U+0126e3: */
+ 0, /* U+0126e4: */
+ 0, /* U+0126e5: */
+ 0, /* U+0126e6: */
+ 0, /* U+0126e7: */
+ 0, /* U+0126e8: */
+ 0, /* U+0126e9: */
+ 0, /* U+0126ea: */
+ 0, /* U+0126eb: */
+ 0, /* U+0126ec: */
+ 0, /* U+0126ed: */
+ 0, /* U+0126ee: */
+ 0, /* U+0126ef: */
+ 0, /* U+0126f0: */
+ 0, /* U+0126f1: */
+ 0, /* U+0126f2: */
+ 0, /* U+0126f3: */
+ 0, /* U+0126f4: */
+ 0, /* U+0126f5: */
+ 0, /* U+0126f6: */
+ 0, /* U+0126f7: */
+ 0, /* U+0126f8: */
+ 0, /* U+0126f9: */
+ 0, /* U+0126fa: */
+ 0, /* U+0126fb: */
+ 0, /* U+0126fc: */
+ 0, /* U+0126fd: */
+ 0, /* U+0126fe: */
+ 0, /* U+0126ff: */
+ 0, /* U+012700: */
+ 0, /* U+012701: */
+ 0, /* U+012702: */
+ 0, /* U+012703: */
+ 0, /* U+012704: */
+ 0, /* U+012705: */
+ 0, /* U+012706: */
+ 0, /* U+012707: */
+ 0, /* U+012708: */
+ 0, /* U+012709: */
+ 0, /* U+01270a: */
+ 0, /* U+01270b: */
+ 0, /* U+01270c: */
+ 0, /* U+01270d: */
+ 0, /* U+01270e: */
+ 0, /* U+01270f: */
+ 0, /* U+012710: */
+ 0, /* U+012711: */
+ 0, /* U+012712: */
+ 0, /* U+012713: */
+ 0, /* U+012714: */
+ 0, /* U+012715: */
+ 0, /* U+012716: */
+ 0, /* U+012717: */
+ 0, /* U+012718: */
+ 0, /* U+012719: */
+ 0, /* U+01271a: */
+ 0, /* U+01271b: */
+ 0, /* U+01271c: */
+ 0, /* U+01271d: */
+ 0, /* U+01271e: */
+ 0, /* U+01271f: */
+ 0, /* U+012720: */
+ 0, /* U+012721: */
+ 0, /* U+012722: */
+ 0, /* U+012723: */
+ 0, /* U+012724: */
+ 0, /* U+012725: */
+ 0, /* U+012726: */
+ 0, /* U+012727: */
+ 0, /* U+012728: */
+ 0, /* U+012729: */
+ 0, /* U+01272a: */
+ 0, /* U+01272b: */
+ 0, /* U+01272c: */
+ 0, /* U+01272d: */
+ 0, /* U+01272e: */
+ 0, /* U+01272f: */
+ 0, /* U+012730: */
+ 0, /* U+012731: */
+ 0, /* U+012732: */
+ 0, /* U+012733: */
+ 0, /* U+012734: */
+ 0, /* U+012735: */
+ 0, /* U+012736: */
+ 0, /* U+012737: */
+ 0, /* U+012738: */
+ 0, /* U+012739: */
+ 0, /* U+01273a: */
+ 0, /* U+01273b: */
+ 0, /* U+01273c: */
+ 0, /* U+01273d: */
+ 0, /* U+01273e: */
+ 0, /* U+01273f: */
+ 0, /* U+012740: */
+ 0, /* U+012741: */
+ 0, /* U+012742: */
+ 0, /* U+012743: */
+ 0, /* U+012744: */
+ 0, /* U+012745: */
+ 0, /* U+012746: */
+ 0, /* U+012747: */
+ 0, /* U+012748: */
+ 0, /* U+012749: */
+ 0, /* U+01274a: */
+ 0, /* U+01274b: */
+ 0, /* U+01274c: */
+ 0, /* U+01274d: */
+ 0, /* U+01274e: */
+ 0, /* U+01274f: */
+ 0, /* U+012750: */
+ 0, /* U+012751: */
+ 0, /* U+012752: */
+ 0, /* U+012753: */
+ 0, /* U+012754: */
+ 0, /* U+012755: */
+ 0, /* U+012756: */
+ 0, /* U+012757: */
+ 0, /* U+012758: */
+ 0, /* U+012759: */
+ 0, /* U+01275a: */
+ 0, /* U+01275b: */
+ 0, /* U+01275c: */
+ 0, /* U+01275d: */
+ 0, /* U+01275e: */
+ 0, /* U+01275f: */
+ 0, /* U+012760: */
+ 0, /* U+012761: */
+ 0, /* U+012762: */
+ 0, /* U+012763: */
+ 0, /* U+012764: */
+ 0, /* U+012765: */
+ 0, /* U+012766: */
+ 0, /* U+012767: */
+ 0, /* U+012768: */
+ 0, /* U+012769: */
+ 0, /* U+01276a: */
+ 0, /* U+01276b: */
+ 0, /* U+01276c: */
+ 0, /* U+01276d: */
+ 0, /* U+01276e: */
+ 0, /* U+01276f: */
+ 0, /* U+012770: */
+ 0, /* U+012771: */
+ 0, /* U+012772: */
+ 0, /* U+012773: */
+ 0, /* U+012774: */
+ 0, /* U+012775: */
+ 0, /* U+012776: */
+ 0, /* U+012777: */
+ 0, /* U+012778: */
+ 0, /* U+012779: */
+ 0, /* U+01277a: */
+ 0, /* U+01277b: */
+ 0, /* U+01277c: */
+ 0, /* U+01277d: */
+ 0, /* U+01277e: */
+ 0, /* U+01277f: */
+ 0, /* U+012780: */
+ 0, /* U+012781: */
+ 0, /* U+012782: */
+ 0, /* U+012783: */
+ 0, /* U+012784: */
+ 0, /* U+012785: */
+ 0, /* U+012786: */
+ 0, /* U+012787: */
+ 0, /* U+012788: */
+ 0, /* U+012789: */
+ 0, /* U+01278a: */
+ 0, /* U+01278b: */
+ 0, /* U+01278c: */
+ 0, /* U+01278d: */
+ 0, /* U+01278e: */
+ 0, /* U+01278f: */
+ 0, /* U+012790: */
+ 0, /* U+012791: */
+ 0, /* U+012792: */
+ 0, /* U+012793: */
+ 0, /* U+012794: */
+ 0, /* U+012795: */
+ 0, /* U+012796: */
+ 0, /* U+012797: */
+ 0, /* U+012798: */
+ 0, /* U+012799: */
+ 0, /* U+01279a: */
+ 0, /* U+01279b: */
+ 0, /* U+01279c: */
+ 0, /* U+01279d: */
+ 0, /* U+01279e: */
+ 0, /* U+01279f: */
+ 0, /* U+0127a0: */
+ 0, /* U+0127a1: */
+ 0, /* U+0127a2: */
+ 0, /* U+0127a3: */
+ 0, /* U+0127a4: */
+ 0, /* U+0127a5: */
+ 0, /* U+0127a6: */
+ 0, /* U+0127a7: */
+ 0, /* U+0127a8: */
+ 0, /* U+0127a9: */
+ 0, /* U+0127aa: */
+ 0, /* U+0127ab: */
+ 0, /* U+0127ac: */
+ 0, /* U+0127ad: */
+ 0, /* U+0127ae: */
+ 0, /* U+0127af: */
+ 0, /* U+0127b0: */
+ 0, /* U+0127b1: */
+ 0, /* U+0127b2: */
+ 0, /* U+0127b3: */
+ 0, /* U+0127b4: */
+ 0, /* U+0127b5: */
+ 0, /* U+0127b6: */
+ 0, /* U+0127b7: */
+ 0, /* U+0127b8: */
+ 0, /* U+0127b9: */
+ 0, /* U+0127ba: */
+ 0, /* U+0127bb: */
+ 0, /* U+0127bc: */
+ 0, /* U+0127bd: */
+ 0, /* U+0127be: */
+ 0, /* U+0127bf: */
+ 0, /* U+0127c0: */
+ 0, /* U+0127c1: */
+ 0, /* U+0127c2: */
+ 0, /* U+0127c3: */
+ 0, /* U+0127c4: */
+ 0, /* U+0127c5: */
+ 0, /* U+0127c6: */
+ 0, /* U+0127c7: */
+ 0, /* U+0127c8: */
+ 0, /* U+0127c9: */
+ 0, /* U+0127ca: */
+ 0, /* U+0127cb: */
+ 0, /* U+0127cc: */
+ 0, /* U+0127cd: */
+ 0, /* U+0127ce: */
+ 0, /* U+0127cf: */
+ 0, /* U+0127d0: */
+ 0, /* U+0127d1: */
+ 0, /* U+0127d2: */
+ 0, /* U+0127d3: */
+ 0, /* U+0127d4: */
+ 0, /* U+0127d5: */
+ 0, /* U+0127d6: */
+ 0, /* U+0127d7: */
+ 0, /* U+0127d8: */
+ 0, /* U+0127d9: */
+ 0, /* U+0127da: */
+ 0, /* U+0127db: */
+ 0, /* U+0127dc: */
+ 0, /* U+0127dd: */
+ 0, /* U+0127de: */
+ 0, /* U+0127df: */
+ 0, /* U+0127e0: */
+ 0, /* U+0127e1: */
+ 0, /* U+0127e2: */
+ 0, /* U+0127e3: */
+ 0, /* U+0127e4: */
+ 0, /* U+0127e5: */
+ 0, /* U+0127e6: */
+ 0, /* U+0127e7: */
+ 0, /* U+0127e8: */
+ 0, /* U+0127e9: */
+ 0, /* U+0127ea: */
+ 0, /* U+0127eb: */
+ 0, /* U+0127ec: */
+ 0, /* U+0127ed: */
+ 0, /* U+0127ee: */
+ 0, /* U+0127ef: */
+ 0, /* U+0127f0: */
+ 0, /* U+0127f1: */
+ 0, /* U+0127f2: */
+ 0, /* U+0127f3: */
+ 0, /* U+0127f4: */
+ 0, /* U+0127f5: */
+ 0, /* U+0127f6: */
+ 0, /* U+0127f7: */
+ 0, /* U+0127f8: */
+ 0, /* U+0127f9: */
+ 0, /* U+0127fa: */
+ 0, /* U+0127fb: */
+ 0, /* U+0127fc: */
+ 0, /* U+0127fd: */
+ 0, /* U+0127fe: */
+ 0, /* U+0127ff: */
+ 0, /* U+012800: */
+ 0, /* U+012801: */
+ 0, /* U+012802: */
+ 0, /* U+012803: */
+ 0, /* U+012804: */
+ 0, /* U+012805: */
+ 0, /* U+012806: */
+ 0, /* U+012807: */
+ 0, /* U+012808: */
+ 0, /* U+012809: */
+ 0, /* U+01280a: */
+ 0, /* U+01280b: */
+ 0, /* U+01280c: */
+ 0, /* U+01280d: */
+ 0, /* U+01280e: */
+ 0, /* U+01280f: */
+ 0, /* U+012810: */
+ 0, /* U+012811: */
+ 0, /* U+012812: */
+ 0, /* U+012813: */
+ 0, /* U+012814: */
+ 0, /* U+012815: */
+ 0, /* U+012816: */
+ 0, /* U+012817: */
+ 0, /* U+012818: */
+ 0, /* U+012819: */
+ 0, /* U+01281a: */
+ 0, /* U+01281b: */
+ 0, /* U+01281c: */
+ 0, /* U+01281d: */
+ 0, /* U+01281e: */
+ 0, /* U+01281f: */
+ 0, /* U+012820: */
+ 0, /* U+012821: */
+ 0, /* U+012822: */
+ 0, /* U+012823: */
+ 0, /* U+012824: */
+ 0, /* U+012825: */
+ 0, /* U+012826: */
+ 0, /* U+012827: */
+ 0, /* U+012828: */
+ 0, /* U+012829: */
+ 0, /* U+01282a: */
+ 0, /* U+01282b: */
+ 0, /* U+01282c: */
+ 0, /* U+01282d: */
+ 0, /* U+01282e: */
+ 0, /* U+01282f: */
+ 0, /* U+012830: */
+ 0, /* U+012831: */
+ 0, /* U+012832: */
+ 0, /* U+012833: */
+ 0, /* U+012834: */
+ 0, /* U+012835: */
+ 0, /* U+012836: */
+ 0, /* U+012837: */
+ 0, /* U+012838: */
+ 0, /* U+012839: */
+ 0, /* U+01283a: */
+ 0, /* U+01283b: */
+ 0, /* U+01283c: */
+ 0, /* U+01283d: */
+ 0, /* U+01283e: */
+ 0, /* U+01283f: */
+ 0, /* U+012840: */
+ 0, /* U+012841: */
+ 0, /* U+012842: */
+ 0, /* U+012843: */
+ 0, /* U+012844: */
+ 0, /* U+012845: */
+ 0, /* U+012846: */
+ 0, /* U+012847: */
+ 0, /* U+012848: */
+ 0, /* U+012849: */
+ 0, /* U+01284a: */
+ 0, /* U+01284b: */
+ 0, /* U+01284c: */
+ 0, /* U+01284d: */
+ 0, /* U+01284e: */
+ 0, /* U+01284f: */
+ 0, /* U+012850: */
+ 0, /* U+012851: */
+ 0, /* U+012852: */
+ 0, /* U+012853: */
+ 0, /* U+012854: */
+ 0, /* U+012855: */
+ 0, /* U+012856: */
+ 0, /* U+012857: */
+ 0, /* U+012858: */
+ 0, /* U+012859: */
+ 0, /* U+01285a: */
+ 0, /* U+01285b: */
+ 0, /* U+01285c: */
+ 0, /* U+01285d: */
+ 0, /* U+01285e: */
+ 0, /* U+01285f: */
+ 0, /* U+012860: */
+ 0, /* U+012861: */
+ 0, /* U+012862: */
+ 0, /* U+012863: */
+ 0, /* U+012864: */
+ 0, /* U+012865: */
+ 0, /* U+012866: */
+ 0, /* U+012867: */
+ 0, /* U+012868: */
+ 0, /* U+012869: */
+ 0, /* U+01286a: */
+ 0, /* U+01286b: */
+ 0, /* U+01286c: */
+ 0, /* U+01286d: */
+ 0, /* U+01286e: */
+ 0, /* U+01286f: */
+ 0, /* U+012870: */
+ 0, /* U+012871: */
+ 0, /* U+012872: */
+ 0, /* U+012873: */
+ 0, /* U+012874: */
+ 0, /* U+012875: */
+ 0, /* U+012876: */
+ 0, /* U+012877: */
+ 0, /* U+012878: */
+ 0, /* U+012879: */
+ 0, /* U+01287a: */
+ 0, /* U+01287b: */
+ 0, /* U+01287c: */
+ 0, /* U+01287d: */
+ 0, /* U+01287e: */
+ 0, /* U+01287f: */
+ 0, /* U+012880: */
+ 0, /* U+012881: */
+ 0, /* U+012882: */
+ 0, /* U+012883: */
+ 0, /* U+012884: */
+ 0, /* U+012885: */
+ 0, /* U+012886: */
+ 0, /* U+012887: */
+ 0, /* U+012888: */
+ 0, /* U+012889: */
+ 0, /* U+01288a: */
+ 0, /* U+01288b: */
+ 0, /* U+01288c: */
+ 0, /* U+01288d: */
+ 0, /* U+01288e: */
+ 0, /* U+01288f: */
+ 0, /* U+012890: */
+ 0, /* U+012891: */
+ 0, /* U+012892: */
+ 0, /* U+012893: */
+ 0, /* U+012894: */
+ 0, /* U+012895: */
+ 0, /* U+012896: */
+ 0, /* U+012897: */
+ 0, /* U+012898: */
+ 0, /* U+012899: */
+ 0, /* U+01289a: */
+ 0, /* U+01289b: */
+ 0, /* U+01289c: */
+ 0, /* U+01289d: */
+ 0, /* U+01289e: */
+ 0, /* U+01289f: */
+ 0, /* U+0128a0: */
+ 0, /* U+0128a1: */
+ 0, /* U+0128a2: */
+ 0, /* U+0128a3: */
+ 0, /* U+0128a4: */
+ 0, /* U+0128a5: */
+ 0, /* U+0128a6: */
+ 0, /* U+0128a7: */
+ 0, /* U+0128a8: */
+ 0, /* U+0128a9: */
+ 0, /* U+0128aa: */
+ 0, /* U+0128ab: */
+ 0, /* U+0128ac: */
+ 0, /* U+0128ad: */
+ 0, /* U+0128ae: */
+ 0, /* U+0128af: */
+ 0, /* U+0128b0: */
+ 0, /* U+0128b1: */
+ 0, /* U+0128b2: */
+ 0, /* U+0128b3: */
+ 0, /* U+0128b4: */
+ 0, /* U+0128b5: */
+ 0, /* U+0128b6: */
+ 0, /* U+0128b7: */
+ 0, /* U+0128b8: */
+ 0, /* U+0128b9: */
+ 0, /* U+0128ba: */
+ 0, /* U+0128bb: */
+ 0, /* U+0128bc: */
+ 0, /* U+0128bd: */
+ 0, /* U+0128be: */
+ 0, /* U+0128bf: */
+ 0, /* U+0128c0: */
+ 0, /* U+0128c1: */
+ 0, /* U+0128c2: */
+ 0, /* U+0128c3: */
+ 0, /* U+0128c4: */
+ 0, /* U+0128c5: */
+ 0, /* U+0128c6: */
+ 0, /* U+0128c7: */
+ 0, /* U+0128c8: */
+ 0, /* U+0128c9: */
+ 0, /* U+0128ca: */
+ 0, /* U+0128cb: */
+ 0, /* U+0128cc: */
+ 0, /* U+0128cd: */
+ 0, /* U+0128ce: */
+ 0, /* U+0128cf: */
+ 0, /* U+0128d0: */
+ 0, /* U+0128d1: */
+ 0, /* U+0128d2: */
+ 0, /* U+0128d3: */
+ 0, /* U+0128d4: */
+ 0, /* U+0128d5: */
+ 0, /* U+0128d6: */
+ 0, /* U+0128d7: */
+ 0, /* U+0128d8: */
+ 0, /* U+0128d9: */
+ 0, /* U+0128da: */
+ 0, /* U+0128db: */
+ 0, /* U+0128dc: */
+ 0, /* U+0128dd: */
+ 0, /* U+0128de: */
+ 0, /* U+0128df: */
+ 0, /* U+0128e0: */
+ 0, /* U+0128e1: */
+ 0, /* U+0128e2: */
+ 0, /* U+0128e3: */
+ 0, /* U+0128e4: */
+ 0, /* U+0128e5: */
+ 0, /* U+0128e6: */
+ 0, /* U+0128e7: */
+ 0, /* U+0128e8: */
+ 0, /* U+0128e9: */
+ 0, /* U+0128ea: */
+ 0, /* U+0128eb: */
+ 0, /* U+0128ec: */
+ 0, /* U+0128ed: */
+ 0, /* U+0128ee: */
+ 0, /* U+0128ef: */
+ 0, /* U+0128f0: */
+ 0, /* U+0128f1: */
+ 0, /* U+0128f2: */
+ 0, /* U+0128f3: */
+ 0, /* U+0128f4: */
+ 0, /* U+0128f5: */
+ 0, /* U+0128f6: */
+ 0, /* U+0128f7: */
+ 0, /* U+0128f8: */
+ 0, /* U+0128f9: */
+ 0, /* U+0128fa: */
+ 0, /* U+0128fb: */
+ 0, /* U+0128fc: */
+ 0, /* U+0128fd: */
+ 0, /* U+0128fe: */
+ 0, /* U+0128ff: */
+ 0, /* U+012900: */
+ 0, /* U+012901: */
+ 0, /* U+012902: */
+ 0, /* U+012903: */
+ 0, /* U+012904: */
+ 0, /* U+012905: */
+ 0, /* U+012906: */
+ 0, /* U+012907: */
+ 0, /* U+012908: */
+ 0, /* U+012909: */
+ 0, /* U+01290a: */
+ 0, /* U+01290b: */
+ 0, /* U+01290c: */
+ 0, /* U+01290d: */
+ 0, /* U+01290e: */
+ 0, /* U+01290f: */
+ 0, /* U+012910: */
+ 0, /* U+012911: */
+ 0, /* U+012912: */
+ 0, /* U+012913: */
+ 0, /* U+012914: */
+ 0, /* U+012915: */
+ 0, /* U+012916: */
+ 0, /* U+012917: */
+ 0, /* U+012918: */
+ 0, /* U+012919: */
+ 0, /* U+01291a: */
+ 0, /* U+01291b: */
+ 0, /* U+01291c: */
+ 0, /* U+01291d: */
+ 0, /* U+01291e: */
+ 0, /* U+01291f: */
+ 0, /* U+012920: */
+ 0, /* U+012921: */
+ 0, /* U+012922: */
+ 0, /* U+012923: */
+ 0, /* U+012924: */
+ 0, /* U+012925: */
+ 0, /* U+012926: */
+ 0, /* U+012927: */
+ 0, /* U+012928: */
+ 0, /* U+012929: */
+ 0, /* U+01292a: */
+ 0, /* U+01292b: */
+ 0, /* U+01292c: */
+ 0, /* U+01292d: */
+ 0, /* U+01292e: */
+ 0, /* U+01292f: */
+ 0, /* U+012930: */
+ 0, /* U+012931: */
+ 0, /* U+012932: */
+ 0, /* U+012933: */
+ 0, /* U+012934: */
+ 0, /* U+012935: */
+ 0, /* U+012936: */
+ 0, /* U+012937: */
+ 0, /* U+012938: */
+ 0, /* U+012939: */
+ 0, /* U+01293a: */
+ 0, /* U+01293b: */
+ 0, /* U+01293c: */
+ 0, /* U+01293d: */
+ 0, /* U+01293e: */
+ 0, /* U+01293f: */
+ 0, /* U+012940: */
+ 0, /* U+012941: */
+ 0, /* U+012942: */
+ 0, /* U+012943: */
+ 0, /* U+012944: */
+ 0, /* U+012945: */
+ 0, /* U+012946: */
+ 0, /* U+012947: */
+ 0, /* U+012948: */
+ 0, /* U+012949: */
+ 0, /* U+01294a: */
+ 0, /* U+01294b: */
+ 0, /* U+01294c: */
+ 0, /* U+01294d: */
+ 0, /* U+01294e: */
+ 0, /* U+01294f: */
+ 0, /* U+012950: */
+ 0, /* U+012951: */
+ 0, /* U+012952: */
+ 0, /* U+012953: */
+ 0, /* U+012954: */
+ 0, /* U+012955: */
+ 0, /* U+012956: */
+ 0, /* U+012957: */
+ 0, /* U+012958: */
+ 0, /* U+012959: */
+ 0, /* U+01295a: */
+ 0, /* U+01295b: */
+ 0, /* U+01295c: */
+ 0, /* U+01295d: */
+ 0, /* U+01295e: */
+ 0, /* U+01295f: */
+ 0, /* U+012960: */
+ 0, /* U+012961: */
+ 0, /* U+012962: */
+ 0, /* U+012963: */
+ 0, /* U+012964: */
+ 0, /* U+012965: */
+ 0, /* U+012966: */
+ 0, /* U+012967: */
+ 0, /* U+012968: */
+ 0, /* U+012969: */
+ 0, /* U+01296a: */
+ 0, /* U+01296b: */
+ 0, /* U+01296c: */
+ 0, /* U+01296d: */
+ 0, /* U+01296e: */
+ 0, /* U+01296f: */
+ 0, /* U+012970: */
+ 0, /* U+012971: */
+ 0, /* U+012972: */
+ 0, /* U+012973: */
+ 0, /* U+012974: */
+ 0, /* U+012975: */
+ 0, /* U+012976: */
+ 0, /* U+012977: */
+ 0, /* U+012978: */
+ 0, /* U+012979: */
+ 0, /* U+01297a: */
+ 0, /* U+01297b: */
+ 0, /* U+01297c: */
+ 0, /* U+01297d: */
+ 0, /* U+01297e: */
+ 0, /* U+01297f: */
+ 0, /* U+012980: */
+ 0, /* U+012981: */
+ 0, /* U+012982: */
+ 0, /* U+012983: */
+ 0, /* U+012984: */
+ 0, /* U+012985: */
+ 0, /* U+012986: */
+ 0, /* U+012987: */
+ 0, /* U+012988: */
+ 0, /* U+012989: */
+ 0, /* U+01298a: */
+ 0, /* U+01298b: */
+ 0, /* U+01298c: */
+ 0, /* U+01298d: */
+ 0, /* U+01298e: */
+ 0, /* U+01298f: */
+ 0, /* U+012990: */
+ 0, /* U+012991: */
+ 0, /* U+012992: */
+ 0, /* U+012993: */
+ 0, /* U+012994: */
+ 0, /* U+012995: */
+ 0, /* U+012996: */
+ 0, /* U+012997: */
+ 0, /* U+012998: */
+ 0, /* U+012999: */
+ 0, /* U+01299a: */
+ 0, /* U+01299b: */
+ 0, /* U+01299c: */
+ 0, /* U+01299d: */
+ 0, /* U+01299e: */
+ 0, /* U+01299f: */
+ 0, /* U+0129a0: */
+ 0, /* U+0129a1: */
+ 0, /* U+0129a2: */
+ 0, /* U+0129a3: */
+ 0, /* U+0129a4: */
+ 0, /* U+0129a5: */
+ 0, /* U+0129a6: */
+ 0, /* U+0129a7: */
+ 0, /* U+0129a8: */
+ 0, /* U+0129a9: */
+ 0, /* U+0129aa: */
+ 0, /* U+0129ab: */
+ 0, /* U+0129ac: */
+ 0, /* U+0129ad: */
+ 0, /* U+0129ae: */
+ 0, /* U+0129af: */
+ 0, /* U+0129b0: */
+ 0, /* U+0129b1: */
+ 0, /* U+0129b2: */
+ 0, /* U+0129b3: */
+ 0, /* U+0129b4: */
+ 0, /* U+0129b5: */
+ 0, /* U+0129b6: */
+ 0, /* U+0129b7: */
+ 0, /* U+0129b8: */
+ 0, /* U+0129b9: */
+ 0, /* U+0129ba: */
+ 0, /* U+0129bb: */
+ 0, /* U+0129bc: */
+ 0, /* U+0129bd: */
+ 0, /* U+0129be: */
+ 0, /* U+0129bf: */
+ 0, /* U+0129c0: */
+ 0, /* U+0129c1: */
+ 0, /* U+0129c2: */
+ 0, /* U+0129c3: */
+ 0, /* U+0129c4: */
+ 0, /* U+0129c5: */
+ 0, /* U+0129c6: */
+ 0, /* U+0129c7: */
+ 0, /* U+0129c8: */
+ 0, /* U+0129c9: */
+ 0, /* U+0129ca: */
+ 0, /* U+0129cb: */
+ 0, /* U+0129cc: */
+ 0, /* U+0129cd: */
+ 0, /* U+0129ce: */
+ 0, /* U+0129cf: */
+ 0, /* U+0129d0: */
+ 0, /* U+0129d1: */
+ 0, /* U+0129d2: */
+ 0, /* U+0129d3: */
+ 0, /* U+0129d4: */
+ 0, /* U+0129d5: */
+ 0, /* U+0129d6: */
+ 0, /* U+0129d7: */
+ 0, /* U+0129d8: */
+ 0, /* U+0129d9: */
+ 0, /* U+0129da: */
+ 0, /* U+0129db: */
+ 0, /* U+0129dc: */
+ 0, /* U+0129dd: */
+ 0, /* U+0129de: */
+ 0, /* U+0129df: */
+ 0, /* U+0129e0: */
+ 0, /* U+0129e1: */
+ 0, /* U+0129e2: */
+ 0, /* U+0129e3: */
+ 0, /* U+0129e4: */
+ 0, /* U+0129e5: */
+ 0, /* U+0129e6: */
+ 0, /* U+0129e7: */
+ 0, /* U+0129e8: */
+ 0, /* U+0129e9: */
+ 0, /* U+0129ea: */
+ 0, /* U+0129eb: */
+ 0, /* U+0129ec: */
+ 0, /* U+0129ed: */
+ 0, /* U+0129ee: */
+ 0, /* U+0129ef: */
+ 0, /* U+0129f0: */
+ 0, /* U+0129f1: */
+ 0, /* U+0129f2: */
+ 0, /* U+0129f3: */
+ 0, /* U+0129f4: */
+ 0, /* U+0129f5: */
+ 0, /* U+0129f6: */
+ 0, /* U+0129f7: */
+ 0, /* U+0129f8: */
+ 0, /* U+0129f9: */
+ 0, /* U+0129fa: */
+ 0, /* U+0129fb: */
+ 0, /* U+0129fc: */
+ 0, /* U+0129fd: */
+ 0, /* U+0129fe: */
+ 0, /* U+0129ff: */
+ 0, /* U+012a00: */
+ 0, /* U+012a01: */
+ 0, /* U+012a02: */
+ 0, /* U+012a03: */
+ 0, /* U+012a04: */
+ 0, /* U+012a05: */
+ 0, /* U+012a06: */
+ 0, /* U+012a07: */
+ 0, /* U+012a08: */
+ 0, /* U+012a09: */
+ 0, /* U+012a0a: */
+ 0, /* U+012a0b: */
+ 0, /* U+012a0c: */
+ 0, /* U+012a0d: */
+ 0, /* U+012a0e: */
+ 0, /* U+012a0f: */
+ 0, /* U+012a10: */
+ 0, /* U+012a11: */
+ 0, /* U+012a12: */
+ 0, /* U+012a13: */
+ 0, /* U+012a14: */
+ 0, /* U+012a15: */
+ 0, /* U+012a16: */
+ 0, /* U+012a17: */
+ 0, /* U+012a18: */
+ 0, /* U+012a19: */
+ 0, /* U+012a1a: */
+ 0, /* U+012a1b: */
+ 0, /* U+012a1c: */
+ 0, /* U+012a1d: */
+ 0, /* U+012a1e: */
+ 0, /* U+012a1f: */
+ 0, /* U+012a20: */
+ 0, /* U+012a21: */
+ 0, /* U+012a22: */
+ 0, /* U+012a23: */
+ 0, /* U+012a24: */
+ 0, /* U+012a25: */
+ 0, /* U+012a26: */
+ 0, /* U+012a27: */
+ 0, /* U+012a28: */
+ 0, /* U+012a29: */
+ 0, /* U+012a2a: */
+ 0, /* U+012a2b: */
+ 0, /* U+012a2c: */
+ 0, /* U+012a2d: */
+ 0, /* U+012a2e: */
+ 0, /* U+012a2f: */
+ 0, /* U+012a30: */
+ 0, /* U+012a31: */
+ 0, /* U+012a32: */
+ 0, /* U+012a33: */
+ 0, /* U+012a34: */
+ 0, /* U+012a35: */
+ 0, /* U+012a36: */
+ 0, /* U+012a37: */
+ 0, /* U+012a38: */
+ 0, /* U+012a39: */
+ 0, /* U+012a3a: */
+ 0, /* U+012a3b: */
+ 0, /* U+012a3c: */
+ 0, /* U+012a3d: */
+ 0, /* U+012a3e: */
+ 0, /* U+012a3f: */
+ 0, /* U+012a40: */
+ 0, /* U+012a41: */
+ 0, /* U+012a42: */
+ 0, /* U+012a43: */
+ 0, /* U+012a44: */
+ 0, /* U+012a45: */
+ 0, /* U+012a46: */
+ 0, /* U+012a47: */
+ 0, /* U+012a48: */
+ 0, /* U+012a49: */
+ 0, /* U+012a4a: */
+ 0, /* U+012a4b: */
+ 0, /* U+012a4c: */
+ 0, /* U+012a4d: */
+ 0, /* U+012a4e: */
+ 0, /* U+012a4f: */
+ 0, /* U+012a50: */
+ 0, /* U+012a51: */
+ 0, /* U+012a52: */
+ 0, /* U+012a53: */
+ 0, /* U+012a54: */
+ 0, /* U+012a55: */
+ 0, /* U+012a56: */
+ 0, /* U+012a57: */
+ 0, /* U+012a58: */
+ 0, /* U+012a59: */
+ 0, /* U+012a5a: */
+ 0, /* U+012a5b: */
+ 0, /* U+012a5c: */
+ 0, /* U+012a5d: */
+ 0, /* U+012a5e: */
+ 0, /* U+012a5f: */
+ 0, /* U+012a60: */
+ 0, /* U+012a61: */
+ 0, /* U+012a62: */
+ 0, /* U+012a63: */
+ 0, /* U+012a64: */
+ 0, /* U+012a65: */
+ 0, /* U+012a66: */
+ 0, /* U+012a67: */
+ 0, /* U+012a68: */
+ 0, /* U+012a69: */
+ 0, /* U+012a6a: */
+ 0, /* U+012a6b: */
+ 0, /* U+012a6c: */
+ 0, /* U+012a6d: */
+ 0, /* U+012a6e: */
+ 0, /* U+012a6f: */
+ 0, /* U+012a70: */
+ 0, /* U+012a71: */
+ 0, /* U+012a72: */
+ 0, /* U+012a73: */
+ 0, /* U+012a74: */
+ 0, /* U+012a75: */
+ 0, /* U+012a76: */
+ 0, /* U+012a77: */
+ 0, /* U+012a78: */
+ 0, /* U+012a79: */
+ 0, /* U+012a7a: */
+ 0, /* U+012a7b: */
+ 0, /* U+012a7c: */
+ 0, /* U+012a7d: */
+ 0, /* U+012a7e: */
+ 0, /* U+012a7f: */
+ 0, /* U+012a80: */
+ 0, /* U+012a81: */
+ 0, /* U+012a82: */
+ 0, /* U+012a83: */
+ 0, /* U+012a84: */
+ 0, /* U+012a85: */
+ 0, /* U+012a86: */
+ 0, /* U+012a87: */
+ 0, /* U+012a88: */
+ 0, /* U+012a89: */
+ 0, /* U+012a8a: */
+ 0, /* U+012a8b: */
+ 0, /* U+012a8c: */
+ 0, /* U+012a8d: */
+ 0, /* U+012a8e: */
+ 0, /* U+012a8f: */
+ 0, /* U+012a90: */
+ 0, /* U+012a91: */
+ 0, /* U+012a92: */
+ 0, /* U+012a93: */
+ 0, /* U+012a94: */
+ 0, /* U+012a95: */
+ 0, /* U+012a96: */
+ 0, /* U+012a97: */
+ 0, /* U+012a98: */
+ 0, /* U+012a99: */
+ 0, /* U+012a9a: */
+ 0, /* U+012a9b: */
+ 0, /* U+012a9c: */
+ 0, /* U+012a9d: */
+ 0, /* U+012a9e: */
+ 0, /* U+012a9f: */
+ 0, /* U+012aa0: */
+ 0, /* U+012aa1: */
+ 0, /* U+012aa2: */
+ 0, /* U+012aa3: */
+ 0, /* U+012aa4: */
+ 0, /* U+012aa5: */
+ 0, /* U+012aa6: */
+ 0, /* U+012aa7: */
+ 0, /* U+012aa8: */
+ 0, /* U+012aa9: */
+ 0, /* U+012aaa: */
+ 0, /* U+012aab: */
+ 0, /* U+012aac: */
+ 0, /* U+012aad: */
+ 0, /* U+012aae: */
+ 0, /* U+012aaf: */
+ 0, /* U+012ab0: */
+ 0, /* U+012ab1: */
+ 0, /* U+012ab2: */
+ 0, /* U+012ab3: */
+ 0, /* U+012ab4: */
+ 0, /* U+012ab5: */
+ 0, /* U+012ab6: */
+ 0, /* U+012ab7: */
+ 0, /* U+012ab8: */
+ 0, /* U+012ab9: */
+ 0, /* U+012aba: */
+ 0, /* U+012abb: */
+ 0, /* U+012abc: */
+ 0, /* U+012abd: */
+ 0, /* U+012abe: */
+ 0, /* U+012abf: */
+ 0, /* U+012ac0: */
+ 0, /* U+012ac1: */
+ 0, /* U+012ac2: */
+ 0, /* U+012ac3: */
+ 0, /* U+012ac4: */
+ 0, /* U+012ac5: */
+ 0, /* U+012ac6: */
+ 0, /* U+012ac7: */
+ 0, /* U+012ac8: */
+ 0, /* U+012ac9: */
+ 0, /* U+012aca: */
+ 0, /* U+012acb: */
+ 0, /* U+012acc: */
+ 0, /* U+012acd: */
+ 0, /* U+012ace: */
+ 0, /* U+012acf: */
+ 0, /* U+012ad0: */
+ 0, /* U+012ad1: */
+ 0, /* U+012ad2: */
+ 0, /* U+012ad3: */
+ 0, /* U+012ad4: */
+ 0, /* U+012ad5: */
+ 0, /* U+012ad6: */
+ 0, /* U+012ad7: */
+ 0, /* U+012ad8: */
+ 0, /* U+012ad9: */
+ 0, /* U+012ada: */
+ 0, /* U+012adb: */
+ 0, /* U+012adc: */
+ 0, /* U+012add: */
+ 0, /* U+012ade: */
+ 0, /* U+012adf: */
+ 0, /* U+012ae0: */
+ 0, /* U+012ae1: */
+ 0, /* U+012ae2: */
+ 0, /* U+012ae3: */
+ 0, /* U+012ae4: */
+ 0, /* U+012ae5: */
+ 0, /* U+012ae6: */
+ 0, /* U+012ae7: */
+ 0, /* U+012ae8: */
+ 0, /* U+012ae9: */
+ 0, /* U+012aea: */
+ 0, /* U+012aeb: */
+ 0, /* U+012aec: */
+ 0, /* U+012aed: */
+ 0, /* U+012aee: */
+ 0, /* U+012aef: */
+ 0, /* U+012af0: */
+ 0, /* U+012af1: */
+ 0, /* U+012af2: */
+ 0, /* U+012af3: */
+ 0, /* U+012af4: */
+ 0, /* U+012af5: */
+ 0, /* U+012af6: */
+ 0, /* U+012af7: */
+ 0, /* U+012af8: */
+ 0, /* U+012af9: */
+ 0, /* U+012afa: */
+ 0, /* U+012afb: */
+ 0, /* U+012afc: */
+ 0, /* U+012afd: */
+ 0, /* U+012afe: */
+ 0, /* U+012aff: */
+ 0, /* U+012b00: */
+ 0, /* U+012b01: */
+ 0, /* U+012b02: */
+ 0, /* U+012b03: */
+ 0, /* U+012b04: */
+ 0, /* U+012b05: */
+ 0, /* U+012b06: */
+ 0, /* U+012b07: */
+ 0, /* U+012b08: */
+ 0, /* U+012b09: */
+ 0, /* U+012b0a: */
+ 0, /* U+012b0b: */
+ 0, /* U+012b0c: */
+ 0, /* U+012b0d: */
+ 0, /* U+012b0e: */
+ 0, /* U+012b0f: */
+ 0, /* U+012b10: */
+ 0, /* U+012b11: */
+ 0, /* U+012b12: */
+ 0, /* U+012b13: */
+ 0, /* U+012b14: */
+ 0, /* U+012b15: */
+ 0, /* U+012b16: */
+ 0, /* U+012b17: */
+ 0, /* U+012b18: */
+ 0, /* U+012b19: */
+ 0, /* U+012b1a: */
+ 0, /* U+012b1b: */
+ 0, /* U+012b1c: */
+ 0, /* U+012b1d: */
+ 0, /* U+012b1e: */
+ 0, /* U+012b1f: */
+ 0, /* U+012b20: */
+ 0, /* U+012b21: */
+ 0, /* U+012b22: */
+ 0, /* U+012b23: */
+ 0, /* U+012b24: */
+ 0, /* U+012b25: */
+ 0, /* U+012b26: */
+ 0, /* U+012b27: */
+ 0, /* U+012b28: */
+ 0, /* U+012b29: */
+ 0, /* U+012b2a: */
+ 0, /* U+012b2b: */
+ 0, /* U+012b2c: */
+ 0, /* U+012b2d: */
+ 0, /* U+012b2e: */
+ 0, /* U+012b2f: */
+ 0, /* U+012b30: */
+ 0, /* U+012b31: */
+ 0, /* U+012b32: */
+ 0, /* U+012b33: */
+ 0, /* U+012b34: */
+ 0, /* U+012b35: */
+ 0, /* U+012b36: */
+ 0, /* U+012b37: */
+ 0, /* U+012b38: */
+ 0, /* U+012b39: */
+ 0, /* U+012b3a: */
+ 0, /* U+012b3b: */
+ 0, /* U+012b3c: */
+ 0, /* U+012b3d: */
+ 0, /* U+012b3e: */
+ 0, /* U+012b3f: */
+ 0, /* U+012b40: */
+ 0, /* U+012b41: */
+ 0, /* U+012b42: */
+ 0, /* U+012b43: */
+ 0, /* U+012b44: */
+ 0, /* U+012b45: */
+ 0, /* U+012b46: */
+ 0, /* U+012b47: */
+ 0, /* U+012b48: */
+ 0, /* U+012b49: */
+ 0, /* U+012b4a: */
+ 0, /* U+012b4b: */
+ 0, /* U+012b4c: */
+ 0, /* U+012b4d: */
+ 0, /* U+012b4e: */
+ 0, /* U+012b4f: */
+ 0, /* U+012b50: */
+ 0, /* U+012b51: */
+ 0, /* U+012b52: */
+ 0, /* U+012b53: */
+ 0, /* U+012b54: */
+ 0, /* U+012b55: */
+ 0, /* U+012b56: */
+ 0, /* U+012b57: */
+ 0, /* U+012b58: */
+ 0, /* U+012b59: */
+ 0, /* U+012b5a: */
+ 0, /* U+012b5b: */
+ 0, /* U+012b5c: */
+ 0, /* U+012b5d: */
+ 0, /* U+012b5e: */
+ 0, /* U+012b5f: */
+ 0, /* U+012b60: */
+ 0, /* U+012b61: */
+ 0, /* U+012b62: */
+ 0, /* U+012b63: */
+ 0, /* U+012b64: */
+ 0, /* U+012b65: */
+ 0, /* U+012b66: */
+ 0, /* U+012b67: */
+ 0, /* U+012b68: */
+ 0, /* U+012b69: */
+ 0, /* U+012b6a: */
+ 0, /* U+012b6b: */
+ 0, /* U+012b6c: */
+ 0, /* U+012b6d: */
+ 0, /* U+012b6e: */
+ 0, /* U+012b6f: */
+ 0, /* U+012b70: */
+ 0, /* U+012b71: */
+ 0, /* U+012b72: */
+ 0, /* U+012b73: */
+ 0, /* U+012b74: */
+ 0, /* U+012b75: */
+ 0, /* U+012b76: */
+ 0, /* U+012b77: */
+ 0, /* U+012b78: */
+ 0, /* U+012b79: */
+ 0, /* U+012b7a: */
+ 0, /* U+012b7b: */
+ 0, /* U+012b7c: */
+ 0, /* U+012b7d: */
+ 0, /* U+012b7e: */
+ 0, /* U+012b7f: */
+ 0, /* U+012b80: */
+ 0, /* U+012b81: */
+ 0, /* U+012b82: */
+ 0, /* U+012b83: */
+ 0, /* U+012b84: */
+ 0, /* U+012b85: */
+ 0, /* U+012b86: */
+ 0, /* U+012b87: */
+ 0, /* U+012b88: */
+ 0, /* U+012b89: */
+ 0, /* U+012b8a: */
+ 0, /* U+012b8b: */
+ 0, /* U+012b8c: */
+ 0, /* U+012b8d: */
+ 0, /* U+012b8e: */
+ 0, /* U+012b8f: */
+ 0, /* U+012b90: */
+ 0, /* U+012b91: */
+ 0, /* U+012b92: */
+ 0, /* U+012b93: */
+ 0, /* U+012b94: */
+ 0, /* U+012b95: */
+ 0, /* U+012b96: */
+ 0, /* U+012b97: */
+ 0, /* U+012b98: */
+ 0, /* U+012b99: */
+ 0, /* U+012b9a: */
+ 0, /* U+012b9b: */
+ 0, /* U+012b9c: */
+ 0, /* U+012b9d: */
+ 0, /* U+012b9e: */
+ 0, /* U+012b9f: */
+ 0, /* U+012ba0: */
+ 0, /* U+012ba1: */
+ 0, /* U+012ba2: */
+ 0, /* U+012ba3: */
+ 0, /* U+012ba4: */
+ 0, /* U+012ba5: */
+ 0, /* U+012ba6: */
+ 0, /* U+012ba7: */
+ 0, /* U+012ba8: */
+ 0, /* U+012ba9: */
+ 0, /* U+012baa: */
+ 0, /* U+012bab: */
+ 0, /* U+012bac: */
+ 0, /* U+012bad: */
+ 0, /* U+012bae: */
+ 0, /* U+012baf: */
+ 0, /* U+012bb0: */
+ 0, /* U+012bb1: */
+ 0, /* U+012bb2: */
+ 0, /* U+012bb3: */
+ 0, /* U+012bb4: */
+ 0, /* U+012bb5: */
+ 0, /* U+012bb6: */
+ 0, /* U+012bb7: */
+ 0, /* U+012bb8: */
+ 0, /* U+012bb9: */
+ 0, /* U+012bba: */
+ 0, /* U+012bbb: */
+ 0, /* U+012bbc: */
+ 0, /* U+012bbd: */
+ 0, /* U+012bbe: */
+ 0, /* U+012bbf: */
+ 0, /* U+012bc0: */
+ 0, /* U+012bc1: */
+ 0, /* U+012bc2: */
+ 0, /* U+012bc3: */
+ 0, /* U+012bc4: */
+ 0, /* U+012bc5: */
+ 0, /* U+012bc6: */
+ 0, /* U+012bc7: */
+ 0, /* U+012bc8: */
+ 0, /* U+012bc9: */
+ 0, /* U+012bca: */
+ 0, /* U+012bcb: */
+ 0, /* U+012bcc: */
+ 0, /* U+012bcd: */
+ 0, /* U+012bce: */
+ 0, /* U+012bcf: */
+ 0, /* U+012bd0: */
+ 0, /* U+012bd1: */
+ 0, /* U+012bd2: */
+ 0, /* U+012bd3: */
+ 0, /* U+012bd4: */
+ 0, /* U+012bd5: */
+ 0, /* U+012bd6: */
+ 0, /* U+012bd7: */
+ 0, /* U+012bd8: */
+ 0, /* U+012bd9: */
+ 0, /* U+012bda: */
+ 0, /* U+012bdb: */
+ 0, /* U+012bdc: */
+ 0, /* U+012bdd: */
+ 0, /* U+012bde: */
+ 0, /* U+012bdf: */
+ 0, /* U+012be0: */
+ 0, /* U+012be1: */
+ 0, /* U+012be2: */
+ 0, /* U+012be3: */
+ 0, /* U+012be4: */
+ 0, /* U+012be5: */
+ 0, /* U+012be6: */
+ 0, /* U+012be7: */
+ 0, /* U+012be8: */
+ 0, /* U+012be9: */
+ 0, /* U+012bea: */
+ 0, /* U+012beb: */
+ 0, /* U+012bec: */
+ 0, /* U+012bed: */
+ 0, /* U+012bee: */
+ 0, /* U+012bef: */
+ 0, /* U+012bf0: */
+ 0, /* U+012bf1: */
+ 0, /* U+012bf2: */
+ 0, /* U+012bf3: */
+ 0, /* U+012bf4: */
+ 0, /* U+012bf5: */
+ 0, /* U+012bf6: */
+ 0, /* U+012bf7: */
+ 0, /* U+012bf8: */
+ 0, /* U+012bf9: */
+ 0, /* U+012bfa: */
+ 0, /* U+012bfb: */
+ 0, /* U+012bfc: */
+ 0, /* U+012bfd: */
+ 0, /* U+012bfe: */
+ 0, /* U+012bff: */
+ 0, /* U+012c00: */
+ 0, /* U+012c01: */
+ 0, /* U+012c02: */
+ 0, /* U+012c03: */
+ 0, /* U+012c04: */
+ 0, /* U+012c05: */
+ 0, /* U+012c06: */
+ 0, /* U+012c07: */
+ 0, /* U+012c08: */
+ 0, /* U+012c09: */
+ 0, /* U+012c0a: */
+ 0, /* U+012c0b: */
+ 0, /* U+012c0c: */
+ 0, /* U+012c0d: */
+ 0, /* U+012c0e: */
+ 0, /* U+012c0f: */
+ 0, /* U+012c10: */
+ 0, /* U+012c11: */
+ 0, /* U+012c12: */
+ 0, /* U+012c13: */
+ 0, /* U+012c14: */
+ 0, /* U+012c15: */
+ 0, /* U+012c16: */
+ 0, /* U+012c17: */
+ 0, /* U+012c18: */
+ 0, /* U+012c19: */
+ 0, /* U+012c1a: */
+ 0, /* U+012c1b: */
+ 0, /* U+012c1c: */
+ 0, /* U+012c1d: */
+ 0, /* U+012c1e: */
+ 0, /* U+012c1f: */
+ 0, /* U+012c20: */
+ 0, /* U+012c21: */
+ 0, /* U+012c22: */
+ 0, /* U+012c23: */
+ 0, /* U+012c24: */
+ 0, /* U+012c25: */
+ 0, /* U+012c26: */
+ 0, /* U+012c27: */
+ 0, /* U+012c28: */
+ 0, /* U+012c29: */
+ 0, /* U+012c2a: */
+ 0, /* U+012c2b: */
+ 0, /* U+012c2c: */
+ 0, /* U+012c2d: */
+ 0, /* U+012c2e: */
+ 0, /* U+012c2f: */
+ 0, /* U+012c30: */
+ 0, /* U+012c31: */
+ 0, /* U+012c32: */
+ 0, /* U+012c33: */
+ 0, /* U+012c34: */
+ 0, /* U+012c35: */
+ 0, /* U+012c36: */
+ 0, /* U+012c37: */
+ 0, /* U+012c38: */
+ 0, /* U+012c39: */
+ 0, /* U+012c3a: */
+ 0, /* U+012c3b: */
+ 0, /* U+012c3c: */
+ 0, /* U+012c3d: */
+ 0, /* U+012c3e: */
+ 0, /* U+012c3f: */
+ 0, /* U+012c40: */
+ 0, /* U+012c41: */
+ 0, /* U+012c42: */
+ 0, /* U+012c43: */
+ 0, /* U+012c44: */
+ 0, /* U+012c45: */
+ 0, /* U+012c46: */
+ 0, /* U+012c47: */
+ 0, /* U+012c48: */
+ 0, /* U+012c49: */
+ 0, /* U+012c4a: */
+ 0, /* U+012c4b: */
+ 0, /* U+012c4c: */
+ 0, /* U+012c4d: */
+ 0, /* U+012c4e: */
+ 0, /* U+012c4f: */
+ 0, /* U+012c50: */
+ 0, /* U+012c51: */
+ 0, /* U+012c52: */
+ 0, /* U+012c53: */
+ 0, /* U+012c54: */
+ 0, /* U+012c55: */
+ 0, /* U+012c56: */
+ 0, /* U+012c57: */
+ 0, /* U+012c58: */
+ 0, /* U+012c59: */
+ 0, /* U+012c5a: */
+ 0, /* U+012c5b: */
+ 0, /* U+012c5c: */
+ 0, /* U+012c5d: */
+ 0, /* U+012c5e: */
+ 0, /* U+012c5f: */
+ 0, /* U+012c60: */
+ 0, /* U+012c61: */
+ 0, /* U+012c62: */
+ 0, /* U+012c63: */
+ 0, /* U+012c64: */
+ 0, /* U+012c65: */
+ 0, /* U+012c66: */
+ 0, /* U+012c67: */
+ 0, /* U+012c68: */
+ 0, /* U+012c69: */
+ 0, /* U+012c6a: */
+ 0, /* U+012c6b: */
+ 0, /* U+012c6c: */
+ 0, /* U+012c6d: */
+ 0, /* U+012c6e: */
+ 0, /* U+012c6f: */
+ 0, /* U+012c70: */
+ 0, /* U+012c71: */
+ 0, /* U+012c72: */
+ 0, /* U+012c73: */
+ 0, /* U+012c74: */
+ 0, /* U+012c75: */
+ 0, /* U+012c76: */
+ 0, /* U+012c77: */
+ 0, /* U+012c78: */
+ 0, /* U+012c79: */
+ 0, /* U+012c7a: */
+ 0, /* U+012c7b: */
+ 0, /* U+012c7c: */
+ 0, /* U+012c7d: */
+ 0, /* U+012c7e: */
+ 0, /* U+012c7f: */
+ 0, /* U+012c80: */
+ 0, /* U+012c81: */
+ 0, /* U+012c82: */
+ 0, /* U+012c83: */
+ 0, /* U+012c84: */
+ 0, /* U+012c85: */
+ 0, /* U+012c86: */
+ 0, /* U+012c87: */
+ 0, /* U+012c88: */
+ 0, /* U+012c89: */
+ 0, /* U+012c8a: */
+ 0, /* U+012c8b: */
+ 0, /* U+012c8c: */
+ 0, /* U+012c8d: */
+ 0, /* U+012c8e: */
+ 0, /* U+012c8f: */
+ 0, /* U+012c90: */
+ 0, /* U+012c91: */
+ 0, /* U+012c92: */
+ 0, /* U+012c93: */
+ 0, /* U+012c94: */
+ 0, /* U+012c95: */
+ 0, /* U+012c96: */
+ 0, /* U+012c97: */
+ 0, /* U+012c98: */
+ 0, /* U+012c99: */
+ 0, /* U+012c9a: */
+ 0, /* U+012c9b: */
+ 0, /* U+012c9c: */
+ 0, /* U+012c9d: */
+ 0, /* U+012c9e: */
+ 0, /* U+012c9f: */
+ 0, /* U+012ca0: */
+ 0, /* U+012ca1: */
+ 0, /* U+012ca2: */
+ 0, /* U+012ca3: */
+ 0, /* U+012ca4: */
+ 0, /* U+012ca5: */
+ 0, /* U+012ca6: */
+ 0, /* U+012ca7: */
+ 0, /* U+012ca8: */
+ 0, /* U+012ca9: */
+ 0, /* U+012caa: */
+ 0, /* U+012cab: */
+ 0, /* U+012cac: */
+ 0, /* U+012cad: */
+ 0, /* U+012cae: */
+ 0, /* U+012caf: */
+ 0, /* U+012cb0: */
+ 0, /* U+012cb1: */
+ 0, /* U+012cb2: */
+ 0, /* U+012cb3: */
+ 0, /* U+012cb4: */
+ 0, /* U+012cb5: */
+ 0, /* U+012cb6: */
+ 0, /* U+012cb7: */
+ 0, /* U+012cb8: */
+ 0, /* U+012cb9: */
+ 0, /* U+012cba: */
+ 0, /* U+012cbb: */
+ 0, /* U+012cbc: */
+ 0, /* U+012cbd: */
+ 0, /* U+012cbe: */
+ 0, /* U+012cbf: */
+ 0, /* U+012cc0: */
+ 0, /* U+012cc1: */
+ 0, /* U+012cc2: */
+ 0, /* U+012cc3: */
+ 0, /* U+012cc4: */
+ 0, /* U+012cc5: */
+ 0, /* U+012cc6: */
+ 0, /* U+012cc7: */
+ 0, /* U+012cc8: */
+ 0, /* U+012cc9: */
+ 0, /* U+012cca: */
+ 0, /* U+012ccb: */
+ 0, /* U+012ccc: */
+ 0, /* U+012ccd: */
+ 0, /* U+012cce: */
+ 0, /* U+012ccf: */
+ 0, /* U+012cd0: */
+ 0, /* U+012cd1: */
+ 0, /* U+012cd2: */
+ 0, /* U+012cd3: */
+ 0, /* U+012cd4: */
+ 0, /* U+012cd5: */
+ 0, /* U+012cd6: */
+ 0, /* U+012cd7: */
+ 0, /* U+012cd8: */
+ 0, /* U+012cd9: */
+ 0, /* U+012cda: */
+ 0, /* U+012cdb: */
+ 0, /* U+012cdc: */
+ 0, /* U+012cdd: */
+ 0, /* U+012cde: */
+ 0, /* U+012cdf: */
+ 0, /* U+012ce0: */
+ 0, /* U+012ce1: */
+ 0, /* U+012ce2: */
+ 0, /* U+012ce3: */
+ 0, /* U+012ce4: */
+ 0, /* U+012ce5: */
+ 0, /* U+012ce6: */
+ 0, /* U+012ce7: */
+ 0, /* U+012ce8: */
+ 0, /* U+012ce9: */
+ 0, /* U+012cea: */
+ 0, /* U+012ceb: */
+ 0, /* U+012cec: */
+ 0, /* U+012ced: */
+ 0, /* U+012cee: */
+ 0, /* U+012cef: */
+ 0, /* U+012cf0: */
+ 0, /* U+012cf1: */
+ 0, /* U+012cf2: */
+ 0, /* U+012cf3: */
+ 0, /* U+012cf4: */
+ 0, /* U+012cf5: */
+ 0, /* U+012cf6: */
+ 0, /* U+012cf7: */
+ 0, /* U+012cf8: */
+ 0, /* U+012cf9: */
+ 0, /* U+012cfa: */
+ 0, /* U+012cfb: */
+ 0, /* U+012cfc: */
+ 0, /* U+012cfd: */
+ 0, /* U+012cfe: */
+ 0, /* U+012cff: */
+ 0, /* U+012d00: */
+ 0, /* U+012d01: */
+ 0, /* U+012d02: */
+ 0, /* U+012d03: */
+ 0, /* U+012d04: */
+ 0, /* U+012d05: */
+ 0, /* U+012d06: */
+ 0, /* U+012d07: */
+ 0, /* U+012d08: */
+ 0, /* U+012d09: */
+ 0, /* U+012d0a: */
+ 0, /* U+012d0b: */
+ 0, /* U+012d0c: */
+ 0, /* U+012d0d: */
+ 0, /* U+012d0e: */
+ 0, /* U+012d0f: */
+ 0, /* U+012d10: */
+ 0, /* U+012d11: */
+ 0, /* U+012d12: */
+ 0, /* U+012d13: */
+ 0, /* U+012d14: */
+ 0, /* U+012d15: */
+ 0, /* U+012d16: */
+ 0, /* U+012d17: */
+ 0, /* U+012d18: */
+ 0, /* U+012d19: */
+ 0, /* U+012d1a: */
+ 0, /* U+012d1b: */
+ 0, /* U+012d1c: */
+ 0, /* U+012d1d: */
+ 0, /* U+012d1e: */
+ 0, /* U+012d1f: */
+ 0, /* U+012d20: */
+ 0, /* U+012d21: */
+ 0, /* U+012d22: */
+ 0, /* U+012d23: */
+ 0, /* U+012d24: */
+ 0, /* U+012d25: */
+ 0, /* U+012d26: */
+ 0, /* U+012d27: */
+ 0, /* U+012d28: */
+ 0, /* U+012d29: */
+ 0, /* U+012d2a: */
+ 0, /* U+012d2b: */
+ 0, /* U+012d2c: */
+ 0, /* U+012d2d: */
+ 0, /* U+012d2e: */
+ 0, /* U+012d2f: */
+ 0, /* U+012d30: */
+ 0, /* U+012d31: */
+ 0, /* U+012d32: */
+ 0, /* U+012d33: */
+ 0, /* U+012d34: */
+ 0, /* U+012d35: */
+ 0, /* U+012d36: */
+ 0, /* U+012d37: */
+ 0, /* U+012d38: */
+ 0, /* U+012d39: */
+ 0, /* U+012d3a: */
+ 0, /* U+012d3b: */
+ 0, /* U+012d3c: */
+ 0, /* U+012d3d: */
+ 0, /* U+012d3e: */
+ 0, /* U+012d3f: */
+ 0, /* U+012d40: */
+ 0, /* U+012d41: */
+ 0, /* U+012d42: */
+ 0, /* U+012d43: */
+ 0, /* U+012d44: */
+ 0, /* U+012d45: */
+ 0, /* U+012d46: */
+ 0, /* U+012d47: */
+ 0, /* U+012d48: */
+ 0, /* U+012d49: */
+ 0, /* U+012d4a: */
+ 0, /* U+012d4b: */
+ 0, /* U+012d4c: */
+ 0, /* U+012d4d: */
+ 0, /* U+012d4e: */
+ 0, /* U+012d4f: */
+ 0, /* U+012d50: */
+ 0, /* U+012d51: */
+ 0, /* U+012d52: */
+ 0, /* U+012d53: */
+ 0, /* U+012d54: */
+ 0, /* U+012d55: */
+ 0, /* U+012d56: */
+ 0, /* U+012d57: */
+ 0, /* U+012d58: */
+ 0, /* U+012d59: */
+ 0, /* U+012d5a: */
+ 0, /* U+012d5b: */
+ 0, /* U+012d5c: */
+ 0, /* U+012d5d: */
+ 0, /* U+012d5e: */
+ 0, /* U+012d5f: */
+ 0, /* U+012d60: */
+ 0, /* U+012d61: */
+ 0, /* U+012d62: */
+ 0, /* U+012d63: */
+ 0, /* U+012d64: */
+ 0, /* U+012d65: */
+ 0, /* U+012d66: */
+ 0, /* U+012d67: */
+ 0, /* U+012d68: */
+ 0, /* U+012d69: */
+ 0, /* U+012d6a: */
+ 0, /* U+012d6b: */
+ 0, /* U+012d6c: */
+ 0, /* U+012d6d: */
+ 0, /* U+012d6e: */
+ 0, /* U+012d6f: */
+ 0, /* U+012d70: */
+ 0, /* U+012d71: */
+ 0, /* U+012d72: */
+ 0, /* U+012d73: */
+ 0, /* U+012d74: */
+ 0, /* U+012d75: */
+ 0, /* U+012d76: */
+ 0, /* U+012d77: */
+ 0, /* U+012d78: */
+ 0, /* U+012d79: */
+ 0, /* U+012d7a: */
+ 0, /* U+012d7b: */
+ 0, /* U+012d7c: */
+ 0, /* U+012d7d: */
+ 0, /* U+012d7e: */
+ 0, /* U+012d7f: */
+ 0, /* U+012d80: */
+ 0, /* U+012d81: */
+ 0, /* U+012d82: */
+ 0, /* U+012d83: */
+ 0, /* U+012d84: */
+ 0, /* U+012d85: */
+ 0, /* U+012d86: */
+ 0, /* U+012d87: */
+ 0, /* U+012d88: */
+ 0, /* U+012d89: */
+ 0, /* U+012d8a: */
+ 0, /* U+012d8b: */
+ 0, /* U+012d8c: */
+ 0, /* U+012d8d: */
+ 0, /* U+012d8e: */
+ 0, /* U+012d8f: */
+ 0, /* U+012d90: */
+ 0, /* U+012d91: */
+ 0, /* U+012d92: */
+ 0, /* U+012d93: */
+ 0, /* U+012d94: */
+ 0, /* U+012d95: */
+ 0, /* U+012d96: */
+ 0, /* U+012d97: */
+ 0, /* U+012d98: */
+ 0, /* U+012d99: */
+ 0, /* U+012d9a: */
+ 0, /* U+012d9b: */
+ 0, /* U+012d9c: */
+ 0, /* U+012d9d: */
+ 0, /* U+012d9e: */
+ 0, /* U+012d9f: */
+ 0, /* U+012da0: */
+ 0, /* U+012da1: */
+ 0, /* U+012da2: */
+ 0, /* U+012da3: */
+ 0, /* U+012da4: */
+ 0, /* U+012da5: */
+ 0, /* U+012da6: */
+ 0, /* U+012da7: */
+ 0, /* U+012da8: */
+ 0, /* U+012da9: */
+ 0, /* U+012daa: */
+ 0, /* U+012dab: */
+ 0, /* U+012dac: */
+ 0, /* U+012dad: */
+ 0, /* U+012dae: */
+ 0, /* U+012daf: */
+ 0, /* U+012db0: */
+ 0, /* U+012db1: */
+ 0, /* U+012db2: */
+ 0, /* U+012db3: */
+ 0, /* U+012db4: */
+ 0, /* U+012db5: */
+ 0, /* U+012db6: */
+ 0, /* U+012db7: */
+ 0, /* U+012db8: */
+ 0, /* U+012db9: */
+ 0, /* U+012dba: */
+ 0, /* U+012dbb: */
+ 0, /* U+012dbc: */
+ 0, /* U+012dbd: */
+ 0, /* U+012dbe: */
+ 0, /* U+012dbf: */
+ 0, /* U+012dc0: */
+ 0, /* U+012dc1: */
+ 0, /* U+012dc2: */
+ 0, /* U+012dc3: */
+ 0, /* U+012dc4: */
+ 0, /* U+012dc5: */
+ 0, /* U+012dc6: */
+ 0, /* U+012dc7: */
+ 0, /* U+012dc8: */
+ 0, /* U+012dc9: */
+ 0, /* U+012dca: */
+ 0, /* U+012dcb: */
+ 0, /* U+012dcc: */
+ 0, /* U+012dcd: */
+ 0, /* U+012dce: */
+ 0, /* U+012dcf: */
+ 0, /* U+012dd0: */
+ 0, /* U+012dd1: */
+ 0, /* U+012dd2: */
+ 0, /* U+012dd3: */
+ 0, /* U+012dd4: */
+ 0, /* U+012dd5: */
+ 0, /* U+012dd6: */
+ 0, /* U+012dd7: */
+ 0, /* U+012dd8: */
+ 0, /* U+012dd9: */
+ 0, /* U+012dda: */
+ 0, /* U+012ddb: */
+ 0, /* U+012ddc: */
+ 0, /* U+012ddd: */
+ 0, /* U+012dde: */
+ 0, /* U+012ddf: */
+ 0, /* U+012de0: */
+ 0, /* U+012de1: */
+ 0, /* U+012de2: */
+ 0, /* U+012de3: */
+ 0, /* U+012de4: */
+ 0, /* U+012de5: */
+ 0, /* U+012de6: */
+ 0, /* U+012de7: */
+ 0, /* U+012de8: */
+ 0, /* U+012de9: */
+ 0, /* U+012dea: */
+ 0, /* U+012deb: */
+ 0, /* U+012dec: */
+ 0, /* U+012ded: */
+ 0, /* U+012dee: */
+ 0, /* U+012def: */
+ 0, /* U+012df0: */
+ 0, /* U+012df1: */
+ 0, /* U+012df2: */
+ 0, /* U+012df3: */
+ 0, /* U+012df4: */
+ 0, /* U+012df5: */
+ 0, /* U+012df6: */
+ 0, /* U+012df7: */
+ 0, /* U+012df8: */
+ 0, /* U+012df9: */
+ 0, /* U+012dfa: */
+ 0, /* U+012dfb: */
+ 0, /* U+012dfc: */
+ 0, /* U+012dfd: */
+ 0, /* U+012dfe: */
+ 0, /* U+012dff: */
+ 0, /* U+012e00: */
+ 0, /* U+012e01: */
+ 0, /* U+012e02: */
+ 0, /* U+012e03: */
+ 0, /* U+012e04: */
+ 0, /* U+012e05: */
+ 0, /* U+012e06: */
+ 0, /* U+012e07: */
+ 0, /* U+012e08: */
+ 0, /* U+012e09: */
+ 0, /* U+012e0a: */
+ 0, /* U+012e0b: */
+ 0, /* U+012e0c: */
+ 0, /* U+012e0d: */
+ 0, /* U+012e0e: */
+ 0, /* U+012e0f: */
+ 0, /* U+012e10: */
+ 0, /* U+012e11: */
+ 0, /* U+012e12: */
+ 0, /* U+012e13: */
+ 0, /* U+012e14: */
+ 0, /* U+012e15: */
+ 0, /* U+012e16: */
+ 0, /* U+012e17: */
+ 0, /* U+012e18: */
+ 0, /* U+012e19: */
+ 0, /* U+012e1a: */
+ 0, /* U+012e1b: */
+ 0, /* U+012e1c: */
+ 0, /* U+012e1d: */
+ 0, /* U+012e1e: */
+ 0, /* U+012e1f: */
+ 0, /* U+012e20: */
+ 0, /* U+012e21: */
+ 0, /* U+012e22: */
+ 0, /* U+012e23: */
+ 0, /* U+012e24: */
+ 0, /* U+012e25: */
+ 0, /* U+012e26: */
+ 0, /* U+012e27: */
+ 0, /* U+012e28: */
+ 0, /* U+012e29: */
+ 0, /* U+012e2a: */
+ 0, /* U+012e2b: */
+ 0, /* U+012e2c: */
+ 0, /* U+012e2d: */
+ 0, /* U+012e2e: */
+ 0, /* U+012e2f: */
+ 0, /* U+012e30: */
+ 0, /* U+012e31: */
+ 0, /* U+012e32: */
+ 0, /* U+012e33: */
+ 0, /* U+012e34: */
+ 0, /* U+012e35: */
+ 0, /* U+012e36: */
+ 0, /* U+012e37: */
+ 0, /* U+012e38: */
+ 0, /* U+012e39: */
+ 0, /* U+012e3a: */
+ 0, /* U+012e3b: */
+ 0, /* U+012e3c: */
+ 0, /* U+012e3d: */
+ 0, /* U+012e3e: */
+ 0, /* U+012e3f: */
+ 0, /* U+012e40: */
+ 0, /* U+012e41: */
+ 0, /* U+012e42: */
+ 0, /* U+012e43: */
+ 0, /* U+012e44: */
+ 0, /* U+012e45: */
+ 0, /* U+012e46: */
+ 0, /* U+012e47: */
+ 0, /* U+012e48: */
+ 0, /* U+012e49: */
+ 0, /* U+012e4a: */
+ 0, /* U+012e4b: */
+ 0, /* U+012e4c: */
+ 0, /* U+012e4d: */
+ 0, /* U+012e4e: */
+ 0, /* U+012e4f: */
+ 0, /* U+012e50: */
+ 0, /* U+012e51: */
+ 0, /* U+012e52: */
+ 0, /* U+012e53: */
+ 0, /* U+012e54: */
+ 0, /* U+012e55: */
+ 0, /* U+012e56: */
+ 0, /* U+012e57: */
+ 0, /* U+012e58: */
+ 0, /* U+012e59: */
+ 0, /* U+012e5a: */
+ 0, /* U+012e5b: */
+ 0, /* U+012e5c: */
+ 0, /* U+012e5d: */
+ 0, /* U+012e5e: */
+ 0, /* U+012e5f: */
+ 0, /* U+012e60: */
+ 0, /* U+012e61: */
+ 0, /* U+012e62: */
+ 0, /* U+012e63: */
+ 0, /* U+012e64: */
+ 0, /* U+012e65: */
+ 0, /* U+012e66: */
+ 0, /* U+012e67: */
+ 0, /* U+012e68: */
+ 0, /* U+012e69: */
+ 0, /* U+012e6a: */
+ 0, /* U+012e6b: */
+ 0, /* U+012e6c: */
+ 0, /* U+012e6d: */
+ 0, /* U+012e6e: */
+ 0, /* U+012e6f: */
+ 0, /* U+012e70: */
+ 0, /* U+012e71: */
+ 0, /* U+012e72: */
+ 0, /* U+012e73: */
+ 0, /* U+012e74: */
+ 0, /* U+012e75: */
+ 0, /* U+012e76: */
+ 0, /* U+012e77: */
+ 0, /* U+012e78: */
+ 0, /* U+012e79: */
+ 0, /* U+012e7a: */
+ 0, /* U+012e7b: */
+ 0, /* U+012e7c: */
+ 0, /* U+012e7d: */
+ 0, /* U+012e7e: */
+ 0, /* U+012e7f: */
+ 0, /* U+012e80: */
+ 0, /* U+012e81: */
+ 0, /* U+012e82: */
+ 0, /* U+012e83: */
+ 0, /* U+012e84: */
+ 0, /* U+012e85: */
+ 0, /* U+012e86: */
+ 0, /* U+012e87: */
+ 0, /* U+012e88: */
+ 0, /* U+012e89: */
+ 0, /* U+012e8a: */
+ 0, /* U+012e8b: */
+ 0, /* U+012e8c: */
+ 0, /* U+012e8d: */
+ 0, /* U+012e8e: */
+ 0, /* U+012e8f: */
+ 0, /* U+012e90: */
+ 0, /* U+012e91: */
+ 0, /* U+012e92: */
+ 0, /* U+012e93: */
+ 0, /* U+012e94: */
+ 0, /* U+012e95: */
+ 0, /* U+012e96: */
+ 0, /* U+012e97: */
+ 0, /* U+012e98: */
+ 0, /* U+012e99: */
+ 0, /* U+012e9a: */
+ 0, /* U+012e9b: */
+ 0, /* U+012e9c: */
+ 0, /* U+012e9d: */
+ 0, /* U+012e9e: */
+ 0, /* U+012e9f: */
+ 0, /* U+012ea0: */
+ 0, /* U+012ea1: */
+ 0, /* U+012ea2: */
+ 0, /* U+012ea3: */
+ 0, /* U+012ea4: */
+ 0, /* U+012ea5: */
+ 0, /* U+012ea6: */
+ 0, /* U+012ea7: */
+ 0, /* U+012ea8: */
+ 0, /* U+012ea9: */
+ 0, /* U+012eaa: */
+ 0, /* U+012eab: */
+ 0, /* U+012eac: */
+ 0, /* U+012ead: */
+ 0, /* U+012eae: */
+ 0, /* U+012eaf: */
+ 0, /* U+012eb0: */
+ 0, /* U+012eb1: */
+ 0, /* U+012eb2: */
+ 0, /* U+012eb3: */
+ 0, /* U+012eb4: */
+ 0, /* U+012eb5: */
+ 0, /* U+012eb6: */
+ 0, /* U+012eb7: */
+ 0, /* U+012eb8: */
+ 0, /* U+012eb9: */
+ 0, /* U+012eba: */
+ 0, /* U+012ebb: */
+ 0, /* U+012ebc: */
+ 0, /* U+012ebd: */
+ 0, /* U+012ebe: */
+ 0, /* U+012ebf: */
+ 0, /* U+012ec0: */
+ 0, /* U+012ec1: */
+ 0, /* U+012ec2: */
+ 0, /* U+012ec3: */
+ 0, /* U+012ec4: */
+ 0, /* U+012ec5: */
+ 0, /* U+012ec6: */
+ 0, /* U+012ec7: */
+ 0, /* U+012ec8: */
+ 0, /* U+012ec9: */
+ 0, /* U+012eca: */
+ 0, /* U+012ecb: */
+ 0, /* U+012ecc: */
+ 0, /* U+012ecd: */
+ 0, /* U+012ece: */
+ 0, /* U+012ecf: */
+ 0, /* U+012ed0: */
+ 0, /* U+012ed1: */
+ 0, /* U+012ed2: */
+ 0, /* U+012ed3: */
+ 0, /* U+012ed4: */
+ 0, /* U+012ed5: */
+ 0, /* U+012ed6: */
+ 0, /* U+012ed7: */
+ 0, /* U+012ed8: */
+ 0, /* U+012ed9: */
+ 0, /* U+012eda: */
+ 0, /* U+012edb: */
+ 0, /* U+012edc: */
+ 0, /* U+012edd: */
+ 0, /* U+012ede: */
+ 0, /* U+012edf: */
+ 0, /* U+012ee0: */
+ 0, /* U+012ee1: */
+ 0, /* U+012ee2: */
+ 0, /* U+012ee3: */
+ 0, /* U+012ee4: */
+ 0, /* U+012ee5: */
+ 0, /* U+012ee6: */
+ 0, /* U+012ee7: */
+ 0, /* U+012ee8: */
+ 0, /* U+012ee9: */
+ 0, /* U+012eea: */
+ 0, /* U+012eeb: */
+ 0, /* U+012eec: */
+ 0, /* U+012eed: */
+ 0, /* U+012eee: */
+ 0, /* U+012eef: */
+ 0, /* U+012ef0: */
+ 0, /* U+012ef1: */
+ 0, /* U+012ef2: */
+ 0, /* U+012ef3: */
+ 0, /* U+012ef4: */
+ 0, /* U+012ef5: */
+ 0, /* U+012ef6: */
+ 0, /* U+012ef7: */
+ 0, /* U+012ef8: */
+ 0, /* U+012ef9: */
+ 0, /* U+012efa: */
+ 0, /* U+012efb: */
+ 0, /* U+012efc: */
+ 0, /* U+012efd: */
+ 0, /* U+012efe: */
+ 0, /* U+012eff: */
+ 0, /* U+012f00: */
+ 0, /* U+012f01: */
+ 0, /* U+012f02: */
+ 0, /* U+012f03: */
+ 0, /* U+012f04: */
+ 0, /* U+012f05: */
+ 0, /* U+012f06: */
+ 0, /* U+012f07: */
+ 0, /* U+012f08: */
+ 0, /* U+012f09: */
+ 0, /* U+012f0a: */
+ 0, /* U+012f0b: */
+ 0, /* U+012f0c: */
+ 0, /* U+012f0d: */
+ 0, /* U+012f0e: */
+ 0, /* U+012f0f: */
+ 0, /* U+012f10: */
+ 0, /* U+012f11: */
+ 0, /* U+012f12: */
+ 0, /* U+012f13: */
+ 0, /* U+012f14: */
+ 0, /* U+012f15: */
+ 0, /* U+012f16: */
+ 0, /* U+012f17: */
+ 0, /* U+012f18: */
+ 0, /* U+012f19: */
+ 0, /* U+012f1a: */
+ 0, /* U+012f1b: */
+ 0, /* U+012f1c: */
+ 0, /* U+012f1d: */
+ 0, /* U+012f1e: */
+ 0, /* U+012f1f: */
+ 0, /* U+012f20: */
+ 0, /* U+012f21: */
+ 0, /* U+012f22: */
+ 0, /* U+012f23: */
+ 0, /* U+012f24: */
+ 0, /* U+012f25: */
+ 0, /* U+012f26: */
+ 0, /* U+012f27: */
+ 0, /* U+012f28: */
+ 0, /* U+012f29: */
+ 0, /* U+012f2a: */
+ 0, /* U+012f2b: */
+ 0, /* U+012f2c: */
+ 0, /* U+012f2d: */
+ 0, /* U+012f2e: */
+ 0, /* U+012f2f: */
+ 0, /* U+012f30: */
+ 0, /* U+012f31: */
+ 0, /* U+012f32: */
+ 0, /* U+012f33: */
+ 0, /* U+012f34: */
+ 0, /* U+012f35: */
+ 0, /* U+012f36: */
+ 0, /* U+012f37: */
+ 0, /* U+012f38: */
+ 0, /* U+012f39: */
+ 0, /* U+012f3a: */
+ 0, /* U+012f3b: */
+ 0, /* U+012f3c: */
+ 0, /* U+012f3d: */
+ 0, /* U+012f3e: */
+ 0, /* U+012f3f: */
+ 0, /* U+012f40: */
+ 0, /* U+012f41: */
+ 0, /* U+012f42: */
+ 0, /* U+012f43: */
+ 0, /* U+012f44: */
+ 0, /* U+012f45: */
+ 0, /* U+012f46: */
+ 0, /* U+012f47: */
+ 0, /* U+012f48: */
+ 0, /* U+012f49: */
+ 0, /* U+012f4a: */
+ 0, /* U+012f4b: */
+ 0, /* U+012f4c: */
+ 0, /* U+012f4d: */
+ 0, /* U+012f4e: */
+ 0, /* U+012f4f: */
+ 0, /* U+012f50: */
+ 0, /* U+012f51: */
+ 0, /* U+012f52: */
+ 0, /* U+012f53: */
+ 0, /* U+012f54: */
+ 0, /* U+012f55: */
+ 0, /* U+012f56: */
+ 0, /* U+012f57: */
+ 0, /* U+012f58: */
+ 0, /* U+012f59: */
+ 0, /* U+012f5a: */
+ 0, /* U+012f5b: */
+ 0, /* U+012f5c: */
+ 0, /* U+012f5d: */
+ 0, /* U+012f5e: */
+ 0, /* U+012f5f: */
+ 0, /* U+012f60: */
+ 0, /* U+012f61: */
+ 0, /* U+012f62: */
+ 0, /* U+012f63: */
+ 0, /* U+012f64: */
+ 0, /* U+012f65: */
+ 0, /* U+012f66: */
+ 0, /* U+012f67: */
+ 0, /* U+012f68: */
+ 0, /* U+012f69: */
+ 0, /* U+012f6a: */
+ 0, /* U+012f6b: */
+ 0, /* U+012f6c: */
+ 0, /* U+012f6d: */
+ 0, /* U+012f6e: */
+ 0, /* U+012f6f: */
+ 0, /* U+012f70: */
+ 0, /* U+012f71: */
+ 0, /* U+012f72: */
+ 0, /* U+012f73: */
+ 0, /* U+012f74: */
+ 0, /* U+012f75: */
+ 0, /* U+012f76: */
+ 0, /* U+012f77: */
+ 0, /* U+012f78: */
+ 0, /* U+012f79: */
+ 0, /* U+012f7a: */
+ 0, /* U+012f7b: */
+ 0, /* U+012f7c: */
+ 0, /* U+012f7d: */
+ 0, /* U+012f7e: */
+ 0, /* U+012f7f: */
+ 0, /* U+012f80: */
+ 0, /* U+012f81: */
+ 0, /* U+012f82: */
+ 0, /* U+012f83: */
+ 0, /* U+012f84: */
+ 0, /* U+012f85: */
+ 0, /* U+012f86: */
+ 0, /* U+012f87: */
+ 0, /* U+012f88: */
+ 0, /* U+012f89: */
+ 0, /* U+012f8a: */
+ 0, /* U+012f8b: */
+ 0, /* U+012f8c: */
+ 0, /* U+012f8d: */
+ 0, /* U+012f8e: */
+ 0, /* U+012f8f: */
+ 0, /* U+012f90: */
+ 0, /* U+012f91: */
+ 0, /* U+012f92: */
+ 0, /* U+012f93: */
+ 0, /* U+012f94: */
+ 0, /* U+012f95: */
+ 0, /* U+012f96: */
+ 0, /* U+012f97: */
+ 0, /* U+012f98: */
+ 0, /* U+012f99: */
+ 0, /* U+012f9a: */
+ 0, /* U+012f9b: */
+ 0, /* U+012f9c: */
+ 0, /* U+012f9d: */
+ 0, /* U+012f9e: */
+ 0, /* U+012f9f: */
+ 0, /* U+012fa0: */
+ 0, /* U+012fa1: */
+ 0, /* U+012fa2: */
+ 0, /* U+012fa3: */
+ 0, /* U+012fa4: */
+ 0, /* U+012fa5: */
+ 0, /* U+012fa6: */
+ 0, /* U+012fa7: */
+ 0, /* U+012fa8: */
+ 0, /* U+012fa9: */
+ 0, /* U+012faa: */
+ 0, /* U+012fab: */
+ 0, /* U+012fac: */
+ 0, /* U+012fad: */
+ 0, /* U+012fae: */
+ 0, /* U+012faf: */
+ 0, /* U+012fb0: */
+ 0, /* U+012fb1: */
+ 0, /* U+012fb2: */
+ 0, /* U+012fb3: */
+ 0, /* U+012fb4: */
+ 0, /* U+012fb5: */
+ 0, /* U+012fb6: */
+ 0, /* U+012fb7: */
+ 0, /* U+012fb8: */
+ 0, /* U+012fb9: */
+ 0, /* U+012fba: */
+ 0, /* U+012fbb: */
+ 0, /* U+012fbc: */
+ 0, /* U+012fbd: */
+ 0, /* U+012fbe: */
+ 0, /* U+012fbf: */
+ 0, /* U+012fc0: */
+ 0, /* U+012fc1: */
+ 0, /* U+012fc2: */
+ 0, /* U+012fc3: */
+ 0, /* U+012fc4: */
+ 0, /* U+012fc5: */
+ 0, /* U+012fc6: */
+ 0, /* U+012fc7: */
+ 0, /* U+012fc8: */
+ 0, /* U+012fc9: */
+ 0, /* U+012fca: */
+ 0, /* U+012fcb: */
+ 0, /* U+012fcc: */
+ 0, /* U+012fcd: */
+ 0, /* U+012fce: */
+ 0, /* U+012fcf: */
+ 0, /* U+012fd0: */
+ 0, /* U+012fd1: */
+ 0, /* U+012fd2: */
+ 0, /* U+012fd3: */
+ 0, /* U+012fd4: */
+ 0, /* U+012fd5: */
+ 0, /* U+012fd6: */
+ 0, /* U+012fd7: */
+ 0, /* U+012fd8: */
+ 0, /* U+012fd9: */
+ 0, /* U+012fda: */
+ 0, /* U+012fdb: */
+ 0, /* U+012fdc: */
+ 0, /* U+012fdd: */
+ 0, /* U+012fde: */
+ 0, /* U+012fdf: */
+ 0, /* U+012fe0: */
+ 0, /* U+012fe1: */
+ 0, /* U+012fe2: */
+ 0, /* U+012fe3: */
+ 0, /* U+012fe4: */
+ 0, /* U+012fe5: */
+ 0, /* U+012fe6: */
+ 0, /* U+012fe7: */
+ 0, /* U+012fe8: */
+ 0, /* U+012fe9: */
+ 0, /* U+012fea: */
+ 0, /* U+012feb: */
+ 0, /* U+012fec: */
+ 0, /* U+012fed: */
+ 0, /* U+012fee: */
+ 0, /* U+012fef: */
+ 0, /* U+012ff0: */
+ 0, /* U+012ff1: */
+ 0, /* U+012ff2: */
+ 0, /* U+012ff3: */
+ 0, /* U+012ff4: */
+ 0, /* U+012ff5: */
+ 0, /* U+012ff6: */
+ 0, /* U+012ff7: */
+ 0, /* U+012ff8: */
+ 0, /* U+012ff9: */
+ 0, /* U+012ffa: */
+ 0, /* U+012ffb: */
+ 0, /* U+012ffc: */
+ 0, /* U+012ffd: */
+ 0, /* U+012ffe: */
+ 0, /* U+012fff: */
+ RTUNI_ALPHA, /* U+013000: EGYPTIAN HIEROGLYPH A001*/
+ RTUNI_ALPHA, /* U+013001: EGYPTIAN HIEROGLYPH A002*/
+ RTUNI_ALPHA, /* U+013002: EGYPTIAN HIEROGLYPH A003*/
+ RTUNI_ALPHA, /* U+013003: EGYPTIAN HIEROGLYPH A004*/
+ RTUNI_ALPHA, /* U+013004: EGYPTIAN HIEROGLYPH A005*/
+ RTUNI_ALPHA, /* U+013005: EGYPTIAN HIEROGLYPH A005A*/
+ RTUNI_ALPHA, /* U+013006: EGYPTIAN HIEROGLYPH A006*/
+ RTUNI_ALPHA, /* U+013007: EGYPTIAN HIEROGLYPH A006A*/
+ RTUNI_ALPHA, /* U+013008: EGYPTIAN HIEROGLYPH A006B*/
+ RTUNI_ALPHA, /* U+013009: EGYPTIAN HIEROGLYPH A007*/
+ RTUNI_ALPHA, /* U+01300a: EGYPTIAN HIEROGLYPH A008*/
+ RTUNI_ALPHA, /* U+01300b: EGYPTIAN HIEROGLYPH A009*/
+ RTUNI_ALPHA, /* U+01300c: EGYPTIAN HIEROGLYPH A010*/
+ RTUNI_ALPHA, /* U+01300d: EGYPTIAN HIEROGLYPH A011*/
+ RTUNI_ALPHA, /* U+01300e: EGYPTIAN HIEROGLYPH A012*/
+ RTUNI_ALPHA, /* U+01300f: EGYPTIAN HIEROGLYPH A013*/
+ RTUNI_ALPHA, /* U+013010: EGYPTIAN HIEROGLYPH A014*/
+ RTUNI_ALPHA, /* U+013011: EGYPTIAN HIEROGLYPH A014A*/
+ RTUNI_ALPHA, /* U+013012: EGYPTIAN HIEROGLYPH A015*/
+ RTUNI_ALPHA, /* U+013013: EGYPTIAN HIEROGLYPH A016*/
+ RTUNI_ALPHA, /* U+013014: EGYPTIAN HIEROGLYPH A017*/
+ RTUNI_ALPHA, /* U+013015: EGYPTIAN HIEROGLYPH A017A*/
+ RTUNI_ALPHA, /* U+013016: EGYPTIAN HIEROGLYPH A018*/
+ RTUNI_ALPHA, /* U+013017: EGYPTIAN HIEROGLYPH A019*/
+ RTUNI_ALPHA, /* U+013018: EGYPTIAN HIEROGLYPH A020*/
+ RTUNI_ALPHA, /* U+013019: EGYPTIAN HIEROGLYPH A021*/
+ RTUNI_ALPHA, /* U+01301a: EGYPTIAN HIEROGLYPH A022*/
+ RTUNI_ALPHA, /* U+01301b: EGYPTIAN HIEROGLYPH A023*/
+ RTUNI_ALPHA, /* U+01301c: EGYPTIAN HIEROGLYPH A024*/
+ RTUNI_ALPHA, /* U+01301d: EGYPTIAN HIEROGLYPH A025*/
+ RTUNI_ALPHA, /* U+01301e: EGYPTIAN HIEROGLYPH A026*/
+ RTUNI_ALPHA, /* U+01301f: EGYPTIAN HIEROGLYPH A027*/
+ RTUNI_ALPHA, /* U+013020: EGYPTIAN HIEROGLYPH A028*/
+ RTUNI_ALPHA, /* U+013021: EGYPTIAN HIEROGLYPH A029*/
+ RTUNI_ALPHA, /* U+013022: EGYPTIAN HIEROGLYPH A030*/
+ RTUNI_ALPHA, /* U+013023: EGYPTIAN HIEROGLYPH A031*/
+ RTUNI_ALPHA, /* U+013024: EGYPTIAN HIEROGLYPH A032*/
+ RTUNI_ALPHA, /* U+013025: EGYPTIAN HIEROGLYPH A032A*/
+ RTUNI_ALPHA, /* U+013026: EGYPTIAN HIEROGLYPH A033*/
+ RTUNI_ALPHA, /* U+013027: EGYPTIAN HIEROGLYPH A034*/
+ RTUNI_ALPHA, /* U+013028: EGYPTIAN HIEROGLYPH A035*/
+ RTUNI_ALPHA, /* U+013029: EGYPTIAN HIEROGLYPH A036*/
+ RTUNI_ALPHA, /* U+01302a: EGYPTIAN HIEROGLYPH A037*/
+ RTUNI_ALPHA, /* U+01302b: EGYPTIAN HIEROGLYPH A038*/
+ RTUNI_ALPHA, /* U+01302c: EGYPTIAN HIEROGLYPH A039*/
+ RTUNI_ALPHA, /* U+01302d: EGYPTIAN HIEROGLYPH A040*/
+ RTUNI_ALPHA, /* U+01302e: EGYPTIAN HIEROGLYPH A040A*/
+ RTUNI_ALPHA, /* U+01302f: EGYPTIAN HIEROGLYPH A041*/
+ RTUNI_ALPHA, /* U+013030: EGYPTIAN HIEROGLYPH A042*/
+ RTUNI_ALPHA, /* U+013031: EGYPTIAN HIEROGLYPH A042A*/
+ RTUNI_ALPHA, /* U+013032: EGYPTIAN HIEROGLYPH A043*/
+ RTUNI_ALPHA, /* U+013033: EGYPTIAN HIEROGLYPH A043A*/
+ RTUNI_ALPHA, /* U+013034: EGYPTIAN HIEROGLYPH A044*/
+ RTUNI_ALPHA, /* U+013035: EGYPTIAN HIEROGLYPH A045*/
+ RTUNI_ALPHA, /* U+013036: EGYPTIAN HIEROGLYPH A045A*/
+ RTUNI_ALPHA, /* U+013037: EGYPTIAN HIEROGLYPH A046*/
+ RTUNI_ALPHA, /* U+013038: EGYPTIAN HIEROGLYPH A047*/
+ RTUNI_ALPHA, /* U+013039: EGYPTIAN HIEROGLYPH A048*/
+ RTUNI_ALPHA, /* U+01303a: EGYPTIAN HIEROGLYPH A049*/
+ RTUNI_ALPHA, /* U+01303b: EGYPTIAN HIEROGLYPH A050*/
+ RTUNI_ALPHA, /* U+01303c: EGYPTIAN HIEROGLYPH A051*/
+ RTUNI_ALPHA, /* U+01303d: EGYPTIAN HIEROGLYPH A052*/
+ RTUNI_ALPHA, /* U+01303e: EGYPTIAN HIEROGLYPH A053*/
+ RTUNI_ALPHA, /* U+01303f: EGYPTIAN HIEROGLYPH A054*/
+ RTUNI_ALPHA, /* U+013040: EGYPTIAN HIEROGLYPH A055*/
+ RTUNI_ALPHA, /* U+013041: EGYPTIAN HIEROGLYPH A056*/
+ RTUNI_ALPHA, /* U+013042: EGYPTIAN HIEROGLYPH A057*/
+ RTUNI_ALPHA, /* U+013043: EGYPTIAN HIEROGLYPH A058*/
+ RTUNI_ALPHA, /* U+013044: EGYPTIAN HIEROGLYPH A059*/
+ RTUNI_ALPHA, /* U+013045: EGYPTIAN HIEROGLYPH A060*/
+ RTUNI_ALPHA, /* U+013046: EGYPTIAN HIEROGLYPH A061*/
+ RTUNI_ALPHA, /* U+013047: EGYPTIAN HIEROGLYPH A062*/
+ RTUNI_ALPHA, /* U+013048: EGYPTIAN HIEROGLYPH A063*/
+ RTUNI_ALPHA, /* U+013049: EGYPTIAN HIEROGLYPH A064*/
+ RTUNI_ALPHA, /* U+01304a: EGYPTIAN HIEROGLYPH A065*/
+ RTUNI_ALPHA, /* U+01304b: EGYPTIAN HIEROGLYPH A066*/
+ RTUNI_ALPHA, /* U+01304c: EGYPTIAN HIEROGLYPH A067*/
+ RTUNI_ALPHA, /* U+01304d: EGYPTIAN HIEROGLYPH A068*/
+ RTUNI_ALPHA, /* U+01304e: EGYPTIAN HIEROGLYPH A069*/
+ RTUNI_ALPHA, /* U+01304f: EGYPTIAN HIEROGLYPH A070*/
+ RTUNI_ALPHA, /* U+013050: EGYPTIAN HIEROGLYPH B001*/
+ RTUNI_ALPHA, /* U+013051: EGYPTIAN HIEROGLYPH B002*/
+ RTUNI_ALPHA, /* U+013052: EGYPTIAN HIEROGLYPH B003*/
+ RTUNI_ALPHA, /* U+013053: EGYPTIAN HIEROGLYPH B004*/
+ RTUNI_ALPHA, /* U+013054: EGYPTIAN HIEROGLYPH B005*/
+ RTUNI_ALPHA, /* U+013055: EGYPTIAN HIEROGLYPH B005A*/
+ RTUNI_ALPHA, /* U+013056: EGYPTIAN HIEROGLYPH B006*/
+ RTUNI_ALPHA, /* U+013057: EGYPTIAN HIEROGLYPH B007*/
+ RTUNI_ALPHA, /* U+013058: EGYPTIAN HIEROGLYPH B008*/
+ RTUNI_ALPHA, /* U+013059: EGYPTIAN HIEROGLYPH B009*/
+ RTUNI_ALPHA, /* U+01305a: EGYPTIAN HIEROGLYPH C001*/
+ RTUNI_ALPHA, /* U+01305b: EGYPTIAN HIEROGLYPH C002*/
+ RTUNI_ALPHA, /* U+01305c: EGYPTIAN HIEROGLYPH C002A*/
+ RTUNI_ALPHA, /* U+01305d: EGYPTIAN HIEROGLYPH C002B*/
+ RTUNI_ALPHA, /* U+01305e: EGYPTIAN HIEROGLYPH C002C*/
+ RTUNI_ALPHA, /* U+01305f: EGYPTIAN HIEROGLYPH C003*/
+ RTUNI_ALPHA, /* U+013060: EGYPTIAN HIEROGLYPH C004*/
+ RTUNI_ALPHA, /* U+013061: EGYPTIAN HIEROGLYPH C005*/
+ RTUNI_ALPHA, /* U+013062: EGYPTIAN HIEROGLYPH C006*/
+ RTUNI_ALPHA, /* U+013063: EGYPTIAN HIEROGLYPH C007*/
+ RTUNI_ALPHA, /* U+013064: EGYPTIAN HIEROGLYPH C008*/
+ RTUNI_ALPHA, /* U+013065: EGYPTIAN HIEROGLYPH C009*/
+ RTUNI_ALPHA, /* U+013066: EGYPTIAN HIEROGLYPH C010*/
+ RTUNI_ALPHA, /* U+013067: EGYPTIAN HIEROGLYPH C010A*/
+ RTUNI_ALPHA, /* U+013068: EGYPTIAN HIEROGLYPH C011*/
+ RTUNI_ALPHA, /* U+013069: EGYPTIAN HIEROGLYPH C012*/
+ RTUNI_ALPHA, /* U+01306a: EGYPTIAN HIEROGLYPH C013*/
+ RTUNI_ALPHA, /* U+01306b: EGYPTIAN HIEROGLYPH C014*/
+ RTUNI_ALPHA, /* U+01306c: EGYPTIAN HIEROGLYPH C015*/
+ RTUNI_ALPHA, /* U+01306d: EGYPTIAN HIEROGLYPH C016*/
+ RTUNI_ALPHA, /* U+01306e: EGYPTIAN HIEROGLYPH C017*/
+ RTUNI_ALPHA, /* U+01306f: EGYPTIAN HIEROGLYPH C018*/
+ RTUNI_ALPHA, /* U+013070: EGYPTIAN HIEROGLYPH C019*/
+ RTUNI_ALPHA, /* U+013071: EGYPTIAN HIEROGLYPH C020*/
+ RTUNI_ALPHA, /* U+013072: EGYPTIAN HIEROGLYPH C021*/
+ RTUNI_ALPHA, /* U+013073: EGYPTIAN HIEROGLYPH C022*/
+ RTUNI_ALPHA, /* U+013074: EGYPTIAN HIEROGLYPH C023*/
+ RTUNI_ALPHA, /* U+013075: EGYPTIAN HIEROGLYPH C024*/
+ RTUNI_ALPHA, /* U+013076: EGYPTIAN HIEROGLYPH D001*/
+ RTUNI_ALPHA, /* U+013077: EGYPTIAN HIEROGLYPH D002*/
+ RTUNI_ALPHA, /* U+013078: EGYPTIAN HIEROGLYPH D003*/
+ RTUNI_ALPHA, /* U+013079: EGYPTIAN HIEROGLYPH D004*/
+ RTUNI_ALPHA, /* U+01307a: EGYPTIAN HIEROGLYPH D005*/
+ RTUNI_ALPHA, /* U+01307b: EGYPTIAN HIEROGLYPH D006*/
+ RTUNI_ALPHA, /* U+01307c: EGYPTIAN HIEROGLYPH D007*/
+ RTUNI_ALPHA, /* U+01307d: EGYPTIAN HIEROGLYPH D008*/
+ RTUNI_ALPHA, /* U+01307e: EGYPTIAN HIEROGLYPH D008A*/
+ RTUNI_ALPHA, /* U+01307f: EGYPTIAN HIEROGLYPH D009*/
+ RTUNI_ALPHA, /* U+013080: EGYPTIAN HIEROGLYPH D010*/
+ RTUNI_ALPHA, /* U+013081: EGYPTIAN HIEROGLYPH D011*/
+ RTUNI_ALPHA, /* U+013082: EGYPTIAN HIEROGLYPH D012*/
+ RTUNI_ALPHA, /* U+013083: EGYPTIAN HIEROGLYPH D013*/
+ RTUNI_ALPHA, /* U+013084: EGYPTIAN HIEROGLYPH D014*/
+ RTUNI_ALPHA, /* U+013085: EGYPTIAN HIEROGLYPH D015*/
+ RTUNI_ALPHA, /* U+013086: EGYPTIAN HIEROGLYPH D016*/
+ RTUNI_ALPHA, /* U+013087: EGYPTIAN HIEROGLYPH D017*/
+ RTUNI_ALPHA, /* U+013088: EGYPTIAN HIEROGLYPH D018*/
+ RTUNI_ALPHA, /* U+013089: EGYPTIAN HIEROGLYPH D019*/
+ RTUNI_ALPHA, /* U+01308a: EGYPTIAN HIEROGLYPH D020*/
+ RTUNI_ALPHA, /* U+01308b: EGYPTIAN HIEROGLYPH D021*/
+ RTUNI_ALPHA, /* U+01308c: EGYPTIAN HIEROGLYPH D022*/
+ RTUNI_ALPHA, /* U+01308d: EGYPTIAN HIEROGLYPH D023*/
+ RTUNI_ALPHA, /* U+01308e: EGYPTIAN HIEROGLYPH D024*/
+ RTUNI_ALPHA, /* U+01308f: EGYPTIAN HIEROGLYPH D025*/
+ RTUNI_ALPHA, /* U+013090: EGYPTIAN HIEROGLYPH D026*/
+ RTUNI_ALPHA, /* U+013091: EGYPTIAN HIEROGLYPH D027*/
+ RTUNI_ALPHA, /* U+013092: EGYPTIAN HIEROGLYPH D027A*/
+ RTUNI_ALPHA, /* U+013093: EGYPTIAN HIEROGLYPH D028*/
+ RTUNI_ALPHA, /* U+013094: EGYPTIAN HIEROGLYPH D029*/
+ RTUNI_ALPHA, /* U+013095: EGYPTIAN HIEROGLYPH D030*/
+ RTUNI_ALPHA, /* U+013096: EGYPTIAN HIEROGLYPH D031*/
+ RTUNI_ALPHA, /* U+013097: EGYPTIAN HIEROGLYPH D031A*/
+ RTUNI_ALPHA, /* U+013098: EGYPTIAN HIEROGLYPH D032*/
+ RTUNI_ALPHA, /* U+013099: EGYPTIAN HIEROGLYPH D033*/
+ RTUNI_ALPHA, /* U+01309a: EGYPTIAN HIEROGLYPH D034*/
+ RTUNI_ALPHA, /* U+01309b: EGYPTIAN HIEROGLYPH D034A*/
+ RTUNI_ALPHA, /* U+01309c: EGYPTIAN HIEROGLYPH D035*/
+ RTUNI_ALPHA, /* U+01309d: EGYPTIAN HIEROGLYPH D036*/
+ RTUNI_ALPHA, /* U+01309e: EGYPTIAN HIEROGLYPH D037*/
+ RTUNI_ALPHA, /* U+01309f: EGYPTIAN HIEROGLYPH D038*/
+ RTUNI_ALPHA, /* U+0130a0: EGYPTIAN HIEROGLYPH D039*/
+ RTUNI_ALPHA, /* U+0130a1: EGYPTIAN HIEROGLYPH D040*/
+ RTUNI_ALPHA, /* U+0130a2: EGYPTIAN HIEROGLYPH D041*/
+ RTUNI_ALPHA, /* U+0130a3: EGYPTIAN HIEROGLYPH D042*/
+ RTUNI_ALPHA, /* U+0130a4: EGYPTIAN HIEROGLYPH D043*/
+ RTUNI_ALPHA, /* U+0130a5: EGYPTIAN HIEROGLYPH D044*/
+ RTUNI_ALPHA, /* U+0130a6: EGYPTIAN HIEROGLYPH D045*/
+ RTUNI_ALPHA, /* U+0130a7: EGYPTIAN HIEROGLYPH D046*/
+ RTUNI_ALPHA, /* U+0130a8: EGYPTIAN HIEROGLYPH D046A*/
+ RTUNI_ALPHA, /* U+0130a9: EGYPTIAN HIEROGLYPH D047*/
+ RTUNI_ALPHA, /* U+0130aa: EGYPTIAN HIEROGLYPH D048*/
+ RTUNI_ALPHA, /* U+0130ab: EGYPTIAN HIEROGLYPH D048A*/
+ RTUNI_ALPHA, /* U+0130ac: EGYPTIAN HIEROGLYPH D049*/
+ RTUNI_ALPHA, /* U+0130ad: EGYPTIAN HIEROGLYPH D050*/
+ RTUNI_ALPHA, /* U+0130ae: EGYPTIAN HIEROGLYPH D050A*/
+ RTUNI_ALPHA, /* U+0130af: EGYPTIAN HIEROGLYPH D050B*/
+ RTUNI_ALPHA, /* U+0130b0: EGYPTIAN HIEROGLYPH D050C*/
+ RTUNI_ALPHA, /* U+0130b1: EGYPTIAN HIEROGLYPH D050D*/
+ RTUNI_ALPHA, /* U+0130b2: EGYPTIAN HIEROGLYPH D050E*/
+ RTUNI_ALPHA, /* U+0130b3: EGYPTIAN HIEROGLYPH D050F*/
+ RTUNI_ALPHA, /* U+0130b4: EGYPTIAN HIEROGLYPH D050G*/
+ RTUNI_ALPHA, /* U+0130b5: EGYPTIAN HIEROGLYPH D050H*/
+ RTUNI_ALPHA, /* U+0130b6: EGYPTIAN HIEROGLYPH D050I*/
+ RTUNI_ALPHA, /* U+0130b7: EGYPTIAN HIEROGLYPH D051*/
+ RTUNI_ALPHA, /* U+0130b8: EGYPTIAN HIEROGLYPH D052*/
+ RTUNI_ALPHA, /* U+0130b9: EGYPTIAN HIEROGLYPH D052A*/
+ RTUNI_ALPHA, /* U+0130ba: EGYPTIAN HIEROGLYPH D053*/
+ RTUNI_ALPHA, /* U+0130bb: EGYPTIAN HIEROGLYPH D054*/
+ RTUNI_ALPHA, /* U+0130bc: EGYPTIAN HIEROGLYPH D054A*/
+ RTUNI_ALPHA, /* U+0130bd: EGYPTIAN HIEROGLYPH D055*/
+ RTUNI_ALPHA, /* U+0130be: EGYPTIAN HIEROGLYPH D056*/
+ RTUNI_ALPHA, /* U+0130bf: EGYPTIAN HIEROGLYPH D057*/
+ RTUNI_ALPHA, /* U+0130c0: EGYPTIAN HIEROGLYPH D058*/
+ RTUNI_ALPHA, /* U+0130c1: EGYPTIAN HIEROGLYPH D059*/
+ RTUNI_ALPHA, /* U+0130c2: EGYPTIAN HIEROGLYPH D060*/
+ RTUNI_ALPHA, /* U+0130c3: EGYPTIAN HIEROGLYPH D061*/
+ RTUNI_ALPHA, /* U+0130c4: EGYPTIAN HIEROGLYPH D062*/
+ RTUNI_ALPHA, /* U+0130c5: EGYPTIAN HIEROGLYPH D063*/
+ RTUNI_ALPHA, /* U+0130c6: EGYPTIAN HIEROGLYPH D064*/
+ RTUNI_ALPHA, /* U+0130c7: EGYPTIAN HIEROGLYPH D065*/
+ RTUNI_ALPHA, /* U+0130c8: EGYPTIAN HIEROGLYPH D066*/
+ RTUNI_ALPHA, /* U+0130c9: EGYPTIAN HIEROGLYPH D067*/
+ RTUNI_ALPHA, /* U+0130ca: EGYPTIAN HIEROGLYPH D067A*/
+ RTUNI_ALPHA, /* U+0130cb: EGYPTIAN HIEROGLYPH D067B*/
+ RTUNI_ALPHA, /* U+0130cc: EGYPTIAN HIEROGLYPH D067C*/
+ RTUNI_ALPHA, /* U+0130cd: EGYPTIAN HIEROGLYPH D067D*/
+ RTUNI_ALPHA, /* U+0130ce: EGYPTIAN HIEROGLYPH D067E*/
+ RTUNI_ALPHA, /* U+0130cf: EGYPTIAN HIEROGLYPH D067F*/
+ RTUNI_ALPHA, /* U+0130d0: EGYPTIAN HIEROGLYPH D067G*/
+ RTUNI_ALPHA, /* U+0130d1: EGYPTIAN HIEROGLYPH D067H*/
+ RTUNI_ALPHA, /* U+0130d2: EGYPTIAN HIEROGLYPH E001*/
+ RTUNI_ALPHA, /* U+0130d3: EGYPTIAN HIEROGLYPH E002*/
+ RTUNI_ALPHA, /* U+0130d4: EGYPTIAN HIEROGLYPH E003*/
+ RTUNI_ALPHA, /* U+0130d5: EGYPTIAN HIEROGLYPH E004*/
+ RTUNI_ALPHA, /* U+0130d6: EGYPTIAN HIEROGLYPH E005*/
+ RTUNI_ALPHA, /* U+0130d7: EGYPTIAN HIEROGLYPH E006*/
+ RTUNI_ALPHA, /* U+0130d8: EGYPTIAN HIEROGLYPH E007*/
+ RTUNI_ALPHA, /* U+0130d9: EGYPTIAN HIEROGLYPH E008*/
+ RTUNI_ALPHA, /* U+0130da: EGYPTIAN HIEROGLYPH E008A*/
+ RTUNI_ALPHA, /* U+0130db: EGYPTIAN HIEROGLYPH E009*/
+ RTUNI_ALPHA, /* U+0130dc: EGYPTIAN HIEROGLYPH E009A*/
+ RTUNI_ALPHA, /* U+0130dd: EGYPTIAN HIEROGLYPH E010*/
+ RTUNI_ALPHA, /* U+0130de: EGYPTIAN HIEROGLYPH E011*/
+ RTUNI_ALPHA, /* U+0130df: EGYPTIAN HIEROGLYPH E012*/
+ RTUNI_ALPHA, /* U+0130e0: EGYPTIAN HIEROGLYPH E013*/
+ RTUNI_ALPHA, /* U+0130e1: EGYPTIAN HIEROGLYPH E014*/
+ RTUNI_ALPHA, /* U+0130e2: EGYPTIAN HIEROGLYPH E015*/
+ RTUNI_ALPHA, /* U+0130e3: EGYPTIAN HIEROGLYPH E016*/
+ RTUNI_ALPHA, /* U+0130e4: EGYPTIAN HIEROGLYPH E016A*/
+ RTUNI_ALPHA, /* U+0130e5: EGYPTIAN HIEROGLYPH E017*/
+ RTUNI_ALPHA, /* U+0130e6: EGYPTIAN HIEROGLYPH E017A*/
+ RTUNI_ALPHA, /* U+0130e7: EGYPTIAN HIEROGLYPH E018*/
+ RTUNI_ALPHA, /* U+0130e8: EGYPTIAN HIEROGLYPH E019*/
+ RTUNI_ALPHA, /* U+0130e9: EGYPTIAN HIEROGLYPH E020*/
+ RTUNI_ALPHA, /* U+0130ea: EGYPTIAN HIEROGLYPH E020A*/
+ RTUNI_ALPHA, /* U+0130eb: EGYPTIAN HIEROGLYPH E021*/
+ RTUNI_ALPHA, /* U+0130ec: EGYPTIAN HIEROGLYPH E022*/
+ RTUNI_ALPHA, /* U+0130ed: EGYPTIAN HIEROGLYPH E023*/
+ RTUNI_ALPHA, /* U+0130ee: EGYPTIAN HIEROGLYPH E024*/
+ RTUNI_ALPHA, /* U+0130ef: EGYPTIAN HIEROGLYPH E025*/
+ RTUNI_ALPHA, /* U+0130f0: EGYPTIAN HIEROGLYPH E026*/
+ RTUNI_ALPHA, /* U+0130f1: EGYPTIAN HIEROGLYPH E027*/
+ RTUNI_ALPHA, /* U+0130f2: EGYPTIAN HIEROGLYPH E028*/
+ RTUNI_ALPHA, /* U+0130f3: EGYPTIAN HIEROGLYPH E028A*/
+ RTUNI_ALPHA, /* U+0130f4: EGYPTIAN HIEROGLYPH E029*/
+ RTUNI_ALPHA, /* U+0130f5: EGYPTIAN HIEROGLYPH E030*/
+ RTUNI_ALPHA, /* U+0130f6: EGYPTIAN HIEROGLYPH E031*/
+ RTUNI_ALPHA, /* U+0130f7: EGYPTIAN HIEROGLYPH E032*/
+ RTUNI_ALPHA, /* U+0130f8: EGYPTIAN HIEROGLYPH E033*/
+ RTUNI_ALPHA, /* U+0130f9: EGYPTIAN HIEROGLYPH E034*/
+ RTUNI_ALPHA, /* U+0130fa: EGYPTIAN HIEROGLYPH E034A*/
+ RTUNI_ALPHA, /* U+0130fb: EGYPTIAN HIEROGLYPH E036*/
+ RTUNI_ALPHA, /* U+0130fc: EGYPTIAN HIEROGLYPH E037*/
+ RTUNI_ALPHA, /* U+0130fd: EGYPTIAN HIEROGLYPH E038*/
+ RTUNI_ALPHA, /* U+0130fe: EGYPTIAN HIEROGLYPH F001*/
+ RTUNI_ALPHA, /* U+0130ff: EGYPTIAN HIEROGLYPH F001A*/
+ RTUNI_ALPHA, /* U+013100: EGYPTIAN HIEROGLYPH F002*/
+ RTUNI_ALPHA, /* U+013101: EGYPTIAN HIEROGLYPH F003*/
+ RTUNI_ALPHA, /* U+013102: EGYPTIAN HIEROGLYPH F004*/
+ RTUNI_ALPHA, /* U+013103: EGYPTIAN HIEROGLYPH F005*/
+ RTUNI_ALPHA, /* U+013104: EGYPTIAN HIEROGLYPH F006*/
+ RTUNI_ALPHA, /* U+013105: EGYPTIAN HIEROGLYPH F007*/
+ RTUNI_ALPHA, /* U+013106: EGYPTIAN HIEROGLYPH F008*/
+ RTUNI_ALPHA, /* U+013107: EGYPTIAN HIEROGLYPH F009*/
+ RTUNI_ALPHA, /* U+013108: EGYPTIAN HIEROGLYPH F010*/
+ RTUNI_ALPHA, /* U+013109: EGYPTIAN HIEROGLYPH F011*/
+ RTUNI_ALPHA, /* U+01310a: EGYPTIAN HIEROGLYPH F012*/
+ RTUNI_ALPHA, /* U+01310b: EGYPTIAN HIEROGLYPH F013*/
+ RTUNI_ALPHA, /* U+01310c: EGYPTIAN HIEROGLYPH F013A*/
+ RTUNI_ALPHA, /* U+01310d: EGYPTIAN HIEROGLYPH F014*/
+ RTUNI_ALPHA, /* U+01310e: EGYPTIAN HIEROGLYPH F015*/
+ RTUNI_ALPHA, /* U+01310f: EGYPTIAN HIEROGLYPH F016*/
+ RTUNI_ALPHA, /* U+013110: EGYPTIAN HIEROGLYPH F017*/
+ RTUNI_ALPHA, /* U+013111: EGYPTIAN HIEROGLYPH F018*/
+ RTUNI_ALPHA, /* U+013112: EGYPTIAN HIEROGLYPH F019*/
+ RTUNI_ALPHA, /* U+013113: EGYPTIAN HIEROGLYPH F020*/
+ RTUNI_ALPHA, /* U+013114: EGYPTIAN HIEROGLYPH F021*/
+ RTUNI_ALPHA, /* U+013115: EGYPTIAN HIEROGLYPH F021A*/
+ RTUNI_ALPHA, /* U+013116: EGYPTIAN HIEROGLYPH F022*/
+ RTUNI_ALPHA, /* U+013117: EGYPTIAN HIEROGLYPH F023*/
+ RTUNI_ALPHA, /* U+013118: EGYPTIAN HIEROGLYPH F024*/
+ RTUNI_ALPHA, /* U+013119: EGYPTIAN HIEROGLYPH F025*/
+ RTUNI_ALPHA, /* U+01311a: EGYPTIAN HIEROGLYPH F026*/
+ RTUNI_ALPHA, /* U+01311b: EGYPTIAN HIEROGLYPH F027*/
+ RTUNI_ALPHA, /* U+01311c: EGYPTIAN HIEROGLYPH F028*/
+ RTUNI_ALPHA, /* U+01311d: EGYPTIAN HIEROGLYPH F029*/
+ RTUNI_ALPHA, /* U+01311e: EGYPTIAN HIEROGLYPH F030*/
+ RTUNI_ALPHA, /* U+01311f: EGYPTIAN HIEROGLYPH F031*/
+ RTUNI_ALPHA, /* U+013120: EGYPTIAN HIEROGLYPH F031A*/
+ RTUNI_ALPHA, /* U+013121: EGYPTIAN HIEROGLYPH F032*/
+ RTUNI_ALPHA, /* U+013122: EGYPTIAN HIEROGLYPH F033*/
+ RTUNI_ALPHA, /* U+013123: EGYPTIAN HIEROGLYPH F034*/
+ RTUNI_ALPHA, /* U+013124: EGYPTIAN HIEROGLYPH F035*/
+ RTUNI_ALPHA, /* U+013125: EGYPTIAN HIEROGLYPH F036*/
+ RTUNI_ALPHA, /* U+013126: EGYPTIAN HIEROGLYPH F037*/
+ RTUNI_ALPHA, /* U+013127: EGYPTIAN HIEROGLYPH F037A*/
+ RTUNI_ALPHA, /* U+013128: EGYPTIAN HIEROGLYPH F038*/
+ RTUNI_ALPHA, /* U+013129: EGYPTIAN HIEROGLYPH F038A*/
+ RTUNI_ALPHA, /* U+01312a: EGYPTIAN HIEROGLYPH F039*/
+ RTUNI_ALPHA, /* U+01312b: EGYPTIAN HIEROGLYPH F040*/
+ RTUNI_ALPHA, /* U+01312c: EGYPTIAN HIEROGLYPH F041*/
+ RTUNI_ALPHA, /* U+01312d: EGYPTIAN HIEROGLYPH F042*/
+ RTUNI_ALPHA, /* U+01312e: EGYPTIAN HIEROGLYPH F043*/
+ RTUNI_ALPHA, /* U+01312f: EGYPTIAN HIEROGLYPH F044*/
+ RTUNI_ALPHA, /* U+013130: EGYPTIAN HIEROGLYPH F045*/
+ RTUNI_ALPHA, /* U+013131: EGYPTIAN HIEROGLYPH F045A*/
+ RTUNI_ALPHA, /* U+013132: EGYPTIAN HIEROGLYPH F046*/
+ RTUNI_ALPHA, /* U+013133: EGYPTIAN HIEROGLYPH F046A*/
+ RTUNI_ALPHA, /* U+013134: EGYPTIAN HIEROGLYPH F047*/
+ RTUNI_ALPHA, /* U+013135: EGYPTIAN HIEROGLYPH F047A*/
+ RTUNI_ALPHA, /* U+013136: EGYPTIAN HIEROGLYPH F048*/
+ RTUNI_ALPHA, /* U+013137: EGYPTIAN HIEROGLYPH F049*/
+ RTUNI_ALPHA, /* U+013138: EGYPTIAN HIEROGLYPH F050*/
+ RTUNI_ALPHA, /* U+013139: EGYPTIAN HIEROGLYPH F051*/
+ RTUNI_ALPHA, /* U+01313a: EGYPTIAN HIEROGLYPH F051A*/
+ RTUNI_ALPHA, /* U+01313b: EGYPTIAN HIEROGLYPH F051B*/
+ RTUNI_ALPHA, /* U+01313c: EGYPTIAN HIEROGLYPH F051C*/
+ RTUNI_ALPHA, /* U+01313d: EGYPTIAN HIEROGLYPH F052*/
+ RTUNI_ALPHA, /* U+01313e: EGYPTIAN HIEROGLYPH F053*/
+ RTUNI_ALPHA, /* U+01313f: EGYPTIAN HIEROGLYPH G001*/
+ RTUNI_ALPHA, /* U+013140: EGYPTIAN HIEROGLYPH G002*/
+ RTUNI_ALPHA, /* U+013141: EGYPTIAN HIEROGLYPH G003*/
+ RTUNI_ALPHA, /* U+013142: EGYPTIAN HIEROGLYPH G004*/
+ RTUNI_ALPHA, /* U+013143: EGYPTIAN HIEROGLYPH G005*/
+ RTUNI_ALPHA, /* U+013144: EGYPTIAN HIEROGLYPH G006*/
+ RTUNI_ALPHA, /* U+013145: EGYPTIAN HIEROGLYPH G006A*/
+ RTUNI_ALPHA, /* U+013146: EGYPTIAN HIEROGLYPH G007*/
+ RTUNI_ALPHA, /* U+013147: EGYPTIAN HIEROGLYPH G007A*/
+ RTUNI_ALPHA, /* U+013148: EGYPTIAN HIEROGLYPH G007B*/
+ RTUNI_ALPHA, /* U+013149: EGYPTIAN HIEROGLYPH G008*/
+ RTUNI_ALPHA, /* U+01314a: EGYPTIAN HIEROGLYPH G009*/
+ RTUNI_ALPHA, /* U+01314b: EGYPTIAN HIEROGLYPH G010*/
+ RTUNI_ALPHA, /* U+01314c: EGYPTIAN HIEROGLYPH G011*/
+ RTUNI_ALPHA, /* U+01314d: EGYPTIAN HIEROGLYPH G011A*/
+ RTUNI_ALPHA, /* U+01314e: EGYPTIAN HIEROGLYPH G012*/
+ RTUNI_ALPHA, /* U+01314f: EGYPTIAN HIEROGLYPH G013*/
+ RTUNI_ALPHA, /* U+013150: EGYPTIAN HIEROGLYPH G014*/
+ RTUNI_ALPHA, /* U+013151: EGYPTIAN HIEROGLYPH G015*/
+ RTUNI_ALPHA, /* U+013152: EGYPTIAN HIEROGLYPH G016*/
+ RTUNI_ALPHA, /* U+013153: EGYPTIAN HIEROGLYPH G017*/
+ RTUNI_ALPHA, /* U+013154: EGYPTIAN HIEROGLYPH G018*/
+ RTUNI_ALPHA, /* U+013155: EGYPTIAN HIEROGLYPH G019*/
+ RTUNI_ALPHA, /* U+013156: EGYPTIAN HIEROGLYPH G020*/
+ RTUNI_ALPHA, /* U+013157: EGYPTIAN HIEROGLYPH G020A*/
+ RTUNI_ALPHA, /* U+013158: EGYPTIAN HIEROGLYPH G021*/
+ RTUNI_ALPHA, /* U+013159: EGYPTIAN HIEROGLYPH G022*/
+ RTUNI_ALPHA, /* U+01315a: EGYPTIAN HIEROGLYPH G023*/
+ RTUNI_ALPHA, /* U+01315b: EGYPTIAN HIEROGLYPH G024*/
+ RTUNI_ALPHA, /* U+01315c: EGYPTIAN HIEROGLYPH G025*/
+ RTUNI_ALPHA, /* U+01315d: EGYPTIAN HIEROGLYPH G026*/
+ RTUNI_ALPHA, /* U+01315e: EGYPTIAN HIEROGLYPH G026A*/
+ RTUNI_ALPHA, /* U+01315f: EGYPTIAN HIEROGLYPH G027*/
+ RTUNI_ALPHA, /* U+013160: EGYPTIAN HIEROGLYPH G028*/
+ RTUNI_ALPHA, /* U+013161: EGYPTIAN HIEROGLYPH G029*/
+ RTUNI_ALPHA, /* U+013162: EGYPTIAN HIEROGLYPH G030*/
+ RTUNI_ALPHA, /* U+013163: EGYPTIAN HIEROGLYPH G031*/
+ RTUNI_ALPHA, /* U+013164: EGYPTIAN HIEROGLYPH G032*/
+ RTUNI_ALPHA, /* U+013165: EGYPTIAN HIEROGLYPH G033*/
+ RTUNI_ALPHA, /* U+013166: EGYPTIAN HIEROGLYPH G034*/
+ RTUNI_ALPHA, /* U+013167: EGYPTIAN HIEROGLYPH G035*/
+ RTUNI_ALPHA, /* U+013168: EGYPTIAN HIEROGLYPH G036*/
+ RTUNI_ALPHA, /* U+013169: EGYPTIAN HIEROGLYPH G036A*/
+ RTUNI_ALPHA, /* U+01316a: EGYPTIAN HIEROGLYPH G037*/
+ RTUNI_ALPHA, /* U+01316b: EGYPTIAN HIEROGLYPH G037A*/
+ RTUNI_ALPHA, /* U+01316c: EGYPTIAN HIEROGLYPH G038*/
+ RTUNI_ALPHA, /* U+01316d: EGYPTIAN HIEROGLYPH G039*/
+ RTUNI_ALPHA, /* U+01316e: EGYPTIAN HIEROGLYPH G040*/
+ RTUNI_ALPHA, /* U+01316f: EGYPTIAN HIEROGLYPH G041*/
+ RTUNI_ALPHA, /* U+013170: EGYPTIAN HIEROGLYPH G042*/
+ RTUNI_ALPHA, /* U+013171: EGYPTIAN HIEROGLYPH G043*/
+ RTUNI_ALPHA, /* U+013172: EGYPTIAN HIEROGLYPH G043A*/
+ RTUNI_ALPHA, /* U+013173: EGYPTIAN HIEROGLYPH G044*/
+ RTUNI_ALPHA, /* U+013174: EGYPTIAN HIEROGLYPH G045*/
+ RTUNI_ALPHA, /* U+013175: EGYPTIAN HIEROGLYPH G045A*/
+ RTUNI_ALPHA, /* U+013176: EGYPTIAN HIEROGLYPH G046*/
+ RTUNI_ALPHA, /* U+013177: EGYPTIAN HIEROGLYPH G047*/
+ RTUNI_ALPHA, /* U+013178: EGYPTIAN HIEROGLYPH G048*/
+ RTUNI_ALPHA, /* U+013179: EGYPTIAN HIEROGLYPH G049*/
+ RTUNI_ALPHA, /* U+01317a: EGYPTIAN HIEROGLYPH G050*/
+ RTUNI_ALPHA, /* U+01317b: EGYPTIAN HIEROGLYPH G051*/
+ RTUNI_ALPHA, /* U+01317c: EGYPTIAN HIEROGLYPH G052*/
+ RTUNI_ALPHA, /* U+01317d: EGYPTIAN HIEROGLYPH G053*/
+ RTUNI_ALPHA, /* U+01317e: EGYPTIAN HIEROGLYPH G054*/
+ RTUNI_ALPHA, /* U+01317f: EGYPTIAN HIEROGLYPH H001*/
+ RTUNI_ALPHA, /* U+013180: EGYPTIAN HIEROGLYPH H002*/
+ RTUNI_ALPHA, /* U+013181: EGYPTIAN HIEROGLYPH H003*/
+ RTUNI_ALPHA, /* U+013182: EGYPTIAN HIEROGLYPH H004*/
+ RTUNI_ALPHA, /* U+013183: EGYPTIAN HIEROGLYPH H005*/
+ RTUNI_ALPHA, /* U+013184: EGYPTIAN HIEROGLYPH H006*/
+ RTUNI_ALPHA, /* U+013185: EGYPTIAN HIEROGLYPH H006A*/
+ RTUNI_ALPHA, /* U+013186: EGYPTIAN HIEROGLYPH H007*/
+ RTUNI_ALPHA, /* U+013187: EGYPTIAN HIEROGLYPH H008*/
+ RTUNI_ALPHA, /* U+013188: EGYPTIAN HIEROGLYPH I001*/
+ RTUNI_ALPHA, /* U+013189: EGYPTIAN HIEROGLYPH I002*/
+ RTUNI_ALPHA, /* U+01318a: EGYPTIAN HIEROGLYPH I003*/
+ RTUNI_ALPHA, /* U+01318b: EGYPTIAN HIEROGLYPH I004*/
+ RTUNI_ALPHA, /* U+01318c: EGYPTIAN HIEROGLYPH I005*/
+ RTUNI_ALPHA, /* U+01318d: EGYPTIAN HIEROGLYPH I005A*/
+ RTUNI_ALPHA, /* U+01318e: EGYPTIAN HIEROGLYPH I006*/
+ RTUNI_ALPHA, /* U+01318f: EGYPTIAN HIEROGLYPH I007*/
+ RTUNI_ALPHA, /* U+013190: EGYPTIAN HIEROGLYPH I008*/
+ RTUNI_ALPHA, /* U+013191: EGYPTIAN HIEROGLYPH I009*/
+ RTUNI_ALPHA, /* U+013192: EGYPTIAN HIEROGLYPH I009A*/
+ RTUNI_ALPHA, /* U+013193: EGYPTIAN HIEROGLYPH I010*/
+ RTUNI_ALPHA, /* U+013194: EGYPTIAN HIEROGLYPH I010A*/
+ RTUNI_ALPHA, /* U+013195: EGYPTIAN HIEROGLYPH I011*/
+ RTUNI_ALPHA, /* U+013196: EGYPTIAN HIEROGLYPH I011A*/
+ RTUNI_ALPHA, /* U+013197: EGYPTIAN HIEROGLYPH I012*/
+ RTUNI_ALPHA, /* U+013198: EGYPTIAN HIEROGLYPH I013*/
+ RTUNI_ALPHA, /* U+013199: EGYPTIAN HIEROGLYPH I014*/
+ RTUNI_ALPHA, /* U+01319a: EGYPTIAN HIEROGLYPH I015*/
+ RTUNI_ALPHA, /* U+01319b: EGYPTIAN HIEROGLYPH K001*/
+ RTUNI_ALPHA, /* U+01319c: EGYPTIAN HIEROGLYPH K002*/
+ RTUNI_ALPHA, /* U+01319d: EGYPTIAN HIEROGLYPH K003*/
+ RTUNI_ALPHA, /* U+01319e: EGYPTIAN HIEROGLYPH K004*/
+ RTUNI_ALPHA, /* U+01319f: EGYPTIAN HIEROGLYPH K005*/
+ RTUNI_ALPHA, /* U+0131a0: EGYPTIAN HIEROGLYPH K006*/
+ RTUNI_ALPHA, /* U+0131a1: EGYPTIAN HIEROGLYPH K007*/
+ RTUNI_ALPHA, /* U+0131a2: EGYPTIAN HIEROGLYPH K008*/
+ RTUNI_ALPHA, /* U+0131a3: EGYPTIAN HIEROGLYPH L001*/
+ RTUNI_ALPHA, /* U+0131a4: EGYPTIAN HIEROGLYPH L002*/
+ RTUNI_ALPHA, /* U+0131a5: EGYPTIAN HIEROGLYPH L002A*/
+ RTUNI_ALPHA, /* U+0131a6: EGYPTIAN HIEROGLYPH L003*/
+ RTUNI_ALPHA, /* U+0131a7: EGYPTIAN HIEROGLYPH L004*/
+ RTUNI_ALPHA, /* U+0131a8: EGYPTIAN HIEROGLYPH L005*/
+ RTUNI_ALPHA, /* U+0131a9: EGYPTIAN HIEROGLYPH L006*/
+ RTUNI_ALPHA, /* U+0131aa: EGYPTIAN HIEROGLYPH L006A*/
+ RTUNI_ALPHA, /* U+0131ab: EGYPTIAN HIEROGLYPH L007*/
+ RTUNI_ALPHA, /* U+0131ac: EGYPTIAN HIEROGLYPH L008*/
+ RTUNI_ALPHA, /* U+0131ad: EGYPTIAN HIEROGLYPH M001*/
+ RTUNI_ALPHA, /* U+0131ae: EGYPTIAN HIEROGLYPH M001A*/
+ RTUNI_ALPHA, /* U+0131af: EGYPTIAN HIEROGLYPH M001B*/
+ RTUNI_ALPHA, /* U+0131b0: EGYPTIAN HIEROGLYPH M002*/
+ RTUNI_ALPHA, /* U+0131b1: EGYPTIAN HIEROGLYPH M003*/
+ RTUNI_ALPHA, /* U+0131b2: EGYPTIAN HIEROGLYPH M003A*/
+ RTUNI_ALPHA, /* U+0131b3: EGYPTIAN HIEROGLYPH M004*/
+ RTUNI_ALPHA, /* U+0131b4: EGYPTIAN HIEROGLYPH M005*/
+ RTUNI_ALPHA, /* U+0131b5: EGYPTIAN HIEROGLYPH M006*/
+ RTUNI_ALPHA, /* U+0131b6: EGYPTIAN HIEROGLYPH M007*/
+ RTUNI_ALPHA, /* U+0131b7: EGYPTIAN HIEROGLYPH M008*/
+ RTUNI_ALPHA, /* U+0131b8: EGYPTIAN HIEROGLYPH M009*/
+ RTUNI_ALPHA, /* U+0131b9: EGYPTIAN HIEROGLYPH M010*/
+ RTUNI_ALPHA, /* U+0131ba: EGYPTIAN HIEROGLYPH M010A*/
+ RTUNI_ALPHA, /* U+0131bb: EGYPTIAN HIEROGLYPH M011*/
+ RTUNI_ALPHA, /* U+0131bc: EGYPTIAN HIEROGLYPH M012*/
+ RTUNI_ALPHA, /* U+0131bd: EGYPTIAN HIEROGLYPH M012A*/
+ RTUNI_ALPHA, /* U+0131be: EGYPTIAN HIEROGLYPH M012B*/
+ RTUNI_ALPHA, /* U+0131bf: EGYPTIAN HIEROGLYPH M012C*/
+ RTUNI_ALPHA, /* U+0131c0: EGYPTIAN HIEROGLYPH M012D*/
+ RTUNI_ALPHA, /* U+0131c1: EGYPTIAN HIEROGLYPH M012E*/
+ RTUNI_ALPHA, /* U+0131c2: EGYPTIAN HIEROGLYPH M012F*/
+ RTUNI_ALPHA, /* U+0131c3: EGYPTIAN HIEROGLYPH M012G*/
+ RTUNI_ALPHA, /* U+0131c4: EGYPTIAN HIEROGLYPH M012H*/
+ RTUNI_ALPHA, /* U+0131c5: EGYPTIAN HIEROGLYPH M013*/
+ RTUNI_ALPHA, /* U+0131c6: EGYPTIAN HIEROGLYPH M014*/
+ RTUNI_ALPHA, /* U+0131c7: EGYPTIAN HIEROGLYPH M015*/
+ RTUNI_ALPHA, /* U+0131c8: EGYPTIAN HIEROGLYPH M015A*/
+ RTUNI_ALPHA, /* U+0131c9: EGYPTIAN HIEROGLYPH M016*/
+ RTUNI_ALPHA, /* U+0131ca: EGYPTIAN HIEROGLYPH M016A*/
+ RTUNI_ALPHA, /* U+0131cb: EGYPTIAN HIEROGLYPH M017*/
+ RTUNI_ALPHA, /* U+0131cc: EGYPTIAN HIEROGLYPH M017A*/
+ RTUNI_ALPHA, /* U+0131cd: EGYPTIAN HIEROGLYPH M018*/
+ RTUNI_ALPHA, /* U+0131ce: EGYPTIAN HIEROGLYPH M019*/
+ RTUNI_ALPHA, /* U+0131cf: EGYPTIAN HIEROGLYPH M020*/
+ RTUNI_ALPHA, /* U+0131d0: EGYPTIAN HIEROGLYPH M021*/
+ RTUNI_ALPHA, /* U+0131d1: EGYPTIAN HIEROGLYPH M022*/
+ RTUNI_ALPHA, /* U+0131d2: EGYPTIAN HIEROGLYPH M022A*/
+ RTUNI_ALPHA, /* U+0131d3: EGYPTIAN HIEROGLYPH M023*/
+ RTUNI_ALPHA, /* U+0131d4: EGYPTIAN HIEROGLYPH M024*/
+ RTUNI_ALPHA, /* U+0131d5: EGYPTIAN HIEROGLYPH M024A*/
+ RTUNI_ALPHA, /* U+0131d6: EGYPTIAN HIEROGLYPH M025*/
+ RTUNI_ALPHA, /* U+0131d7: EGYPTIAN HIEROGLYPH M026*/
+ RTUNI_ALPHA, /* U+0131d8: EGYPTIAN HIEROGLYPH M027*/
+ RTUNI_ALPHA, /* U+0131d9: EGYPTIAN HIEROGLYPH M028*/
+ RTUNI_ALPHA, /* U+0131da: EGYPTIAN HIEROGLYPH M028A*/
+ RTUNI_ALPHA, /* U+0131db: EGYPTIAN HIEROGLYPH M029*/
+ RTUNI_ALPHA, /* U+0131dc: EGYPTIAN HIEROGLYPH M030*/
+ RTUNI_ALPHA, /* U+0131dd: EGYPTIAN HIEROGLYPH M031*/
+ RTUNI_ALPHA, /* U+0131de: EGYPTIAN HIEROGLYPH M031A*/
+ RTUNI_ALPHA, /* U+0131df: EGYPTIAN HIEROGLYPH M032*/
+ RTUNI_ALPHA, /* U+0131e0: EGYPTIAN HIEROGLYPH M033*/
+ RTUNI_ALPHA, /* U+0131e1: EGYPTIAN HIEROGLYPH M033A*/
+ RTUNI_ALPHA, /* U+0131e2: EGYPTIAN HIEROGLYPH M033B*/
+ RTUNI_ALPHA, /* U+0131e3: EGYPTIAN HIEROGLYPH M034*/
+ RTUNI_ALPHA, /* U+0131e4: EGYPTIAN HIEROGLYPH M035*/
+ RTUNI_ALPHA, /* U+0131e5: EGYPTIAN HIEROGLYPH M036*/
+ RTUNI_ALPHA, /* U+0131e6: EGYPTIAN HIEROGLYPH M037*/
+ RTUNI_ALPHA, /* U+0131e7: EGYPTIAN HIEROGLYPH M038*/
+ RTUNI_ALPHA, /* U+0131e8: EGYPTIAN HIEROGLYPH M039*/
+ RTUNI_ALPHA, /* U+0131e9: EGYPTIAN HIEROGLYPH M040*/
+ RTUNI_ALPHA, /* U+0131ea: EGYPTIAN HIEROGLYPH M040A*/
+ RTUNI_ALPHA, /* U+0131eb: EGYPTIAN HIEROGLYPH M041*/
+ RTUNI_ALPHA, /* U+0131ec: EGYPTIAN HIEROGLYPH M042*/
+ RTUNI_ALPHA, /* U+0131ed: EGYPTIAN HIEROGLYPH M043*/
+ RTUNI_ALPHA, /* U+0131ee: EGYPTIAN HIEROGLYPH M044*/
+ RTUNI_ALPHA, /* U+0131ef: EGYPTIAN HIEROGLYPH N001*/
+ RTUNI_ALPHA, /* U+0131f0: EGYPTIAN HIEROGLYPH N002*/
+ RTUNI_ALPHA, /* U+0131f1: EGYPTIAN HIEROGLYPH N003*/
+ RTUNI_ALPHA, /* U+0131f2: EGYPTIAN HIEROGLYPH N004*/
+ RTUNI_ALPHA, /* U+0131f3: EGYPTIAN HIEROGLYPH N005*/
+ RTUNI_ALPHA, /* U+0131f4: EGYPTIAN HIEROGLYPH N006*/
+ RTUNI_ALPHA, /* U+0131f5: EGYPTIAN HIEROGLYPH N007*/
+ RTUNI_ALPHA, /* U+0131f6: EGYPTIAN HIEROGLYPH N008*/
+ RTUNI_ALPHA, /* U+0131f7: EGYPTIAN HIEROGLYPH N009*/
+ RTUNI_ALPHA, /* U+0131f8: EGYPTIAN HIEROGLYPH N010*/
+ RTUNI_ALPHA, /* U+0131f9: EGYPTIAN HIEROGLYPH N011*/
+ RTUNI_ALPHA, /* U+0131fa: EGYPTIAN HIEROGLYPH N012*/
+ RTUNI_ALPHA, /* U+0131fb: EGYPTIAN HIEROGLYPH N013*/
+ RTUNI_ALPHA, /* U+0131fc: EGYPTIAN HIEROGLYPH N014*/
+ RTUNI_ALPHA, /* U+0131fd: EGYPTIAN HIEROGLYPH N015*/
+ RTUNI_ALPHA, /* U+0131fe: EGYPTIAN HIEROGLYPH N016*/
+ RTUNI_ALPHA, /* U+0131ff: EGYPTIAN HIEROGLYPH N017*/
+ RTUNI_ALPHA, /* U+013200: EGYPTIAN HIEROGLYPH N018*/
+ RTUNI_ALPHA, /* U+013201: EGYPTIAN HIEROGLYPH N018A*/
+ RTUNI_ALPHA, /* U+013202: EGYPTIAN HIEROGLYPH N018B*/
+ RTUNI_ALPHA, /* U+013203: EGYPTIAN HIEROGLYPH N019*/
+ RTUNI_ALPHA, /* U+013204: EGYPTIAN HIEROGLYPH N020*/
+ RTUNI_ALPHA, /* U+013205: EGYPTIAN HIEROGLYPH N021*/
+ RTUNI_ALPHA, /* U+013206: EGYPTIAN HIEROGLYPH N022*/
+ RTUNI_ALPHA, /* U+013207: EGYPTIAN HIEROGLYPH N023*/
+ RTUNI_ALPHA, /* U+013208: EGYPTIAN HIEROGLYPH N024*/
+ RTUNI_ALPHA, /* U+013209: EGYPTIAN HIEROGLYPH N025*/
+ RTUNI_ALPHA, /* U+01320a: EGYPTIAN HIEROGLYPH N025A*/
+ RTUNI_ALPHA, /* U+01320b: EGYPTIAN HIEROGLYPH N026*/
+ RTUNI_ALPHA, /* U+01320c: EGYPTIAN HIEROGLYPH N027*/
+ RTUNI_ALPHA, /* U+01320d: EGYPTIAN HIEROGLYPH N028*/
+ RTUNI_ALPHA, /* U+01320e: EGYPTIAN HIEROGLYPH N029*/
+ RTUNI_ALPHA, /* U+01320f: EGYPTIAN HIEROGLYPH N030*/
+ RTUNI_ALPHA, /* U+013210: EGYPTIAN HIEROGLYPH N031*/
+ RTUNI_ALPHA, /* U+013211: EGYPTIAN HIEROGLYPH N032*/
+ RTUNI_ALPHA, /* U+013212: EGYPTIAN HIEROGLYPH N033*/
+ RTUNI_ALPHA, /* U+013213: EGYPTIAN HIEROGLYPH N033A*/
+ RTUNI_ALPHA, /* U+013214: EGYPTIAN HIEROGLYPH N034*/
+ RTUNI_ALPHA, /* U+013215: EGYPTIAN HIEROGLYPH N034A*/
+ RTUNI_ALPHA, /* U+013216: EGYPTIAN HIEROGLYPH N035*/
+ RTUNI_ALPHA, /* U+013217: EGYPTIAN HIEROGLYPH N035A*/
+ RTUNI_ALPHA, /* U+013218: EGYPTIAN HIEROGLYPH N036*/
+ RTUNI_ALPHA, /* U+013219: EGYPTIAN HIEROGLYPH N037*/
+ RTUNI_ALPHA, /* U+01321a: EGYPTIAN HIEROGLYPH N037A*/
+ RTUNI_ALPHA, /* U+01321b: EGYPTIAN HIEROGLYPH N038*/
+ RTUNI_ALPHA, /* U+01321c: EGYPTIAN HIEROGLYPH N039*/
+ RTUNI_ALPHA, /* U+01321d: EGYPTIAN HIEROGLYPH N040*/
+ RTUNI_ALPHA, /* U+01321e: EGYPTIAN HIEROGLYPH N041*/
+ RTUNI_ALPHA, /* U+01321f: EGYPTIAN HIEROGLYPH N042*/
+ RTUNI_ALPHA, /* U+013220: EGYPTIAN HIEROGLYPH NL001*/
+ RTUNI_ALPHA, /* U+013221: EGYPTIAN HIEROGLYPH NL002*/
+ RTUNI_ALPHA, /* U+013222: EGYPTIAN HIEROGLYPH NL003*/
+ RTUNI_ALPHA, /* U+013223: EGYPTIAN HIEROGLYPH NL004*/
+ RTUNI_ALPHA, /* U+013224: EGYPTIAN HIEROGLYPH NL005*/
+ RTUNI_ALPHA, /* U+013225: EGYPTIAN HIEROGLYPH NL005A*/
+ RTUNI_ALPHA, /* U+013226: EGYPTIAN HIEROGLYPH NL006*/
+ RTUNI_ALPHA, /* U+013227: EGYPTIAN HIEROGLYPH NL007*/
+ RTUNI_ALPHA, /* U+013228: EGYPTIAN HIEROGLYPH NL008*/
+ RTUNI_ALPHA, /* U+013229: EGYPTIAN HIEROGLYPH NL009*/
+ RTUNI_ALPHA, /* U+01322a: EGYPTIAN HIEROGLYPH NL010*/
+ RTUNI_ALPHA, /* U+01322b: EGYPTIAN HIEROGLYPH NL011*/
+ RTUNI_ALPHA, /* U+01322c: EGYPTIAN HIEROGLYPH NL012*/
+ RTUNI_ALPHA, /* U+01322d: EGYPTIAN HIEROGLYPH NL013*/
+ RTUNI_ALPHA, /* U+01322e: EGYPTIAN HIEROGLYPH NL014*/
+ RTUNI_ALPHA, /* U+01322f: EGYPTIAN HIEROGLYPH NL015*/
+ RTUNI_ALPHA, /* U+013230: EGYPTIAN HIEROGLYPH NL016*/
+ RTUNI_ALPHA, /* U+013231: EGYPTIAN HIEROGLYPH NL017*/
+ RTUNI_ALPHA, /* U+013232: EGYPTIAN HIEROGLYPH NL017A*/
+ RTUNI_ALPHA, /* U+013233: EGYPTIAN HIEROGLYPH NL018*/
+ RTUNI_ALPHA, /* U+013234: EGYPTIAN HIEROGLYPH NL019*/
+ RTUNI_ALPHA, /* U+013235: EGYPTIAN HIEROGLYPH NL020*/
+ RTUNI_ALPHA, /* U+013236: EGYPTIAN HIEROGLYPH NU001*/
+ RTUNI_ALPHA, /* U+013237: EGYPTIAN HIEROGLYPH NU002*/
+ RTUNI_ALPHA, /* U+013238: EGYPTIAN HIEROGLYPH NU003*/
+ RTUNI_ALPHA, /* U+013239: EGYPTIAN HIEROGLYPH NU004*/
+ RTUNI_ALPHA, /* U+01323a: EGYPTIAN HIEROGLYPH NU005*/
+ RTUNI_ALPHA, /* U+01323b: EGYPTIAN HIEROGLYPH NU006*/
+ RTUNI_ALPHA, /* U+01323c: EGYPTIAN HIEROGLYPH NU007*/
+ RTUNI_ALPHA, /* U+01323d: EGYPTIAN HIEROGLYPH NU008*/
+ RTUNI_ALPHA, /* U+01323e: EGYPTIAN HIEROGLYPH NU009*/
+ RTUNI_ALPHA, /* U+01323f: EGYPTIAN HIEROGLYPH NU010*/
+ RTUNI_ALPHA, /* U+013240: EGYPTIAN HIEROGLYPH NU010A*/
+ RTUNI_ALPHA, /* U+013241: EGYPTIAN HIEROGLYPH NU011*/
+ RTUNI_ALPHA, /* U+013242: EGYPTIAN HIEROGLYPH NU011A*/
+ RTUNI_ALPHA, /* U+013243: EGYPTIAN HIEROGLYPH NU012*/
+ RTUNI_ALPHA, /* U+013244: EGYPTIAN HIEROGLYPH NU013*/
+ RTUNI_ALPHA, /* U+013245: EGYPTIAN HIEROGLYPH NU014*/
+ RTUNI_ALPHA, /* U+013246: EGYPTIAN HIEROGLYPH NU015*/
+ RTUNI_ALPHA, /* U+013247: EGYPTIAN HIEROGLYPH NU016*/
+ RTUNI_ALPHA, /* U+013248: EGYPTIAN HIEROGLYPH NU017*/
+ RTUNI_ALPHA, /* U+013249: EGYPTIAN HIEROGLYPH NU018*/
+ RTUNI_ALPHA, /* U+01324a: EGYPTIAN HIEROGLYPH NU018A*/
+ RTUNI_ALPHA, /* U+01324b: EGYPTIAN HIEROGLYPH NU019*/
+ RTUNI_ALPHA, /* U+01324c: EGYPTIAN HIEROGLYPH NU020*/
+ RTUNI_ALPHA, /* U+01324d: EGYPTIAN HIEROGLYPH NU021*/
+ RTUNI_ALPHA, /* U+01324e: EGYPTIAN HIEROGLYPH NU022*/
+ RTUNI_ALPHA, /* U+01324f: EGYPTIAN HIEROGLYPH NU022A*/
+ RTUNI_ALPHA, /* U+013250: EGYPTIAN HIEROGLYPH O001*/
+ RTUNI_ALPHA, /* U+013251: EGYPTIAN HIEROGLYPH O001A*/
+ RTUNI_ALPHA, /* U+013252: EGYPTIAN HIEROGLYPH O002*/
+ RTUNI_ALPHA, /* U+013253: EGYPTIAN HIEROGLYPH O003*/
+ RTUNI_ALPHA, /* U+013254: EGYPTIAN HIEROGLYPH O004*/
+ RTUNI_ALPHA, /* U+013255: EGYPTIAN HIEROGLYPH O005*/
+ RTUNI_ALPHA, /* U+013256: EGYPTIAN HIEROGLYPH O005A*/
+ RTUNI_ALPHA, /* U+013257: EGYPTIAN HIEROGLYPH O006*/
+ RTUNI_ALPHA, /* U+013258: EGYPTIAN HIEROGLYPH O006A*/
+ RTUNI_ALPHA, /* U+013259: EGYPTIAN HIEROGLYPH O006B*/
+ RTUNI_ALPHA, /* U+01325a: EGYPTIAN HIEROGLYPH O006C*/
+ RTUNI_ALPHA, /* U+01325b: EGYPTIAN HIEROGLYPH O006D*/
+ RTUNI_ALPHA, /* U+01325c: EGYPTIAN HIEROGLYPH O006E*/
+ RTUNI_ALPHA, /* U+01325d: EGYPTIAN HIEROGLYPH O006F*/
+ RTUNI_ALPHA, /* U+01325e: EGYPTIAN HIEROGLYPH O007*/
+ RTUNI_ALPHA, /* U+01325f: EGYPTIAN HIEROGLYPH O008*/
+ RTUNI_ALPHA, /* U+013260: EGYPTIAN HIEROGLYPH O009*/
+ RTUNI_ALPHA, /* U+013261: EGYPTIAN HIEROGLYPH O010*/
+ RTUNI_ALPHA, /* U+013262: EGYPTIAN HIEROGLYPH O010A*/
+ RTUNI_ALPHA, /* U+013263: EGYPTIAN HIEROGLYPH O010B*/
+ RTUNI_ALPHA, /* U+013264: EGYPTIAN HIEROGLYPH O010C*/
+ RTUNI_ALPHA, /* U+013265: EGYPTIAN HIEROGLYPH O011*/
+ RTUNI_ALPHA, /* U+013266: EGYPTIAN HIEROGLYPH O012*/
+ RTUNI_ALPHA, /* U+013267: EGYPTIAN HIEROGLYPH O013*/
+ RTUNI_ALPHA, /* U+013268: EGYPTIAN HIEROGLYPH O014*/
+ RTUNI_ALPHA, /* U+013269: EGYPTIAN HIEROGLYPH O015*/
+ RTUNI_ALPHA, /* U+01326a: EGYPTIAN HIEROGLYPH O016*/
+ RTUNI_ALPHA, /* U+01326b: EGYPTIAN HIEROGLYPH O017*/
+ RTUNI_ALPHA, /* U+01326c: EGYPTIAN HIEROGLYPH O018*/
+ RTUNI_ALPHA, /* U+01326d: EGYPTIAN HIEROGLYPH O019*/
+ RTUNI_ALPHA, /* U+01326e: EGYPTIAN HIEROGLYPH O019A*/
+ RTUNI_ALPHA, /* U+01326f: EGYPTIAN HIEROGLYPH O020*/
+ RTUNI_ALPHA, /* U+013270: EGYPTIAN HIEROGLYPH O020A*/
+ RTUNI_ALPHA, /* U+013271: EGYPTIAN HIEROGLYPH O021*/
+ RTUNI_ALPHA, /* U+013272: EGYPTIAN HIEROGLYPH O022*/
+ RTUNI_ALPHA, /* U+013273: EGYPTIAN HIEROGLYPH O023*/
+ RTUNI_ALPHA, /* U+013274: EGYPTIAN HIEROGLYPH O024*/
+ RTUNI_ALPHA, /* U+013275: EGYPTIAN HIEROGLYPH O024A*/
+ RTUNI_ALPHA, /* U+013276: EGYPTIAN HIEROGLYPH O025*/
+ RTUNI_ALPHA, /* U+013277: EGYPTIAN HIEROGLYPH O025A*/
+ RTUNI_ALPHA, /* U+013278: EGYPTIAN HIEROGLYPH O026*/
+ RTUNI_ALPHA, /* U+013279: EGYPTIAN HIEROGLYPH O027*/
+ RTUNI_ALPHA, /* U+01327a: EGYPTIAN HIEROGLYPH O028*/
+ RTUNI_ALPHA, /* U+01327b: EGYPTIAN HIEROGLYPH O029*/
+ RTUNI_ALPHA, /* U+01327c: EGYPTIAN HIEROGLYPH O029A*/
+ RTUNI_ALPHA, /* U+01327d: EGYPTIAN HIEROGLYPH O030*/
+ RTUNI_ALPHA, /* U+01327e: EGYPTIAN HIEROGLYPH O030A*/
+ RTUNI_ALPHA, /* U+01327f: EGYPTIAN HIEROGLYPH O031*/
+ RTUNI_ALPHA, /* U+013280: EGYPTIAN HIEROGLYPH O032*/
+ RTUNI_ALPHA, /* U+013281: EGYPTIAN HIEROGLYPH O033*/
+ RTUNI_ALPHA, /* U+013282: EGYPTIAN HIEROGLYPH O033A*/
+ RTUNI_ALPHA, /* U+013283: EGYPTIAN HIEROGLYPH O034*/
+ RTUNI_ALPHA, /* U+013284: EGYPTIAN HIEROGLYPH O035*/
+ RTUNI_ALPHA, /* U+013285: EGYPTIAN HIEROGLYPH O036*/
+ RTUNI_ALPHA, /* U+013286: EGYPTIAN HIEROGLYPH O036A*/
+ RTUNI_ALPHA, /* U+013287: EGYPTIAN HIEROGLYPH O036B*/
+ RTUNI_ALPHA, /* U+013288: EGYPTIAN HIEROGLYPH O036C*/
+ RTUNI_ALPHA, /* U+013289: EGYPTIAN HIEROGLYPH O036D*/
+ RTUNI_ALPHA, /* U+01328a: EGYPTIAN HIEROGLYPH O037*/
+ RTUNI_ALPHA, /* U+01328b: EGYPTIAN HIEROGLYPH O038*/
+ RTUNI_ALPHA, /* U+01328c: EGYPTIAN HIEROGLYPH O039*/
+ RTUNI_ALPHA, /* U+01328d: EGYPTIAN HIEROGLYPH O040*/
+ RTUNI_ALPHA, /* U+01328e: EGYPTIAN HIEROGLYPH O041*/
+ RTUNI_ALPHA, /* U+01328f: EGYPTIAN HIEROGLYPH O042*/
+ RTUNI_ALPHA, /* U+013290: EGYPTIAN HIEROGLYPH O043*/
+ RTUNI_ALPHA, /* U+013291: EGYPTIAN HIEROGLYPH O044*/
+ RTUNI_ALPHA, /* U+013292: EGYPTIAN HIEROGLYPH O045*/
+ RTUNI_ALPHA, /* U+013293: EGYPTIAN HIEROGLYPH O046*/
+ RTUNI_ALPHA, /* U+013294: EGYPTIAN HIEROGLYPH O047*/
+ RTUNI_ALPHA, /* U+013295: EGYPTIAN HIEROGLYPH O048*/
+ RTUNI_ALPHA, /* U+013296: EGYPTIAN HIEROGLYPH O049*/
+ RTUNI_ALPHA, /* U+013297: EGYPTIAN HIEROGLYPH O050*/
+ RTUNI_ALPHA, /* U+013298: EGYPTIAN HIEROGLYPH O050A*/
+ RTUNI_ALPHA, /* U+013299: EGYPTIAN HIEROGLYPH O050B*/
+ RTUNI_ALPHA, /* U+01329a: EGYPTIAN HIEROGLYPH O051*/
+ RTUNI_ALPHA, /* U+01329b: EGYPTIAN HIEROGLYPH P001*/
+ RTUNI_ALPHA, /* U+01329c: EGYPTIAN HIEROGLYPH P001A*/
+ RTUNI_ALPHA, /* U+01329d: EGYPTIAN HIEROGLYPH P002*/
+ RTUNI_ALPHA, /* U+01329e: EGYPTIAN HIEROGLYPH P003*/
+ RTUNI_ALPHA, /* U+01329f: EGYPTIAN HIEROGLYPH P003A*/
+ RTUNI_ALPHA, /* U+0132a0: EGYPTIAN HIEROGLYPH P004*/
+ RTUNI_ALPHA, /* U+0132a1: EGYPTIAN HIEROGLYPH P005*/
+ RTUNI_ALPHA, /* U+0132a2: EGYPTIAN HIEROGLYPH P006*/
+ RTUNI_ALPHA, /* U+0132a3: EGYPTIAN HIEROGLYPH P007*/
+ RTUNI_ALPHA, /* U+0132a4: EGYPTIAN HIEROGLYPH P008*/
+ RTUNI_ALPHA, /* U+0132a5: EGYPTIAN HIEROGLYPH P009*/
+ RTUNI_ALPHA, /* U+0132a6: EGYPTIAN HIEROGLYPH P010*/
+ RTUNI_ALPHA, /* U+0132a7: EGYPTIAN HIEROGLYPH P011*/
+ RTUNI_ALPHA, /* U+0132a8: EGYPTIAN HIEROGLYPH Q001*/
+ RTUNI_ALPHA, /* U+0132a9: EGYPTIAN HIEROGLYPH Q002*/
+ RTUNI_ALPHA, /* U+0132aa: EGYPTIAN HIEROGLYPH Q003*/
+ RTUNI_ALPHA, /* U+0132ab: EGYPTIAN HIEROGLYPH Q004*/
+ RTUNI_ALPHA, /* U+0132ac: EGYPTIAN HIEROGLYPH Q005*/
+ RTUNI_ALPHA, /* U+0132ad: EGYPTIAN HIEROGLYPH Q006*/
+ RTUNI_ALPHA, /* U+0132ae: EGYPTIAN HIEROGLYPH Q007*/
+ RTUNI_ALPHA, /* U+0132af: EGYPTIAN HIEROGLYPH R001*/
+ RTUNI_ALPHA, /* U+0132b0: EGYPTIAN HIEROGLYPH R002*/
+ RTUNI_ALPHA, /* U+0132b1: EGYPTIAN HIEROGLYPH R002A*/
+ RTUNI_ALPHA, /* U+0132b2: EGYPTIAN HIEROGLYPH R003*/
+ RTUNI_ALPHA, /* U+0132b3: EGYPTIAN HIEROGLYPH R003A*/
+ RTUNI_ALPHA, /* U+0132b4: EGYPTIAN HIEROGLYPH R003B*/
+ RTUNI_ALPHA, /* U+0132b5: EGYPTIAN HIEROGLYPH R004*/
+ RTUNI_ALPHA, /* U+0132b6: EGYPTIAN HIEROGLYPH R005*/
+ RTUNI_ALPHA, /* U+0132b7: EGYPTIAN HIEROGLYPH R006*/
+ RTUNI_ALPHA, /* U+0132b8: EGYPTIAN HIEROGLYPH R007*/
+ RTUNI_ALPHA, /* U+0132b9: EGYPTIAN HIEROGLYPH R008*/
+ RTUNI_ALPHA, /* U+0132ba: EGYPTIAN HIEROGLYPH R009*/
+ RTUNI_ALPHA, /* U+0132bb: EGYPTIAN HIEROGLYPH R010*/
+ RTUNI_ALPHA, /* U+0132bc: EGYPTIAN HIEROGLYPH R010A*/
+ RTUNI_ALPHA, /* U+0132bd: EGYPTIAN HIEROGLYPH R011*/
+ RTUNI_ALPHA, /* U+0132be: EGYPTIAN HIEROGLYPH R012*/
+ RTUNI_ALPHA, /* U+0132bf: EGYPTIAN HIEROGLYPH R013*/
+ RTUNI_ALPHA, /* U+0132c0: EGYPTIAN HIEROGLYPH R014*/
+ RTUNI_ALPHA, /* U+0132c1: EGYPTIAN HIEROGLYPH R015*/
+ RTUNI_ALPHA, /* U+0132c2: EGYPTIAN HIEROGLYPH R016*/
+ RTUNI_ALPHA, /* U+0132c3: EGYPTIAN HIEROGLYPH R016A*/
+ RTUNI_ALPHA, /* U+0132c4: EGYPTIAN HIEROGLYPH R017*/
+ RTUNI_ALPHA, /* U+0132c5: EGYPTIAN HIEROGLYPH R018*/
+ RTUNI_ALPHA, /* U+0132c6: EGYPTIAN HIEROGLYPH R019*/
+ RTUNI_ALPHA, /* U+0132c7: EGYPTIAN HIEROGLYPH R020*/
+ RTUNI_ALPHA, /* U+0132c8: EGYPTIAN HIEROGLYPH R021*/
+ RTUNI_ALPHA, /* U+0132c9: EGYPTIAN HIEROGLYPH R022*/
+ RTUNI_ALPHA, /* U+0132ca: EGYPTIAN HIEROGLYPH R023*/
+ RTUNI_ALPHA, /* U+0132cb: EGYPTIAN HIEROGLYPH R024*/
+ RTUNI_ALPHA, /* U+0132cc: EGYPTIAN HIEROGLYPH R025*/
+ RTUNI_ALPHA, /* U+0132cd: EGYPTIAN HIEROGLYPH R026*/
+ RTUNI_ALPHA, /* U+0132ce: EGYPTIAN HIEROGLYPH R027*/
+ RTUNI_ALPHA, /* U+0132cf: EGYPTIAN HIEROGLYPH R028*/
+ RTUNI_ALPHA, /* U+0132d0: EGYPTIAN HIEROGLYPH R029*/
+ RTUNI_ALPHA, /* U+0132d1: EGYPTIAN HIEROGLYPH S001*/
+ RTUNI_ALPHA, /* U+0132d2: EGYPTIAN HIEROGLYPH S002*/
+ RTUNI_ALPHA, /* U+0132d3: EGYPTIAN HIEROGLYPH S002A*/
+ RTUNI_ALPHA, /* U+0132d4: EGYPTIAN HIEROGLYPH S003*/
+ RTUNI_ALPHA, /* U+0132d5: EGYPTIAN HIEROGLYPH S004*/
+ RTUNI_ALPHA, /* U+0132d6: EGYPTIAN HIEROGLYPH S005*/
+ RTUNI_ALPHA, /* U+0132d7: EGYPTIAN HIEROGLYPH S006*/
+ RTUNI_ALPHA, /* U+0132d8: EGYPTIAN HIEROGLYPH S006A*/
+ RTUNI_ALPHA, /* U+0132d9: EGYPTIAN HIEROGLYPH S007*/
+ RTUNI_ALPHA, /* U+0132da: EGYPTIAN HIEROGLYPH S008*/
+ RTUNI_ALPHA, /* U+0132db: EGYPTIAN HIEROGLYPH S009*/
+ RTUNI_ALPHA, /* U+0132dc: EGYPTIAN HIEROGLYPH S010*/
+ RTUNI_ALPHA, /* U+0132dd: EGYPTIAN HIEROGLYPH S011*/
+ RTUNI_ALPHA, /* U+0132de: EGYPTIAN HIEROGLYPH S012*/
+ RTUNI_ALPHA, /* U+0132df: EGYPTIAN HIEROGLYPH S013*/
+ RTUNI_ALPHA, /* U+0132e0: EGYPTIAN HIEROGLYPH S014*/
+ RTUNI_ALPHA, /* U+0132e1: EGYPTIAN HIEROGLYPH S014A*/
+ RTUNI_ALPHA, /* U+0132e2: EGYPTIAN HIEROGLYPH S014B*/
+ RTUNI_ALPHA, /* U+0132e3: EGYPTIAN HIEROGLYPH S015*/
+ RTUNI_ALPHA, /* U+0132e4: EGYPTIAN HIEROGLYPH S016*/
+ RTUNI_ALPHA, /* U+0132e5: EGYPTIAN HIEROGLYPH S017*/
+ RTUNI_ALPHA, /* U+0132e6: EGYPTIAN HIEROGLYPH S017A*/
+ RTUNI_ALPHA, /* U+0132e7: EGYPTIAN HIEROGLYPH S018*/
+ RTUNI_ALPHA, /* U+0132e8: EGYPTIAN HIEROGLYPH S019*/
+ RTUNI_ALPHA, /* U+0132e9: EGYPTIAN HIEROGLYPH S020*/
+ RTUNI_ALPHA, /* U+0132ea: EGYPTIAN HIEROGLYPH S021*/
+ RTUNI_ALPHA, /* U+0132eb: EGYPTIAN HIEROGLYPH S022*/
+ RTUNI_ALPHA, /* U+0132ec: EGYPTIAN HIEROGLYPH S023*/
+ RTUNI_ALPHA, /* U+0132ed: EGYPTIAN HIEROGLYPH S024*/
+ RTUNI_ALPHA, /* U+0132ee: EGYPTIAN HIEROGLYPH S025*/
+ RTUNI_ALPHA, /* U+0132ef: EGYPTIAN HIEROGLYPH S026*/
+ RTUNI_ALPHA, /* U+0132f0: EGYPTIAN HIEROGLYPH S026A*/
+ RTUNI_ALPHA, /* U+0132f1: EGYPTIAN HIEROGLYPH S026B*/
+ RTUNI_ALPHA, /* U+0132f2: EGYPTIAN HIEROGLYPH S027*/
+ RTUNI_ALPHA, /* U+0132f3: EGYPTIAN HIEROGLYPH S028*/
+ RTUNI_ALPHA, /* U+0132f4: EGYPTIAN HIEROGLYPH S029*/
+ RTUNI_ALPHA, /* U+0132f5: EGYPTIAN HIEROGLYPH S030*/
+ RTUNI_ALPHA, /* U+0132f6: EGYPTIAN HIEROGLYPH S031*/
+ RTUNI_ALPHA, /* U+0132f7: EGYPTIAN HIEROGLYPH S032*/
+ RTUNI_ALPHA, /* U+0132f8: EGYPTIAN HIEROGLYPH S033*/
+ RTUNI_ALPHA, /* U+0132f9: EGYPTIAN HIEROGLYPH S034*/
+ RTUNI_ALPHA, /* U+0132fa: EGYPTIAN HIEROGLYPH S035*/
+ RTUNI_ALPHA, /* U+0132fb: EGYPTIAN HIEROGLYPH S035A*/
+ RTUNI_ALPHA, /* U+0132fc: EGYPTIAN HIEROGLYPH S036*/
+ RTUNI_ALPHA, /* U+0132fd: EGYPTIAN HIEROGLYPH S037*/
+ RTUNI_ALPHA, /* U+0132fe: EGYPTIAN HIEROGLYPH S038*/
+ RTUNI_ALPHA, /* U+0132ff: EGYPTIAN HIEROGLYPH S039*/
+ RTUNI_ALPHA, /* U+013300: EGYPTIAN HIEROGLYPH S040*/
+ RTUNI_ALPHA, /* U+013301: EGYPTIAN HIEROGLYPH S041*/
+ RTUNI_ALPHA, /* U+013302: EGYPTIAN HIEROGLYPH S042*/
+ RTUNI_ALPHA, /* U+013303: EGYPTIAN HIEROGLYPH S043*/
+ RTUNI_ALPHA, /* U+013304: EGYPTIAN HIEROGLYPH S044*/
+ RTUNI_ALPHA, /* U+013305: EGYPTIAN HIEROGLYPH S045*/
+ RTUNI_ALPHA, /* U+013306: EGYPTIAN HIEROGLYPH S046*/
+ RTUNI_ALPHA, /* U+013307: EGYPTIAN HIEROGLYPH T001*/
+ RTUNI_ALPHA, /* U+013308: EGYPTIAN HIEROGLYPH T002*/
+ RTUNI_ALPHA, /* U+013309: EGYPTIAN HIEROGLYPH T003*/
+ RTUNI_ALPHA, /* U+01330a: EGYPTIAN HIEROGLYPH T003A*/
+ RTUNI_ALPHA, /* U+01330b: EGYPTIAN HIEROGLYPH T004*/
+ RTUNI_ALPHA, /* U+01330c: EGYPTIAN HIEROGLYPH T005*/
+ RTUNI_ALPHA, /* U+01330d: EGYPTIAN HIEROGLYPH T006*/
+ RTUNI_ALPHA, /* U+01330e: EGYPTIAN HIEROGLYPH T007*/
+ RTUNI_ALPHA, /* U+01330f: EGYPTIAN HIEROGLYPH T007A*/
+ RTUNI_ALPHA, /* U+013310: EGYPTIAN HIEROGLYPH T008*/
+ RTUNI_ALPHA, /* U+013311: EGYPTIAN HIEROGLYPH T008A*/
+ RTUNI_ALPHA, /* U+013312: EGYPTIAN HIEROGLYPH T009*/
+ RTUNI_ALPHA, /* U+013313: EGYPTIAN HIEROGLYPH T009A*/
+ RTUNI_ALPHA, /* U+013314: EGYPTIAN HIEROGLYPH T010*/
+ RTUNI_ALPHA, /* U+013315: EGYPTIAN HIEROGLYPH T011*/
+ RTUNI_ALPHA, /* U+013316: EGYPTIAN HIEROGLYPH T011A*/
+ RTUNI_ALPHA, /* U+013317: EGYPTIAN HIEROGLYPH T012*/
+ RTUNI_ALPHA, /* U+013318: EGYPTIAN HIEROGLYPH T013*/
+ RTUNI_ALPHA, /* U+013319: EGYPTIAN HIEROGLYPH T014*/
+ RTUNI_ALPHA, /* U+01331a: EGYPTIAN HIEROGLYPH T015*/
+ RTUNI_ALPHA, /* U+01331b: EGYPTIAN HIEROGLYPH T016*/
+ RTUNI_ALPHA, /* U+01331c: EGYPTIAN HIEROGLYPH T016A*/
+ RTUNI_ALPHA, /* U+01331d: EGYPTIAN HIEROGLYPH T017*/
+ RTUNI_ALPHA, /* U+01331e: EGYPTIAN HIEROGLYPH T018*/
+ RTUNI_ALPHA, /* U+01331f: EGYPTIAN HIEROGLYPH T019*/
+ RTUNI_ALPHA, /* U+013320: EGYPTIAN HIEROGLYPH T020*/
+ RTUNI_ALPHA, /* U+013321: EGYPTIAN HIEROGLYPH T021*/
+ RTUNI_ALPHA, /* U+013322: EGYPTIAN HIEROGLYPH T022*/
+ RTUNI_ALPHA, /* U+013323: EGYPTIAN HIEROGLYPH T023*/
+ RTUNI_ALPHA, /* U+013324: EGYPTIAN HIEROGLYPH T024*/
+ RTUNI_ALPHA, /* U+013325: EGYPTIAN HIEROGLYPH T025*/
+ RTUNI_ALPHA, /* U+013326: EGYPTIAN HIEROGLYPH T026*/
+ RTUNI_ALPHA, /* U+013327: EGYPTIAN HIEROGLYPH T027*/
+ RTUNI_ALPHA, /* U+013328: EGYPTIAN HIEROGLYPH T028*/
+ RTUNI_ALPHA, /* U+013329: EGYPTIAN HIEROGLYPH T029*/
+ RTUNI_ALPHA, /* U+01332a: EGYPTIAN HIEROGLYPH T030*/
+ RTUNI_ALPHA, /* U+01332b: EGYPTIAN HIEROGLYPH T031*/
+ RTUNI_ALPHA, /* U+01332c: EGYPTIAN HIEROGLYPH T032*/
+ RTUNI_ALPHA, /* U+01332d: EGYPTIAN HIEROGLYPH T032A*/
+ RTUNI_ALPHA, /* U+01332e: EGYPTIAN HIEROGLYPH T033*/
+ RTUNI_ALPHA, /* U+01332f: EGYPTIAN HIEROGLYPH T033A*/
+ RTUNI_ALPHA, /* U+013330: EGYPTIAN HIEROGLYPH T034*/
+ RTUNI_ALPHA, /* U+013331: EGYPTIAN HIEROGLYPH T035*/
+ RTUNI_ALPHA, /* U+013332: EGYPTIAN HIEROGLYPH T036*/
+ RTUNI_ALPHA, /* U+013333: EGYPTIAN HIEROGLYPH U001*/
+ RTUNI_ALPHA, /* U+013334: EGYPTIAN HIEROGLYPH U002*/
+ RTUNI_ALPHA, /* U+013335: EGYPTIAN HIEROGLYPH U003*/
+ RTUNI_ALPHA, /* U+013336: EGYPTIAN HIEROGLYPH U004*/
+ RTUNI_ALPHA, /* U+013337: EGYPTIAN HIEROGLYPH U005*/
+ RTUNI_ALPHA, /* U+013338: EGYPTIAN HIEROGLYPH U006*/
+ RTUNI_ALPHA, /* U+013339: EGYPTIAN HIEROGLYPH U006A*/
+ RTUNI_ALPHA, /* U+01333a: EGYPTIAN HIEROGLYPH U006B*/
+ RTUNI_ALPHA, /* U+01333b: EGYPTIAN HIEROGLYPH U007*/
+ RTUNI_ALPHA, /* U+01333c: EGYPTIAN HIEROGLYPH U008*/
+ RTUNI_ALPHA, /* U+01333d: EGYPTIAN HIEROGLYPH U009*/
+ RTUNI_ALPHA, /* U+01333e: EGYPTIAN HIEROGLYPH U010*/
+ RTUNI_ALPHA, /* U+01333f: EGYPTIAN HIEROGLYPH U011*/
+ RTUNI_ALPHA, /* U+013340: EGYPTIAN HIEROGLYPH U012*/
+ RTUNI_ALPHA, /* U+013341: EGYPTIAN HIEROGLYPH U013*/
+ RTUNI_ALPHA, /* U+013342: EGYPTIAN HIEROGLYPH U014*/
+ RTUNI_ALPHA, /* U+013343: EGYPTIAN HIEROGLYPH U015*/
+ RTUNI_ALPHA, /* U+013344: EGYPTIAN HIEROGLYPH U016*/
+ RTUNI_ALPHA, /* U+013345: EGYPTIAN HIEROGLYPH U017*/
+ RTUNI_ALPHA, /* U+013346: EGYPTIAN HIEROGLYPH U018*/
+ RTUNI_ALPHA, /* U+013347: EGYPTIAN HIEROGLYPH U019*/
+ RTUNI_ALPHA, /* U+013348: EGYPTIAN HIEROGLYPH U020*/
+ RTUNI_ALPHA, /* U+013349: EGYPTIAN HIEROGLYPH U021*/
+ RTUNI_ALPHA, /* U+01334a: EGYPTIAN HIEROGLYPH U022*/
+ RTUNI_ALPHA, /* U+01334b: EGYPTIAN HIEROGLYPH U023*/
+ RTUNI_ALPHA, /* U+01334c: EGYPTIAN HIEROGLYPH U023A*/
+ RTUNI_ALPHA, /* U+01334d: EGYPTIAN HIEROGLYPH U024*/
+ RTUNI_ALPHA, /* U+01334e: EGYPTIAN HIEROGLYPH U025*/
+ RTUNI_ALPHA, /* U+01334f: EGYPTIAN HIEROGLYPH U026*/
+ RTUNI_ALPHA, /* U+013350: EGYPTIAN HIEROGLYPH U027*/
+ RTUNI_ALPHA, /* U+013351: EGYPTIAN HIEROGLYPH U028*/
+ RTUNI_ALPHA, /* U+013352: EGYPTIAN HIEROGLYPH U029*/
+ RTUNI_ALPHA, /* U+013353: EGYPTIAN HIEROGLYPH U029A*/
+ RTUNI_ALPHA, /* U+013354: EGYPTIAN HIEROGLYPH U030*/
+ RTUNI_ALPHA, /* U+013355: EGYPTIAN HIEROGLYPH U031*/
+ RTUNI_ALPHA, /* U+013356: EGYPTIAN HIEROGLYPH U032*/
+ RTUNI_ALPHA, /* U+013357: EGYPTIAN HIEROGLYPH U032A*/
+ RTUNI_ALPHA, /* U+013358: EGYPTIAN HIEROGLYPH U033*/
+ RTUNI_ALPHA, /* U+013359: EGYPTIAN HIEROGLYPH U034*/
+ RTUNI_ALPHA, /* U+01335a: EGYPTIAN HIEROGLYPH U035*/
+ RTUNI_ALPHA, /* U+01335b: EGYPTIAN HIEROGLYPH U036*/
+ RTUNI_ALPHA, /* U+01335c: EGYPTIAN HIEROGLYPH U037*/
+ RTUNI_ALPHA, /* U+01335d: EGYPTIAN HIEROGLYPH U038*/
+ RTUNI_ALPHA, /* U+01335e: EGYPTIAN HIEROGLYPH U039*/
+ RTUNI_ALPHA, /* U+01335f: EGYPTIAN HIEROGLYPH U040*/
+ RTUNI_ALPHA, /* U+013360: EGYPTIAN HIEROGLYPH U041*/
+ RTUNI_ALPHA, /* U+013361: EGYPTIAN HIEROGLYPH U042*/
+ RTUNI_ALPHA, /* U+013362: EGYPTIAN HIEROGLYPH V001*/
+ RTUNI_ALPHA, /* U+013363: EGYPTIAN HIEROGLYPH V001A*/
+ RTUNI_ALPHA, /* U+013364: EGYPTIAN HIEROGLYPH V001B*/
+ RTUNI_ALPHA, /* U+013365: EGYPTIAN HIEROGLYPH V001C*/
+ RTUNI_ALPHA, /* U+013366: EGYPTIAN HIEROGLYPH V001D*/
+ RTUNI_ALPHA, /* U+013367: EGYPTIAN HIEROGLYPH V001E*/
+ RTUNI_ALPHA, /* U+013368: EGYPTIAN HIEROGLYPH V001F*/
+ RTUNI_ALPHA, /* U+013369: EGYPTIAN HIEROGLYPH V001G*/
+ RTUNI_ALPHA, /* U+01336a: EGYPTIAN HIEROGLYPH V001H*/
+ RTUNI_ALPHA, /* U+01336b: EGYPTIAN HIEROGLYPH V001I*/
+ RTUNI_ALPHA, /* U+01336c: EGYPTIAN HIEROGLYPH V002*/
+ RTUNI_ALPHA, /* U+01336d: EGYPTIAN HIEROGLYPH V002A*/
+ RTUNI_ALPHA, /* U+01336e: EGYPTIAN HIEROGLYPH V003*/
+ RTUNI_ALPHA, /* U+01336f: EGYPTIAN HIEROGLYPH V004*/
+ RTUNI_ALPHA, /* U+013370: EGYPTIAN HIEROGLYPH V005*/
+ RTUNI_ALPHA, /* U+013371: EGYPTIAN HIEROGLYPH V006*/
+ RTUNI_ALPHA, /* U+013372: EGYPTIAN HIEROGLYPH V007*/
+ RTUNI_ALPHA, /* U+013373: EGYPTIAN HIEROGLYPH V007A*/
+ RTUNI_ALPHA, /* U+013374: EGYPTIAN HIEROGLYPH V007B*/
+ RTUNI_ALPHA, /* U+013375: EGYPTIAN HIEROGLYPH V008*/
+ RTUNI_ALPHA, /* U+013376: EGYPTIAN HIEROGLYPH V009*/
+ RTUNI_ALPHA, /* U+013377: EGYPTIAN HIEROGLYPH V010*/
+ RTUNI_ALPHA, /* U+013378: EGYPTIAN HIEROGLYPH V011*/
+ RTUNI_ALPHA, /* U+013379: EGYPTIAN HIEROGLYPH V011A*/
+ RTUNI_ALPHA, /* U+01337a: EGYPTIAN HIEROGLYPH V011B*/
+ RTUNI_ALPHA, /* U+01337b: EGYPTIAN HIEROGLYPH V011C*/
+ RTUNI_ALPHA, /* U+01337c: EGYPTIAN HIEROGLYPH V012*/
+ RTUNI_ALPHA, /* U+01337d: EGYPTIAN HIEROGLYPH V012A*/
+ RTUNI_ALPHA, /* U+01337e: EGYPTIAN HIEROGLYPH V012B*/
+ RTUNI_ALPHA, /* U+01337f: EGYPTIAN HIEROGLYPH V013*/
+ RTUNI_ALPHA, /* U+013380: EGYPTIAN HIEROGLYPH V014*/
+ RTUNI_ALPHA, /* U+013381: EGYPTIAN HIEROGLYPH V015*/
+ RTUNI_ALPHA, /* U+013382: EGYPTIAN HIEROGLYPH V016*/
+ RTUNI_ALPHA, /* U+013383: EGYPTIAN HIEROGLYPH V017*/
+ RTUNI_ALPHA, /* U+013384: EGYPTIAN HIEROGLYPH V018*/
+ RTUNI_ALPHA, /* U+013385: EGYPTIAN HIEROGLYPH V019*/
+ RTUNI_ALPHA, /* U+013386: EGYPTIAN HIEROGLYPH V020*/
+ RTUNI_ALPHA, /* U+013387: EGYPTIAN HIEROGLYPH V020A*/
+ RTUNI_ALPHA, /* U+013388: EGYPTIAN HIEROGLYPH V020B*/
+ RTUNI_ALPHA, /* U+013389: EGYPTIAN HIEROGLYPH V020C*/
+ RTUNI_ALPHA, /* U+01338a: EGYPTIAN HIEROGLYPH V020D*/
+ RTUNI_ALPHA, /* U+01338b: EGYPTIAN HIEROGLYPH V020E*/
+ RTUNI_ALPHA, /* U+01338c: EGYPTIAN HIEROGLYPH V020F*/
+ RTUNI_ALPHA, /* U+01338d: EGYPTIAN HIEROGLYPH V020G*/
+ RTUNI_ALPHA, /* U+01338e: EGYPTIAN HIEROGLYPH V020H*/
+ RTUNI_ALPHA, /* U+01338f: EGYPTIAN HIEROGLYPH V020I*/
+ RTUNI_ALPHA, /* U+013390: EGYPTIAN HIEROGLYPH V020J*/
+ RTUNI_ALPHA, /* U+013391: EGYPTIAN HIEROGLYPH V020K*/
+ RTUNI_ALPHA, /* U+013392: EGYPTIAN HIEROGLYPH V020L*/
+ RTUNI_ALPHA, /* U+013393: EGYPTIAN HIEROGLYPH V021*/
+ RTUNI_ALPHA, /* U+013394: EGYPTIAN HIEROGLYPH V022*/
+ RTUNI_ALPHA, /* U+013395: EGYPTIAN HIEROGLYPH V023*/
+ RTUNI_ALPHA, /* U+013396: EGYPTIAN HIEROGLYPH V023A*/
+ RTUNI_ALPHA, /* U+013397: EGYPTIAN HIEROGLYPH V024*/
+ RTUNI_ALPHA, /* U+013398: EGYPTIAN HIEROGLYPH V025*/
+ RTUNI_ALPHA, /* U+013399: EGYPTIAN HIEROGLYPH V026*/
+ RTUNI_ALPHA, /* U+01339a: EGYPTIAN HIEROGLYPH V027*/
+ RTUNI_ALPHA, /* U+01339b: EGYPTIAN HIEROGLYPH V028*/
+ RTUNI_ALPHA, /* U+01339c: EGYPTIAN HIEROGLYPH V028A*/
+ RTUNI_ALPHA, /* U+01339d: EGYPTIAN HIEROGLYPH V029*/
+ RTUNI_ALPHA, /* U+01339e: EGYPTIAN HIEROGLYPH V029A*/
+ RTUNI_ALPHA, /* U+01339f: EGYPTIAN HIEROGLYPH V030*/
+ RTUNI_ALPHA, /* U+0133a0: EGYPTIAN HIEROGLYPH V030A*/
+ RTUNI_ALPHA, /* U+0133a1: EGYPTIAN HIEROGLYPH V031*/
+ RTUNI_ALPHA, /* U+0133a2: EGYPTIAN HIEROGLYPH V031A*/
+ RTUNI_ALPHA, /* U+0133a3: EGYPTIAN HIEROGLYPH V032*/
+ RTUNI_ALPHA, /* U+0133a4: EGYPTIAN HIEROGLYPH V033*/
+ RTUNI_ALPHA, /* U+0133a5: EGYPTIAN HIEROGLYPH V033A*/
+ RTUNI_ALPHA, /* U+0133a6: EGYPTIAN HIEROGLYPH V034*/
+ RTUNI_ALPHA, /* U+0133a7: EGYPTIAN HIEROGLYPH V035*/
+ RTUNI_ALPHA, /* U+0133a8: EGYPTIAN HIEROGLYPH V036*/
+ RTUNI_ALPHA, /* U+0133a9: EGYPTIAN HIEROGLYPH V037*/
+ RTUNI_ALPHA, /* U+0133aa: EGYPTIAN HIEROGLYPH V037A*/
+ RTUNI_ALPHA, /* U+0133ab: EGYPTIAN HIEROGLYPH V038*/
+ RTUNI_ALPHA, /* U+0133ac: EGYPTIAN HIEROGLYPH V039*/
+ RTUNI_ALPHA, /* U+0133ad: EGYPTIAN HIEROGLYPH V040*/
+ RTUNI_ALPHA, /* U+0133ae: EGYPTIAN HIEROGLYPH V040A*/
+ RTUNI_ALPHA, /* U+0133af: EGYPTIAN HIEROGLYPH W001*/
+ RTUNI_ALPHA, /* U+0133b0: EGYPTIAN HIEROGLYPH W002*/
+ RTUNI_ALPHA, /* U+0133b1: EGYPTIAN HIEROGLYPH W003*/
+ RTUNI_ALPHA, /* U+0133b2: EGYPTIAN HIEROGLYPH W003A*/
+ RTUNI_ALPHA, /* U+0133b3: EGYPTIAN HIEROGLYPH W004*/
+ RTUNI_ALPHA, /* U+0133b4: EGYPTIAN HIEROGLYPH W005*/
+ RTUNI_ALPHA, /* U+0133b5: EGYPTIAN HIEROGLYPH W006*/
+ RTUNI_ALPHA, /* U+0133b6: EGYPTIAN HIEROGLYPH W007*/
+ RTUNI_ALPHA, /* U+0133b7: EGYPTIAN HIEROGLYPH W008*/
+ RTUNI_ALPHA, /* U+0133b8: EGYPTIAN HIEROGLYPH W009*/
+ RTUNI_ALPHA, /* U+0133b9: EGYPTIAN HIEROGLYPH W009A*/
+ RTUNI_ALPHA, /* U+0133ba: EGYPTIAN HIEROGLYPH W010*/
+ RTUNI_ALPHA, /* U+0133bb: EGYPTIAN HIEROGLYPH W010A*/
+ RTUNI_ALPHA, /* U+0133bc: EGYPTIAN HIEROGLYPH W011*/
+ RTUNI_ALPHA, /* U+0133bd: EGYPTIAN HIEROGLYPH W012*/
+ RTUNI_ALPHA, /* U+0133be: EGYPTIAN HIEROGLYPH W013*/
+ RTUNI_ALPHA, /* U+0133bf: EGYPTIAN HIEROGLYPH W014*/
+ RTUNI_ALPHA, /* U+0133c0: EGYPTIAN HIEROGLYPH W014A*/
+ RTUNI_ALPHA, /* U+0133c1: EGYPTIAN HIEROGLYPH W015*/
+ RTUNI_ALPHA, /* U+0133c2: EGYPTIAN HIEROGLYPH W016*/
+ RTUNI_ALPHA, /* U+0133c3: EGYPTIAN HIEROGLYPH W017*/
+ RTUNI_ALPHA, /* U+0133c4: EGYPTIAN HIEROGLYPH W017A*/
+ RTUNI_ALPHA, /* U+0133c5: EGYPTIAN HIEROGLYPH W018*/
+ RTUNI_ALPHA, /* U+0133c6: EGYPTIAN HIEROGLYPH W018A*/
+ RTUNI_ALPHA, /* U+0133c7: EGYPTIAN HIEROGLYPH W019*/
+ RTUNI_ALPHA, /* U+0133c8: EGYPTIAN HIEROGLYPH W020*/
+ RTUNI_ALPHA, /* U+0133c9: EGYPTIAN HIEROGLYPH W021*/
+ RTUNI_ALPHA, /* U+0133ca: EGYPTIAN HIEROGLYPH W022*/
+ RTUNI_ALPHA, /* U+0133cb: EGYPTIAN HIEROGLYPH W023*/
+ RTUNI_ALPHA, /* U+0133cc: EGYPTIAN HIEROGLYPH W024*/
+ RTUNI_ALPHA, /* U+0133cd: EGYPTIAN HIEROGLYPH W024A*/
+ RTUNI_ALPHA, /* U+0133ce: EGYPTIAN HIEROGLYPH W025*/
+ RTUNI_ALPHA, /* U+0133cf: EGYPTIAN HIEROGLYPH X001*/
+ RTUNI_ALPHA, /* U+0133d0: EGYPTIAN HIEROGLYPH X002*/
+ RTUNI_ALPHA, /* U+0133d1: EGYPTIAN HIEROGLYPH X003*/
+ RTUNI_ALPHA, /* U+0133d2: EGYPTIAN HIEROGLYPH X004*/
+ RTUNI_ALPHA, /* U+0133d3: EGYPTIAN HIEROGLYPH X004A*/
+ RTUNI_ALPHA, /* U+0133d4: EGYPTIAN HIEROGLYPH X004B*/
+ RTUNI_ALPHA, /* U+0133d5: EGYPTIAN HIEROGLYPH X005*/
+ RTUNI_ALPHA, /* U+0133d6: EGYPTIAN HIEROGLYPH X006*/
+ RTUNI_ALPHA, /* U+0133d7: EGYPTIAN HIEROGLYPH X006A*/
+ RTUNI_ALPHA, /* U+0133d8: EGYPTIAN HIEROGLYPH X007*/
+ RTUNI_ALPHA, /* U+0133d9: EGYPTIAN HIEROGLYPH X008*/
+ RTUNI_ALPHA, /* U+0133da: EGYPTIAN HIEROGLYPH X008A*/
+ RTUNI_ALPHA, /* U+0133db: EGYPTIAN HIEROGLYPH Y001*/
+ RTUNI_ALPHA, /* U+0133dc: EGYPTIAN HIEROGLYPH Y001A*/
+ RTUNI_ALPHA, /* U+0133dd: EGYPTIAN HIEROGLYPH Y002*/
+ RTUNI_ALPHA, /* U+0133de: EGYPTIAN HIEROGLYPH Y003*/
+ RTUNI_ALPHA, /* U+0133df: EGYPTIAN HIEROGLYPH Y004*/
+ RTUNI_ALPHA, /* U+0133e0: EGYPTIAN HIEROGLYPH Y005*/
+ RTUNI_ALPHA, /* U+0133e1: EGYPTIAN HIEROGLYPH Y006*/
+ RTUNI_ALPHA, /* U+0133e2: EGYPTIAN HIEROGLYPH Y007*/
+ RTUNI_ALPHA, /* U+0133e3: EGYPTIAN HIEROGLYPH Y008*/
+ RTUNI_ALPHA, /* U+0133e4: EGYPTIAN HIEROGLYPH Z001*/
+ RTUNI_ALPHA, /* U+0133e5: EGYPTIAN HIEROGLYPH Z002*/
+ RTUNI_ALPHA, /* U+0133e6: EGYPTIAN HIEROGLYPH Z002A*/
+ RTUNI_ALPHA, /* U+0133e7: EGYPTIAN HIEROGLYPH Z002B*/
+ RTUNI_ALPHA, /* U+0133e8: EGYPTIAN HIEROGLYPH Z002C*/
+ RTUNI_ALPHA, /* U+0133e9: EGYPTIAN HIEROGLYPH Z002D*/
+ RTUNI_ALPHA, /* U+0133ea: EGYPTIAN HIEROGLYPH Z003*/
+ RTUNI_ALPHA, /* U+0133eb: EGYPTIAN HIEROGLYPH Z003A*/
+ RTUNI_ALPHA, /* U+0133ec: EGYPTIAN HIEROGLYPH Z003B*/
+ RTUNI_ALPHA, /* U+0133ed: EGYPTIAN HIEROGLYPH Z004*/
+ RTUNI_ALPHA, /* U+0133ee: EGYPTIAN HIEROGLYPH Z004A*/
+ RTUNI_ALPHA, /* U+0133ef: EGYPTIAN HIEROGLYPH Z005*/
+ RTUNI_ALPHA, /* U+0133f0: EGYPTIAN HIEROGLYPH Z005A*/
+ RTUNI_ALPHA, /* U+0133f1: EGYPTIAN HIEROGLYPH Z006*/
+ RTUNI_ALPHA, /* U+0133f2: EGYPTIAN HIEROGLYPH Z007*/
+ RTUNI_ALPHA, /* U+0133f3: EGYPTIAN HIEROGLYPH Z008*/
+ RTUNI_ALPHA, /* U+0133f4: EGYPTIAN HIEROGLYPH Z009*/
+ RTUNI_ALPHA, /* U+0133f5: EGYPTIAN HIEROGLYPH Z010*/
+ RTUNI_ALPHA, /* U+0133f6: EGYPTIAN HIEROGLYPH Z011*/
+ RTUNI_ALPHA, /* U+0133f7: EGYPTIAN HIEROGLYPH Z012*/
+ RTUNI_ALPHA, /* U+0133f8: EGYPTIAN HIEROGLYPH Z013*/
+ RTUNI_ALPHA, /* U+0133f9: EGYPTIAN HIEROGLYPH Z014*/
+ RTUNI_ALPHA, /* U+0133fa: EGYPTIAN HIEROGLYPH Z015*/
+ RTUNI_ALPHA, /* U+0133fb: EGYPTIAN HIEROGLYPH Z015A*/
+ RTUNI_ALPHA, /* U+0133fc: EGYPTIAN HIEROGLYPH Z015B*/
+ RTUNI_ALPHA, /* U+0133fd: EGYPTIAN HIEROGLYPH Z015C*/
+ RTUNI_ALPHA, /* U+0133fe: EGYPTIAN HIEROGLYPH Z015D*/
+ RTUNI_ALPHA, /* U+0133ff: EGYPTIAN HIEROGLYPH Z015E*/
+ RTUNI_ALPHA, /* U+013400: EGYPTIAN HIEROGLYPH Z015F*/
+ RTUNI_ALPHA, /* U+013401: EGYPTIAN HIEROGLYPH Z015G*/
+ RTUNI_ALPHA, /* U+013402: EGYPTIAN HIEROGLYPH Z015H*/
+ RTUNI_ALPHA, /* U+013403: EGYPTIAN HIEROGLYPH Z015I*/
+ RTUNI_ALPHA, /* U+013404: EGYPTIAN HIEROGLYPH Z016*/
+ RTUNI_ALPHA, /* U+013405: EGYPTIAN HIEROGLYPH Z016A*/
+ RTUNI_ALPHA, /* U+013406: EGYPTIAN HIEROGLYPH Z016B*/
+ RTUNI_ALPHA, /* U+013407: EGYPTIAN HIEROGLYPH Z016C*/
+ RTUNI_ALPHA, /* U+013408: EGYPTIAN HIEROGLYPH Z016D*/
+ RTUNI_ALPHA, /* U+013409: EGYPTIAN HIEROGLYPH Z016E*/
+ RTUNI_ALPHA, /* U+01340a: EGYPTIAN HIEROGLYPH Z016F*/
+ RTUNI_ALPHA, /* U+01340b: EGYPTIAN HIEROGLYPH Z016G*/
+ RTUNI_ALPHA, /* U+01340c: EGYPTIAN HIEROGLYPH Z016H*/
+ RTUNI_ALPHA, /* U+01340d: EGYPTIAN HIEROGLYPH AA001*/
+ RTUNI_ALPHA, /* U+01340e: EGYPTIAN HIEROGLYPH AA002*/
+ RTUNI_ALPHA, /* U+01340f: EGYPTIAN HIEROGLYPH AA003*/
+ RTUNI_ALPHA, /* U+013410: EGYPTIAN HIEROGLYPH AA004*/
+ RTUNI_ALPHA, /* U+013411: EGYPTIAN HIEROGLYPH AA005*/
+ RTUNI_ALPHA, /* U+013412: EGYPTIAN HIEROGLYPH AA006*/
+ RTUNI_ALPHA, /* U+013413: EGYPTIAN HIEROGLYPH AA007*/
+ RTUNI_ALPHA, /* U+013414: EGYPTIAN HIEROGLYPH AA007A*/
+ RTUNI_ALPHA, /* U+013415: EGYPTIAN HIEROGLYPH AA007B*/
+ RTUNI_ALPHA, /* U+013416: EGYPTIAN HIEROGLYPH AA008*/
+ RTUNI_ALPHA, /* U+013417: EGYPTIAN HIEROGLYPH AA009*/
+ RTUNI_ALPHA, /* U+013418: EGYPTIAN HIEROGLYPH AA010*/
+ RTUNI_ALPHA, /* U+013419: EGYPTIAN HIEROGLYPH AA011*/
+ RTUNI_ALPHA, /* U+01341a: EGYPTIAN HIEROGLYPH AA012*/
+ RTUNI_ALPHA, /* U+01341b: EGYPTIAN HIEROGLYPH AA013*/
+ RTUNI_ALPHA, /* U+01341c: EGYPTIAN HIEROGLYPH AA014*/
+ RTUNI_ALPHA, /* U+01341d: EGYPTIAN HIEROGLYPH AA015*/
+ RTUNI_ALPHA, /* U+01341e: EGYPTIAN HIEROGLYPH AA016*/
+ RTUNI_ALPHA, /* U+01341f: EGYPTIAN HIEROGLYPH AA017*/
+ RTUNI_ALPHA, /* U+013420: EGYPTIAN HIEROGLYPH AA018*/
+ RTUNI_ALPHA, /* U+013421: EGYPTIAN HIEROGLYPH AA019*/
+ RTUNI_ALPHA, /* U+013422: EGYPTIAN HIEROGLYPH AA020*/
+ RTUNI_ALPHA, /* U+013423: EGYPTIAN HIEROGLYPH AA021*/
+ RTUNI_ALPHA, /* U+013424: EGYPTIAN HIEROGLYPH AA022*/
+ RTUNI_ALPHA, /* U+013425: EGYPTIAN HIEROGLYPH AA023*/
+ RTUNI_ALPHA, /* U+013426: EGYPTIAN HIEROGLYPH AA024*/
+ RTUNI_ALPHA, /* U+013427: EGYPTIAN HIEROGLYPH AA025*/
+ RTUNI_ALPHA, /* U+013428: EGYPTIAN HIEROGLYPH AA026*/
+ RTUNI_ALPHA, /* U+013429: EGYPTIAN HIEROGLYPH AA027*/
+ RTUNI_ALPHA, /* U+01342a: EGYPTIAN HIEROGLYPH AA028*/
+ RTUNI_ALPHA, /* U+01342b: EGYPTIAN HIEROGLYPH AA029*/
+ RTUNI_ALPHA, /* U+01342c: EGYPTIAN HIEROGLYPH AA030*/
+ RTUNI_ALPHA, /* U+01342d: EGYPTIAN HIEROGLYPH AA031*/
+ RTUNI_ALPHA, /* U+01342e: EGYPTIAN HIEROGLYPH AA032*/
+};
+
+static const uint8_t g_afRTUniFlags0x016800[] =
+{
+ RTUNI_ALPHA, /* U+016800: BAMUM LETTER PHASE-A NGKUE MFON*/
+ RTUNI_ALPHA, /* U+016801: BAMUM LETTER PHASE-A GBIEE FON*/
+ RTUNI_ALPHA, /* U+016802: BAMUM LETTER PHASE-A PON MFON PIPAEMGBIEE*/
+ RTUNI_ALPHA, /* U+016803: BAMUM LETTER PHASE-A PON MFON PIPAEMBA*/
+ RTUNI_ALPHA, /* U+016804: BAMUM LETTER PHASE-A NAA MFON*/
+ RTUNI_ALPHA, /* U+016805: BAMUM LETTER PHASE-A SHUENSHUET*/
+ RTUNI_ALPHA, /* U+016806: BAMUM LETTER PHASE-A TITA MFON*/
+ RTUNI_ALPHA, /* U+016807: BAMUM LETTER PHASE-A NZA MFON*/
+ RTUNI_ALPHA, /* U+016808: BAMUM LETTER PHASE-A SHINDA PA NJI*/
+ RTUNI_ALPHA, /* U+016809: BAMUM LETTER PHASE-A PON PA NJI PIPAEMGBIEE*/
+ RTUNI_ALPHA, /* U+01680a: BAMUM LETTER PHASE-A PON PA NJI PIPAEMBA*/
+ RTUNI_ALPHA, /* U+01680b: BAMUM LETTER PHASE-A MAEMBGBIEE*/
+ RTUNI_ALPHA, /* U+01680c: BAMUM LETTER PHASE-A TU MAEMBA*/
+ RTUNI_ALPHA, /* U+01680d: BAMUM LETTER PHASE-A NGANGU*/
+ RTUNI_ALPHA, /* U+01680e: BAMUM LETTER PHASE-A MAEMVEUX*/
+ RTUNI_ALPHA, /* U+01680f: BAMUM LETTER PHASE-A MANSUAE*/
+ RTUNI_ALPHA, /* U+016810: BAMUM LETTER PHASE-A MVEUAENGAM*/
+ RTUNI_ALPHA, /* U+016811: BAMUM LETTER PHASE-A SEUNYAM*/
+ RTUNI_ALPHA, /* U+016812: BAMUM LETTER PHASE-A NTOQPEN*/
+ RTUNI_ALPHA, /* U+016813: BAMUM LETTER PHASE-A KEUKEUTNDA*/
+ RTUNI_ALPHA, /* U+016814: BAMUM LETTER PHASE-A NKINDI*/
+ RTUNI_ALPHA, /* U+016815: BAMUM LETTER PHASE-A SUU*/
+ RTUNI_ALPHA, /* U+016816: BAMUM LETTER PHASE-A NGKUENZEUM*/
+ RTUNI_ALPHA, /* U+016817: BAMUM LETTER PHASE-A LAPAQ*/
+ RTUNI_ALPHA, /* U+016818: BAMUM LETTER PHASE-A LET KUT*/
+ RTUNI_ALPHA, /* U+016819: BAMUM LETTER PHASE-A NTAP MFAA*/
+ RTUNI_ALPHA, /* U+01681a: BAMUM LETTER PHASE-A MAEKEUP*/
+ RTUNI_ALPHA, /* U+01681b: BAMUM LETTER PHASE-A PASHAE*/
+ RTUNI_ALPHA, /* U+01681c: BAMUM LETTER PHASE-A GHEUAERAE*/
+ RTUNI_ALPHA, /* U+01681d: BAMUM LETTER PHASE-A PAMSHAE*/
+ RTUNI_ALPHA, /* U+01681e: BAMUM LETTER PHASE-A MON NGGEUAET*/
+ RTUNI_ALPHA, /* U+01681f: BAMUM LETTER PHASE-A NZUN MEUT*/
+ RTUNI_ALPHA, /* U+016820: BAMUM LETTER PHASE-A U YUQ NAE*/
+ RTUNI_ALPHA, /* U+016821: BAMUM LETTER PHASE-A GHEUAEGHEUAE*/
+ RTUNI_ALPHA, /* U+016822: BAMUM LETTER PHASE-A NTAP NTAA*/
+ RTUNI_ALPHA, /* U+016823: BAMUM LETTER PHASE-A SISA*/
+ RTUNI_ALPHA, /* U+016824: BAMUM LETTER PHASE-A MGBASA*/
+ RTUNI_ALPHA, /* U+016825: BAMUM LETTER PHASE-A MEUNJOMNDEUQ*/
+ RTUNI_ALPHA, /* U+016826: BAMUM LETTER PHASE-A MOOMPUQ*/
+ RTUNI_ALPHA, /* U+016827: BAMUM LETTER PHASE-A KAFA*/
+ RTUNI_ALPHA, /* U+016828: BAMUM LETTER PHASE-A PA LEERAEWA*/
+ RTUNI_ALPHA, /* U+016829: BAMUM LETTER PHASE-A NDA LEERAEWA*/
+ RTUNI_ALPHA, /* U+01682a: BAMUM LETTER PHASE-A PET*/
+ RTUNI_ALPHA, /* U+01682b: BAMUM LETTER PHASE-A MAEMKPEN*/
+ RTUNI_ALPHA, /* U+01682c: BAMUM LETTER PHASE-A NIKA*/
+ RTUNI_ALPHA, /* U+01682d: BAMUM LETTER PHASE-A PUP*/
+ RTUNI_ALPHA, /* U+01682e: BAMUM LETTER PHASE-A TUAEP*/
+ RTUNI_ALPHA, /* U+01682f: BAMUM LETTER PHASE-A LUAEP*/
+ RTUNI_ALPHA, /* U+016830: BAMUM LETTER PHASE-A SONJAM*/
+ RTUNI_ALPHA, /* U+016831: BAMUM LETTER PHASE-A TEUTEUWEN*/
+ RTUNI_ALPHA, /* U+016832: BAMUM LETTER PHASE-A MAENYI*/
+ RTUNI_ALPHA, /* U+016833: BAMUM LETTER PHASE-A KET*/
+ RTUNI_ALPHA, /* U+016834: BAMUM LETTER PHASE-A NDAANGGEUAET*/
+ RTUNI_ALPHA, /* U+016835: BAMUM LETTER PHASE-A KUOQ*/
+ RTUNI_ALPHA, /* U+016836: BAMUM LETTER PHASE-A MOOMEUT*/
+ RTUNI_ALPHA, /* U+016837: BAMUM LETTER PHASE-A SHUM*/
+ RTUNI_ALPHA, /* U+016838: BAMUM LETTER PHASE-A LOMMAE*/
+ RTUNI_ALPHA, /* U+016839: BAMUM LETTER PHASE-A FIRI*/
+ RTUNI_ALPHA, /* U+01683a: BAMUM LETTER PHASE-A ROM*/
+ RTUNI_ALPHA, /* U+01683b: BAMUM LETTER PHASE-A KPOQ*/
+ RTUNI_ALPHA, /* U+01683c: BAMUM LETTER PHASE-A SOQ*/
+ RTUNI_ALPHA, /* U+01683d: BAMUM LETTER PHASE-A MAP PIEET*/
+ RTUNI_ALPHA, /* U+01683e: BAMUM LETTER PHASE-A SHIRAE*/
+ RTUNI_ALPHA, /* U+01683f: BAMUM LETTER PHASE-A NTAP*/
+ RTUNI_ALPHA, /* U+016840: BAMUM LETTER PHASE-A SHOQ NSHUT YUM*/
+ RTUNI_ALPHA, /* U+016841: BAMUM LETTER PHASE-A NYIT MONGKEUAEQ*/
+ RTUNI_ALPHA, /* U+016842: BAMUM LETTER PHASE-A PAARAE*/
+ RTUNI_ALPHA, /* U+016843: BAMUM LETTER PHASE-A NKAARAE*/
+ RTUNI_ALPHA, /* U+016844: BAMUM LETTER PHASE-A UNKNOWN*/
+ RTUNI_ALPHA, /* U+016845: BAMUM LETTER PHASE-A NGGEN*/
+ RTUNI_ALPHA, /* U+016846: BAMUM LETTER PHASE-A MAESI*/
+ RTUNI_ALPHA, /* U+016847: BAMUM LETTER PHASE-A NJAM*/
+ RTUNI_ALPHA, /* U+016848: BAMUM LETTER PHASE-A MBANYI*/
+ RTUNI_ALPHA, /* U+016849: BAMUM LETTER PHASE-A NYET*/
+ RTUNI_ALPHA, /* U+01684a: BAMUM LETTER PHASE-A TEUAEN*/
+ RTUNI_ALPHA, /* U+01684b: BAMUM LETTER PHASE-A SOT*/
+ RTUNI_ALPHA, /* U+01684c: BAMUM LETTER PHASE-A PAAM*/
+ RTUNI_ALPHA, /* U+01684d: BAMUM LETTER PHASE-A NSHIEE*/
+ RTUNI_ALPHA, /* U+01684e: BAMUM LETTER PHASE-A MAEM*/
+ RTUNI_ALPHA, /* U+01684f: BAMUM LETTER PHASE-A NYI*/
+ RTUNI_ALPHA, /* U+016850: BAMUM LETTER PHASE-A KAQ*/
+ RTUNI_ALPHA, /* U+016851: BAMUM LETTER PHASE-A NSHA*/
+ RTUNI_ALPHA, /* U+016852: BAMUM LETTER PHASE-A VEE*/
+ RTUNI_ALPHA, /* U+016853: BAMUM LETTER PHASE-A LU*/
+ RTUNI_ALPHA, /* U+016854: BAMUM LETTER PHASE-A NEN*/
+ RTUNI_ALPHA, /* U+016855: BAMUM LETTER PHASE-A NAQ*/
+ RTUNI_ALPHA, /* U+016856: BAMUM LETTER PHASE-A MBAQ*/
+ RTUNI_ALPHA, /* U+016857: BAMUM LETTER PHASE-B NSHUET*/
+ RTUNI_ALPHA, /* U+016858: BAMUM LETTER PHASE-B TU MAEMGBIEE*/
+ RTUNI_ALPHA, /* U+016859: BAMUM LETTER PHASE-B SIEE*/
+ RTUNI_ALPHA, /* U+01685a: BAMUM LETTER PHASE-B SET TU*/
+ RTUNI_ALPHA, /* U+01685b: BAMUM LETTER PHASE-B LOM NTEUM*/
+ RTUNI_ALPHA, /* U+01685c: BAMUM LETTER PHASE-B MBA MAELEE*/
+ RTUNI_ALPHA, /* U+01685d: BAMUM LETTER PHASE-B KIEEM*/
+ RTUNI_ALPHA, /* U+01685e: BAMUM LETTER PHASE-B YEURAE*/
+ RTUNI_ALPHA, /* U+01685f: BAMUM LETTER PHASE-B MBAARAE*/
+ RTUNI_ALPHA, /* U+016860: BAMUM LETTER PHASE-B KAM*/
+ RTUNI_ALPHA, /* U+016861: BAMUM LETTER PHASE-B PEESHI*/
+ RTUNI_ALPHA, /* U+016862: BAMUM LETTER PHASE-B YAFU LEERAEWA*/
+ RTUNI_ALPHA, /* U+016863: BAMUM LETTER PHASE-B LAM NSHUT NYAM*/
+ RTUNI_ALPHA, /* U+016864: BAMUM LETTER PHASE-B NTIEE SHEUOQ*/
+ RTUNI_ALPHA, /* U+016865: BAMUM LETTER PHASE-B NDU NJAA*/
+ RTUNI_ALPHA, /* U+016866: BAMUM LETTER PHASE-B GHEUGHEUAEM*/
+ RTUNI_ALPHA, /* U+016867: BAMUM LETTER PHASE-B PIT*/
+ RTUNI_ALPHA, /* U+016868: BAMUM LETTER PHASE-B TU NSIEE*/
+ RTUNI_ALPHA, /* U+016869: BAMUM LETTER PHASE-B SHET NJAQ*/
+ RTUNI_ALPHA, /* U+01686a: BAMUM LETTER PHASE-B SHEUAEQTU*/
+ RTUNI_ALPHA, /* U+01686b: BAMUM LETTER PHASE-B MFON TEUAEQ*/
+ RTUNI_ALPHA, /* U+01686c: BAMUM LETTER PHASE-B MBIT MBAAKET*/
+ RTUNI_ALPHA, /* U+01686d: BAMUM LETTER PHASE-B NYI NTEUM*/
+ RTUNI_ALPHA, /* U+01686e: BAMUM LETTER PHASE-B KEUPUQ*/
+ RTUNI_ALPHA, /* U+01686f: BAMUM LETTER PHASE-B GHEUGHEN*/
+ RTUNI_ALPHA, /* U+016870: BAMUM LETTER PHASE-B KEUYEUX*/
+ RTUNI_ALPHA, /* U+016871: BAMUM LETTER PHASE-B LAANAE*/
+ RTUNI_ALPHA, /* U+016872: BAMUM LETTER PHASE-B PARUM*/
+ RTUNI_ALPHA, /* U+016873: BAMUM LETTER PHASE-B VEUM*/
+ RTUNI_ALPHA, /* U+016874: BAMUM LETTER PHASE-B NGKINDI MVOP*/
+ RTUNI_ALPHA, /* U+016875: BAMUM LETTER PHASE-B NGGEU MBU*/
+ RTUNI_ALPHA, /* U+016876: BAMUM LETTER PHASE-B WUAET*/
+ RTUNI_ALPHA, /* U+016877: BAMUM LETTER PHASE-B SAKEUAE*/
+ RTUNI_ALPHA, /* U+016878: BAMUM LETTER PHASE-B TAAM*/
+ RTUNI_ALPHA, /* U+016879: BAMUM LETTER PHASE-B MEUQ*/
+ RTUNI_ALPHA, /* U+01687a: BAMUM LETTER PHASE-B NGGUOQ*/
+ RTUNI_ALPHA, /* U+01687b: BAMUM LETTER PHASE-B NGGUOQ LARGE*/
+ RTUNI_ALPHA, /* U+01687c: BAMUM LETTER PHASE-B MFIYAQ*/
+ RTUNI_ALPHA, /* U+01687d: BAMUM LETTER PHASE-B SUE*/
+ RTUNI_ALPHA, /* U+01687e: BAMUM LETTER PHASE-B MBEURI*/
+ RTUNI_ALPHA, /* U+01687f: BAMUM LETTER PHASE-B MONTIEEN*/
+ RTUNI_ALPHA, /* U+016880: BAMUM LETTER PHASE-B NYAEMAE*/
+ RTUNI_ALPHA, /* U+016881: BAMUM LETTER PHASE-B PUNGAAM*/
+ RTUNI_ALPHA, /* U+016882: BAMUM LETTER PHASE-B MEUT NGGEET*/
+ RTUNI_ALPHA, /* U+016883: BAMUM LETTER PHASE-B FEUX*/
+ RTUNI_ALPHA, /* U+016884: BAMUM LETTER PHASE-B MBUOQ*/
+ RTUNI_ALPHA, /* U+016885: BAMUM LETTER PHASE-B FEE*/
+ RTUNI_ALPHA, /* U+016886: BAMUM LETTER PHASE-B KEUAEM*/
+ RTUNI_ALPHA, /* U+016887: BAMUM LETTER PHASE-B MA NJEUAENA*/
+ RTUNI_ALPHA, /* U+016888: BAMUM LETTER PHASE-B MA NJUQA*/
+ RTUNI_ALPHA, /* U+016889: BAMUM LETTER PHASE-B LET*/
+ RTUNI_ALPHA, /* U+01688a: BAMUM LETTER PHASE-B NGGAAM*/
+ RTUNI_ALPHA, /* U+01688b: BAMUM LETTER PHASE-B NSEN*/
+ RTUNI_ALPHA, /* U+01688c: BAMUM LETTER PHASE-B MA*/
+ RTUNI_ALPHA, /* U+01688d: BAMUM LETTER PHASE-B KIQ*/
+ RTUNI_ALPHA, /* U+01688e: BAMUM LETTER PHASE-B NGOM*/
+ RTUNI_ALPHA, /* U+01688f: BAMUM LETTER PHASE-C NGKUE MAEMBA*/
+ RTUNI_ALPHA, /* U+016890: BAMUM LETTER PHASE-C NZA*/
+ RTUNI_ALPHA, /* U+016891: BAMUM LETTER PHASE-C YUM*/
+ RTUNI_ALPHA, /* U+016892: BAMUM LETTER PHASE-C WANGKUOQ*/
+ RTUNI_ALPHA, /* U+016893: BAMUM LETTER PHASE-C NGGEN*/
+ RTUNI_ALPHA, /* U+016894: BAMUM LETTER PHASE-C NDEUAEREE*/
+ RTUNI_ALPHA, /* U+016895: BAMUM LETTER PHASE-C NGKAQ*/
+ RTUNI_ALPHA, /* U+016896: BAMUM LETTER PHASE-C GHARAE*/
+ RTUNI_ALPHA, /* U+016897: BAMUM LETTER PHASE-C MBEEKEET*/
+ RTUNI_ALPHA, /* U+016898: BAMUM LETTER PHASE-C GBAYI*/
+ RTUNI_ALPHA, /* U+016899: BAMUM LETTER PHASE-C NYIR MKPARAQ MEUN*/
+ RTUNI_ALPHA, /* U+01689a: BAMUM LETTER PHASE-C NTU MBIT*/
+ RTUNI_ALPHA, /* U+01689b: BAMUM LETTER PHASE-C MBEUM*/
+ RTUNI_ALPHA, /* U+01689c: BAMUM LETTER PHASE-C PIRIEEN*/
+ RTUNI_ALPHA, /* U+01689d: BAMUM LETTER PHASE-C NDOMBU*/
+ RTUNI_ALPHA, /* U+01689e: BAMUM LETTER PHASE-C MBAA CABBAGE-TREE*/
+ RTUNI_ALPHA, /* U+01689f: BAMUM LETTER PHASE-C KEUSHEUAEP*/
+ RTUNI_ALPHA, /* U+0168a0: BAMUM LETTER PHASE-C GHAP*/
+ RTUNI_ALPHA, /* U+0168a1: BAMUM LETTER PHASE-C KEUKAQ*/
+ RTUNI_ALPHA, /* U+0168a2: BAMUM LETTER PHASE-C YU MUOMAE*/
+ RTUNI_ALPHA, /* U+0168a3: BAMUM LETTER PHASE-C NZEUM*/
+ RTUNI_ALPHA, /* U+0168a4: BAMUM LETTER PHASE-C MBUE*/
+ RTUNI_ALPHA, /* U+0168a5: BAMUM LETTER PHASE-C NSEUAEN*/
+ RTUNI_ALPHA, /* U+0168a6: BAMUM LETTER PHASE-C MBIT*/
+ RTUNI_ALPHA, /* U+0168a7: BAMUM LETTER PHASE-C YEUQ*/
+ RTUNI_ALPHA, /* U+0168a8: BAMUM LETTER PHASE-C KPARAQ*/
+ RTUNI_ALPHA, /* U+0168a9: BAMUM LETTER PHASE-C KAA*/
+ RTUNI_ALPHA, /* U+0168aa: BAMUM LETTER PHASE-C SEUX*/
+ RTUNI_ALPHA, /* U+0168ab: BAMUM LETTER PHASE-C NDIDA*/
+ RTUNI_ALPHA, /* U+0168ac: BAMUM LETTER PHASE-C TAASHAE*/
+ RTUNI_ALPHA, /* U+0168ad: BAMUM LETTER PHASE-C NJUEQ*/
+ RTUNI_ALPHA, /* U+0168ae: BAMUM LETTER PHASE-C TITA YUE*/
+ RTUNI_ALPHA, /* U+0168af: BAMUM LETTER PHASE-C SUAET*/
+ RTUNI_ALPHA, /* U+0168b0: BAMUM LETTER PHASE-C NGGUAEN NYAM*/
+ RTUNI_ALPHA, /* U+0168b1: BAMUM LETTER PHASE-C VEUX*/
+ RTUNI_ALPHA, /* U+0168b2: BAMUM LETTER PHASE-C NANSANAQ*/
+ RTUNI_ALPHA, /* U+0168b3: BAMUM LETTER PHASE-C MA KEUAERI*/
+ RTUNI_ALPHA, /* U+0168b4: BAMUM LETTER PHASE-C NTAA*/
+ RTUNI_ALPHA, /* U+0168b5: BAMUM LETTER PHASE-C NGGUON*/
+ RTUNI_ALPHA, /* U+0168b6: BAMUM LETTER PHASE-C LAP*/
+ RTUNI_ALPHA, /* U+0168b7: BAMUM LETTER PHASE-C MBIRIEEN*/
+ RTUNI_ALPHA, /* U+0168b8: BAMUM LETTER PHASE-C MGBASAQ*/
+ RTUNI_ALPHA, /* U+0168b9: BAMUM LETTER PHASE-C NTEUNGBA*/
+ RTUNI_ALPHA, /* U+0168ba: BAMUM LETTER PHASE-C TEUTEUX*/
+ RTUNI_ALPHA, /* U+0168bb: BAMUM LETTER PHASE-C NGGUM*/
+ RTUNI_ALPHA, /* U+0168bc: BAMUM LETTER PHASE-C FUE*/
+ RTUNI_ALPHA, /* U+0168bd: BAMUM LETTER PHASE-C NDEUT*/
+ RTUNI_ALPHA, /* U+0168be: BAMUM LETTER PHASE-C NSA*/
+ RTUNI_ALPHA, /* U+0168bf: BAMUM LETTER PHASE-C NSHAQ*/
+ RTUNI_ALPHA, /* U+0168c0: BAMUM LETTER PHASE-C BUNG*/
+ RTUNI_ALPHA, /* U+0168c1: BAMUM LETTER PHASE-C VEUAEPEN*/
+ RTUNI_ALPHA, /* U+0168c2: BAMUM LETTER PHASE-C MBERAE*/
+ RTUNI_ALPHA, /* U+0168c3: BAMUM LETTER PHASE-C RU*/
+ RTUNI_ALPHA, /* U+0168c4: BAMUM LETTER PHASE-C NJAEM*/
+ RTUNI_ALPHA, /* U+0168c5: BAMUM LETTER PHASE-C LAM*/
+ RTUNI_ALPHA, /* U+0168c6: BAMUM LETTER PHASE-C TITUAEP*/
+ RTUNI_ALPHA, /* U+0168c7: BAMUM LETTER PHASE-C NSUOT NGOM*/
+ RTUNI_ALPHA, /* U+0168c8: BAMUM LETTER PHASE-C NJEEEE*/
+ RTUNI_ALPHA, /* U+0168c9: BAMUM LETTER PHASE-C KET*/
+ RTUNI_ALPHA, /* U+0168ca: BAMUM LETTER PHASE-C NGGU*/
+ RTUNI_ALPHA, /* U+0168cb: BAMUM LETTER PHASE-C MAESI*/
+ RTUNI_ALPHA, /* U+0168cc: BAMUM LETTER PHASE-C MBUAEM*/
+ RTUNI_ALPHA, /* U+0168cd: BAMUM LETTER PHASE-C LU*/
+ RTUNI_ALPHA, /* U+0168ce: BAMUM LETTER PHASE-C KUT*/
+ RTUNI_ALPHA, /* U+0168cf: BAMUM LETTER PHASE-C NJAM*/
+ RTUNI_ALPHA, /* U+0168d0: BAMUM LETTER PHASE-C NGOM*/
+ RTUNI_ALPHA, /* U+0168d1: BAMUM LETTER PHASE-C WUP*/
+ RTUNI_ALPHA, /* U+0168d2: BAMUM LETTER PHASE-C NGGUEET*/
+ RTUNI_ALPHA, /* U+0168d3: BAMUM LETTER PHASE-C NSOM*/
+ RTUNI_ALPHA, /* U+0168d4: BAMUM LETTER PHASE-C NTEN*/
+ RTUNI_ALPHA, /* U+0168d5: BAMUM LETTER PHASE-C KUOP NKAARAE*/
+ RTUNI_ALPHA, /* U+0168d6: BAMUM LETTER PHASE-C NSUN*/
+ RTUNI_ALPHA, /* U+0168d7: BAMUM LETTER PHASE-C NDAM*/
+ RTUNI_ALPHA, /* U+0168d8: BAMUM LETTER PHASE-C MA NSIEE*/
+ RTUNI_ALPHA, /* U+0168d9: BAMUM LETTER PHASE-C YAA*/
+ RTUNI_ALPHA, /* U+0168da: BAMUM LETTER PHASE-C NDAP*/
+ RTUNI_ALPHA, /* U+0168db: BAMUM LETTER PHASE-C SHUEQ*/
+ RTUNI_ALPHA, /* U+0168dc: BAMUM LETTER PHASE-C SETFON*/
+ RTUNI_ALPHA, /* U+0168dd: BAMUM LETTER PHASE-C MBI*/
+ RTUNI_ALPHA, /* U+0168de: BAMUM LETTER PHASE-C MAEMBA*/
+ RTUNI_ALPHA, /* U+0168df: BAMUM LETTER PHASE-C MBANYI*/
+ RTUNI_ALPHA, /* U+0168e0: BAMUM LETTER PHASE-C KEUSEUX*/
+ RTUNI_ALPHA, /* U+0168e1: BAMUM LETTER PHASE-C MBEUX*/
+ RTUNI_ALPHA, /* U+0168e2: BAMUM LETTER PHASE-C KEUM*/
+ RTUNI_ALPHA, /* U+0168e3: BAMUM LETTER PHASE-C MBAA PICKET*/
+ RTUNI_ALPHA, /* U+0168e4: BAMUM LETTER PHASE-C YUWOQ*/
+ RTUNI_ALPHA, /* U+0168e5: BAMUM LETTER PHASE-C NJEUX*/
+ RTUNI_ALPHA, /* U+0168e6: BAMUM LETTER PHASE-C MIEE*/
+ RTUNI_ALPHA, /* U+0168e7: BAMUM LETTER PHASE-C MUAE*/
+ RTUNI_ALPHA, /* U+0168e8: BAMUM LETTER PHASE-C SHIQ*/
+ RTUNI_ALPHA, /* U+0168e9: BAMUM LETTER PHASE-C KEN LAW*/
+ RTUNI_ALPHA, /* U+0168ea: BAMUM LETTER PHASE-C KEN FATIGUE*/
+ RTUNI_ALPHA, /* U+0168eb: BAMUM LETTER PHASE-C NGAQ*/
+ RTUNI_ALPHA, /* U+0168ec: BAMUM LETTER PHASE-C NAQ*/
+ RTUNI_ALPHA, /* U+0168ed: BAMUM LETTER PHASE-C LIQ*/
+ RTUNI_ALPHA, /* U+0168ee: BAMUM LETTER PHASE-C PIN*/
+ RTUNI_ALPHA, /* U+0168ef: BAMUM LETTER PHASE-C PEN*/
+ RTUNI_ALPHA, /* U+0168f0: BAMUM LETTER PHASE-C TET*/
+ RTUNI_ALPHA, /* U+0168f1: BAMUM LETTER PHASE-D MBUO*/
+ RTUNI_ALPHA, /* U+0168f2: BAMUM LETTER PHASE-D WAP*/
+ RTUNI_ALPHA, /* U+0168f3: BAMUM LETTER PHASE-D NJI*/
+ RTUNI_ALPHA, /* U+0168f4: BAMUM LETTER PHASE-D MFON*/
+ RTUNI_ALPHA, /* U+0168f5: BAMUM LETTER PHASE-D NJIEE*/
+ RTUNI_ALPHA, /* U+0168f6: BAMUM LETTER PHASE-D LIEE*/
+ RTUNI_ALPHA, /* U+0168f7: BAMUM LETTER PHASE-D NJEUT*/
+ RTUNI_ALPHA, /* U+0168f8: BAMUM LETTER PHASE-D NSHEE*/
+ RTUNI_ALPHA, /* U+0168f9: BAMUM LETTER PHASE-D NGGAAMAE*/
+ RTUNI_ALPHA, /* U+0168fa: BAMUM LETTER PHASE-D NYAM*/
+ RTUNI_ALPHA, /* U+0168fb: BAMUM LETTER PHASE-D WUAEN*/
+ RTUNI_ALPHA, /* U+0168fc: BAMUM LETTER PHASE-D NGKUN*/
+ RTUNI_ALPHA, /* U+0168fd: BAMUM LETTER PHASE-D SHEE*/
+ RTUNI_ALPHA, /* U+0168fe: BAMUM LETTER PHASE-D NGKAP*/
+ RTUNI_ALPHA, /* U+0168ff: BAMUM LETTER PHASE-D KEUAETMEUN*/
+ RTUNI_ALPHA, /* U+016900: BAMUM LETTER PHASE-D TEUT*/
+ RTUNI_ALPHA, /* U+016901: BAMUM LETTER PHASE-D SHEUAE*/
+ RTUNI_ALPHA, /* U+016902: BAMUM LETTER PHASE-D NJAP*/
+ RTUNI_ALPHA, /* U+016903: BAMUM LETTER PHASE-D SUE*/
+ RTUNI_ALPHA, /* U+016904: BAMUM LETTER PHASE-D KET*/
+ RTUNI_ALPHA, /* U+016905: BAMUM LETTER PHASE-D YAEMMAE*/
+ RTUNI_ALPHA, /* U+016906: BAMUM LETTER PHASE-D KUOM*/
+ RTUNI_ALPHA, /* U+016907: BAMUM LETTER PHASE-D SAP*/
+ RTUNI_ALPHA, /* U+016908: BAMUM LETTER PHASE-D MFEUT*/
+ RTUNI_ALPHA, /* U+016909: BAMUM LETTER PHASE-D NDEUX*/
+ RTUNI_ALPHA, /* U+01690a: BAMUM LETTER PHASE-D MALEERI*/
+ RTUNI_ALPHA, /* U+01690b: BAMUM LETTER PHASE-D MEUT*/
+ RTUNI_ALPHA, /* U+01690c: BAMUM LETTER PHASE-D SEUAEQ*/
+ RTUNI_ALPHA, /* U+01690d: BAMUM LETTER PHASE-D YEN*/
+ RTUNI_ALPHA, /* U+01690e: BAMUM LETTER PHASE-D NJEUAEM*/
+ RTUNI_ALPHA, /* U+01690f: BAMUM LETTER PHASE-D KEUOT MBUAE*/
+ RTUNI_ALPHA, /* U+016910: BAMUM LETTER PHASE-D NGKEURI*/
+ RTUNI_ALPHA, /* U+016911: BAMUM LETTER PHASE-D TU*/
+ RTUNI_ALPHA, /* U+016912: BAMUM LETTER PHASE-D GHAA*/
+ RTUNI_ALPHA, /* U+016913: BAMUM LETTER PHASE-D NGKYEE*/
+ RTUNI_ALPHA, /* U+016914: BAMUM LETTER PHASE-D FEUFEUAET*/
+ RTUNI_ALPHA, /* U+016915: BAMUM LETTER PHASE-D NDEE*/
+ RTUNI_ALPHA, /* U+016916: BAMUM LETTER PHASE-D MGBOFUM*/
+ RTUNI_ALPHA, /* U+016917: BAMUM LETTER PHASE-D LEUAEP*/
+ RTUNI_ALPHA, /* U+016918: BAMUM LETTER PHASE-D NDON*/
+ RTUNI_ALPHA, /* U+016919: BAMUM LETTER PHASE-D MONI*/
+ RTUNI_ALPHA, /* U+01691a: BAMUM LETTER PHASE-D MGBEUN*/
+ RTUNI_ALPHA, /* U+01691b: BAMUM LETTER PHASE-D PUUT*/
+ RTUNI_ALPHA, /* U+01691c: BAMUM LETTER PHASE-D MGBIEE*/
+ RTUNI_ALPHA, /* U+01691d: BAMUM LETTER PHASE-D MFO*/
+ RTUNI_ALPHA, /* U+01691e: BAMUM LETTER PHASE-D LUM*/
+ RTUNI_ALPHA, /* U+01691f: BAMUM LETTER PHASE-D NSIEEP*/
+ RTUNI_ALPHA, /* U+016920: BAMUM LETTER PHASE-D MBAA*/
+ RTUNI_ALPHA, /* U+016921: BAMUM LETTER PHASE-D KWAET*/
+ RTUNI_ALPHA, /* U+016922: BAMUM LETTER PHASE-D NYET*/
+ RTUNI_ALPHA, /* U+016923: BAMUM LETTER PHASE-D TEUAEN*/
+ RTUNI_ALPHA, /* U+016924: BAMUM LETTER PHASE-D SOT*/
+ RTUNI_ALPHA, /* U+016925: BAMUM LETTER PHASE-D YUWOQ*/
+ RTUNI_ALPHA, /* U+016926: BAMUM LETTER PHASE-D KEUM*/
+ RTUNI_ALPHA, /* U+016927: BAMUM LETTER PHASE-D RAEM*/
+ RTUNI_ALPHA, /* U+016928: BAMUM LETTER PHASE-D TEEEE*/
+ RTUNI_ALPHA, /* U+016929: BAMUM LETTER PHASE-D NGKEUAEQ*/
+ RTUNI_ALPHA, /* U+01692a: BAMUM LETTER PHASE-D MFEUAE*/
+ RTUNI_ALPHA, /* U+01692b: BAMUM LETTER PHASE-D NSIEET*/
+ RTUNI_ALPHA, /* U+01692c: BAMUM LETTER PHASE-D KEUP*/
+ RTUNI_ALPHA, /* U+01692d: BAMUM LETTER PHASE-D PIP*/
+ RTUNI_ALPHA, /* U+01692e: BAMUM LETTER PHASE-D PEUTAE*/
+ RTUNI_ALPHA, /* U+01692f: BAMUM LETTER PHASE-D NYUE*/
+ RTUNI_ALPHA, /* U+016930: BAMUM LETTER PHASE-D LET*/
+ RTUNI_ALPHA, /* U+016931: BAMUM LETTER PHASE-D NGGAAM*/
+ RTUNI_ALPHA, /* U+016932: BAMUM LETTER PHASE-D MFIEE*/
+ RTUNI_ALPHA, /* U+016933: BAMUM LETTER PHASE-D NGGWAEN*/
+ RTUNI_ALPHA, /* U+016934: BAMUM LETTER PHASE-D YUOM*/
+ RTUNI_ALPHA, /* U+016935: BAMUM LETTER PHASE-D PAP*/
+ RTUNI_ALPHA, /* U+016936: BAMUM LETTER PHASE-D YUOP*/
+ RTUNI_ALPHA, /* U+016937: BAMUM LETTER PHASE-D NDAM*/
+ RTUNI_ALPHA, /* U+016938: BAMUM LETTER PHASE-D NTEUM*/
+ RTUNI_ALPHA, /* U+016939: BAMUM LETTER PHASE-D SUAE*/
+ RTUNI_ALPHA, /* U+01693a: BAMUM LETTER PHASE-D KUN*/
+ RTUNI_ALPHA, /* U+01693b: BAMUM LETTER PHASE-D NGGEUX*/
+ RTUNI_ALPHA, /* U+01693c: BAMUM LETTER PHASE-D NGKIEE*/
+ RTUNI_ALPHA, /* U+01693d: BAMUM LETTER PHASE-D TUOT*/
+ RTUNI_ALPHA, /* U+01693e: BAMUM LETTER PHASE-D MEUN*/
+ RTUNI_ALPHA, /* U+01693f: BAMUM LETTER PHASE-D KUQ*/
+ RTUNI_ALPHA, /* U+016940: BAMUM LETTER PHASE-D NSUM*/
+ RTUNI_ALPHA, /* U+016941: BAMUM LETTER PHASE-D TEUN*/
+ RTUNI_ALPHA, /* U+016942: BAMUM LETTER PHASE-D MAENJET*/
+ RTUNI_ALPHA, /* U+016943: BAMUM LETTER PHASE-D NGGAP*/
+ RTUNI_ALPHA, /* U+016944: BAMUM LETTER PHASE-D LEUM*/
+ RTUNI_ALPHA, /* U+016945: BAMUM LETTER PHASE-D NGGUOM*/
+ RTUNI_ALPHA, /* U+016946: BAMUM LETTER PHASE-D NSHUT*/
+ RTUNI_ALPHA, /* U+016947: BAMUM LETTER PHASE-D NJUEQ*/
+ RTUNI_ALPHA, /* U+016948: BAMUM LETTER PHASE-D GHEUAE*/
+ RTUNI_ALPHA, /* U+016949: BAMUM LETTER PHASE-D KU*/
+ RTUNI_ALPHA, /* U+01694a: BAMUM LETTER PHASE-D REN OLD*/
+ RTUNI_ALPHA, /* U+01694b: BAMUM LETTER PHASE-D TAE*/
+ RTUNI_ALPHA, /* U+01694c: BAMUM LETTER PHASE-D TOQ*/
+ RTUNI_ALPHA, /* U+01694d: BAMUM LETTER PHASE-D NYI*/
+ RTUNI_ALPHA, /* U+01694e: BAMUM LETTER PHASE-D RII*/
+ RTUNI_ALPHA, /* U+01694f: BAMUM LETTER PHASE-D LEEEE*/
+ RTUNI_ALPHA, /* U+016950: BAMUM LETTER PHASE-D MEEEE*/
+ RTUNI_ALPHA, /* U+016951: BAMUM LETTER PHASE-D M*/
+ RTUNI_ALPHA, /* U+016952: BAMUM LETTER PHASE-D SUU*/
+ RTUNI_ALPHA, /* U+016953: BAMUM LETTER PHASE-D MU*/
+ RTUNI_ALPHA, /* U+016954: BAMUM LETTER PHASE-D SHII*/
+ RTUNI_ALPHA, /* U+016955: BAMUM LETTER PHASE-D SHEUX*/
+ RTUNI_ALPHA, /* U+016956: BAMUM LETTER PHASE-D KYEE*/
+ RTUNI_ALPHA, /* U+016957: BAMUM LETTER PHASE-D NU*/
+ RTUNI_ALPHA, /* U+016958: BAMUM LETTER PHASE-D SHU*/
+ RTUNI_ALPHA, /* U+016959: BAMUM LETTER PHASE-D NTEE*/
+ RTUNI_ALPHA, /* U+01695a: BAMUM LETTER PHASE-D PEE*/
+ RTUNI_ALPHA, /* U+01695b: BAMUM LETTER PHASE-D NI*/
+ RTUNI_ALPHA, /* U+01695c: BAMUM LETTER PHASE-D SHOQ*/
+ RTUNI_ALPHA, /* U+01695d: BAMUM LETTER PHASE-D PUQ*/
+ RTUNI_ALPHA, /* U+01695e: BAMUM LETTER PHASE-D MVOP*/
+ RTUNI_ALPHA, /* U+01695f: BAMUM LETTER PHASE-D LOQ*/
+ RTUNI_ALPHA, /* U+016960: BAMUM LETTER PHASE-D REN MUCH*/
+ RTUNI_ALPHA, /* U+016961: BAMUM LETTER PHASE-D TI*/
+ RTUNI_ALPHA, /* U+016962: BAMUM LETTER PHASE-D NTUU*/
+ RTUNI_ALPHA, /* U+016963: BAMUM LETTER PHASE-D MBAA SEVEN*/
+ RTUNI_ALPHA, /* U+016964: BAMUM LETTER PHASE-D SAQ*/
+ RTUNI_ALPHA, /* U+016965: BAMUM LETTER PHASE-D FAA*/
+ RTUNI_ALPHA, /* U+016966: BAMUM LETTER PHASE-E NDAP*/
+ RTUNI_ALPHA, /* U+016967: BAMUM LETTER PHASE-E TOON*/
+ RTUNI_ALPHA, /* U+016968: BAMUM LETTER PHASE-E MBEUM*/
+ RTUNI_ALPHA, /* U+016969: BAMUM LETTER PHASE-E LAP*/
+ RTUNI_ALPHA, /* U+01696a: BAMUM LETTER PHASE-E VOM*/
+ RTUNI_ALPHA, /* U+01696b: BAMUM LETTER PHASE-E LOON*/
+ RTUNI_ALPHA, /* U+01696c: BAMUM LETTER PHASE-E PAA*/
+ RTUNI_ALPHA, /* U+01696d: BAMUM LETTER PHASE-E SOM*/
+ RTUNI_ALPHA, /* U+01696e: BAMUM LETTER PHASE-E RAQ*/
+ RTUNI_ALPHA, /* U+01696f: BAMUM LETTER PHASE-E NSHUOP*/
+ RTUNI_ALPHA, /* U+016970: BAMUM LETTER PHASE-E NDUN*/
+ RTUNI_ALPHA, /* U+016971: BAMUM LETTER PHASE-E PUAE*/
+ RTUNI_ALPHA, /* U+016972: BAMUM LETTER PHASE-E TAM*/
+ RTUNI_ALPHA, /* U+016973: BAMUM LETTER PHASE-E NGKA*/
+ RTUNI_ALPHA, /* U+016974: BAMUM LETTER PHASE-E KPEUX*/
+ RTUNI_ALPHA, /* U+016975: BAMUM LETTER PHASE-E WUO*/
+ RTUNI_ALPHA, /* U+016976: BAMUM LETTER PHASE-E SEE*/
+ RTUNI_ALPHA, /* U+016977: BAMUM LETTER PHASE-E NGGEUAET*/
+ RTUNI_ALPHA, /* U+016978: BAMUM LETTER PHASE-E PAAM*/
+ RTUNI_ALPHA, /* U+016979: BAMUM LETTER PHASE-E TOO*/
+ RTUNI_ALPHA, /* U+01697a: BAMUM LETTER PHASE-E KUOP*/
+ RTUNI_ALPHA, /* U+01697b: BAMUM LETTER PHASE-E LOM*/
+ RTUNI_ALPHA, /* U+01697c: BAMUM LETTER PHASE-E NSHIEE*/
+ RTUNI_ALPHA, /* U+01697d: BAMUM LETTER PHASE-E NGOP*/
+ RTUNI_ALPHA, /* U+01697e: BAMUM LETTER PHASE-E MAEM*/
+ RTUNI_ALPHA, /* U+01697f: BAMUM LETTER PHASE-E NGKEUX*/
+ RTUNI_ALPHA, /* U+016980: BAMUM LETTER PHASE-E NGOQ*/
+ RTUNI_ALPHA, /* U+016981: BAMUM LETTER PHASE-E NSHUE*/
+ RTUNI_ALPHA, /* U+016982: BAMUM LETTER PHASE-E RIMGBA*/
+ RTUNI_ALPHA, /* U+016983: BAMUM LETTER PHASE-E NJEUX*/
+ RTUNI_ALPHA, /* U+016984: BAMUM LETTER PHASE-E PEEM*/
+ RTUNI_ALPHA, /* U+016985: BAMUM LETTER PHASE-E SAA*/
+ RTUNI_ALPHA, /* U+016986: BAMUM LETTER PHASE-E NGGURAE*/
+ RTUNI_ALPHA, /* U+016987: BAMUM LETTER PHASE-E MGBA*/
+ RTUNI_ALPHA, /* U+016988: BAMUM LETTER PHASE-E GHEUX*/
+ RTUNI_ALPHA, /* U+016989: BAMUM LETTER PHASE-E NGKEUAEM*/
+ RTUNI_ALPHA, /* U+01698a: BAMUM LETTER PHASE-E NJAEMLI*/
+ RTUNI_ALPHA, /* U+01698b: BAMUM LETTER PHASE-E MAP*/
+ RTUNI_ALPHA, /* U+01698c: BAMUM LETTER PHASE-E LOOT*/
+ RTUNI_ALPHA, /* U+01698d: BAMUM LETTER PHASE-E NGGEEEE*/
+ RTUNI_ALPHA, /* U+01698e: BAMUM LETTER PHASE-E NDIQ*/
+ RTUNI_ALPHA, /* U+01698f: BAMUM LETTER PHASE-E TAEN NTEUM*/
+ RTUNI_ALPHA, /* U+016990: BAMUM LETTER PHASE-E SET*/
+ RTUNI_ALPHA, /* U+016991: BAMUM LETTER PHASE-E PUM*/
+ RTUNI_ALPHA, /* U+016992: BAMUM LETTER PHASE-E NDAA SOFTNESS*/
+ RTUNI_ALPHA, /* U+016993: BAMUM LETTER PHASE-E NGGUAESHAE NYAM*/
+ RTUNI_ALPHA, /* U+016994: BAMUM LETTER PHASE-E YIEE*/
+ RTUNI_ALPHA, /* U+016995: BAMUM LETTER PHASE-E GHEUN*/
+ RTUNI_ALPHA, /* U+016996: BAMUM LETTER PHASE-E TUAE*/
+ RTUNI_ALPHA, /* U+016997: BAMUM LETTER PHASE-E YEUAE*/
+ RTUNI_ALPHA, /* U+016998: BAMUM LETTER PHASE-E PO*/
+ RTUNI_ALPHA, /* U+016999: BAMUM LETTER PHASE-E TUMAE*/
+ RTUNI_ALPHA, /* U+01699a: BAMUM LETTER PHASE-E KEUAE*/
+ RTUNI_ALPHA, /* U+01699b: BAMUM LETTER PHASE-E SUAEN*/
+ RTUNI_ALPHA, /* U+01699c: BAMUM LETTER PHASE-E TEUAEQ*/
+ RTUNI_ALPHA, /* U+01699d: BAMUM LETTER PHASE-E VEUAE*/
+ RTUNI_ALPHA, /* U+01699e: BAMUM LETTER PHASE-E WEUX*/
+ RTUNI_ALPHA, /* U+01699f: BAMUM LETTER PHASE-E LAAM*/
+ RTUNI_ALPHA, /* U+0169a0: BAMUM LETTER PHASE-E PU*/
+ RTUNI_ALPHA, /* U+0169a1: BAMUM LETTER PHASE-E TAAQ*/
+ RTUNI_ALPHA, /* U+0169a2: BAMUM LETTER PHASE-E GHAAMAE*/
+ RTUNI_ALPHA, /* U+0169a3: BAMUM LETTER PHASE-E NGEUREUT*/
+ RTUNI_ALPHA, /* U+0169a4: BAMUM LETTER PHASE-E SHEUAEQ*/
+ RTUNI_ALPHA, /* U+0169a5: BAMUM LETTER PHASE-E MGBEN*/
+ RTUNI_ALPHA, /* U+0169a6: BAMUM LETTER PHASE-E MBEE*/
+ RTUNI_ALPHA, /* U+0169a7: BAMUM LETTER PHASE-E NZAQ*/
+ RTUNI_ALPHA, /* U+0169a8: BAMUM LETTER PHASE-E NKOM*/
+ RTUNI_ALPHA, /* U+0169a9: BAMUM LETTER PHASE-E GBET*/
+ RTUNI_ALPHA, /* U+0169aa: BAMUM LETTER PHASE-E TUM*/
+ RTUNI_ALPHA, /* U+0169ab: BAMUM LETTER PHASE-E KUET*/
+ RTUNI_ALPHA, /* U+0169ac: BAMUM LETTER PHASE-E YAP*/
+ RTUNI_ALPHA, /* U+0169ad: BAMUM LETTER PHASE-E NYI CLEAVER*/
+ RTUNI_ALPHA, /* U+0169ae: BAMUM LETTER PHASE-E YIT*/
+ RTUNI_ALPHA, /* U+0169af: BAMUM LETTER PHASE-E MFEUQ*/
+ RTUNI_ALPHA, /* U+0169b0: BAMUM LETTER PHASE-E NDIAQ*/
+ RTUNI_ALPHA, /* U+0169b1: BAMUM LETTER PHASE-E PIEEQ*/
+ RTUNI_ALPHA, /* U+0169b2: BAMUM LETTER PHASE-E YUEQ*/
+ RTUNI_ALPHA, /* U+0169b3: BAMUM LETTER PHASE-E LEUAEM*/
+ RTUNI_ALPHA, /* U+0169b4: BAMUM LETTER PHASE-E FUE*/
+ RTUNI_ALPHA, /* U+0169b5: BAMUM LETTER PHASE-E GBEUX*/
+ RTUNI_ALPHA, /* U+0169b6: BAMUM LETTER PHASE-E NGKUP*/
+ RTUNI_ALPHA, /* U+0169b7: BAMUM LETTER PHASE-E KET*/
+ RTUNI_ALPHA, /* U+0169b8: BAMUM LETTER PHASE-E MAE*/
+ RTUNI_ALPHA, /* U+0169b9: BAMUM LETTER PHASE-E NGKAAMI*/
+ RTUNI_ALPHA, /* U+0169ba: BAMUM LETTER PHASE-E GHET*/
+ RTUNI_ALPHA, /* U+0169bb: BAMUM LETTER PHASE-E FA*/
+ RTUNI_ALPHA, /* U+0169bc: BAMUM LETTER PHASE-E NTUM*/
+ RTUNI_ALPHA, /* U+0169bd: BAMUM LETTER PHASE-E PEUT*/
+ RTUNI_ALPHA, /* U+0169be: BAMUM LETTER PHASE-E YEUM*/
+ RTUNI_ALPHA, /* U+0169bf: BAMUM LETTER PHASE-E NGGEUAE*/
+ RTUNI_ALPHA, /* U+0169c0: BAMUM LETTER PHASE-E NYI BETWEEN*/
+ RTUNI_ALPHA, /* U+0169c1: BAMUM LETTER PHASE-E NZUQ*/
+ RTUNI_ALPHA, /* U+0169c2: BAMUM LETTER PHASE-E POON*/
+ RTUNI_ALPHA, /* U+0169c3: BAMUM LETTER PHASE-E MIEE*/
+ RTUNI_ALPHA, /* U+0169c4: BAMUM LETTER PHASE-E FUET*/
+ RTUNI_ALPHA, /* U+0169c5: BAMUM LETTER PHASE-E NAE*/
+ RTUNI_ALPHA, /* U+0169c6: BAMUM LETTER PHASE-E MUAE*/
+ RTUNI_ALPHA, /* U+0169c7: BAMUM LETTER PHASE-E GHEUAE*/
+ RTUNI_ALPHA, /* U+0169c8: BAMUM LETTER PHASE-E FU I*/
+ RTUNI_ALPHA, /* U+0169c9: BAMUM LETTER PHASE-E MVI*/
+ RTUNI_ALPHA, /* U+0169ca: BAMUM LETTER PHASE-E PUAQ*/
+ RTUNI_ALPHA, /* U+0169cb: BAMUM LETTER PHASE-E NGKUM*/
+ RTUNI_ALPHA, /* U+0169cc: BAMUM LETTER PHASE-E KUT*/
+ RTUNI_ALPHA, /* U+0169cd: BAMUM LETTER PHASE-E PIET*/
+ RTUNI_ALPHA, /* U+0169ce: BAMUM LETTER PHASE-E NTAP*/
+ RTUNI_ALPHA, /* U+0169cf: BAMUM LETTER PHASE-E YEUAET*/
+ RTUNI_ALPHA, /* U+0169d0: BAMUM LETTER PHASE-E NGGUP*/
+ RTUNI_ALPHA, /* U+0169d1: BAMUM LETTER PHASE-E PA PEOPLE*/
+ RTUNI_ALPHA, /* U+0169d2: BAMUM LETTER PHASE-E FU CALL*/
+ RTUNI_ALPHA, /* U+0169d3: BAMUM LETTER PHASE-E FOM*/
+ RTUNI_ALPHA, /* U+0169d4: BAMUM LETTER PHASE-E NJEE*/
+ RTUNI_ALPHA, /* U+0169d5: BAMUM LETTER PHASE-E A*/
+ RTUNI_ALPHA, /* U+0169d6: BAMUM LETTER PHASE-E TOQ*/
+ RTUNI_ALPHA, /* U+0169d7: BAMUM LETTER PHASE-E O*/
+ RTUNI_ALPHA, /* U+0169d8: BAMUM LETTER PHASE-E I*/
+ RTUNI_ALPHA, /* U+0169d9: BAMUM LETTER PHASE-E LAQ*/
+ RTUNI_ALPHA, /* U+0169da: BAMUM LETTER PHASE-E PA PLURAL*/
+ RTUNI_ALPHA, /* U+0169db: BAMUM LETTER PHASE-E TAA*/
+ RTUNI_ALPHA, /* U+0169dc: BAMUM LETTER PHASE-E TAQ*/
+ RTUNI_ALPHA, /* U+0169dd: BAMUM LETTER PHASE-E NDAA MY HOUSE*/
+ RTUNI_ALPHA, /* U+0169de: BAMUM LETTER PHASE-E SHIQ*/
+ RTUNI_ALPHA, /* U+0169df: BAMUM LETTER PHASE-E YEUX*/
+ RTUNI_ALPHA, /* U+0169e0: BAMUM LETTER PHASE-E NGUAE*/
+ RTUNI_ALPHA, /* U+0169e1: BAMUM LETTER PHASE-E YUAEN*/
+ RTUNI_ALPHA, /* U+0169e2: BAMUM LETTER PHASE-E YOQ SWIMMING*/
+ RTUNI_ALPHA, /* U+0169e3: BAMUM LETTER PHASE-E YOQ COVER*/
+ RTUNI_ALPHA, /* U+0169e4: BAMUM LETTER PHASE-E YUQ*/
+ RTUNI_ALPHA, /* U+0169e5: BAMUM LETTER PHASE-E YUN*/
+ RTUNI_ALPHA, /* U+0169e6: BAMUM LETTER PHASE-E KEUX*/
+ RTUNI_ALPHA, /* U+0169e7: BAMUM LETTER PHASE-E PEUX*/
+ RTUNI_ALPHA, /* U+0169e8: BAMUM LETTER PHASE-E NJEE EPOCH*/
+ RTUNI_ALPHA, /* U+0169e9: BAMUM LETTER PHASE-E PUE*/
+ RTUNI_ALPHA, /* U+0169ea: BAMUM LETTER PHASE-E WUE*/
+ RTUNI_ALPHA, /* U+0169eb: BAMUM LETTER PHASE-E FEE*/
+ RTUNI_ALPHA, /* U+0169ec: BAMUM LETTER PHASE-E VEE*/
+ RTUNI_ALPHA, /* U+0169ed: BAMUM LETTER PHASE-E LU*/
+ RTUNI_ALPHA, /* U+0169ee: BAMUM LETTER PHASE-E MI*/
+ RTUNI_ALPHA, /* U+0169ef: BAMUM LETTER PHASE-E REUX*/
+ RTUNI_ALPHA, /* U+0169f0: BAMUM LETTER PHASE-E RAE*/
+ RTUNI_ALPHA, /* U+0169f1: BAMUM LETTER PHASE-E NGUAET*/
+ RTUNI_ALPHA, /* U+0169f2: BAMUM LETTER PHASE-E NGA*/
+ RTUNI_ALPHA, /* U+0169f3: BAMUM LETTER PHASE-E SHO*/
+ RTUNI_ALPHA, /* U+0169f4: BAMUM LETTER PHASE-E SHOQ*/
+ RTUNI_ALPHA, /* U+0169f5: BAMUM LETTER PHASE-E FU REMEDY*/
+ RTUNI_ALPHA, /* U+0169f6: BAMUM LETTER PHASE-E NA*/
+ RTUNI_ALPHA, /* U+0169f7: BAMUM LETTER PHASE-E PI*/
+ RTUNI_ALPHA, /* U+0169f8: BAMUM LETTER PHASE-E LOQ*/
+ RTUNI_ALPHA, /* U+0169f9: BAMUM LETTER PHASE-E KO*/
+ RTUNI_ALPHA, /* U+0169fa: BAMUM LETTER PHASE-E MEN*/
+ RTUNI_ALPHA, /* U+0169fb: BAMUM LETTER PHASE-E MA*/
+ RTUNI_ALPHA, /* U+0169fc: BAMUM LETTER PHASE-E MAQ*/
+ RTUNI_ALPHA, /* U+0169fd: BAMUM LETTER PHASE-E TEU*/
+ RTUNI_ALPHA, /* U+0169fe: BAMUM LETTER PHASE-E KI*/
+ RTUNI_ALPHA, /* U+0169ff: BAMUM LETTER PHASE-E MON*/
+ RTUNI_ALPHA, /* U+016a00: BAMUM LETTER PHASE-E TEN*/
+ RTUNI_ALPHA, /* U+016a01: BAMUM LETTER PHASE-E FAQ*/
+ RTUNI_ALPHA, /* U+016a02: BAMUM LETTER PHASE-E GHOM*/
+ RTUNI_ALPHA, /* U+016a03: BAMUM LETTER PHASE-F KA*/
+ RTUNI_ALPHA, /* U+016a04: BAMUM LETTER PHASE-F U*/
+ RTUNI_ALPHA, /* U+016a05: BAMUM LETTER PHASE-F KU*/
+ RTUNI_ALPHA, /* U+016a06: BAMUM LETTER PHASE-F EE*/
+ RTUNI_ALPHA, /* U+016a07: BAMUM LETTER PHASE-F REE*/
+ RTUNI_ALPHA, /* U+016a08: BAMUM LETTER PHASE-F TAE*/
+ RTUNI_ALPHA, /* U+016a09: BAMUM LETTER PHASE-F NYI*/
+ RTUNI_ALPHA, /* U+016a0a: BAMUM LETTER PHASE-F LA*/
+ RTUNI_ALPHA, /* U+016a0b: BAMUM LETTER PHASE-F RII*/
+ RTUNI_ALPHA, /* U+016a0c: BAMUM LETTER PHASE-F RIEE*/
+ RTUNI_ALPHA, /* U+016a0d: BAMUM LETTER PHASE-F MEEEE*/
+ RTUNI_ALPHA, /* U+016a0e: BAMUM LETTER PHASE-F TAA*/
+ RTUNI_ALPHA, /* U+016a0f: BAMUM LETTER PHASE-F NDAA*/
+ RTUNI_ALPHA, /* U+016a10: BAMUM LETTER PHASE-F NJAEM*/
+ RTUNI_ALPHA, /* U+016a11: BAMUM LETTER PHASE-F M*/
+ RTUNI_ALPHA, /* U+016a12: BAMUM LETTER PHASE-F SUU*/
+ RTUNI_ALPHA, /* U+016a13: BAMUM LETTER PHASE-F SHII*/
+ RTUNI_ALPHA, /* U+016a14: BAMUM LETTER PHASE-F SI*/
+ RTUNI_ALPHA, /* U+016a15: BAMUM LETTER PHASE-F SEUX*/
+ RTUNI_ALPHA, /* U+016a16: BAMUM LETTER PHASE-F KYEE*/
+ RTUNI_ALPHA, /* U+016a17: BAMUM LETTER PHASE-F KET*/
+ RTUNI_ALPHA, /* U+016a18: BAMUM LETTER PHASE-F NUAE*/
+ RTUNI_ALPHA, /* U+016a19: BAMUM LETTER PHASE-F NU*/
+ RTUNI_ALPHA, /* U+016a1a: BAMUM LETTER PHASE-F NJUAE*/
+ RTUNI_ALPHA, /* U+016a1b: BAMUM LETTER PHASE-F YOQ*/
+ RTUNI_ALPHA, /* U+016a1c: BAMUM LETTER PHASE-F SHU*/
+ RTUNI_ALPHA, /* U+016a1d: BAMUM LETTER PHASE-F YA*/
+ RTUNI_ALPHA, /* U+016a1e: BAMUM LETTER PHASE-F NSHA*/
+ RTUNI_ALPHA, /* U+016a1f: BAMUM LETTER PHASE-F PEUX*/
+ RTUNI_ALPHA, /* U+016a20: BAMUM LETTER PHASE-F NTEE*/
+ RTUNI_ALPHA, /* U+016a21: BAMUM LETTER PHASE-F WUE*/
+ RTUNI_ALPHA, /* U+016a22: BAMUM LETTER PHASE-F PEE*/
+ RTUNI_ALPHA, /* U+016a23: BAMUM LETTER PHASE-F RU*/
+ RTUNI_ALPHA, /* U+016a24: BAMUM LETTER PHASE-F NI*/
+ RTUNI_ALPHA, /* U+016a25: BAMUM LETTER PHASE-F REUX*/
+ RTUNI_ALPHA, /* U+016a26: BAMUM LETTER PHASE-F KEN*/
+ RTUNI_ALPHA, /* U+016a27: BAMUM LETTER PHASE-F NGKWAEN*/
+ RTUNI_ALPHA, /* U+016a28: BAMUM LETTER PHASE-F NGGA*/
+ RTUNI_ALPHA, /* U+016a29: BAMUM LETTER PHASE-F SHO*/
+ RTUNI_ALPHA, /* U+016a2a: BAMUM LETTER PHASE-F PUAE*/
+ RTUNI_ALPHA, /* U+016a2b: BAMUM LETTER PHASE-F FOM*/
+ RTUNI_ALPHA, /* U+016a2c: BAMUM LETTER PHASE-F WA*/
+ RTUNI_ALPHA, /* U+016a2d: BAMUM LETTER PHASE-F LI*/
+ RTUNI_ALPHA, /* U+016a2e: BAMUM LETTER PHASE-F LOQ*/
+ RTUNI_ALPHA, /* U+016a2f: BAMUM LETTER PHASE-F KO*/
+ RTUNI_ALPHA, /* U+016a30: BAMUM LETTER PHASE-F MBEN*/
+ RTUNI_ALPHA, /* U+016a31: BAMUM LETTER PHASE-F REN*/
+ RTUNI_ALPHA, /* U+016a32: BAMUM LETTER PHASE-F MA*/
+ RTUNI_ALPHA, /* U+016a33: BAMUM LETTER PHASE-F MO*/
+ RTUNI_ALPHA, /* U+016a34: BAMUM LETTER PHASE-F MBAA*/
+ RTUNI_ALPHA, /* U+016a35: BAMUM LETTER PHASE-F TET*/
+ RTUNI_ALPHA, /* U+016a36: BAMUM LETTER PHASE-F KPA*/
+ RTUNI_ALPHA, /* U+016a37: BAMUM LETTER PHASE-F SAMBA*/
+ RTUNI_ALPHA, /* U+016a38: BAMUM LETTER PHASE-F VUEQ*/
+ 0, /* U+016a39: */
+ 0, /* U+016a3a: */
+ 0, /* U+016a3b: */
+ 0, /* U+016a3c: */
+ 0, /* U+016a3d: */
+ 0, /* U+016a3e: */
+ 0, /* U+016a3f: */
+ 0, /* U+016a40: */
+ 0, /* U+016a41: */
+ 0, /* U+016a42: */
+ 0, /* U+016a43: */
+ 0, /* U+016a44: */
+ 0, /* U+016a45: */
+ 0, /* U+016a46: */
+ 0, /* U+016a47: */
+ 0, /* U+016a48: */
+ 0, /* U+016a49: */
+ 0, /* U+016a4a: */
+ 0, /* U+016a4b: */
+ 0, /* U+016a4c: */
+ 0, /* U+016a4d: */
+ 0, /* U+016a4e: */
+ 0, /* U+016a4f: */
+ 0, /* U+016a50: */
+ 0, /* U+016a51: */
+ 0, /* U+016a52: */
+ 0, /* U+016a53: */
+ 0, /* U+016a54: */
+ 0, /* U+016a55: */
+ 0, /* U+016a56: */
+ 0, /* U+016a57: */
+ 0, /* U+016a58: */
+ 0, /* U+016a59: */
+ 0, /* U+016a5a: */
+ 0, /* U+016a5b: */
+ 0, /* U+016a5c: */
+ 0, /* U+016a5d: */
+ 0, /* U+016a5e: */
+ 0, /* U+016a5f: */
+ 0, /* U+016a60: */
+ 0, /* U+016a61: */
+ 0, /* U+016a62: */
+ 0, /* U+016a63: */
+ 0, /* U+016a64: */
+ 0, /* U+016a65: */
+ 0, /* U+016a66: */
+ 0, /* U+016a67: */
+ 0, /* U+016a68: */
+ 0, /* U+016a69: */
+ 0, /* U+016a6a: */
+ 0, /* U+016a6b: */
+ 0, /* U+016a6c: */
+ 0, /* U+016a6d: */
+ 0, /* U+016a6e: */
+ 0, /* U+016a6f: */
+ 0, /* U+016a70: */
+ 0, /* U+016a71: */
+ 0, /* U+016a72: */
+ 0, /* U+016a73: */
+ 0, /* U+016a74: */
+ 0, /* U+016a75: */
+ 0, /* U+016a76: */
+ 0, /* U+016a77: */
+ 0, /* U+016a78: */
+ 0, /* U+016a79: */
+ 0, /* U+016a7a: */
+ 0, /* U+016a7b: */
+ 0, /* U+016a7c: */
+ 0, /* U+016a7d: */
+ 0, /* U+016a7e: */
+ 0, /* U+016a7f: */
+ 0, /* U+016a80: */
+ 0, /* U+016a81: */
+ 0, /* U+016a82: */
+ 0, /* U+016a83: */
+ 0, /* U+016a84: */
+ 0, /* U+016a85: */
+ 0, /* U+016a86: */
+ 0, /* U+016a87: */
+ 0, /* U+016a88: */
+ 0, /* U+016a89: */
+ 0, /* U+016a8a: */
+ 0, /* U+016a8b: */
+ 0, /* U+016a8c: */
+ 0, /* U+016a8d: */
+ 0, /* U+016a8e: */
+ 0, /* U+016a8f: */
+ 0, /* U+016a90: */
+ 0, /* U+016a91: */
+ 0, /* U+016a92: */
+ 0, /* U+016a93: */
+ 0, /* U+016a94: */
+ 0, /* U+016a95: */
+ 0, /* U+016a96: */
+ 0, /* U+016a97: */
+ 0, /* U+016a98: */
+ 0, /* U+016a99: */
+ 0, /* U+016a9a: */
+ 0, /* U+016a9b: */
+ 0, /* U+016a9c: */
+ 0, /* U+016a9d: */
+ 0, /* U+016a9e: */
+ 0, /* U+016a9f: */
+ 0, /* U+016aa0: */
+ 0, /* U+016aa1: */
+ 0, /* U+016aa2: */
+ 0, /* U+016aa3: */
+ 0, /* U+016aa4: */
+ 0, /* U+016aa5: */
+ 0, /* U+016aa6: */
+ 0, /* U+016aa7: */
+ 0, /* U+016aa8: */
+ 0, /* U+016aa9: */
+ 0, /* U+016aaa: */
+ 0, /* U+016aab: */
+ 0, /* U+016aac: */
+ 0, /* U+016aad: */
+ 0, /* U+016aae: */
+ 0, /* U+016aaf: */
+ 0, /* U+016ab0: */
+ 0, /* U+016ab1: */
+ 0, /* U+016ab2: */
+ 0, /* U+016ab3: */
+ 0, /* U+016ab4: */
+ 0, /* U+016ab5: */
+ 0, /* U+016ab6: */
+ 0, /* U+016ab7: */
+ 0, /* U+016ab8: */
+ 0, /* U+016ab9: */
+ 0, /* U+016aba: */
+ 0, /* U+016abb: */
+ 0, /* U+016abc: */
+ 0, /* U+016abd: */
+ 0, /* U+016abe: */
+ 0, /* U+016abf: */
+ 0, /* U+016ac0: */
+ 0, /* U+016ac1: */
+ 0, /* U+016ac2: */
+ 0, /* U+016ac3: */
+ 0, /* U+016ac4: */
+ 0, /* U+016ac5: */
+ 0, /* U+016ac6: */
+ 0, /* U+016ac7: */
+ 0, /* U+016ac8: */
+ 0, /* U+016ac9: */
+ 0, /* U+016aca: */
+ 0, /* U+016acb: */
+ 0, /* U+016acc: */
+ 0, /* U+016acd: */
+ 0, /* U+016ace: */
+ 0, /* U+016acf: */
+ 0, /* U+016ad0: */
+ 0, /* U+016ad1: */
+ 0, /* U+016ad2: */
+ 0, /* U+016ad3: */
+ 0, /* U+016ad4: */
+ 0, /* U+016ad5: */
+ 0, /* U+016ad6: */
+ 0, /* U+016ad7: */
+ 0, /* U+016ad8: */
+ 0, /* U+016ad9: */
+ 0, /* U+016ada: */
+ 0, /* U+016adb: */
+ 0, /* U+016adc: */
+ 0, /* U+016add: */
+ 0, /* U+016ade: */
+ 0, /* U+016adf: */
+ 0, /* U+016ae0: */
+ 0, /* U+016ae1: */
+ 0, /* U+016ae2: */
+ 0, /* U+016ae3: */
+ 0, /* U+016ae4: */
+ 0, /* U+016ae5: */
+ 0, /* U+016ae6: */
+ 0, /* U+016ae7: */
+ 0, /* U+016ae8: */
+ 0, /* U+016ae9: */
+ 0, /* U+016aea: */
+ 0, /* U+016aeb: */
+ 0, /* U+016aec: */
+ 0, /* U+016aed: */
+ 0, /* U+016aee: */
+ 0, /* U+016aef: */
+ 0, /* U+016af0: */
+ 0, /* U+016af1: */
+ 0, /* U+016af2: */
+ 0, /* U+016af3: */
+ 0, /* U+016af4: */
+ 0, /* U+016af5: */
+ 0, /* U+016af6: */
+ 0, /* U+016af7: */
+ 0, /* U+016af8: */
+ 0, /* U+016af9: */
+ 0, /* U+016afa: */
+ 0, /* U+016afb: */
+ 0, /* U+016afc: */
+ 0, /* U+016afd: */
+ 0, /* U+016afe: */
+ 0, /* U+016aff: */
+ 0, /* U+016b00: */
+ 0, /* U+016b01: */
+ 0, /* U+016b02: */
+ 0, /* U+016b03: */
+ 0, /* U+016b04: */
+ 0, /* U+016b05: */
+ 0, /* U+016b06: */
+ 0, /* U+016b07: */
+ 0, /* U+016b08: */
+ 0, /* U+016b09: */
+ 0, /* U+016b0a: */
+ 0, /* U+016b0b: */
+ 0, /* U+016b0c: */
+ 0, /* U+016b0d: */
+ 0, /* U+016b0e: */
+ 0, /* U+016b0f: */
+ 0, /* U+016b10: */
+ 0, /* U+016b11: */
+ 0, /* U+016b12: */
+ 0, /* U+016b13: */
+ 0, /* U+016b14: */
+ 0, /* U+016b15: */
+ 0, /* U+016b16: */
+ 0, /* U+016b17: */
+ 0, /* U+016b18: */
+ 0, /* U+016b19: */
+ 0, /* U+016b1a: */
+ 0, /* U+016b1b: */
+ 0, /* U+016b1c: */
+ 0, /* U+016b1d: */
+ 0, /* U+016b1e: */
+ 0, /* U+016b1f: */
+ 0, /* U+016b20: */
+ 0, /* U+016b21: */
+ 0, /* U+016b22: */
+ 0, /* U+016b23: */
+ 0, /* U+016b24: */
+ 0, /* U+016b25: */
+ 0, /* U+016b26: */
+ 0, /* U+016b27: */
+ 0, /* U+016b28: */
+ 0, /* U+016b29: */
+ 0, /* U+016b2a: */
+ 0, /* U+016b2b: */
+ 0, /* U+016b2c: */
+ 0, /* U+016b2d: */
+ 0, /* U+016b2e: */
+ 0, /* U+016b2f: */
+ 0, /* U+016b30: */
+ 0, /* U+016b31: */
+ 0, /* U+016b32: */
+ 0, /* U+016b33: */
+ 0, /* U+016b34: */
+ 0, /* U+016b35: */
+ 0, /* U+016b36: */
+ 0, /* U+016b37: */
+ 0, /* U+016b38: */
+ 0, /* U+016b39: */
+ 0, /* U+016b3a: */
+ 0, /* U+016b3b: */
+ 0, /* U+016b3c: */
+ 0, /* U+016b3d: */
+ 0, /* U+016b3e: */
+ 0, /* U+016b3f: */
+ 0, /* U+016b40: */
+ 0, /* U+016b41: */
+ 0, /* U+016b42: */
+ 0, /* U+016b43: */
+ 0, /* U+016b44: */
+ 0, /* U+016b45: */
+ 0, /* U+016b46: */
+ 0, /* U+016b47: */
+ 0, /* U+016b48: */
+ 0, /* U+016b49: */
+ 0, /* U+016b4a: */
+ 0, /* U+016b4b: */
+ 0, /* U+016b4c: */
+ 0, /* U+016b4d: */
+ 0, /* U+016b4e: */
+ 0, /* U+016b4f: */
+ 0, /* U+016b50: */
+ 0, /* U+016b51: */
+ 0, /* U+016b52: */
+ 0, /* U+016b53: */
+ 0, /* U+016b54: */
+ 0, /* U+016b55: */
+ 0, /* U+016b56: */
+ 0, /* U+016b57: */
+ 0, /* U+016b58: */
+ 0, /* U+016b59: */
+ 0, /* U+016b5a: */
+ 0, /* U+016b5b: */
+ 0, /* U+016b5c: */
+ 0, /* U+016b5d: */
+ 0, /* U+016b5e: */
+ 0, /* U+016b5f: */
+ 0, /* U+016b60: */
+ 0, /* U+016b61: */
+ 0, /* U+016b62: */
+ 0, /* U+016b63: */
+ 0, /* U+016b64: */
+ 0, /* U+016b65: */
+ 0, /* U+016b66: */
+ 0, /* U+016b67: */
+ 0, /* U+016b68: */
+ 0, /* U+016b69: */
+ 0, /* U+016b6a: */
+ 0, /* U+016b6b: */
+ 0, /* U+016b6c: */
+ 0, /* U+016b6d: */
+ 0, /* U+016b6e: */
+ 0, /* U+016b6f: */
+ 0, /* U+016b70: */
+ 0, /* U+016b71: */
+ 0, /* U+016b72: */
+ 0, /* U+016b73: */
+ 0, /* U+016b74: */
+ 0, /* U+016b75: */
+ 0, /* U+016b76: */
+ 0, /* U+016b77: */
+ 0, /* U+016b78: */
+ 0, /* U+016b79: */
+ 0, /* U+016b7a: */
+ 0, /* U+016b7b: */
+ 0, /* U+016b7c: */
+ 0, /* U+016b7d: */
+ 0, /* U+016b7e: */
+ 0, /* U+016b7f: */
+ 0, /* U+016b80: */
+ 0, /* U+016b81: */
+ 0, /* U+016b82: */
+ 0, /* U+016b83: */
+ 0, /* U+016b84: */
+ 0, /* U+016b85: */
+ 0, /* U+016b86: */
+ 0, /* U+016b87: */
+ 0, /* U+016b88: */
+ 0, /* U+016b89: */
+ 0, /* U+016b8a: */
+ 0, /* U+016b8b: */
+ 0, /* U+016b8c: */
+ 0, /* U+016b8d: */
+ 0, /* U+016b8e: */
+ 0, /* U+016b8f: */
+ 0, /* U+016b90: */
+ 0, /* U+016b91: */
+ 0, /* U+016b92: */
+ 0, /* U+016b93: */
+ 0, /* U+016b94: */
+ 0, /* U+016b95: */
+ 0, /* U+016b96: */
+ 0, /* U+016b97: */
+ 0, /* U+016b98: */
+ 0, /* U+016b99: */
+ 0, /* U+016b9a: */
+ 0, /* U+016b9b: */
+ 0, /* U+016b9c: */
+ 0, /* U+016b9d: */
+ 0, /* U+016b9e: */
+ 0, /* U+016b9f: */
+ 0, /* U+016ba0: */
+ 0, /* U+016ba1: */
+ 0, /* U+016ba2: */
+ 0, /* U+016ba3: */
+ 0, /* U+016ba4: */
+ 0, /* U+016ba5: */
+ 0, /* U+016ba6: */
+ 0, /* U+016ba7: */
+ 0, /* U+016ba8: */
+ 0, /* U+016ba9: */
+ 0, /* U+016baa: */
+ 0, /* U+016bab: */
+ 0, /* U+016bac: */
+ 0, /* U+016bad: */
+ 0, /* U+016bae: */
+ 0, /* U+016baf: */
+ 0, /* U+016bb0: */
+ 0, /* U+016bb1: */
+ 0, /* U+016bb2: */
+ 0, /* U+016bb3: */
+ 0, /* U+016bb4: */
+ 0, /* U+016bb5: */
+ 0, /* U+016bb6: */
+ 0, /* U+016bb7: */
+ 0, /* U+016bb8: */
+ 0, /* U+016bb9: */
+ 0, /* U+016bba: */
+ 0, /* U+016bbb: */
+ 0, /* U+016bbc: */
+ 0, /* U+016bbd: */
+ 0, /* U+016bbe: */
+ 0, /* U+016bbf: */
+ 0, /* U+016bc0: */
+ 0, /* U+016bc1: */
+ 0, /* U+016bc2: */
+ 0, /* U+016bc3: */
+ 0, /* U+016bc4: */
+ 0, /* U+016bc5: */
+ 0, /* U+016bc6: */
+ 0, /* U+016bc7: */
+ 0, /* U+016bc8: */
+ 0, /* U+016bc9: */
+ 0, /* U+016bca: */
+ 0, /* U+016bcb: */
+ 0, /* U+016bcc: */
+ 0, /* U+016bcd: */
+ 0, /* U+016bce: */
+ 0, /* U+016bcf: */
+ 0, /* U+016bd0: */
+ 0, /* U+016bd1: */
+ 0, /* U+016bd2: */
+ 0, /* U+016bd3: */
+ 0, /* U+016bd4: */
+ 0, /* U+016bd5: */
+ 0, /* U+016bd6: */
+ 0, /* U+016bd7: */
+ 0, /* U+016bd8: */
+ 0, /* U+016bd9: */
+ 0, /* U+016bda: */
+ 0, /* U+016bdb: */
+ 0, /* U+016bdc: */
+ 0, /* U+016bdd: */
+ 0, /* U+016bde: */
+ 0, /* U+016bdf: */
+ 0, /* U+016be0: */
+ 0, /* U+016be1: */
+ 0, /* U+016be2: */
+ 0, /* U+016be3: */
+ 0, /* U+016be4: */
+ 0, /* U+016be5: */
+ 0, /* U+016be6: */
+ 0, /* U+016be7: */
+ 0, /* U+016be8: */
+ 0, /* U+016be9: */
+ 0, /* U+016bea: */
+ 0, /* U+016beb: */
+ 0, /* U+016bec: */
+ 0, /* U+016bed: */
+ 0, /* U+016bee: */
+ 0, /* U+016bef: */
+ 0, /* U+016bf0: */
+ 0, /* U+016bf1: */
+ 0, /* U+016bf2: */
+ 0, /* U+016bf3: */
+ 0, /* U+016bf4: */
+ 0, /* U+016bf5: */
+ 0, /* U+016bf6: */
+ 0, /* U+016bf7: */
+ 0, /* U+016bf8: */
+ 0, /* U+016bf9: */
+ 0, /* U+016bfa: */
+ 0, /* U+016bfb: */
+ 0, /* U+016bfc: */
+ 0, /* U+016bfd: */
+ 0, /* U+016bfe: */
+ 0, /* U+016bff: */
+ 0, /* U+016c00: */
+ 0, /* U+016c01: */
+ 0, /* U+016c02: */
+ 0, /* U+016c03: */
+ 0, /* U+016c04: */
+ 0, /* U+016c05: */
+ 0, /* U+016c06: */
+ 0, /* U+016c07: */
+ 0, /* U+016c08: */
+ 0, /* U+016c09: */
+ 0, /* U+016c0a: */
+ 0, /* U+016c0b: */
+ 0, /* U+016c0c: */
+ 0, /* U+016c0d: */
+ 0, /* U+016c0e: */
+ 0, /* U+016c0f: */
+ 0, /* U+016c10: */
+ 0, /* U+016c11: */
+ 0, /* U+016c12: */
+ 0, /* U+016c13: */
+ 0, /* U+016c14: */
+ 0, /* U+016c15: */
+ 0, /* U+016c16: */
+ 0, /* U+016c17: */
+ 0, /* U+016c18: */
+ 0, /* U+016c19: */
+ 0, /* U+016c1a: */
+ 0, /* U+016c1b: */
+ 0, /* U+016c1c: */
+ 0, /* U+016c1d: */
+ 0, /* U+016c1e: */
+ 0, /* U+016c1f: */
+ 0, /* U+016c20: */
+ 0, /* U+016c21: */
+ 0, /* U+016c22: */
+ 0, /* U+016c23: */
+ 0, /* U+016c24: */
+ 0, /* U+016c25: */
+ 0, /* U+016c26: */
+ 0, /* U+016c27: */
+ 0, /* U+016c28: */
+ 0, /* U+016c29: */
+ 0, /* U+016c2a: */
+ 0, /* U+016c2b: */
+ 0, /* U+016c2c: */
+ 0, /* U+016c2d: */
+ 0, /* U+016c2e: */
+ 0, /* U+016c2f: */
+ 0, /* U+016c30: */
+ 0, /* U+016c31: */
+ 0, /* U+016c32: */
+ 0, /* U+016c33: */
+ 0, /* U+016c34: */
+ 0, /* U+016c35: */
+ 0, /* U+016c36: */
+ 0, /* U+016c37: */
+ 0, /* U+016c38: */
+ 0, /* U+016c39: */
+ 0, /* U+016c3a: */
+ 0, /* U+016c3b: */
+ 0, /* U+016c3c: */
+ 0, /* U+016c3d: */
+ 0, /* U+016c3e: */
+ 0, /* U+016c3f: */
+ 0, /* U+016c40: */
+ 0, /* U+016c41: */
+ 0, /* U+016c42: */
+ 0, /* U+016c43: */
+ 0, /* U+016c44: */
+ 0, /* U+016c45: */
+ 0, /* U+016c46: */
+ 0, /* U+016c47: */
+ 0, /* U+016c48: */
+ 0, /* U+016c49: */
+ 0, /* U+016c4a: */
+ 0, /* U+016c4b: */
+ 0, /* U+016c4c: */
+ 0, /* U+016c4d: */
+ 0, /* U+016c4e: */
+ 0, /* U+016c4f: */
+ 0, /* U+016c50: */
+ 0, /* U+016c51: */
+ 0, /* U+016c52: */
+ 0, /* U+016c53: */
+ 0, /* U+016c54: */
+ 0, /* U+016c55: */
+ 0, /* U+016c56: */
+ 0, /* U+016c57: */
+ 0, /* U+016c58: */
+ 0, /* U+016c59: */
+ 0, /* U+016c5a: */
+ 0, /* U+016c5b: */
+ 0, /* U+016c5c: */
+ 0, /* U+016c5d: */
+ 0, /* U+016c5e: */
+ 0, /* U+016c5f: */
+ 0, /* U+016c60: */
+ 0, /* U+016c61: */
+ 0, /* U+016c62: */
+ 0, /* U+016c63: */
+ 0, /* U+016c64: */
+ 0, /* U+016c65: */
+ 0, /* U+016c66: */
+ 0, /* U+016c67: */
+ 0, /* U+016c68: */
+ 0, /* U+016c69: */
+ 0, /* U+016c6a: */
+ 0, /* U+016c6b: */
+ 0, /* U+016c6c: */
+ 0, /* U+016c6d: */
+ 0, /* U+016c6e: */
+ 0, /* U+016c6f: */
+ 0, /* U+016c70: */
+ 0, /* U+016c71: */
+ 0, /* U+016c72: */
+ 0, /* U+016c73: */
+ 0, /* U+016c74: */
+ 0, /* U+016c75: */
+ 0, /* U+016c76: */
+ 0, /* U+016c77: */
+ 0, /* U+016c78: */
+ 0, /* U+016c79: */
+ 0, /* U+016c7a: */
+ 0, /* U+016c7b: */
+ 0, /* U+016c7c: */
+ 0, /* U+016c7d: */
+ 0, /* U+016c7e: */
+ 0, /* U+016c7f: */
+ 0, /* U+016c80: */
+ 0, /* U+016c81: */
+ 0, /* U+016c82: */
+ 0, /* U+016c83: */
+ 0, /* U+016c84: */
+ 0, /* U+016c85: */
+ 0, /* U+016c86: */
+ 0, /* U+016c87: */
+ 0, /* U+016c88: */
+ 0, /* U+016c89: */
+ 0, /* U+016c8a: */
+ 0, /* U+016c8b: */
+ 0, /* U+016c8c: */
+ 0, /* U+016c8d: */
+ 0, /* U+016c8e: */
+ 0, /* U+016c8f: */
+ 0, /* U+016c90: */
+ 0, /* U+016c91: */
+ 0, /* U+016c92: */
+ 0, /* U+016c93: */
+ 0, /* U+016c94: */
+ 0, /* U+016c95: */
+ 0, /* U+016c96: */
+ 0, /* U+016c97: */
+ 0, /* U+016c98: */
+ 0, /* U+016c99: */
+ 0, /* U+016c9a: */
+ 0, /* U+016c9b: */
+ 0, /* U+016c9c: */
+ 0, /* U+016c9d: */
+ 0, /* U+016c9e: */
+ 0, /* U+016c9f: */
+ 0, /* U+016ca0: */
+ 0, /* U+016ca1: */
+ 0, /* U+016ca2: */
+ 0, /* U+016ca3: */
+ 0, /* U+016ca4: */
+ 0, /* U+016ca5: */
+ 0, /* U+016ca6: */
+ 0, /* U+016ca7: */
+ 0, /* U+016ca8: */
+ 0, /* U+016ca9: */
+ 0, /* U+016caa: */
+ 0, /* U+016cab: */
+ 0, /* U+016cac: */
+ 0, /* U+016cad: */
+ 0, /* U+016cae: */
+ 0, /* U+016caf: */
+ 0, /* U+016cb0: */
+ 0, /* U+016cb1: */
+ 0, /* U+016cb2: */
+ 0, /* U+016cb3: */
+ 0, /* U+016cb4: */
+ 0, /* U+016cb5: */
+ 0, /* U+016cb6: */
+ 0, /* U+016cb7: */
+ 0, /* U+016cb8: */
+ 0, /* U+016cb9: */
+ 0, /* U+016cba: */
+ 0, /* U+016cbb: */
+ 0, /* U+016cbc: */
+ 0, /* U+016cbd: */
+ 0, /* U+016cbe: */
+ 0, /* U+016cbf: */
+ 0, /* U+016cc0: */
+ 0, /* U+016cc1: */
+ 0, /* U+016cc2: */
+ 0, /* U+016cc3: */
+ 0, /* U+016cc4: */
+ 0, /* U+016cc5: */
+ 0, /* U+016cc6: */
+ 0, /* U+016cc7: */
+ 0, /* U+016cc8: */
+ 0, /* U+016cc9: */
+ 0, /* U+016cca: */
+ 0, /* U+016ccb: */
+ 0, /* U+016ccc: */
+ 0, /* U+016ccd: */
+ 0, /* U+016cce: */
+ 0, /* U+016ccf: */
+ 0, /* U+016cd0: */
+ 0, /* U+016cd1: */
+ 0, /* U+016cd2: */
+ 0, /* U+016cd3: */
+ 0, /* U+016cd4: */
+ 0, /* U+016cd5: */
+ 0, /* U+016cd6: */
+ 0, /* U+016cd7: */
+ 0, /* U+016cd8: */
+ 0, /* U+016cd9: */
+ 0, /* U+016cda: */
+ 0, /* U+016cdb: */
+ 0, /* U+016cdc: */
+ 0, /* U+016cdd: */
+ 0, /* U+016cde: */
+ 0, /* U+016cdf: */
+ 0, /* U+016ce0: */
+ 0, /* U+016ce1: */
+ 0, /* U+016ce2: */
+ 0, /* U+016ce3: */
+ 0, /* U+016ce4: */
+ 0, /* U+016ce5: */
+ 0, /* U+016ce6: */
+ 0, /* U+016ce7: */
+ 0, /* U+016ce8: */
+ 0, /* U+016ce9: */
+ 0, /* U+016cea: */
+ 0, /* U+016ceb: */
+ 0, /* U+016cec: */
+ 0, /* U+016ced: */
+ 0, /* U+016cee: */
+ 0, /* U+016cef: */
+ 0, /* U+016cf0: */
+ 0, /* U+016cf1: */
+ 0, /* U+016cf2: */
+ 0, /* U+016cf3: */
+ 0, /* U+016cf4: */
+ 0, /* U+016cf5: */
+ 0, /* U+016cf6: */
+ 0, /* U+016cf7: */
+ 0, /* U+016cf8: */
+ 0, /* U+016cf9: */
+ 0, /* U+016cfa: */
+ 0, /* U+016cfb: */
+ 0, /* U+016cfc: */
+ 0, /* U+016cfd: */
+ 0, /* U+016cfe: */
+ 0, /* U+016cff: */
+ 0, /* U+016d00: */
+ 0, /* U+016d01: */
+ 0, /* U+016d02: */
+ 0, /* U+016d03: */
+ 0, /* U+016d04: */
+ 0, /* U+016d05: */
+ 0, /* U+016d06: */
+ 0, /* U+016d07: */
+ 0, /* U+016d08: */
+ 0, /* U+016d09: */
+ 0, /* U+016d0a: */
+ 0, /* U+016d0b: */
+ 0, /* U+016d0c: */
+ 0, /* U+016d0d: */
+ 0, /* U+016d0e: */
+ 0, /* U+016d0f: */
+ 0, /* U+016d10: */
+ 0, /* U+016d11: */
+ 0, /* U+016d12: */
+ 0, /* U+016d13: */
+ 0, /* U+016d14: */
+ 0, /* U+016d15: */
+ 0, /* U+016d16: */
+ 0, /* U+016d17: */
+ 0, /* U+016d18: */
+ 0, /* U+016d19: */
+ 0, /* U+016d1a: */
+ 0, /* U+016d1b: */
+ 0, /* U+016d1c: */
+ 0, /* U+016d1d: */
+ 0, /* U+016d1e: */
+ 0, /* U+016d1f: */
+ 0, /* U+016d20: */
+ 0, /* U+016d21: */
+ 0, /* U+016d22: */
+ 0, /* U+016d23: */
+ 0, /* U+016d24: */
+ 0, /* U+016d25: */
+ 0, /* U+016d26: */
+ 0, /* U+016d27: */
+ 0, /* U+016d28: */
+ 0, /* U+016d29: */
+ 0, /* U+016d2a: */
+ 0, /* U+016d2b: */
+ 0, /* U+016d2c: */
+ 0, /* U+016d2d: */
+ 0, /* U+016d2e: */
+ 0, /* U+016d2f: */
+ 0, /* U+016d30: */
+ 0, /* U+016d31: */
+ 0, /* U+016d32: */
+ 0, /* U+016d33: */
+ 0, /* U+016d34: */
+ 0, /* U+016d35: */
+ 0, /* U+016d36: */
+ 0, /* U+016d37: */
+ 0, /* U+016d38: */
+ 0, /* U+016d39: */
+ 0, /* U+016d3a: */
+ 0, /* U+016d3b: */
+ 0, /* U+016d3c: */
+ 0, /* U+016d3d: */
+ 0, /* U+016d3e: */
+ 0, /* U+016d3f: */
+ 0, /* U+016d40: */
+ 0, /* U+016d41: */
+ 0, /* U+016d42: */
+ 0, /* U+016d43: */
+ 0, /* U+016d44: */
+ 0, /* U+016d45: */
+ 0, /* U+016d46: */
+ 0, /* U+016d47: */
+ 0, /* U+016d48: */
+ 0, /* U+016d49: */
+ 0, /* U+016d4a: */
+ 0, /* U+016d4b: */
+ 0, /* U+016d4c: */
+ 0, /* U+016d4d: */
+ 0, /* U+016d4e: */
+ 0, /* U+016d4f: */
+ 0, /* U+016d50: */
+ 0, /* U+016d51: */
+ 0, /* U+016d52: */
+ 0, /* U+016d53: */
+ 0, /* U+016d54: */
+ 0, /* U+016d55: */
+ 0, /* U+016d56: */
+ 0, /* U+016d57: */
+ 0, /* U+016d58: */
+ 0, /* U+016d59: */
+ 0, /* U+016d5a: */
+ 0, /* U+016d5b: */
+ 0, /* U+016d5c: */
+ 0, /* U+016d5d: */
+ 0, /* U+016d5e: */
+ 0, /* U+016d5f: */
+ 0, /* U+016d60: */
+ 0, /* U+016d61: */
+ 0, /* U+016d62: */
+ 0, /* U+016d63: */
+ 0, /* U+016d64: */
+ 0, /* U+016d65: */
+ 0, /* U+016d66: */
+ 0, /* U+016d67: */
+ 0, /* U+016d68: */
+ 0, /* U+016d69: */
+ 0, /* U+016d6a: */
+ 0, /* U+016d6b: */
+ 0, /* U+016d6c: */
+ 0, /* U+016d6d: */
+ 0, /* U+016d6e: */
+ 0, /* U+016d6f: */
+ 0, /* U+016d70: */
+ 0, /* U+016d71: */
+ 0, /* U+016d72: */
+ 0, /* U+016d73: */
+ 0, /* U+016d74: */
+ 0, /* U+016d75: */
+ 0, /* U+016d76: */
+ 0, /* U+016d77: */
+ 0, /* U+016d78: */
+ 0, /* U+016d79: */
+ 0, /* U+016d7a: */
+ 0, /* U+016d7b: */
+ 0, /* U+016d7c: */
+ 0, /* U+016d7d: */
+ 0, /* U+016d7e: */
+ 0, /* U+016d7f: */
+ 0, /* U+016d80: */
+ 0, /* U+016d81: */
+ 0, /* U+016d82: */
+ 0, /* U+016d83: */
+ 0, /* U+016d84: */
+ 0, /* U+016d85: */
+ 0, /* U+016d86: */
+ 0, /* U+016d87: */
+ 0, /* U+016d88: */
+ 0, /* U+016d89: */
+ 0, /* U+016d8a: */
+ 0, /* U+016d8b: */
+ 0, /* U+016d8c: */
+ 0, /* U+016d8d: */
+ 0, /* U+016d8e: */
+ 0, /* U+016d8f: */
+ 0, /* U+016d90: */
+ 0, /* U+016d91: */
+ 0, /* U+016d92: */
+ 0, /* U+016d93: */
+ 0, /* U+016d94: */
+ 0, /* U+016d95: */
+ 0, /* U+016d96: */
+ 0, /* U+016d97: */
+ 0, /* U+016d98: */
+ 0, /* U+016d99: */
+ 0, /* U+016d9a: */
+ 0, /* U+016d9b: */
+ 0, /* U+016d9c: */
+ 0, /* U+016d9d: */
+ 0, /* U+016d9e: */
+ 0, /* U+016d9f: */
+ 0, /* U+016da0: */
+ 0, /* U+016da1: */
+ 0, /* U+016da2: */
+ 0, /* U+016da3: */
+ 0, /* U+016da4: */
+ 0, /* U+016da5: */
+ 0, /* U+016da6: */
+ 0, /* U+016da7: */
+ 0, /* U+016da8: */
+ 0, /* U+016da9: */
+ 0, /* U+016daa: */
+ 0, /* U+016dab: */
+ 0, /* U+016dac: */
+ 0, /* U+016dad: */
+ 0, /* U+016dae: */
+ 0, /* U+016daf: */
+ 0, /* U+016db0: */
+ 0, /* U+016db1: */
+ 0, /* U+016db2: */
+ 0, /* U+016db3: */
+ 0, /* U+016db4: */
+ 0, /* U+016db5: */
+ 0, /* U+016db6: */
+ 0, /* U+016db7: */
+ 0, /* U+016db8: */
+ 0, /* U+016db9: */
+ 0, /* U+016dba: */
+ 0, /* U+016dbb: */
+ 0, /* U+016dbc: */
+ 0, /* U+016dbd: */
+ 0, /* U+016dbe: */
+ 0, /* U+016dbf: */
+ 0, /* U+016dc0: */
+ 0, /* U+016dc1: */
+ 0, /* U+016dc2: */
+ 0, /* U+016dc3: */
+ 0, /* U+016dc4: */
+ 0, /* U+016dc5: */
+ 0, /* U+016dc6: */
+ 0, /* U+016dc7: */
+ 0, /* U+016dc8: */
+ 0, /* U+016dc9: */
+ 0, /* U+016dca: */
+ 0, /* U+016dcb: */
+ 0, /* U+016dcc: */
+ 0, /* U+016dcd: */
+ 0, /* U+016dce: */
+ 0, /* U+016dcf: */
+ 0, /* U+016dd0: */
+ 0, /* U+016dd1: */
+ 0, /* U+016dd2: */
+ 0, /* U+016dd3: */
+ 0, /* U+016dd4: */
+ 0, /* U+016dd5: */
+ 0, /* U+016dd6: */
+ 0, /* U+016dd7: */
+ 0, /* U+016dd8: */
+ 0, /* U+016dd9: */
+ 0, /* U+016dda: */
+ 0, /* U+016ddb: */
+ 0, /* U+016ddc: */
+ 0, /* U+016ddd: */
+ 0, /* U+016dde: */
+ 0, /* U+016ddf: */
+ 0, /* U+016de0: */
+ 0, /* U+016de1: */
+ 0, /* U+016de2: */
+ 0, /* U+016de3: */
+ 0, /* U+016de4: */
+ 0, /* U+016de5: */
+ 0, /* U+016de6: */
+ 0, /* U+016de7: */
+ 0, /* U+016de8: */
+ 0, /* U+016de9: */
+ 0, /* U+016dea: */
+ 0, /* U+016deb: */
+ 0, /* U+016dec: */
+ 0, /* U+016ded: */
+ 0, /* U+016dee: */
+ 0, /* U+016def: */
+ 0, /* U+016df0: */
+ 0, /* U+016df1: */
+ 0, /* U+016df2: */
+ 0, /* U+016df3: */
+ 0, /* U+016df4: */
+ 0, /* U+016df5: */
+ 0, /* U+016df6: */
+ 0, /* U+016df7: */
+ 0, /* U+016df8: */
+ 0, /* U+016df9: */
+ 0, /* U+016dfa: */
+ 0, /* U+016dfb: */
+ 0, /* U+016dfc: */
+ 0, /* U+016dfd: */
+ 0, /* U+016dfe: */
+ 0, /* U+016dff: */
+ 0, /* U+016e00: */
+ 0, /* U+016e01: */
+ 0, /* U+016e02: */
+ 0, /* U+016e03: */
+ 0, /* U+016e04: */
+ 0, /* U+016e05: */
+ 0, /* U+016e06: */
+ 0, /* U+016e07: */
+ 0, /* U+016e08: */
+ 0, /* U+016e09: */
+ 0, /* U+016e0a: */
+ 0, /* U+016e0b: */
+ 0, /* U+016e0c: */
+ 0, /* U+016e0d: */
+ 0, /* U+016e0e: */
+ 0, /* U+016e0f: */
+ 0, /* U+016e10: */
+ 0, /* U+016e11: */
+ 0, /* U+016e12: */
+ 0, /* U+016e13: */
+ 0, /* U+016e14: */
+ 0, /* U+016e15: */
+ 0, /* U+016e16: */
+ 0, /* U+016e17: */
+ 0, /* U+016e18: */
+ 0, /* U+016e19: */
+ 0, /* U+016e1a: */
+ 0, /* U+016e1b: */
+ 0, /* U+016e1c: */
+ 0, /* U+016e1d: */
+ 0, /* U+016e1e: */
+ 0, /* U+016e1f: */
+ 0, /* U+016e20: */
+ 0, /* U+016e21: */
+ 0, /* U+016e22: */
+ 0, /* U+016e23: */
+ 0, /* U+016e24: */
+ 0, /* U+016e25: */
+ 0, /* U+016e26: */
+ 0, /* U+016e27: */
+ 0, /* U+016e28: */
+ 0, /* U+016e29: */
+ 0, /* U+016e2a: */
+ 0, /* U+016e2b: */
+ 0, /* U+016e2c: */
+ 0, /* U+016e2d: */
+ 0, /* U+016e2e: */
+ 0, /* U+016e2f: */
+ 0, /* U+016e30: */
+ 0, /* U+016e31: */
+ 0, /* U+016e32: */
+ 0, /* U+016e33: */
+ 0, /* U+016e34: */
+ 0, /* U+016e35: */
+ 0, /* U+016e36: */
+ 0, /* U+016e37: */
+ 0, /* U+016e38: */
+ 0, /* U+016e39: */
+ 0, /* U+016e3a: */
+ 0, /* U+016e3b: */
+ 0, /* U+016e3c: */
+ 0, /* U+016e3d: */
+ 0, /* U+016e3e: */
+ 0, /* U+016e3f: */
+ 0, /* U+016e40: */
+ 0, /* U+016e41: */
+ 0, /* U+016e42: */
+ 0, /* U+016e43: */
+ 0, /* U+016e44: */
+ 0, /* U+016e45: */
+ 0, /* U+016e46: */
+ 0, /* U+016e47: */
+ 0, /* U+016e48: */
+ 0, /* U+016e49: */
+ 0, /* U+016e4a: */
+ 0, /* U+016e4b: */
+ 0, /* U+016e4c: */
+ 0, /* U+016e4d: */
+ 0, /* U+016e4e: */
+ 0, /* U+016e4f: */
+ 0, /* U+016e50: */
+ 0, /* U+016e51: */
+ 0, /* U+016e52: */
+ 0, /* U+016e53: */
+ 0, /* U+016e54: */
+ 0, /* U+016e55: */
+ 0, /* U+016e56: */
+ 0, /* U+016e57: */
+ 0, /* U+016e58: */
+ 0, /* U+016e59: */
+ 0, /* U+016e5a: */
+ 0, /* U+016e5b: */
+ 0, /* U+016e5c: */
+ 0, /* U+016e5d: */
+ 0, /* U+016e5e: */
+ 0, /* U+016e5f: */
+ 0, /* U+016e60: */
+ 0, /* U+016e61: */
+ 0, /* U+016e62: */
+ 0, /* U+016e63: */
+ 0, /* U+016e64: */
+ 0, /* U+016e65: */
+ 0, /* U+016e66: */
+ 0, /* U+016e67: */
+ 0, /* U+016e68: */
+ 0, /* U+016e69: */
+ 0, /* U+016e6a: */
+ 0, /* U+016e6b: */
+ 0, /* U+016e6c: */
+ 0, /* U+016e6d: */
+ 0, /* U+016e6e: */
+ 0, /* U+016e6f: */
+ 0, /* U+016e70: */
+ 0, /* U+016e71: */
+ 0, /* U+016e72: */
+ 0, /* U+016e73: */
+ 0, /* U+016e74: */
+ 0, /* U+016e75: */
+ 0, /* U+016e76: */
+ 0, /* U+016e77: */
+ 0, /* U+016e78: */
+ 0, /* U+016e79: */
+ 0, /* U+016e7a: */
+ 0, /* U+016e7b: */
+ 0, /* U+016e7c: */
+ 0, /* U+016e7d: */
+ 0, /* U+016e7e: */
+ 0, /* U+016e7f: */
+ 0, /* U+016e80: */
+ 0, /* U+016e81: */
+ 0, /* U+016e82: */
+ 0, /* U+016e83: */
+ 0, /* U+016e84: */
+ 0, /* U+016e85: */
+ 0, /* U+016e86: */
+ 0, /* U+016e87: */
+ 0, /* U+016e88: */
+ 0, /* U+016e89: */
+ 0, /* U+016e8a: */
+ 0, /* U+016e8b: */
+ 0, /* U+016e8c: */
+ 0, /* U+016e8d: */
+ 0, /* U+016e8e: */
+ 0, /* U+016e8f: */
+ 0, /* U+016e90: */
+ 0, /* U+016e91: */
+ 0, /* U+016e92: */
+ 0, /* U+016e93: */
+ 0, /* U+016e94: */
+ 0, /* U+016e95: */
+ 0, /* U+016e96: */
+ 0, /* U+016e97: */
+ 0, /* U+016e98: */
+ 0, /* U+016e99: */
+ 0, /* U+016e9a: */
+ 0, /* U+016e9b: */
+ 0, /* U+016e9c: */
+ 0, /* U+016e9d: */
+ 0, /* U+016e9e: */
+ 0, /* U+016e9f: */
+ 0, /* U+016ea0: */
+ 0, /* U+016ea1: */
+ 0, /* U+016ea2: */
+ 0, /* U+016ea3: */
+ 0, /* U+016ea4: */
+ 0, /* U+016ea5: */
+ 0, /* U+016ea6: */
+ 0, /* U+016ea7: */
+ 0, /* U+016ea8: */
+ 0, /* U+016ea9: */
+ 0, /* U+016eaa: */
+ 0, /* U+016eab: */
+ 0, /* U+016eac: */
+ 0, /* U+016ead: */
+ 0, /* U+016eae: */
+ 0, /* U+016eaf: */
+ 0, /* U+016eb0: */
+ 0, /* U+016eb1: */
+ 0, /* U+016eb2: */
+ 0, /* U+016eb3: */
+ 0, /* U+016eb4: */
+ 0, /* U+016eb5: */
+ 0, /* U+016eb6: */
+ 0, /* U+016eb7: */
+ 0, /* U+016eb8: */
+ 0, /* U+016eb9: */
+ 0, /* U+016eba: */
+ 0, /* U+016ebb: */
+ 0, /* U+016ebc: */
+ 0, /* U+016ebd: */
+ 0, /* U+016ebe: */
+ 0, /* U+016ebf: */
+ 0, /* U+016ec0: */
+ 0, /* U+016ec1: */
+ 0, /* U+016ec2: */
+ 0, /* U+016ec3: */
+ 0, /* U+016ec4: */
+ 0, /* U+016ec5: */
+ 0, /* U+016ec6: */
+ 0, /* U+016ec7: */
+ 0, /* U+016ec8: */
+ 0, /* U+016ec9: */
+ 0, /* U+016eca: */
+ 0, /* U+016ecb: */
+ 0, /* U+016ecc: */
+ 0, /* U+016ecd: */
+ 0, /* U+016ece: */
+ 0, /* U+016ecf: */
+ 0, /* U+016ed0: */
+ 0, /* U+016ed1: */
+ 0, /* U+016ed2: */
+ 0, /* U+016ed3: */
+ 0, /* U+016ed4: */
+ 0, /* U+016ed5: */
+ 0, /* U+016ed6: */
+ 0, /* U+016ed7: */
+ 0, /* U+016ed8: */
+ 0, /* U+016ed9: */
+ 0, /* U+016eda: */
+ 0, /* U+016edb: */
+ 0, /* U+016edc: */
+ 0, /* U+016edd: */
+ 0, /* U+016ede: */
+ 0, /* U+016edf: */
+ 0, /* U+016ee0: */
+ 0, /* U+016ee1: */
+ 0, /* U+016ee2: */
+ 0, /* U+016ee3: */
+ 0, /* U+016ee4: */
+ 0, /* U+016ee5: */
+ 0, /* U+016ee6: */
+ 0, /* U+016ee7: */
+ 0, /* U+016ee8: */
+ 0, /* U+016ee9: */
+ 0, /* U+016eea: */
+ 0, /* U+016eeb: */
+ 0, /* U+016eec: */
+ 0, /* U+016eed: */
+ 0, /* U+016eee: */
+ 0, /* U+016eef: */
+ 0, /* U+016ef0: */
+ 0, /* U+016ef1: */
+ 0, /* U+016ef2: */
+ 0, /* U+016ef3: */
+ 0, /* U+016ef4: */
+ 0, /* U+016ef5: */
+ 0, /* U+016ef6: */
+ 0, /* U+016ef7: */
+ 0, /* U+016ef8: */
+ 0, /* U+016ef9: */
+ 0, /* U+016efa: */
+ 0, /* U+016efb: */
+ 0, /* U+016efc: */
+ 0, /* U+016efd: */
+ 0, /* U+016efe: */
+ 0, /* U+016eff: */
+ RTUNI_ALPHA, /* U+016f00: MIAO LETTER PA*/
+ RTUNI_ALPHA, /* U+016f01: MIAO LETTER BA*/
+ RTUNI_ALPHA, /* U+016f02: MIAO LETTER YI PA*/
+ RTUNI_ALPHA, /* U+016f03: MIAO LETTER PLA*/
+ RTUNI_ALPHA, /* U+016f04: MIAO LETTER MA*/
+ RTUNI_ALPHA, /* U+016f05: MIAO LETTER MHA*/
+ RTUNI_ALPHA, /* U+016f06: MIAO LETTER ARCHAIC MA*/
+ RTUNI_ALPHA, /* U+016f07: MIAO LETTER FA*/
+ RTUNI_ALPHA, /* U+016f08: MIAO LETTER VA*/
+ RTUNI_ALPHA, /* U+016f09: MIAO LETTER VFA*/
+ RTUNI_ALPHA, /* U+016f0a: MIAO LETTER TA*/
+ RTUNI_ALPHA, /* U+016f0b: MIAO LETTER DA*/
+ RTUNI_ALPHA, /* U+016f0c: MIAO LETTER YI TTA*/
+ RTUNI_ALPHA, /* U+016f0d: MIAO LETTER YI TA*/
+ RTUNI_ALPHA, /* U+016f0e: MIAO LETTER TTA*/
+ RTUNI_ALPHA, /* U+016f0f: MIAO LETTER DDA*/
+ RTUNI_ALPHA, /* U+016f10: MIAO LETTER NA*/
+ RTUNI_ALPHA, /* U+016f11: MIAO LETTER NHA*/
+ RTUNI_ALPHA, /* U+016f12: MIAO LETTER YI NNA*/
+ RTUNI_ALPHA, /* U+016f13: MIAO LETTER ARCHAIC NA*/
+ RTUNI_ALPHA, /* U+016f14: MIAO LETTER NNA*/
+ RTUNI_ALPHA, /* U+016f15: MIAO LETTER NNHA*/
+ RTUNI_ALPHA, /* U+016f16: MIAO LETTER LA*/
+ RTUNI_ALPHA, /* U+016f17: MIAO LETTER LYA*/
+ RTUNI_ALPHA, /* U+016f18: MIAO LETTER LHA*/
+ RTUNI_ALPHA, /* U+016f19: MIAO LETTER LHYA*/
+ RTUNI_ALPHA, /* U+016f1a: MIAO LETTER TLHA*/
+ RTUNI_ALPHA, /* U+016f1b: MIAO LETTER DLHA*/
+ RTUNI_ALPHA, /* U+016f1c: MIAO LETTER TLHYA*/
+ RTUNI_ALPHA, /* U+016f1d: MIAO LETTER DLHYA*/
+ RTUNI_ALPHA, /* U+016f1e: MIAO LETTER KA*/
+ RTUNI_ALPHA, /* U+016f1f: MIAO LETTER GA*/
+ RTUNI_ALPHA, /* U+016f20: MIAO LETTER YI KA*/
+ RTUNI_ALPHA, /* U+016f21: MIAO LETTER QA*/
+ RTUNI_ALPHA, /* U+016f22: MIAO LETTER QGA*/
+ RTUNI_ALPHA, /* U+016f23: MIAO LETTER NGA*/
+ RTUNI_ALPHA, /* U+016f24: MIAO LETTER NGHA*/
+ RTUNI_ALPHA, /* U+016f25: MIAO LETTER ARCHAIC NGA*/
+ RTUNI_ALPHA, /* U+016f26: MIAO LETTER HA*/
+ RTUNI_ALPHA, /* U+016f27: MIAO LETTER XA*/
+ RTUNI_ALPHA, /* U+016f28: MIAO LETTER GHA*/
+ RTUNI_ALPHA, /* U+016f29: MIAO LETTER GHHA*/
+ RTUNI_ALPHA, /* U+016f2a: MIAO LETTER TSSA*/
+ RTUNI_ALPHA, /* U+016f2b: MIAO LETTER DZZA*/
+ RTUNI_ALPHA, /* U+016f2c: MIAO LETTER NYA*/
+ RTUNI_ALPHA, /* U+016f2d: MIAO LETTER NYHA*/
+ RTUNI_ALPHA, /* U+016f2e: MIAO LETTER TSHA*/
+ RTUNI_ALPHA, /* U+016f2f: MIAO LETTER DZHA*/
+ RTUNI_ALPHA, /* U+016f30: MIAO LETTER YI TSHA*/
+ RTUNI_ALPHA, /* U+016f31: MIAO LETTER YI DZHA*/
+ RTUNI_ALPHA, /* U+016f32: MIAO LETTER REFORMED TSHA*/
+ RTUNI_ALPHA, /* U+016f33: MIAO LETTER SHA*/
+ RTUNI_ALPHA, /* U+016f34: MIAO LETTER SSA*/
+ RTUNI_ALPHA, /* U+016f35: MIAO LETTER ZHA*/
+ RTUNI_ALPHA, /* U+016f36: MIAO LETTER ZSHA*/
+ RTUNI_ALPHA, /* U+016f37: MIAO LETTER TSA*/
+ RTUNI_ALPHA, /* U+016f38: MIAO LETTER DZA*/
+ RTUNI_ALPHA, /* U+016f39: MIAO LETTER YI TSA*/
+ RTUNI_ALPHA, /* U+016f3a: MIAO LETTER SA*/
+ RTUNI_ALPHA, /* U+016f3b: MIAO LETTER ZA*/
+ RTUNI_ALPHA, /* U+016f3c: MIAO LETTER ZSA*/
+ RTUNI_ALPHA, /* U+016f3d: MIAO LETTER ZZA*/
+ RTUNI_ALPHA, /* U+016f3e: MIAO LETTER ZZSA*/
+ RTUNI_ALPHA, /* U+016f3f: MIAO LETTER ARCHAIC ZZA*/
+ RTUNI_ALPHA, /* U+016f40: MIAO LETTER ZZYA*/
+ RTUNI_ALPHA, /* U+016f41: MIAO LETTER ZZSYA*/
+ RTUNI_ALPHA, /* U+016f42: MIAO LETTER WA*/
+ RTUNI_ALPHA, /* U+016f43: MIAO LETTER AH*/
+ RTUNI_ALPHA, /* U+016f44: MIAO LETTER HHA*/
+ 0, /* U+016f45: */
+ 0, /* U+016f46: */
+ 0, /* U+016f47: */
+ 0, /* U+016f48: */
+ 0, /* U+016f49: */
+ 0, /* U+016f4a: */
+ 0, /* U+016f4b: */
+ 0, /* U+016f4c: */
+ 0, /* U+016f4d: */
+ 0, /* U+016f4e: */
+ 0, /* U+016f4f: */
+ RTUNI_ALPHA, /* U+016f50: MIAO LETTER NASALIZATION*/
+ RTUNI_ALPHA, /* U+016f51: MIAO SIGN ASPIRATION*/
+ RTUNI_ALPHA, /* U+016f52: MIAO SIGN REFORMED VOICING*/
+ RTUNI_ALPHA, /* U+016f53: MIAO SIGN REFORMED ASPIRATION*/
+ RTUNI_ALPHA, /* U+016f54: MIAO VOWEL SIGN A*/
+ RTUNI_ALPHA, /* U+016f55: MIAO VOWEL SIGN AA*/
+ RTUNI_ALPHA, /* U+016f56: MIAO VOWEL SIGN AHH*/
+ RTUNI_ALPHA, /* U+016f57: MIAO VOWEL SIGN AN*/
+ RTUNI_ALPHA, /* U+016f58: MIAO VOWEL SIGN ANG*/
+ RTUNI_ALPHA, /* U+016f59: MIAO VOWEL SIGN O*/
+ RTUNI_ALPHA, /* U+016f5a: MIAO VOWEL SIGN OO*/
+ RTUNI_ALPHA, /* U+016f5b: MIAO VOWEL SIGN WO*/
+ RTUNI_ALPHA, /* U+016f5c: MIAO VOWEL SIGN W*/
+ RTUNI_ALPHA, /* U+016f5d: MIAO VOWEL SIGN E*/
+ RTUNI_ALPHA, /* U+016f5e: MIAO VOWEL SIGN EN*/
+ RTUNI_ALPHA, /* U+016f5f: MIAO VOWEL SIGN ENG*/
+ RTUNI_ALPHA, /* U+016f60: MIAO VOWEL SIGN OEY*/
+ RTUNI_ALPHA, /* U+016f61: MIAO VOWEL SIGN I*/
+ RTUNI_ALPHA, /* U+016f62: MIAO VOWEL SIGN IA*/
+ RTUNI_ALPHA, /* U+016f63: MIAO VOWEL SIGN IAN*/
+ RTUNI_ALPHA, /* U+016f64: MIAO VOWEL SIGN IANG*/
+ RTUNI_ALPHA, /* U+016f65: MIAO VOWEL SIGN IO*/
+ RTUNI_ALPHA, /* U+016f66: MIAO VOWEL SIGN IE*/
+ RTUNI_ALPHA, /* U+016f67: MIAO VOWEL SIGN II*/
+ RTUNI_ALPHA, /* U+016f68: MIAO VOWEL SIGN IU*/
+ RTUNI_ALPHA, /* U+016f69: MIAO VOWEL SIGN ING*/
+ RTUNI_ALPHA, /* U+016f6a: MIAO VOWEL SIGN U*/
+ RTUNI_ALPHA, /* U+016f6b: MIAO VOWEL SIGN UA*/
+ RTUNI_ALPHA, /* U+016f6c: MIAO VOWEL SIGN UAN*/
+ RTUNI_ALPHA, /* U+016f6d: MIAO VOWEL SIGN UANG*/
+ RTUNI_ALPHA, /* U+016f6e: MIAO VOWEL SIGN UU*/
+ RTUNI_ALPHA, /* U+016f6f: MIAO VOWEL SIGN UEI*/
+ RTUNI_ALPHA, /* U+016f70: MIAO VOWEL SIGN UNG*/
+ RTUNI_ALPHA, /* U+016f71: MIAO VOWEL SIGN Y*/
+ RTUNI_ALPHA, /* U+016f72: MIAO VOWEL SIGN YI*/
+ RTUNI_ALPHA, /* U+016f73: MIAO VOWEL SIGN AE*/
+ RTUNI_ALPHA, /* U+016f74: MIAO VOWEL SIGN AEE*/
+ RTUNI_ALPHA, /* U+016f75: MIAO VOWEL SIGN ERR*/
+ RTUNI_ALPHA, /* U+016f76: MIAO VOWEL SIGN ROUNDED ERR*/
+ RTUNI_ALPHA, /* U+016f77: MIAO VOWEL SIGN ER*/
+ RTUNI_ALPHA, /* U+016f78: MIAO VOWEL SIGN ROUNDED ER*/
+ RTUNI_ALPHA, /* U+016f79: MIAO VOWEL SIGN AI*/
+ RTUNI_ALPHA, /* U+016f7a: MIAO VOWEL SIGN EI*/
+ RTUNI_ALPHA, /* U+016f7b: MIAO VOWEL SIGN AU*/
+ RTUNI_ALPHA, /* U+016f7c: MIAO VOWEL SIGN OU*/
+ RTUNI_ALPHA, /* U+016f7d: MIAO VOWEL SIGN N*/
+ RTUNI_ALPHA, /* U+016f7e: MIAO VOWEL SIGN NG*/
+ 0, /* U+016f7f: */
+ 0, /* U+016f80: */
+ 0, /* U+016f81: */
+ 0, /* U+016f82: */
+ 0, /* U+016f83: */
+ 0, /* U+016f84: */
+ 0, /* U+016f85: */
+ 0, /* U+016f86: */
+ 0, /* U+016f87: */
+ 0, /* U+016f88: */
+ 0, /* U+016f89: */
+ 0, /* U+016f8a: */
+ 0, /* U+016f8b: */
+ 0, /* U+016f8c: */
+ 0, /* U+016f8d: */
+ 0, /* U+016f8e: */
+ 0, /* U+016f8f: MIAO TONE RIGHT*/
+ 0, /* U+016f90: MIAO TONE TOP RIGHT*/
+ 0, /* U+016f91: MIAO TONE ABOVE*/
+ 0, /* U+016f92: MIAO TONE BELOW*/
+ RTUNI_ALPHA, /* U+016f93: MIAO LETTER TONE-2*/
+ RTUNI_ALPHA, /* U+016f94: MIAO LETTER TONE-3*/
+ RTUNI_ALPHA, /* U+016f95: MIAO LETTER TONE-4*/
+ RTUNI_ALPHA, /* U+016f96: MIAO LETTER TONE-5*/
+ RTUNI_ALPHA, /* U+016f97: MIAO LETTER TONE-6*/
+ RTUNI_ALPHA, /* U+016f98: MIAO LETTER TONE-7*/
+ RTUNI_ALPHA, /* U+016f99: MIAO LETTER TONE-8*/
+ RTUNI_ALPHA, /* U+016f9a: MIAO LETTER REFORMED TONE-1*/
+ RTUNI_ALPHA, /* U+016f9b: MIAO LETTER REFORMED TONE-2*/
+ RTUNI_ALPHA, /* U+016f9c: MIAO LETTER REFORMED TONE-4*/
+ RTUNI_ALPHA, /* U+016f9d: MIAO LETTER REFORMED TONE-5*/
+ RTUNI_ALPHA, /* U+016f9e: MIAO LETTER REFORMED TONE-6*/
+ RTUNI_ALPHA, /* U+016f9f: MIAO LETTER REFORMED TONE-8*/
+};
+
+static const uint8_t g_afRTUniFlags0x01b000[] =
+{
+ RTUNI_ALPHA, /* U+01b000: KATAKANA LETTER ARCHAIC E*/
+ RTUNI_ALPHA, /* U+01b001: HIRAGANA LETTER ARCHAIC YE*/
+};
+
+static const uint8_t g_afRTUniFlags0x01d400[] =
+{
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d400: MATHEMATICAL BOLD CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d401: MATHEMATICAL BOLD CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d402: MATHEMATICAL BOLD CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d403: MATHEMATICAL BOLD CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d404: MATHEMATICAL BOLD CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d405: MATHEMATICAL BOLD CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d406: MATHEMATICAL BOLD CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d407: MATHEMATICAL BOLD CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d408: MATHEMATICAL BOLD CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d409: MATHEMATICAL BOLD CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d40a: MATHEMATICAL BOLD CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d40b: MATHEMATICAL BOLD CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d40c: MATHEMATICAL BOLD CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d40d: MATHEMATICAL BOLD CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d40e: MATHEMATICAL BOLD CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d40f: MATHEMATICAL BOLD CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d410: MATHEMATICAL BOLD CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d411: MATHEMATICAL BOLD CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d412: MATHEMATICAL BOLD CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d413: MATHEMATICAL BOLD CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d414: MATHEMATICAL BOLD CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d415: MATHEMATICAL BOLD CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d416: MATHEMATICAL BOLD CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d417: MATHEMATICAL BOLD CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d418: MATHEMATICAL BOLD CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d419: MATHEMATICAL BOLD CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d41a: MATHEMATICAL BOLD SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d41b: MATHEMATICAL BOLD SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d41c: MATHEMATICAL BOLD SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d41d: MATHEMATICAL BOLD SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d41e: MATHEMATICAL BOLD SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d41f: MATHEMATICAL BOLD SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d420: MATHEMATICAL BOLD SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d421: MATHEMATICAL BOLD SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d422: MATHEMATICAL BOLD SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d423: MATHEMATICAL BOLD SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d424: MATHEMATICAL BOLD SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d425: MATHEMATICAL BOLD SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d426: MATHEMATICAL BOLD SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d427: MATHEMATICAL BOLD SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d428: MATHEMATICAL BOLD SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d429: MATHEMATICAL BOLD SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d42a: MATHEMATICAL BOLD SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d42b: MATHEMATICAL BOLD SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d42c: MATHEMATICAL BOLD SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d42d: MATHEMATICAL BOLD SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d42e: MATHEMATICAL BOLD SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d42f: MATHEMATICAL BOLD SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d430: MATHEMATICAL BOLD SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d431: MATHEMATICAL BOLD SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d432: MATHEMATICAL BOLD SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d433: MATHEMATICAL BOLD SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d434: MATHEMATICAL ITALIC CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d435: MATHEMATICAL ITALIC CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d436: MATHEMATICAL ITALIC CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d437: MATHEMATICAL ITALIC CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d438: MATHEMATICAL ITALIC CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d439: MATHEMATICAL ITALIC CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d43a: MATHEMATICAL ITALIC CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d43b: MATHEMATICAL ITALIC CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d43c: MATHEMATICAL ITALIC CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d43d: MATHEMATICAL ITALIC CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d43e: MATHEMATICAL ITALIC CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d43f: MATHEMATICAL ITALIC CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d440: MATHEMATICAL ITALIC CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d441: MATHEMATICAL ITALIC CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d442: MATHEMATICAL ITALIC CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d443: MATHEMATICAL ITALIC CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d444: MATHEMATICAL ITALIC CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d445: MATHEMATICAL ITALIC CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d446: MATHEMATICAL ITALIC CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d447: MATHEMATICAL ITALIC CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d448: MATHEMATICAL ITALIC CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d449: MATHEMATICAL ITALIC CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d44a: MATHEMATICAL ITALIC CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d44b: MATHEMATICAL ITALIC CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d44c: MATHEMATICAL ITALIC CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d44d: MATHEMATICAL ITALIC CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d44e: MATHEMATICAL ITALIC SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d44f: MATHEMATICAL ITALIC SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d450: MATHEMATICAL ITALIC SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d451: MATHEMATICAL ITALIC SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d452: MATHEMATICAL ITALIC SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d453: MATHEMATICAL ITALIC SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d454: MATHEMATICAL ITALIC SMALL G*/
+ 0, /* U+01d455: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d456: MATHEMATICAL ITALIC SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d457: MATHEMATICAL ITALIC SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d458: MATHEMATICAL ITALIC SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d459: MATHEMATICAL ITALIC SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d45a: MATHEMATICAL ITALIC SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d45b: MATHEMATICAL ITALIC SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d45c: MATHEMATICAL ITALIC SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d45d: MATHEMATICAL ITALIC SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d45e: MATHEMATICAL ITALIC SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d45f: MATHEMATICAL ITALIC SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d460: MATHEMATICAL ITALIC SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d461: MATHEMATICAL ITALIC SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d462: MATHEMATICAL ITALIC SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d463: MATHEMATICAL ITALIC SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d464: MATHEMATICAL ITALIC SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d465: MATHEMATICAL ITALIC SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d466: MATHEMATICAL ITALIC SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d467: MATHEMATICAL ITALIC SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d468: MATHEMATICAL BOLD ITALIC CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d469: MATHEMATICAL BOLD ITALIC CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d46a: MATHEMATICAL BOLD ITALIC CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d46b: MATHEMATICAL BOLD ITALIC CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d46c: MATHEMATICAL BOLD ITALIC CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d46d: MATHEMATICAL BOLD ITALIC CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d46e: MATHEMATICAL BOLD ITALIC CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d46f: MATHEMATICAL BOLD ITALIC CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d470: MATHEMATICAL BOLD ITALIC CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d471: MATHEMATICAL BOLD ITALIC CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d472: MATHEMATICAL BOLD ITALIC CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d473: MATHEMATICAL BOLD ITALIC CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d474: MATHEMATICAL BOLD ITALIC CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d475: MATHEMATICAL BOLD ITALIC CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d476: MATHEMATICAL BOLD ITALIC CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d477: MATHEMATICAL BOLD ITALIC CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d478: MATHEMATICAL BOLD ITALIC CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d479: MATHEMATICAL BOLD ITALIC CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d47a: MATHEMATICAL BOLD ITALIC CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d47b: MATHEMATICAL BOLD ITALIC CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d47c: MATHEMATICAL BOLD ITALIC CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d47d: MATHEMATICAL BOLD ITALIC CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d47e: MATHEMATICAL BOLD ITALIC CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d47f: MATHEMATICAL BOLD ITALIC CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d480: MATHEMATICAL BOLD ITALIC CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d481: MATHEMATICAL BOLD ITALIC CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d482: MATHEMATICAL BOLD ITALIC SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d483: MATHEMATICAL BOLD ITALIC SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d484: MATHEMATICAL BOLD ITALIC SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d485: MATHEMATICAL BOLD ITALIC SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d486: MATHEMATICAL BOLD ITALIC SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d487: MATHEMATICAL BOLD ITALIC SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d488: MATHEMATICAL BOLD ITALIC SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d489: MATHEMATICAL BOLD ITALIC SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d48a: MATHEMATICAL BOLD ITALIC SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d48b: MATHEMATICAL BOLD ITALIC SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d48c: MATHEMATICAL BOLD ITALIC SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d48d: MATHEMATICAL BOLD ITALIC SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d48e: MATHEMATICAL BOLD ITALIC SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d48f: MATHEMATICAL BOLD ITALIC SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d490: MATHEMATICAL BOLD ITALIC SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d491: MATHEMATICAL BOLD ITALIC SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d492: MATHEMATICAL BOLD ITALIC SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d493: MATHEMATICAL BOLD ITALIC SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d494: MATHEMATICAL BOLD ITALIC SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d495: MATHEMATICAL BOLD ITALIC SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d496: MATHEMATICAL BOLD ITALIC SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d497: MATHEMATICAL BOLD ITALIC SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d498: MATHEMATICAL BOLD ITALIC SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d499: MATHEMATICAL BOLD ITALIC SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d49a: MATHEMATICAL BOLD ITALIC SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d49b: MATHEMATICAL BOLD ITALIC SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d49c: MATHEMATICAL SCRIPT CAPITAL A*/
+ 0, /* U+01d49d: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d49e: MATHEMATICAL SCRIPT CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d49f: MATHEMATICAL SCRIPT CAPITAL D*/
+ 0, /* U+01d4a0: */
+ 0, /* U+01d4a1: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4a2: MATHEMATICAL SCRIPT CAPITAL G*/
+ 0, /* U+01d4a3: */
+ 0, /* U+01d4a4: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4a5: MATHEMATICAL SCRIPT CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4a6: MATHEMATICAL SCRIPT CAPITAL K*/
+ 0, /* U+01d4a7: */
+ 0, /* U+01d4a8: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4a9: MATHEMATICAL SCRIPT CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4aa: MATHEMATICAL SCRIPT CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4ab: MATHEMATICAL SCRIPT CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4ac: MATHEMATICAL SCRIPT CAPITAL Q*/
+ 0, /* U+01d4ad: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4ae: MATHEMATICAL SCRIPT CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4af: MATHEMATICAL SCRIPT CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4b0: MATHEMATICAL SCRIPT CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4b1: MATHEMATICAL SCRIPT CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4b2: MATHEMATICAL SCRIPT CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4b3: MATHEMATICAL SCRIPT CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4b4: MATHEMATICAL SCRIPT CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4b5: MATHEMATICAL SCRIPT CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4b6: MATHEMATICAL SCRIPT SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4b7: MATHEMATICAL SCRIPT SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4b8: MATHEMATICAL SCRIPT SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4b9: MATHEMATICAL SCRIPT SMALL D*/
+ 0, /* U+01d4ba: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4bb: MATHEMATICAL SCRIPT SMALL F*/
+ 0, /* U+01d4bc: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4bd: MATHEMATICAL SCRIPT SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4be: MATHEMATICAL SCRIPT SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4bf: MATHEMATICAL SCRIPT SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4c0: MATHEMATICAL SCRIPT SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4c1: MATHEMATICAL SCRIPT SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4c2: MATHEMATICAL SCRIPT SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4c3: MATHEMATICAL SCRIPT SMALL N*/
+ 0, /* U+01d4c4: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4c5: MATHEMATICAL SCRIPT SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4c6: MATHEMATICAL SCRIPT SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4c7: MATHEMATICAL SCRIPT SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4c8: MATHEMATICAL SCRIPT SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4c9: MATHEMATICAL SCRIPT SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4ca: MATHEMATICAL SCRIPT SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4cb: MATHEMATICAL SCRIPT SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4cc: MATHEMATICAL SCRIPT SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4cd: MATHEMATICAL SCRIPT SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4ce: MATHEMATICAL SCRIPT SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4cf: MATHEMATICAL SCRIPT SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d0: MATHEMATICAL BOLD SCRIPT CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d1: MATHEMATICAL BOLD SCRIPT CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d2: MATHEMATICAL BOLD SCRIPT CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d3: MATHEMATICAL BOLD SCRIPT CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d4: MATHEMATICAL BOLD SCRIPT CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d5: MATHEMATICAL BOLD SCRIPT CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d6: MATHEMATICAL BOLD SCRIPT CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d7: MATHEMATICAL BOLD SCRIPT CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d8: MATHEMATICAL BOLD SCRIPT CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4d9: MATHEMATICAL BOLD SCRIPT CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4da: MATHEMATICAL BOLD SCRIPT CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4db: MATHEMATICAL BOLD SCRIPT CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4dc: MATHEMATICAL BOLD SCRIPT CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4dd: MATHEMATICAL BOLD SCRIPT CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4de: MATHEMATICAL BOLD SCRIPT CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4df: MATHEMATICAL BOLD SCRIPT CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e0: MATHEMATICAL BOLD SCRIPT CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e1: MATHEMATICAL BOLD SCRIPT CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e2: MATHEMATICAL BOLD SCRIPT CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e3: MATHEMATICAL BOLD SCRIPT CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e4: MATHEMATICAL BOLD SCRIPT CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e5: MATHEMATICAL BOLD SCRIPT CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e6: MATHEMATICAL BOLD SCRIPT CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e7: MATHEMATICAL BOLD SCRIPT CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e8: MATHEMATICAL BOLD SCRIPT CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d4e9: MATHEMATICAL BOLD SCRIPT CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4ea: MATHEMATICAL BOLD SCRIPT SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4eb: MATHEMATICAL BOLD SCRIPT SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4ec: MATHEMATICAL BOLD SCRIPT SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4ed: MATHEMATICAL BOLD SCRIPT SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4ee: MATHEMATICAL BOLD SCRIPT SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4ef: MATHEMATICAL BOLD SCRIPT SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f0: MATHEMATICAL BOLD SCRIPT SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f1: MATHEMATICAL BOLD SCRIPT SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f2: MATHEMATICAL BOLD SCRIPT SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f3: MATHEMATICAL BOLD SCRIPT SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f4: MATHEMATICAL BOLD SCRIPT SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f5: MATHEMATICAL BOLD SCRIPT SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f6: MATHEMATICAL BOLD SCRIPT SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f7: MATHEMATICAL BOLD SCRIPT SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f8: MATHEMATICAL BOLD SCRIPT SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4f9: MATHEMATICAL BOLD SCRIPT SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4fa: MATHEMATICAL BOLD SCRIPT SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4fb: MATHEMATICAL BOLD SCRIPT SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4fc: MATHEMATICAL BOLD SCRIPT SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4fd: MATHEMATICAL BOLD SCRIPT SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4fe: MATHEMATICAL BOLD SCRIPT SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d4ff: MATHEMATICAL BOLD SCRIPT SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d500: MATHEMATICAL BOLD SCRIPT SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d501: MATHEMATICAL BOLD SCRIPT SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d502: MATHEMATICAL BOLD SCRIPT SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d503: MATHEMATICAL BOLD SCRIPT SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d504: MATHEMATICAL FRAKTUR CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d505: MATHEMATICAL FRAKTUR CAPITAL B*/
+ 0, /* U+01d506: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d507: MATHEMATICAL FRAKTUR CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d508: MATHEMATICAL FRAKTUR CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d509: MATHEMATICAL FRAKTUR CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d50a: MATHEMATICAL FRAKTUR CAPITAL G*/
+ 0, /* U+01d50b: */
+ 0, /* U+01d50c: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d50d: MATHEMATICAL FRAKTUR CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d50e: MATHEMATICAL FRAKTUR CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d50f: MATHEMATICAL FRAKTUR CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d510: MATHEMATICAL FRAKTUR CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d511: MATHEMATICAL FRAKTUR CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d512: MATHEMATICAL FRAKTUR CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d513: MATHEMATICAL FRAKTUR CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d514: MATHEMATICAL FRAKTUR CAPITAL Q*/
+ 0, /* U+01d515: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d516: MATHEMATICAL FRAKTUR CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d517: MATHEMATICAL FRAKTUR CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d518: MATHEMATICAL FRAKTUR CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d519: MATHEMATICAL FRAKTUR CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d51a: MATHEMATICAL FRAKTUR CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d51b: MATHEMATICAL FRAKTUR CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d51c: MATHEMATICAL FRAKTUR CAPITAL Y*/
+ 0, /* U+01d51d: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d51e: MATHEMATICAL FRAKTUR SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d51f: MATHEMATICAL FRAKTUR SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d520: MATHEMATICAL FRAKTUR SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d521: MATHEMATICAL FRAKTUR SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d522: MATHEMATICAL FRAKTUR SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d523: MATHEMATICAL FRAKTUR SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d524: MATHEMATICAL FRAKTUR SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d525: MATHEMATICAL FRAKTUR SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d526: MATHEMATICAL FRAKTUR SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d527: MATHEMATICAL FRAKTUR SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d528: MATHEMATICAL FRAKTUR SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d529: MATHEMATICAL FRAKTUR SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d52a: MATHEMATICAL FRAKTUR SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d52b: MATHEMATICAL FRAKTUR SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d52c: MATHEMATICAL FRAKTUR SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d52d: MATHEMATICAL FRAKTUR SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d52e: MATHEMATICAL FRAKTUR SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d52f: MATHEMATICAL FRAKTUR SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d530: MATHEMATICAL FRAKTUR SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d531: MATHEMATICAL FRAKTUR SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d532: MATHEMATICAL FRAKTUR SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d533: MATHEMATICAL FRAKTUR SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d534: MATHEMATICAL FRAKTUR SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d535: MATHEMATICAL FRAKTUR SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d536: MATHEMATICAL FRAKTUR SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d537: MATHEMATICAL FRAKTUR SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d538: MATHEMATICAL DOUBLE-STRUCK CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d539: MATHEMATICAL DOUBLE-STRUCK CAPITAL B*/
+ 0, /* U+01d53a: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d53b: MATHEMATICAL DOUBLE-STRUCK CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d53c: MATHEMATICAL DOUBLE-STRUCK CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d53d: MATHEMATICAL DOUBLE-STRUCK CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d53e: MATHEMATICAL DOUBLE-STRUCK CAPITAL G*/
+ 0, /* U+01d53f: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d540: MATHEMATICAL DOUBLE-STRUCK CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d541: MATHEMATICAL DOUBLE-STRUCK CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d542: MATHEMATICAL DOUBLE-STRUCK CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d543: MATHEMATICAL DOUBLE-STRUCK CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d544: MATHEMATICAL DOUBLE-STRUCK CAPITAL M*/
+ 0, /* U+01d545: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d546: MATHEMATICAL DOUBLE-STRUCK CAPITAL O*/
+ 0, /* U+01d547: */
+ 0, /* U+01d548: */
+ 0, /* U+01d549: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d54a: MATHEMATICAL DOUBLE-STRUCK CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d54b: MATHEMATICAL DOUBLE-STRUCK CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d54c: MATHEMATICAL DOUBLE-STRUCK CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d54d: MATHEMATICAL DOUBLE-STRUCK CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d54e: MATHEMATICAL DOUBLE-STRUCK CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d54f: MATHEMATICAL DOUBLE-STRUCK CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d550: MATHEMATICAL DOUBLE-STRUCK CAPITAL Y*/
+ 0, /* U+01d551: */
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d552: MATHEMATICAL DOUBLE-STRUCK SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d553: MATHEMATICAL DOUBLE-STRUCK SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d554: MATHEMATICAL DOUBLE-STRUCK SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d555: MATHEMATICAL DOUBLE-STRUCK SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d556: MATHEMATICAL DOUBLE-STRUCK SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d557: MATHEMATICAL DOUBLE-STRUCK SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d558: MATHEMATICAL DOUBLE-STRUCK SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d559: MATHEMATICAL DOUBLE-STRUCK SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d55a: MATHEMATICAL DOUBLE-STRUCK SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d55b: MATHEMATICAL DOUBLE-STRUCK SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d55c: MATHEMATICAL DOUBLE-STRUCK SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d55d: MATHEMATICAL DOUBLE-STRUCK SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d55e: MATHEMATICAL DOUBLE-STRUCK SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d55f: MATHEMATICAL DOUBLE-STRUCK SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d560: MATHEMATICAL DOUBLE-STRUCK SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d561: MATHEMATICAL DOUBLE-STRUCK SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d562: MATHEMATICAL DOUBLE-STRUCK SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d563: MATHEMATICAL DOUBLE-STRUCK SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d564: MATHEMATICAL DOUBLE-STRUCK SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d565: MATHEMATICAL DOUBLE-STRUCK SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d566: MATHEMATICAL DOUBLE-STRUCK SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d567: MATHEMATICAL DOUBLE-STRUCK SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d568: MATHEMATICAL DOUBLE-STRUCK SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d569: MATHEMATICAL DOUBLE-STRUCK SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d56a: MATHEMATICAL DOUBLE-STRUCK SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d56b: MATHEMATICAL DOUBLE-STRUCK SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d56c: MATHEMATICAL BOLD FRAKTUR CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d56d: MATHEMATICAL BOLD FRAKTUR CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d56e: MATHEMATICAL BOLD FRAKTUR CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d56f: MATHEMATICAL BOLD FRAKTUR CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d570: MATHEMATICAL BOLD FRAKTUR CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d571: MATHEMATICAL BOLD FRAKTUR CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d572: MATHEMATICAL BOLD FRAKTUR CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d573: MATHEMATICAL BOLD FRAKTUR CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d574: MATHEMATICAL BOLD FRAKTUR CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d575: MATHEMATICAL BOLD FRAKTUR CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d576: MATHEMATICAL BOLD FRAKTUR CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d577: MATHEMATICAL BOLD FRAKTUR CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d578: MATHEMATICAL BOLD FRAKTUR CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d579: MATHEMATICAL BOLD FRAKTUR CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d57a: MATHEMATICAL BOLD FRAKTUR CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d57b: MATHEMATICAL BOLD FRAKTUR CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d57c: MATHEMATICAL BOLD FRAKTUR CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d57d: MATHEMATICAL BOLD FRAKTUR CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d57e: MATHEMATICAL BOLD FRAKTUR CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d57f: MATHEMATICAL BOLD FRAKTUR CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d580: MATHEMATICAL BOLD FRAKTUR CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d581: MATHEMATICAL BOLD FRAKTUR CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d582: MATHEMATICAL BOLD FRAKTUR CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d583: MATHEMATICAL BOLD FRAKTUR CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d584: MATHEMATICAL BOLD FRAKTUR CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d585: MATHEMATICAL BOLD FRAKTUR CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d586: MATHEMATICAL BOLD FRAKTUR SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d587: MATHEMATICAL BOLD FRAKTUR SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d588: MATHEMATICAL BOLD FRAKTUR SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d589: MATHEMATICAL BOLD FRAKTUR SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d58a: MATHEMATICAL BOLD FRAKTUR SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d58b: MATHEMATICAL BOLD FRAKTUR SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d58c: MATHEMATICAL BOLD FRAKTUR SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d58d: MATHEMATICAL BOLD FRAKTUR SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d58e: MATHEMATICAL BOLD FRAKTUR SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d58f: MATHEMATICAL BOLD FRAKTUR SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d590: MATHEMATICAL BOLD FRAKTUR SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d591: MATHEMATICAL BOLD FRAKTUR SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d592: MATHEMATICAL BOLD FRAKTUR SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d593: MATHEMATICAL BOLD FRAKTUR SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d594: MATHEMATICAL BOLD FRAKTUR SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d595: MATHEMATICAL BOLD FRAKTUR SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d596: MATHEMATICAL BOLD FRAKTUR SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d597: MATHEMATICAL BOLD FRAKTUR SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d598: MATHEMATICAL BOLD FRAKTUR SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d599: MATHEMATICAL BOLD FRAKTUR SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d59a: MATHEMATICAL BOLD FRAKTUR SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d59b: MATHEMATICAL BOLD FRAKTUR SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d59c: MATHEMATICAL BOLD FRAKTUR SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d59d: MATHEMATICAL BOLD FRAKTUR SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d59e: MATHEMATICAL BOLD FRAKTUR SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d59f: MATHEMATICAL BOLD FRAKTUR SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a0: MATHEMATICAL SANS-SERIF CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a1: MATHEMATICAL SANS-SERIF CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a2: MATHEMATICAL SANS-SERIF CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a3: MATHEMATICAL SANS-SERIF CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a4: MATHEMATICAL SANS-SERIF CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a5: MATHEMATICAL SANS-SERIF CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a6: MATHEMATICAL SANS-SERIF CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a7: MATHEMATICAL SANS-SERIF CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a8: MATHEMATICAL SANS-SERIF CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5a9: MATHEMATICAL SANS-SERIF CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5aa: MATHEMATICAL SANS-SERIF CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5ab: MATHEMATICAL SANS-SERIF CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5ac: MATHEMATICAL SANS-SERIF CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5ad: MATHEMATICAL SANS-SERIF CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5ae: MATHEMATICAL SANS-SERIF CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5af: MATHEMATICAL SANS-SERIF CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b0: MATHEMATICAL SANS-SERIF CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b1: MATHEMATICAL SANS-SERIF CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b2: MATHEMATICAL SANS-SERIF CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b3: MATHEMATICAL SANS-SERIF CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b4: MATHEMATICAL SANS-SERIF CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b5: MATHEMATICAL SANS-SERIF CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b6: MATHEMATICAL SANS-SERIF CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b7: MATHEMATICAL SANS-SERIF CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b8: MATHEMATICAL SANS-SERIF CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5b9: MATHEMATICAL SANS-SERIF CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5ba: MATHEMATICAL SANS-SERIF SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5bb: MATHEMATICAL SANS-SERIF SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5bc: MATHEMATICAL SANS-SERIF SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5bd: MATHEMATICAL SANS-SERIF SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5be: MATHEMATICAL SANS-SERIF SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5bf: MATHEMATICAL SANS-SERIF SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c0: MATHEMATICAL SANS-SERIF SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c1: MATHEMATICAL SANS-SERIF SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c2: MATHEMATICAL SANS-SERIF SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c3: MATHEMATICAL SANS-SERIF SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c4: MATHEMATICAL SANS-SERIF SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c5: MATHEMATICAL SANS-SERIF SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c6: MATHEMATICAL SANS-SERIF SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c7: MATHEMATICAL SANS-SERIF SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c8: MATHEMATICAL SANS-SERIF SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5c9: MATHEMATICAL SANS-SERIF SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5ca: MATHEMATICAL SANS-SERIF SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5cb: MATHEMATICAL SANS-SERIF SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5cc: MATHEMATICAL SANS-SERIF SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5cd: MATHEMATICAL SANS-SERIF SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5ce: MATHEMATICAL SANS-SERIF SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5cf: MATHEMATICAL SANS-SERIF SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5d0: MATHEMATICAL SANS-SERIF SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5d1: MATHEMATICAL SANS-SERIF SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5d2: MATHEMATICAL SANS-SERIF SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5d3: MATHEMATICAL SANS-SERIF SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5d4: MATHEMATICAL SANS-SERIF BOLD CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5d5: MATHEMATICAL SANS-SERIF BOLD CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5d6: MATHEMATICAL SANS-SERIF BOLD CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5d7: MATHEMATICAL SANS-SERIF BOLD CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5d8: MATHEMATICAL SANS-SERIF BOLD CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5d9: MATHEMATICAL SANS-SERIF BOLD CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5da: MATHEMATICAL SANS-SERIF BOLD CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5db: MATHEMATICAL SANS-SERIF BOLD CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5dc: MATHEMATICAL SANS-SERIF BOLD CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5dd: MATHEMATICAL SANS-SERIF BOLD CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5de: MATHEMATICAL SANS-SERIF BOLD CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5df: MATHEMATICAL SANS-SERIF BOLD CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e0: MATHEMATICAL SANS-SERIF BOLD CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e1: MATHEMATICAL SANS-SERIF BOLD CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e2: MATHEMATICAL SANS-SERIF BOLD CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e3: MATHEMATICAL SANS-SERIF BOLD CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e4: MATHEMATICAL SANS-SERIF BOLD CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e5: MATHEMATICAL SANS-SERIF BOLD CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e6: MATHEMATICAL SANS-SERIF BOLD CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e7: MATHEMATICAL SANS-SERIF BOLD CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e8: MATHEMATICAL SANS-SERIF BOLD CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5e9: MATHEMATICAL SANS-SERIF BOLD CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5ea: MATHEMATICAL SANS-SERIF BOLD CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5eb: MATHEMATICAL SANS-SERIF BOLD CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5ec: MATHEMATICAL SANS-SERIF BOLD CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d5ed: MATHEMATICAL SANS-SERIF BOLD CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5ee: MATHEMATICAL SANS-SERIF BOLD SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5ef: MATHEMATICAL SANS-SERIF BOLD SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f0: MATHEMATICAL SANS-SERIF BOLD SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f1: MATHEMATICAL SANS-SERIF BOLD SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f2: MATHEMATICAL SANS-SERIF BOLD SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f3: MATHEMATICAL SANS-SERIF BOLD SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f4: MATHEMATICAL SANS-SERIF BOLD SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f5: MATHEMATICAL SANS-SERIF BOLD SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f6: MATHEMATICAL SANS-SERIF BOLD SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f7: MATHEMATICAL SANS-SERIF BOLD SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f8: MATHEMATICAL SANS-SERIF BOLD SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5f9: MATHEMATICAL SANS-SERIF BOLD SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5fa: MATHEMATICAL SANS-SERIF BOLD SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5fb: MATHEMATICAL SANS-SERIF BOLD SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5fc: MATHEMATICAL SANS-SERIF BOLD SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5fd: MATHEMATICAL SANS-SERIF BOLD SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5fe: MATHEMATICAL SANS-SERIF BOLD SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d5ff: MATHEMATICAL SANS-SERIF BOLD SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d600: MATHEMATICAL SANS-SERIF BOLD SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d601: MATHEMATICAL SANS-SERIF BOLD SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d602: MATHEMATICAL SANS-SERIF BOLD SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d603: MATHEMATICAL SANS-SERIF BOLD SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d604: MATHEMATICAL SANS-SERIF BOLD SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d605: MATHEMATICAL SANS-SERIF BOLD SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d606: MATHEMATICAL SANS-SERIF BOLD SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d607: MATHEMATICAL SANS-SERIF BOLD SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d608: MATHEMATICAL SANS-SERIF ITALIC CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d609: MATHEMATICAL SANS-SERIF ITALIC CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d60a: MATHEMATICAL SANS-SERIF ITALIC CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d60b: MATHEMATICAL SANS-SERIF ITALIC CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d60c: MATHEMATICAL SANS-SERIF ITALIC CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d60d: MATHEMATICAL SANS-SERIF ITALIC CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d60e: MATHEMATICAL SANS-SERIF ITALIC CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d60f: MATHEMATICAL SANS-SERIF ITALIC CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d610: MATHEMATICAL SANS-SERIF ITALIC CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d611: MATHEMATICAL SANS-SERIF ITALIC CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d612: MATHEMATICAL SANS-SERIF ITALIC CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d613: MATHEMATICAL SANS-SERIF ITALIC CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d614: MATHEMATICAL SANS-SERIF ITALIC CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d615: MATHEMATICAL SANS-SERIF ITALIC CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d616: MATHEMATICAL SANS-SERIF ITALIC CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d617: MATHEMATICAL SANS-SERIF ITALIC CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d618: MATHEMATICAL SANS-SERIF ITALIC CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d619: MATHEMATICAL SANS-SERIF ITALIC CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d61a: MATHEMATICAL SANS-SERIF ITALIC CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d61b: MATHEMATICAL SANS-SERIF ITALIC CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d61c: MATHEMATICAL SANS-SERIF ITALIC CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d61d: MATHEMATICAL SANS-SERIF ITALIC CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d61e: MATHEMATICAL SANS-SERIF ITALIC CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d61f: MATHEMATICAL SANS-SERIF ITALIC CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d620: MATHEMATICAL SANS-SERIF ITALIC CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d621: MATHEMATICAL SANS-SERIF ITALIC CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d622: MATHEMATICAL SANS-SERIF ITALIC SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d623: MATHEMATICAL SANS-SERIF ITALIC SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d624: MATHEMATICAL SANS-SERIF ITALIC SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d625: MATHEMATICAL SANS-SERIF ITALIC SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d626: MATHEMATICAL SANS-SERIF ITALIC SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d627: MATHEMATICAL SANS-SERIF ITALIC SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d628: MATHEMATICAL SANS-SERIF ITALIC SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d629: MATHEMATICAL SANS-SERIF ITALIC SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d62a: MATHEMATICAL SANS-SERIF ITALIC SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d62b: MATHEMATICAL SANS-SERIF ITALIC SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d62c: MATHEMATICAL SANS-SERIF ITALIC SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d62d: MATHEMATICAL SANS-SERIF ITALIC SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d62e: MATHEMATICAL SANS-SERIF ITALIC SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d62f: MATHEMATICAL SANS-SERIF ITALIC SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d630: MATHEMATICAL SANS-SERIF ITALIC SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d631: MATHEMATICAL SANS-SERIF ITALIC SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d632: MATHEMATICAL SANS-SERIF ITALIC SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d633: MATHEMATICAL SANS-SERIF ITALIC SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d634: MATHEMATICAL SANS-SERIF ITALIC SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d635: MATHEMATICAL SANS-SERIF ITALIC SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d636: MATHEMATICAL SANS-SERIF ITALIC SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d637: MATHEMATICAL SANS-SERIF ITALIC SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d638: MATHEMATICAL SANS-SERIF ITALIC SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d639: MATHEMATICAL SANS-SERIF ITALIC SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d63a: MATHEMATICAL SANS-SERIF ITALIC SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d63b: MATHEMATICAL SANS-SERIF ITALIC SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d63c: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d63d: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d63e: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d63f: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d640: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d641: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d642: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d643: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d644: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d645: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d646: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d647: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d648: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d649: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d64a: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d64b: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d64c: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d64d: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d64e: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d64f: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d650: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d651: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d652: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d653: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d654: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d655: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d656: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d657: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d658: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d659: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d65a: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d65b: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d65c: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d65d: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d65e: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d65f: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d660: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d661: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d662: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d663: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d664: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d665: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d666: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d667: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d668: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d669: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d66a: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d66b: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d66c: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d66d: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d66e: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d66f: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Z*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d670: MATHEMATICAL MONOSPACE CAPITAL A*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d671: MATHEMATICAL MONOSPACE CAPITAL B*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d672: MATHEMATICAL MONOSPACE CAPITAL C*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d673: MATHEMATICAL MONOSPACE CAPITAL D*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d674: MATHEMATICAL MONOSPACE CAPITAL E*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d675: MATHEMATICAL MONOSPACE CAPITAL F*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d676: MATHEMATICAL MONOSPACE CAPITAL G*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d677: MATHEMATICAL MONOSPACE CAPITAL H*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d678: MATHEMATICAL MONOSPACE CAPITAL I*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d679: MATHEMATICAL MONOSPACE CAPITAL J*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d67a: MATHEMATICAL MONOSPACE CAPITAL K*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d67b: MATHEMATICAL MONOSPACE CAPITAL L*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d67c: MATHEMATICAL MONOSPACE CAPITAL M*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d67d: MATHEMATICAL MONOSPACE CAPITAL N*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d67e: MATHEMATICAL MONOSPACE CAPITAL O*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d67f: MATHEMATICAL MONOSPACE CAPITAL P*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d680: MATHEMATICAL MONOSPACE CAPITAL Q*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d681: MATHEMATICAL MONOSPACE CAPITAL R*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d682: MATHEMATICAL MONOSPACE CAPITAL S*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d683: MATHEMATICAL MONOSPACE CAPITAL T*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d684: MATHEMATICAL MONOSPACE CAPITAL U*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d685: MATHEMATICAL MONOSPACE CAPITAL V*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d686: MATHEMATICAL MONOSPACE CAPITAL W*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d687: MATHEMATICAL MONOSPACE CAPITAL X*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d688: MATHEMATICAL MONOSPACE CAPITAL Y*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d689: MATHEMATICAL MONOSPACE CAPITAL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d68a: MATHEMATICAL MONOSPACE SMALL A*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d68b: MATHEMATICAL MONOSPACE SMALL B*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d68c: MATHEMATICAL MONOSPACE SMALL C*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d68d: MATHEMATICAL MONOSPACE SMALL D*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d68e: MATHEMATICAL MONOSPACE SMALL E*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d68f: MATHEMATICAL MONOSPACE SMALL F*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d690: MATHEMATICAL MONOSPACE SMALL G*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d691: MATHEMATICAL MONOSPACE SMALL H*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d692: MATHEMATICAL MONOSPACE SMALL I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d693: MATHEMATICAL MONOSPACE SMALL J*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d694: MATHEMATICAL MONOSPACE SMALL K*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d695: MATHEMATICAL MONOSPACE SMALL L*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d696: MATHEMATICAL MONOSPACE SMALL M*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d697: MATHEMATICAL MONOSPACE SMALL N*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d698: MATHEMATICAL MONOSPACE SMALL O*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d699: MATHEMATICAL MONOSPACE SMALL P*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d69a: MATHEMATICAL MONOSPACE SMALL Q*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d69b: MATHEMATICAL MONOSPACE SMALL R*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d69c: MATHEMATICAL MONOSPACE SMALL S*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d69d: MATHEMATICAL MONOSPACE SMALL T*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d69e: MATHEMATICAL MONOSPACE SMALL U*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d69f: MATHEMATICAL MONOSPACE SMALL V*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6a0: MATHEMATICAL MONOSPACE SMALL W*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6a1: MATHEMATICAL MONOSPACE SMALL X*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6a2: MATHEMATICAL MONOSPACE SMALL Y*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6a3: MATHEMATICAL MONOSPACE SMALL Z*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6a4: MATHEMATICAL ITALIC SMALL DOTLESS I*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6a5: MATHEMATICAL ITALIC SMALL DOTLESS J*/
+ 0, /* U+01d6a6: */
+ 0, /* U+01d6a7: */
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6a8: MATHEMATICAL BOLD CAPITAL ALPHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6a9: MATHEMATICAL BOLD CAPITAL BETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6aa: MATHEMATICAL BOLD CAPITAL GAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ab: MATHEMATICAL BOLD CAPITAL DELTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ac: MATHEMATICAL BOLD CAPITAL EPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ad: MATHEMATICAL BOLD CAPITAL ZETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ae: MATHEMATICAL BOLD CAPITAL ETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6af: MATHEMATICAL BOLD CAPITAL THETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b0: MATHEMATICAL BOLD CAPITAL IOTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b1: MATHEMATICAL BOLD CAPITAL KAPPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b2: MATHEMATICAL BOLD CAPITAL LAMDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b3: MATHEMATICAL BOLD CAPITAL MU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b4: MATHEMATICAL BOLD CAPITAL NU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b5: MATHEMATICAL BOLD CAPITAL XI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b6: MATHEMATICAL BOLD CAPITAL OMICRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b7: MATHEMATICAL BOLD CAPITAL PI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b8: MATHEMATICAL BOLD CAPITAL RHO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6b9: MATHEMATICAL BOLD CAPITAL THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ba: MATHEMATICAL BOLD CAPITAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6bb: MATHEMATICAL BOLD CAPITAL TAU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6bc: MATHEMATICAL BOLD CAPITAL UPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6bd: MATHEMATICAL BOLD CAPITAL PHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6be: MATHEMATICAL BOLD CAPITAL CHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6bf: MATHEMATICAL BOLD CAPITAL PSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6c0: MATHEMATICAL BOLD CAPITAL OMEGA*/
+ 0, /* U+01d6c1: MATHEMATICAL BOLD NABLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6c2: MATHEMATICAL BOLD SMALL ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6c3: MATHEMATICAL BOLD SMALL BETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6c4: MATHEMATICAL BOLD SMALL GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6c5: MATHEMATICAL BOLD SMALL DELTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6c6: MATHEMATICAL BOLD SMALL EPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6c7: MATHEMATICAL BOLD SMALL ZETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6c8: MATHEMATICAL BOLD SMALL ETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6c9: MATHEMATICAL BOLD SMALL THETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6ca: MATHEMATICAL BOLD SMALL IOTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6cb: MATHEMATICAL BOLD SMALL KAPPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6cc: MATHEMATICAL BOLD SMALL LAMDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6cd: MATHEMATICAL BOLD SMALL MU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6ce: MATHEMATICAL BOLD SMALL NU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6cf: MATHEMATICAL BOLD SMALL XI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d0: MATHEMATICAL BOLD SMALL OMICRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d1: MATHEMATICAL BOLD SMALL PI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d2: MATHEMATICAL BOLD SMALL RHO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d3: MATHEMATICAL BOLD SMALL FINAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d4: MATHEMATICAL BOLD SMALL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d5: MATHEMATICAL BOLD SMALL TAU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d6: MATHEMATICAL BOLD SMALL UPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d7: MATHEMATICAL BOLD SMALL PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d8: MATHEMATICAL BOLD SMALL CHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6d9: MATHEMATICAL BOLD SMALL PSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6da: MATHEMATICAL BOLD SMALL OMEGA*/
+ 0, /* U+01d6db: MATHEMATICAL BOLD PARTIAL DIFFERENTIAL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6dc: MATHEMATICAL BOLD EPSILON SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6dd: MATHEMATICAL BOLD THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6de: MATHEMATICAL BOLD KAPPA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6df: MATHEMATICAL BOLD PHI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6e0: MATHEMATICAL BOLD RHO SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6e1: MATHEMATICAL BOLD PI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6e2: MATHEMATICAL ITALIC CAPITAL ALPHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6e3: MATHEMATICAL ITALIC CAPITAL BETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6e4: MATHEMATICAL ITALIC CAPITAL GAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6e5: MATHEMATICAL ITALIC CAPITAL DELTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6e6: MATHEMATICAL ITALIC CAPITAL EPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6e7: MATHEMATICAL ITALIC CAPITAL ZETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6e8: MATHEMATICAL ITALIC CAPITAL ETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6e9: MATHEMATICAL ITALIC CAPITAL THETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ea: MATHEMATICAL ITALIC CAPITAL IOTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6eb: MATHEMATICAL ITALIC CAPITAL KAPPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ec: MATHEMATICAL ITALIC CAPITAL LAMDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ed: MATHEMATICAL ITALIC CAPITAL MU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ee: MATHEMATICAL ITALIC CAPITAL NU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6ef: MATHEMATICAL ITALIC CAPITAL XI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f0: MATHEMATICAL ITALIC CAPITAL OMICRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f1: MATHEMATICAL ITALIC CAPITAL PI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f2: MATHEMATICAL ITALIC CAPITAL RHO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f3: MATHEMATICAL ITALIC CAPITAL THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f4: MATHEMATICAL ITALIC CAPITAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f5: MATHEMATICAL ITALIC CAPITAL TAU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f6: MATHEMATICAL ITALIC CAPITAL UPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f7: MATHEMATICAL ITALIC CAPITAL PHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f8: MATHEMATICAL ITALIC CAPITAL CHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6f9: MATHEMATICAL ITALIC CAPITAL PSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d6fa: MATHEMATICAL ITALIC CAPITAL OMEGA*/
+ 0, /* U+01d6fb: MATHEMATICAL ITALIC NABLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6fc: MATHEMATICAL ITALIC SMALL ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6fd: MATHEMATICAL ITALIC SMALL BETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6fe: MATHEMATICAL ITALIC SMALL GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d6ff: MATHEMATICAL ITALIC SMALL DELTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d700: MATHEMATICAL ITALIC SMALL EPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d701: MATHEMATICAL ITALIC SMALL ZETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d702: MATHEMATICAL ITALIC SMALL ETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d703: MATHEMATICAL ITALIC SMALL THETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d704: MATHEMATICAL ITALIC SMALL IOTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d705: MATHEMATICAL ITALIC SMALL KAPPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d706: MATHEMATICAL ITALIC SMALL LAMDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d707: MATHEMATICAL ITALIC SMALL MU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d708: MATHEMATICAL ITALIC SMALL NU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d709: MATHEMATICAL ITALIC SMALL XI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d70a: MATHEMATICAL ITALIC SMALL OMICRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d70b: MATHEMATICAL ITALIC SMALL PI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d70c: MATHEMATICAL ITALIC SMALL RHO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d70d: MATHEMATICAL ITALIC SMALL FINAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d70e: MATHEMATICAL ITALIC SMALL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d70f: MATHEMATICAL ITALIC SMALL TAU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d710: MATHEMATICAL ITALIC SMALL UPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d711: MATHEMATICAL ITALIC SMALL PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d712: MATHEMATICAL ITALIC SMALL CHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d713: MATHEMATICAL ITALIC SMALL PSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d714: MATHEMATICAL ITALIC SMALL OMEGA*/
+ 0, /* U+01d715: MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d716: MATHEMATICAL ITALIC EPSILON SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d717: MATHEMATICAL ITALIC THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d718: MATHEMATICAL ITALIC KAPPA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d719: MATHEMATICAL ITALIC PHI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d71a: MATHEMATICAL ITALIC RHO SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d71b: MATHEMATICAL ITALIC PI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d71c: MATHEMATICAL BOLD ITALIC CAPITAL ALPHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d71d: MATHEMATICAL BOLD ITALIC CAPITAL BETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d71e: MATHEMATICAL BOLD ITALIC CAPITAL GAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d71f: MATHEMATICAL BOLD ITALIC CAPITAL DELTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d720: MATHEMATICAL BOLD ITALIC CAPITAL EPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d721: MATHEMATICAL BOLD ITALIC CAPITAL ZETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d722: MATHEMATICAL BOLD ITALIC CAPITAL ETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d723: MATHEMATICAL BOLD ITALIC CAPITAL THETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d724: MATHEMATICAL BOLD ITALIC CAPITAL IOTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d725: MATHEMATICAL BOLD ITALIC CAPITAL KAPPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d726: MATHEMATICAL BOLD ITALIC CAPITAL LAMDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d727: MATHEMATICAL BOLD ITALIC CAPITAL MU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d728: MATHEMATICAL BOLD ITALIC CAPITAL NU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d729: MATHEMATICAL BOLD ITALIC CAPITAL XI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d72a: MATHEMATICAL BOLD ITALIC CAPITAL OMICRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d72b: MATHEMATICAL BOLD ITALIC CAPITAL PI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d72c: MATHEMATICAL BOLD ITALIC CAPITAL RHO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d72d: MATHEMATICAL BOLD ITALIC CAPITAL THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d72e: MATHEMATICAL BOLD ITALIC CAPITAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d72f: MATHEMATICAL BOLD ITALIC CAPITAL TAU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d730: MATHEMATICAL BOLD ITALIC CAPITAL UPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d731: MATHEMATICAL BOLD ITALIC CAPITAL PHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d732: MATHEMATICAL BOLD ITALIC CAPITAL CHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d733: MATHEMATICAL BOLD ITALIC CAPITAL PSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d734: MATHEMATICAL BOLD ITALIC CAPITAL OMEGA*/
+ 0, /* U+01d735: MATHEMATICAL BOLD ITALIC NABLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d736: MATHEMATICAL BOLD ITALIC SMALL ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d737: MATHEMATICAL BOLD ITALIC SMALL BETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d738: MATHEMATICAL BOLD ITALIC SMALL GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d739: MATHEMATICAL BOLD ITALIC SMALL DELTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d73a: MATHEMATICAL BOLD ITALIC SMALL EPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d73b: MATHEMATICAL BOLD ITALIC SMALL ZETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d73c: MATHEMATICAL BOLD ITALIC SMALL ETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d73d: MATHEMATICAL BOLD ITALIC SMALL THETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d73e: MATHEMATICAL BOLD ITALIC SMALL IOTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d73f: MATHEMATICAL BOLD ITALIC SMALL KAPPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d740: MATHEMATICAL BOLD ITALIC SMALL LAMDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d741: MATHEMATICAL BOLD ITALIC SMALL MU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d742: MATHEMATICAL BOLD ITALIC SMALL NU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d743: MATHEMATICAL BOLD ITALIC SMALL XI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d744: MATHEMATICAL BOLD ITALIC SMALL OMICRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d745: MATHEMATICAL BOLD ITALIC SMALL PI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d746: MATHEMATICAL BOLD ITALIC SMALL RHO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d747: MATHEMATICAL BOLD ITALIC SMALL FINAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d748: MATHEMATICAL BOLD ITALIC SMALL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d749: MATHEMATICAL BOLD ITALIC SMALL TAU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d74a: MATHEMATICAL BOLD ITALIC SMALL UPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d74b: MATHEMATICAL BOLD ITALIC SMALL PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d74c: MATHEMATICAL BOLD ITALIC SMALL CHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d74d: MATHEMATICAL BOLD ITALIC SMALL PSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d74e: MATHEMATICAL BOLD ITALIC SMALL OMEGA*/
+ 0, /* U+01d74f: MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d750: MATHEMATICAL BOLD ITALIC EPSILON SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d751: MATHEMATICAL BOLD ITALIC THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d752: MATHEMATICAL BOLD ITALIC KAPPA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d753: MATHEMATICAL BOLD ITALIC PHI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d754: MATHEMATICAL BOLD ITALIC RHO SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d755: MATHEMATICAL BOLD ITALIC PI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d756: MATHEMATICAL SANS-SERIF BOLD CAPITAL ALPHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d757: MATHEMATICAL SANS-SERIF BOLD CAPITAL BETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d758: MATHEMATICAL SANS-SERIF BOLD CAPITAL GAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d759: MATHEMATICAL SANS-SERIF BOLD CAPITAL DELTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d75a: MATHEMATICAL SANS-SERIF BOLD CAPITAL EPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d75b: MATHEMATICAL SANS-SERIF BOLD CAPITAL ZETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d75c: MATHEMATICAL SANS-SERIF BOLD CAPITAL ETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d75d: MATHEMATICAL SANS-SERIF BOLD CAPITAL THETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d75e: MATHEMATICAL SANS-SERIF BOLD CAPITAL IOTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d75f: MATHEMATICAL SANS-SERIF BOLD CAPITAL KAPPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d760: MATHEMATICAL SANS-SERIF BOLD CAPITAL LAMDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d761: MATHEMATICAL SANS-SERIF BOLD CAPITAL MU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d762: MATHEMATICAL SANS-SERIF BOLD CAPITAL NU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d763: MATHEMATICAL SANS-SERIF BOLD CAPITAL XI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d764: MATHEMATICAL SANS-SERIF BOLD CAPITAL OMICRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d765: MATHEMATICAL SANS-SERIF BOLD CAPITAL PI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d766: MATHEMATICAL SANS-SERIF BOLD CAPITAL RHO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d767: MATHEMATICAL SANS-SERIF BOLD CAPITAL THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d768: MATHEMATICAL SANS-SERIF BOLD CAPITAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d769: MATHEMATICAL SANS-SERIF BOLD CAPITAL TAU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d76a: MATHEMATICAL SANS-SERIF BOLD CAPITAL UPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d76b: MATHEMATICAL SANS-SERIF BOLD CAPITAL PHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d76c: MATHEMATICAL SANS-SERIF BOLD CAPITAL CHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d76d: MATHEMATICAL SANS-SERIF BOLD CAPITAL PSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d76e: MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA*/
+ 0, /* U+01d76f: MATHEMATICAL SANS-SERIF BOLD NABLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d770: MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d771: MATHEMATICAL SANS-SERIF BOLD SMALL BETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d772: MATHEMATICAL SANS-SERIF BOLD SMALL GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d773: MATHEMATICAL SANS-SERIF BOLD SMALL DELTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d774: MATHEMATICAL SANS-SERIF BOLD SMALL EPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d775: MATHEMATICAL SANS-SERIF BOLD SMALL ZETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d776: MATHEMATICAL SANS-SERIF BOLD SMALL ETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d777: MATHEMATICAL SANS-SERIF BOLD SMALL THETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d778: MATHEMATICAL SANS-SERIF BOLD SMALL IOTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d779: MATHEMATICAL SANS-SERIF BOLD SMALL KAPPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d77a: MATHEMATICAL SANS-SERIF BOLD SMALL LAMDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d77b: MATHEMATICAL SANS-SERIF BOLD SMALL MU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d77c: MATHEMATICAL SANS-SERIF BOLD SMALL NU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d77d: MATHEMATICAL SANS-SERIF BOLD SMALL XI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d77e: MATHEMATICAL SANS-SERIF BOLD SMALL OMICRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d77f: MATHEMATICAL SANS-SERIF BOLD SMALL PI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d780: MATHEMATICAL SANS-SERIF BOLD SMALL RHO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d781: MATHEMATICAL SANS-SERIF BOLD SMALL FINAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d782: MATHEMATICAL SANS-SERIF BOLD SMALL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d783: MATHEMATICAL SANS-SERIF BOLD SMALL TAU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d784: MATHEMATICAL SANS-SERIF BOLD SMALL UPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d785: MATHEMATICAL SANS-SERIF BOLD SMALL PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d786: MATHEMATICAL SANS-SERIF BOLD SMALL CHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d787: MATHEMATICAL SANS-SERIF BOLD SMALL PSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d788: MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA*/
+ 0, /* U+01d789: MATHEMATICAL SANS-SERIF BOLD PARTIAL DIFFERENTIAL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d78a: MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d78b: MATHEMATICAL SANS-SERIF BOLD THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d78c: MATHEMATICAL SANS-SERIF BOLD KAPPA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d78d: MATHEMATICAL SANS-SERIF BOLD PHI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d78e: MATHEMATICAL SANS-SERIF BOLD RHO SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d78f: MATHEMATICAL SANS-SERIF BOLD PI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d790: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ALPHA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d791: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL BETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d792: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL GAMMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d793: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL DELTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d794: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL EPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d795: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ZETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d796: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL ETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d797: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL THETA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d798: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL IOTA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d799: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL KAPPA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d79a: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL LAMDA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d79b: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL MU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d79c: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL NU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d79d: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL XI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d79e: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMICRON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d79f: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7a0: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL RHO*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7a1: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7a2: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7a3: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL TAU*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7a4: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL UPSILON*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7a5: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7a6: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL CHI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7a7: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PSI*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7a8: MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA*/
+ 0, /* U+01d7a9: MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7aa: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7ab: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL BETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7ac: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL GAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7ad: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL DELTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7ae: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL EPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7af: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ZETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b0: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b1: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL THETA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b2: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL IOTA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b3: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL KAPPA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b4: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL LAMDA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b5: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL MU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b6: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL NU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b7: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL XI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b8: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMICRON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7b9: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7ba: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL RHO*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7bb: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL FINAL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7bc: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL SIGMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7bd: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL TAU*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7be: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL UPSILON*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7bf: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7c0: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL CHI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7c1: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PSI*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7c2: MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA*/
+ 0, /* U+01d7c3: MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7c4: MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7c5: MATHEMATICAL SANS-SERIF BOLD ITALIC THETA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7c6: MATHEMATICAL SANS-SERIF BOLD ITALIC KAPPA SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7c7: MATHEMATICAL SANS-SERIF BOLD ITALIC PHI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7c8: MATHEMATICAL SANS-SERIF BOLD ITALIC RHO SYMBOL*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7c9: MATHEMATICAL SANS-SERIF BOLD ITALIC PI SYMBOL*/
+ RTUNI_ALPHA | RTUNI_UPPER, /* U+01d7ca: MATHEMATICAL BOLD CAPITAL DIGAMMA*/
+ RTUNI_ALPHA | RTUNI_LOWER, /* U+01d7cb: MATHEMATICAL BOLD SMALL DIGAMMA*/
+ 0, /* U+01d7cc: */
+ 0, /* U+01d7cd: */
+ RTUNI_DDIGIT, /* U+01d7ce: MATHEMATICAL BOLD DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+01d7cf: MATHEMATICAL BOLD DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+01d7d0: MATHEMATICAL BOLD DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+01d7d1: MATHEMATICAL BOLD DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+01d7d2: MATHEMATICAL BOLD DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+01d7d3: MATHEMATICAL BOLD DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+01d7d4: MATHEMATICAL BOLD DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+01d7d5: MATHEMATICAL BOLD DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+01d7d6: MATHEMATICAL BOLD DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+01d7d7: MATHEMATICAL BOLD DIGIT NINE*/
+ RTUNI_DDIGIT, /* U+01d7d8: MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+01d7d9: MATHEMATICAL DOUBLE-STRUCK DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+01d7da: MATHEMATICAL DOUBLE-STRUCK DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+01d7db: MATHEMATICAL DOUBLE-STRUCK DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+01d7dc: MATHEMATICAL DOUBLE-STRUCK DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+01d7dd: MATHEMATICAL DOUBLE-STRUCK DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+01d7de: MATHEMATICAL DOUBLE-STRUCK DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+01d7df: MATHEMATICAL DOUBLE-STRUCK DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+01d7e0: MATHEMATICAL DOUBLE-STRUCK DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+01d7e1: MATHEMATICAL DOUBLE-STRUCK DIGIT NINE*/
+ RTUNI_DDIGIT, /* U+01d7e2: MATHEMATICAL SANS-SERIF DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+01d7e3: MATHEMATICAL SANS-SERIF DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+01d7e4: MATHEMATICAL SANS-SERIF DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+01d7e5: MATHEMATICAL SANS-SERIF DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+01d7e6: MATHEMATICAL SANS-SERIF DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+01d7e7: MATHEMATICAL SANS-SERIF DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+01d7e8: MATHEMATICAL SANS-SERIF DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+01d7e9: MATHEMATICAL SANS-SERIF DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+01d7ea: MATHEMATICAL SANS-SERIF DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+01d7eb: MATHEMATICAL SANS-SERIF DIGIT NINE*/
+ RTUNI_DDIGIT, /* U+01d7ec: MATHEMATICAL SANS-SERIF BOLD DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+01d7ed: MATHEMATICAL SANS-SERIF BOLD DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+01d7ee: MATHEMATICAL SANS-SERIF BOLD DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+01d7ef: MATHEMATICAL SANS-SERIF BOLD DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+01d7f0: MATHEMATICAL SANS-SERIF BOLD DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+01d7f1: MATHEMATICAL SANS-SERIF BOLD DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+01d7f2: MATHEMATICAL SANS-SERIF BOLD DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+01d7f3: MATHEMATICAL SANS-SERIF BOLD DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+01d7f4: MATHEMATICAL SANS-SERIF BOLD DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+01d7f5: MATHEMATICAL SANS-SERIF BOLD DIGIT NINE*/
+ RTUNI_DDIGIT, /* U+01d7f6: MATHEMATICAL MONOSPACE DIGIT ZERO*/
+ RTUNI_DDIGIT, /* U+01d7f7: MATHEMATICAL MONOSPACE DIGIT ONE*/
+ RTUNI_DDIGIT, /* U+01d7f8: MATHEMATICAL MONOSPACE DIGIT TWO*/
+ RTUNI_DDIGIT, /* U+01d7f9: MATHEMATICAL MONOSPACE DIGIT THREE*/
+ RTUNI_DDIGIT, /* U+01d7fa: MATHEMATICAL MONOSPACE DIGIT FOUR*/
+ RTUNI_DDIGIT, /* U+01d7fb: MATHEMATICAL MONOSPACE DIGIT FIVE*/
+ RTUNI_DDIGIT, /* U+01d7fc: MATHEMATICAL MONOSPACE DIGIT SIX*/
+ RTUNI_DDIGIT, /* U+01d7fd: MATHEMATICAL MONOSPACE DIGIT SEVEN*/
+ RTUNI_DDIGIT, /* U+01d7fe: MATHEMATICAL MONOSPACE DIGIT EIGHT*/
+ RTUNI_DDIGIT, /* U+01d7ff: MATHEMATICAL MONOSPACE DIGIT NINE*/
+};
+
+static const uint8_t g_afRTUniFlags0x01ee00[] =
+{
+ RTUNI_ALPHA, /* U+01ee00: ARABIC MATHEMATICAL ALEF*/
+ RTUNI_ALPHA, /* U+01ee01: ARABIC MATHEMATICAL BEH*/
+ RTUNI_ALPHA, /* U+01ee02: ARABIC MATHEMATICAL JEEM*/
+ RTUNI_ALPHA, /* U+01ee03: ARABIC MATHEMATICAL DAL*/
+ 0, /* U+01ee04: */
+ RTUNI_ALPHA, /* U+01ee05: ARABIC MATHEMATICAL WAW*/
+ RTUNI_ALPHA, /* U+01ee06: ARABIC MATHEMATICAL ZAIN*/
+ RTUNI_ALPHA, /* U+01ee07: ARABIC MATHEMATICAL HAH*/
+ RTUNI_ALPHA, /* U+01ee08: ARABIC MATHEMATICAL TAH*/
+ RTUNI_ALPHA, /* U+01ee09: ARABIC MATHEMATICAL YEH*/
+ RTUNI_ALPHA, /* U+01ee0a: ARABIC MATHEMATICAL KAF*/
+ RTUNI_ALPHA, /* U+01ee0b: ARABIC MATHEMATICAL LAM*/
+ RTUNI_ALPHA, /* U+01ee0c: ARABIC MATHEMATICAL MEEM*/
+ RTUNI_ALPHA, /* U+01ee0d: ARABIC MATHEMATICAL NOON*/
+ RTUNI_ALPHA, /* U+01ee0e: ARABIC MATHEMATICAL SEEN*/
+ RTUNI_ALPHA, /* U+01ee0f: ARABIC MATHEMATICAL AIN*/
+ RTUNI_ALPHA, /* U+01ee10: ARABIC MATHEMATICAL FEH*/
+ RTUNI_ALPHA, /* U+01ee11: ARABIC MATHEMATICAL SAD*/
+ RTUNI_ALPHA, /* U+01ee12: ARABIC MATHEMATICAL QAF*/
+ RTUNI_ALPHA, /* U+01ee13: ARABIC MATHEMATICAL REH*/
+ RTUNI_ALPHA, /* U+01ee14: ARABIC MATHEMATICAL SHEEN*/
+ RTUNI_ALPHA, /* U+01ee15: ARABIC MATHEMATICAL TEH*/
+ RTUNI_ALPHA, /* U+01ee16: ARABIC MATHEMATICAL THEH*/
+ RTUNI_ALPHA, /* U+01ee17: ARABIC MATHEMATICAL KHAH*/
+ RTUNI_ALPHA, /* U+01ee18: ARABIC MATHEMATICAL THAL*/
+ RTUNI_ALPHA, /* U+01ee19: ARABIC MATHEMATICAL DAD*/
+ RTUNI_ALPHA, /* U+01ee1a: ARABIC MATHEMATICAL ZAH*/
+ RTUNI_ALPHA, /* U+01ee1b: ARABIC MATHEMATICAL GHAIN*/
+ RTUNI_ALPHA, /* U+01ee1c: ARABIC MATHEMATICAL DOTLESS BEH*/
+ RTUNI_ALPHA, /* U+01ee1d: ARABIC MATHEMATICAL DOTLESS NOON*/
+ RTUNI_ALPHA, /* U+01ee1e: ARABIC MATHEMATICAL DOTLESS FEH*/
+ RTUNI_ALPHA, /* U+01ee1f: ARABIC MATHEMATICAL DOTLESS QAF*/
+ 0, /* U+01ee20: */
+ RTUNI_ALPHA, /* U+01ee21: ARABIC MATHEMATICAL INITIAL BEH*/
+ RTUNI_ALPHA, /* U+01ee22: ARABIC MATHEMATICAL INITIAL JEEM*/
+ 0, /* U+01ee23: */
+ RTUNI_ALPHA, /* U+01ee24: ARABIC MATHEMATICAL INITIAL HEH*/
+ 0, /* U+01ee25: */
+ 0, /* U+01ee26: */
+ RTUNI_ALPHA, /* U+01ee27: ARABIC MATHEMATICAL INITIAL HAH*/
+ 0, /* U+01ee28: */
+ RTUNI_ALPHA, /* U+01ee29: ARABIC MATHEMATICAL INITIAL YEH*/
+ RTUNI_ALPHA, /* U+01ee2a: ARABIC MATHEMATICAL INITIAL KAF*/
+ RTUNI_ALPHA, /* U+01ee2b: ARABIC MATHEMATICAL INITIAL LAM*/
+ RTUNI_ALPHA, /* U+01ee2c: ARABIC MATHEMATICAL INITIAL MEEM*/
+ RTUNI_ALPHA, /* U+01ee2d: ARABIC MATHEMATICAL INITIAL NOON*/
+ RTUNI_ALPHA, /* U+01ee2e: ARABIC MATHEMATICAL INITIAL SEEN*/
+ RTUNI_ALPHA, /* U+01ee2f: ARABIC MATHEMATICAL INITIAL AIN*/
+ RTUNI_ALPHA, /* U+01ee30: ARABIC MATHEMATICAL INITIAL FEH*/
+ RTUNI_ALPHA, /* U+01ee31: ARABIC MATHEMATICAL INITIAL SAD*/
+ RTUNI_ALPHA, /* U+01ee32: ARABIC MATHEMATICAL INITIAL QAF*/
+ 0, /* U+01ee33: */
+ RTUNI_ALPHA, /* U+01ee34: ARABIC MATHEMATICAL INITIAL SHEEN*/
+ RTUNI_ALPHA, /* U+01ee35: ARABIC MATHEMATICAL INITIAL TEH*/
+ RTUNI_ALPHA, /* U+01ee36: ARABIC MATHEMATICAL INITIAL THEH*/
+ RTUNI_ALPHA, /* U+01ee37: ARABIC MATHEMATICAL INITIAL KHAH*/
+ 0, /* U+01ee38: */
+ RTUNI_ALPHA, /* U+01ee39: ARABIC MATHEMATICAL INITIAL DAD*/
+ 0, /* U+01ee3a: */
+ RTUNI_ALPHA, /* U+01ee3b: ARABIC MATHEMATICAL INITIAL GHAIN*/
+ 0, /* U+01ee3c: */
+ 0, /* U+01ee3d: */
+ 0, /* U+01ee3e: */
+ 0, /* U+01ee3f: */
+ 0, /* U+01ee40: */
+ 0, /* U+01ee41: */
+ RTUNI_ALPHA, /* U+01ee42: ARABIC MATHEMATICAL TAILED JEEM*/
+ 0, /* U+01ee43: */
+ 0, /* U+01ee44: */
+ 0, /* U+01ee45: */
+ 0, /* U+01ee46: */
+ RTUNI_ALPHA, /* U+01ee47: ARABIC MATHEMATICAL TAILED HAH*/
+ 0, /* U+01ee48: */
+ RTUNI_ALPHA, /* U+01ee49: ARABIC MATHEMATICAL TAILED YEH*/
+ 0, /* U+01ee4a: */
+ RTUNI_ALPHA, /* U+01ee4b: ARABIC MATHEMATICAL TAILED LAM*/
+ 0, /* U+01ee4c: */
+ RTUNI_ALPHA, /* U+01ee4d: ARABIC MATHEMATICAL TAILED NOON*/
+ RTUNI_ALPHA, /* U+01ee4e: ARABIC MATHEMATICAL TAILED SEEN*/
+ RTUNI_ALPHA, /* U+01ee4f: ARABIC MATHEMATICAL TAILED AIN*/
+ 0, /* U+01ee50: */
+ RTUNI_ALPHA, /* U+01ee51: ARABIC MATHEMATICAL TAILED SAD*/
+ RTUNI_ALPHA, /* U+01ee52: ARABIC MATHEMATICAL TAILED QAF*/
+ 0, /* U+01ee53: */
+ RTUNI_ALPHA, /* U+01ee54: ARABIC MATHEMATICAL TAILED SHEEN*/
+ 0, /* U+01ee55: */
+ 0, /* U+01ee56: */
+ RTUNI_ALPHA, /* U+01ee57: ARABIC MATHEMATICAL TAILED KHAH*/
+ 0, /* U+01ee58: */
+ RTUNI_ALPHA, /* U+01ee59: ARABIC MATHEMATICAL TAILED DAD*/
+ 0, /* U+01ee5a: */
+ RTUNI_ALPHA, /* U+01ee5b: ARABIC MATHEMATICAL TAILED GHAIN*/
+ 0, /* U+01ee5c: */
+ RTUNI_ALPHA, /* U+01ee5d: ARABIC MATHEMATICAL TAILED DOTLESS NOON*/
+ 0, /* U+01ee5e: */
+ RTUNI_ALPHA, /* U+01ee5f: ARABIC MATHEMATICAL TAILED DOTLESS QAF*/
+ 0, /* U+01ee60: */
+ RTUNI_ALPHA, /* U+01ee61: ARABIC MATHEMATICAL STRETCHED BEH*/
+ RTUNI_ALPHA, /* U+01ee62: ARABIC MATHEMATICAL STRETCHED JEEM*/
+ 0, /* U+01ee63: */
+ RTUNI_ALPHA, /* U+01ee64: ARABIC MATHEMATICAL STRETCHED HEH*/
+ 0, /* U+01ee65: */
+ 0, /* U+01ee66: */
+ RTUNI_ALPHA, /* U+01ee67: ARABIC MATHEMATICAL STRETCHED HAH*/
+ RTUNI_ALPHA, /* U+01ee68: ARABIC MATHEMATICAL STRETCHED TAH*/
+ RTUNI_ALPHA, /* U+01ee69: ARABIC MATHEMATICAL STRETCHED YEH*/
+ RTUNI_ALPHA, /* U+01ee6a: ARABIC MATHEMATICAL STRETCHED KAF*/
+ 0, /* U+01ee6b: */
+ RTUNI_ALPHA, /* U+01ee6c: ARABIC MATHEMATICAL STRETCHED MEEM*/
+ RTUNI_ALPHA, /* U+01ee6d: ARABIC MATHEMATICAL STRETCHED NOON*/
+ RTUNI_ALPHA, /* U+01ee6e: ARABIC MATHEMATICAL STRETCHED SEEN*/
+ RTUNI_ALPHA, /* U+01ee6f: ARABIC MATHEMATICAL STRETCHED AIN*/
+ RTUNI_ALPHA, /* U+01ee70: ARABIC MATHEMATICAL STRETCHED FEH*/
+ RTUNI_ALPHA, /* U+01ee71: ARABIC MATHEMATICAL STRETCHED SAD*/
+ RTUNI_ALPHA, /* U+01ee72: ARABIC MATHEMATICAL STRETCHED QAF*/
+ 0, /* U+01ee73: */
+ RTUNI_ALPHA, /* U+01ee74: ARABIC MATHEMATICAL STRETCHED SHEEN*/
+ RTUNI_ALPHA, /* U+01ee75: ARABIC MATHEMATICAL STRETCHED TEH*/
+ RTUNI_ALPHA, /* U+01ee76: ARABIC MATHEMATICAL STRETCHED THEH*/
+ RTUNI_ALPHA, /* U+01ee77: ARABIC MATHEMATICAL STRETCHED KHAH*/
+ 0, /* U+01ee78: */
+ RTUNI_ALPHA, /* U+01ee79: ARABIC MATHEMATICAL STRETCHED DAD*/
+ RTUNI_ALPHA, /* U+01ee7a: ARABIC MATHEMATICAL STRETCHED ZAH*/
+ RTUNI_ALPHA, /* U+01ee7b: ARABIC MATHEMATICAL STRETCHED GHAIN*/
+ RTUNI_ALPHA, /* U+01ee7c: ARABIC MATHEMATICAL STRETCHED DOTLESS BEH*/
+ 0, /* U+01ee7d: */
+ RTUNI_ALPHA, /* U+01ee7e: ARABIC MATHEMATICAL STRETCHED DOTLESS FEH*/
+ 0, /* U+01ee7f: */
+ RTUNI_ALPHA, /* U+01ee80: ARABIC MATHEMATICAL LOOPED ALEF*/
+ RTUNI_ALPHA, /* U+01ee81: ARABIC MATHEMATICAL LOOPED BEH*/
+ RTUNI_ALPHA, /* U+01ee82: ARABIC MATHEMATICAL LOOPED JEEM*/
+ RTUNI_ALPHA, /* U+01ee83: ARABIC MATHEMATICAL LOOPED DAL*/
+ RTUNI_ALPHA, /* U+01ee84: ARABIC MATHEMATICAL LOOPED HEH*/
+ RTUNI_ALPHA, /* U+01ee85: ARABIC MATHEMATICAL LOOPED WAW*/
+ RTUNI_ALPHA, /* U+01ee86: ARABIC MATHEMATICAL LOOPED ZAIN*/
+ RTUNI_ALPHA, /* U+01ee87: ARABIC MATHEMATICAL LOOPED HAH*/
+ RTUNI_ALPHA, /* U+01ee88: ARABIC MATHEMATICAL LOOPED TAH*/
+ RTUNI_ALPHA, /* U+01ee89: ARABIC MATHEMATICAL LOOPED YEH*/
+ 0, /* U+01ee8a: */
+ RTUNI_ALPHA, /* U+01ee8b: ARABIC MATHEMATICAL LOOPED LAM*/
+ RTUNI_ALPHA, /* U+01ee8c: ARABIC MATHEMATICAL LOOPED MEEM*/
+ RTUNI_ALPHA, /* U+01ee8d: ARABIC MATHEMATICAL LOOPED NOON*/
+ RTUNI_ALPHA, /* U+01ee8e: ARABIC MATHEMATICAL LOOPED SEEN*/
+ RTUNI_ALPHA, /* U+01ee8f: ARABIC MATHEMATICAL LOOPED AIN*/
+ RTUNI_ALPHA, /* U+01ee90: ARABIC MATHEMATICAL LOOPED FEH*/
+ RTUNI_ALPHA, /* U+01ee91: ARABIC MATHEMATICAL LOOPED SAD*/
+ RTUNI_ALPHA, /* U+01ee92: ARABIC MATHEMATICAL LOOPED QAF*/
+ RTUNI_ALPHA, /* U+01ee93: ARABIC MATHEMATICAL LOOPED REH*/
+ RTUNI_ALPHA, /* U+01ee94: ARABIC MATHEMATICAL LOOPED SHEEN*/
+ RTUNI_ALPHA, /* U+01ee95: ARABIC MATHEMATICAL LOOPED TEH*/
+ RTUNI_ALPHA, /* U+01ee96: ARABIC MATHEMATICAL LOOPED THEH*/
+ RTUNI_ALPHA, /* U+01ee97: ARABIC MATHEMATICAL LOOPED KHAH*/
+ RTUNI_ALPHA, /* U+01ee98: ARABIC MATHEMATICAL LOOPED THAL*/
+ RTUNI_ALPHA, /* U+01ee99: ARABIC MATHEMATICAL LOOPED DAD*/
+ RTUNI_ALPHA, /* U+01ee9a: ARABIC MATHEMATICAL LOOPED ZAH*/
+ RTUNI_ALPHA, /* U+01ee9b: ARABIC MATHEMATICAL LOOPED GHAIN*/
+ 0, /* U+01ee9c: */
+ 0, /* U+01ee9d: */
+ 0, /* U+01ee9e: */
+ 0, /* U+01ee9f: */
+ 0, /* U+01eea0: */
+ RTUNI_ALPHA, /* U+01eea1: ARABIC MATHEMATICAL DOUBLE-STRUCK BEH*/
+ RTUNI_ALPHA, /* U+01eea2: ARABIC MATHEMATICAL DOUBLE-STRUCK JEEM*/
+ RTUNI_ALPHA, /* U+01eea3: ARABIC MATHEMATICAL DOUBLE-STRUCK DAL*/
+ 0, /* U+01eea4: */
+ RTUNI_ALPHA, /* U+01eea5: ARABIC MATHEMATICAL DOUBLE-STRUCK WAW*/
+ RTUNI_ALPHA, /* U+01eea6: ARABIC MATHEMATICAL DOUBLE-STRUCK ZAIN*/
+ RTUNI_ALPHA, /* U+01eea7: ARABIC MATHEMATICAL DOUBLE-STRUCK HAH*/
+ RTUNI_ALPHA, /* U+01eea8: ARABIC MATHEMATICAL DOUBLE-STRUCK TAH*/
+ RTUNI_ALPHA, /* U+01eea9: ARABIC MATHEMATICAL DOUBLE-STRUCK YEH*/
+ 0, /* U+01eeaa: */
+ RTUNI_ALPHA, /* U+01eeab: ARABIC MATHEMATICAL DOUBLE-STRUCK LAM*/
+ RTUNI_ALPHA, /* U+01eeac: ARABIC MATHEMATICAL DOUBLE-STRUCK MEEM*/
+ RTUNI_ALPHA, /* U+01eead: ARABIC MATHEMATICAL DOUBLE-STRUCK NOON*/
+ RTUNI_ALPHA, /* U+01eeae: ARABIC MATHEMATICAL DOUBLE-STRUCK SEEN*/
+ RTUNI_ALPHA, /* U+01eeaf: ARABIC MATHEMATICAL DOUBLE-STRUCK AIN*/
+ RTUNI_ALPHA, /* U+01eeb0: ARABIC MATHEMATICAL DOUBLE-STRUCK FEH*/
+ RTUNI_ALPHA, /* U+01eeb1: ARABIC MATHEMATICAL DOUBLE-STRUCK SAD*/
+ RTUNI_ALPHA, /* U+01eeb2: ARABIC MATHEMATICAL DOUBLE-STRUCK QAF*/
+ RTUNI_ALPHA, /* U+01eeb3: ARABIC MATHEMATICAL DOUBLE-STRUCK REH*/
+ RTUNI_ALPHA, /* U+01eeb4: ARABIC MATHEMATICAL DOUBLE-STRUCK SHEEN*/
+ RTUNI_ALPHA, /* U+01eeb5: ARABIC MATHEMATICAL DOUBLE-STRUCK TEH*/
+ RTUNI_ALPHA, /* U+01eeb6: ARABIC MATHEMATICAL DOUBLE-STRUCK THEH*/
+ RTUNI_ALPHA, /* U+01eeb7: ARABIC MATHEMATICAL DOUBLE-STRUCK KHAH*/
+ RTUNI_ALPHA, /* U+01eeb8: ARABIC MATHEMATICAL DOUBLE-STRUCK THAL*/
+ RTUNI_ALPHA, /* U+01eeb9: ARABIC MATHEMATICAL DOUBLE-STRUCK DAD*/
+ RTUNI_ALPHA, /* U+01eeba: ARABIC MATHEMATICAL DOUBLE-STRUCK ZAH*/
+ RTUNI_ALPHA, /* U+01eebb: ARABIC MATHEMATICAL DOUBLE-STRUCK GHAIN*/
+};
+
+static const uint8_t g_afRTUniFlags0x020000[] =
+{
+ RTUNI_ALPHA, /* U+020000: <CJK Ideograph Extension B, First>*/
+};
+
+static const uint8_t g_afRTUniFlags0x02a6d6[] =
+{
+ RTUNI_ALPHA, /* U+02a6d6: <CJK Ideograph Extension B, Last>*/
+ 0, /* U+02a6d7: */
+ 0, /* U+02a6d8: */
+ 0, /* U+02a6d9: */
+ 0, /* U+02a6da: */
+ 0, /* U+02a6db: */
+ 0, /* U+02a6dc: */
+ 0, /* U+02a6dd: */
+ 0, /* U+02a6de: */
+ 0, /* U+02a6df: */
+ 0, /* U+02a6e0: */
+ 0, /* U+02a6e1: */
+ 0, /* U+02a6e2: */
+ 0, /* U+02a6e3: */
+ 0, /* U+02a6e4: */
+ 0, /* U+02a6e5: */
+ 0, /* U+02a6e6: */
+ 0, /* U+02a6e7: */
+ 0, /* U+02a6e8: */
+ 0, /* U+02a6e9: */
+ 0, /* U+02a6ea: */
+ 0, /* U+02a6eb: */
+ 0, /* U+02a6ec: */
+ 0, /* U+02a6ed: */
+ 0, /* U+02a6ee: */
+ 0, /* U+02a6ef: */
+ 0, /* U+02a6f0: */
+ 0, /* U+02a6f1: */
+ 0, /* U+02a6f2: */
+ 0, /* U+02a6f3: */
+ 0, /* U+02a6f4: */
+ 0, /* U+02a6f5: */
+ 0, /* U+02a6f6: */
+ 0, /* U+02a6f7: */
+ 0, /* U+02a6f8: */
+ 0, /* U+02a6f9: */
+ 0, /* U+02a6fa: */
+ 0, /* U+02a6fb: */
+ 0, /* U+02a6fc: */
+ 0, /* U+02a6fd: */
+ 0, /* U+02a6fe: */
+ 0, /* U+02a6ff: */
+ RTUNI_ALPHA, /* U+02a700: <CJK Ideograph Extension C, First>*/
+};
+
+static const uint8_t g_afRTUniFlags0x02b734[] =
+{
+ RTUNI_ALPHA, /* U+02b734: <CJK Ideograph Extension C, Last>*/
+ 0, /* U+02b735: */
+ 0, /* U+02b736: */
+ 0, /* U+02b737: */
+ 0, /* U+02b738: */
+ 0, /* U+02b739: */
+ 0, /* U+02b73a: */
+ 0, /* U+02b73b: */
+ 0, /* U+02b73c: */
+ 0, /* U+02b73d: */
+ 0, /* U+02b73e: */
+ 0, /* U+02b73f: */
+ RTUNI_ALPHA, /* U+02b740: <CJK Ideograph Extension D, First>*/
+ RTUNI_ALPHA, /* U+02b741: */
+ RTUNI_ALPHA, /* U+02b742: */
+ RTUNI_ALPHA, /* U+02b743: */
+ RTUNI_ALPHA, /* U+02b744: */
+ RTUNI_ALPHA, /* U+02b745: */
+ RTUNI_ALPHA, /* U+02b746: */
+ RTUNI_ALPHA, /* U+02b747: */
+ RTUNI_ALPHA, /* U+02b748: */
+ RTUNI_ALPHA, /* U+02b749: */
+ RTUNI_ALPHA, /* U+02b74a: */
+ RTUNI_ALPHA, /* U+02b74b: */
+ RTUNI_ALPHA, /* U+02b74c: */
+ RTUNI_ALPHA, /* U+02b74d: */
+ RTUNI_ALPHA, /* U+02b74e: */
+ RTUNI_ALPHA, /* U+02b74f: */
+ RTUNI_ALPHA, /* U+02b750: */
+ RTUNI_ALPHA, /* U+02b751: */
+ RTUNI_ALPHA, /* U+02b752: */
+ RTUNI_ALPHA, /* U+02b753: */
+ RTUNI_ALPHA, /* U+02b754: */
+ RTUNI_ALPHA, /* U+02b755: */
+ RTUNI_ALPHA, /* U+02b756: */
+ RTUNI_ALPHA, /* U+02b757: */
+ RTUNI_ALPHA, /* U+02b758: */
+ RTUNI_ALPHA, /* U+02b759: */
+ RTUNI_ALPHA, /* U+02b75a: */
+ RTUNI_ALPHA, /* U+02b75b: */
+ RTUNI_ALPHA, /* U+02b75c: */
+ RTUNI_ALPHA, /* U+02b75d: */
+ RTUNI_ALPHA, /* U+02b75e: */
+ RTUNI_ALPHA, /* U+02b75f: */
+ RTUNI_ALPHA, /* U+02b760: */
+ RTUNI_ALPHA, /* U+02b761: */
+ RTUNI_ALPHA, /* U+02b762: */
+ RTUNI_ALPHA, /* U+02b763: */
+ RTUNI_ALPHA, /* U+02b764: */
+ RTUNI_ALPHA, /* U+02b765: */
+ RTUNI_ALPHA, /* U+02b766: */
+ RTUNI_ALPHA, /* U+02b767: */
+ RTUNI_ALPHA, /* U+02b768: */
+ RTUNI_ALPHA, /* U+02b769: */
+ RTUNI_ALPHA, /* U+02b76a: */
+ RTUNI_ALPHA, /* U+02b76b: */
+ RTUNI_ALPHA, /* U+02b76c: */
+ RTUNI_ALPHA, /* U+02b76d: */
+ RTUNI_ALPHA, /* U+02b76e: */
+ RTUNI_ALPHA, /* U+02b76f: */
+ RTUNI_ALPHA, /* U+02b770: */
+ RTUNI_ALPHA, /* U+02b771: */
+ RTUNI_ALPHA, /* U+02b772: */
+ RTUNI_ALPHA, /* U+02b773: */
+ RTUNI_ALPHA, /* U+02b774: */
+ RTUNI_ALPHA, /* U+02b775: */
+ RTUNI_ALPHA, /* U+02b776: */
+ RTUNI_ALPHA, /* U+02b777: */
+ RTUNI_ALPHA, /* U+02b778: */
+ RTUNI_ALPHA, /* U+02b779: */
+ RTUNI_ALPHA, /* U+02b77a: */
+ RTUNI_ALPHA, /* U+02b77b: */
+ RTUNI_ALPHA, /* U+02b77c: */
+ RTUNI_ALPHA, /* U+02b77d: */
+ RTUNI_ALPHA, /* U+02b77e: */
+ RTUNI_ALPHA, /* U+02b77f: */
+ RTUNI_ALPHA, /* U+02b780: */
+ RTUNI_ALPHA, /* U+02b781: */
+ RTUNI_ALPHA, /* U+02b782: */
+ RTUNI_ALPHA, /* U+02b783: */
+ RTUNI_ALPHA, /* U+02b784: */
+ RTUNI_ALPHA, /* U+02b785: */
+ RTUNI_ALPHA, /* U+02b786: */
+ RTUNI_ALPHA, /* U+02b787: */
+ RTUNI_ALPHA, /* U+02b788: */
+ RTUNI_ALPHA, /* U+02b789: */
+ RTUNI_ALPHA, /* U+02b78a: */
+ RTUNI_ALPHA, /* U+02b78b: */
+ RTUNI_ALPHA, /* U+02b78c: */
+ RTUNI_ALPHA, /* U+02b78d: */
+ RTUNI_ALPHA, /* U+02b78e: */
+ RTUNI_ALPHA, /* U+02b78f: */
+ RTUNI_ALPHA, /* U+02b790: */
+ RTUNI_ALPHA, /* U+02b791: */
+ RTUNI_ALPHA, /* U+02b792: */
+ RTUNI_ALPHA, /* U+02b793: */
+ RTUNI_ALPHA, /* U+02b794: */
+ RTUNI_ALPHA, /* U+02b795: */
+ RTUNI_ALPHA, /* U+02b796: */
+ RTUNI_ALPHA, /* U+02b797: */
+ RTUNI_ALPHA, /* U+02b798: */
+ RTUNI_ALPHA, /* U+02b799: */
+ RTUNI_ALPHA, /* U+02b79a: */
+ RTUNI_ALPHA, /* U+02b79b: */
+ RTUNI_ALPHA, /* U+02b79c: */
+ RTUNI_ALPHA, /* U+02b79d: */
+ RTUNI_ALPHA, /* U+02b79e: */
+ RTUNI_ALPHA, /* U+02b79f: */
+ RTUNI_ALPHA, /* U+02b7a0: */
+ RTUNI_ALPHA, /* U+02b7a1: */
+ RTUNI_ALPHA, /* U+02b7a2: */
+ RTUNI_ALPHA, /* U+02b7a3: */
+ RTUNI_ALPHA, /* U+02b7a4: */
+ RTUNI_ALPHA, /* U+02b7a5: */
+ RTUNI_ALPHA, /* U+02b7a6: */
+ RTUNI_ALPHA, /* U+02b7a7: */
+ RTUNI_ALPHA, /* U+02b7a8: */
+ RTUNI_ALPHA, /* U+02b7a9: */
+ RTUNI_ALPHA, /* U+02b7aa: */
+ RTUNI_ALPHA, /* U+02b7ab: */
+ RTUNI_ALPHA, /* U+02b7ac: */
+ RTUNI_ALPHA, /* U+02b7ad: */
+ RTUNI_ALPHA, /* U+02b7ae: */
+ RTUNI_ALPHA, /* U+02b7af: */
+ RTUNI_ALPHA, /* U+02b7b0: */
+ RTUNI_ALPHA, /* U+02b7b1: */
+ RTUNI_ALPHA, /* U+02b7b2: */
+ RTUNI_ALPHA, /* U+02b7b3: */
+ RTUNI_ALPHA, /* U+02b7b4: */
+ RTUNI_ALPHA, /* U+02b7b5: */
+ RTUNI_ALPHA, /* U+02b7b6: */
+ RTUNI_ALPHA, /* U+02b7b7: */
+ RTUNI_ALPHA, /* U+02b7b8: */
+ RTUNI_ALPHA, /* U+02b7b9: */
+ RTUNI_ALPHA, /* U+02b7ba: */
+ RTUNI_ALPHA, /* U+02b7bb: */
+ RTUNI_ALPHA, /* U+02b7bc: */
+ RTUNI_ALPHA, /* U+02b7bd: */
+ RTUNI_ALPHA, /* U+02b7be: */
+ RTUNI_ALPHA, /* U+02b7bf: */
+ RTUNI_ALPHA, /* U+02b7c0: */
+ RTUNI_ALPHA, /* U+02b7c1: */
+ RTUNI_ALPHA, /* U+02b7c2: */
+ RTUNI_ALPHA, /* U+02b7c3: */
+ RTUNI_ALPHA, /* U+02b7c4: */
+ RTUNI_ALPHA, /* U+02b7c5: */
+ RTUNI_ALPHA, /* U+02b7c6: */
+ RTUNI_ALPHA, /* U+02b7c7: */
+ RTUNI_ALPHA, /* U+02b7c8: */
+ RTUNI_ALPHA, /* U+02b7c9: */
+ RTUNI_ALPHA, /* U+02b7ca: */
+ RTUNI_ALPHA, /* U+02b7cb: */
+ RTUNI_ALPHA, /* U+02b7cc: */
+ RTUNI_ALPHA, /* U+02b7cd: */
+ RTUNI_ALPHA, /* U+02b7ce: */
+ RTUNI_ALPHA, /* U+02b7cf: */
+ RTUNI_ALPHA, /* U+02b7d0: */
+ RTUNI_ALPHA, /* U+02b7d1: */
+ RTUNI_ALPHA, /* U+02b7d2: */
+ RTUNI_ALPHA, /* U+02b7d3: */
+ RTUNI_ALPHA, /* U+02b7d4: */
+ RTUNI_ALPHA, /* U+02b7d5: */
+ RTUNI_ALPHA, /* U+02b7d6: */
+ RTUNI_ALPHA, /* U+02b7d7: */
+ RTUNI_ALPHA, /* U+02b7d8: */
+ RTUNI_ALPHA, /* U+02b7d9: */
+ RTUNI_ALPHA, /* U+02b7da: */
+ RTUNI_ALPHA, /* U+02b7db: */
+ RTUNI_ALPHA, /* U+02b7dc: */
+ RTUNI_ALPHA, /* U+02b7dd: */
+ RTUNI_ALPHA, /* U+02b7de: */
+ RTUNI_ALPHA, /* U+02b7df: */
+ RTUNI_ALPHA, /* U+02b7e0: */
+ RTUNI_ALPHA, /* U+02b7e1: */
+ RTUNI_ALPHA, /* U+02b7e2: */
+ RTUNI_ALPHA, /* U+02b7e3: */
+ RTUNI_ALPHA, /* U+02b7e4: */
+ RTUNI_ALPHA, /* U+02b7e5: */
+ RTUNI_ALPHA, /* U+02b7e6: */
+ RTUNI_ALPHA, /* U+02b7e7: */
+ RTUNI_ALPHA, /* U+02b7e8: */
+ RTUNI_ALPHA, /* U+02b7e9: */
+ RTUNI_ALPHA, /* U+02b7ea: */
+ RTUNI_ALPHA, /* U+02b7eb: */
+ RTUNI_ALPHA, /* U+02b7ec: */
+ RTUNI_ALPHA, /* U+02b7ed: */
+ RTUNI_ALPHA, /* U+02b7ee: */
+ RTUNI_ALPHA, /* U+02b7ef: */
+ RTUNI_ALPHA, /* U+02b7f0: */
+ RTUNI_ALPHA, /* U+02b7f1: */
+ RTUNI_ALPHA, /* U+02b7f2: */
+ RTUNI_ALPHA, /* U+02b7f3: */
+ RTUNI_ALPHA, /* U+02b7f4: */
+ RTUNI_ALPHA, /* U+02b7f5: */
+ RTUNI_ALPHA, /* U+02b7f6: */
+ RTUNI_ALPHA, /* U+02b7f7: */
+ RTUNI_ALPHA, /* U+02b7f8: */
+ RTUNI_ALPHA, /* U+02b7f9: */
+ RTUNI_ALPHA, /* U+02b7fa: */
+ RTUNI_ALPHA, /* U+02b7fb: */
+ RTUNI_ALPHA, /* U+02b7fc: */
+ RTUNI_ALPHA, /* U+02b7fd: */
+ RTUNI_ALPHA, /* U+02b7fe: */
+ RTUNI_ALPHA, /* U+02b7ff: */
+ RTUNI_ALPHA, /* U+02b800: */
+ RTUNI_ALPHA, /* U+02b801: */
+ RTUNI_ALPHA, /* U+02b802: */
+ RTUNI_ALPHA, /* U+02b803: */
+ RTUNI_ALPHA, /* U+02b804: */
+ RTUNI_ALPHA, /* U+02b805: */
+ RTUNI_ALPHA, /* U+02b806: */
+ RTUNI_ALPHA, /* U+02b807: */
+ RTUNI_ALPHA, /* U+02b808: */
+ RTUNI_ALPHA, /* U+02b809: */
+ RTUNI_ALPHA, /* U+02b80a: */
+ RTUNI_ALPHA, /* U+02b80b: */
+ RTUNI_ALPHA, /* U+02b80c: */
+ RTUNI_ALPHA, /* U+02b80d: */
+ RTUNI_ALPHA, /* U+02b80e: */
+ RTUNI_ALPHA, /* U+02b80f: */
+ RTUNI_ALPHA, /* U+02b810: */
+ RTUNI_ALPHA, /* U+02b811: */
+ RTUNI_ALPHA, /* U+02b812: */
+ RTUNI_ALPHA, /* U+02b813: */
+ RTUNI_ALPHA, /* U+02b814: */
+ RTUNI_ALPHA, /* U+02b815: */
+ RTUNI_ALPHA, /* U+02b816: */
+ RTUNI_ALPHA, /* U+02b817: */
+ RTUNI_ALPHA, /* U+02b818: */
+ RTUNI_ALPHA, /* U+02b819: */
+ RTUNI_ALPHA, /* U+02b81a: */
+ RTUNI_ALPHA, /* U+02b81b: */
+ RTUNI_ALPHA, /* U+02b81c: */
+ RTUNI_ALPHA, /* U+02b81d: <CJK Ideograph Extension D, Last>*/
+};
+
+static const uint8_t g_afRTUniFlags0x02f800[] =
+{
+ RTUNI_ALPHA, /* U+02f800: CJK COMPATIBILITY IDEOGRAPH-2F800*/
+ RTUNI_ALPHA, /* U+02f801: CJK COMPATIBILITY IDEOGRAPH-2F801*/
+ RTUNI_ALPHA, /* U+02f802: CJK COMPATIBILITY IDEOGRAPH-2F802*/
+ RTUNI_ALPHA, /* U+02f803: CJK COMPATIBILITY IDEOGRAPH-2F803*/
+ RTUNI_ALPHA, /* U+02f804: CJK COMPATIBILITY IDEOGRAPH-2F804*/
+ RTUNI_ALPHA, /* U+02f805: CJK COMPATIBILITY IDEOGRAPH-2F805*/
+ RTUNI_ALPHA, /* U+02f806: CJK COMPATIBILITY IDEOGRAPH-2F806*/
+ RTUNI_ALPHA, /* U+02f807: CJK COMPATIBILITY IDEOGRAPH-2F807*/
+ RTUNI_ALPHA, /* U+02f808: CJK COMPATIBILITY IDEOGRAPH-2F808*/
+ RTUNI_ALPHA, /* U+02f809: CJK COMPATIBILITY IDEOGRAPH-2F809*/
+ RTUNI_ALPHA, /* U+02f80a: CJK COMPATIBILITY IDEOGRAPH-2F80A*/
+ RTUNI_ALPHA, /* U+02f80b: CJK COMPATIBILITY IDEOGRAPH-2F80B*/
+ RTUNI_ALPHA, /* U+02f80c: CJK COMPATIBILITY IDEOGRAPH-2F80C*/
+ RTUNI_ALPHA, /* U+02f80d: CJK COMPATIBILITY IDEOGRAPH-2F80D*/
+ RTUNI_ALPHA, /* U+02f80e: CJK COMPATIBILITY IDEOGRAPH-2F80E*/
+ RTUNI_ALPHA, /* U+02f80f: CJK COMPATIBILITY IDEOGRAPH-2F80F*/
+ RTUNI_ALPHA, /* U+02f810: CJK COMPATIBILITY IDEOGRAPH-2F810*/
+ RTUNI_ALPHA, /* U+02f811: CJK COMPATIBILITY IDEOGRAPH-2F811*/
+ RTUNI_ALPHA, /* U+02f812: CJK COMPATIBILITY IDEOGRAPH-2F812*/
+ RTUNI_ALPHA, /* U+02f813: CJK COMPATIBILITY IDEOGRAPH-2F813*/
+ RTUNI_ALPHA, /* U+02f814: CJK COMPATIBILITY IDEOGRAPH-2F814*/
+ RTUNI_ALPHA, /* U+02f815: CJK COMPATIBILITY IDEOGRAPH-2F815*/
+ RTUNI_ALPHA, /* U+02f816: CJK COMPATIBILITY IDEOGRAPH-2F816*/
+ RTUNI_ALPHA, /* U+02f817: CJK COMPATIBILITY IDEOGRAPH-2F817*/
+ RTUNI_ALPHA, /* U+02f818: CJK COMPATIBILITY IDEOGRAPH-2F818*/
+ RTUNI_ALPHA, /* U+02f819: CJK COMPATIBILITY IDEOGRAPH-2F819*/
+ RTUNI_ALPHA, /* U+02f81a: CJK COMPATIBILITY IDEOGRAPH-2F81A*/
+ RTUNI_ALPHA, /* U+02f81b: CJK COMPATIBILITY IDEOGRAPH-2F81B*/
+ RTUNI_ALPHA, /* U+02f81c: CJK COMPATIBILITY IDEOGRAPH-2F81C*/
+ RTUNI_ALPHA, /* U+02f81d: CJK COMPATIBILITY IDEOGRAPH-2F81D*/
+ RTUNI_ALPHA, /* U+02f81e: CJK COMPATIBILITY IDEOGRAPH-2F81E*/
+ RTUNI_ALPHA, /* U+02f81f: CJK COMPATIBILITY IDEOGRAPH-2F81F*/
+ RTUNI_ALPHA, /* U+02f820: CJK COMPATIBILITY IDEOGRAPH-2F820*/
+ RTUNI_ALPHA, /* U+02f821: CJK COMPATIBILITY IDEOGRAPH-2F821*/
+ RTUNI_ALPHA, /* U+02f822: CJK COMPATIBILITY IDEOGRAPH-2F822*/
+ RTUNI_ALPHA, /* U+02f823: CJK COMPATIBILITY IDEOGRAPH-2F823*/
+ RTUNI_ALPHA, /* U+02f824: CJK COMPATIBILITY IDEOGRAPH-2F824*/
+ RTUNI_ALPHA, /* U+02f825: CJK COMPATIBILITY IDEOGRAPH-2F825*/
+ RTUNI_ALPHA, /* U+02f826: CJK COMPATIBILITY IDEOGRAPH-2F826*/
+ RTUNI_ALPHA, /* U+02f827: CJK COMPATIBILITY IDEOGRAPH-2F827*/
+ RTUNI_ALPHA, /* U+02f828: CJK COMPATIBILITY IDEOGRAPH-2F828*/
+ RTUNI_ALPHA, /* U+02f829: CJK COMPATIBILITY IDEOGRAPH-2F829*/
+ RTUNI_ALPHA, /* U+02f82a: CJK COMPATIBILITY IDEOGRAPH-2F82A*/
+ RTUNI_ALPHA, /* U+02f82b: CJK COMPATIBILITY IDEOGRAPH-2F82B*/
+ RTUNI_ALPHA, /* U+02f82c: CJK COMPATIBILITY IDEOGRAPH-2F82C*/
+ RTUNI_ALPHA, /* U+02f82d: CJK COMPATIBILITY IDEOGRAPH-2F82D*/
+ RTUNI_ALPHA, /* U+02f82e: CJK COMPATIBILITY IDEOGRAPH-2F82E*/
+ RTUNI_ALPHA, /* U+02f82f: CJK COMPATIBILITY IDEOGRAPH-2F82F*/
+ RTUNI_ALPHA, /* U+02f830: CJK COMPATIBILITY IDEOGRAPH-2F830*/
+ RTUNI_ALPHA, /* U+02f831: CJK COMPATIBILITY IDEOGRAPH-2F831*/
+ RTUNI_ALPHA, /* U+02f832: CJK COMPATIBILITY IDEOGRAPH-2F832*/
+ RTUNI_ALPHA, /* U+02f833: CJK COMPATIBILITY IDEOGRAPH-2F833*/
+ RTUNI_ALPHA, /* U+02f834: CJK COMPATIBILITY IDEOGRAPH-2F834*/
+ RTUNI_ALPHA, /* U+02f835: CJK COMPATIBILITY IDEOGRAPH-2F835*/
+ RTUNI_ALPHA, /* U+02f836: CJK COMPATIBILITY IDEOGRAPH-2F836*/
+ RTUNI_ALPHA, /* U+02f837: CJK COMPATIBILITY IDEOGRAPH-2F837*/
+ RTUNI_ALPHA, /* U+02f838: CJK COMPATIBILITY IDEOGRAPH-2F838*/
+ RTUNI_ALPHA, /* U+02f839: CJK COMPATIBILITY IDEOGRAPH-2F839*/
+ RTUNI_ALPHA, /* U+02f83a: CJK COMPATIBILITY IDEOGRAPH-2F83A*/
+ RTUNI_ALPHA, /* U+02f83b: CJK COMPATIBILITY IDEOGRAPH-2F83B*/
+ RTUNI_ALPHA, /* U+02f83c: CJK COMPATIBILITY IDEOGRAPH-2F83C*/
+ RTUNI_ALPHA, /* U+02f83d: CJK COMPATIBILITY IDEOGRAPH-2F83D*/
+ RTUNI_ALPHA, /* U+02f83e: CJK COMPATIBILITY IDEOGRAPH-2F83E*/
+ RTUNI_ALPHA, /* U+02f83f: CJK COMPATIBILITY IDEOGRAPH-2F83F*/
+ RTUNI_ALPHA, /* U+02f840: CJK COMPATIBILITY IDEOGRAPH-2F840*/
+ RTUNI_ALPHA, /* U+02f841: CJK COMPATIBILITY IDEOGRAPH-2F841*/
+ RTUNI_ALPHA, /* U+02f842: CJK COMPATIBILITY IDEOGRAPH-2F842*/
+ RTUNI_ALPHA, /* U+02f843: CJK COMPATIBILITY IDEOGRAPH-2F843*/
+ RTUNI_ALPHA, /* U+02f844: CJK COMPATIBILITY IDEOGRAPH-2F844*/
+ RTUNI_ALPHA, /* U+02f845: CJK COMPATIBILITY IDEOGRAPH-2F845*/
+ RTUNI_ALPHA, /* U+02f846: CJK COMPATIBILITY IDEOGRAPH-2F846*/
+ RTUNI_ALPHA, /* U+02f847: CJK COMPATIBILITY IDEOGRAPH-2F847*/
+ RTUNI_ALPHA, /* U+02f848: CJK COMPATIBILITY IDEOGRAPH-2F848*/
+ RTUNI_ALPHA, /* U+02f849: CJK COMPATIBILITY IDEOGRAPH-2F849*/
+ RTUNI_ALPHA, /* U+02f84a: CJK COMPATIBILITY IDEOGRAPH-2F84A*/
+ RTUNI_ALPHA, /* U+02f84b: CJK COMPATIBILITY IDEOGRAPH-2F84B*/
+ RTUNI_ALPHA, /* U+02f84c: CJK COMPATIBILITY IDEOGRAPH-2F84C*/
+ RTUNI_ALPHA, /* U+02f84d: CJK COMPATIBILITY IDEOGRAPH-2F84D*/
+ RTUNI_ALPHA, /* U+02f84e: CJK COMPATIBILITY IDEOGRAPH-2F84E*/
+ RTUNI_ALPHA, /* U+02f84f: CJK COMPATIBILITY IDEOGRAPH-2F84F*/
+ RTUNI_ALPHA, /* U+02f850: CJK COMPATIBILITY IDEOGRAPH-2F850*/
+ RTUNI_ALPHA, /* U+02f851: CJK COMPATIBILITY IDEOGRAPH-2F851*/
+ RTUNI_ALPHA, /* U+02f852: CJK COMPATIBILITY IDEOGRAPH-2F852*/
+ RTUNI_ALPHA, /* U+02f853: CJK COMPATIBILITY IDEOGRAPH-2F853*/
+ RTUNI_ALPHA, /* U+02f854: CJK COMPATIBILITY IDEOGRAPH-2F854*/
+ RTUNI_ALPHA, /* U+02f855: CJK COMPATIBILITY IDEOGRAPH-2F855*/
+ RTUNI_ALPHA, /* U+02f856: CJK COMPATIBILITY IDEOGRAPH-2F856*/
+ RTUNI_ALPHA, /* U+02f857: CJK COMPATIBILITY IDEOGRAPH-2F857*/
+ RTUNI_ALPHA, /* U+02f858: CJK COMPATIBILITY IDEOGRAPH-2F858*/
+ RTUNI_ALPHA, /* U+02f859: CJK COMPATIBILITY IDEOGRAPH-2F859*/
+ RTUNI_ALPHA, /* U+02f85a: CJK COMPATIBILITY IDEOGRAPH-2F85A*/
+ RTUNI_ALPHA, /* U+02f85b: CJK COMPATIBILITY IDEOGRAPH-2F85B*/
+ RTUNI_ALPHA, /* U+02f85c: CJK COMPATIBILITY IDEOGRAPH-2F85C*/
+ RTUNI_ALPHA, /* U+02f85d: CJK COMPATIBILITY IDEOGRAPH-2F85D*/
+ RTUNI_ALPHA, /* U+02f85e: CJK COMPATIBILITY IDEOGRAPH-2F85E*/
+ RTUNI_ALPHA, /* U+02f85f: CJK COMPATIBILITY IDEOGRAPH-2F85F*/
+ RTUNI_ALPHA, /* U+02f860: CJK COMPATIBILITY IDEOGRAPH-2F860*/
+ RTUNI_ALPHA, /* U+02f861: CJK COMPATIBILITY IDEOGRAPH-2F861*/
+ RTUNI_ALPHA, /* U+02f862: CJK COMPATIBILITY IDEOGRAPH-2F862*/
+ RTUNI_ALPHA, /* U+02f863: CJK COMPATIBILITY IDEOGRAPH-2F863*/
+ RTUNI_ALPHA, /* U+02f864: CJK COMPATIBILITY IDEOGRAPH-2F864*/
+ RTUNI_ALPHA, /* U+02f865: CJK COMPATIBILITY IDEOGRAPH-2F865*/
+ RTUNI_ALPHA, /* U+02f866: CJK COMPATIBILITY IDEOGRAPH-2F866*/
+ RTUNI_ALPHA, /* U+02f867: CJK COMPATIBILITY IDEOGRAPH-2F867*/
+ RTUNI_ALPHA, /* U+02f868: CJK COMPATIBILITY IDEOGRAPH-2F868*/
+ RTUNI_ALPHA, /* U+02f869: CJK COMPATIBILITY IDEOGRAPH-2F869*/
+ RTUNI_ALPHA, /* U+02f86a: CJK COMPATIBILITY IDEOGRAPH-2F86A*/
+ RTUNI_ALPHA, /* U+02f86b: CJK COMPATIBILITY IDEOGRAPH-2F86B*/
+ RTUNI_ALPHA, /* U+02f86c: CJK COMPATIBILITY IDEOGRAPH-2F86C*/
+ RTUNI_ALPHA, /* U+02f86d: CJK COMPATIBILITY IDEOGRAPH-2F86D*/
+ RTUNI_ALPHA, /* U+02f86e: CJK COMPATIBILITY IDEOGRAPH-2F86E*/
+ RTUNI_ALPHA, /* U+02f86f: CJK COMPATIBILITY IDEOGRAPH-2F86F*/
+ RTUNI_ALPHA, /* U+02f870: CJK COMPATIBILITY IDEOGRAPH-2F870*/
+ RTUNI_ALPHA, /* U+02f871: CJK COMPATIBILITY IDEOGRAPH-2F871*/
+ RTUNI_ALPHA, /* U+02f872: CJK COMPATIBILITY IDEOGRAPH-2F872*/
+ RTUNI_ALPHA, /* U+02f873: CJK COMPATIBILITY IDEOGRAPH-2F873*/
+ RTUNI_ALPHA, /* U+02f874: CJK COMPATIBILITY IDEOGRAPH-2F874*/
+ RTUNI_ALPHA, /* U+02f875: CJK COMPATIBILITY IDEOGRAPH-2F875*/
+ RTUNI_ALPHA, /* U+02f876: CJK COMPATIBILITY IDEOGRAPH-2F876*/
+ RTUNI_ALPHA, /* U+02f877: CJK COMPATIBILITY IDEOGRAPH-2F877*/
+ RTUNI_ALPHA, /* U+02f878: CJK COMPATIBILITY IDEOGRAPH-2F878*/
+ RTUNI_ALPHA, /* U+02f879: CJK COMPATIBILITY IDEOGRAPH-2F879*/
+ RTUNI_ALPHA, /* U+02f87a: CJK COMPATIBILITY IDEOGRAPH-2F87A*/
+ RTUNI_ALPHA, /* U+02f87b: CJK COMPATIBILITY IDEOGRAPH-2F87B*/
+ RTUNI_ALPHA, /* U+02f87c: CJK COMPATIBILITY IDEOGRAPH-2F87C*/
+ RTUNI_ALPHA, /* U+02f87d: CJK COMPATIBILITY IDEOGRAPH-2F87D*/
+ RTUNI_ALPHA, /* U+02f87e: CJK COMPATIBILITY IDEOGRAPH-2F87E*/
+ RTUNI_ALPHA, /* U+02f87f: CJK COMPATIBILITY IDEOGRAPH-2F87F*/
+ RTUNI_ALPHA, /* U+02f880: CJK COMPATIBILITY IDEOGRAPH-2F880*/
+ RTUNI_ALPHA, /* U+02f881: CJK COMPATIBILITY IDEOGRAPH-2F881*/
+ RTUNI_ALPHA, /* U+02f882: CJK COMPATIBILITY IDEOGRAPH-2F882*/
+ RTUNI_ALPHA, /* U+02f883: CJK COMPATIBILITY IDEOGRAPH-2F883*/
+ RTUNI_ALPHA, /* U+02f884: CJK COMPATIBILITY IDEOGRAPH-2F884*/
+ RTUNI_ALPHA, /* U+02f885: CJK COMPATIBILITY IDEOGRAPH-2F885*/
+ RTUNI_ALPHA, /* U+02f886: CJK COMPATIBILITY IDEOGRAPH-2F886*/
+ RTUNI_ALPHA, /* U+02f887: CJK COMPATIBILITY IDEOGRAPH-2F887*/
+ RTUNI_ALPHA, /* U+02f888: CJK COMPATIBILITY IDEOGRAPH-2F888*/
+ RTUNI_ALPHA, /* U+02f889: CJK COMPATIBILITY IDEOGRAPH-2F889*/
+ RTUNI_ALPHA, /* U+02f88a: CJK COMPATIBILITY IDEOGRAPH-2F88A*/
+ RTUNI_ALPHA, /* U+02f88b: CJK COMPATIBILITY IDEOGRAPH-2F88B*/
+ RTUNI_ALPHA, /* U+02f88c: CJK COMPATIBILITY IDEOGRAPH-2F88C*/
+ RTUNI_ALPHA, /* U+02f88d: CJK COMPATIBILITY IDEOGRAPH-2F88D*/
+ RTUNI_ALPHA, /* U+02f88e: CJK COMPATIBILITY IDEOGRAPH-2F88E*/
+ RTUNI_ALPHA, /* U+02f88f: CJK COMPATIBILITY IDEOGRAPH-2F88F*/
+ RTUNI_ALPHA, /* U+02f890: CJK COMPATIBILITY IDEOGRAPH-2F890*/
+ RTUNI_ALPHA, /* U+02f891: CJK COMPATIBILITY IDEOGRAPH-2F891*/
+ RTUNI_ALPHA, /* U+02f892: CJK COMPATIBILITY IDEOGRAPH-2F892*/
+ RTUNI_ALPHA, /* U+02f893: CJK COMPATIBILITY IDEOGRAPH-2F893*/
+ RTUNI_ALPHA, /* U+02f894: CJK COMPATIBILITY IDEOGRAPH-2F894*/
+ RTUNI_ALPHA, /* U+02f895: CJK COMPATIBILITY IDEOGRAPH-2F895*/
+ RTUNI_ALPHA, /* U+02f896: CJK COMPATIBILITY IDEOGRAPH-2F896*/
+ RTUNI_ALPHA, /* U+02f897: CJK COMPATIBILITY IDEOGRAPH-2F897*/
+ RTUNI_ALPHA, /* U+02f898: CJK COMPATIBILITY IDEOGRAPH-2F898*/
+ RTUNI_ALPHA, /* U+02f899: CJK COMPATIBILITY IDEOGRAPH-2F899*/
+ RTUNI_ALPHA, /* U+02f89a: CJK COMPATIBILITY IDEOGRAPH-2F89A*/
+ RTUNI_ALPHA, /* U+02f89b: CJK COMPATIBILITY IDEOGRAPH-2F89B*/
+ RTUNI_ALPHA, /* U+02f89c: CJK COMPATIBILITY IDEOGRAPH-2F89C*/
+ RTUNI_ALPHA, /* U+02f89d: CJK COMPATIBILITY IDEOGRAPH-2F89D*/
+ RTUNI_ALPHA, /* U+02f89e: CJK COMPATIBILITY IDEOGRAPH-2F89E*/
+ RTUNI_ALPHA, /* U+02f89f: CJK COMPATIBILITY IDEOGRAPH-2F89F*/
+ RTUNI_ALPHA, /* U+02f8a0: CJK COMPATIBILITY IDEOGRAPH-2F8A0*/
+ RTUNI_ALPHA, /* U+02f8a1: CJK COMPATIBILITY IDEOGRAPH-2F8A1*/
+ RTUNI_ALPHA, /* U+02f8a2: CJK COMPATIBILITY IDEOGRAPH-2F8A2*/
+ RTUNI_ALPHA, /* U+02f8a3: CJK COMPATIBILITY IDEOGRAPH-2F8A3*/
+ RTUNI_ALPHA, /* U+02f8a4: CJK COMPATIBILITY IDEOGRAPH-2F8A4*/
+ RTUNI_ALPHA, /* U+02f8a5: CJK COMPATIBILITY IDEOGRAPH-2F8A5*/
+ RTUNI_ALPHA, /* U+02f8a6: CJK COMPATIBILITY IDEOGRAPH-2F8A6*/
+ RTUNI_ALPHA, /* U+02f8a7: CJK COMPATIBILITY IDEOGRAPH-2F8A7*/
+ RTUNI_ALPHA, /* U+02f8a8: CJK COMPATIBILITY IDEOGRAPH-2F8A8*/
+ RTUNI_ALPHA, /* U+02f8a9: CJK COMPATIBILITY IDEOGRAPH-2F8A9*/
+ RTUNI_ALPHA, /* U+02f8aa: CJK COMPATIBILITY IDEOGRAPH-2F8AA*/
+ RTUNI_ALPHA, /* U+02f8ab: CJK COMPATIBILITY IDEOGRAPH-2F8AB*/
+ RTUNI_ALPHA, /* U+02f8ac: CJK COMPATIBILITY IDEOGRAPH-2F8AC*/
+ RTUNI_ALPHA, /* U+02f8ad: CJK COMPATIBILITY IDEOGRAPH-2F8AD*/
+ RTUNI_ALPHA, /* U+02f8ae: CJK COMPATIBILITY IDEOGRAPH-2F8AE*/
+ RTUNI_ALPHA, /* U+02f8af: CJK COMPATIBILITY IDEOGRAPH-2F8AF*/
+ RTUNI_ALPHA, /* U+02f8b0: CJK COMPATIBILITY IDEOGRAPH-2F8B0*/
+ RTUNI_ALPHA, /* U+02f8b1: CJK COMPATIBILITY IDEOGRAPH-2F8B1*/
+ RTUNI_ALPHA, /* U+02f8b2: CJK COMPATIBILITY IDEOGRAPH-2F8B2*/
+ RTUNI_ALPHA, /* U+02f8b3: CJK COMPATIBILITY IDEOGRAPH-2F8B3*/
+ RTUNI_ALPHA, /* U+02f8b4: CJK COMPATIBILITY IDEOGRAPH-2F8B4*/
+ RTUNI_ALPHA, /* U+02f8b5: CJK COMPATIBILITY IDEOGRAPH-2F8B5*/
+ RTUNI_ALPHA, /* U+02f8b6: CJK COMPATIBILITY IDEOGRAPH-2F8B6*/
+ RTUNI_ALPHA, /* U+02f8b7: CJK COMPATIBILITY IDEOGRAPH-2F8B7*/
+ RTUNI_ALPHA, /* U+02f8b8: CJK COMPATIBILITY IDEOGRAPH-2F8B8*/
+ RTUNI_ALPHA, /* U+02f8b9: CJK COMPATIBILITY IDEOGRAPH-2F8B9*/
+ RTUNI_ALPHA, /* U+02f8ba: CJK COMPATIBILITY IDEOGRAPH-2F8BA*/
+ RTUNI_ALPHA, /* U+02f8bb: CJK COMPATIBILITY IDEOGRAPH-2F8BB*/
+ RTUNI_ALPHA, /* U+02f8bc: CJK COMPATIBILITY IDEOGRAPH-2F8BC*/
+ RTUNI_ALPHA, /* U+02f8bd: CJK COMPATIBILITY IDEOGRAPH-2F8BD*/
+ RTUNI_ALPHA, /* U+02f8be: CJK COMPATIBILITY IDEOGRAPH-2F8BE*/
+ RTUNI_ALPHA, /* U+02f8bf: CJK COMPATIBILITY IDEOGRAPH-2F8BF*/
+ RTUNI_ALPHA, /* U+02f8c0: CJK COMPATIBILITY IDEOGRAPH-2F8C0*/
+ RTUNI_ALPHA, /* U+02f8c1: CJK COMPATIBILITY IDEOGRAPH-2F8C1*/
+ RTUNI_ALPHA, /* U+02f8c2: CJK COMPATIBILITY IDEOGRAPH-2F8C2*/
+ RTUNI_ALPHA, /* U+02f8c3: CJK COMPATIBILITY IDEOGRAPH-2F8C3*/
+ RTUNI_ALPHA, /* U+02f8c4: CJK COMPATIBILITY IDEOGRAPH-2F8C4*/
+ RTUNI_ALPHA, /* U+02f8c5: CJK COMPATIBILITY IDEOGRAPH-2F8C5*/
+ RTUNI_ALPHA, /* U+02f8c6: CJK COMPATIBILITY IDEOGRAPH-2F8C6*/
+ RTUNI_ALPHA, /* U+02f8c7: CJK COMPATIBILITY IDEOGRAPH-2F8C7*/
+ RTUNI_ALPHA, /* U+02f8c8: CJK COMPATIBILITY IDEOGRAPH-2F8C8*/
+ RTUNI_ALPHA, /* U+02f8c9: CJK COMPATIBILITY IDEOGRAPH-2F8C9*/
+ RTUNI_ALPHA, /* U+02f8ca: CJK COMPATIBILITY IDEOGRAPH-2F8CA*/
+ RTUNI_ALPHA, /* U+02f8cb: CJK COMPATIBILITY IDEOGRAPH-2F8CB*/
+ RTUNI_ALPHA, /* U+02f8cc: CJK COMPATIBILITY IDEOGRAPH-2F8CC*/
+ RTUNI_ALPHA, /* U+02f8cd: CJK COMPATIBILITY IDEOGRAPH-2F8CD*/
+ RTUNI_ALPHA, /* U+02f8ce: CJK COMPATIBILITY IDEOGRAPH-2F8CE*/
+ RTUNI_ALPHA, /* U+02f8cf: CJK COMPATIBILITY IDEOGRAPH-2F8CF*/
+ RTUNI_ALPHA, /* U+02f8d0: CJK COMPATIBILITY IDEOGRAPH-2F8D0*/
+ RTUNI_ALPHA, /* U+02f8d1: CJK COMPATIBILITY IDEOGRAPH-2F8D1*/
+ RTUNI_ALPHA, /* U+02f8d2: CJK COMPATIBILITY IDEOGRAPH-2F8D2*/
+ RTUNI_ALPHA, /* U+02f8d3: CJK COMPATIBILITY IDEOGRAPH-2F8D3*/
+ RTUNI_ALPHA, /* U+02f8d4: CJK COMPATIBILITY IDEOGRAPH-2F8D4*/
+ RTUNI_ALPHA, /* U+02f8d5: CJK COMPATIBILITY IDEOGRAPH-2F8D5*/
+ RTUNI_ALPHA, /* U+02f8d6: CJK COMPATIBILITY IDEOGRAPH-2F8D6*/
+ RTUNI_ALPHA, /* U+02f8d7: CJK COMPATIBILITY IDEOGRAPH-2F8D7*/
+ RTUNI_ALPHA, /* U+02f8d8: CJK COMPATIBILITY IDEOGRAPH-2F8D8*/
+ RTUNI_ALPHA, /* U+02f8d9: CJK COMPATIBILITY IDEOGRAPH-2F8D9*/
+ RTUNI_ALPHA, /* U+02f8da: CJK COMPATIBILITY IDEOGRAPH-2F8DA*/
+ RTUNI_ALPHA, /* U+02f8db: CJK COMPATIBILITY IDEOGRAPH-2F8DB*/
+ RTUNI_ALPHA, /* U+02f8dc: CJK COMPATIBILITY IDEOGRAPH-2F8DC*/
+ RTUNI_ALPHA, /* U+02f8dd: CJK COMPATIBILITY IDEOGRAPH-2F8DD*/
+ RTUNI_ALPHA, /* U+02f8de: CJK COMPATIBILITY IDEOGRAPH-2F8DE*/
+ RTUNI_ALPHA, /* U+02f8df: CJK COMPATIBILITY IDEOGRAPH-2F8DF*/
+ RTUNI_ALPHA, /* U+02f8e0: CJK COMPATIBILITY IDEOGRAPH-2F8E0*/
+ RTUNI_ALPHA, /* U+02f8e1: CJK COMPATIBILITY IDEOGRAPH-2F8E1*/
+ RTUNI_ALPHA, /* U+02f8e2: CJK COMPATIBILITY IDEOGRAPH-2F8E2*/
+ RTUNI_ALPHA, /* U+02f8e3: CJK COMPATIBILITY IDEOGRAPH-2F8E3*/
+ RTUNI_ALPHA, /* U+02f8e4: CJK COMPATIBILITY IDEOGRAPH-2F8E4*/
+ RTUNI_ALPHA, /* U+02f8e5: CJK COMPATIBILITY IDEOGRAPH-2F8E5*/
+ RTUNI_ALPHA, /* U+02f8e6: CJK COMPATIBILITY IDEOGRAPH-2F8E6*/
+ RTUNI_ALPHA, /* U+02f8e7: CJK COMPATIBILITY IDEOGRAPH-2F8E7*/
+ RTUNI_ALPHA, /* U+02f8e8: CJK COMPATIBILITY IDEOGRAPH-2F8E8*/
+ RTUNI_ALPHA, /* U+02f8e9: CJK COMPATIBILITY IDEOGRAPH-2F8E9*/
+ RTUNI_ALPHA, /* U+02f8ea: CJK COMPATIBILITY IDEOGRAPH-2F8EA*/
+ RTUNI_ALPHA, /* U+02f8eb: CJK COMPATIBILITY IDEOGRAPH-2F8EB*/
+ RTUNI_ALPHA, /* U+02f8ec: CJK COMPATIBILITY IDEOGRAPH-2F8EC*/
+ RTUNI_ALPHA, /* U+02f8ed: CJK COMPATIBILITY IDEOGRAPH-2F8ED*/
+ RTUNI_ALPHA, /* U+02f8ee: CJK COMPATIBILITY IDEOGRAPH-2F8EE*/
+ RTUNI_ALPHA, /* U+02f8ef: CJK COMPATIBILITY IDEOGRAPH-2F8EF*/
+ RTUNI_ALPHA, /* U+02f8f0: CJK COMPATIBILITY IDEOGRAPH-2F8F0*/
+ RTUNI_ALPHA, /* U+02f8f1: CJK COMPATIBILITY IDEOGRAPH-2F8F1*/
+ RTUNI_ALPHA, /* U+02f8f2: CJK COMPATIBILITY IDEOGRAPH-2F8F2*/
+ RTUNI_ALPHA, /* U+02f8f3: CJK COMPATIBILITY IDEOGRAPH-2F8F3*/
+ RTUNI_ALPHA, /* U+02f8f4: CJK COMPATIBILITY IDEOGRAPH-2F8F4*/
+ RTUNI_ALPHA, /* U+02f8f5: CJK COMPATIBILITY IDEOGRAPH-2F8F5*/
+ RTUNI_ALPHA, /* U+02f8f6: CJK COMPATIBILITY IDEOGRAPH-2F8F6*/
+ RTUNI_ALPHA, /* U+02f8f7: CJK COMPATIBILITY IDEOGRAPH-2F8F7*/
+ RTUNI_ALPHA, /* U+02f8f8: CJK COMPATIBILITY IDEOGRAPH-2F8F8*/
+ RTUNI_ALPHA, /* U+02f8f9: CJK COMPATIBILITY IDEOGRAPH-2F8F9*/
+ RTUNI_ALPHA, /* U+02f8fa: CJK COMPATIBILITY IDEOGRAPH-2F8FA*/
+ RTUNI_ALPHA, /* U+02f8fb: CJK COMPATIBILITY IDEOGRAPH-2F8FB*/
+ RTUNI_ALPHA, /* U+02f8fc: CJK COMPATIBILITY IDEOGRAPH-2F8FC*/
+ RTUNI_ALPHA, /* U+02f8fd: CJK COMPATIBILITY IDEOGRAPH-2F8FD*/
+ RTUNI_ALPHA, /* U+02f8fe: CJK COMPATIBILITY IDEOGRAPH-2F8FE*/
+ RTUNI_ALPHA, /* U+02f8ff: CJK COMPATIBILITY IDEOGRAPH-2F8FF*/
+ RTUNI_ALPHA, /* U+02f900: CJK COMPATIBILITY IDEOGRAPH-2F900*/
+ RTUNI_ALPHA, /* U+02f901: CJK COMPATIBILITY IDEOGRAPH-2F901*/
+ RTUNI_ALPHA, /* U+02f902: CJK COMPATIBILITY IDEOGRAPH-2F902*/
+ RTUNI_ALPHA, /* U+02f903: CJK COMPATIBILITY IDEOGRAPH-2F903*/
+ RTUNI_ALPHA, /* U+02f904: CJK COMPATIBILITY IDEOGRAPH-2F904*/
+ RTUNI_ALPHA, /* U+02f905: CJK COMPATIBILITY IDEOGRAPH-2F905*/
+ RTUNI_ALPHA, /* U+02f906: CJK COMPATIBILITY IDEOGRAPH-2F906*/
+ RTUNI_ALPHA, /* U+02f907: CJK COMPATIBILITY IDEOGRAPH-2F907*/
+ RTUNI_ALPHA, /* U+02f908: CJK COMPATIBILITY IDEOGRAPH-2F908*/
+ RTUNI_ALPHA, /* U+02f909: CJK COMPATIBILITY IDEOGRAPH-2F909*/
+ RTUNI_ALPHA, /* U+02f90a: CJK COMPATIBILITY IDEOGRAPH-2F90A*/
+ RTUNI_ALPHA, /* U+02f90b: CJK COMPATIBILITY IDEOGRAPH-2F90B*/
+ RTUNI_ALPHA, /* U+02f90c: CJK COMPATIBILITY IDEOGRAPH-2F90C*/
+ RTUNI_ALPHA, /* U+02f90d: CJK COMPATIBILITY IDEOGRAPH-2F90D*/
+ RTUNI_ALPHA, /* U+02f90e: CJK COMPATIBILITY IDEOGRAPH-2F90E*/
+ RTUNI_ALPHA, /* U+02f90f: CJK COMPATIBILITY IDEOGRAPH-2F90F*/
+ RTUNI_ALPHA, /* U+02f910: CJK COMPATIBILITY IDEOGRAPH-2F910*/
+ RTUNI_ALPHA, /* U+02f911: CJK COMPATIBILITY IDEOGRAPH-2F911*/
+ RTUNI_ALPHA, /* U+02f912: CJK COMPATIBILITY IDEOGRAPH-2F912*/
+ RTUNI_ALPHA, /* U+02f913: CJK COMPATIBILITY IDEOGRAPH-2F913*/
+ RTUNI_ALPHA, /* U+02f914: CJK COMPATIBILITY IDEOGRAPH-2F914*/
+ RTUNI_ALPHA, /* U+02f915: CJK COMPATIBILITY IDEOGRAPH-2F915*/
+ RTUNI_ALPHA, /* U+02f916: CJK COMPATIBILITY IDEOGRAPH-2F916*/
+ RTUNI_ALPHA, /* U+02f917: CJK COMPATIBILITY IDEOGRAPH-2F917*/
+ RTUNI_ALPHA, /* U+02f918: CJK COMPATIBILITY IDEOGRAPH-2F918*/
+ RTUNI_ALPHA, /* U+02f919: CJK COMPATIBILITY IDEOGRAPH-2F919*/
+ RTUNI_ALPHA, /* U+02f91a: CJK COMPATIBILITY IDEOGRAPH-2F91A*/
+ RTUNI_ALPHA, /* U+02f91b: CJK COMPATIBILITY IDEOGRAPH-2F91B*/
+ RTUNI_ALPHA, /* U+02f91c: CJK COMPATIBILITY IDEOGRAPH-2F91C*/
+ RTUNI_ALPHA, /* U+02f91d: CJK COMPATIBILITY IDEOGRAPH-2F91D*/
+ RTUNI_ALPHA, /* U+02f91e: CJK COMPATIBILITY IDEOGRAPH-2F91E*/
+ RTUNI_ALPHA, /* U+02f91f: CJK COMPATIBILITY IDEOGRAPH-2F91F*/
+ RTUNI_ALPHA, /* U+02f920: CJK COMPATIBILITY IDEOGRAPH-2F920*/
+ RTUNI_ALPHA, /* U+02f921: CJK COMPATIBILITY IDEOGRAPH-2F921*/
+ RTUNI_ALPHA, /* U+02f922: CJK COMPATIBILITY IDEOGRAPH-2F922*/
+ RTUNI_ALPHA, /* U+02f923: CJK COMPATIBILITY IDEOGRAPH-2F923*/
+ RTUNI_ALPHA, /* U+02f924: CJK COMPATIBILITY IDEOGRAPH-2F924*/
+ RTUNI_ALPHA, /* U+02f925: CJK COMPATIBILITY IDEOGRAPH-2F925*/
+ RTUNI_ALPHA, /* U+02f926: CJK COMPATIBILITY IDEOGRAPH-2F926*/
+ RTUNI_ALPHA, /* U+02f927: CJK COMPATIBILITY IDEOGRAPH-2F927*/
+ RTUNI_ALPHA, /* U+02f928: CJK COMPATIBILITY IDEOGRAPH-2F928*/
+ RTUNI_ALPHA, /* U+02f929: CJK COMPATIBILITY IDEOGRAPH-2F929*/
+ RTUNI_ALPHA, /* U+02f92a: CJK COMPATIBILITY IDEOGRAPH-2F92A*/
+ RTUNI_ALPHA, /* U+02f92b: CJK COMPATIBILITY IDEOGRAPH-2F92B*/
+ RTUNI_ALPHA, /* U+02f92c: CJK COMPATIBILITY IDEOGRAPH-2F92C*/
+ RTUNI_ALPHA, /* U+02f92d: CJK COMPATIBILITY IDEOGRAPH-2F92D*/
+ RTUNI_ALPHA, /* U+02f92e: CJK COMPATIBILITY IDEOGRAPH-2F92E*/
+ RTUNI_ALPHA, /* U+02f92f: CJK COMPATIBILITY IDEOGRAPH-2F92F*/
+ RTUNI_ALPHA, /* U+02f930: CJK COMPATIBILITY IDEOGRAPH-2F930*/
+ RTUNI_ALPHA, /* U+02f931: CJK COMPATIBILITY IDEOGRAPH-2F931*/
+ RTUNI_ALPHA, /* U+02f932: CJK COMPATIBILITY IDEOGRAPH-2F932*/
+ RTUNI_ALPHA, /* U+02f933: CJK COMPATIBILITY IDEOGRAPH-2F933*/
+ RTUNI_ALPHA, /* U+02f934: CJK COMPATIBILITY IDEOGRAPH-2F934*/
+ RTUNI_ALPHA, /* U+02f935: CJK COMPATIBILITY IDEOGRAPH-2F935*/
+ RTUNI_ALPHA, /* U+02f936: CJK COMPATIBILITY IDEOGRAPH-2F936*/
+ RTUNI_ALPHA, /* U+02f937: CJK COMPATIBILITY IDEOGRAPH-2F937*/
+ RTUNI_ALPHA, /* U+02f938: CJK COMPATIBILITY IDEOGRAPH-2F938*/
+ RTUNI_ALPHA, /* U+02f939: CJK COMPATIBILITY IDEOGRAPH-2F939*/
+ RTUNI_ALPHA, /* U+02f93a: CJK COMPATIBILITY IDEOGRAPH-2F93A*/
+ RTUNI_ALPHA, /* U+02f93b: CJK COMPATIBILITY IDEOGRAPH-2F93B*/
+ RTUNI_ALPHA, /* U+02f93c: CJK COMPATIBILITY IDEOGRAPH-2F93C*/
+ RTUNI_ALPHA, /* U+02f93d: CJK COMPATIBILITY IDEOGRAPH-2F93D*/
+ RTUNI_ALPHA, /* U+02f93e: CJK COMPATIBILITY IDEOGRAPH-2F93E*/
+ RTUNI_ALPHA, /* U+02f93f: CJK COMPATIBILITY IDEOGRAPH-2F93F*/
+ RTUNI_ALPHA, /* U+02f940: CJK COMPATIBILITY IDEOGRAPH-2F940*/
+ RTUNI_ALPHA, /* U+02f941: CJK COMPATIBILITY IDEOGRAPH-2F941*/
+ RTUNI_ALPHA, /* U+02f942: CJK COMPATIBILITY IDEOGRAPH-2F942*/
+ RTUNI_ALPHA, /* U+02f943: CJK COMPATIBILITY IDEOGRAPH-2F943*/
+ RTUNI_ALPHA, /* U+02f944: CJK COMPATIBILITY IDEOGRAPH-2F944*/
+ RTUNI_ALPHA, /* U+02f945: CJK COMPATIBILITY IDEOGRAPH-2F945*/
+ RTUNI_ALPHA, /* U+02f946: CJK COMPATIBILITY IDEOGRAPH-2F946*/
+ RTUNI_ALPHA, /* U+02f947: CJK COMPATIBILITY IDEOGRAPH-2F947*/
+ RTUNI_ALPHA, /* U+02f948: CJK COMPATIBILITY IDEOGRAPH-2F948*/
+ RTUNI_ALPHA, /* U+02f949: CJK COMPATIBILITY IDEOGRAPH-2F949*/
+ RTUNI_ALPHA, /* U+02f94a: CJK COMPATIBILITY IDEOGRAPH-2F94A*/
+ RTUNI_ALPHA, /* U+02f94b: CJK COMPATIBILITY IDEOGRAPH-2F94B*/
+ RTUNI_ALPHA, /* U+02f94c: CJK COMPATIBILITY IDEOGRAPH-2F94C*/
+ RTUNI_ALPHA, /* U+02f94d: CJK COMPATIBILITY IDEOGRAPH-2F94D*/
+ RTUNI_ALPHA, /* U+02f94e: CJK COMPATIBILITY IDEOGRAPH-2F94E*/
+ RTUNI_ALPHA, /* U+02f94f: CJK COMPATIBILITY IDEOGRAPH-2F94F*/
+ RTUNI_ALPHA, /* U+02f950: CJK COMPATIBILITY IDEOGRAPH-2F950*/
+ RTUNI_ALPHA, /* U+02f951: CJK COMPATIBILITY IDEOGRAPH-2F951*/
+ RTUNI_ALPHA, /* U+02f952: CJK COMPATIBILITY IDEOGRAPH-2F952*/
+ RTUNI_ALPHA, /* U+02f953: CJK COMPATIBILITY IDEOGRAPH-2F953*/
+ RTUNI_ALPHA, /* U+02f954: CJK COMPATIBILITY IDEOGRAPH-2F954*/
+ RTUNI_ALPHA, /* U+02f955: CJK COMPATIBILITY IDEOGRAPH-2F955*/
+ RTUNI_ALPHA, /* U+02f956: CJK COMPATIBILITY IDEOGRAPH-2F956*/
+ RTUNI_ALPHA, /* U+02f957: CJK COMPATIBILITY IDEOGRAPH-2F957*/
+ RTUNI_ALPHA, /* U+02f958: CJK COMPATIBILITY IDEOGRAPH-2F958*/
+ RTUNI_ALPHA, /* U+02f959: CJK COMPATIBILITY IDEOGRAPH-2F959*/
+ RTUNI_ALPHA, /* U+02f95a: CJK COMPATIBILITY IDEOGRAPH-2F95A*/
+ RTUNI_ALPHA, /* U+02f95b: CJK COMPATIBILITY IDEOGRAPH-2F95B*/
+ RTUNI_ALPHA, /* U+02f95c: CJK COMPATIBILITY IDEOGRAPH-2F95C*/
+ RTUNI_ALPHA, /* U+02f95d: CJK COMPATIBILITY IDEOGRAPH-2F95D*/
+ RTUNI_ALPHA, /* U+02f95e: CJK COMPATIBILITY IDEOGRAPH-2F95E*/
+ RTUNI_ALPHA, /* U+02f95f: CJK COMPATIBILITY IDEOGRAPH-2F95F*/
+ RTUNI_ALPHA, /* U+02f960: CJK COMPATIBILITY IDEOGRAPH-2F960*/
+ RTUNI_ALPHA, /* U+02f961: CJK COMPATIBILITY IDEOGRAPH-2F961*/
+ RTUNI_ALPHA, /* U+02f962: CJK COMPATIBILITY IDEOGRAPH-2F962*/
+ RTUNI_ALPHA, /* U+02f963: CJK COMPATIBILITY IDEOGRAPH-2F963*/
+ RTUNI_ALPHA, /* U+02f964: CJK COMPATIBILITY IDEOGRAPH-2F964*/
+ RTUNI_ALPHA, /* U+02f965: CJK COMPATIBILITY IDEOGRAPH-2F965*/
+ RTUNI_ALPHA, /* U+02f966: CJK COMPATIBILITY IDEOGRAPH-2F966*/
+ RTUNI_ALPHA, /* U+02f967: CJK COMPATIBILITY IDEOGRAPH-2F967*/
+ RTUNI_ALPHA, /* U+02f968: CJK COMPATIBILITY IDEOGRAPH-2F968*/
+ RTUNI_ALPHA, /* U+02f969: CJK COMPATIBILITY IDEOGRAPH-2F969*/
+ RTUNI_ALPHA, /* U+02f96a: CJK COMPATIBILITY IDEOGRAPH-2F96A*/
+ RTUNI_ALPHA, /* U+02f96b: CJK COMPATIBILITY IDEOGRAPH-2F96B*/
+ RTUNI_ALPHA, /* U+02f96c: CJK COMPATIBILITY IDEOGRAPH-2F96C*/
+ RTUNI_ALPHA, /* U+02f96d: CJK COMPATIBILITY IDEOGRAPH-2F96D*/
+ RTUNI_ALPHA, /* U+02f96e: CJK COMPATIBILITY IDEOGRAPH-2F96E*/
+ RTUNI_ALPHA, /* U+02f96f: CJK COMPATIBILITY IDEOGRAPH-2F96F*/
+ RTUNI_ALPHA, /* U+02f970: CJK COMPATIBILITY IDEOGRAPH-2F970*/
+ RTUNI_ALPHA, /* U+02f971: CJK COMPATIBILITY IDEOGRAPH-2F971*/
+ RTUNI_ALPHA, /* U+02f972: CJK COMPATIBILITY IDEOGRAPH-2F972*/
+ RTUNI_ALPHA, /* U+02f973: CJK COMPATIBILITY IDEOGRAPH-2F973*/
+ RTUNI_ALPHA, /* U+02f974: CJK COMPATIBILITY IDEOGRAPH-2F974*/
+ RTUNI_ALPHA, /* U+02f975: CJK COMPATIBILITY IDEOGRAPH-2F975*/
+ RTUNI_ALPHA, /* U+02f976: CJK COMPATIBILITY IDEOGRAPH-2F976*/
+ RTUNI_ALPHA, /* U+02f977: CJK COMPATIBILITY IDEOGRAPH-2F977*/
+ RTUNI_ALPHA, /* U+02f978: CJK COMPATIBILITY IDEOGRAPH-2F978*/
+ RTUNI_ALPHA, /* U+02f979: CJK COMPATIBILITY IDEOGRAPH-2F979*/
+ RTUNI_ALPHA, /* U+02f97a: CJK COMPATIBILITY IDEOGRAPH-2F97A*/
+ RTUNI_ALPHA, /* U+02f97b: CJK COMPATIBILITY IDEOGRAPH-2F97B*/
+ RTUNI_ALPHA, /* U+02f97c: CJK COMPATIBILITY IDEOGRAPH-2F97C*/
+ RTUNI_ALPHA, /* U+02f97d: CJK COMPATIBILITY IDEOGRAPH-2F97D*/
+ RTUNI_ALPHA, /* U+02f97e: CJK COMPATIBILITY IDEOGRAPH-2F97E*/
+ RTUNI_ALPHA, /* U+02f97f: CJK COMPATIBILITY IDEOGRAPH-2F97F*/
+ RTUNI_ALPHA, /* U+02f980: CJK COMPATIBILITY IDEOGRAPH-2F980*/
+ RTUNI_ALPHA, /* U+02f981: CJK COMPATIBILITY IDEOGRAPH-2F981*/
+ RTUNI_ALPHA, /* U+02f982: CJK COMPATIBILITY IDEOGRAPH-2F982*/
+ RTUNI_ALPHA, /* U+02f983: CJK COMPATIBILITY IDEOGRAPH-2F983*/
+ RTUNI_ALPHA, /* U+02f984: CJK COMPATIBILITY IDEOGRAPH-2F984*/
+ RTUNI_ALPHA, /* U+02f985: CJK COMPATIBILITY IDEOGRAPH-2F985*/
+ RTUNI_ALPHA, /* U+02f986: CJK COMPATIBILITY IDEOGRAPH-2F986*/
+ RTUNI_ALPHA, /* U+02f987: CJK COMPATIBILITY IDEOGRAPH-2F987*/
+ RTUNI_ALPHA, /* U+02f988: CJK COMPATIBILITY IDEOGRAPH-2F988*/
+ RTUNI_ALPHA, /* U+02f989: CJK COMPATIBILITY IDEOGRAPH-2F989*/
+ RTUNI_ALPHA, /* U+02f98a: CJK COMPATIBILITY IDEOGRAPH-2F98A*/
+ RTUNI_ALPHA, /* U+02f98b: CJK COMPATIBILITY IDEOGRAPH-2F98B*/
+ RTUNI_ALPHA, /* U+02f98c: CJK COMPATIBILITY IDEOGRAPH-2F98C*/
+ RTUNI_ALPHA, /* U+02f98d: CJK COMPATIBILITY IDEOGRAPH-2F98D*/
+ RTUNI_ALPHA, /* U+02f98e: CJK COMPATIBILITY IDEOGRAPH-2F98E*/
+ RTUNI_ALPHA, /* U+02f98f: CJK COMPATIBILITY IDEOGRAPH-2F98F*/
+ RTUNI_ALPHA, /* U+02f990: CJK COMPATIBILITY IDEOGRAPH-2F990*/
+ RTUNI_ALPHA, /* U+02f991: CJK COMPATIBILITY IDEOGRAPH-2F991*/
+ RTUNI_ALPHA, /* U+02f992: CJK COMPATIBILITY IDEOGRAPH-2F992*/
+ RTUNI_ALPHA, /* U+02f993: CJK COMPATIBILITY IDEOGRAPH-2F993*/
+ RTUNI_ALPHA, /* U+02f994: CJK COMPATIBILITY IDEOGRAPH-2F994*/
+ RTUNI_ALPHA, /* U+02f995: CJK COMPATIBILITY IDEOGRAPH-2F995*/
+ RTUNI_ALPHA, /* U+02f996: CJK COMPATIBILITY IDEOGRAPH-2F996*/
+ RTUNI_ALPHA, /* U+02f997: CJK COMPATIBILITY IDEOGRAPH-2F997*/
+ RTUNI_ALPHA, /* U+02f998: CJK COMPATIBILITY IDEOGRAPH-2F998*/
+ RTUNI_ALPHA, /* U+02f999: CJK COMPATIBILITY IDEOGRAPH-2F999*/
+ RTUNI_ALPHA, /* U+02f99a: CJK COMPATIBILITY IDEOGRAPH-2F99A*/
+ RTUNI_ALPHA, /* U+02f99b: CJK COMPATIBILITY IDEOGRAPH-2F99B*/
+ RTUNI_ALPHA, /* U+02f99c: CJK COMPATIBILITY IDEOGRAPH-2F99C*/
+ RTUNI_ALPHA, /* U+02f99d: CJK COMPATIBILITY IDEOGRAPH-2F99D*/
+ RTUNI_ALPHA, /* U+02f99e: CJK COMPATIBILITY IDEOGRAPH-2F99E*/
+ RTUNI_ALPHA, /* U+02f99f: CJK COMPATIBILITY IDEOGRAPH-2F99F*/
+ RTUNI_ALPHA, /* U+02f9a0: CJK COMPATIBILITY IDEOGRAPH-2F9A0*/
+ RTUNI_ALPHA, /* U+02f9a1: CJK COMPATIBILITY IDEOGRAPH-2F9A1*/
+ RTUNI_ALPHA, /* U+02f9a2: CJK COMPATIBILITY IDEOGRAPH-2F9A2*/
+ RTUNI_ALPHA, /* U+02f9a3: CJK COMPATIBILITY IDEOGRAPH-2F9A3*/
+ RTUNI_ALPHA, /* U+02f9a4: CJK COMPATIBILITY IDEOGRAPH-2F9A4*/
+ RTUNI_ALPHA, /* U+02f9a5: CJK COMPATIBILITY IDEOGRAPH-2F9A5*/
+ RTUNI_ALPHA, /* U+02f9a6: CJK COMPATIBILITY IDEOGRAPH-2F9A6*/
+ RTUNI_ALPHA, /* U+02f9a7: CJK COMPATIBILITY IDEOGRAPH-2F9A7*/
+ RTUNI_ALPHA, /* U+02f9a8: CJK COMPATIBILITY IDEOGRAPH-2F9A8*/
+ RTUNI_ALPHA, /* U+02f9a9: CJK COMPATIBILITY IDEOGRAPH-2F9A9*/
+ RTUNI_ALPHA, /* U+02f9aa: CJK COMPATIBILITY IDEOGRAPH-2F9AA*/
+ RTUNI_ALPHA, /* U+02f9ab: CJK COMPATIBILITY IDEOGRAPH-2F9AB*/
+ RTUNI_ALPHA, /* U+02f9ac: CJK COMPATIBILITY IDEOGRAPH-2F9AC*/
+ RTUNI_ALPHA, /* U+02f9ad: CJK COMPATIBILITY IDEOGRAPH-2F9AD*/
+ RTUNI_ALPHA, /* U+02f9ae: CJK COMPATIBILITY IDEOGRAPH-2F9AE*/
+ RTUNI_ALPHA, /* U+02f9af: CJK COMPATIBILITY IDEOGRAPH-2F9AF*/
+ RTUNI_ALPHA, /* U+02f9b0: CJK COMPATIBILITY IDEOGRAPH-2F9B0*/
+ RTUNI_ALPHA, /* U+02f9b1: CJK COMPATIBILITY IDEOGRAPH-2F9B1*/
+ RTUNI_ALPHA, /* U+02f9b2: CJK COMPATIBILITY IDEOGRAPH-2F9B2*/
+ RTUNI_ALPHA, /* U+02f9b3: CJK COMPATIBILITY IDEOGRAPH-2F9B3*/
+ RTUNI_ALPHA, /* U+02f9b4: CJK COMPATIBILITY IDEOGRAPH-2F9B4*/
+ RTUNI_ALPHA, /* U+02f9b5: CJK COMPATIBILITY IDEOGRAPH-2F9B5*/
+ RTUNI_ALPHA, /* U+02f9b6: CJK COMPATIBILITY IDEOGRAPH-2F9B6*/
+ RTUNI_ALPHA, /* U+02f9b7: CJK COMPATIBILITY IDEOGRAPH-2F9B7*/
+ RTUNI_ALPHA, /* U+02f9b8: CJK COMPATIBILITY IDEOGRAPH-2F9B8*/
+ RTUNI_ALPHA, /* U+02f9b9: CJK COMPATIBILITY IDEOGRAPH-2F9B9*/
+ RTUNI_ALPHA, /* U+02f9ba: CJK COMPATIBILITY IDEOGRAPH-2F9BA*/
+ RTUNI_ALPHA, /* U+02f9bb: CJK COMPATIBILITY IDEOGRAPH-2F9BB*/
+ RTUNI_ALPHA, /* U+02f9bc: CJK COMPATIBILITY IDEOGRAPH-2F9BC*/
+ RTUNI_ALPHA, /* U+02f9bd: CJK COMPATIBILITY IDEOGRAPH-2F9BD*/
+ RTUNI_ALPHA, /* U+02f9be: CJK COMPATIBILITY IDEOGRAPH-2F9BE*/
+ RTUNI_ALPHA, /* U+02f9bf: CJK COMPATIBILITY IDEOGRAPH-2F9BF*/
+ RTUNI_ALPHA, /* U+02f9c0: CJK COMPATIBILITY IDEOGRAPH-2F9C0*/
+ RTUNI_ALPHA, /* U+02f9c1: CJK COMPATIBILITY IDEOGRAPH-2F9C1*/
+ RTUNI_ALPHA, /* U+02f9c2: CJK COMPATIBILITY IDEOGRAPH-2F9C2*/
+ RTUNI_ALPHA, /* U+02f9c3: CJK COMPATIBILITY IDEOGRAPH-2F9C3*/
+ RTUNI_ALPHA, /* U+02f9c4: CJK COMPATIBILITY IDEOGRAPH-2F9C4*/
+ RTUNI_ALPHA, /* U+02f9c5: CJK COMPATIBILITY IDEOGRAPH-2F9C5*/
+ RTUNI_ALPHA, /* U+02f9c6: CJK COMPATIBILITY IDEOGRAPH-2F9C6*/
+ RTUNI_ALPHA, /* U+02f9c7: CJK COMPATIBILITY IDEOGRAPH-2F9C7*/
+ RTUNI_ALPHA, /* U+02f9c8: CJK COMPATIBILITY IDEOGRAPH-2F9C8*/
+ RTUNI_ALPHA, /* U+02f9c9: CJK COMPATIBILITY IDEOGRAPH-2F9C9*/
+ RTUNI_ALPHA, /* U+02f9ca: CJK COMPATIBILITY IDEOGRAPH-2F9CA*/
+ RTUNI_ALPHA, /* U+02f9cb: CJK COMPATIBILITY IDEOGRAPH-2F9CB*/
+ RTUNI_ALPHA, /* U+02f9cc: CJK COMPATIBILITY IDEOGRAPH-2F9CC*/
+ RTUNI_ALPHA, /* U+02f9cd: CJK COMPATIBILITY IDEOGRAPH-2F9CD*/
+ RTUNI_ALPHA, /* U+02f9ce: CJK COMPATIBILITY IDEOGRAPH-2F9CE*/
+ RTUNI_ALPHA, /* U+02f9cf: CJK COMPATIBILITY IDEOGRAPH-2F9CF*/
+ RTUNI_ALPHA, /* U+02f9d0: CJK COMPATIBILITY IDEOGRAPH-2F9D0*/
+ RTUNI_ALPHA, /* U+02f9d1: CJK COMPATIBILITY IDEOGRAPH-2F9D1*/
+ RTUNI_ALPHA, /* U+02f9d2: CJK COMPATIBILITY IDEOGRAPH-2F9D2*/
+ RTUNI_ALPHA, /* U+02f9d3: CJK COMPATIBILITY IDEOGRAPH-2F9D3*/
+ RTUNI_ALPHA, /* U+02f9d4: CJK COMPATIBILITY IDEOGRAPH-2F9D4*/
+ RTUNI_ALPHA, /* U+02f9d5: CJK COMPATIBILITY IDEOGRAPH-2F9D5*/
+ RTUNI_ALPHA, /* U+02f9d6: CJK COMPATIBILITY IDEOGRAPH-2F9D6*/
+ RTUNI_ALPHA, /* U+02f9d7: CJK COMPATIBILITY IDEOGRAPH-2F9D7*/
+ RTUNI_ALPHA, /* U+02f9d8: CJK COMPATIBILITY IDEOGRAPH-2F9D8*/
+ RTUNI_ALPHA, /* U+02f9d9: CJK COMPATIBILITY IDEOGRAPH-2F9D9*/
+ RTUNI_ALPHA, /* U+02f9da: CJK COMPATIBILITY IDEOGRAPH-2F9DA*/
+ RTUNI_ALPHA, /* U+02f9db: CJK COMPATIBILITY IDEOGRAPH-2F9DB*/
+ RTUNI_ALPHA, /* U+02f9dc: CJK COMPATIBILITY IDEOGRAPH-2F9DC*/
+ RTUNI_ALPHA, /* U+02f9dd: CJK COMPATIBILITY IDEOGRAPH-2F9DD*/
+ RTUNI_ALPHA, /* U+02f9de: CJK COMPATIBILITY IDEOGRAPH-2F9DE*/
+ RTUNI_ALPHA, /* U+02f9df: CJK COMPATIBILITY IDEOGRAPH-2F9DF*/
+ RTUNI_ALPHA, /* U+02f9e0: CJK COMPATIBILITY IDEOGRAPH-2F9E0*/
+ RTUNI_ALPHA, /* U+02f9e1: CJK COMPATIBILITY IDEOGRAPH-2F9E1*/
+ RTUNI_ALPHA, /* U+02f9e2: CJK COMPATIBILITY IDEOGRAPH-2F9E2*/
+ RTUNI_ALPHA, /* U+02f9e3: CJK COMPATIBILITY IDEOGRAPH-2F9E3*/
+ RTUNI_ALPHA, /* U+02f9e4: CJK COMPATIBILITY IDEOGRAPH-2F9E4*/
+ RTUNI_ALPHA, /* U+02f9e5: CJK COMPATIBILITY IDEOGRAPH-2F9E5*/
+ RTUNI_ALPHA, /* U+02f9e6: CJK COMPATIBILITY IDEOGRAPH-2F9E6*/
+ RTUNI_ALPHA, /* U+02f9e7: CJK COMPATIBILITY IDEOGRAPH-2F9E7*/
+ RTUNI_ALPHA, /* U+02f9e8: CJK COMPATIBILITY IDEOGRAPH-2F9E8*/
+ RTUNI_ALPHA, /* U+02f9e9: CJK COMPATIBILITY IDEOGRAPH-2F9E9*/
+ RTUNI_ALPHA, /* U+02f9ea: CJK COMPATIBILITY IDEOGRAPH-2F9EA*/
+ RTUNI_ALPHA, /* U+02f9eb: CJK COMPATIBILITY IDEOGRAPH-2F9EB*/
+ RTUNI_ALPHA, /* U+02f9ec: CJK COMPATIBILITY IDEOGRAPH-2F9EC*/
+ RTUNI_ALPHA, /* U+02f9ed: CJK COMPATIBILITY IDEOGRAPH-2F9ED*/
+ RTUNI_ALPHA, /* U+02f9ee: CJK COMPATIBILITY IDEOGRAPH-2F9EE*/
+ RTUNI_ALPHA, /* U+02f9ef: CJK COMPATIBILITY IDEOGRAPH-2F9EF*/
+ RTUNI_ALPHA, /* U+02f9f0: CJK COMPATIBILITY IDEOGRAPH-2F9F0*/
+ RTUNI_ALPHA, /* U+02f9f1: CJK COMPATIBILITY IDEOGRAPH-2F9F1*/
+ RTUNI_ALPHA, /* U+02f9f2: CJK COMPATIBILITY IDEOGRAPH-2F9F2*/
+ RTUNI_ALPHA, /* U+02f9f3: CJK COMPATIBILITY IDEOGRAPH-2F9F3*/
+ RTUNI_ALPHA, /* U+02f9f4: CJK COMPATIBILITY IDEOGRAPH-2F9F4*/
+ RTUNI_ALPHA, /* U+02f9f5: CJK COMPATIBILITY IDEOGRAPH-2F9F5*/
+ RTUNI_ALPHA, /* U+02f9f6: CJK COMPATIBILITY IDEOGRAPH-2F9F6*/
+ RTUNI_ALPHA, /* U+02f9f7: CJK COMPATIBILITY IDEOGRAPH-2F9F7*/
+ RTUNI_ALPHA, /* U+02f9f8: CJK COMPATIBILITY IDEOGRAPH-2F9F8*/
+ RTUNI_ALPHA, /* U+02f9f9: CJK COMPATIBILITY IDEOGRAPH-2F9F9*/
+ RTUNI_ALPHA, /* U+02f9fa: CJK COMPATIBILITY IDEOGRAPH-2F9FA*/
+ RTUNI_ALPHA, /* U+02f9fb: CJK COMPATIBILITY IDEOGRAPH-2F9FB*/
+ RTUNI_ALPHA, /* U+02f9fc: CJK COMPATIBILITY IDEOGRAPH-2F9FC*/
+ RTUNI_ALPHA, /* U+02f9fd: CJK COMPATIBILITY IDEOGRAPH-2F9FD*/
+ RTUNI_ALPHA, /* U+02f9fe: CJK COMPATIBILITY IDEOGRAPH-2F9FE*/
+ RTUNI_ALPHA, /* U+02f9ff: CJK COMPATIBILITY IDEOGRAPH-2F9FF*/
+ RTUNI_ALPHA, /* U+02fa00: CJK COMPATIBILITY IDEOGRAPH-2FA00*/
+ RTUNI_ALPHA, /* U+02fa01: CJK COMPATIBILITY IDEOGRAPH-2FA01*/
+ RTUNI_ALPHA, /* U+02fa02: CJK COMPATIBILITY IDEOGRAPH-2FA02*/
+ RTUNI_ALPHA, /* U+02fa03: CJK COMPATIBILITY IDEOGRAPH-2FA03*/
+ RTUNI_ALPHA, /* U+02fa04: CJK COMPATIBILITY IDEOGRAPH-2FA04*/
+ RTUNI_ALPHA, /* U+02fa05: CJK COMPATIBILITY IDEOGRAPH-2FA05*/
+ RTUNI_ALPHA, /* U+02fa06: CJK COMPATIBILITY IDEOGRAPH-2FA06*/
+ RTUNI_ALPHA, /* U+02fa07: CJK COMPATIBILITY IDEOGRAPH-2FA07*/
+ RTUNI_ALPHA, /* U+02fa08: CJK COMPATIBILITY IDEOGRAPH-2FA08*/
+ RTUNI_ALPHA, /* U+02fa09: CJK COMPATIBILITY IDEOGRAPH-2FA09*/
+ RTUNI_ALPHA, /* U+02fa0a: CJK COMPATIBILITY IDEOGRAPH-2FA0A*/
+ RTUNI_ALPHA, /* U+02fa0b: CJK COMPATIBILITY IDEOGRAPH-2FA0B*/
+ RTUNI_ALPHA, /* U+02fa0c: CJK COMPATIBILITY IDEOGRAPH-2FA0C*/
+ RTUNI_ALPHA, /* U+02fa0d: CJK COMPATIBILITY IDEOGRAPH-2FA0D*/
+ RTUNI_ALPHA, /* U+02fa0e: CJK COMPATIBILITY IDEOGRAPH-2FA0E*/
+ RTUNI_ALPHA, /* U+02fa0f: CJK COMPATIBILITY IDEOGRAPH-2FA0F*/
+ RTUNI_ALPHA, /* U+02fa10: CJK COMPATIBILITY IDEOGRAPH-2FA10*/
+ RTUNI_ALPHA, /* U+02fa11: CJK COMPATIBILITY IDEOGRAPH-2FA11*/
+ RTUNI_ALPHA, /* U+02fa12: CJK COMPATIBILITY IDEOGRAPH-2FA12*/
+ RTUNI_ALPHA, /* U+02fa13: CJK COMPATIBILITY IDEOGRAPH-2FA13*/
+ RTUNI_ALPHA, /* U+02fa14: CJK COMPATIBILITY IDEOGRAPH-2FA14*/
+ RTUNI_ALPHA, /* U+02fa15: CJK COMPATIBILITY IDEOGRAPH-2FA15*/
+ RTUNI_ALPHA, /* U+02fa16: CJK COMPATIBILITY IDEOGRAPH-2FA16*/
+ RTUNI_ALPHA, /* U+02fa17: CJK COMPATIBILITY IDEOGRAPH-2FA17*/
+ RTUNI_ALPHA, /* U+02fa18: CJK COMPATIBILITY IDEOGRAPH-2FA18*/
+ RTUNI_ALPHA, /* U+02fa19: CJK COMPATIBILITY IDEOGRAPH-2FA19*/
+ RTUNI_ALPHA, /* U+02fa1a: CJK COMPATIBILITY IDEOGRAPH-2FA1A*/
+ RTUNI_ALPHA, /* U+02fa1b: CJK COMPATIBILITY IDEOGRAPH-2FA1B*/
+ RTUNI_ALPHA, /* U+02fa1c: CJK COMPATIBILITY IDEOGRAPH-2FA1C*/
+ RTUNI_ALPHA, /* U+02fa1d: CJK COMPATIBILITY IDEOGRAPH-2FA1D*/
+};
+
+
+RT_DECL_DATA_CONST(const RTUNIFLAGSRANGE) g_aRTUniFlagsRanges[] =
+{
+ { 0x000000, 0x003401, &g_afRTUniFlags0x000000[0] },
+ { 0x004db5, 0x004e01, &g_afRTUniFlags0x004db5[0] },
+ { 0x009fcc, 0x00d7fc, &g_afRTUniFlags0x009fcc[0] },
+ { 0x00f900, 0x01342f, &g_afRTUniFlags0x00f900[0] },
+ { 0x016800, 0x016fa0, &g_afRTUniFlags0x016800[0] },
+ { 0x01b000, 0x01b002, &g_afRTUniFlags0x01b000[0] },
+ { 0x01d400, 0x01d800, &g_afRTUniFlags0x01d400[0] },
+ { 0x01ee00, 0x01eebc, &g_afRTUniFlags0x01ee00[0] },
+ { 0x020000, 0x020001, &g_afRTUniFlags0x020000[0] },
+ { 0x02a6d6, 0x02a701, &g_afRTUniFlags0x02a6d6[0] },
+ { 0x02b734, 0x02b81e, &g_afRTUniFlags0x02b734[0] },
+ { 0x02f800, 0x02fa1e, &g_afRTUniFlags0x02f800[0] },
+ { ~(RTUNICP)0, ~(RTUNICP)0, NULL }
+};
+
diff --git a/src/VBox/Runtime/common/string/unidata-lower.cpp b/src/VBox/Runtime/common/string/unidata-lower.cpp
new file mode 100644
index 00000000..02c9ce6c
--- /dev/null
+++ b/src/VBox/Runtime/common/string/unidata-lower.cpp
@@ -0,0 +1,3911 @@
+/* $Id: unidata-lower.cpp $ */
+/** @file
+ * IPRT - Unicode Tables.
+ *
+ * Automatically Generated from d:\src\unicode\6.3.0\
+ * by ..\..\..\..\..\out\win.amd64\debug\obj\uniread\uniread.exe (May 22 2014 19:07:40)
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <iprt/uni.h>
+
+static const RTUNICP g_afRTUniLower0x000000[] =
+{
+ 0x00, /* U+000000: <control>*/
+ 0x01, /* U+000001: <control>*/
+ 0x02, /* U+000002: <control>*/
+ 0x03, /* U+000003: <control>*/
+ 0x04, /* U+000004: <control>*/
+ 0x05, /* U+000005: <control>*/
+ 0x06, /* U+000006: <control>*/
+ 0x07, /* U+000007: <control>*/
+ 0x08, /* U+000008: <control>*/
+ 0x09, /* U+000009: <control>*/
+ 0x0a, /* U+00000a: <control>*/
+ 0x0b, /* U+00000b: <control>*/
+ 0x0c, /* U+00000c: <control>*/
+ 0x0d, /* U+00000d: <control>*/
+ 0x0e, /* U+00000e: <control>*/
+ 0x0f, /* U+00000f: <control>*/
+ 0x10, /* U+000010: <control>*/
+ 0x11, /* U+000011: <control>*/
+ 0x12, /* U+000012: <control>*/
+ 0x13, /* U+000013: <control>*/
+ 0x14, /* U+000014: <control>*/
+ 0x15, /* U+000015: <control>*/
+ 0x16, /* U+000016: <control>*/
+ 0x17, /* U+000017: <control>*/
+ 0x18, /* U+000018: <control>*/
+ 0x19, /* U+000019: <control>*/
+ 0x1a, /* U+00001a: <control>*/
+ 0x1b, /* U+00001b: <control>*/
+ 0x1c, /* U+00001c: <control>*/
+ 0x1d, /* U+00001d: <control>*/
+ 0x1e, /* U+00001e: <control>*/
+ 0x1f, /* U+00001f: <control>*/
+ 0x20, /* U+000020: SPACE*/
+ 0x21, /* U+000021: EXCLAMATION MARK*/
+ 0x22, /* U+000022: QUOTATION MARK*/
+ 0x23, /* U+000023: NUMBER SIGN*/
+ 0x24, /* U+000024: DOLLAR SIGN*/
+ 0x25, /* U+000025: PERCENT SIGN*/
+ 0x26, /* U+000026: AMPERSAND*/
+ 0x27, /* U+000027: APOSTROPHE*/
+ 0x28, /* U+000028: LEFT PARENTHESIS*/
+ 0x29, /* U+000029: RIGHT PARENTHESIS*/
+ 0x2a, /* U+00002a: ASTERISK*/
+ 0x2b, /* U+00002b: PLUS SIGN*/
+ 0x2c, /* U+00002c: COMMA*/
+ 0x2d, /* U+00002d: HYPHEN-MINUS*/
+ 0x2e, /* U+00002e: FULL STOP*/
+ 0x2f, /* U+00002f: SOLIDUS*/
+ 0x30, /* U+000030: DIGIT ZERO*/
+ 0x31, /* U+000031: DIGIT ONE*/
+ 0x32, /* U+000032: DIGIT TWO*/
+ 0x33, /* U+000033: DIGIT THREE*/
+ 0x34, /* U+000034: DIGIT FOUR*/
+ 0x35, /* U+000035: DIGIT FIVE*/
+ 0x36, /* U+000036: DIGIT SIX*/
+ 0x37, /* U+000037: DIGIT SEVEN*/
+ 0x38, /* U+000038: DIGIT EIGHT*/
+ 0x39, /* U+000039: DIGIT NINE*/
+ 0x3a, /* U+00003a: COLON*/
+ 0x3b, /* U+00003b: SEMICOLON*/
+ 0x3c, /* U+00003c: LESS-THAN SIGN*/
+ 0x3d, /* U+00003d: EQUALS SIGN*/
+ 0x3e, /* U+00003e: GREATER-THAN SIGN*/
+ 0x3f, /* U+00003f: QUESTION MARK*/
+ 0x40, /* U+000040: COMMERCIAL AT*/
+ 0x61, /* U+000041: LATIN CAPITAL LETTER A*/
+ 0x62, /* U+000042: LATIN CAPITAL LETTER B*/
+ 0x63, /* U+000043: LATIN CAPITAL LETTER C*/
+ 0x64, /* U+000044: LATIN CAPITAL LETTER D*/
+ 0x65, /* U+000045: LATIN CAPITAL LETTER E*/
+ 0x66, /* U+000046: LATIN CAPITAL LETTER F*/
+ 0x67, /* U+000047: LATIN CAPITAL LETTER G*/
+ 0x68, /* U+000048: LATIN CAPITAL LETTER H*/
+ 0x69, /* U+000049: LATIN CAPITAL LETTER I*/
+ 0x6a, /* U+00004a: LATIN CAPITAL LETTER J*/
+ 0x6b, /* U+00004b: LATIN CAPITAL LETTER K*/
+ 0x6c, /* U+00004c: LATIN CAPITAL LETTER L*/
+ 0x6d, /* U+00004d: LATIN CAPITAL LETTER M*/
+ 0x6e, /* U+00004e: LATIN CAPITAL LETTER N*/
+ 0x6f, /* U+00004f: LATIN CAPITAL LETTER O*/
+ 0x70, /* U+000050: LATIN CAPITAL LETTER P*/
+ 0x71, /* U+000051: LATIN CAPITAL LETTER Q*/
+ 0x72, /* U+000052: LATIN CAPITAL LETTER R*/
+ 0x73, /* U+000053: LATIN CAPITAL LETTER S*/
+ 0x74, /* U+000054: LATIN CAPITAL LETTER T*/
+ 0x75, /* U+000055: LATIN CAPITAL LETTER U*/
+ 0x76, /* U+000056: LATIN CAPITAL LETTER V*/
+ 0x77, /* U+000057: LATIN CAPITAL LETTER W*/
+ 0x78, /* U+000058: LATIN CAPITAL LETTER X*/
+ 0x79, /* U+000059: LATIN CAPITAL LETTER Y*/
+ 0x7a, /* U+00005a: LATIN CAPITAL LETTER Z*/
+ 0x5b, /* U+00005b: LEFT SQUARE BRACKET*/
+ 0x5c, /* U+00005c: REVERSE SOLIDUS*/
+ 0x5d, /* U+00005d: RIGHT SQUARE BRACKET*/
+ 0x5e, /* U+00005e: CIRCUMFLEX ACCENT*/
+ 0x5f, /* U+00005f: LOW LINE*/
+ 0x60, /* U+000060: GRAVE ACCENT*/
+ 0x61, /* U+000061: LATIN SMALL LETTER A*/
+ 0x62, /* U+000062: LATIN SMALL LETTER B*/
+ 0x63, /* U+000063: LATIN SMALL LETTER C*/
+ 0x64, /* U+000064: LATIN SMALL LETTER D*/
+ 0x65, /* U+000065: LATIN SMALL LETTER E*/
+ 0x66, /* U+000066: LATIN SMALL LETTER F*/
+ 0x67, /* U+000067: LATIN SMALL LETTER G*/
+ 0x68, /* U+000068: LATIN SMALL LETTER H*/
+ 0x69, /* U+000069: LATIN SMALL LETTER I*/
+ 0x6a, /* U+00006a: LATIN SMALL LETTER J*/
+ 0x6b, /* U+00006b: LATIN SMALL LETTER K*/
+ 0x6c, /* U+00006c: LATIN SMALL LETTER L*/
+ 0x6d, /* U+00006d: LATIN SMALL LETTER M*/
+ 0x6e, /* U+00006e: LATIN SMALL LETTER N*/
+ 0x6f, /* U+00006f: LATIN SMALL LETTER O*/
+ 0x70, /* U+000070: LATIN SMALL LETTER P*/
+ 0x71, /* U+000071: LATIN SMALL LETTER Q*/
+ 0x72, /* U+000072: LATIN SMALL LETTER R*/
+ 0x73, /* U+000073: LATIN SMALL LETTER S*/
+ 0x74, /* U+000074: LATIN SMALL LETTER T*/
+ 0x75, /* U+000075: LATIN SMALL LETTER U*/
+ 0x76, /* U+000076: LATIN SMALL LETTER V*/
+ 0x77, /* U+000077: LATIN SMALL LETTER W*/
+ 0x78, /* U+000078: LATIN SMALL LETTER X*/
+ 0x79, /* U+000079: LATIN SMALL LETTER Y*/
+ 0x7a, /* U+00007a: LATIN SMALL LETTER Z*/
+ 0x7b, /* U+00007b: LEFT CURLY BRACKET*/
+ 0x7c, /* U+00007c: VERTICAL LINE*/
+ 0x7d, /* U+00007d: RIGHT CURLY BRACKET*/
+ 0x7e, /* U+00007e: TILDE*/
+ 0x7f, /* U+00007f: <control>*/
+ 0x80, /* U+000080: <control>*/
+ 0x81, /* U+000081: <control>*/
+ 0x82, /* U+000082: <control>*/
+ 0x83, /* U+000083: <control>*/
+ 0x84, /* U+000084: <control>*/
+ 0x85, /* U+000085: <control>*/
+ 0x86, /* U+000086: <control>*/
+ 0x87, /* U+000087: <control>*/
+ 0x88, /* U+000088: <control>*/
+ 0x89, /* U+000089: <control>*/
+ 0x8a, /* U+00008a: <control>*/
+ 0x8b, /* U+00008b: <control>*/
+ 0x8c, /* U+00008c: <control>*/
+ 0x8d, /* U+00008d: <control>*/
+ 0x8e, /* U+00008e: <control>*/
+ 0x8f, /* U+00008f: <control>*/
+ 0x90, /* U+000090: <control>*/
+ 0x91, /* U+000091: <control>*/
+ 0x92, /* U+000092: <control>*/
+ 0x93, /* U+000093: <control>*/
+ 0x94, /* U+000094: <control>*/
+ 0x95, /* U+000095: <control>*/
+ 0x96, /* U+000096: <control>*/
+ 0x97, /* U+000097: <control>*/
+ 0x98, /* U+000098: <control>*/
+ 0x99, /* U+000099: <control>*/
+ 0x9a, /* U+00009a: <control>*/
+ 0x9b, /* U+00009b: <control>*/
+ 0x9c, /* U+00009c: <control>*/
+ 0x9d, /* U+00009d: <control>*/
+ 0x9e, /* U+00009e: <control>*/
+ 0x9f, /* U+00009f: <control>*/
+ 0xa0, /* U+0000a0: NO-BREAK SPACE*/
+ 0xa1, /* U+0000a1: INVERTED EXCLAMATION MARK*/
+ 0xa2, /* U+0000a2: CENT SIGN*/
+ 0xa3, /* U+0000a3: POUND SIGN*/
+ 0xa4, /* U+0000a4: CURRENCY SIGN*/
+ 0xa5, /* U+0000a5: YEN SIGN*/
+ 0xa6, /* U+0000a6: BROKEN BAR*/
+ 0xa7, /* U+0000a7: SECTION SIGN*/
+ 0xa8, /* U+0000a8: DIAERESIS*/
+ 0xa9, /* U+0000a9: COPYRIGHT SIGN*/
+ 0xaa, /* U+0000aa: FEMININE ORDINAL INDICATOR*/
+ 0xab, /* U+0000ab: LEFT-POINTING DOUBLE ANGLE QUOTATION MARK*/
+ 0xac, /* U+0000ac: NOT SIGN*/
+ 0xad, /* U+0000ad: SOFT HYPHEN*/
+ 0xae, /* U+0000ae: REGISTERED SIGN*/
+ 0xaf, /* U+0000af: MACRON*/
+ 0xb0, /* U+0000b0: DEGREE SIGN*/
+ 0xb1, /* U+0000b1: PLUS-MINUS SIGN*/
+ 0xb2, /* U+0000b2: SUPERSCRIPT TWO*/
+ 0xb3, /* U+0000b3: SUPERSCRIPT THREE*/
+ 0xb4, /* U+0000b4: ACUTE ACCENT*/
+ 0xb5, /* U+0000b5: MICRO SIGN*/
+ 0xb6, /* U+0000b6: PILCROW SIGN*/
+ 0xb7, /* U+0000b7: MIDDLE DOT*/
+ 0xb8, /* U+0000b8: CEDILLA*/
+ 0xb9, /* U+0000b9: SUPERSCRIPT ONE*/
+ 0xba, /* U+0000ba: MASCULINE ORDINAL INDICATOR*/
+ 0xbb, /* U+0000bb: RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK*/
+ 0xbc, /* U+0000bc: VULGAR FRACTION ONE QUARTER*/
+ 0xbd, /* U+0000bd: VULGAR FRACTION ONE HALF*/
+ 0xbe, /* U+0000be: VULGAR FRACTION THREE QUARTERS*/
+ 0xbf, /* U+0000bf: INVERTED QUESTION MARK*/
+ 0xe0, /* U+0000c0: LATIN CAPITAL LETTER A WITH GRAVE*/
+ 0xe1, /* U+0000c1: LATIN CAPITAL LETTER A WITH ACUTE*/
+ 0xe2, /* U+0000c2: LATIN CAPITAL LETTER A WITH CIRCUMFLEX*/
+ 0xe3, /* U+0000c3: LATIN CAPITAL LETTER A WITH TILDE*/
+ 0xe4, /* U+0000c4: LATIN CAPITAL LETTER A WITH DIAERESIS*/
+ 0xe5, /* U+0000c5: LATIN CAPITAL LETTER A WITH RING ABOVE*/
+ 0xe6, /* U+0000c6: LATIN CAPITAL LETTER AE*/
+ 0xe7, /* U+0000c7: LATIN CAPITAL LETTER C WITH CEDILLA*/
+ 0xe8, /* U+0000c8: LATIN CAPITAL LETTER E WITH GRAVE*/
+ 0xe9, /* U+0000c9: LATIN CAPITAL LETTER E WITH ACUTE*/
+ 0xea, /* U+0000ca: LATIN CAPITAL LETTER E WITH CIRCUMFLEX*/
+ 0xeb, /* U+0000cb: LATIN CAPITAL LETTER E WITH DIAERESIS*/
+ 0xec, /* U+0000cc: LATIN CAPITAL LETTER I WITH GRAVE*/
+ 0xed, /* U+0000cd: LATIN CAPITAL LETTER I WITH ACUTE*/
+ 0xee, /* U+0000ce: LATIN CAPITAL LETTER I WITH CIRCUMFLEX*/
+ 0xef, /* U+0000cf: LATIN CAPITAL LETTER I WITH DIAERESIS*/
+ 0xf0, /* U+0000d0: LATIN CAPITAL LETTER ETH*/
+ 0xf1, /* U+0000d1: LATIN CAPITAL LETTER N WITH TILDE*/
+ 0xf2, /* U+0000d2: LATIN CAPITAL LETTER O WITH GRAVE*/
+ 0xf3, /* U+0000d3: LATIN CAPITAL LETTER O WITH ACUTE*/
+ 0xf4, /* U+0000d4: LATIN CAPITAL LETTER O WITH CIRCUMFLEX*/
+ 0xf5, /* U+0000d5: LATIN CAPITAL LETTER O WITH TILDE*/
+ 0xf6, /* U+0000d6: LATIN CAPITAL LETTER O WITH DIAERESIS*/
+ 0xd7, /* U+0000d7: MULTIPLICATION SIGN*/
+ 0xf8, /* U+0000d8: LATIN CAPITAL LETTER O WITH STROKE*/
+ 0xf9, /* U+0000d9: LATIN CAPITAL LETTER U WITH GRAVE*/
+ 0xfa, /* U+0000da: LATIN CAPITAL LETTER U WITH ACUTE*/
+ 0xfb, /* U+0000db: LATIN CAPITAL LETTER U WITH CIRCUMFLEX*/
+ 0xfc, /* U+0000dc: LATIN CAPITAL LETTER U WITH DIAERESIS*/
+ 0xfd, /* U+0000dd: LATIN CAPITAL LETTER Y WITH ACUTE*/
+ 0xfe, /* U+0000de: LATIN CAPITAL LETTER THORN*/
+ 0xdf, /* U+0000df: LATIN SMALL LETTER SHARP S*/
+ 0xe0, /* U+0000e0: LATIN SMALL LETTER A WITH GRAVE*/
+ 0xe1, /* U+0000e1: LATIN SMALL LETTER A WITH ACUTE*/
+ 0xe2, /* U+0000e2: LATIN SMALL LETTER A WITH CIRCUMFLEX*/
+ 0xe3, /* U+0000e3: LATIN SMALL LETTER A WITH TILDE*/
+ 0xe4, /* U+0000e4: LATIN SMALL LETTER A WITH DIAERESIS*/
+ 0xe5, /* U+0000e5: LATIN SMALL LETTER A WITH RING ABOVE*/
+ 0xe6, /* U+0000e6: LATIN SMALL LETTER AE*/
+ 0xe7, /* U+0000e7: LATIN SMALL LETTER C WITH CEDILLA*/
+ 0xe8, /* U+0000e8: LATIN SMALL LETTER E WITH GRAVE*/
+ 0xe9, /* U+0000e9: LATIN SMALL LETTER E WITH ACUTE*/
+ 0xea, /* U+0000ea: LATIN SMALL LETTER E WITH CIRCUMFLEX*/
+ 0xeb, /* U+0000eb: LATIN SMALL LETTER E WITH DIAERESIS*/
+ 0xec, /* U+0000ec: LATIN SMALL LETTER I WITH GRAVE*/
+ 0xed, /* U+0000ed: LATIN SMALL LETTER I WITH ACUTE*/
+ 0xee, /* U+0000ee: LATIN SMALL LETTER I WITH CIRCUMFLEX*/
+ 0xef, /* U+0000ef: LATIN SMALL LETTER I WITH DIAERESIS*/
+ 0xf0, /* U+0000f0: LATIN SMALL LETTER ETH*/
+ 0xf1, /* U+0000f1: LATIN SMALL LETTER N WITH TILDE*/
+ 0xf2, /* U+0000f2: LATIN SMALL LETTER O WITH GRAVE*/
+ 0xf3, /* U+0000f3: LATIN SMALL LETTER O WITH ACUTE*/
+ 0xf4, /* U+0000f4: LATIN SMALL LETTER O WITH CIRCUMFLEX*/
+ 0xf5, /* U+0000f5: LATIN SMALL LETTER O WITH TILDE*/
+ 0xf6, /* U+0000f6: LATIN SMALL LETTER O WITH DIAERESIS*/
+ 0xf7, /* U+0000f7: DIVISION SIGN*/
+ 0xf8, /* U+0000f8: LATIN SMALL LETTER O WITH STROKE*/
+ 0xf9, /* U+0000f9: LATIN SMALL LETTER U WITH GRAVE*/
+ 0xfa, /* U+0000fa: LATIN SMALL LETTER U WITH ACUTE*/
+ 0xfb, /* U+0000fb: LATIN SMALL LETTER U WITH CIRCUMFLEX*/
+ 0xfc, /* U+0000fc: LATIN SMALL LETTER U WITH DIAERESIS*/
+ 0xfd, /* U+0000fd: LATIN SMALL LETTER Y WITH ACUTE*/
+ 0xfe, /* U+0000fe: LATIN SMALL LETTER THORN*/
+ 0xff, /* U+0000ff: LATIN SMALL LETTER Y WITH DIAERESIS*/
+ 0x101, /* U+000100: LATIN CAPITAL LETTER A WITH MACRON*/
+ 0x101, /* U+000101: LATIN SMALL LETTER A WITH MACRON*/
+ 0x103, /* U+000102: LATIN CAPITAL LETTER A WITH BREVE*/
+ 0x103, /* U+000103: LATIN SMALL LETTER A WITH BREVE*/
+ 0x105, /* U+000104: LATIN CAPITAL LETTER A WITH OGONEK*/
+ 0x105, /* U+000105: LATIN SMALL LETTER A WITH OGONEK*/
+ 0x107, /* U+000106: LATIN CAPITAL LETTER C WITH ACUTE*/
+ 0x107, /* U+000107: LATIN SMALL LETTER C WITH ACUTE*/
+ 0x109, /* U+000108: LATIN CAPITAL LETTER C WITH CIRCUMFLEX*/
+ 0x109, /* U+000109: LATIN SMALL LETTER C WITH CIRCUMFLEX*/
+ 0x10b, /* U+00010a: LATIN CAPITAL LETTER C WITH DOT ABOVE*/
+ 0x10b, /* U+00010b: LATIN SMALL LETTER C WITH DOT ABOVE*/
+ 0x10d, /* U+00010c: LATIN CAPITAL LETTER C WITH CARON*/
+ 0x10d, /* U+00010d: LATIN SMALL LETTER C WITH CARON*/
+ 0x10f, /* U+00010e: LATIN CAPITAL LETTER D WITH CARON*/
+ 0x10f, /* U+00010f: LATIN SMALL LETTER D WITH CARON*/
+ 0x111, /* U+000110: LATIN CAPITAL LETTER D WITH STROKE*/
+ 0x111, /* U+000111: LATIN SMALL LETTER D WITH STROKE*/
+ 0x113, /* U+000112: LATIN CAPITAL LETTER E WITH MACRON*/
+ 0x113, /* U+000113: LATIN SMALL LETTER E WITH MACRON*/
+ 0x115, /* U+000114: LATIN CAPITAL LETTER E WITH BREVE*/
+ 0x115, /* U+000115: LATIN SMALL LETTER E WITH BREVE*/
+ 0x117, /* U+000116: LATIN CAPITAL LETTER E WITH DOT ABOVE*/
+ 0x117, /* U+000117: LATIN SMALL LETTER E WITH DOT ABOVE*/
+ 0x119, /* U+000118: LATIN CAPITAL LETTER E WITH OGONEK*/
+ 0x119, /* U+000119: LATIN SMALL LETTER E WITH OGONEK*/
+ 0x11b, /* U+00011a: LATIN CAPITAL LETTER E WITH CARON*/
+ 0x11b, /* U+00011b: LATIN SMALL LETTER E WITH CARON*/
+ 0x11d, /* U+00011c: LATIN CAPITAL LETTER G WITH CIRCUMFLEX*/
+ 0x11d, /* U+00011d: LATIN SMALL LETTER G WITH CIRCUMFLEX*/
+ 0x11f, /* U+00011e: LATIN CAPITAL LETTER G WITH BREVE*/
+ 0x11f, /* U+00011f: LATIN SMALL LETTER G WITH BREVE*/
+ 0x121, /* U+000120: LATIN CAPITAL LETTER G WITH DOT ABOVE*/
+ 0x121, /* U+000121: LATIN SMALL LETTER G WITH DOT ABOVE*/
+ 0x123, /* U+000122: LATIN CAPITAL LETTER G WITH CEDILLA*/
+ 0x123, /* U+000123: LATIN SMALL LETTER G WITH CEDILLA*/
+ 0x125, /* U+000124: LATIN CAPITAL LETTER H WITH CIRCUMFLEX*/
+ 0x125, /* U+000125: LATIN SMALL LETTER H WITH CIRCUMFLEX*/
+ 0x127, /* U+000126: LATIN CAPITAL LETTER H WITH STROKE*/
+ 0x127, /* U+000127: LATIN SMALL LETTER H WITH STROKE*/
+ 0x129, /* U+000128: LATIN CAPITAL LETTER I WITH TILDE*/
+ 0x129, /* U+000129: LATIN SMALL LETTER I WITH TILDE*/
+ 0x12b, /* U+00012a: LATIN CAPITAL LETTER I WITH MACRON*/
+ 0x12b, /* U+00012b: LATIN SMALL LETTER I WITH MACRON*/
+ 0x12d, /* U+00012c: LATIN CAPITAL LETTER I WITH BREVE*/
+ 0x12d, /* U+00012d: LATIN SMALL LETTER I WITH BREVE*/
+ 0x12f, /* U+00012e: LATIN CAPITAL LETTER I WITH OGONEK*/
+ 0x12f, /* U+00012f: LATIN SMALL LETTER I WITH OGONEK*/
+ 0x69, /* U+000130: LATIN CAPITAL LETTER I WITH DOT ABOVE*/
+ 0x131, /* U+000131: LATIN SMALL LETTER DOTLESS I*/
+ 0x133, /* U+000132: LATIN CAPITAL LIGATURE IJ*/
+ 0x133, /* U+000133: LATIN SMALL LIGATURE IJ*/
+ 0x135, /* U+000134: LATIN CAPITAL LETTER J WITH CIRCUMFLEX*/
+ 0x135, /* U+000135: LATIN SMALL LETTER J WITH CIRCUMFLEX*/
+ 0x137, /* U+000136: LATIN CAPITAL LETTER K WITH CEDILLA*/
+ 0x137, /* U+000137: LATIN SMALL LETTER K WITH CEDILLA*/
+ 0x138, /* U+000138: LATIN SMALL LETTER KRA*/
+ 0x13a, /* U+000139: LATIN CAPITAL LETTER L WITH ACUTE*/
+ 0x13a, /* U+00013a: LATIN SMALL LETTER L WITH ACUTE*/
+ 0x13c, /* U+00013b: LATIN CAPITAL LETTER L WITH CEDILLA*/
+ 0x13c, /* U+00013c: LATIN SMALL LETTER L WITH CEDILLA*/
+ 0x13e, /* U+00013d: LATIN CAPITAL LETTER L WITH CARON*/
+ 0x13e, /* U+00013e: LATIN SMALL LETTER L WITH CARON*/
+ 0x140, /* U+00013f: LATIN CAPITAL LETTER L WITH MIDDLE DOT*/
+ 0x140, /* U+000140: LATIN SMALL LETTER L WITH MIDDLE DOT*/
+ 0x142, /* U+000141: LATIN CAPITAL LETTER L WITH STROKE*/
+ 0x142, /* U+000142: LATIN SMALL LETTER L WITH STROKE*/
+ 0x144, /* U+000143: LATIN CAPITAL LETTER N WITH ACUTE*/
+ 0x144, /* U+000144: LATIN SMALL LETTER N WITH ACUTE*/
+ 0x146, /* U+000145: LATIN CAPITAL LETTER N WITH CEDILLA*/
+ 0x146, /* U+000146: LATIN SMALL LETTER N WITH CEDILLA*/
+ 0x148, /* U+000147: LATIN CAPITAL LETTER N WITH CARON*/
+ 0x148, /* U+000148: LATIN SMALL LETTER N WITH CARON*/
+ 0x149, /* U+000149: LATIN SMALL LETTER N PRECEDED BY APOSTROPHE*/
+ 0x14b, /* U+00014a: LATIN CAPITAL LETTER ENG*/
+ 0x14b, /* U+00014b: LATIN SMALL LETTER ENG*/
+ 0x14d, /* U+00014c: LATIN CAPITAL LETTER O WITH MACRON*/
+ 0x14d, /* U+00014d: LATIN SMALL LETTER O WITH MACRON*/
+ 0x14f, /* U+00014e: LATIN CAPITAL LETTER O WITH BREVE*/
+ 0x14f, /* U+00014f: LATIN SMALL LETTER O WITH BREVE*/
+ 0x151, /* U+000150: LATIN CAPITAL LETTER O WITH DOUBLE ACUTE*/
+ 0x151, /* U+000151: LATIN SMALL LETTER O WITH DOUBLE ACUTE*/
+ 0x153, /* U+000152: LATIN CAPITAL LIGATURE OE*/
+ 0x153, /* U+000153: LATIN SMALL LIGATURE OE*/
+ 0x155, /* U+000154: LATIN CAPITAL LETTER R WITH ACUTE*/
+ 0x155, /* U+000155: LATIN SMALL LETTER R WITH ACUTE*/
+ 0x157, /* U+000156: LATIN CAPITAL LETTER R WITH CEDILLA*/
+ 0x157, /* U+000157: LATIN SMALL LETTER R WITH CEDILLA*/
+ 0x159, /* U+000158: LATIN CAPITAL LETTER R WITH CARON*/
+ 0x159, /* U+000159: LATIN SMALL LETTER R WITH CARON*/
+ 0x15b, /* U+00015a: LATIN CAPITAL LETTER S WITH ACUTE*/
+ 0x15b, /* U+00015b: LATIN SMALL LETTER S WITH ACUTE*/
+ 0x15d, /* U+00015c: LATIN CAPITAL LETTER S WITH CIRCUMFLEX*/
+ 0x15d, /* U+00015d: LATIN SMALL LETTER S WITH CIRCUMFLEX*/
+ 0x15f, /* U+00015e: LATIN CAPITAL LETTER S WITH CEDILLA*/
+ 0x15f, /* U+00015f: LATIN SMALL LETTER S WITH CEDILLA*/
+ 0x161, /* U+000160: LATIN CAPITAL LETTER S WITH CARON*/
+ 0x161, /* U+000161: LATIN SMALL LETTER S WITH CARON*/
+ 0x163, /* U+000162: LATIN CAPITAL LETTER T WITH CEDILLA*/
+ 0x163, /* U+000163: LATIN SMALL LETTER T WITH CEDILLA*/
+ 0x165, /* U+000164: LATIN CAPITAL LETTER T WITH CARON*/
+ 0x165, /* U+000165: LATIN SMALL LETTER T WITH CARON*/
+ 0x167, /* U+000166: LATIN CAPITAL LETTER T WITH STROKE*/
+ 0x167, /* U+000167: LATIN SMALL LETTER T WITH STROKE*/
+ 0x169, /* U+000168: LATIN CAPITAL LETTER U WITH TILDE*/
+ 0x169, /* U+000169: LATIN SMALL LETTER U WITH TILDE*/
+ 0x16b, /* U+00016a: LATIN CAPITAL LETTER U WITH MACRON*/
+ 0x16b, /* U+00016b: LATIN SMALL LETTER U WITH MACRON*/
+ 0x16d, /* U+00016c: LATIN CAPITAL LETTER U WITH BREVE*/
+ 0x16d, /* U+00016d: LATIN SMALL LETTER U WITH BREVE*/
+ 0x16f, /* U+00016e: LATIN CAPITAL LETTER U WITH RING ABOVE*/
+ 0x16f, /* U+00016f: LATIN SMALL LETTER U WITH RING ABOVE*/
+ 0x171, /* U+000170: LATIN CAPITAL LETTER U WITH DOUBLE ACUTE*/
+ 0x171, /* U+000171: LATIN SMALL LETTER U WITH DOUBLE ACUTE*/
+ 0x173, /* U+000172: LATIN CAPITAL LETTER U WITH OGONEK*/
+ 0x173, /* U+000173: LATIN SMALL LETTER U WITH OGONEK*/
+ 0x175, /* U+000174: LATIN CAPITAL LETTER W WITH CIRCUMFLEX*/
+ 0x175, /* U+000175: LATIN SMALL LETTER W WITH CIRCUMFLEX*/
+ 0x177, /* U+000176: LATIN CAPITAL LETTER Y WITH CIRCUMFLEX*/
+ 0x177, /* U+000177: LATIN SMALL LETTER Y WITH CIRCUMFLEX*/
+ 0xff, /* U+000178: LATIN CAPITAL LETTER Y WITH DIAERESIS*/
+ 0x17a, /* U+000179: LATIN CAPITAL LETTER Z WITH ACUTE*/
+ 0x17a, /* U+00017a: LATIN SMALL LETTER Z WITH ACUTE*/
+ 0x17c, /* U+00017b: LATIN CAPITAL LETTER Z WITH DOT ABOVE*/
+ 0x17c, /* U+00017c: LATIN SMALL LETTER Z WITH DOT ABOVE*/
+ 0x17e, /* U+00017d: LATIN CAPITAL LETTER Z WITH CARON*/
+ 0x17e, /* U+00017e: LATIN SMALL LETTER Z WITH CARON*/
+ 0x17f, /* U+00017f: LATIN SMALL LETTER LONG S*/
+ 0x180, /* U+000180: LATIN SMALL LETTER B WITH STROKE*/
+ 0x253, /* U+000181: LATIN CAPITAL LETTER B WITH HOOK*/
+ 0x183, /* U+000182: LATIN CAPITAL LETTER B WITH TOPBAR*/
+ 0x183, /* U+000183: LATIN SMALL LETTER B WITH TOPBAR*/
+ 0x185, /* U+000184: LATIN CAPITAL LETTER TONE SIX*/
+ 0x185, /* U+000185: LATIN SMALL LETTER TONE SIX*/
+ 0x254, /* U+000186: LATIN CAPITAL LETTER OPEN O*/
+ 0x188, /* U+000187: LATIN CAPITAL LETTER C WITH HOOK*/
+ 0x188, /* U+000188: LATIN SMALL LETTER C WITH HOOK*/
+ 0x256, /* U+000189: LATIN CAPITAL LETTER AFRICAN D*/
+ 0x257, /* U+00018a: LATIN CAPITAL LETTER D WITH HOOK*/
+ 0x18c, /* U+00018b: LATIN CAPITAL LETTER D WITH TOPBAR*/
+ 0x18c, /* U+00018c: LATIN SMALL LETTER D WITH TOPBAR*/
+ 0x18d, /* U+00018d: LATIN SMALL LETTER TURNED DELTA*/
+ 0x1dd, /* U+00018e: LATIN CAPITAL LETTER REVERSED E*/
+ 0x259, /* U+00018f: LATIN CAPITAL LETTER SCHWA*/
+ 0x25b, /* U+000190: LATIN CAPITAL LETTER OPEN E*/
+ 0x192, /* U+000191: LATIN CAPITAL LETTER F WITH HOOK*/
+ 0x192, /* U+000192: LATIN SMALL LETTER F WITH HOOK*/
+ 0x260, /* U+000193: LATIN CAPITAL LETTER G WITH HOOK*/
+ 0x263, /* U+000194: LATIN CAPITAL LETTER GAMMA*/
+ 0x195, /* U+000195: LATIN SMALL LETTER HV*/
+ 0x269, /* U+000196: LATIN CAPITAL LETTER IOTA*/
+ 0x268, /* U+000197: LATIN CAPITAL LETTER I WITH STROKE*/
+ 0x199, /* U+000198: LATIN CAPITAL LETTER K WITH HOOK*/
+ 0x199, /* U+000199: LATIN SMALL LETTER K WITH HOOK*/
+ 0x19a, /* U+00019a: LATIN SMALL LETTER L WITH BAR*/
+ 0x19b, /* U+00019b: LATIN SMALL LETTER LAMBDA WITH STROKE*/
+ 0x26f, /* U+00019c: LATIN CAPITAL LETTER TURNED M*/
+ 0x272, /* U+00019d: LATIN CAPITAL LETTER N WITH LEFT HOOK*/
+ 0x19e, /* U+00019e: LATIN SMALL LETTER N WITH LONG RIGHT LEG*/
+ 0x275, /* U+00019f: LATIN CAPITAL LETTER O WITH MIDDLE TILDE*/
+ 0x1a1, /* U+0001a0: LATIN CAPITAL LETTER O WITH HORN*/
+ 0x1a1, /* U+0001a1: LATIN SMALL LETTER O WITH HORN*/
+ 0x1a3, /* U+0001a2: LATIN CAPITAL LETTER OI*/
+ 0x1a3, /* U+0001a3: LATIN SMALL LETTER OI*/
+ 0x1a5, /* U+0001a4: LATIN CAPITAL LETTER P WITH HOOK*/
+ 0x1a5, /* U+0001a5: LATIN SMALL LETTER P WITH HOOK*/
+ 0x280, /* U+0001a6: LATIN LETTER YR*/
+ 0x1a8, /* U+0001a7: LATIN CAPITAL LETTER TONE TWO*/
+ 0x1a8, /* U+0001a8: LATIN SMALL LETTER TONE TWO*/
+ 0x283, /* U+0001a9: LATIN CAPITAL LETTER ESH*/
+ 0x1aa, /* U+0001aa: LATIN LETTER REVERSED ESH LOOP*/
+ 0x1ab, /* U+0001ab: LATIN SMALL LETTER T WITH PALATAL HOOK*/
+ 0x1ad, /* U+0001ac: LATIN CAPITAL LETTER T WITH HOOK*/
+ 0x1ad, /* U+0001ad: LATIN SMALL LETTER T WITH HOOK*/
+ 0x288, /* U+0001ae: LATIN CAPITAL LETTER T WITH RETROFLEX HOOK*/
+ 0x1b0, /* U+0001af: LATIN CAPITAL LETTER U WITH HORN*/
+ 0x1b0, /* U+0001b0: LATIN SMALL LETTER U WITH HORN*/
+ 0x28a, /* U+0001b1: LATIN CAPITAL LETTER UPSILON*/
+ 0x28b, /* U+0001b2: LATIN CAPITAL LETTER V WITH HOOK*/
+ 0x1b4, /* U+0001b3: LATIN CAPITAL LETTER Y WITH HOOK*/
+ 0x1b4, /* U+0001b4: LATIN SMALL LETTER Y WITH HOOK*/
+ 0x1b6, /* U+0001b5: LATIN CAPITAL LETTER Z WITH STROKE*/
+ 0x1b6, /* U+0001b6: LATIN SMALL LETTER Z WITH STROKE*/
+ 0x292, /* U+0001b7: LATIN CAPITAL LETTER EZH*/
+ 0x1b9, /* U+0001b8: LATIN CAPITAL LETTER EZH REVERSED*/
+ 0x1b9, /* U+0001b9: LATIN SMALL LETTER EZH REVERSED*/
+ 0x1ba, /* U+0001ba: LATIN SMALL LETTER EZH WITH TAIL*/
+ 0x1bb, /* U+0001bb: LATIN LETTER TWO WITH STROKE*/
+ 0x1bd, /* U+0001bc: LATIN CAPITAL LETTER TONE FIVE*/
+ 0x1bd, /* U+0001bd: LATIN SMALL LETTER TONE FIVE*/
+ 0x1be, /* U+0001be: LATIN LETTER INVERTED GLOTTAL STOP WITH STROKE*/
+ 0x1bf, /* U+0001bf: LATIN LETTER WYNN*/
+ 0x1c0, /* U+0001c0: LATIN LETTER DENTAL CLICK*/
+ 0x1c1, /* U+0001c1: LATIN LETTER LATERAL CLICK*/
+ 0x1c2, /* U+0001c2: LATIN LETTER ALVEOLAR CLICK*/
+ 0x1c3, /* U+0001c3: LATIN LETTER RETROFLEX CLICK*/
+ 0x1c6, /* U+0001c4: LATIN CAPITAL LETTER DZ WITH CARON*/
+ 0x1c6, /* U+0001c5: LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON*/
+ 0x1c6, /* U+0001c6: LATIN SMALL LETTER DZ WITH CARON*/
+ 0x1c9, /* U+0001c7: LATIN CAPITAL LETTER LJ*/
+ 0x1c9, /* U+0001c8: LATIN CAPITAL LETTER L WITH SMALL LETTER J*/
+ 0x1c9, /* U+0001c9: LATIN SMALL LETTER LJ*/
+ 0x1cc, /* U+0001ca: LATIN CAPITAL LETTER NJ*/
+ 0x1cc, /* U+0001cb: LATIN CAPITAL LETTER N WITH SMALL LETTER J*/
+ 0x1cc, /* U+0001cc: LATIN SMALL LETTER NJ*/
+ 0x1ce, /* U+0001cd: LATIN CAPITAL LETTER A WITH CARON*/
+ 0x1ce, /* U+0001ce: LATIN SMALL LETTER A WITH CARON*/
+ 0x1d0, /* U+0001cf: LATIN CAPITAL LETTER I WITH CARON*/
+ 0x1d0, /* U+0001d0: LATIN SMALL LETTER I WITH CARON*/
+ 0x1d2, /* U+0001d1: LATIN CAPITAL LETTER O WITH CARON*/
+ 0x1d2, /* U+0001d2: LATIN SMALL LETTER O WITH CARON*/
+ 0x1d4, /* U+0001d3: LATIN CAPITAL LETTER U WITH CARON*/
+ 0x1d4, /* U+0001d4: LATIN SMALL LETTER U WITH CARON*/
+ 0x1d6, /* U+0001d5: LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON*/
+ 0x1d6, /* U+0001d6: LATIN SMALL LETTER U WITH DIAERESIS AND MACRON*/
+ 0x1d8, /* U+0001d7: LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE*/
+ 0x1d8, /* U+0001d8: LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE*/
+ 0x1da, /* U+0001d9: LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON*/
+ 0x1da, /* U+0001da: LATIN SMALL LETTER U WITH DIAERESIS AND CARON*/
+ 0x1dc, /* U+0001db: LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE*/
+ 0x1dc, /* U+0001dc: LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE*/
+ 0x1dd, /* U+0001dd: LATIN SMALL LETTER TURNED E*/
+ 0x1df, /* U+0001de: LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON*/
+ 0x1df, /* U+0001df: LATIN SMALL LETTER A WITH DIAERESIS AND MACRON*/
+ 0x1e1, /* U+0001e0: LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON*/
+ 0x1e1, /* U+0001e1: LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON*/
+ 0x1e3, /* U+0001e2: LATIN CAPITAL LETTER AE WITH MACRON*/
+ 0x1e3, /* U+0001e3: LATIN SMALL LETTER AE WITH MACRON*/
+ 0x1e5, /* U+0001e4: LATIN CAPITAL LETTER G WITH STROKE*/
+ 0x1e5, /* U+0001e5: LATIN SMALL LETTER G WITH STROKE*/
+ 0x1e7, /* U+0001e6: LATIN CAPITAL LETTER G WITH CARON*/
+ 0x1e7, /* U+0001e7: LATIN SMALL LETTER G WITH CARON*/
+ 0x1e9, /* U+0001e8: LATIN CAPITAL LETTER K WITH CARON*/
+ 0x1e9, /* U+0001e9: LATIN SMALL LETTER K WITH CARON*/
+ 0x1eb, /* U+0001ea: LATIN CAPITAL LETTER O WITH OGONEK*/
+ 0x1eb, /* U+0001eb: LATIN SMALL LETTER O WITH OGONEK*/
+ 0x1ed, /* U+0001ec: LATIN CAPITAL LETTER O WITH OGONEK AND MACRON*/
+ 0x1ed, /* U+0001ed: LATIN SMALL LETTER O WITH OGONEK AND MACRON*/
+ 0x1ef, /* U+0001ee: LATIN CAPITAL LETTER EZH WITH CARON*/
+ 0x1ef, /* U+0001ef: LATIN SMALL LETTER EZH WITH CARON*/
+ 0x1f0, /* U+0001f0: LATIN SMALL LETTER J WITH CARON*/
+ 0x1f3, /* U+0001f1: LATIN CAPITAL LETTER DZ*/
+ 0x1f3, /* U+0001f2: LATIN CAPITAL LETTER D WITH SMALL LETTER Z*/
+ 0x1f3, /* U+0001f3: LATIN SMALL LETTER DZ*/
+ 0x1f5, /* U+0001f4: LATIN CAPITAL LETTER G WITH ACUTE*/
+ 0x1f5, /* U+0001f5: LATIN SMALL LETTER G WITH ACUTE*/
+ 0x195, /* U+0001f6: LATIN CAPITAL LETTER HWAIR*/
+ 0x1bf, /* U+0001f7: LATIN CAPITAL LETTER WYNN*/
+ 0x1f9, /* U+0001f8: LATIN CAPITAL LETTER N WITH GRAVE*/
+ 0x1f9, /* U+0001f9: LATIN SMALL LETTER N WITH GRAVE*/
+ 0x1fb, /* U+0001fa: LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE*/
+ 0x1fb, /* U+0001fb: LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE*/
+ 0x1fd, /* U+0001fc: LATIN CAPITAL LETTER AE WITH ACUTE*/
+ 0x1fd, /* U+0001fd: LATIN SMALL LETTER AE WITH ACUTE*/
+ 0x1ff, /* U+0001fe: LATIN CAPITAL LETTER O WITH STROKE AND ACUTE*/
+ 0x1ff, /* U+0001ff: LATIN SMALL LETTER O WITH STROKE AND ACUTE*/
+ 0x201, /* U+000200: LATIN CAPITAL LETTER A WITH DOUBLE GRAVE*/
+ 0x201, /* U+000201: LATIN SMALL LETTER A WITH DOUBLE GRAVE*/
+ 0x203, /* U+000202: LATIN CAPITAL LETTER A WITH INVERTED BREVE*/
+ 0x203, /* U+000203: LATIN SMALL LETTER A WITH INVERTED BREVE*/
+ 0x205, /* U+000204: LATIN CAPITAL LETTER E WITH DOUBLE GRAVE*/
+ 0x205, /* U+000205: LATIN SMALL LETTER E WITH DOUBLE GRAVE*/
+ 0x207, /* U+000206: LATIN CAPITAL LETTER E WITH INVERTED BREVE*/
+ 0x207, /* U+000207: LATIN SMALL LETTER E WITH INVERTED BREVE*/
+ 0x209, /* U+000208: LATIN CAPITAL LETTER I WITH DOUBLE GRAVE*/
+ 0x209, /* U+000209: LATIN SMALL LETTER I WITH DOUBLE GRAVE*/
+ 0x20b, /* U+00020a: LATIN CAPITAL LETTER I WITH INVERTED BREVE*/
+ 0x20b, /* U+00020b: LATIN SMALL LETTER I WITH INVERTED BREVE*/
+ 0x20d, /* U+00020c: LATIN CAPITAL LETTER O WITH DOUBLE GRAVE*/
+ 0x20d, /* U+00020d: LATIN SMALL LETTER O WITH DOUBLE GRAVE*/
+ 0x20f, /* U+00020e: LATIN CAPITAL LETTER O WITH INVERTED BREVE*/
+ 0x20f, /* U+00020f: LATIN SMALL LETTER O WITH INVERTED BREVE*/
+ 0x211, /* U+000210: LATIN CAPITAL LETTER R WITH DOUBLE GRAVE*/
+ 0x211, /* U+000211: LATIN SMALL LETTER R WITH DOUBLE GRAVE*/
+ 0x213, /* U+000212: LATIN CAPITAL LETTER R WITH INVERTED BREVE*/
+ 0x213, /* U+000213: LATIN SMALL LETTER R WITH INVERTED BREVE*/
+ 0x215, /* U+000214: LATIN CAPITAL LETTER U WITH DOUBLE GRAVE*/
+ 0x215, /* U+000215: LATIN SMALL LETTER U WITH DOUBLE GRAVE*/
+ 0x217, /* U+000216: LATIN CAPITAL LETTER U WITH INVERTED BREVE*/
+ 0x217, /* U+000217: LATIN SMALL LETTER U WITH INVERTED BREVE*/
+ 0x219, /* U+000218: LATIN CAPITAL LETTER S WITH COMMA BELOW*/
+ 0x219, /* U+000219: LATIN SMALL LETTER S WITH COMMA BELOW*/
+ 0x21b, /* U+00021a: LATIN CAPITAL LETTER T WITH COMMA BELOW*/
+ 0x21b, /* U+00021b: LATIN SMALL LETTER T WITH COMMA BELOW*/
+ 0x21d, /* U+00021c: LATIN CAPITAL LETTER YOGH*/
+ 0x21d, /* U+00021d: LATIN SMALL LETTER YOGH*/
+ 0x21f, /* U+00021e: LATIN CAPITAL LETTER H WITH CARON*/
+ 0x21f, /* U+00021f: LATIN SMALL LETTER H WITH CARON*/
+ 0x19e, /* U+000220: LATIN CAPITAL LETTER N WITH LONG RIGHT LEG*/
+ 0x221, /* U+000221: LATIN SMALL LETTER D WITH CURL*/
+ 0x223, /* U+000222: LATIN CAPITAL LETTER OU*/
+ 0x223, /* U+000223: LATIN SMALL LETTER OU*/
+ 0x225, /* U+000224: LATIN CAPITAL LETTER Z WITH HOOK*/
+ 0x225, /* U+000225: LATIN SMALL LETTER Z WITH HOOK*/
+ 0x227, /* U+000226: LATIN CAPITAL LETTER A WITH DOT ABOVE*/
+ 0x227, /* U+000227: LATIN SMALL LETTER A WITH DOT ABOVE*/
+ 0x229, /* U+000228: LATIN CAPITAL LETTER E WITH CEDILLA*/
+ 0x229, /* U+000229: LATIN SMALL LETTER E WITH CEDILLA*/
+ 0x22b, /* U+00022a: LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON*/
+ 0x22b, /* U+00022b: LATIN SMALL LETTER O WITH DIAERESIS AND MACRON*/
+ 0x22d, /* U+00022c: LATIN CAPITAL LETTER O WITH TILDE AND MACRON*/
+ 0x22d, /* U+00022d: LATIN SMALL LETTER O WITH TILDE AND MACRON*/
+ 0x22f, /* U+00022e: LATIN CAPITAL LETTER O WITH DOT ABOVE*/
+ 0x22f, /* U+00022f: LATIN SMALL LETTER O WITH DOT ABOVE*/
+ 0x231, /* U+000230: LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON*/
+ 0x231, /* U+000231: LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON*/
+ 0x233, /* U+000232: LATIN CAPITAL LETTER Y WITH MACRON*/
+ 0x233, /* U+000233: LATIN SMALL LETTER Y WITH MACRON*/
+ 0x234, /* U+000234: LATIN SMALL LETTER L WITH CURL*/
+ 0x235, /* U+000235: LATIN SMALL LETTER N WITH CURL*/
+ 0x236, /* U+000236: LATIN SMALL LETTER T WITH CURL*/
+ 0x237, /* U+000237: LATIN SMALL LETTER DOTLESS J*/
+ 0x238, /* U+000238: LATIN SMALL LETTER DB DIGRAPH*/
+ 0x239, /* U+000239: LATIN SMALL LETTER QP DIGRAPH*/
+ 0x2c65, /* U+00023a: LATIN CAPITAL LETTER A WITH STROKE*/
+ 0x23c, /* U+00023b: LATIN CAPITAL LETTER C WITH STROKE*/
+ 0x23c, /* U+00023c: LATIN SMALL LETTER C WITH STROKE*/
+ 0x19a, /* U+00023d: LATIN CAPITAL LETTER L WITH BAR*/
+ 0x2c66, /* U+00023e: LATIN CAPITAL LETTER T WITH DIAGONAL STROKE*/
+ 0x23f, /* U+00023f: LATIN SMALL LETTER S WITH SWASH TAIL*/
+ 0x240, /* U+000240: LATIN SMALL LETTER Z WITH SWASH TAIL*/
+ 0x242, /* U+000241: LATIN CAPITAL LETTER GLOTTAL STOP*/
+ 0x242, /* U+000242: LATIN SMALL LETTER GLOTTAL STOP*/
+ 0x180, /* U+000243: LATIN CAPITAL LETTER B WITH STROKE*/
+ 0x289, /* U+000244: LATIN CAPITAL LETTER U BAR*/
+ 0x28c, /* U+000245: LATIN CAPITAL LETTER TURNED V*/
+ 0x247, /* U+000246: LATIN CAPITAL LETTER E WITH STROKE*/
+ 0x247, /* U+000247: LATIN SMALL LETTER E WITH STROKE*/
+ 0x249, /* U+000248: LATIN CAPITAL LETTER J WITH STROKE*/
+ 0x249, /* U+000249: LATIN SMALL LETTER J WITH STROKE*/
+ 0x24b, /* U+00024a: LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL*/
+ 0x24b, /* U+00024b: LATIN SMALL LETTER Q WITH HOOK TAIL*/
+ 0x24d, /* U+00024c: LATIN CAPITAL LETTER R WITH STROKE*/
+ 0x24d, /* U+00024d: LATIN SMALL LETTER R WITH STROKE*/
+ 0x24f, /* U+00024e: LATIN CAPITAL LETTER Y WITH STROKE*/
+ 0x24f, /* U+00024f: LATIN SMALL LETTER Y WITH STROKE*/
+ 0x250, /* U+000250: LATIN SMALL LETTER TURNED A*/
+ 0x251, /* U+000251: LATIN SMALL LETTER ALPHA*/
+ 0x252, /* U+000252: LATIN SMALL LETTER TURNED ALPHA*/
+ 0x253, /* U+000253: LATIN SMALL LETTER B WITH HOOK*/
+ 0x254, /* U+000254: LATIN SMALL LETTER OPEN O*/
+ 0x255, /* U+000255: LATIN SMALL LETTER C WITH CURL*/
+ 0x256, /* U+000256: LATIN SMALL LETTER D WITH TAIL*/
+ 0x257, /* U+000257: LATIN SMALL LETTER D WITH HOOK*/
+ 0x258, /* U+000258: LATIN SMALL LETTER REVERSED E*/
+ 0x259, /* U+000259: LATIN SMALL LETTER SCHWA*/
+ 0x25a, /* U+00025a: LATIN SMALL LETTER SCHWA WITH HOOK*/
+ 0x25b, /* U+00025b: LATIN SMALL LETTER OPEN E*/
+ 0x25c, /* U+00025c: LATIN SMALL LETTER REVERSED OPEN E*/
+ 0x25d, /* U+00025d: LATIN SMALL LETTER REVERSED OPEN E WITH HOOK*/
+ 0x25e, /* U+00025e: LATIN SMALL LETTER CLOSED REVERSED OPEN E*/
+ 0x25f, /* U+00025f: LATIN SMALL LETTER DOTLESS J WITH STROKE*/
+ 0x260, /* U+000260: LATIN SMALL LETTER G WITH HOOK*/
+ 0x261, /* U+000261: LATIN SMALL LETTER SCRIPT G*/
+ 0x262, /* U+000262: LATIN LETTER SMALL CAPITAL G*/
+ 0x263, /* U+000263: LATIN SMALL LETTER GAMMA*/
+ 0x264, /* U+000264: LATIN SMALL LETTER RAMS HORN*/
+ 0x265, /* U+000265: LATIN SMALL LETTER TURNED H*/
+ 0x266, /* U+000266: LATIN SMALL LETTER H WITH HOOK*/
+ 0x267, /* U+000267: LATIN SMALL LETTER HENG WITH HOOK*/
+ 0x268, /* U+000268: LATIN SMALL LETTER I WITH STROKE*/
+ 0x269, /* U+000269: LATIN SMALL LETTER IOTA*/
+ 0x26a, /* U+00026a: LATIN LETTER SMALL CAPITAL I*/
+ 0x26b, /* U+00026b: LATIN SMALL LETTER L WITH MIDDLE TILDE*/
+ 0x26c, /* U+00026c: LATIN SMALL LETTER L WITH BELT*/
+ 0x26d, /* U+00026d: LATIN SMALL LETTER L WITH RETROFLEX HOOK*/
+ 0x26e, /* U+00026e: LATIN SMALL LETTER LEZH*/
+ 0x26f, /* U+00026f: LATIN SMALL LETTER TURNED M*/
+ 0x270, /* U+000270: LATIN SMALL LETTER TURNED M WITH LONG LEG*/
+ 0x271, /* U+000271: LATIN SMALL LETTER M WITH HOOK*/
+ 0x272, /* U+000272: LATIN SMALL LETTER N WITH LEFT HOOK*/
+ 0x273, /* U+000273: LATIN SMALL LETTER N WITH RETROFLEX HOOK*/
+ 0x274, /* U+000274: LATIN LETTER SMALL CAPITAL N*/
+ 0x275, /* U+000275: LATIN SMALL LETTER BARRED O*/
+ 0x276, /* U+000276: LATIN LETTER SMALL CAPITAL OE*/
+ 0x277, /* U+000277: LATIN SMALL LETTER CLOSED OMEGA*/
+ 0x278, /* U+000278: LATIN SMALL LETTER PHI*/
+ 0x279, /* U+000279: LATIN SMALL LETTER TURNED R*/
+ 0x27a, /* U+00027a: LATIN SMALL LETTER TURNED R WITH LONG LEG*/
+ 0x27b, /* U+00027b: LATIN SMALL LETTER TURNED R WITH HOOK*/
+ 0x27c, /* U+00027c: LATIN SMALL LETTER R WITH LONG LEG*/
+ 0x27d, /* U+00027d: LATIN SMALL LETTER R WITH TAIL*/
+ 0x27e, /* U+00027e: LATIN SMALL LETTER R WITH FISHHOOK*/
+ 0x27f, /* U+00027f: LATIN SMALL LETTER REVERSED R WITH FISHHOOK*/
+ 0x280, /* U+000280: LATIN LETTER SMALL CAPITAL R*/
+ 0x281, /* U+000281: LATIN LETTER SMALL CAPITAL INVERTED R*/
+ 0x282, /* U+000282: LATIN SMALL LETTER S WITH HOOK*/
+ 0x283, /* U+000283: LATIN SMALL LETTER ESH*/
+ 0x284, /* U+000284: LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK*/
+ 0x285, /* U+000285: LATIN SMALL LETTER SQUAT REVERSED ESH*/
+ 0x286, /* U+000286: LATIN SMALL LETTER ESH WITH CURL*/
+ 0x287, /* U+000287: LATIN SMALL LETTER TURNED T*/
+ 0x288, /* U+000288: LATIN SMALL LETTER T WITH RETROFLEX HOOK*/
+ 0x289, /* U+000289: LATIN SMALL LETTER U BAR*/
+ 0x28a, /* U+00028a: LATIN SMALL LETTER UPSILON*/
+ 0x28b, /* U+00028b: LATIN SMALL LETTER V WITH HOOK*/
+ 0x28c, /* U+00028c: LATIN SMALL LETTER TURNED V*/
+ 0x28d, /* U+00028d: LATIN SMALL LETTER TURNED W*/
+ 0x28e, /* U+00028e: LATIN SMALL LETTER TURNED Y*/
+ 0x28f, /* U+00028f: LATIN LETTER SMALL CAPITAL Y*/
+ 0x290, /* U+000290: LATIN SMALL LETTER Z WITH RETROFLEX HOOK*/
+ 0x291, /* U+000291: LATIN SMALL LETTER Z WITH CURL*/
+ 0x292, /* U+000292: LATIN SMALL LETTER EZH*/
+ 0x293, /* U+000293: LATIN SMALL LETTER EZH WITH CURL*/
+ 0x294, /* U+000294: LATIN LETTER GLOTTAL STOP*/
+ 0x295, /* U+000295: LATIN LETTER PHARYNGEAL VOICED FRICATIVE*/
+ 0x296, /* U+000296: LATIN LETTER INVERTED GLOTTAL STOP*/
+ 0x297, /* U+000297: LATIN LETTER STRETCHED C*/
+ 0x298, /* U+000298: LATIN LETTER BILABIAL CLICK*/
+ 0x299, /* U+000299: LATIN LETTER SMALL CAPITAL B*/
+ 0x29a, /* U+00029a: LATIN SMALL LETTER CLOSED OPEN E*/
+ 0x29b, /* U+00029b: LATIN LETTER SMALL CAPITAL G WITH HOOK*/
+ 0x29c, /* U+00029c: LATIN LETTER SMALL CAPITAL H*/
+ 0x29d, /* U+00029d: LATIN SMALL LETTER J WITH CROSSED-TAIL*/
+ 0x29e, /* U+00029e: LATIN SMALL LETTER TURNED K*/
+ 0x29f, /* U+00029f: LATIN LETTER SMALL CAPITAL L*/
+ 0x2a0, /* U+0002a0: LATIN SMALL LETTER Q WITH HOOK*/
+ 0x2a1, /* U+0002a1: LATIN LETTER GLOTTAL STOP WITH STROKE*/
+ 0x2a2, /* U+0002a2: LATIN LETTER REVERSED GLOTTAL STOP WITH STROKE*/
+ 0x2a3, /* U+0002a3: LATIN SMALL LETTER DZ DIGRAPH*/
+ 0x2a4, /* U+0002a4: LATIN SMALL LETTER DEZH DIGRAPH*/
+ 0x2a5, /* U+0002a5: LATIN SMALL LETTER DZ DIGRAPH WITH CURL*/
+ 0x2a6, /* U+0002a6: LATIN SMALL LETTER TS DIGRAPH*/
+ 0x2a7, /* U+0002a7: LATIN SMALL LETTER TESH DIGRAPH*/
+ 0x2a8, /* U+0002a8: LATIN SMALL LETTER TC DIGRAPH WITH CURL*/
+ 0x2a9, /* U+0002a9: LATIN SMALL LETTER FENG DIGRAPH*/
+ 0x2aa, /* U+0002aa: LATIN SMALL LETTER LS DIGRAPH*/
+ 0x2ab, /* U+0002ab: LATIN SMALL LETTER LZ DIGRAPH*/
+ 0x2ac, /* U+0002ac: LATIN LETTER BILABIAL PERCUSSIVE*/
+ 0x2ad, /* U+0002ad: LATIN LETTER BIDENTAL PERCUSSIVE*/
+ 0x2ae, /* U+0002ae: LATIN SMALL LETTER TURNED H WITH FISHHOOK*/
+ 0x2af, /* U+0002af: LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL*/
+ 0x2b0, /* U+0002b0: MODIFIER LETTER SMALL H*/
+ 0x2b1, /* U+0002b1: MODIFIER LETTER SMALL H WITH HOOK*/
+ 0x2b2, /* U+0002b2: MODIFIER LETTER SMALL J*/
+ 0x2b3, /* U+0002b3: MODIFIER LETTER SMALL R*/
+ 0x2b4, /* U+0002b4: MODIFIER LETTER SMALL TURNED R*/
+ 0x2b5, /* U+0002b5: MODIFIER LETTER SMALL TURNED R WITH HOOK*/
+ 0x2b6, /* U+0002b6: MODIFIER LETTER SMALL CAPITAL INVERTED R*/
+ 0x2b7, /* U+0002b7: MODIFIER LETTER SMALL W*/
+ 0x2b8, /* U+0002b8: MODIFIER LETTER SMALL Y*/
+ 0x2b9, /* U+0002b9: MODIFIER LETTER PRIME*/
+ 0x2ba, /* U+0002ba: MODIFIER LETTER DOUBLE PRIME*/
+ 0x2bb, /* U+0002bb: MODIFIER LETTER TURNED COMMA*/
+ 0x2bc, /* U+0002bc: MODIFIER LETTER APOSTROPHE*/
+ 0x2bd, /* U+0002bd: MODIFIER LETTER REVERSED COMMA*/
+ 0x2be, /* U+0002be: MODIFIER LETTER RIGHT HALF RING*/
+ 0x2bf, /* U+0002bf: MODIFIER LETTER LEFT HALF RING*/
+ 0x2c0, /* U+0002c0: MODIFIER LETTER GLOTTAL STOP*/
+ 0x2c1, /* U+0002c1: MODIFIER LETTER REVERSED GLOTTAL STOP*/
+ 0x2c2, /* U+0002c2: MODIFIER LETTER LEFT ARROWHEAD*/
+ 0x2c3, /* U+0002c3: MODIFIER LETTER RIGHT ARROWHEAD*/
+ 0x2c4, /* U+0002c4: MODIFIER LETTER UP ARROWHEAD*/
+ 0x2c5, /* U+0002c5: MODIFIER LETTER DOWN ARROWHEAD*/
+ 0x2c6, /* U+0002c6: MODIFIER LETTER CIRCUMFLEX ACCENT*/
+ 0x2c7, /* U+0002c7: CARON*/
+ 0x2c8, /* U+0002c8: MODIFIER LETTER VERTICAL LINE*/
+ 0x2c9, /* U+0002c9: MODIFIER LETTER MACRON*/
+ 0x2ca, /* U+0002ca: MODIFIER LETTER ACUTE ACCENT*/
+ 0x2cb, /* U+0002cb: MODIFIER LETTER GRAVE ACCENT*/
+ 0x2cc, /* U+0002cc: MODIFIER LETTER LOW VERTICAL LINE*/
+ 0x2cd, /* U+0002cd: MODIFIER LETTER LOW MACRON*/
+ 0x2ce, /* U+0002ce: MODIFIER LETTER LOW GRAVE ACCENT*/
+ 0x2cf, /* U+0002cf: MODIFIER LETTER LOW ACUTE ACCENT*/
+ 0x2d0, /* U+0002d0: MODIFIER LETTER TRIANGULAR COLON*/
+ 0x2d1, /* U+0002d1: MODIFIER LETTER HALF TRIANGULAR COLON*/
+ 0x2d2, /* U+0002d2: MODIFIER LETTER CENTRED RIGHT HALF RING*/
+ 0x2d3, /* U+0002d3: MODIFIER LETTER CENTRED LEFT HALF RING*/
+ 0x2d4, /* U+0002d4: MODIFIER LETTER UP TACK*/
+ 0x2d5, /* U+0002d5: MODIFIER LETTER DOWN TACK*/
+ 0x2d6, /* U+0002d6: MODIFIER LETTER PLUS SIGN*/
+ 0x2d7, /* U+0002d7: MODIFIER LETTER MINUS SIGN*/
+ 0x2d8, /* U+0002d8: BREVE*/
+ 0x2d9, /* U+0002d9: DOT ABOVE*/
+ 0x2da, /* U+0002da: RING ABOVE*/
+ 0x2db, /* U+0002db: OGONEK*/
+ 0x2dc, /* U+0002dc: SMALL TILDE*/
+ 0x2dd, /* U+0002dd: DOUBLE ACUTE ACCENT*/
+ 0x2de, /* U+0002de: MODIFIER LETTER RHOTIC HOOK*/
+ 0x2df, /* U+0002df: MODIFIER LETTER CROSS ACCENT*/
+ 0x2e0, /* U+0002e0: MODIFIER LETTER SMALL GAMMA*/
+ 0x2e1, /* U+0002e1: MODIFIER LETTER SMALL L*/
+ 0x2e2, /* U+0002e2: MODIFIER LETTER SMALL S*/
+ 0x2e3, /* U+0002e3: MODIFIER LETTER SMALL X*/
+ 0x2e4, /* U+0002e4: MODIFIER LETTER SMALL REVERSED GLOTTAL STOP*/
+ 0x2e5, /* U+0002e5: MODIFIER LETTER EXTRA-HIGH TONE BAR*/
+ 0x2e6, /* U+0002e6: MODIFIER LETTER HIGH TONE BAR*/
+ 0x2e7, /* U+0002e7: MODIFIER LETTER MID TONE BAR*/
+ 0x2e8, /* U+0002e8: MODIFIER LETTER LOW TONE BAR*/
+ 0x2e9, /* U+0002e9: MODIFIER LETTER EXTRA-LOW TONE BAR*/
+ 0x2ea, /* U+0002ea: MODIFIER LETTER YIN DEPARTING TONE MARK*/
+ 0x2eb, /* U+0002eb: MODIFIER LETTER YANG DEPARTING TONE MARK*/
+ 0x2ec, /* U+0002ec: MODIFIER LETTER VOICING*/
+ 0x2ed, /* U+0002ed: MODIFIER LETTER UNASPIRATED*/
+ 0x2ee, /* U+0002ee: MODIFIER LETTER DOUBLE APOSTROPHE*/
+ 0x2ef, /* U+0002ef: MODIFIER LETTER LOW DOWN ARROWHEAD*/
+ 0x2f0, /* U+0002f0: MODIFIER LETTER LOW UP ARROWHEAD*/
+ 0x2f1, /* U+0002f1: MODIFIER LETTER LOW LEFT ARROWHEAD*/
+ 0x2f2, /* U+0002f2: MODIFIER LETTER LOW RIGHT ARROWHEAD*/
+ 0x2f3, /* U+0002f3: MODIFIER LETTER LOW RING*/
+ 0x2f4, /* U+0002f4: MODIFIER LETTER MIDDLE GRAVE ACCENT*/
+ 0x2f5, /* U+0002f5: MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT*/
+ 0x2f6, /* U+0002f6: MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT*/
+ 0x2f7, /* U+0002f7: MODIFIER LETTER LOW TILDE*/
+ 0x2f8, /* U+0002f8: MODIFIER LETTER RAISED COLON*/
+ 0x2f9, /* U+0002f9: MODIFIER LETTER BEGIN HIGH TONE*/
+ 0x2fa, /* U+0002fa: MODIFIER LETTER END HIGH TONE*/
+ 0x2fb, /* U+0002fb: MODIFIER LETTER BEGIN LOW TONE*/
+ 0x2fc, /* U+0002fc: MODIFIER LETTER END LOW TONE*/
+ 0x2fd, /* U+0002fd: MODIFIER LETTER SHELF*/
+ 0x2fe, /* U+0002fe: MODIFIER LETTER OPEN SHELF*/
+ 0x2ff, /* U+0002ff: MODIFIER LETTER LOW LEFT ARROW*/
+ 0x300, /* U+000300: COMBINING GRAVE ACCENT*/
+ 0x301, /* U+000301: COMBINING ACUTE ACCENT*/
+ 0x302, /* U+000302: COMBINING CIRCUMFLEX ACCENT*/
+ 0x303, /* U+000303: COMBINING TILDE*/
+ 0x304, /* U+000304: COMBINING MACRON*/
+ 0x305, /* U+000305: COMBINING OVERLINE*/
+ 0x306, /* U+000306: COMBINING BREVE*/
+ 0x307, /* U+000307: COMBINING DOT ABOVE*/
+ 0x308, /* U+000308: COMBINING DIAERESIS*/
+ 0x309, /* U+000309: COMBINING HOOK ABOVE*/
+ 0x30a, /* U+00030a: COMBINING RING ABOVE*/
+ 0x30b, /* U+00030b: COMBINING DOUBLE ACUTE ACCENT*/
+ 0x30c, /* U+00030c: COMBINING CARON*/
+ 0x30d, /* U+00030d: COMBINING VERTICAL LINE ABOVE*/
+ 0x30e, /* U+00030e: COMBINING DOUBLE VERTICAL LINE ABOVE*/
+ 0x30f, /* U+00030f: COMBINING DOUBLE GRAVE ACCENT*/
+ 0x310, /* U+000310: COMBINING CANDRABINDU*/
+ 0x311, /* U+000311: COMBINING INVERTED BREVE*/
+ 0x312, /* U+000312: COMBINING TURNED COMMA ABOVE*/
+ 0x313, /* U+000313: COMBINING COMMA ABOVE*/
+ 0x314, /* U+000314: COMBINING REVERSED COMMA ABOVE*/
+ 0x315, /* U+000315: COMBINING COMMA ABOVE RIGHT*/
+ 0x316, /* U+000316: COMBINING GRAVE ACCENT BELOW*/
+ 0x317, /* U+000317: COMBINING ACUTE ACCENT BELOW*/
+ 0x318, /* U+000318: COMBINING LEFT TACK BELOW*/
+ 0x319, /* U+000319: COMBINING RIGHT TACK BELOW*/
+ 0x31a, /* U+00031a: COMBINING LEFT ANGLE ABOVE*/
+ 0x31b, /* U+00031b: COMBINING HORN*/
+ 0x31c, /* U+00031c: COMBINING LEFT HALF RING BELOW*/
+ 0x31d, /* U+00031d: COMBINING UP TACK BELOW*/
+ 0x31e, /* U+00031e: COMBINING DOWN TACK BELOW*/
+ 0x31f, /* U+00031f: COMBINING PLUS SIGN BELOW*/
+ 0x320, /* U+000320: COMBINING MINUS SIGN BELOW*/
+ 0x321, /* U+000321: COMBINING PALATALIZED HOOK BELOW*/
+ 0x322, /* U+000322: COMBINING RETROFLEX HOOK BELOW*/
+ 0x323, /* U+000323: COMBINING DOT BELOW*/
+ 0x324, /* U+000324: COMBINING DIAERESIS BELOW*/
+ 0x325, /* U+000325: COMBINING RING BELOW*/
+ 0x326, /* U+000326: COMBINING COMMA BELOW*/
+ 0x327, /* U+000327: COMBINING CEDILLA*/
+ 0x328, /* U+000328: COMBINING OGONEK*/
+ 0x329, /* U+000329: COMBINING VERTICAL LINE BELOW*/
+ 0x32a, /* U+00032a: COMBINING BRIDGE BELOW*/
+ 0x32b, /* U+00032b: COMBINING INVERTED DOUBLE ARCH BELOW*/
+ 0x32c, /* U+00032c: COMBINING CARON BELOW*/
+ 0x32d, /* U+00032d: COMBINING CIRCUMFLEX ACCENT BELOW*/
+ 0x32e, /* U+00032e: COMBINING BREVE BELOW*/
+ 0x32f, /* U+00032f: COMBINING INVERTED BREVE BELOW*/
+ 0x330, /* U+000330: COMBINING TILDE BELOW*/
+ 0x331, /* U+000331: COMBINING MACRON BELOW*/
+ 0x332, /* U+000332: COMBINING LOW LINE*/
+ 0x333, /* U+000333: COMBINING DOUBLE LOW LINE*/
+ 0x334, /* U+000334: COMBINING TILDE OVERLAY*/
+ 0x335, /* U+000335: COMBINING SHORT STROKE OVERLAY*/
+ 0x336, /* U+000336: COMBINING LONG STROKE OVERLAY*/
+ 0x337, /* U+000337: COMBINING SHORT SOLIDUS OVERLAY*/
+ 0x338, /* U+000338: COMBINING LONG SOLIDUS OVERLAY*/
+ 0x339, /* U+000339: COMBINING RIGHT HALF RING BELOW*/
+ 0x33a, /* U+00033a: COMBINING INVERTED BRIDGE BELOW*/
+ 0x33b, /* U+00033b: COMBINING SQUARE BELOW*/
+ 0x33c, /* U+00033c: COMBINING SEAGULL BELOW*/
+ 0x33d, /* U+00033d: COMBINING X ABOVE*/
+ 0x33e, /* U+00033e: COMBINING VERTICAL TILDE*/
+ 0x33f, /* U+00033f: COMBINING DOUBLE OVERLINE*/
+ 0x340, /* U+000340: COMBINING GRAVE TONE MARK*/
+ 0x341, /* U+000341: COMBINING ACUTE TONE MARK*/
+ 0x342, /* U+000342: COMBINING GREEK PERISPOMENI*/
+ 0x343, /* U+000343: COMBINING GREEK KORONIS*/
+ 0x344, /* U+000344: COMBINING GREEK DIALYTIKA TONOS*/
+ 0x345, /* U+000345: COMBINING GREEK YPOGEGRAMMENI*/
+ 0x346, /* U+000346: COMBINING BRIDGE ABOVE*/
+ 0x347, /* U+000347: COMBINING EQUALS SIGN BELOW*/
+ 0x348, /* U+000348: COMBINING DOUBLE VERTICAL LINE BELOW*/
+ 0x349, /* U+000349: COMBINING LEFT ANGLE BELOW*/
+ 0x34a, /* U+00034a: COMBINING NOT TILDE ABOVE*/
+ 0x34b, /* U+00034b: COMBINING HOMOTHETIC ABOVE*/
+ 0x34c, /* U+00034c: COMBINING ALMOST EQUAL TO ABOVE*/
+ 0x34d, /* U+00034d: COMBINING LEFT RIGHT ARROW BELOW*/
+ 0x34e, /* U+00034e: COMBINING UPWARDS ARROW BELOW*/
+ 0x34f, /* U+00034f: COMBINING GRAPHEME JOINER*/
+ 0x350, /* U+000350: COMBINING RIGHT ARROWHEAD ABOVE*/
+ 0x351, /* U+000351: COMBINING LEFT HALF RING ABOVE*/
+ 0x352, /* U+000352: COMBINING FERMATA*/
+ 0x353, /* U+000353: COMBINING X BELOW*/
+ 0x354, /* U+000354: COMBINING LEFT ARROWHEAD BELOW*/
+ 0x355, /* U+000355: COMBINING RIGHT ARROWHEAD BELOW*/
+ 0x356, /* U+000356: COMBINING RIGHT ARROWHEAD AND UP ARROWHEAD BELOW*/
+ 0x357, /* U+000357: COMBINING RIGHT HALF RING ABOVE*/
+ 0x358, /* U+000358: COMBINING DOT ABOVE RIGHT*/
+ 0x359, /* U+000359: COMBINING ASTERISK BELOW*/
+ 0x35a, /* U+00035a: COMBINING DOUBLE RING BELOW*/
+ 0x35b, /* U+00035b: COMBINING ZIGZAG ABOVE*/
+ 0x35c, /* U+00035c: COMBINING DOUBLE BREVE BELOW*/
+ 0x35d, /* U+00035d: COMBINING DOUBLE BREVE*/
+ 0x35e, /* U+00035e: COMBINING DOUBLE MACRON*/
+ 0x35f, /* U+00035f: COMBINING DOUBLE MACRON BELOW*/
+ 0x360, /* U+000360: COMBINING DOUBLE TILDE*/
+ 0x361, /* U+000361: COMBINING DOUBLE INVERTED BREVE*/
+ 0x362, /* U+000362: COMBINING DOUBLE RIGHTWARDS ARROW BELOW*/
+ 0x363, /* U+000363: COMBINING LATIN SMALL LETTER A*/
+ 0x364, /* U+000364: COMBINING LATIN SMALL LETTER E*/
+ 0x365, /* U+000365: COMBINING LATIN SMALL LETTER I*/
+ 0x366, /* U+000366: COMBINING LATIN SMALL LETTER O*/
+ 0x367, /* U+000367: COMBINING LATIN SMALL LETTER U*/
+ 0x368, /* U+000368: COMBINING LATIN SMALL LETTER C*/
+ 0x369, /* U+000369: COMBINING LATIN SMALL LETTER D*/
+ 0x36a, /* U+00036a: COMBINING LATIN SMALL LETTER H*/
+ 0x36b, /* U+00036b: COMBINING LATIN SMALL LETTER M*/
+ 0x36c, /* U+00036c: COMBINING LATIN SMALL LETTER R*/
+ 0x36d, /* U+00036d: COMBINING LATIN SMALL LETTER T*/
+ 0x36e, /* U+00036e: COMBINING LATIN SMALL LETTER V*/
+ 0x36f, /* U+00036f: COMBINING LATIN SMALL LETTER X*/
+ 0x371, /* U+000370: GREEK CAPITAL LETTER HETA*/
+ 0x371, /* U+000371: GREEK SMALL LETTER HETA*/
+ 0x373, /* U+000372: GREEK CAPITAL LETTER ARCHAIC SAMPI*/
+ 0x373, /* U+000373: GREEK SMALL LETTER ARCHAIC SAMPI*/
+ 0x374, /* U+000374: GREEK NUMERAL SIGN*/
+ 0x375, /* U+000375: GREEK LOWER NUMERAL SIGN*/
+ 0x377, /* U+000376: GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA*/
+ 0x377, /* U+000377: GREEK SMALL LETTER PAMPHYLIAN DIGAMMA*/
+ 0x378, /* U+000378: */
+ 0x379, /* U+000379: */
+ 0x37a, /* U+00037a: GREEK YPOGEGRAMMENI*/
+ 0x37b, /* U+00037b: GREEK SMALL REVERSED LUNATE SIGMA SYMBOL*/
+ 0x37c, /* U+00037c: GREEK SMALL DOTTED LUNATE SIGMA SYMBOL*/
+ 0x37d, /* U+00037d: GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL*/
+ 0x37e, /* U+00037e: GREEK QUESTION MARK*/
+ 0x37f, /* U+00037f: */
+ 0x380, /* U+000380: */
+ 0x381, /* U+000381: */
+ 0x382, /* U+000382: */
+ 0x383, /* U+000383: */
+ 0x384, /* U+000384: GREEK TONOS*/
+ 0x385, /* U+000385: GREEK DIALYTIKA TONOS*/
+ 0x3ac, /* U+000386: GREEK CAPITAL LETTER ALPHA WITH TONOS*/
+ 0x387, /* U+000387: GREEK ANO TELEIA*/
+ 0x3ad, /* U+000388: GREEK CAPITAL LETTER EPSILON WITH TONOS*/
+ 0x3ae, /* U+000389: GREEK CAPITAL LETTER ETA WITH TONOS*/
+ 0x3af, /* U+00038a: GREEK CAPITAL LETTER IOTA WITH TONOS*/
+ 0x38b, /* U+00038b: */
+ 0x3cc, /* U+00038c: GREEK CAPITAL LETTER OMICRON WITH TONOS*/
+ 0x38d, /* U+00038d: */
+ 0x3cd, /* U+00038e: GREEK CAPITAL LETTER UPSILON WITH TONOS*/
+ 0x3ce, /* U+00038f: GREEK CAPITAL LETTER OMEGA WITH TONOS*/
+ 0x390, /* U+000390: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS*/
+ 0x3b1, /* U+000391: GREEK CAPITAL LETTER ALPHA*/
+ 0x3b2, /* U+000392: GREEK CAPITAL LETTER BETA*/
+ 0x3b3, /* U+000393: GREEK CAPITAL LETTER GAMMA*/
+ 0x3b4, /* U+000394: GREEK CAPITAL LETTER DELTA*/
+ 0x3b5, /* U+000395: GREEK CAPITAL LETTER EPSILON*/
+ 0x3b6, /* U+000396: GREEK CAPITAL LETTER ZETA*/
+ 0x3b7, /* U+000397: GREEK CAPITAL LETTER ETA*/
+ 0x3b8, /* U+000398: GREEK CAPITAL LETTER THETA*/
+ 0x3b9, /* U+000399: GREEK CAPITAL LETTER IOTA*/
+ 0x3ba, /* U+00039a: GREEK CAPITAL LETTER KAPPA*/
+ 0x3bb, /* U+00039b: GREEK CAPITAL LETTER LAMDA*/
+ 0x3bc, /* U+00039c: GREEK CAPITAL LETTER MU*/
+ 0x3bd, /* U+00039d: GREEK CAPITAL LETTER NU*/
+ 0x3be, /* U+00039e: GREEK CAPITAL LETTER XI*/
+ 0x3bf, /* U+00039f: GREEK CAPITAL LETTER OMICRON*/
+ 0x3c0, /* U+0003a0: GREEK CAPITAL LETTER PI*/
+ 0x3c1, /* U+0003a1: GREEK CAPITAL LETTER RHO*/
+ 0x3a2, /* U+0003a2: */
+ 0x3c3, /* U+0003a3: GREEK CAPITAL LETTER SIGMA*/
+ 0x3c4, /* U+0003a4: GREEK CAPITAL LETTER TAU*/
+ 0x3c5, /* U+0003a5: GREEK CAPITAL LETTER UPSILON*/
+ 0x3c6, /* U+0003a6: GREEK CAPITAL LETTER PHI*/
+ 0x3c7, /* U+0003a7: GREEK CAPITAL LETTER CHI*/
+ 0x3c8, /* U+0003a8: GREEK CAPITAL LETTER PSI*/
+ 0x3c9, /* U+0003a9: GREEK CAPITAL LETTER OMEGA*/
+ 0x3ca, /* U+0003aa: GREEK CAPITAL LETTER IOTA WITH DIALYTIKA*/
+ 0x3cb, /* U+0003ab: GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA*/
+ 0x3ac, /* U+0003ac: GREEK SMALL LETTER ALPHA WITH TONOS*/
+ 0x3ad, /* U+0003ad: GREEK SMALL LETTER EPSILON WITH TONOS*/
+ 0x3ae, /* U+0003ae: GREEK SMALL LETTER ETA WITH TONOS*/
+ 0x3af, /* U+0003af: GREEK SMALL LETTER IOTA WITH TONOS*/
+ 0x3b0, /* U+0003b0: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS*/
+ 0x3b1, /* U+0003b1: GREEK SMALL LETTER ALPHA*/
+ 0x3b2, /* U+0003b2: GREEK SMALL LETTER BETA*/
+ 0x3b3, /* U+0003b3: GREEK SMALL LETTER GAMMA*/
+ 0x3b4, /* U+0003b4: GREEK SMALL LETTER DELTA*/
+ 0x3b5, /* U+0003b5: GREEK SMALL LETTER EPSILON*/
+ 0x3b6, /* U+0003b6: GREEK SMALL LETTER ZETA*/
+ 0x3b7, /* U+0003b7: GREEK SMALL LETTER ETA*/
+ 0x3b8, /* U+0003b8: GREEK SMALL LETTER THETA*/
+ 0x3b9, /* U+0003b9: GREEK SMALL LETTER IOTA*/
+ 0x3ba, /* U+0003ba: GREEK SMALL LETTER KAPPA*/
+ 0x3bb, /* U+0003bb: GREEK SMALL LETTER LAMDA*/
+ 0x3bc, /* U+0003bc: GREEK SMALL LETTER MU*/
+ 0x3bd, /* U+0003bd: GREEK SMALL LETTER NU*/
+ 0x3be, /* U+0003be: GREEK SMALL LETTER XI*/
+ 0x3bf, /* U+0003bf: GREEK SMALL LETTER OMICRON*/
+ 0x3c0, /* U+0003c0: GREEK SMALL LETTER PI*/
+ 0x3c1, /* U+0003c1: GREEK SMALL LETTER RHO*/
+ 0x3c2, /* U+0003c2: GREEK SMALL LETTER FINAL SIGMA*/
+ 0x3c3, /* U+0003c3: GREEK SMALL LETTER SIGMA*/
+ 0x3c4, /* U+0003c4: GREEK SMALL LETTER TAU*/
+ 0x3c5, /* U+0003c5: GREEK SMALL LETTER UPSILON*/
+ 0x3c6, /* U+0003c6: GREEK SMALL LETTER PHI*/
+ 0x3c7, /* U+0003c7: GREEK SMALL LETTER CHI*/
+ 0x3c8, /* U+0003c8: GREEK SMALL LETTER PSI*/
+ 0x3c9, /* U+0003c9: GREEK SMALL LETTER OMEGA*/
+ 0x3ca, /* U+0003ca: GREEK SMALL LETTER IOTA WITH DIALYTIKA*/
+ 0x3cb, /* U+0003cb: GREEK SMALL LETTER UPSILON WITH DIALYTIKA*/
+ 0x3cc, /* U+0003cc: GREEK SMALL LETTER OMICRON WITH TONOS*/
+ 0x3cd, /* U+0003cd: GREEK SMALL LETTER UPSILON WITH TONOS*/
+ 0x3ce, /* U+0003ce: GREEK SMALL LETTER OMEGA WITH TONOS*/
+ 0x3d7, /* U+0003cf: GREEK CAPITAL KAI SYMBOL*/
+ 0x3d0, /* U+0003d0: GREEK BETA SYMBOL*/
+ 0x3d1, /* U+0003d1: GREEK THETA SYMBOL*/
+ 0x3d2, /* U+0003d2: GREEK UPSILON WITH HOOK SYMBOL*/
+ 0x3d3, /* U+0003d3: GREEK UPSILON WITH ACUTE AND HOOK SYMBOL*/
+ 0x3d4, /* U+0003d4: GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL*/
+ 0x3d5, /* U+0003d5: GREEK PHI SYMBOL*/
+ 0x3d6, /* U+0003d6: GREEK PI SYMBOL*/
+ 0x3d7, /* U+0003d7: GREEK KAI SYMBOL*/
+ 0x3d9, /* U+0003d8: GREEK LETTER ARCHAIC KOPPA*/
+ 0x3d9, /* U+0003d9: GREEK SMALL LETTER ARCHAIC KOPPA*/
+ 0x3db, /* U+0003da: GREEK LETTER STIGMA*/
+ 0x3db, /* U+0003db: GREEK SMALL LETTER STIGMA*/
+ 0x3dd, /* U+0003dc: GREEK LETTER DIGAMMA*/
+ 0x3dd, /* U+0003dd: GREEK SMALL LETTER DIGAMMA*/
+ 0x3df, /* U+0003de: GREEK LETTER KOPPA*/
+ 0x3df, /* U+0003df: GREEK SMALL LETTER KOPPA*/
+ 0x3e1, /* U+0003e0: GREEK LETTER SAMPI*/
+ 0x3e1, /* U+0003e1: GREEK SMALL LETTER SAMPI*/
+ 0x3e3, /* U+0003e2: COPTIC CAPITAL LETTER SHEI*/
+ 0x3e3, /* U+0003e3: COPTIC SMALL LETTER SHEI*/
+ 0x3e5, /* U+0003e4: COPTIC CAPITAL LETTER FEI*/
+ 0x3e5, /* U+0003e5: COPTIC SMALL LETTER FEI*/
+ 0x3e7, /* U+0003e6: COPTIC CAPITAL LETTER KHEI*/
+ 0x3e7, /* U+0003e7: COPTIC SMALL LETTER KHEI*/
+ 0x3e9, /* U+0003e8: COPTIC CAPITAL LETTER HORI*/
+ 0x3e9, /* U+0003e9: COPTIC SMALL LETTER HORI*/
+ 0x3eb, /* U+0003ea: COPTIC CAPITAL LETTER GANGIA*/
+ 0x3eb, /* U+0003eb: COPTIC SMALL LETTER GANGIA*/
+ 0x3ed, /* U+0003ec: COPTIC CAPITAL LETTER SHIMA*/
+ 0x3ed, /* U+0003ed: COPTIC SMALL LETTER SHIMA*/
+ 0x3ef, /* U+0003ee: COPTIC CAPITAL LETTER DEI*/
+ 0x3ef, /* U+0003ef: COPTIC SMALL LETTER DEI*/
+ 0x3f0, /* U+0003f0: GREEK KAPPA SYMBOL*/
+ 0x3f1, /* U+0003f1: GREEK RHO SYMBOL*/
+ 0x3f2, /* U+0003f2: GREEK LUNATE SIGMA SYMBOL*/
+ 0x3f3, /* U+0003f3: GREEK LETTER YOT*/
+ 0x3b8, /* U+0003f4: GREEK CAPITAL THETA SYMBOL*/
+ 0x3f5, /* U+0003f5: GREEK LUNATE EPSILON SYMBOL*/
+ 0x3f6, /* U+0003f6: GREEK REVERSED LUNATE EPSILON SYMBOL*/
+ 0x3f8, /* U+0003f7: GREEK CAPITAL LETTER SHO*/
+ 0x3f8, /* U+0003f8: GREEK SMALL LETTER SHO*/
+ 0x3f2, /* U+0003f9: GREEK CAPITAL LUNATE SIGMA SYMBOL*/
+ 0x3fb, /* U+0003fa: GREEK CAPITAL LETTER SAN*/
+ 0x3fb, /* U+0003fb: GREEK SMALL LETTER SAN*/
+ 0x3fc, /* U+0003fc: GREEK RHO WITH STROKE SYMBOL*/
+ 0x37b, /* U+0003fd: GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL*/
+ 0x37c, /* U+0003fe: GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL*/
+ 0x37d, /* U+0003ff: GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL*/
+ 0x450, /* U+000400: CYRILLIC CAPITAL LETTER IE WITH GRAVE*/
+ 0x451, /* U+000401: CYRILLIC CAPITAL LETTER IO*/
+ 0x452, /* U+000402: CYRILLIC CAPITAL LETTER DJE*/
+ 0x453, /* U+000403: CYRILLIC CAPITAL LETTER GJE*/
+ 0x454, /* U+000404: CYRILLIC CAPITAL LETTER UKRAINIAN IE*/
+ 0x455, /* U+000405: CYRILLIC CAPITAL LETTER DZE*/
+ 0x456, /* U+000406: CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I*/
+ 0x457, /* U+000407: CYRILLIC CAPITAL LETTER YI*/
+ 0x458, /* U+000408: CYRILLIC CAPITAL LETTER JE*/
+ 0x459, /* U+000409: CYRILLIC CAPITAL LETTER LJE*/
+ 0x45a, /* U+00040a: CYRILLIC CAPITAL LETTER NJE*/
+ 0x45b, /* U+00040b: CYRILLIC CAPITAL LETTER TSHE*/
+ 0x45c, /* U+00040c: CYRILLIC CAPITAL LETTER KJE*/
+ 0x45d, /* U+00040d: CYRILLIC CAPITAL LETTER I WITH GRAVE*/
+ 0x45e, /* U+00040e: CYRILLIC CAPITAL LETTER SHORT U*/
+ 0x45f, /* U+00040f: CYRILLIC CAPITAL LETTER DZHE*/
+ 0x430, /* U+000410: CYRILLIC CAPITAL LETTER A*/
+ 0x431, /* U+000411: CYRILLIC CAPITAL LETTER BE*/
+ 0x432, /* U+000412: CYRILLIC CAPITAL LETTER VE*/
+ 0x433, /* U+000413: CYRILLIC CAPITAL LETTER GHE*/
+ 0x434, /* U+000414: CYRILLIC CAPITAL LETTER DE*/
+ 0x435, /* U+000415: CYRILLIC CAPITAL LETTER IE*/
+ 0x436, /* U+000416: CYRILLIC CAPITAL LETTER ZHE*/
+ 0x437, /* U+000417: CYRILLIC CAPITAL LETTER ZE*/
+ 0x438, /* U+000418: CYRILLIC CAPITAL LETTER I*/
+ 0x439, /* U+000419: CYRILLIC CAPITAL LETTER SHORT I*/
+ 0x43a, /* U+00041a: CYRILLIC CAPITAL LETTER KA*/
+ 0x43b, /* U+00041b: CYRILLIC CAPITAL LETTER EL*/
+ 0x43c, /* U+00041c: CYRILLIC CAPITAL LETTER EM*/
+ 0x43d, /* U+00041d: CYRILLIC CAPITAL LETTER EN*/
+ 0x43e, /* U+00041e: CYRILLIC CAPITAL LETTER O*/
+ 0x43f, /* U+00041f: CYRILLIC CAPITAL LETTER PE*/
+ 0x440, /* U+000420: CYRILLIC CAPITAL LETTER ER*/
+ 0x441, /* U+000421: CYRILLIC CAPITAL LETTER ES*/
+ 0x442, /* U+000422: CYRILLIC CAPITAL LETTER TE*/
+ 0x443, /* U+000423: CYRILLIC CAPITAL LETTER U*/
+ 0x444, /* U+000424: CYRILLIC CAPITAL LETTER EF*/
+ 0x445, /* U+000425: CYRILLIC CAPITAL LETTER HA*/
+ 0x446, /* U+000426: CYRILLIC CAPITAL LETTER TSE*/
+ 0x447, /* U+000427: CYRILLIC CAPITAL LETTER CHE*/
+ 0x448, /* U+000428: CYRILLIC CAPITAL LETTER SHA*/
+ 0x449, /* U+000429: CYRILLIC CAPITAL LETTER SHCHA*/
+ 0x44a, /* U+00042a: CYRILLIC CAPITAL LETTER HARD SIGN*/
+ 0x44b, /* U+00042b: CYRILLIC CAPITAL LETTER YERU*/
+ 0x44c, /* U+00042c: CYRILLIC CAPITAL LETTER SOFT SIGN*/
+ 0x44d, /* U+00042d: CYRILLIC CAPITAL LETTER E*/
+ 0x44e, /* U+00042e: CYRILLIC CAPITAL LETTER YU*/
+ 0x44f, /* U+00042f: CYRILLIC CAPITAL LETTER YA*/
+ 0x430, /* U+000430: CYRILLIC SMALL LETTER A*/
+ 0x431, /* U+000431: CYRILLIC SMALL LETTER BE*/
+ 0x432, /* U+000432: CYRILLIC SMALL LETTER VE*/
+ 0x433, /* U+000433: CYRILLIC SMALL LETTER GHE*/
+ 0x434, /* U+000434: CYRILLIC SMALL LETTER DE*/
+ 0x435, /* U+000435: CYRILLIC SMALL LETTER IE*/
+ 0x436, /* U+000436: CYRILLIC SMALL LETTER ZHE*/
+ 0x437, /* U+000437: CYRILLIC SMALL LETTER ZE*/
+ 0x438, /* U+000438: CYRILLIC SMALL LETTER I*/
+ 0x439, /* U+000439: CYRILLIC SMALL LETTER SHORT I*/
+ 0x43a, /* U+00043a: CYRILLIC SMALL LETTER KA*/
+ 0x43b, /* U+00043b: CYRILLIC SMALL LETTER EL*/
+ 0x43c, /* U+00043c: CYRILLIC SMALL LETTER EM*/
+ 0x43d, /* U+00043d: CYRILLIC SMALL LETTER EN*/
+ 0x43e, /* U+00043e: CYRILLIC SMALL LETTER O*/
+ 0x43f, /* U+00043f: CYRILLIC SMALL LETTER PE*/
+ 0x440, /* U+000440: CYRILLIC SMALL LETTER ER*/
+ 0x441, /* U+000441: CYRILLIC SMALL LETTER ES*/
+ 0x442, /* U+000442: CYRILLIC SMALL LETTER TE*/
+ 0x443, /* U+000443: CYRILLIC SMALL LETTER U*/
+ 0x444, /* U+000444: CYRILLIC SMALL LETTER EF*/
+ 0x445, /* U+000445: CYRILLIC SMALL LETTER HA*/
+ 0x446, /* U+000446: CYRILLIC SMALL LETTER TSE*/
+ 0x447, /* U+000447: CYRILLIC SMALL LETTER CHE*/
+ 0x448, /* U+000448: CYRILLIC SMALL LETTER SHA*/
+ 0x449, /* U+000449: CYRILLIC SMALL LETTER SHCHA*/
+ 0x44a, /* U+00044a: CYRILLIC SMALL LETTER HARD SIGN*/
+ 0x44b, /* U+00044b: CYRILLIC SMALL LETTER YERU*/
+ 0x44c, /* U+00044c: CYRILLIC SMALL LETTER SOFT SIGN*/
+ 0x44d, /* U+00044d: CYRILLIC SMALL LETTER E*/
+ 0x44e, /* U+00044e: CYRILLIC SMALL LETTER YU*/
+ 0x44f, /* U+00044f: CYRILLIC SMALL LETTER YA*/
+ 0x450, /* U+000450: CYRILLIC SMALL LETTER IE WITH GRAVE*/
+ 0x451, /* U+000451: CYRILLIC SMALL LETTER IO*/
+ 0x452, /* U+000452: CYRILLIC SMALL LETTER DJE*/
+ 0x453, /* U+000453: CYRILLIC SMALL LETTER GJE*/
+ 0x454, /* U+000454: CYRILLIC SMALL LETTER UKRAINIAN IE*/
+ 0x455, /* U+000455: CYRILLIC SMALL LETTER DZE*/
+ 0x456, /* U+000456: CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I*/
+ 0x457, /* U+000457: CYRILLIC SMALL LETTER YI*/
+ 0x458, /* U+000458: CYRILLIC SMALL LETTER JE*/
+ 0x459, /* U+000459: CYRILLIC SMALL LETTER LJE*/
+ 0x45a, /* U+00045a: CYRILLIC SMALL LETTER NJE*/
+ 0x45b, /* U+00045b: CYRILLIC SMALL LETTER TSHE*/
+ 0x45c, /* U+00045c: CYRILLIC SMALL LETTER KJE*/
+ 0x45d, /* U+00045d: CYRILLIC SMALL LETTER I WITH GRAVE*/
+ 0x45e, /* U+00045e: CYRILLIC SMALL LETTER SHORT U*/
+ 0x45f, /* U+00045f: CYRILLIC SMALL LETTER DZHE*/
+ 0x461, /* U+000460: CYRILLIC CAPITAL LETTER OMEGA*/
+ 0x461, /* U+000461: CYRILLIC SMALL LETTER OMEGA*/
+ 0x463, /* U+000462: CYRILLIC CAPITAL LETTER YAT*/
+ 0x463, /* U+000463: CYRILLIC SMALL LETTER YAT*/
+ 0x465, /* U+000464: CYRILLIC CAPITAL LETTER IOTIFIED E*/
+ 0x465, /* U+000465: CYRILLIC SMALL LETTER IOTIFIED E*/
+ 0x467, /* U+000466: CYRILLIC CAPITAL LETTER LITTLE YUS*/
+ 0x467, /* U+000467: CYRILLIC SMALL LETTER LITTLE YUS*/
+ 0x469, /* U+000468: CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS*/
+ 0x469, /* U+000469: CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS*/
+ 0x46b, /* U+00046a: CYRILLIC CAPITAL LETTER BIG YUS*/
+ 0x46b, /* U+00046b: CYRILLIC SMALL LETTER BIG YUS*/
+ 0x46d, /* U+00046c: CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS*/
+ 0x46d, /* U+00046d: CYRILLIC SMALL LETTER IOTIFIED BIG YUS*/
+ 0x46f, /* U+00046e: CYRILLIC CAPITAL LETTER KSI*/
+ 0x46f, /* U+00046f: CYRILLIC SMALL LETTER KSI*/
+ 0x471, /* U+000470: CYRILLIC CAPITAL LETTER PSI*/
+ 0x471, /* U+000471: CYRILLIC SMALL LETTER PSI*/
+ 0x473, /* U+000472: CYRILLIC CAPITAL LETTER FITA*/
+ 0x473, /* U+000473: CYRILLIC SMALL LETTER FITA*/
+ 0x475, /* U+000474: CYRILLIC CAPITAL LETTER IZHITSA*/
+ 0x475, /* U+000475: CYRILLIC SMALL LETTER IZHITSA*/
+ 0x477, /* U+000476: CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT*/
+ 0x477, /* U+000477: CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT*/
+ 0x479, /* U+000478: CYRILLIC CAPITAL LETTER UK*/
+ 0x479, /* U+000479: CYRILLIC SMALL LETTER UK*/
+ 0x47b, /* U+00047a: CYRILLIC CAPITAL LETTER ROUND OMEGA*/
+ 0x47b, /* U+00047b: CYRILLIC SMALL LETTER ROUND OMEGA*/
+ 0x47d, /* U+00047c: CYRILLIC CAPITAL LETTER OMEGA WITH TITLO*/
+ 0x47d, /* U+00047d: CYRILLIC SMALL LETTER OMEGA WITH TITLO*/
+ 0x47f, /* U+00047e: CYRILLIC CAPITAL LETTER OT*/
+ 0x47f, /* U+00047f: CYRILLIC SMALL LETTER OT*/
+ 0x481, /* U+000480: CYRILLIC CAPITAL LETTER KOPPA*/
+ 0x481, /* U+000481: CYRILLIC SMALL LETTER KOPPA*/
+ 0x482, /* U+000482: CYRILLIC THOUSANDS SIGN*/
+ 0x483, /* U+000483: COMBINING CYRILLIC TITLO*/
+ 0x484, /* U+000484: COMBINING CYRILLIC PALATALIZATION*/
+ 0x485, /* U+000485: COMBINING CYRILLIC DASIA PNEUMATA*/
+ 0x486, /* U+000486: COMBINING CYRILLIC PSILI PNEUMATA*/
+ 0x487, /* U+000487: COMBINING CYRILLIC POKRYTIE*/
+ 0x488, /* U+000488: COMBINING CYRILLIC HUNDRED THOUSANDS SIGN*/
+ 0x489, /* U+000489: COMBINING CYRILLIC MILLIONS SIGN*/
+ 0x48b, /* U+00048a: CYRILLIC CAPITAL LETTER SHORT I WITH TAIL*/
+ 0x48b, /* U+00048b: CYRILLIC SMALL LETTER SHORT I WITH TAIL*/
+ 0x48d, /* U+00048c: CYRILLIC CAPITAL LETTER SEMISOFT SIGN*/
+ 0x48d, /* U+00048d: CYRILLIC SMALL LETTER SEMISOFT SIGN*/
+ 0x48f, /* U+00048e: CYRILLIC CAPITAL LETTER ER WITH TICK*/
+ 0x48f, /* U+00048f: CYRILLIC SMALL LETTER ER WITH TICK*/
+ 0x491, /* U+000490: CYRILLIC CAPITAL LETTER GHE WITH UPTURN*/
+ 0x491, /* U+000491: CYRILLIC SMALL LETTER GHE WITH UPTURN*/
+ 0x493, /* U+000492: CYRILLIC CAPITAL LETTER GHE WITH STROKE*/
+ 0x493, /* U+000493: CYRILLIC SMALL LETTER GHE WITH STROKE*/
+ 0x495, /* U+000494: CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK*/
+ 0x495, /* U+000495: CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK*/
+ 0x497, /* U+000496: CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER*/
+ 0x497, /* U+000497: CYRILLIC SMALL LETTER ZHE WITH DESCENDER*/
+ 0x499, /* U+000498: CYRILLIC CAPITAL LETTER ZE WITH DESCENDER*/
+ 0x499, /* U+000499: CYRILLIC SMALL LETTER ZE WITH DESCENDER*/
+ 0x49b, /* U+00049a: CYRILLIC CAPITAL LETTER KA WITH DESCENDER*/
+ 0x49b, /* U+00049b: CYRILLIC SMALL LETTER KA WITH DESCENDER*/
+ 0x49d, /* U+00049c: CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE*/
+ 0x49d, /* U+00049d: CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE*/
+ 0x49f, /* U+00049e: CYRILLIC CAPITAL LETTER KA WITH STROKE*/
+ 0x49f, /* U+00049f: CYRILLIC SMALL LETTER KA WITH STROKE*/
+ 0x4a1, /* U+0004a0: CYRILLIC CAPITAL LETTER BASHKIR KA*/
+ 0x4a1, /* U+0004a1: CYRILLIC SMALL LETTER BASHKIR KA*/
+ 0x4a3, /* U+0004a2: CYRILLIC CAPITAL LETTER EN WITH DESCENDER*/
+ 0x4a3, /* U+0004a3: CYRILLIC SMALL LETTER EN WITH DESCENDER*/
+ 0x4a5, /* U+0004a4: CYRILLIC CAPITAL LIGATURE EN GHE*/
+ 0x4a5, /* U+0004a5: CYRILLIC SMALL LIGATURE EN GHE*/
+ 0x4a7, /* U+0004a6: CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK*/
+ 0x4a7, /* U+0004a7: CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK*/
+ 0x4a9, /* U+0004a8: CYRILLIC CAPITAL LETTER ABKHASIAN HA*/
+ 0x4a9, /* U+0004a9: CYRILLIC SMALL LETTER ABKHASIAN HA*/
+ 0x4ab, /* U+0004aa: CYRILLIC CAPITAL LETTER ES WITH DESCENDER*/
+ 0x4ab, /* U+0004ab: CYRILLIC SMALL LETTER ES WITH DESCENDER*/
+ 0x4ad, /* U+0004ac: CYRILLIC CAPITAL LETTER TE WITH DESCENDER*/
+ 0x4ad, /* U+0004ad: CYRILLIC SMALL LETTER TE WITH DESCENDER*/
+ 0x4af, /* U+0004ae: CYRILLIC CAPITAL LETTER STRAIGHT U*/
+ 0x4af, /* U+0004af: CYRILLIC SMALL LETTER STRAIGHT U*/
+ 0x4b1, /* U+0004b0: CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE*/
+ 0x4b1, /* U+0004b1: CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE*/
+ 0x4b3, /* U+0004b2: CYRILLIC CAPITAL LETTER HA WITH DESCENDER*/
+ 0x4b3, /* U+0004b3: CYRILLIC SMALL LETTER HA WITH DESCENDER*/
+ 0x4b5, /* U+0004b4: CYRILLIC CAPITAL LIGATURE TE TSE*/
+ 0x4b5, /* U+0004b5: CYRILLIC SMALL LIGATURE TE TSE*/
+ 0x4b7, /* U+0004b6: CYRILLIC CAPITAL LETTER CHE WITH DESCENDER*/
+ 0x4b7, /* U+0004b7: CYRILLIC SMALL LETTER CHE WITH DESCENDER*/
+ 0x4b9, /* U+0004b8: CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE*/
+ 0x4b9, /* U+0004b9: CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE*/
+ 0x4bb, /* U+0004ba: CYRILLIC CAPITAL LETTER SHHA*/
+ 0x4bb, /* U+0004bb: CYRILLIC SMALL LETTER SHHA*/
+ 0x4bd, /* U+0004bc: CYRILLIC CAPITAL LETTER ABKHASIAN CHE*/
+ 0x4bd, /* U+0004bd: CYRILLIC SMALL LETTER ABKHASIAN CHE*/
+ 0x4bf, /* U+0004be: CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER*/
+ 0x4bf, /* U+0004bf: CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER*/
+ 0x4cf, /* U+0004c0: CYRILLIC LETTER PALOCHKA*/
+ 0x4c2, /* U+0004c1: CYRILLIC CAPITAL LETTER ZHE WITH BREVE*/
+ 0x4c2, /* U+0004c2: CYRILLIC SMALL LETTER ZHE WITH BREVE*/
+ 0x4c4, /* U+0004c3: CYRILLIC CAPITAL LETTER KA WITH HOOK*/
+ 0x4c4, /* U+0004c4: CYRILLIC SMALL LETTER KA WITH HOOK*/
+ 0x4c6, /* U+0004c5: CYRILLIC CAPITAL LETTER EL WITH TAIL*/
+ 0x4c6, /* U+0004c6: CYRILLIC SMALL LETTER EL WITH TAIL*/
+ 0x4c8, /* U+0004c7: CYRILLIC CAPITAL LETTER EN WITH HOOK*/
+ 0x4c8, /* U+0004c8: CYRILLIC SMALL LETTER EN WITH HOOK*/
+ 0x4ca, /* U+0004c9: CYRILLIC CAPITAL LETTER EN WITH TAIL*/
+ 0x4ca, /* U+0004ca: CYRILLIC SMALL LETTER EN WITH TAIL*/
+ 0x4cc, /* U+0004cb: CYRILLIC CAPITAL LETTER KHAKASSIAN CHE*/
+ 0x4cc, /* U+0004cc: CYRILLIC SMALL LETTER KHAKASSIAN CHE*/
+ 0x4ce, /* U+0004cd: CYRILLIC CAPITAL LETTER EM WITH TAIL*/
+ 0x4ce, /* U+0004ce: CYRILLIC SMALL LETTER EM WITH TAIL*/
+ 0x4cf, /* U+0004cf: CYRILLIC SMALL LETTER PALOCHKA*/
+ 0x4d1, /* U+0004d0: CYRILLIC CAPITAL LETTER A WITH BREVE*/
+ 0x4d1, /* U+0004d1: CYRILLIC SMALL LETTER A WITH BREVE*/
+ 0x4d3, /* U+0004d2: CYRILLIC CAPITAL LETTER A WITH DIAERESIS*/
+ 0x4d3, /* U+0004d3: CYRILLIC SMALL LETTER A WITH DIAERESIS*/
+ 0x4d5, /* U+0004d4: CYRILLIC CAPITAL LIGATURE A IE*/
+ 0x4d5, /* U+0004d5: CYRILLIC SMALL LIGATURE A IE*/
+ 0x4d7, /* U+0004d6: CYRILLIC CAPITAL LETTER IE WITH BREVE*/
+ 0x4d7, /* U+0004d7: CYRILLIC SMALL LETTER IE WITH BREVE*/
+ 0x4d9, /* U+0004d8: CYRILLIC CAPITAL LETTER SCHWA*/
+ 0x4d9, /* U+0004d9: CYRILLIC SMALL LETTER SCHWA*/
+ 0x4db, /* U+0004da: CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS*/
+ 0x4db, /* U+0004db: CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS*/
+ 0x4dd, /* U+0004dc: CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS*/
+ 0x4dd, /* U+0004dd: CYRILLIC SMALL LETTER ZHE WITH DIAERESIS*/
+ 0x4df, /* U+0004de: CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS*/
+ 0x4df, /* U+0004df: CYRILLIC SMALL LETTER ZE WITH DIAERESIS*/
+ 0x4e1, /* U+0004e0: CYRILLIC CAPITAL LETTER ABKHASIAN DZE*/
+ 0x4e1, /* U+0004e1: CYRILLIC SMALL LETTER ABKHASIAN DZE*/
+ 0x4e3, /* U+0004e2: CYRILLIC CAPITAL LETTER I WITH MACRON*/
+ 0x4e3, /* U+0004e3: CYRILLIC SMALL LETTER I WITH MACRON*/
+ 0x4e5, /* U+0004e4: CYRILLIC CAPITAL LETTER I WITH DIAERESIS*/
+ 0x4e5, /* U+0004e5: CYRILLIC SMALL LETTER I WITH DIAERESIS*/
+ 0x4e7, /* U+0004e6: CYRILLIC CAPITAL LETTER O WITH DIAERESIS*/
+ 0x4e7, /* U+0004e7: CYRILLIC SMALL LETTER O WITH DIAERESIS*/
+ 0x4e9, /* U+0004e8: CYRILLIC CAPITAL LETTER BARRED O*/
+ 0x4e9, /* U+0004e9: CYRILLIC SMALL LETTER BARRED O*/
+ 0x4eb, /* U+0004ea: CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS*/
+ 0x4eb, /* U+0004eb: CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS*/
+ 0x4ed, /* U+0004ec: CYRILLIC CAPITAL LETTER E WITH DIAERESIS*/
+ 0x4ed, /* U+0004ed: CYRILLIC SMALL LETTER E WITH DIAERESIS*/
+ 0x4ef, /* U+0004ee: CYRILLIC CAPITAL LETTER U WITH MACRON*/
+ 0x4ef, /* U+0004ef: CYRILLIC SMALL LETTER U WITH MACRON*/
+ 0x4f1, /* U+0004f0: CYRILLIC CAPITAL LETTER U WITH DIAERESIS*/
+ 0x4f1, /* U+0004f1: CYRILLIC SMALL LETTER U WITH DIAERESIS*/
+ 0x4f3, /* U+0004f2: CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE*/
+ 0x4f3, /* U+0004f3: CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE*/
+ 0x4f5, /* U+0004f4: CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS*/
+ 0x4f5, /* U+0004f5: CYRILLIC SMALL LETTER CHE WITH DIAERESIS*/
+ 0x4f7, /* U+0004f6: CYRILLIC CAPITAL LETTER GHE WITH DESCENDER*/
+ 0x4f7, /* U+0004f7: CYRILLIC SMALL LETTER GHE WITH DESCENDER*/
+ 0x4f9, /* U+0004f8: CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS*/
+ 0x4f9, /* U+0004f9: CYRILLIC SMALL LETTER YERU WITH DIAERESIS*/
+ 0x4fb, /* U+0004fa: CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK*/
+ 0x4fb, /* U+0004fb: CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK*/
+ 0x4fd, /* U+0004fc: CYRILLIC CAPITAL LETTER HA WITH HOOK*/
+ 0x4fd, /* U+0004fd: CYRILLIC SMALL LETTER HA WITH HOOK*/
+ 0x4ff, /* U+0004fe: CYRILLIC CAPITAL LETTER HA WITH STROKE*/
+ 0x4ff, /* U+0004ff: CYRILLIC SMALL LETTER HA WITH STROKE*/
+ 0x501, /* U+000500: CYRILLIC CAPITAL LETTER KOMI DE*/
+ 0x501, /* U+000501: CYRILLIC SMALL LETTER KOMI DE*/
+ 0x503, /* U+000502: CYRILLIC CAPITAL LETTER KOMI DJE*/
+ 0x503, /* U+000503: CYRILLIC SMALL LETTER KOMI DJE*/
+ 0x505, /* U+000504: CYRILLIC CAPITAL LETTER KOMI ZJE*/
+ 0x505, /* U+000505: CYRILLIC SMALL LETTER KOMI ZJE*/
+ 0x507, /* U+000506: CYRILLIC CAPITAL LETTER KOMI DZJE*/
+ 0x507, /* U+000507: CYRILLIC SMALL LETTER KOMI DZJE*/
+ 0x509, /* U+000508: CYRILLIC CAPITAL LETTER KOMI LJE*/
+ 0x509, /* U+000509: CYRILLIC SMALL LETTER KOMI LJE*/
+ 0x50b, /* U+00050a: CYRILLIC CAPITAL LETTER KOMI NJE*/
+ 0x50b, /* U+00050b: CYRILLIC SMALL LETTER KOMI NJE*/
+ 0x50d, /* U+00050c: CYRILLIC CAPITAL LETTER KOMI SJE*/
+ 0x50d, /* U+00050d: CYRILLIC SMALL LETTER KOMI SJE*/
+ 0x50f, /* U+00050e: CYRILLIC CAPITAL LETTER KOMI TJE*/
+ 0x50f, /* U+00050f: CYRILLIC SMALL LETTER KOMI TJE*/
+ 0x511, /* U+000510: CYRILLIC CAPITAL LETTER REVERSED ZE*/
+ 0x511, /* U+000511: CYRILLIC SMALL LETTER REVERSED ZE*/
+ 0x513, /* U+000512: CYRILLIC CAPITAL LETTER EL WITH HOOK*/
+ 0x513, /* U+000513: CYRILLIC SMALL LETTER EL WITH HOOK*/
+ 0x515, /* U+000514: CYRILLIC CAPITAL LETTER LHA*/
+ 0x515, /* U+000515: CYRILLIC SMALL LETTER LHA*/
+ 0x517, /* U+000516: CYRILLIC CAPITAL LETTER RHA*/
+ 0x517, /* U+000517: CYRILLIC SMALL LETTER RHA*/
+ 0x519, /* U+000518: CYRILLIC CAPITAL LETTER YAE*/
+ 0x519, /* U+000519: CYRILLIC SMALL LETTER YAE*/
+ 0x51b, /* U+00051a: CYRILLIC CAPITAL LETTER QA*/
+ 0x51b, /* U+00051b: CYRILLIC SMALL LETTER QA*/
+ 0x51d, /* U+00051c: CYRILLIC CAPITAL LETTER WE*/
+ 0x51d, /* U+00051d: CYRILLIC SMALL LETTER WE*/
+ 0x51f, /* U+00051e: CYRILLIC CAPITAL LETTER ALEUT KA*/
+ 0x51f, /* U+00051f: CYRILLIC SMALL LETTER ALEUT KA*/
+ 0x521, /* U+000520: CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK*/
+ 0x521, /* U+000521: CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK*/
+ 0x523, /* U+000522: CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK*/
+ 0x523, /* U+000523: CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK*/
+ 0x525, /* U+000524: CYRILLIC CAPITAL LETTER PE WITH DESCENDER*/
+ 0x525, /* U+000525: CYRILLIC SMALL LETTER PE WITH DESCENDER*/
+ 0x527, /* U+000526: CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER*/
+ 0x527, /* U+000527: CYRILLIC SMALL LETTER SHHA WITH DESCENDER*/
+ 0x528, /* U+000528: */
+ 0x529, /* U+000529: */
+ 0x52a, /* U+00052a: */
+ 0x52b, /* U+00052b: */
+ 0x52c, /* U+00052c: */
+ 0x52d, /* U+00052d: */
+ 0x52e, /* U+00052e: */
+ 0x52f, /* U+00052f: */
+ 0x530, /* U+000530: */
+ 0x561, /* U+000531: ARMENIAN CAPITAL LETTER AYB*/
+ 0x562, /* U+000532: ARMENIAN CAPITAL LETTER BEN*/
+ 0x563, /* U+000533: ARMENIAN CAPITAL LETTER GIM*/
+ 0x564, /* U+000534: ARMENIAN CAPITAL LETTER DA*/
+ 0x565, /* U+000535: ARMENIAN CAPITAL LETTER ECH*/
+ 0x566, /* U+000536: ARMENIAN CAPITAL LETTER ZA*/
+ 0x567, /* U+000537: ARMENIAN CAPITAL LETTER EH*/
+ 0x568, /* U+000538: ARMENIAN CAPITAL LETTER ET*/
+ 0x569, /* U+000539: ARMENIAN CAPITAL LETTER TO*/
+ 0x56a, /* U+00053a: ARMENIAN CAPITAL LETTER ZHE*/
+ 0x56b, /* U+00053b: ARMENIAN CAPITAL LETTER INI*/
+ 0x56c, /* U+00053c: ARMENIAN CAPITAL LETTER LIWN*/
+ 0x56d, /* U+00053d: ARMENIAN CAPITAL LETTER XEH*/
+ 0x56e, /* U+00053e: ARMENIAN CAPITAL LETTER CA*/
+ 0x56f, /* U+00053f: ARMENIAN CAPITAL LETTER KEN*/
+ 0x570, /* U+000540: ARMENIAN CAPITAL LETTER HO*/
+ 0x571, /* U+000541: ARMENIAN CAPITAL LETTER JA*/
+ 0x572, /* U+000542: ARMENIAN CAPITAL LETTER GHAD*/
+ 0x573, /* U+000543: ARMENIAN CAPITAL LETTER CHEH*/
+ 0x574, /* U+000544: ARMENIAN CAPITAL LETTER MEN*/
+ 0x575, /* U+000545: ARMENIAN CAPITAL LETTER YI*/
+ 0x576, /* U+000546: ARMENIAN CAPITAL LETTER NOW*/
+ 0x577, /* U+000547: ARMENIAN CAPITAL LETTER SHA*/
+ 0x578, /* U+000548: ARMENIAN CAPITAL LETTER VO*/
+ 0x579, /* U+000549: ARMENIAN CAPITAL LETTER CHA*/
+ 0x57a, /* U+00054a: ARMENIAN CAPITAL LETTER PEH*/
+ 0x57b, /* U+00054b: ARMENIAN CAPITAL LETTER JHEH*/
+ 0x57c, /* U+00054c: ARMENIAN CAPITAL LETTER RA*/
+ 0x57d, /* U+00054d: ARMENIAN CAPITAL LETTER SEH*/
+ 0x57e, /* U+00054e: ARMENIAN CAPITAL LETTER VEW*/
+ 0x57f, /* U+00054f: ARMENIAN CAPITAL LETTER TIWN*/
+ 0x580, /* U+000550: ARMENIAN CAPITAL LETTER REH*/
+ 0x581, /* U+000551: ARMENIAN CAPITAL LETTER CO*/
+ 0x582, /* U+000552: ARMENIAN CAPITAL LETTER YIWN*/
+ 0x583, /* U+000553: ARMENIAN CAPITAL LETTER PIWR*/
+ 0x584, /* U+000554: ARMENIAN CAPITAL LETTER KEH*/
+ 0x585, /* U+000555: ARMENIAN CAPITAL LETTER OH*/
+ 0x586, /* U+000556: ARMENIAN CAPITAL LETTER FEH*/
+};
+
+static const RTUNICP g_afRTUniLower0x0010a0[] =
+{
+ 0x2d00, /* U+0010a0: GEORGIAN CAPITAL LETTER AN*/
+ 0x2d01, /* U+0010a1: GEORGIAN CAPITAL LETTER BAN*/
+ 0x2d02, /* U+0010a2: GEORGIAN CAPITAL LETTER GAN*/
+ 0x2d03, /* U+0010a3: GEORGIAN CAPITAL LETTER DON*/
+ 0x2d04, /* U+0010a4: GEORGIAN CAPITAL LETTER EN*/
+ 0x2d05, /* U+0010a5: GEORGIAN CAPITAL LETTER VIN*/
+ 0x2d06, /* U+0010a6: GEORGIAN CAPITAL LETTER ZEN*/
+ 0x2d07, /* U+0010a7: GEORGIAN CAPITAL LETTER TAN*/
+ 0x2d08, /* U+0010a8: GEORGIAN CAPITAL LETTER IN*/
+ 0x2d09, /* U+0010a9: GEORGIAN CAPITAL LETTER KAN*/
+ 0x2d0a, /* U+0010aa: GEORGIAN CAPITAL LETTER LAS*/
+ 0x2d0b, /* U+0010ab: GEORGIAN CAPITAL LETTER MAN*/
+ 0x2d0c, /* U+0010ac: GEORGIAN CAPITAL LETTER NAR*/
+ 0x2d0d, /* U+0010ad: GEORGIAN CAPITAL LETTER ON*/
+ 0x2d0e, /* U+0010ae: GEORGIAN CAPITAL LETTER PAR*/
+ 0x2d0f, /* U+0010af: GEORGIAN CAPITAL LETTER ZHAR*/
+ 0x2d10, /* U+0010b0: GEORGIAN CAPITAL LETTER RAE*/
+ 0x2d11, /* U+0010b1: GEORGIAN CAPITAL LETTER SAN*/
+ 0x2d12, /* U+0010b2: GEORGIAN CAPITAL LETTER TAR*/
+ 0x2d13, /* U+0010b3: GEORGIAN CAPITAL LETTER UN*/
+ 0x2d14, /* U+0010b4: GEORGIAN CAPITAL LETTER PHAR*/
+ 0x2d15, /* U+0010b5: GEORGIAN CAPITAL LETTER KHAR*/
+ 0x2d16, /* U+0010b6: GEORGIAN CAPITAL LETTER GHAN*/
+ 0x2d17, /* U+0010b7: GEORGIAN CAPITAL LETTER QAR*/
+ 0x2d18, /* U+0010b8: GEORGIAN CAPITAL LETTER SHIN*/
+ 0x2d19, /* U+0010b9: GEORGIAN CAPITAL LETTER CHIN*/
+ 0x2d1a, /* U+0010ba: GEORGIAN CAPITAL LETTER CAN*/
+ 0x2d1b, /* U+0010bb: GEORGIAN CAPITAL LETTER JIL*/
+ 0x2d1c, /* U+0010bc: GEORGIAN CAPITAL LETTER CIL*/
+ 0x2d1d, /* U+0010bd: GEORGIAN CAPITAL LETTER CHAR*/
+ 0x2d1e, /* U+0010be: GEORGIAN CAPITAL LETTER XAN*/
+ 0x2d1f, /* U+0010bf: GEORGIAN CAPITAL LETTER JHAN*/
+ 0x2d20, /* U+0010c0: GEORGIAN CAPITAL LETTER HAE*/
+ 0x2d21, /* U+0010c1: GEORGIAN CAPITAL LETTER HE*/
+ 0x2d22, /* U+0010c2: GEORGIAN CAPITAL LETTER HIE*/
+ 0x2d23, /* U+0010c3: GEORGIAN CAPITAL LETTER WE*/
+ 0x2d24, /* U+0010c4: GEORGIAN CAPITAL LETTER HAR*/
+ 0x2d25, /* U+0010c5: GEORGIAN CAPITAL LETTER HOE*/
+ 0x10c6, /* U+0010c6: */
+ 0x2d27, /* U+0010c7: GEORGIAN CAPITAL LETTER YN*/
+ 0x10c8, /* U+0010c8: */
+ 0x10c9, /* U+0010c9: */
+ 0x10ca, /* U+0010ca: */
+ 0x10cb, /* U+0010cb: */
+ 0x10cc, /* U+0010cc: */
+ 0x2d2d, /* U+0010cd: GEORGIAN CAPITAL LETTER AEN*/
+};
+
+static const RTUNICP g_afRTUniLower0x001e00[] =
+{
+ 0x1e01, /* U+001e00: LATIN CAPITAL LETTER A WITH RING BELOW*/
+ 0x1e01, /* U+001e01: LATIN SMALL LETTER A WITH RING BELOW*/
+ 0x1e03, /* U+001e02: LATIN CAPITAL LETTER B WITH DOT ABOVE*/
+ 0x1e03, /* U+001e03: LATIN SMALL LETTER B WITH DOT ABOVE*/
+ 0x1e05, /* U+001e04: LATIN CAPITAL LETTER B WITH DOT BELOW*/
+ 0x1e05, /* U+001e05: LATIN SMALL LETTER B WITH DOT BELOW*/
+ 0x1e07, /* U+001e06: LATIN CAPITAL LETTER B WITH LINE BELOW*/
+ 0x1e07, /* U+001e07: LATIN SMALL LETTER B WITH LINE BELOW*/
+ 0x1e09, /* U+001e08: LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE*/
+ 0x1e09, /* U+001e09: LATIN SMALL LETTER C WITH CEDILLA AND ACUTE*/
+ 0x1e0b, /* U+001e0a: LATIN CAPITAL LETTER D WITH DOT ABOVE*/
+ 0x1e0b, /* U+001e0b: LATIN SMALL LETTER D WITH DOT ABOVE*/
+ 0x1e0d, /* U+001e0c: LATIN CAPITAL LETTER D WITH DOT BELOW*/
+ 0x1e0d, /* U+001e0d: LATIN SMALL LETTER D WITH DOT BELOW*/
+ 0x1e0f, /* U+001e0e: LATIN CAPITAL LETTER D WITH LINE BELOW*/
+ 0x1e0f, /* U+001e0f: LATIN SMALL LETTER D WITH LINE BELOW*/
+ 0x1e11, /* U+001e10: LATIN CAPITAL LETTER D WITH CEDILLA*/
+ 0x1e11, /* U+001e11: LATIN SMALL LETTER D WITH CEDILLA*/
+ 0x1e13, /* U+001e12: LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW*/
+ 0x1e13, /* U+001e13: LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW*/
+ 0x1e15, /* U+001e14: LATIN CAPITAL LETTER E WITH MACRON AND GRAVE*/
+ 0x1e15, /* U+001e15: LATIN SMALL LETTER E WITH MACRON AND GRAVE*/
+ 0x1e17, /* U+001e16: LATIN CAPITAL LETTER E WITH MACRON AND ACUTE*/
+ 0x1e17, /* U+001e17: LATIN SMALL LETTER E WITH MACRON AND ACUTE*/
+ 0x1e19, /* U+001e18: LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW*/
+ 0x1e19, /* U+001e19: LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW*/
+ 0x1e1b, /* U+001e1a: LATIN CAPITAL LETTER E WITH TILDE BELOW*/
+ 0x1e1b, /* U+001e1b: LATIN SMALL LETTER E WITH TILDE BELOW*/
+ 0x1e1d, /* U+001e1c: LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE*/
+ 0x1e1d, /* U+001e1d: LATIN SMALL LETTER E WITH CEDILLA AND BREVE*/
+ 0x1e1f, /* U+001e1e: LATIN CAPITAL LETTER F WITH DOT ABOVE*/
+ 0x1e1f, /* U+001e1f: LATIN SMALL LETTER F WITH DOT ABOVE*/
+ 0x1e21, /* U+001e20: LATIN CAPITAL LETTER G WITH MACRON*/
+ 0x1e21, /* U+001e21: LATIN SMALL LETTER G WITH MACRON*/
+ 0x1e23, /* U+001e22: LATIN CAPITAL LETTER H WITH DOT ABOVE*/
+ 0x1e23, /* U+001e23: LATIN SMALL LETTER H WITH DOT ABOVE*/
+ 0x1e25, /* U+001e24: LATIN CAPITAL LETTER H WITH DOT BELOW*/
+ 0x1e25, /* U+001e25: LATIN SMALL LETTER H WITH DOT BELOW*/
+ 0x1e27, /* U+001e26: LATIN CAPITAL LETTER H WITH DIAERESIS*/
+ 0x1e27, /* U+001e27: LATIN SMALL LETTER H WITH DIAERESIS*/
+ 0x1e29, /* U+001e28: LATIN CAPITAL LETTER H WITH CEDILLA*/
+ 0x1e29, /* U+001e29: LATIN SMALL LETTER H WITH CEDILLA*/
+ 0x1e2b, /* U+001e2a: LATIN CAPITAL LETTER H WITH BREVE BELOW*/
+ 0x1e2b, /* U+001e2b: LATIN SMALL LETTER H WITH BREVE BELOW*/
+ 0x1e2d, /* U+001e2c: LATIN CAPITAL LETTER I WITH TILDE BELOW*/
+ 0x1e2d, /* U+001e2d: LATIN SMALL LETTER I WITH TILDE BELOW*/
+ 0x1e2f, /* U+001e2e: LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE*/
+ 0x1e2f, /* U+001e2f: LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE*/
+ 0x1e31, /* U+001e30: LATIN CAPITAL LETTER K WITH ACUTE*/
+ 0x1e31, /* U+001e31: LATIN SMALL LETTER K WITH ACUTE*/
+ 0x1e33, /* U+001e32: LATIN CAPITAL LETTER K WITH DOT BELOW*/
+ 0x1e33, /* U+001e33: LATIN SMALL LETTER K WITH DOT BELOW*/
+ 0x1e35, /* U+001e34: LATIN CAPITAL LETTER K WITH LINE BELOW*/
+ 0x1e35, /* U+001e35: LATIN SMALL LETTER K WITH LINE BELOW*/
+ 0x1e37, /* U+001e36: LATIN CAPITAL LETTER L WITH DOT BELOW*/
+ 0x1e37, /* U+001e37: LATIN SMALL LETTER L WITH DOT BELOW*/
+ 0x1e39, /* U+001e38: LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON*/
+ 0x1e39, /* U+001e39: LATIN SMALL LETTER L WITH DOT BELOW AND MACRON*/
+ 0x1e3b, /* U+001e3a: LATIN CAPITAL LETTER L WITH LINE BELOW*/
+ 0x1e3b, /* U+001e3b: LATIN SMALL LETTER L WITH LINE BELOW*/
+ 0x1e3d, /* U+001e3c: LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW*/
+ 0x1e3d, /* U+001e3d: LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW*/
+ 0x1e3f, /* U+001e3e: LATIN CAPITAL LETTER M WITH ACUTE*/
+ 0x1e3f, /* U+001e3f: LATIN SMALL LETTER M WITH ACUTE*/
+ 0x1e41, /* U+001e40: LATIN CAPITAL LETTER M WITH DOT ABOVE*/
+ 0x1e41, /* U+001e41: LATIN SMALL LETTER M WITH DOT ABOVE*/
+ 0x1e43, /* U+001e42: LATIN CAPITAL LETTER M WITH DOT BELOW*/
+ 0x1e43, /* U+001e43: LATIN SMALL LETTER M WITH DOT BELOW*/
+ 0x1e45, /* U+001e44: LATIN CAPITAL LETTER N WITH DOT ABOVE*/
+ 0x1e45, /* U+001e45: LATIN SMALL LETTER N WITH DOT ABOVE*/
+ 0x1e47, /* U+001e46: LATIN CAPITAL LETTER N WITH DOT BELOW*/
+ 0x1e47, /* U+001e47: LATIN SMALL LETTER N WITH DOT BELOW*/
+ 0x1e49, /* U+001e48: LATIN CAPITAL LETTER N WITH LINE BELOW*/
+ 0x1e49, /* U+001e49: LATIN SMALL LETTER N WITH LINE BELOW*/
+ 0x1e4b, /* U+001e4a: LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW*/
+ 0x1e4b, /* U+001e4b: LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW*/
+ 0x1e4d, /* U+001e4c: LATIN CAPITAL LETTER O WITH TILDE AND ACUTE*/
+ 0x1e4d, /* U+001e4d: LATIN SMALL LETTER O WITH TILDE AND ACUTE*/
+ 0x1e4f, /* U+001e4e: LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS*/
+ 0x1e4f, /* U+001e4f: LATIN SMALL LETTER O WITH TILDE AND DIAERESIS*/
+ 0x1e51, /* U+001e50: LATIN CAPITAL LETTER O WITH MACRON AND GRAVE*/
+ 0x1e51, /* U+001e51: LATIN SMALL LETTER O WITH MACRON AND GRAVE*/
+ 0x1e53, /* U+001e52: LATIN CAPITAL LETTER O WITH MACRON AND ACUTE*/
+ 0x1e53, /* U+001e53: LATIN SMALL LETTER O WITH MACRON AND ACUTE*/
+ 0x1e55, /* U+001e54: LATIN CAPITAL LETTER P WITH ACUTE*/
+ 0x1e55, /* U+001e55: LATIN SMALL LETTER P WITH ACUTE*/
+ 0x1e57, /* U+001e56: LATIN CAPITAL LETTER P WITH DOT ABOVE*/
+ 0x1e57, /* U+001e57: LATIN SMALL LETTER P WITH DOT ABOVE*/
+ 0x1e59, /* U+001e58: LATIN CAPITAL LETTER R WITH DOT ABOVE*/
+ 0x1e59, /* U+001e59: LATIN SMALL LETTER R WITH DOT ABOVE*/
+ 0x1e5b, /* U+001e5a: LATIN CAPITAL LETTER R WITH DOT BELOW*/
+ 0x1e5b, /* U+001e5b: LATIN SMALL LETTER R WITH DOT BELOW*/
+ 0x1e5d, /* U+001e5c: LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON*/
+ 0x1e5d, /* U+001e5d: LATIN SMALL LETTER R WITH DOT BELOW AND MACRON*/
+ 0x1e5f, /* U+001e5e: LATIN CAPITAL LETTER R WITH LINE BELOW*/
+ 0x1e5f, /* U+001e5f: LATIN SMALL LETTER R WITH LINE BELOW*/
+ 0x1e61, /* U+001e60: LATIN CAPITAL LETTER S WITH DOT ABOVE*/
+ 0x1e61, /* U+001e61: LATIN SMALL LETTER S WITH DOT ABOVE*/
+ 0x1e63, /* U+001e62: LATIN CAPITAL LETTER S WITH DOT BELOW*/
+ 0x1e63, /* U+001e63: LATIN SMALL LETTER S WITH DOT BELOW*/
+ 0x1e65, /* U+001e64: LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE*/
+ 0x1e65, /* U+001e65: LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE*/
+ 0x1e67, /* U+001e66: LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE*/
+ 0x1e67, /* U+001e67: LATIN SMALL LETTER S WITH CARON AND DOT ABOVE*/
+ 0x1e69, /* U+001e68: LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE*/
+ 0x1e69, /* U+001e69: LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE*/
+ 0x1e6b, /* U+001e6a: LATIN CAPITAL LETTER T WITH DOT ABOVE*/
+ 0x1e6b, /* U+001e6b: LATIN SMALL LETTER T WITH DOT ABOVE*/
+ 0x1e6d, /* U+001e6c: LATIN CAPITAL LETTER T WITH DOT BELOW*/
+ 0x1e6d, /* U+001e6d: LATIN SMALL LETTER T WITH DOT BELOW*/
+ 0x1e6f, /* U+001e6e: LATIN CAPITAL LETTER T WITH LINE BELOW*/
+ 0x1e6f, /* U+001e6f: LATIN SMALL LETTER T WITH LINE BELOW*/
+ 0x1e71, /* U+001e70: LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW*/
+ 0x1e71, /* U+001e71: LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW*/
+ 0x1e73, /* U+001e72: LATIN CAPITAL LETTER U WITH DIAERESIS BELOW*/
+ 0x1e73, /* U+001e73: LATIN SMALL LETTER U WITH DIAERESIS BELOW*/
+ 0x1e75, /* U+001e74: LATIN CAPITAL LETTER U WITH TILDE BELOW*/
+ 0x1e75, /* U+001e75: LATIN SMALL LETTER U WITH TILDE BELOW*/
+ 0x1e77, /* U+001e76: LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW*/
+ 0x1e77, /* U+001e77: LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW*/
+ 0x1e79, /* U+001e78: LATIN CAPITAL LETTER U WITH TILDE AND ACUTE*/
+ 0x1e79, /* U+001e79: LATIN SMALL LETTER U WITH TILDE AND ACUTE*/
+ 0x1e7b, /* U+001e7a: LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS*/
+ 0x1e7b, /* U+001e7b: LATIN SMALL LETTER U WITH MACRON AND DIAERESIS*/
+ 0x1e7d, /* U+001e7c: LATIN CAPITAL LETTER V WITH TILDE*/
+ 0x1e7d, /* U+001e7d: LATIN SMALL LETTER V WITH TILDE*/
+ 0x1e7f, /* U+001e7e: LATIN CAPITAL LETTER V WITH DOT BELOW*/
+ 0x1e7f, /* U+001e7f: LATIN SMALL LETTER V WITH DOT BELOW*/
+ 0x1e81, /* U+001e80: LATIN CAPITAL LETTER W WITH GRAVE*/
+ 0x1e81, /* U+001e81: LATIN SMALL LETTER W WITH GRAVE*/
+ 0x1e83, /* U+001e82: LATIN CAPITAL LETTER W WITH ACUTE*/
+ 0x1e83, /* U+001e83: LATIN SMALL LETTER W WITH ACUTE*/
+ 0x1e85, /* U+001e84: LATIN CAPITAL LETTER W WITH DIAERESIS*/
+ 0x1e85, /* U+001e85: LATIN SMALL LETTER W WITH DIAERESIS*/
+ 0x1e87, /* U+001e86: LATIN CAPITAL LETTER W WITH DOT ABOVE*/
+ 0x1e87, /* U+001e87: LATIN SMALL LETTER W WITH DOT ABOVE*/
+ 0x1e89, /* U+001e88: LATIN CAPITAL LETTER W WITH DOT BELOW*/
+ 0x1e89, /* U+001e89: LATIN SMALL LETTER W WITH DOT BELOW*/
+ 0x1e8b, /* U+001e8a: LATIN CAPITAL LETTER X WITH DOT ABOVE*/
+ 0x1e8b, /* U+001e8b: LATIN SMALL LETTER X WITH DOT ABOVE*/
+ 0x1e8d, /* U+001e8c: LATIN CAPITAL LETTER X WITH DIAERESIS*/
+ 0x1e8d, /* U+001e8d: LATIN SMALL LETTER X WITH DIAERESIS*/
+ 0x1e8f, /* U+001e8e: LATIN CAPITAL LETTER Y WITH DOT ABOVE*/
+ 0x1e8f, /* U+001e8f: LATIN SMALL LETTER Y WITH DOT ABOVE*/
+ 0x1e91, /* U+001e90: LATIN CAPITAL LETTER Z WITH CIRCUMFLEX*/
+ 0x1e91, /* U+001e91: LATIN SMALL LETTER Z WITH CIRCUMFLEX*/
+ 0x1e93, /* U+001e92: LATIN CAPITAL LETTER Z WITH DOT BELOW*/
+ 0x1e93, /* U+001e93: LATIN SMALL LETTER Z WITH DOT BELOW*/
+ 0x1e95, /* U+001e94: LATIN CAPITAL LETTER Z WITH LINE BELOW*/
+ 0x1e95, /* U+001e95: LATIN SMALL LETTER Z WITH LINE BELOW*/
+ 0x1e96, /* U+001e96: LATIN SMALL LETTER H WITH LINE BELOW*/
+ 0x1e97, /* U+001e97: LATIN SMALL LETTER T WITH DIAERESIS*/
+ 0x1e98, /* U+001e98: LATIN SMALL LETTER W WITH RING ABOVE*/
+ 0x1e99, /* U+001e99: LATIN SMALL LETTER Y WITH RING ABOVE*/
+ 0x1e9a, /* U+001e9a: LATIN SMALL LETTER A WITH RIGHT HALF RING*/
+ 0x1e9b, /* U+001e9b: LATIN SMALL LETTER LONG S WITH DOT ABOVE*/
+ 0x1e9c, /* U+001e9c: LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE*/
+ 0x1e9d, /* U+001e9d: LATIN SMALL LETTER LONG S WITH HIGH STROKE*/
+ 0xdf, /* U+001e9e: LATIN CAPITAL LETTER SHARP S*/
+ 0x1e9f, /* U+001e9f: LATIN SMALL LETTER DELTA*/
+ 0x1ea1, /* U+001ea0: LATIN CAPITAL LETTER A WITH DOT BELOW*/
+ 0x1ea1, /* U+001ea1: LATIN SMALL LETTER A WITH DOT BELOW*/
+ 0x1ea3, /* U+001ea2: LATIN CAPITAL LETTER A WITH HOOK ABOVE*/
+ 0x1ea3, /* U+001ea3: LATIN SMALL LETTER A WITH HOOK ABOVE*/
+ 0x1ea5, /* U+001ea4: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ea5, /* U+001ea5: LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ea7, /* U+001ea6: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ea7, /* U+001ea7: LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ea9, /* U+001ea8: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ea9, /* U+001ea9: LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1eab, /* U+001eaa: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE*/
+ 0x1eab, /* U+001eab: LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE*/
+ 0x1ead, /* U+001eac: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1ead, /* U+001ead: LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1eaf, /* U+001eae: LATIN CAPITAL LETTER A WITH BREVE AND ACUTE*/
+ 0x1eaf, /* U+001eaf: LATIN SMALL LETTER A WITH BREVE AND ACUTE*/
+ 0x1eb1, /* U+001eb0: LATIN CAPITAL LETTER A WITH BREVE AND GRAVE*/
+ 0x1eb1, /* U+001eb1: LATIN SMALL LETTER A WITH BREVE AND GRAVE*/
+ 0x1eb3, /* U+001eb2: LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE*/
+ 0x1eb3, /* U+001eb3: LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE*/
+ 0x1eb5, /* U+001eb4: LATIN CAPITAL LETTER A WITH BREVE AND TILDE*/
+ 0x1eb5, /* U+001eb5: LATIN SMALL LETTER A WITH BREVE AND TILDE*/
+ 0x1eb7, /* U+001eb6: LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW*/
+ 0x1eb7, /* U+001eb7: LATIN SMALL LETTER A WITH BREVE AND DOT BELOW*/
+ 0x1eb9, /* U+001eb8: LATIN CAPITAL LETTER E WITH DOT BELOW*/
+ 0x1eb9, /* U+001eb9: LATIN SMALL LETTER E WITH DOT BELOW*/
+ 0x1ebb, /* U+001eba: LATIN CAPITAL LETTER E WITH HOOK ABOVE*/
+ 0x1ebb, /* U+001ebb: LATIN SMALL LETTER E WITH HOOK ABOVE*/
+ 0x1ebd, /* U+001ebc: LATIN CAPITAL LETTER E WITH TILDE*/
+ 0x1ebd, /* U+001ebd: LATIN SMALL LETTER E WITH TILDE*/
+ 0x1ebf, /* U+001ebe: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ebf, /* U+001ebf: LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ec1, /* U+001ec0: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ec1, /* U+001ec1: LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ec3, /* U+001ec2: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ec3, /* U+001ec3: LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ec5, /* U+001ec4: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE*/
+ 0x1ec5, /* U+001ec5: LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE*/
+ 0x1ec7, /* U+001ec6: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1ec7, /* U+001ec7: LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1ec9, /* U+001ec8: LATIN CAPITAL LETTER I WITH HOOK ABOVE*/
+ 0x1ec9, /* U+001ec9: LATIN SMALL LETTER I WITH HOOK ABOVE*/
+ 0x1ecb, /* U+001eca: LATIN CAPITAL LETTER I WITH DOT BELOW*/
+ 0x1ecb, /* U+001ecb: LATIN SMALL LETTER I WITH DOT BELOW*/
+ 0x1ecd, /* U+001ecc: LATIN CAPITAL LETTER O WITH DOT BELOW*/
+ 0x1ecd, /* U+001ecd: LATIN SMALL LETTER O WITH DOT BELOW*/
+ 0x1ecf, /* U+001ece: LATIN CAPITAL LETTER O WITH HOOK ABOVE*/
+ 0x1ecf, /* U+001ecf: LATIN SMALL LETTER O WITH HOOK ABOVE*/
+ 0x1ed1, /* U+001ed0: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ed1, /* U+001ed1: LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ed3, /* U+001ed2: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ed3, /* U+001ed3: LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ed5, /* U+001ed4: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ed5, /* U+001ed5: LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ed7, /* U+001ed6: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE*/
+ 0x1ed7, /* U+001ed7: LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE*/
+ 0x1ed9, /* U+001ed8: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1ed9, /* U+001ed9: LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1edb, /* U+001eda: LATIN CAPITAL LETTER O WITH HORN AND ACUTE*/
+ 0x1edb, /* U+001edb: LATIN SMALL LETTER O WITH HORN AND ACUTE*/
+ 0x1edd, /* U+001edc: LATIN CAPITAL LETTER O WITH HORN AND GRAVE*/
+ 0x1edd, /* U+001edd: LATIN SMALL LETTER O WITH HORN AND GRAVE*/
+ 0x1edf, /* U+001ede: LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE*/
+ 0x1edf, /* U+001edf: LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE*/
+ 0x1ee1, /* U+001ee0: LATIN CAPITAL LETTER O WITH HORN AND TILDE*/
+ 0x1ee1, /* U+001ee1: LATIN SMALL LETTER O WITH HORN AND TILDE*/
+ 0x1ee3, /* U+001ee2: LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW*/
+ 0x1ee3, /* U+001ee3: LATIN SMALL LETTER O WITH HORN AND DOT BELOW*/
+ 0x1ee5, /* U+001ee4: LATIN CAPITAL LETTER U WITH DOT BELOW*/
+ 0x1ee5, /* U+001ee5: LATIN SMALL LETTER U WITH DOT BELOW*/
+ 0x1ee7, /* U+001ee6: LATIN CAPITAL LETTER U WITH HOOK ABOVE*/
+ 0x1ee7, /* U+001ee7: LATIN SMALL LETTER U WITH HOOK ABOVE*/
+ 0x1ee9, /* U+001ee8: LATIN CAPITAL LETTER U WITH HORN AND ACUTE*/
+ 0x1ee9, /* U+001ee9: LATIN SMALL LETTER U WITH HORN AND ACUTE*/
+ 0x1eeb, /* U+001eea: LATIN CAPITAL LETTER U WITH HORN AND GRAVE*/
+ 0x1eeb, /* U+001eeb: LATIN SMALL LETTER U WITH HORN AND GRAVE*/
+ 0x1eed, /* U+001eec: LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE*/
+ 0x1eed, /* U+001eed: LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE*/
+ 0x1eef, /* U+001eee: LATIN CAPITAL LETTER U WITH HORN AND TILDE*/
+ 0x1eef, /* U+001eef: LATIN SMALL LETTER U WITH HORN AND TILDE*/
+ 0x1ef1, /* U+001ef0: LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW*/
+ 0x1ef1, /* U+001ef1: LATIN SMALL LETTER U WITH HORN AND DOT BELOW*/
+ 0x1ef3, /* U+001ef2: LATIN CAPITAL LETTER Y WITH GRAVE*/
+ 0x1ef3, /* U+001ef3: LATIN SMALL LETTER Y WITH GRAVE*/
+ 0x1ef5, /* U+001ef4: LATIN CAPITAL LETTER Y WITH DOT BELOW*/
+ 0x1ef5, /* U+001ef5: LATIN SMALL LETTER Y WITH DOT BELOW*/
+ 0x1ef7, /* U+001ef6: LATIN CAPITAL LETTER Y WITH HOOK ABOVE*/
+ 0x1ef7, /* U+001ef7: LATIN SMALL LETTER Y WITH HOOK ABOVE*/
+ 0x1ef9, /* U+001ef8: LATIN CAPITAL LETTER Y WITH TILDE*/
+ 0x1ef9, /* U+001ef9: LATIN SMALL LETTER Y WITH TILDE*/
+ 0x1efb, /* U+001efa: LATIN CAPITAL LETTER MIDDLE-WELSH LL*/
+ 0x1efb, /* U+001efb: LATIN SMALL LETTER MIDDLE-WELSH LL*/
+ 0x1efd, /* U+001efc: LATIN CAPITAL LETTER MIDDLE-WELSH V*/
+ 0x1efd, /* U+001efd: LATIN SMALL LETTER MIDDLE-WELSH V*/
+ 0x1eff, /* U+001efe: LATIN CAPITAL LETTER Y WITH LOOP*/
+ 0x1eff, /* U+001eff: LATIN SMALL LETTER Y WITH LOOP*/
+ 0x1f00, /* U+001f00: GREEK SMALL LETTER ALPHA WITH PSILI*/
+ 0x1f01, /* U+001f01: GREEK SMALL LETTER ALPHA WITH DASIA*/
+ 0x1f02, /* U+001f02: GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA*/
+ 0x1f03, /* U+001f03: GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA*/
+ 0x1f04, /* U+001f04: GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA*/
+ 0x1f05, /* U+001f05: GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA*/
+ 0x1f06, /* U+001f06: GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI*/
+ 0x1f07, /* U+001f07: GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI*/
+ 0x1f00, /* U+001f08: GREEK CAPITAL LETTER ALPHA WITH PSILI*/
+ 0x1f01, /* U+001f09: GREEK CAPITAL LETTER ALPHA WITH DASIA*/
+ 0x1f02, /* U+001f0a: GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA*/
+ 0x1f03, /* U+001f0b: GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA*/
+ 0x1f04, /* U+001f0c: GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA*/
+ 0x1f05, /* U+001f0d: GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA*/
+ 0x1f06, /* U+001f0e: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI*/
+ 0x1f07, /* U+001f0f: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI*/
+ 0x1f10, /* U+001f10: GREEK SMALL LETTER EPSILON WITH PSILI*/
+ 0x1f11, /* U+001f11: GREEK SMALL LETTER EPSILON WITH DASIA*/
+ 0x1f12, /* U+001f12: GREEK SMALL LETTER EPSILON WITH PSILI AND VARIA*/
+ 0x1f13, /* U+001f13: GREEK SMALL LETTER EPSILON WITH DASIA AND VARIA*/
+ 0x1f14, /* U+001f14: GREEK SMALL LETTER EPSILON WITH PSILI AND OXIA*/
+ 0x1f15, /* U+001f15: GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA*/
+ 0x1f16, /* U+001f16: */
+ 0x1f17, /* U+001f17: */
+ 0x1f10, /* U+001f18: GREEK CAPITAL LETTER EPSILON WITH PSILI*/
+ 0x1f11, /* U+001f19: GREEK CAPITAL LETTER EPSILON WITH DASIA*/
+ 0x1f12, /* U+001f1a: GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA*/
+ 0x1f13, /* U+001f1b: GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA*/
+ 0x1f14, /* U+001f1c: GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA*/
+ 0x1f15, /* U+001f1d: GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA*/
+ 0x1f1e, /* U+001f1e: */
+ 0x1f1f, /* U+001f1f: */
+ 0x1f20, /* U+001f20: GREEK SMALL LETTER ETA WITH PSILI*/
+ 0x1f21, /* U+001f21: GREEK SMALL LETTER ETA WITH DASIA*/
+ 0x1f22, /* U+001f22: GREEK SMALL LETTER ETA WITH PSILI AND VARIA*/
+ 0x1f23, /* U+001f23: GREEK SMALL LETTER ETA WITH DASIA AND VARIA*/
+ 0x1f24, /* U+001f24: GREEK SMALL LETTER ETA WITH PSILI AND OXIA*/
+ 0x1f25, /* U+001f25: GREEK SMALL LETTER ETA WITH DASIA AND OXIA*/
+ 0x1f26, /* U+001f26: GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI*/
+ 0x1f27, /* U+001f27: GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI*/
+ 0x1f20, /* U+001f28: GREEK CAPITAL LETTER ETA WITH PSILI*/
+ 0x1f21, /* U+001f29: GREEK CAPITAL LETTER ETA WITH DASIA*/
+ 0x1f22, /* U+001f2a: GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA*/
+ 0x1f23, /* U+001f2b: GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA*/
+ 0x1f24, /* U+001f2c: GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA*/
+ 0x1f25, /* U+001f2d: GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA*/
+ 0x1f26, /* U+001f2e: GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI*/
+ 0x1f27, /* U+001f2f: GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI*/
+ 0x1f30, /* U+001f30: GREEK SMALL LETTER IOTA WITH PSILI*/
+ 0x1f31, /* U+001f31: GREEK SMALL LETTER IOTA WITH DASIA*/
+ 0x1f32, /* U+001f32: GREEK SMALL LETTER IOTA WITH PSILI AND VARIA*/
+ 0x1f33, /* U+001f33: GREEK SMALL LETTER IOTA WITH DASIA AND VARIA*/
+ 0x1f34, /* U+001f34: GREEK SMALL LETTER IOTA WITH PSILI AND OXIA*/
+ 0x1f35, /* U+001f35: GREEK SMALL LETTER IOTA WITH DASIA AND OXIA*/
+ 0x1f36, /* U+001f36: GREEK SMALL LETTER IOTA WITH PSILI AND PERISPOMENI*/
+ 0x1f37, /* U+001f37: GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI*/
+ 0x1f30, /* U+001f38: GREEK CAPITAL LETTER IOTA WITH PSILI*/
+ 0x1f31, /* U+001f39: GREEK CAPITAL LETTER IOTA WITH DASIA*/
+ 0x1f32, /* U+001f3a: GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA*/
+ 0x1f33, /* U+001f3b: GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA*/
+ 0x1f34, /* U+001f3c: GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA*/
+ 0x1f35, /* U+001f3d: GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA*/
+ 0x1f36, /* U+001f3e: GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI*/
+ 0x1f37, /* U+001f3f: GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI*/
+ 0x1f40, /* U+001f40: GREEK SMALL LETTER OMICRON WITH PSILI*/
+ 0x1f41, /* U+001f41: GREEK SMALL LETTER OMICRON WITH DASIA*/
+ 0x1f42, /* U+001f42: GREEK SMALL LETTER OMICRON WITH PSILI AND VARIA*/
+ 0x1f43, /* U+001f43: GREEK SMALL LETTER OMICRON WITH DASIA AND VARIA*/
+ 0x1f44, /* U+001f44: GREEK SMALL LETTER OMICRON WITH PSILI AND OXIA*/
+ 0x1f45, /* U+001f45: GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA*/
+ 0x1f46, /* U+001f46: */
+ 0x1f47, /* U+001f47: */
+ 0x1f40, /* U+001f48: GREEK CAPITAL LETTER OMICRON WITH PSILI*/
+ 0x1f41, /* U+001f49: GREEK CAPITAL LETTER OMICRON WITH DASIA*/
+ 0x1f42, /* U+001f4a: GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA*/
+ 0x1f43, /* U+001f4b: GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA*/
+ 0x1f44, /* U+001f4c: GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA*/
+ 0x1f45, /* U+001f4d: GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA*/
+ 0x1f4e, /* U+001f4e: */
+ 0x1f4f, /* U+001f4f: */
+ 0x1f50, /* U+001f50: GREEK SMALL LETTER UPSILON WITH PSILI*/
+ 0x1f51, /* U+001f51: GREEK SMALL LETTER UPSILON WITH DASIA*/
+ 0x1f52, /* U+001f52: GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA*/
+ 0x1f53, /* U+001f53: GREEK SMALL LETTER UPSILON WITH DASIA AND VARIA*/
+ 0x1f54, /* U+001f54: GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA*/
+ 0x1f55, /* U+001f55: GREEK SMALL LETTER UPSILON WITH DASIA AND OXIA*/
+ 0x1f56, /* U+001f56: GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI*/
+ 0x1f57, /* U+001f57: GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI*/
+ 0x1f58, /* U+001f58: */
+ 0x1f51, /* U+001f59: GREEK CAPITAL LETTER UPSILON WITH DASIA*/
+ 0x1f5a, /* U+001f5a: */
+ 0x1f53, /* U+001f5b: GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA*/
+ 0x1f5c, /* U+001f5c: */
+ 0x1f55, /* U+001f5d: GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA*/
+ 0x1f5e, /* U+001f5e: */
+ 0x1f57, /* U+001f5f: GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI*/
+ 0x1f60, /* U+001f60: GREEK SMALL LETTER OMEGA WITH PSILI*/
+ 0x1f61, /* U+001f61: GREEK SMALL LETTER OMEGA WITH DASIA*/
+ 0x1f62, /* U+001f62: GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA*/
+ 0x1f63, /* U+001f63: GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA*/
+ 0x1f64, /* U+001f64: GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA*/
+ 0x1f65, /* U+001f65: GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA*/
+ 0x1f66, /* U+001f66: GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI*/
+ 0x1f67, /* U+001f67: GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI*/
+ 0x1f60, /* U+001f68: GREEK CAPITAL LETTER OMEGA WITH PSILI*/
+ 0x1f61, /* U+001f69: GREEK CAPITAL LETTER OMEGA WITH DASIA*/
+ 0x1f62, /* U+001f6a: GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA*/
+ 0x1f63, /* U+001f6b: GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA*/
+ 0x1f64, /* U+001f6c: GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA*/
+ 0x1f65, /* U+001f6d: GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA*/
+ 0x1f66, /* U+001f6e: GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI*/
+ 0x1f67, /* U+001f6f: GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI*/
+ 0x1f70, /* U+001f70: GREEK SMALL LETTER ALPHA WITH VARIA*/
+ 0x1f71, /* U+001f71: GREEK SMALL LETTER ALPHA WITH OXIA*/
+ 0x1f72, /* U+001f72: GREEK SMALL LETTER EPSILON WITH VARIA*/
+ 0x1f73, /* U+001f73: GREEK SMALL LETTER EPSILON WITH OXIA*/
+ 0x1f74, /* U+001f74: GREEK SMALL LETTER ETA WITH VARIA*/
+ 0x1f75, /* U+001f75: GREEK SMALL LETTER ETA WITH OXIA*/
+ 0x1f76, /* U+001f76: GREEK SMALL LETTER IOTA WITH VARIA*/
+ 0x1f77, /* U+001f77: GREEK SMALL LETTER IOTA WITH OXIA*/
+ 0x1f78, /* U+001f78: GREEK SMALL LETTER OMICRON WITH VARIA*/
+ 0x1f79, /* U+001f79: GREEK SMALL LETTER OMICRON WITH OXIA*/
+ 0x1f7a, /* U+001f7a: GREEK SMALL LETTER UPSILON WITH VARIA*/
+ 0x1f7b, /* U+001f7b: GREEK SMALL LETTER UPSILON WITH OXIA*/
+ 0x1f7c, /* U+001f7c: GREEK SMALL LETTER OMEGA WITH VARIA*/
+ 0x1f7d, /* U+001f7d: GREEK SMALL LETTER OMEGA WITH OXIA*/
+ 0x1f7e, /* U+001f7e: */
+ 0x1f7f, /* U+001f7f: */
+ 0x1f80, /* U+001f80: GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI*/
+ 0x1f81, /* U+001f81: GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI*/
+ 0x1f82, /* U+001f82: GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI*/
+ 0x1f83, /* U+001f83: GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI*/
+ 0x1f84, /* U+001f84: GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI*/
+ 0x1f85, /* U+001f85: GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI*/
+ 0x1f86, /* U+001f86: GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f87, /* U+001f87: GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f80, /* U+001f88: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI*/
+ 0x1f81, /* U+001f89: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI*/
+ 0x1f82, /* U+001f8a: GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI*/
+ 0x1f83, /* U+001f8b: GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI*/
+ 0x1f84, /* U+001f8c: GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI*/
+ 0x1f85, /* U+001f8d: GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI*/
+ 0x1f86, /* U+001f8e: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1f87, /* U+001f8f: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1f90, /* U+001f90: GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI*/
+ 0x1f91, /* U+001f91: GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI*/
+ 0x1f92, /* U+001f92: GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI*/
+ 0x1f93, /* U+001f93: GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI*/
+ 0x1f94, /* U+001f94: GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI*/
+ 0x1f95, /* U+001f95: GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI*/
+ 0x1f96, /* U+001f96: GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f97, /* U+001f97: GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f90, /* U+001f98: GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI*/
+ 0x1f91, /* U+001f99: GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI*/
+ 0x1f92, /* U+001f9a: GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI*/
+ 0x1f93, /* U+001f9b: GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI*/
+ 0x1f94, /* U+001f9c: GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI*/
+ 0x1f95, /* U+001f9d: GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI*/
+ 0x1f96, /* U+001f9e: GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1f97, /* U+001f9f: GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1fa0, /* U+001fa0: GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI*/
+ 0x1fa1, /* U+001fa1: GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI*/
+ 0x1fa2, /* U+001fa2: GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI*/
+ 0x1fa3, /* U+001fa3: GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI*/
+ 0x1fa4, /* U+001fa4: GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI*/
+ 0x1fa5, /* U+001fa5: GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI*/
+ 0x1fa6, /* U+001fa6: GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1fa7, /* U+001fa7: GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1fa0, /* U+001fa8: GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI*/
+ 0x1fa1, /* U+001fa9: GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI*/
+ 0x1fa2, /* U+001faa: GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI*/
+ 0x1fa3, /* U+001fab: GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI*/
+ 0x1fa4, /* U+001fac: GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI*/
+ 0x1fa5, /* U+001fad: GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI*/
+ 0x1fa6, /* U+001fae: GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1fa7, /* U+001faf: GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1fb0, /* U+001fb0: GREEK SMALL LETTER ALPHA WITH VRACHY*/
+ 0x1fb1, /* U+001fb1: GREEK SMALL LETTER ALPHA WITH MACRON*/
+ 0x1fb2, /* U+001fb2: GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI*/
+ 0x1fb3, /* U+001fb3: GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI*/
+ 0x1fb4, /* U+001fb4: GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI*/
+ 0x1fb5, /* U+001fb5: */
+ 0x1fb6, /* U+001fb6: GREEK SMALL LETTER ALPHA WITH PERISPOMENI*/
+ 0x1fb7, /* U+001fb7: GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1fb0, /* U+001fb8: GREEK CAPITAL LETTER ALPHA WITH VRACHY*/
+ 0x1fb1, /* U+001fb9: GREEK CAPITAL LETTER ALPHA WITH MACRON*/
+ 0x1f70, /* U+001fba: GREEK CAPITAL LETTER ALPHA WITH VARIA*/
+ 0x1f71, /* U+001fbb: GREEK CAPITAL LETTER ALPHA WITH OXIA*/
+ 0x1fb3, /* U+001fbc: GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI*/
+ 0x1fbd, /* U+001fbd: GREEK KORONIS*/
+ 0x1fbe, /* U+001fbe: GREEK PROSGEGRAMMENI*/
+ 0x1fbf, /* U+001fbf: GREEK PSILI*/
+ 0x1fc0, /* U+001fc0: GREEK PERISPOMENI*/
+ 0x1fc1, /* U+001fc1: GREEK DIALYTIKA AND PERISPOMENI*/
+ 0x1fc2, /* U+001fc2: GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI*/
+ 0x1fc3, /* U+001fc3: GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI*/
+ 0x1fc4, /* U+001fc4: GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI*/
+ 0x1fc5, /* U+001fc5: */
+ 0x1fc6, /* U+001fc6: GREEK SMALL LETTER ETA WITH PERISPOMENI*/
+ 0x1fc7, /* U+001fc7: GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f72, /* U+001fc8: GREEK CAPITAL LETTER EPSILON WITH VARIA*/
+ 0x1f73, /* U+001fc9: GREEK CAPITAL LETTER EPSILON WITH OXIA*/
+ 0x1f74, /* U+001fca: GREEK CAPITAL LETTER ETA WITH VARIA*/
+ 0x1f75, /* U+001fcb: GREEK CAPITAL LETTER ETA WITH OXIA*/
+ 0x1fc3, /* U+001fcc: GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI*/
+ 0x1fcd, /* U+001fcd: GREEK PSILI AND VARIA*/
+ 0x1fce, /* U+001fce: GREEK PSILI AND OXIA*/
+ 0x1fcf, /* U+001fcf: GREEK PSILI AND PERISPOMENI*/
+ 0x1fd0, /* U+001fd0: GREEK SMALL LETTER IOTA WITH VRACHY*/
+ 0x1fd1, /* U+001fd1: GREEK SMALL LETTER IOTA WITH MACRON*/
+ 0x1fd2, /* U+001fd2: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA*/
+ 0x1fd3, /* U+001fd3: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA*/
+ 0x1fd4, /* U+001fd4: */
+ 0x1fd5, /* U+001fd5: */
+ 0x1fd6, /* U+001fd6: GREEK SMALL LETTER IOTA WITH PERISPOMENI*/
+ 0x1fd7, /* U+001fd7: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI*/
+ 0x1fd0, /* U+001fd8: GREEK CAPITAL LETTER IOTA WITH VRACHY*/
+ 0x1fd1, /* U+001fd9: GREEK CAPITAL LETTER IOTA WITH MACRON*/
+ 0x1f76, /* U+001fda: GREEK CAPITAL LETTER IOTA WITH VARIA*/
+ 0x1f77, /* U+001fdb: GREEK CAPITAL LETTER IOTA WITH OXIA*/
+ 0x1fdc, /* U+001fdc: */
+ 0x1fdd, /* U+001fdd: GREEK DASIA AND VARIA*/
+ 0x1fde, /* U+001fde: GREEK DASIA AND OXIA*/
+ 0x1fdf, /* U+001fdf: GREEK DASIA AND PERISPOMENI*/
+ 0x1fe0, /* U+001fe0: GREEK SMALL LETTER UPSILON WITH VRACHY*/
+ 0x1fe1, /* U+001fe1: GREEK SMALL LETTER UPSILON WITH MACRON*/
+ 0x1fe2, /* U+001fe2: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA*/
+ 0x1fe3, /* U+001fe3: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA*/
+ 0x1fe4, /* U+001fe4: GREEK SMALL LETTER RHO WITH PSILI*/
+ 0x1fe5, /* U+001fe5: GREEK SMALL LETTER RHO WITH DASIA*/
+ 0x1fe6, /* U+001fe6: GREEK SMALL LETTER UPSILON WITH PERISPOMENI*/
+ 0x1fe7, /* U+001fe7: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI*/
+ 0x1fe0, /* U+001fe8: GREEK CAPITAL LETTER UPSILON WITH VRACHY*/
+ 0x1fe1, /* U+001fe9: GREEK CAPITAL LETTER UPSILON WITH MACRON*/
+ 0x1f7a, /* U+001fea: GREEK CAPITAL LETTER UPSILON WITH VARIA*/
+ 0x1f7b, /* U+001feb: GREEK CAPITAL LETTER UPSILON WITH OXIA*/
+ 0x1fe5, /* U+001fec: GREEK CAPITAL LETTER RHO WITH DASIA*/
+ 0x1fed, /* U+001fed: GREEK DIALYTIKA AND VARIA*/
+ 0x1fee, /* U+001fee: GREEK DIALYTIKA AND OXIA*/
+ 0x1fef, /* U+001fef: GREEK VARIA*/
+ 0x1ff0, /* U+001ff0: */
+ 0x1ff1, /* U+001ff1: */
+ 0x1ff2, /* U+001ff2: GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI*/
+ 0x1ff3, /* U+001ff3: GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI*/
+ 0x1ff4, /* U+001ff4: GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI*/
+ 0x1ff5, /* U+001ff5: */
+ 0x1ff6, /* U+001ff6: GREEK SMALL LETTER OMEGA WITH PERISPOMENI*/
+ 0x1ff7, /* U+001ff7: GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f78, /* U+001ff8: GREEK CAPITAL LETTER OMICRON WITH VARIA*/
+ 0x1f79, /* U+001ff9: GREEK CAPITAL LETTER OMICRON WITH OXIA*/
+ 0x1f7c, /* U+001ffa: GREEK CAPITAL LETTER OMEGA WITH VARIA*/
+ 0x1f7d, /* U+001ffb: GREEK CAPITAL LETTER OMEGA WITH OXIA*/
+ 0x1ff3, /* U+001ffc: GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI*/
+ 0x1ffd, /* U+001ffd: GREEK OXIA*/
+ 0x1ffe, /* U+001ffe: GREEK DASIA*/
+ 0x1fff, /* U+001fff: */
+ 0x2000, /* U+002000: EN QUAD*/
+ 0x2001, /* U+002001: EM QUAD*/
+ 0x2002, /* U+002002: EN SPACE*/
+ 0x2003, /* U+002003: EM SPACE*/
+ 0x2004, /* U+002004: THREE-PER-EM SPACE*/
+ 0x2005, /* U+002005: FOUR-PER-EM SPACE*/
+ 0x2006, /* U+002006: SIX-PER-EM SPACE*/
+ 0x2007, /* U+002007: FIGURE SPACE*/
+ 0x2008, /* U+002008: PUNCTUATION SPACE*/
+ 0x2009, /* U+002009: THIN SPACE*/
+ 0x200a, /* U+00200a: HAIR SPACE*/
+ 0x200b, /* U+00200b: ZERO WIDTH SPACE*/
+ 0x200c, /* U+00200c: ZERO WIDTH NON-JOINER*/
+ 0x200d, /* U+00200d: ZERO WIDTH JOINER*/
+ 0x200e, /* U+00200e: LEFT-TO-RIGHT MARK*/
+ 0x200f, /* U+00200f: RIGHT-TO-LEFT MARK*/
+ 0x2010, /* U+002010: HYPHEN*/
+ 0x2011, /* U+002011: NON-BREAKING HYPHEN*/
+ 0x2012, /* U+002012: FIGURE DASH*/
+ 0x2013, /* U+002013: EN DASH*/
+ 0x2014, /* U+002014: EM DASH*/
+ 0x2015, /* U+002015: HORIZONTAL BAR*/
+ 0x2016, /* U+002016: DOUBLE VERTICAL LINE*/
+ 0x2017, /* U+002017: DOUBLE LOW LINE*/
+ 0x2018, /* U+002018: LEFT SINGLE QUOTATION MARK*/
+ 0x2019, /* U+002019: RIGHT SINGLE QUOTATION MARK*/
+ 0x201a, /* U+00201a: SINGLE LOW-9 QUOTATION MARK*/
+ 0x201b, /* U+00201b: SINGLE HIGH-REVERSED-9 QUOTATION MARK*/
+ 0x201c, /* U+00201c: LEFT DOUBLE QUOTATION MARK*/
+ 0x201d, /* U+00201d: RIGHT DOUBLE QUOTATION MARK*/
+ 0x201e, /* U+00201e: DOUBLE LOW-9 QUOTATION MARK*/
+ 0x201f, /* U+00201f: DOUBLE HIGH-REVERSED-9 QUOTATION MARK*/
+ 0x2020, /* U+002020: DAGGER*/
+ 0x2021, /* U+002021: DOUBLE DAGGER*/
+ 0x2022, /* U+002022: BULLET*/
+ 0x2023, /* U+002023: TRIANGULAR BULLET*/
+ 0x2024, /* U+002024: ONE DOT LEADER*/
+ 0x2025, /* U+002025: TWO DOT LEADER*/
+ 0x2026, /* U+002026: HORIZONTAL ELLIPSIS*/
+ 0x2027, /* U+002027: HYPHENATION POINT*/
+ 0x2028, /* U+002028: LINE SEPARATOR*/
+ 0x2029, /* U+002029: PARAGRAPH SEPARATOR*/
+ 0x202a, /* U+00202a: LEFT-TO-RIGHT EMBEDDING*/
+ 0x202b, /* U+00202b: RIGHT-TO-LEFT EMBEDDING*/
+ 0x202c, /* U+00202c: POP DIRECTIONAL FORMATTING*/
+ 0x202d, /* U+00202d: LEFT-TO-RIGHT OVERRIDE*/
+ 0x202e, /* U+00202e: RIGHT-TO-LEFT OVERRIDE*/
+ 0x202f, /* U+00202f: NARROW NO-BREAK SPACE*/
+ 0x2030, /* U+002030: PER MILLE SIGN*/
+ 0x2031, /* U+002031: PER TEN THOUSAND SIGN*/
+ 0x2032, /* U+002032: PRIME*/
+ 0x2033, /* U+002033: DOUBLE PRIME*/
+ 0x2034, /* U+002034: TRIPLE PRIME*/
+ 0x2035, /* U+002035: REVERSED PRIME*/
+ 0x2036, /* U+002036: REVERSED DOUBLE PRIME*/
+ 0x2037, /* U+002037: REVERSED TRIPLE PRIME*/
+ 0x2038, /* U+002038: CARET*/
+ 0x2039, /* U+002039: SINGLE LEFT-POINTING ANGLE QUOTATION MARK*/
+ 0x203a, /* U+00203a: SINGLE RIGHT-POINTING ANGLE QUOTATION MARK*/
+ 0x203b, /* U+00203b: REFERENCE MARK*/
+ 0x203c, /* U+00203c: DOUBLE EXCLAMATION MARK*/
+ 0x203d, /* U+00203d: INTERROBANG*/
+ 0x203e, /* U+00203e: OVERLINE*/
+ 0x203f, /* U+00203f: UNDERTIE*/
+ 0x2040, /* U+002040: CHARACTER TIE*/
+ 0x2041, /* U+002041: CARET INSERTION POINT*/
+ 0x2042, /* U+002042: ASTERISM*/
+ 0x2043, /* U+002043: HYPHEN BULLET*/
+ 0x2044, /* U+002044: FRACTION SLASH*/
+ 0x2045, /* U+002045: LEFT SQUARE BRACKET WITH QUILL*/
+ 0x2046, /* U+002046: RIGHT SQUARE BRACKET WITH QUILL*/
+ 0x2047, /* U+002047: DOUBLE QUESTION MARK*/
+ 0x2048, /* U+002048: QUESTION EXCLAMATION MARK*/
+ 0x2049, /* U+002049: EXCLAMATION QUESTION MARK*/
+ 0x204a, /* U+00204a: TIRONIAN SIGN ET*/
+ 0x204b, /* U+00204b: REVERSED PILCROW SIGN*/
+ 0x204c, /* U+00204c: BLACK LEFTWARDS BULLET*/
+ 0x204d, /* U+00204d: BLACK RIGHTWARDS BULLET*/
+ 0x204e, /* U+00204e: LOW ASTERISK*/
+ 0x204f, /* U+00204f: REVERSED SEMICOLON*/
+ 0x2050, /* U+002050: CLOSE UP*/
+ 0x2051, /* U+002051: TWO ASTERISKS ALIGNED VERTICALLY*/
+ 0x2052, /* U+002052: COMMERCIAL MINUS SIGN*/
+ 0x2053, /* U+002053: SWUNG DASH*/
+ 0x2054, /* U+002054: INVERTED UNDERTIE*/
+ 0x2055, /* U+002055: FLOWER PUNCTUATION MARK*/
+ 0x2056, /* U+002056: THREE DOT PUNCTUATION*/
+ 0x2057, /* U+002057: QUADRUPLE PRIME*/
+ 0x2058, /* U+002058: FOUR DOT PUNCTUATION*/
+ 0x2059, /* U+002059: FIVE DOT PUNCTUATION*/
+ 0x205a, /* U+00205a: TWO DOT PUNCTUATION*/
+ 0x205b, /* U+00205b: FOUR DOT MARK*/
+ 0x205c, /* U+00205c: DOTTED CROSS*/
+ 0x205d, /* U+00205d: TRICOLON*/
+ 0x205e, /* U+00205e: VERTICAL FOUR DOTS*/
+ 0x205f, /* U+00205f: MEDIUM MATHEMATICAL SPACE*/
+ 0x2060, /* U+002060: WORD JOINER*/
+ 0x2061, /* U+002061: FUNCTION APPLICATION*/
+ 0x2062, /* U+002062: INVISIBLE TIMES*/
+ 0x2063, /* U+002063: INVISIBLE SEPARATOR*/
+ 0x2064, /* U+002064: INVISIBLE PLUS*/
+ 0x2065, /* U+002065: */
+ 0x2066, /* U+002066: LEFT-TO-RIGHT ISOLATE*/
+ 0x2067, /* U+002067: RIGHT-TO-LEFT ISOLATE*/
+ 0x2068, /* U+002068: FIRST STRONG ISOLATE*/
+ 0x2069, /* U+002069: POP DIRECTIONAL ISOLATE*/
+ 0x206a, /* U+00206a: INHIBIT SYMMETRIC SWAPPING*/
+ 0x206b, /* U+00206b: ACTIVATE SYMMETRIC SWAPPING*/
+ 0x206c, /* U+00206c: INHIBIT ARABIC FORM SHAPING*/
+ 0x206d, /* U+00206d: ACTIVATE ARABIC FORM SHAPING*/
+ 0x206e, /* U+00206e: NATIONAL DIGIT SHAPES*/
+ 0x206f, /* U+00206f: NOMINAL DIGIT SHAPES*/
+ 0x2070, /* U+002070: SUPERSCRIPT ZERO*/
+ 0x2071, /* U+002071: SUPERSCRIPT LATIN SMALL LETTER I*/
+ 0x2072, /* U+002072: */
+ 0x2073, /* U+002073: */
+ 0x2074, /* U+002074: SUPERSCRIPT FOUR*/
+ 0x2075, /* U+002075: SUPERSCRIPT FIVE*/
+ 0x2076, /* U+002076: SUPERSCRIPT SIX*/
+ 0x2077, /* U+002077: SUPERSCRIPT SEVEN*/
+ 0x2078, /* U+002078: SUPERSCRIPT EIGHT*/
+ 0x2079, /* U+002079: SUPERSCRIPT NINE*/
+ 0x207a, /* U+00207a: SUPERSCRIPT PLUS SIGN*/
+ 0x207b, /* U+00207b: SUPERSCRIPT MINUS*/
+ 0x207c, /* U+00207c: SUPERSCRIPT EQUALS SIGN*/
+ 0x207d, /* U+00207d: SUPERSCRIPT LEFT PARENTHESIS*/
+ 0x207e, /* U+00207e: SUPERSCRIPT RIGHT PARENTHESIS*/
+ 0x207f, /* U+00207f: SUPERSCRIPT LATIN SMALL LETTER N*/
+ 0x2080, /* U+002080: SUBSCRIPT ZERO*/
+ 0x2081, /* U+002081: SUBSCRIPT ONE*/
+ 0x2082, /* U+002082: SUBSCRIPT TWO*/
+ 0x2083, /* U+002083: SUBSCRIPT THREE*/
+ 0x2084, /* U+002084: SUBSCRIPT FOUR*/
+ 0x2085, /* U+002085: SUBSCRIPT FIVE*/
+ 0x2086, /* U+002086: SUBSCRIPT SIX*/
+ 0x2087, /* U+002087: SUBSCRIPT SEVEN*/
+ 0x2088, /* U+002088: SUBSCRIPT EIGHT*/
+ 0x2089, /* U+002089: SUBSCRIPT NINE*/
+ 0x208a, /* U+00208a: SUBSCRIPT PLUS SIGN*/
+ 0x208b, /* U+00208b: SUBSCRIPT MINUS*/
+ 0x208c, /* U+00208c: SUBSCRIPT EQUALS SIGN*/
+ 0x208d, /* U+00208d: SUBSCRIPT LEFT PARENTHESIS*/
+ 0x208e, /* U+00208e: SUBSCRIPT RIGHT PARENTHESIS*/
+ 0x208f, /* U+00208f: */
+ 0x2090, /* U+002090: LATIN SUBSCRIPT SMALL LETTER A*/
+ 0x2091, /* U+002091: LATIN SUBSCRIPT SMALL LETTER E*/
+ 0x2092, /* U+002092: LATIN SUBSCRIPT SMALL LETTER O*/
+ 0x2093, /* U+002093: LATIN SUBSCRIPT SMALL LETTER X*/
+ 0x2094, /* U+002094: LATIN SUBSCRIPT SMALL LETTER SCHWA*/
+ 0x2095, /* U+002095: LATIN SUBSCRIPT SMALL LETTER H*/
+ 0x2096, /* U+002096: LATIN SUBSCRIPT SMALL LETTER K*/
+ 0x2097, /* U+002097: LATIN SUBSCRIPT SMALL LETTER L*/
+ 0x2098, /* U+002098: LATIN SUBSCRIPT SMALL LETTER M*/
+ 0x2099, /* U+002099: LATIN SUBSCRIPT SMALL LETTER N*/
+ 0x209a, /* U+00209a: LATIN SUBSCRIPT SMALL LETTER P*/
+ 0x209b, /* U+00209b: LATIN SUBSCRIPT SMALL LETTER S*/
+ 0x209c, /* U+00209c: LATIN SUBSCRIPT SMALL LETTER T*/
+ 0x209d, /* U+00209d: */
+ 0x209e, /* U+00209e: */
+ 0x209f, /* U+00209f: */
+ 0x20a0, /* U+0020a0: EURO-CURRENCY SIGN*/
+ 0x20a1, /* U+0020a1: COLON SIGN*/
+ 0x20a2, /* U+0020a2: CRUZEIRO SIGN*/
+ 0x20a3, /* U+0020a3: FRENCH FRANC SIGN*/
+ 0x20a4, /* U+0020a4: LIRA SIGN*/
+ 0x20a5, /* U+0020a5: MILL SIGN*/
+ 0x20a6, /* U+0020a6: NAIRA SIGN*/
+ 0x20a7, /* U+0020a7: PESETA SIGN*/
+ 0x20a8, /* U+0020a8: RUPEE SIGN*/
+ 0x20a9, /* U+0020a9: WON SIGN*/
+ 0x20aa, /* U+0020aa: NEW SHEQEL SIGN*/
+ 0x20ab, /* U+0020ab: DONG SIGN*/
+ 0x20ac, /* U+0020ac: EURO SIGN*/
+ 0x20ad, /* U+0020ad: KIP SIGN*/
+ 0x20ae, /* U+0020ae: TUGRIK SIGN*/
+ 0x20af, /* U+0020af: DRACHMA SIGN*/
+ 0x20b0, /* U+0020b0: GERMAN PENNY SIGN*/
+ 0x20b1, /* U+0020b1: PESO SIGN*/
+ 0x20b2, /* U+0020b2: GUARANI SIGN*/
+ 0x20b3, /* U+0020b3: AUSTRAL SIGN*/
+ 0x20b4, /* U+0020b4: HRYVNIA SIGN*/
+ 0x20b5, /* U+0020b5: CEDI SIGN*/
+ 0x20b6, /* U+0020b6: LIVRE TOURNOIS SIGN*/
+ 0x20b7, /* U+0020b7: SPESMILO SIGN*/
+ 0x20b8, /* U+0020b8: TENGE SIGN*/
+ 0x20b9, /* U+0020b9: INDIAN RUPEE SIGN*/
+ 0x20ba, /* U+0020ba: TURKISH LIRA SIGN*/
+ 0x20bb, /* U+0020bb: */
+ 0x20bc, /* U+0020bc: */
+ 0x20bd, /* U+0020bd: */
+ 0x20be, /* U+0020be: */
+ 0x20bf, /* U+0020bf: */
+ 0x20c0, /* U+0020c0: */
+ 0x20c1, /* U+0020c1: */
+ 0x20c2, /* U+0020c2: */
+ 0x20c3, /* U+0020c3: */
+ 0x20c4, /* U+0020c4: */
+ 0x20c5, /* U+0020c5: */
+ 0x20c6, /* U+0020c6: */
+ 0x20c7, /* U+0020c7: */
+ 0x20c8, /* U+0020c8: */
+ 0x20c9, /* U+0020c9: */
+ 0x20ca, /* U+0020ca: */
+ 0x20cb, /* U+0020cb: */
+ 0x20cc, /* U+0020cc: */
+ 0x20cd, /* U+0020cd: */
+ 0x20ce, /* U+0020ce: */
+ 0x20cf, /* U+0020cf: */
+ 0x20d0, /* U+0020d0: COMBINING LEFT HARPOON ABOVE*/
+ 0x20d1, /* U+0020d1: COMBINING RIGHT HARPOON ABOVE*/
+ 0x20d2, /* U+0020d2: COMBINING LONG VERTICAL LINE OVERLAY*/
+ 0x20d3, /* U+0020d3: COMBINING SHORT VERTICAL LINE OVERLAY*/
+ 0x20d4, /* U+0020d4: COMBINING ANTICLOCKWISE ARROW ABOVE*/
+ 0x20d5, /* U+0020d5: COMBINING CLOCKWISE ARROW ABOVE*/
+ 0x20d6, /* U+0020d6: COMBINING LEFT ARROW ABOVE*/
+ 0x20d7, /* U+0020d7: COMBINING RIGHT ARROW ABOVE*/
+ 0x20d8, /* U+0020d8: COMBINING RING OVERLAY*/
+ 0x20d9, /* U+0020d9: COMBINING CLOCKWISE RING OVERLAY*/
+ 0x20da, /* U+0020da: COMBINING ANTICLOCKWISE RING OVERLAY*/
+ 0x20db, /* U+0020db: COMBINING THREE DOTS ABOVE*/
+ 0x20dc, /* U+0020dc: COMBINING FOUR DOTS ABOVE*/
+ 0x20dd, /* U+0020dd: COMBINING ENCLOSING CIRCLE*/
+ 0x20de, /* U+0020de: COMBINING ENCLOSING SQUARE*/
+ 0x20df, /* U+0020df: COMBINING ENCLOSING DIAMOND*/
+ 0x20e0, /* U+0020e0: COMBINING ENCLOSING CIRCLE BACKSLASH*/
+ 0x20e1, /* U+0020e1: COMBINING LEFT RIGHT ARROW ABOVE*/
+ 0x20e2, /* U+0020e2: COMBINING ENCLOSING SCREEN*/
+ 0x20e3, /* U+0020e3: COMBINING ENCLOSING KEYCAP*/
+ 0x20e4, /* U+0020e4: COMBINING ENCLOSING UPWARD POINTING TRIANGLE*/
+ 0x20e5, /* U+0020e5: COMBINING REVERSE SOLIDUS OVERLAY*/
+ 0x20e6, /* U+0020e6: COMBINING DOUBLE VERTICAL STROKE OVERLAY*/
+ 0x20e7, /* U+0020e7: COMBINING ANNUITY SYMBOL*/
+ 0x20e8, /* U+0020e8: COMBINING TRIPLE UNDERDOT*/
+ 0x20e9, /* U+0020e9: COMBINING WIDE BRIDGE ABOVE*/
+ 0x20ea, /* U+0020ea: COMBINING LEFTWARDS ARROW OVERLAY*/
+ 0x20eb, /* U+0020eb: COMBINING LONG DOUBLE SOLIDUS OVERLAY*/
+ 0x20ec, /* U+0020ec: COMBINING RIGHTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0x20ed, /* U+0020ed: COMBINING LEFTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0x20ee, /* U+0020ee: COMBINING LEFT ARROW BELOW*/
+ 0x20ef, /* U+0020ef: COMBINING RIGHT ARROW BELOW*/
+ 0x20f0, /* U+0020f0: COMBINING ASTERISK ABOVE*/
+ 0x20f1, /* U+0020f1: */
+ 0x20f2, /* U+0020f2: */
+ 0x20f3, /* U+0020f3: */
+ 0x20f4, /* U+0020f4: */
+ 0x20f5, /* U+0020f5: */
+ 0x20f6, /* U+0020f6: */
+ 0x20f7, /* U+0020f7: */
+ 0x20f8, /* U+0020f8: */
+ 0x20f9, /* U+0020f9: */
+ 0x20fa, /* U+0020fa: */
+ 0x20fb, /* U+0020fb: */
+ 0x20fc, /* U+0020fc: */
+ 0x20fd, /* U+0020fd: */
+ 0x20fe, /* U+0020fe: */
+ 0x20ff, /* U+0020ff: */
+ 0x2100, /* U+002100: ACCOUNT OF*/
+ 0x2101, /* U+002101: ADDRESSED TO THE SUBJECT*/
+ 0x2102, /* U+002102: DOUBLE-STRUCK CAPITAL C*/
+ 0x2103, /* U+002103: DEGREE CELSIUS*/
+ 0x2104, /* U+002104: CENTRE LINE SYMBOL*/
+ 0x2105, /* U+002105: CARE OF*/
+ 0x2106, /* U+002106: CADA UNA*/
+ 0x2107, /* U+002107: EULER CONSTANT*/
+ 0x2108, /* U+002108: SCRUPLE*/
+ 0x2109, /* U+002109: DEGREE FAHRENHEIT*/
+ 0x210a, /* U+00210a: SCRIPT SMALL G*/
+ 0x210b, /* U+00210b: SCRIPT CAPITAL H*/
+ 0x210c, /* U+00210c: BLACK-LETTER CAPITAL H*/
+ 0x210d, /* U+00210d: DOUBLE-STRUCK CAPITAL H*/
+ 0x210e, /* U+00210e: PLANCK CONSTANT*/
+ 0x210f, /* U+00210f: PLANCK CONSTANT OVER TWO PI*/
+ 0x2110, /* U+002110: SCRIPT CAPITAL I*/
+ 0x2111, /* U+002111: BLACK-LETTER CAPITAL I*/
+ 0x2112, /* U+002112: SCRIPT CAPITAL L*/
+ 0x2113, /* U+002113: SCRIPT SMALL L*/
+ 0x2114, /* U+002114: L B BAR SYMBOL*/
+ 0x2115, /* U+002115: DOUBLE-STRUCK CAPITAL N*/
+ 0x2116, /* U+002116: NUMERO SIGN*/
+ 0x2117, /* U+002117: SOUND RECORDING COPYRIGHT*/
+ 0x2118, /* U+002118: SCRIPT CAPITAL P*/
+ 0x2119, /* U+002119: DOUBLE-STRUCK CAPITAL P*/
+ 0x211a, /* U+00211a: DOUBLE-STRUCK CAPITAL Q*/
+ 0x211b, /* U+00211b: SCRIPT CAPITAL R*/
+ 0x211c, /* U+00211c: BLACK-LETTER CAPITAL R*/
+ 0x211d, /* U+00211d: DOUBLE-STRUCK CAPITAL R*/
+ 0x211e, /* U+00211e: PRESCRIPTION TAKE*/
+ 0x211f, /* U+00211f: RESPONSE*/
+ 0x2120, /* U+002120: SERVICE MARK*/
+ 0x2121, /* U+002121: TELEPHONE SIGN*/
+ 0x2122, /* U+002122: TRADE MARK SIGN*/
+ 0x2123, /* U+002123: VERSICLE*/
+ 0x2124, /* U+002124: DOUBLE-STRUCK CAPITAL Z*/
+ 0x2125, /* U+002125: OUNCE SIGN*/
+ 0x3c9, /* U+002126: OHM SIGN*/
+ 0x2127, /* U+002127: INVERTED OHM SIGN*/
+ 0x2128, /* U+002128: BLACK-LETTER CAPITAL Z*/
+ 0x2129, /* U+002129: TURNED GREEK SMALL LETTER IOTA*/
+ 0x6b, /* U+00212a: KELVIN SIGN*/
+ 0xe5, /* U+00212b: ANGSTROM SIGN*/
+ 0x212c, /* U+00212c: SCRIPT CAPITAL B*/
+ 0x212d, /* U+00212d: BLACK-LETTER CAPITAL C*/
+ 0x212e, /* U+00212e: ESTIMATED SYMBOL*/
+ 0x212f, /* U+00212f: SCRIPT SMALL E*/
+ 0x2130, /* U+002130: SCRIPT CAPITAL E*/
+ 0x2131, /* U+002131: SCRIPT CAPITAL F*/
+ 0x214e, /* U+002132: TURNED CAPITAL F*/
+ 0x2133, /* U+002133: SCRIPT CAPITAL M*/
+ 0x2134, /* U+002134: SCRIPT SMALL O*/
+ 0x2135, /* U+002135: ALEF SYMBOL*/
+ 0x2136, /* U+002136: BET SYMBOL*/
+ 0x2137, /* U+002137: GIMEL SYMBOL*/
+ 0x2138, /* U+002138: DALET SYMBOL*/
+ 0x2139, /* U+002139: INFORMATION SOURCE*/
+ 0x213a, /* U+00213a: ROTATED CAPITAL Q*/
+ 0x213b, /* U+00213b: FACSIMILE SIGN*/
+ 0x213c, /* U+00213c: DOUBLE-STRUCK SMALL PI*/
+ 0x213d, /* U+00213d: DOUBLE-STRUCK SMALL GAMMA*/
+ 0x213e, /* U+00213e: DOUBLE-STRUCK CAPITAL GAMMA*/
+ 0x213f, /* U+00213f: DOUBLE-STRUCK CAPITAL PI*/
+ 0x2140, /* U+002140: DOUBLE-STRUCK N-ARY SUMMATION*/
+ 0x2141, /* U+002141: TURNED SANS-SERIF CAPITAL G*/
+ 0x2142, /* U+002142: TURNED SANS-SERIF CAPITAL L*/
+ 0x2143, /* U+002143: REVERSED SANS-SERIF CAPITAL L*/
+ 0x2144, /* U+002144: TURNED SANS-SERIF CAPITAL Y*/
+ 0x2145, /* U+002145: DOUBLE-STRUCK ITALIC CAPITAL D*/
+ 0x2146, /* U+002146: DOUBLE-STRUCK ITALIC SMALL D*/
+ 0x2147, /* U+002147: DOUBLE-STRUCK ITALIC SMALL E*/
+ 0x2148, /* U+002148: DOUBLE-STRUCK ITALIC SMALL I*/
+ 0x2149, /* U+002149: DOUBLE-STRUCK ITALIC SMALL J*/
+ 0x214a, /* U+00214a: PROPERTY LINE*/
+ 0x214b, /* U+00214b: TURNED AMPERSAND*/
+ 0x214c, /* U+00214c: PER SIGN*/
+ 0x214d, /* U+00214d: AKTIESELSKAB*/
+ 0x214e, /* U+00214e: TURNED SMALL F*/
+ 0x214f, /* U+00214f: SYMBOL FOR SAMARITAN SOURCE*/
+ 0x2150, /* U+002150: VULGAR FRACTION ONE SEVENTH*/
+ 0x2151, /* U+002151: VULGAR FRACTION ONE NINTH*/
+ 0x2152, /* U+002152: VULGAR FRACTION ONE TENTH*/
+ 0x2153, /* U+002153: VULGAR FRACTION ONE THIRD*/
+ 0x2154, /* U+002154: VULGAR FRACTION TWO THIRDS*/
+ 0x2155, /* U+002155: VULGAR FRACTION ONE FIFTH*/
+ 0x2156, /* U+002156: VULGAR FRACTION TWO FIFTHS*/
+ 0x2157, /* U+002157: VULGAR FRACTION THREE FIFTHS*/
+ 0x2158, /* U+002158: VULGAR FRACTION FOUR FIFTHS*/
+ 0x2159, /* U+002159: VULGAR FRACTION ONE SIXTH*/
+ 0x215a, /* U+00215a: VULGAR FRACTION FIVE SIXTHS*/
+ 0x215b, /* U+00215b: VULGAR FRACTION ONE EIGHTH*/
+ 0x215c, /* U+00215c: VULGAR FRACTION THREE EIGHTHS*/
+ 0x215d, /* U+00215d: VULGAR FRACTION FIVE EIGHTHS*/
+ 0x215e, /* U+00215e: VULGAR FRACTION SEVEN EIGHTHS*/
+ 0x215f, /* U+00215f: FRACTION NUMERATOR ONE*/
+ 0x2170, /* U+002160: ROMAN NUMERAL ONE*/
+ 0x2171, /* U+002161: ROMAN NUMERAL TWO*/
+ 0x2172, /* U+002162: ROMAN NUMERAL THREE*/
+ 0x2173, /* U+002163: ROMAN NUMERAL FOUR*/
+ 0x2174, /* U+002164: ROMAN NUMERAL FIVE*/
+ 0x2175, /* U+002165: ROMAN NUMERAL SIX*/
+ 0x2176, /* U+002166: ROMAN NUMERAL SEVEN*/
+ 0x2177, /* U+002167: ROMAN NUMERAL EIGHT*/
+ 0x2178, /* U+002168: ROMAN NUMERAL NINE*/
+ 0x2179, /* U+002169: ROMAN NUMERAL TEN*/
+ 0x217a, /* U+00216a: ROMAN NUMERAL ELEVEN*/
+ 0x217b, /* U+00216b: ROMAN NUMERAL TWELVE*/
+ 0x217c, /* U+00216c: ROMAN NUMERAL FIFTY*/
+ 0x217d, /* U+00216d: ROMAN NUMERAL ONE HUNDRED*/
+ 0x217e, /* U+00216e: ROMAN NUMERAL FIVE HUNDRED*/
+ 0x217f, /* U+00216f: ROMAN NUMERAL ONE THOUSAND*/
+ 0x2170, /* U+002170: SMALL ROMAN NUMERAL ONE*/
+ 0x2171, /* U+002171: SMALL ROMAN NUMERAL TWO*/
+ 0x2172, /* U+002172: SMALL ROMAN NUMERAL THREE*/
+ 0x2173, /* U+002173: SMALL ROMAN NUMERAL FOUR*/
+ 0x2174, /* U+002174: SMALL ROMAN NUMERAL FIVE*/
+ 0x2175, /* U+002175: SMALL ROMAN NUMERAL SIX*/
+ 0x2176, /* U+002176: SMALL ROMAN NUMERAL SEVEN*/
+ 0x2177, /* U+002177: SMALL ROMAN NUMERAL EIGHT*/
+ 0x2178, /* U+002178: SMALL ROMAN NUMERAL NINE*/
+ 0x2179, /* U+002179: SMALL ROMAN NUMERAL TEN*/
+ 0x217a, /* U+00217a: SMALL ROMAN NUMERAL ELEVEN*/
+ 0x217b, /* U+00217b: SMALL ROMAN NUMERAL TWELVE*/
+ 0x217c, /* U+00217c: SMALL ROMAN NUMERAL FIFTY*/
+ 0x217d, /* U+00217d: SMALL ROMAN NUMERAL ONE HUNDRED*/
+ 0x217e, /* U+00217e: SMALL ROMAN NUMERAL FIVE HUNDRED*/
+ 0x217f, /* U+00217f: SMALL ROMAN NUMERAL ONE THOUSAND*/
+ 0x2180, /* U+002180: ROMAN NUMERAL ONE THOUSAND C D*/
+ 0x2181, /* U+002181: ROMAN NUMERAL FIVE THOUSAND*/
+ 0x2182, /* U+002182: ROMAN NUMERAL TEN THOUSAND*/
+ 0x2184, /* U+002183: ROMAN NUMERAL REVERSED ONE HUNDRED*/
+ 0x2184, /* U+002184: LATIN SMALL LETTER REVERSED C*/
+ 0x2185, /* U+002185: ROMAN NUMERAL SIX LATE FORM*/
+ 0x2186, /* U+002186: ROMAN NUMERAL FIFTY EARLY FORM*/
+ 0x2187, /* U+002187: ROMAN NUMERAL FIFTY THOUSAND*/
+ 0x2188, /* U+002188: ROMAN NUMERAL ONE HUNDRED THOUSAND*/
+ 0x2189, /* U+002189: VULGAR FRACTION ZERO THIRDS*/
+ 0x218a, /* U+00218a: */
+ 0x218b, /* U+00218b: */
+ 0x218c, /* U+00218c: */
+ 0x218d, /* U+00218d: */
+ 0x218e, /* U+00218e: */
+ 0x218f, /* U+00218f: */
+ 0x2190, /* U+002190: LEFTWARDS ARROW*/
+ 0x2191, /* U+002191: UPWARDS ARROW*/
+ 0x2192, /* U+002192: RIGHTWARDS ARROW*/
+ 0x2193, /* U+002193: DOWNWARDS ARROW*/
+ 0x2194, /* U+002194: LEFT RIGHT ARROW*/
+ 0x2195, /* U+002195: UP DOWN ARROW*/
+ 0x2196, /* U+002196: NORTH WEST ARROW*/
+ 0x2197, /* U+002197: NORTH EAST ARROW*/
+ 0x2198, /* U+002198: SOUTH EAST ARROW*/
+ 0x2199, /* U+002199: SOUTH WEST ARROW*/
+ 0x219a, /* U+00219a: LEFTWARDS ARROW WITH STROKE*/
+ 0x219b, /* U+00219b: RIGHTWARDS ARROW WITH STROKE*/
+ 0x219c, /* U+00219c: LEFTWARDS WAVE ARROW*/
+ 0x219d, /* U+00219d: RIGHTWARDS WAVE ARROW*/
+ 0x219e, /* U+00219e: LEFTWARDS TWO HEADED ARROW*/
+ 0x219f, /* U+00219f: UPWARDS TWO HEADED ARROW*/
+ 0x21a0, /* U+0021a0: RIGHTWARDS TWO HEADED ARROW*/
+ 0x21a1, /* U+0021a1: DOWNWARDS TWO HEADED ARROW*/
+ 0x21a2, /* U+0021a2: LEFTWARDS ARROW WITH TAIL*/
+ 0x21a3, /* U+0021a3: RIGHTWARDS ARROW WITH TAIL*/
+ 0x21a4, /* U+0021a4: LEFTWARDS ARROW FROM BAR*/
+ 0x21a5, /* U+0021a5: UPWARDS ARROW FROM BAR*/
+ 0x21a6, /* U+0021a6: RIGHTWARDS ARROW FROM BAR*/
+ 0x21a7, /* U+0021a7: DOWNWARDS ARROW FROM BAR*/
+ 0x21a8, /* U+0021a8: UP DOWN ARROW WITH BASE*/
+ 0x21a9, /* U+0021a9: LEFTWARDS ARROW WITH HOOK*/
+ 0x21aa, /* U+0021aa: RIGHTWARDS ARROW WITH HOOK*/
+ 0x21ab, /* U+0021ab: LEFTWARDS ARROW WITH LOOP*/
+ 0x21ac, /* U+0021ac: RIGHTWARDS ARROW WITH LOOP*/
+ 0x21ad, /* U+0021ad: LEFT RIGHT WAVE ARROW*/
+ 0x21ae, /* U+0021ae: LEFT RIGHT ARROW WITH STROKE*/
+ 0x21af, /* U+0021af: DOWNWARDS ZIGZAG ARROW*/
+ 0x21b0, /* U+0021b0: UPWARDS ARROW WITH TIP LEFTWARDS*/
+ 0x21b1, /* U+0021b1: UPWARDS ARROW WITH TIP RIGHTWARDS*/
+ 0x21b2, /* U+0021b2: DOWNWARDS ARROW WITH TIP LEFTWARDS*/
+ 0x21b3, /* U+0021b3: DOWNWARDS ARROW WITH TIP RIGHTWARDS*/
+ 0x21b4, /* U+0021b4: RIGHTWARDS ARROW WITH CORNER DOWNWARDS*/
+ 0x21b5, /* U+0021b5: DOWNWARDS ARROW WITH CORNER LEFTWARDS*/
+ 0x21b6, /* U+0021b6: ANTICLOCKWISE TOP SEMICIRCLE ARROW*/
+ 0x21b7, /* U+0021b7: CLOCKWISE TOP SEMICIRCLE ARROW*/
+ 0x21b8, /* U+0021b8: NORTH WEST ARROW TO LONG BAR*/
+ 0x21b9, /* U+0021b9: LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR*/
+ 0x21ba, /* U+0021ba: ANTICLOCKWISE OPEN CIRCLE ARROW*/
+ 0x21bb, /* U+0021bb: CLOCKWISE OPEN CIRCLE ARROW*/
+ 0x21bc, /* U+0021bc: LEFTWARDS HARPOON WITH BARB UPWARDS*/
+ 0x21bd, /* U+0021bd: LEFTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0x21be, /* U+0021be: UPWARDS HARPOON WITH BARB RIGHTWARDS*/
+ 0x21bf, /* U+0021bf: UPWARDS HARPOON WITH BARB LEFTWARDS*/
+ 0x21c0, /* U+0021c0: RIGHTWARDS HARPOON WITH BARB UPWARDS*/
+ 0x21c1, /* U+0021c1: RIGHTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0x21c2, /* U+0021c2: DOWNWARDS HARPOON WITH BARB RIGHTWARDS*/
+ 0x21c3, /* U+0021c3: DOWNWARDS HARPOON WITH BARB LEFTWARDS*/
+ 0x21c4, /* U+0021c4: RIGHTWARDS ARROW OVER LEFTWARDS ARROW*/
+ 0x21c5, /* U+0021c5: UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW*/
+ 0x21c6, /* U+0021c6: LEFTWARDS ARROW OVER RIGHTWARDS ARROW*/
+ 0x21c7, /* U+0021c7: LEFTWARDS PAIRED ARROWS*/
+ 0x21c8, /* U+0021c8: UPWARDS PAIRED ARROWS*/
+ 0x21c9, /* U+0021c9: RIGHTWARDS PAIRED ARROWS*/
+ 0x21ca, /* U+0021ca: DOWNWARDS PAIRED ARROWS*/
+ 0x21cb, /* U+0021cb: LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON*/
+ 0x21cc, /* U+0021cc: RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON*/
+ 0x21cd, /* U+0021cd: LEFTWARDS DOUBLE ARROW WITH STROKE*/
+ 0x21ce, /* U+0021ce: LEFT RIGHT DOUBLE ARROW WITH STROKE*/
+ 0x21cf, /* U+0021cf: RIGHTWARDS DOUBLE ARROW WITH STROKE*/
+ 0x21d0, /* U+0021d0: LEFTWARDS DOUBLE ARROW*/
+ 0x21d1, /* U+0021d1: UPWARDS DOUBLE ARROW*/
+ 0x21d2, /* U+0021d2: RIGHTWARDS DOUBLE ARROW*/
+ 0x21d3, /* U+0021d3: DOWNWARDS DOUBLE ARROW*/
+ 0x21d4, /* U+0021d4: LEFT RIGHT DOUBLE ARROW*/
+ 0x21d5, /* U+0021d5: UP DOWN DOUBLE ARROW*/
+ 0x21d6, /* U+0021d6: NORTH WEST DOUBLE ARROW*/
+ 0x21d7, /* U+0021d7: NORTH EAST DOUBLE ARROW*/
+ 0x21d8, /* U+0021d8: SOUTH EAST DOUBLE ARROW*/
+ 0x21d9, /* U+0021d9: SOUTH WEST DOUBLE ARROW*/
+ 0x21da, /* U+0021da: LEFTWARDS TRIPLE ARROW*/
+ 0x21db, /* U+0021db: RIGHTWARDS TRIPLE ARROW*/
+ 0x21dc, /* U+0021dc: LEFTWARDS SQUIGGLE ARROW*/
+ 0x21dd, /* U+0021dd: RIGHTWARDS SQUIGGLE ARROW*/
+ 0x21de, /* U+0021de: UPWARDS ARROW WITH DOUBLE STROKE*/
+ 0x21df, /* U+0021df: DOWNWARDS ARROW WITH DOUBLE STROKE*/
+ 0x21e0, /* U+0021e0: LEFTWARDS DASHED ARROW*/
+ 0x21e1, /* U+0021e1: UPWARDS DASHED ARROW*/
+ 0x21e2, /* U+0021e2: RIGHTWARDS DASHED ARROW*/
+ 0x21e3, /* U+0021e3: DOWNWARDS DASHED ARROW*/
+ 0x21e4, /* U+0021e4: LEFTWARDS ARROW TO BAR*/
+ 0x21e5, /* U+0021e5: RIGHTWARDS ARROW TO BAR*/
+ 0x21e6, /* U+0021e6: LEFTWARDS WHITE ARROW*/
+ 0x21e7, /* U+0021e7: UPWARDS WHITE ARROW*/
+ 0x21e8, /* U+0021e8: RIGHTWARDS WHITE ARROW*/
+ 0x21e9, /* U+0021e9: DOWNWARDS WHITE ARROW*/
+ 0x21ea, /* U+0021ea: UPWARDS WHITE ARROW FROM BAR*/
+ 0x21eb, /* U+0021eb: UPWARDS WHITE ARROW ON PEDESTAL*/
+ 0x21ec, /* U+0021ec: UPWARDS WHITE ARROW ON PEDESTAL WITH HORIZONTAL BAR*/
+ 0x21ed, /* U+0021ed: UPWARDS WHITE ARROW ON PEDESTAL WITH VERTICAL BAR*/
+ 0x21ee, /* U+0021ee: UPWARDS WHITE DOUBLE ARROW*/
+ 0x21ef, /* U+0021ef: UPWARDS WHITE DOUBLE ARROW ON PEDESTAL*/
+ 0x21f0, /* U+0021f0: RIGHTWARDS WHITE ARROW FROM WALL*/
+ 0x21f1, /* U+0021f1: NORTH WEST ARROW TO CORNER*/
+ 0x21f2, /* U+0021f2: SOUTH EAST ARROW TO CORNER*/
+ 0x21f3, /* U+0021f3: UP DOWN WHITE ARROW*/
+ 0x21f4, /* U+0021f4: RIGHT ARROW WITH SMALL CIRCLE*/
+ 0x21f5, /* U+0021f5: DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW*/
+ 0x21f6, /* U+0021f6: THREE RIGHTWARDS ARROWS*/
+ 0x21f7, /* U+0021f7: LEFTWARDS ARROW WITH VERTICAL STROKE*/
+ 0x21f8, /* U+0021f8: RIGHTWARDS ARROW WITH VERTICAL STROKE*/
+ 0x21f9, /* U+0021f9: LEFT RIGHT ARROW WITH VERTICAL STROKE*/
+ 0x21fa, /* U+0021fa: LEFTWARDS ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0x21fb, /* U+0021fb: RIGHTWARDS ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0x21fc, /* U+0021fc: LEFT RIGHT ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0x21fd, /* U+0021fd: LEFTWARDS OPEN-HEADED ARROW*/
+ 0x21fe, /* U+0021fe: RIGHTWARDS OPEN-HEADED ARROW*/
+ 0x21ff, /* U+0021ff: LEFT RIGHT OPEN-HEADED ARROW*/
+ 0x2200, /* U+002200: FOR ALL*/
+ 0x2201, /* U+002201: COMPLEMENT*/
+ 0x2202, /* U+002202: PARTIAL DIFFERENTIAL*/
+ 0x2203, /* U+002203: THERE EXISTS*/
+ 0x2204, /* U+002204: THERE DOES NOT EXIST*/
+ 0x2205, /* U+002205: EMPTY SET*/
+ 0x2206, /* U+002206: INCREMENT*/
+ 0x2207, /* U+002207: NABLA*/
+ 0x2208, /* U+002208: ELEMENT OF*/
+ 0x2209, /* U+002209: NOT AN ELEMENT OF*/
+ 0x220a, /* U+00220a: SMALL ELEMENT OF*/
+ 0x220b, /* U+00220b: CONTAINS AS MEMBER*/
+ 0x220c, /* U+00220c: DOES NOT CONTAIN AS MEMBER*/
+ 0x220d, /* U+00220d: SMALL CONTAINS AS MEMBER*/
+ 0x220e, /* U+00220e: END OF PROOF*/
+ 0x220f, /* U+00220f: N-ARY PRODUCT*/
+ 0x2210, /* U+002210: N-ARY COPRODUCT*/
+ 0x2211, /* U+002211: N-ARY SUMMATION*/
+ 0x2212, /* U+002212: MINUS SIGN*/
+ 0x2213, /* U+002213: MINUS-OR-PLUS SIGN*/
+ 0x2214, /* U+002214: DOT PLUS*/
+ 0x2215, /* U+002215: DIVISION SLASH*/
+ 0x2216, /* U+002216: SET MINUS*/
+ 0x2217, /* U+002217: ASTERISK OPERATOR*/
+ 0x2218, /* U+002218: RING OPERATOR*/
+ 0x2219, /* U+002219: BULLET OPERATOR*/
+ 0x221a, /* U+00221a: SQUARE ROOT*/
+ 0x221b, /* U+00221b: CUBE ROOT*/
+ 0x221c, /* U+00221c: FOURTH ROOT*/
+ 0x221d, /* U+00221d: PROPORTIONAL TO*/
+ 0x221e, /* U+00221e: INFINITY*/
+ 0x221f, /* U+00221f: RIGHT ANGLE*/
+ 0x2220, /* U+002220: ANGLE*/
+ 0x2221, /* U+002221: MEASURED ANGLE*/
+ 0x2222, /* U+002222: SPHERICAL ANGLE*/
+ 0x2223, /* U+002223: DIVIDES*/
+ 0x2224, /* U+002224: DOES NOT DIVIDE*/
+ 0x2225, /* U+002225: PARALLEL TO*/
+ 0x2226, /* U+002226: NOT PARALLEL TO*/
+ 0x2227, /* U+002227: LOGICAL AND*/
+ 0x2228, /* U+002228: LOGICAL OR*/
+ 0x2229, /* U+002229: INTERSECTION*/
+ 0x222a, /* U+00222a: UNION*/
+ 0x222b, /* U+00222b: INTEGRAL*/
+ 0x222c, /* U+00222c: DOUBLE INTEGRAL*/
+ 0x222d, /* U+00222d: TRIPLE INTEGRAL*/
+ 0x222e, /* U+00222e: CONTOUR INTEGRAL*/
+ 0x222f, /* U+00222f: SURFACE INTEGRAL*/
+ 0x2230, /* U+002230: VOLUME INTEGRAL*/
+ 0x2231, /* U+002231: CLOCKWISE INTEGRAL*/
+ 0x2232, /* U+002232: CLOCKWISE CONTOUR INTEGRAL*/
+ 0x2233, /* U+002233: ANTICLOCKWISE CONTOUR INTEGRAL*/
+ 0x2234, /* U+002234: THEREFORE*/
+ 0x2235, /* U+002235: BECAUSE*/
+ 0x2236, /* U+002236: RATIO*/
+ 0x2237, /* U+002237: PROPORTION*/
+ 0x2238, /* U+002238: DOT MINUS*/
+ 0x2239, /* U+002239: EXCESS*/
+ 0x223a, /* U+00223a: GEOMETRIC PROPORTION*/
+ 0x223b, /* U+00223b: HOMOTHETIC*/
+ 0x223c, /* U+00223c: TILDE OPERATOR*/
+ 0x223d, /* U+00223d: REVERSED TILDE*/
+ 0x223e, /* U+00223e: INVERTED LAZY S*/
+ 0x223f, /* U+00223f: SINE WAVE*/
+ 0x2240, /* U+002240: WREATH PRODUCT*/
+ 0x2241, /* U+002241: NOT TILDE*/
+ 0x2242, /* U+002242: MINUS TILDE*/
+ 0x2243, /* U+002243: ASYMPTOTICALLY EQUAL TO*/
+ 0x2244, /* U+002244: NOT ASYMPTOTICALLY EQUAL TO*/
+ 0x2245, /* U+002245: APPROXIMATELY EQUAL TO*/
+ 0x2246, /* U+002246: APPROXIMATELY BUT NOT ACTUALLY EQUAL TO*/
+ 0x2247, /* U+002247: NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO*/
+ 0x2248, /* U+002248: ALMOST EQUAL TO*/
+ 0x2249, /* U+002249: NOT ALMOST EQUAL TO*/
+ 0x224a, /* U+00224a: ALMOST EQUAL OR EQUAL TO*/
+ 0x224b, /* U+00224b: TRIPLE TILDE*/
+ 0x224c, /* U+00224c: ALL EQUAL TO*/
+ 0x224d, /* U+00224d: EQUIVALENT TO*/
+ 0x224e, /* U+00224e: GEOMETRICALLY EQUIVALENT TO*/
+ 0x224f, /* U+00224f: DIFFERENCE BETWEEN*/
+ 0x2250, /* U+002250: APPROACHES THE LIMIT*/
+ 0x2251, /* U+002251: GEOMETRICALLY EQUAL TO*/
+ 0x2252, /* U+002252: APPROXIMATELY EQUAL TO OR THE IMAGE OF*/
+ 0x2253, /* U+002253: IMAGE OF OR APPROXIMATELY EQUAL TO*/
+ 0x2254, /* U+002254: COLON EQUALS*/
+ 0x2255, /* U+002255: EQUALS COLON*/
+ 0x2256, /* U+002256: RING IN EQUAL TO*/
+ 0x2257, /* U+002257: RING EQUAL TO*/
+ 0x2258, /* U+002258: CORRESPONDS TO*/
+ 0x2259, /* U+002259: ESTIMATES*/
+ 0x225a, /* U+00225a: EQUIANGULAR TO*/
+ 0x225b, /* U+00225b: STAR EQUALS*/
+ 0x225c, /* U+00225c: DELTA EQUAL TO*/
+ 0x225d, /* U+00225d: EQUAL TO BY DEFINITION*/
+ 0x225e, /* U+00225e: MEASURED BY*/
+ 0x225f, /* U+00225f: QUESTIONED EQUAL TO*/
+ 0x2260, /* U+002260: NOT EQUAL TO*/
+ 0x2261, /* U+002261: IDENTICAL TO*/
+ 0x2262, /* U+002262: NOT IDENTICAL TO*/
+ 0x2263, /* U+002263: STRICTLY EQUIVALENT TO*/
+ 0x2264, /* U+002264: LESS-THAN OR EQUAL TO*/
+ 0x2265, /* U+002265: GREATER-THAN OR EQUAL TO*/
+ 0x2266, /* U+002266: LESS-THAN OVER EQUAL TO*/
+ 0x2267, /* U+002267: GREATER-THAN OVER EQUAL TO*/
+ 0x2268, /* U+002268: LESS-THAN BUT NOT EQUAL TO*/
+ 0x2269, /* U+002269: GREATER-THAN BUT NOT EQUAL TO*/
+ 0x226a, /* U+00226a: MUCH LESS-THAN*/
+ 0x226b, /* U+00226b: MUCH GREATER-THAN*/
+ 0x226c, /* U+00226c: BETWEEN*/
+ 0x226d, /* U+00226d: NOT EQUIVALENT TO*/
+ 0x226e, /* U+00226e: NOT LESS-THAN*/
+ 0x226f, /* U+00226f: NOT GREATER-THAN*/
+ 0x2270, /* U+002270: NEITHER LESS-THAN NOR EQUAL TO*/
+ 0x2271, /* U+002271: NEITHER GREATER-THAN NOR EQUAL TO*/
+ 0x2272, /* U+002272: LESS-THAN OR EQUIVALENT TO*/
+ 0x2273, /* U+002273: GREATER-THAN OR EQUIVALENT TO*/
+ 0x2274, /* U+002274: NEITHER LESS-THAN NOR EQUIVALENT TO*/
+ 0x2275, /* U+002275: NEITHER GREATER-THAN NOR EQUIVALENT TO*/
+ 0x2276, /* U+002276: LESS-THAN OR GREATER-THAN*/
+ 0x2277, /* U+002277: GREATER-THAN OR LESS-THAN*/
+ 0x2278, /* U+002278: NEITHER LESS-THAN NOR GREATER-THAN*/
+ 0x2279, /* U+002279: NEITHER GREATER-THAN NOR LESS-THAN*/
+ 0x227a, /* U+00227a: PRECEDES*/
+ 0x227b, /* U+00227b: SUCCEEDS*/
+ 0x227c, /* U+00227c: PRECEDES OR EQUAL TO*/
+ 0x227d, /* U+00227d: SUCCEEDS OR EQUAL TO*/
+ 0x227e, /* U+00227e: PRECEDES OR EQUIVALENT TO*/
+ 0x227f, /* U+00227f: SUCCEEDS OR EQUIVALENT TO*/
+ 0x2280, /* U+002280: DOES NOT PRECEDE*/
+ 0x2281, /* U+002281: DOES NOT SUCCEED*/
+ 0x2282, /* U+002282: SUBSET OF*/
+ 0x2283, /* U+002283: SUPERSET OF*/
+ 0x2284, /* U+002284: NOT A SUBSET OF*/
+ 0x2285, /* U+002285: NOT A SUPERSET OF*/
+ 0x2286, /* U+002286: SUBSET OF OR EQUAL TO*/
+ 0x2287, /* U+002287: SUPERSET OF OR EQUAL TO*/
+ 0x2288, /* U+002288: NEITHER A SUBSET OF NOR EQUAL TO*/
+ 0x2289, /* U+002289: NEITHER A SUPERSET OF NOR EQUAL TO*/
+ 0x228a, /* U+00228a: SUBSET OF WITH NOT EQUAL TO*/
+ 0x228b, /* U+00228b: SUPERSET OF WITH NOT EQUAL TO*/
+ 0x228c, /* U+00228c: MULTISET*/
+ 0x228d, /* U+00228d: MULTISET MULTIPLICATION*/
+ 0x228e, /* U+00228e: MULTISET UNION*/
+ 0x228f, /* U+00228f: SQUARE IMAGE OF*/
+ 0x2290, /* U+002290: SQUARE ORIGINAL OF*/
+ 0x2291, /* U+002291: SQUARE IMAGE OF OR EQUAL TO*/
+ 0x2292, /* U+002292: SQUARE ORIGINAL OF OR EQUAL TO*/
+ 0x2293, /* U+002293: SQUARE CAP*/
+ 0x2294, /* U+002294: SQUARE CUP*/
+ 0x2295, /* U+002295: CIRCLED PLUS*/
+ 0x2296, /* U+002296: CIRCLED MINUS*/
+ 0x2297, /* U+002297: CIRCLED TIMES*/
+ 0x2298, /* U+002298: CIRCLED DIVISION SLASH*/
+ 0x2299, /* U+002299: CIRCLED DOT OPERATOR*/
+ 0x229a, /* U+00229a: CIRCLED RING OPERATOR*/
+ 0x229b, /* U+00229b: CIRCLED ASTERISK OPERATOR*/
+ 0x229c, /* U+00229c: CIRCLED EQUALS*/
+ 0x229d, /* U+00229d: CIRCLED DASH*/
+ 0x229e, /* U+00229e: SQUARED PLUS*/
+ 0x229f, /* U+00229f: SQUARED MINUS*/
+ 0x22a0, /* U+0022a0: SQUARED TIMES*/
+ 0x22a1, /* U+0022a1: SQUARED DOT OPERATOR*/
+ 0x22a2, /* U+0022a2: RIGHT TACK*/
+ 0x22a3, /* U+0022a3: LEFT TACK*/
+ 0x22a4, /* U+0022a4: DOWN TACK*/
+ 0x22a5, /* U+0022a5: UP TACK*/
+ 0x22a6, /* U+0022a6: ASSERTION*/
+ 0x22a7, /* U+0022a7: MODELS*/
+ 0x22a8, /* U+0022a8: TRUE*/
+ 0x22a9, /* U+0022a9: FORCES*/
+ 0x22aa, /* U+0022aa: TRIPLE VERTICAL BAR RIGHT TURNSTILE*/
+ 0x22ab, /* U+0022ab: DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE*/
+ 0x22ac, /* U+0022ac: DOES NOT PROVE*/
+ 0x22ad, /* U+0022ad: NOT TRUE*/
+ 0x22ae, /* U+0022ae: DOES NOT FORCE*/
+ 0x22af, /* U+0022af: NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE*/
+ 0x22b0, /* U+0022b0: PRECEDES UNDER RELATION*/
+ 0x22b1, /* U+0022b1: SUCCEEDS UNDER RELATION*/
+ 0x22b2, /* U+0022b2: NORMAL SUBGROUP OF*/
+ 0x22b3, /* U+0022b3: CONTAINS AS NORMAL SUBGROUP*/
+ 0x22b4, /* U+0022b4: NORMAL SUBGROUP OF OR EQUAL TO*/
+ 0x22b5, /* U+0022b5: CONTAINS AS NORMAL SUBGROUP OR EQUAL TO*/
+ 0x22b6, /* U+0022b6: ORIGINAL OF*/
+ 0x22b7, /* U+0022b7: IMAGE OF*/
+ 0x22b8, /* U+0022b8: MULTIMAP*/
+ 0x22b9, /* U+0022b9: HERMITIAN CONJUGATE MATRIX*/
+ 0x22ba, /* U+0022ba: INTERCALATE*/
+ 0x22bb, /* U+0022bb: XOR*/
+ 0x22bc, /* U+0022bc: NAND*/
+ 0x22bd, /* U+0022bd: NOR*/
+ 0x22be, /* U+0022be: RIGHT ANGLE WITH ARC*/
+ 0x22bf, /* U+0022bf: RIGHT TRIANGLE*/
+ 0x22c0, /* U+0022c0: N-ARY LOGICAL AND*/
+ 0x22c1, /* U+0022c1: N-ARY LOGICAL OR*/
+ 0x22c2, /* U+0022c2: N-ARY INTERSECTION*/
+ 0x22c3, /* U+0022c3: N-ARY UNION*/
+ 0x22c4, /* U+0022c4: DIAMOND OPERATOR*/
+ 0x22c5, /* U+0022c5: DOT OPERATOR*/
+ 0x22c6, /* U+0022c6: STAR OPERATOR*/
+ 0x22c7, /* U+0022c7: DIVISION TIMES*/
+ 0x22c8, /* U+0022c8: BOWTIE*/
+ 0x22c9, /* U+0022c9: LEFT NORMAL FACTOR SEMIDIRECT PRODUCT*/
+ 0x22ca, /* U+0022ca: RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT*/
+ 0x22cb, /* U+0022cb: LEFT SEMIDIRECT PRODUCT*/
+ 0x22cc, /* U+0022cc: RIGHT SEMIDIRECT PRODUCT*/
+ 0x22cd, /* U+0022cd: REVERSED TILDE EQUALS*/
+ 0x22ce, /* U+0022ce: CURLY LOGICAL OR*/
+ 0x22cf, /* U+0022cf: CURLY LOGICAL AND*/
+ 0x22d0, /* U+0022d0: DOUBLE SUBSET*/
+ 0x22d1, /* U+0022d1: DOUBLE SUPERSET*/
+ 0x22d2, /* U+0022d2: DOUBLE INTERSECTION*/
+ 0x22d3, /* U+0022d3: DOUBLE UNION*/
+ 0x22d4, /* U+0022d4: PITCHFORK*/
+ 0x22d5, /* U+0022d5: EQUAL AND PARALLEL TO*/
+ 0x22d6, /* U+0022d6: LESS-THAN WITH DOT*/
+ 0x22d7, /* U+0022d7: GREATER-THAN WITH DOT*/
+ 0x22d8, /* U+0022d8: VERY MUCH LESS-THAN*/
+ 0x22d9, /* U+0022d9: VERY MUCH GREATER-THAN*/
+ 0x22da, /* U+0022da: LESS-THAN EQUAL TO OR GREATER-THAN*/
+ 0x22db, /* U+0022db: GREATER-THAN EQUAL TO OR LESS-THAN*/
+ 0x22dc, /* U+0022dc: EQUAL TO OR LESS-THAN*/
+ 0x22dd, /* U+0022dd: EQUAL TO OR GREATER-THAN*/
+ 0x22de, /* U+0022de: EQUAL TO OR PRECEDES*/
+ 0x22df, /* U+0022df: EQUAL TO OR SUCCEEDS*/
+ 0x22e0, /* U+0022e0: DOES NOT PRECEDE OR EQUAL*/
+ 0x22e1, /* U+0022e1: DOES NOT SUCCEED OR EQUAL*/
+ 0x22e2, /* U+0022e2: NOT SQUARE IMAGE OF OR EQUAL TO*/
+ 0x22e3, /* U+0022e3: NOT SQUARE ORIGINAL OF OR EQUAL TO*/
+ 0x22e4, /* U+0022e4: SQUARE IMAGE OF OR NOT EQUAL TO*/
+ 0x22e5, /* U+0022e5: SQUARE ORIGINAL OF OR NOT EQUAL TO*/
+ 0x22e6, /* U+0022e6: LESS-THAN BUT NOT EQUIVALENT TO*/
+ 0x22e7, /* U+0022e7: GREATER-THAN BUT NOT EQUIVALENT TO*/
+ 0x22e8, /* U+0022e8: PRECEDES BUT NOT EQUIVALENT TO*/
+ 0x22e9, /* U+0022e9: SUCCEEDS BUT NOT EQUIVALENT TO*/
+ 0x22ea, /* U+0022ea: NOT NORMAL SUBGROUP OF*/
+ 0x22eb, /* U+0022eb: DOES NOT CONTAIN AS NORMAL SUBGROUP*/
+ 0x22ec, /* U+0022ec: NOT NORMAL SUBGROUP OF OR EQUAL TO*/
+ 0x22ed, /* U+0022ed: DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL*/
+ 0x22ee, /* U+0022ee: VERTICAL ELLIPSIS*/
+ 0x22ef, /* U+0022ef: MIDLINE HORIZONTAL ELLIPSIS*/
+ 0x22f0, /* U+0022f0: UP RIGHT DIAGONAL ELLIPSIS*/
+ 0x22f1, /* U+0022f1: DOWN RIGHT DIAGONAL ELLIPSIS*/
+ 0x22f2, /* U+0022f2: ELEMENT OF WITH LONG HORIZONTAL STROKE*/
+ 0x22f3, /* U+0022f3: ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0x22f4, /* U+0022f4: SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0x22f5, /* U+0022f5: ELEMENT OF WITH DOT ABOVE*/
+ 0x22f6, /* U+0022f6: ELEMENT OF WITH OVERBAR*/
+ 0x22f7, /* U+0022f7: SMALL ELEMENT OF WITH OVERBAR*/
+ 0x22f8, /* U+0022f8: ELEMENT OF WITH UNDERBAR*/
+ 0x22f9, /* U+0022f9: ELEMENT OF WITH TWO HORIZONTAL STROKES*/
+ 0x22fa, /* U+0022fa: CONTAINS WITH LONG HORIZONTAL STROKE*/
+ 0x22fb, /* U+0022fb: CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0x22fc, /* U+0022fc: SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0x22fd, /* U+0022fd: CONTAINS WITH OVERBAR*/
+ 0x22fe, /* U+0022fe: SMALL CONTAINS WITH OVERBAR*/
+ 0x22ff, /* U+0022ff: Z NOTATION BAG MEMBERSHIP*/
+ 0x2300, /* U+002300: DIAMETER SIGN*/
+ 0x2301, /* U+002301: ELECTRIC ARROW*/
+ 0x2302, /* U+002302: HOUSE*/
+ 0x2303, /* U+002303: UP ARROWHEAD*/
+ 0x2304, /* U+002304: DOWN ARROWHEAD*/
+ 0x2305, /* U+002305: PROJECTIVE*/
+ 0x2306, /* U+002306: PERSPECTIVE*/
+ 0x2307, /* U+002307: WAVY LINE*/
+ 0x2308, /* U+002308: LEFT CEILING*/
+ 0x2309, /* U+002309: RIGHT CEILING*/
+ 0x230a, /* U+00230a: LEFT FLOOR*/
+ 0x230b, /* U+00230b: RIGHT FLOOR*/
+ 0x230c, /* U+00230c: BOTTOM RIGHT CROP*/
+ 0x230d, /* U+00230d: BOTTOM LEFT CROP*/
+ 0x230e, /* U+00230e: TOP RIGHT CROP*/
+ 0x230f, /* U+00230f: TOP LEFT CROP*/
+ 0x2310, /* U+002310: REVERSED NOT SIGN*/
+ 0x2311, /* U+002311: SQUARE LOZENGE*/
+ 0x2312, /* U+002312: ARC*/
+ 0x2313, /* U+002313: SEGMENT*/
+ 0x2314, /* U+002314: SECTOR*/
+ 0x2315, /* U+002315: TELEPHONE RECORDER*/
+ 0x2316, /* U+002316: POSITION INDICATOR*/
+ 0x2317, /* U+002317: VIEWDATA SQUARE*/
+ 0x2318, /* U+002318: PLACE OF INTEREST SIGN*/
+ 0x2319, /* U+002319: TURNED NOT SIGN*/
+ 0x231a, /* U+00231a: WATCH*/
+ 0x231b, /* U+00231b: HOURGLASS*/
+ 0x231c, /* U+00231c: TOP LEFT CORNER*/
+ 0x231d, /* U+00231d: TOP RIGHT CORNER*/
+ 0x231e, /* U+00231e: BOTTOM LEFT CORNER*/
+ 0x231f, /* U+00231f: BOTTOM RIGHT CORNER*/
+ 0x2320, /* U+002320: TOP HALF INTEGRAL*/
+ 0x2321, /* U+002321: BOTTOM HALF INTEGRAL*/
+ 0x2322, /* U+002322: FROWN*/
+ 0x2323, /* U+002323: SMILE*/
+ 0x2324, /* U+002324: UP ARROWHEAD BETWEEN TWO HORIZONTAL BARS*/
+ 0x2325, /* U+002325: OPTION KEY*/
+ 0x2326, /* U+002326: ERASE TO THE RIGHT*/
+ 0x2327, /* U+002327: X IN A RECTANGLE BOX*/
+ 0x2328, /* U+002328: KEYBOARD*/
+ 0x2329, /* U+002329: LEFT-POINTING ANGLE BRACKET*/
+ 0x232a, /* U+00232a: RIGHT-POINTING ANGLE BRACKET*/
+ 0x232b, /* U+00232b: ERASE TO THE LEFT*/
+ 0x232c, /* U+00232c: BENZENE RING*/
+ 0x232d, /* U+00232d: CYLINDRICITY*/
+ 0x232e, /* U+00232e: ALL AROUND-PROFILE*/
+ 0x232f, /* U+00232f: SYMMETRY*/
+ 0x2330, /* U+002330: TOTAL RUNOUT*/
+ 0x2331, /* U+002331: DIMENSION ORIGIN*/
+ 0x2332, /* U+002332: CONICAL TAPER*/
+ 0x2333, /* U+002333: SLOPE*/
+ 0x2334, /* U+002334: COUNTERBORE*/
+ 0x2335, /* U+002335: COUNTERSINK*/
+ 0x2336, /* U+002336: APL FUNCTIONAL SYMBOL I-BEAM*/
+ 0x2337, /* U+002337: APL FUNCTIONAL SYMBOL SQUISH QUAD*/
+ 0x2338, /* U+002338: APL FUNCTIONAL SYMBOL QUAD EQUAL*/
+ 0x2339, /* U+002339: APL FUNCTIONAL SYMBOL QUAD DIVIDE*/
+ 0x233a, /* U+00233a: APL FUNCTIONAL SYMBOL QUAD DIAMOND*/
+ 0x233b, /* U+00233b: APL FUNCTIONAL SYMBOL QUAD JOT*/
+ 0x233c, /* U+00233c: APL FUNCTIONAL SYMBOL QUAD CIRCLE*/
+ 0x233d, /* U+00233d: APL FUNCTIONAL SYMBOL CIRCLE STILE*/
+ 0x233e, /* U+00233e: APL FUNCTIONAL SYMBOL CIRCLE JOT*/
+ 0x233f, /* U+00233f: APL FUNCTIONAL SYMBOL SLASH BAR*/
+ 0x2340, /* U+002340: APL FUNCTIONAL SYMBOL BACKSLASH BAR*/
+ 0x2341, /* U+002341: APL FUNCTIONAL SYMBOL QUAD SLASH*/
+ 0x2342, /* U+002342: APL FUNCTIONAL SYMBOL QUAD BACKSLASH*/
+ 0x2343, /* U+002343: APL FUNCTIONAL SYMBOL QUAD LESS-THAN*/
+ 0x2344, /* U+002344: APL FUNCTIONAL SYMBOL QUAD GREATER-THAN*/
+ 0x2345, /* U+002345: APL FUNCTIONAL SYMBOL LEFTWARDS VANE*/
+ 0x2346, /* U+002346: APL FUNCTIONAL SYMBOL RIGHTWARDS VANE*/
+ 0x2347, /* U+002347: APL FUNCTIONAL SYMBOL QUAD LEFTWARDS ARROW*/
+ 0x2348, /* U+002348: APL FUNCTIONAL SYMBOL QUAD RIGHTWARDS ARROW*/
+ 0x2349, /* U+002349: APL FUNCTIONAL SYMBOL CIRCLE BACKSLASH*/
+ 0x234a, /* U+00234a: APL FUNCTIONAL SYMBOL DOWN TACK UNDERBAR*/
+ 0x234b, /* U+00234b: APL FUNCTIONAL SYMBOL DELTA STILE*/
+ 0x234c, /* U+00234c: APL FUNCTIONAL SYMBOL QUAD DOWN CARET*/
+ 0x234d, /* U+00234d: APL FUNCTIONAL SYMBOL QUAD DELTA*/
+ 0x234e, /* U+00234e: APL FUNCTIONAL SYMBOL DOWN TACK JOT*/
+ 0x234f, /* U+00234f: APL FUNCTIONAL SYMBOL UPWARDS VANE*/
+ 0x2350, /* U+002350: APL FUNCTIONAL SYMBOL QUAD UPWARDS ARROW*/
+ 0x2351, /* U+002351: APL FUNCTIONAL SYMBOL UP TACK OVERBAR*/
+ 0x2352, /* U+002352: APL FUNCTIONAL SYMBOL DEL STILE*/
+ 0x2353, /* U+002353: APL FUNCTIONAL SYMBOL QUAD UP CARET*/
+ 0x2354, /* U+002354: APL FUNCTIONAL SYMBOL QUAD DEL*/
+ 0x2355, /* U+002355: APL FUNCTIONAL SYMBOL UP TACK JOT*/
+ 0x2356, /* U+002356: APL FUNCTIONAL SYMBOL DOWNWARDS VANE*/
+ 0x2357, /* U+002357: APL FUNCTIONAL SYMBOL QUAD DOWNWARDS ARROW*/
+ 0x2358, /* U+002358: APL FUNCTIONAL SYMBOL QUOTE UNDERBAR*/
+ 0x2359, /* U+002359: APL FUNCTIONAL SYMBOL DELTA UNDERBAR*/
+ 0x235a, /* U+00235a: APL FUNCTIONAL SYMBOL DIAMOND UNDERBAR*/
+ 0x235b, /* U+00235b: APL FUNCTIONAL SYMBOL JOT UNDERBAR*/
+ 0x235c, /* U+00235c: APL FUNCTIONAL SYMBOL CIRCLE UNDERBAR*/
+ 0x235d, /* U+00235d: APL FUNCTIONAL SYMBOL UP SHOE JOT*/
+ 0x235e, /* U+00235e: APL FUNCTIONAL SYMBOL QUOTE QUAD*/
+ 0x235f, /* U+00235f: APL FUNCTIONAL SYMBOL CIRCLE STAR*/
+ 0x2360, /* U+002360: APL FUNCTIONAL SYMBOL QUAD COLON*/
+ 0x2361, /* U+002361: APL FUNCTIONAL SYMBOL UP TACK DIAERESIS*/
+ 0x2362, /* U+002362: APL FUNCTIONAL SYMBOL DEL DIAERESIS*/
+ 0x2363, /* U+002363: APL FUNCTIONAL SYMBOL STAR DIAERESIS*/
+ 0x2364, /* U+002364: APL FUNCTIONAL SYMBOL JOT DIAERESIS*/
+ 0x2365, /* U+002365: APL FUNCTIONAL SYMBOL CIRCLE DIAERESIS*/
+ 0x2366, /* U+002366: APL FUNCTIONAL SYMBOL DOWN SHOE STILE*/
+ 0x2367, /* U+002367: APL FUNCTIONAL SYMBOL LEFT SHOE STILE*/
+ 0x2368, /* U+002368: APL FUNCTIONAL SYMBOL TILDE DIAERESIS*/
+ 0x2369, /* U+002369: APL FUNCTIONAL SYMBOL GREATER-THAN DIAERESIS*/
+ 0x236a, /* U+00236a: APL FUNCTIONAL SYMBOL COMMA BAR*/
+ 0x236b, /* U+00236b: APL FUNCTIONAL SYMBOL DEL TILDE*/
+ 0x236c, /* U+00236c: APL FUNCTIONAL SYMBOL ZILDE*/
+ 0x236d, /* U+00236d: APL FUNCTIONAL SYMBOL STILE TILDE*/
+ 0x236e, /* U+00236e: APL FUNCTIONAL SYMBOL SEMICOLON UNDERBAR*/
+ 0x236f, /* U+00236f: APL FUNCTIONAL SYMBOL QUAD NOT EQUAL*/
+ 0x2370, /* U+002370: APL FUNCTIONAL SYMBOL QUAD QUESTION*/
+ 0x2371, /* U+002371: APL FUNCTIONAL SYMBOL DOWN CARET TILDE*/
+ 0x2372, /* U+002372: APL FUNCTIONAL SYMBOL UP CARET TILDE*/
+ 0x2373, /* U+002373: APL FUNCTIONAL SYMBOL IOTA*/
+ 0x2374, /* U+002374: APL FUNCTIONAL SYMBOL RHO*/
+ 0x2375, /* U+002375: APL FUNCTIONAL SYMBOL OMEGA*/
+ 0x2376, /* U+002376: APL FUNCTIONAL SYMBOL ALPHA UNDERBAR*/
+ 0x2377, /* U+002377: APL FUNCTIONAL SYMBOL EPSILON UNDERBAR*/
+ 0x2378, /* U+002378: APL FUNCTIONAL SYMBOL IOTA UNDERBAR*/
+ 0x2379, /* U+002379: APL FUNCTIONAL SYMBOL OMEGA UNDERBAR*/
+ 0x237a, /* U+00237a: APL FUNCTIONAL SYMBOL ALPHA*/
+ 0x237b, /* U+00237b: NOT CHECK MARK*/
+ 0x237c, /* U+00237c: RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW*/
+ 0x237d, /* U+00237d: SHOULDERED OPEN BOX*/
+ 0x237e, /* U+00237e: BELL SYMBOL*/
+ 0x237f, /* U+00237f: VERTICAL LINE WITH MIDDLE DOT*/
+ 0x2380, /* U+002380: INSERTION SYMBOL*/
+ 0x2381, /* U+002381: CONTINUOUS UNDERLINE SYMBOL*/
+ 0x2382, /* U+002382: DISCONTINUOUS UNDERLINE SYMBOL*/
+ 0x2383, /* U+002383: EMPHASIS SYMBOL*/
+ 0x2384, /* U+002384: COMPOSITION SYMBOL*/
+ 0x2385, /* U+002385: WHITE SQUARE WITH CENTRE VERTICAL LINE*/
+ 0x2386, /* U+002386: ENTER SYMBOL*/
+ 0x2387, /* U+002387: ALTERNATIVE KEY SYMBOL*/
+ 0x2388, /* U+002388: HELM SYMBOL*/
+ 0x2389, /* U+002389: CIRCLED HORIZONTAL BAR WITH NOTCH*/
+ 0x238a, /* U+00238a: CIRCLED TRIANGLE DOWN*/
+ 0x238b, /* U+00238b: BROKEN CIRCLE WITH NORTHWEST ARROW*/
+ 0x238c, /* U+00238c: UNDO SYMBOL*/
+ 0x238d, /* U+00238d: MONOSTABLE SYMBOL*/
+ 0x238e, /* U+00238e: HYSTERESIS SYMBOL*/
+ 0x238f, /* U+00238f: OPEN-CIRCUIT-OUTPUT H-TYPE SYMBOL*/
+ 0x2390, /* U+002390: OPEN-CIRCUIT-OUTPUT L-TYPE SYMBOL*/
+ 0x2391, /* U+002391: PASSIVE-PULL-DOWN-OUTPUT SYMBOL*/
+ 0x2392, /* U+002392: PASSIVE-PULL-UP-OUTPUT SYMBOL*/
+ 0x2393, /* U+002393: DIRECT CURRENT SYMBOL FORM TWO*/
+ 0x2394, /* U+002394: SOFTWARE-FUNCTION SYMBOL*/
+ 0x2395, /* U+002395: APL FUNCTIONAL SYMBOL QUAD*/
+ 0x2396, /* U+002396: DECIMAL SEPARATOR KEY SYMBOL*/
+ 0x2397, /* U+002397: PREVIOUS PAGE*/
+ 0x2398, /* U+002398: NEXT PAGE*/
+ 0x2399, /* U+002399: PRINT SCREEN SYMBOL*/
+ 0x239a, /* U+00239a: CLEAR SCREEN SYMBOL*/
+ 0x239b, /* U+00239b: LEFT PARENTHESIS UPPER HOOK*/
+ 0x239c, /* U+00239c: LEFT PARENTHESIS EXTENSION*/
+ 0x239d, /* U+00239d: LEFT PARENTHESIS LOWER HOOK*/
+ 0x239e, /* U+00239e: RIGHT PARENTHESIS UPPER HOOK*/
+ 0x239f, /* U+00239f: RIGHT PARENTHESIS EXTENSION*/
+ 0x23a0, /* U+0023a0: RIGHT PARENTHESIS LOWER HOOK*/
+ 0x23a1, /* U+0023a1: LEFT SQUARE BRACKET UPPER CORNER*/
+ 0x23a2, /* U+0023a2: LEFT SQUARE BRACKET EXTENSION*/
+ 0x23a3, /* U+0023a3: LEFT SQUARE BRACKET LOWER CORNER*/
+ 0x23a4, /* U+0023a4: RIGHT SQUARE BRACKET UPPER CORNER*/
+ 0x23a5, /* U+0023a5: RIGHT SQUARE BRACKET EXTENSION*/
+ 0x23a6, /* U+0023a6: RIGHT SQUARE BRACKET LOWER CORNER*/
+ 0x23a7, /* U+0023a7: LEFT CURLY BRACKET UPPER HOOK*/
+ 0x23a8, /* U+0023a8: LEFT CURLY BRACKET MIDDLE PIECE*/
+ 0x23a9, /* U+0023a9: LEFT CURLY BRACKET LOWER HOOK*/
+ 0x23aa, /* U+0023aa: CURLY BRACKET EXTENSION*/
+ 0x23ab, /* U+0023ab: RIGHT CURLY BRACKET UPPER HOOK*/
+ 0x23ac, /* U+0023ac: RIGHT CURLY BRACKET MIDDLE PIECE*/
+ 0x23ad, /* U+0023ad: RIGHT CURLY BRACKET LOWER HOOK*/
+ 0x23ae, /* U+0023ae: INTEGRAL EXTENSION*/
+ 0x23af, /* U+0023af: HORIZONTAL LINE EXTENSION*/
+ 0x23b0, /* U+0023b0: UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION*/
+ 0x23b1, /* U+0023b1: UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION*/
+ 0x23b2, /* U+0023b2: SUMMATION TOP*/
+ 0x23b3, /* U+0023b3: SUMMATION BOTTOM*/
+ 0x23b4, /* U+0023b4: TOP SQUARE BRACKET*/
+ 0x23b5, /* U+0023b5: BOTTOM SQUARE BRACKET*/
+ 0x23b6, /* U+0023b6: BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET*/
+ 0x23b7, /* U+0023b7: RADICAL SYMBOL BOTTOM*/
+ 0x23b8, /* U+0023b8: LEFT VERTICAL BOX LINE*/
+ 0x23b9, /* U+0023b9: RIGHT VERTICAL BOX LINE*/
+ 0x23ba, /* U+0023ba: HORIZONTAL SCAN LINE-1*/
+ 0x23bb, /* U+0023bb: HORIZONTAL SCAN LINE-3*/
+ 0x23bc, /* U+0023bc: HORIZONTAL SCAN LINE-7*/
+ 0x23bd, /* U+0023bd: HORIZONTAL SCAN LINE-9*/
+ 0x23be, /* U+0023be: DENTISTRY SYMBOL LIGHT VERTICAL AND TOP RIGHT*/
+ 0x23bf, /* U+0023bf: DENTISTRY SYMBOL LIGHT VERTICAL AND BOTTOM RIGHT*/
+ 0x23c0, /* U+0023c0: DENTISTRY SYMBOL LIGHT VERTICAL WITH CIRCLE*/
+ 0x23c1, /* U+0023c1: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH CIRCLE*/
+ 0x23c2, /* U+0023c2: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH CIRCLE*/
+ 0x23c3, /* U+0023c3: DENTISTRY SYMBOL LIGHT VERTICAL WITH TRIANGLE*/
+ 0x23c4, /* U+0023c4: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH TRIANGLE*/
+ 0x23c5, /* U+0023c5: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH TRIANGLE*/
+ 0x23c6, /* U+0023c6: DENTISTRY SYMBOL LIGHT VERTICAL AND WAVE*/
+ 0x23c7, /* U+0023c7: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH WAVE*/
+ 0x23c8, /* U+0023c8: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH WAVE*/
+ 0x23c9, /* U+0023c9: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL*/
+ 0x23ca, /* U+0023ca: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL*/
+ 0x23cb, /* U+0023cb: DENTISTRY SYMBOL LIGHT VERTICAL AND TOP LEFT*/
+ 0x23cc, /* U+0023cc: DENTISTRY SYMBOL LIGHT VERTICAL AND BOTTOM LEFT*/
+ 0x23cd, /* U+0023cd: SQUARE FOOT*/
+ 0x23ce, /* U+0023ce: RETURN SYMBOL*/
+ 0x23cf, /* U+0023cf: EJECT SYMBOL*/
+ 0x23d0, /* U+0023d0: VERTICAL LINE EXTENSION*/
+ 0x23d1, /* U+0023d1: METRICAL BREVE*/
+ 0x23d2, /* U+0023d2: METRICAL LONG OVER SHORT*/
+ 0x23d3, /* U+0023d3: METRICAL SHORT OVER LONG*/
+ 0x23d4, /* U+0023d4: METRICAL LONG OVER TWO SHORTS*/
+ 0x23d5, /* U+0023d5: METRICAL TWO SHORTS OVER LONG*/
+ 0x23d6, /* U+0023d6: METRICAL TWO SHORTS JOINED*/
+ 0x23d7, /* U+0023d7: METRICAL TRISEME*/
+ 0x23d8, /* U+0023d8: METRICAL TETRASEME*/
+ 0x23d9, /* U+0023d9: METRICAL PENTASEME*/
+ 0x23da, /* U+0023da: EARTH GROUND*/
+ 0x23db, /* U+0023db: FUSE*/
+ 0x23dc, /* U+0023dc: TOP PARENTHESIS*/
+ 0x23dd, /* U+0023dd: BOTTOM PARENTHESIS*/
+ 0x23de, /* U+0023de: TOP CURLY BRACKET*/
+ 0x23df, /* U+0023df: BOTTOM CURLY BRACKET*/
+ 0x23e0, /* U+0023e0: TOP TORTOISE SHELL BRACKET*/
+ 0x23e1, /* U+0023e1: BOTTOM TORTOISE SHELL BRACKET*/
+ 0x23e2, /* U+0023e2: WHITE TRAPEZIUM*/
+ 0x23e3, /* U+0023e3: BENZENE RING WITH CIRCLE*/
+ 0x23e4, /* U+0023e4: STRAIGHTNESS*/
+ 0x23e5, /* U+0023e5: FLATNESS*/
+ 0x23e6, /* U+0023e6: AC CURRENT*/
+ 0x23e7, /* U+0023e7: ELECTRICAL INTERSECTION*/
+ 0x23e8, /* U+0023e8: DECIMAL EXPONENT SYMBOL*/
+ 0x23e9, /* U+0023e9: BLACK RIGHT-POINTING DOUBLE TRIANGLE*/
+ 0x23ea, /* U+0023ea: BLACK LEFT-POINTING DOUBLE TRIANGLE*/
+ 0x23eb, /* U+0023eb: BLACK UP-POINTING DOUBLE TRIANGLE*/
+ 0x23ec, /* U+0023ec: BLACK DOWN-POINTING DOUBLE TRIANGLE*/
+ 0x23ed, /* U+0023ed: BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR*/
+ 0x23ee, /* U+0023ee: BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR*/
+ 0x23ef, /* U+0023ef: BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR*/
+ 0x23f0, /* U+0023f0: ALARM CLOCK*/
+ 0x23f1, /* U+0023f1: STOPWATCH*/
+ 0x23f2, /* U+0023f2: TIMER CLOCK*/
+ 0x23f3, /* U+0023f3: HOURGLASS WITH FLOWING SAND*/
+ 0x23f4, /* U+0023f4: */
+ 0x23f5, /* U+0023f5: */
+ 0x23f6, /* U+0023f6: */
+ 0x23f7, /* U+0023f7: */
+ 0x23f8, /* U+0023f8: */
+ 0x23f9, /* U+0023f9: */
+ 0x23fa, /* U+0023fa: */
+ 0x23fb, /* U+0023fb: */
+ 0x23fc, /* U+0023fc: */
+ 0x23fd, /* U+0023fd: */
+ 0x23fe, /* U+0023fe: */
+ 0x23ff, /* U+0023ff: */
+ 0x2400, /* U+002400: SYMBOL FOR NULL*/
+ 0x2401, /* U+002401: SYMBOL FOR START OF HEADING*/
+ 0x2402, /* U+002402: SYMBOL FOR START OF TEXT*/
+ 0x2403, /* U+002403: SYMBOL FOR END OF TEXT*/
+ 0x2404, /* U+002404: SYMBOL FOR END OF TRANSMISSION*/
+ 0x2405, /* U+002405: SYMBOL FOR ENQUIRY*/
+ 0x2406, /* U+002406: SYMBOL FOR ACKNOWLEDGE*/
+ 0x2407, /* U+002407: SYMBOL FOR BELL*/
+ 0x2408, /* U+002408: SYMBOL FOR BACKSPACE*/
+ 0x2409, /* U+002409: SYMBOL FOR HORIZONTAL TABULATION*/
+ 0x240a, /* U+00240a: SYMBOL FOR LINE FEED*/
+ 0x240b, /* U+00240b: SYMBOL FOR VERTICAL TABULATION*/
+ 0x240c, /* U+00240c: SYMBOL FOR FORM FEED*/
+ 0x240d, /* U+00240d: SYMBOL FOR CARRIAGE RETURN*/
+ 0x240e, /* U+00240e: SYMBOL FOR SHIFT OUT*/
+ 0x240f, /* U+00240f: SYMBOL FOR SHIFT IN*/
+ 0x2410, /* U+002410: SYMBOL FOR DATA LINK ESCAPE*/
+ 0x2411, /* U+002411: SYMBOL FOR DEVICE CONTROL ONE*/
+ 0x2412, /* U+002412: SYMBOL FOR DEVICE CONTROL TWO*/
+ 0x2413, /* U+002413: SYMBOL FOR DEVICE CONTROL THREE*/
+ 0x2414, /* U+002414: SYMBOL FOR DEVICE CONTROL FOUR*/
+ 0x2415, /* U+002415: SYMBOL FOR NEGATIVE ACKNOWLEDGE*/
+ 0x2416, /* U+002416: SYMBOL FOR SYNCHRONOUS IDLE*/
+ 0x2417, /* U+002417: SYMBOL FOR END OF TRANSMISSION BLOCK*/
+ 0x2418, /* U+002418: SYMBOL FOR CANCEL*/
+ 0x2419, /* U+002419: SYMBOL FOR END OF MEDIUM*/
+ 0x241a, /* U+00241a: SYMBOL FOR SUBSTITUTE*/
+ 0x241b, /* U+00241b: SYMBOL FOR ESCAPE*/
+ 0x241c, /* U+00241c: SYMBOL FOR FILE SEPARATOR*/
+ 0x241d, /* U+00241d: SYMBOL FOR GROUP SEPARATOR*/
+ 0x241e, /* U+00241e: SYMBOL FOR RECORD SEPARATOR*/
+ 0x241f, /* U+00241f: SYMBOL FOR UNIT SEPARATOR*/
+ 0x2420, /* U+002420: SYMBOL FOR SPACE*/
+ 0x2421, /* U+002421: SYMBOL FOR DELETE*/
+ 0x2422, /* U+002422: BLANK SYMBOL*/
+ 0x2423, /* U+002423: OPEN BOX*/
+ 0x2424, /* U+002424: SYMBOL FOR NEWLINE*/
+ 0x2425, /* U+002425: SYMBOL FOR DELETE FORM TWO*/
+ 0x2426, /* U+002426: SYMBOL FOR SUBSTITUTE FORM TWO*/
+ 0x2427, /* U+002427: */
+ 0x2428, /* U+002428: */
+ 0x2429, /* U+002429: */
+ 0x242a, /* U+00242a: */
+ 0x242b, /* U+00242b: */
+ 0x242c, /* U+00242c: */
+ 0x242d, /* U+00242d: */
+ 0x242e, /* U+00242e: */
+ 0x242f, /* U+00242f: */
+ 0x2430, /* U+002430: */
+ 0x2431, /* U+002431: */
+ 0x2432, /* U+002432: */
+ 0x2433, /* U+002433: */
+ 0x2434, /* U+002434: */
+ 0x2435, /* U+002435: */
+ 0x2436, /* U+002436: */
+ 0x2437, /* U+002437: */
+ 0x2438, /* U+002438: */
+ 0x2439, /* U+002439: */
+ 0x243a, /* U+00243a: */
+ 0x243b, /* U+00243b: */
+ 0x243c, /* U+00243c: */
+ 0x243d, /* U+00243d: */
+ 0x243e, /* U+00243e: */
+ 0x243f, /* U+00243f: */
+ 0x2440, /* U+002440: OCR HOOK*/
+ 0x2441, /* U+002441: OCR CHAIR*/
+ 0x2442, /* U+002442: OCR FORK*/
+ 0x2443, /* U+002443: OCR INVERTED FORK*/
+ 0x2444, /* U+002444: OCR BELT BUCKLE*/
+ 0x2445, /* U+002445: OCR BOW TIE*/
+ 0x2446, /* U+002446: OCR BRANCH BANK IDENTIFICATION*/
+ 0x2447, /* U+002447: OCR AMOUNT OF CHECK*/
+ 0x2448, /* U+002448: OCR DASH*/
+ 0x2449, /* U+002449: OCR CUSTOMER ACCOUNT NUMBER*/
+ 0x244a, /* U+00244a: OCR DOUBLE BACKSLASH*/
+ 0x244b, /* U+00244b: */
+ 0x244c, /* U+00244c: */
+ 0x244d, /* U+00244d: */
+ 0x244e, /* U+00244e: */
+ 0x244f, /* U+00244f: */
+ 0x2450, /* U+002450: */
+ 0x2451, /* U+002451: */
+ 0x2452, /* U+002452: */
+ 0x2453, /* U+002453: */
+ 0x2454, /* U+002454: */
+ 0x2455, /* U+002455: */
+ 0x2456, /* U+002456: */
+ 0x2457, /* U+002457: */
+ 0x2458, /* U+002458: */
+ 0x2459, /* U+002459: */
+ 0x245a, /* U+00245a: */
+ 0x245b, /* U+00245b: */
+ 0x245c, /* U+00245c: */
+ 0x245d, /* U+00245d: */
+ 0x245e, /* U+00245e: */
+ 0x245f, /* U+00245f: */
+ 0x2460, /* U+002460: CIRCLED DIGIT ONE*/
+ 0x2461, /* U+002461: CIRCLED DIGIT TWO*/
+ 0x2462, /* U+002462: CIRCLED DIGIT THREE*/
+ 0x2463, /* U+002463: CIRCLED DIGIT FOUR*/
+ 0x2464, /* U+002464: CIRCLED DIGIT FIVE*/
+ 0x2465, /* U+002465: CIRCLED DIGIT SIX*/
+ 0x2466, /* U+002466: CIRCLED DIGIT SEVEN*/
+ 0x2467, /* U+002467: CIRCLED DIGIT EIGHT*/
+ 0x2468, /* U+002468: CIRCLED DIGIT NINE*/
+ 0x2469, /* U+002469: CIRCLED NUMBER TEN*/
+ 0x246a, /* U+00246a: CIRCLED NUMBER ELEVEN*/
+ 0x246b, /* U+00246b: CIRCLED NUMBER TWELVE*/
+ 0x246c, /* U+00246c: CIRCLED NUMBER THIRTEEN*/
+ 0x246d, /* U+00246d: CIRCLED NUMBER FOURTEEN*/
+ 0x246e, /* U+00246e: CIRCLED NUMBER FIFTEEN*/
+ 0x246f, /* U+00246f: CIRCLED NUMBER SIXTEEN*/
+ 0x2470, /* U+002470: CIRCLED NUMBER SEVENTEEN*/
+ 0x2471, /* U+002471: CIRCLED NUMBER EIGHTEEN*/
+ 0x2472, /* U+002472: CIRCLED NUMBER NINETEEN*/
+ 0x2473, /* U+002473: CIRCLED NUMBER TWENTY*/
+ 0x2474, /* U+002474: PARENTHESIZED DIGIT ONE*/
+ 0x2475, /* U+002475: PARENTHESIZED DIGIT TWO*/
+ 0x2476, /* U+002476: PARENTHESIZED DIGIT THREE*/
+ 0x2477, /* U+002477: PARENTHESIZED DIGIT FOUR*/
+ 0x2478, /* U+002478: PARENTHESIZED DIGIT FIVE*/
+ 0x2479, /* U+002479: PARENTHESIZED DIGIT SIX*/
+ 0x247a, /* U+00247a: PARENTHESIZED DIGIT SEVEN*/
+ 0x247b, /* U+00247b: PARENTHESIZED DIGIT EIGHT*/
+ 0x247c, /* U+00247c: PARENTHESIZED DIGIT NINE*/
+ 0x247d, /* U+00247d: PARENTHESIZED NUMBER TEN*/
+ 0x247e, /* U+00247e: PARENTHESIZED NUMBER ELEVEN*/
+ 0x247f, /* U+00247f: PARENTHESIZED NUMBER TWELVE*/
+ 0x2480, /* U+002480: PARENTHESIZED NUMBER THIRTEEN*/
+ 0x2481, /* U+002481: PARENTHESIZED NUMBER FOURTEEN*/
+ 0x2482, /* U+002482: PARENTHESIZED NUMBER FIFTEEN*/
+ 0x2483, /* U+002483: PARENTHESIZED NUMBER SIXTEEN*/
+ 0x2484, /* U+002484: PARENTHESIZED NUMBER SEVENTEEN*/
+ 0x2485, /* U+002485: PARENTHESIZED NUMBER EIGHTEEN*/
+ 0x2486, /* U+002486: PARENTHESIZED NUMBER NINETEEN*/
+ 0x2487, /* U+002487: PARENTHESIZED NUMBER TWENTY*/
+ 0x2488, /* U+002488: DIGIT ONE FULL STOP*/
+ 0x2489, /* U+002489: DIGIT TWO FULL STOP*/
+ 0x248a, /* U+00248a: DIGIT THREE FULL STOP*/
+ 0x248b, /* U+00248b: DIGIT FOUR FULL STOP*/
+ 0x248c, /* U+00248c: DIGIT FIVE FULL STOP*/
+ 0x248d, /* U+00248d: DIGIT SIX FULL STOP*/
+ 0x248e, /* U+00248e: DIGIT SEVEN FULL STOP*/
+ 0x248f, /* U+00248f: DIGIT EIGHT FULL STOP*/
+ 0x2490, /* U+002490: DIGIT NINE FULL STOP*/
+ 0x2491, /* U+002491: NUMBER TEN FULL STOP*/
+ 0x2492, /* U+002492: NUMBER ELEVEN FULL STOP*/
+ 0x2493, /* U+002493: NUMBER TWELVE FULL STOP*/
+ 0x2494, /* U+002494: NUMBER THIRTEEN FULL STOP*/
+ 0x2495, /* U+002495: NUMBER FOURTEEN FULL STOP*/
+ 0x2496, /* U+002496: NUMBER FIFTEEN FULL STOP*/
+ 0x2497, /* U+002497: NUMBER SIXTEEN FULL STOP*/
+ 0x2498, /* U+002498: NUMBER SEVENTEEN FULL STOP*/
+ 0x2499, /* U+002499: NUMBER EIGHTEEN FULL STOP*/
+ 0x249a, /* U+00249a: NUMBER NINETEEN FULL STOP*/
+ 0x249b, /* U+00249b: NUMBER TWENTY FULL STOP*/
+ 0x249c, /* U+00249c: PARENTHESIZED LATIN SMALL LETTER A*/
+ 0x249d, /* U+00249d: PARENTHESIZED LATIN SMALL LETTER B*/
+ 0x249e, /* U+00249e: PARENTHESIZED LATIN SMALL LETTER C*/
+ 0x249f, /* U+00249f: PARENTHESIZED LATIN SMALL LETTER D*/
+ 0x24a0, /* U+0024a0: PARENTHESIZED LATIN SMALL LETTER E*/
+ 0x24a1, /* U+0024a1: PARENTHESIZED LATIN SMALL LETTER F*/
+ 0x24a2, /* U+0024a2: PARENTHESIZED LATIN SMALL LETTER G*/
+ 0x24a3, /* U+0024a3: PARENTHESIZED LATIN SMALL LETTER H*/
+ 0x24a4, /* U+0024a4: PARENTHESIZED LATIN SMALL LETTER I*/
+ 0x24a5, /* U+0024a5: PARENTHESIZED LATIN SMALL LETTER J*/
+ 0x24a6, /* U+0024a6: PARENTHESIZED LATIN SMALL LETTER K*/
+ 0x24a7, /* U+0024a7: PARENTHESIZED LATIN SMALL LETTER L*/
+ 0x24a8, /* U+0024a8: PARENTHESIZED LATIN SMALL LETTER M*/
+ 0x24a9, /* U+0024a9: PARENTHESIZED LATIN SMALL LETTER N*/
+ 0x24aa, /* U+0024aa: PARENTHESIZED LATIN SMALL LETTER O*/
+ 0x24ab, /* U+0024ab: PARENTHESIZED LATIN SMALL LETTER P*/
+ 0x24ac, /* U+0024ac: PARENTHESIZED LATIN SMALL LETTER Q*/
+ 0x24ad, /* U+0024ad: PARENTHESIZED LATIN SMALL LETTER R*/
+ 0x24ae, /* U+0024ae: PARENTHESIZED LATIN SMALL LETTER S*/
+ 0x24af, /* U+0024af: PARENTHESIZED LATIN SMALL LETTER T*/
+ 0x24b0, /* U+0024b0: PARENTHESIZED LATIN SMALL LETTER U*/
+ 0x24b1, /* U+0024b1: PARENTHESIZED LATIN SMALL LETTER V*/
+ 0x24b2, /* U+0024b2: PARENTHESIZED LATIN SMALL LETTER W*/
+ 0x24b3, /* U+0024b3: PARENTHESIZED LATIN SMALL LETTER X*/
+ 0x24b4, /* U+0024b4: PARENTHESIZED LATIN SMALL LETTER Y*/
+ 0x24b5, /* U+0024b5: PARENTHESIZED LATIN SMALL LETTER Z*/
+ 0x24d0, /* U+0024b6: CIRCLED LATIN CAPITAL LETTER A*/
+ 0x24d1, /* U+0024b7: CIRCLED LATIN CAPITAL LETTER B*/
+ 0x24d2, /* U+0024b8: CIRCLED LATIN CAPITAL LETTER C*/
+ 0x24d3, /* U+0024b9: CIRCLED LATIN CAPITAL LETTER D*/
+ 0x24d4, /* U+0024ba: CIRCLED LATIN CAPITAL LETTER E*/
+ 0x24d5, /* U+0024bb: CIRCLED LATIN CAPITAL LETTER F*/
+ 0x24d6, /* U+0024bc: CIRCLED LATIN CAPITAL LETTER G*/
+ 0x24d7, /* U+0024bd: CIRCLED LATIN CAPITAL LETTER H*/
+ 0x24d8, /* U+0024be: CIRCLED LATIN CAPITAL LETTER I*/
+ 0x24d9, /* U+0024bf: CIRCLED LATIN CAPITAL LETTER J*/
+ 0x24da, /* U+0024c0: CIRCLED LATIN CAPITAL LETTER K*/
+ 0x24db, /* U+0024c1: CIRCLED LATIN CAPITAL LETTER L*/
+ 0x24dc, /* U+0024c2: CIRCLED LATIN CAPITAL LETTER M*/
+ 0x24dd, /* U+0024c3: CIRCLED LATIN CAPITAL LETTER N*/
+ 0x24de, /* U+0024c4: CIRCLED LATIN CAPITAL LETTER O*/
+ 0x24df, /* U+0024c5: CIRCLED LATIN CAPITAL LETTER P*/
+ 0x24e0, /* U+0024c6: CIRCLED LATIN CAPITAL LETTER Q*/
+ 0x24e1, /* U+0024c7: CIRCLED LATIN CAPITAL LETTER R*/
+ 0x24e2, /* U+0024c8: CIRCLED LATIN CAPITAL LETTER S*/
+ 0x24e3, /* U+0024c9: CIRCLED LATIN CAPITAL LETTER T*/
+ 0x24e4, /* U+0024ca: CIRCLED LATIN CAPITAL LETTER U*/
+ 0x24e5, /* U+0024cb: CIRCLED LATIN CAPITAL LETTER V*/
+ 0x24e6, /* U+0024cc: CIRCLED LATIN CAPITAL LETTER W*/
+ 0x24e7, /* U+0024cd: CIRCLED LATIN CAPITAL LETTER X*/
+ 0x24e8, /* U+0024ce: CIRCLED LATIN CAPITAL LETTER Y*/
+ 0x24e9, /* U+0024cf: CIRCLED LATIN CAPITAL LETTER Z*/
+};
+
+static const RTUNICP g_afRTUniLower0x002c00[] =
+{
+ 0x2c30, /* U+002c00: GLAGOLITIC CAPITAL LETTER AZU*/
+ 0x2c31, /* U+002c01: GLAGOLITIC CAPITAL LETTER BUKY*/
+ 0x2c32, /* U+002c02: GLAGOLITIC CAPITAL LETTER VEDE*/
+ 0x2c33, /* U+002c03: GLAGOLITIC CAPITAL LETTER GLAGOLI*/
+ 0x2c34, /* U+002c04: GLAGOLITIC CAPITAL LETTER DOBRO*/
+ 0x2c35, /* U+002c05: GLAGOLITIC CAPITAL LETTER YESTU*/
+ 0x2c36, /* U+002c06: GLAGOLITIC CAPITAL LETTER ZHIVETE*/
+ 0x2c37, /* U+002c07: GLAGOLITIC CAPITAL LETTER DZELO*/
+ 0x2c38, /* U+002c08: GLAGOLITIC CAPITAL LETTER ZEMLJA*/
+ 0x2c39, /* U+002c09: GLAGOLITIC CAPITAL LETTER IZHE*/
+ 0x2c3a, /* U+002c0a: GLAGOLITIC CAPITAL LETTER INITIAL IZHE*/
+ 0x2c3b, /* U+002c0b: GLAGOLITIC CAPITAL LETTER I*/
+ 0x2c3c, /* U+002c0c: GLAGOLITIC CAPITAL LETTER DJERVI*/
+ 0x2c3d, /* U+002c0d: GLAGOLITIC CAPITAL LETTER KAKO*/
+ 0x2c3e, /* U+002c0e: GLAGOLITIC CAPITAL LETTER LJUDIJE*/
+ 0x2c3f, /* U+002c0f: GLAGOLITIC CAPITAL LETTER MYSLITE*/
+ 0x2c40, /* U+002c10: GLAGOLITIC CAPITAL LETTER NASHI*/
+ 0x2c41, /* U+002c11: GLAGOLITIC CAPITAL LETTER ONU*/
+ 0x2c42, /* U+002c12: GLAGOLITIC CAPITAL LETTER POKOJI*/
+ 0x2c43, /* U+002c13: GLAGOLITIC CAPITAL LETTER RITSI*/
+ 0x2c44, /* U+002c14: GLAGOLITIC CAPITAL LETTER SLOVO*/
+ 0x2c45, /* U+002c15: GLAGOLITIC CAPITAL LETTER TVRIDO*/
+ 0x2c46, /* U+002c16: GLAGOLITIC CAPITAL LETTER UKU*/
+ 0x2c47, /* U+002c17: GLAGOLITIC CAPITAL LETTER FRITU*/
+ 0x2c48, /* U+002c18: GLAGOLITIC CAPITAL LETTER HERU*/
+ 0x2c49, /* U+002c19: GLAGOLITIC CAPITAL LETTER OTU*/
+ 0x2c4a, /* U+002c1a: GLAGOLITIC CAPITAL LETTER PE*/
+ 0x2c4b, /* U+002c1b: GLAGOLITIC CAPITAL LETTER SHTA*/
+ 0x2c4c, /* U+002c1c: GLAGOLITIC CAPITAL LETTER TSI*/
+ 0x2c4d, /* U+002c1d: GLAGOLITIC CAPITAL LETTER CHRIVI*/
+ 0x2c4e, /* U+002c1e: GLAGOLITIC CAPITAL LETTER SHA*/
+ 0x2c4f, /* U+002c1f: GLAGOLITIC CAPITAL LETTER YERU*/
+ 0x2c50, /* U+002c20: GLAGOLITIC CAPITAL LETTER YERI*/
+ 0x2c51, /* U+002c21: GLAGOLITIC CAPITAL LETTER YATI*/
+ 0x2c52, /* U+002c22: GLAGOLITIC CAPITAL LETTER SPIDERY HA*/
+ 0x2c53, /* U+002c23: GLAGOLITIC CAPITAL LETTER YU*/
+ 0x2c54, /* U+002c24: GLAGOLITIC CAPITAL LETTER SMALL YUS*/
+ 0x2c55, /* U+002c25: GLAGOLITIC CAPITAL LETTER SMALL YUS WITH TAIL*/
+ 0x2c56, /* U+002c26: GLAGOLITIC CAPITAL LETTER YO*/
+ 0x2c57, /* U+002c27: GLAGOLITIC CAPITAL LETTER IOTATED SMALL YUS*/
+ 0x2c58, /* U+002c28: GLAGOLITIC CAPITAL LETTER BIG YUS*/
+ 0x2c59, /* U+002c29: GLAGOLITIC CAPITAL LETTER IOTATED BIG YUS*/
+ 0x2c5a, /* U+002c2a: GLAGOLITIC CAPITAL LETTER FITA*/
+ 0x2c5b, /* U+002c2b: GLAGOLITIC CAPITAL LETTER IZHITSA*/
+ 0x2c5c, /* U+002c2c: GLAGOLITIC CAPITAL LETTER SHTAPIC*/
+ 0x2c5d, /* U+002c2d: GLAGOLITIC CAPITAL LETTER TROKUTASTI A*/
+ 0x2c5e, /* U+002c2e: GLAGOLITIC CAPITAL LETTER LATINATE MYSLITE*/
+ 0x2c2f, /* U+002c2f: */
+ 0x2c30, /* U+002c30: GLAGOLITIC SMALL LETTER AZU*/
+ 0x2c31, /* U+002c31: GLAGOLITIC SMALL LETTER BUKY*/
+ 0x2c32, /* U+002c32: GLAGOLITIC SMALL LETTER VEDE*/
+ 0x2c33, /* U+002c33: GLAGOLITIC SMALL LETTER GLAGOLI*/
+ 0x2c34, /* U+002c34: GLAGOLITIC SMALL LETTER DOBRO*/
+ 0x2c35, /* U+002c35: GLAGOLITIC SMALL LETTER YESTU*/
+ 0x2c36, /* U+002c36: GLAGOLITIC SMALL LETTER ZHIVETE*/
+ 0x2c37, /* U+002c37: GLAGOLITIC SMALL LETTER DZELO*/
+ 0x2c38, /* U+002c38: GLAGOLITIC SMALL LETTER ZEMLJA*/
+ 0x2c39, /* U+002c39: GLAGOLITIC SMALL LETTER IZHE*/
+ 0x2c3a, /* U+002c3a: GLAGOLITIC SMALL LETTER INITIAL IZHE*/
+ 0x2c3b, /* U+002c3b: GLAGOLITIC SMALL LETTER I*/
+ 0x2c3c, /* U+002c3c: GLAGOLITIC SMALL LETTER DJERVI*/
+ 0x2c3d, /* U+002c3d: GLAGOLITIC SMALL LETTER KAKO*/
+ 0x2c3e, /* U+002c3e: GLAGOLITIC SMALL LETTER LJUDIJE*/
+ 0x2c3f, /* U+002c3f: GLAGOLITIC SMALL LETTER MYSLITE*/
+ 0x2c40, /* U+002c40: GLAGOLITIC SMALL LETTER NASHI*/
+ 0x2c41, /* U+002c41: GLAGOLITIC SMALL LETTER ONU*/
+ 0x2c42, /* U+002c42: GLAGOLITIC SMALL LETTER POKOJI*/
+ 0x2c43, /* U+002c43: GLAGOLITIC SMALL LETTER RITSI*/
+ 0x2c44, /* U+002c44: GLAGOLITIC SMALL LETTER SLOVO*/
+ 0x2c45, /* U+002c45: GLAGOLITIC SMALL LETTER TVRIDO*/
+ 0x2c46, /* U+002c46: GLAGOLITIC SMALL LETTER UKU*/
+ 0x2c47, /* U+002c47: GLAGOLITIC SMALL LETTER FRITU*/
+ 0x2c48, /* U+002c48: GLAGOLITIC SMALL LETTER HERU*/
+ 0x2c49, /* U+002c49: GLAGOLITIC SMALL LETTER OTU*/
+ 0x2c4a, /* U+002c4a: GLAGOLITIC SMALL LETTER PE*/
+ 0x2c4b, /* U+002c4b: GLAGOLITIC SMALL LETTER SHTA*/
+ 0x2c4c, /* U+002c4c: GLAGOLITIC SMALL LETTER TSI*/
+ 0x2c4d, /* U+002c4d: GLAGOLITIC SMALL LETTER CHRIVI*/
+ 0x2c4e, /* U+002c4e: GLAGOLITIC SMALL LETTER SHA*/
+ 0x2c4f, /* U+002c4f: GLAGOLITIC SMALL LETTER YERU*/
+ 0x2c50, /* U+002c50: GLAGOLITIC SMALL LETTER YERI*/
+ 0x2c51, /* U+002c51: GLAGOLITIC SMALL LETTER YATI*/
+ 0x2c52, /* U+002c52: GLAGOLITIC SMALL LETTER SPIDERY HA*/
+ 0x2c53, /* U+002c53: GLAGOLITIC SMALL LETTER YU*/
+ 0x2c54, /* U+002c54: GLAGOLITIC SMALL LETTER SMALL YUS*/
+ 0x2c55, /* U+002c55: GLAGOLITIC SMALL LETTER SMALL YUS WITH TAIL*/
+ 0x2c56, /* U+002c56: GLAGOLITIC SMALL LETTER YO*/
+ 0x2c57, /* U+002c57: GLAGOLITIC SMALL LETTER IOTATED SMALL YUS*/
+ 0x2c58, /* U+002c58: GLAGOLITIC SMALL LETTER BIG YUS*/
+ 0x2c59, /* U+002c59: GLAGOLITIC SMALL LETTER IOTATED BIG YUS*/
+ 0x2c5a, /* U+002c5a: GLAGOLITIC SMALL LETTER FITA*/
+ 0x2c5b, /* U+002c5b: GLAGOLITIC SMALL LETTER IZHITSA*/
+ 0x2c5c, /* U+002c5c: GLAGOLITIC SMALL LETTER SHTAPIC*/
+ 0x2c5d, /* U+002c5d: GLAGOLITIC SMALL LETTER TROKUTASTI A*/
+ 0x2c5e, /* U+002c5e: GLAGOLITIC SMALL LETTER LATINATE MYSLITE*/
+ 0x2c5f, /* U+002c5f: */
+ 0x2c61, /* U+002c60: LATIN CAPITAL LETTER L WITH DOUBLE BAR*/
+ 0x2c61, /* U+002c61: LATIN SMALL LETTER L WITH DOUBLE BAR*/
+ 0x26b, /* U+002c62: LATIN CAPITAL LETTER L WITH MIDDLE TILDE*/
+ 0x1d7d, /* U+002c63: LATIN CAPITAL LETTER P WITH STROKE*/
+ 0x27d, /* U+002c64: LATIN CAPITAL LETTER R WITH TAIL*/
+ 0x2c65, /* U+002c65: LATIN SMALL LETTER A WITH STROKE*/
+ 0x2c66, /* U+002c66: LATIN SMALL LETTER T WITH DIAGONAL STROKE*/
+ 0x2c68, /* U+002c67: LATIN CAPITAL LETTER H WITH DESCENDER*/
+ 0x2c68, /* U+002c68: LATIN SMALL LETTER H WITH DESCENDER*/
+ 0x2c6a, /* U+002c69: LATIN CAPITAL LETTER K WITH DESCENDER*/
+ 0x2c6a, /* U+002c6a: LATIN SMALL LETTER K WITH DESCENDER*/
+ 0x2c6c, /* U+002c6b: LATIN CAPITAL LETTER Z WITH DESCENDER*/
+ 0x2c6c, /* U+002c6c: LATIN SMALL LETTER Z WITH DESCENDER*/
+ 0x251, /* U+002c6d: LATIN CAPITAL LETTER ALPHA*/
+ 0x271, /* U+002c6e: LATIN CAPITAL LETTER M WITH HOOK*/
+ 0x250, /* U+002c6f: LATIN CAPITAL LETTER TURNED A*/
+ 0x252, /* U+002c70: LATIN CAPITAL LETTER TURNED ALPHA*/
+ 0x2c71, /* U+002c71: LATIN SMALL LETTER V WITH RIGHT HOOK*/
+ 0x2c73, /* U+002c72: LATIN CAPITAL LETTER W WITH HOOK*/
+ 0x2c73, /* U+002c73: LATIN SMALL LETTER W WITH HOOK*/
+ 0x2c74, /* U+002c74: LATIN SMALL LETTER V WITH CURL*/
+ 0x2c76, /* U+002c75: LATIN CAPITAL LETTER HALF H*/
+ 0x2c76, /* U+002c76: LATIN SMALL LETTER HALF H*/
+ 0x2c77, /* U+002c77: LATIN SMALL LETTER TAILLESS PHI*/
+ 0x2c78, /* U+002c78: LATIN SMALL LETTER E WITH NOTCH*/
+ 0x2c79, /* U+002c79: LATIN SMALL LETTER TURNED R WITH TAIL*/
+ 0x2c7a, /* U+002c7a: LATIN SMALL LETTER O WITH LOW RING INSIDE*/
+ 0x2c7b, /* U+002c7b: LATIN LETTER SMALL CAPITAL TURNED E*/
+ 0x2c7c, /* U+002c7c: LATIN SUBSCRIPT SMALL LETTER J*/
+ 0x2c7d, /* U+002c7d: MODIFIER LETTER CAPITAL V*/
+ 0x23f, /* U+002c7e: LATIN CAPITAL LETTER S WITH SWASH TAIL*/
+ 0x240, /* U+002c7f: LATIN CAPITAL LETTER Z WITH SWASH TAIL*/
+ 0x2c81, /* U+002c80: COPTIC CAPITAL LETTER ALFA*/
+ 0x2c81, /* U+002c81: COPTIC SMALL LETTER ALFA*/
+ 0x2c83, /* U+002c82: COPTIC CAPITAL LETTER VIDA*/
+ 0x2c83, /* U+002c83: COPTIC SMALL LETTER VIDA*/
+ 0x2c85, /* U+002c84: COPTIC CAPITAL LETTER GAMMA*/
+ 0x2c85, /* U+002c85: COPTIC SMALL LETTER GAMMA*/
+ 0x2c87, /* U+002c86: COPTIC CAPITAL LETTER DALDA*/
+ 0x2c87, /* U+002c87: COPTIC SMALL LETTER DALDA*/
+ 0x2c89, /* U+002c88: COPTIC CAPITAL LETTER EIE*/
+ 0x2c89, /* U+002c89: COPTIC SMALL LETTER EIE*/
+ 0x2c8b, /* U+002c8a: COPTIC CAPITAL LETTER SOU*/
+ 0x2c8b, /* U+002c8b: COPTIC SMALL LETTER SOU*/
+ 0x2c8d, /* U+002c8c: COPTIC CAPITAL LETTER ZATA*/
+ 0x2c8d, /* U+002c8d: COPTIC SMALL LETTER ZATA*/
+ 0x2c8f, /* U+002c8e: COPTIC CAPITAL LETTER HATE*/
+ 0x2c8f, /* U+002c8f: COPTIC SMALL LETTER HATE*/
+ 0x2c91, /* U+002c90: COPTIC CAPITAL LETTER THETHE*/
+ 0x2c91, /* U+002c91: COPTIC SMALL LETTER THETHE*/
+ 0x2c93, /* U+002c92: COPTIC CAPITAL LETTER IAUDA*/
+ 0x2c93, /* U+002c93: COPTIC SMALL LETTER IAUDA*/
+ 0x2c95, /* U+002c94: COPTIC CAPITAL LETTER KAPA*/
+ 0x2c95, /* U+002c95: COPTIC SMALL LETTER KAPA*/
+ 0x2c97, /* U+002c96: COPTIC CAPITAL LETTER LAULA*/
+ 0x2c97, /* U+002c97: COPTIC SMALL LETTER LAULA*/
+ 0x2c99, /* U+002c98: COPTIC CAPITAL LETTER MI*/
+ 0x2c99, /* U+002c99: COPTIC SMALL LETTER MI*/
+ 0x2c9b, /* U+002c9a: COPTIC CAPITAL LETTER NI*/
+ 0x2c9b, /* U+002c9b: COPTIC SMALL LETTER NI*/
+ 0x2c9d, /* U+002c9c: COPTIC CAPITAL LETTER KSI*/
+ 0x2c9d, /* U+002c9d: COPTIC SMALL LETTER KSI*/
+ 0x2c9f, /* U+002c9e: COPTIC CAPITAL LETTER O*/
+ 0x2c9f, /* U+002c9f: COPTIC SMALL LETTER O*/
+ 0x2ca1, /* U+002ca0: COPTIC CAPITAL LETTER PI*/
+ 0x2ca1, /* U+002ca1: COPTIC SMALL LETTER PI*/
+ 0x2ca3, /* U+002ca2: COPTIC CAPITAL LETTER RO*/
+ 0x2ca3, /* U+002ca3: COPTIC SMALL LETTER RO*/
+ 0x2ca5, /* U+002ca4: COPTIC CAPITAL LETTER SIMA*/
+ 0x2ca5, /* U+002ca5: COPTIC SMALL LETTER SIMA*/
+ 0x2ca7, /* U+002ca6: COPTIC CAPITAL LETTER TAU*/
+ 0x2ca7, /* U+002ca7: COPTIC SMALL LETTER TAU*/
+ 0x2ca9, /* U+002ca8: COPTIC CAPITAL LETTER UA*/
+ 0x2ca9, /* U+002ca9: COPTIC SMALL LETTER UA*/
+ 0x2cab, /* U+002caa: COPTIC CAPITAL LETTER FI*/
+ 0x2cab, /* U+002cab: COPTIC SMALL LETTER FI*/
+ 0x2cad, /* U+002cac: COPTIC CAPITAL LETTER KHI*/
+ 0x2cad, /* U+002cad: COPTIC SMALL LETTER KHI*/
+ 0x2caf, /* U+002cae: COPTIC CAPITAL LETTER PSI*/
+ 0x2caf, /* U+002caf: COPTIC SMALL LETTER PSI*/
+ 0x2cb1, /* U+002cb0: COPTIC CAPITAL LETTER OOU*/
+ 0x2cb1, /* U+002cb1: COPTIC SMALL LETTER OOU*/
+ 0x2cb3, /* U+002cb2: COPTIC CAPITAL LETTER DIALECT-P ALEF*/
+ 0x2cb3, /* U+002cb3: COPTIC SMALL LETTER DIALECT-P ALEF*/
+ 0x2cb5, /* U+002cb4: COPTIC CAPITAL LETTER OLD COPTIC AIN*/
+ 0x2cb5, /* U+002cb5: COPTIC SMALL LETTER OLD COPTIC AIN*/
+ 0x2cb7, /* U+002cb6: COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE*/
+ 0x2cb7, /* U+002cb7: COPTIC SMALL LETTER CRYPTOGRAMMIC EIE*/
+ 0x2cb9, /* U+002cb8: COPTIC CAPITAL LETTER DIALECT-P KAPA*/
+ 0x2cb9, /* U+002cb9: COPTIC SMALL LETTER DIALECT-P KAPA*/
+ 0x2cbb, /* U+002cba: COPTIC CAPITAL LETTER DIALECT-P NI*/
+ 0x2cbb, /* U+002cbb: COPTIC SMALL LETTER DIALECT-P NI*/
+ 0x2cbd, /* U+002cbc: COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI*/
+ 0x2cbd, /* U+002cbd: COPTIC SMALL LETTER CRYPTOGRAMMIC NI*/
+ 0x2cbf, /* U+002cbe: COPTIC CAPITAL LETTER OLD COPTIC OOU*/
+ 0x2cbf, /* U+002cbf: COPTIC SMALL LETTER OLD COPTIC OOU*/
+ 0x2cc1, /* U+002cc0: COPTIC CAPITAL LETTER SAMPI*/
+ 0x2cc1, /* U+002cc1: COPTIC SMALL LETTER SAMPI*/
+ 0x2cc3, /* U+002cc2: COPTIC CAPITAL LETTER CROSSED SHEI*/
+ 0x2cc3, /* U+002cc3: COPTIC SMALL LETTER CROSSED SHEI*/
+ 0x2cc5, /* U+002cc4: COPTIC CAPITAL LETTER OLD COPTIC SHEI*/
+ 0x2cc5, /* U+002cc5: COPTIC SMALL LETTER OLD COPTIC SHEI*/
+ 0x2cc7, /* U+002cc6: COPTIC CAPITAL LETTER OLD COPTIC ESH*/
+ 0x2cc7, /* U+002cc7: COPTIC SMALL LETTER OLD COPTIC ESH*/
+ 0x2cc9, /* U+002cc8: COPTIC CAPITAL LETTER AKHMIMIC KHEI*/
+ 0x2cc9, /* U+002cc9: COPTIC SMALL LETTER AKHMIMIC KHEI*/
+ 0x2ccb, /* U+002cca: COPTIC CAPITAL LETTER DIALECT-P HORI*/
+ 0x2ccb, /* U+002ccb: COPTIC SMALL LETTER DIALECT-P HORI*/
+ 0x2ccd, /* U+002ccc: COPTIC CAPITAL LETTER OLD COPTIC HORI*/
+ 0x2ccd, /* U+002ccd: COPTIC SMALL LETTER OLD COPTIC HORI*/
+ 0x2ccf, /* U+002cce: COPTIC CAPITAL LETTER OLD COPTIC HA*/
+ 0x2ccf, /* U+002ccf: COPTIC SMALL LETTER OLD COPTIC HA*/
+ 0x2cd1, /* U+002cd0: COPTIC CAPITAL LETTER L-SHAPED HA*/
+ 0x2cd1, /* U+002cd1: COPTIC SMALL LETTER L-SHAPED HA*/
+ 0x2cd3, /* U+002cd2: COPTIC CAPITAL LETTER OLD COPTIC HEI*/
+ 0x2cd3, /* U+002cd3: COPTIC SMALL LETTER OLD COPTIC HEI*/
+ 0x2cd5, /* U+002cd4: COPTIC CAPITAL LETTER OLD COPTIC HAT*/
+ 0x2cd5, /* U+002cd5: COPTIC SMALL LETTER OLD COPTIC HAT*/
+ 0x2cd7, /* U+002cd6: COPTIC CAPITAL LETTER OLD COPTIC GANGIA*/
+ 0x2cd7, /* U+002cd7: COPTIC SMALL LETTER OLD COPTIC GANGIA*/
+ 0x2cd9, /* U+002cd8: COPTIC CAPITAL LETTER OLD COPTIC DJA*/
+ 0x2cd9, /* U+002cd9: COPTIC SMALL LETTER OLD COPTIC DJA*/
+ 0x2cdb, /* U+002cda: COPTIC CAPITAL LETTER OLD COPTIC SHIMA*/
+ 0x2cdb, /* U+002cdb: COPTIC SMALL LETTER OLD COPTIC SHIMA*/
+ 0x2cdd, /* U+002cdc: COPTIC CAPITAL LETTER OLD NUBIAN SHIMA*/
+ 0x2cdd, /* U+002cdd: COPTIC SMALL LETTER OLD NUBIAN SHIMA*/
+ 0x2cdf, /* U+002cde: COPTIC CAPITAL LETTER OLD NUBIAN NGI*/
+ 0x2cdf, /* U+002cdf: COPTIC SMALL LETTER OLD NUBIAN NGI*/
+ 0x2ce1, /* U+002ce0: COPTIC CAPITAL LETTER OLD NUBIAN NYI*/
+ 0x2ce1, /* U+002ce1: COPTIC SMALL LETTER OLD NUBIAN NYI*/
+ 0x2ce3, /* U+002ce2: COPTIC CAPITAL LETTER OLD NUBIAN WAU*/
+ 0x2ce3, /* U+002ce3: COPTIC SMALL LETTER OLD NUBIAN WAU*/
+ 0x2ce4, /* U+002ce4: COPTIC SYMBOL KAI*/
+ 0x2ce5, /* U+002ce5: COPTIC SYMBOL MI RO*/
+ 0x2ce6, /* U+002ce6: COPTIC SYMBOL PI RO*/
+ 0x2ce7, /* U+002ce7: COPTIC SYMBOL STAUROS*/
+ 0x2ce8, /* U+002ce8: COPTIC SYMBOL TAU RO*/
+ 0x2ce9, /* U+002ce9: COPTIC SYMBOL KHI RO*/
+ 0x2cea, /* U+002cea: COPTIC SYMBOL SHIMA SIMA*/
+ 0x2cec, /* U+002ceb: COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI*/
+ 0x2cec, /* U+002cec: COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI*/
+ 0x2cee, /* U+002ced: COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA*/
+ 0x2cee, /* U+002cee: COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA*/
+ 0x2cef, /* U+002cef: COPTIC COMBINING NI ABOVE*/
+ 0x2cf0, /* U+002cf0: COPTIC COMBINING SPIRITUS ASPER*/
+ 0x2cf1, /* U+002cf1: COPTIC COMBINING SPIRITUS LENIS*/
+ 0x2cf3, /* U+002cf2: COPTIC CAPITAL LETTER BOHAIRIC KHEI*/
+};
+
+static const RTUNICP g_afRTUniLower0x00a640[] =
+{
+ 0xa641, /* U+00a640: CYRILLIC CAPITAL LETTER ZEMLYA*/
+ 0xa641, /* U+00a641: CYRILLIC SMALL LETTER ZEMLYA*/
+ 0xa643, /* U+00a642: CYRILLIC CAPITAL LETTER DZELO*/
+ 0xa643, /* U+00a643: CYRILLIC SMALL LETTER DZELO*/
+ 0xa645, /* U+00a644: CYRILLIC CAPITAL LETTER REVERSED DZE*/
+ 0xa645, /* U+00a645: CYRILLIC SMALL LETTER REVERSED DZE*/
+ 0xa647, /* U+00a646: CYRILLIC CAPITAL LETTER IOTA*/
+ 0xa647, /* U+00a647: CYRILLIC SMALL LETTER IOTA*/
+ 0xa649, /* U+00a648: CYRILLIC CAPITAL LETTER DJERV*/
+ 0xa649, /* U+00a649: CYRILLIC SMALL LETTER DJERV*/
+ 0xa64b, /* U+00a64a: CYRILLIC CAPITAL LETTER MONOGRAPH UK*/
+ 0xa64b, /* U+00a64b: CYRILLIC SMALL LETTER MONOGRAPH UK*/
+ 0xa64d, /* U+00a64c: CYRILLIC CAPITAL LETTER BROAD OMEGA*/
+ 0xa64d, /* U+00a64d: CYRILLIC SMALL LETTER BROAD OMEGA*/
+ 0xa64f, /* U+00a64e: CYRILLIC CAPITAL LETTER NEUTRAL YER*/
+ 0xa64f, /* U+00a64f: CYRILLIC SMALL LETTER NEUTRAL YER*/
+ 0xa651, /* U+00a650: CYRILLIC CAPITAL LETTER YERU WITH BACK YER*/
+ 0xa651, /* U+00a651: CYRILLIC SMALL LETTER YERU WITH BACK YER*/
+ 0xa653, /* U+00a652: CYRILLIC CAPITAL LETTER IOTIFIED YAT*/
+ 0xa653, /* U+00a653: CYRILLIC SMALL LETTER IOTIFIED YAT*/
+ 0xa655, /* U+00a654: CYRILLIC CAPITAL LETTER REVERSED YU*/
+ 0xa655, /* U+00a655: CYRILLIC SMALL LETTER REVERSED YU*/
+ 0xa657, /* U+00a656: CYRILLIC CAPITAL LETTER IOTIFIED A*/
+ 0xa657, /* U+00a657: CYRILLIC SMALL LETTER IOTIFIED A*/
+ 0xa659, /* U+00a658: CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS*/
+ 0xa659, /* U+00a659: CYRILLIC SMALL LETTER CLOSED LITTLE YUS*/
+ 0xa65b, /* U+00a65a: CYRILLIC CAPITAL LETTER BLENDED YUS*/
+ 0xa65b, /* U+00a65b: CYRILLIC SMALL LETTER BLENDED YUS*/
+ 0xa65d, /* U+00a65c: CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS*/
+ 0xa65d, /* U+00a65d: CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS*/
+ 0xa65f, /* U+00a65e: CYRILLIC CAPITAL LETTER YN*/
+ 0xa65f, /* U+00a65f: CYRILLIC SMALL LETTER YN*/
+ 0xa661, /* U+00a660: CYRILLIC CAPITAL LETTER REVERSED TSE*/
+ 0xa661, /* U+00a661: CYRILLIC SMALL LETTER REVERSED TSE*/
+ 0xa663, /* U+00a662: CYRILLIC CAPITAL LETTER SOFT DE*/
+ 0xa663, /* U+00a663: CYRILLIC SMALL LETTER SOFT DE*/
+ 0xa665, /* U+00a664: CYRILLIC CAPITAL LETTER SOFT EL*/
+ 0xa665, /* U+00a665: CYRILLIC SMALL LETTER SOFT EL*/
+ 0xa667, /* U+00a666: CYRILLIC CAPITAL LETTER SOFT EM*/
+ 0xa667, /* U+00a667: CYRILLIC SMALL LETTER SOFT EM*/
+ 0xa669, /* U+00a668: CYRILLIC CAPITAL LETTER MONOCULAR O*/
+ 0xa669, /* U+00a669: CYRILLIC SMALL LETTER MONOCULAR O*/
+ 0xa66b, /* U+00a66a: CYRILLIC CAPITAL LETTER BINOCULAR O*/
+ 0xa66b, /* U+00a66b: CYRILLIC SMALL LETTER BINOCULAR O*/
+ 0xa66d, /* U+00a66c: CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O*/
+ 0xa66d, /* U+00a66d: CYRILLIC SMALL LETTER DOUBLE MONOCULAR O*/
+ 0xa66e, /* U+00a66e: CYRILLIC LETTER MULTIOCULAR O*/
+ 0xa66f, /* U+00a66f: COMBINING CYRILLIC VZMET*/
+ 0xa670, /* U+00a670: COMBINING CYRILLIC TEN MILLIONS SIGN*/
+ 0xa671, /* U+00a671: COMBINING CYRILLIC HUNDRED MILLIONS SIGN*/
+ 0xa672, /* U+00a672: COMBINING CYRILLIC THOUSAND MILLIONS SIGN*/
+ 0xa673, /* U+00a673: SLAVONIC ASTERISK*/
+ 0xa674, /* U+00a674: COMBINING CYRILLIC LETTER UKRAINIAN IE*/
+ 0xa675, /* U+00a675: COMBINING CYRILLIC LETTER I*/
+ 0xa676, /* U+00a676: COMBINING CYRILLIC LETTER YI*/
+ 0xa677, /* U+00a677: COMBINING CYRILLIC LETTER U*/
+ 0xa678, /* U+00a678: COMBINING CYRILLIC LETTER HARD SIGN*/
+ 0xa679, /* U+00a679: COMBINING CYRILLIC LETTER YERU*/
+ 0xa67a, /* U+00a67a: COMBINING CYRILLIC LETTER SOFT SIGN*/
+ 0xa67b, /* U+00a67b: COMBINING CYRILLIC LETTER OMEGA*/
+ 0xa67c, /* U+00a67c: COMBINING CYRILLIC KAVYKA*/
+ 0xa67d, /* U+00a67d: COMBINING CYRILLIC PAYEROK*/
+ 0xa67e, /* U+00a67e: CYRILLIC KAVYKA*/
+ 0xa67f, /* U+00a67f: CYRILLIC PAYEROK*/
+ 0xa681, /* U+00a680: CYRILLIC CAPITAL LETTER DWE*/
+ 0xa681, /* U+00a681: CYRILLIC SMALL LETTER DWE*/
+ 0xa683, /* U+00a682: CYRILLIC CAPITAL LETTER DZWE*/
+ 0xa683, /* U+00a683: CYRILLIC SMALL LETTER DZWE*/
+ 0xa685, /* U+00a684: CYRILLIC CAPITAL LETTER ZHWE*/
+ 0xa685, /* U+00a685: CYRILLIC SMALL LETTER ZHWE*/
+ 0xa687, /* U+00a686: CYRILLIC CAPITAL LETTER CCHE*/
+ 0xa687, /* U+00a687: CYRILLIC SMALL LETTER CCHE*/
+ 0xa689, /* U+00a688: CYRILLIC CAPITAL LETTER DZZE*/
+ 0xa689, /* U+00a689: CYRILLIC SMALL LETTER DZZE*/
+ 0xa68b, /* U+00a68a: CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK*/
+ 0xa68b, /* U+00a68b: CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK*/
+ 0xa68d, /* U+00a68c: CYRILLIC CAPITAL LETTER TWE*/
+ 0xa68d, /* U+00a68d: CYRILLIC SMALL LETTER TWE*/
+ 0xa68f, /* U+00a68e: CYRILLIC CAPITAL LETTER TSWE*/
+ 0xa68f, /* U+00a68f: CYRILLIC SMALL LETTER TSWE*/
+ 0xa691, /* U+00a690: CYRILLIC CAPITAL LETTER TSSE*/
+ 0xa691, /* U+00a691: CYRILLIC SMALL LETTER TSSE*/
+ 0xa693, /* U+00a692: CYRILLIC CAPITAL LETTER TCHE*/
+ 0xa693, /* U+00a693: CYRILLIC SMALL LETTER TCHE*/
+ 0xa695, /* U+00a694: CYRILLIC CAPITAL LETTER HWE*/
+ 0xa695, /* U+00a695: CYRILLIC SMALL LETTER HWE*/
+ 0xa697, /* U+00a696: CYRILLIC CAPITAL LETTER SHWE*/
+ 0xa697, /* U+00a697: CYRILLIC SMALL LETTER SHWE*/
+ 0xa698, /* U+00a698: */
+ 0xa699, /* U+00a699: */
+ 0xa69a, /* U+00a69a: */
+ 0xa69b, /* U+00a69b: */
+ 0xa69c, /* U+00a69c: */
+ 0xa69d, /* U+00a69d: */
+ 0xa69e, /* U+00a69e: */
+ 0xa69f, /* U+00a69f: COMBINING CYRILLIC LETTER IOTIFIED E*/
+ 0xa6a0, /* U+00a6a0: BAMUM LETTER A*/
+ 0xa6a1, /* U+00a6a1: BAMUM LETTER KA*/
+ 0xa6a2, /* U+00a6a2: BAMUM LETTER U*/
+ 0xa6a3, /* U+00a6a3: BAMUM LETTER KU*/
+ 0xa6a4, /* U+00a6a4: BAMUM LETTER EE*/
+ 0xa6a5, /* U+00a6a5: BAMUM LETTER REE*/
+ 0xa6a6, /* U+00a6a6: BAMUM LETTER TAE*/
+ 0xa6a7, /* U+00a6a7: BAMUM LETTER O*/
+ 0xa6a8, /* U+00a6a8: BAMUM LETTER NYI*/
+ 0xa6a9, /* U+00a6a9: BAMUM LETTER I*/
+ 0xa6aa, /* U+00a6aa: BAMUM LETTER LA*/
+ 0xa6ab, /* U+00a6ab: BAMUM LETTER PA*/
+ 0xa6ac, /* U+00a6ac: BAMUM LETTER RII*/
+ 0xa6ad, /* U+00a6ad: BAMUM LETTER RIEE*/
+ 0xa6ae, /* U+00a6ae: BAMUM LETTER LEEEE*/
+ 0xa6af, /* U+00a6af: BAMUM LETTER MEEEE*/
+ 0xa6b0, /* U+00a6b0: BAMUM LETTER TAA*/
+ 0xa6b1, /* U+00a6b1: BAMUM LETTER NDAA*/
+ 0xa6b2, /* U+00a6b2: BAMUM LETTER NJAEM*/
+ 0xa6b3, /* U+00a6b3: BAMUM LETTER M*/
+ 0xa6b4, /* U+00a6b4: BAMUM LETTER SUU*/
+ 0xa6b5, /* U+00a6b5: BAMUM LETTER MU*/
+ 0xa6b6, /* U+00a6b6: BAMUM LETTER SHII*/
+ 0xa6b7, /* U+00a6b7: BAMUM LETTER SI*/
+ 0xa6b8, /* U+00a6b8: BAMUM LETTER SHEUX*/
+ 0xa6b9, /* U+00a6b9: BAMUM LETTER SEUX*/
+ 0xa6ba, /* U+00a6ba: BAMUM LETTER KYEE*/
+ 0xa6bb, /* U+00a6bb: BAMUM LETTER KET*/
+ 0xa6bc, /* U+00a6bc: BAMUM LETTER NUAE*/
+ 0xa6bd, /* U+00a6bd: BAMUM LETTER NU*/
+ 0xa6be, /* U+00a6be: BAMUM LETTER NJUAE*/
+ 0xa6bf, /* U+00a6bf: BAMUM LETTER YOQ*/
+ 0xa6c0, /* U+00a6c0: BAMUM LETTER SHU*/
+ 0xa6c1, /* U+00a6c1: BAMUM LETTER YUQ*/
+ 0xa6c2, /* U+00a6c2: BAMUM LETTER YA*/
+ 0xa6c3, /* U+00a6c3: BAMUM LETTER NSHA*/
+ 0xa6c4, /* U+00a6c4: BAMUM LETTER KEUX*/
+ 0xa6c5, /* U+00a6c5: BAMUM LETTER PEUX*/
+ 0xa6c6, /* U+00a6c6: BAMUM LETTER NJEE*/
+ 0xa6c7, /* U+00a6c7: BAMUM LETTER NTEE*/
+ 0xa6c8, /* U+00a6c8: BAMUM LETTER PUE*/
+ 0xa6c9, /* U+00a6c9: BAMUM LETTER WUE*/
+ 0xa6ca, /* U+00a6ca: BAMUM LETTER PEE*/
+ 0xa6cb, /* U+00a6cb: BAMUM LETTER FEE*/
+ 0xa6cc, /* U+00a6cc: BAMUM LETTER RU*/
+ 0xa6cd, /* U+00a6cd: BAMUM LETTER LU*/
+ 0xa6ce, /* U+00a6ce: BAMUM LETTER MI*/
+ 0xa6cf, /* U+00a6cf: BAMUM LETTER NI*/
+ 0xa6d0, /* U+00a6d0: BAMUM LETTER REUX*/
+ 0xa6d1, /* U+00a6d1: BAMUM LETTER RAE*/
+ 0xa6d2, /* U+00a6d2: BAMUM LETTER KEN*/
+ 0xa6d3, /* U+00a6d3: BAMUM LETTER NGKWAEN*/
+ 0xa6d4, /* U+00a6d4: BAMUM LETTER NGGA*/
+ 0xa6d5, /* U+00a6d5: BAMUM LETTER NGA*/
+ 0xa6d6, /* U+00a6d6: BAMUM LETTER SHO*/
+ 0xa6d7, /* U+00a6d7: BAMUM LETTER PUAE*/
+ 0xa6d8, /* U+00a6d8: BAMUM LETTER FU*/
+ 0xa6d9, /* U+00a6d9: BAMUM LETTER FOM*/
+ 0xa6da, /* U+00a6da: BAMUM LETTER WA*/
+ 0xa6db, /* U+00a6db: BAMUM LETTER NA*/
+ 0xa6dc, /* U+00a6dc: BAMUM LETTER LI*/
+ 0xa6dd, /* U+00a6dd: BAMUM LETTER PI*/
+ 0xa6de, /* U+00a6de: BAMUM LETTER LOQ*/
+ 0xa6df, /* U+00a6df: BAMUM LETTER KO*/
+ 0xa6e0, /* U+00a6e0: BAMUM LETTER MBEN*/
+ 0xa6e1, /* U+00a6e1: BAMUM LETTER REN*/
+ 0xa6e2, /* U+00a6e2: BAMUM LETTER MEN*/
+ 0xa6e3, /* U+00a6e3: BAMUM LETTER MA*/
+ 0xa6e4, /* U+00a6e4: BAMUM LETTER TI*/
+ 0xa6e5, /* U+00a6e5: BAMUM LETTER KI*/
+ 0xa6e6, /* U+00a6e6: BAMUM LETTER MO*/
+ 0xa6e7, /* U+00a6e7: BAMUM LETTER MBAA*/
+ 0xa6e8, /* U+00a6e8: BAMUM LETTER TET*/
+ 0xa6e9, /* U+00a6e9: BAMUM LETTER KPA*/
+ 0xa6ea, /* U+00a6ea: BAMUM LETTER TEN*/
+ 0xa6eb, /* U+00a6eb: BAMUM LETTER NTUU*/
+ 0xa6ec, /* U+00a6ec: BAMUM LETTER SAMBA*/
+ 0xa6ed, /* U+00a6ed: BAMUM LETTER FAAMAE*/
+ 0xa6ee, /* U+00a6ee: BAMUM LETTER KOVUU*/
+ 0xa6ef, /* U+00a6ef: BAMUM LETTER KOGHOM*/
+ 0xa6f0, /* U+00a6f0: BAMUM COMBINING MARK KOQNDON*/
+ 0xa6f1, /* U+00a6f1: BAMUM COMBINING MARK TUKWENTIS*/
+ 0xa6f2, /* U+00a6f2: BAMUM NJAEMLI*/
+ 0xa6f3, /* U+00a6f3: BAMUM FULL STOP*/
+ 0xa6f4, /* U+00a6f4: BAMUM COLON*/
+ 0xa6f5, /* U+00a6f5: BAMUM COMMA*/
+ 0xa6f6, /* U+00a6f6: BAMUM SEMICOLON*/
+ 0xa6f7, /* U+00a6f7: BAMUM QUESTION MARK*/
+ 0xa6f8, /* U+00a6f8: */
+ 0xa6f9, /* U+00a6f9: */
+ 0xa6fa, /* U+00a6fa: */
+ 0xa6fb, /* U+00a6fb: */
+ 0xa6fc, /* U+00a6fc: */
+ 0xa6fd, /* U+00a6fd: */
+ 0xa6fe, /* U+00a6fe: */
+ 0xa6ff, /* U+00a6ff: */
+ 0xa700, /* U+00a700: MODIFIER LETTER CHINESE TONE YIN PING*/
+ 0xa701, /* U+00a701: MODIFIER LETTER CHINESE TONE YANG PING*/
+ 0xa702, /* U+00a702: MODIFIER LETTER CHINESE TONE YIN SHANG*/
+ 0xa703, /* U+00a703: MODIFIER LETTER CHINESE TONE YANG SHANG*/
+ 0xa704, /* U+00a704: MODIFIER LETTER CHINESE TONE YIN QU*/
+ 0xa705, /* U+00a705: MODIFIER LETTER CHINESE TONE YANG QU*/
+ 0xa706, /* U+00a706: MODIFIER LETTER CHINESE TONE YIN RU*/
+ 0xa707, /* U+00a707: MODIFIER LETTER CHINESE TONE YANG RU*/
+ 0xa708, /* U+00a708: MODIFIER LETTER EXTRA-HIGH DOTTED TONE BAR*/
+ 0xa709, /* U+00a709: MODIFIER LETTER HIGH DOTTED TONE BAR*/
+ 0xa70a, /* U+00a70a: MODIFIER LETTER MID DOTTED TONE BAR*/
+ 0xa70b, /* U+00a70b: MODIFIER LETTER LOW DOTTED TONE BAR*/
+ 0xa70c, /* U+00a70c: MODIFIER LETTER EXTRA-LOW DOTTED TONE BAR*/
+ 0xa70d, /* U+00a70d: MODIFIER LETTER EXTRA-HIGH DOTTED LEFT-STEM TONE BAR*/
+ 0xa70e, /* U+00a70e: MODIFIER LETTER HIGH DOTTED LEFT-STEM TONE BAR*/
+ 0xa70f, /* U+00a70f: MODIFIER LETTER MID DOTTED LEFT-STEM TONE BAR*/
+ 0xa710, /* U+00a710: MODIFIER LETTER LOW DOTTED LEFT-STEM TONE BAR*/
+ 0xa711, /* U+00a711: MODIFIER LETTER EXTRA-LOW DOTTED LEFT-STEM TONE BAR*/
+ 0xa712, /* U+00a712: MODIFIER LETTER EXTRA-HIGH LEFT-STEM TONE BAR*/
+ 0xa713, /* U+00a713: MODIFIER LETTER HIGH LEFT-STEM TONE BAR*/
+ 0xa714, /* U+00a714: MODIFIER LETTER MID LEFT-STEM TONE BAR*/
+ 0xa715, /* U+00a715: MODIFIER LETTER LOW LEFT-STEM TONE BAR*/
+ 0xa716, /* U+00a716: MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR*/
+ 0xa717, /* U+00a717: MODIFIER LETTER DOT VERTICAL BAR*/
+ 0xa718, /* U+00a718: MODIFIER LETTER DOT SLASH*/
+ 0xa719, /* U+00a719: MODIFIER LETTER DOT HORIZONTAL BAR*/
+ 0xa71a, /* U+00a71a: MODIFIER LETTER LOWER RIGHT CORNER ANGLE*/
+ 0xa71b, /* U+00a71b: MODIFIER LETTER RAISED UP ARROW*/
+ 0xa71c, /* U+00a71c: MODIFIER LETTER RAISED DOWN ARROW*/
+ 0xa71d, /* U+00a71d: MODIFIER LETTER RAISED EXCLAMATION MARK*/
+ 0xa71e, /* U+00a71e: MODIFIER LETTER RAISED INVERTED EXCLAMATION MARK*/
+ 0xa71f, /* U+00a71f: MODIFIER LETTER LOW INVERTED EXCLAMATION MARK*/
+ 0xa720, /* U+00a720: MODIFIER LETTER STRESS AND HIGH TONE*/
+ 0xa721, /* U+00a721: MODIFIER LETTER STRESS AND LOW TONE*/
+ 0xa723, /* U+00a722: LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF*/
+ 0xa723, /* U+00a723: LATIN SMALL LETTER EGYPTOLOGICAL ALEF*/
+ 0xa725, /* U+00a724: LATIN CAPITAL LETTER EGYPTOLOGICAL AIN*/
+ 0xa725, /* U+00a725: LATIN SMALL LETTER EGYPTOLOGICAL AIN*/
+ 0xa727, /* U+00a726: LATIN CAPITAL LETTER HENG*/
+ 0xa727, /* U+00a727: LATIN SMALL LETTER HENG*/
+ 0xa729, /* U+00a728: LATIN CAPITAL LETTER TZ*/
+ 0xa729, /* U+00a729: LATIN SMALL LETTER TZ*/
+ 0xa72b, /* U+00a72a: LATIN CAPITAL LETTER TRESILLO*/
+ 0xa72b, /* U+00a72b: LATIN SMALL LETTER TRESILLO*/
+ 0xa72d, /* U+00a72c: LATIN CAPITAL LETTER CUATRILLO*/
+ 0xa72d, /* U+00a72d: LATIN SMALL LETTER CUATRILLO*/
+ 0xa72f, /* U+00a72e: LATIN CAPITAL LETTER CUATRILLO WITH COMMA*/
+ 0xa72f, /* U+00a72f: LATIN SMALL LETTER CUATRILLO WITH COMMA*/
+ 0xa730, /* U+00a730: LATIN LETTER SMALL CAPITAL F*/
+ 0xa731, /* U+00a731: LATIN LETTER SMALL CAPITAL S*/
+ 0xa733, /* U+00a732: LATIN CAPITAL LETTER AA*/
+ 0xa733, /* U+00a733: LATIN SMALL LETTER AA*/
+ 0xa735, /* U+00a734: LATIN CAPITAL LETTER AO*/
+ 0xa735, /* U+00a735: LATIN SMALL LETTER AO*/
+ 0xa737, /* U+00a736: LATIN CAPITAL LETTER AU*/
+ 0xa737, /* U+00a737: LATIN SMALL LETTER AU*/
+ 0xa739, /* U+00a738: LATIN CAPITAL LETTER AV*/
+ 0xa739, /* U+00a739: LATIN SMALL LETTER AV*/
+ 0xa73b, /* U+00a73a: LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR*/
+ 0xa73b, /* U+00a73b: LATIN SMALL LETTER AV WITH HORIZONTAL BAR*/
+ 0xa73d, /* U+00a73c: LATIN CAPITAL LETTER AY*/
+ 0xa73d, /* U+00a73d: LATIN SMALL LETTER AY*/
+ 0xa73f, /* U+00a73e: LATIN CAPITAL LETTER REVERSED C WITH DOT*/
+ 0xa73f, /* U+00a73f: LATIN SMALL LETTER REVERSED C WITH DOT*/
+ 0xa741, /* U+00a740: LATIN CAPITAL LETTER K WITH STROKE*/
+ 0xa741, /* U+00a741: LATIN SMALL LETTER K WITH STROKE*/
+ 0xa743, /* U+00a742: LATIN CAPITAL LETTER K WITH DIAGONAL STROKE*/
+ 0xa743, /* U+00a743: LATIN SMALL LETTER K WITH DIAGONAL STROKE*/
+ 0xa745, /* U+00a744: LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE*/
+ 0xa745, /* U+00a745: LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE*/
+ 0xa747, /* U+00a746: LATIN CAPITAL LETTER BROKEN L*/
+ 0xa747, /* U+00a747: LATIN SMALL LETTER BROKEN L*/
+ 0xa749, /* U+00a748: LATIN CAPITAL LETTER L WITH HIGH STROKE*/
+ 0xa749, /* U+00a749: LATIN SMALL LETTER L WITH HIGH STROKE*/
+ 0xa74b, /* U+00a74a: LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY*/
+ 0xa74b, /* U+00a74b: LATIN SMALL LETTER O WITH LONG STROKE OVERLAY*/
+ 0xa74d, /* U+00a74c: LATIN CAPITAL LETTER O WITH LOOP*/
+ 0xa74d, /* U+00a74d: LATIN SMALL LETTER O WITH LOOP*/
+ 0xa74f, /* U+00a74e: LATIN CAPITAL LETTER OO*/
+ 0xa74f, /* U+00a74f: LATIN SMALL LETTER OO*/
+ 0xa751, /* U+00a750: LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER*/
+ 0xa751, /* U+00a751: LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER*/
+ 0xa753, /* U+00a752: LATIN CAPITAL LETTER P WITH FLOURISH*/
+ 0xa753, /* U+00a753: LATIN SMALL LETTER P WITH FLOURISH*/
+ 0xa755, /* U+00a754: LATIN CAPITAL LETTER P WITH SQUIRREL TAIL*/
+ 0xa755, /* U+00a755: LATIN SMALL LETTER P WITH SQUIRREL TAIL*/
+ 0xa757, /* U+00a756: LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER*/
+ 0xa757, /* U+00a757: LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER*/
+ 0xa759, /* U+00a758: LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE*/
+ 0xa759, /* U+00a759: LATIN SMALL LETTER Q WITH DIAGONAL STROKE*/
+ 0xa75b, /* U+00a75a: LATIN CAPITAL LETTER R ROTUNDA*/
+ 0xa75b, /* U+00a75b: LATIN SMALL LETTER R ROTUNDA*/
+ 0xa75d, /* U+00a75c: LATIN CAPITAL LETTER RUM ROTUNDA*/
+ 0xa75d, /* U+00a75d: LATIN SMALL LETTER RUM ROTUNDA*/
+ 0xa75f, /* U+00a75e: LATIN CAPITAL LETTER V WITH DIAGONAL STROKE*/
+ 0xa75f, /* U+00a75f: LATIN SMALL LETTER V WITH DIAGONAL STROKE*/
+ 0xa761, /* U+00a760: LATIN CAPITAL LETTER VY*/
+ 0xa761, /* U+00a761: LATIN SMALL LETTER VY*/
+ 0xa763, /* U+00a762: LATIN CAPITAL LETTER VISIGOTHIC Z*/
+ 0xa763, /* U+00a763: LATIN SMALL LETTER VISIGOTHIC Z*/
+ 0xa765, /* U+00a764: LATIN CAPITAL LETTER THORN WITH STROKE*/
+ 0xa765, /* U+00a765: LATIN SMALL LETTER THORN WITH STROKE*/
+ 0xa767, /* U+00a766: LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER*/
+ 0xa767, /* U+00a767: LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER*/
+ 0xa769, /* U+00a768: LATIN CAPITAL LETTER VEND*/
+ 0xa769, /* U+00a769: LATIN SMALL LETTER VEND*/
+ 0xa76b, /* U+00a76a: LATIN CAPITAL LETTER ET*/
+ 0xa76b, /* U+00a76b: LATIN SMALL LETTER ET*/
+ 0xa76d, /* U+00a76c: LATIN CAPITAL LETTER IS*/
+ 0xa76d, /* U+00a76d: LATIN SMALL LETTER IS*/
+ 0xa76f, /* U+00a76e: LATIN CAPITAL LETTER CON*/
+ 0xa76f, /* U+00a76f: LATIN SMALL LETTER CON*/
+ 0xa770, /* U+00a770: MODIFIER LETTER US*/
+ 0xa771, /* U+00a771: LATIN SMALL LETTER DUM*/
+ 0xa772, /* U+00a772: LATIN SMALL LETTER LUM*/
+ 0xa773, /* U+00a773: LATIN SMALL LETTER MUM*/
+ 0xa774, /* U+00a774: LATIN SMALL LETTER NUM*/
+ 0xa775, /* U+00a775: LATIN SMALL LETTER RUM*/
+ 0xa776, /* U+00a776: LATIN LETTER SMALL CAPITAL RUM*/
+ 0xa777, /* U+00a777: LATIN SMALL LETTER TUM*/
+ 0xa778, /* U+00a778: LATIN SMALL LETTER UM*/
+ 0xa77a, /* U+00a779: LATIN CAPITAL LETTER INSULAR D*/
+ 0xa77a, /* U+00a77a: LATIN SMALL LETTER INSULAR D*/
+ 0xa77c, /* U+00a77b: LATIN CAPITAL LETTER INSULAR F*/
+ 0xa77c, /* U+00a77c: LATIN SMALL LETTER INSULAR F*/
+ 0x1d79, /* U+00a77d: LATIN CAPITAL LETTER INSULAR G*/
+ 0xa77f, /* U+00a77e: LATIN CAPITAL LETTER TURNED INSULAR G*/
+ 0xa77f, /* U+00a77f: LATIN SMALL LETTER TURNED INSULAR G*/
+ 0xa781, /* U+00a780: LATIN CAPITAL LETTER TURNED L*/
+ 0xa781, /* U+00a781: LATIN SMALL LETTER TURNED L*/
+ 0xa783, /* U+00a782: LATIN CAPITAL LETTER INSULAR R*/
+ 0xa783, /* U+00a783: LATIN SMALL LETTER INSULAR R*/
+ 0xa785, /* U+00a784: LATIN CAPITAL LETTER INSULAR S*/
+ 0xa785, /* U+00a785: LATIN SMALL LETTER INSULAR S*/
+ 0xa787, /* U+00a786: LATIN CAPITAL LETTER INSULAR T*/
+ 0xa787, /* U+00a787: LATIN SMALL LETTER INSULAR T*/
+ 0xa788, /* U+00a788: MODIFIER LETTER LOW CIRCUMFLEX ACCENT*/
+ 0xa789, /* U+00a789: MODIFIER LETTER COLON*/
+ 0xa78a, /* U+00a78a: MODIFIER LETTER SHORT EQUALS SIGN*/
+ 0xa78c, /* U+00a78b: LATIN CAPITAL LETTER SALTILLO*/
+ 0xa78c, /* U+00a78c: LATIN SMALL LETTER SALTILLO*/
+ 0x265, /* U+00a78d: LATIN CAPITAL LETTER TURNED H*/
+ 0xa78e, /* U+00a78e: LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT*/
+ 0xa78f, /* U+00a78f: */
+ 0xa791, /* U+00a790: LATIN CAPITAL LETTER N WITH DESCENDER*/
+ 0xa791, /* U+00a791: LATIN SMALL LETTER N WITH DESCENDER*/
+ 0xa793, /* U+00a792: LATIN CAPITAL LETTER C WITH BAR*/
+ 0xa793, /* U+00a793: LATIN SMALL LETTER C WITH BAR*/
+ 0xa794, /* U+00a794: */
+ 0xa795, /* U+00a795: */
+ 0xa796, /* U+00a796: */
+ 0xa797, /* U+00a797: */
+ 0xa798, /* U+00a798: */
+ 0xa799, /* U+00a799: */
+ 0xa79a, /* U+00a79a: */
+ 0xa79b, /* U+00a79b: */
+ 0xa79c, /* U+00a79c: */
+ 0xa79d, /* U+00a79d: */
+ 0xa79e, /* U+00a79e: */
+ 0xa79f, /* U+00a79f: */
+ 0xa7a1, /* U+00a7a0: LATIN CAPITAL LETTER G WITH OBLIQUE STROKE*/
+ 0xa7a1, /* U+00a7a1: LATIN SMALL LETTER G WITH OBLIQUE STROKE*/
+ 0xa7a3, /* U+00a7a2: LATIN CAPITAL LETTER K WITH OBLIQUE STROKE*/
+ 0xa7a3, /* U+00a7a3: LATIN SMALL LETTER K WITH OBLIQUE STROKE*/
+ 0xa7a5, /* U+00a7a4: LATIN CAPITAL LETTER N WITH OBLIQUE STROKE*/
+ 0xa7a5, /* U+00a7a5: LATIN SMALL LETTER N WITH OBLIQUE STROKE*/
+ 0xa7a7, /* U+00a7a6: LATIN CAPITAL LETTER R WITH OBLIQUE STROKE*/
+ 0xa7a7, /* U+00a7a7: LATIN SMALL LETTER R WITH OBLIQUE STROKE*/
+ 0xa7a9, /* U+00a7a8: LATIN CAPITAL LETTER S WITH OBLIQUE STROKE*/
+ 0xa7a9, /* U+00a7a9: LATIN SMALL LETTER S WITH OBLIQUE STROKE*/
+ 0x266, /* U+00a7aa: LATIN CAPITAL LETTER H WITH HOOK*/
+};
+
+static const RTUNICP g_afRTUniLower0x00ff21[] =
+{
+ 0xff41, /* U+00ff21: FULLWIDTH LATIN CAPITAL LETTER A*/
+ 0xff42, /* U+00ff22: FULLWIDTH LATIN CAPITAL LETTER B*/
+ 0xff43, /* U+00ff23: FULLWIDTH LATIN CAPITAL LETTER C*/
+ 0xff44, /* U+00ff24: FULLWIDTH LATIN CAPITAL LETTER D*/
+ 0xff45, /* U+00ff25: FULLWIDTH LATIN CAPITAL LETTER E*/
+ 0xff46, /* U+00ff26: FULLWIDTH LATIN CAPITAL LETTER F*/
+ 0xff47, /* U+00ff27: FULLWIDTH LATIN CAPITAL LETTER G*/
+ 0xff48, /* U+00ff28: FULLWIDTH LATIN CAPITAL LETTER H*/
+ 0xff49, /* U+00ff29: FULLWIDTH LATIN CAPITAL LETTER I*/
+ 0xff4a, /* U+00ff2a: FULLWIDTH LATIN CAPITAL LETTER J*/
+ 0xff4b, /* U+00ff2b: FULLWIDTH LATIN CAPITAL LETTER K*/
+ 0xff4c, /* U+00ff2c: FULLWIDTH LATIN CAPITAL LETTER L*/
+ 0xff4d, /* U+00ff2d: FULLWIDTH LATIN CAPITAL LETTER M*/
+ 0xff4e, /* U+00ff2e: FULLWIDTH LATIN CAPITAL LETTER N*/
+ 0xff4f, /* U+00ff2f: FULLWIDTH LATIN CAPITAL LETTER O*/
+ 0xff50, /* U+00ff30: FULLWIDTH LATIN CAPITAL LETTER P*/
+ 0xff51, /* U+00ff31: FULLWIDTH LATIN CAPITAL LETTER Q*/
+ 0xff52, /* U+00ff32: FULLWIDTH LATIN CAPITAL LETTER R*/
+ 0xff53, /* U+00ff33: FULLWIDTH LATIN CAPITAL LETTER S*/
+ 0xff54, /* U+00ff34: FULLWIDTH LATIN CAPITAL LETTER T*/
+ 0xff55, /* U+00ff35: FULLWIDTH LATIN CAPITAL LETTER U*/
+ 0xff56, /* U+00ff36: FULLWIDTH LATIN CAPITAL LETTER V*/
+ 0xff57, /* U+00ff37: FULLWIDTH LATIN CAPITAL LETTER W*/
+ 0xff58, /* U+00ff38: FULLWIDTH LATIN CAPITAL LETTER X*/
+ 0xff59, /* U+00ff39: FULLWIDTH LATIN CAPITAL LETTER Y*/
+ 0xff5a, /* U+00ff3a: FULLWIDTH LATIN CAPITAL LETTER Z*/
+};
+
+static const RTUNICP g_afRTUniLower0x010400[] =
+{
+ 0x10428, /* U+010400: DESERET CAPITAL LETTER LONG I*/
+ 0x10429, /* U+010401: DESERET CAPITAL LETTER LONG E*/
+ 0x1042a, /* U+010402: DESERET CAPITAL LETTER LONG A*/
+ 0x1042b, /* U+010403: DESERET CAPITAL LETTER LONG AH*/
+ 0x1042c, /* U+010404: DESERET CAPITAL LETTER LONG O*/
+ 0x1042d, /* U+010405: DESERET CAPITAL LETTER LONG OO*/
+ 0x1042e, /* U+010406: DESERET CAPITAL LETTER SHORT I*/
+ 0x1042f, /* U+010407: DESERET CAPITAL LETTER SHORT E*/
+ 0x10430, /* U+010408: DESERET CAPITAL LETTER SHORT A*/
+ 0x10431, /* U+010409: DESERET CAPITAL LETTER SHORT AH*/
+ 0x10432, /* U+01040a: DESERET CAPITAL LETTER SHORT O*/
+ 0x10433, /* U+01040b: DESERET CAPITAL LETTER SHORT OO*/
+ 0x10434, /* U+01040c: DESERET CAPITAL LETTER AY*/
+ 0x10435, /* U+01040d: DESERET CAPITAL LETTER OW*/
+ 0x10436, /* U+01040e: DESERET CAPITAL LETTER WU*/
+ 0x10437, /* U+01040f: DESERET CAPITAL LETTER YEE*/
+ 0x10438, /* U+010410: DESERET CAPITAL LETTER H*/
+ 0x10439, /* U+010411: DESERET CAPITAL LETTER PEE*/
+ 0x1043a, /* U+010412: DESERET CAPITAL LETTER BEE*/
+ 0x1043b, /* U+010413: DESERET CAPITAL LETTER TEE*/
+ 0x1043c, /* U+010414: DESERET CAPITAL LETTER DEE*/
+ 0x1043d, /* U+010415: DESERET CAPITAL LETTER CHEE*/
+ 0x1043e, /* U+010416: DESERET CAPITAL LETTER JEE*/
+ 0x1043f, /* U+010417: DESERET CAPITAL LETTER KAY*/
+ 0x10440, /* U+010418: DESERET CAPITAL LETTER GAY*/
+ 0x10441, /* U+010419: DESERET CAPITAL LETTER EF*/
+ 0x10442, /* U+01041a: DESERET CAPITAL LETTER VEE*/
+ 0x10443, /* U+01041b: DESERET CAPITAL LETTER ETH*/
+ 0x10444, /* U+01041c: DESERET CAPITAL LETTER THEE*/
+ 0x10445, /* U+01041d: DESERET CAPITAL LETTER ES*/
+ 0x10446, /* U+01041e: DESERET CAPITAL LETTER ZEE*/
+ 0x10447, /* U+01041f: DESERET CAPITAL LETTER ESH*/
+ 0x10448, /* U+010420: DESERET CAPITAL LETTER ZHEE*/
+ 0x10449, /* U+010421: DESERET CAPITAL LETTER ER*/
+ 0x1044a, /* U+010422: DESERET CAPITAL LETTER EL*/
+ 0x1044b, /* U+010423: DESERET CAPITAL LETTER EM*/
+ 0x1044c, /* U+010424: DESERET CAPITAL LETTER EN*/
+ 0x1044d, /* U+010425: DESERET CAPITAL LETTER ENG*/
+ 0x1044e, /* U+010426: DESERET CAPITAL LETTER OI*/
+ 0x1044f, /* U+010427: DESERET CAPITAL LETTER EW*/
+};
+
+
+RT_DECL_DATA_CONST(const RTUNICASERANGE) g_aRTUniLowerRanges[] =
+{
+ { 0x000000, 0x000557, &g_afRTUniLower0x000000[0] },
+ { 0x0010a0, 0x0010ce, &g_afRTUniLower0x0010a0[0] },
+ { 0x001e00, 0x0024d0, &g_afRTUniLower0x001e00[0] },
+ { 0x002c00, 0x002cf3, &g_afRTUniLower0x002c00[0] },
+ { 0x00a640, 0x00a7ab, &g_afRTUniLower0x00a640[0] },
+ { 0x00ff21, 0x00ff3b, &g_afRTUniLower0x00ff21[0] },
+ { 0x010400, 0x010428, &g_afRTUniLower0x010400[0] },
+ { ~(RTUNICP)0, ~(RTUNICP)0, NULL }
+};
+
diff --git a/src/VBox/Runtime/common/string/unidata-upper.cpp b/src/VBox/Runtime/common/string/unidata-upper.cpp
new file mode 100644
index 00000000..db378a63
--- /dev/null
+++ b/src/VBox/Runtime/common/string/unidata-upper.cpp
@@ -0,0 +1,4078 @@
+/* $Id: unidata-upper.cpp $ */
+/** @file
+ * IPRT - Unicode Tables.
+ *
+ * Automatically Generated from d:\src\unicode\6.3.0\
+ * by ..\..\..\..\..\out\win.amd64\debug\obj\uniread\uniread.exe (May 22 2014 19:07:40)
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#include <iprt/uni.h>
+
+static const RTUNICP g_afRTUniUpper0x000000[] =
+{
+ 0x00, /* U+000000: <control>*/
+ 0x01, /* U+000001: <control>*/
+ 0x02, /* U+000002: <control>*/
+ 0x03, /* U+000003: <control>*/
+ 0x04, /* U+000004: <control>*/
+ 0x05, /* U+000005: <control>*/
+ 0x06, /* U+000006: <control>*/
+ 0x07, /* U+000007: <control>*/
+ 0x08, /* U+000008: <control>*/
+ 0x09, /* U+000009: <control>*/
+ 0x0a, /* U+00000a: <control>*/
+ 0x0b, /* U+00000b: <control>*/
+ 0x0c, /* U+00000c: <control>*/
+ 0x0d, /* U+00000d: <control>*/
+ 0x0e, /* U+00000e: <control>*/
+ 0x0f, /* U+00000f: <control>*/
+ 0x10, /* U+000010: <control>*/
+ 0x11, /* U+000011: <control>*/
+ 0x12, /* U+000012: <control>*/
+ 0x13, /* U+000013: <control>*/
+ 0x14, /* U+000014: <control>*/
+ 0x15, /* U+000015: <control>*/
+ 0x16, /* U+000016: <control>*/
+ 0x17, /* U+000017: <control>*/
+ 0x18, /* U+000018: <control>*/
+ 0x19, /* U+000019: <control>*/
+ 0x1a, /* U+00001a: <control>*/
+ 0x1b, /* U+00001b: <control>*/
+ 0x1c, /* U+00001c: <control>*/
+ 0x1d, /* U+00001d: <control>*/
+ 0x1e, /* U+00001e: <control>*/
+ 0x1f, /* U+00001f: <control>*/
+ 0x20, /* U+000020: SPACE*/
+ 0x21, /* U+000021: EXCLAMATION MARK*/
+ 0x22, /* U+000022: QUOTATION MARK*/
+ 0x23, /* U+000023: NUMBER SIGN*/
+ 0x24, /* U+000024: DOLLAR SIGN*/
+ 0x25, /* U+000025: PERCENT SIGN*/
+ 0x26, /* U+000026: AMPERSAND*/
+ 0x27, /* U+000027: APOSTROPHE*/
+ 0x28, /* U+000028: LEFT PARENTHESIS*/
+ 0x29, /* U+000029: RIGHT PARENTHESIS*/
+ 0x2a, /* U+00002a: ASTERISK*/
+ 0x2b, /* U+00002b: PLUS SIGN*/
+ 0x2c, /* U+00002c: COMMA*/
+ 0x2d, /* U+00002d: HYPHEN-MINUS*/
+ 0x2e, /* U+00002e: FULL STOP*/
+ 0x2f, /* U+00002f: SOLIDUS*/
+ 0x30, /* U+000030: DIGIT ZERO*/
+ 0x31, /* U+000031: DIGIT ONE*/
+ 0x32, /* U+000032: DIGIT TWO*/
+ 0x33, /* U+000033: DIGIT THREE*/
+ 0x34, /* U+000034: DIGIT FOUR*/
+ 0x35, /* U+000035: DIGIT FIVE*/
+ 0x36, /* U+000036: DIGIT SIX*/
+ 0x37, /* U+000037: DIGIT SEVEN*/
+ 0x38, /* U+000038: DIGIT EIGHT*/
+ 0x39, /* U+000039: DIGIT NINE*/
+ 0x3a, /* U+00003a: COLON*/
+ 0x3b, /* U+00003b: SEMICOLON*/
+ 0x3c, /* U+00003c: LESS-THAN SIGN*/
+ 0x3d, /* U+00003d: EQUALS SIGN*/
+ 0x3e, /* U+00003e: GREATER-THAN SIGN*/
+ 0x3f, /* U+00003f: QUESTION MARK*/
+ 0x40, /* U+000040: COMMERCIAL AT*/
+ 0x41, /* U+000041: LATIN CAPITAL LETTER A*/
+ 0x42, /* U+000042: LATIN CAPITAL LETTER B*/
+ 0x43, /* U+000043: LATIN CAPITAL LETTER C*/
+ 0x44, /* U+000044: LATIN CAPITAL LETTER D*/
+ 0x45, /* U+000045: LATIN CAPITAL LETTER E*/
+ 0x46, /* U+000046: LATIN CAPITAL LETTER F*/
+ 0x47, /* U+000047: LATIN CAPITAL LETTER G*/
+ 0x48, /* U+000048: LATIN CAPITAL LETTER H*/
+ 0x49, /* U+000049: LATIN CAPITAL LETTER I*/
+ 0x4a, /* U+00004a: LATIN CAPITAL LETTER J*/
+ 0x4b, /* U+00004b: LATIN CAPITAL LETTER K*/
+ 0x4c, /* U+00004c: LATIN CAPITAL LETTER L*/
+ 0x4d, /* U+00004d: LATIN CAPITAL LETTER M*/
+ 0x4e, /* U+00004e: LATIN CAPITAL LETTER N*/
+ 0x4f, /* U+00004f: LATIN CAPITAL LETTER O*/
+ 0x50, /* U+000050: LATIN CAPITAL LETTER P*/
+ 0x51, /* U+000051: LATIN CAPITAL LETTER Q*/
+ 0x52, /* U+000052: LATIN CAPITAL LETTER R*/
+ 0x53, /* U+000053: LATIN CAPITAL LETTER S*/
+ 0x54, /* U+000054: LATIN CAPITAL LETTER T*/
+ 0x55, /* U+000055: LATIN CAPITAL LETTER U*/
+ 0x56, /* U+000056: LATIN CAPITAL LETTER V*/
+ 0x57, /* U+000057: LATIN CAPITAL LETTER W*/
+ 0x58, /* U+000058: LATIN CAPITAL LETTER X*/
+ 0x59, /* U+000059: LATIN CAPITAL LETTER Y*/
+ 0x5a, /* U+00005a: LATIN CAPITAL LETTER Z*/
+ 0x5b, /* U+00005b: LEFT SQUARE BRACKET*/
+ 0x5c, /* U+00005c: REVERSE SOLIDUS*/
+ 0x5d, /* U+00005d: RIGHT SQUARE BRACKET*/
+ 0x5e, /* U+00005e: CIRCUMFLEX ACCENT*/
+ 0x5f, /* U+00005f: LOW LINE*/
+ 0x60, /* U+000060: GRAVE ACCENT*/
+ 0x41, /* U+000061: LATIN SMALL LETTER A*/
+ 0x42, /* U+000062: LATIN SMALL LETTER B*/
+ 0x43, /* U+000063: LATIN SMALL LETTER C*/
+ 0x44, /* U+000064: LATIN SMALL LETTER D*/
+ 0x45, /* U+000065: LATIN SMALL LETTER E*/
+ 0x46, /* U+000066: LATIN SMALL LETTER F*/
+ 0x47, /* U+000067: LATIN SMALL LETTER G*/
+ 0x48, /* U+000068: LATIN SMALL LETTER H*/
+ 0x49, /* U+000069: LATIN SMALL LETTER I*/
+ 0x4a, /* U+00006a: LATIN SMALL LETTER J*/
+ 0x4b, /* U+00006b: LATIN SMALL LETTER K*/
+ 0x4c, /* U+00006c: LATIN SMALL LETTER L*/
+ 0x4d, /* U+00006d: LATIN SMALL LETTER M*/
+ 0x4e, /* U+00006e: LATIN SMALL LETTER N*/
+ 0x4f, /* U+00006f: LATIN SMALL LETTER O*/
+ 0x50, /* U+000070: LATIN SMALL LETTER P*/
+ 0x51, /* U+000071: LATIN SMALL LETTER Q*/
+ 0x52, /* U+000072: LATIN SMALL LETTER R*/
+ 0x53, /* U+000073: LATIN SMALL LETTER S*/
+ 0x54, /* U+000074: LATIN SMALL LETTER T*/
+ 0x55, /* U+000075: LATIN SMALL LETTER U*/
+ 0x56, /* U+000076: LATIN SMALL LETTER V*/
+ 0x57, /* U+000077: LATIN SMALL LETTER W*/
+ 0x58, /* U+000078: LATIN SMALL LETTER X*/
+ 0x59, /* U+000079: LATIN SMALL LETTER Y*/
+ 0x5a, /* U+00007a: LATIN SMALL LETTER Z*/
+ 0x7b, /* U+00007b: LEFT CURLY BRACKET*/
+ 0x7c, /* U+00007c: VERTICAL LINE*/
+ 0x7d, /* U+00007d: RIGHT CURLY BRACKET*/
+ 0x7e, /* U+00007e: TILDE*/
+ 0x7f, /* U+00007f: <control>*/
+ 0x80, /* U+000080: <control>*/
+ 0x81, /* U+000081: <control>*/
+ 0x82, /* U+000082: <control>*/
+ 0x83, /* U+000083: <control>*/
+ 0x84, /* U+000084: <control>*/
+ 0x85, /* U+000085: <control>*/
+ 0x86, /* U+000086: <control>*/
+ 0x87, /* U+000087: <control>*/
+ 0x88, /* U+000088: <control>*/
+ 0x89, /* U+000089: <control>*/
+ 0x8a, /* U+00008a: <control>*/
+ 0x8b, /* U+00008b: <control>*/
+ 0x8c, /* U+00008c: <control>*/
+ 0x8d, /* U+00008d: <control>*/
+ 0x8e, /* U+00008e: <control>*/
+ 0x8f, /* U+00008f: <control>*/
+ 0x90, /* U+000090: <control>*/
+ 0x91, /* U+000091: <control>*/
+ 0x92, /* U+000092: <control>*/
+ 0x93, /* U+000093: <control>*/
+ 0x94, /* U+000094: <control>*/
+ 0x95, /* U+000095: <control>*/
+ 0x96, /* U+000096: <control>*/
+ 0x97, /* U+000097: <control>*/
+ 0x98, /* U+000098: <control>*/
+ 0x99, /* U+000099: <control>*/
+ 0x9a, /* U+00009a: <control>*/
+ 0x9b, /* U+00009b: <control>*/
+ 0x9c, /* U+00009c: <control>*/
+ 0x9d, /* U+00009d: <control>*/
+ 0x9e, /* U+00009e: <control>*/
+ 0x9f, /* U+00009f: <control>*/
+ 0xa0, /* U+0000a0: NO-BREAK SPACE*/
+ 0xa1, /* U+0000a1: INVERTED EXCLAMATION MARK*/
+ 0xa2, /* U+0000a2: CENT SIGN*/
+ 0xa3, /* U+0000a3: POUND SIGN*/
+ 0xa4, /* U+0000a4: CURRENCY SIGN*/
+ 0xa5, /* U+0000a5: YEN SIGN*/
+ 0xa6, /* U+0000a6: BROKEN BAR*/
+ 0xa7, /* U+0000a7: SECTION SIGN*/
+ 0xa8, /* U+0000a8: DIAERESIS*/
+ 0xa9, /* U+0000a9: COPYRIGHT SIGN*/
+ 0xaa, /* U+0000aa: FEMININE ORDINAL INDICATOR*/
+ 0xab, /* U+0000ab: LEFT-POINTING DOUBLE ANGLE QUOTATION MARK*/
+ 0xac, /* U+0000ac: NOT SIGN*/
+ 0xad, /* U+0000ad: SOFT HYPHEN*/
+ 0xae, /* U+0000ae: REGISTERED SIGN*/
+ 0xaf, /* U+0000af: MACRON*/
+ 0xb0, /* U+0000b0: DEGREE SIGN*/
+ 0xb1, /* U+0000b1: PLUS-MINUS SIGN*/
+ 0xb2, /* U+0000b2: SUPERSCRIPT TWO*/
+ 0xb3, /* U+0000b3: SUPERSCRIPT THREE*/
+ 0xb4, /* U+0000b4: ACUTE ACCENT*/
+ 0x39c, /* U+0000b5: MICRO SIGN*/
+ 0xb6, /* U+0000b6: PILCROW SIGN*/
+ 0xb7, /* U+0000b7: MIDDLE DOT*/
+ 0xb8, /* U+0000b8: CEDILLA*/
+ 0xb9, /* U+0000b9: SUPERSCRIPT ONE*/
+ 0xba, /* U+0000ba: MASCULINE ORDINAL INDICATOR*/
+ 0xbb, /* U+0000bb: RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK*/
+ 0xbc, /* U+0000bc: VULGAR FRACTION ONE QUARTER*/
+ 0xbd, /* U+0000bd: VULGAR FRACTION ONE HALF*/
+ 0xbe, /* U+0000be: VULGAR FRACTION THREE QUARTERS*/
+ 0xbf, /* U+0000bf: INVERTED QUESTION MARK*/
+ 0xc0, /* U+0000c0: LATIN CAPITAL LETTER A WITH GRAVE*/
+ 0xc1, /* U+0000c1: LATIN CAPITAL LETTER A WITH ACUTE*/
+ 0xc2, /* U+0000c2: LATIN CAPITAL LETTER A WITH CIRCUMFLEX*/
+ 0xc3, /* U+0000c3: LATIN CAPITAL LETTER A WITH TILDE*/
+ 0xc4, /* U+0000c4: LATIN CAPITAL LETTER A WITH DIAERESIS*/
+ 0xc5, /* U+0000c5: LATIN CAPITAL LETTER A WITH RING ABOVE*/
+ 0xc6, /* U+0000c6: LATIN CAPITAL LETTER AE*/
+ 0xc7, /* U+0000c7: LATIN CAPITAL LETTER C WITH CEDILLA*/
+ 0xc8, /* U+0000c8: LATIN CAPITAL LETTER E WITH GRAVE*/
+ 0xc9, /* U+0000c9: LATIN CAPITAL LETTER E WITH ACUTE*/
+ 0xca, /* U+0000ca: LATIN CAPITAL LETTER E WITH CIRCUMFLEX*/
+ 0xcb, /* U+0000cb: LATIN CAPITAL LETTER E WITH DIAERESIS*/
+ 0xcc, /* U+0000cc: LATIN CAPITAL LETTER I WITH GRAVE*/
+ 0xcd, /* U+0000cd: LATIN CAPITAL LETTER I WITH ACUTE*/
+ 0xce, /* U+0000ce: LATIN CAPITAL LETTER I WITH CIRCUMFLEX*/
+ 0xcf, /* U+0000cf: LATIN CAPITAL LETTER I WITH DIAERESIS*/
+ 0xd0, /* U+0000d0: LATIN CAPITAL LETTER ETH*/
+ 0xd1, /* U+0000d1: LATIN CAPITAL LETTER N WITH TILDE*/
+ 0xd2, /* U+0000d2: LATIN CAPITAL LETTER O WITH GRAVE*/
+ 0xd3, /* U+0000d3: LATIN CAPITAL LETTER O WITH ACUTE*/
+ 0xd4, /* U+0000d4: LATIN CAPITAL LETTER O WITH CIRCUMFLEX*/
+ 0xd5, /* U+0000d5: LATIN CAPITAL LETTER O WITH TILDE*/
+ 0xd6, /* U+0000d6: LATIN CAPITAL LETTER O WITH DIAERESIS*/
+ 0xd7, /* U+0000d7: MULTIPLICATION SIGN*/
+ 0xd8, /* U+0000d8: LATIN CAPITAL LETTER O WITH STROKE*/
+ 0xd9, /* U+0000d9: LATIN CAPITAL LETTER U WITH GRAVE*/
+ 0xda, /* U+0000da: LATIN CAPITAL LETTER U WITH ACUTE*/
+ 0xdb, /* U+0000db: LATIN CAPITAL LETTER U WITH CIRCUMFLEX*/
+ 0xdc, /* U+0000dc: LATIN CAPITAL LETTER U WITH DIAERESIS*/
+ 0xdd, /* U+0000dd: LATIN CAPITAL LETTER Y WITH ACUTE*/
+ 0xde, /* U+0000de: LATIN CAPITAL LETTER THORN*/
+ 0xdf, /* U+0000df: LATIN SMALL LETTER SHARP S*/
+ 0xc0, /* U+0000e0: LATIN SMALL LETTER A WITH GRAVE*/
+ 0xc1, /* U+0000e1: LATIN SMALL LETTER A WITH ACUTE*/
+ 0xc2, /* U+0000e2: LATIN SMALL LETTER A WITH CIRCUMFLEX*/
+ 0xc3, /* U+0000e3: LATIN SMALL LETTER A WITH TILDE*/
+ 0xc4, /* U+0000e4: LATIN SMALL LETTER A WITH DIAERESIS*/
+ 0xc5, /* U+0000e5: LATIN SMALL LETTER A WITH RING ABOVE*/
+ 0xc6, /* U+0000e6: LATIN SMALL LETTER AE*/
+ 0xc7, /* U+0000e7: LATIN SMALL LETTER C WITH CEDILLA*/
+ 0xc8, /* U+0000e8: LATIN SMALL LETTER E WITH GRAVE*/
+ 0xc9, /* U+0000e9: LATIN SMALL LETTER E WITH ACUTE*/
+ 0xca, /* U+0000ea: LATIN SMALL LETTER E WITH CIRCUMFLEX*/
+ 0xcb, /* U+0000eb: LATIN SMALL LETTER E WITH DIAERESIS*/
+ 0xcc, /* U+0000ec: LATIN SMALL LETTER I WITH GRAVE*/
+ 0xcd, /* U+0000ed: LATIN SMALL LETTER I WITH ACUTE*/
+ 0xce, /* U+0000ee: LATIN SMALL LETTER I WITH CIRCUMFLEX*/
+ 0xcf, /* U+0000ef: LATIN SMALL LETTER I WITH DIAERESIS*/
+ 0xd0, /* U+0000f0: LATIN SMALL LETTER ETH*/
+ 0xd1, /* U+0000f1: LATIN SMALL LETTER N WITH TILDE*/
+ 0xd2, /* U+0000f2: LATIN SMALL LETTER O WITH GRAVE*/
+ 0xd3, /* U+0000f3: LATIN SMALL LETTER O WITH ACUTE*/
+ 0xd4, /* U+0000f4: LATIN SMALL LETTER O WITH CIRCUMFLEX*/
+ 0xd5, /* U+0000f5: LATIN SMALL LETTER O WITH TILDE*/
+ 0xd6, /* U+0000f6: LATIN SMALL LETTER O WITH DIAERESIS*/
+ 0xf7, /* U+0000f7: DIVISION SIGN*/
+ 0xd8, /* U+0000f8: LATIN SMALL LETTER O WITH STROKE*/
+ 0xd9, /* U+0000f9: LATIN SMALL LETTER U WITH GRAVE*/
+ 0xda, /* U+0000fa: LATIN SMALL LETTER U WITH ACUTE*/
+ 0xdb, /* U+0000fb: LATIN SMALL LETTER U WITH CIRCUMFLEX*/
+ 0xdc, /* U+0000fc: LATIN SMALL LETTER U WITH DIAERESIS*/
+ 0xdd, /* U+0000fd: LATIN SMALL LETTER Y WITH ACUTE*/
+ 0xde, /* U+0000fe: LATIN SMALL LETTER THORN*/
+ 0x178, /* U+0000ff: LATIN SMALL LETTER Y WITH DIAERESIS*/
+ 0x100, /* U+000100: LATIN CAPITAL LETTER A WITH MACRON*/
+ 0x100, /* U+000101: LATIN SMALL LETTER A WITH MACRON*/
+ 0x102, /* U+000102: LATIN CAPITAL LETTER A WITH BREVE*/
+ 0x102, /* U+000103: LATIN SMALL LETTER A WITH BREVE*/
+ 0x104, /* U+000104: LATIN CAPITAL LETTER A WITH OGONEK*/
+ 0x104, /* U+000105: LATIN SMALL LETTER A WITH OGONEK*/
+ 0x106, /* U+000106: LATIN CAPITAL LETTER C WITH ACUTE*/
+ 0x106, /* U+000107: LATIN SMALL LETTER C WITH ACUTE*/
+ 0x108, /* U+000108: LATIN CAPITAL LETTER C WITH CIRCUMFLEX*/
+ 0x108, /* U+000109: LATIN SMALL LETTER C WITH CIRCUMFLEX*/
+ 0x10a, /* U+00010a: LATIN CAPITAL LETTER C WITH DOT ABOVE*/
+ 0x10a, /* U+00010b: LATIN SMALL LETTER C WITH DOT ABOVE*/
+ 0x10c, /* U+00010c: LATIN CAPITAL LETTER C WITH CARON*/
+ 0x10c, /* U+00010d: LATIN SMALL LETTER C WITH CARON*/
+ 0x10e, /* U+00010e: LATIN CAPITAL LETTER D WITH CARON*/
+ 0x10e, /* U+00010f: LATIN SMALL LETTER D WITH CARON*/
+ 0x110, /* U+000110: LATIN CAPITAL LETTER D WITH STROKE*/
+ 0x110, /* U+000111: LATIN SMALL LETTER D WITH STROKE*/
+ 0x112, /* U+000112: LATIN CAPITAL LETTER E WITH MACRON*/
+ 0x112, /* U+000113: LATIN SMALL LETTER E WITH MACRON*/
+ 0x114, /* U+000114: LATIN CAPITAL LETTER E WITH BREVE*/
+ 0x114, /* U+000115: LATIN SMALL LETTER E WITH BREVE*/
+ 0x116, /* U+000116: LATIN CAPITAL LETTER E WITH DOT ABOVE*/
+ 0x116, /* U+000117: LATIN SMALL LETTER E WITH DOT ABOVE*/
+ 0x118, /* U+000118: LATIN CAPITAL LETTER E WITH OGONEK*/
+ 0x118, /* U+000119: LATIN SMALL LETTER E WITH OGONEK*/
+ 0x11a, /* U+00011a: LATIN CAPITAL LETTER E WITH CARON*/
+ 0x11a, /* U+00011b: LATIN SMALL LETTER E WITH CARON*/
+ 0x11c, /* U+00011c: LATIN CAPITAL LETTER G WITH CIRCUMFLEX*/
+ 0x11c, /* U+00011d: LATIN SMALL LETTER G WITH CIRCUMFLEX*/
+ 0x11e, /* U+00011e: LATIN CAPITAL LETTER G WITH BREVE*/
+ 0x11e, /* U+00011f: LATIN SMALL LETTER G WITH BREVE*/
+ 0x120, /* U+000120: LATIN CAPITAL LETTER G WITH DOT ABOVE*/
+ 0x120, /* U+000121: LATIN SMALL LETTER G WITH DOT ABOVE*/
+ 0x122, /* U+000122: LATIN CAPITAL LETTER G WITH CEDILLA*/
+ 0x122, /* U+000123: LATIN SMALL LETTER G WITH CEDILLA*/
+ 0x124, /* U+000124: LATIN CAPITAL LETTER H WITH CIRCUMFLEX*/
+ 0x124, /* U+000125: LATIN SMALL LETTER H WITH CIRCUMFLEX*/
+ 0x126, /* U+000126: LATIN CAPITAL LETTER H WITH STROKE*/
+ 0x126, /* U+000127: LATIN SMALL LETTER H WITH STROKE*/
+ 0x128, /* U+000128: LATIN CAPITAL LETTER I WITH TILDE*/
+ 0x128, /* U+000129: LATIN SMALL LETTER I WITH TILDE*/
+ 0x12a, /* U+00012a: LATIN CAPITAL LETTER I WITH MACRON*/
+ 0x12a, /* U+00012b: LATIN SMALL LETTER I WITH MACRON*/
+ 0x12c, /* U+00012c: LATIN CAPITAL LETTER I WITH BREVE*/
+ 0x12c, /* U+00012d: LATIN SMALL LETTER I WITH BREVE*/
+ 0x12e, /* U+00012e: LATIN CAPITAL LETTER I WITH OGONEK*/
+ 0x12e, /* U+00012f: LATIN SMALL LETTER I WITH OGONEK*/
+ 0x130, /* U+000130: LATIN CAPITAL LETTER I WITH DOT ABOVE*/
+ 0x49, /* U+000131: LATIN SMALL LETTER DOTLESS I*/
+ 0x132, /* U+000132: LATIN CAPITAL LIGATURE IJ*/
+ 0x132, /* U+000133: LATIN SMALL LIGATURE IJ*/
+ 0x134, /* U+000134: LATIN CAPITAL LETTER J WITH CIRCUMFLEX*/
+ 0x134, /* U+000135: LATIN SMALL LETTER J WITH CIRCUMFLEX*/
+ 0x136, /* U+000136: LATIN CAPITAL LETTER K WITH CEDILLA*/
+ 0x136, /* U+000137: LATIN SMALL LETTER K WITH CEDILLA*/
+ 0x138, /* U+000138: LATIN SMALL LETTER KRA*/
+ 0x139, /* U+000139: LATIN CAPITAL LETTER L WITH ACUTE*/
+ 0x139, /* U+00013a: LATIN SMALL LETTER L WITH ACUTE*/
+ 0x13b, /* U+00013b: LATIN CAPITAL LETTER L WITH CEDILLA*/
+ 0x13b, /* U+00013c: LATIN SMALL LETTER L WITH CEDILLA*/
+ 0x13d, /* U+00013d: LATIN CAPITAL LETTER L WITH CARON*/
+ 0x13d, /* U+00013e: LATIN SMALL LETTER L WITH CARON*/
+ 0x13f, /* U+00013f: LATIN CAPITAL LETTER L WITH MIDDLE DOT*/
+ 0x13f, /* U+000140: LATIN SMALL LETTER L WITH MIDDLE DOT*/
+ 0x141, /* U+000141: LATIN CAPITAL LETTER L WITH STROKE*/
+ 0x141, /* U+000142: LATIN SMALL LETTER L WITH STROKE*/
+ 0x143, /* U+000143: LATIN CAPITAL LETTER N WITH ACUTE*/
+ 0x143, /* U+000144: LATIN SMALL LETTER N WITH ACUTE*/
+ 0x145, /* U+000145: LATIN CAPITAL LETTER N WITH CEDILLA*/
+ 0x145, /* U+000146: LATIN SMALL LETTER N WITH CEDILLA*/
+ 0x147, /* U+000147: LATIN CAPITAL LETTER N WITH CARON*/
+ 0x147, /* U+000148: LATIN SMALL LETTER N WITH CARON*/
+ 0x149, /* U+000149: LATIN SMALL LETTER N PRECEDED BY APOSTROPHE*/
+ 0x14a, /* U+00014a: LATIN CAPITAL LETTER ENG*/
+ 0x14a, /* U+00014b: LATIN SMALL LETTER ENG*/
+ 0x14c, /* U+00014c: LATIN CAPITAL LETTER O WITH MACRON*/
+ 0x14c, /* U+00014d: LATIN SMALL LETTER O WITH MACRON*/
+ 0x14e, /* U+00014e: LATIN CAPITAL LETTER O WITH BREVE*/
+ 0x14e, /* U+00014f: LATIN SMALL LETTER O WITH BREVE*/
+ 0x150, /* U+000150: LATIN CAPITAL LETTER O WITH DOUBLE ACUTE*/
+ 0x150, /* U+000151: LATIN SMALL LETTER O WITH DOUBLE ACUTE*/
+ 0x152, /* U+000152: LATIN CAPITAL LIGATURE OE*/
+ 0x152, /* U+000153: LATIN SMALL LIGATURE OE*/
+ 0x154, /* U+000154: LATIN CAPITAL LETTER R WITH ACUTE*/
+ 0x154, /* U+000155: LATIN SMALL LETTER R WITH ACUTE*/
+ 0x156, /* U+000156: LATIN CAPITAL LETTER R WITH CEDILLA*/
+ 0x156, /* U+000157: LATIN SMALL LETTER R WITH CEDILLA*/
+ 0x158, /* U+000158: LATIN CAPITAL LETTER R WITH CARON*/
+ 0x158, /* U+000159: LATIN SMALL LETTER R WITH CARON*/
+ 0x15a, /* U+00015a: LATIN CAPITAL LETTER S WITH ACUTE*/
+ 0x15a, /* U+00015b: LATIN SMALL LETTER S WITH ACUTE*/
+ 0x15c, /* U+00015c: LATIN CAPITAL LETTER S WITH CIRCUMFLEX*/
+ 0x15c, /* U+00015d: LATIN SMALL LETTER S WITH CIRCUMFLEX*/
+ 0x15e, /* U+00015e: LATIN CAPITAL LETTER S WITH CEDILLA*/
+ 0x15e, /* U+00015f: LATIN SMALL LETTER S WITH CEDILLA*/
+ 0x160, /* U+000160: LATIN CAPITAL LETTER S WITH CARON*/
+ 0x160, /* U+000161: LATIN SMALL LETTER S WITH CARON*/
+ 0x162, /* U+000162: LATIN CAPITAL LETTER T WITH CEDILLA*/
+ 0x162, /* U+000163: LATIN SMALL LETTER T WITH CEDILLA*/
+ 0x164, /* U+000164: LATIN CAPITAL LETTER T WITH CARON*/
+ 0x164, /* U+000165: LATIN SMALL LETTER T WITH CARON*/
+ 0x166, /* U+000166: LATIN CAPITAL LETTER T WITH STROKE*/
+ 0x166, /* U+000167: LATIN SMALL LETTER T WITH STROKE*/
+ 0x168, /* U+000168: LATIN CAPITAL LETTER U WITH TILDE*/
+ 0x168, /* U+000169: LATIN SMALL LETTER U WITH TILDE*/
+ 0x16a, /* U+00016a: LATIN CAPITAL LETTER U WITH MACRON*/
+ 0x16a, /* U+00016b: LATIN SMALL LETTER U WITH MACRON*/
+ 0x16c, /* U+00016c: LATIN CAPITAL LETTER U WITH BREVE*/
+ 0x16c, /* U+00016d: LATIN SMALL LETTER U WITH BREVE*/
+ 0x16e, /* U+00016e: LATIN CAPITAL LETTER U WITH RING ABOVE*/
+ 0x16e, /* U+00016f: LATIN SMALL LETTER U WITH RING ABOVE*/
+ 0x170, /* U+000170: LATIN CAPITAL LETTER U WITH DOUBLE ACUTE*/
+ 0x170, /* U+000171: LATIN SMALL LETTER U WITH DOUBLE ACUTE*/
+ 0x172, /* U+000172: LATIN CAPITAL LETTER U WITH OGONEK*/
+ 0x172, /* U+000173: LATIN SMALL LETTER U WITH OGONEK*/
+ 0x174, /* U+000174: LATIN CAPITAL LETTER W WITH CIRCUMFLEX*/
+ 0x174, /* U+000175: LATIN SMALL LETTER W WITH CIRCUMFLEX*/
+ 0x176, /* U+000176: LATIN CAPITAL LETTER Y WITH CIRCUMFLEX*/
+ 0x176, /* U+000177: LATIN SMALL LETTER Y WITH CIRCUMFLEX*/
+ 0x178, /* U+000178: LATIN CAPITAL LETTER Y WITH DIAERESIS*/
+ 0x179, /* U+000179: LATIN CAPITAL LETTER Z WITH ACUTE*/
+ 0x179, /* U+00017a: LATIN SMALL LETTER Z WITH ACUTE*/
+ 0x17b, /* U+00017b: LATIN CAPITAL LETTER Z WITH DOT ABOVE*/
+ 0x17b, /* U+00017c: LATIN SMALL LETTER Z WITH DOT ABOVE*/
+ 0x17d, /* U+00017d: LATIN CAPITAL LETTER Z WITH CARON*/
+ 0x17d, /* U+00017e: LATIN SMALL LETTER Z WITH CARON*/
+ 0x53, /* U+00017f: LATIN SMALL LETTER LONG S*/
+ 0x243, /* U+000180: LATIN SMALL LETTER B WITH STROKE*/
+ 0x181, /* U+000181: LATIN CAPITAL LETTER B WITH HOOK*/
+ 0x182, /* U+000182: LATIN CAPITAL LETTER B WITH TOPBAR*/
+ 0x182, /* U+000183: LATIN SMALL LETTER B WITH TOPBAR*/
+ 0x184, /* U+000184: LATIN CAPITAL LETTER TONE SIX*/
+ 0x184, /* U+000185: LATIN SMALL LETTER TONE SIX*/
+ 0x186, /* U+000186: LATIN CAPITAL LETTER OPEN O*/
+ 0x187, /* U+000187: LATIN CAPITAL LETTER C WITH HOOK*/
+ 0x187, /* U+000188: LATIN SMALL LETTER C WITH HOOK*/
+ 0x189, /* U+000189: LATIN CAPITAL LETTER AFRICAN D*/
+ 0x18a, /* U+00018a: LATIN CAPITAL LETTER D WITH HOOK*/
+ 0x18b, /* U+00018b: LATIN CAPITAL LETTER D WITH TOPBAR*/
+ 0x18b, /* U+00018c: LATIN SMALL LETTER D WITH TOPBAR*/
+ 0x18d, /* U+00018d: LATIN SMALL LETTER TURNED DELTA*/
+ 0x18e, /* U+00018e: LATIN CAPITAL LETTER REVERSED E*/
+ 0x18f, /* U+00018f: LATIN CAPITAL LETTER SCHWA*/
+ 0x190, /* U+000190: LATIN CAPITAL LETTER OPEN E*/
+ 0x191, /* U+000191: LATIN CAPITAL LETTER F WITH HOOK*/
+ 0x191, /* U+000192: LATIN SMALL LETTER F WITH HOOK*/
+ 0x193, /* U+000193: LATIN CAPITAL LETTER G WITH HOOK*/
+ 0x194, /* U+000194: LATIN CAPITAL LETTER GAMMA*/
+ 0x1f6, /* U+000195: LATIN SMALL LETTER HV*/
+ 0x196, /* U+000196: LATIN CAPITAL LETTER IOTA*/
+ 0x197, /* U+000197: LATIN CAPITAL LETTER I WITH STROKE*/
+ 0x198, /* U+000198: LATIN CAPITAL LETTER K WITH HOOK*/
+ 0x198, /* U+000199: LATIN SMALL LETTER K WITH HOOK*/
+ 0x23d, /* U+00019a: LATIN SMALL LETTER L WITH BAR*/
+ 0x19b, /* U+00019b: LATIN SMALL LETTER LAMBDA WITH STROKE*/
+ 0x19c, /* U+00019c: LATIN CAPITAL LETTER TURNED M*/
+ 0x19d, /* U+00019d: LATIN CAPITAL LETTER N WITH LEFT HOOK*/
+ 0x220, /* U+00019e: LATIN SMALL LETTER N WITH LONG RIGHT LEG*/
+ 0x19f, /* U+00019f: LATIN CAPITAL LETTER O WITH MIDDLE TILDE*/
+ 0x1a0, /* U+0001a0: LATIN CAPITAL LETTER O WITH HORN*/
+ 0x1a0, /* U+0001a1: LATIN SMALL LETTER O WITH HORN*/
+ 0x1a2, /* U+0001a2: LATIN CAPITAL LETTER OI*/
+ 0x1a2, /* U+0001a3: LATIN SMALL LETTER OI*/
+ 0x1a4, /* U+0001a4: LATIN CAPITAL LETTER P WITH HOOK*/
+ 0x1a4, /* U+0001a5: LATIN SMALL LETTER P WITH HOOK*/
+ 0x1a6, /* U+0001a6: LATIN LETTER YR*/
+ 0x1a7, /* U+0001a7: LATIN CAPITAL LETTER TONE TWO*/
+ 0x1a7, /* U+0001a8: LATIN SMALL LETTER TONE TWO*/
+ 0x1a9, /* U+0001a9: LATIN CAPITAL LETTER ESH*/
+ 0x1aa, /* U+0001aa: LATIN LETTER REVERSED ESH LOOP*/
+ 0x1ab, /* U+0001ab: LATIN SMALL LETTER T WITH PALATAL HOOK*/
+ 0x1ac, /* U+0001ac: LATIN CAPITAL LETTER T WITH HOOK*/
+ 0x1ac, /* U+0001ad: LATIN SMALL LETTER T WITH HOOK*/
+ 0x1ae, /* U+0001ae: LATIN CAPITAL LETTER T WITH RETROFLEX HOOK*/
+ 0x1af, /* U+0001af: LATIN CAPITAL LETTER U WITH HORN*/
+ 0x1af, /* U+0001b0: LATIN SMALL LETTER U WITH HORN*/
+ 0x1b1, /* U+0001b1: LATIN CAPITAL LETTER UPSILON*/
+ 0x1b2, /* U+0001b2: LATIN CAPITAL LETTER V WITH HOOK*/
+ 0x1b3, /* U+0001b3: LATIN CAPITAL LETTER Y WITH HOOK*/
+ 0x1b3, /* U+0001b4: LATIN SMALL LETTER Y WITH HOOK*/
+ 0x1b5, /* U+0001b5: LATIN CAPITAL LETTER Z WITH STROKE*/
+ 0x1b5, /* U+0001b6: LATIN SMALL LETTER Z WITH STROKE*/
+ 0x1b7, /* U+0001b7: LATIN CAPITAL LETTER EZH*/
+ 0x1b8, /* U+0001b8: LATIN CAPITAL LETTER EZH REVERSED*/
+ 0x1b8, /* U+0001b9: LATIN SMALL LETTER EZH REVERSED*/
+ 0x1ba, /* U+0001ba: LATIN SMALL LETTER EZH WITH TAIL*/
+ 0x1bb, /* U+0001bb: LATIN LETTER TWO WITH STROKE*/
+ 0x1bc, /* U+0001bc: LATIN CAPITAL LETTER TONE FIVE*/
+ 0x1bc, /* U+0001bd: LATIN SMALL LETTER TONE FIVE*/
+ 0x1be, /* U+0001be: LATIN LETTER INVERTED GLOTTAL STOP WITH STROKE*/
+ 0x1f7, /* U+0001bf: LATIN LETTER WYNN*/
+ 0x1c0, /* U+0001c0: LATIN LETTER DENTAL CLICK*/
+ 0x1c1, /* U+0001c1: LATIN LETTER LATERAL CLICK*/
+ 0x1c2, /* U+0001c2: LATIN LETTER ALVEOLAR CLICK*/
+ 0x1c3, /* U+0001c3: LATIN LETTER RETROFLEX CLICK*/
+ 0x1c4, /* U+0001c4: LATIN CAPITAL LETTER DZ WITH CARON*/
+ 0x1c4, /* U+0001c5: LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON*/
+ 0x1c4, /* U+0001c6: LATIN SMALL LETTER DZ WITH CARON*/
+ 0x1c7, /* U+0001c7: LATIN CAPITAL LETTER LJ*/
+ 0x1c7, /* U+0001c8: LATIN CAPITAL LETTER L WITH SMALL LETTER J*/
+ 0x1c7, /* U+0001c9: LATIN SMALL LETTER LJ*/
+ 0x1ca, /* U+0001ca: LATIN CAPITAL LETTER NJ*/
+ 0x1ca, /* U+0001cb: LATIN CAPITAL LETTER N WITH SMALL LETTER J*/
+ 0x1ca, /* U+0001cc: LATIN SMALL LETTER NJ*/
+ 0x1cd, /* U+0001cd: LATIN CAPITAL LETTER A WITH CARON*/
+ 0x1cd, /* U+0001ce: LATIN SMALL LETTER A WITH CARON*/
+ 0x1cf, /* U+0001cf: LATIN CAPITAL LETTER I WITH CARON*/
+ 0x1cf, /* U+0001d0: LATIN SMALL LETTER I WITH CARON*/
+ 0x1d1, /* U+0001d1: LATIN CAPITAL LETTER O WITH CARON*/
+ 0x1d1, /* U+0001d2: LATIN SMALL LETTER O WITH CARON*/
+ 0x1d3, /* U+0001d3: LATIN CAPITAL LETTER U WITH CARON*/
+ 0x1d3, /* U+0001d4: LATIN SMALL LETTER U WITH CARON*/
+ 0x1d5, /* U+0001d5: LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON*/
+ 0x1d5, /* U+0001d6: LATIN SMALL LETTER U WITH DIAERESIS AND MACRON*/
+ 0x1d7, /* U+0001d7: LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE*/
+ 0x1d7, /* U+0001d8: LATIN SMALL LETTER U WITH DIAERESIS AND ACUTE*/
+ 0x1d9, /* U+0001d9: LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON*/
+ 0x1d9, /* U+0001da: LATIN SMALL LETTER U WITH DIAERESIS AND CARON*/
+ 0x1db, /* U+0001db: LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE*/
+ 0x1db, /* U+0001dc: LATIN SMALL LETTER U WITH DIAERESIS AND GRAVE*/
+ 0x18e, /* U+0001dd: LATIN SMALL LETTER TURNED E*/
+ 0x1de, /* U+0001de: LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON*/
+ 0x1de, /* U+0001df: LATIN SMALL LETTER A WITH DIAERESIS AND MACRON*/
+ 0x1e0, /* U+0001e0: LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON*/
+ 0x1e0, /* U+0001e1: LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON*/
+ 0x1e2, /* U+0001e2: LATIN CAPITAL LETTER AE WITH MACRON*/
+ 0x1e2, /* U+0001e3: LATIN SMALL LETTER AE WITH MACRON*/
+ 0x1e4, /* U+0001e4: LATIN CAPITAL LETTER G WITH STROKE*/
+ 0x1e4, /* U+0001e5: LATIN SMALL LETTER G WITH STROKE*/
+ 0x1e6, /* U+0001e6: LATIN CAPITAL LETTER G WITH CARON*/
+ 0x1e6, /* U+0001e7: LATIN SMALL LETTER G WITH CARON*/
+ 0x1e8, /* U+0001e8: LATIN CAPITAL LETTER K WITH CARON*/
+ 0x1e8, /* U+0001e9: LATIN SMALL LETTER K WITH CARON*/
+ 0x1ea, /* U+0001ea: LATIN CAPITAL LETTER O WITH OGONEK*/
+ 0x1ea, /* U+0001eb: LATIN SMALL LETTER O WITH OGONEK*/
+ 0x1ec, /* U+0001ec: LATIN CAPITAL LETTER O WITH OGONEK AND MACRON*/
+ 0x1ec, /* U+0001ed: LATIN SMALL LETTER O WITH OGONEK AND MACRON*/
+ 0x1ee, /* U+0001ee: LATIN CAPITAL LETTER EZH WITH CARON*/
+ 0x1ee, /* U+0001ef: LATIN SMALL LETTER EZH WITH CARON*/
+ 0x1f0, /* U+0001f0: LATIN SMALL LETTER J WITH CARON*/
+ 0x1f1, /* U+0001f1: LATIN CAPITAL LETTER DZ*/
+ 0x1f1, /* U+0001f2: LATIN CAPITAL LETTER D WITH SMALL LETTER Z*/
+ 0x1f1, /* U+0001f3: LATIN SMALL LETTER DZ*/
+ 0x1f4, /* U+0001f4: LATIN CAPITAL LETTER G WITH ACUTE*/
+ 0x1f4, /* U+0001f5: LATIN SMALL LETTER G WITH ACUTE*/
+ 0x1f6, /* U+0001f6: LATIN CAPITAL LETTER HWAIR*/
+ 0x1f7, /* U+0001f7: LATIN CAPITAL LETTER WYNN*/
+ 0x1f8, /* U+0001f8: LATIN CAPITAL LETTER N WITH GRAVE*/
+ 0x1f8, /* U+0001f9: LATIN SMALL LETTER N WITH GRAVE*/
+ 0x1fa, /* U+0001fa: LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE*/
+ 0x1fa, /* U+0001fb: LATIN SMALL LETTER A WITH RING ABOVE AND ACUTE*/
+ 0x1fc, /* U+0001fc: LATIN CAPITAL LETTER AE WITH ACUTE*/
+ 0x1fc, /* U+0001fd: LATIN SMALL LETTER AE WITH ACUTE*/
+ 0x1fe, /* U+0001fe: LATIN CAPITAL LETTER O WITH STROKE AND ACUTE*/
+ 0x1fe, /* U+0001ff: LATIN SMALL LETTER O WITH STROKE AND ACUTE*/
+ 0x200, /* U+000200: LATIN CAPITAL LETTER A WITH DOUBLE GRAVE*/
+ 0x200, /* U+000201: LATIN SMALL LETTER A WITH DOUBLE GRAVE*/
+ 0x202, /* U+000202: LATIN CAPITAL LETTER A WITH INVERTED BREVE*/
+ 0x202, /* U+000203: LATIN SMALL LETTER A WITH INVERTED BREVE*/
+ 0x204, /* U+000204: LATIN CAPITAL LETTER E WITH DOUBLE GRAVE*/
+ 0x204, /* U+000205: LATIN SMALL LETTER E WITH DOUBLE GRAVE*/
+ 0x206, /* U+000206: LATIN CAPITAL LETTER E WITH INVERTED BREVE*/
+ 0x206, /* U+000207: LATIN SMALL LETTER E WITH INVERTED BREVE*/
+ 0x208, /* U+000208: LATIN CAPITAL LETTER I WITH DOUBLE GRAVE*/
+ 0x208, /* U+000209: LATIN SMALL LETTER I WITH DOUBLE GRAVE*/
+ 0x20a, /* U+00020a: LATIN CAPITAL LETTER I WITH INVERTED BREVE*/
+ 0x20a, /* U+00020b: LATIN SMALL LETTER I WITH INVERTED BREVE*/
+ 0x20c, /* U+00020c: LATIN CAPITAL LETTER O WITH DOUBLE GRAVE*/
+ 0x20c, /* U+00020d: LATIN SMALL LETTER O WITH DOUBLE GRAVE*/
+ 0x20e, /* U+00020e: LATIN CAPITAL LETTER O WITH INVERTED BREVE*/
+ 0x20e, /* U+00020f: LATIN SMALL LETTER O WITH INVERTED BREVE*/
+ 0x210, /* U+000210: LATIN CAPITAL LETTER R WITH DOUBLE GRAVE*/
+ 0x210, /* U+000211: LATIN SMALL LETTER R WITH DOUBLE GRAVE*/
+ 0x212, /* U+000212: LATIN CAPITAL LETTER R WITH INVERTED BREVE*/
+ 0x212, /* U+000213: LATIN SMALL LETTER R WITH INVERTED BREVE*/
+ 0x214, /* U+000214: LATIN CAPITAL LETTER U WITH DOUBLE GRAVE*/
+ 0x214, /* U+000215: LATIN SMALL LETTER U WITH DOUBLE GRAVE*/
+ 0x216, /* U+000216: LATIN CAPITAL LETTER U WITH INVERTED BREVE*/
+ 0x216, /* U+000217: LATIN SMALL LETTER U WITH INVERTED BREVE*/
+ 0x218, /* U+000218: LATIN CAPITAL LETTER S WITH COMMA BELOW*/
+ 0x218, /* U+000219: LATIN SMALL LETTER S WITH COMMA BELOW*/
+ 0x21a, /* U+00021a: LATIN CAPITAL LETTER T WITH COMMA BELOW*/
+ 0x21a, /* U+00021b: LATIN SMALL LETTER T WITH COMMA BELOW*/
+ 0x21c, /* U+00021c: LATIN CAPITAL LETTER YOGH*/
+ 0x21c, /* U+00021d: LATIN SMALL LETTER YOGH*/
+ 0x21e, /* U+00021e: LATIN CAPITAL LETTER H WITH CARON*/
+ 0x21e, /* U+00021f: LATIN SMALL LETTER H WITH CARON*/
+ 0x220, /* U+000220: LATIN CAPITAL LETTER N WITH LONG RIGHT LEG*/
+ 0x221, /* U+000221: LATIN SMALL LETTER D WITH CURL*/
+ 0x222, /* U+000222: LATIN CAPITAL LETTER OU*/
+ 0x222, /* U+000223: LATIN SMALL LETTER OU*/
+ 0x224, /* U+000224: LATIN CAPITAL LETTER Z WITH HOOK*/
+ 0x224, /* U+000225: LATIN SMALL LETTER Z WITH HOOK*/
+ 0x226, /* U+000226: LATIN CAPITAL LETTER A WITH DOT ABOVE*/
+ 0x226, /* U+000227: LATIN SMALL LETTER A WITH DOT ABOVE*/
+ 0x228, /* U+000228: LATIN CAPITAL LETTER E WITH CEDILLA*/
+ 0x228, /* U+000229: LATIN SMALL LETTER E WITH CEDILLA*/
+ 0x22a, /* U+00022a: LATIN CAPITAL LETTER O WITH DIAERESIS AND MACRON*/
+ 0x22a, /* U+00022b: LATIN SMALL LETTER O WITH DIAERESIS AND MACRON*/
+ 0x22c, /* U+00022c: LATIN CAPITAL LETTER O WITH TILDE AND MACRON*/
+ 0x22c, /* U+00022d: LATIN SMALL LETTER O WITH TILDE AND MACRON*/
+ 0x22e, /* U+00022e: LATIN CAPITAL LETTER O WITH DOT ABOVE*/
+ 0x22e, /* U+00022f: LATIN SMALL LETTER O WITH DOT ABOVE*/
+ 0x230, /* U+000230: LATIN CAPITAL LETTER O WITH DOT ABOVE AND MACRON*/
+ 0x230, /* U+000231: LATIN SMALL LETTER O WITH DOT ABOVE AND MACRON*/
+ 0x232, /* U+000232: LATIN CAPITAL LETTER Y WITH MACRON*/
+ 0x232, /* U+000233: LATIN SMALL LETTER Y WITH MACRON*/
+ 0x234, /* U+000234: LATIN SMALL LETTER L WITH CURL*/
+ 0x235, /* U+000235: LATIN SMALL LETTER N WITH CURL*/
+ 0x236, /* U+000236: LATIN SMALL LETTER T WITH CURL*/
+ 0x237, /* U+000237: LATIN SMALL LETTER DOTLESS J*/
+ 0x238, /* U+000238: LATIN SMALL LETTER DB DIGRAPH*/
+ 0x239, /* U+000239: LATIN SMALL LETTER QP DIGRAPH*/
+ 0x23a, /* U+00023a: LATIN CAPITAL LETTER A WITH STROKE*/
+ 0x23b, /* U+00023b: LATIN CAPITAL LETTER C WITH STROKE*/
+ 0x23b, /* U+00023c: LATIN SMALL LETTER C WITH STROKE*/
+ 0x23d, /* U+00023d: LATIN CAPITAL LETTER L WITH BAR*/
+ 0x23e, /* U+00023e: LATIN CAPITAL LETTER T WITH DIAGONAL STROKE*/
+ 0x2c7e, /* U+00023f: LATIN SMALL LETTER S WITH SWASH TAIL*/
+ 0x2c7f, /* U+000240: LATIN SMALL LETTER Z WITH SWASH TAIL*/
+ 0x241, /* U+000241: LATIN CAPITAL LETTER GLOTTAL STOP*/
+ 0x241, /* U+000242: LATIN SMALL LETTER GLOTTAL STOP*/
+ 0x243, /* U+000243: LATIN CAPITAL LETTER B WITH STROKE*/
+ 0x244, /* U+000244: LATIN CAPITAL LETTER U BAR*/
+ 0x245, /* U+000245: LATIN CAPITAL LETTER TURNED V*/
+ 0x246, /* U+000246: LATIN CAPITAL LETTER E WITH STROKE*/
+ 0x246, /* U+000247: LATIN SMALL LETTER E WITH STROKE*/
+ 0x248, /* U+000248: LATIN CAPITAL LETTER J WITH STROKE*/
+ 0x248, /* U+000249: LATIN SMALL LETTER J WITH STROKE*/
+ 0x24a, /* U+00024a: LATIN CAPITAL LETTER SMALL Q WITH HOOK TAIL*/
+ 0x24a, /* U+00024b: LATIN SMALL LETTER Q WITH HOOK TAIL*/
+ 0x24c, /* U+00024c: LATIN CAPITAL LETTER R WITH STROKE*/
+ 0x24c, /* U+00024d: LATIN SMALL LETTER R WITH STROKE*/
+ 0x24e, /* U+00024e: LATIN CAPITAL LETTER Y WITH STROKE*/
+ 0x24e, /* U+00024f: LATIN SMALL LETTER Y WITH STROKE*/
+ 0x2c6f, /* U+000250: LATIN SMALL LETTER TURNED A*/
+ 0x2c6d, /* U+000251: LATIN SMALL LETTER ALPHA*/
+ 0x2c70, /* U+000252: LATIN SMALL LETTER TURNED ALPHA*/
+ 0x181, /* U+000253: LATIN SMALL LETTER B WITH HOOK*/
+ 0x186, /* U+000254: LATIN SMALL LETTER OPEN O*/
+ 0x255, /* U+000255: LATIN SMALL LETTER C WITH CURL*/
+ 0x189, /* U+000256: LATIN SMALL LETTER D WITH TAIL*/
+ 0x18a, /* U+000257: LATIN SMALL LETTER D WITH HOOK*/
+ 0x258, /* U+000258: LATIN SMALL LETTER REVERSED E*/
+ 0x18f, /* U+000259: LATIN SMALL LETTER SCHWA*/
+ 0x25a, /* U+00025a: LATIN SMALL LETTER SCHWA WITH HOOK*/
+ 0x190, /* U+00025b: LATIN SMALL LETTER OPEN E*/
+ 0x25c, /* U+00025c: LATIN SMALL LETTER REVERSED OPEN E*/
+ 0x25d, /* U+00025d: LATIN SMALL LETTER REVERSED OPEN E WITH HOOK*/
+ 0x25e, /* U+00025e: LATIN SMALL LETTER CLOSED REVERSED OPEN E*/
+ 0x25f, /* U+00025f: LATIN SMALL LETTER DOTLESS J WITH STROKE*/
+ 0x193, /* U+000260: LATIN SMALL LETTER G WITH HOOK*/
+ 0x261, /* U+000261: LATIN SMALL LETTER SCRIPT G*/
+ 0x262, /* U+000262: LATIN LETTER SMALL CAPITAL G*/
+ 0x194, /* U+000263: LATIN SMALL LETTER GAMMA*/
+ 0x264, /* U+000264: LATIN SMALL LETTER RAMS HORN*/
+ 0xa78d, /* U+000265: LATIN SMALL LETTER TURNED H*/
+ 0xa7aa, /* U+000266: LATIN SMALL LETTER H WITH HOOK*/
+ 0x267, /* U+000267: LATIN SMALL LETTER HENG WITH HOOK*/
+ 0x197, /* U+000268: LATIN SMALL LETTER I WITH STROKE*/
+ 0x196, /* U+000269: LATIN SMALL LETTER IOTA*/
+ 0x26a, /* U+00026a: LATIN LETTER SMALL CAPITAL I*/
+ 0x2c62, /* U+00026b: LATIN SMALL LETTER L WITH MIDDLE TILDE*/
+ 0x26c, /* U+00026c: LATIN SMALL LETTER L WITH BELT*/
+ 0x26d, /* U+00026d: LATIN SMALL LETTER L WITH RETROFLEX HOOK*/
+ 0x26e, /* U+00026e: LATIN SMALL LETTER LEZH*/
+ 0x19c, /* U+00026f: LATIN SMALL LETTER TURNED M*/
+ 0x270, /* U+000270: LATIN SMALL LETTER TURNED M WITH LONG LEG*/
+ 0x2c6e, /* U+000271: LATIN SMALL LETTER M WITH HOOK*/
+ 0x19d, /* U+000272: LATIN SMALL LETTER N WITH LEFT HOOK*/
+ 0x273, /* U+000273: LATIN SMALL LETTER N WITH RETROFLEX HOOK*/
+ 0x274, /* U+000274: LATIN LETTER SMALL CAPITAL N*/
+ 0x19f, /* U+000275: LATIN SMALL LETTER BARRED O*/
+ 0x276, /* U+000276: LATIN LETTER SMALL CAPITAL OE*/
+ 0x277, /* U+000277: LATIN SMALL LETTER CLOSED OMEGA*/
+ 0x278, /* U+000278: LATIN SMALL LETTER PHI*/
+ 0x279, /* U+000279: LATIN SMALL LETTER TURNED R*/
+ 0x27a, /* U+00027a: LATIN SMALL LETTER TURNED R WITH LONG LEG*/
+ 0x27b, /* U+00027b: LATIN SMALL LETTER TURNED R WITH HOOK*/
+ 0x27c, /* U+00027c: LATIN SMALL LETTER R WITH LONG LEG*/
+ 0x2c64, /* U+00027d: LATIN SMALL LETTER R WITH TAIL*/
+ 0x27e, /* U+00027e: LATIN SMALL LETTER R WITH FISHHOOK*/
+ 0x27f, /* U+00027f: LATIN SMALL LETTER REVERSED R WITH FISHHOOK*/
+ 0x1a6, /* U+000280: LATIN LETTER SMALL CAPITAL R*/
+ 0x281, /* U+000281: LATIN LETTER SMALL CAPITAL INVERTED R*/
+ 0x282, /* U+000282: LATIN SMALL LETTER S WITH HOOK*/
+ 0x1a9, /* U+000283: LATIN SMALL LETTER ESH*/
+ 0x284, /* U+000284: LATIN SMALL LETTER DOTLESS J WITH STROKE AND HOOK*/
+ 0x285, /* U+000285: LATIN SMALL LETTER SQUAT REVERSED ESH*/
+ 0x286, /* U+000286: LATIN SMALL LETTER ESH WITH CURL*/
+ 0x287, /* U+000287: LATIN SMALL LETTER TURNED T*/
+ 0x1ae, /* U+000288: LATIN SMALL LETTER T WITH RETROFLEX HOOK*/
+ 0x244, /* U+000289: LATIN SMALL LETTER U BAR*/
+ 0x1b1, /* U+00028a: LATIN SMALL LETTER UPSILON*/
+ 0x1b2, /* U+00028b: LATIN SMALL LETTER V WITH HOOK*/
+ 0x245, /* U+00028c: LATIN SMALL LETTER TURNED V*/
+ 0x28d, /* U+00028d: LATIN SMALL LETTER TURNED W*/
+ 0x28e, /* U+00028e: LATIN SMALL LETTER TURNED Y*/
+ 0x28f, /* U+00028f: LATIN LETTER SMALL CAPITAL Y*/
+ 0x290, /* U+000290: LATIN SMALL LETTER Z WITH RETROFLEX HOOK*/
+ 0x291, /* U+000291: LATIN SMALL LETTER Z WITH CURL*/
+ 0x1b7, /* U+000292: LATIN SMALL LETTER EZH*/
+ 0x293, /* U+000293: LATIN SMALL LETTER EZH WITH CURL*/
+ 0x294, /* U+000294: LATIN LETTER GLOTTAL STOP*/
+ 0x295, /* U+000295: LATIN LETTER PHARYNGEAL VOICED FRICATIVE*/
+ 0x296, /* U+000296: LATIN LETTER INVERTED GLOTTAL STOP*/
+ 0x297, /* U+000297: LATIN LETTER STRETCHED C*/
+ 0x298, /* U+000298: LATIN LETTER BILABIAL CLICK*/
+ 0x299, /* U+000299: LATIN LETTER SMALL CAPITAL B*/
+ 0x29a, /* U+00029a: LATIN SMALL LETTER CLOSED OPEN E*/
+ 0x29b, /* U+00029b: LATIN LETTER SMALL CAPITAL G WITH HOOK*/
+ 0x29c, /* U+00029c: LATIN LETTER SMALL CAPITAL H*/
+ 0x29d, /* U+00029d: LATIN SMALL LETTER J WITH CROSSED-TAIL*/
+ 0x29e, /* U+00029e: LATIN SMALL LETTER TURNED K*/
+ 0x29f, /* U+00029f: LATIN LETTER SMALL CAPITAL L*/
+ 0x2a0, /* U+0002a0: LATIN SMALL LETTER Q WITH HOOK*/
+ 0x2a1, /* U+0002a1: LATIN LETTER GLOTTAL STOP WITH STROKE*/
+ 0x2a2, /* U+0002a2: LATIN LETTER REVERSED GLOTTAL STOP WITH STROKE*/
+ 0x2a3, /* U+0002a3: LATIN SMALL LETTER DZ DIGRAPH*/
+ 0x2a4, /* U+0002a4: LATIN SMALL LETTER DEZH DIGRAPH*/
+ 0x2a5, /* U+0002a5: LATIN SMALL LETTER DZ DIGRAPH WITH CURL*/
+ 0x2a6, /* U+0002a6: LATIN SMALL LETTER TS DIGRAPH*/
+ 0x2a7, /* U+0002a7: LATIN SMALL LETTER TESH DIGRAPH*/
+ 0x2a8, /* U+0002a8: LATIN SMALL LETTER TC DIGRAPH WITH CURL*/
+ 0x2a9, /* U+0002a9: LATIN SMALL LETTER FENG DIGRAPH*/
+ 0x2aa, /* U+0002aa: LATIN SMALL LETTER LS DIGRAPH*/
+ 0x2ab, /* U+0002ab: LATIN SMALL LETTER LZ DIGRAPH*/
+ 0x2ac, /* U+0002ac: LATIN LETTER BILABIAL PERCUSSIVE*/
+ 0x2ad, /* U+0002ad: LATIN LETTER BIDENTAL PERCUSSIVE*/
+ 0x2ae, /* U+0002ae: LATIN SMALL LETTER TURNED H WITH FISHHOOK*/
+ 0x2af, /* U+0002af: LATIN SMALL LETTER TURNED H WITH FISHHOOK AND TAIL*/
+ 0x2b0, /* U+0002b0: MODIFIER LETTER SMALL H*/
+ 0x2b1, /* U+0002b1: MODIFIER LETTER SMALL H WITH HOOK*/
+ 0x2b2, /* U+0002b2: MODIFIER LETTER SMALL J*/
+ 0x2b3, /* U+0002b3: MODIFIER LETTER SMALL R*/
+ 0x2b4, /* U+0002b4: MODIFIER LETTER SMALL TURNED R*/
+ 0x2b5, /* U+0002b5: MODIFIER LETTER SMALL TURNED R WITH HOOK*/
+ 0x2b6, /* U+0002b6: MODIFIER LETTER SMALL CAPITAL INVERTED R*/
+ 0x2b7, /* U+0002b7: MODIFIER LETTER SMALL W*/
+ 0x2b8, /* U+0002b8: MODIFIER LETTER SMALL Y*/
+ 0x2b9, /* U+0002b9: MODIFIER LETTER PRIME*/
+ 0x2ba, /* U+0002ba: MODIFIER LETTER DOUBLE PRIME*/
+ 0x2bb, /* U+0002bb: MODIFIER LETTER TURNED COMMA*/
+ 0x2bc, /* U+0002bc: MODIFIER LETTER APOSTROPHE*/
+ 0x2bd, /* U+0002bd: MODIFIER LETTER REVERSED COMMA*/
+ 0x2be, /* U+0002be: MODIFIER LETTER RIGHT HALF RING*/
+ 0x2bf, /* U+0002bf: MODIFIER LETTER LEFT HALF RING*/
+ 0x2c0, /* U+0002c0: MODIFIER LETTER GLOTTAL STOP*/
+ 0x2c1, /* U+0002c1: MODIFIER LETTER REVERSED GLOTTAL STOP*/
+ 0x2c2, /* U+0002c2: MODIFIER LETTER LEFT ARROWHEAD*/
+ 0x2c3, /* U+0002c3: MODIFIER LETTER RIGHT ARROWHEAD*/
+ 0x2c4, /* U+0002c4: MODIFIER LETTER UP ARROWHEAD*/
+ 0x2c5, /* U+0002c5: MODIFIER LETTER DOWN ARROWHEAD*/
+ 0x2c6, /* U+0002c6: MODIFIER LETTER CIRCUMFLEX ACCENT*/
+ 0x2c7, /* U+0002c7: CARON*/
+ 0x2c8, /* U+0002c8: MODIFIER LETTER VERTICAL LINE*/
+ 0x2c9, /* U+0002c9: MODIFIER LETTER MACRON*/
+ 0x2ca, /* U+0002ca: MODIFIER LETTER ACUTE ACCENT*/
+ 0x2cb, /* U+0002cb: MODIFIER LETTER GRAVE ACCENT*/
+ 0x2cc, /* U+0002cc: MODIFIER LETTER LOW VERTICAL LINE*/
+ 0x2cd, /* U+0002cd: MODIFIER LETTER LOW MACRON*/
+ 0x2ce, /* U+0002ce: MODIFIER LETTER LOW GRAVE ACCENT*/
+ 0x2cf, /* U+0002cf: MODIFIER LETTER LOW ACUTE ACCENT*/
+ 0x2d0, /* U+0002d0: MODIFIER LETTER TRIANGULAR COLON*/
+ 0x2d1, /* U+0002d1: MODIFIER LETTER HALF TRIANGULAR COLON*/
+ 0x2d2, /* U+0002d2: MODIFIER LETTER CENTRED RIGHT HALF RING*/
+ 0x2d3, /* U+0002d3: MODIFIER LETTER CENTRED LEFT HALF RING*/
+ 0x2d4, /* U+0002d4: MODIFIER LETTER UP TACK*/
+ 0x2d5, /* U+0002d5: MODIFIER LETTER DOWN TACK*/
+ 0x2d6, /* U+0002d6: MODIFIER LETTER PLUS SIGN*/
+ 0x2d7, /* U+0002d7: MODIFIER LETTER MINUS SIGN*/
+ 0x2d8, /* U+0002d8: BREVE*/
+ 0x2d9, /* U+0002d9: DOT ABOVE*/
+ 0x2da, /* U+0002da: RING ABOVE*/
+ 0x2db, /* U+0002db: OGONEK*/
+ 0x2dc, /* U+0002dc: SMALL TILDE*/
+ 0x2dd, /* U+0002dd: DOUBLE ACUTE ACCENT*/
+ 0x2de, /* U+0002de: MODIFIER LETTER RHOTIC HOOK*/
+ 0x2df, /* U+0002df: MODIFIER LETTER CROSS ACCENT*/
+ 0x2e0, /* U+0002e0: MODIFIER LETTER SMALL GAMMA*/
+ 0x2e1, /* U+0002e1: MODIFIER LETTER SMALL L*/
+ 0x2e2, /* U+0002e2: MODIFIER LETTER SMALL S*/
+ 0x2e3, /* U+0002e3: MODIFIER LETTER SMALL X*/
+ 0x2e4, /* U+0002e4: MODIFIER LETTER SMALL REVERSED GLOTTAL STOP*/
+ 0x2e5, /* U+0002e5: MODIFIER LETTER EXTRA-HIGH TONE BAR*/
+ 0x2e6, /* U+0002e6: MODIFIER LETTER HIGH TONE BAR*/
+ 0x2e7, /* U+0002e7: MODIFIER LETTER MID TONE BAR*/
+ 0x2e8, /* U+0002e8: MODIFIER LETTER LOW TONE BAR*/
+ 0x2e9, /* U+0002e9: MODIFIER LETTER EXTRA-LOW TONE BAR*/
+ 0x2ea, /* U+0002ea: MODIFIER LETTER YIN DEPARTING TONE MARK*/
+ 0x2eb, /* U+0002eb: MODIFIER LETTER YANG DEPARTING TONE MARK*/
+ 0x2ec, /* U+0002ec: MODIFIER LETTER VOICING*/
+ 0x2ed, /* U+0002ed: MODIFIER LETTER UNASPIRATED*/
+ 0x2ee, /* U+0002ee: MODIFIER LETTER DOUBLE APOSTROPHE*/
+ 0x2ef, /* U+0002ef: MODIFIER LETTER LOW DOWN ARROWHEAD*/
+ 0x2f0, /* U+0002f0: MODIFIER LETTER LOW UP ARROWHEAD*/
+ 0x2f1, /* U+0002f1: MODIFIER LETTER LOW LEFT ARROWHEAD*/
+ 0x2f2, /* U+0002f2: MODIFIER LETTER LOW RIGHT ARROWHEAD*/
+ 0x2f3, /* U+0002f3: MODIFIER LETTER LOW RING*/
+ 0x2f4, /* U+0002f4: MODIFIER LETTER MIDDLE GRAVE ACCENT*/
+ 0x2f5, /* U+0002f5: MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT*/
+ 0x2f6, /* U+0002f6: MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT*/
+ 0x2f7, /* U+0002f7: MODIFIER LETTER LOW TILDE*/
+ 0x2f8, /* U+0002f8: MODIFIER LETTER RAISED COLON*/
+ 0x2f9, /* U+0002f9: MODIFIER LETTER BEGIN HIGH TONE*/
+ 0x2fa, /* U+0002fa: MODIFIER LETTER END HIGH TONE*/
+ 0x2fb, /* U+0002fb: MODIFIER LETTER BEGIN LOW TONE*/
+ 0x2fc, /* U+0002fc: MODIFIER LETTER END LOW TONE*/
+ 0x2fd, /* U+0002fd: MODIFIER LETTER SHELF*/
+ 0x2fe, /* U+0002fe: MODIFIER LETTER OPEN SHELF*/
+ 0x2ff, /* U+0002ff: MODIFIER LETTER LOW LEFT ARROW*/
+ 0x300, /* U+000300: COMBINING GRAVE ACCENT*/
+ 0x301, /* U+000301: COMBINING ACUTE ACCENT*/
+ 0x302, /* U+000302: COMBINING CIRCUMFLEX ACCENT*/
+ 0x303, /* U+000303: COMBINING TILDE*/
+ 0x304, /* U+000304: COMBINING MACRON*/
+ 0x305, /* U+000305: COMBINING OVERLINE*/
+ 0x306, /* U+000306: COMBINING BREVE*/
+ 0x307, /* U+000307: COMBINING DOT ABOVE*/
+ 0x308, /* U+000308: COMBINING DIAERESIS*/
+ 0x309, /* U+000309: COMBINING HOOK ABOVE*/
+ 0x30a, /* U+00030a: COMBINING RING ABOVE*/
+ 0x30b, /* U+00030b: COMBINING DOUBLE ACUTE ACCENT*/
+ 0x30c, /* U+00030c: COMBINING CARON*/
+ 0x30d, /* U+00030d: COMBINING VERTICAL LINE ABOVE*/
+ 0x30e, /* U+00030e: COMBINING DOUBLE VERTICAL LINE ABOVE*/
+ 0x30f, /* U+00030f: COMBINING DOUBLE GRAVE ACCENT*/
+ 0x310, /* U+000310: COMBINING CANDRABINDU*/
+ 0x311, /* U+000311: COMBINING INVERTED BREVE*/
+ 0x312, /* U+000312: COMBINING TURNED COMMA ABOVE*/
+ 0x313, /* U+000313: COMBINING COMMA ABOVE*/
+ 0x314, /* U+000314: COMBINING REVERSED COMMA ABOVE*/
+ 0x315, /* U+000315: COMBINING COMMA ABOVE RIGHT*/
+ 0x316, /* U+000316: COMBINING GRAVE ACCENT BELOW*/
+ 0x317, /* U+000317: COMBINING ACUTE ACCENT BELOW*/
+ 0x318, /* U+000318: COMBINING LEFT TACK BELOW*/
+ 0x319, /* U+000319: COMBINING RIGHT TACK BELOW*/
+ 0x31a, /* U+00031a: COMBINING LEFT ANGLE ABOVE*/
+ 0x31b, /* U+00031b: COMBINING HORN*/
+ 0x31c, /* U+00031c: COMBINING LEFT HALF RING BELOW*/
+ 0x31d, /* U+00031d: COMBINING UP TACK BELOW*/
+ 0x31e, /* U+00031e: COMBINING DOWN TACK BELOW*/
+ 0x31f, /* U+00031f: COMBINING PLUS SIGN BELOW*/
+ 0x320, /* U+000320: COMBINING MINUS SIGN BELOW*/
+ 0x321, /* U+000321: COMBINING PALATALIZED HOOK BELOW*/
+ 0x322, /* U+000322: COMBINING RETROFLEX HOOK BELOW*/
+ 0x323, /* U+000323: COMBINING DOT BELOW*/
+ 0x324, /* U+000324: COMBINING DIAERESIS BELOW*/
+ 0x325, /* U+000325: COMBINING RING BELOW*/
+ 0x326, /* U+000326: COMBINING COMMA BELOW*/
+ 0x327, /* U+000327: COMBINING CEDILLA*/
+ 0x328, /* U+000328: COMBINING OGONEK*/
+ 0x329, /* U+000329: COMBINING VERTICAL LINE BELOW*/
+ 0x32a, /* U+00032a: COMBINING BRIDGE BELOW*/
+ 0x32b, /* U+00032b: COMBINING INVERTED DOUBLE ARCH BELOW*/
+ 0x32c, /* U+00032c: COMBINING CARON BELOW*/
+ 0x32d, /* U+00032d: COMBINING CIRCUMFLEX ACCENT BELOW*/
+ 0x32e, /* U+00032e: COMBINING BREVE BELOW*/
+ 0x32f, /* U+00032f: COMBINING INVERTED BREVE BELOW*/
+ 0x330, /* U+000330: COMBINING TILDE BELOW*/
+ 0x331, /* U+000331: COMBINING MACRON BELOW*/
+ 0x332, /* U+000332: COMBINING LOW LINE*/
+ 0x333, /* U+000333: COMBINING DOUBLE LOW LINE*/
+ 0x334, /* U+000334: COMBINING TILDE OVERLAY*/
+ 0x335, /* U+000335: COMBINING SHORT STROKE OVERLAY*/
+ 0x336, /* U+000336: COMBINING LONG STROKE OVERLAY*/
+ 0x337, /* U+000337: COMBINING SHORT SOLIDUS OVERLAY*/
+ 0x338, /* U+000338: COMBINING LONG SOLIDUS OVERLAY*/
+ 0x339, /* U+000339: COMBINING RIGHT HALF RING BELOW*/
+ 0x33a, /* U+00033a: COMBINING INVERTED BRIDGE BELOW*/
+ 0x33b, /* U+00033b: COMBINING SQUARE BELOW*/
+ 0x33c, /* U+00033c: COMBINING SEAGULL BELOW*/
+ 0x33d, /* U+00033d: COMBINING X ABOVE*/
+ 0x33e, /* U+00033e: COMBINING VERTICAL TILDE*/
+ 0x33f, /* U+00033f: COMBINING DOUBLE OVERLINE*/
+ 0x340, /* U+000340: COMBINING GRAVE TONE MARK*/
+ 0x341, /* U+000341: COMBINING ACUTE TONE MARK*/
+ 0x342, /* U+000342: COMBINING GREEK PERISPOMENI*/
+ 0x343, /* U+000343: COMBINING GREEK KORONIS*/
+ 0x344, /* U+000344: COMBINING GREEK DIALYTIKA TONOS*/
+ 0x399, /* U+000345: COMBINING GREEK YPOGEGRAMMENI*/
+ 0x346, /* U+000346: COMBINING BRIDGE ABOVE*/
+ 0x347, /* U+000347: COMBINING EQUALS SIGN BELOW*/
+ 0x348, /* U+000348: COMBINING DOUBLE VERTICAL LINE BELOW*/
+ 0x349, /* U+000349: COMBINING LEFT ANGLE BELOW*/
+ 0x34a, /* U+00034a: COMBINING NOT TILDE ABOVE*/
+ 0x34b, /* U+00034b: COMBINING HOMOTHETIC ABOVE*/
+ 0x34c, /* U+00034c: COMBINING ALMOST EQUAL TO ABOVE*/
+ 0x34d, /* U+00034d: COMBINING LEFT RIGHT ARROW BELOW*/
+ 0x34e, /* U+00034e: COMBINING UPWARDS ARROW BELOW*/
+ 0x34f, /* U+00034f: COMBINING GRAPHEME JOINER*/
+ 0x350, /* U+000350: COMBINING RIGHT ARROWHEAD ABOVE*/
+ 0x351, /* U+000351: COMBINING LEFT HALF RING ABOVE*/
+ 0x352, /* U+000352: COMBINING FERMATA*/
+ 0x353, /* U+000353: COMBINING X BELOW*/
+ 0x354, /* U+000354: COMBINING LEFT ARROWHEAD BELOW*/
+ 0x355, /* U+000355: COMBINING RIGHT ARROWHEAD BELOW*/
+ 0x356, /* U+000356: COMBINING RIGHT ARROWHEAD AND UP ARROWHEAD BELOW*/
+ 0x357, /* U+000357: COMBINING RIGHT HALF RING ABOVE*/
+ 0x358, /* U+000358: COMBINING DOT ABOVE RIGHT*/
+ 0x359, /* U+000359: COMBINING ASTERISK BELOW*/
+ 0x35a, /* U+00035a: COMBINING DOUBLE RING BELOW*/
+ 0x35b, /* U+00035b: COMBINING ZIGZAG ABOVE*/
+ 0x35c, /* U+00035c: COMBINING DOUBLE BREVE BELOW*/
+ 0x35d, /* U+00035d: COMBINING DOUBLE BREVE*/
+ 0x35e, /* U+00035e: COMBINING DOUBLE MACRON*/
+ 0x35f, /* U+00035f: COMBINING DOUBLE MACRON BELOW*/
+ 0x360, /* U+000360: COMBINING DOUBLE TILDE*/
+ 0x361, /* U+000361: COMBINING DOUBLE INVERTED BREVE*/
+ 0x362, /* U+000362: COMBINING DOUBLE RIGHTWARDS ARROW BELOW*/
+ 0x363, /* U+000363: COMBINING LATIN SMALL LETTER A*/
+ 0x364, /* U+000364: COMBINING LATIN SMALL LETTER E*/
+ 0x365, /* U+000365: COMBINING LATIN SMALL LETTER I*/
+ 0x366, /* U+000366: COMBINING LATIN SMALL LETTER O*/
+ 0x367, /* U+000367: COMBINING LATIN SMALL LETTER U*/
+ 0x368, /* U+000368: COMBINING LATIN SMALL LETTER C*/
+ 0x369, /* U+000369: COMBINING LATIN SMALL LETTER D*/
+ 0x36a, /* U+00036a: COMBINING LATIN SMALL LETTER H*/
+ 0x36b, /* U+00036b: COMBINING LATIN SMALL LETTER M*/
+ 0x36c, /* U+00036c: COMBINING LATIN SMALL LETTER R*/
+ 0x36d, /* U+00036d: COMBINING LATIN SMALL LETTER T*/
+ 0x36e, /* U+00036e: COMBINING LATIN SMALL LETTER V*/
+ 0x36f, /* U+00036f: COMBINING LATIN SMALL LETTER X*/
+ 0x370, /* U+000370: GREEK CAPITAL LETTER HETA*/
+ 0x370, /* U+000371: GREEK SMALL LETTER HETA*/
+ 0x372, /* U+000372: GREEK CAPITAL LETTER ARCHAIC SAMPI*/
+ 0x372, /* U+000373: GREEK SMALL LETTER ARCHAIC SAMPI*/
+ 0x374, /* U+000374: GREEK NUMERAL SIGN*/
+ 0x375, /* U+000375: GREEK LOWER NUMERAL SIGN*/
+ 0x376, /* U+000376: GREEK CAPITAL LETTER PAMPHYLIAN DIGAMMA*/
+ 0x376, /* U+000377: GREEK SMALL LETTER PAMPHYLIAN DIGAMMA*/
+ 0x378, /* U+000378: */
+ 0x379, /* U+000379: */
+ 0x37a, /* U+00037a: GREEK YPOGEGRAMMENI*/
+ 0x3fd, /* U+00037b: GREEK SMALL REVERSED LUNATE SIGMA SYMBOL*/
+ 0x3fe, /* U+00037c: GREEK SMALL DOTTED LUNATE SIGMA SYMBOL*/
+ 0x3ff, /* U+00037d: GREEK SMALL REVERSED DOTTED LUNATE SIGMA SYMBOL*/
+ 0x37e, /* U+00037e: GREEK QUESTION MARK*/
+ 0x37f, /* U+00037f: */
+ 0x380, /* U+000380: */
+ 0x381, /* U+000381: */
+ 0x382, /* U+000382: */
+ 0x383, /* U+000383: */
+ 0x384, /* U+000384: GREEK TONOS*/
+ 0x385, /* U+000385: GREEK DIALYTIKA TONOS*/
+ 0x386, /* U+000386: GREEK CAPITAL LETTER ALPHA WITH TONOS*/
+ 0x387, /* U+000387: GREEK ANO TELEIA*/
+ 0x388, /* U+000388: GREEK CAPITAL LETTER EPSILON WITH TONOS*/
+ 0x389, /* U+000389: GREEK CAPITAL LETTER ETA WITH TONOS*/
+ 0x38a, /* U+00038a: GREEK CAPITAL LETTER IOTA WITH TONOS*/
+ 0x38b, /* U+00038b: */
+ 0x38c, /* U+00038c: GREEK CAPITAL LETTER OMICRON WITH TONOS*/
+ 0x38d, /* U+00038d: */
+ 0x38e, /* U+00038e: GREEK CAPITAL LETTER UPSILON WITH TONOS*/
+ 0x38f, /* U+00038f: GREEK CAPITAL LETTER OMEGA WITH TONOS*/
+ 0x390, /* U+000390: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS*/
+ 0x391, /* U+000391: GREEK CAPITAL LETTER ALPHA*/
+ 0x392, /* U+000392: GREEK CAPITAL LETTER BETA*/
+ 0x393, /* U+000393: GREEK CAPITAL LETTER GAMMA*/
+ 0x394, /* U+000394: GREEK CAPITAL LETTER DELTA*/
+ 0x395, /* U+000395: GREEK CAPITAL LETTER EPSILON*/
+ 0x396, /* U+000396: GREEK CAPITAL LETTER ZETA*/
+ 0x397, /* U+000397: GREEK CAPITAL LETTER ETA*/
+ 0x398, /* U+000398: GREEK CAPITAL LETTER THETA*/
+ 0x399, /* U+000399: GREEK CAPITAL LETTER IOTA*/
+ 0x39a, /* U+00039a: GREEK CAPITAL LETTER KAPPA*/
+ 0x39b, /* U+00039b: GREEK CAPITAL LETTER LAMDA*/
+ 0x39c, /* U+00039c: GREEK CAPITAL LETTER MU*/
+ 0x39d, /* U+00039d: GREEK CAPITAL LETTER NU*/
+ 0x39e, /* U+00039e: GREEK CAPITAL LETTER XI*/
+ 0x39f, /* U+00039f: GREEK CAPITAL LETTER OMICRON*/
+ 0x3a0, /* U+0003a0: GREEK CAPITAL LETTER PI*/
+ 0x3a1, /* U+0003a1: GREEK CAPITAL LETTER RHO*/
+ 0x3a2, /* U+0003a2: */
+ 0x3a3, /* U+0003a3: GREEK CAPITAL LETTER SIGMA*/
+ 0x3a4, /* U+0003a4: GREEK CAPITAL LETTER TAU*/
+ 0x3a5, /* U+0003a5: GREEK CAPITAL LETTER UPSILON*/
+ 0x3a6, /* U+0003a6: GREEK CAPITAL LETTER PHI*/
+ 0x3a7, /* U+0003a7: GREEK CAPITAL LETTER CHI*/
+ 0x3a8, /* U+0003a8: GREEK CAPITAL LETTER PSI*/
+ 0x3a9, /* U+0003a9: GREEK CAPITAL LETTER OMEGA*/
+ 0x3aa, /* U+0003aa: GREEK CAPITAL LETTER IOTA WITH DIALYTIKA*/
+ 0x3ab, /* U+0003ab: GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA*/
+ 0x386, /* U+0003ac: GREEK SMALL LETTER ALPHA WITH TONOS*/
+ 0x388, /* U+0003ad: GREEK SMALL LETTER EPSILON WITH TONOS*/
+ 0x389, /* U+0003ae: GREEK SMALL LETTER ETA WITH TONOS*/
+ 0x38a, /* U+0003af: GREEK SMALL LETTER IOTA WITH TONOS*/
+ 0x3b0, /* U+0003b0: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS*/
+ 0x391, /* U+0003b1: GREEK SMALL LETTER ALPHA*/
+ 0x392, /* U+0003b2: GREEK SMALL LETTER BETA*/
+ 0x393, /* U+0003b3: GREEK SMALL LETTER GAMMA*/
+ 0x394, /* U+0003b4: GREEK SMALL LETTER DELTA*/
+ 0x395, /* U+0003b5: GREEK SMALL LETTER EPSILON*/
+ 0x396, /* U+0003b6: GREEK SMALL LETTER ZETA*/
+ 0x397, /* U+0003b7: GREEK SMALL LETTER ETA*/
+ 0x398, /* U+0003b8: GREEK SMALL LETTER THETA*/
+ 0x399, /* U+0003b9: GREEK SMALL LETTER IOTA*/
+ 0x39a, /* U+0003ba: GREEK SMALL LETTER KAPPA*/
+ 0x39b, /* U+0003bb: GREEK SMALL LETTER LAMDA*/
+ 0x39c, /* U+0003bc: GREEK SMALL LETTER MU*/
+ 0x39d, /* U+0003bd: GREEK SMALL LETTER NU*/
+ 0x39e, /* U+0003be: GREEK SMALL LETTER XI*/
+ 0x39f, /* U+0003bf: GREEK SMALL LETTER OMICRON*/
+ 0x3a0, /* U+0003c0: GREEK SMALL LETTER PI*/
+ 0x3a1, /* U+0003c1: GREEK SMALL LETTER RHO*/
+ 0x3a3, /* U+0003c2: GREEK SMALL LETTER FINAL SIGMA*/
+ 0x3a3, /* U+0003c3: GREEK SMALL LETTER SIGMA*/
+ 0x3a4, /* U+0003c4: GREEK SMALL LETTER TAU*/
+ 0x3a5, /* U+0003c5: GREEK SMALL LETTER UPSILON*/
+ 0x3a6, /* U+0003c6: GREEK SMALL LETTER PHI*/
+ 0x3a7, /* U+0003c7: GREEK SMALL LETTER CHI*/
+ 0x3a8, /* U+0003c8: GREEK SMALL LETTER PSI*/
+ 0x3a9, /* U+0003c9: GREEK SMALL LETTER OMEGA*/
+ 0x3aa, /* U+0003ca: GREEK SMALL LETTER IOTA WITH DIALYTIKA*/
+ 0x3ab, /* U+0003cb: GREEK SMALL LETTER UPSILON WITH DIALYTIKA*/
+ 0x38c, /* U+0003cc: GREEK SMALL LETTER OMICRON WITH TONOS*/
+ 0x38e, /* U+0003cd: GREEK SMALL LETTER UPSILON WITH TONOS*/
+ 0x38f, /* U+0003ce: GREEK SMALL LETTER OMEGA WITH TONOS*/
+ 0x3cf, /* U+0003cf: GREEK CAPITAL KAI SYMBOL*/
+ 0x392, /* U+0003d0: GREEK BETA SYMBOL*/
+ 0x398, /* U+0003d1: GREEK THETA SYMBOL*/
+ 0x3d2, /* U+0003d2: GREEK UPSILON WITH HOOK SYMBOL*/
+ 0x3d3, /* U+0003d3: GREEK UPSILON WITH ACUTE AND HOOK SYMBOL*/
+ 0x3d4, /* U+0003d4: GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL*/
+ 0x3a6, /* U+0003d5: GREEK PHI SYMBOL*/
+ 0x3a0, /* U+0003d6: GREEK PI SYMBOL*/
+ 0x3cf, /* U+0003d7: GREEK KAI SYMBOL*/
+ 0x3d8, /* U+0003d8: GREEK LETTER ARCHAIC KOPPA*/
+ 0x3d8, /* U+0003d9: GREEK SMALL LETTER ARCHAIC KOPPA*/
+ 0x3da, /* U+0003da: GREEK LETTER STIGMA*/
+ 0x3da, /* U+0003db: GREEK SMALL LETTER STIGMA*/
+ 0x3dc, /* U+0003dc: GREEK LETTER DIGAMMA*/
+ 0x3dc, /* U+0003dd: GREEK SMALL LETTER DIGAMMA*/
+ 0x3de, /* U+0003de: GREEK LETTER KOPPA*/
+ 0x3de, /* U+0003df: GREEK SMALL LETTER KOPPA*/
+ 0x3e0, /* U+0003e0: GREEK LETTER SAMPI*/
+ 0x3e0, /* U+0003e1: GREEK SMALL LETTER SAMPI*/
+ 0x3e2, /* U+0003e2: COPTIC CAPITAL LETTER SHEI*/
+ 0x3e2, /* U+0003e3: COPTIC SMALL LETTER SHEI*/
+ 0x3e4, /* U+0003e4: COPTIC CAPITAL LETTER FEI*/
+ 0x3e4, /* U+0003e5: COPTIC SMALL LETTER FEI*/
+ 0x3e6, /* U+0003e6: COPTIC CAPITAL LETTER KHEI*/
+ 0x3e6, /* U+0003e7: COPTIC SMALL LETTER KHEI*/
+ 0x3e8, /* U+0003e8: COPTIC CAPITAL LETTER HORI*/
+ 0x3e8, /* U+0003e9: COPTIC SMALL LETTER HORI*/
+ 0x3ea, /* U+0003ea: COPTIC CAPITAL LETTER GANGIA*/
+ 0x3ea, /* U+0003eb: COPTIC SMALL LETTER GANGIA*/
+ 0x3ec, /* U+0003ec: COPTIC CAPITAL LETTER SHIMA*/
+ 0x3ec, /* U+0003ed: COPTIC SMALL LETTER SHIMA*/
+ 0x3ee, /* U+0003ee: COPTIC CAPITAL LETTER DEI*/
+ 0x3ee, /* U+0003ef: COPTIC SMALL LETTER DEI*/
+ 0x39a, /* U+0003f0: GREEK KAPPA SYMBOL*/
+ 0x3a1, /* U+0003f1: GREEK RHO SYMBOL*/
+ 0x3f9, /* U+0003f2: GREEK LUNATE SIGMA SYMBOL*/
+ 0x3f3, /* U+0003f3: GREEK LETTER YOT*/
+ 0x3f4, /* U+0003f4: GREEK CAPITAL THETA SYMBOL*/
+ 0x395, /* U+0003f5: GREEK LUNATE EPSILON SYMBOL*/
+ 0x3f6, /* U+0003f6: GREEK REVERSED LUNATE EPSILON SYMBOL*/
+ 0x3f7, /* U+0003f7: GREEK CAPITAL LETTER SHO*/
+ 0x3f7, /* U+0003f8: GREEK SMALL LETTER SHO*/
+ 0x3f9, /* U+0003f9: GREEK CAPITAL LUNATE SIGMA SYMBOL*/
+ 0x3fa, /* U+0003fa: GREEK CAPITAL LETTER SAN*/
+ 0x3fa, /* U+0003fb: GREEK SMALL LETTER SAN*/
+ 0x3fc, /* U+0003fc: GREEK RHO WITH STROKE SYMBOL*/
+ 0x3fd, /* U+0003fd: GREEK CAPITAL REVERSED LUNATE SIGMA SYMBOL*/
+ 0x3fe, /* U+0003fe: GREEK CAPITAL DOTTED LUNATE SIGMA SYMBOL*/
+ 0x3ff, /* U+0003ff: GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL*/
+ 0x400, /* U+000400: CYRILLIC CAPITAL LETTER IE WITH GRAVE*/
+ 0x401, /* U+000401: CYRILLIC CAPITAL LETTER IO*/
+ 0x402, /* U+000402: CYRILLIC CAPITAL LETTER DJE*/
+ 0x403, /* U+000403: CYRILLIC CAPITAL LETTER GJE*/
+ 0x404, /* U+000404: CYRILLIC CAPITAL LETTER UKRAINIAN IE*/
+ 0x405, /* U+000405: CYRILLIC CAPITAL LETTER DZE*/
+ 0x406, /* U+000406: CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I*/
+ 0x407, /* U+000407: CYRILLIC CAPITAL LETTER YI*/
+ 0x408, /* U+000408: CYRILLIC CAPITAL LETTER JE*/
+ 0x409, /* U+000409: CYRILLIC CAPITAL LETTER LJE*/
+ 0x40a, /* U+00040a: CYRILLIC CAPITAL LETTER NJE*/
+ 0x40b, /* U+00040b: CYRILLIC CAPITAL LETTER TSHE*/
+ 0x40c, /* U+00040c: CYRILLIC CAPITAL LETTER KJE*/
+ 0x40d, /* U+00040d: CYRILLIC CAPITAL LETTER I WITH GRAVE*/
+ 0x40e, /* U+00040e: CYRILLIC CAPITAL LETTER SHORT U*/
+ 0x40f, /* U+00040f: CYRILLIC CAPITAL LETTER DZHE*/
+ 0x410, /* U+000410: CYRILLIC CAPITAL LETTER A*/
+ 0x411, /* U+000411: CYRILLIC CAPITAL LETTER BE*/
+ 0x412, /* U+000412: CYRILLIC CAPITAL LETTER VE*/
+ 0x413, /* U+000413: CYRILLIC CAPITAL LETTER GHE*/
+ 0x414, /* U+000414: CYRILLIC CAPITAL LETTER DE*/
+ 0x415, /* U+000415: CYRILLIC CAPITAL LETTER IE*/
+ 0x416, /* U+000416: CYRILLIC CAPITAL LETTER ZHE*/
+ 0x417, /* U+000417: CYRILLIC CAPITAL LETTER ZE*/
+ 0x418, /* U+000418: CYRILLIC CAPITAL LETTER I*/
+ 0x419, /* U+000419: CYRILLIC CAPITAL LETTER SHORT I*/
+ 0x41a, /* U+00041a: CYRILLIC CAPITAL LETTER KA*/
+ 0x41b, /* U+00041b: CYRILLIC CAPITAL LETTER EL*/
+ 0x41c, /* U+00041c: CYRILLIC CAPITAL LETTER EM*/
+ 0x41d, /* U+00041d: CYRILLIC CAPITAL LETTER EN*/
+ 0x41e, /* U+00041e: CYRILLIC CAPITAL LETTER O*/
+ 0x41f, /* U+00041f: CYRILLIC CAPITAL LETTER PE*/
+ 0x420, /* U+000420: CYRILLIC CAPITAL LETTER ER*/
+ 0x421, /* U+000421: CYRILLIC CAPITAL LETTER ES*/
+ 0x422, /* U+000422: CYRILLIC CAPITAL LETTER TE*/
+ 0x423, /* U+000423: CYRILLIC CAPITAL LETTER U*/
+ 0x424, /* U+000424: CYRILLIC CAPITAL LETTER EF*/
+ 0x425, /* U+000425: CYRILLIC CAPITAL LETTER HA*/
+ 0x426, /* U+000426: CYRILLIC CAPITAL LETTER TSE*/
+ 0x427, /* U+000427: CYRILLIC CAPITAL LETTER CHE*/
+ 0x428, /* U+000428: CYRILLIC CAPITAL LETTER SHA*/
+ 0x429, /* U+000429: CYRILLIC CAPITAL LETTER SHCHA*/
+ 0x42a, /* U+00042a: CYRILLIC CAPITAL LETTER HARD SIGN*/
+ 0x42b, /* U+00042b: CYRILLIC CAPITAL LETTER YERU*/
+ 0x42c, /* U+00042c: CYRILLIC CAPITAL LETTER SOFT SIGN*/
+ 0x42d, /* U+00042d: CYRILLIC CAPITAL LETTER E*/
+ 0x42e, /* U+00042e: CYRILLIC CAPITAL LETTER YU*/
+ 0x42f, /* U+00042f: CYRILLIC CAPITAL LETTER YA*/
+ 0x410, /* U+000430: CYRILLIC SMALL LETTER A*/
+ 0x411, /* U+000431: CYRILLIC SMALL LETTER BE*/
+ 0x412, /* U+000432: CYRILLIC SMALL LETTER VE*/
+ 0x413, /* U+000433: CYRILLIC SMALL LETTER GHE*/
+ 0x414, /* U+000434: CYRILLIC SMALL LETTER DE*/
+ 0x415, /* U+000435: CYRILLIC SMALL LETTER IE*/
+ 0x416, /* U+000436: CYRILLIC SMALL LETTER ZHE*/
+ 0x417, /* U+000437: CYRILLIC SMALL LETTER ZE*/
+ 0x418, /* U+000438: CYRILLIC SMALL LETTER I*/
+ 0x419, /* U+000439: CYRILLIC SMALL LETTER SHORT I*/
+ 0x41a, /* U+00043a: CYRILLIC SMALL LETTER KA*/
+ 0x41b, /* U+00043b: CYRILLIC SMALL LETTER EL*/
+ 0x41c, /* U+00043c: CYRILLIC SMALL LETTER EM*/
+ 0x41d, /* U+00043d: CYRILLIC SMALL LETTER EN*/
+ 0x41e, /* U+00043e: CYRILLIC SMALL LETTER O*/
+ 0x41f, /* U+00043f: CYRILLIC SMALL LETTER PE*/
+ 0x420, /* U+000440: CYRILLIC SMALL LETTER ER*/
+ 0x421, /* U+000441: CYRILLIC SMALL LETTER ES*/
+ 0x422, /* U+000442: CYRILLIC SMALL LETTER TE*/
+ 0x423, /* U+000443: CYRILLIC SMALL LETTER U*/
+ 0x424, /* U+000444: CYRILLIC SMALL LETTER EF*/
+ 0x425, /* U+000445: CYRILLIC SMALL LETTER HA*/
+ 0x426, /* U+000446: CYRILLIC SMALL LETTER TSE*/
+ 0x427, /* U+000447: CYRILLIC SMALL LETTER CHE*/
+ 0x428, /* U+000448: CYRILLIC SMALL LETTER SHA*/
+ 0x429, /* U+000449: CYRILLIC SMALL LETTER SHCHA*/
+ 0x42a, /* U+00044a: CYRILLIC SMALL LETTER HARD SIGN*/
+ 0x42b, /* U+00044b: CYRILLIC SMALL LETTER YERU*/
+ 0x42c, /* U+00044c: CYRILLIC SMALL LETTER SOFT SIGN*/
+ 0x42d, /* U+00044d: CYRILLIC SMALL LETTER E*/
+ 0x42e, /* U+00044e: CYRILLIC SMALL LETTER YU*/
+ 0x42f, /* U+00044f: CYRILLIC SMALL LETTER YA*/
+ 0x400, /* U+000450: CYRILLIC SMALL LETTER IE WITH GRAVE*/
+ 0x401, /* U+000451: CYRILLIC SMALL LETTER IO*/
+ 0x402, /* U+000452: CYRILLIC SMALL LETTER DJE*/
+ 0x403, /* U+000453: CYRILLIC SMALL LETTER GJE*/
+ 0x404, /* U+000454: CYRILLIC SMALL LETTER UKRAINIAN IE*/
+ 0x405, /* U+000455: CYRILLIC SMALL LETTER DZE*/
+ 0x406, /* U+000456: CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I*/
+ 0x407, /* U+000457: CYRILLIC SMALL LETTER YI*/
+ 0x408, /* U+000458: CYRILLIC SMALL LETTER JE*/
+ 0x409, /* U+000459: CYRILLIC SMALL LETTER LJE*/
+ 0x40a, /* U+00045a: CYRILLIC SMALL LETTER NJE*/
+ 0x40b, /* U+00045b: CYRILLIC SMALL LETTER TSHE*/
+ 0x40c, /* U+00045c: CYRILLIC SMALL LETTER KJE*/
+ 0x40d, /* U+00045d: CYRILLIC SMALL LETTER I WITH GRAVE*/
+ 0x40e, /* U+00045e: CYRILLIC SMALL LETTER SHORT U*/
+ 0x40f, /* U+00045f: CYRILLIC SMALL LETTER DZHE*/
+ 0x460, /* U+000460: CYRILLIC CAPITAL LETTER OMEGA*/
+ 0x460, /* U+000461: CYRILLIC SMALL LETTER OMEGA*/
+ 0x462, /* U+000462: CYRILLIC CAPITAL LETTER YAT*/
+ 0x462, /* U+000463: CYRILLIC SMALL LETTER YAT*/
+ 0x464, /* U+000464: CYRILLIC CAPITAL LETTER IOTIFIED E*/
+ 0x464, /* U+000465: CYRILLIC SMALL LETTER IOTIFIED E*/
+ 0x466, /* U+000466: CYRILLIC CAPITAL LETTER LITTLE YUS*/
+ 0x466, /* U+000467: CYRILLIC SMALL LETTER LITTLE YUS*/
+ 0x468, /* U+000468: CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS*/
+ 0x468, /* U+000469: CYRILLIC SMALL LETTER IOTIFIED LITTLE YUS*/
+ 0x46a, /* U+00046a: CYRILLIC CAPITAL LETTER BIG YUS*/
+ 0x46a, /* U+00046b: CYRILLIC SMALL LETTER BIG YUS*/
+ 0x46c, /* U+00046c: CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS*/
+ 0x46c, /* U+00046d: CYRILLIC SMALL LETTER IOTIFIED BIG YUS*/
+ 0x46e, /* U+00046e: CYRILLIC CAPITAL LETTER KSI*/
+ 0x46e, /* U+00046f: CYRILLIC SMALL LETTER KSI*/
+ 0x470, /* U+000470: CYRILLIC CAPITAL LETTER PSI*/
+ 0x470, /* U+000471: CYRILLIC SMALL LETTER PSI*/
+ 0x472, /* U+000472: CYRILLIC CAPITAL LETTER FITA*/
+ 0x472, /* U+000473: CYRILLIC SMALL LETTER FITA*/
+ 0x474, /* U+000474: CYRILLIC CAPITAL LETTER IZHITSA*/
+ 0x474, /* U+000475: CYRILLIC SMALL LETTER IZHITSA*/
+ 0x476, /* U+000476: CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT*/
+ 0x476, /* U+000477: CYRILLIC SMALL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT*/
+ 0x478, /* U+000478: CYRILLIC CAPITAL LETTER UK*/
+ 0x478, /* U+000479: CYRILLIC SMALL LETTER UK*/
+ 0x47a, /* U+00047a: CYRILLIC CAPITAL LETTER ROUND OMEGA*/
+ 0x47a, /* U+00047b: CYRILLIC SMALL LETTER ROUND OMEGA*/
+ 0x47c, /* U+00047c: CYRILLIC CAPITAL LETTER OMEGA WITH TITLO*/
+ 0x47c, /* U+00047d: CYRILLIC SMALL LETTER OMEGA WITH TITLO*/
+ 0x47e, /* U+00047e: CYRILLIC CAPITAL LETTER OT*/
+ 0x47e, /* U+00047f: CYRILLIC SMALL LETTER OT*/
+ 0x480, /* U+000480: CYRILLIC CAPITAL LETTER KOPPA*/
+ 0x480, /* U+000481: CYRILLIC SMALL LETTER KOPPA*/
+ 0x482, /* U+000482: CYRILLIC THOUSANDS SIGN*/
+ 0x483, /* U+000483: COMBINING CYRILLIC TITLO*/
+ 0x484, /* U+000484: COMBINING CYRILLIC PALATALIZATION*/
+ 0x485, /* U+000485: COMBINING CYRILLIC DASIA PNEUMATA*/
+ 0x486, /* U+000486: COMBINING CYRILLIC PSILI PNEUMATA*/
+ 0x487, /* U+000487: COMBINING CYRILLIC POKRYTIE*/
+ 0x488, /* U+000488: COMBINING CYRILLIC HUNDRED THOUSANDS SIGN*/
+ 0x489, /* U+000489: COMBINING CYRILLIC MILLIONS SIGN*/
+ 0x48a, /* U+00048a: CYRILLIC CAPITAL LETTER SHORT I WITH TAIL*/
+ 0x48a, /* U+00048b: CYRILLIC SMALL LETTER SHORT I WITH TAIL*/
+ 0x48c, /* U+00048c: CYRILLIC CAPITAL LETTER SEMISOFT SIGN*/
+ 0x48c, /* U+00048d: CYRILLIC SMALL LETTER SEMISOFT SIGN*/
+ 0x48e, /* U+00048e: CYRILLIC CAPITAL LETTER ER WITH TICK*/
+ 0x48e, /* U+00048f: CYRILLIC SMALL LETTER ER WITH TICK*/
+ 0x490, /* U+000490: CYRILLIC CAPITAL LETTER GHE WITH UPTURN*/
+ 0x490, /* U+000491: CYRILLIC SMALL LETTER GHE WITH UPTURN*/
+ 0x492, /* U+000492: CYRILLIC CAPITAL LETTER GHE WITH STROKE*/
+ 0x492, /* U+000493: CYRILLIC SMALL LETTER GHE WITH STROKE*/
+ 0x494, /* U+000494: CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK*/
+ 0x494, /* U+000495: CYRILLIC SMALL LETTER GHE WITH MIDDLE HOOK*/
+ 0x496, /* U+000496: CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER*/
+ 0x496, /* U+000497: CYRILLIC SMALL LETTER ZHE WITH DESCENDER*/
+ 0x498, /* U+000498: CYRILLIC CAPITAL LETTER ZE WITH DESCENDER*/
+ 0x498, /* U+000499: CYRILLIC SMALL LETTER ZE WITH DESCENDER*/
+ 0x49a, /* U+00049a: CYRILLIC CAPITAL LETTER KA WITH DESCENDER*/
+ 0x49a, /* U+00049b: CYRILLIC SMALL LETTER KA WITH DESCENDER*/
+ 0x49c, /* U+00049c: CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE*/
+ 0x49c, /* U+00049d: CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE*/
+ 0x49e, /* U+00049e: CYRILLIC CAPITAL LETTER KA WITH STROKE*/
+ 0x49e, /* U+00049f: CYRILLIC SMALL LETTER KA WITH STROKE*/
+ 0x4a0, /* U+0004a0: CYRILLIC CAPITAL LETTER BASHKIR KA*/
+ 0x4a0, /* U+0004a1: CYRILLIC SMALL LETTER BASHKIR KA*/
+ 0x4a2, /* U+0004a2: CYRILLIC CAPITAL LETTER EN WITH DESCENDER*/
+ 0x4a2, /* U+0004a3: CYRILLIC SMALL LETTER EN WITH DESCENDER*/
+ 0x4a4, /* U+0004a4: CYRILLIC CAPITAL LIGATURE EN GHE*/
+ 0x4a4, /* U+0004a5: CYRILLIC SMALL LIGATURE EN GHE*/
+ 0x4a6, /* U+0004a6: CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK*/
+ 0x4a6, /* U+0004a7: CYRILLIC SMALL LETTER PE WITH MIDDLE HOOK*/
+ 0x4a8, /* U+0004a8: CYRILLIC CAPITAL LETTER ABKHASIAN HA*/
+ 0x4a8, /* U+0004a9: CYRILLIC SMALL LETTER ABKHASIAN HA*/
+ 0x4aa, /* U+0004aa: CYRILLIC CAPITAL LETTER ES WITH DESCENDER*/
+ 0x4aa, /* U+0004ab: CYRILLIC SMALL LETTER ES WITH DESCENDER*/
+ 0x4ac, /* U+0004ac: CYRILLIC CAPITAL LETTER TE WITH DESCENDER*/
+ 0x4ac, /* U+0004ad: CYRILLIC SMALL LETTER TE WITH DESCENDER*/
+ 0x4ae, /* U+0004ae: CYRILLIC CAPITAL LETTER STRAIGHT U*/
+ 0x4ae, /* U+0004af: CYRILLIC SMALL LETTER STRAIGHT U*/
+ 0x4b0, /* U+0004b0: CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE*/
+ 0x4b0, /* U+0004b1: CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE*/
+ 0x4b2, /* U+0004b2: CYRILLIC CAPITAL LETTER HA WITH DESCENDER*/
+ 0x4b2, /* U+0004b3: CYRILLIC SMALL LETTER HA WITH DESCENDER*/
+ 0x4b4, /* U+0004b4: CYRILLIC CAPITAL LIGATURE TE TSE*/
+ 0x4b4, /* U+0004b5: CYRILLIC SMALL LIGATURE TE TSE*/
+ 0x4b6, /* U+0004b6: CYRILLIC CAPITAL LETTER CHE WITH DESCENDER*/
+ 0x4b6, /* U+0004b7: CYRILLIC SMALL LETTER CHE WITH DESCENDER*/
+ 0x4b8, /* U+0004b8: CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE*/
+ 0x4b8, /* U+0004b9: CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE*/
+ 0x4ba, /* U+0004ba: CYRILLIC CAPITAL LETTER SHHA*/
+ 0x4ba, /* U+0004bb: CYRILLIC SMALL LETTER SHHA*/
+ 0x4bc, /* U+0004bc: CYRILLIC CAPITAL LETTER ABKHASIAN CHE*/
+ 0x4bc, /* U+0004bd: CYRILLIC SMALL LETTER ABKHASIAN CHE*/
+ 0x4be, /* U+0004be: CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER*/
+ 0x4be, /* U+0004bf: CYRILLIC SMALL LETTER ABKHASIAN CHE WITH DESCENDER*/
+ 0x4c0, /* U+0004c0: CYRILLIC LETTER PALOCHKA*/
+ 0x4c1, /* U+0004c1: CYRILLIC CAPITAL LETTER ZHE WITH BREVE*/
+ 0x4c1, /* U+0004c2: CYRILLIC SMALL LETTER ZHE WITH BREVE*/
+ 0x4c3, /* U+0004c3: CYRILLIC CAPITAL LETTER KA WITH HOOK*/
+ 0x4c3, /* U+0004c4: CYRILLIC SMALL LETTER KA WITH HOOK*/
+ 0x4c5, /* U+0004c5: CYRILLIC CAPITAL LETTER EL WITH TAIL*/
+ 0x4c5, /* U+0004c6: CYRILLIC SMALL LETTER EL WITH TAIL*/
+ 0x4c7, /* U+0004c7: CYRILLIC CAPITAL LETTER EN WITH HOOK*/
+ 0x4c7, /* U+0004c8: CYRILLIC SMALL LETTER EN WITH HOOK*/
+ 0x4c9, /* U+0004c9: CYRILLIC CAPITAL LETTER EN WITH TAIL*/
+ 0x4c9, /* U+0004ca: CYRILLIC SMALL LETTER EN WITH TAIL*/
+ 0x4cb, /* U+0004cb: CYRILLIC CAPITAL LETTER KHAKASSIAN CHE*/
+ 0x4cb, /* U+0004cc: CYRILLIC SMALL LETTER KHAKASSIAN CHE*/
+ 0x4cd, /* U+0004cd: CYRILLIC CAPITAL LETTER EM WITH TAIL*/
+ 0x4cd, /* U+0004ce: CYRILLIC SMALL LETTER EM WITH TAIL*/
+ 0x4c0, /* U+0004cf: CYRILLIC SMALL LETTER PALOCHKA*/
+ 0x4d0, /* U+0004d0: CYRILLIC CAPITAL LETTER A WITH BREVE*/
+ 0x4d0, /* U+0004d1: CYRILLIC SMALL LETTER A WITH BREVE*/
+ 0x4d2, /* U+0004d2: CYRILLIC CAPITAL LETTER A WITH DIAERESIS*/
+ 0x4d2, /* U+0004d3: CYRILLIC SMALL LETTER A WITH DIAERESIS*/
+ 0x4d4, /* U+0004d4: CYRILLIC CAPITAL LIGATURE A IE*/
+ 0x4d4, /* U+0004d5: CYRILLIC SMALL LIGATURE A IE*/
+ 0x4d6, /* U+0004d6: CYRILLIC CAPITAL LETTER IE WITH BREVE*/
+ 0x4d6, /* U+0004d7: CYRILLIC SMALL LETTER IE WITH BREVE*/
+ 0x4d8, /* U+0004d8: CYRILLIC CAPITAL LETTER SCHWA*/
+ 0x4d8, /* U+0004d9: CYRILLIC SMALL LETTER SCHWA*/
+ 0x4da, /* U+0004da: CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS*/
+ 0x4da, /* U+0004db: CYRILLIC SMALL LETTER SCHWA WITH DIAERESIS*/
+ 0x4dc, /* U+0004dc: CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS*/
+ 0x4dc, /* U+0004dd: CYRILLIC SMALL LETTER ZHE WITH DIAERESIS*/
+ 0x4de, /* U+0004de: CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS*/
+ 0x4de, /* U+0004df: CYRILLIC SMALL LETTER ZE WITH DIAERESIS*/
+ 0x4e0, /* U+0004e0: CYRILLIC CAPITAL LETTER ABKHASIAN DZE*/
+ 0x4e0, /* U+0004e1: CYRILLIC SMALL LETTER ABKHASIAN DZE*/
+ 0x4e2, /* U+0004e2: CYRILLIC CAPITAL LETTER I WITH MACRON*/
+ 0x4e2, /* U+0004e3: CYRILLIC SMALL LETTER I WITH MACRON*/
+ 0x4e4, /* U+0004e4: CYRILLIC CAPITAL LETTER I WITH DIAERESIS*/
+ 0x4e4, /* U+0004e5: CYRILLIC SMALL LETTER I WITH DIAERESIS*/
+ 0x4e6, /* U+0004e6: CYRILLIC CAPITAL LETTER O WITH DIAERESIS*/
+ 0x4e6, /* U+0004e7: CYRILLIC SMALL LETTER O WITH DIAERESIS*/
+ 0x4e8, /* U+0004e8: CYRILLIC CAPITAL LETTER BARRED O*/
+ 0x4e8, /* U+0004e9: CYRILLIC SMALL LETTER BARRED O*/
+ 0x4ea, /* U+0004ea: CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS*/
+ 0x4ea, /* U+0004eb: CYRILLIC SMALL LETTER BARRED O WITH DIAERESIS*/
+ 0x4ec, /* U+0004ec: CYRILLIC CAPITAL LETTER E WITH DIAERESIS*/
+ 0x4ec, /* U+0004ed: CYRILLIC SMALL LETTER E WITH DIAERESIS*/
+ 0x4ee, /* U+0004ee: CYRILLIC CAPITAL LETTER U WITH MACRON*/
+ 0x4ee, /* U+0004ef: CYRILLIC SMALL LETTER U WITH MACRON*/
+ 0x4f0, /* U+0004f0: CYRILLIC CAPITAL LETTER U WITH DIAERESIS*/
+ 0x4f0, /* U+0004f1: CYRILLIC SMALL LETTER U WITH DIAERESIS*/
+ 0x4f2, /* U+0004f2: CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE*/
+ 0x4f2, /* U+0004f3: CYRILLIC SMALL LETTER U WITH DOUBLE ACUTE*/
+ 0x4f4, /* U+0004f4: CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS*/
+ 0x4f4, /* U+0004f5: CYRILLIC SMALL LETTER CHE WITH DIAERESIS*/
+ 0x4f6, /* U+0004f6: CYRILLIC CAPITAL LETTER GHE WITH DESCENDER*/
+ 0x4f6, /* U+0004f7: CYRILLIC SMALL LETTER GHE WITH DESCENDER*/
+ 0x4f8, /* U+0004f8: CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS*/
+ 0x4f8, /* U+0004f9: CYRILLIC SMALL LETTER YERU WITH DIAERESIS*/
+ 0x4fa, /* U+0004fa: CYRILLIC CAPITAL LETTER GHE WITH STROKE AND HOOK*/
+ 0x4fa, /* U+0004fb: CYRILLIC SMALL LETTER GHE WITH STROKE AND HOOK*/
+ 0x4fc, /* U+0004fc: CYRILLIC CAPITAL LETTER HA WITH HOOK*/
+ 0x4fc, /* U+0004fd: CYRILLIC SMALL LETTER HA WITH HOOK*/
+ 0x4fe, /* U+0004fe: CYRILLIC CAPITAL LETTER HA WITH STROKE*/
+ 0x4fe, /* U+0004ff: CYRILLIC SMALL LETTER HA WITH STROKE*/
+ 0x500, /* U+000500: CYRILLIC CAPITAL LETTER KOMI DE*/
+ 0x500, /* U+000501: CYRILLIC SMALL LETTER KOMI DE*/
+ 0x502, /* U+000502: CYRILLIC CAPITAL LETTER KOMI DJE*/
+ 0x502, /* U+000503: CYRILLIC SMALL LETTER KOMI DJE*/
+ 0x504, /* U+000504: CYRILLIC CAPITAL LETTER KOMI ZJE*/
+ 0x504, /* U+000505: CYRILLIC SMALL LETTER KOMI ZJE*/
+ 0x506, /* U+000506: CYRILLIC CAPITAL LETTER KOMI DZJE*/
+ 0x506, /* U+000507: CYRILLIC SMALL LETTER KOMI DZJE*/
+ 0x508, /* U+000508: CYRILLIC CAPITAL LETTER KOMI LJE*/
+ 0x508, /* U+000509: CYRILLIC SMALL LETTER KOMI LJE*/
+ 0x50a, /* U+00050a: CYRILLIC CAPITAL LETTER KOMI NJE*/
+ 0x50a, /* U+00050b: CYRILLIC SMALL LETTER KOMI NJE*/
+ 0x50c, /* U+00050c: CYRILLIC CAPITAL LETTER KOMI SJE*/
+ 0x50c, /* U+00050d: CYRILLIC SMALL LETTER KOMI SJE*/
+ 0x50e, /* U+00050e: CYRILLIC CAPITAL LETTER KOMI TJE*/
+ 0x50e, /* U+00050f: CYRILLIC SMALL LETTER KOMI TJE*/
+ 0x510, /* U+000510: CYRILLIC CAPITAL LETTER REVERSED ZE*/
+ 0x510, /* U+000511: CYRILLIC SMALL LETTER REVERSED ZE*/
+ 0x512, /* U+000512: CYRILLIC CAPITAL LETTER EL WITH HOOK*/
+ 0x512, /* U+000513: CYRILLIC SMALL LETTER EL WITH HOOK*/
+ 0x514, /* U+000514: CYRILLIC CAPITAL LETTER LHA*/
+ 0x514, /* U+000515: CYRILLIC SMALL LETTER LHA*/
+ 0x516, /* U+000516: CYRILLIC CAPITAL LETTER RHA*/
+ 0x516, /* U+000517: CYRILLIC SMALL LETTER RHA*/
+ 0x518, /* U+000518: CYRILLIC CAPITAL LETTER YAE*/
+ 0x518, /* U+000519: CYRILLIC SMALL LETTER YAE*/
+ 0x51a, /* U+00051a: CYRILLIC CAPITAL LETTER QA*/
+ 0x51a, /* U+00051b: CYRILLIC SMALL LETTER QA*/
+ 0x51c, /* U+00051c: CYRILLIC CAPITAL LETTER WE*/
+ 0x51c, /* U+00051d: CYRILLIC SMALL LETTER WE*/
+ 0x51e, /* U+00051e: CYRILLIC CAPITAL LETTER ALEUT KA*/
+ 0x51e, /* U+00051f: CYRILLIC SMALL LETTER ALEUT KA*/
+ 0x520, /* U+000520: CYRILLIC CAPITAL LETTER EL WITH MIDDLE HOOK*/
+ 0x520, /* U+000521: CYRILLIC SMALL LETTER EL WITH MIDDLE HOOK*/
+ 0x522, /* U+000522: CYRILLIC CAPITAL LETTER EN WITH MIDDLE HOOK*/
+ 0x522, /* U+000523: CYRILLIC SMALL LETTER EN WITH MIDDLE HOOK*/
+ 0x524, /* U+000524: CYRILLIC CAPITAL LETTER PE WITH DESCENDER*/
+ 0x524, /* U+000525: CYRILLIC SMALL LETTER PE WITH DESCENDER*/
+ 0x526, /* U+000526: CYRILLIC CAPITAL LETTER SHHA WITH DESCENDER*/
+ 0x526, /* U+000527: CYRILLIC SMALL LETTER SHHA WITH DESCENDER*/
+ 0x528, /* U+000528: */
+ 0x529, /* U+000529: */
+ 0x52a, /* U+00052a: */
+ 0x52b, /* U+00052b: */
+ 0x52c, /* U+00052c: */
+ 0x52d, /* U+00052d: */
+ 0x52e, /* U+00052e: */
+ 0x52f, /* U+00052f: */
+ 0x530, /* U+000530: */
+ 0x531, /* U+000531: ARMENIAN CAPITAL LETTER AYB*/
+ 0x532, /* U+000532: ARMENIAN CAPITAL LETTER BEN*/
+ 0x533, /* U+000533: ARMENIAN CAPITAL LETTER GIM*/
+ 0x534, /* U+000534: ARMENIAN CAPITAL LETTER DA*/
+ 0x535, /* U+000535: ARMENIAN CAPITAL LETTER ECH*/
+ 0x536, /* U+000536: ARMENIAN CAPITAL LETTER ZA*/
+ 0x537, /* U+000537: ARMENIAN CAPITAL LETTER EH*/
+ 0x538, /* U+000538: ARMENIAN CAPITAL LETTER ET*/
+ 0x539, /* U+000539: ARMENIAN CAPITAL LETTER TO*/
+ 0x53a, /* U+00053a: ARMENIAN CAPITAL LETTER ZHE*/
+ 0x53b, /* U+00053b: ARMENIAN CAPITAL LETTER INI*/
+ 0x53c, /* U+00053c: ARMENIAN CAPITAL LETTER LIWN*/
+ 0x53d, /* U+00053d: ARMENIAN CAPITAL LETTER XEH*/
+ 0x53e, /* U+00053e: ARMENIAN CAPITAL LETTER CA*/
+ 0x53f, /* U+00053f: ARMENIAN CAPITAL LETTER KEN*/
+ 0x540, /* U+000540: ARMENIAN CAPITAL LETTER HO*/
+ 0x541, /* U+000541: ARMENIAN CAPITAL LETTER JA*/
+ 0x542, /* U+000542: ARMENIAN CAPITAL LETTER GHAD*/
+ 0x543, /* U+000543: ARMENIAN CAPITAL LETTER CHEH*/
+ 0x544, /* U+000544: ARMENIAN CAPITAL LETTER MEN*/
+ 0x545, /* U+000545: ARMENIAN CAPITAL LETTER YI*/
+ 0x546, /* U+000546: ARMENIAN CAPITAL LETTER NOW*/
+ 0x547, /* U+000547: ARMENIAN CAPITAL LETTER SHA*/
+ 0x548, /* U+000548: ARMENIAN CAPITAL LETTER VO*/
+ 0x549, /* U+000549: ARMENIAN CAPITAL LETTER CHA*/
+ 0x54a, /* U+00054a: ARMENIAN CAPITAL LETTER PEH*/
+ 0x54b, /* U+00054b: ARMENIAN CAPITAL LETTER JHEH*/
+ 0x54c, /* U+00054c: ARMENIAN CAPITAL LETTER RA*/
+ 0x54d, /* U+00054d: ARMENIAN CAPITAL LETTER SEH*/
+ 0x54e, /* U+00054e: ARMENIAN CAPITAL LETTER VEW*/
+ 0x54f, /* U+00054f: ARMENIAN CAPITAL LETTER TIWN*/
+ 0x550, /* U+000550: ARMENIAN CAPITAL LETTER REH*/
+ 0x551, /* U+000551: ARMENIAN CAPITAL LETTER CO*/
+ 0x552, /* U+000552: ARMENIAN CAPITAL LETTER YIWN*/
+ 0x553, /* U+000553: ARMENIAN CAPITAL LETTER PIWR*/
+ 0x554, /* U+000554: ARMENIAN CAPITAL LETTER KEH*/
+ 0x555, /* U+000555: ARMENIAN CAPITAL LETTER OH*/
+ 0x556, /* U+000556: ARMENIAN CAPITAL LETTER FEH*/
+ 0x557, /* U+000557: */
+ 0x558, /* U+000558: */
+ 0x559, /* U+000559: ARMENIAN MODIFIER LETTER LEFT HALF RING*/
+ 0x55a, /* U+00055a: ARMENIAN APOSTROPHE*/
+ 0x55b, /* U+00055b: ARMENIAN EMPHASIS MARK*/
+ 0x55c, /* U+00055c: ARMENIAN EXCLAMATION MARK*/
+ 0x55d, /* U+00055d: ARMENIAN COMMA*/
+ 0x55e, /* U+00055e: ARMENIAN QUESTION MARK*/
+ 0x55f, /* U+00055f: ARMENIAN ABBREVIATION MARK*/
+ 0x560, /* U+000560: */
+ 0x531, /* U+000561: ARMENIAN SMALL LETTER AYB*/
+ 0x532, /* U+000562: ARMENIAN SMALL LETTER BEN*/
+ 0x533, /* U+000563: ARMENIAN SMALL LETTER GIM*/
+ 0x534, /* U+000564: ARMENIAN SMALL LETTER DA*/
+ 0x535, /* U+000565: ARMENIAN SMALL LETTER ECH*/
+ 0x536, /* U+000566: ARMENIAN SMALL LETTER ZA*/
+ 0x537, /* U+000567: ARMENIAN SMALL LETTER EH*/
+ 0x538, /* U+000568: ARMENIAN SMALL LETTER ET*/
+ 0x539, /* U+000569: ARMENIAN SMALL LETTER TO*/
+ 0x53a, /* U+00056a: ARMENIAN SMALL LETTER ZHE*/
+ 0x53b, /* U+00056b: ARMENIAN SMALL LETTER INI*/
+ 0x53c, /* U+00056c: ARMENIAN SMALL LETTER LIWN*/
+ 0x53d, /* U+00056d: ARMENIAN SMALL LETTER XEH*/
+ 0x53e, /* U+00056e: ARMENIAN SMALL LETTER CA*/
+ 0x53f, /* U+00056f: ARMENIAN SMALL LETTER KEN*/
+ 0x540, /* U+000570: ARMENIAN SMALL LETTER HO*/
+ 0x541, /* U+000571: ARMENIAN SMALL LETTER JA*/
+ 0x542, /* U+000572: ARMENIAN SMALL LETTER GHAD*/
+ 0x543, /* U+000573: ARMENIAN SMALL LETTER CHEH*/
+ 0x544, /* U+000574: ARMENIAN SMALL LETTER MEN*/
+ 0x545, /* U+000575: ARMENIAN SMALL LETTER YI*/
+ 0x546, /* U+000576: ARMENIAN SMALL LETTER NOW*/
+ 0x547, /* U+000577: ARMENIAN SMALL LETTER SHA*/
+ 0x548, /* U+000578: ARMENIAN SMALL LETTER VO*/
+ 0x549, /* U+000579: ARMENIAN SMALL LETTER CHA*/
+ 0x54a, /* U+00057a: ARMENIAN SMALL LETTER PEH*/
+ 0x54b, /* U+00057b: ARMENIAN SMALL LETTER JHEH*/
+ 0x54c, /* U+00057c: ARMENIAN SMALL LETTER RA*/
+ 0x54d, /* U+00057d: ARMENIAN SMALL LETTER SEH*/
+ 0x54e, /* U+00057e: ARMENIAN SMALL LETTER VEW*/
+ 0x54f, /* U+00057f: ARMENIAN SMALL LETTER TIWN*/
+ 0x550, /* U+000580: ARMENIAN SMALL LETTER REH*/
+ 0x551, /* U+000581: ARMENIAN SMALL LETTER CO*/
+ 0x552, /* U+000582: ARMENIAN SMALL LETTER YIWN*/
+ 0x553, /* U+000583: ARMENIAN SMALL LETTER PIWR*/
+ 0x554, /* U+000584: ARMENIAN SMALL LETTER KEH*/
+ 0x555, /* U+000585: ARMENIAN SMALL LETTER OH*/
+ 0x556, /* U+000586: ARMENIAN SMALL LETTER FEH*/
+};
+
+static const RTUNICP g_afRTUniUpper0x001d79[] =
+{
+ 0xa77d, /* U+001d79: LATIN SMALL LETTER INSULAR G*/
+ 0x1d7a, /* U+001d7a: LATIN SMALL LETTER TH WITH STRIKETHROUGH*/
+ 0x1d7b, /* U+001d7b: LATIN SMALL CAPITAL LETTER I WITH STROKE*/
+ 0x1d7c, /* U+001d7c: LATIN SMALL LETTER IOTA WITH STROKE*/
+ 0x2c63, /* U+001d7d: LATIN SMALL LETTER P WITH STROKE*/
+ 0x1d7e, /* U+001d7e: LATIN SMALL CAPITAL LETTER U WITH STROKE*/
+ 0x1d7f, /* U+001d7f: LATIN SMALL LETTER UPSILON WITH STROKE*/
+ 0x1d80, /* U+001d80: LATIN SMALL LETTER B WITH PALATAL HOOK*/
+ 0x1d81, /* U+001d81: LATIN SMALL LETTER D WITH PALATAL HOOK*/
+ 0x1d82, /* U+001d82: LATIN SMALL LETTER F WITH PALATAL HOOK*/
+ 0x1d83, /* U+001d83: LATIN SMALL LETTER G WITH PALATAL HOOK*/
+ 0x1d84, /* U+001d84: LATIN SMALL LETTER K WITH PALATAL HOOK*/
+ 0x1d85, /* U+001d85: LATIN SMALL LETTER L WITH PALATAL HOOK*/
+ 0x1d86, /* U+001d86: LATIN SMALL LETTER M WITH PALATAL HOOK*/
+ 0x1d87, /* U+001d87: LATIN SMALL LETTER N WITH PALATAL HOOK*/
+ 0x1d88, /* U+001d88: LATIN SMALL LETTER P WITH PALATAL HOOK*/
+ 0x1d89, /* U+001d89: LATIN SMALL LETTER R WITH PALATAL HOOK*/
+ 0x1d8a, /* U+001d8a: LATIN SMALL LETTER S WITH PALATAL HOOK*/
+ 0x1d8b, /* U+001d8b: LATIN SMALL LETTER ESH WITH PALATAL HOOK*/
+ 0x1d8c, /* U+001d8c: LATIN SMALL LETTER V WITH PALATAL HOOK*/
+ 0x1d8d, /* U+001d8d: LATIN SMALL LETTER X WITH PALATAL HOOK*/
+ 0x1d8e, /* U+001d8e: LATIN SMALL LETTER Z WITH PALATAL HOOK*/
+ 0x1d8f, /* U+001d8f: LATIN SMALL LETTER A WITH RETROFLEX HOOK*/
+ 0x1d90, /* U+001d90: LATIN SMALL LETTER ALPHA WITH RETROFLEX HOOK*/
+ 0x1d91, /* U+001d91: LATIN SMALL LETTER D WITH HOOK AND TAIL*/
+ 0x1d92, /* U+001d92: LATIN SMALL LETTER E WITH RETROFLEX HOOK*/
+ 0x1d93, /* U+001d93: LATIN SMALL LETTER OPEN E WITH RETROFLEX HOOK*/
+ 0x1d94, /* U+001d94: LATIN SMALL LETTER REVERSED OPEN E WITH RETROFLEX HOOK*/
+ 0x1d95, /* U+001d95: LATIN SMALL LETTER SCHWA WITH RETROFLEX HOOK*/
+ 0x1d96, /* U+001d96: LATIN SMALL LETTER I WITH RETROFLEX HOOK*/
+ 0x1d97, /* U+001d97: LATIN SMALL LETTER OPEN O WITH RETROFLEX HOOK*/
+ 0x1d98, /* U+001d98: LATIN SMALL LETTER ESH WITH RETROFLEX HOOK*/
+ 0x1d99, /* U+001d99: LATIN SMALL LETTER U WITH RETROFLEX HOOK*/
+ 0x1d9a, /* U+001d9a: LATIN SMALL LETTER EZH WITH RETROFLEX HOOK*/
+ 0x1d9b, /* U+001d9b: MODIFIER LETTER SMALL TURNED ALPHA*/
+ 0x1d9c, /* U+001d9c: MODIFIER LETTER SMALL C*/
+ 0x1d9d, /* U+001d9d: MODIFIER LETTER SMALL C WITH CURL*/
+ 0x1d9e, /* U+001d9e: MODIFIER LETTER SMALL ETH*/
+ 0x1d9f, /* U+001d9f: MODIFIER LETTER SMALL REVERSED OPEN E*/
+ 0x1da0, /* U+001da0: MODIFIER LETTER SMALL F*/
+ 0x1da1, /* U+001da1: MODIFIER LETTER SMALL DOTLESS J WITH STROKE*/
+ 0x1da2, /* U+001da2: MODIFIER LETTER SMALL SCRIPT G*/
+ 0x1da3, /* U+001da3: MODIFIER LETTER SMALL TURNED H*/
+ 0x1da4, /* U+001da4: MODIFIER LETTER SMALL I WITH STROKE*/
+ 0x1da5, /* U+001da5: MODIFIER LETTER SMALL IOTA*/
+ 0x1da6, /* U+001da6: MODIFIER LETTER SMALL CAPITAL I*/
+ 0x1da7, /* U+001da7: MODIFIER LETTER SMALL CAPITAL I WITH STROKE*/
+ 0x1da8, /* U+001da8: MODIFIER LETTER SMALL J WITH CROSSED-TAIL*/
+ 0x1da9, /* U+001da9: MODIFIER LETTER SMALL L WITH RETROFLEX HOOK*/
+ 0x1daa, /* U+001daa: MODIFIER LETTER SMALL L WITH PALATAL HOOK*/
+ 0x1dab, /* U+001dab: MODIFIER LETTER SMALL CAPITAL L*/
+ 0x1dac, /* U+001dac: MODIFIER LETTER SMALL M WITH HOOK*/
+ 0x1dad, /* U+001dad: MODIFIER LETTER SMALL TURNED M WITH LONG LEG*/
+ 0x1dae, /* U+001dae: MODIFIER LETTER SMALL N WITH LEFT HOOK*/
+ 0x1daf, /* U+001daf: MODIFIER LETTER SMALL N WITH RETROFLEX HOOK*/
+ 0x1db0, /* U+001db0: MODIFIER LETTER SMALL CAPITAL N*/
+ 0x1db1, /* U+001db1: MODIFIER LETTER SMALL BARRED O*/
+ 0x1db2, /* U+001db2: MODIFIER LETTER SMALL PHI*/
+ 0x1db3, /* U+001db3: MODIFIER LETTER SMALL S WITH HOOK*/
+ 0x1db4, /* U+001db4: MODIFIER LETTER SMALL ESH*/
+ 0x1db5, /* U+001db5: MODIFIER LETTER SMALL T WITH PALATAL HOOK*/
+ 0x1db6, /* U+001db6: MODIFIER LETTER SMALL U BAR*/
+ 0x1db7, /* U+001db7: MODIFIER LETTER SMALL UPSILON*/
+ 0x1db8, /* U+001db8: MODIFIER LETTER SMALL CAPITAL U*/
+ 0x1db9, /* U+001db9: MODIFIER LETTER SMALL V WITH HOOK*/
+ 0x1dba, /* U+001dba: MODIFIER LETTER SMALL TURNED V*/
+ 0x1dbb, /* U+001dbb: MODIFIER LETTER SMALL Z*/
+ 0x1dbc, /* U+001dbc: MODIFIER LETTER SMALL Z WITH RETROFLEX HOOK*/
+ 0x1dbd, /* U+001dbd: MODIFIER LETTER SMALL Z WITH CURL*/
+ 0x1dbe, /* U+001dbe: MODIFIER LETTER SMALL EZH*/
+ 0x1dbf, /* U+001dbf: MODIFIER LETTER SMALL THETA*/
+ 0x1dc0, /* U+001dc0: COMBINING DOTTED GRAVE ACCENT*/
+ 0x1dc1, /* U+001dc1: COMBINING DOTTED ACUTE ACCENT*/
+ 0x1dc2, /* U+001dc2: COMBINING SNAKE BELOW*/
+ 0x1dc3, /* U+001dc3: COMBINING SUSPENSION MARK*/
+ 0x1dc4, /* U+001dc4: COMBINING MACRON-ACUTE*/
+ 0x1dc5, /* U+001dc5: COMBINING GRAVE-MACRON*/
+ 0x1dc6, /* U+001dc6: COMBINING MACRON-GRAVE*/
+ 0x1dc7, /* U+001dc7: COMBINING ACUTE-MACRON*/
+ 0x1dc8, /* U+001dc8: COMBINING GRAVE-ACUTE-GRAVE*/
+ 0x1dc9, /* U+001dc9: COMBINING ACUTE-GRAVE-ACUTE*/
+ 0x1dca, /* U+001dca: COMBINING LATIN SMALL LETTER R BELOW*/
+ 0x1dcb, /* U+001dcb: COMBINING BREVE-MACRON*/
+ 0x1dcc, /* U+001dcc: COMBINING MACRON-BREVE*/
+ 0x1dcd, /* U+001dcd: COMBINING DOUBLE CIRCUMFLEX ABOVE*/
+ 0x1dce, /* U+001dce: COMBINING OGONEK ABOVE*/
+ 0x1dcf, /* U+001dcf: COMBINING ZIGZAG BELOW*/
+ 0x1dd0, /* U+001dd0: COMBINING IS BELOW*/
+ 0x1dd1, /* U+001dd1: COMBINING UR ABOVE*/
+ 0x1dd2, /* U+001dd2: COMBINING US ABOVE*/
+ 0x1dd3, /* U+001dd3: COMBINING LATIN SMALL LETTER FLATTENED OPEN A ABOVE*/
+ 0x1dd4, /* U+001dd4: COMBINING LATIN SMALL LETTER AE*/
+ 0x1dd5, /* U+001dd5: COMBINING LATIN SMALL LETTER AO*/
+ 0x1dd6, /* U+001dd6: COMBINING LATIN SMALL LETTER AV*/
+ 0x1dd7, /* U+001dd7: COMBINING LATIN SMALL LETTER C CEDILLA*/
+ 0x1dd8, /* U+001dd8: COMBINING LATIN SMALL LETTER INSULAR D*/
+ 0x1dd9, /* U+001dd9: COMBINING LATIN SMALL LETTER ETH*/
+ 0x1dda, /* U+001dda: COMBINING LATIN SMALL LETTER G*/
+ 0x1ddb, /* U+001ddb: COMBINING LATIN LETTER SMALL CAPITAL G*/
+ 0x1ddc, /* U+001ddc: COMBINING LATIN SMALL LETTER K*/
+ 0x1ddd, /* U+001ddd: COMBINING LATIN SMALL LETTER L*/
+ 0x1dde, /* U+001dde: COMBINING LATIN LETTER SMALL CAPITAL L*/
+ 0x1ddf, /* U+001ddf: COMBINING LATIN LETTER SMALL CAPITAL M*/
+ 0x1de0, /* U+001de0: COMBINING LATIN SMALL LETTER N*/
+ 0x1de1, /* U+001de1: COMBINING LATIN LETTER SMALL CAPITAL N*/
+ 0x1de2, /* U+001de2: COMBINING LATIN LETTER SMALL CAPITAL R*/
+ 0x1de3, /* U+001de3: COMBINING LATIN SMALL LETTER R ROTUNDA*/
+ 0x1de4, /* U+001de4: COMBINING LATIN SMALL LETTER S*/
+ 0x1de5, /* U+001de5: COMBINING LATIN SMALL LETTER LONG S*/
+ 0x1de6, /* U+001de6: COMBINING LATIN SMALL LETTER Z*/
+ 0x1de7, /* U+001de7: */
+ 0x1de8, /* U+001de8: */
+ 0x1de9, /* U+001de9: */
+ 0x1dea, /* U+001dea: */
+ 0x1deb, /* U+001deb: */
+ 0x1dec, /* U+001dec: */
+ 0x1ded, /* U+001ded: */
+ 0x1dee, /* U+001dee: */
+ 0x1def, /* U+001def: */
+ 0x1df0, /* U+001df0: */
+ 0x1df1, /* U+001df1: */
+ 0x1df2, /* U+001df2: */
+ 0x1df3, /* U+001df3: */
+ 0x1df4, /* U+001df4: */
+ 0x1df5, /* U+001df5: */
+ 0x1df6, /* U+001df6: */
+ 0x1df7, /* U+001df7: */
+ 0x1df8, /* U+001df8: */
+ 0x1df9, /* U+001df9: */
+ 0x1dfa, /* U+001dfa: */
+ 0x1dfb, /* U+001dfb: */
+ 0x1dfc, /* U+001dfc: COMBINING DOUBLE INVERTED BREVE BELOW*/
+ 0x1dfd, /* U+001dfd: COMBINING ALMOST EQUAL TO BELOW*/
+ 0x1dfe, /* U+001dfe: COMBINING LEFT ARROWHEAD ABOVE*/
+ 0x1dff, /* U+001dff: COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW*/
+ 0x1e00, /* U+001e00: LATIN CAPITAL LETTER A WITH RING BELOW*/
+ 0x1e00, /* U+001e01: LATIN SMALL LETTER A WITH RING BELOW*/
+ 0x1e02, /* U+001e02: LATIN CAPITAL LETTER B WITH DOT ABOVE*/
+ 0x1e02, /* U+001e03: LATIN SMALL LETTER B WITH DOT ABOVE*/
+ 0x1e04, /* U+001e04: LATIN CAPITAL LETTER B WITH DOT BELOW*/
+ 0x1e04, /* U+001e05: LATIN SMALL LETTER B WITH DOT BELOW*/
+ 0x1e06, /* U+001e06: LATIN CAPITAL LETTER B WITH LINE BELOW*/
+ 0x1e06, /* U+001e07: LATIN SMALL LETTER B WITH LINE BELOW*/
+ 0x1e08, /* U+001e08: LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE*/
+ 0x1e08, /* U+001e09: LATIN SMALL LETTER C WITH CEDILLA AND ACUTE*/
+ 0x1e0a, /* U+001e0a: LATIN CAPITAL LETTER D WITH DOT ABOVE*/
+ 0x1e0a, /* U+001e0b: LATIN SMALL LETTER D WITH DOT ABOVE*/
+ 0x1e0c, /* U+001e0c: LATIN CAPITAL LETTER D WITH DOT BELOW*/
+ 0x1e0c, /* U+001e0d: LATIN SMALL LETTER D WITH DOT BELOW*/
+ 0x1e0e, /* U+001e0e: LATIN CAPITAL LETTER D WITH LINE BELOW*/
+ 0x1e0e, /* U+001e0f: LATIN SMALL LETTER D WITH LINE BELOW*/
+ 0x1e10, /* U+001e10: LATIN CAPITAL LETTER D WITH CEDILLA*/
+ 0x1e10, /* U+001e11: LATIN SMALL LETTER D WITH CEDILLA*/
+ 0x1e12, /* U+001e12: LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW*/
+ 0x1e12, /* U+001e13: LATIN SMALL LETTER D WITH CIRCUMFLEX BELOW*/
+ 0x1e14, /* U+001e14: LATIN CAPITAL LETTER E WITH MACRON AND GRAVE*/
+ 0x1e14, /* U+001e15: LATIN SMALL LETTER E WITH MACRON AND GRAVE*/
+ 0x1e16, /* U+001e16: LATIN CAPITAL LETTER E WITH MACRON AND ACUTE*/
+ 0x1e16, /* U+001e17: LATIN SMALL LETTER E WITH MACRON AND ACUTE*/
+ 0x1e18, /* U+001e18: LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW*/
+ 0x1e18, /* U+001e19: LATIN SMALL LETTER E WITH CIRCUMFLEX BELOW*/
+ 0x1e1a, /* U+001e1a: LATIN CAPITAL LETTER E WITH TILDE BELOW*/
+ 0x1e1a, /* U+001e1b: LATIN SMALL LETTER E WITH TILDE BELOW*/
+ 0x1e1c, /* U+001e1c: LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE*/
+ 0x1e1c, /* U+001e1d: LATIN SMALL LETTER E WITH CEDILLA AND BREVE*/
+ 0x1e1e, /* U+001e1e: LATIN CAPITAL LETTER F WITH DOT ABOVE*/
+ 0x1e1e, /* U+001e1f: LATIN SMALL LETTER F WITH DOT ABOVE*/
+ 0x1e20, /* U+001e20: LATIN CAPITAL LETTER G WITH MACRON*/
+ 0x1e20, /* U+001e21: LATIN SMALL LETTER G WITH MACRON*/
+ 0x1e22, /* U+001e22: LATIN CAPITAL LETTER H WITH DOT ABOVE*/
+ 0x1e22, /* U+001e23: LATIN SMALL LETTER H WITH DOT ABOVE*/
+ 0x1e24, /* U+001e24: LATIN CAPITAL LETTER H WITH DOT BELOW*/
+ 0x1e24, /* U+001e25: LATIN SMALL LETTER H WITH DOT BELOW*/
+ 0x1e26, /* U+001e26: LATIN CAPITAL LETTER H WITH DIAERESIS*/
+ 0x1e26, /* U+001e27: LATIN SMALL LETTER H WITH DIAERESIS*/
+ 0x1e28, /* U+001e28: LATIN CAPITAL LETTER H WITH CEDILLA*/
+ 0x1e28, /* U+001e29: LATIN SMALL LETTER H WITH CEDILLA*/
+ 0x1e2a, /* U+001e2a: LATIN CAPITAL LETTER H WITH BREVE BELOW*/
+ 0x1e2a, /* U+001e2b: LATIN SMALL LETTER H WITH BREVE BELOW*/
+ 0x1e2c, /* U+001e2c: LATIN CAPITAL LETTER I WITH TILDE BELOW*/
+ 0x1e2c, /* U+001e2d: LATIN SMALL LETTER I WITH TILDE BELOW*/
+ 0x1e2e, /* U+001e2e: LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE*/
+ 0x1e2e, /* U+001e2f: LATIN SMALL LETTER I WITH DIAERESIS AND ACUTE*/
+ 0x1e30, /* U+001e30: LATIN CAPITAL LETTER K WITH ACUTE*/
+ 0x1e30, /* U+001e31: LATIN SMALL LETTER K WITH ACUTE*/
+ 0x1e32, /* U+001e32: LATIN CAPITAL LETTER K WITH DOT BELOW*/
+ 0x1e32, /* U+001e33: LATIN SMALL LETTER K WITH DOT BELOW*/
+ 0x1e34, /* U+001e34: LATIN CAPITAL LETTER K WITH LINE BELOW*/
+ 0x1e34, /* U+001e35: LATIN SMALL LETTER K WITH LINE BELOW*/
+ 0x1e36, /* U+001e36: LATIN CAPITAL LETTER L WITH DOT BELOW*/
+ 0x1e36, /* U+001e37: LATIN SMALL LETTER L WITH DOT BELOW*/
+ 0x1e38, /* U+001e38: LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON*/
+ 0x1e38, /* U+001e39: LATIN SMALL LETTER L WITH DOT BELOW AND MACRON*/
+ 0x1e3a, /* U+001e3a: LATIN CAPITAL LETTER L WITH LINE BELOW*/
+ 0x1e3a, /* U+001e3b: LATIN SMALL LETTER L WITH LINE BELOW*/
+ 0x1e3c, /* U+001e3c: LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW*/
+ 0x1e3c, /* U+001e3d: LATIN SMALL LETTER L WITH CIRCUMFLEX BELOW*/
+ 0x1e3e, /* U+001e3e: LATIN CAPITAL LETTER M WITH ACUTE*/
+ 0x1e3e, /* U+001e3f: LATIN SMALL LETTER M WITH ACUTE*/
+ 0x1e40, /* U+001e40: LATIN CAPITAL LETTER M WITH DOT ABOVE*/
+ 0x1e40, /* U+001e41: LATIN SMALL LETTER M WITH DOT ABOVE*/
+ 0x1e42, /* U+001e42: LATIN CAPITAL LETTER M WITH DOT BELOW*/
+ 0x1e42, /* U+001e43: LATIN SMALL LETTER M WITH DOT BELOW*/
+ 0x1e44, /* U+001e44: LATIN CAPITAL LETTER N WITH DOT ABOVE*/
+ 0x1e44, /* U+001e45: LATIN SMALL LETTER N WITH DOT ABOVE*/
+ 0x1e46, /* U+001e46: LATIN CAPITAL LETTER N WITH DOT BELOW*/
+ 0x1e46, /* U+001e47: LATIN SMALL LETTER N WITH DOT BELOW*/
+ 0x1e48, /* U+001e48: LATIN CAPITAL LETTER N WITH LINE BELOW*/
+ 0x1e48, /* U+001e49: LATIN SMALL LETTER N WITH LINE BELOW*/
+ 0x1e4a, /* U+001e4a: LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW*/
+ 0x1e4a, /* U+001e4b: LATIN SMALL LETTER N WITH CIRCUMFLEX BELOW*/
+ 0x1e4c, /* U+001e4c: LATIN CAPITAL LETTER O WITH TILDE AND ACUTE*/
+ 0x1e4c, /* U+001e4d: LATIN SMALL LETTER O WITH TILDE AND ACUTE*/
+ 0x1e4e, /* U+001e4e: LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS*/
+ 0x1e4e, /* U+001e4f: LATIN SMALL LETTER O WITH TILDE AND DIAERESIS*/
+ 0x1e50, /* U+001e50: LATIN CAPITAL LETTER O WITH MACRON AND GRAVE*/
+ 0x1e50, /* U+001e51: LATIN SMALL LETTER O WITH MACRON AND GRAVE*/
+ 0x1e52, /* U+001e52: LATIN CAPITAL LETTER O WITH MACRON AND ACUTE*/
+ 0x1e52, /* U+001e53: LATIN SMALL LETTER O WITH MACRON AND ACUTE*/
+ 0x1e54, /* U+001e54: LATIN CAPITAL LETTER P WITH ACUTE*/
+ 0x1e54, /* U+001e55: LATIN SMALL LETTER P WITH ACUTE*/
+ 0x1e56, /* U+001e56: LATIN CAPITAL LETTER P WITH DOT ABOVE*/
+ 0x1e56, /* U+001e57: LATIN SMALL LETTER P WITH DOT ABOVE*/
+ 0x1e58, /* U+001e58: LATIN CAPITAL LETTER R WITH DOT ABOVE*/
+ 0x1e58, /* U+001e59: LATIN SMALL LETTER R WITH DOT ABOVE*/
+ 0x1e5a, /* U+001e5a: LATIN CAPITAL LETTER R WITH DOT BELOW*/
+ 0x1e5a, /* U+001e5b: LATIN SMALL LETTER R WITH DOT BELOW*/
+ 0x1e5c, /* U+001e5c: LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON*/
+ 0x1e5c, /* U+001e5d: LATIN SMALL LETTER R WITH DOT BELOW AND MACRON*/
+ 0x1e5e, /* U+001e5e: LATIN CAPITAL LETTER R WITH LINE BELOW*/
+ 0x1e5e, /* U+001e5f: LATIN SMALL LETTER R WITH LINE BELOW*/
+ 0x1e60, /* U+001e60: LATIN CAPITAL LETTER S WITH DOT ABOVE*/
+ 0x1e60, /* U+001e61: LATIN SMALL LETTER S WITH DOT ABOVE*/
+ 0x1e62, /* U+001e62: LATIN CAPITAL LETTER S WITH DOT BELOW*/
+ 0x1e62, /* U+001e63: LATIN SMALL LETTER S WITH DOT BELOW*/
+ 0x1e64, /* U+001e64: LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE*/
+ 0x1e64, /* U+001e65: LATIN SMALL LETTER S WITH ACUTE AND DOT ABOVE*/
+ 0x1e66, /* U+001e66: LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE*/
+ 0x1e66, /* U+001e67: LATIN SMALL LETTER S WITH CARON AND DOT ABOVE*/
+ 0x1e68, /* U+001e68: LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE*/
+ 0x1e68, /* U+001e69: LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE*/
+ 0x1e6a, /* U+001e6a: LATIN CAPITAL LETTER T WITH DOT ABOVE*/
+ 0x1e6a, /* U+001e6b: LATIN SMALL LETTER T WITH DOT ABOVE*/
+ 0x1e6c, /* U+001e6c: LATIN CAPITAL LETTER T WITH DOT BELOW*/
+ 0x1e6c, /* U+001e6d: LATIN SMALL LETTER T WITH DOT BELOW*/
+ 0x1e6e, /* U+001e6e: LATIN CAPITAL LETTER T WITH LINE BELOW*/
+ 0x1e6e, /* U+001e6f: LATIN SMALL LETTER T WITH LINE BELOW*/
+ 0x1e70, /* U+001e70: LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW*/
+ 0x1e70, /* U+001e71: LATIN SMALL LETTER T WITH CIRCUMFLEX BELOW*/
+ 0x1e72, /* U+001e72: LATIN CAPITAL LETTER U WITH DIAERESIS BELOW*/
+ 0x1e72, /* U+001e73: LATIN SMALL LETTER U WITH DIAERESIS BELOW*/
+ 0x1e74, /* U+001e74: LATIN CAPITAL LETTER U WITH TILDE BELOW*/
+ 0x1e74, /* U+001e75: LATIN SMALL LETTER U WITH TILDE BELOW*/
+ 0x1e76, /* U+001e76: LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW*/
+ 0x1e76, /* U+001e77: LATIN SMALL LETTER U WITH CIRCUMFLEX BELOW*/
+ 0x1e78, /* U+001e78: LATIN CAPITAL LETTER U WITH TILDE AND ACUTE*/
+ 0x1e78, /* U+001e79: LATIN SMALL LETTER U WITH TILDE AND ACUTE*/
+ 0x1e7a, /* U+001e7a: LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS*/
+ 0x1e7a, /* U+001e7b: LATIN SMALL LETTER U WITH MACRON AND DIAERESIS*/
+ 0x1e7c, /* U+001e7c: LATIN CAPITAL LETTER V WITH TILDE*/
+ 0x1e7c, /* U+001e7d: LATIN SMALL LETTER V WITH TILDE*/
+ 0x1e7e, /* U+001e7e: LATIN CAPITAL LETTER V WITH DOT BELOW*/
+ 0x1e7e, /* U+001e7f: LATIN SMALL LETTER V WITH DOT BELOW*/
+ 0x1e80, /* U+001e80: LATIN CAPITAL LETTER W WITH GRAVE*/
+ 0x1e80, /* U+001e81: LATIN SMALL LETTER W WITH GRAVE*/
+ 0x1e82, /* U+001e82: LATIN CAPITAL LETTER W WITH ACUTE*/
+ 0x1e82, /* U+001e83: LATIN SMALL LETTER W WITH ACUTE*/
+ 0x1e84, /* U+001e84: LATIN CAPITAL LETTER W WITH DIAERESIS*/
+ 0x1e84, /* U+001e85: LATIN SMALL LETTER W WITH DIAERESIS*/
+ 0x1e86, /* U+001e86: LATIN CAPITAL LETTER W WITH DOT ABOVE*/
+ 0x1e86, /* U+001e87: LATIN SMALL LETTER W WITH DOT ABOVE*/
+ 0x1e88, /* U+001e88: LATIN CAPITAL LETTER W WITH DOT BELOW*/
+ 0x1e88, /* U+001e89: LATIN SMALL LETTER W WITH DOT BELOW*/
+ 0x1e8a, /* U+001e8a: LATIN CAPITAL LETTER X WITH DOT ABOVE*/
+ 0x1e8a, /* U+001e8b: LATIN SMALL LETTER X WITH DOT ABOVE*/
+ 0x1e8c, /* U+001e8c: LATIN CAPITAL LETTER X WITH DIAERESIS*/
+ 0x1e8c, /* U+001e8d: LATIN SMALL LETTER X WITH DIAERESIS*/
+ 0x1e8e, /* U+001e8e: LATIN CAPITAL LETTER Y WITH DOT ABOVE*/
+ 0x1e8e, /* U+001e8f: LATIN SMALL LETTER Y WITH DOT ABOVE*/
+ 0x1e90, /* U+001e90: LATIN CAPITAL LETTER Z WITH CIRCUMFLEX*/
+ 0x1e90, /* U+001e91: LATIN SMALL LETTER Z WITH CIRCUMFLEX*/
+ 0x1e92, /* U+001e92: LATIN CAPITAL LETTER Z WITH DOT BELOW*/
+ 0x1e92, /* U+001e93: LATIN SMALL LETTER Z WITH DOT BELOW*/
+ 0x1e94, /* U+001e94: LATIN CAPITAL LETTER Z WITH LINE BELOW*/
+ 0x1e94, /* U+001e95: LATIN SMALL LETTER Z WITH LINE BELOW*/
+ 0x1e96, /* U+001e96: LATIN SMALL LETTER H WITH LINE BELOW*/
+ 0x1e97, /* U+001e97: LATIN SMALL LETTER T WITH DIAERESIS*/
+ 0x1e98, /* U+001e98: LATIN SMALL LETTER W WITH RING ABOVE*/
+ 0x1e99, /* U+001e99: LATIN SMALL LETTER Y WITH RING ABOVE*/
+ 0x1e9a, /* U+001e9a: LATIN SMALL LETTER A WITH RIGHT HALF RING*/
+ 0x1e60, /* U+001e9b: LATIN SMALL LETTER LONG S WITH DOT ABOVE*/
+ 0x1e9c, /* U+001e9c: LATIN SMALL LETTER LONG S WITH DIAGONAL STROKE*/
+ 0x1e9d, /* U+001e9d: LATIN SMALL LETTER LONG S WITH HIGH STROKE*/
+ 0x1e9e, /* U+001e9e: LATIN CAPITAL LETTER SHARP S*/
+ 0x1e9f, /* U+001e9f: LATIN SMALL LETTER DELTA*/
+ 0x1ea0, /* U+001ea0: LATIN CAPITAL LETTER A WITH DOT BELOW*/
+ 0x1ea0, /* U+001ea1: LATIN SMALL LETTER A WITH DOT BELOW*/
+ 0x1ea2, /* U+001ea2: LATIN CAPITAL LETTER A WITH HOOK ABOVE*/
+ 0x1ea2, /* U+001ea3: LATIN SMALL LETTER A WITH HOOK ABOVE*/
+ 0x1ea4, /* U+001ea4: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ea4, /* U+001ea5: LATIN SMALL LETTER A WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ea6, /* U+001ea6: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ea6, /* U+001ea7: LATIN SMALL LETTER A WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ea8, /* U+001ea8: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ea8, /* U+001ea9: LATIN SMALL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1eaa, /* U+001eaa: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE*/
+ 0x1eaa, /* U+001eab: LATIN SMALL LETTER A WITH CIRCUMFLEX AND TILDE*/
+ 0x1eac, /* U+001eac: LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1eac, /* U+001ead: LATIN SMALL LETTER A WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1eae, /* U+001eae: LATIN CAPITAL LETTER A WITH BREVE AND ACUTE*/
+ 0x1eae, /* U+001eaf: LATIN SMALL LETTER A WITH BREVE AND ACUTE*/
+ 0x1eb0, /* U+001eb0: LATIN CAPITAL LETTER A WITH BREVE AND GRAVE*/
+ 0x1eb0, /* U+001eb1: LATIN SMALL LETTER A WITH BREVE AND GRAVE*/
+ 0x1eb2, /* U+001eb2: LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE*/
+ 0x1eb2, /* U+001eb3: LATIN SMALL LETTER A WITH BREVE AND HOOK ABOVE*/
+ 0x1eb4, /* U+001eb4: LATIN CAPITAL LETTER A WITH BREVE AND TILDE*/
+ 0x1eb4, /* U+001eb5: LATIN SMALL LETTER A WITH BREVE AND TILDE*/
+ 0x1eb6, /* U+001eb6: LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW*/
+ 0x1eb6, /* U+001eb7: LATIN SMALL LETTER A WITH BREVE AND DOT BELOW*/
+ 0x1eb8, /* U+001eb8: LATIN CAPITAL LETTER E WITH DOT BELOW*/
+ 0x1eb8, /* U+001eb9: LATIN SMALL LETTER E WITH DOT BELOW*/
+ 0x1eba, /* U+001eba: LATIN CAPITAL LETTER E WITH HOOK ABOVE*/
+ 0x1eba, /* U+001ebb: LATIN SMALL LETTER E WITH HOOK ABOVE*/
+ 0x1ebc, /* U+001ebc: LATIN CAPITAL LETTER E WITH TILDE*/
+ 0x1ebc, /* U+001ebd: LATIN SMALL LETTER E WITH TILDE*/
+ 0x1ebe, /* U+001ebe: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ebe, /* U+001ebf: LATIN SMALL LETTER E WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ec0, /* U+001ec0: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ec0, /* U+001ec1: LATIN SMALL LETTER E WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ec2, /* U+001ec2: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ec2, /* U+001ec3: LATIN SMALL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ec4, /* U+001ec4: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE*/
+ 0x1ec4, /* U+001ec5: LATIN SMALL LETTER E WITH CIRCUMFLEX AND TILDE*/
+ 0x1ec6, /* U+001ec6: LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1ec6, /* U+001ec7: LATIN SMALL LETTER E WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1ec8, /* U+001ec8: LATIN CAPITAL LETTER I WITH HOOK ABOVE*/
+ 0x1ec8, /* U+001ec9: LATIN SMALL LETTER I WITH HOOK ABOVE*/
+ 0x1eca, /* U+001eca: LATIN CAPITAL LETTER I WITH DOT BELOW*/
+ 0x1eca, /* U+001ecb: LATIN SMALL LETTER I WITH DOT BELOW*/
+ 0x1ecc, /* U+001ecc: LATIN CAPITAL LETTER O WITH DOT BELOW*/
+ 0x1ecc, /* U+001ecd: LATIN SMALL LETTER O WITH DOT BELOW*/
+ 0x1ece, /* U+001ece: LATIN CAPITAL LETTER O WITH HOOK ABOVE*/
+ 0x1ece, /* U+001ecf: LATIN SMALL LETTER O WITH HOOK ABOVE*/
+ 0x1ed0, /* U+001ed0: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ed0, /* U+001ed1: LATIN SMALL LETTER O WITH CIRCUMFLEX AND ACUTE*/
+ 0x1ed2, /* U+001ed2: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ed2, /* U+001ed3: LATIN SMALL LETTER O WITH CIRCUMFLEX AND GRAVE*/
+ 0x1ed4, /* U+001ed4: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ed4, /* U+001ed5: LATIN SMALL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE*/
+ 0x1ed6, /* U+001ed6: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE*/
+ 0x1ed6, /* U+001ed7: LATIN SMALL LETTER O WITH CIRCUMFLEX AND TILDE*/
+ 0x1ed8, /* U+001ed8: LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1ed8, /* U+001ed9: LATIN SMALL LETTER O WITH CIRCUMFLEX AND DOT BELOW*/
+ 0x1eda, /* U+001eda: LATIN CAPITAL LETTER O WITH HORN AND ACUTE*/
+ 0x1eda, /* U+001edb: LATIN SMALL LETTER O WITH HORN AND ACUTE*/
+ 0x1edc, /* U+001edc: LATIN CAPITAL LETTER O WITH HORN AND GRAVE*/
+ 0x1edc, /* U+001edd: LATIN SMALL LETTER O WITH HORN AND GRAVE*/
+ 0x1ede, /* U+001ede: LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE*/
+ 0x1ede, /* U+001edf: LATIN SMALL LETTER O WITH HORN AND HOOK ABOVE*/
+ 0x1ee0, /* U+001ee0: LATIN CAPITAL LETTER O WITH HORN AND TILDE*/
+ 0x1ee0, /* U+001ee1: LATIN SMALL LETTER O WITH HORN AND TILDE*/
+ 0x1ee2, /* U+001ee2: LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW*/
+ 0x1ee2, /* U+001ee3: LATIN SMALL LETTER O WITH HORN AND DOT BELOW*/
+ 0x1ee4, /* U+001ee4: LATIN CAPITAL LETTER U WITH DOT BELOW*/
+ 0x1ee4, /* U+001ee5: LATIN SMALL LETTER U WITH DOT BELOW*/
+ 0x1ee6, /* U+001ee6: LATIN CAPITAL LETTER U WITH HOOK ABOVE*/
+ 0x1ee6, /* U+001ee7: LATIN SMALL LETTER U WITH HOOK ABOVE*/
+ 0x1ee8, /* U+001ee8: LATIN CAPITAL LETTER U WITH HORN AND ACUTE*/
+ 0x1ee8, /* U+001ee9: LATIN SMALL LETTER U WITH HORN AND ACUTE*/
+ 0x1eea, /* U+001eea: LATIN CAPITAL LETTER U WITH HORN AND GRAVE*/
+ 0x1eea, /* U+001eeb: LATIN SMALL LETTER U WITH HORN AND GRAVE*/
+ 0x1eec, /* U+001eec: LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE*/
+ 0x1eec, /* U+001eed: LATIN SMALL LETTER U WITH HORN AND HOOK ABOVE*/
+ 0x1eee, /* U+001eee: LATIN CAPITAL LETTER U WITH HORN AND TILDE*/
+ 0x1eee, /* U+001eef: LATIN SMALL LETTER U WITH HORN AND TILDE*/
+ 0x1ef0, /* U+001ef0: LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW*/
+ 0x1ef0, /* U+001ef1: LATIN SMALL LETTER U WITH HORN AND DOT BELOW*/
+ 0x1ef2, /* U+001ef2: LATIN CAPITAL LETTER Y WITH GRAVE*/
+ 0x1ef2, /* U+001ef3: LATIN SMALL LETTER Y WITH GRAVE*/
+ 0x1ef4, /* U+001ef4: LATIN CAPITAL LETTER Y WITH DOT BELOW*/
+ 0x1ef4, /* U+001ef5: LATIN SMALL LETTER Y WITH DOT BELOW*/
+ 0x1ef6, /* U+001ef6: LATIN CAPITAL LETTER Y WITH HOOK ABOVE*/
+ 0x1ef6, /* U+001ef7: LATIN SMALL LETTER Y WITH HOOK ABOVE*/
+ 0x1ef8, /* U+001ef8: LATIN CAPITAL LETTER Y WITH TILDE*/
+ 0x1ef8, /* U+001ef9: LATIN SMALL LETTER Y WITH TILDE*/
+ 0x1efa, /* U+001efa: LATIN CAPITAL LETTER MIDDLE-WELSH LL*/
+ 0x1efa, /* U+001efb: LATIN SMALL LETTER MIDDLE-WELSH LL*/
+ 0x1efc, /* U+001efc: LATIN CAPITAL LETTER MIDDLE-WELSH V*/
+ 0x1efc, /* U+001efd: LATIN SMALL LETTER MIDDLE-WELSH V*/
+ 0x1efe, /* U+001efe: LATIN CAPITAL LETTER Y WITH LOOP*/
+ 0x1efe, /* U+001eff: LATIN SMALL LETTER Y WITH LOOP*/
+ 0x1f08, /* U+001f00: GREEK SMALL LETTER ALPHA WITH PSILI*/
+ 0x1f09, /* U+001f01: GREEK SMALL LETTER ALPHA WITH DASIA*/
+ 0x1f0a, /* U+001f02: GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA*/
+ 0x1f0b, /* U+001f03: GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA*/
+ 0x1f0c, /* U+001f04: GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA*/
+ 0x1f0d, /* U+001f05: GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA*/
+ 0x1f0e, /* U+001f06: GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI*/
+ 0x1f0f, /* U+001f07: GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI*/
+ 0x1f08, /* U+001f08: GREEK CAPITAL LETTER ALPHA WITH PSILI*/
+ 0x1f09, /* U+001f09: GREEK CAPITAL LETTER ALPHA WITH DASIA*/
+ 0x1f0a, /* U+001f0a: GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA*/
+ 0x1f0b, /* U+001f0b: GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA*/
+ 0x1f0c, /* U+001f0c: GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA*/
+ 0x1f0d, /* U+001f0d: GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA*/
+ 0x1f0e, /* U+001f0e: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI*/
+ 0x1f0f, /* U+001f0f: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI*/
+ 0x1f18, /* U+001f10: GREEK SMALL LETTER EPSILON WITH PSILI*/
+ 0x1f19, /* U+001f11: GREEK SMALL LETTER EPSILON WITH DASIA*/
+ 0x1f1a, /* U+001f12: GREEK SMALL LETTER EPSILON WITH PSILI AND VARIA*/
+ 0x1f1b, /* U+001f13: GREEK SMALL LETTER EPSILON WITH DASIA AND VARIA*/
+ 0x1f1c, /* U+001f14: GREEK SMALL LETTER EPSILON WITH PSILI AND OXIA*/
+ 0x1f1d, /* U+001f15: GREEK SMALL LETTER EPSILON WITH DASIA AND OXIA*/
+ 0x1f16, /* U+001f16: */
+ 0x1f17, /* U+001f17: */
+ 0x1f18, /* U+001f18: GREEK CAPITAL LETTER EPSILON WITH PSILI*/
+ 0x1f19, /* U+001f19: GREEK CAPITAL LETTER EPSILON WITH DASIA*/
+ 0x1f1a, /* U+001f1a: GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA*/
+ 0x1f1b, /* U+001f1b: GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA*/
+ 0x1f1c, /* U+001f1c: GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA*/
+ 0x1f1d, /* U+001f1d: GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA*/
+ 0x1f1e, /* U+001f1e: */
+ 0x1f1f, /* U+001f1f: */
+ 0x1f28, /* U+001f20: GREEK SMALL LETTER ETA WITH PSILI*/
+ 0x1f29, /* U+001f21: GREEK SMALL LETTER ETA WITH DASIA*/
+ 0x1f2a, /* U+001f22: GREEK SMALL LETTER ETA WITH PSILI AND VARIA*/
+ 0x1f2b, /* U+001f23: GREEK SMALL LETTER ETA WITH DASIA AND VARIA*/
+ 0x1f2c, /* U+001f24: GREEK SMALL LETTER ETA WITH PSILI AND OXIA*/
+ 0x1f2d, /* U+001f25: GREEK SMALL LETTER ETA WITH DASIA AND OXIA*/
+ 0x1f2e, /* U+001f26: GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI*/
+ 0x1f2f, /* U+001f27: GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI*/
+ 0x1f28, /* U+001f28: GREEK CAPITAL LETTER ETA WITH PSILI*/
+ 0x1f29, /* U+001f29: GREEK CAPITAL LETTER ETA WITH DASIA*/
+ 0x1f2a, /* U+001f2a: GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA*/
+ 0x1f2b, /* U+001f2b: GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA*/
+ 0x1f2c, /* U+001f2c: GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA*/
+ 0x1f2d, /* U+001f2d: GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA*/
+ 0x1f2e, /* U+001f2e: GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI*/
+ 0x1f2f, /* U+001f2f: GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI*/
+ 0x1f38, /* U+001f30: GREEK SMALL LETTER IOTA WITH PSILI*/
+ 0x1f39, /* U+001f31: GREEK SMALL LETTER IOTA WITH DASIA*/
+ 0x1f3a, /* U+001f32: GREEK SMALL LETTER IOTA WITH PSILI AND VARIA*/
+ 0x1f3b, /* U+001f33: GREEK SMALL LETTER IOTA WITH DASIA AND VARIA*/
+ 0x1f3c, /* U+001f34: GREEK SMALL LETTER IOTA WITH PSILI AND OXIA*/
+ 0x1f3d, /* U+001f35: GREEK SMALL LETTER IOTA WITH DASIA AND OXIA*/
+ 0x1f3e, /* U+001f36: GREEK SMALL LETTER IOTA WITH PSILI AND PERISPOMENI*/
+ 0x1f3f, /* U+001f37: GREEK SMALL LETTER IOTA WITH DASIA AND PERISPOMENI*/
+ 0x1f38, /* U+001f38: GREEK CAPITAL LETTER IOTA WITH PSILI*/
+ 0x1f39, /* U+001f39: GREEK CAPITAL LETTER IOTA WITH DASIA*/
+ 0x1f3a, /* U+001f3a: GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA*/
+ 0x1f3b, /* U+001f3b: GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA*/
+ 0x1f3c, /* U+001f3c: GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA*/
+ 0x1f3d, /* U+001f3d: GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA*/
+ 0x1f3e, /* U+001f3e: GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI*/
+ 0x1f3f, /* U+001f3f: GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI*/
+ 0x1f48, /* U+001f40: GREEK SMALL LETTER OMICRON WITH PSILI*/
+ 0x1f49, /* U+001f41: GREEK SMALL LETTER OMICRON WITH DASIA*/
+ 0x1f4a, /* U+001f42: GREEK SMALL LETTER OMICRON WITH PSILI AND VARIA*/
+ 0x1f4b, /* U+001f43: GREEK SMALL LETTER OMICRON WITH DASIA AND VARIA*/
+ 0x1f4c, /* U+001f44: GREEK SMALL LETTER OMICRON WITH PSILI AND OXIA*/
+ 0x1f4d, /* U+001f45: GREEK SMALL LETTER OMICRON WITH DASIA AND OXIA*/
+ 0x1f46, /* U+001f46: */
+ 0x1f47, /* U+001f47: */
+ 0x1f48, /* U+001f48: GREEK CAPITAL LETTER OMICRON WITH PSILI*/
+ 0x1f49, /* U+001f49: GREEK CAPITAL LETTER OMICRON WITH DASIA*/
+ 0x1f4a, /* U+001f4a: GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA*/
+ 0x1f4b, /* U+001f4b: GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA*/
+ 0x1f4c, /* U+001f4c: GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA*/
+ 0x1f4d, /* U+001f4d: GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA*/
+ 0x1f4e, /* U+001f4e: */
+ 0x1f4f, /* U+001f4f: */
+ 0x1f50, /* U+001f50: GREEK SMALL LETTER UPSILON WITH PSILI*/
+ 0x1f59, /* U+001f51: GREEK SMALL LETTER UPSILON WITH DASIA*/
+ 0x1f52, /* U+001f52: GREEK SMALL LETTER UPSILON WITH PSILI AND VARIA*/
+ 0x1f5b, /* U+001f53: GREEK SMALL LETTER UPSILON WITH DASIA AND VARIA*/
+ 0x1f54, /* U+001f54: GREEK SMALL LETTER UPSILON WITH PSILI AND OXIA*/
+ 0x1f5d, /* U+001f55: GREEK SMALL LETTER UPSILON WITH DASIA AND OXIA*/
+ 0x1f56, /* U+001f56: GREEK SMALL LETTER UPSILON WITH PSILI AND PERISPOMENI*/
+ 0x1f5f, /* U+001f57: GREEK SMALL LETTER UPSILON WITH DASIA AND PERISPOMENI*/
+ 0x1f58, /* U+001f58: */
+ 0x1f59, /* U+001f59: GREEK CAPITAL LETTER UPSILON WITH DASIA*/
+ 0x1f5a, /* U+001f5a: */
+ 0x1f5b, /* U+001f5b: GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA*/
+ 0x1f5c, /* U+001f5c: */
+ 0x1f5d, /* U+001f5d: GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA*/
+ 0x1f5e, /* U+001f5e: */
+ 0x1f5f, /* U+001f5f: GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI*/
+ 0x1f68, /* U+001f60: GREEK SMALL LETTER OMEGA WITH PSILI*/
+ 0x1f69, /* U+001f61: GREEK SMALL LETTER OMEGA WITH DASIA*/
+ 0x1f6a, /* U+001f62: GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA*/
+ 0x1f6b, /* U+001f63: GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA*/
+ 0x1f6c, /* U+001f64: GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA*/
+ 0x1f6d, /* U+001f65: GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA*/
+ 0x1f6e, /* U+001f66: GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI*/
+ 0x1f6f, /* U+001f67: GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI*/
+ 0x1f68, /* U+001f68: GREEK CAPITAL LETTER OMEGA WITH PSILI*/
+ 0x1f69, /* U+001f69: GREEK CAPITAL LETTER OMEGA WITH DASIA*/
+ 0x1f6a, /* U+001f6a: GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA*/
+ 0x1f6b, /* U+001f6b: GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA*/
+ 0x1f6c, /* U+001f6c: GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA*/
+ 0x1f6d, /* U+001f6d: GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA*/
+ 0x1f6e, /* U+001f6e: GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI*/
+ 0x1f6f, /* U+001f6f: GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI*/
+ 0x1fba, /* U+001f70: GREEK SMALL LETTER ALPHA WITH VARIA*/
+ 0x1fbb, /* U+001f71: GREEK SMALL LETTER ALPHA WITH OXIA*/
+ 0x1fc8, /* U+001f72: GREEK SMALL LETTER EPSILON WITH VARIA*/
+ 0x1fc9, /* U+001f73: GREEK SMALL LETTER EPSILON WITH OXIA*/
+ 0x1fca, /* U+001f74: GREEK SMALL LETTER ETA WITH VARIA*/
+ 0x1fcb, /* U+001f75: GREEK SMALL LETTER ETA WITH OXIA*/
+ 0x1fda, /* U+001f76: GREEK SMALL LETTER IOTA WITH VARIA*/
+ 0x1fdb, /* U+001f77: GREEK SMALL LETTER IOTA WITH OXIA*/
+ 0x1ff8, /* U+001f78: GREEK SMALL LETTER OMICRON WITH VARIA*/
+ 0x1ff9, /* U+001f79: GREEK SMALL LETTER OMICRON WITH OXIA*/
+ 0x1fea, /* U+001f7a: GREEK SMALL LETTER UPSILON WITH VARIA*/
+ 0x1feb, /* U+001f7b: GREEK SMALL LETTER UPSILON WITH OXIA*/
+ 0x1ffa, /* U+001f7c: GREEK SMALL LETTER OMEGA WITH VARIA*/
+ 0x1ffb, /* U+001f7d: GREEK SMALL LETTER OMEGA WITH OXIA*/
+ 0x1f7e, /* U+001f7e: */
+ 0x1f7f, /* U+001f7f: */
+ 0x1f88, /* U+001f80: GREEK SMALL LETTER ALPHA WITH PSILI AND YPOGEGRAMMENI*/
+ 0x1f89, /* U+001f81: GREEK SMALL LETTER ALPHA WITH DASIA AND YPOGEGRAMMENI*/
+ 0x1f8a, /* U+001f82: GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA AND YPOGEGRAMMENI*/
+ 0x1f8b, /* U+001f83: GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA AND YPOGEGRAMMENI*/
+ 0x1f8c, /* U+001f84: GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA AND YPOGEGRAMMENI*/
+ 0x1f8d, /* U+001f85: GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA AND YPOGEGRAMMENI*/
+ 0x1f8e, /* U+001f86: GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f8f, /* U+001f87: GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f88, /* U+001f88: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI*/
+ 0x1f89, /* U+001f89: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI*/
+ 0x1f8a, /* U+001f8a: GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI*/
+ 0x1f8b, /* U+001f8b: GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI*/
+ 0x1f8c, /* U+001f8c: GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI*/
+ 0x1f8d, /* U+001f8d: GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI*/
+ 0x1f8e, /* U+001f8e: GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1f8f, /* U+001f8f: GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1f98, /* U+001f90: GREEK SMALL LETTER ETA WITH PSILI AND YPOGEGRAMMENI*/
+ 0x1f99, /* U+001f91: GREEK SMALL LETTER ETA WITH DASIA AND YPOGEGRAMMENI*/
+ 0x1f9a, /* U+001f92: GREEK SMALL LETTER ETA WITH PSILI AND VARIA AND YPOGEGRAMMENI*/
+ 0x1f9b, /* U+001f93: GREEK SMALL LETTER ETA WITH DASIA AND VARIA AND YPOGEGRAMMENI*/
+ 0x1f9c, /* U+001f94: GREEK SMALL LETTER ETA WITH PSILI AND OXIA AND YPOGEGRAMMENI*/
+ 0x1f9d, /* U+001f95: GREEK SMALL LETTER ETA WITH DASIA AND OXIA AND YPOGEGRAMMENI*/
+ 0x1f9e, /* U+001f96: GREEK SMALL LETTER ETA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f9f, /* U+001f97: GREEK SMALL LETTER ETA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1f98, /* U+001f98: GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI*/
+ 0x1f99, /* U+001f99: GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI*/
+ 0x1f9a, /* U+001f9a: GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI*/
+ 0x1f9b, /* U+001f9b: GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI*/
+ 0x1f9c, /* U+001f9c: GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI*/
+ 0x1f9d, /* U+001f9d: GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI*/
+ 0x1f9e, /* U+001f9e: GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1f9f, /* U+001f9f: GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1fa8, /* U+001fa0: GREEK SMALL LETTER OMEGA WITH PSILI AND YPOGEGRAMMENI*/
+ 0x1fa9, /* U+001fa1: GREEK SMALL LETTER OMEGA WITH DASIA AND YPOGEGRAMMENI*/
+ 0x1faa, /* U+001fa2: GREEK SMALL LETTER OMEGA WITH PSILI AND VARIA AND YPOGEGRAMMENI*/
+ 0x1fab, /* U+001fa3: GREEK SMALL LETTER OMEGA WITH DASIA AND VARIA AND YPOGEGRAMMENI*/
+ 0x1fac, /* U+001fa4: GREEK SMALL LETTER OMEGA WITH PSILI AND OXIA AND YPOGEGRAMMENI*/
+ 0x1fad, /* U+001fa5: GREEK SMALL LETTER OMEGA WITH DASIA AND OXIA AND YPOGEGRAMMENI*/
+ 0x1fae, /* U+001fa6: GREEK SMALL LETTER OMEGA WITH PSILI AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1faf, /* U+001fa7: GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1fa8, /* U+001fa8: GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI*/
+ 0x1fa9, /* U+001fa9: GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI*/
+ 0x1faa, /* U+001faa: GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI*/
+ 0x1fab, /* U+001fab: GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI*/
+ 0x1fac, /* U+001fac: GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI*/
+ 0x1fad, /* U+001fad: GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI*/
+ 0x1fae, /* U+001fae: GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1faf, /* U+001faf: GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI*/
+ 0x1fb8, /* U+001fb0: GREEK SMALL LETTER ALPHA WITH VRACHY*/
+ 0x1fb9, /* U+001fb1: GREEK SMALL LETTER ALPHA WITH MACRON*/
+ 0x1fb2, /* U+001fb2: GREEK SMALL LETTER ALPHA WITH VARIA AND YPOGEGRAMMENI*/
+ 0x1fbc, /* U+001fb3: GREEK SMALL LETTER ALPHA WITH YPOGEGRAMMENI*/
+ 0x1fb4, /* U+001fb4: GREEK SMALL LETTER ALPHA WITH OXIA AND YPOGEGRAMMENI*/
+ 0x1fb5, /* U+001fb5: */
+ 0x1fb6, /* U+001fb6: GREEK SMALL LETTER ALPHA WITH PERISPOMENI*/
+ 0x1fb7, /* U+001fb7: GREEK SMALL LETTER ALPHA WITH PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1fb8, /* U+001fb8: GREEK CAPITAL LETTER ALPHA WITH VRACHY*/
+ 0x1fb9, /* U+001fb9: GREEK CAPITAL LETTER ALPHA WITH MACRON*/
+ 0x1fba, /* U+001fba: GREEK CAPITAL LETTER ALPHA WITH VARIA*/
+ 0x1fbb, /* U+001fbb: GREEK CAPITAL LETTER ALPHA WITH OXIA*/
+ 0x1fbc, /* U+001fbc: GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI*/
+ 0x1fbd, /* U+001fbd: GREEK KORONIS*/
+ 0x399, /* U+001fbe: GREEK PROSGEGRAMMENI*/
+ 0x1fbf, /* U+001fbf: GREEK PSILI*/
+ 0x1fc0, /* U+001fc0: GREEK PERISPOMENI*/
+ 0x1fc1, /* U+001fc1: GREEK DIALYTIKA AND PERISPOMENI*/
+ 0x1fc2, /* U+001fc2: GREEK SMALL LETTER ETA WITH VARIA AND YPOGEGRAMMENI*/
+ 0x1fcc, /* U+001fc3: GREEK SMALL LETTER ETA WITH YPOGEGRAMMENI*/
+ 0x1fc4, /* U+001fc4: GREEK SMALL LETTER ETA WITH OXIA AND YPOGEGRAMMENI*/
+ 0x1fc5, /* U+001fc5: */
+ 0x1fc6, /* U+001fc6: GREEK SMALL LETTER ETA WITH PERISPOMENI*/
+ 0x1fc7, /* U+001fc7: GREEK SMALL LETTER ETA WITH PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1fc8, /* U+001fc8: GREEK CAPITAL LETTER EPSILON WITH VARIA*/
+ 0x1fc9, /* U+001fc9: GREEK CAPITAL LETTER EPSILON WITH OXIA*/
+ 0x1fca, /* U+001fca: GREEK CAPITAL LETTER ETA WITH VARIA*/
+ 0x1fcb, /* U+001fcb: GREEK CAPITAL LETTER ETA WITH OXIA*/
+ 0x1fcc, /* U+001fcc: GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI*/
+ 0x1fcd, /* U+001fcd: GREEK PSILI AND VARIA*/
+ 0x1fce, /* U+001fce: GREEK PSILI AND OXIA*/
+ 0x1fcf, /* U+001fcf: GREEK PSILI AND PERISPOMENI*/
+ 0x1fd8, /* U+001fd0: GREEK SMALL LETTER IOTA WITH VRACHY*/
+ 0x1fd9, /* U+001fd1: GREEK SMALL LETTER IOTA WITH MACRON*/
+ 0x1fd2, /* U+001fd2: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND VARIA*/
+ 0x1fd3, /* U+001fd3: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND OXIA*/
+ 0x1fd4, /* U+001fd4: */
+ 0x1fd5, /* U+001fd5: */
+ 0x1fd6, /* U+001fd6: GREEK SMALL LETTER IOTA WITH PERISPOMENI*/
+ 0x1fd7, /* U+001fd7: GREEK SMALL LETTER IOTA WITH DIALYTIKA AND PERISPOMENI*/
+ 0x1fd8, /* U+001fd8: GREEK CAPITAL LETTER IOTA WITH VRACHY*/
+ 0x1fd9, /* U+001fd9: GREEK CAPITAL LETTER IOTA WITH MACRON*/
+ 0x1fda, /* U+001fda: GREEK CAPITAL LETTER IOTA WITH VARIA*/
+ 0x1fdb, /* U+001fdb: GREEK CAPITAL LETTER IOTA WITH OXIA*/
+ 0x1fdc, /* U+001fdc: */
+ 0x1fdd, /* U+001fdd: GREEK DASIA AND VARIA*/
+ 0x1fde, /* U+001fde: GREEK DASIA AND OXIA*/
+ 0x1fdf, /* U+001fdf: GREEK DASIA AND PERISPOMENI*/
+ 0x1fe8, /* U+001fe0: GREEK SMALL LETTER UPSILON WITH VRACHY*/
+ 0x1fe9, /* U+001fe1: GREEK SMALL LETTER UPSILON WITH MACRON*/
+ 0x1fe2, /* U+001fe2: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND VARIA*/
+ 0x1fe3, /* U+001fe3: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND OXIA*/
+ 0x1fe4, /* U+001fe4: GREEK SMALL LETTER RHO WITH PSILI*/
+ 0x1fec, /* U+001fe5: GREEK SMALL LETTER RHO WITH DASIA*/
+ 0x1fe6, /* U+001fe6: GREEK SMALL LETTER UPSILON WITH PERISPOMENI*/
+ 0x1fe7, /* U+001fe7: GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND PERISPOMENI*/
+ 0x1fe8, /* U+001fe8: GREEK CAPITAL LETTER UPSILON WITH VRACHY*/
+ 0x1fe9, /* U+001fe9: GREEK CAPITAL LETTER UPSILON WITH MACRON*/
+ 0x1fea, /* U+001fea: GREEK CAPITAL LETTER UPSILON WITH VARIA*/
+ 0x1feb, /* U+001feb: GREEK CAPITAL LETTER UPSILON WITH OXIA*/
+ 0x1fec, /* U+001fec: GREEK CAPITAL LETTER RHO WITH DASIA*/
+ 0x1fed, /* U+001fed: GREEK DIALYTIKA AND VARIA*/
+ 0x1fee, /* U+001fee: GREEK DIALYTIKA AND OXIA*/
+ 0x1fef, /* U+001fef: GREEK VARIA*/
+ 0x1ff0, /* U+001ff0: */
+ 0x1ff1, /* U+001ff1: */
+ 0x1ff2, /* U+001ff2: GREEK SMALL LETTER OMEGA WITH VARIA AND YPOGEGRAMMENI*/
+ 0x1ffc, /* U+001ff3: GREEK SMALL LETTER OMEGA WITH YPOGEGRAMMENI*/
+ 0x1ff4, /* U+001ff4: GREEK SMALL LETTER OMEGA WITH OXIA AND YPOGEGRAMMENI*/
+ 0x1ff5, /* U+001ff5: */
+ 0x1ff6, /* U+001ff6: GREEK SMALL LETTER OMEGA WITH PERISPOMENI*/
+ 0x1ff7, /* U+001ff7: GREEK SMALL LETTER OMEGA WITH PERISPOMENI AND YPOGEGRAMMENI*/
+ 0x1ff8, /* U+001ff8: GREEK CAPITAL LETTER OMICRON WITH VARIA*/
+ 0x1ff9, /* U+001ff9: GREEK CAPITAL LETTER OMICRON WITH OXIA*/
+ 0x1ffa, /* U+001ffa: GREEK CAPITAL LETTER OMEGA WITH VARIA*/
+ 0x1ffb, /* U+001ffb: GREEK CAPITAL LETTER OMEGA WITH OXIA*/
+ 0x1ffc, /* U+001ffc: GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI*/
+ 0x1ffd, /* U+001ffd: GREEK OXIA*/
+ 0x1ffe, /* U+001ffe: GREEK DASIA*/
+ 0x1fff, /* U+001fff: */
+ 0x2000, /* U+002000: EN QUAD*/
+ 0x2001, /* U+002001: EM QUAD*/
+ 0x2002, /* U+002002: EN SPACE*/
+ 0x2003, /* U+002003: EM SPACE*/
+ 0x2004, /* U+002004: THREE-PER-EM SPACE*/
+ 0x2005, /* U+002005: FOUR-PER-EM SPACE*/
+ 0x2006, /* U+002006: SIX-PER-EM SPACE*/
+ 0x2007, /* U+002007: FIGURE SPACE*/
+ 0x2008, /* U+002008: PUNCTUATION SPACE*/
+ 0x2009, /* U+002009: THIN SPACE*/
+ 0x200a, /* U+00200a: HAIR SPACE*/
+ 0x200b, /* U+00200b: ZERO WIDTH SPACE*/
+ 0x200c, /* U+00200c: ZERO WIDTH NON-JOINER*/
+ 0x200d, /* U+00200d: ZERO WIDTH JOINER*/
+ 0x200e, /* U+00200e: LEFT-TO-RIGHT MARK*/
+ 0x200f, /* U+00200f: RIGHT-TO-LEFT MARK*/
+ 0x2010, /* U+002010: HYPHEN*/
+ 0x2011, /* U+002011: NON-BREAKING HYPHEN*/
+ 0x2012, /* U+002012: FIGURE DASH*/
+ 0x2013, /* U+002013: EN DASH*/
+ 0x2014, /* U+002014: EM DASH*/
+ 0x2015, /* U+002015: HORIZONTAL BAR*/
+ 0x2016, /* U+002016: DOUBLE VERTICAL LINE*/
+ 0x2017, /* U+002017: DOUBLE LOW LINE*/
+ 0x2018, /* U+002018: LEFT SINGLE QUOTATION MARK*/
+ 0x2019, /* U+002019: RIGHT SINGLE QUOTATION MARK*/
+ 0x201a, /* U+00201a: SINGLE LOW-9 QUOTATION MARK*/
+ 0x201b, /* U+00201b: SINGLE HIGH-REVERSED-9 QUOTATION MARK*/
+ 0x201c, /* U+00201c: LEFT DOUBLE QUOTATION MARK*/
+ 0x201d, /* U+00201d: RIGHT DOUBLE QUOTATION MARK*/
+ 0x201e, /* U+00201e: DOUBLE LOW-9 QUOTATION MARK*/
+ 0x201f, /* U+00201f: DOUBLE HIGH-REVERSED-9 QUOTATION MARK*/
+ 0x2020, /* U+002020: DAGGER*/
+ 0x2021, /* U+002021: DOUBLE DAGGER*/
+ 0x2022, /* U+002022: BULLET*/
+ 0x2023, /* U+002023: TRIANGULAR BULLET*/
+ 0x2024, /* U+002024: ONE DOT LEADER*/
+ 0x2025, /* U+002025: TWO DOT LEADER*/
+ 0x2026, /* U+002026: HORIZONTAL ELLIPSIS*/
+ 0x2027, /* U+002027: HYPHENATION POINT*/
+ 0x2028, /* U+002028: LINE SEPARATOR*/
+ 0x2029, /* U+002029: PARAGRAPH SEPARATOR*/
+ 0x202a, /* U+00202a: LEFT-TO-RIGHT EMBEDDING*/
+ 0x202b, /* U+00202b: RIGHT-TO-LEFT EMBEDDING*/
+ 0x202c, /* U+00202c: POP DIRECTIONAL FORMATTING*/
+ 0x202d, /* U+00202d: LEFT-TO-RIGHT OVERRIDE*/
+ 0x202e, /* U+00202e: RIGHT-TO-LEFT OVERRIDE*/
+ 0x202f, /* U+00202f: NARROW NO-BREAK SPACE*/
+ 0x2030, /* U+002030: PER MILLE SIGN*/
+ 0x2031, /* U+002031: PER TEN THOUSAND SIGN*/
+ 0x2032, /* U+002032: PRIME*/
+ 0x2033, /* U+002033: DOUBLE PRIME*/
+ 0x2034, /* U+002034: TRIPLE PRIME*/
+ 0x2035, /* U+002035: REVERSED PRIME*/
+ 0x2036, /* U+002036: REVERSED DOUBLE PRIME*/
+ 0x2037, /* U+002037: REVERSED TRIPLE PRIME*/
+ 0x2038, /* U+002038: CARET*/
+ 0x2039, /* U+002039: SINGLE LEFT-POINTING ANGLE QUOTATION MARK*/
+ 0x203a, /* U+00203a: SINGLE RIGHT-POINTING ANGLE QUOTATION MARK*/
+ 0x203b, /* U+00203b: REFERENCE MARK*/
+ 0x203c, /* U+00203c: DOUBLE EXCLAMATION MARK*/
+ 0x203d, /* U+00203d: INTERROBANG*/
+ 0x203e, /* U+00203e: OVERLINE*/
+ 0x203f, /* U+00203f: UNDERTIE*/
+ 0x2040, /* U+002040: CHARACTER TIE*/
+ 0x2041, /* U+002041: CARET INSERTION POINT*/
+ 0x2042, /* U+002042: ASTERISM*/
+ 0x2043, /* U+002043: HYPHEN BULLET*/
+ 0x2044, /* U+002044: FRACTION SLASH*/
+ 0x2045, /* U+002045: LEFT SQUARE BRACKET WITH QUILL*/
+ 0x2046, /* U+002046: RIGHT SQUARE BRACKET WITH QUILL*/
+ 0x2047, /* U+002047: DOUBLE QUESTION MARK*/
+ 0x2048, /* U+002048: QUESTION EXCLAMATION MARK*/
+ 0x2049, /* U+002049: EXCLAMATION QUESTION MARK*/
+ 0x204a, /* U+00204a: TIRONIAN SIGN ET*/
+ 0x204b, /* U+00204b: REVERSED PILCROW SIGN*/
+ 0x204c, /* U+00204c: BLACK LEFTWARDS BULLET*/
+ 0x204d, /* U+00204d: BLACK RIGHTWARDS BULLET*/
+ 0x204e, /* U+00204e: LOW ASTERISK*/
+ 0x204f, /* U+00204f: REVERSED SEMICOLON*/
+ 0x2050, /* U+002050: CLOSE UP*/
+ 0x2051, /* U+002051: TWO ASTERISKS ALIGNED VERTICALLY*/
+ 0x2052, /* U+002052: COMMERCIAL MINUS SIGN*/
+ 0x2053, /* U+002053: SWUNG DASH*/
+ 0x2054, /* U+002054: INVERTED UNDERTIE*/
+ 0x2055, /* U+002055: FLOWER PUNCTUATION MARK*/
+ 0x2056, /* U+002056: THREE DOT PUNCTUATION*/
+ 0x2057, /* U+002057: QUADRUPLE PRIME*/
+ 0x2058, /* U+002058: FOUR DOT PUNCTUATION*/
+ 0x2059, /* U+002059: FIVE DOT PUNCTUATION*/
+ 0x205a, /* U+00205a: TWO DOT PUNCTUATION*/
+ 0x205b, /* U+00205b: FOUR DOT MARK*/
+ 0x205c, /* U+00205c: DOTTED CROSS*/
+ 0x205d, /* U+00205d: TRICOLON*/
+ 0x205e, /* U+00205e: VERTICAL FOUR DOTS*/
+ 0x205f, /* U+00205f: MEDIUM MATHEMATICAL SPACE*/
+ 0x2060, /* U+002060: WORD JOINER*/
+ 0x2061, /* U+002061: FUNCTION APPLICATION*/
+ 0x2062, /* U+002062: INVISIBLE TIMES*/
+ 0x2063, /* U+002063: INVISIBLE SEPARATOR*/
+ 0x2064, /* U+002064: INVISIBLE PLUS*/
+ 0x2065, /* U+002065: */
+ 0x2066, /* U+002066: LEFT-TO-RIGHT ISOLATE*/
+ 0x2067, /* U+002067: RIGHT-TO-LEFT ISOLATE*/
+ 0x2068, /* U+002068: FIRST STRONG ISOLATE*/
+ 0x2069, /* U+002069: POP DIRECTIONAL ISOLATE*/
+ 0x206a, /* U+00206a: INHIBIT SYMMETRIC SWAPPING*/
+ 0x206b, /* U+00206b: ACTIVATE SYMMETRIC SWAPPING*/
+ 0x206c, /* U+00206c: INHIBIT ARABIC FORM SHAPING*/
+ 0x206d, /* U+00206d: ACTIVATE ARABIC FORM SHAPING*/
+ 0x206e, /* U+00206e: NATIONAL DIGIT SHAPES*/
+ 0x206f, /* U+00206f: NOMINAL DIGIT SHAPES*/
+ 0x2070, /* U+002070: SUPERSCRIPT ZERO*/
+ 0x2071, /* U+002071: SUPERSCRIPT LATIN SMALL LETTER I*/
+ 0x2072, /* U+002072: */
+ 0x2073, /* U+002073: */
+ 0x2074, /* U+002074: SUPERSCRIPT FOUR*/
+ 0x2075, /* U+002075: SUPERSCRIPT FIVE*/
+ 0x2076, /* U+002076: SUPERSCRIPT SIX*/
+ 0x2077, /* U+002077: SUPERSCRIPT SEVEN*/
+ 0x2078, /* U+002078: SUPERSCRIPT EIGHT*/
+ 0x2079, /* U+002079: SUPERSCRIPT NINE*/
+ 0x207a, /* U+00207a: SUPERSCRIPT PLUS SIGN*/
+ 0x207b, /* U+00207b: SUPERSCRIPT MINUS*/
+ 0x207c, /* U+00207c: SUPERSCRIPT EQUALS SIGN*/
+ 0x207d, /* U+00207d: SUPERSCRIPT LEFT PARENTHESIS*/
+ 0x207e, /* U+00207e: SUPERSCRIPT RIGHT PARENTHESIS*/
+ 0x207f, /* U+00207f: SUPERSCRIPT LATIN SMALL LETTER N*/
+ 0x2080, /* U+002080: SUBSCRIPT ZERO*/
+ 0x2081, /* U+002081: SUBSCRIPT ONE*/
+ 0x2082, /* U+002082: SUBSCRIPT TWO*/
+ 0x2083, /* U+002083: SUBSCRIPT THREE*/
+ 0x2084, /* U+002084: SUBSCRIPT FOUR*/
+ 0x2085, /* U+002085: SUBSCRIPT FIVE*/
+ 0x2086, /* U+002086: SUBSCRIPT SIX*/
+ 0x2087, /* U+002087: SUBSCRIPT SEVEN*/
+ 0x2088, /* U+002088: SUBSCRIPT EIGHT*/
+ 0x2089, /* U+002089: SUBSCRIPT NINE*/
+ 0x208a, /* U+00208a: SUBSCRIPT PLUS SIGN*/
+ 0x208b, /* U+00208b: SUBSCRIPT MINUS*/
+ 0x208c, /* U+00208c: SUBSCRIPT EQUALS SIGN*/
+ 0x208d, /* U+00208d: SUBSCRIPT LEFT PARENTHESIS*/
+ 0x208e, /* U+00208e: SUBSCRIPT RIGHT PARENTHESIS*/
+ 0x208f, /* U+00208f: */
+ 0x2090, /* U+002090: LATIN SUBSCRIPT SMALL LETTER A*/
+ 0x2091, /* U+002091: LATIN SUBSCRIPT SMALL LETTER E*/
+ 0x2092, /* U+002092: LATIN SUBSCRIPT SMALL LETTER O*/
+ 0x2093, /* U+002093: LATIN SUBSCRIPT SMALL LETTER X*/
+ 0x2094, /* U+002094: LATIN SUBSCRIPT SMALL LETTER SCHWA*/
+ 0x2095, /* U+002095: LATIN SUBSCRIPT SMALL LETTER H*/
+ 0x2096, /* U+002096: LATIN SUBSCRIPT SMALL LETTER K*/
+ 0x2097, /* U+002097: LATIN SUBSCRIPT SMALL LETTER L*/
+ 0x2098, /* U+002098: LATIN SUBSCRIPT SMALL LETTER M*/
+ 0x2099, /* U+002099: LATIN SUBSCRIPT SMALL LETTER N*/
+ 0x209a, /* U+00209a: LATIN SUBSCRIPT SMALL LETTER P*/
+ 0x209b, /* U+00209b: LATIN SUBSCRIPT SMALL LETTER S*/
+ 0x209c, /* U+00209c: LATIN SUBSCRIPT SMALL LETTER T*/
+ 0x209d, /* U+00209d: */
+ 0x209e, /* U+00209e: */
+ 0x209f, /* U+00209f: */
+ 0x20a0, /* U+0020a0: EURO-CURRENCY SIGN*/
+ 0x20a1, /* U+0020a1: COLON SIGN*/
+ 0x20a2, /* U+0020a2: CRUZEIRO SIGN*/
+ 0x20a3, /* U+0020a3: FRENCH FRANC SIGN*/
+ 0x20a4, /* U+0020a4: LIRA SIGN*/
+ 0x20a5, /* U+0020a5: MILL SIGN*/
+ 0x20a6, /* U+0020a6: NAIRA SIGN*/
+ 0x20a7, /* U+0020a7: PESETA SIGN*/
+ 0x20a8, /* U+0020a8: RUPEE SIGN*/
+ 0x20a9, /* U+0020a9: WON SIGN*/
+ 0x20aa, /* U+0020aa: NEW SHEQEL SIGN*/
+ 0x20ab, /* U+0020ab: DONG SIGN*/
+ 0x20ac, /* U+0020ac: EURO SIGN*/
+ 0x20ad, /* U+0020ad: KIP SIGN*/
+ 0x20ae, /* U+0020ae: TUGRIK SIGN*/
+ 0x20af, /* U+0020af: DRACHMA SIGN*/
+ 0x20b0, /* U+0020b0: GERMAN PENNY SIGN*/
+ 0x20b1, /* U+0020b1: PESO SIGN*/
+ 0x20b2, /* U+0020b2: GUARANI SIGN*/
+ 0x20b3, /* U+0020b3: AUSTRAL SIGN*/
+ 0x20b4, /* U+0020b4: HRYVNIA SIGN*/
+ 0x20b5, /* U+0020b5: CEDI SIGN*/
+ 0x20b6, /* U+0020b6: LIVRE TOURNOIS SIGN*/
+ 0x20b7, /* U+0020b7: SPESMILO SIGN*/
+ 0x20b8, /* U+0020b8: TENGE SIGN*/
+ 0x20b9, /* U+0020b9: INDIAN RUPEE SIGN*/
+ 0x20ba, /* U+0020ba: TURKISH LIRA SIGN*/
+ 0x20bb, /* U+0020bb: */
+ 0x20bc, /* U+0020bc: */
+ 0x20bd, /* U+0020bd: */
+ 0x20be, /* U+0020be: */
+ 0x20bf, /* U+0020bf: */
+ 0x20c0, /* U+0020c0: */
+ 0x20c1, /* U+0020c1: */
+ 0x20c2, /* U+0020c2: */
+ 0x20c3, /* U+0020c3: */
+ 0x20c4, /* U+0020c4: */
+ 0x20c5, /* U+0020c5: */
+ 0x20c6, /* U+0020c6: */
+ 0x20c7, /* U+0020c7: */
+ 0x20c8, /* U+0020c8: */
+ 0x20c9, /* U+0020c9: */
+ 0x20ca, /* U+0020ca: */
+ 0x20cb, /* U+0020cb: */
+ 0x20cc, /* U+0020cc: */
+ 0x20cd, /* U+0020cd: */
+ 0x20ce, /* U+0020ce: */
+ 0x20cf, /* U+0020cf: */
+ 0x20d0, /* U+0020d0: COMBINING LEFT HARPOON ABOVE*/
+ 0x20d1, /* U+0020d1: COMBINING RIGHT HARPOON ABOVE*/
+ 0x20d2, /* U+0020d2: COMBINING LONG VERTICAL LINE OVERLAY*/
+ 0x20d3, /* U+0020d3: COMBINING SHORT VERTICAL LINE OVERLAY*/
+ 0x20d4, /* U+0020d4: COMBINING ANTICLOCKWISE ARROW ABOVE*/
+ 0x20d5, /* U+0020d5: COMBINING CLOCKWISE ARROW ABOVE*/
+ 0x20d6, /* U+0020d6: COMBINING LEFT ARROW ABOVE*/
+ 0x20d7, /* U+0020d7: COMBINING RIGHT ARROW ABOVE*/
+ 0x20d8, /* U+0020d8: COMBINING RING OVERLAY*/
+ 0x20d9, /* U+0020d9: COMBINING CLOCKWISE RING OVERLAY*/
+ 0x20da, /* U+0020da: COMBINING ANTICLOCKWISE RING OVERLAY*/
+ 0x20db, /* U+0020db: COMBINING THREE DOTS ABOVE*/
+ 0x20dc, /* U+0020dc: COMBINING FOUR DOTS ABOVE*/
+ 0x20dd, /* U+0020dd: COMBINING ENCLOSING CIRCLE*/
+ 0x20de, /* U+0020de: COMBINING ENCLOSING SQUARE*/
+ 0x20df, /* U+0020df: COMBINING ENCLOSING DIAMOND*/
+ 0x20e0, /* U+0020e0: COMBINING ENCLOSING CIRCLE BACKSLASH*/
+ 0x20e1, /* U+0020e1: COMBINING LEFT RIGHT ARROW ABOVE*/
+ 0x20e2, /* U+0020e2: COMBINING ENCLOSING SCREEN*/
+ 0x20e3, /* U+0020e3: COMBINING ENCLOSING KEYCAP*/
+ 0x20e4, /* U+0020e4: COMBINING ENCLOSING UPWARD POINTING TRIANGLE*/
+ 0x20e5, /* U+0020e5: COMBINING REVERSE SOLIDUS OVERLAY*/
+ 0x20e6, /* U+0020e6: COMBINING DOUBLE VERTICAL STROKE OVERLAY*/
+ 0x20e7, /* U+0020e7: COMBINING ANNUITY SYMBOL*/
+ 0x20e8, /* U+0020e8: COMBINING TRIPLE UNDERDOT*/
+ 0x20e9, /* U+0020e9: COMBINING WIDE BRIDGE ABOVE*/
+ 0x20ea, /* U+0020ea: COMBINING LEFTWARDS ARROW OVERLAY*/
+ 0x20eb, /* U+0020eb: COMBINING LONG DOUBLE SOLIDUS OVERLAY*/
+ 0x20ec, /* U+0020ec: COMBINING RIGHTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0x20ed, /* U+0020ed: COMBINING LEFTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0x20ee, /* U+0020ee: COMBINING LEFT ARROW BELOW*/
+ 0x20ef, /* U+0020ef: COMBINING RIGHT ARROW BELOW*/
+ 0x20f0, /* U+0020f0: COMBINING ASTERISK ABOVE*/
+ 0x20f1, /* U+0020f1: */
+ 0x20f2, /* U+0020f2: */
+ 0x20f3, /* U+0020f3: */
+ 0x20f4, /* U+0020f4: */
+ 0x20f5, /* U+0020f5: */
+ 0x20f6, /* U+0020f6: */
+ 0x20f7, /* U+0020f7: */
+ 0x20f8, /* U+0020f8: */
+ 0x20f9, /* U+0020f9: */
+ 0x20fa, /* U+0020fa: */
+ 0x20fb, /* U+0020fb: */
+ 0x20fc, /* U+0020fc: */
+ 0x20fd, /* U+0020fd: */
+ 0x20fe, /* U+0020fe: */
+ 0x20ff, /* U+0020ff: */
+ 0x2100, /* U+002100: ACCOUNT OF*/
+ 0x2101, /* U+002101: ADDRESSED TO THE SUBJECT*/
+ 0x2102, /* U+002102: DOUBLE-STRUCK CAPITAL C*/
+ 0x2103, /* U+002103: DEGREE CELSIUS*/
+ 0x2104, /* U+002104: CENTRE LINE SYMBOL*/
+ 0x2105, /* U+002105: CARE OF*/
+ 0x2106, /* U+002106: CADA UNA*/
+ 0x2107, /* U+002107: EULER CONSTANT*/
+ 0x2108, /* U+002108: SCRUPLE*/
+ 0x2109, /* U+002109: DEGREE FAHRENHEIT*/
+ 0x210a, /* U+00210a: SCRIPT SMALL G*/
+ 0x210b, /* U+00210b: SCRIPT CAPITAL H*/
+ 0x210c, /* U+00210c: BLACK-LETTER CAPITAL H*/
+ 0x210d, /* U+00210d: DOUBLE-STRUCK CAPITAL H*/
+ 0x210e, /* U+00210e: PLANCK CONSTANT*/
+ 0x210f, /* U+00210f: PLANCK CONSTANT OVER TWO PI*/
+ 0x2110, /* U+002110: SCRIPT CAPITAL I*/
+ 0x2111, /* U+002111: BLACK-LETTER CAPITAL I*/
+ 0x2112, /* U+002112: SCRIPT CAPITAL L*/
+ 0x2113, /* U+002113: SCRIPT SMALL L*/
+ 0x2114, /* U+002114: L B BAR SYMBOL*/
+ 0x2115, /* U+002115: DOUBLE-STRUCK CAPITAL N*/
+ 0x2116, /* U+002116: NUMERO SIGN*/
+ 0x2117, /* U+002117: SOUND RECORDING COPYRIGHT*/
+ 0x2118, /* U+002118: SCRIPT CAPITAL P*/
+ 0x2119, /* U+002119: DOUBLE-STRUCK CAPITAL P*/
+ 0x211a, /* U+00211a: DOUBLE-STRUCK CAPITAL Q*/
+ 0x211b, /* U+00211b: SCRIPT CAPITAL R*/
+ 0x211c, /* U+00211c: BLACK-LETTER CAPITAL R*/
+ 0x211d, /* U+00211d: DOUBLE-STRUCK CAPITAL R*/
+ 0x211e, /* U+00211e: PRESCRIPTION TAKE*/
+ 0x211f, /* U+00211f: RESPONSE*/
+ 0x2120, /* U+002120: SERVICE MARK*/
+ 0x2121, /* U+002121: TELEPHONE SIGN*/
+ 0x2122, /* U+002122: TRADE MARK SIGN*/
+ 0x2123, /* U+002123: VERSICLE*/
+ 0x2124, /* U+002124: DOUBLE-STRUCK CAPITAL Z*/
+ 0x2125, /* U+002125: OUNCE SIGN*/
+ 0x2126, /* U+002126: OHM SIGN*/
+ 0x2127, /* U+002127: INVERTED OHM SIGN*/
+ 0x2128, /* U+002128: BLACK-LETTER CAPITAL Z*/
+ 0x2129, /* U+002129: TURNED GREEK SMALL LETTER IOTA*/
+ 0x212a, /* U+00212a: KELVIN SIGN*/
+ 0x212b, /* U+00212b: ANGSTROM SIGN*/
+ 0x212c, /* U+00212c: SCRIPT CAPITAL B*/
+ 0x212d, /* U+00212d: BLACK-LETTER CAPITAL C*/
+ 0x212e, /* U+00212e: ESTIMATED SYMBOL*/
+ 0x212f, /* U+00212f: SCRIPT SMALL E*/
+ 0x2130, /* U+002130: SCRIPT CAPITAL E*/
+ 0x2131, /* U+002131: SCRIPT CAPITAL F*/
+ 0x2132, /* U+002132: TURNED CAPITAL F*/
+ 0x2133, /* U+002133: SCRIPT CAPITAL M*/
+ 0x2134, /* U+002134: SCRIPT SMALL O*/
+ 0x2135, /* U+002135: ALEF SYMBOL*/
+ 0x2136, /* U+002136: BET SYMBOL*/
+ 0x2137, /* U+002137: GIMEL SYMBOL*/
+ 0x2138, /* U+002138: DALET SYMBOL*/
+ 0x2139, /* U+002139: INFORMATION SOURCE*/
+ 0x213a, /* U+00213a: ROTATED CAPITAL Q*/
+ 0x213b, /* U+00213b: FACSIMILE SIGN*/
+ 0x213c, /* U+00213c: DOUBLE-STRUCK SMALL PI*/
+ 0x213d, /* U+00213d: DOUBLE-STRUCK SMALL GAMMA*/
+ 0x213e, /* U+00213e: DOUBLE-STRUCK CAPITAL GAMMA*/
+ 0x213f, /* U+00213f: DOUBLE-STRUCK CAPITAL PI*/
+ 0x2140, /* U+002140: DOUBLE-STRUCK N-ARY SUMMATION*/
+ 0x2141, /* U+002141: TURNED SANS-SERIF CAPITAL G*/
+ 0x2142, /* U+002142: TURNED SANS-SERIF CAPITAL L*/
+ 0x2143, /* U+002143: REVERSED SANS-SERIF CAPITAL L*/
+ 0x2144, /* U+002144: TURNED SANS-SERIF CAPITAL Y*/
+ 0x2145, /* U+002145: DOUBLE-STRUCK ITALIC CAPITAL D*/
+ 0x2146, /* U+002146: DOUBLE-STRUCK ITALIC SMALL D*/
+ 0x2147, /* U+002147: DOUBLE-STRUCK ITALIC SMALL E*/
+ 0x2148, /* U+002148: DOUBLE-STRUCK ITALIC SMALL I*/
+ 0x2149, /* U+002149: DOUBLE-STRUCK ITALIC SMALL J*/
+ 0x214a, /* U+00214a: PROPERTY LINE*/
+ 0x214b, /* U+00214b: TURNED AMPERSAND*/
+ 0x214c, /* U+00214c: PER SIGN*/
+ 0x214d, /* U+00214d: AKTIESELSKAB*/
+ 0x2132, /* U+00214e: TURNED SMALL F*/
+ 0x214f, /* U+00214f: SYMBOL FOR SAMARITAN SOURCE*/
+ 0x2150, /* U+002150: VULGAR FRACTION ONE SEVENTH*/
+ 0x2151, /* U+002151: VULGAR FRACTION ONE NINTH*/
+ 0x2152, /* U+002152: VULGAR FRACTION ONE TENTH*/
+ 0x2153, /* U+002153: VULGAR FRACTION ONE THIRD*/
+ 0x2154, /* U+002154: VULGAR FRACTION TWO THIRDS*/
+ 0x2155, /* U+002155: VULGAR FRACTION ONE FIFTH*/
+ 0x2156, /* U+002156: VULGAR FRACTION TWO FIFTHS*/
+ 0x2157, /* U+002157: VULGAR FRACTION THREE FIFTHS*/
+ 0x2158, /* U+002158: VULGAR FRACTION FOUR FIFTHS*/
+ 0x2159, /* U+002159: VULGAR FRACTION ONE SIXTH*/
+ 0x215a, /* U+00215a: VULGAR FRACTION FIVE SIXTHS*/
+ 0x215b, /* U+00215b: VULGAR FRACTION ONE EIGHTH*/
+ 0x215c, /* U+00215c: VULGAR FRACTION THREE EIGHTHS*/
+ 0x215d, /* U+00215d: VULGAR FRACTION FIVE EIGHTHS*/
+ 0x215e, /* U+00215e: VULGAR FRACTION SEVEN EIGHTHS*/
+ 0x215f, /* U+00215f: FRACTION NUMERATOR ONE*/
+ 0x2160, /* U+002160: ROMAN NUMERAL ONE*/
+ 0x2161, /* U+002161: ROMAN NUMERAL TWO*/
+ 0x2162, /* U+002162: ROMAN NUMERAL THREE*/
+ 0x2163, /* U+002163: ROMAN NUMERAL FOUR*/
+ 0x2164, /* U+002164: ROMAN NUMERAL FIVE*/
+ 0x2165, /* U+002165: ROMAN NUMERAL SIX*/
+ 0x2166, /* U+002166: ROMAN NUMERAL SEVEN*/
+ 0x2167, /* U+002167: ROMAN NUMERAL EIGHT*/
+ 0x2168, /* U+002168: ROMAN NUMERAL NINE*/
+ 0x2169, /* U+002169: ROMAN NUMERAL TEN*/
+ 0x216a, /* U+00216a: ROMAN NUMERAL ELEVEN*/
+ 0x216b, /* U+00216b: ROMAN NUMERAL TWELVE*/
+ 0x216c, /* U+00216c: ROMAN NUMERAL FIFTY*/
+ 0x216d, /* U+00216d: ROMAN NUMERAL ONE HUNDRED*/
+ 0x216e, /* U+00216e: ROMAN NUMERAL FIVE HUNDRED*/
+ 0x216f, /* U+00216f: ROMAN NUMERAL ONE THOUSAND*/
+ 0x2160, /* U+002170: SMALL ROMAN NUMERAL ONE*/
+ 0x2161, /* U+002171: SMALL ROMAN NUMERAL TWO*/
+ 0x2162, /* U+002172: SMALL ROMAN NUMERAL THREE*/
+ 0x2163, /* U+002173: SMALL ROMAN NUMERAL FOUR*/
+ 0x2164, /* U+002174: SMALL ROMAN NUMERAL FIVE*/
+ 0x2165, /* U+002175: SMALL ROMAN NUMERAL SIX*/
+ 0x2166, /* U+002176: SMALL ROMAN NUMERAL SEVEN*/
+ 0x2167, /* U+002177: SMALL ROMAN NUMERAL EIGHT*/
+ 0x2168, /* U+002178: SMALL ROMAN NUMERAL NINE*/
+ 0x2169, /* U+002179: SMALL ROMAN NUMERAL TEN*/
+ 0x216a, /* U+00217a: SMALL ROMAN NUMERAL ELEVEN*/
+ 0x216b, /* U+00217b: SMALL ROMAN NUMERAL TWELVE*/
+ 0x216c, /* U+00217c: SMALL ROMAN NUMERAL FIFTY*/
+ 0x216d, /* U+00217d: SMALL ROMAN NUMERAL ONE HUNDRED*/
+ 0x216e, /* U+00217e: SMALL ROMAN NUMERAL FIVE HUNDRED*/
+ 0x216f, /* U+00217f: SMALL ROMAN NUMERAL ONE THOUSAND*/
+ 0x2180, /* U+002180: ROMAN NUMERAL ONE THOUSAND C D*/
+ 0x2181, /* U+002181: ROMAN NUMERAL FIVE THOUSAND*/
+ 0x2182, /* U+002182: ROMAN NUMERAL TEN THOUSAND*/
+ 0x2183, /* U+002183: ROMAN NUMERAL REVERSED ONE HUNDRED*/
+ 0x2183, /* U+002184: LATIN SMALL LETTER REVERSED C*/
+ 0x2185, /* U+002185: ROMAN NUMERAL SIX LATE FORM*/
+ 0x2186, /* U+002186: ROMAN NUMERAL FIFTY EARLY FORM*/
+ 0x2187, /* U+002187: ROMAN NUMERAL FIFTY THOUSAND*/
+ 0x2188, /* U+002188: ROMAN NUMERAL ONE HUNDRED THOUSAND*/
+ 0x2189, /* U+002189: VULGAR FRACTION ZERO THIRDS*/
+ 0x218a, /* U+00218a: */
+ 0x218b, /* U+00218b: */
+ 0x218c, /* U+00218c: */
+ 0x218d, /* U+00218d: */
+ 0x218e, /* U+00218e: */
+ 0x218f, /* U+00218f: */
+ 0x2190, /* U+002190: LEFTWARDS ARROW*/
+ 0x2191, /* U+002191: UPWARDS ARROW*/
+ 0x2192, /* U+002192: RIGHTWARDS ARROW*/
+ 0x2193, /* U+002193: DOWNWARDS ARROW*/
+ 0x2194, /* U+002194: LEFT RIGHT ARROW*/
+ 0x2195, /* U+002195: UP DOWN ARROW*/
+ 0x2196, /* U+002196: NORTH WEST ARROW*/
+ 0x2197, /* U+002197: NORTH EAST ARROW*/
+ 0x2198, /* U+002198: SOUTH EAST ARROW*/
+ 0x2199, /* U+002199: SOUTH WEST ARROW*/
+ 0x219a, /* U+00219a: LEFTWARDS ARROW WITH STROKE*/
+ 0x219b, /* U+00219b: RIGHTWARDS ARROW WITH STROKE*/
+ 0x219c, /* U+00219c: LEFTWARDS WAVE ARROW*/
+ 0x219d, /* U+00219d: RIGHTWARDS WAVE ARROW*/
+ 0x219e, /* U+00219e: LEFTWARDS TWO HEADED ARROW*/
+ 0x219f, /* U+00219f: UPWARDS TWO HEADED ARROW*/
+ 0x21a0, /* U+0021a0: RIGHTWARDS TWO HEADED ARROW*/
+ 0x21a1, /* U+0021a1: DOWNWARDS TWO HEADED ARROW*/
+ 0x21a2, /* U+0021a2: LEFTWARDS ARROW WITH TAIL*/
+ 0x21a3, /* U+0021a3: RIGHTWARDS ARROW WITH TAIL*/
+ 0x21a4, /* U+0021a4: LEFTWARDS ARROW FROM BAR*/
+ 0x21a5, /* U+0021a5: UPWARDS ARROW FROM BAR*/
+ 0x21a6, /* U+0021a6: RIGHTWARDS ARROW FROM BAR*/
+ 0x21a7, /* U+0021a7: DOWNWARDS ARROW FROM BAR*/
+ 0x21a8, /* U+0021a8: UP DOWN ARROW WITH BASE*/
+ 0x21a9, /* U+0021a9: LEFTWARDS ARROW WITH HOOK*/
+ 0x21aa, /* U+0021aa: RIGHTWARDS ARROW WITH HOOK*/
+ 0x21ab, /* U+0021ab: LEFTWARDS ARROW WITH LOOP*/
+ 0x21ac, /* U+0021ac: RIGHTWARDS ARROW WITH LOOP*/
+ 0x21ad, /* U+0021ad: LEFT RIGHT WAVE ARROW*/
+ 0x21ae, /* U+0021ae: LEFT RIGHT ARROW WITH STROKE*/
+ 0x21af, /* U+0021af: DOWNWARDS ZIGZAG ARROW*/
+ 0x21b0, /* U+0021b0: UPWARDS ARROW WITH TIP LEFTWARDS*/
+ 0x21b1, /* U+0021b1: UPWARDS ARROW WITH TIP RIGHTWARDS*/
+ 0x21b2, /* U+0021b2: DOWNWARDS ARROW WITH TIP LEFTWARDS*/
+ 0x21b3, /* U+0021b3: DOWNWARDS ARROW WITH TIP RIGHTWARDS*/
+ 0x21b4, /* U+0021b4: RIGHTWARDS ARROW WITH CORNER DOWNWARDS*/
+ 0x21b5, /* U+0021b5: DOWNWARDS ARROW WITH CORNER LEFTWARDS*/
+ 0x21b6, /* U+0021b6: ANTICLOCKWISE TOP SEMICIRCLE ARROW*/
+ 0x21b7, /* U+0021b7: CLOCKWISE TOP SEMICIRCLE ARROW*/
+ 0x21b8, /* U+0021b8: NORTH WEST ARROW TO LONG BAR*/
+ 0x21b9, /* U+0021b9: LEFTWARDS ARROW TO BAR OVER RIGHTWARDS ARROW TO BAR*/
+ 0x21ba, /* U+0021ba: ANTICLOCKWISE OPEN CIRCLE ARROW*/
+ 0x21bb, /* U+0021bb: CLOCKWISE OPEN CIRCLE ARROW*/
+ 0x21bc, /* U+0021bc: LEFTWARDS HARPOON WITH BARB UPWARDS*/
+ 0x21bd, /* U+0021bd: LEFTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0x21be, /* U+0021be: UPWARDS HARPOON WITH BARB RIGHTWARDS*/
+ 0x21bf, /* U+0021bf: UPWARDS HARPOON WITH BARB LEFTWARDS*/
+ 0x21c0, /* U+0021c0: RIGHTWARDS HARPOON WITH BARB UPWARDS*/
+ 0x21c1, /* U+0021c1: RIGHTWARDS HARPOON WITH BARB DOWNWARDS*/
+ 0x21c2, /* U+0021c2: DOWNWARDS HARPOON WITH BARB RIGHTWARDS*/
+ 0x21c3, /* U+0021c3: DOWNWARDS HARPOON WITH BARB LEFTWARDS*/
+ 0x21c4, /* U+0021c4: RIGHTWARDS ARROW OVER LEFTWARDS ARROW*/
+ 0x21c5, /* U+0021c5: UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW*/
+ 0x21c6, /* U+0021c6: LEFTWARDS ARROW OVER RIGHTWARDS ARROW*/
+ 0x21c7, /* U+0021c7: LEFTWARDS PAIRED ARROWS*/
+ 0x21c8, /* U+0021c8: UPWARDS PAIRED ARROWS*/
+ 0x21c9, /* U+0021c9: RIGHTWARDS PAIRED ARROWS*/
+ 0x21ca, /* U+0021ca: DOWNWARDS PAIRED ARROWS*/
+ 0x21cb, /* U+0021cb: LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON*/
+ 0x21cc, /* U+0021cc: RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON*/
+ 0x21cd, /* U+0021cd: LEFTWARDS DOUBLE ARROW WITH STROKE*/
+ 0x21ce, /* U+0021ce: LEFT RIGHT DOUBLE ARROW WITH STROKE*/
+ 0x21cf, /* U+0021cf: RIGHTWARDS DOUBLE ARROW WITH STROKE*/
+ 0x21d0, /* U+0021d0: LEFTWARDS DOUBLE ARROW*/
+ 0x21d1, /* U+0021d1: UPWARDS DOUBLE ARROW*/
+ 0x21d2, /* U+0021d2: RIGHTWARDS DOUBLE ARROW*/
+ 0x21d3, /* U+0021d3: DOWNWARDS DOUBLE ARROW*/
+ 0x21d4, /* U+0021d4: LEFT RIGHT DOUBLE ARROW*/
+ 0x21d5, /* U+0021d5: UP DOWN DOUBLE ARROW*/
+ 0x21d6, /* U+0021d6: NORTH WEST DOUBLE ARROW*/
+ 0x21d7, /* U+0021d7: NORTH EAST DOUBLE ARROW*/
+ 0x21d8, /* U+0021d8: SOUTH EAST DOUBLE ARROW*/
+ 0x21d9, /* U+0021d9: SOUTH WEST DOUBLE ARROW*/
+ 0x21da, /* U+0021da: LEFTWARDS TRIPLE ARROW*/
+ 0x21db, /* U+0021db: RIGHTWARDS TRIPLE ARROW*/
+ 0x21dc, /* U+0021dc: LEFTWARDS SQUIGGLE ARROW*/
+ 0x21dd, /* U+0021dd: RIGHTWARDS SQUIGGLE ARROW*/
+ 0x21de, /* U+0021de: UPWARDS ARROW WITH DOUBLE STROKE*/
+ 0x21df, /* U+0021df: DOWNWARDS ARROW WITH DOUBLE STROKE*/
+ 0x21e0, /* U+0021e0: LEFTWARDS DASHED ARROW*/
+ 0x21e1, /* U+0021e1: UPWARDS DASHED ARROW*/
+ 0x21e2, /* U+0021e2: RIGHTWARDS DASHED ARROW*/
+ 0x21e3, /* U+0021e3: DOWNWARDS DASHED ARROW*/
+ 0x21e4, /* U+0021e4: LEFTWARDS ARROW TO BAR*/
+ 0x21e5, /* U+0021e5: RIGHTWARDS ARROW TO BAR*/
+ 0x21e6, /* U+0021e6: LEFTWARDS WHITE ARROW*/
+ 0x21e7, /* U+0021e7: UPWARDS WHITE ARROW*/
+ 0x21e8, /* U+0021e8: RIGHTWARDS WHITE ARROW*/
+ 0x21e9, /* U+0021e9: DOWNWARDS WHITE ARROW*/
+ 0x21ea, /* U+0021ea: UPWARDS WHITE ARROW FROM BAR*/
+ 0x21eb, /* U+0021eb: UPWARDS WHITE ARROW ON PEDESTAL*/
+ 0x21ec, /* U+0021ec: UPWARDS WHITE ARROW ON PEDESTAL WITH HORIZONTAL BAR*/
+ 0x21ed, /* U+0021ed: UPWARDS WHITE ARROW ON PEDESTAL WITH VERTICAL BAR*/
+ 0x21ee, /* U+0021ee: UPWARDS WHITE DOUBLE ARROW*/
+ 0x21ef, /* U+0021ef: UPWARDS WHITE DOUBLE ARROW ON PEDESTAL*/
+ 0x21f0, /* U+0021f0: RIGHTWARDS WHITE ARROW FROM WALL*/
+ 0x21f1, /* U+0021f1: NORTH WEST ARROW TO CORNER*/
+ 0x21f2, /* U+0021f2: SOUTH EAST ARROW TO CORNER*/
+ 0x21f3, /* U+0021f3: UP DOWN WHITE ARROW*/
+ 0x21f4, /* U+0021f4: RIGHT ARROW WITH SMALL CIRCLE*/
+ 0x21f5, /* U+0021f5: DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW*/
+ 0x21f6, /* U+0021f6: THREE RIGHTWARDS ARROWS*/
+ 0x21f7, /* U+0021f7: LEFTWARDS ARROW WITH VERTICAL STROKE*/
+ 0x21f8, /* U+0021f8: RIGHTWARDS ARROW WITH VERTICAL STROKE*/
+ 0x21f9, /* U+0021f9: LEFT RIGHT ARROW WITH VERTICAL STROKE*/
+ 0x21fa, /* U+0021fa: LEFTWARDS ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0x21fb, /* U+0021fb: RIGHTWARDS ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0x21fc, /* U+0021fc: LEFT RIGHT ARROW WITH DOUBLE VERTICAL STROKE*/
+ 0x21fd, /* U+0021fd: LEFTWARDS OPEN-HEADED ARROW*/
+ 0x21fe, /* U+0021fe: RIGHTWARDS OPEN-HEADED ARROW*/
+ 0x21ff, /* U+0021ff: LEFT RIGHT OPEN-HEADED ARROW*/
+ 0x2200, /* U+002200: FOR ALL*/
+ 0x2201, /* U+002201: COMPLEMENT*/
+ 0x2202, /* U+002202: PARTIAL DIFFERENTIAL*/
+ 0x2203, /* U+002203: THERE EXISTS*/
+ 0x2204, /* U+002204: THERE DOES NOT EXIST*/
+ 0x2205, /* U+002205: EMPTY SET*/
+ 0x2206, /* U+002206: INCREMENT*/
+ 0x2207, /* U+002207: NABLA*/
+ 0x2208, /* U+002208: ELEMENT OF*/
+ 0x2209, /* U+002209: NOT AN ELEMENT OF*/
+ 0x220a, /* U+00220a: SMALL ELEMENT OF*/
+ 0x220b, /* U+00220b: CONTAINS AS MEMBER*/
+ 0x220c, /* U+00220c: DOES NOT CONTAIN AS MEMBER*/
+ 0x220d, /* U+00220d: SMALL CONTAINS AS MEMBER*/
+ 0x220e, /* U+00220e: END OF PROOF*/
+ 0x220f, /* U+00220f: N-ARY PRODUCT*/
+ 0x2210, /* U+002210: N-ARY COPRODUCT*/
+ 0x2211, /* U+002211: N-ARY SUMMATION*/
+ 0x2212, /* U+002212: MINUS SIGN*/
+ 0x2213, /* U+002213: MINUS-OR-PLUS SIGN*/
+ 0x2214, /* U+002214: DOT PLUS*/
+ 0x2215, /* U+002215: DIVISION SLASH*/
+ 0x2216, /* U+002216: SET MINUS*/
+ 0x2217, /* U+002217: ASTERISK OPERATOR*/
+ 0x2218, /* U+002218: RING OPERATOR*/
+ 0x2219, /* U+002219: BULLET OPERATOR*/
+ 0x221a, /* U+00221a: SQUARE ROOT*/
+ 0x221b, /* U+00221b: CUBE ROOT*/
+ 0x221c, /* U+00221c: FOURTH ROOT*/
+ 0x221d, /* U+00221d: PROPORTIONAL TO*/
+ 0x221e, /* U+00221e: INFINITY*/
+ 0x221f, /* U+00221f: RIGHT ANGLE*/
+ 0x2220, /* U+002220: ANGLE*/
+ 0x2221, /* U+002221: MEASURED ANGLE*/
+ 0x2222, /* U+002222: SPHERICAL ANGLE*/
+ 0x2223, /* U+002223: DIVIDES*/
+ 0x2224, /* U+002224: DOES NOT DIVIDE*/
+ 0x2225, /* U+002225: PARALLEL TO*/
+ 0x2226, /* U+002226: NOT PARALLEL TO*/
+ 0x2227, /* U+002227: LOGICAL AND*/
+ 0x2228, /* U+002228: LOGICAL OR*/
+ 0x2229, /* U+002229: INTERSECTION*/
+ 0x222a, /* U+00222a: UNION*/
+ 0x222b, /* U+00222b: INTEGRAL*/
+ 0x222c, /* U+00222c: DOUBLE INTEGRAL*/
+ 0x222d, /* U+00222d: TRIPLE INTEGRAL*/
+ 0x222e, /* U+00222e: CONTOUR INTEGRAL*/
+ 0x222f, /* U+00222f: SURFACE INTEGRAL*/
+ 0x2230, /* U+002230: VOLUME INTEGRAL*/
+ 0x2231, /* U+002231: CLOCKWISE INTEGRAL*/
+ 0x2232, /* U+002232: CLOCKWISE CONTOUR INTEGRAL*/
+ 0x2233, /* U+002233: ANTICLOCKWISE CONTOUR INTEGRAL*/
+ 0x2234, /* U+002234: THEREFORE*/
+ 0x2235, /* U+002235: BECAUSE*/
+ 0x2236, /* U+002236: RATIO*/
+ 0x2237, /* U+002237: PROPORTION*/
+ 0x2238, /* U+002238: DOT MINUS*/
+ 0x2239, /* U+002239: EXCESS*/
+ 0x223a, /* U+00223a: GEOMETRIC PROPORTION*/
+ 0x223b, /* U+00223b: HOMOTHETIC*/
+ 0x223c, /* U+00223c: TILDE OPERATOR*/
+ 0x223d, /* U+00223d: REVERSED TILDE*/
+ 0x223e, /* U+00223e: INVERTED LAZY S*/
+ 0x223f, /* U+00223f: SINE WAVE*/
+ 0x2240, /* U+002240: WREATH PRODUCT*/
+ 0x2241, /* U+002241: NOT TILDE*/
+ 0x2242, /* U+002242: MINUS TILDE*/
+ 0x2243, /* U+002243: ASYMPTOTICALLY EQUAL TO*/
+ 0x2244, /* U+002244: NOT ASYMPTOTICALLY EQUAL TO*/
+ 0x2245, /* U+002245: APPROXIMATELY EQUAL TO*/
+ 0x2246, /* U+002246: APPROXIMATELY BUT NOT ACTUALLY EQUAL TO*/
+ 0x2247, /* U+002247: NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO*/
+ 0x2248, /* U+002248: ALMOST EQUAL TO*/
+ 0x2249, /* U+002249: NOT ALMOST EQUAL TO*/
+ 0x224a, /* U+00224a: ALMOST EQUAL OR EQUAL TO*/
+ 0x224b, /* U+00224b: TRIPLE TILDE*/
+ 0x224c, /* U+00224c: ALL EQUAL TO*/
+ 0x224d, /* U+00224d: EQUIVALENT TO*/
+ 0x224e, /* U+00224e: GEOMETRICALLY EQUIVALENT TO*/
+ 0x224f, /* U+00224f: DIFFERENCE BETWEEN*/
+ 0x2250, /* U+002250: APPROACHES THE LIMIT*/
+ 0x2251, /* U+002251: GEOMETRICALLY EQUAL TO*/
+ 0x2252, /* U+002252: APPROXIMATELY EQUAL TO OR THE IMAGE OF*/
+ 0x2253, /* U+002253: IMAGE OF OR APPROXIMATELY EQUAL TO*/
+ 0x2254, /* U+002254: COLON EQUALS*/
+ 0x2255, /* U+002255: EQUALS COLON*/
+ 0x2256, /* U+002256: RING IN EQUAL TO*/
+ 0x2257, /* U+002257: RING EQUAL TO*/
+ 0x2258, /* U+002258: CORRESPONDS TO*/
+ 0x2259, /* U+002259: ESTIMATES*/
+ 0x225a, /* U+00225a: EQUIANGULAR TO*/
+ 0x225b, /* U+00225b: STAR EQUALS*/
+ 0x225c, /* U+00225c: DELTA EQUAL TO*/
+ 0x225d, /* U+00225d: EQUAL TO BY DEFINITION*/
+ 0x225e, /* U+00225e: MEASURED BY*/
+ 0x225f, /* U+00225f: QUESTIONED EQUAL TO*/
+ 0x2260, /* U+002260: NOT EQUAL TO*/
+ 0x2261, /* U+002261: IDENTICAL TO*/
+ 0x2262, /* U+002262: NOT IDENTICAL TO*/
+ 0x2263, /* U+002263: STRICTLY EQUIVALENT TO*/
+ 0x2264, /* U+002264: LESS-THAN OR EQUAL TO*/
+ 0x2265, /* U+002265: GREATER-THAN OR EQUAL TO*/
+ 0x2266, /* U+002266: LESS-THAN OVER EQUAL TO*/
+ 0x2267, /* U+002267: GREATER-THAN OVER EQUAL TO*/
+ 0x2268, /* U+002268: LESS-THAN BUT NOT EQUAL TO*/
+ 0x2269, /* U+002269: GREATER-THAN BUT NOT EQUAL TO*/
+ 0x226a, /* U+00226a: MUCH LESS-THAN*/
+ 0x226b, /* U+00226b: MUCH GREATER-THAN*/
+ 0x226c, /* U+00226c: BETWEEN*/
+ 0x226d, /* U+00226d: NOT EQUIVALENT TO*/
+ 0x226e, /* U+00226e: NOT LESS-THAN*/
+ 0x226f, /* U+00226f: NOT GREATER-THAN*/
+ 0x2270, /* U+002270: NEITHER LESS-THAN NOR EQUAL TO*/
+ 0x2271, /* U+002271: NEITHER GREATER-THAN NOR EQUAL TO*/
+ 0x2272, /* U+002272: LESS-THAN OR EQUIVALENT TO*/
+ 0x2273, /* U+002273: GREATER-THAN OR EQUIVALENT TO*/
+ 0x2274, /* U+002274: NEITHER LESS-THAN NOR EQUIVALENT TO*/
+ 0x2275, /* U+002275: NEITHER GREATER-THAN NOR EQUIVALENT TO*/
+ 0x2276, /* U+002276: LESS-THAN OR GREATER-THAN*/
+ 0x2277, /* U+002277: GREATER-THAN OR LESS-THAN*/
+ 0x2278, /* U+002278: NEITHER LESS-THAN NOR GREATER-THAN*/
+ 0x2279, /* U+002279: NEITHER GREATER-THAN NOR LESS-THAN*/
+ 0x227a, /* U+00227a: PRECEDES*/
+ 0x227b, /* U+00227b: SUCCEEDS*/
+ 0x227c, /* U+00227c: PRECEDES OR EQUAL TO*/
+ 0x227d, /* U+00227d: SUCCEEDS OR EQUAL TO*/
+ 0x227e, /* U+00227e: PRECEDES OR EQUIVALENT TO*/
+ 0x227f, /* U+00227f: SUCCEEDS OR EQUIVALENT TO*/
+ 0x2280, /* U+002280: DOES NOT PRECEDE*/
+ 0x2281, /* U+002281: DOES NOT SUCCEED*/
+ 0x2282, /* U+002282: SUBSET OF*/
+ 0x2283, /* U+002283: SUPERSET OF*/
+ 0x2284, /* U+002284: NOT A SUBSET OF*/
+ 0x2285, /* U+002285: NOT A SUPERSET OF*/
+ 0x2286, /* U+002286: SUBSET OF OR EQUAL TO*/
+ 0x2287, /* U+002287: SUPERSET OF OR EQUAL TO*/
+ 0x2288, /* U+002288: NEITHER A SUBSET OF NOR EQUAL TO*/
+ 0x2289, /* U+002289: NEITHER A SUPERSET OF NOR EQUAL TO*/
+ 0x228a, /* U+00228a: SUBSET OF WITH NOT EQUAL TO*/
+ 0x228b, /* U+00228b: SUPERSET OF WITH NOT EQUAL TO*/
+ 0x228c, /* U+00228c: MULTISET*/
+ 0x228d, /* U+00228d: MULTISET MULTIPLICATION*/
+ 0x228e, /* U+00228e: MULTISET UNION*/
+ 0x228f, /* U+00228f: SQUARE IMAGE OF*/
+ 0x2290, /* U+002290: SQUARE ORIGINAL OF*/
+ 0x2291, /* U+002291: SQUARE IMAGE OF OR EQUAL TO*/
+ 0x2292, /* U+002292: SQUARE ORIGINAL OF OR EQUAL TO*/
+ 0x2293, /* U+002293: SQUARE CAP*/
+ 0x2294, /* U+002294: SQUARE CUP*/
+ 0x2295, /* U+002295: CIRCLED PLUS*/
+ 0x2296, /* U+002296: CIRCLED MINUS*/
+ 0x2297, /* U+002297: CIRCLED TIMES*/
+ 0x2298, /* U+002298: CIRCLED DIVISION SLASH*/
+ 0x2299, /* U+002299: CIRCLED DOT OPERATOR*/
+ 0x229a, /* U+00229a: CIRCLED RING OPERATOR*/
+ 0x229b, /* U+00229b: CIRCLED ASTERISK OPERATOR*/
+ 0x229c, /* U+00229c: CIRCLED EQUALS*/
+ 0x229d, /* U+00229d: CIRCLED DASH*/
+ 0x229e, /* U+00229e: SQUARED PLUS*/
+ 0x229f, /* U+00229f: SQUARED MINUS*/
+ 0x22a0, /* U+0022a0: SQUARED TIMES*/
+ 0x22a1, /* U+0022a1: SQUARED DOT OPERATOR*/
+ 0x22a2, /* U+0022a2: RIGHT TACK*/
+ 0x22a3, /* U+0022a3: LEFT TACK*/
+ 0x22a4, /* U+0022a4: DOWN TACK*/
+ 0x22a5, /* U+0022a5: UP TACK*/
+ 0x22a6, /* U+0022a6: ASSERTION*/
+ 0x22a7, /* U+0022a7: MODELS*/
+ 0x22a8, /* U+0022a8: TRUE*/
+ 0x22a9, /* U+0022a9: FORCES*/
+ 0x22aa, /* U+0022aa: TRIPLE VERTICAL BAR RIGHT TURNSTILE*/
+ 0x22ab, /* U+0022ab: DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE*/
+ 0x22ac, /* U+0022ac: DOES NOT PROVE*/
+ 0x22ad, /* U+0022ad: NOT TRUE*/
+ 0x22ae, /* U+0022ae: DOES NOT FORCE*/
+ 0x22af, /* U+0022af: NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE*/
+ 0x22b0, /* U+0022b0: PRECEDES UNDER RELATION*/
+ 0x22b1, /* U+0022b1: SUCCEEDS UNDER RELATION*/
+ 0x22b2, /* U+0022b2: NORMAL SUBGROUP OF*/
+ 0x22b3, /* U+0022b3: CONTAINS AS NORMAL SUBGROUP*/
+ 0x22b4, /* U+0022b4: NORMAL SUBGROUP OF OR EQUAL TO*/
+ 0x22b5, /* U+0022b5: CONTAINS AS NORMAL SUBGROUP OR EQUAL TO*/
+ 0x22b6, /* U+0022b6: ORIGINAL OF*/
+ 0x22b7, /* U+0022b7: IMAGE OF*/
+ 0x22b8, /* U+0022b8: MULTIMAP*/
+ 0x22b9, /* U+0022b9: HERMITIAN CONJUGATE MATRIX*/
+ 0x22ba, /* U+0022ba: INTERCALATE*/
+ 0x22bb, /* U+0022bb: XOR*/
+ 0x22bc, /* U+0022bc: NAND*/
+ 0x22bd, /* U+0022bd: NOR*/
+ 0x22be, /* U+0022be: RIGHT ANGLE WITH ARC*/
+ 0x22bf, /* U+0022bf: RIGHT TRIANGLE*/
+ 0x22c0, /* U+0022c0: N-ARY LOGICAL AND*/
+ 0x22c1, /* U+0022c1: N-ARY LOGICAL OR*/
+ 0x22c2, /* U+0022c2: N-ARY INTERSECTION*/
+ 0x22c3, /* U+0022c3: N-ARY UNION*/
+ 0x22c4, /* U+0022c4: DIAMOND OPERATOR*/
+ 0x22c5, /* U+0022c5: DOT OPERATOR*/
+ 0x22c6, /* U+0022c6: STAR OPERATOR*/
+ 0x22c7, /* U+0022c7: DIVISION TIMES*/
+ 0x22c8, /* U+0022c8: BOWTIE*/
+ 0x22c9, /* U+0022c9: LEFT NORMAL FACTOR SEMIDIRECT PRODUCT*/
+ 0x22ca, /* U+0022ca: RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT*/
+ 0x22cb, /* U+0022cb: LEFT SEMIDIRECT PRODUCT*/
+ 0x22cc, /* U+0022cc: RIGHT SEMIDIRECT PRODUCT*/
+ 0x22cd, /* U+0022cd: REVERSED TILDE EQUALS*/
+ 0x22ce, /* U+0022ce: CURLY LOGICAL OR*/
+ 0x22cf, /* U+0022cf: CURLY LOGICAL AND*/
+ 0x22d0, /* U+0022d0: DOUBLE SUBSET*/
+ 0x22d1, /* U+0022d1: DOUBLE SUPERSET*/
+ 0x22d2, /* U+0022d2: DOUBLE INTERSECTION*/
+ 0x22d3, /* U+0022d3: DOUBLE UNION*/
+ 0x22d4, /* U+0022d4: PITCHFORK*/
+ 0x22d5, /* U+0022d5: EQUAL AND PARALLEL TO*/
+ 0x22d6, /* U+0022d6: LESS-THAN WITH DOT*/
+ 0x22d7, /* U+0022d7: GREATER-THAN WITH DOT*/
+ 0x22d8, /* U+0022d8: VERY MUCH LESS-THAN*/
+ 0x22d9, /* U+0022d9: VERY MUCH GREATER-THAN*/
+ 0x22da, /* U+0022da: LESS-THAN EQUAL TO OR GREATER-THAN*/
+ 0x22db, /* U+0022db: GREATER-THAN EQUAL TO OR LESS-THAN*/
+ 0x22dc, /* U+0022dc: EQUAL TO OR LESS-THAN*/
+ 0x22dd, /* U+0022dd: EQUAL TO OR GREATER-THAN*/
+ 0x22de, /* U+0022de: EQUAL TO OR PRECEDES*/
+ 0x22df, /* U+0022df: EQUAL TO OR SUCCEEDS*/
+ 0x22e0, /* U+0022e0: DOES NOT PRECEDE OR EQUAL*/
+ 0x22e1, /* U+0022e1: DOES NOT SUCCEED OR EQUAL*/
+ 0x22e2, /* U+0022e2: NOT SQUARE IMAGE OF OR EQUAL TO*/
+ 0x22e3, /* U+0022e3: NOT SQUARE ORIGINAL OF OR EQUAL TO*/
+ 0x22e4, /* U+0022e4: SQUARE IMAGE OF OR NOT EQUAL TO*/
+ 0x22e5, /* U+0022e5: SQUARE ORIGINAL OF OR NOT EQUAL TO*/
+ 0x22e6, /* U+0022e6: LESS-THAN BUT NOT EQUIVALENT TO*/
+ 0x22e7, /* U+0022e7: GREATER-THAN BUT NOT EQUIVALENT TO*/
+ 0x22e8, /* U+0022e8: PRECEDES BUT NOT EQUIVALENT TO*/
+ 0x22e9, /* U+0022e9: SUCCEEDS BUT NOT EQUIVALENT TO*/
+ 0x22ea, /* U+0022ea: NOT NORMAL SUBGROUP OF*/
+ 0x22eb, /* U+0022eb: DOES NOT CONTAIN AS NORMAL SUBGROUP*/
+ 0x22ec, /* U+0022ec: NOT NORMAL SUBGROUP OF OR EQUAL TO*/
+ 0x22ed, /* U+0022ed: DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL*/
+ 0x22ee, /* U+0022ee: VERTICAL ELLIPSIS*/
+ 0x22ef, /* U+0022ef: MIDLINE HORIZONTAL ELLIPSIS*/
+ 0x22f0, /* U+0022f0: UP RIGHT DIAGONAL ELLIPSIS*/
+ 0x22f1, /* U+0022f1: DOWN RIGHT DIAGONAL ELLIPSIS*/
+ 0x22f2, /* U+0022f2: ELEMENT OF WITH LONG HORIZONTAL STROKE*/
+ 0x22f3, /* U+0022f3: ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0x22f4, /* U+0022f4: SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0x22f5, /* U+0022f5: ELEMENT OF WITH DOT ABOVE*/
+ 0x22f6, /* U+0022f6: ELEMENT OF WITH OVERBAR*/
+ 0x22f7, /* U+0022f7: SMALL ELEMENT OF WITH OVERBAR*/
+ 0x22f8, /* U+0022f8: ELEMENT OF WITH UNDERBAR*/
+ 0x22f9, /* U+0022f9: ELEMENT OF WITH TWO HORIZONTAL STROKES*/
+ 0x22fa, /* U+0022fa: CONTAINS WITH LONG HORIZONTAL STROKE*/
+ 0x22fb, /* U+0022fb: CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0x22fc, /* U+0022fc: SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE*/
+ 0x22fd, /* U+0022fd: CONTAINS WITH OVERBAR*/
+ 0x22fe, /* U+0022fe: SMALL CONTAINS WITH OVERBAR*/
+ 0x22ff, /* U+0022ff: Z NOTATION BAG MEMBERSHIP*/
+ 0x2300, /* U+002300: DIAMETER SIGN*/
+ 0x2301, /* U+002301: ELECTRIC ARROW*/
+ 0x2302, /* U+002302: HOUSE*/
+ 0x2303, /* U+002303: UP ARROWHEAD*/
+ 0x2304, /* U+002304: DOWN ARROWHEAD*/
+ 0x2305, /* U+002305: PROJECTIVE*/
+ 0x2306, /* U+002306: PERSPECTIVE*/
+ 0x2307, /* U+002307: WAVY LINE*/
+ 0x2308, /* U+002308: LEFT CEILING*/
+ 0x2309, /* U+002309: RIGHT CEILING*/
+ 0x230a, /* U+00230a: LEFT FLOOR*/
+ 0x230b, /* U+00230b: RIGHT FLOOR*/
+ 0x230c, /* U+00230c: BOTTOM RIGHT CROP*/
+ 0x230d, /* U+00230d: BOTTOM LEFT CROP*/
+ 0x230e, /* U+00230e: TOP RIGHT CROP*/
+ 0x230f, /* U+00230f: TOP LEFT CROP*/
+ 0x2310, /* U+002310: REVERSED NOT SIGN*/
+ 0x2311, /* U+002311: SQUARE LOZENGE*/
+ 0x2312, /* U+002312: ARC*/
+ 0x2313, /* U+002313: SEGMENT*/
+ 0x2314, /* U+002314: SECTOR*/
+ 0x2315, /* U+002315: TELEPHONE RECORDER*/
+ 0x2316, /* U+002316: POSITION INDICATOR*/
+ 0x2317, /* U+002317: VIEWDATA SQUARE*/
+ 0x2318, /* U+002318: PLACE OF INTEREST SIGN*/
+ 0x2319, /* U+002319: TURNED NOT SIGN*/
+ 0x231a, /* U+00231a: WATCH*/
+ 0x231b, /* U+00231b: HOURGLASS*/
+ 0x231c, /* U+00231c: TOP LEFT CORNER*/
+ 0x231d, /* U+00231d: TOP RIGHT CORNER*/
+ 0x231e, /* U+00231e: BOTTOM LEFT CORNER*/
+ 0x231f, /* U+00231f: BOTTOM RIGHT CORNER*/
+ 0x2320, /* U+002320: TOP HALF INTEGRAL*/
+ 0x2321, /* U+002321: BOTTOM HALF INTEGRAL*/
+ 0x2322, /* U+002322: FROWN*/
+ 0x2323, /* U+002323: SMILE*/
+ 0x2324, /* U+002324: UP ARROWHEAD BETWEEN TWO HORIZONTAL BARS*/
+ 0x2325, /* U+002325: OPTION KEY*/
+ 0x2326, /* U+002326: ERASE TO THE RIGHT*/
+ 0x2327, /* U+002327: X IN A RECTANGLE BOX*/
+ 0x2328, /* U+002328: KEYBOARD*/
+ 0x2329, /* U+002329: LEFT-POINTING ANGLE BRACKET*/
+ 0x232a, /* U+00232a: RIGHT-POINTING ANGLE BRACKET*/
+ 0x232b, /* U+00232b: ERASE TO THE LEFT*/
+ 0x232c, /* U+00232c: BENZENE RING*/
+ 0x232d, /* U+00232d: CYLINDRICITY*/
+ 0x232e, /* U+00232e: ALL AROUND-PROFILE*/
+ 0x232f, /* U+00232f: SYMMETRY*/
+ 0x2330, /* U+002330: TOTAL RUNOUT*/
+ 0x2331, /* U+002331: DIMENSION ORIGIN*/
+ 0x2332, /* U+002332: CONICAL TAPER*/
+ 0x2333, /* U+002333: SLOPE*/
+ 0x2334, /* U+002334: COUNTERBORE*/
+ 0x2335, /* U+002335: COUNTERSINK*/
+ 0x2336, /* U+002336: APL FUNCTIONAL SYMBOL I-BEAM*/
+ 0x2337, /* U+002337: APL FUNCTIONAL SYMBOL SQUISH QUAD*/
+ 0x2338, /* U+002338: APL FUNCTIONAL SYMBOL QUAD EQUAL*/
+ 0x2339, /* U+002339: APL FUNCTIONAL SYMBOL QUAD DIVIDE*/
+ 0x233a, /* U+00233a: APL FUNCTIONAL SYMBOL QUAD DIAMOND*/
+ 0x233b, /* U+00233b: APL FUNCTIONAL SYMBOL QUAD JOT*/
+ 0x233c, /* U+00233c: APL FUNCTIONAL SYMBOL QUAD CIRCLE*/
+ 0x233d, /* U+00233d: APL FUNCTIONAL SYMBOL CIRCLE STILE*/
+ 0x233e, /* U+00233e: APL FUNCTIONAL SYMBOL CIRCLE JOT*/
+ 0x233f, /* U+00233f: APL FUNCTIONAL SYMBOL SLASH BAR*/
+ 0x2340, /* U+002340: APL FUNCTIONAL SYMBOL BACKSLASH BAR*/
+ 0x2341, /* U+002341: APL FUNCTIONAL SYMBOL QUAD SLASH*/
+ 0x2342, /* U+002342: APL FUNCTIONAL SYMBOL QUAD BACKSLASH*/
+ 0x2343, /* U+002343: APL FUNCTIONAL SYMBOL QUAD LESS-THAN*/
+ 0x2344, /* U+002344: APL FUNCTIONAL SYMBOL QUAD GREATER-THAN*/
+ 0x2345, /* U+002345: APL FUNCTIONAL SYMBOL LEFTWARDS VANE*/
+ 0x2346, /* U+002346: APL FUNCTIONAL SYMBOL RIGHTWARDS VANE*/
+ 0x2347, /* U+002347: APL FUNCTIONAL SYMBOL QUAD LEFTWARDS ARROW*/
+ 0x2348, /* U+002348: APL FUNCTIONAL SYMBOL QUAD RIGHTWARDS ARROW*/
+ 0x2349, /* U+002349: APL FUNCTIONAL SYMBOL CIRCLE BACKSLASH*/
+ 0x234a, /* U+00234a: APL FUNCTIONAL SYMBOL DOWN TACK UNDERBAR*/
+ 0x234b, /* U+00234b: APL FUNCTIONAL SYMBOL DELTA STILE*/
+ 0x234c, /* U+00234c: APL FUNCTIONAL SYMBOL QUAD DOWN CARET*/
+ 0x234d, /* U+00234d: APL FUNCTIONAL SYMBOL QUAD DELTA*/
+ 0x234e, /* U+00234e: APL FUNCTIONAL SYMBOL DOWN TACK JOT*/
+ 0x234f, /* U+00234f: APL FUNCTIONAL SYMBOL UPWARDS VANE*/
+ 0x2350, /* U+002350: APL FUNCTIONAL SYMBOL QUAD UPWARDS ARROW*/
+ 0x2351, /* U+002351: APL FUNCTIONAL SYMBOL UP TACK OVERBAR*/
+ 0x2352, /* U+002352: APL FUNCTIONAL SYMBOL DEL STILE*/
+ 0x2353, /* U+002353: APL FUNCTIONAL SYMBOL QUAD UP CARET*/
+ 0x2354, /* U+002354: APL FUNCTIONAL SYMBOL QUAD DEL*/
+ 0x2355, /* U+002355: APL FUNCTIONAL SYMBOL UP TACK JOT*/
+ 0x2356, /* U+002356: APL FUNCTIONAL SYMBOL DOWNWARDS VANE*/
+ 0x2357, /* U+002357: APL FUNCTIONAL SYMBOL QUAD DOWNWARDS ARROW*/
+ 0x2358, /* U+002358: APL FUNCTIONAL SYMBOL QUOTE UNDERBAR*/
+ 0x2359, /* U+002359: APL FUNCTIONAL SYMBOL DELTA UNDERBAR*/
+ 0x235a, /* U+00235a: APL FUNCTIONAL SYMBOL DIAMOND UNDERBAR*/
+ 0x235b, /* U+00235b: APL FUNCTIONAL SYMBOL JOT UNDERBAR*/
+ 0x235c, /* U+00235c: APL FUNCTIONAL SYMBOL CIRCLE UNDERBAR*/
+ 0x235d, /* U+00235d: APL FUNCTIONAL SYMBOL UP SHOE JOT*/
+ 0x235e, /* U+00235e: APL FUNCTIONAL SYMBOL QUOTE QUAD*/
+ 0x235f, /* U+00235f: APL FUNCTIONAL SYMBOL CIRCLE STAR*/
+ 0x2360, /* U+002360: APL FUNCTIONAL SYMBOL QUAD COLON*/
+ 0x2361, /* U+002361: APL FUNCTIONAL SYMBOL UP TACK DIAERESIS*/
+ 0x2362, /* U+002362: APL FUNCTIONAL SYMBOL DEL DIAERESIS*/
+ 0x2363, /* U+002363: APL FUNCTIONAL SYMBOL STAR DIAERESIS*/
+ 0x2364, /* U+002364: APL FUNCTIONAL SYMBOL JOT DIAERESIS*/
+ 0x2365, /* U+002365: APL FUNCTIONAL SYMBOL CIRCLE DIAERESIS*/
+ 0x2366, /* U+002366: APL FUNCTIONAL SYMBOL DOWN SHOE STILE*/
+ 0x2367, /* U+002367: APL FUNCTIONAL SYMBOL LEFT SHOE STILE*/
+ 0x2368, /* U+002368: APL FUNCTIONAL SYMBOL TILDE DIAERESIS*/
+ 0x2369, /* U+002369: APL FUNCTIONAL SYMBOL GREATER-THAN DIAERESIS*/
+ 0x236a, /* U+00236a: APL FUNCTIONAL SYMBOL COMMA BAR*/
+ 0x236b, /* U+00236b: APL FUNCTIONAL SYMBOL DEL TILDE*/
+ 0x236c, /* U+00236c: APL FUNCTIONAL SYMBOL ZILDE*/
+ 0x236d, /* U+00236d: APL FUNCTIONAL SYMBOL STILE TILDE*/
+ 0x236e, /* U+00236e: APL FUNCTIONAL SYMBOL SEMICOLON UNDERBAR*/
+ 0x236f, /* U+00236f: APL FUNCTIONAL SYMBOL QUAD NOT EQUAL*/
+ 0x2370, /* U+002370: APL FUNCTIONAL SYMBOL QUAD QUESTION*/
+ 0x2371, /* U+002371: APL FUNCTIONAL SYMBOL DOWN CARET TILDE*/
+ 0x2372, /* U+002372: APL FUNCTIONAL SYMBOL UP CARET TILDE*/
+ 0x2373, /* U+002373: APL FUNCTIONAL SYMBOL IOTA*/
+ 0x2374, /* U+002374: APL FUNCTIONAL SYMBOL RHO*/
+ 0x2375, /* U+002375: APL FUNCTIONAL SYMBOL OMEGA*/
+ 0x2376, /* U+002376: APL FUNCTIONAL SYMBOL ALPHA UNDERBAR*/
+ 0x2377, /* U+002377: APL FUNCTIONAL SYMBOL EPSILON UNDERBAR*/
+ 0x2378, /* U+002378: APL FUNCTIONAL SYMBOL IOTA UNDERBAR*/
+ 0x2379, /* U+002379: APL FUNCTIONAL SYMBOL OMEGA UNDERBAR*/
+ 0x237a, /* U+00237a: APL FUNCTIONAL SYMBOL ALPHA*/
+ 0x237b, /* U+00237b: NOT CHECK MARK*/
+ 0x237c, /* U+00237c: RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW*/
+ 0x237d, /* U+00237d: SHOULDERED OPEN BOX*/
+ 0x237e, /* U+00237e: BELL SYMBOL*/
+ 0x237f, /* U+00237f: VERTICAL LINE WITH MIDDLE DOT*/
+ 0x2380, /* U+002380: INSERTION SYMBOL*/
+ 0x2381, /* U+002381: CONTINUOUS UNDERLINE SYMBOL*/
+ 0x2382, /* U+002382: DISCONTINUOUS UNDERLINE SYMBOL*/
+ 0x2383, /* U+002383: EMPHASIS SYMBOL*/
+ 0x2384, /* U+002384: COMPOSITION SYMBOL*/
+ 0x2385, /* U+002385: WHITE SQUARE WITH CENTRE VERTICAL LINE*/
+ 0x2386, /* U+002386: ENTER SYMBOL*/
+ 0x2387, /* U+002387: ALTERNATIVE KEY SYMBOL*/
+ 0x2388, /* U+002388: HELM SYMBOL*/
+ 0x2389, /* U+002389: CIRCLED HORIZONTAL BAR WITH NOTCH*/
+ 0x238a, /* U+00238a: CIRCLED TRIANGLE DOWN*/
+ 0x238b, /* U+00238b: BROKEN CIRCLE WITH NORTHWEST ARROW*/
+ 0x238c, /* U+00238c: UNDO SYMBOL*/
+ 0x238d, /* U+00238d: MONOSTABLE SYMBOL*/
+ 0x238e, /* U+00238e: HYSTERESIS SYMBOL*/
+ 0x238f, /* U+00238f: OPEN-CIRCUIT-OUTPUT H-TYPE SYMBOL*/
+ 0x2390, /* U+002390: OPEN-CIRCUIT-OUTPUT L-TYPE SYMBOL*/
+ 0x2391, /* U+002391: PASSIVE-PULL-DOWN-OUTPUT SYMBOL*/
+ 0x2392, /* U+002392: PASSIVE-PULL-UP-OUTPUT SYMBOL*/
+ 0x2393, /* U+002393: DIRECT CURRENT SYMBOL FORM TWO*/
+ 0x2394, /* U+002394: SOFTWARE-FUNCTION SYMBOL*/
+ 0x2395, /* U+002395: APL FUNCTIONAL SYMBOL QUAD*/
+ 0x2396, /* U+002396: DECIMAL SEPARATOR KEY SYMBOL*/
+ 0x2397, /* U+002397: PREVIOUS PAGE*/
+ 0x2398, /* U+002398: NEXT PAGE*/
+ 0x2399, /* U+002399: PRINT SCREEN SYMBOL*/
+ 0x239a, /* U+00239a: CLEAR SCREEN SYMBOL*/
+ 0x239b, /* U+00239b: LEFT PARENTHESIS UPPER HOOK*/
+ 0x239c, /* U+00239c: LEFT PARENTHESIS EXTENSION*/
+ 0x239d, /* U+00239d: LEFT PARENTHESIS LOWER HOOK*/
+ 0x239e, /* U+00239e: RIGHT PARENTHESIS UPPER HOOK*/
+ 0x239f, /* U+00239f: RIGHT PARENTHESIS EXTENSION*/
+ 0x23a0, /* U+0023a0: RIGHT PARENTHESIS LOWER HOOK*/
+ 0x23a1, /* U+0023a1: LEFT SQUARE BRACKET UPPER CORNER*/
+ 0x23a2, /* U+0023a2: LEFT SQUARE BRACKET EXTENSION*/
+ 0x23a3, /* U+0023a3: LEFT SQUARE BRACKET LOWER CORNER*/
+ 0x23a4, /* U+0023a4: RIGHT SQUARE BRACKET UPPER CORNER*/
+ 0x23a5, /* U+0023a5: RIGHT SQUARE BRACKET EXTENSION*/
+ 0x23a6, /* U+0023a6: RIGHT SQUARE BRACKET LOWER CORNER*/
+ 0x23a7, /* U+0023a7: LEFT CURLY BRACKET UPPER HOOK*/
+ 0x23a8, /* U+0023a8: LEFT CURLY BRACKET MIDDLE PIECE*/
+ 0x23a9, /* U+0023a9: LEFT CURLY BRACKET LOWER HOOK*/
+ 0x23aa, /* U+0023aa: CURLY BRACKET EXTENSION*/
+ 0x23ab, /* U+0023ab: RIGHT CURLY BRACKET UPPER HOOK*/
+ 0x23ac, /* U+0023ac: RIGHT CURLY BRACKET MIDDLE PIECE*/
+ 0x23ad, /* U+0023ad: RIGHT CURLY BRACKET LOWER HOOK*/
+ 0x23ae, /* U+0023ae: INTEGRAL EXTENSION*/
+ 0x23af, /* U+0023af: HORIZONTAL LINE EXTENSION*/
+ 0x23b0, /* U+0023b0: UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION*/
+ 0x23b1, /* U+0023b1: UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION*/
+ 0x23b2, /* U+0023b2: SUMMATION TOP*/
+ 0x23b3, /* U+0023b3: SUMMATION BOTTOM*/
+ 0x23b4, /* U+0023b4: TOP SQUARE BRACKET*/
+ 0x23b5, /* U+0023b5: BOTTOM SQUARE BRACKET*/
+ 0x23b6, /* U+0023b6: BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET*/
+ 0x23b7, /* U+0023b7: RADICAL SYMBOL BOTTOM*/
+ 0x23b8, /* U+0023b8: LEFT VERTICAL BOX LINE*/
+ 0x23b9, /* U+0023b9: RIGHT VERTICAL BOX LINE*/
+ 0x23ba, /* U+0023ba: HORIZONTAL SCAN LINE-1*/
+ 0x23bb, /* U+0023bb: HORIZONTAL SCAN LINE-3*/
+ 0x23bc, /* U+0023bc: HORIZONTAL SCAN LINE-7*/
+ 0x23bd, /* U+0023bd: HORIZONTAL SCAN LINE-9*/
+ 0x23be, /* U+0023be: DENTISTRY SYMBOL LIGHT VERTICAL AND TOP RIGHT*/
+ 0x23bf, /* U+0023bf: DENTISTRY SYMBOL LIGHT VERTICAL AND BOTTOM RIGHT*/
+ 0x23c0, /* U+0023c0: DENTISTRY SYMBOL LIGHT VERTICAL WITH CIRCLE*/
+ 0x23c1, /* U+0023c1: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH CIRCLE*/
+ 0x23c2, /* U+0023c2: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH CIRCLE*/
+ 0x23c3, /* U+0023c3: DENTISTRY SYMBOL LIGHT VERTICAL WITH TRIANGLE*/
+ 0x23c4, /* U+0023c4: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH TRIANGLE*/
+ 0x23c5, /* U+0023c5: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH TRIANGLE*/
+ 0x23c6, /* U+0023c6: DENTISTRY SYMBOL LIGHT VERTICAL AND WAVE*/
+ 0x23c7, /* U+0023c7: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL WITH WAVE*/
+ 0x23c8, /* U+0023c8: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL WITH WAVE*/
+ 0x23c9, /* U+0023c9: DENTISTRY SYMBOL LIGHT DOWN AND HORIZONTAL*/
+ 0x23ca, /* U+0023ca: DENTISTRY SYMBOL LIGHT UP AND HORIZONTAL*/
+ 0x23cb, /* U+0023cb: DENTISTRY SYMBOL LIGHT VERTICAL AND TOP LEFT*/
+ 0x23cc, /* U+0023cc: DENTISTRY SYMBOL LIGHT VERTICAL AND BOTTOM LEFT*/
+ 0x23cd, /* U+0023cd: SQUARE FOOT*/
+ 0x23ce, /* U+0023ce: RETURN SYMBOL*/
+ 0x23cf, /* U+0023cf: EJECT SYMBOL*/
+ 0x23d0, /* U+0023d0: VERTICAL LINE EXTENSION*/
+ 0x23d1, /* U+0023d1: METRICAL BREVE*/
+ 0x23d2, /* U+0023d2: METRICAL LONG OVER SHORT*/
+ 0x23d3, /* U+0023d3: METRICAL SHORT OVER LONG*/
+ 0x23d4, /* U+0023d4: METRICAL LONG OVER TWO SHORTS*/
+ 0x23d5, /* U+0023d5: METRICAL TWO SHORTS OVER LONG*/
+ 0x23d6, /* U+0023d6: METRICAL TWO SHORTS JOINED*/
+ 0x23d7, /* U+0023d7: METRICAL TRISEME*/
+ 0x23d8, /* U+0023d8: METRICAL TETRASEME*/
+ 0x23d9, /* U+0023d9: METRICAL PENTASEME*/
+ 0x23da, /* U+0023da: EARTH GROUND*/
+ 0x23db, /* U+0023db: FUSE*/
+ 0x23dc, /* U+0023dc: TOP PARENTHESIS*/
+ 0x23dd, /* U+0023dd: BOTTOM PARENTHESIS*/
+ 0x23de, /* U+0023de: TOP CURLY BRACKET*/
+ 0x23df, /* U+0023df: BOTTOM CURLY BRACKET*/
+ 0x23e0, /* U+0023e0: TOP TORTOISE SHELL BRACKET*/
+ 0x23e1, /* U+0023e1: BOTTOM TORTOISE SHELL BRACKET*/
+ 0x23e2, /* U+0023e2: WHITE TRAPEZIUM*/
+ 0x23e3, /* U+0023e3: BENZENE RING WITH CIRCLE*/
+ 0x23e4, /* U+0023e4: STRAIGHTNESS*/
+ 0x23e5, /* U+0023e5: FLATNESS*/
+ 0x23e6, /* U+0023e6: AC CURRENT*/
+ 0x23e7, /* U+0023e7: ELECTRICAL INTERSECTION*/
+ 0x23e8, /* U+0023e8: DECIMAL EXPONENT SYMBOL*/
+ 0x23e9, /* U+0023e9: BLACK RIGHT-POINTING DOUBLE TRIANGLE*/
+ 0x23ea, /* U+0023ea: BLACK LEFT-POINTING DOUBLE TRIANGLE*/
+ 0x23eb, /* U+0023eb: BLACK UP-POINTING DOUBLE TRIANGLE*/
+ 0x23ec, /* U+0023ec: BLACK DOWN-POINTING DOUBLE TRIANGLE*/
+ 0x23ed, /* U+0023ed: BLACK RIGHT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR*/
+ 0x23ee, /* U+0023ee: BLACK LEFT-POINTING DOUBLE TRIANGLE WITH VERTICAL BAR*/
+ 0x23ef, /* U+0023ef: BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR*/
+ 0x23f0, /* U+0023f0: ALARM CLOCK*/
+ 0x23f1, /* U+0023f1: STOPWATCH*/
+ 0x23f2, /* U+0023f2: TIMER CLOCK*/
+ 0x23f3, /* U+0023f3: HOURGLASS WITH FLOWING SAND*/
+ 0x23f4, /* U+0023f4: */
+ 0x23f5, /* U+0023f5: */
+ 0x23f6, /* U+0023f6: */
+ 0x23f7, /* U+0023f7: */
+ 0x23f8, /* U+0023f8: */
+ 0x23f9, /* U+0023f9: */
+ 0x23fa, /* U+0023fa: */
+ 0x23fb, /* U+0023fb: */
+ 0x23fc, /* U+0023fc: */
+ 0x23fd, /* U+0023fd: */
+ 0x23fe, /* U+0023fe: */
+ 0x23ff, /* U+0023ff: */
+ 0x2400, /* U+002400: SYMBOL FOR NULL*/
+ 0x2401, /* U+002401: SYMBOL FOR START OF HEADING*/
+ 0x2402, /* U+002402: SYMBOL FOR START OF TEXT*/
+ 0x2403, /* U+002403: SYMBOL FOR END OF TEXT*/
+ 0x2404, /* U+002404: SYMBOL FOR END OF TRANSMISSION*/
+ 0x2405, /* U+002405: SYMBOL FOR ENQUIRY*/
+ 0x2406, /* U+002406: SYMBOL FOR ACKNOWLEDGE*/
+ 0x2407, /* U+002407: SYMBOL FOR BELL*/
+ 0x2408, /* U+002408: SYMBOL FOR BACKSPACE*/
+ 0x2409, /* U+002409: SYMBOL FOR HORIZONTAL TABULATION*/
+ 0x240a, /* U+00240a: SYMBOL FOR LINE FEED*/
+ 0x240b, /* U+00240b: SYMBOL FOR VERTICAL TABULATION*/
+ 0x240c, /* U+00240c: SYMBOL FOR FORM FEED*/
+ 0x240d, /* U+00240d: SYMBOL FOR CARRIAGE RETURN*/
+ 0x240e, /* U+00240e: SYMBOL FOR SHIFT OUT*/
+ 0x240f, /* U+00240f: SYMBOL FOR SHIFT IN*/
+ 0x2410, /* U+002410: SYMBOL FOR DATA LINK ESCAPE*/
+ 0x2411, /* U+002411: SYMBOL FOR DEVICE CONTROL ONE*/
+ 0x2412, /* U+002412: SYMBOL FOR DEVICE CONTROL TWO*/
+ 0x2413, /* U+002413: SYMBOL FOR DEVICE CONTROL THREE*/
+ 0x2414, /* U+002414: SYMBOL FOR DEVICE CONTROL FOUR*/
+ 0x2415, /* U+002415: SYMBOL FOR NEGATIVE ACKNOWLEDGE*/
+ 0x2416, /* U+002416: SYMBOL FOR SYNCHRONOUS IDLE*/
+ 0x2417, /* U+002417: SYMBOL FOR END OF TRANSMISSION BLOCK*/
+ 0x2418, /* U+002418: SYMBOL FOR CANCEL*/
+ 0x2419, /* U+002419: SYMBOL FOR END OF MEDIUM*/
+ 0x241a, /* U+00241a: SYMBOL FOR SUBSTITUTE*/
+ 0x241b, /* U+00241b: SYMBOL FOR ESCAPE*/
+ 0x241c, /* U+00241c: SYMBOL FOR FILE SEPARATOR*/
+ 0x241d, /* U+00241d: SYMBOL FOR GROUP SEPARATOR*/
+ 0x241e, /* U+00241e: SYMBOL FOR RECORD SEPARATOR*/
+ 0x241f, /* U+00241f: SYMBOL FOR UNIT SEPARATOR*/
+ 0x2420, /* U+002420: SYMBOL FOR SPACE*/
+ 0x2421, /* U+002421: SYMBOL FOR DELETE*/
+ 0x2422, /* U+002422: BLANK SYMBOL*/
+ 0x2423, /* U+002423: OPEN BOX*/
+ 0x2424, /* U+002424: SYMBOL FOR NEWLINE*/
+ 0x2425, /* U+002425: SYMBOL FOR DELETE FORM TWO*/
+ 0x2426, /* U+002426: SYMBOL FOR SUBSTITUTE FORM TWO*/
+ 0x2427, /* U+002427: */
+ 0x2428, /* U+002428: */
+ 0x2429, /* U+002429: */
+ 0x242a, /* U+00242a: */
+ 0x242b, /* U+00242b: */
+ 0x242c, /* U+00242c: */
+ 0x242d, /* U+00242d: */
+ 0x242e, /* U+00242e: */
+ 0x242f, /* U+00242f: */
+ 0x2430, /* U+002430: */
+ 0x2431, /* U+002431: */
+ 0x2432, /* U+002432: */
+ 0x2433, /* U+002433: */
+ 0x2434, /* U+002434: */
+ 0x2435, /* U+002435: */
+ 0x2436, /* U+002436: */
+ 0x2437, /* U+002437: */
+ 0x2438, /* U+002438: */
+ 0x2439, /* U+002439: */
+ 0x243a, /* U+00243a: */
+ 0x243b, /* U+00243b: */
+ 0x243c, /* U+00243c: */
+ 0x243d, /* U+00243d: */
+ 0x243e, /* U+00243e: */
+ 0x243f, /* U+00243f: */
+ 0x2440, /* U+002440: OCR HOOK*/
+ 0x2441, /* U+002441: OCR CHAIR*/
+ 0x2442, /* U+002442: OCR FORK*/
+ 0x2443, /* U+002443: OCR INVERTED FORK*/
+ 0x2444, /* U+002444: OCR BELT BUCKLE*/
+ 0x2445, /* U+002445: OCR BOW TIE*/
+ 0x2446, /* U+002446: OCR BRANCH BANK IDENTIFICATION*/
+ 0x2447, /* U+002447: OCR AMOUNT OF CHECK*/
+ 0x2448, /* U+002448: OCR DASH*/
+ 0x2449, /* U+002449: OCR CUSTOMER ACCOUNT NUMBER*/
+ 0x244a, /* U+00244a: OCR DOUBLE BACKSLASH*/
+ 0x244b, /* U+00244b: */
+ 0x244c, /* U+00244c: */
+ 0x244d, /* U+00244d: */
+ 0x244e, /* U+00244e: */
+ 0x244f, /* U+00244f: */
+ 0x2450, /* U+002450: */
+ 0x2451, /* U+002451: */
+ 0x2452, /* U+002452: */
+ 0x2453, /* U+002453: */
+ 0x2454, /* U+002454: */
+ 0x2455, /* U+002455: */
+ 0x2456, /* U+002456: */
+ 0x2457, /* U+002457: */
+ 0x2458, /* U+002458: */
+ 0x2459, /* U+002459: */
+ 0x245a, /* U+00245a: */
+ 0x245b, /* U+00245b: */
+ 0x245c, /* U+00245c: */
+ 0x245d, /* U+00245d: */
+ 0x245e, /* U+00245e: */
+ 0x245f, /* U+00245f: */
+ 0x2460, /* U+002460: CIRCLED DIGIT ONE*/
+ 0x2461, /* U+002461: CIRCLED DIGIT TWO*/
+ 0x2462, /* U+002462: CIRCLED DIGIT THREE*/
+ 0x2463, /* U+002463: CIRCLED DIGIT FOUR*/
+ 0x2464, /* U+002464: CIRCLED DIGIT FIVE*/
+ 0x2465, /* U+002465: CIRCLED DIGIT SIX*/
+ 0x2466, /* U+002466: CIRCLED DIGIT SEVEN*/
+ 0x2467, /* U+002467: CIRCLED DIGIT EIGHT*/
+ 0x2468, /* U+002468: CIRCLED DIGIT NINE*/
+ 0x2469, /* U+002469: CIRCLED NUMBER TEN*/
+ 0x246a, /* U+00246a: CIRCLED NUMBER ELEVEN*/
+ 0x246b, /* U+00246b: CIRCLED NUMBER TWELVE*/
+ 0x246c, /* U+00246c: CIRCLED NUMBER THIRTEEN*/
+ 0x246d, /* U+00246d: CIRCLED NUMBER FOURTEEN*/
+ 0x246e, /* U+00246e: CIRCLED NUMBER FIFTEEN*/
+ 0x246f, /* U+00246f: CIRCLED NUMBER SIXTEEN*/
+ 0x2470, /* U+002470: CIRCLED NUMBER SEVENTEEN*/
+ 0x2471, /* U+002471: CIRCLED NUMBER EIGHTEEN*/
+ 0x2472, /* U+002472: CIRCLED NUMBER NINETEEN*/
+ 0x2473, /* U+002473: CIRCLED NUMBER TWENTY*/
+ 0x2474, /* U+002474: PARENTHESIZED DIGIT ONE*/
+ 0x2475, /* U+002475: PARENTHESIZED DIGIT TWO*/
+ 0x2476, /* U+002476: PARENTHESIZED DIGIT THREE*/
+ 0x2477, /* U+002477: PARENTHESIZED DIGIT FOUR*/
+ 0x2478, /* U+002478: PARENTHESIZED DIGIT FIVE*/
+ 0x2479, /* U+002479: PARENTHESIZED DIGIT SIX*/
+ 0x247a, /* U+00247a: PARENTHESIZED DIGIT SEVEN*/
+ 0x247b, /* U+00247b: PARENTHESIZED DIGIT EIGHT*/
+ 0x247c, /* U+00247c: PARENTHESIZED DIGIT NINE*/
+ 0x247d, /* U+00247d: PARENTHESIZED NUMBER TEN*/
+ 0x247e, /* U+00247e: PARENTHESIZED NUMBER ELEVEN*/
+ 0x247f, /* U+00247f: PARENTHESIZED NUMBER TWELVE*/
+ 0x2480, /* U+002480: PARENTHESIZED NUMBER THIRTEEN*/
+ 0x2481, /* U+002481: PARENTHESIZED NUMBER FOURTEEN*/
+ 0x2482, /* U+002482: PARENTHESIZED NUMBER FIFTEEN*/
+ 0x2483, /* U+002483: PARENTHESIZED NUMBER SIXTEEN*/
+ 0x2484, /* U+002484: PARENTHESIZED NUMBER SEVENTEEN*/
+ 0x2485, /* U+002485: PARENTHESIZED NUMBER EIGHTEEN*/
+ 0x2486, /* U+002486: PARENTHESIZED NUMBER NINETEEN*/
+ 0x2487, /* U+002487: PARENTHESIZED NUMBER TWENTY*/
+ 0x2488, /* U+002488: DIGIT ONE FULL STOP*/
+ 0x2489, /* U+002489: DIGIT TWO FULL STOP*/
+ 0x248a, /* U+00248a: DIGIT THREE FULL STOP*/
+ 0x248b, /* U+00248b: DIGIT FOUR FULL STOP*/
+ 0x248c, /* U+00248c: DIGIT FIVE FULL STOP*/
+ 0x248d, /* U+00248d: DIGIT SIX FULL STOP*/
+ 0x248e, /* U+00248e: DIGIT SEVEN FULL STOP*/
+ 0x248f, /* U+00248f: DIGIT EIGHT FULL STOP*/
+ 0x2490, /* U+002490: DIGIT NINE FULL STOP*/
+ 0x2491, /* U+002491: NUMBER TEN FULL STOP*/
+ 0x2492, /* U+002492: NUMBER ELEVEN FULL STOP*/
+ 0x2493, /* U+002493: NUMBER TWELVE FULL STOP*/
+ 0x2494, /* U+002494: NUMBER THIRTEEN FULL STOP*/
+ 0x2495, /* U+002495: NUMBER FOURTEEN FULL STOP*/
+ 0x2496, /* U+002496: NUMBER FIFTEEN FULL STOP*/
+ 0x2497, /* U+002497: NUMBER SIXTEEN FULL STOP*/
+ 0x2498, /* U+002498: NUMBER SEVENTEEN FULL STOP*/
+ 0x2499, /* U+002499: NUMBER EIGHTEEN FULL STOP*/
+ 0x249a, /* U+00249a: NUMBER NINETEEN FULL STOP*/
+ 0x249b, /* U+00249b: NUMBER TWENTY FULL STOP*/
+ 0x249c, /* U+00249c: PARENTHESIZED LATIN SMALL LETTER A*/
+ 0x249d, /* U+00249d: PARENTHESIZED LATIN SMALL LETTER B*/
+ 0x249e, /* U+00249e: PARENTHESIZED LATIN SMALL LETTER C*/
+ 0x249f, /* U+00249f: PARENTHESIZED LATIN SMALL LETTER D*/
+ 0x24a0, /* U+0024a0: PARENTHESIZED LATIN SMALL LETTER E*/
+ 0x24a1, /* U+0024a1: PARENTHESIZED LATIN SMALL LETTER F*/
+ 0x24a2, /* U+0024a2: PARENTHESIZED LATIN SMALL LETTER G*/
+ 0x24a3, /* U+0024a3: PARENTHESIZED LATIN SMALL LETTER H*/
+ 0x24a4, /* U+0024a4: PARENTHESIZED LATIN SMALL LETTER I*/
+ 0x24a5, /* U+0024a5: PARENTHESIZED LATIN SMALL LETTER J*/
+ 0x24a6, /* U+0024a6: PARENTHESIZED LATIN SMALL LETTER K*/
+ 0x24a7, /* U+0024a7: PARENTHESIZED LATIN SMALL LETTER L*/
+ 0x24a8, /* U+0024a8: PARENTHESIZED LATIN SMALL LETTER M*/
+ 0x24a9, /* U+0024a9: PARENTHESIZED LATIN SMALL LETTER N*/
+ 0x24aa, /* U+0024aa: PARENTHESIZED LATIN SMALL LETTER O*/
+ 0x24ab, /* U+0024ab: PARENTHESIZED LATIN SMALL LETTER P*/
+ 0x24ac, /* U+0024ac: PARENTHESIZED LATIN SMALL LETTER Q*/
+ 0x24ad, /* U+0024ad: PARENTHESIZED LATIN SMALL LETTER R*/
+ 0x24ae, /* U+0024ae: PARENTHESIZED LATIN SMALL LETTER S*/
+ 0x24af, /* U+0024af: PARENTHESIZED LATIN SMALL LETTER T*/
+ 0x24b0, /* U+0024b0: PARENTHESIZED LATIN SMALL LETTER U*/
+ 0x24b1, /* U+0024b1: PARENTHESIZED LATIN SMALL LETTER V*/
+ 0x24b2, /* U+0024b2: PARENTHESIZED LATIN SMALL LETTER W*/
+ 0x24b3, /* U+0024b3: PARENTHESIZED LATIN SMALL LETTER X*/
+ 0x24b4, /* U+0024b4: PARENTHESIZED LATIN SMALL LETTER Y*/
+ 0x24b5, /* U+0024b5: PARENTHESIZED LATIN SMALL LETTER Z*/
+ 0x24b6, /* U+0024b6: CIRCLED LATIN CAPITAL LETTER A*/
+ 0x24b7, /* U+0024b7: CIRCLED LATIN CAPITAL LETTER B*/
+ 0x24b8, /* U+0024b8: CIRCLED LATIN CAPITAL LETTER C*/
+ 0x24b9, /* U+0024b9: CIRCLED LATIN CAPITAL LETTER D*/
+ 0x24ba, /* U+0024ba: CIRCLED LATIN CAPITAL LETTER E*/
+ 0x24bb, /* U+0024bb: CIRCLED LATIN CAPITAL LETTER F*/
+ 0x24bc, /* U+0024bc: CIRCLED LATIN CAPITAL LETTER G*/
+ 0x24bd, /* U+0024bd: CIRCLED LATIN CAPITAL LETTER H*/
+ 0x24be, /* U+0024be: CIRCLED LATIN CAPITAL LETTER I*/
+ 0x24bf, /* U+0024bf: CIRCLED LATIN CAPITAL LETTER J*/
+ 0x24c0, /* U+0024c0: CIRCLED LATIN CAPITAL LETTER K*/
+ 0x24c1, /* U+0024c1: CIRCLED LATIN CAPITAL LETTER L*/
+ 0x24c2, /* U+0024c2: CIRCLED LATIN CAPITAL LETTER M*/
+ 0x24c3, /* U+0024c3: CIRCLED LATIN CAPITAL LETTER N*/
+ 0x24c4, /* U+0024c4: CIRCLED LATIN CAPITAL LETTER O*/
+ 0x24c5, /* U+0024c5: CIRCLED LATIN CAPITAL LETTER P*/
+ 0x24c6, /* U+0024c6: CIRCLED LATIN CAPITAL LETTER Q*/
+ 0x24c7, /* U+0024c7: CIRCLED LATIN CAPITAL LETTER R*/
+ 0x24c8, /* U+0024c8: CIRCLED LATIN CAPITAL LETTER S*/
+ 0x24c9, /* U+0024c9: CIRCLED LATIN CAPITAL LETTER T*/
+ 0x24ca, /* U+0024ca: CIRCLED LATIN CAPITAL LETTER U*/
+ 0x24cb, /* U+0024cb: CIRCLED LATIN CAPITAL LETTER V*/
+ 0x24cc, /* U+0024cc: CIRCLED LATIN CAPITAL LETTER W*/
+ 0x24cd, /* U+0024cd: CIRCLED LATIN CAPITAL LETTER X*/
+ 0x24ce, /* U+0024ce: CIRCLED LATIN CAPITAL LETTER Y*/
+ 0x24cf, /* U+0024cf: CIRCLED LATIN CAPITAL LETTER Z*/
+ 0x24b6, /* U+0024d0: CIRCLED LATIN SMALL LETTER A*/
+ 0x24b7, /* U+0024d1: CIRCLED LATIN SMALL LETTER B*/
+ 0x24b8, /* U+0024d2: CIRCLED LATIN SMALL LETTER C*/
+ 0x24b9, /* U+0024d3: CIRCLED LATIN SMALL LETTER D*/
+ 0x24ba, /* U+0024d4: CIRCLED LATIN SMALL LETTER E*/
+ 0x24bb, /* U+0024d5: CIRCLED LATIN SMALL LETTER F*/
+ 0x24bc, /* U+0024d6: CIRCLED LATIN SMALL LETTER G*/
+ 0x24bd, /* U+0024d7: CIRCLED LATIN SMALL LETTER H*/
+ 0x24be, /* U+0024d8: CIRCLED LATIN SMALL LETTER I*/
+ 0x24bf, /* U+0024d9: CIRCLED LATIN SMALL LETTER J*/
+ 0x24c0, /* U+0024da: CIRCLED LATIN SMALL LETTER K*/
+ 0x24c1, /* U+0024db: CIRCLED LATIN SMALL LETTER L*/
+ 0x24c2, /* U+0024dc: CIRCLED LATIN SMALL LETTER M*/
+ 0x24c3, /* U+0024dd: CIRCLED LATIN SMALL LETTER N*/
+ 0x24c4, /* U+0024de: CIRCLED LATIN SMALL LETTER O*/
+ 0x24c5, /* U+0024df: CIRCLED LATIN SMALL LETTER P*/
+ 0x24c6, /* U+0024e0: CIRCLED LATIN SMALL LETTER Q*/
+ 0x24c7, /* U+0024e1: CIRCLED LATIN SMALL LETTER R*/
+ 0x24c8, /* U+0024e2: CIRCLED LATIN SMALL LETTER S*/
+ 0x24c9, /* U+0024e3: CIRCLED LATIN SMALL LETTER T*/
+ 0x24ca, /* U+0024e4: CIRCLED LATIN SMALL LETTER U*/
+ 0x24cb, /* U+0024e5: CIRCLED LATIN SMALL LETTER V*/
+ 0x24cc, /* U+0024e6: CIRCLED LATIN SMALL LETTER W*/
+ 0x24cd, /* U+0024e7: CIRCLED LATIN SMALL LETTER X*/
+ 0x24ce, /* U+0024e8: CIRCLED LATIN SMALL LETTER Y*/
+ 0x24cf, /* U+0024e9: CIRCLED LATIN SMALL LETTER Z*/
+};
+
+static const RTUNICP g_afRTUniUpper0x002c30[] =
+{
+ 0x2c00, /* U+002c30: GLAGOLITIC SMALL LETTER AZU*/
+ 0x2c01, /* U+002c31: GLAGOLITIC SMALL LETTER BUKY*/
+ 0x2c02, /* U+002c32: GLAGOLITIC SMALL LETTER VEDE*/
+ 0x2c03, /* U+002c33: GLAGOLITIC SMALL LETTER GLAGOLI*/
+ 0x2c04, /* U+002c34: GLAGOLITIC SMALL LETTER DOBRO*/
+ 0x2c05, /* U+002c35: GLAGOLITIC SMALL LETTER YESTU*/
+ 0x2c06, /* U+002c36: GLAGOLITIC SMALL LETTER ZHIVETE*/
+ 0x2c07, /* U+002c37: GLAGOLITIC SMALL LETTER DZELO*/
+ 0x2c08, /* U+002c38: GLAGOLITIC SMALL LETTER ZEMLJA*/
+ 0x2c09, /* U+002c39: GLAGOLITIC SMALL LETTER IZHE*/
+ 0x2c0a, /* U+002c3a: GLAGOLITIC SMALL LETTER INITIAL IZHE*/
+ 0x2c0b, /* U+002c3b: GLAGOLITIC SMALL LETTER I*/
+ 0x2c0c, /* U+002c3c: GLAGOLITIC SMALL LETTER DJERVI*/
+ 0x2c0d, /* U+002c3d: GLAGOLITIC SMALL LETTER KAKO*/
+ 0x2c0e, /* U+002c3e: GLAGOLITIC SMALL LETTER LJUDIJE*/
+ 0x2c0f, /* U+002c3f: GLAGOLITIC SMALL LETTER MYSLITE*/
+ 0x2c10, /* U+002c40: GLAGOLITIC SMALL LETTER NASHI*/
+ 0x2c11, /* U+002c41: GLAGOLITIC SMALL LETTER ONU*/
+ 0x2c12, /* U+002c42: GLAGOLITIC SMALL LETTER POKOJI*/
+ 0x2c13, /* U+002c43: GLAGOLITIC SMALL LETTER RITSI*/
+ 0x2c14, /* U+002c44: GLAGOLITIC SMALL LETTER SLOVO*/
+ 0x2c15, /* U+002c45: GLAGOLITIC SMALL LETTER TVRIDO*/
+ 0x2c16, /* U+002c46: GLAGOLITIC SMALL LETTER UKU*/
+ 0x2c17, /* U+002c47: GLAGOLITIC SMALL LETTER FRITU*/
+ 0x2c18, /* U+002c48: GLAGOLITIC SMALL LETTER HERU*/
+ 0x2c19, /* U+002c49: GLAGOLITIC SMALL LETTER OTU*/
+ 0x2c1a, /* U+002c4a: GLAGOLITIC SMALL LETTER PE*/
+ 0x2c1b, /* U+002c4b: GLAGOLITIC SMALL LETTER SHTA*/
+ 0x2c1c, /* U+002c4c: GLAGOLITIC SMALL LETTER TSI*/
+ 0x2c1d, /* U+002c4d: GLAGOLITIC SMALL LETTER CHRIVI*/
+ 0x2c1e, /* U+002c4e: GLAGOLITIC SMALL LETTER SHA*/
+ 0x2c1f, /* U+002c4f: GLAGOLITIC SMALL LETTER YERU*/
+ 0x2c20, /* U+002c50: GLAGOLITIC SMALL LETTER YERI*/
+ 0x2c21, /* U+002c51: GLAGOLITIC SMALL LETTER YATI*/
+ 0x2c22, /* U+002c52: GLAGOLITIC SMALL LETTER SPIDERY HA*/
+ 0x2c23, /* U+002c53: GLAGOLITIC SMALL LETTER YU*/
+ 0x2c24, /* U+002c54: GLAGOLITIC SMALL LETTER SMALL YUS*/
+ 0x2c25, /* U+002c55: GLAGOLITIC SMALL LETTER SMALL YUS WITH TAIL*/
+ 0x2c26, /* U+002c56: GLAGOLITIC SMALL LETTER YO*/
+ 0x2c27, /* U+002c57: GLAGOLITIC SMALL LETTER IOTATED SMALL YUS*/
+ 0x2c28, /* U+002c58: GLAGOLITIC SMALL LETTER BIG YUS*/
+ 0x2c29, /* U+002c59: GLAGOLITIC SMALL LETTER IOTATED BIG YUS*/
+ 0x2c2a, /* U+002c5a: GLAGOLITIC SMALL LETTER FITA*/
+ 0x2c2b, /* U+002c5b: GLAGOLITIC SMALL LETTER IZHITSA*/
+ 0x2c2c, /* U+002c5c: GLAGOLITIC SMALL LETTER SHTAPIC*/
+ 0x2c2d, /* U+002c5d: GLAGOLITIC SMALL LETTER TROKUTASTI A*/
+ 0x2c2e, /* U+002c5e: GLAGOLITIC SMALL LETTER LATINATE MYSLITE*/
+ 0x2c5f, /* U+002c5f: */
+ 0x2c60, /* U+002c60: LATIN CAPITAL LETTER L WITH DOUBLE BAR*/
+ 0x2c60, /* U+002c61: LATIN SMALL LETTER L WITH DOUBLE BAR*/
+ 0x2c62, /* U+002c62: LATIN CAPITAL LETTER L WITH MIDDLE TILDE*/
+ 0x2c63, /* U+002c63: LATIN CAPITAL LETTER P WITH STROKE*/
+ 0x2c64, /* U+002c64: LATIN CAPITAL LETTER R WITH TAIL*/
+ 0x23a, /* U+002c65: LATIN SMALL LETTER A WITH STROKE*/
+ 0x23e, /* U+002c66: LATIN SMALL LETTER T WITH DIAGONAL STROKE*/
+ 0x2c67, /* U+002c67: LATIN CAPITAL LETTER H WITH DESCENDER*/
+ 0x2c67, /* U+002c68: LATIN SMALL LETTER H WITH DESCENDER*/
+ 0x2c69, /* U+002c69: LATIN CAPITAL LETTER K WITH DESCENDER*/
+ 0x2c69, /* U+002c6a: LATIN SMALL LETTER K WITH DESCENDER*/
+ 0x2c6b, /* U+002c6b: LATIN CAPITAL LETTER Z WITH DESCENDER*/
+ 0x2c6b, /* U+002c6c: LATIN SMALL LETTER Z WITH DESCENDER*/
+ 0x2c6d, /* U+002c6d: LATIN CAPITAL LETTER ALPHA*/
+ 0x2c6e, /* U+002c6e: LATIN CAPITAL LETTER M WITH HOOK*/
+ 0x2c6f, /* U+002c6f: LATIN CAPITAL LETTER TURNED A*/
+ 0x2c70, /* U+002c70: LATIN CAPITAL LETTER TURNED ALPHA*/
+ 0x2c71, /* U+002c71: LATIN SMALL LETTER V WITH RIGHT HOOK*/
+ 0x2c72, /* U+002c72: LATIN CAPITAL LETTER W WITH HOOK*/
+ 0x2c72, /* U+002c73: LATIN SMALL LETTER W WITH HOOK*/
+ 0x2c74, /* U+002c74: LATIN SMALL LETTER V WITH CURL*/
+ 0x2c75, /* U+002c75: LATIN CAPITAL LETTER HALF H*/
+ 0x2c75, /* U+002c76: LATIN SMALL LETTER HALF H*/
+ 0x2c77, /* U+002c77: LATIN SMALL LETTER TAILLESS PHI*/
+ 0x2c78, /* U+002c78: LATIN SMALL LETTER E WITH NOTCH*/
+ 0x2c79, /* U+002c79: LATIN SMALL LETTER TURNED R WITH TAIL*/
+ 0x2c7a, /* U+002c7a: LATIN SMALL LETTER O WITH LOW RING INSIDE*/
+ 0x2c7b, /* U+002c7b: LATIN LETTER SMALL CAPITAL TURNED E*/
+ 0x2c7c, /* U+002c7c: LATIN SUBSCRIPT SMALL LETTER J*/
+ 0x2c7d, /* U+002c7d: MODIFIER LETTER CAPITAL V*/
+ 0x2c7e, /* U+002c7e: LATIN CAPITAL LETTER S WITH SWASH TAIL*/
+ 0x2c7f, /* U+002c7f: LATIN CAPITAL LETTER Z WITH SWASH TAIL*/
+ 0x2c80, /* U+002c80: COPTIC CAPITAL LETTER ALFA*/
+ 0x2c80, /* U+002c81: COPTIC SMALL LETTER ALFA*/
+ 0x2c82, /* U+002c82: COPTIC CAPITAL LETTER VIDA*/
+ 0x2c82, /* U+002c83: COPTIC SMALL LETTER VIDA*/
+ 0x2c84, /* U+002c84: COPTIC CAPITAL LETTER GAMMA*/
+ 0x2c84, /* U+002c85: COPTIC SMALL LETTER GAMMA*/
+ 0x2c86, /* U+002c86: COPTIC CAPITAL LETTER DALDA*/
+ 0x2c86, /* U+002c87: COPTIC SMALL LETTER DALDA*/
+ 0x2c88, /* U+002c88: COPTIC CAPITAL LETTER EIE*/
+ 0x2c88, /* U+002c89: COPTIC SMALL LETTER EIE*/
+ 0x2c8a, /* U+002c8a: COPTIC CAPITAL LETTER SOU*/
+ 0x2c8a, /* U+002c8b: COPTIC SMALL LETTER SOU*/
+ 0x2c8c, /* U+002c8c: COPTIC CAPITAL LETTER ZATA*/
+ 0x2c8c, /* U+002c8d: COPTIC SMALL LETTER ZATA*/
+ 0x2c8e, /* U+002c8e: COPTIC CAPITAL LETTER HATE*/
+ 0x2c8e, /* U+002c8f: COPTIC SMALL LETTER HATE*/
+ 0x2c90, /* U+002c90: COPTIC CAPITAL LETTER THETHE*/
+ 0x2c90, /* U+002c91: COPTIC SMALL LETTER THETHE*/
+ 0x2c92, /* U+002c92: COPTIC CAPITAL LETTER IAUDA*/
+ 0x2c92, /* U+002c93: COPTIC SMALL LETTER IAUDA*/
+ 0x2c94, /* U+002c94: COPTIC CAPITAL LETTER KAPA*/
+ 0x2c94, /* U+002c95: COPTIC SMALL LETTER KAPA*/
+ 0x2c96, /* U+002c96: COPTIC CAPITAL LETTER LAULA*/
+ 0x2c96, /* U+002c97: COPTIC SMALL LETTER LAULA*/
+ 0x2c98, /* U+002c98: COPTIC CAPITAL LETTER MI*/
+ 0x2c98, /* U+002c99: COPTIC SMALL LETTER MI*/
+ 0x2c9a, /* U+002c9a: COPTIC CAPITAL LETTER NI*/
+ 0x2c9a, /* U+002c9b: COPTIC SMALL LETTER NI*/
+ 0x2c9c, /* U+002c9c: COPTIC CAPITAL LETTER KSI*/
+ 0x2c9c, /* U+002c9d: COPTIC SMALL LETTER KSI*/
+ 0x2c9e, /* U+002c9e: COPTIC CAPITAL LETTER O*/
+ 0x2c9e, /* U+002c9f: COPTIC SMALL LETTER O*/
+ 0x2ca0, /* U+002ca0: COPTIC CAPITAL LETTER PI*/
+ 0x2ca0, /* U+002ca1: COPTIC SMALL LETTER PI*/
+ 0x2ca2, /* U+002ca2: COPTIC CAPITAL LETTER RO*/
+ 0x2ca2, /* U+002ca3: COPTIC SMALL LETTER RO*/
+ 0x2ca4, /* U+002ca4: COPTIC CAPITAL LETTER SIMA*/
+ 0x2ca4, /* U+002ca5: COPTIC SMALL LETTER SIMA*/
+ 0x2ca6, /* U+002ca6: COPTIC CAPITAL LETTER TAU*/
+ 0x2ca6, /* U+002ca7: COPTIC SMALL LETTER TAU*/
+ 0x2ca8, /* U+002ca8: COPTIC CAPITAL LETTER UA*/
+ 0x2ca8, /* U+002ca9: COPTIC SMALL LETTER UA*/
+ 0x2caa, /* U+002caa: COPTIC CAPITAL LETTER FI*/
+ 0x2caa, /* U+002cab: COPTIC SMALL LETTER FI*/
+ 0x2cac, /* U+002cac: COPTIC CAPITAL LETTER KHI*/
+ 0x2cac, /* U+002cad: COPTIC SMALL LETTER KHI*/
+ 0x2cae, /* U+002cae: COPTIC CAPITAL LETTER PSI*/
+ 0x2cae, /* U+002caf: COPTIC SMALL LETTER PSI*/
+ 0x2cb0, /* U+002cb0: COPTIC CAPITAL LETTER OOU*/
+ 0x2cb0, /* U+002cb1: COPTIC SMALL LETTER OOU*/
+ 0x2cb2, /* U+002cb2: COPTIC CAPITAL LETTER DIALECT-P ALEF*/
+ 0x2cb2, /* U+002cb3: COPTIC SMALL LETTER DIALECT-P ALEF*/
+ 0x2cb4, /* U+002cb4: COPTIC CAPITAL LETTER OLD COPTIC AIN*/
+ 0x2cb4, /* U+002cb5: COPTIC SMALL LETTER OLD COPTIC AIN*/
+ 0x2cb6, /* U+002cb6: COPTIC CAPITAL LETTER CRYPTOGRAMMIC EIE*/
+ 0x2cb6, /* U+002cb7: COPTIC SMALL LETTER CRYPTOGRAMMIC EIE*/
+ 0x2cb8, /* U+002cb8: COPTIC CAPITAL LETTER DIALECT-P KAPA*/
+ 0x2cb8, /* U+002cb9: COPTIC SMALL LETTER DIALECT-P KAPA*/
+ 0x2cba, /* U+002cba: COPTIC CAPITAL LETTER DIALECT-P NI*/
+ 0x2cba, /* U+002cbb: COPTIC SMALL LETTER DIALECT-P NI*/
+ 0x2cbc, /* U+002cbc: COPTIC CAPITAL LETTER CRYPTOGRAMMIC NI*/
+ 0x2cbc, /* U+002cbd: COPTIC SMALL LETTER CRYPTOGRAMMIC NI*/
+ 0x2cbe, /* U+002cbe: COPTIC CAPITAL LETTER OLD COPTIC OOU*/
+ 0x2cbe, /* U+002cbf: COPTIC SMALL LETTER OLD COPTIC OOU*/
+ 0x2cc0, /* U+002cc0: COPTIC CAPITAL LETTER SAMPI*/
+ 0x2cc0, /* U+002cc1: COPTIC SMALL LETTER SAMPI*/
+ 0x2cc2, /* U+002cc2: COPTIC CAPITAL LETTER CROSSED SHEI*/
+ 0x2cc2, /* U+002cc3: COPTIC SMALL LETTER CROSSED SHEI*/
+ 0x2cc4, /* U+002cc4: COPTIC CAPITAL LETTER OLD COPTIC SHEI*/
+ 0x2cc4, /* U+002cc5: COPTIC SMALL LETTER OLD COPTIC SHEI*/
+ 0x2cc6, /* U+002cc6: COPTIC CAPITAL LETTER OLD COPTIC ESH*/
+ 0x2cc6, /* U+002cc7: COPTIC SMALL LETTER OLD COPTIC ESH*/
+ 0x2cc8, /* U+002cc8: COPTIC CAPITAL LETTER AKHMIMIC KHEI*/
+ 0x2cc8, /* U+002cc9: COPTIC SMALL LETTER AKHMIMIC KHEI*/
+ 0x2cca, /* U+002cca: COPTIC CAPITAL LETTER DIALECT-P HORI*/
+ 0x2cca, /* U+002ccb: COPTIC SMALL LETTER DIALECT-P HORI*/
+ 0x2ccc, /* U+002ccc: COPTIC CAPITAL LETTER OLD COPTIC HORI*/
+ 0x2ccc, /* U+002ccd: COPTIC SMALL LETTER OLD COPTIC HORI*/
+ 0x2cce, /* U+002cce: COPTIC CAPITAL LETTER OLD COPTIC HA*/
+ 0x2cce, /* U+002ccf: COPTIC SMALL LETTER OLD COPTIC HA*/
+ 0x2cd0, /* U+002cd0: COPTIC CAPITAL LETTER L-SHAPED HA*/
+ 0x2cd0, /* U+002cd1: COPTIC SMALL LETTER L-SHAPED HA*/
+ 0x2cd2, /* U+002cd2: COPTIC CAPITAL LETTER OLD COPTIC HEI*/
+ 0x2cd2, /* U+002cd3: COPTIC SMALL LETTER OLD COPTIC HEI*/
+ 0x2cd4, /* U+002cd4: COPTIC CAPITAL LETTER OLD COPTIC HAT*/
+ 0x2cd4, /* U+002cd5: COPTIC SMALL LETTER OLD COPTIC HAT*/
+ 0x2cd6, /* U+002cd6: COPTIC CAPITAL LETTER OLD COPTIC GANGIA*/
+ 0x2cd6, /* U+002cd7: COPTIC SMALL LETTER OLD COPTIC GANGIA*/
+ 0x2cd8, /* U+002cd8: COPTIC CAPITAL LETTER OLD COPTIC DJA*/
+ 0x2cd8, /* U+002cd9: COPTIC SMALL LETTER OLD COPTIC DJA*/
+ 0x2cda, /* U+002cda: COPTIC CAPITAL LETTER OLD COPTIC SHIMA*/
+ 0x2cda, /* U+002cdb: COPTIC SMALL LETTER OLD COPTIC SHIMA*/
+ 0x2cdc, /* U+002cdc: COPTIC CAPITAL LETTER OLD NUBIAN SHIMA*/
+ 0x2cdc, /* U+002cdd: COPTIC SMALL LETTER OLD NUBIAN SHIMA*/
+ 0x2cde, /* U+002cde: COPTIC CAPITAL LETTER OLD NUBIAN NGI*/
+ 0x2cde, /* U+002cdf: COPTIC SMALL LETTER OLD NUBIAN NGI*/
+ 0x2ce0, /* U+002ce0: COPTIC CAPITAL LETTER OLD NUBIAN NYI*/
+ 0x2ce0, /* U+002ce1: COPTIC SMALL LETTER OLD NUBIAN NYI*/
+ 0x2ce2, /* U+002ce2: COPTIC CAPITAL LETTER OLD NUBIAN WAU*/
+ 0x2ce2, /* U+002ce3: COPTIC SMALL LETTER OLD NUBIAN WAU*/
+ 0x2ce4, /* U+002ce4: COPTIC SYMBOL KAI*/
+ 0x2ce5, /* U+002ce5: COPTIC SYMBOL MI RO*/
+ 0x2ce6, /* U+002ce6: COPTIC SYMBOL PI RO*/
+ 0x2ce7, /* U+002ce7: COPTIC SYMBOL STAUROS*/
+ 0x2ce8, /* U+002ce8: COPTIC SYMBOL TAU RO*/
+ 0x2ce9, /* U+002ce9: COPTIC SYMBOL KHI RO*/
+ 0x2cea, /* U+002cea: COPTIC SYMBOL SHIMA SIMA*/
+ 0x2ceb, /* U+002ceb: COPTIC CAPITAL LETTER CRYPTOGRAMMIC SHEI*/
+ 0x2ceb, /* U+002cec: COPTIC SMALL LETTER CRYPTOGRAMMIC SHEI*/
+ 0x2ced, /* U+002ced: COPTIC CAPITAL LETTER CRYPTOGRAMMIC GANGIA*/
+ 0x2ced, /* U+002cee: COPTIC SMALL LETTER CRYPTOGRAMMIC GANGIA*/
+ 0x2cef, /* U+002cef: COPTIC COMBINING NI ABOVE*/
+ 0x2cf0, /* U+002cf0: COPTIC COMBINING SPIRITUS ASPER*/
+ 0x2cf1, /* U+002cf1: COPTIC COMBINING SPIRITUS LENIS*/
+ 0x2cf2, /* U+002cf2: COPTIC CAPITAL LETTER BOHAIRIC KHEI*/
+ 0x2cf2, /* U+002cf3: COPTIC SMALL LETTER BOHAIRIC KHEI*/
+ 0x2cf4, /* U+002cf4: */
+ 0x2cf5, /* U+002cf5: */
+ 0x2cf6, /* U+002cf6: */
+ 0x2cf7, /* U+002cf7: */
+ 0x2cf8, /* U+002cf8: */
+ 0x2cf9, /* U+002cf9: COPTIC OLD NUBIAN FULL STOP*/
+ 0x2cfa, /* U+002cfa: COPTIC OLD NUBIAN DIRECT QUESTION MARK*/
+ 0x2cfb, /* U+002cfb: COPTIC OLD NUBIAN INDIRECT QUESTION MARK*/
+ 0x2cfc, /* U+002cfc: COPTIC OLD NUBIAN VERSE DIVIDER*/
+ 0x2cfd, /* U+002cfd: COPTIC FRACTION ONE HALF*/
+ 0x2cfe, /* U+002cfe: COPTIC FULL STOP*/
+ 0x2cff, /* U+002cff: COPTIC MORPHOLOGICAL DIVIDER*/
+ 0x10a0, /* U+002d00: GEORGIAN SMALL LETTER AN*/
+ 0x10a1, /* U+002d01: GEORGIAN SMALL LETTER BAN*/
+ 0x10a2, /* U+002d02: GEORGIAN SMALL LETTER GAN*/
+ 0x10a3, /* U+002d03: GEORGIAN SMALL LETTER DON*/
+ 0x10a4, /* U+002d04: GEORGIAN SMALL LETTER EN*/
+ 0x10a5, /* U+002d05: GEORGIAN SMALL LETTER VIN*/
+ 0x10a6, /* U+002d06: GEORGIAN SMALL LETTER ZEN*/
+ 0x10a7, /* U+002d07: GEORGIAN SMALL LETTER TAN*/
+ 0x10a8, /* U+002d08: GEORGIAN SMALL LETTER IN*/
+ 0x10a9, /* U+002d09: GEORGIAN SMALL LETTER KAN*/
+ 0x10aa, /* U+002d0a: GEORGIAN SMALL LETTER LAS*/
+ 0x10ab, /* U+002d0b: GEORGIAN SMALL LETTER MAN*/
+ 0x10ac, /* U+002d0c: GEORGIAN SMALL LETTER NAR*/
+ 0x10ad, /* U+002d0d: GEORGIAN SMALL LETTER ON*/
+ 0x10ae, /* U+002d0e: GEORGIAN SMALL LETTER PAR*/
+ 0x10af, /* U+002d0f: GEORGIAN SMALL LETTER ZHAR*/
+ 0x10b0, /* U+002d10: GEORGIAN SMALL LETTER RAE*/
+ 0x10b1, /* U+002d11: GEORGIAN SMALL LETTER SAN*/
+ 0x10b2, /* U+002d12: GEORGIAN SMALL LETTER TAR*/
+ 0x10b3, /* U+002d13: GEORGIAN SMALL LETTER UN*/
+ 0x10b4, /* U+002d14: GEORGIAN SMALL LETTER PHAR*/
+ 0x10b5, /* U+002d15: GEORGIAN SMALL LETTER KHAR*/
+ 0x10b6, /* U+002d16: GEORGIAN SMALL LETTER GHAN*/
+ 0x10b7, /* U+002d17: GEORGIAN SMALL LETTER QAR*/
+ 0x10b8, /* U+002d18: GEORGIAN SMALL LETTER SHIN*/
+ 0x10b9, /* U+002d19: GEORGIAN SMALL LETTER CHIN*/
+ 0x10ba, /* U+002d1a: GEORGIAN SMALL LETTER CAN*/
+ 0x10bb, /* U+002d1b: GEORGIAN SMALL LETTER JIL*/
+ 0x10bc, /* U+002d1c: GEORGIAN SMALL LETTER CIL*/
+ 0x10bd, /* U+002d1d: GEORGIAN SMALL LETTER CHAR*/
+ 0x10be, /* U+002d1e: GEORGIAN SMALL LETTER XAN*/
+ 0x10bf, /* U+002d1f: GEORGIAN SMALL LETTER JHAN*/
+ 0x10c0, /* U+002d20: GEORGIAN SMALL LETTER HAE*/
+ 0x10c1, /* U+002d21: GEORGIAN SMALL LETTER HE*/
+ 0x10c2, /* U+002d22: GEORGIAN SMALL LETTER HIE*/
+ 0x10c3, /* U+002d23: GEORGIAN SMALL LETTER WE*/
+ 0x10c4, /* U+002d24: GEORGIAN SMALL LETTER HAR*/
+ 0x10c5, /* U+002d25: GEORGIAN SMALL LETTER HOE*/
+ 0x2d26, /* U+002d26: */
+ 0x10c7, /* U+002d27: GEORGIAN SMALL LETTER YN*/
+ 0x2d28, /* U+002d28: */
+ 0x2d29, /* U+002d29: */
+ 0x2d2a, /* U+002d2a: */
+ 0x2d2b, /* U+002d2b: */
+ 0x2d2c, /* U+002d2c: */
+ 0x10cd, /* U+002d2d: GEORGIAN SMALL LETTER AEN*/
+};
+
+static const RTUNICP g_afRTUniUpper0x00a641[] =
+{
+ 0xa640, /* U+00a641: CYRILLIC SMALL LETTER ZEMLYA*/
+ 0xa642, /* U+00a642: CYRILLIC CAPITAL LETTER DZELO*/
+ 0xa642, /* U+00a643: CYRILLIC SMALL LETTER DZELO*/
+ 0xa644, /* U+00a644: CYRILLIC CAPITAL LETTER REVERSED DZE*/
+ 0xa644, /* U+00a645: CYRILLIC SMALL LETTER REVERSED DZE*/
+ 0xa646, /* U+00a646: CYRILLIC CAPITAL LETTER IOTA*/
+ 0xa646, /* U+00a647: CYRILLIC SMALL LETTER IOTA*/
+ 0xa648, /* U+00a648: CYRILLIC CAPITAL LETTER DJERV*/
+ 0xa648, /* U+00a649: CYRILLIC SMALL LETTER DJERV*/
+ 0xa64a, /* U+00a64a: CYRILLIC CAPITAL LETTER MONOGRAPH UK*/
+ 0xa64a, /* U+00a64b: CYRILLIC SMALL LETTER MONOGRAPH UK*/
+ 0xa64c, /* U+00a64c: CYRILLIC CAPITAL LETTER BROAD OMEGA*/
+ 0xa64c, /* U+00a64d: CYRILLIC SMALL LETTER BROAD OMEGA*/
+ 0xa64e, /* U+00a64e: CYRILLIC CAPITAL LETTER NEUTRAL YER*/
+ 0xa64e, /* U+00a64f: CYRILLIC SMALL LETTER NEUTRAL YER*/
+ 0xa650, /* U+00a650: CYRILLIC CAPITAL LETTER YERU WITH BACK YER*/
+ 0xa650, /* U+00a651: CYRILLIC SMALL LETTER YERU WITH BACK YER*/
+ 0xa652, /* U+00a652: CYRILLIC CAPITAL LETTER IOTIFIED YAT*/
+ 0xa652, /* U+00a653: CYRILLIC SMALL LETTER IOTIFIED YAT*/
+ 0xa654, /* U+00a654: CYRILLIC CAPITAL LETTER REVERSED YU*/
+ 0xa654, /* U+00a655: CYRILLIC SMALL LETTER REVERSED YU*/
+ 0xa656, /* U+00a656: CYRILLIC CAPITAL LETTER IOTIFIED A*/
+ 0xa656, /* U+00a657: CYRILLIC SMALL LETTER IOTIFIED A*/
+ 0xa658, /* U+00a658: CYRILLIC CAPITAL LETTER CLOSED LITTLE YUS*/
+ 0xa658, /* U+00a659: CYRILLIC SMALL LETTER CLOSED LITTLE YUS*/
+ 0xa65a, /* U+00a65a: CYRILLIC CAPITAL LETTER BLENDED YUS*/
+ 0xa65a, /* U+00a65b: CYRILLIC SMALL LETTER BLENDED YUS*/
+ 0xa65c, /* U+00a65c: CYRILLIC CAPITAL LETTER IOTIFIED CLOSED LITTLE YUS*/
+ 0xa65c, /* U+00a65d: CYRILLIC SMALL LETTER IOTIFIED CLOSED LITTLE YUS*/
+ 0xa65e, /* U+00a65e: CYRILLIC CAPITAL LETTER YN*/
+ 0xa65e, /* U+00a65f: CYRILLIC SMALL LETTER YN*/
+ 0xa660, /* U+00a660: CYRILLIC CAPITAL LETTER REVERSED TSE*/
+ 0xa660, /* U+00a661: CYRILLIC SMALL LETTER REVERSED TSE*/
+ 0xa662, /* U+00a662: CYRILLIC CAPITAL LETTER SOFT DE*/
+ 0xa662, /* U+00a663: CYRILLIC SMALL LETTER SOFT DE*/
+ 0xa664, /* U+00a664: CYRILLIC CAPITAL LETTER SOFT EL*/
+ 0xa664, /* U+00a665: CYRILLIC SMALL LETTER SOFT EL*/
+ 0xa666, /* U+00a666: CYRILLIC CAPITAL LETTER SOFT EM*/
+ 0xa666, /* U+00a667: CYRILLIC SMALL LETTER SOFT EM*/
+ 0xa668, /* U+00a668: CYRILLIC CAPITAL LETTER MONOCULAR O*/
+ 0xa668, /* U+00a669: CYRILLIC SMALL LETTER MONOCULAR O*/
+ 0xa66a, /* U+00a66a: CYRILLIC CAPITAL LETTER BINOCULAR O*/
+ 0xa66a, /* U+00a66b: CYRILLIC SMALL LETTER BINOCULAR O*/
+ 0xa66c, /* U+00a66c: CYRILLIC CAPITAL LETTER DOUBLE MONOCULAR O*/
+ 0xa66c, /* U+00a66d: CYRILLIC SMALL LETTER DOUBLE MONOCULAR O*/
+ 0xa66e, /* U+00a66e: CYRILLIC LETTER MULTIOCULAR O*/
+ 0xa66f, /* U+00a66f: COMBINING CYRILLIC VZMET*/
+ 0xa670, /* U+00a670: COMBINING CYRILLIC TEN MILLIONS SIGN*/
+ 0xa671, /* U+00a671: COMBINING CYRILLIC HUNDRED MILLIONS SIGN*/
+ 0xa672, /* U+00a672: COMBINING CYRILLIC THOUSAND MILLIONS SIGN*/
+ 0xa673, /* U+00a673: SLAVONIC ASTERISK*/
+ 0xa674, /* U+00a674: COMBINING CYRILLIC LETTER UKRAINIAN IE*/
+ 0xa675, /* U+00a675: COMBINING CYRILLIC LETTER I*/
+ 0xa676, /* U+00a676: COMBINING CYRILLIC LETTER YI*/
+ 0xa677, /* U+00a677: COMBINING CYRILLIC LETTER U*/
+ 0xa678, /* U+00a678: COMBINING CYRILLIC LETTER HARD SIGN*/
+ 0xa679, /* U+00a679: COMBINING CYRILLIC LETTER YERU*/
+ 0xa67a, /* U+00a67a: COMBINING CYRILLIC LETTER SOFT SIGN*/
+ 0xa67b, /* U+00a67b: COMBINING CYRILLIC LETTER OMEGA*/
+ 0xa67c, /* U+00a67c: COMBINING CYRILLIC KAVYKA*/
+ 0xa67d, /* U+00a67d: COMBINING CYRILLIC PAYEROK*/
+ 0xa67e, /* U+00a67e: CYRILLIC KAVYKA*/
+ 0xa67f, /* U+00a67f: CYRILLIC PAYEROK*/
+ 0xa680, /* U+00a680: CYRILLIC CAPITAL LETTER DWE*/
+ 0xa680, /* U+00a681: CYRILLIC SMALL LETTER DWE*/
+ 0xa682, /* U+00a682: CYRILLIC CAPITAL LETTER DZWE*/
+ 0xa682, /* U+00a683: CYRILLIC SMALL LETTER DZWE*/
+ 0xa684, /* U+00a684: CYRILLIC CAPITAL LETTER ZHWE*/
+ 0xa684, /* U+00a685: CYRILLIC SMALL LETTER ZHWE*/
+ 0xa686, /* U+00a686: CYRILLIC CAPITAL LETTER CCHE*/
+ 0xa686, /* U+00a687: CYRILLIC SMALL LETTER CCHE*/
+ 0xa688, /* U+00a688: CYRILLIC CAPITAL LETTER DZZE*/
+ 0xa688, /* U+00a689: CYRILLIC SMALL LETTER DZZE*/
+ 0xa68a, /* U+00a68a: CYRILLIC CAPITAL LETTER TE WITH MIDDLE HOOK*/
+ 0xa68a, /* U+00a68b: CYRILLIC SMALL LETTER TE WITH MIDDLE HOOK*/
+ 0xa68c, /* U+00a68c: CYRILLIC CAPITAL LETTER TWE*/
+ 0xa68c, /* U+00a68d: CYRILLIC SMALL LETTER TWE*/
+ 0xa68e, /* U+00a68e: CYRILLIC CAPITAL LETTER TSWE*/
+ 0xa68e, /* U+00a68f: CYRILLIC SMALL LETTER TSWE*/
+ 0xa690, /* U+00a690: CYRILLIC CAPITAL LETTER TSSE*/
+ 0xa690, /* U+00a691: CYRILLIC SMALL LETTER TSSE*/
+ 0xa692, /* U+00a692: CYRILLIC CAPITAL LETTER TCHE*/
+ 0xa692, /* U+00a693: CYRILLIC SMALL LETTER TCHE*/
+ 0xa694, /* U+00a694: CYRILLIC CAPITAL LETTER HWE*/
+ 0xa694, /* U+00a695: CYRILLIC SMALL LETTER HWE*/
+ 0xa696, /* U+00a696: CYRILLIC CAPITAL LETTER SHWE*/
+ 0xa696, /* U+00a697: CYRILLIC SMALL LETTER SHWE*/
+ 0xa698, /* U+00a698: */
+ 0xa699, /* U+00a699: */
+ 0xa69a, /* U+00a69a: */
+ 0xa69b, /* U+00a69b: */
+ 0xa69c, /* U+00a69c: */
+ 0xa69d, /* U+00a69d: */
+ 0xa69e, /* U+00a69e: */
+ 0xa69f, /* U+00a69f: COMBINING CYRILLIC LETTER IOTIFIED E*/
+ 0xa6a0, /* U+00a6a0: BAMUM LETTER A*/
+ 0xa6a1, /* U+00a6a1: BAMUM LETTER KA*/
+ 0xa6a2, /* U+00a6a2: BAMUM LETTER U*/
+ 0xa6a3, /* U+00a6a3: BAMUM LETTER KU*/
+ 0xa6a4, /* U+00a6a4: BAMUM LETTER EE*/
+ 0xa6a5, /* U+00a6a5: BAMUM LETTER REE*/
+ 0xa6a6, /* U+00a6a6: BAMUM LETTER TAE*/
+ 0xa6a7, /* U+00a6a7: BAMUM LETTER O*/
+ 0xa6a8, /* U+00a6a8: BAMUM LETTER NYI*/
+ 0xa6a9, /* U+00a6a9: BAMUM LETTER I*/
+ 0xa6aa, /* U+00a6aa: BAMUM LETTER LA*/
+ 0xa6ab, /* U+00a6ab: BAMUM LETTER PA*/
+ 0xa6ac, /* U+00a6ac: BAMUM LETTER RII*/
+ 0xa6ad, /* U+00a6ad: BAMUM LETTER RIEE*/
+ 0xa6ae, /* U+00a6ae: BAMUM LETTER LEEEE*/
+ 0xa6af, /* U+00a6af: BAMUM LETTER MEEEE*/
+ 0xa6b0, /* U+00a6b0: BAMUM LETTER TAA*/
+ 0xa6b1, /* U+00a6b1: BAMUM LETTER NDAA*/
+ 0xa6b2, /* U+00a6b2: BAMUM LETTER NJAEM*/
+ 0xa6b3, /* U+00a6b3: BAMUM LETTER M*/
+ 0xa6b4, /* U+00a6b4: BAMUM LETTER SUU*/
+ 0xa6b5, /* U+00a6b5: BAMUM LETTER MU*/
+ 0xa6b6, /* U+00a6b6: BAMUM LETTER SHII*/
+ 0xa6b7, /* U+00a6b7: BAMUM LETTER SI*/
+ 0xa6b8, /* U+00a6b8: BAMUM LETTER SHEUX*/
+ 0xa6b9, /* U+00a6b9: BAMUM LETTER SEUX*/
+ 0xa6ba, /* U+00a6ba: BAMUM LETTER KYEE*/
+ 0xa6bb, /* U+00a6bb: BAMUM LETTER KET*/
+ 0xa6bc, /* U+00a6bc: BAMUM LETTER NUAE*/
+ 0xa6bd, /* U+00a6bd: BAMUM LETTER NU*/
+ 0xa6be, /* U+00a6be: BAMUM LETTER NJUAE*/
+ 0xa6bf, /* U+00a6bf: BAMUM LETTER YOQ*/
+ 0xa6c0, /* U+00a6c0: BAMUM LETTER SHU*/
+ 0xa6c1, /* U+00a6c1: BAMUM LETTER YUQ*/
+ 0xa6c2, /* U+00a6c2: BAMUM LETTER YA*/
+ 0xa6c3, /* U+00a6c3: BAMUM LETTER NSHA*/
+ 0xa6c4, /* U+00a6c4: BAMUM LETTER KEUX*/
+ 0xa6c5, /* U+00a6c5: BAMUM LETTER PEUX*/
+ 0xa6c6, /* U+00a6c6: BAMUM LETTER NJEE*/
+ 0xa6c7, /* U+00a6c7: BAMUM LETTER NTEE*/
+ 0xa6c8, /* U+00a6c8: BAMUM LETTER PUE*/
+ 0xa6c9, /* U+00a6c9: BAMUM LETTER WUE*/
+ 0xa6ca, /* U+00a6ca: BAMUM LETTER PEE*/
+ 0xa6cb, /* U+00a6cb: BAMUM LETTER FEE*/
+ 0xa6cc, /* U+00a6cc: BAMUM LETTER RU*/
+ 0xa6cd, /* U+00a6cd: BAMUM LETTER LU*/
+ 0xa6ce, /* U+00a6ce: BAMUM LETTER MI*/
+ 0xa6cf, /* U+00a6cf: BAMUM LETTER NI*/
+ 0xa6d0, /* U+00a6d0: BAMUM LETTER REUX*/
+ 0xa6d1, /* U+00a6d1: BAMUM LETTER RAE*/
+ 0xa6d2, /* U+00a6d2: BAMUM LETTER KEN*/
+ 0xa6d3, /* U+00a6d3: BAMUM LETTER NGKWAEN*/
+ 0xa6d4, /* U+00a6d4: BAMUM LETTER NGGA*/
+ 0xa6d5, /* U+00a6d5: BAMUM LETTER NGA*/
+ 0xa6d6, /* U+00a6d6: BAMUM LETTER SHO*/
+ 0xa6d7, /* U+00a6d7: BAMUM LETTER PUAE*/
+ 0xa6d8, /* U+00a6d8: BAMUM LETTER FU*/
+ 0xa6d9, /* U+00a6d9: BAMUM LETTER FOM*/
+ 0xa6da, /* U+00a6da: BAMUM LETTER WA*/
+ 0xa6db, /* U+00a6db: BAMUM LETTER NA*/
+ 0xa6dc, /* U+00a6dc: BAMUM LETTER LI*/
+ 0xa6dd, /* U+00a6dd: BAMUM LETTER PI*/
+ 0xa6de, /* U+00a6de: BAMUM LETTER LOQ*/
+ 0xa6df, /* U+00a6df: BAMUM LETTER KO*/
+ 0xa6e0, /* U+00a6e0: BAMUM LETTER MBEN*/
+ 0xa6e1, /* U+00a6e1: BAMUM LETTER REN*/
+ 0xa6e2, /* U+00a6e2: BAMUM LETTER MEN*/
+ 0xa6e3, /* U+00a6e3: BAMUM LETTER MA*/
+ 0xa6e4, /* U+00a6e4: BAMUM LETTER TI*/
+ 0xa6e5, /* U+00a6e5: BAMUM LETTER KI*/
+ 0xa6e6, /* U+00a6e6: BAMUM LETTER MO*/
+ 0xa6e7, /* U+00a6e7: BAMUM LETTER MBAA*/
+ 0xa6e8, /* U+00a6e8: BAMUM LETTER TET*/
+ 0xa6e9, /* U+00a6e9: BAMUM LETTER KPA*/
+ 0xa6ea, /* U+00a6ea: BAMUM LETTER TEN*/
+ 0xa6eb, /* U+00a6eb: BAMUM LETTER NTUU*/
+ 0xa6ec, /* U+00a6ec: BAMUM LETTER SAMBA*/
+ 0xa6ed, /* U+00a6ed: BAMUM LETTER FAAMAE*/
+ 0xa6ee, /* U+00a6ee: BAMUM LETTER KOVUU*/
+ 0xa6ef, /* U+00a6ef: BAMUM LETTER KOGHOM*/
+ 0xa6f0, /* U+00a6f0: BAMUM COMBINING MARK KOQNDON*/
+ 0xa6f1, /* U+00a6f1: BAMUM COMBINING MARK TUKWENTIS*/
+ 0xa6f2, /* U+00a6f2: BAMUM NJAEMLI*/
+ 0xa6f3, /* U+00a6f3: BAMUM FULL STOP*/
+ 0xa6f4, /* U+00a6f4: BAMUM COLON*/
+ 0xa6f5, /* U+00a6f5: BAMUM COMMA*/
+ 0xa6f6, /* U+00a6f6: BAMUM SEMICOLON*/
+ 0xa6f7, /* U+00a6f7: BAMUM QUESTION MARK*/
+ 0xa6f8, /* U+00a6f8: */
+ 0xa6f9, /* U+00a6f9: */
+ 0xa6fa, /* U+00a6fa: */
+ 0xa6fb, /* U+00a6fb: */
+ 0xa6fc, /* U+00a6fc: */
+ 0xa6fd, /* U+00a6fd: */
+ 0xa6fe, /* U+00a6fe: */
+ 0xa6ff, /* U+00a6ff: */
+ 0xa700, /* U+00a700: MODIFIER LETTER CHINESE TONE YIN PING*/
+ 0xa701, /* U+00a701: MODIFIER LETTER CHINESE TONE YANG PING*/
+ 0xa702, /* U+00a702: MODIFIER LETTER CHINESE TONE YIN SHANG*/
+ 0xa703, /* U+00a703: MODIFIER LETTER CHINESE TONE YANG SHANG*/
+ 0xa704, /* U+00a704: MODIFIER LETTER CHINESE TONE YIN QU*/
+ 0xa705, /* U+00a705: MODIFIER LETTER CHINESE TONE YANG QU*/
+ 0xa706, /* U+00a706: MODIFIER LETTER CHINESE TONE YIN RU*/
+ 0xa707, /* U+00a707: MODIFIER LETTER CHINESE TONE YANG RU*/
+ 0xa708, /* U+00a708: MODIFIER LETTER EXTRA-HIGH DOTTED TONE BAR*/
+ 0xa709, /* U+00a709: MODIFIER LETTER HIGH DOTTED TONE BAR*/
+ 0xa70a, /* U+00a70a: MODIFIER LETTER MID DOTTED TONE BAR*/
+ 0xa70b, /* U+00a70b: MODIFIER LETTER LOW DOTTED TONE BAR*/
+ 0xa70c, /* U+00a70c: MODIFIER LETTER EXTRA-LOW DOTTED TONE BAR*/
+ 0xa70d, /* U+00a70d: MODIFIER LETTER EXTRA-HIGH DOTTED LEFT-STEM TONE BAR*/
+ 0xa70e, /* U+00a70e: MODIFIER LETTER HIGH DOTTED LEFT-STEM TONE BAR*/
+ 0xa70f, /* U+00a70f: MODIFIER LETTER MID DOTTED LEFT-STEM TONE BAR*/
+ 0xa710, /* U+00a710: MODIFIER LETTER LOW DOTTED LEFT-STEM TONE BAR*/
+ 0xa711, /* U+00a711: MODIFIER LETTER EXTRA-LOW DOTTED LEFT-STEM TONE BAR*/
+ 0xa712, /* U+00a712: MODIFIER LETTER EXTRA-HIGH LEFT-STEM TONE BAR*/
+ 0xa713, /* U+00a713: MODIFIER LETTER HIGH LEFT-STEM TONE BAR*/
+ 0xa714, /* U+00a714: MODIFIER LETTER MID LEFT-STEM TONE BAR*/
+ 0xa715, /* U+00a715: MODIFIER LETTER LOW LEFT-STEM TONE BAR*/
+ 0xa716, /* U+00a716: MODIFIER LETTER EXTRA-LOW LEFT-STEM TONE BAR*/
+ 0xa717, /* U+00a717: MODIFIER LETTER DOT VERTICAL BAR*/
+ 0xa718, /* U+00a718: MODIFIER LETTER DOT SLASH*/
+ 0xa719, /* U+00a719: MODIFIER LETTER DOT HORIZONTAL BAR*/
+ 0xa71a, /* U+00a71a: MODIFIER LETTER LOWER RIGHT CORNER ANGLE*/
+ 0xa71b, /* U+00a71b: MODIFIER LETTER RAISED UP ARROW*/
+ 0xa71c, /* U+00a71c: MODIFIER LETTER RAISED DOWN ARROW*/
+ 0xa71d, /* U+00a71d: MODIFIER LETTER RAISED EXCLAMATION MARK*/
+ 0xa71e, /* U+00a71e: MODIFIER LETTER RAISED INVERTED EXCLAMATION MARK*/
+ 0xa71f, /* U+00a71f: MODIFIER LETTER LOW INVERTED EXCLAMATION MARK*/
+ 0xa720, /* U+00a720: MODIFIER LETTER STRESS AND HIGH TONE*/
+ 0xa721, /* U+00a721: MODIFIER LETTER STRESS AND LOW TONE*/
+ 0xa722, /* U+00a722: LATIN CAPITAL LETTER EGYPTOLOGICAL ALEF*/
+ 0xa722, /* U+00a723: LATIN SMALL LETTER EGYPTOLOGICAL ALEF*/
+ 0xa724, /* U+00a724: LATIN CAPITAL LETTER EGYPTOLOGICAL AIN*/
+ 0xa724, /* U+00a725: LATIN SMALL LETTER EGYPTOLOGICAL AIN*/
+ 0xa726, /* U+00a726: LATIN CAPITAL LETTER HENG*/
+ 0xa726, /* U+00a727: LATIN SMALL LETTER HENG*/
+ 0xa728, /* U+00a728: LATIN CAPITAL LETTER TZ*/
+ 0xa728, /* U+00a729: LATIN SMALL LETTER TZ*/
+ 0xa72a, /* U+00a72a: LATIN CAPITAL LETTER TRESILLO*/
+ 0xa72a, /* U+00a72b: LATIN SMALL LETTER TRESILLO*/
+ 0xa72c, /* U+00a72c: LATIN CAPITAL LETTER CUATRILLO*/
+ 0xa72c, /* U+00a72d: LATIN SMALL LETTER CUATRILLO*/
+ 0xa72e, /* U+00a72e: LATIN CAPITAL LETTER CUATRILLO WITH COMMA*/
+ 0xa72e, /* U+00a72f: LATIN SMALL LETTER CUATRILLO WITH COMMA*/
+ 0xa730, /* U+00a730: LATIN LETTER SMALL CAPITAL F*/
+ 0xa731, /* U+00a731: LATIN LETTER SMALL CAPITAL S*/
+ 0xa732, /* U+00a732: LATIN CAPITAL LETTER AA*/
+ 0xa732, /* U+00a733: LATIN SMALL LETTER AA*/
+ 0xa734, /* U+00a734: LATIN CAPITAL LETTER AO*/
+ 0xa734, /* U+00a735: LATIN SMALL LETTER AO*/
+ 0xa736, /* U+00a736: LATIN CAPITAL LETTER AU*/
+ 0xa736, /* U+00a737: LATIN SMALL LETTER AU*/
+ 0xa738, /* U+00a738: LATIN CAPITAL LETTER AV*/
+ 0xa738, /* U+00a739: LATIN SMALL LETTER AV*/
+ 0xa73a, /* U+00a73a: LATIN CAPITAL LETTER AV WITH HORIZONTAL BAR*/
+ 0xa73a, /* U+00a73b: LATIN SMALL LETTER AV WITH HORIZONTAL BAR*/
+ 0xa73c, /* U+00a73c: LATIN CAPITAL LETTER AY*/
+ 0xa73c, /* U+00a73d: LATIN SMALL LETTER AY*/
+ 0xa73e, /* U+00a73e: LATIN CAPITAL LETTER REVERSED C WITH DOT*/
+ 0xa73e, /* U+00a73f: LATIN SMALL LETTER REVERSED C WITH DOT*/
+ 0xa740, /* U+00a740: LATIN CAPITAL LETTER K WITH STROKE*/
+ 0xa740, /* U+00a741: LATIN SMALL LETTER K WITH STROKE*/
+ 0xa742, /* U+00a742: LATIN CAPITAL LETTER K WITH DIAGONAL STROKE*/
+ 0xa742, /* U+00a743: LATIN SMALL LETTER K WITH DIAGONAL STROKE*/
+ 0xa744, /* U+00a744: LATIN CAPITAL LETTER K WITH STROKE AND DIAGONAL STROKE*/
+ 0xa744, /* U+00a745: LATIN SMALL LETTER K WITH STROKE AND DIAGONAL STROKE*/
+ 0xa746, /* U+00a746: LATIN CAPITAL LETTER BROKEN L*/
+ 0xa746, /* U+00a747: LATIN SMALL LETTER BROKEN L*/
+ 0xa748, /* U+00a748: LATIN CAPITAL LETTER L WITH HIGH STROKE*/
+ 0xa748, /* U+00a749: LATIN SMALL LETTER L WITH HIGH STROKE*/
+ 0xa74a, /* U+00a74a: LATIN CAPITAL LETTER O WITH LONG STROKE OVERLAY*/
+ 0xa74a, /* U+00a74b: LATIN SMALL LETTER O WITH LONG STROKE OVERLAY*/
+ 0xa74c, /* U+00a74c: LATIN CAPITAL LETTER O WITH LOOP*/
+ 0xa74c, /* U+00a74d: LATIN SMALL LETTER O WITH LOOP*/
+ 0xa74e, /* U+00a74e: LATIN CAPITAL LETTER OO*/
+ 0xa74e, /* U+00a74f: LATIN SMALL LETTER OO*/
+ 0xa750, /* U+00a750: LATIN CAPITAL LETTER P WITH STROKE THROUGH DESCENDER*/
+ 0xa750, /* U+00a751: LATIN SMALL LETTER P WITH STROKE THROUGH DESCENDER*/
+ 0xa752, /* U+00a752: LATIN CAPITAL LETTER P WITH FLOURISH*/
+ 0xa752, /* U+00a753: LATIN SMALL LETTER P WITH FLOURISH*/
+ 0xa754, /* U+00a754: LATIN CAPITAL LETTER P WITH SQUIRREL TAIL*/
+ 0xa754, /* U+00a755: LATIN SMALL LETTER P WITH SQUIRREL TAIL*/
+ 0xa756, /* U+00a756: LATIN CAPITAL LETTER Q WITH STROKE THROUGH DESCENDER*/
+ 0xa756, /* U+00a757: LATIN SMALL LETTER Q WITH STROKE THROUGH DESCENDER*/
+ 0xa758, /* U+00a758: LATIN CAPITAL LETTER Q WITH DIAGONAL STROKE*/
+ 0xa758, /* U+00a759: LATIN SMALL LETTER Q WITH DIAGONAL STROKE*/
+ 0xa75a, /* U+00a75a: LATIN CAPITAL LETTER R ROTUNDA*/
+ 0xa75a, /* U+00a75b: LATIN SMALL LETTER R ROTUNDA*/
+ 0xa75c, /* U+00a75c: LATIN CAPITAL LETTER RUM ROTUNDA*/
+ 0xa75c, /* U+00a75d: LATIN SMALL LETTER RUM ROTUNDA*/
+ 0xa75e, /* U+00a75e: LATIN CAPITAL LETTER V WITH DIAGONAL STROKE*/
+ 0xa75e, /* U+00a75f: LATIN SMALL LETTER V WITH DIAGONAL STROKE*/
+ 0xa760, /* U+00a760: LATIN CAPITAL LETTER VY*/
+ 0xa760, /* U+00a761: LATIN SMALL LETTER VY*/
+ 0xa762, /* U+00a762: LATIN CAPITAL LETTER VISIGOTHIC Z*/
+ 0xa762, /* U+00a763: LATIN SMALL LETTER VISIGOTHIC Z*/
+ 0xa764, /* U+00a764: LATIN CAPITAL LETTER THORN WITH STROKE*/
+ 0xa764, /* U+00a765: LATIN SMALL LETTER THORN WITH STROKE*/
+ 0xa766, /* U+00a766: LATIN CAPITAL LETTER THORN WITH STROKE THROUGH DESCENDER*/
+ 0xa766, /* U+00a767: LATIN SMALL LETTER THORN WITH STROKE THROUGH DESCENDER*/
+ 0xa768, /* U+00a768: LATIN CAPITAL LETTER VEND*/
+ 0xa768, /* U+00a769: LATIN SMALL LETTER VEND*/
+ 0xa76a, /* U+00a76a: LATIN CAPITAL LETTER ET*/
+ 0xa76a, /* U+00a76b: LATIN SMALL LETTER ET*/
+ 0xa76c, /* U+00a76c: LATIN CAPITAL LETTER IS*/
+ 0xa76c, /* U+00a76d: LATIN SMALL LETTER IS*/
+ 0xa76e, /* U+00a76e: LATIN CAPITAL LETTER CON*/
+ 0xa76e, /* U+00a76f: LATIN SMALL LETTER CON*/
+ 0xa770, /* U+00a770: MODIFIER LETTER US*/
+ 0xa771, /* U+00a771: LATIN SMALL LETTER DUM*/
+ 0xa772, /* U+00a772: LATIN SMALL LETTER LUM*/
+ 0xa773, /* U+00a773: LATIN SMALL LETTER MUM*/
+ 0xa774, /* U+00a774: LATIN SMALL LETTER NUM*/
+ 0xa775, /* U+00a775: LATIN SMALL LETTER RUM*/
+ 0xa776, /* U+00a776: LATIN LETTER SMALL CAPITAL RUM*/
+ 0xa777, /* U+00a777: LATIN SMALL LETTER TUM*/
+ 0xa778, /* U+00a778: LATIN SMALL LETTER UM*/
+ 0xa779, /* U+00a779: LATIN CAPITAL LETTER INSULAR D*/
+ 0xa779, /* U+00a77a: LATIN SMALL LETTER INSULAR D*/
+ 0xa77b, /* U+00a77b: LATIN CAPITAL LETTER INSULAR F*/
+ 0xa77b, /* U+00a77c: LATIN SMALL LETTER INSULAR F*/
+ 0xa77d, /* U+00a77d: LATIN CAPITAL LETTER INSULAR G*/
+ 0xa77e, /* U+00a77e: LATIN CAPITAL LETTER TURNED INSULAR G*/
+ 0xa77e, /* U+00a77f: LATIN SMALL LETTER TURNED INSULAR G*/
+ 0xa780, /* U+00a780: LATIN CAPITAL LETTER TURNED L*/
+ 0xa780, /* U+00a781: LATIN SMALL LETTER TURNED L*/
+ 0xa782, /* U+00a782: LATIN CAPITAL LETTER INSULAR R*/
+ 0xa782, /* U+00a783: LATIN SMALL LETTER INSULAR R*/
+ 0xa784, /* U+00a784: LATIN CAPITAL LETTER INSULAR S*/
+ 0xa784, /* U+00a785: LATIN SMALL LETTER INSULAR S*/
+ 0xa786, /* U+00a786: LATIN CAPITAL LETTER INSULAR T*/
+ 0xa786, /* U+00a787: LATIN SMALL LETTER INSULAR T*/
+ 0xa788, /* U+00a788: MODIFIER LETTER LOW CIRCUMFLEX ACCENT*/
+ 0xa789, /* U+00a789: MODIFIER LETTER COLON*/
+ 0xa78a, /* U+00a78a: MODIFIER LETTER SHORT EQUALS SIGN*/
+ 0xa78b, /* U+00a78b: LATIN CAPITAL LETTER SALTILLO*/
+ 0xa78b, /* U+00a78c: LATIN SMALL LETTER SALTILLO*/
+ 0xa78d, /* U+00a78d: LATIN CAPITAL LETTER TURNED H*/
+ 0xa78e, /* U+00a78e: LATIN SMALL LETTER L WITH RETROFLEX HOOK AND BELT*/
+ 0xa78f, /* U+00a78f: */
+ 0xa790, /* U+00a790: LATIN CAPITAL LETTER N WITH DESCENDER*/
+ 0xa790, /* U+00a791: LATIN SMALL LETTER N WITH DESCENDER*/
+ 0xa792, /* U+00a792: LATIN CAPITAL LETTER C WITH BAR*/
+ 0xa792, /* U+00a793: LATIN SMALL LETTER C WITH BAR*/
+ 0xa794, /* U+00a794: */
+ 0xa795, /* U+00a795: */
+ 0xa796, /* U+00a796: */
+ 0xa797, /* U+00a797: */
+ 0xa798, /* U+00a798: */
+ 0xa799, /* U+00a799: */
+ 0xa79a, /* U+00a79a: */
+ 0xa79b, /* U+00a79b: */
+ 0xa79c, /* U+00a79c: */
+ 0xa79d, /* U+00a79d: */
+ 0xa79e, /* U+00a79e: */
+ 0xa79f, /* U+00a79f: */
+ 0xa7a0, /* U+00a7a0: LATIN CAPITAL LETTER G WITH OBLIQUE STROKE*/
+ 0xa7a0, /* U+00a7a1: LATIN SMALL LETTER G WITH OBLIQUE STROKE*/
+ 0xa7a2, /* U+00a7a2: LATIN CAPITAL LETTER K WITH OBLIQUE STROKE*/
+ 0xa7a2, /* U+00a7a3: LATIN SMALL LETTER K WITH OBLIQUE STROKE*/
+ 0xa7a4, /* U+00a7a4: LATIN CAPITAL LETTER N WITH OBLIQUE STROKE*/
+ 0xa7a4, /* U+00a7a5: LATIN SMALL LETTER N WITH OBLIQUE STROKE*/
+ 0xa7a6, /* U+00a7a6: LATIN CAPITAL LETTER R WITH OBLIQUE STROKE*/
+ 0xa7a6, /* U+00a7a7: LATIN SMALL LETTER R WITH OBLIQUE STROKE*/
+ 0xa7a8, /* U+00a7a8: LATIN CAPITAL LETTER S WITH OBLIQUE STROKE*/
+ 0xa7a8, /* U+00a7a9: LATIN SMALL LETTER S WITH OBLIQUE STROKE*/
+};
+
+static const RTUNICP g_afRTUniUpper0x00ff41[] =
+{
+ 0xff21, /* U+00ff41: FULLWIDTH LATIN SMALL LETTER A*/
+ 0xff22, /* U+00ff42: FULLWIDTH LATIN SMALL LETTER B*/
+ 0xff23, /* U+00ff43: FULLWIDTH LATIN SMALL LETTER C*/
+ 0xff24, /* U+00ff44: FULLWIDTH LATIN SMALL LETTER D*/
+ 0xff25, /* U+00ff45: FULLWIDTH LATIN SMALL LETTER E*/
+ 0xff26, /* U+00ff46: FULLWIDTH LATIN SMALL LETTER F*/
+ 0xff27, /* U+00ff47: FULLWIDTH LATIN SMALL LETTER G*/
+ 0xff28, /* U+00ff48: FULLWIDTH LATIN SMALL LETTER H*/
+ 0xff29, /* U+00ff49: FULLWIDTH LATIN SMALL LETTER I*/
+ 0xff2a, /* U+00ff4a: FULLWIDTH LATIN SMALL LETTER J*/
+ 0xff2b, /* U+00ff4b: FULLWIDTH LATIN SMALL LETTER K*/
+ 0xff2c, /* U+00ff4c: FULLWIDTH LATIN SMALL LETTER L*/
+ 0xff2d, /* U+00ff4d: FULLWIDTH LATIN SMALL LETTER M*/
+ 0xff2e, /* U+00ff4e: FULLWIDTH LATIN SMALL LETTER N*/
+ 0xff2f, /* U+00ff4f: FULLWIDTH LATIN SMALL LETTER O*/
+ 0xff30, /* U+00ff50: FULLWIDTH LATIN SMALL LETTER P*/
+ 0xff31, /* U+00ff51: FULLWIDTH LATIN SMALL LETTER Q*/
+ 0xff32, /* U+00ff52: FULLWIDTH LATIN SMALL LETTER R*/
+ 0xff33, /* U+00ff53: FULLWIDTH LATIN SMALL LETTER S*/
+ 0xff34, /* U+00ff54: FULLWIDTH LATIN SMALL LETTER T*/
+ 0xff35, /* U+00ff55: FULLWIDTH LATIN SMALL LETTER U*/
+ 0xff36, /* U+00ff56: FULLWIDTH LATIN SMALL LETTER V*/
+ 0xff37, /* U+00ff57: FULLWIDTH LATIN SMALL LETTER W*/
+ 0xff38, /* U+00ff58: FULLWIDTH LATIN SMALL LETTER X*/
+ 0xff39, /* U+00ff59: FULLWIDTH LATIN SMALL LETTER Y*/
+ 0xff3a, /* U+00ff5a: FULLWIDTH LATIN SMALL LETTER Z*/
+};
+
+static const RTUNICP g_afRTUniUpper0x010428[] =
+{
+ 0x10400, /* U+010428: DESERET SMALL LETTER LONG I*/
+ 0x10401, /* U+010429: DESERET SMALL LETTER LONG E*/
+ 0x10402, /* U+01042a: DESERET SMALL LETTER LONG A*/
+ 0x10403, /* U+01042b: DESERET SMALL LETTER LONG AH*/
+ 0x10404, /* U+01042c: DESERET SMALL LETTER LONG O*/
+ 0x10405, /* U+01042d: DESERET SMALL LETTER LONG OO*/
+ 0x10406, /* U+01042e: DESERET SMALL LETTER SHORT I*/
+ 0x10407, /* U+01042f: DESERET SMALL LETTER SHORT E*/
+ 0x10408, /* U+010430: DESERET SMALL LETTER SHORT A*/
+ 0x10409, /* U+010431: DESERET SMALL LETTER SHORT AH*/
+ 0x1040a, /* U+010432: DESERET SMALL LETTER SHORT O*/
+ 0x1040b, /* U+010433: DESERET SMALL LETTER SHORT OO*/
+ 0x1040c, /* U+010434: DESERET SMALL LETTER AY*/
+ 0x1040d, /* U+010435: DESERET SMALL LETTER OW*/
+ 0x1040e, /* U+010436: DESERET SMALL LETTER WU*/
+ 0x1040f, /* U+010437: DESERET SMALL LETTER YEE*/
+ 0x10410, /* U+010438: DESERET SMALL LETTER H*/
+ 0x10411, /* U+010439: DESERET SMALL LETTER PEE*/
+ 0x10412, /* U+01043a: DESERET SMALL LETTER BEE*/
+ 0x10413, /* U+01043b: DESERET SMALL LETTER TEE*/
+ 0x10414, /* U+01043c: DESERET SMALL LETTER DEE*/
+ 0x10415, /* U+01043d: DESERET SMALL LETTER CHEE*/
+ 0x10416, /* U+01043e: DESERET SMALL LETTER JEE*/
+ 0x10417, /* U+01043f: DESERET SMALL LETTER KAY*/
+ 0x10418, /* U+010440: DESERET SMALL LETTER GAY*/
+ 0x10419, /* U+010441: DESERET SMALL LETTER EF*/
+ 0x1041a, /* U+010442: DESERET SMALL LETTER VEE*/
+ 0x1041b, /* U+010443: DESERET SMALL LETTER ETH*/
+ 0x1041c, /* U+010444: DESERET SMALL LETTER THEE*/
+ 0x1041d, /* U+010445: DESERET SMALL LETTER ES*/
+ 0x1041e, /* U+010446: DESERET SMALL LETTER ZEE*/
+ 0x1041f, /* U+010447: DESERET SMALL LETTER ESH*/
+ 0x10420, /* U+010448: DESERET SMALL LETTER ZHEE*/
+ 0x10421, /* U+010449: DESERET SMALL LETTER ER*/
+ 0x10422, /* U+01044a: DESERET SMALL LETTER EL*/
+ 0x10423, /* U+01044b: DESERET SMALL LETTER EM*/
+ 0x10424, /* U+01044c: DESERET SMALL LETTER EN*/
+ 0x10425, /* U+01044d: DESERET SMALL LETTER ENG*/
+ 0x10426, /* U+01044e: DESERET SMALL LETTER OI*/
+ 0x10427, /* U+01044f: DESERET SMALL LETTER EW*/
+};
+
+
+RT_DECL_DATA_CONST(const RTUNICASERANGE) g_aRTUniUpperRanges[] =
+{
+ { 0x000000, 0x000587, &g_afRTUniUpper0x000000[0] },
+ { 0x001d79, 0x0024ea, &g_afRTUniUpper0x001d79[0] },
+ { 0x002c30, 0x002d2e, &g_afRTUniUpper0x002c30[0] },
+ { 0x00a641, 0x00a7aa, &g_afRTUniUpper0x00a641[0] },
+ { 0x00ff41, 0x00ff5b, &g_afRTUniUpper0x00ff41[0] },
+ { 0x010428, 0x010450, &g_afRTUniUpper0x010428[0] },
+ { ~(RTUNICP)0, ~(RTUNICP)0, NULL }
+};
+
diff --git a/src/VBox/Runtime/common/string/uniread.cpp b/src/VBox/Runtime/common/string/uniread.cpp
new file mode 100644
index 00000000..08ffec7d
--- /dev/null
+++ b/src/VBox/Runtime/common/string/uniread.cpp
@@ -0,0 +1,1336 @@
+/* $Id: uniread.cpp $ */
+/** @file
+ * IPRT - Unicode Specification Reader.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/types.h>
+#include <iprt/stdarg.h>
+#include <iprt/ctype.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#ifdef _MSC_VER
+# include <direct.h>
+#else
+# include <unistd.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The file we're currently parsing. */
+static const char *g_pszCurFile;
+/** The current line number. */
+static unsigned g_iLine;
+/** The current output file. */
+static FILE *g_pCurOutFile;
+
+
+/**
+ * Exit the program after printing a parse error.
+ *
+ * @param pszFormat The message.
+ * @param ... Format arguments.
+ */
+static DECL_NO_RETURN(void) ParseError(const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ fprintf(stderr, "parse error: %s:%u: ", g_pszCurFile, g_iLine);
+ vfprintf(stderr, pszFormat, va);
+ va_end(va);
+ exit(1);
+}
+
+/**
+ * Strip a line.
+ * @returns pointer to first non-blank char.
+ * @param pszLine The line string to strip.
+ */
+static char *StripLine(char *pszLine)
+{
+ while (*pszLine == ' ' || *pszLine == '\t')
+ pszLine++;
+
+ char *psz = strchr(pszLine, '#');
+ if (psz)
+ *psz = '\0';
+ else
+ psz = strchr(pszLine, '\0');
+ while (psz > pszLine)
+ {
+ switch (psz[-1])
+ {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ *--psz = '\0';
+ continue;
+ }
+ break;
+ }
+
+ return pszLine;
+}
+
+
+/**
+ * Checks if the line is blank or a comment line and should be skipped.
+ * @returns true/false.
+ * @param pszLine The line to consider.
+ */
+static bool IsCommentOrBlankLine(const char *pszLine)
+{
+ while (*pszLine == ' ' || *pszLine == '\t' || *pszLine == '\n' || *pszLine == '\r')
+ pszLine++;
+ return *pszLine == '#' || *pszLine == '\0';
+}
+
+
+/**
+ * Get the first field in the string.
+ *
+ * @returns Pointer to the next field.
+ * @param ppsz Where to store the pointer to the next field.
+ * @param pszLine The line string. (could also be *ppsz from a FirstNext call)
+ */
+static char *FirstField(char **ppsz, char *pszLine)
+{
+ char *psz = strchr(pszLine, ';');
+ if (!psz)
+ *ppsz = psz = strchr(pszLine, '\0');
+ else
+ {
+ *psz = '\0';
+ *ppsz = psz + 1;
+ }
+
+ /* strip */
+ while (*pszLine == ' ' || *pszLine == '\t' || *pszLine == '\r' || *pszLine == '\n')
+ pszLine++;
+ while (psz > pszLine)
+ {
+ switch (psz[-1])
+ {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ *--psz = '\0';
+ continue;
+ }
+ break;
+ }
+ return pszLine;
+}
+
+
+/**
+ * Get the next field in a field enumeration.
+ *
+ * @returns Pointer to the next field.
+ * @param ppsz Where to get and store the string position.
+ */
+static char *NextField(char **ppsz)
+{
+ return FirstField(ppsz, *ppsz);
+}
+
+
+/**
+ * Splits a decomposition field.
+ *
+ * This may start with a type that is enclosed in angle brackets.
+ *
+ * @returns Pointer to the mapping values following the type. @a *ppsz if empty.
+ * @param ppszType Pointer to the type field pointer. On input the type
+ * field contains the combined type and mapping string. On
+ * output this should only contain the type, no angle
+ * brackets. If no type specified, it is replaced with an
+ * empty string (const).
+ */
+static char *SplitDecompField(char **ppszType)
+{
+ /* Empty field? */
+ char *psz = *ppszType;
+ if (!*psz)
+ return psz;
+
+ /* No type? */
+ if (*psz != '<')
+ {
+ *ppszType = (char *)"";
+ return psz;
+ }
+
+ /* Split out the type. */
+ *ppszType = ++psz;
+ psz = strchr(psz, '>');
+ if (!psz)
+ {
+ ParseError("Bad Decomposition Type/Mappings\n");
+ /* not reached: return *ppszType; */
+ }
+ *psz++ = '\0';
+
+ psz = StripLine(psz);
+ if (!*psz)
+ ParseError("Missing decomposition mappings\n");
+ return psz;
+}
+
+/**
+ * Converts a code point field to a number.
+ * @returns Code point.
+ * @param psz The field string.
+ */
+static RTUNICP ToNum(const char *psz)
+{
+ char *pszEnd = NULL;
+ unsigned long ul = strtoul(psz, &pszEnd, 16);
+ if (pszEnd && *pszEnd)
+ ParseError("failed converting '%s' to a number!\n", psz);
+ return (RTUNICP)ul;
+}
+
+
+/**
+ * Same as ToNum except that if the field is empty the Default is returned.
+ */
+static RTUNICP ToNumDefault(const char *psz, RTUNICP Default)
+{
+ if (*psz)
+ return ToNum(psz);
+ return Default;
+}
+
+
+/**
+ * Converts a code point range to numbers.
+ * @returns The start code point.\
+ * @returns ~(RTUNICP)0 on failure.
+ * @param psz The field string.
+ * @param pLast Where to store the last code point in the range.
+ */
+static RTUNICP ToRange(const char *psz, PRTUNICP pLast)
+{
+ char *pszEnd = NULL;
+ unsigned long ulStart = strtoul(psz, &pszEnd, 16);
+ unsigned long ulLast = ulStart;
+ if (pszEnd && *pszEnd)
+ {
+ if (*pszEnd == '.')
+ {
+ while (*pszEnd == '.')
+ pszEnd++;
+ ulLast = strtoul(pszEnd, &pszEnd, 16);
+ if (pszEnd && *pszEnd)
+ {
+ ParseError("failed converting '%s' to a number!\n", psz);
+ /* not reached: return ~(RTUNICP)0;*/
+ }
+ }
+ else
+ {
+ ParseError("failed converting '%s' to a number!\n", psz);
+ /* not reached: return ~(RTUNICP)0; */
+ }
+ }
+ *pLast = (RTUNICP)ulLast;
+ return (RTUNICP)ulStart;
+
+}
+
+/**
+ * For converting the decomposition mappings field and similar.
+ *
+ * @returns Mapping array or NULL if none.
+ * @param psz The string to convert. Can be empty.
+ * @param pcEntries Where to store the number of entries.
+ * @param cMax The max number of entries.
+ */
+static PRTUNICP ToMapping(char *psz, unsigned *pcEntries, unsigned cMax)
+{
+ PRTUNICP paCps = NULL;
+ unsigned cAlloc = 0;
+ unsigned i = 0;
+
+ /* Convert the code points. */
+ while (psz)
+ {
+ /* skip leading spaces */
+ while (RT_C_IS_BLANK(*psz))
+ psz++;
+
+ /* the end? */
+ if (!*psz)
+ break;
+
+ /* room left? */
+ if (i >= cMax)
+ {
+ ParseError("Too many mappings.\n");
+ /* not reached: break; */
+ }
+ if (i >= cAlloc)
+ {
+ cAlloc += 4;
+ paCps = (PRTUNICP)realloc(paCps, cAlloc * sizeof(paCps[0]));
+ if (!paCps)
+ {
+ fprintf(stderr, "out of memory (%u)\n", (unsigned)(cAlloc * sizeof(paCps[0])));
+ exit(1);
+ }
+ }
+
+ /* Find the end. */
+ char *pszThis = psz;
+ while (RT_C_IS_XDIGIT(*psz))
+ psz++;
+ if (*psz && !RT_C_IS_BLANK(*psz))
+ ParseError("Malformed mappings.\n");
+ if (*psz)
+ *psz++ = '\0';
+
+ /* Convert to number and add it. */
+ paCps[i++] = ToNum(pszThis);
+ }
+
+ *pcEntries = i;
+ return paCps;
+}
+
+
+/**
+ * Duplicate a string, optimize certain strings to save memory.
+ *
+ * @returns Pointer to string copy.
+ * @param pszStr The string to duplicate.
+ */
+static char *DupStr(const char *pszStr)
+{
+ if (!*pszStr)
+ return (char*)"";
+ char *psz = strdup(pszStr);
+ if (psz)
+ return psz;
+
+ fprintf(stderr, "out of memory!\n");
+ exit(1);
+}
+
+
+/**
+ * Array of all possible and impossible unicode code points as of 4.1
+ */
+struct CPINFO
+{
+ RTUNICP CodePoint;
+ RTUNICP SimpleUpperCaseMapping;
+ RTUNICP SimpleLowerCaseMapping;
+ RTUNICP SimpleTitleCaseMapping;
+ unsigned CanonicalCombiningClass;
+ const char *pszDecompositionType;
+ unsigned cDecompositionMapping;
+ PRTUNICP paDecompositionMapping;
+ const char *pszName;
+ /** Set if this is an unused entry */
+ unsigned fNullEntry : 1;
+
+ unsigned fAlphabetic : 1;
+ unsigned fASCIIHexDigit : 1;
+ unsigned fBidiControl : 1;
+ unsigned fCaseIgnorable : 1;
+ unsigned fCased : 1;
+ unsigned fChangesWhenCasefolded : 1;
+ unsigned fChangesWhenCasemapped : 1;
+ unsigned fChangesWhenLowercased : 1;
+ unsigned fChangesWhenTitlecased : 1;
+ unsigned fChangesWhenUppercased : 1;
+ unsigned fDash : 1;
+ unsigned fDefaultIgnorableCodePoint : 1;
+ unsigned fDeprecated : 1;
+ unsigned fDiacritic : 1;
+ unsigned fExtender : 1;
+ unsigned fGraphemeBase : 1;
+ unsigned fGraphemeExtend : 1;
+ unsigned fGraphemeLink : 1;
+ unsigned fHexDigit : 1;
+ unsigned fHyphen : 1;
+ unsigned fIDContinue : 1;
+ unsigned fIdeographic : 1;
+ unsigned fIDSBinaryOperator : 1;
+ unsigned fIDStart : 1;
+ unsigned fIDSTrinaryOperator : 1;
+ unsigned fJoinControl : 1;
+ unsigned fLogicalOrderException : 1;
+ unsigned fLowercase : 1;
+ unsigned fMath : 1;
+ unsigned fNoncharacterCodePoint : 1;
+ unsigned fOtherAlphabetic : 1;
+ unsigned fOtherDefaultIgnorableCodePoint : 1;
+ unsigned fOtherGraphemeExtend : 1;
+ unsigned fOtherIDContinue : 1;
+ unsigned fOtherIDStart : 1;
+ unsigned fOtherLowercase : 1;
+ unsigned fOtherMath : 1;
+ unsigned fOtherUppercase : 1;
+ unsigned fPatternSyntax : 1;
+ unsigned fPatternWhiteSpace : 1;
+ unsigned fQuotationMark : 1;
+ unsigned fRadical : 1;
+ unsigned fSoftDotted : 1;
+ unsigned fSTerm : 1;
+ unsigned fTerminalPunctuation : 1;
+ unsigned fUnifiedIdeograph : 1;
+ unsigned fUppercase : 1;
+ unsigned fVariationSelector : 1;
+ unsigned fWhiteSpace : 1;
+ unsigned fXIDContinue : 1;
+ unsigned fXIDStart : 1;
+
+ /** @name DerivedNormalizationProps.txt
+ * @{ */
+ unsigned fFullCompositionExclusion : 1;
+ unsigned fInvNFC_QC : 2; /**< If 1 (NFC_QC == N) then code point 100% sure not part of NFC string. */
+ unsigned fInvNFD_QC : 2; /**< If 1 (NFD_QC == N) then code point 100% sure not part of NFD string. */
+ unsigned fInvNFKC_QC : 2;
+ unsigned fInvNFKD_QC : 2;
+ unsigned fExpandsOnNFC : 1;
+ unsigned fExpandsOnNFD : 1;
+ unsigned fExpandsOnNFKC : 1;
+ unsigned fExpandsOnNFKD : 1;
+ /** @} */
+
+ /* unprocessed stuff, so far. */
+ const char *pszGeneralCategory;
+ const char *pszBidiClass;
+ const char *pszNumericType;
+ const char *pszNumericValueD;
+ const char *pszNumericValueN;
+ const char *pszBidiMirrored;
+ const char *pszUnicode1Name;
+ const char *pszISOComment;
+} g_aCPInfo[0x110000];
+
+
+/**
+ * Creates a 'null' entry at i.
+ * @param i The entry in question.
+ */
+static void NullEntry(unsigned i)
+{
+ g_aCPInfo[i].CodePoint = i;
+ g_aCPInfo[i].fNullEntry = 1;
+ g_aCPInfo[i].SimpleUpperCaseMapping = i;
+ g_aCPInfo[i].SimpleLowerCaseMapping = i;
+ g_aCPInfo[i].SimpleTitleCaseMapping = i;
+ g_aCPInfo[i].pszDecompositionType = "";
+ g_aCPInfo[i].cDecompositionMapping = 0;
+ g_aCPInfo[i].paDecompositionMapping = NULL;
+ g_aCPInfo[i].pszName = "";
+ g_aCPInfo[i].pszGeneralCategory = "";
+ g_aCPInfo[i].pszBidiClass = "";
+ g_aCPInfo[i].pszNumericType = "";
+ g_aCPInfo[i].pszNumericValueD = "";
+ g_aCPInfo[i].pszNumericValueN = "";
+ g_aCPInfo[i].pszBidiMirrored = "";
+ g_aCPInfo[i].pszUnicode1Name = "";
+ g_aCPInfo[i].pszISOComment = "";
+}
+
+
+/**
+ * Open a file for reading, optionally with a base path prefixed.
+ *
+ * @returns file stream on success, NULL w/ complaint on failure.
+ * @param pszBasePath The base path, can be NULL.
+ * @param pszFilename The name of the file to open.
+ */
+static FILE *OpenFile(const char *pszBasePath, const char *pszFilename)
+{
+ FILE *pFile;
+ if ( !pszBasePath
+ || *pszFilename == '/'
+#if defined(_MSC_VER) || defined(__OS2__)
+ || *pszFilename == '\\'
+ || (*pszFilename && pszFilename[1] == ':')
+#endif
+ )
+ {
+ pFile = fopen(pszFilename, "r");
+ if (!pFile)
+ fprintf(stderr, "uniread: failed to open '%s' for reading\n", pszFilename);
+ }
+ else
+ {
+ size_t cchBasePath = strlen(pszBasePath);
+ size_t cchFilename = strlen(pszFilename);
+ char *pszFullName = (char *)malloc(cchBasePath + 1 + cchFilename + 1);
+ if (!pszFullName)
+ {
+ fprintf(stderr, "uniread: failed to allocate %d bytes\n", (int)(cchBasePath + 1 + cchFilename + 1));
+ return NULL;
+ }
+
+ memcpy(pszFullName, pszBasePath, cchBasePath);
+ pszFullName[cchBasePath] = '/';
+ memcpy(&pszFullName[cchBasePath + 1], pszFilename, cchFilename + 1);
+
+ pFile = fopen(pszFullName, "r");
+ if (!pFile)
+ fprintf(stderr, "uniread: failed to open '%s' for reading\n", pszFullName);
+ free(pszFullName);
+ }
+ g_pszCurFile = pszFilename;
+ g_iLine = 0;
+ return pFile;
+}
+
+
+/**
+ * Wrapper around fgets that keep track of the line number.
+ *
+ * @returns See fgets.
+ * @param pszBuf The buffer. See fgets for output definition.
+ * @param cbBuf The buffer size.
+ * @param pFile The file to read from.
+ */
+static char *GetLineFromFile(char *pszBuf, int cbBuf, FILE *pFile)
+{
+ g_iLine++;
+ return fgets(pszBuf, cbBuf, pFile);
+}
+
+
+/**
+ * Closes a file opened by OpenFile
+ *
+ * @param pFile The file to close.
+ */
+static void CloseFile(FILE *pFile)
+{
+ g_pszCurFile = NULL;
+ g_iLine = 0;
+ fclose(pFile);
+}
+
+
+/**
+ * Read the UnicodeData.txt file.
+ * @returns 0 on success.
+ * @returns !0 on failure.
+ * @param pszBasePath The base path, can be NULL.
+ * @param pszFilename The name of the file.
+ */
+static int ReadUnicodeData(const char *pszBasePath, const char *pszFilename)
+{
+ /*
+ * Open input.
+ */
+ FILE *pFile = OpenFile(pszBasePath, pszFilename);
+ if (!pFile)
+ return 1;
+
+ /*
+ * Parse the input and spit out the output.
+ */
+ char szLine[4096];
+ RTUNICP i = 0;
+ while (GetLineFromFile(szLine, sizeof(szLine), pFile) != NULL)
+ {
+ if (IsCommentOrBlankLine(szLine))
+ continue;
+
+ char *pszCurField;
+ char *pszCodePoint = FirstField(&pszCurField, StripLine(szLine)); /* 0 */
+ char *pszName = NextField(&pszCurField); /* 1 */
+ char *pszGeneralCategory = NextField(&pszCurField); /* 2 */
+ char *pszCanonicalCombiningClass = NextField(&pszCurField); /* 3 */
+ char *pszBidiClass = NextField(&pszCurField); /* 4 */
+ char *pszDecompositionType = NextField(&pszCurField); /* 5 */
+ char *pszDecompositionMapping = SplitDecompField(&pszDecompositionType);
+ char *pszNumericType = NextField(&pszCurField); /* 6 */
+ char *pszNumericValueD = NextField(&pszCurField); /* 7 */
+ char *pszNumericValueN = NextField(&pszCurField); /* 8 */
+ char *pszBidiMirrored = NextField(&pszCurField); /* 9 */
+ char *pszUnicode1Name = NextField(&pszCurField); /* 10 */
+ char *pszISOComment = NextField(&pszCurField); /* 11 */
+ char *pszSimpleUpperCaseMapping = NextField(&pszCurField); /* 12 */
+ char *pszSimpleLowerCaseMapping = NextField(&pszCurField); /* 13 */
+ char *pszSimpleTitleCaseMapping = NextField(&pszCurField); /* 14 */
+
+ RTUNICP CodePoint = ToNum(pszCodePoint);
+ if (CodePoint >= RT_ELEMENTS(g_aCPInfo))
+ {
+ ParseError("U+05X is out of range\n", CodePoint);
+ /* not reached: continue;*/
+ }
+
+ /* catchup? */
+ while (i < CodePoint)
+ NullEntry(i++);
+ if (i != CodePoint)
+ {
+ ParseError("i=%d CodePoint=%u\n", i, CodePoint);
+ /* not reached: CloseFile(pFile);
+ return 1; */
+ }
+
+ /* this one */
+ g_aCPInfo[i].CodePoint = i;
+ g_aCPInfo[i].fNullEntry = 0;
+ g_aCPInfo[i].pszName = DupStr(pszName);
+ g_aCPInfo[i].SimpleUpperCaseMapping = ToNumDefault(pszSimpleUpperCaseMapping, CodePoint);
+ g_aCPInfo[i].SimpleLowerCaseMapping = ToNumDefault(pszSimpleLowerCaseMapping, CodePoint);
+ g_aCPInfo[i].SimpleTitleCaseMapping = ToNumDefault(pszSimpleTitleCaseMapping, CodePoint);
+ g_aCPInfo[i].CanonicalCombiningClass = ToNum(pszCanonicalCombiningClass);
+ g_aCPInfo[i].pszDecompositionType = DupStr(pszDecompositionType);
+ g_aCPInfo[i].paDecompositionMapping = ToMapping(pszDecompositionMapping, &g_aCPInfo[i].cDecompositionMapping, 20);
+ g_aCPInfo[i].pszGeneralCategory = DupStr(pszGeneralCategory);
+ g_aCPInfo[i].pszBidiClass = DupStr(pszBidiClass);
+ g_aCPInfo[i].pszNumericType = DupStr(pszNumericType);
+ g_aCPInfo[i].pszNumericValueD = DupStr(pszNumericValueD);
+ g_aCPInfo[i].pszNumericValueN = DupStr(pszNumericValueN);
+ g_aCPInfo[i].pszBidiMirrored = DupStr(pszBidiMirrored);
+ g_aCPInfo[i].pszUnicode1Name = DupStr(pszUnicode1Name);
+ g_aCPInfo[i].pszISOComment = DupStr(pszISOComment);
+ i++;
+ }
+
+ /* catchup? */
+ while (i < RT_ELEMENTS(g_aCPInfo))
+ NullEntry(i++);
+ CloseFile(pFile);
+
+ return 0;
+}
+
+
+/**
+ * Generates excluded data.
+ *
+ * @returns 0 on success, exit code on failure.
+ */
+static int GenerateExcludedData(void)
+{
+ /*
+ * Hangul Syllables U+AC00 to U+D7A3.
+ */
+ for (RTUNICP i = 0xac00; i <= 0xd7a3; i++)
+ {
+ g_aCPInfo[i].fNullEntry = 0;
+ g_aCPInfo[i].fInvNFD_QC = 1;
+ /** @todo generate the decomposition: http://unicode.org/reports/tr15/#Hangul
+ * */
+ }
+
+ /** @todo
+ * CJK Ideographs Extension A (U+3400 - U+4DB5)
+ * CJK Ideographs (U+4E00 - U+9FA5)
+ * CJK Ideograph Extension B (U+20000 - U+2A6D6)
+ * CJK Ideograph Extension C (U+2A700 - U+2B734)
+ */
+
+ return 0;
+}
+
+
+
+/**
+ * Worker for ApplyProperty that handles a yes, no, maybe property value.
+ *
+ * @returns 0 (NO), 1 (YES), 2 (MAYBE).
+ * @param ppszNextField The field cursor, input and output.
+ */
+static int YesNoMaybePropertyValue(char **ppszNextField)
+{
+ if (!**ppszNextField)
+ ParseError("Missing Y/N/M field\n");
+ else
+ {
+ char *psz = NextField(ppszNextField);
+ if (!strcmp(psz, "N"))
+ return 0;
+ if (!strcmp(psz, "Y"))
+ return 1;
+ if (!strcmp(psz, "M"))
+ return 2;
+ ParseError("Unexpected Y/N/M value: '%s'\n", psz);
+ }
+ /* not reached: return 0; */
+}
+
+
+/**
+ * Inverted version of YesNoMaybePropertyValue
+ *
+ * @returns 1 (NO), 0 (YES), 2 (MAYBE).
+ * @param ppszNextField The field cursor, input and output.
+ */
+static int YesNoMaybePropertyValueInv(char **ppszNextField)
+{
+ unsigned rc = YesNoMaybePropertyValue(ppszNextField);
+ switch (rc)
+ {
+ case 0: return 1;
+ case 1: return 0;
+ default: return rc;
+ }
+}
+
+
+/**
+ * Applies a property to a code point.
+ *
+ * @param StartCP The code point.
+ * @param pszProperty The property name.
+ * @param pszNextField The next field.
+ */
+static void ApplyProperty(RTUNICP StartCP, const char *pszProperty, char *pszNextField)
+{
+ if (StartCP >= RT_ELEMENTS(g_aCPInfo))
+ {
+ ParseError("U+%06X is out of the g_aCPInfo range.\n", StartCP);
+ /* not reached: return; */
+ }
+ struct CPINFO *pCPInfo = &g_aCPInfo[StartCP];
+ /* string switch */
+ if (!strcmp(pszProperty, "ASCII_Hex_Digit")) pCPInfo->fASCIIHexDigit = 1;
+ else if (!strcmp(pszProperty, "Alphabetic")) pCPInfo->fAlphabetic = 1;
+ else if (!strcmp(pszProperty, "Bidi_Control")) pCPInfo->fBidiControl = 1;
+ else if (!strcmp(pszProperty, "Case_Ignorable")) pCPInfo->fCaseIgnorable = 1;
+ else if (!strcmp(pszProperty, "Cased")) pCPInfo->fCased = 1;
+ else if (!strcmp(pszProperty, "Changes_When_Casefolded")) pCPInfo->fChangesWhenCasefolded = 1;
+ else if (!strcmp(pszProperty, "Changes_When_Casemapped")) pCPInfo->fChangesWhenCasemapped = 1;
+ else if (!strcmp(pszProperty, "Changes_When_Lowercased")) pCPInfo->fChangesWhenLowercased = 1;
+ else if (!strcmp(pszProperty, "Changes_When_Titlecased")) pCPInfo->fChangesWhenTitlecased = 1;
+ else if (!strcmp(pszProperty, "Changes_When_Uppercased")) pCPInfo->fChangesWhenUppercased = 1;
+ else if (!strcmp(pszProperty, "Dash")) pCPInfo->fDash = 1;
+ else if (!strcmp(pszProperty, "Default_Ignorable_Code_Point")) pCPInfo->fDefaultIgnorableCodePoint = 1;
+ else if (!strcmp(pszProperty, "Deprecated")) pCPInfo->fDeprecated = 1;
+ else if (!strcmp(pszProperty, "Diacritic")) pCPInfo->fDiacritic = 1;
+ else if (!strcmp(pszProperty, "Extender")) pCPInfo->fExtender = 1;
+ else if (!strcmp(pszProperty, "Grapheme_Base")) pCPInfo->fGraphemeBase = 1;
+ else if (!strcmp(pszProperty, "Grapheme_Extend")) pCPInfo->fGraphemeExtend = 1;
+ else if (!strcmp(pszProperty, "Grapheme_Link")) pCPInfo->fGraphemeLink = 1;
+ else if (!strcmp(pszProperty, "Hex_Digit")) pCPInfo->fHexDigit = 1;
+ else if (!strcmp(pszProperty, "Hyphen")) pCPInfo->fHyphen = 1;
+ else if (!strcmp(pszProperty, "ID_Continue")) pCPInfo->fIDContinue = 1;
+ else if (!strcmp(pszProperty, "ID_Start")) pCPInfo->fIDStart = 1;
+ else if (!strcmp(pszProperty, "Ideographic")) pCPInfo->fIdeographic = 1;
+ else if (!strcmp(pszProperty, "IDS_Binary_Operator")) pCPInfo->fIDSBinaryOperator = 1;
+ else if (!strcmp(pszProperty, "IDS_Trinary_Operator")) pCPInfo->fIDSTrinaryOperator = 1;
+ else if (!strcmp(pszProperty, "Join_Control")) pCPInfo->fJoinControl = 1;
+ else if (!strcmp(pszProperty, "Logical_Order_Exception")) pCPInfo->fLogicalOrderException = 1;
+ else if (!strcmp(pszProperty, "Lowercase")) pCPInfo->fLowercase = 1;
+ else if (!strcmp(pszProperty, "Math")) pCPInfo->fMath = 1;
+ else if (!strcmp(pszProperty, "Noncharacter_Code_Point")) pCPInfo->fNoncharacterCodePoint = 1;
+ else if (!strcmp(pszProperty, "Other_Alphabetic")) pCPInfo->fOtherAlphabetic = 1;
+ else if (!strcmp(pszProperty, "Other_Default_Ignorable_Code_Point")) pCPInfo->fOtherDefaultIgnorableCodePoint = 1;
+ else if (!strcmp(pszProperty, "Other_Grapheme_Extend")) pCPInfo->fOtherGraphemeExtend = 1;
+ else if (!strcmp(pszProperty, "Other_ID_Continue")) pCPInfo->fOtherIDContinue = 1;
+ else if (!strcmp(pszProperty, "Other_ID_Start")) pCPInfo->fOtherIDStart = 1;
+ else if (!strcmp(pszProperty, "Other_Lowercase")) pCPInfo->fOtherLowercase = 1;
+ else if (!strcmp(pszProperty, "Other_Math")) pCPInfo->fOtherMath = 1;
+ else if (!strcmp(pszProperty, "Other_Uppercase")) pCPInfo->fOtherUppercase = 1;
+ else if (!strcmp(pszProperty, "Pattern_Syntax")) pCPInfo->fPatternSyntax = 1;
+ else if (!strcmp(pszProperty, "Pattern_White_Space")) pCPInfo->fPatternWhiteSpace = 1;
+ else if (!strcmp(pszProperty, "Quotation_Mark")) pCPInfo->fQuotationMark = 1;
+ else if (!strcmp(pszProperty, "Radical")) pCPInfo->fRadical = 1;
+ else if (!strcmp(pszProperty, "Soft_Dotted")) pCPInfo->fSoftDotted = 1;
+ else if (!strcmp(pszProperty, "STerm")) pCPInfo->fSTerm = 1;
+ else if (!strcmp(pszProperty, "Terminal_Punctuation")) pCPInfo->fTerminalPunctuation = 1;
+ else if (!strcmp(pszProperty, "Unified_Ideograph")) pCPInfo->fUnifiedIdeograph = 1;
+ else if (!strcmp(pszProperty, "Uppercase")) pCPInfo->fUppercase = 1;
+ else if (!strcmp(pszProperty, "Variation_Selector")) pCPInfo->fVariationSelector = 1;
+ else if (!strcmp(pszProperty, "White_Space")) pCPInfo->fWhiteSpace = 1;
+ else if (!strcmp(pszProperty, "XID_Continue")) pCPInfo->fXIDContinue = 1;
+ else if (!strcmp(pszProperty, "XID_Start")) pCPInfo->fXIDStart = 1;
+ /* DerivedNormalizationProps: */
+ else if (!strcmp(pszProperty, "FC_NFKC")) return; /* ignored */
+ else if (!strcmp(pszProperty, "Full_Composition_Exclusion")) pCPInfo->fFullCompositionExclusion = 1;
+ else if (!strcmp(pszProperty, "NFC_QC")) pCPInfo->fInvNFC_QC = YesNoMaybePropertyValueInv(&pszNextField);
+ else if (!strcmp(pszProperty, "NFD_QC")) pCPInfo->fInvNFD_QC = YesNoMaybePropertyValueInv(&pszNextField);
+ else if (!strcmp(pszProperty, "NFKC_QC")) pCPInfo->fInvNFKC_QC = YesNoMaybePropertyValueInv(&pszNextField);
+ else if (!strcmp(pszProperty, "NFKD_QC")) pCPInfo->fInvNFKD_QC = YesNoMaybePropertyValueInv(&pszNextField);
+ else if (!strcmp(pszProperty, "Expands_On_NFC")) pCPInfo->fExpandsOnNFC = 1;
+ else if (!strcmp(pszProperty, "Expands_On_NFD")) pCPInfo->fExpandsOnNFD = 1;
+ else if (!strcmp(pszProperty, "Expands_On_NFKC")) pCPInfo->fExpandsOnNFKC = 1;
+ else if (!strcmp(pszProperty, "Expands_On_NFKD")) pCPInfo->fExpandsOnNFKD = 1;
+ else if (!strcmp(pszProperty, "NFKC_CF")) return; /*ignore */
+ else if (!strcmp(pszProperty, "Changes_When_NFKC_Casefolded")) return; /*ignore */
+ else
+ {
+ ParseError("Unknown property '%s'\n", pszProperty);
+ /* not reached: return; */
+ }
+
+ if (pszNextField && *pszNextField)
+ ParseError("Unexpected next field: '%s'\n", pszNextField);
+}
+
+
+/**
+ * Reads a property file.
+ *
+ * There are several property files, this code can read all
+ * of those but will only make use of the properties it recognizes.
+ *
+ * @returns 0 on success.
+ * @returns !0 on failure.
+ * @param pszBasePath The base path, can be NULL.
+ * @param pszFilename The name of the file.
+ */
+static int ReadProperties(const char *pszBasePath, const char *pszFilename)
+{
+ /*
+ * Open input.
+ */
+ FILE *pFile = OpenFile(pszBasePath, pszFilename);
+ if (!pFile)
+ return 1;
+
+ /*
+ * Parse the input and spit out the output.
+ */
+ char szLine[4096];
+ while (GetLineFromFile(szLine, sizeof(szLine), pFile) != NULL)
+ {
+ if (IsCommentOrBlankLine(szLine))
+ continue;
+ char *pszCurField;
+ char *pszRange = FirstField(&pszCurField, StripLine(szLine));
+ char *pszProperty = NextField(&pszCurField);
+ if (!*pszProperty)
+ {
+ ParseError("no property field.\n");
+ /* not reached: continue; */
+ }
+
+ RTUNICP LastCP;
+ RTUNICP StartCP = ToRange(pszRange, &LastCP);
+ if (StartCP == ~(RTUNICP)0)
+ continue;
+
+ while (StartCP <= LastCP)
+ ApplyProperty(StartCP++, pszProperty, pszCurField);
+ }
+
+ CloseFile(pFile);
+
+ return 0;
+}
+
+
+/**
+ * Append a flag to the string.
+ */
+static char *AppendFlag(char *psz, const char *pszFlag)
+{
+ char *pszEnd = strchr(psz, '\0');
+ if (pszEnd != psz)
+ {
+ *pszEnd++ = ' ';
+ *pszEnd++ = '|';
+ *pszEnd++ = ' ';
+ }
+ strcpy(pszEnd, pszFlag);
+ return psz;
+}
+
+/**
+ * Calcs the flags for a code point.
+ * @returns true if there is a flag.
+ * @returns false if the isn't.
+ */
+static bool CalcFlags(struct CPINFO *pInfo, char *pszFlags)
+{
+ pszFlags[0] = '\0';
+ /** @todo read the specs on this other vs standard stuff, and check out the finer points */
+ if (pInfo->fAlphabetic || pInfo->fOtherAlphabetic)
+ AppendFlag(pszFlags, "RTUNI_ALPHA");
+ if (pInfo->fHexDigit || pInfo->fASCIIHexDigit)
+ AppendFlag(pszFlags, "RTUNI_XDIGIT");
+ if (!strcmp(pInfo->pszGeneralCategory, "Nd"))
+ AppendFlag(pszFlags, "RTUNI_DDIGIT");
+ if (pInfo->fWhiteSpace)
+ AppendFlag(pszFlags, "RTUNI_WSPACE");
+ if (pInfo->fUppercase || pInfo->fOtherUppercase)
+ AppendFlag(pszFlags, "RTUNI_UPPER");
+ if (pInfo->fLowercase || pInfo->fOtherLowercase)
+ AppendFlag(pszFlags, "RTUNI_LOWER");
+ //if (pInfo->???)
+ // AppendFlag(pszFlags, "RTUNI_BSPACE");
+#if 0
+ if (pInfo->fInvNFD_QC != 0 || pInfo->fInvNFC_QC != 0)
+ {
+ AppendFlag(pszFlags, "RTUNI_QC_NFX");
+ if (!pInfo->paDecompositionMapping && pInfo->fInvNFD_QC)
+ fprintf(stderr, "uniread: U+%05X is QC_NFD but has no mappings.\n", pInfo->CodePoint);
+ else if (*pInfo->pszDecompositionType && pInfo->fInvNFD_QC)
+ fprintf(stderr, "uniread: U+%05X is QC_NFD but has no canonical mappings.\n", pInfo->CodePoint);
+ }
+ else if (pInfo->paDecompositionMapping && !*pInfo->pszDecompositionType)
+ fprintf(stderr, "uniread: U+%05X is not QC_NFX but has canonical mappings.\n", pInfo->CodePoint);
+#endif
+
+ if (!*pszFlags)
+ {
+ pszFlags[0] = '0';
+ pszFlags[1] = '\0';
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Closes the primary output stream.
+ */
+static int Stream1Close(void)
+{
+ if (g_pCurOutFile && g_pCurOutFile != stdout && g_pCurOutFile != stderr)
+ {
+ if (fclose(g_pCurOutFile) != 0)
+ {
+ fprintf(stderr, "Error closing output file.\n");
+ return -1;
+ }
+ }
+ g_pCurOutFile = NULL;
+ return 0;
+}
+
+
+/**
+ * Initializes the 1st stream to output to a given file.
+ */
+static int Stream1Init(const char *pszName)
+{
+ int rc = Stream1Close();
+ if (!rc)
+ {
+ g_pCurOutFile = fopen(pszName, "w");
+ if (!g_pCurOutFile)
+ {
+ fprintf(stderr, "Error opening output file '%s'.\n", pszName);
+ rc = -1;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * printf wrapper for the primary output stream.
+ *
+ * @returns See vfprintf.
+ * @param pszFormat The vfprintf format string.
+ * @param ... The format arguments.
+ */
+static int Stream1Printf(const char *pszFormat, ...)
+{
+ int cch;
+ va_list va;
+ va_start(va, pszFormat);
+ cch = vfprintf(g_pCurOutFile, pszFormat, va);
+ va_end(va);
+ return cch;
+}
+
+
+/** the data store for stream two. */
+static char g_szStream2[10240];
+static unsigned volatile g_offStream2 = 0;
+
+/**
+ * Initializes the 2nd steam.
+ */
+static void Stream2Init(void)
+{
+ g_szStream2[0] = '\0';
+ g_offStream2 = 0;
+}
+
+/**
+ * Flushes the 2nd stream to stdout.
+ */
+static int Stream2Flush(void)
+{
+ g_szStream2[g_offStream2] = '\0';
+ Stream1Printf("%s", g_szStream2);
+ Stream2Init();
+ return 0;
+}
+
+/**
+ * printf to the 2nd stream.
+ */
+static int Stream2Printf(const char *pszFormat, ...)
+{
+ unsigned offStream2 = g_offStream2;
+ va_list va;
+ va_start(va, pszFormat);
+ int cch = vsprintf(&g_szStream2[offStream2], pszFormat, va);
+ va_end(va);
+ offStream2 += cch;
+ if (offStream2 >= sizeof(g_szStream2))
+ {
+ fprintf(stderr, "error: stream2 overflow!\n");
+ exit(1);
+ }
+ g_offStream2 = offStream2;
+ return cch;
+}
+
+
+/**
+ * Print the unidata.cpp file header and include list.
+ */
+int PrintHeader(const char *argv0, const char *pszBaseDir)
+{
+ char szBuf[1024];
+ if (!pszBaseDir)
+ {
+ memset(szBuf, 0, sizeof(szBuf));
+#ifdef _MSC_VER
+ if (!_getcwd(szBuf, sizeof(szBuf)))
+#else
+ if (!getcwd(szBuf, sizeof(szBuf)))
+#endif
+ return RTEXITCODE_FAILURE;
+ pszBaseDir = szBuf;
+ }
+
+ const char *pszYear = __DATE__;
+ pszYear += strlen(pszYear) - 4;
+
+ Stream1Printf("/* $" "Id" "$ */\n"
+ "/** @file\n"
+ " * IPRT - Unicode Tables.\n"
+ " *\n"
+ " * Automatically Generated from %s\n"
+ " * by %s (" __DATE__ " " __TIME__ ")\n"
+ " */\n"
+ "\n"
+ "/*\n"
+ " * Copyright (C) 2006-%s Oracle and/or its affiliates.\n"
+ " *\n"
+ " * This file is part of VirtualBox base platform packages, as\n"
+ " * available from https://www.virtualbox.org.\n"
+ " *\n"
+ " * This program is free software; you can redistribute it and/or\n"
+ " * modify it under the terms of the GNU General Public License\n"
+ " * as published by the Free Software Foundation, in version 3 of the\n"
+ " * License.\n"
+ " *\n"
+ " * This program is distributed in the hope that it will be useful, but\n"
+ " * WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+ " * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n"
+ " * General Public License for more details.\n"
+ " *\n"
+ " * You should have received a copy of the GNU General Public License\n"
+ " * along with this program; if not, see <https://www.gnu.org/licenses>.\n"
+ " *\n"
+ " * The contents of this file may alternatively be used under the terms\n"
+ " * of the Common Development and Distribution License Version 1.0\n"
+ " * (CDDL), a copy of it is provided in the \"COPYING.CDDL\" file included\n"
+ " * in the VirtualBox distribution, in which case the provisions of the\n"
+ " * CDDL are applicable instead of those of the GPL.\n"
+ " *\n"
+ " * You may elect to license modified versions of this file under the\n"
+ " * terms and conditions of either the GPL or the CDDL or both.\n"
+ " *\n"
+ " * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0\n"
+ " */\n"
+ "\n"
+ "#include <iprt/uni.h>\n"
+ "\n",
+ pszBaseDir, argv0, pszYear);
+ return 0;
+}
+
+
+/**
+ * Print the flag tables.
+ */
+int PrintFlags(void)
+{
+ /*
+ * Print flags table.
+ */
+ Stream2Init();
+ Stream2Printf("RT_DECL_DATA_CONST(const RTUNIFLAGSRANGE) g_aRTUniFlagsRanges[] =\n"
+ "{\n");
+ RTUNICP i = 0;
+ int iStart = -1;
+ while (i < RT_ELEMENTS(g_aCPInfo))
+ {
+ /* figure how far off the next chunk is */
+ char szFlags[256];
+ unsigned iNonNull = i;
+ while ( iNonNull < RT_ELEMENTS(g_aCPInfo)
+ && iNonNull >= 256
+ && (g_aCPInfo[iNonNull].fNullEntry || !CalcFlags(&g_aCPInfo[iNonNull], szFlags)) )
+ iNonNull++;
+ if (iNonNull - i > 4096 || iNonNull == RT_ELEMENTS(g_aCPInfo))
+ {
+ if (iStart >= 0)
+ {
+ Stream1Printf("};\n\n");
+ Stream2Printf(" { 0x%06x, 0x%06x, &g_afRTUniFlags0x%06x[0] },\n", iStart, i, iStart);
+ iStart = -1;
+ }
+ i = iNonNull;
+ }
+ else
+ {
+ if (iStart < 0)
+ {
+ Stream1Printf("static const uint8_t g_afRTUniFlags0x%06x[] =\n"
+ "{\n", i);
+ iStart = i;
+ }
+ CalcFlags(&g_aCPInfo[i], szFlags);
+ Stream1Printf(" %50s, /* U+%06x: %s*/\n", szFlags, g_aCPInfo[i].CodePoint, g_aCPInfo[i].pszName);
+ i++;
+ }
+ }
+ Stream2Printf(" { ~(RTUNICP)0, ~(RTUNICP)0, NULL }\n"
+ "};\n\n\n");
+ Stream1Printf("\n");
+ return Stream2Flush();
+}
+
+
+/**
+ * Prints the upper case tables.
+ */
+static int PrintUpper(void)
+{
+ Stream2Init();
+ Stream2Printf("RT_DECL_DATA_CONST(const RTUNICASERANGE) g_aRTUniUpperRanges[] =\n"
+ "{\n");
+ RTUNICP i = 0;
+ int iStart = -1;
+ while (i < RT_ELEMENTS(g_aCPInfo))
+ {
+ /* figure how far off the next chunk is */
+ unsigned iSameCase = i;
+ while ( iSameCase < RT_ELEMENTS(g_aCPInfo)
+ && g_aCPInfo[iSameCase].SimpleUpperCaseMapping == g_aCPInfo[iSameCase].CodePoint
+ && iSameCase >= 256)
+ iSameCase++;
+ if (iSameCase - i > 4096/sizeof(RTUNICP) || iSameCase == RT_ELEMENTS(g_aCPInfo))
+ {
+ if (iStart >= 0)
+ {
+ Stream1Printf("};\n\n");
+ Stream2Printf(" { 0x%06x, 0x%06x, &g_afRTUniUpper0x%06x[0] },\n", iStart, i, iStart);
+ iStart = -1;
+ }
+ i = iSameCase;
+ }
+ else
+ {
+ if (iStart < 0)
+ {
+ Stream1Printf("static const RTUNICP g_afRTUniUpper0x%06x[] =\n"
+ "{\n", i);
+ iStart = i;
+ }
+ Stream1Printf(" 0x%02x, /* U+%06x: %s*/\n", g_aCPInfo[i].SimpleUpperCaseMapping, g_aCPInfo[i].CodePoint, g_aCPInfo[i].pszName);
+ i++;
+ }
+ }
+ Stream2Printf(" { ~(RTUNICP)0, ~(RTUNICP)0, NULL }\n"
+ "};\n\n\n");
+ Stream1Printf("\n");
+ return Stream2Flush();
+}
+
+
+/**
+ * Prints the lowercase tables.
+ */
+static int PrintLower(void)
+{
+ Stream2Init();
+ Stream2Printf("RT_DECL_DATA_CONST(const RTUNICASERANGE) g_aRTUniLowerRanges[] =\n"
+ "{\n");
+ RTUNICP i = 0;
+ int iStart = -1;
+ while (i < RT_ELEMENTS(g_aCPInfo))
+ {
+ /* figure how far off the next chunk is */
+ unsigned iSameCase = i;
+ while ( iSameCase < RT_ELEMENTS(g_aCPInfo)
+ && g_aCPInfo[iSameCase].SimpleLowerCaseMapping == g_aCPInfo[iSameCase].CodePoint
+ && iSameCase >= 256)
+ iSameCase++;
+ if (iSameCase - i > 4096/sizeof(RTUNICP) || iSameCase == RT_ELEMENTS(g_aCPInfo))
+ {
+ if (iStart >= 0)
+ {
+ Stream1Printf("};\n\n");
+ Stream2Printf(" { 0x%06x, 0x%06x, &g_afRTUniLower0x%06x[0] },\n", iStart, i, iStart);
+ iStart = -1;
+ }
+ i = iSameCase;
+ }
+ else
+ {
+ if (iStart < 0)
+ {
+ Stream1Printf("static const RTUNICP g_afRTUniLower0x%06x[] =\n"
+ "{\n", i);
+ iStart = i;
+ }
+ Stream1Printf(" 0x%02x, /* U+%06x: %s*/\n",
+ g_aCPInfo[i].SimpleLowerCaseMapping, g_aCPInfo[i].CodePoint, g_aCPInfo[i].pszName);
+ i++;
+ }
+ }
+ Stream2Printf(" { ~(RTUNICP)0, ~(RTUNICP)0, NULL }\n"
+ "};\n\n\n");
+ Stream1Printf("\n");
+ return Stream2Flush();
+}
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * Parse args.
+ */
+ if (argc <= 1)
+ {
+ printf("usage: %s [-C|--dir <UCD-dir>] [UnicodeData.txt [DerivedCoreProperties.txt [PropList.txt] [DerivedNormalizationProps.txt]]]\n",
+ argv[0]);
+ return 1;
+ }
+
+ const char *pszBaseDir = NULL;
+ const char *pszUnicodeData = "UnicodeData.txt";
+ const char *pszDerivedCoreProperties = "DerivedCoreProperties.txt";
+ const char *pszPropList = "PropList.txt";
+ const char *pszDerivedNormalizationProps = "DerivedNormalizationProps.txt";
+ int iFile = 0;
+ for (int argi = 1; argi < argc; argi++)
+ {
+ if (argv[argi][0] != '-')
+ {
+ switch (iFile++)
+ {
+ case 0: pszUnicodeData = argv[argi]; break;
+ case 1: pszDerivedCoreProperties = argv[argi]; break;
+ case 2: pszPropList = argv[argi]; break;
+ case 3: pszDerivedNormalizationProps = argv[argi]; break;
+ default:
+ fprintf(stderr, "uniread: syntax error at '%s': too many filenames\n", argv[argi]);
+ return 1;
+ }
+ }
+ else if ( !strcmp(argv[argi], "--dir")
+ || !strcmp(argv[argi], "-C"))
+ {
+ if (argi + 1 >= argc)
+ {
+ fprintf(stderr, "uniread: syntax error: '%s' is missing the directory name.\n", argv[argi]);
+ return 1;
+ }
+ argi++;
+ pszBaseDir = argv[argi];
+ }
+ else
+ {
+ fprintf(stderr, "uniread: syntax error at '%s': Unknown argument\n", argv[argi]);
+ return 1;
+ }
+ }
+
+ /*
+ * Read the data.
+ */
+ int rc = ReadUnicodeData(pszBaseDir, pszUnicodeData);
+ if (rc)
+ return rc;
+ rc = GenerateExcludedData();
+ if (rc)
+ return rc;
+ rc = ReadProperties(pszBaseDir, pszPropList);
+ if (rc)
+ return rc;
+ rc = ReadProperties(pszBaseDir, pszDerivedCoreProperties);
+ if (rc)
+ return rc;
+ rc = ReadProperties(pszBaseDir, pszDerivedNormalizationProps);
+ if (rc)
+ return rc;
+
+ /*
+ * Produce output files.
+ */
+ rc = Stream1Init("unidata-flags.cpp");
+ if (!rc)
+ rc = PrintHeader(argv[0], pszBaseDir);
+ if (!rc)
+ rc = PrintFlags();
+
+ rc = Stream1Init("unidata-upper.cpp");
+ if (!rc)
+ rc = PrintHeader(argv[0], pszBaseDir);
+ if (!rc)
+ rc = PrintUpper();
+
+ rc = Stream1Init("unidata-lower.cpp");
+ if (!rc)
+ rc = PrintHeader(argv[0], pszBaseDir);
+ if (!rc)
+ rc = PrintLower();
+ if (!rc)
+ rc = Stream1Close();
+
+ /* done */
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/string/utf-16-case.cpp b/src/VBox/Runtime/common/string/utf-16-case.cpp
new file mode 100644
index 00000000..2f21c833
--- /dev/null
+++ b/src/VBox/Runtime/common/string/utf-16-case.cpp
@@ -0,0 +1,449 @@
+/* $Id: utf-16-case.cpp $ */
+/** @file
+ * IPRT - UTF-16, Case Sensitivity.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/uni.h>
+#include <iprt/alloc.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include "internal/string.h"
+
+
+RTDECL(int) RTUtf16ICmp(PCRTUTF16 pwsz1, PCRTUTF16 pwsz2)
+{
+ if (pwsz1 == pwsz2)
+ return 0;
+ if (!pwsz1)
+ return -1;
+ if (!pwsz2)
+ return 1;
+
+ PCRTUTF16 pwsz1Start = pwsz1; /* keep it around in case we have to backtrack on a surrogate pair */
+ for (;;)
+ {
+ RTUTF16 wc1 = *pwsz1;
+ RTUTF16 wc2 = *pwsz2;
+ int iDiff = wc1 - wc2;
+ if (iDiff)
+ {
+ /* unless they are *both* surrogate pairs, there is no chance they'll be identical. */
+ if ( wc1 < 0xd800
+ || wc2 < 0xd800
+ || wc1 > 0xdfff
+ || wc2 > 0xdfff)
+ {
+ /* simple UCS-2 char */
+ iDiff = RTUniCpToUpper(wc1) - RTUniCpToUpper(wc2);
+ if (iDiff)
+ iDiff = RTUniCpToLower(wc1) - RTUniCpToLower(wc2);
+ }
+ else
+ {
+ /* a damned pair */
+ RTUNICP uc1;
+ RTUNICP uc2;
+ if (wc1 >= 0xdc00)
+ {
+ if (pwsz1Start == pwsz1)
+ return iDiff;
+ uc1 = pwsz1[-1];
+ if (uc1 < 0xd800 || uc1 >= 0xdc00)
+ return iDiff;
+ uc1 = 0x10000 + (((uc1 & 0x3ff) << 10) | (wc1 & 0x3ff));
+ uc2 = 0x10000 + (((pwsz2[-1] & 0x3ff) << 10) | (wc2 & 0x3ff));
+ }
+ else
+ {
+ uc1 = *++pwsz1;
+ if (uc1 < 0xdc00 || uc1 >= 0xe000)
+ return iDiff;
+ uc1 = 0x10000 + (((wc1 & 0x3ff) << 10) | (uc1 & 0x3ff));
+ uc2 = 0x10000 + (((wc2 & 0x3ff) << 10) | (*++pwsz2 & 0x3ff));
+ }
+ iDiff = RTUniCpToUpper(uc1) - RTUniCpToUpper(uc2);
+ if (iDiff)
+ iDiff = RTUniCpToLower(uc1) - RTUniCpToLower(uc2); /* serious paranoia! */
+ }
+ if (iDiff)
+ return iDiff;
+ }
+ if (!wc1)
+ return 0;
+ pwsz1++;
+ pwsz2++;
+ }
+}
+RT_EXPORT_SYMBOL(RTUtf16ICmp);
+
+
+RTDECL(int) RTUtf16BigICmp(PCRTUTF16 pwsz1, PCRTUTF16 pwsz2)
+{
+ if (pwsz1 == pwsz2)
+ return 0;
+ if (!pwsz1)
+ return -1;
+ if (!pwsz2)
+ return 1;
+
+ PCRTUTF16 pwsz1Start = pwsz1; /* keep it around in case we have to backtrack on a surrogate pair */
+ for (;;)
+ {
+ RTUTF16 wc1 = *pwsz1;
+ RTUTF16 wc2 = *pwsz2;
+ int iDiff = wc1 - wc2;
+ if (iDiff)
+ {
+ /* unless they are *both* surrogate pairs, there is no chance they'll be identical. */
+ wc1 = RT_BE2H_U16(wc1);
+ wc2 = RT_BE2H_U16(wc2);
+ if ( wc1 < 0xd800
+ || wc2 < 0xd800
+ || wc1 > 0xdfff
+ || wc2 > 0xdfff)
+ {
+ /* simple UCS-2 char */
+ iDiff = RTUniCpToUpper(wc1) - RTUniCpToUpper(wc2);
+ if (iDiff)
+ iDiff = RTUniCpToLower(wc1) - RTUniCpToLower(wc2);
+ }
+ else
+ {
+ /* a damned pair */
+ RTUNICP uc1;
+ RTUNICP uc2;
+ if (wc1 >= 0xdc00)
+ {
+ if (pwsz1Start == pwsz1)
+ return iDiff;
+ uc1 = RT_BE2H_U16(pwsz1[-1]);
+ if (uc1 < 0xd800 || uc1 >= 0xdc00)
+ return iDiff;
+ uc1 = 0x10000 + (((uc1 & 0x3ff) << 10) | (wc1 & 0x3ff));
+ uc2 = 0x10000 + (((RT_BE2H_U16(pwsz2[-1]) & 0x3ff) << 10) | (wc2 & 0x3ff));
+ }
+ else
+ {
+ RTUTF16 wcTmp = *++pwsz1;
+ uc1 = RT_BE2H_U16(wcTmp);
+ if (uc1 < 0xdc00 || uc1 >= 0xe000)
+ return iDiff;
+ uc1 = 0x10000 + (((wc1 & 0x3ff) << 10) | (uc1 & 0x3ff));
+ wcTmp = *++pwsz2;
+ uc2 = 0x10000 + (((wc2 & 0x3ff) << 10) | (RT_BE2H_U16(wcTmp) & 0x3ff));
+ }
+ iDiff = RTUniCpToUpper(uc1) - RTUniCpToUpper(uc2);
+ if (iDiff)
+ iDiff = RTUniCpToLower(uc1) - RTUniCpToLower(uc2); /* serious paranoia! */
+ }
+ if (iDiff)
+ return iDiff;
+ }
+ if (!wc1)
+ return 0;
+ pwsz1++;
+ pwsz2++;
+ }
+}
+RT_EXPORT_SYMBOL(RTUtf16BigICmp);
+
+
+RTDECL(int) RTUtf16ICmpUtf8(PCRTUTF16 pwsz1, const char *psz2)
+{
+ /*
+ * NULL and empty strings are all the same.
+ */
+ if (!pwsz1)
+ return !psz2 || !*psz2 ? 0 : -1;
+ if (!psz2)
+ return !*pwsz1 ? 0 : 1;
+
+ /*
+ * Compare with a UTF-8 string by enumerating them char by char.
+ */
+ for (;;)
+ {
+ RTUNICP uc1;
+ int rc = RTUtf16GetCpEx(&pwsz1, &uc1);
+ AssertRCReturn(rc, 1);
+
+ RTUNICP uc2;
+ rc = RTStrGetCpEx(&psz2, &uc2);
+ AssertRCReturn(rc, -1);
+ if (uc1 == uc2)
+ {
+ if (uc1)
+ continue;
+ return 0;
+ }
+
+ if (RTUniCpToUpper(uc1) == RTUniCpToUpper(uc2))
+ continue;
+ if (RTUniCpToLower(uc1) == RTUniCpToLower(uc2))
+ continue;
+ return uc1 < uc2 ? -1 : 1;
+ }
+}
+RT_EXPORT_SYMBOL(RTUtf16CmpIUtf8);
+
+
+RTDECL(int) RTUtf16NICmp(PCRTUTF16 pwsz1, PCRTUTF16 pwsz2, size_t cwcMax)
+{
+ if (pwsz1 == pwsz2)
+ return 0;
+ if (!pwsz1)
+ return -1;
+ if (!pwsz2)
+ return 1;
+
+ PCRTUTF16 pwsz1Start = pwsz1; /* keep it around in case we have to backtrack on a surrogate pair */
+ while (cwcMax-- > 0)
+ {
+ RTUTF16 wc1 = *pwsz1;
+ RTUTF16 wc2 = *pwsz2;
+ int iDiff = wc1 - wc2;
+ if (iDiff)
+ {
+ /* unless they are *both* surrogate pairs, there is no chance they'll be identical. */
+ if ( wc1 < 0xd800
+ || wc2 < 0xd800
+ || wc1 > 0xdfff
+ || wc2 > 0xdfff)
+ {
+ /* simple UCS-2 char */
+ iDiff = RTUniCpToUpper(wc1) - RTUniCpToUpper(wc2);
+ if (iDiff)
+ iDiff = RTUniCpToLower(wc1) - RTUniCpToLower(wc2);
+ }
+ else
+ {
+ /* a damned pair */
+ RTUNICP uc1;
+ RTUNICP uc2;
+ if (wc1 >= 0xdc00)
+ {
+ if (pwsz1Start == pwsz1)
+ return iDiff;
+ uc1 = pwsz1[-1];
+ if (uc1 < 0xd800 || uc1 >= 0xdc00)
+ return iDiff;
+ uc1 = 0x10000 + (((uc1 & 0x3ff) << 10) | (wc1 & 0x3ff));
+ uc2 = 0x10000 + (((pwsz2[-1] & 0x3ff) << 10) | (wc2 & 0x3ff));
+ }
+ else if (cwcMax-- > 0)
+ {
+ uc1 = *++pwsz1;
+ if (uc1 < 0xdc00 || uc1 >= 0xe000)
+ return iDiff;
+ uc1 = 0x10000 + (((wc1 & 0x3ff) << 10) | (uc1 & 0x3ff));
+ uc2 = 0x10000 + (((wc2 & 0x3ff) << 10) | (*++pwsz2 & 0x3ff));
+ }
+ else
+ {
+ iDiff = wc1 - wc2;
+ return iDiff;
+ }
+ iDiff = RTUniCpToUpper(uc1) - RTUniCpToUpper(uc2);
+ if (iDiff)
+ iDiff = RTUniCpToLower(uc1) - RTUniCpToLower(uc2); /* serious paranoia! */
+ }
+ if (iDiff)
+ return iDiff;
+ }
+ if (!wc1)
+ return 0;
+ pwsz1++;
+ pwsz2++;
+ }
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTUtf16NICmp);
+
+
+RTDECL(int) RTUtf16BigNICmp(PCRTUTF16 pwsz1, PCRTUTF16 pwsz2, size_t cwcMax)
+{
+ if (pwsz1 == pwsz2)
+ return 0;
+ if (!pwsz1)
+ return -1;
+ if (!pwsz2)
+ return 1;
+
+ PCRTUTF16 pwsz1Start = pwsz1; /* keep it around in case we have to backtrack on a surrogate pair */
+ while (cwcMax-- > 0)
+ {
+ RTUTF16 wc1 = *pwsz1;
+ RTUTF16 wc2 = *pwsz2;
+ int iDiff = wc1 - wc2;
+ if (iDiff)
+ {
+ /* unless they are *both* surrogate pairs, there is no chance they'll be identical. */
+ wc1 = RT_BE2H_U16(wc1);
+ wc2 = RT_BE2H_U16(wc2);
+ if ( wc1 < 0xd800
+ || wc2 < 0xd800
+ || wc1 > 0xdfff
+ || wc2 > 0xdfff)
+ {
+ /* simple UCS-2 char */
+ iDiff = RTUniCpToUpper(wc1) - RTUniCpToUpper(wc2);
+ if (iDiff)
+ iDiff = RTUniCpToLower(wc1) - RTUniCpToLower(wc2);
+ }
+ else
+ {
+ /* a damned pair */
+ RTUNICP uc1;
+ RTUNICP uc2;
+ if (wc1 >= 0xdc00)
+ {
+ if (pwsz1Start == pwsz1)
+ return iDiff;
+ uc1 = RT_BE2H_U16(pwsz1[-1]);
+ if (uc1 < 0xd800 || uc1 >= 0xdc00)
+ return iDiff;
+ uc1 = 0x10000 + (((uc1 & 0x3ff) << 10) | (wc1 & 0x3ff));
+ uc2 = 0x10000 + (((RT_BE2H_U16(pwsz2[-1]) & 0x3ff) << 10) | (wc2 & 0x3ff));
+ }
+ else if (cwcMax > 0)
+ {
+ RTUTF16 wcTmp = *++pwsz1;
+ uc1 = RT_BE2H_U16(wcTmp);
+ if (uc1 < 0xdc00 || uc1 >= 0xe000)
+ return iDiff;
+ uc1 = 0x10000 + (((wc1 & 0x3ff) << 10) | (uc1 & 0x3ff));
+ wcTmp = *++pwsz2;
+ uc2 = 0x10000 + (((wc2 & 0x3ff) << 10) | (RT_BE2H_U16(wcTmp) & 0x3ff));
+ }
+ else
+ {
+ iDiff = wc1 - wc2;
+ return iDiff;
+ }
+ iDiff = RTUniCpToUpper(uc1) - RTUniCpToUpper(uc2);
+ if (iDiff)
+ iDiff = RTUniCpToLower(uc1) - RTUniCpToLower(uc2); /* serious paranoia! */
+ }
+ if (iDiff)
+ return iDiff;
+ }
+ if (!wc1)
+ return 0;
+ pwsz1++;
+ pwsz2++;
+ }
+ return 0;
+}
+RT_EXPORT_SYMBOL(RTUtf16BigNICmp);
+
+
+RTDECL(PRTUTF16) RTUtf16ToLower(PRTUTF16 pwsz)
+{
+ PRTUTF16 pwc = pwsz;
+ for (;;)
+ {
+ RTUTF16 wc = *pwc;
+ if (!wc)
+ break;
+ if (wc < 0xd800 || wc >= 0xdc00)
+ {
+ RTUNICP ucFolded = RTUniCpToLower(wc);
+ if (ucFolded < 0x10000)
+ *pwc++ = RTUniCpToLower(wc);
+ }
+ else
+ {
+ /* surrogate */
+ RTUTF16 wc2 = pwc[1];
+ if (wc2 >= 0xdc00 && wc2 <= 0xdfff)
+ {
+ RTUNICP uc = 0x10000 + (((wc & 0x3ff) << 10) | (wc2 & 0x3ff));
+ RTUNICP ucFolded = RTUniCpToLower(uc);
+ if (uc != ucFolded && ucFolded >= 0x10000) /* we don't support shrinking the string */
+ {
+ uc -= 0x10000;
+ *pwc++ = 0xd800 | (uc >> 10);
+ *pwc++ = 0xdc00 | (uc & 0x3ff);
+ }
+ }
+ else /* invalid encoding. */
+ pwc++;
+ }
+ }
+ return pwsz;
+}
+RT_EXPORT_SYMBOL(RTUtf16ToLower);
+
+
+RTDECL(PRTUTF16) RTUtf16ToUpper(PRTUTF16 pwsz)
+{
+ PRTUTF16 pwc = pwsz;
+ for (;;)
+ {
+ RTUTF16 wc = *pwc;
+ if (!wc)
+ break;
+ if (wc < 0xd800 || wc >= 0xdc00)
+ *pwc++ = RTUniCpToUpper(wc);
+ else
+ {
+ /* surrogate */
+ RTUTF16 wc2 = pwc[1];
+ if (wc2 >= 0xdc00 && wc2 <= 0xdfff)
+ {
+ RTUNICP uc = 0x10000 + (((wc & 0x3ff) << 10) | (wc2 & 0x3ff));
+ RTUNICP ucFolded = RTUniCpToUpper(uc);
+ if (uc != ucFolded && ucFolded >= 0x10000) /* we don't support shrinking the string */
+ {
+ uc -= 0x10000;
+ *pwc++ = 0xd800 | (uc >> 10);
+ *pwc++ = 0xdc00 | (uc & 0x3ff);
+ }
+ }
+ else /* invalid encoding. */
+ pwc++;
+ }
+ }
+ return pwsz;
+}
+RT_EXPORT_SYMBOL(RTUtf16ToUpper);
+
diff --git a/src/VBox/Runtime/common/string/utf-16-latin-1.cpp b/src/VBox/Runtime/common/string/utf-16-latin-1.cpp
new file mode 100644
index 00000000..893cd3f2
--- /dev/null
+++ b/src/VBox/Runtime/common/string/utf-16-latin-1.cpp
@@ -0,0 +1,491 @@
+/* $Id: utf-16-latin-1.cpp $ */
+/** @file
+ * IPRT - Latin-1 and UTF-16.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/latin1.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/utf16.h>
+#include <iprt/uni.h>
+#include "internal/string.h"
+
+
+/**
+ * Validate the UTF-16 encoding and calculates the length of a Latin1 encoding.
+ *
+ * @returns iprt status code.
+ * @param pwsz The UTF-16 string.
+ * @param cwc The max length of the UTF-16 string to consider.
+ * @param pcch Where to store the length (excluding '\\0') of the Latin1 string. (cch == cb, btw)
+ */
+static int rtUtf16CalcLatin1Length(PCRTUTF16 pwsz, size_t cwc, size_t *pcch)
+{
+ int rc = VINF_SUCCESS;
+ size_t cch = 0;
+ while (cwc > 0)
+ {
+ RTUTF16 wc = *pwsz++; cwc--;
+ if (!wc)
+ break;
+ else if (RT_LIKELY(wc < 0x100))
+ ++cch;
+ else
+ {
+ if (wc < 0xd800 || wc > 0xdfff)
+ {
+ if (wc >= 0xfffe)
+ {
+ RTStrAssertMsgFailed(("endian indicator! wc=%#x\n", wc));
+ rc = VERR_CODE_POINT_ENDIAN_INDICATOR;
+ break;
+ }
+ }
+ else
+ {
+ if (wc >= 0xdc00)
+ {
+ RTStrAssertMsgFailed(("Wrong 1st char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ if (cwc <= 0)
+ {
+ RTStrAssertMsgFailed(("Invalid length! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ wc = *pwsz++; cwc--;
+ if (wc < 0xdc00 || wc > 0xdfff)
+ {
+ RTStrAssertMsgFailed(("Wrong 2nd char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ }
+
+ rc = VERR_NO_TRANSLATION;
+ break;
+ }
+ }
+
+ /* done */
+ *pcch = cch;
+ return rc;
+}
+
+
+/**
+ * Recodes an valid UTF-16 string as Latin1.
+ *
+ * @returns iprt status code.
+ * @param pwsz The UTF-16 string.
+ * @param cwc The number of RTUTF16 characters to process from pwsz. The recoding
+ * will stop when cwc or '\\0' is reached.
+ * @param psz Where to store the Latin1 string.
+ * @param cch The size of the Latin1 buffer, excluding the terminator.
+ */
+static int rtUtf16RecodeAsLatin1(PCRTUTF16 pwsz, size_t cwc, char *psz, size_t cch)
+{
+ unsigned char *pch = (unsigned char *)psz;
+ int rc = VINF_SUCCESS;
+ while (cwc > 0)
+ {
+ RTUTF16 wc = *pwsz++; cwc--;
+ if (!wc)
+ break;
+ if (RT_LIKELY(wc < 0x100))
+ {
+ if (RT_UNLIKELY(cch < 1))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 1\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch--;
+ *pch++ = (unsigned char)wc;
+ }
+ else
+ {
+ if (wc < 0xd800 || wc > 0xdfff)
+ {
+ if (wc >= 0xfffe)
+ {
+ RTStrAssertMsgFailed(("endian indicator! wc=%#x\n", wc));
+ rc = VERR_CODE_POINT_ENDIAN_INDICATOR;
+ break;
+ }
+ }
+ else
+ {
+ if (wc >= 0xdc00)
+ {
+ RTStrAssertMsgFailed(("Wrong 1st char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ if (cwc <= 0)
+ {
+ RTStrAssertMsgFailed(("Invalid length! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ RTUTF16 wc2 = *pwsz++; cwc--;
+ if (wc2 < 0xdc00 || wc2 > 0xdfff)
+ {
+ RTStrAssertMsgFailed(("Wrong 2nd char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ }
+
+ rc = VERR_NO_TRANSLATION;
+ break;
+ }
+ }
+
+ /* done */
+ *pch = '\0';
+ return rc;
+}
+
+
+RTDECL(int) RTUtf16ToLatin1Tag(PCRTUTF16 pwszString, char **ppszString, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(ppszString);
+ AssertPtr(pwszString);
+ *ppszString = NULL;
+
+ /*
+ * Validate the UTF-16 string and calculate the length of the UTF-8 encoding of it.
+ */
+ size_t cch;
+ int rc = rtUtf16CalcLatin1Length(pwszString, RTSTR_MAX, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer and recode it.
+ */
+ char *pszResult = (char *)RTMemAllocTag(cch + 1, pszTag);
+ if (pszResult)
+ {
+ rc = rtUtf16RecodeAsLatin1(pwszString, RTSTR_MAX, pszResult, cch);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszString = pszResult;
+ return rc;
+ }
+
+ RTMemFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16ToLatin1Tag);
+
+
+RTDECL(int) RTUtf16ToLatin1ExTag(PCRTUTF16 pwszString, size_t cwcString, char **ppsz, size_t cch, size_t *pcch, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pwszString);
+ AssertPtr(ppsz);
+ AssertPtrNull(pcch);
+
+ /*
+ * Validate the UTF-16 string and calculate the length of the Latin1 encoding of it.
+ */
+ size_t cchResult;
+ int rc = rtUtf16CalcLatin1Length(pwszString, cwcString, &cchResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcch)
+ *pcch = cchResult;
+
+ /*
+ * Check buffer size / Allocate buffer and recode it.
+ */
+ bool fShouldFree;
+ char *pszResult;
+ if (cch > 0 && *ppsz)
+ {
+ fShouldFree = false;
+ if (cch <= cchResult)
+ return VERR_BUFFER_OVERFLOW;
+ pszResult = *ppsz;
+ }
+ else
+ {
+ *ppsz = NULL;
+ fShouldFree = true;
+ cch = RT_MAX(cch, cchResult + 1);
+ pszResult = (char *)RTMemAllocTag(cch, pszTag);
+ }
+ if (pszResult)
+ {
+ rc = rtUtf16RecodeAsLatin1(pwszString, cwcString, pszResult, cch - 1);
+ if (RT_SUCCESS(rc))
+ {
+ *ppsz = pszResult;
+ return rc;
+ }
+
+ if (fShouldFree)
+ RTMemFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16ToLatin1ExTag);
+
+
+RTDECL(size_t) RTUtf16CalcLatin1Len(PCRTUTF16 pwsz)
+{
+ size_t cch;
+ int rc = rtUtf16CalcLatin1Length(pwsz, RTSTR_MAX, &cch);
+ return RT_SUCCESS(rc) ? cch : 0;
+}
+RT_EXPORT_SYMBOL(RTUtf16CalcLatin1Len);
+
+
+RTDECL(int) RTUtf16CalcLatin1LenEx(PCRTUTF16 pwsz, size_t cwc, size_t *pcch)
+{
+ size_t cch;
+ int rc = rtUtf16CalcLatin1Length(pwsz, cwc, &cch);
+ if (pcch)
+ *pcch = RT_SUCCESS(rc) ? cch : ~(size_t)0;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16CalcLatin1LenEx);
+
+
+/**
+ * Calculates the UTF-16 length of a Latin1 string. In fact this is just the
+ * original length, but the function saves us nasty comments to that effect
+ * all over the place.
+ *
+ * @returns IPRT status code.
+ * @param psz Pointer to the Latin1 string.
+ * @param cch The max length of the string. (btw cch = cb)
+ * Use RTSTR_MAX if all of the string is to be examined.s
+ * @param pcwc Where to store the length of the UTF-16 string as a number of RTUTF16 characters.
+ */
+static int rtLatin1CalcUtf16Length(const char *psz, size_t cch, size_t *pcwc)
+{
+ *pcwc = RTStrNLen(psz, cch);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Recodes a Latin1 string as UTF-16. This is just a case of expanding it to
+ * sixteen bits, as Unicode is a superset of Latin1.
+ *
+ * Since we know the input is valid, we do *not* perform length checks.
+ *
+ * @returns iprt status code.
+ * @param psz The Latin1 string to recode.
+ * @param cch The number of chars (the type char, so bytes if you like) to process of the Latin1 string.
+ * The recoding will stop when cch or '\\0' is reached. Pass RTSTR_MAX to process up to '\\0'.
+ * @param pwsz Where to store the UTF-16 string.
+ * @param cwc The number of RTUTF16 items the pwsz buffer can hold, excluding the terminator ('\\0').
+ */
+static int rtLatin1RecodeAsUtf16(const char *psz, size_t cch, PRTUTF16 pwsz, size_t cwc)
+{
+ int rc = VINF_SUCCESS;
+ const unsigned char *puch = (const unsigned char *)psz;
+ PRTUTF16 pwc = pwsz;
+ while (cch-- > 0)
+ {
+ /* read the next char and check for terminator. */
+ const unsigned char uch = *puch;
+ if (!uch)
+ break;
+
+ /* check for output overflow */
+ if (RT_UNLIKELY(cwc < 1))
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ /* expand the code point */
+ *pwc++ = uch;
+ cwc--;
+ puch++;
+ }
+
+ /* done */
+ *pwc = '\0';
+ return rc;
+}
+
+
+RTDECL(int) RTLatin1ToUtf16Tag(const char *pszString, PRTUTF16 *ppwszString, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(ppwszString);
+ AssertPtr(pszString);
+ *ppwszString = NULL;
+
+ /*
+ * Validate the input and calculate the length of the UTF-16 string.
+ */
+ size_t cwc;
+ int rc = rtLatin1CalcUtf16Length(pszString, RTSTR_MAX, &cwc);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer.
+ */
+ PRTUTF16 pwsz = (PRTUTF16)RTMemAllocTag((cwc + 1) * sizeof(RTUTF16), pszTag);
+ if (pwsz)
+ {
+ /*
+ * Encode the UTF-16 string.
+ */
+ rc = rtLatin1RecodeAsUtf16(pszString, RTSTR_MAX, pwsz, cwc);
+ if (RT_SUCCESS(rc))
+ {
+ *ppwszString = pwsz;
+ return rc;
+ }
+ RTMemFree(pwsz);
+ }
+ else
+ rc = VERR_NO_UTF16_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLatin1ToUtf16Tag);
+
+
+RTDECL(int) RTLatin1ToUtf16ExTag(const char *pszString, size_t cchString,
+ PRTUTF16 *ppwsz, size_t cwc, size_t *pcwc, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pszString);
+ AssertPtr(ppwsz);
+ AssertPtrNull(pcwc);
+
+ /*
+ * Validate the input and calculate the length of the UTF-16 string.
+ */
+ size_t cwcResult;
+ int rc = rtLatin1CalcUtf16Length(pszString, cchString, &cwcResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcwc)
+ *pcwc = cwcResult;
+
+ /*
+ * Check buffer size / Allocate buffer.
+ */
+ bool fShouldFree;
+ PRTUTF16 pwszResult;
+ if (cwc > 0 && *ppwsz)
+ {
+ fShouldFree = false;
+ if (cwc <= cwcResult)
+ return VERR_BUFFER_OVERFLOW;
+ pwszResult = *ppwsz;
+ }
+ else
+ {
+ *ppwsz = NULL;
+ fShouldFree = true;
+ cwc = RT_MAX(cwcResult + 1, cwc);
+ pwszResult = (PRTUTF16)RTMemAllocTag(cwc * sizeof(RTUTF16), pszTag);
+ }
+ if (pwszResult)
+ {
+ /*
+ * Encode the UTF-16 string.
+ */
+ rc = rtLatin1RecodeAsUtf16(pszString, cchString, pwszResult, cwc - 1);
+ if (RT_SUCCESS(rc))
+ {
+ *ppwsz = pwszResult;
+ return rc;
+ }
+ if (fShouldFree)
+ RTMemFree(pwszResult);
+ }
+ else
+ rc = VERR_NO_UTF16_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLatin1ToUtf16ExTag);
+
+
+RTDECL(size_t) RTLatin1CalcUtf16Len(const char *psz)
+{
+ size_t cwc;
+ int rc = rtLatin1CalcUtf16Length(psz, RTSTR_MAX, &cwc);
+ return RT_SUCCESS(rc) ? cwc : 0;
+}
+RT_EXPORT_SYMBOL(RTLatin1CalcUtf16Len);
+
+
+RTDECL(int) RTLatin1CalcUtf16LenEx(const char *psz, size_t cch, size_t *pcwc)
+{
+ size_t cwc;
+ int rc = rtLatin1CalcUtf16Length(psz, cch, &cwc);
+ if (pcwc)
+ *pcwc = RT_SUCCESS(rc) ? cwc : ~(size_t)0;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLatin1CalcUtf16LenEx);
+
diff --git a/src/VBox/Runtime/common/string/utf-16-printf.cpp b/src/VBox/Runtime/common/string/utf-16-printf.cpp
new file mode 100644
index 00000000..fce5e3ab
--- /dev/null
+++ b/src/VBox/Runtime/common/string/utf-16-printf.cpp
@@ -0,0 +1,243 @@
+/* $Id: utf-16-printf.cpp $ */
+/** @file
+ * IPRT - String Formatters, Outputting UTF-16.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/uni.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** rtUtf16PrintfOutput() argument structure. */
+typedef struct UTF16PRINTFOUTPUTARGS
+{
+ /** Pointer to current buffer position. */
+ PRTUTF16 pwszCur;
+ /** Number of RTUTF16 units left in the buffer (including the trailing zero). */
+ size_t cwcLeft;
+ /** Set if we overflowed. */
+ bool fOverflowed;
+} UTF16PRINTFOUTPUTARGS;
+/** Pointer to a rtUtf16PrintfOutput() argument structure. */
+typedef UTF16PRINTFOUTPUTARGS *PUTF16PRINTFOUTPUTARGS;
+
+
+/**
+ * Output callback.
+ *
+ * @returns Number of RTUTF16 units we (would have) outputted.
+ *
+ * @param pvArg Pointer to a STRBUFARG structure.
+ * @param pachChars Pointer to an array of utf-8 characters.
+ * @param cbChars Number of bytes in the character array pointed to by pachChars.
+ */
+static DECLCALLBACK(size_t) rtUtf16PrintfOutput(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ PUTF16PRINTFOUTPUTARGS pArgs = (PUTF16PRINTFOUTPUTARGS)pvArg;
+ size_t cwcRet = 0;
+
+ size_t cwcLeft = pArgs->cwcLeft;
+ if (cwcLeft > 1)
+ {
+ Assert(!pArgs->fOverflowed);
+
+ PRTUTF16 pwszCur = pArgs->pwszCur;
+ for (;;)
+ {
+ if (cbChars > 0)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpNEx(&pachChars, &cbChars, &uc);
+ AssertRCStmt(rc, uc = 0xfffd /* REPLACEMENT */);
+
+ /* Simple: */
+ if (RTUniCpIsBMP(uc))
+ {
+ cwcRet += 1;
+ if (RT_LIKELY(cwcLeft > 1))
+ *pwszCur++ = uc;
+ else
+ break;
+ cwcLeft--;
+ }
+ /* Surrogate pair: */
+ else if (uc >= 0x10000 && uc <= 0x0010ffff)
+ {
+ cwcRet += 2;
+ if (RT_LIKELY(cwcLeft > 2))
+ *pwszCur++ = 0xd800 | (uc >> 10);
+ else
+ {
+ if (cwcLeft > 1)
+ {
+ cwcLeft = 1;
+ pwszCur[1] = '\0';
+ }
+ break;
+ }
+ *pwszCur++ = 0xdc00 | (uc & 0x3ff);
+ cwcLeft -= 2;
+ }
+ else
+ {
+ AssertMsgFailed(("uc=%#x\n", uc));
+ cwcRet += 1;
+ if (RT_LIKELY(cwcLeft > 1))
+ *pwszCur++ = 0xfffd; /* REPLACEMENT */
+ else
+ break;
+ cwcLeft--;
+ }
+ }
+ else
+ {
+ *pwszCur = '\0';
+ pArgs->pwszCur = pwszCur;
+ pArgs->cwcLeft = cwcLeft;
+ return cwcRet;
+ }
+ }
+
+ /*
+ * We only get here if we run out of buffer space.
+ */
+ Assert(cwcLeft == 1);
+ *pwszCur = '\0';
+ pArgs->pwszCur = pwszCur;
+ pArgs->cwcLeft = cwcLeft;
+ }
+ /*
+ * We get a special zero byte call at the end for the formatting operation.
+ *
+ * Make sure we don't turn that into an overflow and that we'll terminate
+ * empty result strings.
+ */
+ else if (cbChars == 0 && cwcLeft > 0)
+ {
+ *pArgs->pwszCur = '\0';
+ return 0;
+ }
+
+ /*
+ * Overflow handling. Calc needed space.
+ */
+ pArgs->fOverflowed = true;
+
+ while (cbChars > 0)
+ {
+ RTUNICP uc;
+ int rc = RTStrGetCpNEx(&pachChars, &cbChars, &uc);
+ AssertRCStmt(rc, uc = 0xfffd /* REPLACEMENT */);
+
+ if (RTUniCpIsBMP(uc))
+ cwcRet += 1;
+ else if (uc >= 0x10000 && uc <= 0x0010ffff)
+ cwcRet += 2;
+ else
+ {
+ AssertMsgFailed(("uc=%#x\n", uc));
+ cwcRet += 1;
+ }
+ }
+
+ return cwcRet;
+}
+
+
+RTDECL(ssize_t) RTUtf16Printf(PRTUTF16 pwszBuffer, size_t cwcBuffer, const char *pszFormat, ...)
+{
+ /* Explicitly inline RTStrPrintfV + RTStrPrintfExV here because this is a frequently use API. */
+ UTF16PRINTFOUTPUTARGS Args;
+ size_t cwcRet;
+ va_list args;
+ AssertMsg(cwcBuffer > 0, ("Excellent idea! Format a string with no space for the output!\n"));
+
+ Args.pwszCur = pwszBuffer;
+ Args.cwcLeft = cwcBuffer;
+ Args.fOverflowed = false;
+
+ va_start(args, pszFormat);
+ cwcRet = RTStrFormatV(rtUtf16PrintfOutput, &Args, NULL, NULL, pszFormat, args);
+ va_end(args);
+
+ return !Args.fOverflowed ? (ssize_t)cwcRet : -(ssize_t)cwcRet - 1;
+}
+RT_EXPORT_SYMBOL(RTStrPrintf2);
+
+
+RTDECL(ssize_t) RTUtf16PrintfExV(PFNSTRFORMAT pfnFormat, void *pvArg, PRTUTF16 pwszBuffer, size_t cwcBuffer,
+ const char *pszFormat, va_list args)
+{
+ UTF16PRINTFOUTPUTARGS Args;
+ size_t cwcRet;
+ AssertMsg(cwcBuffer > 0, ("Excellent idea! Format a string with no space for the output!\n"));
+
+ Args.pwszCur = pwszBuffer;
+ Args.cwcLeft = cwcBuffer;
+ Args.fOverflowed = false;
+ cwcRet = RTStrFormatV(rtUtf16PrintfOutput, &Args, pfnFormat, pvArg, pszFormat, args);
+ return !Args.fOverflowed ? (ssize_t)cwcRet : -(ssize_t)cwcRet - 1;
+}
+RT_EXPORT_SYMBOL(RTUtf16PrintfExV);
+
+
+RTDECL(ssize_t) RTUtf16PrintfV(PRTUTF16 pwszBuffer, size_t cwcBuffer, const char *pszFormat, va_list args)
+{
+ return RTUtf16PrintfExV(NULL, NULL, pwszBuffer, cwcBuffer, pszFormat, args);
+}
+RT_EXPORT_SYMBOL(RTUtf16Printf2V);
+
+
+RTDECL(ssize_t) RTUtf16PrintfEx(PFNSTRFORMAT pfnFormat, void *pvArg, PRTUTF16 pwszBuffer, size_t cwcBuffer,
+ const char *pszFormat, ...)
+{
+ va_list args;
+ ssize_t cbRet;
+ va_start(args, pszFormat);
+ cbRet = RTUtf16PrintfExV(pfnFormat, pvArg, pwszBuffer, cwcBuffer, pszFormat, args);
+ va_end(args);
+ return cbRet;
+}
+RT_EXPORT_SYMBOL(RTUtf16PrintfEx);
+
diff --git a/src/VBox/Runtime/common/string/utf-16.cpp b/src/VBox/Runtime/common/string/utf-16.cpp
new file mode 100644
index 00000000..4b8121dd
--- /dev/null
+++ b/src/VBox/Runtime/common/string/utf-16.cpp
@@ -0,0 +1,1329 @@
+/* $Id: utf-16.cpp $ */
+/** @file
+ * IPRT - UTF-16.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/utf16.h>
+#include "internal/iprt.h"
+
+#include <iprt/uni.h>
+#include <iprt/asm.h>
+#include <iprt/mem.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include "internal/string.h"
+
+
+/**
+ * Get get length in code points of an UTF-16 encoded string, validating the
+ * string while doing so.
+ *
+ * @returns IPRT status code.
+ * @param pwsz Pointer to the UTF-16 string.
+ * @param cwc The max length of the string in UTF-16 units. Use
+ * RTSTR_MAX if all of the string is to be examined.
+ * @param pcuc Where to store the length in unicode code points.
+ * @param pcwcActual Where to store the actual size of the UTF-16 string
+ * on success. Optional.
+ */
+static int rtUtf16Length(PCRTUTF16 pwsz, size_t cwc, size_t *pcuc, size_t *pcwcActual)
+{
+ PCRTUTF16 pwszStart = pwsz;
+ size_t cCodePoints = 0;
+ while (cwc > 0)
+ {
+ RTUTF16 wc = *pwsz;
+ if (!wc)
+ break;
+ if (wc < 0xd800 || wc > 0xdfff)
+ {
+ cCodePoints++;
+ pwsz++;
+ cwc--;
+ }
+ /* Surrogate pair: */
+ else if (wc >= 0xdc00)
+ {
+ RTStrAssertMsgFailed(("Lone UTF-16 trail surrogate: %#x (%.*Rhxs)\n", wc, RT_MIN(cwc * 2, 10), pwsz));
+ return VERR_INVALID_UTF16_ENCODING;
+ }
+ else if (cwc < 2)
+ {
+ RTStrAssertMsgFailed(("Lone UTF-16 lead surrogate: %#x\n", wc));
+ return VERR_INVALID_UTF16_ENCODING;
+ }
+ else
+ {
+ RTUTF16 wcTrail = pwsz[1];
+ if (wcTrail < 0xdc00 || wcTrail > 0xdfff)
+ {
+ RTStrAssertMsgFailed(("Invalid UTF-16 trail surrogate: %#x (lead %#x)\n", wcTrail, wc));
+ return VERR_INVALID_UTF16_ENCODING;
+ }
+
+ cCodePoints++;
+ pwsz += 2;
+ cwc -= 2;
+ }
+ }
+
+ /* done */
+ *pcuc = cCodePoints;
+ if (pcwcActual)
+ *pcwcActual = pwsz - pwszStart;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(PRTUTF16) RTUtf16AllocTag(size_t cb, const char *pszTag)
+{
+ if (cb > sizeof(RTUTF16))
+ cb = RT_ALIGN_Z(cb, sizeof(RTUTF16));
+ else
+ cb = sizeof(RTUTF16);
+ PRTUTF16 pwsz = (PRTUTF16)RTMemAllocTag(cb, pszTag);
+ if (pwsz)
+ *pwsz = '\0';
+ return pwsz;
+}
+RT_EXPORT_SYMBOL(RTUtf16AllocTag);
+
+
+RTDECL(int) RTUtf16ReallocTag(PRTUTF16 *ppwsz, size_t cbNew, const char *pszTag)
+{
+ PRTUTF16 pwszOld = *ppwsz;
+ cbNew = RT_ALIGN_Z(cbNew, sizeof(RTUTF16));
+ if (!cbNew)
+ {
+ RTMemFree(pwszOld);
+ *ppwsz = NULL;
+ }
+ else if (pwszOld)
+ {
+ PRTUTF16 pwszNew = (PRTUTF16)RTMemReallocTag(pwszOld, cbNew, pszTag);
+ if (!pwszNew)
+ return VERR_NO_STR_MEMORY;
+ pwszNew[cbNew / sizeof(RTUTF16) - 1] = '\0';
+ *ppwsz = pwszNew;
+ }
+ else
+ {
+ PRTUTF16 pwszNew = (PRTUTF16)RTMemAllocTag(cbNew, pszTag);
+ if (!pwszNew)
+ return VERR_NO_UTF16_MEMORY;
+ pwszNew[0] = '\0';
+ pwszNew[cbNew / sizeof(RTUTF16) - 1] = '\0';
+ *ppwsz = pwszNew;
+ }
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTUtf16ReallocTag);
+
+
+RTDECL(void) RTUtf16Free(PRTUTF16 pwszString)
+{
+ if (pwszString)
+ RTMemTmpFree(pwszString);
+}
+RT_EXPORT_SYMBOL(RTUtf16Free);
+
+
+RTDECL(PRTUTF16) RTUtf16DupTag(PCRTUTF16 pwszString, const char *pszTag)
+{
+ Assert(pwszString);
+ size_t cb = (RTUtf16Len(pwszString) + 1) * sizeof(RTUTF16);
+ PRTUTF16 pwsz = (PRTUTF16)RTMemAllocTag(cb, pszTag);
+ if (pwsz)
+ memcpy(pwsz, pwszString, cb);
+ return pwsz;
+}
+RT_EXPORT_SYMBOL(RTUtf16DupTag);
+
+
+RTDECL(int) RTUtf16DupExTag(PRTUTF16 *ppwszString, PCRTUTF16 pwszString, size_t cwcExtra, const char *pszTag)
+{
+ Assert(pwszString);
+ size_t cb = (RTUtf16Len(pwszString) + 1) * sizeof(RTUTF16);
+ PRTUTF16 pwsz = (PRTUTF16)RTMemAllocTag(cb + cwcExtra * sizeof(RTUTF16), pszTag);
+ if (pwsz)
+ {
+ memcpy(pwsz, pwszString, cb);
+ *ppwszString = pwsz;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_MEMORY;
+}
+RT_EXPORT_SYMBOL(RTUtf16DupExTag);
+
+
+RTDECL(size_t) RTUtf16Len(PCRTUTF16 pwszString)
+{
+ if (!pwszString)
+ return 0;
+
+ PCRTUTF16 pwsz = pwszString;
+ while (*pwsz)
+ pwsz++;
+ return pwsz - pwszString;
+}
+RT_EXPORT_SYMBOL(RTUtf16Len);
+
+
+RTDECL(int) RTUtf16Cmp(PCRTUTF16 pwsz1, PCRTUTF16 pwsz2)
+{
+ if (pwsz1 == pwsz2)
+ return 0;
+ if (!pwsz1)
+ return -1;
+ if (!pwsz2)
+ return 1;
+
+ for (;;)
+ {
+ RTUTF16 wcs = *pwsz1;
+ int iDiff = wcs - *pwsz2;
+ if (iDiff || !wcs)
+ return iDiff;
+ pwsz1++;
+ pwsz2++;
+ }
+}
+RT_EXPORT_SYMBOL(RTUtf16Cmp);
+
+
+RTDECL(int) RTUtf16CmpUtf8(PCRTUTF16 pwsz1, const char *psz2)
+{
+ /*
+ * NULL and empty strings are all the same.
+ */
+ if (!pwsz1)
+ return !psz2 || !*psz2 ? 0 : -1;
+ if (!psz2)
+ return !*pwsz1 ? 0 : 1;
+
+ /*
+ * Compare with a UTF-8 string by enumerating them char by char.
+ */
+ for (;;)
+ {
+ RTUNICP uc1;
+ int rc = RTUtf16GetCpEx(&pwsz1, &uc1);
+ AssertRCReturn(rc, 1);
+
+ RTUNICP uc2;
+ rc = RTStrGetCpEx(&psz2, &uc2);
+ AssertRCReturn(rc, -1);
+ if (uc1 == uc2)
+ {
+ if (uc1)
+ continue;
+ return 0;
+ }
+ return uc1 < uc2 ? -1 : 1;
+ }
+}
+RT_EXPORT_SYMBOL(RTUtf16CmpUtf8);
+
+
+RTDECL(int) RTUtf16ValidateEncoding(PCRTUTF16 pwsz)
+{
+ return RTUtf16ValidateEncodingEx(pwsz, RTSTR_MAX, 0);
+}
+RT_EXPORT_SYMBOL(RTUtf16ValidateEncoding);
+
+
+RTDECL(int) RTUtf16ValidateEncodingEx(PCRTUTF16 pwsz, size_t cwc, uint32_t fFlags)
+{
+ AssertReturn(!(fFlags & ~(RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED | RTSTR_VALIDATE_ENCODING_EXACT_LENGTH)),
+ VERR_INVALID_PARAMETER);
+ AssertPtr(pwsz);
+
+ /*
+ * Use rtUtf16Length for the job.
+ */
+ size_t cwcActual = 0; /* Shut up cc1plus. */
+ size_t cCpsIgnored;
+ int rc = rtUtf16Length(pwsz, cwc, &cCpsIgnored, &cwcActual);
+ if (RT_SUCCESS(rc))
+ {
+ if (fFlags & RTSTR_VALIDATE_ENCODING_EXACT_LENGTH)
+ {
+ if (fFlags & RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED)
+ cwcActual++;
+ if (cwcActual == cwc)
+ rc = VINF_SUCCESS;
+ else if (cwcActual < cwc)
+ rc = VERR_BUFFER_UNDERFLOW;
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else if ( (fFlags & RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED)
+ && cwcActual >= cwc)
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16ValidateEncodingEx);
+
+
+RTDECL(bool) RTUtf16IsValidEncoding(PCRTUTF16 pwsz)
+{
+ int rc = RTUtf16ValidateEncodingEx(pwsz, RTSTR_MAX, 0);
+ return RT_SUCCESS(rc);
+}
+RT_EXPORT_SYMBOL(RTUtf16IsValidEncoding);
+
+
+/**
+ * Helper for RTUtf16PurgeComplementSet.
+ *
+ * @returns true if @a Cp is valid, false if not.
+ * @param Cp The code point to validate.
+ * @param puszValidPairs Pair of valid code point sets.
+ * @param cValidPairs Number of pairs.
+ */
+DECLINLINE(bool) rtUtf16PurgeIsInSet(RTUNICP Cp, PCRTUNICP puszValidPairs, uint32_t cValidPairs)
+{
+ while (cValidPairs-- > 0)
+ {
+ if ( Cp >= puszValidPairs[0]
+ && Cp <= puszValidPairs[1])
+ return true;
+ puszValidPairs += 2;
+ }
+ return false;
+}
+
+
+RTDECL(ssize_t) RTUtf16PurgeComplementSet(PRTUTF16 pwsz, PCRTUNICP puszValidPairs, char chReplacement)
+{
+ AssertReturn(chReplacement && (unsigned)chReplacement < 128, -1);
+
+ /*
+ * Calc valid pairs and check that we've got an even number.
+ */
+ uint32_t cValidPairs = 0;
+ while (puszValidPairs[cValidPairs * 2])
+ {
+ AssertReturn(puszValidPairs[cValidPairs * 2 + 1], -1);
+ AssertMsg(puszValidPairs[cValidPairs * 2] <= puszValidPairs[cValidPairs * 2 + 1],
+ ("%#x vs %#x\n", puszValidPairs[cValidPairs * 2], puszValidPairs[cValidPairs * 2 + 1]));
+ cValidPairs++;
+ }
+
+ /*
+ * Do the replacing.
+ */
+ ssize_t cReplacements = 0;
+ for (;;)
+ {
+ PRTUTF16 pwszCur = pwsz;
+ RTUNICP Cp;
+ int rc = RTUtf16GetCpEx((PCRTUTF16 *)&pwsz, &Cp);
+ if (RT_SUCCESS(rc))
+ {
+ if (Cp)
+ {
+ if (!rtUtf16PurgeIsInSet(Cp, puszValidPairs, cValidPairs))
+ {
+ for (; pwszCur != pwsz; ++pwszCur)
+ *pwszCur = chReplacement;
+ ++cReplacements;
+ }
+ }
+ else
+ break;
+ }
+ else
+ return -1;
+ }
+ return cReplacements;
+}
+RT_EXPORT_SYMBOL(RTUtf16PurgeComplementSet);
+
+
+/**
+ * Validate the UTF-16BE encoding and calculates the length of an UTF-8
+ * encoding.
+ *
+ * @returns iprt status code.
+ * @param pwsz The UTF-16BE string.
+ * @param cwc The max length of the UTF-16BE string to consider.
+ * @param pcch Where to store the length (excluding '\\0') of the UTF-8 string. (cch == cb, btw)
+ *
+ * @note rtUtf16LittleCalcUtf8Length | s/RT_LE2H_U16/RT_BE2H_U16/g
+ */
+static int rtUtf16BigCalcUtf8Length(PCRTUTF16 pwsz, size_t cwc, size_t *pcch)
+{
+ int rc = VINF_SUCCESS;
+ size_t cch = 0;
+ while (cwc > 0)
+ {
+ RTUTF16 wc = *pwsz++; cwc--;
+ if (!wc)
+ break;
+ wc = RT_BE2H_U16(wc);
+ if (wc < 0xd800 || wc > 0xdfff)
+ {
+ if (wc < 0x80)
+ cch++;
+ else if (wc < 0x800)
+ cch += 2;
+ else if (wc < 0xfffe)
+ cch += 3;
+ else
+ {
+ RTStrAssertMsgFailed(("endian indicator! wc=%#x\n", wc));
+ rc = VERR_CODE_POINT_ENDIAN_INDICATOR;
+ break;
+ }
+ }
+ else
+ {
+ if (wc >= 0xdc00)
+ {
+ RTStrAssertMsgFailed(("Wrong 1st char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ if (cwc <= 0)
+ {
+ RTStrAssertMsgFailed(("Invalid length! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ wc = *pwsz++; cwc--;
+ wc = RT_BE2H_U16(wc);
+ if (wc < 0xdc00 || wc > 0xdfff)
+ {
+ RTStrAssertMsgFailed(("Wrong 2nd char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ cch += 4;
+ }
+ }
+
+
+ /* done */
+ *pcch = cch;
+ return rc;
+}
+
+
+/**
+ * Validate the UTF-16LE encoding and calculates the length of an UTF-8
+ * encoding.
+ *
+ * @returns iprt status code.
+ * @param pwsz The UTF-16LE string.
+ * @param cwc The max length of the UTF-16LE string to consider.
+ * @param pcch Where to store the length (excluding '\\0') of the UTF-8 string. (cch == cb, btw)
+ *
+ * @note rtUtf16BigCalcUtf8Length | s/RT_BE2H_U16/RT_LE2H_U16/g
+ */
+static int rtUtf16LittleCalcUtf8Length(PCRTUTF16 pwsz, size_t cwc, size_t *pcch)
+{
+ int rc = VINF_SUCCESS;
+ size_t cch = 0;
+ while (cwc > 0)
+ {
+ RTUTF16 wc = *pwsz++; cwc--;
+ if (!wc)
+ break;
+ wc = RT_LE2H_U16(wc);
+ if (wc < 0xd800 || wc > 0xdfff)
+ {
+ if (wc < 0x80)
+ cch++;
+ else if (wc < 0x800)
+ cch += 2;
+ else if (wc < 0xfffe)
+ cch += 3;
+ else
+ {
+ RTStrAssertMsgFailed(("endian indicator! wc=%#x\n", wc));
+ rc = VERR_CODE_POINT_ENDIAN_INDICATOR;
+ break;
+ }
+ }
+ else
+ {
+ if (wc >= 0xdc00)
+ {
+ RTStrAssertMsgFailed(("Wrong 1st char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ if (cwc <= 0)
+ {
+ RTStrAssertMsgFailed(("Invalid length! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ wc = *pwsz++; cwc--;
+ wc = RT_LE2H_U16(wc);
+ if (wc < 0xdc00 || wc > 0xdfff)
+ {
+ RTStrAssertMsgFailed(("Wrong 2nd char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ cch += 4;
+ }
+ }
+
+
+ /* done */
+ *pcch = cch;
+ return rc;
+}
+
+
+/**
+ * Recodes an valid UTF-16BE string as UTF-8.
+ *
+ * @returns iprt status code.
+ * @param pwsz The UTF-16BE string.
+ * @param cwc The number of RTUTF16 characters to process from pwsz. The recoding
+ * will stop when cwc or '\\0' is reached.
+ * @param psz Where to store the UTF-8 string.
+ * @param cch The size of the UTF-8 buffer, excluding the terminator.
+ * @param pcch Where to store the number of octets actually encoded.
+ *
+ * @note rtUtf16LittleRecodeAsUtf8 == s/RT_BE2H_U16/RT_LE2H_U16/g
+ */
+static int rtUtf16BigRecodeAsUtf8(PCRTUTF16 pwsz, size_t cwc, char *psz, size_t cch, size_t *pcch)
+{
+ unsigned char *pwch = (unsigned char *)psz;
+ int rc = VINF_SUCCESS;
+ while (cwc > 0)
+ {
+ RTUTF16 wc = *pwsz++; cwc--;
+ if (!wc)
+ break;
+ wc = RT_BE2H_U16(wc);
+ if (wc < 0xd800 || wc > 0xdfff)
+ {
+ if (wc < 0x80)
+ {
+ if (RT_UNLIKELY(cch < 1))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 1\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch--;
+ *pwch++ = (unsigned char)wc;
+ }
+ else if (wc < 0x800)
+ {
+ if (RT_UNLIKELY(cch < 2))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 2\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch -= 2;
+ *pwch++ = 0xc0 | (wc >> 6);
+ *pwch++ = 0x80 | (wc & 0x3f);
+ }
+ else if (wc < 0xfffe)
+ {
+ if (RT_UNLIKELY(cch < 3))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 3\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch -= 3;
+ *pwch++ = 0xe0 | (wc >> 12);
+ *pwch++ = 0x80 | ((wc >> 6) & 0x3f);
+ *pwch++ = 0x80 | (wc & 0x3f);
+ }
+ else
+ {
+ RTStrAssertMsgFailed(("endian indicator! wc=%#x\n", wc));
+ rc = VERR_CODE_POINT_ENDIAN_INDICATOR;
+ break;
+ }
+ }
+ else
+ {
+ if (wc >= 0xdc00)
+ {
+ RTStrAssertMsgFailed(("Wrong 1st char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ if (cwc <= 0)
+ {
+ RTStrAssertMsgFailed(("Invalid length! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ RTUTF16 wc2 = *pwsz++; cwc--;
+ wc2 = RT_BE2H_U16(wc2);
+ if (wc2 < 0xdc00 || wc2 > 0xdfff)
+ {
+ RTStrAssertMsgFailed(("Wrong 2nd char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ uint32_t CodePoint = 0x10000
+ + ( ((wc & 0x3ff) << 10)
+ | (wc2 & 0x3ff));
+ if (RT_UNLIKELY(cch < 4))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 4\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch -= 4;
+ *pwch++ = 0xf0 | (CodePoint >> 18);
+ *pwch++ = 0x80 | ((CodePoint >> 12) & 0x3f);
+ *pwch++ = 0x80 | ((CodePoint >> 6) & 0x3f);
+ *pwch++ = 0x80 | (CodePoint & 0x3f);
+ }
+ }
+
+ /* done */
+ *pwch = '\0';
+ *pcch = (char *)pwch - psz;
+ return rc;
+}
+
+
+/**
+ * Recodes an valid UTF-16LE string as UTF-8.
+ *
+ * @returns iprt status code.
+ * @param pwsz The UTF-16LE string.
+ * @param cwc The number of RTUTF16 characters to process from pwsz. The recoding
+ * will stop when cwc or '\\0' is reached.
+ * @param psz Where to store the UTF-8 string.
+ * @param cch The size of the UTF-8 buffer, excluding the terminator.
+ * @param pcch Where to store the number of octets actually encoded.
+ *
+ * @note rtUtf16LittleRecodeAsUtf8 == s/RT_LE2H_U16/RT_GE2H_U16/g
+ */
+static int rtUtf16LittleRecodeAsUtf8(PCRTUTF16 pwsz, size_t cwc, char *psz, size_t cch, size_t *pcch)
+{
+ unsigned char *pwch = (unsigned char *)psz;
+ int rc = VINF_SUCCESS;
+ while (cwc > 0)
+ {
+ RTUTF16 wc = *pwsz++; cwc--;
+ if (!wc)
+ break;
+ wc = RT_LE2H_U16(wc);
+ if (wc < 0xd800 || wc > 0xdfff)
+ {
+ if (wc < 0x80)
+ {
+ if (RT_UNLIKELY(cch < 1))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 1\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch--;
+ *pwch++ = (unsigned char)wc;
+ }
+ else if (wc < 0x800)
+ {
+ if (RT_UNLIKELY(cch < 2))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 2\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch -= 2;
+ *pwch++ = 0xc0 | (wc >> 6);
+ *pwch++ = 0x80 | (wc & 0x3f);
+ }
+ else if (wc < 0xfffe)
+ {
+ if (RT_UNLIKELY(cch < 3))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 3\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch -= 3;
+ *pwch++ = 0xe0 | (wc >> 12);
+ *pwch++ = 0x80 | ((wc >> 6) & 0x3f);
+ *pwch++ = 0x80 | (wc & 0x3f);
+ }
+ else
+ {
+ RTStrAssertMsgFailed(("endian indicator! wc=%#x\n", wc));
+ rc = VERR_CODE_POINT_ENDIAN_INDICATOR;
+ break;
+ }
+ }
+ else
+ {
+ if (wc >= 0xdc00)
+ {
+ RTStrAssertMsgFailed(("Wrong 1st char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ if (cwc <= 0)
+ {
+ RTStrAssertMsgFailed(("Invalid length! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ RTUTF16 wc2 = *pwsz++; cwc--;
+ wc2 = RT_LE2H_U16(wc2);
+ if (wc2 < 0xdc00 || wc2 > 0xdfff)
+ {
+ RTStrAssertMsgFailed(("Wrong 2nd char in surrogate! wc=%#x\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ break;
+ }
+ uint32_t CodePoint = 0x10000
+ + ( ((wc & 0x3ff) << 10)
+ | (wc2 & 0x3ff));
+ if (RT_UNLIKELY(cch < 4))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 4\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch -= 4;
+ *pwch++ = 0xf0 | (CodePoint >> 18);
+ *pwch++ = 0x80 | ((CodePoint >> 12) & 0x3f);
+ *pwch++ = 0x80 | ((CodePoint >> 6) & 0x3f);
+ *pwch++ = 0x80 | (CodePoint & 0x3f);
+ }
+ }
+
+ /* done */
+ *pwch = '\0';
+ *pcch = (char *)pwch - psz;
+ return rc;
+}
+
+
+
+RTDECL(int) RTUtf16ToUtf8Tag(PCRTUTF16 pwszString, char **ppszString, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(ppszString);
+ AssertPtr(pwszString);
+ *ppszString = NULL;
+
+ /*
+ * Validate the UTF-16 string and calculate the length of the UTF-8 encoding of it.
+ */
+ size_t cch;
+#ifdef RT_BIG_ENDIAN
+ int rc = rtUtf16BigCalcUtf8Length(pwszString, RTSTR_MAX, &cch);
+#else
+ int rc = rtUtf16LittleCalcUtf8Length(pwszString, RTSTR_MAX, &cch);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer and recode it.
+ */
+ char *pszResult = (char *)RTMemAllocTag(cch + 1, pszTag);
+ if (pszResult)
+ {
+#ifdef RT_BIG_ENDIAN
+ rc = rtUtf16BigRecodeAsUtf8(pwszString, RTSTR_MAX, pszResult, cch, &cch);
+#else
+ rc = rtUtf16LittleRecodeAsUtf8(pwszString, RTSTR_MAX, pszResult, cch, &cch);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ *ppszString = pszResult;
+ return rc;
+ }
+
+ RTMemFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16ToUtf8Tag);
+
+
+RTDECL(int) RTUtf16BigToUtf8Tag(PCRTUTF16 pwszString, char **ppszString, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(ppszString);
+ AssertPtr(pwszString);
+ *ppszString = NULL;
+
+ /*
+ * Validate the UTF-16 string and calculate the length of the UTF-8 encoding of it.
+ */
+ size_t cch;
+ int rc = rtUtf16BigCalcUtf8Length(pwszString, RTSTR_MAX, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer and recode it.
+ */
+ char *pszResult = (char *)RTMemAllocTag(cch + 1, pszTag);
+ if (pszResult)
+ {
+ rc = rtUtf16BigRecodeAsUtf8(pwszString, RTSTR_MAX, pszResult, cch, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszString = pszResult;
+ return rc;
+ }
+
+ RTMemFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16BigToUtf8Tag);
+
+
+RTDECL(int) RTUtf16LittleToUtf8Tag(PCRTUTF16 pwszString, char **ppszString, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(ppszString);
+ AssertPtr(pwszString);
+ *ppszString = NULL;
+
+ /*
+ * Validate the UTF-16LE string and calculate the length of the UTF-8 encoding of it.
+ */
+ size_t cch;
+ int rc = rtUtf16LittleCalcUtf8Length(pwszString, RTSTR_MAX, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer and recode it.
+ */
+ char *pszResult = (char *)RTMemAllocTag(cch + 1, pszTag);
+ if (pszResult)
+ {
+ rc = rtUtf16LittleRecodeAsUtf8(pwszString, RTSTR_MAX, pszResult, cch, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszString = pszResult;
+ return rc;
+ }
+
+ RTMemFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16LittleToUtf8Tag);
+
+
+RTDECL(int) RTUtf16ToUtf8ExTag(PCRTUTF16 pwszString, size_t cwcString, char **ppsz, size_t cch, size_t *pcch, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pwszString);
+ AssertPtr(ppsz);
+ AssertPtrNull(pcch);
+
+ /*
+ * Validate the UTF-16 string and calculate the length of the UTF-8 encoding of it.
+ */
+ size_t cchResult;
+#ifdef RT_BIG_ENDIAN
+ int rc = rtUtf16BigCalcUtf8Length(pwszString, cwcString, &cchResult);
+#else
+ int rc = rtUtf16LittleCalcUtf8Length(pwszString, cwcString, &cchResult);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ if (pcch)
+ *pcch = cchResult;
+
+ /*
+ * Check buffer size / Allocate buffer and recode it.
+ */
+ bool fShouldFree;
+ char *pszResult;
+ if (cch > 0 && *ppsz)
+ {
+ fShouldFree = false;
+ if (RT_UNLIKELY(cch <= cchResult))
+ return VERR_BUFFER_OVERFLOW;
+ pszResult = *ppsz;
+ }
+ else
+ {
+ *ppsz = NULL;
+ fShouldFree = true;
+ cch = RT_MAX(cch, cchResult + 1);
+ pszResult = (char *)RTStrAllocTag(cch, pszTag);
+ }
+ if (pszResult)
+ {
+#ifdef RT_BIG_ENDIAN
+ rc = rtUtf16BigRecodeAsUtf8(pwszString, cwcString, pszResult, cch - 1, &cch);
+#else
+ rc = rtUtf16LittleRecodeAsUtf8(pwszString, cwcString, pszResult, cch - 1, &cch);
+#endif
+ if (RT_SUCCESS(rc))
+ {
+ *ppsz = pszResult;
+ return rc;
+ }
+
+ if (fShouldFree)
+ RTStrFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16ToUtf8ExTag);
+
+
+RTDECL(int) RTUtf16BigToUtf8ExTag(PCRTUTF16 pwszString, size_t cwcString, char **ppsz, size_t cch, size_t *pcch, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pwszString);
+ AssertPtr(ppsz);
+ AssertPtrNull(pcch);
+
+ /*
+ * Validate the UTF-16BE string and calculate the length of the UTF-8 encoding of it.
+ */
+ size_t cchResult;
+ int rc = rtUtf16BigCalcUtf8Length(pwszString, cwcString, &cchResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcch)
+ *pcch = cchResult;
+
+ /*
+ * Check buffer size / Allocate buffer and recode it.
+ */
+ bool fShouldFree;
+ char *pszResult;
+ if (cch > 0 && *ppsz)
+ {
+ fShouldFree = false;
+ if (RT_UNLIKELY(cch <= cchResult))
+ return VERR_BUFFER_OVERFLOW;
+ pszResult = *ppsz;
+ }
+ else
+ {
+ *ppsz = NULL;
+ fShouldFree = true;
+ cch = RT_MAX(cch, cchResult + 1);
+ pszResult = (char *)RTStrAllocTag(cch, pszTag);
+ }
+ if (pszResult)
+ {
+ rc = rtUtf16BigRecodeAsUtf8(pwszString, cwcString, pszResult, cch - 1, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ *ppsz = pszResult;
+ return rc;
+ }
+
+ if (fShouldFree)
+ RTStrFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16BigToUtf8ExTag);
+
+
+RTDECL(int) RTUtf16LittleToUtf8ExTag(PCRTUTF16 pwszString, size_t cwcString, char **ppsz, size_t cch, size_t *pcch,
+ const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pwszString);
+ AssertPtr(ppsz);
+ AssertPtrNull(pcch);
+
+ /*
+ * Validate the UTF-16LE string and calculate the length of the UTF-8 encoding of it.
+ */
+ size_t cchResult;
+ int rc = rtUtf16LittleCalcUtf8Length(pwszString, cwcString, &cchResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcch)
+ *pcch = cchResult;
+
+ /*
+ * Check buffer size / Allocate buffer and recode it.
+ */
+ bool fShouldFree;
+ char *pszResult;
+ if (cch > 0 && *ppsz)
+ {
+ fShouldFree = false;
+ if (RT_UNLIKELY(cch <= cchResult))
+ return VERR_BUFFER_OVERFLOW;
+ pszResult = *ppsz;
+ }
+ else
+ {
+ *ppsz = NULL;
+ fShouldFree = true;
+ cch = RT_MAX(cch, cchResult + 1);
+ pszResult = (char *)RTStrAllocTag(cch, pszTag);
+ }
+ if (pszResult)
+ {
+ rc = rtUtf16LittleRecodeAsUtf8(pwszString, cwcString, pszResult, cch - 1, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ *ppsz = pszResult;
+ return rc;
+ }
+
+ if (fShouldFree)
+ RTStrFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16BigToUtf8ExTag);
+
+
+RTDECL(size_t) RTUtf16CalcUtf8Len(PCRTUTF16 pwsz)
+{
+ size_t cch;
+#ifdef RT_BIG_ENDIAN
+ int rc = rtUtf16BigCalcUtf8Length(pwsz, RTSTR_MAX, &cch);
+#else
+ int rc = rtUtf16LittleCalcUtf8Length(pwsz, RTSTR_MAX, &cch);
+#endif
+ return RT_SUCCESS(rc) ? cch : 0;
+}
+RT_EXPORT_SYMBOL(RTUtf16CalcUtf8Len);
+
+
+RTDECL(size_t) RTUtf16BigCalcUtf8Len(PCRTUTF16 pwsz)
+{
+ size_t cch;
+ int rc = rtUtf16BigCalcUtf8Length(pwsz, RTSTR_MAX, &cch);
+ return RT_SUCCESS(rc) ? cch : 0;
+}
+RT_EXPORT_SYMBOL(RTUtf16BigCalcUtf8Len);
+
+
+RTDECL(size_t) RTUtf16LittleCalcUtf8Len(PCRTUTF16 pwsz)
+{
+ size_t cch;
+ int rc = rtUtf16LittleCalcUtf8Length(pwsz, RTSTR_MAX, &cch);
+ return RT_SUCCESS(rc) ? cch : 0;
+}
+RT_EXPORT_SYMBOL(RTUtf16LittleCalcUtf8Len);
+
+
+RTDECL(int) RTUtf16CalcUtf8LenEx(PCRTUTF16 pwsz, size_t cwc, size_t *pcch)
+{
+ size_t cch;
+#ifdef RT_BIG_ENDIAN
+ int rc = rtUtf16BigCalcUtf8Length(pwsz, cwc, &cch);
+#else
+ int rc = rtUtf16LittleCalcUtf8Length(pwsz, cwc, &cch);
+#endif
+ if (pcch)
+ *pcch = RT_SUCCESS(rc) ? cch : ~(size_t)0;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16CalcUtf8LenEx);
+
+
+RTDECL(int) RTUtf16BigCalcUtf8LenEx(PCRTUTF16 pwsz, size_t cwc, size_t *pcch)
+{
+ size_t cch;
+ int rc = rtUtf16BigCalcUtf8Length(pwsz, cwc, &cch);
+ if (pcch)
+ *pcch = RT_SUCCESS(rc) ? cch : ~(size_t)0;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16BigCalcUtf8LenEx);
+
+
+RTDECL(int) RTUtf16LittleCalcUtf8LenEx(PCRTUTF16 pwsz, size_t cwc, size_t *pcch)
+{
+ size_t cch;
+ int rc = rtUtf16LittleCalcUtf8Length(pwsz, cwc, &cch);
+ if (pcch)
+ *pcch = RT_SUCCESS(rc) ? cch : ~(size_t)0;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16LittleCalcUtf8LenEx);
+
+
+RTDECL(RTUNICP) RTUtf16GetCpInternal(PCRTUTF16 pwsz)
+{
+ const RTUTF16 wc = *pwsz;
+
+ /* simple */
+ if (wc < 0xd800 || (wc > 0xdfff && wc < 0xfffe))
+ return wc;
+ if (wc < 0xfffe)
+ {
+ /* surrogate pair */
+ if (wc < 0xdc00)
+ {
+ const RTUTF16 wc2 = pwsz[1];
+ if (wc2 >= 0xdc00 && wc2 <= 0xdfff)
+ {
+ RTUNICP uc = 0x10000 + (((wc & 0x3ff) << 10) | (wc2 & 0x3ff));
+ return uc;
+ }
+
+ RTStrAssertMsgFailed(("wc=%#08x wc2=%#08x - invalid 2nd char in surrogate pair\n", wc, wc2));
+ }
+ else
+ RTStrAssertMsgFailed(("wc=%#08x - invalid surrogate pair order\n", wc));
+ }
+ else
+ RTStrAssertMsgFailed(("wc=%#08x - endian indicator\n", wc));
+ return RTUNICP_INVALID;
+}
+RT_EXPORT_SYMBOL(RTUtf16GetCpInternal);
+
+
+RTDECL(int) RTUtf16GetCpExInternal(PCRTUTF16 *ppwsz, PRTUNICP pCp)
+{
+ const RTUTF16 wc = **ppwsz;
+
+ /* simple */
+ if (wc < 0xd800 || (wc > 0xdfff && wc < 0xfffe))
+ {
+ (*ppwsz)++;
+ *pCp = wc;
+ return VINF_SUCCESS;
+ }
+
+ int rc;
+ if (wc < 0xfffe)
+ {
+ /* surrogate pair */
+ if (wc < 0xdc00)
+ {
+ const RTUTF16 wc2 = (*ppwsz)[1];
+ if (wc2 >= 0xdc00 && wc2 <= 0xdfff)
+ {
+ RTUNICP uc = 0x10000 + (((wc & 0x3ff) << 10) | (wc2 & 0x3ff));
+ *pCp = uc;
+ (*ppwsz) += 2;
+ return VINF_SUCCESS;
+ }
+
+ RTStrAssertMsgFailed(("wc=%#08x wc2=%#08x - invalid 2nd char in surrogate pair\n", wc, wc2));
+ }
+ else
+ RTStrAssertMsgFailed(("wc=%#08x - invalid surrogate pair order\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ }
+ else
+ {
+ RTStrAssertMsgFailed(("wc=%#08x - endian indicator\n", wc));
+ rc = VERR_CODE_POINT_ENDIAN_INDICATOR;
+ }
+ *pCp = RTUNICP_INVALID;
+ (*ppwsz)++;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16GetCpExInternal);
+
+
+RTDECL(int) RTUtf16GetCpNExInternal(PCRTUTF16 *ppwsz, size_t *pcwc, PRTUNICP pCp)
+{
+ int rc;
+ const size_t cwc = *pcwc;
+ if (cwc > 0)
+ {
+ PCRTUTF16 pwsz = *ppwsz;
+ const RTUTF16 wc = **ppwsz;
+
+ /* simple */
+ if (wc < 0xd800 || (wc > 0xdfff && wc < 0xfffe))
+ {
+ *pCp = wc;
+ *pcwc = cwc - 1;
+ *ppwsz = pwsz + 1;
+ return VINF_SUCCESS;
+ }
+
+ if (wc < 0xfffe)
+ {
+ /* surrogate pair */
+ if (wc < 0xdc00)
+ {
+ if (cwc >= 2)
+ {
+ const RTUTF16 wc2 = pwsz[1];
+ if (wc2 >= 0xdc00 && wc2 <= 0xdfff)
+ {
+ *pCp = 0x10000 + (((wc & 0x3ff) << 10) | (wc2 & 0x3ff));
+ *pcwc = cwc - 2;
+ *ppwsz = pwsz + 2;
+ return VINF_SUCCESS;
+ }
+
+ RTStrAssertMsgFailed(("wc=%#08x wc2=%#08x - invalid 2nd char in surrogate pair\n", wc, wc2));
+ }
+ else
+ RTStrAssertMsgFailed(("wc=%#08x - incomplete surrogate pair\n", wc));
+ }
+ else
+ RTStrAssertMsgFailed(("wc=%#08x - invalid surrogate pair order\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ }
+ else
+ {
+ RTStrAssertMsgFailed(("wc=%#08x - endian indicator\n", wc));
+ rc = VERR_CODE_POINT_ENDIAN_INDICATOR;
+ }
+ *pcwc = cwc - 1;
+ *ppwsz = pwsz + 1;
+ }
+ else
+ rc = VERR_END_OF_STRING;
+ *pCp = RTUNICP_INVALID;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16GetCpNExInternal);
+
+
+RTDECL(int) RTUtf16BigGetCpExInternal(PCRTUTF16 *ppwsz, PRTUNICP pCp)
+{
+ const RTUTF16 wc = RT_BE2H_U16(**ppwsz);
+
+ /* simple */
+ if (wc < 0xd800 || (wc > 0xdfff && wc < 0xfffe))
+ {
+ (*ppwsz)++;
+ *pCp = wc;
+ return VINF_SUCCESS;
+ }
+
+ int rc;
+ if (wc < 0xfffe)
+ {
+ /* surrogate pair */
+ if (wc < 0xdc00)
+ {
+ const RTUTF16 wc2 = RT_BE2H_U16((*ppwsz)[1]);
+ if (wc2 >= 0xdc00 && wc2 <= 0xdfff)
+ {
+ RTUNICP uc = 0x10000 + (((wc & 0x3ff) << 10) | (wc2 & 0x3ff));
+ *pCp = uc;
+ (*ppwsz) += 2;
+ return VINF_SUCCESS;
+ }
+
+ RTStrAssertMsgFailed(("wc=%#08x wc2=%#08x - invalid 2nd char in surrogate pair\n", wc, wc2));
+ }
+ else
+ RTStrAssertMsgFailed(("wc=%#08x - invalid surrogate pair order\n", wc));
+ rc = VERR_INVALID_UTF16_ENCODING;
+ }
+ else
+ {
+ RTStrAssertMsgFailed(("wc=%#08x - endian indicator\n", wc));
+ rc = VERR_CODE_POINT_ENDIAN_INDICATOR;
+ }
+ *pCp = RTUNICP_INVALID;
+ (*ppwsz)++;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTUtf16GetCpExInternal);
+
+
+RTDECL(PRTUTF16) RTUtf16PutCpInternal(PRTUTF16 pwsz, RTUNICP CodePoint)
+{
+ /* simple */
+ if ( CodePoint < 0xd800
+ || ( CodePoint > 0xdfff
+ && CodePoint < 0xfffe))
+ {
+ *pwsz++ = (RTUTF16)CodePoint;
+ return pwsz;
+ }
+
+ /* surrogate pair */
+ if (CodePoint >= 0x10000 && CodePoint <= 0x0010ffff)
+ {
+ CodePoint -= 0x10000;
+ *pwsz++ = 0xd800 | (CodePoint >> 10);
+ *pwsz++ = 0xdc00 | (CodePoint & 0x3ff);
+ return pwsz;
+ }
+
+ /* invalid code point. */
+ RTStrAssertMsgFailed(("Invalid codepoint %#x\n", CodePoint));
+ *pwsz++ = 0x7f;
+ return pwsz;
+}
+RT_EXPORT_SYMBOL(RTUtf16PutCpInternal);
+
diff --git a/src/VBox/Runtime/common/string/utf-8-case.cpp b/src/VBox/Runtime/common/string/utf-8-case.cpp
new file mode 100644
index 00000000..3701a343
--- /dev/null
+++ b/src/VBox/Runtime/common/string/utf-8-case.cpp
@@ -0,0 +1,318 @@
+/* $Id: utf-8-case.cpp $ */
+/** @file
+ * IPRT - UTF-8 Case Sensitivity and Folding, Part 1.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/uni.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include "internal/string.h"
+
+
+
+RTDECL(int) RTStrICmp(const char *psz1, const char *psz2)
+{
+ if (psz1 == psz2)
+ return 0;
+ if (!psz1)
+ return -1;
+ if (!psz2)
+ return 1;
+
+ const char *pszStart1 = psz1;
+ for (;;)
+ {
+ /* Get the codepoints */
+ RTUNICP uc1;
+ int rc = RTStrGetCpEx(&psz1, &uc1);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ psz1--;
+ break;
+ }
+
+ RTUNICP uc2;
+ rc = RTStrGetCpEx(&psz2, &uc2);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ psz2--;
+ psz1 = RTStrPrevCp(pszStart1, psz1);
+ break;
+ }
+
+ /* compare */
+ int iDiff = uc1 - uc2;
+ if (iDiff)
+ {
+ iDiff = RTUniCpToUpper(uc1) != RTUniCpToUpper(uc2);
+ if (iDiff)
+ {
+ iDiff = RTUniCpToLower(uc1) - RTUniCpToLower(uc2); /* lower case diff last! */
+ if (iDiff)
+ return iDiff;
+ }
+ }
+
+ /* hit the terminator? */
+ if (!uc1)
+ return 0;
+ }
+
+ /* Hit some bad encoding, continue in case sensitive mode. */
+ return RTStrCmp(psz1, psz2);
+}
+RT_EXPORT_SYMBOL(RTStrICmp);
+
+
+RTDECL(int) RTStrNICmp(const char *psz1, const char *psz2, size_t cchMax)
+{
+ if (cchMax == 0)
+ return 0;
+ if (psz1 == psz2)
+ return 0;
+ if (!psz1)
+ return -1;
+ if (!psz2)
+ return 1;
+
+ for (;;)
+ {
+ /* Get the codepoints */
+ RTUNICP uc1;
+ size_t cchMax2 = cchMax;
+ int rc = RTStrGetCpNEx(&psz1, &cchMax, &uc1);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ psz1--;
+ cchMax++;
+ break;
+ }
+
+ RTUNICP uc2;
+ rc = RTStrGetCpNEx(&psz2, &cchMax2, &uc2);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ psz2--;
+ psz1 -= (cchMax - cchMax2 + 1); /* This can't overflow, can it? */
+ cchMax = cchMax2 + 1;
+ break;
+ }
+
+ /* compare */
+ int iDiff = uc1 - uc2;
+ if (iDiff)
+ {
+ iDiff = RTUniCpToUpper(uc1) != RTUniCpToUpper(uc2);
+ if (iDiff)
+ {
+ iDiff = RTUniCpToLower(uc1) - RTUniCpToLower(uc2); /* lower case diff last! */
+ if (iDiff)
+ return iDiff;
+ }
+ }
+
+ /* hit the terminator? */
+ if (!uc1 || cchMax == 0)
+ return 0;
+ }
+
+ /* Hit some bad encoding, continue in case insensitive mode. */
+ return RTStrNCmp(psz1, psz2, cchMax);
+}
+RT_EXPORT_SYMBOL(RTStrNICmp);
+
+
+RTDECL(char *) RTStrIStr(const char *pszHaystack, const char *pszNeedle)
+{
+ /* Any NULL strings means NULL return. (In the RTStrCmp tradition.) */
+ if (!pszHaystack)
+ return NULL;
+ if (!pszNeedle)
+ return NULL;
+
+ /* The empty string matches everything. */
+ if (!*pszNeedle)
+ return (char *)pszHaystack;
+
+ /*
+ * The search strategy is to pick out the first char of the needle, fold it,
+ * and match it against the haystack code point by code point. When encountering
+ * a matching code point we use RTStrNICmp for the remainder (if any) of the needle.
+ */
+ const char * const pszNeedleStart = pszNeedle;
+ RTUNICP Cp0;
+ RTStrGetCpEx(&pszNeedle, &Cp0); /* pszNeedle is advanced one code point. */
+ size_t const cchNeedle = strlen(pszNeedle);
+ size_t const cchNeedleCp0= pszNeedle - pszNeedleStart;
+ RTUNICP const Cp0Lower = RTUniCpToLower(Cp0);
+ RTUNICP const Cp0Upper = RTUniCpToUpper(Cp0);
+ if ( Cp0Lower == Cp0Upper
+ && Cp0Lower == Cp0)
+ {
+ /* Cp0 is not a case sensitive char. */
+ for (;;)
+ {
+ RTUNICP Cp;
+ RTStrGetCpEx(&pszHaystack, &Cp);
+ if (!Cp)
+ break;
+ if ( Cp == Cp0
+ && !RTStrNICmp(pszHaystack, pszNeedle, cchNeedle))
+ return (char *)pszHaystack - cchNeedleCp0;
+ }
+ }
+ else if ( Cp0Lower == Cp0
+ || Cp0Upper != Cp0)
+ {
+ /* Cp0 is case sensitive */
+ for (;;)
+ {
+ RTUNICP Cp;
+ RTStrGetCpEx(&pszHaystack, &Cp);
+ if (!Cp)
+ break;
+ if ( ( Cp == Cp0Upper
+ || Cp == Cp0Lower)
+ && !RTStrNICmp(pszHaystack, pszNeedle, cchNeedle))
+ return (char *)pszHaystack - cchNeedleCp0;
+ }
+ }
+ else
+ {
+ /* Cp0 is case sensitive and folds to two difference chars. (paranoia) */
+ for (;;)
+ {
+ RTUNICP Cp;
+ RTStrGetCpEx(&pszHaystack, &Cp);
+ if (!Cp)
+ break;
+ if ( ( Cp == Cp0
+ || Cp == Cp0Upper
+ || Cp == Cp0Lower)
+ && !RTStrNICmp(pszHaystack, pszNeedle, cchNeedle))
+ return (char *)pszHaystack - cchNeedleCp0;
+ }
+ }
+
+
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTStrIStr);
+
+
+RTDECL(char *) RTStrToLower(char *psz)
+{
+ /*
+ * Loop the code points in the string, converting them one by one.
+ *
+ * ASSUMES that the folded code points have an encoding that is equal or
+ * shorter than the original (this is presently correct).
+ */
+ const char *pszSrc = psz;
+ char *pszDst = psz;
+ RTUNICP uc;
+ do
+ {
+ int rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ RTUNICP uc2 = RTUniCpToLower(uc);
+ if (RT_LIKELY( uc2 == uc
+ || RTUniCpCalcUtf8Len(uc2) == RTUniCpCalcUtf8Len(uc)))
+ pszDst = RTStrPutCp(pszDst, uc2);
+ else
+ pszDst = RTStrPutCp(pszDst, uc);
+ }
+ else
+ {
+ /* bad encoding, just copy it quietly (uc == RTUNICP_INVALID (!= 0)). */
+ AssertRC(rc);
+ *pszDst++ = pszSrc[-1];
+ }
+ Assert((uintptr_t)pszDst <= (uintptr_t)pszSrc);
+ } while (uc != 0);
+
+ return psz;
+}
+RT_EXPORT_SYMBOL(RTStrToLower);
+
+
+RTDECL(char *) RTStrToUpper(char *psz)
+{
+ /*
+ * Loop the code points in the string, converting them one by one.
+ *
+ * ASSUMES that the folded code points have an encoding that is equal or
+ * shorter than the original (this is presently correct).
+ */
+ const char *pszSrc = psz;
+ char *pszDst = psz;
+ RTUNICP uc;
+ do
+ {
+ int rc = RTStrGetCpEx(&pszSrc, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ RTUNICP uc2 = RTUniCpToUpper(uc);
+ if (RT_LIKELY( uc2 == uc
+ || RTUniCpCalcUtf8Len(uc2) == RTUniCpCalcUtf8Len(uc)))
+ pszDst = RTStrPutCp(pszDst, uc2);
+ else
+ pszDst = RTStrPutCp(pszDst, uc);
+ }
+ else
+ {
+ /* bad encoding, just copy it quietly (uc == RTUNICP_INVALID (!= 0)). */
+ AssertRC(rc);
+ *pszDst++ = pszSrc[-1];
+ }
+ Assert((uintptr_t)pszDst <= (uintptr_t)pszSrc);
+ } while (uc != 0);
+
+ return psz;
+}
+RT_EXPORT_SYMBOL(RTStrToUpper);
+
diff --git a/src/VBox/Runtime/common/string/utf-8-case2.cpp b/src/VBox/Runtime/common/string/utf-8-case2.cpp
new file mode 100644
index 00000000..993de434
--- /dev/null
+++ b/src/VBox/Runtime/common/string/utf-8-case2.cpp
@@ -0,0 +1,128 @@
+/* $Id: utf-8-case2.cpp $ */
+/** @file
+ * IPRT - UTF-8 Case Sensitivity and Folding, Part 2 (requires unidata-flags.cpp).
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include "internal/iprt.h"
+
+#include <iprt/uni.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include "internal/string.h"
+
+
+RTDECL(bool) RTStrIsCaseFoldable(const char *psz)
+{
+ /*
+ * Loop the code points in the string, checking them one by one until we
+ * find something that can be folded.
+ */
+ RTUNICP uc;
+ do
+ {
+ int rc = RTStrGetCpEx(&psz, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTUniCpIsFoldable(uc))
+ return true;
+ }
+ else
+ {
+ /* bad encoding, just skip it quietly (uc == RTUNICP_INVALID (!= 0)). */
+ AssertRC(rc);
+ }
+ } while (uc != 0);
+
+ return false;
+}
+RT_EXPORT_SYMBOL(RTStrIsCaseFoldable);
+
+
+RTDECL(bool) RTStrIsUpperCased(const char *psz)
+{
+ /*
+ * Check that there are no lower case chars in the string.
+ */
+ RTUNICP uc;
+ do
+ {
+ int rc = RTStrGetCpEx(&psz, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTUniCpIsLower(uc))
+ return false;
+ }
+ else
+ {
+ /* bad encoding, just skip it quietly (uc == RTUNICP_INVALID (!= 0)). */
+ AssertRC(rc);
+ }
+ } while (uc != 0);
+
+ return true;
+}
+RT_EXPORT_SYMBOL(RTStrIsUpperCased);
+
+
+RTDECL(bool) RTStrIsLowerCased(const char *psz)
+{
+ /*
+ * Check that there are no lower case chars in the string.
+ */
+ RTUNICP uc;
+ do
+ {
+ int rc = RTStrGetCpEx(&psz, &uc);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTUniCpIsUpper(uc))
+ return false;
+ }
+ else
+ {
+ /* bad encoding, just skip it quietly (uc == RTUNICP_INVALID (!= 0)). */
+ AssertRC(rc);
+ }
+ } while (uc != 0);
+
+ return true;
+}
+RT_EXPORT_SYMBOL(RTStrIsLowerCased);
+
diff --git a/src/VBox/Runtime/common/string/utf-8.cpp b/src/VBox/Runtime/common/string/utf-8.cpp
new file mode 100644
index 00000000..c02e6298
--- /dev/null
+++ b/src/VBox/Runtime/common/string/utf-8.cpp
@@ -0,0 +1,2043 @@
+/* $Id: utf-8.cpp $ */
+/** @file
+ * IPRT - UTF-8 Decoding.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/string.h>
+#include <iprt/latin1.h>
+#include "internal/iprt.h"
+
+#include <iprt/uni.h>
+#include <iprt/asm.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include "internal/string.h"
+
+
+
+/**
+ * Get get length in code points of a UTF-8 encoded string.
+ * The string is validated while doing this.
+ *
+ * @returns IPRT status code.
+ * @param psz Pointer to the UTF-8 string.
+ * @param cch The max length of the string. (btw cch = cb)
+ * Use RTSTR_MAX if all of the string is to be examined.
+ * @param pcuc Where to store the length in unicode code points.
+ * @param pcchActual Where to store the actual size of the UTF-8 string
+ * on success (cch = cb again). Optional.
+ */
+DECLHIDDEN(int) rtUtf8Length(const char *psz, size_t cch, size_t *pcuc, size_t *pcchActual)
+{
+ const unsigned char *puch = (const unsigned char *)psz;
+ size_t cCodePoints = 0;
+ while (cch > 0)
+ {
+ const unsigned char uch = *puch;
+ if (!uch)
+ break;
+ if (uch & RT_BIT(7))
+ {
+ /* figure sequence length and validate the first byte */
+/** @todo RT_USE_RTC_3629 */
+ unsigned cb;
+ if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5))) == (RT_BIT(7) | RT_BIT(6)))
+ cb = 2;
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5)))
+ cb = 3;
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4)))
+ cb = 4;
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3) | RT_BIT(2))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3)))
+ cb = 5;
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3) | RT_BIT(2)))
+ cb = 6;
+ else
+ {
+ RTStrAssertMsgFailed(("Invalid UTF-8 first byte: %.*Rhxs\n", RT_MIN(cch, 10), puch));
+ return VERR_INVALID_UTF8_ENCODING;
+ }
+
+ /* check length */
+ if (cb > cch)
+ {
+ RTStrAssertMsgFailed(("Invalid UTF-8 length: cb=%d cch=%d (%.*Rhxs)\n", cb, cch, RT_MIN(cch, 10), puch));
+ return VERR_INVALID_UTF8_ENCODING;
+ }
+
+ /* validate the rest */
+ switch (cb)
+ {
+ case 6:
+ RTStrAssertMsgReturn((puch[5] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("6/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RT_FALL_THRU();
+ case 5:
+ RTStrAssertMsgReturn((puch[4] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("5/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RT_FALL_THRU();
+ case 4:
+ RTStrAssertMsgReturn((puch[3] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("4/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RT_FALL_THRU();
+ case 3:
+ RTStrAssertMsgReturn((puch[2] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("3/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RT_FALL_THRU();
+ case 2:
+ RTStrAssertMsgReturn((puch[1] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("2/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ break;
+ }
+
+ /* validate the code point. */
+ RTUNICP uc;
+ switch (cb)
+ {
+ case 6:
+ uc = (puch[5] & 0x3f)
+ | ((RTUNICP)(puch[4] & 0x3f) << 6)
+ | ((RTUNICP)(puch[3] & 0x3f) << 12)
+ | ((RTUNICP)(puch[2] & 0x3f) << 18)
+ | ((RTUNICP)(puch[1] & 0x3f) << 24)
+ | ((RTUNICP)(uch & 0x01) << 30);
+ RTStrAssertMsgReturn(uc >= 0x04000000 && uc <= 0x7fffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ break;
+ case 5:
+ uc = (puch[4] & 0x3f)
+ | ((RTUNICP)(puch[3] & 0x3f) << 6)
+ | ((RTUNICP)(puch[2] & 0x3f) << 12)
+ | ((RTUNICP)(puch[1] & 0x3f) << 18)
+ | ((RTUNICP)(uch & 0x03) << 24);
+ RTStrAssertMsgReturn(uc >= 0x00200000 && uc <= 0x03ffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ break;
+ case 4:
+ uc = (puch[3] & 0x3f)
+ | ((RTUNICP)(puch[2] & 0x3f) << 6)
+ | ((RTUNICP)(puch[1] & 0x3f) << 12)
+ | ((RTUNICP)(uch & 0x07) << 18);
+ RTStrAssertMsgReturn(uc >= 0x00010000 && uc <= 0x001fffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ break;
+ case 3:
+ uc = (puch[2] & 0x3f)
+ | ((RTUNICP)(puch[1] & 0x3f) << 6)
+ | ((RTUNICP)(uch & 0x0f) << 12);
+ RTStrAssertMsgReturn(uc >= 0x00000800 && uc <= 0x0000fffd,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch),
+ uc == 0xffff || uc == 0xfffe ? VERR_CODE_POINT_ENDIAN_INDICATOR : VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn(uc < 0xd800 || uc > 0xdfff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_CODE_POINT_SURROGATE);
+ break;
+ case 2:
+ uc = (puch[1] & 0x3f)
+ | ((RTUNICP)(uch & 0x1f) << 6);
+ RTStrAssertMsgReturn(uc >= 0x00000080 && uc <= 0x000007ff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ break;
+ }
+
+ /* advance */
+ cch -= cb;
+ puch += cb;
+ }
+ else
+ {
+ /* one ASCII byte */
+ puch++;
+ cch--;
+ }
+ cCodePoints++;
+ }
+
+ /* done */
+ *pcuc = cCodePoints;
+ if (pcchActual)
+ *pcchActual = puch - (unsigned char const *)psz;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Decodes and UTF-8 string into an array of unicode code point.
+ *
+ * Since we know the input is valid, we do *not* perform encoding or length checks.
+ *
+ * @returns iprt status code.
+ * @param psz The UTF-8 string to recode. This is a valid encoding.
+ * @param cch The number of chars (the type char, so bytes if you like) to process of the UTF-8 string.
+ * The recoding will stop when cch or '\\0' is reached. Pass RTSTR_MAX to process up to '\\0'.
+ * @param paCps Where to store the code points array.
+ * @param cCps The number of RTUNICP items the paCps buffer can hold, excluding the terminator ('\\0').
+ */
+static int rtUtf8Decode(const char *psz, size_t cch, PRTUNICP paCps, size_t cCps)
+{
+ int rc = VINF_SUCCESS;
+ const unsigned char *puch = (const unsigned char *)psz;
+ PRTUNICP pCp = paCps;
+ while (cch > 0)
+ {
+ /* read the next char and check for terminator. */
+ const unsigned char uch = *puch;
+ if (uch)
+ { /* we only break once, so consider this the likely branch. */ }
+ else
+ break;
+
+ /* check for output overflow */
+ if (RT_LIKELY(cCps >= 1))
+ { /* likely */ }
+ else
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cCps--;
+
+ /* decode and recode the code point */
+ if (!(uch & RT_BIT(7)))
+ {
+ *pCp++ = uch;
+ puch++;
+ cch--;
+ }
+#ifdef RT_STRICT
+ else if (!(uch & RT_BIT(6)))
+ AssertMsgFailed(("Internal error!\n"));
+#endif
+ else if (!(uch & RT_BIT(5)))
+ {
+ *pCp++ = (puch[1] & 0x3f)
+ | ((uint16_t)(uch & 0x1f) << 6);
+ puch += 2;
+ cch -= 2;
+ }
+ else if (!(uch & RT_BIT(4)))
+ {
+ *pCp++ = (puch[2] & 0x3f)
+ | ((uint16_t)(puch[1] & 0x3f) << 6)
+ | ((uint16_t)(uch & 0x0f) << 12);
+ puch += 3;
+ cch -= 3;
+ }
+ else if (!(uch & RT_BIT(3)))
+ {
+ *pCp++ = (puch[3] & 0x3f)
+ | ((RTUNICP)(puch[2] & 0x3f) << 6)
+ | ((RTUNICP)(puch[1] & 0x3f) << 12)
+ | ((RTUNICP)(uch & 0x07) << 18);
+ puch += 4;
+ cch -= 4;
+ }
+ else if (!(uch & RT_BIT(2)))
+ {
+ *pCp++ = (puch[4] & 0x3f)
+ | ((RTUNICP)(puch[3] & 0x3f) << 6)
+ | ((RTUNICP)(puch[2] & 0x3f) << 12)
+ | ((RTUNICP)(puch[1] & 0x3f) << 18)
+ | ((RTUNICP)(uch & 0x03) << 24);
+ puch += 5;
+ cch -= 6;
+ }
+ else
+ {
+ Assert(!(uch & RT_BIT(1)));
+ *pCp++ = (puch[5] & 0x3f)
+ | ((RTUNICP)(puch[4] & 0x3f) << 6)
+ | ((RTUNICP)(puch[3] & 0x3f) << 12)
+ | ((RTUNICP)(puch[2] & 0x3f) << 18)
+ | ((RTUNICP)(puch[1] & 0x3f) << 24)
+ | ((RTUNICP)(uch & 0x01) << 30);
+ puch += 6;
+ cch -= 6;
+ }
+ }
+
+ /* done */
+ *pCp = 0;
+ return rc;
+}
+
+
+RTDECL(size_t) RTStrUniLen(const char *psz)
+{
+ size_t cCodePoints;
+ int rc = rtUtf8Length(psz, RTSTR_MAX, &cCodePoints, NULL);
+ return RT_SUCCESS(rc) ? cCodePoints : 0;
+}
+RT_EXPORT_SYMBOL(RTStrUniLen);
+
+
+RTDECL(int) RTStrUniLenEx(const char *psz, size_t cch, size_t *pcCps)
+{
+ size_t cCodePoints;
+ int rc = rtUtf8Length(psz, cch, &cCodePoints, NULL);
+ if (pcCps)
+ *pcCps = RT_SUCCESS(rc) ? cCodePoints : 0;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrUniLenEx);
+
+
+RTDECL(int) RTStrValidateEncoding(const char *psz)
+{
+ return RTStrValidateEncodingEx(psz, RTSTR_MAX, 0);
+}
+RT_EXPORT_SYMBOL(RTStrValidateEncoding);
+
+
+RTDECL(int) RTStrValidateEncodingEx(const char *psz, size_t cch, uint32_t fFlags)
+{
+ AssertReturn(!(fFlags & ~(RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED | RTSTR_VALIDATE_ENCODING_EXACT_LENGTH)),
+ VERR_INVALID_PARAMETER);
+ AssertPtr(psz);
+
+ /*
+ * Use rtUtf8Length for the job.
+ */
+ size_t cchActual;
+ size_t cCpsIgnored;
+ int rc = rtUtf8Length(psz, cch, &cCpsIgnored, &cchActual);
+ if (RT_SUCCESS(rc))
+ {
+ if (fFlags & RTSTR_VALIDATE_ENCODING_EXACT_LENGTH)
+ {
+ if (fFlags & RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED)
+ cchActual++;
+ if (cchActual == cch)
+ rc = VINF_SUCCESS;
+ else if (cchActual < cch)
+ rc = VERR_BUFFER_UNDERFLOW;
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ else if ( (fFlags & RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED)
+ && cchActual >= cch)
+ rc = VERR_BUFFER_OVERFLOW;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrValidateEncodingEx);
+
+
+RTDECL(bool) RTStrIsValidEncoding(const char *psz)
+{
+ int rc = RTStrValidateEncodingEx(psz, RTSTR_MAX, 0);
+ return RT_SUCCESS(rc);
+}
+RT_EXPORT_SYMBOL(RTStrIsValidEncoding);
+
+
+RTDECL(size_t) RTStrPurgeEncoding(char *psz)
+{
+ size_t cErrors = 0;
+ for (;;)
+ {
+ RTUNICP Cp;
+ int rc = RTStrGetCpEx((const char **)&psz, &Cp);
+ if (RT_SUCCESS(rc))
+ {
+ if (!Cp)
+ break;
+ }
+ else
+ {
+ psz[-1] = '?';
+ cErrors++;
+ }
+ }
+ return cErrors;
+}
+RT_EXPORT_SYMBOL(RTStrPurgeEncoding);
+
+
+/**
+ * Helper for RTStrPurgeComplementSet.
+ *
+ * @returns true if @a Cp is valid, false if not.
+ * @param Cp The code point to validate.
+ * @param puszValidPairs Pair of valid code point sets.
+ * @param cValidPairs Number of pairs.
+ */
+DECLINLINE(bool) rtStrPurgeIsInSet(RTUNICP Cp, PCRTUNICP puszValidPairs, uint32_t cValidPairs)
+{
+ while (cValidPairs-- > 0)
+ {
+ if ( Cp >= puszValidPairs[0]
+ && Cp <= puszValidPairs[1])
+ return true;
+ puszValidPairs += 2;
+ }
+ return false;
+}
+
+
+RTDECL(ssize_t) RTStrPurgeComplementSet(char *psz, PCRTUNICP puszValidPairs, char chReplacement)
+{
+ AssertReturn(chReplacement && (unsigned)chReplacement < 128, -1);
+
+ /*
+ * Calc valid pairs and check that we've got an even number.
+ */
+ uint32_t cValidPairs = 0;
+ while (puszValidPairs[cValidPairs * 2])
+ {
+ AssertReturn(puszValidPairs[cValidPairs * 2 + 1], -1);
+ AssertMsg(puszValidPairs[cValidPairs * 2] <= puszValidPairs[cValidPairs * 2 + 1],
+ ("%#x vs %#x\n", puszValidPairs[cValidPairs * 2], puszValidPairs[cValidPairs * 2 + 1]));
+ cValidPairs++;
+ }
+
+ /*
+ * Do the replacing.
+ */
+ ssize_t cReplacements = 0;
+ for (;;)
+ {
+ char *pszCur = psz;
+ RTUNICP Cp;
+ int rc = RTStrGetCpEx((const char **)&psz, &Cp);
+ if (RT_SUCCESS(rc))
+ {
+ if (Cp)
+ {
+ if (!rtStrPurgeIsInSet(Cp, puszValidPairs, cValidPairs))
+ {
+ for (; pszCur != psz; ++pszCur)
+ *pszCur = chReplacement;
+ ++cReplacements;
+ }
+ }
+ else
+ break;
+ }
+ else
+ return -1;
+ }
+ return cReplacements;
+}
+RT_EXPORT_SYMBOL(RTStrPurgeComplementSet);
+
+
+RTDECL(int) RTStrToUni(const char *pszString, PRTUNICP *ppaCps)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pszString);
+ AssertPtr(ppaCps);
+ *ppaCps = NULL;
+
+ /*
+ * Validate the UTF-8 input and count its code points.
+ */
+ size_t cCps;
+ int rc = rtUtf8Length(pszString, RTSTR_MAX, &cCps, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer.
+ */
+ PRTUNICP paCps = (PRTUNICP)RTMemAlloc((cCps + 1) * sizeof(RTUNICP));
+ if (paCps)
+ {
+ /*
+ * Decode the string.
+ */
+ rc = rtUtf8Decode(pszString, RTSTR_MAX, paCps, cCps);
+ if (RT_SUCCESS(rc))
+ {
+ *ppaCps = paCps;
+ return rc;
+ }
+ RTMemFree(paCps);
+ }
+ else
+ rc = VERR_NO_CODE_POINT_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUni);
+
+
+RTDECL(int) RTStrToUniEx(const char *pszString, size_t cchString, PRTUNICP *ppaCps, size_t cCps, size_t *pcCps)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pszString);
+ AssertPtr(ppaCps);
+ AssertPtrNull(pcCps);
+
+ /*
+ * Validate the UTF-8 input and count the code points.
+ */
+ size_t cCpsResult;
+ int rc = rtUtf8Length(pszString, cchString, &cCpsResult, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcCps)
+ *pcCps = cCpsResult;
+
+ /*
+ * Check buffer size / Allocate buffer.
+ */
+ bool fShouldFree;
+ PRTUNICP paCpsResult;
+ if (cCps > 0 && *ppaCps)
+ {
+ fShouldFree = false;
+ if (cCps <= cCpsResult)
+ return VERR_BUFFER_OVERFLOW;
+ paCpsResult = *ppaCps;
+ }
+ else
+ {
+ *ppaCps = NULL;
+ fShouldFree = true;
+ cCps = RT_MAX(cCpsResult + 1, cCps);
+ paCpsResult = (PRTUNICP)RTMemAlloc(cCps * sizeof(RTUNICP));
+ }
+ if (paCpsResult)
+ {
+ /*
+ * Encode the UTF-16 string.
+ */
+ rc = rtUtf8Decode(pszString, cchString, paCpsResult, cCps - 1);
+ if (RT_SUCCESS(rc))
+ {
+ *ppaCps = paCpsResult;
+ return rc;
+ }
+ if (fShouldFree)
+ RTMemFree(paCpsResult);
+ }
+ else
+ rc = VERR_NO_CODE_POINT_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUniEx);
+
+
+/**
+ * Calculates the UTF-16 length of a string, validating the encoding while doing so.
+ *
+ * @returns IPRT status code.
+ * @param psz Pointer to the UTF-8 string.
+ * @param cch The max length of the string. (btw cch = cb)
+ * @param pcwc Where to store the length of the UTF-16 string as a number
+ * of RTUTF16 characters.
+ * @sa rtUtf8CalcUtf16Length
+ */
+static int rtUtf8CalcUtf16LengthN(const char *psz, size_t cch, size_t *pcwc)
+{
+ const unsigned char *puch = (const unsigned char *)psz;
+ size_t cwc = 0;
+ while (cch > 0)
+ {
+ const unsigned char uch = *puch;
+ if (!(uch & RT_BIT(7)))
+ {
+ /* one ASCII byte */
+ if (uch)
+ {
+ cwc++;
+ puch++;
+ cch--;
+ }
+ else
+ break;
+ }
+ else
+ {
+ /*
+ * Multibyte sequence is more complicated when we have length
+ * restrictions on the input.
+ */
+ /* figure sequence length and validate the first byte */
+ unsigned cb;
+ if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5))) == (RT_BIT(7) | RT_BIT(6)))
+ cb = 2;
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5)))
+ cb = 3;
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4)))
+ cb = 4;
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3) | RT_BIT(2))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3)))
+ cb = 5;
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3) | RT_BIT(2)))
+ cb = 6;
+ else
+ {
+ RTStrAssertMsgFailed(("Invalid UTF-8 first byte: %.*Rhxs\n", RT_MIN(cch, 10), puch));
+ return VERR_INVALID_UTF8_ENCODING;
+ }
+
+ /* check length */
+ if (cb > cch)
+ {
+ RTStrAssertMsgFailed(("Invalid UTF-8 length: cb=%d cch=%d (%.*Rhxs)\n", cb, cch, RT_MIN(cch, 10), puch));
+ return VERR_INVALID_UTF8_ENCODING;
+ }
+
+ /* validate the rest */
+ switch (cb)
+ {
+ case 6:
+ RTStrAssertMsgReturn((puch[5] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("6/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RT_FALL_THRU();
+ case 5:
+ RTStrAssertMsgReturn((puch[4] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("5/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RT_FALL_THRU();
+ case 4:
+ RTStrAssertMsgReturn((puch[3] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("4/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RT_FALL_THRU();
+ case 3:
+ RTStrAssertMsgReturn((puch[2] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("3/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RT_FALL_THRU();
+ case 2:
+ RTStrAssertMsgReturn((puch[1] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("2/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ break;
+ }
+
+ /* validate the code point. */
+ RTUNICP uc;
+ switch (cb)
+ {
+ case 6:
+ uc = (puch[5] & 0x3f)
+ | ((RTUNICP)(puch[4] & 0x3f) << 6)
+ | ((RTUNICP)(puch[3] & 0x3f) << 12)
+ | ((RTUNICP)(puch[2] & 0x3f) << 18)
+ | ((RTUNICP)(puch[1] & 0x3f) << 24)
+ | ((RTUNICP)(uch & 0x01) << 30);
+ RTStrAssertMsgReturn(uc >= 0x04000000 && uc <= 0x7fffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgFailed(("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch));
+ return VERR_CANT_RECODE_AS_UTF16;
+ case 5:
+ uc = (puch[4] & 0x3f)
+ | ((RTUNICP)(puch[3] & 0x3f) << 6)
+ | ((RTUNICP)(puch[2] & 0x3f) << 12)
+ | ((RTUNICP)(puch[1] & 0x3f) << 18)
+ | ((RTUNICP)(uch & 0x03) << 24);
+ RTStrAssertMsgReturn(uc >= 0x00200000 && uc <= 0x03ffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgFailed(("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch));
+ return VERR_CANT_RECODE_AS_UTF16;
+ case 4:
+ uc = (puch[3] & 0x3f)
+ | ((RTUNICP)(puch[2] & 0x3f) << 6)
+ | ((RTUNICP)(puch[1] & 0x3f) << 12)
+ | ((RTUNICP)(uch & 0x07) << 18);
+ RTStrAssertMsgReturn(uc >= 0x00010000 && uc <= 0x001fffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn(uc <= 0x0010ffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_CANT_RECODE_AS_UTF16);
+ cwc++;
+ break;
+ case 3:
+ uc = (puch[2] & 0x3f)
+ | ((RTUNICP)(puch[1] & 0x3f) << 6)
+ | ((RTUNICP)(uch & 0x0f) << 12);
+ RTStrAssertMsgReturn(uc >= 0x00000800 && uc <= 0x0000fffd,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch),
+ uc == 0xffff || uc == 0xfffe ? VERR_CODE_POINT_ENDIAN_INDICATOR : VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn(uc < 0xd800 || uc > 0xdfff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_CODE_POINT_SURROGATE);
+ break;
+ case 2:
+ uc = (puch[1] & 0x3f)
+ | ((RTUNICP)(uch & 0x1f) << 6);
+ RTStrAssertMsgReturn(uc >= 0x00000080 && uc <= 0x000007ff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ break;
+ }
+
+ /* advance */
+ cch -= cb;
+ puch += cb;
+ cwc++;
+ }
+ }
+
+ /* done */
+ *pcwc = cwc;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Calculates the UTF-16 length of a string, validating the encoding while doing so.
+ *
+ * @returns IPRT status code.
+ * @param psz Pointer to the UTF-8 string.
+ * @param pcwc Where to store the length of the UTF-16 string as a number
+ * of RTUTF16 characters.
+ * @sa rtUtf8CalcUtf16LengthN
+ */
+static int rtUtf8CalcUtf16Length(const char *psz, size_t *pcwc)
+{
+ const unsigned char *puch = (const unsigned char *)psz;
+ size_t cwc = 0;
+ for (;;)
+ {
+ const unsigned char uch = *puch;
+ if (!(uch & RT_BIT(7)))
+ {
+ /* one ASCII byte */
+ if (uch)
+ {
+ cwc++;
+ puch++;
+ }
+ else
+ break;
+ }
+ else
+ {
+ /*
+ * Figure sequence length, implicitly validate the first byte.
+ * Then validate the additional bytes.
+ * Finally validate the code point.
+ */
+ unsigned cb;
+ RTUNICP uc;
+ if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5))) == (RT_BIT(7) | RT_BIT(6)))
+ {
+ RTStrAssertMsgReturn((puch[1] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("2/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ uc = (puch[1] & 0x3f)
+ | ((RTUNICP)(uch & 0x1f) << 6);
+ RTStrAssertMsgReturn(uc >= 0x00000080 && uc <= 0x000007ff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ cb = 2;
+ }
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5)))
+ {
+ RTStrAssertMsgReturn((puch[1] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("2/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[2] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("3/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ uc = (puch[2] & 0x3f)
+ | ((RTUNICP)(puch[1] & 0x3f) << 6)
+ | ((RTUNICP)(uch & 0x0f) << 12);
+ RTStrAssertMsgReturn(uc >= 0x00000800 && uc <= 0x0000fffd,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch),
+ uc == 0xffff || uc == 0xfffe ? VERR_CODE_POINT_ENDIAN_INDICATOR : VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn(uc < 0xd800 || uc > 0xdfff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_CODE_POINT_SURROGATE);
+ cb = 3;
+ }
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4)))
+ {
+ RTStrAssertMsgReturn((puch[1] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("2/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[2] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("3/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[3] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("4/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ uc = (puch[3] & 0x3f)
+ | ((RTUNICP)(puch[2] & 0x3f) << 6)
+ | ((RTUNICP)(puch[1] & 0x3f) << 12)
+ | ((RTUNICP)(uch & 0x07) << 18);
+ RTStrAssertMsgReturn(uc >= 0x00010000 && uc <= 0x001fffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn(uc <= 0x0010ffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_CANT_RECODE_AS_UTF16);
+ cwc++;
+ cb = 4;
+ }
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3) | RT_BIT(2))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3)))
+ {
+ RTStrAssertMsgReturn((puch[1] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("2/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[2] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("3/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[3] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("4/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[4] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("5/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ uc = (puch[4] & 0x3f)
+ | ((RTUNICP)(puch[3] & 0x3f) << 6)
+ | ((RTUNICP)(puch[2] & 0x3f) << 12)
+ | ((RTUNICP)(puch[1] & 0x3f) << 18)
+ | ((RTUNICP)(uch & 0x03) << 24);
+ RTStrAssertMsgReturn(uc >= 0x00200000 && uc <= 0x03ffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgFailed(("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch));
+ return VERR_CANT_RECODE_AS_UTF16;
+ //cb = 5;
+ }
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3) | RT_BIT(2) | RT_BIT(1))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3) | RT_BIT(2)))
+ {
+ RTStrAssertMsgReturn((puch[1] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("2/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[2] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("3/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[3] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("4/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[4] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("5/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgReturn((puch[5] & (RT_BIT(7) | RT_BIT(6))) == RT_BIT(7), ("6/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ uc = (puch[5] & 0x3f)
+ | ((RTUNICP)(puch[4] & 0x3f) << 6)
+ | ((RTUNICP)(puch[3] & 0x3f) << 12)
+ | ((RTUNICP)(puch[2] & 0x3f) << 18)
+ | ((RTUNICP)(puch[1] & 0x3f) << 24)
+ | ((RTUNICP)(uch & 0x01) << 30);
+ RTStrAssertMsgReturn(uc >= 0x04000000 && uc <= 0x7fffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch), VERR_INVALID_UTF8_ENCODING);
+ RTStrAssertMsgFailed(("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, cch), puch));
+ return VERR_CANT_RECODE_AS_UTF16;
+ //cb = 6;
+ }
+ else
+ {
+ RTStrAssertMsgFailed(("Invalid UTF-8 first byte: %.*Rhxs\n", RT_MIN(cch, 10), puch));
+ return VERR_INVALID_UTF8_ENCODING;
+ }
+
+ /* advance */
+ puch += cb;
+ cwc++;
+ }
+ }
+
+ /* done */
+ *pcwc = cwc;
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Recodes a valid UTF-8 string as UTF-16.
+ *
+ * Since we know the input is valid, we do *not* perform encoding or length checks.
+ *
+ * @returns iprt status code.
+ * @param psz The UTF-8 string to recode. This is a valid encoding.
+ * @param cch The number of chars (the type char, so bytes if you like) to process of the UTF-8 string.
+ * The recoding will stop when cch or '\\0' is reached. Pass RTSTR_MAX to process up to '\\0'.
+ * @param pwsz Where to store the UTF-16 string.
+ * @param cwc The number of RTUTF16 items the pwsz buffer can hold, excluding the terminator ('\\0').
+ *
+ * @note rtUtf8RecodeAsUtf16Big is a duplicate with RT_H2BE_U16 applied.
+ */
+static int rtUtf8RecodeAsUtf16(const char *psz, size_t cch, PRTUTF16 pwsz, size_t cwc)
+{
+ int rc = VINF_SUCCESS;
+ const unsigned char *puch = (const unsigned char *)psz;
+ PRTUTF16 pwc = pwsz;
+ while (cch > 0)
+ {
+ /* read the next char and check for terminator. */
+ const unsigned char uch = *puch;
+ if (uch)
+ { /* we only break once, so consider this the likely branch. */ }
+ else
+ break;
+
+ /* check for output overflow */
+ if (RT_LIKELY(cwc >= 1))
+ { /* likely */ }
+ else
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cwc--;
+
+ /* decode and recode the code point */
+ if (!(uch & RT_BIT(7)))
+ {
+ *pwc++ = uch;
+ puch++;
+ cch--;
+ }
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5))) == (RT_BIT(7) | RT_BIT(6)))
+ {
+ uint16_t uc = (puch[1] & 0x3f)
+ | ((uint16_t)(uch & 0x1f) << 6);
+ *pwc++ = uc;
+ puch += 2;
+ cch -= 2;
+ }
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5)))
+ {
+ uint16_t uc = (puch[2] & 0x3f)
+ | ((uint16_t)(puch[1] & 0x3f) << 6)
+ | ((uint16_t)(uch & 0x0f) << 12);
+ *pwc++ = uc;
+ puch += 3;
+ cch -= 3;
+ }
+ else
+ {
+ /* generate surrogate pair */
+ Assert((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4)));
+ RTUNICP uc = (puch[3] & 0x3f)
+ | ((RTUNICP)(puch[2] & 0x3f) << 6)
+ | ((RTUNICP)(puch[1] & 0x3f) << 12)
+ | ((RTUNICP)(uch & 0x07) << 18);
+ if (RT_UNLIKELY(cwc < 1))
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cwc--;
+
+ uc -= 0x10000;
+ *pwc++ = 0xd800 | (uc >> 10);
+ *pwc++ = 0xdc00 | (uc & 0x3ff);
+ puch += 4;
+ cch -= 4;
+ }
+ }
+
+ /* done */
+ *pwc = '\0';
+ return rc;
+}
+
+
+/**
+ * Recodes a valid UTF-8 string as UTF-16BE.
+ *
+ * Since we know the input is valid, we do *not* perform encoding or length checks.
+ *
+ * @returns iprt status code.
+ * @param psz The UTF-8 string to recode. This is a valid encoding.
+ * @param cch The number of chars (the type char, so bytes if you like) to process of the UTF-8 string.
+ * The recoding will stop when cch or '\\0' is reached. Pass RTSTR_MAX to process up to '\\0'.
+ * @param pwsz Where to store the UTF-16BE string.
+ * @param cwc The number of RTUTF16 items the pwsz buffer can hold, excluding the terminator ('\\0').
+ *
+ * @note This is a copy of rtUtf8RecodeAsUtf16 with RT_H2BE_U16 applied.
+ */
+static int rtUtf8RecodeAsUtf16Big(const char *psz, size_t cch, PRTUTF16 pwsz, size_t cwc)
+{
+ int rc = VINF_SUCCESS;
+ const unsigned char *puch = (const unsigned char *)psz;
+ PRTUTF16 pwc = pwsz;
+ while (cch > 0)
+ {
+ /* read the next char and check for terminator. */
+ const unsigned char uch = *puch;
+ if (uch)
+ { /* we only break once, so consider this the likely branch. */ }
+ else
+ break;
+
+ /* check for output overflow */
+ if (RT_LIKELY(cwc >= 1))
+ { /* likely */ }
+ else
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cwc--;
+
+ /* decode and recode the code point */
+ if (!(uch & RT_BIT(7)))
+ {
+ *pwc++ = RT_H2BE_U16((RTUTF16)uch);
+ puch++;
+ cch--;
+ }
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5))) == (RT_BIT(7) | RT_BIT(6)))
+ {
+ uint16_t uc = (puch[1] & 0x3f)
+ | ((uint16_t)(uch & 0x1f) << 6);
+ *pwc++ = RT_H2BE_U16(uc);
+ puch += 2;
+ cch -= 2;
+ }
+ else if ((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5)))
+ {
+ uint16_t uc = (puch[2] & 0x3f)
+ | ((uint16_t)(puch[1] & 0x3f) << 6)
+ | ((uint16_t)(uch & 0x0f) << 12);
+ *pwc++ = RT_H2BE_U16(uc);
+ puch += 3;
+ cch -= 3;
+ }
+ else
+ {
+ /* generate surrogate pair */
+ Assert((uch & (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4) | RT_BIT(3))) == (RT_BIT(7) | RT_BIT(6) | RT_BIT(5) | RT_BIT(4)));
+ RTUNICP uc = (puch[3] & 0x3f)
+ | ((RTUNICP)(puch[2] & 0x3f) << 6)
+ | ((RTUNICP)(puch[1] & 0x3f) << 12)
+ | ((RTUNICP)(uch & 0x07) << 18);
+ if (RT_UNLIKELY(cwc < 1))
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cwc--;
+
+ uc -= 0x10000;
+ *pwc++ = RT_H2BE_U16(0xd800 | (uc >> 10));
+ *pwc++ = RT_H2BE_U16(0xdc00 | (uc & 0x3ff));
+ puch += 4;
+ cch -= 4;
+ }
+ }
+
+ /* done */
+ *pwc = '\0';
+ return rc;
+}
+
+
+RTDECL(int) RTStrToUtf16Tag(const char *pszString, PRTUTF16 *ppwszString, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(ppwszString);
+ AssertPtr(pszString);
+ *ppwszString = NULL;
+
+ /*
+ * Validate the UTF-8 input and calculate the length of the UTF-16 string.
+ */
+ size_t cwc;
+ int rc = rtUtf8CalcUtf16Length(pszString, &cwc);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer.
+ */
+ PRTUTF16 pwsz = (PRTUTF16)RTMemAllocTag((cwc + 1) * sizeof(RTUTF16), pszTag);
+ if (pwsz)
+ {
+ /*
+ * Encode the UTF-16 string.
+ */
+ rc = rtUtf8RecodeAsUtf16(pszString, RTSTR_MAX, pwsz, cwc);
+ if (RT_SUCCESS(rc))
+ {
+ *ppwszString = pwsz;
+ return rc;
+ }
+ RTMemFree(pwsz);
+ }
+ else
+ rc = VERR_NO_UTF16_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUtf16Tag);
+
+
+RTDECL(int) RTStrToUtf16BigTag(const char *pszString, PRTUTF16 *ppwszString, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(ppwszString);
+ AssertPtr(pszString);
+ *ppwszString = NULL;
+
+ /*
+ * Validate the UTF-8 input and calculate the length of the UTF-16 string.
+ */
+ size_t cwc;
+ int rc = rtUtf8CalcUtf16Length(pszString, &cwc);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer.
+ */
+ PRTUTF16 pwsz = (PRTUTF16)RTMemAllocTag((cwc + 1) * sizeof(RTUTF16), pszTag);
+ if (pwsz)
+ {
+ /*
+ * Encode the UTF-16 string.
+ */
+ rc = rtUtf8RecodeAsUtf16Big(pszString, RTSTR_MAX, pwsz, cwc);
+ if (RT_SUCCESS(rc))
+ {
+ *ppwszString = pwsz;
+ return rc;
+ }
+ RTMemFree(pwsz);
+ }
+ else
+ rc = VERR_NO_UTF16_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUtf16BigTag);
+
+
+RTDECL(int) RTStrToUtf16ExTag(const char *pszString, size_t cchString,
+ PRTUTF16 *ppwsz, size_t cwc, size_t *pcwc, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pszString);
+ AssertPtr(ppwsz);
+ AssertPtrNull(pcwc);
+
+ /*
+ * Validate the UTF-8 input and calculate the length of the UTF-16 string.
+ */
+ size_t cwcResult;
+ int rc;
+ if (cchString != RTSTR_MAX)
+ rc = rtUtf8CalcUtf16LengthN(pszString, cchString, &cwcResult);
+ else
+ rc = rtUtf8CalcUtf16Length(pszString, &cwcResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcwc)
+ *pcwc = cwcResult;
+
+ /*
+ * Check buffer size / Allocate buffer.
+ */
+ bool fShouldFree;
+ PRTUTF16 pwszResult;
+ if (cwc > 0 && *ppwsz)
+ {
+ fShouldFree = false;
+ if (cwc <= cwcResult)
+ return VERR_BUFFER_OVERFLOW;
+ pwszResult = *ppwsz;
+ }
+ else
+ {
+ *ppwsz = NULL;
+ fShouldFree = true;
+ cwc = RT_MAX(cwcResult + 1, cwc);
+ pwszResult = (PRTUTF16)RTMemAllocTag(cwc * sizeof(RTUTF16), pszTag);
+ }
+ if (pwszResult)
+ {
+ /*
+ * Encode the UTF-16 string.
+ */
+ rc = rtUtf8RecodeAsUtf16(pszString, cchString, pwszResult, cwc - 1);
+ if (RT_SUCCESS(rc))
+ {
+ *ppwsz = pwszResult;
+ return rc;
+ }
+ if (fShouldFree)
+ RTMemFree(pwszResult);
+ }
+ else
+ rc = VERR_NO_UTF16_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUtf16ExTag);
+
+
+RTDECL(int) RTStrToUtf16BigExTag(const char *pszString, size_t cchString,
+ PRTUTF16 *ppwsz, size_t cwc, size_t *pcwc, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pszString);
+ AssertPtr(ppwsz);
+ AssertPtrNull(pcwc);
+
+ /*
+ * Validate the UTF-8 input and calculate the length of the UTF-16 string.
+ */
+ size_t cwcResult;
+ int rc;
+ if (cchString != RTSTR_MAX)
+ rc = rtUtf8CalcUtf16LengthN(pszString, cchString, &cwcResult);
+ else
+ rc = rtUtf8CalcUtf16Length(pszString, &cwcResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcwc)
+ *pcwc = cwcResult;
+
+ /*
+ * Check buffer size / Allocate buffer.
+ */
+ bool fShouldFree;
+ PRTUTF16 pwszResult;
+ if (cwc > 0 && *ppwsz)
+ {
+ fShouldFree = false;
+ if (cwc <= cwcResult)
+ return VERR_BUFFER_OVERFLOW;
+ pwszResult = *ppwsz;
+ }
+ else
+ {
+ *ppwsz = NULL;
+ fShouldFree = true;
+ cwc = RT_MAX(cwcResult + 1, cwc);
+ pwszResult = (PRTUTF16)RTMemAllocTag(cwc * sizeof(RTUTF16), pszTag);
+ }
+ if (pwszResult)
+ {
+ /*
+ * Encode the UTF-16BE string.
+ */
+ rc = rtUtf8RecodeAsUtf16Big(pszString, cchString, pwszResult, cwc - 1);
+ if (RT_SUCCESS(rc))
+ {
+ *ppwsz = pwszResult;
+ return rc;
+ }
+ if (fShouldFree)
+ RTMemFree(pwszResult);
+ }
+ else
+ rc = VERR_NO_UTF16_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToUtf16BigExTag);
+
+
+RTDECL(size_t) RTStrCalcUtf16Len(const char *psz)
+{
+ size_t cwc;
+ int rc = rtUtf8CalcUtf16Length(psz, &cwc);
+ return RT_SUCCESS(rc) ? cwc : 0;
+}
+RT_EXPORT_SYMBOL(RTStrCalcUtf16Len);
+
+
+RTDECL(int) RTStrCalcUtf16LenEx(const char *psz, size_t cch, size_t *pcwc)
+{
+ size_t cwc;
+ int rc;
+ if (cch != RTSTR_MAX)
+ rc = rtUtf8CalcUtf16LengthN(psz, cch, &cwc);
+ else
+ rc = rtUtf8CalcUtf16Length(psz, &cwc);
+ if (pcwc)
+ *pcwc = RT_SUCCESS(rc) ? cwc : ~(size_t)0;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrCalcUtf16LenEx);
+
+
+/**
+ * Calculates the length of the UTF-8 encoding of a Latin-1 string.
+ *
+ * @returns iprt status code.
+ * @param psz The Latin-1 string.
+ * @param cchIn The max length of the Latin-1 string to consider.
+ * @param pcch Where to store the length (excluding '\\0') of the UTF-8 string. (cch == cb, btw)
+ */
+static int rtLatin1CalcUtf8Length(const char *psz, size_t cchIn, size_t *pcch)
+{
+ size_t cch = 0;
+ for (;;)
+ {
+ RTUNICP Cp;
+ int rc = RTLatin1GetCpNEx(&psz, &cchIn, &Cp);
+ if (Cp == 0 || rc == VERR_END_OF_STRING)
+ break;
+ if (RT_FAILURE(rc))
+ return rc;
+ cch += RTStrCpSize(Cp); /* cannot fail */
+ }
+
+ /* done */
+ *pcch = cch;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Recodes a Latin-1 string as UTF-8.
+ *
+ * @returns iprt status code.
+ * @param pszIn The Latin-1 string.
+ * @param cchIn The number of characters to process from psz. The recoding
+ * will stop when cch or '\\0' is reached.
+ * @param psz Where to store the UTF-8 string.
+ * @param cch The size of the UTF-8 buffer, excluding the terminator.
+ */
+static int rtLatin1RecodeAsUtf8(const char *pszIn, size_t cchIn, char *psz, size_t cch)
+{
+ int rc;
+ for (;;)
+ {
+ RTUNICP Cp;
+ size_t cchCp;
+ rc = RTLatin1GetCpNEx(&pszIn, &cchIn, &Cp);
+ if (Cp == 0 || RT_FAILURE(rc))
+ break;
+ cchCp = RTStrCpSize(Cp);
+ if (RT_UNLIKELY(cch < cchCp))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 1\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch -= cchCp;
+ psz = RTStrPutCp(psz, Cp);
+ }
+
+ /* done */
+ if (rc == VERR_END_OF_STRING)
+ rc = VINF_SUCCESS;
+ *psz = '\0';
+ return rc;
+}
+
+
+
+RTDECL(int) RTLatin1ToUtf8Tag(const char *pszString, char **ppszString, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(ppszString);
+ AssertPtr(pszString);
+ *ppszString = NULL;
+
+ /*
+ * Calculate the length of the UTF-8 encoding of the Latin-1 string.
+ */
+ size_t cch;
+ int rc = rtLatin1CalcUtf8Length(pszString, RTSTR_MAX, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer and recode it.
+ */
+ char *pszResult = (char *)RTMemAllocTag(cch + 1, pszTag);
+ if (pszResult)
+ {
+ rc = rtLatin1RecodeAsUtf8(pszString, RTSTR_MAX, pszResult, cch);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszString = pszResult;
+ return rc;
+ }
+
+ RTMemFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLatin1ToUtf8Tag);
+
+
+RTDECL(int) RTLatin1ToUtf8ExTag(const char *pszString, size_t cchString, char **ppsz, size_t cch, size_t *pcch, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pszString);
+ AssertPtr(ppsz);
+ AssertPtrNull(pcch);
+
+ /*
+ * Calculate the length of the UTF-8 encoding of the Latin-1 string.
+ */
+ size_t cchResult;
+ int rc = rtLatin1CalcUtf8Length(pszString, cchString, &cchResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcch)
+ *pcch = cchResult;
+
+ /*
+ * Check buffer size / Allocate buffer and recode it.
+ */
+ bool fShouldFree;
+ char *pszResult;
+ if (cch > 0 && *ppsz)
+ {
+ fShouldFree = false;
+ if (RT_UNLIKELY(cch <= cchResult))
+ return VERR_BUFFER_OVERFLOW;
+ pszResult = *ppsz;
+ }
+ else
+ {
+ *ppsz = NULL;
+ fShouldFree = true;
+ cch = RT_MAX(cch, cchResult + 1);
+ pszResult = (char *)RTStrAllocTag(cch, pszTag);
+ }
+ if (pszResult)
+ {
+ rc = rtLatin1RecodeAsUtf8(pszString, cchString, pszResult, cch - 1);
+ if (RT_SUCCESS(rc))
+ {
+ *ppsz = pszResult;
+ return rc;
+ }
+
+ if (fShouldFree)
+ RTStrFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLatin1ToUtf8ExTag);
+
+
+RTDECL(size_t) RTLatin1CalcUtf8Len(const char *psz)
+{
+ size_t cch;
+ int rc = rtLatin1CalcUtf8Length(psz, RTSTR_MAX, &cch);
+ return RT_SUCCESS(rc) ? cch : 0;
+}
+RT_EXPORT_SYMBOL(RTLatin1CalcUtf8Len);
+
+
+RTDECL(int) RTLatin1CalcUtf8LenEx(const char *psz, size_t cchIn, size_t *pcch)
+{
+ size_t cch;
+ int rc = rtLatin1CalcUtf8Length(psz, cchIn, &cch);
+ if (pcch)
+ *pcch = RT_SUCCESS(rc) ? cch : ~(size_t)0;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTLatin1CalcUtf8LenEx);
+
+
+/**
+ * Calculates the Latin-1 length of a string, validating the encoding while
+ * doing so.
+ *
+ * @returns IPRT status code.
+ * @param psz Pointer to the UTF-8 string.
+ * @param cchIn The max length of the string. (btw cch = cb)
+ * Use RTSTR_MAX if all of the string is to be examined.
+ * @param pcch Where to store the length of the Latin-1 string in bytes.
+ */
+static int rtUtf8CalcLatin1Length(const char *psz, size_t cchIn, size_t *pcch)
+{
+ size_t cch = 0;
+ for (;;)
+ {
+ RTUNICP Cp;
+ size_t cchCp;
+ int rc = RTStrGetCpNEx(&psz, &cchIn, &Cp);
+ if (Cp == 0 || rc == VERR_END_OF_STRING)
+ break;
+ if (RT_FAILURE(rc))
+ return rc;
+ cchCp = RTLatin1CpSize(Cp);
+ if (cchCp == 0)
+ return VERR_NO_TRANSLATION;
+ cch += cchCp;
+ }
+
+ /* done */
+ *pcch = cch;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Recodes a valid UTF-8 string as Latin-1.
+ *
+ * Since we know the input is valid, we do *not* perform encoding or length checks.
+ *
+ * @returns iprt status code.
+ * @param pszIn The UTF-8 string to recode. This is a valid encoding.
+ * @param cchIn The number of chars (the type char, so bytes if you like) to process of the UTF-8 string.
+ * The recoding will stop when cch or '\\0' is reached. Pass RTSTR_MAX to process up to '\\0'.
+ * @param psz Where to store the Latin-1 string.
+ * @param cch The number of characters the pszOut buffer can hold, excluding the terminator ('\\0').
+ */
+static int rtUtf8RecodeAsLatin1(const char *pszIn, size_t cchIn, char *psz, size_t cch)
+{
+ int rc;
+ for (;;)
+ {
+ RTUNICP Cp;
+ size_t cchCp;
+ rc = RTStrGetCpNEx(&pszIn, &cchIn, &Cp);
+ if (Cp == 0 || RT_FAILURE(rc))
+ break;
+ cchCp = RTLatin1CpSize(Cp);
+ if (RT_UNLIKELY(cch < cchCp))
+ {
+ RTStrAssertMsgFailed(("Buffer overflow! 1\n"));
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+ cch -= cchCp;
+ psz = RTLatin1PutCp(psz, Cp);
+ }
+
+ /* done */
+ if (rc == VERR_END_OF_STRING)
+ rc = VINF_SUCCESS;
+ *psz = '\0';
+ return rc;
+}
+
+
+
+RTDECL(int) RTStrToLatin1Tag(const char *pszString, char **ppszString, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(ppszString);
+ AssertPtr(pszString);
+ *ppszString = NULL;
+
+ /*
+ * Validate the UTF-8 input and calculate the length of the Latin-1 string.
+ */
+ size_t cch;
+ int rc = rtUtf8CalcLatin1Length(pszString, RTSTR_MAX, &cch);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate buffer.
+ */
+ char *psz = (char *)RTMemAllocTag(cch + 1, pszTag);
+ if (psz)
+ {
+ /*
+ * Encode the UTF-16 string.
+ */
+ rc = rtUtf8RecodeAsLatin1(pszString, RTSTR_MAX, psz, cch);
+ if (RT_SUCCESS(rc))
+ {
+ *ppszString = psz;
+ return rc;
+ }
+ RTMemFree(psz);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToLatin1Tag);
+
+
+RTDECL(int) RTStrToLatin1ExTag(const char *pszString, size_t cchString,
+ char **ppsz, size_t cch, size_t *pcch, const char *pszTag)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtr(pszString);
+ AssertPtr(ppsz);
+ AssertPtrNull(pcch);
+
+ /*
+ * Validate the UTF-8 input and calculate the length of the UTF-16 string.
+ */
+ size_t cchResult;
+ int rc = rtUtf8CalcLatin1Length(pszString, cchString, &cchResult);
+ if (RT_SUCCESS(rc))
+ {
+ if (pcch)
+ *pcch = cchResult;
+
+ /*
+ * Check buffer size / Allocate buffer.
+ */
+ bool fShouldFree;
+ char *pszResult;
+ if (cch > 0 && *ppsz)
+ {
+ fShouldFree = false;
+ if (cch <= cchResult)
+ return VERR_BUFFER_OVERFLOW;
+ pszResult = *ppsz;
+ }
+ else
+ {
+ *ppsz = NULL;
+ fShouldFree = true;
+ cch = RT_MAX(cchResult + 1, cch);
+ pszResult = (char *)RTMemAllocTag(cch, pszTag);
+ }
+ if (pszResult)
+ {
+ /*
+ * Encode the Latin-1 string.
+ */
+ rc = rtUtf8RecodeAsLatin1(pszString, cchString, pszResult, cch - 1);
+ if (RT_SUCCESS(rc))
+ {
+ *ppsz = pszResult;
+ return rc;
+ }
+ if (fShouldFree)
+ RTMemFree(pszResult);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrToLatin1ExTag);
+
+
+RTDECL(size_t) RTStrCalcLatin1Len(const char *psz)
+{
+ size_t cch;
+ int rc = rtUtf8CalcLatin1Length(psz, RTSTR_MAX, &cch);
+ return RT_SUCCESS(rc) ? cch : 0;
+}
+RT_EXPORT_SYMBOL(RTStrCalcLatin1Len);
+
+
+RTDECL(int) RTStrCalcLatin1LenEx(const char *psz, size_t cchIn, size_t *pcch)
+{
+ size_t cch;
+ int rc = rtUtf8CalcLatin1Length(psz, cchIn, &cch);
+ if (pcch)
+ *pcch = RT_SUCCESS(rc) ? cch : ~(size_t)0;
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTStrCalcLatin1LenEx);
+
+
+/**
+ * Handle invalid encodings passed to RTStrGetCp() and RTStrGetCpEx().
+ * @returns rc
+ * @param ppsz The pointer to the string position point.
+ * @param pCp Where to store RTUNICP_INVALID.
+ * @param rc The iprt error code.
+ */
+static int rtStrGetCpExFailure(const char **ppsz, PRTUNICP pCp, int rc)
+{
+ /*
+ * Try find a valid encoding.
+ */
+ (*ppsz)++; /** @todo code this! */
+ *pCp = RTUNICP_INVALID;
+ return rc;
+}
+
+
+RTDECL(RTUNICP) RTStrGetCpInternal(const char *psz)
+{
+ RTUNICP Cp;
+ RTStrGetCpExInternal(&psz, &Cp);
+ return Cp;
+}
+RT_EXPORT_SYMBOL(RTStrGetCpInternal);
+
+
+RTDECL(int) RTStrGetCpExInternal(const char **ppsz, PRTUNICP pCp)
+{
+ const unsigned char *puch = (const unsigned char *)*ppsz;
+ const unsigned char uch = *puch;
+ RTUNICP uc;
+
+ /* ASCII ? */
+ if (!(uch & RT_BIT(7)))
+ {
+ uc = uch;
+ puch++;
+ }
+ else if (uch & RT_BIT(6))
+ {
+ /* figure the length and validate the first octet. */
+/** @todo RT_USE_RTC_3629 */
+ unsigned cb;
+ if (!(uch & RT_BIT(5)))
+ cb = 2;
+ else if (!(uch & RT_BIT(4)))
+ cb = 3;
+ else if (!(uch & RT_BIT(3)))
+ cb = 4;
+ else if (!(uch & RT_BIT(2)))
+ cb = 5;
+ else if (!(uch & RT_BIT(1)))
+ cb = 6;
+ else
+ {
+ RTStrAssertMsgFailed(("Invalid UTF-8 first byte: %.*Rhxs\n", RT_MIN(strlen((char *)puch), 10), puch));
+ return rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING);
+ }
+
+ /* validate the rest */
+ switch (cb)
+ {
+ case 6:
+ RTStrAssertMsgReturn((puch[5] & 0xc0) == 0x80, ("6/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING));
+ RT_FALL_THRU();
+ case 5:
+ RTStrAssertMsgReturn((puch[4] & 0xc0) == 0x80, ("5/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING));
+ RT_FALL_THRU();
+ case 4:
+ RTStrAssertMsgReturn((puch[3] & 0xc0) == 0x80, ("4/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING));
+ RT_FALL_THRU();
+ case 3:
+ RTStrAssertMsgReturn((puch[2] & 0xc0) == 0x80, ("3/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING));
+ RT_FALL_THRU();
+ case 2:
+ RTStrAssertMsgReturn((puch[1] & 0xc0) == 0x80, ("2/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ }
+
+ /* get and validate the code point. */
+ switch (cb)
+ {
+ case 6:
+ uc = (puch[5] & 0x3f)
+ | ((RTUNICP)(puch[4] & 0x3f) << 6)
+ | ((RTUNICP)(puch[3] & 0x3f) << 12)
+ | ((RTUNICP)(puch[2] & 0x3f) << 18)
+ | ((RTUNICP)(puch[1] & 0x3f) << 24)
+ | ((RTUNICP)(uch & 0x01) << 30);
+ RTStrAssertMsgReturn(uc >= 0x04000000 && uc <= 0x7fffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ case 5:
+ uc = (puch[4] & 0x3f)
+ | ((RTUNICP)(puch[3] & 0x3f) << 6)
+ | ((RTUNICP)(puch[2] & 0x3f) << 12)
+ | ((RTUNICP)(puch[1] & 0x3f) << 18)
+ | ((RTUNICP)(uch & 0x03) << 24);
+ RTStrAssertMsgReturn(uc >= 0x00200000 && uc <= 0x03ffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ case 4:
+ uc = (puch[3] & 0x3f)
+ | ((RTUNICP)(puch[2] & 0x3f) << 6)
+ | ((RTUNICP)(puch[1] & 0x3f) << 12)
+ | ((RTUNICP)(uch & 0x07) << 18);
+ RTStrAssertMsgReturn(uc >= 0x00010000 && uc <= 0x001fffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ case 3:
+ uc = (puch[2] & 0x3f)
+ | ((RTUNICP)(puch[1] & 0x3f) << 6)
+ | ((RTUNICP)(uch & 0x0f) << 12);
+ RTStrAssertMsgReturn(uc >= 0x00000800 && uc <= 0x0000fffd,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, uc == 0xffff || uc == 0xfffe ? VERR_CODE_POINT_ENDIAN_INDICATOR : VERR_INVALID_UTF8_ENCODING));
+ RTStrAssertMsgReturn(uc < 0xd800 || uc > 0xdfff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_CODE_POINT_SURROGATE));
+ break;
+ case 2:
+ uc = (puch[1] & 0x3f)
+ | ((RTUNICP)(uch & 0x1f) << 6);
+ RTStrAssertMsgReturn(uc >= 0x00000080 && uc <= 0x000007ff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ default: /* impossible, but GCC is bitching. */
+ uc = RTUNICP_INVALID;
+ break;
+ }
+ puch += cb;
+ }
+ else
+ {
+ /* 6th bit is always set. */
+ RTStrAssertMsgFailed(("Invalid UTF-8 first byte: %.*Rhxs\n", RT_MIN(strlen((char *)puch), 10), puch));
+ return rtStrGetCpExFailure(ppsz, pCp, VERR_INVALID_UTF8_ENCODING);
+ }
+ *pCp = uc;
+ *ppsz = (const char *)puch;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTStrGetCpExInternal);
+
+
+/**
+ * Handle invalid encodings passed to RTStrGetCpNEx().
+ * @returns rc
+ * @param ppsz The pointer to the string position point.
+ * @param pcch Pointer to the string length.
+ * @param pCp Where to store RTUNICP_INVALID.
+ * @param rc The iprt error code.
+ */
+static int rtStrGetCpNExFailure(const char **ppsz, size_t *pcch, PRTUNICP pCp, int rc)
+{
+ /*
+ * Try find a valid encoding.
+ */
+ (*ppsz)++; /** @todo code this! */
+ (*pcch)--;
+ *pCp = RTUNICP_INVALID;
+ return rc;
+}
+
+
+RTDECL(int) RTStrGetCpNExInternal(const char **ppsz, size_t *pcch, PRTUNICP pCp)
+{
+ const unsigned char *puch = (const unsigned char *)*ppsz;
+ const unsigned char uch = *puch;
+ size_t cch = *pcch;
+ RTUNICP uc;
+
+ if (cch == 0)
+ {
+ *pCp = RTUNICP_INVALID;
+ return VERR_END_OF_STRING;
+ }
+
+ /* ASCII ? */
+ if (!(uch & RT_BIT(7)))
+ {
+ uc = uch;
+ puch++;
+ cch--;
+ }
+ else if (uch & RT_BIT(6))
+ {
+ /* figure the length and validate the first octet. */
+/** @todo RT_USE_RTC_3629 */
+ unsigned cb;
+ if (!(uch & RT_BIT(5)))
+ cb = 2;
+ else if (!(uch & RT_BIT(4)))
+ cb = 3;
+ else if (!(uch & RT_BIT(3)))
+ cb = 4;
+ else if (!(uch & RT_BIT(2)))
+ cb = 5;
+ else if (!(uch & RT_BIT(1)))
+ cb = 6;
+ else
+ {
+ RTStrAssertMsgFailed(("Invalid UTF-8 first byte: %.*Rhxs\n", RT_MIN(strlen((char *)puch), 10), puch));
+ return rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING);
+ }
+
+ if (cb > cch)
+ return rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING);
+
+ /* validate the rest */
+ switch (cb)
+ {
+ case 6:
+ RTStrAssertMsgReturn((puch[5] & 0xc0) == 0x80, ("6/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING));
+ RT_FALL_THRU();
+ case 5:
+ RTStrAssertMsgReturn((puch[4] & 0xc0) == 0x80, ("5/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING));
+ RT_FALL_THRU();
+ case 4:
+ RTStrAssertMsgReturn((puch[3] & 0xc0) == 0x80, ("4/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING));
+ RT_FALL_THRU();
+ case 3:
+ RTStrAssertMsgReturn((puch[2] & 0xc0) == 0x80, ("3/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING));
+ RT_FALL_THRU();
+ case 2:
+ RTStrAssertMsgReturn((puch[1] & 0xc0) == 0x80, ("2/%u: %.*Rhxs\n", cb, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ }
+
+ /* get and validate the code point. */
+ switch (cb)
+ {
+ case 6:
+ uc = (puch[5] & 0x3f)
+ | ((RTUNICP)(puch[4] & 0x3f) << 6)
+ | ((RTUNICP)(puch[3] & 0x3f) << 12)
+ | ((RTUNICP)(puch[2] & 0x3f) << 18)
+ | ((RTUNICP)(puch[1] & 0x3f) << 24)
+ | ((RTUNICP)(uch & 0x01) << 30);
+ RTStrAssertMsgReturn(uc >= 0x04000000 && uc <= 0x7fffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ case 5:
+ uc = (puch[4] & 0x3f)
+ | ((RTUNICP)(puch[3] & 0x3f) << 6)
+ | ((RTUNICP)(puch[2] & 0x3f) << 12)
+ | ((RTUNICP)(puch[1] & 0x3f) << 18)
+ | ((RTUNICP)(uch & 0x03) << 24);
+ RTStrAssertMsgReturn(uc >= 0x00200000 && uc <= 0x03ffffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ case 4:
+ uc = (puch[3] & 0x3f)
+ | ((RTUNICP)(puch[2] & 0x3f) << 6)
+ | ((RTUNICP)(puch[1] & 0x3f) << 12)
+ | ((RTUNICP)(uch & 0x07) << 18);
+ RTStrAssertMsgReturn(uc >= 0x00010000 && uc <= 0x001fffff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ case 3:
+ uc = (puch[2] & 0x3f)
+ | ((RTUNICP)(puch[1] & 0x3f) << 6)
+ | ((RTUNICP)(uch & 0x0f) << 12);
+ RTStrAssertMsgReturn(uc >= 0x00000800 && uc <= 0x0000fffd,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, uc == 0xffff || uc == 0xfffe ? VERR_CODE_POINT_ENDIAN_INDICATOR : VERR_INVALID_UTF8_ENCODING));
+ RTStrAssertMsgReturn(uc < 0xd800 || uc > 0xdfff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_CODE_POINT_SURROGATE));
+ break;
+ case 2:
+ uc = (puch[1] & 0x3f)
+ | ((RTUNICP)(uch & 0x1f) << 6);
+ RTStrAssertMsgReturn(uc >= 0x00000080 && uc <= 0x000007ff,
+ ("%u: cp=%#010RX32: %.*Rhxs\n", cb, uc, RT_MIN(cb + 10, strlen((char *)puch)), puch),
+ rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING));
+ break;
+ default: /* impossible, but GCC is bitching. */
+ uc = RTUNICP_INVALID;
+ break;
+ }
+ puch += cb;
+ cch -= cb;
+ }
+ else
+ {
+ /* 6th bit is always set. */
+ RTStrAssertMsgFailed(("Invalid UTF-8 first byte: %.*Rhxs\n", RT_MIN(strlen((char *)puch), 10), puch));
+ return rtStrGetCpNExFailure(ppsz, pcch, pCp, VERR_INVALID_UTF8_ENCODING);
+ }
+ *pCp = uc;
+ *ppsz = (const char *)puch;
+ (*pcch) = cch;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTStrGetCpNExInternal);
+
+
+RTDECL(char *) RTStrPutCpInternal(char *psz, RTUNICP uc)
+{
+ unsigned char *puch = (unsigned char *)psz;
+ if (uc < 0x80)
+ *puch++ = (unsigned char )uc;
+ else if (uc < 0x00000800)
+ {
+ *puch++ = 0xc0 | (uc >> 6);
+ *puch++ = 0x80 | (uc & 0x3f);
+ }
+ else if (uc < 0x00010000)
+ {
+/** @todo RT_USE_RTC_3629 */
+ if ( uc < 0x0000d8000
+ || ( uc > 0x0000dfff
+ && uc < 0x0000fffe))
+ {
+ *puch++ = 0xe0 | (uc >> 12);
+ *puch++ = 0x80 | ((uc >> 6) & 0x3f);
+ *puch++ = 0x80 | (uc & 0x3f);
+ }
+ else
+ {
+ AssertMsgFailed(("Invalid code point U+%05x!\n", uc));
+ *puch++ = 0x7f;
+ }
+ }
+/** @todo RT_USE_RTC_3629 */
+ else if (uc < 0x00200000)
+ {
+ *puch++ = 0xf0 | (uc >> 18);
+ *puch++ = 0x80 | ((uc >> 12) & 0x3f);
+ *puch++ = 0x80 | ((uc >> 6) & 0x3f);
+ *puch++ = 0x80 | (uc & 0x3f);
+ }
+ else if (uc < 0x04000000)
+ {
+ *puch++ = 0xf8 | (uc >> 24);
+ *puch++ = 0x80 | ((uc >> 18) & 0x3f);
+ *puch++ = 0x80 | ((uc >> 12) & 0x3f);
+ *puch++ = 0x80 | ((uc >> 6) & 0x3f);
+ *puch++ = 0x80 | (uc & 0x3f);
+ }
+ else if (uc <= 0x7fffffff)
+ {
+ *puch++ = 0xfc | (uc >> 30);
+ *puch++ = 0x80 | ((uc >> 24) & 0x3f);
+ *puch++ = 0x80 | ((uc >> 18) & 0x3f);
+ *puch++ = 0x80 | ((uc >> 12) & 0x3f);
+ *puch++ = 0x80 | ((uc >> 6) & 0x3f);
+ *puch++ = 0x80 | (uc & 0x3f);
+ }
+ else
+ {
+ AssertMsgFailed(("Invalid code point U+%08x!\n", uc));
+ *puch++ = 0x7f;
+ }
+
+ return (char *)puch;
+}
+RT_EXPORT_SYMBOL(RTStrPutCpInternal);
+
+
+RTDECL(char *) RTStrPrevCp(const char *pszStart, const char *psz)
+{
+ if (pszStart < psz)
+ {
+ /* simple char? */
+ const unsigned char *puch = (const unsigned char *)psz;
+ unsigned uch = *--puch;
+ if (!(uch & RT_BIT(7)))
+ return (char *)puch;
+ RTStrAssertMsgReturn(!(uch & RT_BIT(6)), ("uch=%#x\n", uch), (char *)pszStart);
+
+ /* two or more. */
+ uint32_t uMask = 0xffffffc0;
+ while ( (const unsigned char *)pszStart < puch
+ && !(uMask & 1))
+ {
+ uch = *--puch;
+ if ((uch & 0xc0) != 0x80)
+ {
+ RTStrAssertMsgReturn((uch & (uMask >> 1)) == (uMask & 0xff),
+ ("Invalid UTF-8 encoding: %.*Rhxs puch=%p psz=%p\n", psz - (char *)puch, puch, psz),
+ (char *)pszStart);
+ return (char *)puch;
+ }
+ uMask >>= 1;
+ }
+ RTStrAssertMsgFailed(("Invalid UTF-8 encoding: %.*Rhxs puch=%p psz=%p\n", psz - (char *)puch, puch, psz));
+ }
+ return (char *)pszStart;
+}
+RT_EXPORT_SYMBOL(RTStrPrevCp);
+
diff --git a/src/VBox/Runtime/common/string/watcom/bzero.asm b/src/VBox/Runtime/common/string/watcom/bzero.asm
new file mode 100644
index 00000000..b7b0ebe4
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/bzero.asm
@@ -0,0 +1,42 @@
+; $Id: bzero.asm $
+;; @file
+; IPRT - No-CRT bzero - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/bzero.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/memchr.asm b/src/VBox/Runtime/common/string/watcom/memchr.asm
new file mode 100644
index 00000000..b04df09e
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/memchr.asm
@@ -0,0 +1,42 @@
+; $Id: memchr.asm $
+;; @file
+; IPRT - No-CRT memchr - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/memchr.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/memcmp.asm b/src/VBox/Runtime/common/string/watcom/memcmp.asm
new file mode 100644
index 00000000..c29702a5
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/memcmp.asm
@@ -0,0 +1,42 @@
+; $Id: memcmp.asm $
+;; @file
+; IPRT - No-CRT memcmp - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/memcmp.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/memcpy.asm b/src/VBox/Runtime/common/string/watcom/memcpy.asm
new file mode 100644
index 00000000..64617098
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/memcpy.asm
@@ -0,0 +1,42 @@
+; $Id: memcpy.asm $
+;; @file
+; IPRT - No-CRT memcpy - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/memcpy.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/memmove.asm b/src/VBox/Runtime/common/string/watcom/memmove.asm
new file mode 100644
index 00000000..0d741f03
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/memmove.asm
@@ -0,0 +1,42 @@
+; $Id: memmove.asm $
+;; @file
+; IPRT - No-CRT memmove - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/memmove.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/mempcpy.asm b/src/VBox/Runtime/common/string/watcom/mempcpy.asm
new file mode 100644
index 00000000..d00bc6e5
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/mempcpy.asm
@@ -0,0 +1,42 @@
+; $Id: mempcpy.asm $
+;; @file
+; IPRT - No-CRT mempcpy - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/mempcpy.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/memrchr.asm b/src/VBox/Runtime/common/string/watcom/memrchr.asm
new file mode 100644
index 00000000..d020ead1
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/memrchr.asm
@@ -0,0 +1,42 @@
+; $Id: memrchr.asm $
+;; @file
+; IPRT - No-CRT memrchr - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/memrchr.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/memset.asm b/src/VBox/Runtime/common/string/watcom/memset.asm
new file mode 100644
index 00000000..d98f0951
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/memset.asm
@@ -0,0 +1,42 @@
+; $Id: memset.asm $
+;; @file
+; IPRT - No-CRT memset - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/memset.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/strchr.asm b/src/VBox/Runtime/common/string/watcom/strchr.asm
new file mode 100644
index 00000000..9efe0c61
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/strchr.asm
@@ -0,0 +1,42 @@
+; $Id: strchr.asm $
+;; @file
+; IPRT - No-CRT strchr - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/strchr.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/strcmp.asm b/src/VBox/Runtime/common/string/watcom/strcmp.asm
new file mode 100644
index 00000000..a4cd1135
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/strcmp.asm
@@ -0,0 +1,42 @@
+; $Id: strcmp.asm $
+;; @file
+; IPRT - No-CRT strcmp - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/strcmp.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/strcpy.asm b/src/VBox/Runtime/common/string/watcom/strcpy.asm
new file mode 100644
index 00000000..1d9a5f3a
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/strcpy.asm
@@ -0,0 +1,42 @@
+; $Id: strcpy.asm $
+;; @file
+; IPRT - No-CRT strcpy - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/strcpy.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/strlen.asm b/src/VBox/Runtime/common/string/watcom/strlen.asm
new file mode 100644
index 00000000..81d5396d
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/strlen.asm
@@ -0,0 +1,42 @@
+; $Id: strlen.asm $
+;; @file
+; IPRT - No-CRT strlen - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/strlen.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/strncmp.asm b/src/VBox/Runtime/common/string/watcom/strncmp.asm
new file mode 100644
index 00000000..a62dac4e
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/strncmp.asm
@@ -0,0 +1,42 @@
+; $Id: strncmp.asm $
+;; @file
+; IPRT - No-CRT strncmp - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/strncmp.asm"
+
diff --git a/src/VBox/Runtime/common/string/watcom/strncpy.asm b/src/VBox/Runtime/common/string/watcom/strncpy.asm
new file mode 100644
index 00000000..65a6868e
--- /dev/null
+++ b/src/VBox/Runtime/common/string/watcom/strncpy.asm
@@ -0,0 +1,42 @@
+; $Id: strncpy.asm $
+;; @file
+; IPRT - No-CRT strncpy - Watcom register calling convention.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+%define ASM_CALL32_WATCOM
+%define NAME(name) name %+ _
+%include "common/string/strncpy.asm"
+
diff --git a/src/VBox/Runtime/common/string/wcslen.asm b/src/VBox/Runtime/common/string/wcslen.asm
new file mode 100644
index 00000000..5ad683a0
--- /dev/null
+++ b/src/VBox/Runtime/common/string/wcslen.asm
@@ -0,0 +1,75 @@
+; $Id: wcslen.asm $
+;; @file
+; IPRT - No-CRT strlen - AMD64 & X86.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%include "iprt/asmdefs.mac"
+
+BEGINCODE
+
+;;
+; @param psz gcc: rdi msc: rcx x86: [esp+4] wcall: eax
+RT_NOCRT_BEGINPROC wcslen
+ cld
+%ifdef RT_ARCH_AMD64
+ %ifdef ASM_CALL64_MSC
+ mov r9, rdi ; save rdi
+ mov rdi, rcx
+ %endif
+%else
+ mov edx, edi ; save edi
+ %ifdef ASM_CALL32_WATCOM
+ mov edi, eax
+ %else
+ mov edi, [esp + 4]
+ %endif
+%endif
+
+ ; do the search
+ mov xCX, -1
+ xor eax, eax
+ repne scasw
+
+ ; found it
+ neg xCX
+ lea xAX, [xCX - 2]
+ shr xCX, 1
+%ifdef ASM_CALL64_MSC
+ mov rdi, r9
+%elifdef RT_ARCH_X86
+ mov edi, edx
+%endif
+ ret
+ENDPROC RT_NOCRT(wcslen)
+
diff --git a/src/VBox/Runtime/common/table/Makefile.kup b/src/VBox/Runtime/common/table/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/table/Makefile.kup
diff --git a/src/VBox/Runtime/common/table/avl_Base.cpp.h b/src/VBox/Runtime/common/table/avl_Base.cpp.h
new file mode 100644
index 00000000..6c38dff3
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avl_Base.cpp.h
@@ -0,0 +1,474 @@
+/* $Id: avl_Base.cpp.h $ */
+/** @file
+ * kAVLBase - basic routines for all AVL trees.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef _kAVLBase_h_
+#define _kAVLBase_h_
+
+
+/** @page pg_rt_kAVL kAVL Template configuration.
+ * @internal
+ *
+ * This is a template made to implement multiple AVL trees. The differences
+ * among the implementations are related to the key used.
+ *
+ * \#define KAVL_FN
+ * Use this to alter the names of the AVL functions.
+ * Must be defined.
+ *
+ * \#define KAVL_EQUAL_ALLOWED
+ * Define this to tell us that equal keys are allowed.
+ * Then Equal keys will be put in a list pointed to by pList in the KAVLNODECORE.
+ * This is by default not defined.
+ *
+ * \#define KAVL_CHECK_FOR_EQUAL_INSERT
+ * Define this to enable insert check for equal nodes.
+ * This is by default not defined.
+ *
+ * \#define KAVL_MAX_STACK
+ * Use this to specify the number of stack entries the stack will use when inserting
+ * and removing nodes from the tree. I think the size should be about
+ * log2(<max nodes>) + 3
+ * Must be defined.
+ *
+ */
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+#define AVL_HEIGHTOF(pNode) ((unsigned char)((pNode) != NULL ? pNode->uchHeight : 0))
+
+/** @def KAVL_GET_POINTER
+ * Reads a 'pointer' value.
+ *
+ * @returns The native pointer.
+ * @param pp Pointer to the pointer to read.
+ */
+
+/** @def KAVL_GET_POINTER_NULL
+ * Reads a 'pointer' value which can be KAVL_NULL.
+ *
+ * @returns The native pointer.
+ * @returns NULL pointer if KAVL_NULL.
+ * @param pp Pointer to the pointer to read.
+ */
+
+/** @def KAVL_SET_POINTER
+ * Writes a 'pointer' value.
+ * For offset-based schemes offset relative to pp is calculated and assigned to *pp.
+ *
+ * @returns stored pointer.
+ * @param pp Pointer to where to store the pointer.
+ * @param p Native pointer to assign to *pp.
+ */
+
+/** @def KAVL_SET_POINTER_NULL
+ * Writes a 'pointer' value which can be KAVL_NULL.
+ *
+ * For offset-based schemes offset relative to pp is calculated and assigned to *pp,
+ * if p is not KAVL_NULL of course.
+ *
+ * @returns stored pointer.
+ * @param pp Pointer to where to store the pointer.
+ * @param pp2 Pointer to where to pointer to assign to pp. This can be KAVL_NULL
+ */
+
+#ifndef KAVL_GET_POINTER
+# ifdef KAVL_OFFSET
+# define KAVL_GET_POINTER(pp) ( (PKAVLNODECORE)((intptr_t)(pp) + *(pp)) )
+# define KAVL_GET_POINTER_NULL(pp) ( *(pp) != KAVL_NULL ? KAVL_GET_POINTER(pp) : NULL )
+# define KAVL_SET_POINTER(pp, p) ( (*(pp)) = ((intptr_t)(p) - (intptr_t)(pp)) )
+# define KAVL_SET_POINTER_NULL(pp, pp2) ( (*(pp)) = *(pp2) != KAVL_NULL ? (intptr_t)KAVL_GET_POINTER(pp2) - (intptr_t)(pp) : KAVL_NULL )
+# else
+# define KAVL_GET_POINTER(pp) ( *(pp) )
+# define KAVL_GET_POINTER_NULL(pp) ( *(pp) )
+# define KAVL_SET_POINTER(pp, p) ( (*(pp)) = (p) )
+# define KAVL_SET_POINTER_NULL(pp, pp2) ( (*(pp)) = *(pp2) )
+# endif
+#endif
+
+
+/** @def KAVL_NULL
+ * The NULL 'pointer' equivalent.
+ */
+#ifndef KAVL_NULL
+# ifdef KAVL_OFFSET
+# define KAVL_NULL 0
+# else
+# define KAVL_NULL NULL
+# endif
+#endif
+
+#ifndef KAVL_RANGE
+# define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) KAVL_E(key1B, key2B)
+# define KAVL_R_IS_IDENTICAL(key1B, key2B, key1E, key2E) KAVL_E(key1B, key2B)
+#endif
+
+/** @def KAVL_DECL
+ * Function declation macro in the RTDECL tradition.
+ * @param a_Type The function return type. */
+#ifndef KAVL_DECL
+# define KAVL_DECL(a_Type) RTDECL(a_Type)
+#endif
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/*
+ * A stack used to avoid recursive calls...
+ */
+typedef struct _kAvlStack
+{
+ unsigned cEntries;
+ PPKAVLNODECORE aEntries[KAVL_MAX_STACK];
+} KAVLSTACK, *PKAVLSTACK;
+
+typedef struct _kAvlStack2
+{
+ unsigned cEntries;
+ PKAVLNODECORE aEntries[KAVL_MAX_STACK];
+ char achFlags[KAVL_MAX_STACK];
+} KAVLSTACK2, *PLAVLSTACK2;
+
+
+
+/**
+ * Rewinds a stack of pointers to pointers to nodes, rebalancing the tree.
+ * @param pStack Pointer to stack to rewind.
+ * @sketch LOOP thru all stack entries
+ * BEGIN
+ * Get pointer to pointer to node (and pointer to node) from the stack.
+ * IF 2 higher left subtree than in right subtree THEN
+ * BEGIN
+ * IF higher (or equal) left-sub-subtree than right-sub-subtree THEN
+ * * n+2|n+3
+ * / \ / \
+ * n+2 n ==> n+1 n+1|n+2
+ * / \ / \
+ * n+1 n|n+1 n|n+1 n
+ *
+ * Or with keys:
+ *
+ * 4 2
+ * / \ / \
+ * 2 5 ==> 1 4
+ * / \ / \
+ * 1 3 3 5
+ *
+ * ELSE
+ * * n+2
+ * / \ / \
+ * n+2 n n+1 n+1
+ * / \ ==> / \ / \
+ * n n+1 n L R n
+ * / \
+ * L R
+ *
+ * Or with keys:
+ * 6 4
+ * / \ / \
+ * 2 7 ==> 2 6
+ * / \ / \ / \
+ * 1 4 1 3 5 7
+ * / \
+ * 3 5
+ * END
+ * ELSE IF 2 higher in right subtree than in left subtree THEN
+ * BEGIN
+ * Same as above but left <==> right. (invert the picture)
+ * ELSE
+ * IF correct height THEN break
+ * ELSE correct height.
+ * END
+ */
+DECLINLINE(void) KAVL_FN(Rebalance)(PKAVLSTACK pStack)
+{
+ while (pStack->cEntries > 0)
+ {
+ /** @todo Perhaps some of these KAVL_SET_POINTER_NULL() cases could be optimized away.. */
+ PPKAVLNODECORE ppNode = pStack->aEntries[--pStack->cEntries];
+ PKAVLNODECORE pNode = KAVL_GET_POINTER(ppNode);
+ PKAVLNODECORE pLeftNode = KAVL_GET_POINTER_NULL(&pNode->pLeft);
+ unsigned char uchLeftHeight = AVL_HEIGHTOF(pLeftNode);
+ PKAVLNODECORE pRightNode = KAVL_GET_POINTER_NULL(&pNode->pRight);
+ unsigned char uchRightHeight = AVL_HEIGHTOF(pRightNode);
+
+ if (uchRightHeight + 1 < uchLeftHeight)
+ {
+ PKAVLNODECORE pLeftLeftNode = KAVL_GET_POINTER_NULL(&pLeftNode->pLeft);
+ PKAVLNODECORE pLeftRightNode = KAVL_GET_POINTER_NULL(&pLeftNode->pRight);
+ unsigned char uchLeftRightHeight = AVL_HEIGHTOF(pLeftRightNode);
+
+ if (AVL_HEIGHTOF(pLeftLeftNode) >= uchLeftRightHeight)
+ {
+ KAVL_SET_POINTER_NULL(&pNode->pLeft, &pLeftNode->pRight);
+ KAVL_SET_POINTER(&pLeftNode->pRight, pNode);
+ pLeftNode->uchHeight = (unsigned char)(1 + (pNode->uchHeight = (unsigned char)(1 + uchLeftRightHeight)));
+ KAVL_SET_POINTER(ppNode, pLeftNode);
+ }
+ else
+ {
+ KAVL_SET_POINTER_NULL(&pLeftNode->pRight, &pLeftRightNode->pLeft);
+ KAVL_SET_POINTER_NULL(&pNode->pLeft, &pLeftRightNode->pRight);
+ KAVL_SET_POINTER(&pLeftRightNode->pLeft, pLeftNode);
+ KAVL_SET_POINTER(&pLeftRightNode->pRight, pNode);
+ pLeftNode->uchHeight = pNode->uchHeight = uchLeftRightHeight;
+ pLeftRightNode->uchHeight = uchLeftHeight;
+ KAVL_SET_POINTER(ppNode, pLeftRightNode);
+ }
+ }
+ else if (uchLeftHeight + 1 < uchRightHeight)
+ {
+ PKAVLNODECORE pRightLeftNode = KAVL_GET_POINTER_NULL(&pRightNode->pLeft);
+ unsigned char uchRightLeftHeight = AVL_HEIGHTOF(pRightLeftNode);
+ PKAVLNODECORE pRightRightNode = KAVL_GET_POINTER_NULL(&pRightNode->pRight);
+
+ if (AVL_HEIGHTOF(pRightRightNode) >= uchRightLeftHeight)
+ {
+ KAVL_SET_POINTER_NULL(&pNode->pRight, &pRightNode->pLeft);
+ KAVL_SET_POINTER(&pRightNode->pLeft, pNode);
+ pRightNode->uchHeight = (unsigned char)(1 + (pNode->uchHeight = (unsigned char)(1 + uchRightLeftHeight)));
+ KAVL_SET_POINTER(ppNode, pRightNode);
+ }
+ else
+ {
+ KAVL_SET_POINTER_NULL(&pRightNode->pLeft, &pRightLeftNode->pRight);
+ KAVL_SET_POINTER_NULL(&pNode->pRight, &pRightLeftNode->pLeft);
+ KAVL_SET_POINTER(&pRightLeftNode->pRight, pRightNode);
+ KAVL_SET_POINTER(&pRightLeftNode->pLeft, pNode);
+ pRightNode->uchHeight = pNode->uchHeight = uchRightLeftHeight;
+ pRightLeftNode->uchHeight = uchRightHeight;
+ KAVL_SET_POINTER(ppNode, pRightLeftNode);
+ }
+ }
+ else
+ {
+ unsigned char uchHeight = (unsigned char)(KMAX(uchLeftHeight, uchRightHeight) + 1);
+ if (uchHeight == pNode->uchHeight)
+ break;
+ pNode->uchHeight = uchHeight;
+ }
+ }
+
+}
+
+
+
+
+/**
+ * Inserts a node into the AVL-tree.
+ * @returns TRUE if inserted.
+ * FALSE if node exists in tree.
+ * @param ppTree Pointer to the AVL-tree root node pointer.
+ * @param pNode Pointer to the node which is to be added.
+ * @sketch Find the location of the node (using binary tree algorithm.):
+ * LOOP until KAVL_NULL leaf pointer
+ * BEGIN
+ * Add node pointer pointer to the AVL-stack.
+ * IF new-node-key < node key THEN
+ * left
+ * ELSE
+ * right
+ * END
+ * Fill in leaf node and insert it.
+ * Rebalance the tree.
+ */
+KAVL_DECL(bool) KAVL_FN(Insert)(PPKAVLNODECORE ppTree, PKAVLNODECORE pNode)
+{
+ KAVLSTACK AVLStack;
+ PPKAVLNODECORE ppCurNode = ppTree;
+ PKAVLNODECORE pCurNode;
+ KAVLKEY Key = pNode->Key; NOREF(Key);
+#ifdef KAVL_RANGE
+ KAVLKEY KeyLast = pNode->KeyLast; NOREF(KeyLast);
+#endif
+
+ AVLStack.cEntries = 0;
+
+#ifdef KAVL_RANGE
+ if (Key > KeyLast)
+ return false;
+#endif
+
+ for (;;)
+ {
+ if (*ppCurNode != KAVL_NULL)
+ pCurNode = KAVL_GET_POINTER(ppCurNode);
+ else
+ break;
+ Assert(pCurNode->uchHeight == RT_MAX(AVL_HEIGHTOF(KAVL_GET_POINTER_NULL(&pCurNode->pLeft)),
+ AVL_HEIGHTOF(KAVL_GET_POINTER_NULL(&pCurNode->pRight))) + 1);
+
+ kASSERT(AVLStack.cEntries < KAVL_MAX_STACK);
+ AVLStack.aEntries[AVLStack.cEntries++] = ppCurNode;
+#ifdef KAVL_EQUAL_ALLOWED
+ if (KAVL_R_IS_IDENTICAL(pCurNode->Key, Key, pCurNode->KeyLast, KeyLast))
+ {
+ /*
+ * If equal then we'll use a list of equal nodes.
+ */
+ pNode->pLeft = pNode->pRight = KAVL_NULL;
+ pNode->uchHeight = 0;
+ KAVL_SET_POINTER_NULL(&pNode->pList, &pCurNode->pList);
+ KAVL_SET_POINTER(&pCurNode->pList, pNode);
+ return true;
+ }
+#endif
+#ifdef KAVL_CHECK_FOR_EQUAL_INSERT
+ if (KAVL_R_IS_INTERSECTING(pCurNode->Key, Key, pCurNode->KeyLast, KeyLast))
+ return false;
+#endif
+ if (KAVL_G(pCurNode->Key, Key))
+ ppCurNode = &pCurNode->pLeft;
+ else
+ ppCurNode = &pCurNode->pRight;
+ }
+
+ pNode->pLeft = pNode->pRight = KAVL_NULL;
+#ifdef KAVL_EQUAL_ALLOWED
+ pNode->pList = KAVL_NULL;
+#endif
+ pNode->uchHeight = 1;
+ KAVL_SET_POINTER(ppCurNode, pNode);
+
+ KAVL_FN(Rebalance)(SSToDS(&AVLStack));
+ return true;
+}
+
+
+/**
+ * Removes a node from the AVL-tree.
+ * @returns Pointer to the node.
+ * @param ppTree Pointer to the AVL-tree root node pointer.
+ * @param Key Key value of the node which is to be removed.
+ * @sketch Find the node which is to be removed:
+ * LOOP until not found
+ * BEGIN
+ * Add node pointer pointer to the AVL-stack.
+ * IF the keys matches THEN break!
+ * IF remove key < node key THEN
+ * left
+ * ELSE
+ * right
+ * END
+ * IF found THEN
+ * BEGIN
+ * IF left node not empty THEN
+ * BEGIN
+ * Find the right most node in the left tree while adding the pointer to the pointer to it's parent to the stack:
+ * Start at left node.
+ * LOOP until right node is empty
+ * BEGIN
+ * Add to stack.
+ * go right.
+ * END
+ * Link out the found node.
+ * Replace the node which is to be removed with the found node.
+ * Correct the stack entry for the pointer to the left tree.
+ * END
+ * ELSE
+ * BEGIN
+ * Move up right node.
+ * Remove last stack entry.
+ * END
+ * Balance tree using stack.
+ * END
+ * return pointer to the removed node (if found).
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(Remove)(PPKAVLNODECORE ppTree, KAVLKEY Key)
+{
+ KAVLSTACK AVLStack;
+ PPKAVLNODECORE ppDeleteNode = ppTree;
+ PKAVLNODECORE pDeleteNode;
+
+ AVLStack.cEntries = 0;
+
+ for (;;)
+ {
+ if (*ppDeleteNode != KAVL_NULL)
+ pDeleteNode = KAVL_GET_POINTER(ppDeleteNode);
+ else
+ return NULL;
+ Assert(pDeleteNode->uchHeight == RT_MAX(AVL_HEIGHTOF(KAVL_GET_POINTER_NULL(&pDeleteNode->pLeft)),
+ AVL_HEIGHTOF(KAVL_GET_POINTER_NULL(&pDeleteNode->pRight))) + 1);
+
+ kASSERT(AVLStack.cEntries < KAVL_MAX_STACK);
+ AVLStack.aEntries[AVLStack.cEntries++] = ppDeleteNode;
+ if (KAVL_E(pDeleteNode->Key, Key))
+ break;
+
+ if (KAVL_G(pDeleteNode->Key, Key))
+ ppDeleteNode = &pDeleteNode->pLeft;
+ else
+ ppDeleteNode = &pDeleteNode->pRight;
+ }
+
+ if (pDeleteNode->pLeft != KAVL_NULL)
+ {
+ /* find the rightmost node in the left tree. */
+ const unsigned iStackEntry = AVLStack.cEntries;
+ PPKAVLNODECORE ppLeftLeast = &pDeleteNode->pLeft;
+ PKAVLNODECORE pLeftLeast = KAVL_GET_POINTER(ppLeftLeast);
+
+ while (pLeftLeast->pRight != KAVL_NULL)
+ {
+ kASSERT(AVLStack.cEntries < KAVL_MAX_STACK);
+ AVLStack.aEntries[AVLStack.cEntries++] = ppLeftLeast;
+ ppLeftLeast = &pLeftLeast->pRight;
+ pLeftLeast = KAVL_GET_POINTER(ppLeftLeast);
+ }
+
+ /* link out pLeftLeast */
+ KAVL_SET_POINTER_NULL(ppLeftLeast, &pLeftLeast->pLeft);
+
+ /* link it in place of the delete node. */
+ KAVL_SET_POINTER_NULL(&pLeftLeast->pLeft, &pDeleteNode->pLeft);
+ KAVL_SET_POINTER_NULL(&pLeftLeast->pRight, &pDeleteNode->pRight);
+ pLeftLeast->uchHeight = pDeleteNode->uchHeight;
+ KAVL_SET_POINTER(ppDeleteNode, pLeftLeast);
+ AVLStack.aEntries[iStackEntry] = &pLeftLeast->pLeft;
+ }
+ else
+ {
+ KAVL_SET_POINTER_NULL(ppDeleteNode, &pDeleteNode->pRight);
+ AVLStack.cEntries--;
+ }
+
+ KAVL_FN(Rebalance)(SSToDS(&AVLStack));
+ return pDeleteNode;
+}
+
+#endif
diff --git a/src/VBox/Runtime/common/table/avl_Destroy.cpp.h b/src/VBox/Runtime/common/table/avl_Destroy.cpp.h
new file mode 100644
index 00000000..83334129
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avl_Destroy.cpp.h
@@ -0,0 +1,120 @@
+/* $Id: avl_Destroy.cpp.h $ */
+/** @file
+ * kAVLDestroy - Walk the tree calling a callback to destroy all the nodes.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef _kAVLDestroy_h_
+#define _kAVLDestroy_h_
+
+
+/**
+ * Destroys the specified tree, starting with the root node and working our way down.
+ *
+ * @returns 0 on success.
+ * @returns Return value from callback on failure. On failure, the tree will be in
+ * an unbalanced condition and only further calls to the Destroy should be
+ * made on it. Note that the node we fail on will be considered dead and
+ * no action is taken to link it back into the tree.
+ * @param ppTree Pointer to the AVL-tree root node pointer.
+ * @param pfnCallBack Pointer to callback function.
+ * @param pvUser User parameter passed on to the callback function.
+ */
+KAVL_DECL(int) KAVL_FN(Destroy)(PPKAVLNODECORE ppTree, PKAVLCALLBACK pfnCallBack, void *pvUser)
+{
+ unsigned cEntries;
+ PKAVLNODECORE apEntries[KAVL_MAX_STACK];
+ int rc;
+
+ if (*ppTree == KAVL_NULL)
+ return VINF_SUCCESS;
+
+ cEntries = 1;
+ apEntries[0] = KAVL_GET_POINTER(ppTree);
+ while (cEntries > 0)
+ {
+ /*
+ * Process the subtrees first.
+ */
+ PKAVLNODECORE pNode = apEntries[cEntries - 1];
+ if (pNode->pLeft != KAVL_NULL)
+ apEntries[cEntries++] = KAVL_GET_POINTER(&pNode->pLeft);
+ else if (pNode->pRight != KAVL_NULL)
+ apEntries[cEntries++] = KAVL_GET_POINTER(&pNode->pRight);
+ else
+ {
+#ifdef KAVL_EQUAL_ALLOWED
+ /*
+ * Process nodes with the same key.
+ */
+ while (pNode->pList != KAVL_NULL)
+ {
+ PKAVLNODECORE pEqual = KAVL_GET_POINTER(&pNode->pList);
+ KAVL_SET_POINTER(&pNode->pList, KAVL_GET_POINTER_NULL(&pEqual->pList));
+ pEqual->pList = KAVL_NULL;
+
+ rc = pfnCallBack(pEqual, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+#endif
+
+ /*
+ * Unlink the node.
+ */
+ if (--cEntries > 0)
+ {
+ PKAVLNODECORE pParent = apEntries[cEntries - 1];
+ if (KAVL_GET_POINTER(&pParent->pLeft) == pNode)
+ pParent->pLeft = KAVL_NULL;
+ else
+ pParent->pRight = KAVL_NULL;
+ }
+ else
+ *ppTree = KAVL_NULL;
+
+ kASSERT(pNode->pLeft == KAVL_NULL);
+ kASSERT(pNode->pRight == KAVL_NULL);
+ rc = pfnCallBack(pNode, pvUser);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+ } /* while */
+
+ kASSERT(*ppTree == KAVL_NULL);
+
+ return VINF_SUCCESS;
+}
+
+#endif
+
diff --git a/src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h b/src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h
new file mode 100644
index 00000000..88be3623
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avl_DoWithAll.cpp.h
@@ -0,0 +1,156 @@
+/* $Id: avl_DoWithAll.cpp.h $ */
+/** @file
+ * kAVLDoWithAll - Do with all nodes routine for AVL trees.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef _kAVLDoWithAll_h_
+#define _kAVLDoWithAll_h_
+
+
+/**
+ * Iterates thru all nodes in the given tree.
+ * @returns 0 on success. Return from callback on failure.
+ * @param ppTree Pointer to the AVL-tree root node pointer.
+ * @param fFromLeft TRUE: Left to right.
+ * FALSE: Right to left.
+ * @param pfnCallBack Pointer to callback function.
+ * @param pvParam Userparameter passed on to the callback function.
+ */
+KAVL_DECL(int) KAVL_FN(DoWithAll)(PPKAVLNODECORE ppTree, int fFromLeft, PKAVLCALLBACK pfnCallBack, void * pvParam)
+{
+ KAVLSTACK2 AVLStack;
+ PKAVLNODECORE pNode;
+#ifdef KAVL_EQUAL_ALLOWED
+ PKAVLNODECORE pEqual;
+#endif
+ int rc;
+
+ if (*ppTree == KAVL_NULL)
+ return VINF_SUCCESS;
+
+ AVLStack.cEntries = 1;
+ AVLStack.achFlags[0] = 0;
+ AVLStack.aEntries[0] = KAVL_GET_POINTER(ppTree);
+
+ if (fFromLeft)
+ { /* from left */
+ while (AVLStack.cEntries > 0)
+ {
+ pNode = AVLStack.aEntries[AVLStack.cEntries - 1];
+
+ /* left */
+ if (!AVLStack.achFlags[AVLStack.cEntries - 1]++)
+ {
+ if (pNode->pLeft != KAVL_NULL)
+ {
+ AVLStack.achFlags[AVLStack.cEntries] = 0; /* 0 first, 1 last */
+ AVLStack.aEntries[AVLStack.cEntries++] = KAVL_GET_POINTER(&pNode->pLeft);
+ continue;
+ }
+ }
+
+ /* center */
+ Assert(pNode->uchHeight == RT_MAX(AVL_HEIGHTOF(KAVL_GET_POINTER_NULL(&pNode->pLeft)),
+ AVL_HEIGHTOF(KAVL_GET_POINTER_NULL(&pNode->pRight))) + 1);
+ rc = pfnCallBack(pNode, pvParam);
+ if (rc != VINF_SUCCESS)
+ return rc;
+#ifdef KAVL_EQUAL_ALLOWED
+ if (pNode->pList != KAVL_NULL)
+ for (pEqual = KAVL_GET_POINTER(&pNode->pList); pEqual; pEqual = KAVL_GET_POINTER_NULL(&pEqual->pList))
+ {
+ rc = pfnCallBack(pEqual, pvParam);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+#endif
+
+ /* right */
+ AVLStack.cEntries--;
+ if (pNode->pRight != KAVL_NULL)
+ {
+ AVLStack.achFlags[AVLStack.cEntries] = 0;
+ AVLStack.aEntries[AVLStack.cEntries++] = KAVL_GET_POINTER(&pNode->pRight);
+ }
+ } /* while */
+ }
+ else
+ { /* from right */
+ while (AVLStack.cEntries > 0)
+ {
+ pNode = AVLStack.aEntries[AVLStack.cEntries - 1];
+
+ /* right */
+ if (!AVLStack.achFlags[AVLStack.cEntries - 1]++)
+ {
+ if (pNode->pRight != KAVL_NULL)
+ {
+ AVLStack.achFlags[AVLStack.cEntries] = 0; /* 0 first, 1 last */
+ AVLStack.aEntries[AVLStack.cEntries++] = KAVL_GET_POINTER(&pNode->pRight);
+ continue;
+ }
+ }
+
+ /* center */
+ Assert(pNode->uchHeight == RT_MAX(AVL_HEIGHTOF(KAVL_GET_POINTER_NULL(&pNode->pLeft)),
+ AVL_HEIGHTOF(KAVL_GET_POINTER_NULL(&pNode->pRight))) + 1);
+ rc = pfnCallBack(pNode, pvParam);
+ if (rc != VINF_SUCCESS)
+ return rc;
+#ifdef KAVL_EQUAL_ALLOWED
+ if (pNode->pList != KAVL_NULL)
+ for (pEqual = KAVL_GET_POINTER(&pNode->pList); pEqual; pEqual = KAVL_GET_POINTER_NULL(&pEqual->pList))
+ {
+ rc = pfnCallBack(pEqual, pvParam);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+#endif
+
+ /* left */
+ AVLStack.cEntries--;
+ if (pNode->pLeft != KAVL_NULL)
+ {
+ AVLStack.achFlags[AVLStack.cEntries] = 0;
+ AVLStack.aEntries[AVLStack.cEntries++] = KAVL_GET_POINTER(&pNode->pLeft);
+ }
+ } /* while */
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+#endif
+
diff --git a/src/VBox/Runtime/common/table/avl_Enum.cpp.h b/src/VBox/Runtime/common/table/avl_Enum.cpp.h
new file mode 100644
index 00000000..3869c31a
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avl_Enum.cpp.h
@@ -0,0 +1,105 @@
+/* $Id: avl_Enum.cpp.h $ */
+/** @file
+ * Enumeration routines for AVL trees.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef _kAVLEnum_h_
+#define _kAVLEnum_h_
+
+
+/**
+ * Gets the root node.
+ *
+ * @returns Pointer to the root node.
+ * @returns NULL if the tree is empty.
+ *
+ * @param ppTree Pointer to pointer to the tree root node.
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(GetRoot)(PPKAVLNODECORE ppTree)
+{
+ return KAVL_GET_POINTER_NULL(ppTree);
+}
+
+
+/**
+ * Gets the right node.
+ *
+ * @returns Pointer to the right node.
+ * @returns NULL if no right node.
+ *
+ * @param pNode The current node.
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(GetRight)(PKAVLNODECORE pNode)
+{
+ if (pNode)
+ return KAVL_GET_POINTER_NULL(&pNode->pRight);
+ return NULL;
+}
+
+
+/**
+ * Gets the left node.
+ *
+ * @returns Pointer to the left node.
+ * @returns NULL if no left node.
+ *
+ * @param pNode The current node.
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(GetLeft)(PKAVLNODECORE pNode)
+{
+ if (pNode)
+ return KAVL_GET_POINTER_NULL(&pNode->pLeft);
+ return NULL;
+}
+
+
+# ifdef KAVL_EQUAL_ALLOWED
+/**
+ * Gets the next node with an equal (start) key.
+ *
+ * @returns Pointer to the next equal node.
+ * @returns NULL if the current node was the last one with this key.
+ *
+ * @param pNode The current node.
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(GetNextEqual)(PKAVLNODECORE pNode)
+{
+ if (pNode)
+ return KAVL_GET_POINTER_NULL(&pNode->pList);
+ return NULL;
+}
+# endif /* KAVL_EQUAL_ALLOWED */
+
+#endif
+
diff --git a/src/VBox/Runtime/common/table/avl_Get.cpp.h b/src/VBox/Runtime/common/table/avl_Get.cpp.h
new file mode 100644
index 00000000..d3f938ad
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avl_Get.cpp.h
@@ -0,0 +1,77 @@
+/* $Id: avl_Get.cpp.h $ */
+/** @file
+ * kAVLGet - get routine for AVL trees.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef _kAVLGet_h_
+#define _kAVLGet_h_
+
+
+/**
+ * Gets a node from the tree (does not remove it!)
+ * @returns Pointer to the node holding the given key.
+ * @param ppTree Pointer to the AVL-tree root node pointer.
+ * @param Key Key value of the node which is to be found.
+ * @author knut st. osmundsen
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(Get)(PPKAVLNODECORE ppTree, KAVLKEY Key)
+{
+ PKAVLNODECORE pNode = KAVL_GET_POINTER_NULL(ppTree);
+
+ if (pNode)
+ {
+ while (KAVL_NE(pNode->Key, Key))
+ {
+ if (KAVL_G(pNode->Key, Key))
+ {
+ if (pNode->pLeft != KAVL_NULL)
+ pNode = KAVL_GET_POINTER(&pNode->pLeft);
+ else
+ return NULL;
+ }
+ else
+ {
+ if (pNode->pRight != KAVL_NULL)
+ pNode = KAVL_GET_POINTER(&pNode->pRight);
+ else
+ return NULL;
+ }
+ }
+ }
+
+ return pNode;
+}
+
+
+#endif
diff --git a/src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h b/src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h
new file mode 100644
index 00000000..d8438456
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avl_GetBestFit.cpp.h
@@ -0,0 +1,113 @@
+/* $Id: avl_GetBestFit.cpp.h $ */
+/** @file
+ * kAVLGetBestFit - Get Best Fit routine for AVL trees.
+ * Intended specially on heaps. The tree should allow duplicate keys.
+ *
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef _kAVLGetBestFit_h_
+#define _kAVLGetBestFit_h_
+
+
+/**
+ * Finds the best fitting node in the tree for the given Key value.
+ * @returns Pointer to the best fitting node found.
+ * @param ppTree Pointer to Pointer to the tree root node.
+ * @param Key The Key of which is to be found a best fitting match for..
+ * @param fAbove TRUE: Returned node is have the closest key to Key from above.
+ * FALSE: Returned node is have the closest key to Key from below.
+ * @sketch The best fitting node is always located in the searchpath above you.
+ * >= (above): The node where you last turned left.
+ * <= (below): the node where you last turned right.
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(GetBestFit)(PPKAVLNODECORE ppTree, KAVLKEY Key, bool fAbove)
+{
+ PKAVLNODECORE pNode = KAVL_GET_POINTER_NULL(ppTree);
+ if (pNode)
+ {
+ PKAVLNODECORE pNodeLast = NULL;
+ if (fAbove)
+ { /* pNode->Key >= Key */
+ while (KAVL_NE(pNode->Key, Key))
+ {
+ if (KAVL_G(pNode->Key, Key))
+ {
+ if (pNode->pLeft != KAVL_NULL)
+ {
+ pNodeLast = pNode;
+ pNode = KAVL_GET_POINTER(&pNode->pLeft);
+ }
+ else
+ return pNode;
+ }
+ else
+ {
+ if (pNode->pRight != KAVL_NULL)
+ pNode = KAVL_GET_POINTER(&pNode->pRight);
+ else
+ return pNodeLast;
+ }
+ }
+ }
+ else
+ { /* pNode->Key <= Key */
+ while (KAVL_NE(pNode->Key, Key))
+ {
+ if (KAVL_G(pNode->Key, Key))
+ {
+ if (pNode->pLeft != KAVL_NULL)
+ pNode = KAVL_GET_POINTER(&pNode->pLeft);
+ else
+ return pNodeLast;
+ }
+ else
+ {
+ if (pNode->pRight != KAVL_NULL)
+ {
+ pNodeLast = pNode;
+ pNode = KAVL_GET_POINTER(&pNode->pRight);
+ }
+ else
+ return pNode;
+ }
+ }
+ }
+ }
+
+ /* perfect match or nothing. */
+ return pNode;
+}
+
+
+#endif
diff --git a/src/VBox/Runtime/common/table/avl_Range.cpp.h b/src/VBox/Runtime/common/table/avl_Range.cpp.h
new file mode 100644
index 00000000..04a28e34
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avl_Range.cpp.h
@@ -0,0 +1,94 @@
+/* $Id: avl_Range.cpp.h $ */
+/** @file
+ * kAVLRange - Range routines for AVL trees.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef _kAVLRange_h_
+#define _kAVLRange_h_
+
+/**
+ * Finds the range containing the specified key.
+ *
+ * @returns Pointer to the matching range.
+ *
+ * @param ppTree Pointer to Pointer to the tree root node.
+ * @param Key The Key to find matching range for.
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(RangeGet)(PPKAVLNODECORE ppTree, KAVLKEY Key)
+{
+ PKAVLNODECORE pNode = KAVL_GET_POINTER_NULL(ppTree);
+ if (pNode)
+ {
+ for (;;)
+ {
+ if (KAVL_R_IS_IN_RANGE(pNode->Key, pNode->KeyLast, Key))
+ return pNode;
+ if (KAVL_G(pNode->Key, Key))
+ {
+ if (pNode->pLeft != KAVL_NULL)
+ pNode = KAVL_GET_POINTER(&pNode->pLeft);
+ else
+ return NULL;
+ }
+ else
+ {
+ if (pNode->pRight != KAVL_NULL)
+ pNode = KAVL_GET_POINTER(&pNode->pRight);
+ else
+ return NULL;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Removes the range containing the specified key.
+ *
+ * @returns Pointer to the matching range.
+ *
+ * @param ppTree Pointer to Pointer to the tree root node.
+ * @param Key The Key to remove matching range for.
+ */
+RTDECL(PKAVLNODECORE) KAVL_FN(RangeRemove)(PPKAVLNODECORE ppTree, KAVLKEY Key)
+{
+ PKAVLNODECORE pNode = KAVL_FN(RangeGet)(ppTree, Key);
+ if (pNode)
+ return KAVL_FN(Remove)(ppTree, pNode->Key);
+ return NULL;
+}
+
+#endif
diff --git a/src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h b/src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h
new file mode 100644
index 00000000..761ac7d2
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avl_RemoveBestFit.cpp.h
@@ -0,0 +1,80 @@
+/* $Id: avl_RemoveBestFit.cpp.h $ */
+/** @file
+ * kAVLRemoveBestFit - Remove Best Fit routine for AVL trees.
+ * Intended specially on heaps. The tree should allow duplicate keys.
+ *
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef _kAVLRemoveBestFit_h_
+#define _kAVLRemoveBestFit_h_
+
+
+/**
+ * Finds the best fitting node in the tree for the given Key value.
+ * And removes it.
+ * @returns Pointer to the best fitting node found.
+ * @param ppTree Pointer to Pointer to the tree root node.
+ * @param Key The Key of which is to be found a best fitting match for..
+ * @param fAbove TRUE: Returned node is have the closest key to Key from above.
+ * FALSE: Returned node is have the closest key to Key from below.
+ * @sketch The best fitting node is always located in the searchpath above you.
+ * >= (above): The node where you last turned left.
+ * <= (below): the node where you last turned right.
+ * @remark This implementation should be speeded up slightly!
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(RemoveBestFit)(PPKAVLNODECORE ppTree, KAVLKEY Key, bool fAbove)
+{
+ /*
+ * If we find anything we'll have to remove the node and return it.
+ * But, if duplicate keys are allowed we'll have to check for multiple
+ * nodes first and return one of them before doing an expensive remove+insert.
+ */
+ PKAVLNODECORE pNode = KAVL_FN(GetBestFit)(ppTree, Key, fAbove);
+ if (pNode != NULL)
+ {
+#ifdef KAVL_EQUAL_ALLOWED
+ if (pNode->pList != KAVL_NULL)
+ {
+ PKAVLNODECORE pRet = KAVL_GET_POINTER(&pNode->pList);
+ KAVL_SET_POINTER_NULL(&pNode->pList, &pRet->pList);
+ return pRet;
+ }
+#endif
+ pNode = KAVL_FN(Remove)(ppTree, pNode->Key);
+ }
+ return pNode;
+}
+
+
+#endif
diff --git a/src/VBox/Runtime/common/table/avl_RemoveNode.cpp.h b/src/VBox/Runtime/common/table/avl_RemoveNode.cpp.h
new file mode 100644
index 00000000..16e851cf
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avl_RemoveNode.cpp.h
@@ -0,0 +1,153 @@
+/* $Id: avl_RemoveNode.cpp.h $ */
+/** @file
+ * kAVLRemove2 - Remove specific node (by pointer) from an AVL tree.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/**
+ * Removes the specified node from the tree.
+ *
+ * @returns Pointer to the removed node (NULL if not in the tree)
+ * @param ppTree Pointer to the AVL-tree root structure.
+ * @param pNode Pointer to the node to be removed.
+ *
+ * @remark This implementation isn't the most efficient, but it's relatively
+ * short and easier to manage.
+ */
+KAVL_DECL(PKAVLNODECORE) KAVL_FN(RemoveNode)(PPKAVLNODECORE ppTree, PKAVLNODECORE pNode)
+{
+#ifdef KAVL_EQUAL_ALLOWED
+ /*
+ * Find the right node by key together with the parent node.
+ */
+ KAVLKEY const Key = pNode->Key;
+ PKAVLNODECORE pParent = NULL;
+ PKAVLNODECORE pCurNode = KAVL_GET_POINTER_NULL(ppTree);
+ if (!pCurNode)
+ return NULL;
+ while (KAVL_NE(pCurNode->Key, Key))
+ {
+ pParent = pCurNode;
+ if (KAVL_G(pCurNode->Key, Key))
+ {
+ if (pCurNode->pLeft != KAVL_NULL)
+ pCurNode = KAVL_GET_POINTER(&pCurNode->pLeft);
+ else
+ return NULL;
+ }
+ else
+ {
+ if (pCurNode->pRight != KAVL_NULL)
+ pCurNode = KAVL_GET_POINTER(&pCurNode->pRight);
+ else
+ return NULL;
+ }
+ }
+
+ if (pCurNode != pNode)
+ {
+ /*
+ * It's not the one we want, but it could be in the duplicate list.
+ */
+ while (pCurNode->pList != KAVL_NULL)
+ {
+ PKAVLNODECORE pNext = KAVL_GET_POINTER(&pCurNode->pList);
+ if (pNext == pNode)
+ {
+ if (pNode->pList != KAVL_NULL)
+ KAVL_SET_POINTER(&pCurNode->pList, KAVL_GET_POINTER(&pNode->pList));
+ else
+ pCurNode->pList = KAVL_NULL;
+ pNode->pList = KAVL_NULL;
+ return pNode;
+ }
+ pCurNode = pNext;
+ }
+ return NULL;
+ }
+
+ /*
+ * Ok, it's the one we want alright.
+ *
+ * Simply remove it if it's the only one with they Key, if there are
+ * duplicates we'll have to unlink it and insert the first duplicate
+ * in our place.
+ */
+ if (pNode->pList == KAVL_NULL)
+ KAVL_FN(Remove)(ppTree, pNode->Key);
+ else
+ {
+ PKAVLNODECORE pNewUs = KAVL_GET_POINTER(&pNode->pList);
+
+ pNewUs->uchHeight = pNode->uchHeight;
+
+ if (pNode->pLeft != KAVL_NULL)
+ KAVL_SET_POINTER(&pNewUs->pLeft, KAVL_GET_POINTER(&pNode->pLeft));
+ else
+ pNewUs->pLeft = KAVL_NULL;
+
+ if (pNode->pRight != KAVL_NULL)
+ KAVL_SET_POINTER(&pNewUs->pRight, KAVL_GET_POINTER(&pNode->pRight));
+ else
+ pNewUs->pRight = KAVL_NULL;
+
+ if (pParent)
+ {
+ if (KAVL_GET_POINTER_NULL(&pParent->pLeft) == pNode)
+ KAVL_SET_POINTER(&pParent->pLeft, pNewUs);
+ else
+ KAVL_SET_POINTER(&pParent->pRight, pNewUs);
+ }
+ else
+ KAVL_SET_POINTER(ppTree, pNewUs);
+ }
+
+ return pNode;
+
+#else
+ /*
+ * Delete it, if we got the wrong one, reinsert it.
+ *
+ * This ASSUMS that the caller is NOT going to hand us a lot
+ * of wrong nodes but just uses this API for his convenience.
+ */
+ KAVLNODE *pRemovedNode = KAVL_FN(Remove)(pRoot, pNode->Key);
+ if (pRemovedNode == pNode)
+ return pRemovedNode;
+
+ KAVL_FN(Insert)(pRoot, pRemovedNode);
+ return NULL;
+#endif
+}
+
diff --git a/src/VBox/Runtime/common/table/avlgcphys.cpp b/src/VBox/Runtime/common/table/avlgcphys.cpp
new file mode 100644
index 00000000..5863a555
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlgcphys.cpp
@@ -0,0 +1,88 @@
+/* $Id: avlgcphys.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTGCPHYS, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlGCPhys##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLGCPHYSNODECORE
+#define PKAVLNODECORE PAVLGCPHYSNODECORE
+#define PPKAVLNODECORE PPAVLGCPHYSNODECORE
+#define KAVLKEY RTGCPHYS
+#define PKAVLKEY PRTGCPHYS
+#define KAVLENUMDATA AVLGCPHYSENUMDATA
+#define PKAVLENUMDATA PAVLGCPHYSENUMDATA
+#define PKAVLCALLBACK PAVLGCPHYSCALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (key1) > (key2) )
+#define KAVL_E( key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlgcptr.cpp b/src/VBox/Runtime/common/table/avlgcptr.cpp
new file mode 100644
index 00000000..0e263422
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlgcptr.cpp
@@ -0,0 +1,88 @@
+/* $Id: avlgcptr.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTGCPTR, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLPVInt.c,v 1.5 2003/02/13 02:02:35 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlGCPtr##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLGCPTRNODECORE
+#define PKAVLNODECORE PAVLGCPTRNODECORE
+#define PPKAVLNODECORE PPAVLGCPTRNODECORE
+#define KAVLKEY RTGCPTR
+#define PKAVLKEY PRTGCPTR
+#define KAVLENUMDATA AVLGCPTRENUMDATA
+#define PKAVLENUMDATA PAVLGCPTRENUMDATA
+#define PKAVLCALLBACK PAVLGCPTRCALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) ( (RTGCUINTPTR)(key1) > (RTGCUINTPTR)(key2) )
+#define KAVL_E(key1, key2) ( (RTGCUINTPTR)(key1) == (RTGCUINTPTR)(key2) )
+#define KAVL_NE(key1, key2) ( (RTGCUINTPTR)(key1) != (RTGCUINTPTR)(key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlhcphys.cpp b/src/VBox/Runtime/common/table/avlhcphys.cpp
new file mode 100644
index 00000000..22395d25
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlhcphys.cpp
@@ -0,0 +1,88 @@
+/* $Id: avlhcphys.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTHCPHYS, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlHCPhys##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLHCPHYSNODECORE
+#define PKAVLNODECORE PAVLHCPHYSNODECORE
+#define PPKAVLNODECORE PPAVLHCPHYSNODECORE
+#define KAVLKEY RTHCPHYS
+#define PKAVLKEY PRTHCPHYS
+#define KAVLENUMDATA AVLHCPHYSENUMDATA
+#define PKAVLENUMDATA PAVLHCPHYSENUMDATA
+#define PKAVLCALLBACK PAVLHCPHYSCALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (key1) > (key2) )
+#define KAVL_E( key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avllu32.cpp b/src/VBox/Runtime/common/table/avllu32.cpp
new file mode 100644
index 00000000..8d81b78f
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avllu32.cpp
@@ -0,0 +1,89 @@
+/* $Id: avllu32.cpp $ */
+/** @file
+ * IPRT - AVL tree, uint32_t, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvllU32##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_EQUAL_ALLOWED 1 /* List duplicate keys! */
+#define KAVLNODECORE AVLLU32NODECORE
+#define PKAVLNODECORE PAVLLU32NODECORE
+#define PPKAVLNODECORE PPAVLLU32NODECORE
+#define KAVLKEY AVLLU32KEY
+#define PKAVLKEY PAVLLU32KEY
+#define KAVLENUMDATA AVLLU32ENUMDATA
+#define PKAVLENUMDATA PAVLLU32ENUMDATA
+#define PKAVLCALLBACK PAVLLU32CALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) ( (key1) > (key2) )
+#define KAVL_E(key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveNode.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlogcphys.cpp b/src/VBox/Runtime/common/table/avlogcphys.cpp
new file mode 100644
index 00000000..12e5ced3
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlogcphys.cpp
@@ -0,0 +1,89 @@
+/* $Id: avlogcphys.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTGCPHYS, unique keys, offset pointers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvloGCPhys##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLOGCPHYSNODECORE
+#define PKAVLNODECORE PAVLOGCPHYSNODECORE
+#define PPKAVLNODECORE PPAVLOGCPHYSNODECORE
+#define KAVLKEY RTGCPHYS
+#define PKAVLKEY PRTGCPHYS
+#define KAVLENUMDATA AVLOGCPHYSENUMDATA
+#define PKAVLENUMDATA PAVLOGCPHYSENUMDATA
+#define PKAVLCALLBACK PAVLOGCPHYSCALLBACK
+#define KAVL_OFFSET 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (key1) > (key2) )
+#define KAVL_E( key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlogcptr.cpp b/src/VBox/Runtime/common/table/avlogcptr.cpp
new file mode 100644
index 00000000..3a6c64cd
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlogcptr.cpp
@@ -0,0 +1,90 @@
+/* $Id: avlogcptr.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTGCPTR, unique keys, offset pointers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvloGCPtr##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLOGCPTRNODECORE
+#define PKAVLNODECORE PAVLOGCPTRNODECORE
+#define PPKAVLNODECORE PPAVLOGCPTRNODECORE
+#define KAVLKEY RTGCPTR
+#define PKAVLKEY PRTGCPTR
+#define KAVLENUMDATA AVLOGCPTRENUMDATA
+#define PKAVLENUMDATA PAVLOGCPTRENUMDATA
+#define PKAVLCALLBACK PAVLOGCPTRCALLBACK
+#define KAVL_OFFSET 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (RTGCUINTPTR)(key1) > (RTGCUINTPTR)(key2) )
+#define KAVL_E( key1, key2) ( (RTGCUINTPTR)(key1) == (RTGCUINTPTR)(key2) )
+#define KAVL_NE(key1, key2) ( (RTGCUINTPTR)(key1) != (RTGCUINTPTR)(key2) )
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlohcphys.cpp b/src/VBox/Runtime/common/table/avlohcphys.cpp
new file mode 100644
index 00000000..9f43620b
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlohcphys.cpp
@@ -0,0 +1,89 @@
+/* $Id: avlohcphys.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTHCPHYS, unique keys, offset pointers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvloHCPhys##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLOHCPHYSNODECORE
+#define PKAVLNODECORE PAVLOHCPHYSNODECORE
+#define PPKAVLNODECORE PPAVLOHCPHYSNODECORE
+#define KAVLKEY RTHCPHYS
+#define PKAVLKEY PRTHCPHYS
+#define KAVLENUMDATA AVLOHCPHYSENUMDATA
+#define PKAVLENUMDATA PAVLOHCPHYSENUMDATA
+#define PKAVLCALLBACK PAVLOHCPHYSCALLBACK
+#define KAVL_OFFSET 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (key1) > (key2) )
+#define KAVL_E( key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avloioport.cpp b/src/VBox/Runtime/common/table/avloioport.cpp
new file mode 100644
index 00000000..ebabd9d5
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avloioport.cpp
@@ -0,0 +1,89 @@
+/* $Id: avloioport.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTIOPORT, unique keys, offset pointers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvloIOPort##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLOIOPORTNODECORE
+#define PKAVLNODECORE PAVLOIOPORTNODECORE
+#define PPKAVLNODECORE PPAVLOIOPORTNODECORE
+#define KAVLKEY RTIOPORT
+#define PKAVLKEY PRTIOPORT
+#define KAVLENUMDATA AVLOIOPORTENUMDATA
+#define PKAVLENUMDATA PAVLOIOPORTENUMDATA
+#define PKAVLCALLBACK PAVLOIOPORTCALLBACK
+#define KAVL_OFFSET 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (key1) > (key2) )
+#define KAVL_E( key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlou32.cpp b/src/VBox/Runtime/common/table/avlou32.cpp
new file mode 100644
index 00000000..2d565d71
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlou32.cpp
@@ -0,0 +1,90 @@
+/* $Id: avlou32.cpp $ */
+/** @file
+ * IPRT - AVL tree, uint_32, unique keys, offset pointers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvloU32##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLOU32NODECORE
+#define PKAVLNODECORE PAVLOU32NODECORE
+#define PPKAVLNODECORE PPAVLOU32NODECORE
+#define KAVLKEY AVLOU32KEY
+#define PKAVLKEY AVLOU32KEY *
+#define KAVLENUMDATA AVLOU32ENUMDATA
+#define PKAVLENUMDATA PAVLOU32ENUMDATA
+#define PKAVLCALLBACK PAVLOU32CALLBACK
+#define KAVL_OFFSET 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (key1) > (key2) )
+#define KAVL_E( key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlpv.cpp b/src/VBox/Runtime/common/table/avlpv.cpp
new file mode 100644
index 00000000..dcce99fc
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlpv.cpp
@@ -0,0 +1,88 @@
+/* $Id: avlpv.cpp $ */
+/** @file
+ * IPRT - AVL tree, void *, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+/*static const char szFileId[] = "Id: kAVLPVInt.c,v 1.5 2003/02/13 02:02:35 bird Exp $";*/
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlPV##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLPVNODECORE
+#define PKAVLNODECORE PAVLPVNODECORE
+#define PPKAVLNODECORE PPAVLPVNODECORE
+#define KAVLKEY AVLPVKEY
+#define PKAVLKEY PAVLPVKEY
+#define KAVLENUMDATA AVLPVENUMDATA
+#define PKAVLENUMDATA PAVLPVENUMDATA
+#define PKAVLCALLBACK PAVLPVCALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) ( (const char*)(key1) > (const char*)(key2) )
+#define KAVL_E(key1, key2) ( (const char*)(key1) == (const char*)(key2) )
+#define KAVL_NE(key1, key2) ( (const char*)(key1) != (const char*)(key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlrfoff.cpp b/src/VBox/Runtime/common/table/avlrfoff.cpp
new file mode 100644
index 00000000..f39f9c93
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlrfoff.cpp
@@ -0,0 +1,94 @@
+/* $Id: avlrfoff.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTFOFF, range, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlrFileOffset##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLRFOFFNODECORE
+#define PKAVLNODECORE PAVLRFOFFNODECORE
+#define PPKAVLNODECORE PPAVLRFOFFNODECORE
+#define KAVLKEY RTFOFF
+#define PKAVLKEY PRTFOFF
+#define KAVLENUMDATA AVLRFOFFENUMDATA
+#define PKAVLENUMDATA PAVLRFOFFENUMDATA
+#define PKAVLCALLBACK PAVLRFOFFCALLBACK
+#define KAVL_RANGE 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (key1) > (key2) )
+#define KAVL_E( key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+#define KAVL_R_IS_IDENTICAL(key1B, key2B, key1E, key2E) ( (key1B) == (key2B) && (key1E) == (key2E) )
+#define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) ( (key1B) <= (key2E) && (key1E) >= (key2B) )
+#define KAVL_R_IS_IN_RANGE(key1B, key1E, key2) KAVL_R_IS_INTERSECTING(key1B, key2, key1E, key2)
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_Range.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_Enum.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlrgcptr.cpp b/src/VBox/Runtime/common/table/avlrgcptr.cpp
new file mode 100644
index 00000000..5fad033b
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlrgcptr.cpp
@@ -0,0 +1,94 @@
+/* $Id: avlrgcptr.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTGCPTR, range, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlrGCPtr##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLRGCPTRNODECORE
+#define PKAVLNODECORE PAVLRGCPTRNODECORE
+#define PPKAVLNODECORE PPAVLRGCPTRNODECORE
+#define KAVLKEY RTGCPTR
+#define PKAVLKEY PRTGCPTR
+#define KAVLENUMDATA AVLRGCPTRENUMDATA
+#define PKAVLENUMDATA PAVLRGCPTRENUMDATA
+#define PKAVLCALLBACK PAVLRGCPTRCALLBACK
+#define KAVL_RANGE 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (RTGCUINTPTR)(key1) > (RTGCUINTPTR)(key2) )
+#define KAVL_E( key1, key2) ( (RTGCUINTPTR)(key1) == (RTGCUINTPTR)(key2) )
+#define KAVL_NE(key1, key2) ( (RTGCUINTPTR)(key1) != (RTGCUINTPTR)(key2) )
+#define KAVL_R_IS_IDENTICAL(key1B, key2B, key1E, key2E) ( (RTGCUINTPTR)(key1B) == (RTGCUINTPTR)(key2B) && (RTGCUINTPTR)(key1E) == (RTGCUINTPTR)(key2E) )
+#define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) ( (RTGCUINTPTR)(key1B) <= (RTGCUINTPTR)(key2E) && (RTGCUINTPTR)(key1E) >= (RTGCUINTPTR)(key2B) )
+#define KAVL_R_IS_IN_RANGE(key1B, key1E, key2) KAVL_R_IS_INTERSECTING(key1B, key2, key1E, key2)
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_Range.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_Enum.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlrogcphys.cpp b/src/VBox/Runtime/common/table/avlrogcphys.cpp
new file mode 100644
index 00000000..e2b9c645
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlrogcphys.cpp
@@ -0,0 +1,95 @@
+/* $Id: avlrogcphys.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTGCPHYS, range, unique keys, offset pointers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlroGCPhys##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLROGCPHYSNODECORE
+#define PKAVLNODECORE PAVLROGCPHYSNODECORE
+#define PPKAVLNODECORE PPAVLROGCPHYSNODECORE
+#define KAVLKEY RTGCPHYS
+#define PKAVLKEY PRTGCPHYS
+#define KAVLENUMDATA AVLROGCPHYSENUMDATA
+#define PKAVLENUMDATA PAVLROGCPHYSENUMDATA
+#define PKAVLCALLBACK PAVLROGCPHYSCALLBACK
+#define KAVL_OFFSET 1
+#define KAVL_RANGE 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (key1) > (key2) )
+#define KAVL_E( key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+#define KAVL_R_IS_IDENTICAL(key1B, key2B, key1E, key2E) ( (key1B) == (key2B) && (key1E) == (key2E) )
+#define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) ( (key1B) <= (key2E) && (key1E) >= (key2B) )
+#define KAVL_R_IS_IN_RANGE(key1B, key1E, key2) KAVL_R_IS_INTERSECTING(key1B, key2, key1E, key2)
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_Range.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_Enum.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlrogcptr.cpp b/src/VBox/Runtime/common/table/avlrogcptr.cpp
new file mode 100644
index 00000000..0908743f
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlrogcptr.cpp
@@ -0,0 +1,95 @@
+/* $Id: avlrogcptr.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTGCPTR, range, unique keys, offset pointers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlroGCPtr##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLROGCPTRNODECORE
+#define PKAVLNODECORE PAVLROGCPTRNODECORE
+#define PPKAVLNODECORE PPAVLROGCPTRNODECORE
+#define KAVLKEY RTGCPTR
+#define PKAVLKEY PRTGCPTR
+#define KAVLENUMDATA AVLROGCPTRENUMDATA
+#define PKAVLENUMDATA PAVLROGCPTRENUMDATA
+#define PKAVLCALLBACK PAVLROGCPTRCALLBACK
+#define KAVL_OFFSET 1
+#define KAVL_RANGE 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (RTGCUINTPTR)(key1) > (RTGCUINTPTR)(key2) )
+#define KAVL_E( key1, key2) ( (RTGCUINTPTR)(key1) == (RTGCUINTPTR)(key2) )
+#define KAVL_NE(key1, key2) ( (RTGCUINTPTR)(key1) != (RTGCUINTPTR)(key2) )
+#define KAVL_R_IS_IDENTICAL(key1B, key2B, key1E, key2E) ( (RTGCUINTPTR)(key1B) == (RTGCUINTPTR)(key2B) && (RTGCUINTPTR)(key1E) == (RTGCUINTPTR)(key2E) )
+#define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) ( (RTGCUINTPTR)(key1B) <= (RTGCUINTPTR)(key2E) && (RTGCUINTPTR)(key1E) >= (RTGCUINTPTR)(key2B) )
+#define KAVL_R_IS_IN_RANGE(key1B, key1E, key2) KAVL_R_IS_INTERSECTING(key1B, key2, key1E, key2)
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_Range.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_Enum.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlroioport.cpp b/src/VBox/Runtime/common/table/avlroioport.cpp
new file mode 100644
index 00000000..07a300d3
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlroioport.cpp
@@ -0,0 +1,93 @@
+/* $Id: avlroioport.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTIOPORT, range, unique keys, offset pointers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlroIOPort##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLROIOPORTNODECORE
+#define PKAVLNODECORE PAVLROIOPORTNODECORE
+#define PPKAVLNODECORE PPAVLROIOPORTNODECORE
+#define KAVLKEY RTIOPORT
+#define PKAVLKEY PRTIOPORT
+#define KAVLENUMDATA AVLROIOPORTENUMDATA
+#define PKAVLENUMDATA PAVLROIOPORTENUMDATA
+#define PKAVLCALLBACK PAVLROIOPORTCALLBACK
+#define KAVL_OFFSET 1
+#define KAVL_RANGE 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (key1) > (key2) )
+#define KAVL_E( key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+#define KAVL_R_IS_IDENTICAL( key1B, key2B, key1E, key2E) ( (key1B) == (key2B) && (key1E) == (key2E) )
+#define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) ( (key1B) <= (key2E) && (key1E) >= (key2B) )
+#define KAVL_R_IS_IN_RANGE( key1B, key1E, key2) KAVL_R_IS_INTERSECTING(key1B, key2, key1E, key2)
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_Range.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlroogcptr.cpp b/src/VBox/Runtime/common/table/avlroogcptr.cpp
new file mode 100644
index 00000000..2e2d1ad2
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlroogcptr.cpp
@@ -0,0 +1,102 @@
+/* $Id: avlroogcptr.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTGCPTR, range, unique keys, overlapping ranges, offset pointers.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlrooGCPtr##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_EQUAL_ALLOWED 1 /* Duplicate keys allowed */
+#define KAVLNODECORE AVLROOGCPTRNODECORE
+#define PKAVLNODECORE PAVLROOGCPTRNODECORE
+#define PPKAVLNODECORE PPAVLROOGCPTRNODECORE
+#define KAVLKEY RTGCPTR
+#define PKAVLKEY PRTGCPTR
+#define KAVLENUMDATA AVLROOGCPTRENUMDATA
+#define PKAVLENUMDATA PAVLROOGCPTRENUMDATA
+#define PKAVLCALLBACK PAVLROOGCPTRCALLBACK
+#define KAVL_OFFSET 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (RTGCUINTPTR)(key1) > (RTGCUINTPTR)(key2) )
+#define KAVL_E( key1, key2) ( (RTGCUINTPTR)(key1) == (RTGCUINTPTR)(key2) )
+#define KAVL_NE(key1, key2) ( (RTGCUINTPTR)(key1) != (RTGCUINTPTR)(key2) )
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_Enum.cpp.h"
+
+/*
+ * Defined only for the range functions as we allow for overlapping ranges.
+ */
+#undef KAVL_R_IS_IDENTICAL
+#undef KAVL_R_IS_INTERSECTING
+#undef KAVL_R_IS_IN_RANGE
+#define KAVL_RANGE 1
+#define KAVL_R_IS_IDENTICAL( key1B, key2B, key1E, key2E) ( (RTGCUINTPTR)(key1B) == (RTGCUINTPTR)(key2B) && (RTGCUINTPTR)(key1E) == (RTGCUINTPTR)(key2E) )
+#define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) ( (RTGCUINTPTR)(key1B) <= (RTGCUINTPTR)(key2E) && (RTGCUINTPTR)(key1E) >= (RTGCUINTPTR)(key2B) )
+#define KAVL_R_IS_IN_RANGE( key1B, key1E, key2) KAVL_R_IS_INTERSECTING(key1B, key2, key1E, key2)
+#include "avl_Range.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlrpv.cpp b/src/VBox/Runtime/common/table/avlrpv.cpp
new file mode 100644
index 00000000..cd6d5d27
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlrpv.cpp
@@ -0,0 +1,93 @@
+/* $Id: avlrpv.cpp $ */
+/** @file
+ * IPRT - AVL tree, void *, range, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLPVInt.c,v 1.5 2003/02/13 02:02:35 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlrPV##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLRPVNODECORE
+#define PKAVLNODECORE PAVLRPVNODECORE
+#define PPKAVLNODECORE PPAVLRPVNODECORE
+#define KAVLKEY AVLRPVKEY
+#define PKAVLKEY PAVLRPVKEY
+#define KAVLENUMDATA AVLRPVENUMDATA
+#define PKAVLENUMDATA PAVLRPVENUMDATA
+#define PKAVLCALLBACK PAVLRPVCALLBACK
+#define KAVL_RANGE 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) ( (uintptr_t)(key1) > (uintptr_t)(key2) )
+#define KAVL_E(key1, key2) ( (uintptr_t)(key1) == (uintptr_t)(key2) )
+#define KAVL_NE(key1, key2) ( (uintptr_t)(key1) != (uintptr_t)(key2) )
+#define KAVL_R_IS_IDENTICAL(key1B, key2B, key1E, key2E) ( (uintptr_t)(key1B) == (uintptr_t)(key2B) && (uintptr_t)(key1E) == (uintptr_t)(key2E) )
+#define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) ( (uintptr_t)(key1B) <= (uintptr_t)(key2E) && (uintptr_t)(key1E) >= (uintptr_t)(key2B) )
+#define KAVL_R_IS_IN_RANGE(key1B, key1E, key2) KAVL_R_IS_INTERSECTING(key1B, key2, key1E, key2)
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_Range.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlru64.cpp b/src/VBox/Runtime/common/table/avlru64.cpp
new file mode 100644
index 00000000..18d3b143
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlru64.cpp
@@ -0,0 +1,93 @@
+/* $Id: avlru64.cpp $ */
+/** @file
+ * IPRT - AVL tree, void *, range, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.5 2003/02/13 02:02:35 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlrU64##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLRU64NODECORE
+#define PKAVLNODECORE PAVLRU64NODECORE
+#define PPKAVLNODECORE PPAVLRU64NODECORE
+#define KAVLKEY AVLRU64KEY
+#define PKAVLKEY PAVLRU64KEY
+#define KAVLENUMDATA AVLRU64ENUMDATA
+#define PKAVLENUMDATA PAVLRU64ENUMDATA
+#define PKAVLCALLBACK PAVLRU64CALLBACK
+#define KAVL_RANGE 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) ( (key1) > (key2) )
+#define KAVL_E(key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+#define KAVL_R_IS_IDENTICAL(key1B, key2B, key1E, key2E) ( (key1B) == (key2B) && (key1E) == (key2E) )
+#define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) ( (key1B) <= (key2E) && (key1E) >= (key2B) )
+#define KAVL_R_IS_IN_RANGE(key1B, key1E, key2) KAVL_R_IS_INTERSECTING(key1B, key2, key1E, key2)
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_Range.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlruintptr.cpp b/src/VBox/Runtime/common/table/avlruintptr.cpp
new file mode 100644
index 00000000..5c99a979
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlruintptr.cpp
@@ -0,0 +1,94 @@
+/* $Id: avlruintptr.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTUINTPTR, range, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlrUIntPtr##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLRUINTPTRNODECORE
+#define PKAVLNODECORE PAVLRUINTPTRNODECORE
+#define PPKAVLNODECORE PPAVLRUINTPTRNODECORE
+#define KAVLKEY RTUINTPTR
+#define PKAVLKEY PRTUINTPTR
+#define KAVLENUMDATA AVLRUINTPTRENUMDATA
+#define PKAVLENUMDATA PAVLRUINTPTRENUMDATA
+#define PKAVLCALLBACK PAVLRUINTPTRCALLBACK
+#define KAVL_RANGE 1
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (RTGCUINTPTR)(key1) > (RTGCUINTPTR)(key2) )
+#define KAVL_E( key1, key2) ( (RTGCUINTPTR)(key1) == (RTGCUINTPTR)(key2) )
+#define KAVL_NE(key1, key2) ( (RTGCUINTPTR)(key1) != (RTGCUINTPTR)(key2) )
+#define KAVL_R_IS_IDENTICAL(key1B, key2B, key1E, key2E) ( (RTGCUINTPTR)(key1B) == (RTGCUINTPTR)(key2B) && (RTGCUINTPTR)(key1E) == (RTGCUINTPTR)(key2E) )
+#define KAVL_R_IS_INTERSECTING(key1B, key2B, key1E, key2E) ( (RTGCUINTPTR)(key1B) <= (RTGCUINTPTR)(key2E) && (RTGCUINTPTR)(key1E) >= (RTGCUINTPTR)(key2B) )
+#define KAVL_R_IS_IN_RANGE(key1B, key1E, key2) KAVL_R_IS_INTERSECTING(key1B, key2, key1E, key2)
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_Range.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_Enum.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlu32.cpp b/src/VBox/Runtime/common/table/avlu32.cpp
new file mode 100644
index 00000000..35e30b67
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlu32.cpp
@@ -0,0 +1,88 @@
+/* $Id: avlu32.cpp $ */
+/** @file
+ * IPRT - AVL tree, uint32_t, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlU32##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLU32NODECORE
+#define PKAVLNODECORE PAVLU32NODECORE
+#define PPKAVLNODECORE PPAVLU32NODECORE
+#define KAVLKEY AVLU32KEY
+#define PKAVLKEY PAVLU32KEY
+#define KAVLENUMDATA AVLU32ENUMDATA
+#define PKAVLENUMDATA PAVLU32ENUMDATA
+#define PKAVLCALLBACK PAVLU32CALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) ( (key1) > (key2) )
+#define KAVL_E(key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlu64.cpp b/src/VBox/Runtime/common/table/avlu64.cpp
new file mode 100644
index 00000000..6f277021
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlu64.cpp
@@ -0,0 +1,88 @@
+/* $Id: avlu64.cpp $ */
+/** @file
+ * IPRT - AVL tree, uint32_t, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlU64##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLU64NODECORE
+#define PKAVLNODECORE PAVLU64NODECORE
+#define PPKAVLNODECORE PPAVLU64NODECORE
+#define KAVLKEY AVLU64KEY
+#define PKAVLKEY PAVLU64KEY
+#define KAVLENUMDATA AVLU64ENUMDATA
+#define PKAVLENUMDATA PAVLU64ENUMDATA
+#define PKAVLCALLBACK PAVLU64CALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) ( (key1) > (key2) )
+#define KAVL_E(key1, key2) ( (key1) == (key2) )
+#define KAVL_NE(key1, key2) ( (key1) != (key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avluintptr.cpp b/src/VBox/Runtime/common/table/avluintptr.cpp
new file mode 100644
index 00000000..f2b9fbd0
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avluintptr.cpp
@@ -0,0 +1,88 @@
+/* $Id: avluintptr.cpp $ */
+/** @file
+ * IPRT - AVL tree, RTUINTPTR, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlUIntPtr##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLUINTPTRNODECORE
+#define PKAVLNODECORE PAVLUINTPTRNODECORE
+#define PPKAVLNODECORE PPAVLUINTPTRNODECORE
+#define KAVLKEY RTUINTPTR
+#define PKAVLKEY PRTUINTPTR
+#define KAVLENUMDATA AVLUINTPTRENUMDATA
+#define PKAVLENUMDATA PAVLUINTPTRENUMDATA
+#define PKAVLCALLBACK PAVLUINTPTRCALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G( key1, key2) ( (RTGCUINTPTR)(key1) > (RTGCUINTPTR)(key2) )
+#define KAVL_E( key1, key2) ( (RTGCUINTPTR)(key1) == (RTGCUINTPTR)(key2) )
+#define KAVL_NE(key1, key2) ( (RTGCUINTPTR)(key1) != (RTGCUINTPTR)(key2) )
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_Enum.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/avlul.cpp b/src/VBox/Runtime/common/table/avlul.cpp
new file mode 100644
index 00000000..0626d4b3
--- /dev/null
+++ b/src/VBox/Runtime/common/table/avlul.cpp
@@ -0,0 +1,88 @@
+/* $Id: avlul.cpp $ */
+/** @file
+ * IPRT - AVL tree, unsigned long, unique keys.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef NOFILEID
+static const char szFileId[] = "Id: kAVLULInt.c,v 1.4 2003/02/13 02:02:38 bird Exp $";
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*
+ * AVL configuration.
+ */
+#define KAVL_FN(a) RTAvlUL##a
+#define KAVL_MAX_STACK 27 /* Up to 2^24 nodes. */
+#define KAVL_CHECK_FOR_EQUAL_INSERT 1 /* No duplicate keys! */
+#define KAVLNODECORE AVLULNODECORE
+#define PKAVLNODECORE PAVLULNODECORE
+#define PPKAVLNODECORE PPAVLULNODECORE
+#define KAVLKEY AVLULKEY
+#define PKAVLKEY PAVLULKEY
+#define KAVLENUMDATA AVLULENUMDATA
+#define PKAVLENUMDATA PAVLULENUMDATA
+#define PKAVLCALLBACK PAVLULCALLBACK
+
+
+/*
+ * AVL Compare macros
+ */
+#define KAVL_G(key1, key2) (key1 > key2)
+#define KAVL_E(key1, key2) (key1 == key2)
+#define KAVL_NE(key1, key2) (key1 != key2)
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/avl.h>
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+
+/*
+ * Include the code.
+ */
+#define SSToDS(ptr) ptr
+#define KMAX RT_MAX
+#define kASSERT Assert
+#include "avl_Base.cpp.h"
+#include "avl_Get.cpp.h"
+#include "avl_GetBestFit.cpp.h"
+#include "avl_RemoveBestFit.cpp.h"
+#include "avl_DoWithAll.cpp.h"
+#include "avl_Destroy.cpp.h"
+
diff --git a/src/VBox/Runtime/common/table/table.cpp b/src/VBox/Runtime/common/table/table.cpp
new file mode 100644
index 00000000..23ab77e1
--- /dev/null
+++ b/src/VBox/Runtime/common/table/table.cpp
@@ -0,0 +1,42 @@
+/* $Id: table.cpp $ */
+/** @file
+ * IPRT - Generic Tables/Containers, just a thought so far.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/table.h>
+
diff --git a/src/VBox/Runtime/common/time/Makefile.kup b/src/VBox/Runtime/common/time/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/time/Makefile.kup
diff --git a/src/VBox/Runtime/common/time/RTTimeFormatDurationEx.cpp b/src/VBox/Runtime/common/time/RTTimeFormatDurationEx.cpp
new file mode 100644
index 00000000..1b017816
--- /dev/null
+++ b/src/VBox/Runtime/common/time/RTTimeFormatDurationEx.cpp
@@ -0,0 +1,267 @@
+/* $Id: RTTimeFormatDurationEx.cpp $ */
+/** @file
+ * IPRT - RTTimeFormatInterval.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/time.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+static size_t rtTimeFormatDurationNumberEx(char *psz, uint32_t uValue, size_t cchValue)
+{
+ switch (cchValue)
+ {
+ case 10:
+ *psz++ = (uint8_t)(uValue / 1000000000) + '0';
+ uValue %= 1000000000;
+ RT_FALL_THROUGH();
+ case 9:
+ *psz++ = (uint8_t)(uValue / 100000000) + '0';
+ uValue %= 100000000;
+ RT_FALL_THROUGH();
+ case 8:
+ *psz++ = (uint8_t)(uValue / 10000000) + '0';
+ uValue %= 10000000;
+ RT_FALL_THROUGH();
+ case 7:
+ *psz++ = (uint8_t)(uValue / 1000000) + '0';
+ uValue %= 1000000;
+ RT_FALL_THROUGH();
+ case 6:
+ *psz++ = (uint8_t)(uValue / 100000) + '0';
+ uValue %= 100000;
+ RT_FALL_THROUGH();
+ case 5:
+ *psz++ = (uint8_t)(uValue / 10000) + '0';
+ uValue %= 10000;
+ RT_FALL_THROUGH();
+ case 4:
+ *psz++ = (uint8_t)(uValue / 1000) + '0';
+ uValue %= 1000;
+ RT_FALL_THROUGH();
+ case 3:
+ *psz++ = (uint8_t)(uValue / 100) + '0';
+ uValue %= 100;
+ RT_FALL_THROUGH();
+ case 2:
+ *psz++ = (uint8_t)(uValue / 10) + '0';
+ uValue %= 10;
+ RT_FALL_THROUGH();
+ case 1:
+ *psz++ = (uint8_t)uValue + '0';
+ break;
+ }
+ return cchValue;
+}
+
+
+static size_t rtTimeFormatDurationNumber(char *psz, uint32_t uValue)
+{
+ size_t cchValue;
+ if (uValue < 10)
+ cchValue = 1;
+ else if (uValue < 100)
+ cchValue = 2;
+ else if (uValue < 1000)
+ cchValue = 3;
+ else if (uValue < 10000)
+ cchValue = 4;
+ else if (uValue < 100000)
+ cchValue = 5;
+ else if (uValue < 1000000)
+ cchValue = 6;
+ else if (uValue < 10000000)
+ cchValue = 7;
+ else if (uValue < 100000000)
+ cchValue = 8;
+ else if (uValue < 1000000000)
+ cchValue = 9;
+ else
+ cchValue = 10;
+ return rtTimeFormatDurationNumberEx(psz, uValue, cchValue);
+}
+
+
+static ssize_t rtTimeFormatDurationCopyOutResult(char *pszDst, size_t cbDst, const char *pszValue, size_t cchValue)
+{
+ if (cbDst > cchValue)
+ {
+ memcpy(pszDst, pszValue, cchValue);
+ pszDst[cchValue] = '\0';
+ return cchValue;
+ }
+ if (cbDst)
+ {
+ memcpy(pszDst, pszValue, cbDst);
+ pszDst[cbDst - 1] = '\0';
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+
+
+/**
+ * Formats duration as best we can according to ISO-8601.
+ *
+ * The returned value is on the form "[-]PnnnnnWnDTnnHnnMnn.fffffffffS", where a
+ * sequence of 'n' can be between 1 and the given lenght, and all but the
+ * "nn.fffffffffS" part is optional and will only be outputted when the duration
+ * is sufficiently large. The code currently does not omit any inbetween
+ * elements other than the day count (D), so an exactly 7 day duration is
+ * formatted as "P1WT0H0M0.000000000S" when @a cFractionDigits is 9.
+ *
+ * @returns Number of characters in the output on success. VERR_BUFFER_OVEFLOW
+ * on failure.
+ * @retval VERR_OUT_OF_RANGE if @a cFractionDigits is too large.
+ * @param pszDst Pointer to the output buffer. In case of overflow,
+ * the max number of characters will be written and
+ * zero terminated, provided @a cbDst isn't zero.
+ * @param cbDst The size of the output buffer.
+ * @param pDuration The duration to format.
+ * @param cFractionDigits Number of digits in the second fraction part. Zero
+ * for whole no fraction. Max is 9 (nano seconds).
+ */
+RTDECL(ssize_t) RTTimeFormatDurationEx(char *pszDst, size_t cbDst, PCRTTIMESPEC pDuration, uint32_t cFractionDigits)
+{
+ AssertReturn(cFractionDigits <= 9, VERR_OUT_OF_RANGE);
+ AssertReturn(cbDst != 0, VERR_BUFFER_OVERFLOW);
+
+ /*
+ * Get the seconds and .
+ */
+ int64_t cNanoSecsSigned = RTTimeSpecGetNano(pDuration);
+
+ /* Special case: zero interval */
+ if (cNanoSecsSigned == 0)
+ return rtTimeFormatDurationCopyOutResult(pszDst, cbDst, RT_STR_TUPLE("PT0S"));
+
+ char szTmp[64];
+ size_t offTmp = 0;
+
+ /* Negative intervals aren't really allowed by the standard, but we slap a
+ minus in from of the 'P' and get on with it. */
+ if (cNanoSecsSigned < 0)
+ {
+ szTmp[offTmp++] = '-';
+ cNanoSecsSigned = -cNanoSecsSigned;
+ }
+ uint64_t cNanoSecs = (uint64_t)cNanoSecsSigned;
+
+ /* Emit the duration indicator: */
+ szTmp[offTmp++] = 'P';
+ size_t const offPostP = offTmp;
+
+ /* Any full weeks? */
+ if (cNanoSecs >= RT_NS_1WEEK)
+ {
+ uint64_t const cWeeks = cNanoSecs / RT_NS_1WEEK; /* (the max value here is 15250) */
+ cNanoSecs %= RT_NS_1WEEK;
+ offTmp += rtTimeFormatDurationNumber(&szTmp[offTmp], (uint32_t)cWeeks);
+ szTmp[offTmp++] = 'W';
+ }
+
+ /* Any full days?*/
+ if (cNanoSecs >= RT_NS_1DAY)
+ {
+ uint8_t const cDays = (uint8_t)(cNanoSecs / RT_NS_1DAY);
+ cNanoSecs %= RT_NS_1DAY;
+ szTmp[offTmp++] = '0' + cDays;
+ szTmp[offTmp++] = 'D';
+ }
+
+ szTmp[offTmp++] = 'T';
+
+ /* Hours: */
+ if (cNanoSecs >= RT_NS_1HOUR || offTmp > offPostP + 1)
+ {
+ uint8_t const cHours = (uint8_t)(cNanoSecs / RT_NS_1HOUR);
+ cNanoSecs %= RT_NS_1HOUR;
+ offTmp += rtTimeFormatDurationNumber(&szTmp[offTmp], cHours);
+ szTmp[offTmp++] = 'H';
+ }
+
+ /* Minutes: */
+ if (cNanoSecs >= RT_NS_1MIN || offTmp > offPostP + 1)
+ {
+ uint8_t const cMins = (uint8_t)(cNanoSecs / RT_NS_1MIN);
+ cNanoSecs %= RT_NS_1MIN;
+ offTmp += rtTimeFormatDurationNumber(&szTmp[offTmp], cMins);
+ szTmp[offTmp++] = 'M';
+ }
+
+ /* Seconds: */
+ uint8_t const cSecs = (uint8_t)(cNanoSecs / RT_NS_1SEC);
+ cNanoSecs %= RT_NS_1SEC;
+ offTmp += rtTimeFormatDurationNumber(&szTmp[offTmp], cSecs);
+ if (cFractionDigits > 0)
+ {
+ szTmp[offTmp++] = '.';
+ static uint32_t const s_auFactors[9] = { 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
+ offTmp += rtTimeFormatDurationNumberEx(&szTmp[offTmp], (uint32_t)(cNanoSecs / s_auFactors[cFractionDigits - 1]),
+ cFractionDigits);
+ }
+ szTmp[offTmp++] = 'S';
+ szTmp[offTmp] = '\0';
+
+ return rtTimeFormatDurationCopyOutResult(pszDst, cbDst, szTmp, offTmp);
+}
+RT_EXPORT_SYMBOL(RTTimeFormatDurationEx);
+
+
+/**
+ * Formats duration as best we can according to ISO-8601, with no fraction.
+ *
+ * See RTTimeFormatDurationEx for details.
+ *
+ * @returns Number of characters in the output on success. VERR_BUFFER_OVEFLOW
+ * on failure.
+ * @param pszDst Pointer to the output buffer. In case of overflow,
+ * the max number of characters will be written and
+ * zero terminated, provided @a cbDst isn't zero.
+ * @param cbDst The size of the output buffer.
+ * @param pDuration The duration to format.
+ */
+RTDECL(int) RTTimeFormatDuration(char *pszDst, size_t cbDst, PCRTTIMESPEC pDuration)
+{
+ return RTTimeFormatDurationEx(pszDst, cbDst, pDuration, 0);
+}
+RT_EXPORT_SYMBOL(RTTimeFormatDuration);
+
diff --git a/src/VBox/Runtime/common/time/time.cpp b/src/VBox/Runtime/common/time/time.cpp
new file mode 100644
index 00000000..193bbd29
--- /dev/null
+++ b/src/VBox/Runtime/common/time/time.cpp
@@ -0,0 +1,1492 @@
+/* $Id: time.cpp $ */
+/** @file
+ * IPRT - Time.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_TIME
+#include <iprt/time.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+#include "internal/time.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The max year we possibly could implode. */
+#define RTTIME_MAX_YEAR (292 + 1970)
+/** The min year we possibly could implode. */
+#define RTTIME_MIN_YEAR (-293 + 1970)
+
+/** The max day supported by our time representation. (2262-04-11T23-47-16.854775807) */
+#define RTTIME_MAX_DAY (365*292+71 + 101-1)
+/** The min day supported by our time representation. (1677-09-21T00-12-43.145224192) */
+#define RTTIME_MIN_DAY (365*-293-70 + 264-1)
+
+/** The max nano second into the max day. (2262-04-11T23-47-16.854775807) */
+#define RTTIME_MAX_DAY_NANO ( INT64_C(1000000000) * (23*3600 + 47*60 + 16) + 854775807 )
+/** The min nano second into the min day. (1677-09-21T00-12-43.145224192) */
+#define RTTIME_MIN_DAY_NANO ( INT64_C(1000000000) * (00*3600 + 12*60 + 43) + 145224192 )
+
+/**
+ * Asserts that a_pTime is normalized.
+ */
+#define RTTIME_ASSERT_NORMALIZED(a_pTime) \
+ do \
+ { \
+ Assert(RT_ABS((a_pTime)->offUTC) <= 840); \
+ Assert((a_pTime)->u32Nanosecond < 1000000000); \
+ Assert((a_pTime)->u8Second < 60); \
+ Assert((a_pTime)->u8Minute < 60); \
+ Assert((a_pTime)->u8Hour < 24); \
+ Assert((a_pTime)->u8Month >= 1 && (a_pTime)->u8Month <= 12); \
+ Assert((a_pTime)->u8WeekDay < 7); \
+ Assert((a_pTime)->u16YearDay >= 1); \
+ Assert((a_pTime)->u16YearDay <= (rtTimeIsLeapYear((a_pTime)->i32Year) ? 366 : 365)); \
+ Assert((a_pTime)->u8MonthDay >= 1 && (a_pTime)->u8MonthDay <= 31); \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Days per month in a common year.
+ */
+static const uint8_t g_acDaysInMonths[12] =
+{
+ /*Jan Feb Mar Arp May Jun Jul Aug Sep Oct Nov Dec */
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/**
+ * Days per month in a leap year.
+ */
+static const uint8_t g_acDaysInMonthsLeap[12] =
+{
+ /*Jan Feb Mar Arp May Jun Jul Aug Sep Oct Nov Dec */
+ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/**
+ * The day of year for each month in a common year.
+ */
+static const uint16_t g_aiDayOfYear[12 + 1] =
+{
+ 1, /* Jan */
+ 1+31, /* Feb */
+ 1+31+28, /* Mar */
+ 1+31+28+31, /* Apr */
+ 1+31+28+31+30, /* May */
+ 1+31+28+31+30+31, /* Jun */
+ 1+31+28+31+30+31+30, /* Jul */
+ 1+31+28+31+30+31+30+31, /* Aug */
+ 1+31+28+31+30+31+30+31+31, /* Sep */
+ 1+31+28+31+30+31+30+31+31+30, /* Oct */
+ 1+31+28+31+30+31+30+31+31+30+31, /* Nov */
+ 1+31+28+31+30+31+30+31+31+30+31+30, /* Dec */
+ 1+31+28+31+30+31+30+31+31+30+31+30+31
+};
+
+/**
+ * The day of year for each month in a leap year.
+ */
+static const uint16_t g_aiDayOfYearLeap[12 + 1] =
+{
+ 1, /* Jan */
+ 1+31, /* Feb */
+ 1+31+29, /* Mar */
+ 1+31+29+31, /* Apr */
+ 1+31+29+31+30, /* May */
+ 1+31+29+31+30+31, /* Jun */
+ 1+31+29+31+30+31+30, /* Jul */
+ 1+31+29+31+30+31+30+31, /* Aug */
+ 1+31+29+31+30+31+30+31+31, /* Sep */
+ 1+31+29+31+30+31+30+31+31+30, /* Oct */
+ 1+31+29+31+30+31+30+31+31+30+31, /* Nov */
+ 1+31+29+31+30+31+30+31+31+30+31+30, /* Dec */
+ 1+31+29+31+30+31+30+31+31+30+31+30+31
+};
+
+/** The index of 1970 in g_aoffYear */
+#define OFF_YEAR_IDX_EPOCH 300
+/** The year of the first index. */
+#define OFF_YEAR_IDX_0_YEAR 1670
+
+/**
+ * The number of days the 1st of January a year is offseted from 1970-01-01.
+ */
+static const int32_t g_aoffYear[] =
+{
+/*1670:*/ 365*-300+-72, 365*-299+-72, 365*-298+-72, 365*-297+-71, 365*-296+-71, 365*-295+-71, 365*-294+-71, 365*-293+-70, 365*-292+-70, 365*-291+-70,
+/*1680:*/ 365*-290+-70, 365*-289+-69, 365*-288+-69, 365*-287+-69, 365*-286+-69, 365*-285+-68, 365*-284+-68, 365*-283+-68, 365*-282+-68, 365*-281+-67,
+/*1690:*/ 365*-280+-67, 365*-279+-67, 365*-278+-67, 365*-277+-66, 365*-276+-66, 365*-275+-66, 365*-274+-66, 365*-273+-65, 365*-272+-65, 365*-271+-65,
+/*1700:*/ 365*-270+-65, 365*-269+-65, 365*-268+-65, 365*-267+-65, 365*-266+-65, 365*-265+-64, 365*-264+-64, 365*-263+-64, 365*-262+-64, 365*-261+-63,
+/*1710:*/ 365*-260+-63, 365*-259+-63, 365*-258+-63, 365*-257+-62, 365*-256+-62, 365*-255+-62, 365*-254+-62, 365*-253+-61, 365*-252+-61, 365*-251+-61,
+/*1720:*/ 365*-250+-61, 365*-249+-60, 365*-248+-60, 365*-247+-60, 365*-246+-60, 365*-245+-59, 365*-244+-59, 365*-243+-59, 365*-242+-59, 365*-241+-58,
+/*1730:*/ 365*-240+-58, 365*-239+-58, 365*-238+-58, 365*-237+-57, 365*-236+-57, 365*-235+-57, 365*-234+-57, 365*-233+-56, 365*-232+-56, 365*-231+-56,
+/*1740:*/ 365*-230+-56, 365*-229+-55, 365*-228+-55, 365*-227+-55, 365*-226+-55, 365*-225+-54, 365*-224+-54, 365*-223+-54, 365*-222+-54, 365*-221+-53,
+/*1750:*/ 365*-220+-53, 365*-219+-53, 365*-218+-53, 365*-217+-52, 365*-216+-52, 365*-215+-52, 365*-214+-52, 365*-213+-51, 365*-212+-51, 365*-211+-51,
+/*1760:*/ 365*-210+-51, 365*-209+-50, 365*-208+-50, 365*-207+-50, 365*-206+-50, 365*-205+-49, 365*-204+-49, 365*-203+-49, 365*-202+-49, 365*-201+-48,
+/*1770:*/ 365*-200+-48, 365*-199+-48, 365*-198+-48, 365*-197+-47, 365*-196+-47, 365*-195+-47, 365*-194+-47, 365*-193+-46, 365*-192+-46, 365*-191+-46,
+/*1780:*/ 365*-190+-46, 365*-189+-45, 365*-188+-45, 365*-187+-45, 365*-186+-45, 365*-185+-44, 365*-184+-44, 365*-183+-44, 365*-182+-44, 365*-181+-43,
+/*1790:*/ 365*-180+-43, 365*-179+-43, 365*-178+-43, 365*-177+-42, 365*-176+-42, 365*-175+-42, 365*-174+-42, 365*-173+-41, 365*-172+-41, 365*-171+-41,
+/*1800:*/ 365*-170+-41, 365*-169+-41, 365*-168+-41, 365*-167+-41, 365*-166+-41, 365*-165+-40, 365*-164+-40, 365*-163+-40, 365*-162+-40, 365*-161+-39,
+/*1810:*/ 365*-160+-39, 365*-159+-39, 365*-158+-39, 365*-157+-38, 365*-156+-38, 365*-155+-38, 365*-154+-38, 365*-153+-37, 365*-152+-37, 365*-151+-37,
+/*1820:*/ 365*-150+-37, 365*-149+-36, 365*-148+-36, 365*-147+-36, 365*-146+-36, 365*-145+-35, 365*-144+-35, 365*-143+-35, 365*-142+-35, 365*-141+-34,
+/*1830:*/ 365*-140+-34, 365*-139+-34, 365*-138+-34, 365*-137+-33, 365*-136+-33, 365*-135+-33, 365*-134+-33, 365*-133+-32, 365*-132+-32, 365*-131+-32,
+/*1840:*/ 365*-130+-32, 365*-129+-31, 365*-128+-31, 365*-127+-31, 365*-126+-31, 365*-125+-30, 365*-124+-30, 365*-123+-30, 365*-122+-30, 365*-121+-29,
+/*1850:*/ 365*-120+-29, 365*-119+-29, 365*-118+-29, 365*-117+-28, 365*-116+-28, 365*-115+-28, 365*-114+-28, 365*-113+-27, 365*-112+-27, 365*-111+-27,
+/*1860:*/ 365*-110+-27, 365*-109+-26, 365*-108+-26, 365*-107+-26, 365*-106+-26, 365*-105+-25, 365*-104+-25, 365*-103+-25, 365*-102+-25, 365*-101+-24,
+/*1870:*/ 365*-100+-24, 365* -99+-24, 365* -98+-24, 365* -97+-23, 365* -96+-23, 365* -95+-23, 365* -94+-23, 365* -93+-22, 365* -92+-22, 365* -91+-22,
+/*1880:*/ 365* -90+-22, 365* -89+-21, 365* -88+-21, 365* -87+-21, 365* -86+-21, 365* -85+-20, 365* -84+-20, 365* -83+-20, 365* -82+-20, 365* -81+-19,
+/*1890:*/ 365* -80+-19, 365* -79+-19, 365* -78+-19, 365* -77+-18, 365* -76+-18, 365* -75+-18, 365* -74+-18, 365* -73+-17, 365* -72+-17, 365* -71+-17,
+/*1900:*/ 365* -70+-17, 365* -69+-17, 365* -68+-17, 365* -67+-17, 365* -66+-17, 365* -65+-16, 365* -64+-16, 365* -63+-16, 365* -62+-16, 365* -61+-15,
+/*1910:*/ 365* -60+-15, 365* -59+-15, 365* -58+-15, 365* -57+-14, 365* -56+-14, 365* -55+-14, 365* -54+-14, 365* -53+-13, 365* -52+-13, 365* -51+-13,
+/*1920:*/ 365* -50+-13, 365* -49+-12, 365* -48+-12, 365* -47+-12, 365* -46+-12, 365* -45+-11, 365* -44+-11, 365* -43+-11, 365* -42+-11, 365* -41+-10,
+/*1930:*/ 365* -40+-10, 365* -39+-10, 365* -38+-10, 365* -37+-9 , 365* -36+-9 , 365* -35+-9 , 365* -34+-9 , 365* -33+-8 , 365* -32+-8 , 365* -31+-8 ,
+/*1940:*/ 365* -30+-8 , 365* -29+-7 , 365* -28+-7 , 365* -27+-7 , 365* -26+-7 , 365* -25+-6 , 365* -24+-6 , 365* -23+-6 , 365* -22+-6 , 365* -21+-5 ,
+/*1950:*/ 365* -20+-5 , 365* -19+-5 , 365* -18+-5 , 365* -17+-4 , 365* -16+-4 , 365* -15+-4 , 365* -14+-4 , 365* -13+-3 , 365* -12+-3 , 365* -11+-3 ,
+/*1960:*/ 365* -10+-3 , 365* -9+-2 , 365* -8+-2 , 365* -7+-2 , 365* -6+-2 , 365* -5+-1 , 365* -4+-1 , 365* -3+-1 , 365* -2+-1 , 365* -1+0 ,
+/*1970:*/ 365* 0+0 , 365* 1+0 , 365* 2+0 , 365* 3+1 , 365* 4+1 , 365* 5+1 , 365* 6+1 , 365* 7+2 , 365* 8+2 , 365* 9+2 ,
+/*1980:*/ 365* 10+2 , 365* 11+3 , 365* 12+3 , 365* 13+3 , 365* 14+3 , 365* 15+4 , 365* 16+4 , 365* 17+4 , 365* 18+4 , 365* 19+5 ,
+/*1990:*/ 365* 20+5 , 365* 21+5 , 365* 22+5 , 365* 23+6 , 365* 24+6 , 365* 25+6 , 365* 26+6 , 365* 27+7 , 365* 28+7 , 365* 29+7 ,
+/*2000:*/ 365* 30+7 , 365* 31+8 , 365* 32+8 , 365* 33+8 , 365* 34+8 , 365* 35+9 , 365* 36+9 , 365* 37+9 , 365* 38+9 , 365* 39+10 ,
+/*2010:*/ 365* 40+10 , 365* 41+10 , 365* 42+10 , 365* 43+11 , 365* 44+11 , 365* 45+11 , 365* 46+11 , 365* 47+12 , 365* 48+12 , 365* 49+12 ,
+/*2020:*/ 365* 50+12 , 365* 51+13 , 365* 52+13 , 365* 53+13 , 365* 54+13 , 365* 55+14 , 365* 56+14 , 365* 57+14 , 365* 58+14 , 365* 59+15 ,
+/*2030:*/ 365* 60+15 , 365* 61+15 , 365* 62+15 , 365* 63+16 , 365* 64+16 , 365* 65+16 , 365* 66+16 , 365* 67+17 , 365* 68+17 , 365* 69+17 ,
+/*2040:*/ 365* 70+17 , 365* 71+18 , 365* 72+18 , 365* 73+18 , 365* 74+18 , 365* 75+19 , 365* 76+19 , 365* 77+19 , 365* 78+19 , 365* 79+20 ,
+/*2050:*/ 365* 80+20 , 365* 81+20 , 365* 82+20 , 365* 83+21 , 365* 84+21 , 365* 85+21 , 365* 86+21 , 365* 87+22 , 365* 88+22 , 365* 89+22 ,
+/*2060:*/ 365* 90+22 , 365* 91+23 , 365* 92+23 , 365* 93+23 , 365* 94+23 , 365* 95+24 , 365* 96+24 , 365* 97+24 , 365* 98+24 , 365* 99+25 ,
+/*2070:*/ 365* 100+25 , 365* 101+25 , 365* 102+25 , 365* 103+26 , 365* 104+26 , 365* 105+26 , 365* 106+26 , 365* 107+27 , 365* 108+27 , 365* 109+27 ,
+/*2080:*/ 365* 110+27 , 365* 111+28 , 365* 112+28 , 365* 113+28 , 365* 114+28 , 365* 115+29 , 365* 116+29 , 365* 117+29 , 365* 118+29 , 365* 119+30 ,
+/*2090:*/ 365* 120+30 , 365* 121+30 , 365* 122+30 , 365* 123+31 , 365* 124+31 , 365* 125+31 , 365* 126+31 , 365* 127+32 , 365* 128+32 , 365* 129+32 ,
+/*2100:*/ 365* 130+32 , 365* 131+32 , 365* 132+32 , 365* 133+32 , 365* 134+32 , 365* 135+33 , 365* 136+33 , 365* 137+33 , 365* 138+33 , 365* 139+34 ,
+/*2110:*/ 365* 140+34 , 365* 141+34 , 365* 142+34 , 365* 143+35 , 365* 144+35 , 365* 145+35 , 365* 146+35 , 365* 147+36 , 365* 148+36 , 365* 149+36 ,
+/*2120:*/ 365* 150+36 , 365* 151+37 , 365* 152+37 , 365* 153+37 , 365* 154+37 , 365* 155+38 , 365* 156+38 , 365* 157+38 , 365* 158+38 , 365* 159+39 ,
+/*2130:*/ 365* 160+39 , 365* 161+39 , 365* 162+39 , 365* 163+40 , 365* 164+40 , 365* 165+40 , 365* 166+40 , 365* 167+41 , 365* 168+41 , 365* 169+41 ,
+/*2140:*/ 365* 170+41 , 365* 171+42 , 365* 172+42 , 365* 173+42 , 365* 174+42 , 365* 175+43 , 365* 176+43 , 365* 177+43 , 365* 178+43 , 365* 179+44 ,
+/*2150:*/ 365* 180+44 , 365* 181+44 , 365* 182+44 , 365* 183+45 , 365* 184+45 , 365* 185+45 , 365* 186+45 , 365* 187+46 , 365* 188+46 , 365* 189+46 ,
+/*2160:*/ 365* 190+46 , 365* 191+47 , 365* 192+47 , 365* 193+47 , 365* 194+47 , 365* 195+48 , 365* 196+48 , 365* 197+48 , 365* 198+48 , 365* 199+49 ,
+/*2170:*/ 365* 200+49 , 365* 201+49 , 365* 202+49 , 365* 203+50 , 365* 204+50 , 365* 205+50 , 365* 206+50 , 365* 207+51 , 365* 208+51 , 365* 209+51 ,
+/*2180:*/ 365* 210+51 , 365* 211+52 , 365* 212+52 , 365* 213+52 , 365* 214+52 , 365* 215+53 , 365* 216+53 , 365* 217+53 , 365* 218+53 , 365* 219+54 ,
+/*2190:*/ 365* 220+54 , 365* 221+54 , 365* 222+54 , 365* 223+55 , 365* 224+55 , 365* 225+55 , 365* 226+55 , 365* 227+56 , 365* 228+56 , 365* 229+56 ,
+/*2200:*/ 365* 230+56 , 365* 231+56 , 365* 232+56 , 365* 233+56 , 365* 234+56 , 365* 235+57 , 365* 236+57 , 365* 237+57 , 365* 238+57 , 365* 239+58 ,
+/*2210:*/ 365* 240+58 , 365* 241+58 , 365* 242+58 , 365* 243+59 , 365* 244+59 , 365* 245+59 , 365* 246+59 , 365* 247+60 , 365* 248+60 , 365* 249+60 ,
+/*2220:*/ 365* 250+60 , 365* 251+61 , 365* 252+61 , 365* 253+61 , 365* 254+61 , 365* 255+62 , 365* 256+62 , 365* 257+62 , 365* 258+62 , 365* 259+63 ,
+/*2230:*/ 365* 260+63 , 365* 261+63 , 365* 262+63 , 365* 263+64 , 365* 264+64 , 365* 265+64 , 365* 266+64 , 365* 267+65 , 365* 268+65 , 365* 269+65 ,
+/*2240:*/ 365* 270+65 , 365* 271+66 , 365* 272+66 , 365* 273+66 , 365* 274+66 , 365* 275+67 , 365* 276+67 , 365* 277+67 , 365* 278+67 , 365* 279+68 ,
+/*2250:*/ 365* 280+68 , 365* 281+68 , 365* 282+68 , 365* 283+69 , 365* 284+69 , 365* 285+69 , 365* 286+69 , 365* 287+70 , 365* 288+70 , 365* 289+70 ,
+/*2260:*/ 365* 290+70 , 365* 291+71 , 365* 292+71 , 365* 293+71 , 365* 294+71 , 365* 295+72 , 365* 296+72 , 365* 297+72 , 365* 298+72 , 365* 299+73
+};
+
+/* generator code:
+#include <stdio.h>
+bool isLeapYear(int iYear)
+{
+ return iYear % 4 == 0 && (iYear % 100 != 0 || iYear % 400 == 0);
+}
+void printYear(int iYear, int iLeap)
+{
+ if (!(iYear % 10))
+ printf("\n/" "*%d:*" "/", iYear + 1970);
+ printf(" 365*%4d+%-3d,", iYear, iLeap);
+}
+int main()
+{
+ int iYear = 0;
+ int iLeap = 0;
+ while (iYear > -300)
+ iLeap -= isLeapYear(1970 + --iYear);
+ while (iYear < 300)
+ {
+ printYear(iYear, iLeap);
+ iLeap += isLeapYear(1970 + iYear++);
+ }
+ printf("\n");
+ return 0;
+}
+*/
+
+/** RFC-1123 week day names. */
+static const char * const g_apszWeekDays[7] =
+{
+ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
+};
+/** RFC-1123 month of the year names. */
+static const char * const g_apszMonths[1+12] =
+{
+ "000", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+
+/**
+ * Checks if a year is a leap year or not.
+ *
+ * @returns true if it's a leap year.
+ * @returns false if it's a common year.
+ * @param i32Year The year in question.
+ */
+DECLINLINE(bool) rtTimeIsLeapYear(int32_t i32Year)
+{
+ return i32Year % 4 == 0
+ && ( i32Year % 100 != 0
+ || i32Year % 400 == 0);
+}
+
+
+RTDECL(bool) RTTimeIsLeapYear(int32_t i32Year)
+{
+ return rtTimeIsLeapYear(i32Year);
+}
+RT_EXPORT_SYMBOL(RTTimeIsLeapYear);
+
+
+RTDECL(PRTTIME) RTTimeExplode(PRTTIME pTime, PCRTTIMESPEC pTimeSpec)
+{
+ int64_t i64Div;
+ int32_t i32Div;
+ int32_t i32Rem;
+ unsigned iYear;
+ const uint16_t *paiDayOfYear;
+ int iMonth;
+
+ AssertPtr(pTime);
+ AssertPtr(pTimeSpec);
+
+ /*
+ * The simple stuff first.
+ */
+ pTime->fFlags = RTTIME_FLAGS_TYPE_UTC;
+ i64Div = pTimeSpec->i64NanosecondsRelativeToUnixEpoch;
+ i32Rem = (int32_t)(i64Div % 1000000000);
+ i64Div /= 1000000000;
+ if (i32Rem < 0)
+ {
+ i32Rem += 1000000000;
+ i64Div--;
+ }
+ pTime->u32Nanosecond = i32Rem;
+
+ /* second */
+ i32Rem = (int32_t)(i64Div % 60);
+ i64Div /= 60;
+ if (i32Rem < 0)
+ {
+ i32Rem += 60;
+ i64Div--;
+ }
+ pTime->u8Second = i32Rem;
+
+ /* minute */
+ i32Div = (int32_t)i64Div; /* 60,000,000,000 > 33bit, so 31bit suffices. */
+ i32Rem = i32Div % 60;
+ i32Div /= 60;
+ if (i32Rem < 0)
+ {
+ i32Rem += 60;
+ i32Div--;
+ }
+ pTime->u8Minute = i32Rem;
+
+ /* hour */
+ i32Rem = i32Div % 24;
+ i32Div /= 24; /* days relative to 1970-01-01 */
+ if (i32Rem < 0)
+ {
+ i32Rem += 24;
+ i32Div--;
+ }
+ pTime->u8Hour = i32Rem;
+
+ /* weekday - 1970-01-01 was a Thursday (3) */
+ pTime->u8WeekDay = ((int)(i32Div % 7) + 3 + 7) % 7;
+
+ /*
+ * We've now got a number of days relative to 1970-01-01.
+ * To get the correct year number we have to mess with leap years. Fortunately,
+ * the representation we've got only supports a few hundred years, so we can
+ * generate a table and perform a simple two way search from the modulus 365 derived.
+ */
+ iYear = OFF_YEAR_IDX_EPOCH + i32Div / 365;
+ while (g_aoffYear[iYear + 1] <= i32Div)
+ iYear++;
+ while (g_aoffYear[iYear] > i32Div)
+ iYear--;
+ pTime->i32Year = iYear + OFF_YEAR_IDX_0_YEAR;
+ i32Div -= g_aoffYear[iYear];
+ pTime->u16YearDay = i32Div + 1;
+
+ /*
+ * Figuring out the month is done in a manner similar to the year, only here we
+ * ensure that the index is matching or too small.
+ */
+ if (rtTimeIsLeapYear(pTime->i32Year))
+ {
+ pTime->fFlags |= RTTIME_FLAGS_LEAP_YEAR;
+ paiDayOfYear = &g_aiDayOfYearLeap[0];
+ }
+ else
+ {
+ pTime->fFlags |= RTTIME_FLAGS_COMMON_YEAR;
+ paiDayOfYear = &g_aiDayOfYear[0];
+ }
+ iMonth = i32Div / 32;
+ i32Div++;
+ while (paiDayOfYear[iMonth + 1] <= i32Div)
+ iMonth++;
+ pTime->u8Month = iMonth + 1;
+ i32Div -= paiDayOfYear[iMonth];
+ pTime->u8MonthDay = i32Div + 1;
+
+ /* This is for UTC timespecs, so, no offset. */
+ pTime->offUTC = 0;
+
+ return pTime;
+}
+RT_EXPORT_SYMBOL(RTTimeExplode);
+
+
+RTDECL(PRTTIMESPEC) RTTimeImplode(PRTTIMESPEC pTimeSpec, PCRTTIME pTime)
+{
+ int32_t i32Days;
+ uint32_t u32Secs;
+ int64_t i64Nanos;
+
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pTimeSpec, NULL);
+ AssertPtrReturn(pTime, NULL);
+ AssertReturn(pTime->u32Nanosecond < 1000000000, NULL);
+ AssertReturn(pTime->u8Second < 60, NULL);
+ AssertReturn(pTime->u8Minute < 60, NULL);
+ AssertReturn(pTime->u8Hour < 24, NULL);
+ AssertReturn(pTime->u16YearDay >= 1, NULL);
+ AssertReturn(pTime->u16YearDay <= (rtTimeIsLeapYear(pTime->i32Year) ? 366 : 365), NULL);
+ AssertMsgReturn(pTime->i32Year <= RTTIME_MAX_YEAR && pTime->i32Year >= RTTIME_MIN_YEAR, ("%RI32\n", pTime->i32Year), NULL);
+ Assert(pTime->offUTC >= -840 && pTime->offUTC <= 840);
+
+ /*
+ * Do the conversion to nanoseconds.
+ */
+ i32Days = g_aoffYear[pTime->i32Year - OFF_YEAR_IDX_0_YEAR]
+ + pTime->u16YearDay - 1;
+ AssertMsgReturn(i32Days <= RTTIME_MAX_DAY && i32Days >= RTTIME_MIN_DAY, ("%RI32\n", i32Days), NULL);
+
+ u32Secs = pTime->u8Second
+ + pTime->u8Minute * 60
+ + pTime->u8Hour * 3600;
+ i64Nanos = (uint64_t)pTime->u32Nanosecond
+ + u32Secs * UINT64_C(1000000000);
+ AssertMsgReturn(i32Days != RTTIME_MAX_DAY || i64Nanos <= RTTIME_MAX_DAY_NANO, ("%RI64\n", i64Nanos), NULL);
+ AssertMsgReturn(i32Days != RTTIME_MIN_DAY || i64Nanos >= RTTIME_MIN_DAY_NANO, ("%RI64\n", i64Nanos), NULL);
+
+ i64Nanos += i32Days * UINT64_C(86400000000000);
+ if ((pTime->fFlags & RTTIME_FLAGS_TYPE_MASK) == RTTIME_FLAGS_TYPE_LOCAL)
+ i64Nanos -= pTime->offUTC * RT_NS_1MIN;
+
+ pTimeSpec->i64NanosecondsRelativeToUnixEpoch = i64Nanos;
+ return pTimeSpec;
+}
+RT_EXPORT_SYMBOL(RTTimeImplode);
+
+
+/**
+ * Internal worker for RTTimeNormalize and RTTimeLocalNormalize.
+ */
+static PRTTIME rtTimeNormalizeInternal(PRTTIME pTime)
+{
+ unsigned uSecond;
+ unsigned uMinute;
+ unsigned uHour;
+ bool fLeapYear;
+
+ /*
+ * Fix the YearDay and Month/MonthDay.
+ */
+ fLeapYear = rtTimeIsLeapYear(pTime->i32Year);
+ if (!pTime->u16YearDay)
+ {
+ /*
+ * The Month+MonthDay must present, overflow adjust them and calc the year day.
+ */
+ AssertMsgReturn( pTime->u8Month
+ && pTime->u8MonthDay,
+ ("date=%d-%d-%d\n", pTime->i32Year, pTime->u8Month, pTime->u8MonthDay),
+ NULL);
+ while (pTime->u8Month > 12)
+ {
+ pTime->u8Month -= 12;
+ pTime->i32Year++;
+ fLeapYear = rtTimeIsLeapYear(pTime->i32Year);
+ pTime->fFlags &= ~(RTTIME_FLAGS_COMMON_YEAR | RTTIME_FLAGS_LEAP_YEAR);
+ }
+
+ for (;;)
+ {
+ unsigned cDaysInMonth = fLeapYear
+ ? g_acDaysInMonthsLeap[pTime->u8Month - 1]
+ : g_acDaysInMonths[pTime->u8Month - 1];
+ if (pTime->u8MonthDay <= cDaysInMonth)
+ break;
+ pTime->u8MonthDay -= cDaysInMonth;
+ if (pTime->u8Month != 12)
+ pTime->u8Month++;
+ else
+ {
+ pTime->u8Month = 1;
+ pTime->i32Year++;
+ fLeapYear = rtTimeIsLeapYear(pTime->i32Year);
+ pTime->fFlags &= ~(RTTIME_FLAGS_COMMON_YEAR | RTTIME_FLAGS_LEAP_YEAR);
+ }
+ }
+
+ pTime->u16YearDay = pTime->u8MonthDay - 1
+ + (fLeapYear
+ ? g_aiDayOfYearLeap[pTime->u8Month - 1]
+ : g_aiDayOfYear[pTime->u8Month - 1]);
+ }
+ else
+ {
+ /*
+ * Are both YearDay and Month/MonthDay valid?
+ * Check that they don't overflow and match, if not use YearDay (simpler).
+ */
+ bool fRecalc = true;
+ if ( pTime->u8Month
+ && pTime->u8MonthDay)
+ {
+ do
+ {
+ uint16_t u16YearDay;
+
+ /* If you change one, zero the other to make clear what you mean. */
+ AssertBreak(pTime->u8Month <= 12);
+ AssertBreak(pTime->u8MonthDay <= (fLeapYear
+ ? g_acDaysInMonthsLeap[pTime->u8Month - 1]
+ : g_acDaysInMonths[pTime->u8Month - 1]));
+ u16YearDay = pTime->u8MonthDay - 1
+ + (fLeapYear
+ ? g_aiDayOfYearLeap[pTime->u8Month - 1]
+ : g_aiDayOfYear[pTime->u8Month - 1]);
+ AssertBreak(u16YearDay == pTime->u16YearDay);
+ fRecalc = false;
+ } while (0);
+ }
+ if (fRecalc)
+ {
+ const uint16_t *paiDayOfYear;
+
+ /* overflow adjust YearDay */
+ while (pTime->u16YearDay > (fLeapYear ? 366 : 365))
+ {
+ pTime->u16YearDay -= fLeapYear ? 366 : 365;
+ pTime->i32Year++;
+ fLeapYear = rtTimeIsLeapYear(pTime->i32Year);
+ pTime->fFlags &= ~(RTTIME_FLAGS_COMMON_YEAR | RTTIME_FLAGS_LEAP_YEAR);
+ }
+
+ /* calc Month and MonthDay */
+ paiDayOfYear = fLeapYear
+ ? &g_aiDayOfYearLeap[0]
+ : &g_aiDayOfYear[0];
+ pTime->u8Month = 1;
+ while (pTime->u16YearDay >= paiDayOfYear[pTime->u8Month])
+ pTime->u8Month++;
+ Assert(pTime->u8Month >= 1 && pTime->u8Month <= 12);
+ pTime->u8MonthDay = pTime->u16YearDay - paiDayOfYear[pTime->u8Month - 1] + 1;
+ }
+ }
+
+ /*
+ * Fixup time overflows.
+ * Use unsigned int values internally to avoid overflows.
+ */
+ uSecond = pTime->u8Second;
+ uMinute = pTime->u8Minute;
+ uHour = pTime->u8Hour;
+
+ while (pTime->u32Nanosecond >= 1000000000)
+ {
+ pTime->u32Nanosecond -= 1000000000;
+ uSecond++;
+ }
+
+ while (uSecond >= 60)
+ {
+ uSecond -= 60;
+ uMinute++;
+ }
+
+ while (uMinute >= 60)
+ {
+ uMinute -= 60;
+ uHour++;
+ }
+
+ while (uHour >= 24)
+ {
+ uHour -= 24;
+
+ /* This is really a RTTimeIncDay kind of thing... */
+ if (pTime->u16YearDay + 1 != (fLeapYear ? g_aiDayOfYearLeap[pTime->u8Month] : g_aiDayOfYear[pTime->u8Month]))
+ {
+ pTime->u16YearDay++;
+ pTime->u8MonthDay++;
+ }
+ else if (pTime->u8Month != 12)
+ {
+ pTime->u16YearDay++;
+ pTime->u8Month++;
+ pTime->u8MonthDay = 1;
+ }
+ else
+ {
+ pTime->i32Year++;
+ fLeapYear = rtTimeIsLeapYear(pTime->i32Year);
+ pTime->fFlags &= ~(RTTIME_FLAGS_COMMON_YEAR | RTTIME_FLAGS_LEAP_YEAR);
+ pTime->u16YearDay = 1;
+ pTime->u8Month = 1;
+ pTime->u8MonthDay = 1;
+ }
+ }
+
+ pTime->u8Second = uSecond;
+ pTime->u8Minute = uMinute;
+ pTime->u8Hour = uHour;
+
+ /*
+ * Correct the leap year flag.
+ * Assert if it's wrong, but ignore if unset.
+ */
+ if (fLeapYear)
+ {
+ Assert(!(pTime->fFlags & RTTIME_FLAGS_COMMON_YEAR));
+ pTime->fFlags &= ~RTTIME_FLAGS_COMMON_YEAR;
+ pTime->fFlags |= RTTIME_FLAGS_LEAP_YEAR;
+ }
+ else
+ {
+ Assert(!(pTime->fFlags & RTTIME_FLAGS_LEAP_YEAR));
+ pTime->fFlags &= ~RTTIME_FLAGS_LEAP_YEAR;
+ pTime->fFlags |= RTTIME_FLAGS_COMMON_YEAR;
+ }
+
+
+ /*
+ * Calc week day.
+ *
+ * 1970-01-01 was a Thursday (3), so find the number of days relative to
+ * that point. We use the table when possible and a slow+stupid+brute-force
+ * algorithm for points outside it. Feel free to optimize the latter by
+ * using some clever formula.
+ */
+ if ( pTime->i32Year >= OFF_YEAR_IDX_0_YEAR
+ && pTime->i32Year < OFF_YEAR_IDX_0_YEAR + (int32_t)RT_ELEMENTS(g_aoffYear))
+ {
+ int32_t offDays = g_aoffYear[pTime->i32Year - OFF_YEAR_IDX_0_YEAR]
+ + pTime->u16YearDay -1;
+ pTime->u8WeekDay = ((offDays % 7) + 3 + 7) % 7;
+ }
+ else
+ {
+ int32_t i32Year = pTime->i32Year;
+ if (i32Year >= 1970)
+ {
+ uint64_t offDays = pTime->u16YearDay - 1;
+ while (--i32Year >= 1970)
+ offDays += rtTimeIsLeapYear(i32Year) ? 366 : 365;
+ pTime->u8WeekDay = (uint8_t)((offDays + 3) % 7);
+ }
+ else
+ {
+ int64_t offDays = (fLeapYear ? -366 - 1 : -365 - 1) + pTime->u16YearDay;
+ while (++i32Year < 1970)
+ offDays -= rtTimeIsLeapYear(i32Year) ? 366 : 365;
+ pTime->u8WeekDay = ((int)(offDays % 7) + 3 + 7) % 7;
+ }
+ }
+ return pTime;
+}
+
+
+RTDECL(PRTTIME) RTTimeNormalize(PRTTIME pTime)
+{
+ /*
+ * Validate that we've got the minimum of stuff handy.
+ */
+ AssertPtrReturn(pTime, NULL);
+ AssertMsgReturn(!(pTime->fFlags & ~RTTIME_FLAGS_MASK), ("%#x\n", pTime->fFlags), NULL);
+ AssertMsgReturn((pTime->fFlags & RTTIME_FLAGS_TYPE_MASK) != RTTIME_FLAGS_TYPE_LOCAL, ("Use RTTimeLocalNormalize!\n"), NULL);
+ AssertMsgReturn(pTime->offUTC == 0, ("%d; Use RTTimeLocalNormalize!\n", pTime->offUTC), NULL);
+
+ pTime = rtTimeNormalizeInternal(pTime);
+ if (pTime)
+ pTime->fFlags |= RTTIME_FLAGS_TYPE_UTC;
+ return pTime;
+}
+RT_EXPORT_SYMBOL(RTTimeNormalize);
+
+
+RTDECL(PRTTIME) RTTimeLocalNormalize(PRTTIME pTime)
+{
+ /*
+ * Validate that we've got the minimum of stuff handy.
+ */
+ AssertPtrReturn(pTime, NULL);
+ AssertMsgReturn(!(pTime->fFlags & ~RTTIME_FLAGS_MASK), ("%#x\n", pTime->fFlags), NULL);
+ AssertMsgReturn((pTime->fFlags & RTTIME_FLAGS_TYPE_MASK) != RTTIME_FLAGS_TYPE_UTC, ("Use RTTimeNormalize!\n"), NULL);
+
+ pTime = rtTimeNormalizeInternal(pTime);
+ if (pTime)
+ pTime->fFlags |= RTTIME_FLAGS_TYPE_LOCAL;
+ return pTime;
+}
+RT_EXPORT_SYMBOL(RTTimeLocalNormalize);
+
+
+RTDECL(char *) RTTimeToString(PCRTTIME pTime, char *psz, size_t cb)
+{
+ size_t cch;
+
+ /* (Default to UTC if not specified) */
+ if ( (pTime->fFlags & RTTIME_FLAGS_TYPE_MASK) == RTTIME_FLAGS_TYPE_LOCAL
+ && pTime->offUTC)
+ {
+ int32_t offUTC = pTime->offUTC;
+ Assert(offUTC <= 840 && offUTC >= -840);
+ char chSign;
+ if (offUTC >= 0)
+ chSign = '+';
+ else
+ {
+ chSign = '-';
+ offUTC = -offUTC;
+ }
+ uint32_t offUTCHour = (uint32_t)offUTC / 60;
+ uint32_t offUTCMinute = (uint32_t)offUTC % 60;
+ cch = RTStrPrintf(psz, cb,
+ "%RI32-%02u-%02uT%02u:%02u:%02u.%09RU32%c%02d%:02d",
+ pTime->i32Year, pTime->u8Month, pTime->u8MonthDay,
+ pTime->u8Hour, pTime->u8Minute, pTime->u8Second, pTime->u32Nanosecond,
+ chSign, offUTCHour, offUTCMinute);
+ if ( cch <= 15
+ || psz[cch - 6] != chSign)
+ return NULL;
+ }
+ else
+ {
+ cch = RTStrPrintf(psz, cb, "%RI32-%02u-%02uT%02u:%02u:%02u.%09RU32Z",
+ pTime->i32Year, pTime->u8Month, pTime->u8MonthDay,
+ pTime->u8Hour, pTime->u8Minute, pTime->u8Second, pTime->u32Nanosecond);
+ if ( cch <= 15
+ || psz[cch - 1] != 'Z')
+ return NULL;
+ }
+ return psz;
+}
+RT_EXPORT_SYMBOL(RTTimeToString);
+
+
+RTDECL(ssize_t) RTTimeToStringEx(PCRTTIME pTime, char *psz, size_t cb, unsigned cFractionDigits)
+{
+ size_t cch;
+
+ /* Format the fraction. */
+ char szFraction[16];
+ if (!cFractionDigits)
+ szFraction[0] = '\0';
+ else
+ {
+ AssertReturn(cFractionDigits <= 9, VERR_OUT_OF_RANGE);
+ Assert(pTime->u32Nanosecond <= 999999999);
+ RTStrPrintf(szFraction, sizeof(szFraction), ".%09RU32", pTime->u32Nanosecond);
+ szFraction[cFractionDigits + 1] = '\0';
+ }
+
+ /* (Default to UTC if not specified) */
+ if ( (pTime->fFlags & RTTIME_FLAGS_TYPE_MASK) == RTTIME_FLAGS_TYPE_LOCAL
+ && pTime->offUTC)
+ {
+ int32_t offUTC = pTime->offUTC;
+ Assert(offUTC <= 840 && offUTC >= -840);
+ char chSign;
+ if (offUTC >= 0)
+ chSign = '+';
+ else
+ {
+ chSign = '-';
+ offUTC = -offUTC;
+ }
+ uint32_t offUTCHour = (uint32_t)offUTC / 60;
+ uint32_t offUTCMinute = (uint32_t)offUTC % 60;
+
+ /* Examples: 2018-09-07T16:12:00+02:00 2018-09-07T16:12:00.123456789+02:00 */
+ cch = RTStrPrintf(psz, cb,
+ "%04RI32-%02u-%02uT%02u:%02u:%02u%s%c%02d%:02d",
+ pTime->i32Year, pTime->u8Month, pTime->u8MonthDay,
+ pTime->u8Hour, pTime->u8Minute, pTime->u8Second, szFraction,
+ chSign, offUTCHour, offUTCMinute);
+ if ( cch >= 24
+ && psz[cch - 6] == chSign)
+ return cch;
+ }
+ else
+ {
+ /* Examples: 2018-09-07T16:12:00Z 2018-09-07T16:12:00.123456789Z */
+ cch = RTStrPrintf(psz, cb, "%04RI32-%02u-%02uT%02u:%02u:%02u%sZ",
+ pTime->i32Year, pTime->u8Month, pTime->u8MonthDay,
+ pTime->u8Hour, pTime->u8Minute, pTime->u8Second, szFraction);
+ if ( cch >= 19
+ && psz[cch - 1] == 'Z')
+ return cch;
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTTimeToStringEx);
+
+
+RTDECL(char *) RTTimeSpecToString(PCRTTIMESPEC pTime, char *psz, size_t cb)
+{
+ RTTIME Time;
+ return RTTimeToString(RTTimeExplode(&Time, pTime), psz, cb);
+}
+RT_EXPORT_SYMBOL(RTTimeSpecToString);
+
+
+
+RTDECL(PRTTIME) RTTimeFromString(PRTTIME pTime, const char *pszString)
+{
+ /* Ignore leading spaces. */
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+ /*
+ * Init non date & time parts.
+ */
+ pTime->fFlags = RTTIME_FLAGS_TYPE_LOCAL;
+ pTime->offUTC = 0;
+
+ /*
+ * The date part.
+ */
+
+ /* Year */
+ int rc = RTStrToInt32Ex(pszString, (char **)&pszString, 10, &pTime->i32Year);
+ if (rc != VWRN_TRAILING_CHARS)
+ return NULL;
+
+ bool const fLeapYear = rtTimeIsLeapYear(pTime->i32Year);
+ if (fLeapYear)
+ pTime->fFlags |= RTTIME_FLAGS_LEAP_YEAR;
+
+ if (*pszString++ != '-')
+ return NULL;
+
+ /* Month of the year. */
+ rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &pTime->u8Month);
+ if (rc != VWRN_TRAILING_CHARS)
+ return NULL;
+ if (pTime->u8Month == 0 || pTime->u8Month > 12)
+ return NULL;
+ if (*pszString++ != '-')
+ return NULL;
+
+ /* Day of month.*/
+ rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &pTime->u8MonthDay);
+ if (rc != VWRN_TRAILING_CHARS && rc != VINF_SUCCESS)
+ return NULL;
+ unsigned const cDaysInMonth = fLeapYear
+ ? g_acDaysInMonthsLeap[pTime->u8Month - 1]
+ : g_acDaysInMonths[pTime->u8Month - 1];
+ if (pTime->u8MonthDay == 0 || pTime->u8MonthDay > cDaysInMonth)
+ return NULL;
+
+ /* Calculate year day. */
+ pTime->u16YearDay = pTime->u8MonthDay - 1
+ + (fLeapYear
+ ? g_aiDayOfYearLeap[pTime->u8Month - 1]
+ : g_aiDayOfYear[pTime->u8Month - 1]);
+
+ pTime->u8WeekDay = UINT8_MAX; /* later */
+
+ /*
+ * The time part.
+ */
+ if (*pszString++ != 'T')
+ return NULL;
+
+ /* Hour. */
+ rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &pTime->u8Hour);
+ if (rc != VWRN_TRAILING_CHARS)
+ return NULL;
+ if (pTime->u8Hour > 23)
+ return NULL;
+ if (*pszString++ != ':')
+ return NULL;
+
+ /* Minute. */
+ rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &pTime->u8Minute);
+ if (rc != VWRN_TRAILING_CHARS)
+ return NULL;
+ if (pTime->u8Minute > 59)
+ return NULL;
+ if (*pszString++ != ':')
+ return NULL;
+
+ /* Second. */
+ rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &pTime->u8Second);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS && rc != VWRN_TRAILING_SPACES)
+ return NULL;
+ if (pTime->u8Second > 59)
+ return NULL;
+
+ /* We generally put a 9 digit fraction here, but it's entirely optional. */
+ if (*pszString == '.')
+ {
+ const char * const pszStart = ++pszString;
+ rc = RTStrToUInt32Ex(pszString, (char **)&pszString, 10, &pTime->u32Nanosecond);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS && rc != VWRN_TRAILING_SPACES)
+ return NULL;
+ if (pTime->u32Nanosecond >= 1000000000)
+ return NULL;
+ switch (pszString - pszStart)
+ {
+ case 1: pTime->u32Nanosecond *= 100000000; break;
+ case 2: pTime->u32Nanosecond *= 10000000; break;
+ case 3: pTime->u32Nanosecond *= 1000000; break;
+ case 4: pTime->u32Nanosecond *= 100000; break;
+ case 5: pTime->u32Nanosecond *= 10000; break;
+ case 6: pTime->u32Nanosecond *= 1000; break;
+ case 7: pTime->u32Nanosecond *= 100; break;
+ case 8: pTime->u32Nanosecond *= 10; break;
+ case 9: break;
+ default:
+ return NULL;
+ }
+ if (pTime->u32Nanosecond >= 1000000000)
+ return NULL;
+ }
+ else
+ pTime->u32Nanosecond = 0;
+
+ /*
+ * Time zone.
+ */
+ if (*pszString == 'Z')
+ {
+ pszString++;
+ pTime->fFlags &= ~RTTIME_FLAGS_TYPE_MASK;
+ pTime->fFlags |= RTTIME_FLAGS_TYPE_UTC;
+ pTime->offUTC = 0;
+ }
+ else if ( *pszString == '+'
+ || *pszString == '-')
+ {
+ int8_t cUtcHours = 0;
+ rc = RTStrToInt8Ex(pszString, (char **)&pszString, 10, &cUtcHours);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS && rc != VWRN_TRAILING_SPACES)
+ return NULL;
+ uint8_t cUtcMin = 0;
+ if (*pszString == ':')
+ {
+ rc = RTStrToUInt8Ex(pszString + 1, (char **)&pszString, 10, &cUtcMin);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES)
+ return NULL;
+ }
+ else if (*pszString && !RT_C_IS_BLANK(*pszString))
+ return NULL;
+ if (cUtcHours >= 0)
+ pTime->offUTC = cUtcHours * 60 + cUtcMin;
+ else
+ pTime->offUTC = cUtcHours * 60 - cUtcMin;
+ if (RT_ABS(pTime->offUTC) > 840)
+ return NULL;
+ }
+ /* else: No time zone given, local with offUTC = 0. */
+
+ /*
+ * The rest of the string should be blanks.
+ */
+ char ch;
+ while ((ch = *pszString++) != '\0')
+ if (!RT_C_IS_BLANK(ch))
+ return NULL;
+
+ /* Calc week day. */
+ rtTimeNormalizeInternal(pTime);
+ return pTime;
+}
+RT_EXPORT_SYMBOL(RTTimeFromString);
+
+
+RTDECL(PRTTIMESPEC) RTTimeSpecFromString(PRTTIMESPEC pTime, const char *pszString)
+{
+ RTTIME Time;
+ if (RTTimeFromString(&Time, pszString))
+ return RTTimeImplode(pTime, &Time);
+ return NULL;
+}
+RT_EXPORT_SYMBOL(RTTimeSpecFromString);
+
+
+RTDECL(ssize_t) RTTimeToRfc2822(PRTTIME pTime, char *psz, size_t cb, uint32_t fFlags)
+{
+ Assert(pTime->u8Month > 0 && pTime->u8Month <= 12);
+ Assert(pTime->u8WeekDay < 7);
+ Assert(!(fFlags & ~RTTIME_RFC2822_F_GMT));
+
+ /* (Default to UTC if not specified) */
+ if ( (pTime->fFlags & RTTIME_FLAGS_TYPE_MASK) == RTTIME_FLAGS_TYPE_LOCAL
+ && pTime->offUTC)
+ {
+ Assert(!(fFlags & RTTIME_RFC2822_F_GMT) /* don't call with local time. duh! */ );
+
+ /* Calc the UTC offset part. */
+ int32_t offUtc = pTime->offUTC;
+ Assert(offUtc <= 840 && offUtc >= -840);
+ char chSign;
+ if (offUtc >= 0)
+ chSign = '+';
+ else
+ {
+ chSign = '-';
+ offUtc = -offUtc;
+ }
+ uint32_t offUtcHour = (uint32_t)offUtc / 60;
+ uint32_t offUtcMinute = (uint32_t)offUtc % 60;
+
+ /* Example: "Mon, 31 Aug 2018 00:00:00 +0200" */
+ size_t cch = RTStrPrintf(psz, cb, "%s, %u %s %04RI32 %02u:%02u:%02u %c%02u%02u", g_apszWeekDays[pTime->u8WeekDay],
+ pTime->u8MonthDay, g_apszMonths[pTime->u8Month], pTime->i32Year,
+ pTime->u8Hour, pTime->u8Minute, pTime->u8Second, chSign, offUtcHour, offUtcMinute);
+ if ( cch >= 27
+ && psz[cch - 5] == chSign)
+ return cch;
+ }
+ else if (fFlags & RTTIME_RFC2822_F_GMT)
+ {
+ /* Example: "Mon, 1 Jan 1971 23:55:59 GMT" */
+ size_t cch = RTStrPrintf(psz, cb, "%s, %u %s %04RI32 %02u:%02u:%02u GMT", g_apszWeekDays[pTime->u8WeekDay],
+ pTime->u8MonthDay, g_apszMonths[pTime->u8Month], pTime->i32Year,
+ pTime->u8Hour, pTime->u8Minute, pTime->u8Second);
+ if ( cch >= 27
+ && psz[cch - 1] == 'T')
+ return cch;
+ }
+ else
+ {
+ /* Example: "Mon, 1 Jan 1971 00:00:00 -0000" */
+ size_t cch = RTStrPrintf(psz, cb, "%s, %u %s %04RI32 %02u:%02u:%02u -0000", g_apszWeekDays[pTime->u8WeekDay],
+ pTime->u8MonthDay, g_apszMonths[pTime->u8Month], pTime->i32Year,
+ pTime->u8Hour, pTime->u8Minute, pTime->u8Second);
+ if ( cch >= 27
+ && psz[cch - 5] == '-')
+ return cch;
+ }
+ return VERR_BUFFER_OVERFLOW;
+}
+RT_EXPORT_SYMBOL(RTTimeToRfc2822);
+
+
+RTDECL(PRTTIME) RTTimeFromRfc2822(PRTTIME pTime, const char *pszString)
+{
+ /*
+ * Fri, 31 Aug 2018 00:00:00 +0200
+ * Mon, 3 Sep 2018 00:00:00 GMT
+ * Mon, 3 Sep 2018 00:00:00 -0000
+ * 3 Sep 2018 00:00:00 -0000 (?)
+ * 3 Sep 2018 00:00:00 GMT (?)
+ *
+ */
+
+ /* Ignore leading spaces. */
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+ /*
+ * Init non date & time parts.
+ */
+ pTime->fFlags = RTTIME_FLAGS_TYPE_LOCAL;
+ pTime->offUTC = 0;
+
+ /*
+ * The date part.
+ */
+
+ /* Optional day of week: */
+ if (RT_C_IS_ALPHA(pszString[0]) && pszString[1] != '\0')
+ {
+ uint32_t uWeekDay = RT_MAKE_U32_FROM_U8(RT_C_TO_LOWER(pszString[0]), RT_C_TO_LOWER(pszString[1]),
+ RT_C_TO_LOWER(pszString[2]), 0);
+ if ( uWeekDay == RT_MAKE_U32_FROM_U8('m', 'o', 'n', 0)) pTime->u8WeekDay = 0;
+ else if (uWeekDay == RT_MAKE_U32_FROM_U8('t', 'u', 'e', 0)) pTime->u8WeekDay = 1;
+ else if (uWeekDay == RT_MAKE_U32_FROM_U8('w', 'e', 'd', 0)) pTime->u8WeekDay = 2;
+ else if (uWeekDay == RT_MAKE_U32_FROM_U8('t', 'h', 'u', 0)) pTime->u8WeekDay = 3;
+ else if (uWeekDay == RT_MAKE_U32_FROM_U8('f', 'r', 'i', 0)) pTime->u8WeekDay = 4;
+ else if (uWeekDay == RT_MAKE_U32_FROM_U8('s', 'a', 't', 0)) pTime->u8WeekDay = 5;
+ else if (uWeekDay == RT_MAKE_U32_FROM_U8('s', 'u', 'n', 0)) pTime->u8WeekDay = 6;
+ else
+ return NULL;
+ pszString += 3;
+ while (RT_C_IS_ALPHA(*pszString))
+ pszString++;
+ if (*pszString == ',')
+ pszString++;
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+ if (!RT_C_IS_DIGIT(pszString[0]))
+ return NULL;
+ }
+ else if (RT_C_IS_DIGIT(pszString[0]))
+ pTime->u8WeekDay = UINT8_MAX;
+ else
+ return NULL;
+
+ /* Day of month.*/
+ int rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &pTime->u8MonthDay);
+ if (rc != VWRN_TRAILING_CHARS && rc != VINF_SUCCESS)
+ return NULL;
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+ /* Month of the year. */
+ if (pszString[0] == '\0' || pszString[1] == '\0' || pszString[2] == '\0')
+ return NULL;
+ uint32_t uMonth = RT_MAKE_U32_FROM_U8(RT_C_TO_LOWER(pszString[0]), RT_C_TO_LOWER(pszString[1]),
+ RT_C_TO_LOWER(pszString[2]), 0);
+ if ( uMonth == RT_MAKE_U32_FROM_U8('j', 'a', 'n', 0)) pTime->u8Month = 1;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('f', 'e', 'b', 0)) pTime->u8Month = 2;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('m', 'a', 'r', 0)) pTime->u8Month = 3;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('a', 'p', 'r', 0)) pTime->u8Month = 4;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('m', 'a', 'y', 0)) pTime->u8Month = 5;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('j', 'u', 'n', 0)) pTime->u8Month = 6;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('j', 'u', 'l', 0)) pTime->u8Month = 7;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('a', 'u', 'g', 0)) pTime->u8Month = 8;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('s', 'e', 'p', 0)) pTime->u8Month = 9;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('o', 'c', 't', 0)) pTime->u8Month = 10;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('n', 'o', 'v', 0)) pTime->u8Month = 11;
+ else if (uMonth == RT_MAKE_U32_FROM_U8('d', 'e', 'c', 0)) pTime->u8Month = 12;
+ else
+ return NULL;
+ pszString += 3;
+ while (RT_C_IS_ALPHA(*pszString))
+ pszString++;
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+ /* Year */
+ const char * const pszStartYear = pszString;
+ rc = RTStrToInt32Ex(pszString, (char **)&pszString, 10, &pTime->i32Year);
+ if (rc != VWRN_TRAILING_CHARS)
+ return NULL;
+ if (pszString - pszStartYear >= 4 )
+ { /* likely */ }
+ else if (pszString - pszStartYear == 3)
+ pTime->i32Year += 1900;
+ else if (pszString - pszStartYear == 2)
+ pTime->i32Year += pTime->i32Year >= 50 ? 1900 : 2000;
+ else
+ return NULL;
+
+ bool const fLeapYear = rtTimeIsLeapYear(pTime->i32Year);
+ if (fLeapYear)
+ pTime->fFlags |= RTTIME_FLAGS_LEAP_YEAR;
+
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+
+ /* Calculate year day. */
+ unsigned const cDaysInMonth = fLeapYear
+ ? g_acDaysInMonthsLeap[pTime->u8Month - 1]
+ : g_acDaysInMonths[pTime->u8Month - 1];
+ if (pTime->u8MonthDay == 0 || pTime->u8MonthDay > cDaysInMonth)
+ return NULL;
+
+ pTime->u16YearDay = pTime->u8MonthDay - 1
+ + (fLeapYear
+ ? g_aiDayOfYearLeap[pTime->u8Month - 1]
+ : g_aiDayOfYear[pTime->u8Month - 1]);
+
+ /*
+ * The time part.
+ */
+ /* Hour. */
+ rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &pTime->u8Hour);
+ if (rc != VWRN_TRAILING_CHARS)
+ return NULL;
+ if (pTime->u8Hour > 23)
+ return NULL;
+ if (*pszString++ != ':')
+ return NULL;
+
+ /* Minute. */
+ rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &pTime->u8Minute);
+ if (rc != VWRN_TRAILING_CHARS)
+ return NULL;
+ if (pTime->u8Minute > 59)
+ return NULL;
+ if (*pszString++ != ':')
+ return NULL;
+
+ /* Second. */
+ rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &pTime->u8Second);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS && rc != VWRN_TRAILING_SPACES)
+ return NULL;
+ if (pTime->u8Second > 59)
+ return NULL;
+
+ /* Non-standard fraction. Handy for testing, though. */
+ if (*pszString == '.')
+ {
+ const char * const pszStart = ++pszString;
+ rc = RTStrToUInt32Ex(pszString, (char **)&pszString, 10, &pTime->u32Nanosecond);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_CHARS && rc != VWRN_TRAILING_SPACES)
+ return NULL;
+ if (pTime->u32Nanosecond >= 1000000000)
+ return NULL;
+ switch (pszString - pszStart)
+ {
+ case 1: pTime->u32Nanosecond *= 100000000; break;
+ case 2: pTime->u32Nanosecond *= 10000000; break;
+ case 3: pTime->u32Nanosecond *= 1000000; break;
+ case 4: pTime->u32Nanosecond *= 100000; break;
+ case 5: pTime->u32Nanosecond *= 10000; break;
+ case 6: pTime->u32Nanosecond *= 1000; break;
+ case 7: pTime->u32Nanosecond *= 100; break;
+ case 8: pTime->u32Nanosecond *= 10; break;
+ case 9: break;
+ default:
+ return NULL;
+ }
+ if (pTime->u32Nanosecond >= 1000000000)
+ return NULL;
+ }
+ else
+ pTime->u32Nanosecond = 0;
+ while (RT_C_IS_SPACE(*pszString))
+ pszString++;
+
+ /*
+ * Time zone.
+ */
+ if ( *pszString == '+'
+ || *pszString == '-')
+ {
+ if ( !RT_C_IS_DIGIT(pszString[1])
+ || !RT_C_IS_DIGIT(pszString[2]))
+ return NULL;
+ int8_t cUtcHours = (pszString[1] - '0') * 10 + (pszString[2] - '0');
+ char chSign = *pszString;
+ if (chSign == '-')
+ cUtcHours = -cUtcHours;
+ pszString += 3;
+
+ uint8_t cUtcMin = 0;
+ if (RT_C_IS_DIGIT(pszString[0]))
+ {
+ rc = RTStrToUInt8Ex(pszString, (char **)&pszString, 10, &cUtcMin);
+ if (rc != VINF_SUCCESS && rc != VWRN_TRAILING_SPACES)
+ return NULL;
+ }
+ else if (*pszString && !RT_C_IS_BLANK(*pszString))
+ return NULL;
+ if (cUtcHours >= 0)
+ pTime->offUTC = cUtcHours * 60 + cUtcMin;
+ else
+ pTime->offUTC = cUtcHours * 60 - cUtcMin;
+ if (RT_ABS(pTime->offUTC) > 840)
+ return NULL;
+
+ /* -0000: GMT isn't necessarily the local time zone, so change flags from local to UTC. */
+ if (pTime->offUTC == 0 && chSign == '-')
+ {
+ pTime->fFlags &= ~RTTIME_FLAGS_TYPE_MASK;
+ pTime->fFlags |= RTTIME_FLAGS_TYPE_UTC;
+ }
+ }
+ else if (RT_C_IS_ALPHA(*pszString))
+ {
+ uint32_t uTimeZone = RT_MAKE_U32_FROM_U8(RT_C_TO_LOWER(pszString[0]), RT_C_TO_LOWER(pszString[1]),
+ RT_C_TO_LOWER(pszString[2]), 0);
+ if (uTimeZone == RT_MAKE_U32_FROM_U8('g', 'm', 't', 0))
+ {
+ pTime->fFlags &= ~RTTIME_FLAGS_TYPE_MASK;
+ pTime->fFlags |= RTTIME_FLAGS_TYPE_UTC;
+ pTime->offUTC = 0;
+ pszString += 3;
+ }
+ else if ((uint16_t)uTimeZone == RT_MAKE_U16('u', 't'))
+ {
+ pTime->fFlags &= ~RTTIME_FLAGS_TYPE_MASK;
+ pTime->fFlags |= RTTIME_FLAGS_TYPE_UTC;
+ pTime->offUTC = 0;
+ pszString += 2;
+ }
+ else
+ {
+ static const struct { uint32_t uTimeZone; int32_t offUtc; } s_aLegacyTimeZones[] =
+ {
+ { RT_MAKE_U32_FROM_U8('e', 'd', 't', 0), -4*60 },
+ { RT_MAKE_U32_FROM_U8('e', 's', 't', 0), -5*60 },
+ { RT_MAKE_U32_FROM_U8('c', 'd', 't', 0), -5*60 },
+ { RT_MAKE_U32_FROM_U8('c', 's', 't', 0), -6*60 },
+ { RT_MAKE_U32_FROM_U8('m', 'd', 't', 0), -6*60 },
+ { RT_MAKE_U32_FROM_U8('m', 's', 't', 0), -7*60 },
+ { RT_MAKE_U32_FROM_U8('p', 'd', 't', 0), -7*60 },
+ { RT_MAKE_U32_FROM_U8('p', 's', 't', 0), -8*60 },
+ };
+ size_t i = RT_ELEMENTS(s_aLegacyTimeZones);
+ while (i-- > 0)
+ if (s_aLegacyTimeZones[i].uTimeZone == uTimeZone)
+ {
+ pTime->fFlags &= ~RTTIME_FLAGS_TYPE_MASK;
+ pTime->fFlags |= RTTIME_FLAGS_TYPE_LOCAL;
+ pTime->offUTC = s_aLegacyTimeZones[i].offUtc;
+ pszString += 3;
+ break;
+ }
+ }
+
+ }
+ /* else: No time zone given, local with offUTC = 0. */
+
+ /*
+ * The rest of the string should be blanks.
+ */
+ char ch;
+ while ((ch = *pszString++) != '\0')
+ if (!RT_C_IS_BLANK(ch))
+ return NULL;
+
+ rtTimeNormalizeInternal(pTime);
+ return pTime;
+}
+RT_EXPORT_SYMBOL(RTTimeFromRfc2822);
+
+
+/**
+ * Adds one day to @a pTime.
+ *
+ * ASSUMES it is zulu time so DST can be ignored.
+ */
+static PRTTIME rtTimeAdd1Day(PRTTIME pTime)
+{
+ Assert(!pTime->offUTC);
+ rtTimeNormalizeInternal(pTime);
+ pTime->u8MonthDay += 1;
+ pTime->u16YearDay = 0;
+ return rtTimeNormalizeInternal(pTime);
+}
+
+
+/**
+ * Subtracts one day from @a pTime.
+ *
+ * ASSUMES it is zulu time so DST can be ignored.
+ */
+static PRTTIME rtTimeSub1Day(PRTTIME pTime)
+{
+ Assert(!pTime->offUTC);
+ rtTimeNormalizeInternal(pTime);
+ if (pTime->u16YearDay > 1)
+ {
+ pTime->u16YearDay -= 1;
+ pTime->u8Month = 0;
+ pTime->u8MonthDay = 0;
+ }
+ else
+ {
+ pTime->i32Year -= 1;
+ pTime->u16YearDay = rtTimeIsLeapYear(pTime->i32Year) ? 366 : 365;
+ pTime->u8MonthDay = 31;
+ pTime->u8Month = 12;
+ pTime->fFlags &= ~(RTTIME_FLAGS_COMMON_YEAR | RTTIME_FLAGS_LEAP_YEAR);
+ }
+ return rtTimeNormalizeInternal(pTime);
+}
+
+
+/**
+ * Adds a signed number of minutes to @a pTime.
+ *
+ * ASSUMES it is zulu time so DST can be ignored.
+ *
+ * @param pTime The time structure to work on.
+ * @param cAddend Number of minutes to add.
+ * ASSUMES the value isn't all that high!
+ */
+static PRTTIME rtTimeAddMinutes(PRTTIME pTime, int32_t cAddend)
+{
+ Assert(RT_ABS(cAddend) < 31 * 24 * 60);
+
+ /*
+ * Work on minutes of the day.
+ */
+ int32_t const cMinutesInDay = 24 * 60;
+ int32_t iDayMinute = (unsigned)pTime->u8Hour * 60 + pTime->u8Minute;
+ iDayMinute += cAddend;
+
+ while (iDayMinute >= cMinutesInDay)
+ {
+ rtTimeAdd1Day(pTime);
+ iDayMinute -= cMinutesInDay;
+ }
+
+ while (iDayMinute < 0)
+ {
+ rtTimeSub1Day(pTime);
+ iDayMinute += cMinutesInDay;
+ }
+
+ pTime->u8Hour = iDayMinute / 60;
+ pTime->u8Minute = iDayMinute % 60;
+
+ return pTime;
+}
+
+
+/**
+ * Converts @a pTime to zulu time (UTC) if needed.
+ *
+ * @returns pTime.
+ * @param pTime What to convert (in/out).
+ */
+static PRTTIME rtTimeConvertToZulu(PRTTIME pTime)
+{
+ RTTIME_ASSERT_NORMALIZED(pTime);
+ if ((pTime->fFlags & RTTIME_FLAGS_TYPE_MASK) != RTTIME_FLAGS_TYPE_UTC)
+ {
+ int32_t offUTC = pTime->offUTC;
+ pTime->offUTC = 0;
+ pTime->fFlags &= ~RTTIME_FLAGS_TYPE_MASK;
+ pTime->fFlags |= RTTIME_FLAGS_TYPE_UTC;
+ if (offUTC != 0)
+ rtTimeAddMinutes(pTime, -offUTC);
+ }
+ return pTime;
+}
+
+
+RTDECL(PRTTIME) RTTimeConvertToZulu(PRTTIME pTime)
+{
+ /*
+ * Validate that we've got the minimum of stuff handy.
+ */
+ AssertPtrReturn(pTime, NULL);
+ AssertMsgReturn(!(pTime->fFlags & ~RTTIME_FLAGS_MASK), ("%#x\n", pTime->fFlags), NULL);
+
+ return rtTimeConvertToZulu(rtTimeNormalizeInternal(pTime));
+}
+RT_EXPORT_SYMBOL(RTTimeConvertToZulu);
+
+
+RTDECL(int) RTTimeCompare(PCRTTIME pLeft, PCRTTIME pRight)
+{
+#ifdef RT_STRICT
+ if (pLeft)
+ RTTIME_ASSERT_NORMALIZED(pLeft);
+ if (pRight)
+ RTTIME_ASSERT_NORMALIZED(pRight);
+#endif
+
+ int iRet;
+ if (pLeft)
+ {
+ if (pRight)
+ {
+ /*
+ * Only work with normalized zulu time.
+ */
+ RTTIME TmpLeft;
+ if ( pLeft->offUTC != 0
+ || pLeft->u16YearDay == 0
+ || pLeft->u16YearDay > 366
+ || pLeft->u8Hour >= 60
+ || pLeft->u8Minute >= 60
+ || pLeft->u8Second >= 60)
+ {
+ TmpLeft = *pLeft;
+ pLeft = rtTimeConvertToZulu(rtTimeNormalizeInternal(&TmpLeft));
+ }
+
+ RTTIME TmpRight;
+ if ( pRight->offUTC != 0
+ || pRight->u16YearDay == 0
+ || pRight->u16YearDay > 366
+ || pRight->u8Hour >= 60
+ || pRight->u8Minute >= 60
+ || pRight->u8Second >= 60)
+ {
+ TmpRight = *pRight;
+ pRight = rtTimeConvertToZulu(rtTimeNormalizeInternal(&TmpRight));
+ }
+
+ /*
+ * Do the comparison.
+ */
+ if ( pLeft->i32Year != pRight->i32Year)
+ iRet = pLeft->i32Year < pRight->i32Year ? -1 : 1;
+ else if ( pLeft->u16YearDay != pRight->u16YearDay)
+ iRet = pLeft->u16YearDay < pRight->u16YearDay ? -1 : 1;
+ else if ( pLeft->u8Hour != pRight->u8Hour)
+ iRet = pLeft->u8Hour < pRight->u8Hour ? -1 : 1;
+ else if ( pLeft->u8Minute != pRight->u8Minute)
+ iRet = pLeft->u8Minute < pRight->u8Minute ? -1 : 1;
+ else if ( pLeft->u8Second != pRight->u8Second)
+ iRet = pLeft->u8Second < pRight->u8Second ? -1 : 1;
+ else if ( pLeft->u32Nanosecond != pRight->u32Nanosecond)
+ iRet = pLeft->u32Nanosecond < pRight->u32Nanosecond ? -1 : 1;
+ else
+ iRet = 0;
+ }
+ else
+ iRet = 1;
+ }
+ else
+ iRet = pRight ? -1 : 0;
+ return iRet;
+}
+RT_EXPORT_SYMBOL(RTTimeCompare);
+
diff --git a/src/VBox/Runtime/common/time/timeprog.cpp b/src/VBox/Runtime/common/time/timeprog.cpp
new file mode 100644
index 00000000..0f0bf030
--- /dev/null
+++ b/src/VBox/Runtime/common/time/timeprog.cpp
@@ -0,0 +1,107 @@
+/* $Id: timeprog.cpp $ */
+/** @file
+ * IPRT - Time Relative to Program Start.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/time.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include "internal/time.h"
+
+
+
+/**
+ * Get the nanosecond timestamp relative to program startup.
+ *
+ * @returns Timestamp relative to program startup.
+ */
+RTDECL(uint64_t) RTTimeProgramNanoTS(void)
+{
+ return RTTimeNanoTS() - g_u64ProgramStartNanoTS;
+}
+RT_EXPORT_SYMBOL(RTTimeProgramNanoTS);
+
+
+/**
+ * Get the microsecond timestamp relative to program startup.
+ *
+ * @returns Timestamp relative to program startup.
+ */
+RTDECL(uint64_t) RTTimeProgramMicroTS(void)
+{
+ return RTTimeProgramNanoTS() / RT_NS_1US;
+}
+RT_EXPORT_SYMBOL(RTTimeProgramMicroTS);
+
+
+/**
+ * Get the millisecond timestamp relative to program startup.
+ *
+ * @returns Timestamp relative to program startup.
+ */
+RTDECL(uint64_t) RTTimeProgramMilliTS(void)
+{
+ return RTTimeProgramNanoTS() / RT_NS_1MS;
+}
+RT_EXPORT_SYMBOL(RTTimeProgramMilliTS);
+
+
+/**
+ * Get the second timestamp relative to program startup.
+ *
+ * @returns Timestamp relative to program startup.
+ */
+RTDECL(uint32_t) RTTimeProgramSecTS(void)
+{
+ return (uint32_t)(RTTimeProgramNanoTS() / RT_NS_1SEC);
+}
+RT_EXPORT_SYMBOL(RTTimeProgramSecTS);
+
+
+/**
+ * Get the RTTimeNanoTS() of when the program started.
+ *
+ * @returns Program startup timestamp.
+ */
+RTDECL(uint64_t) RTTimeProgramStartNanoTS(void)
+{
+ return g_u64ProgramStartNanoTS;
+}
+RT_EXPORT_SYMBOL(RTTimeProgramStartNanoTS);
+
diff --git a/src/VBox/Runtime/common/time/timesup.cpp b/src/VBox/Runtime/common/time/timesup.cpp
new file mode 100644
index 00000000..a6c86a6b
--- /dev/null
+++ b/src/VBox/Runtime/common/time/timesup.cpp
@@ -0,0 +1,467 @@
+/* $Id: timesup.cpp $ */
+/** @file
+ * IPRT - Time using SUPLib.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_TIME
+#include <iprt/time.h>
+#include "internal/iprt.h"
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/log.h>
+#if !defined(IN_GUEST) && !defined(RT_NO_GIP)
+# include <iprt/asm.h>
+# include <iprt/asm-amd64-x86.h>
+# include <iprt/x86.h>
+# include <VBox/sup.h>
+#endif
+#include "internal/time.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#if !defined(IN_GUEST) && !defined(RT_NO_GIP)
+static DECLCALLBACK(void) rtTimeNanoTSInternalBitch(PRTTIMENANOTSDATA pData, uint64_t u64NanoTS, uint64_t u64DeltaPrev, uint64_t u64PrevNanoTS);
+static DECLCALLBACK(uint64_t) rtTimeNanoTSInternalFallback(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra);
+static DECLCALLBACK(uint64_t) rtTimeNanoTSInternalRediscover(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra);
+static DECLCALLBACK(uint64_t) rtTimeNanoTSInternalBadCpuIndex(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra,
+ uint16_t idApic, uint16_t iCpuSet, uint16_t iGipCpu);
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if !defined(IN_GUEST) && !defined(RT_NO_GIP)
+/** The previous timestamp value returned by RTTimeNanoTS. */
+static uint64_t g_TimeNanoTSPrev = 0;
+
+/** The RTTimeNanoTS data structure that's passed down to the worker functions. */
+static RTTIMENANOTSDATA g_TimeNanoTSData =
+{
+ /* .pu64Prev = */ &g_TimeNanoTSPrev,
+ /* .pfnBad = */ rtTimeNanoTSInternalBitch,
+ /* .pfnRediscover = */ rtTimeNanoTSInternalRediscover,
+ /* .pfnBadCpuIndex = */ rtTimeNanoTSInternalBadCpuIndex,
+ /* .c1nsSteps = */ 0,
+ /* .cExpired = */ 0,
+ /* .cBadPrev = */ 0,
+ /* .cUpdateRaces = */ 0
+};
+
+# ifdef IN_RC
+/** Array of rtTimeNanoTSInternal worker functions.
+ * This array is indexed by g_iWorker. */
+static const PFNTIMENANOTSINTERNAL g_apfnWorkers[] =
+{
+# define RTTIMENANO_WORKER_DETECT 0
+ rtTimeNanoTSInternalRediscover,
+
+# define RTTIMENANO_WORKER_LEGACY_SYNC_INVAR_NO_DELTA 1
+ RTTimeNanoTSLegacySyncInvarNoDelta,
+# define RTTIMENANO_WORKER_LEGACY_SYNC_INVAR_WITH_DELTA 2
+ RTTimeNanoTSLegacySyncInvarWithDelta,
+# define RTTIMENANO_WORKER_LEGACY_ASYNC 3
+ RTTimeNanoTSLegacyAsync,
+
+# define RTTIMENANO_WORKER_LFENCE_SYNC_INVAR_NO_DELTA 4
+ RTTimeNanoTSLFenceSyncInvarNoDelta,
+# define RTTIMENANO_WORKER_LFENCE_SYNC_INVAR_WITH_DELTA 5
+ RTTimeNanoTSLFenceSyncInvarWithDelta,
+# define RTTIMENANO_WORKER_LFENCE_ASYNC 6
+ RTTimeNanoTSLFenceAsync,
+
+# define RTTIMENANO_WORKER_FALLBACK 7
+ rtTimeNanoTSInternalFallback,
+};
+/** The index into g_apfnWorkers for the function to use.
+ * @remarks This cannot be a pointer because that'll break down in RC due to
+ * code relocation. */
+static uint32_t g_iWorker = RTTIMENANO_WORKER_DETECT;
+# else
+/** Pointer to the worker */
+static PFNTIMENANOTSINTERNAL g_pfnWorker = rtTimeNanoTSInternalRediscover;
+# endif /* IN_RC */
+
+
+/**
+ * @interface_method_impl{RTTIMENANOTSDATA,pfnBad}
+ */
+static DECLCALLBACK(void) rtTimeNanoTSInternalBitch(PRTTIMENANOTSDATA pData, uint64_t u64NanoTS, uint64_t u64DeltaPrev,
+ uint64_t u64PrevNanoTS)
+{
+ pData->cBadPrev++;
+ if ((int64_t)u64DeltaPrev < 0)
+ LogRel(("TM: u64DeltaPrev=%RI64 u64PrevNanoTS=0x%016RX64 u64NanoTS=0x%016RX64\n",
+ u64DeltaPrev, u64PrevNanoTS, u64NanoTS));
+ else
+ Log(("TM: u64DeltaPrev=%RI64 u64PrevNanoTS=0x%016RX64 u64NanoTS=0x%016RX64 (debugging?)\n",
+ u64DeltaPrev, u64PrevNanoTS, u64NanoTS));
+}
+
+/**
+ * @interface_method_impl{RTTIMENANOTSDATA,pfnBadCpuIndex}
+ */
+static DECLCALLBACK(uint64_t) rtTimeNanoTSInternalBadCpuIndex(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra,
+ uint16_t idApic, uint16_t iCpuSet, uint16_t iGipCpu)
+{
+ RT_NOREF_PV(pData); RT_NOREF_PV(idApic); RT_NOREF_PV(iCpuSet); RT_NOREF_PV(iGipCpu);
+# ifndef IN_RC
+ AssertMsgFailed(("idApic=%#x iCpuSet=%#x iGipCpu=%#x\n", idApic, iCpuSet, iGipCpu));
+ if (pExtra)
+ pExtra->uTSCValue = ASMReadTSC();
+ return RTTimeSystemNanoTS();
+# else
+ RTAssertReleasePanic();
+ return 0;
+# endif
+}
+
+
+/**
+ * Fallback function.
+ */
+static DECLCALLBACK(uint64_t) rtTimeNanoTSInternalFallback(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra)
+{
+ PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage;
+ if ( pGip
+ && pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC
+ && ( pGip->u32Mode == SUPGIPMODE_INVARIANT_TSC
+ || pGip->u32Mode == SUPGIPMODE_SYNC_TSC
+ || pGip->u32Mode == SUPGIPMODE_ASYNC_TSC))
+ return rtTimeNanoTSInternalRediscover(pData, pExtra);
+ NOREF(pData);
+# ifndef IN_RC
+ if (pExtra)
+ pExtra->uTSCValue = ASMReadTSC();
+ return RTTimeSystemNanoTS();
+# else
+ RTAssertReleasePanic();
+ return 0;
+# endif
+}
+
+
+/**
+ * Called the first time somebody asks for the time or when the GIP
+ * is mapped/unmapped.
+ */
+static DECLCALLBACK(uint64_t) rtTimeNanoTSInternalRediscover(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra)
+{
+ PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage;
+# ifdef IN_RC
+ uint32_t iWorker;
+# else
+ PFNTIMENANOTSINTERNAL pfnWorker;
+# endif
+ if ( pGip
+ && pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC
+ && ( pGip->u32Mode == SUPGIPMODE_INVARIANT_TSC
+ || pGip->u32Mode == SUPGIPMODE_SYNC_TSC
+ || pGip->u32Mode == SUPGIPMODE_ASYNC_TSC))
+ {
+ if (ASMCpuId_EDX(1) & X86_CPUID_FEATURE_EDX_SSE2)
+ {
+# ifdef IN_RC
+ iWorker = pGip->u32Mode == SUPGIPMODE_ASYNC_TSC
+ ? RTTIMENANO_WORKER_LFENCE_ASYNC
+ : pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO
+ ? RTTIMENANO_WORKER_LFENCE_SYNC_INVAR_NO_DELTA
+ : RTTIMENANO_WORKER_LFENCE_SYNC_INVAR_WITH_DELTA;
+# elif defined(IN_RING0)
+ pfnWorker = pGip->u32Mode == SUPGIPMODE_ASYNC_TSC
+ ? RTTimeNanoTSLFenceAsync
+ : pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO
+ ? RTTimeNanoTSLFenceSyncInvarNoDelta
+ : RTTimeNanoTSLFenceSyncInvarWithDelta;
+# else
+ if (pGip->u32Mode == SUPGIPMODE_ASYNC_TSC)
+ {
+ if ( pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS)
+ pfnWorker = RTTimeNanoTSLFenceAsyncUseIdtrLim;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS)
+ pfnWorker = RTTimeNanoTSLFenceAsyncUseRdtscp;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL)
+ pfnWorker = RTTimeNanoTSLFenceAsyncUseRdtscpGroupChNumCl;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_0B)
+ pfnWorker = RTTimeNanoTSLFenceAsyncUseApicIdExt0B;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_8000001E)
+ pfnWorker = RTTimeNanoTSLFenceAsyncUseApicIdExt8000001E;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID)
+ pfnWorker = RTTimeNanoTSLFenceAsyncUseApicId;
+ else
+ pfnWorker = rtTimeNanoTSInternalFallback;
+ }
+ else
+ {
+ if (pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_PRACTICALLY_ZERO)
+ pfnWorker = RTTimeNanoTSLFenceSyncInvarNoDelta;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS)
+ pfnWorker = RTTimeNanoTSLFenceSyncInvarWithDeltaUseIdtrLim;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS)
+ pfnWorker = RTTimeNanoTSLFenceSyncInvarWithDeltaUseRdtscp;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_0B)
+ pfnWorker = RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt0B;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_8000001E)
+ pfnWorker = RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt8000001E;
+ else if (pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID)
+ pfnWorker = RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicId;
+ else
+ pfnWorker = rtTimeNanoTSInternalFallback;
+ }
+# endif
+ }
+ else
+ {
+# ifdef IN_RC
+ iWorker = pGip->u32Mode == SUPGIPMODE_ASYNC_TSC
+ ? RTTIMENANO_WORKER_LEGACY_ASYNC
+ : pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO
+ ? RTTIMENANO_WORKER_LEGACY_SYNC_INVAR_NO_DELTA : RTTIMENANO_WORKER_LEGACY_SYNC_INVAR_WITH_DELTA;
+# elif defined(IN_RING0)
+ pfnWorker = pGip->u32Mode == SUPGIPMODE_ASYNC_TSC
+ ? RTTimeNanoTSLegacyAsync
+ : pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO
+ ? RTTimeNanoTSLegacySyncInvarNoDelta
+ : RTTimeNanoTSLegacySyncInvarWithDelta;
+# else
+ if (pGip->u32Mode == SUPGIPMODE_ASYNC_TSC)
+ pfnWorker = pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
+ ? RTTimeNanoTSLegacyAsyncUseRdtscp
+ : pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL
+ ? RTTimeNanoTSLegacyAsyncUseRdtscpGroupChNumCl
+ : pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
+ ? RTTimeNanoTSLegacyAsyncUseIdtrLim
+ : pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_0B
+ ? RTTimeNanoTSLegacyAsyncUseApicIdExt0B
+ : pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_8000001E
+ ? RTTimeNanoTSLegacyAsyncUseApicIdExt8000001E
+ : pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID
+ ? RTTimeNanoTSLegacyAsyncUseApicId
+ : rtTimeNanoTSInternalFallback;
+ else
+ pfnWorker = pGip->fGetGipCpu & SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
+ ? pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_PRACTICALLY_ZERO
+ ? RTTimeNanoTSLegacySyncInvarNoDelta
+ : RTTimeNanoTSLegacySyncInvarWithDeltaUseRdtscp
+ : pGip->fGetGipCpu & SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
+ ? pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_PRACTICALLY_ZERO
+ ? RTTimeNanoTSLegacySyncInvarNoDelta
+ : RTTimeNanoTSLegacySyncInvarWithDeltaUseIdtrLim
+ : pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_0B
+ ? pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO
+ ? RTTimeNanoTSLegacySyncInvarNoDelta
+ : RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt0B
+ : pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID_EXT_8000001E
+ ? pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO
+ ? RTTimeNanoTSLegacySyncInvarNoDelta
+ : RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt8000001E
+ : pGip->fGetGipCpu & SUPGIPGETCPU_APIC_ID
+ ? pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO
+ ? RTTimeNanoTSLegacySyncInvarNoDelta
+ : RTTimeNanoTSLegacySyncInvarWithDeltaUseApicId
+ : rtTimeNanoTSInternalFallback;
+# endif
+ }
+ }
+ else
+# ifdef IN_RC
+ iWorker = RTTIMENANO_WORKER_FALLBACK;
+# else
+ pfnWorker = rtTimeNanoTSInternalFallback;
+# endif
+
+# ifdef IN_RC
+ ASMAtomicWriteU32((uint32_t volatile *)&g_iWorker, iWorker);
+ return g_apfnWorkers[iWorker](pData, pExtra);
+# else
+ ASMAtomicWritePtr((void * volatile *)&g_pfnWorker, (void *)(uintptr_t)pfnWorker);
+ return pfnWorker(pData, pExtra);
+# endif
+}
+
+# if defined(IN_RING3) || defined(IN_RING0)
+RTDECL(const char *) RTTimeNanoTSWorkerName(void)
+{
+ static const struct { PFNTIMENANOTSINTERNAL pfnWorker; const char *pszName; } s_aWorkersAndNames[] =
+ {
+# define ENTRY(a_fn) { a_fn, #a_fn }
+ ENTRY(RTTimeNanoTSLegacySyncInvarNoDelta),
+ ENTRY(RTTimeNanoTSLFenceSyncInvarNoDelta),
+# ifdef IN_RING3
+ ENTRY(RTTimeNanoTSLegacyAsyncUseApicId),
+ ENTRY(RTTimeNanoTSLegacyAsyncUseApicIdExt0B),
+ ENTRY(RTTimeNanoTSLegacyAsyncUseApicIdExt8000001E),
+ ENTRY(RTTimeNanoTSLegacyAsyncUseRdtscp),
+ ENTRY(RTTimeNanoTSLegacyAsyncUseRdtscpGroupChNumCl),
+ ENTRY(RTTimeNanoTSLegacyAsyncUseIdtrLim),
+ ENTRY(RTTimeNanoTSLegacySyncInvarWithDeltaUseApicId),
+ ENTRY(RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt0B),
+ ENTRY(RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt8000001E),
+ ENTRY(RTTimeNanoTSLegacySyncInvarWithDeltaUseRdtscp),
+ ENTRY(RTTimeNanoTSLegacySyncInvarWithDeltaUseIdtrLim),
+ ENTRY(RTTimeNanoTSLFenceAsyncUseApicId),
+ ENTRY(RTTimeNanoTSLFenceAsyncUseApicIdExt0B),
+ ENTRY(RTTimeNanoTSLFenceAsyncUseApicIdExt8000001E),
+ ENTRY(RTTimeNanoTSLFenceAsyncUseRdtscp),
+ ENTRY(RTTimeNanoTSLFenceAsyncUseRdtscpGroupChNumCl),
+ ENTRY(RTTimeNanoTSLFenceAsyncUseIdtrLim),
+ ENTRY(RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicId),
+ ENTRY(RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt0B),
+ ENTRY(RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt8000001E),
+ ENTRY(RTTimeNanoTSLFenceSyncInvarWithDeltaUseRdtscp),
+ ENTRY(RTTimeNanoTSLFenceSyncInvarWithDeltaUseIdtrLim),
+# else
+ ENTRY(RTTimeNanoTSLegacyAsync),
+ ENTRY(RTTimeNanoTSLegacySyncInvarWithDelta),
+ ENTRY(RTTimeNanoTSLFenceAsync),
+ ENTRY(RTTimeNanoTSLFenceSyncInvarWithDelta),
+# endif
+ ENTRY(rtTimeNanoTSInternalFallback),
+# undef ENTRY
+ };
+ PFNTIMENANOTSINTERNAL pfnWorker = g_pfnWorker;
+ if (pfnWorker == rtTimeNanoTSInternalRediscover)
+ {
+ RTTimeNanoTS();
+ pfnWorker = g_pfnWorker;
+ }
+
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aWorkersAndNames); i++)
+ if (s_aWorkersAndNames[i].pfnWorker == pfnWorker)
+ return s_aWorkersAndNames[i].pszName;
+ AssertFailed();
+ return NULL;
+}
+# endif /* IN_RING3 || IN_RING0 */
+
+#endif /* !IN_GUEST && !RT_NO_GIP */
+
+
+/**
+ * Internal worker for getting the current nanosecond timestamp.
+ */
+DECLINLINE(uint64_t) rtTimeNanoTSInternal(void)
+{
+#if !defined(IN_GUEST) && !defined(RT_NO_GIP)
+# ifdef IN_RC
+ return g_apfnWorkers[g_iWorker](&g_TimeNanoTSData, NULL /*pExtra*/);
+# else
+ return g_pfnWorker(&g_TimeNanoTSData, NULL /*pExtra*/);
+# endif
+#else
+ return RTTimeSystemNanoTS();
+#endif
+}
+
+
+/**
+ * Gets the current nanosecond timestamp.
+ *
+ * @returns nanosecond timestamp.
+ */
+RTDECL(uint64_t) RTTimeNanoTS(void)
+{
+ return rtTimeNanoTSInternal();
+}
+RT_EXPORT_SYMBOL(RTTimeNanoTS);
+
+
+/**
+ * Gets the current millisecond timestamp.
+ *
+ * @returns millisecond timestamp.
+ */
+RTDECL(uint64_t) RTTimeMilliTS(void)
+{
+ return rtTimeNanoTSInternal() / 1000000;
+}
+RT_EXPORT_SYMBOL(RTTimeMilliTS);
+
+
+#if !defined(IN_GUEST) && !defined(RT_NO_GIP)
+/**
+ * Debugging the time api.
+ *
+ * @returns the number of 1ns steps which has been applied by RTTimeNanoTS().
+ */
+RTDECL(uint32_t) RTTimeDbgSteps(void)
+{
+ return g_TimeNanoTSData.c1nsSteps;
+}
+RT_EXPORT_SYMBOL(RTTimeDbgSteps);
+
+
+/**
+ * Debugging the time api.
+ *
+ * @returns the number of times the TSC interval expired RTTimeNanoTS().
+ */
+RTDECL(uint32_t) RTTimeDbgExpired(void)
+{
+ return g_TimeNanoTSData.cExpired;
+}
+RT_EXPORT_SYMBOL(RTTimeDbgExpired);
+
+
+/**
+ * Debugging the time api.
+ *
+ * @returns the number of bad previous values encountered by RTTimeNanoTS().
+ */
+RTDECL(uint32_t) RTTimeDbgBad(void)
+{
+ return g_TimeNanoTSData.cBadPrev;
+}
+RT_EXPORT_SYMBOL(RTTimeDbgBad);
+
+
+/**
+ * Debugging the time api.
+ *
+ * @returns the number of update races in RTTimeNanoTS().
+ */
+RTDECL(uint32_t) RTTimeDbgRaces(void)
+{
+ return g_TimeNanoTSData.cUpdateRaces;
+}
+RT_EXPORT_SYMBOL(RTTimeDbgRaces);
+#endif /* !IN_GUEST && !RT_NO_GIP */
+
diff --git a/src/VBox/Runtime/common/time/timesupA.asm b/src/VBox/Runtime/common/time/timesupA.asm
new file mode 100644
index 00000000..649d3b6f
--- /dev/null
+++ b/src/VBox/Runtime/common/time/timesupA.asm
@@ -0,0 +1,161 @@
+; $Id: timesupA.asm $
+;; @file
+; IPRT - Time using SUPLib, the Assembly Implementation.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%ifndef IN_GUEST
+
+%include "iprt/asmdefs.mac"
+%include "VBox/sup.mac"
+
+;
+; Use the C reference implementation for now.
+;
+%error "This is out of date, use C code. Not worth it for a couple of ticks in some functions and equal or worse performance in others."
+This is out of date
+This is out of date
+This is out of date
+
+
+;; Keep this in sync with iprt/time.h.
+struc RTTIMENANOTSDATA
+ .pu64Prev RTCCPTR_RES 1
+ .pfnBad RTCCPTR_RES 1
+ .pfnRediscover RTCCPTR_RES 1
+ .pvDummy RTCCPTR_RES 1
+ .c1nsSteps resd 1
+ .cExpired resd 1
+ .cBadPrev resd 1
+ .cUpdateRaces resd 1
+endstruc
+
+
+BEGINDATA
+%undef IN_SUPLIB
+%undef IMPORTED_SUPLIB
+%ifdef IN_SUP_R0
+ %define IN_SUPLIB
+%endif
+%ifdef IN_SUP_R3
+ %define IN_SUPLIB
+%endif
+%ifdef IN_SUP_RC
+ %define IN_SUPLIB
+%endif
+%ifdef IN_SUPLIB
+ extern NAME(g_pSUPGlobalInfoPage)
+%elifdef IN_RING0
+ %ifdef RT_OS_WINDOWS
+ %define IMPORTED_SUPLIB
+ extern IMPNAME(g_SUPGlobalInfoPage)
+ %else
+ extern NAME(g_SUPGlobalInfoPage)
+ %endif
+%else
+ %ifdef RT_OS_WINDOWS
+ %define IMPORTED_SUPLIB
+ extern IMPNAME(g_pSUPGlobalInfoPage)
+ %else
+ extern NAME(g_pSUPGlobalInfoPage)
+ %endif
+%endif
+
+BEGINCODE
+
+;
+; The default stuff that works everywhere.
+; Uses cpuid for serializing.
+;
+%undef ASYNC_GIP
+%undef USE_LFENCE
+%undef WITH_TSC_DELTA
+%undef NEED_APIC_ID
+%define NEED_TRANSACTION_ID
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLegacySyncNoDelta
+%include "timesupA.mac"
+
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLegacyInvariantNoDelta
+%include "timesupA.mac"
+
+%define WITH_TSC_DELTA
+%define NEED_APIC_ID
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLegacySyncWithDelta
+%include "timesupA.mac"
+
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLegacyInvariantWithDelta
+%include "timesupA.mac"
+
+%define ASYNC_GIP
+%undef WITH_TSC_DELTA
+%define NEED_APIC_ID
+%ifdef IN_RC
+ %undef NEED_TRANSACTION_ID
+%endif
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLegacyAsync
+%include "timesupA.mac"
+
+
+;
+; Alternative implementation that employs lfence instead of cpuid.
+;
+%undef ASYNC_GIP
+%define USE_LFENCE
+%undef WITH_TSC_DELTA
+%undef NEED_APIC_ID
+%define NEED_TRANSACTION_ID
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLFenceSyncNoDelta
+%include "timesupA.mac"
+
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLFenceInvariantNoDelta
+%include "timesupA.mac"
+
+%define WITH_TSC_DELTA
+%define NEED_APIC_ID
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLFenceSyncWithDelta
+%include "timesupA.mac"
+
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLFenceInvariantWithDelta
+%include "timesupA.mac"
+
+%define ASYNC_GIP
+%undef WITH_TSC_DELTA
+%define NEED_APIC_ID
+%ifdef IN_RC
+ %undef NEED_TRANSACTION_ID
+%endif
+%define rtTimeNanoTSInternalAsm RTTimeNanoTSLFenceAsync
+%include "timesupA.mac"
+
+
+%endif ; !IN_GUEST
diff --git a/src/VBox/Runtime/common/time/timesupA.mac b/src/VBox/Runtime/common/time/timesupA.mac
new file mode 100644
index 00000000..0344e6ce
--- /dev/null
+++ b/src/VBox/Runtime/common/time/timesupA.mac
@@ -0,0 +1,895 @@
+; $Id: timesupA.mac $
+;; @file
+; IPRT - Time using SUPLib, the Assembly Code Template.
+;
+
+;
+; Copyright (C) 2006-2023 Oracle and/or its affiliates.
+;
+; This file is part of VirtualBox base platform packages, as
+; available from https://www.virtualbox.org.
+;
+; This program is free software; you can redistribute it and/or
+; modify it under the terms of the GNU General Public License
+; as published by the Free Software Foundation, in version 3 of the
+; License.
+;
+; This program 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
+; General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with this program; if not, see <https://www.gnu.org/licenses>.
+;
+; The contents of this file may alternatively be used under the terms
+; of the Common Development and Distribution License Version 1.0
+; (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+; in the VirtualBox distribution, in which case the provisions of the
+; CDDL are applicable instead of those of the GPL.
+;
+; You may elect to license modified versions of this file under the
+; terms and conditions of either the GPL or the CDDL or both.
+;
+; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+;
+
+%ifdef RT_ARCH_X86
+;;
+; The x86 assembly implementation of the assembly routines.
+;
+; @returns Nanosecond timestamp.
+; @param pData Pointer to the nanosecond timestamp data.
+;
+BEGINPROC rtTimeNanoTSInternalAsm
+ ;
+ ; Variable definitions.
+ ;
+%define pData [ebp + 08h]
+%define u64RetNanoTS_Hi [ebp - 04h]
+%define u64RetNanoTS [ebp - 08h]
+%define u32UpdateIntervalNS [ebp - 0ch]
+%define u32UpdateIntervalTSC [ebp - 10h]
+%define u64TSC_Hi [ebp - 14h]
+%define u64TSC [ebp - 18h]
+%define u64CurNanoTS_Hi [ebp - 1ch]
+%define u64CurNanoTS [ebp - 20h]
+%define u64PrevNanoTS_Hi [ebp - 24h]
+%define u64PrevNanoTS [ebp - 28h]
+%define u32TransactionId [ebp - 2ch]
+%define u32ApicIdPlus [ebp - 30h]
+%define TmpVar [ebp - 34h]
+%define SavedEBX [ebp - 38h]
+%define SavedEDI [ebp - 3ch]
+%define SavedESI [ebp - 40h]
+
+ ;
+ ; Prolog.
+ ;
+ push ebp
+ mov ebp, esp
+ sub esp, 40h
+ mov SavedEBX, ebx
+ mov SavedEDI, edi
+ mov SavedESI, esi
+
+
+ ;;
+ ;; Read the GIP data and the previous value.
+ ;;
+.ReadGip:
+
+
+ ;
+ ; Load pGip.
+ ;
+%ifdef IMPORTED_SUPLIB
+ %ifdef IN_RING0
+ mov esi, IMP(g_SUPGlobalInfoPage)
+ %else
+ mov esi, IMP(g_pSUPGlobalInfoPage)
+ mov esi, [esi]
+ %endif
+%else
+ mov esi, [NAME(g_pSUPGlobalInfoPage)]
+%endif
+ or esi, esi
+ jz .Rediscover
+ cmp dword [esi + SUPGLOBALINFOPAGE.u32Magic], SUPGLOBALINFOPAGE_MAGIC
+ jne .Rediscover
+
+ ;
+ ; Calc pGipCPU, setting u32ApicIdPlus if necessary.
+ ;
+%ifdef NEED_APIC_ID
+ ; u8ApicId = ASMGetApicId();
+ mov eax, 1
+ cpuid ; expensive
+ %ifdef NEED_TRANSACTION_ID
+ mov u32ApicIdPlus, ebx
+ %endif
+ ; pGipCpu/pGipCpuDelta = &pGip->aCPU[pGip->aiCpuFromApicId[u8ApicId]];
+ shr ebx, 24
+ movzx ebx, word [esi + ebx * 2 + SUPGLOBALINFOPAGE.aiCpuFromApicId]
+ mov eax, SUPGIPCPU_size
+ mul ebx
+ lea edi, [esi + eax + SUPGLOBALINFOPAGE.aCPUs] ; edi == &pGip->aCPU[u8ApicId];
+%endif
+
+%ifdef NEED_TRANSACTION_ID
+ ;
+ ; Serialized loading of u32TransactionId.
+ ;
+ %ifdef ASYNC_GIP
+ mov ebx, [edi + SUPGIPCPU.u32TransactionId]
+ %else
+ mov ebx, [esi + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u32TransactionId]
+ %endif
+ mov u32TransactionId, ebx
+ %ifdef USE_LFENCE
+ lfence
+ %else
+ lock xor dword TmpVar, 0
+ %endif
+%endif
+
+ ;
+ ; Load the data and TSC with delta applied.
+ ;
+ mov eax, [esi + SUPGLOBALINFOPAGE.u32UpdateIntervalNS]
+ mov u32UpdateIntervalNS, eax
+%ifdef ASYNC_GIP ; esi is now free.
+ mov edx, [edi + SUPGIPCPU.u32UpdateIntervalTSC]
+%else
+ mov edx, [esi + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u32UpdateIntervalTSC]
+%endif
+ mov u32UpdateIntervalTSC, edx
+
+ rdtsc
+%ifdef WITH_TSC_DELTA
+ cmp dword [edi + SUPGIPCPU.i64TSCDelta], 0xffffffff
+ je .TscDeltaPossiblyInvalid
+.TscDeltaValid:
+ sub eax, dword [edi + SUPGIPCPU.i64TSCDelta]
+ sbb edx, dword [edi + SUPGIPCPU.i64TSCDelta + 4]
+.TscDeltaNotValid: ; edi is now free.
+%endif
+
+%ifdef ASYNC_GIP
+ mov ecx, [edi + SUPGIPCPU.u64NanoTS]
+ mov esi, [edi + SUPGIPCPU.u64NanoTS + 4]
+%else
+ mov ecx, [esi + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u64NanoTS]
+ mov ebx, [esi + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u64NanoTS + 4]
+%endif
+ mov u64CurNanoTS, ecx
+ mov u64CurNanoTS_Hi, ebx
+%ifdef ASYNC_GIP
+ mov ecx, [edi + SUPGIPCPU.u64TSC]
+ mov ebx, [edi + SUPGIPCPU.u64TSC + 4]
+%else
+ mov ecx, [esi + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u64TSC]
+ mov ebx, [esi + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u64TSC + 4]
+%endif
+ mov u64TSC, ecx
+ mov u64TSC_Hi, ebx
+
+ ; u64PrevNanoTS = ASMAtomicReadU64(pu64Prev);
+ ; This serializes load/save. And with the dependency on the
+ ; RDTSC result, we try to make sure it has completed as well.
+%ifdef ASYNC_GIP
+ mov esi, pData
+ mov esi, [esi + RTTIMENANOTSDATA.pu64Prev]
+%else
+ mov edi, pData
+ mov edi, [esi + RTTIMENANOTSDATA.pu64Prev]
+%endif
+ mov ebx, eax
+ mov ecx, edx
+%ifdef ASYNC_GIP
+ lock cmpxchg8b [esi]
+%else
+ lock cmpxchg8b [edi]
+%endif
+ mov u64PrevNanoTS, eax
+ mov u64PrevNanoTS_Hi, edx
+
+%undef SAVED_u64RetNanoTS
+%ifdef NEED_TRANSACTION_ID
+ ;
+ ; Check that the GIP and CPU didn't change.
+ ; We've already serialized all the loads and stores at this point.
+ ;
+ %ifdef NEED_APIC_ID
+ mov u64RetNanoTS, ebx
+ mov u64RetNanoTS_Hi, ecx
+ %define SAVED_u64RetNanoTS
+ mov eax, 1
+ cpuid
+ cmp u32ApicIdPlus, ebx
+ jne .ReadGip
+ %endif
+ %ifdef ASYNC_GIP
+ mov esi, [edi + SUPGIPCPU.u32TransactionId]
+ %else
+ mov esi, [esi + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u32TransactionId]
+ %endif
+ cmp esi, u32TransactionId
+ jne .ReadGip
+ test esi, 1
+ jnz .ReadGip
+%endif ; NEED_TRANSACTION_ID
+%ifdef SAVED_u64RetNanoTS
+ mov ebx, u64RetNanoTS
+ mov ecx, u64RetNanoTS_Hi
+%endif
+
+ ;;
+ ;; Calc the timestamp.
+ ;;
+ ; u64RetNanoTS -= u64TSC;
+ sub ebx, u64TSC
+ sbb ecx, u64TSC_Hi
+
+ ; if (u64RetNanoTS > u32UpdateIntervalTSC) -> jump
+ or ecx, ecx
+ jnz .OverFlow
+ cmp ebx, u32UpdateIntervalTSC
+ ja .OverFlow
+ mov eax, ebx
+.ContinueCalcs: ; eax <= u32UpdateIntervalTSC
+ mul dword u32UpdateIntervalNS
+ div dword u32UpdateIntervalTSC
+ xor edx, edx
+
+ ; u64RetNanoTS += u64CurNanoTS;
+ add eax, u64CurNanoTS
+ adc edx, u64CurNanoTS_Hi
+
+ ;;
+ ;; Compare it with the previous one.
+ ;;
+ ; if (RT_LIKELY( u64RetNanoTS > u64PrevNanoTS
+ ; && u64RetNanoTS < u64PrevNanoTS + UINT64_C(86000000000000) /* 24h */))
+ ;; @todo optimize this compare (/me too tired).
+ mov ecx, u64PrevNanoTS_Hi
+ mov ebx, u64PrevNanoTS
+ cmp edx, ecx
+ ja .Compare2
+ jb .DeltaPrevTooBig
+ cmp eax, ebx
+ jbe .DeltaPrevTooBig
+
+.Compare2:
+ add ebx, 0x6F736000
+ adc ecx, 0x00004E37
+ cmp edx, ecx
+ jb .CompareDone
+ ja .DeltaPrevTooBig
+ cmp eax, ebx
+ jae .DeltaPrevTooBig
+.CompareDone:
+
+
+ ;;
+ ;; Update the previous value with the u64RetNanoTS value.
+ ;;
+.Update:
+ ; if (RT_LIKELY(ASMAtomicCmpXchgU64(&pData->u64Prev, u64RetNanoTS, u64PrevNanoTS)))
+ mov ebx, eax
+ mov ecx, edx
+ mov esi, pData
+ mov esi, [esi + RTTIMENANOTSDATA.pu64Prev]
+ mov eax, u64PrevNanoTS
+ mov edx, u64PrevNanoTS_Hi
+ lock cmpxchg8b [esi]
+ jnz .UpdateFailed
+
+.Updated:
+ mov eax, ebx
+ mov edx, ecx
+
+.Done:
+ mov esi, SavedESI
+ mov edi, SavedEDI
+ mov ebx, SavedEBX
+ leave
+ ret
+
+
+ ;;
+ ;; We've expired the interval, cap it. If we're here for the 2nd
+ ;; time without any GIP update in-between, the checks against
+ ;; pData->u64Prev below will force 1ns stepping.
+ ;;
+.OverFlow:
+ ; u64Delta = u32UpdateIntervalTSC;
+ mov esi, pData
+ inc dword [esi + RTTIMENANOTSDATA.cExpired]
+ mov eax, u32UpdateIntervalTSC
+ jmp .ContinueCalcs
+
+
+ ;;
+ ;; u64DeltaPrev >= 24h
+ ;;
+ ;; eax:edx = u64RetNanoTS (to be adjusted)
+ ;;
+.DeltaPrevTooBig:
+ ; uint64_t u64DeltaPrev = u64RetNanoTS - u64PrevNanoTS;
+ mov ebx, eax
+ sub ebx, u64PrevNanoTS
+ mov ecx, edx
+ sbb ecx, u64PrevNanoTS_Hi ; ebx:ecx = u64DeltaPrev
+
+ ; else if ( (int64_t)u64DeltaPrev <= 0
+ ; && (int64_t)u64DeltaPrev + u32UpdateIntervalNS * 2 >= 0)
+ ; {
+ ; /* Occasional - u64RetNanoTS is in the recent 'past' relative the previous call. */
+ ; pData->c1nsSteps++;
+ ; u64RetNanoTS = u64PrevNanoTS + 1;
+ ; }
+ mov esi, u32UpdateIntervalNS
+ cmp ecx, 0
+ jl .PrevNotZero2ndTest
+ jg .DeltaPrevNotInRecentPast
+ cmp ebx, 0
+ ja .DeltaPrevNotInRecentPast
+
+.PrevNotZero2ndTest:
+ add esi, esi ; ASSUMES: u32UpdateIntervalNS * 2 <= 32-bit.
+ xor edi, edi
+ add esi, ebx
+ adc edi, ecx
+ test edi, edi
+ js .DeltaPrevNotInRecentPast
+
+.DeltaPrevInRecentPast:
+ mov esi, pData
+ inc dword [esi + RTTIMENANOTSDATA.c1nsSteps]
+ mov eax, u64PrevNanoTS
+ mov edx, u64PrevNanoTS_Hi
+ add eax, 1
+ adc edx, 0
+ jmp .Update
+
+.DeltaPrevNotInRecentPast:
+ ; else if (!u64PrevNanoTS) /* We're resuming (see TMVirtualResume). */
+ ; /* do nothing */;
+ cmp dword u64PrevNanoTS, 0
+ jne .DeltaPrevNotZero
+ cmp dword u64PrevNanoTS_Hi, 0
+ jne .DeltaPrevNotZero
+ jmp .Update
+
+.DeltaPrevNotZero:
+ ; else
+ ; {
+ ; /* Something has gone bust, if negative offset it's real bad. */
+ ; rtTimeNanoTSInternalBitch(pVM,
+ ; }
+
+ ; call C function that does the bitching.
+ mov u64RetNanoTS, eax
+ mov u64RetNanoTS_Hi, edx
+
+ mov edi, u64PrevNanoTS_Hi
+ mov esi, u64PrevNanoTS
+ push edi
+ push esi ; 4 - u64PrevNanoTS
+ push ecx
+ push ebx ; 3 - u64DeltaPrev
+ push edx
+ push eax ; 2 - u64RetNanoTS
+ mov eax, pData
+ push eax ; 1 - pData
+ call dword [eax + RTTIMENANOTSDATA.pfnBad]
+ add esp, 4*7
+
+ mov eax, u64RetNanoTS
+ mov edx, u64RetNanoTS_Hi
+ jmp .Update
+
+
+ ;;
+ ;; Attempt updating the previous value, provided we're still ahead of it.
+ ;;
+ ;; There is no point in recalculating u64NanoTS because we got preempted or if
+ ;; we raced somebody while the GIP was updated, since these are events
+ ;; that might occur at any point in the return path as well.
+ ;;
+ ;; eax:edx = *pData->u64Prev
+ ;; ebx:ecx = u64RetNanoTS
+ ;;
+ ALIGNCODE(16)
+.UpdateFailed:
+ mov edi, pData
+ lock inc dword [edi + RTTIMENANOTSDATA.cUpdateRaces]
+ ; for (i = 0; i < 10; i++)
+ mov edi, 10
+.UpdateLoop:
+ ; if (u64PrevNanoTS >= u64NanoTS)
+ ; break;
+ cmp edx, ecx
+ jg .Updated
+ jne .UpdateLoopLess
+ cmp eax, ebx
+ jae .Updated
+.UpdateLoopLess:
+ ; retry
+ lock cmpxchg8b [esi]
+ jz .Updated
+ dec edi
+ jnz .UpdateLoop
+ jmp .Updated
+
+
+ ;;
+ ;; The GIP is seemingly invalid, redo the discovery.
+ ;;
+.Rediscover:
+ mov eax, pData
+ push eax
+ call [eax + RTTIMENANOTSDATA.pfnRediscover]
+ add esp, 4h
+ jmp .Done
+
+
+%ifdef WITH_TSC_DELTA
+ ;;
+ ;; Unlikely branch for when we think the TSC delta might be invalid.
+ ;;
+.TscDeltaPossiblyInvalid:
+ cmp dword [edi + SUPGIPCPU.i64TSCDelta + 4], 0x7fffffff
+ jne .TscDeltaValid
+ jmp .TscDeltaNotValid
+%endif
+
+ ;
+ ; Cleanup variables
+ ;
+%undef pData
+%undef u64Delta_Hi
+%undef u64Delta
+%undef u32UpdateIntervalNS
+%undef u32UpdateIntervalTSC
+%undef u64TSC_Hi
+%undef u64TSC
+%undef u64NanoTS_Hi
+%undef u64NanoTS
+%undef u64PrevNanoTS_Hi
+%undef u64PrevNanoTS
+%undef u32TransactionId
+%undef u8ApicId
+
+%else ; AMD64
+
+;;
+; The AMD64 assembly implementation of the assembly routines.
+;
+; @returns Nanosecond timestamp.
+; @param pData gcc:rdi msc:rcx Pointer to the nanosecond timestamp data.
+;
+BEGINPROC rtTimeNanoTSInternalAsm
+ ;
+ ; Define variables and stack frame.
+ ;
+%define SavedRBX [rbp - 08h]
+%define SavedR12 [rbp - 10h]
+%define SavedR13 [rbp - 18h]
+%define SavedRDI [rbp - 20h]
+%define SavedRSI [rbp - 28h]
+%define TmpVar [rbp - 30h]
+%define TmpVar2 [rbp - 38h]
+%ifdef NEED_TRANSACTION_ID
+ %ifdef NEED_APIC_ID
+ %define SavedR14 [rbp - 40h]
+ %define SavedR15 [rbp - 48h]
+ %endif
+%endif
+
+%define pData rdi
+
+%ifdef ASYNC_GIP
+ %define u64TSC rsi
+ %define pGip rsi
+ %ifdef NEED_APIC_ID
+ %define pGipCPU r8
+ %endif
+%else
+ %define u64TSC r8
+ %define pGip rsi
+ %ifdef NEED_APIC_ID
+ %define pGipCPU r8
+ %endif
+%endif
+%define u32TransactionId r9d
+%define u64CurNanoTS r10
+%define u64PrevNanoTS r11 ; not parameter register
+%define u32UpdateIntervalTSC r12d
+%define u32UpdateIntervalTSC_64 r12
+%define u32UpdateIntervalNS r13d
+%define u32UpdateIntervalNS_64 r13
+%undef u64SavedRetNanoTS
+%undef u32ApicIdPlus
+%ifdef NEED_TRANSACTION_ID
+ %ifdef NEED_APIC_ID
+ %define u64SavedRetNanoTS r14
+ %define u32ApicIdPlus r15d
+ %endif
+%endif
+
+ ;
+ ; The prolog.
+ ;
+ push rbp
+ mov rbp, rsp
+%ifdef ASM_CALL64_MSC
+ sub rsp, 50h+20h
+%else
+ sub rsp, 50h
+%endif
+ mov SavedRBX, rbx
+ mov SavedR12, r12
+ mov SavedR13, r13
+%ifdef ASM_CALL64_MSC
+ mov SavedRDI, rdi
+ mov SavedRSI, rsi
+ mov pData, rcx
+%else
+ ;mov pData, rdi - already in rdi.
+%endif
+%ifdef SavedR14
+ mov SavedR14, r14
+%endif
+%ifdef SavedR15
+ mov SavedR15, r15
+%endif
+
+
+ ;;
+ ;; Data fetch loop.
+ ;; We take great pain ensuring that data consistency here.
+ ;;
+.ReadGip:
+
+ ;
+ ; Load pGip - finding the GIP is fun...
+ ;
+%ifdef RT_OS_WINDOWS
+ %ifdef IMPORTED_SUPLIB
+ %ifdef IN_RING0
+ mov rax, qword IMP(g_SUPGlobalInfoPage)
+ mov pGip, rax
+ %else
+ mov pGip, [IMP(g_pSUPGlobalInfoPage) wrt rip]
+ mov pGip, [pGip]
+ %endif
+ %else
+ mov pGip, [NAME(g_pSUPGlobalInfoPage) wrt rip]
+ %endif
+%else
+ %ifdef IN_RING0
+ mov rax, qword NAME(g_SUPGlobalInfoPage)
+ mov pGip, rax
+ %else
+ mov pGip, [rel NAME(g_pSUPGlobalInfoPage) wrt ..gotpcrel]
+ mov pGip, [pGip]
+ %endif
+%endif
+ or pGip, pGip
+ jz .Rediscover
+ cmp dword [pGip + SUPGLOBALINFOPAGE.u32Magic], SUPGLOBALINFOPAGE_MAGIC
+ jne .Rediscover
+
+ ;
+ ; pGipCPU, setting u32ApicIdPlus if necessary.
+ ;
+%ifdef NEED_APIC_ID
+ ; u8ApicId = ASMGetApicId();
+ mov eax, 1
+ cpuid ; expensive
+ %ifdef NEED_TRANSACTION_ID
+ mov u32ApicIdPlus, ebx
+ %endif
+ ; pGipCPU = &pGip->aCPU[pGip->aiCpuFromApicId[u8ApicId]];
+ shr ebx, 24
+ movzx eax, word [pGip + rbx * 2 + SUPGLOBALINFOPAGE.aiCpuFromApicId]
+ imul eax, SUPGIPCPU_size
+ lea pGipCPU, [pGip + rax + SUPGLOBALINFOPAGE.aCPUs]
+%endif
+
+%ifdef NEED_TRANSACTION_ID
+ ;
+ ; Serialized loading of u32TransactionId.
+ ;
+ %ifdef ASYNC_GIP
+ mov u32TransactionId, [pGipCPU + SUPGIPCPU.u32TransactionId]
+ %else
+ mov u32TransactionId, [pGip + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u32TransactionId]
+ %endif
+ %ifdef USE_LFENCE
+ lfence
+ %else
+ lock xor dword TmpVar, 0
+ %endif
+%endif
+
+ ;
+ ; Load the data and TSC.
+ ;
+ mov u32UpdateIntervalNS, [pGip + SUPGLOBALINFOPAGE.u32UpdateIntervalNS]
+%ifdef ASYNC_GIP
+ mov u32UpdateIntervalTSC, [pGipCPU + SUPGIPCPU.u32UpdateIntervalTSC]
+%else
+ mov u32UpdateIntervalTSC, [pGip + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u32UpdateIntervalTSC]
+%endif
+
+ rdtsc
+ mov u64PrevNanoTS, [pData + RTTIMENANOTSDATA.pu64Prev]
+ mov u64PrevNanoTS, [u64PrevNanoTS]
+ shl rdx, 32
+ or rax, rdx ; rax is u64RetNanoTS.
+%ifdef WITH_TSC_DELTA
+ mov rdx, [pGipCPU + SUPGIPCPU.i64TSCDelta]
+ mov u64CurNanoTS, 0x7fffffffffffffff ; INT64_MAX - temporarily borrowing u64CurNanoTS
+ cmp rdx, u64CurNanoTS
+ je .TscDeltaNotValid
+ sub rax, rdx
+.TscDeltaNotValid:
+%endif
+%ifdef u64SavedRetNanoTS ; doing this here may save a tick or so?
+ mov u64SavedRetNanoTS, rax
+%endif
+
+%ifdef ASYNC_GIP
+ mov u64CurNanoTS, [pGipCPU + SUPGIPCPU.u64NanoTS]
+ mov u64TSC, [pGipCPU + SUPGIPCPU.u64TSC] ; transhes pGIP!
+%else
+ mov u64CurNanoTS, [pGip + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u64NanoTS]
+ mov u64TSC, [pGip + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u64TSC] ; trashes pGipCPU!
+%endif
+
+
+%ifdef NEED_TRANSACTION_ID
+ ;
+ ; Check that the GIP and CPU didn't change.
+ ;
+ ; It is crucial that the rdtsc instruction has completed before
+ ; we check the transaction id. The LOCK prefixed instruction with
+ ; dependency on the RDTSC result should do the trick, I think.
+ ; CPUID is serializing, so the async path is safe by default.
+ ;
+ %ifdef NEED_APIC_ID
+ mov eax, 1
+ cpuid
+ cmp u32ApicIdPlus, ebx
+ jne .ReadGip
+ %else
+ lock xor qword TmpVar, rax
+ %endif
+ %ifdef ASYNC_GIP
+ cmp u32TransactionId, [pGipCPU + SUPGIPCPU.u32TransactionId]
+ %else
+ cmp u32TransactionId, [pGip + SUPGLOBALINFOPAGE.aCPUs + SUPGIPCPU.u32TransactionId]
+ %endif
+ jne .ReadGip
+ test u32TransactionId, 1
+ jnz .ReadGip
+ %ifdef u64SavedRetNanoTS
+ mov rax, u64SavedRetNanoTS ; rax is u64RetNanoTS.
+ %endif
+%endif ; NEED_TRANSACTION_ID
+
+
+ ;;
+ ;; Calc the timestamp.
+ ;;
+ ; u64RetNanoTS -= u64TSC;
+ sub rax, u64TSC
+ xor edx, edx
+
+ ; if (u64RetNanoTS > u32UpdateIntervalTSC) -> jump
+ cmp rax, u32UpdateIntervalTSC_64
+ ja .OverFlow
+.ContinueCalcs: ; edx = 0; eax <= u32UpdateIntervalTSC
+ mul u32UpdateIntervalNS
+ div u32UpdateIntervalTSC
+
+ ; u64RetNanoTS += u64CurNanoTS;
+ add rax, u64CurNanoTS
+
+
+ ;;
+ ;; Compare it with the previous one.
+ ;;
+ ; if (RT_LIKELY( u64RetNanoTS > u64PrevNanoTS
+ ; && u64RetNanoTS < u64PrevNanoTS + UINT64_C(86000000000000) /* 24h */))
+ ; /* Frequent - less than 24h since last call. */;
+ cmp rax, u64PrevNanoTS
+ jbe .DeltaPrevTooBig
+ mov ecx, 5
+ shl rcx, 44 ; close enough
+ add rcx, u64PrevNanoTS
+ cmp rax, rcx
+ jae .DeltaPrevTooBig
+
+
+ ;;
+ ;; Update the previous value.
+ ;;
+.Update:
+ ; if (RT_LIKELY(ASMAtomicCmpXchgU64(&pData->u64Prev, u64RetNanoTS, u64PrevNanoTS)))
+ mov rbx, [pData + RTTIMENANOTSDATA.pu64Prev]
+ mov rcx, rax
+ mov rax, u64PrevNanoTS
+ lock cmpxchg [rbx], rcx
+ jnz .UpdateFailed
+
+.Updated:
+ mov rax, rcx
+
+.Done:
+ mov rbx, SavedRBX
+ mov r12, SavedR12
+ mov r13, SavedR13
+%ifdef SavedR14
+ mov r14, SavedR14
+%endif
+%ifdef SavedR15
+ mov r15, SavedR15
+%endif
+%ifdef ASM_CALL64_MSC
+ mov rdi, SavedRDI
+ mov rsi, SavedRSI
+%endif
+ leave
+ ret
+
+
+ ;;
+ ;; We've expired the interval, cap it. If we're here for the 2nd
+ ;; time without any GIP update in-between, the checks against
+ ;; pData->u64Prev below will force 1ns stepping.
+ ;;
+ALIGNCODE(16)
+.OverFlow:
+ ; u64RetNanoTS = u32UpdateIntervalTSC;
+ inc dword [pData + RTTIMENANOTSDATA.cExpired]
+ mov eax, u32UpdateIntervalTSC
+ jmp .ContinueCalcs
+
+
+ ;;
+ ;; u64DeltaPrev >= 24h
+ ;;
+ ;; rax = u64RetNanoTS (to be adjusted)
+ ;;
+ALIGNCODE(16)
+.DeltaPrevTooBig:
+ ; uint64_t u64DeltaPrev = u64RetNanoTS - u64PrevNanoTS;
+ mov rbx, rax
+ sub rbx, u64PrevNanoTS
+
+ ; else if ( (int64_t)u64DeltaPrev <= 0
+ ; && (int64_t)u64DeltaPrev + u32UpdateIntervalNS * 2 >= 0)
+ ; {
+ ; /* Occasional - u64NanoTS is in the recent 'past' relative the previous call. */
+ ; pData->c1nsSteps++;
+ ; u64RetNanoTS = u64PrevNanoTS + 1;
+ ; }
+ test rbx, rbx
+ jg .DeltaPrevNotInRecentPast
+
+ lea rdx, [u32UpdateIntervalNS_64 + u32UpdateIntervalNS_64]
+ add rdx, rbx
+ js .DeltaPrevNotInRecentPast
+
+ ; body
+ inc dword [pData + RTTIMENANOTSDATA.c1nsSteps]
+ lea rax, [u64PrevNanoTS + 1]
+ jmp .Update
+
+ ; else if (!u64PrevNanoTS) /* We're resuming (see TMVirtualResume) / first call. */
+ ; /* do nothing */;
+.DeltaPrevNotInRecentPast:
+ or u64PrevNanoTS, u64PrevNanoTS
+ jz .Update
+
+ ; else
+ ; {
+ ; /* Something has gone bust, if negative offset it's real bad. */
+ ; rtTimeNanoTSInternalBitch(pVM,
+ ; }
+
+ ; call C function that does the bitching.
+ mov TmpVar, rax
+ mov TmpVar2, pData
+
+%ifdef ASM_CALL64_MSC
+ mov rcx, pData ; param 1 - pData
+ mov rdx, rax ; param 2 - u64RetNanoTS
+ mov r8, rbx ; param 3 - u64DeltaPrev
+ mov r9, u64PrevNanoTS ; param 4 - u64PrevNanoTS
+%else
+ ;mov rdi, pData - already in rdi; param 1 - pData
+ mov rsi, rax ; param 2 - u64RetNanoTS
+ mov rdx, rbx ; param 3 - u64DeltaPrev
+ mov rcx, u64PrevNanoTS ; param 4 - u64PrevNanoTS
+%endif
+ call qword [pData + RTTIMENANOTSDATA.pfnBad]
+
+ mov rax, TmpVar
+ mov pData, TmpVar2
+ jmp .Update
+
+
+ ;;
+ ;; Attempt updating the previous value, provided we're still ahead of it.
+ ;;
+ ;; There is no point in recalculating u64NanoTS because we got preempted or if
+ ;; we raced somebody while the GIP was updated, since these are events
+ ;; that might occur at any point in the return path as well.
+ ;;
+ ;; rax = *pData->u64Prev;
+ ;; rcx = u64RetNanoTS
+ ;;
+ALIGNCODE(16)
+.UpdateFailed:
+ lock inc dword [pData + RTTIMENANOTSDATA.cUpdateRaces]
+ ; for (i = 0; i < 10; i++)
+ mov edx, 10
+.UpdateLoop:
+ ; if (u64PrevNanoTS >= u64RetNanoTS)
+ ; break;
+ cmp rax, rcx
+ jge .Updated
+.UpdateLoopLess:
+ ; retry
+ lock cmpxchg [rbx], rcx
+ jz .Updated
+ dec edx
+ jnz .UpdateLoop
+ jmp .Updated
+
+
+ ;;
+ ;; The GIP is seemingly invalid, redo the discovery.
+ ;;
+.Rediscover:
+%ifdef ASM_CALL64_MSC
+ mov rcx, pData
+%else
+ ; mov rdi, pData - already in rdi
+%endif
+ call [pData + RTTIMENANOTSDATA.pfnRediscover]
+ jmp .Done
+
+
+ ;
+ ; Cleanup variables
+ ;
+%undef SavedRBX
+%undef SavedR12
+%undef SavedR13
+%undef SavedR14
+%undef SavedR15
+%undef SavedRDI
+%undef SavedRSI
+%undef pData
+%undef TmpVar
+%undef u64TSC
+%undef pGip
+%undef pGipCPU
+%undef u32TransactionId
+%undef u64CurNanoTS
+%undef u64PrevNanoTS
+%undef u32UpdateIntervalTSC
+%undef u32UpdateIntervalTSC_64
+%undef u32UpdateIntervalNS
+%undef u64SavedRetNanoTS
+%undef u32ApicIdPlus
+
+%endif ; AMD64
+ENDPROC rtTimeNanoTSInternalAsm
+
diff --git a/src/VBox/Runtime/common/time/timesupref.cpp b/src/VBox/Runtime/common/time/timesupref.cpp
new file mode 100644
index 00000000..211bbb04
--- /dev/null
+++ b/src/VBox/Runtime/common/time/timesupref.cpp
@@ -0,0 +1,318 @@
+/* $Id: timesupref.cpp $ */
+/** @file
+ * IPRT - Time using SUPLib, the C Implementation.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#if !defined(IN_GUEST) && !defined(RT_NO_GIP)
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/time.h>
+#include "internal/iprt.h"
+
+#include <iprt/asm.h>
+#include <iprt/asm-math.h>
+#include <iprt/asm-amd64-x86.h>
+#include <iprt/param.h>
+#include <iprt/string.h>
+#include <VBox/sup.h>
+#ifdef IN_RC
+# include <VBox/vmm/vmm.h>
+# include <VBox/vmm/vm.h>
+#endif
+#include "internal/time.h"
+
+
+#define TMPL_MODE_SYNC_INVAR_NO_DELTA 1
+#define TMPL_MODE_SYNC_INVAR_WITH_DELTA 2
+#define TMPL_MODE_ASYNC 3
+
+
+/*
+ * Use the XCHG instruction for some kind of serialization.
+ */
+#define TMPL_READ_FENCE() ASMReadFence()
+
+#undef TMPL_MODE
+#define TMPL_MODE TMPL_MODE_SYNC_INVAR_NO_DELTA
+#undef TMPL_GET_CPU_METHOD
+#define TMPL_GET_CPU_METHOD 0
+#undef rtTimeNanoTSInternalRef
+#define rtTimeNanoTSInternalRef RTTimeNanoTSLegacySyncInvarNoDelta
+#include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacySyncInvarNoDelta);
+
+#ifdef IN_RING3
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacySyncInvarWithDeltaUseApicId
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacySyncInvarWithDeltaUseApicId);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID_EXT_0B
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt0B
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt0B);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID_EXT_8000001E
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt8000001E
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacySyncInvarWithDeltaUseApicIdExt8000001E);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacySyncInvarWithDeltaUseRdtscp
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacySyncInvarWithDeltaUseRdtscp);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacySyncInvarWithDeltaUseIdtrLim
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacySyncInvarWithDeltaUseIdtrLim);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_ASYNC
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacyAsyncUseApicId
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacyAsyncUseApicId);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_ASYNC
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID_EXT_0B
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacyAsyncUseApicIdExt0B
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacyAsyncUseApicIdExt0B);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_ASYNC
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID_EXT_8000001E
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacyAsyncUseApicIdExt8000001E
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacyAsyncUseApicIdExt8000001E);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacyAsyncUseRdtscp
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacyAsyncUseRdtscp);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacyAsyncUseIdtrLim
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacyAsyncUseIdtrLim);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacyAsyncUseRdtscpGroupChNumCl
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacyAsyncUseRdtscpGroupChNumCl);
+
+#else /* IN_RC || IN_RING0: Disable interrupts and call getter function. */
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD UINT32_MAX
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacySyncInvarWithDelta
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacySyncInvarWithDelta);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_ASYNC
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLegacyAsync
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLegacyAsync);
+
+#endif
+
+
+/*
+ * Use LFENCE for load serialization.
+ */
+#undef TMPL_READ_FENCE
+#define TMPL_READ_FENCE() ASMReadFenceSSE2()
+
+#undef TMPL_MODE
+#define TMPL_MODE TMPL_MODE_SYNC_INVAR_NO_DELTA
+#undef TMPL_GET_CPU_METHOD
+#define TMPL_GET_CPU_METHOD 0
+#undef rtTimeNanoTSInternalRef
+#define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceSyncInvarNoDelta
+#include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceSyncInvarNoDelta);
+
+#ifdef IN_RING3
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicId
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicId);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID_EXT_0B
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt0B
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt0B);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID_EXT_8000001E
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt8000001E
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceSyncInvarWithDeltaUseApicIdExt8000001E);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceSyncInvarWithDeltaUseRdtscp
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceSyncInvarWithDeltaUseRdtscp);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceSyncInvarWithDeltaUseIdtrLim
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceSyncInvarWithDeltaUseIdtrLim);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_ASYNC
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceAsyncUseApicId
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceAsyncUseApicId);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_ASYNC
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID_EXT_0B
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceAsyncUseApicIdExt0B
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceAsyncUseApicIdExt0B);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_ASYNC
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_APIC_ID_EXT_8000001E
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceAsyncUseApicIdExt8000001E
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceAsyncUseApicIdExt8000001E);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceAsyncUseRdtscp
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceAsyncUseRdtscp);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceAsyncUseIdtrLim
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceAsyncUseIdtrLim);
+
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceAsyncUseRdtscpGroupChNumCl
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceAsyncUseRdtscpGroupChNumCl);
+
+#else /* IN_RC || IN_RING0: Disable interrupts and call getter function. */
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# undef TMPL_GET_CPU_METHOD
+# define TMPL_GET_CPU_METHOD UINT32_MAX
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceSyncInvarWithDelta
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceSyncInvarWithDelta);
+
+# undef TMPL_MODE
+# define TMPL_MODE TMPL_MODE_ASYNC
+# undef rtTimeNanoTSInternalRef
+# define rtTimeNanoTSInternalRef RTTimeNanoTSLFenceAsync
+# include "timesupref.h"
+RT_EXPORT_SYMBOL(RTTimeNanoTSLFenceAsync);
+
+#endif
+
+
+#endif /* !IN_GUEST && !RT_NO_GIP */
+
diff --git a/src/VBox/Runtime/common/time/timesupref.h b/src/VBox/Runtime/common/time/timesupref.h
new file mode 100644
index 00000000..02bb820c
--- /dev/null
+++ b/src/VBox/Runtime/common/time/timesupref.h
@@ -0,0 +1,408 @@
+/* $Id: timesupref.h $ */
+/** @file
+ * IPRT - Time using SUPLib, the C Code Template.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/**
+ * The C reference implementation of the assembly routines.
+ *
+ * Calculate NanoTS using the information in the global information page (GIP)
+ * which the support library (SUPLib) exports.
+ *
+ * This function guarantees that the returned timestamp is later (in time) than
+ * any previous calls in the same thread.
+ *
+ * @remark The way the ever increasing time guarantee is currently implemented means
+ * that if you call this function at a frequency higher than 1GHz you're in for
+ * trouble. We currently assume that no idiot will do that for real life purposes.
+ *
+ * @returns Nanosecond timestamp.
+ * @param pData Pointer to the data structure.
+ * @param pExtra Where to return extra time info. Optional.
+ */
+RTDECL(uint64_t) rtTimeNanoTSInternalRef(PRTTIMENANOTSDATA pData, PRTITMENANOTSEXTRA pExtra)
+{
+#if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA && defined(IN_RING3)
+ PSUPGIPCPU pGipCpuAttemptedTscRecalibration = NULL;
+#endif
+ AssertCompile(RT_IS_POWER_OF_TWO(RTCPUSET_MAX_CPUS));
+
+ for (;;)
+ {
+#ifndef IN_RING3 /* This simplifies and improves everything. */
+ RTCCUINTREG const uFlags = ASMIntDisableFlags();
+#endif
+
+ /*
+ * Check that the GIP is sane and that the premises for this worker function
+ * hasn't changed (CPU onlined with bad delta or missing features).
+ */
+ PSUPGLOBALINFOPAGE pGip = g_pSUPGlobalInfoPage;
+ if ( RT_LIKELY(pGip)
+ && RT_LIKELY(pGip->u32Magic == SUPGLOBALINFOPAGE_MAGIC)
+#if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
+ && RT_LIKELY(pGip->enmUseTscDelta >= SUPGIPUSETSCDELTA_PRACTICALLY_ZERO)
+#else
+ && RT_LIKELY(pGip->enmUseTscDelta <= SUPGIPUSETSCDELTA_ROUGHLY_ZERO)
+#endif
+#if defined(IN_RING3) && TMPL_GET_CPU_METHOD != 0
+ && RT_LIKELY(pGip->fGetGipCpu & TMPL_GET_CPU_METHOD)
+#endif
+ )
+ {
+ /*
+ * Resolve pGipCpu if needed. If the instruction is serializing, we
+ * read the transaction id first if possible.
+ */
+#if TMPL_MODE == TMPL_MODE_ASYNC || TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# if defined(IN_RING0)
+ uint32_t const iCpuSet = RTMpCurSetIndex();
+ uint16_t const iGipCpu = iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx)
+ ? pGip->aiCpuFromCpuSetIdx[iCpuSet] : UINT16_MAX;
+# elif defined(IN_RC)
+ uint32_t const iCpuSet = VMMGetCpu(&g_VM)->iHostCpuSet;
+ uint16_t const iGipCpu = iCpuSet < RT_ELEMENTS(pGip->aiCpuFromCpuSetIdx)
+ ? pGip->aiCpuFromCpuSetIdx[iCpuSet] : UINT16_MAX;
+# elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_APIC_ID
+# if TMPL_MODE != TMPL_MODE_ASYNC
+ uint32_t const u32TransactionId = pGip->aCPUs[0].u32TransactionId;
+# endif
+ uint8_t const idApic = ASMGetApicId();
+ uint16_t const iGipCpu = pGip->aiCpuFromApicId[idApic];
+# elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_APIC_ID_EXT_0B
+# if TMPL_MODE != TMPL_MODE_ASYNC
+ uint32_t const u32TransactionId = pGip->aCPUs[0].u32TransactionId;
+# endif
+ uint32_t const idApic = ASMGetApicIdExt0B();
+ uint16_t const iGipCpu = pGip->aiCpuFromApicId[idApic];
+# elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_APIC_ID_EXT_8000001E
+# if TMPL_MODE != TMPL_MODE_ASYNC
+ uint32_t const u32TransactionId = pGip->aCPUs[0].u32TransactionId;
+# endif
+ uint32_t const idApic = ASMGetApicIdExt8000001E();
+ uint16_t const iGipCpu = pGip->aiCpuFromApicId[idApic];
+# elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS \
+ || TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL
+# if TMPL_MODE != TMPL_MODE_ASYNC
+ uint32_t const u32TransactionId = pGip->aCPUs[0].u32TransactionId;
+# endif
+ uint32_t uAux;
+ ASMReadTscWithAux(&uAux);
+# if TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS
+ uint16_t const iCpuSet = uAux & (RTCPUSET_MAX_CPUS - 1);
+# else
+ uint16_t iCpuSet = 0;
+ uint16_t offGipCpuGroup = pGip->aoffCpuGroup[(uAux >> 8) & UINT8_MAX];
+ if (offGipCpuGroup < pGip->cPages * PAGE_SIZE)
+ {
+ PSUPGIPCPUGROUP pGipCpuGroup = (PSUPGIPCPUGROUP)((uintptr_t)pGip + offGipCpuGroup);
+ if ( (uAux & UINT8_MAX) < pGipCpuGroup->cMaxMembers
+ && pGipCpuGroup->aiCpuSetIdxs[uAux & UINT8_MAX] != -1)
+ iCpuSet = pGipCpuGroup->aiCpuSetIdxs[uAux & UINT8_MAX];
+ }
+# endif
+ uint16_t const iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet];
+# elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
+ uint16_t const cbLim = ASMGetIdtrLimit();
+ uint16_t const iCpuSet = (cbLim - 256 * (ARCH_BITS == 64 ? 16 : 8)) & (RTCPUSET_MAX_CPUS - 1);
+ uint16_t const iGipCpu = pGip->aiCpuFromCpuSetIdx[iCpuSet];
+# else
+# error "What?"
+# endif
+ if (RT_LIKELY(iGipCpu < pGip->cCpus))
+ {
+ PSUPGIPCPU pGipCpu = &pGip->aCPUs[iGipCpu];
+#else
+ {
+#endif
+ /*
+ * Get the transaction ID if necessary and we haven't already
+ * read it before a serializing instruction above. We can skip
+ * this for ASYNC_TSC mode in ring-0 and raw-mode context since
+ * we disable interrupts.
+ */
+#if TMPL_MODE == TMPL_MODE_ASYNC && defined(IN_RING3)
+ uint32_t const u32TransactionId = pGipCpu->u32TransactionId;
+ ASMCompilerBarrier();
+ TMPL_READ_FENCE();
+#elif TMPL_MODE != TMPL_MODE_ASYNC \
+ && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID \
+ && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID_EXT_0B \
+ && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID_EXT_8000001E \
+ && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS \
+ && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL
+ uint32_t const u32TransactionId = pGip->aCPUs[0].u32TransactionId;
+ ASMCompilerBarrier();
+ TMPL_READ_FENCE();
+#endif
+
+ /*
+ * Gather all the data we need. The mess at the end is to make
+ * sure all loads are done before we recheck the transaction ID
+ * without triggering serializing twice.
+ */
+ uint32_t u32NanoTSFactor0 = pGip->u32UpdateIntervalNS;
+#if TMPL_MODE == TMPL_MODE_ASYNC
+ uint32_t u32UpdateIntervalTSC = pGipCpu->u32UpdateIntervalTSC;
+ uint64_t u64NanoTS = pGipCpu->u64NanoTS;
+ uint64_t u64TSC = pGipCpu->u64TSC;
+#else
+ uint32_t u32UpdateIntervalTSC = pGip->aCPUs[0].u32UpdateIntervalTSC;
+ uint64_t u64NanoTS = pGip->aCPUs[0].u64NanoTS;
+ uint64_t u64TSC = pGip->aCPUs[0].u64TSC;
+# if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
+ int64_t i64TscDelta = pGipCpu->i64TSCDelta;
+# endif
+#endif
+ uint64_t u64PrevNanoTS = ASMAtomicUoReadU64(pData->pu64Prev);
+#if TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS \
+ || TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL
+ ASMCompilerBarrier();
+ uint32_t uAux2;
+ uint64_t u64Delta = ASMReadTscWithAux(&uAux2); /* serializing */
+#else
+ uint64_t u64Delta = ASMReadTSC();
+ ASMCompilerBarrier();
+# if TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID /* getting APIC will serialize */ \
+ && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID_EXT_0B \
+ && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID_EXT_8000001E \
+ && (defined(IN_RING3) || TMPL_MODE != TMPL_MODE_ASYNC)
+ TMPL_READ_FENCE(); /* Expensive (~30 ticks). Would like convincing argumentation that let us remove it. */
+# endif
+#endif
+
+ /*
+ * Check that we didn't change CPU.
+ */
+#if defined(IN_RING3) && ( TMPL_MODE == TMPL_MODE_ASYNC || TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA )
+# if TMPL_GET_CPU_METHOD == SUPGIPGETCPU_APIC_ID
+ if (RT_LIKELY(ASMGetApicId() == idApic))
+# elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_APIC_ID_EXT_0B
+ if (RT_LIKELY(ASMGetApicIdExt0B() == idApic))
+# elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_APIC_ID_EXT_8000001E
+ if (RT_LIKELY(ASMGetApicIdExt8000001E() == idApic))
+# elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_MASK_MAX_SET_CPUS \
+ || TMPL_GET_CPU_METHOD == SUPGIPGETCPU_RDTSCP_GROUP_IN_CH_NUMBER_IN_CL
+ if (RT_LIKELY(uAux2 == uAux))
+# elif TMPL_GET_CPU_METHOD == SUPGIPGETCPU_IDTR_LIMIT_MASK_MAX_SET_CPUS
+ if (RT_LIKELY(ASMGetIdtrLimit() == cbLim))
+# endif
+#endif
+ {
+ /*
+ * Check the transaction ID (see above for R0/RC + ASYNC).
+ */
+#if defined(IN_RING3) || TMPL_MODE != TMPL_MODE_ASYNC
+# if TMPL_MODE == TMPL_MODE_ASYNC
+ if (RT_LIKELY(pGipCpu->u32TransactionId == u32TransactionId && !(u32TransactionId & 1) ))
+# else
+ if (RT_LIKELY(pGip->aCPUs[0].u32TransactionId == u32TransactionId && !(u32TransactionId & 1) ))
+# endif
+#endif
+ {
+
+ /*
+ * Apply the TSC delta. If the delta is invalid and the
+ * execution allows it, try trigger delta recalibration.
+ */
+#if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA && defined(IN_RING3)
+ if (RT_LIKELY( i64TscDelta != INT64_MAX
+ || pGipCpu == pGipCpuAttemptedTscRecalibration))
+#endif
+ {
+#if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
+# ifndef IN_RING3
+ if (RT_LIKELY(i64TscDelta != INT64_MAX))
+# endif
+ u64Delta -= i64TscDelta;
+#endif
+
+ /*
+ * Bingo! We've got a consistent set of data.
+ */
+#ifndef IN_RING3
+ ASMSetFlags(uFlags);
+#endif
+
+ if (pExtra)
+ pExtra->uTSCValue = u64Delta;
+
+ /*
+ * Calc NanoTS delta.
+ */
+ u64Delta -= u64TSC;
+ if (RT_LIKELY(u64Delta <= u32UpdateIntervalTSC))
+ { /* MSVC branch hint, probably pointless. */ }
+ else
+ {
+ /*
+ * We've expired the interval, cap it. If we're here for the 2nd
+ * time without any GIP update in-between, the checks against
+ * *pu64Prev below will force 1ns stepping.
+ */
+ ASMAtomicIncU32(&pData->cExpired);
+ u64Delta = u32UpdateIntervalTSC;
+ }
+#if !defined(_MSC_VER) || !defined(RT_ARCH_X86) /* GCC makes very pretty code from these two inline calls, while MSC cannot. */
+ u64Delta = ASMMult2xU32RetU64((uint32_t)u64Delta, u32NanoTSFactor0);
+ u64Delta = ASMDivU64ByU32RetU32(u64Delta, u32UpdateIntervalTSC);
+#else
+ __asm
+ {
+ mov eax, dword ptr [u64Delta]
+ mul dword ptr [u32NanoTSFactor0]
+ div dword ptr [u32UpdateIntervalTSC]
+ mov dword ptr [u64Delta], eax
+ xor edx, edx
+ mov dword ptr [u64Delta + 4], edx
+ }
+#endif
+
+ /*
+ * Calculate the time and compare it with the previously returned value.
+ */
+ u64NanoTS += u64Delta;
+ uint64_t u64DeltaPrev = u64NanoTS - u64PrevNanoTS;
+ if (RT_LIKELY( u64DeltaPrev > 0
+ && u64DeltaPrev < UINT64_C(86000000000000) /* 24h */))
+ { /* Frequent - less than 24h since last call. */ }
+ else if (RT_LIKELY( (int64_t)u64DeltaPrev <= 0
+ && (int64_t)u64DeltaPrev + u32NanoTSFactor0 * 2 >= 0))
+ {
+ /* Occasional - u64NanoTS is in the recent 'past' relative the previous call. */
+ ASMAtomicIncU32(&pData->c1nsSteps);
+ u64NanoTS = u64PrevNanoTS + 1;
+ }
+ else if (!u64PrevNanoTS)
+ /* We're resuming (see TMVirtualResume). */;
+ else
+ {
+ /* Something has gone bust, if negative offset it's real bad. */
+ ASMAtomicIncU32(&pData->cBadPrev);
+ pData->pfnBad(pData, u64NanoTS, u64DeltaPrev, u64PrevNanoTS);
+ }
+
+ /*
+ * Attempt updating the previous value, provided we're still ahead of it.
+ *
+ * There is no point in recalculating u64NanoTS because we got preempted or if
+ * we raced somebody while the GIP was updated, since these are events
+ * that might occur at any point in the return path as well.
+ */
+ if (RT_LIKELY(ASMAtomicCmpXchgU64(pData->pu64Prev, u64NanoTS, u64PrevNanoTS)))
+ return u64NanoTS;
+
+ ASMAtomicIncU32(&pData->cUpdateRaces);
+ for (int cTries = 25; cTries > 0; cTries--)
+ {
+ u64PrevNanoTS = ASMAtomicReadU64(pData->pu64Prev);
+ if (u64PrevNanoTS >= u64NanoTS)
+ break;
+ if (ASMAtomicCmpXchgU64(pData->pu64Prev, u64NanoTS, u64PrevNanoTS))
+ break;
+ ASMNopPause();
+ }
+ return u64NanoTS;
+ }
+
+#if TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA && defined(IN_RING3)
+ /*
+ * Call into the support driver to try make it recalculate the delta. We
+ * remember which GIP CPU structure we're probably working on so we won't
+ * end up in a loop if the driver for some reason cannot get the job done.
+ */
+ else /* else is unecessary, but helps checking the preprocessor spaghetti. */
+ {
+ pGipCpuAttemptedTscRecalibration = pGipCpu;
+ uint64_t u64TscTmp;
+ uint16_t idApicUpdate;
+ int rc = SUPR3ReadTsc(&u64TscTmp, &idApicUpdate);
+ if (RT_SUCCESS(rc) && idApicUpdate < RT_ELEMENTS(pGip->aiCpuFromApicId))
+ {
+ uint32_t iUpdateGipCpu = pGip->aiCpuFromApicId[idApicUpdate];
+ if (iUpdateGipCpu < pGip->cCpus)
+ pGipCpuAttemptedTscRecalibration = &pGip->aCPUs[iUpdateGipCpu];
+ }
+ }
+#endif
+ }
+ }
+
+ /*
+ * No joy must try again.
+ */
+#ifdef _MSC_VER
+# pragma warning(disable: 4702)
+#endif
+#ifndef IN_RING3
+ ASMSetFlags(uFlags);
+#endif
+ ASMNopPause();
+ continue;
+ }
+
+#if TMPL_MODE == TMPL_MODE_ASYNC || TMPL_MODE == TMPL_MODE_SYNC_INVAR_WITH_DELTA
+ /*
+ * We've got a bad CPU or APIC index of some kind.
+ */
+ else /* else is unecessary, but helps checking the preprocessor spaghetti. */
+ {
+# ifndef IN_RING3
+ ASMSetFlags(uFlags);
+# endif
+# if defined(IN_RING0) \
+ || defined(IN_RC) \
+ || ( TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID \
+ && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID_EXT_0B /*?*/ \
+ && TMPL_GET_CPU_METHOD != SUPGIPGETCPU_APIC_ID_EXT_8000001E /*?*/)
+ return pData->pfnBadCpuIndex(pData, pExtra, UINT16_MAX-1, iCpuSet, iGipCpu);
+# else
+ return pData->pfnBadCpuIndex(pData, pExtra, idApic, UINT16_MAX-1, iGipCpu);
+# endif
+ }
+#endif
+ }
+
+ /*
+ * Something changed in the GIP config or it was unmapped, figure out
+ * the right worker function to use now.
+ */
+#ifndef IN_RING3
+ ASMSetFlags(uFlags);
+#endif
+ return pData->pfnRediscover(pData, pExtra);
+ }
+}
+
diff --git a/src/VBox/Runtime/common/time/timesysalias.cpp b/src/VBox/Runtime/common/time/timesysalias.cpp
new file mode 100644
index 00000000..a355a4ed
--- /dev/null
+++ b/src/VBox/Runtime/common/time/timesysalias.cpp
@@ -0,0 +1,67 @@
+/* $Id: timesysalias.cpp $ */
+/** @file
+ * IPRT - Time using RTTimeSystem*.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/time.h>
+#include "internal/iprt.h"
+
+
+/**
+ * Gets the current nanosecond timestamp.
+ *
+ * @returns nanosecond timestamp.
+ */
+RTDECL(uint64_t) RTTimeNanoTS(void)
+{
+ return RTTimeSystemNanoTS();
+}
+RT_EXPORT_SYMBOL(RTTimeNanoTS);
+
+
+/**
+ * Gets the current millisecond timestamp.
+ *
+ * @returns millisecond timestamp.
+ */
+RTDECL(uint64_t) RTTimeMilliTS(void)
+{
+ return RTTimeSystemMilliTS();
+}
+RT_EXPORT_SYMBOL(RTTimeMilliTS);
+
diff --git a/src/VBox/Runtime/common/time/timezoneinfo-gen.py b/src/VBox/Runtime/common/time/timezoneinfo-gen.py
new file mode 100755
index 00000000..6f5670bc
--- /dev/null
+++ b/src/VBox/Runtime/common/time/timezoneinfo-gen.py
@@ -0,0 +1,470 @@
+# -*- coding: utf-8 -*-
+# $Id: timezoneinfo-gen.py $
+
+"""
+Generates timezone mapping info from public domain tz data and
+simple windows tables.
+"""
+from __future__ import print_function;
+
+__copyright__ = \
+"""
+Copyright (C) 2017-2023 Oracle and/or its affiliates.
+
+This file is part of VirtualBox base platform packages, as
+available from https://www.virtualbox.org.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation, in version 3 of the
+License.
+
+This program 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
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, see <https://www.gnu.org/licenses>.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+in the VirtualBox distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+
+SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+"""
+__version__ = "$Revision: 155244 $"
+
+import os;
+import sys;
+import xml.etree.ElementTree as ElementTree;
+
+
+class TzWinZoneEntry(object):
+ def __init__(self):
+ self.sWinName = None;
+ self.sWinTerritory = None;
+ self.fWinGolden = False;
+ self.idxWin = 0;
+
+class TzLinkEntry(TzWinZoneEntry):
+ def __init__(self, sLinkNm, sTarget):
+ TzWinZoneEntry.__init__(self);
+ self.sLinkNm = sLinkNm;
+ self.sTarget = sTarget;
+
+class TzZoneOffset(object):
+ def __init__(self, asFields):
+ self.sOffset = asFields[0]; # GMT offset expression
+ self.sRules = asFields[1] if len(asFields) > 1 and asFields[1] not in [ '-', '' ] else None;
+ self.sFormat = asFields[2] if len(asFields) > 2 and asFields[2] not in [ '-', '' ] else None;
+ self.sUntil = asFields[3] if len(asFields) > 3 and asFields[3] not in [ '-', '' ] else None;
+
+class TzZoneEntry(TzWinZoneEntry):
+ def __init__(self, sName):
+ TzWinZoneEntry.__init__(self);
+ self.sName = sName;
+ self.sTerritory = 'ZZ';
+ self.aOffsets = []; # type: list(TzZoneOffset)
+
+class TzZoneRule(object):
+ def __init__(self, sName, sFrom, sTo, sType, sIn, sOn, sAt, sSave, sLetter):
+ self.sName = sName;
+ self.sFrom = sFrom if sFrom not in [ '-', '' ] else None;
+ self.sTo = sTo if sFrom not in [ '-', '' ] else None;
+ self.sType = sType if sType not in [ '-', '' ] else None;
+ self.sIn = sIn if sIn not in [ '-', '' ] else None;
+ self.sAt = sAt if sAt not in [ '-', '' ] else None;
+ self.sSave = sSave if sSave not in [ '-', '' ] else None;
+ self.sLetter = sLetter if sLetter not in [ '-', '' ] else None;
+
+def info(sMsg):
+ """
+ Outputs an informational message to stderr.
+ """
+ print('info: ' + sMsg, file=sys.stderr);
+
+def warning(sMsg):
+ """
+ Outputs a warning (to stderr).
+ """
+ print('warning: ' + sMsg, file=sys.stderr);
+
+def error(sMsg):
+ """
+ Outputs a warning (to stderr).
+ """
+ print('error: ' + sMsg, file=sys.stderr);
+
+def readTzDataFile(sFile):
+ """ Reads the given data file into memory, stripping comments. """
+ oInFile = open(sFile, 'r');
+ asLines = oInFile.readlines();
+ oInFile.close();
+ iLine = 0;
+ while iLine < len(asLines):
+ offHash = asLines[iLine].find('#');
+ if offHash >= 0:
+ asLines[iLine] = asLines[iLine][:offHash].rstrip();
+ else:
+ asLines[iLine] = asLines[iLine].rstrip();
+ iLine += 1;
+ return asLines;
+
+#
+# tzdata structures.
+#
+g_dZones = {};
+g_dRules = {};
+g_dLinks = {};
+
+def readTzData(sTzDataDir):
+ """
+ Reads in the bits we want from tz data. Assumes 2017b edition.
+ """
+
+ #
+ # Parse the tzdata files.
+ #
+ for sFile in [ 'africa', 'antarctica', 'asia', 'australasia', 'europe', 'northamerica', 'southamerica',
+ 'pacificnew', 'etcetera', 'backward', 'systemv', 'factory', #'backzone'
+ ]:
+ sIn = 'none';
+ asLines = readTzDataFile(os.path.join(sTzDataDir, sFile));
+ iLine = 0;
+ while iLine < len(asLines):
+ sLine = asLines[iLine];
+ sStrippedLine = sLine.strip(); # Fully stripped version.
+ if sStrippedLine:
+ asFields = sLine.split();
+ try:
+ if sLine.startswith('Zone'): # 'Rule' NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+ sIn = 'Zone';
+ oZone = TzZoneEntry(asFields[1]);
+ if oZone.sName in g_dZones: raise Exception('duplicate: %s' % (oZone.sName,));
+ g_dZones[oZone.sName] = oZone;
+ oZone.aOffsets.append(TzZoneOffset(asFields[2:]));
+ elif sLine.startswith('Rule'): # 'Rule' NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+ oRule = TzZoneRule(asFields[1], asFields[2], asFields[3], asFields[4], asFields[5],
+ asFields[6], asFields[7], asFields[8], asFields[9]);
+ if oRule.sName not in g_dRules:
+ g_dRules[oRule] = [oRule,];
+ else:
+ g_dRules[oRule].append(oRule);
+ elif sLine.startswith('Link'):
+ if len(asFields) != 3: raise Exception("malformed link: len(asFields) = %d" % (len(asFields)));
+ oLink = TzLinkEntry(asFields[2].strip(), asFields[1].strip());
+ if oLink.sLinkNm not in g_dLinks:
+ g_dLinks[oLink.sLinkNm] = oLink;
+ elif g_dLinks[oLink.sLinkNm].sTarget != oLink.sTarget:
+ warning('duplicate link for %s: new target %s, previous %s'
+ % (oLink.sLinkNm, oLink.sTarget, g_dLinks[oLink.sLinkNm].sTarget,));
+ elif sIn == 'Zone':
+ oZone.aOffsets.append(TzZoneEntry(asFields[3:]));
+ else:
+ raise Exception('what is this?')
+ except Exception as oXcpt:
+ error("line %u in %s: '%s'" % (iLine + 1, sFile, type(oXcpt) if not str(oXcpt) else str(oXcpt),));
+ info("'%s'" % (asLines[iLine],));
+ return 1;
+ iLine += 1;
+
+ #
+ # Process the country <-> zone mapping file.
+ #
+ asLines = readTzDataFile(os.path.join(sTzDataDir, 'zone.tab'));
+ iLine = 0;
+ while iLine < len(asLines):
+ sLine = asLines[iLine];
+ if sLine and sLine[0] != ' ':
+ asFields = sLine.split('\t');
+ try:
+ sTerritory = asFields[0];
+ if len(sTerritory) != 2: raise Exception('malformed country: %s' % (sTerritory,));
+ sZone = asFields[2];
+ oZone = g_dZones.get(sZone);
+ if oZone:
+ if oZone.sTerritory and oZone.sTerritory != 'ZZ':
+ raise Exception('zone %s already have country %s associated with it (setting %s)'
+ % (sZone, oZone.sTerritory, sTerritory));
+ oZone.sTerritory = sTerritory;
+ else:
+ oLink = g_dLinks.get(sZone);
+ if oLink:
+ pass; # ignore country<->link associations for now.
+ else: raise Exception('country zone not found: %s' % (sZone,));
+
+ except Exception as oXcpt:
+ error("line %u in %s: '%s'" % (iLine + 1, 'zone.tab', type(oXcpt) if not str(oXcpt) else str(oXcpt),));
+ info("'%s'" % (asLines[iLine],));
+ return 1;
+ iLine += 1;
+ return 0
+
+
+def readWindowsToTzMap(sMapXml):
+ """
+ Reads the 'common/supplemental/windowsZones.xml' file from http://cldr.unicode.org/.
+ """
+ oXmlDoc = ElementTree.parse(sMapXml);
+ oMap = oXmlDoc.getroot().find('windowsZones').find('mapTimezones');
+ # <mapZone other="Line Islands Standard Time" territory="001" type="Pacific/Kiritimati"/>
+ for oChild in oMap.findall('mapZone'):
+ sTerritory = oChild.attrib['territory'];
+ sWinZone = oChild.attrib['other'];
+ asUnixZones = oChild.attrib['type'].split();
+ for sZone in asUnixZones:
+ oZone = g_dZones.get(sZone);
+ if oZone:
+ if oZone.sWinName is None or (oZone.sWinTerritory == '001' and oZone.sWinName == sWinZone):
+ oZone.sWinName = sWinZone;
+ oZone.sWinTerritory = sTerritory;
+ if sTerritory == '001':
+ oZone.fWinGolden = True;
+ else:
+ warning('zone "%s" have more than one windows mapping: %s (%s) and now %s (%s)'
+ % (sZone, oZone.sWinName, oZone.sWinTerritory, sWinZone, sTerritory));
+ else:
+ oLink = g_dLinks.get(sZone);
+ if oLink:
+ if oLink.sWinName is None or (oLink.sWinTerritory == '001' and oLink.sWinName == sWinZone):
+ oLink.sWinName = sWinZone;
+ oLink.sWinTerritory = sTerritory;
+ if sTerritory == '001':
+ oLink.fWinGolden = True;
+ else:
+ warning('zone-link "%s" have more than one windows mapping: %s (%s) and now %s (%s)'
+ % (sZone, oLink.sWinName, oLink.sWinTerritory, sWinZone, sTerritory));
+ else:
+ warning('could not find zone "%s" (for mapping win zone "%s" to) - got the right data sets?'
+ % (sZone, sWinZone));
+ return 0;
+
+
+def readWindowsIndexes(sFile):
+ """
+ Reads the windows time zone index from the table in the given file and sets idxWin.
+
+ Assumes format: index{tab}name{tab}(GMT{offset}){space}{cities}
+
+ For instance: https://support.microsoft.com/en-gb/help/973627/microsoft-time-zone-index-values
+ """
+ # Read the file.
+ oInFile = open(sFile, "r");
+ asLines = oInFile.readlines();
+ oInFile.close();
+
+ # Check the header.
+ if not asLines[0].startswith('Index'):
+ error('expected first line of "%s" to start with "Index"' % (sFile,));
+ return 1;
+ fHexIndex = asLines[0].find('hex') > 0;
+ iLine = 1;
+ while iLine < len(asLines):
+ # Parse.
+ asFields = asLines[iLine].split('\t');
+ try:
+ idxWin = int(asFields[0].strip(), 16 if fHexIndex else 10);
+ sWinName = asFields[1].strip();
+ sLocations = ' '.join(asFields[2].split());
+ if sWinName.find('(GMT') >= 0: raise Exception("oops #1");
+ if not sLocations.startswith('(GMT'): raise Exception("oops #2");
+ sStdOffset = sLocations[sLocations.find('(') + 1 : sLocations.find(')')].strip().replace(' ','');
+ sLocations = sLocations[sLocations.find(')') + 1 : ].strip();
+ except Exception as oXcpt:
+ error("line %u in %s: '%s'" % (iLine + 1, sFile, type(oXcpt) if not str(oXcpt) else str(oXcpt),));
+ info("'%s'" % (asLines[iLine],));
+ return 1;
+
+ # Some name adjustments.
+ sWinName = sWinName.lower();
+ if sWinName.startswith('a.u.s.'):
+ sWinName = 'aus' + sWinName[6:];
+ elif sWinName.startswith('u.s. '):
+ sWinName = 'us ' + sWinName[5:];
+ elif sWinName.startswith('s.a. '):
+ sWinName = 'sa ' + sWinName[5:];
+ elif sWinName.startswith('s.e. '):
+ sWinName = 'se ' + sWinName[5:];
+ elif sWinName.startswith('pacific s.a. '):
+ sWinName = 'pacific sa ' + sWinName[13:];
+
+ # Update zone entries with matching windows names.
+ cUpdates = 0;
+ for sZone in g_dZones:
+ oZone = g_dZones[sZone];
+ if oZone.sWinName and oZone.sWinName.lower() == sWinName:
+ oZone.idxWin = idxWin;
+ cUpdates += 1;
+ #info('idxWin=%#x - %s / %s' % (idxWin, oZone.sName, oZone.sWinName,));
+ if cUpdates == 0:
+ warning('No matching zone found for index zone "%s" (%#x, %s)' % (sWinName, idxWin, sLocations));
+
+ # Advance.
+ iLine += 1;
+ return 0;
+
+def getPadding(sField, cchWidth):
+ """ Returns space padding for the given field string. """
+ if len(sField) < cchWidth:
+ return ' ' * (cchWidth - len(sField));
+ return '';
+
+def formatFields(sName, oZone, oWinZone):
+ """ Formats the table fields. """
+
+ # RTTIMEZONEINFO:
+ # const char *pszUnixName;
+ # const char *pszWindowsName;
+ # uint8_t cchUnixName;
+ # uint8_t cchWindowsName;
+ # char szCountry[3];
+ # char szWindowsCountry[3];
+ # uint32_t idxWindows;
+ # uint32_t uReserved;
+
+ asFields = [ '"%s"' % sName, ];
+ if oWinZone.sWinName:
+ asFields.append('"%s"' % oWinZone.sWinName);
+ else:
+ asFields.append('NULL');
+
+ asFields.append('%u' % (len(sName),));
+ if oWinZone.sWinName:
+ asFields.append('%u' % (len(oWinZone.sWinName),));
+ else:
+ asFields.append('0');
+
+ asFields.append('"%s"' % (oZone.sTerritory,));
+ if oWinZone.sWinTerritory:
+ asFields.append('"%s"' % (oWinZone.sWinTerritory,));
+ else:
+ asFields.append('""');
+ asFields.append('%#010x' % (oWinZone.idxWin,));
+
+ asFlags = [];
+ if oWinZone.fWinGolden:
+ asFlags.append('RTTIMEZONEINFO_F_GOLDEN');
+ if asFlags:
+ asFields.append(' | '.join(asFlags));
+ else:
+ asFields.append('0');
+ return asFields;
+
+def produceCode(oDst):
+ """
+ Produces the tables.
+ """
+
+ #
+ # Produce the info table.
+ #
+ aasEntries = [];
+
+ # The straight zones.
+ for sZone in g_dZones:
+ asFields = formatFields(sZone, g_dZones[sZone], g_dZones[sZone]);
+ aasEntries.append(asFields);
+
+ # The links.
+ for sZone in g_dLinks:
+ oLink = g_dLinks[sZone];
+ asFields = formatFields(sZone, g_dZones[oLink.sTarget], oLink);
+ aasEntries.append(asFields);
+
+ # Figure field lengths.
+ acchFields = [ 2, 2, 2, 2, 4, 4, 10, 1 ];
+ for asFields in aasEntries:
+ assert len(asFields) == len(acchFields);
+ for iField, sField in enumerate(asFields):
+ if len(sField) > acchFields[iField]:
+ acchFields[iField] = len(sField);
+
+ # Sort the data on zone name.
+ aasEntries.sort();
+
+ # Do the formatting.
+ oDst.write('/**\n'
+ ' * Static time zone mapping info. Sorted by pszUnixName.\n'
+ ' */\n'
+ 'static const RTTIMEZONEINFO g_aTimeZones[] =\n'
+ '{\n');
+ for iEntry, asFields in enumerate(aasEntries):
+ sLine = ' { ';
+ for iField, sField in enumerate(asFields):
+ sLine += sField;
+ sLine += ', ';
+ sLine += getPadding(sField, acchFields[iField]);
+ sLine += ' }, /* %#05x */\n' % (iEntry,);
+ oDst.write(sLine);
+ oDst.write('};\n'
+ '\n');
+
+ #
+ # Now produce a lookup table for windows time zone names, with indexes into
+ # the g_aTimeZone table.
+ #
+ aasLookup = [];
+ for iEntry, asFields in enumerate(aasEntries):
+ if asFields[1] != 'NULL':
+ aasLookup.append([ asFields[1], # sWinName
+ -1 if asFields[7].find('RTTIMEZONEINFO_F_GOLDEN') >= 0 else 1,
+ asFields[5], # sWinTerritory
+ iEntry,
+ asFields[0]]); # sZone
+ aasLookup.sort();
+
+ oDst.write('/**\n'
+ ' * Windows time zone lookup table. Sorted by name, golden flag and territory.\n'
+ ' */\n'
+ 'static const uint16_t g_aidxWinTimeZones[] = \n'
+ '{\n');
+ for asFields in aasLookup:
+ sLine = ' %#05x, /* %s' % (asFields[3], asFields[0][1:-1]);
+ sLine += getPadding(asFields[0], acchFields[1]);
+ sLine += ' / %s%s' % (asFields[2][1:-1], '+' if asFields[1] < 0 else ' ');
+ if len(asFields[2]) == 2:
+ sLine += ' ';
+ sLine += ' ==> %s */\n' % (asFields[4][1:-1],)
+ oDst.write(sLine);
+
+ oDst.write('};\n'
+ '\n');
+
+ return 0;
+
+
+def main(asArgs):
+ """
+ C-like main function.
+ """
+ if len(asArgs) != 4:
+ error("Takes exacty three arguments: <ms-index-file> <ms-key-file> <tz-data-dir>");
+ return 1;
+ sTzDataDir = asArgs[1];
+ sWinToTzMap = asArgs[2];
+ sWinIndexTable = asArgs[3];
+
+ #
+ # Read in the data first.
+ #
+ iRc = readTzData(sTzDataDir);
+ if iRc == 0:
+ iRc = readWindowsToTzMap(sWinToTzMap);
+ if iRc == 0:
+ iRc = readWindowsIndexes(sWinIndexTable);
+ if iRc == 0:
+ #
+ # Produce the C table.
+ #
+ iRc = produceCode(sys.stdout);
+ return iRc;
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv));
+
diff --git a/src/VBox/Runtime/common/time/timezoneinfo.cpp b/src/VBox/Runtime/common/time/timezoneinfo.cpp
new file mode 100644
index 00000000..b6efb164
--- /dev/null
+++ b/src/VBox/Runtime/common/time/timezoneinfo.cpp
@@ -0,0 +1,1171 @@
+/* $Id: timezoneinfo.cpp $ */
+/** @file
+ * IPRT - Time zone mapping info.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_TIME
+#include <iprt/time.h>
+#include "internal/iprt.h"
+
+#include <iprt/string.h>
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Static time zone mapping info. Sorted by pszUnixName.
+ */
+static const RTTIMEZONEINFO g_aTimeZones[] =
+{
+ { "Africa/Abidjan", "Greenwich Standard Time", 14, 23, "CI", "CI", 0x0000005a, 0, }, /* 0x000 */
+ { "Africa/Accra", "Greenwich Standard Time", 12, 23, "GH", "GH", 0x0000005a, 0, }, /* 0x001 */
+ { "Africa/Addis_Ababa", "E. Africa Standard Time", 18, 23, "KE", "ET", 0x00000000, 0, }, /* 0x002 */
+ { "Africa/Algiers", "W. Central Africa Standard Time", 14, 31, "DZ", "DZ", 0x00000071, 0, }, /* 0x003 */
+ { "Africa/Asmara", NULL, 13, 0, "KE", "", 0x00000000, 0, }, /* 0x004 */
+ { "Africa/Asmera", "E. Africa Standard Time", 13, 23, "KE", "ER", 0x00000000, 0, }, /* 0x005 */
+ { "Africa/Bamako", "Greenwich Standard Time", 13, 23, "CI", "ML", 0x00000000, 0, }, /* 0x006 */
+ { "Africa/Bangui", "W. Central Africa Standard Time", 13, 31, "NG", "CF", 0x00000000, 0, }, /* 0x007 */
+ { "Africa/Banjul", "Greenwich Standard Time", 13, 23, "CI", "GM", 0x00000000, 0, }, /* 0x008 */
+ { "Africa/Bissau", "Greenwich Standard Time", 13, 23, "GW", "GW", 0x0000005a, 0, }, /* 0x009 */
+ { "Africa/Blantyre", "South Africa Standard Time", 15, 26, "MZ", "MW", 0x00000000, 0, }, /* 0x00a */
+ { "Africa/Brazzaville", "W. Central Africa Standard Time", 18, 31, "NG", "CG", 0x00000000, 0, }, /* 0x00b */
+ { "Africa/Bujumbura", "South Africa Standard Time", 16, 26, "MZ", "BI", 0x00000000, 0, }, /* 0x00c */
+ { "Africa/Cairo", "Egypt Standard Time", 12, 19, "EG", "EG", 0x00000078, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x00d */
+ { "Africa/Casablanca", "Morocco Standard Time", 17, 21, "MA", "MA", 0x8000004d, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x00e */
+ { "Africa/Ceuta", "Romance Standard Time", 12, 21, "ES", "ES", 0x00000069, 0, }, /* 0x00f */
+ { "Africa/Conakry", "Greenwich Standard Time", 14, 23, "CI", "GN", 0x00000000, 0, }, /* 0x010 */
+ { "Africa/Dakar", "Greenwich Standard Time", 12, 23, "CI", "SN", 0x00000000, 0, }, /* 0x011 */
+ { "Africa/Dar_es_Salaam", "E. Africa Standard Time", 20, 23, "KE", "TZ", 0x00000000, 0, }, /* 0x012 */
+ { "Africa/Djibouti", "E. Africa Standard Time", 15, 23, "KE", "DJ", 0x00000000, 0, }, /* 0x013 */
+ { "Africa/Douala", "W. Central Africa Standard Time", 13, 31, "NG", "CM", 0x00000000, 0, }, /* 0x014 */
+ { "Africa/El_Aaiun", "Morocco Standard Time", 15, 21, "EH", "EH", 0x8000004d, 0, }, /* 0x015 */
+ { "Africa/Freetown", "Greenwich Standard Time", 15, 23, "CI", "SL", 0x00000000, 0, }, /* 0x016 */
+ { "Africa/Gaborone", "South Africa Standard Time", 15, 26, "MZ", "BW", 0x00000000, 0, }, /* 0x017 */
+ { "Africa/Harare", "South Africa Standard Time", 13, 26, "MZ", "ZW", 0x00000000, 0, }, /* 0x018 */
+ { "Africa/Johannesburg", "South Africa Standard Time", 19, 26, "ZA", "ZA", 0x0000008c, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x019 */
+ { "Africa/Juba", "E. Africa Standard Time", 11, 23, "SD", "SS", 0x00000000, 0, }, /* 0x01a */
+ { "Africa/Kampala", "E. Africa Standard Time", 14, 23, "KE", "UG", 0x00000000, 0, }, /* 0x01b */
+ { "Africa/Khartoum", "E. Africa Standard Time", 15, 23, "SD", "SD", 0x0000009b, 0, }, /* 0x01c */
+ { "Africa/Kigali", "South Africa Standard Time", 13, 26, "MZ", "RW", 0x00000000, 0, }, /* 0x01d */
+ { "Africa/Kinshasa", "W. Central Africa Standard Time", 15, 31, "NG", "CD", 0x00000000, 0, }, /* 0x01e */
+ { "Africa/Lagos", "W. Central Africa Standard Time", 12, 31, "NG", "NG", 0x00000071, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x01f */
+ { "Africa/Libreville", "W. Central Africa Standard Time", 17, 31, "NG", "GA", 0x00000000, 0, }, /* 0x020 */
+ { "Africa/Lome", "Greenwich Standard Time", 11, 23, "CI", "TG", 0x00000000, 0, }, /* 0x021 */
+ { "Africa/Luanda", "W. Central Africa Standard Time", 13, 31, "NG", "AO", 0x00000000, 0, }, /* 0x022 */
+ { "Africa/Lubumbashi", "South Africa Standard Time", 17, 26, "MZ", "CD", 0x00000000, 0, }, /* 0x023 */
+ { "Africa/Lusaka", "South Africa Standard Time", 13, 26, "MZ", "ZM", 0x00000000, 0, }, /* 0x024 */
+ { "Africa/Malabo", "W. Central Africa Standard Time", 13, 31, "NG", "GQ", 0x00000000, 0, }, /* 0x025 */
+ { "Africa/Maputo", "South Africa Standard Time", 13, 26, "MZ", "MZ", 0x0000008c, 0, }, /* 0x026 */
+ { "Africa/Maseru", "South Africa Standard Time", 13, 26, "ZA", "LS", 0x00000000, 0, }, /* 0x027 */
+ { "Africa/Mbabane", "South Africa Standard Time", 14, 26, "ZA", "SZ", 0x00000000, 0, }, /* 0x028 */
+ { "Africa/Mogadishu", "E. Africa Standard Time", 16, 23, "KE", "SO", 0x00000000, 0, }, /* 0x029 */
+ { "Africa/Monrovia", "Greenwich Standard Time", 15, 23, "LR", "LR", 0x0000005a, 0, }, /* 0x02a */
+ { "Africa/Nairobi", "E. Africa Standard Time", 14, 23, "KE", "KE", 0x0000009b, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x02b */
+ { "Africa/Ndjamena", "W. Central Africa Standard Time", 15, 31, "TD", "TD", 0x00000071, 0, }, /* 0x02c */
+ { "Africa/Niamey", "W. Central Africa Standard Time", 13, 31, "NG", "NE", 0x00000000, 0, }, /* 0x02d */
+ { "Africa/Nouakchott", "Greenwich Standard Time", 17, 23, "CI", "MR", 0x00000000, 0, }, /* 0x02e */
+ { "Africa/Ouagadougou", "Greenwich Standard Time", 18, 23, "CI", "BF", 0x00000000, 0, }, /* 0x02f */
+ { "Africa/Porto-Novo", "W. Central Africa Standard Time", 17, 31, "NG", "BJ", 0x00000000, 0, }, /* 0x030 */
+ { "Africa/Sao_Tome", "Greenwich Standard Time", 15, 23, "CI", "ST", 0x00000000, 0, }, /* 0x031 */
+ { "Africa/Timbuktu", NULL, 15, 0, "CI", "", 0x00000000, 0, }, /* 0x032 */
+ { "Africa/Tripoli", "Libya Standard Time", 14, 19, "LY", "LY", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x033 */
+ { "Africa/Tunis", "W. Central Africa Standard Time", 12, 31, "TN", "TN", 0x00000071, 0, }, /* 0x034 */
+ { "Africa/Windhoek", "Namibia Standard Time", 15, 21, "NA", "NA", 0x80000046, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x035 */
+ { "America/Adak", "Aleutian Standard Time", 12, 22, "US", "US", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x036 */
+ { "America/Anchorage", "Alaskan Standard Time", 17, 21, "US", "US", 0x00000003, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x037 */
+ { "America/Anguilla", "SA Western Standard Time", 16, 24, "TT", "AI", 0x00000000, 0, }, /* 0x038 */
+ { "America/Antigua", "SA Western Standard Time", 15, 24, "TT", "AG", 0x00000000, 0, }, /* 0x039 */
+ { "America/Araguaina", "Tocantins Standard Time", 17, 23, "BR", "BR", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x03a */
+ { "America/Argentina/Buenos_Aires", NULL, 30, 0, "AR", "", 0x00000000, 0, }, /* 0x03b */
+ { "America/Argentina/Catamarca", NULL, 27, 0, "AR", "", 0x00000000, 0, }, /* 0x03c */
+ { "America/Argentina/ComodRivadavia", NULL, 32, 0, "AR", "", 0x00000000, 0, }, /* 0x03d */
+ { "America/Argentina/Cordoba", NULL, 25, 0, "AR", "", 0x00000000, 0, }, /* 0x03e */
+ { "America/Argentina/Jujuy", NULL, 23, 0, "AR", "", 0x00000000, 0, }, /* 0x03f */
+ { "America/Argentina/La_Rioja", "Argentina Standard Time", 26, 23, "AR", "AR", 0x8000004c, 0, }, /* 0x040 */
+ { "America/Argentina/Mendoza", NULL, 25, 0, "AR", "", 0x00000000, 0, }, /* 0x041 */
+ { "America/Argentina/Rio_Gallegos", "Argentina Standard Time", 30, 23, "AR", "AR", 0x8000004c, 0, }, /* 0x042 */
+ { "America/Argentina/Salta", "Argentina Standard Time", 23, 23, "AR", "AR", 0x8000004c, 0, }, /* 0x043 */
+ { "America/Argentina/San_Juan", "Argentina Standard Time", 26, 23, "AR", "AR", 0x8000004c, 0, }, /* 0x044 */
+ { "America/Argentina/San_Luis", "Argentina Standard Time", 26, 23, "AR", "AR", 0x8000004c, 0, }, /* 0x045 */
+ { "America/Argentina/Tucuman", "Argentina Standard Time", 25, 23, "AR", "AR", 0x8000004c, 0, }, /* 0x046 */
+ { "America/Argentina/Ushuaia", "Argentina Standard Time", 25, 23, "AR", "AR", 0x8000004c, 0, }, /* 0x047 */
+ { "America/Aruba", "SA Western Standard Time", 13, 24, "CW", "AW", 0x00000000, 0, }, /* 0x048 */
+ { "America/Asuncion", "Paraguay Standard Time", 16, 22, "PY", "PY", 0x80000051, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x049 */
+ { "America/Atikokan", NULL, 16, 0, "CA", "", 0x00000000, 0, }, /* 0x04a */
+ { "America/Atka", NULL, 12, 0, "US", "", 0x00000000, 0, }, /* 0x04b */
+ { "America/Bahia", "Bahia Standard Time", 13, 19, "BR", "BR", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x04c */
+ { "America/Bahia_Banderas", "Central Standard Time (Mexico)", 22, 30, "MX", "MX", 0x80000043, 0, }, /* 0x04d */
+ { "America/Barbados", "SA Western Standard Time", 16, 24, "BB", "BB", 0x00000037, 0, }, /* 0x04e */
+ { "America/Belem", "SA Eastern Standard Time", 13, 24, "BR", "BR", 0x00000046, 0, }, /* 0x04f */
+ { "America/Belize", "Central America Standard Time", 14, 29, "BZ", "BZ", 0x00000021, 0, }, /* 0x050 */
+ { "America/Blanc-Sablon", "SA Western Standard Time", 20, 24, "CA", "CA", 0x00000037, 0, }, /* 0x051 */
+ { "America/Boa_Vista", "SA Western Standard Time", 17, 24, "BR", "BR", 0x00000037, 0, }, /* 0x052 */
+ { "America/Bogota", "SA Pacific Standard Time", 14, 24, "CO", "CO", 0x0000002d, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x053 */
+ { "America/Boise", "Mountain Standard Time", 13, 22, "US", "US", 0x0000000a, 0, }, /* 0x054 */
+ { "America/Buenos_Aires", "Argentina Standard Time", 20, 23, "AR", "AR", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x055 */
+ { "America/Cambridge_Bay", "Mountain Standard Time", 21, 22, "CA", "CA", 0x0000000a, 0, }, /* 0x056 */
+ { "America/Campo_Grande", "Central Brazilian Standard Time", 20, 31, "BR", "BR", 0x80000048, 0, }, /* 0x057 */
+ { "America/Cancun", "Eastern Standard Time (Mexico)", 14, 30, "MX", "MX", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x058 */
+ { "America/Caracas", "Venezuela Standard Time", 15, 23, "VE", "VE", 0x8000004b, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x059 */
+ { "America/Catamarca", "Argentina Standard Time", 17, 23, "AR", "AR", 0x00000000, 0, }, /* 0x05a */
+ { "America/Cayenne", "SA Eastern Standard Time", 15, 24, "GF", "GF", 0x00000046, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x05b */
+ { "America/Cayman", "SA Pacific Standard Time", 14, 24, "PA", "KY", 0x00000000, 0, }, /* 0x05c */
+ { "America/Chicago", "Central Standard Time", 15, 21, "US", "US", 0x00000014, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x05d */
+ { "America/Chihuahua", "Mountain Standard Time (Mexico)", 17, 31, "MX", "MX", 0x80000044, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x05e */
+ { "America/Coral_Harbour", "SA Pacific Standard Time", 21, 24, "CA", "CA", 0x00000000, 0, }, /* 0x05f */
+ { "America/Cordoba", "Argentina Standard Time", 15, 23, "AR", "AR", 0x00000000, 0, }, /* 0x060 */
+ { "America/Costa_Rica", "Central America Standard Time", 18, 29, "CR", "CR", 0x00000021, 0, }, /* 0x061 */
+ { "America/Creston", "US Mountain Standard Time", 15, 25, "CA", "CA", 0x0000000f, 0, }, /* 0x062 */
+ { "America/Cuiaba", "Central Brazilian Standard Time", 14, 31, "BR", "BR", 0x80000048, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x063 */
+ { "America/Curacao", "SA Western Standard Time", 15, 24, "CW", "CW", 0x00000037, 0, }, /* 0x064 */
+ { "America/Danmarkshavn", "UTC", 20, 3, "GL", "GL", 0x80000050, 0, }, /* 0x065 */
+ { "America/Dawson", "Pacific Standard Time", 14, 21, "CA", "CA", 0x00000004, 0, }, /* 0x066 */
+ { "America/Dawson_Creek", "US Mountain Standard Time", 20, 25, "CA", "CA", 0x0000000f, 0, }, /* 0x067 */
+ { "America/Denver", "Mountain Standard Time", 14, 22, "US", "US", 0x0000000a, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x068 */
+ { "America/Detroit", "Eastern Standard Time", 15, 21, "US", "US", 0x00000023, 0, }, /* 0x069 */
+ { "America/Dominica", "SA Western Standard Time", 16, 24, "TT", "DM", 0x00000000, 0, }, /* 0x06a */
+ { "America/Edmonton", "Mountain Standard Time", 16, 22, "CA", "CA", 0x0000000a, 0, }, /* 0x06b */
+ { "America/Eirunepe", "SA Pacific Standard Time", 16, 24, "BR", "BR", 0x0000002d, 0, }, /* 0x06c */
+ { "America/El_Salvador", "Central America Standard Time", 19, 29, "SV", "SV", 0x00000021, 0, }, /* 0x06d */
+ { "America/Ensenada", NULL, 16, 0, "MX", "", 0x00000000, 0, }, /* 0x06e */
+ { "America/Fort_Nelson", "US Mountain Standard Time", 19, 25, "CA", "CA", 0x0000000f, 0, }, /* 0x06f */
+ { "America/Fort_Wayne", NULL, 18, 0, "US", "", 0x00000000, 0, }, /* 0x070 */
+ { "America/Fortaleza", "SA Eastern Standard Time", 17, 24, "BR", "BR", 0x00000046, 0, }, /* 0x071 */
+ { "America/Glace_Bay", "Atlantic Standard Time", 17, 22, "CA", "CA", 0x00000032, 0, }, /* 0x072 */
+ { "America/Godthab", "Greenland Standard Time", 15, 23, "GL", "GL", 0x00000049, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x073 */
+ { "America/Goose_Bay", "Atlantic Standard Time", 17, 22, "CA", "CA", 0x00000032, 0, }, /* 0x074 */
+ { "America/Grand_Turk", "Turks And Caicos Standard Time", 18, 30, "TC", "TC", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x075 */
+ { "America/Grenada", "SA Western Standard Time", 15, 24, "TT", "GD", 0x00000000, 0, }, /* 0x076 */
+ { "America/Guadeloupe", "SA Western Standard Time", 18, 24, "TT", "GP", 0x00000000, 0, }, /* 0x077 */
+ { "America/Guatemala", "Central America Standard Time", 17, 29, "GT", "GT", 0x00000021, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x078 */
+ { "America/Guayaquil", "SA Pacific Standard Time", 17, 24, "EC", "EC", 0x0000002d, 0, }, /* 0x079 */
+ { "America/Guyana", "SA Western Standard Time", 14, 24, "GY", "GY", 0x00000037, 0, }, /* 0x07a */
+ { "America/Halifax", "Atlantic Standard Time", 15, 22, "CA", "CA", 0x00000032, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x07b */
+ { "America/Havana", "Cuba Standard Time", 14, 18, "CU", "CU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x07c */
+ { "America/Hermosillo", "US Mountain Standard Time", 18, 25, "MX", "MX", 0x0000000f, 0, }, /* 0x07d */
+ { "America/Indiana/Indianapolis", NULL, 28, 0, "US", "", 0x00000000, 0, }, /* 0x07e */
+ { "America/Indiana/Knox", "Central Standard Time", 20, 21, "US", "US", 0x00000014, 0, }, /* 0x07f */
+ { "America/Indiana/Marengo", "US Eastern Standard Time", 23, 24, "US", "US", 0x00000028, 0, }, /* 0x080 */
+ { "America/Indiana/Petersburg", "Eastern Standard Time", 26, 21, "US", "US", 0x00000023, 0, }, /* 0x081 */
+ { "America/Indiana/Tell_City", "Central Standard Time", 25, 21, "US", "US", 0x00000014, 0, }, /* 0x082 */
+ { "America/Indiana/Vevay", "US Eastern Standard Time", 21, 24, "US", "US", 0x00000028, 0, }, /* 0x083 */
+ { "America/Indiana/Vincennes", "Eastern Standard Time", 25, 21, "US", "US", 0x00000023, 0, }, /* 0x084 */
+ { "America/Indiana/Winamac", "Eastern Standard Time", 23, 21, "US", "US", 0x00000023, 0, }, /* 0x085 */
+ { "America/Indianapolis", "US Eastern Standard Time", 20, 24, "US", "US", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x086 */
+ { "America/Inuvik", "Mountain Standard Time", 14, 22, "CA", "CA", 0x0000000a, 0, }, /* 0x087 */
+ { "America/Iqaluit", "Eastern Standard Time", 15, 21, "CA", "CA", 0x00000023, 0, }, /* 0x088 */
+ { "America/Jamaica", "SA Pacific Standard Time", 15, 24, "JM", "JM", 0x0000002d, 0, }, /* 0x089 */
+ { "America/Jujuy", "Argentina Standard Time", 13, 23, "AR", "AR", 0x00000000, 0, }, /* 0x08a */
+ { "America/Juneau", "Alaskan Standard Time", 14, 21, "US", "US", 0x00000003, 0, }, /* 0x08b */
+ { "America/Kentucky/Louisville", NULL, 27, 0, "US", "", 0x00000000, 0, }, /* 0x08c */
+ { "America/Kentucky/Monticello", "Eastern Standard Time", 27, 21, "US", "US", 0x00000023, 0, }, /* 0x08d */
+ { "America/Knox_IN", NULL, 15, 0, "US", "", 0x00000000, 0, }, /* 0x08e */
+ { "America/Kralendijk", "SA Western Standard Time", 18, 24, "CW", "BQ", 0x00000000, 0, }, /* 0x08f */
+ { "America/La_Paz", "SA Western Standard Time", 14, 24, "BO", "BO", 0x00000037, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x090 */
+ { "America/Lima", "SA Pacific Standard Time", 12, 24, "PE", "PE", 0x0000002d, 0, }, /* 0x091 */
+ { "America/Los_Angeles", "Pacific Standard Time", 19, 21, "US", "US", 0x00000004, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x092 */
+ { "America/Louisville", "Eastern Standard Time", 18, 21, "US", "US", 0x00000000, 0, }, /* 0x093 */
+ { "America/Lower_Princes", "SA Western Standard Time", 21, 24, "CW", "SX", 0x00000000, 0, }, /* 0x094 */
+ { "America/Maceio", "SA Eastern Standard Time", 14, 24, "BR", "BR", 0x00000046, 0, }, /* 0x095 */
+ { "America/Managua", "Central America Standard Time", 15, 29, "NI", "NI", 0x00000021, 0, }, /* 0x096 */
+ { "America/Manaus", "SA Western Standard Time", 14, 24, "BR", "BR", 0x00000037, 0, }, /* 0x097 */
+ { "America/Marigot", "SA Western Standard Time", 15, 24, "TT", "MF", 0x00000000, 0, }, /* 0x098 */
+ { "America/Martinique", "SA Western Standard Time", 18, 24, "MQ", "MQ", 0x00000037, 0, }, /* 0x099 */
+ { "America/Matamoros", "Central Standard Time", 17, 21, "MX", "MX", 0x00000014, 0, }, /* 0x09a */
+ { "America/Mazatlan", "Mountain Standard Time (Mexico)", 16, 31, "MX", "MX", 0x80000044, 0, }, /* 0x09b */
+ { "America/Mendoza", "Argentina Standard Time", 15, 23, "AR", "AR", 0x00000000, 0, }, /* 0x09c */
+ { "America/Menominee", "Central Standard Time", 17, 21, "US", "US", 0x00000014, 0, }, /* 0x09d */
+ { "America/Merida", "Central Standard Time (Mexico)", 14, 30, "MX", "MX", 0x80000043, 0, }, /* 0x09e */
+ { "America/Metlakatla", "Alaskan Standard Time", 18, 21, "US", "US", 0x00000003, 0, }, /* 0x09f */
+ { "America/Mexico_City", "Central Standard Time (Mexico)", 19, 30, "MX", "MX", 0x80000043, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0a0 */
+ { "America/Miquelon", "Saint Pierre Standard Time", 16, 26, "PM", "PM", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0a1 */
+ { "America/Moncton", "Atlantic Standard Time", 15, 22, "CA", "CA", 0x00000032, 0, }, /* 0x0a2 */
+ { "America/Monterrey", "Central Standard Time (Mexico)", 17, 30, "MX", "MX", 0x80000043, 0, }, /* 0x0a3 */
+ { "America/Montevideo", "Montevideo Standard Time", 18, 24, "UY", "UY", 0x80000049, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0a4 */
+ { "America/Montreal", "Eastern Standard Time", 16, 21, "CA", "CA", 0x00000000, 0, }, /* 0x0a5 */
+ { "America/Montserrat", "SA Western Standard Time", 18, 24, "TT", "MS", 0x00000000, 0, }, /* 0x0a6 */
+ { "America/Nassau", "Eastern Standard Time", 14, 21, "BS", "BS", 0x00000023, 0, }, /* 0x0a7 */
+ { "America/New_York", "Eastern Standard Time", 16, 21, "US", "US", 0x00000023, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0a8 */
+ { "America/Nipigon", "Eastern Standard Time", 15, 21, "CA", "CA", 0x00000023, 0, }, /* 0x0a9 */
+ { "America/Nome", "Alaskan Standard Time", 12, 21, "US", "US", 0x00000003, 0, }, /* 0x0aa */
+ { "America/Noronha", "UTC-02", 15, 6, "BR", "BR", 0x00000000, 0, }, /* 0x0ab */
+ { "America/North_Dakota/Beulah", "Central Standard Time", 27, 21, "US", "US", 0x00000014, 0, }, /* 0x0ac */
+ { "America/North_Dakota/Center", "Central Standard Time", 27, 21, "US", "US", 0x00000014, 0, }, /* 0x0ad */
+ { "America/North_Dakota/New_Salem", "Central Standard Time", 30, 21, "US", "US", 0x00000014, 0, }, /* 0x0ae */
+ { "America/Ojinaga", "Mountain Standard Time", 15, 22, "MX", "MX", 0x0000000a, 0, }, /* 0x0af */
+ { "America/Panama", "SA Pacific Standard Time", 14, 24, "PA", "PA", 0x0000002d, 0, }, /* 0x0b0 */
+ { "America/Pangnirtung", "Eastern Standard Time", 19, 21, "CA", "CA", 0x00000023, 0, }, /* 0x0b1 */
+ { "America/Paramaribo", "SA Eastern Standard Time", 18, 24, "SR", "SR", 0x00000046, 0, }, /* 0x0b2 */
+ { "America/Phoenix", "US Mountain Standard Time", 15, 25, "US", "US", 0x0000000f, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0b3 */
+ { "America/Port-au-Prince", "Haiti Standard Time", 22, 19, "HT", "HT", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0b4 */
+ { "America/Port_of_Spain", "SA Western Standard Time", 21, 24, "TT", "TT", 0x00000037, 0, }, /* 0x0b5 */
+ { "America/Porto_Acre", NULL, 18, 0, "BR", "", 0x00000000, 0, }, /* 0x0b6 */
+ { "America/Porto_Velho", "SA Western Standard Time", 19, 24, "BR", "BR", 0x00000037, 0, }, /* 0x0b7 */
+ { "America/Puerto_Rico", "SA Western Standard Time", 19, 24, "PR", "PR", 0x00000037, 0, }, /* 0x0b8 */
+ { "America/Punta_Arenas", "SA Eastern Standard Time", 20, 24, "CL", "CL", 0x00000046, 0, }, /* 0x0b9 */
+ { "America/Rainy_River", "Central Standard Time", 19, 21, "CA", "CA", 0x00000014, 0, }, /* 0x0ba */
+ { "America/Rankin_Inlet", "Central Standard Time", 20, 21, "CA", "CA", 0x00000014, 0, }, /* 0x0bb */
+ { "America/Recife", "SA Eastern Standard Time", 14, 24, "BR", "BR", 0x00000046, 0, }, /* 0x0bc */
+ { "America/Regina", "Canada Central Standard Time", 14, 28, "CA", "CA", 0x00000019, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0bd */
+ { "America/Resolute", "Central Standard Time", 16, 21, "CA", "CA", 0x00000014, 0, }, /* 0x0be */
+ { "America/Rio_Branco", "SA Pacific Standard Time", 18, 24, "BR", "BR", 0x0000002d, 0, }, /* 0x0bf */
+ { "America/Rosario", NULL, 15, 0, "AR", "", 0x00000000, 0, }, /* 0x0c0 */
+ { "America/Santa_Isabel", "Pacific Standard Time (Mexico)", 20, 30, "MX", "MX", 0x00000000, 0, }, /* 0x0c1 */
+ { "America/Santarem", "SA Eastern Standard Time", 16, 24, "BR", "BR", 0x00000046, 0, }, /* 0x0c2 */
+ { "America/Santiago", "Pacific SA Standard Time", 16, 24, "CL", "CL", 0x00000038, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0c3 */
+ { "America/Santo_Domingo", "SA Western Standard Time", 21, 24, "DO", "DO", 0x00000037, 0, }, /* 0x0c4 */
+ { "America/Sao_Paulo", "E. South America Standard Time", 17, 30, "BR", "BR", 0x00000041, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0c5 */
+ { "America/Scoresbysund", "Azores Standard Time", 20, 20, "GL", "GL", 0x00000050, 0, }, /* 0x0c6 */
+ { "America/Shiprock", NULL, 16, 0, "US", "", 0x00000000, 0, }, /* 0x0c7 */
+ { "America/Sitka", "Alaskan Standard Time", 13, 21, "US", "US", 0x00000003, 0, }, /* 0x0c8 */
+ { "America/St_Barthelemy", "SA Western Standard Time", 21, 24, "TT", "BL", 0x00000000, 0, }, /* 0x0c9 */
+ { "America/St_Johns", "Newfoundland Standard Time", 16, 26, "CA", "CA", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0ca */
+ { "America/St_Kitts", "SA Western Standard Time", 16, 24, "TT", "KN", 0x00000000, 0, }, /* 0x0cb */
+ { "America/St_Lucia", "SA Western Standard Time", 16, 24, "TT", "LC", 0x00000000, 0, }, /* 0x0cc */
+ { "America/St_Thomas", "SA Western Standard Time", 17, 24, "TT", "VI", 0x00000000, 0, }, /* 0x0cd */
+ { "America/St_Vincent", "SA Western Standard Time", 18, 24, "TT", "VC", 0x00000000, 0, }, /* 0x0ce */
+ { "America/Swift_Current", "Canada Central Standard Time", 21, 28, "CA", "CA", 0x00000019, 0, }, /* 0x0cf */
+ { "America/Tegucigalpa", "Central America Standard Time", 19, 29, "HN", "HN", 0x00000021, 0, }, /* 0x0d0 */
+ { "America/Thule", "Atlantic Standard Time", 13, 22, "GL", "GL", 0x00000032, 0, }, /* 0x0d1 */
+ { "America/Thunder_Bay", "Eastern Standard Time", 19, 21, "CA", "CA", 0x00000023, 0, }, /* 0x0d2 */
+ { "America/Tijuana", "Pacific Standard Time (Mexico)", 15, 30, "MX", "MX", 0x80000045, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0d3 */
+ { "America/Toronto", "Eastern Standard Time", 15, 21, "CA", "CA", 0x00000023, 0, }, /* 0x0d4 */
+ { "America/Tortola", "SA Western Standard Time", 15, 24, "TT", "VG", 0x00000000, 0, }, /* 0x0d5 */
+ { "America/Vancouver", "Pacific Standard Time", 17, 21, "CA", "CA", 0x00000004, 0, }, /* 0x0d6 */
+ { "America/Virgin", NULL, 14, 0, "TT", "", 0x00000000, 0, }, /* 0x0d7 */
+ { "America/Whitehorse", "Pacific Standard Time", 18, 21, "CA", "CA", 0x00000004, 0, }, /* 0x0d8 */
+ { "America/Winnipeg", "Central Standard Time", 16, 21, "CA", "CA", 0x00000014, 0, }, /* 0x0d9 */
+ { "America/Yakutat", "Alaskan Standard Time", 15, 21, "US", "US", 0x00000003, 0, }, /* 0x0da */
+ { "America/Yellowknife", "Mountain Standard Time", 19, 22, "CA", "CA", 0x0000000a, 0, }, /* 0x0db */
+ { "Antarctica/Casey", "Central Pacific Standard Time", 16, 29, "AQ", "AQ", 0x00000118, 0, }, /* 0x0dc */
+ { "Antarctica/Davis", "SE Asia Standard Time", 16, 21, "AQ", "AQ", 0x000000cd, 0, }, /* 0x0dd */
+ { "Antarctica/DumontDUrville", "West Pacific Standard Time", 25, 26, "AQ", "AQ", 0x00000113, 0, }, /* 0x0de */
+ { "Antarctica/Macquarie", "Central Pacific Standard Time", 20, 29, "AU", "AU", 0x00000118, 0, }, /* 0x0df */
+ { "Antarctica/Mawson", "West Asia Standard Time", 17, 23, "AQ", "AQ", 0x000000b9, 0, }, /* 0x0e0 */
+ { "Antarctica/McMurdo", "New Zealand Standard Time", 18, 25, "NZ", "AQ", 0x00000000, 0, }, /* 0x0e1 */
+ { "Antarctica/Palmer", "SA Eastern Standard Time", 17, 24, "AQ", "AQ", 0x00000046, 0, }, /* 0x0e2 */
+ { "Antarctica/Rothera", "SA Eastern Standard Time", 18, 24, "AQ", "AQ", 0x00000046, 0, }, /* 0x0e3 */
+ { "Antarctica/South_Pole", NULL, 21, 0, "NZ", "", 0x00000000, 0, }, /* 0x0e4 */
+ { "Antarctica/Syowa", "E. Africa Standard Time", 16, 23, "AQ", "AQ", 0x0000009b, 0, }, /* 0x0e5 */
+ { "Antarctica/Troll", NULL, 16, 0, "AQ", "", 0x00000000, 0, }, /* 0x0e6 */
+ { "Antarctica/Vostok", "Central Asia Standard Time", 17, 26, "AQ", "AQ", 0x000000c3, 0, }, /* 0x0e7 */
+ { "Arctic/Longyearbyen", "W. Europe Standard Time", 19, 23, "NO", "SJ", 0x00000000, 0, }, /* 0x0e8 */
+ { "Asia/Aden", "Arab Standard Time", 9, 18, "SA", "YE", 0x00000000, 0, }, /* 0x0e9 */
+ { "Asia/Almaty", "Central Asia Standard Time", 11, 26, "KZ", "KZ", 0x000000c3, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0ea */
+ { "Asia/Amman", "Jordan Standard Time", 10, 20, "JO", "JO", 0x80000042, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0eb */
+ { "Asia/Anadyr", "Russia Time Zone 11", 11, 19, "RU", "RU", 0x00000000, 0, }, /* 0x0ec */
+ { "Asia/Aqtau", "West Asia Standard Time", 10, 23, "KZ", "KZ", 0x000000b9, 0, }, /* 0x0ed */
+ { "Asia/Aqtobe", "West Asia Standard Time", 11, 23, "KZ", "KZ", 0x000000b9, 0, }, /* 0x0ee */
+ { "Asia/Ashgabat", "West Asia Standard Time", 13, 23, "TM", "TM", 0x000000b9, 0, }, /* 0x0ef */
+ { "Asia/Ashkhabad", NULL, 14, 0, "TM", "", 0x00000000, 0, }, /* 0x0f0 */
+ { "Asia/Atyrau", "West Asia Standard Time", 11, 23, "KZ", "KZ", 0x000000b9, 0, }, /* 0x0f1 */
+ { "Asia/Baghdad", "Arabic Standard Time", 12, 20, "IQ", "IQ", 0x0000009e, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0f2 */
+ { "Asia/Bahrain", "Arab Standard Time", 12, 18, "QA", "BH", 0x00000000, 0, }, /* 0x0f3 */
+ { "Asia/Baku", "Azerbaijan Standard Time", 9, 24, "AZ", "AZ", 0x80000040, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0f4 */
+ { "Asia/Bangkok", "SE Asia Standard Time", 12, 21, "TH", "TH", 0x000000cd, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0f5 */
+ { "Asia/Barnaul", "Altai Standard Time", 12, 19, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0f6 */
+ { "Asia/Beirut", "Middle East Standard Time", 11, 25, "LB", "LB", 0x80000041, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0f7 */
+ { "Asia/Bishkek", "Central Asia Standard Time", 12, 26, "KG", "KG", 0x000000c3, 0, }, /* 0x0f8 */
+ { "Asia/Brunei", "Singapore Standard Time", 11, 23, "BN", "BN", 0x000000d7, 0, }, /* 0x0f9 */
+ { "Asia/Calcutta", "India Standard Time", 13, 19, "IN", "IN", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0fa */
+ { "Asia/Chita", "Transbaikal Standard Time", 10, 25, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0fb */
+ { "Asia/Choibalsan", "Ulaanbaatar Standard Time", 15, 25, "MN", "MN", 0x00000000, 0, }, /* 0x0fc */
+ { "Asia/Chongqing", NULL, 14, 0, "CN", "", 0x00000000, 0, }, /* 0x0fd */
+ { "Asia/Chungking", NULL, 14, 0, "CN", "", 0x00000000, 0, }, /* 0x0fe */
+ { "Asia/Colombo", "Sri Lanka Standard Time", 12, 23, "LK", "LK", 0x000000c8, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x0ff */
+ { "Asia/Dacca", NULL, 10, 0, "BD", "", 0x00000000, 0, }, /* 0x100 */
+ { "Asia/Damascus", "Syria Standard Time", 13, 19, "SY", "SY", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x101 */
+ { "Asia/Dhaka", "Bangladesh Standard Time", 10, 24, "BD", "BD", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x102 */
+ { "Asia/Dili", "Tokyo Standard Time", 9, 19, "TL", "TL", 0x000000eb, 0, }, /* 0x103 */
+ { "Asia/Dubai", "Arabian Standard Time", 10, 21, "AE", "AE", 0x000000a5, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x104 */
+ { "Asia/Dushanbe", "West Asia Standard Time", 13, 23, "TJ", "TJ", 0x000000b9, 0, }, /* 0x105 */
+ { "Asia/Famagusta", "Turkey Standard Time", 14, 20, "CY", "CY", 0x00000000, 0, }, /* 0x106 */
+ { "Asia/Gaza", "West Bank Standard Time", 9, 23, "PS", "PS", 0x00000000, 0, }, /* 0x107 */
+ { "Asia/Harbin", NULL, 11, 0, "CN", "", 0x00000000, 0, }, /* 0x108 */
+ { "Asia/Hebron", "West Bank Standard Time", 11, 23, "PS", "PS", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x109 */
+ { "Asia/Ho_Chi_Minh", NULL, 16, 0, "VN", "", 0x00000000, 0, }, /* 0x10a */
+ { "Asia/Hong_Kong", "China Standard Time", 14, 19, "HK", "HK", 0x000000d2, 0, }, /* 0x10b */
+ { "Asia/Hovd", "W. Mongolia Standard Time", 9, 25, "MN", "MN", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x10c */
+ { "Asia/Irkutsk", "North Asia East Standard Time", 12, 29, "RU", "RU", 0x000000e3, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x10d */
+ { "Asia/Istanbul", NULL, 13, 0, "TR", "", 0x00000000, 0, }, /* 0x10e */
+ { "Asia/Jakarta", "SE Asia Standard Time", 12, 21, "ID", "ID", 0x000000cd, 0, }, /* 0x10f */
+ { "Asia/Jayapura", "Tokyo Standard Time", 13, 19, "ID", "ID", 0x000000eb, 0, }, /* 0x110 */
+ { "Asia/Jerusalem", "Israel Standard Time", 14, 20, "IL", "IL", 0x00000087, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x111 */
+ { "Asia/Kabul", "Afghanistan Standard Time", 10, 25, "AF", "AF", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x112 */
+ { "Asia/Kamchatka", "Russia Time Zone 11", 14, 19, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x113 */
+ { "Asia/Karachi", "Pakistan Standard Time", 12, 22, "PK", "PK", 0x8000004e, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x114 */
+ { "Asia/Kashgar", NULL, 12, 0, "CN", "", 0x00000000, 0, }, /* 0x115 */
+ { "Asia/Kathmandu", NULL, 14, 0, "NP", "", 0x00000000, 0, }, /* 0x116 */
+ { "Asia/Katmandu", "Nepal Standard Time", 13, 19, "NP", "NP", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x117 */
+ { "Asia/Khandyga", "Yakutsk Standard Time", 13, 21, "RU", "RU", 0x000000f0, 0, }, /* 0x118 */
+ { "Asia/Kolkata", NULL, 12, 0, "IN", "", 0x00000000, 0, }, /* 0x119 */
+ { "Asia/Krasnoyarsk", "North Asia Standard Time", 16, 24, "RU", "RU", 0x000000cf, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x11a */
+ { "Asia/Kuala_Lumpur", "Singapore Standard Time", 17, 23, "MY", "MY", 0x000000d7, 0, }, /* 0x11b */
+ { "Asia/Kuching", "Singapore Standard Time", 12, 23, "MY", "MY", 0x000000d7, 0, }, /* 0x11c */
+ { "Asia/Kuwait", "Arab Standard Time", 11, 18, "SA", "KW", 0x00000000, 0, }, /* 0x11d */
+ { "Asia/Macao", NULL, 10, 0, "MO", "", 0x00000000, 0, }, /* 0x11e */
+ { "Asia/Macau", "China Standard Time", 10, 19, "MO", "MO", 0x000000d2, 0, }, /* 0x11f */
+ { "Asia/Magadan", "Magadan Standard Time", 12, 21, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x120 */
+ { "Asia/Makassar", "Singapore Standard Time", 13, 23, "ID", "ID", 0x000000d7, 0, }, /* 0x121 */
+ { "Asia/Manila", "Singapore Standard Time", 11, 23, "PH", "PH", 0x000000d7, 0, }, /* 0x122 */
+ { "Asia/Muscat", "Arabian Standard Time", 11, 21, "AE", "OM", 0x00000000, 0, }, /* 0x123 */
+ { "Asia/Nicosia", "GTB Standard Time", 12, 17, "CY", "CY", 0x00000082, 0, }, /* 0x124 */
+ { "Asia/Novokuznetsk", "North Asia Standard Time", 17, 24, "RU", "RU", 0x000000cf, 0, }, /* 0x125 */
+ { "Asia/Novosibirsk", "N. Central Asia Standard Time", 16, 29, "RU", "RU", 0x000000c9, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x126 */
+ { "Asia/Omsk", "Omsk Standard Time", 9, 18, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x127 */
+ { "Asia/Oral", "West Asia Standard Time", 9, 23, "KZ", "KZ", 0x000000b9, 0, }, /* 0x128 */
+ { "Asia/Phnom_Penh", "SE Asia Standard Time", 15, 21, "TH", "KH", 0x00000000, 0, }, /* 0x129 */
+ { "Asia/Pontianak", "SE Asia Standard Time", 14, 21, "ID", "ID", 0x000000cd, 0, }, /* 0x12a */
+ { "Asia/Pyongyang", "North Korea Standard Time", 14, 25, "KP", "KP", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x12b */
+ { "Asia/Qatar", "Arab Standard Time", 10, 18, "QA", "QA", 0x00000096, 0, }, /* 0x12c */
+ { "Asia/Qyzylorda", "Central Asia Standard Time", 14, 26, "KZ", "KZ", 0x000000c3, 0, }, /* 0x12d */
+ { "Asia/Rangoon", "Myanmar Standard Time", 12, 21, "MM", "MM", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x12e */
+ { "Asia/Riyadh", "Arab Standard Time", 11, 18, "SA", "SA", 0x00000096, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x12f */
+ { "Asia/Saigon", "SE Asia Standard Time", 11, 21, "VN", "VN", 0x00000000, 0, }, /* 0x130 */
+ { "Asia/Sakhalin", "Sakhalin Standard Time", 13, 22, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x131 */
+ { "Asia/Samarkand", "West Asia Standard Time", 14, 23, "UZ", "UZ", 0x000000b9, 0, }, /* 0x132 */
+ { "Asia/Seoul", "Korea Standard Time", 10, 19, "KR", "KR", 0x000000e6, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x133 */
+ { "Asia/Shanghai", "China Standard Time", 13, 19, "CN", "CN", 0x000000d2, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x134 */
+ { "Asia/Singapore", "Singapore Standard Time", 14, 23, "SG", "SG", 0x000000d7, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x135 */
+ { "Asia/Srednekolymsk", "Russia Time Zone 10", 18, 19, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x136 */
+ { "Asia/Taipei", "Taipei Standard Time", 11, 20, "TW", "TW", 0x000000dc, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x137 */
+ { "Asia/Tashkent", "West Asia Standard Time", 13, 23, "UZ", "UZ", 0x000000b9, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x138 */
+ { "Asia/Tbilisi", "Georgian Standard Time", 12, 22, "GE", "GE", 0x80000047, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x139 */
+ { "Asia/Tehran", "Iran Standard Time", 11, 18, "IR", "IR", 0x000000a0, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x13a */
+ { "Asia/Tel_Aviv", NULL, 13, 0, "IL", "", 0x00000000, 0, }, /* 0x13b */
+ { "Asia/Thimbu", NULL, 11, 0, "BT", "", 0x00000000, 0, }, /* 0x13c */
+ { "Asia/Thimphu", "Bangladesh Standard Time", 12, 24, "BT", "BT", 0x00000000, 0, }, /* 0x13d */
+ { "Asia/Tokyo", "Tokyo Standard Time", 10, 19, "JP", "JP", 0x000000eb, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x13e */
+ { "Asia/Tomsk", "Tomsk Standard Time", 10, 19, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x13f */
+ { "Asia/Ujung_Pandang", NULL, 18, 0, "ID", "", 0x00000000, 0, }, /* 0x140 */
+ { "Asia/Ulaanbaatar", "Ulaanbaatar Standard Time", 16, 25, "MN", "MN", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x141 */
+ { "Asia/Ulan_Bator", NULL, 15, 0, "MN", "", 0x00000000, 0, }, /* 0x142 */
+ { "Asia/Urumqi", "Central Asia Standard Time", 11, 26, "CN", "CN", 0x000000c3, 0, }, /* 0x143 */
+ { "Asia/Ust-Nera", "Vladivostok Standard Time", 13, 25, "RU", "RU", 0x0000010e, 0, }, /* 0x144 */
+ { "Asia/Vientiane", "SE Asia Standard Time", 14, 21, "TH", "LA", 0x00000000, 0, }, /* 0x145 */
+ { "Asia/Vladivostok", "Vladivostok Standard Time", 16, 25, "RU", "RU", 0x0000010e, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x146 */
+ { "Asia/Yakutsk", "Yakutsk Standard Time", 12, 21, "RU", "RU", 0x000000f0, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x147 */
+ { "Asia/Yangon", NULL, 11, 0, "MM", "", 0x00000000, 0, }, /* 0x148 */
+ { "Asia/Yekaterinburg", "Ekaterinburg Standard Time", 18, 26, "RU", "RU", 0x000000b4, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x149 */
+ { "Asia/Yerevan", "Caucasus Standard Time", 12, 22, "AM", "AM", 0x000000aa, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x14a */
+ { "Atlantic/Azores", "Azores Standard Time", 15, 20, "PT", "PT", 0x00000050, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x14b */
+ { "Atlantic/Bermuda", "Atlantic Standard Time", 16, 22, "BM", "BM", 0x00000032, 0, }, /* 0x14c */
+ { "Atlantic/Canary", "GMT Standard Time", 15, 17, "ES", "ES", 0x00000055, 0, }, /* 0x14d */
+ { "Atlantic/Cape_Verde", "Cape Verde Standard Time", 19, 24, "CV", "CV", 0x00000053, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x14e */
+ { "Atlantic/Faeroe", "GMT Standard Time", 15, 17, "FO", "FO", 0x00000000, 0, }, /* 0x14f */
+ { "Atlantic/Faroe", NULL, 14, 0, "FO", "", 0x00000000, 0, }, /* 0x150 */
+ { "Atlantic/Jan_Mayen", NULL, 18, 0, "NO", "", 0x00000000, 0, }, /* 0x151 */
+ { "Atlantic/Madeira", "GMT Standard Time", 16, 17, "PT", "PT", 0x00000055, 0, }, /* 0x152 */
+ { "Atlantic/Reykjavik", "Greenwich Standard Time", 18, 23, "IS", "IS", 0x0000005a, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x153 */
+ { "Atlantic/South_Georgia", "UTC-02", 22, 6, "GS", "GS", 0x00000000, 0, }, /* 0x154 */
+ { "Atlantic/St_Helena", "Greenwich Standard Time", 18, 23, "CI", "SH", 0x00000000, 0, }, /* 0x155 */
+ { "Atlantic/Stanley", "SA Eastern Standard Time", 16, 24, "FK", "FK", 0x00000046, 0, }, /* 0x156 */
+ { "Australia/ACT", NULL, 13, 0, "AU", "", 0x00000000, 0, }, /* 0x157 */
+ { "Australia/Adelaide", "Cen. Australia Standard Time", 18, 28, "AU", "AU", 0x000000fa, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x158 */
+ { "Australia/Brisbane", "E. Australia Standard Time", 18, 26, "AU", "AU", 0x00000104, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x159 */
+ { "Australia/Broken_Hill", "Cen. Australia Standard Time", 21, 28, "AU", "AU", 0x000000fa, 0, }, /* 0x15a */
+ { "Australia/Canberra", NULL, 18, 0, "AU", "", 0x00000000, 0, }, /* 0x15b */
+ { "Australia/Currie", "Tasmania Standard Time", 16, 22, "AU", "AU", 0x00000109, 0, }, /* 0x15c */
+ { "Australia/Darwin", "AUS Central Standard Time", 16, 25, "AU", "AU", 0x000000f5, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x15d */
+ { "Australia/Eucla", "Aus Central W. Standard Time", 15, 28, "AU", "AU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x15e */
+ { "Australia/Hobart", "Tasmania Standard Time", 16, 22, "AU", "AU", 0x00000109, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x15f */
+ { "Australia/LHI", NULL, 13, 0, "AU", "", 0x00000000, 0, }, /* 0x160 */
+ { "Australia/Lindeman", "E. Australia Standard Time", 18, 26, "AU", "AU", 0x00000104, 0, }, /* 0x161 */
+ { "Australia/Lord_Howe", "Lord Howe Standard Time", 19, 23, "AU", "AU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x162 */
+ { "Australia/Melbourne", "AUS Eastern Standard Time", 19, 25, "AU", "AU", 0x000000ff, 0, }, /* 0x163 */
+ { "Australia/NSW", NULL, 13, 0, "AU", "", 0x00000000, 0, }, /* 0x164 */
+ { "Australia/North", NULL, 15, 0, "AU", "", 0x00000000, 0, }, /* 0x165 */
+ { "Australia/Perth", "W. Australia Standard Time", 15, 26, "AU", "AU", 0x000000e1, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x166 */
+ { "Australia/Queensland", NULL, 20, 0, "AU", "", 0x00000000, 0, }, /* 0x167 */
+ { "Australia/South", NULL, 15, 0, "AU", "", 0x00000000, 0, }, /* 0x168 */
+ { "Australia/Sydney", "AUS Eastern Standard Time", 16, 25, "AU", "AU", 0x000000ff, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x169 */
+ { "Australia/Tasmania", NULL, 18, 0, "AU", "", 0x00000000, 0, }, /* 0x16a */
+ { "Australia/Victoria", NULL, 18, 0, "AU", "", 0x00000000, 0, }, /* 0x16b */
+ { "Australia/West", NULL, 14, 0, "AU", "", 0x00000000, 0, }, /* 0x16c */
+ { "Australia/Yancowinna", NULL, 20, 0, "AU", "", 0x00000000, 0, }, /* 0x16d */
+ { "Brazil/Acre", NULL, 11, 0, "BR", "", 0x00000000, 0, }, /* 0x16e */
+ { "Brazil/DeNoronha", NULL, 16, 0, "BR", "", 0x00000000, 0, }, /* 0x16f */
+ { "Brazil/East", NULL, 11, 0, "BR", "", 0x00000000, 0, }, /* 0x170 */
+ { "Brazil/West", NULL, 11, 0, "BR", "", 0x00000000, 0, }, /* 0x171 */
+ { "CET", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x172 */
+ { "CST6CDT", "Central Standard Time", 7, 21, "ZZ", "ZZ", 0x00000014, 0, }, /* 0x173 */
+ { "Canada/Atlantic", NULL, 15, 0, "CA", "", 0x00000000, 0, }, /* 0x174 */
+ { "Canada/Central", NULL, 14, 0, "CA", "", 0x00000000, 0, }, /* 0x175 */
+ { "Canada/East-Saskatchewan", NULL, 24, 0, "CA", "", 0x00000000, 0, }, /* 0x176 */
+ { "Canada/Eastern", NULL, 14, 0, "CA", "", 0x00000000, 0, }, /* 0x177 */
+ { "Canada/Mountain", NULL, 15, 0, "CA", "", 0x00000000, 0, }, /* 0x178 */
+ { "Canada/Newfoundland", NULL, 19, 0, "CA", "", 0x00000000, 0, }, /* 0x179 */
+ { "Canada/Pacific", NULL, 14, 0, "CA", "", 0x00000000, 0, }, /* 0x17a */
+ { "Canada/Saskatchewan", NULL, 19, 0, "CA", "", 0x00000000, 0, }, /* 0x17b */
+ { "Canada/Yukon", NULL, 12, 0, "CA", "", 0x00000000, 0, }, /* 0x17c */
+ { "Chile/Continental", NULL, 17, 0, "CL", "", 0x00000000, 0, }, /* 0x17d */
+ { "Chile/EasterIsland", NULL, 18, 0, "CL", "", 0x00000000, 0, }, /* 0x17e */
+ { "Cuba", NULL, 4, 0, "CU", "", 0x00000000, 0, }, /* 0x17f */
+ { "EET", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x180 */
+ { "EST", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x181 */
+ { "EST5EDT", "Eastern Standard Time", 7, 21, "ZZ", "ZZ", 0x00000023, 0, }, /* 0x182 */
+ { "Egypt", NULL, 5, 0, "EG", "", 0x00000000, 0, }, /* 0x183 */
+ { "Eire", NULL, 4, 0, "IE", "", 0x00000000, 0, }, /* 0x184 */
+ { "Etc/GMT", "UTC", 7, 3, "ZZ", "ZZ", 0x80000050, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x185 */
+ { "Etc/GMT+0", NULL, 9, 0, "ZZ", "", 0x00000000, 0, }, /* 0x186 */
+ { "Etc/GMT+1", "Cape Verde Standard Time", 9, 24, "ZZ", "ZZ", 0x00000053, 0, }, /* 0x187 */
+ { "Etc/GMT+10", "Hawaiian Standard Time", 10, 22, "ZZ", "ZZ", 0x00000002, 0, }, /* 0x188 */
+ { "Etc/GMT+11", "UTC-11", 10, 6, "ZZ", "ZZ", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x189 */
+ { "Etc/GMT+12", "Dateline Standard Time", 10, 22, "ZZ", "ZZ", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x18a */
+ { "Etc/GMT+2", "UTC-02", 9, 6, "ZZ", "ZZ", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x18b */
+ { "Etc/GMT+3", "SA Eastern Standard Time", 9, 24, "ZZ", "ZZ", 0x00000046, 0, }, /* 0x18c */
+ { "Etc/GMT+4", "SA Western Standard Time", 9, 24, "ZZ", "ZZ", 0x00000037, 0, }, /* 0x18d */
+ { "Etc/GMT+5", "SA Pacific Standard Time", 9, 24, "ZZ", "ZZ", 0x0000002d, 0, }, /* 0x18e */
+ { "Etc/GMT+6", "Central America Standard Time", 9, 29, "ZZ", "ZZ", 0x00000021, 0, }, /* 0x18f */
+ { "Etc/GMT+7", "US Mountain Standard Time", 9, 25, "ZZ", "ZZ", 0x0000000f, 0, }, /* 0x190 */
+ { "Etc/GMT+8", "UTC-08", 9, 6, "ZZ", "ZZ", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x191 */
+ { "Etc/GMT+9", "UTC-09", 9, 6, "ZZ", "ZZ", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x192 */
+ { "Etc/GMT-0", NULL, 9, 0, "ZZ", "", 0x00000000, 0, }, /* 0x193 */
+ { "Etc/GMT-1", "W. Central Africa Standard Time", 9, 31, "ZZ", "ZZ", 0x00000071, 0, }, /* 0x194 */
+ { "Etc/GMT-10", "West Pacific Standard Time", 10, 26, "ZZ", "ZZ", 0x00000113, 0, }, /* 0x195 */
+ { "Etc/GMT-11", "Central Pacific Standard Time", 10, 29, "ZZ", "ZZ", 0x00000118, 0, }, /* 0x196 */
+ { "Etc/GMT-12", "UTC+12", 10, 6, "ZZ", "ZZ", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x197 */
+ { "Etc/GMT-13", "Tonga Standard Time", 10, 19, "ZZ", "ZZ", 0x0000012c, 0, }, /* 0x198 */
+ { "Etc/GMT-14", "Line Islands Standard Time", 10, 26, "ZZ", "ZZ", 0x00000000, 0, }, /* 0x199 */
+ { "Etc/GMT-2", "South Africa Standard Time", 9, 26, "ZZ", "ZZ", 0x0000008c, 0, }, /* 0x19a */
+ { "Etc/GMT-3", "E. Africa Standard Time", 9, 23, "ZZ", "ZZ", 0x0000009b, 0, }, /* 0x19b */
+ { "Etc/GMT-4", "Arabian Standard Time", 9, 21, "ZZ", "ZZ", 0x000000a5, 0, }, /* 0x19c */
+ { "Etc/GMT-5", "West Asia Standard Time", 9, 23, "ZZ", "ZZ", 0x000000b9, 0, }, /* 0x19d */
+ { "Etc/GMT-6", "Central Asia Standard Time", 9, 26, "ZZ", "ZZ", 0x000000c3, 0, }, /* 0x19e */
+ { "Etc/GMT-7", "SE Asia Standard Time", 9, 21, "ZZ", "ZZ", 0x000000cd, 0, }, /* 0x19f */
+ { "Etc/GMT-8", "Singapore Standard Time", 9, 23, "ZZ", "ZZ", 0x000000d7, 0, }, /* 0x1a0 */
+ { "Etc/GMT-9", "Tokyo Standard Time", 9, 19, "ZZ", "ZZ", 0x000000eb, 0, }, /* 0x1a1 */
+ { "Etc/GMT0", NULL, 8, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1a2 */
+ { "Etc/Greenwich", NULL, 13, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1a3 */
+ { "Etc/UCT", NULL, 7, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1a4 */
+ { "Etc/UTC", "UTC", 7, 3, "ZZ", "ZZ", 0x80000050, 0, }, /* 0x1a5 */
+ { "Etc/Universal", NULL, 13, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1a6 */
+ { "Etc/Zulu", NULL, 8, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1a7 */
+ { "Europe/Amsterdam", "W. Europe Standard Time", 16, 23, "NL", "NL", 0x0000006e, 0, }, /* 0x1a8 */
+ { "Europe/Andorra", "W. Europe Standard Time", 14, 23, "AD", "AD", 0x0000006e, 0, }, /* 0x1a9 */
+ { "Europe/Astrakhan", "Astrakhan Standard Time", 16, 23, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1aa */
+ { "Europe/Athens", "GTB Standard Time", 13, 17, "GR", "GR", 0x00000082, 0, }, /* 0x1ab */
+ { "Europe/Belfast", NULL, 14, 0, "GB", "", 0x00000000, 0, }, /* 0x1ac */
+ { "Europe/Belgrade", "Central Europe Standard Time", 15, 28, "RS", "RS", 0x0000005f, 0, }, /* 0x1ad */
+ { "Europe/Berlin", "W. Europe Standard Time", 13, 23, "DE", "DE", 0x0000006e, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1ae */
+ { "Europe/Bratislava", "Central Europe Standard Time", 17, 28, "CZ", "SK", 0x00000000, 0, }, /* 0x1af */
+ { "Europe/Brussels", "Romance Standard Time", 15, 21, "BE", "BE", 0x00000069, 0, }, /* 0x1b0 */
+ { "Europe/Bucharest", "GTB Standard Time", 16, 17, "RO", "RO", 0x00000082, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1b1 */
+ { "Europe/Budapest", "Central Europe Standard Time", 15, 28, "HU", "HU", 0x0000005f, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1b2 */
+ { "Europe/Busingen", "W. Europe Standard Time", 15, 23, "CH", "DE", 0x00000000, 0, }, /* 0x1b3 */
+ { "Europe/Chisinau", "E. Europe Standard Time", 15, 23, "MD", "MD", 0x00000073, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1b4 */
+ { "Europe/Copenhagen", "Romance Standard Time", 17, 21, "DK", "DK", 0x00000069, 0, }, /* 0x1b5 */
+ { "Europe/Dublin", "GMT Standard Time", 13, 17, "IE", "IE", 0x00000055, 0, }, /* 0x1b6 */
+ { "Europe/Gibraltar", "W. Europe Standard Time", 16, 23, "GI", "GI", 0x0000006e, 0, }, /* 0x1b7 */
+ { "Europe/Guernsey", "GMT Standard Time", 15, 17, "GB", "GG", 0x00000000, 0, }, /* 0x1b8 */
+ { "Europe/Helsinki", "FLE Standard Time", 15, 17, "FI", "FI", 0x0000007d, 0, }, /* 0x1b9 */
+ { "Europe/Isle_of_Man", "GMT Standard Time", 18, 17, "GB", "IM", 0x00000000, 0, }, /* 0x1ba */
+ { "Europe/Istanbul", "Turkey Standard Time", 15, 20, "TR", "TR", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1bb */
+ { "Europe/Jersey", "GMT Standard Time", 13, 17, "GB", "JE", 0x00000000, 0, }, /* 0x1bc */
+ { "Europe/Kaliningrad", "Kaliningrad Standard Time", 18, 25, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1bd */
+ { "Europe/Kiev", "FLE Standard Time", 11, 17, "UA", "UA", 0x0000007d, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1be */
+ { "Europe/Kirov", "Russian Standard Time", 12, 21, "RU", "RU", 0x00000091, 0, }, /* 0x1bf */
+ { "Europe/Lisbon", "GMT Standard Time", 13, 17, "PT", "PT", 0x00000055, 0, }, /* 0x1c0 */
+ { "Europe/Ljubljana", "Central Europe Standard Time", 16, 28, "RS", "SI", 0x00000000, 0, }, /* 0x1c1 */
+ { "Europe/London", "GMT Standard Time", 13, 17, "GB", "GB", 0x00000055, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1c2 */
+ { "Europe/Luxembourg", "W. Europe Standard Time", 17, 23, "LU", "LU", 0x0000006e, 0, }, /* 0x1c3 */
+ { "Europe/Madrid", "Romance Standard Time", 13, 21, "ES", "ES", 0x00000069, 0, }, /* 0x1c4 */
+ { "Europe/Malta", "W. Europe Standard Time", 12, 23, "MT", "MT", 0x0000006e, 0, }, /* 0x1c5 */
+ { "Europe/Mariehamn", "FLE Standard Time", 16, 17, "FI", "AX", 0x00000000, 0, }, /* 0x1c6 */
+ { "Europe/Minsk", "Belarus Standard Time", 12, 21, "BY", "BY", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1c7 */
+ { "Europe/Monaco", "W. Europe Standard Time", 13, 23, "MC", "MC", 0x0000006e, 0, }, /* 0x1c8 */
+ { "Europe/Moscow", "Russian Standard Time", 13, 21, "RU", "RU", 0x00000091, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1c9 */
+ { "Europe/Nicosia", NULL, 14, 0, "CY", "", 0x00000000, 0, }, /* 0x1ca */
+ { "Europe/Oslo", "W. Europe Standard Time", 11, 23, "NO", "NO", 0x0000006e, 0, }, /* 0x1cb */
+ { "Europe/Paris", "Romance Standard Time", 12, 21, "FR", "FR", 0x00000069, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1cc */
+ { "Europe/Podgorica", "Central Europe Standard Time", 16, 28, "RS", "ME", 0x00000000, 0, }, /* 0x1cd */
+ { "Europe/Prague", "Central Europe Standard Time", 13, 28, "CZ", "CZ", 0x0000005f, 0, }, /* 0x1ce */
+ { "Europe/Riga", "FLE Standard Time", 11, 17, "LV", "LV", 0x0000007d, 0, }, /* 0x1cf */
+ { "Europe/Rome", "W. Europe Standard Time", 11, 23, "IT", "IT", 0x0000006e, 0, }, /* 0x1d0 */
+ { "Europe/Samara", "Russia Time Zone 3", 13, 18, "RU", "RU", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1d1 */
+ { "Europe/San_Marino", "W. Europe Standard Time", 17, 23, "IT", "SM", 0x00000000, 0, }, /* 0x1d2 */
+ { "Europe/Sarajevo", "Central European Standard Time", 15, 30, "RS", "BA", 0x00000000, 0, }, /* 0x1d3 */
+ { "Europe/Saratov", "Astrakhan Standard Time", 14, 23, "RU", "RU", 0x00000000, 0, }, /* 0x1d4 */
+ { "Europe/Simferopol", "Russian Standard Time", 17, 21, "RU", "UA", 0x00000091, 0, }, /* 0x1d5 */
+ { "Europe/Skopje", "Central European Standard Time", 13, 30, "RS", "MK", 0x00000000, 0, }, /* 0x1d6 */
+ { "Europe/Sofia", "FLE Standard Time", 12, 17, "BG", "BG", 0x0000007d, 0, }, /* 0x1d7 */
+ { "Europe/Stockholm", "W. Europe Standard Time", 16, 23, "SE", "SE", 0x0000006e, 0, }, /* 0x1d8 */
+ { "Europe/Tallinn", "FLE Standard Time", 14, 17, "EE", "EE", 0x0000007d, 0, }, /* 0x1d9 */
+ { "Europe/Tirane", "Central Europe Standard Time", 13, 28, "AL", "AL", 0x0000005f, 0, }, /* 0x1da */
+ { "Europe/Tiraspol", NULL, 15, 0, "MD", "", 0x00000000, 0, }, /* 0x1db */
+ { "Europe/Ulyanovsk", "Astrakhan Standard Time", 16, 23, "RU", "RU", 0x00000000, 0, }, /* 0x1dc */
+ { "Europe/Uzhgorod", "FLE Standard Time", 15, 17, "UA", "UA", 0x0000007d, 0, }, /* 0x1dd */
+ { "Europe/Vaduz", "W. Europe Standard Time", 12, 23, "CH", "LI", 0x00000000, 0, }, /* 0x1de */
+ { "Europe/Vatican", "W. Europe Standard Time", 14, 23, "IT", "VA", 0x00000000, 0, }, /* 0x1df */
+ { "Europe/Vienna", "W. Europe Standard Time", 13, 23, "AT", "AT", 0x0000006e, 0, }, /* 0x1e0 */
+ { "Europe/Vilnius", "FLE Standard Time", 14, 17, "LT", "LT", 0x0000007d, 0, }, /* 0x1e1 */
+ { "Europe/Volgograd", "Russian Standard Time", 16, 21, "RU", "RU", 0x00000091, 0, }, /* 0x1e2 */
+ { "Europe/Warsaw", "Central European Standard Time", 13, 30, "PL", "PL", 0x00000064, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1e3 */
+ { "Europe/Zagreb", "Central European Standard Time", 13, 30, "RS", "HR", 0x00000000, 0, }, /* 0x1e4 */
+ { "Europe/Zaporozhye", "FLE Standard Time", 17, 17, "UA", "UA", 0x0000007d, 0, }, /* 0x1e5 */
+ { "Europe/Zurich", "W. Europe Standard Time", 13, 23, "CH", "CH", 0x0000006e, 0, }, /* 0x1e6 */
+ { "Factory", NULL, 7, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1e7 */
+ { "GB", NULL, 2, 0, "GB", "", 0x00000000, 0, }, /* 0x1e8 */
+ { "GB-Eire", NULL, 7, 0, "GB", "", 0x00000000, 0, }, /* 0x1e9 */
+ { "GMT", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1ea */
+ { "GMT+0", NULL, 5, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1eb */
+ { "GMT-0", NULL, 5, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1ec */
+ { "GMT0", NULL, 4, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1ed */
+ { "Greenwich", NULL, 9, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1ee */
+ { "HST", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x1ef */
+ { "Hongkong", NULL, 8, 0, "HK", "", 0x00000000, 0, }, /* 0x1f0 */
+ { "Iceland", NULL, 7, 0, "IS", "", 0x00000000, 0, }, /* 0x1f1 */
+ { "Indian/Antananarivo", "E. Africa Standard Time", 19, 23, "KE", "MG", 0x00000000, 0, }, /* 0x1f2 */
+ { "Indian/Chagos", "Central Asia Standard Time", 13, 26, "IO", "IO", 0x000000c3, 0, }, /* 0x1f3 */
+ { "Indian/Christmas", "SE Asia Standard Time", 16, 21, "CX", "CX", 0x000000cd, 0, }, /* 0x1f4 */
+ { "Indian/Cocos", "Myanmar Standard Time", 12, 21, "CC", "CC", 0x000000cb, 0, }, /* 0x1f5 */
+ { "Indian/Comoro", "E. Africa Standard Time", 13, 23, "KE", "KM", 0x00000000, 0, }, /* 0x1f6 */
+ { "Indian/Kerguelen", "West Asia Standard Time", 16, 23, "TF", "TF", 0x000000b9, 0, }, /* 0x1f7 */
+ { "Indian/Mahe", "Mauritius Standard Time", 11, 23, "SC", "SC", 0x8000004f, 0, }, /* 0x1f8 */
+ { "Indian/Maldives", "West Asia Standard Time", 15, 23, "MV", "MV", 0x000000b9, 0, }, /* 0x1f9 */
+ { "Indian/Mauritius", "Mauritius Standard Time", 16, 23, "MU", "MU", 0x8000004f, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x1fa */
+ { "Indian/Mayotte", "E. Africa Standard Time", 14, 23, "KE", "YT", 0x00000000, 0, }, /* 0x1fb */
+ { "Indian/Reunion", "Mauritius Standard Time", 14, 23, "RE", "RE", 0x8000004f, 0, }, /* 0x1fc */
+ { "Iran", NULL, 4, 0, "IR", "", 0x00000000, 0, }, /* 0x1fd */
+ { "Israel", NULL, 6, 0, "IL", "", 0x00000000, 0, }, /* 0x1fe */
+ { "Jamaica", NULL, 7, 0, "JM", "", 0x00000000, 0, }, /* 0x1ff */
+ { "Japan", NULL, 5, 0, "JP", "", 0x00000000, 0, }, /* 0x200 */
+ { "Kwajalein", NULL, 9, 0, "MH", "", 0x00000000, 0, }, /* 0x201 */
+ { "Libya", NULL, 5, 0, "LY", "", 0x00000000, 0, }, /* 0x202 */
+ { "MET", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x203 */
+ { "MST", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x204 */
+ { "MST7MDT", "Mountain Standard Time", 7, 22, "ZZ", "ZZ", 0x0000000a, 0, }, /* 0x205 */
+ { "Mexico/BajaNorte", NULL, 16, 0, "MX", "", 0x00000000, 0, }, /* 0x206 */
+ { "Mexico/BajaSur", NULL, 14, 0, "MX", "", 0x00000000, 0, }, /* 0x207 */
+ { "Mexico/General", NULL, 14, 0, "MX", "", 0x00000000, 0, }, /* 0x208 */
+ { "NZ", NULL, 2, 0, "NZ", "", 0x00000000, 0, }, /* 0x209 */
+ { "NZ-CHAT", NULL, 7, 0, "NZ", "", 0x00000000, 0, }, /* 0x20a */
+ { "Navajo", NULL, 6, 0, "US", "", 0x00000000, 0, }, /* 0x20b */
+ { "PRC", NULL, 3, 0, "CN", "", 0x00000000, 0, }, /* 0x20c */
+ { "PST8PDT", "Pacific Standard Time", 7, 21, "ZZ", "ZZ", 0x00000004, 0, }, /* 0x20d */
+ { "Pacific/Apia", "Samoa Standard Time", 12, 19, "WS", "WS", 0x00000001, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x20e */
+ { "Pacific/Auckland", "New Zealand Standard Time", 16, 25, "NZ", "NZ", 0x00000122, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x20f */
+ { "Pacific/Bougainville", "Bougainville Standard Time", 20, 26, "PG", "PG", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x210 */
+ { "Pacific/Chatham", "Chatham Islands Standard Time", 15, 29, "NZ", "NZ", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x211 */
+ { "Pacific/Chuuk", NULL, 13, 0, "FM", "", 0x00000000, 0, }, /* 0x212 */
+ { "Pacific/Easter", "Easter Island Standard Time", 14, 27, "CL", "CL", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x213 */
+ { "Pacific/Efate", "Central Pacific Standard Time", 13, 29, "VU", "VU", 0x00000118, 0, }, /* 0x214 */
+ { "Pacific/Enderbury", "Tonga Standard Time", 17, 19, "KI", "KI", 0x0000012c, 0, }, /* 0x215 */
+ { "Pacific/Fakaofo", "Tonga Standard Time", 15, 19, "TK", "TK", 0x0000012c, 0, }, /* 0x216 */
+ { "Pacific/Fiji", "Fiji Standard Time", 12, 18, "FJ", "FJ", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x217 */
+ { "Pacific/Funafuti", "UTC+12", 16, 6, "TV", "TV", 0x00000000, 0, }, /* 0x218 */
+ { "Pacific/Galapagos", "Central America Standard Time", 17, 29, "EC", "EC", 0x00000021, 0, }, /* 0x219 */
+ { "Pacific/Gambier", "UTC-09", 15, 6, "PF", "PF", 0x00000000, 0, }, /* 0x21a */
+ { "Pacific/Guadalcanal", "Central Pacific Standard Time", 19, 29, "SB", "SB", 0x00000118, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x21b */
+ { "Pacific/Guam", "West Pacific Standard Time", 12, 26, "GU", "GU", 0x00000113, 0, }, /* 0x21c */
+ { "Pacific/Honolulu", "Hawaiian Standard Time", 16, 22, "US", "US", 0x00000002, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x21d */
+ { "Pacific/Johnston", "Hawaiian Standard Time", 16, 22, "US", "UM", 0x00000000, 0, }, /* 0x21e */
+ { "Pacific/Kiritimati", "Line Islands Standard Time", 18, 26, "KI", "KI", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x21f */
+ { "Pacific/Kosrae", "Central Pacific Standard Time", 14, 29, "FM", "FM", 0x00000118, 0, }, /* 0x220 */
+ { "Pacific/Kwajalein", "UTC+12", 17, 6, "MH", "MH", 0x00000000, 0, }, /* 0x221 */
+ { "Pacific/Majuro", "UTC+12", 14, 6, "MH", "MH", 0x00000000, 0, }, /* 0x222 */
+ { "Pacific/Marquesas", "Marquesas Standard Time", 17, 23, "PF", "PF", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x223 */
+ { "Pacific/Midway", "UTC-11", 14, 6, "AS", "UM", 0x00000000, 0, }, /* 0x224 */
+ { "Pacific/Nauru", "UTC+12", 13, 6, "NR", "NR", 0x00000000, 0, }, /* 0x225 */
+ { "Pacific/Niue", "UTC-11", 12, 6, "NU", "NU", 0x00000000, 0, }, /* 0x226 */
+ { "Pacific/Norfolk", "Norfolk Standard Time", 15, 21, "NF", "NF", 0x00000000, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x227 */
+ { "Pacific/Noumea", "Central Pacific Standard Time", 14, 29, "NC", "NC", 0x00000118, 0, }, /* 0x228 */
+ { "Pacific/Pago_Pago", "UTC-11", 17, 6, "AS", "AS", 0x00000000, 0, }, /* 0x229 */
+ { "Pacific/Palau", "Tokyo Standard Time", 13, 19, "PW", "PW", 0x000000eb, 0, }, /* 0x22a */
+ { "Pacific/Pitcairn", "UTC-08", 16, 6, "PN", "PN", 0x00000000, 0, }, /* 0x22b */
+ { "Pacific/Pohnpei", NULL, 15, 0, "FM", "", 0x00000000, 0, }, /* 0x22c */
+ { "Pacific/Ponape", "Central Pacific Standard Time", 14, 29, "FM", "FM", 0x00000000, 0, }, /* 0x22d */
+ { "Pacific/Port_Moresby", "West Pacific Standard Time", 20, 26, "PG", "PG", 0x00000113, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x22e */
+ { "Pacific/Rarotonga", "Hawaiian Standard Time", 17, 22, "CK", "CK", 0x00000002, 0, }, /* 0x22f */
+ { "Pacific/Saipan", "West Pacific Standard Time", 14, 26, "GU", "MP", 0x00000000, 0, }, /* 0x230 */
+ { "Pacific/Samoa", NULL, 13, 0, "AS", "", 0x00000000, 0, }, /* 0x231 */
+ { "Pacific/Tahiti", "Hawaiian Standard Time", 14, 22, "PF", "PF", 0x00000002, 0, }, /* 0x232 */
+ { "Pacific/Tarawa", "UTC+12", 14, 6, "KI", "KI", 0x00000000, 0, }, /* 0x233 */
+ { "Pacific/Tongatapu", "Tonga Standard Time", 17, 19, "TO", "TO", 0x0000012c, RTTIMEZONEINFO_F_GOLDEN, }, /* 0x234 */
+ { "Pacific/Truk", "West Pacific Standard Time", 12, 26, "FM", "FM", 0x00000000, 0, }, /* 0x235 */
+ { "Pacific/Wake", "UTC+12", 12, 6, "UM", "UM", 0x00000000, 0, }, /* 0x236 */
+ { "Pacific/Wallis", "UTC+12", 14, 6, "WF", "WF", 0x00000000, 0, }, /* 0x237 */
+ { "Pacific/Yap", NULL, 11, 0, "FM", "", 0x00000000, 0, }, /* 0x238 */
+ { "Poland", NULL, 6, 0, "PL", "", 0x00000000, 0, }, /* 0x239 */
+ { "Portugal", NULL, 8, 0, "PT", "", 0x00000000, 0, }, /* 0x23a */
+ { "ROC", NULL, 3, 0, "TW", "", 0x00000000, 0, }, /* 0x23b */
+ { "ROK", NULL, 3, 0, "KR", "", 0x00000000, 0, }, /* 0x23c */
+ { "Singapore", NULL, 9, 0, "SG", "", 0x00000000, 0, }, /* 0x23d */
+ { "Turkey", NULL, 6, 0, "TR", "", 0x00000000, 0, }, /* 0x23e */
+ { "UCT", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x23f */
+ { "US/Alaska", NULL, 9, 0, "US", "", 0x00000000, 0, }, /* 0x240 */
+ { "US/Aleutian", NULL, 11, 0, "US", "", 0x00000000, 0, }, /* 0x241 */
+ { "US/Arizona", NULL, 10, 0, "US", "", 0x00000000, 0, }, /* 0x242 */
+ { "US/Central", NULL, 10, 0, "US", "", 0x00000000, 0, }, /* 0x243 */
+ { "US/East-Indiana", NULL, 15, 0, "US", "", 0x00000000, 0, }, /* 0x244 */
+ { "US/Eastern", NULL, 10, 0, "US", "", 0x00000000, 0, }, /* 0x245 */
+ { "US/Hawaii", NULL, 9, 0, "US", "", 0x00000000, 0, }, /* 0x246 */
+ { "US/Indiana-Starke", NULL, 17, 0, "US", "", 0x00000000, 0, }, /* 0x247 */
+ { "US/Michigan", NULL, 11, 0, "US", "", 0x00000000, 0, }, /* 0x248 */
+ { "US/Mountain", NULL, 11, 0, "US", "", 0x00000000, 0, }, /* 0x249 */
+ { "US/Pacific", NULL, 10, 0, "US", "", 0x00000000, 0, }, /* 0x24a */
+ { "US/Pacific-New", NULL, 14, 0, "US", "", 0x00000000, 0, }, /* 0x24b */
+ { "US/Samoa", NULL, 8, 0, "AS", "", 0x00000000, 0, }, /* 0x24c */
+ { "UTC", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x24d */
+ { "Universal", NULL, 9, 0, "ZZ", "", 0x00000000, 0, }, /* 0x24e */
+ { "W-SU", NULL, 4, 0, "RU", "", 0x00000000, 0, }, /* 0x24f */
+ { "WET", NULL, 3, 0, "ZZ", "", 0x00000000, 0, }, /* 0x250 */
+ { "Zulu", NULL, 4, 0, "ZZ", "", 0x00000000, 0, }, /* 0x251 */
+};
+
+/**
+ * Windows time zone lookup table. Sorted by name, golden flag and territory.
+ */
+static const uint16_t g_aidxWinTimeZones[] =
+{
+ 0x15d, /* AUS Central Standard Time / AU+ ==> Australia/Darwin */
+ 0x169, /* AUS Eastern Standard Time / AU+ ==> Australia/Sydney */
+ 0x163, /* AUS Eastern Standard Time / AU ==> Australia/Melbourne */
+ 0x112, /* Afghanistan Standard Time / AF+ ==> Asia/Kabul */
+ 0x037, /* Alaskan Standard Time / US+ ==> America/Anchorage */
+ 0x08b, /* Alaskan Standard Time / US ==> America/Juneau */
+ 0x09f, /* Alaskan Standard Time / US ==> America/Metlakatla */
+ 0x0aa, /* Alaskan Standard Time / US ==> America/Nome */
+ 0x0c8, /* Alaskan Standard Time / US ==> America/Sitka */
+ 0x0da, /* Alaskan Standard Time / US ==> America/Yakutat */
+ 0x036, /* Aleutian Standard Time / US+ ==> America/Adak */
+ 0x0f6, /* Altai Standard Time / RU+ ==> Asia/Barnaul */
+ 0x12f, /* Arab Standard Time / SA+ ==> Asia/Riyadh */
+ 0x0f3, /* Arab Standard Time / BH ==> Asia/Bahrain */
+ 0x11d, /* Arab Standard Time / KW ==> Asia/Kuwait */
+ 0x12c, /* Arab Standard Time / QA ==> Asia/Qatar */
+ 0x0e9, /* Arab Standard Time / YE ==> Asia/Aden */
+ 0x104, /* Arabian Standard Time / AE+ ==> Asia/Dubai */
+ 0x123, /* Arabian Standard Time / OM ==> Asia/Muscat */
+ 0x19c, /* Arabian Standard Time / ZZ ==> Etc/GMT-4 */
+ 0x0f2, /* Arabic Standard Time / IQ+ ==> Asia/Baghdad */
+ 0x055, /* Argentina Standard Time / AR+ ==> America/Buenos_Aires */
+ 0x040, /* Argentina Standard Time / AR ==> America/Argentina/La_Rioja */
+ 0x042, /* Argentina Standard Time / AR ==> America/Argentina/Rio_Gallegos */
+ 0x043, /* Argentina Standard Time / AR ==> America/Argentina/Salta */
+ 0x044, /* Argentina Standard Time / AR ==> America/Argentina/San_Juan */
+ 0x045, /* Argentina Standard Time / AR ==> America/Argentina/San_Luis */
+ 0x046, /* Argentina Standard Time / AR ==> America/Argentina/Tucuman */
+ 0x047, /* Argentina Standard Time / AR ==> America/Argentina/Ushuaia */
+ 0x05a, /* Argentina Standard Time / AR ==> America/Catamarca */
+ 0x060, /* Argentina Standard Time / AR ==> America/Cordoba */
+ 0x08a, /* Argentina Standard Time / AR ==> America/Jujuy */
+ 0x09c, /* Argentina Standard Time / AR ==> America/Mendoza */
+ 0x1aa, /* Astrakhan Standard Time / RU+ ==> Europe/Astrakhan */
+ 0x1d4, /* Astrakhan Standard Time / RU ==> Europe/Saratov */
+ 0x1dc, /* Astrakhan Standard Time / RU ==> Europe/Ulyanovsk */
+ 0x07b, /* Atlantic Standard Time / CA+ ==> America/Halifax */
+ 0x14c, /* Atlantic Standard Time / BM ==> Atlantic/Bermuda */
+ 0x072, /* Atlantic Standard Time / CA ==> America/Glace_Bay */
+ 0x074, /* Atlantic Standard Time / CA ==> America/Goose_Bay */
+ 0x0a2, /* Atlantic Standard Time / CA ==> America/Moncton */
+ 0x0d1, /* Atlantic Standard Time / GL ==> America/Thule */
+ 0x15e, /* Aus Central W. Standard Time / AU+ ==> Australia/Eucla */
+ 0x0f4, /* Azerbaijan Standard Time / AZ+ ==> Asia/Baku */
+ 0x14b, /* Azores Standard Time / PT+ ==> Atlantic/Azores */
+ 0x0c6, /* Azores Standard Time / GL ==> America/Scoresbysund */
+ 0x04c, /* Bahia Standard Time / BR+ ==> America/Bahia */
+ 0x102, /* Bangladesh Standard Time / BD+ ==> Asia/Dhaka */
+ 0x13d, /* Bangladesh Standard Time / BT ==> Asia/Thimphu */
+ 0x1c7, /* Belarus Standard Time / BY+ ==> Europe/Minsk */
+ 0x210, /* Bougainville Standard Time / PG+ ==> Pacific/Bougainville */
+ 0x0bd, /* Canada Central Standard Time / CA+ ==> America/Regina */
+ 0x0cf, /* Canada Central Standard Time / CA ==> America/Swift_Current */
+ 0x14e, /* Cape Verde Standard Time / CV+ ==> Atlantic/Cape_Verde */
+ 0x187, /* Cape Verde Standard Time / ZZ ==> Etc/GMT+1 */
+ 0x14a, /* Caucasus Standard Time / AM+ ==> Asia/Yerevan */
+ 0x158, /* Cen. Australia Standard Time / AU+ ==> Australia/Adelaide */
+ 0x15a, /* Cen. Australia Standard Time / AU ==> Australia/Broken_Hill */
+ 0x078, /* Central America Standard Time / GT+ ==> America/Guatemala */
+ 0x050, /* Central America Standard Time / BZ ==> America/Belize */
+ 0x061, /* Central America Standard Time / CR ==> America/Costa_Rica */
+ 0x219, /* Central America Standard Time / EC ==> Pacific/Galapagos */
+ 0x0d0, /* Central America Standard Time / HN ==> America/Tegucigalpa */
+ 0x096, /* Central America Standard Time / NI ==> America/Managua */
+ 0x06d, /* Central America Standard Time / SV ==> America/El_Salvador */
+ 0x18f, /* Central America Standard Time / ZZ ==> Etc/GMT+6 */
+ 0x0ea, /* Central Asia Standard Time / KZ+ ==> Asia/Almaty */
+ 0x0e7, /* Central Asia Standard Time / AQ ==> Antarctica/Vostok */
+ 0x143, /* Central Asia Standard Time / CN ==> Asia/Urumqi */
+ 0x1f3, /* Central Asia Standard Time / IO ==> Indian/Chagos */
+ 0x0f8, /* Central Asia Standard Time / KG ==> Asia/Bishkek */
+ 0x12d, /* Central Asia Standard Time / KZ ==> Asia/Qyzylorda */
+ 0x19e, /* Central Asia Standard Time / ZZ ==> Etc/GMT-6 */
+ 0x063, /* Central Brazilian Standard Time / BR+ ==> America/Cuiaba */
+ 0x057, /* Central Brazilian Standard Time / BR ==> America/Campo_Grande */
+ 0x1b2, /* Central Europe Standard Time / HU+ ==> Europe/Budapest */
+ 0x1da, /* Central Europe Standard Time / AL ==> Europe/Tirane */
+ 0x1ce, /* Central Europe Standard Time / CZ ==> Europe/Prague */
+ 0x1cd, /* Central Europe Standard Time / ME ==> Europe/Podgorica */
+ 0x1ad, /* Central Europe Standard Time / RS ==> Europe/Belgrade */
+ 0x1c1, /* Central Europe Standard Time / SI ==> Europe/Ljubljana */
+ 0x1af, /* Central Europe Standard Time / SK ==> Europe/Bratislava */
+ 0x1e3, /* Central European Standard Time / PL+ ==> Europe/Warsaw */
+ 0x1d3, /* Central European Standard Time / BA ==> Europe/Sarajevo */
+ 0x1e4, /* Central European Standard Time / HR ==> Europe/Zagreb */
+ 0x1d6, /* Central European Standard Time / MK ==> Europe/Skopje */
+ 0x21b, /* Central Pacific Standard Time / SB+ ==> Pacific/Guadalcanal */
+ 0x0dc, /* Central Pacific Standard Time / AQ ==> Antarctica/Casey */
+ 0x0df, /* Central Pacific Standard Time / AU ==> Antarctica/Macquarie */
+ 0x220, /* Central Pacific Standard Time / FM ==> Pacific/Kosrae */
+ 0x22d, /* Central Pacific Standard Time / FM ==> Pacific/Ponape */
+ 0x228, /* Central Pacific Standard Time / NC ==> Pacific/Noumea */
+ 0x214, /* Central Pacific Standard Time / VU ==> Pacific/Efate */
+ 0x196, /* Central Pacific Standard Time / ZZ ==> Etc/GMT-11 */
+ 0x0a0, /* Central Standard Time (Mexico) / MX+ ==> America/Mexico_City */
+ 0x04d, /* Central Standard Time (Mexico) / MX ==> America/Bahia_Banderas */
+ 0x09e, /* Central Standard Time (Mexico) / MX ==> America/Merida */
+ 0x0a3, /* Central Standard Time (Mexico) / MX ==> America/Monterrey */
+ 0x05d, /* Central Standard Time / US+ ==> America/Chicago */
+ 0x0ba, /* Central Standard Time / CA ==> America/Rainy_River */
+ 0x0bb, /* Central Standard Time / CA ==> America/Rankin_Inlet */
+ 0x0be, /* Central Standard Time / CA ==> America/Resolute */
+ 0x0d9, /* Central Standard Time / CA ==> America/Winnipeg */
+ 0x09a, /* Central Standard Time / MX ==> America/Matamoros */
+ 0x07f, /* Central Standard Time / US ==> America/Indiana/Knox */
+ 0x082, /* Central Standard Time / US ==> America/Indiana/Tell_City */
+ 0x09d, /* Central Standard Time / US ==> America/Menominee */
+ 0x0ac, /* Central Standard Time / US ==> America/North_Dakota/Beulah */
+ 0x0ad, /* Central Standard Time / US ==> America/North_Dakota/Center */
+ 0x0ae, /* Central Standard Time / US ==> America/North_Dakota/New_Salem */
+ 0x173, /* Central Standard Time / ZZ ==> CST6CDT */
+ 0x211, /* Chatham Islands Standard Time / NZ+ ==> Pacific/Chatham */
+ 0x134, /* China Standard Time / CN+ ==> Asia/Shanghai */
+ 0x10b, /* China Standard Time / HK ==> Asia/Hong_Kong */
+ 0x11f, /* China Standard Time / MO ==> Asia/Macau */
+ 0x07c, /* Cuba Standard Time / CU+ ==> America/Havana */
+ 0x18a, /* Dateline Standard Time / ZZ+ ==> Etc/GMT+12 */
+ 0x02b, /* E. Africa Standard Time / KE+ ==> Africa/Nairobi */
+ 0x0e5, /* E. Africa Standard Time / AQ ==> Antarctica/Syowa */
+ 0x013, /* E. Africa Standard Time / DJ ==> Africa/Djibouti */
+ 0x005, /* E. Africa Standard Time / ER ==> Africa/Asmera */
+ 0x002, /* E. Africa Standard Time / ET ==> Africa/Addis_Ababa */
+ 0x1f6, /* E. Africa Standard Time / KM ==> Indian/Comoro */
+ 0x1f2, /* E. Africa Standard Time / MG ==> Indian/Antananarivo */
+ 0x01c, /* E. Africa Standard Time / SD ==> Africa/Khartoum */
+ 0x029, /* E. Africa Standard Time / SO ==> Africa/Mogadishu */
+ 0x01a, /* E. Africa Standard Time / SS ==> Africa/Juba */
+ 0x012, /* E. Africa Standard Time / TZ ==> Africa/Dar_es_Salaam */
+ 0x01b, /* E. Africa Standard Time / UG ==> Africa/Kampala */
+ 0x1fb, /* E. Africa Standard Time / YT ==> Indian/Mayotte */
+ 0x19b, /* E. Africa Standard Time / ZZ ==> Etc/GMT-3 */
+ 0x159, /* E. Australia Standard Time / AU+ ==> Australia/Brisbane */
+ 0x161, /* E. Australia Standard Time / AU ==> Australia/Lindeman */
+ 0x1b4, /* E. Europe Standard Time / MD+ ==> Europe/Chisinau */
+ 0x0c5, /* E. South America Standard Time / BR+ ==> America/Sao_Paulo */
+ 0x213, /* Easter Island Standard Time / CL+ ==> Pacific/Easter */
+ 0x058, /* Eastern Standard Time (Mexico) / MX+ ==> America/Cancun */
+ 0x0a8, /* Eastern Standard Time / US+ ==> America/New_York */
+ 0x0a7, /* Eastern Standard Time / BS ==> America/Nassau */
+ 0x088, /* Eastern Standard Time / CA ==> America/Iqaluit */
+ 0x0a5, /* Eastern Standard Time / CA ==> America/Montreal */
+ 0x0a9, /* Eastern Standard Time / CA ==> America/Nipigon */
+ 0x0b1, /* Eastern Standard Time / CA ==> America/Pangnirtung */
+ 0x0d2, /* Eastern Standard Time / CA ==> America/Thunder_Bay */
+ 0x0d4, /* Eastern Standard Time / CA ==> America/Toronto */
+ 0x069, /* Eastern Standard Time / US ==> America/Detroit */
+ 0x081, /* Eastern Standard Time / US ==> America/Indiana/Petersburg */
+ 0x084, /* Eastern Standard Time / US ==> America/Indiana/Vincennes */
+ 0x085, /* Eastern Standard Time / US ==> America/Indiana/Winamac */
+ 0x08d, /* Eastern Standard Time / US ==> America/Kentucky/Monticello */
+ 0x093, /* Eastern Standard Time / US ==> America/Louisville */
+ 0x182, /* Eastern Standard Time / ZZ ==> EST5EDT */
+ 0x00d, /* Egypt Standard Time / EG+ ==> Africa/Cairo */
+ 0x149, /* Ekaterinburg Standard Time / RU+ ==> Asia/Yekaterinburg */
+ 0x1be, /* FLE Standard Time / UA+ ==> Europe/Kiev */
+ 0x1c6, /* FLE Standard Time / AX ==> Europe/Mariehamn */
+ 0x1d7, /* FLE Standard Time / BG ==> Europe/Sofia */
+ 0x1d9, /* FLE Standard Time / EE ==> Europe/Tallinn */
+ 0x1b9, /* FLE Standard Time / FI ==> Europe/Helsinki */
+ 0x1e1, /* FLE Standard Time / LT ==> Europe/Vilnius */
+ 0x1cf, /* FLE Standard Time / LV ==> Europe/Riga */
+ 0x1dd, /* FLE Standard Time / UA ==> Europe/Uzhgorod */
+ 0x1e5, /* FLE Standard Time / UA ==> Europe/Zaporozhye */
+ 0x217, /* Fiji Standard Time / FJ+ ==> Pacific/Fiji */
+ 0x1c2, /* GMT Standard Time / GB+ ==> Europe/London */
+ 0x14d, /* GMT Standard Time / ES ==> Atlantic/Canary */
+ 0x14f, /* GMT Standard Time / FO ==> Atlantic/Faeroe */
+ 0x1b8, /* GMT Standard Time / GG ==> Europe/Guernsey */
+ 0x1b6, /* GMT Standard Time / IE ==> Europe/Dublin */
+ 0x1ba, /* GMT Standard Time / IM ==> Europe/Isle_of_Man */
+ 0x1bc, /* GMT Standard Time / JE ==> Europe/Jersey */
+ 0x152, /* GMT Standard Time / PT ==> Atlantic/Madeira */
+ 0x1c0, /* GMT Standard Time / PT ==> Europe/Lisbon */
+ 0x1b1, /* GTB Standard Time / RO+ ==> Europe/Bucharest */
+ 0x124, /* GTB Standard Time / CY ==> Asia/Nicosia */
+ 0x1ab, /* GTB Standard Time / GR ==> Europe/Athens */
+ 0x139, /* Georgian Standard Time / GE+ ==> Asia/Tbilisi */
+ 0x073, /* Greenland Standard Time / GL+ ==> America/Godthab */
+ 0x153, /* Greenwich Standard Time / IS+ ==> Atlantic/Reykjavik */
+ 0x02f, /* Greenwich Standard Time / BF ==> Africa/Ouagadougou */
+ 0x000, /* Greenwich Standard Time / CI ==> Africa/Abidjan */
+ 0x001, /* Greenwich Standard Time / GH ==> Africa/Accra */
+ 0x008, /* Greenwich Standard Time / GM ==> Africa/Banjul */
+ 0x010, /* Greenwich Standard Time / GN ==> Africa/Conakry */
+ 0x009, /* Greenwich Standard Time / GW ==> Africa/Bissau */
+ 0x02a, /* Greenwich Standard Time / LR ==> Africa/Monrovia */
+ 0x006, /* Greenwich Standard Time / ML ==> Africa/Bamako */
+ 0x02e, /* Greenwich Standard Time / MR ==> Africa/Nouakchott */
+ 0x155, /* Greenwich Standard Time / SH ==> Atlantic/St_Helena */
+ 0x016, /* Greenwich Standard Time / SL ==> Africa/Freetown */
+ 0x011, /* Greenwich Standard Time / SN ==> Africa/Dakar */
+ 0x031, /* Greenwich Standard Time / ST ==> Africa/Sao_Tome */
+ 0x021, /* Greenwich Standard Time / TG ==> Africa/Lome */
+ 0x0b4, /* Haiti Standard Time / HT+ ==> America/Port-au-Prince */
+ 0x21d, /* Hawaiian Standard Time / US+ ==> Pacific/Honolulu */
+ 0x22f, /* Hawaiian Standard Time / CK ==> Pacific/Rarotonga */
+ 0x232, /* Hawaiian Standard Time / PF ==> Pacific/Tahiti */
+ 0x21e, /* Hawaiian Standard Time / UM ==> Pacific/Johnston */
+ 0x188, /* Hawaiian Standard Time / ZZ ==> Etc/GMT+10 */
+ 0x0fa, /* India Standard Time / IN+ ==> Asia/Calcutta */
+ 0x13a, /* Iran Standard Time / IR+ ==> Asia/Tehran */
+ 0x111, /* Israel Standard Time / IL+ ==> Asia/Jerusalem */
+ 0x0eb, /* Jordan Standard Time / JO+ ==> Asia/Amman */
+ 0x1bd, /* Kaliningrad Standard Time / RU+ ==> Europe/Kaliningrad */
+ 0x133, /* Korea Standard Time / KR+ ==> Asia/Seoul */
+ 0x033, /* Libya Standard Time / LY+ ==> Africa/Tripoli */
+ 0x21f, /* Line Islands Standard Time / KI+ ==> Pacific/Kiritimati */
+ 0x199, /* Line Islands Standard Time / ZZ ==> Etc/GMT-14 */
+ 0x162, /* Lord Howe Standard Time / AU+ ==> Australia/Lord_Howe */
+ 0x120, /* Magadan Standard Time / RU+ ==> Asia/Magadan */
+ 0x223, /* Marquesas Standard Time / PF+ ==> Pacific/Marquesas */
+ 0x1fa, /* Mauritius Standard Time / MU+ ==> Indian/Mauritius */
+ 0x1fc, /* Mauritius Standard Time / RE ==> Indian/Reunion */
+ 0x1f8, /* Mauritius Standard Time / SC ==> Indian/Mahe */
+ 0x0f7, /* Middle East Standard Time / LB+ ==> Asia/Beirut */
+ 0x0a4, /* Montevideo Standard Time / UY+ ==> America/Montevideo */
+ 0x00e, /* Morocco Standard Time / MA+ ==> Africa/Casablanca */
+ 0x015, /* Morocco Standard Time / EH ==> Africa/El_Aaiun */
+ 0x05e, /* Mountain Standard Time (Mexico) / MX+ ==> America/Chihuahua */
+ 0x09b, /* Mountain Standard Time (Mexico) / MX ==> America/Mazatlan */
+ 0x068, /* Mountain Standard Time / US+ ==> America/Denver */
+ 0x056, /* Mountain Standard Time / CA ==> America/Cambridge_Bay */
+ 0x06b, /* Mountain Standard Time / CA ==> America/Edmonton */
+ 0x087, /* Mountain Standard Time / CA ==> America/Inuvik */
+ 0x0db, /* Mountain Standard Time / CA ==> America/Yellowknife */
+ 0x0af, /* Mountain Standard Time / MX ==> America/Ojinaga */
+ 0x054, /* Mountain Standard Time / US ==> America/Boise */
+ 0x205, /* Mountain Standard Time / ZZ ==> MST7MDT */
+ 0x12e, /* Myanmar Standard Time / MM+ ==> Asia/Rangoon */
+ 0x1f5, /* Myanmar Standard Time / CC ==> Indian/Cocos */
+ 0x126, /* N. Central Asia Standard Time / RU+ ==> Asia/Novosibirsk */
+ 0x035, /* Namibia Standard Time / NA+ ==> Africa/Windhoek */
+ 0x117, /* Nepal Standard Time / NP+ ==> Asia/Katmandu */
+ 0x20f, /* New Zealand Standard Time / NZ+ ==> Pacific/Auckland */
+ 0x0e1, /* New Zealand Standard Time / AQ ==> Antarctica/McMurdo */
+ 0x0ca, /* Newfoundland Standard Time / CA+ ==> America/St_Johns */
+ 0x227, /* Norfolk Standard Time / NF+ ==> Pacific/Norfolk */
+ 0x10d, /* North Asia East Standard Time / RU+ ==> Asia/Irkutsk */
+ 0x11a, /* North Asia Standard Time / RU+ ==> Asia/Krasnoyarsk */
+ 0x125, /* North Asia Standard Time / RU ==> Asia/Novokuznetsk */
+ 0x12b, /* North Korea Standard Time / KP+ ==> Asia/Pyongyang */
+ 0x127, /* Omsk Standard Time / RU+ ==> Asia/Omsk */
+ 0x0c3, /* Pacific SA Standard Time / CL+ ==> America/Santiago */
+ 0x0d3, /* Pacific Standard Time (Mexico) / MX+ ==> America/Tijuana */
+ 0x0c1, /* Pacific Standard Time (Mexico) / MX ==> America/Santa_Isabel */
+ 0x092, /* Pacific Standard Time / US+ ==> America/Los_Angeles */
+ 0x066, /* Pacific Standard Time / CA ==> America/Dawson */
+ 0x0d6, /* Pacific Standard Time / CA ==> America/Vancouver */
+ 0x0d8, /* Pacific Standard Time / CA ==> America/Whitehorse */
+ 0x20d, /* Pacific Standard Time / ZZ ==> PST8PDT */
+ 0x114, /* Pakistan Standard Time / PK+ ==> Asia/Karachi */
+ 0x049, /* Paraguay Standard Time / PY+ ==> America/Asuncion */
+ 0x1cc, /* Romance Standard Time / FR+ ==> Europe/Paris */
+ 0x1b0, /* Romance Standard Time / BE ==> Europe/Brussels */
+ 0x1b5, /* Romance Standard Time / DK ==> Europe/Copenhagen */
+ 0x00f, /* Romance Standard Time / ES ==> Africa/Ceuta */
+ 0x1c4, /* Romance Standard Time / ES ==> Europe/Madrid */
+ 0x136, /* Russia Time Zone 10 / RU+ ==> Asia/Srednekolymsk */
+ 0x113, /* Russia Time Zone 11 / RU+ ==> Asia/Kamchatka */
+ 0x0ec, /* Russia Time Zone 11 / RU ==> Asia/Anadyr */
+ 0x1d1, /* Russia Time Zone 3 / RU+ ==> Europe/Samara */
+ 0x1c9, /* Russian Standard Time / RU+ ==> Europe/Moscow */
+ 0x1bf, /* Russian Standard Time / RU ==> Europe/Kirov */
+ 0x1e2, /* Russian Standard Time / RU ==> Europe/Volgograd */
+ 0x1d5, /* Russian Standard Time / UA ==> Europe/Simferopol */
+ 0x05b, /* SA Eastern Standard Time / GF+ ==> America/Cayenne */
+ 0x0e2, /* SA Eastern Standard Time / AQ ==> Antarctica/Palmer */
+ 0x0e3, /* SA Eastern Standard Time / AQ ==> Antarctica/Rothera */
+ 0x04f, /* SA Eastern Standard Time / BR ==> America/Belem */
+ 0x071, /* SA Eastern Standard Time / BR ==> America/Fortaleza */
+ 0x095, /* SA Eastern Standard Time / BR ==> America/Maceio */
+ 0x0bc, /* SA Eastern Standard Time / BR ==> America/Recife */
+ 0x0c2, /* SA Eastern Standard Time / BR ==> America/Santarem */
+ 0x0b9, /* SA Eastern Standard Time / CL ==> America/Punta_Arenas */
+ 0x156, /* SA Eastern Standard Time / FK ==> Atlantic/Stanley */
+ 0x0b2, /* SA Eastern Standard Time / SR ==> America/Paramaribo */
+ 0x18c, /* SA Eastern Standard Time / ZZ ==> Etc/GMT+3 */
+ 0x053, /* SA Pacific Standard Time / CO+ ==> America/Bogota */
+ 0x06c, /* SA Pacific Standard Time / BR ==> America/Eirunepe */
+ 0x0bf, /* SA Pacific Standard Time / BR ==> America/Rio_Branco */
+ 0x05f, /* SA Pacific Standard Time / CA ==> America/Coral_Harbour */
+ 0x079, /* SA Pacific Standard Time / EC ==> America/Guayaquil */
+ 0x089, /* SA Pacific Standard Time / JM ==> America/Jamaica */
+ 0x05c, /* SA Pacific Standard Time / KY ==> America/Cayman */
+ 0x0b0, /* SA Pacific Standard Time / PA ==> America/Panama */
+ 0x091, /* SA Pacific Standard Time / PE ==> America/Lima */
+ 0x18e, /* SA Pacific Standard Time / ZZ ==> Etc/GMT+5 */
+ 0x090, /* SA Western Standard Time / BO+ ==> America/La_Paz */
+ 0x039, /* SA Western Standard Time / AG ==> America/Antigua */
+ 0x038, /* SA Western Standard Time / AI ==> America/Anguilla */
+ 0x048, /* SA Western Standard Time / AW ==> America/Aruba */
+ 0x04e, /* SA Western Standard Time / BB ==> America/Barbados */
+ 0x0c9, /* SA Western Standard Time / BL ==> America/St_Barthelemy */
+ 0x08f, /* SA Western Standard Time / BQ ==> America/Kralendijk */
+ 0x052, /* SA Western Standard Time / BR ==> America/Boa_Vista */
+ 0x097, /* SA Western Standard Time / BR ==> America/Manaus */
+ 0x0b7, /* SA Western Standard Time / BR ==> America/Porto_Velho */
+ 0x051, /* SA Western Standard Time / CA ==> America/Blanc-Sablon */
+ 0x064, /* SA Western Standard Time / CW ==> America/Curacao */
+ 0x06a, /* SA Western Standard Time / DM ==> America/Dominica */
+ 0x0c4, /* SA Western Standard Time / DO ==> America/Santo_Domingo */
+ 0x076, /* SA Western Standard Time / GD ==> America/Grenada */
+ 0x077, /* SA Western Standard Time / GP ==> America/Guadeloupe */
+ 0x07a, /* SA Western Standard Time / GY ==> America/Guyana */
+ 0x0cb, /* SA Western Standard Time / KN ==> America/St_Kitts */
+ 0x0cc, /* SA Western Standard Time / LC ==> America/St_Lucia */
+ 0x098, /* SA Western Standard Time / MF ==> America/Marigot */
+ 0x099, /* SA Western Standard Time / MQ ==> America/Martinique */
+ 0x0a6, /* SA Western Standard Time / MS ==> America/Montserrat */
+ 0x0b8, /* SA Western Standard Time / PR ==> America/Puerto_Rico */
+ 0x094, /* SA Western Standard Time / SX ==> America/Lower_Princes */
+ 0x0b5, /* SA Western Standard Time / TT ==> America/Port_of_Spain */
+ 0x0ce, /* SA Western Standard Time / VC ==> America/St_Vincent */
+ 0x0d5, /* SA Western Standard Time / VG ==> America/Tortola */
+ 0x0cd, /* SA Western Standard Time / VI ==> America/St_Thomas */
+ 0x18d, /* SA Western Standard Time / ZZ ==> Etc/GMT+4 */
+ 0x0f5, /* SE Asia Standard Time / TH+ ==> Asia/Bangkok */
+ 0x0dd, /* SE Asia Standard Time / AQ ==> Antarctica/Davis */
+ 0x1f4, /* SE Asia Standard Time / CX ==> Indian/Christmas */
+ 0x10f, /* SE Asia Standard Time / ID ==> Asia/Jakarta */
+ 0x12a, /* SE Asia Standard Time / ID ==> Asia/Pontianak */
+ 0x129, /* SE Asia Standard Time / KH ==> Asia/Phnom_Penh */
+ 0x145, /* SE Asia Standard Time / LA ==> Asia/Vientiane */
+ 0x130, /* SE Asia Standard Time / VN ==> Asia/Saigon */
+ 0x19f, /* SE Asia Standard Time / ZZ ==> Etc/GMT-7 */
+ 0x0a1, /* Saint Pierre Standard Time / PM+ ==> America/Miquelon */
+ 0x131, /* Sakhalin Standard Time / RU+ ==> Asia/Sakhalin */
+ 0x20e, /* Samoa Standard Time / WS+ ==> Pacific/Apia */
+ 0x135, /* Singapore Standard Time / SG+ ==> Asia/Singapore */
+ 0x0f9, /* Singapore Standard Time / BN ==> Asia/Brunei */
+ 0x121, /* Singapore Standard Time / ID ==> Asia/Makassar */
+ 0x11b, /* Singapore Standard Time / MY ==> Asia/Kuala_Lumpur */
+ 0x11c, /* Singapore Standard Time / MY ==> Asia/Kuching */
+ 0x122, /* Singapore Standard Time / PH ==> Asia/Manila */
+ 0x1a0, /* Singapore Standard Time / ZZ ==> Etc/GMT-8 */
+ 0x019, /* South Africa Standard Time / ZA+ ==> Africa/Johannesburg */
+ 0x00c, /* South Africa Standard Time / BI ==> Africa/Bujumbura */
+ 0x017, /* South Africa Standard Time / BW ==> Africa/Gaborone */
+ 0x023, /* South Africa Standard Time / CD ==> Africa/Lubumbashi */
+ 0x027, /* South Africa Standard Time / LS ==> Africa/Maseru */
+ 0x00a, /* South Africa Standard Time / MW ==> Africa/Blantyre */
+ 0x026, /* South Africa Standard Time / MZ ==> Africa/Maputo */
+ 0x01d, /* South Africa Standard Time / RW ==> Africa/Kigali */
+ 0x028, /* South Africa Standard Time / SZ ==> Africa/Mbabane */
+ 0x024, /* South Africa Standard Time / ZM ==> Africa/Lusaka */
+ 0x018, /* South Africa Standard Time / ZW ==> Africa/Harare */
+ 0x19a, /* South Africa Standard Time / ZZ ==> Etc/GMT-2 */
+ 0x0ff, /* Sri Lanka Standard Time / LK+ ==> Asia/Colombo */
+ 0x101, /* Syria Standard Time / SY+ ==> Asia/Damascus */
+ 0x137, /* Taipei Standard Time / TW+ ==> Asia/Taipei */
+ 0x15f, /* Tasmania Standard Time / AU+ ==> Australia/Hobart */
+ 0x15c, /* Tasmania Standard Time / AU ==> Australia/Currie */
+ 0x03a, /* Tocantins Standard Time / BR+ ==> America/Araguaina */
+ 0x13e, /* Tokyo Standard Time / JP+ ==> Asia/Tokyo */
+ 0x110, /* Tokyo Standard Time / ID ==> Asia/Jayapura */
+ 0x22a, /* Tokyo Standard Time / PW ==> Pacific/Palau */
+ 0x103, /* Tokyo Standard Time / TL ==> Asia/Dili */
+ 0x1a1, /* Tokyo Standard Time / ZZ ==> Etc/GMT-9 */
+ 0x13f, /* Tomsk Standard Time / RU+ ==> Asia/Tomsk */
+ 0x234, /* Tonga Standard Time / TO+ ==> Pacific/Tongatapu */
+ 0x215, /* Tonga Standard Time / KI ==> Pacific/Enderbury */
+ 0x216, /* Tonga Standard Time / TK ==> Pacific/Fakaofo */
+ 0x198, /* Tonga Standard Time / ZZ ==> Etc/GMT-13 */
+ 0x0fb, /* Transbaikal Standard Time / RU+ ==> Asia/Chita */
+ 0x1bb, /* Turkey Standard Time / TR+ ==> Europe/Istanbul */
+ 0x106, /* Turkey Standard Time / CY ==> Asia/Famagusta */
+ 0x075, /* Turks And Caicos Standard Time / TC+ ==> America/Grand_Turk */
+ 0x086, /* US Eastern Standard Time / US+ ==> America/Indianapolis */
+ 0x080, /* US Eastern Standard Time / US ==> America/Indiana/Marengo */
+ 0x083, /* US Eastern Standard Time / US ==> America/Indiana/Vevay */
+ 0x0b3, /* US Mountain Standard Time / US+ ==> America/Phoenix */
+ 0x062, /* US Mountain Standard Time / CA ==> America/Creston */
+ 0x067, /* US Mountain Standard Time / CA ==> America/Dawson_Creek */
+ 0x06f, /* US Mountain Standard Time / CA ==> America/Fort_Nelson */
+ 0x07d, /* US Mountain Standard Time / MX ==> America/Hermosillo */
+ 0x190, /* US Mountain Standard Time / ZZ ==> Etc/GMT+7 */
+ 0x185, /* UTC / ZZ+ ==> Etc/GMT */
+ 0x065, /* UTC / GL ==> America/Danmarkshavn */
+ 0x1a5, /* UTC / ZZ ==> Etc/UTC */
+ 0x197, /* UTC+12 / ZZ+ ==> Etc/GMT-12 */
+ 0x233, /* UTC+12 / KI ==> Pacific/Tarawa */
+ 0x221, /* UTC+12 / MH ==> Pacific/Kwajalein */
+ 0x222, /* UTC+12 / MH ==> Pacific/Majuro */
+ 0x225, /* UTC+12 / NR ==> Pacific/Nauru */
+ 0x218, /* UTC+12 / TV ==> Pacific/Funafuti */
+ 0x236, /* UTC+12 / UM ==> Pacific/Wake */
+ 0x237, /* UTC+12 / WF ==> Pacific/Wallis */
+ 0x18b, /* UTC-02 / ZZ+ ==> Etc/GMT+2 */
+ 0x0ab, /* UTC-02 / BR ==> America/Noronha */
+ 0x154, /* UTC-02 / GS ==> Atlantic/South_Georgia */
+ 0x191, /* UTC-08 / ZZ+ ==> Etc/GMT+8 */
+ 0x22b, /* UTC-08 / PN ==> Pacific/Pitcairn */
+ 0x192, /* UTC-09 / ZZ+ ==> Etc/GMT+9 */
+ 0x21a, /* UTC-09 / PF ==> Pacific/Gambier */
+ 0x189, /* UTC-11 / ZZ+ ==> Etc/GMT+11 */
+ 0x229, /* UTC-11 / AS ==> Pacific/Pago_Pago */
+ 0x226, /* UTC-11 / NU ==> Pacific/Niue */
+ 0x224, /* UTC-11 / UM ==> Pacific/Midway */
+ 0x141, /* Ulaanbaatar Standard Time / MN+ ==> Asia/Ulaanbaatar */
+ 0x0fc, /* Ulaanbaatar Standard Time / MN ==> Asia/Choibalsan */
+ 0x059, /* Venezuela Standard Time / VE+ ==> America/Caracas */
+ 0x146, /* Vladivostok Standard Time / RU+ ==> Asia/Vladivostok */
+ 0x144, /* Vladivostok Standard Time / RU ==> Asia/Ust-Nera */
+ 0x166, /* W. Australia Standard Time / AU+ ==> Australia/Perth */
+ 0x01f, /* W. Central Africa Standard Time / NG+ ==> Africa/Lagos */
+ 0x022, /* W. Central Africa Standard Time / AO ==> Africa/Luanda */
+ 0x030, /* W. Central Africa Standard Time / BJ ==> Africa/Porto-Novo */
+ 0x01e, /* W. Central Africa Standard Time / CD ==> Africa/Kinshasa */
+ 0x007, /* W. Central Africa Standard Time / CF ==> Africa/Bangui */
+ 0x00b, /* W. Central Africa Standard Time / CG ==> Africa/Brazzaville */
+ 0x014, /* W. Central Africa Standard Time / CM ==> Africa/Douala */
+ 0x003, /* W. Central Africa Standard Time / DZ ==> Africa/Algiers */
+ 0x020, /* W. Central Africa Standard Time / GA ==> Africa/Libreville */
+ 0x025, /* W. Central Africa Standard Time / GQ ==> Africa/Malabo */
+ 0x02d, /* W. Central Africa Standard Time / NE ==> Africa/Niamey */
+ 0x02c, /* W. Central Africa Standard Time / TD ==> Africa/Ndjamena */
+ 0x034, /* W. Central Africa Standard Time / TN ==> Africa/Tunis */
+ 0x194, /* W. Central Africa Standard Time / ZZ ==> Etc/GMT-1 */
+ 0x1ae, /* W. Europe Standard Time / DE+ ==> Europe/Berlin */
+ 0x1a9, /* W. Europe Standard Time / AD ==> Europe/Andorra */
+ 0x1e0, /* W. Europe Standard Time / AT ==> Europe/Vienna */
+ 0x1e6, /* W. Europe Standard Time / CH ==> Europe/Zurich */
+ 0x1b3, /* W. Europe Standard Time / DE ==> Europe/Busingen */
+ 0x1b7, /* W. Europe Standard Time / GI ==> Europe/Gibraltar */
+ 0x1d0, /* W. Europe Standard Time / IT ==> Europe/Rome */
+ 0x1de, /* W. Europe Standard Time / LI ==> Europe/Vaduz */
+ 0x1c3, /* W. Europe Standard Time / LU ==> Europe/Luxembourg */
+ 0x1c8, /* W. Europe Standard Time / MC ==> Europe/Monaco */
+ 0x1c5, /* W. Europe Standard Time / MT ==> Europe/Malta */
+ 0x1a8, /* W. Europe Standard Time / NL ==> Europe/Amsterdam */
+ 0x1cb, /* W. Europe Standard Time / NO ==> Europe/Oslo */
+ 0x1d8, /* W. Europe Standard Time / SE ==> Europe/Stockholm */
+ 0x0e8, /* W. Europe Standard Time / SJ ==> Arctic/Longyearbyen */
+ 0x1d2, /* W. Europe Standard Time / SM ==> Europe/San_Marino */
+ 0x1df, /* W. Europe Standard Time / VA ==> Europe/Vatican */
+ 0x10c, /* W. Mongolia Standard Time / MN+ ==> Asia/Hovd */
+ 0x138, /* West Asia Standard Time / UZ+ ==> Asia/Tashkent */
+ 0x0e0, /* West Asia Standard Time / AQ ==> Antarctica/Mawson */
+ 0x0ed, /* West Asia Standard Time / KZ ==> Asia/Aqtau */
+ 0x0ee, /* West Asia Standard Time / KZ ==> Asia/Aqtobe */
+ 0x0f1, /* West Asia Standard Time / KZ ==> Asia/Atyrau */
+ 0x128, /* West Asia Standard Time / KZ ==> Asia/Oral */
+ 0x1f9, /* West Asia Standard Time / MV ==> Indian/Maldives */
+ 0x1f7, /* West Asia Standard Time / TF ==> Indian/Kerguelen */
+ 0x105, /* West Asia Standard Time / TJ ==> Asia/Dushanbe */
+ 0x0ef, /* West Asia Standard Time / TM ==> Asia/Ashgabat */
+ 0x132, /* West Asia Standard Time / UZ ==> Asia/Samarkand */
+ 0x19d, /* West Asia Standard Time / ZZ ==> Etc/GMT-5 */
+ 0x109, /* West Bank Standard Time / PS+ ==> Asia/Hebron */
+ 0x107, /* West Bank Standard Time / PS ==> Asia/Gaza */
+ 0x22e, /* West Pacific Standard Time / PG+ ==> Pacific/Port_Moresby */
+ 0x0de, /* West Pacific Standard Time / AQ ==> Antarctica/DumontDUrville */
+ 0x235, /* West Pacific Standard Time / FM ==> Pacific/Truk */
+ 0x21c, /* West Pacific Standard Time / GU ==> Pacific/Guam */
+ 0x230, /* West Pacific Standard Time / MP ==> Pacific/Saipan */
+ 0x195, /* West Pacific Standard Time / ZZ ==> Etc/GMT-10 */
+ 0x147, /* Yakutsk Standard Time / RU+ ==> Asia/Yakutsk */
+ 0x118, /* Yakutsk Standard Time / RU ==> Asia/Khandyga */
+};
+
+
+
+
+RTDECL(PCRTTIMEZONEINFO) RTTimeZoneGetInfoByUnixName(const char *pszName)
+{
+ /*
+ * Try a case sensitive binary search first.
+ */
+ /** @todo binary searching */
+
+ /*
+ * Fallback: Linear case-insensitive search.
+ */
+ size_t const cchName = strlen(pszName);
+ for (size_t i = 0; i < RT_ELEMENTS(g_aTimeZones); i++)
+ if ( g_aTimeZones[i].cchUnixName == cchName
+ && RTStrICmpAscii(pszName, g_aTimeZones[i].pszUnixName) == 0)
+ return &g_aTimeZones[i];
+ return NULL;
+}
+
+
+RTDECL(PCRTTIMEZONEINFO) RTTimeZoneGetInfoByWindowsName(const char *pszName)
+{
+ /*
+ * Try a case sensitive binary search first.
+ */
+ /** @todo binary searching */
+
+ /*
+ * Fallback: Linear case-insensitive search.
+ */
+ size_t const cchName = strlen(pszName);
+ for (size_t i = 0; i < RT_ELEMENTS(g_aidxWinTimeZones); i++)
+ {
+ PCRTTIMEZONEINFO pZone = &g_aTimeZones[g_aidxWinTimeZones[i]];
+ if ( pZone->cchWindowsName == cchName
+ && RTStrICmpAscii(pszName, pZone->pszWindowsName) == 0)
+ return pZone;
+ }
+ return NULL;
+}
+
+
+RTDECL(PCRTTIMEZONEINFO) RTTimeZoneGetInfoByWindowsIndex(uint32_t idxZone)
+{
+ for (size_t i = 0; i < RT_ELEMENTS(g_aidxWinTimeZones); i++)
+ {
+ PCRTTIMEZONEINFO pZone = &g_aTimeZones[g_aidxWinTimeZones[i]];
+ if (pZone->idxWindows == idxZone)
+ return pZone;
+ }
+ return NULL;
+}
+
diff --git a/src/VBox/Runtime/common/vfs/Makefile.kup b/src/VBox/Runtime/common/vfs/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/Makefile.kup
diff --git a/src/VBox/Runtime/common/vfs/vfsbase.cpp b/src/VBox/Runtime/common/vfs/vfsbase.cpp
new file mode 100644
index 00000000..aa810644
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsbase.cpp
@@ -0,0 +1,4385 @@
+/* $Id: vfsbase.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Base.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_FS
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include <iprt/asm.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/mem.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/poll.h>
+#include <iprt/semaphore.h>
+#include <iprt/thread.h>
+#include <iprt/zero.h>
+
+#include "internal/file.h"
+#include "internal/fs.h"
+#include "internal/magics.h"
+#include "internal/path.h"
+//#include "internal/vfs.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The instance data alignment. */
+#define RTVFS_INST_ALIGNMENT 16U
+
+/** The max number of symbolic links to resolve in a path. */
+#define RTVFS_MAX_LINKS 20U
+
+
+/** Asserts that the VFS base object vtable is valid. */
+#define RTVFSOBJ_ASSERT_OPS(a_pObjOps, a_enmType) \
+ do \
+ { \
+ Assert((a_pObjOps)->uVersion == RTVFSOBJOPS_VERSION); \
+ Assert((a_pObjOps)->enmType == (a_enmType) || (a_enmType) == RTVFSOBJTYPE_INVALID); \
+ AssertPtr((a_pObjOps)->pszName); \
+ Assert(*(a_pObjOps)->pszName); \
+ AssertPtr((a_pObjOps)->pfnClose); \
+ AssertPtr((a_pObjOps)->pfnQueryInfo); \
+ AssertPtrNull((a_pObjOps)->pfnQueryInfoEx); \
+ Assert((a_pObjOps)->uEndMarker == RTVFSOBJOPS_VERSION); \
+ } while (0)
+
+/** Asserts that the VFS set object vtable is valid. */
+#define RTVFSOBJSET_ASSERT_OPS(a_pSetOps, a_offObjOps) \
+ do \
+ { \
+ Assert((a_pSetOps)->uVersion == RTVFSOBJSETOPS_VERSION); \
+ Assert((a_pSetOps)->offObjOps == (a_offObjOps)); \
+ AssertPtrNull((a_pSetOps)->pfnSetMode); \
+ AssertPtrNull((a_pSetOps)->pfnSetTimes); \
+ AssertPtrNull((a_pSetOps)->pfnSetOwner); \
+ Assert((a_pSetOps)->uEndMarker == RTVFSOBJSETOPS_VERSION); \
+ } while (0)
+
+/** Asserts that the VFS directory vtable is valid. */
+#define RTVFSDIR_ASSERT_OPS(pDirOps, a_enmType) \
+ do { \
+ RTVFSOBJ_ASSERT_OPS(&(pDirOps)->Obj, a_enmType); \
+ RTVFSOBJSET_ASSERT_OPS(&(pDirOps)->ObjSet, RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj)); \
+ Assert((pDirOps)->uVersion == RTVFSDIROPS_VERSION); \
+ Assert(!(pDirOps)->fReserved); \
+ AssertPtr((pDirOps)->pfnOpen); \
+ AssertPtrNull((pDirOps)->pfnOpenFile); \
+ AssertPtrNull((pDirOps)->pfnOpenDir); \
+ AssertPtrNull((pDirOps)->pfnCreateDir); \
+ AssertPtrNull((pDirOps)->pfnOpenSymlink); \
+ AssertPtr((pDirOps)->pfnCreateSymlink); \
+ AssertPtr((pDirOps)->pfnUnlinkEntry); \
+ AssertPtr((pDirOps)->pfnRewindDir); \
+ AssertPtr((pDirOps)->pfnReadDir); \
+ Assert((pDirOps)->uEndMarker == RTVFSDIROPS_VERSION); \
+ } while (0)
+
+/** Asserts that the VFS I/O stream vtable is valid. */
+#define RTVFSIOSTREAM_ASSERT_OPS(pIoStreamOps, a_enmType) \
+ do { \
+ RTVFSOBJ_ASSERT_OPS(&(pIoStreamOps)->Obj, a_enmType); \
+ Assert((pIoStreamOps)->uVersion == RTVFSIOSTREAMOPS_VERSION); \
+ Assert(!((pIoStreamOps)->fFeatures & ~RTVFSIOSTREAMOPS_FEAT_VALID_MASK)); \
+ AssertPtr((pIoStreamOps)->pfnRead); \
+ AssertPtrNull((pIoStreamOps)->pfnWrite); \
+ AssertPtr((pIoStreamOps)->pfnFlush); \
+ AssertPtrNull((pIoStreamOps)->pfnPollOne); \
+ AssertPtr((pIoStreamOps)->pfnTell); \
+ AssertPtrNull((pIoStreamOps)->pfnSkip); \
+ AssertPtrNull((pIoStreamOps)->pfnZeroFill); \
+ Assert((pIoStreamOps)->uEndMarker == RTVFSIOSTREAMOPS_VERSION); \
+ } while (0)
+
+/** Asserts that the VFS I/O stream vtable is valid. */
+#define RTVFSFILE_ASSERT_OPS(pFileOps, a_enmType) \
+ do { \
+ RTVFSIOSTREAM_ASSERT_OPS(&(pFileOps)->Stream, a_enmType); \
+ Assert((pFileOps)->uVersion == RTVFSFILEOPS_VERSION); \
+ Assert((pFileOps)->fReserved == 0); \
+ AssertPtr((pFileOps)->pfnSeek); \
+ AssertPtrNull((pFileOps)->pfnQuerySize); \
+ AssertPtrNull((pFileOps)->pfnSetSize); \
+ AssertPtrNull((pFileOps)->pfnQueryMaxSize); \
+ Assert((pFileOps)->uEndMarker == RTVFSFILEOPS_VERSION); \
+ } while (0)
+
+/** Asserts that the VFS symlink vtable is valid. */
+#define RTVFSSYMLINK_ASSERT_OPS(pSymlinkOps, a_enmType) \
+ do { \
+ RTVFSOBJ_ASSERT_OPS(&(pSymlinkOps)->Obj, a_enmType); \
+ RTVFSOBJSET_ASSERT_OPS(&(pSymlinkOps)->ObjSet, RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj)); \
+ Assert((pSymlinkOps)->uVersion == RTVFSSYMLINKOPS_VERSION); \
+ Assert(!(pSymlinkOps)->fReserved); \
+ AssertPtr((pSymlinkOps)->pfnRead); \
+ Assert((pSymlinkOps)->uEndMarker == RTVFSSYMLINKOPS_VERSION); \
+ } while (0)
+
+
+/** Validates a VFS handle and returns @a rcRet if it's invalid. */
+#define RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, rcRet) \
+ do { \
+ if ((hVfs) != NIL_RTVFS) \
+ { \
+ AssertPtrReturn((hVfs), (rcRet)); \
+ AssertReturn((hVfs)->uMagic == RTVFS_MAGIC, (rcRet)); \
+ } \
+ } while (0)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** @todo Move all this stuff to internal/vfs.h */
+
+
+/**
+ * The VFS internal lock data.
+ */
+typedef struct RTVFSLOCKINTERNAL
+{
+ /** The number of references to the this lock. */
+ uint32_t volatile cRefs;
+ /** The lock type. */
+ RTVFSLOCKTYPE enmType;
+ /** Type specific data. */
+ union
+ {
+ /** Read/Write semaphore handle. */
+ RTSEMRW hSemRW;
+ /** Fast mutex semaphore handle. */
+ RTSEMFASTMUTEX hFastMtx;
+ /** Regular mutex semaphore handle. */
+ RTSEMMUTEX hMtx;
+ } u;
+} RTVFSLOCKINTERNAL;
+
+
+/**
+ * The VFS base object handle data.
+ *
+ * All other VFS handles are derived from this one. The final handle type is
+ * indicated by RTVFSOBJOPS::enmType via the RTVFSOBJINTERNAL::pOps member.
+ */
+typedef struct RTVFSOBJINTERNAL
+{
+ /** The VFS magic (RTVFSOBJ_MAGIC). */
+ uint32_t uMagic : 31;
+ /** Set if we've got no VFS reference but still got a valid hVfs.
+ * This is hack for permanent root directory objects. */
+ uint32_t fNoVfsRef : 1;
+ /** The number of references to this VFS object. */
+ uint32_t volatile cRefs;
+ /** Pointer to the instance data. */
+ void *pvThis;
+ /** The vtable. */
+ PCRTVFSOBJOPS pOps;
+ /** The lock protecting all access to the VFS.
+ * Only valid if RTVFS_C_THREAD_SAFE is set, otherwise it is NIL_RTVFSLOCK. */
+ RTVFSLOCK hLock;
+ /** Reference back to the VFS containing this object. */
+ RTVFS hVfs;
+} RTVFSOBJINTERNAL;
+
+
+/**
+ * The VFS filesystem stream handle data.
+ *
+ * @extends RTVFSOBJINTERNAL
+ */
+typedef struct RTVFSFSSTREAMINTERNAL
+{
+ /** The VFS magic (RTVFSFSTREAM_MAGIC). */
+ uint32_t uMagic;
+ /** File open flags, at a minimum the access mask. */
+ uint32_t fFlags;
+ /** The vtable. */
+ PCRTVFSFSSTREAMOPS pOps;
+ /** The base object handle data. */
+ RTVFSOBJINTERNAL Base;
+} RTVFSFSSTREAMINTERNAL;
+
+
+/**
+ * The VFS handle data.
+ *
+ * @extends RTVFSOBJINTERNAL
+ */
+typedef struct RTVFSINTERNAL
+{
+ /** The VFS magic (RTVFS_MAGIC). */
+ uint32_t uMagic;
+ /** Creation flags (RTVFS_C_XXX). */
+ uint32_t fFlags;
+ /** The vtable. */
+ PCRTVFSOPS pOps;
+ /** The base object handle data. */
+ RTVFSOBJINTERNAL Base;
+} RTVFSINTERNAL;
+
+
+/**
+ * The VFS directory handle data.
+ *
+ * @extends RTVFSOBJINTERNAL
+ */
+typedef struct RTVFSDIRINTERNAL
+{
+ /** The VFS magic (RTVFSDIR_MAGIC). */
+ uint32_t uMagic;
+ /** Reserved for flags or something. */
+ uint32_t fReserved;
+ /** The vtable. */
+ PCRTVFSDIROPS pOps;
+ /** The base object handle data. */
+ RTVFSOBJINTERNAL Base;
+} RTVFSDIRINTERNAL;
+
+
+/**
+ * The VFS symbolic link handle data.
+ *
+ * @extends RTVFSOBJINTERNAL
+ */
+typedef struct RTVFSSYMLINKINTERNAL
+{
+ /** The VFS magic (RTVFSSYMLINK_MAGIC). */
+ uint32_t uMagic;
+ /** Reserved for flags or something. */
+ uint32_t fReserved;
+ /** The vtable. */
+ PCRTVFSSYMLINKOPS pOps;
+ /** The base object handle data. */
+ RTVFSOBJINTERNAL Base;
+} RTVFSSYMLINKINTERNAL;
+
+
+/**
+ * The VFS I/O stream handle data.
+ *
+ * This is often part of a type specific handle, like a file or pipe.
+ *
+ * @extends RTVFSOBJINTERNAL
+ */
+typedef struct RTVFSIOSTREAMINTERNAL
+{
+ /** The VFS magic (RTVFSIOSTREAM_MAGIC). */
+ uint32_t uMagic;
+ /** File open flags, at a minimum the access mask. */
+ uint32_t fFlags;
+ /** The vtable. */
+ PCRTVFSIOSTREAMOPS pOps;
+ /** The base object handle data. */
+ RTVFSOBJINTERNAL Base;
+} RTVFSIOSTREAMINTERNAL;
+
+
+/**
+ * The VFS file handle data.
+ *
+ * @extends RTVFSIOSTREAMINTERNAL
+ */
+typedef struct RTVFSFILEINTERNAL
+{
+ /** The VFS magic (RTVFSFILE_MAGIC). */
+ uint32_t uMagic;
+ /** Reserved for flags or something. */
+ uint32_t fReserved;
+ /** The vtable. */
+ PCRTVFSFILEOPS pOps;
+ /** The stream handle data. */
+ RTVFSIOSTREAMINTERNAL Stream;
+} RTVFSFILEINTERNAL;
+
+#if 0 /* later */
+
+/**
+ * The VFS pipe handle data.
+ *
+ * @extends RTVFSIOSTREAMINTERNAL
+ */
+typedef struct RTVFSPIPEINTERNAL
+{
+ /** The VFS magic (RTVFSPIPE_MAGIC). */
+ uint32_t uMagic;
+ /** Reserved for flags or something. */
+ uint32_t fReserved;
+ /** The vtable. */
+ PCRTVFSPIPEOPS pOps;
+ /** The stream handle data. */
+ RTVFSIOSTREAMINTERNAL Stream;
+} RTVFSPIPEINTERNAL;
+
+
+/**
+ * The VFS socket handle data.
+ *
+ * @extends RTVFSIOSTREAMINTERNAL
+ */
+typedef struct RTVFSSOCKETINTERNAL
+{
+ /** The VFS magic (RTVFSSOCKET_MAGIC). */
+ uint32_t uMagic;
+ /** Reserved for flags or something. */
+ uint32_t fReserved;
+ /** The vtable. */
+ PCRTVFSSOCKETOPS pOps;
+ /** The stream handle data. */
+ RTVFSIOSTREAMINTERNAL Stream;
+} RTVFSSOCKETINTERNAL;
+
+#endif /* later */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+DECLINLINE(uint32_t) rtVfsObjRelease(RTVFSOBJINTERNAL *pThis);
+static int rtVfsTraverseToParent(RTVFSINTERNAL *pThis, PRTVFSPARSEDPATH pPath, uint32_t fFlags, RTVFSDIRINTERNAL **ppVfsParentDir);
+static int rtVfsDirFollowSymlinkObjToParent(RTVFSDIRINTERNAL **ppVfsParentDir, RTVFSOBJ hVfsObj,
+ PRTVFSPARSEDPATH pPath, uint32_t fFlags);
+
+
+
+/**
+ * Translates a RTVFSOBJTYPE value into a string.
+ *
+ * @returns Pointer to readonly name.
+ * @param enmType The object type to name.
+ */
+RTDECL(const char *) RTVfsTypeName(RTVFSOBJTYPE enmType)
+{
+ switch (enmType)
+ {
+ case RTVFSOBJTYPE_INVALID: return "invalid";
+ case RTVFSOBJTYPE_BASE: return "base";
+ case RTVFSOBJTYPE_VFS: return "VFS";
+ case RTVFSOBJTYPE_FS_STREAM: return "FS stream";
+ case RTVFSOBJTYPE_IO_STREAM: return "I/O stream";
+ case RTVFSOBJTYPE_DIR: return "directory";
+ case RTVFSOBJTYPE_FILE: return "file";
+ case RTVFSOBJTYPE_SYMLINK: return "symlink";
+ case RTVFSOBJTYPE_END: return "end";
+ case RTVFSOBJTYPE_32BIT_HACK:
+ break;
+ }
+ return "unknown";
+}
+
+
+/*
+ *
+ * V F S L o c k A b s t r a c t i o n
+ * V F S L o c k A b s t r a c t i o n
+ * V F S L o c k A b s t r a c t i o n
+ *
+ *
+ */
+
+
+RTDECL(uint32_t) RTVfsLockRetain(RTVFSLOCK hLock)
+{
+ RTVFSLOCKINTERNAL *pThis = hLock;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->enmType > RTVFSLOCKTYPE_INVALID && pThis->enmType < RTVFSLOCKTYPE_END, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p %d\n", cRefs, pThis, pThis->enmType));
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTVfsLockRetainDebug(RTVFSLOCK hLock, RT_SRC_POS_DECL)
+{
+ RTVFSLOCKINTERNAL *pThis = hLock;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->enmType > RTVFSLOCKTYPE_INVALID && pThis->enmType < RTVFSLOCKTYPE_END, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p %d\n", cRefs, pThis, pThis->enmType));
+ LogFlow(("RTVfsLockRetainDebug(%p) -> %d; caller: %s %s(%u)\n", hLock, cRefs, pszFunction, pszFile, iLine));
+ RT_SRC_POS_NOREF();
+ return cRefs;
+}
+
+
+/**
+ * Destroys a VFS lock handle.
+ *
+ * @param pThis The lock to destroy.
+ */
+static void rtVfsLockDestroy(RTVFSLOCKINTERNAL *pThis)
+{
+ switch (pThis->enmType)
+ {
+ case RTVFSLOCKTYPE_RW:
+ RTSemRWDestroy(pThis->u.hSemRW);
+ pThis->u.hSemRW = NIL_RTSEMRW;
+ break;
+
+ case RTVFSLOCKTYPE_FASTMUTEX:
+ RTSemFastMutexDestroy(pThis->u.hFastMtx);
+ pThis->u.hFastMtx = NIL_RTSEMFASTMUTEX;
+ break;
+
+ case RTVFSLOCKTYPE_MUTEX:
+ RTSemMutexDestroy(pThis->u.hMtx);
+ pThis->u.hFastMtx = NIL_RTSEMMUTEX;
+ break;
+
+ default:
+ AssertMsgFailedReturnVoid(("%p %d\n", pThis, pThis->enmType));
+ }
+
+ pThis->enmType = RTVFSLOCKTYPE_INVALID;
+ RTMemFree(pThis);
+}
+
+
+RTDECL(uint32_t) RTVfsLockRelease(RTVFSLOCK hLock)
+{
+ RTVFSLOCKINTERNAL *pThis = hLock;
+ if (pThis == NIL_RTVFSLOCK)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->enmType > RTVFSLOCKTYPE_INVALID && pThis->enmType < RTVFSLOCKTYPE_END, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p %d\n", cRefs, pThis, pThis->enmType));
+ if (cRefs == 0)
+ rtVfsLockDestroy(pThis);
+ return cRefs;
+}
+
+
+/**
+ * Creates a read/write lock.
+ *
+ * @returns IPRT status code
+ * @param phLock Where to return the lock handle.
+ */
+static int rtVfsLockCreateRW(PRTVFSLOCK phLock)
+{
+ RTVFSLOCKINTERNAL *pThis = (RTVFSLOCKINTERNAL *)RTMemAlloc(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ pThis->cRefs = 1;
+ pThis->enmType = RTVFSLOCKTYPE_RW;
+
+ int rc = RTSemRWCreate(&pThis->u.hSemRW);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ *phLock = pThis;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Creates a fast mutex lock.
+ *
+ * @returns IPRT status code
+ * @param phLock Where to return the lock handle.
+ */
+static int rtVfsLockCreateFastMutex(PRTVFSLOCK phLock)
+{
+ RTVFSLOCKINTERNAL *pThis = (RTVFSLOCKINTERNAL *)RTMemAlloc(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ pThis->cRefs = 1;
+ pThis->enmType = RTVFSLOCKTYPE_FASTMUTEX;
+
+ int rc = RTSemFastMutexCreate(&pThis->u.hFastMtx);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ *phLock = pThis;
+ return VINF_SUCCESS;
+
+}
+
+
+/**
+ * Creates a mutex lock.
+ *
+ * @returns IPRT status code
+ * @param phLock Where to return the lock handle.
+ */
+static int rtVfsLockCreateMutex(PRTVFSLOCK phLock)
+{
+ RTVFSLOCKINTERNAL *pThis = (RTVFSLOCKINTERNAL *)RTMemAlloc(sizeof(*pThis));
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ pThis->cRefs = 1;
+ pThis->enmType = RTVFSLOCKTYPE_MUTEX;
+
+ int rc = RTSemMutexCreate(&pThis->u.hMtx);
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ *phLock = pThis;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Acquires the lock for reading.
+ *
+ * @param hLock Non-nil lock handle.
+ * @internal
+ */
+RTDECL(void) RTVfsLockAcquireReadSlow(RTVFSLOCK hLock)
+{
+ RTVFSLOCKINTERNAL *pThis = hLock;
+ int rc;
+
+ AssertPtr(pThis);
+ switch (pThis->enmType)
+ {
+ case RTVFSLOCKTYPE_RW:
+ rc = RTSemRWRequestRead(pThis->u.hSemRW, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ break;
+
+ case RTVFSLOCKTYPE_FASTMUTEX:
+ rc = RTSemFastMutexRequest(pThis->u.hFastMtx);
+ AssertRC(rc);
+ break;
+
+ case RTVFSLOCKTYPE_MUTEX:
+ rc = RTSemMutexRequest(pThis->u.hMtx, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ break;
+ default:
+ AssertFailed();
+ }
+}
+
+
+/**
+ * Release a lock held for reading.
+ *
+ * @param hLock Non-nil lock handle.
+ * @internal
+ */
+RTDECL(void) RTVfsLockReleaseReadSlow(RTVFSLOCK hLock)
+{
+ RTVFSLOCKINTERNAL *pThis = hLock;
+ int rc;
+
+ AssertPtr(pThis);
+ switch (pThis->enmType)
+ {
+ case RTVFSLOCKTYPE_RW:
+ rc = RTSemRWReleaseRead(pThis->u.hSemRW);
+ AssertRC(rc);
+ break;
+
+ case RTVFSLOCKTYPE_FASTMUTEX:
+ rc = RTSemFastMutexRelease(pThis->u.hFastMtx);
+ AssertRC(rc);
+ break;
+
+ case RTVFSLOCKTYPE_MUTEX:
+ rc = RTSemMutexRelease(pThis->u.hMtx);
+ AssertRC(rc);
+ break;
+ default:
+ AssertFailed();
+ }
+}
+
+
+/**
+ * Acquires the lock for writing.
+ *
+ * @param hLock Non-nil lock handle.
+ * @internal
+ */
+RTDECL(void) RTVfsLockAcquireWriteSlow(RTVFSLOCK hLock)
+{
+ RTVFSLOCKINTERNAL *pThis = hLock;
+ int rc;
+
+ AssertPtr(pThis);
+ switch (pThis->enmType)
+ {
+ case RTVFSLOCKTYPE_RW:
+ rc = RTSemRWRequestWrite(pThis->u.hSemRW, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ break;
+
+ case RTVFSLOCKTYPE_FASTMUTEX:
+ rc = RTSemFastMutexRequest(pThis->u.hFastMtx);
+ AssertRC(rc);
+ break;
+
+ case RTVFSLOCKTYPE_MUTEX:
+ rc = RTSemMutexRequest(pThis->u.hMtx, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ break;
+ default:
+ AssertFailed();
+ }
+}
+
+
+/**
+ * Release a lock held for writing.
+ *
+ * @param hLock Non-nil lock handle.
+ * @internal
+ */
+RTDECL(void) RTVfsLockReleaseWriteSlow(RTVFSLOCK hLock)
+{
+ RTVFSLOCKINTERNAL *pThis = hLock;
+ int rc;
+
+ AssertPtr(pThis);
+ switch (pThis->enmType)
+ {
+ case RTVFSLOCKTYPE_RW:
+ rc = RTSemRWReleaseWrite(pThis->u.hSemRW);
+ AssertRC(rc);
+ break;
+
+ case RTVFSLOCKTYPE_FASTMUTEX:
+ rc = RTSemFastMutexRelease(pThis->u.hFastMtx);
+ AssertRC(rc);
+ break;
+
+ case RTVFSLOCKTYPE_MUTEX:
+ rc = RTSemMutexRelease(pThis->u.hMtx);
+ AssertRC(rc);
+ break;
+ default:
+ AssertFailed();
+ }
+}
+
+
+
+/*
+ *
+ * B A S E O B J E C T
+ * B A S E O B J E C T
+ * B A S E O B J E C T
+ *
+ */
+
+/**
+ * Internal object retainer that asserts sanity in strict builds.
+ *
+ * @param pThis The base object handle data.
+ * @param pszCaller Where we're called from.
+ */
+DECLINLINE(void) rtVfsObjRetainVoid(RTVFSOBJINTERNAL *pThis, const char *pszCaller)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+LogFlow(("rtVfsObjRetainVoid(%p/%p) -> %d; caller=%s\n", pThis, pThis->pvThis, cRefs, pszCaller)); RT_NOREF(pszCaller);
+ AssertMsg(cRefs > 1 && cRefs < _1M,
+ ("%#x %p ops=%p %s (%d); caller=%s\n", cRefs, pThis, pThis->pOps, pThis->pOps->pszName, pThis->pOps->enmType, pszCaller));
+ NOREF(cRefs);
+}
+
+
+/**
+ * Initializes the base object part of a new object.
+ *
+ * @returns IPRT status code.
+ * @param pThis Pointer to the base object part.
+ * @param pObjOps The base object vtable.
+ * @param hVfs The VFS handle to associate with.
+ * @param fNoVfsRef If set, do not retain an additional reference to
+ * @a hVfs. Permanent root dir hack.
+ * @param hLock The lock handle, pseudo handle or nil.
+ * @param pvThis Pointer to the private data.
+ */
+static int rtVfsObjInitNewObject(RTVFSOBJINTERNAL *pThis, PCRTVFSOBJOPS pObjOps, RTVFS hVfs, bool fNoVfsRef,
+ RTVFSLOCK hLock, void *pvThis)
+{
+ /*
+ * Deal with the lock first as that's the most complicated matter.
+ */
+ if (hLock != NIL_RTVFSLOCK)
+ {
+ int rc;
+ if (hLock == RTVFSLOCK_CREATE_RW)
+ {
+ rc = rtVfsLockCreateRW(&hLock);
+ AssertRCReturn(rc, rc);
+ }
+ else if (hLock == RTVFSLOCK_CREATE_FASTMUTEX)
+ {
+ rc = rtVfsLockCreateFastMutex(&hLock);
+ AssertRCReturn(rc, rc);
+ }
+ else if (hLock == RTVFSLOCK_CREATE_MUTEX)
+ {
+ rc = rtVfsLockCreateMutex(&hLock);
+ AssertRCReturn(rc, rc);
+ }
+ else
+ {
+ /*
+ * The caller specified a lock, we consume the this reference.
+ */
+ AssertPtrReturn(hLock, VERR_INVALID_HANDLE);
+ AssertReturn(hLock->enmType > RTVFSLOCKTYPE_INVALID && hLock->enmType < RTVFSLOCKTYPE_END, VERR_INVALID_HANDLE);
+ AssertReturn(hLock->cRefs > 0, VERR_INVALID_HANDLE);
+ }
+ }
+ else if (hVfs != NIL_RTVFS)
+ {
+ /*
+ * Retain a reference to the VFS lock, if there is one.
+ */
+ hLock = hVfs->Base.hLock;
+ if (hLock != NIL_RTVFSLOCK)
+ {
+ uint32_t cRefs = RTVfsLockRetain(hLock);
+ if (RT_UNLIKELY(cRefs == UINT32_MAX))
+ return VERR_INVALID_HANDLE;
+ }
+ }
+
+
+ /*
+ * Do the actual initializing.
+ */
+ pThis->uMagic = RTVFSOBJ_MAGIC;
+ pThis->fNoVfsRef = fNoVfsRef;
+ pThis->pvThis = pvThis;
+ pThis->pOps = pObjOps;
+ pThis->cRefs = 1;
+ pThis->hVfs = hVfs;
+ pThis->hLock = hLock;
+ if (hVfs != NIL_RTVFS && !fNoVfsRef)
+ rtVfsObjRetainVoid(&hVfs->Base, "rtVfsObjInitNewObject");
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTVfsNewBaseObj(PCRTVFSOBJOPS pObjOps, size_t cbInstance, RTVFS hVfs, RTVFSLOCK hLock,
+ PRTVFSOBJ phVfsObj, void **ppvInstance)
+{
+ /*
+ * Validate the input, be extra strict in strict builds.
+ */
+ AssertPtr(pObjOps);
+ AssertReturn(pObjOps->uVersion == RTVFSOBJOPS_VERSION, VERR_VERSION_MISMATCH);
+ AssertReturn(pObjOps->uEndMarker == RTVFSOBJOPS_VERSION, VERR_VERSION_MISMATCH);
+ RTVFSOBJ_ASSERT_OPS(pObjOps, RTVFSOBJTYPE_BASE);
+ Assert(cbInstance > 0);
+ AssertPtr(ppvInstance);
+ AssertPtr(phVfsObj);
+ RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE);
+
+ /*
+ * Allocate the handle + instance data.
+ */
+ size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSOBJINTERNAL), RTVFS_INST_ALIGNMENT)
+ + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT);
+ RTVFSOBJINTERNAL *pThis = (RTVFSOBJINTERNAL *)RTMemAllocZ(cbThis);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ int rc = rtVfsObjInitNewObject(pThis, pObjOps, hVfs, false /*fNoVfsRef*/, hLock,
+ (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT));
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ *phVfsObj = pThis;
+ *ppvInstance = pThis->pvThis;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void *) RTVfsObjToPrivate(RTVFSOBJ hVfsObj, PCRTVFSOBJOPS pObjOps)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NULL);
+ if (pThis->pOps != pObjOps)
+ return NULL;
+ return pThis->pvThis;
+}
+
+
+/**
+ * Internal object retainer that asserts sanity in strict builds.
+ *
+ * @returns The new reference count.
+ * @param pThis The base object handle data.
+ */
+DECLINLINE(uint32_t) rtVfsObjRetain(RTVFSOBJINTERNAL *pThis)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+LogFlow(("rtVfsObjRetain(%p/%p) -> %d\n", pThis, pThis->pvThis, cRefs));
+ AssertMsg(cRefs > 1 && cRefs < _1M,
+ ("%#x %p ops=%p %s (%d)\n", cRefs, pThis, pThis->pOps, pThis->pOps->pszName, pThis->pOps->enmType));
+ return cRefs;
+}
+
+/**
+ * Internal object retainer that asserts sanity in strict builds.
+ *
+ * @returns The new reference count.
+ * @param pThis The base object handle data.
+ */
+DECLINLINE(uint32_t) rtVfsObjRetainDebug(RTVFSOBJINTERNAL *pThis, const char *pszApi, RT_SRC_POS_DECL)
+{
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ AssertMsg(cRefs > 1 && cRefs < _1M,
+ ("%#x %p ops=%p %s (%d)\n", cRefs, pThis, pThis->pOps, pThis->pOps->pszName, pThis->pOps->enmType));
+ LogFlow(("%s(%p/%p) -> %2d; caller: %s %s(%d) \n", pszApi, pThis, pThis->pvThis, cRefs, pszFunction, pszFile, iLine));
+ RT_SRC_POS_NOREF(); RT_NOREF(pszApi);
+ return cRefs;
+}
+
+
+#ifdef DEBUG
+# undef RTVfsObjRetain
+#endif
+RTDECL(uint32_t) RTVfsObjRetain(RTVFSOBJ hVfsObj)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, UINT32_MAX);
+
+ return rtVfsObjRetain(pThis);
+}
+#ifdef DEBUG
+# define RTVfsObjRetain(hVfsObj) RTVfsObjRetainDebug(hVfsObj, RT_SRC_POS)
+#endif
+
+
+RTDECL(uint32_t) RTVfsObjRetainDebug(RTVFSOBJ hVfsObj, RT_SRC_POS_DECL)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, UINT32_MAX);
+
+ return rtVfsObjRetainDebug(pThis, "RTVfsObjRetainDebug", RT_SRC_POS_ARGS);
+}
+
+
+/**
+ * Does the actual object destruction for rtVfsObjRelease().
+ *
+ * @param pThis The object to destroy.
+ */
+static void rtVfsObjDestroy(RTVFSOBJINTERNAL *pThis)
+{
+ RTVFSOBJTYPE const enmType = pThis->pOps->enmType;
+
+ /*
+ * Invalidate the object.
+ */
+ RTVfsLockAcquireWrite(pThis->hLock); /* paranoia */
+ void *pvToFree = NULL;
+ switch (enmType)
+ {
+ case RTVFSOBJTYPE_BASE:
+ pvToFree = pThis;
+ break;
+
+ case RTVFSOBJTYPE_VFS:
+ pvToFree = RT_FROM_MEMBER(pThis, RTVFSINTERNAL, Base);
+ ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSINTERNAL, Base)->uMagic, RTVFS_MAGIC_DEAD);
+ break;
+
+ case RTVFSOBJTYPE_FS_STREAM:
+ pvToFree = RT_FROM_MEMBER(pThis, RTVFSFSSTREAMINTERNAL, Base);
+ ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSFSSTREAMINTERNAL, Base)->uMagic, RTVFSFSSTREAM_MAGIC_DEAD);
+ break;
+
+ case RTVFSOBJTYPE_IO_STREAM:
+ pvToFree = RT_FROM_MEMBER(pThis, RTVFSIOSTREAMINTERNAL, Base);
+ ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSIOSTREAMINTERNAL, Base)->uMagic, RTVFSIOSTREAM_MAGIC_DEAD);
+ break;
+
+ case RTVFSOBJTYPE_DIR:
+ pvToFree = RT_FROM_MEMBER(pThis, RTVFSDIRINTERNAL, Base);
+ ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSDIRINTERNAL, Base)->uMagic, RTVFSDIR_MAGIC_DEAD);
+ break;
+
+ case RTVFSOBJTYPE_FILE:
+ pvToFree = RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream.Base);
+ ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream.Base)->uMagic, RTVFSFILE_MAGIC_DEAD);
+ ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSIOSTREAMINTERNAL, Base)->uMagic, RTVFSIOSTREAM_MAGIC_DEAD);
+ break;
+
+ case RTVFSOBJTYPE_SYMLINK:
+ pvToFree = RT_FROM_MEMBER(pThis, RTVFSSYMLINKINTERNAL, Base);
+ ASMAtomicWriteU32(&RT_FROM_MEMBER(pThis, RTVFSSYMLINKINTERNAL, Base)->uMagic, RTVFSSYMLINK_MAGIC_DEAD);
+ break;
+
+ case RTVFSOBJTYPE_INVALID:
+ case RTVFSOBJTYPE_END:
+ case RTVFSOBJTYPE_32BIT_HACK:
+ AssertMsgFailed(("enmType=%d ops=%p %s\n", enmType, pThis->pOps, pThis->pOps->pszName));
+ break;
+ /* no default as we want gcc warnings. */
+ }
+ pThis->uMagic = RTVFSOBJ_MAGIC_DEAD;
+ RTVfsLockReleaseWrite(pThis->hLock);
+
+ /*
+ * Close the object and free the handle.
+ */
+ int rc = pThis->pOps->pfnClose(pThis->pvThis);
+ AssertRC(rc);
+ if (pThis->hVfs != NIL_RTVFS)
+ {
+ if (!pThis->fNoVfsRef)
+ rtVfsObjRelease(&pThis->hVfs->Base);
+ pThis->hVfs = NIL_RTVFS;
+ }
+ if (pThis->hLock != NIL_RTVFSLOCK)
+ {
+ RTVfsLockRelease(pThis->hLock);
+ pThis->hLock = NIL_RTVFSLOCK;
+ }
+ RTMemFree(pvToFree);
+}
+
+
+/**
+ * Internal object releaser that asserts sanity in strict builds.
+ *
+ * @returns The new reference count.
+ * @param pcRefs The reference counter.
+ */
+DECLINLINE(uint32_t) rtVfsObjRelease(RTVFSOBJINTERNAL *pThis)
+{
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ AssertMsg(cRefs < _1M, ("%#x %p ops=%p %s (%d)\n", cRefs, pThis, pThis->pOps, pThis->pOps->pszName, pThis->pOps->enmType));
+ LogFlow(("rtVfsObjRelease(%p/%p) -> %d\n", pThis, pThis->pvThis, cRefs));
+ if (cRefs == 0)
+ rtVfsObjDestroy(pThis);
+ return cRefs;
+}
+
+
+RTDECL(uint32_t) RTVfsObjRelease(RTVFSOBJ hVfsObj)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ if (pThis == NIL_RTVFSOBJ)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, UINT32_MAX);
+ return rtVfsObjRelease(pThis);
+}
+
+
+RTDECL(RTVFSOBJTYPE) RTVfsObjGetType(RTVFSOBJ hVfsObj)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ if (pThis != NIL_RTVFSOBJ)
+ {
+ AssertPtrReturn(pThis, RTVFSOBJTYPE_INVALID);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, RTVFSOBJTYPE_INVALID);
+ return pThis->pOps->enmType;
+ }
+ return RTVFSOBJTYPE_INVALID;
+}
+
+
+RTDECL(RTVFS) RTVfsObjToVfs(RTVFSOBJ hVfsObj)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ if (pThis != NIL_RTVFSOBJ)
+ {
+ AssertPtrReturn(pThis, NIL_RTVFS);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFS);
+
+ if (pThis->pOps->enmType == RTVFSOBJTYPE_VFS)
+ {
+ rtVfsObjRetainVoid(pThis, "RTVfsObjToVfs");
+ LogFlow(("RTVfsObjToVfs(%p) -> %p\n", pThis, RT_FROM_MEMBER(pThis, RTVFSINTERNAL, Base)));
+ return RT_FROM_MEMBER(pThis, RTVFSINTERNAL, Base);
+ }
+ }
+ return NIL_RTVFS;
+}
+
+
+RTDECL(RTVFSFSSTREAM) RTVfsObjToFsStream(RTVFSOBJ hVfsObj)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ if (pThis != NIL_RTVFSOBJ)
+ {
+ AssertPtrReturn(pThis, NIL_RTVFSFSSTREAM);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSFSSTREAM);
+
+ if (pThis->pOps->enmType == RTVFSOBJTYPE_FS_STREAM)
+ {
+ rtVfsObjRetainVoid(pThis, "RTVfsObjToFsStream");
+ return RT_FROM_MEMBER(pThis, RTVFSFSSTREAMINTERNAL, Base);
+ }
+ }
+ return NIL_RTVFSFSSTREAM;
+}
+
+
+RTDECL(RTVFSDIR) RTVfsObjToDir(RTVFSOBJ hVfsObj)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ if (pThis != NIL_RTVFSOBJ)
+ {
+ AssertPtrReturn(pThis, NIL_RTVFSDIR);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSDIR);
+
+ if (pThis->pOps->enmType == RTVFSOBJTYPE_DIR)
+ {
+ rtVfsObjRetainVoid(pThis, "RTVfsObjToDir");
+ return RT_FROM_MEMBER(pThis, RTVFSDIRINTERNAL, Base);
+ }
+ }
+ return NIL_RTVFSDIR;
+}
+
+
+RTDECL(RTVFSIOSTREAM) RTVfsObjToIoStream(RTVFSOBJ hVfsObj)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ if (pThis != NIL_RTVFSOBJ)
+ {
+ AssertPtrReturn(pThis, NIL_RTVFSIOSTREAM);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSIOSTREAM);
+
+ if ( pThis->pOps->enmType == RTVFSOBJTYPE_IO_STREAM
+ || pThis->pOps->enmType == RTVFSOBJTYPE_FILE)
+ {
+ rtVfsObjRetainVoid(pThis, "RTVfsObjToIoStream");
+ return RT_FROM_MEMBER(pThis, RTVFSIOSTREAMINTERNAL, Base);
+ }
+ }
+ return NIL_RTVFSIOSTREAM;
+}
+
+
+RTDECL(RTVFSFILE) RTVfsObjToFile(RTVFSOBJ hVfsObj)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ if (pThis != NIL_RTVFSOBJ)
+ {
+ AssertPtrReturn(pThis, NIL_RTVFSFILE);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSFILE);
+
+ if (pThis->pOps->enmType == RTVFSOBJTYPE_FILE)
+ {
+ rtVfsObjRetainVoid(pThis, "RTVfsObjToFile");
+ return RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream.Base);
+ }
+ }
+ return NIL_RTVFSFILE;
+}
+
+
+RTDECL(RTVFSSYMLINK) RTVfsObjToSymlink(RTVFSOBJ hVfsObj)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ if (pThis != NIL_RTVFSOBJ)
+ {
+ AssertPtrReturn(pThis, NIL_RTVFSSYMLINK);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSSYMLINK);
+
+ if (pThis->pOps->enmType == RTVFSOBJTYPE_SYMLINK)
+ {
+ rtVfsObjRetainVoid(pThis, "RTVfsObjToSymlink");
+ return RT_FROM_MEMBER(pThis, RTVFSSYMLINKINTERNAL, Base);
+ }
+ }
+ return NIL_RTVFSSYMLINK;
+}
+
+
+RTDECL(RTVFSOBJ) RTVfsObjFromVfs(RTVFS hVfs)
+{
+ if (hVfs != NIL_RTVFS)
+ {
+ RTVFSOBJINTERNAL *pThis = &hVfs->Base;
+ AssertPtrReturn(pThis, NIL_RTVFSOBJ);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ);
+
+ rtVfsObjRetainVoid(pThis, "RTVfsObjFromVfs");
+ LogFlow(("RTVfsObjFromVfs(%p) -> %p\n", hVfs, pThis));
+ return pThis;
+ }
+ return NIL_RTVFSOBJ;
+}
+
+
+RTDECL(RTVFSOBJ) RTVfsObjFromFsStream(RTVFSFSSTREAM hVfsFss)
+{
+ if (hVfsFss != NIL_RTVFSFSSTREAM)
+ {
+ RTVFSOBJINTERNAL *pThis = &hVfsFss->Base;
+ AssertPtrReturn(pThis, NIL_RTVFSOBJ);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ);
+
+ rtVfsObjRetainVoid(pThis, "RTVfsObjFromFsStream");
+ return pThis;
+ }
+ return NIL_RTVFSOBJ;
+}
+
+
+RTDECL(RTVFSOBJ) RTVfsObjFromDir(RTVFSDIR hVfsDir)
+{
+ if (hVfsDir != NIL_RTVFSDIR)
+ {
+ RTVFSOBJINTERNAL *pThis = &hVfsDir->Base;
+ AssertPtrReturn(pThis, NIL_RTVFSOBJ);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ);
+
+ rtVfsObjRetainVoid(pThis, "RTVfsObjFromDir");
+ return pThis;
+ }
+ return NIL_RTVFSOBJ;
+}
+
+
+RTDECL(RTVFSOBJ) RTVfsObjFromIoStream(RTVFSIOSTREAM hVfsIos)
+{
+ if (hVfsIos != NIL_RTVFSIOSTREAM)
+ {
+ RTVFSOBJINTERNAL *pThis = &hVfsIos->Base;
+ AssertPtrReturn(pThis, NIL_RTVFSOBJ);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ);
+
+ rtVfsObjRetainVoid(pThis, "RTVfsObjFromIoStream");
+ return pThis;
+ }
+ return NIL_RTVFSOBJ;
+}
+
+
+RTDECL(RTVFSOBJ) RTVfsObjFromFile(RTVFSFILE hVfsFile)
+{
+ if (hVfsFile != NIL_RTVFSFILE)
+ {
+ RTVFSOBJINTERNAL *pThis = &hVfsFile->Stream.Base;
+ AssertPtrReturn(pThis, NIL_RTVFSOBJ);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ);
+
+ rtVfsObjRetainVoid(pThis, "RTVfsObjFromFile");
+ return pThis;
+ }
+ return NIL_RTVFSOBJ;
+}
+
+
+RTDECL(RTVFSOBJ) RTVfsObjFromSymlink(RTVFSSYMLINK hVfsSym)
+{
+ if (hVfsSym != NIL_RTVFSSYMLINK)
+ {
+ RTVFSOBJINTERNAL *pThis = &hVfsSym->Base;
+ AssertPtrReturn(pThis, NIL_RTVFSOBJ);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, NIL_RTVFSOBJ);
+
+ rtVfsObjRetainVoid(pThis, "RTVfsObjFromSymlink");
+ return pThis;
+ }
+ return NIL_RTVFSOBJ;
+}
+
+
+RTDECL(int) RTVfsObjOpen(RTVFS hVfs, const char *pszPath, uint64_t fFileOpen, uint32_t fObjFlags, PRTVFSOBJ phVfsObj)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSINTERNAL *pThis = hVfs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(phVfsObj, VERR_INVALID_POINTER);
+
+ int rc = rtFileRecalcAndValidateFlags(&fFileOpen);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertMsgReturn( RTPATH_F_IS_VALID(fObjFlags, RTVFSOBJ_F_VALID_MASK)
+ && (fObjFlags & RTVFSOBJ_F_CREATE_MASK) <= RTVFSOBJ_F_CREATE_DIRECTORY,
+ ("fObjFlags=%#x\n", fObjFlags),
+ VERR_INVALID_FLAGS);
+ /*
+ * Parse the path, assume current directory is root since we've got no
+ * caller context here.
+ */
+ PRTVFSPARSEDPATH pPath;
+ rc = RTVfsParsePathA(pszPath, "/", &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tranverse the path, resolving the parent node.
+ * We'll do the symbolic link checking here with help of pfnOpen.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ rc = rtVfsTraverseToParent(pThis, pPath, (fObjFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+
+ /*
+ * Do the opening. Loop if we need to follow symbolic links.
+ */
+ for (uint32_t cLoops = 1; ; cLoops++)
+ {
+ /* If we end with a directory slash, adjust open flags. */
+ if (pPath->fDirSlash)
+ {
+ fObjFlags &= ~RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_OPEN_DIRECTORY;
+ if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_DIRECTORY)
+ fObjFlags = (fObjFlags & ~RTVFSOBJ_F_CREATE_MASK) | RTVFSOBJ_F_CREATE_NOTHING;
+ }
+ if (fObjFlags & RTPATH_F_FOLLOW_LINK)
+ fObjFlags |= RTVFSOBJ_F_OPEN_SYMLINK;
+
+ /* Open it. */
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+ RTVFSOBJ hVfsObj;
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fFileOpen, fObjFlags, &hVfsObj);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* We're done if we don't follow links or this wasn't a link. */
+ if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK)
+ || RTVfsObjGetType(*phVfsObj) != RTVFSOBJTYPE_SYMLINK)
+ {
+ *phVfsObj = hVfsObj;
+ break;
+ }
+
+ /* Follow symbolic link. */
+ if (cLoops < RTVFS_MAX_LINKS)
+ rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK);
+ else
+ rc = VERR_TOO_MANY_SYMLINKS;
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsObjQueryInfo(RTVFSOBJ hVfsObj, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE);
+
+ RTVfsLockAcquireRead(pThis->hLock);
+ int rc = pThis->pOps->pfnQueryInfo(pThis->pvThis, pObjInfo, enmAddAttr);
+ RTVfsLockReleaseRead(pThis->hLock);
+ return rc;
+}
+
+
+/**
+ * Gets the RTVFSOBJSETOPS for the given base object.
+ *
+ * @returns Pointer to the vtable if supported by the type, otherwise NULL.
+ * @param pThis The base object.
+ */
+static PCRTVFSOBJSETOPS rtVfsObjGetSetOps(RTVFSOBJINTERNAL *pThis)
+{
+ switch (pThis->pOps->enmType)
+ {
+ case RTVFSOBJTYPE_DIR:
+ return &RT_FROM_MEMBER(pThis, RTVFSDIRINTERNAL, Base)->pOps->ObjSet;
+ case RTVFSOBJTYPE_FILE:
+ return &RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream.Base)->pOps->ObjSet;
+ case RTVFSOBJTYPE_SYMLINK:
+ return &RT_FROM_MEMBER(pThis, RTVFSSYMLINKINTERNAL, Base)->pOps->ObjSet;
+ default:
+ return NULL;
+ }
+}
+
+
+RTDECL(int) RTVfsObjSetMode(RTVFSOBJ hVfsObj, RTFMODE fMode, RTFMODE fMask)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE);
+
+ fMode = rtFsModeNormalize(fMode, NULL, 0, 0);
+ if (!rtFsModeIsValid(fMode))
+ return VERR_INVALID_PARAMETER;
+
+ PCRTVFSOBJSETOPS pObjSetOps = rtVfsObjGetSetOps(pThis);
+ AssertReturn(pObjSetOps, VERR_INVALID_FUNCTION);
+
+ int rc;
+ if (pObjSetOps->pfnSetMode)
+ {
+ RTVfsLockAcquireWrite(pThis->hLock);
+ rc = pObjSetOps->pfnSetMode(pThis->pvThis, fMode, fMask);
+ RTVfsLockReleaseWrite(pThis->hLock);
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+ return rc;
+}
+
+
+RTDECL(int) RTVfsObjSetTimes(RTVFSOBJ hVfsObj, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE);
+
+ AssertPtrNullReturn(pAccessTime, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pModificationTime, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pChangeTime, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pBirthTime, VERR_INVALID_POINTER);
+
+ PCRTVFSOBJSETOPS pObjSetOps = rtVfsObjGetSetOps(pThis);
+ AssertReturn(pObjSetOps, VERR_INVALID_FUNCTION);
+
+ int rc;
+ if (pObjSetOps->pfnSetTimes)
+ {
+ RTVfsLockAcquireWrite(pThis->hLock);
+ rc = pObjSetOps->pfnSetTimes(pThis->pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ RTVfsLockReleaseWrite(pThis->hLock);
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+ return rc;
+}
+
+
+RTDECL(int) RTVfsObjSetOwner(RTVFSOBJ hVfsObj, RTUID uid, RTGID gid)
+{
+ RTVFSOBJINTERNAL *pThis = hVfsObj;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE);
+
+ PCRTVFSOBJSETOPS pObjSetOps = rtVfsObjGetSetOps(pThis);
+ AssertReturn(pObjSetOps, VERR_INVALID_FUNCTION);
+
+ int rc;
+ if (pObjSetOps->pfnSetOwner)
+ {
+ RTVfsLockAcquireWrite(pThis->hLock);
+ rc = pObjSetOps->pfnSetOwner(pThis->pvThis, uid, gid);
+ RTVfsLockReleaseWrite(pThis->hLock);
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+ return rc;
+}
+
+
+/*
+ *
+ * U T I L U T I L U T I L
+ * U T I L U T I L U T I L
+ * U T I L U T I L U T I L
+ *
+ */
+
+
+RTDECL(int) RTVfsParsePathAppend(PRTVFSPARSEDPATH pPath, const char *pszPath, uint16_t *piRestartComp)
+{
+ AssertReturn(*pszPath != '/' && *pszPath != '\\', VERR_INTERNAL_ERROR_4);
+
+ /* In case *piRestartComp was set higher than the number of components
+ before making the call to this function. */
+ if (piRestartComp && *piRestartComp + 1 >= pPath->cComponents)
+ *piRestartComp = pPath->cComponents > 0 ? pPath->cComponents - 1 : 0;
+
+/** @todo The '..' handling doesn't really work wrt to symbolic links in the
+ * path. */
+
+ /*
+ * Append a slash to the destination path if necessary.
+ */
+ char * const pszDst = pPath->szPath;
+ size_t offDst = pPath->cch;
+ if (pPath->cComponents > 0)
+ {
+ pszDst[offDst++] = '/';
+ if (offDst >= RTVFSPARSEDPATH_MAX)
+ return VERR_FILENAME_TOO_LONG;
+ }
+ if (pPath->fAbsolute)
+ Assert(pszDst[offDst - 1] == '/' && pszDst[0] == '/');
+ else
+ Assert(offDst == 0 || (pszDst[0] != '/' && pszDst[offDst - 1] == '/'));
+
+ /*
+ * Parse and append the relative path.
+ */
+ const char *pszSrc = pszPath;
+ pPath->fDirSlash = false;
+ for (;;)
+ {
+ /* Copy until we encounter the next slash. */
+ pPath->aoffComponents[pPath->cComponents++] = (uint16_t)offDst;
+ for (;;)
+ {
+ char ch = *pszSrc++;
+ if ( ch != '/'
+ && ch != '\\'
+ && ch != '\0')
+ {
+ pszDst[offDst++] = ch;
+ if (offDst < RTVFSPARSEDPATH_MAX)
+ { /* likely */ }
+ else
+ return VERR_FILENAME_TOO_LONG;
+ }
+ else
+ {
+ /* Deal with dot components before we processes the slash/end. */
+ if (pszDst[offDst - 1] == '.')
+ {
+ if ( offDst == 1
+ || pszDst[offDst - 2] == '/')
+ {
+ pPath->cComponents--;
+ offDst = pPath->aoffComponents[pPath->cComponents];
+ }
+ else if ( offDst > 3
+ && pszDst[offDst - 2] == '.'
+ && pszDst[offDst - 3] == '/')
+ {
+ if ( pPath->fAbsolute
+ || offDst < 5
+ || pszDst[offDst - 4] != '.'
+ || pszDst[offDst - 5] != '.'
+ || (offDst >= 6 && pszDst[offDst - 6] != '/') )
+ {
+ pPath->cComponents -= pPath->cComponents > 1 ? 2 : 1;
+ offDst = pPath->aoffComponents[pPath->cComponents];
+ if (piRestartComp && *piRestartComp + 1 >= pPath->cComponents)
+ *piRestartComp = pPath->cComponents > 0 ? pPath->cComponents - 1 : 0;
+ }
+ }
+ }
+
+ if (ch != '\0')
+ {
+ /* Skip unnecessary slashes and check for end of path. */
+ while ((ch = *pszSrc) == '/' || ch == '\\')
+ pszSrc++;
+
+ if (ch == '\0')
+ pPath->fDirSlash = true;
+ }
+
+ if (ch == '\0')
+ {
+ /* Drop trailing slash unless it's the root slash. */
+ if ( offDst > 0
+ && pszDst[offDst - 1] == '/'
+ && ( !pPath->fAbsolute
+ || offDst > 1))
+ offDst--;
+
+ /* Terminate the string and enter its length. */
+ pszDst[offDst] = '\0';
+ pszDst[offDst + 1] = '\0'; /* for aoffComponents[pPath->cComponents] */
+ pPath->cch = (uint16_t)offDst;
+ pPath->aoffComponents[pPath->cComponents] = (uint16_t)(offDst + 1);
+ return VINF_SUCCESS;
+ }
+
+ /* Append component separator before continuing with the next component. */
+ if (offDst > 0 && pszDst[offDst - 1] != '/')
+ pszDst[offDst++] = '/';
+ if (offDst >= RTVFSPARSEDPATH_MAX)
+ return VERR_FILENAME_TOO_LONG;
+ break;
+ }
+ }
+ }
+}
+
+
+/** @todo Replace RTVfsParsePath with RTPathParse and friends? */
+RTDECL(int) RTVfsParsePath(PRTVFSPARSEDPATH pPath, const char *pszPath, const char *pszCwd)
+{
+ if (*pszPath != '/' && *pszPath != '\\')
+ {
+ if (pszCwd)
+ {
+ /*
+ * Relative with a CWD.
+ */
+ int rc = RTVfsParsePath(pPath, pszCwd, NULL /*crash if pszCwd is not absolute*/);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ /*
+ * Relative.
+ */
+ pPath->cch = 0;
+ pPath->cComponents = 0;
+ pPath->fDirSlash = false;
+ pPath->fAbsolute = false;
+ pPath->aoffComponents[0] = 0;
+ pPath->aoffComponents[1] = 1;
+ pPath->szPath[0] = '\0';
+ pPath->szPath[1] = '\0';
+ }
+ }
+ else
+ {
+ /*
+ * Make pszPath relative, i.e. set up pPath for the root and skip
+ * leading slashes in pszPath before appending it.
+ */
+ pPath->cch = 1;
+ pPath->cComponents = 0;
+ pPath->fDirSlash = false;
+ pPath->fAbsolute = true;
+ pPath->aoffComponents[0] = 1;
+ pPath->aoffComponents[1] = 2;
+ pPath->szPath[0] = '/';
+ pPath->szPath[1] = '\0';
+ pPath->szPath[2] = '\0';
+ while (pszPath[0] == '/' || pszPath[0] == '\\')
+ pszPath++;
+ if (!pszPath[0])
+ return VINF_SUCCESS;
+ }
+ return RTVfsParsePathAppend(pPath, pszPath, NULL);
+}
+
+
+
+RTDECL(int) RTVfsParsePathA(const char *pszPath, const char *pszCwd, PRTVFSPARSEDPATH *ppPath)
+{
+ /*
+ * Allocate the output buffer and hand the problem to rtVfsParsePath.
+ */
+ int rc;
+ PRTVFSPARSEDPATH pPath = (PRTVFSPARSEDPATH)RTMemTmpAlloc(sizeof(RTVFSPARSEDPATH));
+ if (pPath)
+ {
+ rc = RTVfsParsePath(pPath, pszPath, pszCwd);
+ if (RT_FAILURE(rc))
+ {
+ RTMemTmpFree(pPath);
+ pPath = NULL;
+ }
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ *ppPath = pPath; /* always set it */
+ return rc;
+}
+
+
+RTDECL(void) RTVfsParsePathFree(PRTVFSPARSEDPATH pPath)
+{
+ if (pPath)
+ {
+ pPath->cch = UINT16_MAX;
+ pPath->cComponents = UINT16_MAX;
+ pPath->aoffComponents[0] = UINT16_MAX;
+ pPath->aoffComponents[1] = UINT16_MAX;
+ RTMemTmpFree(pPath);
+ }
+}
+
+
+/**
+ * Handles a symbolic link, adding it to
+ *
+ * @returns IPRT status code.
+ * @param ppCurDir The current directory variable. We change it if
+ * the symbolic links is absolute.
+ * @param pPath The parsed path to update.
+ * @param iPathComponent The current path component.
+ * @param hSymlink The symbolic link to process.
+ */
+static int rtVfsTraverseHandleSymlink(RTVFSDIRINTERNAL **ppCurDir, PRTVFSPARSEDPATH pPath,
+ uint16_t iPathComponent, RTVFSSYMLINK hSymlink)
+{
+ /*
+ * Read the link and append the trailing path to it.
+ */
+ char szPath[RTPATH_MAX];
+ int rc = RTVfsSymlinkRead(hSymlink, szPath, sizeof(szPath) - 1);
+ if (RT_SUCCESS(rc))
+ {
+ szPath[sizeof(szPath) - 1] = '\0';
+ if (iPathComponent + 1 < pPath->cComponents)
+ rc = RTPathAppend(szPath, sizeof(szPath), &pPath->szPath[pPath->aoffComponents[iPathComponent + 1]]);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Special hack help vfsstddir.cpp deal with symbolic links.
+ */
+ RTVFSDIRINTERNAL *pCurDir = *ppCurDir;
+ char *pszPath = szPath;
+ if (pCurDir->pOps->pfnFollowAbsoluteSymlink)
+ {
+ size_t cchRoot = rtPathRootSpecLen(szPath);
+ if (cchRoot > 0)
+ {
+ pszPath = &szPath[cchRoot];
+ char const chSaved = *pszPath;
+ *pszPath = '\0';
+ RTVFSDIRINTERNAL *pVfsRootDir;
+ RTVfsLockAcquireWrite(pCurDir->Base.hLock);
+ rc = pCurDir->pOps->pfnFollowAbsoluteSymlink(pCurDir, szPath, &pVfsRootDir);
+ RTVfsLockAcquireWrite(pCurDir->Base.hLock);
+ *pszPath = chSaved;
+ if (RT_SUCCESS(rc))
+ {
+ RTVfsDirRelease(pCurDir);
+ *ppCurDir = pCurDir = pVfsRootDir;
+ }
+ else if (rc == VERR_PATH_IS_RELATIVE)
+ pszPath = szPath;
+ else
+ return rc;
+ }
+ }
+
+ rc = RTVfsParsePath(pPath, pszPath, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Deal with absolute references in a VFS setup.
+ * Note! The current approach only correctly handles this on root volumes.
+ */
+ if ( pPath->fAbsolute
+ && pCurDir->Base.hVfs != NIL_RTVFS) /** @todo This needs fixing once we implement mount points. */
+ {
+ RTVFSINTERNAL *pVfs = pCurDir->Base.hVfs;
+ RTVFSDIRINTERNAL *pVfsRootDir;
+ RTVfsLockAcquireRead(pVfs->Base.hLock);
+ rc = pVfs->pOps->pfnOpenRoot(pVfs->Base.pvThis, &pVfsRootDir);
+ RTVfsLockReleaseRead(pVfs->Base.hLock);
+ if (RT_SUCCESS(rc))
+ {
+ RTVfsDirRelease(pCurDir);
+ *ppCurDir = pCurDir = pVfsRootDir;
+ }
+ else
+ return rc;
+ }
+ }
+ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ rc = VERR_FILENAME_TOO_LONG;
+ return rc == VERR_BUFFER_OVERFLOW ? VERR_FILENAME_TOO_LONG : rc;
+}
+
+
+/**
+ * Internal worker for various open functions as well as RTVfsTraverseToParent.
+ *
+ *
+ * @returns IPRT status code.
+ * @param pThis The VFS.
+ * @param pPath The parsed path. This may be changed as symbolic
+ * links are processed during the path traversal. If
+ * it contains zero components, a dummy component is
+ * added to assist the caller.
+ * @param fFlags RTPATH_F_XXX.
+ * @param ppVfsParentDir Where to return the parent directory handle
+ * (referenced).
+ */
+static int rtVfsDirTraverseToParent(RTVFSDIRINTERNAL *pThis, PRTVFSPARSEDPATH pPath, uint32_t fFlags,
+ RTVFSDIRINTERNAL **ppVfsParentDir)
+{
+ /*
+ * Assert sanity.
+ */
+ AssertPtr(pThis);
+ Assert(pThis->uMagic == RTVFSDIR_MAGIC);
+ Assert(pThis->Base.cRefs > 0);
+ AssertPtr(pPath);
+ AssertPtr(ppVfsParentDir);
+ *ppVfsParentDir = NULL;
+ Assert(RTPATH_F_IS_VALID(fFlags, 0));
+
+ /*
+ * Start with the pThis directory.
+ */
+ if (RTVfsDirRetain(pThis) == UINT32_MAX)
+ return VERR_INVALID_HANDLE;
+ RTVFSDIRINTERNAL *pCurDir = pThis;
+
+ /*
+ * Special case for traversing zero components.
+ * We fake up a "./" in the pPath to help the caller along.
+ */
+ if (pPath->cComponents == 0)
+ {
+ pPath->fDirSlash = true;
+ pPath->szPath[0] = '.';
+ pPath->szPath[1] = '\0';
+ pPath->szPath[2] = '\0';
+ pPath->cch = 1;
+ pPath->cComponents = 1;
+ pPath->aoffComponents[0] = 0;
+ pPath->aoffComponents[1] = 1;
+ pPath->aoffComponents[2] = 1;
+
+ *ppVfsParentDir = pCurDir;
+ return VINF_SUCCESS;
+ }
+
+
+ /*
+ * The traversal loop.
+ */
+ int rc = VINF_SUCCESS;
+ unsigned cLinks = 0;
+ uint16_t iComponent = 0;
+ for (;;)
+ {
+ /*
+ * Are we done yet?
+ */
+ bool fFinal = iComponent + 1 >= pPath->cComponents;
+ if (fFinal && (fFlags & RTPATH_F_ON_LINK))
+ {
+ *ppVfsParentDir = pCurDir;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Try open the next entry.
+ */
+ const char *pszEntry = &pPath->szPath[pPath->aoffComponents[iComponent]];
+ char *pszEntryEnd = &pPath->szPath[pPath->aoffComponents[iComponent + 1] - 1];
+ *pszEntryEnd = '\0';
+ RTVFSDIR hDir = NIL_RTVFSDIR;
+ RTVFSSYMLINK hSymlink = NIL_RTVFSSYMLINK;
+ RTVFS hVfsMnt = NIL_RTVFS;
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ if (fFinal)
+ {
+ RTVfsLockAcquireRead(pCurDir->Base.hLock);
+ rc = pCurDir->pOps->pfnOpen(pCurDir->Base.pvThis, pszEntry,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_CREATE_NOTHING
+ | RTVFSOBJ_F_TRAVERSAL | RTPATH_F_ON_LINK,
+ &hVfsObj);
+ RTVfsLockReleaseRead(pCurDir->Base.hLock);
+ *pszEntryEnd = '\0';
+ if (RT_FAILURE(rc))
+ {
+ if ( rc == VERR_PATH_NOT_FOUND
+ || rc == VERR_FILE_NOT_FOUND
+ || rc == VERR_IS_A_DIRECTORY
+ || rc == VERR_IS_A_FILE
+ || rc == VERR_IS_A_FIFO
+ || rc == VERR_IS_A_SOCKET
+ || rc == VERR_IS_A_CHAR_DEVICE
+ || rc == VERR_IS_A_BLOCK_DEVICE
+ || rc == VERR_NOT_SYMLINK)
+ {
+ *ppVfsParentDir = pCurDir;
+ return VINF_SUCCESS;
+ }
+ break;
+ }
+ hSymlink = RTVfsObjToSymlink(hVfsObj);
+ Assert(hSymlink != NIL_RTVFSSYMLINK);
+ }
+ else
+ {
+ RTVfsLockAcquireRead(pCurDir->Base.hLock);
+ rc = pCurDir->pOps->pfnOpen(pCurDir->Base.pvThis, pszEntry,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ RTVFSOBJ_F_OPEN_DIRECTORY | RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_OPEN_MOUNT
+ | RTVFSOBJ_F_CREATE_NOTHING | RTVFSOBJ_F_TRAVERSAL | RTPATH_F_ON_LINK,
+ &hVfsObj);
+ RTVfsLockReleaseRead(pCurDir->Base.hLock);
+ *pszEntryEnd = '/';
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_FILE_NOT_FOUND)
+ rc = VERR_PATH_NOT_FOUND;
+ break;
+ }
+ hDir = RTVfsObjToDir(hVfsObj);
+ hSymlink = RTVfsObjToSymlink(hVfsObj);
+ hVfsMnt = RTVfsObjToVfs(hVfsObj);
+ }
+ Assert( (hDir != NIL_RTVFSDIR && hSymlink == NIL_RTVFSSYMLINK && hVfsMnt == NIL_RTVFS)
+ || (hDir == NIL_RTVFSDIR && hSymlink != NIL_RTVFSSYMLINK && hVfsMnt == NIL_RTVFS)
+ || (hDir == NIL_RTVFSDIR && hSymlink == NIL_RTVFSSYMLINK && hVfsMnt != NIL_RTVFS));
+ RTVfsObjRelease(hVfsObj);
+
+ if (hDir != NIL_RTVFSDIR)
+ {
+ /*
+ * Directory - advance down the path.
+ */
+ AssertPtr(hDir);
+ Assert(hDir->uMagic == RTVFSDIR_MAGIC);
+ RTVfsDirRelease(pCurDir);
+ pCurDir = hDir;
+ iComponent++;
+ }
+ else if (hSymlink != NIL_RTVFSSYMLINK)
+ {
+ /*
+ * Symbolic link - deal with it and retry the current component.
+ */
+ AssertPtr(hSymlink);
+ Assert(hSymlink->uMagic == RTVFSSYMLINK_MAGIC);
+ if (fFlags & RTPATH_F_NO_SYMLINKS)
+ {
+ rc = VERR_SYMLINK_NOT_ALLOWED;
+ break;
+ }
+ cLinks++;
+ if (cLinks >= RTVFS_MAX_LINKS)
+ {
+ rc = VERR_TOO_MANY_SYMLINKS;
+ break;
+ }
+ rc = rtVfsTraverseHandleSymlink(&pCurDir, pPath, iComponent, hSymlink);
+ if (RT_FAILURE(rc))
+ break;
+ iComponent = 0;
+ }
+ else
+ {
+ /*
+ * Mount point - deal with it and retry the current component.
+ */
+ RTVfsDirRelease(pCurDir);
+ RTVfsLockAcquireRead(hVfsMnt->Base.hLock);
+ rc = hVfsMnt->pOps->pfnOpenRoot(hVfsMnt->Base.pvThis, &pCurDir);
+ RTVfsLockReleaseRead(hVfsMnt->Base.hLock);
+ if (RT_FAILURE(rc))
+ {
+ pCurDir = NULL;
+ break;
+ }
+ iComponent = 0;
+ /** @todo union mounts. */
+ }
+ }
+
+ if (pCurDir)
+ RTVfsDirRelease(pCurDir);
+
+ return rc;
+}
+
+
+/**
+ * Internal worker for various open functions as well as RTVfsTraverseToParent.
+ *
+ * @returns IPRT status code.
+ * @param pThis The VFS.
+ * @param pPath The parsed path. This may be changed as symbolic
+ * links are processed during the path traversal.
+ * @param fFlags RTPATH_F_XXX.
+ * @param ppVfsParentDir Where to return the parent directory handle
+ * (referenced).
+ */
+static int rtVfsTraverseToParent(RTVFSINTERNAL *pThis, PRTVFSPARSEDPATH pPath, uint32_t fFlags, RTVFSDIRINTERNAL **ppVfsParentDir)
+{
+ /*
+ * Assert sanity.
+ */
+ AssertPtr(pThis);
+ Assert(pThis->uMagic == RTVFS_MAGIC);
+ Assert(pThis->Base.cRefs > 0);
+ AssertPtr(pPath);
+ AssertPtr(ppVfsParentDir);
+ *ppVfsParentDir = NULL;
+ Assert(RTPATH_F_IS_VALID(fFlags, 0));
+
+ /*
+ * Open the root directory and join paths with the directory traversal.
+ */
+ /** @todo Union mounts, traversal optimization methods, races, ++ */
+ RTVFSDIRINTERNAL *pRootDir;
+ RTVfsLockAcquireRead(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnOpenRoot(pThis->Base.pvThis, &pRootDir);
+ RTVfsLockReleaseRead(pThis->Base.hLock);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtVfsDirTraverseToParent(pRootDir, pPath, fFlags, ppVfsParentDir);
+ RTVfsDirRelease(pRootDir);
+ }
+ return rc;
+}
+
+
+
+/**
+ * Follows a symbolic link object to the next parent directory.
+ *
+ * @returns IPRT status code
+ * @param ppVfsParentDir Pointer to the parent directory of @a hVfsObj on
+ * input, the parent directory of the link target on
+ * return.
+ * @param hVfsObj Symbolic link object handle.
+ * @param pPath Path buffer to use parse the symbolic link target.
+ * @param fFlags See rtVfsDirTraverseToParent.
+ */
+static int rtVfsDirFollowSymlinkObjToParent(RTVFSDIRINTERNAL **ppVfsParentDir, RTVFSOBJ hVfsObj,
+ PRTVFSPARSEDPATH pPath, uint32_t fFlags)
+{
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, VERR_INTERNAL_ERROR_3);
+
+ int rc = rtVfsTraverseHandleSymlink(ppVfsParentDir, pPath, pPath->cComponents, hVfsSymlink);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIRINTERNAL *pVfsStartDir = *ppVfsParentDir;
+ rc = rtVfsDirTraverseToParent(pVfsStartDir, pPath, fFlags, ppVfsParentDir);
+ RTVfsDirRelease(pVfsStartDir);
+ }
+
+ RTVfsSymlinkRelease(hVfsSymlink);
+ return rc;
+}
+
+
+
+RTDECL(int) RTVfsUtilDummyPollOne(uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents)
+{
+ NOREF(fEvents);
+ int rc;
+ if (fIntr)
+ rc = RTThreadSleep(cMillies);
+ else
+ {
+ uint64_t uMsStart = RTTimeMilliTS();
+ do
+ rc = RTThreadSleep(cMillies);
+ while ( rc == VERR_INTERRUPTED
+ && !fIntr
+ && RTTimeMilliTS() - uMsStart < cMillies);
+ if (rc == VERR_INTERRUPTED)
+ rc = VERR_TIMEOUT;
+ }
+
+ *pfRetEvents = 0;
+ return rc;
+}
+
+
+RTDECL(int) RTVfsUtilPumpIoStreams(RTVFSIOSTREAM hVfsIosSrc, RTVFSIOSTREAM hVfsIosDst, size_t cbBufHint)
+{
+ /*
+ * Allocate a temporary buffer.
+ */
+ size_t cbBuf = cbBufHint;
+ if (!cbBuf)
+ cbBuf = _64K;
+ else if (cbBuf < _4K)
+ cbBuf = _4K;
+ else if (cbBuf > _1M)
+ cbBuf = _1M;
+
+ void *pvBuf = RTMemTmpAlloc(cbBuf);
+ if (!pvBuf)
+ {
+ cbBuf = _4K;
+ pvBuf = RTMemTmpAlloc(cbBuf);
+ if (!pvBuf)
+ return VERR_NO_TMP_MEMORY;
+ }
+
+ /*
+ * Pump loop.
+ */
+ int rc;
+ for (;;)
+ {
+ size_t cbRead;
+ rc = RTVfsIoStrmRead(hVfsIosSrc, pvBuf, cbBuf, true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+ if (rc == VINF_EOF && cbRead == 0)
+ break;
+
+ rc = RTVfsIoStrmWrite(hVfsIosDst, pvBuf, cbRead, true /*fBlocking*/, NULL /*cbWritten*/);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ RTMemTmpFree(pvBuf);
+
+ /*
+ * Flush the destination stream on success to make sure we've caught
+ * errors caused by buffering delays.
+ */
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmFlush(hVfsIosDst);
+
+ return rc;
+}
+
+
+
+
+
+/*
+ * F I L E S Y S T E M R O O T
+ * F I L E S Y S T E M R O O T
+ * F I L E S Y S T E M R O O T
+ */
+
+
+RTDECL(int) RTVfsNew(PCRTVFSOPS pVfsOps, size_t cbInstance, RTVFS hVfs, RTVFSLOCK hLock,
+ PRTVFS phVfs, void **ppvInstance)
+{
+ /*
+ * Validate the input, be extra strict in strict builds.
+ */
+ AssertPtr(pVfsOps);
+ AssertReturn(pVfsOps->uVersion == RTVFSOPS_VERSION, VERR_VERSION_MISMATCH);
+ AssertReturn(pVfsOps->uEndMarker == RTVFSOPS_VERSION, VERR_VERSION_MISMATCH);
+ RTVFSOBJ_ASSERT_OPS(&pVfsOps->Obj, RTVFSOBJTYPE_VFS);
+ Assert(cbInstance > 0);
+ AssertPtr(ppvInstance);
+ AssertPtr(phVfs);
+
+ /*
+ * Allocate the handle + instance data.
+ */
+ size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSINTERNAL), RTVFS_INST_ALIGNMENT)
+ + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT);
+ RTVFSINTERNAL *pThis = (RTVFSINTERNAL *)RTMemAllocZ(cbThis);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ int rc = rtVfsObjInitNewObject(&pThis->Base, &pVfsOps->Obj, hVfs, false /*fNoVfsRef*/, hLock,
+ (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT));
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ pThis->uMagic = RTVFS_MAGIC;
+ pThis->pOps = pVfsOps;
+
+ *phVfs = pThis;
+ *ppvInstance = pThis->Base.pvThis;
+
+ LogFlow(("RTVfsNew -> VINF_SUCCESS; hVfs=%p pvThis=%p\n", pThis, pThis->Base.pvThis));
+ return VINF_SUCCESS;
+}
+
+#ifdef DEBUG
+# undef RTVfsRetain
+#endif
+RTDECL(uint32_t) RTVfsRetain(RTVFS hVfs)
+{
+ RTVFSINTERNAL *pThis = hVfs;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, UINT32_MAX);
+ uint32_t cRefs = rtVfsObjRetain(&pThis->Base);
+ LogFlow(("RTVfsRetain(%p/%p) -> %d\n", pThis, pThis->Base.pvThis, cRefs));
+ return cRefs;
+}
+#ifdef DEBUG
+# define RTVfsRetain(hVfs) RTVfsRetainDebug(hVfs, RT_SRC_POS)
+#endif
+
+
+RTDECL(uint32_t) RTVfsRetainDebug(RTVFS hVfs, RT_SRC_POS_DECL)
+{
+ RTVFSINTERNAL *pThis = hVfs;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, UINT32_MAX);
+ RT_SRC_POS_NOREF();
+ return rtVfsObjRetainDebug(&pThis->Base, "RTVfsRetainDebug", RT_SRC_POS_ARGS);
+}
+
+
+RTDECL(uint32_t) RTVfsRelease(RTVFS hVfs)
+{
+ RTVFSINTERNAL *pThis = hVfs;
+ if (pThis == NIL_RTVFS)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, UINT32_MAX);
+#ifdef LOG_ENABLED
+ void *pvThis = pThis->Base.pvThis;
+#endif
+ uint32_t cRefs = rtVfsObjRelease(&pThis->Base);
+ Log(("RTVfsRelease(%p/%p) -> %d\n", pThis, pvThis, cRefs));
+ return cRefs;
+}
+
+
+RTDECL(int) RTVfsOpenRoot(RTVFS hVfs, PRTVFSDIR phDir)
+{
+ RTVFSINTERNAL *pThis = hVfs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phDir, VERR_INVALID_POINTER);
+ *phDir = NIL_RTVFSDIR;
+
+ if (!pThis->pOps->pfnOpenRoot)
+ return VERR_NOT_SUPPORTED;
+ RTVfsLockAcquireRead(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnOpenRoot(pThis->Base.pvThis, phDir);
+ RTVfsLockReleaseRead(pThis->Base.hLock);
+
+ return rc;
+}
+
+
+RTDECL(int) RTVfsQueryPathInfo(RTVFS hVfs, const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr, uint32_t fFlags)
+{
+ RTVFSINTERNAL *pThis = hVfs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(*pszPath, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(enmAddAttr >= RTFSOBJATTRADD_NOTHING && enmAddAttr <= RTFSOBJATTRADD_LAST, VERR_INVALID_PARAMETER);
+ AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
+
+ /*
+ * Parse the path, assume current directory is root since we've got no
+ * caller context here. Then traverse to the parent directory.
+ */
+ PRTVFSPARSEDPATH pPath;
+ int rc = RTVfsParsePathA(pszPath, "/", &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tranverse the path, resolving the parent node.
+ * We'll do the symbolic link checking here with help of pfnOpen/pfnQueryEntryInfo.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ rc = rtVfsTraverseToParent(pThis, pPath, (fFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the opening. Loop if we need to follow symbolic links.
+ */
+ uint32_t fObjFlags = RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_CREATE_NOTHING;
+ for (uint32_t cLoops = 1; ; cLoops++)
+ {
+ /* If we end with a directory slash, adjust open flags. */
+ if (pPath->fDirSlash)
+ {
+ fObjFlags &= ~RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_OPEN_DIRECTORY;
+ if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_DIRECTORY)
+ fObjFlags = (fObjFlags & ~RTVFSOBJ_F_CREATE_MASK) | RTVFSOBJ_F_CREATE_NOTHING;
+ }
+ if (fObjFlags & RTPATH_F_FOLLOW_LINK)
+ fObjFlags |= RTVFSOBJ_F_OPEN_SYMLINK;
+
+ /* Do the querying. If pfnQueryEntryInfo is available, we use it first,
+ falling back on pfnOpen in case of symbolic links that needs following. */
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+ if (pVfsParentDir->pOps->pfnQueryEntryInfo)
+ {
+ RTVfsLockAcquireRead(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnQueryEntryInfo(pVfsParentDir->Base.pvThis, pszEntryName, pObjInfo, enmAddAttr);
+ RTVfsLockReleaseRead(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+ if ( !RTFS_IS_SYMLINK(pObjInfo->Attr.fMode)
+ || !(fFlags & RTPATH_F_FOLLOW_LINK))
+ {
+ if ( (fObjFlags & RTVFSOBJ_F_OPEN_MASK) != RTVFSOBJ_F_OPEN_ANY
+ && !RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ rc = VERR_NOT_A_DIRECTORY;
+ break;
+ }
+ }
+
+ RTVFSOBJ hVfsObj;
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName,
+ RTFILE_O_ACCESS_ATTR_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ fObjFlags, &hVfsObj);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */
+ if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK)
+ || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK)
+ {
+ rc = RTVfsObjQueryInfo(hVfsObj, pObjInfo, enmAddAttr);
+ RTVfsObjRelease(hVfsObj);
+ break;
+ }
+
+ /* Follow symbolic link. */
+ if (cLoops < RTVFS_MAX_LINKS)
+ rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK);
+ else
+ rc = VERR_TOO_MANY_SYMLINKS;
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+}
+
+
+
+RTDECL(int) RTVfsQueryRangeState(RTVFS hVfs, uint64_t off, size_t cb, bool *pfUsed)
+{
+ RTVFSINTERNAL *pThis = hVfs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE);
+
+ if (!pThis->pOps->pfnQueryRangeState)
+ return VERR_NOT_SUPPORTED;
+ RTVfsLockAcquireRead(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnQueryRangeState(pThis->Base.pvThis, off, cb, pfUsed);
+ RTVfsLockReleaseRead(pThis->Base.hLock);
+
+ return rc;
+}
+
+
+RTDECL(int) RTVfsQueryLabel(RTVFS hVfs, bool fAlternative, char *pszLabel, size_t cbLabel, size_t *pcbActual)
+{
+ RTVFSINTERNAL *pThis = hVfs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE);
+
+ if (cbLabel > 0)
+ AssertPtrReturn(pszLabel, VERR_INVALID_POINTER);
+
+ int rc;
+ if (pThis->pOps->Obj.pfnQueryInfoEx)
+ {
+ size_t cbActualIgn;
+ if (!pcbActual)
+ pcbActual = &cbActualIgn;
+
+ RTVfsLockAcquireRead(pThis->Base.hLock);
+ rc = pThis->pOps->Obj.pfnQueryInfoEx(pThis->Base.pvThis, !fAlternative ? RTVFSQIEX_VOL_LABEL : RTVFSQIEX_VOL_LABEL_ALT,
+ pszLabel, cbLabel, pcbActual);
+ RTVfsLockReleaseRead(pThis->Base.hLock);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ return rc;
+}
+
+
+
+/*
+ *
+ * F I L E S Y S T E M S T R E A M
+ * F I L E S Y S T E M S T R E A M
+ * F I L E S Y S T E M S T R E A M
+ *
+ */
+
+
+RTDECL(int) RTVfsNewFsStream(PCRTVFSFSSTREAMOPS pFsStreamOps, size_t cbInstance, RTVFS hVfs, RTVFSLOCK hLock, uint32_t fAccess,
+ PRTVFSFSSTREAM phVfsFss, void **ppvInstance)
+{
+ /*
+ * Validate the input, be extra strict in strict builds.
+ */
+ AssertPtr(pFsStreamOps);
+ AssertReturn(pFsStreamOps->uVersion == RTVFSFSSTREAMOPS_VERSION, VERR_VERSION_MISMATCH);
+ AssertReturn(pFsStreamOps->uEndMarker == RTVFSFSSTREAMOPS_VERSION, VERR_VERSION_MISMATCH);
+ Assert(!pFsStreamOps->fReserved);
+ RTVFSOBJ_ASSERT_OPS(&pFsStreamOps->Obj, RTVFSOBJTYPE_FS_STREAM);
+ Assert((fAccess & (RTFILE_O_READ | RTFILE_O_WRITE)) == fAccess);
+ Assert(fAccess);
+ if (fAccess & RTFILE_O_READ)
+ AssertPtr(pFsStreamOps->pfnNext);
+ if (fAccess & RTFILE_O_WRITE)
+ {
+ AssertPtr(pFsStreamOps->pfnAdd);
+ AssertPtr(pFsStreamOps->pfnEnd);
+ }
+ Assert(cbInstance > 0);
+ AssertPtr(ppvInstance);
+ AssertPtr(phVfsFss);
+ RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE);
+
+ /*
+ * Allocate the handle + instance data.
+ */
+ size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSFSSTREAMINTERNAL), RTVFS_INST_ALIGNMENT)
+ + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT);
+ RTVFSFSSTREAMINTERNAL *pThis = (RTVFSFSSTREAMINTERNAL *)RTMemAllocZ(cbThis);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ int rc = rtVfsObjInitNewObject(&pThis->Base, &pFsStreamOps->Obj, hVfs, false /*fNoVfsRef*/, hLock,
+ (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT));
+
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ pThis->uMagic = RTVFSFSSTREAM_MAGIC;
+ pThis->pOps = pFsStreamOps;
+ pThis->fFlags = fAccess;
+ if (fAccess == RTFILE_O_READ)
+ pThis->fFlags |= RTFILE_O_OPEN | RTFILE_O_DENY_NONE;
+ else if (fAccess == RTFILE_O_WRITE)
+ pThis->fFlags |= RTFILE_O_CREATE | RTFILE_O_DENY_ALL;
+ else
+ pThis->fFlags |= RTFILE_O_OPEN | RTFILE_O_DENY_ALL;
+
+ *phVfsFss = pThis;
+ *ppvInstance = pThis->Base.pvThis;
+ return VINF_SUCCESS;
+}
+
+
+#ifdef DEBUG
+# undef RTVfsFsStrmRetain
+#endif
+RTDECL(uint32_t) RTVfsFsStrmRetain(RTVFSFSSTREAM hVfsFss)
+{
+ RTVFSFSSTREAMINTERNAL *pThis = hVfsFss;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, UINT32_MAX);
+ return rtVfsObjRetain(&pThis->Base);
+}
+#ifdef DEBUG
+# define RTVfsFsStrmRetain(hVfsFss) RTVfsFsStrmRetainDebug(hVfsFss, RT_SRC_POS)
+#endif
+
+
+RTDECL(uint32_t) RTVfsFsStrmRetainDebug(RTVFSFSSTREAM hVfsFss, RT_SRC_POS_DECL)
+{
+ RTVFSFSSTREAMINTERNAL *pThis = hVfsFss;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, UINT32_MAX);
+ return rtVfsObjRetainDebug(&pThis->Base, "RTVfsFsStrmRetain", RT_SRC_POS_ARGS);
+}
+
+
+RTDECL(uint32_t) RTVfsFsStrmRelease(RTVFSFSSTREAM hVfsFss)
+{
+ RTVFSFSSTREAMINTERNAL *pThis = hVfsFss;
+ if (pThis == NIL_RTVFSFSSTREAM)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, UINT32_MAX);
+ return rtVfsObjRelease(&pThis->Base);
+}
+
+
+RTDECL(int) RTVfsFsStrmQueryInfo(RTVFSFSSTREAM hVfsFss, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RTVFSFSSTREAMINTERNAL *pThis = hVfsFss;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ return RTVfsObjQueryInfo(&pThis->Base, pObjInfo, enmAddAttr);
+}
+
+
+RTDECL(int) RTVfsFsStrmNext(RTVFSFSSTREAM hVfsFss, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ RTVFSFSSTREAMINTERNAL *pThis = hVfsFss;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrNullReturn(ppszName, VERR_INVALID_POINTER);
+ if (ppszName)
+ *ppszName = NULL;
+ AssertPtrNullReturn(penmType, VERR_INVALID_POINTER);
+ if (penmType)
+ *penmType = RTVFSOBJTYPE_INVALID;
+ AssertPtrNullReturn(penmType, VERR_INVALID_POINTER);
+ if (phVfsObj)
+ *phVfsObj = NIL_RTVFSOBJ;
+
+ AssertReturn(pThis->fFlags & RTFILE_O_READ, VERR_INVALID_FUNCTION);
+
+ return pThis->pOps->pfnNext(pThis->Base.pvThis, ppszName, penmType, phVfsObj);
+}
+
+
+RTDECL(int) RTVfsFsStrmAdd(RTVFSFSSTREAM hVfsFss, const char *pszPath, RTVFSOBJ hVfsObj, uint32_t fFlags)
+{
+ RTVFSFSSTREAMINTERNAL *pThis = hVfsFss;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(*pszPath != '\0', VERR_INVALID_NAME);
+ AssertPtrReturn(hVfsObj, VERR_INVALID_HANDLE);
+ AssertReturn(hVfsObj->uMagic == RTVFSOBJ_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(!(fFlags & ~RTVFSFSSTRM_ADD_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_INVALID_FUNCTION);
+
+ return pThis->pOps->pfnAdd(pThis->Base.pvThis, pszPath, hVfsObj, fFlags);
+}
+
+
+RTDECL(int) RTVfsFsStrmPushFile(RTVFSFSSTREAM hVfsFss, const char *pszPath, uint64_t cbFile,
+ PCRTFSOBJINFO paObjInfo, uint32_t cObjInfo, uint32_t fFlags, PRTVFSIOSTREAM phVfsIos)
+{
+ RTVFSFSSTREAMINTERNAL *pThis = hVfsFss;
+ AssertPtrReturn(phVfsIos, VERR_INVALID_POINTER);
+ *phVfsIos = NIL_RTVFSIOSTREAM;
+
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE);
+
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(*pszPath != '\0', VERR_INVALID_NAME);
+
+ AssertReturn(!(fFlags & ~RTVFSFSSTRM_PUSH_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(RT_BOOL(cbFile == UINT64_MAX) == RT_BOOL(fFlags & RTVFSFSSTRM_PUSH_F_STREAM), VERR_INVALID_FLAGS);
+
+ if (cObjInfo)
+ {
+ AssertPtrReturn(paObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(paObjInfo[0].Attr.enmAdditional == RTFSOBJATTRADD_UNIX, VERR_INVALID_PARAMETER);
+ }
+
+ AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_INVALID_FUNCTION);
+ if (pThis->pOps->pfnPushFile)
+ return pThis->pOps->pfnPushFile(pThis->Base.pvThis, pszPath, cbFile, paObjInfo, cObjInfo, fFlags, phVfsIos);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+RTDECL(int) RTVfsFsStrmEnd(RTVFSFSSTREAM hVfsFss)
+{
+ RTVFSFSSTREAMINTERNAL *pThis = hVfsFss;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, VERR_INVALID_HANDLE);
+
+ return pThis->pOps->pfnEnd(pThis->Base.pvThis);
+}
+
+
+RTDECL(void *) RTVfsFsStreamToPrivate(RTVFSFSSTREAM hVfsFss, PCRTVFSFSSTREAMOPS pFsStreamOps)
+{
+ RTVFSFSSTREAMINTERNAL *pThis = hVfsFss;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->uMagic == RTVFSFSSTREAM_MAGIC, NULL);
+ if (pThis->pOps != pFsStreamOps)
+ return NULL;
+ return pThis->Base.pvThis;
+}
+
+
+/*
+ *
+ * D I R D I R D I R
+ * D I R D I R D I R
+ * D I R D I R D I R
+ *
+ */
+
+
+RTDECL(int) RTVfsNewDir(PCRTVFSDIROPS pDirOps, size_t cbInstance, uint32_t fFlags, RTVFS hVfs, RTVFSLOCK hLock,
+ PRTVFSDIR phVfsDir, void **ppvInstance)
+{
+ /*
+ * Validate the input, be extra strict in strict builds.
+ */
+ AssertPtr(pDirOps);
+ AssertReturn(pDirOps->uVersion == RTVFSDIROPS_VERSION, VERR_VERSION_MISMATCH);
+ AssertReturn(pDirOps->uEndMarker == RTVFSDIROPS_VERSION, VERR_VERSION_MISMATCH);
+ Assert(!pDirOps->fReserved);
+ RTVFSDIR_ASSERT_OPS(pDirOps, RTVFSOBJTYPE_DIR);
+ Assert(cbInstance > 0);
+ AssertReturn(!(fFlags & ~RTVFSDIR_F_NO_VFS_REF), VERR_INVALID_FLAGS);
+ AssertPtr(ppvInstance);
+ AssertPtr(phVfsDir);
+ RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE);
+
+ /*
+ * Allocate the handle + instance data.
+ */
+ size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSDIRINTERNAL), RTVFS_INST_ALIGNMENT)
+ + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT);
+ RTVFSDIRINTERNAL *pThis = (RTVFSDIRINTERNAL *)RTMemAllocZ(cbThis);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ int rc = rtVfsObjInitNewObject(&pThis->Base, &pDirOps->Obj, hVfs, RT_BOOL(fFlags & RTVFSDIR_F_NO_VFS_REF), hLock,
+ (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT));
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ pThis->uMagic = RTVFSDIR_MAGIC;
+ pThis->fReserved = 0;
+ pThis->pOps = pDirOps;
+
+ *phVfsDir = pThis;
+ *ppvInstance = pThis->Base.pvThis;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void *) RTVfsDirToPrivate(RTVFSDIR hVfsDir, PCRTVFSDIROPS pDirOps)
+{
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, NULL);
+ if (pThis->pOps != pDirOps)
+ return NULL;
+ return pThis->Base.pvThis;
+}
+
+
+#ifdef DEBUG
+# undef RTVfsDirRetain
+#endif
+RTDECL(uint32_t) RTVfsDirRetain(RTVFSDIR hVfsDir)
+{
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, UINT32_MAX);
+ uint32_t cRefs = rtVfsObjRetain(&pThis->Base);
+ LogFlow(("RTVfsDirRetain(%p/%p) -> %#x\n", pThis, pThis->Base.pvThis, cRefs));
+ return cRefs;
+}
+#ifdef DEBUG
+# define RTVfsDirRetain(hVfsDir) RTVfsDirRetainDebug(hVfsDir, RT_SRC_POS)
+#endif
+
+
+RTDECL(uint32_t) RTVfsDirRetainDebug(RTVFSDIR hVfsDir, RT_SRC_POS_DECL)
+{
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, UINT32_MAX);
+ return rtVfsObjRetainDebug(&pThis->Base, "RTVfsDirRetain", RT_SRC_POS_ARGS);
+}
+
+
+RTDECL(uint32_t) RTVfsDirRelease(RTVFSDIR hVfsDir)
+{
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ if (pThis == NIL_RTVFSDIR)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, UINT32_MAX);
+#ifdef LOG_ENABLED
+ void *pvThis = pThis->Base.pvThis;
+#endif
+ uint32_t cRefs = rtVfsObjRelease(&pThis->Base);
+ LogFlow(("RTVfsDirRelease(%p/%p) -> %#x\n", pThis, pvThis, cRefs));
+ return cRefs;
+}
+
+
+RTDECL(int) RTVfsDirOpen(RTVFS hVfs, const char *pszPath, uint32_t fFlags, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSINTERNAL *pThis = hVfs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(phVfsDir, VERR_INVALID_POINTER);
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS); /** @todo sort out flags! */
+
+ /*
+ * Parse the path, assume current directory is root since we've got no
+ * caller context here.
+ */
+ PRTVFSPARSEDPATH pPath;
+ int rc = RTVfsParsePathA(pszPath, "/", &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tranverse the path, resolving the parent node.
+ * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenDir.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ rc = rtVfsTraverseToParent(pThis, pPath, (fFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the opening. Loop if we need to follow symbolic links.
+ */
+ uint64_t fOpenFlags = RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN;
+ uint32_t fObjFlags = RTVFSOBJ_F_OPEN_DIRECTORY | RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_CREATE_NOTHING;
+ for (uint32_t cLoops = 1; ; cLoops++)
+ {
+ /* Do the querying. If pfnOpenDir is available, we use it first, falling
+ back on pfnOpen in case of symbolic links that needs following. */
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+ if (pVfsParentDir->pOps->pfnOpenDir)
+ {
+ RTVfsLockAcquireRead(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpenDir(pVfsParentDir->Base.pvThis, pszEntryName, fFlags, phVfsDir);
+ RTVfsLockReleaseRead(pVfsParentDir->Base.hLock);
+ if ( RT_SUCCESS(rc)
+ || ( rc != VERR_NOT_A_DIRECTORY
+ && rc != VERR_IS_A_SYMLINK))
+ break;
+ }
+
+ RTVFSOBJ hVfsObj;
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpenFlags, fObjFlags, &hVfsObj);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */
+ if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK)
+ || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK)
+ {
+ *phVfsDir = RTVfsObjToDir(hVfsObj);
+ AssertStmt(*phVfsDir != NIL_RTVFSDIR, rc = VERR_INTERNAL_ERROR_3);
+ RTVfsObjRelease(hVfsObj);
+ break;
+ }
+
+ /* Follow symbolic link. */
+ if (cLoops < RTVFS_MAX_LINKS)
+ rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK);
+ else
+ rc = VERR_TOO_MANY_SYMLINKS;
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsDirOpenDir(RTVFSDIR hVfsDir, const char *pszPath, uint32_t fFlags, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(phVfsDir, VERR_INVALID_POINTER);
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS); /** @todo sort out flags! */
+
+ /*
+ * Parse the path, it's always relative to the given directory.
+ */
+ PRTVFSPARSEDPATH pPath;
+ int rc = RTVfsParsePathA(pszPath, NULL, &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tranverse the path, resolving the parent node.
+ * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenDir.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ uint32_t const fTraverse = (fFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK;
+ rc = rtVfsDirTraverseToParent(pThis, pPath, fTraverse, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the opening. Loop if we need to follow symbolic links.
+ */
+ uint64_t fOpenFlags = RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN;
+ uint32_t fObjFlags = RTVFSOBJ_F_OPEN_DIRECTORY | RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_CREATE_NOTHING | fTraverse;
+ for (uint32_t cLoops = 1; ; cLoops++)
+ {
+ /* Do the querying. If pfnOpenDir is available, we use it first, falling
+ back on pfnOpen in case of symbolic links that needs following. */
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+ if (pVfsParentDir->pOps->pfnOpenDir)
+ {
+ RTVfsLockAcquireRead(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpenDir(pVfsParentDir->Base.pvThis, pszEntryName, fFlags, phVfsDir);
+ RTVfsLockReleaseRead(pVfsParentDir->Base.hLock);
+ if ( RT_SUCCESS(rc)
+ || ( rc != VERR_NOT_A_DIRECTORY
+ && rc != VERR_IS_A_SYMLINK))
+ break;
+ }
+
+ RTVFSOBJ hVfsObj;
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpenFlags, fObjFlags, &hVfsObj);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */
+ if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK)
+ || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK)
+ {
+ *phVfsDir = RTVfsObjToDir(hVfsObj);
+ AssertStmt(*phVfsDir != NIL_RTVFSDIR, rc = VERR_INTERNAL_ERROR_3);
+ RTVfsObjRelease(hVfsObj);
+ break;
+ }
+
+ /* Follow symbolic link. */
+ if (cLoops < RTVFS_MAX_LINKS)
+ rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fTraverse);
+ else
+ rc = VERR_TOO_MANY_SYMLINKS;
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsDirCreateDir(RTVFSDIR hVfsDir, const char *pszRelPath, RTFMODE fMode, uint32_t fFlags, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszRelPath, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(phVfsDir, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTDIRCREATE_FLAGS_VALID_MASK), VERR_INVALID_FLAGS);
+ fMode = rtFsModeNormalize(fMode, pszRelPath, 0, RTFS_TYPE_DIRECTORY);
+ AssertReturn(rtFsModeIsValidPermissions(fMode), VERR_INVALID_FMODE);
+ if (!(fFlags & RTDIRCREATE_FLAGS_NOT_CONTENT_INDEXED_DONT_SET))
+ fMode |= RTFS_DOS_NT_NOT_CONTENT_INDEXED;
+
+ /*
+ * Parse the path, it's always relative to the given directory.
+ */
+ PRTVFSPARSEDPATH pPath;
+ int rc = RTVfsParsePathA(pszRelPath, NULL, &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tranverse the path, resolving the parent node.
+ * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenDir.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ uint32_t fTraverse = (fFlags & RTDIRCREATE_FLAGS_NO_SYMLINKS ? RTPATH_F_NO_SYMLINKS : 0) | RTPATH_F_ON_LINK;
+ rc = rtVfsDirTraverseToParent(pThis, pPath, fTraverse, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the opening. Loop if we need to follow symbolic links.
+ */
+ uint64_t fOpenFlags = RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_CREATE
+ | ((fMode << RTFILE_O_CREATE_MODE_SHIFT) & RTFILE_O_CREATE_MODE_MASK);
+ uint32_t fObjFlags = RTVFSOBJ_F_OPEN_SYMLINK | RTVFSOBJ_F_CREATE_DIRECTORY | fTraverse;
+ for (uint32_t cLoops = 1; ; cLoops++)
+ {
+ /* Do the querying. If pfnOpenDir is available, we use it first, falling
+ back on pfnOpen in case of symbolic links that needs following. */
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+ if (pVfsParentDir->pOps->pfnCreateDir)
+ {
+ RTVfsLockAcquireRead(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnCreateDir(pVfsParentDir->Base.pvThis, pszEntryName, fMode, phVfsDir);
+ RTVfsLockReleaseRead(pVfsParentDir->Base.hLock);
+ if ( RT_SUCCESS(rc)
+ || ( rc != VERR_NOT_A_DIRECTORY
+ && rc != VERR_IS_A_SYMLINK))
+ break;
+ }
+
+ RTVFSOBJ hVfsObj;
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpenFlags, fObjFlags, &hVfsObj);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */
+ if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK)
+ || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK)
+ {
+ if (phVfsDir)
+ {
+ *phVfsDir = RTVfsObjToDir(hVfsObj);
+ AssertStmt(*phVfsDir != NIL_RTVFSDIR, rc = VERR_INTERNAL_ERROR_3);
+ }
+ RTVfsObjRelease(hVfsObj);
+ break;
+ }
+
+ /* Follow symbolic link. */
+ if (cLoops < RTVFS_MAX_LINKS)
+ rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fTraverse);
+ else
+ rc = VERR_TOO_MANY_SYMLINKS;
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsDirOpenFile(RTVFSDIR hVfsDir, const char *pszPath, uint64_t fOpen, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER);
+
+ int rc = rtFileRecalcAndValidateFlags(&fOpen);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Parse the path, it's always relative to the given directory.
+ */
+ PRTVFSPARSEDPATH pPath;
+ rc = RTVfsParsePathA(pszPath, NULL, &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tranverse the path, resolving the parent node.
+ * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenFile.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ uint32_t const fTraverse = (fOpen & RTFILE_O_NO_SYMLINKS ? RTPATH_F_NO_SYMLINKS : 0) | RTPATH_F_ON_LINK;
+ rc = rtVfsDirTraverseToParent(pThis, pPath, fTraverse, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo join path with RTVfsFileOpen. */
+
+ /*
+ * Do the opening. Loop if we need to follow symbolic links.
+ */
+ bool fDirSlash = pPath->fDirSlash;
+
+ uint32_t fObjFlags = RTVFSOBJ_F_OPEN_ANY_FILE | RTVFSOBJ_F_OPEN_SYMLINK;
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ fObjFlags |= RTVFSOBJ_F_CREATE_FILE;
+ else
+ fObjFlags |= RTVFSOBJ_F_CREATE_NOTHING;
+ fObjFlags |= fTraverse & RTPATH_F_MASK;
+
+ for (uint32_t cLoops = 1;; cLoops++)
+ {
+ /* Do the querying. If pfnOpenFile is available, we use it first, falling
+ back on pfnOpen in case of symbolic links that needs following or we got
+ a trailing directory slash (to get file-not-found error). */
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+ if ( pVfsParentDir->pOps->pfnOpenFile
+ && !fDirSlash)
+ {
+ RTVfsLockAcquireRead(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpenFile(pVfsParentDir->Base.pvThis, pszEntryName, fOpen, phVfsFile);
+ RTVfsLockReleaseRead(pVfsParentDir->Base.hLock);
+ if ( RT_SUCCESS(rc)
+ || ( rc != VERR_NOT_A_FILE
+ && rc != VERR_IS_A_SYMLINK))
+ break;
+ }
+
+ RTVFSOBJ hVfsObj;
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpen, fObjFlags, &hVfsObj);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */
+ if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK)
+ || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK)
+ {
+ *phVfsFile = RTVfsObjToFile(hVfsObj);
+ AssertStmt(*phVfsFile != NIL_RTVFSFILE, rc = VERR_INTERNAL_ERROR_3);
+ RTVfsObjRelease(hVfsObj);
+ break;
+ }
+
+ /* Follow symbolic link. */
+ if (cLoops < RTVFS_MAX_LINKS)
+ rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fTraverse);
+ else
+ rc = VERR_TOO_MANY_SYMLINKS;
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ fDirSlash |= pPath->fDirSlash;
+ }
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsDirOpenFileAsIoStream(RTVFSDIR hVfsDir, const char *pszPath, uint64_t fOpen, PRTVFSIOSTREAM phVfsIos)
+{
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsDirOpenFile(hVfsDir, pszPath, fOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsIos = RTVfsFileToIoStream(hVfsFile);
+ AssertStmt(*phVfsIos != NIL_RTVFSIOSTREAM, rc = VERR_INTERNAL_ERROR_2);
+ RTVfsFileRelease(hVfsFile);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsDirOpenObj(RTVFSDIR hVfsDir, const char *pszPath, uint64_t fFileOpen, uint32_t fObjFlags, PRTVFSOBJ phVfsObj)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertPtrReturn(phVfsObj, VERR_INVALID_POINTER);
+
+ int rc = rtFileRecalcAndValidateFlags(&fFileOpen);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertMsgReturn( RTPATH_F_IS_VALID(fObjFlags, RTVFSOBJ_F_VALID_MASK)
+ && (fObjFlags & RTVFSOBJ_F_CREATE_MASK) <= RTVFSOBJ_F_CREATE_DIRECTORY,
+ ("fObjFlags=%#x\n", fObjFlags),
+ VERR_INVALID_FLAGS);
+
+ /*
+ * Parse the relative path. If it ends with a directory slash or it boils
+ * down to an empty path (i.e. re-opening hVfsDir), adjust the flags to only
+ * open/create directories.
+ */
+ PRTVFSPARSEDPATH pPath;
+ rc = RTVfsParsePathA(pszPath, NULL, &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tranverse the path, resolving the parent node.
+ * We'll do the symbolic link checking here with help of pfnOpen.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ rc = rtVfsDirTraverseToParent(pThis, pPath, (fObjFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the opening. Loop if we need to follow symbolic links.
+ */
+ for (uint32_t cLoops = 1;; cLoops++)
+ {
+ /* If we end with a directory slash, adjust open flags. */
+ if (pPath->fDirSlash)
+ {
+ fObjFlags &= ~RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_OPEN_DIRECTORY;
+ if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_DIRECTORY)
+ fObjFlags = (fObjFlags & ~RTVFSOBJ_F_CREATE_MASK) | RTVFSOBJ_F_CREATE_NOTHING;
+ }
+ if (fObjFlags & RTPATH_F_FOLLOW_LINK)
+ fObjFlags |= RTVFSOBJ_F_OPEN_SYMLINK;
+
+ /* Open it. */
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+ RTVFSOBJ hVfsObj;
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fFileOpen, fObjFlags, &hVfsObj);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* We're done if we don't follow links or this wasn't a link. */
+ if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK)
+ || RTVfsObjGetType(*phVfsObj) != RTVFSOBJTYPE_SYMLINK)
+ {
+ *phVfsObj = hVfsObj;
+ break;
+ }
+
+ /* Follow symbolic link. */
+ if (cLoops < RTVFS_MAX_LINKS)
+ rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK);
+ else
+ rc = VERR_TOO_MANY_SYMLINKS;
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsDirQueryPathInfo(RTVFSDIR hVfsDir, const char *pszPath, PRTFSOBJINFO pObjInfo,
+ RTFSOBJATTRADD enmAddAttr, uint32_t fFlags)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
+ AssertReturn(*pszPath, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(enmAddAttr >= RTFSOBJATTRADD_NOTHING && enmAddAttr <= RTFSOBJATTRADD_LAST, VERR_INVALID_PARAMETER);
+ AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
+
+ /*
+ * Parse the relative path. Then traverse to the parent directory.
+ */
+ PRTVFSPARSEDPATH pPath;
+ int rc = RTVfsParsePathA(pszPath, NULL, &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tranverse the path, resolving the parent node.
+ * We'll do the symbolic link checking here with help of pfnOpen.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ rc = rtVfsDirTraverseToParent(pThis, pPath, (fFlags & RTPATH_F_NO_SYMLINKS) | RTPATH_F_ON_LINK, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do the opening. Loop if we need to follow symbolic links.
+ */
+ uint32_t fObjFlags = RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_CREATE_NOTHING;
+ for (uint32_t cLoops = 1;; cLoops++)
+ {
+ /* If we end with a directory slash, adjust open flags. */
+ if (pPath->fDirSlash)
+ {
+ fObjFlags &= ~RTVFSOBJ_F_OPEN_ANY | RTVFSOBJ_F_OPEN_DIRECTORY;
+ if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_DIRECTORY)
+ fObjFlags = (fObjFlags & ~RTVFSOBJ_F_CREATE_MASK) | RTVFSOBJ_F_CREATE_NOTHING;
+ }
+ if (fObjFlags & RTPATH_F_FOLLOW_LINK)
+ fObjFlags |= RTVFSOBJ_F_OPEN_SYMLINK;
+
+ /* Do the querying. If pfnQueryEntryInfo is available, we use it first,
+ falling back on pfnOpen in case of symbolic links that needs following. */
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+ if (pVfsParentDir->pOps->pfnQueryEntryInfo)
+ {
+ RTVfsLockAcquireRead(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnQueryEntryInfo(pVfsParentDir->Base.pvThis, pszEntryName, pObjInfo, enmAddAttr);
+ RTVfsLockReleaseRead(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+ if ( !RTFS_IS_SYMLINK(pObjInfo->Attr.fMode)
+ || !(fFlags & RTPATH_F_FOLLOW_LINK))
+ {
+ if ( (fObjFlags & RTVFSOBJ_F_OPEN_MASK) != RTVFSOBJ_F_OPEN_ANY
+ && !RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ rc = VERR_NOT_A_DIRECTORY;
+ break;
+ }
+ }
+
+ RTVFSOBJ hVfsObj;
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName,
+ RTFILE_O_ACCESS_ATTR_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ fObjFlags, &hVfsObj);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */
+ if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK)
+ || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK)
+ {
+ rc = RTVfsObjQueryInfo(hVfsObj, pObjInfo, enmAddAttr);
+ RTVfsObjRelease(hVfsObj);
+ break;
+ }
+
+ /* Follow symbolic link. */
+ if (cLoops < RTVFS_MAX_LINKS)
+ rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fObjFlags & RTPATH_F_MASK);
+ else
+ rc = VERR_TOO_MANY_SYMLINKS;
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ }
+
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsDirRemoveDir(RTVFSDIR hVfsDir, const char *pszRelPath, uint32_t fFlags)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszRelPath, VERR_INVALID_POINTER);
+ AssertReturn(!fFlags, VERR_INVALID_FLAGS);
+
+ /*
+ * Parse the path, it's always relative to the given directory.
+ */
+ PRTVFSPARSEDPATH pPath;
+ int rc = RTVfsParsePathA(pszRelPath, NULL, &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ if (pPath->cComponents > 0)
+ {
+ /*
+ * Tranverse the path, resolving the parent node, not checking for symbolic
+ * links in the final element, and ask the directory to remove the subdir.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ rc = rtVfsDirTraverseToParent(pThis, pPath, RTPATH_F_ON_LINK, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnUnlinkEntry(pVfsParentDir->Base.pvThis, pszEntryName, RTFS_TYPE_DIRECTORY);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ }
+ else
+ rc = VERR_PATH_ZERO_LENGTH;
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+}
+
+
+
+RTDECL(int) RTVfsDirReadEx(RTVFSDIR hVfsDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAddAttr)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER);
+ AssertReturn(enmAddAttr >= RTFSOBJATTRADD_NOTHING && enmAddAttr <= RTFSOBJATTRADD_LAST, VERR_INVALID_PARAMETER);
+
+ size_t cbDirEntry = sizeof(*pDirEntry);
+ if (!pcbDirEntry)
+ pcbDirEntry = &cbDirEntry;
+ else
+ {
+ cbDirEntry = *pcbDirEntry;
+ AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRYEX, szName[2]),
+ ("Invalid *pcbDirEntry=%d (min %zu)\n", *pcbDirEntry, RT_UOFFSETOF(RTDIRENTRYEX, szName[2])),
+ VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Call the directory method.
+ */
+ RTVfsLockAcquireRead(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnReadDir(pThis->Base.pvThis, pDirEntry, pcbDirEntry, enmAddAttr);
+ RTVfsLockReleaseRead(pThis->Base.hLock);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsDirRewind(RTVFSDIR hVfsDir)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSDIRINTERNAL *pThis = hVfsDir;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSDIR_MAGIC, VERR_INVALID_HANDLE);
+
+ /*
+ * Call the directory method.
+ */
+ RTVfsLockAcquireRead(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnRewindDir(pThis->Base.pvThis);
+ RTVfsLockReleaseRead(pThis->Base.hLock);
+ return rc;
+}
+
+
+/*
+ *
+ * S Y M B O L I C L I N K
+ * S Y M B O L I C L I N K
+ * S Y M B O L I C L I N K
+ *
+ */
+
+RTDECL(int) RTVfsNewSymlink(PCRTVFSSYMLINKOPS pSymlinkOps, size_t cbInstance, RTVFS hVfs, RTVFSLOCK hLock,
+ PRTVFSSYMLINK phVfsSym, void **ppvInstance)
+{
+ /*
+ * Validate the input, be extra strict in strict builds.
+ */
+ AssertPtr(pSymlinkOps);
+ AssertReturn(pSymlinkOps->uVersion == RTVFSSYMLINKOPS_VERSION, VERR_VERSION_MISMATCH);
+ AssertReturn(pSymlinkOps->uEndMarker == RTVFSSYMLINKOPS_VERSION, VERR_VERSION_MISMATCH);
+ Assert(!pSymlinkOps->fReserved);
+ RTVFSSYMLINK_ASSERT_OPS(pSymlinkOps, RTVFSOBJTYPE_SYMLINK);
+ Assert(cbInstance > 0);
+ AssertPtr(ppvInstance);
+ AssertPtr(phVfsSym);
+ RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE);
+
+ /*
+ * Allocate the handle + instance data.
+ */
+ size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSSYMLINKINTERNAL), RTVFS_INST_ALIGNMENT)
+ + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT);
+ RTVFSSYMLINKINTERNAL *pThis = (RTVFSSYMLINKINTERNAL *)RTMemAllocZ(cbThis);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ int rc = rtVfsObjInitNewObject(&pThis->Base, &pSymlinkOps->Obj, hVfs, false /*fNoVfsRef*/, hLock,
+ (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT));
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ pThis->uMagic = RTVFSSYMLINK_MAGIC;
+ pThis->pOps = pSymlinkOps;
+
+ *phVfsSym = pThis;
+ *ppvInstance = pThis->Base.pvThis;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void *) RTVfsSymlinkToPrivate(RTVFSSYMLINK hVfsSym, PCRTVFSSYMLINKOPS pSymlinkOps)
+{
+ RTVFSSYMLINKINTERNAL *pThis = hVfsSym;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, NULL);
+ if (pThis->pOps != pSymlinkOps)
+ return NULL;
+ return pThis->Base.pvThis;
+}
+
+
+RTDECL(uint32_t) RTVfsSymlinkRetain(RTVFSSYMLINK hVfsSym)
+{
+ RTVFSSYMLINKINTERNAL *pThis = hVfsSym;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, UINT32_MAX);
+ return rtVfsObjRetain(&pThis->Base);
+}
+
+
+RTDECL(uint32_t) RTVfsSymlinkRetainDebug(RTVFSSYMLINK hVfsSym, RT_SRC_POS_DECL)
+{
+ RTVFSSYMLINKINTERNAL *pThis = hVfsSym;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, UINT32_MAX);
+ return rtVfsObjRetainDebug(&pThis->Base, "RTVfsSymlinkRetainDebug", RT_SRC_POS_ARGS);
+}
+
+
+RTDECL(uint32_t) RTVfsSymlinkRelease(RTVFSSYMLINK hVfsSym)
+{
+ RTVFSSYMLINKINTERNAL *pThis = hVfsSym;
+ if (pThis == NIL_RTVFSSYMLINK)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, UINT32_MAX);
+ return rtVfsObjRelease(&pThis->Base);
+}
+
+
+RTDECL(int) RTVfsSymlinkQueryInfo(RTVFSSYMLINK hVfsSym, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RTVFSSYMLINKINTERNAL *pThis = hVfsSym;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE);
+ return RTVfsObjQueryInfo(&pThis->Base, pObjInfo, enmAddAttr);
+}
+
+
+RTDECL(int) RTVfsSymlinkSetMode(RTVFSSYMLINK hVfsSym, RTFMODE fMode, RTFMODE fMask)
+{
+ RTVFSSYMLINKINTERNAL *pThis = hVfsSym;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE);
+
+ fMode = rtFsModeNormalize(fMode, NULL, 0, RTFS_TYPE_SYMLINK);
+ if (!rtFsModeIsValid(fMode))
+ return VERR_INVALID_PARAMETER;
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ int rc = pThis->pOps->ObjSet.pfnSetMode(pThis->Base.pvThis, fMode, fMask);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsSymlinkSetTimes(RTVFSSYMLINK hVfsSym, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RTVFSSYMLINKINTERNAL *pThis = hVfsSym;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE);
+
+ AssertPtrNullReturn(pAccessTime, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pModificationTime, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pChangeTime, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pBirthTime, VERR_INVALID_POINTER);
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ int rc = pThis->pOps->ObjSet.pfnSetTimes(pThis->Base.pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsSymlinkSetOwner(RTVFSSYMLINK hVfsSym, RTUID uid, RTGID gid)
+{
+ RTVFSSYMLINKINTERNAL *pThis = hVfsSym;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE);
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ int rc = pThis->pOps->ObjSet.pfnSetOwner(pThis->Base.pvThis, uid, gid);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsSymlinkRead(RTVFSSYMLINK hVfsSym, char *pszTarget, size_t cbTarget)
+{
+ RTVFSSYMLINKINTERNAL *pThis = hVfsSym;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSSYMLINK_MAGIC, VERR_INVALID_HANDLE);
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnRead(pThis->Base.pvThis, pszTarget, cbTarget);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+
+ return rc;
+}
+
+
+
+/*
+ *
+ * I / O S T R E A M I / O S T R E A M I / O S T R E A M
+ * I / O S T R E A M I / O S T R E A M I / O S T R E A M
+ * I / O S T R E A M I / O S T R E A M I / O S T R E A M
+ *
+ */
+
+RTDECL(int) RTVfsNewIoStream(PCRTVFSIOSTREAMOPS pIoStreamOps, size_t cbInstance, uint32_t fOpen, RTVFS hVfs, RTVFSLOCK hLock,
+ PRTVFSIOSTREAM phVfsIos, void **ppvInstance)
+{
+ /*
+ * Validate the input, be extra strict in strict builds.
+ */
+ AssertPtr(pIoStreamOps);
+ AssertReturn(pIoStreamOps->uVersion == RTVFSIOSTREAMOPS_VERSION, VERR_VERSION_MISMATCH);
+ AssertReturn(pIoStreamOps->uEndMarker == RTVFSIOSTREAMOPS_VERSION, VERR_VERSION_MISMATCH);
+ Assert(!(pIoStreamOps->fFeatures & ~RTVFSIOSTREAMOPS_FEAT_VALID_MASK));
+ RTVFSIOSTREAM_ASSERT_OPS(pIoStreamOps, RTVFSOBJTYPE_IO_STREAM);
+ Assert(cbInstance > 0);
+ Assert(fOpen & RTFILE_O_ACCESS_MASK);
+ AssertPtr(ppvInstance);
+ AssertPtr(phVfsIos);
+ RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE);
+
+ /*
+ * Allocate the handle + instance data.
+ */
+ size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSIOSTREAMINTERNAL), RTVFS_INST_ALIGNMENT)
+ + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT);
+ RTVFSIOSTREAMINTERNAL *pThis = (RTVFSIOSTREAMINTERNAL *)RTMemAllocZ(cbThis);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ int rc = rtVfsObjInitNewObject(&pThis->Base, &pIoStreamOps->Obj, hVfs, false /*fNoVfsRef*/, hLock,
+ (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT));
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ pThis->uMagic = RTVFSIOSTREAM_MAGIC;
+ pThis->fFlags = fOpen;
+ pThis->pOps = pIoStreamOps;
+
+ *phVfsIos = pThis;
+ *ppvInstance = pThis->Base.pvThis;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(void *) RTVfsIoStreamToPrivate(RTVFSIOSTREAM hVfsIos, PCRTVFSIOSTREAMOPS pIoStreamOps)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, NULL);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, NULL);
+ if (pThis->pOps != pIoStreamOps)
+ return NULL;
+ return pThis->Base.pvThis;
+}
+
+
+#ifdef DEBUG
+# undef RTVfsIoStrmRetain
+#endif
+RTDECL(uint32_t) RTVfsIoStrmRetain(RTVFSIOSTREAM hVfsIos)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, UINT32_MAX);
+ return rtVfsObjRetain(&pThis->Base);
+}
+#ifdef DEBUG
+# define RTVfsIoStrmRetain(hVfsIos) RTVfsIoStrmRetainDebug(hVfsIos, RT_SRC_POS)
+#endif
+
+
+RTDECL(uint32_t) RTVfsIoStrmRetainDebug(RTVFSIOSTREAM hVfsIos, RT_SRC_POS_DECL)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, UINT32_MAX);
+ return rtVfsObjRetainDebug(&pThis->Base, "RTVfsIoStrmRetainDebug", RT_SRC_POS_ARGS);
+}
+
+
+RTDECL(uint32_t) RTVfsIoStrmRelease(RTVFSIOSTREAM hVfsIos)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ if (pThis == NIL_RTVFSIOSTREAM)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, UINT32_MAX);
+ return rtVfsObjRelease(&pThis->Base);
+}
+
+
+RTDECL(RTVFSFILE) RTVfsIoStrmToFile(RTVFSIOSTREAM hVfsIos)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, NIL_RTVFSFILE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, NIL_RTVFSFILE);
+
+ if (pThis->pOps->Obj.enmType == RTVFSOBJTYPE_FILE)
+ {
+ rtVfsObjRetainVoid(&pThis->Base, "RTVfsIoStrmToFile");
+ return RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream);
+ }
+
+ /* this is no crime, so don't assert. */
+ return NIL_RTVFSFILE;
+}
+
+
+RTDECL(int) RTVfsIoStrmQueryInfo(RTVFSIOSTREAM hVfsIos, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ return RTVfsObjQueryInfo(&pThis->Base, pObjInfo, enmAddAttr);
+}
+
+
+RTDECL(int) RTVfsIoStrmRead(RTVFSIOSTREAM hVfsIos, void *pvBuf, size_t cbToRead, bool fBlocking, size_t *pcbRead)
+{
+ AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
+ if (pcbRead)
+ *pcbRead = 0;
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(fBlocking || pcbRead, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->fFlags & RTFILE_O_READ, VERR_ACCESS_DENIED);
+
+ RTSGSEG Seg = { pvBuf, cbToRead };
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnRead(pThis->Base.pvThis, -1 /*off*/, &SgBuf, fBlocking, pcbRead);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmReadAt(RTVFSIOSTREAM hVfsIos, RTFOFF off, void *pvBuf, size_t cbToRead,
+ bool fBlocking, size_t *pcbRead)
+{
+ AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
+ if (pcbRead)
+ *pcbRead = 0;
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(fBlocking || pcbRead, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->fFlags & RTFILE_O_READ, VERR_ACCESS_DENIED);
+
+ RTSGSEG Seg = { pvBuf, cbToRead };
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnRead(pThis->Base.pvThis, off, &SgBuf, fBlocking, pcbRead);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmWrite(RTVFSIOSTREAM hVfsIos, const void *pvBuf, size_t cbToWrite, bool fBlocking, size_t *pcbWritten)
+{
+ AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER);
+ if (pcbWritten)
+ *pcbWritten = 0;
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(fBlocking || pcbWritten, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_ACCESS_DENIED);
+
+ int rc;
+ if (pThis->pOps->pfnWrite)
+ {
+ RTSGSEG Seg = { (void *)pvBuf, cbToWrite };
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ rc = pThis->pOps->pfnWrite(pThis->Base.pvThis, -1 /*off*/, &SgBuf, fBlocking, pcbWritten);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmWriteAt(RTVFSIOSTREAM hVfsIos, RTFOFF off, const void *pvBuf, size_t cbToWrite,
+ bool fBlocking, size_t *pcbWritten)
+{
+ AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER);
+ if (pcbWritten)
+ *pcbWritten = 0;
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(fBlocking || pcbWritten, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_ACCESS_DENIED);
+
+ int rc;
+ if (pThis->pOps->pfnWrite)
+ {
+ RTSGSEG Seg = { (void *)pvBuf, cbToWrite };
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &Seg, 1);
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ rc = pThis->pOps->pfnWrite(pThis->Base.pvThis, off, &SgBuf, fBlocking, pcbWritten);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmSgRead(RTVFSIOSTREAM hVfsIos, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
+ if (pcbRead)
+ *pcbRead = 0;
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pSgBuf);
+ AssertReturn(fBlocking || pcbRead, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->fFlags & RTFILE_O_READ, VERR_ACCESS_DENIED);
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ int rc;
+ if (!(pThis->pOps->fFeatures & RTVFSIOSTREAMOPS_FEAT_NO_SG))
+ rc = pThis->pOps->pfnRead(pThis->Base.pvThis, off, pSgBuf, fBlocking, pcbRead);
+ else
+ {
+ size_t cbRead = 0;
+ rc = VINF_SUCCESS;
+
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ {
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &pSgBuf->paSegs[iSeg], 1);
+
+ size_t cbReadSeg = pcbRead ? 0 : pSgBuf->paSegs[iSeg].cbSeg;
+ rc = pThis->pOps->pfnRead(pThis->Base.pvThis, off, &SgBuf, fBlocking, pcbRead ? &cbReadSeg : NULL);
+ if (RT_FAILURE(rc))
+ break;
+ cbRead += cbReadSeg;
+ if ((pcbRead && cbReadSeg != SgBuf.paSegs[0].cbSeg) || rc != VINF_SUCCESS)
+ break;
+ if (off != -1)
+ off += cbReadSeg;
+ }
+
+ if (pcbRead)
+ *pcbRead = cbRead;
+ }
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmSgWrite(RTVFSIOSTREAM hVfsIos, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER);
+ if (pcbWritten)
+ *pcbWritten = 0;
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtr(pSgBuf);
+ AssertReturn(fBlocking || pcbWritten, VERR_INVALID_PARAMETER);
+ AssertReturn(pThis->fFlags & RTFILE_O_WRITE, VERR_ACCESS_DENIED);
+
+ int rc;
+ if (pThis->pOps->pfnWrite)
+ {
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ if (!(pThis->pOps->fFeatures & RTVFSIOSTREAMOPS_FEAT_NO_SG))
+ rc = pThis->pOps->pfnWrite(pThis->Base.pvThis, off, pSgBuf, fBlocking, pcbWritten);
+ else
+ {
+ size_t cbWritten = 0;
+ rc = VINF_SUCCESS;
+
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ {
+ RTSGBUF SgBuf;
+ RTSgBufInit(&SgBuf, &pSgBuf->paSegs[iSeg], 1);
+
+ size_t cbWrittenSeg = 0;
+ rc = pThis->pOps->pfnWrite(pThis->Base.pvThis, off, &SgBuf, fBlocking, pcbWritten ? &cbWrittenSeg : NULL);
+ if (RT_FAILURE(rc))
+ break;
+ if (pcbWritten)
+ {
+ cbWritten += cbWrittenSeg;
+ if (cbWrittenSeg != SgBuf.paSegs[0].cbSeg)
+ break;
+ if (off != -1)
+ off += cbWrittenSeg;
+ }
+ else if (off != -1)
+ off += pSgBuf->paSegs[iSeg].cbSeg;
+ }
+
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ }
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmFlush(RTVFSIOSTREAM hVfsIos)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE);
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnFlush(pThis->Base.pvThis);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmPoll(RTVFSIOSTREAM hVfsIos, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, VERR_INVALID_HANDLE);
+
+ int rc;
+ if (pThis->pOps->pfnPollOne)
+ {
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ rc = pThis->pOps->pfnPollOne(pThis->Base.pvThis, fEvents, cMillies, fIntr, pfRetEvents);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ }
+ /*
+ * Default implementation. Polling for non-error events returns
+ * immediately, waiting for errors will work like sleep.
+ */
+ else if (fEvents != RTPOLL_EVT_ERROR)
+ {
+ *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR;
+ rc = VINF_SUCCESS;
+ }
+ else if (fIntr)
+ rc = RTThreadSleep(cMillies);
+ else
+ {
+ uint64_t uMsStart = RTTimeMilliTS();
+ do
+ rc = RTThreadSleep(cMillies);
+ while ( rc == VERR_INTERRUPTED
+ && !fIntr
+ && RTTimeMilliTS() - uMsStart < cMillies);
+ if (rc == VERR_INTERRUPTED)
+ rc = VERR_TIMEOUT;
+ }
+ return rc;
+}
+
+
+RTDECL(RTFOFF) RTVfsIoStrmTell(RTVFSIOSTREAM hVfsIos)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, -1);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, -1);
+
+ RTFOFF off;
+ RTVfsLockAcquireRead(pThis->Base.hLock);
+ int rc = pThis->pOps->pfnTell(pThis->Base.pvThis, &off);
+ RTVfsLockReleaseRead(pThis->Base.hLock);
+ if (RT_FAILURE(rc))
+ off = rc;
+ return off;
+}
+
+
+RTDECL(int) RTVfsIoStrmSkip(RTVFSIOSTREAM hVfsIos, RTFOFF cb)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, -1);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, -1);
+ AssertReturn(cb >= 0, VERR_INVALID_PARAMETER);
+
+ int rc;
+ if (pThis->pOps->pfnSkip)
+ {
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ rc = pThis->pOps->pfnSkip(pThis->Base.pvThis, cb);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ }
+ else if (pThis->pOps->Obj.enmType == RTVFSOBJTYPE_FILE)
+ {
+ RTVFSFILEINTERNAL *pThisFile = RT_FROM_MEMBER(pThis, RTVFSFILEINTERNAL, Stream);
+ RTFOFF offIgnored;
+
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ rc = pThisFile->pOps->pfnSeek(pThis->Base.pvThis, cb, RTFILE_SEEK_CURRENT, &offIgnored);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ }
+ else
+ {
+ void *pvBuf = RTMemTmpAlloc(_64K);
+ if (pvBuf)
+ {
+ rc = VINF_SUCCESS;
+ while (cb > 0)
+ {
+ size_t cbToRead = (size_t)RT_MIN(cb, _64K);
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ rc = RTVfsIoStrmRead(hVfsIos, pvBuf, cbToRead, true /*fBlocking*/, NULL);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+ cb -= cbToRead;
+ }
+
+ RTMemTmpFree(pvBuf);
+ }
+ else
+ rc = VERR_NO_TMP_MEMORY;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmZeroFill(RTVFSIOSTREAM hVfsIos, RTFOFF cb)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, -1);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, -1);
+
+ int rc;
+ if (pThis->pOps->pfnZeroFill)
+ {
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ rc = pThis->pOps->pfnZeroFill(pThis->Base.pvThis, cb);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ }
+ else
+ {
+ rc = VINF_SUCCESS;
+ while (cb > 0)
+ {
+ size_t cbToWrite = (size_t)RT_MIN(cb, (ssize_t)sizeof(g_abRTZero64K));
+ RTVfsLockAcquireWrite(pThis->Base.hLock);
+ rc = RTVfsIoStrmWrite(hVfsIos, g_abRTZero64K, cbToWrite, true /*fBlocking*/, NULL);
+ RTVfsLockReleaseWrite(pThis->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+ cb -= cbToWrite;
+ }
+ }
+ return rc;
+}
+
+
+RTDECL(bool) RTVfsIoStrmIsAtEnd(RTVFSIOSTREAM hVfsIos)
+{
+ /*
+ * There is where the zero read behavior comes in handy.
+ */
+ char bDummy;
+ size_t cbRead;
+ int rc = RTVfsIoStrmRead(hVfsIos, &bDummy, 0 /*cbToRead*/, false /*fBlocking*/, &cbRead);
+ return rc == VINF_EOF;
+}
+
+
+
+RTDECL(uint64_t) RTVfsIoStrmGetOpenFlags(RTVFSIOSTREAM hVfsIos)
+{
+ RTVFSIOSTREAMINTERNAL *pThis = hVfsIos;
+ AssertPtrReturn(pThis, 0);
+ AssertReturn(pThis->uMagic == RTVFSIOSTREAM_MAGIC, 0);
+ return pThis->fFlags;
+}
+
+
+
+/*
+ *
+ * F I L E F I L E F I L E
+ * F I L E F I L E F I L E
+ * F I L E F I L E F I L E
+ *
+ */
+
+RTDECL(int) RTVfsNewFile(PCRTVFSFILEOPS pFileOps, size_t cbInstance, uint32_t fOpen, RTVFS hVfs, RTVFSLOCK hLock,
+ PRTVFSFILE phVfsFile, void **ppvInstance)
+{
+ /*
+ * Validate the input, be extra strict in strict builds.
+ */
+ RTVFSFILE_ASSERT_OPS(pFileOps, RTVFSOBJTYPE_FILE);
+ Assert(cbInstance > 0);
+ Assert(fOpen & (RTFILE_O_ACCESS_MASK | RTFILE_O_ACCESS_ATTR_MASK));
+ AssertPtr(ppvInstance);
+ AssertPtr(phVfsFile);
+ RTVFS_ASSERT_VALID_HANDLE_OR_NIL_RETURN(hVfs, VERR_INVALID_HANDLE);
+
+ /*
+ * Allocate the handle + instance data.
+ */
+ size_t const cbThis = RT_ALIGN_Z(sizeof(RTVFSFILEINTERNAL), RTVFS_INST_ALIGNMENT)
+ + RT_ALIGN_Z(cbInstance, RTVFS_INST_ALIGNMENT);
+ RTVFSFILEINTERNAL *pThis = (RTVFSFILEINTERNAL *)RTMemAllocZ(cbThis);
+ if (!pThis)
+ return VERR_NO_MEMORY;
+
+ int rc = rtVfsObjInitNewObject(&pThis->Stream.Base, &pFileOps->Stream.Obj, hVfs, false /*fNoVfsRef*/, hLock,
+ (char *)pThis + RT_ALIGN_Z(sizeof(*pThis), RTVFS_INST_ALIGNMENT));
+ if (RT_FAILURE(rc))
+ {
+ RTMemFree(pThis);
+ return rc;
+ }
+
+ pThis->uMagic = RTVFSFILE_MAGIC;
+ pThis->fReserved = 0;
+ pThis->pOps = pFileOps;
+ pThis->Stream.uMagic = RTVFSIOSTREAM_MAGIC;
+ pThis->Stream.fFlags = fOpen;
+ pThis->Stream.pOps = &pFileOps->Stream;
+
+ *phVfsFile = pThis;
+ *ppvInstance = pThis->Stream.Base.pvThis;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTVfsFileOpen(RTVFS hVfs, const char *pszFilename, uint64_t fOpen, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Validate input.
+ */
+ RTVFSINTERNAL *pThis = hVfs;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFS_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pszFilename, VERR_INVALID_POINTER);
+ AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER);
+
+ int rc = rtFileRecalcAndValidateFlags(&fOpen);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Parse the path, assume current directory is root since we've got no
+ * caller context here.
+ */
+ PRTVFSPARSEDPATH pPath;
+ rc = RTVfsParsePathA(pszFilename, "/", &pPath);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Tranverse the path, resolving the parent node.
+ * We'll do the symbolic link checking here with help of pfnOpen/pfnOpenFile.
+ */
+ RTVFSDIRINTERNAL *pVfsParentDir;
+ uint32_t const fTraverse = (fOpen & RTFILE_O_NO_SYMLINKS ? RTPATH_F_NO_SYMLINKS : 0) | RTPATH_F_ON_LINK;
+ rc = rtVfsTraverseToParent(pThis, pPath, fTraverse, &pVfsParentDir);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo join path with RTVfsDirOpenFile. */
+ /*
+ * Do the opening. Loop if we need to follow symbolic links.
+ */
+ bool fDirSlash = pPath->fDirSlash;
+
+ uint32_t fObjFlags = RTVFSOBJ_F_OPEN_ANY_FILE | RTVFSOBJ_F_OPEN_SYMLINK;
+ if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE
+ || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ fObjFlags |= RTVFSOBJ_F_CREATE_FILE;
+ else
+ fObjFlags |= RTVFSOBJ_F_CREATE_NOTHING;
+ fObjFlags |= fTraverse & RTPATH_F_MASK;
+
+ for (uint32_t cLoops = 1;; cLoops++)
+ {
+ /* Do the querying. If pfnOpenFile is available, we use it first, falling
+ back on pfnOpen in case of symbolic links that needs following or we got
+ a trailing directory slash (to get file-not-found error). */
+ const char *pszEntryName = &pPath->szPath[pPath->aoffComponents[pPath->cComponents - 1]];
+ if ( pVfsParentDir->pOps->pfnOpenFile
+ && !fDirSlash)
+ {
+ RTVfsLockAcquireRead(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpenFile(pVfsParentDir->Base.pvThis, pszEntryName, fOpen, phVfsFile);
+ RTVfsLockReleaseRead(pVfsParentDir->Base.hLock);
+ if ( RT_SUCCESS(rc)
+ || ( rc != VERR_NOT_A_FILE
+ && rc != VERR_IS_A_SYMLINK))
+ break;
+ }
+
+ RTVFSOBJ hVfsObj;
+ RTVfsLockAcquireWrite(pVfsParentDir->Base.hLock);
+ rc = pVfsParentDir->pOps->pfnOpen(pVfsParentDir->Base.pvThis, pszEntryName, fOpen, fObjFlags, &hVfsObj);
+ RTVfsLockReleaseWrite(pVfsParentDir->Base.hLock);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* If we don't follow links or this wasn't a link we just have to do the query and we're done. */
+ if ( !(fObjFlags & RTPATH_F_FOLLOW_LINK)
+ || RTVfsObjGetType(hVfsObj) != RTVFSOBJTYPE_SYMLINK)
+ {
+ *phVfsFile = RTVfsObjToFile(hVfsObj);
+ AssertStmt(*phVfsFile != NIL_RTVFSFILE, rc = VERR_INTERNAL_ERROR_3);
+ RTVfsObjRelease(hVfsObj);
+ break;
+ }
+
+ /* Follow symbolic link. */
+ if (cLoops < RTVFS_MAX_LINKS)
+ rc = rtVfsDirFollowSymlinkObjToParent(&pVfsParentDir, hVfsObj, pPath, fTraverse);
+ else
+ rc = VERR_TOO_MANY_SYMLINKS;
+ RTVfsObjRelease(hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ fDirSlash |= pPath->fDirSlash;
+ }
+ RTVfsDirRelease(pVfsParentDir);
+ }
+ RTVfsParsePathFree(pPath);
+ }
+ return rc;
+
+}
+
+
+#ifdef DEBUG
+# undef RTVfsFileRetain
+#endif
+RTDECL(uint32_t) RTVfsFileRetain(RTVFSFILE hVfsFile)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, UINT32_MAX);
+ return rtVfsObjRetain(&pThis->Stream.Base);
+}
+#ifdef DEBUG
+# define RTVfsFileRetain(hVfsFile) RTVfsFileRetainDebug(hVfsFile, RT_SRC_POS)
+#endif
+
+
+RTDECL(uint32_t) RTVfsFileRetainDebug(RTVFSFILE hVfsFile, RT_SRC_POS_DECL)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, UINT32_MAX);
+ return rtVfsObjRetainDebug(&pThis->Stream.Base, "RTVFsFileRetainDebug", RT_SRC_POS_ARGS);
+}
+
+
+RTDECL(uint32_t) RTVfsFileRelease(RTVFSFILE hVfsFile)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ if (pThis == NIL_RTVFSFILE)
+ return 0;
+ AssertPtrReturn(pThis, UINT32_MAX);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, UINT32_MAX);
+ return rtVfsObjRelease(&pThis->Stream.Base);
+}
+
+
+RTDECL(RTVFSIOSTREAM) RTVfsFileToIoStream(RTVFSFILE hVfsFile)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, NIL_RTVFSIOSTREAM);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, NIL_RTVFSIOSTREAM);
+
+ rtVfsObjRetainVoid(&pThis->Stream.Base, "RTVfsFileToIoStream");
+ return &pThis->Stream;
+}
+
+
+RTDECL(int) RTVfsFileQueryInfo(RTVFSFILE hVfsFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+ return RTVfsObjQueryInfo(&pThis->Stream.Base, pObjInfo, enmAddAttr);
+}
+
+
+RTDECL(int) RTVfsFileRead(RTVFSFILE hVfsFile, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
+ if (pcbRead)
+ *pcbRead = 0;
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+ return RTVfsIoStrmRead(&pThis->Stream, pvBuf, cbToRead, true /*fBlocking*/, pcbRead);
+}
+
+
+RTDECL(int) RTVfsFileWrite(RTVFSFILE hVfsFile, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten)
+{
+ AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER);
+ if (pcbWritten)
+ *pcbWritten = 0;
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+ return RTVfsIoStrmWrite(&pThis->Stream, pvBuf, cbToWrite, true /*fBlocking*/, pcbWritten);
+}
+
+
+RTDECL(int) RTVfsFileWriteAt(RTVFSFILE hVfsFile, RTFOFF off, const void *pvBuf, size_t cbToWrite, size_t *pcbWritten)
+{
+ AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER);
+ if (pcbWritten)
+ *pcbWritten = 0;
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+
+ int rc = RTVfsFileSeek(hVfsFile, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmWriteAt(&pThis->Stream, off, pvBuf, cbToWrite, true /*fBlocking*/, pcbWritten);
+
+ return rc;
+}
+
+
+RTDECL(int) RTVfsFileReadAt(RTVFSFILE hVfsFile, RTFOFF off, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
+ if (pcbRead)
+ *pcbRead = 0;
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+
+ int rc = RTVfsFileSeek(hVfsFile, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmReadAt(&pThis->Stream, off, pvBuf, cbToRead, true /*fBlocking*/, pcbRead);
+
+ return rc;
+}
+
+
+RTDECL(int) RTVfsFileSgRead(RTVFSFILE hVfsFile, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);
+ if (pcbRead)
+ *pcbRead = 0;
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+
+ return RTVfsIoStrmSgRead(&pThis->Stream, off, pSgBuf, fBlocking, pcbRead);
+}
+
+
+RTDECL(int) RTVfsFileSgWrite(RTVFSFILE hVfsFile, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ AssertPtrNullReturn(pcbWritten, VERR_INVALID_POINTER);
+ if (pcbWritten)
+ *pcbWritten = 0;
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+
+ return RTVfsIoStrmSgWrite(&pThis->Stream, off, pSgBuf, fBlocking, pcbWritten);
+}
+
+
+RTDECL(int) RTVfsFileFlush(RTVFSFILE hVfsFile)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+ return RTVfsIoStrmFlush(&pThis->Stream);
+}
+
+
+RTDECL(RTFOFF) RTVfsFilePoll(RTVFSFILE hVfsFile, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+ return RTVfsIoStrmPoll(&pThis->Stream, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+RTDECL(RTFOFF) RTVfsFileTell(RTVFSFILE hVfsFile)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+ return RTVfsIoStrmTell(&pThis->Stream);
+}
+
+
+RTDECL(int) RTVfsFileSeek(RTVFSFILE hVfsFile, RTFOFF offSeek, uint32_t uMethod, uint64_t *poffActual)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+
+ AssertReturn( uMethod == RTFILE_SEEK_BEGIN
+ || uMethod == RTFILE_SEEK_CURRENT
+ || uMethod == RTFILE_SEEK_END, VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(poffActual, VERR_INVALID_POINTER);
+
+ RTFOFF offActual = 0;
+ RTVfsLockAcquireWrite(pThis->Stream.Base.hLock);
+ int rc = pThis->pOps->pfnSeek(pThis->Stream.Base.pvThis, offSeek, uMethod, &offActual);
+ RTVfsLockReleaseWrite(pThis->Stream.Base.hLock);
+ if (RT_SUCCESS(rc) && poffActual)
+ {
+ Assert(offActual >= 0);
+ *poffActual = offActual;
+ }
+
+ return rc;
+}
+
+
+RTDECL(int) RTVfsFileQuerySize(RTVFSFILE hVfsFile, uint64_t *pcbSize)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pcbSize, VERR_INVALID_POINTER);
+
+ RTVfsLockAcquireWrite(pThis->Stream.Base.hLock);
+ int rc = pThis->pOps->pfnQuerySize(pThis->Stream.Base.pvThis, pcbSize);
+ RTVfsLockReleaseWrite(pThis->Stream.Base.hLock);
+
+ return rc;
+}
+
+
+RTDECL(int) RTVfsFileSetSize(RTVFSFILE hVfsFile, uint64_t cbSize, uint32_t fFlags)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+ AssertReturn(RTVFSFILE_SIZE_F_IS_VALID(fFlags), VERR_INVALID_FLAGS);
+ AssertReturn(pThis->Stream.fFlags & RTFILE_O_WRITE, VERR_ACCESS_DENIED);
+
+ int rc;
+ if (pThis->pOps->pfnSetSize)
+ {
+ RTVfsLockAcquireWrite(pThis->Stream.Base.hLock);
+ rc = pThis->pOps->pfnSetSize(pThis->Stream.Base.pvThis, cbSize, fFlags);
+ RTVfsLockReleaseWrite(pThis->Stream.Base.hLock);
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+ return rc;
+}
+
+
+RTDECL(RTFOFF) RTVfsFileGetMaxSize(RTVFSFILE hVfsFile)
+{
+ uint64_t cbMax;
+ int rc = RTVfsFileQueryMaxSize(hVfsFile, &cbMax);
+ return RT_SUCCESS(rc) ? (RTFOFF)RT_MIN(cbMax, (uint64_t)RTFOFF_MAX) : -1;
+}
+
+
+RTDECL(int) RTVfsFileQueryMaxSize(RTVFSFILE hVfsFile, uint64_t *pcbMax)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, VERR_INVALID_HANDLE);
+ AssertPtrReturn(pcbMax, VERR_INVALID_POINTER);
+ *pcbMax = RTFOFF_MAX;
+
+ int rc;
+ if (pThis->pOps->pfnQueryMaxSize)
+ {
+ RTVfsLockAcquireWrite(pThis->Stream.Base.hLock);
+ rc = pThis->pOps->pfnQueryMaxSize(pThis->Stream.Base.pvThis, pcbMax);
+ RTVfsLockReleaseWrite(pThis->Stream.Base.hLock);
+ }
+ else
+ rc = VERR_WRITE_PROTECT;
+ return rc;
+}
+
+
+RTDECL(uint64_t) RTVfsFileGetOpenFlags(RTVFSFILE hVfsFile)
+{
+ RTVFSFILEINTERNAL *pThis = hVfsFile;
+ AssertPtrReturn(pThis, 0);
+ AssertReturn(pThis->uMagic == RTVFSFILE_MAGIC, 0);
+ return pThis->Stream.fFlags;
+}
+
diff --git a/src/VBox/Runtime/common/vfs/vfschain.cpp b/src/VBox/Runtime/common/vfs/vfschain.cpp
new file mode 100644
index 00000000..e3811970
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfschain.cpp
@@ -0,0 +1,1861 @@
+/* $Id: vfschain.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Chains.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include <iprt/asm.h>
+#include <iprt/critsect.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+
+#include "internal/file.h"
+#include "internal/magics.h"
+//#include "internal/vfs.h"
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static PCRTVFSCHAINELEMENTREG rtVfsChainFindProviderLocked(const char *pszProvider);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Init the critical section once. */
+static RTONCE g_rtVfsChainElementInitOnce = RTONCE_INITIALIZER;
+/** Critical section protecting g_rtVfsChainElementProviderList. */
+static RTCRITSECTRW g_rtVfsChainElementCritSect;
+/** List of VFS chain element providers (RTVFSCHAINELEMENTREG). */
+static RTLISTANCHOR g_rtVfsChainElementProviderList;
+
+
+
+RTDECL(int) RTVfsChainValidateOpenFileOrIoStream(PRTVFSCHAINSPEC pSpec, PRTVFSCHAINELEMSPEC pElement,
+ uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ if (pElement->cArgs < 1)
+ return VERR_VFS_CHAIN_AT_LEAST_ONE_ARG;
+ if (pElement->cArgs > 4)
+ return VERR_VFS_CHAIN_AT_MOST_FOUR_ARGS;
+ if (!*pElement->paArgs[0].psz)
+ return VERR_VFS_CHAIN_EMPTY_ARG;
+
+ /*
+ * Calculate the flags, storing them in the first argument.
+ */
+ const char *pszAccess = pElement->cArgs >= 2 ? pElement->paArgs[1].psz : "";
+ if (!*pszAccess)
+ pszAccess = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READWRITE ? "rw"
+ : (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ ? "r"
+ : (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_WRITE ? "w"
+ : "rw";
+
+ const char *pszDisp = pElement->cArgs >= 3 ? pElement->paArgs[2].psz : "";
+ if (!*pszDisp)
+ pszDisp = strchr(pszAccess, 'w') != NULL ? "open-create" : "open";
+
+ const char *pszSharing = pElement->cArgs >= 4 ? pElement->paArgs[3].psz : "";
+
+ int rc = RTFileModeToFlagsEx(pszAccess, pszDisp, pszSharing, &pElement->uProvider);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ /*
+ * Now try figure out which argument offended us.
+ */
+ AssertReturn(pElement->cArgs > 1, VERR_VFS_CHAIN_IPE);
+ if ( pElement->cArgs == 2
+ || RT_FAILURE(RTFileModeToFlagsEx(pszAccess, "open-create", "", &pElement->uProvider)))
+ {
+ *poffError = pElement->paArgs[1].offSpec;
+ rc = RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected valid access flags: 'r', 'rw', or 'w'");
+ }
+ else if ( pElement->cArgs == 3
+ || RT_FAILURE(RTFileModeToFlagsEx(pszAccess, pszDisp, "", &pElement->uProvider)))
+ {
+ *poffError = pElement->paArgs[2].offSpec;
+ rc = RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT,
+ "Expected valid open disposition: create, create-replace, open, open-create, open-append, open-truncate");
+ }
+ else
+ {
+ *poffError = pElement->paArgs[3].offSpec;
+ rc = RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected valid sharing flags: nr, nw, nrw, d");
+
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainOpen_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basic checks.
+ */
+ if ( pElement->enmType != RTVFSOBJTYPE_DIR
+ && pElement->enmType != RTVFSOBJTYPE_FILE
+ && pElement->enmType != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_ONLY_FILE_OR_IOS_OR_DIR;
+ if ( pElement->enmTypeIn != RTVFSOBJTYPE_DIR
+ && pElement->enmTypeIn != RTVFSOBJTYPE_FS_STREAM
+ && pElement->enmTypeIn != RTVFSOBJTYPE_VFS)
+ {
+ if (pElement->enmTypeIn == RTVFSOBJTYPE_INVALID)
+ {
+ /*
+ * First element: Transform into 'stdfile' or 'stddir' if registered.
+ */
+ const char *pszNewProvider = pElement->enmType == RTVFSOBJTYPE_DIR ? "stddir" : "stdfile";
+ PCRTVFSCHAINELEMENTREG pNewProvider = rtVfsChainFindProviderLocked(pszNewProvider);
+ if (pNewProvider)
+ {
+ pElement->pProvider = pNewProvider;
+ return pNewProvider->pfnValidate(pNewProvider, pSpec, pElement, poffError, pErrInfo);
+ }
+ return VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT;
+ }
+ return VERR_VFS_CHAIN_TAKES_DIR_OR_FSS_OR_VFS;
+ }
+
+ /*
+ * Make common cause with 'stdfile' if we're opening a file or I/O stream.
+ * If the input is a FSS, we have to make sure it's a read-only operation.
+ */
+ if ( pElement->enmType == RTVFSOBJTYPE_FILE
+ || pElement->enmType == RTVFSOBJTYPE_IO_STREAM)
+ {
+ int rc = RTVfsChainValidateOpenFileOrIoStream(pSpec, pElement, poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FS_STREAM)
+ return VINF_SUCCESS;
+ if ( !(pElement->uProvider & RTFILE_O_WRITE)
+ && (pElement->uProvider & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN)
+ return VINF_SUCCESS;
+ *poffError = pElement->cArgs > 1 ? pElement->paArgs[1].offSpec : pElement->offSpec;
+ return VERR_VFS_CHAIN_INVALID_ARGUMENT;
+ }
+ return rc;
+ }
+
+
+ /*
+ * Directory checks. Path argument only, optional. If not given the root directory of a VFS or the
+ */
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+ pElement->uProvider = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainOpen_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ /*
+ * File system stream: Seek thru the stream looking for the object to open.
+ */
+ RTVFSFSSTREAM hVfsFssIn = RTVfsObjToFsStream(hPrevVfsObj);
+ if (hVfsFssIn != NIL_RTVFSFSSTREAM)
+ {
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ /*
+ * VFS: Use RTVfsFileOpen or RTVfsDirOpen.
+ */
+ RTVFS hVfsIn = RTVfsObjToVfs(hPrevVfsObj);
+ if (hVfsIn != NIL_RTVFS)
+ {
+ if ( pElement->enmType == RTVFSOBJTYPE_FILE
+ || pElement->enmType == RTVFSOBJTYPE_IO_STREAM)
+ {
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int rc = RTVfsFileOpen(hVfsIn, pElement->paArgs[0].psz, pElement->uProvider, &hVfsFile);
+ RTVfsRelease(hVfsIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+ }
+ if (pElement->enmType == RTVFSOBJTYPE_DIR)
+ {
+ RTVFSDIR hVfsDir = NIL_RTVFSDIR;
+ int rc = RTVfsDirOpen(hVfsIn, pElement->paArgs[0].psz, (uint32_t)pElement->uProvider, &hVfsDir);
+ RTVfsRelease(hVfsIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+ }
+ RTVfsRelease(hVfsIn);
+ return VERR_VFS_CHAIN_IPE;
+ }
+
+ /*
+ * Directory: Similar to above, just relative to a directory.
+ */
+ RTVFSDIR hVfsDirIn = RTVfsObjToDir(hPrevVfsObj);
+ if (hVfsDirIn != NIL_RTVFSDIR)
+ {
+ if ( pElement->enmType == RTVFSOBJTYPE_FILE
+ || pElement->enmType == RTVFSOBJTYPE_IO_STREAM)
+ {
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ int rc = RTVfsDirOpenFile(hVfsDirIn, pElement->paArgs[0].psz, pElement->uProvider, &hVfsFile);
+ RTVfsDirRelease(hVfsDirIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+ }
+ if (pElement->enmType == RTVFSOBJTYPE_DIR)
+ {
+ RTVFSDIR hVfsDir = NIL_RTVFSDIR;
+ int rc = RTVfsDirOpenDir(hVfsDirIn, pElement->paArgs[0].psz, pElement->uProvider, &hVfsDir);
+ RTVfsDirRelease(hVfsDirIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+ }
+ RTVfsDirRelease(hVfsDirIn);
+ return VERR_VFS_CHAIN_IPE;
+ }
+
+ AssertFailed();
+ return VERR_VFS_CHAIN_CAST_FAILED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainOpen_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement);
+ return false;
+}
+
+
+/** VFS chain element 'gunzip'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainGunzipReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "open",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Generic VFS open, that can open files (or I/O stream) and directories in a VFS, directory or file system stream.\n"
+ "If used as the first element in a chain, it will work like 'stdfile' or 'stddir' and work on the real file system.\n"
+ "First argument is the filename or directory path.\n"
+ "Second argument is access mode, files only, optional: r, w, rw.\n"
+ "Third argument is open disposition, files only, optional: create, create-replace, open, open-create, open-append, open-truncate.\n"
+ "Forth argument is file sharing, files only, optional: nr, nw, nrw, d.",
+ /* pfnValidate = */ rtVfsChainOpen_Validate,
+ /* pfnInstantiate = */ rtVfsChainOpen_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainOpen_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainGunzipReg, rtVfsChainGunzipReg);
+
+
+
+
+/**
+ * Initializes the globals via RTOnce.
+ *
+ * @returns IPRT status code
+ * @param pvUser Unused, ignored.
+ */
+static DECLCALLBACK(int) rtVfsChainElementRegisterInit(void *pvUser)
+{
+ NOREF(pvUser);
+ if (!g_rtVfsChainElementProviderList.pNext)
+ RTListInit(&g_rtVfsChainElementProviderList);
+ int rc = RTCritSectRwInit(&g_rtVfsChainElementCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsChainElementRegisterProvider(PRTVFSCHAINELEMENTREG pRegRec, bool fFromCtor)
+{
+ int rc;
+
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(pRegRec, VERR_INVALID_POINTER);
+ AssertMsgReturn(pRegRec->uVersion == RTVFSCHAINELEMENTREG_VERSION, ("%#x", pRegRec->uVersion), VERR_INVALID_POINTER);
+ AssertMsgReturn(pRegRec->uEndMarker == RTVFSCHAINELEMENTREG_VERSION, ("%#zx", pRegRec->uEndMarker), VERR_INVALID_POINTER);
+ AssertReturn(pRegRec->fReserved == 0, VERR_INVALID_POINTER);
+ AssertPtrReturn(pRegRec->pszName, VERR_INVALID_POINTER);
+ AssertPtrReturn(pRegRec->pfnValidate, VERR_INVALID_POINTER);
+ AssertPtrReturn(pRegRec->pfnInstantiate, VERR_INVALID_POINTER);
+ AssertPtrReturn(pRegRec->pfnCanReuseElement, VERR_INVALID_POINTER);
+
+ /*
+ * Init and take the lock.
+ */
+ if (!fFromCtor)
+ {
+ rc = RTOnce(&g_rtVfsChainElementInitOnce, rtVfsChainElementRegisterInit, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = RTCritSectRwEnterExcl(&g_rtVfsChainElementCritSect);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else if (!g_rtVfsChainElementProviderList.pNext)
+ RTListInit(&g_rtVfsChainElementProviderList);
+
+ /*
+ * Duplicate name?
+ */
+ rc = VINF_SUCCESS;
+ PRTVFSCHAINELEMENTREG pIterator, pIterNext;
+ RTListForEachSafe(&g_rtVfsChainElementProviderList, pIterator, pIterNext, RTVFSCHAINELEMENTREG, ListEntry)
+ {
+ if (!strcmp(pIterator->pszName, pRegRec->pszName))
+ {
+ AssertMsgFailed(("duplicate name '%s' old=%p new=%p\n", pIterator->pszName, pIterator, pRegRec));
+ rc = VERR_ALREADY_EXISTS;
+ break;
+ }
+ }
+
+ /*
+ * If not, append the record to the list.
+ */
+ if (RT_SUCCESS(rc))
+ RTListAppend(&g_rtVfsChainElementProviderList, &pRegRec->ListEntry);
+
+ /*
+ * Leave the lock and return.
+ */
+ if (!fFromCtor)
+ RTCritSectRwLeaveExcl(&g_rtVfsChainElementCritSect);
+ return rc;
+}
+
+
+/**
+ * Allocates and initializes an empty spec
+ *
+ * @returns Pointer to the spec on success, NULL on failure.
+ */
+static PRTVFSCHAINSPEC rtVfsChainSpecAlloc(void)
+{
+ PRTVFSCHAINSPEC pSpec = (PRTVFSCHAINSPEC)RTMemTmpAlloc(sizeof(*pSpec));
+ if (pSpec)
+ {
+ pSpec->fOpenFile = 0;
+ pSpec->fOpenDir = 0;
+ pSpec->cElements = 0;
+ pSpec->paElements = NULL;
+ }
+ return pSpec;
+}
+
+
+/**
+ * Checks if @a ch is a character that can be escaped.
+ *
+ * @returns true / false.
+ * @param ch The character to consider.
+ */
+DECLINLINE(bool) rtVfsChainSpecIsEscapableChar(char ch)
+{
+ return ch == '('
+ || ch == ')'
+ || ch == '{'
+ || ch == '}'
+ || ch == '\\'
+ || ch == ','
+ || ch == '|'
+ || ch == ':';
+}
+
+
+/**
+ * Duplicate a spec string after unescaping it.
+ *
+ * This differs from RTStrDupN in that it uses RTMemTmpAlloc instead of
+ * RTMemAlloc.
+ *
+ * @returns String copy on success, NULL on failure.
+ * @param psz The string to duplicate.
+ * @param cch The number of bytes to duplicate.
+ * @param prc The status code variable to set on failure. (Leeps the
+ * code shorter. -lazy bird)
+ */
+DECLINLINE(char *) rtVfsChainSpecDupStrN(const char *psz, size_t cch, int *prc)
+{
+ char *pszCopy = (char *)RTMemTmpAlloc(cch + 1);
+ if (pszCopy)
+ {
+ if (!memchr(psz, '\\', cch))
+ {
+ /* Plain string, copy it raw. */
+ memcpy(pszCopy, psz, cch);
+ pszCopy[cch] = '\0';
+ }
+ else
+ {
+ /* Has escape sequences, must unescape it. */
+ char *pszDst = pszCopy;
+ while (cch-- > 0)
+ {
+ char ch = *psz++;
+ if (ch == '\\' && cch > 0)
+ {
+ char ch2 = *psz;
+ if (rtVfsChainSpecIsEscapableChar(ch2))
+ {
+ psz++;
+ cch--;
+ ch = ch2;
+ }
+ }
+ *pszDst++ = ch;
+ }
+ *pszDst = '\0';
+ }
+ }
+ else
+ *prc = VERR_NO_TMP_MEMORY;
+ return pszCopy;
+}
+
+
+/**
+ * Adds an empty element to the chain specification.
+ *
+ * The caller is responsible for filling it the element attributes.
+ *
+ * @returns Pointer to the new element on success, NULL on failure. The
+ * pointer is only valid till the next call to this function.
+ * @param pSpec The chain specification.
+ * @param prc The status code variable to set on failure. (Leeps the
+ * code shorter. -lazy bird)
+ */
+static PRTVFSCHAINELEMSPEC rtVfsChainSpecAddElement(PRTVFSCHAINSPEC pSpec, uint16_t offSpec, int *prc)
+{
+ AssertPtr(pSpec);
+
+ /*
+ * Resize the element table if necessary.
+ */
+ uint32_t const iElement = pSpec->cElements;
+ if ((iElement % 32) == 0)
+ {
+ PRTVFSCHAINELEMSPEC paNew = (PRTVFSCHAINELEMSPEC)RTMemTmpAlloc((iElement + 32) * sizeof(paNew[0]));
+ if (!paNew)
+ {
+ *prc = VERR_NO_TMP_MEMORY;
+ return NULL;
+ }
+
+ if (iElement)
+ memcpy(paNew, pSpec->paElements, iElement * sizeof(paNew[0]));
+ RTMemTmpFree(pSpec->paElements);
+ pSpec->paElements = paNew;
+ }
+
+ /*
+ * Initialize and add the new element.
+ */
+ PRTVFSCHAINELEMSPEC pElement = &pSpec->paElements[iElement];
+ pElement->pszProvider = NULL;
+ pElement->enmTypeIn = iElement ? pSpec->paElements[iElement - 1].enmType : RTVFSOBJTYPE_INVALID;
+ pElement->enmType = RTVFSOBJTYPE_INVALID;
+ pElement->offSpec = offSpec;
+ pElement->cchSpec = 0;
+ pElement->cArgs = 0;
+ pElement->paArgs = NULL;
+ pElement->pProvider = NULL;
+ pElement->hVfsObj = NIL_RTVFSOBJ;
+
+ pSpec->cElements = iElement + 1;
+ return pElement;
+}
+
+
+/**
+ * Adds an argument to the element spec.
+ *
+ * @returns IPRT status code.
+ * @param pElement The element.
+ * @param psz The start of the argument string.
+ * @param cch The length of the argument string, escape
+ * sequences counted twice.
+ */
+static int rtVfsChainSpecElementAddArg(PRTVFSCHAINELEMSPEC pElement, const char *psz, size_t cch, uint16_t offSpec)
+{
+ uint32_t iArg = pElement->cArgs;
+ if ((iArg % 32) == 0)
+ {
+ PRTVFSCHAINELEMENTARG paNew = (PRTVFSCHAINELEMENTARG)RTMemTmpAlloc((iArg + 32) * sizeof(paNew[0]));
+ if (!paNew)
+ return VERR_NO_TMP_MEMORY;
+ if (iArg)
+ memcpy(paNew, pElement->paArgs, iArg * sizeof(paNew[0]));
+ RTMemTmpFree(pElement->paArgs);
+ pElement->paArgs = paNew;
+ }
+
+ int rc = VINF_SUCCESS;
+ pElement->paArgs[iArg].psz = rtVfsChainSpecDupStrN(psz, cch, &rc);
+ pElement->paArgs[iArg].offSpec = offSpec;
+ pElement->cArgs = iArg + 1;
+ return rc;
+}
+
+
+RTDECL(void) RTVfsChainSpecFree(PRTVFSCHAINSPEC pSpec)
+{
+ if (!pSpec)
+ return;
+
+ uint32_t i = pSpec->cElements;
+ while (i-- > 0)
+ {
+ uint32_t iArg = pSpec->paElements[i].cArgs;
+ while (iArg-- > 0)
+ RTMemTmpFree(pSpec->paElements[i].paArgs[iArg].psz);
+ RTMemTmpFree(pSpec->paElements[i].paArgs);
+ RTMemTmpFree(pSpec->paElements[i].pszProvider);
+ if (pSpec->paElements[i].hVfsObj != NIL_RTVFSOBJ)
+ {
+ RTVfsObjRelease(pSpec->paElements[i].hVfsObj);
+ pSpec->paElements[i].hVfsObj = NIL_RTVFSOBJ;
+ }
+ }
+
+ RTMemTmpFree(pSpec->paElements);
+ pSpec->paElements = NULL;
+ RTMemTmpFree(pSpec);
+}
+
+
+/**
+ * Checks if @a psz is pointing to the final element specification.
+ *
+ * @returns true / false.
+ * @param psz Start of an element or path.
+ * @param pcch Where to return the length.
+ */
+static bool rtVfsChainSpecIsFinalElement(const char *psz, size_t *pcch)
+{
+ size_t off = 0;
+ char ch;
+ while ((ch = psz[off]) != '\0')
+ {
+ if (ch == '|' || ch == ':')
+ return false;
+ if ( ch == '\\'
+ && rtVfsChainSpecIsEscapableChar(psz[off + 1]))
+ off++;
+ off++;
+ }
+ *pcch = off;
+ return off > 0;
+}
+
+
+/**
+ * Makes the final path element.
+ * @returns IPRT status code
+ * @param pElement The element.
+ * @param pszPath The path.
+ * @param cchPath The path length.
+ */
+static int rtVfsChainSpecMakeFinalPathElement(PRTVFSCHAINELEMSPEC pElement, const char *pszPath, size_t cchPath)
+{
+ pElement->pszProvider = NULL;
+ pElement->enmType = RTVFSOBJTYPE_END;
+ pElement->cchSpec = (uint16_t)cchPath;
+ return rtVfsChainSpecElementAddArg(pElement, pszPath, cchPath, pElement->offSpec);
+}
+
+
+/**
+ * Finds the end of the argument string.
+ *
+ * @returns The offset of the end character relative to @a psz.
+ * @param psz The argument string.
+ * @param chCloseParen The closing parenthesis.
+ */
+static size_t rtVfsChainSpecFindArgEnd(const char *psz, char const chCloseParen)
+{
+ size_t off = 0;
+ char ch;
+ while ( (ch = psz[off]) != '\0'
+ && ch != ','
+ && ch != chCloseParen)
+ {
+ if ( ch == '\\'
+ && rtVfsChainSpecIsEscapableChar(psz[off+1]))
+ off++;
+ off++;
+ }
+ return off;
+}
+
+
+RTDECL(int) RTVfsChainSpecParse(const char *pszSpec, uint32_t fFlags, RTVFSOBJTYPE enmDesiredType,
+ PRTVFSCHAINSPEC *ppSpec, uint32_t *poffError)
+{
+ if (poffError)
+ {
+ AssertPtrReturn(poffError, VERR_INVALID_POINTER);
+ *poffError = 0;
+ }
+ AssertPtrReturn(ppSpec, VERR_INVALID_POINTER);
+ *ppSpec = NULL;
+ AssertPtrReturn(pszSpec, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTVFSCHAIN_PF_VALID_MASK), VERR_INVALID_PARAMETER);
+ AssertReturn(enmDesiredType > RTVFSOBJTYPE_INVALID && enmDesiredType < RTVFSOBJTYPE_END, VERR_INVALID_PARAMETER);
+
+ /*
+ * Check the start of the specification and allocate an empty return spec.
+ */
+ if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1))
+ return VERR_VFS_CHAIN_NO_PREFIX;
+ const char *pszSrc = RTStrStripL(pszSpec + sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1);
+ if (!*pszSrc)
+ return VERR_VFS_CHAIN_EMPTY;
+
+ PRTVFSCHAINSPEC pSpec = rtVfsChainSpecAlloc();
+ if (!pSpec)
+ return VERR_NO_TMP_MEMORY;
+ pSpec->enmDesiredType = enmDesiredType;
+
+ /*
+ * Parse the spec one element at a time.
+ */
+ int rc = VINF_SUCCESS;
+ while (*pszSrc && RT_SUCCESS(rc))
+ {
+ /*
+ * Digest element separator, except for the first element.
+ */
+ if (*pszSrc == '|' || *pszSrc == ':')
+ {
+ if (pSpec->cElements != 0)
+ pszSrc = RTStrStripL(pszSrc + 1);
+ else
+ {
+ rc = VERR_VFS_CHAIN_LEADING_SEPARATOR;
+ break;
+ }
+ }
+ else if (pSpec->cElements != 0)
+ {
+ rc = VERR_VFS_CHAIN_EXPECTED_SEPARATOR;
+ break;
+ }
+
+ /*
+ * Ok, there should be an element here so add one to the return struct.
+ */
+ PRTVFSCHAINELEMSPEC pElement = rtVfsChainSpecAddElement(pSpec, (uint16_t)(pszSrc - pszSpec), &rc);
+ if (!pElement)
+ break;
+
+ /*
+ * First up is the VFS object type followed by a parenthesis/curly, or
+ * this could be the trailing action. Alternatively, we could have a
+ * final path-only element here.
+ */
+ size_t cch;
+ if (strncmp(pszSrc, "base", cch = 4) == 0)
+ pElement->enmType = RTVFSOBJTYPE_BASE;
+ else if (strncmp(pszSrc, "vfs", cch = 3) == 0)
+ pElement->enmType = RTVFSOBJTYPE_VFS;
+ else if (strncmp(pszSrc, "fss", cch = 3) == 0)
+ pElement->enmType = RTVFSOBJTYPE_FS_STREAM;
+ else if (strncmp(pszSrc, "ios", cch = 3) == 0)
+ pElement->enmType = RTVFSOBJTYPE_IO_STREAM;
+ else if (strncmp(pszSrc, "dir", cch = 3) == 0)
+ pElement->enmType = RTVFSOBJTYPE_DIR;
+ else if (strncmp(pszSrc, "file", cch = 4) == 0)
+ pElement->enmType = RTVFSOBJTYPE_FILE;
+ else if (strncmp(pszSrc, "sym", cch = 3) == 0)
+ pElement->enmType = RTVFSOBJTYPE_SYMLINK;
+ else
+ {
+ if (rtVfsChainSpecIsFinalElement(pszSrc, &cch))
+ rc = rtVfsChainSpecMakeFinalPathElement(pElement, pszSrc, cch);
+ else if (*pszSrc == '\0')
+ rc = VERR_VFS_CHAIN_TRAILING_SEPARATOR;
+ else
+ rc = VERR_VFS_CHAIN_UNKNOWN_TYPE;
+ break;
+ }
+
+ /* Check and skip past the parenthesis/curly. If not there, we might
+ have a final path element at our hands. */
+ char const chOpenParen = pszSrc[cch];
+ if (chOpenParen != '(' && chOpenParen != '{')
+ {
+ if (rtVfsChainSpecIsFinalElement(pszSrc, &cch))
+ rc = rtVfsChainSpecMakeFinalPathElement(pElement, pszSrc, cch);
+ else
+ rc = VERR_VFS_CHAIN_EXPECTED_LEFT_PARENTHESES;
+ break;
+ }
+ char const chCloseParen = (chOpenParen == '(' ? ')' : '}');
+ pszSrc = RTStrStripL(pszSrc + cch + 1);
+
+ /*
+ * The name of the element provider.
+ */
+ cch = rtVfsChainSpecFindArgEnd(pszSrc, chCloseParen);
+ if (!cch)
+ {
+ rc = VERR_VFS_CHAIN_EXPECTED_PROVIDER_NAME;
+ break;
+ }
+ pElement->pszProvider = rtVfsChainSpecDupStrN(pszSrc, cch, &rc);
+ if (!pElement->pszProvider)
+ break;
+ pszSrc += cch;
+
+ /*
+ * The arguments.
+ */
+ while (*pszSrc == ',')
+ {
+ pszSrc = RTStrStripL(pszSrc + 1);
+ cch = rtVfsChainSpecFindArgEnd(pszSrc, chCloseParen);
+ rc = rtVfsChainSpecElementAddArg(pElement, pszSrc, cch, (uint16_t)(pszSrc - pszSpec));
+ if (RT_FAILURE(rc))
+ break;
+ pszSrc += cch;
+ }
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Must end with a right parentheses/curly. */
+ if (*pszSrc != chCloseParen)
+ {
+ rc = VERR_VFS_CHAIN_EXPECTED_RIGHT_PARENTHESES;
+ break;
+ }
+ pElement->cchSpec = (uint16_t)(pszSrc - pszSpec) - pElement->offSpec + 1;
+
+ pszSrc = RTStrStripL(pszSrc + 1);
+ }
+
+#if 0
+ /*
+ * Dump the chain. Useful for debugging the above code.
+ */
+ RTAssertMsg2("dbg: cElements=%d rc=%Rrc\n", pSpec->cElements, rc);
+ for (uint32_t i = 0; i < pSpec->cElements; i++)
+ {
+ uint32_t const cArgs = pSpec->paElements[i].cArgs;
+ RTAssertMsg2("dbg: #%u: enmTypeIn=%d enmType=%d cArgs=%d",
+ i, pSpec->paElements[i].enmTypeIn, pSpec->paElements[i].enmType, cArgs);
+ for (uint32_t j = 0; j < cArgs; j++)
+ RTAssertMsg2(j == 0 ? (cArgs > 1 ? " [%s" : " [%s]") : j + 1 < cArgs ? ", %s" : ", %s]",
+ pSpec->paElements[i].paArgs[j].psz);
+ RTAssertMsg2(" offSpec=%d cchSpec=%d", pSpec->paElements[i].offSpec, pSpec->paElements[i].cchSpec);
+ RTAssertMsg2(" spec: %.*s\n", pSpec->paElements[i].cchSpec, &pszSpec[pSpec->paElements[i].offSpec]);
+ }
+#endif
+
+ /*
+ * Return the chain on success; Cleanup and set the error indicator on
+ * failure.
+ */
+ if (RT_SUCCESS(rc))
+ *ppSpec = pSpec;
+ else
+ {
+ if (poffError)
+ *poffError = (uint32_t)(pszSrc - pszSpec);
+ RTVfsChainSpecFree(pSpec);
+ }
+ return rc;
+}
+
+
+/**
+ * Looks up @a pszProvider among the registered providers.
+ *
+ * @returns Pointer to registration record if found, NULL if not.
+ * @param pszProvider The provider.
+ */
+static PCRTVFSCHAINELEMENTREG rtVfsChainFindProviderLocked(const char *pszProvider)
+{
+ PCRTVFSCHAINELEMENTREG pIterator;
+ RTListForEach(&g_rtVfsChainElementProviderList, pIterator, RTVFSCHAINELEMENTREG, ListEntry)
+ {
+ if (strcmp(pIterator->pszName, pszProvider) == 0)
+ return pIterator;
+ }
+ return NULL;
+}
+
+
+/**
+ * Does reusable object type matching.
+ *
+ * @returns true if the types matches, false if not.
+ * @param pElement The target element specification.
+ * @param pReuseElement The source element specification.
+ */
+static bool rtVfsChainMatchReusableType(PRTVFSCHAINELEMSPEC pElement, PRTVFSCHAINELEMSPEC pReuseElement)
+{
+ if (pElement->enmType == pReuseElement->enmType)
+ return true;
+
+ /* File objects can always be cast to I/O streams. */
+ if ( pElement->enmType == RTVFSOBJTYPE_IO_STREAM
+ && pReuseElement->enmType == RTVFSOBJTYPE_FILE)
+ return true;
+
+ /* I/O stream objects may be file objects. */
+ if ( pElement->enmType == RTVFSOBJTYPE_FILE
+ && pReuseElement->enmType == RTVFSOBJTYPE_IO_STREAM)
+ {
+ RTVFSFILE hVfsFile = RTVfsObjToFile(pReuseElement->hVfsObj);
+ if (hVfsFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(hVfsFile);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+RTDECL(int) RTVfsChainSpecCheckAndSetup(PRTVFSCHAINSPEC pSpec, PCRTVFSCHAINSPEC pReuseSpec,
+ PRTVFSOBJ phVfsObj, const char **ppszFinalPath, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ AssertPtrReturn(poffError, VERR_INVALID_POINTER);
+ *poffError = 0;
+ AssertPtrReturn(phVfsObj, VERR_INVALID_POINTER);
+ *phVfsObj = NIL_RTVFSOBJ;
+ AssertPtrReturn(ppszFinalPath, VERR_INVALID_POINTER);
+ *ppszFinalPath = NULL;
+ AssertPtrReturn(pSpec, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER);
+
+ /*
+ * Check for final path-only component as we will not touch it yet.
+ */
+ uint32_t cElements = pSpec->cElements;
+ if (cElements > 0)
+ {
+ if (pSpec->paElements[pSpec->cElements - 1].enmType == RTVFSOBJTYPE_END)
+ {
+ if (cElements > 1)
+ cElements--;
+ else
+ {
+ *ppszFinalPath = pSpec->paElements[0].paArgs[0].psz;
+ return VERR_VFS_CHAIN_PATH_ONLY;
+ }
+ }
+ }
+ else
+ return VERR_VFS_CHAIN_EMPTY;
+
+ /*
+ * Enter the critical section after making sure it has been initialized.
+ */
+ int rc = RTOnce(&g_rtVfsChainElementInitOnce, rtVfsChainElementRegisterInit, NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectRwEnterShared(&g_rtVfsChainElementCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Resolve and check each element first.
+ */
+ for (uint32_t i = 0; i < cElements; i++)
+ {
+ PRTVFSCHAINELEMSPEC const pElement = &pSpec->paElements[i];
+ *poffError = pElement->offSpec;
+ pElement->pProvider = rtVfsChainFindProviderLocked(pElement->pszProvider);
+ if (pElement->pProvider)
+ {
+ rc = pElement->pProvider->pfnValidate(pElement->pProvider, pSpec, pElement, poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ continue;
+ }
+ else
+ rc = VERR_VFS_CHAIN_PROVIDER_NOT_FOUND;
+ break;
+ }
+
+ /*
+ * Check that the desired type is compatible with the last element.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ PRTVFSCHAINELEMSPEC const pLast = &pSpec->paElements[cElements - 1];
+ if (cElements == pSpec->cElements)
+ {
+ if ( pLast->enmType == pSpec->enmDesiredType
+ || pSpec->enmDesiredType == RTVFSOBJTYPE_BASE
+ || ( pLast->enmType == RTVFSOBJTYPE_FILE
+ && pSpec->enmDesiredType == RTVFSOBJTYPE_IO_STREAM) )
+ rc = VINF_SUCCESS;
+ else
+ {
+ *poffError = pLast->offSpec;
+ rc = VERR_VFS_CHAIN_FINAL_TYPE_MISMATCH;
+ }
+ }
+ /* Ends with a path-only element, so check the type of the element preceding it. */
+ else if ( pLast->enmType == RTVFSOBJTYPE_DIR
+ || pLast->enmType == RTVFSOBJTYPE_VFS
+ || pLast->enmType == RTVFSOBJTYPE_FS_STREAM)
+ rc = VINF_SUCCESS;
+ else
+ {
+ *poffError = pLast->offSpec;
+ rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Try construct the chain.
+ */
+ RTVFSOBJ hPrevVfsObj = NIL_RTVFSOBJ; /* No extra reference, kept in chain structure. */
+ for (uint32_t i = 0; i < cElements; i++)
+ {
+ PRTVFSCHAINELEMSPEC const pElement = &pSpec->paElements[i];
+ *poffError = pElement->offSpec;
+
+ /*
+ * Try reuse the VFS objects at the start of the passed in reuse chain.
+ */
+ if (!pReuseSpec)
+ { /* likely */ }
+ else
+ {
+ if (i < pReuseSpec->cElements)
+ {
+ PRTVFSCHAINELEMSPEC const pReuseElement = &pReuseSpec->paElements[i];
+ if (pReuseElement->hVfsObj != NIL_RTVFSOBJ)
+ {
+ if (strcmp(pElement->pszProvider, pReuseElement->pszProvider) == 0)
+ {
+ if (rtVfsChainMatchReusableType(pElement, pReuseElement))
+ {
+ if (pElement->pProvider->pfnCanReuseElement(pElement->pProvider, pSpec, pElement,
+ pReuseSpec, pReuseElement))
+ {
+ uint32_t cRefs = RTVfsObjRetain(pReuseElement->hVfsObj);
+ if (cRefs != UINT32_MAX)
+ {
+ pElement->hVfsObj = hPrevVfsObj = pReuseElement->hVfsObj;
+ continue;
+ }
+ }
+ }
+ }
+ }
+ }
+ pReuseSpec = NULL;
+ }
+
+ /*
+ * Instantiate a new VFS object.
+ */
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ rc = pElement->pProvider->pfnInstantiate(pElement->pProvider, pSpec, pElement, hPrevVfsObj,
+ &hVfsObj, poffError, pErrInfo);
+ if (RT_FAILURE(rc))
+ break;
+ pElement->hVfsObj = hVfsObj;
+ hPrevVfsObj = hVfsObj;
+ }
+
+ /*
+ * Add another reference to the final object and return.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cRefs = RTVfsObjRetain(hPrevVfsObj);
+ AssertStmt(cRefs != UINT32_MAX, rc = VERR_VFS_CHAIN_IPE);
+ *phVfsObj = hPrevVfsObj;
+ *ppszFinalPath = cElements == pSpec->cElements ? NULL : pSpec->paElements[cElements].paArgs[0].psz;
+ }
+ }
+
+ int rc2 = RTCritSectRwLeaveShared(&g_rtVfsChainElementCritSect);
+ if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsChainElementDeregisterProvider(PRTVFSCHAINELEMENTREG pRegRec, bool fFromDtor)
+{
+ /*
+ * Fend off wildlife.
+ */
+ if (pRegRec == NULL)
+ return VINF_SUCCESS;
+ AssertPtrReturn(pRegRec, VERR_INVALID_POINTER);
+ AssertMsgReturn(pRegRec->uVersion == RTVFSCHAINELEMENTREG_VERSION, ("%#x", pRegRec->uVersion), VERR_INVALID_POINTER);
+ AssertMsgReturn(pRegRec->uEndMarker == RTVFSCHAINELEMENTREG_VERSION, ("%#zx", pRegRec->uEndMarker), VERR_INVALID_POINTER);
+ AssertPtrReturn(pRegRec->pszName, VERR_INVALID_POINTER);
+
+ /*
+ * Take the lock if that's safe.
+ */
+ if (!fFromDtor)
+ RTCritSectRwEnterExcl(&g_rtVfsChainElementCritSect);
+ else if (!g_rtVfsChainElementProviderList.pNext)
+ RTListInit(&g_rtVfsChainElementProviderList);
+
+ /*
+ * Ok, remove it.
+ */
+ int rc = VERR_NOT_FOUND;
+ PRTVFSCHAINELEMENTREG pIterator, pIterNext;
+ RTListForEachSafe(&g_rtVfsChainElementProviderList, pIterator, pIterNext, RTVFSCHAINELEMENTREG, ListEntry)
+ {
+ if (pIterator == pRegRec)
+ {
+ RTListNodeRemove(&pRegRec->ListEntry);
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+
+ /*
+ * Leave the lock and return.
+ */
+ if (!fFromDtor)
+ RTCritSectRwLeaveExcl(&g_rtVfsChainElementCritSect);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsChainOpenObj(const char *pszSpec, uint64_t fFileOpen, uint32_t fObjFlags,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ /*
+ * Validate input.
+ */
+ uint32_t offErrorIgn;
+ if (!poffError)
+ poffError = &offErrorIgn;
+ *poffError = 0;
+ AssertPtrReturn(pszSpec, VERR_INVALID_POINTER);
+ AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phVfsObj, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER);
+
+ int rc = rtFileRecalcAndValidateFlags(&fFileOpen);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertMsgReturn( RTPATH_F_IS_VALID(fObjFlags, RTVFSOBJ_F_VALID_MASK)
+ && (fObjFlags & RTVFSOBJ_F_CREATE_MASK) <= RTVFSOBJ_F_CREATE_DIRECTORY,
+ ("fObjFlags=%#x\n", fObjFlags),
+ VERR_INVALID_FLAGS);
+
+ /*
+ * Try for a VFS chain first, falling back on regular file system stuff if it's just a path.
+ */
+ PRTVFSCHAINSPEC pSpec = NULL;
+ if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0)
+ {
+ rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_DIR, &pSpec, poffError);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Assert(pSpec->cElements > 0);
+ if ( pSpec->cElements > 1
+ || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END)
+ {
+ const char *pszFinal = NULL;
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ pSpec->fOpenFile = fFileOpen;
+ rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszFinal)
+ {
+ *phVfsObj = hVfsObj;
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ /*
+ * Do a file open with the final path on the returned object.
+ */
+ RTVFS hVfs = RTVfsObjToVfs(hVfsObj);
+ RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj);
+ RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj);
+ if (hVfs != NIL_RTVFS)
+ rc = RTVfsObjOpen(hVfs, pszFinal, fFileOpen, fObjFlags, phVfsObj);
+ else if (hVfsDir != NIL_RTVFSDIR)
+ rc = RTVfsDirOpenObj(hVfsDir, pszFinal, fFileOpen, fObjFlags, phVfsObj);
+ else if (hVfsFss != NIL_RTVFSFSSTREAM)
+ rc = VERR_NOT_IMPLEMENTED;
+ else
+ rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY;
+ RTVfsRelease(hVfs);
+ RTVfsDirRelease(hVfsDir);
+ RTVfsFsStrmRelease(hVfsFss);
+ RTVfsObjRelease(hVfsObj);
+ }
+ }
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+ }
+
+ /* Only a path element. */
+ pszSpec = pSpec->paElements[0].paArgs[0].psz;
+ }
+
+ /*
+ * Path to regular file system.
+ * Go via the directory VFS wrapper to avoid duplicating code.
+ */
+ RTVFSDIR hVfsParentDir = NIL_RTVFSDIR;
+ const char *pszFilename;
+ if (RTPathHasPath(pszSpec))
+ {
+ char *pszCopy = RTStrDup(pszSpec);
+ if (pszCopy)
+ {
+ RTPathStripFilename(pszCopy);
+ rc = RTVfsDirOpenNormal(pszCopy, 0 /*fOpen*/, &hVfsParentDir);
+ RTStrFree(pszCopy);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ pszFilename = RTPathFilename(pszSpec);
+ }
+ else
+ {
+ pszFilename = pszSpec;
+ rc = RTVfsDirOpenNormal(".", 0 /*fOpen*/, &hVfsParentDir);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsDirOpenObj(hVfsParentDir, pszFilename, fFileOpen, fObjFlags, phVfsObj);
+ RTVfsDirRelease(hVfsParentDir);
+ }
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsChainOpenDir(const char *pszSpec, uint32_t fOpen,
+ PRTVFSDIR phVfsDir, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ uint32_t offErrorIgn;
+ if (!poffError)
+ poffError = &offErrorIgn;
+ *poffError = 0;
+ AssertPtrReturn(pszSpec, VERR_INVALID_POINTER);
+ AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phVfsDir, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER);
+
+ /*
+ * Try for a VFS chain first, falling back on regular file system stuff if it's just a path.
+ */
+ int rc;
+ PRTVFSCHAINSPEC pSpec = NULL;
+ if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0)
+ {
+ rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_DIR, &pSpec, poffError);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Assert(pSpec->cElements > 0);
+ if ( pSpec->cElements > 1
+ || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END)
+ {
+ const char *pszFinal = NULL;
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ pSpec->fOpenFile = RTFILE_O_READ;
+ rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszFinal)
+ {
+ /* Try convert it to a directory object and we're done. */
+ *phVfsDir = RTVfsObjToDir(hVfsObj);
+ if (*phVfsDir)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ else
+ {
+ /*
+ * Do a file open with the final path on the returned object.
+ */
+ RTVFS hVfs = RTVfsObjToVfs(hVfsObj);
+ RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj);
+ RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj);
+ if (hVfs != NIL_RTVFS)
+ rc = RTVfsDirOpen(hVfs, pszFinal, fOpen, phVfsDir);
+ else if (hVfsDir != NIL_RTVFSDIR)
+ rc = RTVfsDirOpenDir(hVfsDir, pszFinal, fOpen, phVfsDir);
+ else if (hVfsFss != NIL_RTVFSFSSTREAM)
+ rc = VERR_NOT_IMPLEMENTED;
+ else
+ rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY;
+ RTVfsRelease(hVfs);
+ RTVfsDirRelease(hVfsDir);
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+ RTVfsObjRelease(hVfsObj);
+ }
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+ }
+
+ /* Only a path element. */
+ pszSpec = pSpec->paElements[0].paArgs[0].psz;
+ }
+
+ /*
+ * Path to regular file system.
+ */
+ rc = RTVfsDirOpenNormal(pszSpec, fOpen, phVfsDir);
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsChainOpenParentDir(const char *pszSpec, uint32_t fOpen, PRTVFSDIR phVfsDir, const char **ppszChild,
+ uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ uint32_t offErrorIgn;
+ if (!poffError)
+ poffError = &offErrorIgn;
+ *poffError = 0;
+ AssertPtrReturn(pszSpec, VERR_INVALID_POINTER);
+ AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phVfsDir, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppszChild, VERR_INVALID_POINTER);
+ *ppszChild = NULL;
+ AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER);
+
+ /*
+ * Process the spec from the end, trying to find the child part of it.
+ * We cannot use RTPathFilename here because we must ignore trailing slashes.
+ */
+ const char * const pszEnd = RTStrEnd(pszSpec, RTSTR_MAX);
+ const char *pszChild = pszEnd;
+ while ( pszChild != pszSpec
+ && RTPATH_IS_SLASH(pszChild[-1]))
+ pszChild--;
+ while ( pszChild != pszSpec
+ && !RTPATH_IS_SLASH(pszChild[-1])
+ && !RTPATH_IS_VOLSEP(pszChild[-1]))
+ pszChild--;
+ size_t const cchChild = pszEnd - pszChild;
+ *ppszChild = pszChild;
+
+ /*
+ * Try for a VFS chain first, falling back on regular file system stuff if it's just a path.
+ */
+ int rc;
+ PRTVFSCHAINSPEC pSpec = NULL;
+ if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0)
+ {
+ rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_DIR, &pSpec, poffError);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Assert(pSpec->cElements > 0);
+ if ( pSpec->cElements > 1
+ || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END)
+ {
+ /*
+ * Check that it ends with a path-only element and that this in turn ends with
+ * what pszChild points to. (We cannot easiy figure out the parent part of
+ * an element that isn't path-only, so we don't bother trying try.)
+ */
+ PRTVFSCHAINELEMSPEC pLast = &pSpec->paElements[pSpec->cElements - 1];
+ if (pLast->pszProvider == NULL)
+ {
+ size_t cchFinal = strlen(pLast->paArgs[0].psz);
+ if ( cchFinal >= cchChild
+ && memcmp(&pLast->paArgs[0].psz[cchFinal - cchChild], pszChild, cchChild + 1) == 0)
+ {
+ /*
+ * Drop the child part so we have a path to the parent, then setup the chain.
+ */
+ if (cchFinal > cchChild)
+ pLast->paArgs[0].psz[cchFinal - cchChild] = '\0';
+ else
+ pSpec->cElements--;
+
+ const char *pszFinal = NULL;
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ pSpec->fOpenFile = fOpen;
+ rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszFinal)
+ {
+ Assert(cchFinal == cchChild);
+
+ /* Try convert it to a file object and we're done. */
+ *phVfsDir = RTVfsObjToDir(hVfsObj);
+ if (*phVfsDir)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ else
+ {
+ /*
+ * Do a file open with the final path on the returned object.
+ */
+ RTVFS hVfs = RTVfsObjToVfs(hVfsObj);
+ RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj);
+ RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj);
+ if (hVfs != NIL_RTVFS)
+ rc = RTVfsDirOpen(hVfs, pszFinal, fOpen, phVfsDir);
+ else if (hVfsDir != NIL_RTVFSDIR)
+ rc = RTVfsDirOpenDir(hVfsDir, pszFinal, fOpen, phVfsDir);
+ else if (hVfsFss != NIL_RTVFSFSSTREAM)
+ rc = VERR_NOT_IMPLEMENTED;
+ else
+ rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY;
+ RTVfsRelease(hVfs);
+ RTVfsDirRelease(hVfsDir);
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+ RTVfsObjRelease(hVfsObj);
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_TOO_SHORT_FOR_PARENT;
+ }
+ else
+ rc = VERR_VFS_CHAIN_NOT_PATH_ONLY;
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+ }
+
+ /* Only a path element. */
+ pszSpec = pSpec->paElements[0].paArgs[0].psz;
+ }
+
+ /*
+ * Path to regular file system.
+ */
+ if (RTPathHasPath(pszSpec))
+ {
+ char *pszCopy = RTStrDup(pszSpec);
+ if (pszCopy)
+ {
+ RTPathStripFilename(pszCopy);
+ rc = RTVfsDirOpenNormal(pszCopy, fOpen, phVfsDir);
+ RTStrFree(pszCopy);
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ rc = RTVfsDirOpenNormal(".", fOpen, phVfsDir);
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+
+}
+
+
+RTDECL(int) RTVfsChainOpenFile(const char *pszSpec, uint64_t fOpen,
+ PRTVFSFILE phVfsFile, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ uint32_t offErrorIgn;
+ if (!poffError)
+ poffError = &offErrorIgn;
+ *poffError = 0;
+ AssertPtrReturn(pszSpec, VERR_INVALID_POINTER);
+ AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER);
+
+ /*
+ * Try for a VFS chain first, falling back on regular file system stuff if it's just a path.
+ */
+ int rc;
+ PRTVFSCHAINSPEC pSpec = NULL;
+ if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0)
+ {
+ rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_FILE, &pSpec, poffError);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Assert(pSpec->cElements > 0);
+ if ( pSpec->cElements > 1
+ || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END)
+ {
+ const char *pszFinal = NULL;
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ pSpec->fOpenFile = fOpen;
+ rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszFinal)
+ {
+ /* Try convert it to a file object and we're done. */
+ *phVfsFile = RTVfsObjToFile(hVfsObj);
+ if (*phVfsFile)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ else
+ {
+ /*
+ * Do a file open with the final path on the returned object.
+ */
+ RTVFS hVfs = RTVfsObjToVfs(hVfsObj);
+ RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj);
+ RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj);
+ if (hVfs != NIL_RTVFS)
+ rc = RTVfsFileOpen(hVfs, pszFinal, fOpen, phVfsFile);
+ else if (hVfsDir != NIL_RTVFSDIR)
+ rc = RTVfsDirOpenFile(hVfsDir, pszFinal, fOpen, phVfsFile);
+ else if (hVfsFss != NIL_RTVFSFSSTREAM)
+ rc = VERR_NOT_IMPLEMENTED;
+ else
+ rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY;
+ RTVfsRelease(hVfs);
+ RTVfsDirRelease(hVfsDir);
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+ RTVfsObjRelease(hVfsObj);
+ }
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+ }
+
+ /* Only a path element. */
+ pszSpec = pSpec->paElements[0].paArgs[0].psz;
+ }
+
+ /*
+ * Path to regular file system.
+ */
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszSpec, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTVfsFileFromRTFile(hFile, fOpen, false /*fLeaveOpen*/, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ *phVfsFile = hVfsFile;
+ else
+ RTFileClose(hFile);
+ }
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsChainOpenIoStream(const char *pszSpec, uint64_t fOpen,
+ PRTVFSIOSTREAM phVfsIos, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ uint32_t offErrorIgn;
+ if (!poffError)
+ poffError = &offErrorIgn;
+ *poffError = 0;
+ AssertPtrReturn(pszSpec, VERR_INVALID_POINTER);
+ AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phVfsIos, VERR_INVALID_POINTER);
+ AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER);
+
+ /*
+ * Try for a VFS chain first, falling back on regular file system stuff if it's just a path.
+ */
+ int rc;
+ PRTVFSCHAINSPEC pSpec = NULL;
+ if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0)
+ {
+ rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_IO_STREAM, &pSpec, poffError);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Assert(pSpec->cElements > 0);
+ if ( pSpec->cElements > 1
+ || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END)
+ {
+ const char *pszFinal = NULL;
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ pSpec->fOpenFile = fOpen;
+ rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszFinal)
+ {
+ /* Try convert it to an I/O object and we're done. */
+ *phVfsIos = RTVfsObjToIoStream(hVfsObj);
+ if (*phVfsIos)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ else
+ {
+ /*
+ * Do a file open with the final path on the returned object.
+ */
+ RTVFS hVfs = RTVfsObjToVfs(hVfsObj);
+ RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj);
+ RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj);
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ if (hVfs != NIL_RTVFS)
+ rc = RTVfsFileOpen(hVfs, pszFinal, fOpen, &hVfsFile);
+ else if (hVfsDir != NIL_RTVFSDIR)
+ rc = RTVfsDirOpenFile(hVfsDir, pszFinal, fOpen, &hVfsFile);
+ else if (hVfsFss != NIL_RTVFSFSSTREAM)
+ rc = VERR_NOT_IMPLEMENTED;
+ else
+ rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY;
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsIos = RTVfsFileToIoStream(hVfsFile);
+ if (*phVfsIos)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ RTVfsFileRelease(hVfsFile);
+ }
+ RTVfsRelease(hVfs);
+ RTVfsDirRelease(hVfsDir);
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+ RTVfsObjRelease(hVfsObj);
+ }
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+ }
+
+ /* Only a path element. */
+ pszSpec = pSpec->paElements[0].paArgs[0].psz;
+ }
+
+ /*
+ * Path to regular file system.
+ */
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszSpec, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTVfsFileFromRTFile(hFile, fOpen, false /*fLeaveOpen*/, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsIos = RTVfsFileToIoStream(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ RTFileClose(hFile);
+ }
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+}
+
+
+/**
+ * The equivalent of RTPathQueryInfoEx
+ */
+RTDECL(int) RTVfsChainQueryInfo(const char *pszSpec, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs,
+ uint32_t fFlags, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ uint32_t offErrorIgn;
+ if (!poffError)
+ poffError = &offErrorIgn;
+ *poffError = 0;
+ AssertPtrReturn(pszSpec, VERR_INVALID_POINTER);
+ AssertReturn(*pszSpec != '\0', VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST,
+ VERR_INVALID_PARAMETER);
+ AssertPtrNullReturn(pErrInfo, VERR_INVALID_POINTER);
+
+ /*
+ * Try for a VFS chain first, falling back on regular file system stuff if it's just a path.
+ */
+ int rc;
+ PRTVFSCHAINSPEC pSpec = NULL;
+ if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) == 0)
+ {
+ rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_BASE, &pSpec, poffError);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ Assert(pSpec->cElements > 0);
+ if ( pSpec->cElements > 1
+ || pSpec->paElements[0].enmType != RTVFSOBJTYPE_END)
+ {
+ const char *pszFinal = NULL;
+ RTVFSOBJ hVfsObj = NIL_RTVFSOBJ;
+ pSpec->fOpenFile = RTFILE_O_READ | RTFILE_O_OPEN;
+ rc = RTVfsChainSpecCheckAndSetup(pSpec, NULL /*pReuseSpec*/, &hVfsObj, &pszFinal, poffError, pErrInfo);
+ if (RT_SUCCESS(rc))
+ {
+ if (!pszFinal)
+ {
+ /*
+ * Do the job on the final object.
+ */
+ rc = RTVfsObjQueryInfo(hVfsObj, pObjInfo, enmAdditionalAttribs);
+ }
+ else
+ {
+ /*
+ * Do a path query operation on the penultimate object.
+ */
+ RTVFS hVfs = RTVfsObjToVfs(hVfsObj);
+ RTVFSDIR hVfsDir = RTVfsObjToDir(hVfsObj);
+ RTVFSFSSTREAM hVfsFss = RTVfsObjToFsStream(hVfsObj);
+ if (hVfs != NIL_RTVFS)
+ rc = RTVfsQueryPathInfo(hVfs, pszFinal, pObjInfo, enmAdditionalAttribs, fFlags);
+ else if (hVfsDir != NIL_RTVFSDIR)
+ rc = RTVfsDirQueryPathInfo(hVfsDir, pszFinal, pObjInfo, enmAdditionalAttribs, fFlags);
+ else if (hVfsFss != NIL_RTVFSFSSTREAM)
+ rc = VERR_NOT_SUPPORTED;
+ else
+ rc = VERR_VFS_CHAIN_TYPE_MISMATCH_PATH_ONLY;
+ RTVfsRelease(hVfs);
+ RTVfsDirRelease(hVfsDir);
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+ RTVfsObjRelease(hVfsObj);
+ }
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+ }
+
+ /* Only a path element. */
+ pszSpec = pSpec->paElements[0].paArgs[0].psz;
+ }
+
+ /*
+ * Path to regular file system.
+ */
+ rc = RTPathQueryInfoEx(pszSpec, pObjInfo, enmAdditionalAttribs, fFlags);
+
+ RTVfsChainSpecFree(pSpec);
+ return rc;
+}
+
+
+RTDECL(bool) RTVfsChainIsSpec(const char *pszSpec)
+{
+ return pszSpec
+ && strncmp(pszSpec, RT_STR_TUPLE(RTVFSCHAIN_SPEC_PREFIX)) == 0;
+}
+
+
+RTDECL(int) RTVfsChainQueryFinalPath(const char *pszSpec, char **ppszFinalPath, uint32_t *poffError)
+{
+ /* Make sure we've got an error info variable. */
+ uint32_t offErrorIgn;
+ if (!poffError)
+ poffError = &offErrorIgn;
+ *poffError = 0;
+
+ /*
+ * If not chain specifier, just duplicate the input and return.
+ */
+ if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) != 0)
+ return RTStrDupEx(ppszFinalPath, pszSpec);
+
+ /*
+ * Parse it and check out the last element.
+ */
+ PRTVFSCHAINSPEC pSpec = NULL;
+ int rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_BASE, &pSpec, poffError);
+ if (RT_SUCCESS(rc))
+ {
+ PCRTVFSCHAINELEMSPEC pLast = &pSpec->paElements[pSpec->cElements - 1];
+ if (pLast->pszProvider == NULL)
+ rc = RTStrDupEx(ppszFinalPath, pLast->paArgs[0].psz);
+ else
+ {
+ rc = VERR_VFS_CHAIN_NOT_PATH_ONLY;
+ *poffError = pLast->offSpec;
+ }
+ RTVfsChainSpecFree(pSpec);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsChainSplitOffFinalPath(char *pszSpec, char **ppszSpec, char **ppszFinalPath, uint32_t *poffError)
+{
+ /* Make sure we've got an error info variable. */
+ uint32_t offErrorIgn;
+ if (!poffError)
+ poffError = &offErrorIgn;
+ *poffError = 0;
+
+ /*
+ * If not chain specifier, just duplicate the input and return.
+ */
+ if (strncmp(pszSpec, RTVFSCHAIN_SPEC_PREFIX, sizeof(RTVFSCHAIN_SPEC_PREFIX) - 1) != 0)
+ {
+ *ppszSpec = NULL;
+ *ppszFinalPath = pszSpec;
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Parse it and check out the last element.
+ */
+ PRTVFSCHAINSPEC pSpec = NULL;
+ int rc = RTVfsChainSpecParse(pszSpec, 0 /*fFlags*/, RTVFSOBJTYPE_BASE, &pSpec, poffError);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pSpec->cElements > 0);
+ PCRTVFSCHAINELEMSPEC pLast = &pSpec->paElements[pSpec->cElements - 1];
+ if (pLast->pszProvider == NULL)
+ {
+ char *psz = &pszSpec[pLast->offSpec];
+ *ppszFinalPath = psz;
+ if (pSpec->cElements > 1)
+ {
+ *ppszSpec = pszSpec;
+
+ /* Remove the separator and any whitespace around it. */
+ while ( psz != pszSpec
+ && RT_C_IS_SPACE(psz[-1]))
+ psz--;
+ if ( psz != pszSpec
+ && ( psz[-1] == ':'
+ || psz[-1] == '|'))
+ psz--;
+ while ( psz != pszSpec
+ && RT_C_IS_SPACE(psz[-1]))
+ psz--;
+ *psz = '\0';
+ }
+ else
+ *ppszSpec = NULL;
+ }
+ else
+ {
+ *ppszFinalPath = NULL;
+ *ppszSpec = pszSpec;
+ }
+ RTVfsChainSpecFree(pSpec);
+ }
+ else
+ {
+ *ppszSpec = NULL;
+ *ppszFinalPath = NULL;
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/vfs/vfsfss2dir.cpp b/src/VBox/Runtime/common/vfs/vfsfss2dir.cpp
new file mode 100644
index 00000000..36163c5e
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsfss2dir.cpp
@@ -0,0 +1,445 @@
+/* $Id: vfsfss2dir.cpp $ */
+/** @file
+ * IPRT - Virtual File System, FS write stream dumping in a normal directory.
+ *
+ * This is just a simple mechanism to provide a drop in for the TAR creator
+ * that writes files individually to the disk instead of a TAR archive. It
+ * has an additional feature for removing the files to help bail out on error.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+/// @todo #define RTVFSFSS2DIR_USE_DIR
+#include "internal/iprt.h"
+#include <iprt/vfs.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#ifndef RTVFSFSS2DIR_USE_DIR
+# include <iprt/path.h>
+#endif
+#include <iprt/string.h>
+#include <iprt/vfslowlevel.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Undo entry for RTVFSFSSWRITE2DIR.
+ */
+typedef struct RTVFSFSSWRITE2DIRENTRY
+{
+ /** The list entry (head is RTVFSFSSWRITE2DIR::Entries). */
+ RTLISTNODE Entry;
+ /** The file mode mask. */
+ RTFMODE fMode;
+#ifdef RTVFSFSS2DIR_USE_DIR
+ /** The name (relative to RTVFSFSSWRITE2DIR::hVfsBaseDir). */
+#else
+ /** The name (relative to RTVFSFSSWRITE2DIR::szBaseDir). */
+#endif
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szName[RT_FLEXIBLE_ARRAY];
+} RTVFSFSSWRITE2DIRENTRY;
+/** Pointer to a RTVFSFSSWRITE2DIR undo entry. */
+typedef RTVFSFSSWRITE2DIRENTRY *PRTVFSFSSWRITE2DIRENTRY;
+
+/**
+ * FSS write to directory instance.
+ */
+typedef struct RTVFSFSSWRITE2DIR
+{
+ /** Flags (RTVFSFSS2DIR_F_XXX). */
+ uint32_t fFlags;
+ /** Number of files and stuff we've created. */
+ uint32_t cEntries;
+ /** Files and stuff we've created (RTVFSFSSWRITE2DIRENTRY).
+ * This is used for reverting changes on failure. */
+ RTLISTANCHOR Entries;
+#ifdef RTVFSFSS2DIR_USE_DIR
+ /** The handle of the base directory. */
+ RTVFSDIR hVfsBaseDir;
+#else
+ /** Path to the directory that all operations are relative to. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szBaseDir[RT_FLEXIBLE_ARRAY];
+#endif
+} RTVFSFSSWRITE2DIR;
+/** Pointer to a write-to-directory FSS instance. */
+typedef RTVFSFSSWRITE2DIR *PRTVFSFSSWRITE2DIR;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) rtVfsFssToDir_PushFile(void *pvThis, const char *pszPath, uint64_t cbFile, PCRTFSOBJINFO paObjInfo,
+ uint32_t cObjInfo, uint32_t fFlags, PRTVFSIOSTREAM phVfsIos);
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtVfsFssToDir_Close(void *pvThis)
+{
+ PRTVFSFSSWRITE2DIR pThis = (PRTVFSFSSWRITE2DIR)pvThis;
+
+#ifdef RTVFSFSS2DIR_USE_DIR
+ RTVfsDirRelease(pThis->hVfsBaseDir);
+ pThis->hVfsBaseDir = NIL_RTVFSDIR;
+#endif
+
+ PRTVFSFSSWRITE2DIRENTRY pCur;
+ PRTVFSFSSWRITE2DIRENTRY pNext;
+ RTListForEachSafe(&pThis->Entries, pCur, pNext, RTVFSFSSWRITE2DIRENTRY, Entry)
+ {
+ RTMemFree(pCur);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtVfsFssToDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ RT_NOREF(pvThis);
+
+ /* no info here, sorry. */
+ RT_ZERO(*pObjInfo);
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnAdd}
+ */
+static DECLCALLBACK(int) rtVfsFssToDir_Add(void *pvThis, const char *pszPath, RTVFSOBJ hVfsObj, uint32_t fFlags)
+{
+ PRTVFSFSSWRITE2DIR pThis = (PRTVFSFSSWRITE2DIR)pvThis;
+ RT_NOREF(fFlags);
+
+ /*
+ * Query information about the object.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Deal with files.
+ */
+ if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ {
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj);
+ AssertReturn(hVfsIosSrc != NIL_RTVFSIOSTREAM, VERR_WRONG_TYPE);
+
+ RTVFSIOSTREAM hVfsIosDst;
+ rc = rtVfsFssToDir_PushFile(pvThis, pszPath, ObjInfo.cbObject, &ObjInfo, 1, 0 /*fFlags*/, &hVfsIosDst);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (size_t)RT_ALIGN(ObjInfo.cbObject, _4K));
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ }
+ /*
+ * Symbolic links.
+ */
+ else if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ {
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, VERR_WRONG_TYPE);
+
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+ RT_NOREF(pThis);
+
+ RTVfsSymlinkRelease(hVfsSymlink);
+ }
+ /*
+ * Directories.
+ */
+ else if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+ /*
+ * And whatever else we need when we need it...
+ */
+ else
+ AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnPushFile}
+ */
+static DECLCALLBACK(int) rtVfsFssToDir_PushFile(void *pvThis, const char *pszPath, uint64_t cbFile, PCRTFSOBJINFO paObjInfo,
+ uint32_t cObjInfo, uint32_t fFlags, PRTVFSIOSTREAM phVfsIos)
+{
+ PRTVFSFSSWRITE2DIR pThis = (PRTVFSFSSWRITE2DIR)pvThis;
+ RT_NOREF(cbFile, fFlags);
+ int rc;
+
+#ifndef RTVFSFSS2DIR_USE_DIR
+ /*
+ * Join up the path with the base dir and make sure it fits.
+ */
+ char szFullPath[RTPATH_MAX];
+ rc = RTPathJoin(szFullPath, sizeof(szFullPath), pThis->szBaseDir, pszPath);
+ if (RT_SUCCESS(rc))
+ {
+#endif
+ /*
+ * Create an undo entry for it.
+ */
+ size_t const cbRelativePath = strlen(pszPath);
+ PRTVFSFSSWRITE2DIRENTRY pEntry;
+ pEntry = (PRTVFSFSSWRITE2DIRENTRY)RTMemAllocVar(RT_UOFFSETOF_DYN(RTVFSFSSWRITE2DIRENTRY, szName[cbRelativePath]));
+ if (pEntry)
+ {
+ if (cObjInfo)
+ pEntry->fMode = (paObjInfo[0].Attr.fMode & ~RTFS_TYPE_MASK) | RTFS_TYPE_FILE;
+ else
+ pEntry->fMode = RTFS_TYPE_FILE | 0664;
+ memcpy(pEntry->szName, pszPath, cbRelativePath);
+
+ /*
+ * Create the file.
+ */
+ uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
+ fOpen |= ((pEntry->fMode & RTFS_UNIX_ALL_ACCESS_PERMS) << RTFILE_O_CREATE_MODE_SHIFT);
+ if (!(pThis->fFlags & RTVFSFSS2DIR_F_OVERWRITE_FILES))
+ fOpen |= RTFILE_O_CREATE;
+ else
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+#ifdef RTVFSFSS2DIR_USE_DIR
+ rc = RTVfsDirOpenFileAsIoStream(pThis->hVfsBaseDir, pszPath, fOpen, phVfsIos);
+#else
+ rc = RTVfsIoStrmOpenNormal(szFullPath, fOpen, phVfsIos);
+#endif
+ if (RT_SUCCESS(rc))
+ RTListAppend(&pThis->Entries, &pEntry->Entry);
+ else
+ RTMemFree(pEntry);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+#ifndef RTVFSFSS2DIR_USE_DIR
+ }
+ else if (rc == VERR_BUFFER_OVERFLOW)
+ rc = VERR_FILENAME_TOO_LONG;
+#endif
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnEnd}
+ */
+static DECLCALLBACK(int) rtVfsFssToDir_End(void *pvThis)
+{
+ RT_NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The write-to-directory FSS operations.
+ */
+static const RTVFSFSSTREAMOPS g_rtVfsFssToDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "TarFsStreamWriter",
+ rtVfsFssToDir_Close,
+ rtVfsFssToDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ NULL,
+ rtVfsFssToDir_Add,
+ rtVfsFssToDir_PushFile,
+ rtVfsFssToDir_End,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+#ifdef RTVFSFSS2DIR_USE_DIR
+RTDECL(int) RTVfsFsStrmToDir(RTVFSDIR hVfsBaseDir, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertReturn(!(fFlags & ~RTVFSFSS2DIR_F_VALID_MASK), VERR_INVALID_FLAGS);
+ uint32_t cRefs = RTVfsDirRetain(hVfsBaseDir);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create the file system stream handle and init our data.
+ */
+ PRTVFSFSSWRITE2DIR pThis;
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&g_rtVfsFssToDirOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_WRITE,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->fFlags = fFlags;
+ pThis->cEntries = 0;
+ pThis->hVfsBaseDir = hVfsBaseDir;
+ RTListInit(&pThis->Entries);
+
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+ RTVfsDirRelease(hVfsBaseDir);
+
+}
+#endif /* RTVFSFSS2DIR_USE_DIR */
+
+
+RTDECL(int) RTVfsFsStrmToNormalDir(const char *pszBaseDir, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+#ifdef RTVFSFSS2DIR_USE_DIR
+ RTVFSDIR hVfsBaseDir;
+ int rc = RTVfsDirOpenNormal(pszBaseDir, 0 /*fFlags*/, &hVfsBaseDir);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFsStrmToDir(hVfsBaseDir, fFlags, phVfsFss);
+ RTVfsDirRelease(hVfsBaseDir);
+ }
+#else
+
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertReturn(!(fFlags & ~RTVFSFSS2DIR_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrReturn(pszBaseDir, VERR_INVALID_POINTER);
+ AssertReturn(*pszBaseDir != '\0', VERR_INVALID_NAME);
+
+ /*
+ * Straighten the path and make sure it's an existing directory.
+ */
+ char szAbsPath[RTPATH_MAX];
+ int rc = RTPathAbs(pszBaseDir, szAbsPath, sizeof(szAbsPath));
+ if (RT_SUCCESS(rc))
+ {
+ RTFSOBJINFO ObjInfo;
+ rc = RTPathQueryInfo(szAbsPath, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
+ {
+ /*
+ * Create the file system stream handle and init our data.
+ */
+ size_t const cbBaseDir = strlen(szAbsPath) + 1;
+ PRTVFSFSSWRITE2DIR pThis;
+ RTVFSFSSTREAM hVfsFss;
+ rc = RTVfsNewFsStream(&g_rtVfsFssToDirOps, RT_UOFFSETOF_DYN(RTVFSFSSWRITE2DIR, szBaseDir[cbBaseDir]),
+ NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_WRITE, &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->fFlags = fFlags;
+ pThis->cEntries = 0;
+ RTListInit(&pThis->Entries);
+ memcpy(pThis->szBaseDir, szAbsPath, cbBaseDir);
+
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_NOT_A_DIRECTORY;
+ }
+ }
+#endif
+ return rc;
+}
+
+
+RTDECL(int) RTVfsFsStrmToDirUndo(RTVFSFSSTREAM hVfsFss)
+{
+ /*
+ * Validate input.
+ */
+ PRTVFSFSSWRITE2DIR pThis = (PRTVFSFSSWRITE2DIR)RTVfsFsStreamToPrivate(hVfsFss, &g_rtVfsFssToDirOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ /*
+ * Do the job, in reverse order. Dropping stuff we
+ * successfully remove from the list.
+ */
+ int rc = VINF_SUCCESS;
+ PRTVFSFSSWRITE2DIRENTRY pCur;
+ PRTVFSFSSWRITE2DIRENTRY pPrev;
+ RTListForEachReverseSafe(&pThis->Entries, pCur, pPrev, RTVFSFSSWRITE2DIRENTRY, Entry)
+ {
+#ifdef RTVFSFSS2DIR_USE_DIR
+ int rc2 = RTVfsDirUnlinkEntry(pThis->hVfsBaseDir, pCur->szName);
+#else
+ char szFullPath[RTPATH_MAX];
+ int rc2 = RTPathJoin(szFullPath, sizeof(szFullPath), pThis->szBaseDir, pCur->szName);
+ AssertRC(rc2);
+ if (RT_SUCCESS(rc2))
+ rc2 = RTPathUnlink(szFullPath, 0 /*fUnlink*/);
+#endif
+ if ( RT_SUCCESS(rc2)
+ || rc2 == VERR_PATH_NOT_FOUND
+ || rc2 == VERR_FILE_NOT_FOUND
+ || rc2 == VERR_NOT_FOUND)
+ {
+ RTListNodeRemove(&pCur->Entry);
+ RTMemFree(pCur);
+ }
+ else if (RT_SUCCESS(rc))
+ rc = rc2;
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/vfs/vfsiosmisc.cpp b/src/VBox/Runtime/common/vfs/vfsiosmisc.cpp
new file mode 100644
index 00000000..92645f09
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsiosmisc.cpp
@@ -0,0 +1,238 @@
+/* $Id: vfsiosmisc.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Misc I/O Stream Operations.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+
+
+
+RTDECL(int) RTVfsIoStrmValidateUtf8Encoding(RTVFSIOSTREAM hVfsIos, uint32_t fFlags, PRTFOFF poffError)
+{
+ /*
+ * Validate input.
+ */
+ if (poffError)
+ {
+ AssertPtrReturn(poffError, VINF_SUCCESS);
+ *poffError = 0;
+ }
+ AssertReturn(!(fFlags & ~RTVFS_VALIDATE_UTF8_VALID_MASK), VERR_INVALID_PARAMETER);
+
+ /*
+ * The loop.
+ */
+ char achBuf[1024 + 1];
+ size_t cbUsed = 0;
+ int rc;
+ for (;;)
+ {
+ /*
+ * Fill the buffer
+ */
+ size_t cbRead = 0;
+ rc = RTVfsIoStrmRead(hVfsIos, &achBuf[cbUsed], sizeof(achBuf) - cbUsed - 1, true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ break;
+ cbUsed += cbRead;
+ if (!cbUsed)
+ {
+ Assert(rc == VINF_EOF);
+ break;
+ }
+ achBuf[sizeof(achBuf) - 1] = '\0';
+
+ /*
+ * Process the data in the buffer, maybe leaving the final chars till
+ * the next round.
+ */
+ const char *pszCur = achBuf;
+ size_t offEnd = rc == VINF_EOF
+ ? cbUsed
+ : cbUsed >= 7
+ ? cbUsed - 7
+ : 0;
+ size_t off;
+ while ((off = (pszCur - &achBuf[0])) < offEnd)
+ {
+ RTUNICP uc;
+ rc = RTStrGetCpEx(&pszCur, &uc);
+ if (RT_FAILURE(rc))
+ break;
+ if (!uc)
+ {
+ if (fFlags & RTVFS_VALIDATE_UTF8_NO_NULL)
+ {
+ rc = VERR_INVALID_UTF8_ENCODING;
+ break;
+ }
+ }
+ else if (uc > 0x10ffff)
+ {
+ if (fFlags & RTVFS_VALIDATE_UTF8_BY_RTC_3629)
+ {
+ rc = VERR_INVALID_UTF8_ENCODING;
+ break;
+ }
+ }
+ }
+
+ if (off < cbUsed)
+ {
+ cbUsed -= off;
+ memmove(achBuf, pszCur, cbUsed);
+ }
+ }
+
+ /*
+ * Set the offset on failure.
+ */
+ if (poffError && RT_FAILURE(rc))
+ {
+ }
+
+ return rc == VINF_EOF ? VINF_SUCCESS : rc;
+}
+
+
+/** Header size. */
+#define READ_ALL_HEADER_SIZE 0x20
+/** The header magic. It's followed by the size (both size_t). */
+#define READ_ALL_HEADER_MAGIC UINT32_C(0x11223355)
+
+RTDECL(int) RTVfsIoStrmReadAll(RTVFSIOSTREAM hVfsIos, void **ppvBuf, size_t *pcbBuf)
+{
+ /*
+ * Try query the object information and in case the stream has a known
+ * size we could use for guidance.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsIoStrmQueryInfo(hVfsIos, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ size_t cbAllocated = RT_SUCCESS(rc) && ObjInfo.cbObject > 0 && ObjInfo.cbObject < _1G
+ ? (size_t)ObjInfo.cbObject + 1 : _16K;
+ cbAllocated += READ_ALL_HEADER_SIZE;
+ void *pvBuf = RTMemAlloc(cbAllocated);
+ if (pvBuf)
+ {
+ memset(pvBuf, 0xfe, READ_ALL_HEADER_SIZE);
+ size_t off = 0;
+ for (;;)
+ {
+ /*
+ * Handle buffer growing and detecting the end of it all.
+ */
+ size_t cbToRead = cbAllocated - off - READ_ALL_HEADER_SIZE - 1;
+ if (!cbToRead)
+ {
+ /* The end? */
+ uint8_t bIgn;
+ size_t cbIgn;
+ rc = RTVfsIoStrmRead(hVfsIos, &bIgn, 0, true /*fBlocking*/, &cbIgn);
+ if (rc == VINF_EOF)
+ break;
+
+ /* Grow the buffer. */
+ cbAllocated -= READ_ALL_HEADER_SIZE - 1;
+ cbAllocated = RT_MAX(RT_MIN(cbAllocated, _32M), _1K);
+ cbAllocated = RT_ALIGN_Z(cbAllocated, _4K);
+ cbAllocated += READ_ALL_HEADER_SIZE + 1;
+
+ void *pvNew = RTMemRealloc(pvBuf, cbAllocated);
+ AssertBreakStmt(pvNew, rc = VERR_NO_MEMORY);
+ pvBuf = pvNew;
+
+ cbToRead = cbAllocated - off - READ_ALL_HEADER_SIZE - 1;
+ }
+ Assert(cbToRead < cbAllocated);
+
+ /*
+ * Read.
+ */
+ size_t cbActual;
+ rc = RTVfsIoStrmRead(hVfsIos, (uint8_t *)pvBuf + READ_ALL_HEADER_SIZE + off, cbToRead,
+ true /*fBlocking*/, &cbActual);
+ if (RT_FAILURE(rc))
+ break;
+ Assert(cbActual > 0);
+ Assert(cbActual <= cbToRead);
+ off += cbActual;
+ if (rc == VINF_EOF)
+ break;
+ }
+ Assert(rc != VERR_EOF);
+ if (RT_SUCCESS(rc))
+ {
+ ((size_t *)pvBuf)[0] = READ_ALL_HEADER_MAGIC;
+ ((size_t *)pvBuf)[1] = off;
+ ((uint8_t *)pvBuf)[READ_ALL_HEADER_SIZE + off] = 0;
+
+ *ppvBuf = (uint8_t *)pvBuf + READ_ALL_HEADER_SIZE;
+ *pcbBuf = off;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pvBuf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ *ppvBuf = NULL;
+ *pcbBuf = 0;
+ return rc;
+}
+
+
+RTDECL(void) RTVfsIoStrmReadAllFree(void *pvBuf, size_t cbBuf)
+{
+ AssertPtrReturnVoid(pvBuf);
+
+ /* Spool back to the start of the header. */
+ pvBuf = (uint8_t *)pvBuf - READ_ALL_HEADER_SIZE;
+
+ /* Make sure the caller isn't messing with us. Hardcoded, but works. */
+ Assert(((size_t *)pvBuf)[0] == READ_ALL_HEADER_MAGIC);
+ Assert(((size_t *)pvBuf)[1] == cbBuf); RT_NOREF_PV(cbBuf);
+
+ /* Free it. */
+ RTMemFree(pvBuf);
+}
+
diff --git a/src/VBox/Runtime/common/vfs/vfsmemory.cpp b/src/VBox/Runtime/common/vfs/vfsmemory.cpp
new file mode 100644
index 00000000..a044f3d8
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsmemory.cpp
@@ -0,0 +1,971 @@
+/* $Id: vfsmemory.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Memory Backed VFS.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/vfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/vfslowlevel.h>
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/vfs.h>
+
+#include <iprt/err.h>
+#include <iprt/mem.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The max extent size. */
+#define RTVFSMEM_MAX_EXTENT_SIZE _2M
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Memory base object info.
+ */
+typedef struct RTVFSMEMBASE
+{
+ /** The basic object info. */
+ RTFSOBJINFO ObjInfo;
+} RTVFSMEMBASE;
+
+
+/**
+ * Memory file extent.
+ *
+ * This stores part of the file content.
+ */
+typedef struct RTVFSMEMEXTENT
+{
+ /** Extent list entry. */
+ RTLISTNODE Entry;
+ /** The offset of this extent within the file. */
+ uint64_t off;
+ /** The size of the this extent. */
+ uint32_t cb;
+ /** The data. */
+ uint8_t abData[1];
+} RTVFSMEMEXTENT;
+/** Pointer to a memory file extent. */
+typedef RTVFSMEMEXTENT *PRTVFSMEMEXTENT;
+
+/**
+ * Memory file.
+ */
+typedef struct RTVFSMEMFILE
+{
+ /** The base info. */
+ RTVFSMEMBASE Base;
+ /** The current file position. */
+ uint64_t offCurPos;
+ /** Pointer to the current file extent. */
+ PRTVFSMEMEXTENT pCurExt;
+ /** Linked list of file extents - RTVFSMEMEXTENT. */
+ RTLISTANCHOR ExtentHead;
+ /** The current extent size.
+ * This is slowly grown to RTVFSMEM_MAX_EXTENT_SIZE as the file grows. */
+ uint32_t cbExtent;
+} RTVFSMEMFILE;
+/** Pointer to a memory file. */
+typedef RTVFSMEMFILE *PRTVFSMEMFILE;
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_Close(void *pvThis)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+
+ /*
+ * Free the extent list.
+ */
+ PRTVFSMEMEXTENT pCur, pNext;
+ RTListForEachSafe(&pThis->ExtentHead, pCur, pNext, RTVFSMEMEXTENT, Entry)
+ {
+ pCur->off = RTFOFF_MAX;
+ pCur->cb = UINT32_MAX;
+ RTListNodeRemove(&pCur->Entry);
+ RTMemFree(pCur);
+ }
+ pThis->pCurExt = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ *pObjInfo = pThis->Base.ObjInfo;
+ return VINF_SUCCESS;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+}
+
+
+/**
+ * The slow paths of rtVfsMemFile_LocateExtent.
+ *
+ * @copydoc rtVfsMemFile_LocateExtent
+ */
+static PRTVFSMEMEXTENT rtVfsMemFile_LocateExtentSlow(PRTVFSMEMFILE pThis, uint64_t off, bool *pfHit)
+{
+ /*
+ * Search from the start or the previously used extent. The heuristics
+ * are very very simple, but whatever.
+ */
+ PRTVFSMEMEXTENT pExtent = pThis->pCurExt;
+ if (!pExtent || off < pExtent->off)
+ {
+ /* Consider the last entry first (for writes). */
+ pExtent = RTListGetLast(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry);
+ if (!pExtent)
+ {
+ *pfHit = false;
+ return NULL;
+ }
+ if (off - pExtent->off < pExtent->cb)
+ {
+ *pfHit = true;
+ pThis->pCurExt = pExtent;
+ return pExtent;
+ }
+
+ /* Otherwise, start from the head after making sure it is not an
+ offset before the first extent. */
+ pExtent = RTListGetFirst(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry);
+ if (off < pExtent->off)
+ {
+ *pfHit = false;
+ return pExtent;
+ }
+ }
+
+ while (off - pExtent->off >= pExtent->cb)
+ {
+ Assert(pExtent->off <= off);
+ PRTVFSMEMEXTENT pNext = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry);
+ if ( !pNext
+ || pNext->off > off)
+ {
+ *pfHit = false;
+ return pNext;
+ }
+
+ pExtent = pNext;
+ }
+
+ *pfHit = true;
+ pThis->pCurExt = pExtent;
+ return pExtent;
+}
+
+
+/**
+ * Locates the extent covering the specified offset, or the one after it.
+ *
+ * @returns The closest extent. NULL if off is 0 and there are no extent
+ * covering byte 0 yet.
+ * @param pThis The memory file.
+ * @param off The offset (0-positive).
+ * @param pfHit Where to indicate whether the extent is a
+ * direct hit (@c true) or just a closest match
+ * (@c false).
+ */
+DECLINLINE(PRTVFSMEMEXTENT) rtVfsMemFile_LocateExtent(PRTVFSMEMFILE pThis, uint64_t off, bool *pfHit)
+{
+ /*
+ * The most likely case is that we're hitting the extent we used in the
+ * previous access or the one immediately following it.
+ */
+ PRTVFSMEMEXTENT pExtent = pThis->pCurExt;
+ if (!pExtent)
+ return rtVfsMemFile_LocateExtentSlow(pThis, off, pfHit);
+
+ if (off - pExtent->off >= pExtent->cb)
+ {
+ pExtent = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry);
+ if ( !pExtent
+ || off - pExtent->off >= pExtent->cb)
+ return rtVfsMemFile_LocateExtentSlow(pThis, off, pfHit);
+ pThis->pCurExt = pExtent;
+ }
+
+ *pfHit = true;
+ return pExtent;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+
+ Assert(pSgBuf->cSegs == 1);
+ NOREF(fBlocking);
+
+ /*
+ * Find the current position and check if it's within the file.
+ */
+ uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off;
+ if (offUnsigned >= (uint64_t)pThis->Base.ObjInfo.cbObject)
+ {
+ if (pcbRead)
+ {
+ *pcbRead = 0;
+ pThis->offCurPos = offUnsigned;
+ return VINF_EOF;
+ }
+ return VERR_EOF;
+ }
+
+ size_t cbLeftToRead;
+ if (offUnsigned + pSgBuf->paSegs[0].cbSeg > (uint64_t)pThis->Base.ObjInfo.cbObject)
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ *pcbRead = cbLeftToRead = (size_t)((uint64_t)pThis->Base.ObjInfo.cbObject - offUnsigned);
+ }
+ else
+ {
+ cbLeftToRead = pSgBuf->paSegs[0].cbSeg;
+ if (pcbRead)
+ *pcbRead = cbLeftToRead;
+ }
+
+ /*
+ * Ok, we've got a valid stretch within the file. Do the reading.
+ */
+ if (cbLeftToRead > 0)
+ {
+ uint8_t *pbDst = (uint8_t *)pSgBuf->paSegs[0].pvSeg;
+ bool fHit;
+ PRTVFSMEMEXTENT pExtent = rtVfsMemFile_LocateExtent(pThis, offUnsigned, &fHit);
+ for (;;)
+ {
+ size_t cbThisRead;
+
+ /*
+ * Do we hit an extent covering the current file surface?
+ */
+ if (fHit)
+ {
+ /* Yes, copy the data. */
+ Assert(offUnsigned - pExtent->off < pExtent->cb);
+ size_t const offExtent = (size_t)(offUnsigned - pExtent->off);
+ cbThisRead = pExtent->cb - offExtent;
+ if (cbThisRead >= cbLeftToRead)
+ cbThisRead = cbLeftToRead;
+
+ memcpy(pbDst, &pExtent->abData[offUnsigned - pExtent->off], cbThisRead);
+
+ offUnsigned += cbThisRead;
+ cbLeftToRead -= cbThisRead;
+ if (!cbLeftToRead)
+ break;
+ pbDst += cbThisRead;
+
+ /* Advance, looping immediately if not sparse. */
+ PRTVFSMEMEXTENT pNext = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry);
+ if ( pNext
+ && pNext->off == pExtent->off + pExtent->cb)
+ {
+ pExtent = pNext;
+ continue;
+ }
+
+ Assert(!pNext || pNext->off > pExtent->off);
+ pExtent = pNext;
+ fHit = false;
+ }
+ else
+ Assert(!pExtent || pExtent->off > offUnsigned);
+
+ /*
+ * No extent of this portion (sparse file) - Read zeros.
+ */
+ if ( !pExtent
+ || offUnsigned + cbLeftToRead <= pExtent->off)
+ cbThisRead = cbLeftToRead;
+ else
+ cbThisRead = (size_t)(pExtent->off - offUnsigned);
+
+ RT_BZERO(pbDst, cbThisRead);
+
+ offUnsigned += cbThisRead;
+ cbLeftToRead -= cbThisRead;
+ if (!cbLeftToRead)
+ break;
+ pbDst += cbThisRead;
+
+ /* Go on and read content from the next extent. */
+ fHit = true;
+ }
+ }
+
+ pThis->offCurPos = offUnsigned;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Allocates a new extent covering the ground at @a offUnsigned.
+ *
+ * @returns Pointer to the new extent on success, NULL if we're out of memory.
+ * @param pThis The memory file.
+ * @param offUnsigned The location to allocate the extent at.
+ * @param cbToWrite The number of bytes we're interested in writing
+ * starting at @a offUnsigned.
+ * @param pNext The extention after @a offUnsigned. NULL if
+ * none, i.e. we're allocating space at the end of
+ * the file.
+ */
+static PRTVFSMEMEXTENT rtVfsMemFile_AllocExtent(PRTVFSMEMFILE pThis, uint64_t offUnsigned, size_t cbToWrite,
+ PRTVFSMEMEXTENT pNext)
+{
+ /*
+ * Adjust the extent size if we haven't reached the max size yet.
+ */
+ if (pThis->cbExtent != RTVFSMEM_MAX_EXTENT_SIZE)
+ {
+ if (cbToWrite >= RTVFSMEM_MAX_EXTENT_SIZE)
+ pThis->cbExtent = RTVFSMEM_MAX_EXTENT_SIZE;
+ else if (!RTListIsEmpty(&pThis->ExtentHead))
+ {
+ uint32_t cbNextExtent = pThis->cbExtent;
+ if (RT_IS_POWER_OF_TWO(cbNextExtent))
+ cbNextExtent *= 2;
+ else
+ {
+ /* Make it a power of two (seeRTVfsMemorizeIoStreamAsFile). */
+ cbNextExtent = _4K;
+ while (cbNextExtent < pThis->cbExtent)
+ cbNextExtent *= 2;
+ }
+ if (((pThis->Base.ObjInfo.cbAllocated + cbNextExtent) & (cbNextExtent - 1)) == 0)
+ pThis->cbExtent = cbNextExtent;
+ }
+ }
+
+ /*
+ * Figure out the size and position of the extent we're adding.
+ */
+ uint64_t offExtent = offUnsigned & ~(uint64_t)(pThis->cbExtent - 1);
+ uint32_t cbExtent = pThis->cbExtent;
+
+ PRTVFSMEMEXTENT pPrev = pNext
+ ? RTListGetPrev(&pThis->ExtentHead, pNext, RTVFSMEMEXTENT, Entry)
+ : RTListGetLast(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry);
+ uint64_t const offPrev = pPrev ? pPrev->off + pPrev->cb : 0;
+ if (offExtent < offPrev)
+ offExtent = offPrev;
+
+ if (pNext)
+ {
+ uint64_t cbMaxExtent = pNext->off - offExtent;
+ if (cbMaxExtent < cbExtent)
+ cbExtent = (uint32_t)cbMaxExtent;
+ }
+
+ /*
+ * Allocate, initialize and insert the new extent.
+ */
+ PRTVFSMEMEXTENT pNew = (PRTVFSMEMEXTENT)RTMemAllocZ(RT_UOFFSETOF_DYN(RTVFSMEMEXTENT, abData[cbExtent]));
+ if (pNew)
+ {
+ pNew->off = offExtent;
+ pNew->cb = cbExtent;
+ if (pPrev)
+ RTListNodeInsertAfter(&pPrev->Entry, &pNew->Entry);
+ else
+ RTListPrepend(&pThis->ExtentHead, &pNew->Entry);
+
+ pThis->Base.ObjInfo.cbAllocated += cbExtent;
+ }
+ /** @todo retry with minimum size. */
+
+ return pNew;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+
+ Assert(pSgBuf->cSegs == 1);
+ NOREF(fBlocking);
+
+ /*
+ * Validate the write and set up the write loop.
+ */
+ size_t cbLeftToWrite = pSgBuf->paSegs[0].cbSeg;
+ if (!cbLeftToWrite)
+ return VINF_SUCCESS; /* pcbWritten is already 0. */
+ uint64_t offUnsigned = off < 0 ? pThis->offCurPos : (uint64_t)off;
+ if (offUnsigned + cbLeftToWrite >= (uint64_t)RTFOFF_MAX)
+ return VERR_OUT_OF_RANGE;
+
+ int rc = VINF_SUCCESS;
+ uint8_t const *pbSrc = (uint8_t const *)pSgBuf->paSegs[0].pvSeg;
+ bool fHit;
+ PRTVFSMEMEXTENT pExtent = rtVfsMemFile_LocateExtent(pThis, offUnsigned, &fHit);
+ for (;;)
+ {
+ /*
+ * If we didn't hit an extent, allocate one (unless it's all zeros).
+ */
+ if (!fHit)
+ {
+ Assert(!pExtent || pExtent->off > offUnsigned);
+
+ /* Skip leading zeros if there is a whole bunch of them. */
+ uint8_t const *pbSrcNZ = (uint8_t const *)ASMMemFirstNonZero(pbSrc, cbLeftToWrite);
+ size_t cbZeros = pbSrcNZ ? pbSrcNZ - pbSrc : cbLeftToWrite;
+ if (cbZeros)
+ {
+ uint64_t const cbToNext = pExtent ? pExtent->off - offUnsigned : UINT64_MAX;
+ if (cbZeros > cbToNext)
+ cbZeros = (size_t)cbToNext;
+ offUnsigned += cbZeros;
+ cbLeftToWrite -= cbZeros;
+ if (!cbLeftToWrite)
+ break;
+ pbSrc += cbZeros;
+
+ Assert(!pExtent || offUnsigned <= pExtent->off);
+ if (pExtent && pExtent->off == offUnsigned)
+ {
+ fHit = true;
+ continue;
+ }
+ }
+
+ fHit = true;
+ pExtent = rtVfsMemFile_AllocExtent(pThis, offUnsigned, cbLeftToWrite, pExtent);
+ if (!pExtent)
+ {
+ rc = VERR_NO_MEMORY;
+ break;
+ }
+ }
+ Assert(offUnsigned - pExtent->off < pExtent->cb);
+
+ /*
+ * Copy the source data into the current extent.
+ */
+ uint32_t const offDst = (uint32_t)(offUnsigned - pExtent->off);
+ uint32_t cbThisWrite = pExtent->cb - offDst;
+ if (cbThisWrite > cbLeftToWrite)
+ cbThisWrite = (uint32_t)cbLeftToWrite;
+ memcpy(&pExtent->abData[offDst], pbSrc, cbThisWrite);
+
+ offUnsigned += cbThisWrite;
+ cbLeftToWrite -= cbThisWrite;
+ if (!cbLeftToWrite)
+ break;
+ pbSrc += cbThisWrite;
+ Assert(offUnsigned == pExtent->off + pExtent->cb);
+
+ /*
+ * Advance to the next extent (emulate the lookup).
+ */
+ pExtent = RTListGetNext(&pThis->ExtentHead, pExtent, RTVFSMEMEXTENT, Entry);
+ fHit = pExtent && (offUnsigned - pExtent->off < pExtent->cb);
+ }
+
+ /*
+ * Update the state, set return value and return.
+ * Note! There must be no alternative exit path from the loop above.
+ */
+ pThis->offCurPos = offUnsigned;
+ if ((uint64_t)pThis->Base.ObjInfo.cbObject < offUnsigned)
+ pThis->Base.ObjInfo.cbObject = offUnsigned;
+
+ if (pcbWritten)
+ *pcbWritten = pSgBuf->paSegs[0].cbSeg - cbLeftToWrite;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_Flush(void *pvThis)
+{
+ NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+ *poffActual = pThis->offCurPos;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+ pThis->Base.ObjInfo.Attr.fMode = (pThis->Base.ObjInfo.Attr.fMode & ~fMask) | fMode;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+
+ if (pAccessTime)
+ pThis->Base.ObjInfo.AccessTime = *pAccessTime;
+ if (pModificationTime)
+ pThis->Base.ObjInfo.ModificationTime = *pModificationTime;
+ if (pChangeTime)
+ pThis->Base.ObjInfo.ChangeTime = *pChangeTime;
+ if (pBirthTime)
+ pThis->Base.ObjInfo.BirthTime = *pBirthTime;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+
+ if (uid != NIL_RTUID)
+ pThis->Base.ObjInfo.Attr.u.Unix.uid = uid;
+ if (gid != NIL_RTUID)
+ pThis->Base.ObjInfo.Attr.u.Unix.gid = gid;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+
+ /*
+ * Seek relative to which position.
+ */
+ uint64_t offWrt;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offWrt = 0;
+ break;
+
+ case RTFILE_SEEK_CURRENT:
+ offWrt = pThis->offCurPos;
+ break;
+
+ case RTFILE_SEEK_END:
+ offWrt = pThis->Base.ObjInfo.cbObject;
+ break;
+
+ default:
+ return VERR_INTERNAL_ERROR_5;
+ }
+
+ /*
+ * Calc new position, take care to stay within RTFOFF type bounds.
+ */
+ uint64_t offNew;
+ if (offSeek == 0)
+ offNew = offWrt;
+ else if (offSeek > 0)
+ {
+ offNew = offWrt + offSeek;
+ if ( offNew < offWrt
+ || offNew > RTFOFF_MAX)
+ offNew = RTFOFF_MAX;
+ }
+ else if ((uint64_t)-offSeek < offWrt)
+ offNew = offWrt + offSeek;
+ else
+ offNew = 0;
+
+ /*
+ * Update the state and set return value.
+ */
+ if ( pThis->pCurExt
+ && pThis->pCurExt->off - offNew >= pThis->pCurExt->cb)
+ pThis->pCurExt = NULL;
+ pThis->offCurPos = offNew;
+
+ *poffActual = offNew;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+ *pcbFile = pThis->Base.ObjInfo.cbObject;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ AssertReturn(RTVFSFILE_SIZE_F_IS_VALID(fFlags), VERR_INVALID_PARAMETER);
+
+ PRTVFSMEMFILE pThis = (PRTVFSMEMFILE)pvThis;
+ if ( (fFlags & RTVFSFILE_SIZE_F_ACTION_MASK) == RTVFSFILE_SIZE_F_NORMAL
+ && (RTFOFF)cbFile >= pThis->Base.ObjInfo.cbObject)
+ {
+ /* Growing is just a matter of increasing the size of the object. */
+ pThis->Base.ObjInfo.cbObject = cbFile;
+ return VINF_SUCCESS;
+ }
+
+ AssertMsgFailed(("Lucky you! You get to implement this (or bug bird about it).\n"));
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtVfsMemFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ RT_NOREF(pvThis);
+ *pcbMax = ~(size_t)0 >> 1;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Memory file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtVfsMemFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "MemFile",
+ rtVfsMemFile_Close,
+ rtVfsMemFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtVfsMemFile_Read,
+ rtVfsMemFile_Write,
+ rtVfsMemFile_Flush,
+ NULL /*PollOne*/,
+ rtVfsMemFile_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ /*RTVFSIOFILEOPS_FEAT_NO_AT_OFFSET*/ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtVfsMemFile_SetMode,
+ rtVfsMemFile_SetTimes,
+ rtVfsMemFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtVfsMemFile_Seek,
+ rtVfsMemFile_QuerySize,
+ rtVfsMemFile_SetSize,
+ rtVfsMemFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Initialize the RTVFSMEMFILE::Base.ObjInfo specific members.
+ *
+ * @param pObjInfo The object info to init.
+ * @param cbObject The object size set.
+ */
+static void rtVfsMemInitObjInfo(PRTFSOBJINFO pObjInfo, uint64_t cbObject)
+{
+ pObjInfo->cbObject = cbObject;
+ pObjInfo->cbAllocated = cbObject;
+ pObjInfo->Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | RTFS_UNIX_IRWXU;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
+ pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = 0;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ RTTimeNow(&pObjInfo->AccessTime);
+ pObjInfo->ModificationTime = pObjInfo->AccessTime;
+ pObjInfo->ChangeTime = pObjInfo->AccessTime;
+ pObjInfo->BirthTime = pObjInfo->AccessTime;
+}
+
+
+/**
+ * Initialize the RTVFSMEMFILE specific members.
+ *
+ * @param pThis The memory file to initialize.
+ * @param cbObject The object size for estimating extent size.
+ * @param fFlags The user specified flags.
+ */
+static void rtVfsMemFileInit(PRTVFSMEMFILE pThis, RTFOFF cbObject, uint32_t fFlags)
+{
+ pThis->offCurPos = 0;
+ pThis->pCurExt = NULL;
+ RTListInit(&pThis->ExtentHead);
+ if (cbObject <= 0)
+ pThis->cbExtent = _4K;
+ else if (cbObject < RTVFSMEM_MAX_EXTENT_SIZE)
+ pThis->cbExtent = fFlags & RTFILE_O_WRITE ? _4K : cbObject;
+ else
+ pThis->cbExtent = RTVFSMEM_MAX_EXTENT_SIZE;
+}
+
+
+/**
+ * Rewinds the file to position 0 and clears the WRITE flag if necessary.
+ *
+ * @param pThis The memory file instance.
+ * @param fFlags The user specified flags.
+ */
+static void rtVfsMemFileResetAndFixWriteFlag(PRTVFSMEMFILE pThis, uint32_t fFlags)
+{
+ pThis->pCurExt = RTListGetFirst(&pThis->ExtentHead, RTVFSMEMEXTENT, Entry);
+ pThis->offCurPos = 0;
+
+ if (!(fFlags & RTFILE_O_WRITE))
+ {
+ /** @todo clear RTFILE_O_WRITE from the resulting. */
+ }
+}
+
+
+RTDECL(int) RTVfsMemFileCreate(RTVFSIOSTREAM hVfsIos, size_t cbEstimate, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Create a memory file instance and set the extension size according to the
+ * buffer size. Add the WRITE flag so we can use normal write APIs for
+ * copying the buffer.
+ */
+ RTVFSFILE hVfsFile;
+ PRTVFSMEMFILE pThis;
+ int rc = RTVfsNewFile(&g_rtVfsMemFileOps, sizeof(*pThis), RTFILE_O_READ | RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsFile, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rtVfsMemInitObjInfo(&pThis->Base.ObjInfo, 0);
+ rtVfsMemFileInit(pThis, cbEstimate, RTFILE_O_READ | RTFILE_O_WRITE);
+
+ if (hVfsIos != NIL_RTVFSIOSTREAM)
+ {
+ RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsFile);
+ rc = RTVfsUtilPumpIoStreams(hVfsIos, hVfsIosDst, pThis->cbExtent);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsFile = hVfsFile;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsFileRelease(hVfsFile);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsMemIoStrmCreate(RTVFSIOSTREAM hVfsIos, size_t cbEstimate, PRTVFSIOSTREAM phVfsIos)
+{
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsMemFileCreate(hVfsIos, cbEstimate, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsIos = RTVfsFileToIoStream(hVfsFile);
+ AssertStmt(*phVfsIos != NIL_RTVFSIOSTREAM, rc = VERR_INTERNAL_ERROR_2);
+ RTVfsFileRelease(hVfsFile);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsFileFromBuffer(uint32_t fFlags, void const *pvBuf, size_t cbBuf, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Create a memory file instance and set the extension size according to the
+ * buffer size. Add the WRITE flag so we can use normal write APIs for
+ * copying the buffer.
+ */
+ RTVFSFILE hVfsFile;
+ PRTVFSMEMFILE pThis;
+ int rc = RTVfsNewFile(&g_rtVfsMemFileOps, sizeof(*pThis), fFlags | RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsFile, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rtVfsMemInitObjInfo(&pThis->Base.ObjInfo, cbBuf);
+ rtVfsMemFileInit(pThis, cbBuf, fFlags);
+
+ /*
+ * Copy the buffer and reposition the file pointer to the start.
+ */
+ rc = RTVfsFileWrite(hVfsFile, pvBuf, cbBuf, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rtVfsMemFileResetAndFixWriteFlag(pThis, fFlags);
+ *phVfsFile = hVfsFile;
+ return VINF_SUCCESS;
+ }
+ RTVfsFileRelease(hVfsFile);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmFromBuffer(uint32_t fFlags, void const *pvBuf, size_t cbBuf, PRTVFSIOSTREAM phVfsIos)
+{
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsFileFromBuffer(fFlags, pvBuf, cbBuf, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsIos = RTVfsFileToIoStream(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsMemorizeIoStreamAsFile(RTVFSIOSTREAM hVfsIos, uint32_t fFlags, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Create a memory file instance and try set the extension size to match
+ * the length of the I/O stream.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTVfsIoStrmQueryInfo(hVfsIos, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ PRTVFSMEMFILE pThis;
+ rc = RTVfsNewFile(&g_rtVfsMemFileOps, sizeof(*pThis), fFlags | RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsFile, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Base.ObjInfo = ObjInfo;
+ rtVfsMemFileInit(pThis, ObjInfo.cbObject, fFlags);
+
+ /*
+ * Copy the stream.
+ */
+ RTVFSIOSTREAM hVfsIosDst = RTVfsFileToIoStream(hVfsFile);
+ rc = RTVfsUtilPumpIoStreams(hVfsIos, hVfsIosDst, pThis->cbExtent);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ if (RT_SUCCESS(rc))
+ {
+ rtVfsMemFileResetAndFixWriteFlag(pThis, fFlags);
+ *phVfsFile = hVfsFile;
+ return VINF_SUCCESS;
+ }
+ RTVfsFileRelease(hVfsFile);
+ }
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/vfs/vfsmisc.cpp b/src/VBox/Runtime/common/vfs/vfsmisc.cpp
new file mode 100644
index 00000000..64d9b606
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsmisc.cpp
@@ -0,0 +1,99 @@
+/* $Id: vfsmisc.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Misc functions with heavy dependencies.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/vfs.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/handle.h>
+
+
+
+RTDECL(int) RTVfsIoStrmFromStdHandle(RTHANDLESTD enmStdHandle, uint64_t fOpen, bool fLeaveOpen,
+ PRTVFSIOSTREAM phVfsIos)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsIos, VERR_INVALID_POINTER);
+ *phVfsIos = NIL_RTVFSIOSTREAM;
+ AssertReturn( enmStdHandle == RTHANDLESTD_INPUT
+ || enmStdHandle == RTHANDLESTD_OUTPUT
+ || enmStdHandle == RTHANDLESTD_ERROR,
+ VERR_INVALID_PARAMETER);
+ AssertReturn(!(fOpen & ~RTFILE_O_VALID_MASK), VERR_INVALID_PARAMETER);
+ if (enmStdHandle == RTHANDLESTD_INPUT)
+ fOpen |= RTFILE_O_READ;
+ else
+ fOpen |= RTFILE_O_WRITE;
+
+ /*
+ * Open the handle and see what we get back.
+ */
+ RTHANDLE h;
+ int rc = RTHandleGetStandard(enmStdHandle, fLeaveOpen, &h);
+ if (RT_SUCCESS(rc))
+ {
+ switch (h.enmType)
+ {
+ case RTHANDLETYPE_FILE:
+ rc = RTVfsIoStrmFromRTFile(h.u.hFile, fOpen, fLeaveOpen, phVfsIos);
+ break;
+
+ case RTHANDLETYPE_PIPE:
+ rc = RTVfsIoStrmFromRTPipe(h.u.hPipe, fLeaveOpen, phVfsIos);
+ break;
+
+ case RTHANDLETYPE_SOCKET:
+ /** @todo */
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+
+ default:
+ rc = VERR_NOT_IMPLEMENTED;
+ break;
+ }
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/vfs/vfsmount.cpp b/src/VBox/Runtime/common/vfs/vfsmount.cpp
new file mode 100644
index 00000000..9de89a34
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsmount.cpp
@@ -0,0 +1,584 @@
+/* $Id: vfsmount.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Mounting.
+ */
+
+/*
+ * Copyright (C) 2012-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_VFS
+#include <iprt/vfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/fsvfs.h>
+#include <iprt/mem.h>
+#include <iprt/log.h>
+#include <iprt/string.h>
+#include <iprt/vfslowlevel.h>
+
+#include <iprt/formats/fat.h>
+#include <iprt/formats/iso9660.h>
+#include <iprt/formats/udf.h>
+#include <iprt/formats/ext.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Buffer structure for the detection routines. */
+typedef union RTVFSMOUNTBUF
+{
+ uint8_t ab[2048];
+ uint32_t au32[2048/4];
+ FATBOOTSECTOR Bootsector;
+ ISO9660VOLDESCHDR IsoHdr;
+} RTVFSMOUNTBUF;
+AssertCompileSize(RTVFSMOUNTBUF, 2048);
+typedef RTVFSMOUNTBUF *PRTVFSMOUNTBUF;
+
+
+
+/**
+ * Checks if the given 2K sector at offset 32KB looks like ISO-9660 or UDF.
+ *
+ * @returns true if likely ISO or UDF, otherwise false.
+ * @param pVolDescHdr Whatever is at offset 32KB. 2KB buffer.
+ */
+static bool rtVfsMountIsIsoFs(PCISO9660VOLDESCHDR pVolDescHdr)
+{
+ if ( memcmp(pVolDescHdr->achStdId, RT_STR_TUPLE(ISO9660VOLDESC_STD_ID)) == 0
+ && pVolDescHdr->bDescType <= ISO9660VOLDESC_TYPE_PARTITION
+ && pVolDescHdr->bDescVersion != 0
+ && pVolDescHdr->bDescVersion <= 3 /* don't be too picky, just increase the likelyhood */ )
+ return true;
+
+ if ( memcmp(pVolDescHdr->achStdId, RT_STR_TUPLE(UDF_EXT_VOL_DESC_STD_ID_BEGIN)) == 0
+ && pVolDescHdr->bDescType == UDF_EXT_VOL_DESC_TYPE
+ && pVolDescHdr->bDescVersion == UDF_EXT_VOL_DESC_VERSION)
+ return true;
+
+ return false;
+}
+
+
+/**
+ * Check if the given bootsector is a NTFS boot sector.
+ *
+ * @returns true if NTFS, false if not.
+ * @param pBootSector The boot sector to inspect.
+ */
+static bool rtVfsMountIsNtfs(PCFATBOOTSECTOR pBootSector)
+{
+ if (memcmp(pBootSector->achOemName, RT_STR_TUPLE("NTFS ")) != 0)
+ return false;
+
+ uint16_t cbSector = RT_LE2H_U16(pBootSector->Bpb.Bpb331.cbSector);
+ if ( cbSector < 0x100
+ || cbSector >= 0x1000
+ || (cbSector & 0xff) != 0)
+ {
+ Log2(("rtVfsMountIsNtfs: cbSector=%#x: out of range\n", cbSector));
+ return false;
+ }
+
+ if ( !RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb331.cSectorsPerCluster)
+ || pBootSector->Bpb.Bpb331.cSectorsPerCluster == 0
+ || pBootSector->Bpb.Bpb331.cSectorsPerCluster > 128)
+ {
+ Log2(("rtVfsMountIsNtfs: cSectorsPerCluster=%#x: out of range\n", pBootSector->Bpb.Bpb331.cSectorsPerCluster));
+ return false;
+ }
+
+ if ((uint32_t)pBootSector->Bpb.Bpb331.cSectorsPerCluster * cbSector > _64K)
+ {
+ Log2(("rtVfsMountIsNtfs: cSectorsPerCluster=%#x * cbSector=%#x => %#x: out of range\n",
+ pBootSector->Bpb.Bpb331.cSectorsPerCluster, cbSector,
+ (uint32_t)pBootSector->Bpb.Bpb331.cSectorsPerCluster * cbSector));
+ return false;
+ }
+
+ if ( pBootSector->Bpb.Bpb331.cReservedSectors != 0
+ || pBootSector->Bpb.Bpb331.cMaxRootDirEntries != 0
+ || pBootSector->Bpb.Bpb331.cTotalSectors16 != 0
+ || pBootSector->Bpb.Bpb331.cTotalSectors32 != 0
+ || pBootSector->Bpb.Bpb331.cSectorsPerFat != 0
+ || pBootSector->Bpb.Bpb331.cFats != 0)
+ {
+ Log2(("rtVfsMountIsNtfs: cReservedSectors=%#x cMaxRootDirEntries=%#x cTotalSectors=%#x cTotalSectors32=%#x cSectorsPerFat=%#x cFats=%#x: should all be zero, but one or more aren't\n",
+ RT_LE2H_U16(pBootSector->Bpb.Bpb331.cReservedSectors),
+ RT_LE2H_U16(pBootSector->Bpb.Bpb331.cMaxRootDirEntries),
+ RT_LE2H_U16(pBootSector->Bpb.Bpb331.cTotalSectors16),
+ RT_LE2H_U32(pBootSector->Bpb.Bpb331.cTotalSectors32),
+ RT_LE2H_U16(pBootSector->Bpb.Bpb331.cSectorsPerFat),
+ pBootSector->Bpb.Bpb331.cFats));
+ return false;
+ }
+
+ /** @todo NTFS specific checks: MFT cluster number, cluster per index block. */
+
+ return true;
+}
+
+
+/**
+ * Check if the given bootsector is a HPFS boot sector.
+ *
+ * @returns true if NTFS, false if not.
+ * @param pBootSector The boot sector to inspect.
+ * @param hVfsFileIn The volume file.
+ * @param pBuf2 A 2nd buffer.
+ */
+static bool rtVfsMountIsHpfs(PCFATBOOTSECTOR pBootSector, RTVFSFILE hVfsFileIn, PRTVFSMOUNTBUF pBuf2)
+{
+ if (memcmp(pBootSector->Bpb.Ebpb.achType, RT_STR_TUPLE("HPFS ")) != 0)
+ return false;
+
+ /* Superblock is at sector 16, spare superblock at 17. */
+ int rc = RTVfsFileReadAt(hVfsFileIn, 16 * 512, pBuf2, 512 * 2, NULL);
+ if (RT_FAILURE(rc))
+ {
+ Log2(("rtVfsMountIsHpfs: Error reading superblock: %Rrc\n", rc));
+ return false;
+ }
+
+ if ( RT_LE2H_U32(pBuf2->au32[0]) != UINT32_C(0xf995e849)
+ || RT_LE2H_U32(pBuf2->au32[1]) != UINT32_C(0xfa53e9c5)
+ || RT_LE2H_U32(pBuf2->au32[512/4 + 0]) != UINT32_C(0xf9911849)
+ || RT_LE2H_U32(pBuf2->au32[512/4 + 1]) != UINT32_C(0xfa5229c5))
+ {
+ Log2(("rtVfsMountIsHpfs: Superblock or spare superblock signature mismatch: %#x %#x %#x %#x\n",
+ RT_LE2H_U32(pBuf2->au32[0]), RT_LE2H_U32(pBuf2->au32[1]),
+ RT_LE2H_U32(pBuf2->au32[512/4 + 0]), RT_LE2H_U32(pBuf2->au32[512/4 + 1]) ));
+ return false;
+ }
+
+ return true;
+}
+
+
+/**
+ * Check if the given bootsector is a FAT boot sector.
+ *
+ * @returns true if NTFS, false if not.
+ * @param pBootSector The boot sector to inspect.
+ * @param pbRaw Pointer to the raw boot sector buffer.
+ * @param cbRaw Number of bytes read starting with the boot
+ * sector (which @a pbRaw points to).
+ * @param hVfsFileIn The volume file.
+ * @param pBuf2 A 2nd buffer.
+ */
+static bool rtVfsMountIsFat(PCFATBOOTSECTOR pBootSector, uint8_t const *pbRaw, size_t cbRaw,
+ RTVFSFILE hVfsFileIn, PRTVFSMOUNTBUF pBuf2)
+{
+ Assert(cbRaw >= 1024);
+
+ /*
+ * Check the DOS signature first. The PC-DOS 1.0 boot floppy does not have
+ * a signature and we ASSUME this is the case for all floppies formated by it.
+ */
+ if (pBootSector->uSignature != FATBOOTSECTOR_SIGNATURE)
+ {
+ if (pBootSector->uSignature != 0)
+ return false;
+
+ /*
+ * PC-DOS 1.0 does a 2fh byte short jump w/o any NOP following it.
+ * Instead the following are three words and a 9 byte build date
+ * string. The remaining space is zero filled.
+ *
+ * Note! No idea how this would look like for 8" floppies, only got 5"1/4'.
+ *
+ * ASSUME all non-BPB disks are using this format.
+ */
+ if ( pBootSector->abJmp[0] != 0xeb /* jmp rel8 */
+ || pBootSector->abJmp[1] < 0x2f
+ || pBootSector->abJmp[1] >= 0x80
+ || pBootSector->abJmp[2] == 0x90 /* nop */)
+ {
+ Log2(("rtVfsMountIsFat: No DOS v1.0 bootsector either - invalid jmp: %.3Rhxs\n", pBootSector->abJmp));
+ return false;
+ }
+
+ /* Check the FAT ID so we can tell if this is double or single sided, as well as being a valid FAT12 start. */
+ if ( (pbRaw[512] != 0xfe && pbRaw[0] != 0xff)
+ || pbRaw[512 + 1] != 0xff
+ || pbRaw[512 + 2] != 0xff)
+ {
+ Log2(("rtVfsMountIsFat: No DOS v1.0 bootsector either - unexpected start of FAT: %.3Rhxs\n", &pbRaw[512]));
+ return false;
+ }
+
+ uint32_t const offJump = 2 + pBootSector->abJmp[1];
+ uint32_t const offFirstZero = 2 /*jmp */ + 3 * 2 /* words */ + 9 /* date string */;
+ Assert(offFirstZero >= RT_UOFFSETOF(FATBOOTSECTOR, Bpb));
+ uint32_t const cbZeroPad = RT_MIN(offJump - offFirstZero,
+ sizeof(pBootSector->Bpb.Bpb20) - (offFirstZero - RT_UOFFSETOF(FATBOOTSECTOR, Bpb)));
+
+ if (!ASMMemIsAllU8((uint8_t const *)pBootSector + offFirstZero, cbZeroPad, 0))
+ {
+ Log2(("rtVfsMountIsFat: No DOS v1.0 bootsector either - expected zero padding %#x LB %#x: %.*Rhxs\n",
+ offFirstZero, cbZeroPad, cbZeroPad, (uint8_t const *)pBootSector + offFirstZero));
+ return false;
+ }
+ }
+ else
+ {
+ /*
+ * DOS 2.0 or later.
+ *
+ * Start by checking if we've got a known jump instruction first, because
+ * that will give us a max (E)BPB size hint.
+ */
+ uint8_t offJmp = UINT8_MAX;
+ if ( pBootSector->abJmp[0] == 0xeb
+ && pBootSector->abJmp[1] <= 0x7f)
+ offJmp = pBootSector->abJmp[1] + 2;
+ else if ( pBootSector->abJmp[0] == 0x90
+ && pBootSector->abJmp[1] == 0xeb
+ && pBootSector->abJmp[2] <= 0x7f)
+ offJmp = pBootSector->abJmp[2] + 3;
+ else if ( pBootSector->abJmp[0] == 0xe9
+ && pBootSector->abJmp[2] <= 0x7f)
+ offJmp = RT_MIN(127, RT_MAKE_U16(pBootSector->abJmp[1], pBootSector->abJmp[2]));
+ uint8_t const cbMaxBpb = offJmp - RT_UOFFSETOF(FATBOOTSECTOR, Bpb);
+ if (cbMaxBpb < sizeof(FATBPB20))
+ {
+ Log2(("rtVfsMountIsFat: DOS signature, but jmp too short for any BPB: %#x (max %#x BPB)\n", offJmp, cbMaxBpb));
+ return false;
+ }
+
+ if ( pBootSector->Bpb.Bpb20.cFats == 0
+ || pBootSector->Bpb.Bpb20.cFats > 4)
+ {
+ if (pBootSector->Bpb.Bpb20.cFats == 0)
+ Log2(("rtVfsMountIsFat: DOS signature, number of FATs is zero, so not FAT file system\n"));
+ else
+ Log2(("rtVfsMountIsFat: DOS signature, too many FATs: %#x\n", pBootSector->Bpb.Bpb20.cFats));
+ return false;
+ }
+
+ if (!FATBPB_MEDIA_IS_VALID(pBootSector->Bpb.Bpb20.bMedia))
+ {
+ Log2(("rtVfsMountIsFat: DOS signature, invalid media byte: %#x\n", pBootSector->Bpb.Bpb20.bMedia));
+ return false;
+ }
+
+ uint16_t cbSector = RT_LE2H_U16(pBootSector->Bpb.Bpb20.cbSector);
+ if ( cbSector != 512
+ && cbSector != 4096
+ && cbSector != 1024
+ && cbSector != 128)
+ {
+ Log2(("rtVfsMountIsFat: DOS signature, unsupported sector size: %#x\n", cbSector));
+ return false;
+ }
+
+ if ( !RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cSectorsPerCluster)
+ || !pBootSector->Bpb.Bpb20.cSectorsPerCluster)
+ {
+ Log2(("rtVfsMountIsFat: DOS signature, cluster size not non-zero power of two: %#x",
+ pBootSector->Bpb.Bpb20.cSectorsPerCluster));
+ return false;
+ }
+
+ uint16_t const cReservedSectors = RT_LE2H_U16(pBootSector->Bpb.Bpb20.cReservedSectors);
+ if ( cReservedSectors == 0
+ || cReservedSectors >= _32K)
+ {
+ Log2(("rtVfsMountIsFat: DOS signature, bogus reserved sector count: %#x\n", cReservedSectors));
+ return false;
+ }
+
+ /*
+ * Match the media byte with the first FAT byte and check that the next
+ * 4 bits are set. (To match further bytes in the FAT we'd need to
+ * determin the FAT type, which is too much hazzle to do here.)
+ */
+ uint8_t const *pbFat;
+ if ((size_t)cReservedSectors * cbSector < cbRaw)
+ pbFat = &pbRaw[cReservedSectors * cbSector];
+ else
+ {
+ int rc = RTVfsFileReadAt(hVfsFileIn, cReservedSectors * cbSector, pBuf2, 512, NULL);
+ if (RT_FAILURE(rc))
+ {
+ Log2(("rtVfsMountIsFat: error reading first FAT sector at %#x: %Rrc\n", cReservedSectors * cbSector, rc));
+ return false;
+ }
+ pbFat = pBuf2->ab;
+ }
+ if (*pbFat != pBootSector->Bpb.Bpb20.bMedia)
+ {
+ Log2(("rtVfsMountIsFat: Media byte and FAT ID mismatch: %#x vs %#x (%.8Rhxs)\n",
+ pbFat[0], pBootSector->Bpb.Bpb20.bMedia, pbFat));
+ return false;
+ }
+ if ((pbFat[1] & 0xf) != 0xf)
+ {
+ Log2(("rtVfsMountIsFat: Media byte and FAT ID mismatch: %#x vs %#x (%.8Rhxs)\n",
+ pbFat[0], pBootSector->Bpb.Bpb20.bMedia, pbFat));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Check if the given bootsector is an ext2/3/4 super block.
+ *
+ * @returns true if NTFS, false if not.
+ * @param pSuperBlock The ext2 superblock.
+ */
+static bool rtVfsMountIsExt(PCEXTSUPERBLOCK pSuperBlock)
+{
+ if (RT_LE2H_U16(pSuperBlock->u16Signature) != EXT_SB_SIGNATURE)
+ return false;
+
+ uint32_t cShift = RT_LE2H_U32(pSuperBlock->cLogBlockSize);
+ if (cShift > 54)
+ {
+ Log2(("rtVfsMountIsExt: cLogBlockSize=%#x: out of range\n", cShift));
+ return false;
+ }
+
+ cShift = RT_LE2H_U32(pSuperBlock->cLogClusterSize);
+ if (cShift > 54)
+ {
+ Log2(("rtVfsMountIsExt: cLogClusterSize=%#x: out of range\n", cShift));
+ return false;
+ }
+
+ /* Some more checks here would be nice actually since a 16-bit word and a
+ couple of field limits doesn't feel all that conclusive. */
+
+ return true;
+}
+
+
+/**
+ * Does the file system detection and mounting.
+ *
+ * Since we only support a handful of file systems at the moment and the
+ * interface isn't yet extensible in any way, we combine the file system
+ * recognition code for all. This reduces the number of reads we need to do and
+ * avoids unnecessary processing.
+ *
+ * @returns IPRT status code.
+ * @param hVfsFileIn The volume file.
+ * @param fFlags RTVFSMTN_F_XXX.
+ * @param pBuf Pointer to the primary buffer
+ * @param pBuf2 Pointer to the secondary buffer.
+ * @param phVfs Where to return the .
+ * @param pErrInfo Where to return additional error information.
+ * Optional.
+ */
+static int rtVfsMountInner(RTVFSFILE hVfsFileIn, uint32_t fFlags, RTVFSMOUNTBUF *pBuf,
+ RTVFSMOUNTBUF *pBuf2, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ AssertCompile(sizeof(*pBuf) >= ISO9660_SECTOR_SIZE);
+
+ /* Start by checking for ISO-9660 and UDFS since these may have confusing
+ data at the start of the volume. */
+ int rc = RTVfsFileReadAt(hVfsFileIn, _32K, pBuf, ISO9660_SECTOR_SIZE, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (rtVfsMountIsIsoFs(&pBuf->IsoHdr))
+ {
+ Log(("RTVfsMount: Detected ISO-9660 or UDF.\n"));
+ return RTFsIso9660VolOpen(hVfsFileIn, 0 /*fFlags*/, phVfs, pErrInfo);
+ }
+ }
+
+ /* Now read the boot sector and whatever the next 1536 bytes may contain.
+ With ext2 superblock at 1024, we can recognize quite a bit thru this read. */
+ rc = RTVfsFileReadAt(hVfsFileIn, 0, pBuf, sizeof(*pBuf), NULL);
+ if (RT_FAILURE(rc))
+ return RTErrInfoSet(pErrInfo, rc, "Error reading boot sector");
+
+ if (rtVfsMountIsNtfs(&pBuf->Bootsector))
+ return RTFsNtfsVolOpen(hVfsFileIn, fFlags, 0 /*fNtfsFlags*/, phVfs, pErrInfo);
+
+ if (rtVfsMountIsHpfs(&pBuf->Bootsector, hVfsFileIn, pBuf2))
+ return RTERRINFO_LOG_SET(pErrInfo, VERR_VFS_UNSUPPORTED_FORMAT, "HPFS not yet supported");
+
+ if (rtVfsMountIsFat(&pBuf->Bootsector, pBuf->ab, sizeof(*pBuf), hVfsFileIn, pBuf2))
+ {
+ Log(("RTVfsMount: Detected ISO-9660 or UDF.\n"));
+ return RTFsFatVolOpen(hVfsFileIn, RT_BOOL(fFlags & RTVFSMNT_F_READ_ONLY), 0 /*offBootSector*/, phVfs, pErrInfo);
+ }
+
+ AssertCompile(sizeof(*pBuf) >= 1024 + sizeof(EXTSUPERBLOCK));
+ if (rtVfsMountIsExt((PCEXTSUPERBLOCK)&pBuf->ab[1024]))
+ {
+ Log(("RTVfsMount: Detected EXT2/3/4.\n"));
+ return RTFsExtVolOpen(hVfsFileIn, fFlags, 0 /*fExt2Flags*/, phVfs, pErrInfo);
+ }
+
+ return VERR_VFS_UNSUPPORTED_FORMAT;
+}
+
+
+RTDECL(int) RTVfsMountVol(RTVFSFILE hVfsFileIn, uint32_t fFlags, PRTVFS phVfs, PRTERRINFO pErrInfo)
+{
+ AssertReturn(!(fFlags & ~RTVFSMNT_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertPtrReturn(hVfsFileIn, VERR_INVALID_HANDLE);
+ AssertPtrReturn(phVfs, VERR_INVALID_HANDLE);
+
+ *phVfs = NIL_RTVFS;
+
+ RTVFSMOUNTBUF *pBufs = (RTVFSMOUNTBUF *)RTMemTmpAlloc(sizeof(*pBufs) * 2);
+ AssertReturn(pBufs, VERR_NO_TMP_MEMORY);
+
+ int rc = rtVfsMountInner(hVfsFileIn, fFlags, pBufs, pBufs + 1, phVfs, pErrInfo);
+
+ RTMemTmpFree(pBufs);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainMountVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
+ return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
+ if ( pElement->enmType != RTVFSOBJTYPE_VFS
+ && pElement->enmType != RTVFSOBJTYPE_DIR)
+ return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Parse the flag if present, save in pElement->uProvider.
+ */
+ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ if (!strcmp(psz, "ro"))
+ fReadOnly = true;
+ else if (!strcmp(psz, "rw"))
+ fReadOnly = false;
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
+ }
+ }
+ }
+
+ pElement->uProvider = fReadOnly ? RTVFSMNT_F_READ_ONLY : 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainMountVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError);
+
+ int rc;
+ RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
+ if (hVfsFileIn != NIL_RTVFSFILE)
+ {
+ RTVFS hVfs;
+ rc = RTVfsMountVol(hVfsFileIn, (uint32_t)pElement->uProvider, &hVfs, pErrInfo);
+ RTVfsFileRelease(hVfsFileIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromVfs(hVfs);
+ RTVfsRelease(hVfs);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainMountVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider
+ || !pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'file'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainMountVolReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "mount",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a file system, requires a file object on the left side.\n"
+ "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
+ /* pfnValidate = */ rtVfsChainMountVol_Validate,
+ /* pfnInstantiate = */ rtVfsChainMountVol_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainMountVol_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainMountVolReg, rtVfsChainMountVolReg);
+
diff --git a/src/VBox/Runtime/common/vfs/vfsmsg.cpp b/src/VBox/Runtime/common/vfs/vfsmsg.cpp
new file mode 100644
index 00000000..52353786
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsmsg.cpp
@@ -0,0 +1,78 @@
+/* $Id: vfsmsg.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Error Messaging for Chains.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+
+#include <iprt/errcore.h>
+#include <iprt/message.h>
+
+
+RTDECL(void) RTVfsChainMsgError(const char *pszFunction, const char *pszSpec, int rc, uint32_t offError, PRTERRINFO pErrInfo)
+{
+ if (RTErrInfoIsSet(pErrInfo))
+ {
+ if (offError > 0)
+ RTMsgError("%s failed with rc=%Rrc: %s\n"
+ " '%s'\n"
+ " %*s^\n",
+ pszFunction, rc, pErrInfo->pszMsg, pszSpec, offError, "");
+ else
+ RTMsgError("%s failed to open '%s': %Rrc: %s\n", pszFunction, pszSpec, rc, pErrInfo->pszMsg);
+ }
+ else
+ {
+ if (offError > 0)
+ RTMsgError("%s failed with rc=%Rrc:\n"
+ " '%s'\n"
+ " %*s^\n",
+ pszFunction, rc, pszSpec, offError, "");
+ else
+ RTMsgError("%s failed to open '%s': %Rrc\n", pszFunction, pszSpec, rc);
+ }
+}
+
+
+RTDECL(RTEXITCODE) RTVfsChainMsgErrorExitFailure(const char *pszFunction, const char *pszSpec,
+ int rc, uint32_t offError, PRTERRINFO pErrInfo)
+{
+ RTVfsChainMsgError(pszFunction, pszSpec, rc, offError, pErrInfo);
+ return RTEXITCODE_FAILURE;
+}
+
diff --git a/src/VBox/Runtime/common/vfs/vfsprintf.cpp b/src/VBox/Runtime/common/vfs/vfsprintf.cpp
new file mode 100644
index 00000000..496eedb4
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsprintf.cpp
@@ -0,0 +1,165 @@
+/* $Id: vfsprintf.cpp $ */
+/** @file
+ * IPRT - Virtual File System, File Printf.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+
+#include <iprt/errcore.h>
+#include <iprt/string.h>
+
+
+/** Writes the buffer to the VFS file. */
+static void FlushPrintfBuffer(PVFSIOSTRMOUTBUF pBuf)
+{
+ if (pBuf->offBuf)
+ {
+ int rc = RTVfsIoStrmWrite(pBuf->hVfsIos, pBuf->szBuf, pBuf->offBuf, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ pBuf->rc = rc;
+ pBuf->offBuf = 0;
+ pBuf->szBuf[0] = '\0';
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNRTSTROUTPUT,
+ * For use with VFSIOSTRMOUTBUF.}
+ */
+RTDECL(size_t) RTVfsIoStrmStrOutputCallback(void *pvArg, const char *pachChars, size_t cbChars)
+{
+ PVFSIOSTRMOUTBUF pBuf = (PVFSIOSTRMOUTBUF)pvArg;
+ AssertReturn(pBuf->cbSelf == sizeof(*pBuf), 0);
+
+ if (cbChars != 0)
+ {
+ if (cbChars <= sizeof(pBuf->szBuf) * 3 / 2)
+ {
+ /*
+ * Small piece of output: Buffer it.
+ */
+ size_t offSrc = 0;
+ while (offSrc < cbChars)
+ {
+ size_t cbLeft = sizeof(pBuf->szBuf) - pBuf->offBuf - 1;
+ if (cbLeft > 0)
+ {
+ size_t cbToCopy = RT_MIN(cbChars - offSrc, cbLeft);
+ memcpy(&pBuf->szBuf[pBuf->offBuf], &pachChars[offSrc], cbToCopy);
+ pBuf->offBuf += cbToCopy;
+ pBuf->szBuf[pBuf->offBuf] = '\0';
+ if (cbLeft > cbToCopy)
+ break;
+ offSrc += cbToCopy;
+ }
+ FlushPrintfBuffer(pBuf);
+ }
+ }
+ else
+ {
+ /*
+ * Large chunk of output: Output it directly.
+ */
+ FlushPrintfBuffer(pBuf);
+
+ int rc = RTVfsIoStrmWrite(pBuf->hVfsIos, pachChars, cbChars, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ pBuf->rc = rc;
+ }
+ }
+ else /* Special zero byte write at the end of the formatting. */
+ FlushPrintfBuffer(pBuf);
+ return cbChars;
+}
+
+
+RTDECL(ssize_t) RTVfsIoStrmPrintfV(RTVFSIOSTREAM hVfsIos, const char *pszFormat, va_list va)
+{
+ VFSIOSTRMOUTBUF Buf;
+ VFSIOSTRMOUTBUF_INIT(&Buf, hVfsIos);
+
+ size_t cchRet = RTStrFormatV(RTVfsIoStrmStrOutputCallback, &Buf, NULL, NULL, pszFormat, va);
+ if (RT_SUCCESS(Buf.rc))
+ return cchRet;
+ return Buf.rc;
+}
+
+
+RTDECL(ssize_t) RTVfsIoStrmPrintf(RTVFSIOSTREAM hVfsIos, const char *pszFormat, ...)
+{
+ va_list va;
+ va_start(va, pszFormat);
+ ssize_t cchRet = RTVfsIoStrmPrintfV(hVfsIos, pszFormat, va);
+ va_end(va);
+ return cchRet;
+}
+
+
+RTDECL(ssize_t) RTVfsFilePrintfV(RTVFSFILE hVfsFile, const char *pszFormat, va_list va)
+{
+ ssize_t cchRet;
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ if (hVfsIos != NIL_RTVFSIOSTREAM)
+ {
+ cchRet = RTVfsIoStrmPrintfV(hVfsIos, pszFormat, va);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else
+ cchRet = VERR_INVALID_HANDLE;
+ return cchRet;
+}
+
+
+RTDECL(ssize_t) RTVfsFilePrintf(RTVFSFILE hVfsFile, const char *pszFormat, ...)
+{
+ ssize_t cchRet;
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ if (hVfsIos != NIL_RTVFSIOSTREAM)
+ {
+ va_list va;
+ va_start(va, pszFormat);
+ cchRet = RTVfsIoStrmPrintfV(hVfsIos, pszFormat, va);
+ va_end(va);
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else
+ cchRet = VERR_INVALID_HANDLE;
+ return cchRet;
+}
+
diff --git a/src/VBox/Runtime/common/vfs/vfsprogress.cpp b/src/VBox/Runtime/common/vfs/vfsprogress.cpp
new file mode 100644
index 00000000..d6ef6533
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsprogress.cpp
@@ -0,0 +1,554 @@
+/* $Id: vfsprogress.cpp $ */
+/** @file
+ * IPRT - Virtual File System, progress filter for files.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include <iprt/assert.h>
+#include <iprt/errcore.h>
+#include <iprt/file.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Private data of a standard file.
+ */
+typedef struct RTVFSPROGRESSFILE
+{
+ /** This is negative (RT_FAILURE) if canceled. */
+ int rcCanceled;
+ /** RTVFSPROGRESS_F_XXX. */
+ uint32_t fFlags;
+ /** Progress callback. */
+ PFNRTPROGRESS pfnProgress;
+ /** User argument for the callback. */
+ void *pvUser;
+ /** The I/O stream handle. */
+ RTVFSIOSTREAM hVfsIos;
+ /** The file handle. NIL_RTFILE if a pure I/O stream. */
+ RTVFSFILE hVfsFile;
+ /** Total number of bytes expected to be read and written. */
+ uint64_t cbExpected;
+ /** The number of bytes expected to be read. */
+ uint64_t cbExpectedRead;
+ /** The number of bytes expected to be written. */
+ uint64_t cbExpectedWritten;
+ /** Number of bytes currently read. */
+ uint64_t cbCurrentlyRead;
+ /** Number of bytes currently written. */
+ uint64_t cbCurrentlyWritten;
+ /** Current precentage. */
+ unsigned uCurPct;
+} RTVFSPROGRESSFILE;
+/** Pointer to the private data of a standard file. */
+typedef RTVFSPROGRESSFILE *PRTVFSPROGRESSFILE;
+
+
+/**
+ * Update the progress and do the progress callback if necessary.
+ *
+ * @returns Callback return code.
+ * @param pThis The file progress instance.
+ */
+static int rtVfsProgressFile_UpdateProgress(PRTVFSPROGRESSFILE pThis)
+{
+ uint64_t cbDone = RT_MIN(pThis->cbCurrentlyRead, pThis->cbExpectedRead)
+ + RT_MIN(pThis->cbCurrentlyWritten, pThis->cbExpectedWritten);
+ unsigned uPct = cbDone * 100 / pThis->cbExpected;
+ if (uPct == pThis->uCurPct)
+ return pThis->rcCanceled;
+ pThis->uCurPct = uPct;
+
+ int rc = pThis->pfnProgress(uPct, pThis->pvUser);
+ if (!(pThis->fFlags & RTVFSPROGRESS_F_CANCELABLE))
+ rc = VINF_SUCCESS;
+ else if (RT_FAILURE(rc) && RT_SUCCESS(pThis->rcCanceled))
+ pThis->rcCanceled = rc;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_Close(void *pvThis)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+
+ if (pThis->hVfsFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pThis->hVfsFile);
+ pThis->hVfsFile = NIL_RTVFSFILE;
+ }
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ pThis->pfnProgress = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ int rc = pThis->rcCanceled;
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+
+ int rc = pThis->rcCanceled;
+ if (RT_SUCCESS(rc))
+ {
+ /* Simplify a little there if a seeks is implied and assume the read goes well. */
+ if ( off >= 0
+ && (pThis->fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ))
+ {
+ uint64_t offCurrent = RTVfsFileTell(pThis->hVfsFile);
+ if (offCurrent < (uint64_t)off)
+ pThis->cbCurrentlyRead += off - offCurrent;
+ }
+
+ /* Calc the request before calling down the stack. */
+ size_t cbReq = 0;
+ unsigned i = pSgBuf->cSegs;
+ while (i-- > 0)
+ cbReq += pSgBuf->paSegs[i].cbSeg;
+
+ /* Do the read. */
+ rc = RTVfsIoStrmSgRead(pThis->hVfsIos, off, pSgBuf, fBlocking, pcbRead);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update the progress (we cannot cancel here, sorry). */
+ pThis->cbCurrentlyRead += pcbRead ? *pcbRead : cbReq;
+ rtVfsProgressFile_UpdateProgress(pThis);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+
+ int rc = pThis->rcCanceled;
+ if (RT_SUCCESS(rc))
+ {
+ /* Simplify a little there if a seeks is implied and assume the write goes well. */
+ if ( off >= 0
+ && (pThis->fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE))
+ {
+ uint64_t offCurrent = RTVfsFileTell(pThis->hVfsFile);
+ if (offCurrent < (uint64_t)off)
+ pThis->cbCurrentlyWritten += off - offCurrent;
+ }
+
+ /* Calc the request before calling down the stack. */
+ size_t cbReq = 0;
+ unsigned i = pSgBuf->cSegs;
+ while (i-- > 0)
+ cbReq += pSgBuf->paSegs[i].cbSeg;
+
+ /* Do the read. */
+ rc = RTVfsIoStrmSgWrite(pThis->hVfsIos, off, pSgBuf, fBlocking, pcbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update the progress (we cannot cancel here, sorry). */
+ pThis->cbCurrentlyWritten += pcbWritten ? *pcbWritten : cbReq;
+ rtVfsProgressFile_UpdateProgress(pThis);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_Flush(void *pvThis)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ int rc = pThis->rcCanceled;
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmFlush(pThis->hVfsIos);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int)
+rtVfsProgressFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ int rc = pThis->rcCanceled;
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+ else
+ {
+ *pfRetEvents |= RTPOLL_EVT_ERROR;
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ *poffActual = RTVfsIoStrmTell(pThis->hVfsIos);
+ return *poffActual >= 0 ? VINF_SUCCESS : (int)*poffActual;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnSkip}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_Skip(void *pvThis, RTFOFF cb)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ int rc = pThis->rcCanceled;
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmSkip(pThis->hVfsIos, cb);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbCurrentlyRead += cb;
+ rtVfsProgressFile_UpdateProgress(pThis);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnZeroFill}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_ZeroFill(void *pvThis, RTFOFF cb)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ int rc = pThis->rcCanceled;
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmZeroFill(pThis->hVfsIos, cb);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbCurrentlyWritten += cb;
+ rtVfsProgressFile_UpdateProgress(pThis);
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * I/O stream progress operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_rtVfsProgressIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "I/O Stream Progress",
+ rtVfsProgressFile_Close,
+ rtVfsProgressFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ 0,
+ rtVfsProgressFile_Read,
+ rtVfsProgressFile_Write,
+ rtVfsProgressFile_Flush,
+ rtVfsProgressFile_PollOne,
+ rtVfsProgressFile_Tell,
+ rtVfsProgressFile_Skip,
+ rtVfsProgressFile_ZeroFill,
+ RTVFSIOSTREAMOPS_VERSION,
+};
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ //return RTVfsFileSetMode(pThis->hVfsIos, fMode, fMask); - missing
+ RT_NOREF(pThis, fMode, fMask);
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ //return RTVfsFileSetTimes(pThis->hVfsIos, pAccessTime, pModificationTime, pChangeTime, pBirthTime); - missing
+ RT_NOREF(pThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ //return RTVfsFileSetOwern(pThis->hVfsIos, uid, gid); - missing
+ RT_NOREF(pThis, uid, gid);
+ AssertFailedReturn(VERR_NOT_IMPLEMENTED);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+
+ uint64_t offPrev = UINT64_MAX;
+ if (pThis->fFlags & (RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ | RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE))
+ offPrev = RTVfsFileTell(pThis->hVfsFile);
+
+ uint64_t offActual = 0;
+ int rc = RTVfsFileSeek(pThis->hVfsFile, offSeek, uMethod, &offActual);
+ if (RT_SUCCESS(rc))
+ {
+ if (poffActual)
+ *poffActual = offActual;
+
+ /* Do progress updates as requested. */
+ if (pThis->fFlags & (RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ | RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE))
+ {
+ if (offActual > offPrev)
+ {
+ if (pThis->fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ)
+ pThis->cbCurrentlyRead += offActual - offPrev;
+ else
+ pThis->cbCurrentlyWritten += offActual - offPrev;
+ rtVfsProgressFile_UpdateProgress(pThis);
+ }
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ return RTVfsFileQuerySize(pThis->hVfsFile, pcbFile);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ return RTVfsFileSetSize(pThis->hVfsFile, cbFile, fFlags);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtVfsProgressFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ PRTVFSPROGRESSFILE pThis = (PRTVFSPROGRESSFILE)pvThis;
+ return RTVfsFileQueryMaxSize(pThis->hVfsFile, pcbMax);
+}
+
+
+
+/**
+ * File progress operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtVfsProgressFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "File Progress",
+ rtVfsProgressFile_Close,
+ rtVfsProgressFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ 0,
+ rtVfsProgressFile_Read,
+ rtVfsProgressFile_Write,
+ rtVfsProgressFile_Flush,
+ rtVfsProgressFile_PollOne,
+ rtVfsProgressFile_Tell,
+ rtVfsProgressFile_Skip,
+ rtVfsProgressFile_ZeroFill,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtVfsProgressFile_SetMode,
+ rtVfsProgressFile_SetTimes,
+ rtVfsProgressFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtVfsProgressFile_Seek,
+ rtVfsProgressFile_QuerySize,
+ rtVfsProgressFile_SetSize,
+ rtVfsProgressFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+RTDECL(int) RTVfsCreateProgressForIoStream(RTVFSIOSTREAM hVfsIos, PFNRTPROGRESS pfnProgress, void *pvUser, uint32_t fFlags,
+ uint64_t cbExpectedRead, uint64_t cbExpectedWritten, PRTVFSIOSTREAM phVfsIos)
+{
+ AssertPtrReturn(pfnProgress, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTVFSPROGRESS_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ) || !(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE),
+ VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIos);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ PRTVFSPROGRESSFILE pThis;
+ int rc = RTVfsNewIoStream(&g_rtVfsProgressIosOps, sizeof(*pThis), RTVfsIoStrmGetOpenFlags(hVfsIos),
+ NIL_RTVFS, NIL_RTVFSLOCK, phVfsIos, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->rcCanceled = VINF_SUCCESS;
+ pThis->fFlags = fFlags;
+ pThis->pfnProgress = pfnProgress;
+ pThis->pvUser = pvUser;
+ pThis->hVfsIos = hVfsIos;
+ pThis->hVfsFile = RTVfsIoStrmToFile(hVfsIos);
+ pThis->cbCurrentlyRead = 0;
+ pThis->cbCurrentlyWritten = 0;
+ pThis->cbExpectedRead = cbExpectedRead;
+ pThis->cbExpectedWritten = cbExpectedWritten;
+ pThis->cbExpected = cbExpectedRead + cbExpectedWritten;
+ if (!pThis->cbExpected)
+ pThis->cbExpected = 1;
+ pThis->uCurPct = 0;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsCreateProgressForFile(RTVFSFILE hVfsFile, PFNRTPROGRESS pfnProgress, void *pvUser, uint32_t fFlags,
+ uint64_t cbExpectedRead, uint64_t cbExpectedWritten, PRTVFSFILE phVfsFile)
+{
+ AssertPtrReturn(pfnProgress, VERR_INVALID_POINTER);
+ AssertReturn(!(fFlags & ~RTVFSPROGRESS_F_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_READ) || !(fFlags & RTVFSPROGRESS_F_FORWARD_SEEK_AS_WRITE),
+ VERR_INVALID_FLAGS);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ AssertReturnStmt(hVfsIos != NIL_RTVFSIOSTREAM, RTVfsFileRelease(hVfsFile), VERR_INVALID_HANDLE);
+
+ PRTVFSPROGRESSFILE pThis;
+ int rc = RTVfsNewFile(&g_rtVfsProgressFileOps, sizeof(*pThis), RTVfsFileGetOpenFlags(hVfsFile),
+ NIL_RTVFS, NIL_RTVFSLOCK, phVfsFile, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->fFlags = fFlags;
+ pThis->pfnProgress = pfnProgress;
+ pThis->pvUser = pvUser;
+ pThis->hVfsIos = hVfsIos;
+ pThis->hVfsFile = hVfsFile;
+ pThis->cbCurrentlyRead = 0;
+ pThis->cbCurrentlyWritten = 0;
+ pThis->cbExpectedRead = cbExpectedRead;
+ pThis->cbExpectedWritten = cbExpectedWritten;
+ pThis->cbExpected = cbExpectedRead + cbExpectedWritten;
+ if (!pThis->cbExpected)
+ pThis->cbExpected = 1;
+ pThis->uCurPct = 0;
+ }
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/vfs/vfsreadahead.cpp b/src/VBox/Runtime/common/vfs/vfsreadahead.cpp
new file mode 100644
index 00000000..2a427c77
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsreadahead.cpp
@@ -0,0 +1,1013 @@
+/* $Id: vfsreadahead.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Read-Ahead Thread.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_VFS
+#include "internal/iprt.h"
+#include <iprt/vfs.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/list.h>
+#include <iprt/log.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/vfslowlevel.h>
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/vfs.h>
+
+#include <iprt/critsect.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Buffer descriptor.
+ */
+typedef struct RTVFSREADAHEADBUFDESC
+{
+ /** List entry. */
+ RTLISTNODE ListEntry;
+ /** The offset of this extent within the file. */
+ uint64_t off;
+ /** The amount of the buffer that has been filled.
+ * (Buffer size is RTVFSREADAHEAD::cbBuffer.) */
+ uint32_t cbFilled;
+ /** */
+ uint32_t volatile fReserved;
+ /** Pointer to the buffer. */
+ uint8_t *pbBuffer;
+} RTVFSREADAHEADBUFDESC;
+/** Pointer to a memory file extent. */
+typedef RTVFSREADAHEADBUFDESC *PRTVFSREADAHEADBUFDESC;
+
+/**
+ * Read ahead file or I/O stream.
+ */
+typedef struct RTVFSREADAHEAD
+{
+ /** The I/O critical section (protects offActual).
+ * The thread doing I/O or seeking always need to own this. */
+ RTCRITSECT IoCritSect;
+
+ /** The critical section protecting the buffer lists and offConsumer.
+ *
+ * This can be taken while holding IoCritSect as that eliminates a race
+ * condition between the read ahead thread inserting into ConsumerList and
+ * a consumer thread deciding to do a direct read. */
+ RTCRITSECT BufferCritSect;
+ /** List of buffers available for consumption.
+ * The producer thread (hThread) puts buffers into this list once it's done
+ * reading into them. The consumer moves them to the FreeList once the
+ * current position has passed beyond each buffer. */
+ RTLISTANCHOR ConsumerList;
+ /** List of buffers available for the producer. */
+ RTLISTANCHOR FreeList;
+
+ /** The current file position from the consumer point of view. */
+ uint64_t offConsumer;
+
+ /** The end-of-file(/stream) offset. This is initially UINT64_MAX and later
+ * set when reading past EOF. */
+ uint64_t offEof;
+
+ /** The read ahead thread. */
+ RTTHREAD hThread;
+ /** Set when we want the thread to terminate. */
+ bool volatile fTerminateThread;
+ /** Creation flags. */
+ uint32_t fFlags;
+
+ /** The I/O stream we read from. */
+ RTVFSIOSTREAM hIos;
+ /** The file face of hIos, if we're fronting for an actual file. */
+ RTVFSFILE hFile;
+ /** The buffer size. */
+ uint32_t cbBuffer;
+ /** The number of buffers. */
+ uint32_t cBuffers;
+ /** Single big buffer allocation, cBuffers * cbBuffer in size. */
+ uint8_t *pbAllBuffers;
+ /** Array of buffer descriptors (cBuffers in size). */
+ RTVFSREADAHEADBUFDESC aBufDescs[1];
+} RTVFSREADAHEAD;
+/** Pointer to a memory file. */
+typedef RTVFSREADAHEAD *PRTVFSREADAHEAD;
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_Close(void *pvThis)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ int rc;
+
+ /*
+ * Stop the read-ahead thread.
+ */
+ if (pThis->hThread != NIL_RTTHREAD)
+ {
+ ASMAtomicWriteBool(&pThis->fTerminateThread, true);
+ rc = RTThreadUserSignal(pThis->hThread);
+ AssertRC(rc);
+ rc = RTThreadWait(pThis->hThread, RT_INDEFINITE_WAIT, NULL);
+ AssertRCReturn(rc, rc);
+ pThis->hThread = NIL_RTTHREAD;
+ }
+
+ /*
+ * Release the upstream objects.
+ */
+ RTCritSectEnter(&pThis->IoCritSect);
+
+ RTVfsIoStrmRelease(pThis->hIos);
+ pThis->hIos = NIL_RTVFSIOSTREAM;
+ RTVfsFileRelease(pThis->hFile);
+ pThis->hFile = NIL_RTVFSFILE;
+
+ RTCritSectLeave(&pThis->IoCritSect);
+
+ /*
+ * Free the buffers.
+ */
+ RTCritSectEnter(&pThis->BufferCritSect);
+ if (pThis->pbAllBuffers)
+ {
+ RTMemPageFree(pThis->pbAllBuffers, pThis->cBuffers * pThis->cbBuffer);
+ pThis->pbAllBuffers = NULL;
+ }
+ RTCritSectLeave(&pThis->BufferCritSect);
+
+ /*
+ * Destroy the critical sections.
+ */
+ RTCritSectDelete(&pThis->BufferCritSect);
+ RTCritSectDelete(&pThis->IoCritSect);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ return RTVfsIoStrmQueryInfo(pThis->hIos, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+
+ Assert(pSgBuf->cSegs == 1); /* Caller deals with multiple SGs. */
+
+ /*
+ * We loop here to repeat the buffer search after entering the I/O critical
+ * section, just in case a buffer got inserted while we were waiting for it.
+ */
+ int rc = VINF_SUCCESS;
+ uint8_t *pbDst = (uint8_t *)pSgBuf->paSegs[0].pvSeg;
+ size_t cbDst = pSgBuf->paSegs[0].cbSeg;
+ size_t cbTotalRead = 0;
+ bool fPokeReader = false;
+ bool fOwnsIoCritSect = false;
+ RTCritSectEnter(&pThis->BufferCritSect);
+ for (;;)
+ {
+ /*
+ * Try satisfy the read from the buffers.
+ */
+ uint64_t offCur = pThis->offConsumer;
+ if (off != -1)
+ {
+ offCur = (uint64_t)off;
+ if (pThis->offConsumer != offCur)
+ fPokeReader = true; /* If the current position changed, poke it in case it stopped at EOF. */
+ pThis->offConsumer = offCur;
+ }
+
+ PRTVFSREADAHEADBUFDESC pBufDesc, pNextBufDesc;
+ RTListForEachSafe(&pThis->ConsumerList, pBufDesc, pNextBufDesc, RTVFSREADAHEADBUFDESC, ListEntry)
+ {
+ /* The buffers are sorted and reads must start in a buffer if
+ anything should be taken from the buffer (at least for now). */
+ if (offCur < pBufDesc->off)
+ break;
+
+ /* Anything we can read from this buffer? */
+ uint64_t offCurBuf = offCur - pBufDesc->off;
+ if (offCurBuf < pBufDesc->cbFilled)
+ {
+ size_t const cbFromCurBuf = RT_MIN(pBufDesc->cbFilled - offCurBuf, cbDst);
+ memcpy(pbDst, pBufDesc->pbBuffer + offCurBuf, cbFromCurBuf);
+ pbDst += cbFromCurBuf;
+ cbDst -= cbFromCurBuf;
+ cbTotalRead += cbFromCurBuf;
+ offCur += cbFromCurBuf;
+ }
+
+ /* Discard buffers we've read past. */
+ if (pBufDesc->off + pBufDesc->cbFilled <= offCur)
+ {
+ RTListNodeRemove(&pBufDesc->ListEntry);
+ RTListAppend(&pThis->FreeList, &pBufDesc->ListEntry);
+ fPokeReader = true; /* Poke it as there are now more buffers available. */
+ }
+
+ /* Stop if we're done. */
+ if (!cbDst)
+ break;
+ }
+
+ pThis->offConsumer = offCur;
+ if (off != -1)
+ off = offCur;
+
+ if (!cbDst)
+ break;
+
+ /*
+ * Check if we've reached the end of the file/stream.
+ */
+ if (offCur >= pThis->offEof)
+ {
+ rc = pcbRead ? VINF_EOF : VERR_EOF;
+ Log(("rtVfsReadAhead_Read: ret %Rrc; offCur=%#llx offEof=%#llx\n", rc, offCur, pThis->offEof));
+ break;
+ }
+
+
+ /*
+ * First time around we don't own the I/O critsect and need to take it
+ * and repeat the above buffer reading code.
+ */
+ if (!fOwnsIoCritSect)
+ {
+ RTCritSectLeave(&pThis->BufferCritSect);
+ RTCritSectEnter(&pThis->IoCritSect);
+ RTCritSectEnter(&pThis->BufferCritSect);
+ fOwnsIoCritSect = true;
+ continue;
+ }
+
+
+ /*
+ * Do a direct read of the remaining data.
+ */
+ if (off == -1)
+ {
+ RTFOFF offActual = RTVfsIoStrmTell(pThis->hIos);
+ if (offActual >= 0 && (uint64_t)offActual != offCur)
+ off = offCur;
+ }
+ RTSGSEG TmpSeg = { pbDst, cbDst };
+ RTSGBUF TmpSgBuf;
+ RTSgBufInit(&TmpSgBuf, &TmpSeg, 1);
+ size_t cbThisRead = cbDst;
+ rc = RTVfsIoStrmSgRead(pThis->hIos, off, &TmpSgBuf, fBlocking, pcbRead ? &cbThisRead : NULL);
+ if (RT_SUCCESS(rc))
+ {
+ cbTotalRead += cbThisRead;
+ offCur += cbThisRead;
+ pThis->offConsumer = offCur;
+ if (rc != VINF_EOF)
+ fPokeReader = true;
+ else
+ {
+ pThis->offEof = offCur;
+ Log(("rtVfsReadAhead_Read: EOF %llu (%#llx)\n", pThis->offEof, pThis->offEof));
+ }
+ }
+ /* else if (rc == VERR_EOF): hard to say where exactly the current position
+ is here as cannot have had a non-NULL pcbRead. Set offEof later. */
+ break;
+ }
+ RTCritSectLeave(&pThis->BufferCritSect);
+ if (fOwnsIoCritSect)
+ RTCritSectLeave(&pThis->IoCritSect);
+ if (fPokeReader && rc != VINF_EOF && rc != VERR_EOF)
+ RTThreadUserSignal(pThis->hThread);
+
+ if (pcbRead)
+ *pcbRead = cbTotalRead;
+ Assert(cbTotalRead <= pSgBuf->paSegs[0].cbSeg);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ RT_NOREF_PV(pvThis); RT_NOREF_PV(off); RT_NOREF_PV(pSgBuf); RT_NOREF_PV(fBlocking); RT_NOREF_PV(pcbWritten);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_Flush(void *pvThis)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ return RTVfsIoStrmFlush(pThis->hIos);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ if (pThis->hThread != NIL_RTTHREAD)
+ {
+ /** @todo poll one with read-ahead thread. */
+ return VERR_NOT_IMPLEMENTED;
+ }
+ return RTVfsIoStrmPoll(pThis->hIos, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+
+ RTCritSectEnter(&pThis->BufferCritSect);
+ *poffActual = pThis->offConsumer;
+ RTCritSectLeave(&pThis->BufferCritSect);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED);
+
+ RTCritSectEnter(&pThis->IoCritSect);
+ RT_NOREF_PV(fMode); RT_NOREF_PV(fMask); /// @todo int rc = RTVfsFileSetMode(pThis->hFile, fMode, fMask);
+ int rc = VERR_NOT_SUPPORTED;
+ RTCritSectLeave(&pThis->IoCritSect);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED);
+
+ RTCritSectEnter(&pThis->IoCritSect);
+ RT_NOREF_PV(pAccessTime); RT_NOREF_PV(pModificationTime); RT_NOREF_PV(pChangeTime); RT_NOREF_PV(pBirthTime);
+ /// @todo int rc = RTVfsFileSetTimes(pThis->hFile, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ int rc = VERR_NOT_SUPPORTED;
+ RTCritSectLeave(&pThis->IoCritSect);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED);
+
+ RTCritSectEnter(&pThis->IoCritSect);
+ RT_NOREF_PV(uid); RT_NOREF_PV(gid);
+ /// @todo int rc = RTVfsFileSetOwner(pThis->hFile, uid, gid);
+ int rc = VERR_NOT_SUPPORTED;
+ RTCritSectLeave(&pThis->IoCritSect);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED);
+
+ RTCritSectEnter(&pThis->IoCritSect); /* protects against concurrent I/O using the offset. */
+ RTCritSectEnter(&pThis->BufferCritSect); /* protects offConsumer */
+
+ uint64_t offActual = UINT64_MAX;
+ int rc = RTVfsFileSeek(pThis->hFile, offSeek, uMethod, &offActual);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offConsumer = offActual;
+ if (poffActual)
+ *poffActual = offActual;
+ }
+
+ RTCritSectLeave(&pThis->BufferCritSect);
+ RTCritSectLeave(&pThis->IoCritSect);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED);
+
+ RTCritSectEnter(&pThis->IoCritSect); /* paranoia */
+ int rc = RTVfsFileQuerySize(pThis->hFile, pcbFile);
+ RTCritSectLeave(&pThis->IoCritSect);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED);
+
+ RTCritSectEnter(&pThis->IoCritSect); /* paranoia */
+ int rc = RTVfsFileSetSize(pThis->hFile, cbFile, fFlags);
+ RTCritSectLeave(&pThis->IoCritSect);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtVfsReadAhead_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvThis;
+ AssertReturn(pThis->hFile != NIL_RTVFSFILE, VERR_NOT_SUPPORTED);
+
+ RTCritSectEnter(&pThis->IoCritSect); /* paranoia */
+ int rc = RTVfsFileQueryMaxSize(pThis->hFile, pcbMax);
+ RTCritSectLeave(&pThis->IoCritSect);
+
+ return rc;
+}
+
+
+/**
+ * Read ahead I/O stream operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_VfsReadAheadIosOps =
+{ /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "Read ahead I/O stream",
+ rtVfsReadAhead_Close,
+ rtVfsReadAhead_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtVfsReadAhead_Read,
+ rtVfsReadAhead_Write,
+ rtVfsReadAhead_Flush,
+ rtVfsReadAhead_PollOne,
+ rtVfsReadAhead_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+};
+
+
+/**
+ * Read ahead file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_VfsReadAheadFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "Read ahead file",
+ rtVfsReadAhead_Close,
+ rtVfsReadAhead_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtVfsReadAhead_Read,
+ rtVfsReadAhead_Write,
+ rtVfsReadAhead_Flush,
+ rtVfsReadAhead_PollOne,
+ rtVfsReadAhead_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ /*RTVFSIOFILEOPS_FEAT_NO_AT_OFFSET*/ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtVfsReadAhead_SetMode,
+ rtVfsReadAhead_SetTimes,
+ rtVfsReadAhead_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtVfsReadAhead_Seek,
+ rtVfsReadAhead_QuerySize,
+ rtVfsReadAhead_SetSize,
+ rtVfsReadAhead_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * @callback_method_impl{PFNRTTHREAD, Read ahead thread procedure}
+ */
+static DECLCALLBACK(int) rtVfsReadAheadThreadProc(RTTHREAD hThreadSelf, void *pvUser)
+{
+ PRTVFSREADAHEAD pThis = (PRTVFSREADAHEAD)pvUser;
+ Assert(pThis);
+
+ while (!pThis->fTerminateThread)
+ {
+ int rc;
+
+ /*
+ * Is there a buffer handy for reading ahead.
+ */
+ PRTVFSREADAHEADBUFDESC pBufDesc = NULL;
+ RTCritSectEnter(&pThis->BufferCritSect);
+ if (!pThis->fTerminateThread)
+ pBufDesc = RTListRemoveFirst(&pThis->FreeList, RTVFSREADAHEADBUFDESC, ListEntry);
+ RTCritSectLeave(&pThis->BufferCritSect);
+
+ if (pBufDesc)
+ {
+ /*
+ * Got a buffer, take the I/O lock and read into it.
+ */
+ rc = VERR_CALLBACK_RETURN;
+ RTCritSectEnter(&pThis->IoCritSect);
+ if (!pThis->fTerminateThread)
+ {
+
+ pBufDesc->off = RTVfsIoStrmTell(pThis->hIos);
+ size_t cbRead = 0;
+ rc = RTVfsIoStrmRead(pThis->hIos, pBufDesc->pbBuffer, pThis->cbBuffer, true /*fBlocking*/, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ if (rc == VINF_EOF)
+ {
+ pThis->offEof = pBufDesc->off + cbRead;
+ Log(("rtVfsReadAheadThreadProc: EOF %llu (%#llx)\n", pThis->offEof, pThis->offEof));
+ }
+ pBufDesc->cbFilled = (uint32_t)cbRead;
+
+ /*
+ * Put back the buffer. The consumer list is sorted by offset, but
+ * we should usually end up appending the buffer.
+ */
+ RTCritSectEnter(&pThis->BufferCritSect);
+ PRTVFSREADAHEADBUFDESC pAfter = RTListGetLast(&pThis->ConsumerList, RTVFSREADAHEADBUFDESC, ListEntry);
+ if (!pAfter || pAfter->off <= pBufDesc->off)
+ RTListAppend(&pThis->ConsumerList, &pBufDesc->ListEntry);
+ else
+ {
+ do
+ pAfter = RTListGetPrev(&pThis->ConsumerList, pAfter, RTVFSREADAHEADBUFDESC, ListEntry);
+ while (pAfter && pAfter->off > pBufDesc->off);
+ if (!pAfter)
+ RTListPrepend(&pThis->ConsumerList, &pBufDesc->ListEntry);
+ else
+ {
+ Assert(pAfter->off <= pBufDesc->off);
+ RTListNodeInsertAfter(&pAfter->ListEntry, &pBufDesc->ListEntry);
+ }
+ }
+ RTCritSectLeave(&pThis->BufferCritSect);
+ pBufDesc = NULL;
+
+#ifdef RT_STRICT
+ /* Verify the list ordering. */
+ unsigned cAsserted = 0;
+ uint64_t offAssert = 0;
+ PRTVFSREADAHEADBUFDESC pAssertCur;
+ RTListForEach(&pThis->ConsumerList, pAssertCur, RTVFSREADAHEADBUFDESC, ListEntry)
+ {
+ Assert(offAssert <= pAssertCur->off);
+ offAssert = pAssertCur->off;
+ Assert(cAsserted < pThis->cBuffers);
+ cAsserted++;
+ }
+#endif
+ }
+ else
+ Assert(rc != VERR_EOF);
+ }
+ RTCritSectLeave(&pThis->IoCritSect);
+
+ /*
+ * If we succeeded and we didn't yet reach the end of the stream,
+ * loop without delay to start processing the next buffer.
+ */
+ if (RT_LIKELY(!pBufDesc && rc != VINF_EOF))
+ continue;
+
+ /* Put any unused buffer back in the free list (termination/failure, not EOF). */
+ if (pBufDesc)
+ {
+ RTCritSectEnter(&pThis->BufferCritSect);
+ RTListPrepend(&pThis->FreeList, &pBufDesc->ListEntry);
+ RTCritSectLeave(&pThis->BufferCritSect);
+ }
+ if (pThis->fTerminateThread)
+ break;
+ }
+
+ /*
+ * Wait for more to do.
+ */
+ rc = RTThreadUserWait(hThreadSelf, RT_MS_1MIN);
+ if (RT_SUCCESS(rc))
+ rc = RTThreadUserReset(hThreadSelf);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+static int rtVfsCreateReadAheadInstance(RTVFSIOSTREAM hVfsIosSrc, RTVFSFILE hVfsFileSrc, uint32_t fFlags,
+ uint32_t cBuffers, uint32_t cbBuffer, PRTVFSIOSTREAM phVfsIos, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Validate input a little.
+ */
+ int rc = VINF_SUCCESS;
+ AssertStmt(cBuffers < _4K, rc = VERR_OUT_OF_RANGE);
+ if (cBuffers == 0)
+ cBuffers = 4;
+ AssertStmt(cbBuffer <= _4M, rc = VERR_OUT_OF_RANGE);
+ if (cbBuffer == 0)
+ cbBuffer = _256K / cBuffers;
+ AssertStmt(cbBuffer * cBuffers < (ARCH_BITS < 64 ? _64M : _256M), rc = VERR_OUT_OF_RANGE);
+ AssertStmt(!fFlags, rc = VERR_INVALID_FLAGS);
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a file or I/O stream instance.
+ */
+ RTVFSFILE hVfsFileReadAhead = NIL_RTVFSFILE;
+ RTVFSIOSTREAM hVfsIosReadAhead = NIL_RTVFSIOSTREAM;
+ PRTVFSREADAHEAD pThis;
+ size_t cbThis = RT_UOFFSETOF_DYN(RTVFSREADAHEAD, aBufDescs[cBuffers]);
+ if (hVfsFileSrc != NIL_RTVFSFILE)
+ rc = RTVfsNewFile(&g_VfsReadAheadFileOps, cbThis, RTFILE_O_READ, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsFileReadAhead, (void **)&pThis);
+ else
+ rc = RTVfsNewIoStream(&g_VfsReadAheadIosOps, cbThis, RTFILE_O_READ, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsIosReadAhead, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ RTListInit(&pThis->ConsumerList);
+ RTListInit(&pThis->FreeList);
+ pThis->hThread = NIL_RTTHREAD;
+ pThis->fTerminateThread = false;
+ pThis->fFlags = fFlags;
+ pThis->hFile = hVfsFileSrc;
+ pThis->hIos = hVfsIosSrc;
+ pThis->cBuffers = cBuffers;
+ pThis->cbBuffer = cbBuffer;
+ pThis->offEof = UINT64_MAX;
+ pThis->offConsumer = RTVfsIoStrmTell(hVfsIosSrc);
+ if ((RTFOFF)pThis->offConsumer >= 0)
+ {
+ rc = RTCritSectInit(&pThis->IoCritSect);
+ if (RT_SUCCESS(rc))
+ rc = RTCritSectInit(&pThis->BufferCritSect);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pbAllBuffers = (uint8_t *)RTMemPageAlloc(pThis->cbBuffer * pThis->cBuffers);
+ if (pThis->pbAllBuffers)
+ {
+ for (uint32_t i = 0; i < cBuffers; i++)
+ {
+ pThis->aBufDescs[i].cbFilled = 0;
+ pThis->aBufDescs[i].off = UINT64_MAX / 2;
+ pThis->aBufDescs[i].pbBuffer = &pThis->pbAllBuffers[cbBuffer * i];
+ RTListAppend(&pThis->FreeList, &pThis->aBufDescs[i].ListEntry);
+ }
+
+ /*
+ * Create thread.
+ */
+ rc = RTThreadCreate(&pThis->hThread, rtVfsReadAheadThreadProc, pThis, 0, RTTHREADTYPE_DEFAULT,
+ RTTHREADFLAGS_WAITABLE, "vfsreadahead");
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * We're good.
+ */
+ if (phVfsFile)
+ *phVfsFile = hVfsFileReadAhead;
+ else if (hVfsFileReadAhead == NIL_RTVFSFILE)
+ *phVfsIos = hVfsIosReadAhead;
+ else
+ {
+ *phVfsIos = RTVfsFileToIoStream(hVfsFileReadAhead);
+ RTVfsFileRelease(hVfsFileReadAhead);
+ AssertReturn(*phVfsIos != NIL_RTVFSIOSTREAM, VERR_INTERNAL_ERROR_5);
+ }
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ else
+ rc = (int)pThis->offConsumer;
+ }
+ }
+
+ RTVfsFileRelease(hVfsFileSrc);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ return rc;
+}
+
+
+RTDECL(int) RTVfsCreateReadAheadForIoStream(RTVFSIOSTREAM hVfsIos, uint32_t fFlags, uint32_t cBuffers, uint32_t cbBuffer,
+ PRTVFSIOSTREAM phVfsIos)
+{
+ AssertPtrReturn(phVfsIos, VERR_INVALID_POINTER);
+ *phVfsIos = NIL_RTVFSIOSTREAM;
+
+ /*
+ * Retain the input stream, trying to obtain a file handle too so we can
+ * fully mirror it.
+ */
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIos);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+ RTVFSFILE hVfsFile = RTVfsIoStrmToFile(hVfsIos);
+
+ /*
+ * Do the job. (This always consumes the above retained references.)
+ */
+ return rtVfsCreateReadAheadInstance(hVfsIos, hVfsFile, fFlags, cBuffers, cbBuffer, phVfsIos, NULL);
+}
+
+
+RTDECL(int) RTVfsCreateReadAheadForFile(RTVFSFILE hVfsFile, uint32_t fFlags, uint32_t cBuffers, uint32_t cbBuffer,
+ PRTVFSFILE phVfsFile)
+{
+ AssertPtrReturn(phVfsFile, VERR_INVALID_POINTER);
+ *phVfsFile = NIL_RTVFSFILE;
+
+ /*
+ * Retain the input file and cast it o an I/O stream.
+ */
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_INVALID_HANDLE);
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ AssertReturnStmt(cRefs != UINT32_MAX, RTVfsIoStrmRelease(hVfsIos), VERR_INVALID_HANDLE);
+
+ /*
+ * Do the job. (This always consumes the above retained references.)
+ */
+ return rtVfsCreateReadAheadInstance(hVfsIos, hVfsFile, fFlags, cBuffers, cbBuffer, NULL, phVfsFile);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainReadAhead_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, poffError, pErrInfo);
+
+ /*
+ * Basics.
+ */
+ if ( pElement->enmType != RTVFSOBJTYPE_FILE
+ && pElement->enmType != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_ONLY_FILE_OR_IOS;
+ if (pElement->enmTypeIn == RTVFSOBJTYPE_INVALID)
+ return VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT;
+ if ( pElement->enmTypeIn != RTVFSOBJTYPE_FILE
+ && pElement->enmTypeIn != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_TAKES_FILE_OR_IOS;
+ if (pSpec->fOpenFile & RTFILE_O_WRITE)
+ return VERR_VFS_CHAIN_READ_ONLY_IOS;
+ if (pElement->cArgs > 2)
+ return VERR_VFS_CHAIN_AT_MOST_TWO_ARGS;
+
+ /*
+ * Parse the two optional arguments.
+ */
+ uint32_t cBuffers = 0;
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (*psz)
+ {
+ int rc = RTStrToUInt32Full(psz, 0, &cBuffers);
+ if (RT_FAILURE(rc))
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return VERR_VFS_CHAIN_INVALID_ARGUMENT;
+ }
+ }
+ }
+
+ uint32_t cbBuffer = 0;
+ if (pElement->cArgs > 1)
+ {
+ const char *psz = pElement->paArgs[1].psz;
+ if (*psz)
+ {
+ int rc = RTStrToUInt32Full(psz, 0, &cbBuffer);
+ if (RT_FAILURE(rc))
+ {
+ *poffError = pElement->paArgs[1].offSpec;
+ return VERR_VFS_CHAIN_INVALID_ARGUMENT;
+ }
+ }
+ }
+
+ /*
+ * Save the parsed arguments in the spec since their both optional.
+ */
+ pElement->uProvider = RT_MAKE_U64(cBuffers, cbBuffer);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainReadAhead_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ /* Try for a file if we can. */
+ int rc;
+ RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
+ if (hVfsFileIn != NIL_RTVFSFILE)
+ {
+ RTVFSFILE hVfsFile = NIL_RTVFSFILE;
+ rc = RTVfsCreateReadAheadForFile(hVfsFileIn, 0 /*fFlags*/, RT_LO_U32(pElement->uProvider),
+ RT_HI_U32(pElement->uProvider), &hVfsFile);
+ RTVfsFileRelease(hVfsFileIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else if (pElement->enmType == RTVFSOBJTYPE_IO_STREAM)
+ {
+ RTVFSIOSTREAM hVfsIosIn = RTVfsObjToIoStream(hPrevVfsObj);
+ if (hVfsIosIn != NIL_RTVFSIOSTREAM)
+ {
+ RTVFSIOSTREAM hVfsIos = NIL_RTVFSIOSTREAM;
+ rc = RTVfsCreateReadAheadForIoStream(hVfsIosIn, 0 /*fFlags*/, RT_LO_U32(pElement->uProvider),
+ RT_HI_U32(pElement->uProvider), &hVfsIos);
+ RTVfsIoStrmRelease(hVfsIosIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ else
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainReadAhead_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement);
+ return false;
+}
+
+
+/** VFS chain element 'pull'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainReadAheadReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "pull",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Takes an I/O stream or file and provides read-ahead caching.\n"
+ "Optional first argument specifies how many buffers to use, 0 indicating the default.\n"
+ "Optional second argument specifies the buffer size, 0 indicating the default.",
+ /* pfnValidate = */ rtVfsChainReadAhead_Validate,
+ /* pfnInstantiate = */ rtVfsChainReadAhead_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainReadAhead_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainReadAheadReg, rtVfsChainReadAheadReg);
+
diff --git a/src/VBox/Runtime/common/vfs/vfsstddir.cpp b/src/VBox/Runtime/common/vfs/vfsstddir.cpp
new file mode 100644
index 00000000..38ebb8c3
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsstddir.cpp
@@ -0,0 +1,867 @@
+/* $Id: vfsstddir.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Standard Directory Implementation.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP RTLOGGROUP_VFS
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include <iprt/assert.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/log.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+
+#define RTDIR_AGNOSTIC
+#include "internal/dir.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Private data of a standard directory.
+ */
+typedef struct RTVFSSTDDIR
+{
+ /** The directory handle. */
+ RTDIR hDir;
+ /** Whether to leave the handle open when the VFS handle is closed. */
+ bool fLeaveOpen;
+ /** Open flags, RTDIR_F_XXX. */
+ uint32_t fFlags;
+ /** Handle to the director so we can make sure it sticks around for symbolic
+ * link objects. */
+ RTVFSDIR hSelf;
+} RTVFSSTDDIR;
+/** Pointer to the private data of a standard directory. */
+typedef RTVFSSTDDIR *PRTVFSSTDDIR;
+
+
+/**
+ * Private data of a standard symbolic link.
+ */
+typedef struct RTVFSSTDSYMLINK
+{
+ /** Pointer to the VFS directory where the symbolic link lives . */
+ PRTVFSSTDDIR pDir;
+ /** The symbolic link name. */
+ RT_FLEXIBLE_ARRAY_EXTENSION
+ char szSymlink[RT_FLEXIBLE_ARRAY];
+} RTVFSSTDSYMLINK;
+/** Pointer to the private data of a standard symbolic link. */
+typedef RTVFSSTDSYMLINK *PRTVFSSTDSYMLINK;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) rtVfsStdDir_OpenDir(void *pvThis, const char *pszSubDir, uint32_t fFlags, PRTVFSDIR phVfsDir);
+static DECLCALLBACK(int) rtVfsStdDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink);
+static DECLCALLBACK(int) rtVfsStdDir_QueryEntryInfo(void *pvThis, const char *pszEntry,
+ PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr);
+static int rtVfsDirFromRTDir(RTDIR hDir, uint32_t fFlags, bool fLeaveOpen, PRTVFSDIR phVfsDir);
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtVfsStdSym_Close(void *pvThis)
+{
+ PRTVFSSTDSYMLINK pThis = (PRTVFSSTDSYMLINK)pvThis;
+ RTVfsDirRelease(pThis->pDir->hSelf);
+ pThis->pDir = NULL;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtVfsStdSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSSTDSYMLINK pThis = (PRTVFSSTDSYMLINK)pvThis;
+ return rtVfsStdDir_QueryEntryInfo(pThis->pDir, pThis->szSymlink, pObjInfo, enmAddAttr);
+}
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtVfsStdSym_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ NOREF(pvThis); NOREF(fMode); NOREF(fMask);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtVfsStdSym_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ NOREF(pvThis); NOREF(pAccessTime); NOREF(pModificationTime); NOREF(pChangeTime); NOREF(pBirthTime);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtVfsStdSym_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ NOREF(pvThis); NOREF(uid); NOREF(gid);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSSYMLINKOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtVfsStdSym_Read(void *pvThis, char *pszTarget, size_t cbTarget)
+{
+ PRTVFSSTDSYMLINK pThis = (PRTVFSSTDSYMLINK)pvThis;
+ return RTDirRelSymlinkRead(pThis->pDir->hDir, pThis->szSymlink, pszTarget, cbTarget, 0 /*fRead*/);
+}
+
+
+/**
+ * Symbolic operations for standard directory.
+ */
+static const RTVFSSYMLINKOPS g_rtVfsStdSymOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_SYMLINK,
+ "StdSymlink",
+ rtVfsStdSym_Close,
+ rtVfsStdSym_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSSYMLINKOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj),
+ rtVfsStdSym_SetMode,
+ rtVfsStdSym_SetTimes,
+ rtVfsStdSym_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtVfsStdSym_Read,
+ RTVFSSYMLINKOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_Close(void *pvThis)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+
+ int rc;
+ if (!pThis->fLeaveOpen)
+ rc = RTDirClose(pThis->hDir);
+ else
+ rc = VINF_SUCCESS;
+ pThis->hDir = NULL;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ return RTDirQueryInfo(pThis->hDir, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ if (fMask != ~RTFS_TYPE_MASK)
+ {
+ RTFSOBJINFO ObjInfo;
+ int rc = RTDirQueryInfo(pThis->hDir, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ return rc;
+ fMode |= ~fMask & ObjInfo.Attr.fMode;
+ }
+ //RTPathSetMode
+ //return RTFileSetMode(pThis->hDir, fMode);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ return RTDirSetTimes(pThis->hDir, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ return RTDirRelPathSetOwner(pThis->hDir, ".", uid, gid, RTPATH_F_FOLLOW_LINK);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpen}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_Open(void *pvThis, const char *pszEntry, uint64_t fFileOpen,
+ uint32_t fObjFlags, PRTVFSOBJ phVfsObj)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+
+ /*
+ * This is subject to race conditions, but we haven't too much of a choice
+ * without going platform specific here (we'll do that eventually).
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTDirRelPathQueryInfo(pThis->hDir, pszEntry, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
+ if (RT_SUCCESS(rc))
+ {
+ switch (ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ if (fObjFlags & RTVFSOBJ_F_OPEN_DIRECTORY)
+ {
+ if ( (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ {
+ RTDIR hSubDir;
+ rc = RTDirRelDirOpenFiltered(pThis->hDir, pszEntry, RTDIRFILTER_NONE, 0 /*fFlags*/, &hSubDir);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtVfsDirFromRTDir(hSubDir, 0 /** @todo subdir open/inherit flags... */, false, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ else
+ RTDirClose(hSubDir);
+ }
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+ }
+ else
+ rc = VERR_IS_A_DIRECTORY;
+ break;
+
+ case RTFS_TYPE_FILE:
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_FIFO:
+ case RTFS_TYPE_SOCKET:
+ switch (ObjInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ rc = fObjFlags & RTVFSOBJ_F_OPEN_FILE ? VINF_SUCCESS : VERR_IS_A_FILE;
+ break;
+ case RTFS_TYPE_DEV_BLOCK:
+ rc = fObjFlags & RTVFSOBJ_F_OPEN_DEV_BLOCK ? VINF_SUCCESS : VERR_IS_A_BLOCK_DEVICE;
+ break;
+ case RTFS_TYPE_DEV_CHAR:
+ rc = fObjFlags & RTVFSOBJ_F_OPEN_DEV_CHAR ? VINF_SUCCESS : VERR_IS_A_CHAR_DEVICE;
+ break;
+ /** @todo These two types should not result in files, but pure I/O streams.
+ * possibly char device too. */
+ case RTFS_TYPE_FIFO:
+ rc = fObjFlags & RTVFSOBJ_F_OPEN_FIFO ? VINF_SUCCESS : VERR_IS_A_FIFO;
+ break;
+ case RTFS_TYPE_SOCKET:
+ rc = fObjFlags & RTVFSOBJ_F_OPEN_SOCKET ? VINF_SUCCESS : VERR_IS_A_SOCKET;
+ break;
+ default:
+ rc = VERR_INVALID_FLAGS;
+ break;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ if ( (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN
+ || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ {
+ RTFILE hFile;
+ rc = RTDirRelFileOpen(pThis->hDir, pszEntry, fFileOpen, &hFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTVfsFileFromRTFile(hFile, fFileOpen, false /*fLeaveOpen*/, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ else
+ RTFileClose(hFile);
+ }
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+ }
+ break;
+
+ case RTFS_TYPE_SYMLINK:
+ if (fObjFlags & RTVFSOBJ_F_OPEN_SYMLINK)
+ {
+ uint32_t cRefs = RTVfsDirRetain(pThis->hSelf);
+ if (cRefs != UINT32_MAX)
+ {
+ RTVFSSYMLINK hVfsSymlink;
+ PRTVFSSTDSYMLINK pNewSymlink;
+ size_t cchSymlink = strlen(pszEntry);
+ rc = RTVfsNewSymlink(&g_rtVfsStdSymOps, RT_UOFFSETOF_DYN(RTVFSSTDSYMLINK, szSymlink[cchSymlink + 1]),
+ NIL_RTVFS, NIL_RTVFSLOCK, &hVfsSymlink, (void **)&pNewSymlink);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(pNewSymlink->szSymlink, pszEntry, cchSymlink);
+ pNewSymlink->szSymlink[cchSymlink] = '\0';
+ pNewSymlink->pDir = pThis;
+
+ *phVfsObj = RTVfsObjFromSymlink(hVfsSymlink);
+ RTVfsSymlinkRelease(hVfsSymlink);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ else
+ RTVfsDirRelease(pThis->hSelf);
+ }
+ else
+ rc = VERR_INTERNAL_ERROR_2;
+ }
+ else
+ rc = VERR_IS_A_SYMLINK;
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if ( rc == VERR_FILE_NOT_FOUND
+ || rc == VERR_PATH_NOT_FOUND)
+ {
+ /*
+ * Consider file or directory creation.
+ */
+ if ( ( (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE
+ || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE
+ || (fFileOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE)
+ && (fObjFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_NOTHING)
+ {
+
+ if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_FILE)
+ {
+ RTFILE hFile;
+ rc = RTDirRelFileOpen(pThis->hDir, pszEntry, fFileOpen, &hFile);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTVfsFileFromRTFile(hFile, fFileOpen, false /*fLeaveOpen*/, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ else
+ RTFileClose(hFile);
+ }
+ }
+ else if ((fObjFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_DIRECTORY)
+ {
+ RTDIR hSubDir;
+ rc = RTDirRelDirCreate(pThis->hDir, pszEntry, (fFileOpen & RTFILE_O_CREATE_MODE_MASK) >> RTFILE_O_CREATE_MODE_SHIFT,
+ 0 /* fFlags */, &hSubDir);
+ if (RT_SUCCESS(rc))
+ {
+ RTVFSDIR hVfsDir;
+ rc = rtVfsDirFromRTDir(hSubDir, 0 /** @todo subdir open/inherit flags... */, false, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ AssertStmt(*phVfsObj == NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3);
+ }
+ else
+ RTDirClose(hSubDir);
+ }
+ }
+ else
+ rc = VERR_VFS_UNSUPPORTED_CREATE_TYPE;
+ }
+ else
+ rc = VERR_FILE_NOT_FOUND;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnFollowAbsoluteSymlink}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_FollowAbsoluteSymlink(void *pvThis, const char *pszRoot, PRTVFSDIR phVfsDir)
+{
+ //PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ RT_NOREF(pvThis);
+ /** @todo walking restriction. */
+ return RTVfsDirOpenNormal(pszRoot, 0 /*fFlags*/, phVfsDir);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenFile}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_OpenFile(void *pvThis, const char *pszFilename, uint64_t fOpen, PRTVFSFILE phVfsFile)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ RTFILE hFile;
+ int rc = RTDirRelFileOpen(pThis->hDir, pszFilename, fOpen, &hFile);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileFromRTFile(hFile, fOpen, false /*fLeaveOpen*/, phVfsFile);
+ if (RT_FAILURE(rc))
+ RTFileClose(hFile);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenDir}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_OpenDir(void *pvThis, const char *pszSubDir, uint32_t fFlags, PRTVFSDIR phVfsDir)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ /** @todo subdir open flags */
+ RTDIR hSubDir;
+ int rc = RTDirRelDirOpenFiltered(pThis->hDir, pszSubDir, RTDIRFILTER_NONE, fFlags, &hSubDir);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtVfsDirFromRTDir(hSubDir, fFlags, false, phVfsDir);
+ if (RT_FAILURE(rc))
+ RTDirClose(hSubDir);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ int rc;
+ if (!phVfsDir)
+ rc = RTDirRelDirCreate(pThis->hDir, pszSubDir, fMode, 0 /* fFlags */, NULL);
+ else
+ {
+ RTDIR hSubDir;
+ rc = RTDirRelDirCreate(pThis->hDir, pszSubDir, fMode, 0 /* fFlags */, &hSubDir);
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo subdir open flags... */
+ rc = rtVfsDirFromRTDir(hSubDir, 0, false, phVfsDir);
+ if (RT_FAILURE(rc))
+ RTDirClose(hSubDir);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
+{
+ RTFSOBJINFO ObjInfo;
+ int rc = rtVfsStdDir_QueryEntryInfo(pvThis, pszSymlink, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ {
+ if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ {
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ uint32_t cRefs = RTVfsDirRetain(pThis->hSelf);
+ if (cRefs != UINT32_MAX)
+ {
+ PRTVFSSTDSYMLINK pNewSymlink;
+ size_t cchSymlink = strlen(pszSymlink);
+ rc = RTVfsNewSymlink(&g_rtVfsStdSymOps, RT_UOFFSETOF_DYN(RTVFSSTDSYMLINK, szSymlink[cchSymlink + 1]),
+ NIL_RTVFS, NIL_RTVFSLOCK, phVfsSymlink, (void **)&pNewSymlink);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(pNewSymlink->szSymlink, pszSymlink, cchSymlink);
+ pNewSymlink->szSymlink[cchSymlink] = '\0';
+ pNewSymlink->pDir = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsDirRelease(pThis->hSelf);
+ }
+ else
+ rc = VERR_INTERNAL_ERROR_2;
+ }
+ else
+ rc = VERR_NOT_SYMLINK;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
+ RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ int rc = RTDirRelSymlinkCreate(pThis->hDir, pszSymlink, pszTarget, enmType, 0 /*fCreate*/);
+ if (RT_SUCCESS(rc))
+ {
+ if (!phVfsSymlink)
+ return VINF_SUCCESS;
+ return rtVfsStdDir_OpenSymlink(pThis, pszSymlink, phVfsSymlink);
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnQueryEntryInfo}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_QueryEntryInfo(void *pvThis, const char *pszEntry,
+ PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ return RTDirRelPathQueryInfo(pThis->hDir, pszEntry, pObjInfo, enmAddAttr, RTPATH_F_ON_LINK);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ if (fType != 0)
+ {
+ if (fType == RTFS_TYPE_DIRECTORY)
+ return RTDirRelDirRemove(pThis->hDir, pszEntry);
+
+ RTFSOBJINFO ObjInfo;
+ int rc = rtVfsStdDir_QueryEntryInfo(pThis, pszEntry, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ((fType & RTFS_TYPE_MASK) != (ObjInfo.Attr.fMode & RTFS_TYPE_MASK))
+ return VERR_WRONG_TYPE;
+ }
+ return RTDirRelPathUnlink(pThis->hDir, pszEntry, 0 /*fUnlink*/);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ if (fType != 0)
+ {
+ RTFSOBJINFO ObjInfo;
+ int rc = rtVfsStdDir_QueryEntryInfo(pThis, pszEntry, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ return rc;
+ if ((fType & RTFS_TYPE_MASK) != (ObjInfo.Attr.fMode & RTFS_TYPE_MASK))
+ return VERR_WRONG_TYPE;
+ }
+
+ /** @todo RTVFSDIROPS::pfnRenameEntry doesn't really work, this must move to
+ * file system level. */
+ return RTDirRelPathRename(pThis->hDir, pszEntry, pThis->hDir, pszNewName,
+ RTPATHRENAME_FLAGS_NO_SYMLINKS | RTPATHRENAME_FLAGS_NO_REPLACE);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_RewindDir(void *pvThis)
+{
+ NOREF(pvThis);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
+ */
+static DECLCALLBACK(int) rtVfsStdDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSSTDDIR pThis = (PRTVFSSTDDIR)pvThis;
+ return RTDirReadEx(pThis->hDir, pDirEntry, pcbDirEntry, enmAddAttr, RTPATH_F_ON_LINK);
+}
+
+
+/**
+ * Standard file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSDIROPS) g_rtVfsStdDirOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_DIR,
+ "StdDir",
+ rtVfsStdDir_Close,
+ rtVfsStdDir_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSDIROPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj),
+ rtVfsStdDir_SetMode,
+ rtVfsStdDir_SetTimes,
+ rtVfsStdDir_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtVfsStdDir_Open,
+ rtVfsStdDir_FollowAbsoluteSymlink,
+ rtVfsStdDir_OpenFile,
+ rtVfsStdDir_OpenDir,
+ rtVfsStdDir_CreateDir,
+ rtVfsStdDir_OpenSymlink,
+ rtVfsStdDir_CreateSymlink,
+ rtVfsStdDir_QueryEntryInfo,
+ rtVfsStdDir_UnlinkEntry,
+ rtVfsStdDir_RenameEntry,
+ rtVfsStdDir_RewindDir,
+ rtVfsStdDir_ReadDir,
+ RTVFSDIROPS_VERSION
+};
+
+
+/**
+ * Internal worker for RTVfsDirFromRTDir and RTVfsDirOpenNormal.
+ *
+ * @returns IRPT status code.
+ * @param hDir The IPRT directory handle.
+ * @param fOpen Reserved for future.
+ * @param fLeaveOpen Whether to leave it open or close it.
+ * @param phVfsDir Where to return the handle.
+ */
+static int rtVfsDirFromRTDir(RTDIR hDir, uint32_t fFlags, bool fLeaveOpen, PRTVFSDIR phVfsDir)
+{
+ PRTVFSSTDDIR pThis;
+ RTVFSDIR hVfsDir;
+ int rc = RTVfsNewDir(&g_rtVfsStdDirOps, sizeof(RTVFSSTDDIR), 0 /*fFlags*/, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsDir, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hDir = hDir;
+ pThis->fLeaveOpen = fLeaveOpen;
+ pThis->fFlags = fFlags;
+ pThis->hSelf = hVfsDir;
+
+ *phVfsDir = hVfsDir;
+ return VINF_SUCCESS;
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsDirFromRTDir(RTDIR hDir, bool fLeaveOpen, PRTVFSDIR phVfsDir)
+{
+ AssertReturn(RTDirIsValid(hDir), VERR_INVALID_HANDLE);
+ return rtVfsDirFromRTDir(hDir, hDir->fFlags, fLeaveOpen, phVfsDir);
+}
+
+
+RTDECL(int) RTVfsDirOpenNormal(const char *pszPath, uint32_t fFlags, PRTVFSDIR phVfsDir)
+{
+ /*
+ * Open the file the normal way and pass it to RTVfsFileFromRTFile.
+ */
+ RTDIR hDir;
+ int rc = RTDirOpenFiltered(&hDir, pszPath, RTDIRFILTER_NONE, fFlags);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a VFS file handle.
+ */
+ rc = rtVfsDirFromRTDir(hDir, fFlags, false /*fLeaveOpen*/, phVfsDir);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ RTDirClose(hDir);
+ }
+ return rc;
+}
+
+
+RTDECL(bool) RTVfsDirIsStdDir(RTVFSDIR hVfsDir)
+{
+ return RTVfsDirToPrivate(hVfsDir, &g_rtVfsStdDirOps) != NULL;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainStdDir_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_INVALID)
+ return VERR_VFS_CHAIN_MUST_BE_FIRST_ELEMENT;
+ if (pElement->enmType != RTVFSOBJTYPE_DIR)
+ return VERR_VFS_CHAIN_ONLY_DIR;
+ if (pElement->cArgs < 1)
+ return VERR_VFS_CHAIN_AT_LEAST_ONE_ARG;
+
+ /*
+ * Parse flag arguments if any, storing them in the element.
+ */
+ uint32_t fFlags = 0;
+ for (uint32_t i = 1; i < pElement->cArgs; i++)
+ if (strcmp(pElement->paArgs[i].psz, "deny-ascent") == 0)
+ fFlags |= RTDIR_F_DENY_ASCENT;
+ else if (strcmp(pElement->paArgs[i].psz, "allow-ascent") == 0)
+ fFlags &= ~RTDIR_F_DENY_ASCENT;
+ else
+ {
+ *poffError = pElement->paArgs[i].offSpec;
+ return RTErrInfoSetF(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Unknown flag argument: %s", pElement->paArgs[i].psz);
+ }
+ pElement->uProvider = fFlags;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainStdDir_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj == NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ RTVFSDIR hVfsDir;
+ int rc = RTVfsDirOpenNormal(pElement->paArgs[0].psz, (uint32_t)pElement->uProvider, &hVfsDir);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromDir(hVfsDir);
+ RTVfsDirRelease(hVfsDir);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainStdDir_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if (strcmp(pElement->paArgs[0].psz, pReuseElement->paArgs[0].psz) == 0)
+ if (pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'file'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainStdDirReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "stddir",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a real directory. Initial element.\n"
+ "Takes zero or more flag arguments: deny-ascent, allow-ascent",
+ /* pfnValidate = */ rtVfsChainStdDir_Validate,
+ /* pfnInstantiate = */ rtVfsChainStdDir_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainStdDir_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainStdDirReg, rtVfsChainStdDirReg);
+
diff --git a/src/VBox/Runtime/common/vfs/vfsstdfile.cpp b/src/VBox/Runtime/common/vfs/vfsstdfile.cpp
new file mode 100644
index 00000000..92ba028c
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsstdfile.cpp
@@ -0,0 +1,669 @@
+/* $Id: vfsstdfile.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Standard File Implementation.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Private data of a standard file.
+ */
+typedef struct RTVFSSTDFILE
+{
+ /** The file handle. */
+ RTFILE hFile;
+ /** Whether to leave the handle open when the VFS handle is closed. */
+ bool fLeaveOpen;
+} RTVFSSTDFILE;
+/** Pointer to the private data of a standard file. */
+typedef RTVFSSTDFILE *PRTVFSSTDFILE;
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_Close(void *pvThis)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+
+ int rc;
+ if (!pThis->fLeaveOpen)
+ rc = RTFileClose(pThis->hFile);
+ else
+ rc = VINF_SUCCESS;
+ pThis->hFile = NIL_RTFILE;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ return RTFileQueryInfo(pThis->hFile, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * RTFileRead and RTFileReadAt does not return VINF_EOF or VINF_TRY_AGAIN, this
+ * function tries to fix this as best as it can.
+ *
+ * This fixing can be subject to races if some other thread or process is
+ * modifying the file size between the read and our size query here.
+ *
+ * @returns VINF_SUCCESS, VINF_EOF or VINF_TRY_AGAIN.
+ * @param pThis The instance data.
+ * @param off The offset parameter.
+ * @param cbToRead The number of bytes attempted read .
+ * @param cbActuallyRead The number of bytes actually read.
+ */
+DECLINLINE(int) rtVfsStdFile_ReadFixRC(PRTVFSSTDFILE pThis, RTFOFF off, size_t cbToRead, size_t cbActuallyRead)
+{
+ /* If the read returned less bytes than requested, it means the end of the
+ file has been reached. */
+ if (cbToRead > cbActuallyRead)
+ return VINF_EOF;
+
+ /* The other case here is the very special zero byte read at the end of the
+ file, where we're supposed to indicate EOF. */
+ if (cbToRead > 0)
+ return VINF_SUCCESS;
+
+ uint64_t cbFile;
+ int rc = RTFileQuerySize(pThis->hFile, &cbFile);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint64_t off2;
+ if (off >= 0)
+ off2 = off;
+ else
+ {
+ rc = RTFileSeek(pThis->hFile, 0, RTFILE_SEEK_CURRENT, &off2);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return off2 >= cbFile ? VINF_EOF : VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ int rc;
+
+ NOREF(fBlocking);
+ if (pSgBuf->cSegs == 1)
+ {
+ if (off < 0)
+ rc = RTFileRead( pThis->hFile, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbRead);
+ else
+ {
+ rc = RTFileReadAt(pThis->hFile, off, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbRead);
+ if (RT_SUCCESS(rc)) /* RTFileReadAt() doesn't increment the file-position indicator on some platforms */
+ rc = RTFileSeek(pThis->hFile, off + (pcbRead ? *pcbRead : pSgBuf->paSegs[0].cbSeg), RTFILE_SEEK_BEGIN, NULL);
+ }
+ if (rc == VINF_SUCCESS && pcbRead)
+ rc = rtVfsStdFile_ReadFixRC(pThis, off, pSgBuf->paSegs[0].cbSeg, *pcbRead);
+ }
+ else
+ {
+ size_t cbSeg = 0;
+ size_t cbRead = 0;
+ size_t cbReadSeg = 0;
+ rc = VINF_SUCCESS;
+
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ {
+ void *pvSeg = pSgBuf->paSegs[iSeg].pvSeg;
+ cbSeg = pSgBuf->paSegs[iSeg].cbSeg;
+
+ cbReadSeg = cbSeg;
+ if (off < 0)
+ rc = RTFileRead( pThis->hFile, pvSeg, cbSeg, pcbRead ? &cbReadSeg : NULL);
+ else
+ {
+ rc = RTFileReadAt(pThis->hFile, off, pvSeg, cbSeg, pcbRead ? &cbReadSeg : NULL);
+ if (RT_SUCCESS(rc)) /* RTFileReadAt() doesn't increment the file-position indicator on some platforms */
+ rc = RTFileSeek(pThis->hFile, off + cbReadSeg, RTFILE_SEEK_BEGIN, NULL);
+ }
+ if (RT_FAILURE(rc))
+ break;
+ if (off >= 0)
+ off += cbReadSeg;
+ cbRead += cbReadSeg;
+ if ((pcbRead && cbReadSeg != cbSeg) || rc != VINF_SUCCESS)
+ break;
+ }
+
+ if (pcbRead)
+ {
+ *pcbRead = cbRead;
+ if (rc == VINF_SUCCESS)
+ rc = rtVfsStdFile_ReadFixRC(pThis, off, cbSeg, cbReadSeg);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ int rc;
+
+ NOREF(fBlocking);
+ if (pSgBuf->cSegs == 1)
+ {
+ if (off < 0)
+ rc = RTFileWrite(pThis->hFile, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbWritten);
+ else
+ {
+ rc = RTFileWriteAt(pThis->hFile, off, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbWritten);
+ if (RT_SUCCESS(rc)) /* RTFileWriteAt() doesn't increment the file-position indicator on some platforms */
+ rc = RTFileSeek(pThis->hFile, off + (pcbWritten ? *pcbWritten : pSgBuf->paSegs[0].cbSeg), RTFILE_SEEK_BEGIN,
+ NULL);
+ }
+ }
+ else
+ {
+ size_t cbWritten = 0;
+ size_t cbWrittenSeg;
+ size_t *pcbWrittenSeg = pcbWritten ? &cbWrittenSeg : NULL;
+ rc = VINF_SUCCESS;
+
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ {
+ void *pvSeg = pSgBuf->paSegs[iSeg].pvSeg;
+ size_t cbSeg = pSgBuf->paSegs[iSeg].cbSeg;
+
+ cbWrittenSeg = 0;
+ if (off < 0)
+ rc = RTFileWrite(pThis->hFile, pvSeg, cbSeg, pcbWrittenSeg);
+ else
+ {
+ rc = RTFileWriteAt(pThis->hFile, off, pvSeg, cbSeg, pcbWrittenSeg);
+ if (RT_SUCCESS(rc))
+ {
+ off += pcbWrittenSeg ? *pcbWrittenSeg : cbSeg;
+ /* RTFileWriteAt() doesn't increment the file-position indicator on some platforms */
+ rc = RTFileSeek(pThis->hFile, off, RTFILE_SEEK_BEGIN, NULL);
+ }
+ }
+ if (RT_FAILURE(rc))
+ break;
+ if (pcbWritten)
+ {
+ cbWritten += cbWrittenSeg;
+ if (cbWrittenSeg != cbSeg)
+ break;
+ }
+ }
+
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_Flush(void *pvThis)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ int rc = RTFileFlush(pThis->hFile);
+#ifdef RT_OS_WINDOWS
+ /* Workaround for console handles. */ /** @todo push this further down? */
+ if ( rc == VERR_INVALID_HANDLE
+ && RTFileIsValid(pThis->hFile))
+ rc = VINF_NOT_SUPPORTED; /* not flushable */
+#endif
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ NOREF(pvThis);
+ int rc;
+ if (fEvents != RTPOLL_EVT_ERROR)
+ {
+ *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR;
+ rc = VINF_SUCCESS;
+ }
+ else if (fIntr)
+ rc = RTThreadSleep(cMillies);
+ else
+ {
+ uint64_t uMsStart = RTTimeMilliTS();
+ do
+ rc = RTThreadSleep(cMillies);
+ while ( rc == VERR_INTERRUPTED
+ && !fIntr
+ && RTTimeMilliTS() - uMsStart < cMillies);
+ if (rc == VERR_INTERRUPTED)
+ rc = VERR_TIMEOUT;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ uint64_t offActual;
+ int rc = RTFileSeek(pThis->hFile, 0, RTFILE_SEEK_CURRENT, &offActual);
+ if (RT_SUCCESS(rc))
+ *poffActual = (RTFOFF)offActual;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnSkip}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_Skip(void *pvThis, RTFOFF cb)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ uint64_t offIgnore;
+ return RTFileSeek(pThis->hFile, cb, RTFILE_SEEK_CURRENT, &offIgnore);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ if (fMask != ~RTFS_TYPE_MASK)
+ {
+#if 0
+ RTFMODE fCurMode;
+ int rc = RTFileGetMode(pThis->hFile, &fCurMode);
+ if (RT_FAILURE(rc))
+ return rc;
+ fMode |= ~fMask & fCurMode;
+#else
+ RTFSOBJINFO ObjInfo;
+ int rc = RTFileQueryInfo(pThis->hFile, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ return rc;
+ fMode |= ~fMask & ObjInfo.Attr.fMode;
+#endif
+ }
+ return RTFileSetMode(pThis->hFile, fMode);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ return RTFileSetTimes(pThis->hFile, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+#if 0
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ return RTFileSetOwner(pThis->hFile, uid, gid);
+#else
+ NOREF(pvThis); NOREF(uid); NOREF(gid);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ uint64_t offActual = 0;
+ int rc = RTFileSeek(pThis->hFile, offSeek, uMethod, &offActual);
+ if (RT_SUCCESS(rc))
+ *poffActual = offActual;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ return RTFileQuerySize(pThis->hFile, pcbFile);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSetSize}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ switch (fFlags & RTVFSFILE_SIZE_F_ACTION_MASK)
+ {
+ case RTVFSFILE_SIZE_F_NORMAL:
+ return RTFileSetSize(pThis->hFile, cbFile);
+ case RTVFSFILE_SIZE_F_GROW:
+ return RTFileSetAllocationSize(pThis->hFile, cbFile, RTFILE_ALLOC_SIZE_F_DEFAULT);
+ case RTVFSFILE_SIZE_F_GROW_KEEP_SIZE:
+ return RTFileSetAllocationSize(pThis->hFile, cbFile, RTFILE_ALLOC_SIZE_F_KEEP_SIZE);
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize}
+ */
+static DECLCALLBACK(int) rtVfsStdFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax)
+{
+ PRTVFSSTDFILE pThis = (PRTVFSSTDFILE)pvThis;
+ RTFOFF cbMax = 0;
+ int rc = RTFileQueryMaxSizeEx(pThis->hFile, &cbMax);
+ if (RT_SUCCESS(rc))
+ *pcbMax = cbMax;
+ return rc;
+}
+
+
+/**
+ * Standard file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtVfsStdFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "StdFile",
+ rtVfsStdFile_Close,
+ rtVfsStdFile_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ 0,
+ rtVfsStdFile_Read,
+ rtVfsStdFile_Write,
+ rtVfsStdFile_Flush,
+ rtVfsStdFile_PollOne,
+ rtVfsStdFile_Tell,
+ rtVfsStdFile_Skip,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtVfsStdFile_SetMode,
+ rtVfsStdFile_SetTimes,
+ rtVfsStdFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtVfsStdFile_Seek,
+ rtVfsStdFile_QuerySize,
+ rtVfsStdFile_SetSize,
+ rtVfsStdFile_QueryMaxSize,
+ RTVFSFILEOPS_VERSION
+};
+
+
+/**
+ * Internal worker for RTVfsFileFromRTFile and RTVfsFileOpenNormal.
+ *
+ * @returns IRPT status code.
+ * @param hFile The IPRT file handle.
+ * @param fOpen The RTFILE_O_XXX flags.
+ * @param fLeaveOpen Whether to leave it open or close it.
+ * @param phVfsFile Where to return the handle.
+ */
+static int rtVfsFileFromRTFile(RTFILE hFile, uint64_t fOpen, bool fLeaveOpen, PRTVFSFILE phVfsFile)
+{
+ PRTVFSSTDFILE pThis;
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsNewFile(&g_rtVfsStdFileOps, sizeof(RTVFSSTDFILE), fOpen, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsFile, (void **)&pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->hFile = hFile;
+ pThis->fLeaveOpen = fLeaveOpen;
+ *phVfsFile = hVfsFile;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTVfsFileFromRTFile(RTFILE hFile, uint64_t fOpen, bool fLeaveOpen, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Check the handle validity.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTFileQueryInfo(hFile, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Set up some fake fOpen flags if necessary and create a VFS file handle.
+ */
+ if (!fOpen)
+ fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_NONE | RTFILE_O_OPEN_CREATE;
+
+ return rtVfsFileFromRTFile(hFile, fOpen, fLeaveOpen, phVfsFile);
+}
+
+
+RTDECL(int) RTVfsFileOpenNormal(const char *pszFilename, uint64_t fOpen, PRTVFSFILE phVfsFile)
+{
+ /*
+ * Open the file the normal way and pass it to RTVfsFileFromRTFile.
+ */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszFilename, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create a VFS file handle.
+ */
+ rc = rtVfsFileFromRTFile(hFile, fOpen, false /*fLeaveOpen*/, phVfsFile);
+ if (RT_FAILURE(rc))
+ RTFileClose(hFile);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmFromRTFile(RTFILE hFile, uint64_t fOpen, bool fLeaveOpen, PRTVFSIOSTREAM phVfsIos)
+{
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsFileFromRTFile(hFile, fOpen, fLeaveOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsIos = RTVfsFileToIoStream(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+ return rc;
+}
+
+
+RTDECL(int) RTVfsIoStrmOpenNormal(const char *pszFilename, uint64_t fOpen, PRTVFSIOSTREAM phVfsIos)
+{
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsFileOpenNormal(pszFilename, fOpen, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsIos = RTVfsFileToIoStream(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+ return rc;
+}
+
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainStdFile_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basic checks.
+ */
+ if (pElement->enmTypeIn != RTVFSOBJTYPE_INVALID)
+ return VERR_VFS_CHAIN_MUST_BE_FIRST_ELEMENT;
+ if ( pElement->enmType != RTVFSOBJTYPE_FILE
+ && pElement->enmType != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_ONLY_FILE_OR_IOS;
+
+ /*
+ * Join common cause with the 'open' provider.
+ */
+ return RTVfsChainValidateOpenFileOrIoStream(pSpec, pElement, poffError, pErrInfo);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainStdFile_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj == NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ RTVFSFILE hVfsFile;
+ int rc = RTVfsFileOpenNormal(pElement->paArgs[0].psz, pElement->uProvider, &hVfsFile);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainStdFile_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pReuseSpec);
+ if (strcmp(pElement->paArgs[0].psz, pReuseElement->paArgs[0].psz) == 0)
+ if (pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider)
+ return true;
+ return false;
+}
+
+
+/** VFS chain element 'file'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainStdFileReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "stdfile",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Open a real file, providing either a file or an I/O stream object. Initial element.\n"
+ "First argument is the filename path.\n"
+ "Second argument is access mode, optional: r, w, rw.\n"
+ "Third argument is open disposition, optional: create, create-replace, open, open-create, open-append, open-truncate.\n"
+ "Forth argument is file sharing, optional: nr, nw, nrw, d.",
+ /* pfnValidate = */ rtVfsChainStdFile_Validate,
+ /* pfnInstantiate = */ rtVfsChainStdFile_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainStdFile_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainStdFileReg, rtVfsChainStdFileReg);
+
diff --git a/src/VBox/Runtime/common/vfs/vfsstdpipe.cpp b/src/VBox/Runtime/common/vfs/vfsstdpipe.cpp
new file mode 100644
index 00000000..113455fd
--- /dev/null
+++ b/src/VBox/Runtime/common/vfs/vfsstdpipe.cpp
@@ -0,0 +1,324 @@
+/* $Id: vfsstdpipe.cpp $ */
+/** @file
+ * IPRT - Virtual File System, Standard Pipe I/O stream Implementation.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Private data of a standard pipe.
+ */
+typedef struct RTVFSSTDPIPE
+{
+ /** The pipe handle. */
+ RTPIPE hPipe;
+ /** Whether to leave the handle open when the VFS handle is closed. */
+ bool fLeaveOpen;
+ /** Set if primarily read, clear if write. */
+ bool fReadPipe;
+ /** Fake stream position. */
+ uint64_t offFakePos;
+} RTVFSSTDPIPE;
+/** Pointer to the private data of a standard pipe. */
+typedef RTVFSSTDPIPE *PRTVFSSTDPIPE;
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtVfsStdPipe_Close(void *pvThis)
+{
+ PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis;
+
+ int rc = RTPipeCloseEx(pThis->hPipe, pThis->fLeaveOpen);
+ pThis->hPipe = NIL_RTPIPE;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtVfsStdPipe_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis;
+ return RTPipeQueryInfo(pThis->hPipe, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtVfsStdPipe_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis;
+ int rc;
+ AssertReturn(off < 0 || pThis->offFakePos == (uint64_t)off, VERR_SEEK_ON_DEVICE);
+
+ NOREF(fBlocking);
+ if (pSgBuf->cSegs == 1)
+ {
+ if (fBlocking)
+ rc = RTPipeReadBlocking(pThis->hPipe, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbRead);
+ else
+ rc = RTPipeRead( pThis->hPipe, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbRead);
+ if (RT_SUCCESS(rc))
+ pThis->offFakePos += pcbRead ? *pcbRead : pSgBuf->paSegs[0].cbSeg;
+ }
+ else
+ {
+ size_t cbSeg = 0;
+ size_t cbRead = 0;
+ size_t cbReadSeg = 0;
+ size_t *pcbReadSeg = pcbRead ? &cbReadSeg : NULL;
+ rc = VINF_SUCCESS;
+
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ {
+ void *pvSeg = pSgBuf->paSegs[iSeg].pvSeg;
+ cbSeg = pSgBuf->paSegs[iSeg].cbSeg;
+
+ cbReadSeg = cbSeg;
+ if (fBlocking)
+ rc = RTPipeReadBlocking(pThis->hPipe, pvSeg, cbSeg, pcbReadSeg);
+ else
+ rc = RTPipeRead( pThis->hPipe, pvSeg, cbSeg, pcbReadSeg);
+ if (RT_FAILURE(rc))
+ break;
+ pThis->offFakePos += pcbRead ? cbReadSeg : cbSeg;
+ cbRead += cbReadSeg;
+ if (rc != VINF_SUCCESS)
+ break;
+ AssertBreakStmt(!pcbRead || cbReadSeg == cbSeg, rc = VINF_TRY_AGAIN);
+ }
+
+ if (pcbRead)
+ *pcbRead = cbRead;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtVfsStdPipe_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis;
+ int rc;
+ AssertReturn(off < 0 || pThis->offFakePos == (uint64_t)off, VERR_SEEK_ON_DEVICE);
+
+ if (pSgBuf->cSegs == 1)
+ {
+ if (fBlocking)
+ rc = RTPipeWriteBlocking(pThis->hPipe, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbWritten);
+ else
+ rc = RTPipeWrite( pThis->hPipe, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, pcbWritten);
+ if (RT_SUCCESS(rc))
+ pThis->offFakePos += pcbWritten ? *pcbWritten : pSgBuf->paSegs[0].cbSeg;
+ }
+ else
+ {
+ size_t cbWritten = 0;
+ size_t cbWrittenSeg;
+ size_t *pcbWrittenSeg = pcbWritten ? &cbWrittenSeg : NULL;
+ rc = VINF_SUCCESS;
+
+ for (uint32_t iSeg = 0; iSeg < pSgBuf->cSegs; iSeg++)
+ {
+ void *pvSeg = pSgBuf->paSegs[iSeg].pvSeg;
+ size_t cbSeg = pSgBuf->paSegs[iSeg].cbSeg;
+
+ cbWrittenSeg = 0;
+ if (fBlocking)
+ rc = RTPipeWriteBlocking(pThis->hPipe, pvSeg, cbSeg, pcbWrittenSeg);
+ else
+ rc = RTPipeWrite( pThis->hPipe, pvSeg, cbSeg, pcbWrittenSeg);
+ if (RT_FAILURE(rc))
+ break;
+ pThis->offFakePos += pcbWritten ? cbWrittenSeg : cbSeg;
+ if (pcbWritten)
+ {
+ cbWritten += cbWrittenSeg;
+ if (rc != VINF_SUCCESS)
+ break;
+ AssertStmt(cbWrittenSeg == cbSeg, rc = VINF_TRY_AGAIN);
+ }
+ else
+ AssertBreak(rc == VINF_SUCCESS);
+ }
+
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtVfsStdPipe_Flush(void *pvThis)
+{
+ PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis;
+ return RTPipeFlush(pThis->hPipe);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtVfsStdPipe_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis;
+ uint32_t const fPossibleEvt = pThis->fReadPipe ? RTPOLL_EVT_READ : RTPOLL_EVT_WRITE;
+
+ int rc = RTPipeSelectOne(pThis->hPipe, cMillies);
+ if (RT_SUCCESS(rc))
+ {
+ if (fEvents & fPossibleEvt)
+ *pfRetEvents = fPossibleEvt;
+ else
+ rc = RTVfsUtilDummyPollOne(fEvents, cMillies, fIntr, pfRetEvents);
+ }
+ else if ( rc != VERR_TIMEOUT
+ && rc != VERR_INTERRUPTED
+ && rc != VERR_TRY_AGAIN /* paranoia */)
+ {
+ *pfRetEvents = RTPOLL_EVT_ERROR;
+ rc = VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtVfsStdPipe_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTVFSSTDPIPE pThis = (PRTVFSSTDPIPE)pvThis;
+ *poffActual = pThis->offFakePos;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Standard pipe operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_rtVfsStdPipeOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "StdFile",
+ rtVfsStdPipe_Close,
+ rtVfsStdPipe_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ 0,
+ rtVfsStdPipe_Read,
+ rtVfsStdPipe_Write,
+ rtVfsStdPipe_Flush,
+ rtVfsStdPipe_PollOne,
+ rtVfsStdPipe_Tell,
+ NULL /*rtVfsStdPipe_Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+};
+
+
+/**
+ * Internal worker for RTVfsIosFromRTPipe and later some create API.
+ *
+ * @returns IRPT status code.
+ * @param hPipe The IPRT file handle.
+ * @param fOpen The RTFILE_O_XXX flags.
+ * @param fLeaveOpen Whether to leave it open or close it.
+ * @param phVfsFile Where to return the handle.
+ */
+static int rtVfsFileFromRTPipe(RTPIPE hPipe, uint64_t fOpen, bool fLeaveOpen, PRTVFSIOSTREAM phVfsIos)
+{
+ PRTVFSSTDPIPE pThis;
+ RTVFSIOSTREAM hVfsIos;
+ int rc = RTVfsNewIoStream(&g_rtVfsStdPipeOps, sizeof(RTVFSSTDPIPE), fOpen, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsIos, (void **)&pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ pThis->hPipe = hPipe;
+ pThis->fLeaveOpen = fLeaveOpen;
+ *phVfsIos = hVfsIos;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTVfsIoStrmFromRTPipe(RTPIPE hPipe, bool fLeaveOpen, PRTVFSIOSTREAM phVfsIos)
+{
+ /*
+ * Check the handle validity and read/write mode, then create a stream for it.
+ */
+ RTFSOBJINFO ObjInfo;
+ int rc = RTPipeQueryInfo(hPipe, &ObjInfo, RTFSOBJATTRADD_NOTHING);
+ if (RT_SUCCESS(rc))
+ rc = rtVfsFileFromRTPipe(hPipe,
+ ObjInfo.Attr.fMode & RTFS_DOS_READONLY ? RTFILE_O_READ : RTFILE_O_WRITE,
+ fLeaveOpen, phVfsIos);
+ return rc;
+}
+
+/** @todo Create pipe API? */
+
diff --git a/src/VBox/Runtime/common/zip/Makefile.kup b/src/VBox/Runtime/common/zip/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/Makefile.kup
diff --git a/src/VBox/Runtime/common/zip/cpiovfs.cpp b/src/VBox/Runtime/common/zip/cpiovfs.cpp
new file mode 100644
index 00000000..04dc8675
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/cpiovfs.cpp
@@ -0,0 +1,1146 @@
+/* $Id: cpiovfs.cpp $ */
+/** @file
+ * IPRT - CPIO Virtual Filesystem, Reader.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/poll.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include "cpiovfsreader.h"
+
+
+/**
+ * Converts a octal numeric header field to the C native type.
+ *
+ * @returns IPRT status code.
+ * @param pachField The CPIO header field.
+ * @param cchField The length of the field.
+ * @param pi64 Where to store the value.
+ */
+static int rtZipCpioHdrOctalFieldToNum(const char *pachField, size_t cchField, int64_t *pi64)
+{
+ /*
+ * Skip leading zeros to save a few slower loops below.
+ */
+ while (cchField > 0 && *pachField == '0')
+ cchField--, pachField++;
+
+ /*
+ * Convert octal digits.
+ */
+ int64_t i64 = 0;
+ while (cchField > 0)
+ {
+ unsigned char uDigit = *pachField - '0';
+ if (uDigit >= 8)
+ return VERR_TAR_BAD_NUM_FIELD;
+ i64 <<= 3;
+ i64 |= uDigit;
+
+ pachField++;
+ cchField--;
+ }
+ *pi64 = i64;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Converts a hex character to the appropriate nibble.
+ *
+ * @returns Nibble of the character.
+ * @param chVal The value to convert.
+ */
+static inline uint8_t rtZipCpioHexToNibble(char chVal)
+{
+ if (chVal >= '0' && chVal <= '9')
+ return chVal - '0';
+ else if (chVal >= 'a' && chVal <= 'f')
+ return chVal - 'a' + 10;
+ else if (chVal >= 'A' && chVal <= 'F')
+ return chVal - 'A' + 10;
+
+ return 0xff;
+}
+
+
+/**
+ * Converts a hexadecimal numeric header field to the C native type.
+ *
+ * @returns IPRT status code.
+ * @param pachField The CPIO header field.
+ * @param cchField The length of the field.
+ * @param pi64 Where to store the value.
+ */
+static int rtZipCpioHdrHexFieldToNum(const char *pachField, size_t cchField, int64_t *pi64)
+{
+ uint64_t u64 = 0;
+
+ while (cchField-- > 0)
+ {
+ uint8_t bNb = rtZipCpioHexToNibble(*pachField++);
+
+ if (RT_LIKELY(bNb != 0xff))
+ u64 = (u64 << 4) | bNb;
+ else
+ return VERR_TAR_BAD_NUM_FIELD;
+ }
+
+ *pi64 = (int64_t)u64;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses the given ancient binary header and converts it to an FS object info structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CPIO reader state.
+ * @param pHdr The header to convert.
+ * @param pcbFilePath Where to store the file path size on success.
+ * @param pcbPad Where to store the number of bytes padded after the header and file path
+ * before the content begins.
+ */
+static int rtZipCpioReaderParseHeaderAncientBin(PRTZIPCPIOREADER pThis, PCCPIOHDRBIN pHdr,
+ uint32_t *pcbFilePath, uint32_t *pcbPad)
+{
+ RT_NOREF(pThis, pHdr, pcbFilePath, pcbPad);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * Parses the given SuSv2 ASCII header and converts it to an FS object info structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CPIO reader state.
+ * @param pHdr The header to convert.
+ * @param pcbFilePath Where to store the file path size on success.
+ * @param pcbPad Where to store the number of bytes padded after the header and file path
+ * before the content begins.
+ */
+static int rtZipCpioReaderParseHeaderAsciiSusV2(PRTZIPCPIOREADER pThis, PCCPIOHDRSUSV2 pHdr,
+ uint32_t *pcbFilePath, uint32_t *pcbPad)
+{
+ PRTFSOBJINFO pObjInfo = &pThis->ObjInfo;
+ int rc;
+ int64_t i64Tmp;
+ int64_t c64SecModTime;
+
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+
+#define GET_CPIO_NUMERIC_FIELD_RET(a_Var, a_Field) \
+ do { \
+ rc = rtZipCpioHdrOctalFieldToNum(a_Field, sizeof(a_Field), &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = i64Tmp; \
+ if ((a_Var) != i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+#define GET_CPIO_NUMERIC_FIELD_RET_U64(a_Var, a_Field) \
+ do { \
+ rc = rtZipCpioHdrOctalFieldToNum(a_Field, sizeof(a_Field), &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = (uint64_t)i64Tmp; \
+ if ((a_Var) != (uint64_t)i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.fMode, pHdr->achMode);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.uid, pHdr->achUid);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.gid, pHdr->achGid);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.cHardlinks, pHdr->achNLinks);
+ GET_CPIO_NUMERIC_FIELD_RET_U64(pObjInfo->Attr.u.Unix.INodeId, pHdr->achInode);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.Device, pHdr->achDev);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->cbObject, pHdr->achFileSize);
+ pObjInfo->cbAllocated = pObjInfo->cbObject;
+ GET_CPIO_NUMERIC_FIELD_RET( c64SecModTime, pHdr->achMTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ChangeTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ModificationTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->AccessTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->BirthTime, c64SecModTime);
+ if (c64SecModTime != RTTimeSpecGetSeconds(&pObjInfo->ModificationTime))
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+
+ GET_CPIO_NUMERIC_FIELD_RET(*pcbFilePath, pHdr->achNameSize);
+
+ /* There is never any padding. */
+ *pcbPad = 0;
+
+#undef GET_CPIO_NUMERIC_FIELD_RET
+#undef GET_CPIO_NUMERIC_FIELD_RET_U64
+
+ return rc;
+}
+
+
+/**
+ * Parses the given "new" ASCII header and converts it to an FS object info structure.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CPIO reader state.
+ * @param pHdr The header to convert.
+ * @param fWithChksum Flag whether the header uses the checksum field.
+ * @param pcbFilePath Where to store the file path size on success.
+ * @param pcbPad Where to store the number of bytes padded after the header and file path
+ * before the content begins.
+ */
+static int rtZipCpioReaderParseHeaderAsciiNew(PRTZIPCPIOREADER pThis, PCCPIOHDRNEW pHdr, bool fWithChksum,
+ uint32_t *pcbFilePath, uint32_t *pcbPad)
+{
+ RT_NOREF(fWithChksum); /** @todo */
+ PRTFSOBJINFO pObjInfo = &pThis->ObjInfo;
+ int rc;
+ int64_t i64Tmp;
+ int64_t c64SecModTime;
+ uint32_t uMajor, uMinor;
+
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+
+#define GET_CPIO_NUMERIC_FIELD_RET(a_Var, a_Field) \
+ do { \
+ rc = rtZipCpioHdrHexFieldToNum(a_Field, sizeof(a_Field), &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = i64Tmp; \
+ if ((a_Var) != i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+#define GET_CPIO_NUMERIC_FIELD_RET_U64(a_Var, a_Field) \
+ do { \
+ rc = rtZipCpioHdrHexFieldToNum(a_Field, sizeof(a_Field), &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = (uint64_t)i64Tmp; \
+ if ((a_Var) != (uint64_t)i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.fMode, pHdr->achMode);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.uid, pHdr->achUid);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.gid, pHdr->achGid);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->Attr.u.Unix.cHardlinks, pHdr->achNLinks);
+ GET_CPIO_NUMERIC_FIELD_RET_U64(pObjInfo->Attr.u.Unix.INodeId, pHdr->achInode);
+ GET_CPIO_NUMERIC_FIELD_RET( uMajor, pHdr->achDevMajor);
+ GET_CPIO_NUMERIC_FIELD_RET( uMinor, pHdr->achDevMinor);
+ GET_CPIO_NUMERIC_FIELD_RET( pObjInfo->cbObject, pHdr->achFileSize);
+ pObjInfo->cbAllocated = RT_ALIGN_64(pObjInfo->cbObject, 4);
+ GET_CPIO_NUMERIC_FIELD_RET( c64SecModTime, pHdr->achMTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ChangeTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ModificationTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->AccessTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->BirthTime, c64SecModTime);
+ if (c64SecModTime != RTTimeSpecGetSeconds(&pObjInfo->ModificationTime))
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+ pObjInfo->Attr.u.Unix.Device = RTDEV_MAKE(uMajor, uMinor);
+ if ( uMajor != RTDEV_MAJOR(pObjInfo->Attr.u.Unix.Device)
+ || uMinor != RTDEV_MINOR(pObjInfo->Attr.u.Unix.Device))
+ return VERR_TAR_DEV_VALUE_TOO_LARGE;
+
+ GET_CPIO_NUMERIC_FIELD_RET(*pcbFilePath, pHdr->achNameSize);
+
+ /* Header and file path are padded with 0 bytes to a 4 byte boundary. */
+ uint32_t cbComp = *pcbFilePath + sizeof(*pHdr);
+ *pcbPad = RT_ALIGN_32(cbComp, 4) - cbComp;
+
+#undef GET_CPIO_NUMERIC_FIELD_RET
+#undef GET_CPIO_NUMERIC_FIELD_RET_U64
+
+ return rc;
+}
+
+
+/**
+ * Parses and validates a CPIO header.
+ *
+ * @returns IPRT status code.
+ * @param pThis The CPIO reader state.
+ * @param enmType The CPIO header type.
+ * @param pHdr The CPIO header that has been read.
+ * @param pcbFilePath Where to store the size of the file path on success.
+ * @param pcbPad Where to store the number of bytes padded after the header and file path
+ * before the content begins.
+ */
+static int rtZipCpioReaderParseHeader(PRTZIPCPIOREADER pThis, RTZIPCPIOTYPE enmType, PCCPIOHDR pHdr,
+ uint32_t *pcbFilePath, uint32_t *pcbPad)
+{
+ int rc;
+
+ switch (enmType)
+ {
+ case RTZIPCPIOTYPE_ANCIENT_BIN:
+ rc = rtZipCpioReaderParseHeaderAncientBin(pThis, &pHdr->AncientBin,
+ pcbFilePath, pcbPad);
+ break;
+ case RTZIPCPIOTYPE_ASCII_SUSV2:
+ rc = rtZipCpioReaderParseHeaderAsciiSusV2(pThis, &pHdr->AsciiSuSv2,
+ pcbFilePath, pcbPad);
+ break;
+ case RTZIPCPIOTYPE_ASCII_NEW:
+ rc = rtZipCpioReaderParseHeaderAsciiNew(pThis, &pHdr->AsciiNew, false /*fWithChksum*/,
+ pcbFilePath, pcbPad);
+ break;
+ case RTZIPCPIOTYPE_ASCII_NEW_CHKSUM:
+ rc = rtZipCpioReaderParseHeaderAsciiNew(pThis, &pHdr->AsciiNew, true /*fWithChksum*/,
+ pcbFilePath, pcbPad);
+ break;
+ default:
+ AssertMsgFailedBreakStmt(("Invalid CPIO type %d\n", enmType), rc = VERR_INTERNAL_ERROR);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Reads the file path from the CPIO archive stream.
+ *
+ * @returns IPRT status code.
+ * @param hVfsIos The I/O stream to read from.
+ * @param pThis The CPIO reader state.
+ * @param cbFilePath Size of the file path in bytes.
+ */
+static int rtZipCpioReaderReadPath(RTVFSIOSTREAM hVfsIos, PRTZIPCPIOREADER pThis, size_t cbFilePath)
+{
+ if (cbFilePath >= sizeof(pThis->szName))
+ return VERR_TAR_NAME_TOO_LONG;
+
+ size_t cbRead;
+ int rc = RTVfsIoStrmRead(hVfsIos, &pThis->szName[0], cbFilePath, true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbRead != cbFilePath)
+ return VERR_TAR_UNEXPECTED_EOS;
+
+ /* The read file name should be zero terminated at the end. */
+ if (pThis->szName[cbFilePath - 1] != '\0')
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+
+ return VINF_SUCCESS;
+}
+
+
+/*
+ *
+ * T h e V F S F i l e s y s t e m S t r e a m B i t s.
+ * T h e V F S F i l e s y s t e m S t r e a m B i t s.
+ * T h e V F S F i l e s y s t e m S t r e a m B i t s.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipCpioFssBaseObj_Close(void *pvThis)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+
+ /* Currently there is nothing we really have to do here. */
+ pThis->offHdr = -1;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipCpioFssBaseObj_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+
+ /*
+ * Copy the desired data.
+ */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER;
+ pObjInfo->Attr.u.UnixOwner.uid = pThis->ObjInfo.Attr.u.Unix.uid;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP;
+ pObjInfo->Attr.u.UnixGroup.gid = pThis->ObjInfo.Attr.u.Unix.gid;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE;
+ RT_ZERO(pObjInfo->Attr.u);
+ break;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tar filesystem base object operations.
+ */
+static const RTVFSOBJOPS g_rtZipCpioFssBaseObjOps =
+{
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_BASE,
+ "CpioFsStream::Obj",
+ rtZipCpioFssBaseObj_Close,
+ rtZipCpioFssBaseObj_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_Close(void *pvThis)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)pvThis;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return rtZipCpioFssBaseObj_Close(&pThis->BaseObj);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)pvThis;
+ return rtZipCpioFssBaseObj_QueryInfo(&pThis->BaseObj, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)pvThis;
+ Assert(pSgBuf->cSegs == 1);
+
+ /*
+ * Make offset into a real offset so it's possible to do random access
+ * on CPIO files that are seekable. Fend of reads beyond the end of the
+ * stream.
+ */
+ if (off < 0)
+ off = pThis->offFile;
+ if (off >= pThis->cbFile)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+
+ Assert(pThis->cbFile >= pThis->offFile);
+ uint64_t cbLeft = (uint64_t)(pThis->cbFile - off);
+ size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
+ if (cbToRead > cbLeft)
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ cbToRead = (size_t)cbLeft;
+ }
+
+ /*
+ * Do the reading.
+ */
+ size_t cbReadStack = 0;
+ if (!pcbRead)
+ pcbRead = &cbReadStack;
+ int rc = RTVfsIoStrmReadAt(pThis->hVfsIos, pThis->offStart + off, pSgBuf->paSegs[0].pvSeg, cbToRead, fBlocking, pcbRead);
+ pThis->offFile = off + *pcbRead;
+ if (pThis->offFile >= pThis->cbFile)
+ {
+ Assert(pThis->offFile == pThis->cbFile);
+ pThis->fEndOfStream = true;
+ RTVfsIoStrmSkip(pThis->hVfsIos, pThis->cbPadding);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ /* Cannot write to a read-only I/O stream. */
+ NOREF(pvThis); NOREF(off); NOREF(pSgBuf); NOREF(fBlocking); NOREF(pcbWritten);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_Flush(void *pvThis)
+{
+ /* It's a read only stream, nothing dirty to flush. */
+ NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)pvThis;
+
+ /* When we've reached the end, read will be set to indicate it. */
+ if ( (fEvents & RTPOLL_EVT_READ)
+ && pThis->fEndOfStream)
+ {
+ int rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, 0, fIntr, pfRetEvents);
+ if (RT_SUCCESS(rc))
+ *pfRetEvents |= RTPOLL_EVT_READ;
+ else
+ *pfRetEvents = RTPOLL_EVT_READ;
+ return VINF_SUCCESS;
+ }
+
+ return RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtZipCpioFssIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPCPIOIOSTREAM pThis = (PRTZIPCPIOIOSTREAM)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tar I/O stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_rtZipCpioFssIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "CpioFsStream::IoStream",
+ rtZipCpioFssIos_Close,
+ rtZipCpioFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipCpioFssIos_Read,
+ rtZipCpioFssIos_Write,
+ rtZipCpioFssIos_Flush,
+ rtZipCpioFssIos_PollOne,
+ rtZipCpioFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipCpioFssSym_Close(void *pvThis)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+ return rtZipCpioFssBaseObj_Close(pThis);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipCpioFssSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+ return rtZipCpioFssBaseObj_QueryInfo(pThis, pObjInfo, enmAddAttr);
+}
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtZipCpioFssSym_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ NOREF(pvThis); NOREF(fMode); NOREF(fMask);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtZipCpioFssSym_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ NOREF(pvThis); NOREF(pAccessTime); NOREF(pModificationTime); NOREF(pChangeTime); NOREF(pBirthTime);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtZipCpioFssSym_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ NOREF(pvThis); NOREF(uid); NOREF(gid);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSSYMLINKOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipCpioFssSym_Read(void *pvThis, char *pszTarget, size_t cbTarget)
+{
+ PRTZIPCPIOBASEOBJ pThis = (PRTZIPCPIOBASEOBJ)pvThis;
+ return RTStrCopy(pszTarget, cbTarget, pThis->pCpioReader->szTarget);
+}
+
+
+/**
+ * CPIO symbolic (and hardlink) operations.
+ */
+static const RTVFSSYMLINKOPS g_rtZipCpioFssSymOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_SYMLINK,
+ "CpioFsStream::Symlink",
+ rtZipCpioFssSym_Close,
+ rtZipCpioFssSym_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSSYMLINKOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj),
+ rtZipCpioFssSym_SetMode,
+ rtZipCpioFssSym_SetTimes,
+ rtZipCpioFssSym_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtZipCpioFssSym_Read,
+ RTVFSSYMLINKOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipCpioFss_Close(void *pvThis)
+{
+ PRTZIPCPIOFSSTREAM pThis = (PRTZIPCPIOFSSTREAM)pvThis;
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipCpioFss_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPCPIOFSSTREAM pThis = (PRTZIPCPIOFSSTREAM)pvThis;
+ /* Take the lazy approach here, with the sideffect of providing some info
+ that is actually kind of useful. */
+ return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnNext}
+ */
+DECL_HIDDEN_CALLBACK(int) rtZipCpioFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ PRTZIPCPIOFSSTREAM pThis = (PRTZIPCPIOFSSTREAM)pvThis;
+
+ /*
+ * Dispense with the current object.
+ */
+ if (pThis->hVfsCurObj != NIL_RTVFSOBJ)
+ {
+ if (pThis->pCurIosData)
+ {
+ pThis->pCurIosData->fEndOfStream = true;
+ pThis->pCurIosData->offFile = pThis->pCurIosData->cbFile;
+ pThis->pCurIosData = NULL;
+ }
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ }
+
+ /*
+ * Check if we've already reached the end in some way.
+ */
+ if (pThis->fEndOfStream)
+ return VERR_EOF;
+ if (pThis->rcFatal != VINF_SUCCESS)
+ return pThis->rcFatal;
+
+ /*
+ * Make sure the input stream is in the right place.
+ */
+ RTFOFF offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ while ( offHdr >= 0
+ && offHdr < pThis->offNextHdr)
+ {
+ int rc = RTVfsIoStrmSkip(pThis->hVfsIos, pThis->offNextHdr - offHdr);
+ if (RT_FAILURE(rc))
+ {
+ /** @todo Ignore if we're at the end of the stream? */
+ return pThis->rcFatal = rc;
+ }
+
+ offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ }
+
+ if (offHdr < 0)
+ return pThis->rcFatal = (int)offHdr;
+ if (offHdr > pThis->offNextHdr)
+ return pThis->rcFatal = VERR_INTERNAL_ERROR_3;
+ Assert(pThis->offNextHdr == offHdr);
+ pThis->offCurHdr = offHdr;
+
+ /*
+ * Consume CPIO headers.
+ */
+ size_t cbHdr = 0;
+ /*
+ * Read the next header.
+ *
+ * Read the first 6 bytes to determine the header type and continue reading the
+ * rest of the header.
+ */
+ CPIOHDR Hdr;
+ RTZIPCPIOTYPE enmHdrType = RTZIPCPIOTYPE_INVALID;
+ size_t cbRead;
+ int rc = RTVfsIoStrmRead(pThis->hVfsIos, &Hdr.ab[0], sizeof(Hdr.AsciiNew.achMagic), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ if (rc == VINF_EOF && cbRead == 0)
+ {
+ pThis->fEndOfStream = true;
+ return VERR_EOF;
+ }
+ if (cbRead != sizeof(Hdr.AsciiNew.achMagic))
+ return pThis->rcFatal = VERR_TAR_UNEXPECTED_EOS;
+
+ if (Hdr.AncientBin.u16Magic == CPIO_HDR_BIN_MAGIC)
+ {
+ cbHdr = sizeof(Hdr.AncientBin);
+ enmHdrType = RTZIPCPIOTYPE_ANCIENT_BIN;
+ }
+ else if (!strncmp(&Hdr.AsciiSuSv2.achMagic[0], CPIO_HDR_SUSV2_MAGIC, sizeof(Hdr.AsciiSuSv2.achMagic)))
+ {
+ cbHdr = sizeof(Hdr.AsciiSuSv2);
+ enmHdrType = RTZIPCPIOTYPE_ASCII_SUSV2;
+ }
+ else if (!strncmp(&Hdr.AsciiNew.achMagic[0], CPIO_HDR_NEW_MAGIC, sizeof(Hdr.AsciiNew.achMagic)))
+ {
+ cbHdr = sizeof(Hdr.AsciiNew);
+ enmHdrType = RTZIPCPIOTYPE_ASCII_NEW;
+ }
+ else if (!strncmp(&Hdr.AsciiNew.achMagic[0], CPIO_HDR_NEW_CHKSUM_MAGIC, sizeof(Hdr.AsciiNew.achMagic)))
+ {
+ cbHdr = sizeof(Hdr.AsciiNew);
+ enmHdrType = RTZIPCPIOTYPE_ASCII_NEW_CHKSUM;
+ }
+ else
+ return pThis->rcFatal = VERR_TAR_UNKNOWN_TYPE_FLAG; /** @todo Dedicated status code. */
+
+ /* Read the rest of the header. */
+ size_t cbHdrLeft = cbHdr - sizeof(Hdr.AsciiNew.achMagic);
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, &Hdr.ab[sizeof(Hdr.AsciiNew.achMagic)], cbHdr - sizeof(Hdr.AsciiNew.achMagic), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ if (cbRead != cbHdrLeft)
+ return pThis->rcFatal = VERR_TAR_UNEXPECTED_EOS;
+
+ /*
+ * Parse it.
+ */
+ uint32_t cbFilePath = 0;
+ uint32_t cbPad = 0;
+ rc = rtZipCpioReaderParseHeader(&pThis->CpioReader, enmHdrType, &Hdr, &cbFilePath, &cbPad);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ /* Read the file path following the header. */
+ rc = rtZipCpioReaderReadPath(pThis->hVfsIos, &pThis->CpioReader, cbFilePath);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ if (cbPad)
+ RTVfsIoStrmSkip(pThis->hVfsIos, cbPad);
+ pThis->offNextHdr = offHdr + cbHdr + cbFilePath + cbPad;
+
+ /*
+ * CPIO uses a special trailer file record with a 0 mode and size and using a special
+ * marker filename. The filesystem stream is marked EOS When such a record is encountered
+ * to not try to read anything which might come behind it, imagine an initramfs image consisting
+ * of multiple archives which don't need to be necessarily be all of the CPIO kind (yes, this is
+ * a reality with ubuntu for example containing microcode updates as seperate CPIO archives
+ * coming before the main LZ4 compressed CPIO archive...).
+ */
+ PCRTFSOBJINFO pInfo = &pThis->CpioReader.ObjInfo;
+ if (RT_UNLIKELY( !pInfo->Attr.fMode
+ && !pInfo->cbAllocated
+ && !strcmp(&pThis->CpioReader.szName[0], CPIO_EOS_FILE_NAME)))
+ {
+ pThis->fEndOfStream = true;
+ return VERR_EOF;
+ }
+
+ /*
+ * Create an object of the appropriate type.
+ */
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ RTFMODE fType = pInfo->Attr.fMode & RTFS_TYPE_MASK;
+ switch (fType)
+ {
+ /*
+ * Files are represented by a VFS I/O stream, hardlinks have their content
+ * embedded as it is another file.
+ */
+ case RTFS_TYPE_FILE:
+ {
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPCPIOIOSTREAM pIosData;
+ rc = RTVfsNewIoStream(&g_rtZipCpioFssIosOps,
+ sizeof(*pIosData),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIos,
+ (void **)&pIosData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pIosData->BaseObj.offHdr = offHdr;
+ pIosData->BaseObj.offNextHdr = pThis->offNextHdr;
+ pIosData->BaseObj.pCpioReader = &pThis->CpioReader;
+ pIosData->BaseObj.ObjInfo = *pInfo;
+ pIosData->cbFile = pInfo->cbObject;
+ pIosData->offFile = 0;
+ pIosData->offStart = RTVfsIoStrmTell(pThis->hVfsIos);
+ pIosData->cbPadding = (uint32_t)(pInfo->cbAllocated - pInfo->cbObject);
+ pIosData->fEndOfStream = false;
+ pIosData->hVfsIos = pThis->hVfsIos;
+ RTVfsIoStrmRetain(pThis->hVfsIos);
+
+ pThis->pCurIosData = pIosData;
+ pThis->offNextHdr += pIosData->cbFile + pIosData->cbPadding;
+
+ enmType = RTVFSOBJTYPE_IO_STREAM;
+ hVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ break;
+ }
+
+ case RTFS_TYPE_SYMLINK:
+ {
+ RTVFSSYMLINK hVfsSym;
+ PRTZIPCPIOBASEOBJ pBaseObjData;
+ rc = RTVfsNewSymlink(&g_rtZipCpioFssSymOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsSym,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->offHdr = offHdr;
+ pBaseObjData->offNextHdr = pThis->offNextHdr;
+ pBaseObjData->pCpioReader = &pThis->CpioReader;
+ pBaseObjData->ObjInfo = *pInfo;
+
+ /* Read the body of the symlink (as normal file data). */
+ if (pInfo->cbObject + 1 > (RTFOFF)sizeof(pThis->CpioReader.szTarget))
+ return VERR_TAR_NAME_TOO_LONG;
+
+ cbPad = (uint32_t)(pInfo->cbAllocated - pInfo->cbObject);
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, &pThis->CpioReader.szTarget[0], pInfo->cbObject, true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ if (cbRead != (uint32_t)pInfo->cbObject)
+ return pThis->rcFatal = VERR_TAR_UNEXPECTED_EOS;
+
+ pThis->CpioReader.szTarget[pInfo->cbObject] = '\0';
+
+ if (cbPad)
+ rc = RTVfsIoStrmSkip(pThis->hVfsIos, cbPad);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pThis->offNextHdr += pInfo->cbAllocated;
+
+ enmType = RTVFSOBJTYPE_SYMLINK;
+ hVfsObj = RTVfsObjFromSymlink(hVfsSym);
+ RTVfsSymlinkRelease(hVfsSym);
+ break;
+ }
+
+ /*
+ * All other objects are repesented using a VFS base object since they
+ * carry no data streams.
+ */
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DIRECTORY:
+ case RTFS_TYPE_FIFO:
+ {
+ PRTZIPCPIOBASEOBJ pBaseObjData;
+ rc = RTVfsNewBaseObj(&g_rtZipCpioFssBaseObjOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsObj,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->offHdr = offHdr;
+ pBaseObjData->offNextHdr = pThis->offNextHdr;
+ pBaseObjData->pCpioReader = &pThis->CpioReader;
+ pBaseObjData->ObjInfo = *pInfo;
+
+ enmType = RTVFSOBJTYPE_BASE;
+ break;
+ }
+
+ default:
+ AssertFailed();
+ return pThis->rcFatal = VERR_INTERNAL_ERROR_5;
+ }
+ pThis->hVfsCurObj = hVfsObj;
+
+ /*
+ * Set the return data and we're done.
+ */
+ if (ppszName)
+ {
+ rc = RTStrDupEx(ppszName, pThis->CpioReader.szName);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (phVfsObj)
+ {
+ RTVfsObjRetain(hVfsObj);
+ *phVfsObj = hVfsObj;
+ }
+
+ if (penmType)
+ *penmType = enmType;
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * CPIO filesystem stream operations.
+ */
+static const RTVFSFSSTREAMOPS rtZipCpioFssOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "CpioFsStream",
+ rtZipCpioFss_Close,
+ rtZipCpioFss_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipCpioFss_Next,
+ NULL,
+ NULL,
+ NULL,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+/**
+ * Internal function use both by RTZipCpioFsStreamFromIoStream() and by
+ * RTZipCpioFsStreamForFile() in updating mode.
+ */
+DECLHIDDEN(void) rtZipCpioReaderInit(PRTZIPCPIOFSSTREAM pThis, RTVFSIOSTREAM hVfsIos, uint64_t offStart)
+{
+ pThis->hVfsIos = hVfsIos;
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+ pThis->offStart = offStart;
+ pThis->offNextHdr = offStart;
+ pThis->fEndOfStream = false;
+ pThis->rcFatal = VINF_SUCCESS;
+
+ /* Don't check if it's a CPIO stream here, do that in the
+ rtZipCpioFss_Next. */
+}
+
+
+RTDECL(int) RTZipCpioFsStreamFromIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertPtrReturn(hVfsIosIn, VERR_INVALID_HANDLE);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+
+ RTFOFF const offStart = RTVfsIoStrmTell(hVfsIosIn);
+ AssertReturn(offStart >= 0, (int)offStart);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPCPIOFSSTREAM pThis;
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&rtZipCpioFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_READ,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rtZipCpioReaderInit(pThis, hVfsIosIn, fFlags);
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosIn);
+ return rc;
+}
+
+
+/**
+ * Used by RTZipCpioFsStreamTruncate to resolve @a hVfsObj.
+ */
+DECLHIDDEN(PRTZIPCPIOBASEOBJ) rtZipCpioFsStreamBaseObjToPrivate(PRTZIPCPIOFSSTREAM pThis, RTVFSOBJ hVfsObj)
+{
+ PRTZIPCPIOBASEOBJ pThisObj;
+ RTVFSOBJTYPE enmType = RTVfsObjGetType(hVfsObj);
+ switch (enmType)
+ {
+ case RTVFSOBJTYPE_IO_STREAM:
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, NULL);
+ PRTZIPCPIOIOSTREAM pThisStrm = (PRTZIPCPIOIOSTREAM)RTVfsIoStreamToPrivate(hVfsIos, &g_rtZipCpioFssIosOps);
+ RTVfsIoStrmRelease(hVfsIos);
+ pThisObj = &pThisStrm->BaseObj;
+ break;
+ }
+
+ case RTVFSOBJTYPE_SYMLINK:
+ {
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, NULL);
+ pThisObj = (PRTZIPCPIOBASEOBJ)RTVfsSymlinkToPrivate(hVfsSymlink, &g_rtZipCpioFssSymOps);
+ RTVfsSymlinkRelease(hVfsSymlink);
+ break;
+ }
+
+ case RTVFSOBJTYPE_BASE:
+ pThisObj = (PRTZIPCPIOBASEOBJ)RTVfsObjToPrivate(hVfsObj, &g_rtZipCpioFssBaseObjOps);
+ break;
+
+ default:
+ /** @todo implement. */
+ AssertFailedReturn(NULL);
+ }
+
+ AssertReturn(pThisObj->pCpioReader == &pThis->CpioReader, NULL);
+ return pThisObj;
+}
+
diff --git a/src/VBox/Runtime/common/zip/cpiovfsreader.h b/src/VBox/Runtime/common/zip/cpiovfsreader.h
new file mode 100644
index 00000000..43db36e9
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/cpiovfsreader.h
@@ -0,0 +1,169 @@
+/* $Id: cpiovfsreader.h $ */
+/** @file
+ * IPRT - CPIO Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2020-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_zip_cpiovfsreader_h
+#define IPRT_INCLUDED_SRC_common_zip_cpiovfsreader_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/formats/cpio.h>
+
+
+/**
+ * CPIO archive type.
+ */
+typedef enum RTZIPCPIOTYPE
+{
+ /** Invalid type value. */
+ RTZIPCPIOTYPE_INVALID = 0,
+ /** Ancient binary archive. */
+ RTZIPCPIOTYPE_ANCIENT_BIN,
+ /** Portable ASCII format as defined by SuSV2. */
+ RTZIPCPIOTYPE_ASCII_SUSV2,
+ /** "New" ASCII format. */
+ RTZIPCPIOTYPE_ASCII_NEW,
+ /** "New" ASCII format with checksumming. */
+ RTZIPCPIOTYPE_ASCII_NEW_CHKSUM,
+ /** End of the valid type values (this is not valid). */
+ RTZIPCPIOTYPE_END,
+ /** The usual type blow up. */
+ RTZIPCPIOTYPE_32BIT_HACK = 0x7fffffff
+} RTZIPCPIOTYPE;
+typedef RTZIPCPIOTYPE *PRTZIPCPIOTYPE;
+
+
+/**
+ * CPIO reader instance data.
+ */
+typedef struct RTZIPCPIOREADER
+{
+ /** The object info with unix attributes. */
+ RTFSOBJINFO ObjInfo;
+ /** The path length. */
+ uint32_t cbPath;
+ /** The name of the current object. */
+ char szName[RTPATH_MAX];
+ /** The current link target if symlink. */
+ char szTarget[RTPATH_MAX];
+} RTZIPCPIOREADER;
+/** Pointer to the CPIO reader instance data. */
+typedef RTZIPCPIOREADER *PRTZIPCPIOREADER;
+
+/**
+ * CPIO directory, character device, block device, fifo socket or symbolic link.
+ */
+typedef struct RTZIPCPIOBASEOBJ
+{
+ /** The stream offset of the (first) header in the input stream/file. */
+ RTFOFF offHdr;
+ /** The stream offset of the first header of the next object (for truncating the
+ * tar file after this object (updating)). */
+ RTFOFF offNextHdr;
+ /** Pointer to the reader instance data (resides in the filesystem
+ * stream).
+ * @todo Fix this so it won't go stale... Back ref from this obj to fss? */
+ PRTZIPCPIOREADER pCpioReader;
+ /** The object info with unix attributes. */
+ RTFSOBJINFO ObjInfo;
+} RTZIPCPIOBASEOBJ;
+/** Pointer to a CPIO filesystem stream base object. */
+typedef RTZIPCPIOBASEOBJ *PRTZIPCPIOBASEOBJ;
+
+
+/**
+ * CPIO file represented as a VFS I/O stream.
+ */
+typedef struct RTZIPCPIOIOSTREAM
+{
+ /** The basic TAR object data. */
+ RTZIPCPIOBASEOBJ BaseObj;
+ /** The number of bytes in the file. */
+ RTFOFF cbFile;
+ /** The current file position. */
+ RTFOFF offFile;
+ /** The start position in the hVfsIos (for seekable hVfsIos). */
+ RTFOFF offStart;
+ /** The number of padding bytes following the file. */
+ uint32_t cbPadding;
+ /** Set if we've reached the end of this file. */
+ bool fEndOfStream;
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+} RTZIPCPIOIOSTREAM;
+/** Pointer to a the private data of a CPIO file I/O stream. */
+typedef RTZIPCPIOIOSTREAM *PRTZIPCPIOIOSTREAM;
+
+
+/**
+ * CPIO filesystem stream private data.
+ */
+typedef struct RTZIPCPIOFSSTREAM
+{
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+
+ /** The current object (referenced). */
+ RTVFSOBJ hVfsCurObj;
+ /** Pointer to the private data if hVfsCurObj is representing a file. */
+ PRTZIPCPIOIOSTREAM pCurIosData;
+
+ /** The start offset. */
+ RTFOFF offStart;
+ /** The offset of the next header. */
+ RTFOFF offNextHdr;
+ /** The offset of the first header for the current object.
+ * When reaching the end, this will be the same as offNextHdr which will be
+ * pointing to the first zero header */
+ RTFOFF offCurHdr;
+
+ /** Set if we've reached the end of the stream. */
+ bool fEndOfStream;
+ /** Set if we've encountered a fatal error. */
+ int rcFatal;
+
+ /** The CPIO reader instance data. */
+ RTZIPCPIOREADER CpioReader;
+} RTZIPCPIOFSSTREAM;
+/** Pointer to a the private data of a CPIO filesystem stream. */
+typedef RTZIPCPIOFSSTREAM *PRTZIPCPIOFSSTREAM;
+
+DECLHIDDEN(void) rtZipCpioReaderInit(PRTZIPCPIOFSSTREAM pThis, RTVFSIOSTREAM hVfsIos, uint64_t offStart);
+DECL_HIDDEN_CALLBACK(int) rtZipCpioFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj);
+DECLHIDDEN(PRTZIPCPIOBASEOBJ) rtZipCpioFsStreamBaseObjToPrivate(PRTZIPCPIOFSSTREAM pThis, RTVFSOBJ hVfsObj);
+
+#endif /* !IPRT_INCLUDED_SRC_common_zip_cpiovfsreader_h */
+
diff --git a/src/VBox/Runtime/common/zip/gzipcmd.cpp b/src/VBox/Runtime/common/zip/gzipcmd.cpp
new file mode 100644
index 00000000..0c00bf24
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/gzipcmd.cpp
@@ -0,0 +1,606 @@
+/* $Id: gzipcmd.cpp $ */
+/** @file
+ * IPRT - GZIP Utility.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+
+#include <iprt/buildconfig.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/zip.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Gzip command options.
+ */
+typedef struct RTGZIPCMDOPTS
+{
+ bool fAscii;
+ bool fStdOut;
+ bool fDecompress;
+ bool fForce;
+ bool fKeep;
+ bool fList;
+ bool fName;
+ bool fQuiet;
+ bool fRecursive;
+ const char *pszSuff;
+ bool fTest;
+ unsigned uLevel;
+ /** The current output filename (for deletion). */
+ char szOutput[RTPATH_MAX];
+ /** The current input filename (for deletion and messages). */
+ const char *pszInput;
+} RTGZIPCMDOPTS;
+/** Pointer to GZIP options. */
+typedef RTGZIPCMDOPTS *PRTGZIPCMDOPTS;
+/** Pointer to const GZIP options. */
+typedef RTGZIPCMDOPTS const *PCRTGZIPCMDOPTS;
+
+
+
+/**
+ * Checks if the given standard handle is a TTY.
+ *
+ * @returns true / false
+ * @param enmStdHandle The standard handle.
+ */
+static bool gzipIsStdHandleATty(RTHANDLESTD enmStdHandle)
+{
+ /** @todo Add isatty() to IPRT. */
+ RT_NOREF1(enmStdHandle);
+ return false;
+}
+
+
+/**
+ * Pushes data from the input to the output I/O streams.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE.
+ * @param hVfsSrc The source I/O stream.
+ * @param hVfsDst The destination I/O stream.
+ */
+static RTEXITCODE gzipPush(RTVFSIOSTREAM hVfsSrc, RTVFSIOSTREAM hVfsDst)
+{
+ for (;;)
+ {
+ uint8_t abBuf[_64K];
+ size_t cbRead;
+ int rc = RTVfsIoStrmRead(hVfsSrc, abBuf, sizeof(abBuf), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmRead failed: %Rrc", rc);
+ if (rc == VINF_EOF && cbRead == 0)
+ return RTEXITCODE_SUCCESS;
+
+ rc = RTVfsIoStrmWrite(hVfsDst, abBuf, cbRead, true /*fBlocking*/, NULL /*cbWritten*/);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmWrite failed: %Rrc", rc);
+ }
+}
+
+
+/**
+ * Pushes the bytes from the input to the output stream, flushes the output
+ * stream and closes both of them.
+ *
+ * On failure, we will delete the output file, if it's a file. The input file
+ * may be deleted, if we're not told to keep it (--keep, --to-stdout).
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE.
+ * @param phVfsSrc The input stream. Set to NIL if closed.
+ * @param pOpts The options.
+ * @param phVfsDst The output stream. Set to NIL if closed.
+ */
+static RTEXITCODE gzipPushFlushAndClose(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsDst)
+{
+ /*
+ * Push bytes, flush and close the streams.
+ */
+ RTEXITCODE rcExit = gzipPush(*phVfsSrc, *phVfsDst);
+
+ RTVfsIoStrmRelease(*phVfsSrc);
+ *phVfsSrc = NIL_RTVFSIOSTREAM;
+
+ int rc = RTVfsIoStrmFlush(*phVfsDst);
+ if (RT_FAILURE(rc) && rc != VERR_INVALID_PARAMETER)
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to flush the output file: %Rrc", rc);
+ RTVfsIoStrmRelease(*phVfsDst);
+ *phVfsDst = NIL_RTVFSIOSTREAM;
+
+ /*
+ * Do the cleaning up, if needed. Remove the input file, if that's the
+ * desire of the user, or remove the output file on failure.
+ */
+ if (!pOpts->fStdOut)
+ {
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ if (!pOpts->fKeep)
+ {
+ rc = RTFileDelete(pOpts->pszInput);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to delete '%s': %Rrc", pOpts->pszInput, rc);
+ }
+ }
+ else
+ {
+ rc = RTFileDelete(pOpts->szOutput);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to delete '%s': %Rrc", pOpts->szOutput, rc);
+ }
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Compresses one stream to another.
+ *
+ * @returns Exit code.
+ * @param phVfsSrc The input stream. Set to NIL if closed.
+ * @param pOpts The options.
+ * @param phVfsDst The output stream. Set to NIL if closed.
+ */
+static RTEXITCODE gzipCompressFile(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsDst)
+{
+ /*
+ * Attach the ompressor to the output stream.
+ */
+ RTVFSIOSTREAM hVfsGzip;
+ int rc = RTZipGzipCompressIoStream(*phVfsDst, 0 /*fFlags*/, pOpts->uLevel, &hVfsGzip);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTZipGzipCompressIoStream failed: %Rrc", rc);
+
+ uint32_t cRefs = RTVfsIoStrmRelease(*phVfsDst);
+ Assert(cRefs > 0); RT_NOREF_PV(cRefs);
+ *phVfsDst = hVfsGzip;
+
+ return gzipPushFlushAndClose(phVfsSrc, pOpts, phVfsDst);
+}
+
+
+/**
+ * Attach a decompressor to the given source stream, replacing and releasing the
+ * input handle with the decompressor.
+ *
+ * @returns Exit code.
+ * @param phVfsSrc The input stream. Replaced on success.
+ */
+static RTEXITCODE gzipSetupDecompressor(PRTVFSIOSTREAM phVfsSrc)
+{
+ /*
+ * Attach the decompressor to the input stream.
+ */
+ uint32_t fFlags = 0;
+ fFlags |= RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR;
+ RTVFSIOSTREAM hVfsGunzip;
+ int rc = RTZipGzipDecompressIoStream(*phVfsSrc, fFlags, &hVfsGunzip);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTZipGzipDecompressIoStream failed: %Rrc", rc);
+
+ uint32_t cRefs = RTVfsIoStrmRelease(*phVfsSrc);
+ Assert(cRefs > 0); RT_NOREF_PV(cRefs);
+ *phVfsSrc = hVfsGunzip;
+
+#if 0
+ /* This is a good place for testing stuff. */
+ rc = RTVfsCreateReadAheadForIoStream(*phVfsSrc, 0, 16, _4K+1, &hVfsGunzip);
+ AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t cRefs = RTVfsIoStrmRelease(*phVfsSrc);
+ Assert(cRefs > 0);
+ *phVfsSrc = hVfsGunzip;
+ }
+#endif
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Decompresses one stream to another.
+ *
+ * @returns Exit code.
+ * @param phVfsSrc The input stream. Set to NIL if closed.
+ * @param pOpts The options.
+ * @param phVfsDst The output stream. Set to NIL if closed.
+ */
+static RTEXITCODE gzipDecompressFile(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsDst)
+{
+ RTEXITCODE rcExit = gzipSetupDecompressor(phVfsSrc);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ rcExit = gzipPushFlushAndClose(phVfsSrc, pOpts, phVfsDst);
+ return rcExit;
+}
+
+
+/**
+ * For testing the archive (todo).
+ *
+ * @returns Exit code.
+ * @param phVfsSrc The input stream. Set to NIL if closed.
+ * @param pOpts The options.
+ */
+static RTEXITCODE gzipTestFile(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts)
+{
+ RT_NOREF_PV(pOpts);
+
+ /*
+ * Read the whole stream.
+ */
+ RTEXITCODE rcExit = gzipSetupDecompressor(phVfsSrc);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ for (;;)
+ {
+ uint8_t abBuf[_64K];
+ size_t cbRead;
+ int rc = RTVfsIoStrmRead(*phVfsSrc, abBuf, sizeof(abBuf), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmRead failed: %Rrc", rc);
+ if (rc == VINF_EOF && cbRead == 0)
+ return RTEXITCODE_SUCCESS;
+ }
+ }
+ return rcExit;
+}
+
+
+static RTEXITCODE gzipListFile(PRTVFSIOSTREAM phVfsSrc, PCRTGZIPCMDOPTS pOpts)
+{
+ RT_NOREF2(phVfsSrc, pOpts);
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Listing has not been implemented");
+}
+
+
+/**
+ * Opens the output file.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ *
+ * @param pszFile The input filename.
+ * @param pOpts The options, szOutput will be filled in by this
+ * function on success.
+ * @param phVfsIos Where to return the output stream handle.
+ *
+ * @remarks This is actually not quite the way we need to do things.
+ *
+ * First of all, we need a GZIP file system stream for a real GZIP
+ * implementation, since there may be more than one file in the gzipped
+ * file.
+ *
+ * Second, we need to open the output files as we encounter files in the input
+ * file system stream. The gzip format contains timestamp and usually a
+ * filename, the default is to use this name (see the --no-name
+ * option).
+ */
+static RTEXITCODE gzipOpenOutput(const char *pszFile, PRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsIos)
+{
+ int rc;
+ if (!strcmp(pszFile, "-") || pOpts->fStdOut)
+ {
+ strcpy(pOpts->szOutput, "-");
+
+ if ( !pOpts->fForce
+ && !pOpts->fDecompress
+ && gzipIsStdHandleATty(RTHANDLESTD_OUTPUT))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Yeah, right. I'm not writing any compressed data to the terminal without --force.\n");
+
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT,
+ RTFILE_O_WRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ true /*fLeaveOpen*/,
+ phVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening standard output: %Rrc", rc);
+ }
+ else
+ {
+ Assert(!RTVfsChainIsSpec(pszFile));
+
+ /* Construct an output filename. */
+ rc = RTStrCopy(pOpts->szOutput, sizeof(pOpts->szOutput), pszFile);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error constructing output filename: %Rrc", rc);
+ if (pOpts->fDecompress)
+ {
+ /** @todo take filename from archive? */
+ size_t cchSuff = strlen(pOpts->pszSuff); Assert(cchSuff > 0);
+ size_t cch = strlen(pOpts->szOutput);
+ if ( cch <= cchSuff
+ || strcmp(&pOpts->szOutput[cch - cchSuff], pOpts->pszSuff))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Input file does not end with: '%s'", pOpts->pszSuff);
+ pOpts->szOutput[cch - cchSuff] = '\0';
+ if (!RTPathFilename(pOpts->szOutput))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error constructing output filename: Input file name is all suffix.");
+ }
+ else
+ {
+ rc = RTStrCat(pOpts->szOutput, sizeof(pOpts->szOutput), pOpts->pszSuff);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error constructing output filename: %Rrc", rc);
+ }
+
+ /* Open the output file. */
+ uint32_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
+ if (pOpts->fForce)
+ fOpen |= RTFILE_O_CREATE_REPLACE;
+ else
+ fOpen |= RTFILE_O_CREATE;
+ rc = RTVfsIoStrmOpenNormal(pOpts->szOutput, fOpen, phVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening output file '%s': %Rrc", pOpts->szOutput, rc);
+ }
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Opens the input file.
+ *
+ * @returns Command exit, error messages written using RTMsg*.
+ *
+ * @param pszFile The input filename.
+ * @param pOpts The options, szOutput will be filled in by this
+ * function on success.
+ * @param phVfsIos Where to return the input stream handle.
+ */
+static RTEXITCODE gzipOpenInput(const char *pszFile, PRTGZIPCMDOPTS pOpts, PRTVFSIOSTREAM phVfsIos)
+{
+ int rc;
+
+ pOpts->pszInput = pszFile;
+ if (!strcmp(pszFile, "-"))
+ {
+ if ( !pOpts->fForce
+ && pOpts->fDecompress
+ && gzipIsStdHandleATty(RTHANDLESTD_OUTPUT))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Yeah, right. I'm not reading any compressed data from the terminal without --force.\n");
+
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT,
+ RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ true /*fLeaveOpen*/,
+ phVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error opening standard input: %Rrc", rc);
+ }
+ else
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pszFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE,
+ phVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszFile, rc, offError, &ErrInfo.Core);
+ }
+
+ return RTEXITCODE_SUCCESS;
+
+}
+
+
+/**
+ * A mini GZIP program.
+ *
+ * @returns Program exit code.
+ *
+ * @param cArgs The number of arguments.
+ * @param papszArgs The argument vector. (Note that this may be
+ * reordered, so the memory must be writable.)
+ */
+RTDECL(RTEXITCODE) RTZipGzipCmd(unsigned cArgs, char **papszArgs)
+{
+
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ { "--ascii", 'a', RTGETOPT_REQ_NOTHING },
+ { "--stdout", 'c', RTGETOPT_REQ_NOTHING },
+ { "--to-stdout", 'c', RTGETOPT_REQ_NOTHING },
+ { "--decompress", 'd', RTGETOPT_REQ_NOTHING },
+ { "--uncompress", 'd', RTGETOPT_REQ_NOTHING },
+ { "--force", 'f', RTGETOPT_REQ_NOTHING },
+ { "--keep", 'k', RTGETOPT_REQ_NOTHING },
+ { "--list", 'l', RTGETOPT_REQ_NOTHING },
+ { "--no-name", 'n', RTGETOPT_REQ_NOTHING },
+ { "--name", 'N', RTGETOPT_REQ_NOTHING },
+ { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
+ { "--recursive", 'r', RTGETOPT_REQ_NOTHING },
+ { "--suffix", 'S', RTGETOPT_REQ_STRING },
+ { "--test", 't', RTGETOPT_REQ_NOTHING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--fast", '1', RTGETOPT_REQ_NOTHING },
+ { "-1", '1', RTGETOPT_REQ_NOTHING },
+ { "-2", '2', RTGETOPT_REQ_NOTHING },
+ { "-3", '3', RTGETOPT_REQ_NOTHING },
+ { "-4", '4', RTGETOPT_REQ_NOTHING },
+ { "-5", '5', RTGETOPT_REQ_NOTHING },
+ { "-6", '6', RTGETOPT_REQ_NOTHING },
+ { "-7", '7', RTGETOPT_REQ_NOTHING },
+ { "-8", '8', RTGETOPT_REQ_NOTHING },
+ { "-9", '9', RTGETOPT_REQ_NOTHING },
+ { "--best", '9', RTGETOPT_REQ_NOTHING }
+ };
+
+ RTGZIPCMDOPTS Opts;
+ Opts.fAscii = false;
+ Opts.fStdOut = false;
+ Opts.fDecompress = false;
+ Opts.fForce = false;
+ Opts.fKeep = false;
+ Opts.fList = false;
+ Opts.fName = true;
+ Opts.fQuiet = false;
+ Opts.fRecursive = false;
+ Opts.pszSuff = ".gz";
+ Opts.fTest = false;
+ Opts.uLevel = 6;
+
+ RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
+ unsigned cProcessed = 0;
+ //RTVFSIOSTREAM hVfsStdOut= NIL_RTVFSIOSTREAM;
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTGetOptInit: %Rrc", rc);
+
+ for (;;)
+ {
+ RTGETOPTUNION ValueUnion;
+ int chOpt = RTGetOpt(&GetState, &ValueUnion);
+ switch (chOpt)
+ {
+ case 0:
+ /*
+ * If we've processed any files we're done. Otherwise take
+ * input from stdin and write the output to stdout.
+ */
+ if (cProcessed > 0)
+ return rcExit;
+ ValueUnion.psz = "-";
+ Opts.fStdOut = true;
+ RT_FALL_THRU();
+ case VINF_GETOPT_NOT_OPTION:
+ {
+ if (!*Opts.pszSuff && !Opts.fStdOut)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --suffix option specified an empty string");
+ if (!Opts.fStdOut && RTVfsChainIsSpec(ValueUnion.psz))
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Must use standard out with VFS chain specifications");
+ if ( Opts.fName
+ && !Opts.fList
+ && !Opts.fTest
+ && !Opts.fDecompress)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --name option has not yet been implemented. Use --no-name.");
+ if (Opts.fAscii)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --ascii option has not yet been implemented.");
+ if (Opts.fRecursive)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --recursive option has not yet been implemented.");
+
+ /* Open the input file. */
+ RTVFSIOSTREAM hVfsSrc;
+ RTEXITCODE rcExit2 = gzipOpenInput(ValueUnion.psz, &Opts, &hVfsSrc);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ if (Opts.fList)
+ rcExit2 = gzipListFile(&hVfsSrc, &Opts);
+ else if (Opts.fTest)
+ rcExit2 = gzipTestFile(&hVfsSrc, &Opts);
+ else
+ {
+ RTVFSIOSTREAM hVfsDst;
+ rcExit2 = gzipOpenOutput(ValueUnion.psz, &Opts, &hVfsDst);
+ if (rcExit2 == RTEXITCODE_SUCCESS)
+ {
+ if (Opts.fDecompress)
+ rcExit2 = gzipDecompressFile(&hVfsSrc, &Opts, &hVfsDst);
+ else
+ rcExit2 = gzipCompressFile(&hVfsSrc, &Opts, &hVfsDst);
+ RTVfsIoStrmRelease(hVfsDst);
+ }
+ }
+ RTVfsIoStrmRelease(hVfsSrc);
+ }
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+
+ cProcessed++;
+ break;
+ }
+
+ case 'a': Opts.fAscii = true; break;
+ case 'c':
+ Opts.fStdOut = true;
+ Opts.fKeep = true;
+ break;
+ case 'd': Opts.fDecompress = true; break;
+ case 'f': Opts.fForce = true; break;
+ case 'k': Opts.fKeep = true; break;
+ case 'l': Opts.fList = true; break;
+ case 'n': Opts.fName = false; break;
+ case 'N': Opts.fName = true; break;
+ case 'q': Opts.fQuiet = true; break;
+ case 'r': Opts.fRecursive = true; break;
+ case 'S': Opts.pszSuff = ValueUnion.psz; break;
+ case 't': Opts.fTest = true; break;
+ case 'v': Opts.fQuiet = false; break;
+ case '1': Opts.uLevel = 1; break;
+ case '2': Opts.uLevel = 2; break;
+ case '3': Opts.uLevel = 3; break;
+ case '4': Opts.uLevel = 4; break;
+ case '5': Opts.uLevel = 5; break;
+ case '6': Opts.uLevel = 6; break;
+ case '7': Opts.uLevel = 7; break;
+ case '8': Opts.uLevel = 8; break;
+ case '9': Opts.uLevel = 9; break;
+
+ case 'h':
+ RTPrintf("Usage: to be written\nOption dump:\n");
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aOptions); i++)
+ RTPrintf(" -%c,%s\n", s_aOptions[i].iShort, s_aOptions[i].pszLong);
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(chOpt, &ValueUnion);
+ }
+ }
+}
+
diff --git a/src/VBox/Runtime/common/zip/gzipvfs.cpp b/src/VBox/Runtime/common/zip/gzipvfs.cpp
new file mode 100644
index 00000000..de38938a
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/gzipvfs.cpp
@@ -0,0 +1,1035 @@
+/* $Id: gzipvfs.cpp $ */
+/** @file
+ * IPRT - GZIP Compressor and Decompressor I/O Stream.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/vfslowlevel.h>
+
+#include <zlib.h>
+
+#if defined(RT_OS_OS2) || defined(RT_OS_SOLARIS) || defined(RT_OS_WINDOWS)
+/**
+ * Drag in the missing zlib symbols.
+ */
+PFNRT g_apfnRTZlibDeps[] =
+{
+ (PFNRT)gzrewind,
+ (PFNRT)gzread,
+ (PFNRT)gzopen,
+ (PFNRT)gzwrite,
+ (PFNRT)gzclose,
+ (PFNRT)gzdopen,
+ NULL
+};
+#endif /* RT_OS_OS2 || RT_OS_SOLARIS || RT_OS_WINDOWS */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+#pragma pack(1)
+typedef struct RTZIPGZIPHDR
+{
+ /** RTZIPGZIPHDR_ID1. */
+ uint8_t bId1;
+ /** RTZIPGZIPHDR_ID2. */
+ uint8_t bId2;
+ /** CM - The compression method. */
+ uint8_t bCompressionMethod;
+ /** FLG - Flags. */
+ uint8_t fFlags;
+ /** Modification time of the source file or the timestamp at the time the
+ * compression took place. Can also be zero. Is the number of seconds since
+ * unix epoch. */
+ uint32_t u32ModTime;
+ /** Flags specific to the compression method. */
+ uint8_t bXtraFlags;
+ /** An ID indicating which OS or FS gzip ran on. */
+ uint8_t bOS;
+} RTZIPGZIPHDR;
+#pragma pack()
+AssertCompileSize(RTZIPGZIPHDR, 10);
+/** Pointer to a const gzip header. */
+typedef RTZIPGZIPHDR const *PCRTZIPGZIPHDR;
+
+/** gzip header identification no 1. */
+#define RTZIPGZIPHDR_ID1 0x1f
+/** gzip header identification no 2. */
+#define RTZIPGZIPHDR_ID2 0x8b
+/** gzip deflate compression method. */
+#define RTZIPGZIPHDR_CM_DEFLATE 8
+
+/** @name gzip header flags
+ * @{ */
+/** Probably a text file */
+#define RTZIPGZIPHDR_FLG_TEXT UINT8_C(0x01)
+/** Header CRC present (crc32 of header cast to uint16_t). */
+#define RTZIPGZIPHDR_FLG_HDR_CRC UINT8_C(0x02)
+/** Length prefixed xtra field is present. */
+#define RTZIPGZIPHDR_FLG_EXTRA UINT8_C(0x04)
+/** A name field is present (latin-1). */
+#define RTZIPGZIPHDR_FLG_NAME UINT8_C(0x08)
+/** A comment field is present (latin-1). */
+#define RTZIPGZIPHDR_FLG_COMMENT UINT8_C(0x10)
+/** Mask of valid flags. */
+#define RTZIPGZIPHDR_FLG_VALID_MASK UINT8_C(0x1f)
+/** @} */
+
+/** @name gzip default xtra flag values
+ * @{ */
+#define RTZIPGZIPHDR_XFL_DEFLATE_MAX UINT8_C(0x02)
+#define RTZIPGZIPHDR_XFL_DEFLATE_FASTEST UINT8_C(0x04)
+/** @} */
+
+/** @name Operating system / Filesystem IDs
+ * @{ */
+#define RTZIPGZIPHDR_OS_FAT UINT8_C(0x00)
+#define RTZIPGZIPHDR_OS_AMIGA UINT8_C(0x01)
+#define RTZIPGZIPHDR_OS_VMS UINT8_C(0x02)
+#define RTZIPGZIPHDR_OS_UNIX UINT8_C(0x03)
+#define RTZIPGZIPHDR_OS_VM_CMS UINT8_C(0x04)
+#define RTZIPGZIPHDR_OS_ATARIS_TOS UINT8_C(0x05)
+#define RTZIPGZIPHDR_OS_HPFS UINT8_C(0x06)
+#define RTZIPGZIPHDR_OS_MACINTOSH UINT8_C(0x07)
+#define RTZIPGZIPHDR_OS_Z_SYSTEM UINT8_C(0x08)
+#define RTZIPGZIPHDR_OS_CPM UINT8_C(0x09)
+#define RTZIPGZIPHDR_OS_TOPS_20 UINT8_C(0x0a)
+#define RTZIPGZIPHDR_OS_NTFS UINT8_C(0x0b)
+#define RTZIPGZIPHDR_OS_QDOS UINT8_C(0x0c)
+#define RTZIPGZIPHDR_OS_ACORN_RISCOS UINT8_C(0x0d)
+#define RTZIPGZIPHDR_OS_UNKNOWN UINT8_C(0xff)
+/** @} */
+
+
+/**
+ * The internal data of a GZIP I/O stream.
+ */
+typedef struct RTZIPGZIPSTREAM
+{
+ /** The stream we're reading or writing the compressed data from or to. */
+ RTVFSIOSTREAM hVfsIos;
+ /** Set if it's a decompressor, clear if it's a compressor. */
+ bool fDecompress;
+ /** Set if zlib reported a fatal error. */
+ bool fFatalError;
+ /** Set if we've reached the end of the zlib stream. */
+ bool fEndOfStream;
+ /** The stream offset for pfnTell, always the uncompressed data. */
+ RTFOFF offStream;
+ /** The zlib stream. */
+ z_stream Zlib;
+ /** The data buffer. */
+ uint8_t abBuffer[_64K];
+ /** Scatter gather segment describing abBuffer. */
+ RTSGSEG SgSeg;
+ /** Scatter gather buffer describing abBuffer. */
+ RTSGBUF SgBuf;
+ /** The original file name (decompressor only). */
+ char *pszOrgName;
+ /** The comment (decompressor only). */
+ char *pszComment;
+ /** The gzip header. */
+ RTZIPGZIPHDR Hdr;
+} RTZIPGZIPSTREAM;
+/** Pointer to a the internal data of a GZIP I/O stream. */
+typedef RTZIPGZIPSTREAM *PRTZIPGZIPSTREAM;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int rtZipGzip_FlushIt(PRTZIPGZIPSTREAM pThis, uint8_t fFlushType);
+
+
+/**
+ * Convert from zlib to IPRT status codes.
+ *
+ * This will also set the fFatalError flag when appropriate.
+ *
+ * @returns IPRT status code.
+ * @param pThis The gzip I/O stream instance data.
+ * @param rc Zlib error code.
+ */
+static int rtZipGzipConvertErrFromZlib(PRTZIPGZIPSTREAM pThis, int rc)
+{
+ switch (rc)
+ {
+ case Z_OK:
+ return VINF_SUCCESS;
+
+ case Z_BUF_ERROR:
+ /* This isn't fatal. */
+ return VINF_SUCCESS; /** @todo The code in zip.cpp treats Z_BUF_ERROR as fatal... */
+
+ case Z_STREAM_ERROR:
+ pThis->fFatalError = true;
+ return VERR_ZIP_CORRUPTED;
+
+ case Z_DATA_ERROR:
+ pThis->fFatalError = true;
+ return pThis->fDecompress ? VERR_ZIP_CORRUPTED : VERR_ZIP_ERROR;
+
+ case Z_MEM_ERROR:
+ pThis->fFatalError = true;
+ return VERR_ZIP_NO_MEMORY;
+
+ case Z_VERSION_ERROR:
+ pThis->fFatalError = true;
+ return VERR_ZIP_UNSUPPORTED_VERSION;
+
+ case Z_ERRNO: /* We shouldn't see this status! */
+ default:
+ AssertMsgFailed(("%d\n", rc));
+ if (rc >= 0)
+ return VINF_SUCCESS;
+ pThis->fFatalError = true;
+ return VERR_ZIP_ERROR;
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipGzip_Close(void *pvThis)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+
+ int rc;
+ if (pThis->fDecompress)
+ {
+ rc = inflateEnd(&pThis->Zlib);
+ if (rc != Z_OK)
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc);
+ }
+ else
+ {
+ /* Flush the compression stream before terminating it. */
+ rc = VINF_SUCCESS;
+ if (!pThis->fFatalError)
+ rc = rtZipGzip_FlushIt(pThis, Z_FINISH);
+
+ int rc2 = deflateEnd(&pThis->Zlib);
+ if (RT_SUCCESS(rc) && rc2 != Z_OK)
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc);
+ }
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+ RTStrFree(pThis->pszOrgName);
+ pThis->pszOrgName = NULL;
+ RTStrFree(pThis->pszComment);
+ pThis->pszComment = NULL;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipGzip_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+ return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * Reads one segment.
+ *
+ * @returns IPRT status code.
+ * @param pThis The gzip I/O stream instance data.
+ * @param pvBuf Where to put the read bytes.
+ * @param cbToRead The number of bytes to read.
+ * @param fBlocking Whether to block or not.
+ * @param pcbRead Where to store the number of bytes actually read.
+ */
+static int rtZipGzip_ReadOneSeg(PRTZIPGZIPSTREAM pThis, void *pvBuf, size_t cbToRead, bool fBlocking, size_t *pcbRead)
+{
+ /*
+ * This simplifies life a wee bit below.
+ */
+ if (pThis->fEndOfStream)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+ /*
+ * Set up the output buffer.
+ */
+ pThis->Zlib.next_out = (Bytef *)pvBuf;
+ pThis->Zlib.avail_out = (uInt)cbToRead;
+ AssertReturn(pThis->Zlib.avail_out == cbToRead, VERR_OUT_OF_RANGE);
+
+ /*
+ * Be greedy reading input, even if no output buffer is left. It's possible
+ * that it's just the end of stream marker which needs to be read. Happens
+ * for incompressible blocks just larger than the input buffer size.
+ */
+ int rc = VINF_SUCCESS;
+ while ( pThis->Zlib.avail_out > 0
+ || pThis->Zlib.avail_in == 0 /* greedy */)
+ {
+ /*
+ * Read more input?
+ *
+ * N.B. The assertions here validate the RTVfsIoStrmSgRead behavior
+ * since the API is new and untested. They could be removed later
+ * but, better leaving them in.
+ */
+ if (pThis->Zlib.avail_in == 0)
+ {
+ size_t cbReadIn = ~(size_t)0;
+ rc = RTVfsIoStrmSgRead(pThis->hVfsIos, -1 /*off*/, &pThis->SgBuf, fBlocking, &cbReadIn);
+ if (rc != VINF_SUCCESS)
+ {
+ AssertMsg(RT_FAILURE(rc) || rc == VINF_TRY_AGAIN || rc == VINF_EOF, ("%Rrc\n", rc));
+ if (rc == VERR_INTERRUPTED)
+ {
+ Assert(cbReadIn == 0);
+ continue;
+ }
+ if (RT_FAILURE(rc) || rc == VINF_TRY_AGAIN || cbReadIn == 0)
+ {
+ Assert(cbReadIn == 0);
+ break;
+ }
+ AssertMsg(rc == VINF_EOF, ("%Rrc\n", rc));
+ }
+ AssertMsgBreakStmt(cbReadIn > 0 && cbReadIn <= sizeof(pThis->abBuffer), ("%zu %Rrc\n", cbReadIn, rc),
+ rc = VERR_INTERNAL_ERROR_4);
+
+ pThis->Zlib.avail_in = (uInt)cbReadIn;
+ pThis->Zlib.next_in = &pThis->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ rc = inflate(&pThis->Zlib, Z_NO_FLUSH);
+ if (rc != Z_OK && rc != Z_BUF_ERROR)
+ {
+ if (rc == Z_STREAM_END)
+ {
+ pThis->fEndOfStream = true;
+ if (pThis->Zlib.avail_out == 0)
+ rc = VINF_SUCCESS;
+ else
+ rc = pcbRead ? VINF_EOF : VERR_EOF;
+ }
+ else
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc);
+ break;
+ }
+ rc = VINF_SUCCESS;
+ }
+
+ /*
+ * Update the read counters before returning.
+ */
+ size_t const cbRead = cbToRead - pThis->Zlib.avail_out;
+ pThis->offStream += cbRead;
+ if (pcbRead)
+ *pcbRead = cbRead;
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipGzip_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+
+ Assert(pSgBuf->cSegs == 1);
+ if (!pThis->fDecompress)
+ return VERR_ACCESS_DENIED;
+ AssertReturn(off == -1 || off == pThis->offStream , VERR_INVALID_PARAMETER);
+
+ return rtZipGzip_ReadOneSeg(pThis, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg, fBlocking, pcbRead);
+}
+
+
+/**
+ * Internal helper for rtZipGzip_Write, rtZipGzip_Flush and rtZipGzip_Close.
+ *
+ * @returns IPRT status code.
+ * @retval VINF_SUCCESS
+ * @retval VINF_TRY_AGAIN - the only informational status.
+ * @retval VERR_INTERRUPTED - call again.
+ *
+ * @param pThis The gzip I/O stream instance data.
+ * @param fBlocking Whether to block or not.
+ */
+static int rtZipGzip_WriteOutputBuffer(PRTZIPGZIPSTREAM pThis, bool fBlocking)
+{
+ /*
+ * Anything to write? No, then just return immediately.
+ */
+ size_t cbToWrite = sizeof(pThis->abBuffer) - pThis->Zlib.avail_out;
+ if (cbToWrite == 0)
+ {
+ Assert(pThis->Zlib.next_out == &pThis->abBuffer[0]);
+ return VINF_SUCCESS;
+ }
+ Assert(cbToWrite <= sizeof(pThis->abBuffer));
+
+ /*
+ * Loop write on VERR_INTERRUPTED.
+ *
+ * Note! Asserting a bit extra here to make sure the
+ * RTVfsIoStrmSgWrite works correctly.
+ */
+ int rc;
+ size_t cbWrittenOut;
+ for (;;)
+ {
+ /* Set up the buffer. */
+ pThis->SgSeg.cbSeg = cbToWrite;
+ Assert(pThis->SgSeg.pvSeg == &pThis->abBuffer[0]);
+ RTSgBufReset(&pThis->SgBuf);
+
+ cbWrittenOut = ~(size_t)0;
+ rc = RTVfsIoStrmSgWrite(pThis->hVfsIos, -1 /*off*/, &pThis->SgBuf, fBlocking, &cbWrittenOut);
+ if (rc != VINF_SUCCESS)
+ {
+ AssertMsg(RT_FAILURE(rc) || rc == VINF_TRY_AGAIN, ("%Rrc\n", rc));
+ if (rc == VERR_INTERRUPTED)
+ {
+ Assert(cbWrittenOut == 0);
+ continue;
+ }
+ if (RT_FAILURE(rc) || rc == VINF_TRY_AGAIN || cbWrittenOut == 0)
+ {
+ AssertReturn(cbWrittenOut == 0, VERR_INTERNAL_ERROR_3);
+ AssertReturn(rc != VINF_SUCCESS, VERR_IPE_UNEXPECTED_INFO_STATUS);
+ return rc;
+ }
+ }
+ break;
+ }
+ AssertMsgReturn(cbWrittenOut > 0 && cbWrittenOut <= sizeof(pThis->abBuffer),
+ ("%zu %Rrc\n", cbWrittenOut, rc),
+ VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Adjust the Zlib output buffer members.
+ */
+ if (cbWrittenOut == pThis->SgBuf.paSegs[0].cbSeg)
+ {
+ pThis->Zlib.avail_out = sizeof(pThis->abBuffer);
+ pThis->Zlib.next_out = &pThis->abBuffer[0];
+ }
+ else
+ {
+ Assert(cbWrittenOut <= pThis->SgBuf.paSegs[0].cbSeg);
+ size_t cbLeft = pThis->SgBuf.paSegs[0].cbSeg - cbWrittenOut;
+ memmove(&pThis->abBuffer[0], &pThis->abBuffer[cbWrittenOut], cbLeft);
+ pThis->Zlib.avail_out += (uInt)cbWrittenOut;
+ pThis->Zlib.next_out = &pThis->abBuffer[cbWrittenOut];
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Processes all available input.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pThis The gzip I/O stream instance data.
+ * @param fBlocking Whether to block or not.
+ */
+static int rtZipGzip_CompressIt(PRTZIPGZIPSTREAM pThis, bool fBlocking)
+{
+ /*
+ * Processes all the intput currently lined up for us.
+ */
+ while (pThis->Zlib.avail_in > 0)
+ {
+ /* Make sure there is some space in the output buffer before calling
+ deflate() so we don't waste time filling up the corners. */
+ static const size_t s_cbFlushThreshold = 4096;
+ AssertCompile(sizeof(pThis->abBuffer) >= s_cbFlushThreshold * 4);
+ if (pThis->Zlib.avail_out < s_cbFlushThreshold)
+ {
+ int rc = rtZipGzip_WriteOutputBuffer(pThis, fBlocking);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ Assert(pThis->Zlib.avail_out >= s_cbFlushThreshold);
+ }
+
+ int rcZlib = deflate(&pThis->Zlib, Z_NO_FLUSH);
+ if (rcZlib != Z_OK)
+ return rtZipGzipConvertErrFromZlib(pThis, rcZlib);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipGzip_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+
+ Assert(pSgBuf->cSegs == 1); NOREF(fBlocking);
+ if (pThis->fDecompress)
+ return VERR_ACCESS_DENIED;
+ AssertReturn(off == -1 || off == pThis->offStream , VERR_INVALID_PARAMETER);
+
+ /*
+ * Write out the input buffer. Using a loop here because of potential
+ * integer type overflow since avail_in is uInt and cbSeg is size_t.
+ */
+ int rc = VINF_SUCCESS;
+ size_t cbWritten = 0;
+ uint8_t const *pbSrc = (uint8_t const *)pSgBuf->paSegs[0].pvSeg;
+ size_t cbLeft = pSgBuf->paSegs[0].cbSeg;
+ if (cbLeft > 0)
+ for (;;)
+ {
+ size_t cbThis = cbLeft < ~(uInt)0 ? cbLeft : ~(uInt)0 / 2;
+ pThis->Zlib.next_in = (Bytef * )pbSrc;
+ pThis->Zlib.avail_in = (uInt)cbThis;
+ rc = rtZipGzip_CompressIt(pThis, fBlocking);
+
+ Assert(cbThis >= pThis->Zlib.avail_in);
+ cbThis -= pThis->Zlib.avail_in;
+ cbWritten += cbThis;
+ if (cbLeft == cbThis || rc != VINF_SUCCESS)
+ break;
+ pbSrc += cbThis;
+ cbLeft -= cbThis;
+ }
+
+ pThis->offStream += cbWritten;
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ return rc;
+}
+
+
+/**
+ * Processes all available input.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pThis The gzip I/O stream instance data.
+ * @param fFlushType The flush type to pass to deflate().
+ */
+static int rtZipGzip_FlushIt(PRTZIPGZIPSTREAM pThis, uint8_t fFlushType)
+{
+ /*
+ * Tell Zlib to flush until it stops producing more output.
+ */
+ int rc;
+ bool fMaybeMore = true;
+ for (;;)
+ {
+ /* Write the entire output buffer. */
+ do
+ {
+ rc = rtZipGzip_WriteOutputBuffer(pThis, true /*fBlocking*/);
+ if (RT_FAILURE(rc))
+ return rc;
+ Assert(rc == VINF_SUCCESS);
+ } while (pThis->Zlib.avail_out < sizeof(pThis->abBuffer));
+
+ if (!fMaybeMore)
+ return VINF_SUCCESS;
+
+ /* Do the flushing. */
+ pThis->Zlib.next_in = NULL;
+ pThis->Zlib.avail_in = 0;
+ int rcZlib = deflate(&pThis->Zlib, fFlushType);
+ if (rcZlib == Z_OK)
+ fMaybeMore = pThis->Zlib.avail_out < 64 || fFlushType == Z_FINISH;
+ else if (rcZlib == Z_STREAM_END)
+ fMaybeMore = false;
+ else
+ {
+ rtZipGzip_WriteOutputBuffer(pThis, true /*fBlocking*/);
+ return rtZipGzipConvertErrFromZlib(pThis, rcZlib);
+ }
+ }
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtZipGzip_Flush(void *pvThis)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+ if (!pThis->fDecompress)
+ {
+ int rc = rtZipGzip_FlushIt(pThis, Z_SYNC_FLUSH);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return RTVfsIoStrmFlush(pThis->hVfsIos);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipGzip_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+
+ /*
+ * Collect our own events first and see if that satisfies the request. If
+ * not forward the call to the compressed stream.
+ */
+ uint32_t fRetEvents = 0;
+ if (pThis->fFatalError)
+ fRetEvents |= RTPOLL_EVT_ERROR;
+ if (pThis->fDecompress)
+ {
+ fEvents &= ~RTPOLL_EVT_WRITE;
+ if (pThis->Zlib.avail_in > 0)
+ fRetEvents = RTPOLL_EVT_READ;
+ }
+ else
+ {
+ fEvents &= ~RTPOLL_EVT_READ;
+ if (pThis->Zlib.avail_out > 0)
+ fRetEvents = RTPOLL_EVT_WRITE;
+ }
+
+ int rc = VINF_SUCCESS;
+ fRetEvents &= fEvents;
+ if (!fRetEvents)
+ rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtZipGzip_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPGZIPSTREAM pThis = (PRTZIPGZIPSTREAM)pvThis;
+ *poffActual = pThis->offStream;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The GZIP I/O stream vtable.
+ */
+static RTVFSIOSTREAMOPS g_rtZipGzipOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "gzip",
+ rtZipGzip_Close,
+ rtZipGzip_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipGzip_Read,
+ rtZipGzip_Write,
+ rtZipGzip_Flush,
+ rtZipGzip_PollOne,
+ rtZipGzip_Tell,
+ NULL /* Skip */,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+};
+
+
+RTDECL(int) RTZipGzipDecompressIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSIOSTREAM phVfsIosOut)
+{
+ AssertPtrReturn(hVfsIosIn, VERR_INVALID_HANDLE);
+ AssertReturn(!(fFlags & ~RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR), VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phVfsIosOut, VERR_INVALID_POINTER);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create the decompression I/O stream.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPGZIPSTREAM pThis;
+ int rc = RTVfsNewIoStream(&g_rtZipGzipOps, sizeof(RTZIPGZIPSTREAM), RTFILE_O_READ, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsIos, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIosIn;
+ pThis->offStream = 0;
+ pThis->fDecompress = true;
+ pThis->SgSeg.pvSeg = &pThis->abBuffer[0];
+ pThis->SgSeg.cbSeg = sizeof(pThis->abBuffer);
+ RTSgBufInit(&pThis->SgBuf, &pThis->SgSeg, 1);
+
+ memset(&pThis->Zlib, 0, sizeof(pThis->Zlib));
+ pThis->Zlib.opaque = pThis;
+ rc = inflateInit2(&pThis->Zlib, MAX_WBITS | RT_BIT(5) /* autodetect gzip header */);
+ if (rc >= 0)
+ {
+ /*
+ * Read the gzip header from the input stream to check that it's
+ * a gzip stream as specified by the user.
+ *
+ * Note! Since we've told zlib to check for the gzip header, we
+ * prebuffer what we read in the input buffer so it can
+ * be handed on to zlib later on.
+ */
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, pThis->abBuffer, sizeof(RTZIPGZIPHDR), true /*fBlocking*/, NULL /*pcbRead*/);
+ if (RT_SUCCESS(rc))
+ {
+ /* Validate the header and make a copy of it. */
+ PCRTZIPGZIPHDR pHdr = (PCRTZIPGZIPHDR)pThis->abBuffer;
+ if ( pHdr->bId1 == RTZIPGZIPHDR_ID1
+ && pHdr->bId2 == RTZIPGZIPHDR_ID2
+ && !(pHdr->fFlags & ~RTZIPGZIPHDR_FLG_VALID_MASK))
+ {
+ if (pHdr->bCompressionMethod == RTZIPGZIPHDR_CM_DEFLATE)
+ rc = VINF_SUCCESS;
+ else
+ rc = VERR_ZIP_UNSUPPORTED_METHOD;
+ }
+ else if ( (fFlags & RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR)
+ && (RT_MAKE_U16(pHdr->bId2, pHdr->bId1) % 31) == 0
+ && (pHdr->bId1 & 0xf) == RTZIPGZIPHDR_CM_DEFLATE )
+ {
+ pHdr = NULL;
+ rc = VINF_SUCCESS;
+ }
+ else
+ rc = VERR_ZIP_BAD_HEADER;
+ if (RT_SUCCESS(rc))
+ {
+ pThis->Zlib.avail_in = sizeof(RTZIPGZIPHDR);
+ pThis->Zlib.next_in = &pThis->abBuffer[0];
+ if (pHdr)
+ {
+ pThis->Hdr = *pHdr;
+ /* Parse on if there are names or comments. */
+ if (pHdr->fFlags & (RTZIPGZIPHDR_FLG_NAME | RTZIPGZIPHDR_FLG_COMMENT))
+ {
+ /** @todo Can implement this when someone needs the
+ * name or comment for something useful. */
+ }
+ }
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsIosOut = hVfsIos;
+ return VINF_SUCCESS;
+ }
+ }
+ }
+ }
+ else
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc); /** @todo cleaning up in this situation is going to go wrong. */
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else
+ RTVfsIoStrmRelease(hVfsIosIn);
+ return rc;
+}
+
+
+RTDECL(int) RTZipGzipCompressIoStream(RTVFSIOSTREAM hVfsIosDst, uint32_t fFlags, uint8_t uLevel, PRTVFSIOSTREAM phVfsIosZip)
+{
+ AssertPtrReturn(hVfsIosDst, VERR_INVALID_HANDLE);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(phVfsIosZip, VERR_INVALID_POINTER);
+ AssertReturn(uLevel > 0 && uLevel <= 9, VERR_INVALID_PARAMETER);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosDst);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Create the compression I/O stream.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPGZIPSTREAM pThis;
+ int rc = RTVfsNewIoStream(&g_rtZipGzipOps, sizeof(RTZIPGZIPSTREAM), RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsIos, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIosDst;
+ pThis->offStream = 0;
+ pThis->fDecompress = false;
+ pThis->SgSeg.pvSeg = &pThis->abBuffer[0];
+ pThis->SgSeg.cbSeg = sizeof(pThis->abBuffer);
+ RTSgBufInit(&pThis->SgBuf, &pThis->SgSeg, 1);
+
+ RT_ZERO(pThis->Zlib);
+ pThis->Zlib.opaque = pThis;
+ pThis->Zlib.next_out = &pThis->abBuffer[0];
+ pThis->Zlib.avail_out = sizeof(pThis->abBuffer);
+
+ rc = deflateInit2(&pThis->Zlib,
+ uLevel,
+ Z_DEFLATED,
+ 15 /* Windows Size */ + 16 /* GZIP header */,
+ 9 /* Max memory level for optimal speed */,
+ Z_DEFAULT_STRATEGY);
+
+ if (rc >= 0)
+ {
+ *phVfsIosZip = hVfsIos;
+ return VINF_SUCCESS;
+ }
+
+ rc = rtZipGzipConvertErrFromZlib(pThis, rc); /** @todo cleaning up in this situation is going to go wrong. */
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else
+ RTVfsIoStrmRelease(hVfsIosDst);
+ return rc;
+}
+
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainGunzip_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, poffError, pErrInfo);
+
+ if (pElement->enmType != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_ONLY_IOS;
+ if (pElement->enmTypeIn == RTVFSOBJTYPE_INVALID)
+ return VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT;
+ if ( pElement->enmTypeIn != RTVFSOBJTYPE_FILE
+ && pElement->enmTypeIn != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_TAKES_FILE_OR_IOS;
+ if (pSpec->fOpenFile & RTFILE_O_WRITE)
+ return VERR_VFS_CHAIN_READ_ONLY_IOS;
+ if (pElement->cArgs != 0)
+ return VERR_VFS_CHAIN_NO_ARGS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainGunzip_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ RTVFSIOSTREAM hVfsIosIn = RTVfsObjToIoStream(hPrevVfsObj);
+ if (hVfsIosIn == NIL_RTVFSIOSTREAM)
+ return VERR_VFS_CHAIN_CAST_FAILED;
+
+ RTVFSIOSTREAM hVfsIos = NIL_RTVFSIOSTREAM;
+ int rc = RTZipGzipDecompressIoStream(hVfsIosIn, 0 /*fFlags*/, &hVfsIos);
+ RTVfsObjFromIoStream(hVfsIosIn);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainGunzip_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement);
+ return false;
+}
+
+
+/** VFS chain element 'gunzip'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainGunzipReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "gunzip",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Takes an I/O stream and gunzips it. No arguments.",
+ /* pfnValidate = */ rtVfsChainGunzip_Validate,
+ /* pfnInstantiate = */ rtVfsChainGunzip_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainGunzip_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainGunzipReg, rtVfsChainGunzipReg);
+
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
+ */
+static DECLCALLBACK(int) rtVfsChainGzip_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
+ PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg);
+
+ /*
+ * Basics.
+ */
+ if (pElement->enmType != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_ONLY_IOS;
+ if (pElement->enmTypeIn == RTVFSOBJTYPE_INVALID)
+ return VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT;
+ if ( pElement->enmTypeIn != RTVFSOBJTYPE_FILE
+ && pElement->enmTypeIn != RTVFSOBJTYPE_IO_STREAM)
+ return VERR_VFS_CHAIN_TAKES_FILE_OR_IOS;
+ if (pSpec->fOpenFile & RTFILE_O_READ)
+ return VERR_VFS_CHAIN_WRITE_ONLY_IOS;
+ if (pElement->cArgs > 1)
+ return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
+
+ /*
+ * Optional argument 1..9 indicating the compression level.
+ * We store it in pSpec->uProvider.
+ */
+ if (pElement->cArgs > 0)
+ {
+ const char *psz = pElement->paArgs[0].psz;
+ if (!*psz || !strcmp(psz, "default"))
+ pElement->uProvider = 6;
+ else if (!strcmp(psz, "fast"))
+ pElement->uProvider = 3;
+ else if ( RT_C_IS_DIGIT(*psz)
+ && *psz != '0'
+ && *RTStrStripL(psz + 1) == '\0')
+ pElement->uProvider = *psz - '0';
+ else
+ {
+ *poffError = pElement->paArgs[0].offSpec;
+ return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected compression level: 1-9, default, or fast");
+ }
+ }
+ else
+ pElement->uProvider = 6;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
+ */
+static DECLCALLBACK(int) rtVfsChainGzip_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
+ PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
+ PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, poffError, pErrInfo);
+ AssertReturn(hPrevVfsObj != NIL_RTVFSOBJ, VERR_VFS_CHAIN_IPE);
+
+ RTVFSIOSTREAM hVfsIosOut = RTVfsObjToIoStream(hPrevVfsObj);
+ if (hVfsIosOut == NIL_RTVFSIOSTREAM)
+ return VERR_VFS_CHAIN_CAST_FAILED;
+
+ RTVFSIOSTREAM hVfsIos = NIL_RTVFSIOSTREAM;
+ int rc = RTZipGzipCompressIoStream(hVfsIosOut, 0 /*fFlags*/, pElement->uProvider, &hVfsIos);
+ RTVfsObjFromIoStream(hVfsIosOut);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (*phVfsObj != NIL_RTVFSOBJ)
+ return VINF_SUCCESS;
+ rc = VERR_VFS_CHAIN_CAST_FAILED;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
+ */
+static DECLCALLBACK(bool) rtVfsChainGzip_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
+ PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
+ PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
+{
+ RT_NOREF(pProviderReg, pSpec, pElement, pReuseSpec, pReuseElement);
+ return false;
+}
+
+
+/** VFS chain element 'gzip'. */
+static RTVFSCHAINELEMENTREG g_rtVfsChainGzipReg =
+{
+ /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
+ /* fReserved = */ 0,
+ /* pszName = */ "gzip",
+ /* ListEntry = */ { NULL, NULL },
+ /* pszHelp = */ "Takes an I/O stream and gzips it.\n"
+ "Optional argument specifying compression level: 1-9, default, fast",
+ /* pfnValidate = */ rtVfsChainGzip_Validate,
+ /* pfnInstantiate = */ rtVfsChainGzip_Instantiate,
+ /* pfnCanReuseElement = */ rtVfsChainGzip_CanReuseElement,
+ /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
+};
+
+RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainGzipReg, rtVfsChainGzipReg);
+
diff --git a/src/VBox/Runtime/common/zip/pkzip.cpp b/src/VBox/Runtime/common/zip/pkzip.cpp
new file mode 100644
index 00000000..fbee651b
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/pkzip.cpp
@@ -0,0 +1,259 @@
+/* $Id: pkzip.cpp $ */
+/** @file
+ * IPRT - PKZIP archive I/O.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/fs.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Memory stream private data.
+ */
+typedef struct MEMIOSTREAM
+{
+ /** Size of the memory buffer. */
+ size_t cbBuf;
+ /** Pointer to the memory buffer. */
+ uint8_t *pu8Buf;
+ /** Current offset. */
+ size_t off;
+} MEMIOSTREAM;
+typedef MEMIOSTREAM *PMEMIOSTREAM;
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) memFssIos_Close(void *pvThis)
+{
+ NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) memFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PMEMIOSTREAM pThis = (PMEMIOSTREAM)pvThis;
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ RT_ZERO(*pObjInfo);
+ pObjInfo->cbObject = pThis->cbBuf;
+ break;
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) memFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PMEMIOSTREAM pThis = (PMEMIOSTREAM)pvThis;
+ Assert(pSgBuf->cSegs == 1);
+ RT_NOREF_PV(fBlocking);
+
+ if (off < 0)
+ off = pThis->off;
+ if (off >= (RTFOFF)pThis->cbBuf)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+ size_t cbLeft = pThis->cbBuf - off;
+ size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
+ if (cbToRead > cbLeft)
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ cbToRead = (size_t)cbLeft;
+ }
+
+ memcpy(pSgBuf->paSegs[0].pvSeg, pThis->pu8Buf + off, cbToRead);
+ pThis->off = off + cbToRead;
+ if (pcbRead)
+ *pcbRead = cbToRead;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) memFssIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ RT_NOREF_PV(pvThis); RT_NOREF_PV(off); RT_NOREF_PV(pSgBuf); RT_NOREF_PV(fBlocking); RT_NOREF_PV(pcbWritten);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) memFssIos_Flush(void *pvThis)
+{
+ RT_NOREF_PV(pvThis);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) memFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents)
+{
+ RT_NOREF_PV(pvThis); RT_NOREF_PV(fEvents); RT_NOREF_PV(cMillies); RT_NOREF_PV(fIntr); RT_NOREF_PV(pfRetEvents);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) memFssIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PMEMIOSTREAM pThis = (PMEMIOSTREAM)pvThis;
+ *poffActual = pThis->off;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Memory I/O object stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_memFssIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "MemFsStream::IoStream",
+ memFssIos_Close,
+ memFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ memFssIos_Read,
+ memFssIos_Write,
+ memFssIos_Flush,
+ memFssIos_PollOne,
+ memFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+RTDECL(int) RTZipPkzipMemDecompress(void **ppvDst, size_t *pcbDst, const void *pvSrc, size_t cbSrc, const char *pszObject)
+{
+ PMEMIOSTREAM pIosData;
+ RTVFSIOSTREAM hVfsIos;
+ int rc = RTVfsNewIoStream(&g_memFssIosOps,
+ sizeof(*pIosData),
+ RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIos,
+ (void **)&pIosData);
+ if (RT_SUCCESS(rc))
+ {
+ pIosData->pu8Buf = (uint8_t*)pvSrc;
+ pIosData->cbBuf = cbSrc;
+ pIosData->off = 0;
+ RTVFSFSSTREAM hVfsFss;
+ rc = RTZipPkzipFsStreamFromIoStream(hVfsIos, 0 /*fFlags*/, &hVfsFss);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Loop through all objects. Actually this wouldn't be required
+ * for .zip files but we opened it as I/O stream.
+ */
+ for (bool fFound = false; !fFound;)
+ {
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ rc = RTVfsFsStrmNext(hVfsFss, &pszName, NULL /*penmType*/, &hVfsObj);
+ if (RT_FAILURE(rc))
+ break;
+ fFound = !strcmp(pszName, pszObject);
+ if (fFound)
+ {
+ RTFSOBJINFO UnixInfo;
+ rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cb = UnixInfo.cbObject;
+ void *pv = RTMemAlloc(cb);
+ if (pv)
+ {
+ RTVFSIOSTREAM hVfsIosObj = RTVfsObjToIoStream(hVfsObj);
+ if (hVfsIos != NIL_RTVFSIOSTREAM)
+ {
+ rc = RTVfsIoStrmRead(hVfsIosObj, pv, cb, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ *ppvDst = pv;
+ *pcbDst = cb;
+ }
+ }
+ else
+ rc = VERR_INTERNAL_ERROR_4;
+ if (RT_FAILURE(rc))
+ RTMemFree(pv);
+ }
+ }
+ }
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ }
+ RTVfsFsStrmRelease(hVfsFss);
+ }
+ }
+ return rc;
+}
diff --git a/src/VBox/Runtime/common/zip/pkzipvfs.cpp b/src/VBox/Runtime/common/zip/pkzipvfs.cpp
new file mode 100644
index 00000000..5831687a
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/pkzipvfs.cpp
@@ -0,0 +1,1296 @@
+/* $Id: pkzipvfs.cpp $ */
+/** @file
+ * IPRT - PKZIP Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/stream.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/* See http://www.pkware.com/documents/casestudies/APPNOTE.TXT */
+
+/**
+ * PKZip Local File Header.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIPLOCALFILEHDR
+{
+ /** Magic value, see RTZIPPKZIPLOCALFILEHDR_MAGIC. */
+ uint32_t u32Magic;
+ /** Minimum version needed to extract. */
+ uint16_t u16Version;
+ /** General purpose bit flag. */
+ uint16_t fFlags;
+ /** Compression method. See RTZIPPKZIP_COMP_METHOD_XXX. */
+ uint16_t u16ComprMethod;
+ /** Last modified time, MS-DOS format: HHHHHMMM MMMSSSSS, multiply seconds by 2 */
+ uint16_t u16LastModifiedTime;
+ /** Last modified date, MS-DOS format: YYYYYYYM MMMDDDDD, year starts at 1980 */
+ uint16_t u16LastModifiedDate;
+ /** Checksum. */
+ uint32_t u32Crc;
+ /** Compressed size. */
+ uint32_t cbCompressed;
+ /** Uncompressed size. */
+ uint32_t cbUncompressed;
+ /** Length of the file name. */
+ uint16_t cbFilename;
+ /** Length of the extra field. */
+ uint16_t cbExtra;
+ /** Start of the file name. */
+ uint8_t u8Filename;
+} RTZIPPKZIPLOCALFILEHDR;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIPLOCALFILEHDR, 30+1);
+/** Pointer to PKZip Local File Header. */
+typedef RTZIPPKZIPLOCALFILEHDR *PRTZIPPKZIPLOCALFILEHDR;
+
+#define RTZIPPKZIPLOCALFILEHDR_MAGIC RT_MAKE_U32_FROM_U8('P','K','\003','\004')
+
+/**
+ * PKZip compression method.
+ */
+typedef enum RTZIPPKZIP_COMP_METHOD
+{
+ /** No compression */
+ RTZIPPKZIP_COMP_METHOD_STORED = 0,
+ /** Shrunk */
+ RTZIPPKZIP_COMP_METHOD_SHRUNK = 1,
+ /** Reduced with compression factor 1 */
+ RTZIPPKZIP_COMP_METHOD_REDUCED1 = 2,
+ /** Reduced with compression factor 2 */
+ RTZIPPKZIP_COMP_METHOD_REDUCED2 = 3,
+ /** Reduced with compression factor 3 */
+ RTZIPPKZIP_COMP_METHOD_REDUCED3 = 4,
+ /** Reduced with compression factor 4 */
+ RTZIPPKZIP_COMP_METHOD_REDUCED4 = 5,
+ /** Imploded */
+ RTZIPPKZIP_COMP_METHOD_IMPLODED = 6,
+ /** Deflated */
+ RTZIPPKZIP_COMP_METHOD_DEFLATED = 8,
+ /** Deflated64 */
+ RTZIPPKZIP_COMP_METHOD_DEFLATED64 = 9,
+ /* Compressed using bzip2 */
+ RTZIPPKZIP_COMP_METHOD_BZIP2 = 12,
+ /** Compressed using LZMA */
+ RTZIPPKZIP_COMP_METHOD_LZMA = 14
+} RTZIPPKZIP_COMP_METHOD;
+
+/**
+ * PKZip Central Directory Header.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIPCENTRDIRHDR
+{
+ /** The magic value. See RTZIPPKZIPCENTRDIRHDR_MAGIC. */
+ uint32_t u32Magic;
+ /** The version used for creating the item. */
+ uint16_t u16VerMade;
+ /** The minimum version required for extracting the item. */
+ uint16_t u16VerRequired;
+ /** General purpose flags. */
+ uint16_t fFlags;
+ /** Compresstion method. See RTZIPPKZIP_COMP_METHOD_XXX */
+ uint16_t u16ComprMethod;
+ /** Last modified time, MS-DOS format: HHHHHMMM MMMSSSSS, multiply seconds by 2 */
+ uint16_t u16LastModifiedTime;
+ /** Last modified date, MS-DOS format: YYYYYYYM MMMDDDDD, year starts at 1980 */
+ uint16_t u16LastModifiedDate;
+ /** Checksum. */
+ uint32_t u32Crc;
+ /** Compressed size. */
+ uint32_t cbCompressed;
+ /** Uncompressed size. */
+ uint32_t cbUncompressed;
+ /** Length of the object file name. */
+ uint16_t cbFilename;
+ /** Length of the extra field. */
+ uint16_t cbExtra;
+ /** Length of the object comment. */
+ uint16_t cbComment;
+ /** The number of the disk on which this file begins. */
+ uint16_t iDiskStart;
+ /** Internal attributes. */
+ uint16_t u16IntAttrib;
+ /** External attributes. */
+ uint32_t u32ExtAttrib;
+ /** Offset from the start of the first disk on which this file appears to
+ * where the local file header should be found. */
+ uint32_t offLocalFileHeader;
+ /** Start of the file name. */
+ uint8_t u8Filename;
+} RTZIPPKZIPCENTRDIRHDR;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIPCENTRDIRHDR, 46+1);
+/** Pointer to the PKZip Central Directory Header. */
+typedef RTZIPPKZIPCENTRDIRHDR *PRTZIPPKZIPCENTRDIRHDR;
+
+#define RTZIPPKZIPCENTRDIRHDR_MAGIC RT_MAKE_U32_FROM_U8('P','K','\001','\002')
+
+/**
+ * PKZip End of Central Directory Record.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIPENDOFCENTRDIRREC
+{
+ /** The magic value. See RTZIPPKZIPENDOFCENTRDIRREC_MAGIC. */
+ uint32_t u32Magic;
+ /** Number of this disk. */
+ uint16_t iThisDisk;
+ /** Number of the disk with the start of the Central Directory. */
+ uint16_t iDiskStartCentrDirectory;
+ /** Number of Central Directory entries on this disk. */
+ uint16_t cCentrDirRecordsThisDisk;
+ /** Number of Central Directory records. */
+ uint16_t cCentrDirRecords;
+ /** Size of the Central Directory in bytes. */
+ uint32_t cbCentrDir;
+ /** Offset of the Central Directory. */
+ uint32_t offCentrDir;
+ /** Size of the comment in bytes. */
+ uint16_t cbComment;
+ /** Start of the comment. */
+ uint8_t u8Comment;
+} RTZIPPKZIPENDOFCENTRDIRREC;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIPENDOFCENTRDIRREC, 22+1);
+/** Pointer to the PKZip End of Central Directory Record. */
+typedef RTZIPPKZIPENDOFCENTRDIRREC const *PCRTZIPPKZIPENDOFCENTRDIRREC;
+
+#define RTZIPPKZIPENDOFCENTRDIRREC_MAGIC RT_MAKE_U32_FROM_U8('P','K','\005','\006')
+
+/**
+ * PKZip ZIP64 End of Central Directory Record.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIP64ENDOFCENTRDIRREC
+{
+ /** The magic value. See RTZIPPKZIP64ENDOFCENTRDIRREC_MAGIC. */
+ uint32_t u32Magic;
+ /** Size of Zip64 end of Central Directory Record. */
+ uint64_t cbSizeEocdr;
+ /** The version used for creating the item. */
+ uint16_t u16VerMade;
+ /** The minimum version required for extracting the item. */
+ uint16_t u16VerRequired;
+ /** Number of this disk. */
+ uint32_t iThisDisk;
+ /** Number of the disk with the start of the Central Directory. */
+ uint32_t iDiskStartCentrDirectory;
+ /** Number of Central Directory entries on this disk. */
+ uint64_t cCentrDirRecordsThisDisk;
+ /** Number of Central Directory records. */
+ uint64_t cCentrDirRecords;
+ /** Size of the Central Directory in bytes. */
+ uint64_t cbCentrDir;
+ /** Offset of the Central Directory. */
+ uint64_t offCentrDir;
+} RTZIPPKZIP64ENDOFCENTRDIRREC;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIP64ENDOFCENTRDIRREC, 56);
+/** Pointer to the 64-bit PKZip End of Central Directory Record. */
+typedef RTZIPPKZIP64ENDOFCENTRDIRREC *PRTZIPPKZIP64ENDOFCENTRDIRREC;
+
+#define RTZIPPKZIP64ENDOFCENTRDIRREC_MAGIC RT_MAKE_U32_FROM_U8('P','K','\006','\006')
+
+/**
+ * PKZip ZIP64 End of Central Directory Locator.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIP64ENDOFCENTRDIRLOC
+{
+ /** The magic value. See RTZIPPKZIP64ENDOFCENTRDIRLOC_MAGIC. */
+ uint32_t u32Magic;
+ /** Number of the disk with the start of the ZIP64 End of Central Directory. */
+ uint32_t iDiskStartCentrDir;
+ /** Relative offset of the ZIP64 End of Central Directory Record. */
+ uint64_t offEndOfCentrDirRec;
+ /** Total number of disks. */
+ uint32_t cDisks;
+} RTZIPPKZIP64ENDOFCENTRDIRLOC;
+#pragma pack()
+AssertCompileSize(RTZIPPKZIP64ENDOFCENTRDIRLOC, 20);
+
+#define RTZIPPKZIP64ENDOFCENTRDIRLOC_MAGIC RT_MAKE_U32_FROM_U8('P','K','\006','\007')
+
+/**
+ * PKZip ZIP64 Extended Information Extra Field.
+ */
+#pragma pack(1)
+typedef struct RTZIPPKZIP64EXTRAFIELD
+{
+ /** Uncompressed size. */
+ uint64_t cbUncompressed;
+ /** Compressed size. */
+ uint64_t cbCompressed;
+ /** Offset from the start of the first disk on which this file appears to
+ * where the local file header should be found. */
+ uint64_t offLocalFileHeader;
+ /** The number of the disk on which this file begins. */
+ uint32_t iDiskStart;
+} RTZIPPKZIP64EXTRAFIELD;
+#pragma pack()
+/** Pointer to the ZIP64 Extended Information Extra Field. */
+typedef RTZIPPKZIP64EXTRAFIELD *PRTZIPPKZIP64EXTRAFIELD;
+AssertCompileSize(RTZIPPKZIP64EXTRAFIELD, 28);
+
+/**
+ * PKZip reader instance data.
+ */
+typedef struct RTZIPPKZIPREADER
+{
+ /** Set if we have the End of Central Directory record. */
+ bool fHaveEocd;
+ /** The Central Directory header. */
+ RTZIPPKZIPCENTRDIRHDR cdh;
+ /** ZIP64 extended information. */
+ RTZIPPKZIP64EXTRAFIELD cd64ex;
+ /** Set if ZIP64 End of Central Directory Locator is present (archive setting). */
+ bool fZip64Eocd;
+ /** Set if cd64ex is valid for the current file header (object setting). */
+ bool fZip64Ex;
+ /* The name of the current object. */
+ char szName[RTPATH_MAX];
+} RTZIPPKZIPREADER;
+/** Pointer to the PKZip reader instance data. */
+typedef RTZIPPKZIPREADER *PRTZIPPKZIPREADER;
+
+/**
+ * Pkzip object (directory).
+ */
+typedef struct RTZIPPKZIPBASEOBJ
+{
+ /** Pointer to the reader instance data (resides in the filesystem
+ * stream). */
+ PRTZIPPKZIPREADER pPkzipReader;
+ /** The object info with unix attributes. */
+ RTFSOBJINFO ObjInfo;
+} RTZIPPKZIPBASEOBJ;
+/** Pointer to a PKZIP filesystem stream base object. */
+typedef RTZIPPKZIPBASEOBJ *PRTZIPPKZIPBASEOBJ;
+
+/**
+ * Pkzip object (file) represented as a VFS I/O stream.
+ */
+typedef struct RTZIPPKZIPIOSTREAM
+{
+ /** The basic PKZIP object data. */
+ RTZIPPKZIPBASEOBJ BaseObj;
+ /** The number of (uncompressed) bytes in the file. */
+ uint64_t cbFile;
+ /** The current file position at uncompressed file data. */
+ uint64_t offFile;
+ /** The start position of the compressed data in the hVfsIos. */
+ uint64_t offCompStart;
+ /** The current position for decompressing bytes in the hVfsIos. */
+ uint64_t offComp;
+ /** The number of compressed bytes starting at offCompStart. */
+ uint64_t cbComp;
+ /** Set if we have to pass the type function the next time the input
+ * function is called. */
+ bool fPassZipType;
+ /** Set if we've reached the end of the file. */
+ bool fEndOfStream;
+ /** Pkzip compression method for this object. */
+ RTZIPPKZIP_COMP_METHOD enmCompMethod;
+ /** Zip compression method. */
+ RTZIPTYPE enmZipType;
+ /** The decompressor instance. */
+ PRTZIPDECOMP pZip;
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+} RTZIPPKZIPIOSTREAM;
+/** Pointer to a the private data of a PKZIP file I/O stream. */
+typedef RTZIPPKZIPIOSTREAM *PRTZIPPKZIPIOSTREAM;
+
+
+/**
+ * PKZip filesystem stream private data. The stream must be seekable!
+ */
+typedef struct RTZIPPKZIPFSSTREAM
+{
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+
+ /** The current object (referenced). */
+ RTVFSOBJ hVfsCurObj;
+ /** Pointer to the private data if hVfsCurObj is representing a file. */
+ PRTZIPPKZIPIOSTREAM pCurIosData;
+
+ /** The offset of the first Central Directory header. */
+ uint64_t offFirstCdh;
+ /** The offset of the next Central Directory header. */
+ uint64_t offNextCdh;
+
+ /** Size of the central directory. */
+ uint64_t cbCentrDir;
+ /** Current central directory entry. */
+ uint64_t iCentrDirEntry;
+ /** Number of central directory entries. */
+ uint64_t cCentrDirEntries;
+
+ /** Set if we have the End of Central Directory Record. */
+ bool fHaveEocd;
+ /** Set if we've reached the end of the stream. */
+ bool fEndOfStream;
+ /** Set if we've encountered a fatal error. */
+ int rcFatal;
+
+ /** The PKZIP reader instance data. */
+ RTZIPPKZIPREADER PkzipReader;
+} RTZIPPKZIPFSSTREAM;
+/** Pointer to a the private data of a PKZIP filesystem stream. */
+typedef RTZIPPKZIPFSSTREAM *PRTZIPPKZIPFSSTREAM;
+
+
+
+/**
+ * Decode date/time from DOS format as used in PKZip.
+ */
+static int rtZipPkzipReaderDecodeDosTime(PRTTIMESPEC pTimeSpec, uint16_t u16Time, uint16_t u16Date)
+{
+ RTTIME time;
+ RT_ZERO(time);
+ time.i32Year = ((u16Date & 0xfe00) >> 9) + 1980;
+ time.u8Month = (u16Date & 0x01e0) >> 5;
+ time.u8MonthDay = u16Date & 0x001f;
+ time.u8Hour = (u16Time & 0xf800) >> 11;
+ time.u8Minute = (u16Time & 0x07e0) >> 5;
+ time.u8Second = u16Time & 0x001f;
+ RTTimeNormalize(&time);
+ RTTimeImplode(pTimeSpec, &time);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parse the Local File Header.
+ * Just skip the data as we trust the Central Directory.
+ */
+static int rtZipPkzipParseLocalFileHeader(PRTZIPPKZIPREADER pThis, PRTZIPPKZIPLOCALFILEHDR pLfh, size_t *pcbExtra)
+{
+ RT_NOREF_PV(pThis);
+
+ if (pLfh->cbFilename >= sizeof(pThis->szName))
+ return VERR_PKZIP_NAME_TOO_LONG;
+
+ *pcbExtra = pLfh->cbFilename + pLfh->cbExtra;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parse the Central Directory Header.
+ */
+static int rtZipPkzipParseCentrDirHeader(PRTZIPPKZIPREADER pThis, PRTZIPPKZIPCENTRDIRHDR pCdh, size_t *pcbExtra)
+{
+ if (pCdh->u32Magic != RTZIPPKZIPCENTRDIRHDR_MAGIC)
+ return VERR_PKZIP_BAD_CDF_HEADER;
+
+ if (pCdh->cbFilename >= sizeof(pThis->szName))
+ return VERR_PKZIP_NAME_TOO_LONG;
+
+ *pcbExtra = pCdh->cbFilename + pCdh->cbExtra + pCdh->cbComment;
+
+ pThis->cdh = *pCdh;
+ pThis->fZip64Ex = false;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Return the offset of the Local File Header.
+ */
+static uint64_t rtZipPkzipReaderOffLocalHeader(PRTZIPPKZIPREADER pThis)
+{
+ if (pThis->fZip64Ex && pThis->cdh.offLocalFileHeader == (uint32_t)-1)
+ return pThis->cd64ex.offLocalFileHeader;
+
+ return pThis->cdh.offLocalFileHeader;
+}
+
+
+/**
+ * Return the uncompressed object size.
+ */
+static uint64_t rtZipPkzipReaderUncompressed(PRTZIPPKZIPREADER pThis)
+{
+ if (pThis->fZip64Ex && pThis->cdh.cbUncompressed == (uint32_t)-1)
+ return pThis->cd64ex.cbUncompressed;
+
+ return pThis->cdh.cbUncompressed;
+}
+
+
+/**
+ * Return the compressed object size.
+ */
+static uint64_t rtZipPkzipReaderCompressed(PRTZIPPKZIPREADER pThis)
+{
+ if (pThis->fZip64Ex && pThis->cdh.cbCompressed == (uint32_t)-1)
+ return pThis->cd64ex.cbCompressed;
+
+ return pThis->cdh.cbCompressed;
+}
+
+
+/**
+ * Parse the extra part of the Central Directory Header.
+ */
+static int rtZipPkzipParseCentrDirHeaderExtra(PRTZIPPKZIPREADER pThis, uint8_t *pu8Buf, size_t cb,
+ RTZIPPKZIP_COMP_METHOD *penmCompMethod, uint64_t *pcbCompressed)
+{
+ int rc = RTStrCopyEx(pThis->szName, sizeof(pThis->szName), (const char*)pu8Buf, pThis->cdh.cbFilename);
+ if (RT_SUCCESS(rc))
+ {
+ pu8Buf += pThis->cdh.cbFilename;
+ cb = pThis->cdh.cbExtra;
+ while (cb >= 4)
+ {
+ uint16_t idExtra = *(uint16_t*)pu8Buf;
+ pu8Buf += 2;
+ uint16_t cbExtra = *(uint16_t*)pu8Buf;
+ pu8Buf += 2;
+ cb -= 4;
+
+ if (cb >= cbExtra)
+ {
+ switch (idExtra)
+ {
+ case 0x0001:
+ /*
+ * ZIP64 Extended Information Extra Field.
+ */
+ if (!pThis->fZip64Eocd)
+ return VERR_PKZIP_ZIP64EX_IN_ZIP32;
+ /* Not all fields are really used. */
+ RT_ZERO(pThis->cd64ex);
+ memcpy(&pThis->cd64ex, pu8Buf, cbExtra);
+ pThis->fZip64Ex = true;
+ break;
+
+ default:
+ /* unknown, just skip */
+ break;
+ }
+ pu8Buf += cbExtra;
+ cb -= cbExtra;
+ }
+ else
+ {
+ rc = VERR_PKZIP_BAD_CDF_HEADER;
+ break;
+ }
+ }
+
+ *penmCompMethod = (RTZIPPKZIP_COMP_METHOD)pThis->cdh.u16ComprMethod;
+ *pcbCompressed = rtZipPkzipReaderCompressed(pThis);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Translate a PKZip header to an IPRT object info structure.
+ */
+static int rtZipPkzipReaderGetFsObjInfo(PRTZIPPKZIPREADER pThis, PRTFSOBJINFO pObjInfo)
+{
+ /*
+ * Zap the whole structure, this takes care of unused space in the union.
+ */
+ RT_ZERO(*pObjInfo);
+ pObjInfo->cbObject = rtZipPkzipReaderUncompressed(pThis);
+ pObjInfo->cbAllocated = rtZipPkzipReaderUncompressed(pThis); /* XXX */
+ RTTIMESPEC ts;
+ rtZipPkzipReaderDecodeDosTime(&ts, pThis->cdh.u16LastModifiedTime, pThis->cdh.u16LastModifiedDate);
+ pObjInfo->ChangeTime = ts;
+ pObjInfo->ModificationTime = ts;
+ pObjInfo->AccessTime = ts;
+ pObjInfo->BirthTime = ts;
+ const char *pszEnd = strchr(pThis->szName, '\0');
+ if (pszEnd == &pThis->szName[0] || pszEnd[-1] != '/')
+ pObjInfo->Attr.fMode = RTFS_TYPE_FILE \
+ | RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR \
+ | RTFS_UNIX_IRGRP \
+ | RTFS_UNIX_IROTH;
+ else
+ pObjInfo->Attr.fMode = RTFS_TYPE_DIRECTORY \
+ | RTFS_UNIX_IRWXU \
+ | RTFS_UNIX_IRGRP | RTFS_UNIX_IXGRP \
+ | RTFS_UNIX_IROTH | RTFS_UNIX_IXOTH;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Search the magic value of the End Of Central Directory Record.
+ *
+ * @returns true if found, false otherwise.
+ * @param pu8Buf buffer.
+ * @param cb size of buffer.
+ * @param piPos where to store the position of the magic value.
+ */
+static bool rtZipPkzipReaderScanEocd(const uint8_t *pu8Buf, size_t cb, int *piPos)
+{
+ if (cb < 4)
+ return false;
+ ssize_t i;
+ for (i = (ssize_t)cb - 4; i >= 0; --i)
+ if (*(uint32_t*)(pu8Buf + i) == RTZIPPKZIPENDOFCENTRDIRREC_MAGIC)
+ {
+ *piPos = i;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Read the Local File Header. We ignore the content -- we trust the Central
+ * Directory.
+ */
+static int rtZipPkzipFssIosReadLfh(PRTZIPPKZIPFSSTREAM pThis, uint64_t *poffStartData)
+{
+ RTZIPPKZIPLOCALFILEHDR lfh;
+ uint64_t offLocalFileHeader = rtZipPkzipReaderOffLocalHeader(&pThis->PkzipReader);
+ int rc = RTVfsIoStrmReadAt(pThis->hVfsIos, offLocalFileHeader,
+ &lfh, sizeof(lfh) - 1, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (lfh.u32Magic == RTZIPPKZIPLOCALFILEHDR_MAGIC)
+ {
+ size_t cbExtra = 0;
+ rc = rtZipPkzipParseLocalFileHeader(&pThis->PkzipReader, &lfh, &cbExtra);
+ if (RT_SUCCESS(rc))
+ {
+ /* Just skip the file name and and extra field. We use the data
+ * from the Central Directory Header. */
+ rc = RTVfsIoStrmSkip(pThis->hVfsIos, cbExtra);
+ if (RT_SUCCESS(rc))
+ *poffStartData = offLocalFileHeader + sizeof(lfh) - 1 + cbExtra;
+ }
+ }
+ else
+ rc = VERR_PKZIP_BAD_LF_HEADER;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Scan the current Central Directory Header.
+ */
+static int rtZipPkzipFssIosReadCdh(PRTZIPPKZIPFSSTREAM pThis, uint64_t *poffStartData,
+ RTZIPPKZIP_COMP_METHOD *penmCompMethod, uint64_t *pcbCompressed)
+{
+ int rc;
+
+ uint64_t offCd = pThis->offNextCdh - pThis->offFirstCdh;
+ if ( pThis->iCentrDirEntry < pThis->cCentrDirEntries
+ || offCd + sizeof(RTZIPPKZIPCENTRDIRHDR) - 1 <= pThis->cbCentrDir)
+ {
+ RTZIPPKZIPCENTRDIRHDR cdh;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, pThis->offNextCdh,
+ &cdh, sizeof(cdh) - 1, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->offNextCdh += sizeof(cdh) - 1;
+ pThis->iCentrDirEntry++;
+ size_t cbExtra = 0;
+ rc = rtZipPkzipParseCentrDirHeader(&pThis->PkzipReader, &cdh, &cbExtra);
+ if (RT_SUCCESS(rc))
+ {
+ if (offCd + sizeof(RTZIPPKZIPCENTRDIRHDR) - 1 + cbExtra <= pThis->cbCentrDir)
+ {
+ /* extra data up to 64k */
+ uint8_t *pu8Buf = (uint8_t*)RTMemTmpAlloc(cbExtra);
+ if (pu8Buf)
+ {
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, pu8Buf, cbExtra, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = rtZipPkzipParseCentrDirHeaderExtra(&pThis->PkzipReader, pu8Buf, cbExtra,
+ penmCompMethod, pcbCompressed);
+ if (RT_SUCCESS(rc))
+ rc = rtZipPkzipFssIosReadLfh(pThis, poffStartData);
+ }
+ pThis->offNextCdh += cbExtra;
+ RTMemTmpFree(pu8Buf);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+ else
+ rc = VERR_EOF;
+ }
+ }
+ }
+ else
+ rc = VERR_EOF;
+
+ return rc;
+}
+
+
+/**
+ * Scan for the End of Central Directory Record. Of course this works not if
+ * the stream is non-seekable (i.e. a pipe).
+ */
+static int rtZipPkzipFssIosReadEocb(PRTZIPPKZIPFSSTREAM pThis)
+{
+ RTFSOBJINFO Info;
+ int rc = RTVfsIoStrmQueryInfo(pThis->hVfsIos, &Info, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ uint64_t cbFile = Info.cbObject;
+ if (cbFile < sizeof(RTZIPPKZIPENDOFCENTRDIRREC)-1)
+ return VERR_PKZIP_NO_EOCB;
+
+ /* search for start of the 'end of Central Directory Record' */
+ size_t cbBuf = RT_MIN(_1K, cbFile);
+ uint8_t *pu8Buf = (uint8_t*)RTMemTmpAlloc(cbBuf);
+ if (!pu8Buf)
+ return VERR_NO_MEMORY;
+
+ /* maximum size of EOCD comment 2^16-1 */
+ const size_t cbHdrMax = 0xffff + sizeof(RTZIPPKZIPENDOFCENTRDIRREC) - 1;
+ uint64_t offMin = cbFile >= cbHdrMax ? cbFile - cbHdrMax : 0;
+
+ uint64_t off = cbFile - cbBuf;
+ while (off >= offMin)
+ {
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, off, pu8Buf, cbBuf, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ int offMagic;
+ if (rtZipPkzipReaderScanEocd(pu8Buf, cbBuf, &offMagic))
+ {
+ off += offMagic;
+ RTZIPPKZIPENDOFCENTRDIRREC eocd;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, off, &eocd, sizeof(eocd) - 1,
+ true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ /* well, this shouldn't fail if the content didn't change */
+ if (eocd.u32Magic == RTZIPPKZIPENDOFCENTRDIRREC_MAGIC)
+ {
+ /* sanity check */
+ if (off + RT_UOFFSETOF(RTZIPPKZIPENDOFCENTRDIRREC, u8Comment) + eocd.cbComment == cbFile)
+ {
+ pThis->offFirstCdh = eocd.offCentrDir;
+ pThis->offNextCdh = eocd.offCentrDir;
+ pThis->iCentrDirEntry = 0;
+ pThis->cCentrDirEntries = eocd.cCentrDirRecords;
+ pThis->cbCentrDir = eocd.cbCentrDir;
+ pThis->PkzipReader.fHaveEocd = true;
+ }
+ else
+ rc = VERR_PKZIP_NO_EOCB;
+ }
+ else
+ rc = VERR_PKZIP_NO_EOCB;
+ }
+ if (rc != VERR_PKZIP_NO_EOCB)
+ break;
+ }
+ else
+ rc = VERR_PKZIP_NO_EOCB;
+ /* overlap the following read */
+ off -= cbBuf - 4;
+ }
+
+ RTMemTmpFree(pu8Buf);
+
+ /*
+ * Now check for the presence of the Zip64 End of Central Directory Locator.
+ */
+ if ( RT_SUCCESS(rc)
+ && off > (unsigned)sizeof(RTZIPPKZIP64ENDOFCENTRDIRLOC))
+ {
+ off -= sizeof(RTZIPPKZIP64ENDOFCENTRDIRLOC);
+
+ RTZIPPKZIP64ENDOFCENTRDIRLOC eocd64loc;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, off, &eocd64loc, sizeof(eocd64loc), true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ if (eocd64loc.u32Magic == RTZIPPKZIP64ENDOFCENTRDIRLOC_MAGIC)
+ pThis->PkzipReader.fZip64Eocd = true;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssBaseObj_Close(void *pvThis)
+{
+ NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssBaseObj_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPPKZIPBASEOBJ pThis = (PRTZIPPKZIPBASEOBJ)pvThis;
+
+ /*
+ * Copy the desired data.
+ */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * PKZip filesystem base object operations (directory objects).
+ */
+static const RTVFSOBJOPS g_rtZipPkzipFssBaseObjOps =
+{
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_BASE,
+ "PkzipFsStream::Obj",
+ rtZipPkzipFssBaseObj_Close,
+ rtZipPkzipFssBaseObj_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIos_Close(void *pvThis)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ if (pThis->pZip)
+ {
+ RTZipDecompDestroy(pThis->pZip);
+ pThis->pZip = NULL;
+ }
+
+ return rtZipPkzipFssBaseObj_Close(&pThis->BaseObj);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+ return rtZipPkzipFssBaseObj_QueryInfo(&pThis->BaseObj, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * Callback function for rtZipPkzipFssIos_Read. For feeding compressed data
+ * into the decompressor function.
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIosReadHelper(void *pvThis, void *pvBuf, size_t cbToRead, size_t *pcbRead)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+ int rc = VINF_SUCCESS;
+ if (!cbToRead)
+ return rc;
+ if ( pThis->fPassZipType
+ && cbToRead > 0)
+ {
+ uint8_t *pu8Buf = (uint8_t*)pvBuf;
+ pu8Buf[0] = pThis->enmZipType;
+ pvBuf = &pu8Buf[1];
+ cbToRead--;
+ pThis->fPassZipType = false;
+ }
+ if (cbToRead > 0)
+ {
+ size_t cbRead = 0;
+ const size_t cbAvail = pThis->cbComp;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, pThis->offComp, pvBuf,
+ RT_MIN(cbToRead, cbAvail), true /*fBlocking*/, &cbRead);
+ if ( RT_SUCCESS(rc)
+ && cbToRead > cbAvail)
+ rc = pcbRead ? VINF_EOF : VERR_EOF;
+ if ( rc == VINF_EOF
+ && !pcbRead)
+ rc = VERR_EOF;
+ pThis->offComp += cbRead;
+ if (pcbRead)
+ *pcbRead = cbRead;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+ Assert(pSgBuf->cSegs == 1);
+ RT_NOREF_PV(fBlocking);
+
+ if (off < 0)
+ off = pThis->offFile;
+ if (off >= (RTFOFF)pThis->cbFile)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+ Assert(pThis->cbFile >= pThis->offFile);
+ uint64_t cbLeft = (uint64_t)(pThis->cbFile - pThis->offFile);
+ size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
+ if (cbToRead > cbLeft)
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ cbToRead = (size_t)cbLeft;
+ }
+
+ /*
+ * Restart decompression at start of stream or on backward seeking.
+ */
+ if ( !pThis->pZip
+ || !off
+ || off < (RTFOFF)pThis->offFile)
+ {
+ switch (pThis->enmCompMethod)
+ {
+ case RTZIPPKZIP_COMP_METHOD_STORED:
+ pThis->enmZipType = RTZIPTYPE_STORE;
+ break;
+
+ case RTZIPPKZIP_COMP_METHOD_DEFLATED:
+ pThis->enmZipType = RTZIPTYPE_ZLIB_NO_HEADER;
+ break;
+
+ default:
+ pThis->enmZipType = RTZIPTYPE_INVALID;
+ break;
+ }
+
+ if (pThis->pZip)
+ {
+ RTZipDecompDestroy(pThis->pZip);
+ pThis->pZip = NULL;
+ }
+ int rc = RTZipDecompCreate(&pThis->pZip, (void*)pThis, rtZipPkzipFssIosReadHelper);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Skip bytes if necessary.
+ */
+ if (off > (RTFOFF)pThis->offFile)
+ {
+ uint8_t u8Buf[_1K];
+ while (off > (RTFOFF)pThis->offFile)
+ {
+ size_t cbSkip = off - pThis->offFile;
+ if (cbSkip > sizeof(u8Buf))
+ cbSkip = sizeof(u8Buf);
+ int rc = RTZipDecompress(pThis->pZip, u8Buf, cbSkip, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->offFile += cbSkip;
+ }
+ }
+
+ /*
+ * Do the actual reading.
+ */
+ size_t cbReadStack = 0;
+ if (!pcbRead)
+ pcbRead = &cbReadStack;
+ int rc = RTZipDecompress(pThis->pZip, pSgBuf->paSegs[0].pvSeg, cbToRead, pcbRead);
+ pThis->offFile = off + *pcbRead;
+ if (pThis->offFile >= pThis->cbFile)
+ {
+ Assert(pThis->offFile == pThis->cbFile);
+ pThis->fEndOfStream = true;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) rtZipPkzipFssIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ RT_NOREF_PV(pvThis); RT_NOREF_PV(off); RT_NOREF_PV(pSgBuf); RT_NOREF_PV(fBlocking); RT_NOREF_PV(pcbWritten);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+static DECLCALLBACK(int) rtZipPkzipFssIos_Flush(void *pvThis)
+{
+ RT_NOREF_PV(pvThis);
+ return VERR_NOT_IMPLEMENTED;
+}
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies,
+ bool fIntr, uint32_t *pfRetEvents)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+
+ /* When we've reached the end, read will be set to indicate it. */
+ if ( (fEvents & RTPOLL_EVT_READ)
+ && pThis->fEndOfStream)
+ {
+ int rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, 0, fIntr, pfRetEvents);
+ if (RT_SUCCESS(rc))
+ *pfRetEvents |= RTPOLL_EVT_READ;
+ else
+ *pfRetEvents = RTPOLL_EVT_READ;
+ return VINF_SUCCESS;
+ }
+
+ return RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtZipPkzipFssIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPPKZIPIOSTREAM pThis = (PRTZIPPKZIPIOSTREAM)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Pkzip I/O object stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_rtZipPkzipFssIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "PkzipFsStream::IoStream",
+ rtZipPkzipFssIos_Close,
+ rtZipPkzipFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipPkzipFssIos_Read,
+ rtZipPkzipFssIos_Write,
+ rtZipPkzipFssIos_Flush,
+ rtZipPkzipFssIos_PollOne,
+ rtZipPkzipFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipPkzipFss_Close(void *pvThis)
+{
+ PRTZIPPKZIPFSSTREAM pThis = (PRTZIPPKZIPFSSTREAM)pvThis;
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipPkzipFss_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPPKZIPFSSTREAM pThis = (PRTZIPPKZIPFSSTREAM)pvThis;
+ /* Take the lazy approach here, with the sideffect of providing some info
+ that is actually kind of useful. */
+ return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnNext}
+ */
+static DECLCALLBACK(int) rtZipPkzipFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ PRTZIPPKZIPFSSTREAM pThis = (PRTZIPPKZIPFSSTREAM)pvThis;
+
+ /*
+ * Dispense with the current object.
+ */
+ if (pThis->hVfsCurObj != NIL_RTVFSOBJ)
+ {
+ if (pThis->pCurIosData)
+ {
+ pThis->pCurIosData->fEndOfStream = true;
+ pThis->pCurIosData->offFile = pThis->pCurIosData->cbFile;
+ pThis->pCurIosData = NULL;
+ }
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ }
+
+ /*
+ * Check if we've already reached the end in some way.
+ */
+ if (pThis->fEndOfStream)
+ return VERR_EOF;
+ if (pThis->rcFatal != VINF_SUCCESS)
+ return pThis->rcFatal;
+
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Read the end of Central Directory Record once.
+ */
+ if (!pThis->PkzipReader.fHaveEocd)
+ rc = rtZipPkzipFssIosReadEocb(pThis);
+ uint64_t offData = 0;
+
+ /*
+ * Parse the current Central Directory Header.
+ */
+ RTZIPPKZIP_COMP_METHOD enmCompMethod = RTZIPPKZIP_COMP_METHOD_STORED;
+ uint64_t cbCompressed = 0;
+ if (RT_SUCCESS(rc))
+ rc = rtZipPkzipFssIosReadCdh(pThis, &offData, &enmCompMethod, &cbCompressed);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ /*
+ * Fill an object info structure from the current Pkzip state.
+ */
+ RTFSOBJINFO Info;
+ rc = rtZipPkzipReaderGetFsObjInfo(&pThis->PkzipReader, &Info);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ /*
+ * Create an object of the appropriate type.
+ */
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ RTFMODE fType = Info.Attr.fMode & RTFS_TYPE_MASK;
+ switch (fType)
+ {
+ case RTFS_TYPE_FILE:
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPPKZIPIOSTREAM pIosData;
+ rc = RTVfsNewIoStream(&g_rtZipPkzipFssIosOps,
+ sizeof(*pIosData),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIos,
+ (void **)&pIosData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pIosData->BaseObj.pPkzipReader = &pThis->PkzipReader;
+ pIosData->BaseObj.ObjInfo = Info;
+ pIosData->cbFile = Info.cbObject;
+ pIosData->offFile = 0;
+ pIosData->offComp = offData;
+ pIosData->offCompStart = offData;
+ pIosData->cbComp = cbCompressed;
+ pIosData->enmCompMethod = enmCompMethod;
+ pIosData->fPassZipType = true;
+ pIosData->hVfsIos = pThis->hVfsIos;
+ RTVfsIoStrmRetain(pThis->hVfsIos);
+ pThis->pCurIosData = pIosData;
+ enmType = RTVFSOBJTYPE_IO_STREAM;
+ hVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ break;
+
+ case RTFS_TYPE_DIRECTORY:
+ PRTZIPPKZIPBASEOBJ pBaseObjData;
+ rc = RTVfsNewBaseObj(&g_rtZipPkzipFssBaseObjOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsObj,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->pPkzipReader = &pThis->PkzipReader;
+ pBaseObjData->ObjInfo = Info;
+ enmType = RTVFSOBJTYPE_BASE;
+ break;
+
+ default:
+ return pThis->rcFatal = VERR_PKZIP_UNKNOWN_TYPE_FLAG;
+ }
+ pThis->hVfsCurObj = hVfsObj;
+
+ if (ppszName)
+ {
+ rc = RTStrDupEx(ppszName, pThis->PkzipReader.szName);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ }
+
+ if (phVfsObj)
+ {
+ RTVfsObjRetain(hVfsObj);
+ *phVfsObj = hVfsObj;
+ }
+
+ if (penmType)
+ *penmType = enmType;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * PKZip filesystem stream operations.
+ */
+static const RTVFSFSSTREAMOPS rtZipPkzipFssOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "PkzipFsStream",
+ rtZipPkzipFss_Close,
+ rtZipPkzipFss_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipPkzipFss_Next,
+ NULL,
+ NULL,
+ NULL,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+RTDECL(int) RTZipPkzipFsStreamFromIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertPtrReturn(hVfsIosIn, VERR_INVALID_HANDLE);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPPKZIPFSSTREAM pThis;
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&rtZipPkzipFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_READ,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIosIn;
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+ pThis->fEndOfStream = false;
+ pThis->rcFatal = VINF_SUCCESS;
+ pThis->fHaveEocd = false;
+
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosIn);
+ return rc;
+}
diff --git a/src/VBox/Runtime/common/zip/tar.h b/src/VBox/Runtime/common/zip/tar.h
new file mode 100644
index 00000000..b247b777
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tar.h
@@ -0,0 +1,146 @@
+/* $Id: tar.h $ */
+/** @file
+ * IPRT - TAR Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_zip_tar_h
+#define IPRT_INCLUDED_SRC_common_zip_tar_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <iprt/assert.h>
+#include <iprt/formats/tar.h>
+
+/**
+ * Tar header union.
+ */
+typedef union RTZIPTARHDR
+{
+ /** Byte view. */
+ char ab[512];
+ /** The standard header. */
+ RTZIPTARHDRANCIENT Ancient;
+ /** The standard header. */
+ RTZIPTARHDRPOSIX Posix;
+ /** The GNU header. */
+ RTZIPTARHDRGNU Gnu;
+ /** The bits common to both GNU and the standard header. */
+ RTZIPTARHDRCOMMON Common;
+ /** GNU sparse header. */
+ RTZIPTARHDRGNUSPARSE GnuSparse;
+} RTZIPTARHDR;
+AssertCompileSize(RTZIPTARHDR, 512);
+/** Pointer to a tar file header. */
+typedef RTZIPTARHDR *PRTZIPTARHDR;
+/** Pointer to a const tar file header. */
+typedef RTZIPTARHDR const *PCRTZIPTARHDR;
+
+
+/**
+ * Tar header type.
+ */
+typedef enum RTZIPTARTYPE
+{
+ /** Invalid type value. */
+ RTZIPTARTYPE_INVALID = 0,
+ /** Posix header. */
+ RTZIPTARTYPE_POSIX,
+ /** The old GNU header, has layout conflicting with posix. */
+ RTZIPTARTYPE_GNU,
+ /** Ancient tar header which does not use anything beyond the magic. */
+ RTZIPTARTYPE_ANCIENT,
+ /** End of the valid type values (this is not valid). */
+ RTZIPTARTYPE_END,
+ /** The usual type blow up. */
+ RTZIPTARTYPE_32BIT_HACK = 0x7fffffff
+} RTZIPTARTYPE;
+typedef RTZIPTARTYPE *PRTZIPTARTYPE;
+
+
+/**
+ * Calculates the TAR header checksums and detects if it's all zeros.
+ *
+ * @returns true if all zeros, false if not.
+ * @param pHdr The header to checksum.
+ * @param pi32Unsigned Where to store the checksum calculated using
+ * unsigned chars. This is the one POSIX specifies.
+ * @param pi32Signed Where to store the checksum calculated using
+ * signed chars.
+ *
+ * @remarks The reason why we calculate the checksum as both signed and unsigned
+ * has to do with various the char C type being signed on some hosts
+ * and unsigned on others.
+ */
+DECLINLINE(bool) rtZipTarCalcChkSum(PCRTZIPTARHDR pHdr, int32_t *pi32Unsigned, int32_t *pi32Signed)
+{
+ int32_t i32Unsigned = 0;
+ int32_t i32Signed = 0;
+
+ /*
+ * Sum up the entire header.
+ */
+ const char *pch = (const char *)pHdr;
+ const char *pchEnd = pch + sizeof(*pHdr);
+ do
+ {
+ i32Unsigned += *(unsigned char *)pch;
+ i32Signed += *(signed char *)pch;
+ } while (++pch != pchEnd);
+
+ /*
+ * Check if it's all zeros and replace the chksum field with spaces.
+ */
+ bool const fZeroHdr = i32Unsigned == 0;
+
+ pch = pHdr->Common.chksum;
+ pchEnd = pch + sizeof(pHdr->Common.chksum);
+ do
+ {
+ i32Unsigned -= *(unsigned char *)pch;
+ i32Signed -= *(signed char *)pch;
+ } while (++pch != pchEnd);
+
+ i32Unsigned += (unsigned char)' ' * sizeof(pHdr->Common.chksum);
+ i32Signed += (signed char)' ' * sizeof(pHdr->Common.chksum);
+
+ *pi32Unsigned = i32Unsigned;
+ if (pi32Signed)
+ *pi32Signed = i32Signed;
+ return fZeroHdr;
+}
+
+
+#endif /* !IPRT_INCLUDED_SRC_common_zip_tar_h */
+
diff --git a/src/VBox/Runtime/common/zip/tarcmd.cpp b/src/VBox/Runtime/common/zip/tarcmd.cpp
new file mode 100644
index 00000000..5c4f62bb
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tarcmd.cpp
@@ -0,0 +1,1960 @@
+/* $Id: tarcmd.cpp $ */
+/** @file
+ * IPRT - A mini TAR Command.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/buildconfig.h>
+#include <iprt/ctype.h>
+#include <iprt/dir.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/getopt.h>
+#include <iprt/initterm.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/param.h>
+#include <iprt/path.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/symlink.h>
+#include <iprt/vfs.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTZIPTARCMD_OPT_DELETE 1000
+#define RTZIPTARCMD_OPT_OWNER 1001
+#define RTZIPTARCMD_OPT_GROUP 1002
+#define RTZIPTARCMD_OPT_UTC 1003
+#define RTZIPTARCMD_OPT_PREFIX 1004
+#define RTZIPTARCMD_OPT_FILE_MODE_AND_MASK 1005
+#define RTZIPTARCMD_OPT_FILE_MODE_OR_MASK 1006
+#define RTZIPTARCMD_OPT_DIR_MODE_AND_MASK 1007
+#define RTZIPTARCMD_OPT_DIR_MODE_OR_MASK 1008
+#define RTZIPTARCMD_OPT_FORMAT 1009
+#define RTZIPTARCMD_OPT_READ_AHEAD 1010
+#define RTZIPTARCMD_OPT_USE_PUSH_FILE 1011
+#define RTZIPTARCMD_OPT_NO_RECURSION 1012
+
+/** File format. */
+typedef enum RTZIPTARCMDFORMAT
+{
+ RTZIPTARCMDFORMAT_INVALID = 0,
+ /** Autodetect if possible, defaulting to TAR. */
+ RTZIPTARCMDFORMAT_AUTO_DEFAULT,
+ /** TAR. */
+ RTZIPTARCMDFORMAT_TAR,
+ /** XAR. */
+ RTZIPTARCMDFORMAT_XAR,
+ /** CPIO. */
+ RTZIPTARCMDFORMAT_CPIO
+} RTZIPTARCMDFORMAT;
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * IPRT TAR option structure.
+ */
+typedef struct RTZIPTARCMDOPS
+{
+ /** The file format. */
+ RTZIPTARCMDFORMAT enmFormat;
+
+ /** The operation (Acdrtux or RTZIPTARCMD_OPT_DELETE). */
+ int iOperation;
+ /** The long operation option name. */
+ const char *pszOperation;
+
+ /** The directory to change into when packing and unpacking. */
+ const char *pszDirectory;
+ /** The tar file name. */
+ const char *pszFile;
+ /** Whether we're verbose or quiet. */
+ bool fVerbose;
+ /** Whether to preserve the original file owner when restoring. */
+ bool fPreserveOwner;
+ /** Whether to preserve the original file group when restoring. */
+ bool fPreserveGroup;
+ /** Whether to skip restoring the modification time (only time stored by the
+ * traditional TAR format). */
+ bool fNoModTime;
+ /** Whether to add a read ahead thread. */
+ bool fReadAhead;
+ /** Use RTVfsFsStrmPushFile instead of RTVfsFsStrmAdd for files. */
+ bool fUsePushFile;
+ /** Whether to handle directories recursively or not. Defaults to \c true. */
+ bool fRecursive;
+ /** The compressor/decompressor method to employ (0, z or j). */
+ char chZipper;
+
+ /** The owner to set. NULL if not applicable.
+ * Always resolved into uidOwner for extraction. */
+ const char *pszOwner;
+ /** The owner ID to set. NIL_RTUID if not applicable. */
+ RTUID uidOwner;
+ /** The group to set. NULL if not applicable.
+ * Always resolved into gidGroup for extraction. */
+ const char *pszGroup;
+ /** The group ID to set. NIL_RTGUID if not applicable. */
+ RTGID gidGroup;
+ /** Display the modification times in UTC instead of local time. */
+ bool fDisplayUtc;
+ /** File mode AND mask. */
+ RTFMODE fFileModeAndMask;
+ /** File mode OR mask. */
+ RTFMODE fFileModeOrMask;
+ /** Directory mode AND mask. */
+ RTFMODE fDirModeAndMask;
+ /** Directory mode OR mask. */
+ RTFMODE fDirModeOrMask;
+
+ /** What to prefix all names with when creating, adding, whatever. */
+ const char *pszPrefix;
+
+ /** The number of files(, directories or whatever) specified. */
+ uint32_t cFiles;
+ /** Array of files(, directories or whatever).
+ * Terminated by a NULL entry. */
+ const char * const *papszFiles;
+
+ /** The TAR format to create. */
+ RTZIPTARFORMAT enmTarFormat;
+ /** TAR creation flags. */
+ uint32_t fTarCreate;
+
+} RTZIPTARCMDOPS;
+/** Pointer to the IPRT tar options. */
+typedef RTZIPTARCMDOPS *PRTZIPTARCMDOPS;
+
+/** The size of the directory entry buffer we're using. */
+#define RTZIPTARCMD_DIRENTRY_BUF_SIZE (sizeof(RTDIRENTRYEX) + RTPATH_MAX)
+
+/**
+ * Callback used by rtZipTarDoWithMembers
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param hVfsObj The tar object to display
+ * @param pszName The name.
+ * @param rcExit The current exit code.
+ */
+typedef RTEXITCODE (*PFNDOWITHMEMBER)(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit);
+
+
+/**
+ * Checks if @a pszName is a member of @a papszNames, optionally returning the
+ * index.
+ *
+ * @returns true if the name is in the list, otherwise false.
+ * @param pszName The name to find.
+ * @param papszNames The array of names.
+ * @param piName Where to optionally return the array index.
+ */
+static bool rtZipTarCmdIsNameInArray(const char *pszName, const char * const *papszNames, uint32_t *piName)
+{
+ for (uint32_t iName = 0; papszNames[iName]; iName++)
+ if (!strcmp(papszNames[iName], pszName))
+ {
+ if (piName)
+ *piName = iName;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Queries information about a VFS object.
+ *
+ * @returns VBox status code.
+ * @param pszSpec VFS object spec to use.
+ * @param paObjInfo Where to store the queried object information.
+ * Must at least provide 3 structs, namely for UNIX, UNIX_OWNER and UNIX_GROUP attributes.
+ * @param cObjInfo Number of objection information structs handed in.
+ */
+static int rtZipTarCmdQueryObjInfo(const char *pszSpec, PRTFSOBJINFO paObjInfo, unsigned cObjInfo)
+{
+ AssertPtrReturn(paObjInfo, VERR_INVALID_POINTER);
+ AssertReturn(cObjInfo >= 3, VERR_INVALID_PARAMETER);
+
+ RTERRINFOSTATIC ErrInfo;
+ uint32_t offError;
+ int rc = RTVfsChainQueryInfo(pszSpec, &paObjInfo[0], RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsChainQueryInfo(pszSpec, &paObjInfo[1], RTFSOBJATTRADD_UNIX_OWNER, RTPATH_F_ON_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsChainQueryInfo(pszSpec, &paObjInfo[2], RTFSOBJATTRADD_UNIX_GROUP, RTPATH_F_ON_LINK,
+ &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ RT_BZERO(&paObjInfo[2], sizeof(RTFSOBJINFO));
+ }
+ else
+ {
+ RT_BZERO(&paObjInfo[1], sizeof(RTFSOBJINFO));
+ RT_BZERO(&paObjInfo[2], sizeof(RTFSOBJINFO));
+ }
+
+ rc = VINF_SUCCESS; /* aObjInfo[1] + aObjInfo[2] are optional. */
+ }
+ else
+ RTVfsChainMsgError("RTVfsChainQueryInfo", pszSpec, rc, offError, &ErrInfo.Core);
+
+ return rc;
+}
+
+
+/**
+ * Archives a file.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param hVfsFss The TAR filesystem stream handle.
+ * @param pszSrc The file path or VFS spec.
+ * @param paObjInfo[3] Array of three FS object info structures. The first
+ * one is always filled with RTFSOBJATTRADD_UNIX info.
+ * The next two may contain owner and group names if
+ * available. Buffers can be modified.
+ * @param pszDst The name to archive the file under.
+ * @param pErrInfo Error info buffer (saves stack space).
+ */
+static RTEXITCODE rtZipTarCmdArchiveFile(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss, const char *pszSrc,
+ RTFSOBJINFO paObjInfo[3], const char *pszDst, PRTERRINFOSTATIC pErrInfo)
+{
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDst);
+
+ /* Open the file. */
+ uint32_t offError;
+ RTVFSIOSTREAM hVfsIosSrc;
+ int rc = RTVfsChainOpenIoStream(pszSrc, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE,
+ &hVfsIosSrc, &offError, RTErrInfoInitStatic(pErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pszSrc, rc, offError, &pErrInfo->Core);
+
+ /* I/O stream to base object. */
+ RTVFSOBJ hVfsObjSrc = RTVfsObjFromIoStream(hVfsIosSrc);
+ if (hVfsObjSrc != NIL_RTVFSOBJ)
+ {
+ /*
+ * Add it to the stream. Got to variants here so we can test the
+ * RTVfsFsStrmPushFile API too.
+ */
+ if (!pOpts->fUsePushFile)
+ rc = RTVfsFsStrmAdd(hVfsFss, pszDst, hVfsObjSrc, 0 /*fFlags*/);
+ else
+ {
+ uint32_t cObjInfo = 1 + (paObjInfo[1].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_OWNER)
+ + (paObjInfo[2].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_GROUP);
+ RTVFSIOSTREAM hVfsIosDst;
+ rc = RTVfsFsStrmPushFile(hVfsFss, pszDst, paObjInfo[0].cbObject, paObjInfo, cObjInfo, 0 /*fFlags*/, &hVfsIosDst);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, 0);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ }
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsObjRelease(hVfsObjSrc);
+
+ if (RT_SUCCESS(rc))
+ {
+ if (rc != VINF_SUCCESS)
+ RTMsgWarning("%Rrc adding '%s'", rc, pszDst);
+ return RTEXITCODE_SUCCESS;
+ }
+ return RTMsgErrorExitFailure("%Rrc adding '%s'", rc, pszDst);
+ }
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ return RTMsgErrorExitFailure("RTVfsObjFromIoStream failed unexpectedly!");
+}
+
+
+/**
+ * Sub-directory helper for creating archives.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param hVfsFss The TAR filesystem stream handle.
+ * @param pszSrc The directory path or VFS spec. We append to the
+ * buffer as we decend.
+ * @param cchSrc The length of the input.
+ * @param paObjInfo[3] Array of three FS object info structures. The first
+ * one is always filled with RTFSOBJATTRADD_UNIX info.
+ * The next two may contain owner and group names if
+ * available. The three buffers can be reused.
+ * @param pszDst The name to archive it the under. We append to the
+ * buffer as we decend.
+ * @param cchDst The length of the input.
+ * @param pDirEntry Directory entry to use for the directory to handle.
+ * @param pErrInfo Error info buffer (saves stack space).
+ */
+static RTEXITCODE rtZipTarCmdArchiveDirSub(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss,
+ char *pszSrc, size_t cchSrc, RTFSOBJINFO paObjInfo[3],
+ char pszDst[RTPATH_MAX], size_t cchDst, PRTDIRENTRYEX pDirEntry,
+ PRTERRINFOSTATIC pErrInfo)
+{
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszDst);
+
+ uint32_t offError;
+ RTVFSDIR hVfsIoDir;
+ int rc = RTVfsChainOpenDir(pszSrc, 0 /*fFlags*/,
+ &hVfsIoDir, &offError, RTErrInfoInitStatic(pErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenDir", pszSrc, rc, offError, &pErrInfo->Core);
+
+ /* Make sure we've got some room in the path, to save us extra work further down. */
+ if (cchSrc + 3 >= RTPATH_MAX)
+ return RTMsgErrorExitFailure("Source path too long: '%s'\n", pszSrc);
+
+ /* Ensure we've got a trailing slash (there is space for it see above). */
+ if (!RTPATH_IS_SEP(pszSrc[cchSrc - 1]))
+ {
+ pszSrc[cchSrc++] = RTPATH_SLASH;
+ pszSrc[cchSrc] = '\0';
+ }
+
+ /* Ditto for destination. */
+ if (cchDst + 3 >= RTPATH_MAX)
+ return RTMsgErrorExitFailure("Destination path too long: '%s'\n", pszDst);
+
+ if (!RTPATH_IS_SEP(pszDst[cchDst - 1]))
+ {
+ pszDst[cchDst++] = RTPATH_SLASH;
+ pszDst[cchDst] = '\0';
+ }
+
+ /*
+ * Process the files and subdirs.
+ */
+ for (;;)
+ {
+ size_t cbDirEntry = RTZIPTARCMD_DIRENTRY_BUF_SIZE;
+ rc = RTVfsDirReadEx(hVfsIoDir, pDirEntry, &cbDirEntry, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ break;
+
+ /* Check length. */
+ if (pDirEntry->cbName + cchSrc + 3 >= RTPATH_MAX)
+ {
+ rc = VERR_BUFFER_OVERFLOW;
+ break;
+ }
+
+ switch (pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DIRECTORY:
+ {
+ if (RTDirEntryExIsStdDotLink(pDirEntry))
+ continue;
+
+ if (!pOpts->fRecursive)
+ continue;
+
+ memcpy(&pszSrc[cchSrc], pDirEntry->szName, pDirEntry->cbName + 1);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pszDst[cchDst], pDirEntry->szName, pDirEntry->cbName + 1);
+ rc = rtZipTarCmdArchiveDirSub(pOpts, hVfsFss, pszSrc, cchSrc + pDirEntry->cbName, paObjInfo,
+ pszDst, cchDst + pDirEntry->cbName, pDirEntry, pErrInfo);
+ }
+
+ break;
+ }
+
+ case RTFS_TYPE_FILE:
+ {
+ memcpy(&pszSrc[cchSrc], pDirEntry->szName, pDirEntry->cbName + 1);
+ rc = rtZipTarCmdQueryObjInfo(pszSrc, paObjInfo, 3 /* cObjInfo */);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(&pszDst[cchDst], pDirEntry->szName, pDirEntry->cbName + 1);
+ rc = rtZipTarCmdArchiveFile(pOpts, hVfsFss, pszSrc, paObjInfo, pszDst, pErrInfo);
+ }
+ break;
+ }
+
+ default:
+ {
+ if (pOpts->fVerbose)
+ RTPrintf("Warning: File system type %#x for '%s' not implemented yet, sorry! Skipping ...\n",
+ pDirEntry->Info.Attr.fMode & RTFS_TYPE_MASK, pDirEntry->szName);
+ break;
+ }
+ }
+ }
+
+ RTVfsDirRelease(hVfsIoDir);
+
+ if (rc != VERR_NO_MORE_FILES)
+ return RTMsgErrorExitFailure("RTVfsDirReadEx failed unexpectedly!");
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Archives a directory recursively.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param hVfsFss The TAR filesystem stream handle.
+ * @param pszSrc The directory path or VFS spec. We append to the
+ * buffer as we decend.
+ * @param cchSrc The length of the input.
+ * @param paObjInfo[3] Array of three FS object info structures. The first
+ * one is always filled with RTFSOBJATTRADD_UNIX info.
+ * The next two may contain owner and group names if
+ * available. The three buffers can be reused.
+ * @param pszDst The name to archive it the under. We append to the
+ * buffer as we decend.
+ * @param cchDst The length of the input.
+ * @param pErrInfo Error info buffer (saves stack space).
+ */
+static RTEXITCODE rtZipTarCmdArchiveDir(PRTZIPTARCMDOPS pOpts, RTVFSFSSTREAM hVfsFss, char pszSrc[RTPATH_MAX], size_t cchSrc,
+ RTFSOBJINFO paObjInfo[3], char pszDst[RTPATH_MAX], size_t cchDst,
+ PRTERRINFOSTATIC pErrInfo)
+{
+ RT_NOREF(cchSrc);
+
+ char szSrcAbs[RTPATH_MAX];
+ int rc = RTPathAbs(pszSrc, szSrcAbs, sizeof(szSrcAbs));
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTPathAbs failed on '%s': %Rrc\n", pszSrc, rc);
+
+ union
+ {
+ uint8_t abPadding[RTZIPTARCMD_DIRENTRY_BUF_SIZE];
+ RTDIRENTRYEX DirEntry;
+ } uBuf;
+
+ return rtZipTarCmdArchiveDirSub(pOpts, hVfsFss, szSrcAbs, strlen(szSrcAbs), paObjInfo, pszDst, cchDst, &uBuf.DirEntry, pErrInfo);
+}
+
+
+/**
+ * Opens the output archive specified by the options.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param phVfsFss Where to return the TAR filesystem stream handle.
+ */
+static RTEXITCODE rtZipTarCmdOpenOutputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
+{
+ int rc;
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+
+ /*
+ * Open the output file.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ if ( pOpts->pszFile
+ && strcmp(pOpts->pszFile, "-") != 0)
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
+ }
+ else
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT,
+ RTFILE_O_WRITE | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ true /*fLeaveOpen*/,
+ &hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to prepare standard output for writing: %Rrc", rc);
+ }
+
+ /*
+ * Pass it thru a compressor?
+ */
+ RTVFSIOSTREAM hVfsIosComp = NIL_RTVFSIOSTREAM;
+ switch (pOpts->chZipper)
+ {
+ /* no */
+ case '\0':
+ rc = VINF_SUCCESS;
+ break;
+
+ /* gunzip */
+ case 'z':
+ rc = RTZipGzipCompressIoStream(hVfsIos, 0 /*fFlags*/, 6, &hVfsIosComp);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
+ break;
+
+ /* bunzip2 */
+ case 'j':
+ rc = VERR_NOT_SUPPORTED;
+ RTMsgError("bzip2 is not supported by this build");
+ break;
+
+ /* bug */
+ default:
+ rc = VERR_INTERNAL_ERROR_2;
+ RTMsgError("unknown decompression method '%c'", pOpts->chZipper);
+ break;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (hVfsIosComp != NIL_RTVFSIOSTREAM)
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ hVfsIos = hVfsIosComp;
+ hVfsIosComp = NIL_RTVFSIOSTREAM;
+ }
+
+ /*
+ * Open the filesystem stream creator.
+ */
+ if ( pOpts->enmFormat == RTZIPTARCMDFORMAT_TAR
+ || pOpts->enmFormat == RTZIPTARCMDFORMAT_AUTO_DEFAULT)
+ {
+ RTVFSFSSTREAM hVfsFss;
+ rc = RTZipTarFsStreamToIoStream(hVfsIos, pOpts->enmTarFormat, pOpts->fTarCreate, &hVfsFss);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Set transformation options.
+ */
+ rc = RTZipTarFsStreamSetFileMode(hVfsFss, pOpts->fFileModeAndMask, pOpts->fFileModeOrMask);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTZipTarFsStreamSetDirMode(hVfsFss, pOpts->fDirModeAndMask, pOpts->fDirModeOrMask);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTZipTarFsStreamSetDirMode(%o,%o) failed: %Rrc", pOpts->fDirModeAndMask, pOpts->fDirModeOrMask, rc);
+ }
+ else
+ RTMsgError("RTZipTarFsStreamSetFileMode(%o,%o) failed: %Rrc", pOpts->fFileModeAndMask, pOpts->fFileModeOrMask, rc);
+ if ((pOpts->pszOwner || pOpts->uidOwner != NIL_RTUID) && RT_SUCCESS(rc))
+ {
+ rc = RTZipTarFsStreamSetOwner(hVfsFss, pOpts->uidOwner, pOpts->pszOwner);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTZipTarFsStreamSetOwner(%d,%s) failed: %Rrc", pOpts->uidOwner, pOpts->pszOwner, rc);
+ }
+ if ((pOpts->pszGroup || pOpts->gidGroup != NIL_RTGID) && RT_SUCCESS(rc))
+ {
+ rc = RTZipTarFsStreamSetGroup(hVfsFss, pOpts->gidGroup, pOpts->pszGroup);
+ if (RT_FAILURE(rc))
+ RTMsgError("RTZipTarFsStreamSetGroup(%d,%s) failed: %Rrc", pOpts->gidGroup, pOpts->pszGroup, rc);
+ }
+ if (RT_SUCCESS(rc))
+ *phVfsFss = hVfsFss;
+ else
+ {
+ RTVfsFsStrmRelease(hVfsFss);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ }
+ }
+ else
+ rc = RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
+ }
+ else
+ rc = VERR_NOT_SUPPORTED;
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Implements archive creation.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ */
+static RTEXITCODE rtZipTarCreate(PRTZIPTARCMDOPS pOpts)
+{
+ /*
+ * Refuse to create empty archive.
+ */
+ if (pOpts->cFiles == 0)
+ return RTMsgErrorExitFailure("Nothing to archive - refusing to create empty archive!");
+
+ /*
+ * First open the output file.
+ */
+ RTVFSFSSTREAM hVfsFss;
+ RTEXITCODE rcExit = rtZipTarCmdOpenOutputArchive(pOpts, &hVfsFss);
+ if (rcExit != RTEXITCODE_SUCCESS)
+ return rcExit;
+
+ /*
+ * Process the input files.
+ */
+ for (uint32_t iFile = 0; iFile < pOpts->cFiles; iFile++)
+ {
+ const char *pszFile = pOpts->papszFiles[iFile];
+
+ /*
+ * Construct/copy the source name.
+ */
+ int rc = VINF_SUCCESS;
+ char szSrc[RTPATH_MAX];
+ if ( RTPathStartsWithRoot(pszFile)
+ || RTVfsChainIsSpec(pszFile))
+ rc = RTStrCopy(szSrc, sizeof(szSrc), pszFile);
+ else
+ rc = RTPathJoin(szSrc, sizeof(szSrc), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pOpts->papszFiles[iFile]);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Construct the archived name. We must strip leading root specifier.
+ */
+ char *pszFinalPath = NULL;
+ char szDst[RTPATH_MAX];
+ const char *pszDst = pszFile;
+ if (RTVfsChainIsSpec(pszFile))
+ {
+ uint32_t offError;
+ rc = RTVfsChainQueryFinalPath(pszFile, &pszFinalPath, &offError);
+ if (RT_SUCCESS(rc))
+ pszDst = pszFinalPath;
+ else
+ rcExit = RTVfsChainMsgErrorExitFailure("RTVfsChainQueryFinalPath", pszFile, rc, offError, NULL);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pszDst = RTPathSkipRootSpec(pszDst);
+ if (*pszDst == '\0')
+ {
+ pszDst = pOpts->pszPrefix ? pOpts->pszPrefix : ".";
+ rc = RTStrCopy(szDst, sizeof(szDst), pszDst);
+ }
+ else
+ {
+ if (pOpts->pszPrefix)
+ rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszPrefix, pszDst);
+ else
+ rc = RTStrCopy(szDst, sizeof(szDst), pszDst);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * What kind of object is this and what affiliations does it have?
+ */
+ RTFSOBJINFO aObjInfo[3];
+ rc = rtZipTarCmdQueryObjInfo(szSrc, aObjInfo, RT_ELEMENTS(aObjInfo));
+ if (RT_SUCCESS(rc))
+ {
+ RTERRINFOSTATIC ErrInfo;
+
+ /*
+ * Process on an object type basis.
+ */
+ RTEXITCODE rcExit2;
+ if (RTFS_IS_DIRECTORY(aObjInfo[0].Attr.fMode))
+ rcExit2 = rtZipTarCmdArchiveDir(pOpts, hVfsFss, szSrc, strlen(szSrc), aObjInfo,
+ szDst, strlen(szDst), &ErrInfo);
+ else if (RTFS_IS_FILE(aObjInfo[0].Attr.fMode))
+ rcExit2 = rtZipTarCmdArchiveFile(pOpts, hVfsFss, szSrc, aObjInfo, szDst, &ErrInfo);
+ else if (RTFS_IS_SYMLINK(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTMsgErrorExitFailure("Symlink archiving is not implemented");
+ else if (RTFS_IS_FIFO(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTMsgErrorExitFailure("FIFO archiving is not implemented");
+ else if (RTFS_IS_SOCKET(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTMsgErrorExitFailure("Socket archiving is not implemented");
+ else if (RTFS_IS_DEV_CHAR(aObjInfo[0].Attr.fMode) || RTFS_IS_DEV_BLOCK(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTMsgErrorExitFailure("Device archiving is not implemented");
+ else if (RTFS_IS_WHITEOUT(aObjInfo[0].Attr.fMode))
+ rcExit2 = RTEXITCODE_SUCCESS;
+ else
+ rcExit2 = RTMsgErrorExitFailure("Unknown file type: %#x\n", aObjInfo[0].Attr.fMode);
+ if (rcExit2 != RTEXITCODE_SUCCESS)
+ rcExit = rcExit2;
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("querying object information for '%s' failed (%s)", szSrc, pszFile);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("archived file name is too long, skipping '%s' (%s)", pszDst, pszFile);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("input file name is too long, skipping '%s'", pszFile);
+ }
+
+ /*
+ * Finalize the archive.
+ */
+ int rc = RTVfsFsStrmEnd(hVfsFss);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("RTVfsFsStrmEnd failed: %Rrc", rc);
+
+ RTVfsFsStrmRelease(hVfsFss);
+ return rcExit;
+}
+
+
+/**
+ * Opens the input archive specified by the options.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param phVfsFss Where to return the TAR filesystem stream handle.
+ */
+static RTEXITCODE rtZipTarCmdOpenInputArchive(PRTZIPTARCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
+{
+ int rc;
+
+ /*
+ * Open the input file.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ if ( pOpts->pszFile
+ && strcmp(pOpts->pszFile, "-") != 0)
+ {
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
+ }
+ else
+ {
+ rc = RTVfsIoStrmFromStdHandle(RTHANDLESTD_INPUT,
+ RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ true /*fLeaveOpen*/,
+ &hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to prepare standard in for reading: %Rrc", rc);
+ }
+
+ /*
+ * Pass it thru a decompressor?
+ */
+ RTVFSIOSTREAM hVfsIosDecomp = NIL_RTVFSIOSTREAM;
+ switch (pOpts->chZipper)
+ {
+ /* no */
+ case '\0':
+ rc = VINF_SUCCESS;
+ break;
+
+ /* gunzip */
+ case 'z':
+ rc = RTZipGzipDecompressIoStream(hVfsIos, 0 /*fFlags*/, &hVfsIosDecomp);
+ if (RT_FAILURE(rc))
+ RTMsgError("Failed to open gzip decompressor: %Rrc", rc);
+ break;
+
+ /* bunzip2 */
+ case 'j':
+ rc = VERR_NOT_SUPPORTED;
+ RTMsgError("bzip2 is not supported by this build");
+ break;
+
+ /* bug */
+ default:
+ rc = VERR_INTERNAL_ERROR_2;
+ RTMsgError("unknown decompression method '%c'", pOpts->chZipper);
+ break;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ return RTEXITCODE_FAILURE;
+ }
+
+ if (hVfsIosDecomp != NIL_RTVFSIOSTREAM)
+ {
+ RTVfsIoStrmRelease(hVfsIos);
+ hVfsIos = hVfsIosDecomp;
+ hVfsIosDecomp = NIL_RTVFSIOSTREAM;
+ }
+
+ /*
+ * Open the filesystem stream.
+ */
+ if (pOpts->enmFormat == RTZIPTARCMDFORMAT_TAR)
+ rc = RTZipTarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
+ else if (pOpts->enmFormat == RTZIPTARCMDFORMAT_XAR)
+#ifdef IPRT_WITH_XAR /* Requires C++ and XML, so only in some configruation of IPRT. */
+ rc = RTZipXarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
+#else
+ rc = VERR_NOT_SUPPORTED;
+#endif
+ else if (pOpts->enmFormat == RTZIPTARCMDFORMAT_CPIO)
+ rc = RTZipCpioFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
+ else /** @todo make RTZipTarFsStreamFromIoStream fail if not tar file! */
+ rc = RTZipTarFsStreamFromIoStream(hVfsIos, 0/*fFlags*/, phVfsFss);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("Failed to open tar filesystem stream: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Worker for the --list and --extract commands.
+ *
+ * @returns The appropriate exit code.
+ * @param pOpts The tar options.
+ * @param pfnCallback The command specific callback.
+ */
+static RTEXITCODE rtZipTarDoWithMembers(PRTZIPTARCMDOPS pOpts, PFNDOWITHMEMBER pfnCallback)
+{
+ /*
+ * Allocate a bitmap to go with the file list. This will be used to
+ * indicate which files we've processed and which not.
+ */
+ uint32_t *pbmFound = NULL;
+ if (pOpts->cFiles)
+ {
+ pbmFound = (uint32_t *)RTMemAllocZ(((pOpts->cFiles + 31) / 32) * sizeof(uint32_t));
+ if (!pbmFound)
+ return RTMsgErrorExitFailure("Failed to allocate the found-file-bitmap");
+ }
+
+
+ /*
+ * Open the input archive.
+ */
+ RTVFSFSSTREAM hVfsFssIn;
+ RTEXITCODE rcExit = rtZipTarCmdOpenInputArchive(pOpts, &hVfsFssIn);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Process the stream.
+ */
+ for (;;)
+ {
+ /*
+ * Retrive the next object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ int rc = RTVfsFsStrmNext(hVfsFssIn, &pszName, NULL, &hVfsObj);
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_EOF)
+ rcExit = RTMsgErrorExitFailure("RTVfsFsStrmNext returned %Rrc", rc);
+ break;
+ }
+
+ /*
+ * Should we process this entry?
+ */
+ uint32_t iFile = UINT32_MAX;
+ if ( !pOpts->cFiles
+ || rtZipTarCmdIsNameInArray(pszName, pOpts->papszFiles, &iFile) )
+ {
+ if (pbmFound)
+ ASMBitSet(pbmFound, iFile);
+
+ rcExit = pfnCallback(pOpts, hVfsObj, pszName, rcExit);
+ }
+
+ /*
+ * Release the current object and string.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ }
+
+ /*
+ * Complain about any files we didn't find.
+ */
+ for (uint32_t iFile = 0; iFile < pOpts->cFiles; iFile++)
+ if (!ASMBitTest(pbmFound, iFile))
+ {
+ RTMsgError("%s: Was not found in the archive", pOpts->papszFiles[iFile]);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ RTVfsFsStrmRelease(hVfsFssIn);
+ }
+ RTMemFree(pbmFound);
+ return rcExit;
+}
+
+
+/**
+ * Checks if the name contains any escape sequences.
+ *
+ * An escape sequence would generally be one or more '..' references. On DOS
+ * like system, something that would make up a drive letter reference is also
+ * considered an escape sequence.
+ *
+ * @returns true / false.
+ * @param pszName The name to consider.
+ */
+static bool rtZipTarHasEscapeSequence(const char *pszName)
+{
+#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2)
+ if (pszName[0] == ':')
+ return true;
+#endif
+ while (*pszName)
+ {
+ while (RTPATH_IS_SEP(*pszName))
+ pszName++;
+ if ( pszName[0] == '.'
+ && pszName[1] == '.'
+ && (pszName[2] == '\0' || RTPATH_IS_SLASH(pszName[2])) )
+ return true;
+ while (*pszName && !RTPATH_IS_SEP(*pszName))
+ pszName++;
+ }
+
+ return false;
+}
+
+
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+
+/**
+ * Queries the user ID to use when extracting a member.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param pUser The user info.
+ * @param pszName The file name to use when complaining.
+ * @param rcExit The current exit code.
+ * @param pUid Where to return the user ID.
+ */
+static RTEXITCODE rtZipTarQueryExtractOwner(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pOwner, const char *pszName, RTEXITCODE rcExit,
+ PRTUID pUid)
+{
+ if (pOpts->uidOwner != NIL_RTUID)
+ *pUid = pOpts->uidOwner;
+ else if (pOpts->fPreserveGroup)
+ {
+ if (!pOwner->Attr.u.UnixGroup.szName[0])
+ *pUid = pOwner->Attr.u.UnixOwner.uid;
+ else
+ {
+ *pUid = NIL_RTUID;
+ return RTMsgErrorExitFailure("%s: User resolving is not implemented.", pszName);
+ }
+ }
+ else
+ *pUid = NIL_RTUID;
+ return rcExit;
+}
+
+
+/**
+ * Queries the group ID to use when extracting a member.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param pGroup The group info.
+ * @param pszName The file name to use when complaining.
+ * @param rcExit The current exit code.
+ * @param pGid Where to return the group ID.
+ */
+static RTEXITCODE rtZipTarQueryExtractGroup(PRTZIPTARCMDOPS pOpts, PCRTFSOBJINFO pGroup, const char *pszName, RTEXITCODE rcExit,
+ PRTGID pGid)
+{
+ if (pOpts->gidGroup != NIL_RTGID)
+ *pGid = pOpts->gidGroup;
+ else if (pOpts->fPreserveGroup)
+ {
+ if (!pGroup->Attr.u.UnixGroup.szName[0])
+ *pGid = pGroup->Attr.u.UnixGroup.gid;
+ else
+ {
+ *pGid = NIL_RTGID;
+ return RTMsgErrorExitFailure("%s: Group resolving is not implemented.", pszName);
+ }
+ }
+ else
+ *pGid = NIL_RTGID;
+ return rcExit;
+}
+
+#endif /* !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) */
+
+
+/**
+ * Corrects the file mode and other attributes.
+ *
+ * Common worker for rtZipTarCmdExtractFile and rtZipTarCmdExtractHardlink.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param rcExit The current exit code.
+ * @param hFile The handle to the destination file.
+ * @param pszDst The destination path (for error reporting).
+ * @param pUnixInfo The unix fs object info.
+ * @param pOwner The owner info.
+ * @param pGroup The group info.
+ */
+static RTEXITCODE rtZipTarCmdExtractSetAttribs(PRTZIPTARCMDOPS pOpts, RTEXITCODE rcExit, RTFILE hFile, const char *pszDst,
+ PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
+{
+ int rc;
+
+ if (!pOpts->fNoModTime)
+ {
+ rc = RTFileSetTimes(hFile, NULL, &pUnixInfo->ModificationTime, NULL, NULL);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error setting times: %Rrc", pszDst, rc);
+ }
+
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ if ( pOpts->uidOwner != NIL_RTUID
+ || pOpts->gidGroup != NIL_RTGID
+ || pOpts->fPreserveOwner
+ || pOpts->fPreserveGroup)
+ {
+ RTUID uidFile;
+ rcExit = rtZipTarQueryExtractOwner(pOpts, pOwner, pszDst, rcExit, &uidFile);
+
+ RTGID gidFile;
+ rcExit = rtZipTarQueryExtractGroup(pOpts, pGroup, pszDst, rcExit, &gidFile);
+ if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
+ {
+ rc = RTFileSetOwner(hFile, uidFile, gidFile);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error owner/group: %Rrc", pszDst, rc);
+ }
+ }
+#else
+ RT_NOREF_PV(pOwner); RT_NOREF_PV(pGroup);
+#endif
+
+ RTFMODE fMode = (pUnixInfo->Attr.fMode & pOpts->fFileModeAndMask) | pOpts->fFileModeOrMask;
+ rc = RTFileSetMode(hFile, fMode | RTFS_TYPE_FILE);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error changing mode: %Rrc", pszDst, rc);
+
+ return rcExit;
+}
+
+
+/**
+ * Extracts a hard linked file.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param rcExit The current exit code.
+ * @param pszDst The destination path.
+ * @param pszTarget The target relative path.
+ * @param pUnixInfo The unix fs object info.
+ * @param pOwner The owner info.
+ * @param pGroup The group info.
+ */
+static RTEXITCODE rtZipTarCmdExtractHardlink(PRTZIPTARCMDOPS pOpts, RTEXITCODE rcExit, const char *pszDst,
+ const char *pszTarget, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner,
+ PCRTFSOBJINFO pGroup)
+{
+ /*
+ * Construct full target path and check that it exists.
+ */
+ char szFullTarget[RTPATH_MAX];
+ int rc = RTPathJoin(szFullTarget, sizeof(szFullTarget), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszTarget);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Failed to construct full hardlink target path for %s: %Rrc",
+ pszDst, pszTarget, rc);
+
+ if (!RTFileExists(szFullTarget))
+ return RTMsgErrorExitFailure("%s: Hardlink target not found (or not a file): %s", pszDst, szFullTarget);
+
+ /*
+ * Try hardlink the file, falling back on copying.
+ */
+ /** @todo actual hardlinking */
+ if (true)
+ {
+ RTMsgWarning("%s: Hardlinking not available, copying '%s' instead.", pszDst, szFullTarget);
+
+ RTFILE hSrcFile;
+ rc = RTFileOpen(&hSrcFile, szFullTarget, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
+ | ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << RTFILE_O_CREATE_MODE_SHIFT);
+ RTFILE hDstFile;
+ rc = RTFileOpen(&hDstFile, pszDst, fOpen);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTFileCopyByHandles(hSrcFile, hDstFile);
+ if (RT_SUCCESS(rc))
+ {
+ rcExit = rtZipTarCmdExtractSetAttribs(pOpts, rcExit, hDstFile, pszDst, pUnixInfo, pOwner, pGroup);
+ rc = RTFileClose(hDstFile);
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTMsgErrorExitFailure("%s: Error closing hardlinked file copy: %Rrc", pszDst, rc);
+ RTFileDelete(pszDst);
+ }
+ }
+ else
+ {
+ rcExit = RTMsgErrorExitFailure("%s: Failed copying hardlinked file '%s': %Rrc", pszDst, szFullTarget, rc);
+ rc = RTFileClose(hDstFile);
+ RTFileDelete(pszDst);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
+ RTFileClose(hSrcFile);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("%s: Error opening file '%s' for reading (hardlink target): %Rrc",
+ pszDst, szFullTarget, rc);
+ }
+
+ return rcExit;
+}
+
+
+
+/**
+ * Extracts a file.
+ *
+ * Since we can restore permissions and attributes more efficiently by working
+ * directly on the file handle, we have special code path for files.
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The tar options.
+ * @param hVfsObj The tar object to display
+ * @param rcExit The current exit code.
+ * @param pszDst The destination path.
+ * @param pUnixInfo The unix fs object info.
+ * @param pOwner The owner info.
+ * @param pGroup The group info.
+ */
+static RTEXITCODE rtZipTarCmdExtractFile(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit,
+ const char *pszDst, PCRTFSOBJINFO pUnixInfo, PCRTFSOBJINFO pOwner, PCRTFSOBJINFO pGroup)
+{
+ /*
+ * Open the destination file and create a stream object for it.
+ */
+ uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
+ | ((RTFS_UNIX_IWUSR | RTFS_UNIX_IRUSR) << RTFILE_O_CREATE_MODE_SHIFT);
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszDst, fOpen);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Error creating file: %Rrc", pszDst, rc);
+
+ RTVFSIOSTREAM hVfsIosDst;
+ rc = RTVfsIoStrmFromRTFile(hFile, fOpen, true /*fLeaveOpen*/, &hVfsIosDst);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Convert source to a stream and optionally add a read ahead stage.
+ */
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj);
+ if (pOpts->fReadAhead)
+ {
+ RTVFSIOSTREAM hVfsReadAhead;
+ rc = RTVfsCreateReadAheadForIoStream(hVfsIosSrc, 0 /*fFlag*/, 16 /*cBuffers*/, _256K /*cbBuffer*/, &hVfsReadAhead);
+ if (RT_SUCCESS(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ hVfsIosSrc = hVfsReadAhead;
+ }
+ else
+ AssertRC(rc); /* can be ignored in release builds. */
+ }
+
+ /*
+ * Pump the data thru and correct the file attributes.
+ */
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(pUnixInfo->cbObject, _1M));
+ if (RT_SUCCESS(rc))
+ rcExit = rtZipTarCmdExtractSetAttribs(pOpts, rcExit, hFile, pszDst, pUnixInfo, pOwner, pGroup);
+ else
+ rcExit = RTMsgErrorExitFailure("%s: Error writing out file: %Rrc", pszDst, rc);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ else
+ rcExit = RTMsgErrorExitFailure("%s: Error creating I/O stream for file: %Rrc", pszDst, rc);
+ RTFileClose(hFile);
+ return rcExit;
+}
+
+
+/**
+ * @callback_method_impl{PFNDOWITHMEMBER, Implements --extract.}
+ */
+static RTEXITCODE rtZipTarCmdExtractCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
+{
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszName);
+
+ /*
+ * Query all the information.
+ */
+ RTFSOBJINFO UnixInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
+
+ RTFSOBJINFO Owner;
+ rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
+ rc, pszName);
+
+ RTFSOBJINFO Group;
+ rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
+ rc, pszName);
+
+ bool fIsHardLink = false;
+ char szTarget[RTPATH_MAX];
+ szTarget[0] = '\0';
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ if (hVfsSymlink != NIL_RTVFSSYMLINK)
+ {
+ rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
+ RTVfsSymlinkRelease(hVfsSymlink);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: RTVfsSymlinkRead failed: %Rrc", pszName, rc);
+ if (!szTarget[0])
+ return RTMsgErrorExitFailure("%s: Link target is empty.", pszName);
+ if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
+ {
+ fIsHardLink = true;
+ if (!RTFS_IS_FILE(UnixInfo.Attr.fMode))
+ return RTMsgErrorExitFailure("%s: Hardlinks are only supported for regular files (target=%s).",
+ pszName, szTarget);
+ if (rtZipTarHasEscapeSequence(pszName))
+ return RTMsgErrorExitFailure("%s: Hardlink target '%s' contains an escape sequence.",
+ pszName, szTarget);
+ }
+ }
+ else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
+ return RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
+
+ if (rtZipTarHasEscapeSequence(pszName))
+ return RTMsgErrorExitFailure("Name '%s' contains an escape sequence.", pszName);
+
+ /*
+ * Construct the path to the extracted member.
+ */
+ char szDst[RTPATH_MAX];
+ rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Failed to construct destination path for: %Rrc", pszName, rc);
+
+ /*
+ * Extract according to the type.
+ */
+ if (!fIsHardLink)
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ return rtZipTarCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo, &Owner, &Group);
+
+ case RTFS_TYPE_DIRECTORY:
+ rc = RTDirCreateFullPath(szDst, UnixInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Error creating directory: %Rrc", szDst, rc);
+ break;
+
+ case RTFS_TYPE_SYMLINK:
+ rc = RTSymlinkCreate(szDst, szTarget, RTSYMLINKTYPE_UNKNOWN, 0);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("%s: Error creating symbolic link: %Rrc", szDst, rc);
+ break;
+
+ case RTFS_TYPE_FIFO:
+ return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
+ case RTFS_TYPE_DEV_CHAR:
+ return RTMsgErrorExitFailure("%s: FIFOs are not supported.", pszName);
+ case RTFS_TYPE_DEV_BLOCK:
+ return RTMsgErrorExitFailure("%s: Block devices are not supported.", pszName);
+ case RTFS_TYPE_SOCKET:
+ return RTMsgErrorExitFailure("%s: Sockets are not supported.", pszName);
+ case RTFS_TYPE_WHITEOUT:
+ return RTMsgErrorExitFailure("%s: Whiteouts are not support.", pszName);
+ default:
+ return RTMsgErrorExitFailure("%s: Unknown file type.", pszName);
+ }
+ else
+ return rtZipTarCmdExtractHardlink(pOpts, rcExit, szDst, szTarget, &UnixInfo, &Owner, &Group);
+
+ /*
+ * Set other attributes as requested.
+ *
+ * Note! File extraction does get here.
+ */
+ if (!pOpts->fNoModTime)
+ {
+ rc = RTPathSetTimesEx(szDst, NULL, &UnixInfo.ModificationTime, NULL, NULL, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED && rc != VERR_NS_SYMLINK_SET_TIME)
+ rcExit = RTMsgErrorExitFailure("%s: Error changing modification time: %Rrc.", pszName, rc);
+ }
+
+#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2)
+ if ( pOpts->uidOwner != NIL_RTUID
+ || pOpts->gidGroup != NIL_RTGID
+ || pOpts->fPreserveOwner
+ || pOpts->fPreserveGroup)
+ {
+ RTUID uidFile;
+ rcExit = rtZipTarQueryExtractOwner(pOpts, &Owner, szDst, rcExit, &uidFile);
+
+ RTGID gidFile;
+ rcExit = rtZipTarQueryExtractGroup(pOpts, &Group, szDst, rcExit, &gidFile);
+ if (uidFile != NIL_RTUID || gidFile != NIL_RTGID)
+ {
+ rc = RTPathSetOwnerEx(szDst, uidFile, gidFile, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error owner/group: %Rrc", szDst, rc);
+ }
+ }
+#endif
+
+#if !defined(RT_OS_WINDOWS) /** @todo implement RTPathSetMode on windows... */
+ if (!RTFS_IS_SYMLINK(UnixInfo.Attr.fMode)) /* RTPathSetMode follows symbolic links atm. */
+ {
+ RTFMODE fMode;
+ if (RTFS_IS_DIRECTORY(UnixInfo.Attr.fMode))
+ fMode = (UnixInfo.Attr.fMode & (pOpts->fDirModeAndMask | RTFS_TYPE_MASK)) | pOpts->fDirModeOrMask;
+ else
+ fMode = (UnixInfo.Attr.fMode & (pOpts->fFileModeAndMask | RTFS_TYPE_MASK)) | pOpts->fFileModeOrMask;
+ rc = RTPathSetMode(szDst, fMode);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("%s: Error changing mode: %Rrc", szDst, rc);
+ }
+#endif
+
+ return rcExit;
+}
+
+
+/**
+ * @callback_method_impl{PFNDOWITHMEMBER, Implements --list.}
+ */
+static RTEXITCODE rtZipTarCmdListCallback(PRTZIPTARCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit)
+{
+ /*
+ * This is very simple in non-verbose mode.
+ */
+ if (!pOpts->fVerbose)
+ {
+ RTPrintf("%s\n", pszName);
+ return rcExit;
+ }
+
+ /*
+ * Query all the information.
+ */
+ RTFSOBJINFO UnixInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTMsgErrorExitFailure("RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
+ RT_ZERO(UnixInfo);
+ }
+
+ RTFSOBJINFO Owner;
+ rc = RTVfsObjQueryInfo(hVfsObj, &Owner, RTFSOBJATTRADD_UNIX_OWNER);
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
+ rc, pszName);
+ RT_ZERO(Owner);
+ }
+
+ RTFSOBJINFO Group;
+ rc = RTVfsObjQueryInfo(hVfsObj, &Group, RTFSOBJATTRADD_UNIX_GROUP);
+ if (RT_FAILURE(rc))
+ {
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
+ "RTVfsObjQueryInfo(,,UNIX_OWNER) returned %Rrc on '%s'",
+ rc, pszName);
+ RT_ZERO(Group);
+ }
+
+ const char *pszLinkType = NULL;
+ char szTarget[RTPATH_MAX];
+ szTarget[0] = '\0';
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ if (hVfsSymlink != NIL_RTVFSSYMLINK)
+ {
+ rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExitFailure("RTVfsSymlinkRead returned %Rrc on '%s'", rc, pszName);
+ RTVfsSymlinkRelease(hVfsSymlink);
+ pszLinkType = RTFS_IS_SYMLINK(UnixInfo.Attr.fMode) ? "->" : "link to";
+ }
+ else if (RTFS_IS_SYMLINK(UnixInfo.Attr.fMode))
+ rcExit = RTMsgErrorExitFailure("Failed to get symlink object for '%s'", pszName);
+
+ /*
+ * Translate the mode mask.
+ */
+ char szMode[16];
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FIFO: szMode[0] = 'f'; break;
+ case RTFS_TYPE_DEV_CHAR: szMode[0] = 'c'; break;
+ case RTFS_TYPE_DIRECTORY: szMode[0] = 'd'; break;
+ case RTFS_TYPE_DEV_BLOCK: szMode[0] = 'b'; break;
+ case RTFS_TYPE_FILE: szMode[0] = '-'; break;
+ case RTFS_TYPE_SYMLINK: szMode[0] = 'l'; break;
+ case RTFS_TYPE_SOCKET: szMode[0] = 's'; break;
+ case RTFS_TYPE_WHITEOUT: szMode[0] = 'w'; break;
+ default: szMode[0] = '?'; break;
+ }
+ if (pszLinkType && szMode[0] != 's')
+ szMode[0] = 'h';
+
+ szMode[1] = UnixInfo.Attr.fMode & RTFS_UNIX_IRUSR ? 'r' : '-';
+ szMode[2] = UnixInfo.Attr.fMode & RTFS_UNIX_IWUSR ? 'w' : '-';
+ szMode[3] = UnixInfo.Attr.fMode & RTFS_UNIX_IXUSR ? 'x' : '-';
+
+ szMode[4] = UnixInfo.Attr.fMode & RTFS_UNIX_IRGRP ? 'r' : '-';
+ szMode[5] = UnixInfo.Attr.fMode & RTFS_UNIX_IWGRP ? 'w' : '-';
+ szMode[6] = UnixInfo.Attr.fMode & RTFS_UNIX_IXGRP ? 'x' : '-';
+
+ szMode[7] = UnixInfo.Attr.fMode & RTFS_UNIX_IROTH ? 'r' : '-';
+ szMode[8] = UnixInfo.Attr.fMode & RTFS_UNIX_IWOTH ? 'w' : '-';
+ szMode[9] = UnixInfo.Attr.fMode & RTFS_UNIX_IXOTH ? 'x' : '-';
+ szMode[10] = '\0';
+
+ /** @todo sticky and set-uid/gid bits. */
+
+ /*
+ * Make sure we've got valid owner and group strings.
+ */
+ if (!Owner.Attr.u.UnixGroup.szName[0])
+ RTStrPrintf(Owner.Attr.u.UnixOwner.szName, sizeof(Owner.Attr.u.UnixOwner.szName),
+ "%u", UnixInfo.Attr.u.Unix.uid);
+
+ if (!Group.Attr.u.UnixOwner.szName[0])
+ RTStrPrintf(Group.Attr.u.UnixGroup.szName, sizeof(Group.Attr.u.UnixGroup.szName),
+ "%u", UnixInfo.Attr.u.Unix.gid);
+
+ /*
+ * Format the modification time.
+ */
+ char szModTime[32];
+ RTTIME ModTime;
+ PRTTIME pTime;
+ if (!pOpts->fDisplayUtc)
+ pTime = RTTimeLocalExplode(&ModTime, &UnixInfo.ModificationTime);
+ else
+ pTime = RTTimeExplode(&ModTime, &UnixInfo.ModificationTime);
+ if (!pTime)
+ RT_ZERO(ModTime);
+ RTStrPrintf(szModTime, sizeof(szModTime), "%04d-%02u-%02u %02u:%02u",
+ ModTime.i32Year, ModTime.u8Month, ModTime.u8MonthDay, ModTime.u8Hour, ModTime.u8Minute);
+
+ /*
+ * Format the size and figure how much space is needed between the
+ * user/group and the size.
+ */
+ char szSize[64];
+ size_t cchSize;
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DEV_BLOCK:
+ cchSize = RTStrPrintf(szSize, sizeof(szSize), "%u,%u",
+ RTDEV_MAJOR(UnixInfo.Attr.u.Unix.Device), RTDEV_MINOR(UnixInfo.Attr.u.Unix.Device));
+ break;
+ default:
+ cchSize = RTStrPrintf(szSize, sizeof(szSize), "%RU64", UnixInfo.cbObject);
+ break;
+ }
+
+ size_t cchUserGroup = strlen(Owner.Attr.u.UnixOwner.szName)
+ + 1
+ + strlen(Group.Attr.u.UnixGroup.szName);
+ ssize_t cchPad = cchUserGroup + cchSize + 1 < 19
+ ? 19 - (cchUserGroup + cchSize + 1)
+ : 0;
+
+ /*
+ * Go to press.
+ */
+ if (pszLinkType)
+ RTPrintf("%s %s/%s%*s %s %s %s %s %s\n",
+ szMode,
+ Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
+ cchPad, "",
+ szSize,
+ szModTime,
+ pszName,
+ pszLinkType,
+ szTarget);
+ else
+ RTPrintf("%s %s/%s%*s %s %s %s\n",
+ szMode,
+ Owner.Attr.u.UnixOwner.szName, Group.Attr.u.UnixGroup.szName,
+ cchPad, "",
+ szSize,
+ szModTime,
+ pszName);
+
+ return rcExit;
+}
+
+
+/**
+ * Display usage.
+ *
+ * @param pszProgName The program name.
+ */
+static void rtZipTarUsage(const char *pszProgName)
+{
+ /*
+ * 0 1 2 3 4 5 6 7 8
+ * 012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ */
+ RTPrintf("Usage: %s [options]\n"
+ "\n",
+ pszProgName);
+ RTPrintf("Operations:\n"
+ " -A, --concatenate, --catenate\n"
+ " Append the content of one tar archive to another. (not impl)\n"
+ " -c, --create\n"
+ " Create a new tar archive. (not impl)\n"
+ " -d, --diff, --compare\n"
+ " Compare atar archive with the file system. (not impl)\n"
+ " -r, --append\n"
+ " Append more files to the tar archive. (not impl)\n"
+ " -t, --list\n"
+ " List the contents of the tar archive.\n"
+ " -u, --update\n"
+ " Update the archive, adding files that are newer than the\n"
+ " ones in the archive. (not impl)\n"
+ " -x, --extract, --get\n"
+ " Extract the files from the tar archive.\n"
+ " --delete\n"
+ " Delete files from the tar archive.\n"
+ "\n"
+ );
+ RTPrintf("Basic Options:\n"
+ " -C <dir>, --directory <dir> (-A, -c, -d, -r, -u, -x)\n"
+ " Sets the base directory for input and output file members.\n"
+ " This does not apply to --file, even if it preceeds it.\n"
+ " -f <archive>, --file <archive> (all)\n"
+ " The tar file to create or process. '-' indicates stdout/stdin,\n"
+ " which is is the default.\n"
+ " -v, --verbose (all)\n"
+ " Verbose operation.\n"
+ " -p, --preserve-permissions (-x)\n"
+ " Preserve all permissions when extracting. Must be used\n"
+ " before the mode mask options as it will change some of these.\n"
+ " -j, --bzip2 (all)\n"
+ " Compress/decompress the archive with bzip2.\n"
+ " -z, --gzip, --gunzip, --ungzip (all)\n"
+ " Compress/decompress the archive with gzip.\n"
+ "\n");
+ RTPrintf("Misc Options:\n"
+ " --owner <uid/username> (-A, -c, -d, -r, -u, -x)\n"
+ " Set the owner of extracted and archived files to the user specified.\n"
+ " --group <uid/username> (-A, -c, -d, -r, -u, -x)\n"
+ " Set the group of extracted and archived files to the group specified.\n"
+ " --utc (-t)\n"
+ " Display timestamps as UTC instead of local time.\n"
+ " -S, --sparse (-A, -c, -u)\n"
+ " Detect sparse files and store them (gnu tar extension).\n"
+ " --format <format> (-A, -c, -u, but also -d, -r, -x)\n"
+ " The file format:\n"
+ " auto (gnu tar)\n"
+ " default (gnu tar)\n"
+ " tar (gnu tar)"
+ " gnu (tar v1.13+), "
+ " ustar (tar POSIX.1-1988), "
+ " pax (tar POSIX.1-2001),\n"
+ " xar\n"
+ " cpio\n"
+ " Note! Because XAR/TAR/CPIO detection isn't implemented yet, it\n"
+ " is necessary to specifcy --format=xar when reading a\n"
+ " XAR file or --format=cpio for a CPIO file.\n"
+ " Otherwise this option is only for creation.\n"
+ "\n");
+ RTPrintf("IPRT Options:\n"
+ " --prefix <dir-prefix> (-A, -c, -d, -r, -u)\n"
+ " Directory prefix to give the members added to the archive.\n"
+ " --file-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
+ " Restrict the access mode of regular and special files.\n"
+ " --file-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
+ " Include the given access mode for regular and special files.\n"
+ " --dir-mode-and-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
+ " Restrict the access mode of directories.\n"
+ " --dir-mode-or-mask <octal-mode> (-A, -c, -d, -r, -u, -x)\n"
+ " Include the given access mode for directories.\n"
+ " --read-ahead (-x)\n"
+ " Enabled read ahead thread when extracting files.\n"
+ " --push-file (-A, -c, -u)\n"
+ " Use RTVfsFsStrmPushFile instead of RTVfsFsStrmAdd.\n"
+ "\n");
+ RTPrintf("Standard Options:\n"
+ " -h, -?, --help\n"
+ " Display this help text.\n"
+ " -V, --version\n"
+ " Display version number.\n");
+}
+
+
+RTDECL(RTEXITCODE) RTZipTarCmd(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ *
+ * N.B. This is less flexible that your regular tar program in that it
+ * requires the operation to be specified as an option. On the other
+ * hand, you can specify it where ever you like in the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* operations */
+ { "--concatenate", 'A', RTGETOPT_REQ_NOTHING },
+ { "--catenate", 'A', RTGETOPT_REQ_NOTHING },
+ { "--create", 'c', RTGETOPT_REQ_NOTHING },
+ { "--diff", 'd', RTGETOPT_REQ_NOTHING },
+ { "--compare", 'd', RTGETOPT_REQ_NOTHING },
+ { "--append", 'r', RTGETOPT_REQ_NOTHING },
+ { "--list", 't', RTGETOPT_REQ_NOTHING },
+ { "--update", 'u', RTGETOPT_REQ_NOTHING },
+ { "--extract", 'x', RTGETOPT_REQ_NOTHING },
+ { "--get", 'x', RTGETOPT_REQ_NOTHING },
+ { "--delete", RTZIPTARCMD_OPT_DELETE, RTGETOPT_REQ_NOTHING },
+
+ /* basic options */
+ { "--directory", 'C', RTGETOPT_REQ_STRING },
+ { "--file", 'f', RTGETOPT_REQ_STRING },
+ { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
+ { "--preserve-permissions", 'p', RTGETOPT_REQ_NOTHING },
+ { "--bzip2", 'j', RTGETOPT_REQ_NOTHING },
+ { "--gzip", 'z', RTGETOPT_REQ_NOTHING },
+ { "--gunzip", 'z', RTGETOPT_REQ_NOTHING },
+ { "--ungzip", 'z', RTGETOPT_REQ_NOTHING },
+
+ /* other options. */
+ { "--owner", RTZIPTARCMD_OPT_OWNER, RTGETOPT_REQ_STRING },
+ { "--group", RTZIPTARCMD_OPT_GROUP, RTGETOPT_REQ_STRING },
+ { "--utc", RTZIPTARCMD_OPT_UTC, RTGETOPT_REQ_NOTHING },
+ { "--sparse", 'S', RTGETOPT_REQ_NOTHING },
+ { "--format", RTZIPTARCMD_OPT_FORMAT, RTGETOPT_REQ_STRING },
+ { "--no-recursion", RTZIPTARCMD_OPT_NO_RECURSION, RTGETOPT_REQ_NOTHING },
+
+ /* IPRT extensions */
+ { "--prefix", RTZIPTARCMD_OPT_PREFIX, RTGETOPT_REQ_STRING },
+ { "--file-mode-and-mask", RTZIPTARCMD_OPT_FILE_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--file-mode-or-mask", RTZIPTARCMD_OPT_FILE_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--dir-mode-and-mask", RTZIPTARCMD_OPT_DIR_MODE_AND_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--dir-mode-or-mask", RTZIPTARCMD_OPT_DIR_MODE_OR_MASK, RTGETOPT_REQ_UINT32 | RTGETOPT_FLAG_OCT },
+ { "--read-ahead", RTZIPTARCMD_OPT_READ_AHEAD, RTGETOPT_REQ_NOTHING },
+ { "--use-push-file", RTZIPTARCMD_OPT_USE_PUSH_FILE, RTGETOPT_REQ_NOTHING },
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExitFailure("RTGetOpt failed: %Rrc", rc);
+
+ RTZIPTARCMDOPS Opts;
+ RT_ZERO(Opts);
+ Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
+ Opts.uidOwner = NIL_RTUID;
+ Opts.gidGroup = NIL_RTUID;
+ Opts.fFileModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
+ Opts.fDirModeAndMask = RTFS_UNIX_ALL_ACCESS_PERMS;
+#if 0
+ if (RTPermIsSuperUser())
+ {
+ Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
+ Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
+ Opts.fPreserveOwner = true;
+ Opts.fPreserveGroup = true;
+ }
+#endif
+ Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
+ Opts.fRecursive = true; /* Recursion is implicit unless otherwise specified. */
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ /* operations */
+ case 'A':
+ case 'c':
+ case 'd':
+ case 'r':
+ case 't':
+ case 'u':
+ case 'x':
+ case RTZIPTARCMD_OPT_DELETE:
+ if (Opts.iOperation)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Conflicting tar operation (%s already set, now %s)",
+ Opts.pszOperation, ValueUnion.pDef->pszLong);
+ Opts.iOperation = rc;
+ Opts.pszOperation = ValueUnion.pDef->pszLong;
+ break;
+
+ /* basic options */
+ case 'C':
+ if (Opts.pszDirectory)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -C/--directory once");
+ Opts.pszDirectory = ValueUnion.psz;
+ break;
+
+ case 'f':
+ if (Opts.pszFile)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -f/--file once");
+ Opts.pszFile = ValueUnion.psz;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ case 'p':
+ Opts.fFileModeAndMask = RTFS_UNIX_ALL_PERMS;
+ Opts.fDirModeAndMask = RTFS_UNIX_ALL_PERMS;
+ Opts.fPreserveOwner = true;
+ Opts.fPreserveGroup = true;
+ break;
+
+ case 'j':
+ case 'z':
+ if (Opts.chZipper)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify one compressor / decompressor");
+ Opts.chZipper = rc;
+ break;
+
+ case RTZIPTARCMD_OPT_OWNER:
+ if (Opts.pszOwner)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --owner once");
+ Opts.pszOwner = ValueUnion.psz;
+
+ rc = RTStrToUInt32Full(Opts.pszOwner, 0, &ValueUnion.u32);
+ if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Error convering --owner '%s' into a number: %Rrc", Opts.pszOwner, rc);
+ if (RT_SUCCESS(rc))
+ {
+ Opts.uidOwner = ValueUnion.u32;
+ Opts.pszOwner = NULL;
+ }
+ break;
+
+ case RTZIPTARCMD_OPT_GROUP:
+ if (Opts.pszGroup)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --group once");
+ Opts.pszGroup = ValueUnion.psz;
+
+ rc = RTStrToUInt32Full(Opts.pszGroup, 0, &ValueUnion.u32);
+ if (RT_SUCCESS(rc) && rc != VINF_SUCCESS)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Error convering --group '%s' into a number: %Rrc", Opts.pszGroup, rc);
+ if (RT_SUCCESS(rc))
+ {
+ Opts.gidGroup = ValueUnion.u32;
+ Opts.pszGroup = NULL;
+ }
+ break;
+
+ case RTZIPTARCMD_OPT_UTC:
+ Opts.fDisplayUtc = true;
+ break;
+
+ case RTZIPTARCMD_OPT_NO_RECURSION:
+ Opts.fRecursive = false;
+ break;
+
+ /* GNU */
+ case 'S':
+ Opts.fTarCreate |= RTZIPTAR_C_SPARSE;
+ break;
+
+ /* iprt extensions */
+ case RTZIPTARCMD_OPT_PREFIX:
+ if (Opts.pszPrefix)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify --prefix once");
+ Opts.pszPrefix = ValueUnion.psz;
+ break;
+
+ case RTZIPTARCMD_OPT_FILE_MODE_AND_MASK:
+ Opts.fFileModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
+ break;
+
+ case RTZIPTARCMD_OPT_FILE_MODE_OR_MASK:
+ Opts.fFileModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
+ break;
+
+ case RTZIPTARCMD_OPT_DIR_MODE_AND_MASK:
+ Opts.fDirModeAndMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
+ break;
+
+ case RTZIPTARCMD_OPT_DIR_MODE_OR_MASK:
+ Opts.fDirModeOrMask = ValueUnion.u32 & RTFS_UNIX_ALL_PERMS;
+ break;
+
+ case RTZIPTARCMD_OPT_FORMAT:
+ if (!strcmp(ValueUnion.psz, "auto") || !strcmp(ValueUnion.psz, "default"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_AUTO_DEFAULT;
+ Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
+ }
+ else if (!strcmp(ValueUnion.psz, "tar"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
+ Opts.enmTarFormat = RTZIPTARFORMAT_DEFAULT;
+ }
+ else if (!strcmp(ValueUnion.psz, "gnu"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
+ Opts.enmTarFormat = RTZIPTARFORMAT_GNU;
+ }
+ else if (!strcmp(ValueUnion.psz, "ustar"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
+ Opts.enmTarFormat = RTZIPTARFORMAT_USTAR;
+ }
+ else if ( !strcmp(ValueUnion.psz, "posix")
+ || !strcmp(ValueUnion.psz, "pax"))
+ {
+ Opts.enmFormat = RTZIPTARCMDFORMAT_TAR;
+ Opts.enmTarFormat = RTZIPTARFORMAT_PAX;
+ }
+ else if (!strcmp(ValueUnion.psz, "xar"))
+ Opts.enmFormat = RTZIPTARCMDFORMAT_XAR;
+ else if (!strcmp(ValueUnion.psz, "cpio"))
+ Opts.enmFormat = RTZIPTARCMDFORMAT_CPIO;
+ else
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown archive format: '%s'", ValueUnion.psz);
+ break;
+
+ case RTZIPTARCMD_OPT_READ_AHEAD:
+ Opts.fReadAhead = true;
+ break;
+
+ case RTZIPTARCMD_OPT_USE_PUSH_FILE:
+ Opts.fUsePushFile = true;
+ break;
+
+ /* Standard bits. */
+ case 'h':
+ rtZipTarUsage(RTPathFilename(papszArgs[0]));
+ return RTEXITCODE_SUCCESS;
+
+ case 'V':
+ RTPrintf("%sr%d\n", RTBldCfgVersion(), RTBldCfgRevision());
+ return RTEXITCODE_SUCCESS;
+
+ default:
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ /* this is kind of ugly. */
+ Assert((unsigned)GetState.iNext - 1 <= cArgs);
+ Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext - 1];
+ Opts.cFiles = cArgs - GetState.iNext + 1;
+ }
+
+ if (!Opts.pszFile)
+ return RTMsgErrorExitFailure("No archive specified");
+
+ /*
+ * Post proceess the options.
+ */
+ if (Opts.iOperation == 0)
+ {
+ Opts.iOperation = 't';
+ Opts.pszOperation = "--list";
+ }
+
+ if ( Opts.iOperation == 'x'
+ && Opts.pszOwner)
+ return RTMsgErrorExitFailure("The use of --owner with %s has not implemented yet", Opts.pszOperation);
+
+ if ( Opts.iOperation == 'x'
+ && Opts.pszGroup)
+ return RTMsgErrorExitFailure("The use of --group with %s has not implemented yet", Opts.pszOperation);
+
+ /*
+ * Do the job.
+ */
+ switch (Opts.iOperation)
+ {
+ case 't':
+ return rtZipTarDoWithMembers(&Opts, rtZipTarCmdListCallback);
+
+ case 'x':
+ return rtZipTarDoWithMembers(&Opts, rtZipTarCmdExtractCallback);
+
+ case 'c':
+ return rtZipTarCreate(&Opts);
+
+ case 'A':
+ case 'd':
+ case 'r':
+ case 'u':
+ case RTZIPTARCMD_OPT_DELETE:
+ return RTMsgErrorExitFailure("The operation %s is not implemented yet", Opts.pszOperation);
+
+ default:
+ return RTMsgErrorExitFailure("Internal error");
+ }
+}
+
diff --git a/src/VBox/Runtime/common/zip/tarvfs.cpp b/src/VBox/Runtime/common/zip/tarvfs.cpp
new file mode 100644
index 00000000..72db1dc7
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tarvfs.cpp
@@ -0,0 +1,1477 @@
+/* $Id: tarvfs.cpp $ */
+/** @file
+ * IPRT - TAR Virtual Filesystem, Reader.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/poll.h>
+#include <iprt/file.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+
+#include "tarvfsreader.h"
+
+
+/**
+ * Converts a numeric header field to the C native type.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pszField The TAR header field.
+ * @param cchField The length of the field.
+ * @param fOctalOnly Must be octal.
+ * @param pi64 Where to store the value.
+ */
+static int rtZipTarHdrFieldToNum(const char *pszField, size_t cchField, bool fOctalOnly, int64_t *pi64)
+{
+ unsigned char const *puchField = (unsigned char const *)pszField;
+ size_t const cchFieldOrg = cchField;
+ if ( fOctalOnly
+ || !(*puchField & 0x80))
+ {
+ /*
+ * Skip leading spaces. Include zeros to save a few slower loops below.
+ */
+ unsigned char ch;
+ while (cchField > 0 && ((ch = *puchField) == ' '|| ch == '0'))
+ cchField--, puchField++;
+
+ /*
+ * Convert octal digits.
+ */
+ int64_t i64 = 0;
+ while (cchField > 0)
+ {
+ unsigned char uDigit = *puchField - '0';
+ if (uDigit >= 8)
+ break;
+ i64 <<= 3;
+ i64 |= uDigit;
+
+ puchField++;
+ cchField--;
+ }
+ *pi64 = i64;
+
+ /*
+ * Was it terminated correctly?
+ */
+ while (cchField > 0)
+ {
+ ch = *puchField++;
+ if (ch != 0 && ch != ' ')
+ return cchField < cchFieldOrg
+ ? VERR_TAR_BAD_NUM_FIELD_TERM
+ : VERR_TAR_BAD_NUM_FIELD;
+ cchField--;
+ }
+ }
+ else
+ {
+ /*
+ * The first byte has the bit 7 set to indicate base-256, while bit 6
+ * is the signed bit. Bits 5:0 are the most significant value bits.
+ */
+ uint64_t u64;
+ if (!(0x40 & *puchField))
+ {
+ /* Positive or zero value. */
+ u64 = *puchField & 0x3f;
+ cchField--;
+ puchField++;
+
+ while (cchField-- > 0)
+ {
+ if (RT_LIKELY(u64 <= (uint64_t)INT64_MAX / 256))
+ u64 = (u64 << 8) | *puchField++;
+ else
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+ }
+ }
+ else
+ {
+ /* Negative value (could be used in timestamp). We do manual sign extending here. */
+ u64 = (UINT64_MAX << 6) | (*puchField & 0x3f);
+ cchField--;
+ puchField++;
+
+ while (cchField-- > 0)
+ {
+ if (RT_LIKELY(u64 >= (uint64_t)(INT64_MIN / 256)))
+ u64 = (u64 << 8) | *puchField++;
+ else
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+ }
+ }
+ *pi64 = (int64_t)u64;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Validates the TAR header.
+ *
+ * @returns VINF_SUCCESS if valid, VERR_TAR_ZERO_HEADER if all zeros, and
+ * the appropriate VERR_TAR_XXX otherwise.
+ * @param pTar The TAR header.
+ * @param penmType Where to return the type of header on success.
+ */
+static int rtZipTarHdrValidate(PCRTZIPTARHDR pTar, PRTZIPTARTYPE penmType)
+{
+ /*
+ * Calc the checksum first since this enables us to detect zero headers.
+ */
+ int32_t i32ChkSum;
+ int32_t i32ChkSumSignedAlt;
+ if (rtZipTarCalcChkSum(pTar, &i32ChkSum, &i32ChkSumSignedAlt))
+ return VERR_TAR_ZERO_HEADER;
+
+ /*
+ * Read the checksum field and match the checksums.
+ */
+ int64_t i64HdrChkSum;
+ int rc = rtZipTarHdrFieldToNum(pTar->Common.chksum, sizeof(pTar->Common.chksum), true /*fOctalOnly*/, &i64HdrChkSum);
+ if (RT_FAILURE(rc))
+ return VERR_TAR_BAD_CHKSUM_FIELD;
+ if ( i32ChkSum != i64HdrChkSum
+ && i32ChkSumSignedAlt != i64HdrChkSum) /** @todo test this */
+ return VERR_TAR_CHKSUM_MISMATCH;
+
+ /*
+ * Detect the TAR type.
+ */
+ RTZIPTARTYPE enmType;
+ if ( pTar->Common.magic[0] == 'u'
+ && pTar->Common.magic[1] == 's'
+ && pTar->Common.magic[2] == 't'
+ && pTar->Common.magic[3] == 'a'
+ && pTar->Common.magic[4] == 'r')
+ {
+/** @todo detect star headers */
+ if ( pTar->Common.magic[5] == '\0'
+ && pTar->Common.version[0] == '0'
+ && pTar->Common.version[1] == '0')
+ enmType = RTZIPTARTYPE_POSIX;
+ else if ( pTar->Common.magic[5] == ' '
+ && pTar->Common.version[0] == ' '
+ && pTar->Common.version[1] == '\0')
+ enmType = RTZIPTARTYPE_GNU;
+ else if ( pTar->Common.magic[5] == '\0' /* VMWare ambiguity - they probably mean posix but */
+ && pTar->Common.version[0] == ' ' /* got the version wrong. */
+ && pTar->Common.version[1] == '\0')
+ enmType = RTZIPTARTYPE_POSIX;
+ else
+ return VERR_TAR_NOT_USTAR_V00;
+ }
+ else
+ enmType = RTZIPTARTYPE_ANCIENT;
+ *penmType = enmType;
+
+ /*
+ * Perform some basic checks.
+ */
+ switch (enmType)
+ {
+ case RTZIPTARTYPE_POSIX:
+ if ( !RT_C_IS_ALNUM(pTar->Common.typeflag)
+ && pTar->Common.typeflag != '\0')
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ break;
+
+ case RTZIPTARTYPE_GNU:
+ switch (pTar->Common.typeflag)
+ {
+ case RTZIPTAR_TF_OLDNORMAL:
+ case RTZIPTAR_TF_NORMAL:
+ case RTZIPTAR_TF_CONTIG:
+ case RTZIPTAR_TF_DIR:
+ case RTZIPTAR_TF_CHR:
+ case RTZIPTAR_TF_BLK:
+ case RTZIPTAR_TF_LINK:
+ case RTZIPTAR_TF_SYMLINK:
+ case RTZIPTAR_TF_FIFO:
+ break;
+
+ case RTZIPTAR_TF_GNU_LONGLINK:
+ case RTZIPTAR_TF_GNU_LONGNAME:
+ break;
+
+ case RTZIPTAR_TF_GNU_DUMPDIR:
+ case RTZIPTAR_TF_GNU_MULTIVOL:
+ case RTZIPTAR_TF_GNU_SPARSE:
+ case RTZIPTAR_TF_GNU_VOLDHR:
+ /** @todo Implement full GNU TAR support. .*/
+ return VERR_TAR_UNSUPPORTED_GNU_HDR_TYPE;
+
+ default:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ }
+ break;
+
+ case RTZIPTARTYPE_ANCIENT:
+ switch (pTar->Common.typeflag)
+ {
+ case RTZIPTAR_TF_OLDNORMAL:
+ case RTZIPTAR_TF_NORMAL:
+ case RTZIPTAR_TF_CONTIG:
+ case RTZIPTAR_TF_DIR:
+ case RTZIPTAR_TF_LINK:
+ case RTZIPTAR_TF_SYMLINK:
+ case RTZIPTAR_TF_FIFO:
+ break;
+ default:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ }
+ break;
+ default: /* shut up gcc */
+ AssertFailedReturn(VERR_INTERNAL_ERROR_3);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses and validates the first TAR header of a archive/file/dir/whatever.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR reader stat.
+ * @param pTar The TAR header that has been read.
+ * @param fFirst Set if this is the first header, otherwise
+ * clear.
+ */
+static int rtZipTarReaderParseNextHeader(PRTZIPTARREADER pThis, PCRTZIPTARHDR pHdr, bool fFirst)
+{
+ int rc;
+
+ /*
+ * Basic header validation and detection first.
+ */
+ RTZIPTARTYPE enmType;
+ rc = rtZipTarHdrValidate(pHdr, &enmType);
+ if (RT_FAILURE_NP(rc))
+ {
+ if (rc == VERR_TAR_ZERO_HEADER)
+ {
+ pThis->cZeroHdrs = 1;
+ pThis->enmState = RTZIPTARREADERSTATE_ZERO;
+ return VINF_SUCCESS;
+ }
+ return rc;
+ }
+ if (fFirst)
+ {
+ pThis->enmType = enmType;
+ if (pThis->enmPrevType == RTZIPTARTYPE_INVALID)
+ pThis->enmPrevType = enmType;
+ }
+
+ /*
+ * Handle the header by type.
+ */
+ switch (pHdr->Common.typeflag)
+ {
+ case RTZIPTAR_TF_OLDNORMAL:
+ case RTZIPTAR_TF_NORMAL:
+ case RTZIPTAR_TF_CONTIG:
+ case RTZIPTAR_TF_LINK:
+ case RTZIPTAR_TF_SYMLINK:
+ case RTZIPTAR_TF_CHR:
+ case RTZIPTAR_TF_BLK:
+ case RTZIPTAR_TF_FIFO:
+ case RTZIPTAR_TF_DIR:
+ /*
+ * Extract the name first.
+ */
+ if (!pHdr->Common.name[0])
+ return VERR_TAR_EMPTY_NAME;
+ if (pThis->enmType == RTZIPTARTYPE_POSIX)
+ {
+ Assert(pThis->offGnuLongCur == 0); Assert(pThis->szName[0] == '\0');
+ pThis->szName[0] = '\0';
+ if (pHdr->Posix.prefix[0])
+ {
+ rc = RTStrCopyEx(pThis->szName, sizeof(pThis->szName), pHdr->Posix.prefix, sizeof(pHdr->Posix.prefix));
+ AssertRC(rc); /* shall not fail */
+ rc = RTStrCat(pThis->szName, sizeof(pThis->szName), "/");
+ AssertRC(rc); /* ditto */
+ }
+ rc = RTStrCatEx(pThis->szName, sizeof(pThis->szName), pHdr->Common.name, sizeof(pHdr->Common.name));
+ AssertRCReturn(rc, rc);
+ }
+ else if (pThis->enmType == RTZIPTARTYPE_GNU)
+ {
+ if (!pThis->szName[0])
+ {
+ rc = RTStrCopyEx(pThis->szName, sizeof(pThis->szName), pHdr->Common.name, sizeof(pHdr->Common.name));
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ {
+ /* Old TAR */
+ Assert(pThis->offGnuLongCur == 0); Assert(pThis->szName[0] == '\0');
+ rc = RTStrCopyEx(pThis->szName, sizeof(pThis->szName), pHdr->Common.name, sizeof(pHdr->Common.name));
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Extract the link target.
+ */
+ if ( pHdr->Common.typeflag == RTZIPTAR_TF_LINK
+ || pHdr->Common.typeflag == RTZIPTAR_TF_SYMLINK)
+ {
+ if ( pThis->enmType == RTZIPTARTYPE_POSIX
+ || pThis->enmType == RTZIPTARTYPE_ANCIENT
+ || (pThis->enmType == RTZIPTARTYPE_GNU && pThis->szTarget[0] == '\0')
+ )
+ {
+ Assert(pThis->szTarget[0] == '\0');
+ rc = RTStrCopyEx(pThis->szTarget, sizeof(pThis->szTarget),
+ pHdr->Common.linkname, sizeof(pHdr->Common.linkname));
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ pThis->szTarget[0] = '\0';
+
+ pThis->Hdr = *pHdr;
+ break;
+
+ case RTZIPTAR_TF_X_HDR:
+ case RTZIPTAR_TF_X_GLOBAL:
+ /** @todo implement PAX */
+ return VERR_TAR_UNSUPPORTED_PAX_TYPE;
+
+ case RTZIPTAR_TF_SOLARIS_XHDR:
+ /** @todo implement solaris / pax attribute lists. */
+ return VERR_TAR_UNSUPPORTED_SOLARIS_HDR_TYPE;
+
+
+ /*
+ * A GNU long name or long link is a dummy record followed by one or
+ * more 512 byte string blocks holding the long name/link. The name
+ * lenght is encoded in the size field, null terminator included. If
+ * it is a symlink or hard link the long name may be followed by a
+ * long link sequence.
+ */
+ case RTZIPTAR_TF_GNU_LONGNAME:
+ case RTZIPTAR_TF_GNU_LONGLINK:
+ {
+ if (strcmp(pHdr->Gnu.name, "././@LongLink"))
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+
+ int64_t cb64;
+ rc = rtZipTarHdrFieldToNum(pHdr->Gnu.size, sizeof(pHdr->Gnu.size), false /*fOctalOnly*/, &cb64);
+ if (RT_FAILURE(rc) || cb64 < 0 || cb64 > _1M)
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+ uint32_t cb = (uint32_t)cb64;
+ if (cb >= sizeof(pThis->szName))
+ return VERR_TAR_NAME_TOO_LONG;
+
+ pThis->cbGnuLongExpect = cb;
+ pThis->offGnuLongCur = 0;
+ pThis->enmState = pHdr->Common.typeflag == RTZIPTAR_TF_GNU_LONGNAME
+ ? RTZIPTARREADERSTATE_GNU_LONGNAME
+ : RTZIPTARREADERSTATE_GNU_LONGLINK;
+ break;
+ }
+
+ case RTZIPTAR_TF_GNU_DUMPDIR:
+ case RTZIPTAR_TF_GNU_MULTIVOL:
+ case RTZIPTAR_TF_GNU_SPARSE:
+ case RTZIPTAR_TF_GNU_VOLDHR:
+ /** @todo Implement or skip GNU headers */
+ return VERR_TAR_UNSUPPORTED_GNU_HDR_TYPE;
+
+ default:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses and validates a TAR header.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR reader stat.
+ * @param pTar The TAR header that has been read.
+ */
+static int rtZipTarReaderParseHeader(PRTZIPTARREADER pThis, PCRTZIPTARHDR pHdr)
+{
+ switch (pThis->enmState)
+ {
+ /*
+ * The first record for a file/directory/whatever.
+ */
+ case RTZIPTARREADERSTATE_FIRST:
+ pThis->Hdr.Common.typeflag = 0x7f;
+ pThis->enmPrevType = pThis->enmType;
+ pThis->enmType = RTZIPTARTYPE_INVALID;
+ pThis->offGnuLongCur = 0;
+ pThis->cbGnuLongExpect = 0;
+ pThis->szName[0] = '\0';
+ pThis->szTarget[0] = '\0';
+ return rtZipTarReaderParseNextHeader(pThis, pHdr, true /*fFirst*/);
+
+ /*
+ * There should only be so many zero headers at the end of the file as
+ * it is a function of the block size used when writing. Don't go on
+ * reading them forever in case someone points us to /dev/zero.
+ */
+ case RTZIPTARREADERSTATE_ZERO:
+ if (!ASMMemIsZero(pHdr, sizeof(*pHdr)))
+ return VERR_TAR_ZERO_HEADER;
+ pThis->cZeroHdrs++;
+ if (pThis->cZeroHdrs <= _64K / 512 + 2)
+ return VINF_SUCCESS;
+ return VERR_TAR_ZERO_HEADER;
+
+ case RTZIPTARREADERSTATE_GNU_LONGNAME:
+ case RTZIPTARREADERSTATE_GNU_LONGLINK:
+ {
+ size_t cbIncoming = RTStrNLen((const char *)pHdr->ab, sizeof(*pHdr));
+ if (cbIncoming < sizeof(*pHdr))
+ cbIncoming += 1;
+
+ if (cbIncoming + pThis->offGnuLongCur > pThis->cbGnuLongExpect)
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+ if ( cbIncoming < sizeof(*pHdr)
+ && cbIncoming + pThis->offGnuLongCur != pThis->cbGnuLongExpect)
+ return VERR_TAR_MALFORMED_GNU_LONGXXXX;
+
+ char *pszDst = pThis->enmState == RTZIPTARREADERSTATE_GNU_LONGNAME ? pThis->szName : pThis->szTarget;
+ pszDst += pThis->offGnuLongCur;
+ memcpy(pszDst, pHdr->ab, cbIncoming);
+
+ pThis->offGnuLongCur += (uint32_t)cbIncoming;
+ if (pThis->offGnuLongCur == pThis->cbGnuLongExpect)
+ pThis->enmState = RTZIPTARREADERSTATE_GNU_NEXT;
+ return VINF_SUCCESS;
+ }
+
+ case RTZIPTARREADERSTATE_GNU_NEXT:
+ pThis->enmState = RTZIPTARREADERSTATE_FIRST;
+ return rtZipTarReaderParseNextHeader(pThis, pHdr, false /*fFirst*/);
+
+ default:
+ return VERR_INTERNAL_ERROR_5;
+ }
+}
+
+
+/**
+ * Translate a TAR header to an IPRT object info structure with additional UNIX
+ * attributes.
+ *
+ * This completes the validation done by rtZipTarHdrValidate.
+ *
+ * @returns VINF_SUCCESS if valid, appropriate VERR_TAR_XXX if not.
+ * @param pThis The TAR reader instance.
+ * @param pObjInfo The object info structure (output).
+ */
+static int rtZipTarReaderGetFsObjInfo(PRTZIPTARREADER pThis, PRTFSOBJINFO pObjInfo)
+{
+ /*
+ * Zap the whole structure, this takes care of unused space in the union.
+ */
+ RT_ZERO(*pObjInfo);
+
+ /*
+ * Convert the TAR field in RTFSOBJINFO order.
+ */
+ int rc;
+ int64_t i64Tmp;
+#define GET_TAR_NUMERIC_FIELD_RET(a_Var, a_Field) \
+ do { \
+ rc = rtZipTarHdrFieldToNum(a_Field, sizeof(a_Field), false /*fOctalOnly*/, &i64Tmp); \
+ if (RT_FAILURE(rc)) \
+ return rc; \
+ (a_Var) = i64Tmp; \
+ if ((a_Var) != i64Tmp) \
+ return VERR_TAR_NUM_VALUE_TOO_LARGE; \
+ } while (0)
+
+ GET_TAR_NUMERIC_FIELD_RET(pObjInfo->cbObject, pThis->Hdr.Common.size);
+ pObjInfo->cbAllocated = RT_ALIGN_64(pObjInfo->cbObject, 512);
+ int64_t c64SecModTime;
+ GET_TAR_NUMERIC_FIELD_RET(c64SecModTime, pThis->Hdr.Common.mtime);
+ RTTimeSpecSetSeconds(&pObjInfo->ChangeTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->ModificationTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->AccessTime, c64SecModTime);
+ RTTimeSpecSetSeconds(&pObjInfo->BirthTime, c64SecModTime);
+ if (c64SecModTime != RTTimeSpecGetSeconds(&pObjInfo->ModificationTime))
+ return VERR_TAR_NUM_VALUE_TOO_LARGE;
+ GET_TAR_NUMERIC_FIELD_RET(pObjInfo->Attr.fMode, pThis->Hdr.Common.mode);
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ GET_TAR_NUMERIC_FIELD_RET(pObjInfo->Attr.u.Unix.uid, pThis->Hdr.Common.uid);
+ GET_TAR_NUMERIC_FIELD_RET(pObjInfo->Attr.u.Unix.gid, pThis->Hdr.Common.gid);
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
+ pObjInfo->Attr.u.Unix.INodeId = 0;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ switch (pThis->enmType)
+ {
+ case RTZIPTARTYPE_POSIX:
+ case RTZIPTARTYPE_GNU:
+ if ( pThis->Hdr.Common.typeflag == RTZIPTAR_TF_CHR
+ || pThis->Hdr.Common.typeflag == RTZIPTAR_TF_BLK)
+ {
+ uint32_t uMajor, uMinor;
+ GET_TAR_NUMERIC_FIELD_RET(uMajor, pThis->Hdr.Common.devmajor);
+ GET_TAR_NUMERIC_FIELD_RET(uMinor, pThis->Hdr.Common.devminor);
+ pObjInfo->Attr.u.Unix.Device = RTDEV_MAKE(uMajor, uMinor);
+ if ( uMajor != RTDEV_MAJOR(pObjInfo->Attr.u.Unix.Device)
+ || uMinor != RTDEV_MINOR(pObjInfo->Attr.u.Unix.Device))
+ return VERR_TAR_DEV_VALUE_TOO_LARGE;
+ }
+ break;
+
+ default:
+ if ( pThis->Hdr.Common.typeflag == RTZIPTAR_TF_CHR
+ || pThis->Hdr.Common.typeflag == RTZIPTAR_TF_BLK)
+ return VERR_TAR_UNKNOWN_TYPE_FLAG;
+ }
+
+#undef GET_TAR_NUMERIC_FIELD_RET
+
+ /*
+ * Massage the result a little bit.
+ * Also validate some more now that we've got the numbers to work with.
+ */
+ if ( (pObjInfo->Attr.fMode & ~RTFS_UNIX_MASK)
+ && pThis->enmType == RTZIPTARTYPE_POSIX)
+ return VERR_TAR_BAD_MODE_FIELD;
+ pObjInfo->Attr.fMode &= RTFS_UNIX_MASK;
+
+ RTFMODE fModeType = 0;
+ switch (pThis->Hdr.Common.typeflag)
+ {
+ case RTZIPTAR_TF_OLDNORMAL:
+ case RTZIPTAR_TF_NORMAL:
+ case RTZIPTAR_TF_CONTIG:
+ {
+ const char *pszEnd = strchr(pThis->szName, '\0');
+ if (pszEnd == &pThis->szName[0] || pszEnd[-1] != '/')
+ fModeType |= RTFS_TYPE_FILE;
+ else
+ fModeType |= RTFS_TYPE_DIRECTORY;
+ break;
+ }
+
+ case RTZIPTAR_TF_LINK:
+ if (pObjInfo->cbObject != 0)
+#if 0 /* too strict */
+ return VERR_TAR_SIZE_NOT_ZERO;
+#else
+ pObjInfo->cbObject = pObjInfo->cbAllocated = 0;
+#endif
+ fModeType |= RTFS_TYPE_FILE; /* no better idea for now */
+ break;
+
+ case RTZIPTAR_TF_SYMLINK:
+ fModeType |= RTFS_TYPE_SYMLINK;
+ break;
+
+ case RTZIPTAR_TF_CHR:
+ fModeType |= RTFS_TYPE_DEV_CHAR;
+ break;
+
+ case RTZIPTAR_TF_BLK:
+ fModeType |= RTFS_TYPE_DEV_BLOCK;
+ break;
+
+ case RTZIPTAR_TF_DIR:
+ fModeType |= RTFS_TYPE_DIRECTORY;
+ break;
+
+ case RTZIPTAR_TF_FIFO:
+ fModeType |= RTFS_TYPE_FIFO;
+ break;
+
+ case RTZIPTAR_TF_GNU_LONGLINK:
+ case RTZIPTAR_TF_GNU_LONGNAME:
+ /* ASSUMES RTFS_TYPE_XXX uses the same values as GNU stored in the mode field. */
+ fModeType = pObjInfo->Attr.fMode & RTFS_TYPE_MASK;
+ switch (fModeType)
+ {
+ case RTFS_TYPE_FILE:
+ case RTFS_TYPE_DIRECTORY:
+ case RTFS_TYPE_SYMLINK:
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_FIFO:
+ break;
+
+ default:
+ case 0:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG; /** @todo new status code */
+ }
+ break;
+
+ default:
+ return VERR_TAR_UNKNOWN_TYPE_FLAG; /* Should've been caught in validate. */
+ }
+ if ( (pObjInfo->Attr.fMode & RTFS_TYPE_MASK)
+ && (pObjInfo->Attr.fMode & RTFS_TYPE_MASK) != fModeType)
+ return VERR_TAR_MODE_WITH_TYPE;
+ pObjInfo->Attr.fMode &= ~RTFS_TYPE_MASK;
+ pObjInfo->Attr.fMode |= fModeType;
+
+ switch (pThis->Hdr.Common.typeflag)
+ {
+ case RTZIPTAR_TF_CHR:
+ case RTZIPTAR_TF_BLK:
+ case RTZIPTAR_TF_DIR:
+ case RTZIPTAR_TF_FIFO:
+ pObjInfo->cbObject = 0;
+ pObjInfo->cbAllocated = 0;
+ break;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Checks if the reader is expecting more headers.
+ *
+ * @returns true / false.
+ * @param pThis The TAR reader instance.
+ */
+static bool rtZipTarReaderExpectingMoreHeaders(PRTZIPTARREADER pThis)
+{
+ return pThis->enmState != RTZIPTARREADERSTATE_FIRST;
+}
+
+
+/**
+ * Checks if we're at the end of the TAR file.
+ *
+ * @returns true / false.
+ * @param pThis The TAR reader instance.
+ */
+static bool rtZipTarReaderIsAtEnd(PRTZIPTARREADER pThis)
+{
+ /*
+ * In theory there shall always be two zero headers at the end of the
+ * archive, but life isn't that simple. We've been creating archives
+ * without any zero headers at the end ourselves for a long long time
+ * (old tar.cpp).
+ *
+ * So, we're fine if the state is 'FIRST' or 'ZERO' here, but we'll barf
+ * if we're in the middle of a multi-header stream (long GNU names, sparse
+ * files, PAX, etc).
+ */
+ return pThis->enmState == RTZIPTARREADERSTATE_FIRST
+ || pThis->enmState == RTZIPTARREADERSTATE_ZERO;
+}
+
+
+/**
+ * Checks if the current TAR object is a hard link or not.
+ *
+ * @returns true if it is, false if not.
+ * @param pThis The TAR reader instance.
+ */
+static bool rtZipTarReaderIsHardlink(PRTZIPTARREADER pThis)
+{
+ return pThis->Hdr.Common.typeflag == RTZIPTAR_TF_LINK;
+}
+
+
+/**
+ * Checks if the TAR header includes a POSIX or GNU user name field.
+ *
+ * @returns true / false.
+ * @param pThis The TAR reader instance.
+ */
+DECLINLINE(bool) rtZipTarReaderHasUserName(PRTZIPTARREADER pThis)
+{
+ return pThis->Hdr.Common.uname[0] != '\0'
+ && ( pThis->enmType == RTZIPTARTYPE_POSIX
+ || pThis->enmType == RTZIPTARTYPE_GNU);
+}
+
+
+/**
+ * Checks if the TAR header includes a POSIX or GNU group name field.
+ *
+ * @returns true / false.
+ * @param pThis The TAR reader instance.
+ */
+DECLINLINE(bool) rtZipTarReaderHasGroupName(PRTZIPTARREADER pThis)
+{
+ return pThis->Hdr.Common.gname[0] != '\0'
+ && ( pThis->enmType == RTZIPTARTYPE_POSIX
+ || pThis->enmType == RTZIPTARTYPE_GNU);
+}
+
+
+/*
+ *
+ * T h e V F S F i l e s y s t e m S t r e a m B i t s.
+ * T h e V F S F i l e s y s t e m S t r e a m B i t s.
+ * T h e V F S F i l e s y s t e m S t r e a m B i t s.
+ *
+ */
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarFssBaseObj_Close(void *pvThis)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+
+ /* Currently there is nothing we really have to do here. */
+ pThis->offHdr = -1;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFssBaseObj_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+
+ /*
+ * Copy the desired data.
+ */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ *pObjInfo = pThis->ObjInfo;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER;
+ pObjInfo->Attr.u.UnixOwner.uid = pThis->ObjInfo.Attr.u.Unix.uid;
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ if (rtZipTarReaderHasUserName(pThis->pTarReader))
+ RTStrCopy(pObjInfo->Attr.u.UnixOwner.szName, sizeof(pObjInfo->Attr.u.UnixOwner.szName),
+ pThis->pTarReader->Hdr.Common.uname);
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP;
+ pObjInfo->Attr.u.UnixGroup.gid = pThis->ObjInfo.Attr.u.Unix.gid;
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ if (rtZipTarReaderHasGroupName(pThis->pTarReader))
+ RTStrCopy(pObjInfo->Attr.u.UnixGroup.szName, sizeof(pObjInfo->Attr.u.UnixGroup.szName),
+ pThis->pTarReader->Hdr.Common.gname);
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ *pObjInfo = pThis->ObjInfo;
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE;
+ RT_ZERO(pObjInfo->Attr.u);
+ break;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tar filesystem base object operations.
+ */
+static const RTVFSOBJOPS g_rtZipTarFssBaseObjOps =
+{
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_BASE,
+ "TarFsStream::Obj",
+ rtZipTarFssBaseObj_Close,
+ rtZipTarFssBaseObj_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_Close(void *pvThis)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)pvThis;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return rtZipTarFssBaseObj_Close(&pThis->BaseObj);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)pvThis;
+ return rtZipTarFssBaseObj_QueryInfo(&pThis->BaseObj, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)pvThis;
+ Assert(pSgBuf->cSegs == 1);
+
+ /*
+ * Make offset into a real offset so it's possible to do random access
+ * on TAR files that are seekable. Fend of reads beyond the end of the
+ * stream.
+ */
+ if (off < 0)
+ off = pThis->offFile;
+ if (off >= pThis->cbFile)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+
+
+ Assert(pThis->cbFile >= pThis->offFile);
+ uint64_t cbLeft = (uint64_t)(pThis->cbFile - off);
+ size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
+ if (cbToRead > cbLeft)
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ cbToRead = (size_t)cbLeft;
+ }
+
+ /*
+ * Do the reading.
+ */
+ size_t cbReadStack = 0;
+ if (!pcbRead)
+ pcbRead = &cbReadStack;
+ int rc = RTVfsIoStrmReadAt(pThis->hVfsIos, pThis->offStart + off, pSgBuf->paSegs[0].pvSeg, cbToRead, fBlocking, pcbRead);
+ pThis->offFile = off + *pcbRead;
+ if (pThis->offFile >= pThis->cbFile)
+ {
+ Assert(pThis->offFile == pThis->cbFile);
+ pThis->fEndOfStream = true;
+ RTVfsIoStrmSkip(pThis->hVfsIos, pThis->cbPadding);
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ /* Cannot write to a read-only I/O stream. */
+ NOREF(pvThis); NOREF(off); NOREF(pSgBuf); NOREF(fBlocking); NOREF(pcbWritten);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_Flush(void *pvThis)
+{
+ /* It's a read only stream, nothing dirty to flush. */
+ NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)pvThis;
+
+ /* When we've reached the end, read will be set to indicate it. */
+ if ( (fEvents & RTPOLL_EVT_READ)
+ && pThis->fEndOfStream)
+ {
+ int rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, 0, fIntr, pfRetEvents);
+ if (RT_SUCCESS(rc))
+ *pfRetEvents |= RTPOLL_EVT_READ;
+ else
+ *pfRetEvents = RTPOLL_EVT_READ;
+ return VINF_SUCCESS;
+ }
+
+ return RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtZipTarFssIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPTARIOSTREAM pThis = (PRTZIPTARIOSTREAM)pvThis;
+ *poffActual = pThis->offFile;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Tar I/O stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_rtZipTarFssIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "TarFsStream::IoStream",
+ rtZipTarFssIos_Close,
+ rtZipTarFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipTarFssIos_Read,
+ rtZipTarFssIos_Write,
+ rtZipTarFssIos_Flush,
+ rtZipTarFssIos_PollOne,
+ rtZipTarFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarFssSym_Close(void *pvThis)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+ return rtZipTarFssBaseObj_Close(pThis);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFssSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+ return rtZipTarFssBaseObj_QueryInfo(pThis, pObjInfo, enmAddAttr);
+}
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtZipTarFssSym_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ NOREF(pvThis); NOREF(fMode); NOREF(fMask);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtZipTarFssSym_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ NOREF(pvThis); NOREF(pAccessTime); NOREF(pModificationTime); NOREF(pChangeTime); NOREF(pBirthTime);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtZipTarFssSym_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ NOREF(pvThis); NOREF(uid); NOREF(gid);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSSYMLINKOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipTarFssSym_Read(void *pvThis, char *pszTarget, size_t cbTarget)
+{
+ PRTZIPTARBASEOBJ pThis = (PRTZIPTARBASEOBJ)pvThis;
+ return RTStrCopy(pszTarget, cbTarget, pThis->pTarReader->szTarget);
+}
+
+
+/**
+ * Tar symbolic (and hardlink) operations.
+ */
+static const RTVFSSYMLINKOPS g_rtZipTarFssSymOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_SYMLINK,
+ "TarFsStream::Symlink",
+ rtZipTarFssSym_Close,
+ rtZipTarFssSym_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSSYMLINKOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj),
+ rtZipTarFssSym_SetMode,
+ rtZipTarFssSym_SetTimes,
+ rtZipTarFssSym_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtZipTarFssSym_Read,
+ RTVFSSYMLINKOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarFss_Close(void *pvThis)
+{
+ PRTZIPTARFSSTREAM pThis = (PRTZIPTARFSSTREAM)pvThis;
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFss_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARFSSTREAM pThis = (PRTZIPTARFSSTREAM)pvThis;
+ /* Take the lazy approach here, with the sideffect of providing some info
+ that is actually kind of useful. */
+ return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnNext}
+ */
+DECL_HIDDEN_CALLBACK(int) rtZipTarFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ PRTZIPTARFSSTREAM pThis = (PRTZIPTARFSSTREAM)pvThis;
+
+ /*
+ * Dispense with the current object.
+ */
+ if (pThis->hVfsCurObj != NIL_RTVFSOBJ)
+ {
+ if (pThis->pCurIosData)
+ {
+ pThis->pCurIosData->fEndOfStream = true;
+ pThis->pCurIosData->offFile = pThis->pCurIosData->cbFile;
+ pThis->pCurIosData = NULL;
+ }
+
+ RTVfsObjRelease(pThis->hVfsCurObj);
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ }
+
+ /*
+ * Check if we've already reached the end in some way.
+ */
+ if (pThis->fEndOfStream)
+ return VERR_EOF;
+ if (pThis->rcFatal != VINF_SUCCESS)
+ return pThis->rcFatal;
+
+ /*
+ * Make sure the input stream is in the right place.
+ */
+ RTFOFF offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ while ( offHdr >= 0
+ && offHdr < pThis->offNextHdr)
+ {
+ int rc = RTVfsIoStrmSkip(pThis->hVfsIos, pThis->offNextHdr - offHdr);
+ if (RT_FAILURE(rc))
+ {
+ /** @todo Ignore if we're at the end of the stream? */
+ return pThis->rcFatal = rc;
+ }
+
+ offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ }
+
+ if (offHdr < 0)
+ return pThis->rcFatal = (int)offHdr;
+ if (offHdr > pThis->offNextHdr)
+ return pThis->rcFatal = VERR_INTERNAL_ERROR_3;
+ Assert(pThis->offNextHdr == offHdr);
+ pThis->offCurHdr = offHdr;
+
+ /*
+ * Consume TAR headers.
+ */
+ size_t cbHdrs = 0;
+ int rc;
+ do
+ {
+ /*
+ * Read the next header.
+ */
+ RTZIPTARHDR Hdr;
+ size_t cbRead;
+ rc = RTVfsIoStrmRead(pThis->hVfsIos, &Hdr, sizeof(Hdr), true /*fBlocking*/, &cbRead);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ if (rc == VINF_EOF && cbRead == 0)
+ {
+ pThis->fEndOfStream = true;
+ return rtZipTarReaderIsAtEnd(&pThis->TarReader) ? VERR_EOF : VERR_TAR_UNEXPECTED_EOS;
+ }
+ if (cbRead != sizeof(Hdr))
+ return pThis->rcFatal = VERR_TAR_UNEXPECTED_EOS;
+
+ cbHdrs += sizeof(Hdr);
+
+ /*
+ * Parse the it.
+ */
+ rc = rtZipTarReaderParseHeader(&pThis->TarReader, &Hdr);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ } while (rtZipTarReaderExpectingMoreHeaders(&pThis->TarReader));
+
+ pThis->offNextHdr = offHdr + cbHdrs;
+
+ /*
+ * Fill an object info structure from the current TAR state.
+ */
+ RTFSOBJINFO Info;
+ rc = rtZipTarReaderGetFsObjInfo(&pThis->TarReader, &Info);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ /*
+ * Create an object of the appropriate type.
+ */
+ RTVFSOBJTYPE enmType;
+ RTVFSOBJ hVfsObj;
+ RTFMODE fType = Info.Attr.fMode & RTFS_TYPE_MASK;
+ if (rtZipTarReaderIsHardlink(&pThis->TarReader))
+ fType = RTFS_TYPE_SYMLINK;
+ switch (fType)
+ {
+ /*
+ * Files are represented by a VFS I/O stream.
+ */
+ case RTFS_TYPE_FILE:
+ {
+ RTVFSIOSTREAM hVfsIos;
+ PRTZIPTARIOSTREAM pIosData;
+ rc = RTVfsNewIoStream(&g_rtZipTarFssIosOps,
+ sizeof(*pIosData),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIos,
+ (void **)&pIosData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pIosData->BaseObj.offHdr = offHdr;
+ pIosData->BaseObj.offNextHdr = pThis->offNextHdr;
+ pIosData->BaseObj.pTarReader = &pThis->TarReader;
+ pIosData->BaseObj.ObjInfo = Info;
+ pIosData->cbFile = Info.cbObject;
+ pIosData->offFile = 0;
+ pIosData->offStart = RTVfsIoStrmTell(pThis->hVfsIos);
+ pIosData->cbPadding = (uint32_t)(Info.cbAllocated - Info.cbObject);
+ pIosData->fEndOfStream = false;
+ pIosData->hVfsIos = pThis->hVfsIos;
+ RTVfsIoStrmRetain(pThis->hVfsIos);
+
+ pThis->pCurIosData = pIosData;
+ pThis->offNextHdr += pIosData->cbFile + pIosData->cbPadding;
+
+ enmType = RTVFSOBJTYPE_IO_STREAM;
+ hVfsObj = RTVfsObjFromIoStream(hVfsIos);
+ RTVfsIoStrmRelease(hVfsIos);
+ break;
+ }
+
+ /*
+ * We represent hard links using a symbolic link object. This fits
+ * best with the way TAR stores it and there is currently no better
+ * fitting VFS type alternative.
+ */
+ case RTFS_TYPE_SYMLINK:
+ {
+ RTVFSSYMLINK hVfsSym;
+ PRTZIPTARBASEOBJ pBaseObjData;
+ rc = RTVfsNewSymlink(&g_rtZipTarFssSymOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsSym,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->offHdr = offHdr;
+ pBaseObjData->offNextHdr = pThis->offNextHdr;
+ pBaseObjData->pTarReader = &pThis->TarReader;
+ pBaseObjData->ObjInfo = Info;
+
+ enmType = RTVFSOBJTYPE_SYMLINK;
+ hVfsObj = RTVfsObjFromSymlink(hVfsSym);
+ RTVfsSymlinkRelease(hVfsSym);
+ break;
+ }
+
+ /*
+ * All other objects are repesented using a VFS base object since they
+ * carry no data streams (unless some TAR extension implements extended
+ * attributes / alternative streams).
+ */
+ case RTFS_TYPE_DEV_BLOCK:
+ case RTFS_TYPE_DEV_CHAR:
+ case RTFS_TYPE_DIRECTORY:
+ case RTFS_TYPE_FIFO:
+ {
+ PRTZIPTARBASEOBJ pBaseObjData;
+ rc = RTVfsNewBaseObj(&g_rtZipTarFssBaseObjOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsObj,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->offHdr = offHdr;
+ pBaseObjData->offNextHdr = pThis->offNextHdr;
+ pBaseObjData->pTarReader = &pThis->TarReader;
+ pBaseObjData->ObjInfo = Info;
+
+ enmType = RTVFSOBJTYPE_BASE;
+ break;
+ }
+
+ default:
+ AssertFailed();
+ return pThis->rcFatal = VERR_INTERNAL_ERROR_5;
+ }
+ pThis->hVfsCurObj = hVfsObj;
+
+ /*
+ * Set the return data and we're done.
+ */
+ if (ppszName)
+ {
+ rc = RTStrDupEx(ppszName, pThis->TarReader.szName);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (phVfsObj)
+ {
+ RTVfsObjRetain(hVfsObj);
+ *phVfsObj = hVfsObj;
+ }
+
+ if (penmType)
+ *penmType = enmType;
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Tar filesystem stream operations.
+ */
+static const RTVFSFSSTREAMOPS rtZipTarFssOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "TarFsStream",
+ rtZipTarFss_Close,
+ rtZipTarFss_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipTarFss_Next,
+ NULL,
+ NULL,
+ NULL,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+/**
+ * Internal function use both by RTZipTarFsStreamFromIoStream() and by
+ * RTZipTarFsStreamForFile() in updating mode.
+ */
+DECLHIDDEN(void) rtZipTarReaderInit(PRTZIPTARFSSTREAM pThis, RTVFSIOSTREAM hVfsIos, uint64_t offStart)
+{
+ pThis->hVfsIos = hVfsIos;
+ pThis->hVfsCurObj = NIL_RTVFSOBJ;
+ pThis->pCurIosData = NULL;
+ pThis->offStart = offStart;
+ pThis->offNextHdr = offStart;
+ pThis->fEndOfStream = false;
+ pThis->rcFatal = VINF_SUCCESS;
+ pThis->TarReader.enmPrevType= RTZIPTARTYPE_INVALID;
+ pThis->TarReader.enmType = RTZIPTARTYPE_INVALID;
+ pThis->TarReader.enmState = RTZIPTARREADERSTATE_FIRST;
+
+ /* Don't check if it's a TAR stream here, do that in the
+ rtZipTarFss_Next. */
+}
+
+
+RTDECL(int) RTZipTarFsStreamFromIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertPtrReturn(hVfsIosIn, VERR_INVALID_HANDLE);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+
+ RTFOFF const offStart = RTVfsIoStrmTell(hVfsIosIn);
+ AssertReturn(offStart >= 0, (int)offStart);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPTARFSSTREAM pThis;
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&rtZipTarFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_READ,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ rtZipTarReaderInit(pThis, hVfsIosIn, fFlags);
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosIn);
+ return rc;
+}
+
+
+/**
+ * Used by RTZipTarFsStreamTruncate to resolve @a hVfsObj.
+ */
+DECLHIDDEN(PRTZIPTARBASEOBJ) rtZipTarFsStreamBaseObjToPrivate(PRTZIPTARFSSTREAM pThis, RTVFSOBJ hVfsObj)
+{
+ PRTZIPTARBASEOBJ pThisObj;
+ RTVFSOBJTYPE enmType = RTVfsObjGetType(hVfsObj);
+ switch (enmType)
+ {
+ case RTVFSOBJTYPE_IO_STREAM:
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, NULL);
+ PRTZIPTARIOSTREAM pThisStrm = (PRTZIPTARIOSTREAM)RTVfsIoStreamToPrivate(hVfsIos, &g_rtZipTarFssIosOps);
+ RTVfsIoStrmRelease(hVfsIos);
+ pThisObj = &pThisStrm->BaseObj;
+ break;
+ }
+
+ case RTVFSOBJTYPE_SYMLINK:
+ {
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, NULL);
+ pThisObj = (PRTZIPTARBASEOBJ)RTVfsSymlinkToPrivate(hVfsSymlink, &g_rtZipTarFssSymOps);
+ RTVfsSymlinkRelease(hVfsSymlink);
+ break;
+ }
+
+ case RTVFSOBJTYPE_BASE:
+ pThisObj = (PRTZIPTARBASEOBJ)RTVfsObjToPrivate(hVfsObj, &g_rtZipTarFssBaseObjOps);
+ break;
+
+ default:
+ /** @todo implement. */
+ AssertFailedReturn(NULL);
+ }
+
+ AssertReturn(pThisObj->pTarReader == &pThis->TarReader, NULL);
+ return pThisObj;
+}
+
diff --git a/src/VBox/Runtime/common/zip/tarvfsreader.h b/src/VBox/Runtime/common/zip/tarvfsreader.h
new file mode 100644
index 00000000..ebe74fbc
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tarvfsreader.h
@@ -0,0 +1,179 @@
+/* $Id: tarvfsreader.h $ */
+/** @file
+ * IPRT - TAR Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+#ifndef IPRT_INCLUDED_SRC_common_zip_tarvfsreader_h
+#define IPRT_INCLUDED_SRC_common_zip_tarvfsreader_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include "tar.h"
+
+
+/**
+ * TAR reader state machine states.
+ */
+typedef enum RTZIPTARREADERSTATE
+{
+ /** Invalid state. */
+ RTZIPTARREADERSTATE_INVALID = 0,
+ /** Expecting the next file/dir/whatever entry. */
+ RTZIPTARREADERSTATE_FIRST,
+ /** Expecting more zero headers or the end of the stream. */
+ RTZIPTARREADERSTATE_ZERO,
+ /** Expecting a GNU long name. */
+ RTZIPTARREADERSTATE_GNU_LONGNAME,
+ /** Expecting a GNU long link. */
+ RTZIPTARREADERSTATE_GNU_LONGLINK,
+ /** Expecting a normal header or another GNU specific one. */
+ RTZIPTARREADERSTATE_GNU_NEXT,
+ /** End of valid states (not included). */
+ RTZIPTARREADERSTATE_END
+} RTZIPTARREADERSTATE;
+
+/**
+ * Tar reader instance data.
+ */
+typedef struct RTZIPTARREADER
+{
+ /** Zero header counter. */
+ uint32_t cZeroHdrs;
+ /** The state machine state. */
+ RTZIPTARREADERSTATE enmState;
+ /** The type of the previous TAR header.
+ * @remarks Same a enmType for the first header in the TAR stream. */
+ RTZIPTARTYPE enmPrevType;
+ /** The type of the current TAR header. */
+ RTZIPTARTYPE enmType;
+ /** The current header. */
+ RTZIPTARHDR Hdr;
+ /** The expected long name/link length (GNU). */
+ uint32_t cbGnuLongExpect;
+ /** The current long name/link length (GNU). */
+ uint32_t offGnuLongCur;
+ /** The name of the current object.
+ * This is for handling GNU and PAX long names. */
+ char szName[RTPATH_MAX];
+ /** The current link target if symlink or hardlink. */
+ char szTarget[RTPATH_MAX];
+} RTZIPTARREADER;
+/** Pointer to the TAR reader instance data. */
+typedef RTZIPTARREADER *PRTZIPTARREADER;
+
+/**
+ * Tar directory, character device, block device, fifo socket or symbolic link.
+ */
+typedef struct RTZIPTARBASEOBJ
+{
+ /** The stream offset of the (first) header in the input stream/file. */
+ RTFOFF offHdr;
+ /** The stream offset of the first header of the next object (for truncating the
+ * tar file after this object (updating)). */
+ RTFOFF offNextHdr;
+ /** Pointer to the reader instance data (resides in the filesystem
+ * stream).
+ * @todo Fix this so it won't go stale... Back ref from this obj to fss? */
+ PRTZIPTARREADER pTarReader;
+ /** The object info with unix attributes. */
+ RTFSOBJINFO ObjInfo;
+} RTZIPTARBASEOBJ;
+/** Pointer to a TAR filesystem stream base object. */
+typedef RTZIPTARBASEOBJ *PRTZIPTARBASEOBJ;
+
+
+/**
+ * Tar file represented as a VFS I/O stream.
+ */
+typedef struct RTZIPTARIOSTREAM
+{
+ /** The basic TAR object data. */
+ RTZIPTARBASEOBJ BaseObj;
+ /** The number of bytes in the file. */
+ RTFOFF cbFile;
+ /** The current file position. */
+ RTFOFF offFile;
+ /** The start position in the hVfsIos (for seekable hVfsIos). */
+ RTFOFF offStart;
+ /** The number of padding bytes following the file. */
+ uint32_t cbPadding;
+ /** Set if we've reached the end of this file. */
+ bool fEndOfStream;
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+} RTZIPTARIOSTREAM;
+/** Pointer to a the private data of a TAR file I/O stream. */
+typedef RTZIPTARIOSTREAM *PRTZIPTARIOSTREAM;
+
+
+/**
+ * Tar filesystem stream private data.
+ */
+typedef struct RTZIPTARFSSTREAM
+{
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+
+ /** The current object (referenced). */
+ RTVFSOBJ hVfsCurObj;
+ /** Pointer to the private data if hVfsCurObj is representing a file. */
+ PRTZIPTARIOSTREAM pCurIosData;
+
+ /** The start offset. */
+ RTFOFF offStart;
+ /** The offset of the next header. */
+ RTFOFF offNextHdr;
+ /** The offset of the first header for the current object.
+ * When reaching the end, this will be the same as offNextHdr which will be
+ * pointing to the first zero header */
+ RTFOFF offCurHdr;
+
+ /** Set if we've reached the end of the stream. */
+ bool fEndOfStream;
+ /** Set if we've encountered a fatal error. */
+ int rcFatal;
+
+ /** The TAR reader instance data. */
+ RTZIPTARREADER TarReader;
+} RTZIPTARFSSTREAM;
+/** Pointer to a the private data of a TAR filesystem stream. */
+typedef RTZIPTARFSSTREAM *PRTZIPTARFSSTREAM;
+
+DECLHIDDEN(void) rtZipTarReaderInit(PRTZIPTARFSSTREAM pThis, RTVFSIOSTREAM hVfsIos, uint64_t offStart);
+DECL_HIDDEN_CALLBACK(int) rtZipTarFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj);
+DECLHIDDEN(PRTZIPTARBASEOBJ) rtZipTarFsStreamBaseObjToPrivate(PRTZIPTARFSSTREAM pThis, RTVFSOBJ hVfsObj);
+
+#endif /* !IPRT_INCLUDED_SRC_common_zip_tarvfsreader_h */
+
diff --git a/src/VBox/Runtime/common/zip/tarvfswriter.cpp b/src/VBox/Runtime/common/zip/tarvfswriter.cpp
new file mode 100644
index 00000000..ac5d603a
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/tarvfswriter.cpp
@@ -0,0 +1,2363 @@
+/* $Id: tarvfswriter.cpp $ */
+/** @file
+ * IPRT - TAR Virtual Filesystem, Writer.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/zero.h>
+
+#include "tarvfsreader.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The TAR block size we're using in this implementation.
+ * @remarks Should technically be user configurable, but we don't currently need that. */
+#define RTZIPTAR_BLOCKSIZE sizeof(RTZIPTARHDR)
+
+/** Minimum file size we consider for sparse files. */
+#define RTZIPTAR_MIN_SPARSE _64K
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * A data span descriptor in a sparse file.
+ */
+typedef struct RTZIPTARSPARSESPAN
+{
+ /** Byte offset into the file of the data. */
+ uint64_t off;
+ /** Number of bytes of data, rounded up to a multiple of blocksize. */
+ uint64_t cb;
+} RTZIPTARSPARSESPAN;
+/** Pointer to a data span. */
+typedef RTZIPTARSPARSESPAN *PRTZIPTARSPARSESPAN;
+/** Pointer to a const data span. */
+typedef RTZIPTARSPARSESPAN const *PCRTZIPTARSPARSESPAN;
+
+/**
+ * Chunk of TAR sparse file data spans.
+ */
+typedef struct RTZIPTARSPARSECHUNK
+{
+ /** List entry. */
+ RTLISTNODE Entry;
+ /** Array of data spans. */
+ RTZIPTARSPARSESPAN aSpans[63];
+} RTZIPTARSPARSECHUNK;
+AssertCompile(sizeof(RTZIPTARSPARSECHUNK) <= 1024);
+AssertCompile(sizeof(RTZIPTARSPARSECHUNK) >= 1008);
+/** Pointer to a chunk of TAR data spans. */
+typedef RTZIPTARSPARSECHUNK *PRTZIPTARSPARSECHUNK;
+/** Pointer to a const chunk of TAR data spans. */
+typedef RTZIPTARSPARSECHUNK const *PCRTZIPTARSPARSECHUNK;
+
+/**
+ * TAR sparse file info.
+ */
+typedef struct RTZIPTARSPARSE
+{
+ /** Number of data bytes (real size). */
+ uint64_t cbDataSpans;
+ /** Number of data spans. */
+ uint32_t cDataSpans;
+ /** The index of the next span in the tail chunk (to avoid modulus 63). */
+ uint32_t iNextSpan;
+ /** Head of the data span chunk list (PRTZIPTARSPARSECHUNK). */
+ RTLISTANCHOR ChunkHead;
+} RTZIPTARSPARSE;
+/** Pointer to TAR sparse file info. */
+typedef RTZIPTARSPARSE *PRTZIPTARSPARSE;
+/** Pointer to a const TAR sparse file info. */
+typedef RTZIPTARSPARSE const *PCRTZIPTARSPARSE;
+
+
+/** Pointer to a the private data of a TAR filesystem stream. */
+typedef struct RTZIPTARFSSTREAMWRITER *PRTZIPTARFSSTREAMWRITER;
+
+
+/**
+ * Instance data for a file or I/O stream returned by
+ * RTVFSFSSTREAMOPS::pfnPushFile.
+ */
+typedef struct RTZIPTARFSSTREAMWRITERPUSH
+{
+ /** Pointer to the parent FS stream writer instance.
+ * This is set to NULL should the push object live longer than the stream. */
+ PRTZIPTARFSSTREAMWRITER pParent;
+ /** The header offset, UINT64_MAX if non-seekable output. */
+ uint64_t offHdr;
+ /** The data offset, UINT64_MAX if non-seekable output. */
+ uint64_t offData;
+ /** The current I/O stream position (relative to offData). */
+ uint64_t offCurrent;
+ /** The expected size amount of file content, or max file size if open-ended. */
+ uint64_t cbExpected;
+ /** The current amount of file content written. */
+ uint64_t cbCurrent;
+ /** Object info copy for rtZipTarWriterPush_QueryInfo. */
+ RTFSOBJINFO ObjInfo;
+ /** Set if open-ended file size requiring a tar header update when done. */
+ bool fOpenEnded;
+} RTZIPTARFSSTREAMWRITERPUSH;
+/** Pointer to a push I/O instance. */
+typedef RTZIPTARFSSTREAMWRITERPUSH *PRTZIPTARFSSTREAMWRITERPUSH;
+
+
+/**
+ * Tar filesystem stream private data.
+ */
+typedef struct RTZIPTARFSSTREAMWRITER
+{
+ /** The output I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+ /** Non-nil if the output is a file. */
+ RTVFSFILE hVfsFile;
+
+ /** The current push file. NULL if none. */
+ PRTZIPTARFSSTREAMWRITERPUSH pPush;
+
+ /** The TAR format. */
+ RTZIPTARFORMAT enmFormat;
+ /** Set if we've encountered a fatal error. */
+ int rcFatal;
+ /** Flags, RTZIPTAR_C_XXX. */
+ uint32_t fFlags;
+
+ /** Number of bytes written. */
+ uint64_t cbWritten;
+
+ /** @name Attribute overrides.
+ * @{
+ */
+ RTUID uidOwner; /**< Owner, NIL_RTUID if no change. */
+ char *pszOwner; /**< Owner, NULL if no change. */
+ RTGID gidGroup; /**< Group, NIL_RTGID if no change. */
+ char *pszGroup; /**< Group, NULL if no change. */
+ char *pszPrefix; /**< Path prefix, NULL if no change. */
+ size_t cchPrefix; /**< The length of pszPrefix. */
+ PRTTIMESPEC pModTime; /**< Modification time, NULL of no change. */
+ RTTIMESPEC ModTime; /**< pModTime points to this. */
+ RTFMODE fFileModeAndMask; /**< File mode AND mask. */
+ RTFMODE fFileModeOrMask; /**< File mode OR mask. */
+ RTFMODE fDirModeAndMask; /**< Directory mode AND mask. */
+ RTFMODE fDirModeOrMask; /**< Directory mode OR mask. */
+ /** @} */
+
+ /** When in update mode (RTZIPTAR_C_UPDATE) we have an reader FSS instance,
+ * though w/o the RTVFSFSSTREAM bits. (Allocated after this structure.) */
+ PRTZIPTARFSSTREAM pRead;
+ /** Set if we're in writing mode and pfnNext shall fail. */
+ bool fWriting;
+
+
+ /** Number of headers returned by rtZipTarFssWriter_ObjInfoToHdr. */
+ uint32_t cHdrs;
+ /** Header buffers returned by rtZipTarFssWriter_ObjInfoToHdr. */
+ RTZIPTARHDR aHdrs[3];
+} RTZIPTARFSSTREAMWRITER;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static DECLCALLBACK(int) rtZipTarWriterPush_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual);
+static int rtZipTarFssWriter_CompleteCurrentPushFile(PRTZIPTARFSSTREAMWRITER pThis);
+static int rtZipTarFssWriter_AddFile(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos,
+ PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm);
+
+
+/**
+ * Calculates the header checksum and stores it in the chksum field.
+ *
+ * @returns IPRT status code.
+ * @param pHdr The header.
+ */
+static int rtZipTarFssWriter_ChecksumHdr(PRTZIPTARHDR pHdr)
+{
+ int32_t iUnsignedChksum;
+ rtZipTarCalcChkSum(pHdr, &iUnsignedChksum, NULL);
+
+ int rc = RTStrFormatU32(pHdr->Common.chksum, sizeof(pHdr->Common.chksum), iUnsignedChksum,
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pHdr->Common.chksum) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Formats a 12 character wide file offset or size field.
+ *
+ * This is mainly used for RTZIPTARHDR::Common.size, but also for formatting the
+ * sparse map.
+ *
+ * @returns IPRT status code.
+ * @param pach12Field The 12 character wide destination field.
+ * @param off The offset to set.
+ */
+static int rtZipTarFssWriter_FormatOffset(char pach12Field[12], uint64_t off)
+{
+ /*
+ * Is the size small enough for the standard octal string encoding?
+ *
+ * Note! We could actually use the terminator character as well if we liked,
+ * but let not do that as it's easier to test this way.
+ */
+ if (off < _4G * 2U)
+ {
+ int rc = RTStrFormatU64(pach12Field, 12, off, 8 /*uBase*/, -1 /*cchWidth*/, 12 - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, rc);
+ }
+ /*
+ * No, use the base 256 extension. Set the highest bit of the left most
+ * character. We don't deal with negatives here, cause the size have to
+ * be greater than zero.
+ *
+ * Note! The base-256 extension are never used by gtar or libarchive
+ * with the "ustar \0" format version, only the later
+ * "ustar\000" version. However, this shouldn't cause much
+ * trouble as they are not picky about what they read.
+ */
+ /** @todo above note is wrong: GNU tar only uses base-256 with the GNU tar
+ * format, i.e. "ustar \0", see create.c line 303 in v1.29. */
+ else
+ {
+ size_t cchField = 12 - 1;
+ unsigned char *puchField = (unsigned char *)pach12Field;
+ puchField[0] = 0x80;
+ do
+ {
+ puchField[cchField--] = off & 0xff;
+ off >>= 8;
+ } while (cchField);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Creates one or more tar headers for the object.
+ *
+ * Returns RTZIPTARFSSTREAMWRITER::aHdrs and RTZIPTARFSSTREAMWRITER::cHdrs.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param hVfsIos The I/O stream of the file.
+ * @param fFlags The RTVFSFSSTREAMOPS::pfnAdd flags.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ * @param chType The tar record type, UINT8_MAX for default.
+ */
+static int rtZipTarFssWriter_ObjInfoToHdr(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, PCRTFSOBJINFO pObjInfo,
+ const char *pszOwnerNm, const char *pszGroupNm, uint8_t chType)
+{
+ pThis->cHdrs = 0;
+ RT_ZERO(pThis->aHdrs[0]);
+
+ /*
+ * The path name first. Make sure to flip DOS slashes.
+ */
+ size_t cchPath = strlen(pszPath);
+ if (cchPath < sizeof(pThis->aHdrs[0].Common.name))
+ {
+ memcpy(pThis->aHdrs[0].Common.name, pszPath, cchPath + 1);
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_UNIX
+ char *pszDosSlash = strchr(pThis->aHdrs[0].Common.name, '\\');
+ while (pszDosSlash)
+ {
+ *pszDosSlash = '/';
+ pszDosSlash = strchr(pszDosSlash + 1, '\\');
+ }
+#endif
+ }
+ else
+ {
+ /** @todo implement gnu and pax long name extensions. */
+ return VERR_TAR_NAME_TOO_LONG;
+ }
+
+ /*
+ * File mode. ASSUME that the unix part of the IPRT mode mask is
+ * compatible with the TAR/Unix world.
+ */
+ uint32_t uValue = pObjInfo->Attr.fMode & RTFS_UNIX_MASK;
+ if (RTFS_IS_DIRECTORY(pObjInfo->Attr.fMode))
+ uValue = (uValue & pThis->fDirModeAndMask) | pThis->fDirModeOrMask;
+ else
+ uValue = (uValue & pThis->fFileModeAndMask) | pThis->fFileModeOrMask;
+ int rc = RTStrFormatU32(pThis->aHdrs[0].Common.mode, sizeof(pThis->aHdrs[0].Common.mode), uValue, 8 /*uBase*/,
+ -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.mode) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+
+ /*
+ * uid & gid. Just guard against NIL values as they won't fit.
+ */
+ uValue = pThis->uidOwner != NIL_RTUID ? pThis->uidOwner
+ : pObjInfo->Attr.u.Unix.uid != NIL_RTUID ? pObjInfo->Attr.u.Unix.uid : 0;
+ rc = RTStrFormatU32(pThis->aHdrs[0].Common.uid, sizeof(pThis->aHdrs[0].Common.uid), uValue,
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.uid) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+
+ uValue = pThis->gidGroup != NIL_RTGID ? pThis->gidGroup
+ : pObjInfo->Attr.u.Unix.gid != NIL_RTGID ? pObjInfo->Attr.u.Unix.gid : 0;
+ rc = RTStrFormatU32(pThis->aHdrs[0].Common.gid, sizeof(pThis->aHdrs[0].Common.gid), uValue,
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.gid) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+
+ /*
+ * The file size.
+ */
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, pObjInfo->cbObject);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Modification time relative to unix epoc.
+ */
+ rc = RTStrFormatU64(pThis->aHdrs[0].Common.mtime, sizeof(pThis->aHdrs[0].Common.mtime),
+ RTTimeSpecGetSeconds(pThis->pModTime ? pThis->pModTime : &pObjInfo->ModificationTime),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.mtime) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, rc);
+
+ /* Skipping checksum for now */
+
+ /*
+ * The type flag.
+ */
+ if (chType == UINT8_MAX)
+ switch (pObjInfo->Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FIFO: chType = RTZIPTAR_TF_FIFO; break;
+ case RTFS_TYPE_DEV_CHAR: chType = RTZIPTAR_TF_CHR; break;
+ case RTFS_TYPE_DIRECTORY: chType = RTZIPTAR_TF_DIR; break;
+ case RTFS_TYPE_DEV_BLOCK: chType = RTZIPTAR_TF_BLK; break;
+ case RTFS_TYPE_FILE: chType = RTZIPTAR_TF_NORMAL; break;
+ case RTFS_TYPE_SYMLINK: chType = RTZIPTAR_TF_SYMLINK; break;
+ case RTFS_TYPE_SOCKET: chType = RTZIPTAR_TF_FIFO; break;
+ case RTFS_TYPE_WHITEOUT: AssertFailedReturn(VERR_WRONG_TYPE);
+ }
+ pThis->aHdrs[0].Common.typeflag = chType;
+
+ /* No link name, at least not for now. Caller might set it. */
+
+ /*
+ * Set TAR record magic and version.
+ */
+ if (pThis->enmFormat == RTZIPTARFORMAT_GNU)
+ memcpy(pThis->aHdrs[0].Gnu.magic, RTZIPTAR_GNU_MAGIC, sizeof(pThis->aHdrs[0].Gnu.magic));
+ else if ( pThis->enmFormat == RTZIPTARFORMAT_USTAR
+ || pThis->enmFormat == RTZIPTARFORMAT_PAX)
+ {
+ memcpy(pThis->aHdrs[0].Common.magic, RTZIPTAR_USTAR_MAGIC, sizeof(pThis->aHdrs[0].Common.magic));
+ memcpy(pThis->aHdrs[0].Common.version, RTZIPTAR_USTAR_VERSION, sizeof(pThis->aHdrs[0].Common.version));
+ }
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Owner and group names. Silently truncate them for now.
+ */
+ RTStrCopy(pThis->aHdrs[0].Common.uname, sizeof(pThis->aHdrs[0].Common.uname), pThis->pszOwner ? pThis->pszOwner : pszOwnerNm);
+ RTStrCopy(pThis->aHdrs[0].Common.gname, sizeof(pThis->aHdrs[0].Common.uname), pThis->pszGroup ? pThis->pszGroup : pszGroupNm);
+
+ /*
+ * Char/block device numbers.
+ */
+ if ( RTFS_IS_DEV_BLOCK(pObjInfo->Attr.fMode)
+ || RTFS_IS_DEV_CHAR(pObjInfo->Attr.fMode) )
+ {
+ rc = RTStrFormatU32(pThis->aHdrs[0].Common.devmajor, sizeof(pThis->aHdrs[0].Common.devmajor),
+ RTDEV_MAJOR(pObjInfo->Attr.u.Unix.Device),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.devmajor) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+
+ rc = RTStrFormatU32(pThis->aHdrs[0].Common.devminor, sizeof(pThis->aHdrs[0].Common.devmajor),
+ RTDEV_MINOR(pObjInfo->Attr.u.Unix.Device),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Common.devmajor) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, VERR_TAR_NUM_VALUE_TOO_LARGE);
+ }
+
+#if 0 /** @todo why doesn't this work? */
+ /*
+ * Set GNU specific stuff.
+ */
+ if (pThis->enmFormat == RTZIPTARFORMAT_GNU)
+ {
+ rc = RTStrFormatU64(pThis->aHdrs[0].Gnu.ctime, sizeof(pThis->aHdrs[0].Gnu.ctime),
+ RTTimeSpecGetSeconds(&pObjInfo->ChangeTime),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Gnu.ctime) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, rc);
+
+ rc = RTStrFormatU64(pThis->aHdrs[0].Gnu.atime, sizeof(pThis->aHdrs[0].Gnu.atime),
+ RTTimeSpecGetSeconds(&pObjInfo->ChangeTime),
+ 8 /*uBase*/, -1 /*cchWidth*/, sizeof(pThis->aHdrs[0].Gnu.atime) - 1, RTSTR_F_ZEROPAD | RTSTR_F_PRECISION);
+ AssertRCReturn(rc, rc);
+ }
+#endif
+
+ /*
+ * Finally the checksum.
+ */
+ pThis->cHdrs = 1;
+ return rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+}
+
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Close(void *pvThis)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ if (pParent)
+ {
+ if (pParent->pPush == pPush)
+ rtZipTarFssWriter_CompleteCurrentPushFile(pParent);
+ else
+ AssertFailedStmt(pPush->pParent = NULL);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+
+ /* Basic info (w/ additional unix attribs). */
+ *pObjInfo = pPush->ObjInfo;
+ pObjInfo->cbObject = pPush->cbCurrent;
+ pObjInfo->cbAllocated = RT_ALIGN_64(pPush->cbCurrent, RTZIPTAR_BLOCKSIZE);
+
+ /* Additional info. */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ Assert(pObjInfo->Attr.enmAdditional == RTFSOBJATTRADD_UNIX);
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ pObjInfo->Attr.u.UnixOwner.uid = pPush->ObjInfo.Attr.u.Unix.uid;
+ if (pPush->pParent)
+ strcpy(pObjInfo->Attr.u.UnixOwner.szName, pPush->pParent->aHdrs[0].Common.uname);
+ else
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ pObjInfo->Attr.u.UnixGroup.gid = pPush->ObjInfo.Attr.u.Unix.gid;
+ if (pPush->pParent)
+ strcpy(pObjInfo->Attr.u.UnixGroup.szName, pPush->pParent->aHdrs[0].Common.uname);
+ else
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ break;
+
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.u.EASize.cb = 0;
+ pObjInfo->Attr.enmAdditional = enmAddAttr;
+ break;
+
+ default:
+ AssertFailed();
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ /* No read support, sorry. */
+ RT_NOREF(pvThis, off, pSgBuf, fBlocking, pcbRead);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ AssertPtrReturn(pParent, VERR_WRONG_ORDER);
+
+ int rc = pParent->rcFatal;
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Single segment at a time.
+ */
+ Assert(pSgBuf->cSegs == 1);
+ size_t cbToWrite = pSgBuf->paSegs[0].cbSeg;
+ void const *pvToWrite = pSgBuf->paSegs[0].pvSeg;
+
+ /*
+ * Hopefully we don't need to seek. But if we do, let the seek method do
+ * it as it's not entirely trivial.
+ */
+ if ( off < 0
+ || (uint64_t)off == pPush->offCurrent)
+ rc = VINF_SUCCESS;
+ else
+ rc = rtZipTarWriterPush_Seek(pvThis, off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ Assert(pPush->offCurrent <= pPush->cbExpected);
+ Assert(pPush->offCurrent <= pPush->cbCurrent);
+ AssertMsgReturn(cbToWrite <= pPush->cbExpected - pPush->offCurrent,
+ ("offCurrent=%#RX64 + cbToWrite=%#zx = %#RX64; cbExpected=%RX64\n",
+ pPush->offCurrent, cbToWrite, pPush->offCurrent + cbToWrite, pPush->cbExpected),
+ VERR_DISK_FULL);
+ size_t cbWritten = 0;
+ rc = RTVfsIoStrmWrite(pParent->hVfsIos, pvToWrite, cbToWrite, fBlocking, &cbWritten);
+ if (RT_SUCCESS(rc))
+ {
+ pPush->offCurrent += cbWritten;
+ if (pPush->offCurrent > pPush->cbCurrent)
+ {
+ pParent->cbWritten = pPush->offCurrent - pPush->cbCurrent;
+ pPush->cbCurrent = pPush->offCurrent;
+ }
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ }
+ }
+
+ /*
+ * Fatal errors get down here, non-fatal ones returns earlier.
+ */
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ pParent->rcFatal = rc;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Flush(void *pvThis)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ AssertPtrReturn(pParent, VERR_WRONG_ORDER);
+ int rc = pParent->rcFatal;
+ if (RT_SUCCESS(rc))
+ pParent->rcFatal = rc = RTVfsIoStrmFlush(pParent->hVfsIos);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ AssertPtrReturn(pParent, VERR_WRONG_ORDER);
+ return RTVfsIoStrmPoll(pParent->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ *poffActual = (RTFOFF)pPush->offCurrent;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnSkip}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Skip(void *pvThis, RTFOFF cb)
+{
+ RT_NOREF(pvThis, cb);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ RT_NOREF(pvThis, fMode, fMask);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ RT_NOREF(pvThis, uid, gid);
+ AssertFailed();
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ PRTZIPTARFSSTREAMWRITER pParent = pPush->pParent;
+ AssertPtrReturn(pParent, VERR_WRONG_ORDER);
+
+ int rc = pParent->rcFatal;
+ AssertRCReturn(rc, rc);
+ Assert(pPush->offCurrent <= pPush->cbCurrent);
+
+ /*
+ * Calculate the new file offset.
+ */
+ RTFOFF offNewSigned;
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ offNewSigned = offSeek;
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offNewSigned = pPush->offCurrent + offSeek;
+ break;
+ case RTFILE_SEEK_END:
+ offNewSigned = pPush->cbCurrent + offSeek;
+ break;
+ default:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+
+ /*
+ * Check the new file offset against expectations.
+ */
+ AssertMsgReturn(offNewSigned >= 0, ("offNewSigned=%RTfoff\n", offNewSigned), VERR_NEGATIVE_SEEK);
+
+ uint64_t offNew = (uint64_t)offNewSigned;
+ AssertMsgReturn(offNew <= pPush->cbExpected, ("offNew=%#RX64 cbExpected=%#Rx64\n", offNew, pPush->cbExpected), VERR_SEEK);
+
+ /*
+ * Any change at all? We can always hope...
+ */
+ if (offNew == pPush->offCurrent)
+ { }
+ /*
+ * Gap that needs zero filling?
+ */
+ else if (offNew > pPush->cbCurrent)
+ {
+ if (pPush->offCurrent != pPush->cbCurrent)
+ {
+ AssertReturn(pParent->hVfsFile != NIL_RTVFSFILE, VERR_NOT_A_FILE);
+ rc = RTVfsFileSeek(pParent->hVfsFile, pPush->offData + pPush->cbCurrent, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(rc))
+ return pParent->rcFatal = rc;
+ pPush->offCurrent = pPush->cbCurrent;
+ }
+
+ uint64_t cbToZero = offNew - pPush->cbCurrent;
+ rc = RTVfsIoStrmZeroFill(pParent->hVfsIos, cbToZero);
+ if (RT_FAILURE(rc))
+ return pParent->rcFatal = rc;
+ pParent->cbWritten += cbToZero;
+ pPush->cbCurrent = pPush->offCurrent = offNew;
+ }
+ /*
+ * Just change the file position to somewhere we've already written.
+ */
+ else
+ {
+ AssertReturn(pParent->hVfsFile != NIL_RTVFSFILE, VERR_NOT_A_FILE);
+ rc = RTVfsFileSeek(pParent->hVfsFile, pPush->offData + offNew, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(rc))
+ return pParent->rcFatal = rc;
+ pPush->offCurrent = offNew;
+ }
+ Assert(pPush->offCurrent <= pPush->cbCurrent);
+
+ if (poffActual)
+ *poffActual = pPush->offCurrent;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtZipTarWriterPush_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = (PRTZIPTARFSSTREAMWRITERPUSH)pvThis;
+ *pcbFile = pPush->cbCurrent;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * TAR writer push I/O stream operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSIOSTREAMOPS) g_rtZipTarWriterIoStrmOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "TAR push I/O Stream",
+ rtZipTarWriterPush_Close,
+ rtZipTarWriterPush_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipTarWriterPush_Read,
+ rtZipTarWriterPush_Write,
+ rtZipTarWriterPush_Flush,
+ rtZipTarWriterPush_PollOne,
+ rtZipTarWriterPush_Tell,
+ rtZipTarWriterPush_Skip,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+};
+
+
+/**
+ * TAR writer push file operations.
+ */
+DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtZipTarWriterFileOps =
+{
+ { /* Stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "TAR push file",
+ rtZipTarWriterPush_Close,
+ rtZipTarWriterPush_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipTarWriterPush_Read,
+ rtZipTarWriterPush_Write,
+ rtZipTarWriterPush_Flush,
+ rtZipTarWriterPush_PollOne,
+ rtZipTarWriterPush_Tell,
+ rtZipTarWriterPush_Skip,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION,
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtZipTarWriterPush_SetMode,
+ rtZipTarWriterPush_SetTimes,
+ rtZipTarWriterPush_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtZipTarWriterPush_Seek,
+ rtZipTarWriterPush_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION
+};
+
+
+
+/**
+ * Checks rcFatal and completes any current push file.
+ *
+ * On return the output stream position will be at the next header location.
+ *
+ * After this call, the push object no longer can write anything.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ */
+static int rtZipTarFssWriter_CompleteCurrentPushFile(PRTZIPTARFSSTREAMWRITER pThis)
+{
+ /*
+ * Check if there is a push file pending, remove it if there is.
+ * We also check for fatal errors at this point so the caller doesn't need to.
+ */
+ PRTZIPTARFSSTREAMWRITERPUSH pPush = pThis->pPush;
+ if (!pPush)
+ {
+ AssertRC(pThis->rcFatal);
+ return pThis->rcFatal;
+ }
+
+ pThis->pPush = NULL;
+ pPush->pParent = NULL;
+
+ int rc = pThis->rcFatal;
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Do we need to update the header. pThis->aHdrs[0] will retain the current
+ * content at pPush->offHdr and we only need to update the size.
+ */
+ if (pPush->fOpenEnded)
+ {
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, pPush->cbCurrent);
+ if (RT_SUCCESS(rc))
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWriteAt(pThis->hVfsFile, pPush->offHdr, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileSeek(pThis->hVfsFile, pPush->offData + pPush->cbCurrent, RTFILE_SEEK_BEGIN, NULL);
+ }
+ }
+ /*
+ * Check that we've received all the data we were promissed in the PushFile
+ * call, fail if we weren't.
+ */
+ else
+ AssertMsgStmt(pPush->cbCurrent == pPush->cbExpected,
+ ("cbCurrent=%#RX64 cbExpected=%#RX64\n", pPush->cbCurrent, pPush->cbExpected),
+ rc = VERR_BUFFER_UNDERFLOW);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Do zero padding if necessary.
+ */
+ if (pPush->cbCurrent & (RTZIPTAR_BLOCKSIZE - 1))
+ {
+ size_t cbToZero = RTZIPTAR_BLOCKSIZE - (pPush->cbCurrent & (RTZIPTAR_BLOCKSIZE - 1));
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, cbToZero, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->cbWritten += cbToZero;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ pThis->rcFatal = rc;
+ return rc;
+}
+
+
+/**
+ * Does the actual work for rtZipTarFssWriter_SwitchToWriteMode().
+ *
+ * @note We won't be here if we've truncate the tar file. Truncation
+ * switches it into write mode.
+ */
+DECL_NO_INLINE(static, int) rtZipTarFssWriter_SwitchToWriteModeSlow(PRTZIPTARFSSTREAMWRITER pThis)
+{
+ /* Always go thru rtZipTarFssWriter_SwitchToWriteMode(). */
+ AssertRCReturn(pThis->rcFatal, pThis->rcFatal);
+ AssertReturn(!pThis->fWriting, VINF_SUCCESS);
+ AssertReturn(pThis->fFlags & RTZIPTAR_C_UPDATE, VERR_INTERNAL_ERROR_3);
+
+ /*
+ * If we're not at the end, locate the end of the tar file.
+ * Because I'm lazy, we do that using rtZipTarFss_Next. This isn't entirely
+ * optimial as it involves VFS object instantations and such.
+ */
+ /** @todo Optimize skipping to end of tar file in update mode. */
+ while (!pThis->pRead->fEndOfStream)
+ {
+ int rc = rtZipTarFss_Next(pThis->pRead, NULL, NULL, NULL);
+ if (rc == VERR_EOF)
+ break;
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Seek to the desired cut-off point and indicate that we've switched to writing.
+ */
+ Assert(pThis->pRead->offNextHdr == pThis->pRead->offCurHdr);
+ int rc = RTVfsFileSeek(pThis->hVfsFile, pThis->pRead->offNextHdr, RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ if (RT_SUCCESS(rc))
+ pThis->fWriting = true;
+ else
+ pThis->rcFatal = rc;
+
+ return rc;
+}
+
+
+/**
+ * Switches the stream into writing mode if necessary.
+ *
+ * @returns VBox status code.
+ * @param pThis The TAR writer instance.
+ *
+ */
+DECLINLINE(int) rtZipTarFssWriter_SwitchToWriteMode(PRTZIPTARFSSTREAMWRITER pThis)
+{
+ if (pThis->fWriting)
+ return VINF_SUCCESS; /* ASSUMES caller already checked pThis->rcFatal. */
+ return rtZipTarFssWriter_SwitchToWriteModeSlow(pThis);
+}
+
+
+/**
+ * Allocates a buffer for transfering file data.
+ *
+ * @note Will use the 3rd TAR header as fallback buffer if we're out of
+ * memory!
+ *
+ * @returns Pointer to buffer (won't ever fail).
+ * @param pThis The TAR writer instance.
+ * @param pcbBuf Where to return the buffer size. This will be a
+ * multiple of the TAR block size.
+ * @param ppvFree Where to return the pointer to pass to RTMemTmpFree
+ * when done with the buffer.
+ * @param cbFile The file size. Used as a buffer size hint.
+ */
+static uint8_t *rtZipTarFssWriter_AllocBuf(PRTZIPTARFSSTREAMWRITER pThis, size_t *pcbBuf, void **ppvFree, uint64_t cbObject)
+{
+ uint8_t *pbBuf;
+
+ /*
+ * If this is a large file, try for a large buffer with 16KB alignment.
+ */
+ if (cbObject >= _64M)
+ {
+ pbBuf = (uint8_t *)RTMemTmpAlloc(_2M + _16K - 1);
+ if (pbBuf)
+ {
+ *pcbBuf = _2M;
+ *ppvFree = pbBuf;
+ return RT_ALIGN_PT(pbBuf, _16K, uint8_t *);
+ }
+ }
+ /*
+ * 4KB aligned 512KB buffer if larger 512KB or larger.
+ */
+ else if (cbObject >= _512K)
+ {
+ pbBuf = (uint8_t *)RTMemTmpAlloc(_512K + _4K - 1);
+ if (pbBuf)
+ {
+ *pcbBuf = _512K;
+ *ppvFree = pbBuf;
+ return RT_ALIGN_PT(pbBuf, _4K, uint8_t *);
+ }
+ }
+ /*
+ * Otherwise a 4KB aligned 128KB buffer.
+ */
+ else
+ {
+ pbBuf = (uint8_t *)RTMemTmpAlloc(_128K + _4K - 1);
+ if (pbBuf)
+ {
+ *pcbBuf = _128K;
+ *ppvFree = pbBuf;
+ return RT_ALIGN_PT(pbBuf, _4K, uint8_t *);
+ }
+ }
+
+ /*
+ * If allocation failed, fallback on a 16KB buffer without any extra alignment.
+ */
+ pbBuf = (uint8_t *)RTMemTmpAlloc(_16K);
+ if (pbBuf)
+ {
+ *pcbBuf = _16K;
+ *ppvFree = pbBuf;
+ return pbBuf;
+ }
+
+ /*
+ * Final fallback, 512KB buffer using the 3rd header.
+ */
+ AssertCompile(RT_ELEMENTS(pThis->aHdrs) >= 3);
+ *pcbBuf = sizeof(pThis->aHdrs[2]);
+ *ppvFree = NULL;
+ return (uint8_t *)&pThis->aHdrs[2];
+}
+
+
+/**
+ * Frees the sparse info for a TAR file.
+ *
+ * @param pSparse The sparse info to free.
+ */
+static void rtZipTarFssWriter_SparseInfoDestroy(PRTZIPTARSPARSE pSparse)
+{
+ PRTZIPTARSPARSECHUNK pCur;
+ PRTZIPTARSPARSECHUNK pNext;
+ RTListForEachSafe(&pSparse->ChunkHead, pCur, pNext, RTZIPTARSPARSECHUNK, Entry)
+ RTMemTmpFree(pCur);
+ RTMemTmpFree(pSparse);
+}
+
+
+/**
+ * Adds a data span to the sparse info.
+ *
+ * @returns VINF_SUCCESS or VINF_NO_TMP_MEMORY.
+ * @param pSparse The sparse info to free.
+ * @param offSpan Offset of the span.
+ * @param cbSpan Number of bytes.
+ */
+static int rtZipTarFssWriter_SparseInfoAddSpan(PRTZIPTARSPARSE pSparse, uint64_t offSpan, uint64_t cbSpan)
+{
+ /*
+ * Get the chunk we're adding it to.
+ */
+ PRTZIPTARSPARSECHUNK pChunk;
+ if (pSparse->iNextSpan != 0)
+ {
+ pChunk = RTListGetLast(&pSparse->ChunkHead, RTZIPTARSPARSECHUNK, Entry);
+ Assert(pSparse->iNextSpan < RT_ELEMENTS(pChunk->aSpans));
+ }
+ else
+ {
+ pChunk = (PRTZIPTARSPARSECHUNK)RTMemTmpAllocZ(sizeof(*pChunk));
+ if (!pChunk)
+ return VERR_NO_TMP_MEMORY;
+ RTListAppend(&pSparse->ChunkHead, &pChunk->Entry);
+ }
+
+ /*
+ * Append it.
+ */
+ pSparse->cDataSpans += 1;
+ pSparse->cbDataSpans += cbSpan;
+ pChunk->aSpans[pSparse->iNextSpan].cb = cbSpan;
+ pChunk->aSpans[pSparse->iNextSpan].off = offSpan;
+ if (++pSparse->iNextSpan >= RT_ELEMENTS(pChunk->aSpans))
+ pSparse->iNextSpan = 0;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Scans the input stream recording non-zero blocks.
+ */
+static int rtZipTarFssWriter_ScanSparseFile(PRTZIPTARFSSTREAMWRITER pThis, RTVFSFILE hVfsFile, uint64_t cbFile,
+ size_t cbBuf, uint8_t *pbBuf, PRTZIPTARSPARSE *ppSparse)
+{
+ RT_NOREF(pThis);
+
+ /*
+ * Create an empty sparse info bundle.
+ */
+ PRTZIPTARSPARSE pSparse = (PRTZIPTARSPARSE)RTMemTmpAlloc(sizeof(*pSparse));
+ AssertReturn(pSparse, VERR_NO_MEMORY);
+ pSparse->cbDataSpans = 0;
+ pSparse->cDataSpans = 0;
+ pSparse->iNextSpan = 0;
+ RTListInit(&pSparse->ChunkHead);
+
+ /*
+ * Scan the file from the start.
+ */
+ int rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ bool fZeroSpan = false;
+ uint64_t offSpan = 0;
+ uint64_t cbSpan = 0;
+
+ for (uint64_t off = 0; off < cbFile;)
+ {
+ uint64_t cbLeft = cbFile - off;
+ size_t cbToRead = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft;
+ rc = RTVfsFileRead(hVfsFile, pbBuf, cbToRead, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ size_t cBlocks = cbToRead / RTZIPTAR_BLOCKSIZE;
+
+ /* Zero pad the final buffer to a multiple of the blocksize. */
+ if (!(cbToRead & (RTZIPTAR_BLOCKSIZE - 1)))
+ { /* likely */ }
+ else
+ {
+ AssertBreakStmt(cbLeft == cbToRead, rc = VERR_INTERNAL_ERROR_3);
+ RT_BZERO(&pbBuf[cbToRead], RTZIPTAR_BLOCKSIZE - (cbToRead & (RTZIPTAR_BLOCKSIZE - 1)));
+ cBlocks++;
+ }
+
+ /*
+ * Process the blocks we've just read one by one.
+ */
+ uint8_t const *pbBlock = pbBuf;
+ for (size_t iBlock = 0; iBlock < cBlocks; iBlock++)
+ {
+ bool fZeroBlock = ASMMemIsZero(pbBlock, RTZIPTAR_BLOCKSIZE);
+ if (fZeroBlock == fZeroSpan)
+ cbSpan += RTZIPTAR_BLOCKSIZE;
+ else
+ {
+ if (!fZeroSpan && cbSpan)
+ {
+ rc = rtZipTarFssWriter_SparseInfoAddSpan(pSparse, offSpan, cbSpan);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ fZeroSpan = fZeroBlock;
+ offSpan = off;
+ cbSpan = RTZIPTAR_BLOCKSIZE;
+ }
+
+ /* next block. */
+ pbBlock += RTZIPTAR_BLOCKSIZE;
+ off += RTZIPTAR_BLOCKSIZE;
+ }
+ }
+
+ /*
+ * Deal with the final span. If we've got zeros thowards the end, we
+ * must add a zero byte data span at the end.
+ */
+ if (RT_SUCCESS(rc))
+ {
+ if (!fZeroSpan && cbSpan)
+ {
+ if (cbFile & (RTZIPTAR_BLOCKSIZE - 1))
+ {
+ Assert(!(cbSpan & (RTZIPTAR_BLOCKSIZE - 1)));
+ cbSpan -= RTZIPTAR_BLOCKSIZE;
+ cbSpan |= cbFile & (RTZIPTAR_BLOCKSIZE - 1);
+ }
+ rc = rtZipTarFssWriter_SparseInfoAddSpan(pSparse, offSpan, cbSpan);
+ }
+ if (RT_SUCCESS(rc))
+ rc = rtZipTarFssWriter_SparseInfoAddSpan(pSparse, cbFile, 0);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Return the file back to the start position before we return so that we
+ * can segue into the regular rtZipTarFssWriter_AddFile without further ado.
+ */
+ rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ *ppSparse = pSparse;
+ return VINF_SUCCESS;
+ }
+ }
+
+ rtZipTarFssWriter_SparseInfoDestroy(pSparse);
+ *ppSparse = NULL;
+ return rc;
+}
+
+
+/**
+ * Writes GNU the sparse file headers.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ * @param pSparse The sparse file info.
+ */
+static int rtZipTarFssWriter_WriteGnuSparseHeaders(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, PCRTFSOBJINFO pObjInfo,
+ const char *pszOwnerNm, const char *pszGroupNm, PCRTZIPTARSPARSE pSparse)
+{
+ /*
+ * Format the first header.
+ */
+ int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, RTZIPTAR_TF_GNU_SPARSE);
+ AssertRCReturn(rc, rc);
+ AssertReturn(pThis->cHdrs == 1, VERR_INTERNAL_ERROR_2);
+
+ /* data size. */
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, pSparse->cbDataSpans);
+ AssertRCReturn(rc, rc);
+
+ /* realsize. */
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Gnu.realsize, pObjInfo->cbObject);
+ AssertRCReturn(rc, rc);
+
+ Assert(pThis->aHdrs[0].Gnu.isextended == 0);
+
+ /*
+ * Walk the sparse spans, fill and write headers one by one.
+ */
+ PRTZIPTARGNUSPARSE paSparse = &pThis->aHdrs[0].Gnu.sparse[0];
+ uint32_t cSparse = RT_ELEMENTS(pThis->aHdrs[0].Gnu.sparse);
+ uint32_t iSparse = 0;
+
+ PRTZIPTARSPARSECHUNK const pLastChunk = RTListGetLast(&pSparse->ChunkHead, RTZIPTARSPARSECHUNK, Entry);
+ PRTZIPTARSPARSECHUNK pChunk;
+ RTListForEach(&pSparse->ChunkHead, pChunk, RTZIPTARSPARSECHUNK, Entry)
+ {
+ uint32_t cSpans = pChunk != pLastChunk || pSparse->iNextSpan == 0
+ ? RT_ELEMENTS(pChunk->aSpans) : pSparse->iNextSpan;
+ for (uint32_t iSpan = 0; iSpan < cSpans; iSpan++)
+ {
+ /* Flush the header? */
+ if (iSparse >= cSparse)
+ {
+ if (cSparse != RT_ELEMENTS(pThis->aHdrs[0].Gnu.sparse))
+ pThis->aHdrs[0].GnuSparse.isextended = 1; /* more headers to come */
+ else
+ {
+ pThis->aHdrs[0].Gnu.isextended = 1; /* more headers to come */
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ RT_ZERO(pThis->aHdrs[0]);
+ cSparse = RT_ELEMENTS(pThis->aHdrs[0].GnuSparse.sp);
+ iSparse = 0;
+ paSparse = &pThis->aHdrs[0].GnuSparse.sp[0];
+ }
+
+ /* Append sparse data segment. */
+ rc = rtZipTarFssWriter_FormatOffset(paSparse[iSparse].offset, pChunk->aSpans[iSpan].off);
+ AssertRCReturn(rc, rc);
+ rc = rtZipTarFssWriter_FormatOffset(paSparse[iSparse].numbytes, pChunk->aSpans[iSpan].cb);
+ AssertRCReturn(rc, rc);
+ iSparse++;
+ }
+ }
+
+ /*
+ * The final header.
+ */
+ if (iSparse != 0)
+ {
+ if (cSparse != RT_ELEMENTS(pThis->aHdrs[0].Gnu.sparse))
+ Assert(pThis->aHdrs[0].GnuSparse.isextended == 0);
+ else
+ {
+ Assert(pThis->aHdrs[0].Gnu.isextended == 0);
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ }
+ if (RT_SUCCESS(rc))
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ }
+ pThis->cHdrs = 0;
+ return rc;
+}
+
+
+/**
+ * Adds a potentially sparse file to the output.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param hVfsFile The potentially sparse file.
+ * @param hVfsIos The I/O stream of the file. Same as @a hVfsFile.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddFileSparse(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSFILE hVfsFile,
+ RTVFSIOSTREAM hVfsIos, PCRTFSOBJINFO pObjInfo,
+ const char *pszOwnerNm, const char *pszGroupNm)
+{
+ /*
+ * Scan the input file to locate all zero blocks.
+ */
+ void *pvBufFree;
+ size_t cbBuf;
+ uint8_t *pbBuf = rtZipTarFssWriter_AllocBuf(pThis, &cbBuf, &pvBufFree, pObjInfo->cbObject);
+
+ PRTZIPTARSPARSE pSparse;
+ int rc = rtZipTarFssWriter_ScanSparseFile(pThis, hVfsFile, pObjInfo->cbObject, cbBuf, pbBuf, &pSparse);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * If there aren't at least 2 zero blocks in the file, don't bother
+ * doing the sparse stuff and store it as a normal file.
+ */
+ if (pSparse->cbDataSpans + RTZIPTAR_BLOCKSIZE > (uint64_t)pObjInfo->cbObject)
+ {
+ rtZipTarFssWriter_SparseInfoDestroy(pSparse);
+ RTMemTmpFree(pvBufFree);
+ return rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, pObjInfo, pszOwnerNm, pszGroupNm);
+ }
+
+ /*
+ * Produce and write the headers.
+ */
+ if (pThis->enmFormat == RTZIPTARFORMAT_GNU)
+ rc = rtZipTarFssWriter_WriteGnuSparseHeaders(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, pSparse);
+ else
+ AssertStmt(pThis->enmFormat != RTZIPTARFORMAT_GNU, rc = VERR_NOT_IMPLEMENTED);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Write the file bytes.
+ */
+ PRTZIPTARSPARSECHUNK const pLastChunk = RTListGetLast(&pSparse->ChunkHead, RTZIPTARSPARSECHUNK, Entry);
+ PRTZIPTARSPARSECHUNK pChunk;
+ RTListForEach(&pSparse->ChunkHead, pChunk, RTZIPTARSPARSECHUNK, Entry)
+ {
+ uint32_t cSpans = pChunk != pLastChunk || pSparse->iNextSpan == 0
+ ? RT_ELEMENTS(pChunk->aSpans) : pSparse->iNextSpan;
+ for (uint32_t iSpan = 0; iSpan < cSpans; iSpan++)
+ {
+ rc = RTVfsFileSeek(hVfsFile, pChunk->aSpans[iSpan].off, RTFILE_SEEK_BEGIN, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ uint64_t cbLeft = pChunk->aSpans[iSpan].cb;
+ Assert( !(cbLeft & (RTZIPTAR_BLOCKSIZE - 1))
+ || (iSpan + 1 == cSpans && pChunk == pLastChunk));
+ while (cbLeft > 0)
+ {
+ size_t cbToRead = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft;
+ rc = RTVfsFileRead(hVfsFile, pbBuf, cbToRead, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pbBuf, cbToRead, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += cbToRead;
+ cbLeft -= cbToRead;
+ continue;
+ }
+ }
+ break;
+ }
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ /*
+ * Do the zero padding.
+ */
+ if ( RT_SUCCESS(rc)
+ && (pSparse->cbDataSpans & (RTZIPTAR_BLOCKSIZE - 1)))
+ {
+ size_t cbToZero = RTZIPTAR_BLOCKSIZE - (pSparse->cbDataSpans & (RTZIPTAR_BLOCKSIZE - 1));
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, cbToZero, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->cbWritten += cbToZero;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ pThis->rcFatal = rc;
+ rtZipTarFssWriter_SparseInfoDestroy(pSparse);
+ }
+ RTMemTmpFree(pvBufFree);
+ return rc;
+}
+
+
+/**
+ * Adds an I/O stream of indeterminate length to the TAR file.
+ *
+ * This requires the output to be seekable, i.e. a file, because we need to go
+ * back and update @c size field of the TAR header after pumping all the data
+ * bytes thru and establishing the file length.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param hVfsIos The I/O stream of the file.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddFileStream(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos,
+ PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm)
+{
+ AssertReturn(pThis->hVfsFile != NIL_RTVFSFILE, VERR_NOT_A_FILE);
+
+ /*
+ * Append the header.
+ */
+ int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, UINT8_MAX);
+ if (RT_SUCCESS(rc))
+ {
+ RTFOFF const offHdr = RTVfsFileTell(pThis->hVfsFile);
+ if (offHdr >= 0)
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+
+ /*
+ * Transfer the bytes.
+ */
+ void *pvBufFree;
+ size_t cbBuf;
+ uint8_t *pbBuf = rtZipTarFssWriter_AllocBuf(pThis, &cbBuf, &pvBufFree,
+ pObjInfo->cbObject > 0 && pObjInfo->cbObject != RTFOFF_MAX
+ ? pObjInfo->cbObject : _1G);
+
+ uint64_t cbReadTotal = 0;
+ for (;;)
+ {
+ size_t cbRead = 0;
+ int rc2 = rc = RTVfsIoStrmRead(hVfsIos, pbBuf, cbBuf, true /*fBlocking*/, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ cbReadTotal += cbRead;
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pbBuf, cbRead, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += cbRead;
+ if (rc2 != VINF_EOF)
+ continue;
+ }
+ }
+ Assert(rc != VERR_EOF /* expecting VINF_EOF! */);
+ break;
+ }
+
+ RTMemTmpFree(pvBufFree);
+
+ /*
+ * Do the zero padding.
+ */
+ if ((cbReadTotal & (RTZIPTAR_BLOCKSIZE - 1)) && RT_SUCCESS(rc))
+ {
+ size_t cbToZero = RTZIPTAR_BLOCKSIZE - (cbReadTotal & (RTZIPTAR_BLOCKSIZE - 1));
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, cbToZero, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->cbWritten += cbToZero;
+ }
+
+ /*
+ * Update the header. We ASSUME that aHdr[0] is unmodified
+ * from before the data pumping above and just update the size.
+ */
+ if ((RTFOFF)cbReadTotal != pObjInfo->cbObject && RT_SUCCESS(rc))
+ {
+ RTFOFF const offRestore = RTVfsFileTell(pThis->hVfsFile);
+ if (offRestore >= 0)
+ {
+ rc = rtZipTarFssWriter_FormatOffset(pThis->aHdrs[0].Common.size, cbReadTotal);
+ if (RT_SUCCESS(rc))
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsFileWriteAt(pThis->hVfsFile, offHdr, &pThis->aHdrs[0], sizeof(pThis->aHdrs[0]), NULL);
+ if (RT_SUCCESS(rc))
+ rc = RTVfsFileSeek(pThis->hVfsFile, offRestore, RTFILE_SEEK_BEGIN, NULL);
+ }
+ }
+ else
+ rc = (int)offRestore;
+ }
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = (int)offHdr;
+ pThis->rcFatal = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a file to the stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the file.
+ * @param hVfsIos The I/O stream of the file.
+ * @param fFlags The RTVFSFSSTREAMOPS::pfnAdd flags.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddFile(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSIOSTREAM hVfsIos,
+ PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm)
+{
+ /*
+ * Append the header.
+ */
+ int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, UINT8_MAX);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+
+ /*
+ * Copy the bytes. Padding the last buffer to a multiple of 512.
+ */
+ void *pvBufFree;
+ size_t cbBuf;
+ uint8_t *pbBuf = rtZipTarFssWriter_AllocBuf(pThis, &cbBuf, &pvBufFree, pObjInfo->cbObject);
+
+ uint64_t cbLeft = pObjInfo->cbObject;
+ while (cbLeft > 0)
+ {
+ size_t cbRead = cbLeft > cbBuf ? cbBuf : (size_t)cbLeft;
+ rc = RTVfsIoStrmRead(hVfsIos, pbBuf, cbRead, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ break;
+
+ size_t cbToWrite = cbRead;
+ if (cbRead & (RTZIPTAR_BLOCKSIZE - 1))
+ {
+ size_t cbToZero = RTZIPTAR_BLOCKSIZE - (cbRead & (RTZIPTAR_BLOCKSIZE - 1));
+ memset(&pbBuf[cbRead], 0, cbToZero);
+ cbToWrite += cbToZero;
+ }
+
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pbBuf, cbToWrite, true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ break;
+ pThis->cbWritten += cbToWrite;
+ cbLeft -= cbRead;
+ }
+
+ RTMemTmpFree(pvBufFree);
+
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+ }
+ pThis->rcFatal = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a symbolic link to the stream.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the object.
+ * @param hVfsSymlink The symbolic link object to add.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddSymlink(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, RTVFSSYMLINK hVfsSymlink,
+ PCRTFSOBJINFO pObjInfo, const char *pszOwnerNm, const char *pszGroupNm)
+{
+ /*
+ * Read the symlink target first and check that it's not too long.
+ * Flip DOS slashes.
+ */
+ char szTarget[RTPATH_MAX];
+ int rc = RTVfsSymlinkRead(hVfsSymlink, szTarget, sizeof(szTarget));
+ if (RT_SUCCESS(rc))
+ {
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_UNIX
+ char *pszDosSlash = strchr(szTarget, '\\');
+ while (pszDosSlash)
+ {
+ *pszDosSlash = '/';
+ pszDosSlash = strchr(pszDosSlash + 1, '\\');
+ }
+#endif
+ size_t cchTarget = strlen(szTarget);
+ if (cchTarget < sizeof(pThis->aHdrs[0].Common.linkname))
+ {
+ /*
+ * Create a header, add the link target and push it out.
+ */
+ rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, UINT8_MAX);
+ if (RT_SUCCESS(rc))
+ {
+ memcpy(pThis->aHdrs[0].Common.linkname, szTarget, cchTarget + 1);
+ rc = rtZipTarFssWriter_ChecksumHdr(&pThis->aHdrs[0]);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]),
+ true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+ return VINF_SUCCESS;
+ }
+ pThis->rcFatal = rc;
+ }
+ }
+ }
+ else
+ {
+ /** @todo implement gnu and pax long name extensions. */
+ rc = VERR_TAR_NAME_TOO_LONG;
+ }
+ }
+ return rc;
+}
+
+
+/**
+ * Adds a simple object to the stream.
+ *
+ * Simple objects only contains metadata, no actual data bits. Directories,
+ * devices, fifos, sockets and such.
+ *
+ * @returns IPRT status code.
+ * @param pThis The TAR writer instance.
+ * @param pszPath The path to the object.
+ * @param pObjInfo The object information.
+ * @param pszOwnerNm The owner name.
+ * @param pszGroupNm The group name.
+ */
+static int rtZipTarFssWriter_AddSimpleObject(PRTZIPTARFSSTREAMWRITER pThis, const char *pszPath, PCRTFSOBJINFO pObjInfo,
+ const char *pszOwnerNm, const char *pszGroupNm)
+{
+ int rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, pObjInfo, pszOwnerNm, pszGroupNm, UINT8_MAX);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, pThis->cHdrs * sizeof(pThis->aHdrs[0]), true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+ return VINF_SUCCESS;
+ }
+ pThis->rcFatal = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_Close(void *pvThis)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ rtZipTarFssWriter_CompleteCurrentPushFile(pThis);
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ if (pThis->hVfsFile != NIL_RTVFSFILE)
+ {
+ RTVfsFileRelease(pThis->hVfsFile);
+ pThis->hVfsFile = NIL_RTVFSFILE;
+ }
+
+ if (pThis->pszOwner)
+ {
+ RTStrFree(pThis->pszOwner);
+ pThis->pszOwner = NULL;
+ }
+ if (pThis->pszGroup)
+ {
+ RTStrFree(pThis->pszGroup);
+ pThis->pszGroup = NULL;
+ }
+ if (pThis->pszPrefix)
+ {
+ RTStrFree(pThis->pszPrefix);
+ pThis->pszPrefix = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+ /* Take the lazy approach here, with the sideffect of providing some info
+ that is actually kind of useful. */
+ return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnNext}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ /*
+ * This only works in update mode and up to the point where
+ * modifications takes place (truncating the archive or appending files).
+ */
+ AssertReturn(pThis->pRead, VERR_ACCESS_DENIED);
+ AssertReturn(pThis->fFlags & RTZIPTAR_C_UPDATE, VERR_ACCESS_DENIED);
+
+ AssertReturn(!pThis->fWriting, VERR_WRONG_ORDER);
+
+ return rtZipTarFss_Next(pThis->pRead, ppszName, penmType, phVfsObj);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnAdd}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_Add(void *pvThis, const char *pszPath, RTVFSOBJ hVfsObj, uint32_t fFlags)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ /*
+ * Before we continue we must complete any current push file and check rcFatal.
+ */
+ int rc = rtZipTarFssWriter_CompleteCurrentPushFile(pThis);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Query information about the object.
+ */
+ RTFSOBJINFO ObjInfo;
+ rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_UNIX);
+ AssertRCReturn(rc, rc);
+
+ RTFSOBJINFO ObjOwnerName;
+ rc = RTVfsObjQueryInfo(hVfsObj, &ObjOwnerName, RTFSOBJATTRADD_UNIX_OWNER);
+ if (RT_FAILURE(rc) || ObjOwnerName.Attr.u.UnixOwner.szName[0] == '\0')
+ strcpy(ObjOwnerName.Attr.u.UnixOwner.szName, "someone");
+
+ RTFSOBJINFO ObjGrpName;
+ rc = RTVfsObjQueryInfo(hVfsObj, &ObjGrpName, RTFSOBJATTRADD_UNIX_GROUP);
+ if (RT_FAILURE(rc) || ObjGrpName.Attr.u.UnixGroup.szName[0] == '\0')
+ strcpy(ObjGrpName.Attr.u.UnixGroup.szName, "somegroup");
+
+ /*
+ * Switch the stream into write mode if necessary.
+ */
+ rc = rtZipTarFssWriter_SwitchToWriteMode(pThis);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Do type specific handling. File have several options and variations to
+ * take into account, thus the mess.
+ */
+ if (RTFS_IS_FILE(ObjInfo.Attr.fMode))
+ {
+ RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
+ AssertReturn(hVfsIos != NIL_RTVFSIOSTREAM, VERR_WRONG_TYPE);
+
+ if (fFlags & RTVFSFSSTRM_ADD_F_STREAM)
+ rc = rtZipTarFssWriter_AddFileStream(pThis, pszPath, hVfsIos, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ else if ( !(pThis->fFlags & RTZIPTAR_C_SPARSE)
+ || ObjInfo.cbObject < RTZIPTAR_MIN_SPARSE)
+ rc = rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ else
+ {
+ RTVFSFILE hVfsFile = RTVfsObjToFile(hVfsObj);
+ if (hVfsFile != NIL_RTVFSFILE)
+ {
+ rc = rtZipTarFssWriter_AddFileSparse(pThis, pszPath, hVfsFile, hVfsIos, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ rc = rtZipTarFssWriter_AddFile(pThis, pszPath, hVfsIos, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ }
+ RTVfsIoStrmRelease(hVfsIos);
+ }
+ else if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
+ {
+ RTVFSSYMLINK hVfsSymlink = RTVfsObjToSymlink(hVfsObj);
+ AssertReturn(hVfsSymlink != NIL_RTVFSSYMLINK, VERR_WRONG_TYPE);
+ rc = rtZipTarFssWriter_AddSymlink(pThis, pszPath, hVfsSymlink, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+ RTVfsSymlinkRelease(hVfsSymlink);
+ }
+ else
+ rc = rtZipTarFssWriter_AddSimpleObject(pThis, pszPath, &ObjInfo,
+ ObjOwnerName.Attr.u.UnixOwner.szName, ObjGrpName.Attr.u.UnixOwner.szName);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnPushFile}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_PushFile(void *pvThis, const char *pszPath, uint64_t cbFile, PCRTFSOBJINFO paObjInfo,
+ uint32_t cObjInfo, uint32_t fFlags, PRTVFSIOSTREAM phVfsIos)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ /*
+ * We can only deal with output of indeterminate length if the output is
+ * seekable (see also rtZipTarFssWriter_AddFileStream).
+ */
+ AssertReturn(cbFile != UINT64_MAX || pThis->hVfsFile != NIL_RTVFSFILE, VERR_NOT_A_FILE);
+ AssertReturn(RT_BOOL(cbFile == UINT64_MAX) == RT_BOOL(fFlags & RTVFSFSSTRM_ADD_F_STREAM), VERR_INVALID_FLAGS);
+
+ /*
+ * Before we continue we must complete any current push file and check rcFatal.
+ */
+ int rc = rtZipTarFssWriter_CompleteCurrentPushFile(pThis);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * If no object info was provideded, fake up some.
+ */
+ const char *pszOwnerNm = "someone";
+ const char *pszGroupNm = "somegroup";
+ RTFSOBJINFO ObjInfo;
+ if (cObjInfo == 0)
+ {
+ /* Fake up a info. */
+ RT_ZERO(ObjInfo);
+ ObjInfo.cbObject = cbFile != UINT64_MAX ? cbFile : 0;
+ ObjInfo.cbAllocated = cbFile != UINT64_MAX ? RT_ALIGN_64(cbFile, RTZIPTAR_BLOCKSIZE) : UINT64_MAX;
+ RTTimeNow(&ObjInfo.ModificationTime);
+ ObjInfo.BirthTime = ObjInfo.ModificationTime;
+ ObjInfo.ChangeTime = ObjInfo.ModificationTime;
+ ObjInfo.AccessTime = ObjInfo.ModificationTime;
+ ObjInfo.Attr.fMode = RTFS_TYPE_FILE | 0666;
+ ObjInfo.Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ ObjInfo.Attr.u.Unix.uid = NIL_RTUID;
+ ObjInfo.Attr.u.Unix.gid = NIL_RTGID;
+ ObjInfo.Attr.u.Unix.cHardlinks = 1;
+ //ObjInfo.Attr.u.Unix.INodeIdDevice = 0;
+ //ObjInfo.Attr.u.Unix.INodeId = 0;
+ //ObjInfo.Attr.u.Unix.fFlags = 0;
+ //ObjInfo.Attr.u.Unix.GenerationId = 0;
+ //ObjInfo.Attr.u.Unix.Device = 0;
+ }
+ else
+ {
+ /* Make a copy of the object info and adjust the size, if necessary. */
+ ObjInfo = paObjInfo[0];
+ Assert(ObjInfo.Attr.enmAdditional == RTFSOBJATTRADD_UNIX);
+ Assert(RTFS_IS_FILE(ObjInfo.Attr.fMode));
+ if ((uint64_t)ObjInfo.cbObject != cbFile)
+ {
+ ObjInfo.cbObject = cbFile != UINT64_MAX ? cbFile : 0;
+ ObjInfo.cbAllocated = cbFile != UINT64_MAX ? RT_ALIGN_64(cbFile, RTZIPTAR_BLOCKSIZE) : UINT64_MAX;
+ }
+
+ /* Lookup the group and user names. */
+ for (uint32_t i = 0; i < cObjInfo; i++)
+ if ( paObjInfo[i].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_OWNER
+ && paObjInfo[i].Attr.u.UnixOwner.szName[0] != '\0')
+ pszOwnerNm = paObjInfo[i].Attr.u.UnixOwner.szName;
+ else if ( paObjInfo[i].Attr.enmAdditional == RTFSOBJATTRADD_UNIX_GROUP
+ && paObjInfo[i].Attr.u.UnixGroup.szName[0] != '\0')
+ pszGroupNm = paObjInfo[i].Attr.u.UnixGroup.szName;
+ }
+
+ /*
+ * Switch the stream into write mode if necessary.
+ */
+ rc = rtZipTarFssWriter_SwitchToWriteMode(pThis);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create an I/O stream object for the caller to use.
+ */
+ RTFOFF const offHdr = RTVfsIoStrmTell(pThis->hVfsIos);
+ AssertReturn(offHdr >= 0, (int)offHdr);
+
+ PRTZIPTARFSSTREAMWRITERPUSH pPush;
+ RTVFSIOSTREAM hVfsIos;
+ if (pThis->hVfsFile == NIL_RTVFSFILE)
+ {
+ rc = RTVfsNewIoStream(&g_rtZipTarWriterIoStrmOps, sizeof(*pPush), RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsIos, (void **)&pPush);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ {
+ RTVFSFILE hVfsFile;
+ rc = RTVfsNewFile(&g_rtZipTarWriterFileOps, sizeof(*pPush), RTFILE_O_WRITE, NIL_RTVFS, NIL_RTVFSLOCK,
+ &hVfsFile, (void **)&pPush);
+ if (RT_FAILURE(rc))
+ return rc;
+ hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+ pPush->pParent = NULL;
+ pPush->cbExpected = cbFile;
+ pPush->offHdr = (uint64_t)offHdr;
+ pPush->offData = 0;
+ pPush->offCurrent = 0;
+ pPush->cbCurrent = 0;
+ pPush->ObjInfo = ObjInfo;
+ pPush->fOpenEnded = cbFile == UINT64_MAX;
+
+ /*
+ * Produce and write file headers.
+ */
+ rc = rtZipTarFssWriter_ObjInfoToHdr(pThis, pszPath, &ObjInfo, pszOwnerNm, pszGroupNm, RTZIPTAR_TF_NORMAL);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cbHdrs = pThis->cHdrs * sizeof(pThis->aHdrs[0]);
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, pThis->aHdrs, cbHdrs, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += cbHdrs;
+
+ /*
+ * Complete the object and return.
+ */
+ pPush->offData = pPush->offHdr + cbHdrs;
+ if (cbFile == UINT64_MAX)
+ pPush->cbExpected = (uint64_t)(RTFOFF_MAX - _4K) - pPush->offData;
+ pPush->pParent = pThis;
+ pThis->pPush = pPush;
+
+ *phVfsIos = hVfsIos;
+ return VINF_SUCCESS;
+ }
+ pThis->rcFatal = rc;
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnEnd}
+ */
+static DECLCALLBACK(int) rtZipTarFssWriter_End(void *pvThis)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)pvThis;
+
+ /*
+ * Make sure to complete any pending push file and that rcFatal is fine.
+ */
+ int rc = rtZipTarFssWriter_CompleteCurrentPushFile(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * There are supposed to be two zero headers at the end of the archive.
+ * GNU tar may write more because of the way it does buffering,
+ * libarchive OTOH writes exactly two.
+ */
+ rc = RTVfsIoStrmWrite(pThis->hVfsIos, g_abRTZero4K, RTZIPTAR_BLOCKSIZE * 2, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbWritten += RTZIPTAR_BLOCKSIZE * 2;
+
+ /*
+ * Flush the output.
+ */
+ rc = RTVfsIoStrmFlush(pThis->hVfsIos);
+
+ /*
+ * If we're in update mode, set the end-of-file here to make sure
+ * unwanted bytes are really discarded.
+ */
+ if (RT_SUCCESS(rc) && (pThis->fFlags & RTZIPTAR_C_UPDATE))
+ {
+ RTFOFF cbTarFile = RTVfsFileTell(pThis->hVfsFile);
+ if (cbTarFile >= 0)
+ rc = RTVfsFileSetSize(pThis->hVfsFile, (uint64_t)cbTarFile, RTVFSFILE_SIZE_F_NORMAL);
+ else
+ rc = (int)cbTarFile;
+ }
+
+ /*
+ * Success?
+ */
+ if (RT_SUCCESS(rc))
+ return rc;
+ }
+ pThis->rcFatal = rc;
+ }
+ return rc;
+}
+
+
+/**
+ * Tar filesystem stream operations.
+ */
+static const RTVFSFSSTREAMOPS g_rtZipTarFssOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "TarFsStreamWriter",
+ rtZipTarFssWriter_Close,
+ rtZipTarFssWriter_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipTarFssWriter_Next,
+ rtZipTarFssWriter_Add,
+ rtZipTarFssWriter_PushFile,
+ rtZipTarFssWriter_End,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+RTDECL(int) RTZipTarFsStreamToIoStream(RTVFSIOSTREAM hVfsIosOut, RTZIPTARFORMAT enmFormat,
+ uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertPtrReturn(hVfsIosOut, VERR_INVALID_HANDLE);
+ AssertReturn(enmFormat > RTZIPTARFORMAT_INVALID && enmFormat < RTZIPTARFORMAT_END, VERR_INVALID_PARAMETER);
+ AssertReturn(!(fFlags & ~RTZIPTAR_C_VALID_MASK), VERR_INVALID_FLAGS);
+ AssertReturn(!(fFlags & RTZIPTAR_C_UPDATE), VERR_NOT_SUPPORTED); /* Must use RTZipTarFsStreamForFile! */
+
+ if (enmFormat == RTZIPTARFORMAT_DEFAULT)
+ enmFormat = RTZIPTARFORMAT_GNU;
+ AssertReturn( enmFormat == RTZIPTARFORMAT_GNU
+ || enmFormat == RTZIPTARFORMAT_USTAR
+ , VERR_NOT_IMPLEMENTED); /* Only implementing GNU and USTAR output at the moment. */
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosOut);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPTARFSSTREAMWRITER pThis;
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&g_rtZipTarFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_WRITE,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIosOut;
+ pThis->hVfsFile = RTVfsIoStrmToFile(hVfsIosOut);
+
+ pThis->enmFormat = enmFormat;
+ pThis->fFlags = fFlags;
+ pThis->rcFatal = VINF_SUCCESS;
+
+ pThis->uidOwner = NIL_RTUID;
+ pThis->pszOwner = NULL;
+ pThis->gidGroup = NIL_RTGID;
+ pThis->pszGroup = NULL;
+ pThis->pszPrefix = NULL;
+ pThis->pModTime = NULL;
+ pThis->fFileModeAndMask = ~(RTFMODE)0;
+ pThis->fFileModeOrMask = 0;
+ pThis->fDirModeAndMask = ~(RTFMODE)0;
+ pThis->fDirModeOrMask = 0;
+ pThis->fWriting = true;
+
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosOut);
+ return rc;
+}
+
+
+RTDECL(int) RTZipTarFsStreamForFile(RTVFSFILE hVfsFile, RTZIPTARFORMAT enmFormat, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertReturn(hVfsFile != NIL_RTVFSFILE, VERR_INVALID_HANDLE);
+ AssertReturn(enmFormat > RTZIPTARFORMAT_INVALID && enmFormat < RTZIPTARFORMAT_END, VERR_INVALID_PARAMETER);
+ AssertReturn(!(fFlags & ~RTZIPTAR_C_VALID_MASK), VERR_INVALID_FLAGS);
+
+ if (enmFormat == RTZIPTARFORMAT_DEFAULT)
+ enmFormat = RTZIPTARFORMAT_GNU;
+ AssertReturn( enmFormat == RTZIPTARFORMAT_GNU
+ || enmFormat == RTZIPTARFORMAT_USTAR
+ , VERR_NOT_IMPLEMENTED); /* Only implementing GNU and USTAR output at the moment. */
+
+ RTFOFF const offStart = RTVfsFileTell(hVfsFile);
+ AssertReturn(offStart >= 0, (int)offStart);
+
+ uint32_t cRefs = RTVfsFileRetain(hVfsFile);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hVfsFile);
+ AssertReturnStmt(hVfsIos != NIL_RTVFSIOSTREAM, RTVfsFileRelease(hVfsFile), VERR_INVALID_HANDLE);
+
+ /*
+ * Retain the input stream and create a new filesystem stream handle.
+ */
+ PRTZIPTARFSSTREAMWRITER pThis;
+ size_t const cbThis = sizeof(*pThis) + (fFlags & RTZIPTAR_C_UPDATE ? sizeof(*pThis->pRead) : 0);
+ RTVFSFSSTREAM hVfsFss;
+ int rc = RTVfsNewFsStream(&g_rtZipTarFssOps, cbThis, NIL_RTVFS, NIL_RTVFSLOCK,
+ fFlags & RTZIPTAR_C_UPDATE ? RTFILE_O_READWRITE : RTFILE_O_WRITE,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIos;
+ pThis->hVfsFile = hVfsFile;
+
+ pThis->enmFormat = enmFormat;
+ pThis->fFlags = fFlags;
+ pThis->rcFatal = VINF_SUCCESS;
+
+ pThis->uidOwner = NIL_RTUID;
+ pThis->pszOwner = NULL;
+ pThis->gidGroup = NIL_RTGID;
+ pThis->pszGroup = NULL;
+ pThis->pszPrefix = NULL;
+ pThis->pModTime = NULL;
+ pThis->fFileModeAndMask = ~(RTFMODE)0;
+ pThis->fFileModeOrMask = 0;
+ pThis->fDirModeAndMask = ~(RTFMODE)0;
+ pThis->fDirModeOrMask = 0;
+ if (!(fFlags & RTZIPTAR_C_UPDATE))
+ pThis->fWriting = true;
+ else
+ {
+ pThis->fWriting = false;
+ pThis->pRead = (PRTZIPTARFSSTREAM)(pThis + 1);
+ rtZipTarReaderInit(pThis->pRead, hVfsIos, (uint64_t)offStart);
+ }
+
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsIoStrmRelease(hVfsIos);
+ RTVfsFileRelease(hVfsFile);
+ return rc;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetOwner(RTVFSFSSTREAM hVfsFss, RTUID uid, const char *pszOwner)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ pThis->uidOwner = uid;
+ if (pThis->pszOwner)
+ {
+ RTStrFree(pThis->pszOwner);
+ pThis->pszOwner = NULL;
+ }
+ if (pszOwner)
+ {
+ pThis->pszOwner = RTStrDup(pszOwner);
+ AssertReturn(pThis->pszOwner, VERR_NO_STR_MEMORY);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetGroup(RTVFSFSSTREAM hVfsFss, RTGID gid, const char *pszGroup)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ pThis->gidGroup = gid;
+ if (pThis->pszGroup)
+ {
+ RTStrFree(pThis->pszGroup);
+ pThis->pszGroup = NULL;
+ }
+ if (pszGroup)
+ {
+ pThis->pszGroup = RTStrDup(pszGroup);
+ AssertReturn(pThis->pszGroup, VERR_NO_STR_MEMORY);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetPrefix(RTVFSFSSTREAM hVfsFss, const char *pszPrefix)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+ AssertReturn(!pszPrefix || *pszPrefix, VERR_INVALID_NAME);
+
+ if (pThis->pszPrefix)
+ {
+ RTStrFree(pThis->pszPrefix);
+ pThis->pszPrefix = NULL;
+ pThis->cchPrefix = 0;
+ }
+ if (pszPrefix)
+ {
+ /*
+ * Make a copy of the prefix, make sure it ends with a slash,
+ * then flip DOS slashes.
+ */
+ size_t cchPrefix = strlen(pszPrefix);
+ char *pszCopy = RTStrAlloc(cchPrefix + 3);
+ AssertReturn(pszCopy, VERR_NO_STR_MEMORY);
+ memcpy(pszCopy, pszPrefix, cchPrefix + 1);
+
+ RTPathEnsureTrailingSeparator(pszCopy, cchPrefix + 3);
+
+#if RTPATH_STYLE != RTPATH_STR_F_STYLE_UNIX
+ char *pszDosSlash = strchr(pszCopy, '\\');
+ while (pszDosSlash)
+ {
+ *pszDosSlash = '/';
+ pszDosSlash = strchr(pszDosSlash + 1, '\\');
+ }
+#endif
+
+ pThis->cchPrefix = cchPrefix + strlen(&pszCopy[cchPrefix]);
+ pThis->pszPrefix = pszCopy;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetModTime(RTVFSFSSTREAM hVfsFss, PCRTTIMESPEC pModificationTime)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ if (pModificationTime)
+ {
+ pThis->ModTime = *pModificationTime;
+ pThis->pModTime = &pThis->ModTime;
+ }
+ else
+ pThis->pModTime = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetFileMode(RTVFSFSSTREAM hVfsFss, RTFMODE fAndMode, RTFMODE fOrMode)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ pThis->fFileModeAndMask = fAndMode | ~RTFS_UNIX_ALL_PERMS;
+ pThis->fFileModeOrMask = fOrMode & RTFS_UNIX_ALL_PERMS;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamSetDirMode(RTVFSFSSTREAM hVfsFss, RTFMODE fAndMode, RTFMODE fOrMode)
+{
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ pThis->fDirModeAndMask = fAndMode | ~RTFS_UNIX_ALL_PERMS;
+ pThis->fDirModeOrMask = fOrMode & RTFS_UNIX_ALL_PERMS;
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipTarFsStreamTruncate(RTVFSFSSTREAM hVfsFss, RTVFSOBJ hVfsObj, bool fAfter)
+{
+ /*
+ * Translate and validate the input.
+ */
+ PRTZIPTARFSSTREAMWRITER pThis = (PRTZIPTARFSSTREAMWRITER)RTVfsFsStreamToPrivate(hVfsFss, &g_rtZipTarFssOps);
+ AssertReturn(pThis, VERR_WRONG_TYPE);
+
+ AssertReturn(hVfsObj != NIL_RTVFSOBJ, VERR_INVALID_HANDLE);
+ PRTZIPTARBASEOBJ pThisObj = rtZipTarFsStreamBaseObjToPrivate(pThis->pRead, hVfsObj);
+ AssertReturn(pThis, VERR_NOT_OWNER);
+
+ AssertReturn(pThis->pRead, VERR_ACCESS_DENIED);
+ AssertReturn(pThis->fFlags & RTZIPTAR_C_UPDATE, VERR_ACCESS_DENIED);
+ AssertReturn(!pThis->fWriting, VERR_WRONG_ORDER);
+
+ /*
+ * Seek to the desired cut-off point and indicate that we've switched to writing.
+ */
+ int rc = RTVfsFileSeek(pThis->hVfsFile, fAfter ? pThisObj->offNextHdr : pThisObj->offHdr,
+ RTFILE_SEEK_BEGIN, NULL /*poffActual*/);
+ if (RT_SUCCESS(rc))
+ pThis->fWriting = true;
+ else
+ pThis->rcFatal = rc;
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/zip/unzipcmd.cpp b/src/VBox/Runtime/common/zip/unzipcmd.cpp
new file mode 100644
index 00000000..58d70289
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/unzipcmd.cpp
@@ -0,0 +1,480 @@
+/* $Id: unzipcmd.cpp $ */
+/** @file
+ * IPRT - A mini UNZIP Command.
+ */
+
+/*
+ * Copyright (C) 2014-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/zip.h>
+#include <iprt/asm.h>
+#include <iprt/getopt.h>
+#include <iprt/file.h>
+#include <iprt/err.h>
+#include <iprt/mem.h>
+#include <iprt/message.h>
+#include <iprt/path.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/stream.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * IPRT UNZIP option structure.
+ */
+typedef struct RTZIPUNZIPCMDOPS
+{
+ /** The operation. */
+ int iOperation;
+ /** The long operation option name. */
+ const char *pszOperation;
+ /** The directory to change into when upacking. */
+ const char *pszDirectory;
+ /** The unzip file name. */
+ const char *pszFile;
+ /** The number of files/directories to be extracted from archive specified. */
+ uint32_t cFiles;
+ /** Wether we're verbose or quiet. */
+ bool fVerbose;
+ /** Skip the restauration of the modification time for directories. */
+ bool fNoModTimeDirectories;
+ /** Skip the restauration of the modification time for files. */
+ bool fNoModTimeFiles;
+ /** Array of files/directories, terminated by a NULL entry. */
+ const char * const *papszFiles;
+} RTZIPUNZIPCMDOPS;
+/** Pointer to the UNZIP options. */
+typedef RTZIPUNZIPCMDOPS *PRTZIPUNZIPCMDOPS;
+
+/**
+ * Callback used by rtZipUnzipDoWithMembers
+ *
+ * @returns rcExit or RTEXITCODE_FAILURE.
+ * @param pOpts The Unzip options.
+ * @param hVfsObj The Unzip object to display
+ * @param pszName The name.
+ * @param rcExit The current exit code.
+ */
+typedef RTEXITCODE (*PFNDOWITHMEMBER)(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes);
+
+
+/**
+ *
+ */
+static RTEXITCODE rtZipUnzipCmdListCallback(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj,
+ const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes)
+{
+ RT_NOREF_PV(pOpts);
+
+ /*
+ * Query all the information.
+ */
+ RTFSOBJINFO UnixInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
+
+ RTTIME time;
+ if (!RTTimeExplode(&time, &UnixInfo.ModificationTime))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Cannot explode time on '%s'", pszName);
+
+ RTPrintf("%9RU64 %04d-%02d-%02d %02d:%02d %s\n",
+ UnixInfo.cbObject,
+ time.i32Year, time.u8Month, time.u8MonthDay,
+ time.u8Hour, time.u8Minute,
+ pszName);
+
+ *pcBytes = UnixInfo.cbObject;
+ return rcExit;
+}
+
+
+/**
+ * Extracts a file.
+ */
+static RTEXITCODE rtZipUnzipCmdExtractFile(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj, RTEXITCODE rcExit,
+ const char *pszDst, PCRTFSOBJINFO pUnixInfo)
+{
+ /*
+ * Open the destination file and create a stream object for it.
+ */
+ uint32_t fOpen = RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_ACCESS_ATTR_DEFAULT
+ | (pUnixInfo->Attr.fMode << RTFILE_O_CREATE_MODE_SHIFT);
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszDst, fOpen);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating file: %Rrc", pszDst, rc);
+
+ RTVFSIOSTREAM hVfsIosDst;
+ rc = RTVfsIoStrmFromRTFile(hFile, fOpen, true /*fLeaveOpen*/, &hVfsIosDst);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Pump the data thru.
+ */
+ RTVFSIOSTREAM hVfsIosSrc = RTVfsObjToIoStream(hVfsObj);
+ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(pUnixInfo->cbObject, _1M));
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Correct the file mode and other attributes.
+ */
+ if (!pOpts->fNoModTimeFiles)
+ {
+ rc = RTFileSetTimes(hFile, NULL, &pUnixInfo->ModificationTime, NULL, NULL);
+ if (RT_FAILURE(rc))
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error setting times: %Rrc", pszDst, rc);
+ }
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error writing out file: %Rrc", pszDst, rc);
+ RTVfsIoStrmRelease(hVfsIosSrc);
+ RTVfsIoStrmRelease(hVfsIosDst);
+ }
+ else
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating I/O stream for file: %Rrc", pszDst, rc);
+
+ return rcExit;
+}
+
+
+/**
+ *
+ */
+static RTEXITCODE rtZipUnzipCmdExtractCallback(PRTZIPUNZIPCMDOPS pOpts, RTVFSOBJ hVfsObj,
+ const char *pszName, RTEXITCODE rcExit, PRTFOFF pcBytes)
+{
+ if (pOpts->fVerbose)
+ RTPrintf("%s\n", pszName);
+
+ /*
+ * Query all the information.
+ */
+ RTFSOBJINFO UnixInfo;
+ int rc = RTVfsObjQueryInfo(hVfsObj, &UnixInfo, RTFSOBJATTRADD_UNIX);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo returned %Rrc on '%s'", rc, pszName);
+
+ *pcBytes = UnixInfo.cbObject;
+
+ char szDst[RTPATH_MAX];
+ rc = RTPathJoin(szDst, sizeof(szDst), pOpts->pszDirectory ? pOpts->pszDirectory : ".", pszName);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Failed to construct destination path for: %Rrc", pszName, rc);
+
+ /*
+ * Extract according to the type.
+ */
+ switch (UnixInfo.Attr.fMode & RTFS_TYPE_MASK)
+ {
+ case RTFS_TYPE_FILE:
+ return rtZipUnzipCmdExtractFile(pOpts, hVfsObj, rcExit, szDst, &UnixInfo);
+
+ case RTFS_TYPE_DIRECTORY:
+ rc = RTDirCreateFullPath(szDst, UnixInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error creating directory: %Rrc", szDst, rc);
+ break;
+
+ default:
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Unknown file type.", pszName);
+ }
+
+ if (!pOpts->fNoModTimeDirectories)
+ {
+ rc = RTPathSetTimesEx(szDst, NULL, &UnixInfo.ModificationTime, NULL, NULL, RTPATH_F_ON_LINK);
+ if (RT_FAILURE(rc) && rc != VERR_NOT_SUPPORTED && rc != VERR_NS_SYMLINK_SET_TIME)
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "%s: Error changing modification time: %Rrc.", pszName, rc);
+ }
+
+ return rcExit;
+}
+
+
+/**
+ * Checks if @a pszName is a member of @a papszNames, optionally returning the
+ * index.
+ *
+ * @returns true if the name is in the list, otherwise false.
+ * @param pszName The name to find.
+ * @param papszNames The array of names.
+ * @param piName Where to optionally return the array index.
+ */
+static bool rtZipUnzipCmdIsNameInArray(const char *pszName, const char * const *papszNames, uint32_t *piName)
+{
+ for (uint32_t iName = 0; papszNames[iName]; ++iName)
+ if (!strcmp(papszNames[iName], pszName))
+ {
+ if (piName)
+ *piName = iName;
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Opens the input archive specified by the options.
+ *
+ * @returns RTEXITCODE_SUCCESS or RTEXITCODE_FAILURE + printed message.
+ * @param pOpts The options.
+ * @param phVfsFss Where to return the UNZIP filesystem stream handle.
+ */
+static RTEXITCODE rtZipUnzipCmdOpenInputArchive(PRTZIPUNZIPCMDOPS pOpts, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Open the input file.
+ */
+ RTVFSIOSTREAM hVfsIos;
+ uint32_t offError = 0;
+ RTERRINFOSTATIC ErrInfo;
+ int rc = RTVfsChainOpenIoStream(pOpts->pszFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN,
+ &hVfsIos, &offError, RTErrInfoInitStatic(&ErrInfo));
+ if (RT_FAILURE(rc))
+ return RTVfsChainMsgErrorExitFailure("RTVfsChainOpenIoStream", pOpts->pszFile, rc, offError, &ErrInfo.Core);
+
+ rc = RTZipPkzipFsStreamFromIoStream(hVfsIos, 0 /*fFlags*/, phVfsFss);
+ RTVfsIoStrmRelease(hVfsIos);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open pkzip filesystem stream: %Rrc", rc);
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+/**
+ * Worker for the --list and --extract commands.
+ *
+ * @returns The appropriate exit code.
+ * @param pOpts The Unzip options.
+ * @param pfnCallback The command specific callback.
+ */
+static RTEXITCODE rtZipUnzipDoWithMembers(PRTZIPUNZIPCMDOPS pOpts, PFNDOWITHMEMBER pfnCallback,
+ uint32_t *pcFiles, PRTFOFF pcBytes)
+{
+ /*
+ * Allocate a bitmap to go with the file list. This will be used to
+ * indicate which files we've processed and which not.
+ */
+ uint32_t *pbmFound = NULL;
+ if (pOpts->cFiles)
+ {
+ pbmFound = (uint32_t *)RTMemAllocZ(((pOpts->cFiles + 31) / 32) * sizeof(uint32_t));
+ if (!pbmFound)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to allocate the found-file-bitmap");
+ }
+
+ uint32_t cFiles = 0;
+ RTFOFF cBytesSum = 0;
+
+ /*
+ * Open the input archive.
+ */
+ RTVFSFSSTREAM hVfsFssIn;
+ RTEXITCODE rcExit = rtZipUnzipCmdOpenInputArchive(pOpts, &hVfsFssIn);
+ if (rcExit == RTEXITCODE_SUCCESS)
+ {
+ /*
+ * Process the stream.
+ */
+ for (;;)
+ {
+ /*
+ * Retrieve the next object.
+ */
+ char *pszName;
+ RTVFSOBJ hVfsObj;
+ int rc = RTVfsFsStrmNext(hVfsFssIn, &pszName, NULL, &hVfsObj);
+ if (RT_FAILURE(rc))
+ {
+ if (rc != VERR_EOF)
+ rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext returned %Rrc", rc);
+ break;
+ }
+
+ /*
+ * Should we process this object?
+ */
+ uint32_t iFile = UINT32_MAX;
+ if ( !pOpts->cFiles
+ || rtZipUnzipCmdIsNameInArray(pszName, pOpts->papszFiles, &iFile))
+ {
+ if (pbmFound)
+ ASMBitSet(pbmFound, iFile);
+
+ RTFOFF cBytes = 0;
+ rcExit = pfnCallback(pOpts, hVfsObj, pszName, rcExit, &cBytes);
+
+ cBytesSum += cBytes;
+ cFiles++;
+ }
+
+ /*
+ * Release the current object and string.
+ */
+ RTVfsObjRelease(hVfsObj);
+ RTStrFree(pszName);
+ }
+
+ /*
+ * Complain about any files we didn't find.
+ */
+ for (uint32_t iFile = 0; iFile <pOpts->cFiles; iFile++)
+ if (!ASMBitTest(pbmFound, iFile))
+ {
+ RTMsgError("%s: Was not found in the archive", pOpts->papszFiles[iFile]);
+ rcExit = RTEXITCODE_FAILURE;
+ }
+
+ RTVfsFsStrmRelease(hVfsFssIn);
+ }
+
+ RTMemFree(pbmFound);
+
+ *pcFiles = cFiles;
+ *pcBytes = cBytesSum;
+
+ return RTEXITCODE_SUCCESS;
+}
+
+
+RTDECL(RTEXITCODE) RTZipUnzipCmd(unsigned cArgs, char **papszArgs)
+{
+ /*
+ * Parse the command line.
+ */
+ static const RTGETOPTDEF s_aOptions[] =
+ {
+ /* options */
+ { NULL, 'c', RTGETOPT_REQ_NOTHING }, /* extract files to stdout/stderr */
+ { NULL, 'd', RTGETOPT_REQ_STRING }, /* extract files to this directory */
+ { NULL, 'l', RTGETOPT_REQ_NOTHING }, /* list archive files (short format) */
+ { NULL, 'p', RTGETOPT_REQ_NOTHING }, /* extract files to stdout */
+ { NULL, 't', RTGETOPT_REQ_NOTHING }, /* test archive files */
+ { NULL, 'v', RTGETOPT_REQ_NOTHING }, /* verbose */
+
+ /* modifiers */
+ { NULL, 'a', RTGETOPT_REQ_NOTHING }, /* convert text files */
+ { NULL, 'b', RTGETOPT_REQ_NOTHING }, /* no conversion, treat as binary */
+ { NULL, 'D', RTGETOPT_REQ_NOTHING }, /* don't restore timestamps for directories
+ (and files) */
+ };
+
+ RTGETOPTSTATE GetState;
+ int rc = RTGetOptInit(&GetState, cArgs, papszArgs, s_aOptions, RT_ELEMENTS(s_aOptions), 1,
+ RTGETOPTINIT_FLAGS_OPTS_FIRST);
+ if (RT_FAILURE(rc))
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOpt failed: %Rrc", rc);
+
+ RTZIPUNZIPCMDOPS Opts;
+ RT_ZERO(Opts);
+
+ RTGETOPTUNION ValueUnion;
+ while ( (rc = RTGetOpt(&GetState, &ValueUnion)) != 0
+ && rc != VINF_GETOPT_NOT_OPTION)
+ {
+ switch (rc)
+ {
+ case 'd':
+ if (Opts.pszDirectory)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX, "You may only specify -d once");
+ Opts.pszDirectory = ValueUnion.psz;
+ break;
+
+ case 'D':
+ if (!Opts.fNoModTimeDirectories)
+ Opts.fNoModTimeDirectories = true; /* -D */
+ else
+ Opts.fNoModTimeFiles = true; /* -DD */
+ break;
+
+ case 'l':
+ case 't': /* treat 'test' like 'list' */
+ if (Opts.iOperation)
+ return RTMsgErrorExit(RTEXITCODE_SYNTAX,
+ "Conflicting unzip operation (%s already set, now %s)",
+ Opts.pszOperation, ValueUnion.pDef->pszLong);
+ Opts.iOperation = 'l';
+ Opts.pszOperation = ValueUnion.pDef->pszLong;
+ break;
+
+ case 'v':
+ Opts.fVerbose = true;
+ break;
+
+ default:
+ Opts.pszFile = ValueUnion.psz;
+ return RTGetOptPrintError(rc, &ValueUnion);
+ }
+ }
+
+ if (rc == VINF_GETOPT_NOT_OPTION)
+ {
+ Assert((unsigned)GetState.iNext - 1 <= cArgs);
+ Opts.pszFile = papszArgs[GetState.iNext - 1];
+ if ((unsigned)GetState.iNext <= cArgs)
+ {
+ Opts.papszFiles = (const char * const *)&papszArgs[GetState.iNext];
+ Opts.cFiles = cArgs - GetState.iNext;
+ }
+ }
+
+ if (!Opts.pszFile)
+ return RTMsgErrorExit(RTEXITCODE_FAILURE, "No input archive specified");
+
+ RTFOFF cBytes = 0;
+ uint32_t cFiles = 0;
+ switch (Opts.iOperation)
+ {
+ case 'l':
+ {
+ RTPrintf(" Length Date Time Name\n"
+ "--------- ---------- ----- ----\n");
+ RTEXITCODE rcExit = rtZipUnzipDoWithMembers(&Opts, rtZipUnzipCmdListCallback, &cFiles, &cBytes);
+ RTPrintf("--------- -------\n"
+ "%9RU64 %u file%s\n",
+ cBytes, cFiles, cFiles != 1 ? "s" : "");
+
+ return rcExit;
+ }
+
+ default:
+ return rtZipUnzipDoWithMembers(&Opts, rtZipUnzipCmdExtractCallback, &cFiles, &cBytes);
+ }
+}
diff --git a/src/VBox/Runtime/common/zip/xarvfs.cpp b/src/VBox/Runtime/common/zip/xarvfs.cpp
new file mode 100644
index 00000000..571a51c7
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/xarvfs.cpp
@@ -0,0 +1,2156 @@
+/* $Id: xarvfs.cpp $ */
+/** @file
+ * IPRT - XAR Virtual Filesystem.
+ */
+
+/*
+ * Copyright (C) 2010-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "internal/iprt.h"
+#include <iprt/zip.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/ctype.h>
+#include <iprt/err.h>
+#include <iprt/md5.h>
+#include <iprt/poll.h>
+#include <iprt/file.h>
+#include <iprt/sha.h>
+#include <iprt/string.h>
+#include <iprt/vfs.h>
+#include <iprt/vfslowlevel.h>
+#include <iprt/formats/xar.h>
+#include <iprt/cpp/xml.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name Hash state
+ * @{ */
+#define RTZIPXAR_HASH_PENDING 0
+#define RTZIPXAR_HASH_OK 1
+#define RTZIPXAR_HASH_FAILED_ARCHIVED 2
+#define RTZIPXAR_HASH_FAILED_EXTRACTED 3
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Hash digest value union for the supported XAR hash functions.
+ * @todo This could be generalized in iprt/checksum.h or somewhere.
+ */
+typedef union RTZIPXARHASHDIGEST
+{
+ uint8_t abMd5[RTMD5_HASH_SIZE];
+ uint8_t abSha1[RTSHA1_HASH_SIZE];
+} RTZIPXARHASHDIGEST;
+/** Pointer to a XAR hash digest union. */
+typedef RTZIPXARHASHDIGEST *PRTZIPXARHASHDIGEST;
+/** Pointer to a const XAR hash digest union. */
+typedef RTZIPXARHASHDIGEST *PCRTZIPXARHASHDIGEST;
+
+/**
+ * Hash context union.
+ */
+typedef union RTZIPXARHASHCTX
+{
+ RTMD5CONTEXT Md5;
+ RTSHA1CONTEXT Sha1;
+} RTZIPXARHASHCTX;
+/** Pointer to a hash context union. */
+typedef RTZIPXARHASHCTX *PRTZIPXARHASHCTX;
+
+/**
+ * XAR reader instance data.
+ */
+typedef struct RTZIPXARREADER
+{
+ /** The TOC XML element. */
+ xml::ElementNode const *pToc;
+ /** The TOC XML document. */
+ xml::Document *pDoc;
+
+ /** The current file. */
+ xml::ElementNode const *pCurFile;
+ /** The depth of the current file, with 0 being the root level. */
+ uint32_t cCurDepth;
+} RTZIPXARREADER;
+/** Pointer to the XAR reader instance data. */
+typedef RTZIPXARREADER *PRTZIPXARREADER;
+
+/**
+ * Xar directory, character device, block device, fifo socket or symbolic link.
+ */
+typedef struct RTZIPXARBASEOBJ
+{
+ /** The file TOC element. */
+ xml::ElementNode const *pFileElem;
+ /** RTFS_TYPE_XXX value for the object. */
+ RTFMODE fModeType;
+} RTZIPXARBASEOBJ;
+/** Pointer to a XAR filesystem stream base object. */
+typedef RTZIPXARBASEOBJ *PRTZIPXARBASEOBJ;
+
+
+/**
+ * XAR data encoding.
+ */
+typedef enum RTZIPXARENCODING
+{
+ RTZIPXARENCODING_INVALID = 0,
+ RTZIPXARENCODING_STORE,
+ RTZIPXARENCODING_GZIP,
+ RTZIPXARENCODING_UNSUPPORTED,
+ RTZIPXARENCODING_END
+} RTZIPXARENCODING;
+
+
+/**
+ * Data stream attributes.
+ */
+typedef struct RTZIPXARDATASTREAM
+{
+ /** Offset of the data in the stream.
+ * @remarks The I/O stream and file constructor will adjust this so that it
+ * relative to the start of the input stream, instead of the first byte
+ * after the TOC. */
+ RTFOFF offData;
+ /** The size of the archived data. */
+ RTFOFF cbDataArchived;
+ /** The size of the extracted data. */
+ RTFOFF cbDataExtracted;
+ /** The encoding of the archived ata. */
+ RTZIPXARENCODING enmEncoding;
+ /** The hash function used for the archived data. */
+ uint8_t uHashFunArchived;
+ /** The hash function used for the extracted data. */
+ uint8_t uHashFunExtracted;
+ /** The digest of the archived data. */
+ RTZIPXARHASHDIGEST DigestArchived;
+ /** The digest of the extracted data. */
+ RTZIPXARHASHDIGEST DigestExtracted;
+} RTZIPXARDATASTREAM;
+/** Pointer to XAR data stream attributes. */
+typedef RTZIPXARDATASTREAM *PRTZIPXARDATASTREAM;
+
+
+/**
+ * Xar file represented as a VFS I/O stream.
+ */
+typedef struct RTZIPXARIOSTREAM
+{
+ /** The basic XAR object data. */
+ RTZIPXARBASEOBJ BaseObj;
+ /** The attributes of the primary data stream. */
+ RTZIPXARDATASTREAM DataAttr;
+ /** The current file position in the archived file. */
+ RTFOFF offCurPos;
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+ /** Set if we've reached the end of the file or if the next object in the
+ * file system stream has been requested. */
+ bool fEndOfStream;
+ /** Whether the stream is seekable. */
+ bool fSeekable;
+ /** Hash state. */
+ uint8_t uHashState;
+ /** The size of the file that we've currently hashed.
+ * We use this to check whether the user skips part of the file while reading
+ * and when to compare the digests. */
+ RTFOFF cbDigested;
+ /** The digest of the archived data. */
+ RTZIPXARHASHCTX CtxArchived;
+ /** The digest of the extracted data. */
+ RTZIPXARHASHCTX CtxExtracted;
+} RTZIPXARIOSTREAM;
+/** Pointer to a the private data of a XAR file I/O stream. */
+typedef RTZIPXARIOSTREAM *PRTZIPXARIOSTREAM;
+
+
+/**
+ * Xar file represented as a VFS file.
+ */
+typedef struct RTZIPXARFILE
+{
+ /** The XAR I/O stream data. */
+ RTZIPXARIOSTREAM Ios;
+ /** The input file. */
+ RTVFSFILE hVfsFile;
+} RTZIPXARFILE;
+/** Pointer to the private data of a XAR file. */
+typedef RTZIPXARFILE *PRTZIPXARFILE;
+
+
+/**
+ * Decompressed I/O stream instance.
+ *
+ * This is just a front that checks digests and other sanity stuff.
+ */
+typedef struct RTZIPXARDECOMPIOS
+{
+ /** The decompressor I/O stream. */
+ RTVFSIOSTREAM hVfsIosDecompressor;
+ /** The raw XAR I/O stream. */
+ RTVFSIOSTREAM hVfsIosRaw;
+ /** Pointer to the raw XAR I/O stream instance data. */
+ PRTZIPXARIOSTREAM pIosRaw;
+ /** The current file position in the archived file. */
+ RTFOFF offCurPos;
+ /** The hash function to use on the extracted data. */
+ uint8_t uHashFunExtracted;
+ /** Hash state on the extracted data. */
+ uint8_t uHashState;
+ /** The digest of the extracted data. */
+ RTZIPXARHASHCTX CtxExtracted;
+ /** The expected digest of the extracted data. */
+ RTZIPXARHASHDIGEST DigestExtracted;
+} RTZIPXARDECOMPIOS;
+/** Pointer to the private data of a XAR decompressed I/O stream. */
+typedef RTZIPXARDECOMPIOS *PRTZIPXARDECOMPIOS;
+
+
+/**
+ * Xar filesystem stream private data.
+ */
+typedef struct RTZIPXARFSSTREAM
+{
+ /** The input I/O stream. */
+ RTVFSIOSTREAM hVfsIos;
+ /** The input file, if the stream is actually a file. */
+ RTVFSFILE hVfsFile;
+
+ /** The start offset in the input I/O stream. */
+ RTFOFF offStart;
+ /** The zero offset in the file which all others are relative to. */
+ RTFOFF offZero;
+
+ /** The hash function we're using (XAR_HASH_XXX). */
+ uint8_t uHashFunction;
+ /** The size of the digest produced by the hash function we're using. */
+ uint8_t cbHashDigest;
+
+ /** Set if we've reached the end of the stream. */
+ bool fEndOfStream;
+ /** Set if we've encountered a fatal error. */
+ int rcFatal;
+
+
+ /** The XAR reader instance data. */
+ RTZIPXARREADER XarReader;
+} RTZIPXARFSSTREAM;
+/** Pointer to a the private data of a XAR filesystem stream. */
+typedef RTZIPXARFSSTREAM *PRTZIPXARFSSTREAM;
+
+
+/**
+ * Hashes a block of data.
+ *
+ * @param uHashFunction The hash function to use.
+ * @param pvSrc The data to hash.
+ * @param cbSrc The size of the data to hash.
+ * @param pHashDigest Where to return the message digest.
+ */
+static void rtZipXarCalcHash(uint32_t uHashFunction, void const *pvSrc, size_t cbSrc, PRTZIPXARHASHDIGEST pHashDigest)
+{
+ switch (uHashFunction)
+ {
+ case XAR_HASH_SHA1:
+ RTSha1(pvSrc, cbSrc, pHashDigest->abSha1);
+ break;
+ case XAR_HASH_MD5:
+ RTMd5(pvSrc, cbSrc, pHashDigest->abMd5);
+ break;
+ default:
+ RT_ZERO(*pHashDigest);
+ break;
+ }
+}
+
+
+/**
+ * Initializes a hash context.
+ *
+ * @param pCtx Pointer to the context union.
+ * @param uHashFunction The hash function to use.
+ */
+static void rtZipXarHashInit(PRTZIPXARHASHCTX pCtx, uint32_t uHashFunction)
+{
+ switch (uHashFunction)
+ {
+ case XAR_HASH_SHA1:
+ RTSha1Init(&pCtx->Sha1);
+ break;
+ case XAR_HASH_MD5:
+ RTMd5Init(&pCtx->Md5);;
+ break;
+ default:
+ RT_ZERO(*pCtx);
+ break;
+ }
+}
+
+
+/**
+ * Adds a block to the hash calculation.
+ *
+ * @param pCtx Pointer to the context union.
+ * @param uHashFunction The hash function to use.
+ * @param pvSrc The data to add to the hash.
+ * @param cbSrc The size of the data.
+ */
+static void rtZipXarHashUpdate(PRTZIPXARHASHCTX pCtx, uint32_t uHashFunction, void const *pvSrc, size_t cbSrc)
+{
+ switch (uHashFunction)
+ {
+ case XAR_HASH_SHA1:
+ RTSha1Update(&pCtx->Sha1, pvSrc, cbSrc);
+ break;
+ case XAR_HASH_MD5:
+ RTMd5Update(&pCtx->Md5, pvSrc, cbSrc);
+ break;
+ }
+}
+
+
+/**
+ * Finalizes the hash, producing the message digest.
+ *
+ * @param pCtx Pointer to the context union.
+ * @param uHashFunction The hash function to use.
+ * @param pHashDigest Where to return the message digest.
+ */
+static void rtZipXarHashFinal(PRTZIPXARHASHCTX pCtx, uint32_t uHashFunction, PRTZIPXARHASHDIGEST pHashDigest)
+{
+ switch (uHashFunction)
+ {
+ case XAR_HASH_SHA1:
+ RTSha1Final(&pCtx->Sha1, pHashDigest->abSha1);
+ break;
+ case XAR_HASH_MD5:
+ RTMd5Final(pHashDigest->abMd5, &pCtx->Md5);
+ break;
+ default:
+ RT_ZERO(*pHashDigest);
+ break;
+ }
+}
+
+
+/**
+ * Compares two hash digests.
+ *
+ * @returns true if equal, false if not.
+ * @param uHashFunction The hash function to use.
+ * @param pHashDigest1 The first hash digest.
+ * @param pHashDigest2 The second hash digest.
+ */
+static bool rtZipXarHashIsEqual(uint32_t uHashFunction, PRTZIPXARHASHDIGEST pHashDigest1, PRTZIPXARHASHDIGEST pHashDigest2)
+{
+ switch (uHashFunction)
+ {
+ case XAR_HASH_SHA1:
+ return memcmp(pHashDigest1->abSha1, pHashDigest2->abSha1, sizeof(pHashDigest1->abSha1)) == 0;
+ case XAR_HASH_MD5:
+ return memcmp(pHashDigest1->abMd5, pHashDigest2->abMd5, sizeof(pHashDigest1->abMd5)) == 0;
+ default:
+ return true;
+ }
+}
+
+
+/**
+ * Gets the 'offset', 'size' and optionally 'length' sub elements.
+ *
+ * @returns IPRT status code.
+ * @param pElement The parent element.
+ * @param poff Where to return the offset value.
+ * @param pcbSize Where to return the size value.
+ * @param pcbLength Where to return the length value, optional.
+ */
+static int rtZipXarGetOffsetSizeLengthFromElem(xml::ElementNode const *pElement,
+ PRTFOFF poff, PRTFOFF pcbSize, PRTFOFF pcbLength)
+{
+ /*
+ * The offset.
+ */
+ xml::ElementNode const *pElem = pElement->findChildElement("offset");
+ if (!pElem)
+ return VERR_XAR_MISSING_OFFSET_ELEMENT;
+ const char *pszValue = pElem->getValue();
+ if (!pszValue)
+ return VERR_XAR_BAD_OFFSET_ELEMENT;
+
+ int rc = RTStrToInt64Full(pszValue, 0, poff);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG
+ || *poff > RTFOFF_MAX / 2 /* make sure to not overflow calculating offsets. */
+ || *poff < 0)
+ return VERR_XAR_BAD_OFFSET_ELEMENT;
+
+ /*
+ * The 'size' stored in the archive.
+ */
+ pElem = pElement->findChildElement("size");
+ if (!pElem)
+ return VERR_XAR_MISSING_SIZE_ELEMENT;
+
+ pszValue = pElem->getValue();
+ if (!pszValue)
+ return VERR_XAR_BAD_SIZE_ELEMENT;
+
+ rc = RTStrToInt64Full(pszValue, 0, pcbSize);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG
+ || *pcbSize >= RTFOFF_MAX - _1M
+ || *pcbSize < 0)
+ return VERR_XAR_BAD_SIZE_ELEMENT;
+ AssertCompile(RTFOFF_MAX == UINT64_MAX / 2);
+
+ /*
+ * The 'length' of the uncompressed data. Not present for checksums, so
+ * the caller might not want it.
+ */
+ if (pcbLength)
+ {
+ pElem = pElement->findChildElement("length");
+ if (!pElem)
+ return VERR_XAR_MISSING_LENGTH_ELEMENT;
+
+ pszValue = pElem->getValue();
+ if (!pszValue)
+ return VERR_XAR_BAD_LENGTH_ELEMENT;
+
+ rc = RTStrToInt64Full(pszValue, 0, pcbLength);
+ if ( RT_FAILURE(rc)
+ || rc == VWRN_NUMBER_TOO_BIG
+ || *pcbLength >= RTFOFF_MAX - _1M
+ || *pcbLength < 0)
+ return VERR_XAR_BAD_LENGTH_ELEMENT;
+ AssertCompile(RTFOFF_MAX == UINT64_MAX / 2);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Convers a checksum style value into a XAR hash function number.
+ *
+ * @returns IPRT status code.
+ * @param pszStyle The XAR checksum style.
+ * @param puHashFunction Where to return the hash function number on success.
+ */
+static int rtZipXarParseChecksumStyle(const char *pszStyle, uint8_t *puHashFunction)
+{
+ size_t cchStyle = strlen(pszStyle);
+ if ( cchStyle == 4
+ && (pszStyle[0] == 's' || pszStyle[0] == 'S')
+ && (pszStyle[1] == 'h' || pszStyle[1] == 'H')
+ && (pszStyle[2] == 'a' || pszStyle[2] == 'A')
+ && pszStyle[3] == '1' )
+ *puHashFunction = XAR_HASH_SHA1;
+ else if ( cchStyle == 3
+ && (pszStyle[0] == 'm' || pszStyle[0] == 'M')
+ && (pszStyle[1] == 'd' || pszStyle[1] == 'D')
+ && pszStyle[2] == '5' )
+ *puHashFunction = XAR_HASH_MD5;
+ else if ( cchStyle == 4
+ && (pszStyle[0] == 'n' || pszStyle[0] == 'N')
+ && (pszStyle[1] == 'o' || pszStyle[1] == 'O')
+ && (pszStyle[2] == 'n' || pszStyle[2] == 'N')
+ && (pszStyle[3] == 'e' || pszStyle[3] == 'E') )
+ *puHashFunction = XAR_HASH_NONE;
+ else
+ {
+ *puHashFunction = UINT8_MAX;
+ return VERR_XAR_BAD_CHECKSUM_ELEMENT;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses a checksum element typically found under 'data'.
+ *
+ * @returns IPRT status code.
+ * @param pParentElem The parent element ('data').
+ * @param pszName The name of the element, like 'checksum-archived' or
+ * 'checksum-extracted'.
+ * @param puHashFunction Where to return the XAR hash function number.
+ * @param pDigest Where to return the expected message digest.
+ */
+static int rtZipXarParseChecksumElem(xml::ElementNode const *pParentElem, const char *pszName,
+ uint8_t *puHashFunction, PRTZIPXARHASHDIGEST pDigest)
+{
+ /* Default is no checksum. */
+ *puHashFunction = XAR_HASH_NONE;
+ RT_ZERO(*pDigest);
+
+ xml::ElementNode const *pChecksumElem = pParentElem->findChildElement(pszName);
+ if (!pChecksumElem)
+ return VINF_SUCCESS;
+
+ /* The style. */
+ const char *pszStyle = pChecksumElem->findAttributeValue("style");
+ if (!pszStyle)
+ return VERR_XAR_BAD_CHECKSUM_ELEMENT;
+ int rc = rtZipXarParseChecksumStyle(pszStyle, puHashFunction);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (*puHashFunction == XAR_HASH_NONE)
+ return VINF_SUCCESS;
+
+ /* The digest. */
+ const char *pszDigest = pChecksumElem->getValue();
+ if (!pszDigest)
+ return VERR_XAR_BAD_CHECKSUM_ELEMENT;
+
+ switch (*puHashFunction)
+ {
+ case XAR_HASH_SHA1:
+ rc = RTSha1FromString(pszDigest, pDigest->abSha1);
+ break;
+ case XAR_HASH_MD5:
+ rc = RTMd5FromString(pszDigest, pDigest->abMd5);
+ break;
+ default:
+ rc = VERR_INTERNAL_ERROR_2;
+ }
+ return rc;
+}
+
+
+/**
+ * Gets all the attributes of the primary data stream.
+ *
+ * @returns IPRT status code.
+ * @param pFileElem The file element, we'll be parsing the 'data'
+ * sub element of this.
+ * @param pDataAttr Where to return the attributes.
+ */
+static int rtZipXarGetDataStreamAttributes(xml::ElementNode const *pFileElem, PRTZIPXARDATASTREAM pDataAttr)
+{
+ /*
+ * Get the data element.
+ */
+ xml::ElementNode const *pDataElem = pFileElem->findChildElement("data");
+ if (!pDataElem)
+ return VERR_XAR_MISSING_DATA_ELEMENT;
+
+ /*
+ * Checksums.
+ */
+ int rc = rtZipXarParseChecksumElem(pDataElem, "extracted-checksum",
+ &pDataAttr->uHashFunExtracted, &pDataAttr->DigestExtracted);
+ if (RT_FAILURE(rc))
+ return rc;
+ rc = rtZipXarParseChecksumElem(pDataElem, "archived-checksum",
+ &pDataAttr->uHashFunArchived, &pDataAttr->DigestArchived);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * The encoding.
+ */
+ const char *pszEncoding = pDataElem->findChildElementAttributeValueP("encoding", "style");
+ if (!pszEncoding)
+ return VERR_XAR_NO_ENCODING;
+ if (!strcmp(pszEncoding, "application/octet-stream"))
+ pDataAttr->enmEncoding = RTZIPXARENCODING_STORE;
+ else if (!strcmp(pszEncoding, "application/x-gzip"))
+ pDataAttr->enmEncoding = RTZIPXARENCODING_GZIP;
+ else
+ pDataAttr->enmEncoding = RTZIPXARENCODING_UNSUPPORTED;
+
+ /*
+ * The data offset and the compressed and uncompressed sizes.
+ */
+ rc = rtZipXarGetOffsetSizeLengthFromElem(pDataElem, &pDataAttr->offData,
+ &pDataAttr->cbDataExtracted, &pDataAttr->cbDataArchived);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* No zero padding or other alignment crap, please. */
+ if ( pDataAttr->enmEncoding == RTZIPXARENCODING_STORE
+ && pDataAttr->cbDataExtracted != pDataAttr->cbDataArchived)
+ return VERR_XAR_ARCHIVED_AND_EXTRACTED_SIZES_MISMATCH;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Parses a timestamp.
+ *
+ * We consider all timestamps optional, and will only fail (return @c false) on
+ * parse errors. If the specified element isn't found, we'll return epoc time.
+ *
+ * @returns boolean success indicator.
+ * @param pParent The parent element (typically 'file').
+ * @param pszChild The name of the child element.
+ * @param pTimeSpec Where to return the timespec on success.
+ */
+static bool rtZipXarParseTimestamp(const xml::ElementNode *pParent, const char *pszChild, PRTTIMESPEC pTimeSpec)
+{
+ const char *pszValue = pParent->findChildElementValueP(pszChild);
+ if (pszValue)
+ {
+ if (RTTimeSpecFromString(pTimeSpec, pszValue))
+ return true;
+ return false;
+ }
+ RTTimeSpecSetNano(pTimeSpec, 0);
+ return true;
+}
+
+
+/**
+ * Gets the next file element in the TOC.
+ *
+ * @returns Pointer to the next file, NULL if we've reached the end.
+ * @param pCurFile The current element.
+ * @param pcCurDepth Depth gauge we update when decending and
+ * acending thru the tree.
+ */
+static xml::ElementNode const *rtZipXarGetNextFileElement(xml::ElementNode const *pCurFile, uint32_t *pcCurDepth)
+{
+ /*
+ * Consider children first.
+ */
+ xml::ElementNode const *pChild = pCurFile->findChildElement("file");
+ if (pChild)
+ {
+ *pcCurDepth += 1;
+ return pChild;
+ }
+
+ /*
+ * Siblings and ancestor siblings.
+ */
+ for (;;)
+ {
+ xml::ElementNode const *pSibling = pCurFile->findNextSibilingElement("file");
+ if (pSibling != NULL)
+ return pSibling;
+
+ if (*pcCurDepth == 0)
+ break;
+ *pcCurDepth -= 1;
+ pCurFile = static_cast<const xml::ElementNode *>(pCurFile->getParent());
+ AssertBreak(pCurFile);
+ Assert(pCurFile->nameEquals("file"));
+ }
+
+ return NULL;
+}
+
+
+
+/*
+ *
+ * T h e V F S F i l e s y s t e m S t r e a m B i t s.
+ * T h e V F S F i l e s y s t e m S t r e a m B i t s.
+ * T h e V F S F i l e s y s t e m S t r e a m B i t s.
+ *
+ */
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipXarFssBaseObj_Close(void *pvThis)
+{
+ PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis;
+
+ /* Currently there is nothing we really have to do here. */
+ NOREF(pThis);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipXarFssBaseObj_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis;
+
+ /*
+ * Get the common data.
+ */
+
+ /* Sizes. */
+ if (pThis->fModeType == RTFS_TYPE_FILE)
+ {
+ PRTZIPXARIOSTREAM pThisIos = RT_FROM_MEMBER(pThis, RTZIPXARIOSTREAM, BaseObj);
+ pObjInfo->cbObject = pThisIos->DataAttr.cbDataArchived; /* Modified by decomp ios. */
+ pObjInfo->cbAllocated = pThisIos->DataAttr.cbDataArchived;
+ }
+ else
+ {
+ pObjInfo->cbObject = 0;
+ pObjInfo->cbAllocated = 0;
+ }
+
+ /* The file mode. */
+ if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("mode", 0755, &pObjInfo->Attr.fMode)))
+ return VERR_XAR_BAD_FILE_MODE;
+ if (pObjInfo->Attr.fMode & RTFS_TYPE_MASK)
+ return VERR_XAR_BAD_FILE_MODE;
+ pObjInfo->Attr.fMode &= RTFS_UNIX_MASK & ~RTFS_TYPE_MASK;
+ pObjInfo->Attr.fMode |= pThis->fModeType;
+
+ /* File times. */
+ if (RT_UNLIKELY(!rtZipXarParseTimestamp(pThis->pFileElem, "atime", &pObjInfo->AccessTime)))
+ return VERR_XAR_BAD_FILE_TIMESTAMP;
+ if (RT_UNLIKELY(!rtZipXarParseTimestamp(pThis->pFileElem, "ctime", &pObjInfo->ChangeTime)))
+ return VERR_XAR_BAD_FILE_TIMESTAMP;
+ if (RT_UNLIKELY(!rtZipXarParseTimestamp(pThis->pFileElem, "mtime", &pObjInfo->ModificationTime)))
+ return VERR_XAR_BAD_FILE_TIMESTAMP;
+ pObjInfo->BirthTime = RTTimeSpecGetNano(&pObjInfo->AccessTime) <= RTTimeSpecGetNano(&pObjInfo->ChangeTime)
+ ? pObjInfo->AccessTime : pObjInfo->ChangeTime;
+ if (RTTimeSpecGetNano(&pObjInfo->BirthTime) > RTTimeSpecGetNano(&pObjInfo->ModificationTime))
+ pObjInfo->BirthTime = pObjInfo->ModificationTime;
+
+ /*
+ * Copy the desired data.
+ */
+ switch (enmAddAttr)
+ {
+ case RTFSOBJATTRADD_NOTHING:
+ case RTFSOBJATTRADD_UNIX:
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX;
+ if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("uid", 0, &pObjInfo->Attr.u.Unix.uid)))
+ return VERR_XAR_BAD_FILE_UID;
+ if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("gid", 0, &pObjInfo->Attr.u.Unix.gid)))
+ return VERR_XAR_BAD_FILE_GID;
+ if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("deviceno", 0, &pObjInfo->Attr.u.Unix.INodeIdDevice)))
+ return VERR_XAR_BAD_FILE_DEVICE_NO;
+ if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("inode", 0, &pObjInfo->Attr.u.Unix.INodeId)))
+ return VERR_XAR_BAD_FILE_INODE;
+ pObjInfo->Attr.u.Unix.cHardlinks = 1;
+ pObjInfo->Attr.u.Unix.fFlags = 0;
+ pObjInfo->Attr.u.Unix.GenerationId = 0;
+ pObjInfo->Attr.u.Unix.Device = 0;
+ break;
+
+ case RTFSOBJATTRADD_UNIX_OWNER:
+ {
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_OWNER;
+ if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("uid", 0, &pObjInfo->Attr.u.Unix.uid)))
+ return VERR_XAR_BAD_FILE_UID;
+ const char *pszUser = pThis->pFileElem->findChildElementValueP("user");
+ if (pszUser)
+ RTStrCopy(pObjInfo->Attr.u.UnixOwner.szName, sizeof(pObjInfo->Attr.u.UnixOwner.szName), pszUser);
+ else
+ pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
+ break;
+ }
+
+ case RTFSOBJATTRADD_UNIX_GROUP:
+ {
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_UNIX_GROUP;
+ if (RT_UNLIKELY(!pThis->pFileElem->getChildElementValueDefP("gid", 0, &pObjInfo->Attr.u.Unix.gid)))
+ return VERR_XAR_BAD_FILE_GID;
+ const char *pszGroup = pThis->pFileElem->findChildElementValueP("group");
+ if (pszGroup)
+ RTStrCopy(pObjInfo->Attr.u.UnixGroup.szName, sizeof(pObjInfo->Attr.u.UnixGroup.szName), pszGroup);
+ else
+ pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
+ break;
+ }
+
+ case RTFSOBJATTRADD_EASIZE:
+ pObjInfo->Attr.enmAdditional = RTFSOBJATTRADD_EASIZE;
+ RT_ZERO(pObjInfo->Attr.u);
+ break;
+
+ default:
+ return VERR_NOT_SUPPORTED;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Xar filesystem base object operations.
+ */
+static const RTVFSOBJOPS g_rtZipXarFssBaseObjOps =
+{
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_BASE,
+ "XarFsStream::Obj",
+ rtZipXarFssBaseObj_Close,
+ rtZipXarFssBaseObj_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipXarFssIos_Close(void *pvThis)
+{
+ PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ return rtZipXarFssBaseObj_Close(&pThis->BaseObj);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipXarFssIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis;
+ return rtZipXarFssBaseObj_QueryInfo(&pThis->BaseObj, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipXarFssIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis;
+ AssertReturn(off >= -1, VERR_INVALID_PARAMETER);
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INVALID_PARAMETER);
+
+ /*
+ * Fend of reads beyond the end of the stream here. If
+ */
+ if (off == -1)
+ off = pThis->offCurPos;
+ if (off < 0 || off > pThis->DataAttr.cbDataArchived)
+ return VERR_EOF;
+ if (pThis->fEndOfStream)
+ {
+ if (off >= pThis->DataAttr.cbDataArchived)
+ return pcbRead ? VINF_EOF : VERR_EOF;
+ if (!pThis->fSeekable)
+ return VERR_SEEK_ON_DEVICE;
+ pThis->fEndOfStream = false;
+ }
+
+ size_t cbToRead = pSgBuf->paSegs[0].cbSeg;
+ uint64_t cbLeft = pThis->DataAttr.cbDataArchived - off;
+ if (cbToRead > cbLeft)
+ {
+ if (!pcbRead)
+ return VERR_EOF;
+ cbToRead = (size_t)cbLeft;
+ }
+
+ /*
+ * Do the reading.
+ */
+ size_t cbReadStack = 0;
+ if (!pcbRead)
+ pcbRead = &cbReadStack;
+ int rc = RTVfsIoStrmReadAt(pThis->hVfsIos, off + pThis->DataAttr.offData, pSgBuf->paSegs[0].pvSeg,
+ cbToRead, fBlocking, pcbRead);
+
+ /* Feed the hashes. */
+ size_t cbActuallyRead = *pcbRead;
+ if (pThis->uHashState == RTZIPXAR_HASH_PENDING)
+ {
+ if (pThis->offCurPos == pThis->cbDigested)
+ {
+ rtZipXarHashUpdate(&pThis->CtxArchived, pThis->DataAttr.uHashFunArchived, pSgBuf->paSegs[0].pvSeg, cbActuallyRead);
+ rtZipXarHashUpdate(&pThis->CtxExtracted, pThis->DataAttr.uHashFunExtracted, pSgBuf->paSegs[0].pvSeg, cbActuallyRead);
+ pThis->cbDigested += cbActuallyRead;
+ }
+ else if ( pThis->cbDigested > pThis->offCurPos
+ && pThis->cbDigested < (RTFOFF)(pThis->offCurPos + cbActuallyRead))
+ {
+ size_t offHash = pThis->cbDigested - pThis->offCurPos;
+ void const *pvHash = (uint8_t const *)pSgBuf->paSegs[0].pvSeg + offHash;
+ size_t cbHash = cbActuallyRead - offHash;
+ rtZipXarHashUpdate(&pThis->CtxArchived, pThis->DataAttr.uHashFunArchived, pvHash, cbHash);
+ rtZipXarHashUpdate(&pThis->CtxExtracted, pThis->DataAttr.uHashFunExtracted, pvHash, cbHash);
+ pThis->cbDigested += cbHash;
+ }
+ }
+
+ /* Update the file position. */
+ pThis->offCurPos += cbActuallyRead;
+
+ /*
+ * Check for end of stream, also check the hash.
+ */
+ if (pThis->offCurPos >= pThis->DataAttr.cbDataArchived)
+ {
+ Assert(pThis->offCurPos == pThis->DataAttr.cbDataArchived);
+ pThis->fEndOfStream = true;
+
+ /* Check hash. */
+ if ( pThis->uHashState == RTZIPXAR_HASH_PENDING
+ && pThis->cbDigested == pThis->DataAttr.cbDataArchived)
+ {
+ RTZIPXARHASHDIGEST Digest;
+ rtZipXarHashFinal(&pThis->CtxArchived, pThis->DataAttr.uHashFunArchived, &Digest);
+ if (rtZipXarHashIsEqual(pThis->DataAttr.uHashFunArchived, &Digest, &pThis->DataAttr.DigestArchived))
+ {
+ rtZipXarHashFinal(&pThis->CtxExtracted, pThis->DataAttr.uHashFunExtracted, &Digest);
+ if (rtZipXarHashIsEqual(pThis->DataAttr.uHashFunExtracted, &Digest, &pThis->DataAttr.DigestExtracted))
+ pThis->uHashState = RTZIPXAR_HASH_OK;
+ else
+ {
+ pThis->uHashState = RTZIPXAR_HASH_FAILED_EXTRACTED;
+ rc = VERR_XAR_EXTRACTED_HASH_MISMATCH;
+ }
+ }
+ else
+ {
+ pThis->uHashState = RTZIPXAR_HASH_FAILED_ARCHIVED;
+ rc = VERR_XAR_ARCHIVED_HASH_MISMATCH;
+ }
+ }
+ else if (pThis->uHashState == RTZIPXAR_HASH_FAILED_ARCHIVED)
+ rc = VERR_XAR_ARCHIVED_HASH_MISMATCH;
+ else if (pThis->uHashState == RTZIPXAR_HASH_FAILED_EXTRACTED)
+ rc = VERR_XAR_EXTRACTED_HASH_MISMATCH;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipXarFssIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ /* Cannot write to a read-only I/O stream. */
+ NOREF(pvThis); NOREF(off); NOREF(pSgBuf); NOREF(fBlocking); NOREF(pcbWritten);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtZipXarFssIos_Flush(void *pvThis)
+{
+ /* It's a read only stream, nothing dirty to flush. */
+ NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipXarFssIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis;
+
+ /* When we've reached the end, read will be set to indicate it. */
+ if ( (fEvents & RTPOLL_EVT_READ)
+ && pThis->fEndOfStream)
+ {
+ int rc = RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, 0, fIntr, pfRetEvents);
+ if (RT_SUCCESS(rc))
+ *pfRetEvents |= RTPOLL_EVT_READ;
+ else
+ *pfRetEvents = RTPOLL_EVT_READ;
+ return VINF_SUCCESS;
+ }
+
+ return RTVfsIoStrmPoll(pThis->hVfsIos, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtZipXarFssIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPXARIOSTREAM pThis = (PRTZIPXARIOSTREAM)pvThis;
+ *poffActual = pThis->offCurPos;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Xar I/O stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_rtZipXarFssIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "XarFsStream::IoStream",
+ rtZipXarFssIos_Close,
+ rtZipXarFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ 0,
+ rtZipXarFssIos_Read,
+ rtZipXarFssIos_Write,
+ rtZipXarFssIos_Flush,
+ rtZipXarFssIos_PollOne,
+ rtZipXarFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipXarFssFile_Close(void *pvThis)
+{
+ PRTZIPXARFILE pThis = (PRTZIPXARFILE)pvThis;
+
+ RTVfsFileRelease(pThis->hVfsFile);
+ pThis->hVfsFile = NIL_RTVFSFILE;
+
+ return rtZipXarFssIos_Close(&pThis->Ios);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtZipXarFssFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ NOREF(pvThis);
+ NOREF(fMode);
+ NOREF(fMask);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtZipXarFssFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ NOREF(pvThis);
+ NOREF(pAccessTime);
+ NOREF(pModificationTime);
+ NOREF(pChangeTime);
+ NOREF(pBirthTime);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtZipXarFssFile_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ NOREF(pvThis);
+ NOREF(uid);
+ NOREF(gid);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
+ */
+static DECLCALLBACK(int) rtZipXarFssFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
+{
+ PRTZIPXARFILE pThis = (PRTZIPXARFILE)pvThis;
+
+ /* Recalculate the request to RTFILE_SEEK_BEGIN. */
+ switch (uMethod)
+ {
+ case RTFILE_SEEK_BEGIN:
+ break;
+ case RTFILE_SEEK_CURRENT:
+ offSeek += pThis->Ios.offCurPos;
+ break;
+ case RTFILE_SEEK_END:
+ offSeek = pThis->Ios.DataAttr.cbDataArchived + offSeek;
+ break;
+ default:
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ }
+
+ /* Do limit checks. */
+ if (offSeek < 0)
+ offSeek = 0;
+ else if (offSeek > pThis->Ios.DataAttr.cbDataArchived)
+ offSeek = pThis->Ios.DataAttr.cbDataArchived;
+
+ /* Apply and return. */
+ pThis->Ios.fEndOfStream = (offSeek >= pThis->Ios.DataAttr.cbDataArchived);
+ pThis->Ios.offCurPos = offSeek;
+ if (poffActual)
+ *poffActual = offSeek;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
+ */
+static DECLCALLBACK(int) rtZipXarFssFile_QuerySize(void *pvThis, uint64_t *pcbFile)
+{
+ PRTZIPXARFILE pThis = (PRTZIPXARFILE)pvThis;
+ *pcbFile = pThis->Ios.DataAttr.cbDataArchived;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Xar file operations.
+ */
+static const RTVFSFILEOPS g_rtZipXarFssFileOps =
+{
+ { /* I/O stream */
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FILE,
+ "XarFsStream::File",
+ rtZipXarFssFile_Close,
+ rtZipXarFssIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ RTVFSIOSTREAMOPS_FEAT_NO_SG,
+ rtZipXarFssIos_Read,
+ rtZipXarFssIos_Write,
+ rtZipXarFssIos_Flush,
+ rtZipXarFssIos_PollOne,
+ rtZipXarFssIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+ },
+ RTVFSFILEOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj),
+ rtZipXarFssFile_SetMode,
+ rtZipXarFssFile_SetTimes,
+ rtZipXarFssFile_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtZipXarFssFile_Seek,
+ rtZipXarFssFile_QuerySize,
+ NULL /*SetSize*/,
+ NULL /*QueryMaxSize*/,
+ RTVFSFILEOPS_VERSION,
+};
+
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipXarFssDecompIos_Close(void *pvThis)
+{
+ PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis;
+
+ RTVfsIoStrmRelease(pThis->hVfsIosDecompressor);
+ pThis->hVfsIosDecompressor = NIL_RTVFSIOSTREAM;
+
+ RTVfsIoStrmRelease(pThis->hVfsIosRaw);
+ pThis->hVfsIosRaw = NIL_RTVFSIOSTREAM;
+ pThis->pIosRaw = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipXarFssDecompIos_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis;
+
+ int rc = rtZipXarFssBaseObj_QueryInfo(&pThis->pIosRaw->BaseObj, pObjInfo, enmAddAttr);
+ pObjInfo->cbObject = pThis->pIosRaw->DataAttr.cbDataExtracted;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipXarFssDecompIos_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
+{
+ PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis;
+ AssertReturn(pSgBuf->cSegs == 1, VERR_INVALID_PARAMETER);
+
+ /*
+ * Enforce the cbDataExtracted limit.
+ */
+ if (pThis->offCurPos > pThis->pIosRaw->DataAttr.cbDataExtracted)
+ return VERR_XAR_EXTRACTED_SIZE_EXCEEDED;
+
+ /*
+ * Read the data.
+ *
+ * ASSUMES the decompressor stream isn't seekable, so we don't have to
+ * validate off wrt data digest updating.
+ */
+ int rc = RTVfsIoStrmReadAt(pThis->hVfsIosDecompressor, off, pSgBuf->paSegs[0].pvSeg, pSgBuf->paSegs[0].cbSeg,
+ fBlocking, pcbRead);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Hash the data. When reaching the end match against the expected digest.
+ */
+ size_t cbActuallyRead = pcbRead ? *pcbRead : pSgBuf->paSegs[0].cbSeg;
+ pThis->offCurPos += cbActuallyRead;
+ rtZipXarHashUpdate(&pThis->CtxExtracted, pThis->uHashFunExtracted, pSgBuf->paSegs[0].pvSeg, cbActuallyRead);
+ if (rc == VINF_EOF)
+ {
+ if (pThis->offCurPos == pThis->pIosRaw->DataAttr.cbDataExtracted)
+ {
+ if (pThis->uHashState == RTZIPXAR_HASH_PENDING)
+ {
+ RTZIPXARHASHDIGEST Digest;
+ rtZipXarHashFinal(&pThis->CtxExtracted, pThis->uHashFunExtracted, &Digest);
+ if (rtZipXarHashIsEqual(pThis->uHashFunExtracted, &Digest, &pThis->DigestExtracted))
+ pThis->uHashState = RTZIPXAR_HASH_OK;
+ else
+ {
+ pThis->uHashState = RTZIPXAR_HASH_FAILED_EXTRACTED;
+ rc = VERR_XAR_EXTRACTED_HASH_MISMATCH;
+ }
+ }
+ else if (pThis->uHashState != RTZIPXAR_HASH_OK)
+ rc = VERR_XAR_EXTRACTED_HASH_MISMATCH;
+ }
+ else
+ rc = VERR_XAR_EXTRACTED_SIZE_EXCEEDED;
+
+ /* Ensure that the raw stream is also at the end so that both
+ message digests are checked. */
+ if (RT_SUCCESS(rc))
+ {
+ if ( pThis->pIosRaw->offCurPos < pThis->pIosRaw->DataAttr.cbDataArchived
+ || pThis->pIosRaw->uHashState == RTZIPXAR_HASH_PENDING)
+ rc = VERR_XAR_UNUSED_ARCHIVED_DATA;
+ else if (pThis->pIosRaw->uHashState != RTZIPXAR_HASH_OK)
+ rc = VERR_XAR_ARCHIVED_HASH_MISMATCH;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
+ */
+static DECLCALLBACK(int) rtZipXarFssDecompIos_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
+{
+ /* Cannot write to a read-only I/O stream. */
+ NOREF(pvThis); NOREF(off); NOREF(pSgBuf); NOREF(fBlocking); NOREF(pcbWritten);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
+ */
+static DECLCALLBACK(int) rtZipXarFssDecompIos_Flush(void *pvThis)
+{
+ /* It's a read only stream, nothing dirty to flush. */
+ NOREF(pvThis);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
+ */
+static DECLCALLBACK(int) rtZipXarFssDecompIos_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
+ uint32_t *pfRetEvents)
+{
+ PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis;
+ return RTVfsIoStrmPoll(pThis->hVfsIosDecompressor, fEvents, cMillies, fIntr, pfRetEvents);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
+ */
+static DECLCALLBACK(int) rtZipXarFssDecompIos_Tell(void *pvThis, PRTFOFF poffActual)
+{
+ PRTZIPXARDECOMPIOS pThis = (PRTZIPXARDECOMPIOS)pvThis;
+ *poffActual = pThis->offCurPos;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Xar I/O stream operations.
+ */
+static const RTVFSIOSTREAMOPS g_rtZipXarFssDecompIosOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_IO_STREAM,
+ "XarFsStream::DecompIoStream",
+ rtZipXarFssDecompIos_Close,
+ rtZipXarFssDecompIos_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSIOSTREAMOPS_VERSION,
+ 0,
+ rtZipXarFssDecompIos_Read,
+ rtZipXarFssDecompIos_Write,
+ rtZipXarFssDecompIos_Flush,
+ rtZipXarFssDecompIos_PollOne,
+ rtZipXarFssDecompIos_Tell,
+ NULL /*Skip*/,
+ NULL /*ZeroFill*/,
+ RTVFSIOSTREAMOPS_VERSION
+};
+
+
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipXarFssSym_Close(void *pvThis)
+{
+ PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis;
+ return rtZipXarFssBaseObj_Close(pThis);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipXarFssSym_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis;
+ return rtZipXarFssBaseObj_QueryInfo(pThis, pObjInfo, enmAddAttr);
+}
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
+ */
+static DECLCALLBACK(int) rtZipXarFssSym_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
+{
+ NOREF(pvThis); NOREF(fMode); NOREF(fMask);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
+ */
+static DECLCALLBACK(int) rtZipXarFssSym_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
+ PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
+{
+ NOREF(pvThis); NOREF(pAccessTime); NOREF(pModificationTime); NOREF(pChangeTime); NOREF(pBirthTime);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
+ */
+static DECLCALLBACK(int) rtZipXarFssSym_SetOwner(void *pvThis, RTUID uid, RTGID gid)
+{
+ NOREF(pvThis); NOREF(uid); NOREF(gid);
+ return VERR_ACCESS_DENIED;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSSYMLINKOPS,pfnRead}
+ */
+static DECLCALLBACK(int) rtZipXarFssSym_Read(void *pvThis, char *pszTarget, size_t cbTarget)
+{
+ PRTZIPXARBASEOBJ pThis = (PRTZIPXARBASEOBJ)pvThis;
+#if 0
+ return RTStrCopy(pszTarget, cbXarget, pThis->pXarReader->szTarget);
+#else
+ RT_NOREF_PV(pThis); RT_NOREF_PV(pszTarget); RT_NOREF_PV(cbTarget);
+ return VERR_NOT_IMPLEMENTED;
+#endif
+}
+
+
+/**
+ * Xar symbolic (and hardlink) operations.
+ */
+static const RTVFSSYMLINKOPS g_rtZipXarFssSymOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_SYMLINK,
+ "XarFsStream::Symlink",
+ rtZipXarFssSym_Close,
+ rtZipXarFssSym_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSSYMLINKOPS_VERSION,
+ 0,
+ { /* ObjSet */
+ RTVFSOBJSETOPS_VERSION,
+ RT_UOFFSETOF(RTVFSSYMLINKOPS, ObjSet) - RT_UOFFSETOF(RTVFSSYMLINKOPS, Obj),
+ rtZipXarFssSym_SetMode,
+ rtZipXarFssSym_SetTimes,
+ rtZipXarFssSym_SetOwner,
+ RTVFSOBJSETOPS_VERSION
+ },
+ rtZipXarFssSym_Read,
+ RTVFSSYMLINKOPS_VERSION
+};
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnClose}
+ */
+static DECLCALLBACK(int) rtZipXarFss_Close(void *pvThis)
+{
+ PRTZIPXARFSSTREAM pThis = (PRTZIPXARFSSTREAM)pvThis;
+
+ RTVfsIoStrmRelease(pThis->hVfsIos);
+ pThis->hVfsIos = NIL_RTVFSIOSTREAM;
+
+ RTVfsFileRelease(pThis->hVfsFile);
+ pThis->hVfsFile = NIL_RTVFSFILE;
+
+ if (pThis->XarReader.pDoc)
+ delete pThis->XarReader.pDoc;
+ pThis->XarReader.pDoc = NULL;
+ /* The other XarReader fields only point to elements within pDoc. */
+ pThis->XarReader.pToc = NULL;
+ pThis->XarReader.cCurDepth = 0;
+ pThis->XarReader.pCurFile = NULL;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
+ */
+static DECLCALLBACK(int) rtZipXarFss_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
+{
+ PRTZIPXARFSSTREAM pThis = (PRTZIPXARFSSTREAM)pvThis;
+ /* Take the lazy approach here, with the sideffect of providing some info
+ that is actually kind of useful. */
+ return RTVfsIoStrmQueryInfo(pThis->hVfsIos, pObjInfo, enmAddAttr);
+}
+
+
+/**
+ * @interface_method_impl{RTVFSFSSTREAMOPS,pfnNext}
+ */
+static DECLCALLBACK(int) rtZipXarFss_Next(void *pvThis, char **ppszName, RTVFSOBJTYPE *penmType, PRTVFSOBJ phVfsObj)
+{
+ PRTZIPXARFSSTREAM pThis = (PRTZIPXARFSSTREAM)pvThis;
+
+ /*
+ * Check if we've already reached the end in some way.
+ */
+ if (pThis->fEndOfStream)
+ return VERR_EOF;
+ if (pThis->rcFatal != VINF_SUCCESS)
+ return pThis->rcFatal;
+
+ /*
+ * Get the next file element.
+ */
+ xml::ElementNode const *pCurFile = pThis->XarReader.pCurFile;
+ if (pCurFile)
+ pThis->XarReader.pCurFile = pCurFile = rtZipXarGetNextFileElement(pCurFile, &pThis->XarReader.cCurDepth);
+ else if (!pThis->fEndOfStream)
+ {
+ pThis->XarReader.cCurDepth = 0;
+ pThis->XarReader.pCurFile = pCurFile = pThis->XarReader.pToc->findChildElement("file");
+ }
+ if (!pCurFile)
+ {
+ pThis->fEndOfStream = true;
+ return VERR_EOF;
+ }
+
+ /*
+ * Retrive the fundamental attributes (elements actually).
+ */
+ const char *pszName = pCurFile->findChildElementValueP("name");
+ const char *pszType = pCurFile->findChildElementValueP("type");
+ if (RT_UNLIKELY(!pszName || !pszType))
+ return pThis->rcFatal = VERR_XAR_BAD_FILE_ELEMENT;
+
+ /*
+ * Validate the filename. Being a little too paranoid here, perhaps, wrt
+ * path separators and escapes...
+ */
+ if ( !*pszName
+ || strchr(pszName, '/')
+ || strchr(pszName, '\\')
+ || strchr(pszName, ':')
+ || !strcmp(pszName, "..") )
+ return pThis->rcFatal = VERR_XAR_INVALID_FILE_NAME;
+
+ /*
+ * Gather any additional attributes that are essential to the file type,
+ * then create the VFS object we're going to return.
+ */
+ int rc;
+ RTVFSOBJ hVfsObj;
+ RTVFSOBJTYPE enmType;
+ if (!strcmp(pszType, "file"))
+ {
+ RTZIPXARDATASTREAM DataAttr;
+ rc = rtZipXarGetDataStreamAttributes(pCurFile, &DataAttr);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+ DataAttr.offData += pThis->offZero + pThis->offStart;
+
+ if ( pThis->hVfsFile != NIL_RTVFSFILE
+ && DataAttr.enmEncoding == RTZIPXARENCODING_STORE)
+ {
+ /*
+ * The input is seekable and the XAR file isn't compressed, so we
+ * can provide a seekable file to the user.
+ */
+ RTVFSFILE hVfsFile;
+ PRTZIPXARFILE pFileData;
+ rc = RTVfsNewFile(&g_rtZipXarFssFileOps,
+ sizeof(*pFileData),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsFile,
+ (void **)&pFileData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pFileData->Ios.BaseObj.pFileElem = pCurFile;
+ pFileData->Ios.BaseObj.fModeType = RTFS_TYPE_FILE;
+ pFileData->Ios.DataAttr = DataAttr;
+ pFileData->Ios.offCurPos = 0;
+ pFileData->Ios.fEndOfStream = false;
+ pFileData->Ios.fSeekable = true;
+ pFileData->Ios.uHashState = RTZIPXAR_HASH_PENDING;
+ pFileData->Ios.cbDigested = 0;
+ rtZipXarHashInit(&pFileData->Ios.CtxArchived, pFileData->Ios.DataAttr.uHashFunArchived);
+ rtZipXarHashInit(&pFileData->Ios.CtxExtracted, pFileData->Ios.DataAttr.uHashFunExtracted);
+
+ pFileData->Ios.hVfsIos = pThis->hVfsIos;
+ RTVfsIoStrmRetain(pFileData->Ios.hVfsIos);
+ pFileData->hVfsFile = pThis->hVfsFile;
+ RTVfsFileRetain(pFileData->hVfsFile);
+
+ /* Try avoid double content hashing. */
+ if (pFileData->Ios.DataAttr.uHashFunArchived == pFileData->Ios.DataAttr.uHashFunExtracted)
+ pFileData->Ios.DataAttr.uHashFunExtracted = XAR_HASH_NONE;
+
+ enmType = RTVFSOBJTYPE_FILE;
+ hVfsObj = RTVfsObjFromFile(hVfsFile);
+ RTVfsFileRelease(hVfsFile);
+ }
+ else
+ {
+ RTVFSIOSTREAM hVfsIosRaw;
+ PRTZIPXARIOSTREAM pIosData;
+ rc = RTVfsNewIoStream(&g_rtZipXarFssIosOps,
+ sizeof(*pIosData),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIosRaw,
+ (void **)&pIosData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pIosData->BaseObj.pFileElem = pCurFile;
+ pIosData->BaseObj.fModeType = RTFS_TYPE_FILE;
+ pIosData->DataAttr = DataAttr;
+ pIosData->offCurPos = 0;
+ pIosData->fEndOfStream = false;
+ pIosData->fSeekable = pThis->hVfsFile != NIL_RTVFSFILE;
+ pIosData->uHashState = RTZIPXAR_HASH_PENDING;
+ pIosData->cbDigested = 0;
+ rtZipXarHashInit(&pIosData->CtxArchived, pIosData->DataAttr.uHashFunArchived);
+ rtZipXarHashInit(&pIosData->CtxExtracted, pIosData->DataAttr.uHashFunExtracted);
+
+ pIosData->hVfsIos = pThis->hVfsIos;
+ RTVfsIoStrmRetain(pThis->hVfsIos);
+
+ if ( pIosData->DataAttr.enmEncoding != RTZIPXARENCODING_STORE
+ && pIosData->DataAttr.enmEncoding != RTZIPXARENCODING_UNSUPPORTED)
+ {
+ /*
+ * We need to set up a decompression chain.
+ */
+ RTVFSIOSTREAM hVfsIosDecomp;
+ PRTZIPXARDECOMPIOS pIosDecompData;
+ rc = RTVfsNewIoStream(&g_rtZipXarFssDecompIosOps,
+ sizeof(*pIosDecompData),
+ RTFILE_O_READ | RTFILE_O_DENY_NONE | RTFILE_O_OPEN,
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsIosDecomp,
+ (void **)&pIosDecompData);
+ if (RT_FAILURE(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIosRaw);
+ return pThis->rcFatal = rc;
+ }
+
+ pIosDecompData->hVfsIosDecompressor = NIL_RTVFSIOSTREAM;
+ pIosDecompData->hVfsIosRaw = hVfsIosRaw;
+ pIosDecompData->pIosRaw = pIosData;
+ pIosDecompData->offCurPos = 0;
+ pIosDecompData->uHashFunExtracted = DataAttr.uHashFunExtracted;
+ pIosDecompData->uHashState = RTZIPXAR_HASH_PENDING;
+ rtZipXarHashInit(&pIosDecompData->CtxExtracted, pIosDecompData->uHashFunExtracted);
+ pIosDecompData->DigestExtracted = DataAttr.DigestExtracted;
+
+ /* Tell the raw end to only hash the archived data. */
+ pIosData->DataAttr.uHashFunExtracted = XAR_HASH_NONE;
+
+ /*
+ * Hook up the decompressor.
+ */
+ switch (DataAttr.enmEncoding)
+ {
+ case RTZIPXARENCODING_GZIP:
+ /* Must allow zlib header, all examples I've got seems
+ to be using it rather than the gzip one. Makes
+ sense as there is no need to repeat the file name
+ and the attributes. */
+ rc = RTZipGzipDecompressIoStream(hVfsIosRaw, RTZIPGZIPDECOMP_F_ALLOW_ZLIB_HDR,
+ &pIosDecompData->hVfsIosDecompressor);
+ break;
+ default:
+ rc = VERR_INTERNAL_ERROR_5;
+ break;
+ }
+ if (RT_FAILURE(rc))
+ {
+ RTVfsIoStrmRelease(hVfsIosDecomp);
+ return pThis->rcFatal = rc;
+ }
+
+ /* What to return. */
+ hVfsObj = RTVfsObjFromIoStream(hVfsIosDecomp);
+ RTVfsIoStrmRelease(hVfsIosDecomp);
+ }
+ else
+ {
+ /* Try avoid double content hashing. */
+ if (pIosData->DataAttr.uHashFunArchived == pIosData->DataAttr.uHashFunExtracted)
+ pIosData->DataAttr.uHashFunExtracted = XAR_HASH_NONE;
+
+ /* What to return. */
+ hVfsObj = RTVfsObjFromIoStream(hVfsIosRaw);
+ RTVfsIoStrmRelease(hVfsIosRaw);
+ }
+ enmType = RTVFSOBJTYPE_IO_STREAM;
+ }
+ }
+ else if (!strcmp(pszType, "directory"))
+ {
+ PRTZIPXARBASEOBJ pBaseObjData;
+ rc = RTVfsNewBaseObj(&g_rtZipXarFssBaseObjOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsObj,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->pFileElem = pCurFile;
+ pBaseObjData->fModeType = RTFS_TYPE_DIRECTORY;
+
+ enmType = RTVFSOBJTYPE_BASE;
+ }
+ else if (!strcmp(pszType, "symlink"))
+ {
+ RTVFSSYMLINK hVfsSym;
+ PRTZIPXARBASEOBJ pBaseObjData;
+ rc = RTVfsNewSymlink(&g_rtZipXarFssSymOps,
+ sizeof(*pBaseObjData),
+ NIL_RTVFS,
+ NIL_RTVFSLOCK,
+ &hVfsSym,
+ (void **)&pBaseObjData);
+ if (RT_FAILURE(rc))
+ return pThis->rcFatal = rc;
+
+ pBaseObjData->pFileElem = pCurFile;
+ pBaseObjData->fModeType = RTFS_TYPE_SYMLINK;
+
+ enmType = RTVFSOBJTYPE_SYMLINK;
+ hVfsObj = RTVfsObjFromSymlink(hVfsSym);
+ RTVfsSymlinkRelease(hVfsSym);
+ }
+ else
+ return pThis->rcFatal = VERR_XAR_UNKNOWN_FILE_TYPE;
+
+ /*
+ * Set the return data and we're done.
+ */
+ if (ppszName)
+ {
+ /* Figure the length. */
+ size_t const cbCurName = strlen(pszName) + 1;
+ size_t cbFullName = cbCurName;
+ const xml::ElementNode *pAncestor = pCurFile;
+ uint32_t cLeft = pThis->XarReader.cCurDepth;
+ while (cLeft-- > 0)
+ {
+ pAncestor = (const xml::ElementNode *)pAncestor->getParent(); Assert(pAncestor);
+ const char *pszAncestorName = pAncestor->findChildElementValueP("name"); Assert(pszAncestorName);
+ cbFullName += strlen(pszAncestorName) + 1;
+ }
+
+ /* Allocate a buffer. */
+ char *psz = *ppszName = RTStrAlloc(cbFullName);
+ if (!psz)
+ {
+ RTVfsObjRelease(hVfsObj);
+ return VERR_NO_STR_MEMORY;
+ }
+
+ /* Construct it, from the end. */
+ psz += cbFullName;
+ psz -= cbCurName;
+ memcpy(psz, pszName, cbCurName);
+
+ pAncestor = pCurFile;
+ cLeft = pThis->XarReader.cCurDepth;
+ while (cLeft-- > 0)
+ {
+ pAncestor = (const xml::ElementNode *)pAncestor->getParent(); Assert(pAncestor);
+ const char *pszAncestorName = pAncestor->findChildElementValueP("name"); Assert(pszAncestorName);
+ *--psz = '/';
+ size_t cchAncestorName = strlen(pszAncestorName);
+ psz -= cchAncestorName;
+ memcpy(psz, pszAncestorName, cchAncestorName);
+ }
+ Assert(*ppszName == psz);
+ }
+
+ if (phVfsObj)
+ *phVfsObj = hVfsObj;
+ else
+ RTVfsObjRelease(hVfsObj);
+
+ if (penmType)
+ *penmType = enmType;
+
+ return VINF_SUCCESS;
+}
+
+
+
+/**
+ * Xar filesystem stream operations.
+ */
+static const RTVFSFSSTREAMOPS rtZipXarFssOps =
+{
+ { /* Obj */
+ RTVFSOBJOPS_VERSION,
+ RTVFSOBJTYPE_FS_STREAM,
+ "XarFsStream",
+ rtZipXarFss_Close,
+ rtZipXarFss_QueryInfo,
+ NULL,
+ RTVFSOBJOPS_VERSION
+ },
+ RTVFSFSSTREAMOPS_VERSION,
+ 0,
+ rtZipXarFss_Next,
+ NULL,
+ NULL,
+ NULL,
+ RTVFSFSSTREAMOPS_VERSION
+};
+
+
+
+/**
+ * TOC validation part 2.
+ *
+ * Will advance the input stream past the TOC hash and signature data.
+ *
+ * @returns IPRT status code.
+ * @param pThis The FS stream instance being created.
+ * @param pXarHdr The XAR header.
+ * @param pTocDigest The TOC input data digest.
+ */
+static int rtZipXarValidateTocPart2(PRTZIPXARFSSTREAM pThis, PCXARHEADER pXarHdr, PCRTZIPXARHASHDIGEST pTocDigest)
+{
+ int rc;
+ RT_NOREF_PV(pXarHdr);
+
+ /*
+ * Check that the hash function in the TOC matches the one in the XAR header.
+ */
+ const xml::ElementNode *pChecksumElem = pThis->XarReader.pToc->findChildElement("checksum");
+ if (pChecksumElem)
+ {
+ const xml::AttributeNode *pAttr = pChecksumElem->findAttribute("style");
+ if (!pAttr)
+ return VERR_XAR_BAD_CHECKSUM_ELEMENT;
+
+ const char *pszStyle = pAttr->getValue();
+ if (!pszStyle)
+ return VERR_XAR_BAD_CHECKSUM_ELEMENT;
+
+ uint8_t uHashFunction;
+ rc = rtZipXarParseChecksumStyle(pszStyle, &uHashFunction);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (uHashFunction != pThis->uHashFunction)
+ return VERR_XAR_HASH_FUNCTION_MISMATCH;
+
+ /*
+ * Verify the checksum if we got one.
+ */
+ if (pThis->uHashFunction != XAR_HASH_NONE)
+ {
+ RTFOFF offChecksum;
+ RTFOFF cbChecksum;
+ rc = rtZipXarGetOffsetSizeLengthFromElem(pChecksumElem, &offChecksum, &cbChecksum, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (cbChecksum != (RTFOFF)pThis->cbHashDigest)
+ return VERR_XAR_BAD_DIGEST_LENGTH;
+ if (offChecksum != 0 && pThis->hVfsFile == NIL_RTVFSFILE)
+ return VERR_XAR_NOT_STREAMBLE_ELEMENT_ORDER;
+
+ RTZIPXARHASHDIGEST StoredDigest;
+ rc = RTVfsIoStrmReadAt(pThis->hVfsIos, pThis->offZero + offChecksum, &StoredDigest, pThis->cbHashDigest,
+ true /*fBlocking*/, NULL /*pcbRead*/);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (memcmp(&StoredDigest, pTocDigest, pThis->cbHashDigest))
+ return VERR_XAR_TOC_DIGEST_MISMATCH;
+ }
+ }
+ else if (pThis->uHashFunction != XAR_HASH_NONE)
+ return VERR_XAR_BAD_CHECKSUM_ELEMENT;
+
+ /*
+ * Check the signature, if we got one.
+ */
+ /** @todo signing. */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads and validates the table of content.
+ *
+ * @returns IPRT status code.
+ * @param hVfsIosIn The input stream.
+ * @param pXarHdr The XAR header.
+ * @param pDoc The TOC XML document.
+ * @param ppTocElem Where to return the pointer to the TOC element on
+ * success.
+ * @param pTocDigest Where to return the TOC digest on success.
+ */
+static int rtZipXarReadAndValidateToc(RTVFSIOSTREAM hVfsIosIn, PCXARHEADER pXarHdr,
+ xml::Document *pDoc, xml::ElementNode const **ppTocElem, PRTZIPXARHASHDIGEST pTocDigest)
+{
+ /*
+ * Decompress it, calculating the hash while doing so.
+ */
+ char *pszOutput = (char *)RTMemTmpAlloc(pXarHdr->cbTocUncompressed + 1);
+ if (!pszOutput)
+ return VERR_NO_TMP_MEMORY;
+ int rc = VERR_NO_TMP_MEMORY;
+ void *pvInput = RTMemTmpAlloc(pXarHdr->cbTocCompressed);
+ if (pvInput)
+ {
+ rc = RTVfsIoStrmRead(hVfsIosIn, pvInput, pXarHdr->cbTocCompressed, true /*fBlocking*/, NULL);
+ if (RT_SUCCESS(rc))
+ {
+ rtZipXarCalcHash(pXarHdr->uHashFunction, pvInput, pXarHdr->cbTocCompressed, pTocDigest);
+
+ size_t cbActual;
+ rc = RTZipBlockDecompress(RTZIPTYPE_ZLIB, 0 /*fFlags*/,
+ pvInput, pXarHdr->cbTocCompressed, NULL,
+ pszOutput, pXarHdr->cbTocUncompressed, &cbActual);
+ if (RT_SUCCESS(rc) && cbActual != pXarHdr->cbTocUncompressed)
+ rc = VERR_XAR_TOC_UNCOMP_SIZE_MISMATCH;
+ }
+ RTMemTmpFree(pvInput);
+ }
+ if (RT_SUCCESS(rc))
+ {
+ pszOutput[pXarHdr->cbTocUncompressed] = '\0';
+
+ /*
+ * Parse the TOC (XML document) and do some basic validations.
+ */
+ size_t cchToc = strlen(pszOutput);
+ if ( cchToc == pXarHdr->cbTocUncompressed
+ || cchToc + 1 == pXarHdr->cbTocUncompressed)
+ {
+ rc = RTStrValidateEncoding(pszOutput);
+ if (RT_SUCCESS(rc))
+ {
+ xml::XmlMemParser Parser;
+ try
+ {
+ Parser.read(pszOutput, cchToc, RTCString("xar-toc.xml"), *pDoc);
+ }
+ catch (xml::XmlError &)
+ {
+ rc = VERR_XAR_TOC_XML_PARSE_ERROR;
+ }
+ catch (...)
+ {
+ rc = VERR_NO_MEMORY;
+ }
+ if (RT_SUCCESS(rc))
+ {
+ xml::ElementNode const *pRootElem = pDoc->getRootElement();
+ xml::ElementNode const *pTocElem = NULL;
+ if (pRootElem && pRootElem->nameEquals("xar"))
+ pTocElem = pRootElem ? pRootElem->findChildElement("toc") : NULL;
+ if (pTocElem)
+ {
+#ifndef USE_STD_LIST_FOR_CHILDREN
+ Assert(pRootElem->getParent() == NULL);
+ Assert(pTocElem->getParent() == pRootElem);
+ if ( !pTocElem->getNextSibiling()
+ && !pTocElem->getPrevSibiling())
+#endif
+ {
+ /*
+ * Further parsing and validation is done after the
+ * caller has created an file system stream instance.
+ */
+ *ppTocElem = pTocElem;
+
+ RTMemTmpFree(pszOutput);
+ return VINF_SUCCESS;
+ }
+
+ rc = VERR_XML_TOC_ELEMENT_HAS_SIBLINGS;
+ }
+ else
+ rc = VERR_XML_TOC_ELEMENT_MISSING;
+ }
+ }
+ else
+ rc = VERR_XAR_TOC_UTF8_ENCODING;
+ }
+ else
+ rc = VERR_XAR_TOC_STRLEN_MISMATCH;
+ }
+
+ RTMemTmpFree(pszOutput);
+ return rc;
+}
+
+
+/**
+ * Reads and validates the XAR header.
+ *
+ * @returns IPRT status code.
+ * @param hVfsIosIn The input stream.
+ * @param pXarHdr Where to return the XAR header in host byte order.
+ */
+static int rtZipXarReadAndValidateHeader(RTVFSIOSTREAM hVfsIosIn, PXARHEADER pXarHdr)
+{
+ /*
+ * Read it and check the signature.
+ */
+ int rc = RTVfsIoStrmRead(hVfsIosIn, pXarHdr, sizeof(*pXarHdr), true /*fBlocking*/, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (pXarHdr->u32Magic != XAR_HEADER_MAGIC)
+ return VERR_XAR_WRONG_MAGIC;
+
+ /*
+ * Correct the byte order.
+ */
+ pXarHdr->cbHeader = RT_BE2H_U16(pXarHdr->cbHeader);
+ pXarHdr->uVersion = RT_BE2H_U16(pXarHdr->uVersion);
+ pXarHdr->cbTocCompressed = RT_BE2H_U64(pXarHdr->cbTocCompressed);
+ pXarHdr->cbTocUncompressed = RT_BE2H_U64(pXarHdr->cbTocUncompressed);
+ pXarHdr->uHashFunction = RT_BE2H_U32(pXarHdr->uHashFunction);
+
+ /*
+ * Validate the header.
+ */
+ if (pXarHdr->uVersion > XAR_HEADER_VERSION)
+ return VERR_XAR_UNSUPPORTED_VERSION;
+ if (pXarHdr->cbHeader < sizeof(XARHEADER))
+ return VERR_XAR_BAD_HDR_SIZE;
+ if (pXarHdr->uHashFunction > XAR_HASH_MAX)
+ return VERR_XAR_UNSUPPORTED_HASH_FUNCTION;
+ if (pXarHdr->cbTocUncompressed < 16)
+ return VERR_XAR_TOC_TOO_SMALL;
+ if (pXarHdr->cbTocUncompressed > _4M)
+ return VERR_XAR_TOC_TOO_BIG;
+ if (pXarHdr->cbTocCompressed > _4M)
+ return VERR_XAR_TOC_TOO_BIG_COMPRESSED;
+
+ /*
+ * Skip over bytes we don't understand (could be padding).
+ */
+ if (pXarHdr->cbHeader > sizeof(XARHEADER))
+ {
+ rc = RTVfsIoStrmSkip(hVfsIosIn, pXarHdr->cbHeader - sizeof(XARHEADER));
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+RTDECL(int) RTZipXarFsStreamFromIoStream(RTVFSIOSTREAM hVfsIosIn, uint32_t fFlags, PRTVFSFSSTREAM phVfsFss)
+{
+ /*
+ * Input validation.
+ */
+ AssertPtrReturn(phVfsFss, VERR_INVALID_HANDLE);
+ *phVfsFss = NIL_RTVFSFSSTREAM;
+ AssertPtrReturn(hVfsIosIn, VERR_INVALID_HANDLE);
+ AssertReturn(!fFlags, VERR_INVALID_PARAMETER);
+
+ RTFOFF const offStart = RTVfsIoStrmTell(hVfsIosIn);
+ AssertReturn(offStart >= 0, (int)offStart);
+
+ uint32_t cRefs = RTVfsIoStrmRetain(hVfsIosIn);
+ AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
+
+ /*
+ * Read and validate the header, then uncompress the TOC.
+ */
+ XARHEADER XarHdr;
+ int rc = rtZipXarReadAndValidateHeader(hVfsIosIn, &XarHdr);
+ if (RT_SUCCESS(rc))
+ {
+ xml::Document *pDoc = NULL;
+ try { pDoc = new xml::Document(); }
+ catch (...) { }
+ if (pDoc)
+ {
+ RTZIPXARHASHDIGEST TocDigest;
+ xml::ElementNode const *pTocElem = NULL;
+ rc = rtZipXarReadAndValidateToc(hVfsIosIn, &XarHdr, pDoc, &pTocElem, &TocDigest);
+ if (RT_SUCCESS(rc))
+ {
+ size_t offZero = RTVfsIoStrmTell(hVfsIosIn);
+ if (offZero > 0)
+ {
+ /*
+ * Create a file system stream before we continue the parsing.
+ */
+ PRTZIPXARFSSTREAM pThis;
+ RTVFSFSSTREAM hVfsFss;
+ rc = RTVfsNewFsStream(&rtZipXarFssOps, sizeof(*pThis), NIL_RTVFS, NIL_RTVFSLOCK, RTFILE_O_READ,
+ &hVfsFss, (void **)&pThis);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hVfsIos = hVfsIosIn;
+ pThis->hVfsFile = RTVfsIoStrmToFile(hVfsIosIn);
+ pThis->offStart = offStart;
+ pThis->offZero = offZero;
+ pThis->uHashFunction = (uint8_t)XarHdr.uHashFunction;
+ switch (pThis->uHashFunction)
+ {
+ case XAR_HASH_MD5: pThis->cbHashDigest = sizeof(TocDigest.abMd5); break;
+ case XAR_HASH_SHA1: pThis->cbHashDigest = sizeof(TocDigest.abSha1); break;
+ default: pThis->cbHashDigest = 0; break;
+ }
+ pThis->fEndOfStream = false;
+ pThis->rcFatal = VINF_SUCCESS;
+ pThis->XarReader.pDoc = pDoc;
+ pThis->XarReader.pToc = pTocElem;
+ pThis->XarReader.pCurFile = 0;
+ pThis->XarReader.cCurDepth = 0;
+
+ /*
+ * Next validation step.
+ */
+ rc = rtZipXarValidateTocPart2(pThis, &XarHdr, &TocDigest);
+ if (RT_SUCCESS(rc))
+ {
+ *phVfsFss = hVfsFss;
+ return VINF_SUCCESS;
+ }
+
+ RTVfsFsStrmRelease(hVfsFss);
+ return rc;
+ }
+ }
+ else
+ rc = (int)offZero;
+ }
+ delete pDoc;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ RTVfsIoStrmRelease(hVfsIosIn);
+ return rc;
+}
+
diff --git a/src/VBox/Runtime/common/zip/zip.cpp b/src/VBox/Runtime/common/zip/zip.cpp
new file mode 100644
index 00000000..68c2a9c6
--- /dev/null
+++ b/src/VBox/Runtime/common/zip/zip.cpp
@@ -0,0 +1,2016 @@
+/* $Id: zip.cpp $ */
+/** @file
+ * IPRT - Compression.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * The contents of this file may alternatively be used under the terms
+ * of the Common Development and Distribution License Version 1.0
+ * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included
+ * in the VirtualBox distribution, in which case the provisions of the
+ * CDDL are applicable instead of those of the GPL.
+ *
+ * You may elect to license modified versions of this file under the
+ * terms and conditions of either the GPL or the CDDL or both.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define RTZIP_USE_STORE 1
+#define RTZIP_USE_ZLIB 1
+//#define RTZIP_USE_BZLIB 1
+#if !defined(IN_GUEST) && !defined(IPRT_NO_CRT)
+# define RTZIP_USE_LZF 1
+#endif
+#define RTZIP_LZF_BLOCK_BY_BLOCK
+//#define RTZIP_USE_LZJB 1
+//#define RTZIP_USE_LZO 1
+
+/** @todo FastLZ? QuickLZ? Others? */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <iprt/cdefs.h>
+#ifdef RTZIP_USE_BZLIB
+# include <bzlib.h>
+#endif
+#ifdef RTZIP_USE_ZLIB
+# include <zlib.h>
+#endif
+#ifdef RTZIP_USE_LZF
+ RT_C_DECLS_BEGIN
+# include <lzf.h>
+ RT_C_DECLS_END
+# include <iprt/crc.h>
+#endif
+#ifdef RTZIP_USE_LZJB
+# include "lzjb.h"
+#endif
+#ifdef RTZIP_USE_LZO
+# include <lzo/lzo1x.h>
+#endif
+
+#include <iprt/zip.h>
+#include "internal/iprt.h"
+
+/*#include <iprt/asm.h>*/
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/err.h>
+#include <iprt/log.h>
+#include <iprt/string.h>
+
+#ifndef IPRT_NO_CRT
+# include <errno.h>
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+#ifdef RTZIP_USE_LZF
+
+/**
+ * LZF block header.
+ */
+#pragma pack(1) /* paranoia */
+typedef struct RTZIPLZFHDR
+{
+ /** Magic word (RTZIPLZFHDR_MAGIC). */
+ uint16_t u16Magic;
+ /** The number of bytes of data following this header. */
+ uint16_t cbData;
+ /** The CRC32 of the block. */
+ uint32_t u32CRC;
+ /** The size of the uncompressed data in bytes. */
+ uint16_t cbUncompressed;
+} RTZIPLZFHDR;
+#pragma pack()
+/** Pointer to a LZF block header. */
+typedef RTZIPLZFHDR *PRTZIPLZFHDR;
+/** Pointer to a const LZF block header. */
+typedef const RTZIPLZFHDR *PCRTZIPLZFHDR;
+
+/** The magic of a LZF block header. */
+#define RTZIPLZFHDR_MAGIC ('Z' | ('V' << 8))
+
+/** The max compressed data size.
+ * The maximum size of a block is currently 16KB.
+ * This is very important so we don't have to move input buffers around. */
+#define RTZIPLZF_MAX_DATA_SIZE (16384 - sizeof(RTZIPLZFHDR))
+
+/** The max uncompressed data size.
+ * This is important so we don't overflow the spill buffer in the decompressor. */
+#define RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE (32*_1K)
+
+#endif /* RTZIP_USE_LZF */
+
+
+/**
+ * Compressor/Decompressor instance data.
+ */
+typedef struct RTZIPCOMP
+{
+ /** Output buffer. */
+ uint8_t abBuffer[_128K];
+ /** Compression output consumer. */
+ PFNRTZIPOUT pfnOut;
+ /** User argument for the callback. */
+ void *pvUser;
+
+ /**
+ * @copydoc RTZipCompress
+ */
+ DECLCALLBACKMEMBER(int, pfnCompress,(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf));
+
+ /**
+ * @copydoc RTZipCompFinish
+ */
+ DECLCALLBACKMEMBER(int, pfnFinish,(PRTZIPCOMP pZip));
+
+ /**
+ * @copydoc RTZipCompDestroy
+ */
+ DECLCALLBACKMEMBER(int, pfnDestroy,(PRTZIPCOMP pZip));
+
+ /** Compression type. */
+ RTZIPTYPE enmType;
+ /** Type specific data. */
+ union
+ {
+#ifdef RTZIP_USE_STORE
+ /** Simple storing. */
+ struct
+ {
+ /** Current buffer position. (where to start write) */
+ uint8_t *pb;
+ } Store;
+#endif
+#ifdef RTZIP_USE_ZLIB
+ /** Zlib stream. */
+ z_stream Zlib;
+#endif
+#ifdef RTZIP_USE_BZLIB
+ /** BZlib stream. */
+ bz_stream BZlib;
+#endif
+#ifdef RTZIP_USE_LZF
+ /** LZF stream. */
+ struct
+ {
+ /** Current output buffer position. */
+ uint8_t *pbOutput;
+ /** The input buffer position. */
+ uint8_t *pbInput;
+ /** The number of free bytes in the input buffer. */
+ size_t cbInputFree;
+ /** The input buffer. */
+ uint8_t abInput[RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE];
+ } LZF;
+#endif
+
+ } u;
+} RTZIPCOMP;
+
+
+
+/**
+ * Decompressor instance data.
+ */
+typedef struct RTZIPDECOMP
+{
+ /** Input buffer. */
+ uint8_t abBuffer[_128K];
+ /** Decompression input producer. */
+ PFNRTZIPIN pfnIn;
+ /** User argument for the callback. */
+ void *pvUser;
+
+ /**
+ * @copydoc RTZipDecompress
+ */
+ DECLCALLBACKMEMBER(int, pfnDecompress,(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten));
+
+ /**
+ * @copydoc RTZipDecompDestroy
+ */
+ DECLCALLBACKMEMBER(int, pfnDestroy,(PRTZIPDECOMP pZip));
+
+ /** Compression type. */
+ RTZIPTYPE enmType;
+ /** Type specific data. */
+ union
+ {
+#ifdef RTZIP_USE_STORE
+ /** Simple storing. */
+ struct
+ {
+ /** Current buffer position. (where to start read) */
+ uint8_t *pb;
+ /** Number of bytes left in the buffer. */
+ size_t cbBuffer;
+ } Store;
+#endif
+#ifdef RTZIP_USE_ZLIB
+ /** Zlib stream. */
+ z_stream Zlib;
+#endif
+#ifdef RTZIP_USE_BZLIB
+ /** BZlib stream. */
+ bz_stream BZlib;
+#endif
+#ifdef RTZIP_USE_LZF
+ /** LZF 'stream'. */
+ struct
+ {
+# ifndef RTZIP_LZF_BLOCK_BY_BLOCK
+ /** Current input buffer position. */
+ uint8_t *pbInput;
+ /** The number of bytes left in the input buffer. */
+ size_t cbInput;
+# endif
+ /** The spill buffer.
+ * LZF is a block based compressor and not a stream compressor. So,
+ * we have to decompress full blocks if we want to get any of the data.
+ * This buffer is to store the spill after decompressing a block. */
+ uint8_t abSpill[RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE];
+ /** The number of bytes left spill buffer. */
+ unsigned cbSpill;
+ /** The current spill buffer position. */
+ uint8_t *pbSpill;
+ } LZF;
+#endif
+
+ } u;
+} RTZIPDECOM;
+
+
+
+#ifdef RTZIP_USE_STORE
+
+/**
+ * @copydoc RTZipCompress
+ */
+static DECLCALLBACK(int) rtZipStoreCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+ uint8_t *pbDst = pZip->u.Store.pb;
+ while (cbBuf)
+ {
+ /*
+ * Flush.
+ */
+ size_t cb = sizeof(pZip->abBuffer) - (size_t)(pbDst - &pZip->abBuffer[0]); /* careful here, g++ 4.1.2 screws up easily */
+ if (cb == 0)
+ {
+ int rc = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ cb = sizeof(pZip->abBuffer);
+ pbDst = &pZip->abBuffer[0];
+ }
+
+ /*
+ * Add to the buffer and advance.
+ */
+ if (cbBuf < cb)
+ cb = cbBuf;
+ memcpy(pbDst, pvBuf, cb);
+
+ pbDst += cb;
+ cbBuf -= cb;
+ pvBuf = (uint8_t *)pvBuf + cb;
+ }
+ pZip->u.Store.pb = pbDst;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompFinish
+ */
+static DECLCALLBACK(int) rtZipStoreCompFinish(PRTZIPCOMP pZip)
+{
+ size_t cb = (uintptr_t)pZip->u.Store.pb - (uintptr_t)&pZip->abBuffer[0];
+ if (cb > 0)
+ {
+ int rc = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompDestroy
+ */
+static DECLCALLBACK(int) rtZipStoreCompDestroy(PRTZIPCOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initializes the compressor instance.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param enmLevel The desired compression level.
+ */
+static DECLCALLBACK(int) rtZipStoreCompInit(PRTZIPCOMP pZip, RTZIPLEVEL enmLevel)
+{
+ NOREF(enmLevel);
+ pZip->pfnCompress = rtZipStoreCompress;
+ pZip->pfnFinish = rtZipStoreCompFinish;
+ pZip->pfnDestroy = rtZipStoreCompDestroy;
+
+ pZip->u.Store.pb = &pZip->abBuffer[1];
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipStoreDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ size_t cbWritten = 0;
+ while (cbBuf)
+ {
+ /*
+ * Fill buffer.
+ */
+ size_t cb = pZip->u.Store.cbBuffer;
+ if (cb <= 0)
+ {
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer), &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.Store.cbBuffer = cb;
+ pZip->u.Store.pb = &pZip->abBuffer[0];
+ }
+
+ /*
+ * No more data?
+ */
+ if (cb == 0)
+ {
+ if (pcbWritten)
+ {
+ *pcbWritten = cbWritten;
+ return VINF_SUCCESS;
+ }
+ return VERR_NO_DATA;
+ }
+
+ /*
+ * Add to the buffer and advance.
+ */
+ if (cbBuf < cb)
+ cb = cbBuf;
+ memcpy(pvBuf, pZip->u.Store.pb, cb);
+ pZip->u.Store.pb += cb;
+ pZip->u.Store.cbBuffer -= cb;
+ cbBuf -= cb;
+ pvBuf = (char *)pvBuf + cb;
+ cbWritten += cb;
+ }
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipStoreDecompDestroy(PRTZIPDECOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize the decompressor instance.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+static DECLCALLBACK(int) rtZipStoreDecompInit(PRTZIPDECOMP pZip)
+{
+ pZip->pfnDecompress = rtZipStoreDecompress;
+ pZip->pfnDestroy = rtZipStoreDecompDestroy;
+
+ pZip->u.Store.pb = &pZip->abBuffer[0];
+ pZip->u.Store.cbBuffer = 0;
+ return VINF_SUCCESS;
+}
+
+#endif /* RTZIP_USE_STORE */
+
+
+#ifdef RTZIP_USE_ZLIB
+
+/*
+ * Missing definitions from zutil.h. We need these constants for calling
+ * inflateInit2() / deflateInit2().
+ */
+# ifndef Z_DEF_WBITS
+# define Z_DEF_WBITS MAX_WBITS
+# endif
+# ifndef Z_DEF_MEM_LEVEL
+# define Z_DEF_MEM_LEVEL 8
+# endif
+
+/**
+ * Convert from zlib errno to iprt status code.
+ * @returns iprt status code.
+ * @param rc Zlib error code.
+ * @param fCompressing Set if we're compressing, clear if decompressing.
+ */
+static int zipErrConvertFromZlib(int rc, bool fCompressing)
+{
+ switch (rc)
+ {
+ case Z_OK:
+ return VINF_SUCCESS;
+
+ case Z_STREAM_ERROR:
+ return VERR_ZIP_CORRUPTED;
+
+ case Z_DATA_ERROR:
+ return fCompressing ? VERR_ZIP_ERROR : VERR_ZIP_CORRUPTED;
+
+ case Z_MEM_ERROR:
+ return VERR_ZIP_NO_MEMORY;
+
+ case Z_BUF_ERROR:
+ return VERR_ZIP_ERROR;
+
+ case Z_VERSION_ERROR:
+ return VERR_ZIP_UNSUPPORTED_VERSION;
+
+ case Z_ERRNO: /* We shouldn't see this status! */
+ default:
+ AssertMsgFailed(("%d\n", rc));
+ if (rc >= 0)
+ return VINF_SUCCESS;
+ return VERR_ZIP_ERROR;
+ }
+}
+
+
+/**
+ * @copydoc RTZipCompress
+ */
+static DECLCALLBACK(int) rtZipZlibCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+ pZip->u.Zlib.next_in = (Bytef *)pvBuf;
+ pZip->u.Zlib.avail_in = (uInt)cbBuf; Assert(pZip->u.Zlib.avail_in == cbBuf);
+ while (pZip->u.Zlib.avail_in > 0)
+ {
+ /*
+ * Flush output buffer?
+ */
+ if (pZip->u.Zlib.avail_out <= 0)
+ {
+ int rc = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer) - pZip->u.Zlib.avail_out);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.Zlib.avail_out = sizeof(pZip->abBuffer);
+ pZip->u.Zlib.next_out = &pZip->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ int rc = deflate(&pZip->u.Zlib, Z_NO_FLUSH);
+ if (rc != Z_OK)
+ return zipErrConvertFromZlib(rc, true /*fCompressing*/);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompFinish
+ */
+static DECLCALLBACK(int) rtZipZlibCompFinish(PRTZIPCOMP pZip)
+{
+ int rc = Z_OK;
+ for (;;)
+ {
+ /*
+ * Flush outstanding stuff. writes.
+ */
+ if (rc == Z_STREAM_END || pZip->u.Zlib.avail_out <= 0)
+ {
+ int rc2 = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer) - pZip->u.Zlib.avail_out);
+ if (RT_FAILURE(rc2))
+ return rc2;
+ pZip->u.Zlib.avail_out = sizeof(pZip->abBuffer);
+ pZip->u.Zlib.next_out = &pZip->abBuffer[0];
+ if (rc == Z_STREAM_END)
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Tell zlib to flush.
+ */
+ rc = deflate(&pZip->u.Zlib, Z_FINISH);
+ if (rc != Z_OK && rc != Z_STREAM_END)
+ return zipErrConvertFromZlib(rc, true /*fCompressing*/);
+ }
+}
+
+
+/**
+ * @copydoc RTZipCompDestroy
+ */
+static DECLCALLBACK(int) rtZipZlibCompDestroy(PRTZIPCOMP pZip)
+{
+ /*
+ * Terminate the deflate instance.
+ */
+ int rc = deflateEnd(&pZip->u.Zlib);
+ if (rc != Z_OK)
+ rc = zipErrConvertFromZlib(rc, true /*fCompressing*/);
+ return rc;
+}
+
+
+/**
+ * Initializes the compressor instance.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param enmLevel The desired compression level.
+ * @param fZlibHeader If true, write the Zlib header.
+ */
+static DECLCALLBACK(int) rtZipZlibCompInit(PRTZIPCOMP pZip, RTZIPLEVEL enmLevel, bool fZlibHeader)
+{
+ pZip->pfnCompress = rtZipZlibCompress;
+ pZip->pfnFinish = rtZipZlibCompFinish;
+ pZip->pfnDestroy = rtZipZlibCompDestroy;
+
+ int iLevel = Z_DEFAULT_COMPRESSION;
+ switch (enmLevel)
+ {
+ case RTZIPLEVEL_STORE: iLevel = 0; break;
+ case RTZIPLEVEL_FAST: iLevel = 2; break;
+ case RTZIPLEVEL_DEFAULT: iLevel = Z_DEFAULT_COMPRESSION; break;
+ case RTZIPLEVEL_MAX: iLevel = 9; break;
+ }
+
+ memset(&pZip->u.Zlib, 0, sizeof(pZip->u.Zlib));
+ pZip->u.Zlib.next_out = &pZip->abBuffer[1];
+ pZip->u.Zlib.avail_out = sizeof(pZip->abBuffer) - 1;
+ pZip->u.Zlib.opaque = pZip;
+
+ int rc = deflateInit2(&pZip->u.Zlib, iLevel, Z_DEFLATED, fZlibHeader ? Z_DEF_WBITS : -Z_DEF_WBITS,
+ Z_DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+ return rc >= 0 ? rc = VINF_SUCCESS : zipErrConvertFromZlib(rc, true /*fCompressing*/);
+}
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipZlibDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ pZip->u.Zlib.next_out = (Bytef *)pvBuf;
+ pZip->u.Zlib.avail_out = (uInt)cbBuf;
+ Assert(pZip->u.Zlib.avail_out == cbBuf);
+
+ /*
+ * Be greedy reading input, even if no output buffer is left. It's possible
+ * that it's just the end of stream marker which needs to be read. Happens
+ * for incompressible blocks just larger than the input buffer size.
+ */
+ while (pZip->u.Zlib.avail_out > 0 || pZip->u.Zlib.avail_in <= 0)
+ {
+ /*
+ * Read more input?
+ */
+ if (pZip->u.Zlib.avail_in <= 0)
+ {
+ size_t cb = sizeof(pZip->abBuffer);
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer), &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.Zlib.avail_in = (uInt)cb; Assert(pZip->u.Zlib.avail_in == cb);
+ pZip->u.Zlib.next_in = &pZip->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ int rc = inflate(&pZip->u.Zlib, Z_NO_FLUSH);
+ if (rc == Z_STREAM_END)
+ {
+ if (pcbWritten)
+ *pcbWritten = cbBuf - pZip->u.Zlib.avail_out;
+ else if (pZip->u.Zlib.avail_out > 0)
+ return VERR_NO_DATA;
+ break;
+ }
+ if (rc != Z_OK)
+ return zipErrConvertFromZlib(rc, false /*fCompressing*/);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipZlibDecompDestroy(PRTZIPDECOMP pZip)
+{
+ /*
+ * Terminate the deflate instance.
+ */
+ int rc = inflateEnd(&pZip->u.Zlib);
+ if (rc != Z_OK)
+ rc = zipErrConvertFromZlib(rc, false /*fCompressing*/);
+ return rc;
+}
+
+
+/**
+ * Initialize the decompressor instance.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ * @param fZlibHeader If true, expect the Zlib header.
+ */
+static DECLCALLBACK(int) rtZipZlibDecompInit(PRTZIPDECOMP pZip, bool fZlibHeader)
+{
+ pZip->pfnDecompress = rtZipZlibDecompress;
+ pZip->pfnDestroy = rtZipZlibDecompDestroy;
+
+ memset(&pZip->u.Zlib, 0, sizeof(pZip->u.Zlib));
+ pZip->u.Zlib.opaque = pZip;
+
+ int rc = inflateInit2(&pZip->u.Zlib, fZlibHeader ? Z_DEF_WBITS : -Z_DEF_WBITS);
+ return rc >= 0 ? VINF_SUCCESS : zipErrConvertFromZlib(rc, false /*fCompressing*/);
+}
+
+#endif /* RTZIP_USE_ZLIB */
+
+
+#ifdef RTZIP_USE_BZLIB
+/**
+ * Convert from BZlib errno to iprt status code.
+ * @returns iprt status code.
+ * @param rc BZlib error code.
+ */
+static int zipErrConvertFromBZlib(int rc)
+{
+ /** @todo proper bzlib error conversion. */
+ switch (rc)
+ {
+ case BZ_SEQUENCE_ERROR:
+ AssertMsgFailed(("BZ_SEQUENCE_ERROR shall not happen!\n"));
+ return VERR_GENERAL_FAILURE;
+ case BZ_PARAM_ERROR:
+ return VERR_INVALID_PARAMETER;
+ case BZ_MEM_ERROR:
+ return VERR_NO_MEMORY;
+ case BZ_DATA_ERROR:
+ case BZ_DATA_ERROR_MAGIC:
+ case BZ_IO_ERROR:
+ case BZ_UNEXPECTED_EOF:
+ case BZ_CONFIG_ERROR:
+ return VERR_GENERAL_FAILURE;
+ case BZ_OUTBUFF_FULL:
+ AssertMsgFailed(("BZ_OUTBUFF_FULL shall not happen!\n"));
+ return VERR_GENERAL_FAILURE;
+ default:
+ if (rc >= 0)
+ return VINF_SUCCESS;
+ return VERR_GENERAL_FAILURE;
+ }
+}
+
+
+/**
+ * @copydoc RTZipCompress
+ */
+static DECLCALLBACK(int) rtZipBZlibCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+ pZip->u.BZlib.next_in = (char *)pvBuf;
+ pZip->u.BZlib.avail_in = cbBuf;
+ while (pZip->u.BZlib.avail_in > 0)
+ {
+ /*
+ * Flush output buffer?
+ */
+ if (pZip->u.BZlib.avail_out <= 0)
+ {
+ int rc = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer) - pZip->u.BZlib.avail_out);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.BZlib.avail_out = sizeof(pZip->abBuffer);
+ pZip->u.BZlib.next_out = (char *)&pZip->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ int rc = BZ2_bzCompress(&pZip->u.BZlib, BZ_RUN);
+ if (rc < 0 && rc != BZ_OUTBUFF_FULL)
+ return zipErrConvertFromBZlib(rc);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompFinish
+ */
+static DECLCALLBACK(int) rtZipBZlibCompFinish(PRTZIPCOMP pZip)
+{
+ int rc = BZ_FINISH_OK;
+ for (;;)
+ {
+ /*
+ * Flush output buffer?
+ */
+ if (rc == BZ_STREAM_END || pZip->u.BZlib.avail_out <= 0)
+ {
+ int rc2 = pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer) - pZip->u.BZlib.avail_out);
+ if (RT_FAILURE(rc2))
+ return rc2;
+ pZip->u.BZlib.avail_out = sizeof(pZip->abBuffer);
+ pZip->u.BZlib.next_out = (char *)&pZip->abBuffer[0];
+ if (rc == BZ_STREAM_END)
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Tell BZlib to finish it.
+ */
+ rc = BZ2_bzCompress(&pZip->u.BZlib, BZ_FINISH);
+ if (rc < 0 && rc != BZ_OUTBUFF_FULL)
+ return zipErrConvertFromBZlib(rc);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompDestroy
+ */
+static DECLCALLBACK(int) rtZipBZlibCompDestroy(PRTZIPCOMP pZip)
+{
+ /*
+ * Terminate the deflate instance.
+ */
+ int rc = BZ2_bzCompressEnd(&pZip->u.BZlib);
+ if (rc != BZ_OK)
+ rc = zipErrConvertFromBZlib(rc);
+ return rc;
+}
+
+
+/**
+ * Initializes the compressor instance.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param enmLevel The desired compression level.
+ */
+static DECLCALLBACK(int) rtZipBZlibCompInit(PRTZIPCOMP pZip, RTZIPLEVEL enmLevel)
+{
+ pZip->pfnCompress = rtZipBZlibCompress;
+ pZip->pfnFinish = rtZipBZlibCompFinish;
+ pZip->pfnDestroy = rtZipBZlibCompDestroy;
+
+ int iSize = 6;
+ int iWork = 0;
+ switch (enmLevel)
+ {
+ case RTZIPLEVEL_STORE: iSize = 1; iWork = 2; break;
+ case RTZIPLEVEL_FAST: iSize = 2; iWork = 0; break;
+ case RTZIPLEVEL_DEFAULT: iSize = 5; iWork = 0; break;
+ case RTZIPLEVEL_MAX: iSize = 9; iWork = 0; break;
+ }
+
+ memset(&pZip->u.BZlib, 0, sizeof(pZip->u.BZlib));
+ pZip->u.BZlib.next_out = (char *)&pZip->abBuffer[1];
+ pZip->u.BZlib.avail_out = sizeof(pZip->abBuffer) - 1;
+ pZip->u.BZlib.opaque = pZip;
+
+ int rc = BZ2_bzCompressInit(&pZip->u.BZlib, iSize, 0, iWork);
+ return rc >= 0 ? VINF_SUCCESS : zipErrConvertFromBZlib(rc);;
+}
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipBZlibDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ pZip->u.BZlib.next_out = (char *)pvBuf;
+ pZip->u.BZlib.avail_out = cbBuf;
+ while (pZip->u.BZlib.avail_out > 0)
+ {
+ /*
+ * Read more output buffer?
+ */
+ if (pZip->u.BZlib.avail_in <= 0)
+ {
+ size_t cb;
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0], sizeof(pZip->abBuffer), &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.BZlib.avail_in = cb;
+ pZip->u.BZlib.next_in = (char *)&pZip->abBuffer[0];
+ }
+
+ /*
+ * Pass it on to zlib.
+ */
+ int rc = BZ2_bzDecompress(&pZip->u.BZlib);
+ if (rc == BZ_STREAM_END || rc == BZ_OUTBUFF_FULL)
+ {
+ if (pcbWritten)
+ *pcbWritten = cbBuf - pZip->u.BZlib.avail_out;
+ else if (pZip->u.BZlib.avail_out > 0)
+ return VERR_NO_DATA;
+ break;
+ }
+ if (rc < 0)
+ return zipErrConvertFromBZlib(rc);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipBZlibDecompDestroy(PRTZIPDECOMP pZip)
+{
+ /*
+ * Terminate the deflate instance.
+ */
+ int rc = BZ2_bzDecompressEnd(&pZip->u.BZlib);
+ if (rc != BZ_OK)
+ rc = zipErrConvertFromBZlib(rc);
+ return rc;
+}
+
+
+/**
+ * Initialize the decompressor instance.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+static DECLCALLBACK(int) rtZipBZlibDecompInit(PRTZIPDECOMP pZip)
+{
+ pZip->pfnDecompress = rtZipBZlibDecompress;
+ pZip->pfnDestroy = rtZipBZlibDecompDestroy;
+
+ memset(&pZip->u.BZlib, 0, sizeof(pZip->u.BZlib));
+ pZip->u.BZlib.opaque = pZip;
+
+ int rc = BZ2_bzDecompressInit(&pZip->u.BZlib, 0, 0);
+ return rc >= 0 ? VINF_SUCCESS : zipErrConvertFromBZlib(rc);
+}
+
+#endif /* RTZIP_USE_BZLIB */
+
+
+#ifdef RTZIP_USE_LZF
+
+/**
+ * Flushes the output buffer.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ */
+static int rtZipLZFCompFlushOutput(PRTZIPCOMP pZip)
+{
+ size_t cb = pZip->u.LZF.pbOutput - &pZip->abBuffer[0];
+ pZip->u.LZF.pbOutput = &pZip->abBuffer[0];
+ return pZip->pfnOut(pZip->pvUser, &pZip->abBuffer[0], cb);
+}
+
+
+/**
+ * Compresses a buffer using LZF.
+ *
+ * @returns VBox status code.
+ * @param pZip The compressor instance.
+ * @param pbBuf What to compress.
+ * @param cbBuf How much to compress.
+ */
+static int rtZipLZFCompressBuffer(PRTZIPCOMP pZip, const uint8_t *pbBuf, size_t cbBuf)
+{
+ bool fForceFlush = false;
+ while (cbBuf > 0)
+ {
+ /*
+ * Flush output buffer?
+ */
+ unsigned cbFree = (unsigned)(sizeof(pZip->abBuffer) - (pZip->u.LZF.pbOutput - &pZip->abBuffer[0]));
+ if ( fForceFlush
+ || cbFree < RTZIPLZF_MAX_DATA_SIZE + sizeof(RTZIPLZFHDR))
+ {
+ int rc = rtZipLZFCompFlushOutput(pZip);
+ if (RT_FAILURE(rc))
+ return rc;
+ fForceFlush = false;
+ cbFree = sizeof(pZip->abBuffer);
+ }
+
+ /*
+ * Setup the block header.
+ */
+ PRTZIPLZFHDR pHdr = (PRTZIPLZFHDR)pZip->u.LZF.pbOutput; /* warning: This might be unaligned! */
+ pHdr->u16Magic = RTZIPLZFHDR_MAGIC;
+ pHdr->cbData = 0;
+ pHdr->u32CRC = 0;
+ pHdr->cbUncompressed = 0;
+ cbFree -= sizeof(*pHdr);
+ pZip->u.LZF.pbOutput += sizeof(*pHdr);
+
+ /*
+ * Compress data for the block.
+ *
+ * We try compress as much as we have freespace for at first,
+ * but if it turns out the compression is inefficient, we'll
+ * reduce the size of data we try compress till it fits the
+ * output space.
+ */
+ cbFree = RT_MIN(cbFree, RTZIPLZF_MAX_DATA_SIZE);
+ unsigned cbInput = (unsigned)RT_MIN(RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE, cbBuf);
+ unsigned cbOutput = lzf_compress(pbBuf, cbInput, pZip->u.LZF.pbOutput, cbFree);
+ if (!cbOutput)
+ {
+ /** @todo add an alternative method which stores the raw data if bad compression. */
+ do
+ {
+ cbInput /= 2;
+ if (!cbInput)
+ {
+ AssertMsgFailed(("lzf_compress bug! cbFree=%zu\n", cbFree));
+ return VERR_INTERNAL_ERROR;
+ }
+ cbOutput = lzf_compress(pbBuf, cbInput, pZip->u.LZF.pbOutput, cbFree);
+ } while (!cbOutput);
+ fForceFlush = true;
+ }
+
+ /*
+ * Update the header and advance the input buffer.
+ */
+ pHdr->cbData = cbOutput;
+ //pHdr->u32CRC = RTCrc32(pbBuf, cbInput); - too slow
+ pHdr->cbUncompressed = cbInput;
+
+ pZip->u.LZF.pbOutput += cbOutput;
+ cbBuf -= cbInput;
+ pbBuf += cbInput;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Flushes the input buffer.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ */
+static int rtZipLZFCompFlushInput(PRTZIPCOMP pZip)
+{
+ size_t cb = pZip->u.LZF.pbInput - &pZip->u.LZF.abInput[0];
+ pZip->u.LZF.pbInput = &pZip->u.LZF.abInput[0];
+ pZip->u.LZF.cbInputFree = sizeof(pZip->u.LZF.abInput);
+ if (cb)
+ return rtZipLZFCompressBuffer(pZip, pZip->u.LZF.abInput, cb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompress
+ */
+static DECLCALLBACK(int) rtZipLZFCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+#define RTZIPLZF_SMALL_CHUNK (128)
+
+ /*
+ * Flush the input buffer if necessary.
+ */
+ if ( ( cbBuf <= RTZIPLZF_SMALL_CHUNK
+ && cbBuf > pZip->u.LZF.cbInputFree)
+ || ( cbBuf > RTZIPLZF_SMALL_CHUNK
+ && pZip->u.LZF.cbInputFree != sizeof(pZip->u.LZF.abInput))
+ )
+ {
+ int rc = rtZipLZFCompFlushInput(pZip);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * If it's a relativly small block put it in the input buffer, elsewise
+ * compress directly it.
+ */
+ if (cbBuf <= RTZIPLZF_SMALL_CHUNK)
+ {
+ Assert(pZip->u.LZF.cbInputFree >= cbBuf);
+ memcpy(pZip->u.LZF.pbInput, pvBuf, cbBuf);
+ pZip->u.LZF.pbInput += cbBuf;
+ pZip->u.LZF.cbInputFree -= cbBuf;
+ }
+ else
+ {
+ Assert(pZip->u.LZF.cbInputFree == sizeof(pZip->u.LZF.abInput));
+ int rc = rtZipLZFCompressBuffer(pZip, (const uint8_t *)pvBuf, cbBuf);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipCompFinish
+ */
+static DECLCALLBACK(int) rtZipLZFCompFinish(PRTZIPCOMP pZip)
+{
+ int rc = rtZipLZFCompFlushInput(pZip);
+ if (RT_SUCCESS(rc))
+ rc = rtZipLZFCompFlushOutput(pZip);
+ return rc;
+}
+
+
+/**
+ * @copydoc RTZipCompDestroy
+ */
+static DECLCALLBACK(int) rtZipLZFCompDestroy(PRTZIPCOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initializes the compressor instance.
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param enmLevel The desired compression level.
+ */
+static DECLCALLBACK(int) rtZipLZFCompInit(PRTZIPCOMP pZip, RTZIPLEVEL enmLevel)
+{
+ NOREF(enmLevel);
+ pZip->pfnCompress = rtZipLZFCompress;
+ pZip->pfnFinish = rtZipLZFCompFinish;
+ pZip->pfnDestroy = rtZipLZFCompDestroy;
+
+ pZip->u.LZF.pbOutput = &pZip->abBuffer[1];
+ pZip->u.LZF.pbInput = &pZip->u.LZF.abInput[0];
+ pZip->u.LZF.cbInputFree = sizeof(pZip->u.LZF.abInput);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * This will validate a header and to all the necessary bitching if it's invalid.
+ * @returns true if valid.
+ * @returns false if invalid.
+ * @param pHdr Pointer to the header.\
+ */
+static bool rtZipLZFValidHeader(PCRTZIPLZFHDR pHdr)
+{
+ if ( pHdr->u16Magic != RTZIPLZFHDR_MAGIC
+ || !pHdr->cbData
+ || pHdr->cbData > RTZIPLZF_MAX_DATA_SIZE
+ || !pHdr->cbUncompressed
+ || pHdr->cbUncompressed > RTZIPLZF_MAX_UNCOMPRESSED_DATA_SIZE
+ )
+ {
+ AssertMsgFailed(("Invalid LZF header! %.*Rhxs\n", sizeof(*pHdr), pHdr));
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipLZFDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ /*
+ * Decompression loop.
+ *
+ * This a bit ugly because we have to deal with reading block...
+ * To simplify matters we've put a max block size and will never
+ * fill the input buffer with more than allows us to complete
+ * any partially read blocks.
+ *
+ * When possible we decompress directly to the user buffer, when
+ * not possible we'll use the spill buffer.
+ */
+# ifdef RTZIP_LZF_BLOCK_BY_BLOCK
+ size_t cbWritten = 0;
+ while (cbBuf > 0)
+ {
+ /*
+ * Anything in the spill buffer?
+ */
+ if (pZip->u.LZF.cbSpill > 0)
+ {
+ unsigned cb = (unsigned)RT_MIN(pZip->u.LZF.cbSpill, cbBuf);
+ memcpy(pvBuf, pZip->u.LZF.pbSpill, cb);
+ pZip->u.LZF.pbSpill += cb;
+ pZip->u.LZF.cbSpill -= cb;
+ cbWritten += cb;
+ cbBuf -= cb;
+ if (!cbBuf)
+ break;
+ pvBuf = (uint8_t *)pvBuf + cb;
+ }
+
+ /*
+ * We always read and work one block at a time.
+ */
+ RTZIPLZFHDR Hdr;
+ int rc = pZip->pfnIn(pZip->pvUser, &Hdr, sizeof(Hdr), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ if (!rtZipLZFValidHeader(&Hdr))
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ if (Hdr.cbData > 0)
+ {
+ rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0], Hdr.cbData, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Does the uncompressed data fit into the supplied buffer?
+ * If so we uncompress it directly into the user buffer, else we'll have to use the spill buffer.
+ */
+ unsigned cbUncompressed = Hdr.cbUncompressed;
+ if (cbUncompressed <= cbBuf)
+ {
+ unsigned cbOutput = lzf_decompress(&pZip->abBuffer[0], Hdr.cbData, pvBuf, cbUncompressed);
+ if (cbOutput != cbUncompressed)
+ {
+# ifndef IPRT_NO_CRT /* no errno */
+ AssertMsgFailed(("Decompression error, errno=%d. cbOutput=%#x cbUncompressed=%#x\n",
+ errno, cbOutput, cbUncompressed));
+# endif
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ cbBuf -= cbUncompressed;
+ pvBuf = (uint8_t *)pvBuf + cbUncompressed;
+ cbWritten += cbUncompressed;
+ }
+ else
+ {
+ unsigned cbOutput = lzf_decompress(&pZip->abBuffer[0], Hdr.cbData, pZip->u.LZF.abSpill, cbUncompressed);
+ if (cbOutput != cbUncompressed)
+ {
+# ifndef IPRT_NO_CRT /* no errno */
+ AssertMsgFailed(("Decompression error, errno=%d. cbOutput=%#x cbUncompressed=%#x\n",
+ errno, cbOutput, cbUncompressed));
+# endif
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ pZip->u.LZF.pbSpill = &pZip->u.LZF.abSpill[0];
+ pZip->u.LZF.cbSpill = cbUncompressed;
+ }
+ }
+
+ if (pcbWritten)
+ *pcbWritten = cbWritten;
+# else /* !RTZIP_LZF_BLOCK_BY_BLOCK */
+ while (cbBuf > 0)
+ {
+ /*
+ * Anything in the spill buffer?
+ */
+ if (pZip->u.LZF.cbSpill > 0)
+ {
+ unsigned cb = (unsigned)RT_MIN(pZip->u.LZF.cbSpill, cbBuf);
+ memcpy(pvBuf, pZip->u.LZF.pbSpill, cb);
+ pZip->u.LZF.pbSpill += cb;
+ pZip->u.LZF.cbSpill -= cb;
+ cbBuf -= cb;
+ if (pcbWritten)
+ *pcbWritten = cb;
+ if (!cbBuf)
+ break;
+ pvBuf = (uint8_t *)pvBuf + cb;
+ }
+
+ /*
+ * Incomplete header or nothing at all.
+ */
+ PCRTZIPLZFHDR pHdr;
+ if (pZip->u.LZF.cbInput < sizeof(RTZIPLZFHDR))
+ {
+ if (pZip->u.LZF.cbInput <= 0)
+ {
+ /* empty, fill the buffer. */
+ size_t cb = 0;
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[0],
+ sizeof(pZip->abBuffer) - RTZIPLZF_MAX_DATA_SIZE, &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.LZF.pbInput = &pZip->abBuffer[0];
+ pZip->u.LZF.cbInput = cb;
+ pHdr = (PCRTZIPLZFHDR)pZip->u.LZF.pbInput;
+ }
+ else
+ {
+ /* move the header up and fill the buffer. */
+ size_t cbCur = pZip->u.LZF.cbInput;
+ memmove(&pZip->abBuffer[0], pZip->u.LZF.pbInput, cbCur);
+ pZip->u.LZF.pbInput = &pZip->abBuffer[0];
+
+ size_t cb = 0;
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->abBuffer[cbCur],
+ sizeof(pZip->abBuffer) - RTZIPLZF_MAX_DATA_SIZE - cbCur, &cb);
+ if (RT_FAILURE(rc))
+ return rc;
+ pHdr = (PCRTZIPLZFHDR)pZip->u.LZF.pbInput;
+ pZip->u.LZF.cbInput += cb;
+ }
+
+ /*
+ * Validate the header.
+ */
+ if (!rtZipLZFValidHeader(pHdr))
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ else
+ {
+ /*
+ * Validate the header and check if it's an incomplete block.
+ */
+ pHdr = (PCRTZIPLZFHDR)pZip->u.LZF.pbInput;
+ if (!rtZipLZFValidHeader(pHdr))
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+
+ if (pHdr->cbData > pZip->u.LZF.cbInput - sizeof(*pHdr))
+ {
+ /* read the remainder of the block. */
+ size_t cbToRead = pHdr->cbData - (pZip->u.LZF.cbInput - sizeof(*pHdr));
+ Assert(&pZip->u.LZF.pbInput[pZip->u.LZF.cbInput + cbToRead] <= &pZip->u.LZF.pbInput[sizeof(pZip->abBuffer)]);
+ int rc = pZip->pfnIn(pZip->pvUser, &pZip->u.LZF.pbInput[pZip->u.LZF.cbInput],
+ cbToRead, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ pZip->u.LZF.cbInput += cbToRead;
+ }
+ }
+ AssertMsgReturn(sizeof(*pHdr) + pHdr->cbData <= pZip->u.LZF.cbInput,
+ ("cbData=%#x cbInput=%#x\n", pHdr->cbData, pZip->u.LZF.cbInput),
+ VERR_GENERAL_FAILURE); /** @todo Get better error codes for RTZip! */
+
+ /*
+ * Does the uncompressed data fit into the supplied buffer?
+ * If so we uncompress it directly into the user buffer, else we'll have to use the spill buffer.
+ */
+ unsigned cbUncompressed = pHdr->cbUncompressed;
+ if (cbUncompressed <= cbBuf)
+ {
+ unsigned cbOutput = lzf_decompress(pHdr + 1, pHdr->cbData, pvBuf, cbUncompressed);
+ if (cbOutput != cbUncompressed)
+ {
+ AssertMsgFailed(("Decompression error, errno=%d. cbOutput=%#x cbUncompressed=%#x\n",
+ errno, cbOutput, cbUncompressed));
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ cbBuf -= cbUncompressed;
+ pvBuf = (uint8_t *)pvBuf + cbUncompressed;
+ }
+ else
+ {
+ unsigned cbOutput = lzf_decompress(pHdr + 1, pHdr->cbData, pZip->u.LZF.abSpill, cbUncompressed);
+ if (cbOutput != cbUncompressed)
+ {
+ AssertMsgFailed(("Decompression error, errno=%d. cbOutput=%#x cbUncompressed=%#x\n",
+ errno, cbOutput, cbUncompressed));
+ return VERR_GENERAL_FAILURE; /** @todo Get better error codes for RTZip! */
+ }
+ pZip->u.LZF.pbSpill = &pZip->u.LZF.abSpill[0];
+ pZip->u.LZF.cbSpill = cbUncompressed;
+ }
+
+ /* advance the input buffer */
+ pZip->u.LZF.cbInput -= pHdr->cbData + sizeof(*pHdr);
+ pZip->u.LZF.pbInput += pHdr->cbData + sizeof(*pHdr);
+ if (pcbWritten)
+ *pcbWritten += cbUncompressed;
+ }
+# endif /* !RTZIP_LZF_BLOCK_BY_BLOCK */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipLZFDecompDestroy(PRTZIPDECOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Initialize the decompressor instance.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+static DECLCALLBACK(int) rtZipLZFDecompInit(PRTZIPDECOMP pZip)
+{
+ pZip->pfnDecompress = rtZipLZFDecompress;
+ pZip->pfnDestroy = rtZipLZFDecompDestroy;
+
+# ifndef RTZIP_LZF_BLOCK_BY_BLOCK
+ pZip->u.LZF.pbInput = NULL;
+ pZip->u.LZF.cbInput = 0;
+# endif
+ pZip->u.LZF.cbSpill = 0;
+ pZip->u.LZF.pbSpill = NULL;
+
+ return VINF_SUCCESS;
+}
+
+#endif /* RTZIP_USE_LZF */
+
+
+/**
+ * Create a compressor instance.
+ *
+ * @returns iprt status code.
+ * @param ppZip Where to store the instance handle.
+ * @param pvUser User argument which will be passed on to pfnOut and pfnIn.
+ * @param pfnOut Callback for consuming output of compression.
+ * @param enmType Type of compressor to create.
+ * @param enmLevel Compression level.
+ */
+RTDECL(int) RTZipCompCreate(PRTZIPCOMP *ppZip, void *pvUser, PFNRTZIPOUT pfnOut, RTZIPTYPE enmType, RTZIPLEVEL enmLevel)
+{
+ /*
+ * Validate input.
+ */
+ AssertReturn(enmType >= RTZIPTYPE_INVALID && enmType < RTZIPTYPE_END, VERR_INVALID_PARAMETER);
+ AssertReturn(enmLevel >= RTZIPLEVEL_STORE && enmLevel <= RTZIPLEVEL_MAX, VERR_INVALID_PARAMETER);
+ AssertPtrReturn(pfnOut, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppZip, VERR_INVALID_POINTER);
+
+ /*
+ * Allocate memory for the instance data.
+ */
+ PRTZIPCOMP pZip = (PRTZIPCOMP)RTMemAlloc(sizeof(RTZIPCOMP));
+ if (!pZip)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Determine auto type.
+ */
+ if (enmType == RTZIPTYPE_AUTO)
+ {
+ if (enmLevel == RTZIPLEVEL_STORE)
+ enmType = RTZIPTYPE_STORE;
+ else
+ {
+#if defined(RTZIP_USE_ZLIB) && defined(RTZIP_USE_BZLIB)
+ if (enmLevel == RTZIPLEVEL_MAX)
+ enmType = RTZIPTYPE_BZLIB;
+ else
+ enmType = RTZIPTYPE_ZLIB;
+#elif defined(RTZIP_USE_ZLIB)
+ enmType = RTZIPTYPE_ZLIB;
+#elif defined(RTZIP_USE_BZLIB)
+ enmType = RTZIPTYPE_BZLIB;
+#else
+ enmType = RTZIPTYPE_STORE;
+#endif
+ }
+ }
+
+ /*
+ * Init instance.
+ */
+ pZip->pfnOut = pfnOut;
+ pZip->enmType = enmType;
+ pZip->pvUser = pvUser;
+ pZip->abBuffer[0] = enmType; /* first byte is the compression type. */
+ int rc = VERR_NOT_IMPLEMENTED;
+ switch (enmType)
+ {
+ case RTZIPTYPE_STORE:
+#ifdef RTZIP_USE_STORE
+ rc = rtZipStoreCompInit(pZip, enmLevel);
+#endif
+ break;
+
+ case RTZIPTYPE_ZLIB:
+ case RTZIPTYPE_ZLIB_NO_HEADER:
+#ifdef RTZIP_USE_ZLIB
+ rc = rtZipZlibCompInit(pZip, enmLevel, enmType == RTZIPTYPE_ZLIB /*fZlibHeader*/);
+#endif
+ break;
+
+ case RTZIPTYPE_BZLIB:
+#ifdef RTZIP_USE_BZLIB
+ rc = rtZipBZlibCompInit(pZip, enmLevel);
+#endif
+ break;
+
+ case RTZIPTYPE_LZF:
+#ifdef RTZIP_USE_LZF
+ rc = rtZipLZFCompInit(pZip, enmLevel);
+#endif
+ break;
+
+ case RTZIPTYPE_LZJB:
+ case RTZIPTYPE_LZO:
+ break;
+
+ default:
+ AssertFailedBreak();
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppZip = pZip;
+ else
+ RTMemFree(pZip);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTZipCompCreate);
+
+
+/**
+ * Compresses a chunk of memory.
+ *
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ * @param pvBuf Pointer to buffer containing the bits to compress.
+ * @param cbBuf Number of bytes to compress.
+ */
+RTDECL(int) RTZipCompress(PRTZIPCOMP pZip, const void *pvBuf, size_t cbBuf)
+{
+ if (!cbBuf)
+ return VINF_SUCCESS;
+ return pZip->pfnCompress(pZip, pvBuf, cbBuf);
+}
+RT_EXPORT_SYMBOL(RTZipCompress);
+
+
+/**
+ * Finishes the compression.
+ * This will flush all data and terminate the compression data stream.
+ *
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ */
+RTDECL(int) RTZipCompFinish(PRTZIPCOMP pZip)
+{
+ return pZip->pfnFinish(pZip);
+}
+RT_EXPORT_SYMBOL(RTZipCompFinish);
+
+
+/**
+ * Destroys the compressor instance.
+ *
+ * @returns iprt status code.
+ * @param pZip The compressor instance.
+ */
+RTDECL(int) RTZipCompDestroy(PRTZIPCOMP pZip)
+{
+ /*
+ * Compressor specific destruction attempt first.
+ */
+ int rc = pZip->pfnDestroy(pZip);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Free the instance memory.
+ */
+ pZip->enmType = RTZIPTYPE_INVALID;
+ RTMemFree(pZip);
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTZipCompDestroy);
+
+
+/**
+ * @copydoc RTZipDecompress
+ */
+static DECLCALLBACK(int) rtZipStubDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ NOREF(pZip); NOREF(pvBuf); NOREF(cbBuf); NOREF(pcbWritten);
+ return VERR_NOT_SUPPORTED;
+}
+
+
+/**
+ * @copydoc RTZipDecompDestroy
+ */
+static DECLCALLBACK(int) rtZipStubDecompDestroy(PRTZIPDECOMP pZip)
+{
+ NOREF(pZip);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Create a decompressor instance.
+ *
+ * @returns iprt status code.
+ * @param ppZip Where to store the instance handle.
+ * @param pvUser User argument which will be passed on to pfnOut and pfnIn.
+ * @param pfnIn Callback for producing input for decompression.
+ */
+RTDECL(int) RTZipDecompCreate(PRTZIPDECOMP *ppZip, void *pvUser, PFNRTZIPIN pfnIn)
+{
+ /*
+ * Validate input.
+ */
+ AssertPtrReturn(pfnIn, VERR_INVALID_POINTER);
+ AssertPtrReturn(ppZip, VERR_INVALID_POINTER);
+
+ /*
+ * Allocate memory for the instance data.
+ */
+ PRTZIPDECOMP pZip = (PRTZIPDECOMP)RTMemAlloc(sizeof(RTZIPDECOMP));
+ if (!pZip)
+ return VERR_NO_MEMORY;
+
+ /*
+ * Init instance.
+ */
+ pZip->pfnIn = pfnIn;
+ pZip->enmType = RTZIPTYPE_INVALID;
+ pZip->pvUser = pvUser;
+ pZip->pfnDecompress = NULL;
+ pZip->pfnDestroy = rtZipStubDecompDestroy;
+
+ *ppZip = pZip;
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTZipDecompCreate);
+
+
+/**
+ * Lazy init of the decompressor.
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+static int rtzipDecompInit(PRTZIPDECOMP pZip)
+{
+ /*
+ * Read the first byte from the stream so we can determine the type.
+ */
+ uint8_t u8Type;
+ int rc = pZip->pfnIn(pZip->pvUser, &u8Type, sizeof(u8Type), NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Determine type and do type specific init.
+ */
+ pZip->enmType = (RTZIPTYPE)u8Type;
+ rc = VERR_NOT_SUPPORTED;
+ switch (pZip->enmType)
+ {
+ case RTZIPTYPE_STORE:
+#ifdef RTZIP_USE_STORE
+ rc = rtZipStoreDecompInit(pZip);
+#else
+ AssertMsgFailed(("Store is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_ZLIB:
+ case RTZIPTYPE_ZLIB_NO_HEADER:
+#ifdef RTZIP_USE_ZLIB
+ rc = rtZipZlibDecompInit(pZip, pZip->enmType == RTZIPTYPE_ZLIB /*fHeader*/);
+#else
+ AssertMsgFailed(("Zlib is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_BZLIB:
+#ifdef RTZIP_USE_BZLIB
+ rc = rtZipBZlibDecompInit(pZip);
+#else
+ AssertMsgFailed(("BZlib is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_LZF:
+#ifdef RTZIP_USE_LZF
+ rc = rtZipLZFDecompInit(pZip);
+#else
+ AssertMsgFailed(("LZF is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_LZJB:
+#ifdef RTZIP_USE_LZJB
+ AssertMsgFailed(("LZJB streaming support is not implemented yet!\n"));
+#else
+ AssertMsgFailed(("LZJB is not include in this build!\n"));
+#endif
+ break;
+
+ case RTZIPTYPE_LZO:
+#ifdef RTZIP_USE_LZJB
+ AssertMsgFailed(("LZO streaming support is not implemented yet!\n"));
+#else
+ AssertMsgFailed(("LZO is not include in this build!\n"));
+#endif
+ break;
+
+ default:
+ AssertMsgFailed(("Invalid compression type %d (%#x)!\n", pZip->enmType, pZip->enmType));
+ rc = VERR_INVALID_MAGIC;
+ break;
+ }
+ if (RT_FAILURE(rc))
+ {
+ pZip->pfnDecompress = rtZipStubDecompress;
+ pZip->pfnDestroy = rtZipStubDecompDestroy;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Decompresses a chunk of memory.
+ *
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ * @param pvBuf Where to store the decompressed data.
+ * @param cbBuf Number of bytes to produce. If pcbWritten is set
+ * any number of bytes up to cbBuf might be returned.
+ * @param pcbWritten Number of bytes actually written to the buffer. If NULL
+ * cbBuf number of bytes must be written.
+ */
+RTDECL(int) RTZipDecompress(PRTZIPDECOMP pZip, void *pvBuf, size_t cbBuf, size_t *pcbWritten)
+{
+ /*
+ * Skip empty requests.
+ */
+ if (!cbBuf)
+ return VINF_SUCCESS;
+
+ /*
+ * Lazy init.
+ */
+ if (!pZip->pfnDecompress)
+ {
+ int rc = rtzipDecompInit(pZip);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * 'Read' the decompressed stream.
+ */
+ return pZip->pfnDecompress(pZip, pvBuf, cbBuf, pcbWritten);
+}
+RT_EXPORT_SYMBOL(RTZipDecompress);
+
+
+/**
+ * Destroys the decompressor instance.
+ *
+ * @returns iprt status code.
+ * @param pZip The decompressor instance.
+ */
+RTDECL(int) RTZipDecompDestroy(PRTZIPDECOMP pZip)
+{
+ /*
+ * Destroy compressor instance and flush the output buffer.
+ */
+ int rc = pZip->pfnDestroy(pZip);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Free the instance memory.
+ */
+ pZip->enmType = RTZIPTYPE_INVALID;
+ RTMemFree(pZip);
+ return rc;
+}
+RT_EXPORT_SYMBOL(RTZipDecompDestroy);
+
+
+RTDECL(int) RTZipBlockCompress(RTZIPTYPE enmType, RTZIPLEVEL enmLevel, uint32_t fFlags,
+ void const *pvSrc, size_t cbSrc,
+ void *pvDst, size_t cbDst, size_t *pcbDstActual) RT_NO_THROW_DEF
+{
+ /* input validation - the crash and burn approach as speed is essential here. */
+ Assert(enmLevel <= RTZIPLEVEL_MAX && enmLevel >= RTZIPLEVEL_STORE); RT_NOREF_PV(enmLevel);
+ Assert(!fFlags); RT_NOREF_PV(fFlags);
+
+ /*
+ * Deal with flags involving prefixes.
+ */
+ /** @todo later: type and/or compressed length prefix. */
+
+ /*
+ * The type specific part.
+ */
+ switch (enmType)
+ {
+ case RTZIPTYPE_LZF:
+ {
+#ifdef RTZIP_USE_LZF
+# if 0
+ static const uint8_t s_abZero4K[] =
+ {
+ 0x01, 0x00, 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff,
+ 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0,
+ 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00,
+ 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff,
+ 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0,
+ 0xff, 0x00, 0xe0, 0xff, 0x00, 0xe0, 0xff, 0x00,
+ 0xe0, 0x7d, 0x00
+ };
+ if ( cbSrc == _4K
+ && !((uintptr_t)pvSrc & 15)
+ && ASMMemIsZeroPage(pvSrc))
+ {
+ if (RT_UNLIKELY(cbDst < sizeof(s_abZero4K)))
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pvDst, s_abZero4K, sizeof(s_abZero4K));
+ *pcbDstActual = sizeof(s_abZero4K);
+ break;
+ }
+# endif
+
+ unsigned cbDstActual = lzf_compress(pvSrc, (unsigned)cbSrc, pvDst, (unsigned)cbDst); /** @todo deal with size type overflows */
+ if (RT_UNLIKELY(cbDstActual < 1))
+ return VERR_BUFFER_OVERFLOW;
+ *pcbDstActual = cbDstActual;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_STORE:
+ {
+ if (cbDst < cbSrc)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pvDst, pvSrc, cbSrc);
+ *pcbDstActual = cbSrc;
+ break;
+ }
+
+ case RTZIPTYPE_LZJB:
+ {
+#ifdef RTZIP_USE_LZJB
+ AssertReturn(cbDst > cbSrc, VERR_BUFFER_OVERFLOW);
+ size_t cbDstActual = lzjb_compress((void *)pvSrc, (uint8_t *)pvDst + 1, cbSrc, cbSrc, 0 /*??*/);
+ if (cbDstActual == cbSrc)
+ *(uint8_t *)pvDst = 0;
+ else
+ *(uint8_t *)pvDst = 1;
+ *pcbDstActual = cbDstActual + 1;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_LZO:
+ {
+#ifdef RTZIP_USE_LZO
+ uint64_t Scratch[RT_ALIGN(LZO1X_1_MEM_COMPRESS, sizeof(uint64_t)) / sizeof(uint64_t)];
+ int rc = lzo_init();
+ if (RT_UNLIKELY(rc != LZO_E_OK))
+ return VERR_INTERNAL_ERROR;
+
+ lzo_uint cbDstInOut = cbDst;
+ rc = lzo1x_1_compress((const lzo_bytep)pvSrc, cbSrc, (lzo_bytep )pvDst, &cbDstInOut, &Scratch[0]);
+ if (RT_UNLIKELY(rc != LZO_E_OK))
+ switch (rc)
+ {
+ case LZO_E_OUTPUT_OVERRUN: return VERR_BUFFER_OVERFLOW;
+ default: return VERR_GENERAL_FAILURE;
+ }
+ *pcbDstActual = cbDstInOut;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_ZLIB:
+ case RTZIPTYPE_BZLIB:
+ return VERR_NOT_SUPPORTED;
+
+ default:
+ AssertMsgFailed(("%d\n", enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTZipBlockCompress);
+
+
+RTDECL(int) RTZipBlockDecompress(RTZIPTYPE enmType, uint32_t fFlags,
+ void const *pvSrc, size_t cbSrc, size_t *pcbSrcActual,
+ void *pvDst, size_t cbDst, size_t *pcbDstActual) RT_NO_THROW_DEF
+{
+ /* input validation - the crash and burn approach as speed is essential here. */
+ Assert(!fFlags); RT_NOREF_PV(fFlags);
+
+ /*
+ * Deal with flags involving prefixes.
+ */
+ /** @todo later: type and/or compressed length prefix. */
+
+ /*
+ * The type specific part.
+ */
+ switch (enmType)
+ {
+ case RTZIPTYPE_LZF:
+ {
+#ifdef RTZIP_USE_LZF
+ unsigned cbDstActual = lzf_decompress(pvSrc, (unsigned)cbSrc, pvDst, (unsigned)cbDst); /** @todo deal with size type overflows */
+ if (RT_UNLIKELY(cbDstActual < 1))
+ {
+# ifndef IPRT_NO_CRT /* no errno */
+ if (errno == E2BIG)
+ return VERR_BUFFER_OVERFLOW;
+ Assert(errno == EINVAL);
+# endif
+ return VERR_GENERAL_FAILURE;
+ }
+ if (pcbDstActual)
+ *pcbDstActual = cbDstActual;
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_STORE:
+ {
+ if (cbDst < cbSrc)
+ return VERR_BUFFER_OVERFLOW;
+ memcpy(pvDst, pvSrc, cbSrc);
+ if (pcbDstActual)
+ *pcbDstActual = cbSrc;
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc;
+ break;
+ }
+
+ case RTZIPTYPE_LZJB:
+ {
+#ifdef RTZIP_USE_LZJB
+ if (*(uint8_t *)pvSrc == 1)
+ {
+ int rc = lzjb_decompress((uint8_t *)pvSrc + 1, pvDst, cbSrc - 1, cbDst, 0 /*??*/);
+ if (RT_UNLIKELY(rc != 0))
+ return VERR_GENERAL_FAILURE;
+ if (pcbDstActual)
+ *pcbDstActual = cbDst;
+ }
+ else
+ {
+ AssertReturn(cbDst >= cbSrc - 1, VERR_BUFFER_OVERFLOW);
+ memcpy(pvDst, (uint8_t *)pvSrc + 1, cbSrc - 1);
+ if (pcbDstActual)
+ *pcbDstActual = cbSrc - 1;
+ }
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_LZO:
+ {
+#ifdef RTZIP_USE_LZO
+ int rc = lzo_init();
+ if (RT_UNLIKELY(rc != LZO_E_OK))
+ return VERR_INTERNAL_ERROR;
+ lzo_uint cbDstInOut = cbDst;
+ rc = lzo1x_decompress((const lzo_bytep)pvSrc, cbSrc, (lzo_bytep)pvDst, &cbDstInOut, NULL);
+ if (RT_UNLIKELY(rc != LZO_E_OK))
+ switch (rc)
+ {
+ case LZO_E_OUTPUT_OVERRUN: return VERR_BUFFER_OVERFLOW;
+ default:
+ case LZO_E_INPUT_OVERRUN: return VERR_GENERAL_FAILURE;
+ }
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc;
+ if (pcbDstActual)
+ *pcbDstActual = cbDstInOut;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_ZLIB:
+ case RTZIPTYPE_ZLIB_NO_HEADER:
+ {
+#ifdef RTZIP_USE_ZLIB
+ AssertReturn(cbSrc == (uInt)cbSrc, VERR_TOO_MUCH_DATA);
+ AssertReturn(cbDst == (uInt)cbDst, VERR_OUT_OF_RANGE);
+
+ z_stream ZStrm;
+ RT_ZERO(ZStrm);
+ ZStrm.next_in = (Bytef *)pvSrc;
+ ZStrm.avail_in = (uInt)cbSrc;
+ ZStrm.next_out = (Bytef *)pvDst;
+ ZStrm.avail_out = (uInt)cbDst;
+
+ int rc;
+ if (enmType == RTZIPTYPE_ZLIB)
+ rc = inflateInit(&ZStrm);
+ else if (enmType == RTZIPTYPE_ZLIB_NO_HEADER)
+ rc = inflateInit2(&ZStrm, -Z_DEF_WBITS);
+ else
+ AssertFailedReturn(VERR_INTERNAL_ERROR);
+
+ if (RT_UNLIKELY(rc != Z_OK))
+ return zipErrConvertFromZlib(rc, false /*fCompressing*/);
+ rc = inflate(&ZStrm, Z_FINISH);
+ if (rc != Z_STREAM_END)
+ {
+ inflateEnd(&ZStrm);
+ if ((rc == Z_BUF_ERROR && ZStrm.avail_in == 0) || rc == Z_NEED_DICT)
+ return VERR_ZIP_CORRUPTED;
+ if (rc == Z_BUF_ERROR)
+ return VERR_BUFFER_OVERFLOW;
+ AssertReturn(rc < Z_OK, VERR_GENERAL_FAILURE);
+ return zipErrConvertFromZlib(rc, false /*fCompressing*/);
+ }
+ rc = inflateEnd(&ZStrm);
+ if (rc != Z_OK)
+ return zipErrConvertFromZlib(rc, false /*fCompressing*/);
+
+ if (pcbSrcActual)
+ *pcbSrcActual = cbSrc - ZStrm.avail_in;
+ if (pcbDstActual)
+ *pcbDstActual = ZStrm.total_out;
+ break;
+#else
+ return VERR_NOT_SUPPORTED;
+#endif
+ }
+
+ case RTZIPTYPE_BZLIB:
+ return VERR_NOT_SUPPORTED;
+
+ default:
+ AssertMsgFailed(("%d\n", enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+ return VINF_SUCCESS;
+}
+RT_EXPORT_SYMBOL(RTZipBlockDecompress);
+